From f9ebd743cc16dfebbe6ec772f75ea616c5466032 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Fri, 13 Aug 2021 21:22:26 +0100 Subject: [PATCH] hide email --- .gitignore | 9 + CONTRIBUTING.md | 238 + LICENSE.md | 32 + README.md | 91 + apps/bochs.ini | 17 + apps/file_manager.ini | 114 + apps/file_manager/commands.cpp | 170 + apps/file_manager/folder.cpp | 597 + apps/file_manager/main.cpp | 573 + apps/file_manager/string.cpp | 201 + apps/file_manager/type_database.cpp | 110 + apps/file_manager/ui.cpp | 1079 + apps/fly.cpp | 942 + apps/fly.ini | 66 + apps/font_book.cpp | 414 + apps/font_book.ini | 14 + apps/gl_test.c | 282 + apps/gl_test.ini | 6 + apps/hello.c | 29 + apps/hello.ini | 5 + apps/image_editor.cpp | 840 + apps/image_editor.ini | 15 + apps/irc_client.cpp | 327 + apps/irc_client.ini | 7 + apps/markdown_viewer.cpp | 500 + apps/markdown_viewer.ini | 11 + apps/posix_launcher.cpp | 311 + apps/posix_launcher.ini | 9 + apps/system_monitor.cpp | 497 + apps/system_monitor.ini | 8 + apps/test.cpp | 180 + apps/test.ini | 8 + apps/text_editor.cpp | 334 + apps/text_editor.ini | 27 + boot/x86/esfs-stage1.s | 178 + boot/x86/esfs-stage2.s | 134 + boot/x86/loader.s | 913 + boot/x86/mbr-emu.s | 144 + boot/x86/mbr.s | 106 + boot/x86/uefi.c | 404 + boot/x86/uefi_loader.s | 156 + boot/x86/vbe.s | 361 + desktop/api.cpp | 1591 + desktop/api.s | 99 + desktop/crt1.c | 12 + desktop/crtglue.c | 5 + desktop/crti.s | 11 + desktop/crtn.s | 7 + desktop/desktop.cpp | 1933 + desktop/docs.md | 458 + desktop/gui.cpp | 7259 ++ desktop/icons.header | 1067 + desktop/list_view.cpp | 2616 + desktop/os.header | 2399 + desktop/posix.cpp | 775 + desktop/prefix.h | 371 + desktop/renderer.cpp | 925 + desktop/styles.header | 113 + desktop/syscall.cpp | 658 + desktop/text.cpp | 4984 + desktop/theme.cpp | 2209 + drivers/acpi.cpp | 1073 + drivers/ahci.cpp | 886 + drivers/bga.cpp | 81 + drivers/esfs2.cpp | 2009 + drivers/ext2.cpp | 650 + drivers/fat.cpp | 514 + drivers/hda.cpp | 520 + drivers/i8254x.cpp | 499 + drivers/ide.cpp | 602 + drivers/iso9660.cpp | 545 + drivers/ntfs.cpp | 1265 + drivers/nvme.cpp | 878 + drivers/pci.cpp | 554 + drivers/ps2.cpp | 566 + drivers/svga.cpp | 285 + drivers/usb.cpp | 340 + drivers/usb_bulk.cpp | 223 + drivers/usb_hid.cpp | 737 + drivers/xhci.cpp | 1282 + kernel/audio.cpp | 0 kernel/cache.cpp | 1220 + kernel/config.ini | 146 + kernel/drivers.cpp | 324 + kernel/elf.cpp | 422 + kernel/files.cpp | 1916 + kernel/graphics.cpp | 562 + kernel/kernel.h | 194 + kernel/main.cpp | 29 + kernel/memory.cpp | 2305 + kernel/module.h | 1014 + kernel/networking.cpp | 2167 + kernel/objects.cpp | 701 + kernel/posix.cpp | 821 + kernel/scheduler.cpp | 1423 + kernel/symbols.cpp | 46 + kernel/synchronisation.cpp | 862 + kernel/syscall.cpp | 2095 + kernel/terminal.cpp | 431 + kernel/windows.cpp | 1407 + kernel/x86_64.cpp | 1166 + kernel/x86_64.h | 33 + kernel/x86_64.s | 1078 + ports/acpica/build.sh | 165 + ports/acpica/include/acapps.h | 343 + ports/acpica/include/acbuffer.h | 363 + ports/acpica/include/acclib.h | 431 + ports/acpica/include/accommon.h | 175 + ports/acpica/include/acconfig.h | 365 + ports/acpica/include/acconvert.h | 312 + ports/acpica/include/acdebug.h | 611 + ports/acpica/include/acdisasm.h | 1343 + ports/acpica/include/acdispat.h | 599 + ports/acpica/include/acevents.h | 495 + ports/acpica/include/acexcep.h | 470 + ports/acpica/include/acglobal.h | 533 + ports/acpica/include/achware.h | 335 + ports/acpica/include/acinterp.h | 861 + ports/acpica/include/aclocal.h | 1651 + ports/acpica/include/acmacros.h | 647 + ports/acpica/include/acnames.h | 204 + ports/acpica/include/acnamesp.h | 691 + ports/acpica/include/acobject.h | 703 + ports/acpica/include/acopcode.h | 441 + ports/acpica/include/acoutput.h | 608 + ports/acpica/include/acparser.h | 476 + ports/acpica/include/acpi.h | 175 + ports/acpica/include/acpiosxf.h | 704 + ports/acpica/include/acpixf.h | 1408 + ports/acpica/include/acpredef.h | 1251 + ports/acpica/include/acresrc.h | 563 + ports/acpica/include/acrestyp.h | 924 + ports/acpica/include/acstruct.h | 377 + ports/acpica/include/actables.h | 390 + ports/acpica/include/actbinfo.h | 411 + ports/acpica/include/actbl.h | 578 + ports/acpica/include/actbl1.h | 2046 + ports/acpica/include/actbl2.h | 2150 + ports/acpica/include/actbl3.h | 906 + ports/acpica/include/actypes.h | 1522 + ports/acpica/include/acutils.h | 1264 + ports/acpica/include/acuuid.h | 203 + ports/acpica/include/amlcode.h | 618 + ports/acpica/include/amlresrc.h | 857 + ports/acpica/include/platform/acenv.h | 504 + ports/acpica/include/platform/acenvex.h | 190 + ports/acpica/include/platform/acessence.h | 11 + ports/acpica/include/platform/acgcc.h | 199 + ports/acpica/include/platform/acgccex.h | 166 + ports/acpica/libacpica.a | Bin 0 -> 10059998 bytes ports/acpica/licensing.txt | 144 + ports/bochs/Makefile.in | 842 + ports/bochs/config.cc | 3426 + ports/bochs/config.h.in | 935 + ports/bochs/configure.in | 3273 + ports/bochs/essence.cc | 478 + ports/bochs/gui_Makefile.in | 443 + ports/bochs/main.cc | 1529 + ports/bochs/plugin.cc | 970 + ports/bochs/plugin.h | 468 + ports/bochs/port.sh | 31 + ports/busybox/config | 1182 + ports/busybox/port.sh | 19 + ports/ffmpeg/port.sh | 21 + ports/freetype/FTL.TXT | 169 + ports/freetype/build.sh | 26 + ports/freetype/patch-ftoption.h | 970 + ports/freetype/patch-ftstdlib.h | 183 + ports/freetype/patch-modules.cfg | 270 + ports/gcc/changes/binutils_bfd_config.bfd | 1479 + ports/gcc/changes/binutils_config.sub | 1855 + ports/gcc/changes/binutils_gas_configure.tgt | 460 + ports/gcc/changes/binutils_ld_configure.tgt | 1130 + ports/gcc/changes/gcc_config.sub | 1860 + ports/gcc/changes/gcc_fixincludes_mkfixinc.sh | 33 + ports/gcc/changes/gcc_gcc_config.gcc | 5493 ++ ports/gcc/changes/gcc_gcc_config_essence.h | 22 + .../gcc_gcc_config_i386_t-x86_64-essence | 2 + ports/gcc/changes/gcc_libgcc_config.host | 1584 + ports/gcc/changes/gcc_libstdc++-v3_configure | 81243 ++++++++++++++++ ports/gcc/hello.c | 7 + ports/gcc/notes.txt | 92 + ports/gcc/port.sh | 214 + ports/harfbuzz/LICENSE | 37 + ports/harfbuzz/build.sh | 90 + ports/harfbuzz/essence-config.h | 74 + ports/md4c/LICENSE.md | 22 + ports/md4c/md4c.c | 6395 ++ ports/md4c/md4c.h | 405 + .../mesa/changes/include_c11_threads_posix.h | 396 + ports/mesa/changes/meson.build | 1869 + .../src_gallium_targets_osmesa_meson.build | 86 + ports/mesa/changes/src_util_anon_file.c | 167 + ports/mesa/changes/src_util_detect_os.h | 136 + ports/mesa/changes/src_util_os_misc.c | 235 + ports/mesa/changes/src_util_u_thread.h | 256 + ports/mesa/port.sh | 47 + ports/musl/COPYRIGHT | 193 + ports/musl/build.sh | 35 + ports/musl/changes/arch_x86_64_syscall_arch.h | 49 + ports/musl/changes/config.mak | 30 + ports/musl/changes/dist_config.mak | 36 + ports/musl/changes/src_env___init_tls.c | 157 + ports/musl/changes/src_process_x86_64_vfork.s | 11 + .../musl/changes/src_signal_x86_64_restore.s | 8 + .../changes/src_thread_x86_64___unmapself.s | 13 + ports/musl/changes/src_thread_x86_64_clone.s | 32 + .../changes/src_thread_x86_64_syscall_cp.s | 37 + ports/musl/empty.a | 1 + ports/musl/libc.a | Bin 0 -> 12091854 bytes ports/musl/obj_bits/bits/alltypes.h | 409 + ports/musl/obj_bits/bits/syscall.h | 695 + ports/nasm/port.sh | 18 + res/Cursors.png | Bin 0 -> 30260 bytes res/Fly Assets/R07_12.DAT | Bin 0 -> 1261 bytes res/Fly Assets/R07_13.DAT | Bin 0 -> 775 bytes res/Fly Assets/R07_14.DAT | Bin 0 -> 1198 bytes res/Fly Assets/R07_16.DAT | Bin 0 -> 550 bytes res/Fly Assets/R07_17.DAT | Bin 0 -> 1432 bytes res/Fly Assets/R07_18.DAT | Bin 0 -> 1729 bytes res/Fly Assets/R08_12.DAT | Bin 0 -> 766 bytes res/Fly Assets/R08_13.DAT | Bin 0 -> 838 bytes res/Fly Assets/R08_14.DAT | Bin 0 -> 1009 bytes res/Fly Assets/R08_16.DAT | Bin 0 -> 1738 bytes res/Fly Assets/R08_17.DAT | Bin 0 -> 1324 bytes res/Fly Assets/R08_18.DAT | Bin 0 -> 883 bytes res/Fly Assets/R09_10.DAT | Bin 0 -> 1180 bytes res/Fly Assets/R09_11.DAT | Bin 0 -> 1261 bytes res/Fly Assets/R09_12.DAT | Bin 0 -> 1504 bytes res/Fly Assets/R09_13.DAT | Bin 0 -> 811 bytes res/Fly Assets/R09_14.DAT | Bin 0 -> 478 bytes res/Fly Assets/R09_15.DAT | Bin 0 -> 955 bytes res/Fly Assets/R09_16.DAT | Bin 0 -> 1153 bytes res/Fly Assets/R09_17.DAT | Bin 0 -> 1018 bytes res/Fly Assets/R09_18.DAT | Bin 0 -> 1432 bytes res/Fly Assets/R10_09.DAT | Bin 0 -> 2026 bytes res/Fly Assets/R10_10.DAT | Bin 0 -> 1666 bytes res/Fly Assets/R10_11.DAT | Bin 0 -> 973 bytes res/Fly Assets/R10_12.DAT | Bin 0 -> 2035 bytes res/Fly Assets/R10_13.DAT | Bin 0 -> 649 bytes res/Fly Assets/R10_14.DAT | Bin 0 -> 568 bytes res/Fly Assets/R10_15.DAT | Bin 0 -> 1423 bytes res/Fly Assets/R10_16.DAT | Bin 0 -> 775 bytes res/Fly Assets/R11_09.DAT | Bin 0 -> 1594 bytes res/Fly Assets/R11_10.DAT | Bin 0 -> 2260 bytes res/Fly Assets/R11_11.DAT | Bin 0 -> 946 bytes res/Fly Assets/R11_13.DAT | Bin 0 -> 577 bytes res/Fly Assets/R11_14.DAT | Bin 0 -> 559 bytes res/Fly Assets/R11_15.DAT | Bin 0 -> 550 bytes res/Fly Assets/R12_09.DAT | Bin 0 -> 28 bytes res/Fly Assets/R12_10.DAT | Bin 0 -> 1108 bytes res/Fly Assets/R12_11.DAT | Bin 0 -> 595 bytes res/Fly Assets/bgm1.mid | Bin 0 -> 22884 bytes res/Fly Assets/bgm2.mid | Bin 0 -> 26455 bytes res/Fly Assets/bgm3.mid | Bin 0 -> 30205 bytes res/Fly Assets/block.png | Bin 0 -> 173 bytes res/Fly Assets/block2.png | Bin 0 -> 368 bytes res/Fly Assets/check1.png | Bin 0 -> 257 bytes res/Fly Assets/check2.png | Bin 0 -> 264 bytes res/Fly Assets/controls.png | Bin 0 -> 1184 bytes res/Fly Assets/dash_msg.png | Bin 0 -> 759 bytes res/Fly Assets/end1.png | Bin 0 -> 1152 bytes res/Fly Assets/end2.png | Bin 0 -> 1278 bytes res/Fly Assets/end3.png | Bin 0 -> 629 bytes res/Fly Assets/key.png | Bin 0 -> 278 bytes res/Fly Assets/key_msg.png | Bin 0 -> 740 bytes res/Fly Assets/lock.png | Bin 0 -> 268 bytes res/Fly Assets/moveblock.png | Bin 0 -> 375 bytes res/Fly Assets/numbers.png | Bin 0 -> 656 bytes res/Fly Assets/player.png | Bin 0 -> 427 bytes res/Fly Assets/powerup.png | Bin 0 -> 256 bytes res/Fly Assets/star.png | Bin 0 -> 283 bytes res/Fly Assets/white.png | Bin 0 -> 149 bytes res/Fonts/Hack Bold Italic.ttf | Bin 0 -> 322288 bytes res/Fonts/Hack Bold.ttf | Bin 0 -> 317628 bytes res/Fonts/Hack License.md | 45 + res/Fonts/Hack Regular Italic.ttf | Bin 0 -> 316156 bytes res/Fonts/Hack Regular.ttf | Bin 0 -> 309408 bytes res/Fonts/Inter Black Italic.otf | Bin 0 -> 267084 bytes res/Fonts/Inter Black.otf | Bin 0 -> 259712 bytes res/Fonts/Inter Bold Italic.otf | Bin 0 -> 272808 bytes res/Fonts/Inter Bold.otf | Bin 0 -> 265580 bytes res/Fonts/Inter Extra Bold Italic.otf | Bin 0 -> 272652 bytes res/Fonts/Inter Extra Bold.otf | Bin 0 -> 266104 bytes res/Fonts/Inter Extra Light Italic.otf | Bin 0 -> 270908 bytes res/Fonts/Inter Extra Light.otf | Bin 0 -> 259968 bytes res/Fonts/Inter License.txt | 94 + res/Fonts/Inter Light Italic.otf | Bin 0 -> 270528 bytes res/Fonts/Inter Light.otf | Bin 0 -> 259636 bytes res/Fonts/Inter Medium Italic.otf | Bin 0 -> 272000 bytes res/Fonts/Inter Medium.otf | Bin 0 -> 264176 bytes res/Fonts/Inter Regular Italic.otf | Bin 0 -> 265960 bytes res/Fonts/Inter Regular.otf | Bin 0 -> 254772 bytes res/Fonts/Inter Semi Bold Italic.otf | Bin 0 -> 272484 bytes res/Fonts/Inter Semi Bold.otf | Bin 0 -> 265000 bytes res/Fonts/Inter Thin Italic.otf | Bin 0 -> 264960 bytes res/Fonts/Inter Thin.otf | Bin 0 -> 253124 bytes res/Media/A Study in Scarlet.txt | 4633 + res/Media/Aurora.jpg | Bin 0 -> 882738 bytes res/Media/Blue.jpg | Bin 0 -> 436702 bytes res/Media/Desert.jpg | Bin 0 -> 265985 bytes res/Media/Flower.jpg | Bin 0 -> 437817 bytes res/Media/Jellyfish.jpg | Bin 0 -> 244149 bytes res/Media/Licenses.txt | 45 + res/Media/Paint.jpg | Bin 0 -> 511763 bytes res/Media/Shutdown Sound.wav | Bin 0 -> 70954 bytes res/Media/Startup Sound.wav | Bin 0 -> 728320 bytes res/Media/Waves.jpg | Bin 0 -> 540294 bytes res/System Configuration Template.ini | 19 + res/Theme Source.dat | Bin 0 -> 44613 bytes res/Themes/Theme.dat | Bin 0 -> 56696 bytes res/Themes/elementary Icons License.txt | 674 + res/Themes/elementary Icons.dat | Bin 0 -> 8573438 bytes shared/arena.cpp | 103 + shared/array.cpp | 177 + shared/avl_tree.cpp | 401 + shared/bitset.cpp | 182 + shared/common.cpp | 2615 + shared/hash.cpp | 110 + shared/hash_table.cpp | 520 + shared/heap.cpp | 542 + shared/ini.h | 75 + shared/linked_list.cpp | 258 + shared/math.cpp | 1835 + shared/png_decoder.cpp | 485 + shared/range_set.cpp | 404 + shared/stb_image.h | 7193 ++ shared/stb_sprintf.h | 1834 + shared/strings.cpp | 216 + shared/unicode.cpp | 1116 + shared/vga_font.cpp | 40 + start.sh | 47 + util/api_table.ini | 417 + util/build.c | 1491 + util/build_common.h | 407 + util/build_core.c | 1597 + util/config_editor.c | 119 + util/designer/designer.c | 5541 ++ util/designer/designer.rf | 430 + util/designer/designer_luigi.c | 8 + util/designer/reflect.h | 652 + util/designer/reflect_gen.c | 531 + util/esfs2.h | 1178 + util/header_generator.c | 1270 + util/linker/api64.ld | 23 + util/linker/kernel32.ld | 32 + util/linker/kernel64.ld | 24 + util/linker/userland32.ld | 24 + util/linker/userland64.ld | 23 + util/luigi.h | 5046 + util/nanosvg.h | 3018 + util/render_svg.c | 281 + util/stb_ds.h | 1736 + util/stb_truetype.h | 5011 + util/test.txt | 3 + util/w32/build.bat | 16 + util/w32/embed.cpp | 51 + util/w32/platform.cpp | 924 + 358 files changed, 278369 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 apps/bochs.ini create mode 100644 apps/file_manager.ini create mode 100644 apps/file_manager/commands.cpp create mode 100644 apps/file_manager/folder.cpp create mode 100644 apps/file_manager/main.cpp create mode 100644 apps/file_manager/string.cpp create mode 100644 apps/file_manager/type_database.cpp create mode 100644 apps/file_manager/ui.cpp create mode 100644 apps/fly.cpp create mode 100644 apps/fly.ini create mode 100644 apps/font_book.cpp create mode 100644 apps/font_book.ini create mode 100644 apps/gl_test.c create mode 100644 apps/gl_test.ini create mode 100644 apps/hello.c create mode 100644 apps/hello.ini create mode 100644 apps/image_editor.cpp create mode 100644 apps/image_editor.ini create mode 100644 apps/irc_client.cpp create mode 100644 apps/irc_client.ini create mode 100644 apps/markdown_viewer.cpp create mode 100644 apps/markdown_viewer.ini create mode 100644 apps/posix_launcher.cpp create mode 100644 apps/posix_launcher.ini create mode 100644 apps/system_monitor.cpp create mode 100644 apps/system_monitor.ini create mode 100644 apps/test.cpp create mode 100644 apps/test.ini create mode 100644 apps/text_editor.cpp create mode 100644 apps/text_editor.ini create mode 100644 boot/x86/esfs-stage1.s create mode 100644 boot/x86/esfs-stage2.s create mode 100644 boot/x86/loader.s create mode 100644 boot/x86/mbr-emu.s create mode 100644 boot/x86/mbr.s create mode 100644 boot/x86/uefi.c create mode 100644 boot/x86/uefi_loader.s create mode 100644 boot/x86/vbe.s create mode 100644 desktop/api.cpp create mode 100644 desktop/api.s create mode 100644 desktop/crt1.c create mode 100644 desktop/crtglue.c create mode 100644 desktop/crti.s create mode 100644 desktop/crtn.s create mode 100644 desktop/desktop.cpp create mode 100644 desktop/docs.md create mode 100644 desktop/gui.cpp create mode 100644 desktop/icons.header create mode 100644 desktop/list_view.cpp create mode 100644 desktop/os.header create mode 100644 desktop/posix.cpp create mode 100644 desktop/prefix.h create mode 100644 desktop/renderer.cpp create mode 100644 desktop/styles.header create mode 100644 desktop/syscall.cpp create mode 100644 desktop/text.cpp create mode 100644 desktop/theme.cpp create mode 100644 drivers/acpi.cpp create mode 100644 drivers/ahci.cpp create mode 100644 drivers/bga.cpp create mode 100644 drivers/esfs2.cpp create mode 100644 drivers/ext2.cpp create mode 100644 drivers/fat.cpp create mode 100644 drivers/hda.cpp create mode 100644 drivers/i8254x.cpp create mode 100644 drivers/ide.cpp create mode 100644 drivers/iso9660.cpp create mode 100644 drivers/ntfs.cpp create mode 100644 drivers/nvme.cpp create mode 100644 drivers/pci.cpp create mode 100644 drivers/ps2.cpp create mode 100644 drivers/svga.cpp create mode 100644 drivers/usb.cpp create mode 100644 drivers/usb_bulk.cpp create mode 100644 drivers/usb_hid.cpp create mode 100644 drivers/xhci.cpp create mode 100644 kernel/audio.cpp create mode 100644 kernel/cache.cpp create mode 100644 kernel/config.ini create mode 100644 kernel/drivers.cpp create mode 100644 kernel/elf.cpp create mode 100644 kernel/files.cpp create mode 100644 kernel/graphics.cpp create mode 100644 kernel/kernel.h create mode 100644 kernel/main.cpp create mode 100644 kernel/memory.cpp create mode 100644 kernel/module.h create mode 100644 kernel/networking.cpp create mode 100644 kernel/objects.cpp create mode 100644 kernel/posix.cpp create mode 100644 kernel/scheduler.cpp create mode 100644 kernel/symbols.cpp create mode 100644 kernel/synchronisation.cpp create mode 100644 kernel/syscall.cpp create mode 100644 kernel/terminal.cpp create mode 100644 kernel/windows.cpp create mode 100644 kernel/x86_64.cpp create mode 100644 kernel/x86_64.h create mode 100644 kernel/x86_64.s create mode 100755 ports/acpica/build.sh create mode 100644 ports/acpica/include/acapps.h create mode 100644 ports/acpica/include/acbuffer.h create mode 100644 ports/acpica/include/acclib.h create mode 100644 ports/acpica/include/accommon.h create mode 100644 ports/acpica/include/acconfig.h create mode 100644 ports/acpica/include/acconvert.h create mode 100644 ports/acpica/include/acdebug.h create mode 100644 ports/acpica/include/acdisasm.h create mode 100644 ports/acpica/include/acdispat.h create mode 100644 ports/acpica/include/acevents.h create mode 100644 ports/acpica/include/acexcep.h create mode 100644 ports/acpica/include/acglobal.h create mode 100644 ports/acpica/include/achware.h create mode 100644 ports/acpica/include/acinterp.h create mode 100644 ports/acpica/include/aclocal.h create mode 100644 ports/acpica/include/acmacros.h create mode 100644 ports/acpica/include/acnames.h create mode 100644 ports/acpica/include/acnamesp.h create mode 100644 ports/acpica/include/acobject.h create mode 100644 ports/acpica/include/acopcode.h create mode 100644 ports/acpica/include/acoutput.h create mode 100644 ports/acpica/include/acparser.h create mode 100644 ports/acpica/include/acpi.h create mode 100644 ports/acpica/include/acpiosxf.h create mode 100644 ports/acpica/include/acpixf.h create mode 100644 ports/acpica/include/acpredef.h create mode 100644 ports/acpica/include/acresrc.h create mode 100644 ports/acpica/include/acrestyp.h create mode 100644 ports/acpica/include/acstruct.h create mode 100644 ports/acpica/include/actables.h create mode 100644 ports/acpica/include/actbinfo.h create mode 100644 ports/acpica/include/actbl.h create mode 100644 ports/acpica/include/actbl1.h create mode 100644 ports/acpica/include/actbl2.h create mode 100644 ports/acpica/include/actbl3.h create mode 100644 ports/acpica/include/actypes.h create mode 100644 ports/acpica/include/acutils.h create mode 100644 ports/acpica/include/acuuid.h create mode 100644 ports/acpica/include/amlcode.h create mode 100644 ports/acpica/include/amlresrc.h create mode 100644 ports/acpica/include/platform/acenv.h create mode 100644 ports/acpica/include/platform/acenvex.h create mode 100644 ports/acpica/include/platform/acessence.h create mode 100644 ports/acpica/include/platform/acgcc.h create mode 100644 ports/acpica/include/platform/acgccex.h create mode 100644 ports/acpica/libacpica.a create mode 100644 ports/acpica/licensing.txt create mode 100644 ports/bochs/Makefile.in create mode 100644 ports/bochs/config.cc create mode 100644 ports/bochs/config.h.in create mode 100644 ports/bochs/configure.in create mode 100644 ports/bochs/essence.cc create mode 100644 ports/bochs/gui_Makefile.in create mode 100644 ports/bochs/main.cc create mode 100644 ports/bochs/plugin.cc create mode 100644 ports/bochs/plugin.h create mode 100755 ports/bochs/port.sh create mode 100644 ports/busybox/config create mode 100755 ports/busybox/port.sh create mode 100755 ports/ffmpeg/port.sh create mode 100644 ports/freetype/FTL.TXT create mode 100755 ports/freetype/build.sh create mode 100644 ports/freetype/patch-ftoption.h create mode 100644 ports/freetype/patch-ftstdlib.h create mode 100644 ports/freetype/patch-modules.cfg create mode 100644 ports/gcc/changes/binutils_bfd_config.bfd create mode 100755 ports/gcc/changes/binutils_config.sub create mode 100644 ports/gcc/changes/binutils_gas_configure.tgt create mode 100644 ports/gcc/changes/binutils_ld_configure.tgt create mode 100755 ports/gcc/changes/gcc_config.sub create mode 100755 ports/gcc/changes/gcc_fixincludes_mkfixinc.sh create mode 100644 ports/gcc/changes/gcc_gcc_config.gcc create mode 100644 ports/gcc/changes/gcc_gcc_config_essence.h create mode 100644 ports/gcc/changes/gcc_gcc_config_i386_t-x86_64-essence create mode 100644 ports/gcc/changes/gcc_libgcc_config.host create mode 100755 ports/gcc/changes/gcc_libstdc++-v3_configure create mode 100644 ports/gcc/hello.c create mode 100644 ports/gcc/notes.txt create mode 100755 ports/gcc/port.sh create mode 100644 ports/harfbuzz/LICENSE create mode 100755 ports/harfbuzz/build.sh create mode 100644 ports/harfbuzz/essence-config.h create mode 100644 ports/md4c/LICENSE.md create mode 100644 ports/md4c/md4c.c create mode 100644 ports/md4c/md4c.h create mode 100644 ports/mesa/changes/include_c11_threads_posix.h create mode 100644 ports/mesa/changes/meson.build create mode 100644 ports/mesa/changes/src_gallium_targets_osmesa_meson.build create mode 100644 ports/mesa/changes/src_util_anon_file.c create mode 100644 ports/mesa/changes/src_util_detect_os.h create mode 100644 ports/mesa/changes/src_util_os_misc.c create mode 100644 ports/mesa/changes/src_util_u_thread.h create mode 100755 ports/mesa/port.sh create mode 100644 ports/musl/COPYRIGHT create mode 100755 ports/musl/build.sh create mode 100644 ports/musl/changes/arch_x86_64_syscall_arch.h create mode 100644 ports/musl/changes/config.mak create mode 100644 ports/musl/changes/dist_config.mak create mode 100644 ports/musl/changes/src_env___init_tls.c create mode 100644 ports/musl/changes/src_process_x86_64_vfork.s create mode 100644 ports/musl/changes/src_signal_x86_64_restore.s create mode 100644 ports/musl/changes/src_thread_x86_64___unmapself.s create mode 100644 ports/musl/changes/src_thread_x86_64_clone.s create mode 100644 ports/musl/changes/src_thread_x86_64_syscall_cp.s create mode 100644 ports/musl/empty.a create mode 100644 ports/musl/libc.a create mode 100644 ports/musl/obj_bits/bits/alltypes.h create mode 100644 ports/musl/obj_bits/bits/syscall.h create mode 100755 ports/nasm/port.sh create mode 100644 res/Cursors.png create mode 100644 res/Fly Assets/R07_12.DAT create mode 100644 res/Fly Assets/R07_13.DAT create mode 100644 res/Fly Assets/R07_14.DAT create mode 100644 res/Fly Assets/R07_16.DAT create mode 100644 res/Fly Assets/R07_17.DAT create mode 100644 res/Fly Assets/R07_18.DAT create mode 100644 res/Fly Assets/R08_12.DAT create mode 100644 res/Fly Assets/R08_13.DAT create mode 100644 res/Fly Assets/R08_14.DAT create mode 100644 res/Fly Assets/R08_16.DAT create mode 100644 res/Fly Assets/R08_17.DAT create mode 100644 res/Fly Assets/R08_18.DAT create mode 100644 res/Fly Assets/R09_10.DAT create mode 100644 res/Fly Assets/R09_11.DAT create mode 100644 res/Fly Assets/R09_12.DAT create mode 100644 res/Fly Assets/R09_13.DAT create mode 100644 res/Fly Assets/R09_14.DAT create mode 100644 res/Fly Assets/R09_15.DAT create mode 100644 res/Fly Assets/R09_16.DAT create mode 100644 res/Fly Assets/R09_17.DAT create mode 100644 res/Fly Assets/R09_18.DAT create mode 100644 res/Fly Assets/R10_09.DAT create mode 100644 res/Fly Assets/R10_10.DAT create mode 100644 res/Fly Assets/R10_11.DAT create mode 100644 res/Fly Assets/R10_12.DAT create mode 100644 res/Fly Assets/R10_13.DAT create mode 100644 res/Fly Assets/R10_14.DAT create mode 100644 res/Fly Assets/R10_15.DAT create mode 100644 res/Fly Assets/R10_16.DAT create mode 100644 res/Fly Assets/R11_09.DAT create mode 100644 res/Fly Assets/R11_10.DAT create mode 100644 res/Fly Assets/R11_11.DAT create mode 100644 res/Fly Assets/R11_13.DAT create mode 100644 res/Fly Assets/R11_14.DAT create mode 100644 res/Fly Assets/R11_15.DAT create mode 100644 res/Fly Assets/R12_09.DAT create mode 100644 res/Fly Assets/R12_10.DAT create mode 100644 res/Fly Assets/R12_11.DAT create mode 100644 res/Fly Assets/bgm1.mid create mode 100644 res/Fly Assets/bgm2.mid create mode 100644 res/Fly Assets/bgm3.mid create mode 100644 res/Fly Assets/block.png create mode 100644 res/Fly Assets/block2.png create mode 100644 res/Fly Assets/check1.png create mode 100644 res/Fly Assets/check2.png create mode 100644 res/Fly Assets/controls.png create mode 100644 res/Fly Assets/dash_msg.png create mode 100644 res/Fly Assets/end1.png create mode 100644 res/Fly Assets/end2.png create mode 100644 res/Fly Assets/end3.png create mode 100644 res/Fly Assets/key.png create mode 100644 res/Fly Assets/key_msg.png create mode 100644 res/Fly Assets/lock.png create mode 100644 res/Fly Assets/moveblock.png create mode 100644 res/Fly Assets/numbers.png create mode 100644 res/Fly Assets/player.png create mode 100644 res/Fly Assets/powerup.png create mode 100644 res/Fly Assets/star.png create mode 100644 res/Fly Assets/white.png create mode 100644 res/Fonts/Hack Bold Italic.ttf create mode 100644 res/Fonts/Hack Bold.ttf create mode 100644 res/Fonts/Hack License.md create mode 100644 res/Fonts/Hack Regular Italic.ttf create mode 100644 res/Fonts/Hack Regular.ttf create mode 100644 res/Fonts/Inter Black Italic.otf create mode 100644 res/Fonts/Inter Black.otf create mode 100644 res/Fonts/Inter Bold Italic.otf create mode 100644 res/Fonts/Inter Bold.otf create mode 100644 res/Fonts/Inter Extra Bold Italic.otf create mode 100644 res/Fonts/Inter Extra Bold.otf create mode 100644 res/Fonts/Inter Extra Light Italic.otf create mode 100644 res/Fonts/Inter Extra Light.otf create mode 100644 res/Fonts/Inter License.txt create mode 100644 res/Fonts/Inter Light Italic.otf create mode 100644 res/Fonts/Inter Light.otf create mode 100644 res/Fonts/Inter Medium Italic.otf create mode 100644 res/Fonts/Inter Medium.otf create mode 100644 res/Fonts/Inter Regular Italic.otf create mode 100644 res/Fonts/Inter Regular.otf create mode 100644 res/Fonts/Inter Semi Bold Italic.otf create mode 100644 res/Fonts/Inter Semi Bold.otf create mode 100644 res/Fonts/Inter Thin Italic.otf create mode 100644 res/Fonts/Inter Thin.otf create mode 100644 res/Media/A Study in Scarlet.txt create mode 100644 res/Media/Aurora.jpg create mode 100644 res/Media/Blue.jpg create mode 100644 res/Media/Desert.jpg create mode 100644 res/Media/Flower.jpg create mode 100644 res/Media/Jellyfish.jpg create mode 100644 res/Media/Licenses.txt create mode 100755 res/Media/Paint.jpg create mode 100644 res/Media/Shutdown Sound.wav create mode 100644 res/Media/Startup Sound.wav create mode 100644 res/Media/Waves.jpg create mode 100644 res/System Configuration Template.ini create mode 100644 res/Theme Source.dat create mode 100644 res/Themes/Theme.dat create mode 100644 res/Themes/elementary Icons License.txt create mode 100644 res/Themes/elementary Icons.dat create mode 100644 shared/arena.cpp create mode 100644 shared/array.cpp create mode 100644 shared/avl_tree.cpp create mode 100644 shared/bitset.cpp create mode 100644 shared/common.cpp create mode 100644 shared/hash.cpp create mode 100644 shared/hash_table.cpp create mode 100644 shared/heap.cpp create mode 100644 shared/ini.h create mode 100644 shared/linked_list.cpp create mode 100644 shared/math.cpp create mode 100644 shared/png_decoder.cpp create mode 100644 shared/range_set.cpp create mode 100644 shared/stb_image.h create mode 100644 shared/stb_sprintf.h create mode 100644 shared/strings.cpp create mode 100644 shared/unicode.cpp create mode 100644 shared/vga_font.cpp create mode 100755 start.sh create mode 100644 util/api_table.ini create mode 100644 util/build.c create mode 100644 util/build_common.h create mode 100644 util/build_core.c create mode 100644 util/config_editor.c create mode 100644 util/designer/designer.c create mode 100644 util/designer/designer.rf create mode 100644 util/designer/designer_luigi.c create mode 100644 util/designer/reflect.h create mode 100644 util/designer/reflect_gen.c create mode 100644 util/esfs2.h create mode 100644 util/header_generator.c create mode 100644 util/linker/api64.ld create mode 100644 util/linker/kernel32.ld create mode 100644 util/linker/kernel64.ld create mode 100644 util/linker/userland32.ld create mode 100644 util/linker/userland64.ld create mode 100644 util/luigi.h create mode 100644 util/nanosvg.h create mode 100644 util/render_svg.c create mode 100644 util/stb_ds.h create mode 100644 util/stb_truetype.h create mode 100644 util/test.txt create mode 100644 util/w32/build.bat create mode 100644 util/w32/embed.cpp create mode 100644 util/w32/platform.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24df049 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.swp +*.swo +tags +.project.gf + +bin/ +cross/ +old/ +root/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a3366da --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,238 @@ +# Contributing Guidelines + +## Map + +- `apps/` Builtin applications. +- `boot/` Contains files for booting the operating system. + - `x86/` ...on x86. + - `esfs-stage1.s` Loads `loader.s` from the start of a EsFS volume and passes control to it. + - `esfs-stage2.s` Provides basic read-only EsFS functions for `loader.s`. + - `loader.s` Loads the kernel and passes control to it. + - `mbr.s` Finds and loads a bootable partition. + - `uefi.c` UEFI bootloader first stage. + - `uefi_loader.s` UEFI bootloader second stage. + - `vbe.s` Sets the VBE graphics mode. +- `desktop/` Contains files for Desktop, which provides both the desktop environment, and a layer between applications and the kernel. + - `api.cpp` API initialisation and internal messaging. + - `api.s` API functions that must be implemented in assembly. + - `calculator.cpp` Evaluates basic math expressions. + - `crti.s, ctrn.s` Global constructors and destructors setup. + - `cstdlib.cpp` Provides the system call interface for the POSIX subsystem. + - `desktop.cpp` Desktop. Manages windows and the taskbar. + - `glue.cpp` Entry point for applications using the POSIX subsystem. + - `gui.cpp` The GUI. + - `icons.header` A list of available icons. + - `list_view.cpp` A list view control for the GUI. + - `os.header` The header file containing the API's definitions. + - `prefix.h` The header file prefix for C/C++. + - `renderer.cpp` Provides visual style management and software rendering. + - `renderer2.cpp` Vector graphics rendering. + - `start.cpp` Entry point for all applications. + - `syscall.cpp` Kernel system call wrappers. + - `text.cpp` Text rendering and text-based GUI elements. +- `drivers/` Kernel drivers. + - `acpi.cpp` A layer between the kernel and ACPICA. Also starts application processors on SMP systems. + - `ata.cpp` A ATA/IDE driver. + - `esfs2.cpp` The EssenceFS filesystem driver. + - `fat.cpp` The FAT12 filesystem driver. + - `hda.cpp` Intel HD Audio driver. + - `pci.cpp` Finds devices on the PCI bus. + - `ps2.cpp` A driver for PS/2 keyboard and mice. + - `vbe.cpp` Basic VBE SVGA driver. + - `vga.cpp` Basic VGA driver. +- `kernel/` The kernel and its drivers. + - `audio.cpp` Audio system. + - `config.mtsrc` System configuration. Describes all the modules that should be built, and when they should be loaded. + - `devices.cpp` The device and IO manager. + - `elf.cpp` Parses and loads ELF executables and kernel modules. + - `graphics.cpp` Graphics system. Mostly deprecated. + - `kernel.h` Internal kernel definitions. Includes all other source files in the kernel. + - `main.cpp` Kernel initilisation and shutdown. + - `memory.cpp` Physical, virtual and shared memory management. + - `module.h` Kernel API available to driver modules. + - `objects.cpp` Manages object and handles shared between the kernel and applications. + - `posix.cpp` The (optional) POSIX subsystem. + - `scheduler.cpp` A scheduler, and manager of threads and processes. + - `symbols.cpp` Locating kernel symbols. + - `synchronisation.cpp` Defines synchronisation primitives. Closely linked with the scheduler. + - `syscall.cpp` Defers system calls to other parts of the kernel. + - `terminal.cpp` Kernel debugging and serial output. + - `vfs.cpp` The virtual filesystem. + - `windows.cpp` The window manager. Passes messages from HID devices to applications. + - `x86_64.cpp` Code for the x64 architecture. + - `x86_64.h` Definitions specific to the x64 architecture. + - `x86_64.s` Assembly code for the x64 architecture. +- `ports/` A mess of ported applications. Enter with caution. +- `res/` Resources, such as fonts and visual styles. + - `Fonts` Fonts used by the GUI. + - `Icons` Icon packs used by the GUI. + - `Sample Images` Sample images. + - `Themes` Themes for the user interface.. +- `shared/` Shared code between the componenets of the operating system. + - `arena.cpp` Fixed-size allocations. + - `avl_tree.cpp` Balanced binary tree, and maps. + - `bitset.cpp` Managing sparse bitsets. + - `common.cpp` Common functions. + - `format_string.cpp` Locale-dependent text formatter. + - `hash.cpp` Hash functions. + - `heap.cpp` Heap allocator. + - `linked_list.cpp` Doubly-linked lists. + - `stb_ds.h`, `stb_image.h`, `stb_sprintf.h` STB libraries. + - `style_parser.cpp` Parsing visual style specifiers. + - `unicode.cpp` Functions for managing Unicode and UTF-8 strings. + - `vga_font.cpp` A fallback bitmap font. +- `util/` Utilities for building the operating system. + - `build.c` The build system. + - `esfs2.h` A version of EssenceFS for use on linux from the command line. + - `header_generator.c` Language independent header generation. + - `render_svg.c` Renders SVG icons. + +## Code Style + +Functions and structures names use `PascalCase`. +Variables use `camelCase`, while constants and macros use `SCREAMING_SNAKE_CASE`. + +Tabs are `\t`, and are 8 characters in size. + +Braces are placed at the end of the line: + + if (a > b) { + ... + } + +Blocks are always surrounded by newlines, and always have braces. + + int x = 5; + + if (x < 6) { + x++; // Postfix operators are preferred. + } + +Exception: If there are lot of short, linked blocks, then they may be written like this- + + if (width == DIMENSION_PUSH) { bool a = grid->widths[i] == DIMENSION_PUSH; grid->widths[i] = DIMENSION_PUSH; if (!a) pushH++; } + else if (grid->widths[i] < width && grid->widths[i] != DIMENSION_PUSH) grid->widths[i] = width; + if (height == DIMENSION_PUSH) { bool a = grid->heights[j] == DIMENSION_PUSH; grid->heights[j] = DIMENSION_PUSH; if (!a) pushV++; } + else if (grid->heights[j] < height && grid->heights[j] != DIMENSION_PUSH) grid->heights[j] = height; + +Function names are always descriptive, and use prepositions and conjuctions if neccesary. + + EsFileReadAll // Symbols provided by the API are prefixed with Es-. + EsDrawSurface + EsMemoryZero + +Variable names are usually descriptive, but sometimes shortened names are used for short-lived variables. + + EsMessage m = {}; + m.type = OS_MESSAGE_MEASURE; + EsMessagePost(&m); + +Operators are padded with spaces on either side. + + bounds.left = (grid->bounds.left + grid->bounds.right) / 2 - 4; + +A space should be placed between a cast and its expression. + + int x = (float) y; + +Although the operating system is written in C++, most C++ features are avoided. +However, the kernel uses a lot of member functions. + + struct Window { + void Update(bool fromUser); + void SetCursorStyle(OSCursorStyle style); + void NeedWMTimer(int hz); + void Destroy(); + bool Move(OSRectangle &newBounds); + void ClearImage(); + + Mutex mutex; // Mutex for drawing to the window. Also needed when moving the window. + Surface *surface; + OSPoint position; + size_t width, height; + ... + } + +Default arguments often provided as functions grow over time. + +There is no limit on function size. However, you should avoid regularly exceeding 120 columns. + +Pointers are declared like this: `Type *name;`. + +Identifiers may be prefixed with `i`, `e` or `c` to indicate inclusive, exclusive or C-style-zero-terminated-string respectively. + +## Kernel and Driver Development + +See `module.h` for definitions available to driver developers. See `drivers/fat.cpp` and `drivers/ata.cpp` for simple examples of the driver API. + +The following subroutines may be of interest: + + void KWaitMicroseconds(uint64_t mcs); // Spin until a given number of microseconds have elapsed. + void EsPrint(const char *format, ...); // Print a message to serial output. (Ctrl+Alt+3 in Qemu) + void KernelPanic(const char *format, ...); // Print a message and halt the OS. + Defer(); // Defer a statement. Deferred statements will be executed in reverse order when they go out of scope. + size_t EsCStringLength(const char *string); // Get the length of a zero-terminated string. + void EsMemoryCopy(void *destination, const void *source, size_t bytes); // Copy memory forwards. + void EsMemoryZero(void *destination, size_t bytes); // Zero a buffer. + void EsMemoryMove(void *start, void *end, intptr_t amount, bool zeroEmptySpace); // Move a memory region left (amount < 0) or right (amount > 0). + int EsMemoryCopy(const void *a, const void *b, size_t bytes); // Compare two memory regions. Returns 0 if equal. + uint8_t EsSumBytes(uint8_t *source, size_t bytes); // Calculate the 8-bit sum of the bytes in a buffer. + size_t EsStringFormat(char *buffer, size_t bufferLength, const char *format, ...); // Format a string. Returns the length. + uint8_t EsGetRandomByte(); // Get a non-secure random byte. + void EsSort(void *base, size_t count, size_t size, int (*compare)(const void *, const void *, void *), void *callbackArgument); // Sort an array of count items of size size. + uint32_t CalculateCRC32(void *buffer, size_t length); // Calculate the CRC32 checksum of a buffer. + void ProcessorEnableInterrupts(); // Enable interrupts. + void ProcessorDisableInterrupts(); // Disable interrupts. Critical interrupts, such as TLB shootdown IPIs, will remain enabled. + void ProcessorOut(uint16_t port, uint_t value); // Write to an IO port. + uint_t ProcessorIn(uint16_t port); // Read from an IO port. + uint64_t ProcessorReadTimeStamp(); // Read the time stamp in ticks. acpi.timestampTicksPerMs gives the number of ticks per millisecond. + bool KRegisterIRQ(uintptr_t interruptIndex, IRQHandler handler, void *context, const char *cOwner); // Register an IRQ handler. Returns false if the IRQ could not be registered. The handler should return false if its devices was not responsible for the IRQ. + void *MMMapPhysical(MMSpace *space /* = kernelMMSpace */, uintptr_t offset, size_t bytes); // Memory mapped IO. + void *EsHeapAllocate(size_t size, bool zero); // Allocate memory from the heap. + void EsHeapFree(void *pointer); // Free memory from the heap. + bool KThreadCreate(const char *cName, void (*startAddress)(uintptr_t), uintptr_t argument = 0); // Create a thread. + +Synchronisation: + + void Mutex::Acquire(); + void Mutex::Release(); + void Mutex::AssertLocked(); + + void Spinlock::Acquire(); // Disables interrupts. + void Spinlock::Release(); + void Spinlock::AssertLocked(); + + bool Event::Set(); + void Event::Reset(); + bool Event::Pool(); + bool Event::Wait(uintptr_t timeoutMs); // Return false on timeout. + // event.autoReset determines whether the event will automatically reset after Poll() or Wait() return. + +Linked lists: + + LinkedList::InsertStart(LinkedItem *item); // Insert an item at the start of a linked list. + LinkedList::InsertEnd(LinkedItem *item); // Insert an item at the end of a linked list. + LinkedList::Remove(LinkedItem *item); // Remove an item from a linked list. + + struct LinkedList { + LinkedItem *firstItem; // The start of the linked list. + LinkedItem *lastItem; // The end of the linked list. + size_t count; // The number of items in the linked list. + } + + struct LinkedItem { + LinkedItem *previousItem; // The previous item in the linked list. + LinkedItem *nextItem; // The next item in the linked list. + LinkedList *list; // The list the item is in. + T *thisItem; // A pointer to the item itself. + } + +## Contributors + +Put your name here! + +- nakst +- Brett R. Toomey +- vtlmks +- Aleksander Birkeland + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..500b2c3 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,32 @@ +MIT License + +Copyright (c) 2017-2021 nakst and other contributors (see CONTRIBUTING.md). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This project also include the work of other projects. +All trademarks are registered trademarks of their respective owners. +They licenses may be found in the following files: + - util/nanosvg.h + - shared/stb_image.h, shared/stb_sprintf.h, shared/stb_ds.h and util/stb_truetype.h + - res/Fonts/Hack License.txt, res/Fonts/Inter License.txt + - res/Icons/elementary Icons License.txt + - res/Media/Licenses.txt + - Ported applications have their licenses in their respective folders. +Please tell me if I've forgotten something! diff --git a/README.md b/README.md new file mode 100644 index 0000000..878c8f2 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# **Essence** — An Operating System + +![Screenshot of the OS running in an emulator, showing File Manager, and the new tab screen.](https://essence.handmade.network/static/media/image/p-essence-s1.png) +![Screenshot of the OS running in an emulator, showing GCC running under the POSIX subsystem.](https://essence.handmade.network/static/media/image/p-essence-s3.png) +![Screenshot of the OS running in an emulator, showing the shutdown dialog.](https://essence.handmade.network/static/media/image/p-essence-s5.png) + +## Support + +To support development, you can donate to my Patreon: https://www.patreon.com/nakst. + +## Features + +Kernel +* Audio mixer. +* Filesystem independent cache manager. +* Memory manager with shared memory, memory-mapped files and multithreaded paging zeroing and working set balancing. +* Networking stack for TCP/IP. +* Scheduler with multiple priority levels and priority inversion. +* On-demand module loading. +* Virtual filesystem. +* Window manager. +* Optional POSIX subsystem, capable of running GCC and some Busybox tools. + +Applications +* File Manager +* Text Editor +* IRC Client +* System Monitor + +Ports +* Bochs +* GCC and Binutils +* FFmpeg +* Mesa (for software-rendered OpenGL) +* Musl + +Drivers +* Power management: ACPI with ACPICA. +* Secondary storage: IDE, AHCI and NVMe. +* Graphics: BGA and SVGA. +* Read-write filesystems: EssenceFS. +* Read-only filesystems: Ext2, FAT, NTFS, ISO9660. +* Audio: HD Audio. +* NICs: 8254x. +* USB: XHCI, bulk storage devices, human interface devices. + +Desktop +* Custom user interface library. +* Software vector renderer with complex animation support. +* Tabbed windows. +* Multi-lingual text rendering and layout with FreeType and Harfbuzz. + +## Discussion + +Visit https://essence.handmade.network/forums. + +## Building + +**Warning: This software is still in development. Expect bugs.** + +### Linux + +Download this project's source. + + git clone --depth=1 https://gitlab.com/nakst/essence.git/ + +Start the build system. + + ./start.sh + +Follow the on-screen instructions to build a cross compiler. + +Once complete, you can test the operating system in an emulator. +* Please note that by default a checked build is produced, which runs additional checks at runtime (such as heap validation on every allocation and deallocation). This may impact the performance during testing. +* If you have Qemu installed, run `t2` in the build system. +* If you have VirtualBox installed, make a 128MB drive called `vbox.vdi` in the `bin` folder, attach it as a to a virtual machine called "Essence" (choose "Windows 7 64-bit" as the OS), and run `v` in the build system. + +## Configuration + +From within the build system, run the command `config` to open the configuration editor. Click an option to change its value, and then click the `Save` button. You changes are saved locally, and will not be uploaded by Git. Not all configurations are likely to work; if you don't know what you're doing, it's probably best to stick with the defaults. + +## Generating the API header + +If you want your project to target Essence, you need to generate the API header for your programming language of choice. + + g++ -o bin/build_core util/build_core.c + bin/build_core headers + +Currently supported languages are 'c' (also works for C++), 'zig' and 'odin'. + +There is currently no documentation for the API; for examples of how to use the API, consult the standard applications in the `apps/` folder of the source tree. A minimal application is demonstrated in `apps/hello.c` and `apps/hello.ini`. By placing your application's `.ini` file in the `apps/` folder, it will be automatically built by the build system. diff --git a/apps/bochs.ini b/apps/bochs.ini new file mode 100644 index 0000000..7a7e168 --- /dev/null +++ b/apps/bochs.ini @@ -0,0 +1,17 @@ +[general] +name=Bochs +icon=icon_applications_development +permission_posix_subsystem=1 + +[build] +custom_compile_command=cp root/Applications/POSIX/bin/bochs root/Applications/Bochs.esx +require=root/Applications/POSIX/bin/bochs + +[@file_type] +extension=bochsrc +name=Bochs configuration +icon=icon_applications_development + +[@handler] +extension=bochsrc +action=open diff --git a/apps/file_manager.ini b/apps/file_manager.ini new file mode 100644 index 0000000..d96242c --- /dev/null +++ b/apps/file_manager.ini @@ -0,0 +1,114 @@ +[general] +name=File Manager +icon=icon_system_file_manager +permission_all_files=1 +use_single_process=1 + +[build] +source=apps/file_manager/main.cpp + +[@file_type] +extension=esx +name=Executable +icon=icon_application_default_icon + +[@file_type] +extension=ini +name=Configuration file +icon=icon_application_x_desktop + +[@file_type] +extension=esx_symbols +name=Debugging data +icon=icon_extension + +[@file_type] +extension=exe +name=PC executable +icon=icon_application_x_ms_dos_executable + +[@file_type] +extension=png +name=PNG image +icon=icon_image_x_generic + +[@file_type] +extension=jpg +name=JPG image +icon=icon_image_x_generic + +[@file_type] +extension=ttf +name=TrueType font +icon=icon_font_x_generic + +[@file_type] +extension=otf +name=OpenType font +icon=icon_font_x_generic + +[@file_type] +extension=a +name=Static library +icon=icon_extension + +[@file_type] +extension=dat +name=Database +icon=icon_office_database + +[@file_type] +extension=icon_pack +name=Icon pack +icon=icon_package_x_generic + +[@file_type] +extension=ekm +name=Kernel module +icon=icon_extension + +[@file_type] +extension=o +name=Binary object +icon=icon_extension + +[@file_type] +extension=c +name=C source +icon=icon_text_x_csrc + +[@file_type] +extension=cpp +name=C++ source +icon=icon_text_x_csrc + +[@file_type] +extension=h +name=C header +icon=icon_text_x_csrc + +[@file_type] +extension=txt +name=Text file +icon=icon_text + +[@file_type] +extension=md +name=Markdown file +icon=icon_text_markdown + +[@file_type] +extension=ogg +name=OGG audio +icon=icon_audio_x_generic + +[@file_type] +extension=mp4 +name=MP4 video +icon=icon_view_list_video_symbolic + +[@file_type] +extension=wav +name=Wave audio +icon=icon_audio_x_generic +ES_ICON_AUDIO_X_GENERIC diff --git a/apps/file_manager/commands.cpp b/apps/file_manager/commands.cpp new file mode 100644 index 0000000..556d17d --- /dev/null +++ b/apps/file_manager/commands.cpp @@ -0,0 +1,170 @@ +void CommandRename(Instance *instance, EsElement *, EsCommand *) { + intptr_t index = -1; + + for (uintptr_t i = 0; i < instance->listContents.Length(); i++) { + ListEntry *entry = &instance->listContents[i]; + + if (entry->selected) { + index = i; + break; + } + } + + EsAssert(index != -1); + + instance->rename.textbox = EsListViewCreateInlineTextbox(instance->list, 0, index, ES_LIST_VIEW_INLINE_TEXTBOX_COPY_EXISTING_TEXT); + instance->rename.index = index; + + FolderEntry *entry = instance->listContents[index].entry; + + if (entry->extensionOffset != entry->nameBytes) { + // Don't include the file extension in the initial selection. + EsTextboxSetSelection(instance->rename.textbox, 0, 0, 0, entry->extensionOffset - 1); + } + + instance->rename.textbox->messageUser = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_TEXTBOX_EDIT_END) { + Instance *instance = element->instance; + + String name = {}; + name.text = EsTextboxGetContents((EsTextbox *) element, &name.bytes); + name.allocated = name.bytes; + + if (!name.bytes || message->endEdit.rejected) { + StringDestroy(&name); + } else { + FolderEntry *entry = instance->listContents[instance->rename.index].entry; + String oldName = entry->GetName(); + + BlockingTaskQueue(instance, { + .string = name, + .string2 = StringDuplicate(oldName), + .cDescription = interfaceString_FileManagerRenameTask, + + .callback = [] (Instance *instance, Task *task) { + if (StringEquals(task->string, task->string2)) { + task->result = ES_SUCCESS; + } else { + task->result = instance->folder->itemHandler->renameItem(instance->folder, task->string2, task->string); + } + }, + + .then = [] (Instance *instance, Task *task) { + if (task->result != ES_SUCCESS) { + InstanceReportError(instance, ERROR_RENAME_ITEM, task->result); + } else { + Folder *folder = instance->folder; + + size_t newPathBytes; + char *newPath = EsStringAllocateAndFormat(&newPathBytes, "%s%s", STRFMT(instance->folder->path), STRFMT(task->string)); + size_t oldPathBytes; + char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s%s", STRFMT(instance->folder->path), STRFMT(task->string2)); + + FolderPathMoved(instance, { .text = oldPath, .bytes = oldPathBytes }, { .text = newPath, .bytes = newPathBytes }); + + EsDirectoryChild information = {}; + EsPathQueryInformation(newPath, newPathBytes, &information); + uint64_t id = FolderRemoveEntryAndUpdateInstances(folder, STRING(task->string2)); + FolderAddEntryAndUpdateInstances(folder, STRING(task->string), &information, instance, false, id); + + EsHeapFree(oldPath); + EsHeapFree(newPath); + } + + StringDestroy(&task->string); + StringDestroy(&task->string2); + }, + }); + } + + EsElementDestroy(element); + } + + return 0; + }; +} + +void CommandNewFolder(Instance *instance, EsElement *, EsCommand *) { + String name = StringAllocateAndFormat("%z", interfaceString_FileManagerNewFolderName); + + BlockingTaskQueue(instance, { + .string = name, + .cDescription = interfaceString_FileManagerNewFolderTask, + + .callback = [] (Instance *instance, Task *task) { + task->result = instance->folder->itemHandler->createChildFolder(instance->folder, &task->string, true); + }, + + .then = [] (Instance *instance, Task *task) { + if (task->result != ES_SUCCESS) { + InstanceReportError(instance, ERROR_NEW_FOLDER, task->result); + } else { + Folder *folder = instance->folder; + EsDirectoryChild information = {}; + information.type = ES_NODE_DIRECTORY; + FolderAddEntryAndUpdateInstances(folder, STRING(task->string), &information, instance, false); + CommandRename(instance, nullptr, nullptr); + } + + StringDestroy(&task->string); + }, + }); +} + +void InstanceRegisterCommands(Instance *instance) { + uint32_t stableCommandID = 1; + + EsCommandRegister(&instance->commandGoBackwards, instance, [] (Instance *instance, EsElement *, EsCommand *) { + EsAssert(instance->pathBackwardHistory.Length()); + HistoryEntry entry = instance->pathBackwardHistory.Pop(); + StringDestroy(&instance->delayedFocusItem); + instance->delayedFocusItem = entry.focusedItem; + InstanceLoadFolder(instance, entry.path, LOAD_FOLDER_BACK); + }, stableCommandID++, "Backspace|Alt+Left"); + + EsCommandRegister(&instance->commandGoForwards, instance, [] (Instance *instance, EsElement *, EsCommand *) { + EsAssert(instance->pathForwardHistory.Length()); + HistoryEntry entry = instance->pathForwardHistory.Pop(); + StringDestroy(&instance->delayedFocusItem); + instance->delayedFocusItem = entry.focusedItem; + InstanceLoadFolder(instance, entry.path, LOAD_FOLDER_FORWARD); + }, stableCommandID++, "Alt+Right"); + + EsCommandRegister(&instance->commandGoParent, instance, [] (Instance *instance, EsElement *, EsCommand *) { + String parent = PathGetParent(instance->folder->path); + InstanceLoadFolder(instance, StringDuplicate(parent)); + }, stableCommandID++, "Alt+Up"); + + EsCommandRegister(&instance->commandRefresh, instance, [] (Instance *instance, EsElement *, EsCommand *) { + EsMutexAcquire(&loadedFoldersMutex); + FolderRefresh(instance->folder); + EsMutexRelease(&loadedFoldersMutex); + }, stableCommandID++, "F5"); + + EsCommandRegister(&instance->commandNewFolder, instance, CommandNewFolder, stableCommandID++, "Ctrl+Shift+N"); + EsCommandRegister(&instance->commandRename, instance, CommandRename, stableCommandID++, "F2"); + + EsCommandRegister(&instance->commandViewDetails, instance, [] (Instance *instance, EsElement *, EsCommand *) { + instance->viewSettings.viewType = VIEW_DETAILS; + InstanceRefreshViewType(instance); + InstanceViewSettingsUpdated(instance); + }, stableCommandID++); + + EsCommandRegister(&instance->commandViewTiles, instance, [] (Instance *instance, EsElement *, EsCommand *) { + instance->viewSettings.viewType = VIEW_TILES; + InstanceRefreshViewType(instance); + InstanceViewSettingsUpdated(instance); + }, stableCommandID++); + + EsCommandRegister(&instance->commandViewThumbnails, instance, [] (Instance *instance, EsElement *, EsCommand *) { + instance->viewSettings.viewType = VIEW_THUMBNAILS; + InstanceRefreshViewType(instance); + InstanceViewSettingsUpdated(instance); + }, stableCommandID++); + + EsCommandSetDisabled(&instance->commandViewDetails, false); + EsCommandSetDisabled(&instance->commandViewTiles, false); + EsCommandSetDisabled(&instance->commandViewThumbnails, false); + + EsCommandSetCheck(&instance->commandViewDetails, ES_CHECK_CHECKED, false); +} diff --git a/apps/file_manager/folder.cpp b/apps/file_manager/folder.cpp new file mode 100644 index 0000000..b30c270 --- /dev/null +++ b/apps/file_manager/folder.cpp @@ -0,0 +1,597 @@ +#define NAMESPACE_HANDLER_FILE_SYSTEM (1) +#define NAMESPACE_HANDLER_DRIVES_PAGE (2) +#define NAMESPACE_HANDLER_ROOT (3) // Acts as a container handler where needed. +#define NAMESPACE_HANDLER_INVALID (4) // For when a folder does not exist. + +EsMutex loadedFoldersMutex; +Array loadedFolders; + +#define MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES (20) +Array foldersWithNoAttachedInstances; + +///////////////////////////////// + +bool FSDirHandlesPath(String path) { + EsDirectoryChild information; + + if (EsPathQueryInformation(STRING(path), &information)) { + return information.type == ES_NODE_DIRECTORY; + } else { + return false; + } +} + +EsError FSDirCreateChildFolder(Folder *folder, String *name, bool findUniqueName) { + size_t pathBytes; + char *path; + + if (findUniqueName) { + const char *extra = " "; + path = EsStringAllocateAndFormat(&pathBytes, "%s%s%z", STRFMT(folder->path), STRFMT((*name)), extra); + pathBytes = EsPathFindUniqueName(path, pathBytes - EsCStringLength(extra), pathBytes); + StringDestroy(name); + *name = StringAllocateAndFormat("%s", pathBytes - folder->path.bytes, path + folder->path.bytes); + } else { + path = EsStringAllocateAndFormat(&pathBytes, "%s%s", STRFMT(folder->path), STRFMT((*name))); + } + + EsError error = EsPathCreate(path, pathBytes, ES_NODE_DIRECTORY, false); + EsHeapFree(path); + return error; +} + +EsError FSDirRenameItem(Folder *folder, String oldName, String newName) { + size_t oldPathBytes; + char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s%s", STRFMT(folder->path), STRFMT(oldName)); + + size_t newPathBytes; + char *newPath = EsStringAllocateAndFormat(&newPathBytes, "%s%s", STRFMT(folder->path), STRFMT(newName)); + + EsError error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes); + + EsHeapFree(oldPath); + EsHeapFree(newPath); + + return error; +}; + +EsError FSDirEnumerate(Folder *folder) { + // Get the initial directory children. + // TODO Recurse mode. + + EsNodeType type; + + if (!EsPathExists(STRING(folder->path), &type) || type != ES_NODE_DIRECTORY) { + return ES_ERROR_FILE_DOES_NOT_EXIST; + } + + EsDirectoryChild *buffer = nullptr; + ptrdiff_t _entryCount = EsDirectoryEnumerateChildren(STRING(folder->path), &buffer); + + if (ES_CHECK_ERROR(_entryCount)) { + EsHeapFree(buffer); + return (EsError) _entryCount; + } + + FolderAddEntries(folder, buffer, _entryCount); + EsHeapFree(buffer); + + return ES_SUCCESS; +} + +void FSDirGetTotalSize(Folder *folder) { + EsDirectoryChild information; + + if (EsPathQueryInformation(STRING(folder->path), &information)) { + folder->spaceUsed = information.fileSize; + folder->spaceTotal = 0; + } +} + +String FSDirGetPathForChildFolder(Folder *folder, String item) { + return StringAllocateAndFormat("%s%s/", STRFMT(folder->path), STRFMT(item)); +} + +///////////////////////////////// + +bool DrivesPageHandlesPath(String path) { + return StringEquals(path, StringFromLiteralWithSize(INTERFACE_STRING(FileManagerDrivesPage))); +} + +uint32_t DrivesPageGetFileType(String path) { + path = PathRemoveTrailingSlash(path); + String string = PathGetSection(path, PathCountSections(path)); + EsVolumeInformation volume; + + if (EsMountPointGetVolumeInformation(STRING(string), &volume)) { + if (volume.driveType == ES_DRIVE_TYPE_HDD ) return KNOWN_FILE_TYPE_DRIVE_HDD; + if (volume.driveType == ES_DRIVE_TYPE_SSD ) return KNOWN_FILE_TYPE_DRIVE_SSD; + if (volume.driveType == ES_DRIVE_TYPE_CDROM ) return KNOWN_FILE_TYPE_DRIVE_CDROM; + if (volume.driveType == ES_DRIVE_TYPE_USB_MASS_STORAGE) return KNOWN_FILE_TYPE_DRIVE_USB_MASS_STORAGE; + return KNOWN_FILE_TYPE_DRIVE_HDD; + } + + return KNOWN_FILE_TYPE_DRIVE_HDD; +} + +void DrivesPageGetTotalSize(Folder *folder) { + EsVolumeInformation information; + + if (EsMountPointGetVolumeInformation(STRING(folder->path), &information)) { + folder->spaceUsed = information.spaceUsed; + folder->spaceTotal = information.spaceTotal; + } +} + +void DrivesPageGetVisibleName(EsBuffer *buffer, String path) { + path = PathRemoveTrailingSlash(path); + String string = PathGetSection(path, PathCountSections(path)); + EsVolumeInformation volume; + + if (EsMountPointGetVolumeInformation(STRING(string), &volume)) { + EsBufferFormat(buffer, "%s", volume.labelBytes, volume.label); + return; + } + + EsBufferFormat(buffer, "%s", STRFMT(string)); +} + +EsError DrivesPageEnumerate(Folder *folder) { + EsMutexAcquire(&drivesMutex); + EsDirectoryChild information = {}; + information.type = ES_NODE_DIRECTORY; + + for (uintptr_t i = 0; i < drives.Length(); i++) { + Drive *drive = &drives[i]; + information.fileSize = drive->information.spaceTotal; + FolderAddEntry(folder, drive->prefix, drive->prefixBytes, &information); + } + + EsMutexRelease(&drivesMutex); + return ES_SUCCESS; +} + +String DrivesPageGetPathForChildFolder(Folder *, String item) { + return StringAllocateAndFormat("%s/", STRFMT(item)); +} + +void DrivesPageGetDefaultViewSettings(Folder *, FolderViewSettings *settings) { + settings->viewType = VIEW_TILES; +} + +///////////////////////////////// + +uint32_t NamespaceDefaultGetFileType(String) { + return KNOWN_FILE_TYPE_DIRECTORY; +} + +void NamespaceDefaultGetVisibleName(EsBuffer *buffer, String path) { + String string = PathGetSection(path, -1); + EsBufferFormat(buffer, "%s", STRFMT(string)); +} + +uint32_t NamespaceRootGetFileType(String path) { + if (StringEquals(path, StringFromLiteralWithSize(INTERFACE_STRING(FileManagerDrivesPage)))) { + return KNOWN_FILE_TYPE_DRIVES_PAGE; + } else { + return KNOWN_FILE_TYPE_DIRECTORY; + } +} + +void NamespaceRootGetVisibleName(EsBuffer *buffer, String path) { + EsBufferFormat(buffer, "%s", STRFMT(StringSlice(path, 0, path.bytes - 1))); +} + +EsError NamespaceInvalidEnumerate(Folder *folder) { + folder->cEmptyMessage = folder->driveRemoved ? interfaceString_FileManagerInvalidDrive : interfaceString_FileManagerInvalidPath; + return ES_SUCCESS; +} + +NamespaceHandler namespaceHandlers[] = { + { + .type = NAMESPACE_HANDLER_DRIVES_PAGE, + .rootContainerHandlerType = NAMESPACE_HANDLER_ROOT, + .handlesPath = DrivesPageHandlesPath, + .getFileType = DrivesPageGetFileType, + .getVisibleName = DrivesPageGetVisibleName, + .getTotalSize = DrivesPageGetTotalSize, + .getPathForChildFolder = DrivesPageGetPathForChildFolder, + .getDefaultViewSettings = DrivesPageGetDefaultViewSettings, + .enumerate = DrivesPageEnumerate, + }, + + { + .type = NAMESPACE_HANDLER_FILE_SYSTEM, + .rootContainerHandlerType = NAMESPACE_HANDLER_DRIVES_PAGE, + .handlesPath = FSDirHandlesPath, + .getFileType = NamespaceDefaultGetFileType, + .getVisibleName = NamespaceDefaultGetVisibleName, + .getTotalSize = FSDirGetTotalSize, + .getPathForChildFolder = FSDirGetPathForChildFolder, + .createChildFolder = FSDirCreateChildFolder, + .renameItem = FSDirRenameItem, + .enumerate = FSDirEnumerate, + }, + + { + .type = NAMESPACE_HANDLER_ROOT, + .handlesPath = [] (String) { return false; }, + .getFileType = NamespaceRootGetFileType, + .getVisibleName = NamespaceRootGetVisibleName, + }, + + { + .type = NAMESPACE_HANDLER_INVALID, + .rootContainerHandlerType = NAMESPACE_HANDLER_ROOT, + .handlesPath = [] (String) { return true; }, + .getFileType = NamespaceDefaultGetFileType, + .getVisibleName = NamespaceDefaultGetVisibleName, + .enumerate = NamespaceInvalidEnumerate, + }, +}; + +void NamespaceFindHandlersForPath(NamespaceHandler **itemHandler, NamespaceHandler **containerHandler, String path) { + String pathContainer = PathGetParent(path); + *itemHandler = *containerHandler = nullptr; + + for (uintptr_t i = 0; i < sizeof(namespaceHandlers) / sizeof(namespaceHandlers[0]); i++) { + if (!(*itemHandler) && namespaceHandlers[i].handlesPath(path)) { + *itemHandler = &namespaceHandlers[i]; + } + + if (pathContainer.bytes && !(*containerHandler) && namespaceHandlers[i].handlesPath(pathContainer)) { + *containerHandler = &namespaceHandlers[i]; + } + } + + if (!pathContainer.bytes && (*itemHandler)) { + for (uintptr_t i = 0; i < sizeof(namespaceHandlers) / sizeof(namespaceHandlers[0]); i++) { + if (namespaceHandlers[i].type == (*itemHandler)->rootContainerHandlerType) { + *containerHandler = &namespaceHandlers[i]; + break; + } + } + } +} + +uint32_t NamespaceGetIcon(String path) { + NamespaceHandler *itemHandler, *containerHandler; + NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path); + + if (containerHandler) { + return knownFileTypes[containerHandler->getFileType(path)].iconID; + } else { + return ES_ICON_FOLDER; + } +} + +void NamespaceGetVisibleName(EsBuffer *buffer, String path) { + NamespaceHandler *itemHandler, *containerHandler; + NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path); + + if (containerHandler) { + containerHandler->getVisibleName(buffer, path); + } else { + String string = PathGetSection(path, -1); + EsBufferFormat(buffer, "%s", STRFMT(string)); + } +} + +///////////////////////////////// + +void BookmarkAdd(String path, bool saveConfiguration = true) { + bookmarks.Add(StringDuplicate(path)); + if (saveConfiguration) ConfigurationSave(); + + for (uintptr_t i = 0; i < instances.Length(); i++) { + EsListViewInsert(instances[i]->placesView, PLACES_VIEW_GROUP_BOOKMARKS, bookmarks.Length(), bookmarks.Length()); + } +} + +void BookmarkRemove(String path) { + for (uintptr_t i = 0; i < bookmarks.Length(); i++) { + if (0 == EsStringCompareRaw(STRING(bookmarks[i]), STRING(path))) { + bookmarks.Delete(i); + + for (uintptr_t i = 0; i < instances.Length(); i++) { + EsListViewRemove(instances[i]->placesView, PLACES_VIEW_GROUP_BOOKMARKS, i + 1, i + 1); + } + + ConfigurationSave(); + return; + } + } + + EsAssert(false); +} + +///////////////////////////////// + +void FolderEntryCloseHandle(Folder *folder, FolderEntry *entry) { + entry->handles--; + + if (!entry->handles) { + if (entry->name != entry->internalName) { + EsHeapFree(entry->internalName); + } + + EsHeapFree(entry->name); + EsArenaFree(&folder->entryArena, entry); + } +} + +void FolderDestroy(Folder *folder) { + for (uintptr_t i = 0; i < folder->entries.slotCount; i++) { + if (folder->entries.slots[i].key.used) { + FolderEntryCloseHandle(folder, (FolderEntry *) folder->entries.slots[i].value); + } + } + + EsHeapFree(folder); +} + +void FolderDetachInstance(Instance *instance) { + Folder *folder = instance->folder; + if (!folder) return; + instance->folder = nullptr; + folder->attachedInstances.FindAndDeleteSwap(instance, true); + + if (!folder->attachedInstances.Length() && !folder->attachingInstances.Length()) { + foldersWithNoAttachedInstances.Add(folder); + + if (foldersWithNoAttachedInstances.Length() > MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES) { + Folder *leastRecentlyUsed = foldersWithNoAttachedInstances[0]; + loadedFolders.FindAndDeleteSwap(leastRecentlyUsed, true); + foldersWithNoAttachedInstances.Delete(0); + FolderDestroy(leastRecentlyUsed); + } + } +} + +FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, EsDirectoryChild *information, uint64_t id) { + char *name = (char *) EsHeapAllocate(nameBytes + 1, false); + name[nameBytes] = 0; + EsMemoryCopy(name, _name, nameBytes); + + FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes); + + if (!entry) { + entry = (FolderEntry *) EsArenaAllocate(&folder->entryArena, true); + HashTablePutLong(&folder->entries, name, nameBytes, entry, false); + + static uint64_t nextEntryID = 1; + entry->id = id ?: __sync_fetch_and_add(&nextEntryID, 1); + entry->handles = 1; + entry->name = name; + entry->nameBytes = nameBytes; + entry->extensionOffset = PathGetExtension(entry->GetName()).text - name; + entry->internalName = name; + entry->internalNameBytes = nameBytes; + + if (folder->itemHandler->getVisibleName != NamespaceDefaultGetVisibleName) { + String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetName())); + uint8_t _buffer[256]; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + folder->itemHandler->getVisibleName(&buffer, path); + entry->nameBytes = buffer.position; + entry->name = (char *) EsHeapAllocate(buffer.position, false); + EsMemoryCopy(entry->name, _buffer, buffer.position); + StringDestroy(&path); + } + } else { + EsHeapFree(name); + name = entry->name; + entry->previousSize = entry->size; + } + + entry->size = information->fileSize; + entry->isFolder = information->type == ES_NODE_DIRECTORY; + entry->sizeUnknown = entry->isFolder && information->directoryChildren == ES_DIRECTORY_CHILDREN_UNKNOWN; + + return entry; +} + +void FolderAddEntries(Folder *folder, EsDirectoryChild *buffer, size_t entryCount) { + for (uintptr_t i = 0; i < entryCount; i++) { + FolderAddEntry(folder, buffer[i].name, buffer[i].nameBytes, &buffer[i]); + } +} + +void FolderAddEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes, + EsDirectoryChild *information, Instance *selectItem, bool mutexAlreadyAcquired, uint64_t id = 0) { + if (!mutexAlreadyAcquired) EsMutexAcquire(&loadedFoldersMutex); + + if (folder->containerHandler->getTotalSize) { + folder->containerHandler->getTotalSize(folder); + } + + FolderEntry *entry = FolderAddEntry(folder, name, nameBytes, information, id); + ListEntry listEntry = { .entry = entry }; + + for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) { + Instance *instance = folder->attachedInstances[i]; + listEntry.selected = instance == selectItem; + InstanceAddSingle(instance, listEntry); + InstanceUpdateStatusString(instance); + } + + if (!mutexAlreadyAcquired) EsMutexRelease(&loadedFoldersMutex); +} + +uint64_t FolderRemoveEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes) { + EsMutexAcquire(&loadedFoldersMutex); + + FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes); + uint64_t id = 0; + + if (entry) { + for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) { + InstanceRemoveSingle(folder->attachedInstances[i], entry); + } + + id = entry->id; + HashTableDeleteLong(&folder->entries, name, nameBytes); + FolderEntryCloseHandle(folder, entry); + } + + EsMutexRelease(&loadedFoldersMutex); + return id; +} + +void FolderPathMoved(Instance *instance, String oldPath, String newPath) { + _EsPathAnnouncePathMoved(instance, STRING(oldPath), STRING(newPath)); + + EsMutexAcquire(&loadedFoldersMutex); + + for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { + Folder *folder = loadedFolders[i]; + + if (PathReplacePrefix(&folder->path, oldPath, newPath)) { + for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) { + InstanceFolderPathChanged(folder->attachedInstances[i], false); + } + } + } + + EsMutexRelease(&loadedFoldersMutex); + + for (uintptr_t i = 0; i < bookmarks.Length(); i++) { + PathReplacePrefix(&bookmarks[i], oldPath, newPath); + } + + for (uintptr_t i = 0; i < instances.Length(); i++) { + EsListViewInvalidateAll(instances[i]->placesView); + + for (uintptr_t j = 0; j < instances[i]->pathBackwardHistory.Length(); j++) { + HistoryEntry *entry = &instances[i]->pathBackwardHistory[j]; + PathReplacePrefix(&entry->path, oldPath, newPath); + + if (StringStartsWith(oldPath, entry->path) && StringEquals(entry->focusedItem, StringSlice(oldPath, entry->path.bytes, -1))) { + StringDestroy(&entry->focusedItem); + entry->focusedItem = StringDuplicate(StringSlice(newPath, entry->path.bytes, -1)); + } + } + + for (uintptr_t j = 0; j < instances[i]->pathForwardHistory.Length(); j++) { + HistoryEntry *entry = &instances[i]->pathForwardHistory[j]; + PathReplacePrefix(&entry->path, oldPath, newPath); + + if (StringStartsWith(oldPath, entry->path) && StringEquals(entry->focusedItem, StringSlice(oldPath, entry->path.bytes, -1))) { + StringDestroy(&entry->focusedItem); + entry->focusedItem = StringDuplicate(StringSlice(newPath, entry->path.bytes, -1)); + } + } + } + + for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) { + String knownPath = StringFromLiteralWithSize(folderViewSettings[i].path, folderViewSettings[i].pathBytes); + + if (knownPath.bytes - oldPath.bytes + newPath.bytes >= sizeof(folderViewSettings[i].path)) { + continue; + } + + if (!PathHasPrefix(knownPath, oldPath)) { + continue; + } + + String after = StringSlice(knownPath, oldPath.bytes, -1); + folderViewSettings[i].pathBytes = EsStringFormat(folderViewSettings[i].path, sizeof(folderViewSettings[i].path), + "%s%s", STRFMT(newPath), STRFMT(after)); + } + + ConfigurationSave(); +} + +EsError FolderAttachInstance(Instance *instance, String path, bool recurse, Folder **newFolder) { + // TODO Don't modify attachedInstances/attachingInstances/loadedFolders without the message mutex! + // And then we can remove loadedFoldersMutex. + + // (Called on the blocking task thread.) + + Folder *folder = nullptr; + NamespaceHandler *itemHandler = nullptr, *containerHandler = nullptr; + EsError error; + bool driveRemoved = false; + + // Check if we've already loaded the folder. + + EsMutexAcquire(&loadedFoldersMutex); + + for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { + if (StringEquals(loadedFolders[i]->path, path) && loadedFolders[i]->recurse == recurse) { + if (!loadedFolders[i]->refreshing) { + folder = loadedFolders[i]; + goto success; + } else { + driveRemoved = loadedFolders[i]->driveRemoved; + } + } + } + + EsMutexRelease(&loadedFoldersMutex); + + // Find the handler for the path. + + NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path); + + if (!itemHandler || !containerHandler) { + return ES_ERROR_FILE_DOES_NOT_EXIST; + } + + // Load a new folder. + + folder = (Folder *) EsHeapAllocate(sizeof(Folder), true); + + folder->path = StringDuplicate(path); + folder->recurse = recurse; + folder->itemHandler = itemHandler; + folder->containerHandler = containerHandler; + folder->cEmptyMessage = interfaceString_FileManagerEmptyFolderView; + folder->driveRemoved = driveRemoved; + + EsArenaInitialise(&folder->entryArena, 1048576, sizeof(FolderEntry)); + + // TODO Make this asynchronous for some folder providers, or recursive requests + // (that is, immediately present to the user while streaming data in). + error = itemHandler->enumerate(folder); + + folder->driveRemoved = false; + folder->refreshing = false; + + if (error != ES_SUCCESS) { + StringDestroy(&folder->path); + EsHeapFree(folder); + return error; + } + + if (containerHandler->getTotalSize) { + containerHandler->getTotalSize(folder); + } + + EsMutexAcquire(&loadedFoldersMutex); + loadedFolders.Add(folder); + + success:; + + foldersWithNoAttachedInstances.FindAndDeleteSwap(folder, false); + folder->attachingInstances.Add(instance); + + EsMutexRelease(&loadedFoldersMutex); + + *newFolder = folder; + return ES_SUCCESS; +} + +void FolderRefresh(Folder *folder) { + // EsMutexAssertLocked(&loadedFoldersMutex); + + if (folder->refreshing) { + return; + } + + folder->refreshing = true; + + for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) { + InstanceLoadFolder(folder->attachedInstances[i], StringDuplicate(folder->path), LOAD_FOLDER_REFRESH); + } +} diff --git a/apps/file_manager/main.cpp b/apps/file_manager/main.cpp new file mode 100644 index 0000000..04dfa04 --- /dev/null +++ b/apps/file_manager/main.cpp @@ -0,0 +1,573 @@ +#define ES_INSTANCE_TYPE Instance +#include +#include + +#include +#include +#define IMPLEMENTATION +#include +#undef IMPLEMENTATION + +// TODO Possible candidates for moving in the core API: +// - String/paths utils +// - Blocking/non-blocking task systems + +// TODO Don't show modals if a folder can't be loaded. +// Instead, show a list view with an error message, +// and disable interactions. + +#define SETTINGS_FILE "|Settings:/Default.ini" + +#define ERROR_LOAD_FOLDER (1) +#define ERROR_NEW_FOLDER (2) +#define ERROR_RENAME_ITEM (3) + +#define PLACES_VIEW_GROUP_BOOKMARKS (0) +#define PLACES_VIEW_GROUP_DRIVES (1) + +#define MESSAGE_BLOCKING_TASK_COMPLETE ((EsMessageType) (ES_MSG_USER_START + 1)) +#define MESSAGE_NON_BLOCKING_TASK_COMPLETE ((EsMessageType) (ES_MSG_USER_START + 2)) + +#define VIEW_DETAILS (0) +#define VIEW_TILES (1) +#define VIEW_THUMBNAILS (2) + +const char *errorTypeStrings[] = { + interfaceString_FileManagerUnknownError, + interfaceString_FileManagerOpenFolderError, + interfaceString_FileManagerNewFolderError, + interfaceString_FileManagerRenameItemError, +}; + +#include "string.cpp" + +EsListViewColumn folderOutputColumns[] = { +#define COLUMN_NAME (0) + { INTERFACE_STRING(FileManagerColumnName), ES_LIST_VIEW_COLUMN_HAS_MENU }, +#define COLUMN_TYPE (1) + { INTERFACE_STRING(FileManagerColumnType), ES_LIST_VIEW_COLUMN_HAS_MENU }, +#define COLUMN_SIZE (2) + { INTERFACE_STRING(FileManagerColumnSize), ES_LIST_VIEW_COLUMN_HAS_MENU | ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED }, +}; + +#define LOAD_FOLDER_BACK (1) +#define LOAD_FOLDER_FORWARD (2) +#define LOAD_FOLDER_START (3) +#define LOAD_FOLDER_REFRESH (4) +#define LOAD_FOLDER_NO_FOCUS (1 << 8) + +struct FolderEntry { + uint16_t handles; + bool isFolder, sizeUnknown; + uint8_t nameBytes, extensionOffset, internalNameBytes; // 0 -> 256. + char *name, *internalName; + EsFileOffset size, previousSize; + uint64_t id; + + inline String GetName() { + return { .text = name, .bytes = nameBytes ?: 256u, .allocated = nameBytes ?: 256u }; + } + + inline String GetInternalName() { + return { .text = internalName, .bytes = internalNameBytes ?: 256u, .allocated = internalNameBytes ?: 256u }; + } + + inline String GetExtension() { + uintptr_t offset = extensionOffset ?: 256u; + return { .text = name + offset, .bytes = (nameBytes ?: 256u) - offset }; + } +}; + +struct ListEntry { + FolderEntry *entry; + bool selected; +}; + +struct Task { + EsGeneric context, context2; + String string, string2; + uint64_t id; + EsError result; + const char *cDescription; + Instance *instance; + + void (*callback)(Instance *instance, Task *task); + void (*then)(Instance *instance, Task *task); // Called on the main thread. +}; + +struct HistoryEntry { + String path; + String focusedItem; +}; + +struct Drive { + const char *prefix; + size_t prefixBytes; + EsVolumeInformation information; +}; + +struct FolderViewSettings { + uint16_t sortColumn; + uint8_t viewType; +}; + +struct FolderViewSettingsEntry { + char path[160]; + FolderViewSettings settings; + uint8_t pathBytes; +}; + +struct Thumbnail { + uint32_t *bits; + uint32_t width, height; + uintptr_t referenceCount; + uint32_t generatingTasksInProgress; +}; + +struct Instance : EsInstance { + // Interface elements. + + EsListView *list; + EsListView *placesView; + EsTextbox *breadcrumbBar; + EsButton *newFolderButton; + EsTextDisplay *status; + + union { + struct { + EsTextbox *textbox; + uintptr_t index; + } rename; + }; + + // Path and history. + + String path; + Array pathBackwardHistory; + Array pathForwardHistory; + + // Commands. + + EsCommand commandGoBackwards, commandGoForwards, commandGoParent; + EsCommand commandNewFolder, commandRename; + EsCommand commandViewDetails, commandViewTiles, commandViewThumbnails; + EsCommand commandRefresh; + + // Active folder. + + struct Folder *folder; + + // Sorted and filtered list contents. + + Array listContents; + + size_t selectedItemCount; + EsFileOffset selectedItemsTotalSize; + + String delayedFocusItem; + + FolderViewSettings viewSettings; + + // Blocking task thread. + // Tasks that block the use of the instance, + // but display progress and can be (optionally) cancelled. + // Shows the dialog after some threshold. +#define BLOCKING_TASK_DIALOG_THRESHOLD_MS (100) + Task blockingTask; + volatile bool blockingTaskInProgress, blockingTaskReachedTimeout; + volatile uint32_t blockingTaskID; +}; + +struct NamespaceHandler { + uint8_t type; + uint8_t rootContainerHandlerType; + + bool (*handlesPath)(String path); + + uint32_t (*getFileType)(String path); + void (*getVisibleName)(EsBuffer *buffer, String path); + void (*getTotalSize)(Folder *folder); // Possibly called on the blocking task thread. + String (*getPathForChildFolder)(Folder *folder, String item); + void (*getDefaultViewSettings)(Folder *folder, FolderViewSettings *settings); + + // Called on the blocking task thread: + EsError (*createChildFolder)(Folder *folder, String *name, bool findUniqueName); // Optional. + EsError (*renameItem)(Folder *folder, String oldName, String name); // Optional. + EsError (*enumerate)(Folder *folder); +}; + +struct Folder { + HashTable entries; + EsArena entryArena; + + Array attachedInstances; + Array attachingInstances; + + String path; + bool recurse; + bool refreshing; + bool driveRemoved; + + EsFileOffset spaceTotal; + EsFileOffset spaceUsed; + + NamespaceHandler *itemHandler, *containerHandler; + + const char *cEmptyMessage; +}; + +void InstanceReportError(struct Instance *instance, int error, EsError code); +bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, int historyMode = 0); +void InstanceUpdateStatusString(Instance *instance); +void InstanceViewSettingsUpdated(Instance *instance); +void InstanceRefreshViewType(Instance *instance); +void InstanceFolderPathChanged(Instance *instance, bool fromLoadFolder); +void InstanceAddContents(struct Instance *instance, HashTable *newEntries); +void InstanceAddSingle(struct Instance *instance, ListEntry newEntry); +void InstanceRemoveContents(struct Instance *instance); +ListEntry InstanceRemoveSingle(Instance *instance, FolderEntry *folderEntry); +ListEntry *InstanceGetSelectedListEntry(Instance *instance); +void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename); +FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, EsDirectoryChild *information, uint64_t id = 0); +void FolderAddEntries(Folder *folder, EsDirectoryChild *buffer, size_t entryCount); +uint32_t NamespaceDefaultGetFileType(String); + +Array drives; +EsMutex drivesMutex; + +Array instances; + +Array bookmarks; +#define FOLDER_VIEW_SETTINGS_MAXIMUM_ENTRIES (10000) +Array folderViewSettings; +HashStore thumbnailCache; + +// Non-blocking task thread. + +EsHandle nonBlockingTaskWorkAvailable; +EsMutex nonBlockingTaskMutex; +Array nonBlockingTasks; + +// Styles. + +const EsStyle styleFolderView = { + .inherit = ES_STYLE_LIST_VIEW, + + .metrics = { + .mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH, + .preferredWidth = 200, + .minimumWidth = 150, + }, +}; + +const EsStyle styleFolderViewTiled = { + .inherit = ES_STYLE_LIST_VIEW, + + .metrics = { + .mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_GAP_MINOR, + .preferredWidth = 200, + .minimumWidth = 150, + .gapMinor = 5, + .gapWrap = 5, + }, +}; + +const EsStyle styleFolderItemThumbnail = { + .inherit = ES_STYLE_LIST_ITEM_TILE, + + .metrics = { + .mask = ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_PREFERRED_HEIGHT + | ES_THEME_METRICS_ICON_SIZE | ES_THEME_METRICS_LAYOUT_VERTICAL | ES_THEME_METRICS_INSETS | ES_THEME_METRICS_TEXT_ALIGN, + .insets = ES_RECT_2(5, 10), + .preferredWidth = 170, + .preferredHeight = 150, + .textAlign = ES_TEXT_H_CENTER | ES_TEXT_V_CENTER | ES_TEXT_ELLIPSIS, + .iconSize = 80, + .layoutVertical = true, + }, +}; + +const EsStyle stylePlacesView = { + .inherit = ES_STYLE_LIST_VIEW, + + .metrics = { + .mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_INSETS + | ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_GAP_MAJOR, + .insets = ES_RECT_1(8), + .preferredWidth = 200, + .minimumWidth = 150, + .gapMajor = 16, + .gapWrap = 16, + }, +}; + +void BlockingTaskThread(EsGeneric _instance) { + Instance *instance = (Instance *) _instance.p; + instance->blockingTask.callback(instance, &instance->blockingTask); + EsMessage m = { MESSAGE_BLOCKING_TASK_COMPLETE }; + m.user.context1.p = instance; + m.user.context2.u = instance->blockingTaskID; + EsMessagePost(nullptr, &m); +} + +void BlockingTaskComplete(Instance *instance) { + EsAssert(instance->blockingTaskInProgress); // Task should have been in progress. + instance->blockingTaskInProgress = false; + if (instance->blockingTaskReachedTimeout) EsDialogClose(instance->window); + Task *task = &instance->blockingTask; + if (task->then) task->then(instance, task); +} + +void BlockingTaskQueue(Instance *instance, Task task) { + EsAssert(!instance->blockingTaskInProgress); // Cannot queue a new blocking task if the previous has not finished. + + instance->blockingTask = task; + instance->blockingTaskInProgress = true; + + EsThreadInformation thread; + EsThreadCreate(BlockingTaskThread, &thread, instance); + + ptrdiff_t result = EsWait(&thread.handle, 1, BLOCKING_TASK_DIALOG_THRESHOLD_MS); + EsHandleClose(thread.handle); + + if (result == ES_ERROR_TIMEOUT_REACHED) { + instance->blockingTaskReachedTimeout = true; + EsDialogShowAlert(instance->window, task.cDescription, -1, INTERFACE_STRING(FileManagerOngoingTaskDescription), ES_ICON_TOOLS_TIMER_SYMBOLIC); + // TODO Progress bar; cancelling tasks. + } else { + instance->blockingTaskReachedTimeout = false; + BlockingTaskComplete(instance); + instance->blockingTaskID++; // Prevent the task being completed twice. + } +} + +void NonBlockingTaskThread(EsGeneric) { + while (true) { + EsWait(&nonBlockingTaskWorkAvailable, 1, ES_WAIT_NO_TIMEOUT); + + while (true) { + EsMutexAcquire(&nonBlockingTaskMutex); + + if (!nonBlockingTasks.Length()) { + EsMutexRelease(&nonBlockingTaskMutex); + break; + } + + Task *task = nonBlockingTasks[0]; + nonBlockingTasks.Delete(0); + EsMutexRelease(&nonBlockingTaskMutex); + + task->callback(nullptr, task); + + EsMessage m = { MESSAGE_NON_BLOCKING_TASK_COMPLETE }; + m.user.context2.p = task; + EsMessagePost(nullptr, &m); + } + } +} + +void NonBlockingTaskQueue(Task _task) { + // NOTE We can't store instances in tasks on the non-blocking queue thread, + // because the instances might be destroyed while the task is in progress! + + Task *task = (Task *) EsHeapAllocate(sizeof(Task), false); + EsMemoryCopy(task, &_task, sizeof(Task)); + EsMutexAcquire(&nonBlockingTaskMutex); + nonBlockingTasks.Add(task); + EsMutexRelease(&nonBlockingTaskMutex); + EsEventSet(nonBlockingTaskWorkAvailable); +} + +void NonBlockingTaskComplete(EsMessage *message) { + Task *task = (Task *) message->user.context2.p; + if (task->then) task->then(nullptr, task); + EsHeapFree(task); +} + +void ConfigurationSave() { + EsBuffer buffer = {}; + buffer.canGrow = true; + + EsBufferFormat(&buffer, "[bookmarks]\n"); + + for (uintptr_t i = 0; i < bookmarks.Length(); i++) { + EsBufferFormat(&buffer, "=%s\n", STRFMT(bookmarks[i])); + } + + for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) { + FolderViewSettingsEntry *entry = &folderViewSettings[i]; + EsBufferFormat(&buffer, "\n[@folder]\npath=%z\nsort_column=%d\nview_type=%d\n", + entry->path, entry->settings.sortColumn, entry->settings.viewType); + } + + EsFileWriteAll(EsLiteral(SETTINGS_FILE), buffer.out, buffer.position); + EsHeapFree(buffer.out); +} + +#include "type_database.cpp" +#include "folder.cpp" +#include "commands.cpp" +#include "ui.cpp" + +void DriveRemove(const char *prefix, size_t prefixBytes) { + if (!prefixBytes || prefix[0] == '|') return; + EsMutexAcquire(&drivesMutex); + bool found = false; + + for (uintptr_t index = 0; index < drives.Length(); index++) { + if (0 == EsStringCompareRaw(prefix, prefixBytes, drives[index].prefix, drives[index].prefixBytes)) { + drives.Delete(index); + found = true; + + for (uintptr_t i = 0; i < instances.Length(); i++) { + EsListViewRemove(instances[i]->placesView, PLACES_VIEW_GROUP_DRIVES, index, index); + } + + break; + } + } + + EsAssert(found); + EsMutexRelease(&drivesMutex); + + EsMutexAcquire(&loadedFoldersMutex); + + for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { + Folder *folder = loadedFolders[i]; + + if (folder->itemHandler->type == NAMESPACE_HANDLER_DRIVES_PAGE + || StringStartsWith(folder->path, StringFromLiteralWithSize(prefix, prefixBytes))) { + folder->driveRemoved = true; + FolderRefresh(folder); + } + } + + EsMutexRelease(&loadedFoldersMutex); +} + +void DriveAdd(const char *prefix, size_t prefixBytes) { + if (!prefixBytes || prefix[0] == '|') return; + + EsMutexAcquire(&drivesMutex); + + Drive drive = {}; + drive.prefix = prefix; + drive.prefixBytes = prefixBytes; + EsMountPointGetVolumeInformation(prefix, prefixBytes, &drive.information); + drives.Add(drive); + + for (uintptr_t i = 0; i < instances.Length(); i++) { + EsListViewInsert(instances[i]->placesView, PLACES_VIEW_GROUP_DRIVES, drives.Length(), drives.Length()); + } + + EsMutexRelease(&drivesMutex); + + for (uintptr_t i = 0; i < instances.Length(); i++) { + if (instances[i]->folder->itemHandler->type == NAMESPACE_HANDLER_DRIVES_PAGE + || StringStartsWith(instances[i]->folder->path, StringFromLiteralWithSize(prefix, prefixBytes))) { + FolderRefresh(instances[i]->folder); + } + } +} + +void LoadSettings() { + EsINIState state = { (char *) EsFileReadAll(EsLiteral(SETTINGS_FILE), &state.bytes) }; + FolderViewSettings *folder = nullptr; + + while (EsINIParse(&state)) { + if (state.value && 0 == EsStringCompareRaw(state.section, state.sectionBytes, EsLiteral("bookmarks"))) { + String string = {}; + string.text = state.value, string.bytes = state.valueBytes; + BookmarkAdd(string, false); + } else if (0 == EsStringCompareRaw(state.sectionClass, state.sectionClassBytes, EsLiteral("folder"))) { + if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("path"))) { + if (state.keyBytes < sizeof(folderViewSettings[0].path)) { + FolderViewSettingsEntry *entry = folderViewSettings.Add(); + EsMemoryCopy(entry->path, state.value, state.valueBytes); + entry->pathBytes = state.valueBytes; + folder = &entry->settings; + } + } else if (folder && 0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("sort_column"))) { + folder->sortColumn = EsIntegerParse(state.value, state.valueBytes); + } else if (folder && 0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("view_type"))) { + folder->viewType = EsIntegerParse(state.value, state.valueBytes); + } + } + } +} + +void _start() { + _init(); + + AddKnownFileTypes(); + LoadSettings(); + + // Enumerate drives. + + EsMountPointEnumerate([] (const char *prefix, size_t prefixBytes, EsGeneric) { + DriveAdd(prefix, prefixBytes); + }, 0); + + // Start the non-blocking task threads. + + nonBlockingTaskWorkAvailable = EsEventCreate(true /* autoReset */); + EsThreadInformation _nonBlockingTaskThread = {}; + + for (uintptr_t i = 0; i < EsSystemGetConstant(ES_SYSTEM_CONSTANT_OPTIMAL_WORK_QUEUE_THREAD_COUNT); i++) { + EsThreadCreate(NonBlockingTaskThread, &_nonBlockingTaskThread, nullptr); + } + + // Process messages. + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(FileManagerTitle)); + instances.Add(instance); + InstanceCreateUI(instance); + } else if (message->type == ES_MSG_INSTANCE_DESTROY) { + // TODO Cleanup/cancel any unfinished non-blocking tasks before we get here! + + Instance *instance = message->instanceDestroy.instance; + InstanceDestroy(instance); + instances.FindAndDeleteSwap(instance, true); + } else if (message->type == ES_MSG_REGISTER_FILE_SYSTEM) { + DriveAdd(message->registerFileSystem.mountPoint->prefix, message->registerFileSystem.mountPoint->prefixBytes); + } else if (message->type == ES_MSG_UNREGISTER_FILE_SYSTEM) { + DriveRemove(message->unregisterFileSystem.mountPoint->prefix, message->unregisterFileSystem.mountPoint->prefixBytes); + } else if (message->type == ES_MSG_FILE_MANAGER_FILE_MODIFIED) { + char *_path = (char *) EsHeapAllocate(message->user.context2.u, false); + EsConstantBufferRead(message->user.context1.u, _path); + String fullPath = StringFromLiteralWithSize(_path, message->user.context2.u); + size_t pathSectionCount = PathCountSections(fullPath); + + for (uintptr_t i = 0; i < pathSectionCount; i++) { + String path = PathGetParent(fullPath, i + 1); + String file = PathGetSection(fullPath, i + 1); + String folder = PathGetParent(path); + EsDirectoryChild information = {}; + + if (EsPathQueryInformation(STRING(path), &information)) { + EsMutexAcquire(&loadedFoldersMutex); + + for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { + if (loadedFolders[i]->itemHandler->type != NAMESPACE_HANDLER_FILE_SYSTEM) continue; + if (EsStringCompareRaw(STRING(loadedFolders[i]->path), STRING(folder))) continue; + FolderAddEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes, &information, nullptr, true); + } + + EsMutexRelease(&loadedFoldersMutex); + } + } + + EsHandleClose(message->user.context1.u); + EsHeapFree(_path); + } else if (message->type == MESSAGE_BLOCKING_TASK_COMPLETE) { + Instance *instance = (Instance *) message->user.context1.p; + if (message->user.context2.u == instance->blockingTaskID) BlockingTaskComplete(instance); + } else if (message->type == MESSAGE_NON_BLOCKING_TASK_COMPLETE) { + NonBlockingTaskComplete(message); + } + } +} diff --git a/apps/file_manager/string.cpp b/apps/file_manager/string.cpp new file mode 100644 index 0000000..d321641 --- /dev/null +++ b/apps/file_manager/string.cpp @@ -0,0 +1,201 @@ +struct String { + char *text; + size_t bytes, allocated; +}; + +String StringAllocateAndFormat(const char *format, ...) { + String string = {}; + va_list arguments; + va_start(arguments, format); + string.text = EsStringAllocateAndFormatV(&string.bytes, format, arguments); + va_end(arguments); + string.allocated = string.bytes; + return string; +} + +String StringFromLiteral(const char *literal) { + String string = {}; + string.text = (char *) literal; + string.bytes = EsCStringLength(literal); + return string; +} + +String StringFromLiteralWithSize(const char *literal, ptrdiff_t bytes) { + String string = {}; + string.text = (char *) literal; + string.bytes = bytes == -1 ? EsCStringLength(literal) : bytes; + return string; +} + +void StringAppend(String *string, String with) { + if (string->bytes + with.bytes > string->allocated) { + string->allocated = (string->allocated + with.bytes) * 2; + string->text = (char *) EsHeapReallocate(string->text, string->allocated, false); + } + + EsMemoryCopy(string->text + string->bytes, with.text, with.bytes); + string->bytes += with.bytes; +} + +void StringDestroy(String *string) { + EsAssert(string->allocated == string->bytes); // Attempting to free a partial string. + EsHeapFree(string->text); + string->text = nullptr; + string->bytes = string->allocated = 0; +} + +String StringDuplicate(String string) { + String result = {}; + result.bytes = result.allocated = string.bytes; + result.text = (char *) EsHeapAllocate(result.bytes + 1, false); + result.text[result.bytes] = 0; + EsMemoryCopy(result.text, string.text, result.bytes); + return result; +} + +inline bool StringStartsWith(String a, String b) { + return a.bytes >= b.bytes && 0 == EsMemoryCompare(a.text, b.text, b.bytes); +} + +inline bool StringEndsWith(String a, String b) { + return a.bytes >= b.bytes && 0 == EsMemoryCompare(a.text + a.bytes - b.bytes, b.text, b.bytes); +} + +inline bool StringEquals(String a, String b) { + return a.bytes == b.bytes && 0 == EsMemoryCompare(a.text, b.text, a.bytes); +} + +String StringSlice(String string, uintptr_t offset, ptrdiff_t length) { + if (length == -1) { + length = string.bytes - offset; + } + + string.text += offset; + string.bytes = length; + string.allocated = 0; + return string; +} + +#define STRING(x) x.text, x.bytes +#define STRFMT(x) x.bytes, x.text + +uintptr_t PathCountSections(String string) { + ptrdiff_t sectionCount = 0; + + for (uintptr_t i = 0; i < string.bytes; i++) { + if (string.text[i] == '/') { + sectionCount++; + } + } + + return sectionCount; +} + +String PathGetSection(String string, ptrdiff_t index) { + String output = {}; + size_t stringBytes = string.bytes; + char *text = string.text; + + if (index < 0) { + index += PathCountSections(string); + } + + if (index < 0) { + return output; + } + + uintptr_t i = 0, bytes = 0; + + for (; index && i < stringBytes; i++) { + if (text[i] == '/') { + index--; + } + } + + if (index) { + return output; + } + + output.text = text + i; + + for (; i < stringBytes; i++) { + if (text[i] == '/') { + break; + } else { + bytes++; + } + } + + output.bytes = bytes; + + return output; +} + +String PathGetParent(String string, uintptr_t index) { + if (index == PathCountSections(string)) return string; + String section = PathGetSection(string, index); + string.bytes = section.bytes + section.text - string.text + 1; + return string; +} + +String PathGetExtension(String string) { + String extension = {}; + int lastSeparator = 0; + + for (intptr_t i = string.bytes - 1; i >= 0; i--) { + if (string.text[i] == '.') { + lastSeparator = i; + break; + } + } + + if (!lastSeparator && string.text[0] != '.') { + extension.text = string.text + string.bytes; + extension.bytes = 0; + return extension; + } else { + extension.text = string.text + lastSeparator + 1; + extension.bytes = string.bytes - lastSeparator - 1; + return extension; + } +} + +String PathGetParent(String string) { + size_t newPathBytes = 0; + + for (uintptr_t i = 0; i < string.bytes - 1; i++) { + if (string.text[i] == '/') { + newPathBytes = i + 1; + } + } + + String result = {}; + result.bytes = newPathBytes; + result.text = string.text; + + return result; +} + +bool PathHasPrefix(String path, String prefix) { + return StringStartsWith(path, prefix) && path.bytes > prefix.bytes && path.text[prefix.bytes] == '/'; +} + +bool PathReplacePrefix(String *knownPath, String oldPath, String newPath) { + if (PathHasPrefix(*knownPath, oldPath)) { + String after = StringSlice(*knownPath, oldPath.bytes, -1); + String path = StringAllocateAndFormat("%s%s", STRFMT(newPath), STRFMT(after)); + StringDestroy(knownPath); + *knownPath = path; + return true; + } + + return false; +} + +String PathRemoveTrailingSlash(String path) { + if (path.bytes && path.text[path.bytes - 1] == '/') { + path.bytes--; + } + + return path; +} diff --git a/apps/file_manager/type_database.cpp b/apps/file_manager/type_database.cpp new file mode 100644 index 0000000..9f31f3b --- /dev/null +++ b/apps/file_manager/type_database.cpp @@ -0,0 +1,110 @@ +struct FileType { + char *name; + size_t nameBytes; + uint32_t iconID; + int64_t openHandler; + + // TODO Allow applications to register their own thumbnail generators. + bool hasThumbnailGenerator; +}; + +Array knownFileTypes; +HashStore knownFileTypesByExtension; + +void AddKnownFileTypes() { +#define ADD_FILE_TYPE(_extension, _name, _iconID) \ + { \ + FileType type = {}; \ + type.name = (char *) _name; \ + type.iconID = _iconID; \ + uintptr_t index = knownFileTypes.Length(); \ + knownFileTypes.Add(type); \ + *knownFileTypesByExtension.Put(_extension, EsCStringLength(_extension)) = index; \ + } + +#define KNOWN_FILE_TYPE_DIRECTORY (0) + ADD_FILE_TYPE("", interfaceString_CommonItemFolder, ES_ICON_FOLDER); +#define KNOWN_FILE_TYPE_UNKNOWN (1) + ADD_FILE_TYPE("", interfaceString_CommonItemFile, ES_ICON_UNKNOWN); +#define KNOWN_FILE_TYPE_DRIVE_HDD (2) + ADD_FILE_TYPE("", interfaceString_CommonDriveHDD, ES_ICON_DRIVE_HARDDISK); +#define KNOWN_FILE_TYPE_DRIVE_SSD (3) + ADD_FILE_TYPE("", interfaceString_CommonDriveSSD, ES_ICON_DRIVE_HARDDISK_SOLIDSTATE); +#define KNOWN_FILE_TYPE_DRIVE_CDROM (4) + ADD_FILE_TYPE("", interfaceString_CommonDriveCDROM, ES_ICON_MEDIA_OPTICAL); +#define KNOWN_FILE_TYPE_DRIVE_USB_MASS_STORAGE (5) + ADD_FILE_TYPE("", interfaceString_CommonDriveUSBMassStorage, ES_ICON_DRIVE_REMOVABLE_MEDIA_USB); +#define KNOWN_FILE_TYPE_DRIVES_PAGE (6) + ADD_FILE_TYPE("", interfaceString_FileManagerDrivesPage, ES_ICON_COMPUTER_LAPTOP); + + size_t groupCount; + EsSystemConfigurationGroup *groups = EsSystemConfigurationReadAll(&groupCount); + + for (uintptr_t i = 0; i < groupCount; i++) { + EsSystemConfigurationGroup *group = groups + i; + + if (EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, EsLiteral("file_type"))) { + continue; + } + + FileType type = {}; + + type.name = EsSystemConfigurationGroupReadString(group, "name", -1, &type.nameBytes); + type.openHandler = EsSystemConfigurationGroupReadInteger(group, "open", -1); + + char *iconName = EsSystemConfigurationGroupReadString(group, "icon", -1); + + if (iconName) { + type.iconID = EsIconIDFromString(iconName); + EsHeapFree(iconName); + } + + char *extension = EsSystemConfigurationGroupReadString(group, "extension", -1); + + // TODO Proper thumbnail generator registrations. + + if (0 == EsCRTstrcmp(extension, "jpg") || 0 == EsCRTstrcmp(extension, "png") || 0 == EsCRTstrcmp(extension, "bmp")) { + type.hasThumbnailGenerator = true; + } + + uintptr_t index = knownFileTypes.Length(); + knownFileTypes.Add(type); + *knownFileTypesByExtension.Put(extension, EsCStringLength(extension)) = index; + } +} + +FileType *FolderEntryGetType(Folder *folder, FolderEntry *entry) { + if (entry->isFolder) { + if (folder->itemHandler->getFileType != NamespaceDefaultGetFileType) { + String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetInternalName())); + FileType *type = &knownFileTypes[folder->itemHandler->getFileType(path)]; + StringDestroy(&path); + return type; + } else { + return &knownFileTypes[KNOWN_FILE_TYPE_DIRECTORY]; + } + } else { + String extension = entry->GetExtension(); + char buffer[32]; + uintptr_t i = 0; + + for (; i < extension.bytes && i < 32; i++) { + if (EsCRTisupper(extension.text[i])) { + buffer[i] = EsCRTtolower(extension.text[i]); + } else { + buffer[i] = extension.text[i]; + } + } + + uintptr_t index = knownFileTypesByExtension.Get1(buffer, i); + return &knownFileTypes[index ? index : KNOWN_FILE_TYPE_UNKNOWN]; + } +} + +uint32_t IconFromDriveType(uint8_t driveType) { + if (driveType == ES_DRIVE_TYPE_HDD ) return ES_ICON_DRIVE_HARDDISK; + if (driveType == ES_DRIVE_TYPE_SSD ) return ES_ICON_DRIVE_HARDDISK_SOLIDSTATE; + if (driveType == ES_DRIVE_TYPE_CDROM ) return ES_ICON_MEDIA_OPTICAL; + if (driveType == ES_DRIVE_TYPE_USB_MASS_STORAGE) return ES_ICON_DRIVE_REMOVABLE_MEDIA_USB; + return ES_ICON_DRIVE_HARDDISK; +} diff --git a/apps/file_manager/ui.cpp b/apps/file_manager/ui.cpp new file mode 100644 index 0000000..63217dc --- /dev/null +++ b/apps/file_manager/ui.cpp @@ -0,0 +1,1079 @@ +// TODO Custom columns. + +void InstanceFolderPathChanged(Instance *instance, bool fromLoadFolder) { + if (fromLoadFolder) { + // Don't free the old path; it's used in the history stack. + } else { + StringDestroy(&instance->path); + } + + instance->path = StringDuplicate(instance->folder->path); + EsTextboxSelectAll(instance->breadcrumbBar); + EsTextboxInsert(instance->breadcrumbBar, STRING(instance->path)); + + uint8_t _buffer[256]; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + instance->folder->containerHandler->getVisibleName(&buffer, instance->path); + EsWindowSetTitle(instance->window, (char *) buffer.out, buffer.position); + EsWindowSetIcon(instance->window, knownFileTypes[instance->folder->containerHandler->getFileType(instance->path)].iconID); + + EsListViewEnumerateVisibleItems(instance->list, [] (EsListView *, EsElement *item, uint32_t, EsGeneric index) { + ListItemCreated(item, index.u, true); + }); +} + +bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, int historyMode) { + // Check if the path hasn't changed. + + if (instance->folder && !instance->folder->refreshing && StringEquals(path, instance->folder->path)) { + StringDestroy(&path); + return true; + } + + Task task = {}; + + task.context = historyMode; + task.string = path; + task.cDescription = interfaceString_FileManagerOpenFolderTask; + + task.callback = [] (Instance *instance, Task *task) { + Folder *newFolder = nullptr; + task->result = FolderAttachInstance(instance, task->string, false, &newFolder); + task->context2 = newFolder; + StringDestroy(&task->string); + }; + + task.then = [] (Instance *instance, Task *task) { + if (ES_CHECK_ERROR(task->result)) { + EsTextboxSelectAll(instance->breadcrumbBar); + EsTextboxInsert(instance->breadcrumbBar, STRING(instance->path)); + InstanceReportError(instance, ERROR_LOAD_FOLDER, task->result); + return; + } + + int historyMode = task->context.i & 0xFF; + int flags = task->context.i; + Folder *folder = (Folder *) task->context2.p; + + // Add the path to the history array. + + HistoryEntry historyEntry = {}; + historyEntry.path = instance->path; + + EsGeneric focusedIndex; + + if (EsListViewGetFocusedItem(instance->list, nullptr, &focusedIndex)) { + String name = instance->listContents[focusedIndex.u].entry->GetName(); + historyEntry.focusedItem = StringDuplicate(name); + } + + if (historyMode == LOAD_FOLDER_BACK) { + instance->pathForwardHistory.Add(historyEntry); + } else if (historyMode == LOAD_FOLDER_FORWARD) { + instance->pathBackwardHistory.Add(historyEntry); + } else if (historyMode == LOAD_FOLDER_START || historyMode == LOAD_FOLDER_REFRESH) { + } else { + instance->pathBackwardHistory.Add(historyEntry); + + for (int i = 0; i < (int) instance->pathForwardHistory.Length(); i++) { + StringDestroy(&instance->pathForwardHistory[i].path); + StringDestroy(&instance->pathForwardHistory[i].focusedItem); + } + + instance->pathForwardHistory.SetLength(0); + } + + // Update commands. + + EsCommandSetDisabled(&instance->commandGoBackwards, !instance->pathBackwardHistory.Length()); + EsCommandSetDisabled(&instance->commandGoForwards, !instance->pathForwardHistory.Length()); + EsCommandSetDisabled(&instance->commandGoParent, PathCountSections(folder->path) == 1); + EsCommandSetDisabled(&instance->commandNewFolder, !folder->itemHandler->createChildFolder); + EsCommandSetDisabled(&instance->commandRename, true); + EsCommandSetDisabled(&instance->commandRefresh, false); + + // Detach from the old folder. + + EsMutexAcquire(&loadedFoldersMutex); + + InstanceRemoveContents(instance); + FolderDetachInstance(instance); + + // Load the view settings for the folder. + + bool foundViewSettings = false; + + if (folder->path.bytes < sizeof(folderViewSettings[0].path)) { + for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) { + if (folderViewSettings[i].pathBytes == folder->path.bytes + && 0 == EsMemoryCompare(folderViewSettings[i].path, STRING(folder->path))) { + foundViewSettings = true; + FolderViewSettingsEntry entry = folderViewSettings[i]; + instance->viewSettings = entry.settings; + folderViewSettings.Delete(i); + folderViewSettings.Add(entry); // Update the LRU order. + break; + } + } + } + + if (!foundViewSettings) { + if (folder->itemHandler->getDefaultViewSettings) { + folder->itemHandler->getDefaultViewSettings(folder, &instance->viewSettings); + } else { + // TODO Get default from configuration. + instance->viewSettings.sortColumn = COLUMN_NAME; + instance->viewSettings.viewType = VIEW_DETAILS; + } + } + + InstanceRefreshViewType(instance); + + // Attach to the new folder. + + folder->attachingInstances.FindAndDeleteSwap(instance, true); + instance->folder = folder; + folder->attachedInstances.Add(instance); + + EsListViewSetEmptyMessage(instance->list, folder->cEmptyMessage); + InstanceAddContents(instance, &folder->entries); + + EsMutexRelease(&loadedFoldersMutex); + + InstanceFolderPathChanged(instance, true); + + if (~flags & LOAD_FOLDER_NO_FOCUS) { + EsElementFocus(instance->list); + } + + EsListViewInvalidateAll(instance->placesView); + }; + + BlockingTaskQueue(instance, task); + return true; +} + +void InstanceRefreshViewType(Instance *instance) { + if (instance->viewSettings.viewType == VIEW_DETAILS) { + EsCommandSetCheck(&instance->commandViewDetails, ES_CHECK_CHECKED, false); + EsCommandSetCheck(&instance->commandViewTiles, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(&instance->commandViewThumbnails, ES_CHECK_UNCHECKED, false); + + EsListViewChangeStyles(instance->list, &styleFolderView, ES_STYLE_LIST_ITEM, nullptr, nullptr, ES_LIST_VIEW_COLUMNS, ES_LIST_VIEW_TILED); + EsListViewSetColumns(instance->list, folderOutputColumns, sizeof(folderOutputColumns) / sizeof(folderOutputColumns[0])); + } else if (instance->viewSettings.viewType == VIEW_TILES) { + EsCommandSetCheck(&instance->commandViewTiles, ES_CHECK_CHECKED, false); + EsCommandSetCheck(&instance->commandViewDetails, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(&instance->commandViewThumbnails, ES_CHECK_UNCHECKED, false); + + EsListViewChangeStyles(instance->list, &styleFolderViewTiled, ES_STYLE_LIST_ITEM_TILE, nullptr, nullptr, ES_LIST_VIEW_TILED, ES_LIST_VIEW_COLUMNS); + } else if (instance->viewSettings.viewType == VIEW_THUMBNAILS) { + EsCommandSetCheck(&instance->commandViewThumbnails, ES_CHECK_CHECKED, false); + EsCommandSetCheck(&instance->commandViewTiles, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(&instance->commandViewDetails, ES_CHECK_UNCHECKED, false); + + EsListViewChangeStyles(instance->list, &styleFolderViewTiled, &styleFolderItemThumbnail, nullptr, nullptr, ES_LIST_VIEW_TILED, ES_LIST_VIEW_COLUMNS); + } +} + +void InstanceUpdateItemSelectionCountCommands(Instance *instance) { + EsCommandSetDisabled(&instance->commandRename, instance->selectedItemCount != 1 || !instance->folder->itemHandler->renameItem); +} + +int InstanceCompareFolderEntries(FolderEntry *left, FolderEntry *right, uint16_t sortColumn) { + bool descending = sortColumn & (1 << 8); + sortColumn &= 0xFF; + + int result = 0; + + if (!left->isFolder && right->isFolder) { + result = 1; + } else if (!right->isFolder && left->isFolder) { + result = -1; + } else { + if (sortColumn == COLUMN_NAME) { + result = EsStringCompare(STRING(left->GetName()), STRING(right->GetName())); + } else if (sortColumn == COLUMN_TYPE) { + result = EsStringCompare(STRING(left->GetExtension()), STRING(right->GetExtension())); + } else if (sortColumn == COLUMN_SIZE) { + if (right->size < left->size) result = 1; + else if (right->size > left->size) result = -1; + else result = 0; + } + + if (!result && sortColumn != COLUMN_NAME) { + result = EsStringCompare(STRING(left->GetName()), STRING(right->GetName())); + } + + if (!result) { + if ((uintptr_t) right > (uintptr_t) left) result = 1; + else if ((uintptr_t) right < (uintptr_t) left) result = -1; + else result = 0; + } + } + + return descending ? -result : result; +} + +bool InstanceAddInternal(Instance *instance, ListEntry *entry) { + // TODO Filtering. + + entry->entry->handles++; + + if (entry->selected) { + instance->selectedItemCount++; + instance->selectedItemsTotalSize += entry->entry->size; + InstanceUpdateItemSelectionCountCommands(instance); + } + + return true; +} + +void InstanceRemoveInternal(Instance *instance, ListEntry *entry) { + FolderEntryCloseHandle(instance->folder, entry->entry); + + if (entry->selected) { + instance->selectedItemCount--; + instance->selectedItemsTotalSize -= entry->entry->size; + InstanceUpdateItemSelectionCountCommands(instance); + } +} + +void InstanceUpdateStatusString(Instance *instance) { + // TODO Localization. + + char buffer[1024]; + size_t bytes; + + size_t itemCount = instance->listContents.Length(); + + if (instance->selectedItemCount) { + bytes = EsStringFormat(buffer, sizeof(buffer), "Selected %d item%z (%D)", + instance->selectedItemCount, instance->selectedItemCount == 1 ? "" : "s", instance->selectedItemsTotalSize); + } else { + if (itemCount) { + if (instance->folder->spaceUsed) { + if (instance->folder->spaceTotal) { + bytes = EsStringFormat(buffer, sizeof(buffer), "%d item%z " HYPHENATION_POINT " %D out of %D used", + itemCount, itemCount == 1 ? "" : "s", instance->folder->spaceUsed, instance->folder->spaceTotal); + } else { + bytes = EsStringFormat(buffer, sizeof(buffer), "%d item%z (%D)", + itemCount, itemCount == 1 ? "" : "s", instance->folder->spaceUsed); + } + } else { + bytes = EsStringFormat(buffer, sizeof(buffer), "%d item%z", + itemCount, itemCount == 1 ? "" : "s"); + } + } else { + if (instance->folder && instance->folder->itemHandler->type == NAMESPACE_HANDLER_INVALID) { + bytes = EsStringFormat(buffer, sizeof(buffer), "Invalid path"); + } else { + bytes = EsStringFormat(buffer, sizeof(buffer), "Empty folder"); + } + } + } + + EsTextDisplaySetContents(instance->status, buffer, bytes); +} + +void InstanceAddSingle(Instance *instance, ListEntry entry) { + // Call with loadedFoldersMutex and message mutex. + + if (!InstanceAddInternal(instance, &entry)) { + return; // Filtered out. + } + + uintptr_t low = 0, high = instance->listContents.Length(); + + while (low < high) { + uintptr_t middle = (low + high) / 2; + int compare = InstanceCompareFolderEntries(instance->listContents[middle].entry, entry.entry, instance->viewSettings.sortColumn); + + if (compare == 0) { + // The entry is already in the list. + + EsListViewInvalidateContent(instance->list, 0, middle); + InstanceRemoveInternal(instance, &entry); + + ListEntry *existingEntry = &instance->listContents[middle]; + + if (existingEntry->selected) { + instance->selectedItemsTotalSize += existingEntry->entry->size - existingEntry->entry->previousSize; + InstanceUpdateStatusString(instance); + } + + return; + } else if (compare > 0) { + high = middle; + } else { + low = middle + 1; + } + } + + instance->listContents.Insert(entry, low); + EsListViewInsert(instance->list, 0, low, low); + + if (entry.selected) { + EsListViewSelect(instance->list, 0, low); + EsListViewFocusItem(instance->list, 0, low); + } + + InstanceUpdateStatusString(instance); +} + +ES_MACRO_SORT(InstanceSortListContents, ListEntry, { + result = InstanceCompareFolderEntries(_left->entry, _right->entry, context); +}, uint16_t); + +void InstanceAddContents(Instance *instance, HashTable *newEntries) { + // Call with loadedFoldersMutex and message mutex. + + size_t oldListEntryCount = instance->listContents.Length(); + + for (uintptr_t i = 0; i < newEntries->slotCount; i++) { + if (!newEntries->slots[i].key.used) { + continue; + } + + ListEntry entry = {}; + entry.entry = (FolderEntry *) newEntries->slots[i].value; + + if (InstanceAddInternal(instance, &entry)) { + instance->listContents.Add(entry); + } + } + + if (oldListEntryCount) { + EsListViewRemove(instance->list, 0, 0, oldListEntryCount - 1); + } + + InstanceSortListContents(instance->listContents.array, instance->listContents.Length(), instance->viewSettings.sortColumn); + + if (instance->listContents.Length()) { + EsListViewInsert(instance->list, 0, 0, instance->listContents.Length() - 1); + + if (instance->delayedFocusItem.bytes) { + for (uintptr_t i = 0; i < instance->listContents.Length(); i++) { + if (0 == EsStringCompareRaw(STRING(instance->listContents[i].entry->GetName()), STRING(instance->delayedFocusItem))) { + EsListViewSelect(instance->list, 0, i); + EsListViewFocusItem(instance->list, 0, i); + break; + } + } + + StringDestroy(&instance->delayedFocusItem); + } + } + + InstanceUpdateStatusString(instance); +} + +ListEntry InstanceRemoveSingle(Instance *instance, FolderEntry *folderEntry) { + uintptr_t low = 0, high = instance->listContents.Length(); + + while (low <= high) { + uintptr_t middle = (low + high) / 2; + int compare = InstanceCompareFolderEntries(instance->listContents[middle].entry, folderEntry, instance->viewSettings.sortColumn); + + if (compare == 0) { + ListEntry entry = instance->listContents[middle]; + InstanceRemoveInternal(instance, &entry); + EsListViewRemove(instance->list, 0, middle, middle); + instance->listContents.Delete(middle); + InstanceUpdateStatusString(instance); + return entry; + } else if (compare > 0) { + high = middle; + } else { + low = middle + 1; + } + } + + // It wasn't in the list. + return {}; +} + +void InstanceRemoveContents(Instance *instance) { + for (uintptr_t i = 0; i < instance->listContents.Length(); i++) { + InstanceRemoveInternal(instance, &instance->listContents[i]); + } + + EsAssert(instance->selectedItemCount == 0); // After removing all items none should be selected. + + if (instance->listContents.Length()) { + EsListViewRemove(instance->list, 0, 0, instance->listContents.Length() - 1); + EsListViewContentChanged(instance->list); + } + + instance->listContents.Free(); + InstanceUpdateStatusString(instance); +} + +ListEntry *InstanceGetSelectedListEntry(Instance *instance) { + for (uintptr_t i = 0; i < instance->listContents.Length(); i++) { + ListEntry *entry = &instance->listContents[i]; + + if (entry->selected) { + return entry; + } + } + + return nullptr; +} + +void InstanceViewSettingsUpdated(Instance *instance) { + if (!instance->folder) return; // We were called early in initialization of the instance; ignore. + + if (instance->path.bytes < sizeof(folderViewSettings[0].path)) { + bool foundViewSettings = false; + + for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) { + if (folderViewSettings[i].pathBytes == instance->path.bytes + && 0 == EsMemoryCompare(folderViewSettings[i].path, STRING(instance->path))) { + foundViewSettings = true; + folderViewSettings[i].settings = instance->viewSettings; + break; + } + } + + if (!foundViewSettings) { + if (folderViewSettings.Length() == FOLDER_VIEW_SETTINGS_MAXIMUM_ENTRIES) { + folderViewSettings.Delete(0); + } + + FolderViewSettingsEntry *entry = folderViewSettings.Add(); + EsMemoryCopy(entry->path, STRING(instance->path)); + entry->pathBytes = instance->path.bytes; + entry->settings = instance->viewSettings; + } + } + + ConfigurationSave(); +} + +void InstanceChangeSortColumn(EsMenu *menu, EsGeneric context) { + Instance *instance = menu->instance; + instance->viewSettings.sortColumn = context.u; + InstanceViewSettingsUpdated(instance); + InstanceSortListContents(instance->listContents.array, instance->listContents.Length(), instance->viewSettings.sortColumn); + EsListViewContentChanged(instance->list); +} + +__attribute__((optimize("-O3"))) +void ThumbnailResize(uint32_t *bits, uint32_t originalWidth, uint32_t originalHeight, uint32_t targetWidth, uint32_t targetHeight) { + // NOTE Modifies the original bits! + // NOTE It looks like this only gets vectorised in -O3. + // TODO Move into the API. + + float cx = (float) originalWidth / targetWidth; + float cy = (float) originalHeight / targetHeight; + + for (uint32_t i = 0; i < originalHeight; i++) { + uint32_t *output = bits + i * originalWidth; + uint32_t *input = output; + + for (uint32_t j = 0; j < targetWidth; j++) { + uint32_t sumAlpha = 0, sumRed = 0, sumGreen = 0, sumBlue = 0; + uint32_t count = (uint32_t) ((j + 1) * cx) - (uint32_t) (j * cx); + + for (uint32_t k = 0; k < count; k++, input++) { + uint32_t pixel = *input; + sumAlpha += (pixel >> 24) & 0xFF; + sumRed += (pixel >> 16) & 0xFF; + sumGreen += (pixel >> 8) & 0xFF; + sumBlue += (pixel >> 0) & 0xFF; + } + + sumAlpha /= count; + sumRed /= count; + sumGreen /= count; + sumBlue /= count; + + *output = (sumAlpha << 24) | (sumRed << 16) | (sumGreen << 8) | (sumBlue << 0); + output++; + } + } + + for (uint32_t i = 0; i < targetWidth; i++) { + uint32_t *output = bits + i; + uint32_t *input = output; + + for (uint32_t j = 0; j < targetHeight; j++) { + uint32_t sumAlpha = 0, sumRed = 0, sumGreen = 0, sumBlue = 0; + uint32_t count = (uint32_t) ((j + 1) * cy) - (uint32_t) (j * cy); + + for (uint32_t k = 0; k < count; k++, input += originalWidth) { + uint32_t pixel = *input; + sumAlpha += (pixel >> 24) & 0xFF; + sumRed += (pixel >> 16) & 0xFF; + sumGreen += (pixel >> 8) & 0xFF; + sumBlue += (pixel >> 0) & 0xFF; + } + + sumAlpha /= count; + sumRed /= count; + sumGreen /= count; + sumBlue /= count; + + *output = (sumAlpha << 24) | (sumRed << 16) | (sumGreen << 8) | (sumBlue << 0); + output += originalWidth; + } + } + + for (uint32_t i = 0; i < targetHeight; i++) { + for (uint32_t j = 0; j < targetWidth; j++) { + bits[i * targetWidth + j] = bits[i * originalWidth + j]; + } + } +} + +void ListItemGenerateThumbnailTask(Instance *, Task *task) { + EsMessageMutexAcquire(); + Thumbnail *thumbnail = thumbnailCache.Get(&task->id); + bool cancelTask = !thumbnail || thumbnail->referenceCount == 0; + EsMessageMutexRelease(); + + if (cancelTask) { + return; // There are no longer any list items visible for this file. + } + + size_t fileBytes; + void *file = EsFileReadAll(STRING(task->string), &fileBytes); + + if (!file) { + return; // The file could not be loaded. + } + + // TODO Allow applications to register their own thumbnail generators. + uint32_t originalWidth, originalHeight; + uint32_t *originalBits = (uint32_t *) EsImageLoad(file, fileBytes, &originalWidth, &originalHeight, 4); + EsHeapFree(file); + + if (!originalBits) { + return; // The image could not be loaded. + } + + // TODO Determine the best value for these constants -- maybe base it off the current UI scale factor? + uint32_t thumbnailMaximumWidth = 120; + uint32_t thumbnailMaximumHeight = 80; + EsRectangle targetRectangle = EsRectangleFit(ES_RECT_2S(thumbnailMaximumWidth, thumbnailMaximumHeight), ES_RECT_2S(originalWidth, originalHeight), false); + uint32_t targetWidth = ES_RECT_WIDTH(targetRectangle), targetHeight = ES_RECT_HEIGHT(targetRectangle); + uint32_t *targetBits; + + if (targetWidth == originalWidth && targetHeight == originalHeight) { + targetBits = originalBits; + } else { + targetBits = (uint32_t *) EsHeapAllocate(targetWidth * targetHeight * 4, false); + + if (!targetBits) { + EsHeapFree(originalBits); + return; // Allocation failure; could not resize the image. + } + + ThumbnailResize(originalBits, originalWidth, originalHeight, targetWidth, targetHeight); + targetBits = (uint32_t *) EsHeapReallocate(originalBits, targetWidth * targetHeight * 4, false); + } + + EsMessageMutexAcquire(); + + thumbnail = thumbnailCache.Get(&task->id); + + if (thumbnail) { + if (thumbnail->bits) EsHeapFree(thumbnail->bits); + thumbnail->bits = targetBits; + thumbnail->width = targetWidth; + thumbnail->height = targetHeight; + // TODO Submit width/height properties. + } + + EsMessageMutexRelease(); +} + +void ListItemGenerateThumbnailTaskComplete(Instance *, Task *task) { + Thumbnail *thumbnail = thumbnailCache.Get(&task->id); + + if (thumbnail) { + thumbnail->generatingTasksInProgress--; + } + + String parent = PathGetParent(task->string); + + for (uintptr_t i = 0; i < instances.Length(); i++) { + if (StringEquals(parent, instances[i]->path)) { // TODO Support recursive views. + EsListViewInvalidateAll(instances[i]->list); + } + } + + StringDestroy(&task->string); +} + +Thumbnail *ListItemGetThumbnail(EsElement *element) { + Instance *instance = element->instance; + ListEntry *entry = &instance->listContents[EsListViewGetIndexFromItem(element).u]; + Thumbnail *thumbnail = thumbnailCache.Get(&entry->entry->id); + return thumbnail; +} + +void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename) { + Instance *instance = element->instance; + + if (instance->viewSettings.viewType != VIEW_THUMBNAILS && instance->viewSettings.viewType != VIEW_TILES) { + return; // The current view does not display thumbnails. + } + + ListEntry *listEntry = &instance->listContents[index]; + FolderEntry *entry = listEntry->entry; + FileType *fileType = FolderEntryGetType(instance->folder, entry); + + if (!fileType->hasThumbnailGenerator) { + return; // The file type does not support thumbnail generation. + } + + Thumbnail *thumbnail = thumbnailCache.Put(&entry->id); + + if (!fromFolderRename) { + thumbnail->referenceCount++; + } + + // TODO Remove from LRU if needed. + + if ((thumbnail->generatingTasksInProgress && !fromFolderRename) || thumbnail->bits) { + return; // The thumbnail is already being/has already been generated. + } + + thumbnail->generatingTasksInProgress++; + + String path = StringAllocateAndFormat("%s%s", STRFMT(instance->path), STRFMT(entry->GetName())); + + Task task = { + .string = path, + .id = entry->id, + .callback = ListItemGenerateThumbnailTask, + .then = ListItemGenerateThumbnailTaskComplete, + }; + + NonBlockingTaskQueue(task); +} + +int ListItemMessage(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_DESTROY) { + Thumbnail *thumbnail = ListItemGetThumbnail(element); + + if (thumbnail) { + thumbnail->referenceCount--; + + // TODO When the referenceCount drops to 0, put it on a LRU list. + } + } else if (message->type == ES_MSG_PAINT_ICON) { + if (element->instance->viewSettings.viewType == VIEW_THUMBNAILS || element->instance->viewSettings.viewType == VIEW_TILES) { + Thumbnail *thumbnail = ListItemGetThumbnail(element); + + if (thumbnail && thumbnail->bits) { + EsRectangle destination = EsPainterBoundsClient(message->painter); + EsRectangle source = ES_RECT_2S(thumbnail->width, thumbnail->height); + destination = EsRectangleFit(destination, source, false); + // EsDrawBlock(message->painter, EsRectangleAdd(destination, ES_RECT_1(2)), 0x20000000); + EsDrawBitmapScaled(message->painter, destination, source, thumbnail->bits, thumbnail->width * 4, 0xFF); + return ES_HANDLED; + } + } + } + + return 0; +} + +int ListCallback(EsElement *element, EsMessage *message) { + Instance *instance = element->instance; + + if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) { + int column = message->getContent.column, index = message->getContent.index.i; + EsAssert(index < (int) instance->listContents.Length() && index >= 0); + ListEntry *listEntry = &instance->listContents[index]; + FolderEntry *entry = listEntry->entry; + FileType *fileType = FolderEntryGetType(instance->folder, entry); + + if (column == COLUMN_NAME) { + String name = entry->GetName(); + EsBufferFormat(message->getContent.buffer, "%s", name.bytes, name.text); + message->getContent.icon = fileType->iconID; + } else if (column == COLUMN_TYPE) { + EsBufferFormat(message->getContent.buffer, "%z", fileType->name); + } else if (column == COLUMN_SIZE) { + if (!entry->sizeUnknown) { + EsBufferFormat(message->getContent.buffer, "%D", entry->size); + } + } + } else if (message->type == ES_MSG_LIST_VIEW_GET_SUMMARY) { + int index = message->getContent.index.i; + EsAssert(index < (int) instance->listContents.Length() && index >= 0); + ListEntry *listEntry = &instance->listContents[index]; + FolderEntry *entry = listEntry->entry; + FileType *fileType = FolderEntryGetType(instance->folder, entry); + String name = entry->GetName(); + EsBufferFormat(message->getContent.buffer, "%s\n\a2]%z " HYPHENATION_POINT " %D", name.bytes, name.text, fileType->name, entry->size); + message->getContent.icon = fileType->iconID; + message->getContent.richText = true; + } else if (message->type == ES_MSG_LIST_VIEW_SELECT_RANGE) { + for (intptr_t i = message->selectRange.fromIndex.i; i <= message->selectRange.toIndex.i; i++) { + ListEntry *entry = &instance->listContents[i]; + if (entry->selected) { instance->selectedItemCount--; instance->selectedItemsTotalSize -= entry->entry->size; } + entry->selected = message->selectRange.toggle ? !entry->selected : message->selectRange.select; + if (entry->selected) { instance->selectedItemCount++; instance->selectedItemsTotalSize += entry->entry->size; } + } + + InstanceUpdateItemSelectionCountCommands(instance); + StringDestroy(&instance->delayedFocusItem); + InstanceUpdateStatusString(instance); + } else if (message->type == ES_MSG_LIST_VIEW_SELECT) { + ListEntry *entry = &instance->listContents[message->selectItem.index.i]; + if (entry->selected) { instance->selectedItemCount--; instance->selectedItemsTotalSize -= entry->entry->size; } + entry->selected = message->selectItem.isSelected; + if (entry->selected) { instance->selectedItemCount++; instance->selectedItemsTotalSize += entry->entry->size; } + InstanceUpdateItemSelectionCountCommands(instance); + StringDestroy(&instance->delayedFocusItem); + InstanceUpdateStatusString(instance); + } else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) { + ListEntry *entry = &instance->listContents[message->selectItem.index.i]; + message->selectItem.isSelected = entry->selected; + } else if (message->type == ES_MSG_LIST_VIEW_CHOOSE_ITEM) { + ListEntry *listEntry = &instance->listContents[message->chooseItem.index.i]; + + if (listEntry) { + FolderEntry *entry = listEntry->entry; + + if (entry->isFolder) { + String path = instance->folder->itemHandler->getPathForChildFolder(instance->folder, entry->GetInternalName()); + InstanceLoadFolder(instance, path); + } else { + FileType *fileType = FolderEntryGetType(instance->folder, entry); + + if (fileType->openHandler) { + String path = StringAllocateAndFormat("%s%s", STRFMT(instance->folder->path), STRFMT(entry->GetInternalName())); + EsApplicationStartupInformation information = {}; + information.id = fileType->openHandler; + information.filePath = path.text; + information.filePathBytes = path.bytes; + EsApplicationStart(&information); + StringDestroy(&path); + } else { + EsDialogShowAlert(instance->window, INTERFACE_STRING(FileManagerOpenFileError), + INTERFACE_STRING(FileManagerNoRegisteredApplicationsForFile), + ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); + } + } + } + } else if (message->type == ES_MSG_LIST_VIEW_COLUMN_MENU) { + EsMenu *menu = EsMenuCreate(message->columnMenu.source); + uint32_t index = (uint32_t) message->columnMenu.index; + EsMenuAddItem(menu, instance->viewSettings.sortColumn == index ? ES_MENU_ITEM_CHECKED : 0, + INTERFACE_STRING(CommonSortAscending), InstanceChangeSortColumn, index); + EsMenuAddItem(menu, instance->viewSettings.sortColumn == (index | (1 << 8)) ? ES_MENU_ITEM_CHECKED : 0, + INTERFACE_STRING(CommonSortDescending), InstanceChangeSortColumn, index | (1 << 8)); + EsMenuShow(menu); + } else if (message->type == ES_MSG_LIST_VIEW_GET_COLUMN_SORT) { + if (message->getColumnSort.index == (instance->viewSettings.sortColumn & 0xFF)) { + return (instance->viewSettings.sortColumn & (1 << 8)) ? ES_LIST_VIEW_COLUMN_SORT_DESCENDING : ES_LIST_VIEW_COLUMN_SORT_ASCENDING; + } + } else if (message->type == ES_MSG_LIST_VIEW_CREATE_ITEM) { + EsElement *element = message->createItem.item; + element->messageUser = ListItemMessage; + ListItemCreated(element, message->createItem.index.u, false); + } else if (message->type == ES_MSG_MOUSE_RIGHT_CLICK) { + EsMenu *menu = EsMenuCreate(element, ES_MENU_AT_CURSOR); + +#define ADD_SORT_COLUMN_MENU_ITEM(_column, _string) \ + EsMenuAddItem(menu, instance->viewSettings.sortColumn == (_column) ? ES_MENU_ITEM_CHECKED : ES_FLAGS_DEFAULT, \ + INTERFACE_STRING(_string), InstanceChangeSortColumn, _column) + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonSortAscending)); + ADD_SORT_COLUMN_MENU_ITEM(COLUMN_NAME, FileManagerColumnName); + ADD_SORT_COLUMN_MENU_ITEM(COLUMN_TYPE, FileManagerColumnType); + ADD_SORT_COLUMN_MENU_ITEM(COLUMN_SIZE, FileManagerColumnSize); + EsMenuAddSeparator(menu); + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonSortDescending)); + ADD_SORT_COLUMN_MENU_ITEM(COLUMN_NAME | (1 << 8), FileManagerColumnName); + ADD_SORT_COLUMN_MENU_ITEM(COLUMN_TYPE | (1 << 8), FileManagerColumnType); + ADD_SORT_COLUMN_MENU_ITEM(COLUMN_SIZE | (1 << 8), FileManagerColumnSize); +#undef ADD_SORT_COLUMN_MENU_ITEM + + EsMenuNextColumn(menu); + + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(FileManagerListContextActions)); + EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonClipboardPaste), EsCommandByID(instance, ES_COMMAND_PASTE)); + EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonSelectionSelectAll), EsCommandByID(instance, ES_COMMAND_SELECT_ALL)); + EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerNewFolderToolbarItem), &instance->commandNewFolder); + EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerRefresh), &instance->commandRefresh); + + EsMenuAddSeparator(menu); + +#define ADD_VIEW_TYPE_MENU_ITEM(_command, _string) \ + EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(_string), _command) + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(CommonListViewType)); + ADD_VIEW_TYPE_MENU_ITEM(&instance->commandViewThumbnails, CommonListViewTypeThumbnails); + ADD_VIEW_TYPE_MENU_ITEM(&instance->commandViewTiles, CommonListViewTypeTiles); + ADD_VIEW_TYPE_MENU_ITEM(&instance->commandViewDetails, CommonListViewTypeDetails); +#undef ADD_VIEW_TYPE_MENU_ITEM + + EsMenuShow(menu); + } else { + return 0; + } + + return ES_HANDLED; +} + +int PlacesViewCallback(EsElement *element, EsMessage *message) { + Instance *instance = element->instance; + + if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) { + int group = message->getContent.group; + int index = message->getContent.index.i; + + if (group == PLACES_VIEW_GROUP_DRIVES) { + // TODO Use namespace lookup. + + if (index == 0) { + EsBufferFormat(message->getContent.buffer, "%z", interfaceString_FileManagerPlacesDrives); + message->getContent.icon = ES_ICON_COMPUTER_LAPTOP; + } else { + Drive *drive = &drives[index - 1]; + EsBufferFormat(message->getContent.buffer, "%s", drive->information.labelBytes, drive->information.label); + message->getContent.icon = IconFromDriveType(drive->information.driveType); + } + } else if (group == PLACES_VIEW_GROUP_BOOKMARKS) { + if (index == 0) { + EsBufferFormat(message->getContent.buffer, "%z", interfaceString_FileManagerPlacesBookmarks); + message->getContent.icon = ES_ICON_HELP_ABOUT; + } else { + // TODO The namespace lookup might be expensive. Perhaps these should be cached? + NamespaceGetVisibleName(message->getContent.buffer, bookmarks[index - 1]); + message->getContent.icon = NamespaceGetIcon(bookmarks[index - 1]); + } + } + } else if (message->type == ES_MSG_LIST_VIEW_SELECT && message->selectItem.isSelected) { + if (message->selectItem.group == PLACES_VIEW_GROUP_DRIVES) { + if (message->selectItem.index.i == 0) { + InstanceLoadFolder(instance, StringAllocateAndFormat("%z", interfaceString_FileManagerDrivesPage), LOAD_FOLDER_NO_FOCUS); + } else { + Drive *drive = &drives[message->selectItem.index.i - 1]; + InstanceLoadFolder(instance, StringAllocateAndFormat("%s/", drive->prefixBytes, drive->prefix), LOAD_FOLDER_NO_FOCUS); + } + } else if (message->selectItem.group == PLACES_VIEW_GROUP_BOOKMARKS && message->selectItem.index.i) { + String string = bookmarks[message->selectItem.index.i - 1]; + InstanceLoadFolder(instance, StringAllocateAndFormat("%s", STRFMT(string)), LOAD_FOLDER_NO_FOCUS); + } + } else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) { + if (message->selectItem.group == PLACES_VIEW_GROUP_DRIVES) { + if (message->selectItem.index.i == 0) { + message->selectItem.isSelected = 0 == EsStringCompareRaw(INTERFACE_STRING(FileManagerDrivesPage), + instance->path.text, instance->path.bytes); + } else { + Drive *drive = &drives[message->selectItem.index.i - 1]; + message->selectItem.isSelected = 0 == EsStringCompareRaw(drive->prefix, drive->prefixBytes, + instance->path.text, instance->path.bytes - 1); + } + } else if (message->selectItem.group == PLACES_VIEW_GROUP_BOOKMARKS && message->selectItem.index.i) { + String string = bookmarks[message->selectItem.index.i - 1]; + message->selectItem.isSelected = 0 == EsStringCompareRaw(string.text, string.bytes, + instance->path.text, instance->path.bytes); + } + } else if (message->type == ES_MSG_LIST_VIEW_CONTEXT_MENU) { + if (message->selectItem.group == PLACES_VIEW_GROUP_BOOKMARKS && !message->selectItem.index.i) { + bool isCurrentFolderBookmarked = false; + + for (uintptr_t i = 0; i < bookmarks.Length(); i++) { + if (0 == EsStringCompareRaw(STRING(bookmarks[i]), STRING(instance->path))) { + isCurrentFolderBookmarked = true; + break; + } + } + + EsMenu *menu = EsMenuCreate(element, ES_MENU_AT_CURSOR); + + if (isCurrentFolderBookmarked) { + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerBookmarksRemoveHere), [] (EsMenu *menu, EsGeneric) { + BookmarkRemove(menu->instance->path); + }); + } else { + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(FileManagerBookmarksAddHere), [] (EsMenu *menu, EsGeneric) { + BookmarkAdd(menu->instance->path); + }); + } + + EsMenuShow(menu); + } + } else { + return 0; + } + + return ES_HANDLED; +} + +int BreadcrumbBarMessage(EsElement *element, EsMessage *message) { + Instance *instance = element->instance; + + if (message->type == ES_MSG_TEXTBOX_ACTIVATE_BREADCRUMB) { + String section = PathGetSection(instance->folder->path, message->activateBreadcrumb); + size_t bytes = section.text + section.bytes - instance->folder->path.text; + + String path = StringAllocateAndFormat("%s%z", + bytes, instance->folder->path.text, + instance->folder->path.text[bytes - 1] != '/' ? "/" : ""); + InstanceLoadFolder(instance, path); + } else if (message->type == ES_MSG_TEXTBOX_EDIT_END) { + String section; + section.text = EsTextboxGetContents(instance->breadcrumbBar, §ion.bytes); + section.allocated = section.bytes; + + String path = StringAllocateAndFormat("%s%z", + section.bytes, section.text, + section.text[section.bytes - 1] != '/' ? "/" : ""); + + if (!InstanceLoadFolder(instance, path)) { + StringDestroy(§ion); + return ES_REJECTED; + } + + StringDestroy(§ion); + } else if (message->type == ES_MSG_TEXTBOX_GET_BREADCRUMB) { + if (!instance->folder || PathCountSections(instance->folder->path) == message->getBreadcrumb.index) { + return ES_REJECTED; + } + + String path = PathGetParent(instance->folder->path, message->getBreadcrumb.index); + NamespaceGetVisibleName(message->getBreadcrumb.buffer, path); + return ES_HANDLED; + } + + return 0; +} + +#define ADD_BUTTON_TO_TOOLBAR(_command, _label, _icon, _accessKey, _name) \ + { \ + _name = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, 0, _label); \ + EsButtonSetIcon(_name, _icon); \ + EsCommandAddButton(&instance->_command, _name); \ + _name->accessKey = _accessKey; \ + } + +#define ADD_BUTTON_TO_STATUS_BAR(_command, _label, _icon, _accessKey, _name) \ + { \ + _name = EsButtonCreate(statusBar, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_STATUS_BAR, _label); \ + EsButtonSetIcon(_name, _icon); \ + EsCommandAddButton(&instance->_command, _name); \ + _name->accessKey = _accessKey; \ + } + +void InstanceCreateUI(Instance *instance) { + EsButton *button; + + EsWindowSetIcon(instance->window, ES_ICON_SYSTEM_FILE_MANAGER); + InstanceRegisterCommands(instance); + + EsPanel *rootPanel = EsPanelCreate(instance->window, ES_CELL_FILL | ES_PANEL_VERTICAL); + EsSplitter *splitter = EsSplitterCreate(rootPanel, ES_SPLITTER_HORIZONTAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_WITH_STATUS_BAR_CONTENT); + + // Places: + + instance->placesView = EsListViewCreate(splitter, ES_CELL_EXPAND | ES_CELL_COLLAPSABLE | ES_LIST_VIEW_SINGLE_SELECT | ES_CELL_V_FILL, + &stylePlacesView, ES_STYLE_LIST_ITEM, ES_STYLE_LIST_ITEM, nullptr); + instance->placesView->accessKey = 'P'; + instance->placesView->messageUser = PlacesViewCallback; + EsListViewInsertGroup(instance->placesView, PLACES_VIEW_GROUP_BOOKMARKS, ES_LIST_VIEW_GROUP_HAS_HEADER | ES_LIST_VIEW_GROUP_INDENT); + EsListViewInsertGroup(instance->placesView, PLACES_VIEW_GROUP_DRIVES, ES_LIST_VIEW_GROUP_HAS_HEADER | ES_LIST_VIEW_GROUP_INDENT); + if (bookmarks.Length()) EsListViewInsert(instance->placesView, PLACES_VIEW_GROUP_BOOKMARKS, 1, bookmarks.Length()); + EsListViewInsert(instance->placesView, PLACES_VIEW_GROUP_DRIVES, 1, drives.Length()); + + // Main list: + + instance->list = EsListViewCreate(splitter, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_MULTI_SELECT, &styleFolderView); + instance->list->accessKey = 'L'; + instance->list->messageUser = ListCallback; + EsListViewSetColumns(instance->list, folderOutputColumns, sizeof(folderOutputColumns) / sizeof(folderOutputColumns[0])); + EsListViewInsertGroup(instance->list, 0); + + // Toolbar: + + EsElement *toolbar = EsWindowGetToolbar(instance->window); + ADD_BUTTON_TO_TOOLBAR(commandGoBackwards, nullptr, ES_ICON_GO_PREVIOUS_SYMBOLIC, 'B', button); + ADD_BUTTON_TO_TOOLBAR(commandGoForwards, nullptr, ES_ICON_GO_NEXT_SYMBOLIC, 'F', button); + ADD_BUTTON_TO_TOOLBAR(commandGoParent, nullptr, ES_ICON_GO_UP_SYMBOLIC, 'U', button); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT); + instance->breadcrumbBar = EsTextboxCreate(toolbar, ES_CELL_H_FILL | ES_TEXTBOX_EDIT_BASED | ES_TEXTBOX_REJECT_EDIT_IF_LOST_FOCUS, {}); + instance->breadcrumbBar->messageUser = BreadcrumbBarMessage; + EsTextboxUseBreadcrumbOverlay(instance->breadcrumbBar); + ADD_BUTTON_TO_TOOLBAR(commandNewFolder, interfaceString_FileManagerNewFolderToolbarItem, ES_ICON_FOLDER_NEW_SYMBOLIC, 'N', instance->newFolderButton); + + // Status bar: + + EsPanel *statusBar = EsPanelCreate(rootPanel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_STATUS_BAR); + instance->status = EsTextDisplayCreate(statusBar, ES_CELL_H_FILL); + ADD_BUTTON_TO_STATUS_BAR(commandViewDetails, nullptr, ES_ICON_VIEW_LIST_SYMBOLIC, 0, button); + ADD_BUTTON_TO_STATUS_BAR(commandViewTiles, nullptr, ES_ICON_VIEW_LIST_COMPACT_SYMBOLIC, 0, button); + ADD_BUTTON_TO_STATUS_BAR(commandViewThumbnails, nullptr, ES_ICON_VIEW_GRID_SYMBOLIC, 0, button); + + // Load initial folder: + + const EsApplicationStartupInformation *startupInformation = EsInstanceGetStartupInformation(instance); + String path; + + if (startupInformation && (startupInformation->flags & ES_APPLICATION_STARTUP_MANUAL_PATH)) { + uintptr_t directoryEnd = startupInformation->filePathBytes; + + for (uintptr_t i = 0; i < (uintptr_t) startupInformation->filePathBytes; i++) { + if (startupInformation->filePath[i] == '/') { + directoryEnd = i + 1; + } + } + + instance->delayedFocusItem = StringAllocateAndFormat("%s", startupInformation->filePathBytes - directoryEnd, startupInformation->filePath + directoryEnd); + path = StringAllocateAndFormat("%s", directoryEnd, startupInformation->filePath); + } else { + path = StringAllocateAndFormat("0:/"); + } + + InstanceLoadFolder(instance, path, LOAD_FOLDER_START); +} + +void InstanceReportError(Instance *instance, int error, EsError code) { + EsPrint("FM error %d/%d.\n", error, code); + + // TODO Error messages. + + const char *message = interfaceString_FileManagerGenericError; + + if (code == ES_ERROR_FILE_ALREADY_EXISTS) { + message = interfaceString_FileManagerItemAlreadyExistsError; + } else if (code == ES_ERROR_FILE_DOES_NOT_EXIST) { + message = interfaceString_FileManagerItemDoesNotExistError; + } else if (code == ES_ERROR_FILE_PERMISSION_NOT_GRANTED) { + message = interfaceString_FileManagerPermissionNotGrantedError; + } + + EsDialogShowAlert(instance->window, errorTypeStrings[error], -1, message, -1, + ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); +} + +void InstanceDestroy(Instance *instance) { + StringDestroy(&instance->path); + StringDestroy(&instance->delayedFocusItem); + + for (uintptr_t i = 0; i < instance->pathBackwardHistory.Length(); i++) { + StringDestroy(&instance->pathBackwardHistory[i].path); + StringDestroy(&instance->pathBackwardHistory[i].focusedItem); + } + + for (uintptr_t i = 0; i < instance->pathForwardHistory.Length(); i++) { + StringDestroy(&instance->pathForwardHistory[i].path); + StringDestroy(&instance->pathForwardHistory[i].focusedItem); + } + + for (uintptr_t i = 0; i < instance->listContents.Length(); i++) { + FolderEntryCloseHandle(instance->folder, instance->listContents[i].entry); + } + + FolderDetachInstance(instance); + + instance->pathBackwardHistory.Free(); + instance->pathForwardHistory.Free(); + instance->listContents.Free(); +} diff --git a/apps/fly.cpp b/apps/fly.cpp new file mode 100644 index 0000000..988c26f --- /dev/null +++ b/apps/fly.cpp @@ -0,0 +1,942 @@ +// TODO Icon. +// TODO Music. +// TODO Draw() with rotation. +// TODO Saving with new file IO. + +#include + +EsInstance *instance; +uint32_t backgroundColor; +int musicIndex; +bool keysPressedSpace, keysPressedEscape, keysPressedX; +uint32_t previousControllerButtons[ES_GAME_CONTROLLER_MAX_COUNT]; +uint32_t *targetBits; +size_t targetWidth, targetHeight, targetStride; +double updateTimeAccumulator; +float gameScale; +uint32_t gameOffsetX, gameOffsetY, gameWidth, gameHeight; + +#define GAME_SIZE (380) +#define BRAND "fly" + +struct Texture { + uint32_t width, height; + uint32_t *bits; +}; + +void Transform(float *destination, float *left, float *right) { + float d[6]; + d[0] = left[0] * right[0] + left[1] * right[3]; + d[1] = left[0] * right[1] + left[1] * right[4]; + d[2] = left[0] * right[2] + left[1] * right[5] + left[2]; + d[3] = left[3] * right[0] + left[4] * right[3]; + d[4] = left[3] * right[1] + left[4] * right[4]; + d[5] = left[3] * right[2] + left[4] * right[5] + left[5]; + destination[0] = d[0]; + destination[1] = d[1]; + destination[2] = d[2]; + destination[3] = d[3]; + destination[4] = d[4]; + destination[5] = d[5]; +} + +__attribute__((optimize("-O2"))) +void Draw(Texture *texture, + float x, float y, float w = -1, float h = -1, + float sx = 0, float sy = 0, float sw = -1, float sh = -1, + float _a = 1, float _r = 1, float _g = 1, float _b = 1, + float rot = 0) { + (void) rot; + + if (sw == -1 && sh == -1) sw = texture->width, sh = texture->height; + if (w == -1 && h == -1) w = sw, h = sh; + if (_a <= 0) return; + + if (x + w < 0 || y + h < 0 || x > GAME_SIZE || y > GAME_SIZE) return; + + x *= gameScale, y *= gameScale, w *= gameScale, h *= gameScale; + + float m[6] = {}; + float m1[6] = { sw / w, 0, 0, 0, sh / h, 0 }; + float m2[6] = { 1, 0, -x * (sw / w), 0, 1, -y * (sh / h) }; + + Transform(m, m2, m1); + + intptr_t yStart = y - 1, yEnd = y + h + 1, xStart = x - 1, xEnd = x + w + 1; + // if (rot) yStart -= h * 0.45f, yEnd += h * 0.45f, xStart -= w * 0.45f, xEnd += w * 0.45f; + if (yStart < 0) yStart = 0; + if (yStart > gameHeight) yStart = gameHeight; + if (yEnd < 0) yEnd = 0; + if (yEnd > gameHeight) yEnd = gameHeight; + if (xStart < 0) xStart = 0; + if (xStart > gameWidth) xStart = gameWidth; + if (xEnd < 0) xEnd = 0; + if (xEnd > gameWidth) xEnd = gameWidth; + m[2] += m[0] * xStart, m[5] += m[3] * xStart; + + uint32_t *scanlineStart = targetBits + (gameOffsetY + yStart) * targetStride / 4 + (gameOffsetX + xStart); + + uint32_t r = _r * 255, g = _g * 255, b = _b * 255, a = _a * 255; + + for (intptr_t y = yStart; y < yEnd; y++, scanlineStart += targetStride / 4) { + uint32_t *output = scanlineStart; + + float tx = m[1] * y + m[2]; + float ty = m[4] * y + m[5]; + + for (intptr_t x = xStart; x < xEnd; x++, output++, tx += m[0], ty += m[3]) { + if (tx + sx < 0 || ty + sy < 0) continue; + uintptr_t txi = tx + sx, tyi = ty + sy; + if (txi < sx || tyi < sy || txi >= sx + sw || tyi >= sy + sh) continue; + uint32_t modified = texture->bits[tyi * texture->width + txi]; + if (!(modified & 0xFF000000)) continue; + uint32_t original = *output; + uint32_t alpha1 = (((modified & 0xFF000000) >> 24) * a) >> 8; + uint32_t alpha2 = (255 - alpha1) << 8; + uint32_t r2 = alpha2 * ((original & 0x00FF0000) >> 16); + uint32_t g2 = alpha2 * ((original & 0x0000FF00) >> 8); + uint32_t b2 = alpha2 * ((original & 0x000000FF) >> 0); + uint32_t r1 = r * alpha1 * ((modified & 0x00FF0000) >> 16); + uint32_t g1 = g * alpha1 * ((modified & 0x0000FF00) >> 8); + uint32_t b1 = b * alpha1 * ((modified & 0x000000FF) >> 0); + *output = 0xFF000000 + | (0x00FF0000 & ((r1 + r2) << 0)) + | (0x0000FF00 & ((g1 + g2) >> 8)) + | (0x000000FF & ((b1 + b2) >> 16)); + } + } +} + +void CreateTexture(Texture *texture, const char *cName) { + size_t dataBytes; + const void *data = EsEmbeddedFileGet(cName, &dataBytes); + texture->bits = (uint32_t *) EsImageLoad(data, dataBytes, &texture->width, &texture->height, 4); + EsAssert(texture->bits); +} + +void ExitGame() { + EsInstanceDestroy(instance); +} + +/////////////////////////////////////////////////////////// + +#define TAG_ALL (-1) +#define TAG_SOLID (-2) +#define TAG_KILL (-3) +#define TAG_WORLD (-4) + +#define TILE_SIZE (16) + +struct Entity { + uint8_t tag; + uint8_t layer; + bool solid, kill, hide; + char symbol; + int8_t frameCount, stepsPerFrame; + Texture *texture; + float texOffX, texOffY, w, h; + int uid; + + void (*create)(Entity *); + void (*destroy)(Entity *); + void (*stepAlways)(Entity *); // Called even if level editing. + void (*step)(Entity *); + void (*draw)(Entity *); + void (*drawAfter)(Entity *); + + float x, y, px, py; + int stepIndex; + bool isUsed, isDestroyed; + + union { + struct { + float cx, cy; + float th, dth; + float dths; + int respawn, respawnGrow, dash; + } player; + + struct { + int random; + } block; + + struct { + float vel; + } moveBlock; + + struct { + float th, cx, cy; + } spin; + + struct { + float vx, vy, th, dth; + int life; + uint32_t col; + } star; + }; + + void Destroy() { + if (isDestroyed) return; + isDestroyed = true; + if (destroy) destroy(this); + } +}; + +Texture textureDashMessage, textureKeyMessage, textureKey, textureControlsMessage, textureWhite; + +Entity typePlayer, typeBlock, typeCheck, typeMoveH, typeStar, typeMoveV, typePowerup, typeShowMessage, typeKey, typeLock, typeSpin, typeEnd; + +// All the entities that can be loaded from the rooms. +Entity *entityTypes[] = { + &typeBlock, &typeCheck, &typeMoveH, &typeMoveV, &typePowerup, &typeKey, &typeLock, &typeSpin, &typeEnd, + nullptr, +}; + +int levelTick; +bool justLoaded = true; + +#define MAX_ENTITIES (1000) + +struct SaveState { + float checkX, checkY; + int roomX, roomY; + bool hasDash, hasKey; + int deathCount; + uint32_t check; +}; + +struct GameState : SaveState { + Entity entities[MAX_ENTITIES]; + int world; + Entity *player; +}; + +GameState state; + +Entity *AddEntity(Entity *templateEntity, float x, float y, int uid = 0) { + for (int i = 0; i < MAX_ENTITIES; i++) { + if (!state.entities[i].isUsed) { + EsMemoryCopy(state.entities + i, templateEntity, sizeof(Entity)); + state.entities[i].isUsed = true; + if (!state.entities[i].frameCount) state.entities[i].frameCount = 1; + if (!state.entities[i].stepsPerFrame) state.entities[i].stepsPerFrame = 1; + state.entities[i].x = state.entities[i].px = x; + state.entities[i].y = state.entities[i].py = y; + if (!state.entities[i].w) state.entities[i].w = state.entities[i].texture->width - 1; + if (!state.entities[i].h) state.entities[i].h = state.entities[i].texture->height / state.entities[i].frameCount - 1; + state.entities[i].uid = uid; + if (state.entities[i].create) state.entities[i].create(state.entities + i); + return state.entities + i; + } + } + + static Entity fake = {}; + return &fake; +} + +Entity *FindEntity(float x, float y, float w, float h, int tag, Entity *exclude) { + for (int i = 0; i < MAX_ENTITIES; i++) { + if (state.entities[i].isUsed && !state.entities[i].isDestroyed + && (state.entities[i].tag == tag + || tag == TAG_ALL + || (tag == TAG_SOLID && state.entities[i].solid) + || (tag == TAG_KILL && state.entities[i].kill) + ) + && state.entities + i != exclude) { + if (x <= state.entities[i].x + state.entities[i].w && state.entities[i].x <= x + w + && y <= state.entities[i].y + state.entities[i].h && state.entities[i].y <= y + h) { + return state.entities + i; + } + } + } + + return nullptr; +} + +char roomName[16]; + +void UpdateRoomName() { + roomName[0] = 'R'; + roomName[1] = (state.roomX / 10) + '0'; + roomName[2] = (state.roomX % 10) + '0'; + roomName[3] = '_'; + roomName[4] = (state.roomY / 10) + '0'; + roomName[5] = (state.roomY % 10) + '0'; + roomName[6] = '.'; + roomName[7] = 'D'; + roomName[8] = 'A'; + roomName[9] = 'T'; + roomName[10] = 0; +} + +#define ROOM_ID ((state.roomX - 7) + (state.roomY - 9) * 6) + +void LoadRoom() { + state.world = 0; + + UpdateRoomName(); + + roomName[6] = '_'; + const uint8_t *buffer = (const uint8_t *) EsEmbeddedFileGet(roomName); + + for (int i = 0; i < MAX_ENTITIES; i++) { + if (!state.entities[i].isUsed || state.entities[i].isDestroyed) continue; + + if (state.entities[i].tag != typePlayer.tag) { + state.entities[i].Destroy(); + } else { + state.entities[i].px = state.entities[i].x; + state.entities[i].py = state.entities[i].y; + } + } + + int p = 0; + int iir = 0; + + while (true) { + uint8_t tag = buffer[p++]; + if (!tag) break; + float x = *(float *) (buffer + p); p += 4; + float y = *(float *) (buffer + p); p += 4; + + if (tag == (uint8_t) TAG_WORLD) { + state.world = x; + } + + for (int i = 0; entityTypes[i]; i++) { + if (entityTypes[i]->tag == tag) { + AddEntity(entityTypes[i], x, y, (state.roomX << 24) | (state.roomY << 16) | iir); + iir++; + } + } + } + + musicIndex = state.world; +} + +void CalculateCheck() { + state.check = 0; + + uint8_t *buffer = (uint8_t *) &state; + uint32_t check = 0x1D471D47; + + for (uintptr_t i = 0; i < sizeof(SaveState); i++) { + check ^= ((uint32_t) buffer[i] + 10) * (i + 100); + } + + state.check = check; +} + +float FadeInOut(float t) { + if (t < 0.3f) return t / 0.3f; + else if (t < 0.7f) return 1; + else return 1 - (t - 0.7f) / 0.3f; +} + +void InitialiseGame() { + state.roomX = 10; + state.roomY = 10; + + CreateTexture(&textureWhite, "white_png"); + CreateTexture(&textureKey, "key_png"); + CreateTexture(&textureDashMessage, "dash_msg_png"); + CreateTexture(&textureKeyMessage, "key_msg_png"); + CreateTexture(&textureControlsMessage, "controls_png"); + + int tag = 1; + + { + static Texture texture; + CreateTexture(&texture, "player_png"); + typePlayer.tag = tag++; + typePlayer.texture = &texture; + typePlayer.frameCount = 6; + typePlayer.stepsPerFrame = 5; + typePlayer.layer = 1; + + typePlayer.step = [] (Entity *entity) { + if (entity->player.respawn) { + if (entity->stepIndex > entity->player.respawn) { + entity->player.respawn = 0; + entity->hide = false; + entity->player.cx = state.checkX; + entity->player.cy = state.checkY; + entity->player.respawnGrow = entity->stepIndex + 20; + entity->player.dash = 0; + } else { + return; + } + } + + if (!entity->player.respawnGrow && !entity->player.dash) { + if (keysPressedSpace) { + entity->player.cx += (entity->x - entity->player.cx) * 2; + entity->player.cy += (entity->y - entity->player.cy) * 2; + entity->player.th += 3.15f; + entity->player.dth = -entity->player.dth; + entity->player.dths = 5; + } else if (keysPressedX && state.hasDash) { + entity->player.dash = 10; + } + } + + float rd = 40; + + if (entity->player.respawnGrow) { + if (entity->stepIndex > entity->player.respawnGrow) { + entity->player.respawnGrow = 0; + } else { + rd *= 1 - (entity->player.respawnGrow - entity->stepIndex) / 20.0f; + } + } + + entity->x = rd * EsCRTcosf(entity->player.th) + entity->player.cx; + entity->y = rd * EsCRTsinf(entity->player.th) + entity->player.cy + 5 * EsCRTsinf(4.71f + entity->stepIndex * 0.1f); + + if (entity->player.dash) { + float pt = entity->player.th - entity->player.dth; + float px = rd * EsCRTcosf(pt) + entity->player.cx; + float py = rd * EsCRTsinf(pt) + entity->player.cy + 5 * EsCRTsinf(4.71f + (entity->stepIndex - 1) * 0.1f); + float dx = entity->x - px; + float dy = entity->y - py; + entity->player.cx += dx * 3.2f; + entity->player.cy += dy * 3.2f; + entity->player.dash--; + AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFF; + } else { + entity->player.th += entity->player.dth; + } + + if (entity->player.respawnGrow) { + entity->px = entity->x; + entity->py = entity->y; + } + + if (entity->player.dths) { + entity->player.th += entity->player.dth; + entity->player.dths--; + entity->stepIndex = 5 * 3; + } + + if (FindEntity(entity->x + 5, entity->y + 5, entity->w - 10, entity->h - 10, TAG_KILL, 0)) { + for (int i = 0; i < 20; i++) { + AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFF0000; + } + + entity->hide = true; + entity->player.respawn = entity->stepIndex + 20; + state.deathCount++; + } + + if (entity->x > GAME_SIZE - 8) { + entity->x -= GAME_SIZE; + entity->player.cx -= GAME_SIZE; + state.checkX -= GAME_SIZE; + state.roomX++; + LoadRoom(); + } else if (entity->x < -8) { + entity->x += GAME_SIZE; + entity->player.cx += GAME_SIZE; + state.checkX += GAME_SIZE; + state.roomX--; + LoadRoom(); + } + + if (entity->y > GAME_SIZE - 8) { + entity->y -= GAME_SIZE; + entity->player.cy -= GAME_SIZE; + state.checkY -= GAME_SIZE; + state.roomY++; + LoadRoom(); + } else if (entity->y < -8) { + entity->y += GAME_SIZE; + entity->player.cy += GAME_SIZE; + state.checkY += GAME_SIZE; + state.roomY--; + LoadRoom(); + } + }; + + typePlayer.create = [] (Entity *entity) { + state.player = entity; + entity->player.cx = entity->x; + entity->player.cy = entity->y; + entity->player.dth = 0.06f; + }; + } + + { + static Texture texture; + CreateTexture(&texture, "block_png"); + + static Texture block2; + CreateTexture(&block2, "block2_png"); + + typeBlock.tag = tag++; + typeBlock.texture = &texture; + typeBlock.kill = true; + typeBlock.hide = true; + typeBlock.layer = 2; + + typeBlock.create = [] (Entity *entity) { + uint8_t r = EsRandomU8(); + + if (r < 20) { + entity->block.random = 1; + } else if (r < 50) { + entity->block.random = 2; + } else { + entity->block.random = 0; + } + }; + + typeBlock.stepAlways = [] (Entity *entity) { + if (FindEntity(entity->x + 1, entity->y + 1, entity->w - 2, entity->h - 2, entity->tag, entity)) { + entity->Destroy(); + } + }; + + typeBlock.drawAfter = [] (Entity *entity) { + if (!FindEntity(entity->x - 16, entity->y, 1, 1, entity->tag, entity)) { + Draw(&block2, entity->x - 16, entity->y, 16, 16, 0, entity->block.random * 16, 16, 16, 1); + } + + if (!FindEntity(entity->x + 16, entity->y, 1, 1, entity->tag, entity)) { + Draw(&block2, entity->x + 16, entity->y, 16, 16, 32, entity->block.random * 16, 16, 16, 1); + } + + if (!FindEntity(entity->x, entity->y - 16, 1, 1, entity->tag, entity)) { + Draw(&block2, entity->x, entity->y - 16, 16, 16, 16, entity->block.random * 16, 16, 16, 1); + } + + if (!FindEntity(entity->x, entity->y + 16, 1, 1, entity->tag, entity)) { + Draw(&block2, entity->x, entity->y + 16, 16, 16, 48, entity->block.random * 16, 16, 16, 1); + } + }; + } + + { + static Texture check1, check2; + CreateTexture(&check1, "check1_png"); + CreateTexture(&check2, "check2_png"); + typeCheck.tag = tag++; + typeCheck.texture = &check1; + + typeCheck.step = [] (Entity *entity) { + if (state.checkX == entity->x && state.checkY == entity->y) { + entity->texture = &check2; + } else { + entity->texture = &check1; + } + + if (FindEntity(entity->x - 4, entity->y - 4, entity->w + 8, entity->h + 8, typePlayer.tag, 0)) { + if (state.checkX != entity->x || state.checkY != entity->y) { + for (int i = 0; i < 10; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFFFFFF; + } + + state.checkX = entity->x; + state.checkY = entity->y; + + CalculateCheck(); + EsFileWriteAll("|Settings:/Save.dat", -1, &state, sizeof(SaveState)); + } + }; + } + + { + static Texture texture; + CreateTexture(&texture, "moveblock_png"); + typeMoveH.tag = tag++; + typeMoveH.texture = &texture; + typeMoveH.kill = true; + typeMoveH.w = 16; + typeMoveH.h = 16; + typeMoveH.texOffX = -4; + typeMoveH.texOffY = -4; + + typeMoveH.create = [] (Entity *entity) { + entity->moveBlock.vel = -4; + }; + + typeMoveH.step = [] (Entity *entity) { + entity->x += entity->moveBlock.vel; + + if (FindEntity(entity->x, entity->y, entity->w, entity->h, typeBlock.tag, 0)) { + entity->moveBlock.vel = -entity->moveBlock.vel; + } + }; + } + + { + // Removed entity. + tag++; + } + + { + static Texture texture; + CreateTexture(&texture, "star_png"); + typeStar.texture = &texture; + typeStar.tag = tag++; + typeStar.hide = true; // Draw manually. + typeStar.layer = 2; + + typeStar.create = [] (Entity *entity) { + float th = EsRandomU8() / 255.0 * 6.24; + float sp = EsRandomU8() / 255.0 * 0.5 + 0.5; + entity->star.vx = sp * EsCRTcosf(th); + entity->star.vy = sp * EsCRTsinf(th); + entity->star.life = EsRandomU8(); + entity->star.dth = (EsRandomU8() / 255.0f - 0.5f) * 0.2f; + }; + + typeStar.step = [] (Entity *entity) { + entity->x += entity->star.vx; + entity->y += entity->star.vy; + entity->star.th += entity->star.dth; + + if (entity->star.life < entity->stepIndex) { + entity->Destroy(); + } + }; + + typeStar.drawAfter = [] (Entity *entity) { + Draw(entity->texture, entity->x - 4, entity->y - 4, -1, -1, 0, 0, -1, -1, + 1.0 - (float) entity->stepIndex / entity->star.life, + ((entity->star.col >> 16) & 0xFF) / 255.0f, + ((entity->star.col >> 8) & 0xFF) / 255.0f, + ((entity->star.col >> 0) & 0xFF) / 255.0f, + entity->star.th); + }; + } + + { + static Texture texture; + CreateTexture(&texture, "moveblock_png"); + typeMoveV.tag = tag++; + typeMoveV.texture = &texture; + typeMoveV.kill = true; + typeMoveV.w = 16; + typeMoveV.h = 16; + typeMoveV.texOffX = -4; + typeMoveV.texOffY = -4; + + typeMoveV.create = [] (Entity *entity) { + entity->moveBlock.vel = -4; + }; + + typeMoveV.step = [] (Entity *entity) { + entity->y += entity->moveBlock.vel; + + if (FindEntity(entity->x, entity->y, entity->w, entity->h, typeBlock.tag, 0)) { + entity->moveBlock.vel = -entity->moveBlock.vel; + } + }; + } + + { + static Texture texture; + CreateTexture(&texture, "powerup_png"); + typePowerup.tag = tag++; + typePowerup.texture = &texture; + + typePowerup.step = [] (Entity *entity) { + if (state.hasDash) { + entity->Destroy(); + return; + } + + if (FindEntity(entity->x, entity->y, entity->w, entity->h, typePlayer.tag, 0)) { + state.hasDash = true; + AddEntity(&typeShowMessage, 0, 0)->texture = &textureDashMessage; + entity->Destroy(); + } + }; + } + + { + typeShowMessage.texture = &textureKeyMessage; + typeShowMessage.tag = tag++; + typeShowMessage.hide = true; + + typeShowMessage.draw = [] (Entity *entity) { + Draw(entity->texture, entity->x, entity->y, -1, -1, 0, 0, -1, -1, FadeInOut(entity->stepIndex / 180.0)); + if (entity->stepIndex > 180) entity->Destroy(); + }; + } + + { + typeKey.tag = tag++; + typeKey.texture = &textureKey; + + typeKey.step = [] (Entity *entity) { + if (state.hasKey) { + entity->Destroy(); + } else if (FindEntity(entity->x, entity->y, entity->w, entity->h, typePlayer.tag, 0)) { + state.hasKey = true; + AddEntity(&typeShowMessage, 0, 0)->texture = &textureKeyMessage; + entity->Destroy(); + for (int i = 0; i < 10; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFFFFFF; + } + }; + } + + { + static Texture texture; + CreateTexture(&texture, "lock_png"); + typeLock.tag = tag++; + typeLock.texture = &texture; + typeLock.kill = true; + + typeLock.step = [] (Entity *entity) { + if (state.hasKey) { + for (int i = 0; i < 1; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0x000000; + entity->Destroy(); + } + }; + } + + { + static Texture texture; + CreateTexture(&texture, "moveblock_png"); + typeSpin.tag = tag++; + typeSpin.texture = &texture; + typeSpin.kill = true; + typeSpin.w = 16; + typeSpin.h = 16; + typeSpin.texOffX = -4; + typeSpin.texOffY = -4; + + typeSpin.create = [] (Entity *entity) { + entity->spin.cx = entity->x; + entity->spin.cy = entity->y; + }; + + typeSpin.step = [] (Entity *entity) { + entity->x = 60 * EsCRTcosf(entity->spin.th) + entity->spin.cx; + entity->y = 60 * EsCRTsinf(entity->spin.th) + entity->spin.cy; + entity->spin.th += 0.04f; + }; + } + + { + static Texture msg1, msg2, msg3, num; + CreateTexture(&msg1, "end1_png"); + CreateTexture(&msg2, "end2_png"); + CreateTexture(&msg3, "end3_png"); + CreateTexture(&num, "numbers_png"); + + typeEnd.tag = tag++; + typeEnd.texture = &textureKey; + typeEnd.hide = true; + + typeEnd.create = [] (Entity *) { + state.player->Destroy(); + }; + + typeEnd.draw = [] (Entity *entity) { + float t = entity->stepIndex / 180.0f; + + if (t < 1) { + Draw(&msg1, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t)); + } else if (t < 2) { + Draw(&msg2, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t - 1)); + } else if (t < 3) { + Draw(&msg3, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t - 2)); + + int p = state.deathCount; + char digits[10]; + int dc = 0; + + if (p == 0) { + digits[dc++] = 0; + } else { + while (p) { + digits[dc++] = p % 10; + p /= 10; + } + } + + int w = dc * 16; + + for (int i = dc - 1; i >= 0; i--) { + Draw(&num, 40 + 150 - w / 2 + (dc - 1 - i) * 16, + 150 + 33, 16, 30, 16 * digits[i], 0, 16, 30, FadeInOut(t - 2)); + } + } else { + ExitGame(); + } + }; + } + + state.checkX = GAME_SIZE / 2; + state.checkY = GAME_SIZE / 2 - 20; + + size_t loadedStateBytes; + SaveState *loadedState = (SaveState *) EsFileReadAll("|Settings:/Save.dat", -1, &loadedStateBytes); + bool noSave = true; + + if (loadedStateBytes == sizeof(SaveState)) { + EsMemoryCopy(&state, loadedState, loadedStateBytes); + + uint32_t oldCheck = state.check; + CalculateCheck(); + EsAssert(oldCheck == state.check); + + noSave = false; + } + + LoadRoom(); + + AddEntity(&typePlayer, state.checkX, state.checkY); + + if (noSave) { + AddEntity(&typeShowMessage, 0, GAME_SIZE - 65)->texture = &textureControlsMessage; + } +} + +void UpdateGame() { + if (keysPressedEscape) { + ExitGame(); + } + + for (int i = 0; i < MAX_ENTITIES; i++) { + if (state.entities[i].isUsed) { + state.entities[i].stepIndex++; + + state.entities[i].px += (state.entities[i].x - state.entities[i].px) * 0.5f; + state.entities[i].py += (state.entities[i].y - state.entities[i].py) * 0.5f; + } + } + + for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].stepAlways) state.entities[i].stepAlways(state.entities + i); + + for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].step) { + state.entities[i].step(state.entities + i); + } + + for (int i = 0; i < MAX_ENTITIES; i++) { + if (state.entities[i].isUsed && state.entities[i].isDestroyed) state.entities[i].isUsed = false; + } + + levelTick++; + + state.world %= 3; + + if (state.world == 0) { + backgroundColor = 0xbef1b1; + } else if (state.world == 1) { + backgroundColor = 0xcee5f1; + } else if (state.world == 2) { + backgroundColor = 0xf3bdf6; + } +} + +void RenderGame() { + for (int layer = -1; layer <= 3; layer++) { + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *entity = state.entities + i; + if (!entity->isUsed) continue; + if (entity->layer != layer) continue; + if (!entity->texture) continue; + if (entity->hide) continue; + + int frame = entity->stepsPerFrame >= 0 ? ((entity->stepIndex / entity->stepsPerFrame) % entity->frameCount) : (-entity->stepsPerFrame - 1); + Draw(entity->texture, (int) (entity->px + entity->texOffX + 0.5f), + (int) (entity->py + entity->texOffY + 0.5f), + entity->texture->width, entity->texture->height / entity->frameCount, + 0, entity->texture->height / entity->frameCount * frame, + entity->texture->width, entity->texture->height / entity->frameCount); + } + } + + for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].draw ) state.entities[i].draw (state.entities + i); + for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].drawAfter ) state.entities[i].drawAfter (state.entities + i); + + if (state.hasKey) { + Draw(&textureKey, GAME_SIZE - 20, 4); + } +} + +/////////////////////////////////////////////////////////// + +int ProcessCanvasMessage(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_ANIMATE) { + message->animate.complete = false; + updateTimeAccumulator += message->animate.deltaMs / 1000.0; + + while (updateTimeAccumulator > 1 / 60.0) { + { + EsGameControllerState state[ES_GAME_CONTROLLER_MAX_COUNT]; + size_t count = EsGameControllerStatePoll(state); + + for (uintptr_t i = 0; i < count; i++) { + if (state[i].buttons & (1 << 0) && (~previousControllerButtons[i] & (1 << 0))) { + keysPressedSpace = true; + } else if (state[i].buttons & (1 << 1) && (~previousControllerButtons[i] & (1 << 1))) { + keysPressedX = true; + } + + previousControllerButtons[i] = state[i].buttons; + } + } + + UpdateGame(); + updateTimeAccumulator -= 1 / 60.0; + keysPressedSpace = keysPressedEscape = keysPressedX = false; + } + + EsElementRepaint(element); + } else if (message->type == ES_MSG_KEY_DOWN && !message->keyboard.repeat) { + if (message->keyboard.scancode == ES_SCANCODE_SPACE || message->keyboard.scancode == ES_SCANCODE_Z) { + keysPressedSpace = true; + } else if (message->keyboard.scancode == ES_SCANCODE_ESCAPE) { + keysPressedEscape = true; + } else if (message->keyboard.scancode == ES_SCANCODE_X) { + keysPressedX = true; + } + } else if (message->type == ES_MSG_PAINT) { + EsPainter *painter = message->painter; + EsPaintTargetStartDirectAccess(painter->target, &targetBits, nullptr, nullptr, &targetStride); + targetBits = (uint32_t *) ((uint8_t *) targetBits + targetStride * painter->offsetY + 4 * painter->offsetX); + targetWidth = painter->width, targetHeight = painter->height; + + gameScale = (float) painter->width / GAME_SIZE; + if (gameScale * GAME_SIZE > painter->height) gameScale = (float) painter->height / GAME_SIZE; + if (gameScale > 1) gameScale = EsCRTfloorf(gameScale); + gameWidth = GAME_SIZE * gameScale, gameHeight = GAME_SIZE * gameScale; + gameOffsetX = painter->width / 2 - gameWidth / 2; + gameOffsetY = painter->height / 2 - gameHeight / 2; + + // TODO Clear margins. + + Draw(&textureWhite, 0, 0, GAME_SIZE, GAME_SIZE, 0, 0, 1, 1, 1, + ((backgroundColor >> 16) & 0xFF) / 255.0f, + ((backgroundColor >> 8) & 0xFF) / 255.0f, + ((backgroundColor >> 0) & 0xFF) / 255.0f); + + RenderGame(); + + EsPaintTargetEndDirectAccess(painter->target); + } + + return 0; +} + +void _start() { + _init(); + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + instance = EsInstanceCreate(message, BRAND); + EsWindow *window = instance->window; + EsPanel *container = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); + EsElement *canvas = EsCustomElementCreate(container, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE, {}); + canvas->messageUser = ProcessCanvasMessage; + EsElementStartAnimating(canvas); + EsElementFocus(canvas); + InitialiseGame(); + } + } +} diff --git a/apps/fly.ini b/apps/fly.ini new file mode 100644 index 0000000..65ad1f3 --- /dev/null +++ b/apps/fly.ini @@ -0,0 +1,66 @@ +[general] +name=Fly + +[build] +source=apps/fly.cpp + +[embed] +bgm1_mid=res/Fly Assets/bgm1.mid +bgm2_mid=res/Fly Assets/bgm2.mid +bgm3_mid=res/Fly Assets/bgm3.mid +block2_png=res/Fly Assets/block2.png +block_png=res/Fly Assets/block.png +check1_png=res/Fly Assets/check1.png +check2_png=res/Fly Assets/check2.png +controls_png=res/Fly Assets/controls.png +dash_msg_png=res/Fly Assets/dash_msg.png +end1_png=res/Fly Assets/end1.png +end2_png=res/Fly Assets/end2.png +end3_png=res/Fly Assets/end3.png +key_msg_png=res/Fly Assets/key_msg.png +key_png=res/Fly Assets/key.png +lock_png=res/Fly Assets/lock.png +moveblock_png=res/Fly Assets/moveblock.png +numbers_png=res/Fly Assets/numbers.png +player_png=res/Fly Assets/player.png +powerup_png=res/Fly Assets/powerup.png +R07_12_DAT=res/Fly Assets/R07_12.DAT +R07_13_DAT=res/Fly Assets/R07_13.DAT +R07_14_DAT=res/Fly Assets/R07_14.DAT +R07_16_DAT=res/Fly Assets/R07_16.DAT +R07_17_DAT=res/Fly Assets/R07_17.DAT +R07_18_DAT=res/Fly Assets/R07_18.DAT +R08_12_DAT=res/Fly Assets/R08_12.DAT +R08_13_DAT=res/Fly Assets/R08_13.DAT +R08_14_DAT=res/Fly Assets/R08_14.DAT +R08_16_DAT=res/Fly Assets/R08_16.DAT +R08_17_DAT=res/Fly Assets/R08_17.DAT +R08_18_DAT=res/Fly Assets/R08_18.DAT +R09_10_DAT=res/Fly Assets/R09_10.DAT +R09_11_DAT=res/Fly Assets/R09_11.DAT +R09_12_DAT=res/Fly Assets/R09_12.DAT +R09_13_DAT=res/Fly Assets/R09_13.DAT +R09_14_DAT=res/Fly Assets/R09_14.DAT +R09_15_DAT=res/Fly Assets/R09_15.DAT +R09_16_DAT=res/Fly Assets/R09_16.DAT +R09_17_DAT=res/Fly Assets/R09_17.DAT +R09_18_DAT=res/Fly Assets/R09_18.DAT +R10_09_DAT=res/Fly Assets/R10_09.DAT +R10_10_DAT=res/Fly Assets/R10_10.DAT +R10_11_DAT=res/Fly Assets/R10_11.DAT +R10_12_DAT=res/Fly Assets/R10_12.DAT +R10_13_DAT=res/Fly Assets/R10_13.DAT +R10_14_DAT=res/Fly Assets/R10_14.DAT +R10_15_DAT=res/Fly Assets/R10_15.DAT +R10_16_DAT=res/Fly Assets/R10_16.DAT +R11_09_DAT=res/Fly Assets/R11_09.DAT +R11_10_DAT=res/Fly Assets/R11_10.DAT +R11_11_DAT=res/Fly Assets/R11_11.DAT +R11_13_DAT=res/Fly Assets/R11_13.DAT +R11_14_DAT=res/Fly Assets/R11_14.DAT +R11_15_DAT=res/Fly Assets/R11_15.DAT +R12_09_DAT=res/Fly Assets/R12_09.DAT +R12_10_DAT=res/Fly Assets/R12_10.DAT +R12_11_DAT=res/Fly Assets/R12_11.DAT +star_png=res/Fly Assets/star.png +white_png=res/Fly Assets/white.png diff --git a/apps/font_book.cpp b/apps/font_book.cpp new file mode 100644 index 0000000..188facc --- /dev/null +++ b/apps/font_book.cpp @@ -0,0 +1,414 @@ +#define ES_INSTANCE_TYPE Instance +#include + +// TODO Previewing font files from the database. +// TODO Installing fonts. +// TODO Character map. +// TODO Searching/filtering fonts. +// TODO Single instance. + +#include +#define IMPLEMENTATION +#include +#include + +#define SETTINGS_FILE "|Settings:/Default.ini" + +struct Instance : EsInstance { + Array fonts; + + EsPanel *switcher; + + EsListView *fontList; + EsTextbox *fontSizeTextbox; + EsTextbox *previewTextTextbox; + + EsPanel *fontPreview; + uint16_t fontPreviewID; + + EsElement *fontListToolbar; + EsElement *fontPreviewToolbar; + + int fontSize, fontVariant; + char *previewText; + size_t previewTextBytes; +}; + +EsStyle styleFontList = { + .inherit = ES_STYLE_PANEL_FILLED, + + .metrics = { + .mask = ES_THEME_METRICS_GAP_MINOR | ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_INSETS, + .insets = ES_RECT_1(25), + .gapMinor = 15, + .gapWrap = 15, + }, +}; + +EsStyle styleFontItem = { + .inherit = ES_STYLE_PANEL_SHEET, + + .metrics = { + .mask = ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_PREFERRED_HEIGHT, + .preferredWidth = 300, + .preferredHeight = 250, + }, +}; + +EsStyle styleFontInformationPanel = { + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR, + .insets = ES_RECT_1(15), + .gapMajor = 10, + }, +}; + +EsStyle styleFontName = { + .inherit = ES_STYLE_TEXT_LABEL_SECONDARY, + + .metrics = { + .mask = ES_THEME_METRICS_TEXT_SIZE, + .textSize = 10, + }, +}; + +EsStyle styleFontInformationRow = { + .metrics = { + .mask = ES_THEME_METRICS_GAP_MAJOR, + .gapMajor = 10, + }, +}; + +EsStyle styleFontPreviewPage = { + .inherit = ES_STYLE_PANEL_DOCUMENT, + + .metrics = { + .mask = ES_THEME_METRICS_INSETS, + .insets = ES_RECT_1(20), + }, +}; + +inline int ClampInteger(int low, int high, int integer) { + if (integer < low) return low; + if (integer > high) return high; + return integer; +} + +bool LoadSettings(Instance *instance) { + EsINIState state = {}; + char *file = (char *) EsFileReadAll(EsLiteral(SETTINGS_FILE), &state.bytes); + + if (!state.buffer) { + return false; + } + + while (EsINIParse(&state)) { + if (state.value && 0 == EsStringCompareRaw(state.section, state.sectionBytes, EsLiteral("preview"))) { + if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("size"))) { + instance->fontSize = EsIntegerParse(state.value, state.valueBytes); + } else if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("variant"))) { + instance->fontVariant = EsIntegerParse(state.value, state.valueBytes); + } else if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("text"))) { + instance->previewTextBytes = state.valueBytes; + instance->previewText = (char *) EsHeapAllocate(instance->previewTextBytes, false); + EsMemoryCopy(instance->previewText, state.value, instance->previewTextBytes); + } + } + } + + EsHeapFree(file); + return true; +} + +void SaveSettings(Instance *instance) { + EsBuffer buffer = { .canGrow = true }; + EsBufferFormat(&buffer, "[preview]\nsize=%d\nvariant=%d\ntext=%s\n", + instance->fontSize, instance->fontVariant, instance->previewTextBytes, instance->previewText); + EsFileWriteAll(EsLiteral(SETTINGS_FILE), buffer.out, buffer.position); + EsHeapFree(buffer.out); +} + +int FontPreviewMessage(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_PAINT) { + // TODO Cache the text plan? + EsTextPlanProperties properties = {}; + properties.flags = ES_TEXT_PLAN_SINGLE_USE | ES_TEXT_PLAN_TRIM_SPACES | ES_TEXT_WRAP | ES_TEXT_ELLIPSIS | ES_TEXT_PLAN_NO_FONT_SUBSTITUTION; + EsRectangle bounds = EsPainterBoundsInset(message->painter); + EsTextRun runs[2] = {}; + EsElementGetTextStyle(element, &runs[0].style); + runs[0].style.font.family = element->userData.u; + runs[0].style.font.weight = element->instance->fontVariant % 10; + runs[0].style.font.italic = element->instance->fontVariant / 10; + runs[0].style.size = element->instance->fontSize; + runs[1].offset = element->instance->previewTextBytes; + EsTextPlan *plan = EsTextPlanCreate(&properties, bounds, element->instance->previewText, runs, 1); + EsDrawText(message->painter, plan, bounds); + } + + return 0; +} + +int FontListMessage(EsElement *element, EsMessage *message) { + Instance *instance = element->instance; + + if (message->type == ES_MSG_LIST_VIEW_CREATE_ITEM) { + EsPanel *panel = EsPanelCreate(message->createItem.item, ES_CELL_FILL, &styleFontInformationPanel); + EsFontInformation *font = &instance->fonts[message->createItem.index.u]; + + EsPanel *row = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleFontInformationRow); + // EsIconDisplayCreate(row, ES_FLAGS_DEFAULT, ES_STYLE_ICON_DISPLAY_SMALL, ES_ICON_FONT_X_GENERIC); + EsTextDisplayCreate(row, ES_FLAGS_DEFAULT, &styleFontName, font->name, font->nameBytes); + EsSpacerCreate(row, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL); + + EsElement *preview = EsCustomElementCreate(panel, ES_CELL_FILL, ES_STYLE_TEXT_PARAGRAPH); + preview->userData = font->id; + preview->messageUser = FontPreviewMessage; + + size_t variants = 0; + + for (uintptr_t i = 0; i < 16; i++) { + if (font->availableWeightsNormal & (1 << i)) variants++; + if (font->availableWeightsItalic & (1 << i)) variants++; + } + + row = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleFontInformationRow); + + char description[256]; // TODO Localization. + size_t descriptionBytes = EsStringFormat(description, sizeof(description), "%s " HYPHENATION_POINT " %d variant%z", + font->categoryBytes, font->category, variants, variants == 1 ? "" : "s"); + EsTextDisplayCreate(row, ES_CELL_H_FILL | ES_CELL_V_BOTTOM, ES_STYLE_TEXT_LABEL_SECONDARY, description, descriptionBytes); + + EsButton *openButton = EsButtonCreate(row, ES_BUTTON_NOT_FOCUSABLE); + EsButtonSetIcon(openButton, ES_ICON_GO_NEXT_SYMBOLIC); + } + + return 0; +} + +void FontSizeTextboxUpdate(Instance *instance) { + char result[64]; + size_t resultBytes = EsStringFormat(result, sizeof(result), "%d", instance->fontSize); + EsTextboxSelectAll(instance->fontSizeTextbox); + EsTextboxInsert(instance->fontSizeTextbox, result, resultBytes); + EsListViewInvalidateAll(instance->fontList); +} + +int FontSizeTextboxMessage(EsElement *element, EsMessage *message) { + EsTextbox *textbox = (EsTextbox *) element; + Instance *instance = element->instance; + + if (message->type == ES_MSG_TEXTBOX_EDIT_END) { + char *expression = EsTextboxGetContents(textbox); + EsCalculationValue value = EsCalculateFromUserExpression(expression); + EsHeapFree(expression); + + if (value.error) { + return ES_REJECTED; + } else { + instance->fontSize = ClampInteger(6, 300, value.number); + FontSizeTextboxUpdate(instance); + } + } else if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA) { + instance->fontSize = ClampInteger(6, 300, instance->fontSize + message->numberDragDelta.delta * (message->numberDragDelta.fast ? 10 : 1)); + FontSizeTextboxUpdate(instance); + } else { + return 0; + } + + return ES_HANDLED; +} + +int PreviewTextTextboxMessage(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_TEXTBOX_UPDATED) { + EsHeapFree(element->instance->previewText); + element->instance->previewText = EsTextboxGetContents((EsTextbox *) element, &element->instance->previewTextBytes); + EsListViewInvalidateAll(element->instance->fontList); + } else { + return 0; + } + + return ES_HANDLED; +} + +void VariantsPopupCreate(Instance *instance, EsElement *element, EsCommand *) { + EsMenu *menu = EsMenuCreate(element, ES_FLAGS_DEFAULT); + EsPanel *panel = EsPanelCreate(menu, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_POPUP); + + EsListView *list = EsListViewCreate(panel, ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal100), 1); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal200), 2); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal300), 3); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal400), 4); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal500), 5); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal600), 6); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal700), 7); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal800), 8); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal900), 9); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic100), 11); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic200), 12); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic300), 13); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic400), 14); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic500), 15); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic600), 16); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic700), 17); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic800), 18); + EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic900), 19); + EsListViewSelectFixedItem(list, instance->fontVariant); + + list->messageUser = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_LIST_VIEW_SELECT) { + Instance *instance = element->instance; + EsGeneric selected; + + if (EsListViewGetSelectedFixedItem(((EsListView *) element), &selected)) { + instance->fontVariant = selected.i; + EsListViewInvalidateAll(element->instance->fontList); + } + } + + return 0; + }; + + EsMenuShow(menu); +} + +void LoadFontsFromDatabase(Instance *instance) { + EsFontDatabaseEnumerate([] (const EsFontInformation *information, EsGeneric context) { + ((Instance *) context.p)->fonts.AddPointer(information); + }, instance); + + EsSort(instance->fonts.array, instance->fonts.Length(), sizeof(EsFontInformation), [] (const void *left, const void *right, EsGeneric) { + EsFontInformation *fontLeft = (EsFontInformation *) left; + EsFontInformation *fontRight = (EsFontInformation *) right; + int x = EsStringCompare(fontLeft->name, fontLeft->nameBytes, fontRight->name, fontRight->nameBytes); + if (x) return x; + return EsStringCompareRaw(fontLeft->name, fontLeft->nameBytes, fontRight->name, fontRight->nameBytes); + }, 0); + + EsListViewInsert(instance->fontList, 0, 0, instance->fonts.Length() - 1); +} + +void BackCommand(Instance *instance, EsElement *, EsCommand *) { + EsPanelSwitchTo(instance->switcher, instance->fontList, ES_TRANSITION_NONE); + EsWindowSwitchToolbar(instance->window, instance->fontListToolbar, ES_TRANSITION_SLIDE_DOWN); +} + +void _start() { + _init(); + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(FontBookTitle)); + EsWindowSetIcon(instance->window, ES_ICON_APPLICATIONS_FONTS); + EsPanel *rootPanel = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); + + instance->switcher = EsPanelCreate(rootPanel, ES_CELL_FILL | ES_PANEL_SWITCHER); + + // Settings: + + if (!LoadSettings(instance)) { + instance->fontSize = 24; + instance->fontVariant = 4 /* regular */; + instance->previewTextBytes = EsCStringLength(interfaceString_FontBookPreviewTextDefault); + instance->previewText = (char *) EsHeapAllocate(instance->previewTextBytes, false); + EsMemoryCopy(instance->previewText, interfaceString_FontBookPreviewTextDefault, instance->previewTextBytes); + } + + // Font list page: + + uint64_t flags = ES_CELL_FILL | ES_LIST_VIEW_TILED | ES_LIST_VIEW_CENTER_TILES; + EsListView *fontList = EsListViewCreate(instance->switcher, flags, &styleFontList, &styleFontItem); + EsListViewSetMaximumItemsPerBand(fontList, 4); + EsListViewInsertGroup(fontList, 0); + instance->fontList = fontList; + fontList->messageUser = FontListMessage; + fontList->accessKey = 'F'; + LoadFontsFromDatabase(instance); + + EsPanelSwitchTo(instance->switcher, instance->fontList, ES_TRANSITION_NONE); + + // Font preview page: + + instance->fontPreview = EsPanelCreate(instance->switcher, ES_CELL_FILL | ES_PANEL_V_SCROLL_AUTO, &styleFontPreviewPage); + + // Font list toolbar: + + EsElement *toolbar = EsWindowGetToolbar(instance->window, true /* create new */); + instance->fontListToolbar = toolbar; + + EsPanel *section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(FontBookTextSize)); + instance->fontSizeTextbox = EsTextboxCreate(section, ES_TEXTBOX_EDIT_BASED, ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT); + instance->fontSizeTextbox->messageUser = FontSizeTextboxMessage; + instance->fontSizeTextbox->accessKey = 'S'; + EsTextboxUseNumberOverlay(instance->fontSizeTextbox, false); + FontSizeTextboxUpdate(instance); + + EsSpacerCreate(toolbar, ES_CELL_H_FILL); + + section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(FontBookPreviewText)); + instance->previewTextTextbox = EsTextboxCreate(section, ES_FLAGS_DEFAULT, ES_STYLE_TEXTBOX_BORDERED_SINGLE); + instance->previewTextTextbox->messageUser = PreviewTextTextboxMessage; + instance->previewTextTextbox->accessKey = 'P'; + EsTextboxInsert(instance->previewTextTextbox, instance->previewText, instance->previewTextBytes, false); + + EsSpacerCreate(toolbar, ES_CELL_H_FILL); + + EsButton *button = EsButtonCreate(toolbar, ES_BUTTON_DROPDOWN, {}, INTERFACE_STRING(FontBookVariants)); + button->accessKey = 'V'; + EsButtonOnCommand(button, VariantsPopupCreate); + + // Font preview toolbar: + + toolbar = EsWindowGetToolbar(instance->window, true /* create new */); + instance->fontPreviewToolbar = toolbar; + button = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(FontBookNavigationBack)); + button->accessKey = 'B'; + EsButtonSetIcon(button, ES_ICON_GO_PREVIOUS_SYMBOLIC); + EsButtonOnCommand(button, BackCommand); + } else if (message->type == ES_MSG_INSTANCE_OPEN) { + if (!message->instanceOpen.update) { + Instance *instance = message->instanceOpen.instance; + EsFontInformation information = {}; + information.availableWeightsNormal = 1 << 4 /* regular */; + instance->fontPreviewID = EsFontDatabaseInsertFile(&information, message->instanceOpen.file); + // TODO Check that the font is valid. + + EsElementDestroyContents(instance->fontPreview); + + EsPanel *titleRow = EsPanelCreate(instance->fontPreview, ES_CELL_H_CENTER | ES_PANEL_HORIZONTAL, &styleFontInformationRow); + EsIconDisplayCreate(titleRow, ES_FLAGS_DEFAULT, ES_STYLE_ICON_DISPLAY, ES_ICON_FONT_X_GENERIC); + EsTextDisplayCreate(titleRow, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_HEADING0, message->instanceOpen.name, message->instanceOpen.nameBytes); + EsSpacerCreate(instance->fontPreview, ES_FLAGS_DEFAULT, 0, 0, 20); + + int sizes[] = { 12, 18, 24, 36, 48, 60, 72, 0 }; + + for (uintptr_t i = 0; sizes[i]; i++) { + EsPanel *row = EsPanelCreate(instance->fontPreview, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleFontInformationRow); + char buffer[64]; + EsTextDisplayCreate(row, ES_FLAGS_DEFAULT, 0, buffer, EsStringFormat(buffer, sizeof(buffer), "%d", sizes[i])); + EsTextDisplay *display = EsTextDisplayCreate(row, ES_TEXT_DISPLAY_NO_FONT_SUBSTITUTION); + const char *string = interfaceString_FontBookPreviewTextLongDefault; + EsTextRun runs[2] = {}; + EsElementGetTextStyle(display, &runs[0].style); + runs[0].style.size = sizes[i]; + runs[0].style.font.family = instance->fontPreviewID; + runs[1].offset = EsCStringLength(string); + EsTextDisplaySetStyledContents(display, string, runs, 1); + } + + EsPanelSwitchTo(instance->switcher, instance->fontPreview, ES_TRANSITION_NONE); + EsWindowSwitchToolbar(instance->window, instance->fontPreviewToolbar, ES_TRANSITION_SLIDE_UP); + } + + EsInstanceOpenComplete(message, true); + } else if (message->type == ES_MSG_INSTANCE_DESTROY) { + SaveSettings(message->instanceDestroy.instance); + EsHeapFree(message->instanceDestroy.instance->previewText); + // TODO Remove the font added to the font database. + } + } +} diff --git a/apps/font_book.ini b/apps/font_book.ini new file mode 100644 index 0000000..0abf52e --- /dev/null +++ b/apps/font_book.ini @@ -0,0 +1,14 @@ +[general] +name=Font Book +icon=icon_applications_fonts + +[build] +source=apps/font_book.cpp + +[@handler] +extension=ttf +action=open + +[@handler] +extension=otf +action=open diff --git a/apps/gl_test.c b/apps/gl_test.c new file mode 100644 index 0000000..53fa23e --- /dev/null +++ b/apps/gl_test.c @@ -0,0 +1,282 @@ +// gcc -o tri tri.c -lOSMesa && ./tri +// x86_64-essence-gcc -o root/tri ports/mesa/tri.c -lOSMesa -lstdc++ -lz -g -D ESSENCE_WINDOW -D MODERN_GL + +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMAGE_WIDTH (700) +#define IMAGE_HEIGHT (600) +uint32_t *buffer; + +#ifdef MODERN_GL +static GLenum (*glCheckFramebufferStatus)(GLenum target); +static GLint (*glGetUniformLocation)(GLuint program, const GLchar *name); +static GLuint (*glCreateProgram)(); +static GLuint (*glCreateShader)(GLenum type); +static void (*glAttachShader)(GLuint program, GLuint shader); +static void (*glBindBuffer)(GLenum target, GLuint buffer); +static void (*glBindFramebuffer)(GLenum target, GLuint framebuffer); +static void (*glBindVertexArray)(GLuint array); +static void (*glBufferData)(GLenum target, GLsizeiptr size, const void *data, GLenum usage); +static void (*glCompileShader)(GLuint shader); +static void (*glDeleteFramebuffers)(GLsizei n, const GLuint *framebuffers); +static void (*glDrawBuffers)(GLsizei n, const GLenum *bufs); +static void (*glEnableVertexAttribArray)(); +static void (*glFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +static void (*glGenBuffers)(GLsizei n, GLuint *buffers); +static void (*glGenFramebuffers)(GLsizei n, GLuint *framebuffers); +static void (*glGenVertexArrays)(GLsizei n, GLuint *arrays); +static void (*glGetProgramInfoLog)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +static void (*glGetShaderInfoLog)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +static void (*glGetShaderiv)(GLuint shader, GLenum pname, GLint *param); +static void (*glLinkProgram)(GLuint program); +static void (*glShaderSource)(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length); +static void (*glUniform1f)(GLint location, GLfloat v0); +static void (*glUniform1i)(GLint location, GLint v0); +static void (*glUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +static void (*glUniformMatrix4fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +static void (*glUseProgram)(GLuint program); +static void (*glValidateProgram)(GLuint program); +static void (*glVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); + +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_VERTEX_SHADER 0x8B31 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_TEXTURE0 0x84C0 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_STATIC_DRAW 0x88E4 +#define GL_MULTISAMPLE 0x809D +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_BGRA 0x80E1 +#endif + +#ifdef ESSENCE_WINDOW +#include + +int CanvasCallback(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_PAINT_BACKGROUND) { + *response = ES_HANDLED; + + int ox = message->painter->width / 2 - IMAGE_WIDTH / 2; + int oy = message->painter->height / 2 - IMAGE_HEIGHT / 2; + EsRectangle bounds = { ox, ox + IMAGE_WIDTH, oy, oy + IMAGE_HEIGHT }; + EsDrawBitmap(message->painter, bounds, buffer, IMAGE_WIDTH * 4, ES_DRAW_BITMAP_OPAQUE); + +#if 0 + EsPainter *painter = message->painter; + size_t stride; + uint32_t *bits; + EsPaintTargetStartDirectAccess(painter->target, &bits, NULL, NULL, &stride); + int width = painter->width, height = painter->height; + int ox = width / 2 - IMAGE_WIDTH / 2; + int oy = height / 2 - IMAGE_HEIGHT / 2; + stride /= 4; + + uint32_t *start = bits + painter->offsetX + painter->offsetY * stride; + + for (int i = 0; i < height; i++) { + uint32_t *destination = start + i * stride; + + for (int j = 0; j < width; j++, destination++) { + int sx = j - ox, sy = i - oy; + + if (sy >= 0 && sy < IMAGE_HEIGHT && sx >= 0 && sx < IMAGE_WIDTH) { + *destination = buffer[sy * IMAGE_WIDTH + sx]; + } else { + *destination = 0xFF000000; + } + } + } +#endif + } + + return ES_NOT_HANDLED; +} +#endif + +int main(int argc, char **argv) { +#ifndef MODERN_GL + OSMesaContext context = OSMesaCreateContextExt(OSMESA_RGBA, 16, 0, 0, NULL); + buffer = (uint32_t *) malloc(IMAGE_WIDTH * IMAGE_HEIGHT * 4); + OSMesaMakeCurrent(context, buffer, GL_UNSIGNED_BYTE, IMAGE_WIDTH, IMAGE_HEIGHT); + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glLoadIdentity(); + glBegin(GL_TRIANGLES); + glColor4f(1, 0, 0, 1); + glVertex2f(-1, -1); + glColor4f(0, 1, 0, 1); + glVertex2f(0, 1); + glColor4f(0, 0, 1, 1); + glVertex2f(1, -1); + glEnd(); +#else + const int contextAttributes[] = { + OSMESA_FORMAT, OSMESA_RGBA, + OSMESA_DEPTH_BITS, 16, + OSMESA_PROFILE, OSMESA_CORE_PROFILE, + OSMESA_CONTEXT_MAJOR_VERSION, 3, + OSMESA_CONTEXT_MINOR_VERSION, 0, + 0 + }; + + OSMesaContext context = OSMesaCreateContextAttribs(contextAttributes, NULL); + buffer = (uint32_t *) malloc(IMAGE_WIDTH * IMAGE_HEIGHT * 4); + OSMesaMakeCurrent(context, buffer, GL_UNSIGNED_BYTE, IMAGE_WIDTH, IMAGE_HEIGHT); + +#define LOADEXT(x) *(void **) &x = (void *) OSMesaGetProcAddress(#x); assert(x); + LOADEXT(glAttachShader); + LOADEXT(glBindBuffer); + LOADEXT(glBindFramebuffer); + LOADEXT(glBindVertexArray); + LOADEXT(glBufferData); + LOADEXT(glCheckFramebufferStatus); + LOADEXT(glCompileShader); + LOADEXT(glCreateProgram); + LOADEXT(glCreateShader); + LOADEXT(glDeleteFramebuffers); + LOADEXT(glDrawBuffers); + LOADEXT(glEnableVertexAttribArray); + LOADEXT(glFramebufferTexture2D); + LOADEXT(glGenBuffers); + LOADEXT(glGenFramebuffers); + LOADEXT(glGenVertexArrays); + LOADEXT(glGetProgramInfoLog); + LOADEXT(glGetShaderInfoLog); + LOADEXT(glGetShaderiv); + LOADEXT(glGetUniformLocation); + LOADEXT(glLinkProgram); + LOADEXT(glShaderSource); + LOADEXT(glUniform1f); + LOADEXT(glUniform1i); + LOADEXT(glUniform4f); + LOADEXT(glUniformMatrix4fv); + LOADEXT(glUseProgram); + LOADEXT(glValidateProgram); + LOADEXT(glVertexAttribPointer); +#undef LOADEXT + + glClearColor(0, 0, 0, 1); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_MULTISAMPLE); + + int squareVBO, squareIBO; + float squareVBOArray[] = { -1, -1, 0.5f, /**/ -1, 1, 0.5f, /**/ 1, 1, 0.5f, /**/ 1, -1, 0.5f }; + unsigned squareIBOArray[] = { 0, 1, 2, 0, 2, 3 }; + glGenBuffers(1, &squareVBO); + glBindBuffer(GL_ARRAY_BUFFER, squareVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(squareVBOArray), squareVBOArray, GL_STATIC_DRAW); + glGenBuffers(1, &squareIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, squareIBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(squareIBOArray), squareIBOArray, GL_STATIC_DRAW); + + const char *vertexShaderSource = + "#version 330\n" + "layout(location = 0) in vec3 Position;\n" + "uniform mat4 transform;\n" + "out vec2 TexCoord0;\n" + "void main() { \n" + " gl_Position = transform * vec4(Position, 1.0);\n" + "}\n"; + const char *fragmentShaderSource = + "#version 330\n" + "layout(location = 0) out vec4 FragColor;\n" + "in vec2 TexCoord0;\n" + "uniform vec4 blendColor;\n" + "void main() { \n" + " FragColor = blendColor;\n" + "}\n"; + + const char *shaderSources[] = { vertexShaderSource, fragmentShaderSource }; + int shaderSourceLengths[] = { strlen(vertexShaderSource), strlen(fragmentShaderSource) }; + + unsigned shader = glCreateProgram(); + char shaderInfoLog[1024]; + + unsigned vertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexShader, 1, shaderSources + 0, shaderSourceLengths + 0); + glCompileShader(vertexShader); + glGetShaderInfoLog(vertexShader, sizeof(shaderInfoLog), NULL, shaderInfoLog); + glAttachShader(shader, vertexShader); + printf("Vertex shader log: '%s'\n", shaderInfoLog); + + unsigned fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, 1, shaderSources + 1, shaderSourceLengths + 1); + glCompileShader(fragmentShader); + glGetShaderInfoLog(fragmentShader, sizeof(shaderInfoLog), NULL, shaderInfoLog); + glAttachShader(shader, fragmentShader); + printf("Fragment shader log: '%s'\n", shaderInfoLog); + + glLinkProgram(shader); + glValidateProgram(shader); + + int shaderBlendColor = glGetUniformLocation(shader, "blendColor"); + int shaderTransform = glGetUniformLocation(shader, "transform"); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + unsigned vertexArray; + glGenVertexArrays(1, &vertexArray); + glBindVertexArray(vertexArray); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, squareVBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, squareIBO); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (const GLvoid *) 0); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(shader); + float transform[] = { 0.5f, 0, 0, 0, /**/ 0, 0.5f, 0, 0, /**/ 0, 0, 1, 0, /**/ 0, 0, 0, 1 }; + glUniformMatrix4fv(shaderTransform, 1, GL_TRUE, transform); + glUniform4f(shaderBlendColor, 1, 0, 1, 1); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); +#endif + + glFinish(); + +#ifndef ESSENCE_WINDOW + FILE *out = fopen("test.ppm", "wb"); + fprintf(out, "P6\n%d %d\n255\n", IMAGE_WIDTH, IMAGE_HEIGHT); + + for (int j = 0; j < IMAGE_HEIGHT; j++) { + for (int i = 0; i < IMAGE_WIDTH; i++) { + fwrite(buffer + j * IMAGE_WIDTH + i, 1, 3, out); + } + } + + fclose(out); + + OSMesaDestroyContext(context); +#else + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + EsInstance *instance = EsInstanceCreate(message, "GL Test"); + EsWindowSetTitle(instance->window, "GL Test", -1); + EsCustomElementCreate(instance->window, ES_CELL_FILL, (EsElementProperties) { .callback = CanvasCallback }); + } + } +#endif + + free(buffer); + printf("all done :)\n"); + + return 0; +} diff --git a/apps/gl_test.ini b/apps/gl_test.ini new file mode 100644 index 0000000..339b5fc --- /dev/null +++ b/apps/gl_test.ini @@ -0,0 +1,6 @@ +[general] +name=GL Test + +[build] +custom_compile_command=x86_64-essence-gcc -o "root/Applications/GL Test/Entry.esx" apps/gl_test.c -lOSMesa -lstdc++ -lz -g -D ESSENCE_WINDOW +require=root/Applications/POSIX/lib/libOSMesa.a diff --git a/apps/hello.c b/apps/hello.c new file mode 100644 index 0000000..90f90ac --- /dev/null +++ b/apps/hello.c @@ -0,0 +1,29 @@ +// Include the Essence system header. +#include + +void _start() { + // We're not using the C standard library, + // so we need to initialise global constructors manually. + _init(); + + while (true) { + // Receive a message from the system. + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + // The system wants us to create an instance of our application. + // Call EsInstanceCreate with the message and application name. + EsInstance *instance = EsInstanceCreate(message, "Hello", -1); + + // Create a text display with the "Hello, world!" message. + EsTextDisplayCreate( + instance->window, // Add the text display to the instance's window. + ES_CELL_FILL, // The text display should fill the window. + ES_STYLE_PANEL_WINDOW_BACKGROUND, // Use the window background style. + "Hello, world!", -1); // Pass -1 for a zero-terminated string. + + // Keep receiving messages in a loop, + // so the system can handle input messages for the window. + } + } +} diff --git a/apps/hello.ini b/apps/hello.ini new file mode 100644 index 0000000..513220f --- /dev/null +++ b/apps/hello.ini @@ -0,0 +1,5 @@ +[general] +name=Hello + +[build] +source=apps/hello.c diff --git a/apps/image_editor.cpp b/apps/image_editor.cpp new file mode 100644 index 0000000..3486249 --- /dev/null +++ b/apps/image_editor.cpp @@ -0,0 +1,840 @@ +// TODO Saving. +// TODO Don't use an EsPaintTarget for the bitmap? +// TODO Show brush preview. +// TODO Other tools: text, selection. +// TODO Resize and crop image. +// TODO Clipboard. +// TODO Clearing textbox undo from EsTextboxInsert? +// TODO Handling out of memory. +// TODO Color palette. +// TODO More brushes? +// TODO Grid. +// TODO Zoom and pan with EsCanvasPane. +// TODO Status bar. +// TODO Clearing undo if too much memory is used. + +#define ES_INSTANCE_TYPE Instance +#include + +#include +#include + +#ifdef OS_ESSENCE +#define IMPLEMENTATION +#include +#endif + +#define TILE_SIZE (128) + +struct Tile { + size_t referenceCount; + uint64_t ownerID; + uint32_t bits[TILE_SIZE * TILE_SIZE]; +}; + +struct Image { + Tile **tiles; + size_t tileCountX, tileCountY; + uint64_t id; + uint32_t width, height; +}; + +struct Instance : EsInstance { + EsElement *canvas; + EsColorWell *colorWell; + EsTextbox *brushSize; + + EsPanel *toolPanel; + EsButton *toolDropdown; + + EsPaintTarget *bitmap; + uint32_t bitmapWidth, bitmapHeight; + + Image image; + uint64_t nextImageID; + + EsCommand commandBrush; + EsCommand commandFill; + EsCommand commandRectangle; + EsCommand commandSelect; + EsCommand commandText; + + // Data while drawing: + EsRectangle modifiedBounds; + float previousPointX, previousPointY; + bool dragged; +}; + +const EsStyle styleBitmapSizeTextbox = { + .inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT, + + .metrics = { + .mask = ES_THEME_METRICS_PREFERRED_WIDTH, + .preferredWidth = 70, + }, +}; + +const EsStyle styleImageMenuTable = { + .metrics = { + .mask = ES_THEME_METRICS_GAP_ALL | ES_THEME_METRICS_INSETS, + .insets = ES_RECT_4(20, 20, 5, 8), + .gapMajor = 6, + .gapMinor = 6, + }, +}; + +Image ImageFork(Instance *instance, Image image, uint32_t newWidth = 0, uint32_t newHeight = 0) { + Image copy = image; + + copy.width = newWidth ?: image.width; + copy.height = newHeight ?: image.height; + copy.tileCountX = newWidth ? (newWidth + TILE_SIZE - 1) / TILE_SIZE : image.tileCountX; + copy.tileCountY = newHeight ? (newHeight + TILE_SIZE - 1) / TILE_SIZE : image.tileCountY; + copy.id = instance->nextImageID++; + copy.tiles = (Tile **) EsHeapAllocate(copy.tileCountX * copy.tileCountY * sizeof(Tile *), true); + + for (uintptr_t y = 0; y < copy.tileCountY; y++) { + for (uintptr_t x = 0; x < copy.tileCountX; x++) { + uintptr_t source = y * image.tileCountX + x; + uintptr_t destination = y * copy.tileCountX + x; + + if (y < image.tileCountY && x < image.tileCountX && image.tiles[source]) { + image.tiles[source]->referenceCount++; + copy.tiles[destination] = image.tiles[source]; + } + } + } + + return copy; +} + +void ImageDelete(Image image) { + for (uintptr_t i = 0; i < image.tileCountX * image.tileCountY; i++) { + image.tiles[i]->referenceCount--; + + if (!image.tiles[i]->referenceCount) { + // EsPrint("free tile %d, %d from image %d\n", i % image.tileCountX, i / image.tileCountX, image.tiles[i]->ownerID); + EsHeapFree(image.tiles[i]); + } + } + + EsHeapFree(image.tiles); +} + +void ImageCopyToPaintTarget(Instance *instance, const Image *image) { + uint32_t *bits; + size_t width, height, stride; + EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride); + + for (int32_t i = 0; i < (int32_t) image->tileCountY; i++) { + for (int32_t j = 0; j < (int32_t) image->tileCountX; j++) { + Tile *tile = image->tiles[i * image->tileCountX + j]; + int32_t copyWidth = TILE_SIZE, copyHeight = TILE_SIZE; + + if (j * TILE_SIZE + copyWidth > (int32_t) width) { + copyWidth = width - j * TILE_SIZE; + } + + if (i * TILE_SIZE + copyHeight > (int32_t) height) { + copyHeight = height - i * TILE_SIZE; + } + + if (tile) { + for (int32_t y = 0; y < copyHeight; y++) { + for (int32_t x = 0; x < copyWidth; x++) { + bits[stride / 4 * (y + i * TILE_SIZE) + (x + j * TILE_SIZE)] = tile->bits[y * TILE_SIZE + x]; + } + } + } else { + for (int32_t y = 0; y < copyHeight; y++) { + for (int32_t x = 0; x < copyWidth; x++) { + bits[stride / 4 * (y + i * TILE_SIZE) + (x + j * TILE_SIZE)] = 0; + } + } + } + } + } + + EsPaintTargetEndDirectAccess(instance->bitmap); +} + +Tile *ImageUpdateTile(Image *image, uint32_t x, uint32_t y, bool copyOldBits) { + EsAssert(x < image->tileCountX && y < image->tileCountY); + Tile **tileReference = image->tiles + y * image->tileCountX + x; + Tile *tile = *tileReference; + + if (!tile || tile->ownerID != image->id) { + if (tile && tile->referenceCount == 1) { + tile->ownerID = image->id; + + // EsPrint("reuse tile %d, %d for image %d\n", x, y, image->id); + } else { + Tile *old = tile; + if (old) old->referenceCount--; + + *tileReference = tile = (Tile *) EsHeapAllocate(sizeof(Tile), false); + tile->referenceCount = 1; + tile->ownerID = image->id; + + if (copyOldBits && old) { + EsMemoryCopy(tile->bits, old->bits, sizeof(old->bits)); + } + + // EsPrint("allocate new tile %d, %d for image %d\n", x, y, image->id); + } + } + + return tile; +} + +void ImageCopyFromPaintTarget(Instance *instance, Image *image, EsRectangle modifiedBounds) { + uint32_t *bits; + size_t width, height, stride; + EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride); + + modifiedBounds = EsRectangleIntersection(modifiedBounds, ES_RECT_4(0, width, 0, height)); + + for (int32_t i = modifiedBounds.t / TILE_SIZE; i <= modifiedBounds.b / TILE_SIZE; i++) { + for (int32_t j = modifiedBounds.l / TILE_SIZE; j <= modifiedBounds.r / TILE_SIZE; j++) { + if ((uint32_t) j >= image->tileCountX || (uint32_t) i >= image->tileCountY) { + continue; + } + + Tile *tile = ImageUpdateTile(image, j, i, false); + + int32_t copyWidth = TILE_SIZE, copyHeight = TILE_SIZE; + + if (j * TILE_SIZE + copyWidth > (int32_t) width) { + copyWidth = width - j * TILE_SIZE; + } + + if (i * TILE_SIZE + copyHeight > (int32_t) height) { + copyHeight = height - i * TILE_SIZE; + } + + for (int32_t y = 0; y < copyHeight; y++) { + for (int32_t x = 0; x < copyWidth; x++) { + tile->bits[y * TILE_SIZE + x] = bits[stride / 4 * (y + i * TILE_SIZE) + (x + j * TILE_SIZE)]; + } + } + } + } + + EsPaintTargetEndDirectAccess(instance->bitmap); +} + +void ImageUndoMessage(const void *item, EsUndoManager *manager, EsMessage *message) { + const Image *image = (const Image *) item; + Instance *instance = EsUndoGetInstance(manager); + + if (message->type == ES_MSG_UNDO_INVOKE) { + EsUndoPush(manager, ImageUndoMessage, &instance->image, sizeof(Image)); + instance->image = *image; + + if (instance->bitmapWidth != image->width || instance->bitmapHeight != image->height) { + instance->bitmapWidth = image->width; + instance->bitmapHeight = image->height; + EsPaintTargetDestroy(instance->bitmap); + instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false); + EsElementRelayout(EsElementGetLayoutParent(instance->canvas)); + } + + ImageCopyToPaintTarget(instance, image); + EsElementRepaint(instance->canvas); + } else if (message->type == ES_MSG_UNDO_CANCEL) { + ImageDelete(*image); + } +} + +int BrushSizeMessage(EsElement *element, EsMessage *message) { + EsTextbox *textbox = (EsTextbox *) element; + + if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA) { + double oldValue = EsTextboxGetContentsAsDouble(textbox); + double newValue = oldValue + message->numberDragDelta.delta * (message->numberDragDelta.fast ? 1 : 0.1); + + if (newValue < 1) { + newValue = 1; + } else if (newValue > 1000) { + newValue = 1000; + } + + char result[64]; + size_t resultBytes = EsStringFormat(result, sizeof(result), "%d.%d", (int) newValue, (int) (newValue * 10) % 10); + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, result, resultBytes); + } + + return 0; +} + +EsRectangle DrawFill(Instance *instance, EsPoint point) { + uint32_t color = 0xFF000000 | EsColorWellGetRGB(instance->colorWell); + EsRectangle modifiedBounds = ES_RECT_4(point.x, point.x, point.y, point.y); + + if ((uint32_t) point.x >= instance->bitmapWidth || (uint32_t) point.y >= instance->bitmapHeight) { + return {}; + } + + uint32_t *bits; + size_t width, height, stride; + EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride); + stride /= 4; + + Array pointsToVisit = {}; + + uint32_t replaceColor = bits[point.y * stride + point.x]; + if (replaceColor != color) pointsToVisit.Add(point); + + while (pointsToVisit.Length()) { + EsPoint startPoint = pointsToVisit.Pop(); + + if (startPoint.y < modifiedBounds.t) { + modifiedBounds.t = startPoint.y; + } + + if (startPoint.y > modifiedBounds.b) { + modifiedBounds.b = startPoint.y; + } + + for (ptrdiff_t delta = -1; delta <= 1; delta += 2) { + EsPoint point = startPoint; + uint32_t *pointer = bits + point.y * stride + point.x; + + bool spaceAbove = false; + bool spaceBelow = false; + + if (delta == 1) { + point.x += delta; + pointer += delta; + + if (point.x == (int32_t) width) { + break; + } + } + + while (true) { + if (*pointer != replaceColor) { + break; + } + + *pointer = color; + + if (point.x < modifiedBounds.l) { + modifiedBounds.l = point.x; + } + + if (point.x > modifiedBounds.r) { + modifiedBounds.r = point.x; + } + + if (point.y) { + if (!spaceAbove && pointer[-stride] == replaceColor) { + spaceAbove = true; + pointsToVisit.Add({ point.x, point.y - 1 }); + } else if (spaceAbove && pointer[-stride] != replaceColor) { + spaceAbove = false; + } + } + + if (point.y != (int32_t) height - 1) { + if (!spaceBelow && pointer[stride] == replaceColor) { + spaceBelow = true; + pointsToVisit.Add({ point.x, point.y + 1 }); + } else if (spaceBelow && pointer[stride] != replaceColor) { + spaceBelow = false; + } + } + + point.x += delta; + pointer += delta; + + if (point.x == (int32_t) width || point.x < 0) { + break; + } + } + } + } + + modifiedBounds.r++, modifiedBounds.b += 2; + pointsToVisit.Free(); + EsPaintTargetEndDirectAccess(instance->bitmap); + EsElementRepaint(instance->canvas, &modifiedBounds); + return modifiedBounds; +} + +EsRectangle DrawLine(Instance *instance, bool force = false) { + EsPoint point = EsMouseGetPosition(instance->canvas); + float brushSize = EsTextboxGetContentsAsDouble(instance->brushSize); + float spacing = brushSize * 0.1f; + uint32_t color = 0xFF000000 | EsColorWellGetRGB(instance->colorWell); + EsRectangle modifiedBounds = ES_RECT_4(instance->bitmapWidth, 0, instance->bitmapHeight, 0); + + uint32_t *bits; + size_t width, height, stride; + EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride); + stride /= 4; + + // Draw the line. + + while (true) { + float dx = point.x - instance->previousPointX; + float dy = point.y - instance->previousPointY; + float distance = EsCRTsqrtf(dx * dx + dy * dy); + + if (distance < spacing && !force) { + break; + } + + int32_t x0 = instance->previousPointX; + int32_t y0 = instance->previousPointY; + + EsRectangle bounds = ES_RECT_4(x0 - brushSize / 2 - 1, x0 + brushSize / 2 + 1, + y0 - brushSize / 2 - 1, y0 + brushSize / 2 + 1); + bounds = EsRectangleIntersection(bounds, ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight)); + modifiedBounds = EsRectangleBounding(modifiedBounds, bounds); + + for (int32_t y = bounds.t; y < bounds.b; y++) { + for (int32_t x = bounds.l; x < bounds.r; x++) { + float distance = (x - x0) * (x - x0) + (y - y0) * (y - y0); + + if (distance < brushSize * brushSize * 0.25f) { + bits[y * stride + x] = color; + } + } + } + + if (force) { + break; + } + + instance->previousPointX += dx / distance * spacing; + instance->previousPointY += dy / distance * spacing; + } + + // Repaint the canvas. + + modifiedBounds.r++, modifiedBounds.b++; + EsPaintTargetEndDirectAccess(instance->bitmap); + EsElementRepaint(instance->canvas, &modifiedBounds); + return modifiedBounds; +} + +int CanvasMessage(EsElement *element, EsMessage *message) { + Instance *instance = element->instance; + + if (message->type == ES_MSG_PAINT) { + EsPainter *painter = message->painter; + EsRectangle bounds = EsPainterBoundsInset(painter); + EsRectangle area = ES_RECT_4(bounds.l, bounds.l + instance->bitmapWidth, bounds.t, bounds.t + instance->bitmapHeight); + EsDrawPaintTarget(painter, instance->bitmap, area, ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight), 0xFF); + + if (instance->commandRectangle.check == ES_CHECK_CHECKED && instance->dragged) { + EsRectangle rectangle = instance->modifiedBounds; + rectangle.l += painter->offsetX, rectangle.r += painter->offsetX; + rectangle.t += painter->offsetY, rectangle.b += painter->offsetY; + EsDrawBlock(painter, rectangle, 0xFF000000 | EsColorWellGetRGB(instance->colorWell)); + } + } else if ((message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_MOVED) + && EsMouseIsLeftHeld() && instance->commandBrush.check == ES_CHECK_CHECKED) { + instance->modifiedBounds = EsRectangleBounding(DrawLine(instance), instance->modifiedBounds); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG && instance->commandRectangle.check == ES_CHECK_CHECKED) { + EsRectangle previous = instance->modifiedBounds; + EsPoint point = EsMouseGetPosition(element); + EsRectangle rectangle; + if (point.x < instance->previousPointX) rectangle.l = point.x, rectangle.r = instance->previousPointX + 1; + else rectangle.l = instance->previousPointX, rectangle.r = point.x + 1; + if (point.y < instance->previousPointY) rectangle.t = point.y, rectangle.b = instance->previousPointY + 1; + else rectangle.t = instance->previousPointY, rectangle.b = point.y + 1; + instance->modifiedBounds = rectangle; + EsRectangle bounding = EsRectangleBounding(rectangle, previous); + EsElementRepaint(element, &bounding); + instance->dragged = true; + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN && instance->commandBrush.check == ES_CHECK_CHECKED) { + EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image)); + instance->image = ImageFork(instance, instance->image); + EsPoint point = EsMouseGetPosition(element); + instance->previousPointX = point.x, instance->previousPointY = point.y; + instance->modifiedBounds = ES_RECT_4(instance->bitmapWidth, 0, instance->bitmapHeight, 0); + DrawLine(instance, true); + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN && instance->commandRectangle.check == ES_CHECK_CHECKED) { + EsPoint point = EsMouseGetPosition(element); + instance->previousPointX = point.x, instance->previousPointY = point.y; + instance->dragged = false; + } else if (message->type == ES_MSG_MOUSE_LEFT_UP && instance->commandBrush.check == ES_CHECK_CHECKED) { + ImageCopyFromPaintTarget(instance, &instance->image, instance->modifiedBounds); + } else if (message->type == ES_MSG_MOUSE_LEFT_UP && instance->commandRectangle.check == ES_CHECK_CHECKED && instance->dragged) { + instance->dragged = false; + EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image)); + instance->image = ImageFork(instance, instance->image); + EsPainter painter = {}; + painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight); + painter.target = instance->bitmap; + EsDrawBlock(&painter, instance->modifiedBounds, 0xFF000000 | EsColorWellGetRGB(instance->colorWell)); + ImageCopyFromPaintTarget(instance, &instance->image, instance->modifiedBounds); + } else if (message->type == ES_MSG_MOUSE_LEFT_UP && instance->commandFill.check == ES_CHECK_CHECKED) { + EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image)); + instance->image = ImageFork(instance, instance->image); + + EsRectangle modifiedBounds = DrawFill(instance, EsMouseGetPosition(element)); + ImageCopyFromPaintTarget(instance, &instance->image, modifiedBounds); + } else if (message->type == ES_MSG_GET_CURSOR) { + message->cursorStyle = ES_CURSOR_CROSS_HAIR_PICK; + } else if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = instance->bitmapWidth; + } else if (message->type == ES_MSG_GET_HEIGHT) { + message->measure.height = instance->bitmapHeight; + } else { + return 0; + } + + return ES_HANDLED; +} + +void CommandSelectTool(Instance *instance, EsElement *, EsCommand *command) { + if (command->check == ES_CHECK_CHECKED) { + return; + } + + EsCommandSetCheck(&instance->commandBrush, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(&instance->commandFill, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(&instance->commandRectangle, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(&instance->commandSelect, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(&instance->commandText, ES_CHECK_UNCHECKED, false); + EsCommandSetCheck(command, ES_CHECK_CHECKED, false); + + if (command == &instance->commandBrush) EsButtonSetIcon(instance->toolDropdown, ES_ICON_DRAW_FREEHAND); + if (command == &instance->commandFill) EsButtonSetIcon(instance->toolDropdown, ES_ICON_COLOR_FILL); + if (command == &instance->commandRectangle) EsButtonSetIcon(instance->toolDropdown, ES_ICON_DRAW_RECTANGLE); + if (command == &instance->commandSelect) EsButtonSetIcon(instance->toolDropdown, ES_ICON_OBJECT_GROUP); + if (command == &instance->commandText) EsButtonSetIcon(instance->toolDropdown, ES_ICON_DRAW_TEXT); + + instance->dragged = false; +} + +int BitmapSizeTextboxMessage(EsElement *element, EsMessage *message) { + EsTextbox *textbox = (EsTextbox *) element; + Instance *instance = textbox->instance; + + if (message->type == ES_MSG_TEXTBOX_EDIT_END || message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_END) { + char *expression = EsTextboxGetContents(textbox); + EsCalculationValue value = EsCalculateFromUserExpression(expression); + EsHeapFree(expression); + + if (value.error) { + return ES_REJECTED; + } + + if (value.number < 1) value.number = 1; + else if (value.number > 20000) value.number = 20000; + int newSize = (int) (value.number + 0.5); + char result[64]; + size_t resultBytes = EsStringFormat(result, sizeof(result), "%d", newSize); + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, result, resultBytes); + + int oldSize = textbox->userData.i ? instance->bitmapHeight : instance->bitmapWidth; + + if (oldSize == newSize) { + return ES_HANDLED; + } + + EsRectangle clearRegion; + + if (textbox->userData.i) { + instance->bitmapHeight = newSize; + clearRegion = ES_RECT_4(0, instance->bitmapWidth, oldSize, newSize); + } else { + instance->bitmapWidth = newSize; + clearRegion = ES_RECT_4(oldSize, newSize, 0, instance->bitmapHeight); + } + + EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image)); + instance->image = ImageFork(instance, instance->image, instance->bitmapWidth, instance->bitmapHeight); + EsPaintTargetDestroy(instance->bitmap); + instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false); + ImageCopyToPaintTarget(instance, &instance->image); + + EsPainter painter = {}; + painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight); + painter.target = instance->bitmap; + EsDrawBlock(&painter, clearRegion, 0xFFFFFFFF); + ImageCopyFromPaintTarget(instance, &instance->image, clearRegion); + + EsElementRelayout(EsElementGetLayoutParent(instance->canvas)); + + return ES_HANDLED; + } else if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA) { + int oldValue = EsTextboxGetContentsAsDouble(textbox); + int newValue = oldValue + message->numberDragDelta.delta * (message->numberDragDelta.fast ? 10 : 1); + if (newValue < 1) newValue = 1; + else if (newValue > 20000) newValue = 20000; + char result[64]; + size_t resultBytes = EsStringFormat(result, sizeof(result), "%d", newValue); + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, result, resultBytes); + return ES_HANDLED; + } + + return 0; +} + +void ImageTransform(EsMenu *menu, EsGeneric context) { + Instance *instance = menu->instance; + EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image)); + + uint32_t *bits; + size_t width, height, stride; + EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride); + + EsPaintTarget *newTarget = nullptr; + uint32_t *newBits = nullptr; + size_t newStride = 0; + + if (context.i == 1 || context.i == 2) { + instance->image = ImageFork(instance, instance->image, height, width); + newTarget = EsPaintTargetCreate(height, width, false); + EsPaintTargetStartDirectAccess(newTarget, &newBits, &height, &width, &newStride); + } else { + instance->image = ImageFork(instance, instance->image); + } + + if (context.i == 1 /* rotate left */) { + for (uintptr_t i = 0; i < height; i++) { + for (uintptr_t j = 0; j < width; j++) { + newBits[(width - j - 1) * newStride / 4 + i] = bits[i * stride / 4 + j]; + } + } + } else if (context.i == 2 /* rotate right */) { + for (uintptr_t i = 0; i < height; i++) { + for (uintptr_t j = 0; j < width; j++) { + newBits[j * newStride / 4 + (height - i - 1)] = bits[i * stride / 4 + j]; + } + } + } else if (context.i == 3 /* flip horizontally */) { + for (uintptr_t i = 0; i < height; i++) { + for (uintptr_t j = 0; j < width / 2; j++) { + uint32_t temporary = bits[i * stride / 4 + j]; + bits[i * stride / 4 + j] = bits[i * stride / 4 + (width - j - 1)]; + bits[i * stride / 4 + (width - j - 1)] = temporary; + } + } + } else if (context.i == 4 /* flip vertically */) { + for (uintptr_t i = 0; i < height / 2; i++) { + for (uintptr_t j = 0; j < width; j++) { + uint32_t temporary = bits[i * stride / 4 + j]; + bits[i * stride / 4 + j] = bits[(height - i - 1) * stride / 4 + j]; + bits[(height - i - 1) * stride / 4 + j] = temporary; + } + } + } + + EsPaintTargetEndDirectAccess(instance->bitmap); + + if (newTarget) { + EsPaintTargetDestroy(instance->bitmap); + instance->bitmap = newTarget; + size_t width, height; + EsPaintTargetGetSize(instance->bitmap, &width, &height); + instance->bitmapWidth = width; + instance->bitmapHeight = height; + EsElementRelayout(EsElementGetLayoutParent(instance->canvas)); + } + + ImageCopyFromPaintTarget(instance, &instance->image, ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight)); + EsElementRepaint(instance->canvas); +} + +void MenuTools(Instance *instance, EsElement *element, EsCommand *) { + EsMenu *menu = EsMenuCreate(element); + EsMenuAddCommandsFromToolbar(menu, instance->toolPanel); + EsMenuShow(menu); +} + +void MenuImage(Instance *instance, EsElement *element, EsCommand *) { + EsMenu *menu = EsMenuCreate(element); + + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(ImageEditorCanvasSize)); + EsPanel *table = EsPanelCreate(menu, ES_PANEL_HORIZONTAL | ES_PANEL_TABLE, &styleImageMenuTable); + EsPanelSetBands(table, 2, 2); + + char buffer[64]; + size_t bytes; + EsTextbox *textbox; + + bytes = EsStringFormat(buffer, sizeof(buffer), "%d", instance->bitmapWidth); + EsTextDisplayCreate(table, ES_CELL_H_RIGHT, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(ImageEditorPropertyWidth)); + textbox = EsTextboxCreate(table, ES_TEXTBOX_EDIT_BASED, &styleBitmapSizeTextbox); + EsTextboxInsert(textbox, buffer, bytes, false); + textbox->userData.i = 0; + textbox->messageUser = BitmapSizeTextboxMessage; + EsTextboxUseNumberOverlay(textbox, true); + + bytes = EsStringFormat(buffer, sizeof(buffer), "%d", instance->bitmapHeight); + EsTextDisplayCreate(table, ES_CELL_H_RIGHT, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(ImageEditorPropertyHeight)); + textbox = EsTextboxCreate(table, ES_TEXTBOX_EDIT_BASED, &styleBitmapSizeTextbox); + EsTextboxInsert(textbox, buffer, bytes, false); + textbox->userData.i = 1; + textbox->messageUser = BitmapSizeTextboxMessage; + EsTextboxUseNumberOverlay(textbox, true); + + EsMenuAddSeparator(menu); + EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(ImageEditorImageTransformations)); + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorRotateLeft), ImageTransform, 1); + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorRotateRight), ImageTransform, 2); + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorFlipHorizontally), ImageTransform, 3); + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorFlipVertically), ImageTransform, 4); + + EsMenuShow(menu); +} + +void InstanceCreate(EsMessage *message) { + Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(ImageEditorTitle)); + EsElement *toolbar = EsWindowGetToolbar(instance->window); + + // Register commands. + + EsCommandRegister(&instance->commandBrush, instance, CommandSelectTool, 1, "N", true); + EsCommandRegister(&instance->commandFill, instance, CommandSelectTool, 2, "Shift+B", true); + EsCommandRegister(&instance->commandRectangle, instance, CommandSelectTool, 3, "Shift+R", true); + EsCommandRegister(&instance->commandSelect, instance, CommandSelectTool, 4, "R", false); + EsCommandRegister(&instance->commandText, instance, CommandSelectTool, 5, "T", false); + + EsCommandSetCheck(&instance->commandBrush, ES_CHECK_CHECKED, false); + + // Create the toolbar. + + EsButton *button; + + button = EsButtonCreate(toolbar, ES_BUTTON_DROPDOWN, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorImage)); + EsButtonSetIcon(button, ES_ICON_IMAGE_X_GENERIC); + button->accessKey = 'I'; + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT); + EsButtonOnCommand(button, MenuImage); + button = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_MEDIUM); + EsCommandAddButton(EsCommandByID(instance, ES_COMMAND_UNDO), button); + EsButtonSetIcon(button, ES_ICON_EDIT_UNDO_SYMBOLIC); + button->accessKey = 'U'; + button = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_MEDIUM); + EsCommandAddButton(EsCommandByID(instance, ES_COMMAND_REDO), button); + EsButtonSetIcon(button, ES_ICON_EDIT_REDO_SYMBOLIC); + button->accessKey = 'R'; + + EsSpacerCreate(toolbar, ES_CELL_FILL); + + button = instance->toolDropdown = EsButtonCreate(toolbar, ES_BUTTON_DROPDOWN, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorPickTool)); + EsButtonSetIcon(button, ES_ICON_DRAW_FREEHAND); + EsButtonOnCommand(button, MenuTools); + button->accessKey = 'T'; + + instance->toolPanel = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR); + button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolBrush)); + EsCommandAddButton(&instance->commandBrush, button); + EsButtonSetIcon(button, ES_ICON_DRAW_FREEHAND); + button->accessKey = 'B'; + button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolFill)); + EsCommandAddButton(&instance->commandFill, button); + EsButtonSetIcon(button, ES_ICON_COLOR_FILL); + button->accessKey = 'F'; + button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolRectangle)); + EsCommandAddButton(&instance->commandRectangle, button); + EsButtonSetIcon(button, ES_ICON_DRAW_RECTANGLE); + button->accessKey = 'E'; + button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolSelect)); + EsCommandAddButton(&instance->commandSelect, button); + EsButtonSetIcon(button, ES_ICON_OBJECT_GROUP); + button->accessKey = 'S'; + button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolText)); + EsCommandAddButton(&instance->commandText, button); + EsButtonSetIcon(button, ES_ICON_DRAW_TEXT); + button->accessKey = 'T'; + + EsWindowAddSizeAlternative(instance->window, instance->toolDropdown, instance->toolPanel, 1100, 0); + + EsSpacerCreate(toolbar, ES_CELL_FILL); + + EsPanel *section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(ImageEditorPropertyColor)); + instance->colorWell = EsColorWellCreate(section, ES_FLAGS_DEFAULT, 0); + instance->colorWell->accessKey = 'C'; + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, 0, 5, 0); + + section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(ImageEditorPropertyBrushSize)); + instance->brushSize = EsTextboxCreate(section, ES_TEXTBOX_EDIT_BASED, ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT); + instance->brushSize->messageUser = BrushSizeMessage; + EsTextboxUseNumberOverlay(instance->brushSize, false); + EsTextboxInsert(instance->brushSize, EsLiteral("5.0")); + instance->brushSize->accessKey = 'Z'; + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, 0, 1, 0); + + // Create the user interface. + + EsWindowSetIcon(instance->window, ES_ICON_MULTIMEDIA_PHOTO_MANAGER); + + EsCanvasPane *canvasPane = EsCanvasPaneCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND); + instance->canvas = EsCustomElementCreate(canvasPane, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE); + instance->canvas->messageUser = CanvasMessage; + EsElementFocus(instance->canvas, false); + + // Setup the paint target and the image. + + instance->bitmapWidth = 500; + instance->bitmapHeight = 400; + instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false); + EsPainter painter = {}; + painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight); + painter.target = instance->bitmap; + EsDrawBlock(&painter, painter.clip, 0xFFFFFFFF); + instance->image = ImageFork(instance, {}, instance->bitmapWidth, instance->bitmapHeight); + ImageCopyFromPaintTarget(instance, &instance->image, painter.clip); +} + +void _start() { + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + InstanceCreate(message); + } else if (message->type == ES_MSG_INSTANCE_OPEN) { + Instance *instance = message->instanceOpen.instance; + size_t fileSize; + uint8_t *file = (uint8_t *) EsFileStoreReadAll(message->instanceOpen.file, &fileSize); + + if (!file) { + EsInstanceOpenComplete(message, false); + continue; + } + + uint32_t width, height; + uint8_t *bits = EsImageLoad(file, fileSize, &width, &height, 4); + EsHeapFree(file); + + if (!bits) { + EsInstanceOpenComplete(message, false, INTERFACE_STRING(ImageEditorUnsupportedFormat)); + continue; + } + + EsPaintTargetDestroy(instance->bitmap); + ImageDelete(instance->image); + + instance->bitmapWidth = width; + instance->bitmapHeight = height; + instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false); + EsPainter painter = {}; + painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight); + painter.target = instance->bitmap; + EsDrawBitmap(&painter, painter.clip, (uint32_t *) bits, width * 4, 0xFF); + instance->image = ImageFork(instance, {}, instance->bitmapWidth, instance->bitmapHeight); + ImageCopyFromPaintTarget(instance, &instance->image, painter.clip); + EsElementRelayout(EsElementGetLayoutParent(instance->canvas)); + + EsHeapFree(bits); + EsInstanceOpenComplete(message, true); + } else if (message->type == ES_MSG_INSTANCE_DESTROY) { + Instance *instance = message->instanceDestroy.instance; + EsPaintTargetDestroy(instance->bitmap); + ImageDelete(instance->image); + } + } +} diff --git a/apps/image_editor.ini b/apps/image_editor.ini new file mode 100644 index 0000000..0e526df --- /dev/null +++ b/apps/image_editor.ini @@ -0,0 +1,15 @@ +[general] +name=Image Editor +icon=icon_multimedia_photo_manager +use_single_process=1 + +[build] +source=apps/image_editor.cpp + +[@handler] +extension=jpg +action=open + +[@handler] +extension=png +action=open diff --git a/apps/irc_client.cpp b/apps/irc_client.cpp new file mode 100644 index 0000000..2f7ab68 --- /dev/null +++ b/apps/irc_client.cpp @@ -0,0 +1,327 @@ +// TODO Don't use EsTextbox for the output.. +// TODO Put the connection settings in a Panel.Popup. + +#define ES_INSTANCE_TYPE Instance +#include + +struct Instance : EsInstance { + EsTextbox *textboxNick; + EsTextbox *textboxAddress; + EsTextbox *textboxPort; + EsTextbox *textboxOutput; + EsTextbox *textboxInput; + EsButton *buttonConnect; + + EsThreadInformation networkingThread; + + EsMutex inputCommandMutex; + char *inputCommand; + size_t inputCommandBytes; +}; + +const EsStyle styleSmallTextbox = { + .inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE, + + .metrics = { + .mask = ES_THEME_METRICS_PREFERRED_WIDTH, + .preferredWidth = 100, + }, +}; + +const EsStyle styleOutputTextbox = { + .inherit = ES_STYLE_TEXTBOX_NO_BORDER, + + .metrics = { + .mask = ES_THEME_METRICS_FONT_FAMILY, + .fontFamily = ES_FONT_MONOSPACED, + }, +}; + +const EsStyle styleInputTextbox = { + .inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE, + + .metrics = { + .mask = ES_THEME_METRICS_FONT_FAMILY, + .fontFamily = ES_FONT_MONOSPACED, + }, +}; + +int TextboxInputCallback(EsElement *element, EsMessage *message) { + Instance *instance = element->instance; + + if (message->type == ES_MSG_KEY_DOWN) { + if (message->keyboard.scancode == ES_SCANCODE_ENTER) { + size_t inputCommandBytes = 0; + char *inputCommand = EsTextboxGetContents(instance->textboxInput, &inputCommandBytes); + + if (inputCommandBytes) { + EsMutexAcquire(&instance->inputCommandMutex); + EsHeapFree(instance->inputCommand); + instance->inputCommand = inputCommand; + instance->inputCommandBytes = inputCommandBytes; + EsMutexRelease(&instance->inputCommandMutex); + } else { + EsHeapFree(inputCommand); + } + + EsTextboxClear(instance->textboxInput, false); + return ES_HANDLED; + } + } + + return 0; +} + +void NetworkingThread(EsGeneric argument) { + Instance *instance = (Instance *) argument.p; + + char errorMessage[4096]; + size_t errorMessageBytes = 0; + + char message[4096]; + size_t messageBytes = 0; + + char nick[64]; + size_t nickBytes = 0; + + char *password = nullptr; + size_t passwordBytes = 0; + + char *address = nullptr; + size_t addressBytes = 0; + + char buffer[1024]; + uintptr_t bufferPosition = 0; + + EsConnection connection = {}; + connection.sendBufferBytes = 65536; + connection.receiveBufferBytes = 65536; + + { + EsMessageMutexAcquire(); + address = EsTextboxGetContents(instance->textboxAddress, &addressBytes); + EsMessageMutexRelease(); + EsError error = EsAddressResolve(address, addressBytes, ES_FLAGS_DEFAULT, &connection.address); + + if (error != ES_SUCCESS) { + errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage), + "The address name '%s' could not be found.\n", addressBytes, address); + goto exit; + } + } + + { + EsMessageMutexAcquire(); + size_t portBytes; + char *port = EsTextboxGetContents(instance->textboxPort, &portBytes); + EsMessageMutexRelease(); + connection.address.port = EsIntegerParse(port, portBytes); + EsHeapFree(port); + } + + { + EsMessageMutexAcquire(); + char *_nick = EsTextboxGetContents(instance->textboxNick, &nickBytes); + EsMessageMutexRelease(); + if (nickBytes > sizeof(nick)) nickBytes = sizeof(nick); + EsMemoryCopy(nick, _nick, nickBytes); + EsHeapFree(_nick); + + for (uintptr_t i = 0; i < nickBytes; i++) { + if (nick[i] == ':') { + password = nick + i + 1; + passwordBytes = nickBytes - i - 1; + nickBytes = i; + } + } + } + + { + EsError error = EsConnectionOpen(&connection, ES_CONNECTION_OPEN_WAIT); + + if (error != ES_SUCCESS) { + errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage), + "Could not open the connection (%d).", error); + goto exit; + } + + messageBytes = EsStringFormat(message, sizeof(message), "%z%s%zNICK %s\r\nUSER %s localhost %s :%s\r\n", + password ? "PASS " : "", passwordBytes, password, password ? "\r\n" : "", + nickBytes, nick, nickBytes, nick, addressBytes, address, nickBytes, nick); + EsConnectionWriteSync(&connection, message, messageBytes); + } + + while (true) { + // TODO Ping the server every 2 minutes. + // TODO If we've received no messages for 5 minutes, timeout. + + uintptr_t inputBytes = 0; + + while (true) { + char *inputCommand = nullptr; + size_t inputCommandBytes = 0; + + EsMutexAcquire(&instance->inputCommandMutex); + inputCommand = instance->inputCommand; + inputCommandBytes = instance->inputCommandBytes; + instance->inputCommand = nullptr; + instance->inputCommandBytes = 0; + EsMutexRelease(&instance->inputCommandMutex); + + if (inputCommand) { + messageBytes = EsStringFormat(message, sizeof(message), "%s\r\n", inputCommandBytes, inputCommand); + EsConnectionWriteSync(&connection, message, messageBytes); + EsHeapFree(inputCommand); + } + + size_t bytesRead; + EsError error = EsConnectionRead(&connection, buffer + bufferPosition, sizeof(buffer) - bufferPosition, &bytesRead); + + if (error != ES_SUCCESS) { + errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage), "The connection was lost (%d).", error); + goto exit; + } + + bufferPosition += bytesRead; + + if (bufferPosition >= 2) { + for (uintptr_t i = 0; i < bufferPosition - 1; i++) { + if (buffer[i] == '\r' && buffer[i + 1] == '\n') { + buffer[i] = 0; + inputBytes = i + 2; + goto gotMessage; + } + } + } + + if (bufferPosition == sizeof(buffer)) { + errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage), "The server sent an invalid message."); + goto exit; + } + } + + gotMessage:; + + EsMessageMutexAcquire(); + EsTextboxInsert(instance->textboxOutput, buffer); + EsTextboxInsert(instance->textboxOutput, "\n"); + EsMessageMutexRelease(); + + { + EsPrint("=================\n%z\n", buffer); + + const char *command = nullptr, *user = nullptr, *parameters = nullptr, *text = nullptr; + char *position = buffer; + + if (*position == ':') { + user = ++position; + while (*position && *position != ' ') position++; + if (*position) *position++ = 0; + } else { + user = address; + } + + while (*position && *position == ' ') position++; + command = position; + while (*position && *position != ' ') position++; + if (*position) *position++ = 0; + while (*position && *position == ' ') position++; + + if (*position != ':') { + parameters = position; + while (*position && *position != ' ') position++; + if (*position) *position++ = 0; + } + + while (*position && *position == ' ') position++; + if (*position == ':') text = position + 1; + + if (0 == EsCRTstrcmp(command, "PING")) { + messageBytes = EsStringFormat(message, sizeof(message), "PONG :%z\r\n", text ?: parameters); + EsConnectionWriteSync(&connection, message, messageBytes); + } + + EsPrint("command: '%z'\nuser: '%z'\nparameters: '%z'\ntext: '%z'\n\n", command, user, parameters, text); + } + + EsMemoryMove(buffer + inputBytes, buffer + bufferPosition, -inputBytes, false); + bufferPosition -= inputBytes; + } + + exit:; + + if (connection.handle) { + EsConnectionClose(&connection); + } + + EsMessageMutexAcquire(); + + if (errorMessageBytes) { + EsDialogShowAlert(instance->window, EsLiteral("Connection failed"), errorMessage, errorMessageBytes, + ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); + } + + EsElementSetDisabled(instance->textboxAddress, false); + EsElementSetDisabled(instance->textboxNick, false); + EsElementSetDisabled(instance->textboxPort, false); + EsElementSetDisabled(instance->textboxInput, true); + EsElementSetDisabled(instance->buttonConnect, false); + + EsMessageMutexRelease(); + + EsHeapFree(address); +} + +void ConnectCommand(Instance *instance, EsElement *, EsCommand *) { + EsElementSetDisabled(instance->textboxAddress, true); + EsElementSetDisabled(instance->textboxNick, true); + EsElementSetDisabled(instance->textboxPort, true); + EsElementSetDisabled(instance->textboxInput, false); + EsElementSetDisabled(instance->buttonConnect, true); + + EsThreadCreate(NetworkingThread, &instance->networkingThread, instance); +} + +void _start() { + _init(); + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + // Create an new instance. + + Instance *instance = EsInstanceCreate(message, "IRC Client"); + EsWindow *window = instance->window; + EsWindowSetIcon(window, ES_ICON_INTERNET_CHAT); + + // Create the toolbar. + + EsElement *toolbar = EsWindowGetToolbar(window); + EsPanel *section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, EsLiteral("Nick:")); + instance->textboxNick = EsTextboxCreate(section, ES_FLAGS_DEFAULT, &styleSmallTextbox); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, EsLiteral("Address:")); + instance->textboxAddress = EsTextboxCreate(section, ES_FLAGS_DEFAULT, &styleSmallTextbox); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, EsLiteral("Port:")); + instance->textboxPort = EsTextboxCreate(section, ES_FLAGS_DEFAULT, &styleSmallTextbox); + EsSpacerCreate(toolbar, ES_CELL_H_FILL); + instance->buttonConnect = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, 0, EsLiteral("Connect")); + EsButtonOnCommand(instance->buttonConnect, ConnectCommand); + + // Create the main area. + + EsPanel *panel = EsPanelCreate(window, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); + instance->textboxOutput = EsTextboxCreate(panel, ES_CELL_FILL | ES_TEXTBOX_MULTILINE, &styleOutputTextbox); + EsPanelCreate(panel, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL); + EsPanel *inputArea = EsPanelCreate(panel, ES_PANEL_HORIZONTAL | ES_CELL_H_FILL, ES_STYLE_PANEL_FILLED); + instance->textboxInput = EsTextboxCreate(inputArea, ES_CELL_FILL, &styleInputTextbox); + instance->textboxInput->messageUser = TextboxInputCallback; + EsElementSetDisabled(instance->textboxInput); + } + } +} diff --git a/apps/irc_client.ini b/apps/irc_client.ini new file mode 100644 index 0000000..1ed7d78 --- /dev/null +++ b/apps/irc_client.ini @@ -0,0 +1,7 @@ +[general] +name=IRC Client +icon=icon_internet_chat +use_single_process=1 + +[build] +source=apps/irc_client.cpp diff --git a/apps/markdown_viewer.cpp b/apps/markdown_viewer.cpp new file mode 100644 index 0000000..c9149ef --- /dev/null +++ b/apps/markdown_viewer.cpp @@ -0,0 +1,500 @@ +#define ES_INSTANCE_TYPE Instance +#include + +#include +#include + +#include +#define IMPLEMENTATION +#include +#undef IMPLEMENTATION + +// TODO Inline code background? + +// TODO When resizing the window, maintain the scroll position. +// TODO Table of contents/navigation pane. +// TODO Searching. + +// TODO Images. +// TODO Proper link support. + +// TODO Embedding markdown viewers into other applications. + +// #define DEBUG_PARSER_OUTPUT + +struct Span { + bool em, strong, monospaced, link; + uintptr_t offset; +}; + +struct Instance : EsInstance { + EsElement *root; + + EsElement *active; + Array spans; + Array text; + int32_t paddingBefore; + bool inBlockQuote; + int inListDepth; + int tableColumnCount; + + int debugNestDepth; +}; + +#define MAKE_TEXT_STYLE(_textColor, _textSize, _fontFamily, _fontWeight, _isItalic) \ + { \ + .metrics = { \ + .mask = ES_THEME_METRICS_TEXT_COLOR | ES_THEME_METRICS_TEXT_ALIGN | ES_THEME_METRICS_TEXT_SIZE \ + | ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_FONT_WEIGHT \ + | ES_THEME_METRICS_IS_ITALIC, \ + .textColor = _textColor, \ + .textAlign = ES_TEXT_H_LEFT | ES_TEXT_V_TOP | ES_TEXT_WRAP, \ + .textSize = (int) (_textSize * 0.64 + 0.5), \ + .fontFamily = _fontFamily, \ + .fontWeight = _fontWeight, \ + .isItalic = _isItalic, \ + }, \ + } + +#define PARAGRAPH_PADDING_BEFORE (0) +#define PARAGRAPH_PADDING_AFTER (16) +#define H1_PADDING_BEFORE (16) +#define H1_PADDING_AFTER (16) +#define H2_PADDING_BEFORE (24) +#define H2_PADDING_AFTER (16) +#define H3_PADDING_BEFORE (24) +#define H3_PADDING_AFTER (16) +#define HEADING_UNDERLINE_GAP (7) +#define HR_PADDING_BEFORE (24) +#define HR_PADDING_AFTER (24) +#define QUOTE_PADDING_BEFORE (16) +#define QUOTE_PADDING_AFTER (0) +#define TABLE_PADDING_BEFORE (16) +#define TABLE_PADDING_AFTER (16) + +#define COLOR_BACKGROUND (0xFFFDFDFD) +#define COLOR_GRAY1 (0xFFE1E4E8) +#define COLOR_GRAY2 (0xFFEBEDEF) +#define COLOR_GRAY3 (0xFFF6F8FA) +#define COLOR_TEXT1 (0xFF24292E) +#define COLOR_TEXT2 (0xFF5A636D) +#define COLOR_TEXT_LINK (0xFF0366D6) + +EsStyle styleBackground = { + .appearance = { + .enabled = true, + .backgroundColor = COLOR_BACKGROUND, + }, +}; + +EsStyle styleRoot = { + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_PREFERRED_WIDTH, + .insets = ES_RECT_4(32, 32, 16, 32), + .preferredWidth = 800, + }, +}; + +EsStyle styleQuote = { + .metrics = { + .mask = ES_THEME_METRICS_INSETS, + .insets = ES_RECT_4(20, 16, 0, 0), + }, + + .appearance = { + .enabled = true, + .borderColor = COLOR_GRAY1, + .borderSize = ES_RECT_4(4, 0, 0, 0), + }, +}; + +EsStyle styleList = { + .metrics = { + .mask = ES_THEME_METRICS_GAP_MAJOR, + .gapMajor = 5, + }, +}; + +EsStyle styleHeadingUnderline = { + .metrics = { + .mask = ES_THEME_METRICS_PREFERRED_HEIGHT, + .preferredHeight = 1, + }, + + .appearance = { + .enabled = true, + .backgroundColor = COLOR_GRAY2, + }, +}; + +EsStyle styleCodeBlock = { + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_CLIP_ENABLED, + .insets = ES_RECT_4(16, 16, 10, 10), + .clipEnabled = true, + }, + + .appearance = { + .enabled = true, + .backgroundColor = COLOR_GRAY3, + }, +}; + +EsStyle styleHorizontalRule = { + .metrics = { + .mask = ES_THEME_METRICS_PREFERRED_HEIGHT, + .preferredHeight = 4, + }, + + .appearance = { + .enabled = true, + .backgroundColor = COLOR_GRAY1, + }, +}; + +EsStyle styleTable = { +}; + +EsStyle styleTD = { + .inherit = ES_STYLE_TEXT_PARAGRAPH, + + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_TEXT_SIZE | ES_THEME_METRICS_TEXT_COLOR, + .insets = ES_RECT_4(8, 8, 8, 0), + .textColor = COLOR_TEXT1, + .textSize = (int) (16 * 0.64 + 0.5), + }, +}; + +EsStyle styleTH = { + .inherit = ES_STYLE_TEXT_PARAGRAPH, + + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_TEXT_SIZE | ES_THEME_METRICS_TEXT_COLOR | ES_THEME_METRICS_FONT_WEIGHT, + .insets = ES_RECT_4(8, 8, 0, 4), + .textColor = COLOR_TEXT1, + .textSize = (int) (16 * 0.64 + 0.5), + .fontWeight = 5, + }, + + .appearance = { + .enabled = true, + .borderColor = COLOR_GRAY1, + .borderSize = ES_RECT_4(0, 0, 0, 1), + }, +}; + +EsStyle styleHeading1 = MAKE_TEXT_STYLE(COLOR_TEXT1, 32, ES_FONT_SANS, 6, false); +EsStyle styleHeading2 = MAKE_TEXT_STYLE(COLOR_TEXT1, 24, ES_FONT_SANS, 6, false); +EsStyle styleHeading3 = MAKE_TEXT_STYLE(COLOR_TEXT1, 20, ES_FONT_SANS, 6, false); +EsStyle styleParagraph = MAKE_TEXT_STYLE(COLOR_TEXT1, 16, ES_FONT_SANS, 4, false); +EsStyle styleQuoteParagraph = MAKE_TEXT_STYLE(COLOR_TEXT2, 16, ES_FONT_SANS, 4, false); +EsStyle styleCode = MAKE_TEXT_STYLE(COLOR_TEXT1, 16, ES_FONT_MONOSPACED, 4, false); + +const char *blockTypes[] = { + "MD_BLOCK_DOC", + "MD_BLOCK_QUOTE", + "MD_BLOCK_UL", + "MD_BLOCK_OL", + "MD_BLOCK_LI", + "MD_BLOCK_HR", + "MD_BLOCK_H", + "MD_BLOCK_CODE", + "MD_BLOCK_HTML", + "MD_BLOCK_P", + "MD_BLOCK_TABLE", + "MD_BLOCK_THEAD", + "MD_BLOCK_TBODY", + "MD_BLOCK_TR", + "MD_BLOCK_TH", + "MD_BLOCK_TD", +}; + +const char *spanTypes[] = { + "MD_SPAN_EM", + "MD_SPAN_STRONG", + "MD_SPAN_A", + "MD_SPAN_IMG", + "MD_SPAN_CODE", + "MD_SPAN_DEL", + "MD_SPAN_LATEXMATH", + "MD_SPAN_LATEXMATH_DISPLAY", + "MD_SPAN_WIKILINK", + "MD_SPAN_U", +}; + +const char *textTypes[] = { + "MD_TEXT_NORMAL", + "MD_TEXT_NULLCHAR", + "MD_TEXT_BR", + "MD_TEXT_SOFTBR", + "MD_TEXT_ENTITY", + "MD_TEXT_CODE", + "MD_TEXT_HTML", + "MD_TEXT_LATEXMATH", +}; + +void AddPadding(Instance *instance, int32_t before, int32_t after) { + if (instance->inListDepth) return; + if (before < instance->paddingBefore) before = instance->paddingBefore; + EsSpacerCreate(instance->active, ES_FLAGS_DEFAULT, nullptr, 0, before); + instance->paddingBefore = after; +} + +void CreateStyledTextDisplay(Instance *instance, EsStyle *style, uint64_t flags = ES_CELL_H_FILL) { + EsTextDisplay *display = EsTextDisplayCreate(instance->active, flags, style); + EsTextRun *runs = (EsTextRun *) EsHeapAllocate(sizeof(EsTextRun) * (instance->spans.Length() + 1), false); + + for (uintptr_t i = 0; i < instance->spans.Length(); i++) { + runs[i].offset = instance->spans[i].offset; + EsElementGetTextStyle(display, &runs[i].style); + if (instance->spans[i].link) { runs[i].style.decorations |= ES_TEXT_DECORATION_UNDERLINE; runs[i].style.color = COLOR_TEXT_LINK; } + if (instance->spans[i].em) runs[i].style.font.italic = true; + if (instance->spans[i].strong) runs[i].style.font.weight = 7; + if (instance->spans[i].monospaced) runs[i].style.font.family = ES_FONT_MONOSPACED; + runs[i].style.decorationsColor = runs[i].style.color; + } + + runs[instance->spans.Length()].offset = instance->text.Length(); + EsTextDisplaySetStyledContents(display, instance->text.array, runs, instance->spans.Length()); + EsHeapFree(runs); +} + +#ifdef DEBUG_PARSER_OUTPUT +void ParserOutputPrintIndentation(Instance *instance) { + for (int i = 0; i < instance->debugNestDepth; i++) { + EsPrint(" "); + } +} +#endif + +int ParserEnterBlock(MD_BLOCKTYPE type, void *detail, void *_instance) { + Instance *instance = (Instance *) _instance; +#ifdef DEBUG_PARSER_OUTPUT + ParserOutputPrintIndentation(instance); + EsPrint(">> Enter block %z\n", blockTypes[type]); + instance->debugNestDepth++; +#endif + (void) detail; + + if (instance->inListDepth && instance->text.Length()) { + CreateStyledTextDisplay(instance, &styleParagraph); + } + + instance->text.SetLength(0); + instance->spans.SetLength(0); + + Span span = {}; + instance->spans.Add(span); + + if (type == MD_BLOCK_UL) { + AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER); + instance->active = EsListDisplayCreate(instance->active, ES_CELL_H_FILL | ES_LIST_DISPLAY_BULLETED, ES_STYLE_LIST_DISPLAY_DEFAULT); + } else if (type == MD_BLOCK_OL) { + AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER); + EsListDisplay *display = EsListDisplayCreate(instance->active, ES_CELL_H_FILL | ES_LIST_DISPLAY_NUMBERED, ES_STYLE_LIST_DISPLAY_DEFAULT); + instance->active = display; + EsListDisplaySetCounterStart(display, ((MD_BLOCK_OL_DETAIL *) detail)->start - 1); + } else if (type == MD_BLOCK_QUOTE) { + AddPadding(instance, QUOTE_PADDING_BEFORE, QUOTE_PADDING_AFTER); + instance->active = EsPanelCreate(instance->active, ES_CELL_H_FILL, &styleQuote); + instance->inBlockQuote = true; + } else if (type == MD_BLOCK_LI) { + instance->active = EsPanelCreate(instance->active, ES_CELL_H_FILL, &styleList); + instance->inListDepth++; + } else if (type == MD_BLOCK_TABLE) { + AddPadding(instance, TABLE_PADDING_BEFORE, TABLE_PADDING_AFTER); + EsPanel *table = EsPanelCreate(instance->active, ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_CELL_H_SHRINK, &styleTable); + instance->active = table; + instance->tableColumnCount = 0; + } + + return 0; +} + +int ParserLeaveBlock(MD_BLOCKTYPE type, void *detail, void *_instance) { + Instance *instance = (Instance *) _instance; +#ifdef DEBUG_PARSER_OUTPUT + instance->debugNestDepth--; + ParserOutputPrintIndentation(instance); + EsPrint(">> Leave block %z\n", blockTypes[type]); +#endif + + if (type == MD_BLOCK_P) { + if (instance->text.Length()) { + if (type == MD_BLOCK_P) { + AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER); + } + + CreateStyledTextDisplay(instance, instance->inBlockQuote ? &styleQuoteParagraph : &styleParagraph); + } + } else if (type == MD_BLOCK_LI) { + if (instance->text.Length()) CreateStyledTextDisplay(instance, &styleParagraph); + instance->text.SetLength(0); + instance->spans.SetLength(0); + instance->active = EsElementGetLayoutParent(instance->active); + instance->inListDepth--; + } else if (type == MD_BLOCK_TD || type == MD_BLOCK_TH) { + if (type == MD_BLOCK_TH) instance->tableColumnCount++; + CreateStyledTextDisplay(instance, type == MD_BLOCK_TH ? &styleTH : &styleTD, ES_CELL_H_EXPAND | ES_CELL_V_EXPAND | ES_CELL_H_SHRINK | ES_CELL_V_SHRINK); + instance->text.SetLength(0); + instance->spans.SetLength(0); + } else if (type == MD_BLOCK_H) { + unsigned level = ((MD_BLOCK_H_DETAIL *) detail)->level; + + if (level == 1) AddPadding(instance, H1_PADDING_BEFORE, H1_PADDING_AFTER); + else if (level == 2) AddPadding(instance, H2_PADDING_BEFORE, H2_PADDING_AFTER); + else AddPadding(instance, H3_PADDING_BEFORE, H3_PADDING_AFTER); + + CreateStyledTextDisplay(instance, level == 1 ? &styleHeading1 : level == 2 ? &styleHeading2 : &styleHeading3); + + if (level <= 2) { + EsSpacerCreate(instance->active, ES_FLAGS_DEFAULT, nullptr, 0, HEADING_UNDERLINE_GAP); + EsSpacerCreate(instance->active, ES_CELL_H_FILL, &styleHeadingUnderline, 0, 0); + } + } else if (type == MD_BLOCK_CODE) { + MD_BLOCK_CODE_DETAIL *code = (MD_BLOCK_CODE_DETAIL *) detail; + AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER); + EsElement *wrapper = EsPanelCreate(instance->active, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL | ES_PANEL_H_SCROLL_AUTO, &styleCodeBlock); + EsTextDisplay *display = EsTextDisplayCreate(wrapper, ES_TEXT_DISPLAY_PREFORMATTED, &styleCode, instance->text.array, instance->text.Length()); + + if (0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("ini"))) { + EsTextDisplaySetupSyntaxHighlighting(display, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI); + } else if (0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("c")) + || 0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("cpp")) + || 0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("c++"))) { + EsTextDisplaySetupSyntaxHighlighting(display, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C); + } + } else if (type == MD_BLOCK_UL || type == MD_BLOCK_OL || type == MD_BLOCK_QUOTE) { + instance->active = EsElementGetLayoutParent(instance->active); + instance->inBlockQuote = false; + } else if (type == MD_BLOCK_HR) { + AddPadding(instance, HR_PADDING_BEFORE, HR_PADDING_AFTER); + EsSpacerCreate(instance->active, ES_CELL_H_FILL, &styleHorizontalRule, 0, 0); + } else if (type == MD_BLOCK_TABLE) { + EsPanel *panel = (EsPanel *) instance->active; + + EsPanelSetBands(panel, instance->tableColumnCount); + EsPanelTableSetChildCells(panel); + + EsPanelBand column = {}; + column.preferredSize = column.minimumSize = column.maximumSize = ES_PANEL_BAND_SIZE_DEFAULT; + column.pull = 1; // Shrink all columns with equal weight. + EsPanelSetBandsAll(panel, &column); + + instance->active = EsElementGetLayoutParent(instance->active); + } else { + // EsPrint("Unhandled block of type %z.\n", blockTypes[type]); + } + + return 0; +} + +int ParserEnterSpan(MD_SPANTYPE type, void *detail, void *_instance) { + Instance *instance = (Instance *) _instance; +#ifdef DEBUG_PARSER_OUTPUT + ParserOutputPrintIndentation(instance); + EsPrint(">> Enter span %z\n", spanTypes[type]); + instance->debugNestDepth++; +#endif + (void) detail; + + if (type == MD_SPAN_EM || type == MD_SPAN_STRONG || type == MD_SPAN_CODE || type == MD_SPAN_A) { + Span span = instance->spans.Last(); + if (type == MD_SPAN_EM) span.em = true; + if (type == MD_SPAN_STRONG) span.strong = true; + if (type == MD_SPAN_CODE) span.monospaced = true; + if (type == MD_SPAN_A) span.link = true; + span.offset = instance->text.Length(); + instance->spans.Add(span); + } + + return 0; +} + +int ParserLeaveSpan(MD_SPANTYPE type, void *detail, void *_instance) { + Instance *instance = (Instance *) _instance; +#ifdef DEBUG_PARSER_OUTPUT + instance->debugNestDepth--; + ParserOutputPrintIndentation(instance); + EsPrint(">> Leave span %z\n", spanTypes[type]); +#endif + (void) detail; + + if (type == MD_SPAN_EM || type == MD_SPAN_STRONG || type == MD_SPAN_CODE || type == MD_SPAN_A) { + Span span = instance->spans.Last(); + if (type == MD_SPAN_EM) span.em = false; + if (type == MD_SPAN_STRONG) span.strong = false; + if (type == MD_SPAN_CODE) span.monospaced = false; + if (type == MD_SPAN_A) span.link = false; + span.offset = instance->text.Length(); + instance->spans.Add(span); + } + + return 0; +} + +int ParserText(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *_instance) { + (void) type; + Instance *instance = (Instance *) _instance; +#ifdef DEBUG_PARSER_OUTPUT + ParserOutputPrintIndentation(instance); + EsPrint(">> Text %z, %x: %s\n", textTypes[type], text, size, text); +#endif + char *buffer = instance->text.InsertMany(instance->text.Length(), size); + EsMemoryCopy(buffer, text, size); + return 0; +} + +void ProcessApplicationMessage(EsMessage *message) { + if (message->type == ES_MSG_INSTANCE_CREATE) { + Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(MarkdownViewerTitle)); + EsInstanceSetClassViewer(instance, nullptr); + EsWindow *window = instance->window; + EsPanel *wrapper = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); + EsPanel *background = EsPanelCreate(wrapper, ES_CELL_FILL | ES_PANEL_V_SCROLL_AUTO, &styleBackground); + instance->root = EsPanelCreate(background, ES_CELL_H_SHRINK, &styleRoot); + EsWindowSetIcon(window, ES_ICON_TEXT_MARKDOWN); + } else if (message->type == ES_MSG_INSTANCE_OPEN) { + Instance *instance = message->instanceOpen.instance; + + if (message->instanceOpen.update) { + EsElementStartTransition(instance->root, ES_TRANSITION_ZOOM_IN); + } + + EsElementDestroyContents(instance->root); + + size_t fileSize; + char *file = (char *) EsFileStoreReadAll(message->instanceOpen.file, &fileSize); + + if (!file || !EsUTF8IsValid(file, fileSize)) { + EsInstanceOpenComplete(message, false); + return; + } + + MD_PARSER parser = {}; + parser.flags = MD_DIALECT_GITHUB | MD_FLAG_NOHTML; + parser.enter_block = ParserEnterBlock; + parser.leave_block = ParserLeaveBlock; + parser.enter_span = ParserEnterSpan; + parser.leave_span = ParserLeaveSpan; + parser.text = ParserText; + instance->active = instance->root; + int result = md_parse(file, fileSize, &parser, instance); + if (result) EsElementDestroyContents(instance->root); // An error occurred. + EsInstanceOpenComplete(message, result == 0); + EsHeapFree(file); + + EsElementRelayout(instance->root); + instance->spans.Free(); + instance->text.Free(); + } +} + +void _start() { + _init(); + + while (true) { + ProcessApplicationMessage(EsMessageReceive()); + } +} diff --git a/apps/markdown_viewer.ini b/apps/markdown_viewer.ini new file mode 100644 index 0000000..8c25397 --- /dev/null +++ b/apps/markdown_viewer.ini @@ -0,0 +1,11 @@ +[general] +name=Markdown Viewer +icon=icon_text_markdown +use_single_process=1 + +[build] +source=apps/markdown_viewer.cpp + +[@handler] +extension=md +action=open diff --git a/apps/posix_launcher.cpp b/apps/posix_launcher.cpp new file mode 100644 index 0000000..a1c1d32 --- /dev/null +++ b/apps/posix_launcher.cpp @@ -0,0 +1,311 @@ +#include +#include + +#define _GNU_SOURCE +#include +#include +#include +#include + +#define MSG_RECEIVED_OUTPUT ((EsMessageType) (ES_MSG_USER_START + 1)) + +EsInstance *instance; +EsHandle commandEvent; +char outputBuffer[262144]; +uintptr_t outputBufferPosition; +EsMutex mutex; +volatile bool runningCommand; +int stdinWritePipe; +char *command; +EsTextbox *textboxOutput, *textboxInput; + +const EsStyle styleMonospacedTextbox = { + .inherit = ES_STYLE_TEXTBOX_NO_BORDER, + + .metrics = { + .mask = ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_TEXT_SIZE, + .textSize = 12, + .fontFamily = ES_FONT_MONOSPACED, + }, +}; + +char *ParseArgument(char **position) { + char *start = *position; + + while (*start == ' ') { + start++; + } + + if (!(*start)) { + return nullptr; + } + + char *end = start; + + while ((*end != ' ' || (end != start && end[-1] == '\\')) && *end) { + end++; + } + + if (*end) { + *end = 0; + end++; + } + + *position = end; + return start; +} + +void WriteToOutputTextbox(const char *string, ptrdiff_t stringBytes) { + if (stringBytes == -1) { + stringBytes = EsCRTstrlen(string); + } + + bool done = false, postMessage = false; + + while (true) { + EsMutexAcquire(&mutex); + + if (outputBufferPosition + stringBytes <= sizeof(outputBuffer)) { + EsMemoryCopy(outputBuffer + outputBufferPosition, string, stringBytes); + postMessage = outputBufferPosition == 0; + outputBufferPosition += stringBytes; + done = true; + } + + EsMutexRelease(&mutex); + + if (!done) { + // The main thread is busy. Wait a little bit before trying again. + EsSleep(100); + } else { + break; + } + } + + if (postMessage) { + EsMessage m = {}; + m.type = MSG_RECEIVED_OUTPUT; + EsMessagePost(nullptr, &m); + } +} + +void RunCommandThread() { + char *argv[64]; + int argc = 0; + char executable[4096]; + int status; + int standardOutputPipe[2]; + int standardInputPipe[2]; + pid_t pid; + struct timespec startTime, endTime; + + char *envp[5] = { + (char *) "LANG=en_US.UTF-8", + (char *) "HOME=/", + (char *) "PATH=/Applications/POSIX/bin", + (char *) "TMPDIR=/Applications/POSIX/tmp", + nullptr + }; + + char *commandPosition = command; + + while (argc < 63) { + argv[argc] = ParseArgument(&commandPosition); + if (!argv[argc]) break; + argc++; + } + + if (!argc) { + goto done; + } + + argv[argc] = nullptr; + + if (0 == EsCRTstrcmp(argv[0], "run")) { + if (argc != 2) { + WriteToOutputTextbox("\nUsage: run \n", -1); + } else { + EsApplicationRunTemporary(instance, argv[1], EsCStringLength(argv[1])); + } + + WriteToOutputTextbox("\n----------------\n", -1); + goto done; + } else if (0 == EsCRTstrcmp(argv[0], "cd")) { + if (argc != 2) { + WriteToOutputTextbox("\nUsage: cd \n", -1); + } else { + chdir(argv[1]); + + WriteToOutputTextbox("\nNew working directory:\n", -1); + WriteToOutputTextbox(getcwd(nullptr, 0), -1); + WriteToOutputTextbox("\n", -1); + } + + WriteToOutputTextbox("\n----------------\n", -1); + goto done; + } + + if (argv[0][0] == '/') { + executable[EsStringFormat(executable, sizeof(executable) - 1, "%z", argv[0])] = 0; + } else { + executable[EsStringFormat(executable, sizeof(executable) - 1, "/Applications/POSIX/bin/%z", argv[0])] = 0; + } + + clock_gettime(CLOCK_MONOTONIC, &startTime); + + pipe(standardOutputPipe); + pipe(standardInputPipe); + + pid = vfork(); + + if (pid == 0) { + dup2(standardInputPipe[0], 0); + dup2(standardOutputPipe[1], 1); + dup2(standardOutputPipe[1], 2); + close(standardInputPipe[0]); + close(standardOutputPipe[1]); + execve(executable, argv, envp); + WriteToOutputTextbox("\nThe executable failed to load.\n", -1); + _exit(-1); + } else if (pid == -1) { + // TODO Report the error. + } + + close(standardInputPipe[0]); + close(standardOutputPipe[1]); + + EsMutexAcquire(&mutex); + stdinWritePipe = standardInputPipe[1]; + EsMutexRelease(&mutex); + + while (true) { + char buffer[1024]; + ssize_t bytesRead = read(standardOutputPipe[0], buffer, 1024); + + if (bytesRead <= 0) { + break; + } else { + WriteToOutputTextbox(buffer, bytesRead); + } + } + + EsMutexAcquire(&mutex); + stdinWritePipe = 0; + EsMutexRelease(&mutex); + + close(standardInputPipe[1]); + close(standardOutputPipe[0]); + + wait4(-1, &status, 0, NULL); + + clock_gettime(CLOCK_MONOTONIC, &endTime); + + { + double startTimeS = startTime.tv_sec + startTime.tv_nsec / 1000000000.0; + double endTimeS = endTime.tv_sec + endTime.tv_nsec / 1000000000.0; + char buffer[256]; + size_t bytes = EsStringFormat(buffer, sizeof(buffer), "\nProcess exited with status %d.\nExecution time: %Fs.\n", status >> 8, endTimeS - startTimeS); + WriteToOutputTextbox(buffer, bytes); + WriteToOutputTextbox("\n----------------\n", -1); + } + + done:; + EsHeapFree(command); + __sync_synchronize(); + runningCommand = false; +} + +int ProcessTextboxInputMessage(EsElement *, EsMessage *message) { + if (message->type == ES_MSG_KEY_DOWN) { + if (message->keyboard.scancode == ES_SCANCODE_ENTER + && !message->keyboard.modifiers + && EsTextboxGetLineLength(textboxInput)) { + char *data = EsTextboxGetContents(textboxInput); + + if (!runningCommand) { + runningCommand = true; + command = data; + + EsTextboxInsert(textboxOutput, "\n> ", -1, false); + EsTextboxInsert(textboxOutput, command, -1, false); + EsTextboxInsert(textboxOutput, "\n", -1, false); + EsTextboxEnsureCaretVisible(textboxOutput, false); + + EsEventSet(commandEvent); + } else { + EsTextboxInsert(textboxOutput, data, -1, false); + EsTextboxInsert(textboxOutput, "\n", -1, false); + EsMutexAcquire(&mutex); + + if (stdinWritePipe) { + write(stdinWritePipe, data, EsCStringLength(data)); + write(stdinWritePipe, "\n", 1); + } + + EsMutexRelease(&mutex); + EsHeapFree(data); + } + + EsTextboxClear(textboxInput, false); + return ES_HANDLED; + } + } + + return 0; +} + +void MessageLoopThread(EsGeneric) { + EsMessageMutexAcquire(); + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + EsAssert(!instance); + instance = EsInstanceCreate(message, "POSIX Launcher"); + EsWindow *window = instance->window; + EsWindowSetIcon(window, ES_ICON_UTILITIES_TERMINAL); + EsPanel *panel = EsPanelCreate(window, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND); + textboxOutput = EsTextboxCreate(panel, ES_TEXTBOX_MULTILINE | ES_CELL_FILL, &styleMonospacedTextbox); + EsSpacerCreate(panel, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL); + textboxInput = EsTextboxCreate(panel, ES_CELL_H_FILL, &styleMonospacedTextbox); + textboxInput->messageUser = ProcessTextboxInputMessage; + EsElementFocus(textboxInput); + } else if (message->type == MSG_RECEIVED_OUTPUT) { + EsMutexAcquire(&mutex); + + if (outputBufferPosition) { + // EsPrint("Inserting %d bytes...\n", outputBufferPosition); + EsTextboxMoveCaretRelative(textboxOutput, ES_TEXTBOX_MOVE_CARET_ALL); + EsTextboxInsert(textboxOutput, outputBuffer, outputBufferPosition, false); + EsTextboxEnsureCaretVisible(textboxOutput, false); + outputBufferPosition = 0; + } + + EsMutexRelease(&mutex); + } + } +} + +int main(int argc, char **argv) { + (void) argc; + (void) argv; + + commandEvent = EsEventCreate(true); + EsMessageMutexRelease(); + EsThreadCreate(MessageLoopThread, nullptr, 0); + +#if 0 + runningCommand = true; + command = (char *) EsHeapAllocate(128, true); + EsStringFormat(command, 128, "gcc es/util/build_core.c -g"); + EsEventSet(commandEvent); +#endif + + while (true) { + EsWaitSingle(commandEvent); + RunCommandThread(); + } + + return 0; +} diff --git a/apps/posix_launcher.ini b/apps/posix_launcher.ini new file mode 100644 index 0000000..eee7e16 --- /dev/null +++ b/apps/posix_launcher.ini @@ -0,0 +1,9 @@ +[general] +name=POSIX Launcher +icon=icon_utilities_terminal +permission_posix_subsystem=1 +permission_run_temporary_application=1 + +[build] +source=apps/posix_launcher.cpp +link_flags=-Lroot/Applications/POSIX/lib -lc bin/crtglue.o bin/crt1.o diff --git a/apps/system_monitor.cpp b/apps/system_monitor.cpp new file mode 100644 index 0000000..088ba86 --- /dev/null +++ b/apps/system_monitor.cpp @@ -0,0 +1,497 @@ +#define ES_INSTANCE_TYPE Instance +#include + +#include +#define IMPLEMENTATION +#include +#undef IMPLEMENTATION + +// TODO Single instance. +// TODO Sorting lists. +// TODO Processes: handle/thread count; IO statistics; more memory information. + +struct Instance : EsInstance { + EsPanel *switcher; + EsTextbox *textboxGeneralLog; + EsListView *listViewProcesses; + EsListView *listViewPCIDevices; + EsPanel *panelMemoryStatistics; + int index; + EsCommand commandTerminateProcess; + Array textDisplaysMemory; +}; + +#define REFRESH_INTERVAL (1000) + +#define DISPLAY_PROCESSES (1) +#define DISPLAY_GENERAL_LOG (3) +#define DISPLAY_PCI_DEVICES (6) +#define DISPLAY_MEMORY (12) + +EsListViewColumn listViewProcessesColumns[] = { + { EsLiteral("Name"), 0, 150 }, + { EsLiteral("PID"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, + { EsLiteral("Memory"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, + { EsLiteral("CPU"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, + { EsLiteral("Handles"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, +}; + +EsListViewColumn listViewContextSwitchesColumns[] = { + { EsLiteral("Time stamp (ms)"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, + { EsLiteral("CPU"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, + { EsLiteral("Process"), 0, 150 }, + { EsLiteral("Thread"), 0, 150 }, + { EsLiteral("Count"), 0, 150 }, +}; + +EsListViewColumn listViewPCIDevicesColumns[] = { + { EsLiteral("Driver"), 0, 200 }, + { EsLiteral("Device ID"), 0, 200 }, + { EsLiteral("Class"), 0, 250 }, + { EsLiteral("Subclass"), 0, 250 }, + { EsLiteral("ProgIF"), 0, 150 }, + { EsLiteral("Bus"), 0, 100 }, + { EsLiteral("Slot"), 0, 100 }, + { EsLiteral("Function"), 0, 100 }, + { EsLiteral("Interrupt pin"), 0, 100 }, + { EsLiteral("Interrupt line"), 0, 100 }, + { EsLiteral("BAR0"), 0, 250 }, + { EsLiteral("BAR1"), 0, 250 }, + { EsLiteral("BAR2"), 0, 250 }, + { EsLiteral("BAR3"), 0, 250 }, + { EsLiteral("BAR4"), 0, 250 }, + { EsLiteral("BAR5"), 0, 250 }, +}; + +const EsStyle styleMonospacedTextbox = { + .inherit = ES_STYLE_TEXTBOX_NO_BORDER, + + .metrics = { + .mask = ES_THEME_METRICS_FONT_FAMILY, + .fontFamily = ES_FONT_MONOSPACED, + }, +}; + +const EsStyle stylePanelMemoryStatistics = { + .inherit = ES_STYLE_PANEL_FILLED, + + .metrics = { + .mask = ES_THEME_METRICS_GAP_ALL, + .gapMajor = 5, + .gapMinor = 5, + }, +}; + +const EsStyle stylePanelMemoryCommands = { + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_ALL, + .insets = ES_RECT_4(0, 0, 0, 5), + .gapMajor = 5, + .gapMinor = 5, + }, +}; + +const char *pciClassCodeStrings[] = { + "Unknown", + "Mass storage controller", + "Network controller", + "Display controller", + "Multimedia controller", + "Memory controller", + "Bridge controller", + "Simple communication controller", + "Base system peripheral", + "Input device controller", + "Docking station", + "Processor", + "Serial bus controller", + "Wireless controller", + "Intelligent controller", + "Satellite communication controller", + "Encryption controller", + "Signal processing controller", +}; + +const char *pciSubclassCodeStrings1[] = { + "SCSI bus controller", + "IDE controller", + "Floppy disk controller", + "IPI bus controller", + "RAID controller", + "ATA controller", + "Serial ATA", + "Serial attached SCSI", + "Non-volatile memory controller", +}; + +const char *pciSubclassCodeStrings12[] = { + "FireWire (IEEE 1394) controller", + "ACCESS bus", + "SSA", + "USB controller", + "Fibre channel", + "SMBus", + "InfiniBand", + "IPMI interface", + "SERCOS interface (IEC 61491)", + "CANbus", +}; + +const char *pciProgIFStrings12_3[] = { + "UHCI", + "OHCI", + "EHCI", + "XHCI", +}; + +struct ProcessItem { + EsSnapshotProcessesItem data; + uintptr_t cpuUsage; +}; + +char generalLogBuffer[256 * 1024]; +char contextSwitchesBuffer[2 * 1024 * 1024]; +EsPCIDevice pciDevices[1024]; +Array processes; +int64_t selectedPID = -2; + +ProcessItem *FindProcessByPID(Array snapshot, int64_t pid) { + for (uintptr_t i = 0; i < snapshot.Length(); i++) { + if (pid == snapshot[i].data.pid) { + return &snapshot[i]; + } + } + + return nullptr; +} + +void UpdateProcesses(Instance *instance) { + Array previous = processes; + processes = {}; + + size_t bufferSize; + EsHandle handle = EsTakeSystemSnapshot(ES_SYSTEM_SNAPSHOT_PROCESSES, &bufferSize); + EsSnapshotProcesses *snapshot = (EsSnapshotProcesses *) EsHeapAllocate(bufferSize, false); + EsConstantBufferRead(handle, snapshot); + EsHandleClose(handle); + + for (uintptr_t i = 0; i < snapshot->count; i++) { + ProcessItem item = {}; + item.data = snapshot->processes[i]; + processes.Add(item); + + if (snapshot->processes[i].isKernel) { + ProcessItem item = {}; + item.data.cpuTimeSlices = snapshot->processes[i].idleTimeSlices; + item.data.pid = -1; + const char *idle = "CPU idle"; + item.data.nameBytes = EsCStringLength(idle); + EsMemoryCopy(item.data.name, idle, item.data.nameBytes); + processes.Add(item); + } + } + + EsHeapFree(snapshot); + + for (uintptr_t i = 0; i < previous.Length(); i++) { + if (!FindProcessByPID(processes, previous[i].data.pid)) { + EsListViewRemove(instance->listViewProcesses, 0, i, i); + previous.Delete(i--); + } + } + + for (uintptr_t i = 0; i < processes.Length(); i++) { + processes[i].cpuUsage = processes[i].data.cpuTimeSlices; + ProcessItem *item = FindProcessByPID(previous, processes[i].data.pid); + if (item) processes[i].cpuUsage -= item->data.cpuTimeSlices; + } + + int64_t totalCPUTimeSlices = 0; + + for (uintptr_t i = 0; i < processes.Length(); i++) { + totalCPUTimeSlices += processes[i].cpuUsage; + } + + int64_t percentageSum = 0; + + for (uintptr_t i = 0; i < processes.Length(); i++) { + processes[i].cpuUsage = processes[i].cpuUsage * 100 / totalCPUTimeSlices; + percentageSum += processes[i].cpuUsage; + } + + while (percentageSum < 100 && percentageSum) { + for (uintptr_t i = 0; i < processes.Length(); i++) { + if (processes[i].cpuUsage && percentageSum < 100) { + processes[i].cpuUsage++, percentageSum++; + } + } + } + + for (uintptr_t i = 0; i < processes.Length(); i++) { + if (!FindProcessByPID(previous, processes[i].data.pid)) { + EsListViewInsert(instance->listViewProcesses, 0, i, i); + } + } + + EsListViewInvalidateAll(instance->listViewProcesses); + EsCommandSetDisabled(&instance->commandTerminateProcess, selectedPID < 0 || !FindProcessByPID(processes, selectedPID)); + + EsTimerSet(REFRESH_INTERVAL, [] (EsGeneric context) { + Instance *instance = (Instance *) context.p; + + if (instance->index == DISPLAY_PROCESSES) { + UpdateProcesses(instance); + } + }, instance); + + previous.Free(); +} + +void UpdateDisplay(Instance *instance, int index) { + instance->index = index; + + if (index != DISPLAY_PROCESSES) { + EsCommandSetDisabled(&instance->commandTerminateProcess, true); + } + + if (index == DISPLAY_PROCESSES) { + UpdateProcesses(instance); + EsPanelSwitchTo(instance->switcher, instance->listViewProcesses, ES_TRANSITION_NONE); + EsElementFocus(instance->listViewProcesses); + } else if (index == DISPLAY_GENERAL_LOG) { + size_t bytes = EsSyscall(ES_SYSCALL_DEBUG_COMMAND, index, (uintptr_t) generalLogBuffer, sizeof(generalLogBuffer), 0); + EsTextboxSelectAll(instance->textboxGeneralLog); + EsTextboxInsert(instance->textboxGeneralLog, generalLogBuffer, bytes); + EsTextboxEnsureCaretVisible(instance->textboxGeneralLog, false); + EsPanelSwitchTo(instance->switcher, instance->textboxGeneralLog, ES_TRANSITION_NONE); + } else if (index == DISPLAY_PCI_DEVICES) { + size_t count = EsSyscall(ES_SYSCALL_DEBUG_COMMAND, index, (uintptr_t) pciDevices, sizeof(pciDevices) / sizeof(pciDevices[0]), 0); + EsListViewRemoveAll(instance->listViewPCIDevices, 0); + EsListViewInsert(instance->listViewPCIDevices, 0, 0, count - 1); + EsPanelSwitchTo(instance->switcher, instance->listViewPCIDevices, ES_TRANSITION_NONE); + } else if (index == DISPLAY_MEMORY) { + EsMemoryStatistics statistics = {}; + EsSyscall(ES_SYSCALL_DEBUG_COMMAND, index, (uintptr_t) &statistics, 0, 0); + + EsPanelSwitchTo(instance->switcher, instance->panelMemoryStatistics, ES_TRANSITION_NONE); + + if (!instance->textDisplaysMemory.Length()) { + EsPanel *panel = EsPanelCreate(instance->panelMemoryStatistics, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &stylePanelMemoryCommands); + EsButton *button; + + button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 1 MB")); + EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x100000); }); + button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 4 MB")); + EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x400000); }); + button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 16 MB")); + EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x1000000); }); + button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 64 MB")); + EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x4000000); }); + button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 256 MB")); + EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x10000000); }); + + EsSpacerCreate(instance->panelMemoryStatistics, ES_CELL_H_FILL); + } + + char buffer[256]; + size_t bytes; + uintptr_t index = 0; +#define ADD_MEMORY_STATISTIC_DISPLAY(label, ...) \ + bytes = EsStringFormat(buffer, sizeof(buffer), __VA_ARGS__); \ + if (instance->textDisplaysMemory.Length() == index) { \ + EsTextDisplayCreate(instance->panelMemoryStatistics, ES_CELL_H_PUSH | ES_CELL_H_RIGHT, 0, EsLiteral(label)); \ + instance->textDisplaysMemory.Add(EsTextDisplayCreate(instance->panelMemoryStatistics, ES_CELL_H_PUSH | ES_CELL_H_LEFT)); \ + } \ + EsTextDisplaySetContents(instance->textDisplaysMemory[index++], buffer, bytes) + + ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap allocation count:", "%d", statistics.fixedHeapAllocationCount); + ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap graphics surfaces:", "%D (%d B)", + statistics.totalSurfaceBytes, statistics.totalSurfaceBytes); + ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap normal size:", "%D (%d B)", + statistics.fixedHeapTotalSize - statistics.totalSurfaceBytes, statistics.fixedHeapTotalSize - statistics.totalSurfaceBytes); + ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap total size:", "%D (%d B)", + statistics.fixedHeapTotalSize, statistics.fixedHeapTotalSize); + ADD_MEMORY_STATISTIC_DISPLAY("Core heap allocation count:", "%d", statistics.coreHeapAllocationCount); + ADD_MEMORY_STATISTIC_DISPLAY("Core heap total size:", "%D (%d B)", statistics.coreHeapTotalSize, statistics.coreHeapTotalSize); + ADD_MEMORY_STATISTIC_DISPLAY("Cached boot FS nodes:", "%d", statistics.cachedNodes); + ADD_MEMORY_STATISTIC_DISPLAY("Cached boot FS directory entries:", "%d", statistics.cachedDirectoryEntries); + ADD_MEMORY_STATISTIC_DISPLAY("Maximum object cache size:", "%D (%d pages)", statistics.maximumObjectCachePages * ES_PAGE_SIZE, + statistics.maximumObjectCachePages); + ADD_MEMORY_STATISTIC_DISPLAY("Approximate object cache size:", "%D (%d pages)", statistics.approximateObjectCacheSize, + statistics.approximateObjectCacheSize / ES_PAGE_SIZE); + ADD_MEMORY_STATISTIC_DISPLAY("Commit (pageable):", "%D (%d pages)", statistics.commitPageable * ES_PAGE_SIZE, statistics.commitPageable); + ADD_MEMORY_STATISTIC_DISPLAY("Commit (fixed):", "%D (%d pages)", statistics.commitFixed * ES_PAGE_SIZE, statistics.commitFixed); + ADD_MEMORY_STATISTIC_DISPLAY("Commit (total):", "%D (%d pages)", (statistics.commitPageable + statistics.commitFixed) * ES_PAGE_SIZE, + statistics.commitPageable + statistics.commitFixed); + ADD_MEMORY_STATISTIC_DISPLAY("Commit limit:", "%D (%d pages)", statistics.commitLimit * ES_PAGE_SIZE, statistics.commitLimit); + ADD_MEMORY_STATISTIC_DISPLAY("Commit fixed limit:", "%D (%d pages)", statistics.commitFixedLimit * ES_PAGE_SIZE, statistics.commitFixedLimit); + ADD_MEMORY_STATISTIC_DISPLAY("Commit remaining:", "%D (%d pages)", statistics.commitRemaining * ES_PAGE_SIZE, statistics.commitRemaining); + + EsTimerSet(REFRESH_INTERVAL, [] (EsGeneric context) { + Instance *instance = (Instance *) context.p; + + if (instance->index == DISPLAY_MEMORY) { + UpdateDisplay(instance, DISPLAY_MEMORY); + } + }, instance); + } +} + +#define GET_CONTENT(...) EsBufferFormat(message->getContent.buffer, __VA_ARGS__) + +int ListViewProcessesCallback(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) { + int column = message->getContent.column, index = message->getContent.index.i; + ProcessItem *item = &processes[index]; + if (column == 0) GET_CONTENT("%s", item->data.nameBytes, item->data.name); + else if (column == 1) { if (item->data.pid == -1) GET_CONTENT("n/a"); else GET_CONTENT("%d", item->data.pid); } + else if (column == 2) GET_CONTENT("%D", item->data.memoryUsage); + else if (column == 3) GET_CONTENT("%d%%", item->cpuUsage); + else if (column == 4) GET_CONTENT("%d", item->data.handleCount); + else EsAssert(false); + } else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) { + message->selectItem.isSelected = processes[message->selectItem.index.u].data.pid == selectedPID; + } else if (message->type == ES_MSG_LIST_VIEW_SELECT && message->selectItem.isSelected) { + selectedPID = processes[message->selectItem.index.u].data.pid; + EsCommandSetDisabled(&element->instance->commandTerminateProcess, selectedPID < 0 || !FindProcessByPID(processes, selectedPID)); + } else { + return 0; + } + + return ES_HANDLED; +} + +int ListViewPCIDevicesCallback(EsElement *, EsMessage *message) { + if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) { + int column = message->getContent.column, index = message->getContent.index.i; + + EsPCIDevice *entry = pciDevices + index; + + if (column == 0) { + GET_CONTENT("%s", entry->driverNameBytes, entry->driverName); + } else if (column == 1) { + GET_CONTENT("%x", entry->deviceID); + } else if (column == 2) { + const char *string = entry->classCode < sizeof(pciClassCodeStrings) / sizeof(pciClassCodeStrings[0]) + ? pciClassCodeStrings[entry->classCode] : "Unknown"; + GET_CONTENT("%d - %z", entry->classCode, string); + } else if (column == 3) { + const char *string = + entry->classCode == 1 && entry->subclassCode < sizeof(pciSubclassCodeStrings1) / sizeof(const char *) + ? pciSubclassCodeStrings1 [entry->subclassCode] + : entry->classCode == 12 && entry->subclassCode < sizeof(pciSubclassCodeStrings12) / sizeof(const char *) + ? pciSubclassCodeStrings12[entry->subclassCode] : ""; + GET_CONTENT("%d%z%z", entry->subclassCode, *string ? " - " : "", string); + } else if (column == 4) { + const char *string = + entry->classCode == 12 && entry->subclassCode == 3 && entry->progIF / 0x10 < sizeof(pciProgIFStrings12_3) / sizeof(const char *) + ? pciProgIFStrings12_3[entry->progIF / 0x10] : ""; + GET_CONTENT("%d%z%z", entry->progIF, *string ? " - " : "", string); + } else if (column == 5) { + GET_CONTENT("%d", entry->bus); + } else if (column == 6) { + GET_CONTENT("%d", entry->slot); + } else if (column == 7) { + GET_CONTENT("%d", entry->function); + } else if (column == 8) { + GET_CONTENT("%d", entry->interruptPin); + } else if (column == 9) { + GET_CONTENT("%d", entry->interruptLine); + } else if (column == 10) { + GET_CONTENT("%x, %D", entry->baseAddresses[0], entry->baseAddressesSizes[0]); + } else if (column == 11) { + GET_CONTENT("%x, %D", entry->baseAddresses[1], entry->baseAddressesSizes[1]); + } else if (column == 12) { + GET_CONTENT("%x, %D", entry->baseAddresses[2], entry->baseAddressesSizes[2]); + } else if (column == 13) { + GET_CONTENT("%x, %D", entry->baseAddresses[3], entry->baseAddressesSizes[3]); + } else if (column == 14) { + GET_CONTENT("%x, %D", entry->baseAddresses[4], entry->baseAddressesSizes[4]); + } else if (column == 15) { + GET_CONTENT("%x, %D", entry->baseAddresses[5], entry->baseAddressesSizes[5]); + } else { + EsAssert(false); + } + + return ES_HANDLED; + } + + return 0; +} + +void AddTab(EsElement *toolbar, uintptr_t index, const char *label, bool asDefault = false) { + EsButton *button = EsButtonCreate(toolbar, ES_BUTTON_RADIOBOX, 0, label); + button->userData.u = index; + + EsButtonOnCommand(button, [] (Instance *instance, EsElement *element, EsCommand *) { + if (EsButtonGetCheck((EsButton *) element) == ES_CHECK_CHECKED) { + UpdateDisplay(instance, element->userData.u); + } + }); + + if (asDefault) EsButtonSetCheck(button, ES_CHECK_CHECKED); +} + +void AddListView(EsListView **pointer, EsElement *switcher, EsUICallbackFunction callback, EsListViewColumn *columns, size_t columnsSize, uint64_t additionalFlags) { + *pointer = EsListViewCreate(switcher, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | additionalFlags); + (*pointer)->messageUser = callback; + EsListViewSetColumns(*pointer, columns, columnsSize / sizeof(EsListViewColumn)); + EsListViewInsertGroup(*pointer, 0); +} + +void TerminateProcess(Instance *instance, EsElement *, EsCommand *) { + if (selectedPID == 0 /* Kernel */) { + // Terminating the kernel process is a meaningless action; the closest equivalent is shutting down. + EsSystemShowShutdownDialog(instance); + return; + } + + EsHandle handle = EsProcessOpen(selectedPID); + + if (handle) { + EsProcessTerminate(handle, 1); + } else { + EsRectangle bounds = EsElementGetWindowBounds(instance->listViewProcesses); + EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, (bounds.t + bounds.b) / 2, EsLiteral("Could not terminate process")); + } +} + +void ProcessApplicationMessage(EsMessage *message) { + if (message->type == ES_MSG_INSTANCE_CREATE) { + Instance *instance = EsInstanceCreate(message, "System Monitor"); + + EsCommandRegister(&instance->commandTerminateProcess, instance, TerminateProcess, 1, "Del", false); + + EsWindow *window = instance->window; + EsWindowSetIcon(window, ES_ICON_UTILITIES_SYSTEM_MONITOR); + EsPanel *switcher = EsPanelCreate(window, ES_CELL_FILL | ES_PANEL_SWITCHER, ES_STYLE_PANEL_WINDOW_DIVIDER); + instance->switcher = switcher; + + instance->textboxGeneralLog = EsTextboxCreate(switcher, ES_TEXTBOX_MULTILINE | ES_CELL_FILL, &styleMonospacedTextbox); + + AddListView(&instance->listViewProcesses, switcher, ListViewProcessesCallback, + listViewProcessesColumns, sizeof(listViewProcessesColumns), ES_LIST_VIEW_SINGLE_SELECT); + AddListView(&instance->listViewPCIDevices, switcher, ListViewPCIDevicesCallback, + listViewPCIDevicesColumns, sizeof(listViewPCIDevicesColumns), ES_FLAGS_DEFAULT); + + instance->panelMemoryStatistics = EsPanelCreate(switcher, + ES_CELL_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_PANEL_V_SCROLL_AUTO, &stylePanelMemoryStatistics); + EsPanelSetBands(instance->panelMemoryStatistics, 2 /* columns */); + + EsElement *toolbar = EsWindowGetToolbar(window); + AddTab(toolbar, DISPLAY_PROCESSES, "Processes", true); + AddTab(toolbar, DISPLAY_GENERAL_LOG, "System log"); + AddTab(toolbar, DISPLAY_PCI_DEVICES, "PCI devices"); + AddTab(toolbar, DISPLAY_MEMORY, "Memory"); + } else if (message->type == ES_MSG_INSTANCE_DESTROY) { + processes.Free(); + } +} + +void _start() { + _init(); + + while (true) { + ProcessApplicationMessage(EsMessageReceive()); + } +} diff --git a/apps/system_monitor.ini b/apps/system_monitor.ini new file mode 100644 index 0000000..78a0691 --- /dev/null +++ b/apps/system_monitor.ini @@ -0,0 +1,8 @@ +[general] +name=System Monitor +icon=icon_utilities_system_monitor +permission_manage_processes=1 +permission_shutdown=1 + +[build] +source=apps/system_monitor.cpp diff --git a/apps/test.cpp b/apps/test.cpp new file mode 100644 index 0000000..f82a20d --- /dev/null +++ b/apps/test.cpp @@ -0,0 +1,180 @@ +#include +#include +// #include + +EsTextbox *textbox; + +#if 0 + +char *master; +char **undo; + +void OffsetToLineAndByte(uintptr_t offset, int32_t *_line, int32_t *_byte) { + int32_t line = 0, byte = 0; + + for (uintptr_t i = 0; i < offset; i++) { + if (master[i] == '\n') { + line++; + byte = 0; + } else { + byte++; + } + } + + *_line = line; + *_byte = byte; +} + +void Compare() { + size_t bytes; + char *contents = EsTextboxGetContents(textbox, &bytes); + // EsPrint("\tContents: '%e'\n\tMaster: '%e'\n", bytes, contents, arrlenu(master), master); + EsAssert(bytes == arrlenu(master)); + EsAssert(0 == EsMemoryCompare(master, contents, bytes)); + EsHeapFree(contents); +} + +void FakeUndoItem(const void *, EsUndoManager *manager, EsMessage *message) { + if (message->type == ES_MSG_UNDO_INVOKE) { + EsUndoPush(manager, FakeUndoItem, nullptr, 0); + } +} + +void AddUndoItem() { + char *copy = nullptr; + arrsetlen(copy, arrlenu(master)); + EsMemoryCopy(copy, master, arrlenu(copy)); + arrput(undo, copy); +} + +void Complete() { + EsUndoPush(textbox->instance->undoManager, FakeUndoItem, nullptr, 0); + EsUndoEndGroup(textbox->instance->undoManager); +} + +void Insert(uintptr_t offset, const char *string, size_t stringBytes) { + if (!stringBytes) return; + AddUndoItem(); + // EsPrint("Insert '%e' at %d.\n", stringBytes, string, offset); + int32_t line, byte; + OffsetToLineAndByte(offset, &line, &byte); + EsTextboxSetSelection(textbox, line, byte, line, byte); + EsTextboxInsert(textbox, string, stringBytes); + arrinsn(master, offset, stringBytes); + EsMemoryCopy(master + offset, string, stringBytes); + Compare(); + Complete(); +} + +void Delete(uintptr_t from, uintptr_t to) { + if (from == to) return; + AddUndoItem(); + // EsPrint("Delete from %d to %d.\n", from, to); + int32_t fromLine, fromByte, toLine, toByte; + OffsetToLineAndByte(from, &fromLine, &fromByte); + OffsetToLineAndByte(to, &toLine, &toByte); + EsTextboxSetSelection(textbox, fromLine, fromByte, toLine, toByte); + EsTextboxInsert(textbox, 0, 0); + if (to > from) arrdeln(master, from, to - from); + else arrdeln(master, to, from - to); + Compare(); + Complete(); +} + +void Test() { + EsRandomSeed(10); + EsTextboxSetUndoManager(textbox, textbox->instance->undoManager); + + while (true) { + uint8_t action = EsRandomU8(); + + if (action < 0x70) { + size_t stringBytes = EsRandomU8() & 0x1F; + char string[0x20]; + + for (uintptr_t i = 0; i < stringBytes; i++) { + string[i] = EsRandomU8() < 0x40 ? '\n' : ((EsRandomU8() % 26) + 'a'); + } + + Insert(EsRandomU64() % (arrlenu(master) + 1), string, stringBytes); + } else if (action < 0xE0) { + if (arrlenu(master)) { + Delete(EsRandomU64() % arrlenu(master), EsRandomU64() % arrlenu(master)); + } + } else { + if (!EsUndoIsEmpty(textbox->instance->undoManager, false)) { + // EsPrint("Undo.\n"); + EsUndoInvokeGroup(textbox->instance->undoManager, false); + arrfree(master); + master = arrlast(undo); + arrpop(undo); + Compare(); + } + } + } +} + +#endif + +const EsStyle stylePanel = { + .inherit = ES_STYLE_PANEL_WINDOW_BACKGROUND, + + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR, + .insets = ES_RECT_1(20), + .gapMajor = 1, + }, +}; + +int TestCanvasMessage(EsElement *, EsMessage *message) { + if (message->type == ES_MSG_PAINT) { + size_t dataBytes; + const void *data = EsEmbeddedFileGet("test", &dataBytes); + if (data) EsDrawVectorFile(message->painter, EsPainterBoundsClient(message->painter), data, dataBytes); + } else if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = 256; + } else if (message->type == ES_MSG_GET_HEIGHT) { + message->measure.height = 256; + } + + return 0; +} + +void InitialiseInstance(EsInstance *instance) { + // EsPanel *panel = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); + // textbox = EsTextboxCreate(panel, ES_CELL_FILL | ES_TEXTBOX_ALLOW_TABS | ES_TEXTBOX_MULTILINE, ES_STYLE_TEXTBOX_NO_BORDER); + // Test(); + + EsPanel *panel = EsPanelCreate(instance->window, ES_CELL_FILL, &stylePanel); + EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Crash"), [] (EsInstance *, EsElement *, EsCommand *) { EsAssert(false); }); + EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Hang"), [] (EsInstance *, EsElement *, EsCommand *) { while (true); }); + EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Wait"), [] (EsInstance *, EsElement *, EsCommand *) { EsSleep(8000); }); + EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Wait, then crash"), [] (EsInstance *, EsElement *, EsCommand *) { EsSleep(8000); EsAssert(false); }); + + EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Announcement 1"), [] (EsInstance *, EsElement *element, EsCommand *) { + EsRectangle bounds = EsElementGetWindowBounds(element); + EsAnnouncementShow(element->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, (bounds.t + bounds.b) / 2, "Hello, world!", -1); + }); + + EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Announcement 2"), [] (EsInstance *, EsElement *element, EsCommand *) { + EsRectangle bounds = EsElementGetWindowBounds(element); + EsAnnouncementShow(element->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, (bounds.t + bounds.b) / 2, INTERFACE_STRING(DesktopApplicationStartupError)); + }); + + EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Push"); + EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Push"); + EsTextboxUseNumberOverlay(EsTextboxCreate(panel, ES_TEXTBOX_EDIT_BASED), true); + EsCustomElementCreate(panel)->messageUser = TestCanvasMessage; +} + +void _start() { + _init(); + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + InitialiseInstance(EsInstanceCreate(message, "Test App")); + } + } +} diff --git a/apps/test.ini b/apps/test.ini new file mode 100644 index 0000000..cb68016 --- /dev/null +++ b/apps/test.ini @@ -0,0 +1,8 @@ +[general] +name=Test +icon=icon_system_software_install +use_single_process=1 + +[build] +source=apps/test.cpp +link_flags=-Lroot/Applications/POSIX/lib -lc diff --git a/apps/text_editor.cpp b/apps/text_editor.cpp new file mode 100644 index 0000000..fa6dc69 --- /dev/null +++ b/apps/text_editor.cpp @@ -0,0 +1,334 @@ +#define ES_INSTANCE_TYPE Instance +#include + +#include + +// TODO Document save/load model, then merge into API. +// TODO Replace toolbar, then merge into API. +// TODO Merge Format menu into API. +// TODO Word wrap (textbox feature). + +// TODO Possible extension features: +// - Block selection +// - Folding +// - Tab settings and auto-indent +// - Macros +// - Status bar +// - Goto line +// - Find in files +// - Convert case +// - Sort lines +// - Trim trailing space +// - Indent/comment/join/split shortcuts + +const EsStyle styleFormatPopupColumn = { + .metrics = { + .mask = ES_THEME_METRICS_GAP_MAJOR, + .gapMajor = 5, + }, +}; + +const EsInstanceClassEditorSettings editorSettings = { + INTERFACE_STRING(TextEditorNewFileName), + INTERFACE_STRING(TextEditorNewDocument), + ES_ICON_TEXT, +}; + +struct Instance : EsInstance { + EsTextbox *textboxDocument, + *textboxSearch; + + EsElement *toolbarMain, *toolbarSearch; + + EsTextDisplay *displaySearch; + + EsButton *buttonFormat; + + EsCommand commandFindNext, + commandFindPrevious, + commandFind, + commandFormat; + + uint32_t syntaxHighlightingLanguage; +}; + +void Find(Instance *instance, bool backwards) { + EsWindowSwitchToolbar(instance->window, instance->toolbarSearch, ES_TRANSITION_SLIDE_UP); + + size_t needleBytes; + char *needle = EsTextboxGetContents(instance->textboxSearch, &needleBytes); + + int32_t line0, byte0, line1, byte1; + EsTextboxGetSelection(instance->textboxDocument, &line0, &byte0, &line1, &byte1); + + if (backwards) { + if (line1 < line0) { + line0 = line1; + byte0 = byte1; + } else if (line1 == line0 && byte1 < byte0) { + byte0 = byte1; + } + } else { + if (line1 > line0) { + line0 = line1; + byte0 = byte1; + } else if (line1 == line0 && byte1 > byte0) { + byte0 = byte1; + } + } + + bool found = EsTextboxFind(instance->textboxDocument, needle, needleBytes, &line0, &byte0, backwards ? ES_TEXTBOX_FIND_BACKWARDS : ES_FLAGS_DEFAULT); + + if (found) { + EsTextDisplaySetContents(instance->displaySearch, ""); + EsTextboxSetSelection(instance->textboxDocument, line0, byte0, line0, byte0 + needleBytes); + EsTextboxEnsureCaretVisible(instance->textboxDocument, true); + EsElementFocus(instance->textboxDocument); + } else if (!needleBytes) { + EsTextDisplaySetContents(instance->displaySearch, INTERFACE_STRING(CommonSearchPrompt2)); + EsElementFocus(instance->textboxSearch); + } else { + EsTextDisplaySetContents(instance->displaySearch, INTERFACE_STRING(CommonSearchNoMatches)); + EsElementFocus(instance->textboxSearch); + } + + EsHeapFree(needle); +} + +void SetLanguage(Instance *instance, uint32_t newLanguage) { + EsTextStyle textStyle = {}; + EsTextboxGetTextStyle(instance->textboxDocument, &textStyle); + textStyle.font.family = newLanguage ? ES_FONT_MONOSPACED : ES_FONT_SANS; + EsTextboxSetTextStyle(instance->textboxDocument, &textStyle); + + instance->syntaxHighlightingLanguage = newLanguage; + EsTextboxSetupSyntaxHighlighting(instance->textboxDocument, newLanguage); +} + +void FormatPopupCreate(Instance *instance) { + EsMenu *menu = EsMenuCreate(instance->buttonFormat, ES_FLAGS_DEFAULT); + EsPanel *panel = EsPanelCreate(menu, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_POPUP); + + { + EsPanel *column = EsPanelCreate(panel, ES_FLAGS_DEFAULT, &styleFormatPopupColumn); + EsTextDisplayCreate(column, ES_CELL_H_EXPAND, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(CommonFormatSize)); + EsListView *list = EsListViewCreate(column, ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED); + + const int presetSizes[] = { + 8, 9, 10, 11, 12, 13, + 14, 16, + 18, 24, 30, + 36, 48, 60, + 72, 96, 120, 144, + }; + + for (uintptr_t i = 0; i < sizeof(presetSizes) / sizeof(presetSizes[0]); i++) { + char buffer[64]; + EsListViewInsertFixedItem(list, buffer, EsStringFormat(buffer, sizeof(buffer), "%d pt", presetSizes[i]), presetSizes[i]); + } + + EsTextStyle textStyle = {}; + EsTextboxGetTextStyle(instance->textboxDocument, &textStyle); + EsListViewSelectFixedItem(list, textStyle.size); + + list->messageUser = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_LIST_VIEW_SELECT) { + Instance *instance = element->instance; + EsGeneric newSize; + + if (EsListViewGetSelectedFixedItem(((EsListView *) element), &newSize)) { + EsTextStyle textStyle = {}; + EsTextboxGetTextStyle(instance->textboxDocument, &textStyle); + textStyle.size = newSize.u; + EsTextboxSetTextStyle(instance->textboxDocument, &textStyle); + } + } + + return 0; + }; + } + + { + EsPanel *column = EsPanelCreate(panel, ES_FLAGS_DEFAULT, &styleFormatPopupColumn); + EsTextDisplayCreate(column, ES_CELL_H_EXPAND, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(CommonFormatLanguage)); + EsListView *list = EsListViewCreate(column, ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED); + EsListViewInsertFixedItem(list, INTERFACE_STRING(CommonFormatPlainText), 0); + EsListViewInsertFixedItem(list, "C/C++", -1, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C); + EsListViewInsertFixedItem(list, "Ini file", -1, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI); + EsListViewSelectFixedItem(list, instance->syntaxHighlightingLanguage); + + list->messageUser = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_LIST_VIEW_SELECT) { + Instance *instance = element->instance; + EsGeneric newLanguage; + + if (EsListViewGetSelectedFixedItem(((EsListView *) element), &newLanguage)) { + SetLanguage(instance, newLanguage.u); + } + } + + return 0; + }; + } + + EsMenuShow(menu); +} + +void ProcessApplicationMessage(EsMessage *message) { + if (message->type == ES_MSG_INSTANCE_CREATE) { + Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(TextEditorTitle)); + EsInstanceSetClassEditor(instance, &editorSettings); + + EsWindow *window = instance->window; + EsWindowSetIcon(window, ES_ICON_ACCESSORIES_TEXT_EDITOR); + EsButton *button; + + // Commands: + + uint32_t stableID = 1; + + EsCommandRegister(&instance->commandFindNext, instance, [] (Instance *instance, EsElement *, EsCommand *) { + Find(instance, false); + }, stableID++, "F3"); + + EsCommandRegister(&instance->commandFindPrevious, instance, [] (Instance *instance, EsElement *, EsCommand *) { + Find(instance, true); + }, stableID++, "Shift+F3"); + + EsCommandRegister(&instance->commandFind, instance, [] (Instance *instance, EsElement *, EsCommand *) { + EsWindowSwitchToolbar(instance->window, instance->toolbarSearch, ES_TRANSITION_ZOOM_OUT); + EsElementFocus(instance->textboxSearch); + }, stableID++, "Ctrl+F"); + + EsCommandRegister(&instance->commandFormat, instance, [] (Instance *instance, EsElement *, EsCommand *) { + FormatPopupCreate(instance); + }, stableID++, "Ctrl+Alt+T"); + + EsCommandSetDisabled(&instance->commandFindNext, false); + EsCommandSetDisabled(&instance->commandFindPrevious, false); + EsCommandSetDisabled(&instance->commandFind, false); + EsCommandSetDisabled(&instance->commandFormat, false); + + // Content: + + EsPanel *panel = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); + instance->textboxDocument = EsTextboxCreate(panel, + ES_CELL_FILL | ES_TEXTBOX_MULTILINE | ES_TEXTBOX_ALLOW_TABS | ES_TEXTBOX_MARGIN, + ES_STYLE_TEXTBOX_NO_BORDER); + instance->textboxDocument->cName = "document"; + EsTextboxSetUndoManager(instance->textboxDocument, instance->undoManager); + EsElementFocus(instance->textboxDocument); + + // Main toolbar: + + EsElement *toolbarMain = instance->toolbarMain = EsWindowGetToolbar(window, true); + + EsToolbarAddFileMenu(toolbarMain); + + button = EsButtonCreate(toolbarMain, ES_FLAGS_DEFAULT, {}, INTERFACE_STRING(CommonSearchOpen)); + button->accessKey = 'S'; + EsButtonSetIcon(button, ES_ICON_EDIT_FIND_SYMBOLIC); + + EsButtonOnCommand(button, [] (Instance *instance, EsElement *, EsCommand *) { + EsWindowSwitchToolbar(instance->window, instance->toolbarSearch, ES_TRANSITION_SLIDE_UP); + EsElementFocus(instance->textboxSearch); + }); + + button = EsButtonCreate(toolbarMain, ES_BUTTON_DROPDOWN, {}, INTERFACE_STRING(CommonFormatPopup)); + button->accessKey = 'M'; + EsButtonSetIcon(button, ES_ICON_FORMAT_TEXT_LARGER_SYMBOLIC); + EsCommandAddButton(&instance->commandFormat, button); + instance->buttonFormat = button; + + EsWindowSwitchToolbar(window, toolbarMain, ES_TRANSITION_NONE); + + // Search toolbar: + + EsElement *toolbarSearch = instance->toolbarSearch = EsWindowGetToolbar(window, true); + + button = EsButtonCreate(toolbarSearch, ES_FLAGS_DEFAULT, 0); + button->cName = "go back", button->accessKey = 'X'; + EsButtonSetIcon(button, ES_ICON_GO_FIRST_SYMBOLIC); + + EsButtonOnCommand(button, [] (Instance *instance, EsElement *, EsCommand *) { + EsWindowSwitchToolbar(instance->window, instance->toolbarMain, ES_TRANSITION_SLIDE_DOWN); + }); + + EsPanel *section = EsPanelCreate(toolbarSearch, ES_PANEL_HORIZONTAL); + EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(CommonSearchPrompt)); + + instance->textboxSearch = EsTextboxCreate(section, ES_FLAGS_DEFAULT, {}); + instance->textboxSearch->cName = "search textbox"; + instance->textboxSearch->accessKey = 'S'; + + instance->textboxSearch->messageUser = [] (EsElement *element, EsMessage *message) { + Instance *instance = element->instance; + + if (message->type == ES_MSG_KEY_DOWN && message->keyboard.scancode == ES_SCANCODE_ENTER) { + EsCommand *command = (message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? &instance->commandFindPrevious : &instance->commandFindNext; + command->callback(instance, element, command); + return ES_HANDLED; + } else if (message->type == ES_MSG_KEY_DOWN && message->keyboard.scancode == ES_SCANCODE_ESCAPE) { + EsWindowSwitchToolbar(instance->window, instance->toolbarMain, ES_TRANSITION_SLIDE_DOWN); + EsElementFocus(instance->textboxDocument); + return ES_HANDLED; + } else if (message->type == ES_MSG_FOCUSED_START) { + EsTextboxSelectAll(instance->textboxSearch); + } + + return 0; + }; + + instance->displaySearch = EsTextDisplayCreate(toolbarSearch, ES_CELL_H_FILL, {}, ""); + + button = EsButtonCreate(toolbarSearch, ES_FLAGS_DEFAULT, {}, INTERFACE_STRING(CommonSearchNext)); + button->accessKey = 'N'; + EsCommandAddButton(&instance->commandFindNext, button); + button = EsButtonCreate(toolbarSearch, ES_FLAGS_DEFAULT, {}, INTERFACE_STRING(CommonSearchPrevious)); + button->accessKey = 'P'; + EsCommandAddButton(&instance->commandFindPrevious, button); + } else if (message->type == ES_MSG_INSTANCE_OPEN) { + Instance *instance = message->instanceOpen.instance; + + size_t fileSize; + char *file = (char *) EsFileStoreReadAll(message->instanceOpen.file, &fileSize); + + if (!file) { + EsInstanceOpenComplete(message, false); + } else if (!EsUTF8IsValid(file, fileSize)) { + EsInstanceOpenComplete(message, false); + } else { + EsTextboxSelectAll(instance->textboxDocument); + EsTextboxInsert(instance->textboxDocument, file, fileSize); + EsTextboxSetSelection(instance->textboxDocument, 0, 0, 0, 0); + EsElementRelayout(instance->textboxDocument); + + if (EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".c"), true) + || EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".cpp"), true) + || EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".h"), true)) { + SetLanguage(instance, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C); + } else if (EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".ini"), true)) { + SetLanguage(instance, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI); + } else { + SetLanguage(instance, 0); + } + + EsInstanceOpenComplete(message, true); + } + } else if (message->type == ES_MSG_INSTANCE_SAVE) { + Instance *instance = message->instanceSave.instance; + size_t byteCount; + char *contents = EsTextboxGetContents(instance->textboxDocument, &byteCount); + EsFileStoreWriteAll(message->instanceSave.file, contents, byteCount); + EsHeapFree(contents); + EsInstanceSaveComplete(message, true); + } +} + +void _start() { + _init(); + + while (true) { + ProcessApplicationMessage(EsMessageReceive()); + } +} diff --git a/apps/text_editor.ini b/apps/text_editor.ini new file mode 100644 index 0000000..45c2025 --- /dev/null +++ b/apps/text_editor.ini @@ -0,0 +1,27 @@ +[general] +name=Text Editor +icon=icon_accessories_text_editor +use_single_process=1 + +[build] +source=apps/text_editor.cpp + +[@handler] +extension=txt +action=open + +[@handler] +extension=cpp +action=open + +[@handler] +extension=h +action=open + +[@handler] +extension=c +action=open + +[@handler] +extension=ini +action=open diff --git a/boot/x86/esfs-stage1.s b/boot/x86/esfs-stage1.s new file mode 100644 index 0000000..3a5ed57 --- /dev/null +++ b/boot/x86/esfs-stage1.s @@ -0,0 +1,178 @@ +[bits 16] +[org 0x7C00] + +%define superblock 0x8000 +%define temporary_load_buffer 0x9000 +%define block_group_descriptor_table 0x10000 +%define directory_load_location 0x1000 +%define stage2_load_location 0x1000 +%define inode_table_load_buffer 0x20000 +%define magic_breakpoint xchg bx,bx +;%define magic_breakpoint + +start: + ; Setup segment registers + cli + mov ax,0 + mov ds,ax + mov es,ax + mov fs,ax + mov gs,ax + mov ss,ax + mov sp,0x7C00 + jmp 0x0:.continue + .continue: + sti + + ; Save the information passed from the MBR + mov [drive_number],dl + mov [partition_entry],si + mov [use_emu_read],dh + + ; Get drive parameters. + or dh,dh + jz .skip_params + mov ah,0x08 + xor di,di + int 0x13 + mov si,error_cannot_read_disk + jc error + and cx,63 + mov [max_sectors],cx + inc dh + shr dx,8 + mov [max_heads],dx + .skip_params: + + ; Print a startup message + mov si,startup_message + .loop: + lodsb + or al,al + jz .end + mov ah,0xE + int 0x10 + jmp .loop + .end: + + ; Load the second stage + mov cx,15 + mov eax,1 + mov edi,stage2_load_location + call load_sectors + + mov dl,[drive_number] + mov si,[partition_entry] + mov dh,[use_emu_read] + mov bx,[max_sectors] + mov cx,[max_heads] + jmp 0:stage2_load_location + +load_sectors: + ; Load CX sectors from sector EAX to buffer [EDI]. Returns end of buffer in EDI. + pusha + push edi + + ; Add the partition offset to EAX + mov bx,[partition_entry] + mov ebx,[bx + 8] + add eax,ebx + + ; Load 1 sector + mov [read_structure.lba],eax + mov ah,0x42 + mov dl,[drive_number] + mov si,read_structure + cmp byte [use_emu_read],1 + je .use_emu + int 0x13 + jmp .done_read + .use_emu: + call load_sector_emu + .done_read: + + ; Check for error + mov si,error_cannot_read_disk + jc error + + ; Copy the data to its destination + pop edi + mov cx,0x200 + mov eax,edi + shr eax,4 + and eax,0xF000 + mov es,ax + mov si,temporary_load_buffer + rep movsb + + ; Go to the next sector + popa + add edi,0x200 + inc eax + loop load_sectors + ret + +load_sector_emu: + mov di,[read_structure.lba] + xor ax,ax + mov es,ax + mov bx,0x9000 + + ; Calculate cylinder and head. + mov ax,di + xor dx,dx + div word [max_sectors] + xor dx,dx + div word [max_heads] + push dx ; remainder - head + mov ch,al ; quotient - cylinder + shl ah,6 + mov cl,ah + + ; Calculate sector. + mov ax,di + xor dx,dx + div word [max_sectors] + inc dx + or cl,dl + + ; Load the sector. + pop dx + mov dh,dl + mov dl,[drive_number] + mov ax,0x0201 + int 0x13 + + ret + +error: + ; Print an error message + lodsb + or al,al + jz .break + mov ah,0xE + int 0x10 + jmp error + + ; Break indefinitely + .break: + cli + hlt + +startup_message: db "Booting operating system...",10,13,0 + +drive_number: db 0 +use_emu_read: db 0 +partition_entry: dw 0 +max_sectors: dw 0 +max_heads: dw 0 + +read_structure: ; Data for the extended read calls + dw 0x10 + dw 1 + dd temporary_load_buffer + .lba: dq 0 + +error_cannot_read_disk: db "Error: The disk could not be read. (S1)",0 + +times (0x200 - ($-$$)) nop diff --git a/boot/x86/esfs-stage2.s b/boot/x86/esfs-stage2.s new file mode 100644 index 0000000..6bd0d6d --- /dev/null +++ b/boot/x86/esfs-stage2.s @@ -0,0 +1,134 @@ +%macro FilesystemInitialise 0 +%define superblock 0x8000 +%define kernel_file_entry 0x8800 +%endmacro + +%macro FilesystemGetKernelSize 0 +load_kernel: + ; Load the superblock. + mov eax,16 + mov edi,superblock + mov cx,1 + call load_sectors + mov ax,superblock / 16 + mov fs,ax + + ; Check the signature. + mov eax,[fs:0] + cmp eax,0x73734521 + mov si,ErrorBadFilesystem + jne error + + ; Check the read version. + mov ax,[fs:48] + cmp ax,10 + mov si,ErrorBadFilesystem + jg error + + ; Save the OS installation identifier. + mov eax,[fs:152] + mov [os_installation_identifier + 0],eax + mov eax,[fs:156] + mov [os_installation_identifier + 4],eax + mov eax,[fs:160] + mov [os_installation_identifier + 8],eax + mov eax,[fs:164] + mov [os_installation_identifier + 12],eax + + ; Load the kernel's file entry. + mov eax,[fs:184] ; Get the block containing the kernel. + mul dword [fs:64] ; Multiply by block size. + shr eax,9 ; Divide by sector size. + mov cx,2 ; 2 sectors - 1024 bytes. + mov edi,[fs:192] ; The offset into the block. + shr edi,9 ; Divide by sector size. + add eax,edi ; Add to the first sector. + mov edi,kernel_file_entry + call load_sectors + + ; Find the data stream. +FindDataStreamLoop: + mov bx,[KernelDataStreamPosition] + mov eax,[fs:bx] + cmp ax,1 + je SaveKernelSize + shr eax,16 + add [KernelDataStreamPosition],ax + cmp bx,0xC00 + mov si,ErrorUnexpectedFileProblem + jge error + jmp FindDataStreamLoop + + ; Save the size of the kernel. +SaveKernelSize: + mov eax,[fs:0x838] + mov [kernel_size],eax + mov si,error_kernel_too_large + cmp eax,0x300000 + jg error +%endmacro + +%macro FilesystemLoadKernelIntoBuffer 0 + ; Check that the kernel is stored as DATA_INDIRECT file. +CheckForDataIndirect: + mov ax,superblock / 16 + mov fs,ax + mov bx,[KernelDataStreamPosition] + mov ax,[fs:bx+4] + mov si,ErrorUnexpectedFileProblem + cmp al,2 + jne error + + ; Load each extent from the file. +LoadEachExtent: + mov edi,[kernel_buffer] + mov si,[fs:bx+6] + add bx,32 + + ; TODO More than 1 extent. (We don't do the offset correctly). + push si + cmp si,1 + mov si,ErrorUnexpectedFileProblem + jne error + pop si + + ; Load the blocks. + .ExtentLoop: + + ; TODO Other extents than offset = 1, count = 2. + mov al,[fs:bx] + cmp al,0x08 + push si + mov si,ErrorUnexpectedFileProblem + jne error + pop si + + mov eax,[fs:64] + shr eax,9 ; EAX = Sectors per block. + xor ecx,ecx + mov cx,[fs:bx+2] + xchg ch,cl + mul cx + mov cx,ax ; CX = Count. + + xor eax,eax + mov al,[fs:bx+1] + mul dword [fs:64] + shr eax,9 ; EAX = Block. + + xchg bx,bx + + call load_sectors + add bx,4 + + ; Go to the next extent. + sub si,1 + cmp si,0 + jne .ExtentLoop +%endmacro + +%macro FilesystemSpecificCode 0 +KernelDataStreamPosition: dw 0x850 +ErrorBadFilesystem: db "Invalid boot EsFS volume.",0 +ErrorUnexpectedFileProblem: db "The kernel file could not be loaded.",0 +%endmacro diff --git a/boot/x86/loader.s b/boot/x86/loader.s new file mode 100644 index 0000000..ac408b7 --- /dev/null +++ b/boot/x86/loader.s @@ -0,0 +1,913 @@ +[bits 16] +[org 0x1000] + +; This is missing any fileSystem specific macros. +%define vesa_info 0x7000 +%define os_installation_identifier 0x7FF0 +%define temporary_load_buffer 0x9000 +%define page_directory 0x40000 +%define page_directory_length 0x20000 +%define memory_map 0x60000 +%define indirect_block_buffer 0x64000 +; %define magic_breakpoint xchg bx,bx +%define magic_breakpoint + +start: + mov esp,0x7C00 + + ; Save information passed from stage 1 + mov [drive_number],dl + mov [partition_entry],si + mov [use_emu_read],dh + mov [max_sectors],bx + mov [max_heads],cx + + ; @FilesystemSpecific + FilesystemInitialise + +check_pci: + ; Check the computer has PCI + mov ax,0xB101 + xor edi,edi + int 0x1A + mov si,error_no_pci + jc error + or ah,ah + jnz error + +check_cpuid: + ; Check the CPU has CPUID + mov dword [24],.no_cpuid + mov eax,0 + cpuid + jmp .has_cpuid + .no_cpuid: + mov si,error_no_cpuid + jmp error + .has_cpuid: + +check_msr: + ; Check the CPU has MSRs + mov dword [24],.no_msr + mov ecx,0xC0000080 + rdmsr + jmp .has_msr + .no_msr: + mov si,error_no_msr + jmp error + .has_msr: + +enable_a20: + ; Enable the A20 line, if necessary + cli + call check_a20 + jc .a20_enabled + mov ax,0x2401 + int 0x15 + call check_a20 + jc .a20_enabled + mov si,error_cannot_enable_a20_line + jmp error + .a20_enabled: + sti + +identity_paging: + ; Map the first 4MB to itself for the bootloader to do work in protected mode + mov eax,page_directory / 16 + mov es,ax + + ; Clear the page directory + xor eax,eax + mov ecx,0x400 + xor di,di + rep stosd + + ; Recursive map the directory + mov dword [es:0x3FF * 4],page_directory | 3 + + ; Put the first table in the directory + mov dword [es:0],(page_directory + 0x1000) | 3 + + ; Fill the table + mov edi,0x1000 + mov cx,0x400 + mov eax,3 + .loop: + mov [es:edi],eax + add edi,4 + add eax,0x1000 + loop .loop + + ; Set the pointer to the page directory + mov eax,page_directory + mov cr3,eax + +load_gdt: + ; Load the GDT + lgdt [gdt_data.gdt] + +inform_bios_mixed_mode: + mov eax,0xEC00 + mov ebx,3 ; may switch between legacy and long mode + int 0x15 + +load_memory_map: + ; Load the memory map + xor ebx,ebx + + ; Set FS to access the memory map + mov ax,0 + mov es,ax + mov ax,memory_map / 16 + mov fs,ax + + ; Loop through each memory map entry + .loop: + mov di,.entry + mov edx,0x534D4150 + mov ecx,24 + mov eax,0xE820 + mov byte [.acpi],1 + int 0x15 + jc .finished + + ; Check the BIOS call worked + cmp eax,0x534D4150 + jne .fail + +; pusha +; mov di,.entry +; call .print_bytes +; popa + + ; Check if this is usable memory + cmp dword [.type],1 + jne .try_next + cmp dword [.size],0 + je .try_next + cmp dword [.acpi],0 + je .try_next + + ; Check that the region is big enough + mov eax,[.size] + and eax,~0x3FFF + or eax,eax + jz .try_next + + ; Check that the base is above 1MB + cmp dword [.base + 4],0 + jne .base_good + cmp dword [.base],0x100000 + jl .try_next + .base_good: + + ; Align the base to the nearest page + mov eax,[.base] + and eax,0xFFF + or eax,eax + jz .base_aligned + mov eax,[.base] + and eax,~0xFFF + add eax,0x1000 + mov [.base],eax + sub dword [.size],0x1000 + sbb dword [.size + 4],0 + .base_aligned: + + ; Align the size to the nearest page + mov eax,[.size] + and eax,~0xFFF + mov [.size],eax + + ; Convert the size from bytes to 4KB pages + mov eax,[.size] + shr eax,12 + push ebx + mov ebx,[.size + 4] + shl ebx,20 + add eax,ebx + pop ebx + mov [.size],eax + mov dword [.size + 4],0 + + ; Store the entry + push ebx + mov ebx,[.pointer] + mov eax,[.base] + mov [fs:bx],eax + mov eax,[.base + 4] + mov [fs:bx + 4],eax + mov eax,[.size] + mov [fs:bx + 8],eax + add [.total_memory],eax + mov eax,[.size + 4] + adc [.total_memory + 4],eax + mov [fs:bx + 12],eax + add dword [.pointer],16 + pop ebx + + ; Continue to the next entry + .try_next: + or ebx,ebx + jnz .loop + + ; Make sure that there were enough entries + .finished: + mov eax,[.pointer] + shr eax,4 + or eax,eax + jz .fail + + ; Clear the base value for the entry after last + mov ebx,[.pointer] + mov dword [fs:bx],0 + mov dword [fs:bx + 4],0 + + ; Store the total memory + mov eax,[.total_memory] + mov dword [fs:bx + 8],eax + mov eax,[.total_memory + 4] + mov dword [fs:bx + 12],eax + + ; Load the kernel! + jmp load_kernel + + ; Display an error message if we could not load the memory map + .fail: + mov si,error_could_not_get_memory_map + jmp error + + .pointer: dd 0 + .entry: + .base: dq 0 + .size: dq 0 + .type: dd 0 + .acpi: dd 0 + .total_memory: dq 0 + + ; @FilesystemSpecific + FilesystemGetKernelSize + +allocate_kernel_buffer: + ; Switch to protected mode + push ds + push es + push ss + cli + mov eax,cr0 + or eax,0x80000001 + mov cr0,eax + jmp 0x8:.pmode + + ; Set the data segment registers + [bits 32] + .pmode: + mov ax,0x10 + mov ds,ax + mov es,ax + mov ss,ax + + ; Work out the size of memory we'll allocate + mov ecx,[kernel_size] + shr ecx,12 + inc ecx + mov edx,ecx + shl edx,12 + + ; For every memory region + xor ebx,ebx + .memory_region_loop: + + ; Is this the region starting at 1MB? + mov eax,[ebx + memory_map + 4] + or eax,eax + jnz .try_next_memory_region + mov eax,[ebx + memory_map] + cmp eax,0x100000 + jne .try_next_memory_region + + ; Check the region has enough pages remaining + mov eax,[ebx + memory_map + 8] + cmp eax,ecx + jl .try_next_memory_region + + ; Remove ECX pages from the region + mov eax,[ebx + memory_map + 0] + mov [kernel_buffer],eax + add eax,edx + mov [ebx + memory_map + 0],eax + sub dword [ebx + memory_map + 8],ecx + sbb dword [ebx + memory_map + 12],0 + + jmp .found_buffer + + ; Go to the next memory region + .try_next_memory_region: + add ebx,16 + mov eax,[load_memory_map.pointer] + cmp ebx,eax + jne .memory_region_loop + mov si,error_no_memory + jmp error_32 + + .found_buffer: + ; Switch to 16-bit mode + mov eax,cr0 + and eax,0x7FFFFFFF + mov cr0,eax + jmp 0x18:.rmode + + ; Switch to real mode + [bits 16] + .rmode: + mov eax,cr0 + and eax,0x7FFFFFFE + mov cr0,eax + jmp 0x0:.finish + + ; Go to error + .finish: + pop ss + pop es + pop ds + + ; @FilesystemSpecific + FilesystemLoadKernelIntoBuffer + +enable_video_mode: + call vbe_init + jmp enable_video_mode_done +%include "boot/x86/vbe.s" +enable_video_mode_done: + +setup_elf: + ; Switch to protected mode + cli + mov eax,cr0 + or eax,0x80000001 + mov cr0,eax + jmp 0x8:.pmode + + ; Set the data segment registers + [bits 32] + .pmode: + mov ax,0x10 + mov ds,ax + mov es,ax + mov ss,ax + + ; Check the ELF data is correct + mov ebx,[kernel_buffer] + mov esi,error_bad_kernel + cmp dword [ebx + 0],0x464C457F + jne error_32 + cmp byte [ebx + 4],2 + je setup_elf_64 + jne error_32 + +setup_elf_64: + ; Check that the processor is 64-bit + mov ecx,0x80000001 + cpuid + mov esi,error_no_long_mode + test eax,0x20000000 + jnz error_32 + + ; Disable paging + mov eax,cr0 + and eax,0x7FFFFFFF + mov cr0,eax + + ; Identity map the first 4MB + mov dword [page_table_allocation_location],0x5000 + mov ecx,page_directory_length + xor eax,eax + mov edi,page_directory + rep stosb + mov dword [page_directory + 0x1000 - 16],page_directory | 3 + mov dword [page_directory],(page_directory + 0x1000) | 7 + mov dword [page_directory + 0x1000],(page_directory + 0x2000) | 7 + mov dword [page_directory + 0x2000],(page_directory + 0x3000) | 7 + mov dword [page_directory + 0x2000 + 8],(page_directory + 0x4000) | 7 + mov edi,page_directory + 0x3000 + mov eax,0x000003 + mov ebx,0x200003 + mov ecx,0x400 + .identity_loop: + mov [edi],eax + add edi,8 + add eax,0x1000 + loop .identity_loop + mov eax,page_directory + mov cr3,eax + + ; Enable long mode + mov eax,cr4 + or eax,32 + mov cr4,eax + mov ecx,0xC0000080 + rdmsr + or eax,256 + wrmsr + mov eax,cr0 + or eax,0x80000000 + mov cr0,eax + + ; Go to 64-bit mode + jmp 0x48:.start_64_bit_mode +[bits 64] + .start_64_bit_mode: + + mov rax,0x50 + mov ds,rax + mov es,rax + mov ss,rax + + ; Check the ELF data is correct + mov rbx,[kernel_buffer] + mov rsi,error_bad_kernel + cmp byte [rbx + 5],1 + jne error_64 + cmp byte [rbx + 7],0 + jne error_64 + cmp byte [rbx + 16],2 + jne error_64 + cmp byte [rbx + 18],0x3E + jne error_64 + + ; Find the program headers + ; RAX = ELF header, RBX = program headers + mov rax,rbx + mov rbx,[rax + 32] + add rbx,rax + + ; ECX = entries, EDX = size of entry + movzx rcx,word [rax + 56] + movzx rdx,word [rax + 54] + + ; Loop through each program header + .loop_program_headers: + push rax + push rcx + push rdx + push rbx + + ; Only deal with load segments + mov eax,[rbx] + cmp eax,1 + jne .next_entry + + ; Work out how many physical pages we need to allocate + mov rcx,[rbx + 40] + shr rcx,12 + inc rcx + + ; Get the starting virtual address + mov rax,[rbx + 16] + shl rax,16 + shr rax,16 + mov [.target_page],rax + + ; For every frame in the segment + .frame_loop: + xor rbx,rbx + + ; For every memory region + .memory_region_loop: + + ; Check the region has enough pages remaining + mov rax,[rbx + memory_map + 8] + or rax,rax + jz .try_next_memory_region + + ; Remove one page from the region + mov rax,[rbx + memory_map + 0] + mov [.physical_page],rax + add rax,0x1000 + mov [rbx + memory_map + 0],rax + sub qword [rbx + memory_map + 8],1 + + jmp .found_physical_page + + ; Go to the next memory region + .try_next_memory_region: + add rbx,16 + mov eax,[load_memory_map.pointer] + cmp ebx,eax + jne .memory_region_loop + mov si,error_no_memory + jmp error_64 + + ; Map the page into virtual memory + .found_physical_page: + + ; Make sure we have a PDP + mov rax,[.target_page] + shr rax,39 + mov r8,0xFFFFFF7FBFDFE000 + mov rbx,[r8 + rax * 8] + cmp rbx,0 + jne .has_pdp + mov rbx,[page_table_allocation_location] + add rbx,page_directory + or rbx,7 + mov [r8 + rax * 8],rbx + add qword [page_table_allocation_location],0x1000 + mov rax,cr3 + mov cr3,rax + .has_pdp: + + ; Make sure we have a PD + mov rax,[.target_page] + shr rax,30 + mov r8,0xFFFFFF7FBFC00000 + mov rbx,[r8 + rax * 8] + cmp rbx,0 + jne .has_pd + mov rbx,[page_table_allocation_location] + add rbx,page_directory + or rbx,7 + mov [r8 + rax * 8],rbx + add qword [page_table_allocation_location],0x1000 + mov rax,cr3 + mov cr3,rax + .has_pd: + + ; Make sure we have a PT + mov rax,[.target_page] + shr rax,21 + mov r8,0xFFFFFF7F80000000 + mov rbx,[r8 + rax * 8] + cmp rbx,0 + jne .has_pt + mov rbx,[page_table_allocation_location] + add rbx,page_directory + or rbx,7 + mov [r8 + rax * 8],rbx + add qword [page_table_allocation_location],0x1000 + mov rax,cr3 + mov cr3,rax + .has_pt: + + ; Map the page! + mov rax,[.target_page] + shr rax,12 + mov rbx,[.physical_page] + or rbx,0x103 + shl rax,3 + xor r8,r8 + mov r8,0xFFFFFF00 + shl r8,32 + add rax,r8 + mov [rax],rbx + mov rbx,[.target_page] + invlpg [rbx] + + ; Go to the next frame + add qword [.target_page],0x1000 + dec rcx + or rcx,rcx + jnz .frame_loop + + ; Restore the pointer to the segment + pop rbx + push rbx + + ; Clear the memory + mov rcx,[rbx + 40] + xor rax,rax + mov rdi,[rbx + 16] + rep stosb + + ; Copy the memory + mov rcx,[rbx + 32] + mov rsi,[rbx + 8] + add rsi,[kernel_buffer] + mov rdi,[ebx + 16] + rep movsb + + ; Go to the next entry + .next_entry: + pop rbx + pop rdx + pop rcx + pop rax + + add rbx,rdx + dec rcx + or rcx,rcx + jnz .loop_program_headers + + jmp run_kernel64 + + .target_page: dq 0 + .physical_page: dq 0 + +run_kernel64: + ; Get the start address of the kernel + mov rbx,[kernel_buffer] + mov rcx,[rbx + 24] + + ; Let the kernel use the memory that was used to store the executable + xor eax,eax + mov ebx,[load_memory_map.pointer] + mov [memory_map + ebx + 4],eax + mov [memory_map + ebx + 12],eax + mov [memory_map + ebx + 16],eax + mov [memory_map + ebx + 20],eax + mov eax,[memory_map + ebx + 8] + mov [memory_map + ebx + 24],eax + mov eax,[memory_map + ebx + 12] + mov [memory_map + ebx + 28],eax + mov eax,[kernel_buffer] + mov [memory_map + ebx],eax + mov eax,[kernel_size] + shr eax,12 + mov [memory_map + ebx + 8],eax + + ; Map the first MB at 0xFFFFFE0000000000 --> 0xFFFFFE0000100000 + mov rdi,0xFFFFFF7FBFDFE000 + mov rax,[rdi] + mov rdi,0xFFFFFF7FBFDFEFE0 + mov [rdi],rax + mov rax,cr3 + mov cr3,rax + + ; Use the new linear address of the GDT + mov rax,0xFFFFFE0000000000 + add qword [gdt_data.gdt2],rax + lgdt [gdt_data.gdt] + + ; Execute the kernel's _start function + xor rdi,rdi + mov rsi,1 + jmp rcx + +error_64: + jmp far [.error_32_ind] + .error_32_ind: dq error_32 + dd 8 + +[bits 32] +error_32: + ; Switch to 16-bit mode + mov eax,cr0 + and eax,0x7FFFFFFF + mov cr0,eax + jmp 0x18:.rmode + + ; Switch to real mode + [bits 16] + .rmode: + mov eax,cr0 + and eax,0x7FFFFFFE + mov cr0,eax + jmp 0x0:.finish + + ; Go to error + .finish: + mov ax,0 + mov ds,ax + mov es,ax + mov ss,ax + jmp error + +check_a20: + ; Set the carry flag if the A20 line is enabled + mov ax,0 + mov es,ax + mov ax,0xFFFF + mov fs,ax + mov byte [es:0x600],0 + mov byte [fs:0x610],0xFF + cmp byte [es:0x600],0xFF + je .enabled + stc + ret + .enabled: + clc + ret + +error: + ; Print an error message + lodsb + or al,al + jz .break + mov ah,0xE + int 0x10 + jmp error + + ; Break indefinitely + .break: + cli + hlt + +gdt_data: + .null_entry: dq 0 + .code_entry: dd 0xFFFF ; 0x08 + db 0 + dw 0xCF9A + db 0 + .data_entry: dd 0xFFFF ; 0x10 + db 0 + dw 0xCF92 + db 0 + .code_entry_16: dd 0xFFFF ; 0x18 + db 0 + dw 0x0F9A + db 0 + .data_entry_16: dd 0xFFFF ; 0x20 + db 0 + dw 0x0F92 + db 0 + .user_code: dd 0xFFFF ; 0x2B + db 0 + dw 0xCFFA + db 0 + .user_data: dd 0xFFFF ; 0x33 + db 0 + dw 0xCFF2 + db 0 + .tss: dd 0x68 ; 0x38 + db 0 + dw 0xE9 + db 0 + dq 0 + .code_entry64: dd 0xFFFF ; 0x48 + db 0 + dw 0xAF9A + db 0 + .data_entry64: dd 0xFFFF ; 0x50 + db 0 + dw 0xAF92 + db 0 + .user_code64: dd 0xFFFF ; 0x5B + db 0 + dw 0xAFFA + db 0 + .user_data64: dd 0xFFFF ; 0x63 + db 0 + dw 0xAFF2 + db 0 + .user_code64c: dd 0xFFFF ; 0x6B + db 0 + dw 0xAFFA + db 0 + .gdt: dw (gdt_data.gdt - gdt_data - 1) + .gdt2: dq gdt_data + + ; @FilesystemSpecific + FilesystemSpecificCode + +load_sectors: + ; Load CX sectors from sector EAX to EDI + pushad + push edi + + ; Add the partition offset to EAX + mov bx,[partition_entry] + mov ebx,[bx + 8] + add eax,ebx + + ; Load 1 sector + mov [read_structure.lba],eax + mov ah,0x42 + mov dl,[drive_number] + mov si,read_structure + cmp byte [use_emu_read],1 + je .use_emu + int 0x13 + jmp .done_read + .use_emu: + call load_sector_emu + .done_read: + + ; Check for error + mov si,error_cannot_read_disk + jc error + + ; Copy the data to its destination + pop edi + call move_sector_to_target + + ; Go to the next sector + popad + add edi,0x200 + inc eax + loop load_sectors + ret + + ; Move data from the temporary load buffer to EDI +move_sector_to_target: + push ss + push ds + push es + + ; Switch to protected mode + cli + mov eax,cr0 + or eax,0x80000001 + mov cr0,eax + jmp 0x8:.pmode + + ; Set the data segment registers + [bits 32] + .pmode: + mov ax,0x10 + mov ds,ax + mov es,ax + mov ss,ax + + ; Copy the data + mov ecx,0x200 + mov esi,temporary_load_buffer + rep movsb + + ; Switch to 16-bit mode + mov eax,cr0 + and eax,0x7FFFFFFF + mov cr0,eax + jmp 0x18:.rmode + + ; Switch to real mode + [bits 16] + .rmode: + mov eax,cr0 + and eax,0x7FFFFFFE + mov cr0,eax + jmp 0x0:.finish + + ; Return to load_sectors + .finish: + pop es + pop ds + pop ss + sti + ret + +load_sector_emu: + mov di,[read_structure.lba] + xor ax,ax + mov es,ax + mov bx,0x9000 + + ; Calculate cylinder and head. + mov ax,di + xor dx,dx + div word [max_sectors] + xor dx,dx + div word [max_heads] + push dx ; remainder - head + mov ch,al ; quotient - cylinder + shl ah,6 + mov cl,ah + + ; Calculate sector. + mov ax,di + xor dx,dx + div word [max_sectors] + inc dx + or cl,dl + + ; Load the sector. + pop dx + mov dh,dl + mov dl,[drive_number] + mov ax,0x0201 + int 0x13 + + ret + +read_structure: + ; Data for the extended read calls + dw 0x10 + dw 1 + dd temporary_load_buffer + .lba: dq 0 + +drive_number: db 0 +use_emu_read: db 0 +partition_entry: dw 0 +max_sectors: dw 0 +max_heads: dw 0 + +page_table_allocation_location: dq 0 ; Relative to page_directory. + +kernel_buffer: dq 0 +kernel_size: dq 0 + +error_cannot_enable_a20_line: db "Error: Cannot enable the A20 line",0 +error_could_not_get_memory_map: db "Error: Could not get the memory map from the BIOS",0 +error_no_pci: db "Error: Could not find the PCI bus",0 +error_no_cpuid: db "Error: CPU does not have the CPUID instruction",0 +error_no_msr: db "Error: CPU does not have the RDMSR instruction",0 +error_cannot_find_file: db "Error: A file necessary for booting could not found",0 +error_cannot_read_disk: db "Error: The disk could not be read.",0 +error_file_too_large: db "Error: The file was too large to be loaded (more than 256KB).",0 +error_kernel_too_large: db "Error: The kernel was too large for the 3MB buffer.",0 +error_bad_kernel: db "Error: Invalid or unsupported kernel ELF format.",0 +error_no_memory: db "Error: Not enough memory to load kernel" +error_no_long_mode: db "Error: The kernel is compiled for a 64-bit processor but the current processor is 32-bit only.",0 +error_could_not_set_video_mode: db "Error: Could not set video mode 1024x768x24.",0 + +reached_end: db "Reached end of stage 2 bootloader.",0 diff --git a/boot/x86/mbr-emu.s b/boot/x86/mbr-emu.s new file mode 100644 index 0000000..6f711df --- /dev/null +++ b/boot/x86/mbr-emu.s @@ -0,0 +1,144 @@ +[bits 16] +[org 0x600] + +start: + ; Setup segment registers and the stack + cli + mov ax,0 + mov ds,ax + mov es,ax + mov fs,ax + mov gs,ax + mov ss,ax + mov sp,0x7C00 + sti + + ; Clear the screen + mov ax,0 + int 0x10 + mov ax,3 + int 0x10 + + ; Relocate to 0x600 + cld + mov si,0x7C00 + mov di,0x600 + mov cx,0x200 + rep movsb + jmp 0x0:find_partition + +find_partition: + ; Save the drive number + mov byte [drive_number],dl + + ; Get drive parameters. + mov ah,0x08 + xor di,di + int 0x13 + mov si,error_cannot_read_disk + jc error + and cx,63 + mov [max_sectors],cx + inc dh + shr dx,8 + mov [max_heads],dx + mov si,error_bad_geometry + or cx,cx + jz error + or dx,dx + jz error + + ; Find the bootable flag (0x80) + mov bx,partition_entry_1 + cmp byte [bx],0x80 + je found_partition + mov bx,partition_entry_2 + cmp byte [bx],0x80 + je found_partition + mov bx,partition_entry_3 + cmp byte [bx],0x80 + je found_partition + mov bx,partition_entry_4 + cmp byte [bx],0x80 + je found_partition + + ; No bootable partition + mov si,error_no_bootable_partition + jmp error + +found_partition: + ; Load the first sector of the partition at 0x7C00 + push bx + mov di,[bx + 8] + mov bx,0x7C00 + call load_sector + + ; Jump to the partition's boot sector + mov dl,[drive_number] + pop si + mov dh,0x01 + jmp 0x0:0x7C00 + +error: + ; Print an error message + lodsb + or al,al + jz .break + mov ah,0xE + int 0x10 + jmp error + + ; Break indefinitely + .break: + cli + hlt + +; di - LBA. +; es:bx - buffer +load_sector: + ; Calculate cylinder and head. + mov ax,di + xor dx,dx + div word [max_sectors] + xor dx,dx + div word [max_heads] + push dx ; remainder - head + mov ch,al ; quotient - cylinder + shl ah,6 + mov cl,ah + + ; Calculate sector. + mov ax,di + xor dx,dx + div word [max_sectors] + inc dx + or cl,dl + + ; Load the sector. + pop dx + mov dh,dl + mov dl,[drive_number] + mov ax,0x0201 + int 0x13 + mov si,error_cannot_read_disk + jc error + + ret + +error_cannot_read_disk: db "Error: The disk could not be read. (MBR)",0 +error_no_bootable_partition: db "Error: No bootable partition could be found on the disk.",0 +error_bad_geometry: db 'Error: The BIOS reported invalid disk geometry.',0 + +drive_number: db 0 +max_sectors: dw 0 +max_heads: dw 0 + +times (0x1B4 - ($-$$)) nop + +disk_identifier: times 10 db 0 +partition_entry_1: times 16 db 0 +partition_entry_2: times 16 db 0 +partition_entry_3: times 16 db 0 +partition_entry_4: times 16 db 0 + +dw 0xAA55 diff --git a/boot/x86/mbr.s b/boot/x86/mbr.s new file mode 100644 index 0000000..c480cf3 --- /dev/null +++ b/boot/x86/mbr.s @@ -0,0 +1,106 @@ +[bits 16] +[org 0x600] + +start: + ; Setup segment registers and the stack + cli + mov ax,0 + mov ds,ax + mov es,ax + mov fs,ax + mov gs,ax + mov ss,ax + mov sp,0x7C00 + sti + + ; Clear the screen + mov ax,0 + int 0x10 + mov ax,3 + int 0x10 + + ; Relocate to 0x600 + cld + mov si,0x7C00 + mov di,0x600 + mov cx,0x200 + rep movsb + jmp 0x0:find_partition + +drive_number: db 0 + +find_partition: + ; Save the drive number + mov byte [drive_number],dl + + ; Find the bootable flag (0x80) + mov bx,partition_entry_1 + cmp byte [bx], 0x80 + je found_partition + mov bx,partition_entry_2 + cmp byte [bx], 0x80 + je found_partition + mov bx,partition_entry_3 + cmp byte [bx], 0x80 + je found_partition + mov bx,partition_entry_4 + cmp byte [bx], 0x80 + je found_partition + + ; No bootable partition + mov si,error_no_bootable_partition + jmp error + +found_partition: + ; Load the first sector of the partition at 0x7C00 + push bx + mov eax,[bx + 8] + mov [read_structure.lba],eax + mov ah,0x42 + mov dl,[drive_number] + push dx + mov si,read_structure + int 0x13 + + ; Check for an error + mov si,error_cannot_read_disk + jc error + + ; Jump to the partition's boot sector + pop dx + pop si + xor dh,dh + jmp 0x0:0x7C00 + +error: + ; Print an error message + lodsb + or al,al + jz .break + mov ah,0xE + int 0x10 + jmp error + + ; Break indefinitely + .break: + cli + hlt + +read_structure: ; Data for the extended read calls + dw 0x10 + dw 1 + dd 0x7C00 + .lba: dq 0 + +error_cannot_read_disk: db "Error: The disk could not be read. (MBR)",0 +error_no_bootable_partition: db "Error: No bootable partition could be found on the disk.",0 + +times (0x1B4 - ($-$$)) nop + +disk_identifier: times 10 db 0 +partition_entry_1: times 16 db 0 +partition_entry_2: times 16 db 0 +partition_entry_3: times 16 db 0 +partition_entry_4: times 16 db 0 + +dw 0xAA55 diff --git a/boot/x86/uefi.c b/boot/x86/uefi.c new file mode 100644 index 0000000..ac5c2f4 --- /dev/null +++ b/boot/x86/uefi.c @@ -0,0 +1,404 @@ +#include +#include + +#define ENTRIES_PER_PAGE_TABLE (512) +#define ENTRIES_PER_PAGE_TABLE_BITS (9) +#define K_PAGE_SIZE (4096) +#define K_PAGE_BITS (12) + +typedef struct __attribute__((packed)) GDTData { + uint16_t length; + uint64_t address; +} GDTData; + +typedef struct MemoryRegion { + uintptr_t base, pages; +} MemoryRegion; + +#define MAX_MEMORY_REGIONS (1024) +MemoryRegion memoryRegions[1024]; +#define KERNEL_BUFFER_SIZE (1048576) +#define kernelBuffer ((char *) 0x200000) +#define IID_BUFFER_SIZE (64) +char iidBuffer[IID_BUFFER_SIZE]; +#define MEMORY_MAP_BUFFER_SIZE (16384) +char memoryMapBuffer[MEMORY_MAP_BUFFER_SIZE]; + +#define VESA_VM_INFO_ONLY +#define ARCH_64 +#include "../../kernel/graphics.cpp" +#include "../../kernel/elf.cpp" + +void ZeroMemory(void *pointer, uint64_t size) { + char *d = (char *) pointer; + + for (uintptr_t i = 0; i < size; i++) { + d[i] = 0; + } +} + +EFI_STATUS EFIAPI efi_main(EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE *systemTable) { + UINTN mapKey; + uint32_t *framebuffer, horizontalResolution, verticalResolution, pixelsPerScanline; + InitializeLib(imageHandle, systemTable); + ElfHeader *header; + Print(L"Loading OS...\n"); + + // Make sure 0x100000 -> 0x300000 is identity mapped. + { + EFI_PHYSICAL_ADDRESS address = 0x100000; + + if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->AllocatePages, 4, AllocateAddress, EfiLoaderData, 0x200, &address)) { + Print(L"Error: Could not map 0x100000 -> 0x180000.\n"); + while (1); + } + } + + // Find the RSDP. + { + for (uintptr_t i = 0; i < systemTable->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *entry = systemTable->ConfigurationTable + i; + if (entry->VendorGuid.Data1 == 0x8868E871 && entry->VendorGuid.Data2 == 0xE4F1 && entry->VendorGuid.Data3 == 0x11D3 + && entry->VendorGuid.Data4[0] == 0xBC && entry->VendorGuid.Data4[1] == 0x22 && entry->VendorGuid.Data4[2] == 0x00 + && entry->VendorGuid.Data4[3] == 0x80 && entry->VendorGuid.Data4[4] == 0xC7 && entry->VendorGuid.Data4[5] == 0x3C + && entry->VendorGuid.Data4[6] == 0x88 && entry->VendorGuid.Data4[7] == 0x81) { + *((uint64_t *) 0x107FE8) = (uint64_t) entry->VendorTable; + Print(L"The RSDP can be found at 0x%x.\n", entry->VendorTable); + } + } + } + + // Read the kernel, IID and loader files. + { + EFI_GUID loadedImageProtocolGUID = LOADED_IMAGE_PROTOCOL; + EFI_GUID simpleFilesystemProtocolGUID = SIMPLE_FILE_SYSTEM_PROTOCOL; + + EFI_LOADED_IMAGE_PROTOCOL *loadedImageProtocol; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *simpleFilesystemProtocol; + + EFI_FILE *filesystemRoot, *kernelFile, *iidFile, *loaderFile; + + UINTN size; + + if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->OpenProtocol, 6, imageHandle, &loadedImageProtocolGUID, &loadedImageProtocol, imageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL)) { + Print(L"Error: Could not open protocol 1.\n"); + while (1); + } + + EFI_HANDLE deviceHandle = loadedImageProtocol->DeviceHandle; + + if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->OpenProtocol, 6, deviceHandle, &simpleFilesystemProtocolGUID, &simpleFilesystemProtocol, imageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL)) { + Print(L"Error: Could not open procotol 2.\n"); + while (1); + } + + if (EFI_SUCCESS != uefi_call_wrapper(simpleFilesystemProtocol->OpenVolume, 2, simpleFilesystemProtocol, &filesystemRoot)) { + Print(L"Error: Could not open ESP volume.\n"); + while (1); + } + + if (EFI_SUCCESS != uefi_call_wrapper(filesystemRoot->Open, 5, filesystemRoot, &kernelFile, L"EssenceKernel.esx", EFI_FILE_MODE_READ, 0)) { + Print(L"Error: Could not open EssenceKernel.esx.\n"); + while (1); + } + + size = KERNEL_BUFFER_SIZE; + + if (EFI_SUCCESS != uefi_call_wrapper(kernelFile->Read, 3, kernelFile, &size, kernelBuffer)) { + Print(L"Error: Could not load EssenceKernel.esx.\n"); + while (1); + } + + Print(L"Kernel size: %d bytes\n", size); + + if (size == KERNEL_BUFFER_SIZE) { + Print(L"Kernel too large to fit into buffer.\n"); + while (1); + } + + if (EFI_SUCCESS != uefi_call_wrapper(filesystemRoot->Open, 5, filesystemRoot, &iidFile, L"EssenceIID.dat", EFI_FILE_MODE_READ, 0)) { + Print(L"Error: Could not open EssenceIID.dat.\n"); + while (1); + } + + size = IID_BUFFER_SIZE; + + if (EFI_SUCCESS != uefi_call_wrapper(iidFile->Read, 3, iidFile, &size, iidBuffer)) { + Print(L"Error: Could not load EssenceIID.dat.\n"); + while (1); + } + + if (EFI_SUCCESS != uefi_call_wrapper(filesystemRoot->Open, 5, filesystemRoot, &loaderFile, L"EssenceLoader.bin", EFI_FILE_MODE_READ, 0)) { + Print(L"Error: Could not open EssenceLoader.bin.\n"); + while (1); + } + + size = 0x80000; + + if (EFI_SUCCESS != uefi_call_wrapper(loaderFile->Read, 3, loaderFile, &size, (char *) 0x180000)) { + Print(L"Error: Could not load EssenceLoader.bin.\n"); + while (1); + } + } + +#if 0 + // Print the memory map. + { + UINTN descriptorSize, descriptorVersion, size = MEMORY_MAP_BUFFER_SIZE; + + if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &size, (EFI_MEMORY_DESCRIPTOR *) memoryMapBuffer, &mapKey, &descriptorSize, &descriptorVersion)) { + Print(L"Error: Could not get memory map.\n"); + while (1); + } + + WCHAR *memoryTypes[] = { + L"EfiReservedMemoryType", + L"EfiLoaderCode", + L"EfiLoaderData", + L"EfiBootServicesCode", + L"EfiBootServicesData", + L"EfiRuntimeServicesCode", + L"EfiRuntimeServicesData", + L"EfiConventionalMemory", + L"EfiUnusableMemory", + L"EfiACPIReclaimMemory", + L"EfiACPIMemoryNVS", + L"EfiMemoryMappedIO", + L"EfiMemoryMappedIOPortSpace", + L"EfiPalCode", + L"EfiMaxMemoryType", + }; + + for (uintptr_t i = 0; i < size / descriptorSize; i++) { + EFI_MEMORY_DESCRIPTOR *descriptor = (EFI_MEMORY_DESCRIPTOR *) (memoryMapBuffer + i * descriptorSize); + Print(L"memory %s: %llx -> %llx\n", memoryTypes[descriptor->Type], descriptor->PhysicalStart, descriptor->PhysicalStart + descriptor->NumberOfPages * 0x1000 - 1); + } + } +#endif + + // Get the graphics mode information. + { + EFI_GRAPHICS_OUTPUT_PROTOCOL *graphicsOutputProtocol; + EFI_GUID graphicsOutputProtocolGUID = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + + if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->LocateProtocol, 3, &graphicsOutputProtocolGUID, NULL, &graphicsOutputProtocol)) { + Print(L"Error: Could not open protocol 3.\n"); + while (1); + } + + int32_t desiredMode = -1; + + Print(L"Select a graphics mode:\n"); + + for (uint32_t i = 0; i < graphicsOutputProtocol->Mode->MaxMode; i++) { + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *modeInformation; + UINTN modeInformationSize; + uefi_call_wrapper(graphicsOutputProtocol->QueryMode, 4, graphicsOutputProtocol, i, &modeInformationSize, &modeInformation); + + if (modeInformation->HorizontalResolution >= 800 && modeInformation->VerticalResolution >= 600) { + Print(L" %d: %d by %d\n", i + 1, modeInformation->HorizontalResolution, modeInformation->VerticalResolution); + } + } + + Print(L"> "); + + while (1) { + UINTN index; + uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &systemTable->ConIn->WaitForKey, &index); + EFI_INPUT_KEY key; + uefi_call_wrapper(systemTable->ConIn->ReadKeyStroke, 2, systemTable->ConIn, &key); + + if (key.UnicodeChar >= '0' && key.UnicodeChar <= '9') { + if (desiredMode == -1) desiredMode = 0; + desiredMode = (desiredMode * 10) + key.UnicodeChar - '0'; + Print(L"%c", key.UnicodeChar); + } else if (key.UnicodeChar == 13) { + break; + } + } + + Print(L"\n"); + + if (desiredMode != -1) { + Print(L"Setting mode %d...\n", desiredMode); + desiredMode--; + + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *modeInformation; + UINTN modeInformationSize; + uefi_call_wrapper(graphicsOutputProtocol->QueryMode, 4, graphicsOutputProtocol, desiredMode, &modeInformationSize, &modeInformation); + + horizontalResolution = modeInformation->HorizontalResolution; + verticalResolution = modeInformation->VerticalResolution; + pixelsPerScanline = modeInformation->PixelsPerScanLine; + + uefi_call_wrapper(graphicsOutputProtocol->SetMode, 2, graphicsOutputProtocol, desiredMode); + } else { + horizontalResolution = graphicsOutputProtocol->Mode->Info->HorizontalResolution; + verticalResolution = graphicsOutputProtocol->Mode->Info->VerticalResolution; + pixelsPerScanline = graphicsOutputProtocol->Mode->Info->PixelsPerScanLine; + } + + framebuffer = (uint32_t *) graphicsOutputProtocol->Mode->FrameBufferBase; + } + + // Get the memory map. + { + UINTN descriptorSize, descriptorVersion, size = MEMORY_MAP_BUFFER_SIZE; + + if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &size, (EFI_MEMORY_DESCRIPTOR *) memoryMapBuffer, &mapKey, &descriptorSize, &descriptorVersion)) { + Print(L"Error: Could not get memory map.\n"); + while (1); + } + + uintptr_t memoryRegionCount = 0; + + for (uintptr_t i = 0; i < size / descriptorSize && memoryRegionCount != MAX_MEMORY_REGIONS - 1; i++) { + EFI_MEMORY_DESCRIPTOR *descriptor = (EFI_MEMORY_DESCRIPTOR *) (memoryMapBuffer + i * descriptorSize); + + if (descriptor->Type == EfiConventionalMemory && descriptor->PhysicalStart >= 0x300000) { + memoryRegions[memoryRegionCount].base = descriptor->PhysicalStart; + memoryRegions[memoryRegionCount].pages = descriptor->NumberOfPages; + memoryRegionCount++; + } + } + + memoryRegions[memoryRegionCount].base = 0; + } + + // Exit boot services. + { + if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->ExitBootServices, 2, imageHandle, mapKey)) { + Print(L"Error: Could not exit boot services.\n"); + while (1); + } + } + + // Identity map the first 3MB for the loader. + { + uint64_t *paging = (uint64_t *) 0x140000; + ZeroMemory(paging, 0x5000); + + paging[0x1FE] = 0x140003; + paging[0x000] = 0x141003; + paging[0x200] = 0x142003; + paging[0x400] = 0x143003; + paging[0x401] = 0x144003; + + for (uintptr_t i = 0; i < 0x400; i++) { + paging[0x600 + i] = (i * 0x1000) | 3; + } + } + + // Copy the installation ID across. + { + uint8_t *destination = (uint8_t *) (0x107FF0); + + for (uintptr_t i = 0; i < 16; i++) { + char c1 = iidBuffer[i * 3 + 0]; + char c2 = iidBuffer[i * 3 + 1]; + if (c1 >= '0' && c1 <= '9') c1 -= '0'; else c1 -= 'A' - 10; + if (c2 >= '0' && c2 <= '9') c2 -= '0'; else c2 -= 'A' - 10; + destination[i] = (c2) | ((c1) << 4); + } + } + + // Copy the graphics information across. + { + struct VESAVideoModeInformation *destination = (struct VESAVideoModeInformation *) (0x107000); + destination->widthPixels = horizontalResolution; + destination->heightPixels = verticalResolution; + destination->bufferPhysical = (uintptr_t) framebuffer; // TODO 64-bit framebuffers. + destination->bytesPerScanlineLinear = pixelsPerScanline * 4; + destination->bitsPerPixel = 32; + } + + // Allocate and map memory for the kernel. + { + uint64_t nextPageTable = 0x1C0000; + + header = (ElfHeader *) kernelBuffer; + ElfProgramHeader *programHeaders = (ElfProgramHeader *) (kernelBuffer + header->programHeaderTable); + uintptr_t programHeaderEntrySize = header->programHeaderEntrySize; + + for (uintptr_t i = 0; i < header->programHeaderEntries; i++) { + ElfProgramHeader *header = (ElfProgramHeader *) ((uint8_t *) programHeaders + programHeaderEntrySize * i); + if (header->type != 1) continue; + + uintptr_t pagesToAllocate = header->segmentSize >> 12; + if (header->segmentSize & 0xFFF) pagesToAllocate++; + uintptr_t physicalAddress = 0; + + for (uintptr_t j = 0; j < MAX_MEMORY_REGIONS; j++) { + MemoryRegion *region = memoryRegions + j; + if (!region->base) break; + if (region->pages < pagesToAllocate) continue; + physicalAddress = region->base; + region->pages -= pagesToAllocate; + region->base += pagesToAllocate << 12; + break; + } + + if (!physicalAddress) { + // TODO Error handling. + *((uint32_t *) framebuffer + 3) = 0xFFFF00FF; + while (1); + } + + for (uintptr_t j = 0; j < pagesToAllocate; j++, physicalAddress += 0x1000) { + uintptr_t virtualAddress = header->virtualAddress + j * K_PAGE_SIZE; + physicalAddress &= 0xFFFFFFFFFFFFF000; + virtualAddress &= 0x0000FFFFFFFFF000; + + uintptr_t indexL4 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3)) & (ENTRIES_PER_PAGE_TABLE - 1); + uintptr_t indexL3 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2)) & (ENTRIES_PER_PAGE_TABLE - 1); + uintptr_t indexL2 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1)) & (ENTRIES_PER_PAGE_TABLE - 1); + uintptr_t indexL1 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 0)) & (ENTRIES_PER_PAGE_TABLE - 1); + + uint64_t *tableL4 = (uint64_t *) 0x140000; + + if (!(tableL4[indexL4] & 1)) { + tableL4[indexL4] = nextPageTable | 7; + ZeroMemory((void *) nextPageTable, K_PAGE_SIZE); + nextPageTable += K_PAGE_SIZE; + } + + uint64_t *tableL3 = (uint64_t *) (tableL4[indexL4] & ~(K_PAGE_SIZE - 1)); + + if (!(tableL3[indexL3] & 1)) { + tableL3[indexL3] = nextPageTable | 7; + ZeroMemory((void *) nextPageTable, K_PAGE_SIZE); + nextPageTable += K_PAGE_SIZE; + } + + uint64_t *tableL2 = (uint64_t *) (tableL3[indexL3] & ~(K_PAGE_SIZE - 1)); + + if (!(tableL2[indexL2] & 1)) { + tableL2[indexL2] = nextPageTable | 7; + ZeroMemory((void *) nextPageTable, K_PAGE_SIZE); + nextPageTable += K_PAGE_SIZE; + } + + uint64_t *tableL1 = (uint64_t *) (tableL2[indexL2] & ~(K_PAGE_SIZE - 1)); + uintptr_t value = physicalAddress | 3; + tableL1[indexL1] = value; + } + } + } + + // Copy the memory regions information across. + { + MemoryRegion *destination = (MemoryRegion *) 0x160000; + + for (uintptr_t i = 0; i < MAX_MEMORY_REGIONS; i++) { + destination[i] = memoryRegions[i]; + } + } + + // Start the loader. + { + ((void (*)()) 0x180000)(); + } + + while (1); + return EFI_SUCCESS; +} diff --git a/boot/x86/uefi_loader.s b/boot/x86/uefi_loader.s new file mode 100644 index 0000000..57efc90 --- /dev/null +++ b/boot/x86/uefi_loader.s @@ -0,0 +1,156 @@ +[bits 64] +[org 0x180000] +[section .text] + +; 0x107000 Graphics info +; 0x107FE8 RSDP address +; 0x107FF0 Installation ID +; 0x140000-0x150000 Identity paging tables +; 0x160000-0x170000 Memory regions +; 0x180000-0x1C0000 Loader (this) +; 0x1C0000-0x1E0000 Kernel paging tables +; 0x1F0000-0x200000 Stack +; 0x200000-0x300000 Kernel + +%define memory_map 0x160000 +%define kernel_buffer 0x200000 + +start: + ; Setup the environment. + lgdt [gdt_data.gdt] + mov rax,0x140000 + mov cr3,rax + mov rax,0x200000 + mov rsp,rax + mov rax,0x50 + mov ds,rax + mov es,rax + mov ss,rax + + ; Find the program headers + ; RAX = ELF header, RBX = program headers + mov rax,kernel_buffer + mov rbx,[rax + 32] + add rbx,rax + + ; ECX = entries, EDX = size of entry + movzx rcx,word [rax + 56] + movzx rdx,word [rax + 54] + + ; Loop through each program header + .loop_program_headers: + push rax + push rcx + + ; Only deal with load segments + mov eax,[rbx] + cmp eax,1 + jne .next_entry + + ; Clear the memory + mov rcx,[rbx + 40] + xor rax,rax + mov rdi,[rbx + 16] + rep stosb + + ; Copy the memory + mov rcx,[rbx + 32] + mov rsi,[rbx + 8] + add rsi,kernel_buffer + mov rdi,[rbx + 16] + rep movsb + + ; Go to the next entry + .next_entry: + pop rcx + pop rax + + add rbx,rdx + dec rcx + or rcx,rcx + jnz .loop_program_headers + + jmp run_kernel64 + +run_kernel64: + ; Get the start address of the kernel + mov rbx,kernel_buffer + mov rcx,[rbx + 24] + + ; Map the first MB at 0xFFFFFE0000000000 --> 0xFFFFFE0000100000 + mov rax,[0xFFFFFF7FBFDFE000] + mov [0xFFFFFF7FBFDFEFE0],rax + mov rax,cr3 + mov cr3,rax + + ; Use the new linear address of the GDT + mov rax,0xFFFFFE0000000000 + add qword [gdt_data.gdt2],rax + lgdt [gdt_data.gdt] + + call set_cs + + ; Execute the kernel's _start function + mov rdi,0x100000 + mov rsi,2 + jmp rcx + +set_cs: + pop rax + push 0x48 + push rax + db 0x48, 0xCB + +gdt_data: + .null_entry: dq 0 + .code_entry: dd 0xFFFF ; 0x08 + db 0 + dw 0xCF9A + db 0 + .data_entry: dd 0xFFFF ; 0x10 + db 0 + dw 0xCF92 + db 0 + .code_entry_16: dd 0xFFFF ; 0x18 + db 0 + dw 0x0F9A + db 0 + .data_entry_16: dd 0xFFFF ; 0x20 + db 0 + dw 0x0F92 + db 0 + .user_code: dd 0xFFFF ; 0x2B + db 0 + dw 0xCFFA + db 0 + .user_data: dd 0xFFFF ; 0x33 + db 0 + dw 0xCFF2 + db 0 + .tss: dd 0x68 ; 0x38 + db 0 + dw 0xE9 + db 0 + dq 0 + .code_entry64: dd 0xFFFF ; 0x48 + db 0 + dw 0xAF9A + db 0 + .data_entry64: dd 0xFFFF ; 0x50 + db 0 + dw 0xAF92 + db 0 + .user_code64: dd 0xFFFF ; 0x5B + db 0 + dw 0xAFFA + db 0 + .user_data64: dd 0xFFFF ; 0x63 + db 0 + dw 0xAFF2 + db 0 + .user_code64c: dd 0xFFFF ; 0x6B + db 0 + dw 0xAFFA + db 0 + .gdt: dw (gdt_data.gdt - gdt_data - 1) + .gdt2: dq gdt_data diff --git a/boot/x86/vbe.s b/boot/x86/vbe.s new file mode 100644 index 0000000..820ab42 --- /dev/null +++ b/boot/x86/vbe.s @@ -0,0 +1,361 @@ +vbe_init: + mov ax,vesa_info >> 4 + mov es,ax + + xor di,di +%ifndef BOOT_USE_VBE + jmp vbe_bad +%endif + + + ; Get EDID information. + mov ax,0x4F15 + mov bl,1 + xor cx,cx + xor dx,dx + xor di,di + int 0x10 + cmp ax,0x4F + jne .no_edid + cmp byte [es:1],0xFF + jne .no_edid + mov al,[es:0x38] + mov ah,[es:0x3A] + shr ah,4 + mov bl,[es:0x3B] + mov bh,[es:0x3D] + shr bh,4 + or ax,ax + jz .no_edid + or bx,bx + jz .no_edid + mov [vbe_best_width],ax + mov [vbe_best_height],bx + mov byte [vbe_has_edid],1 + jmp .no_flat_panel + .no_edid: + ; Get flat panel information. + mov ax,0x4F11 + mov bx,1 + xor di,di + int 0x10 + cmp ax,0x4F + jne .no_flat_panel + mov ax,[es:0x00] + mov bx,[es:0x02] + or ax,ax + jz .no_flat_panel + or bx,bx + jz .no_flat_panel + cmp ax,4096 + ja .no_flat_panel + cmp bx,4096 + ja .no_flat_panel + mov [vbe_best_width],ax + mov [vbe_best_height],bx + .no_flat_panel: + + ; Get SVGA information. + xor di,di + mov ax,0x4F00 + int 0x10 + cmp ax,0x4F + jne vbe_bad + + ; Load the list of available modes. + add di,0x200 + mov eax,[es:14] + cmp eax,0 + je .find_done + mov ax,[es:16] + mov fs,ax + mov si,[es:14] + xor cx,cx + .find_loop: + mov ax,[fs:si] + cmp ax,0xFFFF + je .find_done + mov [es:di],ax + add di,2 + add si,2 + jmp .find_loop + .find_done: + + ; Add standard modes (if necessary). + mov word [es:di],0xFFFF + cmp di,0x200 + jne .added_modes + mov word [es:di + 0],257 + mov word [es:di + 2],259 + mov word [es:di + 4],261 + mov word [es:di + 6],263 + mov word [es:di + 8],273 + mov word [es:di + 10],276 + mov word [es:di + 12],279 + mov word [es:di + 14],282 + mov word [es:di + 16],274 + mov word [es:di + 18],277 + mov word [es:di + 20],280 + mov word [es:di + 22],283 + mov word [es:di + 24],0xFFFF + .added_modes: + + ; Check which of these modes can be used. + mov si,0x200 + mov di,0x200 + .check_loop: + mov cx,[es:si] + mov [es:di],cx + cmp cx,0xFFFF + je .check_done + push di + push si + mov ax,0x4F01 + xor di,di + or cx,(1 << 14) + int 0x10 + pop si + pop di + add si,2 + cmp ax,0x4F ; Interrupt failed. + jne .check_loop + cmp byte [es:0x19],24 ; We only support 24-bit and 32-bit modes currently. + je .valid_bpp + cmp byte [es:0x19],32 + je .valid_bpp + jne .check_loop + .valid_bpp: + cmp word [es:0x14],480 ; We support a minimum vertical resolution of 480 pixels. + jl .check_loop + mov ax,[vbe_best_width] + cmp [es:0x12],ax + jne .not_best_mode + mov ax,[vbe_best_height] + cmp [es:0x14],ax + jne .not_best_mode + mov ax,[es:di] + mov [vbe_best_mode],ax + .not_best_mode: + add di,2 + jmp .check_loop + .check_done: + + ; If we found a best mode, use that. + mov bx,[vbe_best_mode] + or bx,bx + jnz .set_graphics_mode + .no_best_mode: + + ; Print a list of the available modes. + mov si,vbe_s_select_video_mode + call vbe_print_string + mov bx,0x200 + mov cx,1 + .print_loop: + mov dx,[es:bx] + cmp dx,0xFFFF + je .print_done + cmp cx,21 ; Maximum of 20 options. TODO Scrolling! + je .print_done + xor di,di + push cx + mov ax,0x4F01 + mov cx,dx + or cx,(1 << 14) + int 0x10 + pop cx + mov si,vbe_s_left_bracket + call vbe_print_string + mov ax,cx + call vbe_print_decimal + mov si,vbe_s_right_bracket + call vbe_print_string + mov ax,[es:0x12] + call vbe_print_decimal + mov si,vbe_s_by + call vbe_print_string + mov ax,[es:0x14] + call vbe_print_decimal + mov si,vbe_s_space + call vbe_print_string + xor ah,ah + mov al,[es:0x19] + call vbe_print_decimal + mov si,vbe_s_bpp + call vbe_print_string + call vbe_print_newline + inc cx + add bx,2 + jmp .print_loop + .print_done: + + ; Let the user select a mode. + mov dx,cx + dec dx + xor cx,cx + .select_loop: + cmp cx,dx + jb .c1 + mov cx,0 + .c1: + call vbe_set_highlighted_line + xor ax,ax + int 0x16 + shr ax,8 + cmp ax,72 + jne .k11 + dec cx + .k11: + cmp ax,80 + jne .k12 + inc cx + .k12: + cmp ax,28 + jne .select_loop + + ; Set the graphics mode. + mov di,cx + shl di,1 + add di,0x200 + mov bx,[es:di] + .set_graphics_mode: + or bx,(1 << 14) + mov cx,bx + mov ax,0x4F02 + int 0x10 + cmp ax,0x4F + jne vbe_failed + + ; Save information about the mode for the kernel. + mov ax,0x4F01 + xor di,di + int 0x10 + mov byte [es:0],1 ; valid + mov al,[es:0x19] + mov [es:1],al ; bpp + mov ax,[es:0x12] + mov [es:2],ax ; width + mov ax,[es:0x14] + mov [es:4],ax ; height + mov ax,[es:0x10] + mov [es:6],ax ; stride + mov eax,[es:40] + mov [es:8],eax ; buffer + xor eax,eax + mov [es:12],eax + mov ax,0x4F15 + mov bl,1 + xor cx,cx + xor dx,dx + mov di,0x10 + int 0x10 + mov al,[vbe_has_edid] + shl al,1 + or [es:0],al + + ret + +vbe_bad: + mov byte [es:di],0 + ret + +vbe_failed: + mov si,vbe_s_failed + call vbe_print_string + jmp vbe_init.select_loop + +vbe_print_newline: + pusha + mov ah,0xE + mov al,13 + int 0x10 + mov ah,0xE + mov al,10 + int 0x10 + popa + ret + +vbe_print_space: + pusha + mov ah,0xE + mov al,' ' + int 0x10 + popa + ret + +vbe_print_string: ; Input - SI. + pusha + .loop: + lodsb + or al,al + jz .done + mov ah,0xE + int 0x10 + jmp .loop + .done: + popa + ret + +vbe_print_decimal: ; Input - AX. + pusha + mov bx,.buffer + mov cx,10 + .next: + xor dx,dx + div cx + add dx,'0' + mov [bx],dl + inc bx + cmp ax,0 + jne .next + .loop: + dec bx + mov al,[bx] + mov ah,0xE + int 0x10 + cmp bx,.buffer + jne .loop + popa + ret + .buffer: db 0, 0, 0, 0, 0 + +vbe_set_highlighted_line: ; Input - CX + pusha + mov ax,0xB800 + mov fs,ax + mov di,1 + mov dx,(80 * 25) + .clear_loop: + mov byte [fs:di],0x07 + add di,2 + dec dx + cmp dx,0 + jne .clear_loop + mov ax,cx + add ax,2 + mov cx,160 + mul cx + mov dx,80 + mov di,ax + inc di + .highlight_loop: + mov byte [fs:di],0x70 + add di,2 + dec dx + cmp dx,0 + jne .highlight_loop + popa + ret + +vbe_s_select_video_mode: db 'Select a video mode: [use up/down then press enter]',13,10,0 +vbe_s_left_bracket: db '(',0 +vbe_s_right_bracket: db ') ',0 +vbe_s_by: db 'x',0 +vbe_s_space: db ' ',0 +vbe_s_bpp: db 'bpp',0 +vbe_s_failed: db 'This graphics mode could not be selected. Please try a different one.',13,10,0 + +vbe_best_width: dw 0 +vbe_best_height: dw 0 +vbe_best_mode: dw 0 +vbe_has_edid: db 0 diff --git a/desktop/api.cpp b/desktop/api.cpp new file mode 100644 index 0000000..5fd9798 --- /dev/null +++ b/desktop/api.cpp @@ -0,0 +1,1591 @@ +#define ES_API +#define ES_FORWARD(x) x +#define ES_EXTERN_FORWARD extern "C" +#define ES_DIRECT_API +#include + +extern "C" void EsUnimplemented(); +#define alloca __builtin_alloca +#define FT_EXPORT(x) extern "C" x + +#ifdef USE_STB_IMAGE +#define STB_IMAGE_IMPLEMENTATION +#define STBI_MALLOC(sz) EsCRTmalloc(sz) +#define STBI_REALLOC(p,newsz) EsCRTrealloc(p,newsz) +#define STBI_FREE(p) EsCRTfree(p) +#define STBI_NO_STDIO +#define STBI_ONLY_PNG +#define STBI_ONLY_JPEG +#define STBI_NO_LINEAR +#define STB_IMAGE_STATIC +#include +#endif + +#include + +#define SHARED_COMMON_WANT_ALL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMPLEMENTATION +#include +#undef IMPLEMENTATION + +struct EnumString { const char *cName; int value; }; +#include + +#define ANIMATION_TIME_SCALE (1) + +#define DESKTOP_MSG_SET_TITLE (1) +#define DESKTOP_MSG_SET_ICON (2) +#define DESKTOP_MSG_REQUEST_SAVE (3) +#define DESKTOP_MSG_COMPLETE_SAVE (4) +#define DESKTOP_MSG_SHOW_IN_FILE_MANAGER (5) +#define DESKTOP_MSG_ANNOUNCE_PATH_MOVED (6) +#define DESKTOP_MSG_RUN_TEMPORARY_APPLICATION (7) +#define DESKTOP_MSG_REQUEST_SHUTDOWN (8) +#define DESKTOP_MSG_START_APPLICATION (9) + +extern "C" uintptr_t ProcessorTLSRead(uintptr_t offset); + +struct EsFileStore { +#define FILE_STORE_HANDLE (1) +#define FILE_STORE_PATH (2) + uint8_t type; + + bool operationComplete; + uint32_t handles; + EsError error; + + union { + EsHandle handle; + + struct { + char *path; + size_t pathBytes; + }; + }; +}; + +struct GlobalData { + uint32_t clickChainTimeoutMs; +}; + +struct ThreadLocalStorage { + // This must be the first field. + ThreadLocalStorage *self; + + uint64_t id; +}; + +struct MountPoint : EsMountPoint { + EsVolumeInformation information; + bool removing; +}; + +struct Timer { + EsTimer id; + double afterMs; + EsTimerCallbackFunction callback; + EsGeneric argument; +}; + +EsError NodeOpen(const char *path, size_t pathBytes, uint32_t flags, _EsNodeInformation *node); + +struct { + Array systemConfigurationGroups; + Array mountPoints; + bool foundBootFileSystem; + EsProcessStartupInformation *startupInformation; + uint64_t systemConstants[ES_SYSTEM_CONSTANT_COUNT]; + GlobalData *global; + + EsMutex messageMutex; + volatile uintptr_t messageMutexThreadID; + + Array<_EsMessageWithObject> postBox; + EsMutex postBoxMutex; + + Array timers; + EsMutex timersMutex; + EsHandle timersThread; + EsHandle timersEvent; + + EsSpinlock performanceTimerStackLock; +#define PERFORMANCE_TIMER_STACK_SIZE (100) + uint64_t performanceTimerStack[PERFORMANCE_TIMER_STACK_SIZE]; + uintptr_t performanceTimerStackCount; +} api; + +ptrdiff_t tlsStorageOffset; + +#include "syscall.cpp" + +// Miscellanous forward declarations. +void MaybeDestroyElement(EsElement *element); +const char *GetConstantString(const char *key); +void UndoManagerDestroy(EsUndoManager *manager); +int TextGetStringWidth(const EsTextStyle *style, const char *string, size_t stringBytes); +struct APIInstance *InstanceSetup(EsInstance *instance); +EsTextStyle TextPlanGetPrimaryStyle(EsTextPlan *plan); +EsFileStore *FileStoreCreateFromPath(const char *path, size_t pathBytes); + +struct ProcessMessageTiming { + double startLogic, endLogic; + double startLayout, endLayout; + double startPaint, endPaint; + double startUpdate, endUpdate; +}; + +struct EsUndoManager { + EsInstance *instance; + + Array undoStack; + Array redoStack; + +#define UNDO_MANAGER_STATE_NORMAL (0) +#define UNDO_MANAGER_STATE_UNDOING (1) +#define UNDO_MANAGER_STATE_REDOING (2) + int state; +}; + +struct APIInstance { + HashStore commands; + + EsApplicationStartupInformation *startupInformation; + EsHandle mainWindowHandle; + + char *documentPath; + size_t documentPathBytes; + uint32_t instanceClass; + + EsCommand commandDelete, + commandSelectAll, + commandCopy, + commandCut, + commandPaste, + commandUndo, + commandRedo, + commandSave, + commandShowInFileManager; + + const char *applicationName; + size_t applicationNameBytes; + + struct InspectorWindow *attachedInspector; + + EsUndoManager undoManager; + EsUndoManager *activeUndoManager; + + EsFileStore *fileStore; + + union { + EsInstanceClassEditorSettings editorSettings; + EsInstanceClassViewerSettings viewerSettings; + }; +}; + +MountPoint *NodeAddMountPoint(const char *prefix, size_t prefixBytes, EsHandle base, bool queryInformation) { + MountPoint mountPoint = {}; + EsAssert(prefixBytes < sizeof(mountPoint.prefix)); + EsMemoryCopy(mountPoint.prefix, prefix, prefixBytes); + mountPoint.base = base; + mountPoint.prefixBytes = prefixBytes; + + if (queryInformation) { + EsSyscall(ES_SYSCALL_VOLUME_GET_INFORMATION, base, (uintptr_t) &mountPoint.information, 0, 0); + } + + return api.mountPoints.Add(mountPoint); +} + +MountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes) { + for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) { + MountPoint *mountPoint = &api.mountPoints[i]; + + if (prefixBytes >= mountPoint->prefixBytes + && 0 == EsMemoryCompare(prefix, mountPoint->prefix, mountPoint->prefixBytes) + && !mountPoint->removing) { + return mountPoint; + } + } + + return nullptr; +} + +bool EsMountPointGetVolumeInformation(const char *prefix, size_t prefixBytes, EsVolumeInformation *information) { + MountPoint *mountPoint = NodeFindMountPoint(prefix, prefixBytes); + if (!mountPoint) return false; + EsMemoryCopy(information, &mountPoint->information, sizeof(EsVolumeInformation)); + return true; +} + +void EsMountPointEnumerate(EsMountPointEnumerationCallbackFunction callback, EsGeneric context) { + for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) { + MountPoint *mountPoint = &api.mountPoints[i]; + callback(mountPoint->prefix, mountPoint->prefixBytes, context); + } +} + +EsError NodeOpen(const char *path, size_t pathBytes, uint32_t flags, _EsNodeInformation *node) { + EsMountPoint *mountPoint = NodeFindMountPoint(path, pathBytes); + + if (!mountPoint) { + return ES_ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME; + } + + node->handle = mountPoint->base; + path += mountPoint->prefixBytes; + pathBytes -= mountPoint->prefixBytes; + + return EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, flags, (uintptr_t) node); +} + +EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes) { + if (keyBytes == -1) keyBytes = EsCStringLength(key); + + for (uintptr_t i = 0; i < group->itemCount; i++) { + if (0 == EsStringCompareRaw(key, keyBytes, group->items[i].key, group->items[i].keyBytes)) { + return group->items + i; + } + } + + return nullptr; +} + +EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptrdiff_t sectionBytes) { + if (sectionBytes == -1) sectionBytes = EsCStringLength(section); + + for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) { + if (0 == EsStringCompareRaw(section, sectionBytes, api.systemConfigurationGroups[i].section, api.systemConfigurationGroups[i].sectionBytes)) { + return &api.systemConfigurationGroups[i]; + } + } + + return nullptr; +} + +char *EsSystemConfigurationGroupReadString(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, size_t *valueBytes) { + EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, key, keyBytes); + if (!item) { if (valueBytes) *valueBytes = 0; return nullptr; } + if (valueBytes) *valueBytes = item->valueBytes; + char *copy = (char *) EsHeapAllocate(item->valueBytes + 1, false); + copy[item->valueBytes] = 0; + EsMemoryCopy(copy, item->value, item->valueBytes); + return copy; +} + +int64_t EsSystemConfigurationGroupReadInteger(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, int64_t defaultValue) { + EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, key, keyBytes); + if (!item) return defaultValue; + return EsIntegerParse(item->value, item->valueBytes); +} + +char *EsSystemConfigurationReadString(const char *section, ptrdiff_t sectionBytes, const char *key, ptrdiff_t keyBytes, size_t *valueBytes) { + EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(section, sectionBytes); + if (!group) { if (valueBytes) *valueBytes = 0; return nullptr; } + return EsSystemConfigurationGroupReadString(group, key, keyBytes, valueBytes); +} + +int64_t EsSystemConfigurationReadInteger(const char *section, ptrdiff_t sectionBytes, const char *key, ptrdiff_t keyBytes, int64_t defaultValue) { + EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(section, sectionBytes); + if (!group) return defaultValue; + return EsSystemConfigurationGroupReadInteger(group, key, keyBytes, defaultValue); +} + +void SystemConfigurationLoad(char *file, size_t fileBytes) { + EsINIState s = {}; + s.buffer = file; + s.bytes = fileBytes; + + EsSystemConfigurationGroup *group = nullptr; + + while (EsINIParse(&s)) { + if (!s.keyBytes) { + EsSystemConfigurationGroup _group = {}; + api.systemConfigurationGroups.Add(_group); + group = &api.systemConfigurationGroups.Last(); + group->section = (char *) EsHeapAllocate(s.sectionBytes, false); + EsMemoryCopy(group->section, s.section, (group->sectionBytes = s.sectionBytes)); + group->sectionClass = (char *) EsHeapAllocate(s.sectionClassBytes, false); + EsMemoryCopy(group->sectionClass, s.sectionClass, (group->sectionClassBytes = s.sectionClassBytes)); + } else if (group) { + EsSystemConfigurationItem item = {}; + item.key = (char *) EsHeapAllocate(s.keyBytes, false); + EsMemoryCopy(item.key, s.key, (item.keyBytes = s.keyBytes)); + item.value = (char *) EsHeapAllocate(s.valueBytes + 1, false); + item.value[s.valueBytes] = 0; + EsMemoryCopy(item.value, s.value, (item.valueBytes = s.valueBytes)); + Array items = { group->items }; + items.Add(item); + group->items = items.array; + group->itemCount++; + } + } + + EsHeapFree(file); +} + +EsSystemConfigurationGroup *EsSystemConfigurationReadAll(size_t *groupCount) { + EsMessageMutexCheck(); + *groupCount = api.systemConfigurationGroups.Length(); + return api.systemConfigurationGroups.array; +} + +uint8_t *ApplicationStartupInformationToBuffer(const EsApplicationStartupInformation *information, size_t *dataBytes = nullptr) { + EsApplicationStartupInformation copy = *information; + if (copy.filePathBytes == -1) copy.filePathBytes = EsCStringLength(copy.filePath); + size_t bytes = 1 + sizeof(EsApplicationStartupInformation) + copy.filePathBytes; + uint8_t *buffer = (uint8_t *) EsHeapAllocate(bytes, false); + buffer[0] = DESKTOP_MSG_START_APPLICATION; + EsMemoryCopy(buffer + 1, ©, sizeof(EsApplicationStartupInformation)); + EsMemoryCopy(buffer + 1 + sizeof(EsApplicationStartupInformation), copy.filePath, copy.filePathBytes); + if (dataBytes) *dataBytes = bytes; + return buffer; +} + +void EsApplicationStart(const EsApplicationStartupInformation *information) { + size_t bufferBytes; + uint8_t *buffer = ApplicationStartupInformationToBuffer(information, &bufferBytes); + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, bufferBytes, 0, 0); +} + +EsApplicationStartupInformation *ApplicationStartupInformationParse(const void *data, size_t dataBytes) { + EsApplicationStartupInformation *startupInformation = (EsApplicationStartupInformation *) data; + + if (sizeof(EsApplicationStartupInformation) <= dataBytes) { + dataBytes -= sizeof(EsApplicationStartupInformation); + if ((size_t) startupInformation->filePathBytes > dataBytes) goto error; + dataBytes -= startupInformation->filePathBytes; + if (dataBytes) goto error; + startupInformation->filePath = (const char *) (startupInformation + 1); + } else { + error:; + EsPrint("Warning: received corrupted startup information.\n"); + return nullptr; + } + + return startupInformation; +} + +void EsInstanceSetClassEditor(EsInstance *_instance, const EsInstanceClassEditorSettings *settings) { + APIInstance *instance = (APIInstance *) _instance->_private; + instance->instanceClass = ES_INSTANCE_CLASS_EDITOR; + instance->editorSettings = *settings; + + if (instance->editorSettings.newDocumentTitleBytes == -1) { + instance->editorSettings.newDocumentTitleBytes = EsCStringLength(instance->editorSettings.newDocumentTitle); + } + + if (instance->editorSettings.newDocumentFileNameBytes == -1) { + instance->editorSettings.newDocumentFileNameBytes = EsCStringLength(instance->editorSettings.newDocumentFileName); + } +} + +void EsInstanceSetClassViewer(EsInstance *_instance, const EsInstanceClassViewerSettings *) { + APIInstance *instance = (APIInstance *) _instance->_private; + instance->instanceClass = ES_INSTANCE_CLASS_VIEWER; +} + +#define CHARACTER_MONO (1) // 1 bit per pixel. +#define CHARACTER_SUBPIXEL (2) // 24 bits per pixel; each byte specifies the alpha of each RGB channel. +#define CHARACTER_IMAGE (3) // 32 bits per pixel, ARGB. +#define CHARACTER_RECOLOR (4) // 32 bits per pixel, AXXX. + +#include "renderer.cpp" +#include "theme.cpp" + +#define TEXT_RENDERER +#include "text.cpp" +#undef TEXT_RENDERER + +#include "gui.cpp" + +#ifndef NO_API_TABLE +const void *const apiTable[] = { +#include +}; +#endif + +extern "C" void _init(); +typedef void (*StartFunction)(); + +const char *EnumLookupNameFromValue(const EnumString *array, int value) { + if (array[0].value == -1) { + return array[value].cName; + } else { + // The values are non-linear, and we have to search the array. + + for (uintptr_t i = 0; array[i].cName; i++) { + if (array[i].value == value) { + return array[i].cName; + } + } + + EsAssert(false); + return nullptr; + } +} + +void EsMessageMutexAcquire() { + EsMutexAcquire(&api.messageMutex); + api.messageMutexThreadID = EsThreadGetID(ES_CURRENT_THREAD); +} + +void EsMessageMutexRelease() { + api.messageMutexThreadID = 0; + EsMutexRelease(&api.messageMutex); +} + +void EsMessageMutexCheck() { + EsAssert(api.messageMutexThreadID == EsThreadGetID(ES_CURRENT_THREAD)); // Expected message mutex to be acquired. +} + +int EsMessageSend(EsElement *element, EsMessage *message) { + int response = 0; + + if (element->messageUser) { + response = element->messageUser(element, message); + } + + bool handledByUser = response; + + if (response == 0 && element->messageClass) { + response = element->messageClass(element, message); + } + + if (element->state & UI_STATE_INSPECTING) { + InspectorNotifyElementEvent(element, "message", "Element processed message '%z' with response %i%z.\n", + EnumLookupNameFromValue(enumStrings_EsMessageType, message->type), response, handledByUser ? " (from user callback)" : ""); + } + + if (message->type >= ES_MSG_STATE_CHANGE_MESSAGE_START && message->type <= ES_MSG_STATE_CHANGE_MESSAGE_END) { + ((EsElement *) element)->MaybeRefreshStyle(); + } + + return response; +} + +void _EsPathAnnouncePathMoved(EsInstance *instance, const char *oldPath, ptrdiff_t oldPathBytes, const char *newPath, ptrdiff_t newPathBytes) { + if (oldPathBytes == -1) oldPathBytes = EsCStringLength(oldPath); + if (newPathBytes == -1) newPathBytes = EsCStringLength(newPath); + size_t bufferBytes = 1 + sizeof(uintptr_t) * 2 + oldPathBytes + newPathBytes; + char *buffer = (char *) EsHeapAllocate(bufferBytes, false); + buffer[0] = DESKTOP_MSG_ANNOUNCE_PATH_MOVED; + EsMemoryCopy(buffer + 1, &oldPathBytes, sizeof(uintptr_t)); + EsMemoryCopy(buffer + 1 + sizeof(uintptr_t), &newPathBytes, sizeof(uintptr_t)); + EsMemoryCopy(buffer + 1 + sizeof(uintptr_t) * 2, oldPath, oldPathBytes); + EsMemoryCopy(buffer + 1 + sizeof(uintptr_t) * 2 + oldPathBytes, newPath, newPathBytes); + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, bufferBytes, instance->window->handle, 0); + EsHeapFree(buffer); +} + +void EsApplicationRunTemporary(EsInstance *instance, const char *path, ptrdiff_t pathBytes) { + if (pathBytes == -1) pathBytes = EsCStringLength(path); + char *buffer = (char *) EsHeapAllocate(pathBytes + 1, false); + buffer[0] = DESKTOP_MSG_RUN_TEMPORARY_APPLICATION; + EsMemoryCopy(buffer + 1, path, pathBytes); + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, pathBytes + 1, instance->window->handle, 0); + EsHeapFree(buffer); +} + +void EsSystemShowShutdownDialog(EsInstance *instance) { + uint8_t message = DESKTOP_MSG_REQUEST_SHUTDOWN; + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) &message, 1, instance->window->handle, 0); +} + +void InstanceSave(EsInstance *_instance) { + APIInstance *instance = (APIInstance *) _instance->_private; + EsAssert(instance->instanceClass == ES_INSTANCE_CLASS_EDITOR); + size_t bufferBytes = instance->editorSettings.newDocumentFileNameBytes + 1; + char *buffer = (char *) EsHeapAllocate(bufferBytes, false); + buffer[0] = DESKTOP_MSG_REQUEST_SAVE; + EsMemoryCopy(buffer + 1, instance->editorSettings.newDocumentFileName, instance->editorSettings.newDocumentFileNameBytes); + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, bufferBytes, _instance->window->handle, 0); + EsHeapFree(buffer); +} + +void FileStoreCloseHandle(EsFileStore *fileStore) { + EsMessageMutexCheck(); // TODO Remove this limitation? + EsAssert(fileStore->handles < 0x80000000); + + if (--fileStore->handles) { + return; + } + + if (fileStore->type == FILE_STORE_HANDLE) { + if (fileStore->handle) { + EsHandleClose(fileStore->handle); + } + } else if (fileStore->type == FILE_STORE_PATH) { + // The path is stored after the file store allocation. + } + + EsHeapFree(fileStore); +} + +EsFileStore *FileStoreCreateFromPath(const char *path, size_t pathBytes) { + EsFileStore *fileStore = (EsFileStore *) EsHeapAllocate(sizeof(EsFileStore) + pathBytes, false); + EsMemoryZero(fileStore, sizeof(EsFileStore)); + fileStore->type = FILE_STORE_PATH; + fileStore->handles = 1; + fileStore->error = ES_SUCCESS; + fileStore->path = (char *) (fileStore + 1); + fileStore->pathBytes = pathBytes; + EsMemoryCopy(fileStore->path, path, pathBytes); + return fileStore; +} + +void InstanceCreateFileStore(APIInstance *instance, EsHandle handle) { + if (instance->fileStore) FileStoreCloseHandle(instance->fileStore); + instance->fileStore = (EsFileStore *) EsHeapAllocate(sizeof(EsFileStore), true); + instance->fileStore->type = FILE_STORE_HANDLE; + instance->fileStore->handle = handle; + instance->fileStore->error = ES_SUCCESS; + instance->fileStore->handles = 1; +} + +void InstancePostOpenMessage(EsInstance *_instance, bool update) { + APIInstance *instance = (APIInstance *) _instance->_private; + EsMessage m = { ES_MSG_INSTANCE_OPEN }; + m.instanceOpen.instance = _instance; + m.instanceOpen.name = instance->startupInformation->filePath; + m.instanceOpen.nameBytes = instance->startupInformation->filePathBytes; + m.instanceOpen.file = instance->fileStore; + m.instanceOpen.update = update; + EsMessagePost(nullptr, &m); +} + +APIInstance *InstanceSetup(EsInstance *instance) { + APIInstance *apiInstance = (APIInstance *) EsHeapAllocate(sizeof(APIInstance), true); + + instance->_private = apiInstance; + + instance->undoManager = &apiInstance->undoManager; + instance->undoManager->instance = instance; + apiInstance->activeUndoManager = instance->undoManager; + + EsCommandRegister(&apiInstance->commandDelete, instance, nullptr, ES_COMMAND_DELETE, "Del"); + EsCommandRegister(&apiInstance->commandSelectAll, instance, nullptr, ES_COMMAND_SELECT_ALL, "Ctrl+A"); + EsCommandRegister(&apiInstance->commandCopy, instance, nullptr, ES_COMMAND_COPY, "Ctrl+C|Ctrl+Ins"); + EsCommandRegister(&apiInstance->commandCut, instance, nullptr, ES_COMMAND_CUT, "Ctrl+X|Shift+Del"); + EsCommandRegister(&apiInstance->commandPaste, instance, nullptr, ES_COMMAND_PASTE, "Ctrl+V|Shift+Ins"); + EsCommandRegister(&apiInstance->commandUndo, instance, nullptr, ES_COMMAND_UNDO, "Ctrl+Z"); + EsCommandRegister(&apiInstance->commandRedo, instance, nullptr, ES_COMMAND_REDO, "Ctrl+Y"); + EsCommandRegister(&apiInstance->commandSave, instance, nullptr, ES_COMMAND_SAVE, "Ctrl+S"); + EsCommandRegister(&apiInstance->commandShowInFileManager, instance, nullptr, ES_COMMAND_SHOW_IN_FILE_MANAGER, "Ctrl+Shift+O"); + + EsCommandSetCallback(&apiInstance->commandUndo, [] (EsInstance *instance, EsElement *, EsCommand *) { + EsUndoInvokeGroup(((APIInstance *) instance->_private)->activeUndoManager, false); + }); + + EsCommandSetCallback(&apiInstance->commandRedo, [] (EsInstance *instance, EsElement *, EsCommand *) { + EsUndoInvokeGroup(((APIInstance *) instance->_private)->activeUndoManager, true); + }); + + EsCommandSetCallback(&apiInstance->commandSave, [] (EsInstance *instance, EsElement *, EsCommand *) { + InstanceSave(instance); + }); + + EsCommandSetCallback(&apiInstance->commandShowInFileManager, [] (EsInstance *instance, EsElement *, EsCommand *) { + char buffer[1]; + buffer[0] = DESKTOP_MSG_SHOW_IN_FILE_MANAGER; + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, 1, instance->window->handle, 0); + }); + + return apiInstance; +} + +EsInstance *_EsInstanceCreate(size_t bytes, EsMessage *message, const char *applicationName, ptrdiff_t applicationNameBytes) { + if (applicationNameBytes == -1) { + applicationNameBytes = EsCStringLength(applicationName); + } + + EsInstance *instance = (EsInstance *) EsHeapAllocate(bytes, true); + EsAssert(bytes >= sizeof(EsInstance)); + APIInstance *apiInstance = InstanceSetup(instance); + apiInstance->applicationName = applicationName; + apiInstance->applicationNameBytes = applicationNameBytes; + + if (message && message->createInstance.data != ES_INVALID_HANDLE && message->createInstance.dataBytes > 1) { + apiInstance->startupInformation = (EsApplicationStartupInformation *) EsHeapAllocate(message->createInstance.dataBytes, false); + + if (apiInstance->startupInformation) { + void *buffer = EsHeapAllocate(message->createInstance.dataBytes, false); + EsConstantBufferRead(message->createInstance.data, buffer); + EsMemoryCopy(apiInstance->startupInformation, (char *) buffer + 1, message->createInstance.dataBytes - 1); + EsHeapFree(buffer); + + if (!ApplicationStartupInformationParse(apiInstance->startupInformation, message->createInstance.dataBytes - 1)) { + EsHeapFree(apiInstance->startupInformation); + apiInstance->startupInformation = nullptr; + } else { + // Duplicate the file path, so that it can be modified in response to ES_MSG_INSTANCE_DOCUMENT_RENAMED messages. + char *filePath = (char *) EsHeapAllocate(apiInstance->startupInformation->filePathBytes, false); + EsMemoryCopy(filePath, apiInstance->startupInformation->filePath, apiInstance->startupInformation->filePathBytes); + apiInstance->startupInformation->filePath = filePath; + } + } + } + + apiInstance->mainWindowHandle = message->createInstance.window; + instance->window = EsWindowCreate(instance, ES_WINDOW_NORMAL); + EsWindowSetTitle(instance->window, nullptr, 0); + + if (apiInstance->startupInformation && apiInstance->startupInformation->readHandle) { + InstanceCreateFileStore(apiInstance, apiInstance->startupInformation->readHandle); + InstancePostOpenMessage(instance, false); + EsWindowSetTitle(instance->window, apiInstance->startupInformation->filePath, apiInstance->startupInformation->filePathBytes); + EsCommandSetDisabled(&apiInstance->commandShowInFileManager, false); + } + + return instance; +} + +const EsApplicationStartupInformation *EsInstanceGetStartupInformation(EsInstance *_instance) { + APIInstance *instance = (APIInstance *) _instance->_private; + return instance->startupInformation; +} + +void EsInstanceDestroy(EsInstance *_instance) { + UndoManagerDestroy(_instance->undoManager); + EsAssert(_instance->window->instance == _instance); + _instance->window->destroyInstanceAfterClose = true; + EsElementDestroy(_instance->window); +} + +EsInstance *InstanceFromWindowID(uint64_t id) { + for (uintptr_t i = 0; i < gui.allWindows.Length(); i++) { + if (EsSyscall(ES_SYSCALL_WINDOW_GET_ID, gui.allWindows[i]->handle, 0, 0, 0) == id) { + return gui.allWindows[i]->instance; + } + } + + return nullptr; +} + +EsError GetMessage(_EsMessageWithObject *message) { + // Process posted messages first, + // so that messages like ES_MSG_WINDOW_DESTROYED are received last. + + bool gotMessage = false; + EsMutexAcquire(&api.postBoxMutex); + + if (api.postBox.Length()) { + *message = api.postBox[0]; + api.postBox.Delete(0); + gotMessage = true; + } + + EsMutexRelease(&api.postBoxMutex); + if (gotMessage) return ES_SUCCESS; + + return EsSyscall(ES_SYSCALL_MESSAGE_GET, (uintptr_t) message, 0, 0, 0); +} + +EsMessage *EsMessageReceive() { + static _EsMessageWithObject message = {}; + EsMessageMutexCheck(); + + while (true) { + TS("Process message\n"); + + if (message.message.type == ES_MSG_INSTANCE_CREATE) { + if (message.message.createInstance.data != ES_INVALID_HANDLE) { + EsHandleClose(message.message.createInstance.data); + } + } else if (message.message.type == ES_MSG_INSTANCE_OPEN) { + // TODO Support multithreaded file operations. + EsAssert(message.message.instanceOpen.file->operationComplete); + } else if (message.message.type == ES_MSG_INSTANCE_SAVE) { + // TODO Support multithreaded file operations. + EsAssert(message.message.instanceSave.file->operationComplete); + FileStoreCloseHandle(message.message.instanceSave.file); + } else if (message.message.type == ES_MSG_APPLICATION_EXIT) { + EsProcessTerminateCurrent(); + } else if (message.message.type == ES_MSG_INSTANCE_DESTROY) { + APIInstance *instance = (APIInstance *) message.message.instanceDestroy.instance->_private; + + if (instance->startupInformation && (instance->startupInformation->flags & ES_APPLICATION_STARTUP_SINGLE_INSTANCE_IN_PROCESS)) { + EsMessage m = { ES_MSG_APPLICATION_EXIT }; + EsMessagePost(nullptr, &m); + } + + if (instance->startupInformation) { + EsHeapFree((void *) instance->startupInformation->filePath); + } + + EsHeapFree(instance->startupInformation); + EsHeapFree(instance->documentPath); + + for (uintptr_t i = 0; i < instance->commands.Count(); i++) { + EsCommand *command = instance->commands[i]; + EsAssert(command->registered); + EsAssert(!ArrayLength(command->elements)); + Array elements = { command->elements }; + elements.Free(); + } + + instance->commands.Free(false); + if (instance->fileStore) FileStoreCloseHandle(instance->fileStore); + EsHeapFree(instance); + EsHeapFree(message.message.instanceDestroy.instance); + } else if (message.message.type == ES_MSG_UNREGISTER_FILE_SYSTEM) { + for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) { + if (api.mountPoints[i].information.id == message.message.unregisterFileSystem.id) { + EsHandleClose(api.mountPoints[i].base); + api.mountPoints.Delete(i); + } + } + } + + EsMessageMutexRelease(); + + EsError error = GetMessage(&message); + + while (error != ES_SUCCESS) { + if (!gui.animationSleep && gui.animatingElements.Length()) { + EsMessageMutexAcquire(); + ProcessAnimations(); + EsMessageMutexRelease(); + } else { + EsSyscall(ES_SYSCALL_MESSAGE_WAIT, ES_WAIT_NO_TIMEOUT, 0, 0, 0); + } + + error = GetMessage(&message); + } + + EsMessageMutexAcquire(); + + EsMessageType type = message.message.type; + + if (type == ES_MSG_SYSTEM_CONSTANT_UPDATED) { + api.systemConstants[message.message.systemConstantUpdated.index] = message.message.systemConstantUpdated.newValue; + } + + if (type == ES_MSG_EYEDROP_REPORT) { + EsMessageSend((EsElement *) message.object, &message.message); + } else if (type == ES_MSG_TIMER) { + ((EsTimerCallbackFunction) message.message.user.context1.p)(message.message.user.context2); + } else if (type >= ES_MSG_WM_START && type <= ES_MSG_WM_END) { +#if 0 + ProcessMessageTiming timing = {}; + double start = EsTimeStampMs(); + UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, &timing); + EsPrint("Processed message from WM %x in %Fms (%Fms logic, %Fms layout, %Fms paint, %Fms update screen).\n", + type, EsTimeStampMs() - start, + timing.endLogic - timing.startLogic, + timing.endLayout - timing.startLayout, + timing.endPaint - timing.startPaint, + timing.endUpdate - timing.startUpdate); +#endif + + if (message.object) { + UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, nullptr); + } + } else if (type == ES_MSG_TAB_INSPECT_UI) { + EsInstance *_instance = InstanceFromWindowID(message.message.tabOperation.id); + + if (_instance) { + APIInstance *instance = (APIInstance *) _instance->_private; + + if (instance->attachedInspector) { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, instance->attachedInspector->window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED); + } else { + EsWindowCreate(_instance, ES_WINDOW_INSPECTOR); + } + } + } else if (type == ES_MSG_TAB_CLOSE_REQUEST) { + EsInstance *instance = InstanceFromWindowID(message.message.tabOperation.id); + if (instance) EsInstanceDestroy(instance); + } else if (type == ES_MSG_INSTANCE_SAVE_RESPONSE) { + EsMessage m = {}; + m.type = ES_MSG_INSTANCE_SAVE; + + m.instanceSave.file = (EsFileStore *) EsHeapAllocate(sizeof(EsFileStore), true); + m.instanceSave.file->error = message.message.tabOperation.error; + m.instanceSave.file->handle = message.message.tabOperation.handle; + m.instanceSave.file->type = FILE_STORE_HANDLE; + m.instanceSave.file->handles = 1; + m.instanceSave.instance = InstanceFromWindowID(message.message.tabOperation.id); + + if (m.instanceSave.file->error == ES_SUCCESS) { + EsMemoryCopy(&message.message, &m, sizeof(EsMessage)); + return &message.message; + } else { + EsInstanceSaveComplete(&m, false); + } + + EsMemoryCopy(&message.message, &m, sizeof(EsMessage)); + } else if (type == ES_MSG_INSTANCE_DOCUMENT_RENAMED) { + char *buffer = (char *) EsHeapAllocate(message.message.tabOperation.bytes, false); + EsConstantBufferRead(message.message.tabOperation.handle, buffer); + EsInstance *_instance = InstanceFromWindowID(message.message.tabOperation.id); + + if (_instance) { + APIInstance *instance = (APIInstance *) _instance->_private; + EsHeapFree((void *) instance->startupInformation->filePath); + instance->startupInformation->filePath = buffer; + instance->startupInformation->filePathBytes = message.message.tabOperation.bytes; + EsWindowSetTitle(_instance->window, buffer, message.message.tabOperation.bytes); + } else { + EsHeapFree(buffer); + } + + EsHandleClose(message.message.tabOperation.handle); + } else if (type == ES_MSG_INSTANCE_DOCUMENT_UPDATED) { + EsInstance *_instance = InstanceFromWindowID(message.message.tabOperation.id); + + if (_instance) { + APIInstance *instance = (APIInstance *) _instance->_private; + + InstanceCreateFileStore(instance, message.message.tabOperation.handle); + + if (!message.message.tabOperation.isSource) { + InstancePostOpenMessage(_instance, true); + } + } else { + EsHandleClose(message.message.tabOperation.handle); + } + } else if (type == ES_MSG_REGISTER_FILE_SYSTEM) { + EsMessageRegisterFileSystem *m = &message.message.registerFileSystem; + + int64_t index = 1; // TODO This is incorrect! + + while (true) { + bool seen = false; + + for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) { + if (index == EsIntegerParse(api.mountPoints[i].prefix, api.mountPoints[i].prefixBytes)) { + seen = true; + break; + } + } + + if (seen) { + index++; + } else { + break; + } + } + + bool isBootFileSystem = m->isBootFileSystem; + char prefix[16]; + size_t prefixBytes = EsStringFormat(prefix, sizeof(prefix), "%fd:", ES_STRING_FORMAT_SIMPLE, isBootFileSystem ? 0 : index); + + m->mountPoint = NodeAddMountPoint(prefix, prefixBytes, m->rootDirectory, true); + + if (isBootFileSystem) { + api.foundBootFileSystem = true; + } + + return &message.message; + } else if (type == ES_MSG_UNREGISTER_FILE_SYSTEM) { + for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) { + if (api.mountPoints[i].information.id == message.message.unregisterFileSystem.id) { + message.message.unregisterFileSystem.mountPoint = &api.mountPoints[i]; + api.mountPoints[i].removing = true; + return &message.message; + } + } + } else { + return &message.message; + } + } +} + +void EsInstanceOpenComplete(EsMessage *message, bool success, const char *errorText, ptrdiff_t errorTextBytes) { + EsInstance *instance = message->instanceOpen.instance; + + if (!success || message->instanceOpen.file->error != ES_SUCCESS) { + if (errorTextBytes) { + EsDialogShowAlert(instance->window, INTERFACE_STRING(FileCannotOpen), + errorText, errorTextBytes, + ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); + } else { + const char *errorMessage = interfaceString_FileLoadErrorUnknown; + + switch (message->instanceOpen.file->error) { + case ES_ERROR_DRIVE_ERROR_FILE_DAMAGED: + errorMessage = interfaceString_FileLoadErrorCorrupt; + break; + case ES_ERROR_DRIVE_CONTROLLER_REPORTED: + errorMessage = interfaceString_FileLoadErrorDrive; + break; + case ES_ERROR_INSUFFICIENT_RESOURCES: + errorMessage = interfaceString_FileLoadErrorResourcesLow; + break; + } + + EsDialogShowAlert(instance->window, INTERFACE_STRING(FileCannotOpen), + errorMessage, -1, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); + } + + // TODO Close the instance. + } else { + if (!message->instanceOpen.update) { + EsUndoClear(instance->undoManager); + } + + EsCommandSetDisabled(EsCommandByID(instance, ES_COMMAND_SAVE), true); + } + + EsAssert(!message->instanceOpen.file->operationComplete); + message->instanceOpen.file->operationComplete = true; +} + +void EsInstanceSaveComplete(EsMessage *message, bool success) { + if (message->instanceSave.file->error != ES_SUCCESS) { + success = false; + } + + if (success) { + message->instanceSave.file->error = EsFileControl(message->instanceSave.file->handle, + ES_FILE_CONTROL_NOTIFY_MONITORS | ES_FILE_CONTROL_FLUSH); + + if (message->instanceSave.file->error != ES_SUCCESS) { + success = false; + } + } + + EsInstance *instance = message->instanceSave.instance; + APIInstance *apiInstance = (APIInstance *) instance->_private; + + if (instance) { + char buffer[1]; + buffer[0] = DESKTOP_MSG_COMPLETE_SAVE; + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, 1, instance->window->handle, 0); + + if (success) { + EsCommandSetDisabled(EsCommandByID(instance, ES_COMMAND_SAVE), true); + EsRectangle bounds = EsElementGetWindowBounds(instance->window->toolbarSwitcher); + size_t messageBytes; + char *message = EsStringAllocateAndFormat(&messageBytes, "Saved to %s", // TODO Localization. + apiInstance->startupInformation->filePathBytes, apiInstance->startupInformation->filePath); + EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, bounds.b, message, messageBytes); + EsHeapFree(message); + } else { + const char *errorMessage = interfaceString_FileSaveErrorUnknown; + + switch (message->instanceSave.file->error) { + case ES_ERROR_FILE_DOES_NOT_EXIST: + case ES_ERROR_NODE_DELETED: + case ES_ERROR_FILE_PERMISSION_NOT_GRANTED: + case ES_ERROR_INCORRECT_NODE_TYPE: + errorMessage = interfaceString_FileSaveErrorFileDeleted; + break; + case ES_ERROR_DRIVE_ERROR_FILE_DAMAGED: + errorMessage = interfaceString_FileSaveErrorCorrupt; + break; + case ES_ERROR_DRIVE_CONTROLLER_REPORTED: + errorMessage = interfaceString_FileSaveErrorDrive; + break; + case ES_ERROR_COULD_NOT_RESIZE_FILE: + case ES_ERROR_FILE_TOO_FRAGMENTED: + errorMessage = interfaceString_FileSaveErrorTooLarge; + break; + case ES_ERROR_FILE_IN_EXCLUSIVE_USE: + case ES_ERROR_FILE_CANNOT_GET_EXCLUSIVE_USE: + case ES_ERROR_FILE_HAS_WRITERS: + errorMessage = interfaceString_FileSaveErrorConcurrentAccess; + break; + case ES_ERROR_DRIVE_FULL: + errorMessage = interfaceString_FileSaveErrorDriveFull; + break; + case ES_ERROR_INSUFFICIENT_RESOURCES: + errorMessage = interfaceString_FileSaveErrorResourcesLow; + break; + case ES_ERROR_FILE_ALREADY_EXISTS: + errorMessage = interfaceString_FileSaveErrorAlreadyExists; + break; + } + + EsDialogShowAlert(instance->window, INTERFACE_STRING(FileCannotSave), + errorMessage, -1, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON); + } + } + + EsAssert(!message->instanceSave.file->operationComplete); + message->instanceSave.file->operationComplete = true; +} + +uint64_t EsSystemGetConstant(uintptr_t index) { + return api.systemConstants[index]; +} + +void ThreadInitialise() { + // TODO Memory leak: this structure is never freed. + // Perhaps the kernel should send a message when a user thread is terminated to its owner process? + + ThreadLocalStorage *local = (ThreadLocalStorage *) EsHeapAllocate(sizeof(ThreadLocalStorage), true); + local->id = EsSyscall(ES_SYSCALL_THREAD_GET_ID, ES_CURRENT_THREAD, 0, 0, 0); + local->self = local; + EsSyscall(ES_SYSCALL_PROCESS_SET_TLS, (uintptr_t) local - tlsStorageOffset, 0, 0, 0); +} + +#include "desktop.cpp" + +extern "C" void _start(EsProcessStartupInformation *_startupInformation) { + api.startupInformation = _startupInformation; + bool desktop = api.startupInformation->isDesktop; + +#ifndef NO_API_TABLE + if (desktop) { + // Initialise the API table. + + EsAssert(sizeof(apiTable) <= 0xF000); // API table is too large. + EsMemoryCopy(ES_API_BASE, apiTable, sizeof(apiTable)); + } +#endif + + { + // Initialise the API. + + _init(); + EsSyscall(ES_SYSCALL_SYSTEM_GET_CONSTANTS, (uintptr_t) api.systemConstants, 0, 0, 0); + EsRandomSeed(EsTimeStamp()); + ThreadInitialise(); + EsMessageMutexAcquire(); + + api.global = (GlobalData *) EsObjectMap(EsMemoryOpen(sizeof(GlobalData), EsLiteral("Desktop.Global"), ES_FLAGS_DEFAULT), + 0, sizeof(GlobalData), desktop ? ES_MAP_OBJECT_READ_WRITE : ES_MAP_OBJECT_READ_ONLY); + } + + bool uiProcess = false; + + if (desktop) { + EsPrint("Reached Desktop process.\n"); + + uiProcess = true; + + // Process messages until we find the boot file system. + + while (!api.foundBootFileSystem) { + EsMessage *message = EsMessageReceive(); + DesktopMessage(message); + } + + size_t fileSize; + void *file = EsFileReadAll(K_SYSTEM_CONFIGURATION, -1, &fileSize); + EsAssert(file); + SystemConfigurationLoad((char *) file, fileSize); + + _EsNodeInformation node; + char *path; + + path = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("fonts_path")); + NodeOpen(path, EsCStringLength(path), ES_FLAGS_DEFAULT, &node); + NodeAddMountPoint(EsLiteral("|Fonts:"), node.handle, false); + EsHeapFree(path); + + path = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("themes_path")); + NodeOpen(path, EsCStringLength(path), ES_FLAGS_DEFAULT, &node); + NodeAddMountPoint(EsLiteral("|Themes:"), node.handle, false); + EsHeapFree(path); + + api.global->clickChainTimeoutMs = EsIntegerParse(EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("click_chain_timeout_ms")), -1); + } else { + EsHandle initialMountPointsBuffer = EsSyscall(ES_SYSCALL_PROCESS_GET_CREATION_ARGUMENT, ES_CURRENT_PROCESS, CREATION_ARGUMENT_INITIAL_MOUNT_POINTS, 0, 0); + size_t initialMountPointCount = EsConstantBufferGetSize(initialMountPointsBuffer) / sizeof(EsMountPoint); + EsMountPoint *initialMountPoints = (EsMountPoint *) EsHeapAllocate(initialMountPointCount * sizeof(EsMountPoint), false); + EsConstantBufferRead(initialMountPointsBuffer, initialMountPoints); + + for (uintptr_t i = 0; i < initialMountPointCount; i++) { + NodeAddMountPoint(initialMountPoints[i].prefix, initialMountPoints[i].prefixBytes, initialMountPoints[i].base, true); + + if (0 == EsStringCompareRaw(initialMountPoints[i].prefix, initialMountPoints[i].prefixBytes, EsLiteral("|Themes:"))) { + uiProcess = true; + } + } + + EsHeapFree(initialMountPoints); + EsHandleClose(initialMountPointsBuffer); + + size_t bytes; + EsHandle handle = EsSyscall(ES_SYSCALL_SYSTEM_CONFIGURATION_READ, 0, 0, (uintptr_t) &bytes, 0); + EsAssert(handle); + char *buffer = (char *) EsHeapAllocate(bytes, false); + EsConstantBufferRead(handle, buffer); + EsHandleClose(handle); + SystemConfigurationLoad(buffer, bytes); + } + + if (uiProcess) { + // Initialise the GUI. + + theming.scale = api.systemConstants[ES_SYSTEM_CONSTANT_UI_SCALE] / 100.0f; + + size_t pathBytes, fileBytes; + char *path = EsSystemConfigurationReadString(EsLiteral("ui"), EsLiteral("theme"), &pathBytes); + void *file = EsFileMap(path, pathBytes, &fileBytes, ES_MAP_OBJECT_READ_ONLY); + EsAssert(ThemeLoadData(file, fileBytes)); + EsHeapFree(path); + + theming.cursors.width = ES_THEME_CURSORS_WIDTH; + theming.cursors.height = ES_THEME_CURSORS_HEIGHT; + theming.cursors.stride = ES_THEME_CURSORS_WIDTH * 4; + theming.cursors.bits = EsObjectMap(EsMemoryOpen(theming.cursors.height * theming.cursors.stride, EsLiteral(ES_THEME_CURSORS_NAME), 0), + 0, ES_MAP_OBJECT_ALL, ES_MAP_OBJECT_READ_ONLY); + theming.cursors.fullAlpha = true; + theming.cursors.readOnly = true; + } + + if (desktop) { + DesktopEntry(); + } else { + ((StartFunction) api.startupInformation->applicationStartAddress)(); + } + + EsThreadTerminate(ES_CURRENT_THREAD); +} + +void EsPanic(const char *cFormat, ...) { + char buffer[256]; + va_list arguments; + va_start(arguments, cFormat); + size_t bytes = EsStringFormatV(buffer, sizeof(buffer), cFormat, arguments); + va_end(arguments); + EsPrintDirect(buffer, bytes); + EsSyscall(ES_SYSCALL_PROCESS_CRASH, 0, 0, 0, 0); +} + +void EsAssertionFailure(const char *cFile, int line) { + EsPanic("Assertion failure at %z:%d.\n", cFile, line); +} + +void EsUnimplemented() { + EsAssert(false); +} + +void EsCommandAddButton(EsCommand *command, EsButton *button) { + EsAssert(command->registered); // Command has not been registered. + Array elements = { command->elements }; + elements.Add(button); + command->elements = elements.array; + EsButtonOnCommand(button, command->callback, command); + button->state |= UI_STATE_COMMAND_BUTTON; + EsElementSetDisabled(button, command->disabled); + EsButtonSetCheck(button, command->check); +} + +EsCommand *EsCommandRegister(EsCommand *command, EsInstance *_instance, EsCommandCallbackFunction callback, uint32_t stableID, + const char *cDefaultKeyboardShortcut, bool enabled) { + if (!command) { + command = (EsCommand *) EsHeapAllocate(sizeof(EsCommand), true); + command->allocated = true; + } + + APIInstance *instance = (APIInstance *) _instance->_private; + command->callback = callback; + command->registered = true; + command->stableID = stableID; + command->cKeyboardShortcut = cDefaultKeyboardShortcut; + command->disabled = !enabled; + EsAssert(!instance->commands.Get(&stableID)); // Command already registered. + *instance->commands.Put(&stableID) = command; + return command; +} + +void EsCommandSetDisabled(EsCommand *command, bool disabled) { + EsAssert(command->registered); // Command has not been registered. + + if (disabled != command->disabled) { + command->disabled = disabled; + + for (uintptr_t i = 0; i < ArrayLength(command->elements); i++) { + EsElementSetDisabled(command->elements[i], disabled); + } + } +} + +void EsCommandSetCheck(EsCommand *command, EsCheckState check, bool sendUpdatedMessage) { + EsAssert(command->registered); // Command has not been registered. + + if (check != command->check) { + command->check = check; + + for (uintptr_t i = 0; i < ArrayLength(command->elements); i++) { + EsButtonSetCheck((EsButton *) command->elements[i], check, sendUpdatedMessage); + } + } +} + +void EsCommandSetCallback(EsCommand *command, EsCommandCallbackFunction callback) { + EsAssert(command->registered); // Command has not been registered. + + if (callback != command->callback) { + command->callback = callback; + + for (uintptr_t i = 0; i < ArrayLength(command->elements); i++) { + if (command->elements[i]->state & UI_STATE_COMMAND_BUTTON) { + EsButtonOnCommand((EsButton *) command->elements[i], callback, command); + } + } + } + + if (!callback) { + EsCommandSetDisabled(command, true); + } +} + +EsCommand *EsCommandByID(EsInstance *_instance, uint32_t id) { + APIInstance *instance = (APIInstance *) _instance->_private; + EsCommand *command = instance->commands.Get1(&id); + EsAssert(command); // Invalid command ID. + return command; +} + +void EsPerformanceTimerPush() { + EsSpinlockAcquire(&api.performanceTimerStackLock); + + if (api.performanceTimerStackCount < PERFORMANCE_TIMER_STACK_SIZE) { + api.performanceTimerStack[api.performanceTimerStackCount++] = EsTimeStamp(); + } + + EsSpinlockRelease(&api.performanceTimerStackLock); +} + +double EsPerformanceTimerPop() { + double result = 0; + EsSpinlockAcquire(&api.performanceTimerStackLock); + + if (api.performanceTimerStackCount) { + uint64_t start = api.performanceTimerStack[--api.performanceTimerStackCount]; + result = ((double) (EsTimeStamp() - start) / (double) (api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND])) / 1000000.0; + } + + EsSpinlockRelease(&api.performanceTimerStackLock); + return result; +} + +uint32_t EsIconIDFromString(const char *string, ptrdiff_t stringBytes) { + if (!string) { + return 0; + } + + for (uintptr_t i = 0; i < sizeof(enumStrings_EsStandardIcon) / sizeof(enumStrings_EsStandardIcon[0]) - 1; i++) { + if (0 == EsStringCompare(enumStrings_EsStandardIcon[i].cName + 3, -1, string, stringBytes)) { + return i; + } + } + + return 0; +} + +struct UndoItemFooter { + EsUndoCallback callback; + ptrdiff_t bytes; + bool endOfGroup; +}; + +bool EsUndoPeek(EsUndoManager *manager, EsUndoCallback *callback, const void **item) { + EsMessageMutexCheck(); + Array *stack = manager->state == UNDO_MANAGER_STATE_UNDOING ? &manager->redoStack : &manager->undoStack; + + if (!stack->Length()) { + return false; + } + + UndoItemFooter *footer = (UndoItemFooter *) (stack->array + stack->Length()) - 1; + *callback = footer->callback; + *item = (uint8_t *) footer - footer->bytes; + return true; +} + +void EsUndoPop(EsUndoManager *manager) { + EsMessageMutexCheck(); + EsInstanceSetActiveUndoManager(manager->instance, manager); + Array *stack = manager->state == UNDO_MANAGER_STATE_UNDOING ? &manager->redoStack : &manager->undoStack; + size_t oldLength = stack->Length(); + EsAssert(oldLength); + UndoItemFooter *footer = (UndoItemFooter *) (stack->array + stack->Length()) - 1; + EsMessage m = {}; + m.type = ES_MSG_UNDO_CANCEL; + footer->callback((uint8_t *) footer - footer->bytes, manager, &m); + stack->SetLength(oldLength - footer->bytes - sizeof(UndoItemFooter)); + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_UNDO), !manager->undoStack.Length()); + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_REDO), !manager->redoStack.Length()); +} + +void EsUndoClear(EsUndoManager *manager) { + Array *stack = manager->state == UNDO_MANAGER_STATE_UNDOING ? &manager->redoStack : &manager->undoStack; + + while (stack->Length()) { + EsUndoPop(manager); + } +} + +void EsUndoPush(EsUndoManager *manager, EsUndoCallback callback, const void *item, size_t itemBytes) { + EsMessageMutexCheck(); + EsInstanceSetActiveUndoManager(manager->instance, manager); + + Array *stack = manager->state == UNDO_MANAGER_STATE_UNDOING ? &manager->redoStack : &manager->undoStack; + + UndoItemFooter footer = {}; + footer.callback = callback; + footer.bytes = (itemBytes + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); + + size_t oldLength = stack->Length(); + stack->SetLength(footer.bytes + sizeof(footer) + oldLength); + EsMemoryCopy(stack->array + oldLength, item, itemBytes); + EsMemoryCopy(stack->array + oldLength + footer.bytes, &footer, sizeof(footer)); + + if (manager->state == UNDO_MANAGER_STATE_NORMAL) { + // Clear the redo stack. + manager->state = UNDO_MANAGER_STATE_UNDOING; + EsUndoClear(manager); + manager->state = UNDO_MANAGER_STATE_NORMAL; + } + + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_UNDO), !manager->undoStack.Length()); + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_REDO), !manager->redoStack.Length()); + + if (manager->instance->undoManager == manager) { + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_SAVE), false); + } +} + +void EsUndoContinueGroup(EsUndoManager *manager) { + EsMessageMutexCheck(); + EsAssert(manager->state == UNDO_MANAGER_STATE_NORMAL); + Array stack = manager->undoStack; + if (!stack.Length()) return; + UndoItemFooter *footer = (UndoItemFooter *) (stack.array + stack.Length()) - 1; + footer->endOfGroup = false; +} + +void EsUndoEndGroup(EsUndoManager *manager) { + EsMessageMutexCheck(); + EsAssert(manager->state == UNDO_MANAGER_STATE_NORMAL); + Array stack = manager->undoStack; + if (!stack.Length()) return; + UndoItemFooter *footer = (UndoItemFooter *) (stack.array + stack.Length()) - 1; + footer->endOfGroup = true; +} + +bool EsUndoIsEmpty(EsUndoManager *manager, bool redo) { + EsMessageMutexCheck(); + EsAssert(manager->state == UNDO_MANAGER_STATE_NORMAL); + Array stack = redo ? manager->redoStack : manager->undoStack; + return stack.Length() == 0; +} + +void EsUndoInvokeGroup(EsUndoManager *manager, bool redo) { + EsMessageMutexCheck(); + EsInstanceSetActiveUndoManager(manager->instance, manager); + EsAssert(manager->state == UNDO_MANAGER_STATE_NORMAL); + manager->state = redo ? UNDO_MANAGER_STATE_REDOING : UNDO_MANAGER_STATE_UNDOING; + + Array *stack = redo ? &manager->redoStack : &manager->undoStack; + EsAssert(stack->Length()); + bool first = true; + + while (stack->Length()) { + size_t oldLength = stack->Length(); + UndoItemFooter *footer = (UndoItemFooter *) (stack->array + oldLength) - 1; + + if (!first && footer->endOfGroup) break; + first = false; + + EsMessage m = {}; + m.type = ES_MSG_UNDO_INVOKE; + footer->callback((uint8_t *) footer - footer->bytes, manager, &m); + stack->SetLength(oldLength - footer->bytes - sizeof(UndoItemFooter)); + } + + { + Array stack = redo ? manager->undoStack : manager->redoStack; + EsAssert(stack.Length()); + UndoItemFooter *footer = (UndoItemFooter *) (stack.array + stack.Length()) - 1; + footer->endOfGroup = true; + } + + manager->state = UNDO_MANAGER_STATE_NORMAL; + + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_UNDO), !manager->undoStack.Length()); + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_REDO), !manager->redoStack.Length()); +} + +bool EsUndoInUndo(EsUndoManager *manager) { + EsMessageMutexCheck(); + return manager->state != UNDO_MANAGER_STATE_NORMAL; +} + +void UndoManagerDestroy(EsUndoManager *manager) { + EsMessageMutexCheck(); + + EsAssert(manager->state == UNDO_MANAGER_STATE_NORMAL); + EsUndoClear(manager); + manager->state = UNDO_MANAGER_STATE_UNDOING; + EsUndoClear(manager); + manager->undoStack.Free(); + manager->redoStack.Free(); + manager->state = UNDO_MANAGER_STATE_NORMAL; + + if (((APIInstance *) manager->instance->_private)->activeUndoManager == manager) { + EsInstanceSetActiveUndoManager(manager->instance, manager->instance->undoManager); + } +} + +EsInstance *EsUndoGetInstance(EsUndoManager *manager) { + return manager->instance; +} + +void EsInstanceSetActiveUndoManager(EsInstance *_instance, EsUndoManager *manager) { + EsMessageMutexCheck(); + APIInstance *instance = (APIInstance *) _instance->_private; + + if (instance->activeUndoManager == manager) { + return; + } + + EsUndoEndGroup(instance->activeUndoManager); + instance->activeUndoManager = manager; + + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_UNDO), !manager->undoStack.Length()); + EsCommandSetDisabled(EsCommandByID(manager->instance, ES_COMMAND_REDO), !manager->redoStack.Length()); +} + +const void *EsEmbeddedFileGet(const char *cName, size_t *byteCount) { + uint64_t name = CalculateCRC64(cName, EsCStringLength(cName)); + + const BundleHeader *header = (const BundleHeader *) BUNDLE_FILE_MAP_ADDRESS; + const BundleFile *files = (const BundleFile *) (header + 1); + + if (header->signature != BUNDLE_SIGNATURE) { + return nullptr; + } + + for (uintptr_t i = 0; i < header->fileCount; i++) { + if (files[i].nameCRC64 == name) { + if (byteCount) { + *byteCount = files[i].bytes; + } + + return (const uint8_t *) header + files[i].offset; + } + } + + return nullptr; +} + +void TimersThread(EsGeneric) { + // TODO Maybe terminate this thread after ~10 seconds of no timers? + + double oldTimeMs = EsTimeStampMs(); + + while (true) { + double newTimeMs = EsTimeStampMs(); + double deltaTimeMs = newTimeMs - oldTimeMs; + oldTimeMs = newTimeMs; + + int64_t waitFor = ES_WAIT_NO_TIMEOUT; + + EsMutexAcquire(&api.timersMutex); + + for (uintptr_t i = 0; i < api.timers.Length(); i++) { + Timer *timer = &api.timers[i]; + timer->afterMs -= deltaTimeMs; // Update time remaining. + + if (timer->afterMs <= 0) { + EsMessage m = { ES_MSG_TIMER }; + m.user.context1.p = (void *) timer->callback; + m.user.context2 = timer->argument; + EsMessagePost(nullptr, &m); + api.timers.DeleteSwap(i); + i--; + } else if (waitFor == ES_WAIT_NO_TIMEOUT || timer->afterMs < waitFor) { + waitFor = timer->afterMs; + } + } + + EsMutexRelease(&api.timersMutex); + + EsWait(&api.timersEvent /* timer set/canceled */, 1, waitFor /* until the next timer */); + } +} + +EsTimer EsTimerSet(uint64_t afterMs, EsTimerCallbackFunction callback, EsGeneric argument) { + EsMutexAcquire(&api.timersMutex); + + static EsTimer _id = 0; + EsTimer id = ++_id; + + Timer timer = { .id = id, .afterMs = (double) afterMs, .callback = callback, .argument = argument }; + + if (!api.timers.Add(timer)) { + id = 0; // Insufficient resources. + } else { + if (!api.timersEvent) { + api.timersEvent = EsEventCreate(true); + } + + if (!api.timersThread) { + EsThreadInformation information; + api.timersThread = EsThreadCreate(TimersThread, &information, 0); + } + + EsEventSet(api.timersEvent); // Wake up the timer thread. + } + + EsMutexRelease(&api.timersMutex); + + return id; +} + +void EsTimerCancel(EsTimer id) { + EsMutexAcquire(&api.timersMutex); + + for (uintptr_t i = 0; i < api.timers.Length(); i++) { + if (api.timers[i].id == id) { + api.timers.DeleteSwap(i); + + if (api.timersEvent) { + EsEventSet(api.timersEvent); // Wake up the timer thread. + } + + break; + } + } + + EsMutexRelease(&api.timersMutex); +} + +#ifndef ENABLE_POSIX_SUBSYSTEM +void EsPOSIXInitialise(int *, char ***) { + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + EsInstance *instance = EsInstanceCreate(message, INTERFACE_STRING(POSIXTitle)); + EsPanel *panel = EsPanelCreate((EsElement *) instance->window, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND); + EsTextDisplayCreate(panel, ES_CELL_H_CENTER | ES_CELL_V_FILL | ES_TEXT_DISPLAY_RICH_TEXT, nullptr, INTERFACE_STRING(POSIXUnavailable)); + } + } +} + +long EsPOSIXSystemCall(long, long, long, long, long, long, long) { + EsAssert(false); + return 0; +} + +char *EsPOSIXConvertPath(const char *, size_t *, bool) { + EsAssert(false); + return nullptr; +} +#else +EsProcessStartupInformation *ProcessGetStartupInformation() { + return api.startupInformation; +} +#endif diff --git a/desktop/api.s b/desktop/api.s new file mode 100644 index 0000000..bb08f0b --- /dev/null +++ b/desktop/api.s @@ -0,0 +1,99 @@ +[section .text] + +[global _APISyscall] +_APISyscall: + push rbp + push rbx + push r15 + push r14 + push r13 + push r12 + push r11 + push rcx + mov r12,rsp + syscall + mov rsp,r12 + pop rcx + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + pop rbx + pop rbp + ret + +[global _EsCRTsetjmp] +_EsCRTsetjmp: + mov [rdi + 0x00],rsp + mov [rdi + 0x08],rbp + mov [rdi + 0x10],rbx + mov [rdi + 0x18],r12 + mov [rdi + 0x20],r13 + mov [rdi + 0x28],r14 + mov [rdi + 0x30],r15 + mov rax,[rsp] + mov [rdi + 0x38],rax + xor rax,rax + ret + +[global _EsCRTlongjmp] +_EsCRTlongjmp: + mov rsp,[rdi + 0x00] + mov rbp,[rdi + 0x08] + mov rbx,[rdi + 0x10] + mov r12,[rdi + 0x18] + mov r13,[rdi + 0x20] + mov r14,[rdi + 0x28] + mov r15,[rdi + 0x30] + mov rax,[rdi + 0x38] + mov [rsp],rax + mov rax,rsi + cmp rax,0 + jne .return + mov rax,1 + .return: + ret + +[global EsTimeStamp] +EsTimeStamp: + rdtsc + shl rdx,32 + or rax,rdx + ret + +[global EsCRTsqrt] +EsCRTsqrt: + sqrtsd xmm0,xmm0 + ret + +[global EsCRTsqrtf] +EsCRTsqrtf: + sqrtss xmm0,xmm0 + ret + +[global ProcessorCheckStackAlignment] +ProcessorCheckStackAlignment: + mov rax,rsp + and rax,15 + cmp rax,8 + jne $ + ret + +[global ProcessorTLSRead] +ProcessorTLSRead: + mov rax,[fs:rdi] + ret + +[global ProcessorTLSWrite] +ProcessorTLSWrite: + mov [fs:rdi],rsi + ret + +[global __cyg_profile_func_enter] +__cyg_profile_func_enter: + ret + +[global __cyg_profile_func_exit] +__cyg_profile_func_exit: + ret diff --git a/desktop/crt1.c b/desktop/crt1.c new file mode 100644 index 0000000..85a2897 --- /dev/null +++ b/desktop/crt1.c @@ -0,0 +1,12 @@ +#include + +int main(int argc, char **argv, char **envp); +int __libc_start_main(int (*main)(int, char **, char **), int argc, char **argv); + +void _start() { + int argc; + char **argv; + EsPOSIXInitialise(&argc, &argv); + __libc_start_main(main, argc, argv); +} + diff --git a/desktop/crtglue.c b/desktop/crtglue.c new file mode 100644 index 0000000..c7d7bd9 --- /dev/null +++ b/desktop/crtglue.c @@ -0,0 +1,5 @@ +#include + +long OSMakeLinuxSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long a6) { + return EsPOSIXSystemCall(n, a1, a2, a3, a4, a5, a6); +} diff --git a/desktop/crti.s b/desktop/crti.s new file mode 100644 index 0000000..0b37e73 --- /dev/null +++ b/desktop/crti.s @@ -0,0 +1,11 @@ +[section .init] +[global _init] +_init: + push rbp + mov rbp, rsp + +[section .fini] +[global _fini] +_fini: + push rbp + mov rbp, rsp diff --git a/desktop/crtn.s b/desktop/crtn.s new file mode 100644 index 0000000..fccb715 --- /dev/null +++ b/desktop/crtn.s @@ -0,0 +1,7 @@ +[section .init] + pop rbp + ret + +[section .fini] + pop rbp + ret diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp new file mode 100644 index 0000000..bd572f6 --- /dev/null +++ b/desktop/desktop.cpp @@ -0,0 +1,1933 @@ +// TODO Tabs: +// - Dragging out of the window. +// - Dragging onto other windows. +// - Keyboard shortcuts. +// - New tab page - search; recent files. +// - Right click menu. +// - Duplicate tabs. +// - If a process exits, its tabs won't close because Desktop has a handle. +// - Also clear any OpenDocument currentWriter fields it owns. + +// TODO Graphical issues: +// - New tab button isn't flush with right border when tab band full. +// - Cursor doesn't update after switching embed window owners. +// - Closing tabs isn't animating. +// - Inactivate windows don't dim outline around tabs. +// - Resizing windows doesn't redraw old shadow sometimes. + +// TODO Task bar: +// - Right click menu. +// - Notification area. + +// TODO Desktop experience: +// - Alt+tab. +// - Changing wallpaper. +// +// TODO Global shortcuts: +// - Restoring closed tabs. +// - Switch to window. +// - Print screen. + +// TODO Only let File Manager read the file_type sections of the system configuration. +// TODO Maybe extend tab hitbox to top of window when maximised? Might need a visual change too. +// TODO Restarting Desktop if it crashes. +// TODO Make sure applications can't delete |Fonts: and |Themes:. + +#define MSG_SETUP_DESKTOP_UI ((EsMessageType) (ES_MSG_USER_START + 1)) + +#define APPLICATION_PERMISSION_ALL_FILES (1 << 0) +#define APPLICATION_PERMISSION_MANAGE_PROCESSES (1 << 1) +#define APPLICATION_PERMISSION_POSIX_SUBSYSTEM (1 << 2) +#define APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION (1 << 3) +#define APPLICATION_PERMISSION_SHUTDOWN (1 << 4) + +#define APPLICATION_ID_DESKTOP_BLANK_TAB ((int64_t) 0x8000000000000000L) + +#define CRASHED_TAB_FATAL_ERROR (1) +#define CRASHED_TAB_PROGRAM_NOT_FOUND (2) +#define CRASHED_TAB_INVALID_EXECUTABLE (3) +#define CRASHED_TAB_NOT_RESPONDING (4) + +#define INSTALLATION_STATE_NONE (0) +#define INSTALLATION_STATE_INSTALLER (1) + +struct ReorderItem : EsElement { + double sizeProgress, sizeTarget; + double offsetProgress, offsetTarget; + bool dragging; + int dragOffset, dragPosition; +}; + +struct ReorderList : EsElement { + int targetWidth, extraWidth; + Array items; // By index. +}; + +struct WindowTab : ReorderItem { + struct ContainerWindow *container; + struct ApplicationInstance *applicationInstance; + EsButton *closeButton; +}; + +struct WindowTabBand : ReorderList { + struct ContainerWindow *container; + bool preventNextTabSizeAnimation; +}; + +struct TaskBarButton : ReorderItem { + ContainerWindow *containerWindow; +}; + +struct TaskList : ReorderList { +}; + +struct TaskBar : EsElement { + double enterProgress; + EsRectangle targetBounds; + TaskList taskList; +}; + +struct ContainerWindow { + WindowTabBand *tabBand; + TaskBarButton *taskBarButton; + EsWindow *window; + WindowTab *active; +}; + +struct OpenDocument { + char *path; + size_t pathBytes; + char *temporarySavePath; + size_t temporarySavePathBytes; + EsHandle readHandle; + uint64_t id; + uint64_t currentWriter; +}; + +struct InstalledApplication { + char *cName; + char *cExecutable; + int64_t id; + uint32_t iconID; + bool hidden, useSingleProcess, temporary; + uint64_t permissions; + size_t openInstanceCount; // Only used if useSingleProcess is true. + EsHandle singleProcessHandle; +}; + +struct CrashedTabInstance : EsInstance { +}; + +struct BlankTabInstance : EsInstance { +}; + +struct ApplicationInstance { + WindowTab *tab; + EsInstance *localInstance; + InstalledApplication *application; + uint64_t documentID; + + char title[128]; + size_t titleBytes; + uint32_t iconID; + + uint64_t processID; + EsHandle processHandle; + uint64_t embeddedWindowID; + EsHandle embeddedWindowHandle; + + bool notResponding; + EsHandle restoreEmbeddedWindowHandle; + uint64_t restoreEmbeddedWindowID; +}; + +const EsStyle styleNewTabContent = { + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR, + .insets = ES_RECT_4(50, 50, 50, 50), + .gapMajor = 30, + }, +}; + +const EsStyle styleButtonGroupContainer = { + .inherit = ES_STYLE_BUTTON_GROUP_CONTAINER, + + .metrics = { + .mask = ES_THEME_METRICS_PREFERRED_WIDTH, + .preferredWidth = 400, + }, +}; + +struct { + Array installedApplications; + Array allApplicationInstances; + Array allContainerWindows; + InstalledApplication *fileManager; + uint64_t currentDocumentID; + HashStore openDocuments; + TaskBar taskBar; + EsWindow *wallpaperWindow; + bool shutdownWindowOpen; + bool setupDesktopUIComplete; + int installationState; +} desktop; + +int TaskBarButtonMessage(EsElement *element, EsMessage *message); +ApplicationInstance *ApplicationInstanceCreate(int64_t id, EsApplicationStartupInformation *startupInformation, ContainerWindow *container); +void ApplicationInstanceStart(int64_t applicationID, EsApplicationStartupInformation *startupInformation, ApplicationInstance *instance); +void ApplicationInstanceClose(ApplicationInstance *instance); +ApplicationInstance *ApplicationInstanceFindByWindowID(uint64_t windowID, bool remove = false); +void EmbeddedWindowDestroyed(uint64_t id); + +////////////////////////////////////////////////////// +// Reorder lists: +////////////////////////////////////////////////////// + +bool ReorderItemAnimate(ReorderItem *item, uint64_t deltaMs, const char *entranceDuration) { + item->sizeProgress += (item->sizeTarget - item->sizeProgress) + * (1 - EsCRTexp(deltaMs * -3.0f / GetConstantNumber(entranceDuration))); + item->offsetProgress += (item->offsetTarget - item->offsetProgress) + * (1 - EsCRTexp(deltaMs * -3.0f / GetConstantNumber("taskBarButtonMoveDuration"))); + + bool complete = true; + + if (EsCRTfabs(item->sizeTarget - item->sizeProgress) < 1.5) { + item->sizeProgress = item->sizeTarget; + } else { + complete = false; + } + + if (EsCRTfabs(item->offsetTarget - item->offsetProgress) < 1.5) { + item->offsetProgress = item->offsetTarget; + } else { + complete = false; + } + + EsElementRelayout(item->parent); + return complete; +} + +bool ReorderItemDragged(ReorderItem *item, int mouseX) { + ReorderList *list = (ReorderList *) item->parent; + size_t childCount = list->items.Length(); + + if (!item->dragging) { + item->dragOffset = gui.lastClickX - item->offsetX; + item->BringToFront(); + } + + item->dragPosition = mouseX + item->GetWindowBounds().l - item->dragOffset; + + int draggedIndex = (item->dragPosition + list->targetWidth / 2) / list->targetWidth; + if (draggedIndex < 0) draggedIndex = 0; + if (draggedIndex >= (int) childCount) draggedIndex = childCount - 1; + int currentIndex = -1; + + for (uintptr_t i = 0; i < childCount; i++) { + if (list->items[i] == item) { + currentIndex = i; + break; + } + } + + EsAssert(currentIndex != -1); + + bool changed = false; + + if (draggedIndex != currentIndex) { + list->items.Delete(currentIndex); + list->items.Insert(item, draggedIndex); + + for (uintptr_t i = 0, x = list->currentStyle->insets.l; i < childCount; i++) { + ReorderItem *child = (ReorderItem *) list->items[i]; + + if ((int) i != draggedIndex) { + int oldPosition = child->offsetX, newPosition = x + child->offsetProgress; + + if (child->offsetProgress != oldPosition - newPosition) { + child->offsetProgress = oldPosition - newPosition; + child->StartAnimating(); + } + } + + x += child->sizeProgress; + } + + changed = true; + } + + item->dragging = true; + EsElementRelayout(item->parent); + + return changed; +} + +void ReorderItemDragComplete(ReorderItem *item) { + if (!item->dragging) { + return; + } + + ReorderList *list = (ReorderList *) item->parent; + + for (uintptr_t i = 0, x = list->currentStyle->insets.l; i < list->items.Length(); i++) { + if (list->items[i] == item) { + int oldPosition = item->offsetX, newPosition = x + item->offsetProgress; + + if (item->offsetProgress != oldPosition - newPosition) { + item->offsetProgress = oldPosition - newPosition; + item->StartAnimating(); + } + } + + x += item->sizeTarget; + } + + item->dragging = false; +} + +int ReorderListLayout(ReorderList *list, int additionalRightMargin, bool clampDraggedItem, bool preventTabSizeAnimation) { + EsRectangle bounds = list->GetBounds(); + bounds.l += list->currentStyle->insets.l; + bounds.r -= list->currentStyle->insets.r; + bounds.r -= additionalRightMargin; + + size_t childCount = list->items.Length(); + + if (!childCount) { + return 0; + } + + int totalWidth = 0; + + for (uintptr_t i = 0; i < childCount; i++) { + ReorderItem *child = list->items[i]; + totalWidth += child->currentStyle->metrics->maximumWidth + list->currentStyle->metrics->gapMinor; + } + + bool widthClamped = false; + + if (totalWidth > Width(bounds)) { + totalWidth = Width(bounds); + widthClamped = true; + } + + int targetWidth = totalWidth / childCount; + int extraWidth = totalWidth % childCount; + + list->targetWidth = targetWidth; + list->extraWidth = extraWidth; + + for (uintptr_t i = 0; i < childCount; i++) { + ReorderItem *child = list->items[i]; + + int sizeTarget = targetWidth; + if (extraWidth) sizeTarget++, extraWidth--; + + if (preventTabSizeAnimation) { + child->sizeTarget = child->sizeProgress = sizeTarget; + } + + if (child->sizeTarget != sizeTarget) { + child->sizeTarget = sizeTarget; + child->StartAnimating(); + } + } + + int x = bounds.l; + + for (uintptr_t i = 0; i < childCount; i++) { + ReorderItem *child = list->items[i]; + int width = (i == childCount - 1 && widthClamped) ? (totalWidth - x) : child->sizeProgress; + int gap = list->currentStyle->metrics->gapMinor; + + if (child->dragging) { + int p = child->dragPosition; + + if (clampDraggedItem) { + if (p + width > bounds.r) p = bounds.r - width; + if (p < bounds.l) p = bounds.l; + } + + EsElementMove(child, p, 0, width - gap, Height(bounds)); + } else { + EsElementMove(child, x + child->offsetProgress, 0, width - gap, Height(bounds)); + } + + x += width; + } + + return x; +} + +int ReorderListMessage(EsElement *_list, EsMessage *message) { + ReorderList *list = (ReorderList *) _list; + + if (message->type == ES_MSG_LAYOUT) { + ReorderListLayout(list, 0, false, false); + } else if (message->type == ES_MSG_DESTROY) { + list->items.Free(); + } else if (message->type == ES_MSG_ADD_CHILD) { + EsMessage m = { ES_MSG_REORDER_ITEM_TEST }; + + if (ES_HANDLED == EsMessageSend(message->child, &m)) { + list->items.Add((ReorderItem *) message->child); + } + } else if (message->type == ES_MSG_REMOVE_CHILD) { + EsMessage m = { ES_MSG_REORDER_ITEM_TEST }; + + if (ES_HANDLED == EsMessageSend(message->child, &m)) { + list->items.FindAndDelete((ReorderItem *) message->child, true); + } + } + + return 0; +} + +////////////////////////////////////////////////////// +// Container windows: +////////////////////////////////////////////////////// + +void WindowTabActivate(WindowTab *tab) { + if (tab->container->active != tab) { + tab->container->active = tab; + EsElementRelayout(tab->container->tabBand); + tab->container->taskBarButton->Repaint(true); + + // EsPrint("Activating tab %d...\n", tab->tabIndex); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, tab->container->window->handle, (uintptr_t) tab->applicationInstance->embeddedWindowHandle, 0, ES_WINDOW_PROPERTY_EMBED); + } +} + +int ContainerWindowMessage(EsElement *element, EsMessage *message) { + ContainerWindow *container = (ContainerWindow *) element->userData.p; + + if (message->type == ES_MSG_WINDOW_ACTIVATED) { + container->taskBarButton->customStyleState |= THEME_STATE_SELECTED; + container->taskBarButton->MaybeRefreshStyle(); + } else if (message->type == ES_MSG_WINDOW_DEACTIVATED) { + container->taskBarButton->customStyleState &= ~THEME_STATE_SELECTED; + container->taskBarButton->MaybeRefreshStyle(); + } else if (message->type == ES_MSG_KEY_DOWN) { + bool ctrlOnly = message->keyboard.modifiers == ES_MODIFIER_CTRL; + int scancode = message->keyboard.scancode; + + if (((message->keyboard.modifiers & ~ES_MODIFIER_SHIFT) == ES_MODIFIER_CTRL) && message->keyboard.scancode == ES_SCANCODE_TAB) { + int tab = -1; + + for (uintptr_t i = 0; i < container->tabBand->items.Length(); i++) { + if (container->tabBand->items[i] == container->active) { + tab = i; + } + } + + EsAssert(tab != -1); + tab += ((message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? -1 : 1); + if (tab == -1) tab = container->tabBand->items.Length() - 1; + if (tab == (int) container->tabBand->items.Length()) tab = 0; + WindowTabActivate((WindowTab *) container->tabBand->items[tab]); + } else if (ctrlOnly && scancode == ES_SCANCODE_T) { + ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, container); + } else if (ctrlOnly && scancode == ES_SCANCODE_W) { + ApplicationInstanceClose(container->active->applicationInstance); + } else if (ctrlOnly && scancode == ES_SCANCODE_N) { + ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, nullptr); + } else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_UP_ARROW) { + WindowSnap(container->window, false, false, SNAP_EDGE_MAXIMIZE); + } else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_DOWN_ARROW) { + if (container->window->isMaximised) { + WindowRestore(container->window); + } else { + EsSyscall(ES_SYSCALL_WINDOW_MOVE, container->window->handle, 0, 0, ES_WINDOW_MOVE_HIDDEN); + } + } else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_LEFT_ARROW) { + if (container->window->restoreOnNextMove) { + WindowRestore(container->window); + } else { + WindowSnap(container->window, false, false, SNAP_EDGE_LEFT); + } + } else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_RIGHT_ARROW) { + if (container->window->restoreOnNextMove) { + WindowRestore(container->window); + } else { + WindowSnap(container->window, false, false, SNAP_EDGE_RIGHT); + } + } else { + for (uintptr_t i = 0; i < 9; i++) { + if (ctrlOnly && scancode == (int) (ES_SCANCODE_1 + i) && container->tabBand->items.Length() > i) { + WindowTabActivate((WindowTab *) container->tabBand->items[i]); + } + } + } + } else if (message->type == ES_MSG_WINDOW_RESIZED) { + container->tabBand->preventNextTabSizeAnimation = true; + } + + return 0; +} + +int WindowTabMessage(EsElement *element, EsMessage *message) { + WindowTab *tab = (WindowTab *) element; + WindowTabBand *band = (WindowTabBand *) tab->parent; + ApplicationInstance *instance = tab->applicationInstance; + + if (message->type == ES_MSG_PRESSED_START) { + tab->BringToFront(); + WindowTabActivate(tab); + } else if (message->type == ES_MSG_HIT_TEST) { + EsRectangle bounds = tab->GetBounds(); + + if (message->hitTest.x <= 12) { + message->hitTest.inside = (bounds.b - message->hitTest.y) * 14 < message->hitTest.x * bounds.b; + } else if (message->hitTest.x > bounds.r - 12) { + message->hitTest.inside = (bounds.b - message->hitTest.y) * 14 < (bounds.r - message->hitTest.x) * bounds.b; + } + } else if (message->type == ES_MSG_LAYOUT) { + int closeButtonWidth = tab->closeButton->currentStyle->preferredWidth; + Rectangle16 insets = tab->currentStyle->metrics->insets; + EsElementSetHidden(tab->closeButton, tab->currentStyle->gapWrap * 2 + closeButtonWidth >= tab->width); + EsElementMove(tab->closeButton, tab->width - tab->currentStyle->gapWrap - closeButtonWidth, + insets.t, closeButtonWidth, tab->height - insets.t - insets.b); + } else if (message->type == ES_MSG_PAINT) { + EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height), + instance->title, instance->titleBytes, instance->iconID); + } else if (message->type == ES_MSG_ANIMATE) { + message->animate.complete = ReorderItemAnimate(tab, message->animate.deltaMs, "windowTabEntranceDuration"); + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + if (band->items.Length() == 1) { + EsPoint screenPosition = EsMouseGetPosition(); + WindowChangeBounds(RESIZE_MOVE, screenPosition.x, screenPosition.y, &gui.lastClickX, &gui.lastClickY, band->window); + } else { + ReorderItemDragged(tab, message->mouseDragged.newPositionX); + } + + EsElementSetDisabled(band->GetChild(0), true); + } else if (message->type == ES_MSG_MOUSE_LEFT_UP) { + ReorderItemDragComplete(tab); + EsElementSetDisabled(band->GetChild(0), false); + } else if (message->type == ES_MSG_MOUSE_RIGHT_CLICK) { + EsMenu *menu = EsMenuCreate(tab, ES_FLAGS_DEFAULT); + + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(DesktopCloseTab), [] (EsMenu *, EsGeneric context) { + ApplicationInstanceClose(((WindowTab *) context.p)->applicationInstance); + }, tab); + + if (EsKeyboardIsShiftHeld() && !instance->localInstance) { + EsAssert(instance->processHandle); + EsMenuAddSeparator(menu); + + EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(DesktopInspectUI), [] (EsMenu *, EsGeneric context) { + WindowTab *tab = (WindowTab *) context.p; + ApplicationInstance *instance = tab->applicationInstance; + EsMessage m = { ES_MSG_TAB_INSPECT_UI }; + m.tabOperation.id = instance->embeddedWindowID; + EsMessagePostRemote(instance->processHandle, &m); + }, tab); + } + + EsMenuShow(menu); + } else if (message->type == ES_MSG_MOUSE_MIDDLE_UP && (element->state & UI_STATE_HOVERED)) { + ApplicationInstanceClose(tab->applicationInstance); + } else if (message->type == ES_MSG_REORDER_ITEM_TEST) { + } else { + return 0; + } + + return ES_HANDLED; +} + +WindowTab *WindowTabCreate(ContainerWindow *container, ApplicationInstance *instance) { + WindowTab *tab = (WindowTab *) EsHeapAllocate(sizeof(WindowTab), true); + tab->container = container; + tab->applicationInstance = instance; + tab->Initialise(container->tabBand, ES_CELL_H_SHRINK | ES_CELL_V_BOTTOM, WindowTabMessage, nullptr); + tab->cName = "window tab"; + + tab->closeButton = EsButtonCreate(tab, ES_FLAGS_DEFAULT, ES_STYLE_WINDOW_TAB_CLOSE_BUTTON); + tab->closeButton->userData = tab; + + EsButtonOnCommand(tab->closeButton, [] (EsInstance *, EsElement *element, EsCommand *) { + ApplicationInstanceClose(((WindowTab *) element->userData.p)->applicationInstance); + }); + + return tab; +} + +int WindowTabBandMessage(EsElement *element, EsMessage *message) { + WindowTabBand *band = (WindowTabBand *) element; + + if (message->type == ES_MSG_LAYOUT) { + for (uint16_t i = 0; i < band->items.Length(); i++) { + WindowTab *tab = (WindowTab *) band->items[i]; + tab->SetStyle(tab == tab->container->active ? ES_STYLE_WINDOW_TAB_ACTIVE : ES_STYLE_WINDOW_TAB_INACTIVE); + + if (tab == tab->container->active) { + tab->BringToFront(); + } + } + + int x = ReorderListLayout(band, band->GetChild(0)->currentStyle->preferredWidth + 10, true, band->preventNextTabSizeAnimation); + band->GetChild(0)->InternalMove(band->GetChild(0)->currentStyle->preferredWidth, + band->GetChild(0)->currentStyle->preferredHeight, x + 10, 4); + band->preventNextTabSizeAnimation = false; + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + EsPoint screenPosition = EsMouseGetPosition(); + WindowChangeBounds(RESIZE_MOVE, screenPosition.x, screenPosition.y, &gui.lastClickX, &gui.lastClickY, band->window); + } else if (message->type == ES_MSG_MOUSE_LEFT_UP) { + if (band->window->restoreOnNextMove) { + band->window->resetPositionOnNextMove = true; + } + } else if (message->type == ES_MSG_MOUSE_RIGHT_CLICK) { + EsMenu *menu = EsMenuCreate(band, ES_MENU_AT_CURSOR); + + EsMenuAddItem(menu, band->window->isMaximised ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT, + INTERFACE_STRING(DesktopCenterWindow), [] (EsMenu *, EsGeneric context) { + WindowTabBand *band = (WindowTabBand *) context.p; + EsRectangle workArea; + EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_GET, 0, (uintptr_t) &workArea, 0, 0); + EsRectangle newBounds = EsRectangleCenter(workArea, EsWindowGetBounds(band->window)); + newBounds.t -= 8, newBounds.b -= 8; // Because of the shadow, it looks a little better to be slightly above center :) + EsSyscall(ES_SYSCALL_WINDOW_MOVE, band->window->handle, (uintptr_t) &newBounds, 0, ES_FLAGS_DEFAULT); + }, band); + + EsMenuShow(menu); + } else { + return ReorderListMessage(band, message); + } + + return ES_HANDLED; +} + +ContainerWindow *ContainerWindowCreate() { + ContainerWindow *container = (ContainerWindow *) EsHeapAllocate(sizeof(ContainerWindow), true); + container->window = EsWindowCreate(nullptr, ES_WINDOW_CONTAINER); + desktop.allContainerWindows.Add(container); + container->window->messageUser = ContainerWindowMessage; + container->window->userData = container; + + container->tabBand = (WindowTabBand *) EsHeapAllocate(sizeof(WindowTabBand), true); + container->tabBand->container = container; + container->tabBand->Initialise(container->window, ES_CELL_FILL, WindowTabBandMessage, ES_STYLE_WINDOW_TAB_BAND); + container->tabBand->cName = "window tab band"; + + EsButton *newTabButton = EsButtonCreate(container->tabBand, ES_FLAGS_DEFAULT, ES_STYLE_WINDOW_TAB_BAND_NEW); + + EsButtonOnCommand(newTabButton, [] (EsInstance *, EsElement *element, EsCommand *) { + ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, (ContainerWindow *) element->window->userData.p); + }); + + container->taskBarButton = (TaskBarButton *) EsHeapAllocate(sizeof(TaskBarButton), true); + container->taskBarButton->customStyleState = THEME_STATE_SELECTED; + container->taskBarButton->containerWindow = container; + container->taskBarButton->Initialise(&desktop.taskBar.taskList, ES_CELL_FILL, + TaskBarButtonMessage, ES_STYLE_TASK_BAR_BUTTON); + container->taskBarButton->cName = "task bar button"; + + return container; +} + +////////////////////////////////////////////////////// +// Task bar and system modals: +////////////////////////////////////////////////////// + +int TaskBarButtonMessage(EsElement *element, EsMessage *message) { + TaskBarButton *button = (TaskBarButton *) element; + + if (message->type == ES_MSG_PAINT) { + ContainerWindow *containerWindow = button->containerWindow; + ApplicationInstance *instance = containerWindow->active->applicationInstance; + EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height), + instance->title, instance->titleBytes, instance->iconID); + } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { + if (button->customStyleState & THEME_STATE_SELECTED) { + EsSyscall(ES_SYSCALL_WINDOW_MOVE, button->containerWindow->window->handle, 0, 0, ES_WINDOW_MOVE_HIDDEN); + } else { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, button->containerWindow->window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED); + } + } else if (message->type == ES_MSG_ANIMATE) { + message->animate.complete = ReorderItemAnimate(button, message->animate.deltaMs, "taskBarButtonEntranceDuration"); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + ReorderItemDragged(button, message->mouseDragged.newPositionX); + } else if (message->type == ES_MSG_MOUSE_LEFT_UP) { + ReorderItemDragComplete(button); + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + } else if (message->type == ES_MSG_REORDER_ITEM_TEST) { + } else { + return 0; + } + + return ES_HANDLED; +} + +int TaskBarWindowMessage(EsElement *, EsMessage *message) { + if (message->type == ES_MSG_ANIMATE) { + desktop.taskBar.enterProgress += (1 - desktop.taskBar.enterProgress) + * (1 - EsCRTexp(message->animate.deltaMs * -3.0 / GetConstantNumber("taskBarEntranceDuration"))); + + if (EsCRTfabs(1 - desktop.taskBar.enterProgress) < 0.001) { + desktop.taskBar.enterProgress = 1; + message->animate.complete = true; + } else { + message->animate.complete = false; + } + + EsRectangle bounds = desktop.taskBar.targetBounds; + bounds = Translate(bounds, 0, Height(bounds) * (1 - desktop.taskBar.enterProgress)); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, desktop.taskBar.window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_UPDATE_SCREEN); + } + + return 0; +} + +int TaskBarMessage(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_LAYOUT) { + EsRectangle bounds = element->GetBounds(); + element->GetChild(0)->InternalMove(Width(bounds), Height(bounds), 0, 0); + } + + return 0; +} + +void ShutdownModalCreate() { + if (desktop.shutdownWindowOpen) { + return; + } + + desktop.shutdownWindowOpen = true; + + // Setup the window. + + EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_PLAIN); + EsRectangle screen; + EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &screen, 0, ES_WINDOW_MOVE_ALWAYS_ON_TOP); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, (uintptr_t) &screen, 0, ES_WINDOW_PROPERTY_BLUR_BOUNDS); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE, 0, ES_WINDOW_PROPERTY_SOLID); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, BLEND_WINDOW_MATERIAL_LIGHT_BLUR, 0, ES_WINDOW_PROPERTY_MATERIAL); + + // Setup the UI. + + EsPanel *stack = EsPanelCreate(window, ES_CELL_FILL | ES_PANEL_Z_STACK, ES_STYLE_PANEL_NORMAL_WINDOW_ROOT); + stack->cName = "window stack"; + EsPanelCreate(stack, ES_CELL_FILL, ES_STYLE_PANEL_SHUTDOWN_OVERLAY)->cName = "modal overlay"; + EsPanel *dialog = EsPanelCreate(stack, ES_PANEL_VERTICAL | ES_CELL_CENTER, ES_STYLE_PANEL_DIALOG_ROOT); + dialog->cName = "dialog"; + EsPanel *heading = EsPanelCreate(dialog, ES_PANEL_HORIZONTAL | ES_CELL_H_FILL, ES_STYLE_DIALOG_HEADING); + EsIconDisplayCreate(heading, ES_FLAGS_DEFAULT, {}, ES_ICON_SYSTEM_SHUTDOWN); + EsTextDisplayCreate(heading, ES_CELL_H_FILL | ES_CELL_V_CENTER, ES_STYLE_TEXT_HEADING2, + INTERFACE_STRING(DesktopShutdownTitle))->cName = "dialog heading"; + EsTextDisplayCreate(EsPanelCreate(dialog, ES_PANEL_VERTICAL | ES_CELL_H_FILL, ES_STYLE_DIALOG_CONTENT), + ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopConfirmShutdown))->cName = "dialog contents"; + EsPanel *buttonArea = EsPanelCreate(dialog, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_CELL_H_FILL, ES_STYLE_DIALOG_BUTTON_AREA); + EsButton *cancelButton = EsButtonCreate(buttonArea, ES_BUTTON_DEFAULT, 0, INTERFACE_STRING(CommonCancel)); + EsButton *restartButton = EsButtonCreate(buttonArea, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(DesktopRestartAction)); + EsButton *shutdownButton = EsButtonCreate(buttonArea, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_DANGEROUS, INTERFACE_STRING(DesktopShutdownAction)); + EsElementFocus(cancelButton); + + // Setup command callbacks when the buttons are pressed. + + EsButtonOnCommand(shutdownButton, [] (EsInstance *, EsElement *, EsCommand *) { + EsSyscall(ES_SYSCALL_SHUTDOWN, SHUTDOWN_ACTION_POWER_OFF, 0, 0, 0); + }); + + EsButtonOnCommand(restartButton, [] (EsInstance *, EsElement *, EsCommand *) { + EsSyscall(ES_SYSCALL_SHUTDOWN, SHUTDOWN_ACTION_RESTART, 0, 0, 0); + }); + + EsButtonOnCommand(cancelButton, [] (EsInstance *, EsElement *element, EsCommand *) { + EsElementDestroy(element->window); + desktop.shutdownWindowOpen = false; + }); +} + +////////////////////////////////////////////////////// +// Built-in tabs: +////////////////////////////////////////////////////// + +void InstanceForceQuit(EsInstance *, EsElement *element, EsCommand *) { + ApplicationInstance *instance = (ApplicationInstance *) element->userData.p; + EsProcessTerminate(instance->processHandle, 1); +} + +void InstanceCrashedTabCreate(int reason, ApplicationInstance *_instance) { + EsMessage m = {}; + m.type = ES_MSG_INSTANCE_CREATE; + m.createInstance.window = EsSyscall(ES_SYSCALL_WINDOW_CREATE, ES_WINDOW_NORMAL, 0, 0, 0); + CrashedTabInstance *instance = (CrashedTabInstance *) _EsInstanceCreate(sizeof(CrashedTabInstance), &m, nullptr); + instance->window->toolbarFillMode = true; + if (reason != CRASHED_TAB_NOT_RESPONDING) EsWindowSetIcon(instance->window, ES_ICON_DIALOG_ERROR); + EsElement *toolbar = EsWindowGetToolbar(instance->window); + EsPanel *panel = EsPanelCreate(toolbar, ES_CELL_V_CENTER | ES_CELL_V_PUSH | ES_CELL_H_SHRINK | ES_CELL_H_PUSH | ES_PANEL_VERTICAL, ES_STYLE_PANEL_CRASH_INFO); + + if (reason == CRASHED_TAB_FATAL_ERROR) { + EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopCrashedApplication)); + } else if (reason == CRASHED_TAB_PROGRAM_NOT_FOUND) { + EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopNoSuchApplication)); + EsWindowSetTitle(instance->window, INTERFACE_STRING(CommonErrorTitle)); + } else if (reason == CRASHED_TAB_INVALID_EXECUTABLE) { + EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopApplicationStartupError)); + EsWindowSetTitle(instance->window, INTERFACE_STRING(CommonErrorTitle)); + } else if (reason == CRASHED_TAB_NOT_RESPONDING) { + EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopNotResponding)); + EsButton *button = EsButtonCreate(panel, ES_CELL_H_RIGHT, ES_STYLE_PUSH_BUTTON_DANGEROUS, INTERFACE_STRING(DesktopForceQuit)); + button->userData = _instance; + EsButtonOnCommand(button, InstanceForceQuit); + } + + _instance->embeddedWindowHandle = m.createInstance.window; + _instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, m.createInstance.window, 0, 0, 0); + _instance->localInstance = instance; +} + +void InstanceBlankTabCreate(ApplicationInstance *_instance) { + EsMessage m = {}; + m.type = ES_MSG_INSTANCE_CREATE; + m.createInstance.window = EsSyscall(ES_SYSCALL_WINDOW_CREATE, ES_WINDOW_NORMAL, 0, 0, 0); + EsInstance *instance = _EsInstanceCreate(sizeof(BlankTabInstance), &m, nullptr); + EsWindowSetTitle(instance->window, INTERFACE_STRING(DesktopNewTabTitle)); + EsPanel *windowBackground = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND); + EsPanel *content = EsPanelCreate(windowBackground, ES_CELL_FILL | ES_PANEL_V_SCROLL_AUTO, &styleNewTabContent); + + { + // Installed applications list. + + EsPanel *buttonGroup = EsPanelCreate(content, ES_PANEL_VERTICAL | ES_CELL_H_SHRINK, &styleButtonGroupContainer); + + buttonGroup->separatorStylePart = ES_STYLE_BUTTON_GROUP_SEPARATOR; + buttonGroup->separatorFlags = ES_CELL_H_FILL; + + for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) { + InstalledApplication *application = desktop.installedApplications[i]; + + if (application->hidden) { + continue; + } + + EsButton *button = EsButtonCreate(buttonGroup, ES_CELL_H_FILL | ES_BUTTON_NOT_FOCUSABLE, ES_STYLE_BUTTON_GROUP_ITEM, application->cName); + + button->userData = application; + + EsButtonSetIcon(button, (EsStandardIcon) application->iconID ?: ES_ICON_APPLICATION_DEFAULT_ICON); + + EsButtonOnCommand(button, [] (EsInstance *, EsElement *element, EsCommand *) { + uint64_t tabID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, element->window->handle, 0, 0, 0); + ApplicationInstance *instance = ApplicationInstanceFindByWindowID(tabID); + ApplicationInstanceStart(((InstalledApplication *) element->userData.p)->id, nullptr, instance); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, instance->tab->window->handle, (uintptr_t) instance->embeddedWindowHandle, 0, ES_WINDOW_PROPERTY_EMBED); + EsInstanceDestroy(element->instance); + instance->localInstance = nullptr; + }); + } + } + + _instance->embeddedWindowHandle = m.createInstance.window; + _instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, m.createInstance.window, 0, 0, 0); + _instance->localInstance = instance; +} + +////////////////////////////////////////////////////// +// Application management: +////////////////////////////////////////////////////// + +ApplicationInstance *ApplicationInstanceFindByWindowID(uint64_t windowID, bool remove) { + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if (instance->embeddedWindowID == windowID || (instance->restoreEmbeddedWindowID == windowID && instance->notResponding)) { + if (remove) { + desktop.allApplicationInstances.Delete(i); + } + + return instance; + } + } + + return nullptr; +} + +void ApplicationInstanceClose(ApplicationInstance *instance) { + if (!instance->processID) { + // This is a Desktop owned instance. + if (instance->localInstance) EsInstanceDestroy(instance->localInstance); + instance->localInstance = nullptr; + } else { + // TODO Force closing not responding instances. + EsMessage m = { ES_MSG_TAB_CLOSE_REQUEST }; + m.tabOperation.id = instance->embeddedWindowID; + EsMessagePostRemote(instance->processHandle, &m); + } +} + +void ApplicationInstanceStart(int64_t applicationID, EsApplicationStartupInformation *startupInformation, ApplicationInstance *instance) { + EsApplicationStartupInformation _startupInformation = {}; + + if (!startupInformation) { + startupInformation = &_startupInformation; + } + + if (instance->restoreEmbeddedWindowHandle) { + EsHandleClose(instance->restoreEmbeddedWindowHandle); + instance->restoreEmbeddedWindowHandle = ES_INVALID_HANDLE; + } + + if (applicationID == APPLICATION_ID_DESKTOP_BLANK_TAB) { + InstanceBlankTabCreate(instance); + return; + } + + InstalledApplication *application = nullptr; + + for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) { + if (desktop.installedApplications[i]->id == applicationID) { + application = desktop.installedApplications[i]; + } + } + + if (!application) { + InstanceCrashedTabCreate(CRASHED_TAB_PROGRAM_NOT_FOUND, instance); + return; + } + + instance->application = application; + + if (application->useSingleProcess && application->singleProcessHandle) { + EsProcessState state; + EsProcessGetState(application->singleProcessHandle, &state); + + if (state.flags & (ES_PROCESS_STATE_ALL_THREADS_TERMINATED | ES_PROCESS_STATE_TERMINATING | ES_PROCESS_STATE_CRASHED)) { + EsHandleClose(application->singleProcessHandle); + application->singleProcessHandle = ES_INVALID_HANDLE; + } + } + + EsHandle process = application->singleProcessHandle; + + if (!application->useSingleProcess || process == ES_INVALID_HANDLE) { + EsProcessInformation information; + EsProcessCreationArguments arguments = {}; + + _EsNodeInformation executableNode; + EsError error = NodeOpen(application->cExecutable, EsCStringLength(application->cExecutable), + ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND, &executableNode); + + if (ES_CHECK_ERROR(error)) { + InstanceCrashedTabCreate(CRASHED_TAB_INVALID_EXECUTABLE, instance); + return; + } + + arguments.executable = executableNode.handle; + arguments.permissions = ES_PERMISSION_WINDOW_MANAGER; + + Array initialMountPoints = {}; + _EsNodeInformation settingsNode = {}; + + if (application->permissions & APPLICATION_PERMISSION_MANAGE_PROCESSES) { + arguments.permissions |= ES_PERMISSION_TAKE_SYSTEM_SNAPSHOT; + arguments.permissions |= ES_PERMISSION_PROCESS_CREATE; + arguments.permissions |= ES_PERMISSION_PROCESS_OPEN; + } + + if (application->permissions & APPLICATION_PERMISSION_POSIX_SUBSYSTEM) { + arguments.permissions |= ES_PERMISSION_PROCESS_OPEN; + arguments.permissions |= ES_PERMISSION_POSIX_SUBSYSTEM; + + MountPoint root = *NodeFindMountPoint(EsLiteral("0:")); + root.write = true; + root.prefixBytes = EsStringFormat(root.prefix, sizeof(root.prefix), "|POSIX:"); + initialMountPoints.Add(root); + } + + if (application->permissions & APPLICATION_PERMISSION_ALL_FILES) { + for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) { + initialMountPoints.Add(api.mountPoints[i]); + initialMountPoints[i].write = true; + } + + arguments.permissions |= ES_PERMISSION_GET_VOLUME_INFORMATION; + } else { + initialMountPoints.Add(*NodeFindMountPoint(EsLiteral("|Themes:"))); + initialMountPoints.Add(*NodeFindMountPoint(EsLiteral("|Fonts:"))); + } + + { + size_t settingsPathBytes, settingsFolderBytes; + char *settingsFolder = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("settings_path"), &settingsFolderBytes); + char *settingsPath = EsStringAllocateAndFormat(&settingsPathBytes, "%s/%z", settingsFolderBytes, settingsFolder, application->cName); + error = NodeOpen(settingsPath, settingsPathBytes, ES_NODE_DIRECTORY | ES_NODE_CREATE_DIRECTORIES | _ES_NODE_DIRECTORY_WRITE, &settingsNode); + EsHeapFree(settingsPath); + EsHeapFree(settingsFolder); + + if (error == ES_SUCCESS) { + EsMountPoint settings = {}; + settings.prefixBytes = EsStringFormat(settings.prefix, sizeof(settings.prefix), "|Settings:"); + settings.base = settingsNode.handle; + settings.write = true; + initialMountPoints.Add(settings); + } else { + settingsNode.handle = ES_INVALID_HANDLE; + } + } + + arguments.initialMountPoints = initialMountPoints.array; + arguments.initialMountPointCount = initialMountPoints.Length(); + + error = EsProcessCreate(&arguments, &information); + EsHandleClose(arguments.executable); + + initialMountPoints.Free(); + if (settingsNode.handle) EsHandleClose(settingsNode.handle); + + if (!ES_CHECK_ERROR(error)) { + process = information.handle; + EsHandleClose(information.mainThread.handle); + } else { + InstanceCrashedTabCreate(CRASHED_TAB_INVALID_EXECUTABLE, instance); + return; + } + } + + if (application->useSingleProcess) { + application->singleProcessHandle = process; + } + + instance->processID = EsProcessGetID(process); + instance->processHandle = EsSyscall(ES_SYSCALL_PROCESS_SHARE, process, ES_CURRENT_PROCESS, 0, 0); + + EsMessage m = { ES_MSG_INSTANCE_CREATE }; + + if (~startupInformation->flags & ES_APPLICATION_STARTUP_MANUAL_PATH) { + // Only tell the application the name of the file. + + for (uintptr_t i = 0; i < (size_t) startupInformation->filePathBytes; i++) { + if (startupInformation->filePath[i] == '/') { + startupInformation->filePath += i + 1; + startupInformation->filePathBytes -= i + 1; + i = 0; + } + } + } + + // Share handles to the file and the startup information buffer. + + if (startupInformation->readHandle) { + startupInformation->readHandle = EsSyscall(ES_SYSCALL_NODE_SHARE, startupInformation->readHandle, process, 0, 0); + } + + if (!application->useSingleProcess) { + startupInformation->flags |= ES_APPLICATION_STARTUP_SINGLE_INSTANCE_IN_PROCESS; + } + + uint8_t *createInstanceDataBuffer = ApplicationStartupInformationToBuffer(startupInformation, &m.createInstance.dataBytes); + m.createInstance.data = EsConstantBufferCreate(createInstanceDataBuffer, m.createInstance.dataBytes, process); + EsHeapFree(createInstanceDataBuffer); + + EsHandle handle = EsSyscall(ES_SYSCALL_WINDOW_CREATE, ES_WINDOW_NORMAL, 0, 0, 0); + instance->embeddedWindowHandle = handle; + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, handle, 0xFF000000 | GetConstantNumber("windowFillColor"), 0, ES_WINDOW_PROPERTY_RESIZE_CLEAR_COLOR); + instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, handle, 0, 0, 0); + m.createInstance.window = EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, handle, process, 0, ES_WINDOW_PROPERTY_EMBED_OWNER); + EsMessagePostRemote(process, &m); + + if (!application->useSingleProcess) { + EsHandleClose(process); + } else { + application->openInstanceCount++; + } +} + +ApplicationInstance *ApplicationInstanceCreate(int64_t id, EsApplicationStartupInformation *startupInformation, ContainerWindow *container) { + ApplicationInstance *instance = (ApplicationInstance *) EsHeapAllocate(sizeof(ApplicationInstance), true); + WindowTab *tab = WindowTabCreate(container ?: ContainerWindowCreate(), instance); + instance->title[0] = ' '; + instance->titleBytes = 1; + desktop.allApplicationInstances.Add(instance); + instance->tab = tab; + ApplicationInstanceStart(id, startupInformation, instance); + WindowTabActivate(tab); + return instance; +} + +void ApplicationTemporaryDestroy(InstalledApplication *application) { + if (!application->temporary) return; + EsAssert(!application->singleProcessHandle); + + for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) { + if (desktop.installedApplications[i] == application) { + desktop.installedApplications.Delete(i); + EsHeapFree(application->cName); + EsHeapFree(application->cExecutable); + EsHeapFree(application); + // TODO Delete the settings folder. + return; + } + } + + EsAssert(false); +} + +void ApplicationInstanceCrashed(EsMessage *message) { + EsProcessState state; + EsProcessGetState(message->crash.process, &state); + const char *fatalErrorString = state.crashReason.errorCode >= ES_FATAL_ERROR_COUNT ? "[unknown]" + : EnumLookupNameFromValue(enumStrings_EsFatalError, state.crashReason.errorCode); + const char *systemCallString = state.crashReason.duringSystemCall == -1 ? "[none]" + : EnumLookupNameFromValue(enumStrings_EsSyscallType, state.crashReason.duringSystemCall); + EsPrint("Process %d has crashed with error %z, during system call %z.\n", state.id, fatalErrorString, systemCallString); + + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if (instance->processID != message->crash.pid) { + continue; + } + + if (instance->notResponding) { + instance->notResponding = false; + instance->embeddedWindowHandle = instance->restoreEmbeddedWindowHandle; + instance->restoreEmbeddedWindowHandle = ES_INVALID_HANDLE; + instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, instance->embeddedWindowHandle, 0, 0, 0); + EsInstanceDestroy(instance->localInstance); + instance->localInstance = nullptr; + } + + instance->processID = 0; + EsHandleClose(instance->embeddedWindowHandle); + InstanceCrashedTabCreate(CRASHED_TAB_FATAL_ERROR, instance); + + ContainerWindow *container = instance->tab->container; + + if (instance->tab == container->active) { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, container->window->handle, (uintptr_t) instance->embeddedWindowHandle, 0, ES_WINDOW_PROPERTY_EMBED); + } + } + + for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) { + if (desktop.installedApplications[i]->useSingleProcess && desktop.installedApplications[i]->singleProcessHandle + && EsProcessGetID(desktop.installedApplications[i]->singleProcessHandle) == message->crash.pid) { + EsHandleClose(desktop.installedApplications[i]->singleProcessHandle); + desktop.installedApplications[i]->singleProcessHandle = ES_INVALID_HANDLE; + desktop.installedApplications[i]->openInstanceCount = 0; + ApplicationTemporaryDestroy(desktop.installedApplications[i]); + break; + } + } + + EsProcessTerminate(message->crash.process, 1); + EsHandleClose(message->crash.process); +} + +void ApplicationProcessTerminated(uint64_t pid) { + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if (instance->processID != pid) { + continue; + } + + if (instance->notResponding) { + instance->notResponding = false; + instance->embeddedWindowHandle = instance->restoreEmbeddedWindowHandle; + instance->restoreEmbeddedWindowHandle = ES_INVALID_HANDLE; + instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, instance->embeddedWindowHandle, 0, 0, 0); + EsInstanceDestroy(instance->localInstance); + instance->localInstance = nullptr; + } + + EmbeddedWindowDestroyed(instance->embeddedWindowID); + } + + for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) { + if (desktop.installedApplications[i]->useSingleProcess && desktop.installedApplications[i]->singleProcessHandle + && EsProcessGetID(desktop.installedApplications[i]->singleProcessHandle) == pid) { + EsHandleClose(desktop.installedApplications[i]->singleProcessHandle); + desktop.installedApplications[i]->singleProcessHandle = ES_INVALID_HANDLE; + desktop.installedApplications[i]->openInstanceCount = 0; + ApplicationTemporaryDestroy(desktop.installedApplications[i]); + break; + } + } +} + +////////////////////////////////////////////////////// +// Document management: +////////////////////////////////////////////////////// + +void OpenDocumentWithApplication(EsApplicationStartupInformation *startupInformation) { + bool foundDocument = false; + uint64_t documentID; + + for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) { + OpenDocument *document = &desktop.openDocuments[i]; + + if (document->pathBytes == (size_t) startupInformation->filePathBytes + && 0 == EsMemoryCompare(document->path, startupInformation->filePath, document->pathBytes)) { + foundDocument = true; + startupInformation->readHandle = document->readHandle; + documentID = document->id; + break; + } + } + + if (!foundDocument) { + EsFileInformation file = EsFileOpen(startupInformation->filePath, startupInformation->filePathBytes, + ES_FILE_READ_SHARED | ES_NODE_FAIL_IF_NOT_FOUND); + + if (file.error != ES_SUCCESS) { + // TODO Report error? + return; + } + + OpenDocument document = {}; + document.path = (char *) EsHeapAllocate(startupInformation->filePathBytes, false); + document.pathBytes = startupInformation->filePathBytes; + document.readHandle = file.handle; + document.id = ++desktop.currentDocumentID; + documentID = document.id; + EsMemoryCopy(document.path, startupInformation->filePath, startupInformation->filePathBytes); + *desktop.openDocuments.Put(&document.id) = document; + + startupInformation->readHandle = document.readHandle; + } + + ApplicationInstance *instance = ApplicationInstanceCreate(startupInformation->id, startupInformation, nullptr); + + if (instance) { + instance->documentID = documentID; + } +} + +void ApplicationInstanceRequestSave(ApplicationInstance *instance, const char *newName, size_t newNameBytes) { + if (!instance->processHandle) return; + + EsMessage m = {}; + m.type = ES_MSG_INSTANCE_SAVE_RESPONSE; + m.tabOperation.id = instance->embeddedWindowID; + + if (!instance->documentID) { + size_t folderBytes; + char *folder = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("default_user_documents_path"), &folderBytes); + char *name = (char *) EsHeapAllocate(folderBytes + newNameBytes + 32, false); + EsMemoryCopy(name, folder, folderBytes); + EsMemoryCopy(name + folderBytes, newName, newNameBytes); + EsHeapFree(folder); + size_t nameBytes = EsPathFindUniqueName(name, folderBytes + newNameBytes, folderBytes + newNameBytes + 32); + + if (!nameBytes) { + EsHeapFree(name); + m.tabOperation.error = ES_ERROR_FILE_ALREADY_EXISTS; + EsMessagePostRemote(instance->processHandle, &m); + return; + } + + EsFileInformation file = EsFileOpen(name, nameBytes, ES_FILE_READ_SHARED | ES_NODE_FAIL_IF_FOUND); + + if (file.error != ES_SUCCESS) { + EsHeapFree(name); + m.tabOperation.error = file.error; + EsMessagePostRemote(instance->processHandle, &m); + return; + } + + OpenDocument document = {}; + document.path = name; + document.pathBytes = nameBytes; + document.readHandle = file.handle; + document.id = ++desktop.currentDocumentID; + *desktop.openDocuments.Put(&document.id) = document; + + instance->documentID = document.id; + + { + // Tell the instance the chosen name for the document. + + uintptr_t nameOffset = 0; + + for (uintptr_t i = 0; i < nameBytes; i++) { + if (name[i] == '/') { + nameOffset = i + 1; + } + } + + EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED }; + m.tabOperation.id = instance->embeddedWindowID; + m.tabOperation.handle = EsConstantBufferCreate(name + nameOffset, nameBytes - nameOffset, instance->processHandle); + m.tabOperation.bytes = nameBytes - nameOffset; + EsMessagePostRemote(instance->processHandle, &m); + } + } + + OpenDocument *document = desktop.openDocuments.Get(&instance->documentID); + + if (!document) { + return; + } + + if (document->currentWriter) { + m.tabOperation.error = ES_ERROR_FILE_CANNOT_GET_EXCLUSIVE_USE; + } else { + char temporaryFileName[32]; + + for (uintptr_t i = 0; i < sizeof(temporaryFileName); i++) { + temporaryFileName[i] = (EsRandomU8() % 26) + 'a'; + } + + size_t temporaryFolderBytes; + char *temporaryFolder = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("temporary_path"), &temporaryFolderBytes); + char *temporaryFilePath = (char *) EsHeapAllocate(temporaryFolderBytes + 1 + sizeof(temporaryFileName), false); + size_t temporaryFilePathBytes = EsStringFormat(temporaryFilePath, ES_STRING_FORMAT_ENOUGH_SPACE, "%s/%s", + temporaryFolderBytes, temporaryFolder, sizeof(temporaryFileName), temporaryFileName); + + EsFileInformation file = EsFileOpen(temporaryFilePath, temporaryFilePathBytes, + ES_FILE_WRITE_EXCLUSIVE | ES_NODE_FAIL_IF_FOUND | ES_NODE_CREATE_DIRECTORIES); + + EsHeapFree(temporaryFolder); + + if (file.error != ES_SUCCESS) { + m.tabOperation.error = file.error; + EsHeapFree(temporaryFilePath); + } else { + m.tabOperation.handle = EsSyscall(ES_SYSCALL_NODE_SHARE, file.handle, instance->processHandle, 0, 0); + m.tabOperation.error = ES_SUCCESS; + document->currentWriter = instance->embeddedWindowID; + document->temporarySavePath = temporaryFilePath; + document->temporarySavePathBytes = temporaryFilePathBytes; + EsHandleClose(file.handle); + } + } + + EsMessagePostRemote(instance->processHandle, &m); +} + +void InstanceAnnouncePathMoved(ApplicationInstance *fromInstance, const uint8_t *buffer, size_t embedWindowMessageBytes) { + // TODO Update the location of installed applications and other things in the configuration. + + uintptr_t oldPathBytes, newPathBytes; + EsMemoryCopy(&oldPathBytes, buffer + 1, sizeof(uintptr_t)); + EsMemoryCopy(&newPathBytes, buffer + 1 + sizeof(uintptr_t), sizeof(uintptr_t)); + + if (oldPathBytes >= 0x4000 || newPathBytes >= 0x4000 + || oldPathBytes + newPathBytes + sizeof(uintptr_t) * 2 + 1 != embedWindowMessageBytes) { + return; + } + + const char *oldPath = (const char *) buffer + 1 + sizeof(uintptr_t) * 2; + const char *newPath = (const char *) buffer + 1 + sizeof(uintptr_t) * 2 + oldPathBytes; + + uint64_t documentID = 0; + + for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) { + OpenDocument *document = &desktop.openDocuments[i]; + + if (document->pathBytes >= oldPathBytes + && 0 == EsMemoryCompare(document->path, oldPath, oldPathBytes) + && (oldPathBytes == document->pathBytes || document->path[oldPathBytes] == '/')) { + if (document->pathBytes == oldPathBytes) documentID = document->id; + char *newDocumentPath = (char *) EsHeapAllocate(document->pathBytes - oldPathBytes + newPathBytes, false); + EsMemoryCopy(newDocumentPath, newPath, newPathBytes); + EsMemoryCopy(newDocumentPath + newPathBytes, document->path + oldPathBytes, document->pathBytes - oldPathBytes); + document->pathBytes += newPathBytes - oldPathBytes; + EsHeapFree(document->path); + document->path = newDocumentPath; + } + } + + if (!documentID) { + return; + } + + uintptr_t newNameOffset = 0; + + for (uintptr_t i = 0; i < newPathBytes; i++) { + if (newPath[i] == '/') { + newNameOffset = i + 1; + } + } + + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if (instance->documentID != documentID) continue; + if (instance->application == fromInstance->application) continue; + if (!instance->processHandle) continue; + + EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED }; + m.tabOperation.id = instance->embeddedWindowID; + m.tabOperation.handle = EsConstantBufferCreate(newPath + newNameOffset, newPathBytes - newNameOffset, instance->processHandle); + m.tabOperation.bytes = newPathBytes - newNameOffset; + EsMessagePostRemote(instance->processHandle, &m); + } +} + +void ApplicationInstanceCompleteSave(ApplicationInstance *fromInstance) { + OpenDocument *document = desktop.openDocuments.Get(&fromInstance->documentID); + + if (!document || fromInstance->embeddedWindowID != document->currentWriter) { + return; + } + + // Move the temporary file to its target destination. + // TODO Handling errors. + // TODO What should happen if the old file is deleted, but the new file isn't moved? + + EsPathDelete(document->path, document->pathBytes); + EsPathMove(document->temporarySavePath, document->temporarySavePathBytes, document->path, document->pathBytes); + + // Re-open the read handle. + + EsFileInformation file = EsFileOpen(document->path, document->pathBytes, ES_FILE_READ_SHARED | ES_NODE_FAIL_IF_NOT_FOUND); + + if (file.error != ES_SUCCESS) { + // TODO What now? + } else { + EsHandleClose(document->readHandle); + document->readHandle = file.handle; + } + + document->currentWriter = 0; + + if (desktop.fileManager->singleProcessHandle) { + EsMessage m = {}; + m.type = ES_MSG_FILE_MANAGER_FILE_MODIFIED; + m.user.context1 = EsConstantBufferCreate(document->path, document->pathBytes, desktop.fileManager->singleProcessHandle); + m.user.context2 = document->pathBytes; + EsMessagePostRemote(desktop.fileManager->singleProcessHandle, &m); + } + + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if (instance->documentID != document->id) continue; + if (!instance->processHandle) continue; + + EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_UPDATED }; + m.tabOperation.isSource = instance == fromInstance; + m.tabOperation.id = instance->embeddedWindowID; + m.tabOperation.handle = EsSyscall(ES_SYSCALL_NODE_SHARE, document->readHandle, instance->processHandle, 0, 0); + EsMessagePostRemote(instance->processHandle, &m); + } +} + +////////////////////////////////////////////////////// +// Configuration file management: +////////////////////////////////////////////////////// + +void ConfigurationLoad() { + for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) { + // Load information about installed applications. + + EsSystemConfigurationGroup *group = &api.systemConfigurationGroups[i]; + + if (0 != EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, EsLiteral("application"))) { + continue; + } + + InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true); + + application->cName = EsSystemConfigurationGroupReadString(group, EsLiteral("name")); + size_t executableBytes = 0; + char *icon = EsSystemConfigurationGroupReadString(group, EsLiteral("icon")); + application->iconID = EsIconIDFromString(icon); + EsHeapFree(icon); + char *executable = EsSystemConfigurationGroupReadString(group, EsLiteral("executable"), &executableBytes); + application->cExecutable = (char *) EsHeapAllocate(executableBytes + 1, false); + EsMemoryCopy(application->cExecutable, executable, executableBytes); + application->cExecutable[executableBytes] = 0; + EsHeapFree(executable); + application->useSingleProcess = EsSystemConfigurationGroupReadInteger(group, EsLiteral("use_single_process"), true); + application->hidden = EsSystemConfigurationGroupReadInteger(group, EsLiteral("hidden"), false); + application->id = EsIntegerParse(group->section, group->sectionBytes); + +#define READ_PERMISSION(x, y) if (EsSystemConfigurationGroupReadInteger(group, EsLiteral(x), 0)) application->permissions |= y + READ_PERMISSION("permission_all_files", APPLICATION_PERMISSION_ALL_FILES); + READ_PERMISSION("permission_manage_processes", APPLICATION_PERMISSION_MANAGE_PROCESSES); + READ_PERMISSION("permission_posix_subsystem", APPLICATION_PERMISSION_POSIX_SUBSYSTEM); + READ_PERMISSION("permission_run_temporary_application", APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION); + READ_PERMISSION("permission_shutdown", APPLICATION_PERMISSION_SHUTDOWN); + + desktop.installedApplications.Add(application); + + if (EsSystemConfigurationGroupReadInteger(group, EsLiteral("is_file_manager"))) { + desktop.fileManager = application; + } + } + + EsSort(desktop.installedApplications.array, desktop.installedApplications.Length(), + sizeof(InstalledApplication *), [] (const void *_left, const void *_right, EsGeneric) { + InstalledApplication *left = *(InstalledApplication **) _left; + InstalledApplication *right = *(InstalledApplication **) _right; + return EsStringCompare(left->cName, EsCStringLength(left->cName), right->cName, EsCStringLength(right->cName)); + }, 0); + + // Set the system configuration for other applications to read. + + // TODO Enforce a limit of 4MB on the size of the system configuration. + // TODO Alternatively, replace this with a growable EsBuffer. + const size_t bufferSize = 4194304; + char *buffer = (char *) EsHeapAllocate(bufferSize, false); + size_t position = 0; + + for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) { + EsSystemConfigurationGroup *group = &api.systemConfigurationGroups[i]; + + if (EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, EsLiteral("font")) + && EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, EsLiteral("file_type")) + && EsStringCompareRaw(group->section, group->sectionBytes, EsLiteral("ui"))) { + continue; + } + + EsINIState s = {}; + s.sectionClass = group->sectionClass, s.sectionClassBytes = group->sectionClassBytes; + s.section = group->section, s.sectionBytes = group->sectionBytes; + position += EsINIFormat(&s, buffer + position, bufferSize - position); + + for (uintptr_t i = 0; i < group->itemCount; i++) { + EsSystemConfigurationItem *item = group->items + i; + + if (!item->keyBytes || item->key[0] == ';') { + continue; + } + + s.key = item->key, s.keyBytes = item->keyBytes; + s.value = item->value, s.valueBytes = item->valueBytes; + position += EsINIFormat(&s, buffer + position, bufferSize - position); + } + } + + EsSyscall(ES_SYSCALL_SYSTEM_CONFIGURATION_WRITE, (uintptr_t) buffer, position, 0, 0); + EsHeapFree(buffer); +} + +////////////////////////////////////////////////////// +// Image utilities: +////////////////////////////////////////////////////// + +void WallpaperLoad(EsGeneric) { + size_t pathBytes; + char *path = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("wallpaper"), &pathBytes); + + if (path) { + void *buffer = EsHeapAllocate(desktop.wallpaperWindow->windowWidth * desktop.wallpaperWindow->windowHeight * 4, false); + LoadImage(path, pathBytes, buffer, desktop.wallpaperWindow->windowWidth, desktop.wallpaperWindow->windowHeight, false); + EsHeapFree(path); + + EsRectangle region = ES_RECT_2S(desktop.wallpaperWindow->windowWidth, desktop.wallpaperWindow->windowHeight); + EsSyscall(ES_SYSCALL_WINDOW_SET_BITS, desktop.wallpaperWindow->handle, (uintptr_t) ®ion, (uintptr_t) buffer, 0); + EsSyscall(ES_SYSCALL_SCREEN_FORCE_UPDATE, true, 0, 0, 0); + + // Work out the most common hue on the wallpaper, and set the system hue. + + uint32_t hueBuckets[36] = {}; + uint32_t hueSelected = 0; + + for (uintptr_t i = 0; i < desktop.wallpaperWindow->windowWidth * desktop.wallpaperWindow->windowHeight; i += 7) { + float h, s, v; + EsColorConvertToHSV(((uint32_t *) buffer)[i], &h, &s, &v); + uintptr_t bucket = (uintptr_t) (h * 6); + hueBuckets[bucket] += 2; + hueBuckets[bucket == 35 ? 0 : (bucket + 1)] += 1; + hueBuckets[bucket == 0 ? 35 : (bucket - 1)] += 1; + } + + for (uintptr_t i = 1; i < 36; i++) { + if (hueBuckets[i] > hueBuckets[hueSelected]) { + hueSelected = i; + } + } + + theming.systemHue = hueSelected / 6.0f; + if (theming.systemHue < 0) theming.systemHue += 6.0f; + + EsHeapFree(buffer); + + // Tell all container windows to redraw with the new system hue. + + EsMessageMutexAcquire(); + + for (uintptr_t i = 0; i < gui.allWindows.Length(); i++) { + if (gui.allWindows[i]->windowStyle == ES_WINDOW_CONTAINER) { + gui.allWindows[i]->Repaint(true); + UIWindowNeedsUpdate(gui.allWindows[i]); + } + } + + EsMessageMutexRelease(); + } + + // TODO Fade wallpaper in. +} + +////////////////////////////////////////////////////// +// General Desktop: +////////////////////////////////////////////////////// + +void CheckForegroundWindowResponding(EsGeneric) { + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if ((~instance->tab->container->taskBarButton->customStyleState & THEME_STATE_SELECTED) + || instance->tab->container->active != instance->tab + || !instance->processID || !instance->processHandle) { + continue; + } + + EsProcessState state; + EsProcessGetState(instance->processHandle, &state); + + WindowTab *tab = instance->tab; + + if (state.flags & ES_PROCESS_STATE_PINGED) { + if (instance->notResponding) { + // Tab is already in a non-responding state. + } else { + instance->notResponding = true; + instance->restoreEmbeddedWindowHandle = instance->embeddedWindowHandle; + instance->restoreEmbeddedWindowID = instance->embeddedWindowID; + InstanceCrashedTabCreate(CRASHED_TAB_NOT_RESPONDING, instance); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, tab->container->window->handle, instance->embeddedWindowHandle, 0, ES_WINDOW_PROPERTY_EMBED); + } + } else { + if (instance->notResponding) { + instance->notResponding = false; + instance->embeddedWindowHandle = instance->restoreEmbeddedWindowHandle; + instance->restoreEmbeddedWindowHandle = ES_INVALID_HANDLE; + instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, instance->embeddedWindowHandle, 0, 0, 0); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, tab->container->window->handle, instance->embeddedWindowHandle, 0, ES_WINDOW_PROPERTY_EMBED); + tab->Repaint(true); + tab->container->taskBarButton->Repaint(true); + EsAssert(instance->localInstance); + EsInstanceDestroy(instance->localInstance); + instance->localInstance = nullptr; + } else { + EsMessage m; + EsMemoryZero(&m, sizeof(EsMessage)); + m.type = ES_MSG_PING; + EsMessagePostRemote(instance->processHandle, &m); + } + } + + break; + } + + EsTimerSet(2500, CheckForegroundWindowResponding, 0); +} + +void DesktopSetup() { + if (!desktop.setupDesktopUIComplete) { + // Get the installation state. + desktop.installationState = EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("installation_state")); + } + + if (!desktop.setupDesktopUIComplete) { + // Load the theme bitmap. + + EsHandle handle = EsMemoryOpen(ES_THEME_CURSORS_WIDTH * ES_THEME_CURSORS_HEIGHT * 4, EsLiteral(ES_THEME_CURSORS_NAME), ES_FLAGS_DEFAULT); + void *destination = EsObjectMap(handle, 0, ES_THEME_CURSORS_WIDTH * ES_THEME_CURSORS_HEIGHT * 4, ES_MAP_OBJECT_READ_WRITE); + LoadImage(theming.system.in + theming.system.bytes - theming.header->bitmapBytes, theming.header->bitmapBytes, + destination, ES_THEME_CURSORS_WIDTH, ES_THEME_CURSORS_HEIGHT, true); + EsObjectUnmap(destination); + EsHandleClose(handle); + } + + { + // Create the wallpaper window. + + if (!desktop.wallpaperWindow) desktop.wallpaperWindow = EsWindowCreate(nullptr, ES_WINDOW_PLAIN); + EsRectangle screen; + EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, desktop.wallpaperWindow->handle, (uintptr_t) &screen, 0, ES_WINDOW_MOVE_AT_BOTTOM); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, desktop.wallpaperWindow->handle, (uintptr_t) &screen, 0, ES_WINDOW_PROPERTY_OPAQUE_BOUNDS); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, desktop.wallpaperWindow->handle, + ES_WINDOW_SOLID_TRUE | ES_WINDOW_SOLID_NO_BRING_TO_FRONT, 0, ES_WINDOW_PROPERTY_SOLID); + desktop.wallpaperWindow->windowWidth = Width(screen); + desktop.wallpaperWindow->windowHeight = Height(screen); + desktop.wallpaperWindow->doNotPaint = true; + EsThreadCreate(WallpaperLoad, nullptr, 0); + } + + if (desktop.installationState == INSTALLATION_STATE_NONE) { + // Create the taskbar. + + EsWindow *window = desktop.setupDesktopUIComplete ? desktop.taskBar.window : EsWindowCreate(nullptr, ES_WINDOW_PLAIN); + window->messageUser = TaskBarWindowMessage; + window->StartAnimating(); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE | ES_WINDOW_SOLID_NO_ACTIVATE, 0, ES_WINDOW_PROPERTY_SOLID); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, BLEND_WINDOW_MATERIAL_GLASS, 0, ES_WINDOW_PROPERTY_MATERIAL); + + if (!desktop.setupDesktopUIComplete) desktop.taskBar.Initialise(window, ES_CELL_FILL, TaskBarMessage, ES_STYLE_TASK_BAR_BAR); + desktop.taskBar.cName = "task bar"; + EsThemeMetrics metrics = EsElementGetMetrics(&desktop.taskBar); + window->userData = &desktop.taskBar; + + EsRectangle screen; + EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0); + + EsRectangle bounds = screen; + bounds.t = bounds.b - metrics.preferredHeight; + desktop.taskBar.targetBounds = bounds; + + screen.b = bounds.t; + EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_SET, 0, (uintptr_t) &screen, 0, 0); + + bounds.r -= bounds.l, bounds.b -= bounds.t; + bounds.l = bounds.t = 0; + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_PROPERTY_BLUR_BOUNDS); + + if (!desktop.setupDesktopUIComplete) { + EsPanel *panel = EsPanelCreate(&desktop.taskBar, ES_PANEL_HORIZONTAL | ES_CELL_FILL, {}); + + EsButton *newWindowButton = EsButtonCreate(panel, ES_FLAGS_DEFAULT, ES_STYLE_TASK_BAR_NEW_WINDOW); + + EsButtonOnCommand(newWindowButton, [] (EsInstance *, EsElement *, EsCommand *) { + ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, nullptr); + }); + + desktop.taskBar.taskList.Initialise(panel, ES_CELL_FILL, ReorderListMessage, nullptr); + desktop.taskBar.taskList.cName = "task list"; + + EsButton *shutdownButton = EsButtonCreate(panel, ES_FLAGS_DEFAULT, ES_STYLE_TASK_BAR_EXTRA); + EsButtonSetIcon(shutdownButton, ES_ICON_SYSTEM_SHUTDOWN_SYMBOLIC); + + EsButtonOnCommand(shutdownButton, [] (EsInstance *, EsElement *, EsCommand *) { + ShutdownModalCreate(); + }); + + } + } + + if (!desktop.setupDesktopUIComplete) { + // Launch the first application. + + char *firstApplication = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("first_application")); + + if (firstApplication && firstApplication[0]) { + for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) { + if (0 == EsCRTstrcmp(desktop.installedApplications[i]->cName, firstApplication)) { + ApplicationInstanceCreate(desktop.installedApplications[i]->id, nullptr, nullptr); + } + } + } + + EsHeapFree(firstApplication); + } + +#ifdef CHECK_FOR_NOT_RESPONDING + if (!desktop.setupDesktopUIComplete) { + // Setup the timer callback to check if the foreground window is responding. + EsTimerSet(2500, CheckForegroundWindowResponding, 0); + } +#endif + + if (desktop.setupDesktopUIComplete) { + } else if (desktop.installationState == INSTALLATION_STATE_NONE) { +#if 0 + // Play the startup sound. + + EsThreadCreate([] (EsGeneric) { + size_t pathBytes; + char *path = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("startup_sound"), &pathBytes); + + if (path) { + PlaySound(path, pathBytes); + EsHeapFree(path); + } + }, nullptr, 0); +#endif + } else if (desktop.installationState == INSTALLATION_STATE_INSTALLER) { + // Start the installer. + + EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_PLAIN); + + EsRectangle screen; + EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &screen, 0, ES_WINDOW_MOVE_ALWAYS_ON_TOP); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE, 0, ES_WINDOW_PROPERTY_SOLID); + + EsPanel *root = EsPanelCreate(window, ES_PANEL_VERTICAL | ES_CELL_PUSH | ES_CELL_CENTER, ES_STYLE_INSTALLER_ROOT); + EsTextDisplayCreate(root, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, EsLiteral("Essence Installation")); + + // TODO. + } + + desktop.setupDesktopUIComplete = true; +} + +void DesktopMessage2(EsMessage *message, uint8_t *buffer) { + ApplicationInstance *instance = ApplicationInstanceFindByWindowID(message->desktop.windowID); + + if (buffer[0] == DESKTOP_MSG_START_APPLICATION) { + EsApplicationStartupInformation *information = ApplicationStartupInformationParse(buffer + 1, message->desktop.bytes - 1); + if (information) OpenDocumentWithApplication(information); + } else if (!instance) { + // ------------------------------------------------- + // | Messages below here require a valid instance. | + // ------------------------------------------------- + } else if (buffer[0] == DESKTOP_MSG_SET_TITLE || buffer[0] == DESKTOP_MSG_SET_ICON) { + if (buffer[0] == DESKTOP_MSG_SET_TITLE) { + instance->titleBytes = EsStringFormat(instance->title, sizeof(instance->title), "%s", + message->desktop.bytes - 1, buffer + 1); + } else { + if (message->desktop.bytes == 5) { + EsMemoryCopy(&instance->iconID, buffer + 1, sizeof(uint32_t)); + } + } + + instance->tab->Repaint(true); + + if (instance->tab == instance->tab->container->active) { + instance->tab->container->taskBarButton->Repaint(true); + } + } else if (buffer[0] == DESKTOP_MSG_REQUEST_SAVE) { + ApplicationInstanceRequestSave(instance, (const char *) buffer + 1, message->desktop.bytes - 1); + } else if (buffer[0] == DESKTOP_MSG_COMPLETE_SAVE) { + ApplicationInstanceCompleteSave(instance); + } else if (buffer[0] == DESKTOP_MSG_SHOW_IN_FILE_MANAGER) { + // TODO Don't open a new instance if the folder is already open? + OpenDocument *document = desktop.openDocuments.Get(&instance->documentID); + + if (document) { + EsApplicationStartupInformation startupInformation = {}; + startupInformation.flags = ES_APPLICATION_STARTUP_MANUAL_PATH; + startupInformation.filePath = document->path; + startupInformation.filePathBytes = document->pathBytes; + ApplicationInstanceCreate(desktop.fileManager->id, &startupInformation, instance->tab->container); + } + } else if (buffer[0] == DESKTOP_MSG_ANNOUNCE_PATH_MOVED + && (instance->application->permissions & APPLICATION_PERMISSION_ALL_FILES) + && message->desktop.bytes > 1 + sizeof(uintptr_t) * 2) { + InstanceAnnouncePathMoved(instance, buffer, message->desktop.bytes); + } else if (buffer[0] == DESKTOP_MSG_RUN_TEMPORARY_APPLICATION) { + if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION)) { + InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true); + application->temporary = true; + application->hidden = true; + application->useSingleProcess = true; + application->cExecutable = (char *) EsHeapAllocate(message->desktop.bytes, false); + EsMemoryCopy(application->cExecutable, buffer + 1, message->desktop.bytes - 1); + application->cExecutable[message->desktop.bytes - 1] = 0; + static int64_t nextTemporaryID = -1; + application->id = nextTemporaryID--; + application->cName = (char *) EsHeapAllocate(32, false); + for (int i = 1; i < 31; i++) application->cName[i] = (EsRandomU8() % 26) + 'a'; + application->cName[0] = '_', application->cName[31] = 0; + desktop.installedApplications.Add(application); + ApplicationInstanceCreate(application->id, nullptr, nullptr); + } + } else if (buffer[0] == DESKTOP_MSG_REQUEST_SHUTDOWN) { + if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_SHUTDOWN)) { + ShutdownModalCreate(); + } + } +} + +void EmbeddedWindowDestroyed(uint64_t id) { + // TODO Close open documents. + + EsMenuCloseAll(); + ApplicationInstance *instance = ApplicationInstanceFindByWindowID(id, true /* remove if found */); + if (!instance) return; + + ContainerWindow *container = instance->tab->container; + if (instance->processID && !instance->notResponding) EsHandleClose(instance->embeddedWindowHandle); + if (instance->processHandle) EsHandleClose(instance->processHandle); + if (instance->restoreEmbeddedWindowHandle) EsHandleClose(instance->restoreEmbeddedWindowHandle); + instance->embeddedWindowID = 0; + + InstalledApplication *application = instance->application; + + if (application && application->singleProcessHandle && instance->processID) { + EsAssert(application->openInstanceCount); + application->openInstanceCount--; + + if (!application->openInstanceCount && application->useSingleProcess) { + EsMessage m = { ES_MSG_APPLICATION_EXIT }; + EsMessagePostRemote(application->singleProcessHandle, &m); + EsHandleClose(application->singleProcessHandle); + application->singleProcessHandle = ES_INVALID_HANDLE; + ApplicationTemporaryDestroy(application); + application = nullptr; + } + } + + if (container->tabBand->items.Length() == 1) { + EsElementDestroy(container->window); + EsElementDestroy(container->taskBarButton); + desktop.allContainerWindows.FindAndDeleteSwap(container, true); + } else { + if (container->active == instance->tab) { + container->active = nullptr; + + for (uintptr_t i = 0; i < container->tabBand->items.Length(); i++) { + if (container->tabBand->items[i] != instance->tab) continue; + WindowTabActivate((WindowTab *) container->tabBand->items[i ? (i - 1) : 1]); + break; + } + } + + EsElementDestroy(instance->tab); + } + + EsHeapFree(instance); +} + +void DesktopMessage(EsMessage *message) { + if (message->type == ES_MSG_POWER_BUTTON_PRESSED) { + ShutdownModalCreate(); + } else if (message->type == ES_MSG_EMBEDDED_WINDOW_DESTROYED) { + EmbeddedWindowDestroyed(message->desktop.windowID); + } else if (message->type == ES_MSG_DESKTOP) { + if (message->desktop.bytes <= 0x4000) { + uint8_t *buffer = (uint8_t *) EsHeapAllocate(message->desktop.bytes, false); + if (!buffer) return; + EsConstantBufferRead(message->desktop.buffer, buffer); + DesktopMessage2(message, buffer); + EsHeapFree(buffer); + } + + EsHandleClose(message->desktop.buffer); + } else if (message->type == ES_MSG_APPLICATION_CRASH) { + ApplicationInstanceCrashed(message); + } else if (message->type == ES_MSG_PROCESS_TERMINATED) { + ApplicationProcessTerminated(message->crash.pid); + } else if (message->type == ES_MSG_REGISTER_FILE_SYSTEM) { + EsHandle rootDirectory = message->registerFileSystem.rootDirectory; + + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_ALL_FILES) && instance->processHandle) { + message->registerFileSystem.rootDirectory = EsSyscall(ES_SYSCALL_NODE_SHARE, rootDirectory, instance->processHandle, 0, 0); + EsMessagePostRemote(instance->processHandle, message); + } + } + } else if (message->type == ES_MSG_UNREGISTER_FILE_SYSTEM) { + for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) { + ApplicationInstance *instance = desktop.allApplicationInstances[i]; + + if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_ALL_FILES) && instance->processHandle) { + EsMessagePostRemote(instance->processHandle, message); + } + } + } else if (message->type == ES_MSG_SET_SCREEN_RESOLUTION) { + if (desktop.setupDesktopUIComplete) { + DesktopSetup(); // Refresh desktop UI. + } else { + // The screen resolution will be correctly queried in DesktopSetup. + } + } else if (message->type == MSG_SETUP_DESKTOP_UI) { + DesktopSetup(); + } +} + +void DesktopEntry() { + ConfigurationLoad(); + + EsMessage m = { MSG_SETUP_DESKTOP_UI }; + EsMessagePost(nullptr, &m); + + while (true) { + EsMessage *message = EsMessageReceive(); + DesktopMessage(message); + } +} diff --git a/desktop/docs.md b/desktop/docs.md new file mode 100644 index 0000000..534fede --- /dev/null +++ b/desktop/docs.md @@ -0,0 +1,458 @@ +# Random number generator + +## Definitions + +```c +uint8_t EsRandomU8(); +uint64_t EsRandomU64(); +void EsRandomAddEntropy(uint64_t x); +void EsRandomSeed(uint64_t x); +``` + +## Description + +Used to generate pseudo-random numbers. **Note**: the algorithm used is not suitable for cryptographic or statistical applications. These functions are thread-safe. + +`EsRandomU8` generates a single byte of random data, and `EsRandomU64` generates a random `uint64_t`. + +`EsRandomSeed` begins a new sequence of random numbers. For a given seed, subsequent calls to `EsRandomU64` will form the same sequence every time. + +`EsRandomAddEntropy` is used to move to a different point in the sequence of numbers, where there is no obvious link between the input value and the new sequence position. + +## Example + +```c +EsPrint("A random number between 1 and 100 is:\n", 1 + (EsRandomU64() % 100)); +``` + +# Performance timers + +## Definitions + +```c +void EsPerformanceTimerPush(); +double EsPerformanceTimerPop(); +``` + +## Description + +Used to accurately time sections of code. + +`EsPerformanceTimerPush` pushes the current time onto a stack. `EsPerformanceTimerPop` removes the top item, and returns the elapsed time since that item was added. + +The stack must not exceed more than 100 items. + +## Example + +```c +EsPerformanceTimerPush(); + +EsPerformanceTimerPush(); +PerformStep1(); +double timeStep1 = EsPerformanceTimerPop(); + +EsPerformanceTimerPush(); +PerformStep2(); +double timeStep2 = EsPerformanceTimerPop(); + +double timeTotal = EsPerformanceTimerPop(); + +EsPrint("Total time: %F seconds.\n", timeTotal); +EsPrint("\tStep 1 took %F seconds.\n", timeStep1); +EsPrint("\tStep 2 took %F seconds.\n", timeStep2); +``` + +# Threads + +## Definitions + +```c +struct EsThreadInformation { + EsHandle handle; + uint64_t tid; +} + +#define ES_CURRENT_THREAD ((EsHandle) (0x10)) + +typedef void (*EsThreadEntryFunction)(EsGeneric argument); + +EsError EsThreadCreate(EsThreadEntryFunction entryFunction, EsThreadInformation *information, EsGeneric argument); +uint64_t EsThreadGetID(EsHandle thread); +void EsThreadTerminate(EsHandle thread); +``` + +## Description + +Threads are used to execute code in parallel. Threads can be created and terminated. A process can manipulate threads via handles. A handle to a thread can be closed with `EsHandleClose`. Each thread has a unique ID. A thread ID will not be reused until all handles to the thread that previously used the ID have been closed. Threads may not necessarily execute in parallel; the system may simulate the effect of parallel execution by causing the CPU to switch rapidly between which thread it is executing. + +`EsThreadCreate` creates a new thread, starting at the provided `entryFunction`, which will be passed `argument`. After the call, `information` will be filled with a `handle` to the newly created thread, and the thread's unique ID in `tid`. This function returns `ES_SUCCESS` if the thread was successfully created. + +`EsThreadGetID` gets the ID of a thread from its handle. + +`EsThreadTerminate` instructs a thread to terminate. If the thread is executing privileged code at the time of the request, it will complete the prviledged code before terminating. If a thread is waiting on a synchronisation object, such as a mutex or event, it will stop waiting and terminate regardless. **Note**: if a thread owns a mutex or spinlock when it is terminated, it will **not** release the object. + +A thread can always use the handle `ES_CURRENT_THREAD` to access itself. This handle should not be closed. + +## Example + +```c +void MyThread(EsGeneric number) { + while (true) { + EsPrint("Thread %d has ID %d!\n", number.i, EsThreadGetID(ES_CURRENT_THREAD)); + } +} + +for (uintptr_t number = 1; number <= 5; i++) { + EsThreadInformation information; + EsError error = EsThreadCreate(MyThread, &information, number); + + if (error != ES_SUCCESS) { + EsPrint("Thread %d could not be created.\n", number); + } else { + EsPrint("Started thread %d with ID %d.\n", number, information.tid); + + // Close the handle to the thread. + EsHandleClose(information.handle): + } +} +``` + +# Mutexes + +## Definitions + +```c +void EsMutexAcquire(EsMutex *mutex); +void EsMutexDestroy(EsMutex *mutex); +void EsMutexRelease(EsMutex *mutex); +``` + +## Description + +A mutex is a synchronisation primitive. Threads can *acquire* and *release* it. Only one thread can have acquired the mutex at a time. Before another thread can acquire it, the original thread must release it. When a thread tries to acquire an already-acquired mutex, it will wait until the mutex is released, and then proceed to acquire it. + +The `EsMutex` structure contains a mutex. It should be initialised to zero. When the mutex is no longer needed, it can be destroyed with `EsMutexDestroy`. A mutex must not be acquired when it is destroyed. + +To acquire a mutex, call `EsMutexAcquire` with a pointer to the mutex. To release a mutex, call `EsMutexRelease`. A thread must not attempt to acquire a mutex it already owns, and it must not attempt to release a mutex it does not own. + +## Example + +In this example, the function `IncrementCount` can safely be called on different threads at the same time. + +```c +EsMutex mutex; +#define INITIAL_COUNT (10) + +void IncrementCount(int *count) { + EsMutexAcquire(&mutex); + + if (count == 0) { + // count is uninitialised, so initialise it now. + *count = INITIAL_COUNT; + } + + count++; + + EsMutexRelease(&mutex); +} +``` + +Without the mutex, unexpected behaviour may occur, as the effective operation of threads may be arbitrarily interleaved. + +Consider two threads, A and B, that both call `IncrementCount`: +- Thread A enters IncrementCount and sees that `count = 0`. +- Thread B enters IncrementCount and sees that `count = 0`. +- Thread A sets `count` to `10`. +- Thread A increments `count` by `1` to `11`. +- Thread B sets `count` to `10`. +- Thread B increments `count` by `1` to `11`. + +Despite `IncrementCount` being called twice, the count is only incremented once. + +Another possibility is: +- Thread A enters IncrementCount and reads `count` into a register; it has the value `10`. +- Thread A adds `1` to the register, giving `11`. +- Thread B enters IncrementCount and reads `count` into a register; it has the value `10`. +- Thread B adds `1` to the register, giving `11`. +- Thread A stores the value in its register into `count`, `11`. +- Thread B stores the value in its register into `count`, `11`. + +Again, despite `IncrementCount` being called twice, the count is only incremented once. + +## Deadlock + +Mutexes must be acquired in a consistent order. For example, if the following pattern appears in your code: + +1. Acquire mutex X. +2. Acquire mutex Y. +3. Release mutex Y. +4. Release mutex X. + +Then the following pattern must not occur: + +1. Acquire mutex Y. +2. Acquire mutex X. +3. Release mutex X. +4. Release mutex Y. + +To explain why, suppose thread A executes the first pattern and thread B executes the second. +- Thread A acquires mutex X. +- Thread B acquires mutex Y. +- Thread A attempts to acquire mutex Y. Mutex Y is owned by thread B, so thread A starts waiting for thread B to release it. +- Thread B attempts to acquire mutex X. Mutex X is owned by thread A, so thread B starts waiting for thread A to release it. +- Both threads will continue to wait indefinitely for the other to perform an operation. + +# INI files + +## Definitions + +```c +struct EsINIState { + char *buffer, *sectionClass, *section, *key, *value; + size_t bytes, sectionClassBytes, sectionBytes, keyBytes, valueBytes; +}; + +bool EsINIParse(EsINIState *s); +bool EsINIPeek(EsINIState *s); +size_t EsINIFormat(EsINIState *s, char *buffer, size_t bytes); +void EsINIZeroTerminate(EsINIState *s); +``` + +## Description + +To parse an INI file, first initialise a blank EsINIState structure. Set `buffer` to point to the INI data, and set `bytes` to the byte count of the data. Then, call `EsINIParse` repeatedly, until it returns false, indicating it has reached the end of the data. After each call to `EsINIParse`, the fields of `EsINIState` are updated to give the information about the last parsed line in the INI file. `EsINIPeek` is the same as `EsINIParse` except it does not advance to the next line in the INI data. Fields in `EsINIState` that are not applicable to the parsed line are set to empty strings. Comment lines set `key` to `;` and `value` contains the comment itself. Aside for empty strings, the fields in `EsINIState` will always point into the provided buffer. + +For example, the line `[@hello world]` will set `sectionClass` to `"hello"`, `section` to `"world"`, and `key` and `value` to empty strings. When followed by the line `k=v`, `sectionClass` and `section` will retain their previous values, and `key` will be set to `"k"` and `value` will be set to `"v"`. + +`EsINIFormat` formats the contents of `EsINIState` into a buffer. `bytes` gives the size of `buffer`. The return value gives the number of bytes written to `buffer`; this is clipped to the end of the buffer. + +`EsINIZeroTerminate` zero-terminates `sectionClass`, `section`, `key` and `value` in the `EsINIState`. It cannot be used after calling `EsINIPeek`. + +## Example + +```c +size_t fileBytes; +void *file = EsFileReadAll(EsLiteral("|Settings:/Default.ini"), &fileBytes); + +EsINIState s = {}; +s.buffer = file; +s.bytes = fileBytes; + +while (EsINIParse(&s)) { + EsINIZeroTerminate(&s); + EsPrint("section = %z, key = %z, value = %z\n", s.section, s.key, s.value); +} + +EsHeapFree(file); +``` + +# Heap allocator + +## Definitions + +```c +void *EsHeapAllocate(size_t size, bool zeroMemory, EsHeap *heap = ES_NULL); +void EsHeapFree(void *address, size_t expectedSize = 0, EsHeap *heap = ES_NULL); +void *EsHeapReallocate(void *oldAddress, size_t newAllocationSize, bool zeroNewSpace, EsHeap *heap = ES_NULL); +void EsHeapValidate(); +``` + +## Description + +The heap allocator is a general-purpose allocator. It is thread-safe. It is designed to handle regions of memory of arbitrary size, and arbitrary lifetime. + +To allocate memory, call `EsHeapAllocate`. Set `size` to be the size of the region in bytes. Set `zeroMemory` to `true` for the contents of the region to be automatically zeroed, otherwise `false`. Set `heap` to `NULL`; this parameter is reserved. The return value is the address of the start of allocated region. It will be suitably aligned, given the specified region size and current processor architecture. If `size` is `0`, then `NULL` is returned. + +To free memory, call `EsHeapFree`. Set `address` to be the start of the previously allocated region. Optionally, set `expectedSize` to be the size of the allocated region; if non-zero, the system will assert that this value is correct. Set `heap` to `NULL`; this parameter is reserved. If `address` is `NULL`, this function will do nothing. + +To grow or shrink an existing region, call `EsHeapReallocate`. Set `oldAddress` to be the start of the previously allocated region. Set `newAllocationSize` to be new the size of the region. Set `heap` to `NULL`; this parameter is reserved. If `oldAddress` is `NULL`, this call is equivalent to `EsHeapAllocate`. If `newAllocationSize` is `0`, this call is equivalent to `EsHeapFree`. The return value gives the new start address of the region. This may be the same as `oldAddress`, if the region was able to change size in place. If the region was not able to change size in place, the old contents will be copied to the new region. If `zeroNewSpace` is set, and `newAllocationSize` is greater than the previous size of the region, then the newly accessible bytes at the end of the region will be cleared to zero. + +`EsHeapValidate` will check the current process's heap for errors. If it finds an error, it will crash the process. Do not rely on any behaviour of this function. It is only intended to aid debugging memory errors. + +## Example + +```c +int *array = (int *) EsHeapAllocate(sizeof(int) * 10, true); + +for (int i = 0; i < 5; i++) { + array[i] = i + 1; +} + +for (int i = 0; i < 10; i++) { + EsPrint("%d ", array[i]); // Prints 1 2 3 4 5 0 0 0 0 0. +} + +EsPrint("\n"); + +array = (int *) EsHeapReallocate(array, sizeof(int) * 15, true); + +for (int i = 0; i < 15; i++) { + EsPrint("%d ", array[i]); // Prints 1 2 3 4 5 0 0 0 0 0 0 0 0 0 0. +} + +EsHeapFree(array); +``` + +# Rectangles + +## Definitions + +```c +struct EsRectangle { + int32_t l; + int32_t r; + int32_t t; + int32_t b; +}; + +#define ES_RECT_1(x) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (x), (int32_t) (x) }) +#define ES_RECT_1I(x) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (x), (int32_t) -(x) }) +#define ES_RECT_2(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (y), (int32_t) (y) }) +#define ES_RECT_2I(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (y), (int32_t) -(y) }) +#define ES_RECT_2S(x, y) ((EsRectangle) { 0, (int32_t) (x), 0, (int32_t) (y) }) +#define ES_RECT_4(x, y, z, w) ((EsRectangle) { (int32_t) (x), (int32_t) (y), (int32_t) (z), (int32_t) (w) }) +#define ES_RECT_4PD(x, y, w, h) ((EsRectangle) { (int32_t) (x), (int32_t) ((x) + (w)), (int32_t) (y), (int32_t) ((y) + (h)) }) +#define ES_RECT_WIDTH(_r) ((_r).r - (_r).l) +#define ES_RECT_HEIGHT(_r) ((_r).b - (_r).t) +#define ES_RECT_TOTAL_H(_r) ((_r).r + (_r).l) +#define ES_RECT_TOTAL_V(_r) ((_r).b + (_r).t) +#define ES_RECT_ALL(_r) (_r).l, (_r).r, (_r).t, (_r).b +#define ES_RECT_VALID(_r) (ES_RECT_WIDTH(_r) > 0 && ES_RECT_HEIGHT(_r) > 0) + +EsRectangle EsRectangleAdd(EsRectangle a, EsRectangle b); +EsRectangle EsRectangleAddBorder(EsRectangle rectangle, EsRectangle border); +EsRectangle EsRectangleBounding(EsRectangle a, EsRectangle b); +EsRectangle EsRectangleCenter(EsRectangle parent, EsRectangle child); +EsRectangle EsRectangleCut(EsRectangle a, int32_t amount, char side); +EsRectangle EsRectangleFit(EsRectangle parent, EsRectangle child, bool allowScalingUp); +EsRectangle EsRectangleIntersection(EsRectangle a, EsRectangle b); +EsRectangle EsRectangleSplit(EsRectangle *a, int32_t amount, char side, int32_t gap = 0); +EsRectangle EsRectangleSubtract(EsRectangle a, EsRectangle b); +EsRectangle EsRectangleTranslate(EsRectangle a, EsRectangle b); +bool EsRectangleEquals(EsRectangle a, EsRectangle b); +bool EsRectangleContains(EsRectangle a, int32_t x, int32_t y); +``` + +## Description + +`EsRectangle` is used to store an integral rectangular region. `l` gives the offset of the left edge, `r` the right edge, `t` the top edge, and `b` the bottom edge. Note that this means the rectangle does not contain the pixels with x coordinate `r` and y coordinate `b`. The edges are stored are 32-bit signed integers. TODO Diagram. + +`EsRectangleAdd` performs a component-wise sum of two rectangles. + +`EsRectangleAddBorder` is similar to `EsRectangleAdd`, but it negates the `r` and `b` fields of `border` before the addition. TODO Diagram. + +`EsRectangleSubtract` is similar to `EsRectangleAdd`, but it negates all fields of the second rectangle before the addition. TODO Diagram. + +`EsRectangleTranslate` is similar to `EsRectangleAdd`, but it sets `r` to `l` and `b` to `t` in the second rectangle before the addition. TODO Diagram. + +`EsRectangleBounding` computes the smallest possible rectangle that contains both parameters. TODO Diagram. + +`EsRectangleCenter` centers the `child` rectangle within the `parent` rectangle. The origin of `child` is ignored; only its dimensions matter. TODO Diagram. + +`EsRectangleCut` cuts a slice of a rectangle, by moving one of the edges of the input rectangle. The returned rectangle is the slice that was cut off. `side` determines the edge; it is a single character, set to be the same as the name of the field that will be modified. If `amount` is positive, then the edge will be moved inwards by that amount. If `amount` is negative, then the edge will be moved outwards by the absolute value. TODO Diagram. + +`EsRectangleSplit` is similar to `EsRectangleCut`, except it modifies the input rectangle so that it contains the modified rectangle after a side is moved. `gap` may be optionally specified to change the distance between the two returned rectangles. TODO Diagram. + +`EsRectangleFit` resizes and moves the `child` rectangle, while preserving its aspect ratio, so that it fits in, and is centered in, `parent`. If `allowScalingUp` is set to `false`, the function will never resize `child` to a larger size. TODO Diagram. + +`EsRectangleIntersection` computes the intersection of the two parameters. TODO Diagram. + +`EsRectangleEquals` returns `true` if the rectangles are identical, otherwise `false`. + +`EsRectangleContains` returns `true` if the rectangle contains the specified point, otherwise `false`. Recall, as noted above, if `x` is `r` or larger, the point is considered to not be inside the rectangle, and similarly if `y` is `b` or larger. + +`ES_RECT_1` creates a rectangle where all fields are the same value. `ES_RECT_1I` creates a rectangle similarly, except `r` and `b` are negated. + +`ES_RECT_2` creates a rectangle where `l` and `r` are the same value, and `t` and `b` are the same value. `ES_RECT_2I` creates a rectangle similarly, except `r` and `b` are negated. `ES_RECT_2S` creates a rectangle with its top-left corner at `(0, 0)`, with the specified width and height. + +`ES_RECT_4` creates a rectangle with the specified values of `l`, `r`, `t` and `b`. `ES_RECT_4PD` creates a rectangle with its top-left corner at `(x, y)` and a width of `w` and height of `h`. + +`ES_RECT_WIDTH` returns the width of a rectangle. `ES_RECT_HEIGHT` returns the height. `ES_RECT_TOTAL_H` returns the sum of the left and right components. `ES_RECT_TOTAL_V` returns the sum of the top and bottom coponents. + +`ES_RECT_ALL` splits a rectangle into its four components, separated by commas. + +`ES_RECT_VALID` returns `true` if the rectangle has positive width and height, otherwise `false`. + +# Text plans + +## Definitions + +```c +struct EsTextRun { + EsTextStyle style; + uint32_t offset; +}; + +struct EsTextPlanProperties { + EsCString cLanguage; + uint32_t flags; + int maxLines; +}; + +#define ES_TEXT_H_LEFT (1 << 0) +#define ES_TEXT_H_CENTER (1 << 1) +#define ES_TEXT_H_RIGHT (1 << 2) +#define ES_TEXT_V_TOP (1 << 3) +#define ES_TEXT_V_CENTER (1 << 4) +#define ES_TEXT_V_BOTTOM (1 << 5) +#define ES_TEXT_ELLIPSIS (1 << 6) +#define ES_TEXT_WRAP (1 << 7) +#define ES_TEXT_PLAN_SINGLE_USE (1 << 8) +#define ES_TEXT_PLAN_TRIM_SPACES (1 << 9) +#define ES_TEXT_PLAN_RTL (1 << 10) +#define ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES (1 << 11) + +EsTextPlan *EsTextPlanCreate(EsTextPlanProperties *properties, EsRectangle bounds, const char *string, const EsTextRun *textRuns, size_t textRunCount); + +int EsTextPlanGetWidth(EsTextPlan *plan); +int EsTextPlanGetHeight(EsTextPlan *plan); +size_t EsTextPlanGetLineCount(EsTextPlan *plan); + +void EsTextPlanDestroy(EsTextPlan *plan); + +void EsTextPlanReplaceStyleRenderProperties(EsTextPlan *plan, EsTextStyle *style); +``` + +## Description + +Before you can draw text, you first need to create a *text plan*, which contains all the necessary information to draw the text. The advantage of using text plans is that it enables you to draw the same block of text multiple times without needing the text shaping and layout to be recalculated. + +To create a text plan, use `EsTextPlanCreate`. +- `properties`: Contains properties to apply while laying out the text. `cLanguage` is the BCP 47 language tag as a string; if `NULL`, the default language is used. `maxLines` contains the maximum number of lines allowed in the layout, after which lines will be clipped; if `0`, the number of lines will be unlimited. `flags` contains any combination of the following constants: + - `ES_TEXT_H/V_...`: Sets the alignment of the text in the bounds. + - `ES_TEXT_WRAP`: The text is allowed to wrap when it reaches the end of a line. + - `ES_TEXT_ELLIPSIS`: If the text is to be truncated, an ellipsis will be inserted. + - `ES_TEXT_PLAN_SINGLE_USE`: Set to automatically destroy the text plan after the first time it is drawn. + - `ES_TEXT_PLAN_TRIM_SPACES`: Removes any leading and trailing spaces from each line. + - `ES_TEXT_PLAN_RTL`: Sets the default text direction to right-to-left. + - `ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES`: If a word is to long to be word-wrapped, prevent it being from being wrapped regardless, and clip it instead. + - `ES_TEXT_PLAN_NO_FONT_SUBSTITUTION`: Prevents font substitution. Otherwise, glyphs missing from the selected font will be drawn in an automatically-selected font that does support them. +- `bounds`: Gives the width and height of the bounds in which the text will be drawn. Only the dimensions of this rectangle matters. It is unused if word wrapping is disabled, and no text alignment flags are specified. +- `string`: Gives the text string, in UTF-8. This string should remain accessible until the text plan is destroyed. +- `textRuns`: An array of text runs, describing the offset and style of each run in the text. The length of each run is automatically calculated as the difference between its offset and the offset of the next run in the array. The first run should have an offset of `0`. The last item in the array should have its `offset` field set to be the total length of the input string, i.e. the end of the last run. The `style` field of the last item is ignored. +- `textRunCount`: The number of text runs in the array. **Note** that the last item in the array should not be included in this count, because it itself does not describe a run, it is only used to indicate the end position of the actual last run. + +Once a text plan has been created, it can be drawn with `EsDrawText`. Information about the calculated text layout can be accessed with `EsTextPlanGetWidth` (returns the horizontal size of the layout), `EsTextPlanGetHeight` (returns the vertical size), and `EsTextPlanGetLineCount` (returns the number of lines in the layout, including lines containing wrapped text). The text plan can be destroyed with `EsTextPlanDestroy`. + +After a text plan is created, some of the style properties can be replaced. These are called the *render properties*, because they only are used when the text is rendered, and do not affect the layout. These are the `color`, `blur`, `decorations` and `decorationsColor` fields in `EsTextStyle`. They can be replaced using `EsTextPlanReplaceStyleRenderProperties`. Note that this replaces the properties for all text runs in the text plan. + +## Example + +```c +void DrawElementText(EsElement *element, EsRectangle bounds, const char *string, size_t stringBytes) { + EsTextRun textRun[2] = {}; + EsElementGetTextStyle(element, &textRun[0].style); + textRun[0].offset = 0; + textRun[1].offset = stringBytes; + EsTextPlanProperties properties = {}; + EsTextPlan *plan = EsTextPlanCreate(&properties, bounds, string, textRun, 1); + EsDrawText(painter, plan, bounds, nullptr, nullptr); + EsTextPlanDestroy(plan); +} +``` + +# CRT functions + +## Description + +The Essence API provides a number of functions from the C standard library which may be used without requiring the POSIX subsystem. See the API header file for an authoritative list of which functions are available. Please note that the functions may not be fully compliant with standards where it would cause unwanted complexity. The functions are prefixed with `EsCRT`; to use the functions without needing this prefix, define `ES_CRT_WITHOUT_PREFIX` before including the API header. diff --git a/desktop/gui.cpp b/desktop/gui.cpp new file mode 100644 index 0000000..8029490 --- /dev/null +++ b/desktop/gui.cpp @@ -0,0 +1,7259 @@ +// TODO Styling features: +// - Specifying aspect ratio of element. +// - Animation parts (list of keyframes). +// - Ripple animations. +// - Exiting animations. +// - Morph from/to entrance/exit animations. +// TODO Close menus within menus (bug). +// TODO Keyboard navigation - menus; escape to restore default focus. +// TODO Middle click panning. +// TODO Scrollbar middle click and zooming; scroll wheel. +// TODO Textboxes: date/time overlays, keyboard shortcut overlay, custom overlays. +// TODO Breadcrumb bar overflow menu; keep hover after recreating UI. +// TODO Textbox embedded objects. +// TODO Closing windows in menu/access key mode. +// TODO Ignore ES_MSG_LAYOUT in panels if layout.sizeChanged is false? + +// Behaviour of activation clicks. --> Only ignore activation clicks from menus. +// Behaviour of the scroll wheel with regards to focused/hovered elements --> Scroll the hovered element only. + +#define WINDOW_INSET ((int) api.systemConstants[ES_SYSTEM_CONSTANT_WINDOW_INSET]) +#define CONTAINER_TAB_BAND_HEIGHT ((int) api.systemConstants[ES_SYSTEM_CONSTANT_CONTAINER_TAB_BAND_HEIGHT]) +#define BORDER_THICKNESS ((int) api.systemConstants[ES_SYSTEM_CONSTANT_BORDER_THICKNESS]) + +// #define TRACE_LAYOUT + +struct AccessKeyEntry { + char character; + int number; + EsElement *element; + int x, y; +}; + +struct { + // Animations. + EsTimer animationSleepTimer; + bool animationSleep; + Array animatingElements; + + // Input. + bool draggingStarted, mouseButtonDown; + uint8_t leftModifiers, rightModifiers; + int lastClickX, lastClickY, lastClickButton, resizeType; + + // Menus. + bool menuMode; + + // Access keys. + bool accessKeyMode, unhandledAltPress; + + struct { + Array entries; + int numbers[26]; + struct UIStyle *hintStyle; + EsWindow *window; + char typedCharacter; + } accessKeys; + + // Misc. + Array allWindows; + HashTable keyboardShortcutNames; + EsCursorStyle resizeCursor; + bool resizing; + EsElement *insertAfter; + + // Click chains. + double clickChainStartMs; + int clickChainCount; + EsElement *clickChainElement; +} gui; + +struct TableCell { + uint16_t from[2], to[2]; +}; + +// Miscellanous forward declarations. +void UIWindowPaintNow(EsWindow *window, ProcessMessageTiming *timing, bool afterResize); +EsElement *WindowGetMainPanel(EsWindow *window); +int AccessKeyLayerMessage(EsElement *element, EsMessage *message); +void AccessKeyModeExit(); +int ProcessButtonMessage(EsElement *element, EsMessage *message); +void UIMousePressReleased(EsWindow *window, EsMessage *message, bool sendClick); +void UIMaybeRemoveFocusedElement(EsWindow *window); +EsTextStyle TextPlanGetPrimaryStyle(EsTextPlan *plan); +EsElement *UIFindHoverElementRecursively(EsElement *element, int offsetX, int offsetY, EsPoint position); +const EsStyle *UIGetDefaultStyleVariant(const EsStyle *style, EsElement *parent); + +void InspectorSetup(EsWindow *window); +void InspectorNotifyElementEvent(EsElement *element, const char *cCategory, const char *cFormat, ...); +void InspectorNotifyElementCreated(EsElement *element); +void InspectorNotifyElementDestroyed(EsElement *element); +void InspectorNotifyElementMoved(EsElement *element, EsRectangle takenBounds); +void InspectorNotifyElementPainted(EsElement *element, EsPainter *painter); +void InspectorNotifyElementContentChanged(EsElement *element); + +#define UI_STATE_RELAYOUT (1 << 2) +#define UI_STATE_RELAYOUT_CHILD (1 << 3) +#define UI_STATE_DESTROYING (1 << 4) +#define UI_STATE_DESTROYING_CHILD (1 << 5) + +#define UI_STATE_HOVERED (1 << 6) +#define UI_STATE_PRESSED (1 << 7) +#define UI_STATE_STRONG_PRESSED (1 << 8) +#define UI_STATE_FOCUS_WITHIN (1 << 9) +#define UI_STATE_FOCUSED (1 << 10) +#define UI_STATE_LOST_STRONG_FOCUS (1 << 11) +#define UI_STATE_MENU_SOURCE (1 << 12) + +#define UI_STATE_ANIMATING (1 << 13) +#define UI_STATE_ENTERED (1 << 14) +#define UI_STATE_EXITING (1 << 15) +#define UI_STATE_BLOCK_INTERACTION (1 << 16) + +#define UI_STATE_TEMP (1 << 17) +#define UI_STATE_Z_STACK (1 << 18) +#define UI_STATE_COMMAND_BUTTON (1 << 19) +#define UI_STATE_USE_MEASUREMENT_CACHE (1 << 20) +#define UI_STATE_CHECK_VISIBLE (1 << 21) +#define UI_STATE_INSPECTING (1 << 22) + +struct EsElement : EsElementPublic { + EsUICallbackFunction messageClass; + EsElement *parent; + Array children; + uint32_t state; + + uint8_t transitionType; + uint16_t customStyleState; // ORed to the style state in RefreshStyle. + uint16_t previousStyleState; // Set by RefreshStyleState. + uint16_t transitionDurationMs, transitionTimeMs; + uint64_t lastTimeStamp; + UIStyle *currentStyle; + UIStyleKey currentStyleKey; + ThemeAnimation animation; + EsPaintTarget *previousTransitionFrame; + + int width, height, offsetX, offsetY; + uint8_t internalOffsetLeft, internalOffsetRight, internalOffsetTop, internalOffsetBottom; + TableCell tableCell; + + void Destroy(bool manual = true); + void PrintTree(int depth = 0); + + inline size_t GetChildCount() { + return children.Length(); + } + + inline EsElement *GetChild(uintptr_t index) { + EsAssert(index < children.Length()); // Invalid child index. + return children[index]; + } + + inline EsElement *GetChildByZ(uintptr_t index) { + EsMessage m = { ES_MSG_Z_ORDER }; + m.zOrder.index = index, m.zOrder.child = GetChild(index); + if (m.zOrder.child->flags & ES_ELEMENT_NON_CLIENT) return m.zOrder.child; + if (ES_REJECTED == EsMessageSend(this, &m)) return nullptr; + EsAssert(!m.zOrder.child || m.zOrder.child->parent == this); // Child obtained from ES_MSG_Z_ORDER had different parent. + return m.zOrder.child; + } + + void BringToFront() { + for (uintptr_t i = 0; i < parent->children.Length(); i++) { + if (parent->children[i] == this) { + InspectorNotifyElementDestroyed(this); + EsElement *swap = parent->children.Last(); + parent->children.Last() = this; + parent->children[i] = swap; + InspectorNotifyElementCreated(this); + return; + } + } + + EsAssert(false); + } + + bool IsFocusable() { + if ((~flags & ES_ELEMENT_FOCUSABLE) || (flags & ES_ELEMENT_DISABLED) || (state & UI_STATE_DESTROYING)) { + return false; + } + + EsElement *element = this; + + while (element) { + if ((element->flags & ES_ELEMENT_BLOCK_FOCUS) || (element->state & UI_STATE_BLOCK_INTERACTION) || (element->flags & ES_ELEMENT_HIDDEN)) { + return false; + } + + element = element->parent; + } + + return true; + } + + bool RefreshStyleState(); // Returns true if any observed bits have changed. + void RefreshStyle(UIStyleKey *oldStyleKey = nullptr, bool alreadyRefreshStyleState = false, bool force = false); + bool StartAnimating(); + void SetStyle(const EsStyle *stylePart, bool refreshIfChanged = true); + + inline void MaybeRefreshStyle() { + if (RefreshStyleState()) { + RefreshStyle(nullptr, true); + } + } + + inline EsRectangle GetWindowBounds(bool client = true) { return EsElementGetWindowBounds(this, client); } + inline EsRectangle GetScreenBounds(bool client = true) { return EsElementGetScreenBounds(this, client); } + inline EsRectangle GetBounds() { return ES_RECT_2S(width - internalOffsetLeft - internalOffsetRight, height - internalOffsetTop - internalOffsetBottom); } + +#define PAINT_SHADOW (1 << 0) // Paint the shadow layers. +#define PAINT_NO_OFFSET (1 << 1) // Don't add the element's offset to the painter. +#define PAINT_NO_TRANSITION (1 << 2) // Ignore entrance/exit transitions. +#define PAINT_OVERLAY (1 << 3) // Paint the overlay layers. + void InternalPaint(EsPainter *painter, int flags); + + void InternalMove(int _width, int _height, int _offsetX, int _offsetY); // Non-client offset. + void InternalCalculateRepaintRegion(int x, int y, bool forwards, bool overlappedBySibling = false); + bool InternalDestroy(); // Called after processing each message, to destroy any elements marked by ::Destroy. + + int GetWidth(int height); + int GetHeight(int width); + + void Repaint(bool all, EsRectangle region = ES_RECT_1(0) /* client coordinates */); + + void Initialise(EsElement *_parent, uint64_t _flags, EsUICallbackFunction _classCallback, const EsStyle *style); +}; + +struct MeasurementCache { + int width0, width2, width2Height; + int height0, height2, height2Width; + + bool Get(EsMessage *message, uint32_t *state) { + if (~(*state) & UI_STATE_USE_MEASUREMENT_CACHE) { + width0 = 0, width2 = 0, width2Height = 0; + height0 = 0, height2 = 0, height2Width = 0; + *state |= UI_STATE_USE_MEASUREMENT_CACHE; + } + + if (message->type == ES_MSG_GET_WIDTH) { + if (message->measure.height && message->measure.height == width2Height) { + message->measure.width = width2; + } else if (!message->measure.height && width0) { + message->measure.width = width0; + } else { + return false; + } + } else { + if (message->measure.width && message->measure.width == height2Width) { + message->measure.height = height2; + } else if (!message->measure.width && height0) { + message->measure.height = height0; + } else { + return false; + } + } + + return true; + } + + void Store(EsMessage *message) { + if (message->type == ES_MSG_GET_WIDTH) { + if (message->measure.height) { + width2 = message->measure.width; + width2Height = message->measure.height; + } else { + width0 = message->measure.width; + } + } else { + if (message->measure.width) { + height2Width = message->measure.width; + height2 = message->measure.height; + } else { + height0 = message->measure.height; + } + } + } +}; + +struct EsButton : EsElement { + char *label; + size_t labelBytes; + EsGeneric menuItemContext; + uint32_t iconID; + MeasurementCache measurementCache; + EsCommand *command; + EsCommandCallbackFunction onCommand; + EsElement *checkBuddy; +}; + +struct ScrollPane { + EsElement *parent, *pad; + EsScrollbar *bar[2]; + double position[2]; + int64_t limit[2]; + int32_t fixedViewport[2]; + bool dragScrolling; + +#define SCROLL_MODE_NONE (0) // No scrolling takes place on this axis. +#define SCROLL_MODE_HIDDEN (1) // Scrolling takes place, but there is no visible scrollbar. +#define SCROLL_MODE_FIXED (2) // The scrollbar is always visible. +#define SCROLL_MODE_AUTO (3) // The scrollbar is only visible if the content is larger than the viewport. + uint8_t mode[2]; +#define SCROLL_X_DRAG (1 << 0) +#define SCROLL_Y_DRAG (1 << 1) + uint16_t flags; + + void Setup(EsElement *parent, uint8_t xMode, uint8_t yMode, uint16_t flags); + void SetPosition(int axis, double newPosition, bool sendMovedMessage = true); + void Refresh(); + void ReceivedMessage(EsMessage *message); + + inline void SetX(double scrollX, bool sendMovedMessage = true) { SetPosition(0, scrollX, sendMovedMessage); } + inline void SetY(double scrollY, bool sendMovedMessage = true) { SetPosition(1, scrollY, sendMovedMessage); } + + // Internal. + bool RefreshLimit(int axis, int64_t *content); +}; + +struct PanelMovementItem { + EsElement *element; + EsRectangle oldBounds; +}; + +struct EsPanel : EsElement { + // TODO Make this structure smaller? + + ScrollPane scroll; + + const EsStyle *separatorStylePart; + bool addingSeparator; + uint64_t separatorFlags; + + uint16_t bandCount[2]; + EsPanelBand *bands[2]; + uintptr_t tableIndex; + + uint16_t transitionType; + uint32_t transitionTimeMs, + transitionLengthMs; + + bool destroyPreviousAfterTransitionCompletes; + EsElement *switchedTo, + *switchedFrom; + + Array movementItems; + + MeasurementCache measurementCache; + + int GetGapMajor() { + return currentStyle->gapMajor; + } + + int GetGapMinor() { + return currentStyle->gapMinor; + } + + EsRectangle GetInsets() { + return currentStyle->insets; + } + + int GetInsetWidth() { + EsRectangle insets = GetInsets(); + return insets.l + insets.r; + } + + int GetInsetHeight() { + EsRectangle insets = GetInsets(); + return insets.t + insets.b; + } +}; + +struct EsTextDisplay : EsElement { + EsTextPlanProperties properties; + EsTextRun *textRuns; + size_t textRunCount; + char *contents; + + bool usingSyntaxHighlighting; + + MeasurementCache measurementCache; + EsTextPlan *plan; + int planWidth, planHeight; +}; + +struct ColorPickerHost { + struct EsElement *well; + bool *indeterminate; + bool hasOpacity; +}; + +void ColorPickerCreate(EsElement *parent, ColorPickerHost host, uint32_t initialColor, bool showTextbox); + +void HeapDuplicate(void **pointer, const void *data, size_t bytes) { + if (*pointer) { + EsHeapFree(*pointer); + } + + if (!data && !bytes) { + *pointer = nullptr; + } else { + void *buffer = EsHeapAllocate(bytes, false); + EsMemoryCopy(buffer, data, bytes); + *pointer = buffer; + } +} + +// --------------------------------- Windows. + +struct EsWindow : EsElement { + EsHandle handle; + uint32_t windowWidth, windowHeight; + + bool willUpdate, toolbarFillMode, destroyInstanceAfterClose, hasDialog, doNotPaint; + bool restoreOnNextMove, resetPositionOnNextMove, receivedFirstResize, isMaximised; + bool hovering, activated; + bool visualizeRepaints, visualizeLayoutBounds, visualizePaintSteps; // Inspector properties. + + EsPoint mousePosition; + + EsElement *mainPanel, *toolbar; + EsPanel *toolbarSwitcher; + + EsElement *hovered, + *pressed, + *focused, + *inactiveFocus, + *dragged, + *ensureVisible; + + EsButton *enterButton, + *escapeButton, + *defaultEnterButton; + + // An array of elements that we check are visible after every layout. + // e.g. image displays that want to unload the decoded bitmap when they are scrolled off-screen. + // TODO Support a more advanced queueing system for scroll panes asynchronous tasks. + Array checkVisible; + bool processCheckVisible; + + EsElement *dialogOverlay, *dialogPanel; + EsWindowStyle windowStyle; + EsRectangle beforeMaximiseBounds; + + EsRectangle updateRegion; + EsRectangle updateRegionInProgress; // For visualizePaintSteps. + + Array sizeAlternatives; + + EsElement *source; // Menu source. + EsWindow *targetMenu; // The menu that keyboard events should be sent to. + + int32_t announcementBaseY; + double announcementTimeMs; +}; + +struct SizeAlternative { + EsElement *small, *big; + int widthThreshold, heightThreshold; +}; + +// --------------------------------- Container windows. + +#define RESIZE_LEFT (1) +#define RESIZE_RIGHT (2) +#define RESIZE_TOP (4) +#define RESIZE_BOTTOM (8) +#define RESIZE_TOP_LEFT (5) +#define RESIZE_TOP_RIGHT (6) +#define RESIZE_BOTTOM_LEFT (9) +#define RESIZE_BOTTOM_RIGHT (10) +#define RESIZE_MOVE (0) + +#define SNAP_EDGE_MAXIMIZE (1) +#define SNAP_EDGE_LEFT (2) +#define SNAP_EDGE_RIGHT (3) + +void WindowSnap(EsWindow *window, bool restored, bool dragging, uint8_t edge) { + if (window->isMaximised) { + return; + } + + EsRectangle screen; + EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_GET, 0, (uintptr_t) &screen, 0, 0); + + if (!window->restoreOnNextMove && !restored) { + window->beforeMaximiseBounds = EsWindowGetBounds(window); + } + + window->restoreOnNextMove = true; + window->isMaximised = edge == SNAP_EDGE_MAXIMIZE; + + EsRectangle bounds; + + if (edge == SNAP_EDGE_MAXIMIZE) { + bounds.t = screen.t - 16 * theming.scale; + bounds.b = screen.b + 19 * theming.scale; + bounds.l = screen.l - 19 * theming.scale; + bounds.r = screen.r + 19 * theming.scale; + } else if (edge == SNAP_EDGE_LEFT) { + bounds.t = screen.t; + bounds.b = screen.b; + bounds.l = screen.l; + bounds.r = (screen.r + screen.l) / 2; + } else if (edge == SNAP_EDGE_RIGHT) { + bounds.t = screen.t; + bounds.b = screen.b; + bounds.l = (screen.r + screen.l) / 2; + bounds.r = screen.r; + } + + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, + ES_WINDOW_MOVE_DYNAMIC | (edge == SNAP_EDGE_MAXIMIZE ? ES_WINDOW_MOVE_MAXIMISED : 0)); + + if (!dragging) { + window->resetPositionOnNextMove = true; + } +} + +void WindowRestore(EsWindow *window) { + if (!window->restoreOnNextMove) { + return; + } + + window->isMaximised = false; + window->restoreOnNextMove = false; + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &window->beforeMaximiseBounds, 0, ES_WINDOW_MOVE_DYNAMIC); +} + +void WindowChangeBounds(int direction, int newX, int newY, int *originalX, int *originalY, EsWindow *window) { + EsRectangle bounds = EsWindowGetBounds(window), bounds2; + bounds2 = bounds; + + int oldWidth = bounds.r - bounds.l; + int oldHeight = bounds.b - bounds.t; + bool restored = false, canSnap = true; + + if (window->restoreOnNextMove) { + window->restoreOnNextMove = false; + oldWidth = window->beforeMaximiseBounds.r - window->beforeMaximiseBounds.l; + oldHeight = window->beforeMaximiseBounds.b - window->beforeMaximiseBounds.t; + restored = true; + } + + if (direction & RESIZE_LEFT) bounds.l = newX + BORDER_THICKNESS / 2 - WINDOW_INSET; + if (direction & RESIZE_RIGHT) bounds.r = newX - BORDER_THICKNESS / 2 + WINDOW_INSET; + if (direction & RESIZE_TOP) bounds.t = newY + BORDER_THICKNESS / 2 - WINDOW_INSET; + if (direction & RESIZE_BOTTOM) bounds.b = newY - BORDER_THICKNESS / 2 + WINDOW_INSET; + + EsRectangle screen; + EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_GET, 0, (uintptr_t) &screen, 0, 0); + + int newWidth = bounds.r - bounds.l; + int newHeight = bounds.b - bounds.t; + + int windowSnapRange = GetConstantNumber("windowSnapRange"); + int windowMinimumWidth = GetConstantNumber("windowMinimumWidth"); + int windowMinimumHeight = GetConstantNumber("windowMinimumHeight"); + int windowRestoreDragYPosition = GetConstantNumber("windowRestoreDragYPosition"); + + window->isMaximised = false; + + if (newWidth < windowMinimumWidth && direction & RESIZE_LEFT) bounds.l = bounds.r - windowMinimumWidth; + if (newWidth < windowMinimumWidth && direction & RESIZE_RIGHT) bounds.r = bounds.l + windowMinimumWidth; + if (newHeight < windowMinimumHeight && direction & RESIZE_TOP) bounds.t = bounds.b - windowMinimumHeight; + if (newHeight < windowMinimumHeight && direction & RESIZE_BOTTOM) bounds.b = bounds.t + windowMinimumHeight; + + if (direction == RESIZE_MOVE) { + if (newY < screen.t + windowSnapRange && canSnap) { + WindowSnap(window, restored, true, SNAP_EDGE_MAXIMIZE); + return; + } else if (newX < screen.l + windowSnapRange && canSnap) { + WindowSnap(window, restored, true, SNAP_EDGE_LEFT); + return; + } else if (newX >= screen.r - windowSnapRange && canSnap) { + WindowSnap(window, restored, true, SNAP_EDGE_RIGHT); + return; + } else { + if (restored && window->resetPositionOnNextMove) { + // The user previously snapped/maximised the window in a previous operation. + // Therefore, the movement anchor won't be what the user expects. + // Try to put it in the center. + int positionAlongWindow = *originalX - bounds2.l; + int maxPosition = bounds2.r - bounds2.l; + if (positionAlongWindow > maxPosition - oldWidth / 2) *originalX = gui.lastClickX = positionAlongWindow - maxPosition + oldWidth; + else if (positionAlongWindow > oldWidth / 2) *originalX = gui.lastClickX = oldWidth / 2; + *originalY = gui.lastClickY = windowRestoreDragYPosition; + window->resetPositionOnNextMove = false; + } + + bounds.l = newX - *originalX; + bounds.t = newY - *originalY; + bounds.r = bounds.l + oldWidth; + bounds.b = bounds.t + oldHeight; + } + } else { + window->resetPositionOnNextMove = window->restoreOnNextMove = false; + } + + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_DYNAMIC); +} + +int ProcessWindowBorderMessage(EsWindow *window, EsMessage *message, EsRectangle bounds, int from, int to) { + if (message->type == ES_MSG_GET_CURSOR) { + EsPoint position = EsMouseGetPosition(window); + message->cursorStyle = ES_CURSOR_NORMAL; + + if (window->isMaximised) { + gui.resizeType = 0; + gui.resizeCursor = message->cursorStyle; + } else { + bool left = position.x < to, right = position.x >= bounds.r - to, + top = position.y < to, bottom = position.y >= bounds.b - to; + + if (gui.resizing) { + message->cursorStyle = gui.resizeCursor; + } else if (position.x < from || position.y < from + || position.x >= bounds.r - from || position.y >= bounds.b - from) { + } else if ((right && top) || (bottom && left)) { + message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_1; + } else if ((left && top) || (bottom && right)) { + message->cursorStyle = ES_CURSOR_RESIZE_DIAGONAL_2; + } else if (left || right) { + message->cursorStyle = ES_CURSOR_RESIZE_HORIZONTAL; + } else if (top || bottom) { + message->cursorStyle = ES_CURSOR_RESIZE_VERTICAL; + } + + if (!window->pressed && !gui.mouseButtonDown) { + gui.resizeType = (left ? RESIZE_LEFT : 0) | (right ? RESIZE_RIGHT : 0) | (top ? RESIZE_TOP : 0) | (bottom ? RESIZE_BOTTOM : 0); + gui.resizeCursor = message->cursorStyle; + } + } + + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + EsPoint screenPosition = EsMouseGetPosition(nullptr); + + if (!window->isMaximised || gui.resizeType == RESIZE_MOVE) { + WindowChangeBounds(gui.resizeType, + screenPosition.x, screenPosition.y, + &gui.lastClickX, &gui.lastClickY, window); + gui.resizing = true; + } + } else if (message->type == ES_MSG_MOUSE_LEFT_UP) { + if (window->restoreOnNextMove) { + window->resetPositionOnNextMove = true; + } + + gui.resizing = false; + } else { + return 0; + } + + return ES_HANDLED; +} + +// --------------------------------- Dialogs. + +void EsDialogClose(EsWindow *window) { + EsMessageMutexCheck(); + EsAssert(window->hasDialog); // The window does not have an open dialog. + window->dialogPanel->Destroy(); + window->dialogOverlay->Destroy(); // TODO This looks bad if we immediately open a new dialog. But maybe it'll look alright with exiting transitions? + window->dialogOverlay = window->dialogPanel = nullptr; + window->children[0]->children[0]->flags &= ~ES_ELEMENT_BLOCK_FOCUS; + window->children[1]->state &= ~UI_STATE_BLOCK_INTERACTION; + window->hasDialog = false; +} + +EsElement *EsDialogShowAlert(EsWindow *window, const char *title, ptrdiff_t titleBytes, + const char *content, ptrdiff_t contentBytes, uint32_t iconID, uint32_t flags) { + EsElement *dialog = EsDialogShow(window); + EsPanel *heading = EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_DIALOG_HEADING); + + if (iconID) { + EsIconDisplayCreate(heading, ES_FLAGS_DEFAULT, {}, iconID); + } + + EsTextDisplayCreate(heading, ES_CELL_H_FILL | ES_CELL_V_CENTER, ES_STYLE_TEXT_HEADING2, + title, titleBytes)->cName = "dialog heading"; + EsTextDisplayCreate(EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_VERTICAL, ES_STYLE_DIALOG_CONTENT), + ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, + content, contentBytes)->cName = "dialog contents"; + + EsPanel *buttonArea = EsPanelCreate(dialog, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE, ES_STYLE_DIALOG_BUTTON_AREA); + + if (flags & ES_DIALOG_ALERT_OK_BUTTON) { + EsButton *button = EsButtonCreate(buttonArea, ES_BUTTON_DEFAULT, 0, "OK"); + EsButtonOnCommand(button, [] (EsInstance *instance, EsElement *, EsCommand *) { EsDialogClose(instance->window); }); + EsElementFocus(button); + } + + return buttonArea; +} + +EsElement *EsDialogShow(EsWindow *window) { + EsAssert(window->windowStyle == ES_WINDOW_NORMAL); // Can only show dialogs on normal windows. + EsAssert(!window->hasDialog); // Cannot nest dialogs. + + EsElement *mainStack = window->children[0]; + mainStack->children[0]->flags |= ES_ELEMENT_BLOCK_FOCUS; + window->children[1]->state |= UI_STATE_BLOCK_INTERACTION; + window->hasDialog = true; + window->dialogOverlay = EsPanelCreate(mainStack, ES_CELL_FILL, ES_STYLE_PANEL_MODAL_OVERLAY); + window->dialogOverlay->cName = "modal overlay"; + window->dialogPanel = EsPanelCreate(mainStack, ES_PANEL_VERTICAL | ES_CELL_CENTER | ES_CELL_SHRINK, ES_STYLE_PANEL_DIALOG_ROOT); + window->dialogPanel->cName = "dialog"; + + return window->dialogPanel; +} + +// --------------------------------- Windows. + +void UIWindowNeedsUpdate(EsWindow *window) { + if (!window->willUpdate) { + EsMessage m = { ES_MSG_UPDATE_WINDOW }; + // Don't use the userland posted message queue, since we don't want this to block WM messages. + EsSyscall(ES_SYSCALL_MESSAGE_POST, (uintptr_t) &m, (uintptr_t) window, ES_CURRENT_PROCESS, 0); + window->willUpdate = true; + } +} + +void UIWindowDestroy(EsWindow *window) { + gui.allWindows.FindAndDeleteSwap(window, true); + AccessKeyModeExit(); + EsSyscall(ES_SYSCALL_WINDOW_CLOSE, window->handle, 0, 0, 0); + EsHandleClose(window->handle); + window->checkVisible.Free(); + window->handle = ES_INVALID_HANDLE; +} + +EsElement *WindowGetMainPanel(EsWindow *window) { + if (window->windowStyle == ES_WINDOW_MENU) { + if (!window->children[0]->GetChildCount()) { + EsMenuNextColumn((EsMenu *) window, ES_FLAGS_DEFAULT); + } + + return window->children[0]->GetChild(window->children[0]->GetChildCount() - 1); + } + + return window->mainPanel; +} + +int ProcessRootMessage(EsElement *element, EsMessage *message) { + EsWindow *window = (EsWindow *) element; + EsRectangle bounds = window->GetBounds(); + int response = 0; + + if (window->windowStyle == ES_WINDOW_CONTAINER) { + if (message->type == ES_MSG_LAYOUT) { + EsElementMove(window->GetChild(0), WINDOW_INSET, WINDOW_INSET, bounds.r - WINDOW_INSET * 2, CONTAINER_TAB_BAND_HEIGHT); + } else { + response = ProcessWindowBorderMessage(window, message, bounds, WINDOW_INSET - BORDER_THICKNESS, WINDOW_INSET); + } + } else if (window->windowStyle == ES_WINDOW_MENU) { + if (message->type == ES_MSG_PAINT_BACKGROUND) { + EsDrawClear(message->painter, message->painter->clip); + } else if (message->type == ES_MSG_LAYOUT) { + if (window->GetChildCount()) { + EsElementMove(window->GetChild(0), 0, 0, bounds.r, bounds.b); + } + } + } else if (window->windowStyle == ES_WINDOW_INSPECTOR) { + if (message->type == ES_MSG_LAYOUT) { + if (window->GetChildCount()) { + EsElementMove(window->GetChild(0), BORDER_THICKNESS, BORDER_THICKNESS + 30, + bounds.r - BORDER_THICKNESS * 2, bounds.b - BORDER_THICKNESS * 2 - 30); + } + } else { + response = ProcessWindowBorderMessage(window, message, bounds, 0, BORDER_THICKNESS); + } + } else if (window->windowStyle == ES_WINDOW_NORMAL) { + if (message->type == ES_MSG_LAYOUT) { + if (window->GetChildCount()) { + EsElement *toolbar = window->toolbarSwitcher; + + if (window->toolbarFillMode) { + EsElementMove(toolbar, 0, 0, bounds.r, bounds.b); + } else { + int toolbarHeight = toolbar->GetChildCount() ? toolbar->GetHeight(bounds.r) + : toolbar->currentStyle->metrics->minimumHeight; + EsElementMove(window->GetChild(0), 0, toolbarHeight, bounds.r, bounds.b - toolbarHeight); + EsElementMove(toolbar, 0, 0, bounds.r, toolbarHeight); + EsElementMove(window->GetChild(2), 0, 0, bounds.r, bounds.b); + } + } + } + } else if (window->windowStyle == ES_WINDOW_TIP || window->windowStyle == ES_WINDOW_PLAIN) { + if (message->type == ES_MSG_LAYOUT) { + if (window->GetChildCount()) { + EsElementMove(window->GetChild(0), 0, 0, bounds.r, bounds.b); + } + } + } + + if (message->type == ES_MSG_DESTROY) { + if (window->windowStyle != ES_WINDOW_NORMAL) { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_FLAGS_DEFAULT, 0, ES_WINDOW_PROPERTY_SOLID); + } + + if (window->windowStyle == ES_WINDOW_MENU) { + window->source->state &= ~UI_STATE_MENU_SOURCE; + window->source->MaybeRefreshStyle(); + EsAssert(window->source->window->targetMenu == window); + window->source->window->targetMenu = nullptr; + } + } + + return response; +} + +EsWindow *EsWindowCreate(EsInstance *instance, EsWindowStyle style) { + EsMessageMutexCheck(); + + for (uintptr_t i = 0; i < gui.allWindows.Length(); i++) { + UIMousePressReleased(gui.allWindows[i], nullptr, false); + } + + EsWindow *window = (EsWindow *) EsHeapAllocate(sizeof(EsWindow), true); + gui.allWindows.Add(window); + window->instance = instance; + + if (style == ES_WINDOW_NORMAL) { + window->handle = ((APIInstance *) instance->_private)->mainWindowHandle; + } else { + window->handle = EsSyscall(ES_SYSCALL_WINDOW_CREATE, style, 0, (uintptr_t) window, 0); + } + + window->Initialise(nullptr, ES_CELL_FILL, ProcessRootMessage, nullptr); + window->cName = "window"; + window->window = window; + window->width = window->windowWidth, window->height = window->windowHeight; + window->hovered = window; + window->windowStyle = style; + window->RefreshStyle(); + + if (style == ES_WINDOW_NORMAL) { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0, (uintptr_t) window, ES_WINDOW_PROPERTY_OBJECT); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0xFF000000 | GetConstantNumber("windowFillColor"), 0, ES_WINDOW_PROPERTY_RESIZE_CLEAR_COLOR); + window->activated = true; + EsPanel *panel = EsPanelCreate(window, ES_ELEMENT_NON_CLIENT | ES_CELL_FILL | ES_PANEL_Z_STACK, ES_STYLE_PANEL_NORMAL_WINDOW_ROOT); + panel->cName = "window stack"; + window->mainPanel = EsPanelCreate(panel, ES_CELL_FILL, ES_STYLE_PANEL_NORMAL_WINDOW_ROOT); + window->mainPanel->cName = "window root"; + window->toolbarSwitcher = EsPanelCreate(window, ES_ELEMENT_NON_CLIENT | ES_PANEL_SWITCHER | ES_CELL_FILL, ES_STYLE_PANEL_TOOLBAR_ROOT); + window->toolbarSwitcher->cName = "toolbar"; + EsElement *accessKeyLayer = EsCustomElementCreate(window, ES_ELEMENT_NON_CLIENT | ES_CELL_FILL | ES_ELEMENT_NO_HOVER, nullptr); + accessKeyLayer->cName = "access key layer"; + accessKeyLayer->messageUser = AccessKeyLayerMessage; + window->state |= UI_STATE_Z_STACK; + } else if (style == ES_WINDOW_CONTAINER) { + window->windowWidth = GetConstantNumber("windowDefaultWidth"); + window->windowHeight = GetConstantNumber("windowDefaultHeight"); + static int cascadeX = -1, cascadeY = -1; + EsRectangle workArea; + EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_GET, 0, (uintptr_t) &workArea, 0, 0); + int cascadeMargin = GetConstantNumber("windowCascadeMargin"); + int cascadeOffset = GetConstantNumber("windowCascadeOffset"); + if (cascadeX == -1 || cascadeX + (int) window->windowWidth > workArea.r - cascadeMargin) cascadeX = workArea.l + cascadeMargin; + if (cascadeY == -1 || cascadeY + (int) window->windowHeight > workArea.b - cascadeMargin) cascadeY = workArea.t + cascadeMargin; + EsRectangle bounds = ES_RECT_4(cascadeX, cascadeX + window->windowWidth, cascadeY, cascadeY + window->windowHeight); + if (bounds.r > workArea.r - cascadeMargin) bounds.r = workArea.r - cascadeMargin; + if (bounds.b > workArea.b - cascadeMargin) bounds.b = workArea.b - cascadeMargin; + cascadeX += cascadeOffset, cascadeY += cascadeOffset; + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_FLAGS_DEFAULT); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE, 10, ES_WINDOW_PROPERTY_SOLID); + window->mainPanel = EsPanelCreate(window, ES_ELEMENT_NON_CLIENT | ES_CELL_FILL, ES_STYLE_PANEL_CONTAINER_WINDOW_ROOT); + window->SetStyle(ES_STYLE_CONTAINER_WINDOW_ACTIVE); + } else if (style == ES_WINDOW_INSPECTOR) { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE, 0, ES_WINDOW_PROPERTY_SOLID); + window->SetStyle(ES_STYLE_PANEL_INSPECTOR_WINDOW_ROOT); + window->mainPanel = EsPanelCreate(window, ES_ELEMENT_NON_CLIENT | ES_CELL_FILL, ES_STYLE_PANEL_INSPECTOR_WINDOW_CONTAINER); + EsRectangle bounds = { 10, 600, 10, 500 }; + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN); + } else if (style == ES_WINDOW_TIP || style == ES_WINDOW_PLAIN) { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, style == ES_WINDOW_PLAIN ? ES_WINDOW_SOLID_TRUE : ES_FLAGS_DEFAULT, 0, ES_WINDOW_PROPERTY_SOLID); + window->mainPanel = EsPanelCreate(window, ES_ELEMENT_NON_CLIENT | ES_CELL_FILL, nullptr); + } else if (style == ES_WINDOW_MENU) { + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE, 9 * theming.scale, ES_WINDOW_PROPERTY_SOLID); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, BLEND_WINDOW_MATERIAL_GLASS, 0, ES_WINDOW_PROPERTY_MATERIAL); + + window->SetStyle(ES_STYLE_PANEL_MENU_ROOT); + + EsPanel *panel = EsPanelCreate(window, ES_ELEMENT_NON_CLIENT | ES_PANEL_HORIZONTAL | ES_CELL_FILL, ES_STYLE_PANEL_MENU_CONTAINER); + panel->cName = "menu"; + panel->separatorStylePart = ES_STYLE_MENU_SEPARATOR_VERTICAL; + panel->separatorFlags = ES_CELL_V_FILL; + + panel->messageUser = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_PAINT) { + EsPainter *painter = message->painter; + EsRectangle blurBounds = element->GetWindowBounds(); + blurBounds = EsRectangleAddBorder(blurBounds, ((UIStyle *) painter->style)->insets); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, element->window->handle, (uintptr_t) &blurBounds, 0, ES_WINDOW_PROPERTY_BLUR_BOUNDS); + } + + return 0; + }; + + window->mainPanel = panel; + } + + if (style == ES_WINDOW_INSPECTOR) { + InspectorSetup(window); + } + + return window; +} + +void EsWindowAddSizeAlternative(EsWindow *window, EsElement *small, EsElement *big, int widthThreshold, int heightThreshold) { + EsMessageMutexCheck(); + EsAssert(small->window == window && big->window == window); + + SizeAlternative alternative = {}; + alternative.small = small, alternative.big = big; + alternative.widthThreshold = widthThreshold, alternative.heightThreshold = heightThreshold; + window->sizeAlternatives.Add(alternative); + + bool belowThreshold = window->width < widthThreshold || window->height < heightThreshold; + EsElementSetHidden(small, !belowThreshold); + EsElementSetHidden(big, belowThreshold); +} + +// --------------------------------- Menus. + +struct EsMenu : EsWindow {}; + +void EsMenuAddSeparator(EsMenu *menu) { + EsCustomElementCreate(menu, ES_CELL_H_FILL, ES_STYLE_MENU_SEPARATOR_HORIZONTAL)->cName = "menu separator"; +} + +void EsMenuNextColumn(EsMenu *menu, uint64_t flags) { + EsPanelCreate(menu->children[0], ES_PANEL_VERTICAL | ES_CELL_V_TOP | flags, ES_STYLE_PANEL_MENU_COLUMN); +} + +EsElement *EsMenuGetSource(EsMenu *menu) { + return ((EsWindow *) menu)->source; +} + +EsMenu *EsMenuCreate(EsElement *source, uint64_t flags) { + EsWindow *menu = (EsWindow *) EsWindowCreate(source->instance, ES_WINDOW_MENU); + menu->flags |= flags; + menu->activated = true; + menu->source = source; + return (EsMenu *) menu; +} + +void EsMenuAddCommandsFromToolbar(EsMenu *menu, EsElement *element) { + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + EsElement *child = element->GetChild(i); + + if (child->flags & ES_ELEMENT_NON_CLIENT) { + continue; + } + + if (child->messageClass == ProcessButtonMessage) { + EsButton *button = (EsButton *) child; + + if (button->command) { + EsMenuAddCommand(menu, button->command->check, button->label, button->labelBytes, button->command); + } + } else { + EsMenuAddCommandsFromToolbar(menu, element); + } + } +} + +void EsMenuShow(EsMenu *menu, int fixedWidth, int fixedHeight) { + EsAssert(!menu->source->window->targetMenu); + EsAssert(~menu->source->state & UI_STATE_MENU_SOURCE); + menu->source->state |= UI_STATE_MENU_SOURCE; + menu->source->MaybeRefreshStyle(); + menu->source->window->targetMenu = menu; + + EsRectangle menuInsets = menu->GetChild(0)->currentStyle->insets; + if (fixedWidth) fixedWidth += menuInsets.l + menuInsets.r; + if (fixedHeight) fixedHeight += menuInsets.t + menuInsets.b; + int width = fixedWidth ?: menu->GetChild(0)->GetWidth(fixedHeight); + int height = menu->GetChild(0)->GetHeight(width); + + if (fixedHeight) { + if (menu->flags & ES_MENU_MAXIMUM_HEIGHT) { + if (height >= fixedHeight) { + height = fixedHeight; + } + } else { + height = fixedHeight; + } + } + + EsPoint position; + + if (~menu->flags & ES_MENU_AT_CURSOR) { + EsRectangle bounds = menu->source->GetScreenBounds(false); + + position = ES_POINT(bounds.l - (width + menuInsets.l - menuInsets.r) / 2 + menu->source->width / 2, + bounds.b - menuInsets.t); + + // Try to the keep the menu within the bounds of the source window. + + EsRectangle windowBounds = menu->source->window->GetScreenBounds(); + + if (position.x + width >= windowBounds.r) { + position.x = windowBounds.r - width - 1; + } + + if (position.x < windowBounds.l) { + position.x = windowBounds.l; + } + + if (position.y + height >= windowBounds.b) { + position.y = windowBounds.b - height - 1; + } + + if (position.y < windowBounds.t) { + position.y = windowBounds.t; + } + } else { + position = EsMouseGetPosition(); + position.x -= menuInsets.l, position.y -= menuInsets.t; + } + + menu->width = width, menu->height = height; + EsRectangle bounds = ES_RECT_4(position.x, position.x + width, position.y, position.y + height); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, menu->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN | ES_WINDOW_MOVE_ALWAYS_ON_TOP); +} + +void EsMenuCloseAll() { + for (uintptr_t i = 0; i < gui.allWindows.Length(); i++) { + if (gui.allWindows[i]->windowStyle == ES_WINDOW_MENU) { + EsElementDestroy(gui.allWindows[i]); + } + } +} + +// --------------------------------- File menu. + +const EsStyle styleFileMenuDocumentInformationPanel1 = { + .metrics = { + .mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR, + .insets = ES_RECT_4(10, 10, 5, 5), + .gapMajor = 5, + }, +}; + +const EsStyle styleFileMenuDocumentInformationPanel2 = { + .metrics = { + .mask = ES_THEME_METRICS_GAP_MAJOR, + .gapMajor = 5, + }, +}; + +void FileMenuCreate(EsInstance *_instance, EsElement *element, EsCommand *) { + // TODO Make this user-customizable? + + // const EsFileMenuSettings *settings = (const EsFileMenuSettings *) element->userData.p; + APIInstance *instance = (APIInstance *) _instance->_private; + EsAssert(instance->instanceClass == ES_INSTANCE_CLASS_EDITOR); + EsInstanceClassEditorSettings *editorSettings = &instance->editorSettings; + EsMenu *menu = EsMenuCreate(element, ES_FLAGS_DEFAULT); + EsPanel *panel1 = EsPanelCreate(menu, ES_PANEL_HORIZONTAL | ES_CELL_H_LEFT, &styleFileMenuDocumentInformationPanel1); + + bool newDocument = !instance->startupInformation || !instance->startupInformation->filePath; + + { + // TODO Get this icon from the file type database? + // We'll probably need Desktop to send this via EsApplicationStartupInformation and when the file is renamed. + + EsIconDisplayCreate(panel1, ES_FLAGS_DEFAULT, 0, editorSettings->documentIconID); + EsSpacerCreate(panel1, ES_FLAGS_DEFAULT, 0, 5, 0); + + EsPanel *panel2 = EsPanelCreate(panel1, ES_FLAGS_DEFAULT, &styleFileMenuDocumentInformationPanel2); + EsPanel *panel3 = EsPanelCreate(panel2, ES_PANEL_HORIZONTAL | ES_PANEL_H_LEFT, &styleFileMenuDocumentInformationPanel2); + + if (newDocument) { + EsTextDisplayCreate(panel3, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_LABEL, + editorSettings->newDocumentTitle, editorSettings->newDocumentTitleBytes); + } else { + EsTextDisplayCreate(panel3, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_LABEL, + instance->startupInformation->filePath, instance->startupInformation->filePathBytes); + } + + EsButton *renameButton = EsButtonCreate(panel3, ES_BUTTON_TOOLBAR); // TODO. + EsButtonSetIcon(renameButton, ES_ICON_DOCUMENT_EDIT_SYMBOLIC); + + if (!newDocument) { + EsPanel *panel4 = EsPanelCreate(panel2, ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_CELL_H_LEFT, &styleFileMenuDocumentInformationPanel2); + EsPanelSetBands(panel4, 2 /* columns */); + + char buffer[64]; + size_t bytes; + + bytes = EsStringFormat(buffer, sizeof(buffer), "%D", EsFileStoreGetSize(instance->fileStore)); + EsTextDisplayCreate(panel4, ES_CELL_H_RIGHT, ES_STYLE_TEXT_LABEL_SECONDARY, INTERFACE_STRING(CommonFileMenuFileSize)); + EsTextDisplayCreate(panel4, ES_CELL_H_LEFT, ES_STYLE_TEXT_LABEL, buffer, bytes); + + // TODO Modification date, author, etc. + } + } + + EsMenuAddSeparator(menu); + + if (instance->instanceClass == ES_INSTANCE_CLASS_EDITOR) { + if (instance->commandSave.disabled) { + EsMenuAddItem(menu, ES_ELEMENT_DISABLED, INTERFACE_STRING(CommonFileUnchanged)); + } else { + EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonFileSave), &instance->commandSave); + } + + EsMenuAddItem(menu, newDocument ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonFileMakeCopy)); // TODO. + EsMenuAddSeparator(menu); + } + + EsMenuAddItem(menu, newDocument ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonFileShare)); // TODO. + EsMenuAddItem(menu, newDocument ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonFileVersionHistory)); // TODO. + EsMenuAddCommand(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(CommonFileShowInFileManager), &instance->commandShowInFileManager); + EsMenuShow(menu); +} + +void EsToolbarAddFileMenu(EsElement *element, const EsFileMenuSettings *settings) { + EsButton *button = EsButtonCreate(element, ES_BUTTON_DROPDOWN, 0, INTERFACE_STRING(CommonFileMenu)); + button->accessKey = 'F'; + button->userData = (void *) settings; + EsButtonOnCommand(button, FileMenuCreate); +} + +// --------------------------------- Paint targets. + +bool EsPaintTargetTake(EsPaintTarget *target, size_t width, size_t height, bool hasAlphaChannel = true) { + EsMemoryZero(target, sizeof(EsPaintTarget)); + target->fullAlpha = hasAlphaChannel; + target->width = width; + target->height = height; + target->stride = width * 4; + target->bits = EsHeapAllocate(target->stride * target->height, true); + return target->bits != nullptr; +} + +EsPaintTarget *EsPaintTargetCreate(size_t width, size_t height, bool hasAlphaChannel) { + EsPaintTarget *target = (EsPaintTarget *) EsHeapAllocate(sizeof(EsPaintTarget), true); + + if (!target) { + return nullptr; + } else if (EsPaintTargetTake(target, width, height, hasAlphaChannel)) { + return target; + } else { + EsHeapFree(target); + return nullptr; + } +} + +EsPaintTarget *EsPaintTargetCreateFromBitmap(uint32_t *bits, size_t width, size_t height, bool hasAlphaChannel) { + EsPaintTarget *target = (EsPaintTarget *) EsHeapAllocate(sizeof(EsPaintTarget), true); + + if (!target) { + return nullptr; + } + + target->bits = bits; + target->width = width; + target->height = height; + target->stride = width * 4; + target->fullAlpha = hasAlphaChannel; + target->fromBitmap = true; + return target; +} + +void EsPaintTargetClear(EsPaintTarget *t) { + EsPainter painter = {}; + painter.clip.r = t->width; + painter.clip.b = t->height; + painter.target = t; + EsDrawClear(&painter, painter.clip); +} + +void EsPaintTargetReturn(EsPaintTarget *target) { + EsHeapFree(target->bits); +} + +void EsPaintTargetDestroy(EsPaintTarget *target) { + if (!target->fromBitmap) EsHeapFree(target->bits); + EsHeapFree(target); +} + +void EsPaintTargetEndDirectAccess(EsPaintTarget *target) { + // Don't need to do anything, currently. + (void) target; +} + +void EsPaintTargetGetSize(EsPaintTarget *target, size_t *width, size_t *height) { + if (width) *width = target->width; + if (height) *height = target->height; +} + +void EsPaintTargetStartDirectAccess(EsPaintTarget *target, uint32_t **bits, size_t *width, size_t *height, size_t *stride) { + if (bits) *bits = (uint32_t *) target->bits; + if (width) *width = target->width; + if (height) *height = target->height; + if (stride) *stride = target->stride; +} + +// --------------------------------- Transitions. + +EsRectangle UIGetTransitionEffectRectangle(EsRectangle bounds, EsTransitionType type, double progress, bool to) { + int width = Width(bounds), height = Height(bounds); + double ratio = (double) height / (double) width; + + if (!to) { + if (type == ES_TRANSITION_SLIDE_UP) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t - progress * height / 2, bounds.b - progress * height / 2); + } else if (type == ES_TRANSITION_SLIDE_DOWN) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + progress * height / 2, bounds.b + progress * height / 2); + } else if (type == ES_TRANSITION_COVER_UP) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t, bounds.b); + } else if (type == ES_TRANSITION_COVER_DOWN) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t, bounds.b); + } else if (type == ES_TRANSITION_SQUISH_UP || type == ES_TRANSITION_REVEAL_UP) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t, bounds.t + height * (1 - progress)); + } else if (type == ES_TRANSITION_SQUISH_DOWN || type == ES_TRANSITION_REVEAL_DOWN) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + height * progress, bounds.b); + } else if (type == ES_TRANSITION_ZOOM_OUT) { + return ES_RECT_4(bounds.l + 20 * progress, bounds.r - 20 * progress, + bounds.t + 20 * progress * ratio, bounds.b - 20 * progress * ratio); + } else if (type == ES_TRANSITION_ZOOM_IN) { + return ES_RECT_4(bounds.l - 20 * progress, bounds.r + 20 * progress, + bounds.t - 20 * progress * ratio, bounds.b + 20 * progress * ratio); + } else if (type == ES_TRANSITION_ZOOM_OUT_LIGHT) { + return ES_RECT_4(bounds.l + 5 * progress, bounds.r - 5 * progress, + bounds.t + 5 * progress * ratio, bounds.b - 5 * progress * ratio); + } else if (type == ES_TRANSITION_ZOOM_IN_LIGHT) { + return ES_RECT_4(bounds.l - 5 * progress, bounds.r + 5 * progress, + bounds.t - 5 * progress * ratio, bounds.b + 5 * progress * ratio); + } else if (type == ES_TRANSITION_FADE_IN || type == ES_TRANSITION_FADE_OUT) { + return bounds; + } else if (type == ES_TRANSITION_SLIDE_UP_OVER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t - progress * height / 4, bounds.b - progress * height / 4); + } else if (type == ES_TRANSITION_SLIDE_DOWN_OVER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + progress * height / 4, bounds.b + progress * height / 4); + } else if (type == ES_TRANSITION_SLIDE_UP_UNDER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t - progress * height / 2, bounds.b - progress * height / 2); + } else if (type == ES_TRANSITION_SLIDE_DOWN_UNDER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + progress * height / 2, bounds.b + progress * height / 2); + } + } else { + if (type == ES_TRANSITION_SLIDE_UP) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + (1 - progress) * height / 2, bounds.b + (1 - progress) * height / 2); + } else if (type == ES_TRANSITION_SLIDE_DOWN) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t - (1 - progress) * height / 2, bounds.b - (1 - progress) * height / 2); + } else if (type == ES_TRANSITION_COVER_UP) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + (1 - progress) * height, bounds.b + (1 - progress) * height); + } else if (type == ES_TRANSITION_COVER_DOWN) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t - (1 - progress) * height, bounds.b - (1 - progress) * height); + } else if (type == ES_TRANSITION_SQUISH_UP || type == ES_TRANSITION_REVEAL_UP) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + (1 - progress) * height, bounds.b); + } else if (type == ES_TRANSITION_SQUISH_DOWN || type == ES_TRANSITION_REVEAL_DOWN) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t, bounds.t + progress * height); + } else if (type == ES_TRANSITION_ZOOM_OUT) { + return ES_RECT_4(bounds.l - 20 * (1 - progress), bounds.r + 20 * (1 - progress), + bounds.t - 20 * (1 - progress) * ratio, bounds.b + 20 * (1 - progress) * ratio); + } else if (type == ES_TRANSITION_ZOOM_IN) { + return ES_RECT_4(bounds.l + 20 * (1 - progress), bounds.r - 20 * (1 - progress) + 0.5, + bounds.t + 20 * (1 - progress) * ratio, bounds.b - 20 * (1 - progress) * ratio + 0.5); + } else if (type == ES_TRANSITION_ZOOM_OUT_LIGHT) { + return ES_RECT_4(bounds.l - 5 * (1 - progress), bounds.r + 5 * (1 - progress), + bounds.t - 5 * (1 - progress) * ratio, bounds.b + 5 * (1 - progress) * ratio); + } else if (type == ES_TRANSITION_ZOOM_IN_LIGHT) { + return ES_RECT_4(bounds.l + 5 * (1 - progress), bounds.r - 5 * (1 - progress) + 0.5, + bounds.t + 5 * (1 - progress) * ratio, bounds.b - 5 * (1 - progress) * ratio + 0.5); + } else if (type == ES_TRANSITION_FADE_IN || type == ES_TRANSITION_FADE_OUT) { + return bounds; + } else if (type == ES_TRANSITION_SLIDE_UP_OVER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + (1 - progress) * height / 2, bounds.b + (1 - progress) * height / 2); + } else if (type == ES_TRANSITION_SLIDE_DOWN_OVER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t - (1 - progress) * height / 2, bounds.b - (1 - progress) * height / 2); + } else if (type == ES_TRANSITION_SLIDE_UP_UNDER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t + (1 - progress) * height / 4, bounds.b + (1 - progress) * height / 4); + } else if (type == ES_TRANSITION_SLIDE_DOWN_UNDER) { + return ES_RECT_4(bounds.l, bounds.r, + bounds.t - (1 - progress) * height / 4, bounds.b - (1 - progress) * height / 4); + } + } + + EsAssert(false); // Unknown transition type. + return {}; +} + +void UIDrawTransitionEffect(EsPainter *painter, EsPaintTarget *sourceSurface, EsRectangle bounds, EsTransitionType type, double progress, bool to) { + EsRectangle destinationRegion = UIGetTransitionEffectRectangle(bounds, type, progress, to); + EsRectangle sourceRegion = ES_RECT_4(0, bounds.r - bounds.l, 0, bounds.b - bounds.t); + uint16_t alpha = (to ? progress : (1 - progress)) * 255; + EsDrawPaintTarget(painter, sourceSurface, destinationRegion, sourceRegion, alpha); +} + +void EsElementStartTransition(EsElement *element, EsTransitionType transitionType, uint32_t flags, uint32_t durationMs) { + durationMs *= ANIMATION_TIME_SCALE; + + if (!durationMs) { + return; + } + + if (element->previousTransitionFrame) { + EsPaintTargetDestroy(element->previousTransitionFrame); + element->previousTransitionFrame = nullptr; + } + + if (~flags & ES_ELEMENT_TRANSITION_ENTRANCE) { + EsRectangle paintOutsets = element->currentStyle->paintOutsets; + int width = element->width + paintOutsets.l + paintOutsets.r; + int height = element->height + paintOutsets.t + paintOutsets.b; + + element->previousTransitionFrame = EsPaintTargetCreate(width, height, true); + + if (element->previousTransitionFrame) { + EsPainter painter = {}; + painter.clip = ES_RECT_4(0, width, 0, height); + painter.offsetX = paintOutsets.l; + painter.offsetY = paintOutsets.t; + painter.target = element->previousTransitionFrame; + element->InternalPaint(&painter, PAINT_NO_TRANSITION | PAINT_NO_OFFSET); + } + } + + element->transitionTimeMs = 0; + element->transitionDurationMs = durationMs; + element->transitionType = transitionType; + element->StartAnimating(); +} + +// --------------------------------- Painting. + +EsRectangle EsPainterBoundsClient(EsPainter *painter) { + return ES_RECT_4(painter->offsetX, painter->offsetX + painter->width, painter->offsetY, painter->offsetY + painter->height); +} + +EsRectangle EsPainterBoundsInset(EsPainter *painter) { + UIStyle *style = (UIStyle *) painter->style; + return ES_RECT_4(painter->offsetX + style->insets.l, painter->offsetX + painter->width - style->insets.r, + painter->offsetY + style->insets.t, painter->offsetY + painter->height - style->insets.b); +} + +void EsElement::Repaint(bool all, EsRectangle region) { + // TODO Optimisation: don't paint if overlapped by an opaque child or sibling. + + if (all) { + region.l = -currentStyle->paintOutsets.l, region.r = width + currentStyle->paintOutsets.r; + region.t = -currentStyle->paintOutsets.t, region.b = height + currentStyle->paintOutsets.b; + } else { + region = Translate(region, -internalOffsetLeft, -internalOffsetTop); + } + + if (parent) { + EsRectangle parentBounds = parent->GetWindowBounds(false); + + region = Translate(region, offsetX + parentBounds.l, offsetY + parentBounds.t); + + if (parent->currentStyle->metrics->clipEnabled) { + Rectangle16 clipInsets = parent->currentStyle->metrics->clipInsets; + region = EsRectangleIntersection(region, EsRectangleAddBorder(parentBounds, RECT16_TO_RECT(clipInsets))); + } + + UIWindowNeedsUpdate(window); + } + + if (THEME_RECT_VALID(region)) { + window->updateRegion = EsRectangleBounding(window->updateRegion, region); + } +} + +void EsElement::InternalPaint(EsPainter *painter, int paintFlags) { + if (width <= 0 || height <= 0 || (flags & ES_ELEMENT_HIDDEN)) { + return; + } + + state |= UI_STATE_ENTERED; + + int pOffsetX = painter->offsetX; + int pOffsetY = painter->offsetY; + + if (~paintFlags & PAINT_NO_OFFSET) { + pOffsetX += offsetX; + pOffsetY += offsetY; + } + + // Is it possible for this element to paint within the clip? + + { + EsRectangle area; + area.l = pOffsetX - currentStyle->paintOutsets.l; + area.r = pOffsetX + width + currentStyle->paintOutsets.r; + area.t = pOffsetY - currentStyle->paintOutsets.t; + area.b = pOffsetY + height + currentStyle->paintOutsets.b; + + if (!THEME_RECT_VALID(EsRectangleIntersection(area, painter->clip))) { + return; + } + } + + if (state & UI_STATE_INSPECTING) EsPerformanceTimerPush(); + double timeChildPaint = 0; + + // Get the interpolated style. + + EsPainter oldPainter = *painter; + + UIStyle *style = ThemeAnimationComplete(&animation) ? currentStyle : ThemeStyleInterpolate(currentStyle, &animation); + EsDefer(if (style != currentStyle) EsHeapFree(style)); + painter->style = style; + + painter->offsetX = pOffsetX, painter->offsetY = pOffsetY; + painter->width = width, painter->height = height; + + // Get the child type of the element. + + int childType = 0; + + if (parent && parent->GetChildCount()) { + if (parent->GetChildCount() == 1 && parent->GetChild(0) == this) { + childType = THEME_CHILD_TYPE_ONLY; + } else if (parent->GetChild(0) == this) { + childType = (parent->flags & ES_ELEMENT_LAYOUT_HINT_REVERSE) ? THEME_CHILD_TYPE_LAST : THEME_CHILD_TYPE_FIRST; + } else if (parent->GetChild(parent->GetChildCount() - 1) == this) { + childType = (parent->flags & ES_ELEMENT_LAYOUT_HINT_REVERSE) ? THEME_CHILD_TYPE_FIRST : THEME_CHILD_TYPE_LAST; + } else { + childType = THEME_CHILD_TYPE_NONE; + } + + if (parent->flags & ES_ELEMENT_LAYOUT_HINT_HORIZONTAL) { + childType |= THEME_CHILD_TYPE_HORIZONTAL; + } + } + + if (paintFlags & PAINT_SHADOW) { + style->PaintLayers(painter, ES_RECT_2S(painter->width, painter->height), childType, THEME_LAYER_MODE_SHADOW); + } else if (paintFlags & PAINT_OVERLAY) { + style->PaintLayers(painter, ES_RECT_2S(painter->width, painter->height), childType, THEME_LAYER_MODE_OVERLAY); + + // Paint layout bounds, if active. + + if (window->visualizeLayoutBounds) { + EsDrawRectangle(painter, EsPainterBoundsClient(painter), 0, 0x7FFF0000, ES_RECT_1(1)); + EsDrawRectangle(painter, EsPainterBoundsInset(painter), 0, 0x7F0000FF, ES_RECT_1(1)); + } + } else if (transitionTimeMs < transitionDurationMs && (~paintFlags & PAINT_NO_TRANSITION)) { + double progress = SmoothAnimationTime((double) transitionTimeMs / (double) transitionDurationMs); + EsRectangle bounds = EsPainterBoundsClient(painter); + EsPaintTarget target; + + EsRectangle paintOutsets = currentStyle->paintOutsets; + int targetWidth = width + paintOutsets.l + paintOutsets.r; + int targetHeight = height + paintOutsets.t + paintOutsets.b; + bounds.l -= paintOutsets.l, bounds.r += paintOutsets.r; + bounds.t -= paintOutsets.t, bounds.b += paintOutsets.b; + + if (EsPaintTargetTake(&target, targetWidth, targetHeight)) { + if (previousTransitionFrame) { + UIDrawTransitionEffect(painter, previousTransitionFrame, bounds, + (EsTransitionType) transitionType, progress, false); + } + + EsPainter p = {}; + p.clip = ES_RECT_4(0, targetWidth, 0, targetHeight); + p.offsetX = paintOutsets.l; + p.offsetY = paintOutsets.t; + p.target = ⌖ + InternalPaint(&p, PAINT_NO_TRANSITION | PAINT_NO_OFFSET); + + UIDrawTransitionEffect(painter, &target, bounds, (EsTransitionType) transitionType, progress, true); + + EsPaintTargetReturn(&target); + } else { + goto paintBackground; + } + } else { + paintBackground:; + + // Paint the background. + + EsMessage m; + m.type = ES_MSG_PAINT_BACKGROUND; + m.painter = painter; + + if (!EsMessageSend(this, &m)) { + // TODO Optimisation: don't paint if overlapped by an opaque child. + style->PaintLayers(painter, ES_RECT_2S(painter->width, painter->height), childType, THEME_LAYER_MODE_BACKGROUND); + } + + // Apply the clipping insets. + + EsRectangle oldClip = painter->clip; + + if (currentStyle->metrics->clipEnabled && (~flags & ES_ELEMENT_NO_CLIP)) { + Rectangle16 insets = currentStyle->metrics->clipInsets; + EsRectangle content = ES_RECT_4(painter->offsetX + insets.l, painter->offsetX + width - insets.r, + painter->offsetY + insets.t, painter->offsetY + height - insets.b); + painter->clip = EsRectangleIntersection(content, painter->clip); + } + + if (THEME_RECT_VALID(painter->clip)) { + // Paint the content. + + painter->width -= internalOffsetLeft + internalOffsetRight; + painter->height -= internalOffsetTop + internalOffsetBottom; + painter->offsetX += internalOffsetLeft, painter->offsetY += internalOffsetTop; + + m.type = ES_MSG_PAINT; + m.painter = painter; + EsMessageSend(this, &m); + + painter->width += internalOffsetLeft + internalOffsetRight; + painter->height += internalOffsetTop + internalOffsetBottom; + painter->offsetX -= internalOffsetLeft, painter->offsetY -= internalOffsetTop; + + // Paint the children. + // TODO Optimisation: don't paint children overlapped by an opaque sibling. + + if (state & UI_STATE_INSPECTING) EsPerformanceTimerPush(); + + m.type = ES_MSG_PAINT_CHILDREN; + m.painter = painter; + + if (!EsMessageSend(this, &m)) { + bool isZStack = state & UI_STATE_Z_STACK; + + EsMessage zOrder; + zOrder.type = ES_MSG_BEFORE_Z_ORDER; + zOrder.beforeZOrder.start = 0; + zOrder.beforeZOrder.nonClient = zOrder.beforeZOrder.end = children.Length(); + zOrder.beforeZOrder.clip = Translate(painter->clip, -painter->offsetX, -painter->offsetY); + EsMessageSend(this, &zOrder); + + if (isZStack) { + // Elements cast shadows on each other. + + for (uintptr_t i = zOrder.beforeZOrder.start; i < zOrder.beforeZOrder.end; i++) { + EsElement *child = GetChildByZ(i); + if (!child) continue; + child->InternalPaint(painter, PAINT_SHADOW); + child->InternalPaint(painter, ES_FLAGS_DEFAULT); + child->InternalPaint(painter, PAINT_OVERLAY); + } + + for (uintptr_t i = zOrder.beforeZOrder.nonClient; i < children.Length(); i++) { + EsElement *child = GetChildByZ(i); + if (!child) continue; + child->InternalPaint(painter, PAINT_SHADOW); + child->InternalPaint(painter, ES_FLAGS_DEFAULT); + child->InternalPaint(painter, PAINT_OVERLAY); + } + } else { + // Elements cast shadows on the container. + + for (uintptr_t i = zOrder.beforeZOrder.start; i < zOrder.beforeZOrder.end; i++) { + EsElement *child = GetChildByZ(i); + if (child) child->InternalPaint(painter, PAINT_SHADOW); + } + + for (uintptr_t i = zOrder.beforeZOrder.nonClient; i < children.Length(); i++) { + EsElement *child = GetChildByZ(i); + if (child) child->InternalPaint(painter, PAINT_SHADOW); + } + + for (uintptr_t i = zOrder.beforeZOrder.start; i < zOrder.beforeZOrder.end; i++) { + EsElement *child = GetChildByZ(i); + if (child) child->InternalPaint(painter, ES_FLAGS_DEFAULT); + } + + for (uintptr_t i = zOrder.beforeZOrder.nonClient; i < children.Length(); i++) { + EsElement *child = GetChildByZ(i); + if (child) child->InternalPaint(painter, ES_FLAGS_DEFAULT); + } + + for (uintptr_t i = zOrder.beforeZOrder.start; i < zOrder.beforeZOrder.end; i++) { + EsElement *child = GetChildByZ(i); + if (child) child->InternalPaint(painter, PAINT_OVERLAY); + } + + for (uintptr_t i = zOrder.beforeZOrder.nonClient; i < children.Length(); i++) { + EsElement *child = GetChildByZ(i); + if (child) child->InternalPaint(painter, PAINT_OVERLAY); + } + } + + zOrder.type = ES_MSG_AFTER_Z_ORDER; + EsMessageSend(this, &zOrder); + } + + if (state & UI_STATE_INSPECTING) timeChildPaint = EsPerformanceTimerPop() * 1000; + } + + // Let the inspector draw some decorations over the element. + + painter->clip = oldClip; + InspectorNotifyElementPainted(this, painter); + } + + if (state & UI_STATE_INSPECTING) { + double timeTotalPaint = EsPerformanceTimerPop() * 1000; + InspectorNotifyElementEvent(this, "paint", "Paint in %Fms (%Fms with children).", timeTotalPaint - timeChildPaint, timeTotalPaint); + } + + *painter = oldPainter; + + if (window->visualizePaintSteps && ES_RECT_VALID(window->updateRegionInProgress) && painter->target->forWindowManager) { + EsSyscall(ES_SYSCALL_WINDOW_SET_BITS, window->handle, (uintptr_t) &window->updateRegionInProgress, + (uintptr_t) painter->target->bits, WINDOW_SET_BITS_NORMAL); + } +} + +// --------------------------------- Animations. + +bool EsElement::StartAnimating() { + if ((state & UI_STATE_ANIMATING) || (state & UI_STATE_DESTROYING)) return false; + gui.animatingElements.Add(this); + gui.animationSleep = false; + state |= UI_STATE_ANIMATING; + lastTimeStamp = 0; + return true; +} + +void ProcessAnimations() { + uint64_t timeStamp = EsTimeStamp(); // TODO Use global time instead. + int64_t waitMs = -1; + + for (uintptr_t i = 0; i < gui.animatingElements.Length(); i++) { + EsElement *element = gui.animatingElements[i]; + // EsPrint("Animating %x...\n", element); + EsAssert(element->state & UI_STATE_ANIMATING); // Element was not animating but was in the animating elements list. + + if (element->lastTimeStamp == 0) { + element->lastTimeStamp = timeStamp; + } + + EsMessage m = {}; + m.type = ES_MSG_ANIMATE; + int64_t deltaUs = (timeStamp - element->lastTimeStamp) / api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND]; + m.animate.deltaMs = deltaUs / 1000; + m.animate.complete = true; + + if (!m.animate.deltaMs) { + waitMs = 0; + continue; + } + + if (ThemeAnimationStep(&element->animation, m.animate.deltaMs)) { + element->Repaint(true, ES_RECT_1(0)); + } + + element->transitionTimeMs += m.animate.deltaMs; + bool transitionComplete = element->transitionTimeMs >= element->transitionDurationMs; + + if (!transitionComplete) { + element->Repaint(true, ES_RECT_1(0)); + } + + bool backgroundAnimationComplete = ThemeAnimationComplete(&element->animation); + + EsMessageSend(element, &m); + + if (m.animate.complete && backgroundAnimationComplete && transitionComplete) { + gui.animatingElements.DeleteSwap(i); + element->state &= ~UI_STATE_ANIMATING; + i--; + + if (element->state & UI_STATE_EXITING) { + EsElement *ancestor = element; + + while (ancestor) { + ancestor->state |= UI_STATE_DESTROYING_CHILD; + ancestor = ancestor->parent; + } + + element->state &= ~UI_STATE_EXITING; + } + } else if (m.animate.waitMs < waitMs || waitMs == -1) { + waitMs = m.animate.waitMs; + } + + element->lastTimeStamp += m.animate.deltaMs * 1000 * api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND]; + UIWindowNeedsUpdate(element->window); + } + + if (waitMs > 0) { + gui.animationSleep = true; + + EsTimerCancel(gui.animationSleepTimer); + gui.animationSleepTimer = EsTimerSet(waitMs, [] (EsGeneric) { gui.animationSleep = false; }, nullptr); + } +} + +// --------------------------------- Style state management. +// TODO Move into theme.cpp? + +bool EsElement::RefreshStyleState() { + uint16_t styleStateFlags = customStyleState; + + if (flags & ES_ELEMENT_DISABLED) { + styleStateFlags |= THEME_PRIMARY_STATE_DISABLED; + } else { + if (((state & UI_STATE_PRESSED) && ((state & UI_STATE_HOVERED) || gui.draggingStarted || (state & UI_STATE_STRONG_PRESSED))) || (state & UI_STATE_MENU_SOURCE)) { + styleStateFlags |= THEME_PRIMARY_STATE_PRESSED; + } else if (((state & UI_STATE_HOVERED) && !window->pressed) || (state & UI_STATE_PRESSED)) { + styleStateFlags |= THEME_PRIMARY_STATE_HOVERED; + } else { + styleStateFlags |= THEME_PRIMARY_STATE_IDLE; + } + + if (state & UI_STATE_FOCUSED) { + styleStateFlags |= THEME_STATE_FOCUSED; + } + } + + if (state & UI_STATE_EXITING) { + styleStateFlags |= THEME_STATE_AFTER_EXIT; + } + + bool observedBitsChanged = false; + + if (!currentStyle || currentStyle->IsStateChangeObserved(styleStateFlags, previousStyleState)) { + observedBitsChanged = true; + } + + previousStyleState = styleStateFlags; + + return observedBitsChanged; +} + +void EsElement::RefreshStyle(UIStyleKey *_oldStyleKey, bool alreadyRefreshStyleState, bool force) { + // Compute state flags. + + if (!alreadyRefreshStyleState) { + RefreshStyleState(); + } + + uint16_t styleStateFlags = previousStyleState; + + // Initialise the style. + + UIStyleKey oldStyleKey = _oldStyleKey ? *_oldStyleKey : currentStyleKey; + currentStyleKey.stateFlags = styleStateFlags; + + if (!force && 0 == EsMemoryCompare(¤tStyleKey, &oldStyleKey, sizeof(UIStyleKey)) && currentStyle) { + return; + } + + if (~state & UI_STATE_ENTERED) { + if (currentStyle) currentStyle->CloseReference(); + oldStyleKey = currentStyleKey; + oldStyleKey.stateFlags |= THEME_STATE_BEFORE_ENTER; + currentStyle = GetStyle(oldStyleKey, false); + } + + UIStyle *oldStyle = currentStyle; + currentStyle = GetStyle(currentStyleKey, false); // TODO Forcing new styles if force flag set. + + // Respond to modifications. + + bool repaint = false, animate = false; + + if (force) { + repaint = true; + } + + if (oldStyle) { + if (oldStyle->style == currentStyle->style) { + ThemeAnimationBuild(&animation, oldStyle, oldStyleKey.stateFlags, currentStyleKey.stateFlags); + animate = !ThemeAnimationComplete(&animation); + } else { + ThemeAnimationDestroy(&animation); + } + + repaint = true; + } else { + repaint = animate = true; + } + + if (repaint) { + if (animate) StartAnimating(); + Repaint(true, ES_RECT_1(0)); + } + + // Delete the old style if necessary. + + if (oldStyle) { + oldStyle->CloseReference(); + } +} + +void EsElement::SetStyle(const EsStyle *part, bool refreshIfChanged) { + UIStyleKey oldStyleKey = currentStyleKey; + currentStyleKey.part = (uintptr_t) part; + + if (currentStyleKey.part != oldStyleKey.part && refreshIfChanged) { + RefreshStyle(&oldStyleKey); + } +} + +// --------------------------------- Layouting. + +EsRectangle LayoutCell(EsElement *element, int width, int height) { + uint64_t layout = element->flags; + + int maximumWidth = element->currentStyle->metrics->maximumWidth ?: ES_PANEL_BAND_SIZE_DEFAULT; + int minimumWidth = element->currentStyle->metrics->minimumWidth ?: ES_PANEL_BAND_SIZE_DEFAULT; + int maximumHeight = element->currentStyle->metrics->maximumHeight ?: ES_PANEL_BAND_SIZE_DEFAULT; + int minimumHeight = element->currentStyle->metrics->minimumHeight ?: ES_PANEL_BAND_SIZE_DEFAULT; + + if (layout & ES_CELL_H_EXPAND) maximumWidth = INT_MAX; + if (layout & ES_CELL_H_SHRINK) minimumWidth = 0; + if (layout & ES_CELL_V_EXPAND) maximumHeight = INT_MAX; + if (layout & ES_CELL_V_SHRINK) minimumHeight = 0; + + if (maximumWidth == ES_PANEL_BAND_SIZE_DEFAULT || minimumWidth == ES_PANEL_BAND_SIZE_DEFAULT) { + int width = element->GetWidth(height); + if (maximumWidth == ES_PANEL_BAND_SIZE_DEFAULT) maximumWidth = width; + if (minimumWidth == ES_PANEL_BAND_SIZE_DEFAULT) minimumWidth = width; + } + + int preferredWidth = ClampInteger(minimumWidth, maximumWidth, width); + + if (maximumHeight == ES_PANEL_BAND_SIZE_DEFAULT || minimumHeight == ES_PANEL_BAND_SIZE_DEFAULT) { + int height = element->GetHeight(preferredWidth); + if (maximumHeight == ES_PANEL_BAND_SIZE_DEFAULT) maximumHeight = height; + if (minimumHeight == ES_PANEL_BAND_SIZE_DEFAULT) minimumHeight = height; + } + + int preferredHeight = ClampInteger(minimumHeight, maximumHeight, height); + + EsRectangle bounds = ES_RECT_4(0, width, 0, height); + + if ((layout & (ES_CELL_H_LEFT | ES_CELL_H_RIGHT)) == ES_CELL_H_LEFT) { + bounds.r = bounds.l + preferredWidth; + } else if ((layout & (ES_CELL_H_LEFT | ES_CELL_H_RIGHT)) == ES_CELL_H_RIGHT) { + bounds.l = bounds.r - preferredWidth; + } else { + bounds.l = bounds.l + width / 2 - preferredWidth / 2; + bounds.r = bounds.l + preferredWidth; + } + + if ((layout & (ES_CELL_V_TOP | ES_CELL_V_BOTTOM)) == ES_CELL_V_TOP) { + bounds.b = bounds.t + preferredHeight; + } else if ((layout & (ES_CELL_V_TOP | ES_CELL_V_BOTTOM)) == ES_CELL_V_BOTTOM) { + bounds.t = bounds.b - preferredHeight; + } else { + bounds.t = bounds.t + height / 2 - preferredHeight / 2; + bounds.b = bounds.t + preferredHeight; + } + + return bounds; +} + +void EsElementMove(EsElement *element, int x, int y, int width, int height, bool applyCellLayout) { + EsMessageMutexCheck(); + + if (applyCellLayout) { + EsRectangle bounds = LayoutCell(element, width, height); + width = Width(bounds), height = Height(bounds); + x += bounds.l, y += bounds.t; + } + + element->InternalMove(width, height, x, y); +} + +void PanelMoveChild(EsElement *element, int width, int height, int offsetX, int offsetY) { + EsPanel *panel = (EsPanel *) element->parent; + + { + EsRectangle bounds = LayoutCell(element, width, height); + width = Width(bounds), height = Height(bounds); + offsetX += bounds.l, offsetY += bounds.t; + } + + float progress = panel->transitionLengthMs ? SmoothAnimationTime((float) panel->transitionTimeMs / panel->transitionLengthMs) : 1; + + // TODO Make this faster than O(n^2). + + for (uintptr_t i = 0; i < panel->movementItems.Length(); i++) { + PanelMovementItem *item = &panel->movementItems[i]; + + if (item->element != element) { + continue; + } + + int oldWidth = Width(item->oldBounds); + int oldHeight = Height(item->oldBounds); + int oldOffsetX = item->oldBounds.l; + int oldOffsetY = item->oldBounds.t; + + element->InternalMove(LinearInterpolate(oldWidth, width, progress), + LinearInterpolate(oldHeight, height, progress), + LinearInterpolate(oldOffsetX, offsetX, progress), + LinearInterpolate(oldOffsetY, offsetY, progress)); + + return; + } + + element->InternalMove(width, height, offsetX, offsetY); +} + +void LayoutTable(EsPanel *panel, EsMessage *message) { + bool debug = false; + + bool has[2] = {}; + int in[2] = {}; + int out[2] = {}; + + if (message->type == ES_MSG_GET_WIDTH) { + in[1] = message->measure.height; + has[1] = has[1]; + } else if (message->type == ES_MSG_GET_HEIGHT) { + in[0] = message->measure.width; + has[0] = in[0]; + } else { + EsRectangle bounds = panel->GetBounds(); + in[0] = bounds.r, in[1] = bounds.b; + has[0] = has[1] = true; + } + + // if (debug) EsPrint("LayoutTable, %z, %d/%d\n", isMeasure ? "measure" : "layout", in[0], in[1]); + + size_t childCount = panel->GetChildCount(); + + uint8_t *memoryBase = (uint8_t *) EsHeapAllocate(sizeof(int) * childCount * 2 + sizeof(EsPanelBand) * (panel->bandCount[0] + panel->bandCount[1]), true), *memory = memoryBase; + + int *calculatedSize[2]; + calculatedSize[0] = (int *) memory; memory += sizeof(int) * childCount; + calculatedSize[1] = (int *) memory; memory += sizeof(int) * childCount; + EsPanelBand *calculatedProperties[2]; + calculatedProperties[0] = (EsPanelBand *) memory; memory += sizeof(EsPanelBand) * panel->bandCount[0]; + calculatedProperties[1] = (EsPanelBand *) memory; memory += sizeof(EsPanelBand) * panel->bandCount[1]; + + for (int axis = 0; axis < 2; axis++) { + if (panel->bands[axis]) { + EsMemoryCopy(calculatedProperties[axis], panel->bands[axis], sizeof(EsPanelBand) * panel->bandCount[axis]); + } else { + for (uintptr_t i = 0; i < panel->bandCount[axis]; i++) { + calculatedProperties[axis][i].preferredSize + = calculatedProperties[axis][i].maximumSize + = calculatedProperties[axis][i].minimumSize + = ES_PANEL_BAND_SIZE_DEFAULT; + } + } + } + + EsRectangle insets = panel->GetInsets(); + +#define TABLE_AXIS_HORIZONTAL (0) +#define TABLE_AXIS_VERTICAL (1) + + for (int _axis = 0; _axis < 2; _axis++) { + int axis = (~panel->flags & ES_PANEL_HORIZONTAL) ? (1 - _axis) : _axis; + int gapSize = _axis ? panel->GetGapMinor() : panel->GetGapMajor(); + int insetStart = axis ? insets.t : insets.l; + int insetEnd = axis ? insets.b : insets.r; + + if (debug) EsPrint("\tAxis %d\n", axis); + + for (uintptr_t i = 0; i < childCount; i++) { + EsElement *child = panel->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + + // Step 1: Find the preferred size of the children for this axis. + + int size; + + if ((child->flags & (axis ? ES_CELL_V_PUSH : ES_CELL_H_PUSH)) && has[axis]) { + size = 0; + } else { + int alternate = _axis ? calculatedSize[1 - axis][i] : 0; + size = axis == TABLE_AXIS_HORIZONTAL ? child->GetWidth(alternate) : child->GetHeight(alternate); + } + + if (debug) EsPrint("\tChild %d (%z) in cells %d->%d has size %d\n", i, child->cName, child->tableCell.from[axis], child->tableCell.to[axis], size); + + // Step 2: Find the preferred size of each band on this axis. + + int bandSpan = child->tableCell.to[axis] - child->tableCell.from[axis] + 1; + int totalGapSize = (bandSpan - 1) * gapSize; + + int preferredSizePerBand = (size - totalGapSize) / bandSpan; + int maximumSizeValue = axis ? child->currentStyle->metrics->maximumHeight : child->currentStyle->metrics->maximumWidth; + int minimumSizeValue = axis ? child->currentStyle->metrics->minimumHeight : child->currentStyle->metrics->minimumWidth; + int maximumSizePerBand = maximumSizeValue ? (((int) maximumSizeValue - totalGapSize) / bandSpan) : ES_PANEL_BAND_SIZE_DEFAULT; + int minimumSizePerBand = maximumSizeValue ? (((int) minimumSizeValue - totalGapSize) / bandSpan) : ES_PANEL_BAND_SIZE_DEFAULT; + + for (int j = child->tableCell.from[axis]; j <= child->tableCell.to[axis]; j++) { + EsAssert(j >= 0 && j < panel->bandCount[axis]); // Invalid element cell. + + EsPanelBand *band = calculatedProperties[axis] + j; + + if (child->flags & (axis ? ES_CELL_V_PUSH : ES_CELL_H_PUSH)) { + if (!band->push) { + band->push = 1; + } + } + + if (!panel->bands[axis] || panel->bands[axis][j].preferredSize == ES_PANEL_BAND_SIZE_DEFAULT) { + if (band->preferredSize != ES_PANEL_BAND_SIZE_DEFAULT) { + if (band->preferredSize < preferredSizePerBand) { + band->preferredSize = preferredSizePerBand; + } + } else { + band->preferredSize = preferredSizePerBand; + } + } + + if (!panel->bands[axis] || panel->bands[axis][j].maximumSize == ES_PANEL_BAND_SIZE_DEFAULT) { + if (maximumSizePerBand != ES_PANEL_BAND_SIZE_DEFAULT) { + if (band->maximumSize != ES_PANEL_BAND_SIZE_DEFAULT) { + if (band->maximumSize > maximumSizePerBand) { + band->maximumSize = maximumSizePerBand; + } + } else { + band->maximumSize = maximumSizePerBand; + } + } + } + + if (!panel->bands[axis] || panel->bands[axis][j].minimumSize == ES_PANEL_BAND_SIZE_DEFAULT) { + if (minimumSizePerBand != ES_PANEL_BAND_SIZE_DEFAULT) { + if (band->minimumSize != ES_PANEL_BAND_SIZE_DEFAULT) { + if (band->minimumSize < minimumSizePerBand) { + band->minimumSize = minimumSizePerBand; + } + } else { + band->minimumSize = minimumSizePerBand; + } + } + } + } + } + + // Step 3: Work out the size of each band. + + if (has[axis]) { + int contentSpace = in[axis] - insetStart - insetEnd - (panel->bandCount[axis] - 1) * gapSize; + + for (int i = 0; i < panel->bandCount[axis]; i++) { + EsPanelBand *band = calculatedProperties[axis] + i; + if (band->minimumSize == ES_PANEL_BAND_SIZE_DEFAULT) band->minimumSize = 0; + if (band->preferredSize == ES_PANEL_BAND_SIZE_DEFAULT) band->preferredSize = 0; + if (band->maximumSize == ES_PANEL_BAND_SIZE_DEFAULT) band->maximumSize = INT_MAX; + } + + int usedSpace = 0; + + for (int i = 0; i < panel->bandCount[axis]; i++) { + usedSpace += calculatedProperties[axis][i].preferredSize; + } + + bool shrink = usedSpace > contentSpace; + int remainingDifference = AbsoluteInteger(usedSpace - contentSpace); + + while (remainingDifference > 0) { + int availableWeight = 0; + + for (int i = 0; i < panel->bandCount[axis]; i++) { + EsPanelBand *band = calculatedProperties[axis] + i; + availableWeight += shrink ? band->pull : band->push; + } + + if (!availableWeight) { + break; // There are no more flexible bands. + } + + int perWeight = remainingDifference / availableWeight, perWeightExtra = remainingDifference % availableWeight; + bool stable = true; + + for (int i = 0; i < panel->bandCount[axis]; i++) { + EsPanelBand *band = calculatedProperties[axis] + i; + int available = shrink ? (band->preferredSize - band->minimumSize) : (band->maximumSize - band->preferredSize); + int weight = shrink ? band->pull : band->push; + int change = weight * perWeight; + int extra = MinimumInteger(perWeightExtra, weight); + change += extra, perWeightExtra -= extra; + + if (change > available) { + band->preferredSize = shrink ? band->minimumSize : band->maximumSize; + band->pull = band->push = 0; + remainingDifference -= available; + stable = false; + } + } + + if (stable) { + perWeightExtra = remainingDifference % availableWeight; + + for (int i = 0; i < panel->bandCount[axis]; i++) { + EsPanelBand *band = calculatedProperties[axis] + i; + int weight = shrink ? band->pull : band->push; + int change = weight * perWeight; + int extra = MinimumInteger(perWeightExtra, weight); + change += extra, perWeightExtra -= extra; + band->preferredSize += (shrink ? -1 : 1) * change; + if (band->preferredSize < band->minimumSize) band->preferredSize = band->minimumSize; + if (band->preferredSize > band->maximumSize) band->preferredSize = band->maximumSize; + } + + break; // We've found a working configuration. + } + } + } + + // Step 4: Work out the final size of each child. + + for (uintptr_t i = 0; i < childCount; i++) { + EsElement *child = panel->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + + int size = (child->tableCell.to[axis] - child->tableCell.from[axis]) * gapSize; + + for (int j = child->tableCell.from[axis]; j <= child->tableCell.to[axis]; j++) { + EsPanelBand *band = calculatedProperties[axis] + j; + size += band->preferredSize; + } + + calculatedSize[axis][i] = size; + } + + // Step 5: Calculate the position of the bands. + + int position = insetStart; + + for (int i = 0; i < panel->bandCount[axis]; i++) { + if (i) position += gapSize; + EsPanelBand *band = calculatedProperties[axis] + i; + int size = band->preferredSize; + band->maximumSize = position; // Aliasing maximumSize with position. + position += size; + } + + out[axis] = position + insetEnd; + } + + // Step 6: Move the children to their new location. + + if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = out[0]; + } else if (message->type == ES_MSG_GET_HEIGHT) { + message->measure.height = out[1]; + } else { + for (uintptr_t i = 0; i < childCount; i++) { + EsElement *element = panel->GetChild(i); + + if (element->flags & ES_ELEMENT_NON_CLIENT) { + continue; + } else if (element->flags & ES_ELEMENT_HIDDEN) { + element->InternalMove(0, 0, -1, -1); + continue; + } + + int position[2], size[2]; + + for (int axis = 0; axis < 2; axis++) { + position[axis] = calculatedProperties[axis][element->tableCell.from[axis]].maximumSize; + size[axis] = calculatedSize[axis][i]; + } + + PanelMoveChild(element, size[0], size[1], position[0] - panel->scroll.position[0], position[1] - panel->scroll.position[1]); + } + } + + if (debug) { + EsPrint("\t%d/%d\n", out[0], out[1]); + } + + EsHeapFree(memoryBase); +} + +int LayoutStackDeterminePerPush(EsPanel *panel, int available, int secondary) { + size_t childCount = panel->GetChildCount(); + int fill = 0, count = 0, perPush = 0; + + for (uintptr_t i = 0; i < childCount; i++) { + EsElement *child = panel->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + + count++; + + if (panel->flags & ES_PANEL_HORIZONTAL) { + if (child->flags & ES_CELL_H_PUSH) { + fill++; + } else if (available > 0) { + available -= child->GetWidth(secondary); + } + } else { + if (child->flags & ES_CELL_V_PUSH) { + fill++; + } else if (available > 0) { + available -= child->GetHeight(secondary); + } + } + } + + if (count) { + available -= (count - 1) * panel->GetGapMajor(); + } + + if (available > 0 && fill) { + perPush = available / fill; + } + + return perPush; +} + +void LayoutStackSecondary(EsPanel *panel, EsMessage *message) { + bool horizontal = panel->flags & ES_PANEL_HORIZONTAL; + size_t childCount = panel->GetChildCount(); + EsRectangle insets = panel->GetInsets(); + int size = 0; + + int primary = horizontal ? message->measure.width : message->measure.height; + int perPush = 0; + + if (panel->state & UI_STATE_INSPECTING) { + InspectorNotifyElementEvent(panel, "layout", "Measuring stack on secondary axis with %d children, insets %R; provided primary size is %d.\n", + panel, childCount, insets, primary); + } + + if (primary) { + if (horizontal) primary -= insets.l + insets.r; + else primary -= insets.t + insets.b; + perPush = LayoutStackDeterminePerPush(panel, primary, 0); + } + + for (uintptr_t i = 0; i < childCount; i++) { + EsElement *child = panel->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + + if (horizontal) { + int height = child->GetHeight((child->flags & ES_CELL_H_PUSH) ? perPush : 0); + if (height > size) size = height; + } else { + int width = child->GetWidth((child->flags & ES_CELL_V_PUSH) ? perPush : 0); + if (width > size) size = width; + } + } + + if (horizontal) message->measure.height = size + insets.t + insets.b; + else message->measure.width = size + insets.l + insets.r; +} + +void LayoutStackPrimary(EsPanel *panel, EsMessage *message) { + bool horizontal = panel->flags & ES_PANEL_HORIZONTAL; + bool reverse = panel->flags & ES_PANEL_REVERSE; + EsRectangle bounds = panel->GetBounds(); + size_t childCount = panel->GetChildCount(); + + EsRectangle insets = panel->GetInsets(); + int gap = panel->GetGapMajor(); + + if (message->type != ES_MSG_LAYOUT && (panel->state & UI_STATE_INSPECTING)) { + InspectorNotifyElementEvent(panel, "layout", "Measuring stack on primary axis with %d children, gap %d, insets %R.\n", childCount, gap, insets); + } + + if (message->type == ES_MSG_LAYOUT && (panel->state & UI_STATE_INSPECTING)) { + InspectorNotifyElementEvent(panel, "layout", "LayoutStack into %R with %d children, gap %d, insets %R.\n", bounds, childCount, gap, insets); + } + + int hBase = message->type == ES_MSG_GET_HEIGHT ? message->measure.width : Width(bounds); + int vBase = message->type == ES_MSG_GET_WIDTH ? message->measure.height : Height(bounds); + + int hSpace = hBase ? (hBase - insets.l - insets.r) : 0; + int vSpace = vBase ? (vBase - insets.t - insets.b) : 0; + int available = horizontal ? hSpace : vSpace; + int perPush = LayoutStackDeterminePerPush(panel, available, horizontal ? vSpace : hSpace); + + int position = horizontal ? (reverse ? insets.r : insets.l) : (reverse ? insets.b : insets.t); + bool anyNonHiddenChildren = false; + + for (uintptr_t i = 0; i < childCount; i++) { + EsElement *child = panel->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + EsRectangle relative; + anyNonHiddenChildren = true; + + if (horizontal) { + int width = (child->flags & ES_CELL_H_PUSH) ? perPush : child->GetWidth(vSpace); + + if (reverse) { + relative = ES_RECT_4(bounds.r - position - width, bounds.r - position, insets.t, bounds.b - insets.b); + } else { + relative = ES_RECT_4(position, position + width, insets.t, bounds.b - insets.b); + } + + position += width + gap; + } else { + int height = (child->flags & ES_CELL_V_PUSH) ? perPush : child->GetHeight(hSpace); + + if (reverse) { + relative = ES_RECT_4(insets.l, bounds.r - insets.r, bounds.b - position - height, bounds.b - position); + } else { + relative = ES_RECT_4(insets.l, bounds.r - insets.r, position, position + height); + } + + position += height + gap; + } + + if (message->type == ES_MSG_LAYOUT) { + if (panel->state & UI_STATE_INSPECTING) { + InspectorNotifyElementEvent(panel, "layout", "\tMove child %d into %R.\n", i, relative); + } + + EsRectangle childBounds = Translate(relative, -panel->scroll.position[0], -panel->scroll.position[1]); + PanelMoveChild(child, Width(childBounds), Height(childBounds), childBounds.l, childBounds.t); + } + } + + if (anyNonHiddenChildren) position -= gap; + + if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = position + (reverse ? insets.l : insets.r); + } else if (message->type == ES_MSG_GET_HEIGHT) { + message->measure.height = position + (reverse ? insets.t : insets.b); + } +} + +void LayoutStack(EsPanel *panel, EsMessage *message) { + bool horizontal = panel->flags & ES_PANEL_HORIZONTAL; + + if (message->type == ES_MSG_LAYOUT + || (message->type == ES_MSG_GET_WIDTH && horizontal) + || (message->type == ES_MSG_GET_HEIGHT && !horizontal)) { + LayoutStackPrimary(panel, message); + } else { + LayoutStackSecondary(panel, message); + } +} + +int EsElement::GetWidth(int height) { + if (currentStyle->preferredWidth) return currentStyle->preferredWidth; + if (!height) height = currentStyle->preferredHeight; + else if (currentStyle->preferredHeight && currentStyle->preferredHeight > height && (~flags & (ES_CELL_V_SHRINK))) height = currentStyle->preferredHeight; + else if (currentStyle->preferredHeight && currentStyle->preferredHeight < height && (~flags & (ES_CELL_V_EXPAND))) height = currentStyle->preferredHeight; + else if (currentStyle->metrics->minimumHeight && currentStyle->metrics->minimumHeight > height) height = currentStyle->metrics->minimumHeight; + else if (currentStyle->metrics->maximumHeight && currentStyle->metrics->maximumHeight < height) height = currentStyle->metrics->maximumHeight; + if (height) height -= internalOffsetTop + internalOffsetBottom; + EsMessage m = { ES_MSG_GET_WIDTH }; + m.measure.height = height; + EsMessageSend(this, &m); + int width = m.measure.width + internalOffsetLeft + internalOffsetRight; + if (currentStyle->metrics->minimumWidth && currentStyle->metrics->minimumWidth > width) width = currentStyle->metrics->minimumWidth; + if (currentStyle->metrics->maximumWidth && currentStyle->metrics->maximumWidth < width) width = currentStyle->metrics->maximumWidth; + return width; +} + +int EsElement::GetHeight(int width) { + if (currentStyle->preferredHeight) return currentStyle->preferredHeight; + if (!width) width = currentStyle->preferredWidth; + else if (currentStyle->preferredWidth && currentStyle->preferredWidth > width && (~flags & (ES_CELL_H_SHRINK))) width = currentStyle->preferredWidth; + else if (currentStyle->preferredWidth && currentStyle->preferredWidth < width && (~flags & (ES_CELL_H_EXPAND))) width = currentStyle->preferredWidth; + else if (currentStyle->metrics->minimumWidth && currentStyle->metrics->minimumWidth > width) width = currentStyle->metrics->minimumWidth; + else if (currentStyle->metrics->maximumWidth && currentStyle->metrics->maximumWidth < width) width = currentStyle->metrics->maximumWidth; + if (width) width -= internalOffsetLeft + internalOffsetRight; + EsMessage m = { ES_MSG_GET_HEIGHT }; + m.measure.width = width; + EsMessageSend(this, &m); + int height = m.measure.height + internalOffsetTop + internalOffsetBottom; + if (currentStyle->metrics->minimumHeight && currentStyle->metrics->minimumHeight > height) height = currentStyle->metrics->minimumHeight; + if (currentStyle->metrics->maximumHeight && currentStyle->metrics->maximumHeight < height) height = currentStyle->metrics->maximumHeight; + return height; +} + +void EsElement::InternalMove(int _width, int _height, int _offsetX, int _offsetY) { + if (state & UI_STATE_EXITING) { + return; + } + +#ifdef TRACE_LAYOUT + if (parent) { + EsElement *parent = this->parent->parent; + while (parent) parent = parent->parent, EsPrint("\t"); + EsPrint("(move) %z\n", debugName); + } +#endif + +#ifdef TRACE_LAYOUT + if (parent) { + EsElement *parent = this->parent->parent; + while (parent) parent = parent->parent, EsPrint("\t"); + EsPrint("(move) %z :: in %d, %d; %d, %d :: ", debugName, _offsetX, _offsetY, _width, _height); + } +#endif + + // Add the internal offset. + + if (parent) { + _offsetX += parent->internalOffsetLeft; + _offsetY += parent->internalOffsetTop; + } + + // What has changed? + + bool hasPositionChanged = _offsetX != offsetX || _offsetY != offsetY; + bool hasSizeChanged = _width != width || _height != height; + bool relayoutRequested = state & UI_STATE_RELAYOUT; + bool relayoutChild = state & UI_STATE_RELAYOUT_CHILD; + int oldOffsetX = offsetX, oldOffsetY = offsetY; + +#ifdef TRACE_LAYOUT + if (parent) { + EsPrint("align %d, %d; %d, %d ::%z%z%z%z\n", _offsetX, _offsetY, _width, _height, + hasPositionChanged ? " pos" : "", hasSizeChanged ? " size" : "", + relayoutRequested ? " rel" : "", relayoutChild ? " child" : ""); + } +#endif + + // Update the variables. + + offsetX = _offsetX; + offsetY = _offsetY; + width = _width; + height = _height; + state &= ~(UI_STATE_RELAYOUT | UI_STATE_RELAYOUT_CHILD); + + if (!relayoutRequested && !hasSizeChanged) { + // If our size hasn't changed and a relayout wasn't requested, then we don't need to do any layouting. + + if (hasPositionChanged) { + // Clear the old position. + + if (parent) { + EsRectangle paintOutsets = currentStyle->paintOutsets; + EsRectangle rectangle = ES_RECT_4(oldOffsetX - paintOutsets.l, oldOffsetX + width + paintOutsets.r, + oldOffsetY - paintOutsets.t, oldOffsetY + height + paintOutsets.b); + parent->Repaint(false, rectangle); + } + + // Repaint if we've moved. + + Repaint(true); + } + + if (relayoutChild) { + for (uintptr_t i = 0; i < children.Length(); i++) { + if (children[i]->state & (UI_STATE_RELAYOUT | UI_STATE_RELAYOUT_CHILD)) { + children[i]->InternalMove(children[i]->width, children[i]->height, children[i]->offsetX, children[i]->offsetY); + } + } + } + } else { + // Tell the element to layout its contents. + + if (state & UI_STATE_INSPECTING) EsPerformanceTimerPush(); + EsMessage m = { ES_MSG_LAYOUT }; + m.layout.sizeChanged = hasSizeChanged; + EsMessageSend(this, &m); + if (state & UI_STATE_INSPECTING) InspectorNotifyElementEvent(this, "layout", "Layout in %Fms.\n", EsPerformanceTimerPop() * 1000); + + // Repaint. + + Repaint(true); + } + + if (window != this) { + window->processCheckVisible = true; + } + + if (hasPositionChanged || hasSizeChanged) { + InspectorNotifyElementMoved(this, ES_RECT_4(offsetX, offsetX + width, offsetY, offsetY + height)); + } +} + +EsRectangle EsElementGetPreferredSize(EsElement *element) { + EsMessageMutexCheck(); + + return ES_RECT_4(0, element->currentStyle->preferredWidth, 0, element->currentStyle->preferredHeight); +} + +void EsElementRelayout(EsElement *element) { + if (element->state & UI_STATE_DESTROYING) return; + element->state |= UI_STATE_RELAYOUT; + UIWindowNeedsUpdate(element->window); + + while (element) { + element->state |= UI_STATE_RELAYOUT_CHILD; + element = element->parent; + } +} + +void EsElementUpdateContentSize(EsElement *element, uint32_t flags) { + if (element->state & UI_STATE_DESTROYING) return; + if (!flags) flags = ES_ELEMENT_UPDATE_CONTENT_WIDTH | ES_ELEMENT_UPDATE_CONTENT_HEIGHT; + + while (element && flags) { + element->state &= ~UI_STATE_USE_MEASUREMENT_CACHE; + EsElementRelayout(element); + + if (element->currentStyle->preferredWidth || ((element->flags & ES_CELL_H_FILL) == ES_CELL_H_FILL)) { + flags &= ~ES_ELEMENT_UPDATE_CONTENT_WIDTH; + } + + if (element->currentStyle->preferredHeight || ((element->flags & ES_CELL_V_FILL) == ES_CELL_V_FILL)) { + flags &= ~ES_ELEMENT_UPDATE_CONTENT_HEIGHT; + } + + element = element->parent; + } +} + +// --------------------------------- Scrollbars. + +// #define ENABLE_SMOOTH_SCROLLING + +struct EsScrollbar : EsElement { + EsButton *up, *down; + EsElement *thumb; + double position, autoScrollSpeed, smoothScrollTarget; + int viewportSize, contentSize, thumbSize, oldThumbPosition, thumbPosition, originalThumbPosition, oldPosition; + bool horizontal; +}; + +void ScrollbarLayout(EsScrollbar *scrollbar) { + if (scrollbar->viewportSize >= scrollbar->contentSize || scrollbar->viewportSize <= 0 || scrollbar->contentSize <= 0) { + EsElementSetDisabled(scrollbar, true); + } else { + EsElementSetDisabled(scrollbar, false); + EsRectangle bounds = scrollbar->GetBounds(); + + if (scrollbar->horizontal) { + scrollbar->thumbSize = scrollbar->viewportSize * (bounds.r - scrollbar->height * 2) / scrollbar->contentSize; + + if (scrollbar->thumbSize < scrollbar->thumb->currentStyle->preferredWidth) { + scrollbar->thumbSize = scrollbar->thumb->currentStyle->preferredWidth; + } + + if (scrollbar->thumbSize > Width(bounds) - scrollbar->height * 2) { + scrollbar->thumbSize = Width(bounds) - scrollbar->height * 2; + } + + scrollbar->thumbPosition = LinearMap(0, scrollbar->contentSize - scrollbar->viewportSize, + scrollbar->height, bounds.r - scrollbar->thumbSize - scrollbar->height, scrollbar->smoothScrollTarget); + + EsElementMove(scrollbar->up, 0, 0, (int) scrollbar->thumbPosition + scrollbar->thumbSize / 2, scrollbar->thumb->currentStyle->preferredHeight); + EsElementMove(scrollbar->thumb, (int) scrollbar->thumbPosition, 0, scrollbar->thumbSize, scrollbar->thumb->currentStyle->preferredHeight); + EsElementMove(scrollbar->down, (int) scrollbar->thumbPosition + scrollbar->thumbSize / 2, 0, + bounds.r - scrollbar->thumbSize / 2 - (int) scrollbar->thumbPosition, + scrollbar->thumb->currentStyle->preferredHeight); + } else { + scrollbar->thumbSize = scrollbar->viewportSize * (bounds.b - scrollbar->width * 2) / scrollbar->contentSize; + + if (scrollbar->thumbSize < scrollbar->thumb->currentStyle->preferredHeight) { + scrollbar->thumbSize = scrollbar->thumb->currentStyle->preferredHeight; + } + + if (scrollbar->thumbSize > Height(bounds) - scrollbar->width * 2) { + scrollbar->thumbSize = Height(bounds) - scrollbar->width * 2; + } + + scrollbar->thumbPosition = LinearMap(0, scrollbar->contentSize - scrollbar->viewportSize, + scrollbar->width, bounds.b - scrollbar->thumbSize - scrollbar->width, scrollbar->smoothScrollTarget); + + EsElementMove(scrollbar->up, 0, 0, scrollbar->thumb->currentStyle->preferredWidth, (int) scrollbar->thumbPosition + scrollbar->thumbSize / 2); + EsElementMove(scrollbar->thumb, 0, (int) scrollbar->thumbPosition, scrollbar->thumb->currentStyle->preferredWidth, scrollbar->thumbSize); + EsElementMove(scrollbar->down, 0, (int) scrollbar->thumbPosition + scrollbar->thumbSize / 2, + scrollbar->thumb->currentStyle->preferredWidth, + bounds.b - scrollbar->thumbSize / 2 - (int) scrollbar->thumbPosition); + } + } +} + +void ScrollbarSetMeasurements(EsScrollbar *scrollbar, int viewportSize, int contentSize) { + EsMessageMutexCheck(); + + if (scrollbar->viewportSize == viewportSize && scrollbar->contentSize == contentSize) { + return; + } + + scrollbar->viewportSize = viewportSize; + scrollbar->contentSize = contentSize; + + ScrollbarLayout(scrollbar); +} + +void ScrollbarSetPosition(EsScrollbar *scrollbar, double position, bool sendMovedMessage, bool smoothScroll) { + EsMessageMutexCheck(); + + if (position > scrollbar->contentSize - scrollbar->viewportSize) position = scrollbar->contentSize - scrollbar->viewportSize; + if (position < 0) position = 0; + + scrollbar->smoothScrollTarget = position; + + int previous = scrollbar->position; + +#ifdef ENABLE_SMOOTH_SCROLLING + if (smoothScroll && OSCRTabsf(position - scrollbar->position) > 10) { + scrollbar->StartAnimating(); + } else { + scrollbar->position = position; + } +#else + (void) smoothScroll; + scrollbar->position = position; +#endif + + EsRectangle bounds = scrollbar->GetBounds(); + + scrollbar->thumbPosition = LinearMap(0, scrollbar->contentSize - scrollbar->viewportSize, + 0, (scrollbar->horizontal ? bounds.r : bounds.b) - scrollbar->thumbSize, position); + + if (sendMovedMessage && scrollbar->oldPosition != (int) scrollbar->position) { + EsMessage m = { ES_MSG_SCROLLBAR_MOVED }; + m.scrollbarMoved.scroll = (int) position; + m.scrollbarMoved.previous = previous; + EsMessageSend(scrollbar, &m); + } + + if (scrollbar->thumbPosition != scrollbar->oldThumbPosition) { + ScrollbarLayout(scrollbar); + } + + scrollbar->oldThumbPosition = scrollbar->thumbPosition; + scrollbar->oldPosition = scrollbar->position; +} + +int ProcessScrollbarButtonMessage(EsElement *element, EsMessage *message) { + EsScrollbar *scrollbar = (EsScrollbar *) element->parent; + + if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + element->state |= UI_STATE_STRONG_PRESSED; + +#define UI_SCROLLBAR_AUTO_SPEED (10.0) + scrollbar->autoScrollSpeed = scrollbar->viewportSize / UI_SCROLLBAR_AUTO_SPEED / (100); + + if (scrollbar->up == element) { + scrollbar->autoScrollSpeed *= -1; + } + + element->StartAnimating(); + } else if (message->type == ES_MSG_MOUSE_LEFT_UP) { + element->state &= ~UI_STATE_STRONG_PRESSED; + scrollbar->autoScrollSpeed = 0; + } else if (message->type == ES_MSG_ANIMATE) { + if (scrollbar->autoScrollSpeed) { + ScrollbarSetPosition(scrollbar, scrollbar->autoScrollSpeed * message->animate.deltaMs + scrollbar->position, true, false); + message->animate.waitMs = 0; + message->animate.complete = false; + } else { + message->animate.complete = true; + } + } else { + return 0; + } + + return ES_HANDLED; +} + +EsScrollbar *ScrollbarCreate(EsElement *parent, uint64_t flags) { + EsScrollbar *scrollbar = (EsScrollbar *) EsHeapAllocate(sizeof(EsScrollbar), true); + scrollbar->thumb = (EsElement *) EsHeapAllocate(sizeof(EsElement), true); + + if (flags & ES_SCROLLBAR_HORIZONTAL) { + scrollbar->horizontal = true; + } + + scrollbar->Initialise(parent, flags, [] (EsElement *element, EsMessage *message) { + EsScrollbar *scrollbar = (EsScrollbar *) element; + + if (message->type == ES_MSG_LAYOUT) { + ScrollbarLayout(scrollbar); + } else if (message->type == ES_MSG_ANIMATE) { + if (scrollbar->position != scrollbar->smoothScrollTarget) { + double factor = EsCRTexp2f(-5.0f / message->animate.deltaMs); + // EsPrint("%dmcs -> %F\n", message->animate.deltaUs, factor); + scrollbar->position += (scrollbar->smoothScrollTarget - scrollbar->position) * factor; + ScrollbarSetPosition(scrollbar, scrollbar->smoothScrollTarget, true, true); + bool done = scrollbar->position == scrollbar->smoothScrollTarget; + message->animate.waitMs = 0, message->animate.complete = done; + } else message->animate.complete = true; + } else { + return 0; + } + + return ES_HANDLED; + }, nullptr); + + scrollbar->cName = "scrollbar"; + + scrollbar->up = EsButtonCreate(scrollbar, ES_CELL_FILL); + scrollbar->up->messageUser = ProcessScrollbarButtonMessage; + scrollbar->down = EsButtonCreate(scrollbar, ES_CELL_FILL); + scrollbar->down->messageUser = ProcessScrollbarButtonMessage; + + scrollbar->thumb->Initialise(scrollbar, ES_CELL_FILL, [] (EsElement *element, EsMessage *message) { + EsScrollbar *scrollbar = (EsScrollbar *) element->parent; + EsRectangle bounds = scrollbar->GetBounds(); + + if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + if (scrollbar->horizontal) { + float p = LinearMap(scrollbar->height, bounds.r - scrollbar->thumbSize - scrollbar->height, 0, scrollbar->contentSize - scrollbar->viewportSize, + message->mouseDragged.newPositionX - message->mouseDragged.originalPositionX + scrollbar->originalThumbPosition); + ScrollbarSetPosition(scrollbar, p, true, true); + } else { + float p = LinearMap(scrollbar->width, bounds.b - scrollbar->thumbSize - scrollbar->width, 0, scrollbar->contentSize - scrollbar->viewportSize, + message->mouseDragged.newPositionY - message->mouseDragged.originalPositionY + scrollbar->originalThumbPosition); + ScrollbarSetPosition(scrollbar, p, true, true); + } + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + scrollbar->originalThumbPosition = scrollbar->thumbPosition; + } else { + return 0; + } + + return ES_HANDLED; + }, nullptr); + + scrollbar->thumb->cName = "scrollbar thumb"; + + if (scrollbar->horizontal) { + scrollbar->up->SetStyle(ES_STYLE_PUSH_BUTTON_SCROLLBAR_LEFT); + scrollbar->down->SetStyle(ES_STYLE_PUSH_BUTTON_SCROLLBAR_RIGHT); + scrollbar->thumb->SetStyle(ES_STYLE_SCROLLBAR_THUMB_HORIZONTAL); + scrollbar->SetStyle(ES_STYLE_SCROLLBAR_BAR_HORIZONTAL); + } else { + scrollbar->up->SetStyle(ES_STYLE_PUSH_BUTTON_SCROLLBAR_UP); + scrollbar->down->SetStyle(ES_STYLE_PUSH_BUTTON_SCROLLBAR_DOWN); + scrollbar->thumb->SetStyle(ES_STYLE_SCROLLBAR_THUMB_VERTICAL); + scrollbar->SetStyle(ES_STYLE_SCROLLBAR_BAR_VERTICAL); + } + + scrollbar->up->flags &= ~ES_ELEMENT_FOCUSABLE; + scrollbar->down->flags &= ~ES_ELEMENT_FOCUSABLE; + + return scrollbar; +} + +void ScrollPane::Setup(EsElement *_parent, uint8_t _xMode, uint8_t _yMode, uint16_t _flags) { + parent = _parent; + mode[0] = _xMode; + mode[1] = _yMode; + flags = _flags; + + if (mode[0] == SCROLL_MODE_NONE) flags &= ~SCROLL_X_DRAG; + if (mode[1] == SCROLL_MODE_NONE) flags &= ~SCROLL_Y_DRAG; + + for (int axis = 0; axis < 2; axis++) { + if (mode[axis] == SCROLL_MODE_FIXED || mode[axis] == SCROLL_MODE_AUTO) { + uint64_t flags = ES_CELL_FILL | ES_ELEMENT_NON_CLIENT | (axis ? ES_SCROLLBAR_VERTICAL : ES_SCROLLBAR_HORIZONTAL); + if (!bar[axis]) bar[axis] = ScrollbarCreate(parent, flags); + bar[axis]->userData = this; + + bar[axis]->messageUser = [] (EsElement *element, EsMessage *message) { + ScrollPane *pane = (ScrollPane *) element->userData.p; + + if (message->type == ES_MSG_SCROLLBAR_MOVED) { + int axis = (element->flags & ES_SCROLLBAR_HORIZONTAL) ? 0 : 1; + EsMessage m = *message; + m.type = axis ? ES_MSG_SCROLL_Y : ES_MSG_SCROLL_X; + pane->position[axis] = m.scrollbarMoved.scroll; + EsMessageSend(pane->parent, &m); + } + + return 0; + }; + } else if (bar[axis]) { + EsElementDestroy(bar[axis]); + bar[axis] = nullptr; + } + } + + if (bar[0] && bar[1]) { + if (!pad) pad = EsCustomElementCreate(parent, ES_CELL_FILL | ES_ELEMENT_NON_CLIENT, ES_STYLE_SCROLLBAR_PAD); + pad->cName = "scrollbar pad"; + } else if (pad) { + EsElementDestroy(pad); + pad = nullptr; + } +} + +void ScrollPane::ReceivedMessage(EsMessage *message) { + if (message->type == ES_MSG_LAYOUT) { + Refresh(); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_RIGHT_DRAG || message->type == ES_MSG_MOUSE_MIDDLE_DRAG) { + if (flags & (SCROLL_X_DRAG | SCROLL_Y_DRAG)) { + parent->StartAnimating(); + dragScrolling = true; + } + } else if (message->type == ES_MSG_MOUSE_LEFT_UP || message->type == ES_MSG_MOUSE_RIGHT_UP || message->type == ES_MSG_MOUSE_MIDDLE_UP) { + dragScrolling = false; + } else if (message->type == ES_MSG_ANIMATE) { + if (dragScrolling) { + EsPoint point = EsMouseGetPosition(parent); + EsRectangle bounds = parent->GetBounds(); + double distanceX = point.x < bounds.l ? point.x - bounds.l : point.x >= bounds.r ? point.x - bounds.r + 1 : 0; + double distanceY = point.y < bounds.t ? point.y - bounds.t : point.y >= bounds.b ? point.y - bounds.b + 1 : 0; + double deltaX = message->animate.deltaMs * distanceX / 300.0; + double deltaY = message->animate.deltaMs * distanceY / 300.0; + if (deltaX && (flags & SCROLL_X_DRAG)) SetX(position[0] + deltaX, true); + if (deltaY && (flags & SCROLL_Y_DRAG)) SetY(position[1] + deltaY, true); + message->animate.complete = false; + } + } else if (message->type == ES_MSG_GET_HEIGHT) { + if (message->measure.width && (mode[0] == SCROLL_MODE_AUTO) && (mode[1] != SCROLL_MODE_AUTO)) { + // To accurately measure the height of the element for this width, + // we need to determine whether the horizontal scrollbar will be present. + // TODO This assumes that the element will be send a LAYOUT message after measurements are complete, + // in order for the scrollbars to be updated. But I think this will always happen..? + EsMessage m = {}; + m.type = ES_MSG_GET_WIDTH; + EsMessageSend(parent, &m); + parent->internalOffsetBottom = (m.measure.width + fixedViewport[0] > message->measure.width) ? bar[0]->currentStyle->preferredHeight : 0; + } + } else if (message->type == ES_MSG_GET_WIDTH) { + if (message->measure.width && (mode[1] == SCROLL_MODE_AUTO) && (mode[0] != SCROLL_MODE_AUTO)) { + // As above. + EsMessage m = {}; + m.type = ES_MSG_GET_HEIGHT; + EsMessageSend(parent, &m); + parent->internalOffsetRight = (m.measure.height + fixedViewport[1] > message->measure.height) ? bar[1]->currentStyle->preferredWidth : 0; + } + } +} + +void ScrollPane::SetPosition(int axis, double newScroll, bool sendMovedMessage) { + if (mode[axis] == SCROLL_MODE_NONE) return; + if (newScroll < 0) newScroll = 0; + else if (newScroll > limit[axis]) newScroll = limit[axis]; + if (newScroll == position[axis]) return; + double previous = position[axis]; + position[axis] = newScroll; + if (bar[axis]) ScrollbarSetPosition(bar[axis], position[axis], false, false); + + if (sendMovedMessage) { + EsMessage m = {}; + m.type = axis ? ES_MSG_SCROLL_Y : ES_MSG_SCROLL_X; + m.scrollbarMoved.scroll = position[axis]; + m.scrollbarMoved.previous = previous; + EsMessageSend(parent, &m); + } +} + +bool ScrollPane::RefreshLimit(int axis, int64_t *contentSize) { + if (mode[axis] != SCROLL_MODE_NONE) { + uint8_t *internalOffset = axis ? &parent->internalOffsetRight : &parent->internalOffsetBottom; + EsRectangle bounds = parent->GetBounds(); + + EsMessage m = {}; + m.type = axis ? ES_MSG_GET_HEIGHT : ES_MSG_GET_WIDTH; + if (axis) m.measure.width = bounds.r; + else m.measure.height = bounds.b; + EsMessageSend(parent, &m); + + *contentSize = axis ? m.measure.height : m.measure.width; + limit[axis] = *contentSize - (axis ? bounds.b : bounds.r) + fixedViewport[axis]; + if (limit[axis] < 0) limit[axis] = 0; + + if (parent->state & UI_STATE_INSPECTING) { + InspectorNotifyElementEvent(parent, "scroll", "New %c limit: %d. (Measured content %d with other axis %d.)\n", + axis + 'X', limit[axis], *contentSize, axis ? bounds.r : bounds.b); + } + + if (mode[axis] == SCROLL_MODE_AUTO && limit[axis] > 0 && !(*internalOffset)) { + *internalOffset = axis ? bar[axis]->currentStyle->preferredWidth : bar[axis]->currentStyle->preferredHeight; + return true; + } + } + + return false; +} + +void ScrollPane::Refresh() { + if (parent->state & UI_STATE_INSPECTING) { + InspectorNotifyElementEvent(parent, "scroll", "Refreshing scroll pane...\n"); + } + + parent->internalOffsetRight = mode[1] == SCROLL_MODE_FIXED ? bar[1]->currentStyle->preferredWidth : 0; + parent->internalOffsetBottom = mode[0] == SCROLL_MODE_FIXED ? bar[0]->currentStyle->preferredHeight : 0; + + int64_t contentWidth = 0, contentHeight = 0; + + bool recalculateLimits1 = RefreshLimit(0, &contentWidth); + bool recalculateLimits2 = RefreshLimit(1, &contentHeight); + + if (recalculateLimits1 || recalculateLimits2) { + RefreshLimit(0, &contentWidth); + RefreshLimit(1, &contentHeight); + } + + EsRectangle bounds = parent->GetBounds(); + + if (bar[0]) ScrollbarSetMeasurements(bar[0], bounds.r - fixedViewport[0], contentWidth); + if (bar[1]) ScrollbarSetMeasurements(bar[1], bounds.b - fixedViewport[1], contentHeight); + + SetPosition(0, position[0], true); + SetPosition(1, position[1], true); + + if (bar[0]) { + bar[0]->InternalMove(parent->width - parent->internalOffsetRight, bar[0]->currentStyle->preferredHeight, + 0, parent->height - parent->internalOffsetBottom); + } + + if (bar[1]) { + bar[1]->InternalMove(bar[1]->currentStyle->preferredWidth, parent->height - parent->internalOffsetBottom, + parent->width - parent->internalOffsetRight, 0); + } + + if (pad) { + pad->InternalMove(parent->internalOffsetRight, parent->internalOffsetBottom, + parent->width - parent->internalOffsetRight, parent->height - parent->internalOffsetBottom); + } +} + +// --------------------------------- Panels. + +void PanelSwitcherTransitionComplete(EsPanel *panel) { + if (panel->switchedFrom) { + if (panel->destroyPreviousAfterTransitionCompletes) { + panel->switchedFrom->Destroy(); + } else { + EsElementSetHidden(panel->switchedFrom, true); + } + + panel->switchedFrom = nullptr; + } + + panel->transitionType = ES_TRANSITION_NONE; +} + +void PanelTableSetChildCell(EsPanel *panel, EsElement *child) { + uintptr_t index = panel->tableIndex++; + TableCell cell = {}; + + if (panel->flags & ES_PANEL_HORIZONTAL) { + cell.from[0] = cell.to[0] = index % panel->bandCount[0]; + cell.from[1] = cell.to[1] = index / panel->bandCount[0]; + + if (panel->bandCount[1] <= cell.from[1] && !panel->bands[1]) { + panel->bandCount[1] = cell.from[1] + 1; + } + } else { + cell.from[0] = cell.to[0] = index / panel->bandCount[1]; + cell.from[1] = cell.to[1] = index % panel->bandCount[1]; + + if (panel->bandCount[0] <= cell.from[0] && !panel->bands[0]) { + panel->bandCount[0] = cell.from[0] + 1; + } + } + + child->tableCell = cell; +} + +int ProcessPanelMessage(EsElement *element, EsMessage *message) { + EsPanel *panel = (EsPanel *) element; + EsRectangle bounds = panel->GetBounds(); + + panel->scroll.ReceivedMessage(message); + + if (message->type == ES_MSG_LAYOUT) { + if (panel->flags & ES_PANEL_TABLE) { + LayoutTable(panel, message); + } else if (panel->flags & ES_PANEL_SWITCHER) { + EsRectangle insets = panel->GetInsets(); + + if (panel->switchedFrom) { + EsElementMove(panel->switchedFrom, bounds.l + insets.l, bounds.t + insets.t, + bounds.r - bounds.l - insets.r - insets.l, bounds.b - bounds.t - insets.b - insets.t); + } + + if (panel->switchedTo) { + EsElementMove(panel->switchedTo, bounds.l + insets.l, bounds.t + insets.t, + bounds.r - bounds.l - insets.r - insets.l, bounds.b - bounds.t - insets.b - insets.t); + } + } else if (panel->flags & ES_PANEL_Z_STACK) { + EsRectangle insets = panel->GetInsets(); + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + EsElement *child = element->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + PanelMoveChild(child, bounds.r - bounds.l - insets.r - insets.l, + bounds.b - bounds.t - insets.b - insets.t, + bounds.l + insets.l, bounds.t + insets.t); + } + } else { + LayoutStack(panel, message); + } + } else if (message->type == ES_MSG_PAINT_CHILDREN) { + if ((panel->flags & ES_PANEL_SWITCHER) && panel->transitionType != ES_TRANSITION_NONE) { + double progress = SmoothAnimationTimeSharp((double) panel->transitionTimeMs / (double) panel->transitionLengthMs); + EsRectangle bounds = EsPainterBoundsClient(message->painter); + int width = Width(bounds), height = Height(bounds); + EsPaintTarget target; + + if (EsPaintTargetTake(&target, width, height)) { + EsPainter painter = { .clip = ES_RECT_4(0, width, 0, height), .width = width, .height = height, .target = &target }; + + // TODO 'Clip'-style transitions. ES_TRANSITION_REVEAL_UP/ES_TRANSITION_REVEAL_DOWN. + + if (panel->switchedFrom) { + panel->switchedFrom->InternalPaint(&painter, PAINT_SHADOW); + panel->switchedFrom->InternalPaint(&painter, ES_FLAGS_DEFAULT); + panel->switchedFrom->InternalPaint(&painter, PAINT_OVERLAY); + UIDrawTransitionEffect(message->painter, &target, bounds, (EsTransitionType) panel->transitionType, progress, false); + EsPaintTargetClear(&target); + } + + if (panel->switchedTo) { + panel->switchedTo->InternalPaint(&painter, PAINT_SHADOW); + panel->switchedTo->InternalPaint(&painter, ES_FLAGS_DEFAULT); + panel->switchedTo->InternalPaint(&painter, PAINT_OVERLAY); + UIDrawTransitionEffect(message->painter, &target, bounds, (EsTransitionType) panel->transitionType, progress, true); + } + + EsPaintTargetReturn(&target); + } else { + // Not enough memory to get a paint target. + return 0; + } + } else { + return 0; + } + } else if (message->type == ES_MSG_GET_WIDTH) { + if (!panel->measurementCache.Get(message, &panel->state)) { + if (panel->flags & ES_PANEL_TABLE) { + LayoutTable(panel, message); + } else if (panel->flags & (ES_PANEL_Z_STACK | ES_PANEL_SWITCHER)) { + int maximum = 0; + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + EsElement *child = element->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + int size = child->GetWidth(message->measure.height); + if (size > maximum) maximum = size; + } + + message->measure.width = maximum + panel->GetInsetWidth(); + } else { + LayoutStack(panel, message); + } + + panel->measurementCache.Store(message); + } + } else if (message->type == ES_MSG_GET_HEIGHT) { + if (!panel->measurementCache.Get(message, &panel->state)) { + if (panel->flags & ES_PANEL_TABLE) { + LayoutTable(panel, message); + } else if (panel->flags & (ES_PANEL_Z_STACK | ES_PANEL_SWITCHER)) { + int maximum = 0; + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + EsElement *child = element->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + int size = child->GetHeight(message->measure.width); + if (size > maximum) maximum = size; + } + + message->measure.height = maximum + panel->GetInsetHeight(); + } else { + LayoutStack(panel, message); + } + panel->measurementCache.Store(message); + } + } else if (message->type == ES_MSG_ENSURE_VISIBLE) { + EsElement *child = message->child, *e = child; + int offsetX = panel->scroll.position[0], offsetY = panel->scroll.position[1]; + while (e != element) offsetX += e->offsetX, offsetY += e->offsetY, e = e->parent; + EsRectangle bounds = panel->GetBounds(); + panel->scroll.SetX(offsetX + child->width / 2 - bounds.r / 2); + panel->scroll.SetY(offsetY + child->height / 2 - bounds.b / 2); + } else if (message->type == ES_MSG_PRE_ADD_CHILD) { + if (!panel->addingSeparator && panel->separatorStylePart && panel->GetChildCount()) { + panel->addingSeparator = true; + EsCustomElementCreate(panel, panel->separatorFlags, panel->separatorStylePart)->cName = "panel separator"; + panel->addingSeparator = false; + } + } else if (message->type == ES_MSG_ADD_CHILD) { + if (panel->flags & ES_PANEL_TABLE) { + if (!panel->bandCount[0] && !panel->bandCount[1]) { + // The application has not yet set the number of columns/rows, + // so we can't perform automatical element placement. + // The application will need to call EsPanelTableSetChildCells. + } else { + PanelTableSetChildCell(panel, (EsElement *) message->child); + } + } else if (panel->flags & ES_PANEL_SWITCHER) { + EsElement *child = (EsElement *) message->child; + child->state |= UI_STATE_BLOCK_INTERACTION; + } + } else if (message->type == ES_MSG_SCROLL_X || message->type == ES_MSG_SCROLL_Y) { + int delta = message->scrollbarMoved.scroll - message->scrollbarMoved.previous; + int deltaX = message->type == ES_MSG_SCROLL_X ? delta : 0; + int deltaY = message->type == ES_MSG_SCROLL_Y ? delta : 0; + + for (uintptr_t i = 0; i < panel->GetChildCount(); i++) { + EsElement *child = panel->GetChild(i); + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) continue; + child->InternalMove(child->width, child->height, child->offsetX - deltaX, child->offsetY - deltaY); + } + } else if (message->type == ES_MSG_ANIMATE) { + panel->transitionTimeMs += message->animate.deltaMs; + message->animate.complete = panel->transitionTimeMs >= panel->transitionLengthMs; + + if (panel->flags & ES_PANEL_SWITCHER) { + panel->Repaint(true); + + if (message->animate.complete) { + PanelSwitcherTransitionComplete(panel); + } + } else if (panel->movementItems.Length()) { + EsElementRelayout(panel); + + if (message->animate.complete) { + panel->movementItems.Free(); + } + } + } else if (message->type == ES_MSG_DESTROY_CONTENTS) { + if ((panel->flags & ES_PANEL_TABLE)) { + panel->tableIndex = 0; + panel->bandCount[(panel->flags & ES_PANEL_HORIZONTAL) ? 1 : 0] = 0; + } + } else if (message->type == ES_MSG_DESTROY) { + if ((panel->flags & ES_PANEL_TABLE)) { + EsHeapFree(panel->bands[0]); + EsHeapFree(panel->bands[1]); + } + } else if (message->type == ES_MSG_KEY_DOWN) { + if (!(panel->flags & (ES_PANEL_TABLE | ES_PANEL_SWITCHER)) + && panel->window->focused && panel->window->focused->parent == panel + && (panel->flags & ES_PANEL_HORIZONTAL)) { + bool reverse = panel->flags & ES_PANEL_REVERSE, + left = message->keyboard.scancode == ES_SCANCODE_LEFT_ARROW, + right = message->keyboard.scancode == ES_SCANCODE_RIGHT_ARROW; + + if ((left && !reverse) || (right && reverse)) { + EsElement *focus = nullptr; + + for (uintptr_t i = 0; i < panel->GetChildCount(); i++) { + EsElement *child = panel->GetChild(i); + + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) { + continue; + } + + if (child == panel->window->focused) { + break; + } else if (child->IsFocusable()) { + focus = child; + } + } + + if (focus) { + EsElementFocus(focus); + } + } else if ((left && reverse) || (right && !reverse)) { + EsElement *focus = nullptr; + + for (uintptr_t i = panel->GetChildCount(); i > 0; i--) { + EsElement *child = panel->GetChild(i - 1); + + if (child->flags & (ES_ELEMENT_HIDDEN | ES_ELEMENT_NON_CLIENT)) { + continue; + } + + if (child == panel->window->focused) { + break; + } else if (child->IsFocusable()) { + focus = child; + } + } + + if (focus) { + EsElementFocus(focus); + } + } else { + return 0; + } + } else { + return 0; + } + } else if (message->type == ES_MSG_GET_INSPECTOR_INFORMATION) { + EsBuffer *buffer = message->getContent.buffer; + + if (panel->flags & ES_PANEL_Z_STACK) { + EsBufferFormat(buffer, "z-stack"); + } else if (panel->flags & ES_PANEL_SWITCHER) { + EsBufferFormat(buffer, "switcher"); + } else if (panel->flags & ES_PANEL_TABLE) { + EsBufferFormat(buffer, "table"); + } else { + EsBufferFormat(buffer, "%z%z stack", + (panel->flags & ES_PANEL_REVERSE) ? "reverse " : "", + (panel->flags & ES_PANEL_HORIZONTAL) ? "horizontal" : "vertical"); + } + } else if (message->type == ES_MSG_BEFORE_Z_ORDER) { + bool isStack = !(panel->flags & (ES_PANEL_TABLE | ES_PANEL_SWITCHER | ES_PANEL_Z_STACK)); + + if (isStack && panel->children.Length() > 100) { + // Count the number of client children. + + size_t childCount = panel->children.Length(); + + while (childCount) { + if (panel->children[childCount - 1]->flags & ES_ELEMENT_NON_CLIENT) { + childCount--; + } else { + break; + } + } + + if (childCount < 100) { + return 0; + } + + message->beforeZOrder.nonClient = childCount; + + // Binary search for an early visible child. + + bool found = false; + uintptr_t position = 0; + + if (panel->flags & ES_PANEL_HORIZONTAL) { + ES_MACRO_SEARCH(childCount, result = message->beforeZOrder.clip.l - panel->children[index]->offsetX;, position, found); + } else { + ES_MACRO_SEARCH(childCount, result = message->beforeZOrder.clip.t - panel->children[index]->offsetY;, position, found); + } + + if (!found) { + position = 0; + } + + // Search back until we find the first. + // Assumption: children with paint outsets do not extend beyond the next child. + + while (position) { + if (position < childCount) { + EsElement *child = panel->children[position]; + + if (panel->flags & ES_PANEL_HORIZONTAL) { + if (child->offsetX + child->width + child->currentStyle->paintOutsets.r < message->beforeZOrder.clip.l) { + break; + } + } else { + if (child->offsetY + child->height + child->currentStyle->paintOutsets.b < message->beforeZOrder.clip.t) { + break; + } + } + } + + position--; + } + + message->beforeZOrder.start = position; + + // Search forward until we find the last visible child. + + while (position < childCount) { + EsElement *child = panel->children[position]; + + if (panel->flags & ES_PANEL_HORIZONTAL) { + if (child->offsetX - child->currentStyle->paintOutsets.l > message->beforeZOrder.clip.r) { + break; + } + } else { + if (child->offsetY - child->currentStyle->paintOutsets.t > message->beforeZOrder.clip.b) { + break; + } + } + + position++; + } + + message->beforeZOrder.end = position; + } + } else { + return 0; + } + + return ES_HANDLED; +} + +EsPanel *EsPanelCreate(EsElement *parent, uint64_t flags, const EsStyle *style) { + EsPanel *panel = (EsPanel *) EsHeapAllocate(sizeof(EsPanel), true); + + panel->Initialise(parent, flags, ProcessPanelMessage, style); + panel->cName = "panel"; + + if (flags & ES_PANEL_Z_STACK) panel->state |= UI_STATE_Z_STACK; + if (flags & ES_PANEL_HORIZONTAL) panel->flags |= ES_ELEMENT_LAYOUT_HINT_HORIZONTAL; + if (flags & ES_PANEL_REVERSE) panel->flags |= ES_ELEMENT_LAYOUT_HINT_REVERSE; + + panel->scroll.Setup(panel, + ((flags & ES_PANEL_H_SCROLL_FIXED) ? SCROLL_MODE_FIXED : (flags & ES_PANEL_H_SCROLL_AUTO) ? SCROLL_MODE_AUTO : SCROLL_MODE_NONE), + ((flags & ES_PANEL_V_SCROLL_FIXED) ? SCROLL_MODE_FIXED : (flags & ES_PANEL_V_SCROLL_AUTO) ? SCROLL_MODE_AUTO : SCROLL_MODE_NONE), + ES_FLAGS_DEFAULT); + + return panel; +} + +struct EsSpacer : EsElement { + int width, height; +}; + +int ProcessSpacerMessage(EsElement *element, EsMessage *message) { + EsSpacer *spacer = (EsSpacer *) element; + + if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = spacer->width * spacer->currentStyle->scale; + } else if (message->type == ES_MSG_GET_HEIGHT) { + message->measure.height = spacer->height * spacer->currentStyle->scale; + } + + return 0; +} + +EsElement *EsSpacerCreate(EsElement *panel, uint64_t flags, const EsStyle *style, int width, int height) { + EsSpacer *spacer = (EsSpacer *) EsHeapAllocate(sizeof(EsSpacer), true); + spacer->Initialise(panel, flags, ProcessSpacerMessage, style); + spacer->cName = "spacer"; + spacer->width = width == -1 ? 4 : width; + spacer->height = height == -1 ? 4 : height; + return spacer; +} + +EsElement *EsCustomElementCreate(EsElement *parent, uint64_t flags, const EsStyle *style) { + EsElement *element = (EsElement *) EsHeapAllocate(sizeof(EsElement), true); + element->Initialise(parent, flags, nullptr, style); + element->cName = "custom element"; + return element; +} + +void EsElementSetCellRange(EsElement *element, int xFrom, int yFrom, int xTo, int yTo) { + EsMessageMutexCheck(); + + if (xFrom == -1) xFrom = element->tableCell.from[0]; + if (yFrom == -1) yFrom = element->tableCell.from[1]; + if (xTo == -1) xTo = xFrom; + if (yTo == -1) yTo = yFrom; + + EsPanel *panel = (EsPanel *) element->parent; + EsAssert(panel->messageClass == ProcessPanelMessage && panel->flags & ES_PANEL_TABLE); // Invalid parent for SetCellRange. + + TableCell cell = {}; + cell.from[0] = xFrom, cell.from[1] = yFrom; + cell.to[0] = xTo, cell.to[1] = yTo; + element->tableCell = cell; +} + +void EsPanelSetBands(EsPanel *panel, size_t columnCount, size_t rowCount, EsPanelBand *columns, EsPanelBand *rows) { + EsMessageMutexCheck(); + EsAssert(panel->flags & ES_PANEL_TABLE); // Cannot set the bands layout for a non-table panel. + EsHeapFree(panel->bands[0]); + EsHeapFree(panel->bands[1]); + panel->bands[0] = nullptr; + panel->bands[1] = nullptr; + panel->bandCount[0] = columnCount; + panel->bandCount[1] = rowCount; + panel->bands[0] = columns ? (EsPanelBand *) EsHeapAllocate(columnCount * sizeof(EsPanelBand), false) : nullptr; + panel->bands[1] = rows ? (EsPanelBand *) EsHeapAllocate(rowCount * sizeof(EsPanelBand), false) : nullptr; + if (columns) EsMemoryCopy(panel->bands[0], columns, columnCount * sizeof(EsPanelBand)); + if (rows) EsMemoryCopy(panel->bands[1], rows, rowCount * sizeof(EsPanelBand)); +} + +void EsPanelSetBandsAll(EsPanel *panel, EsPanelBand *column, EsPanelBand *row) { + EsMessageMutexCheck(); + EsAssert(panel->flags & ES_PANEL_TABLE); // Cannot set the bands layout for a non-table panel. + + EsPanelBand *templates[2] = { column, row }; + + for (uintptr_t axis = 0; axis < 2; axis++) { + if (!templates[axis]) continue; + + if (!panel->bands[axis]) { + panel->bands[axis] = (EsPanelBand *) EsHeapAllocate(panel->bandCount[axis] * sizeof(EsPanelBand), false); + } + + for (uintptr_t i = 0; i < panel->bandCount[axis]; i++) { + panel->bands[axis][i] = *templates[axis]; + } + } +} + +void EsPanelTableSetChildCells(EsPanel *panel) { + // The number of columns/rows should have been set by the time this function is called. + EsAssert(panel->bandCount[0] || panel->bandCount[1]); + + panel->tableIndex = 0; + panel->bandCount[(panel->flags & ES_PANEL_HORIZONTAL) ? 1 : 0] = 0; + + for (uintptr_t i = 0; i < panel->GetChildCount(); i++) { + EsElement *child = panel->GetChild(i); + if (child->flags & ES_ELEMENT_NON_CLIENT) continue; + PanelTableSetChildCell(panel, child); + } +} + +void EsPanelSwitchTo(EsPanel *panel, EsElement *targetChild, EsTransitionType transitionType, uint32_t flags, uint32_t timeMs) { + EsMessageMutexCheck(); + EsAssert(panel->flags & ES_PANEL_SWITCHER); // Cannot switch element for a non-switcher panel. + timeMs *= ANIMATION_TIME_SCALE; + + if (targetChild == panel->switchedTo) { + return; + } + + if (panel->switchedFrom) { + // We're interrupting the previous transition. + PanelSwitcherTransitionComplete(panel); + } + + panel->transitionType = transitionType; + panel->transitionTimeMs = 0; + panel->transitionLengthMs = timeMs; + panel->switchedFrom = panel->switchedTo; + panel->switchedTo = targetChild; + panel->destroyPreviousAfterTransitionCompletes = flags & ES_PANEL_SWITCHER_DESTROY_PREVIOUS_AFTER_TRANSITION; + + if (panel->switchedTo) { + EsElementSetHidden(panel->switchedTo, false); + panel->switchedTo->state &= ~UI_STATE_BLOCK_INTERACTION; + panel->switchedTo->BringToFront(); + } + + if (panel->switchedFrom) { + panel->switchedFrom->state |= UI_STATE_BLOCK_INTERACTION; + UIMaybeRemoveFocusedElement(panel->window); + } + + if (transitionType == ES_TRANSITION_NONE || panel->switchedFrom == panel->switchedTo || !panel->transitionLengthMs) { + PanelSwitcherTransitionComplete(panel); + } else { + panel->StartAnimating(); + } + + EsElementRelayout(panel); +} + +void EsPanelStartMovementAnimation(EsPanel *panel, uint32_t timeMs) { + // TODO Custom smoothing functions. + + timeMs *= ANIMATION_TIME_SCALE; + if (!timeMs) return; + EsMessageMutexCheck(); + EsAssert(~panel->flags & ES_PANEL_SWITCHER); // Use EsPanelSwitchTo! + panel->transitionTimeMs = 0; + panel->transitionLengthMs = timeMs; + panel->StartAnimating(); + panel->movementItems.Free(); + + for (uintptr_t i = 0; i < panel->GetChildCount(); i++) { + EsElement *element = panel->GetChild(i); + + if (element->flags & ES_ELEMENT_NON_CLIENT) { + continue; + } + + PanelMovementItem item = {}; + item.element = element; + item.oldBounds = ES_RECT_4(element->offsetX, element->offsetX + element->width, + element->offsetY, element->offsetY + element->height); + panel->movementItems.Add(item); + } + + EsElementRelayout(panel); +} + +// --------------------------------- Canvas panes. + +struct EsCanvasPane : EsElement { + double panX, panY, zoom; + bool zoomFit, contentsChanged, center; + int previousWidth, previousHeight; +}; + +EsElement *CanvasPaneGetCanvas(EsElement *element) { + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + if (~element->GetChild(i)->flags & ES_ELEMENT_NON_CLIENT) { + return element->GetChild(i); + } + } + + return nullptr; +} + +int ProcessCanvasPaneMessage(EsElement *element, EsMessage *message) { + EsCanvasPane *pane = (EsCanvasPane *) element; + + if (message->type == ES_MSG_LAYOUT) { + EsElement *canvas = CanvasPaneGetCanvas(element); + if (!canvas) return 0; + + EsRectangle bounds = element->GetBounds(); + EsRectangle insets = element->currentStyle->insets; + bounds.l += insets.l, bounds.r -= insets.r; + bounds.t += insets.t, bounds.b -= insets.b; + + pane->panX -= (Width(bounds) - pane->previousWidth) / 2 / pane->zoom; + pane->panY -= (Height(bounds) - pane->previousHeight) / 2 / pane->zoom; + pane->previousWidth = Width(bounds), pane->previousHeight = Height(bounds); + + int width = canvas->GetWidth(0), height = canvas->GetHeight(0); + + double minimumZoomX = 1, minimumZoomY = 1; + if (width > Width(bounds)) minimumZoomX = (double) Width(bounds) / width; + if (height > Height(bounds)) minimumZoomY = (double) Height(bounds) / height; + double minimumZoom = minimumZoomX < minimumZoomY ? minimumZoomX : minimumZoomY; + + if (pane->zoom < minimumZoom || pane->contentsChanged || pane->zoomFit) { + pane->zoom = minimumZoom; + pane->zoomFit = true; + } + + pane->contentsChanged = false; + + if (pane->panX < 0) pane->panX = 0; + if (pane->panX > width - Width(bounds) / pane->zoom) pane->panX = width - Width(bounds) / pane->zoom; + if (pane->panY < 0) pane->panY = 0; + if (pane->panY > height - Height(bounds) / pane->zoom) pane->panY = height - Height(bounds) / pane->zoom; + + if (width * pane->zoom <= Width(bounds) || pane->center) { + pane->panX = width / 2 - Width(bounds) / pane->zoom / 2; + } + + if (height * pane->zoom <= Height(bounds) || pane->center) { + pane->panY = height / 2 - Height(bounds) / pane->zoom / 2; + } + + pane->center = false; + + int x = (int) (0.5f + LinearMap(pane->panX, pane->panX + Width(bounds) / pane->zoom, 0, Width(bounds), 0)); + int y = (int) (0.5f + LinearMap(pane->panY, pane->panY + Height(bounds) / pane->zoom, 0, Height(bounds), 0)); + canvas->InternalMove(width, height, x + bounds.l, y + bounds.t); + } else if (message->type == ES_MSG_PAINT) { + EsElement *canvas = CanvasPaneGetCanvas(element); + if (!canvas) return 0; + + UIStyle *style = GetStyle(MakeStyleKey(ES_STYLE_CANVAS_SHADOW, 0), true); + EsRectangle shadow1 = ES_RECT_4PD(canvas->offsetX + style->preferredWidth, canvas->offsetY + canvas->height, + canvas->width, style->preferredHeight); + EsRectangle shadow2 = ES_RECT_4PD(canvas->offsetX + canvas->width, canvas->offsetY + style->preferredHeight, + style->preferredWidth, canvas->height - style->preferredHeight); + style->PaintLayers(message->painter, shadow1, THEME_CHILD_TYPE_ONLY, ES_FLAGS_DEFAULT); + style->PaintLayers(message->painter, shadow2, THEME_CHILD_TYPE_ONLY, ES_FLAGS_DEFAULT); + } + + return 0; +} + +EsCanvasPane *EsCanvasPaneCreate(EsElement *parent, uint64_t flags, const EsStyle *style) { + EsCanvasPane *pane = (EsCanvasPane *) EsHeapAllocate(sizeof(EsCanvasPane), true); + pane->Initialise(parent, flags, ProcessCanvasPaneMessage, style); + pane->cName = "canvas pane"; + pane->zoom = 1.0; + return pane; +} + +// --------------------------------- Text displays and textboxes. + +#define TEXT_ELEMENTS +#include "text.cpp" +#undef TEXT_ELEMENTS + +// --------------------------------- Announcements. + +// TODO Different colored messages for info/warning/error. +// TODO Different hold times. + +int AnnouncementMessage(EsElement *element, EsMessage *message) { + EsWindow *window = (EsWindow *) element; + + if (message->type == ES_MSG_ANIMATE) { + window->announcementTimeMs += message->animate.deltaMs; + + double progress = window->announcementTimeMs / GetConstantNumber("announcementDuration"); + + if (progress > 1) { + progress = 1; + EsElementDestroy(window); + return 0; + } + + progress = 2 * progress - 1; + progress = (1 + progress * progress * progress * progress * progress) * 0.5; + + double inOnly = 2 * (progress < 0.5 ? progress : 0.5); + double inOut = progress < 0.5 ? progress * 2 : (2 - progress * 2); + + EsRectangle bounds = EsWindowGetBounds(window); + int32_t height = Height(bounds); + bounds.t = window->announcementBaseY - inOnly * GetConstantNumber("announcementMovement"); + bounds.b = bounds.t + height; + + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0xFF * inOut, 0, ES_WINDOW_PROPERTY_ALPHA); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, + ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN | ES_WINDOW_MOVE_ALWAYS_ON_TOP | ES_WINDOW_MOVE_UPDATE_SCREEN); + + message->animate.complete = false; + } + + return 0; +} + +void EsAnnouncementShow(EsWindow *parent, uint64_t flags, int32_t x, int32_t y, const char *text, ptrdiff_t textBytes) { + (void) flags; + + EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_TIP); + window->messageUser = AnnouncementMessage; + + EsTextDisplay *display = EsTextDisplayCreate(window, ES_CELL_FILL, ES_STYLE_ANNOUNCEMENT, text, textBytes); + + int32_t width = display->GetWidth(0); + int32_t height = display->GetHeight(width); + + EsRectangle parentBounds = EsWindowGetBounds(parent); + EsRectangle bounds = ES_RECT_4PD(x - width / 2 + parentBounds.l, y - height + parentBounds.t, width, height); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0x00, 0, ES_WINDOW_PROPERTY_ALPHA); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN | ES_WINDOW_MOVE_ALWAYS_ON_TOP); + window->announcementBaseY = EsWindowGetBounds(window).t; + window->StartAnimating(); +} + +// --------------------------------- Buttons. + +int ProcessButtonMessage(EsElement *element, EsMessage *message) { + EsButton *button = (EsButton *) element; + + if (message->type == ES_MSG_PAINT) { + EsDrawContent(message->painter, element, + ES_RECT_2S(message->painter->width, message->painter->height), + button->label, button->labelBytes, button->iconID, + (button->flags & ES_BUTTON_DROPDOWN) ? ES_DRAW_CONTENT_MARKER_DOWN_ARROW : ES_FLAGS_DEFAULT); + } else if (message->type == ES_MSG_GET_WIDTH) { + if (!button->measurementCache.Get(message, &button->state)) { + int stringWidth = button->currentStyle->MeasureTextWidth(button->label, button->labelBytes); + int iconWidth = button->iconID ? button->currentStyle->metrics->iconSize : 0; + int contentWidth = stringWidth + iconWidth + ((stringWidth && iconWidth) ? button->currentStyle->gapMinor : 0) + + button->currentStyle->insets.l + button->currentStyle->insets.r; + + if (button->flags & ES_BUTTON_DROPDOWN) { + int64_t width = 0; + GetPreferredSizeFromStylePart(ES_STYLE_MARKER_DOWN_ARROW, &width, nullptr); + contentWidth += width + button->currentStyle->gapMinor; + } + + int minimumReportedWidth = GetConstantNumber("pushButtonMinimumReportedWidth"); + if (button->flags & ES_BUTTON_MENU_ITEM) minimumReportedWidth = GetConstantNumber("menuItemMinimumReportedWidth"); + if (!stringWidth || (button->flags & ES_BUTTON_COMPACT)) minimumReportedWidth = 0; + + message->measure.width = minimumReportedWidth > contentWidth ? minimumReportedWidth : contentWidth; + + button->measurementCache.Store(message); + } + } else if (message->type == ES_MSG_DESTROY) { + EsHeapFree(button->label); + + if (button->command) { + Array elements = { button->command->elements }; + elements.FindAndDeleteSwap(button, true); + button->command->elements = elements.array; + } + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { + if (button->flags & ES_BUTTON_CHECKBOX) { + button->customStyleState &= ~THEME_STATE_INDETERMINATE; + button->customStyleState ^= THEME_STATE_CHECKED; + } else if (button->flags & ES_BUTTON_RADIOBOX) { + button->customStyleState |= THEME_STATE_CHECKED; + + EsMessage m = { ES_MSG_RADIO_GROUP_UPDATED }; + + for (uintptr_t i = 0; i < button->parent->GetChildCount(); i++) { + if (button->parent->GetChild(i) != button) { + EsMessageSend(button->parent->GetChild(i), &m); + } + } + } + + if (button->checkBuddy) { + EsElementSetDisabled(button->checkBuddy, !(button->customStyleState & (THEME_STATE_CHECKED | THEME_STATE_INDETERMINATE))); + } + + if (button->onCommand) { + button->onCommand(button->instance, button, button->command); + } + + if (button->flags & ES_BUTTON_MENU_ITEM) { + button->window->Destroy(); + } + } else if (message->type == ES_MSG_RADIO_GROUP_UPDATED && (button->flags & ES_BUTTON_RADIOBOX)) { + EsButtonSetCheck(button, ES_CHECK_UNCHECKED); + } else if (message->type == ES_MSG_FOCUSED_START) { + if (button->window->defaultEnterButton && (button->flags & ES_BUTTON_PUSH)) { + button->window->enterButton->customStyleState &= ~THEME_STATE_DEFAULT_BUTTON; + button->window->enterButton->MaybeRefreshStyle(); + button->customStyleState |= THEME_STATE_DEFAULT_BUTTON; + button->window->enterButton = button; + } + } else if (message->type == ES_MSG_FOCUSED_END) { + if (button->window->enterButton == button) { + button->customStyleState &= ~THEME_STATE_DEFAULT_BUTTON; + button->window->enterButton = button->window->defaultEnterButton; + button->window->enterButton->customStyleState |= THEME_STATE_DEFAULT_BUTTON; + button->window->enterButton->MaybeRefreshStyle(); + } + } else if (message->type == ES_MSG_GET_INSPECTOR_INFORMATION) { + EsBufferFormat(message->getContent.buffer, "'%s'", button->labelBytes, button->label); + } else { + return 0; + } + + return ES_HANDLED; +} + +EsButton *EsButtonCreate(EsElement *parent, uint64_t flags, const EsStyle *style, const char *label, ptrdiff_t labelBytes) { + EsButton *button = (EsButton *) EsHeapAllocate(sizeof(EsButton), true); + + if (!style) { + if (flags & ES_BUTTON_MENU_ITEM) { + if (flags & ES_MENU_ITEM_HEADER) { + style = ES_STYLE_MENU_ITEM_HEADER; + } else { + style = ES_STYLE_MENU_ITEM_NORMAL; + } + } else if (flags & ES_BUTTON_TOOLBAR) { + style = ES_STYLE_PUSH_BUTTON_TOOLBAR; + } else if (flags & ES_BUTTON_CHECKBOX) { + style = ES_STYLE_CHECKBOX_NORMAL; + } else if (flags & ES_BUTTON_RADIOBOX) { + style = ES_STYLE_CHECKBOX_RADIOBOX; + } else if (flags & ES_BUTTON_DEFAULT) { + style = ES_STYLE_PUSH_BUTTON_NORMAL; + } else { + style = ES_STYLE_PUSH_BUTTON_NORMAL; + } + + style = UIGetDefaultStyleVariant(style, parent); + } + + if (style == ES_STYLE_PUSH_BUTTON_NORMAL) { + flags |= ES_BUTTON_PUSH; + } else if (style == ES_STYLE_PUSH_BUTTON_TOOLBAR || style == ES_STYLE_PUSH_BUTTON_TOOLBAR_MEDIUM + || style == ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG || style == ES_STYLE_PUSH_BUTTON_STATUS_BAR) { + flags |= ES_BUTTON_COMPACT | ES_BUTTON_NOT_FOCUSABLE; + } else if (style == ES_STYLE_CHECKBOX_NORMAL || style == ES_STYLE_CHECKBOX_RADIOBOX) { + flags |= ES_BUTTON_COMPACT; + } + + if (~flags & ES_BUTTON_NOT_FOCUSABLE) { + flags |= ES_ELEMENT_FOCUSABLE; + } + + button->Initialise(parent, flags, ProcessButtonMessage, style); + button->cName = "button"; + + if (flags & ES_BUTTON_DEFAULT) { + button->window->defaultEnterButton = button; + button->window->enterButton = button; + button->customStyleState |= THEME_STATE_DEFAULT_BUTTON; + } else if (flags & ES_BUTTON_CANCEL) { + button->window->escapeButton = button; + } + + if (labelBytes == -1) labelBytes = EsCStringLength(label); + HeapDuplicate((void **) &button->label, label, labelBytes); + button->labelBytes = labelBytes; + + if ((flags & ES_BUTTON_MENU_ITEM) && (flags & ES_MENU_ITEM_HEADER)) { + EsElementSetDisabled(button, true); + } + + EsButtonSetCheck(button, (EsCheckState) (flags & 3), false); + + return button; +} + +void EsButtonSetIcon(EsButton *button, uint32_t iconID) { + EsMessageMutexCheck(); + + button->iconID = iconID; + button->Repaint(true); +} + +void EsButtonOnCommand(EsButton *button, EsCommandCallbackFunction onCommand, EsCommand *command) { + EsMessageMutexCheck(); + + button->onCommand = onCommand; + button->command = command; +} + +void EsButtonSetCheckBuddy(EsButton *button, EsElement *checkBuddy) { + EsMessageMutexCheck(); + + button->checkBuddy = checkBuddy; + EsElementSetDisabled(button->checkBuddy, !(button->customStyleState & (THEME_STATE_CHECKED | THEME_STATE_INDETERMINATE))); +} + +EsElement *EsButtonGetCheckBuddy(EsButton *button) { + EsMessageMutexCheck(); + + return button->checkBuddy; +} + +EsCheckState EsButtonGetCheck(EsButton *button) { + EsMessageMutexCheck(); + + if (button->customStyleState & THEME_STATE_CHECKED) return ES_CHECK_CHECKED; + if (button->customStyleState & THEME_STATE_INDETERMINATE) return ES_CHECK_INDETERMINATE; + return ES_CHECK_UNCHECKED; +} + +void EsButtonSetCheck(EsButton *button, EsCheckState checkState, bool sendUpdatedMessage) { + if (checkState == EsButtonGetCheck(button)) { + return; + } + + button->customStyleState &= ~(THEME_STATE_CHECKED | THEME_STATE_INDETERMINATE); + + if (checkState == ES_CHECK_CHECKED) button->customStyleState |= THEME_STATE_CHECKED; + if (checkState == ES_CHECK_INDETERMINATE) button->customStyleState |= THEME_STATE_INDETERMINATE; + + if (sendUpdatedMessage) { + EsMessage m = { ES_MSG_CHECK_UPDATED }; + m.checkState = checkState; + EsMessageSend(button, &m); + + if (button->onCommand) { + button->onCommand(button->instance, button, button->command); + } + } + + if (button->checkBuddy) { + EsElementSetDisabled(button->checkBuddy, !(button->customStyleState & (THEME_STATE_CHECKED | THEME_STATE_INDETERMINATE))); + } + + button->MaybeRefreshStyle(); +} + +void EsMenuAddItem(EsMenu *menu, uint64_t flags, const char *label, ptrdiff_t labelBytes, EsMenuCallbackFunction callback, EsGeneric context) { + EsButton *button = (EsButton *) EsButtonCreate(menu, + ES_BUTTON_NOT_FOCUSABLE | ES_BUTTON_MENU_ITEM | ES_CELL_H_FILL | flags, 0, + label, labelBytes != -1 ? labelBytes : EsCStringLength(label)); + button->userData = (void *) callback; + + button->messageUser = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { + EsMenuCallbackFunction callback = (EsMenuCallbackFunction) element->userData.p; + if (callback) callback((EsMenu *) element->window, ((EsButton *) element)->menuItemContext); + } + + return 0; + }; + + button->menuItemContext = context; + + if (flags & ES_MENU_ITEM_CHECKED) { + EsButtonSetCheck(button, ES_CHECK_CHECKED); + } +} + +void EsMenuAddCommand(EsMenu *menu, uint64_t flags, const char *label, ptrdiff_t labelBytes, EsCommand *command) { + EsButton *button = (EsButton *) EsButtonCreate(menu, + ES_BUTTON_NOT_FOCUSABLE | ES_BUTTON_MENU_ITEM | ES_CELL_H_FILL | flags, + 0, label, labelBytes); + EsCommandAddButton(command, button); +} + +// --------------------------------- Color wells and pickers. + +struct EsColorWell : EsElement { + uint32_t color; + struct ColorPicker *picker; + bool indeterminate; +}; + +int ProcessColorWellMessage(EsElement *element, EsMessage *message); + +struct ColorPicker { + uint32_t color; + float hue, saturation, value, opacity; + float dragStartHue, dragStartSaturation; + int dragComponent; + bool indeterminateBeforeEyedrop; + bool modified; + + ColorPickerHost host; + EsPanel *panel; + EsElement *circle, *slider, *circlePoint, *sliderPoint, *opacitySlider, *opacitySliderPoint; + EsTextbox *textbox; + + uint32_t GetColorForHost() { + return color | (host.hasOpacity ? ((uint32_t) (255.0f * opacity) << 24) : 0); + } + + void Sync(EsElement *excluding) { + if (excluding != textbox && textbox) { + char string[16]; + size_t length; + + if (host.indeterminate && *host.indeterminate) { + string[0] = '#'; + length = 1; + } else { + const char *hexChars = "0123456789ABCDEF"; + + if (host.hasOpacity) { + uint8_t alpha = (uint8_t) (opacity * 0xFF); + length = EsStringFormat(string, sizeof(string), "#%c%c%c%c%c%c%c%c", + hexChars[(alpha >> 4) & 0xF], hexChars[(alpha >> 0) & 0xF], hexChars[(color >> 20) & 0xF], hexChars[(color >> 16) & 0xF], + hexChars[(color >> 12) & 0xF], hexChars[(color >> 8) & 0xF], hexChars[(color >> 4) & 0xF], hexChars[(color >> 0) & 0xF]); + } else { + length = EsStringFormat(string, sizeof(string), "#%c%c%c%c%c%c", + hexChars[(color >> 20) & 0xF], hexChars[(color >> 16) & 0xF], hexChars[(color >> 12) & 0xF], + hexChars[(color >> 8) & 0xF], hexChars[(color >> 4) & 0xF], hexChars[(color >> 0) & 0xF]); + } + } + + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, string, length, false); + } + + if (excluding != circle) circle->Repaint(true); + if (excluding != slider) slider->Repaint(true); + if (excluding != opacitySlider && opacitySlider) opacitySlider->Repaint(true); + + if (excluding != circlePoint) { + if (host.indeterminate && *host.indeterminate) { + EsElementSetHidden(circlePoint, true); + } else { + EsElementSetHidden(circlePoint, false); + float x = saturation * EsCRTcosf((hue - 3) * 1.047197551) * 0.5f + 0.5f; + float y = saturation * EsCRTsinf((hue - 3) * 1.047197551) * 0.5f + 0.5f; + EsRectangle pointSize = EsElementGetPreferredSize(circlePoint), circleSize = EsElementGetPreferredSize(circle); + int x2 = x * circleSize.r - pointSize.r / 2, y2 = y * circleSize.b - pointSize.b / 2; + EsElementMove(circlePoint, x2, y2, pointSize.r, pointSize.b, true); + circlePoint->Repaint(true); + } + } + + if (excluding != sliderPoint) { + if (host.indeterminate && *host.indeterminate) { + EsElementSetHidden(sliderPoint, true); + } else { + EsElementSetHidden(sliderPoint, false); + float x = 0.5f, y = 1.0f - EsCRTpowf(value, 1.333f); + EsRectangle pointSize = EsElementGetPreferredSize(sliderPoint), sliderSize = EsElementGetPreferredSize(slider); + int x2 = x * sliderSize.r - pointSize.r / 2, y2 = y * sliderSize.b - pointSize.b / 2; + EsElementMove(sliderPoint, x2, y2, pointSize.r, pointSize.b, true); + sliderPoint->Repaint(true); + } + } + + if (excluding != opacitySliderPoint && opacitySliderPoint) { + if (host.indeterminate && *host.indeterminate) { + EsElementSetHidden(opacitySliderPoint, true); + } else { + EsElementSetHidden(opacitySliderPoint, false); + float x = 0.5f, y = 1.0f - opacity; + EsRectangle pointSize = EsElementGetPreferredSize(opacitySliderPoint), sliderSize = EsElementGetPreferredSize(opacitySlider); + int x2 = x * sliderSize.r - pointSize.r / 2, y2 = y * sliderSize.b - pointSize.b / 2; + EsElementMove(opacitySliderPoint, x2, y2, pointSize.r, pointSize.b, true); + opacitySliderPoint->Repaint(true); + } + } + + if (excluding != host.well && host.well) { + if (host.well->messageClass == ProcessColorWellMessage) { + ((EsColorWell *) host.well)->color = GetColorForHost(); + host.well->Repaint(true); + } + + EsMessage m = { ES_MSG_COLOR_CHANGED }; + m.colorChanged.newColor = GetColorForHost(); + m.colorChanged.pickerClosed = false; + EsMessageSend(host.well, &m); + + modified = true; + } + } + + void PositionOnCircleToColor(int _x, int _y) { + EsRectangle size = EsElementGetInsetSize(circle); + float x = (float) _x / (float) (size.r - 1) * 2.0f - 1.0f; + float y = (float) _y / (float) (size.b - 1) * 2.0f - 1.0f; + float newSaturation = EsCRTsqrtf(x * x + y * y), newHue = EsCRTatan2f(y, x) * 0.954929659f + 3; + if (!EsKeyboardIsAltHeld() && newSaturation < 0.1f) newSaturation = 0; + if (newSaturation > 1) newSaturation = 1; + if (newHue >= 6) newHue -= 6; + if (newHue < 0 || newHue >= 6) newHue = 0; + + if (EsKeyboardIsShiftHeld()) { + float deltaHue = dragStartHue - newHue; + float deltaSaturation = EsCRTfabs(dragStartSaturation - newSaturation); + + if (-3 < deltaHue && deltaHue < 3) deltaHue = EsCRTfabs(deltaHue); + if (deltaHue < -3) deltaHue += 6; + if (deltaHue > 3) deltaHue = -deltaHue + 6; + deltaHue /= 2; + + if (deltaHue < deltaSaturation) { + newHue = dragStartHue; + } else { + newSaturation = dragStartSaturation; + } + } + + uint32_t newColor = EsColorConvertToRGB(newHue, newSaturation, value); + hue = newHue, color = newColor, saturation = newSaturation; + if (host.indeterminate) *host.indeterminate = false; + Sync(circle); + } + + void PositionOnSliderToColor(int _x, int _y) { + (void) _x; + EsRectangle size = EsElementGetInsetSize(slider); + float y = 1 - (float) _y / (float) (size.b - 1); + if (y < 0) y = 0; + y = EsCRTsqrtf(y) * EsCRTsqrtf(EsCRTsqrtf(y)); + if (y > 1) y = 1; + if (y < 0) y = 0; + uint32_t newColor = EsColorConvertToRGB(hue, saturation, y); + color = newColor; + value = y; + if (host.indeterminate) *host.indeterminate = false; + Sync(slider); + } + + void PositionOnOpacitySliderToColor(int _x, int _y) { + (void) _x; + EsRectangle size = EsElementGetInsetSize(opacitySlider); + float y = 1 - (float) _y / (float) (size.b - 1); + if (y > 1) y = 1; + if (y < 0) y = 0; + opacity = y; + if (host.indeterminate) *host.indeterminate = false; + Sync(opacitySlider); + } +}; + +int ProcessColorChosenPointMessage(EsElement *element, EsMessage *message) { + ColorPicker *picker = (ColorPicker *) element->userData.p; + + if (message->type == ES_MSG_PAINT) { + EsRectangle bounds = EsPainterBoundsInset(message->painter); + EsStyledBox box = {}; + box.bounds = bounds; + box.clip = message->painter->clip; + box.borderColor = 0xFFFFFFFF; + box.backgroundColor = picker->color | 0xFF000000; + box.backgroundColor2 = picker->color | ((uint32_t) (255.0f * picker->opacity) << 24); + box.borders = ES_RECT_1(2); + box.cornerRadiusTopLeft = box.cornerRadiusTopRight = box.cornerRadiusBottomLeft = box.cornerRadiusBottomRight = Width(box.bounds) / 2; + + if (picker->opacity < 1 && picker->host.hasOpacity) { + box.fragmentShader = [] (int x, int y, EsStyledBox *box) -> uint32_t { + // TODO Move the alpha background as the chosen point moves. + return EsColorBlend(((((x - 2) >> 3) ^ ((y + 5) >> 3)) & 1) ? 0xFFFFFFFF : 0xFFC0C0C0, + box->backgroundColor2, false); + }; + } + + DrawStyledBox(message->painter, box); + } + + return 0; +} + +void ColorPickerCreate(EsElement *parent, ColorPickerHost host, uint32_t initialColor, bool showTextbox) { + ColorPicker *picker = (ColorPicker *) EsHeapAllocate(sizeof(ColorPicker), true); + picker->host = host; + picker->color = initialColor & 0xFFFFFF; + picker->opacity = (float) (initialColor >> 24) / 255.0f; + if (host.well && host.well->messageClass == ProcessColorWellMessage) ((EsColorWell *) host.well)->picker = picker; + EsColorConvertToHSV(picker->color, &picker->hue, &picker->saturation, &picker->value); + + picker->panel = EsPanelCreate(parent, ES_PANEL_HORIZONTAL | ES_PANEL_TABLE, ES_STYLE_COLOR_PICKER_MAIN_PANEL); + + picker->panel->userData = picker; + + picker->panel->messageUser = [] (EsElement *element, EsMessage *message) { + ColorPicker *picker = (ColorPicker *) element->userData.p; + + if (message->type == ES_MSG_DESTROY) { + if (picker->host.well && picker->modified) { + EsMessage m = { ES_MSG_COLOR_CHANGED }; + m.colorChanged.newColor = picker->GetColorForHost(); + m.colorChanged.pickerClosed = true; + EsMessageSend(picker->host.well, &m); + + if (picker->host.well->messageClass == ProcessColorWellMessage) { + ((EsColorWell *) picker->host.well)->picker = nullptr; + } + } + + EsHeapFree(picker); + } + + return 0; + }; + + bool hasOpacity = picker->host.hasOpacity; + + EsPanelSetBands(picker->panel, hasOpacity ? 3 : 2, showTextbox ? 2 : 1); + + picker->circle = EsCustomElementCreate(picker->panel, ES_ELEMENT_FOCUSABLE | ES_ELEMENT_NOT_TAB_TRAVERSABLE, ES_STYLE_COLOR_CIRCLE); + + picker->circle->cName = "hue-saturation wheel"; + picker->circle->userData = picker; + + picker->circle->messageUser = [] (EsElement *element, EsMessage *message) { + ColorPicker *picker = (ColorPicker *) element->userData.p; + + if (message->type == ES_MSG_PAINT) { + // EsPerformanceTimerPush(); + + EsPainter *painter = message->painter; + EsRectangle bounds = EsPainterBoundsInset(painter); + EsRectangle clip = painter->clip; + EsRectangleClip(clip, bounds, &clip); + uint32_t stride = painter->target->stride; + uint32_t *bitmap = (uint32_t *) painter->target->bits; + float epsilon = 1.0f / (bounds.b - bounds.t - 1); + + for (int j = clip.t; j < clip.b; j++) { + for (int i = clip.l; i < clip.r; i++) { + float x = (float) (i - bounds.l) / (float) (bounds.r - bounds.l - 1) * 2.0f - 1.0f; + float y = (float) (j - bounds.t) / (float) (bounds.b - bounds.t - 1) * 2.0f - 1.0f; + float radius = EsCRTsqrtf(x * x + y * y), hue = EsCRTatan2f(y, x) * 0.954929659f + 3; + if (hue >= 6) hue -= 6; + + if (radius > 1.0f + epsilon) { + // Outside the circle. + } else if (radius > 1.0f - epsilon) { + // On the edge. + uint32_t over = EsColorConvertToRGB(hue, 1, picker->value); + // float opacity = (1.0f - ((radius - (1.0f - epsilon)) / epsilon * 0.5f)); + float opacity = 0.5f - (radius - 1.0f) / epsilon * 0.5f; + uint32_t alpha = (((uint32_t) (255.0f * opacity)) & 0xFF) << 24; + uint32_t *under = &bitmap[i + j * (stride >> 2)]; + *under = EsColorBlend(*under, over | alpha, true); + } else { + // Inside the circle. + uint32_t over = EsColorConvertToRGB(hue, radius, picker->value); + bitmap[i + j * (stride >> 2)] = over | 0xFF000000; + } + } + } + + // EsPrint("Rendered color circle in %*Fms.\n", 3, 1000 * EsPerformanceTimerPop()); + } else if (message->type == ES_MSG_HIT_TEST) { + EsRectangle size = EsElementGetInsetSize(element); + float x = (float) message->hitTest.x / (float) (size.r - 1) * 2.0f - 1.0f; + float y = (float) message->hitTest.y / (float) (size.b - 1) * 2.0f - 1.0f; + message->hitTest.inside = x * x + y * y <= 1; + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + picker->PositionOnCircleToColor(message->mouseDragged.newPositionX, message->mouseDragged.newPositionY); + } else if (message->type == ES_MSG_KEY_DOWN) { + if ((message->keyboard.scancode == ES_SCANCODE_LEFT_SHIFT || message->keyboard.scancode == ES_SCANCODE_RIGHT_SHIFT) && !message->keyboard.repeat) { + picker->dragStartHue = picker->hue; + picker->dragStartSaturation = picker->saturation; + } else { + return 0; + } + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + picker->PositionOnCircleToColor(message->mouseDown.positionX, message->mouseDown.positionY); + picker->dragStartHue = picker->hue; + picker->dragStartSaturation = picker->saturation; + } else { + return 0; + } + + return ES_HANDLED; + }; + + picker->circlePoint = EsCustomElementCreate(picker->circle, ES_ELEMENT_NO_HOVER, ES_STYLE_COLOR_CHOSEN_POINT); + picker->circlePoint->messageUser = ProcessColorChosenPointMessage; + picker->circlePoint->cName = "selected hue-saturation"; + picker->circlePoint->userData = picker; + + picker->slider = EsCustomElementCreate(picker->panel, ES_ELEMENT_FOCUSABLE | ES_ELEMENT_NOT_TAB_TRAVERSABLE, ES_STYLE_COLOR_SLIDER); + picker->slider->cName = "value slider"; + picker->slider->userData = picker; + + picker->slider->messageUser = [] (EsElement *element, EsMessage *message) { + ColorPicker *picker = (ColorPicker *) element->userData.p; + + if (message->type == ES_MSG_PAINT) { + // EsPerformanceTimerPush(); + + EsPainter *painter = message->painter; + EsRectangle bounds = EsPainterBoundsInset(painter); + EsRectangle clip = painter->clip; + EsRectangleClip(clip, bounds, &clip); + uint32_t stride = painter->target->stride; + uint32_t *bitmap = (uint32_t *) painter->target->bits; + + float valueIncrement = -1.0f / (bounds.b - bounds.t - 1), value = 1.0f; + + for (int j = clip.t; j < clip.b; j++, value += valueIncrement) { + // float valueSqrt = EsCRTsqrtf(value); + // uint32_t color = EsColorConvertToRGB(picker->hue, picker->saturation, valueSqrt * EsCRTsqrtf(valueSqrt)); + + for (int i = clip.l; i < clip.r; i++) { + float i2 = (float) (i - ((bounds.l + bounds.r) >> 1)) / (float) (bounds.r - bounds.l); + uint32_t color = EsColorConvertToRGB(picker->hue, picker->saturation, EsCRTpowf(value, 0.75f + i2 * i2 * 0.3f)); + bitmap[i + j * (stride >> 2)] = 0xFF000000 | color; + } + } + + // EsPrint("Rendered color slider in %*Fms.\n", 3, 1000 * EsPerformanceTimerPop()); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + picker->PositionOnSliderToColor(message->mouseDragged.newPositionX, message->mouseDragged.newPositionY); + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + picker->PositionOnSliderToColor(message->mouseDown.positionX, message->mouseDown.positionY); + } else { + return 0; + } + + return ES_HANDLED; + }; + + picker->sliderPoint = EsCustomElementCreate(picker->slider, ES_ELEMENT_NO_HOVER, ES_STYLE_COLOR_CHOSEN_POINT); + picker->sliderPoint->messageUser = ProcessColorChosenPointMessage; + picker->sliderPoint->cName = "selected value"; + picker->sliderPoint->userData = picker; + + if (hasOpacity) { + picker->opacitySlider = EsCustomElementCreate(picker->panel, ES_ELEMENT_FOCUSABLE | ES_ELEMENT_NOT_TAB_TRAVERSABLE, ES_STYLE_COLOR_SLIDER); + picker->opacitySlider->cName = "opacity slider"; + picker->opacitySlider->userData = picker; + + picker->opacitySlider->messageUser = [] (EsElement *element, EsMessage *message) { + ColorPicker *picker = (ColorPicker *) element->userData.p; + + if (message->type == ES_MSG_PAINT) { + // EsPerformanceTimerPush(); + + EsPainter *painter = message->painter; + EsRectangle bounds = EsPainterBoundsInset(painter); + EsRectangle clip = painter->clip; + EsRectangleClip(clip, bounds, &clip); + uint32_t stride = painter->target->stride; + uint32_t *bitmap = (uint32_t *) painter->target->bits; + + float opacityIncrement = -1.0f / (bounds.b - bounds.t - 1), opacity = 1.0f; + + for (int j = clip.t; j < clip.b; j++, opacity += opacityIncrement) { + uint32_t alpha = (uint32_t) (opacity * 255.0f) << 24; + + for (int i = clip.l; i < clip.r; i++) { + bitmap[i + j * (stride >> 2)] + = EsColorBlend(((((i - bounds.l + 1) >> 3) ^ ((j - bounds.t + 2) >> 3)) & 1) ? 0xFFFFFFFF : 0xFFC0C0C0, + alpha | (picker->color & 0xFFFFFF), false); + } + } + + // EsPrint("Rendered opacity slider in %*Fms.\n", 3, 1000 * EsPerformanceTimerPop()); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + picker->PositionOnOpacitySliderToColor(message->mouseDragged.newPositionX, message->mouseDragged.newPositionY); + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + picker->PositionOnOpacitySliderToColor(message->mouseDown.positionX, message->mouseDown.positionY); + } else { + return 0; + } + + return ES_HANDLED; + }; + + picker->opacitySliderPoint = EsCustomElementCreate(picker->opacitySlider, ES_ELEMENT_NO_HOVER, ES_STYLE_COLOR_CHOSEN_POINT); + picker->opacitySliderPoint->messageUser = ProcessColorChosenPointMessage; + picker->opacitySliderPoint->cName = "selected opacity"; + picker->opacitySliderPoint->userData = picker; + } + + if (showTextbox) { + picker->textbox = EsTextboxCreate(picker->panel, ES_TEXTBOX_EDIT_BASED | ES_CELL_EXPAND | ES_TEXTBOX_NO_SMART_CONTEXT_MENUS, ES_STYLE_COLOR_HEX_TEXTBOX); + picker->textbox->userData = picker; + + picker->textbox->messageUser = [] (EsElement *element, EsMessage *message) { + ColorPicker *picker = (ColorPicker *) element->userData.p; + + if (message->type == ES_MSG_TEXTBOX_UPDATED) { + size_t bytes; + char *string = EsTextboxGetContents(picker->textbox, &bytes); + uint32_t color = EsColorParse(string, bytes); + picker->opacity = (float) (color >> 24) / 255.0f; + color &= 0xFFFFFF; + EsColorConvertToHSV(color, &picker->hue, &picker->saturation, &picker->value); + picker->color = color; + if (picker->host.indeterminate) *picker->host.indeterminate = false; + picker->Sync(picker->textbox); + EsHeapFree(string); + } else if (message->type == ES_MSG_TEXTBOX_EDIT_START) { + EsTextboxSetSelection((EsTextbox *) element, 0, 1, 0, -1); + } else if (message->type == ES_MSG_TEXTBOX_EDIT_END) { + picker->Sync(nullptr); + } else if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA) { + int componentCount = picker->host.hasOpacity ? 4 : 3; + int componentIndex = (message->numberDragDelta.hoverCharacter - 1) / 2; + if (componentIndex < 0) componentIndex = 0; + if (componentIndex >= componentCount) componentIndex = componentCount - 1; + componentIndex = componentCount - componentIndex - 1; + picker->dragComponent = componentIndex; + + if (componentIndex == 3) { + picker->opacity += message->numberDragDelta.delta / 255.0f; + if (picker->opacity < 0) picker->opacity = 0; + if (picker->opacity > 1) picker->opacity = 1; + } else { + int32_t componentValue = (picker->color >> (componentIndex << 3)) & 0xFF; + componentValue += message->numberDragDelta.delta; + if (componentValue < 0) componentValue = 0; + if (componentValue > 255) componentValue = 255; + picker->color &= ~(0xFF << (componentIndex << 3)); + picker->color |= (uint32_t) componentValue << (componentIndex << 3); + EsColorConvertToHSV(picker->color, &picker->hue, &picker->saturation, &picker->value); + } + + picker->Sync(nullptr); + } else { + return 0; + } + + return ES_HANDLED; + }; + + EsTextboxUseNumberOverlay(picker->textbox, false); + + EsButton *eyedropperButton = EsButtonCreate(picker->panel, ES_CELL_EXPAND, 0); + eyedropperButton->userData = picker; + + eyedropperButton->messageUser = [] (EsElement *element, EsMessage *message) { + ColorPicker *picker = (ColorPicker *) element->userData.p; + + if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { + picker->indeterminateBeforeEyedrop = picker->host.indeterminate && *picker->host.indeterminate; + EsSyscall(ES_SYSCALL_EYEDROP_START, (uintptr_t) element, picker->circle->window->handle, picker->color, 0); + } else if (message->type == ES_MSG_EYEDROP_REPORT) { + if (message->eyedrop.cancelled && picker->indeterminateBeforeEyedrop) { + if (picker->host.well && picker->host.well->messageClass == ProcessColorWellMessage) { + EsColorWellSetIndeterminate((EsColorWell *) picker->host.well); + } + } else { + picker->color = message->eyedrop.color; + EsColorConvertToHSV(picker->color, &picker->hue, &picker->saturation, &picker->value); + if (picker->host.indeterminate) *picker->host.indeterminate = false; + picker->Sync(nullptr); + } + } else { + return 0; + } + + return ES_HANDLED; + }; + + EsButtonSetIcon(eyedropperButton, ES_ICON_COLOR_SELECT_SYMBOLIC); + + if (hasOpacity) { + EsElementSetCellRange(eyedropperButton, 1, 1, 2, 1); + } + } + + picker->Sync(picker->host.well); + if (picker->textbox) EsElementFocus(picker->textbox); +} + +uint32_t EsColorWellGetRGB(EsColorWell *well) { + EsMessageMutexCheck(); + + return well->color & ((well->flags & ES_COLOR_WELL_HAS_OPACITY) ? 0xFFFFFFFF : 0x00FFFFFF); +} + +void EsColorWellSetRGB(EsColorWell *well, uint32_t color, bool sendChangedMessage) { + EsMessageMutexCheck(); + + well->color = color; + well->indeterminate = false; + well->Repaint(true); + + if (sendChangedMessage) { + EsMessage m = { ES_MSG_COLOR_CHANGED }; + m.colorChanged.newColor = color; + m.colorChanged.pickerClosed = true; + EsMessageSend(well, &m); + } + + if (well->picker) { + well->picker->color = color & 0xFFFFFF; + well->picker->opacity = (color >> 24) / 255.0f; + EsColorConvertToHSV(well->picker->color, &well->picker->hue, &well->picker->saturation, &well->picker->value); + well->picker->Sync(well); + } +} + +void EsColorWellSetIndeterminate(EsColorWell *well) { + EsMessageMutexCheck(); + + well->color = 0xFFFFFFFF; + well->indeterminate = true; + well->Repaint(true); + + if (well->picker) { + well->picker->color = 0xFFFFFF; + well->picker->opacity = 1.0f; + EsColorConvertToHSV(well->picker->color, &well->picker->hue, &well->picker->saturation, &well->picker->value); + well->picker->Sync(well); + } +} + +int ProcessColorWellMessage(EsElement *element, EsMessage *message) { + EsColorWell *well = (EsColorWell *) element; + + if (message->type == ES_MSG_PAINT) { + EsRectangle bounds = EsPainterBoundsInset(message->painter); + EsStyledBox box = {}; + box.bounds = bounds; + box.clip = message->painter->clip; + box.borders = ES_RECT_1(1); + + if (well->indeterminate) { + box.backgroundColor = 0; + box.borderColor = 0x40000000; + } else { + box.backgroundColor = well->color; + if (~well->flags & ES_COLOR_WELL_HAS_OPACITY) box.backgroundColor |= 0xFF000000; + box.borderColor = EsColorBlend(well->color | 0xFF000000, 0x40000000, false); + + if ((well->flags & ES_COLOR_WELL_HAS_OPACITY) && ((well->color & 0xFF000000) != 0xFF000000)) { + box.fragmentShader = [] (int x, int y, EsStyledBox *box) -> uint32_t { + return EsColorBlend(((((x - box->bounds.l - 4) >> 3) ^ ((y - box->bounds.t + 2) >> 3)) & 1) + ? 0xFFFFFFFF : 0xFFC0C0C0, box->backgroundColor, false); + }; + } + } + + DrawStyledBox(message->painter, box); + } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { + EsMenu *menu = EsMenuCreate(well, ES_FLAGS_DEFAULT); + ColorPickerHost host = { well, &well->indeterminate, (well->flags & ES_COLOR_WELL_HAS_OPACITY) ? true : false }; + ColorPickerCreate((EsElement *) menu, host, well->color, true); + EsMenuShow(menu); + } else { + return 0; + } + + return ES_HANDLED; +} + +EsColorWell *EsColorWellCreate(EsElement *parent, uint64_t flags, uint32_t initialColor) { + EsColorWell *well = (EsColorWell *) EsHeapAllocate(sizeof(EsColorWell), true); + well->color = initialColor; + well->Initialise(parent, flags | ES_ELEMENT_FOCUSABLE, ProcessColorWellMessage, ES_STYLE_PUSH_BUTTON_NORMAL_COLOR_WELL); + well->cName = "color well"; + return well; +} + +// --------------------------------- Splitters. + +// TODO With dockable UI, show split bars at the start and end of the splitter as drop targets. +// The root splitter will also need two split bars at the start and end of the other axis. +// Split bars should also be enlarged when actings as drop targets. +// When dropping on an existing non-splitter panel, you can either form a tab group, +// or create a new split on the other axis, on one of the two sides. + +struct EsSplitter : EsElement { + bool horizontal; + bool addingSplitBar; + int previousSize; + Array resizeStartSizes; + bool calculatedInitialSize; +}; + +struct SplitBar : EsElement { + int position, dragStartPosition; + + void Move(int newPosition, bool fromKeyboard) { + EsSplitter *splitter = (EsSplitter *) parent; + EsElement *panelBefore = nullptr, *panelAfter = nullptr; + int barBefore = 0, barAfter; + if (splitter->horizontal) barAfter = EsRectangleAddBorder(splitter->GetBounds(), splitter->currentStyle->borders).r - currentStyle->preferredWidth; + else barAfter = EsRectangleAddBorder(splitter->GetBounds(), splitter->currentStyle->borders).b - currentStyle->preferredHeight; + int preferredSize = splitter->horizontal ? currentStyle->preferredWidth : currentStyle->preferredHeight; + splitter->resizeStartSizes.Free(); + + for (uintptr_t i = 0; i < splitter->GetChildCount(); i++) { + if (splitter->GetChild(i) == this) { + EsAssert(i & 1); // Expected split bars between each EsSplitter child. + panelBefore = splitter->GetChild(i - 1); + panelAfter = splitter->GetChild(i + 1); + + if (i != 1) { + barBefore = ((SplitBar *) splitter->GetChild(i - 2))->position + preferredSize; + } + + if (i != splitter->GetChildCount() - 2) { + barAfter = ((SplitBar *) splitter->GetChild(i + 2))->position - preferredSize; + } + + break; + } + } + + EsAssert(panelBefore && panelAfter); // Could not find split bar in parent. + + barBefore -= splitter->horizontal ? currentStyle->borders.l : currentStyle->borders.t; + barAfter += splitter->horizontal ? currentStyle->borders.r : currentStyle->borders.b; + + int minimumPosition, maximumPosition, minimumPosition1, maximumPosition1, minimumPosition2, maximumPosition2; + + if (splitter->horizontal) { + minimumPosition1 = barBefore + panelBefore->currentStyle->metrics->minimumWidth; + maximumPosition1 = barAfter - panelAfter ->currentStyle->metrics->minimumWidth; + minimumPosition2 = barAfter - panelAfter ->currentStyle->metrics->maximumWidth; + maximumPosition2 = barBefore + panelBefore->currentStyle->metrics->maximumWidth; + if (!panelAfter ->currentStyle->metrics->maximumWidth) minimumPosition2 = INT_MIN; + if (!panelBefore->currentStyle->metrics->maximumWidth) maximumPosition2 = INT_MAX; + } else { + minimumPosition1 = barBefore + panelBefore->currentStyle->metrics->minimumHeight; + maximumPosition1 = barAfter - panelAfter ->currentStyle->metrics->minimumHeight; + minimumPosition2 = barAfter - panelAfter ->currentStyle->metrics->maximumHeight; + maximumPosition2 = barBefore + panelBefore->currentStyle->metrics->maximumHeight; + if (!panelAfter ->currentStyle->metrics->maximumHeight) minimumPosition2 = INT_MIN; + if (!panelBefore->currentStyle->metrics->maximumHeight) maximumPosition2 = INT_MAX; + } + + minimumPosition = minimumPosition1 > minimumPosition2 ? minimumPosition1 : minimumPosition2; + maximumPosition = maximumPosition1 < maximumPosition2 ? maximumPosition1 : maximumPosition2; + + if (minimumPosition < maximumPosition) { + int oldPosition = position; + + if (newPosition < minimumPosition) { + if (newPosition > minimumPosition2 + && (fromKeyboard || newPosition < (barBefore + minimumPosition1) / 2) + && (!fromKeyboard || newPosition < position) + && panelBefore->flags & ES_CELL_COLLAPSABLE) { + position = barBefore > minimumPosition2 ? barBefore : minimumPosition2; + } else { + position = minimumPosition; + } + } else if (newPosition > maximumPosition) { + if (newPosition < maximumPosition2 + && (fromKeyboard || newPosition > (barAfter + maximumPosition1) / 2) + && (!fromKeyboard || newPosition > position) + && panelAfter->flags & ES_CELL_COLLAPSABLE) { + position = barAfter < maximumPosition2 ? barAfter : maximumPosition2; + } else { + position = maximumPosition; + } + } else { + position = newPosition; + } + + if (oldPosition != position) { + EsElementRelayout(splitter); + } + } + } +}; + +int ProcessSplitBarMessage(EsElement *element, EsMessage *message) { + SplitBar *bar = (SplitBar *) element; + EsSplitter *splitter = (EsSplitter *) bar->parent; + + if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + bar->dragStartPosition = bar->position; + + if (!bar->window->focused || bar->window->focused->messageClass != ProcessSplitBarMessage) { + // Don't take focus. + return ES_REJECTED; + } + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + if (splitter->horizontal) { + bar->Move(message->mouseDragged.newPositionX - message->mouseDragged.originalPositionX + bar->dragStartPosition, false); + } else { + bar->Move(message->mouseDragged.newPositionY - message->mouseDragged.originalPositionY + bar->dragStartPosition, false); + } + } else if (message->type == ES_MSG_KEY_TYPED) { + if (message->keyboard.scancode == (splitter->horizontal ? ES_SCANCODE_LEFT_ARROW : ES_SCANCODE_UP_ARROW)) { + bar->Move(bar->position - GetConstantNumber("splitBarKeyboardMovementAmount"), true); + } else if (message->keyboard.scancode == (splitter->horizontal ? ES_SCANCODE_RIGHT_ARROW : ES_SCANCODE_DOWN_ARROW)) { + bar->Move(bar->position + GetConstantNumber("splitBarKeyboardMovementAmount"), true); + } else { + return 0; + } + } else { + return 0; + } + + return ES_HANDLED; +} + +int ProcessSplitterMessage(EsElement *element, EsMessage *message) { + EsSplitter *splitter = (EsSplitter *) element; + + if (message->type == ES_MSG_LAYOUT && splitter->GetChildCount()) { + EsRectangle client = splitter->GetBounds(); + EsRectangle bounds = EsRectangleAddBorder(client, splitter->currentStyle->insets); + + size_t childCount = splitter->GetChildCount(); + EsAssert(childCount & 1); // Expected split bars between each EsSplitter child. + uint64_t pushFlag = splitter->horizontal ? ES_CELL_H_PUSH : ES_CELL_V_PUSH; + + if (!splitter->calculatedInitialSize) { + for (uintptr_t i = 0; i < childCount; i += 2) { + EsElement *child = splitter->GetChild(i); + + if (~child->flags & pushFlag) { + int width = child->GetWidth(bounds.b - bounds.t); + int height = child->GetHeight(width); + splitter->resizeStartSizes.Add(splitter->horizontal ? width : height); + } else { + splitter->resizeStartSizes.Add(0); + } + } + } + + int64_t newSize = splitter->horizontal ? (bounds.r - bounds.l) : (bounds.b - bounds.t); + + if (newSize != splitter->previousSize && childCount > 1) { + // Step 1: Make a list of current sizes. + + int64_t barSize = splitter->horizontal ? splitter->GetChild(1)->currentStyle->preferredWidth : splitter->GetChild(1)->currentStyle->preferredHeight; + int64_t previousPosition = 0; + + if (!splitter->resizeStartSizes.Length()) { + for (uintptr_t i = 1; i < childCount; i += 2) { + int64_t position = ((SplitBar *) splitter->GetChild(i))->position; + splitter->resizeStartSizes.Add(position - previousPosition); + previousPosition = position + barSize; + } + + splitter->resizeStartSizes.Add(splitter->previousSize - previousPosition); + } + + Array currentSizes = {}; + + for (uintptr_t i = 0; i < splitter->resizeStartSizes.Length(); i++) { + currentSizes.Add(splitter->resizeStartSizes[i]); + } + + // Step 2: Calculate the fixed size, and total weight. + + int64_t fixedSize = 0, totalWeight = 0; + + for (uintptr_t i = 0; i < childCount; i += 2) { + EsElement *child = splitter->GetChild(i); + + if (~child->flags & pushFlag) { + fixedSize += currentSizes[i >> 1]; + } else { + if (currentSizes[i >> 1] < 1) currentSizes[i >> 1] = 1; + totalWeight += currentSizes[i >> 1]; + } + } + + EsAssert(totalWeight); // Splitter must have at least one child with a PUSH flag for its orientation. + + // Step 3: Calculate the new weighted sizes. + + int64_t availableSpace = newSize - fixedSize - barSize * (childCount >> 1); + + if (availableSpace >= 0) { + for (uintptr_t i = 0; i < childCount; i += 2) { + EsElement *child = splitter->GetChild(i); + + if (child->flags & pushFlag) { + currentSizes[i >> 1] = availableSpace * currentSizes[i >> 1] / totalWeight; + } + } + } else { + availableSpace += fixedSize; + if (availableSpace < 0) availableSpace = 0; + + for (uintptr_t i = 0; i < childCount; i += 2) { + EsElement *child = splitter->GetChild(i); + + if (child->flags & pushFlag) { + currentSizes[i >> 1] = 0; + } else { + currentSizes[i >> 1] = availableSpace * currentSizes[i >> 1] / fixedSize; + } + } + } + + // Step 4: Update the positions. + + previousPosition = 0; + + for (uintptr_t i = 1; i < childCount; i += 2) { + SplitBar *bar = (SplitBar *) splitter->GetChild(i); + bar->position = previousPosition + currentSizes[i >> 1]; + previousPosition = bar->position + barSize - (splitter->horizontal ? bar->currentStyle->borders.l : bar->currentStyle->borders.t); + + if (bar->position == 0) { + bar->position -= splitter->horizontal ? bar->currentStyle->borders.l : bar->currentStyle->borders.t; + } else if (bar->position == newSize - barSize) { + bar->position += splitter->horizontal ? bar->currentStyle->borders.r : bar->currentStyle->borders.b; + } + } + + currentSizes.Free(); + } + + splitter->calculatedInitialSize = true; + splitter->previousSize = newSize; + + int position = splitter->horizontal ? bounds.l : bounds.t; + + for (uintptr_t i = 0; i < childCount; i++) { + EsElement *child = splitter->GetChild(i); + + if (i & 1) { + if (splitter->horizontal) { + int size = child->currentStyle->preferredWidth; + EsElementMove(child, position, client.t, size, client.b - client.t); + position += size; + } else { + int size = child->currentStyle->preferredHeight; + EsElementMove(child, client.l, position, client.r - client.l, size); + position += size; + } + } else if (i == childCount - 1) { + if (splitter->horizontal) { + EsElementMove(child, position, bounds.t, bounds.r - position, bounds.b - bounds.t); + } else { + EsElementMove(child, bounds.l, position, bounds.r - bounds.l, bounds.b - position); + } + } else { + SplitBar *bar = (SplitBar *) splitter->GetChild(i + 1); + int size = bar->position - position; + + if (splitter->horizontal) { + EsElementMove(child, position, bounds.t, size, bounds.b - bounds.t); + } else { + EsElementMove(child, bounds.l, position, bounds.r - bounds.l, size); + } + + position += size; + } + } + } else if ((message->type == ES_MSG_GET_WIDTH && splitter->horizontal) + || (message->type == ES_MSG_GET_HEIGHT && !splitter->horizontal)) { + int size = 0; + + for (uintptr_t i = 0; i < splitter->GetChildCount(); i++) { + EsElement *child = splitter->GetChild(i); + size += splitter->horizontal ? child->GetWidth(message->measure.height) : child->GetHeight(message->measure.width); + } + + if (splitter->horizontal) { + message->measure.width = size; + } else { + message->measure.height = size; + } + } else if (message->type == ES_MSG_PRE_ADD_CHILD && !splitter->addingSplitBar && splitter->GetChildCount()) { + splitter->addingSplitBar = true; + SplitBar *bar = (SplitBar *) EsHeapAllocate(sizeof(SplitBar), true); + + bar->Initialise(splitter, ES_ELEMENT_FOCUSABLE | ES_ELEMENT_NOT_TAB_TRAVERSABLE | ES_ELEMENT_CENTER_ACCESS_KEY_HINT | ES_CELL_EXPAND, + ProcessSplitBarMessage, splitter->horizontal ? ES_STYLE_SPLIT_BAR_VERTICAL : ES_STYLE_SPLIT_BAR_HORIZONTAL); + + bar->cName = "split bar"; + bar->accessKey = 'Q'; + + splitter->addingSplitBar = false; + } else if (message->type == ES_MSG_REMOVE_CHILD && ((EsElement *) message->child)->messageClass != ProcessSplitBarMessage) { + for (uintptr_t i = 0; i < splitter->GetChildCount(); i++) { + if (splitter->GetChild(i) == message->child) { + // Remove the corresponding split bar. + + if (i) { + splitter->GetChild(i - 1)->Destroy(); + } else { + splitter->GetChild(i + 1)->Destroy(); + } + + break; + } + } + } else if (message->type == ES_MSG_DESTROY) { + splitter->resizeStartSizes.Free(); + } + + return 0; +} + +EsSplitter *EsSplitterCreate(EsElement *parent, uint64_t flags, const EsStyle *style) { + EsSplitter *splitter = (EsSplitter *) EsHeapAllocate(sizeof(EsSplitter), true); + splitter->horizontal = flags & ES_SPLITTER_HORIZONTAL; + splitter->Initialise(parent, flags | ES_ELEMENT_NO_CLIP, ProcessSplitterMessage, + style ?: ES_STYLE_PANEL_WINDOW_BACKGROUND); + splitter->cName = "splitter"; + return splitter; +} + +// --------------------------------- Image displays. + +// TODO +// asynchronous/synchronous load/decode from file/memory +// unloading image when not visible +// aspect ratio; sizing +// upscale/downscale quality +// subregion, transformations +// transparency, IsRegionCompletelyOpaque, proper blending mode with fragmentShader in DrawStyledBox +// image sets, DPI; SVG scaling +// embedding in TextDisplay +// merge with IconDisplay +// decode in separate process for security? +// clipboard +// zoom/pan + +struct EsImageDisplay : EsElement { + void *source; + size_t sourceBytes; + + uint32_t *bits; + size_t width, height, stride; +}; + +int ProcessImageDisplayMessage(EsElement *element, EsMessage *message) { + EsImageDisplay *display = (EsImageDisplay *) element; + + if (message->type == ES_MSG_PAINT && (display->bits || display->source)) { + if (!display->bits && display->source) { + uint32_t width, height; + uint8_t *bits = EsImageLoad((uint8_t *) display->source, display->sourceBytes, &width, &height, 4); + + if (bits) { + display->bits = (uint32_t *) bits; + display->width = width; + display->height = height; + display->stride = width * 4; + } + + if (~display->flags & UI_STATE_CHECK_VISIBLE) { + display->state |= UI_STATE_CHECK_VISIBLE; + display->window->checkVisible.Add(display); + } + } + + EsPaintTarget source = {}; + source.bits = display->bits; + source.width = display->width; + source.height = display->height; + source.stride = display->stride; + EsDrawPaintTarget(message->painter, &source, + EsPainterBoundsInset(message->painter), + ES_RECT_4(0, display->width, 0, display->height), 0xFF); + } else if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = display->width; + } else if (message->type == ES_MSG_GET_HEIGHT) { + message->measure.height = display->height; + } else if (message->type == ES_MSG_DESTROY) { + EsHeapFree(display->bits); + EsHeapFree(display->source); + } else if (message->type == ES_MSG_NOT_VISIBLE) { + EsHeapFree(display->bits); + display->bits = nullptr; + } + + return 0; +} + +EsImageDisplay *EsImageDisplayCreate(EsElement *parent, uint64_t flags, const EsStyle *style) { + EsImageDisplay *display = (EsImageDisplay *) EsHeapAllocate(sizeof(EsImageDisplay), true); + display->Initialise(parent, flags, ProcessImageDisplayMessage, style); + display->cName = "image"; + return display; +} + +void EsImageDisplayLoadBits(EsImageDisplay *display, const uint32_t *bits, size_t width, size_t height, size_t stride) { + EsHeapFree(display->bits); + display->bits = (uint32_t *) EsHeapAllocate(stride * height, false); + display->width = width; + display->height = height; + display->stride = stride; + EsMemoryCopy(display->bits, bits, stride * height); +} + +void EsImageDisplayLoadFromMemory(EsImageDisplay *display, const void *buffer, size_t bufferBytes) { + if (display->flags & ES_IMAGE_DISPLAY_DECODE_WHEN_NEEDED) { + EsHeapFree(display->source); + EsHeapFree(display->bits); + display->bits = nullptr; + display->source = EsHeapAllocate(bufferBytes, false); + if (!display->source) return; + EsMemoryCopy(display->source, buffer, bufferBytes); + display->sourceBytes = bufferBytes; + + if (~display->flags & ES_IMAGE_DISPLAY_MANUAL_SIZE) { + // TODO Make a version of EsImageLoad that doesn't load the full image, but only gets the size. + uint32_t width, height; + uint8_t *bits = EsImageLoad((uint8_t *) buffer, bufferBytes, &width, &height, 4); + EsHeapFree(bits); + display->width = width; + display->height = height; + } + } else { + uint32_t width, height; + uint8_t *bits = EsImageLoad((uint8_t *) buffer, bufferBytes, &width, &height, 4); + if (!bits) return; + EsHeapFree(display->bits); + display->bits = (uint32_t *) bits; + display->width = width; + display->height = height; + display->stride = width * 4; + } +} + +struct EsIconDisplay : EsElement { + uint32_t iconID; +}; + +int ProcessIconDisplayMessage(EsElement *element, EsMessage *message) { + EsIconDisplay *display = (EsIconDisplay *) element; + + if (message->type == ES_MSG_PAINT) { + EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height), "", 0, display->iconID); + } + + return 0; +} + +EsIconDisplay *EsIconDisplayCreate(EsElement *parent, uint64_t flags, const EsStyle *style, uint32_t iconID) { + EsIconDisplay *display = (EsIconDisplay *) EsHeapAllocate(sizeof(EsIconDisplay), true); + display->Initialise(parent, flags, ProcessIconDisplayMessage, style ?: ES_STYLE_ICON_DISPLAY); + display->cName = "icon"; + display->iconID = iconID; + return display; +} + +// --------------------------------- Message loop and core UI infrastructure. + +void EsElement::PrintTree(int depth) { + const char *tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + EsPrint("%s%z c:%d, %z\n", depth, tabs, cName, children.Length(), state & UI_STATE_DESTROYING ? "DESTROYING" : ""); + + for (uintptr_t i = 0; i < children.Length(); i++) { + children[i]->PrintTree(depth + 1); + } +} + +void EsElement::Destroy(bool manual) { + if (state & UI_STATE_DESTROYING) { + return; + } + + if (manual) { +#ifndef DISABLE_ALL_ANIMATIONS + if (currentStyle->metrics->exitDuration) { + if (previousTransitionFrame) { + EsPaintTargetDestroy(previousTransitionFrame); + } + + previousTransitionFrame = EsPaintTargetCreate(width, height, true); + + if (previousTransitionFrame) { + // TODO Doesn't support shadows. + EsPainter painter = {}; + painter.clip = ES_RECT_4(0, width, 0, height); + painter.target = previousTransitionFrame; + InternalPaint(&painter, PAINT_NO_TRANSITION | PAINT_NO_OFFSET); + state |= UI_STATE_EXITING; + RefreshStyle(); + } + } +#endif + + EsElement *ancestor = parent; + + while (ancestor && (~ancestor->state & UI_STATE_DESTROYING_CHILD)) { + ancestor->state |= UI_STATE_DESTROYING_CHILD; + ancestor = ancestor->parent; + } + } + + state |= UI_STATE_DESTROYING | UI_STATE_DESTROYING_CHILD | UI_STATE_BLOCK_INTERACTION; + + if (parent) { + EsMessage m = { ES_MSG_REMOVE_CHILD }; + m.child = this; + EsMessageSend(parent, &m); + } + + if (window->hovered == this) { + window->hovered = window; + window->state |= UI_STATE_HOVERED; + EsMessage m = {}; + m.type = ES_MSG_HOVERED_START; + EsMessageSend(window, &m); + } + + EsMessage m = { ES_MSG_DESTROY }; + if (messageUser) messageUser(this, &m); + messageUser = nullptr; + if (messageClass) messageClass(this, &m); + messageClass = nullptr; + + if (window->inactiveFocus == this) window->inactiveFocus = nullptr; + if (window->pressed == this) window->pressed = nullptr; + if (window->focused == this) window->focused = nullptr; + if (window->defaultEnterButton == this) window->defaultEnterButton = nullptr; + if (window->enterButton == this) window->enterButton = window->defaultEnterButton; + if (window->escapeButton == this) window->escapeButton = nullptr; + if (window->ensureVisible == this) window->ensureVisible = nullptr; + if (window->dragged == this) window->dragged = nullptr; + if (gui.clickChainElement == this) gui.clickChainElement = nullptr; + + if (parent) EsElementUpdateContentSize(parent); + + UIWindowNeedsUpdate(window); +} + +bool EsElement::InternalDestroy() { + if (~state & UI_STATE_DESTROYING_CHILD) { + return false; + } + + for (uintptr_t i = 0; i < children.Length(); i++) { + if (state & UI_STATE_DESTROYING) { + children[i]->Destroy(false); + } + + if (children[i]->InternalDestroy() && (~state & UI_STATE_DESTROYING)) { + children.Delete(i); + i--; + } + } + + state &= ~UI_STATE_DESTROYING_CHILD; + + if (~state & UI_STATE_DESTROYING) { + return false; + } + + children.Free(); + + if (state & UI_STATE_EXITING) { + return false; + } + + InspectorNotifyElementDestroyed(this); + + if (state & UI_STATE_ANIMATING) { + for (uintptr_t i = 0; i < gui.animatingElements.Length(); i++) { + if (gui.animatingElements[i] == this) { + gui.animatingElements.DeleteSwap(i); + break; + } + } + + state &= ~UI_STATE_ANIMATING; + } + + if (state & UI_STATE_CHECK_VISIBLE) { + window->checkVisible.FindAndDeleteSwap(this, true); + } + + if (currentStyle) currentStyle->CloseReference(); + if (previousTransitionFrame) EsPaintTargetDestroy(previousTransitionFrame); + ThemeAnimationDestroy(&animation); + if (window == this) UIWindowDestroy(window); // Windows are deallocated after receiving ES_MSG_WINDOW_DESTROYED. + else EsHeapFree(this); + + return true; +} + +EsElement *EsWindowGetToolbar(EsWindow *window, bool createNew) { + if (createNew || !window->toolbar) { + bool first = !window->toolbar; + window->toolbar = EsPanelCreate(window->toolbarSwitcher, ES_PANEL_HORIZONTAL | ES_CELL_FILL, ES_STYLE_PANEL_TOOLBAR); + window->toolbar->cName = "toolbar"; + EsAssert(window->toolbar->messageClass == ProcessPanelMessage); + + window->toolbar->messageClass = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_GET_CHILD_STYLE_VARIANT) { + if (message->childStyleVariant == ES_STYLE_TEXT_LABEL) { + message->childStyleVariant = ES_STYLE_TEXT_TOOLBAR; + return ES_HANDLED; + } else if (message->childStyleVariant == ES_STYLE_PUSH_BUTTON_NORMAL + || message->childStyleVariant == ES_STYLE_CHECKBOX_NORMAL + || message->childStyleVariant == ES_STYLE_CHECKBOX_RADIOBOX) { + message->childStyleVariant = ES_STYLE_PUSH_BUTTON_TOOLBAR; + return ES_HANDLED; + } + } + + return ProcessPanelMessage(element, message); + }; + + if (first) EsPanelSwitchTo(window->toolbarSwitcher, window->toolbar, ES_TRANSITION_NONE); + } + + return window->toolbar; +} + +void EsWindowSwitchToolbar(EsWindow *window, EsElement *toolbar, EsTransitionType transitionType) { + EsPanelSwitchTo(window->toolbarSwitcher, toolbar, transitionType); +} + +EsRectangle EsWindowGetBounds(EsWindow *window) { + EsRectangle bounds; + EsSyscall(ES_SYSCALL_WINDOW_GET_BOUNDS, window->handle, (uintptr_t) &bounds, 0, 0); + return bounds; +} + +EsRectangle EsElementGetInsetSize(EsElement *element) { + EsMessageMutexCheck(); + + EsRectangle insets = element->currentStyle->insets; + return ES_RECT_4(0, element->width - insets.l - insets.r, + 0, element->height - insets.t - insets.b); +} + +void EsWindowSetIcon(EsWindow *window, uint32_t iconID) { + EsMessageMutexCheck(); + + char buffer[5]; + buffer[0] = DESKTOP_MSG_SET_ICON; + EsMemoryCopy(buffer + 1, &iconID, sizeof(uint32_t)); + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, sizeof(buffer), window->handle, 0); +} + +void EsWindowSetTitle(EsWindow *window, const char *title, ptrdiff_t titleBytes) { + EsMessageMutexCheck(); + + APIInstance *instance = window->instance ? ((APIInstance *) window->instance->_private) : nullptr; + const char *applicationName = instance ? instance->applicationName : nullptr; + size_t applicationNameBytes = instance ? instance->applicationNameBytes : 0; + + if (!applicationNameBytes && !titleBytes) { + return; + } + + if (titleBytes == -1) { + titleBytes = EsCStringLength(title); + } + + if (titleBytes) { + applicationNameBytes = 0; + } + + char buffer[4096]; + size_t bytes = EsStringFormat(buffer, 4096, "%c%s%s", DESKTOP_MSG_SET_TITLE, titleBytes, title, applicationNameBytes, applicationName); + EsSyscall(ES_SYSCALL_MESSAGE_DESKTOP, (uintptr_t) buffer, bytes, window->handle, 0); +} + +void EsMouseSetPosition(EsWindow *relativeWindow, int x, int y) { + if (relativeWindow) { + EsRectangle bounds = EsWindowGetBounds(relativeWindow); + x += bounds.l; + y += bounds.t; + } + + EsSyscall(ES_SYSCALL_CURSOR_POSITION_SET, x, y, 0, 0); +} + +EsPoint EsMouseGetPosition(EsElement *relativeElement) { + if (relativeElement) { + EsPoint position = relativeElement->window->mousePosition; + EsRectangle bounds = relativeElement->GetWindowBounds(); + position.x -= bounds.l; + position.y -= bounds.t; + return position; + } else { + EsPoint position; + EsSyscall(ES_SYSCALL_CURSOR_POSITION_GET, (uintptr_t) &position, 0, 0, 0); + return position; + } +} + +const EsStyle *UIGetDefaultStyleVariant(const EsStyle *style, EsElement *parent) { + EsMessage m = { .type = ES_MSG_GET_CHILD_STYLE_VARIANT, .childStyleVariant = style }; + EsElement *ancestor = parent; + + while (ancestor) { + if (ES_HANDLED == EsMessageSend(ancestor, &m)) { + break; + } + + ancestor = ancestor->parent; + } + + return m.childStyleVariant; +} + +EsElement *UIFindHoverElementRecursively2(EsElement *element, int offsetX, int offsetY, EsPoint position, uintptr_t i) { + EsElement *child = element->GetChildByZ(i - 1); + + if (!child) return nullptr; + if (child->flags & ES_ELEMENT_HIDDEN) return nullptr; + if (child->state & UI_STATE_DESTROYING) return nullptr; + if (child->state & UI_STATE_BLOCK_INTERACTION) return nullptr; + + if (!EsRectangleContains(ES_RECT_4(offsetX + child->offsetX, offsetX + child->offsetX + child->width, + offsetY + child->offsetY, offsetY + child->offsetY + child->height), + position.x, position.y)) { + return nullptr; + } + + EsMessage m = { ES_MSG_HIT_TEST }; + m.hitTest.x = position.x - offsetX - child->offsetX - child->internalOffsetLeft; + m.hitTest.y = position.y - offsetY - child->offsetY - child->internalOffsetTop; + m.hitTest.inside = true; + int response = EsMessageSend(child, &m); + + if ((response != ES_HANDLED || m.hitTest.inside) && response != ES_REJECTED) { + return UIFindHoverElementRecursively(child, offsetX, offsetY, position); + } + + return nullptr; +} + +EsElement *UIFindHoverElementRecursively(EsElement *element, int offsetX, int offsetY, EsPoint position) { + offsetX += element->offsetX; + offsetY += element->offsetY; + + EsMessage zOrder = { ES_MSG_BEFORE_Z_ORDER }; + zOrder.beforeZOrder.nonClient = zOrder.beforeZOrder.end = element->children.Length(); + zOrder.beforeZOrder.clip = Translate(ES_RECT_4(0, element->width, 0, element->height), offsetX, offsetY); + EsMessageSend(element, &zOrder); + + EsElement *result = nullptr; + + if (~element->flags & ES_ELEMENT_NO_HOVER_DESCENDENTS) { + for (uintptr_t i = element->children.Length(); !result && i > zOrder.beforeZOrder.nonClient; i--) { + result = UIFindHoverElementRecursively2(element, offsetX, offsetY, position, i); + } + + for (uintptr_t i = zOrder.beforeZOrder.end; !result && i > zOrder.beforeZOrder.start; i--) { + result = UIFindHoverElementRecursively2(element, offsetX, offsetY, position, i); + } + } + + zOrder.type = ES_MSG_AFTER_Z_ORDER; + EsMessageSend(element, &zOrder); + + return result ? result : (element->flags & ES_ELEMENT_NO_HOVER) ? nullptr : element; +} + +void UIFindHoverElement(EsWindow *window) { + // TS("Finding element under cursor\n"); + + EsPoint position = EsMouseGetPosition(window); + + EsElement *element; + + if (position.x < 0 || position.y < 0 || position.x >= window->width || position.y >= window->height || !window->hovering) { + element = window; + } else { + element = UIFindHoverElementRecursively(window, 0, 0, position); + } + + if (element->state & UI_STATE_HOVERED) { + EsAssert(window->hovered == element); // Window's hovered element mismatched element state flags. + } else { + EsMessage m = {}; + m.type = ES_MSG_HOVERED_END; + window->hovered->state &= ~UI_STATE_HOVERED; + EsMessageSend(window->hovered, &m); + element->state |= UI_STATE_HOVERED; + m.type = ES_MSG_HOVERED_START; + EsMessageSend((window->hovered = element), &m); + } +} + +void UIRemoveFocusFromElement(EsElement *oldFocus) { + if (!oldFocus) return; + EsMessage m = {}; + + if (~oldFocus->state & UI_STATE_LOST_STRONG_FOCUS) { + m.type = ES_MSG_STRONG_FOCUS_END; + oldFocus->state |= UI_STATE_LOST_STRONG_FOCUS; + EsMessageSend(oldFocus, &m); + } + + m.type = ES_MSG_FOCUSED_END; + oldFocus->state &= ~(UI_STATE_FOCUSED | UI_STATE_LOST_STRONG_FOCUS); + EsMessageSend(oldFocus, &m); +} + +void UIMaybeRemoveFocusedElement(EsWindow *window) { + if (window->focused && !window->focused->IsFocusable()) { + EsElement *oldFocus = window->focused; + window->focused = nullptr; + UIRemoveFocusFromElement(oldFocus); + } +} + +void EsElementFocus(EsElement *element, uint32_t flags) { + EsMessageMutexCheck(); + + EsWindow *window = element->window; + EsMessage m; + + // If this element is not focusable or if the window doesn't allow focused elements, ignore the request. + + if (!element->IsFocusable() || (window->windowStyle == ES_WINDOW_CONTAINER)) return; + + // If the element is already focused, then don't resend it any messages. + + if (window->focused == element) return; + + // Tell the previously focused element it's no longer focused. + + EsElement *oldFocus = window->focused; + window->focused = element; + UIRemoveFocusFromElement(oldFocus); + + // Tell any parents of the previously focused element that aren't parents of the newly focused element that they no longer has focus-within, + // and the parents of the newly focused element that they have focus-within. + + EsElement *parent = element->parent; + + while (parent) { + parent->state |= UI_STATE_TEMP; + parent = parent->parent; + } + + if (oldFocus) { + parent = oldFocus->parent; + + while (parent) { + if (~parent->state & UI_STATE_TEMP) { + parent->state &= ~UI_STATE_FOCUS_WITHIN; + m.type = ES_MSG_FOCUS_WITHIN_END; + EsMessageSend(parent, &m); + } + + parent = parent->parent; + } + } + + parent = element->parent; + window->focused = element; + + while (parent) { + if (~parent->state & UI_STATE_FOCUS_WITHIN) { + parent->state |= UI_STATE_FOCUS_WITHIN; + m.type = ES_MSG_FOCUS_WITHIN_START; + EsMessageSend(parent, &m); + + EsAssert(window->focused == element); // Cannot change window focus from FOCUS_WITHIN_START message. + } + + parent->state &= ~UI_STATE_TEMP; + parent = parent->parent; + } + + // Tell the newly focused element it's focused. + + m.type = ES_MSG_FOCUSED_START; + m.focus.flags = flags; + window->focused->state |= UI_STATE_FOCUSED; + EsMessageSend(element, &m); + EsAssert(window->focused == element); // Cannot change window focus from FOCUSED_START message. + + // Ensure the element is visible. + + if ((flags & ES_ELEMENT_FOCUS_ENSURE_VISIBLE) && element) { + window->ensureVisible = element; + } +} + +EsRectangle EsElementGetWindowBounds(EsElement *element, bool client) { + EsElement *e = element; + int x = 0, y = 0; + + while (element) { + x += element->offsetX, y += element->offsetY; + element = element->parent; + } + + int cw = e->width - (client ? (e->internalOffsetLeft + e->internalOffsetRight) : 0); + int ch = e->height - (client ? (e->internalOffsetTop + e->internalOffsetBottom) : 0); + + return ES_RECT_4(x, x + cw, y, y + ch); +} + +EsRectangle EsElementGetScreenBounds(EsElement *element, bool client) { + EsRectangle elementBoundsInWindow = EsElementGetWindowBounds(element, client); + EsRectangle windowBoundsInScreen = EsWindowGetBounds(element->window); + return Translate(elementBoundsInWindow, windowBoundsInScreen.l, windowBoundsInScreen.t); +} + +void EsElementInsertAfter(EsElement *element) { + EsAssert(!gui.insertAfter); + gui.insertAfter = element; +} + +void EsElement::Initialise(EsElement *_parent, uint64_t _flags, EsUICallbackFunction _classCallback, const EsStyle *style) { + EsMessageMutexCheck(); + + // EsPrint("New element '%z' %x with parent %x.\n", _debugName, this, _parent); + + messageClass = _classCallback; + flags = _flags; + + if (gui.insertAfter) { + if (_parent) { + EsAssert(_parent == gui.insertAfter || _parent == gui.insertAfter->parent); + } else { + _parent = gui.insertAfter->parent; + } + } + + if (_parent) { + if (!_parent->parent && (~_flags & ES_ELEMENT_NON_CLIENT)) { + _parent = WindowGetMainPanel((EsWindow *) _parent); + } + + parent = _parent; + window = parent->window; + instance = window->instance; + + if (parent->flags & ES_ELEMENT_DISABLED) flags |= ES_ELEMENT_DISABLED; + + if (~flags & ES_ELEMENT_NON_CLIENT) { + EsMessage m = {}; + m.type = ES_MSG_PRE_ADD_CHILD; + m.child = this; + EsMessageSend(parent, &m); + } + + if (gui.insertAfter == _parent) { + parent->children.Insert(this, 0); + gui.insertAfter = nullptr; + } else if (gui.insertAfter) { + uintptr_t i = parent->children.Find(gui.insertAfter, true); + parent->children.Insert(this, i + 1); + gui.insertAfter = nullptr; + } else if (flags & ES_ELEMENT_NON_CLIENT) { + parent->children.Add(this); + } else { + for (uintptr_t i = parent->children.Length();; i--) { + if (i == 0 || (~parent->children[i - 1]->flags & ES_ELEMENT_NON_CLIENT)) { + parent->children.Insert(this, i); + break; + } + } + } + + if (~flags & ES_ELEMENT_NON_CLIENT) { + EsMessage m = {}; + m.type = ES_MSG_ADD_CHILD; + m.child = this; + EsMessageSend(parent, &m); + } + + EsElementUpdateContentSize(parent); + } + + SetStyle(style, false); + RefreshStyle(); + InspectorNotifyElementCreated(this); +} + +EsRectangle EsElementGetInsets(EsElement *element) { + EsMessageMutexCheck(); + return element->currentStyle->insets; +} + +EsThemeMetrics EsElementGetMetrics(EsElement *element) { + EsMessageMutexCheck(); + EsThemeMetrics m = {}; + ThemeMetrics *metrics = element->currentStyle->metrics; +#define RECTANGLE_8_TO_ES_RECTANGLE(x) { (int32_t) (x).l, (int32_t) (x).r, (int32_t) (x).t, (int32_t) (x).b } + m.insets = RECTANGLE_8_TO_ES_RECTANGLE(metrics->insets); + m.clipInsets = RECTANGLE_8_TO_ES_RECTANGLE(metrics->clipInsets); + m.globalOffset = RECTANGLE_8_TO_ES_RECTANGLE(metrics->globalOffset); + m.clipEnabled = metrics->clipEnabled; + m.cursor = metrics->cursor; + m.entranceTransition = metrics->entranceTransition; + m.exitTransition = metrics->exitTransition; + m.entranceDuration = metrics->entranceDuration; + m.exitDuration = metrics->exitDuration; + m.preferredWidth = metrics->preferredWidth; + m.preferredHeight = metrics->preferredHeight; + m.minimumWidth = metrics->minimumWidth; + m.minimumHeight = metrics->minimumHeight; + m.maximumWidth = metrics->maximumWidth; + m.maximumHeight = metrics->maximumHeight; + m.gapMajor = metrics->gapMajor; + m.gapMinor = metrics->gapMinor; + m.gapWrap = metrics->gapWrap; + m.textColor = metrics->textColor; + m.selectedBackground = metrics->selectedBackground; + m.selectedText = metrics->selectedText; + m.iconColor = metrics->iconColor; + m.textAlign = metrics->textAlign; + m.textSize = metrics->textSize; + m.fontFamily = metrics->fontFamily; + m.fontWeight = metrics->fontWeight; + m.iconSize = metrics->iconSize; + m.isItalic = metrics->isItalic; + m.ellipsis = metrics->ellipsis; + return m; +} + +void EsElementSetCallback(EsElement *element, EsUICallbackFunction callback) { + EsMessageMutexCheck(); + element->messageUser = callback; +} + +void EsElementSetHidden(EsElement *element, bool hidden) { + EsMessageMutexCheck(); + + bool old = element->flags & ES_ELEMENT_HIDDEN; + if (old == hidden) return; + + if (hidden) { + element->flags |= ES_ELEMENT_HIDDEN; + } else { + element->flags &= ~ES_ELEMENT_HIDDEN; + } + + EsElementUpdateContentSize(element->parent); + UIMaybeRemoveFocusedElement(element->window); +} + +void EsElementSetDisabled(EsElement *element, bool disabled) { + EsMessageMutexCheck(); + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + if (element->GetChild(i)->flags & ES_ELEMENT_NON_CLIENT) continue; + EsElementSetDisabled(element->GetChild(i), disabled); + } + + if ((element->flags & ES_ELEMENT_DISABLED) && disabled) return; + if ((~element->flags & ES_ELEMENT_DISABLED) && !disabled) return; + + if (disabled) element->flags |= ES_ELEMENT_DISABLED; + else element->flags &= ~ES_ELEMENT_DISABLED; + + element->MaybeRefreshStyle(); + + if (element->window->focused == element) { + element->window->focused = nullptr; + } +} + +void EsElementDestroy(EsElement *element) { + EsMessageMutexCheck(); + element->Destroy(); +} + +void EsElementDestroyContents(EsElement *element) { + EsMessageMutexCheck(); + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + if (element->GetChild(i)->flags & ES_ELEMENT_NON_CLIENT) continue; + element->GetChild(i)->Destroy(); + } + + EsMessage m = {}; + m.type = ES_MSG_DESTROY_CONTENTS; + EsMessageSend(element, &m); +} + +EsElement *UITabTraversalGetChild(EsElement *element, uintptr_t index) { + if (element->flags & ES_ELEMENT_LAYOUT_HINT_REVERSE) { + return element->children[element->children.Length() - index - 1]; + } else { + return element->children[index]; + } +} + +EsElement *UITabTraversalDo(EsElement *element, bool shift) { + if (shift) { + if (element->parent && UITabTraversalGetChild(element->parent, 0) != element) { + for (uintptr_t i = 1; i < element->parent->children.Length(); i++) { + if (UITabTraversalGetChild(element->parent, i) == element) { + element = UITabTraversalGetChild(element->parent, i - 1); + + while (element->children.Length()) { + element = UITabTraversalGetChild(element, element->children.Length() - 1); + } + + return element; + } + } + } else if (element->parent) { + return element->parent; + } else { + while (element->children.Length()) { + element = UITabTraversalGetChild(element, element->children.Length() - 1); + } + + return element; + } + } else { + if (element->children.Length()) { + return UITabTraversalGetChild(element, 0); + } else while (element->parent) { + EsElement *child = element; + element = element->parent; + + for (uintptr_t i = 0; i < element->children.Length() - 1u; i++) { + if (UITabTraversalGetChild(element, i) == child) { + element = UITabTraversalGetChild(element, i + 1); + return element; + } + } + } + } + + return element; +} + +uint8_t EsKeyboardGetModifiers() { + return gui.leftModifiers | gui.rightModifiers; +} + +bool EsMouseIsLeftHeld() { return gui.mouseButtonDown && gui.lastClickButton == ES_MSG_MOUSE_LEFT_DOWN; } +bool EsMouseIsRightHeld() { return gui.mouseButtonDown && gui.lastClickButton == ES_MSG_MOUSE_RIGHT_DOWN; } +bool EsMouseIsMiddleHeld() { return gui.mouseButtonDown && gui.lastClickButton == ES_MSG_MOUSE_MIDDLE_DOWN; } + +void EsStyleRefreshAll(EsElement *element) { + EsMessageMutexCheck(); + + element->RefreshStyle(nullptr, false, true); + + for (uintptr_t i = 0; i < element->children.Length(); i++) { + if (element->children[i]) { + EsStyleRefreshAll(element->children[i]); + } + } +} + +void EsUISetDPI(int dpiScale) { + EsMessageMutexCheck(); + + if (dpiScale < 50) { + dpiScale = 50; + } + + theming.scale = dpiScale / 100.0f; + + for (uintptr_t i = 0; i < gui.allWindows.Length(); i++) { + EsStyleRefreshAll(gui.allWindows[i]); + } +} + +void EsElementGetSize(EsElement *element, int *width, int *height) { + EsMessageMutexCheck(); + + EsRectangle bounds = element->GetBounds(); + *width = bounds.r; + *height = bounds.b; +} + +void EsElementGetTextStyle(EsElement *element, EsTextStyle *style) { + element->currentStyle->GetTextStyle(style); +} + +void EsElementRepaint(EsElement *element, const EsRectangle *region) { + EsMessageMutexCheck(); + if (region) element->Repaint(false, *region); + else element->Repaint(true /* repaint all */); +} + +void EsElementRepaintForScroll(EsElement *element, EsMessage *message) { + // TODO Support custom borders sizes. + + EsRectangle borders = ES_RECT_4(element->internalOffsetLeft, element->internalOffsetRight, + element->internalOffsetTop, element->internalOffsetBottom); + EsRectangle content = ES_RECT_4(borders.l, element->width - borders.r, borders.t, element->height - borders.b); + EsRectangle repaint = content; + int64_t delta = message->scrollbarMoved.scroll - message->scrollbarMoved.previous; + + if (message->type == ES_MSG_SCROLL_Y) { + if (delta > 0) repaint.t = element->height - delta - borders.b; + else repaint.b = borders.t - delta; + } else if (message->type == ES_MSG_SCROLL_X) { + if (delta > 0) repaint.l = element->width - delta - borders.r; + else repaint.r = borders.l - delta; + } else { + EsAssert(false); + } + + EsRectangle rectangle = element->GetWindowBounds(false); + EsRectangle scrollBits = Translate(content, rectangle.l, rectangle.t); + EsSyscall(ES_SYSCALL_WINDOW_SET_BITS, element->window->handle, (uintptr_t) &scrollBits, delta, + message->type == ES_MSG_SCROLL_Y ? WINDOW_SET_BITS_SCROLL_VERTICAL : WINDOW_SET_BITS_SCROLL_HORIZONTAL); + + repaint = EsRectangleIntersection(repaint, content); + EsElementRepaint(element, &repaint); + UIWindowPaintNow(element->window, nullptr, false); +} + +bool EsElementStartAnimating(EsElement *element) { + EsMessageMutexCheck(); + + return element->StartAnimating(); +} + +EsElement *EsElementGetLayoutParent(EsElement *element) { + EsMessageMutexCheck(); + + return element->parent; +} + +void EsElementDraw(EsElement *element, EsPainter *painter) { + element->InternalPaint(painter, PAINT_SHADOW); + element->InternalPaint(painter, ES_FLAGS_DEFAULT); + element->InternalPaint(painter, PAINT_OVERLAY); +} + +int UIMessageSendPropagateToAncestors(EsElement *element, EsMessage *message, EsElement **handler = nullptr) { + while (element) { + int response = EsMessageSend(element, message); + + if (response) { + if (handler) *handler = element; + return response; + } + + element = element->parent; + } + + if (handler) *handler = nullptr; + return 0; +} + +void UIMousePressReleased(EsWindow *window, EsMessage *message, bool sendClick) { + if (window->pressed) { + EsElement *pressed = window->pressed; + window->pressed = nullptr; + + if (message) { + EsRectangle bounds = pressed->GetWindowBounds(); + message->mouseDown.positionX -= bounds.l; + message->mouseDown.positionY -= bounds.t; + UIMessageSendPropagateToAncestors(pressed, message); + } else { + EsMessage m = {}; + m.type = (EsMessageType) (gui.lastClickButton + 1); + UIMessageSendPropagateToAncestors(pressed, &m); + } + + pressed->state &= ~UI_STATE_PRESSED; + EsMessage m = { ES_MSG_PRESSED_END }; + EsMessageSend(pressed, &m); + + if (message && (pressed->state & UI_STATE_HOVERED) && !gui.draggingStarted && sendClick) { + if (message->type == ES_MSG_MOUSE_LEFT_UP) { + m.type = ES_MSG_MOUSE_LEFT_CLICK; + EsMessageSend(pressed, &m); + } else if (message->type == ES_MSG_MOUSE_RIGHT_UP) { + m.type = ES_MSG_MOUSE_RIGHT_CLICK; + EsMessageSend(pressed, &m); + } else if (message->type == ES_MSG_MOUSE_MIDDLE_UP) { + m.type = ES_MSG_MOUSE_MIDDLE_CLICK; + EsMessageSend(pressed, &m); + } + } + + if (window->hovered) window->hovered->MaybeRefreshStyle(); + } + + gui.draggingStarted = false; +} + +void UIInitialiseKeyboardShortcutNamesTable() { +#define ADD_KEYBOARD_SHORTCUT_NAME(a, b) HashTablePutShort(&gui.keyboardShortcutNames, a, (void *) b) + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_A, "A"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_B, "B"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_C, "C"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_D, "D"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_E, "E"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F, "F"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_G, "G"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_H, "H"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_I, "I"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_J, "J"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_K, "K"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_L, "L"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_M, "M"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_N, "N"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_O, "O"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_P, "P"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_Q, "Q"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_R, "R"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_S, "S"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_T, "T"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_U, "U"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_V, "V"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_W, "W"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_X, "X"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_Y, "Y"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_Z, "Z"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_0, "0"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_1, "1"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_2, "2"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_3, "3"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_4, "4"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_5, "5"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_6, "6"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_7, "7"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_8, "8"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_9, "9"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_BACKSPACE, "Backspace"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_ESCAPE, "Esc"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_INSERT, "Ins"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_HOME, "Home"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_PAGE_UP, "PgUp"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_DELETE, "Del"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_END, "End"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_PAGE_DOWN, "PgDn"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_UP_ARROW, "Up"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_LEFT_ARROW, "Left"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_DOWN_ARROW, "Down"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_RIGHT_ARROW, "Right"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_SPACE, "Space"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_TAB, "Tab"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_ENTER, "Enter"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F1, "F1"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F2, "F2"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F3, "F3"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F4, "F4"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F5, "F5"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F6, "F6"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F7, "F7"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F8, "F8"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F9, "F9"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F10, "F10"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F11, "F11"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_F12, "F12"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_NEXT, "Next Track"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_PREVIOUS, "Previous Track"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_STOP, "Stop Media"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_PAUSE, "Play/Pause"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_MUTE, "Mute"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_QUIETER, "Quieter"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_LOUDER, "Louder"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_MM_SELECT, "Open Media"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_WWW_SEARCH, "Search"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_WWW_HOME, "Homepage"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_WWW_BACK, "Back"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_WWW_FORWARD, "Forward"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_WWW_STOP, "Stop"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_WWW_REFRESH, "Refresh"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_WWW_STARRED, "Bookmark"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_SLASH, "/"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_PUNCTUATION_1, "\\"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_LEFT_BRACE, "("); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_RIGHT_BRACE, ")"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_EQUALS, "="); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_PUNCTUATION_5, "`"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_HYPHEN, "-"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_PUNCTUATION_3, ";"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_PUNCTUATION_4, "'"); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_COMMA, ","); + ADD_KEYBOARD_SHORTCUT_NAME(ES_SCANCODE_PERIOD, "."); +} + +void AccessKeysGather(EsElement *element) { + if (element->flags & ES_ELEMENT_BLOCK_FOCUS) return; + if (element->state & UI_STATE_BLOCK_INTERACTION) return; + if (element->flags & ES_ELEMENT_HIDDEN) return; + + for (uintptr_t i = 0; i < element->children.Length(); i++) { + AccessKeysGather(element->children[i]); + } + + if (!element->accessKey) return; + if (element->state & UI_STATE_DESTROYING) return; + if (element->flags & ES_ELEMENT_DISABLED) return; + + EsRectangle bounds = element->GetWindowBounds(); + + AccessKeyEntry entry = {}; + entry.character = element->accessKey; + entry.number = gui.accessKeys.numbers[entry.character - 'A']; + entry.element = element; + + if (element->flags & ES_ELEMENT_CENTER_ACCESS_KEY_HINT) { + entry.x = (bounds.l + bounds.r) / 2; + entry.y = (bounds.t + bounds.b) / 2; + } else { + entry.x = (bounds.l + bounds.r) / 2; + entry.y = bounds.b; + } + + if (entry.number >= 10) return; + + gui.accessKeys.entries.Add(entry); + gui.accessKeys.numbers[entry.character - 'A']++; +} + +void AccessKeyHintsShow(EsPainter *painter) { + for (uintptr_t i = 0; i < gui.accessKeys.entries.Length(); i++) { + AccessKeyEntry *entry = &gui.accessKeys.entries[i]; + UIStyle *style = gui.accessKeys.hintStyle; + + if (gui.accessKeys.typedCharacter && entry->character != gui.accessKeys.typedCharacter) { + continue; + } + + EsRectangle bounds = ES_RECT_4( + entry->x - style->preferredWidth / 2, + entry->x + style->preferredWidth / 2, + entry->y - style->preferredHeight / 4, + entry->y + 3 * style->preferredHeight / 4); + + if (entry->element->flags & ES_ELEMENT_CENTER_ACCESS_KEY_HINT) { + bounds.t -= style->preferredHeight / 4; + bounds.b -= style->preferredHeight / 4; + } + + if (bounds.r > (int32_t) gui.accessKeys.window->windowWidth) { + bounds.l = gui.accessKeys.window->windowWidth - style->preferredWidth; + bounds.r = bounds.l + style->preferredWidth; + } + + if (bounds.l < 0) { + bounds.l = 0; + bounds.r = bounds.l + style->preferredWidth; + } + + if (bounds.b > (int32_t) gui.accessKeys.window->windowHeight) { + bounds.t = gui.accessKeys.window->windowHeight - style->preferredHeight; + bounds.b = bounds.t + style->preferredHeight; + } + + if (bounds.t < 0) { + bounds.t = 0; + bounds.b = bounds.t + style->preferredHeight; + } + + style->PaintLayers(painter, bounds, 0, THEME_LAYER_MODE_BACKGROUND); + + char c = gui.accessKeys.typedCharacter ? entry->number + '0' : entry->character; + style->PaintText(painter, nullptr, bounds, &c, 1, 0, ES_FLAGS_DEFAULT); + } +} + +int AccessKeyLayerMessage(EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_PAINT && gui.accessKeyMode && gui.accessKeys.window == element->window) { + AccessKeyHintsShow(message->painter); + } + + return 0; +} + +void AccessKeyModeEnter(EsWindow *window) { + if (window->hasDialog || gui.menuMode || gui.accessKeyMode || window->windowStyle != ES_WINDOW_NORMAL) { + return; + } + + gui.accessKeyMode = true; + AccessKeysGather(window); + + if (!gui.accessKeys.hintStyle) { + gui.accessKeys.hintStyle = GetStyle(MakeStyleKey(ES_STYLE_ACCESS_KEY_HINT, 0), false); + } + + for (uintptr_t i = 0; i < gui.accessKeys.entries.Length(); i++) { + if (gui.accessKeys.numbers[gui.accessKeys.entries[i].character - 'A'] == 1) { + gui.accessKeys.entries[i].number = -1; + } + } + + gui.accessKeys.window = window; + window->Repaint(true); +} + +void AccessKeyModeExit() { + if (!gui.accessKeyMode) { + return; + } + + gui.accessKeys.entries.Free(); + gui.accessKeys.window->Repaint(true); + EsMemoryZero(gui.accessKeys.numbers, sizeof(gui.accessKeys.numbers)); + gui.accessKeys.typedCharacter = 0; + gui.accessKeys.window = nullptr; + gui.accessKeyMode = false; +} + +void AccessKeyModeHandleKeyPress(EsMessage *message) { + int ic, isc; + ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, false, false); + ic = EsCRTtoupper(ic); + + bool keepAccessKeyModeActive = false; + + if (ic >= 'A' && ic <= 'Z' && !gui.accessKeys.typedCharacter) { + if (gui.accessKeys.numbers[ic - 'A'] > 1) { + keepAccessKeyModeActive = true; + gui.accessKeys.typedCharacter = ic; + } else if (gui.accessKeys.numbers[ic - 'A'] == 1) { + for (uintptr_t i = 0; i < gui.accessKeys.entries.Length(); i++) { + AccessKeyEntry *entry = &gui.accessKeys.entries[i]; + + if (entry->character == ic) { + EsMessage m = { ES_MSG_MOUSE_LEFT_CLICK }; + EsMessageSend(entry->element, &m); + EsElementFocus(entry->element, ES_ELEMENT_FOCUS_ENSURE_VISIBLE | ES_ELEMENT_FOCUS_FROM_KEYBOARD); + + keepAccessKeyModeActive = entry->element->flags & ES_ELEMENT_STICKY_ACCESS_KEY; + } + } + } + } else if (ic >= '0' && ic <= '9' && gui.accessKeys.typedCharacter) { + for (uintptr_t i = 0; i < gui.accessKeys.entries.Length(); i++) { + AccessKeyEntry *entry = &gui.accessKeys.entries[i]; + + if (entry->character == gui.accessKeys.typedCharacter && entry->number == ic - '0') { + EsMessage m = { ES_MSG_MOUSE_LEFT_CLICK }; + EsMessageSend(entry->element, &m); + EsElementFocus(entry->element, ES_ELEMENT_FOCUS_ENSURE_VISIBLE | ES_ELEMENT_FOCUS_FROM_KEYBOARD); + + keepAccessKeyModeActive = entry->element->flags & ES_ELEMENT_STICKY_ACCESS_KEY; + } + } + } + + if (!keepAccessKeyModeActive) { + AccessKeyModeExit(); + } else { + gui.accessKeys.window->Repaint(true); + } +} + +void UIRefreshPrimaryClipboard(EsWindow *window) { + if (window->focused) { + EsMessage m = {}; + m.type = ES_MSG_PRIMARY_CLIPBOARD_UPDATED; + EsMessageSend(window->focused, &m); + } +} + +void UIHandleKeyMessage(EsWindow *window, EsMessage *message) { + if (message->type == ES_MSG_KEY_UP) { + if (message->keyboard.scancode == ES_SCANCODE_LEFT_CTRL ) gui.leftModifiers &= ~ES_MODIFIER_CTRL; + if (message->keyboard.scancode == ES_SCANCODE_LEFT_ALT ) gui.leftModifiers &= ~ES_MODIFIER_ALT; + if (message->keyboard.scancode == ES_SCANCODE_LEFT_SHIFT ) gui.leftModifiers &= ~ES_MODIFIER_SHIFT; + if (message->keyboard.scancode == ES_SCANCODE_LEFT_FLAG ) gui.leftModifiers &= ~ES_MODIFIER_FLAG; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_CTRL ) gui.rightModifiers &= ~ES_MODIFIER_CTRL; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_ALT ) gui.rightModifiers &= ~ES_MODIFIER_ALT; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_SHIFT) gui.rightModifiers &= ~ES_MODIFIER_SHIFT; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_FLAG ) gui.rightModifiers &= ~ES_MODIFIER_FLAG; + + if (message->keyboard.scancode == ES_SCANCODE_LEFT_ALT && gui.unhandledAltPress) { + AccessKeyModeEnter(window); + } + + if (window->focused) EsMessageSend(window->focused, message); + return; + } + + if (window->targetMenu) { + window = window->targetMenu; + } + + gui.unhandledAltPress = false; + + if (message->keyboard.scancode == ES_SCANCODE_F2 && message->keyboard.modifiers == ES_MODIFIER_ALT) { + EnterDebugger(); + EsPrint("[Alt-F2]\n"); + } + + if (message->keyboard.scancode == ES_SCANCODE_LEFT_CTRL ) gui.leftModifiers |= ES_MODIFIER_CTRL; + if (message->keyboard.scancode == ES_SCANCODE_LEFT_ALT ) gui.leftModifiers |= ES_MODIFIER_ALT; + if (message->keyboard.scancode == ES_SCANCODE_LEFT_SHIFT ) gui.leftModifiers |= ES_MODIFIER_SHIFT; + if (message->keyboard.scancode == ES_SCANCODE_LEFT_FLAG ) gui.leftModifiers |= ES_MODIFIER_FLAG; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_CTRL ) gui.rightModifiers |= ES_MODIFIER_CTRL; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_ALT ) gui.rightModifiers |= ES_MODIFIER_ALT; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_SHIFT) gui.rightModifiers |= ES_MODIFIER_SHIFT; + if (message->keyboard.scancode == ES_SCANCODE_RIGHT_FLAG ) gui.rightModifiers |= ES_MODIFIER_FLAG; + + if (window->windowStyle == ES_WINDOW_MENU && message->keyboard.scancode == ES_SCANCODE_ESCAPE) { + window->Destroy(); + return; + } + + if (gui.menuMode) { + // TODO Check the window is the one that enabled menu mode. + // TODO Escape to close/exit menu mode. + // TODO Left/right to navigate columns/cycle menubar and open/close submenus. + // TODO Up/down to traverse menu. + // TODO Enter to open submenu/invoke item. + return; + } else if (gui.accessKeyMode) { + if (gui.accessKeys.window != window) { + AccessKeyModeExit(); + } else { + AccessKeyModeHandleKeyPress(message); + return; + } + } + + if (window->pressed) { + if (message->keyboard.scancode == ES_SCANCODE_ESCAPE) { + UIMousePressReleased(window, nullptr, false); + return; + } + } + + if (window->focused) { + message->type = ES_MSG_KEY_TYPED; + if (EsMessageSend(window->focused, message) != 0) return; + + EsElement *element = window->focused; + message->type = ES_MSG_KEY_DOWN; + + if (UIMessageSendPropagateToAncestors(element, message)) { + return; + } + } + + if (message->keyboard.scancode == ES_SCANCODE_TAB && (message->keyboard.modifiers & ~ES_MODIFIER_SHIFT) == 0) { + EsElement *element = window->focused ?: window; + EsElement *start = element; + + do element = UITabTraversalDo(element, message->keyboard.modifiers & ES_MODIFIER_SHIFT); + while ((!element->IsFocusable() || (element->flags & ES_ELEMENT_NOT_TAB_TRAVERSABLE)) && element != start); + + EsElementFocus(element, ES_ELEMENT_FOCUS_ENSURE_VISIBLE | ES_ELEMENT_FOCUS_FROM_KEYBOARD); + return; + } + + if (window->focused) { + if (message->keyboard.scancode == ES_SCANCODE_SPACE && message->keyboard.modifiers == 0) { + EsMessage m = { ES_MSG_MOUSE_LEFT_CLICK }; + EsMessageSend(window->focused, &m); + return; + } + } else { + EsMessageSend(window, message); + } + + // TODO Radio group navigation. + + if (window->enterButton && message->keyboard.scancode == ES_SCANCODE_ENTER && !message->keyboard.modifiers + && window->enterButton->onCommand && (~window->enterButton->flags & ES_ELEMENT_DISABLED)) { + window->enterButton->onCommand(window->instance, window->enterButton, window->enterButton->command); + } else if (window->escapeButton && message->keyboard.scancode == ES_SCANCODE_ESCAPE && !message->keyboard.modifiers + && window->escapeButton->onCommand && (~window->escapeButton->flags & ES_ELEMENT_DISABLED)) { + window->escapeButton->onCommand(window->instance, window->escapeButton, window->escapeButton->command); + } + + if (!window->hasDialog) { + // TODO Sort out what commands can be used from within dialogs and menus. + + if (message->keyboard.scancode == ES_SCANCODE_LEFT_ALT) { + gui.unhandledAltPress = true; + return; + } + + if (!gui.keyboardShortcutNames.itemCount) UIInitialiseKeyboardShortcutNamesTable(); + const char *shortcutName = (const char *) HashTableGetShort(&gui.keyboardShortcutNames, message->keyboard.scancode); + + if (shortcutName && window->instance && window->instance->_private) { + APIInstance *instance = (APIInstance *) window->instance->_private; + + char keyboardShortcutString[128]; + size_t bytes = EsStringFormat(keyboardShortcutString, 128, "%z%z%z%z%c", + (message->keyboard.modifiers & ES_MODIFIER_CTRL) ? "Ctrl+" : "", + (message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? "Shift+" : "", + (message->keyboard.modifiers & ES_MODIFIER_ALT) ? "Alt+" : "", + shortcutName, 0) - 1; + + for (uintptr_t i = 0; i < instance->commands.Count(); i++) { + EsCommand *command = instance->commands[i]; + if (!command->cKeyboardShortcut || command->disabled) continue; + const char *position = EsCRTstrstr(command->cKeyboardShortcut, keyboardShortcutString); + if (!position) continue; + + if ((position[bytes] == 0 || position[bytes] == '|') && (position == command->cKeyboardShortcut || position[-1] == '|')) { + if (command->callback) { + command->callback(window->instance, nullptr, command); + } + + return; + } + } + } + } +} + +void UIWindowPaintNow(EsWindow *window, ProcessMessageTiming *timing, bool afterResize) { + if (window->doNotPaint) { + return; + } + + // Calculate the regions to repaint, and then perform painting. + + if (timing) timing->startPaint = EsTimeStampMs(); + + EsRectangle updateRegion = window->updateRegion; + + EsRectangle bounds = ES_RECT_4(0, window->windowWidth, 0, window->windowHeight); + EsRectangleClip(updateRegion, bounds, &updateRegion); + + if (THEME_RECT_VALID(updateRegion)) { + EsPainter painter = {}; + EsPaintTarget target = {}; + + target.fullAlpha = window->windowStyle != ES_WINDOW_NORMAL; + target.width = Width(updateRegion); + target.height = Height(updateRegion); + target.stride = target.width * 4; + target.bits = EsHeapAllocate(target.stride * target.height, false); + target.forWindowManager = true; + + if (!target.bits) { + return; // Insufficient memory for painting. + } + + EsMemoryFaultRange(target.bits, target.stride * target.height); + painter.offsetX -= updateRegion.l; + painter.offsetY -= updateRegion.t; + painter.clip = ES_RECT_4(0, target.width, 0, target.height); + painter.target = ⌖ + + window->updateRegionInProgress = updateRegion; + window->InternalPaint(&painter, ES_FLAGS_DEFAULT); + window->updateRegionInProgress = {}; + if (timing) timing->endPaint = EsTimeStampMs(); + + if (window->visualizeRepaints) { + EsDrawRectangle(&painter, painter.clip, 0, EsRandomU64(), ES_RECT_1(3)); + } + + // Update the screen. + if (timing) timing->startUpdate = EsTimeStampMs(); + EsSyscall(ES_SYSCALL_WINDOW_SET_BITS, window->handle, (uintptr_t) &updateRegion, (uintptr_t) target.bits, + afterResize ? WINDOW_SET_BITS_AFTER_RESIZE : WINDOW_SET_BITS_NORMAL); + if (timing) timing->endUpdate = EsTimeStampMs(); + + EsHeapFree(target.bits); + } + + window->updateRegion = ES_RECT_4(window->windowWidth, 0, window->windowHeight, 0); +} + +bool UISetCursor(EsWindow *window) { + EsCursorStyle cursorStyle = ES_CURSOR_NORMAL; + EsElement *element = window->pressed ?: window->hovered; + + if (element) { + EsMessage m = { ES_MSG_GET_CURSOR }; + + if (ES_HANDLED == EsMessageSend(element, &m)) { + cursorStyle = m.cursorStyle; + } else { + cursorStyle = (EsCursorStyle) element->currentStyle->metrics->cursor; + } + } + + // TODO Make these configurable in the theme file! + + int x, y, ox, oy, w, h; + +#define CURSOR(_constant, _x, _y, _ox, _oy, _w, _h) _constant: { x = _x; y = _y; ox = -_ox; oy = -_oy; w = _w; h = _h; } break + + switch (cursorStyle) { + CURSOR(case ES_CURSOR_TEXT, 84, 60, 4, 10, 11, 22); + CURSOR(case ES_CURSOR_RESIZE_VERTICAL, 44, 12, 4, 11, 11, 24); + CURSOR(case ES_CURSOR_RESIZE_HORIZONTAL, 68, 15, 11, 4, 24, 11); + CURSOR(case ES_CURSOR_RESIZE_DIAGONAL_1, 55, 35, 8, 8, 19, 19); + CURSOR(case ES_CURSOR_RESIZE_DIAGONAL_2, 82, 35, 8, 8, 19, 19); + CURSOR(case ES_CURSOR_SPLIT_VERTICAL, 37, 55, 4, 10, 12, 24); + CURSOR(case ES_CURSOR_SPLIT_HORIZONTAL, 56, 60, 10, 4, 24, 12); + CURSOR(case ES_CURSOR_HAND_HOVER, 131, 14, 8, 1, 21, 26); + CURSOR(case ES_CURSOR_HAND_DRAG, 107, 18, 7, 1, 20, 22); + CURSOR(case ES_CURSOR_HAND_POINT, 159, 14, 5, 1, 21, 26); + CURSOR(case ES_CURSOR_SCROLL_UP_LEFT, 217, 89, 9, 9, 16, 16); + CURSOR(case ES_CURSOR_SCROLL_UP, 193, 87, 5, 11, 13, 18); + CURSOR(case ES_CURSOR_SCROLL_UP_RIGHT, 234, 89, 4, 9, 16, 16); + CURSOR(case ES_CURSOR_SCROLL_LEFT, 175, 93, 11, 5, 18, 13); + CURSOR(case ES_CURSOR_SCROLL_CENTER, 165, 51, 12, 12, 28, 28); + CURSOR(case ES_CURSOR_SCROLL_RIGHT, 194, 106, 4, 4, 18, 13); + CURSOR(case ES_CURSOR_SCROLL_DOWN_LEFT, 216, 106, 10, 4, 16, 16); + CURSOR(case ES_CURSOR_SCROLL_DOWN, 182, 107, 4, 3, 12, 16); + CURSOR(case ES_CURSOR_SCROLL_DOWN_RIGHT, 234, 106, 4, 4, 16, 16); + CURSOR(case ES_CURSOR_SELECT_LINES, 7, 19, 10, 0, 16, 23); + CURSOR(case ES_CURSOR_DROP_TEXT, 11, 48, 1, 1, 21, 28); + CURSOR(case ES_CURSOR_CROSS_HAIR_PICK, 104, 56, 11, 11, 26, 26); + CURSOR(case ES_CURSOR_CROSS_HAIR_RESIZE, 134, 53, 11, 11, 26, 26); + CURSOR(case ES_CURSOR_MOVE_HOVER, 226, 13, 10, 10, 24, 32); + CURSOR(case ES_CURSOR_MOVE_DRAG, 197, 13, 10, 10, 24, 24); + CURSOR(case ES_CURSOR_ROTATE_HOVER, 228, 49, 9, 10, 24, 32); + CURSOR(case ES_CURSOR_ROTATE_DRAG, 198, 49, 9, 10, 22, 22); + CURSOR(case ES_CURSOR_BLANK, 5, 13, 0, 0, 1, 1); + CURSOR(default, 23, 18, 1, 1, 15, 24); + } + + bool _xor = cursorStyle == ES_CURSOR_TEXT; + + return EsSyscall(ES_SYSCALL_WINDOW_SET_CURSOR, window->handle, + (uintptr_t) theming.cursors.bits + x * 4 + y * theming.cursors.stride, + ((0xFF & ox) << 0) | ((0xFF & oy) << 8) | ((0xFF & w) << 16) | ((0xFF & h) << 24), + theming.cursors.stride | (cursorStyle << 24) | ((uint32_t) _xor << 31)); +} + +void UIProcessWindowManagerMessage(EsWindow *window, EsMessage *message, ProcessMessageTiming *timing) { + // Check if the window has been destroyed. + + if (message->type == ES_MSG_WINDOW_DESTROYED) { + if (window->destroyInstanceAfterClose) { + EsMessage m = {}; + m.type = ES_MSG_INSTANCE_DESTROY; + m.instanceDestroy.instance = window->instance; + EsAssert(window->instance->window == window); + window->instance->window = nullptr; + window->instance = nullptr; + EsMessagePost(nullptr, &m); + } + + EsAssert(window->handle == ES_INVALID_HANDLE); + EsHeapFree(window); + return; + } else if (window->handle == ES_INVALID_HANDLE) { + return; + } + + // Begin update. + + window->willUpdate = true; + + // Make sure any elements marked to be destroyed in between updates are actually destroyed, + // before we begin message processing. + + if (window->InternalDestroy()) { + // The window has been destroyed. + return; + } + + ProcessAnimations(); + UIFindHoverElement(window); + + // Process input message. + + if (timing) timing->startLogic = EsTimeStampMs(); + + if (message->type == ES_MSG_MOUSE_MOVED) { + window->mousePosition.x = message->mouseMoved.newPositionX; + window->mousePosition.y = message->mouseMoved.newPositionY; + + if ((!window->activated || window->targetMenu) && window->windowStyle == ES_WINDOW_NORMAL) { + window->hovering = false; + goto skipInputMessage; + } else if (window->dragged) { + if (gui.draggingStarted || DistanceSquared(message->mouseMoved.newPositionX - gui.lastClickX, + message->mouseMoved.newPositionY - gui.lastClickY) >= GetConstantNumber("dragThreshold")) { + EsRectangle bounds = window->dragged->GetWindowBounds(); + message->type = gui.lastClickButton == ES_MSG_MOUSE_LEFT_DOWN ? ES_MSG_MOUSE_LEFT_DRAG + : gui.lastClickButton == ES_MSG_MOUSE_RIGHT_DOWN ? ES_MSG_MOUSE_RIGHT_DRAG + : gui.lastClickButton == ES_MSG_MOUSE_MIDDLE_DOWN ? ES_MSG_MOUSE_MIDDLE_DRAG : (EsMessageType) 0; + EsAssert(message->type); + message->mouseDragged.originalPositionX = gui.lastClickX; + message->mouseDragged.originalPositionY = gui.lastClickY; + message->mouseDragged.newPositionX -= bounds.l; + message->mouseDragged.newPositionY -= bounds.t; + message->mouseDragged.originalPositionX -= bounds.l; + message->mouseDragged.originalPositionY -= bounds.t; + + if (ES_HANDLED == EsMessageSend(window->dragged, message)) { + gui.draggingStarted = true; + } + } + } else { + EsRectangle bounds = window->hovered->GetWindowBounds(); + message->mouseMoved.newPositionX -= bounds.l; + message->mouseMoved.newPositionY -= bounds.t; + EsMessageSend(window->hovered, message); + } + + window->hovering = true; + } else if (message->type == ES_MSG_MOUSE_EXIT) { + window->hovering = false; + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN || message->type == ES_MSG_MOUSE_RIGHT_DOWN || message->type == ES_MSG_MOUSE_MIDDLE_DOWN) { + window->mousePosition.x = message->mouseDown.positionX; + window->mousePosition.y = message->mouseDown.positionY; + + AccessKeyModeExit(); + + if (gui.mouseButtonDown || window->targetMenu) { + goto skipInputMessage; + } + + double timeStampMs = EsTimeStampMs(); + + if (gui.clickChainStartMs + api.global->clickChainTimeoutMs < timeStampMs + || window->hovered != gui.clickChainElement) { + // Start a new click chain. + gui.clickChainStartMs = timeStampMs; + gui.clickChainCount = 1; + gui.clickChainElement = window->hovered; + } else { + gui.clickChainStartMs = timeStampMs; + gui.clickChainCount++; + } + + message->mouseDown.clickChainCount = gui.clickChainCount; + + gui.lastClickX = message->mouseDown.positionX; + gui.lastClickY = message->mouseDown.positionY; + gui.lastClickButton = message->type; + gui.mouseButtonDown = true; + + if ((~window->hovered->flags & ES_ELEMENT_DISABLED) && (~window->hovered->state & UI_STATE_BLOCK_INTERACTION)) { + // If the hovered element is destroyed in response to one of these messages, + // window->hovered will be set to nullptr, so save the element here. + EsElement *element = window->hovered; + + element->state |= UI_STATE_PRESSED; + window->pressed = element; + EsMessage m = { ES_MSG_PRESSED_START }; + EsMessageSend(element, &m); + + EsRectangle bounds = element->GetWindowBounds(); + message->mouseDown.positionX -= bounds.l; + message->mouseDown.positionY -= bounds.t; + + if (ES_REJECTED != UIMessageSendPropagateToAncestors(element, message, &window->dragged)) { + if (window->dragged) { + EsElementFocus(window->dragged, false); + } + } + } + + if (window->hovered != window->focused && window->focused && (~window->focused->state & UI_STATE_LOST_STRONG_FOCUS)) { + EsMessage m = { ES_MSG_STRONG_FOCUS_END }; + window->focused->state |= UI_STATE_LOST_STRONG_FOCUS; + EsMessageSend(window->focused, &m); + } + } else if (message->type == ES_MSG_MOUSE_LEFT_UP || message->type == ES_MSG_MOUSE_RIGHT_UP || message->type == ES_MSG_MOUSE_MIDDLE_UP) { + AccessKeyModeExit(); + + if (gui.mouseButtonDown && gui.lastClickButton == message->type - 1) { + gui.mouseButtonDown = false; + window->dragged = nullptr; + UIMousePressReleased(window, message, true); + } + } else if (message->type == ES_MSG_KEY_UP || message->type == ES_MSG_KEY_DOWN) { + UIHandleKeyMessage(window, message); + + // If this key was on the numpad, translate it to the normal key. + + int numpad = 0, nshift = 0; + uint32_t scancode = message->keyboard.scancode; + bool allowShift = false; + + if (scancode == ES_SCANCODE_NUM_DIVIDE ) { numpad = ES_SCANCODE_SLASH; } + if (scancode == ES_SCANCODE_NUM_MULTIPLY) { numpad = ES_SCANCODE_8; nshift = 1; } + if (scancode == ES_SCANCODE_NUM_SUBTRACT) { numpad = ES_SCANCODE_HYPHEN; } + if (scancode == ES_SCANCODE_NUM_ADD ) { numpad = ES_SCANCODE_EQUALS; nshift = 1; } + if (scancode == ES_SCANCODE_NUM_ENTER ) { numpad = ES_SCANCODE_ENTER; } + + if (message->keyboard.numlock) { + if (scancode == ES_SCANCODE_NUM_POINT) { numpad = ES_SCANCODE_PERIOD; } + if (scancode == ES_SCANCODE_NUM_0 ) { numpad = ES_SCANCODE_0; } + if (scancode == ES_SCANCODE_NUM_1 ) { numpad = ES_SCANCODE_1; } + if (scancode == ES_SCANCODE_NUM_2 ) { numpad = ES_SCANCODE_2; } + if (scancode == ES_SCANCODE_NUM_3 ) { numpad = ES_SCANCODE_3; } + if (scancode == ES_SCANCODE_NUM_4 ) { numpad = ES_SCANCODE_4; } + if (scancode == ES_SCANCODE_NUM_5 ) { numpad = ES_SCANCODE_5; } + if (scancode == ES_SCANCODE_NUM_6 ) { numpad = ES_SCANCODE_6; } + if (scancode == ES_SCANCODE_NUM_7 ) { numpad = ES_SCANCODE_7; } + if (scancode == ES_SCANCODE_NUM_8 ) { numpad = ES_SCANCODE_8; } + if (scancode == ES_SCANCODE_NUM_9 ) { numpad = ES_SCANCODE_9; } + } else { + if (scancode == ES_SCANCODE_NUM_POINT) { numpad = ES_SCANCODE_DELETE; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_0 ) { numpad = ES_SCANCODE_INSERT; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_1 ) { numpad = ES_SCANCODE_END; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_2 ) { numpad = ES_SCANCODE_DOWN_ARROW; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_3 ) { numpad = ES_SCANCODE_PAGE_DOWN; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_4 ) { numpad = ES_SCANCODE_LEFT_ARROW; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_6 ) { numpad = ES_SCANCODE_RIGHT_ARROW; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_7 ) { numpad = ES_SCANCODE_HOME; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_8 ) { numpad = ES_SCANCODE_UP_ARROW; allowShift = true; } + if (scancode == ES_SCANCODE_NUM_9 ) { numpad = ES_SCANCODE_PAGE_UP; allowShift = true; } + } + + if (numpad && ((~message->keyboard.modifiers & ES_MODIFIER_SHIFT) || allowShift)) { + EsMessage m = *message; + m.type = message->type; + m.keyboard.modifiers = message->keyboard.modifiers | (nshift ? ES_MODIFIER_SHIFT : 0); + m.keyboard.scancode = numpad; + m.keyboard.numpad = true; + UIHandleKeyMessage(window, &m); + } + } else if (message->type == ES_MSG_WINDOW_RESIZED) { + AccessKeyModeExit(); + + gui.leftModifiers = gui.rightModifiers = 0; + gui.clickChainStartMs = 0; + + window->receivedFirstResize = true; + + window->width = window->windowWidth = message->windowResized.content.r; + window->height = window->windowHeight = message->windowResized.content.b; + + if (window->windowStyle == ES_WINDOW_CONTAINER) { + EsRectangle opaqueBounds = ES_RECT_4(WINDOW_INSET, window->windowWidth - WINDOW_INSET, + WINDOW_INSET, window->windowHeight - WINDOW_INSET); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, (uintptr_t) &opaqueBounds, 0, ES_WINDOW_PROPERTY_OPAQUE_BOUNDS); + } else if (window->windowStyle == ES_WINDOW_INSPECTOR) { + EsRectangle opaqueBounds = ES_RECT_4(0, window->windowWidth, 0, window->windowHeight); + EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, (uintptr_t) &opaqueBounds, 0, ES_WINDOW_PROPERTY_OPAQUE_BOUNDS); + } + + if (!message->windowResized.hidden) { + EsElementRelayout(window); + window->Repaint(true); + } + + EsMessageSend(window, message); + + for (uintptr_t i = 0; i < window->sizeAlternatives.Length(); i++) { + SizeAlternative *alternative = &window->sizeAlternatives[i]; + bool belowThreshold = window->width < alternative->widthThreshold * theming.scale + || window->height < alternative->heightThreshold * theming.scale; + EsElementSetHidden(alternative->small, !belowThreshold); + EsElementSetHidden(alternative->big, belowThreshold); + } + } else if (message->type == ES_MSG_WINDOW_DEACTIVATED) { + AccessKeyModeExit(); + + if (window->windowStyle == ES_WINDOW_MENU) { + window->Destroy(); + } else if (window->windowStyle == ES_WINDOW_CONTAINER) { + // Redraw window borders. + window->SetStyle(ES_STYLE_CONTAINER_WINDOW_INACTIVE); + window->Repaint(true); + } + + window->activated = false; + window->hovering = false; + + if (window->focused) { + window->inactiveFocus = window->focused; + window->inactiveFocus->Repaint(true); + UIRemoveFocusFromElement(window->focused); + window->focused = nullptr; + } + + EsMessageSend(window, message); + } else if (message->type == ES_MSG_WINDOW_ACTIVATED) { + AccessKeyModeExit(); + + gui.leftModifiers = gui.rightModifiers = 0; + gui.clickChainStartMs = 0; + + window->activated = true; + EsMessage m = { ES_MSG_WINDOW_ACTIVATED }; + EsMessageSend(window, &m); + + if (window->windowStyle == ES_WINDOW_CONTAINER) { + // Redraw window borders. + window->SetStyle(ES_STYLE_CONTAINER_WINDOW_ACTIVE); + window->Repaint(true); + } + + if (!window->focused && window->inactiveFocus) { + EsElementFocus(window->inactiveFocus, false); + window->inactiveFocus->Repaint(true); + window->inactiveFocus = nullptr; + } + + UIRefreshPrimaryClipboard(window); + } else if (message->type == ES_MSG_PRIMARY_CLIPBOARD_UPDATED) { + UIRefreshPrimaryClipboard(window); + } + + skipInputMessage:; + + if (timing) timing->endLogic = EsTimeStampMs(); + + // Destroy, relayout, and repaint elements as necessary. + + if (window->InternalDestroy()) { + // The window has been destroyed. + return; + } + + UIFindHoverElement(window); + + bool changedCursor = UISetCursor(window); + + if (window->receivedFirstResize || window->windowStyle != ES_WINDOW_NORMAL) { + if (timing) timing->startLayout = EsTimeStampMs(); + + window->InternalMove(window->width, window->height, 0, 0); + + if (window->ensureVisible) { + EsElement *child = window->ensureVisible, *e = window->ensureVisible; + + while (e->parent) { + EsMessage m = { ES_MSG_ENSURE_VISIBLE }; + m.child = child; + e = e->parent; + + if (ES_HANDLED == EsMessageSend(e, &m)) { + child = e; + } + } + + window->ensureVisible = nullptr; + } + + if (window->processCheckVisible) { + for (uintptr_t i = 0; i < window->checkVisible.Length(); i++) { + EsElement *element = window->checkVisible[i]; + EsAssert(element->state & UI_STATE_CHECK_VISIBLE); + EsRectangle bounds = element->GetWindowBounds(); + bool offScreen = bounds.r < 0 || bounds.b < 0 || bounds.l >= element->window->width || bounds.t >= element->window->height; + if (!offScreen) continue; + element->state &= ~UI_STATE_CHECK_VISIBLE; + window->checkVisible.Delete(i); + i--; + EsMessage m = { ES_MSG_NOT_VISIBLE }; + EsMessageSend(element, &m); + } + + window->processCheckVisible = false; + } + + if (timing) timing->endLayout = EsTimeStampMs(); + } + + if (THEME_RECT_VALID(window->updateRegion) && window->width == (int) window->windowWidth && window->height == (int) window->windowHeight) { + UIWindowPaintNow(window, timing, message->type == ES_MSG_WINDOW_RESIZED); + } else if (changedCursor) { + EsSyscall(ES_SYSCALL_SCREEN_FORCE_UPDATE, 0, 0, 0, 0); + } + + if (window->instance) { + APIInstance *instance = (APIInstance *) window->instance->_private; + EsUndoEndGroup(instance->activeUndoManager); + } + + // Free any unused styles. + + FreeUnusedStyles(); + + // Finish update. + + window->willUpdate = false; +} + +// --------------------------------- List view. + +#include "list_view.cpp" + +// --------------------------------- Inspector. + +struct InspectorElementEntry { + EsElement *element; + EsRectangle takenBounds, givenBounds; + int depth; +}; + +struct InspectorWindow : EsInstance { + EsInstance *instance; + + EsWindow *window; + + EsListView *elementList; + Array elements; + InspectorElementEntry hoveredElement; + char *cCategoryFilter; + + intptr_t selectedElement; + EsButton *alignH[6]; + EsButton *alignV[6]; + EsButton *direction[4]; + EsTextbox *contentTextbox; + EsButton *addChildButton; + EsButton *addSiblingButton; + EsButton *visualizeRepaints; + EsButton *visualizeLayoutBounds; + EsButton *visualizePaintSteps; + EsListView *listEvents; + EsTextbox *textboxCategoryFilter; +}; + +EsListViewColumn inspectorElementListColumns[] = { + { "Name", -1, 0, 300 }, + { "Bounds", -1, 0, 200 }, + { "Information", -1, 0, 200 }, +}; + +int InspectorElementItemCallback(EsElement *element, EsMessage *message) { + InspectorWindow *inspector = (InspectorWindow *) element->instance; + + if (message->type == ES_MSG_HOVERED_START) { + InspectorElementEntry *entry = &inspector->elements[EsListViewGetIndexFromItem(element).u]; + if (entry->element->parent) entry->element->parent->Repaint(true); + else entry->element->Repaint(true); + inspector->hoveredElement = *entry; + } else if (message->type == ES_MSG_HOVERED_END || message->type == ES_MSG_DESTROY) { + InspectorElementEntry *entry = &inspector->elements[EsListViewGetIndexFromItem(element).u]; + if (entry->element->parent) entry->element->parent->Repaint(true); + else entry->element->Repaint(true); + inspector->hoveredElement = {}; + } + + return 0; +} + +void InspectorUpdateEditor(InspectorWindow *inspector) { + EsElement *e = inspector->selectedElement == -1 ? nullptr : inspector->elements[inspector->selectedElement].element; + + bool isStack = e && e->messageClass == ProcessPanelMessage && !(e->flags & (ES_PANEL_Z_STACK | ES_PANEL_TABLE | ES_PANEL_SWITCHER)); + bool alignHLeft = e ? (e->flags & ES_CELL_H_LEFT) : false, alignHRight = e ? (e->flags & ES_CELL_H_RIGHT) : false; + bool alignHExpand = e ? (e->flags & ES_CELL_H_EXPAND) : false, alignHShrink = e ? (e->flags & ES_CELL_H_SHRINK) : false; + bool alignHPush = e ? (e->flags & ES_CELL_H_PUSH) : false; + bool alignVTop = e ? (e->flags & ES_CELL_V_TOP) : false, alignVBottom = e ? (e->flags & ES_CELL_V_BOTTOM) : false; + bool alignVExpand = e ? (e->flags & ES_CELL_V_EXPAND) : false, alignVShrink = e ? (e->flags & ES_CELL_V_SHRINK) : false; + bool alignVPush = e ? (e->flags & ES_CELL_V_PUSH) : false; + bool stackHorizontal = isStack && (e->flags & ES_PANEL_HORIZONTAL); + bool stackReverse = isStack && (e->flags & ES_PANEL_REVERSE); + + EsButtonSetCheck(inspector->alignH[0], (EsCheckState) (e && alignHLeft && !alignHRight), false); + EsButtonSetCheck(inspector->alignH[1], (EsCheckState) (e && alignHLeft == alignHRight), false); + EsButtonSetCheck(inspector->alignH[2], (EsCheckState) (e && !alignHLeft && alignHRight), false); + EsButtonSetCheck(inspector->alignH[3], (EsCheckState) (e && alignHExpand), false); + EsButtonSetCheck(inspector->alignH[4], (EsCheckState) (e && alignHShrink), false); + EsButtonSetCheck(inspector->alignH[5], (EsCheckState) (e && alignHPush), false); + + EsButtonSetCheck(inspector->alignV[0], (EsCheckState) (e && alignVTop && !alignVBottom), false); + EsButtonSetCheck(inspector->alignV[1], (EsCheckState) (e && alignVTop == alignVBottom), false); + EsButtonSetCheck(inspector->alignV[2], (EsCheckState) (e && !alignVTop && alignVBottom), false); + EsButtonSetCheck(inspector->alignV[3], (EsCheckState) (e && alignVExpand), false); + EsButtonSetCheck(inspector->alignV[4], (EsCheckState) (e && alignVShrink), false); + EsButtonSetCheck(inspector->alignV[5], (EsCheckState) (e && alignVPush), false); + + EsButtonSetCheck(inspector->direction[0], (EsCheckState) (isStack && stackHorizontal && stackReverse), false); + EsButtonSetCheck(inspector->direction[1], (EsCheckState) (isStack && stackHorizontal && !stackReverse), false); + EsButtonSetCheck(inspector->direction[2], (EsCheckState) (isStack && !stackHorizontal && stackReverse), false); + EsButtonSetCheck(inspector->direction[3], (EsCheckState) (isStack && !stackHorizontal && !stackReverse), false); + + EsElementSetDisabled(inspector->alignH[0], !e); + EsElementSetDisabled(inspector->alignH[1], !e); + EsElementSetDisabled(inspector->alignH[2], !e); + EsElementSetDisabled(inspector->alignH[3], !e); + EsElementSetDisabled(inspector->alignH[4], !e); + EsElementSetDisabled(inspector->alignH[5], !e); + EsElementSetDisabled(inspector->alignV[0], !e); + EsElementSetDisabled(inspector->alignV[1], !e); + EsElementSetDisabled(inspector->alignV[2], !e); + EsElementSetDisabled(inspector->alignV[3], !e); + EsElementSetDisabled(inspector->alignV[4], !e); + EsElementSetDisabled(inspector->alignV[5], !e); + EsElementSetDisabled(inspector->direction[0], !isStack); + EsElementSetDisabled(inspector->direction[1], !isStack); + EsElementSetDisabled(inspector->direction[2], !isStack); + EsElementSetDisabled(inspector->direction[3], !isStack); + EsElementSetDisabled(inspector->addChildButton, !isStack); + EsElementSetDisabled(inspector->addSiblingButton, !e || !e->parent); + + EsElementSetDisabled(inspector->textboxCategoryFilter, !e); + + EsTextboxSelectAll(inspector->contentTextbox); + EsTextboxInsert(inspector->contentTextbox, "", 0, false); + + if (e) { +#if 0 + for (uintptr_t i = 0; i < sizeof(builtinStyles) / sizeof(builtinStyles[0]); i++) { + if (e->currentStyleKey.partHash == CalculateCRC64(EsLiteral(builtinStyles[i]))) { + EsTextboxInsert(inspector->styleTextbox, builtinStyles[i], -1, false); + break; + } + } +#endif + + if (e->messageClass == ProcessButtonMessage) { + EsButton *button = (EsButton *) e; + EsElementSetDisabled(inspector->contentTextbox, false); + EsTextboxInsert(inspector->contentTextbox, button->label, button->labelBytes, false); + } else if (e->messageClass == ProcessTextDisplayMessage) { + EsTextDisplay *display = (EsTextDisplay *) e; + EsElementSetDisabled(inspector->contentTextbox, false); + EsTextboxInsert(inspector->contentTextbox, display->contents, display->textRuns[display->textRunCount].offset, false); + } else { + EsElementSetDisabled(inspector->contentTextbox, true); + } + } else { + EsElementSetDisabled(inspector->contentTextbox, true); + } +} + +int InspectorElementListCallback(EsElement *element, EsMessage *message) { + InspectorWindow *inspector = (InspectorWindow *) element->instance; + + if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) { + int column = message->getContent.column, index = message->getContent.index.i; + EsAssert(index >= 0 && index < (int) inspector->elements.Length()); + InspectorElementEntry *entry = &inspector->elements[index]; + + if (column == 0) { + EsBufferFormat(message->getContent.buffer, "%z", entry->element->cName); + } else if (column == 1) { + EsBufferFormat(message->getContent.buffer, "%R", entry->element->GetWindowBounds(false)); + } else if (column == 2) { + EsMessage m = *message; + m.type = ES_MSG_GET_INSPECTOR_INFORMATION; + EsMessageSend(entry->element, &m); + } + + return ES_HANDLED; + } else if (message->type == ES_MSG_LIST_VIEW_GET_INDENT) { + message->getIndent.indent = inspector->elements[message->getIndent.index.i].depth; + return ES_HANDLED; + } else if (message->type == ES_MSG_LIST_VIEW_CREATE_ITEM) { + message->createItem.item->messageUser = InspectorElementItemCallback; + return ES_HANDLED; + } else if (message->type == ES_MSG_LIST_VIEW_SELECT) { + if (inspector->selectedElement != -1) { + inspector->elements[inspector->selectedElement].element->state &= ~UI_STATE_INSPECTING; + } + + inspector->selectedElement = message->selectItem.isSelected ? message->selectItem.index.i : -1; + + if (inspector->selectedElement != -1) { + EsElement *e = inspector->elements[inspector->selectedElement].element; + e->state |= UI_STATE_INSPECTING; + InspectorNotifyElementEvent(e, nullptr, "Viewing events from '%z'.\n", e->cName); + } + + InspectorUpdateEditor(inspector); + return ES_HANDLED; + } else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) { + message->selectItem.isSelected = message->selectItem.index.i == inspector->selectedElement; + return ES_HANDLED; + } + + return 0; +} + +int InspectorContentTextboxCallback(EsElement *element, EsMessage *message) { + InspectorWindow *inspector = (InspectorWindow *) element->instance; + + if (message->type == ES_MSG_TEXTBOX_EDIT_END) { + size_t newContentBytes; + char *newContent = EsTextboxGetContents(inspector->contentTextbox, &newContentBytes); + EsElement *e = inspector->elements[inspector->selectedElement].element; + + if (e->messageClass == ProcessButtonMessage) { + EsButton *button = (EsButton *) e; + HeapDuplicate((void **) &button->label, newContent, newContentBytes); + button->labelBytes = newContentBytes; + } else if (e->messageClass == ProcessTextDisplayMessage) { + EsTextDisplay *display = (EsTextDisplay *) e; + EsTextDisplaySetContents(display, newContent, newContentBytes); + } else { + EsAssert(false); + } + + EsElementUpdateContentSize(e); + if (e->parent) EsElementUpdateContentSize(e->parent); + EsHeapFree(newContent); + return ES_HANDLED; + } + + return 0; +} + +int InspectorTextboxCategoryFilterCallback(EsElement *element, EsMessage *message) { + InspectorWindow *inspector = (InspectorWindow *) element->instance; + + if (message->type == ES_MSG_TEXTBOX_UPDATED) { + EsHeapFree(inspector->cCategoryFilter); + inspector->cCategoryFilter = EsTextboxGetContents((EsTextbox *) element); + } + + return 0; +} + +InspectorWindow *InspectorGet(EsElement *element) { + if (!element->window || !element->instance) return nullptr; + APIInstance *instance = (APIInstance *) element->instance->_private; + InspectorWindow *inspector = instance->attachedInspector; + if (!inspector || inspector->instance->window != element->window) return nullptr; + return inspector; +} + +void InspectorNotifyElementEvent(EsElement *element, const char *cCategory, const char *cFormat, ...) { + if (~element->state & UI_STATE_INSPECTING) return; + InspectorWindow *inspector = InspectorGet(element); + if (!inspector) return; + if (inspector->cCategoryFilter && inspector->cCategoryFilter[0] && cCategory && EsCRTstrcmp(cCategory, inspector->cCategoryFilter)) return; + va_list arguments; + va_start(arguments, cFormat); + char _buffer[256]; + EsBuffer buffer = { .out = (uint8_t *) _buffer, .bytes = sizeof(_buffer) }; + if (cCategory) EsBufferFormat(&buffer, "%z: ", cCategory); + EsBufferFormatV(&buffer, cFormat, arguments); + va_end(arguments); + EsListViewInsertFixedItem(inspector->listEvents, _buffer, buffer.position); + EsListViewScrollToEnd(inspector->listEvents); +} + +void InspectorNotifyElementContentChanged(EsElement *element) { + InspectorWindow *inspector = InspectorGet(element); + if (!inspector) return; + + for (uintptr_t i = 0; i < inspector->elements.Length(); i++) { + if (inspector->elements[i].element == element) { + EsListViewInvalidateContent(inspector->elementList, 0, i); + return; + } + } + + EsAssert(false); +} + +void InspectorNotifyElementMoved(EsElement *element, EsRectangle takenBounds) { + InspectorWindow *inspector = InspectorGet(element); + if (!inspector) return; + + for (uintptr_t i = 0; i < inspector->elements.Length(); i++) { + if (inspector->elements[i].element == element) { + inspector->elements[i].takenBounds = takenBounds; + inspector->elements[i].givenBounds = takenBounds; // TODO. + EsListViewInvalidateContent(inspector->elementList, 0, i); + return; + } + } + + EsAssert(false); +} + +void InspectorNotifyElementDestroyed(EsElement *element) { + InspectorWindow *inspector = InspectorGet(element); + if (!inspector) return; + + for (uintptr_t i = 0; i < inspector->elements.Length(); i++) { + if (inspector->elements[i].element == element) { + if (inspector->selectedElement == (intptr_t) i) { + inspector->selectedElement = -1; + InspectorUpdateEditor(inspector); + } else if (inspector->selectedElement > (intptr_t) i) { + inspector->selectedElement--; + } + + EsListViewRemove(inspector->elementList, 0, i, i); + inspector->elements.Delete(i); + return; + } + } + + EsAssert(false); +} + +void InspectorNotifyElementCreated(EsElement *element) { + InspectorWindow *inspector = InspectorGet(element); + if (!inspector) return; + + ptrdiff_t indexInParent = -1; + + for (uintptr_t i = 0; i < element->parent->children.Length(); i++) { + if (element->parent->children[i] == element) { + indexInParent = i; + break; + } + } + + EsAssert(indexInParent != -1); + + ptrdiff_t insertAfterIndex = -1; + + for (uintptr_t i = 0; i < inspector->elements.Length(); i++) { + if (indexInParent == 0) { + if (inspector->elements[i].element == element->parent) { + insertAfterIndex = i; + break; + } + } else { + if (inspector->elements[i].element == element->parent->children[indexInParent - 1]) { + insertAfterIndex = i; + int baseDepth = inspector->elements[i++].depth; + + for (; i < inspector->elements.Length(); i++) { + if (inspector->elements[i].depth > baseDepth) { + insertAfterIndex++; + } else { + break; + } + } + + break; + } + } + } + + EsAssert(insertAfterIndex != -1); + + int depth = 0; + EsElement *ancestor = element->parent; + + while (ancestor) { + depth++; + ancestor = ancestor->parent; + } + + if (inspector->selectedElement > insertAfterIndex) { + inspector->selectedElement++; + } + + InspectorElementEntry entry; + entry.element = element; + entry.depth = depth; + inspector->elements.Insert(entry, insertAfterIndex + 1); + EsListViewInsert(inspector->elementList, 0, insertAfterIndex + 1, insertAfterIndex + 1); +} + +void InspectorFindElementsRecursively(InspectorWindow *inspector, EsElement *element, int depth) { + InspectorElementEntry entry = {}; + entry.element = element; + entry.depth = depth; + inspector->elements.Add(entry); + + for (uintptr_t i = 0; i < element->children.Length(); i++) { + InspectorFindElementsRecursively(inspector, element->children[i], depth + 1); + } +} + +void InspectorRefreshElementList(InspectorWindow *inspector) { + EsListViewRemoveAll(inspector->elementList, 0); + inspector->elements.Free(); + InspectorFindElementsRecursively(inspector, inspector->instance->window, 0); + EsListViewInsert(inspector->elementList, 0, 0, inspector->elements.Length() - 1); +} + +void InspectorNotifyElementPainted(EsElement *element, EsPainter *painter) { + InspectorWindow *inspector = InspectorGet(element); + if (!inspector) return; + + InspectorElementEntry *entry = inspector->hoveredElement.element ? &inspector->hoveredElement : nullptr; + if (!entry) return; + + EsRectangle bounds = ES_RECT_4(painter->offsetX, painter->offsetX + painter->width, + painter->offsetY, painter->offsetY + painter->height); + + if (entry->element == element) { + EsDrawRectangle(painter, bounds, 0x607F7FFF, 0x60FFFF7F, element->currentStyle->insets); + } else if (entry->element->parent == element) { + if ((element->flags & ES_CELL_FILL) != ES_CELL_FILL) { + EsRectangle rectangle = entry->givenBounds; + rectangle.l += bounds.l, rectangle.r += bounds.l; + rectangle.t += bounds.t, rectangle.b += bounds.t; + // EsDrawBlock(painter, rectangle, 0x20FF7FFF); + } + } +} + +#define INSPECTOR_ALIGN_COMMAND(name, clear, set, toggle) \ +void name (EsInstance *instance, EsElement *, EsCommand *) { \ + InspectorWindow *inspector = (InspectorWindow *) instance; \ + EsElement *e = inspector->elements[inspector->selectedElement].element; \ + if (toggle) e->flags ^= set; \ + else { e->flags &= ~(clear); e->flags |= set; } \ + EsElementUpdateContentSize(e); \ + if (e->parent) EsElementUpdateContentSize(e->parent); \ + inspector->elementList->Repaint(true); \ + InspectorUpdateEditor(inspector); \ +} + +INSPECTOR_ALIGN_COMMAND(InspectorHAlignLeft, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_LEFT, false); +INSPECTOR_ALIGN_COMMAND(InspectorHAlignCenter, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, false); +INSPECTOR_ALIGN_COMMAND(InspectorHAlignRight, ES_CELL_H_LEFT | ES_CELL_H_RIGHT, ES_CELL_H_RIGHT, false); +INSPECTOR_ALIGN_COMMAND(InspectorHAlignExpand, 0, ES_CELL_H_EXPAND, true); +INSPECTOR_ALIGN_COMMAND(InspectorHAlignShrink, 0, ES_CELL_H_SHRINK, true); +INSPECTOR_ALIGN_COMMAND(InspectorHAlignPush, 0, ES_CELL_H_PUSH, true); +INSPECTOR_ALIGN_COMMAND(InspectorVAlignTop, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_TOP, false); +INSPECTOR_ALIGN_COMMAND(InspectorVAlignCenter, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, false); +INSPECTOR_ALIGN_COMMAND(InspectorVAlignBottom, ES_CELL_V_TOP | ES_CELL_V_BOTTOM, ES_CELL_V_BOTTOM, false); +INSPECTOR_ALIGN_COMMAND(InspectorVAlignExpand, 0, ES_CELL_V_EXPAND, true); +INSPECTOR_ALIGN_COMMAND(InspectorVAlignShrink, 0, ES_CELL_V_SHRINK, true); +INSPECTOR_ALIGN_COMMAND(InspectorVAlignPush, 0, ES_CELL_V_PUSH, true); +INSPECTOR_ALIGN_COMMAND(InspectorDirectionLeft, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, + ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, false); +INSPECTOR_ALIGN_COMMAND(InspectorDirectionRight, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, + ES_PANEL_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL, false); +INSPECTOR_ALIGN_COMMAND(InspectorDirectionUp, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, + ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_REVERSE, false); +INSPECTOR_ALIGN_COMMAND(InspectorDirectionDown, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_ELEMENT_LAYOUT_HINT_HORIZONTAL | ES_ELEMENT_LAYOUT_HINT_REVERSE, 0, false); + +void InspectorVisualizeRepaints(EsInstance *instance, EsElement *, EsCommand *) { + InspectorWindow *inspector = (InspectorWindow *) instance; + EsWindow *window = inspector->instance->window; + window->visualizeRepaints = !window->visualizeRepaints; + EsButtonSetCheck(inspector->visualizeRepaints, window->visualizeRepaints ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false); +} + +void InspectorVisualizePaintSteps(EsInstance *instance, EsElement *, EsCommand *) { + InspectorWindow *inspector = (InspectorWindow *) instance; + EsWindow *window = inspector->instance->window; + window->visualizePaintSteps = !window->visualizePaintSteps; + EsButtonSetCheck(inspector->visualizePaintSteps, window->visualizePaintSteps ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false); +} + +void InspectorVisualizeLayoutBounds(EsInstance *instance, EsElement *, EsCommand *) { + InspectorWindow *inspector = (InspectorWindow *) instance; + EsWindow *window = inspector->instance->window; + window->visualizeLayoutBounds = !window->visualizeLayoutBounds; + EsButtonSetCheck(inspector->visualizeLayoutBounds, window->visualizeLayoutBounds ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false); + EsElementRepaint(window); +} + +void InspectorAddElement2(EsMenu *menu, EsGeneric context) { + InspectorWindow *inspector = (InspectorWindow *) menu->instance; + if (inspector->selectedElement == -1) return; + EsElement *e = inspector->elements[inspector->selectedElement].element; + int asSibling = context.u & 0x80; + context.u &= ~0x80; + + if (asSibling) { + EsElementInsertAfter(e); + e = e->parent; + } + + if (context.u == 1) { + EsButtonCreate(e); + } else if (context.u == 2) { + EsPanelCreate(e); + } else if (context.u == 3) { + EsSpacerCreate(e); + } else if (context.u == 4) { + EsTextboxCreate(e); + } else if (context.u == 5) { + EsTextDisplayCreate(e); + } +} + +void InspectorAddElement(EsInstance *, EsElement *element, EsCommand *) { + EsMenu *menu = EsMenuCreate(element, ES_FLAGS_DEFAULT); + EsMenuAddItem(menu, 0, "Add button", -1, InspectorAddElement2, element->userData.u | 1); + EsMenuAddItem(menu, 0, "Add panel", -1, InspectorAddElement2, element->userData.u | 2); + EsMenuAddItem(menu, 0, "Add spacer", -1, InspectorAddElement2, element->userData.u | 3); + EsMenuAddItem(menu, 0, "Add textbox", -1, InspectorAddElement2, element->userData.u | 4); + EsMenuAddItem(menu, 0, "Add text display", -1, InspectorAddElement2, element->userData.u | 5); + EsMenuShow(menu); +} + +void InspectorSetup(EsWindow *window) { + InspectorWindow *inspector = (InspectorWindow *) EsHeapAllocate(sizeof(InspectorWindow), true); // TODO Freeing this. + inspector->window = window; + InstanceSetup(inspector); + inspector->instance = window->instance; + window->instance = inspector; + + inspector->selectedElement = -1; + + EsPanel *panel = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_PANEL_FILLED); + + { + EsPanel *toolbar = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR); + inspector->visualizeRepaints = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize repaints"); + EsButtonOnCommand(inspector->visualizeRepaints, InspectorVisualizeRepaints); + inspector->visualizeLayoutBounds = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize layout bounds"); + EsButtonOnCommand(inspector->visualizeLayoutBounds, InspectorVisualizeLayoutBounds); + inspector->visualizePaintSteps = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR, 0, "Visualize paint steps"); + EsButtonOnCommand(inspector->visualizePaintSteps, InspectorVisualizePaintSteps); + } + + inspector->elementList = EsListViewCreate(panel, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_SINGLE_SELECT); + inspector->elementList->messageUser = InspectorElementListCallback; + EsListViewSetColumns(inspector->elementList, inspectorElementListColumns, sizeof(inspectorElementListColumns) / sizeof(EsListViewColumn)); + EsListViewInsertGroup(inspector->elementList, 0); + + { + EsPanel *toolbar = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Horizontal:"); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + inspector->alignH[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->alignH[0], ES_ICON_ALIGN_HORIZONTAL_LEFT); + EsButtonOnCommand(inspector->alignH[0], InspectorHAlignLeft); + inspector->alignH[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->alignH[1], ES_ICON_ALIGN_HORIZONTAL_CENTER); + EsButtonOnCommand(inspector->alignH[1], InspectorHAlignCenter); + inspector->alignH[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->alignH[2], ES_ICON_ALIGN_HORIZONTAL_RIGHT); + EsButtonOnCommand(inspector->alignH[2], InspectorHAlignRight); + inspector->alignH[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Expand"); + EsButtonOnCommand(inspector->alignH[3], InspectorHAlignExpand); + inspector->alignH[4] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Shrink"); + EsButtonOnCommand(inspector->alignH[4], InspectorHAlignShrink); + inspector->alignH[5] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Push"); + EsButtonOnCommand(inspector->alignH[5], InspectorHAlignPush); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Vertical:"); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + inspector->alignV[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->alignV[0], ES_ICON_ALIGN_VERTICAL_TOP); + EsButtonOnCommand(inspector->alignV[0], InspectorVAlignTop); + inspector->alignV[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->alignV[1], ES_ICON_ALIGN_VERTICAL_CENTER); + EsButtonOnCommand(inspector->alignV[1], InspectorVAlignCenter); + inspector->alignV[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->alignV[2], ES_ICON_ALIGN_VERTICAL_BOTTOM); + EsButtonOnCommand(inspector->alignV[2], InspectorVAlignBottom); + inspector->alignV[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Expand"); + EsButtonOnCommand(inspector->alignV[3], InspectorVAlignExpand); + inspector->alignV[4] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Shrink"); + EsButtonOnCommand(inspector->alignV[4], InspectorVAlignShrink); + inspector->alignV[5] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED, 0, "Push"); + EsButtonOnCommand(inspector->alignV[5], InspectorVAlignPush); + } + + { + EsPanel *toolbar = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Stack:"); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + inspector->direction[0] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->direction[0], ES_ICON_GO_PREVIOUS); + EsButtonOnCommand(inspector->direction[0], InspectorDirectionLeft); + inspector->direction[1] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->direction[1], ES_ICON_GO_NEXT); + EsButtonOnCommand(inspector->direction[1], InspectorDirectionRight); + inspector->direction[2] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->direction[2], ES_ICON_GO_UP); + EsButtonOnCommand(inspector->direction[2], InspectorDirectionUp); + inspector->direction[3] = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_ELEMENT_DISABLED); + EsButtonSetIcon(inspector->direction[3], ES_ICON_GO_DOWN); + EsButtonOnCommand(inspector->direction[3], InspectorDirectionDown); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 25, 0); + inspector->addChildButton = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_BUTTON_DROPDOWN | ES_ELEMENT_DISABLED | ES_BUTTON_COMPACT, nullptr, "Add child... "); + EsButtonOnCommand(inspector->addChildButton, InspectorAddElement); + inspector->addSiblingButton = EsButtonCreate(toolbar, ES_BUTTON_TOOLBAR | ES_BUTTON_DROPDOWN | ES_ELEMENT_DISABLED | ES_BUTTON_COMPACT, nullptr, "Add sibling... "); + inspector->addSiblingButton->userData.i = 0x80; + EsButtonOnCommand(inspector->addSiblingButton, InspectorAddElement); + } + + { + EsPanel *toolbar = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR); + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0); + EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Content:"); + inspector->contentTextbox = EsTextboxCreate(toolbar, ES_ELEMENT_DISABLED | ES_TEXTBOX_EDIT_BASED); + inspector->contentTextbox->messageUser = InspectorContentTextboxCallback; + EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 25, 0); + EsTextDisplayCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, "Event category filter:"); + inspector->textboxCategoryFilter = EsTextboxCreate(toolbar, ES_ELEMENT_DISABLED); + inspector->textboxCategoryFilter->messageUser = InspectorTextboxCategoryFilterCallback; + } + + { + inspector->listEvents = EsListViewCreate(panel, ES_CELL_FILL | ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED); + } + + InspectorRefreshElementList(inspector); + + APIInstance *instance = (APIInstance *) inspector->instance->_private; + instance->attachedInspector = inspector; +} diff --git a/desktop/icons.header b/desktop/icons.header new file mode 100644 index 0000000..d7ceb48 --- /dev/null +++ b/desktop/icons.header @@ -0,0 +1,1067 @@ +enum EsStandardIcon { // Taken from the elementary icon pack, see res/Icons for license. + ES_ICON_NONE + ES_ICON_ACTION_UNAVAILABLE_SYMBOLIC + ES_ICON_ADDRESS_BOOK_NEW + ES_ICON_ADDRESS_BOOK_NEW_SYMBOLIC + ES_ICON_ALIGN_HORIZONTAL_CENTER + ES_ICON_ALIGN_HORIZONTAL_CENTER_SYMBOLIC + ES_ICON_ALIGN_HORIZONTAL_LEFT + ES_ICON_ALIGN_HORIZONTAL_LEFT_SYMBOLIC + ES_ICON_ALIGN_HORIZONTAL_LEFT_TO_ANCHOR + ES_ICON_ALIGN_HORIZONTAL_LEFT_TO_ANCHOR_SYMBOLIC + ES_ICON_ALIGN_HORIZONTAL_RIGHT + ES_ICON_ALIGN_HORIZONTAL_RIGHT_SYMBOLIC + ES_ICON_ALIGN_HORIZONTAL_RIGHT_TO_ANCHOR + ES_ICON_ALIGN_HORIZONTAL_RIGHT_TO_ANCHOR_SYMBOLIC + ES_ICON_ALIGN_VERTICAL_BOTTOM + ES_ICON_ALIGN_VERTICAL_BOTTOM_SYMBOLIC + ES_ICON_ALIGN_VERTICAL_BOTTOM_TO_ANCHOR + ES_ICON_ALIGN_VERTICAL_BOTTOM_TO_ANCHOR_SYMBOLIC + ES_ICON_ALIGN_VERTICAL_CENTER + ES_ICON_ALIGN_VERTICAL_CENTER_SYMBOLIC + ES_ICON_ALIGN_VERTICAL_TOP + ES_ICON_ALIGN_VERTICAL_TOP_SYMBOLIC + ES_ICON_ALIGN_VERTICAL_TOP_TO_ANCHOR + ES_ICON_ALIGN_VERTICAL_TOP_TO_ANCHOR_SYMBOLIC + ES_ICON_APPLICATION_ADD_SYMBOLIC + ES_ICON_APPOINTMENT_NEW + ES_ICON_APPOINTMENT_NEW_SYMBOLIC + ES_ICON_APPOINTMENT_SYMBOLIC + ES_ICON_BOOKMARK_NEW + ES_ICON_BOOKMARK_NEW_SYMBOLIC + ES_ICON_BROWSER_DOWNLOAD + ES_ICON_BROWSER_DOWNLOAD_SYMBOLIC + ES_ICON_CALL_START + ES_ICON_CALL_START_SYMBOLIC + ES_ICON_CALL_STOP + ES_ICON_CALL_STOP_SYMBOLIC + ES_ICON_COLOR_FILL + ES_ICON_COLOR_GRADIENT + ES_ICON_COLOR_GRADIENT_MESH + ES_ICON_COLOR_SELECT_SYMBOLIC + ES_ICON_CONTACT_NEW + ES_ICON_CONTACT_NEW_SYMBOLIC + ES_ICON_DISTRIBUTE_HORIZONTAL_CENTER + ES_ICON_DISTRIBUTE_HORIZONTAL_GAPS + ES_ICON_DISTRIBUTE_HORIZONTAL_LEFT + ES_ICON_DISTRIBUTE_HORIZONTAL_RIGHT + ES_ICON_DISTRIBUTE_VERTICAL_BOTTOM + ES_ICON_DISTRIBUTE_VERTICAL_CENTER + ES_ICON_DISTRIBUTE_VERTICAL_GAPS + ES_ICON_DISTRIBUTE_VERTICAL_TOP + ES_ICON_DOCUMENT_EDIT + ES_ICON_DOCUMENT_EDIT_SYMBOLIC + ES_ICON_DOCUMENT_EXPORT + ES_ICON_DOCUMENT_EXPORT_SYMBOLIC + ES_ICON_DOCUMENT_IMPORT + ES_ICON_DOCUMENT_IMPORT_SYMBOLIC + ES_ICON_DOCUMENT_NEW + ES_ICON_DOCUMENT_NEW_SYMBOLIC + ES_ICON_DOCUMENT_OPEN_RECENT + ES_ICON_DOCUMENT_OPEN_RECENT_SYMBOLIC + ES_ICON_DOCUMENT_OPEN_SYMBOLIC + ES_ICON_DOCUMENT_PAGE_SETUP + ES_ICON_DOCUMENT_PAGE_SETUP_SYMBOLIC + ES_ICON_DOCUMENT_PRINT_PREVIEW + ES_ICON_DOCUMENT_PRINT_PREVIEW_SYMBOLIC + ES_ICON_DOCUMENT_PRINT_SYMBOLIC + ES_ICON_DOCUMENT_PROPERTIES + ES_ICON_DOCUMENT_PROPERTIES_SYMBOLIC + ES_ICON_DOCUMENT_REVERT + ES_ICON_DOCUMENT_REVERT_SYMBOLIC + ES_ICON_DOCUMENT_SAVE_AS + ES_ICON_DOCUMENT_SAVE_AS_SYMBOLIC + ES_ICON_DOCUMENT_SAVE_SYMBOLIC + ES_ICON_DOCUMENT_SEND + ES_ICON_DOCUMENT_SEND_SYMBOLIC + ES_ICON_DRAW_CUBOID + ES_ICON_DRAW_ELLIPSE + ES_ICON_DRAW_ERASER + ES_ICON_DRAW_FREEHAND + ES_ICON_DRAW_PATH + ES_ICON_DRAW_POLYGON_STAR + ES_ICON_DRAW_RECTANGLE + ES_ICON_DRAW_SPIRAL + ES_ICON_DRAW_TEXT + ES_ICON_EDIT_CLEAR + ES_ICON_EDIT_CLEAR_ALL_SYMBOLIC + ES_ICON_EDIT_CLEAR_SYMBOLIC + ES_ICON_EDIT_COPY + ES_ICON_EDIT_COPY_SYMBOLIC + ES_ICON_EDIT_CUT + ES_ICON_EDIT_CUT_SYMBOLIC + ES_ICON_EDIT_DELETE_SYMBOLIC + ES_ICON_EDIT_FIND + ES_ICON_EDIT_FIND_REPLACE + ES_ICON_EDIT_FIND_REPLACE_SYMBOLIC + ES_ICON_EDIT_FIND_SYMBOLIC + ES_ICON_EDIT_FLAG + ES_ICON_EDIT_FLAG_SYMBOLIC + ES_ICON_EDIT_MARK + ES_ICON_EDIT_PASTE + ES_ICON_EDIT_PASTE_SYMBOLIC + ES_ICON_EDIT_REDO + ES_ICON_EDIT_REDO_SYMBOLIC + ES_ICON_EDIT_SELECT_ALL + ES_ICON_EDIT_SELECT_ALL_SYMBOLIC + ES_ICON_EDIT_SELECT_SYMBOLIC + ES_ICON_EDIT_UNDO + ES_ICON_EDIT_UNDO_ARCHIVE + ES_ICON_EDIT_UNDO_SYMBOLIC + ES_ICON_ERROR_CORRECT_SYMBOLIC + ES_ICON_EVENT_NEW + ES_ICON_FIND_LOCATION + ES_ICON_FIND_LOCATION_SYMBOLIC + ES_ICON_FOLDER_COPY + ES_ICON_FOLDER_MOVE + ES_ICON_FOLDER_NEW + ES_ICON_FOLDER_NEW_SYMBOLIC + ES_ICON_FONT_SELECT_SYMBOLIC + ES_ICON_FORMAT_INDENT_LESS + ES_ICON_FORMAT_INDENT_LESS_SYMBOLIC + ES_ICON_FORMAT_INDENT_MORE + ES_ICON_FORMAT_INDENT_MORE_SYMBOLIC + ES_ICON_FORMAT_JUSTIFY_CENTER + ES_ICON_FORMAT_JUSTIFY_CENTER_SYMBOLIC + ES_ICON_FORMAT_JUSTIFY_FILL + ES_ICON_FORMAT_JUSTIFY_FILL_SYMBOLIC + ES_ICON_FORMAT_JUSTIFY_LEFT + ES_ICON_FORMAT_JUSTIFY_LEFT_SYMBOLIC + ES_ICON_FORMAT_JUSTIFY_RIGHT + ES_ICON_FORMAT_JUSTIFY_RIGHT_SYMBOLIC + ES_ICON_FORMAT_TEXT_BOLD + ES_ICON_FORMAT_TEXT_BOLD_ES_SYMBOLIC + ES_ICON_FORMAT_TEXT_BOLD_FR_SYMBOLIC + ES_ICON_FORMAT_TEXT_BOLD_SYMBOLIC + ES_ICON_FORMAT_TEXT_CLEAR_FORMATTING_SYMBOLIC + ES_ICON_FORMAT_TEXT_DIRECTION_LTR_SYMBOLIC + ES_ICON_FORMAT_TEXT_HIGHLIGHT + ES_ICON_FORMAT_TEXT_ITALIC + ES_ICON_FORMAT_TEXT_ITALIC_ES_SYMBOLIC + ES_ICON_FORMAT_TEXT_ITALIC_SYMBOLIC + ES_ICON_FORMAT_TEXT_LARGER_SYMBOLIC + ES_ICON_FORMAT_TEXT_NONE + ES_ICON_FORMAT_TEXT_SMALLER_SYMBOLIC + ES_ICON_FORMAT_TEXT_STRIKETHROUGH + ES_ICON_FORMAT_TEXT_STRIKETHROUGH_FR_SYMBOLIC + ES_ICON_FORMAT_TEXT_STRIKETHROUGH_SYMBOLIC + ES_ICON_FORMAT_TEXT_UNDERLINE + ES_ICON_FORMAT_TEXT_UNDERLINE_FR_SYMBOLIC + ES_ICON_FORMAT_TEXT_UNDERLINE_SYMBOLIC + ES_ICON_GO_BOTTOM + ES_ICON_GO_BOTTOM_SYMBOLIC + ES_ICON_GO_DOWN + ES_ICON_GO_DOWN_SYMBOLIC + ES_ICON_GO_FIRST + ES_ICON_GO_FIRST_SYMBOLIC + ES_ICON_GO_HOME_SYMBOLIC + ES_ICON_GO_JUMP + ES_ICON_GO_JUMP_SYMBOLIC + ES_ICON_GO_LAST + ES_ICON_GO_LAST_SYMBOLIC + ES_ICON_GO_NEXT + ES_ICON_GO_NEXT_SYMBOLIC + ES_ICON_GO_PREVIOUS + ES_ICON_GO_PREVIOUS_SYMBOLIC + ES_ICON_GO_TOP + ES_ICON_GO_TOP_SYMBOLIC + ES_ICON_GO_UP + ES_ICON_GO_UP_SYMBOLIC + ES_ICON_HELP_ABOUT + ES_ICON_HELP_ABOUT_SYMBOLIC + ES_ICON_HELP_CONTENTS + ES_ICON_HELP_CONTENTS_SYMBOLIC + ES_ICON_HELP_INFO_SYMBOLIC + ES_ICON_IMAGE_ADJUST + ES_ICON_IMAGE_AUTO_ADJUST + ES_ICON_IMAGE_CROP + ES_ICON_IMAGE_CROP_SYMBOLIC + ES_ICON_IMAGE_RED_EYE + ES_ICON_IMAGE_RED_EYE_SYMBOLIC + ES_ICON_INSERT_IMAGE + ES_ICON_INSERT_IMAGE_SYMBOLIC + ES_ICON_INSERT_LINK + ES_ICON_INSERT_LINK_SYMBOLIC + ES_ICON_INSERT_OBJECT + ES_ICON_INSERT_OBJECT_SYMBOLIC + ES_ICON_INSERT_TEXT_SYMBOLIC + ES_ICON_LIST_ADD + ES_ICON_LIST_ADD_SYMBOLIC + ES_ICON_LIST_REMOVE + ES_ICON_LIST_REMOVE_SYMBOLIC + ES_ICON_MAIL_ARCHIVE + ES_ICON_MAIL_FORWARD + ES_ICON_MAIL_FORWARD_SYMBOLIC + ES_ICON_MAIL_MARK_IMPORTANT + ES_ICON_MAIL_MARK_IMPORTANT_SYMBOLIC + ES_ICON_MAIL_MARK_JUNK + ES_ICON_MAIL_MARK_JUNK_SYMBOLIC + ES_ICON_MAIL_MARK_NOTJUNK + ES_ICON_MAIL_MARK_NOTJUNK_SYMBOLIC + ES_ICON_MAIL_MESSAGE_NEW + ES_ICON_MAIL_MESSAGE_NEW_SYMBOLIC + ES_ICON_MAIL_MOVE + ES_ICON_MAIL_MOVE_SYMBOLIC + ES_ICON_MAIL_REPLY_ALL + ES_ICON_MAIL_REPLY_ALL_SYMBOLIC + ES_ICON_MAIL_REPLY_SENDER + ES_ICON_MAIL_REPLY_SENDER_SYMBOLIC + ES_ICON_MAIL_SEND + ES_ICON_MAIL_SEND_RECEIVE_SYMBOLIC + ES_ICON_MAIL_SEND_SYMBOLIC + ES_ICON_MARK_LOCATION_SYMBOLIC + ES_ICON_MEDIA_EJECT + ES_ICON_MEDIA_EJECT_SYMBOLIC + ES_ICON_MEDIA_EQ_SYMBOLIC + ES_ICON_MEDIA_PLAYBACK_PAUSE + ES_ICON_MEDIA_PLAYBACK_PAUSE_SYMBOLIC + ES_ICON_MEDIA_PLAYBACK_START + ES_ICON_MEDIA_PLAYBACK_START_SYMBOLIC + ES_ICON_MEDIA_PLAYBACK_STOP + ES_ICON_MEDIA_PLAYBACK_STOP_SYMBOLIC + ES_ICON_MEDIA_RECORD + ES_ICON_MEDIA_RECORD_SYMBOLIC + ES_ICON_MEDIA_SEEK_BACKWARD + ES_ICON_MEDIA_SEEK_BACKWARD_SYMBOLIC + ES_ICON_MEDIA_SEEK_FORWARD + ES_ICON_MEDIA_SEEK_FORWARD_SYMBOLIC + ES_ICON_MEDIA_SKIP_BACKWARD + ES_ICON_MEDIA_SKIP_FORWARD + ES_ICON_MEDIA_VIEW_SUBTITLES_SYMBOLIC + ES_ICON_NODE_ADD + ES_ICON_NODE_ALIGN_HORIZONTAL + ES_ICON_NODE_ALIGN_VERTICAL + ES_ICON_NODE_BREAK + ES_ICON_NODE_CUSP + ES_ICON_NODE_DELETE + ES_ICON_NODE_DELETE_SEGMENT + ES_ICON_NODE_DISTRIBUTE_HORIZONTAL + ES_ICON_NODE_DISTRIBUTE_VERTICAL + ES_ICON_NODE_INSERT + ES_ICON_NODE_JOIN + ES_ICON_NODE_JOIN_SEGMENT + ES_ICON_NODE_SMOOTH + ES_ICON_NODE_SYMMETRIC + ES_ICON_OBJECT_FLIP_HORIZONTAL + ES_ICON_OBJECT_FLIP_HORIZONTAL_SYMBOLIC + ES_ICON_OBJECT_FLIP_VERTICAL + ES_ICON_OBJECT_FLIP_VERTICAL_SYMBOLIC + ES_ICON_OBJECT_GROUP + ES_ICON_OBJECT_GROUP_SYMBOLIC + ES_ICON_OBJECT_INVERSE + ES_ICON_OBJECT_INVERSE_SYMBOLIC + ES_ICON_OBJECT_MERGE + ES_ICON_OBJECT_ROTATE_LEFT + ES_ICON_OBJECT_ROTATE_LEFT_SYMBOLIC + ES_ICON_OBJECT_ROTATE_RIGHT + ES_ICON_OBJECT_ROTATE_RIGHT_SYMBOLIC + ES_ICON_OBJECT_SELECT_SYMBOLIC + ES_ICON_OBJECT_STRAIGHTEN + ES_ICON_OBJECT_TO_PATH + ES_ICON_OBJECT_UNGROUP + ES_ICON_OBJECT_UNGROUP_SYMBOLIC + ES_ICON_OPEN_MENU + ES_ICON_OPEN_MENU_SYMBOLIC + ES_ICON_PAN_DOWN_SYMBOLIC + ES_ICON_PANE_HIDE_SYMBOLIC + ES_ICON_PAN_END_SYMBOLIC + ES_ICON_PANE_SHOW_SYMBOLIC + ES_ICON_PAN_START_SYMBOLIC + ES_ICON_PAN_UP_SYMBOLIC + ES_ICON_PATH_BREAK_APART + ES_ICON_PATH_BREAK_APART_SYMBOLIC + ES_ICON_PATH_COMBINE + ES_ICON_PATH_COMBINE_SYMBOLIC + ES_ICON_PATH_DIFFERENCE + ES_ICON_PATH_DIFFERENCE_SYMBOLIC + ES_ICON_PATH_DIVISION + ES_ICON_PATH_DIVISION_SYMBOLIC + ES_ICON_PATH_EXCLUSION + ES_ICON_PATH_EXCLUSION_SYMBOLIC + ES_ICON_PATH_INTERSECTION + ES_ICON_PATH_INTERSECTION_SYMBOLIC + ES_ICON_PATH_UNION + ES_ICON_PATH_UNION_SYMBOLIC + ES_ICON_PROCESS_STOP + ES_ICON_PROCESS_STOP_SYMBOLIC + ES_ICON_SEGMENT_CURVE + ES_ICON_SEGMENT_LINE + ES_ICON_SELECTION_ADD + ES_ICON_SELECTION_BOTTOM + ES_ICON_SELECTION_BOTTOM_SYMBOLIC + ES_ICON_SELECTION_CHECKED + ES_ICON_SELECTION_END_SYMBOLIC + ES_ICON_SELECTION_LOWER + ES_ICON_SELECTION_LOWER_SYMBOLIC + ES_ICON_SELECTION_RAISE + ES_ICON_SELECTION_RAISE_SYMBOLIC + ES_ICON_SELECTION_REMOVE + ES_ICON_SELECTION_START_SYMBOLIC + ES_ICON_SELECTION_TOP + ES_ICON_SELECTION_TOP_SYMBOLIC + ES_ICON_SEND_TO + ES_ICON_SEND_TO_SYMBOLIC + ES_ICON_STAR_NEW_SYMBOLIC + ES_ICON_STROKE_TO_PATH + ES_ICON_SYSTEM_LOCK_SCREEN + ES_ICON_SYSTEM_LOCK_SCREEN_SYMBOLIC + ES_ICON_SYSTEM_LOG_OUT + ES_ICON_SYSTEM_REBOOT + ES_ICON_SYSTEM_RUN + ES_ICON_SYSTEM_RUN_SYMBOLIC + ES_ICON_SYSTEM_SHUTDOWN + ES_ICON_SYSTEM_SHUTDOWN_SYMBOLIC + ES_ICON_SYSTEM_SUSPEND + ES_ICON_TAB_NEW_SYMBOLIC + ES_ICON_TAG_NEW + ES_ICON_TAG_NEW_SYMBOLIC + ES_ICON_TOOL_MEASURE + ES_ICON_TOOL_NODE_EDITOR + ES_ICON_TOOLS_CHECK_SPELLING_SYMBOLIC + ES_ICON_TOOLS_TIMER_SYMBOLIC + ES_ICON_VIEW_COLUMN_SYMBOLIC + ES_ICON_VIEW_CONTINUOUS_SYMBOLIC + ES_ICON_VIEW_DUAL_SYMBOLIC + ES_ICON_VIEW_FILTER_SYMBOLIC + ES_ICON_VIEW_FULLSCREEN_SYMBOLIC + ES_ICON_VIEW_GRID_SYMBOLIC + ES_ICON_VIEW_LIST_COMPACT_SYMBOLIC + ES_ICON_VIEW_LIST_IMAGES_SYMBOLIC + ES_ICON_VIEW_LIST_SYMBOLIC + ES_ICON_VIEW_LIST_VIDEO_SYMBOLIC + ES_ICON_VIEW_MORE_HORIZONTAL_SYMBOLIC + ES_ICON_VIEW_MORE_SYMBOLIC + ES_ICON_VIEW_PAGED_SYMBOLIC + ES_ICON_VIEW_PIN_SYMBOLIC + ES_ICON_VIEW_READER + ES_ICON_VIEW_READER_SYMBOLIC + ES_ICON_VIEW_REFRESH + ES_ICON_VIEW_REFRESH_SYMBOLIC + ES_ICON_VIEW_RESTORE_SYMBOLIC + ES_ICON_VIEW_SORT_ASCENDING_SYMBOLIC + ES_ICON_VIEW_SORT_DESCENDING_SYMBOLIC + ES_ICON_WINDOW_CLOSE + ES_ICON_WINDOW_CLOSE_SYMBOLIC + ES_ICON_WINDOW_MAXIMIZE_SYMBOLIC + ES_ICON_WINDOW_MINIMIZE_SYMBOLIC + ES_ICON_WINDOW_NEW + ES_ICON_WINDOW_NEW_SYMBOLIC + ES_ICON_WINDOW_POP_OUT_SYMBOLIC + ES_ICON_WINDOW_RESTORE_SYMBOLIC + ES_ICON_ZOOM_FIT_BEST + ES_ICON_ZOOM_FIT_BEST_SYMBOLIC + ES_ICON_ZOOM_IN + ES_ICON_ZOOM_IN_SYMBOLIC + ES_ICON_ZOOM_ORIGINAL + ES_ICON_ZOOM_ORIGINAL_SYMBOLIC + ES_ICON_ZOOM_OUT + ES_ICON_ZOOM_OUT_SYMBOLIC + ES_ICON_ACCESSORIES_CALCULATOR + ES_ICON_ACCESSORIES_CALCULATOR_SYMBOLIC + ES_ICON_ACCESSORIES_SCREENSHOT + ES_ICON_ACCESSORIES_TEXT_EDITOR + ES_ICON_ACCESSORIES_TEXT_EDITOR_SYMBOLIC + ES_ICON_APPLICATION_DEFAULT_ICON + ES_ICON_ARCHIVE_MANAGER + ES_ICON_INTERNET_CHAT + ES_ICON_INTERNET_CHAT_SYMBOLIC + ES_ICON_INTERNET_MAIL + ES_ICON_INTERNET_MAIL_SYMBOLIC + ES_ICON_INTERNET_NEWS_READER + ES_ICON_INTERNET_NEWS_READER_SYMBOLIC + ES_ICON_INTERNET_WEB_BROWSER + ES_ICON_INTERNET_WEB_BROWSER_SYMBOLIC + ES_ICON_MULTIMEDIA_AUDIO_PLAYER + ES_ICON_MULTIMEDIA_PHOTO_MANAGER + ES_ICON_MULTIMEDIA_VIDEO_PLAYER + ES_ICON_OFFICE_ADDRESS_BOOK + ES_ICON_OFFICE_CALENDAR + ES_ICON_OFFICE_CALENDAR_SYMBOLIC + ES_ICON_ONBOARD + ES_ICON_POSTSCRIPT_VIEWER + ES_ICON_PREFERENCES_DESKTOP + ES_ICON_PREFERENCES_DESKTOP_FONT + ES_ICON_SYSTEM_FILE_MANAGER + ES_ICON_SYSTEM_OS_INSTALLER + ES_ICON_SYSTEM_SOFTWARE_INSTALL + ES_ICON_SYSTEM_SOFTWARE_INSTALL_SYMBOLIC + ES_ICON_SYSTEM_SOFTWARE_UPDATE + ES_ICON_SYSTEM_SOFTWARE_UPDATE_SYMBOLIC + ES_ICON_SYSTEM_USERS + ES_ICON_SYSTEM_USERS_SYMBOLIC + ES_ICON_UTILITIES_SYSTEM_MONITOR + ES_ICON_UTILITIES_TERMINAL + ES_ICON_UTILITIES_TERMINAL_SYMBOLIC + ES_ICON_APPLICATIONS_ACCESSORIES + ES_ICON_APPLICATIONS_AUDIO_SYMBOLIC + ES_ICON_APPLICATIONS_DEVELOPMENT + ES_ICON_APPLICATIONS_DEVELOPMENT_SYMBOLIC + ES_ICON_APPLICATIONS_EDUCATION + ES_ICON_APPLICATIONS_EDUCATION_SYMBOLIC + ES_ICON_APPLICATIONS_ENGINEERING_SYMBOLIC + ES_ICON_APPLICATIONS_FONTS + ES_ICON_APPLICATIONS_GAMES + ES_ICON_APPLICATIONS_GAMES_SYMBOLIC + ES_ICON_APPLICATIONS_GRAPHICS + ES_ICON_APPLICATIONS_GRAPHICS_SYMBOLIC + ES_ICON_APPLICATIONS_INTERFACEDESIGN + ES_ICON_APPLICATIONS_INTERNET_SYMBOLIC + ES_ICON_APPLICATIONS_MULTIMEDIA + ES_ICON_APPLICATIONS_MULTIMEDIA_SYMBOLIC + ES_ICON_APPLICATIONS_OFFICE + ES_ICON_APPLICATIONS_OFFICE_SYMBOLIC + ES_ICON_APPLICATIONS_OTHER + ES_ICON_APPLICATIONS_OTHER_SYMBOLIC + ES_ICON_APPLICATIONS_PHOTOGRAPHY + ES_ICON_APPLICATIONS_SCIENCE + ES_ICON_APPLICATIONS_SCIENCE_SYMBOLIC + ES_ICON_APPLICATIONS_UTILITIES + ES_ICON_APPLICATIONS_UTILITIES_SYMBOLIC + ES_ICON_APPLICATIONS_VIDEO_SYMBOLIC + ES_ICON_BUG + ES_ICON_BUG_SYMBOLIC + ES_ICON_EMOJI_ACTIVITY_SYMBOLIC + ES_ICON_EMOJI_BODY_SYMBOLIC + ES_ICON_EMOJI_FOOD_SYMBOLIC + ES_ICON_EMOJI_NATURE_SYMBOLIC + ES_ICON_EMOJI_OBJECTS_SYMBOLIC + ES_ICON_EMOJI_TRAVEL_SYMBOLIC + ES_ICON_EVENT_BIRTHDAY_SYMBOLIC + ES_ICON_PREFERENCES_BLUETOOTH_SYMBOLIC + ES_ICON_PREFERENCES_COLOR + ES_ICON_PREFERENCES_COLOR_SYMBOLIC + ES_ICON_PREFERENCES_DESKTOP_ACCESSIBILITY + ES_ICON_PREFERENCES_DESKTOP_ACCESSIBILITY_POINTING + ES_ICON_PREFERENCES_DESKTOP_ACCESSIBILITY_SYMBOLIC + ES_ICON_PREFERENCES_DESKTOP_ACCESSIBILITY_ZOOM + ES_ICON_PREFERENCES_DESKTOP_APPLICATIONS + ES_ICON_PREFERENCES_DESKTOP_DISPLAY + ES_ICON_PREFERENCES_DESKTOP_DISPLAY_SYMBOLIC + ES_ICON_PREFERENCES_DESKTOP_KEYBOARD + ES_ICON_PREFERENCES_DESKTOP_KEYBOARD_SYMBOLIC + ES_ICON_PREFERENCES_DESKTOP_LOCALE + ES_ICON_PREFERENCES_DESKTOP_LOCALE_SYMBOLIC + ES_ICON_PREFERENCES_DESKTOP_ONLINE_ACCOUNTS + ES_ICON_PREFERENCES_DESKTOP_ONLINE_ACCOUNTS_SYMBOLIC + ES_ICON_PREFERENCES_DESKTOP_PERIPHERALS + ES_ICON_PREFERENCES_DESKTOP_SOUND + ES_ICON_PREFERENCES_DESKTOP_WALLPAPER + ES_ICON_PREFERENCES_DESKTOP_WORKSPACES + ES_ICON_PREFERENCES_OTHER_SYMBOLIC + ES_ICON_PREFERENCES_SYSTEM + ES_ICON_PREFERENCES_SYSTEM_NETWORK + ES_ICON_PREFERENCES_SYSTEM_NETWORK_SYMBOLIC + ES_ICON_PREFERENCES_SYSTEM_NOTIFICATIONS + ES_ICON_PREFERENCES_SYSTEM_PARENTAL_CONTROLS + ES_ICON_PREFERENCES_SYSTEM_PARENTAL_CONTROL_SYMBOLIC + ES_ICON_PREFERENCES_SYSTEM_POWER + ES_ICON_PREFERENCES_SYSTEM_POWER_SYMBOLIC + ES_ICON_PREFERENCES_SYSTEM_PRIVACY_HOUSEKEEPING + ES_ICON_PREFERENCES_SYSTEM_SHARING + ES_ICON_PREFERENCES_SYSTEM_SHARING_SYMBOLIC + ES_ICON_PREFERENCES_SYSTEM_TIME + ES_ICON_PREFERENCES_SYSTEM_TIME_SYMBOLIC + ES_ICON_PREFERENCES_SYSTEM_WINDOWS + ES_ICON_AC_ADAPTER_SYMBOLIC + ES_ICON_AUDIO_CARD_SYMBOLIC + ES_ICON_AUDIO_HEADPHONES + ES_ICON_AUDIO_HEADPHONES_SYMBOLIC + ES_ICON_AUDIO_HEADSETS + ES_ICON_AUDIO_HEADSET_SYMBOLIC + ES_ICON_AUDIO_INPUT_MICROPHONE + ES_ICON_AUDIO_INPUT_MICROPHONE_SYMBOLIC + ES_ICON_AUDIO_SPEAKER_CENTER + ES_ICON_AUDIO_SPEAKER_CENTER_BACK + ES_ICON_AUDIO_SPEAKER_CENTER_BACK_TESTING + ES_ICON_AUDIO_SPEAKER_CENTER_TESTING + ES_ICON_AUDIO_SPEAKER_LEFT + ES_ICON_AUDIO_SPEAKER_LEFT_BACK + ES_ICON_AUDIO_SPEAKER_LEFT_BACK_TESTING + ES_ICON_AUDIO_SPEAKER_LEFT_SIDE + ES_ICON_AUDIO_SPEAKER_LEFT_SIDE_TESTING + ES_ICON_AUDIO_SPEAKER_LEFT_TESTING + ES_ICON_AUDIO_SPEAKER_RIGHT + ES_ICON_AUDIO_SPEAKER_RIGHT_BACK + ES_ICON_AUDIO_SPEAKER_RIGHT_BACK_TESTING + ES_ICON_AUDIO_SPEAKER_RIGHT_SIDE + ES_ICON_AUDIO_SPEAKER_RIGHT_SIDE_TESTING + ES_ICON_AUDIO_SPEAKER_RIGHT_TESTING + ES_ICON_AUDIO_SPEAKERS + ES_ICON_AUDIO_SPEAKERS_SYMBOLIC + ES_ICON_AUDIO_SUBWOOFER + ES_ICON_AUDIO_SUBWOOFER_TESTING + ES_ICON_BATTERY + ES_ICON_BATTERY_SYMBOLIC + ES_ICON_BLUETOOTH + ES_ICON_BLUETOOTH_SYMBOLIC + ES_ICON_CAMERA_PHOTO + ES_ICON_CAMERA_PHOTO_SYMBOLIC + ES_ICON_CAMERA_VIDEO + ES_ICON_CAMERA_VIDEO_SYMBOLIC + ES_ICON_CAMERA_WEB + ES_ICON_CAMERA_WEB_SYMBOLIC + ES_ICON_COLORIMETER_COLORHUG_SYMBOLIC + ES_ICON_COMPUTER_LAPTOP + ES_ICON_COMPUTER_LAPTOP_SYMBOLIC + ES_ICON_DISPLAY_PROJECTOR_SYMBOLIC + ES_ICON_DRIVE_HARDDISK + ES_ICON_DRIVE_HARDDISK_IEEE1394_SYMBOLIC + ES_ICON_DRIVE_HARDDISK_SOLIDSTATE + ES_ICON_DRIVE_HARDDISK_SOLIDSTATE_SYMBOLIC + ES_ICON_DRIVE_HARDDISK_SYMBOLIC + ES_ICON_DRIVE_MULTIDISK_SYMBOLIC + ES_ICON_DRIVE_OPTICAL_SYMBOLIC + ES_ICON_DRIVE_REMOVABLE_MEDIA + ES_ICON_DRIVE_REMOVABLE_MEDIA_SYMBOLIC + ES_ICON_DRIVE_REMOVABLE_MEDIA_USB + ES_ICON_FINGERPRINT + ES_ICON_FINGERPRINT_SYMBOLIC + ES_ICON_GNOME_DEV_PRINTER_NEW + ES_ICON_INPUT_DIALPAD_SYMBOLIC + ES_ICON_INPUT_GAMING + ES_ICON_INPUT_GAMING_SYMBOLIC + ES_ICON_INPUT_KEYBOARD + ES_ICON_INPUT_KEYBOARD_SYMBOLIC + ES_ICON_INPUT_MOUSE + ES_ICON_INPUT_MOUSE_SYMBOLIC + ES_ICON_INPUT_TABLET + ES_ICON_INPUT_TABLET_SYMBOLIC + ES_ICON_INPUT_TOUCHPAD + ES_ICON_INPUT_TOUCHPAD_SYMBOLIC + ES_ICON_MEDIA_FLASH_CF + ES_ICON_MEDIA_FLASH_MS + ES_ICON_MEDIA_FLASH_SYMBOLIC + ES_ICON_MEDIA_FLOPPY_SYMBOLIC + ES_ICON_MEDIA_MEMORY + ES_ICON_MEDIA_MEMORY_SD + ES_ICON_MEDIA_MEMORY_SEMBOLIC + ES_ICON_MEDIA_MEMORY_SM + ES_ICON_MEDIA_OPTICAL + ES_ICON_MEDIA_OPTICAL_SYMBOLIC + ES_ICON_MEDIA_REMOVABLE_SYMBOLIC + ES_ICON_MEDIA_TAPE_SYMBOLIC + ES_ICON_MEDIA_ZIP_SYMBOLIC + ES_ICON_MODEM + ES_ICON_MODEM_SYMBOLIC + ES_ICON_MULTIMEDIA_PLAYER + ES_ICON_MULTIMEDIA_PLAYER_SYMBOLIC + ES_ICON_NETWORK_CELLULAR + ES_ICON_NETWORK_FIREWALL + ES_ICON_NETWORK_VPN + ES_ICON_NETWORK_WIRED + ES_ICON_NETWORK_WIRELESS + ES_ICON_NETWORK_WIRELESS_HOTSPOT + ES_ICON_NM_DEVICE_WWAN + ES_ICON_PDA_SYMBOLIC + ES_ICON_PHONE + ES_ICON_PHONE_SYMBOLIC + ES_ICON_PRINTER + ES_ICON_PRINTER_NETWORK + ES_ICON_PRINTER_SYMBOLIC + ES_ICON_SCANNER + ES_ICON_SCANNER_SYMBOLIC + ES_ICON_TABLET + ES_ICON_TABLET_SYMBOLIC + ES_ICON_TV_SYMBOLIC + ES_ICON_UNINTERRUPTIBLE_POWER_SUPPLY + ES_ICON_UNINTERRUPTIBLE_POWER_SUPPLY_SYMBOLIC + ES_ICON_VIDEO_DISPLAY + ES_ICON_VIDEO_DISPLAY_SYMBOLIC + ES_ICON_EMBLEM_DEFAULT_SYMBOLIC + ES_ICON_EMBLEM_DOCUMENTS_SYMBOLIC + ES_ICON_EMBLEM_FAVORITE_SYMBOLIC + ES_ICON_EMBLEM_IMPORTANT_SYMBOLIC + ES_ICON_EMBLEM_MUSIC_SYMBOLIC + ES_ICON_EMBLEM_OK_SYMBOLIC + ES_ICON_EMBLEM_PHOTOS_SYMBOLIC + ES_ICON_EMBLEM_READONLY + ES_ICON_EMBLEM_SHARED_SYMBOLIC + ES_ICON_EMBLEM_SYMBOLIC_LINK + ES_ICON_EMBLEM_SYNCHRONIZED + ES_ICON_EMBLEM_SYNCHRONIZING_SYMBOLIC + ES_ICON_EMBLEM_UNREADABLE + ES_ICON_EMBLEM_VIDEOS_SYMBOLIC + ES_ICON_FACE_ANGEL + ES_ICON_FACE_ANGEL_SYMBOLIC + ES_ICON_FACE_ANGRY + ES_ICON_FACE_ANGRY_SYMBOLIC + ES_ICON_FACE_COOL + ES_ICON_FACE_COOL_SYMBOLIC + ES_ICON_FACE_CRYING + ES_ICON_FACE_CRYING_SYMBOLIC + ES_ICON_FACE_DEVILISH + ES_ICON_FACE_DEVILISH_SYMBOLIC + ES_ICON_FACE_EMBARRASSED + ES_ICON_FACE_EMBARRASSED_SYMBOLIC + ES_ICON_FACE_HEART + ES_ICON_FACE_HEART_BROKEN + ES_ICON_FACE_HEART_BROKEN_SYMBOLIC + ES_ICON_FACE_HEART_SYMBOLIC + ES_ICON_FACE_KISS + ES_ICON_FACE_KISS_SYMBOLIC + ES_ICON_FACE_LAUGH + ES_ICON_FACE_LAUGH_SYMBOLIC + ES_ICON_FACE_MONKEY_SYMBOLIC + ES_ICON_FACE_PLAIN + ES_ICON_FACE_PLAIN_SYMBOLIC + ES_ICON_FACE_RASPBERRY + ES_ICON_FACE_RASPBERRY_SYMBOLIC + ES_ICON_FACE_SAD + ES_ICON_FACE_SAD_SYMBOLIC + ES_ICON_FACE_SICK + ES_ICON_FACE_SICK_SYMBOLIC + ES_ICON_FACE_SMILE + ES_ICON_FACE_SMILE_BIG + ES_ICON_FACE_SMILE_BIG_SYMBOLIC + ES_ICON_FACE_SMILE_SYMBOLIC + ES_ICON_FACE_SMIRK + ES_ICON_FACE_SMIRK_SYMBOLIC + ES_ICON_FACE_SURPRISE + ES_ICON_FACE_SURPRISE_SYMBOLIC + ES_ICON_FACE_TIRED + ES_ICON_FACE_TIRED_SYMBOLIC + ES_ICON_FACE_UNCERTAIN + ES_ICON_FACE_UNCERTAIN_SYMBOLIC + ES_ICON_FACE_WINK + ES_ICON_FACE_WINK_SYMBOLIC + ES_ICON_FACE_WORRIED + ES_ICON_FACE_WORRIED_SYMBOLIC + ES_ICON_APPLICATION_CERTIFICATE_SYMBOLIC + ES_ICON_APPLICATION_EPUB_ZIP + ES_ICON_APPLICATION_ILLUSTRATOR + ES_ICON_APPLICATION_JAVASCRIPT + ES_ICON_APPLICATION_MSWORD + ES_ICON_APPLICATION_OCTET_STREAM + ES_ICON_APPLICATION_PDF + ES_ICON_APPLICATION_PGP + ES_ICON_APPLICATION_RSS_XML_SYMBOLIC + ES_ICON_APPLICATION_VND + ES_ICON_APPLICATION_X_APPLIANCE_SYMBOLIC + ES_ICON_APPLICATION_X_BITTORRENT + ES_ICON_APPLICATION_X_CD_IMAGE + ES_ICON_APPLICATION_X_DESKTOP + ES_ICON_APPLICATION_X_EXECUTABLE_SYMBOLIC + ES_ICON_APPLICATION_X_FICTIONBOOK_XML + ES_ICON_APPLICATION_X_FIRMWARE + ES_ICON_APPLICATION_X_FIRMWARE_SYMBOLIC + ES_ICON_APPLICATION_X_FLASH_VIDEO + ES_ICON_APPLICATION_X_MS_DOS_EXECUTABLE + ES_ICON_APPLICATION_X_PARTIAL_DOWNLOAD + ES_ICON_APPLICATION_X_PHP + ES_ICON_APPLICATION_X_RUBY + ES_ICON_AUDIO_X_GENERIC + ES_ICON_AUDIO_X_GENERIC_SYMBOLIC + ES_ICON_AUDIO_X_PLAYLIST + ES_ICON_EXTENSION + ES_ICON_FONT_X_GENERIC + ES_ICON_FONT_X_GENERIC_SYMBOLIC + ES_ICON_IMAGE_VND + ES_ICON_IMAGE_X_GENERIC + ES_ICON_IMAGE_X_GENERIC_SYMBOLIC + ES_ICON_IMAGE_X_XCF + ES_ICON_INTERNET_FEED + ES_ICON_MODEL + ES_ICON_OFFICE_CONTACT + ES_ICON_OFFICE_DATABASE + ES_ICON_PACKAGE_X_GENERIC + ES_ICON_PACKAGE_X_GENERIC_SYMBOLIC + ES_ICON_PAYMENT_CARD + ES_ICON_PAYMENT_CARD_AMEX + ES_ICON_PAYMENT_CARD_DINERS_CLUB + ES_ICON_PAYMENT_CARD_DISCOVER + ES_ICON_PAYMENT_CARD_JCB + ES_ICON_PAYMENT_CARD_MASTERCARD + ES_ICON_PAYMENT_CARD_SYMBOLIC + ES_ICON_PAYMENT_CARD_UNIONPAY + ES_ICON_PAYMENT_CARD_VISA + ES_ICON_TEXT + ES_ICON_TEXT_CSS + ES_ICON_TEXT_HTML + ES_ICON_TEXT_HTML_SYMBOLIC + ES_ICON_TEXT_MARKDOWN + ES_ICON_TEXT_X_BIBTEX + ES_ICON_TEXT_X_CHANGELOG + ES_ICON_TEXT_X_CHDR + ES_ICON_TEXT_X_COPYING + ES_ICON_TEXT_X_COPYING_SYMBOLIC + ES_ICON_TEXT_X_CSRC + ES_ICON_TEXT_X_GENERIC_SYMBOLIC + ES_ICON_TEXT_X_GENERIC_TEMPLATE + ES_ICON_TEXT_X_GETTEXT_TRANSLATION + ES_ICON_TEXT_X_GETTEXT_TRANSLATION_TEMPLATE + ES_ICON_TEXT_X_GO + ES_ICON_TEXT_X_INSTALL + ES_ICON_TEXT_X_MAKEFILE + ES_ICON_TEXT_X_PREVIEW + ES_ICON_TEXT_X_PYTHON + ES_ICON_TEXT_X_README + ES_ICON_TEXT_X_SASS + ES_ICON_TEXT_X_SCRIPT + ES_ICON_TEXT_X_SSA + ES_ICON_TEXT_X_TEX + ES_ICON_TEXT_X_VALA + ES_ICON_UNKNOWN + ES_ICON_VIDEO_X_GENERIC + ES_ICON_VIDEO_X_GENERIC_SYMBOLIC + ES_ICON_X_OFFICE_ADDRESS_BOOK_SYMBOLIC + ES_ICON_X_OFFICE_DOCUMENT + ES_ICON_X_OFFICE_DOCUMENT_SYMBOLIC + ES_ICON_X_OFFICE_DOCUMENT_TEMPLATE + ES_ICON_X_OFFICE_DRAWING + ES_ICON_X_OFFICE_DRAWING_SYMBOLIC + ES_ICON_X_OFFICE_DRAWING_TEMPLATE + ES_ICON_X_OFFICE_PRESENTATION + ES_ICON_X_OFFICE_PRESENTATION_SYMBOLIC + ES_ICON_X_OFFICE_PRESENTATION_TEMPLATE + ES_ICON_X_OFFICE_SPREADSHEET + ES_ICON_X_OFFICE_SPREADSHEET_SYMBOLIC + ES_ICON_X_OFFICE_SPREADSHEET_TEMPLATE + ES_ICON_BOOKMARK_MISSING + ES_ICON_DISTRIBUTOR_LOGO + ES_ICON_DISTRIBUTOR_LOGO_SYMBOLIC + ES_ICON_FOLDER + ES_ICON_FOLDER_DOCUMENTS + ES_ICON_FOLDER_DOCUMENTS_OPEN + ES_ICON_FOLDER_DOCUMENTS_SYMBOLIC + ES_ICON_FOLDER_DOWNLOAD + ES_ICON_FOLDER_DOWNLOAD_OPEN + ES_ICON_FOLDER_DOWNLOAD_SYMBOLIC + ES_ICON_FOLDER_MUSIC + ES_ICON_FOLDER_MUSIC_OPEN + ES_ICON_FOLDER_MUSIC_SYMBOLIC + ES_ICON_FOLDER_OPEN + ES_ICON_FOLDER_PICTURES + ES_ICON_FOLDER_PICTURES_OPEN + ES_ICON_FOLDER_PICTURES_SYMBOLIC + ES_ICON_FOLDER_PUBLICSHARE + ES_ICON_FOLDER_PUBLICSHARE_OPEN + ES_ICON_FOLDER_PUBLICSHARE_SYMBOLIC + ES_ICON_FOLDER_RECENT + ES_ICON_FOLDER_RECENT_SYMBOLIC + ES_ICON_FOLDER_REMOTE + ES_ICON_FOLDER_REMOTE_OPEN + ES_ICON_FOLDER_SAVED_SEARCH + ES_ICON_FOLDER_SYMBOLIC + ES_ICON_FOLDER_TAG + ES_ICON_FOLDER_TEMPLATES + ES_ICON_FOLDER_TEMPLATES_OPEN + ES_ICON_FOLDER_TEMPLATES_SYMBOLIC + ES_ICON_FOLDER_VIDEOS + ES_ICON_FOLDER_VIDEOS_OPEN + ES_ICON_FOLDER_VIDEOS_SYMBOLIC + ES_ICON_INTERNET_RADIO + ES_ICON_INTERNET_RADIO_SYMBOLIC + ES_ICON_LIBRARY_AUDIOBOOK + ES_ICON_LIBRARY_PLACES + ES_ICON_LIBRARY_PODCAST + ES_ICON_MAIL_INBOX + ES_ICON_MAIL_INBOX_SYMBOLIC + ES_ICON_MAIL_MAILBOX + ES_ICON_MAIL_MAILBOX_SYMBOLIC + ES_ICON_MAIL_OUTBOX + ES_ICON_MAIL_OUTBOX_SYMBOLIC + ES_ICON_NETWORK_SERVER_SYMBOLIC + ES_ICON_PLAYLIST + ES_ICON_PLAYLIST_AUTOMATIC + ES_ICON_PLAYLIST_QUEUE + ES_ICON_PLAYLIST_QUEUE_SYMBOLIC + ES_ICON_PLAYLIST_SIMILAR + ES_ICON_PLAYLIST_SYMBOLIC + ES_ICON_TAG_SYMBOLIC + ES_ICON_USER_BOOKMARKS_SYMBOLIC + ES_ICON_USER_HOME + ES_ICON_USER_HOME_OPEN + ES_ICON_USER_HOME_SYMBOLIC + ES_ICON_USER_TRASH + ES_ICON_USER_TRASH_FULL + ES_ICON_USER_TRASH_SYMBOLIC + ES_ICON_AIRPLANE_MODE + ES_ICON_AIRPLANE_MODE_SYMBOLIC + ES_ICON_ALARM_SYMBOLIC + ES_ICON_APPOINTMENT_MISSED + ES_ICON_APPOINTMENT_MISSED_SYMBOLIC + ES_ICON_APPOINTMENT_SOON + ES_ICON_APPOINTMENT_SOON_SYMBOLIC + ES_ICON_AUDIO_VOLUME_HIGH_SYMBOLIC + ES_ICON_AUDIO_VOLUME_LOW_SYMBOLIC + ES_ICON_AUDIO_VOLUME_MEDIUM_SYMBOLIC + ES_ICON_AUDIO_VOLUME_MUTED_BLOCKING_SYMBOLIC + ES_ICON_AUDIO_VOLUME_MUTED_SYMBOLIC + ES_ICON_AVATAR_DEFAULT + ES_ICON_AVATAR_DEFAULT_SYMBOLIC + ES_ICON_BATTERY_AC_ADAPTER + ES_ICON_BATTERY_AC_ADAPTER_SYMBOLIC + ES_ICON_BATTERY_CAUTION + ES_ICON_BATTERY_CAUTION_CHARGING + ES_ICON_BATTERY_CAUTION_CHARGING_SYMBOLIC + ES_ICON_BATTERY_CAUTION_SYMBOLIC + ES_ICON_BATTERY_EMPTY + ES_ICON_BATTERY_EMPTY_CHARGING + ES_ICON_BATTERY_EMPTY_CHARGING_SYMBOLIC + ES_ICON_BATTERY_EMPTY_SYMBOLIC + ES_ICON_BATTERY_FULL + ES_ICON_BATTERY_FULL_CHARGED + ES_ICON_BATTERY_FULL_CHARGED_SYMBOLIC + ES_ICON_BATTERY_FULL_CHARGING + ES_ICON_BATTERY_FULL_CHARGING_SYMBOLIC + ES_ICON_BATTERY_FULL_SYMBOLIC + ES_ICON_BATTERY_GOOD + ES_ICON_BATTERY_GOOD_CHARGING + ES_ICON_BATTERY_GOOD_CHARGING_SYMBOLIC + ES_ICON_BATTERY_GOOD_SYMBOLIC + ES_ICON_BATTERY_LOW + ES_ICON_BATTERY_LOW_CHARGING + ES_ICON_BATTERY_LOW_CHARGING_SYMBOLIC + ES_ICON_BATTERY_LOW_SYMBOLIC + ES_ICON_BATTERY_MISSING + ES_ICON_BATTERY_MISSING_SYMBOLIC + ES_ICON_BLUETOOTH_ACTIVE_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED + ES_ICON_BLUETOOTH_DISABLED_10_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_20_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_30_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_40_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_50_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_60_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_70_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_80_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_90_SYMBOLIC + ES_ICON_BLUETOOTH_DISABLED_SYMBOLIC + ES_ICON_BLUETOOTH_PAIRED_SYMBOLIC + ES_ICON_CALL_MISSED_SYMBOLIC + ES_ICON_CHANGES_ALLOW + ES_ICON_CHANGES_ALLOW_SYMBOLIC + ES_ICON_CHANGES_PREVENT_SYMBOLIC + ES_ICON_CHANNEL_INSECURE_SYMBOLIC + ES_ICON_CHANNEL_SECURE_SYMBOLIC + ES_ICON_CHECK_ACTIVE_SYMBOLIC + ES_ICON_CHECKBOX_CHECKED_SYMBOLIC + ES_ICON_CHECKBOX_MIXED_SYMBOLIC + ES_ICON_CHECKBOX_SYMBOLIC + ES_ICON_CHECK_MIXED_SYMBOLIC + ES_ICON_COMPUTER_FAIL_SYMBOLIC + ES_ICON_CONTENT_LOADING_SYMBOLIC + ES_ICON_DAYTIME_SUNRISE_SYMBOLIC + ES_ICON_DAYTIME_SUNSET_SYMBOLIC + ES_ICON_DIALOG_ERROR + ES_ICON_DIALOG_ERROR_SYMBOLIC + ES_ICON_DIALOG_INFORMATION + ES_ICON_DIALOG_INFORMATION_SYMBOLIC + ES_ICON_DIALOG_PASSWORD + ES_ICON_DIALOG_PASSWORD_SYMBOLIC + ES_ICON_DIALOG_WARNING + ES_ICON_DIALOG_WARNING_SYMBOLIC + ES_ICON_DISPLAY_BRIGHTNESS_SYMBOLIC + ES_ICON_FOLDER_OPEN_SYMBOLIC + ES_ICON_FOLDER_VISITING_SYMBOLIC + ES_ICON_IMAGE_LOADING + ES_ICON_IMAGE_MISSING + ES_ICON_INPUT_KEYBOARD_CAPSLOCK_SYMBOLIC + ES_ICON_INPUT_KEYBOARD_NUMLOCK_SYMBOLIC + ES_ICON_KEYBOARD_BRIGHTNESS_SYMBOLIC + ES_ICON_LOCATION_ACTIVE_SYMBOLIC + ES_ICON_LOCATION_DISABLED_SYMBOLIC + ES_ICON_LOCATION_INACTIVE_SYMBOLIC + ES_ICON_LOCKED + ES_ICON_MAIL_ATTACHMENT_SYMBOLIC + ES_ICON_MAIL_FORWARDED_SYMBOLIC + ES_ICON_MAIL_IMPORTANT_SYMBOLIC + ES_ICON_MAIL_READ_SYMBOLIC + ES_ICON_MAIL_REPLIED_SYMBOLIC + ES_ICON_MAIL_UNREAD + ES_ICON_MAIL_UNREAD_SYMBOLIC + ES_ICON_MEDIA_PLAYLIST_CONSECUTIVE_SYMBOLIC + ES_ICON_MEDIA_PLAYLIST_NO_REPEAT_SYMBOLIC + ES_ICON_MEDIA_PLAYLIST_REPEAT + ES_ICON_MEDIA_PLAYLIST_REPEAT_SONG_SYMBOLIC + ES_ICON_MEDIA_PLAYLIST_REPEAT_SYMBOLIC + ES_ICON_MEDIA_PLAYLIST_SHUFFLE_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_HIGH_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_LOW_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MEDIUM_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_10_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_20_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_30_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_40_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_50_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_60_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_70_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_80_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_90_SYMBOLIC + ES_ICON_MICROPHONE_SENSITIVITY_MUTED_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_ACQUIRING_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_CONNECTED_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_NO_ROUTE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_OFFLINE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_EXCELLENT_SECURE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_EXCELLENT_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_GOOD_SECURE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_GOOD_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_NONE_SECURE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_NONE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_OK_SECURE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_OK_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_WEAK_SECURE_SYMBOLIC + ES_ICON_NETWORK_CELLULAR_SIGNAL_WEAK_SYMBOLIC + ES_ICON_NETWORK_ERROR + ES_ICON_NETWORK_ERROR_SYMBOLIC + ES_ICON_NETWORK_IDLE + ES_ICON_NETWORK_OFFLINE_SYMBOLIC + ES_ICON_NETWORK_VPN_ACQUIRING_SYMBOLIC + ES_ICON_NETWORK_VPN_LOCK_SYMBOLIC + ES_ICON_NETWORK_VPN_SYMBOLIC + ES_ICON_NETWORK_WIRED_ACQUIRING_SYMBOLIC + ES_ICON_NETWORK_WIRED_DISCONNECTED + ES_ICON_NETWORK_WIRED_NO_ROUTE_SYMBOLIC + ES_ICON_NETWORK_WIRED_OFFLINE_SYMBOLIC + ES_ICON_NETWORK_WIRED_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_ACQUIRING_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_CONNECTED_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_ENCRYPTED_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_HOTSPOT_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_NO_ROUTE_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_OFFLINE_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_EXCELLENT + ES_ICON_NETWORK_WIRELESS_SIGNAL_EXCELLENT_SECURE_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_EXCELLENT_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_GOOD + ES_ICON_NETWORK_WIRELESS_SIGNAL_GOOD_SECURE_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_GOOD_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_NONE_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_OK + ES_ICON_NETWORK_WIRELESS_SIGNAL_OK_SECURE_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_OK_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_WEAK + ES_ICON_NETWORK_WIRELESS_SIGNAL_WEAK_SECURE_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SIGNAL_WEAK_SYMBOLIC + ES_ICON_NETWORK_WIRELESS_SYMBOLIC + ES_ICON_NIGHT_LIGHT + ES_ICON_NIGHT_LIGHT_DISABLED_10_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_20_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_30_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_40_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_50_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_60_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_70_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_80_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_90_SYMBOLIC + ES_ICON_NIGHT_LIGHT_DISABLED_SYMBOLIC + ES_ICON_NIGHT_LIGHT_SYMBOLIC + ES_ICON_NM_NO_CONNECTION + ES_ICON_NM_SIGNAL_0 + ES_ICON_NM_SIGNAL_0_SECURE + ES_ICON_NM_SIGNAL_100 + ES_ICON_NM_SIGNAL_100_SECURE + ES_ICON_NM_SIGNAL_25 + ES_ICON_NM_SIGNAL_25_SECURE + ES_ICON_NM_SIGNAL_50 + ES_ICON_NM_SIGNAL_50_SECURE + ES_ICON_NM_SIGNAL_75 + ES_ICON_NM_SIGNAL_75_SECURE + ES_ICON_NM_VPN_ACTIVE_LOCK + ES_ICON_NM_VPN_LOCK + ES_ICON_NON_STARRED + ES_ICON_NON_STARRED_SYMBOLIC + ES_ICON_NOTIFICATION_AUDIO_VOLUME_HIGH + ES_ICON_NOTIFICATION_AUDIO_VOLUME_LOW + ES_ICON_NOTIFICATION_AUDIO_VOLUME_MEDIUM + ES_ICON_NOTIFICATION_AUDIO_VOLUME_MUTED + ES_ICON_NOTIFICATION_DEVICE_EJECT + ES_ICON_NOTIFICATION_DISABLED + ES_ICON_NOTIFICATION_DISABLED_10_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_20_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_30_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_40_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_50_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_60_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_70_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_80_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_90_SYMBOLIC + ES_ICON_NOTIFICATION_DISABLED_SYMBOLIC + ES_ICON_NOTIFICATION_DISPLAY_BRIGHTNESS + ES_ICON_NOTIFICATION_KEYBOARD_BRIGHTNESS + ES_ICON_NOTIFICATION_NETWORK_ETHERNET_DISCONNECTED + ES_ICON_NOTIFICATION_NETWORK_WIRED + ES_ICON_NOTIFICATION_NETWORK_WIRELESS + ES_ICON_NOTIFICATION_NETWORK_WIRELESS_DISCONNECTED + ES_ICON_NOTIFICATION_NETWORK_WIRELESS_DISCONNECTED_SYMBOLIC + ES_ICON_NOTIFICATION_NETWORK_WIRELESS_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_10_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_20_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_30_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_40_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_50_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_60_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_70_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_80_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_90_SYMBOLIC + ES_ICON_NOTIFICATION_NEW_SYMBOLIC + ES_ICON_NOTIFICATION_SYMBOLIC + ES_ICON_PAGER_CHECKED_SYMBOLIC + ES_ICON_PRINTER_ERROR + ES_ICON_PRINTER_ERROR_SYMBOLIC + ES_ICON_PRINTER_PRINTING_SYMBOLIC + ES_ICON_PRINTER_WARNING_SYMBOLIC + ES_ICON_PROCESS_COMPLETED + ES_ICON_PROCESS_COMPLETED_SYMBOLIC + ES_ICON_PROCESS_ERROR_SYMBOLIC + ES_ICON_PROCESS_WORKING_SYMBOLIC + ES_ICON_RADIO_CHECKED_SYMBOLIC + ES_ICON_RADIO_MIXED_SYMBOLIC + ES_ICON_RADIO_SYMBOLIC + ES_ICON_ROTATION_ALLOWED_SYMBOLIC + ES_ICON_ROTATION_LOCKED_SYMBOLIC + ES_ICON_SECURITY_HIGH + ES_ICON_SECURITY_HIGH_SYMBOLIC + ES_ICON_SECURITY_LOW + ES_ICON_SECURITY_LOW_SYMBOLIC + ES_ICON_SECURITY_MEDIUM + ES_ICON_SECURITY_MEDIUM_SYMBOLIC + ES_ICON_SEMI_STARRED + ES_ICON_SEMI_STARRED_SYMBOLIC + ES_ICON_SOFTWARE_UPDATE_AVAILABLE_SYMBOLIC + ES_ICON_SOFTWARE_UPDATE_URGENT_SYMBOLIC + ES_ICON_STARRED + ES_ICON_STARRED_SYMBOLIC + ES_ICON_TASK_DUE_SYMBOLIC + ES_ICON_TASK_PAST_DUE_SYMBOLIC + ES_ICON_TEST + ES_ICON_TOUCHPAD_DISABLED_SYMBOLIC + ES_ICON_USER_AVAILABLE + ES_ICON_USER_AVAILABLE_SYMBOLIC + ES_ICON_USER_AWAY + ES_ICON_USER_AWAY_SYMBOLIC + ES_ICON_USER_BUSY + ES_ICON_USER_BUSY_SYMBOLIC + ES_ICON_USER_IDLE_SYMBOLIC + ES_ICON_USER_INVISIBLE + ES_ICON_USER_INVISIBLE_SYMBOLIC + ES_ICON_USER_OFFLINE + ES_ICON_USER_OFFLINE_SYMBOLIC + ES_ICON_USER_STATUS_PENDING_SYMBOLIC + ES_ICON_USER_TRASH_FULL_SYMBOLIC + ES_ICON_USER_TYPING + ES_ICON_VIEW_PRIVATE + ES_ICON_VIEW_PRIVATE_SYMBOLIC + ES_ICON_VIEW_WRAPPED_SYMBOLIC + ES_ICON_WEATHER_CLEAR_NIGHT_SYMBOLIC + ES_ICON_WEATHER_CLEAR_SYMBOLIC + ES_ICON_WEATHER_FEW_CLOUDS_NIGHT_SYMBOLIC + ES_ICON_WEATHER_FEW_CLOUDS_SYMBOLIC + ES_ICON_WEATHER_FOG_NIGHT_SYMBOLIC + ES_ICON_WEATHER_FOG_SYMBOLIC + ES_ICON_WEATHER_OVERCAST_NIGHT_SYMBOLIC + ES_ICON_WEATHER_OVERCAST_SYMBOLIC + ES_ICON_WEATHER_SEVERE_ALERT_SYMBOLIC + ES_ICON_WEATHER_SHOWERS_NIGHT_SYMBOLIC + ES_ICON_WEATHER_SHOWERS_SCATTERED_NIGHT_SYMBOLIC + ES_ICON_WEATHER_SHOWERS_SCATTERED_SYMBOLIC + ES_ICON_WEATHER_SHOWERS_SYMBOLIC + ES_ICON_WEATHER_SNOW_NIGHT_SYMBOLIC + ES_ICON_WEATHER_SNOW_SYMBOLIC + ES_ICON_WEATHER_STORM_NIGHT_SYMBOLIC + ES_ICON_WEATHER_STORM_SYMBOLIC + ES_ICON_WEATHER_STORM_TORNADO_NIGHT_SYMBOLIC + ES_ICON_WEATHER_STORM_TORNADO_SYMBOLIC + ES_ICON_WEATHER_WINDY_SYMBOLIC +} diff --git a/desktop/list_view.cpp b/desktop/list_view.cpp new file mode 100644 index 0000000..50b25d5 --- /dev/null +++ b/desktop/list_view.cpp @@ -0,0 +1,2616 @@ +// TODO RMB click/drag. +// TODO Consistent int64_t/intptr_t. +// TODO Drag and drop. +// TODO GetFirstIndex/GetLastIndex assume that every group is non-empty. + +struct ListViewItemElement : EsElement { + uintptr_t index; // Index into the visible items array. +}; + +struct ListViewItem { + ListViewItemElement *element; + int32_t group; + int32_t size; + EsGeneric index; + uint8_t indent; + bool startAtSecondColumn; + bool isHeader, isFooter; + bool showSearchHighlight; +}; + +struct ListViewGroup { + // TODO Empty groups. + uint64_t itemCount; + int64_t totalSize; + uint32_t flags; + bool initialised; +}; + +struct ListViewFixedItem { + char *string; + size_t stringBytes; + EsGeneric data; +}; + +int ListViewProcessItemMessage(EsElement *element, EsMessage *message); + +struct EsListView : EsElement { + ScrollPane scroll; + + uint64_t totalItemCount; + uint64_t totalSize; + Array groups; + Array visibleItems; + + const EsStyle *itemStyle, *headerItemStyle, *footerItemStyle; + int64_t fixedWidth, fixedHeight; + int64_t fixedHeaderSize, fixedFooterSize; + + // TODO Updating these when the style changes. + UIStyle *primaryCellStyle; + UIStyle *secondaryCellStyle; + + bool hasFocusedItem; + int32_t focusedItemGroup; + EsGeneric focusedItemIndex; + + bool hasAnchorItem; + int32_t anchorItemGroup; + EsGeneric anchorItemIndex; + + // Valid only during Z-order messages. + Array zOrderItems; + + EsElement *selectionBox; + bool hasSelectionBoxAnchor; + int64_t selectionBoxAnchorX, selectionBoxAnchorY, + selectionBoxPositionX, selectionBoxPositionY; + + bool firstLayout; + + char searchBuffer[64]; + size_t searchBufferBytes; + uint64_t searchBufferLastKeyTime; + + char *emptyMessage; + size_t emptyMessageBytes; + + EsElement *columnHeader; + EsListViewColumn *columns; + size_t columnCount; + int columnResizingOriginalWidth; + // TODO Updating this when the style changes. + int64_t totalColumnWidth; + + EsTextbox *inlineTextbox; + int32_t inlineTextboxGroup; + EsGeneric inlineTextboxIndex; + + int maximumItemsPerBand; + + // Fixed item storage: + Array fixedItems; + ptrdiff_t fixedItemSelection; + + inline EsRectangle GetListBounds() { + EsRectangle bounds = GetBounds(); + + if (columnHeader) { + bounds.t += columnHeader->currentStyle->preferredHeight; + } + + return bounds; + } + + int CompareIndices(int32_t groupIndex, EsGeneric left, EsGeneric right) { + if (left.u == right.u) { + return 0; + } + + if (~flags & ES_LIST_VIEW_NON_LINEAR) { + if (left.i > right.i) return 1; + if (left.i < right.i) return -1; + } + + EsMessage m = { ES_MSG_LIST_VIEW_COMPARE_INDICES }; + m.compareIndices.group = groupIndex; + m.compareIndices.left = left; + m.compareIndices.right = right; + EsAssert(EsMessageSend(this, &m) == ES_HANDLED); // Could not compare indices. + return m.compareIndices.result; + } + + inline void GetFirstIndex(EsMessage *message) { + EsAssert(message->iterateIndex.group < (int32_t) groups.Length()); // Invalid group index. + EsAssert(groups[message->iterateIndex.group].itemCount); // No items in the group. + + if (flags & ES_LIST_VIEW_NON_LINEAR) { + message->type = ES_MSG_LIST_VIEW_FIRST_INDEX; + EsAssert(ES_HANDLED == EsMessageSend(this, message)); // First index message not handled. + } else { + message->iterateIndex.index.i = 0; + } + } + + inline EsGeneric GetFirstIndex(int32_t group) { + EsMessage m = {}; + m.iterateIndex.group = group; + GetFirstIndex(&m); + return m.iterateIndex.index; + } + + inline void GetLastIndex(EsMessage *message) { + EsAssert(message->iterateIndex.group < (int32_t) groups.Length()); // Invalid group index. + EsAssert(groups[message->iterateIndex.group].itemCount); // No items in the group. + + if (flags & ES_LIST_VIEW_NON_LINEAR) { + message->type = ES_MSG_LIST_VIEW_LAST_INDEX; + EsAssert(ES_HANDLED == EsMessageSend(this, message)); // First index message not handled. + } else { + message->iterateIndex.index.i = groups[message->iterateIndex.group].itemCount - 1; + } + } + + inline EsGeneric GetLastIndex(int32_t group) { + EsMessage m = {}; + m.iterateIndex.group = group; + GetLastIndex(&m); + return m.iterateIndex.index; + } + + inline bool IterateForwards(EsMessage *message) { + if (flags & ES_LIST_VIEW_NON_LINEAR) { + message->type = ES_MSG_LIST_VIEW_NEXT_INDEX; + int response = EsMessageSend(this, message); + EsAssert(0 != response); // Next index message not handled. + return response == ES_HANDLED; + } else { + if (message->iterateIndex.index.u == groups[message->iterateIndex.group].itemCount - 1) { + if (message->iterateIndex.group == (int32_t) groups.Length() - 1) { + return false; + } + + message->iterateIndex.group++; + message->iterateIndex.index.i = 0; + } else { + message->iterateIndex.index.i++; + } + + return true; + } + } + + inline bool IterateBackwards(EsMessage *message) { + if (flags & ES_LIST_VIEW_NON_LINEAR) { + message->type = ES_MSG_LIST_VIEW_PREVIOUS_INDEX; + int response = EsMessageSend(this, message); + EsAssert(0 != response); // Previous index message not handled. + return response == ES_HANDLED; + } else { + if (message->iterateIndex.index.u == 0) { + if (message->iterateIndex.group == 0) { + return false; + } + + message->iterateIndex.group--; + message->iterateIndex.index.i = groups[message->iterateIndex.group].itemCount - 1; + } else { + message->iterateIndex.index.i--; + } + + return true; + } + } + + int64_t CountItems(int32_t groupIndex, EsGeneric firstIndex, EsGeneric lastIndex) { + if (firstIndex.u == lastIndex.u) { + return 1; + } + + if (~flags & ES_LIST_VIEW_NON_LINEAR) { + return lastIndex.i - firstIndex.i + 1; + } + + { + EsMessage m = { ES_MSG_LIST_VIEW_COUNT_ITEMS }; + m.itemRange.group = groupIndex; + m.itemRange.firstIndex = firstIndex; + m.itemRange.lastIndex = lastIndex; + + if (ES_HANDLED == EsMessageSend(this, &m)) { + return m.itemRange.result; + } + } + + EsMessage m = {}; + m.iterateIndex.group = groupIndex; + m.iterateIndex.index = firstIndex; + + int64_t count = 1; + + while (true) { + IterateForwards(&m); + EsAssert(groupIndex == m.iterateIndex.group); // Index range did not exist in group. + count++; + + if (m.iterateIndex.index.u == lastIndex.u) { + return count; + } + } + } + + int64_t MeasureItems(int32_t groupIndex, EsGeneric firstIndex, EsGeneric lastIndex, int64_t *count = nullptr) { + int64_t _tempCount = -1; + if (!count) count = &_tempCount; + + if (~flags & ES_LIST_VIEW_NON_LINEAR) { + if (*count == -1) { + *count = lastIndex.i - firstIndex.i + 1; + } else { + EsAssert(*count == lastIndex.i - firstIndex.i + 1); // Invalid item count. + } + } + + bool haveCount = *count != -1; + bool variableSize = flags & ES_LIST_VIEW_VARIABLE_SIZE; + + if (!variableSize) { + if (!haveCount) { + *count = CountItems(groupIndex, firstIndex, lastIndex); + } + + ListViewGroup *group = &groups[groupIndex]; + int64_t normalCount = *count; + int64_t additionalSize = 0; + + if ((group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) && firstIndex.u == 0) { + normalCount--; + additionalSize += fixedHeaderSize; + } + + if ((group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) && lastIndex.u == group->itemCount - 1) { + normalCount--; + additionalSize += fixedFooterSize; + } + + return additionalSize + normalCount * (flags & ES_LIST_VIEW_HORIZONTAL ? fixedWidth : fixedHeight); + } + + if (firstIndex.u != lastIndex.u) { + EsMessage m = { ES_MSG_LIST_VIEW_MEASURE_RANGE }; + m.itemRange.group = groupIndex; + m.itemRange.firstIndex = firstIndex; + m.itemRange.lastIndex = lastIndex; + + if (ES_HANDLED == EsMessageSend(this, &m)) { + return m.itemRange.result; + } + } + + EsMessage m = {}; + m.iterateIndex.group = groupIndex; + m.iterateIndex.index = firstIndex; + int64_t total = 0; + int64_t _count = 0; + + while (true) { + EsMessage m2 = { ES_MSG_LIST_VIEW_MEASURE_ITEM }; + m2.measureItem.group = groupIndex; + m2.measureItem.index = m.iterateIndex.index; + EsAssert(ES_HANDLED == EsMessageSend(this, &m2)); // Variable height list view must be able to measure items. + total += m2.measureItem.result; + _count++; + + if (m.iterateIndex.index.u == lastIndex.u) { + if (*count != -1) EsAssert(*count == _count); // Invalid item count. + else *count = _count; + return total; + } + + IterateForwards(&m); + EsAssert(groupIndex == m.iterateIndex.group); // Index range did not exist in group. + } + } + + void GetItemPosition(int32_t groupIndex, EsGeneric index, int64_t *_position, int64_t *_itemSize) { + int64_t gapBetweenGroup = currentStyle->gapMajor, + gapBetweenItems = (flags & ES_LIST_VIEW_TILED) ? currentStyle->gapWrap : currentStyle->gapMinor, + fixedSize = (flags & ES_LIST_VIEW_VARIABLE_SIZE) ? 0 : (flags & ES_LIST_VIEW_HORIZONTAL ? fixedWidth : fixedHeight), + startInset = flags & ES_LIST_VIEW_HORIZONTAL ? currentStyle->insets.l : currentStyle->insets.t; + + int64_t position = (flags & ES_LIST_VIEW_HORIZONTAL ? -scroll.position[0] : -scroll.position[1]) + startInset, + itemSize = 0; + + EsGeneric targetIndex = index; + + for (int32_t i = 0; i < groupIndex; i++) { + position += groups[i].totalSize + gapBetweenGroup; + } + + ListViewGroup *group = &groups[groupIndex]; + + if ((group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) && index.u == 0) { + itemSize = fixedHeaderSize; + } else if ((group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) && index.u == group->itemCount - 1) { + position += group->totalSize - fixedFooterSize; + itemSize = fixedFooterSize; + } else if ((~flags & ES_LIST_VIEW_NON_LINEAR) && (~flags & ES_LIST_VIEW_VARIABLE_SIZE)) { + // TODO MAP_TO_LINEAR if non-linear but fixed size. + + intptr_t linearIndex = index.i; + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) { + linearIndex--; + position += fixedHeaderSize + gapBetweenItems; + } + + linearIndex /= GetItemsPerBand(); + position += (fixedSize + gapBetweenItems) * linearIndex; + itemSize = fixedSize; + } else { + EsAssert(~flags & ES_LIST_VIEW_TILED); // Tiled list views must be linear and fixed-size.; + + EsMessage index = {}; + index.type = ES_MSG_LIST_VIEW_FIND_POSITION; + index.iterateIndex.group = groupIndex; + index.iterateIndex.index = targetIndex; + + if (ES_HANDLED == EsMessageSend(this, &index)) { + position += index.iterateIndex.position; + } else { + if (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) { + position += fixedHeaderSize + gapBetweenItems; + } + + bool forwards; + ListViewItem *reference = visibleItems.Length() ? visibleItems.array : nullptr; + + bool closerToStartThanReference = reference && targetIndex.i < reference->index.i / 2; + bool closerToEndThanReference = reference && targetIndex.i > reference->index.i / 2 + (intptr_t) group->itemCount / 2; + if (flags & ES_LIST_VIEW_NON_LINEAR) closerToStartThanReference = closerToEndThanReference = false; + + if (reference && reference->group == groupIndex && !closerToStartThanReference && !closerToEndThanReference) { + index.iterateIndex.index = reference->index; + position = (flags & ES_LIST_VIEW_HORIZONTAL) ? reference->element->offsetX : reference->element->offsetY; + forwards = CompareIndices(groupIndex, reference->index, targetIndex) < 0; + + EsMessage firstIndex = {}; + firstIndex.iterateIndex.group = groupIndex; + GetFirstIndex(&firstIndex); + + if (index.iterateIndex.index == firstIndex.iterateIndex.index) { + forwards = true; + } + } else if ((~flags & ES_LIST_VIEW_NON_LINEAR) && targetIndex.u > group->itemCount / 2) { + GetLastIndex(&index); + position += group->totalSize; + position -= MeasureItems(index.iterateIndex.group, index.iterateIndex.index, index.iterateIndex.index); + forwards = false; + } else { + GetFirstIndex(&index); + forwards = true; + } + + while (index.iterateIndex.index.u != targetIndex.u) { + int64_t size = MeasureItems(index.iterateIndex.group, index.iterateIndex.index, index.iterateIndex.index); + position += forwards ? (size + gapBetweenItems) : -(size + gapBetweenItems); + EsAssert((forwards ? IterateForwards(&index) : IterateBackwards(&index)) && index.iterateIndex.group == groupIndex); + // Could not find the item in the group. + } + + itemSize = MeasureItems(index.iterateIndex.group, index.iterateIndex.index, index.iterateIndex.index); + } + } + + *_position = position; + *_itemSize = itemSize; + } + + void EnsureItemVisible(int32_t groupIndex, EsGeneric index, bool alignTop) { + EsRectangle contentBounds = GetListBounds(); + + int64_t startInset = flags & ES_LIST_VIEW_HORIZONTAL ? currentStyle->insets.l : currentStyle->insets.t, + endInset = flags & ES_LIST_VIEW_HORIZONTAL ? currentStyle->insets.r : currentStyle->insets.b, + contentSize = flags & ES_LIST_VIEW_HORIZONTAL ? Width(contentBounds) : Height(contentBounds); + + int64_t position, itemSize; + GetItemPosition(groupIndex, index, &position, &itemSize); + + if (position >= 0 && position + itemSize <= contentSize - endInset) { + return; + } + + if (alignTop) { + if (flags & ES_LIST_VIEW_HORIZONTAL) { + scroll.SetX(scroll.position[0] + position - startInset); + } else { + scroll.SetY(scroll.position[1] + position - startInset); + } + } else { + if (flags & ES_LIST_VIEW_HORIZONTAL) { + scroll.SetX(scroll.position[0] + position + itemSize - contentSize + endInset); + } else { + scroll.SetY(scroll.position[1] + position + itemSize - contentSize + endInset); + } + } + } + + EsMessage FindFirstVisibleItem(int64_t *_position, int64_t position, ListViewItem *reference, bool *noItems) { + int64_t gapBetweenGroup = currentStyle->gapMajor, + gapBetweenItems = (flags & ES_LIST_VIEW_TILED) ? currentStyle->gapWrap : currentStyle->gapMinor, + fixedSize = (flags & ES_LIST_VIEW_VARIABLE_SIZE) ? 0 : (flags & ES_LIST_VIEW_HORIZONTAL ? fixedWidth : fixedHeight); + + // Find the group. + // TODO Faster searching when there are many groups. + + int32_t groupIndex = 0; + bool foundGroup = false; + + for (; groupIndex < (int32_t) groups.Length(); groupIndex++) { + ListViewGroup *group = &groups[groupIndex]; + int64_t totalSize = group->totalSize; + + if (position + totalSize > 0) { + foundGroup = true; + break; + } + + position += totalSize + gapBetweenGroup; + } + + if (!foundGroup) { + if (noItems) { + *noItems = true; + return {}; + } else { + EsAssert(false); // Could not find the first visible item with the given scroll. + } + } + + EsMessage index = {}; + index.iterateIndex.group = groupIndex; + + // Can we go directly to the item? + + if ((~flags & ES_LIST_VIEW_NON_LINEAR) && (~flags & ES_LIST_VIEW_VARIABLE_SIZE)) { + // TODO MAP_FROM_LINEAR message if non-linear but fixed size. + + index.iterateIndex.index.i = 0; + intptr_t addHeader = 0; + + ListViewGroup *group = &groups[groupIndex]; + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) { + if (position + fixedHeaderSize > 0) { + *_position = position; + return index; + } + + position += fixedHeaderSize + gapBetweenItems; + addHeader = 1; + } + + intptr_t band = -position / (fixedSize + gapBetweenItems); + if (band < 0) band = 0; + position += band * (fixedSize + gapBetweenItems); + + if (flags & ES_LIST_VIEW_TILED) { + band *= GetItemsPerBand(); + } + + index.iterateIndex.index.i = band + addHeader; + + if (index.iterateIndex.index.i >= (intptr_t) group->itemCount) { + index.iterateIndex.index.i = group->itemCount - 1; + } + + *_position = position; + return index; + } + + EsAssert(~flags & ES_LIST_VIEW_TILED); // Trying to use TILED mode with NON_LINEAR or VARIABLE_SIZE mode. + + // Try asking the application to find the item. + + index.type = ES_MSG_LIST_VIEW_FIND_INDEX; + index.iterateIndex.position = -position; + + if (ES_HANDLED == EsMessageSend(this, &index)) { + *_position = -index.iterateIndex.position; + return index; + } + + // Find the item within the group, manually. + + bool forwards; + + if (reference && reference->group == groupIndex) { + int64_t referencePosition = (flags & ES_LIST_VIEW_HORIZONTAL) ? reference->element->offsetX : reference->element->offsetY; + + if (AbsoluteInteger64(referencePosition) < AbsoluteInteger64(position) + && AbsoluteInteger64(referencePosition) < AbsoluteInteger64(position + groups[groupIndex].totalSize)) { + index.iterateIndex.index = reference->index; + position = referencePosition; // Use previous first visible item as reference. + forwards = position < 0; + + EsMessage firstIndex = {}; + firstIndex.iterateIndex.group = groupIndex; + GetFirstIndex(&firstIndex); + + if (index.iterateIndex.index == firstIndex.iterateIndex.index) { + forwards = true; + } + + goto gotReference; + } + } + + if (position + groups[groupIndex].totalSize / 2 >= 0) { + GetFirstIndex(&index); // Use start of group as reference. + forwards = true; + } else { + GetLastIndex(&index); // Use end of group as reference + position += groups[groupIndex].totalSize; + position -= MeasureItems(index.iterateIndex.group, index.iterateIndex.index, index.iterateIndex.index); + forwards = false; + } + + gotReference:; + + if (forwards) { + // Iterate forwards from reference point. + + while (true) { + int64_t size = fixedSize ?: MeasureItems(index.iterateIndex.group, index.iterateIndex.index, index.iterateIndex.index); + + if (position + size > 0) { + *_position = position; + return index; + } + + EsAssert(IterateForwards(&index) && index.iterateIndex.group == groupIndex); + // No items in the group are visible. Maybe invalid scroll position? + position += size + gapBetweenItems; + } + } else { + // Iterate backwards from reference point. + + while (true) { + if (position <= 0 || !IterateBackwards(&index)) { + *_position = position; + return index; + } + + int64_t size = fixedSize ?: MeasureItems(index.iterateIndex.group, index.iterateIndex.index, index.iterateIndex.index); + EsAssert(index.iterateIndex.group == groupIndex); + // No items in the group are visible. Maybe invalid scroll position? + position -= size + gapBetweenItems; + } + } + } + + void Populate() { + // TODO Keep one item before and after the viewport, so tab traversal on custom elements works. + // TODO Always keep an item if it has FOCUS_WITHIN. + // - But maybe we shouldn't allow focusable elements in a list view. + + if (!totalItemCount) { + return; + } + + EsRectangle contentBounds = GetListBounds(); + int64_t contentSize = flags & ES_LIST_VIEW_HORIZONTAL ? Width(contentBounds) : Height(contentBounds); + int64_t scroll = EsCRTfloor(flags & ES_LIST_VIEW_HORIZONTAL ? (this->scroll.position[0] - currentStyle->insets.l) + : (this->scroll.position[1] - currentStyle->insets.t)); + + int64_t position = 0; + bool noItems = false; + EsMessage currentItem = FindFirstVisibleItem(&position, -scroll, visibleItems.Length() ? visibleItems.array : nullptr, &noItems); + uintptr_t visibleIndex = 0; + + int64_t wrapLimit = GetWrapLimit(); + int64_t fixedMinorSize = (flags & ES_LIST_VIEW_HORIZONTAL) ? fixedHeight : fixedWidth; + intptr_t itemsPerBand = GetItemsPerBand(); + intptr_t itemInBand = 0; + int64_t computedMinorGap = currentStyle->gapMinor; + int64_t minorPosition = 0; + int64_t centerOffset = (flags & ES_LIST_VIEW_CENTER_TILES) + ? (wrapLimit - itemsPerBand * (fixedMinorSize + currentStyle->gapMinor) + currentStyle->gapMinor) / 2 : 0; + + while (visibleIndex < visibleItems.Length()) { + // Remove visible items no longer visible, before the viewport. + + ListViewItem *visibleItem = &visibleItems[visibleIndex]; + int64_t visibleItemPosition = flags & ES_LIST_VIEW_HORIZONTAL ? visibleItem->element->offsetX : visibleItem->element->offsetY; + if (visibleItemPosition >= position) break; + visibleItem->element->index = visibleIndex; + visibleItem->element->Destroy(); + visibleItems.Delete(visibleIndex); + } + + while (position < contentSize && !noItems) { + ListViewItem *visibleItem = visibleIndex == visibleItems.Length() ? nullptr : &visibleItems[visibleIndex]; + + if (visibleItem && visibleItem->index == currentItem.iterateIndex.index + && visibleItem->group == currentItem.iterateIndex.group) { + // This is already a visible item. + + if (~flags & ES_LIST_VIEW_TILED) { + int64_t expectedPosition = (flags & ES_LIST_VIEW_HORIZONTAL) + ? visibleItem->element->offsetX - contentBounds.l + : visibleItem->element->offsetY - contentBounds.t; + + if (position < expectedPosition - 1 || position > expectedPosition + 1) { + EsPrint("Item in unexpected position: expected %d, got %d; index %d, scroll %d.\n", + expectedPosition, position, visibleItem->index.i, scroll); + EsAssert(false); + } + } + } else { + // Add a new visible item. + + ListViewItem empty = {}; + visibleItems.Insert(empty, visibleIndex); + visibleItem = &visibleItems[visibleIndex]; + + visibleItem->group = currentItem.iterateIndex.group; + visibleItem->index = currentItem.iterateIndex.index; + visibleItem->size = MeasureItems(visibleItem->group, visibleItem->index, visibleItem->index); + + ListViewGroup *group = &groups[visibleItem->group]; + const EsStyle *style = nullptr; + + if ((group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) && visibleItem->index.i == 0 ) { + style = headerItemStyle; + visibleItem->isHeader = true; + } else if ((group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) && visibleItem->index.i == (intptr_t) group->itemCount - 1) { + style = footerItemStyle; + visibleItem->isFooter = true; + } else { + if (group->flags & ES_LIST_VIEW_GROUP_INDENT) { + visibleItem->indent++; + } + + style = itemStyle; + } + + visibleItem->element = (ListViewItemElement *) EsHeapAllocate(sizeof(ListViewItemElement), true); + visibleItem->element->Initialise(this, ES_CELL_FILL, nullptr, style); + visibleItem->element->index = visibleIndex; + visibleItem->element->cName = "list view item"; + + visibleItem->element->messageClass = ListViewProcessItemMessage; + + if (hasFocusedItem && visibleItem->group == focusedItemGroup && visibleItem->index.u == focusedItemIndex.u) { + visibleItem->element->customStyleState |= THEME_STATE_FOCUSED_ITEM; + } + + if (state & UI_STATE_FOCUSED) { + visibleItem->element->customStyleState |= THEME_STATE_LIST_FOCUSED; + } + + EsMessage m = {}; + + m.type = ES_MSG_LIST_VIEW_IS_SELECTED; + m.selectItem.group = visibleItem->group; + m.selectItem.index = visibleItem->index; + EsMessageSend(this, &m); + if (m.selectItem.isSelected) visibleItem->element->customStyleState |= THEME_STATE_SELECTED; + + m.type = ES_MSG_LIST_VIEW_CREATE_ITEM; + m.createItem.group = visibleItem->group; + m.createItem.index = visibleItem->index; + m.createItem.item = visibleItem->element; + EsMessageSend(this, &m); + + m.type = ES_MSG_LIST_VIEW_GET_INDENT; + m.getIndent.group = visibleItem->group; + m.getIndent.index = visibleItem->index; + m.getIndent.indent = 0; + EsMessageSend(this, &m); + visibleItem->indent += m.getIndent.indent; + + SelectPreview(visibleItems.Length() - 1); + + visibleItem->element->MaybeRefreshStyle(); + } + + visibleItem->element->index = visibleIndex; + + // Update the item's position. + + ListViewGroup *group = &groups[visibleItem->group]; + + if ((flags & ES_LIST_VIEW_TILED) && !visibleItem->isHeader && !visibleItem->isFooter) { + if (flags & ES_LIST_VIEW_HORIZONTAL) { + visibleItem->element->InternalMove(fixedWidth, fixedHeight, + position + contentBounds.l, minorPosition + currentStyle->insets.t + contentBounds.t + centerOffset); + } else { + visibleItem->element->InternalMove(fixedWidth, fixedHeight, + minorPosition + currentStyle->insets.l + contentBounds.l + centerOffset, position + contentBounds.t); + } + + minorPosition += computedMinorGap + fixedMinorSize; + itemInBand++; + + bool endOfGroup = ((group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) && currentItem.iterateIndex.index.u == group->itemCount - 2) + || (currentItem.iterateIndex.index.u == group->itemCount - 1); + + if (itemInBand == itemsPerBand || endOfGroup) { + minorPosition = 0; + itemInBand = 0; + position += (flags & ES_LIST_VIEW_HORIZONTAL) ? visibleItem->element->width : visibleItem->element->height; + if (!endOfGroup || (group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER)) position += currentStyle->gapWrap; + } + } else { + if (flags & ES_LIST_VIEW_HORIZONTAL) { + visibleItem->element->InternalMove( + visibleItem->size, + Height(contentBounds) - currentStyle->insets.t - currentStyle->insets.b - visibleItem->indent * currentStyle->gapWrap, + position + contentBounds.l, + currentStyle->insets.t - this->scroll.position[1] + visibleItem->indent * currentStyle->gapWrap + contentBounds.t); + position += visibleItem->element->width; + } else if (flags & ES_LIST_VIEW_COLUMNS) { + int indent = visibleItem->indent * currentStyle->gapWrap; + int firstColumn = columns[0].width + secondaryCellStyle->gapMajor; + visibleItem->startAtSecondColumn = indent > firstColumn; + if (indent > firstColumn) indent = firstColumn; + visibleItem->element->InternalMove( + totalColumnWidth - indent, + visibleItem->size, + indent - this->scroll.position[0] + contentBounds.l + currentStyle->insets.l, + position + contentBounds.t); + position += visibleItem->element->height; + } else { + int indent = visibleItem->indent * currentStyle->gapWrap + currentStyle->insets.l; + visibleItem->element->InternalMove(Width(contentBounds) - indent - currentStyle->insets.r, visibleItem->size, + indent + contentBounds.l - this->scroll.position[0], position + contentBounds.t); + position += visibleItem->element->height; + } + + if ((flags & ES_LIST_VIEW_TILED) && (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) && currentItem.iterateIndex.index.u == 0) { + position += currentStyle->gapWrap; + } + } + + // Go to the next item. + + visibleIndex++; + int32_t previousGroup = currentItem.iterateIndex.group; + if (!IterateForwards(¤tItem)) break; + position += previousGroup == currentItem.iterateIndex.group ? (flags & ES_LIST_VIEW_TILED ? 0 : currentStyle->gapMinor) : currentStyle->gapMajor; + } + + while (visibleIndex < visibleItems.Length()) { + // Remove visible items no longer visible, after the viewport. + + ListViewItem *visibleItem = &visibleItems[visibleIndex]; + visibleItem->element->index = visibleIndex; + visibleItem->element->Destroy(); + visibleItems.Delete(visibleIndex); + } + } + + void Wrap(bool autoScroll) { + if (~flags & ES_LIST_VIEW_TILED) return; + + totalSize = 0; + + intptr_t itemsPerBand = GetItemsPerBand(); + + for (uintptr_t i = 0; i < groups.Length(); i++) { + ListViewGroup *group = &groups[i]; + int64_t groupSize = 0; + + intptr_t itemCount = group->itemCount; + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) { + groupSize += fixedHeaderSize + currentStyle->gapWrap; + itemCount--; + } + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) { + groupSize += fixedFooterSize + currentStyle->gapWrap; + itemCount--; + } + + intptr_t bandsInGroup = (itemCount + itemsPerBand - 1) / itemsPerBand; + groupSize += (((flags & ES_LIST_VIEW_HORIZONTAL) ? fixedWidth : fixedHeight) + currentStyle->gapWrap) * bandsInGroup; + groupSize -= currentStyle->gapWrap; + group->totalSize = groupSize; + + totalSize += groupSize + (group == &groups.Last() ? 0 : currentStyle->gapMajor); + } + + scroll.Refresh(); + + if (visibleItems.Length() && autoScroll) { + EnsureItemVisible(visibleItems[0].group, visibleItems[0].index, true); + } + } + + void InsertSpace(int64_t space, uintptr_t beforeItem) { + if (!space) return; + + if (flags & ES_LIST_VIEW_TILED) { + EsElementUpdateContentSize(this); + return; + } + + int64_t currentScroll = (flags & ES_LIST_VIEW_HORIZONTAL) ? scroll.position[0] : scroll.position[1]; + int64_t scrollLimit = (flags & ES_LIST_VIEW_HORIZONTAL) ? scroll.limit[0] : scroll.limit[1]; + + totalSize += space; + + if (((beforeItem == 0 && currentScroll) || currentScroll == scrollLimit) && firstLayout && space > 0 && scrollLimit) { + scroll.Refresh(); + + if (flags & ES_LIST_VIEW_HORIZONTAL) { + scroll.SetX(scroll.position[0] + space, false); + } else { + scroll.SetY(scroll.position[1] + space, false); + } + } else { + for (uintptr_t i = beforeItem; i < visibleItems.Length(); i++) { + ListViewItem *item = &visibleItems[i]; + + if (flags & ES_LIST_VIEW_HORIZONTAL) { + item->element->offsetX += space; + } else { + item->element->offsetY += space; + } + } + + scroll.Refresh(); + } + + EsElementUpdateContentSize(this); + } + + void SetSelected(int32_t fromGroup, EsGeneric fromIndex, int32_t toGroup, EsGeneric toIndex, + bool select, bool toggle, + intptr_t period = 0, intptr_t periodBegin = 0, intptr_t periodEnd = 0) { + if (!select && (flags & ES_LIST_VIEW_CHOICE_SELECT)) { + return; + } + + if (!select && (flags & ES_LIST_VIEW_SINGLE_SELECT)) { + EsMessage m = {}; + m.type = ES_MSG_LIST_VIEW_SELECT; + m.selectItem.isSelected = false; + fixedItemSelection = -1; + EsMessageSend(this, &m); + return; + } + + if (fromGroup == toGroup && CompareIndices(fromGroup, fromIndex, toIndex) > 0) { + EsGeneric temp = fromIndex; + fromIndex = toIndex; + toIndex = temp; + } else if (fromGroup > toGroup) { + int32_t temp1 = fromGroup; + fromGroup = toGroup; + toGroup = temp1; + EsGeneric temp2 = fromIndex; + fromIndex = toIndex; + toIndex = temp2; + } + + EsMessage start = {}, end = {}; + start.iterateIndex.group = fromGroup; + end.iterateIndex.group = fromGroup; + + for (; start.iterateIndex.group <= toGroup; start.iterateIndex.group++, end.iterateIndex.group++) { + if (start.iterateIndex.group == fromGroup) { + start.iterateIndex.index = fromIndex; + } else { + GetFirstIndex(&start); + } + + if (end.iterateIndex.group == toGroup) { + end.iterateIndex.index = toIndex; + } else { + GetLastIndex(&end); + } + + EsMessage m = { ES_MSG_LIST_VIEW_SELECT_RANGE }; + m.selectRange.group = start.iterateIndex.group; + m.selectRange.fromIndex = start.iterateIndex.index; + m.selectRange.toIndex = end.iterateIndex.index; + m.selectRange.select = select; + m.selectRange.toggle = toggle; + + if (!period && 0 != EsMessageSend(this, &m)) { + continue; + } + + intptr_t linearIndex = 0; + + while (true) { + m.selectItem.group = start.iterateIndex.group; + m.selectItem.index = start.iterateIndex.index; + m.selectItem.isSelected = select; + + if (period) { + ListViewGroup *group = &groups[m.selectItem.group]; + intptr_t i = linearIndex; + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) { + if (linearIndex == 0) { + goto process; + } else { + i--; + } + } + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) { + if (linearIndex == (intptr_t) group->itemCount - 1) { + goto process; + } + } + + i %= period; + + if (i < periodBegin || i > periodEnd) { + goto ignore; + } + } + + process:; + + if (toggle) { + m.type = ES_MSG_LIST_VIEW_IS_SELECTED; + EsMessageSend(this, &m); + m.selectItem.isSelected = !m.selectItem.isSelected; + } + + m.type = ES_MSG_LIST_VIEW_SELECT; + fixedItemSelection = m.selectItem.index.u; + EsMessageSend(this, &m); + + ignore:; + + if (start.iterateIndex.index == end.iterateIndex.index) { + break; + } + + IterateForwards(&start); + linearIndex++; + EsAssert(start.iterateIndex.group == end.iterateIndex.group); // The from and to selection indices in the group were incorrectly ordered. + } + } + } + + void SelectPreview(intptr_t singleItem = -1) { + if (!hasSelectionBoxAnchor) { + return; + } + + int64_t x1 = selectionBoxPositionX, x2 = selectionBoxAnchorX, + y1 = selectionBoxPositionY, y2 = selectionBoxAnchorY; + + if (x1 > x2) { int64_t temp = x1; x1 = x2; x2 = temp; } + if (y1 > y2) { int64_t temp = y1; y1 = y2; y2 = temp; } + + x1 -= scroll.position[0], x2 -= scroll.position[0]; + y1 -= scroll.position[1], y2 -= scroll.position[1]; + + EsRectangle bounds = GetListBounds(); + + if (x1 < -1000) x1 = -1000; + if (x2 < -1000) x2 = -1000; + if (y1 < -1000) y1 = -1000; + if (y2 < -1000) y2 = -1000; + + if (x1 > bounds.r + 1000) x1 = bounds.r + 1000; + if (x2 > bounds.r + 1000) x2 = bounds.r + 1000; + if (y1 > bounds.b + 1000) y1 = bounds.b + 1000; + if (y2 > bounds.b + 1000) y2 = bounds.b + 1000; + + selectionBox->InternalMove(x2 - x1, y2 - y1, x1, y1); + + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + if (singleItem != -1) { + i = singleItem; + } + + EsMessage m = { ES_MSG_LIST_VIEW_IS_SELECTED }; + m.selectItem.index = visibleItems[i].index; + m.selectItem.group = visibleItems[i].group; + EsMessageSend(this, &m); + + EsElement *item = visibleItems[i].element; + + if (x1 < item->offsetX + item->width && x2 >= item->offsetX && y1 < item->offsetY + item->height && y2 >= item->offsetY) { + if (EsKeyboardIsCtrlHeld()) { + m.selectItem.isSelected = !m.selectItem.isSelected; + } else { + m.selectItem.isSelected = true; + } + } + + if (m.selectItem.isSelected) { + item->customStyleState |= THEME_STATE_SELECTED; + } else { + item->customStyleState &= ~THEME_STATE_SELECTED; + } + + item->MaybeRefreshStyle(); + + if (singleItem != -1) { + break; + } + } + } + + intptr_t GetItemsPerBand() { + if (~flags & ES_LIST_VIEW_TILED) { + return 1; + } else { + int64_t wrapLimit = GetWrapLimit(); + int64_t fixedMinorSize = (flags & ES_LIST_VIEW_HORIZONTAL) ? fixedHeight : fixedWidth; + intptr_t itemsPerBand = fixedMinorSize && ((fixedMinorSize + currentStyle->gapMinor) < wrapLimit) + ? (wrapLimit / (fixedMinorSize + currentStyle->gapMinor)) : 1; + return MinimumInteger(itemsPerBand, maximumItemsPerBand); + } + } + + void SelectBox(int64_t x1, int64_t x2, int64_t y1, int64_t y2, bool toggle) { + if (!totalItemCount) { + return; + } + + EsRectangle contentBounds = GetListBounds(); + int64_t offset = 0; + EsMessage start, end; + bool noItems = false; + + if (flags & ES_LIST_VIEW_HORIZONTAL) { + if (y1 >= contentBounds.b - currentStyle->insets.b || y2 < contentBounds.t + currentStyle->insets.t) { + return; + } + } else if (flags & ES_LIST_VIEW_COLUMNS) { + if (x1 >= contentBounds.l + currentStyle->insets.l + totalColumnWidth || x2 < contentBounds.l + currentStyle->insets.l) { + return; + } + } else { + if (x1 >= contentBounds.r - currentStyle->insets.r || x2 < contentBounds.l + currentStyle->insets.l) { + return; + } + } + + // TODO Use reference for FindFirstVisibleItem. + + bool adjustStart = false, adjustEnd = false; + int r1 = (flags & ES_LIST_VIEW_HORIZONTAL) ? currentStyle->insets.l - x1 : currentStyle->insets.t - y1 + scroll.fixedViewport[1]; + int r2 = (flags & ES_LIST_VIEW_HORIZONTAL) ? currentStyle->insets.l - x2 : currentStyle->insets.t - y2 + scroll.fixedViewport[1]; + start = FindFirstVisibleItem(&offset, r1, nullptr, &noItems); + if (noItems) return; + adjustStart = -offset >= MeasureItems(start.iterateIndex.group, start.iterateIndex.index, start.iterateIndex.index); + end = FindFirstVisibleItem(&offset, r2, nullptr, &noItems); + adjustEnd = !noItems; + if (noItems) { end.iterateIndex.group = groups.Length() - 1; GetLastIndex(&end); } + + if (flags & ES_LIST_VIEW_TILED) { + int64_t wrapLimit = GetWrapLimit(); + int64_t fixedMinorSize = (flags & ES_LIST_VIEW_HORIZONTAL) ? fixedHeight : fixedWidth; + intptr_t itemsPerBand = GetItemsPerBand(); + int64_t computedMinorGap = (wrapLimit - itemsPerBand * fixedMinorSize) / (itemsPerBand + 1); + int64_t minorStartOffset = computedMinorGap + ((flags & ES_LIST_VIEW_HORIZONTAL) ? currentStyle->insets.t : currentStyle->insets.l); + intptr_t startInBand = (((flags & ES_LIST_VIEW_HORIZONTAL) ? y1 : x1) - minorStartOffset) / (fixedMinorSize + computedMinorGap); + intptr_t endInBand = (((flags & ES_LIST_VIEW_HORIZONTAL) ? y2 : x2) - minorStartOffset) / (fixedMinorSize + computedMinorGap); + + if (adjustStart) { + ListViewGroup *group = &groups[start.iterateIndex.group]; + + if (((group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) && start.iterateIndex.index.u == 0) + || ((group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) && start.iterateIndex.index.u == group->itemCount - 1)) { + IterateForwards(&start); + } else { + for (intptr_t i = 0; i < itemsPerBand; i++) { + if ((group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) && start.iterateIndex.index.u == group->itemCount - 1) { + break; + } + + IterateForwards(&start); + } + } + } + + if (adjustEnd) { + ListViewGroup *group = &groups[end.iterateIndex.group]; + + if (((group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) && end.iterateIndex.index.u == 0) + || ((group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) && end.iterateIndex.index.u == group->itemCount - 1)) { + } else { + for (intptr_t i = 0; i < itemsPerBand - 1; i++) { + if ((group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) && end.iterateIndex.index.u == group->itemCount - 1) { + IterateBackwards(&end); + break; + } + + IterateForwards(&end); + } + } + } + + SetSelected(start.iterateIndex.group, start.iterateIndex.index, end.iterateIndex.group, end.iterateIndex.index, true, toggle, + itemsPerBand, startInBand, endInBand); + } else { + if (adjustStart) { + IterateForwards(&start); + } + + SetSelected(start.iterateIndex.group, start.iterateIndex.index, end.iterateIndex.group, end.iterateIndex.index, true, toggle); + } + } + + void UpdateVisibleItemSelectionState(uintptr_t i) { + EsMessage m = {}; + m.type = ES_MSG_LIST_VIEW_IS_SELECTED; + m.selectItem.group = visibleItems[i].group; + m.selectItem.index = visibleItems[i].index; + EsMessageSend(this, &m); + + if (m.selectItem.isSelected) { + visibleItems[i].element->customStyleState |= THEME_STATE_SELECTED; + } else { + visibleItems[i].element->customStyleState &= ~THEME_STATE_SELECTED; + } + + visibleItems[i].element->MaybeRefreshStyle(); + } + + void UpdateVisibleItemsSelectionState() { + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + UpdateVisibleItemSelectionState(i); + } + } + + void Select(int32_t group, EsGeneric index, bool range, bool toggle, bool moveAnchorOnly) { + if ((~flags & ES_LIST_VIEW_SINGLE_SELECT) && (~flags & ES_LIST_VIEW_MULTI_SELECT) && (~flags & ES_LIST_VIEW_CHOICE_SELECT)) { + return; + } + + if (!totalItemCount) { + return; + } + + if (!hasAnchorItem || (~flags & ES_LIST_VIEW_MULTI_SELECT)) { + range = false; + } + + bool emptySpace = false; + + if (group == -1) { + // Clicked on empty space. + if (range || toggle) return; + emptySpace = true; + } + + if (!range && !emptySpace) { + hasAnchorItem = true; + anchorItemGroup = group; + anchorItemIndex = index; + } + + if (moveAnchorOnly) { + return; + } + + if (!toggle) { + // Clear existing selection. + SetSelected(0, GetFirstIndex(0), groups.Length() - 1, GetLastIndex(groups.Length() - 1), false, false); + } + + if (range) { + // Select range. + SetSelected(anchorItemGroup, anchorItemIndex, group, index, true, false); + } else if (toggle) { + // Toggle single item. + SetSelected(group, index, group, index, false, true); + } else if (!emptySpace) { + // Select single item. + SetSelected(group, index, group, index, true, false); + } + + UpdateVisibleItemsSelectionState(); + } + + int ProcessItemMessage(uintptr_t visibleIndex, EsMessage *message, ListViewItemElement *element) { + ListViewItem *item = &visibleItems[visibleIndex]; + + if (message->type == ES_MSG_PAINT) { + EsMessage m = { ES_MSG_LIST_VIEW_GET_CONTENT }; + uint8_t _buffer[512]; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + m.getContent.buffer = &buffer; + m.getContent.index = item->index; + m.getContent.group = item->group; + + EsTextSelection selection = {}; + selection.hideCaret = true; + + if (searchBufferBytes && item->showSearchHighlight) { + // TODO We might need to store the matched bytes per item, because of case insensitivity. + selection.caret1 = searchBufferBytes; + selection.hideCaret = false; + } + + if (flags & ES_LIST_VIEW_COLUMNS) { + EsRectangle bounds = EsRectangleAddBorder(element->GetBounds(), element->currentStyle->insets); + + for (uintptr_t i = item->startAtSecondColumn ? 1 : 0; i < columnCount; i++) { + m.getContent.column = i; + m.getContent.icon = 0; + buffer.position = 0; + + bounds.r = bounds.l + columns[i].width + - element->currentStyle->insets.r - element->currentStyle->insets.l; + + if (i == 0) { + bounds.r -= item->indent * currentStyle->gapWrap; + } + + EsRectangle drawBounds = { bounds.l + message->painter->offsetX, bounds.r + message->painter->offsetX, + bounds.t + message->painter->offsetY, bounds.b + message->painter->offsetY }; + + if (EsRectangleClip(drawBounds, message->painter->clip, nullptr) + && ES_HANDLED == EsMessageSend(this, &m)) { + UIStyle *style = i ? secondaryCellStyle : primaryCellStyle; + + uint8_t previousTextAlign = style->textAlign; + + if (columns[i].flags & ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED) { + style->textAlign ^= ES_TEXT_H_RIGHT | ES_TEXT_H_LEFT; + } + + style->PaintText(message->painter, element, bounds, + (char *) _buffer, buffer.position, m.getContent.icon, + (columns[i].flags & ES_LIST_VIEW_COLUMN_TABULAR) ? ES_DRAW_CONTENT_TABULAR : ES_FLAGS_DEFAULT, + i ? nullptr : &selection); + style->textAlign = previousTextAlign; + } + + bounds.l += columns[i].width + secondaryCellStyle->gapMajor; + + if (i == 0) { + bounds.l -= item->indent * currentStyle->gapWrap; + } + } + } else { + if (flags & ES_LIST_VIEW_TILED) { + m.type = ES_MSG_LIST_VIEW_GET_SUMMARY; + if (ES_HANDLED == EsMessageSend(this, &m)) goto standardPaint; + m.type = ES_MSG_LIST_VIEW_GET_CONTENT; + } + + if (ES_HANDLED == EsMessageSend(this, &m)) goto standardPaint; + + return 0; + + standardPaint:; + + if (inlineTextbox && inlineTextboxGroup == item->group && inlineTextboxIndex.u == item->index.u) { + buffer.position = 0; + } + + EsDrawContent(message->painter, element, element->GetBounds(), + (char *) _buffer, buffer.position, m.getContent.icon, + m.getContent.richText ? ES_DRAW_CONTENT_RICH_TEXT : ES_FLAGS_DEFAULT, + &selection); + } + } else if (message->type == ES_MSG_LAYOUT) { + if (element->GetChildCount()) { + EsElement *child = element->GetChild(0); + EsRectangle bounds = element->GetBounds(); + child->InternalMove(bounds.r - bounds.l, bounds.b - bounds.t, bounds.l, bounds.t); + } + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + EsElementFocus(this); + + if (hasFocusedItem) { + ListViewItem *oldFocus = FindVisibleItem(focusedItemGroup, focusedItemIndex); + + if (oldFocus) { + oldFocus->element->customStyleState &= ~THEME_STATE_FOCUSED_ITEM; + oldFocus->element->MaybeRefreshStyle(); + } + } + + hasFocusedItem = true; + focusedItemGroup = item->group; + focusedItemIndex = item->index; + element->customStyleState |= THEME_STATE_FOCUSED_ITEM; + + Select(item->group, item->index, EsKeyboardIsShiftHeld(), EsKeyboardIsCtrlHeld(), false); + + if (message->mouseDown.clickChainCount == 2 && !EsKeyboardIsShiftHeld() && !EsKeyboardIsCtrlHeld()) { + EsMessage m = { ES_MSG_LIST_VIEW_CHOOSE_ITEM }; + m.chooseItem.group = item->group; + m.chooseItem.index = item->index; + EsMessageSend(this, &m); + } + } else if (message->type == ES_MSG_MOUSE_RIGHT_DOWN) { + EsMessage m = { ES_MSG_LIST_VIEW_IS_SELECTED }; + m.selectItem.index = item->index; + m.selectItem.group = item->group; + EsMessageSend(this, &m); + + if (!m.selectItem.isSelected) { + Select(item->group, item->index, EsKeyboardIsShiftHeld(), EsKeyboardIsCtrlHeld(), false); + } + + m.type = ES_MSG_LIST_VIEW_CONTEXT_MENU; + EsMessageSend(this, &m); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + if (flags & ES_LIST_VIEW_CHOICE_SELECT) { + window->pressed = this; + ProcessMessage(message); + } + } else if (message->type == ES_MSG_GET_INSPECTOR_INFORMATION) { + EsMessage m = { ES_MSG_LIST_VIEW_GET_CONTENT }; + uint8_t _buffer[256]; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + m.getContent.buffer = &buffer; + m.getContent.index = item->index; + m.getContent.group = item->group; + EsMessageSend(this, &m); + EsBufferFormat(message->getContent.buffer, "index %d '%s'", item->index.u, buffer.position, buffer.out); + } else { + return 0; + } + + return ES_HANDLED; + } + + inline int GetWrapLimit() { + EsRectangle bounds = GetListBounds(); + return (flags & ES_LIST_VIEW_HORIZONTAL) + ? bounds.b - bounds.t - currentStyle->insets.b - currentStyle->insets.t + : bounds.r - bounds.l - currentStyle->insets.r - currentStyle->insets.l; + } + + ListViewItem *FindVisibleItem(int32_t group, EsGeneric index) { + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + ListViewItem *item = &visibleItems[i]; + + if (item->group == group && item->index.u == index.u) { + return item; + } + } + + return nullptr; + } + + bool Search() { + uint8_t _buffer[64]; + + if (!hasFocusedItem) { + // Select the first item in the list. + KeyInput(ES_SCANCODE_DOWN_ARROW, false, false, false, true); + if (!hasFocusedItem) return false; + } + + EsMessage m = { ES_MSG_LIST_VIEW_SEARCH }; + m.searchItem.index = focusedItemIndex; + m.searchItem.group = focusedItemGroup; + m.searchItem.query = searchBuffer; + m.searchItem.queryBytes = searchBufferBytes; + int response = EsMessageSend(this, &m); + + if (response == ES_REJECTED) { + return false; + } + + ListViewItem *oldFocus = FindVisibleItem(focusedItemGroup, focusedItemIndex); + + if (oldFocus) { + oldFocus->element->customStyleState &= ~THEME_STATE_FOCUSED_ITEM; + oldFocus->element->MaybeRefreshStyle(); + } + + bool found = false; + + if (response == ES_HANDLED) { + focusedItemIndex = m.searchItem.index; + focusedItemGroup = m.searchItem.group; + found = true; + } else { + EsMessage m = { ES_MSG_LIST_VIEW_GET_CONTENT }; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + m.getContent.buffer = &buffer; + m.getContent.index = focusedItemIndex; + m.getContent.group = focusedItemGroup; + EsMessage m2 = {}; + m2.iterateIndex.index = focusedItemIndex; + m2.iterateIndex.group = focusedItemGroup; + + do { + buffer.position = 0; + EsMessageSend(this, &m); + + if (EsStringStartsWith((char *) _buffer, buffer.position, searchBuffer, searchBufferBytes, true)) { + found = true; + break; + } + + if (!IterateForwards(&m2)) { + m2.iterateIndex.group = 0; + GetFirstIndex(&m2); + } + + m.getContent.index = m2.iterateIndex.index; + m.getContent.group = m2.iterateIndex.group; + } while (m.getContent.index.u != focusedItemIndex.u || m.getContent.group != focusedItemGroup); + + focusedItemIndex = m.getContent.index; + focusedItemGroup = m.getContent.group; + EnsureItemVisible(focusedItemGroup, focusedItemIndex, false); + } + + ListViewItem *newFocus = FindVisibleItem(focusedItemGroup, focusedItemIndex); + + if (newFocus) { + newFocus->element->customStyleState |= THEME_STATE_FOCUSED_ITEM; + newFocus->element->MaybeRefreshStyle(); + } + + { + EsMessage m = { ES_MSG_LIST_VIEW_GET_CONTENT }; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + m.getContent.buffer = &buffer; + + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + ListViewItem *item = &visibleItems[i]; + m.getContent.index = item->index; + m.getContent.group = item->group; + buffer.position = 0; + EsMessageSend(this, &m); + bool shouldShowSearchHighlight = EsStringStartsWith((char *) _buffer, buffer.position, searchBuffer, searchBufferBytes, true); + + if (shouldShowSearchHighlight || (!shouldShowSearchHighlight && item->showSearchHighlight)) { + item->showSearchHighlight = shouldShowSearchHighlight; + item->element->Repaint(true); + } + } + } + + Select(-1, 0, false, false, false); + Select(focusedItemGroup, focusedItemIndex, false, false, false); + return found; + } + + bool KeyInput(int scancode, bool ctrl, bool alt, bool shift, bool keepSearchBuffer = false) { + if (!totalItemCount || alt) { + return false; + } + + if (scancode == ES_SCANCODE_BACKSPACE && searchBufferBytes) { + searchBufferBytes = 0; + Search(); + return true; + } + + bool isNext = false, + isPrevious = false, + isNextBand = false, + isPreviousBand = false, + isHome = scancode == ES_SCANCODE_HOME, + isEnd = scancode == ES_SCANCODE_END, + isPageUp = scancode == ES_SCANCODE_PAGE_UP, + isPageDown = scancode == ES_SCANCODE_PAGE_DOWN, + isSpace = scancode == ES_SCANCODE_SPACE, + isEnter = scancode == ES_SCANCODE_ENTER; + + if (flags & ES_LIST_VIEW_HORIZONTAL) { + isNext = scancode == ES_SCANCODE_DOWN_ARROW; + isNextBand = scancode == ES_SCANCODE_RIGHT_ARROW; + isPrevious = scancode == ES_SCANCODE_UP_ARROW; + isPreviousBand = scancode == ES_SCANCODE_LEFT_ARROW; + } else { + isNext = scancode == ES_SCANCODE_RIGHT_ARROW; + isNextBand = scancode == ES_SCANCODE_DOWN_ARROW; + isPrevious = scancode == ES_SCANCODE_LEFT_ARROW; + isPreviousBand = scancode == ES_SCANCODE_UP_ARROW; + } + + if (hasSelectionBoxAnchor) { + if (scancode == ES_SCANCODE_UP_ARROW) scroll.SetY(scroll.position[1] - GetConstantNumber("scrollKeyMovement")); + if (scancode == ES_SCANCODE_DOWN_ARROW) scroll.SetY(scroll.position[1] + GetConstantNumber("scrollKeyMovement")); + if (scancode == ES_SCANCODE_LEFT_ARROW) scroll.SetX(scroll.position[0] - GetConstantNumber("scrollKeyMovement")); + if (scancode == ES_SCANCODE_RIGHT_ARROW) scroll.SetX(scroll.position[0] + GetConstantNumber("scrollKeyMovement")); + if (scancode == ES_SCANCODE_PAGE_UP) scroll.SetY(scroll.position[1] - Height(GetBounds())); + if (scancode == ES_SCANCODE_PAGE_DOWN) scroll.SetY(scroll.position[1] + Height(GetBounds())); + + if (flags & ES_LIST_VIEW_HORIZONTAL) { + if (scancode == ES_SCANCODE_HOME) scroll.SetX(0); + if (scancode == ES_SCANCODE_END) scroll.SetX(scroll.limit[0]); + } else { + if (scancode == ES_SCANCODE_HOME) scroll.SetY(0); + if (scancode == ES_SCANCODE_END) scroll.SetY(scroll.limit[1]); + } + } else if (isPrevious || isNext || isHome || isEnd || isPageUp || isPageDown || isNextBand || isPreviousBand) { + ListViewItem *oldFocus = FindVisibleItem(focusedItemGroup, focusedItemIndex); + + if (oldFocus) { + oldFocus->element->customStyleState &= ~THEME_STATE_FOCUSED_ITEM; + oldFocus->element->MaybeRefreshStyle(); + } + + EsMessage m = {}; + + if (hasFocusedItem && (isPrevious || isNext || isPageUp || isPageDown || isNextBand || isPreviousBand)) { + m.iterateIndex.group = focusedItemGroup; + m.iterateIndex.index = focusedItemIndex; + + uintptr_t itemsPerBand = GetItemsPerBand(); + + for (uintptr_t i = 0; i < ((isPageUp || isPageDown) ? (10 * itemsPerBand) : (isNextBand || isPreviousBand) ? itemsPerBand : 1); i++) { + if (isNext || isPageDown || isNextBand) IterateForwards(&m); + else IterateBackwards(&m); + } + } else { + if (isNext || isNextBand || isHome) { + m.iterateIndex.group = 0; + GetFirstIndex(&m); + } else { + m.iterateIndex.group = groups.Length() - 1; + GetLastIndex(&m); + } + } + + hasFocusedItem = true; + focusedItemGroup = m.iterateIndex.group; + focusedItemIndex = m.iterateIndex.index; + + ListViewItem *newFocus = FindVisibleItem(focusedItemGroup, focusedItemIndex); + + if (newFocus) { + newFocus->element->customStyleState |= THEME_STATE_FOCUSED_ITEM; + newFocus->element->MaybeRefreshStyle(); + } + + if (!keepSearchBuffer) ClearSearchBuffer(); + EnsureItemVisible(focusedItemGroup, focusedItemIndex, isPrevious || isHome || isPageUp || isPreviousBand); + Select(focusedItemGroup, focusedItemIndex, shift, ctrl, ctrl && !shift); + return true; + } else if (isSpace && ctrl && !shift && hasFocusedItem) { + Select(focusedItemGroup, focusedItemIndex, false, true, false); + return true; + } else if (isEnter && hasFocusedItem && !shift && !ctrl && !alt) { + EsMessage m = { ES_MSG_LIST_VIEW_CHOOSE_ITEM }; + m.chooseItem.group = focusedItemGroup; + m.chooseItem.index = focusedItemIndex; + EsMessageSend(this, &m); + return true; + } else if (!ctrl && !alt) { + uint64_t currentTime = EsTimeStamp() / (api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND] * 1000); + + if (searchBufferLastKeyTime + GetConstantNumber("listViewSearchBufferTimeout") < currentTime) { + searchBufferBytes = 0; + } + + StartAnimating(); + searchBufferLastKeyTime = currentTime; + int ic, isc; + ConvertScancodeToCharacter(scancode, &ic, &isc, false, false); + int character = shift ? isc : ic; + + if (character != -1 && searchBufferBytes + 4 < sizeof(searchBuffer)) { + utf8_encode(character, searchBuffer + searchBufferBytes); + size_t previousSearchBufferBytes = searchBufferBytes; + searchBufferBytes += utf8_length_char(searchBuffer + searchBufferBytes); + if (!Search()) searchBufferBytes = previousSearchBufferBytes; + return true; + } + } + + return false; + } + + void ClearSearchBuffer() { + searchBufferBytes = 0; + + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + if (visibleItems[i].showSearchHighlight) { + visibleItems[i].showSearchHighlight = false; + visibleItems[i].element->Repaint(true); + } + } + } + + void DragSelect() { + EsRectangle bounds = GetWindowBounds(); + EsPoint mouse = EsMouseGetPosition(window); + + if (mouse.x < bounds.l) mouse.x = bounds.l; + if (mouse.x >= bounds.r) mouse.x = bounds.r - 1; + + if (visibleItems.Length()) { + int32_t start = visibleItems[0].element->GetWindowBounds().t; + int32_t end = visibleItems.Last().element->GetWindowBounds().b; + if (mouse.y < start) mouse.y = start; + if (mouse.y >= end) mouse.y = end - 1; + } + + EsElement *hoverItem = UIFindHoverElementRecursively(this, bounds.l - offsetX, bounds.t - offsetY, mouse); + + if (hoverItem && hoverItem->messageClass == ListViewProcessItemMessage) { + EsMessage m = {}; + m.type = ES_MSG_MOUSE_LEFT_DOWN; + EsMessageSend(hoverItem, &m); + } + } + + void MoveInlineTextbox(ListViewItem *item) { + UIStyle *style = item->element->currentStyle; + + if (flags & ES_LIST_VIEW_COLUMNS) { + int offset = primaryCellStyle->metrics->iconSize + primaryCellStyle->gapMinor + + style->insets.l - inlineTextbox->currentStyle->insets.l; + inlineTextbox->InternalMove(columns[0].width - offset, item->element->height, + item->element->offsetX + offset, item->element->offsetY); + } else if (flags & ES_LIST_VIEW_TILED) { + if (style->metrics->layoutVertical) { + int height = inlineTextbox->currentStyle->preferredHeight; + int textStart = style->metrics->iconSize + style->gapMinor + style->insets.t; + int textEnd = item->element->height - style->insets.b; + int offset = (textStart + textEnd - height) / 2; + inlineTextbox->InternalMove(item->element->width - style->insets.r - style->insets.l, height, + item->element->offsetX + style->insets.l, item->element->offsetY + offset); + } else { + int textboxInset = inlineTextbox->currentStyle->insets.l; + int offset = style->metrics->iconSize + style->gapMinor + + style->insets.l - textboxInset; + int height = inlineTextbox->currentStyle->preferredHeight; + inlineTextbox->InternalMove(item->element->width - offset - style->insets.r + textboxInset, height, + item->element->offsetX + offset, + item->element->offsetY + (item->element->height - height) / 2); + } + } else { + inlineTextbox->InternalMove(item->element->width, item->element->height, + item->element->offsetX, item->element->offsetY); + } + } + + int ProcessMessage(EsMessage *message) { + scroll.ReceivedMessage(message); + + if (message->type == ES_MSG_GET_WIDTH || message->type == ES_MSG_GET_HEIGHT) { + if (flags & ES_LIST_VIEW_HORIZONTAL) { + message->measure.width = totalSize + currentStyle->insets.l + currentStyle->insets.r; + } else { + message->measure.height = totalSize + currentStyle->insets.t + currentStyle->insets.b; + + if (flags & ES_LIST_VIEW_COLUMNS) { + message->measure.width = totalColumnWidth + currentStyle->insets.l + currentStyle->insets.r; + } + } + } else if (message->type == ES_MSG_LAYOUT) { + firstLayout = true; + Wrap(message->layout.sizeChanged); + Populate(); + + if (columnHeader) { + EsRectangle bounds = GetBounds(); + columnHeader->InternalMove(Width(bounds), columnHeader->currentStyle->preferredHeight, 0, 0); + } + + if (inlineTextbox) { + ListViewItem *item = FindVisibleItem(inlineTextboxGroup, inlineTextboxIndex); + if (item) MoveInlineTextbox(item); + } + } else if (message->type == ES_MSG_SCROLL_X || message->type == ES_MSG_SCROLL_Y) { + int64_t delta = message->scrollbarMoved.scroll - message->scrollbarMoved.previous; + + if ((message->type == ES_MSG_SCROLL_X) == ((flags & ES_LIST_VIEW_HORIZONTAL) ? true : false)) { + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + if (flags & ES_LIST_VIEW_HORIZONTAL) visibleItems[i].element->offsetX -= delta; + else visibleItems[i].element->offsetY -= delta; + } + } + + Populate(); + Repaint(true); + + if (columnHeader) { + EsElementRelayout(columnHeader); + } + + if (selectionBox) { + EsPoint position = EsMouseGetPosition(this); + selectionBoxPositionX = position.x + scroll.position[0]; + selectionBoxPositionY = position.y + scroll.position[1]; + SelectPreview(); + } + } else if (message->type == ES_MSG_DESTROY) { + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + visibleItems[i].element->Destroy(); + } + + for (uintptr_t i = 0; i < fixedItems.Length(); i++) { + EsHeapFree(fixedItems[i].string); + } + + primaryCellStyle->CloseReference(); + secondaryCellStyle->CloseReference(); + fixedItems.Free(); + visibleItems.Free(); + groups.Free(); + } else if (message->type == ES_MSG_KEY_UP) { + if (message->keyboard.scancode == ES_SCANCODE_LEFT_CTRL || message->keyboard.scancode == ES_SCANCODE_RIGHT_CTRL) { + SelectPreview(); + } + } else if (message->type == ES_MSG_KEY_DOWN) { + if (message->keyboard.scancode == ES_SCANCODE_LEFT_CTRL || message->keyboard.scancode == ES_SCANCODE_RIGHT_CTRL) { + SelectPreview(); + } + + return KeyInput(message->keyboard.scancode, + message->keyboard.modifiers & ES_MODIFIER_CTRL, + message->keyboard.modifiers & ES_MODIFIER_ALT, + message->keyboard.modifiers & ES_MODIFIER_SHIFT) + ? ES_HANDLED : 0; + } else if (message->type == ES_MSG_FOCUSED_START) { + if (!hasFocusedItem && groups.Length() && (message->focus.flags & ES_ELEMENT_FOCUS_FROM_KEYBOARD)) { + hasFocusedItem = true; + focusedItemGroup = 0; + focusedItemIndex = GetFirstIndex(focusedItemGroup); + } + + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + ListViewItem *item = &visibleItems[i]; + item->element->customStyleState |= THEME_STATE_LIST_FOCUSED; + + if (hasFocusedItem && focusedItemGroup == item->group && focusedItemIndex.u == item->index.u) { + item->element->customStyleState |= THEME_STATE_FOCUSED_ITEM; + } + + item->element->MaybeRefreshStyle(); + } + + EsCommand *command = EsCommandByID(instance, ES_COMMAND_SELECT_ALL); + command->data = this; + + EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) { + EsListView *list = (EsListView *) command->data.p; + if (!list->groups.Length() || !list->totalItemCount) return; + list->SetSelected(0, list->GetFirstIndex(0), list->groups.Length() - 1, list->GetLastIndex(list->groups.Length() - 1), true, false); + list->UpdateVisibleItemsSelectionState(); + }); + + EsCommandSetDisabled(command, false); + } else if (message->type == ES_MSG_FOCUSED_END) { + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + ListViewItem *item = &visibleItems[i]; + item->element->customStyleState &= ~(THEME_STATE_LIST_FOCUSED | THEME_STATE_FOCUSED_ITEM); + item->element->MaybeRefreshStyle(); + } + + EsCommandSetCallback(EsCommandByID(instance, ES_COMMAND_SELECT_ALL), nullptr); + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + Select(-1, 0, EsKeyboardIsShiftHeld(), EsKeyboardIsCtrlHeld(), false); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + if (selectionBox) { + Repaint(false, ES_RECT_4(selectionBox->offsetX, selectionBox->offsetX + selectionBox->width, + selectionBox->offsetY, selectionBox->offsetY + selectionBox->height)); + + if (!hasSelectionBoxAnchor) { + hasSelectionBoxAnchor = true; + selectionBoxAnchorX = message->mouseDragged.originalPositionX + scroll.position[0]; + selectionBoxAnchorY = message->mouseDragged.originalPositionY + scroll.position[1]; + + if (gui.lastClickButton == ES_MSG_MOUSE_LEFT_DOWN) { + Select(-1, 0, EsKeyboardIsShiftHeld(), EsKeyboardIsCtrlHeld(), false); + } + } + + EsElementSetDisabled(selectionBox, false); + + selectionBoxPositionX = message->mouseDragged.newPositionX + scroll.position[0]; + selectionBoxPositionY = message->mouseDragged.newPositionY + scroll.position[1]; + + SelectPreview(); + } else if (flags & ES_LIST_VIEW_CHOICE_SELECT) { + DragSelect(); + } + } else if (message->type == ES_MSG_MOUSE_LEFT_UP || message->type == ES_MSG_MOUSE_RIGHT_UP) { + if (selectionBox) { + EsElementSetDisabled(selectionBox, true); + + if (hasSelectionBoxAnchor) { + hasSelectionBoxAnchor = false; + + int64_t x1 = selectionBoxPositionX, x2 = selectionBoxAnchorX, + y1 = selectionBoxPositionY, y2 = selectionBoxAnchorY; + if (x1 > x2) { int64_t temp = x1; x1 = x2; x2 = temp; } + if (y1 > y2) { int64_t temp = y1; y1 = y2; y2 = temp; } + + SelectBox(x1, x2, y1, y2, EsKeyboardIsCtrlHeld()); + } + + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + EsMessage m = { ES_MSG_LIST_VIEW_IS_SELECTED }; + m.selectItem.index = visibleItems[i].index; + m.selectItem.group = visibleItems[i].group; + EsMessageSend(this, &m); + + EsElement *item = visibleItems[i].element; + + if (m.selectItem.isSelected) { + item->customStyleState |= THEME_STATE_SELECTED; + } else { + item->customStyleState &= ~THEME_STATE_SELECTED; + } + + item->MaybeRefreshStyle(); + } + } + } else if (message->type == ES_MSG_Z_ORDER) { + uintptr_t index = message->zOrder.index; + + if (index < visibleItems.Length()) { + EsAssert(zOrderItems.Length() == visibleItems.Length()); + message->zOrder.child = zOrderItems[index]; + return ES_HANDLED; + } else { + index -= visibleItems.Length(); + } + + if (selectionBox) { if (index == 0) return message->zOrder.child = selectionBox, ES_HANDLED; else index--; } + if (columnHeader) { if (index == 0) return message->zOrder.child = columnHeader, ES_HANDLED; else index--; } + if (inlineTextbox) { if (index == 0) return message->zOrder.child = inlineTextbox, ES_HANDLED; else index--; } + + message->zOrder.child = nullptr; + } else if (message->type == ES_MSG_PAINT && !totalItemCount && emptyMessageBytes) { + UIStyle *style = GetStyle(MakeStyleKey(ES_STYLE_TEXT_LABEL_SECONDARY, 0), true); + EsTextPlanProperties properties = {}; + properties.flags = ES_TEXT_H_CENTER | ES_TEXT_V_CENTER | ES_TEXT_WRAP | ES_TEXT_PLAN_SINGLE_USE; + EsTextRun textRun[2] = {}; + style->GetTextStyle(&textRun[0].style); + textRun[1].offset = emptyMessageBytes; + EsRectangle bounds = EsPainterBoundsInset(message->painter); + EsTextPlan *plan = EsTextPlanCreate(&properties, bounds, emptyMessage, textRun, 1); + EsDrawText(message->painter, plan, bounds); + } else if (message->type == ES_MSG_ANIMATE) { + if (scroll.dragScrolling && (flags & ES_LIST_VIEW_CHOICE_SELECT)) { + DragSelect(); + } + + uint64_t currentTime = EsTimeStamp() / (api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND] * 1000); + int64_t remainingTime = searchBufferLastKeyTime + GetConstantNumber("listViewSearchBufferTimeout") - currentTime; + + if (remainingTime < 0) { + ClearSearchBuffer(); + } else { + message->animate.waitMs = remainingTime; + message->animate.complete = false; + } + } else if (message->type == ES_MSG_BEFORE_Z_ORDER) { + EsAssert(!zOrderItems.Length()); + intptr_t focused = -1, hovered = -1; + + for (uintptr_t i = 0; i < visibleItems.Length(); i++) { + if (hasFocusedItem && visibleItems[i].index.u == focusedItemIndex.u && visibleItems[i].group == focusedItemGroup) { + focused = i; + } else if (visibleItems[i].element->state & UI_STATE_HOVERED) { + hovered = i; + } else { + zOrderItems.Add(visibleItems[i].element); + } + } + + if (hovered != -1) { + zOrderItems.Add(visibleItems[hovered].element); + } + + if (focused != -1) { + zOrderItems.Add(visibleItems[focused].element); + } + } else if (message->type == ES_MSG_AFTER_Z_ORDER) { + zOrderItems.Free(); + } else if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT && (flags & ES_LIST_VIEW_FIXED_ITEMS)) { + uintptr_t index = message->getContent.index.u; + EsAssert(index < fixedItems.Length()); + EsBufferFormat(message->getContent.buffer, "%s", fixedItems[index].stringBytes, fixedItems[index].string); + } else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED && (flags & ES_LIST_VIEW_FIXED_ITEMS)) { + message->selectItem.isSelected = message->selectItem.index.i == fixedItemSelection; + } else { + return 0; + } + + return ES_HANDLED; + } +}; + +int ListViewProcessMessage(EsElement *element, EsMessage *message) { + return ((EsListView *) element)->ProcessMessage(message); +} + +int ListViewProcessItemMessage(EsElement *_element, EsMessage *message) { + ListViewItemElement *element = (ListViewItemElement *) _element; + return ((EsListView *) element->parent)->ProcessItemMessage(element->index, message, element); +} + +void EsListViewChangeStyles(EsListView *view, const EsStyle *style, const EsStyle *itemStyle, + const EsStyle *headerItemStyle, const EsStyle *footerItemStyle, uint32_t addFlags, uint32_t removeFlags) { + // TODO Animating changes. + + bool wasTiledView = view->flags & ES_LIST_VIEW_TILED; + + EsAssert(!(addFlags & removeFlags)); + view->flags |= addFlags; + view->flags &= ~(uint64_t) removeFlags; + + bool horizontal = view->flags & ES_LIST_VIEW_HORIZONTAL; + + if (style) view->SetStyle(style, true); + if (itemStyle) view->itemStyle = itemStyle; + if (headerItemStyle) view->headerItemStyle = headerItemStyle; + if (footerItemStyle) view->footerItemStyle = footerItemStyle; + + GetPreferredSizeFromStylePart(view->itemStyle, &view->fixedWidth, &view->fixedHeight); + GetPreferredSizeFromStylePart(view->headerItemStyle, horizontal ? &view->fixedHeaderSize : nullptr, horizontal ? nullptr : &view->fixedHeaderSize); + GetPreferredSizeFromStylePart(view->footerItemStyle, horizontal ? &view->fixedFooterSize : nullptr, horizontal ? nullptr : &view->fixedFooterSize); + + if ((view->flags & ES_LIST_VIEW_MULTI_SELECT) && !view->selectionBox) { + view->selectionBox = EsCustomElementCreate(view, ES_CELL_FILL | ES_ELEMENT_DISABLED | ES_ELEMENT_NO_HOVER, ES_STYLE_LIST_SELECTION_BOX); + view->selectionBox->cName = "selection box"; + } else if ((~view->flags & ES_LIST_VIEW_MULTI_SELECT) && view->selectionBox) { + EsElementDestroy(view->selectionBox); + view->selectionBox = nullptr; + } + + if ((view->flags & ES_LIST_VIEW_COLUMNS) && !view->columnHeader) { + view->columnHeader = EsCustomElementCreate(view, ES_CELL_FILL, ES_STYLE_LIST_COLUMN_HEADER); + view->columnHeader->cName = "column header"; + view->columnHeader->userData = view; + + view->columnHeader->messageUser = [] (EsElement *element, EsMessage *message) { + EsListView *view = (EsListView *) element->userData.p; + + if (message->type == ES_MSG_LAYOUT) { + int x = view->currentStyle->insets.l - view->scroll.position[0]; + + for (uintptr_t i = 0; i < element->children.Length(); i += 2) { + EsElement *item = element->children[i], *splitter = element->children[i + 1]; + EsListViewColumn *column = view->columns + item->userData.u; + int splitterLeft = splitter->currentStyle->preferredWidth - view->secondaryCellStyle->gapMajor; + item->InternalMove(column->width - splitterLeft, element->height, x, 0); + splitter->InternalMove(splitter->currentStyle->preferredWidth, element->height, x + column->width - splitterLeft, 0); + x += column->width + view->secondaryCellStyle->gapMajor; + } + } + + return 0; + }; + + view->scroll.fixedViewport[1] = view->columnHeader->currentStyle->preferredHeight; + } else if ((~view->flags & ES_LIST_VIEW_COLUMNS) && view->columnHeader) { + EsElementDestroy(view->columnHeader); + view->columnHeader = nullptr; + view->scroll.fixedViewport[1] = 0; + } + + // It's safe to use SCROLL_MODE_AUTO even in tiled mode, + // because decreasing the secondary axis can only increase the primary axis. + + uint8_t scrollXMode = 0, scrollYMode = 0; + + if (view->flags & ES_LIST_VIEW_COLUMNS) { + scrollXMode = SCROLL_MODE_AUTO; + scrollYMode = SCROLL_MODE_AUTO; + } else if (view->flags & ES_LIST_VIEW_HORIZONTAL) { + scrollXMode = SCROLL_MODE_AUTO; + } else { + scrollYMode = SCROLL_MODE_AUTO; + } + + view->scroll.Setup(view, scrollXMode, scrollYMode, SCROLL_X_DRAG | SCROLL_Y_DRAG); + + // Remove existing visible items; the list will need to be repopulated. + + for (uintptr_t i = view->visibleItems.Length(); i > 0; i--) { + view->visibleItems[i - 1].element->Destroy(); + } + + view->visibleItems.SetLength(0); + + // Remeasure each group. + + if (wasTiledView) { + view->totalSize = 0; + + for (uintptr_t i = 0; i < view->groups.Length(); i++) { + view->groups[i].totalSize = 0; + } + } + + int64_t spaceDelta = 0; + + for (uintptr_t i = 0; i < view->groups.Length(); i++) { + if (!view->groups[i].itemCount) continue; + spaceDelta -= view->groups[i].totalSize; + view->groups[i].totalSize = view->MeasureItems(i, view->GetFirstIndex(i), view->GetLastIndex(i)); + spaceDelta += view->groups[i].totalSize; + } + + view->InsertSpace(spaceDelta, 0); + + EsElementRelayout(view); +} + +EsListView *EsListViewCreate(EsElement *parent, uint64_t flags, const EsStyle *style, + const EsStyle *itemStyle, const EsStyle *headerItemStyle, const EsStyle *footerItemStyle) { + EsListView *view = (EsListView *) EsHeapAllocate(sizeof(EsListView), true); + + view->primaryCellStyle = GetStyle(MakeStyleKey(ES_STYLE_LIST_PRIMARY_CELL, 0), false); + view->secondaryCellStyle = GetStyle(MakeStyleKey(ES_STYLE_LIST_SECONDARY_CELL, 0), false); + + view->Initialise(parent, flags | ES_ELEMENT_FOCUSABLE | ES_ELEMENT_CENTER_ACCESS_KEY_HINT, ListViewProcessMessage, style ?: ES_STYLE_LIST_VIEW); + view->cName = "list view"; + + view->fixedItemSelection = -1; + view->maximumItemsPerBand = INT_MAX; + + if (!itemStyle) { + if (flags & ES_LIST_VIEW_CHOICE_SELECT) itemStyle = ES_STYLE_LIST_CHOICE_ITEM; + else if (flags & ES_LIST_VIEW_TILED) itemStyle = ES_STYLE_LIST_ITEM_TILE; + else itemStyle = ES_STYLE_LIST_ITEM; + } + + EsListViewChangeStyles(view, nullptr, itemStyle, headerItemStyle ?: ES_STYLE_LIST_ITEM_GROUP_HEADER, + footerItemStyle ?: ES_STYLE_LIST_ITEM_GROUP_FOOTER, ES_FLAGS_DEFAULT, ES_FLAGS_DEFAULT); + + return view; +} + +void EsListViewInsertGroup(EsListView *view, int32_t group, uint32_t flags) { + EsMessageMutexCheck(); + + // Add the group. + + ListViewGroup empty = { .flags = flags }; + EsAssert(group <= (int32_t) view->groups.Length()); // Invalid group index. + view->groups.Insert(empty, group); + + // Update the group index on visible items. + + uintptr_t firstVisibleItemToMove = view->visibleItems.Length(); + + for (uintptr_t i = 0; i < view->visibleItems.Length(); i++) { + ListViewItem *item = &view->visibleItems[i]; + + if (item->group >= group) { + item->group++; + + if (i < firstVisibleItemToMove) { + firstVisibleItemToMove = i; + } + } + } + + // Insert gap between groups. + + view->InsertSpace(view->groups.Length() > 1 ? view->currentStyle->gapMajor : 0, firstVisibleItemToMove); + + // Create header and footer items. + + int64_t additionalItems = ((flags & ES_LIST_VIEW_GROUP_HAS_HEADER) ? 1 : 0) + ((flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) ? 1 : 0); + if (additionalItems) EsListViewInsert(view, group, 0, additionalItems - 1, additionalItems); + view->groups[group].initialised = true; +} + +void EsListViewInsert(EsListView *view, int32_t groupIndex, EsGeneric firstIndex, EsGeneric lastIndex, int64_t count) { + EsMessageMutexCheck(); + + bool pushIndices = false; + + if (~view->flags & ES_LIST_VIEW_NON_LINEAR) { + // Skip check for non-linear views, because it'd be expensive. + EsAssert(firstIndex.i <= lastIndex.i); // Invalid item index range. + + pushIndices = true; + } + + // Get the group. + + EsAssert(groupIndex < (int32_t) view->groups.Length()); // Invalid group index. + ListViewGroup *group = &view->groups[groupIndex]; + + if (group->initialised) { + if (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) { + EsAssert(firstIndex.i > 0); // Cannot insert before group header. + } + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) { + EsAssert(firstIndex.i < (intptr_t) group->itemCount); // Cannot insert after group footer. + } + } + + // Add the items to the group. + + bool alreadySetItemCount = false; + bool addedFirstItemInGroup = !group->itemCount; + + if (count != -1) { + // Setting the item count early is necessary for MeasureItems when adding the group header/footer items. + alreadySetItemCount = true; + group->itemCount += count; + } + + int64_t totalSizeOfItems = view->MeasureItems(groupIndex, firstIndex, lastIndex, &count); + int64_t sizeToAdd = (count - (addedFirstItemInGroup ? 1 : 0)) * view->currentStyle->gapMinor + totalSizeOfItems; + + if (!alreadySetItemCount) group->itemCount += count; + group->totalSize += sizeToAdd; + view->totalItemCount += count; + + // Update indices of visible items. + + uintptr_t firstVisibleItemToMove = view->visibleItems.Length(); + + if (view->hasFocusedItem && view->focusedItemGroup == groupIndex && pushIndices) { + if (view->CompareIndices(groupIndex, view->focusedItemIndex, firstIndex) >= 0) { + view->focusedItemIndex.i += count; + } + } + + if (view->hasAnchorItem && view->anchorItemGroup == groupIndex && pushIndices) { + if (view->CompareIndices(groupIndex, view->anchorItemIndex, firstIndex) >= 0) { + view->anchorItemIndex.i += count; + } + } + + for (uintptr_t i = 0; i < view->visibleItems.Length(); i++) { + ListViewItem *item = &view->visibleItems[i]; + + if (item->group < groupIndex) { + continue; + } else if (item->group > groupIndex) { + if (i < firstVisibleItemToMove) { + firstVisibleItemToMove = i; + } + + break; + } + + if (view->flags & ES_LIST_VIEW_NON_LINEAR) { + int result = 1; + + if (firstVisibleItemToMove >= i) { + result = view->CompareIndices(groupIndex, item->index, lastIndex); + } + + EsAssert(result != 0); // Adding item already in the list. + + if (result > 0) { + if (i < firstVisibleItemToMove) { + firstVisibleItemToMove = i; + } + } + } else { + if (item->index.i >= firstIndex.i) { + item->index.i += count; + + if (i < firstVisibleItemToMove) { + firstVisibleItemToMove = i; + } + } + } + } + + // Insert the space for the items. + + view->InsertSpace(sizeToAdd, firstVisibleItemToMove); +} + +void EsListViewRemove(EsListView *view, int32_t groupIndex, EsGeneric firstIndex, EsGeneric lastIndex, int64_t count) { + EsMessageMutexCheck(); + + bool pushIndices = false; + + if (~view->flags & ES_LIST_VIEW_NON_LINEAR) { + // Skip check for non-linear views, because it'd be expensive. + EsAssert(firstIndex.i <= lastIndex.i); // Invalid item index range. + + pushIndices = true; + } + + // Get the group. + + EsAssert(groupIndex < (int32_t) view->groups.Length()); // Invalid group index. + ListViewGroup *group = &view->groups[groupIndex]; + + if (group->initialised) { + if (group->flags & ES_LIST_VIEW_GROUP_HAS_HEADER) { + EsAssert(firstIndex.i > 0); // Cannot remove the group header. + } + + if (group->flags & ES_LIST_VIEW_GROUP_HAS_FOOTER) { + EsAssert(lastIndex.i < (intptr_t) group->itemCount - 1); // Cannot remove the group footer. + } + } + + // Remove the items from the group. + + int64_t totalSizeOfItems = view->MeasureItems(groupIndex, firstIndex, lastIndex, &count); + int64_t sizeToRemove = (int64_t) group->itemCount == count ? group->totalSize + : (count * view->currentStyle->gapMinor + totalSizeOfItems); + + group->itemCount -= count; + group->totalSize -= sizeToRemove; + view->totalItemCount -= count; + + // Update indices of visible items, + // and remove deleted items. + + uintptr_t firstVisibleItemToMove = view->visibleItems.Length(); + + if (view->hasFocusedItem && view->focusedItemGroup == groupIndex) { + if (view->CompareIndices(groupIndex, view->focusedItemIndex, firstIndex) >= 0 + && view->CompareIndices(groupIndex, view->focusedItemIndex, lastIndex) <= 0) { + view->hasFocusedItem = false; + } else if (pushIndices) { + view->focusedItemIndex.i -= count; + } + } + + if (view->hasAnchorItem && view->anchorItemGroup == groupIndex) { + if (view->CompareIndices(groupIndex, view->focusedItemIndex, firstIndex) >= 0 + && view->CompareIndices(groupIndex, view->anchorItemIndex, lastIndex) <= 0) { + view->hasAnchorItem = false; + } else if (pushIndices) { + view->anchorItemIndex.i -= count; + } + } + + for (uintptr_t i = 0; i < view->visibleItems.Length(); i++) { + ListViewItem *item = &view->visibleItems[i]; + + if (item->group < groupIndex) { + continue; + } else if (item->group > groupIndex) { + if (i < firstVisibleItemToMove) { + firstVisibleItemToMove = i; + } + + break; + } + + if (view->flags & ES_LIST_VIEW_NON_LINEAR) { + int r1 = view->CompareIndices(groupIndex, item->index, firstIndex); + int r2 = view->CompareIndices(groupIndex, item->index, lastIndex); + + if (r1 < 0) EsAssert(r2 < 0); // Invalid index order. + if (r2 > 0) EsAssert(r1 > 0); // Invalid index order. + + if (r2 > 0) { + if (i < firstVisibleItemToMove) { + firstVisibleItemToMove = i; + } + } else if (r1 >= 0 && r2 <= 0) { + item->element->index = i; + item->element->Destroy(); + view->visibleItems.Delete(i); + i--; + } + } else { + if (item->index.i > lastIndex.i) { + item->index.i -= count; + + if (i < firstVisibleItemToMove) { + firstVisibleItemToMove = i; + } + } else if (item->index.i >= firstIndex.i && item->index.i <= lastIndex.i) { + item->element->index = i; + item->element->Destroy(); + view->visibleItems.Delete(i); + i--; + } + } + } + + // Remove the space of the items. + + view->InsertSpace(-sizeToRemove, firstVisibleItemToMove); +} + +void EsListViewRemoveAll(EsListView *view, int32_t group) { + EsMessageMutexCheck(); + + if (view->groups[group].itemCount) { + EsListViewRemove(view, group, view->GetFirstIndex(group), view->GetLastIndex(group), view->groups[group].itemCount); + } +} + +int ListViewColumnHeaderItemMessage(EsElement *element, EsMessage *message) { + EsListView *view = (EsListView *) element->parent->parent; + EsListViewColumn *column = view->columns + element->userData.u; + + if (message->type == ES_MSG_PAINT) { + EsMessage m = { ES_MSG_LIST_VIEW_GET_COLUMN_SORT }; + m.getColumnSort.index = element->userData.u; + int sort = EsMessageSend(view, &m); + EsDrawContent(message->painter, element, element->GetBounds(), + column->title, column->titleBytes, 0, + sort == ES_LIST_VIEW_COLUMN_SORT_ASCENDING ? ES_DRAW_CONTENT_MARKER_UP_ARROW + : sort == ES_LIST_VIEW_COLUMN_SORT_DESCENDING ? ES_DRAW_CONTENT_MARKER_DOWN_ARROW : ES_FLAGS_DEFAULT); + } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK && (column->flags & ES_LIST_VIEW_COLUMN_HAS_MENU)) { + EsMessage m = { ES_MSG_LIST_VIEW_COLUMN_MENU }; + m.columnMenu.source = element; + m.columnMenu.index = element->userData.u; + EsMessageSend(view, &m); + } else { + return 0; + } + + return ES_HANDLED; +} + +void EsListViewSetColumns(EsListView *view, EsListViewColumn *columns, size_t columnCount) { + EsMessageMutexCheck(); + + EsAssert(view->flags & ES_LIST_VIEW_COLUMNS); // List view does not have columns flag set. + + EsElementDestroyContents(view->columnHeader); + + view->columns = columns; + view->columnCount = columnCount; + + view->totalColumnWidth = -view->secondaryCellStyle->gapMajor; + + for (uintptr_t i = 0; i < columnCount; i++) { + EsElement *columnHeaderItem = EsCustomElementCreate(view->columnHeader, ES_CELL_FILL, + (columns[i].flags & ES_LIST_VIEW_COLUMN_HAS_MENU) ? ES_STYLE_LIST_COLUMN_HEADER_ITEM_HAS_MENU : ES_STYLE_LIST_COLUMN_HEADER_ITEM); + + columnHeaderItem->messageUser = ListViewColumnHeaderItemMessage; + columnHeaderItem->cName = "column header item"; + columnHeaderItem->userData = i; + + if (!columns[i].width) { + columns[i].width = (i ? view->secondaryCellStyle : view->primaryCellStyle)->preferredWidth; + } + + EsElement *splitter = EsCustomElementCreate(view->columnHeader, ES_CELL_FILL, ES_STYLE_LIST_COLUMN_HEADER_SPLITTER); + + splitter->messageUser = [] (EsElement *element, EsMessage *message) { + EsListViewColumn *column = (EsListViewColumn *) element->userData.p; + EsListView *view = (EsListView *) element->parent->parent; + + if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + view->columnResizingOriginalWidth = column->width; + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + int width = message->mouseDragged.newPositionX - message->mouseDragged.originalPositionX + view->columnResizingOriginalWidth; + int minimumWidth = element->currentStyle->metrics->minimumWidth; + if (width < minimumWidth) width = minimumWidth; + + view->totalColumnWidth += width - column->width; + column->width = width; + EsElementRelayout(element->parent); + EsElementRelayout(view); + } else { + return 0; + } + + return ES_HANDLED; + }, + + splitter->cName = "column header splitter"; + splitter->userData = columns + i; + + view->totalColumnWidth += columns[i].width + view->secondaryCellStyle->gapMajor; + } + + view->scroll.Refresh(); +} + +void EsListViewContentChanged(EsListView *view) { + EsMessageMutexCheck(); + + view->searchBufferLastKeyTime = 0; + view->searchBufferBytes = 0; + + view->scroll.SetX(0); + view->scroll.SetY(0); + + EsListViewInvalidateAll(view); +} + +void EsListViewFocusItem(EsListView *view, int32_t group, EsGeneric index) { + ListViewItem *oldFocus = view->FindVisibleItem(view->focusedItemGroup, view->focusedItemIndex); + + if (oldFocus) { + oldFocus->element->customStyleState &= ~THEME_STATE_FOCUSED_ITEM; + oldFocus->element->MaybeRefreshStyle(); + } + + view->hasFocusedItem = true; + view->focusedItemGroup = group; + view->focusedItemIndex = index; + + ListViewItem *newFocus = view->FindVisibleItem(view->focusedItemGroup, view->focusedItemIndex); + + if (newFocus) { + newFocus->element->customStyleState |= THEME_STATE_FOCUSED_ITEM; + newFocus->element->MaybeRefreshStyle(); + } + + view->EnsureItemVisible(group, index, false); +} + +bool EsListViewGetFocusedItem(EsListView *view, int32_t *group, EsGeneric *index) { + if (view->hasFocusedItem) { + if (group) *group = view->focusedItemGroup; + if (index) *index = view->focusedItemIndex; + } + + return view->hasFocusedItem; +} + +void EsListViewSelect(EsListView *view, int32_t group, EsGeneric index) { + EsMessageMutexCheck(); + + view->Select(group, index, false, false, false); +} + +void EsListViewSetEmptyMessage(EsListView *view, const char *message, ptrdiff_t messageBytes) { + EsMessageMutexCheck(); + if (messageBytes == -1) messageBytes = EsCStringLength(message); + HeapDuplicate((void **) &view->emptyMessage, message, messageBytes); + view->emptyMessageBytes = messageBytes; + + if (!view->totalItemCount) { + view->Repaint(true); + } +} + +EsGeneric EsListViewGetIndexFromItem(EsElement *_element, int32_t *group) { + ListViewItemElement *element = (ListViewItemElement *) _element; + EsListView *view = (EsListView *) element->parent; + EsAssert(element->index < view->visibleItems.Length()); + if (group) *group = view->visibleItems[element->index].group; + return view->visibleItems[element->index].index; +} + +void EsListViewInvalidateAll(EsListView *view) { + view->UpdateVisibleItemsSelectionState(); + view->Repaint(true); +} + +void EsListViewInvalidateContent(EsListView *view, int32_t group, EsGeneric index) { + for (uintptr_t i = 0; i < view->visibleItems.Length(); i++) { + if (view->visibleItems[i].group == group && view->visibleItems[i].index.u == index.u) { + view->UpdateVisibleItemSelectionState(i); + view->visibleItems[i].element->Repaint(true); + break; + } + } +} + +void EsListViewInsertFixedItem(EsListView *view, const char *string, ptrdiff_t stringBytes, EsGeneric data, intptr_t index) { + EsAssert(view->flags & ES_LIST_VIEW_FIXED_ITEMS); + + if (stringBytes == -1) { + stringBytes = EsCStringLength(string); + } + + if (!view->groups.Length()) { + EsListViewInsertGroup(view, 0, ES_FLAGS_DEFAULT); + } + + if (index == -1) { + index = view->fixedItems.Length(); + } + + EsAssert(index >= 0 && index <= (intptr_t) view->fixedItems.Length()); + ListViewFixedItem item = {}; + item.data = data; + HeapDuplicate((void **) &item.string, string, stringBytes); + item.stringBytes = stringBytes; + view->fixedItems.Insert(item, index); + + EsListViewInsert(view, 0, index, index); +} + +bool EsListViewSelectFixedItem(EsListView *view, EsGeneric data) { + EsAssert(view->flags & ES_LIST_VIEW_FIXED_ITEMS); + EsMessageMutexCheck(); + + for (uintptr_t i = 0; i < view->fixedItems.Length(); i++) { + if (view->fixedItems[i].data.u == data.u) { + EsListViewSelect(view, 0, i); + return true; + } + } + + return false; +} + +bool EsListViewGetSelectedFixedItem(EsListView *view, EsGeneric *data) { + EsAssert(view->flags & ES_LIST_VIEW_FIXED_ITEMS); + EsMessageMutexCheck(); + + if (view->fixedItemSelection == -1 || view->fixedItemSelection >= (intptr_t) view->fixedItems.Length()) { + return false; + } else { + *data = view->fixedItems[view->fixedItemSelection].data; + return true; + } +} + +int ListViewInlineTextboxMessage(EsElement *element, EsMessage *message) { + int response = ProcessTextboxMessage(element, message); + + if (message->type == ES_MSG_DESTROY) { + EsListView *view = (EsListView *) EsElementGetLayoutParent(element); + view->inlineTextbox = nullptr; + ListViewItem *item = view->FindVisibleItem(view->inlineTextboxGroup, view->inlineTextboxIndex); + if (item) EsElementRepaint(item->element); + EsElementFocus(view); + } + + return response; +} + +EsTextbox *EsListViewCreateInlineTextbox(EsListView *view, int32_t group, EsGeneric index, uint32_t flags) { + if (view->inlineTextbox) { + view->inlineTextbox->Destroy(); + } + + view->inlineTextboxGroup = group; + view->inlineTextboxIndex = index; + view->EnsureItemVisible(group, index, true); + + uint64_t textboxFlags = ES_CELL_FILL | ES_TEXTBOX_EDIT_BASED | ES_TEXTBOX_ALLOW_TABS; + + if (flags & ES_LIST_VIEW_INLINE_TEXTBOX_REJECT_EDIT_IF_FOCUS_LOST) { + textboxFlags |= ES_TEXTBOX_REJECT_EDIT_IF_LOST_FOCUS; + } + + view->inlineTextbox = EsTextboxCreate(view, textboxFlags, ES_STYLE_TEXTBOX_INLINE); + EsAssert(view->inlineTextbox->messageClass == ProcessTextboxMessage); + view->inlineTextbox->messageClass = ListViewInlineTextboxMessage; + + if (flags & ES_LIST_VIEW_INLINE_TEXTBOX_COPY_EXISTING_TEXT) { + EsMessage m = { ES_MSG_LIST_VIEW_GET_CONTENT }; + uint8_t _buffer[256]; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + m.getContent.buffer = &buffer; + m.getContent.index = index; + m.getContent.group = group; + EsMessageSend(view, &m); + EsTextboxInsert(view->inlineTextbox, (char *) _buffer, buffer.position); + EsTextboxSelectAll(view->inlineTextbox); + } + + if (view->searchBufferBytes) { + view->searchBufferBytes = 0; + EsElementRepaint(view); + } + + EsElementRelayout(view); + EsElementFocus(view->inlineTextbox); + EsTextboxStartEdit(view->inlineTextbox); + + return view->inlineTextbox; +} + +void EsListViewScrollToEnd(EsListView *view) { + if (view->flags & ES_LIST_VIEW_HORIZONTAL) { + view->scroll.SetX(view->scroll.limit[0]); + } else { + view->scroll.SetY(view->scroll.limit[1]); + } +} + +void EsListViewEnumerateVisibleItems(EsListView *view, EsListViewEnumerateVisibleItemsCallbackFunction callback) { + for (uintptr_t i = 0; i < view->visibleItems.Length(); i++) { + callback(view, view->visibleItems[i].element, view->visibleItems[i].group, view->visibleItems[i].index); + } +} + +void EsListViewSetMaximumItemsPerBand(EsListView *view, int maximumItemsPerBand) { + view->maximumItemsPerBand = maximumItemsPerBand; +} diff --git a/desktop/os.header b/desktop/os.header new file mode 100644 index 0000000..170f9ba --- /dev/null +++ b/desktop/os.header @@ -0,0 +1,2399 @@ +opaque_type EsElement EsElementPublic; +opaque_type EsPanel EsElement; +opaque_type EsWindow EsElement; +opaque_type EsScrollbar EsElement; +opaque_type EsButton EsElement; +opaque_type EsTextDisplay EsElement; +opaque_type EsIconDisplay EsElement; +opaque_type EsTextbox EsElement; +opaque_type EsListView EsElement; +opaque_type EsMenu EsElement; +opaque_type EsColorWell EsElement; +opaque_type EsSplitter EsElement; +opaque_type EsImageDisplay EsElement; +opaque_type EsListDisplay EsElement; +opaque_type EsCanvasPane EsElement; +opaque_type EsTextPlan none; +opaque_type EsPaintTarget none; +opaque_type EsUndoManager none; +opaque_type EsHeap none; +opaque_type EsFileStore none; + +type_name uint8_t EsNodeType; +type_name intptr_t EsError; +type_name uintptr_t EsHandle; +type_name uint64_t EsFileOffset; +type_name int64_t EsFileOffsetDifference; +type_name uint64_t EsAudioDeviceID; +type_name uint16_t EsFontFamily; +type_name uint64_t EsTimer; + +define ES_SCANCODE_A (0x04) +define ES_SCANCODE_B (0x05) +define ES_SCANCODE_C (0x06) +define ES_SCANCODE_D (0x07) +define ES_SCANCODE_E (0x08) +define ES_SCANCODE_F (0x09) +define ES_SCANCODE_G (0x0A) +define ES_SCANCODE_H (0x0B) +define ES_SCANCODE_I (0x0C) +define ES_SCANCODE_J (0x0D) +define ES_SCANCODE_K (0x0E) +define ES_SCANCODE_L (0x0F) +define ES_SCANCODE_M (0x10) +define ES_SCANCODE_N (0x11) +define ES_SCANCODE_O (0x12) +define ES_SCANCODE_P (0x13) +define ES_SCANCODE_Q (0x14) +define ES_SCANCODE_R (0x15) +define ES_SCANCODE_S (0x16) +define ES_SCANCODE_T (0x17) +define ES_SCANCODE_U (0x18) +define ES_SCANCODE_V (0x19) +define ES_SCANCODE_W (0x1A) +define ES_SCANCODE_X (0x1B) +define ES_SCANCODE_Y (0x1C) +define ES_SCANCODE_Z (0x1D) + +define ES_SCANCODE_1 (0x1E) +define ES_SCANCODE_2 (0x1F) +define ES_SCANCODE_3 (0x20) +define ES_SCANCODE_4 (0x21) +define ES_SCANCODE_5 (0x22) +define ES_SCANCODE_6 (0x23) +define ES_SCANCODE_7 (0x24) +define ES_SCANCODE_8 (0x25) +define ES_SCANCODE_9 (0x26) +define ES_SCANCODE_0 (0x27) + +define ES_SCANCODE_ENTER (0x28) +define ES_SCANCODE_ESCAPE (0x29) +define ES_SCANCODE_BACKSPACE (0x2A) +define ES_SCANCODE_TAB (0x2B) +define ES_SCANCODE_SPACE (0x2C) + +define ES_SCANCODE_HYPHEN (0x2D) +define ES_SCANCODE_EQUALS (0x2E) +define ES_SCANCODE_LEFT_BRACE (0x2F) +define ES_SCANCODE_RIGHT_BRACE (0x30) +define ES_SCANCODE_COMMA (0x36) +define ES_SCANCODE_PERIOD (0x37) +define ES_SCANCODE_SLASH (0x38) +define ES_SCANCODE_PUNCTUATION_1 (0x31) // On US keyboard, \| +define ES_SCANCODE_PUNCTUATION_2 (0x32) // Not on US keyboard +define ES_SCANCODE_PUNCTUATION_3 (0x33) // On US keyboard, ;: +define ES_SCANCODE_PUNCTUATION_4 (0x34) // On US keyboard, '" +define ES_SCANCODE_PUNCTUATION_5 (0x35) // On US keyboard, `~ +define ES_SCANCODE_PUNCTUATION_6 (0x64) // Not on US keyboard + +define ES_SCANCODE_F1 (0x3A) +define ES_SCANCODE_F2 (0x3B) +define ES_SCANCODE_F3 (0x3C) +define ES_SCANCODE_F4 (0x3D) +define ES_SCANCODE_F5 (0x3E) +define ES_SCANCODE_F6 (0x3F) +define ES_SCANCODE_F7 (0x40) +define ES_SCANCODE_F8 (0x41) +define ES_SCANCODE_F9 (0x42) +define ES_SCANCODE_F10 (0x43) +define ES_SCANCODE_F11 (0x44) +define ES_SCANCODE_F12 (0x45) +define ES_SCANCODE_F13 (0x68) +define ES_SCANCODE_F14 (0x69) +define ES_SCANCODE_F15 (0x6A) +define ES_SCANCODE_F16 (0x6B) +define ES_SCANCODE_F17 (0x6C) +define ES_SCANCODE_F18 (0x6D) +define ES_SCANCODE_F19 (0x6E) +define ES_SCANCODE_F20 (0x6F) +define ES_SCANCODE_F21 (0x70) +define ES_SCANCODE_F22 (0x71) +define ES_SCANCODE_F23 (0x72) +define ES_SCANCODE_F24 (0x73) + +define ES_SCANCODE_CAPS_LOCK (0x39) +define ES_SCANCODE_PRINT_SCREEN (0x46) +define ES_SCANCODE_SCROLL_LOCK (0x47) +define ES_SCANCODE_PAUSE (0x48) +define ES_SCANCODE_INSERT (0x49) +define ES_SCANCODE_HOME (0x4A) +define ES_SCANCODE_PAGE_UP (0x4B) +define ES_SCANCODE_DELETE (0x4C) +define ES_SCANCODE_END (0x4D) +define ES_SCANCODE_PAGE_DOWN (0x4E) +define ES_SCANCODE_RIGHT_ARROW (0x4F) +define ES_SCANCODE_LEFT_ARROW (0x50) +define ES_SCANCODE_DOWN_ARROW (0x51) +define ES_SCANCODE_UP_ARROW (0x52) +define ES_SCANCODE_NUM_LOCK (0x53) +define ES_SCANCODE_CONTEXT_MENU (0x65) +define ES_SCANCODE_SYSTEM_REQUEST (0x9A) + +define ES_SCANCODE_ACTION_EXECUTE (0x74) +define ES_SCANCODE_ACTION_HELP (0x75) +define ES_SCANCODE_ACTION_MENU (0x76) +define ES_SCANCODE_ACTION_SELECT (0x77) +define ES_SCANCODE_ACTION_STOP (0x78) +define ES_SCANCODE_ACTION_AGAIN (0x79) +define ES_SCANCODE_ACTION_UNDO (0x7A) +define ES_SCANCODE_ACTION_CUT (0x7B) +define ES_SCANCODE_ACTION_COPY (0x7C) +define ES_SCANCODE_ACTION_PASTE (0x7D) +define ES_SCANCODE_ACTION_FIND (0x7E) +define ES_SCANCODE_ACTION_CANCEL (0x9B) +define ES_SCANCODE_ACTION_CLEAR (0x9C) +define ES_SCANCODE_ACTION_PRIOR (0x9D) +define ES_SCANCODE_ACTION_RETURN (0x9E) +define ES_SCANCODE_ACTION_SEPARATOR (0x9F) + +define ES_SCANCODE_MM_MUTE (0x7F) +define ES_SCANCODE_MM_LOUDER (0x80) +define ES_SCANCODE_MM_QUIETER (0x81) +define ES_SCANCODE_MM_NEXT (0x103) +define ES_SCANCODE_MM_PREVIOUS (0x104) +define ES_SCANCODE_MM_STOP (0x105) +define ES_SCANCODE_MM_PAUSE (0x106) +define ES_SCANCODE_MM_SELECT (0x107) +define ES_SCANCODE_MM_EMAIL (0x108) +define ES_SCANCODE_MM_CALC (0x109) +define ES_SCANCODE_MM_FILES (0x10A) + +define ES_SCANCODE_INTERNATIONAL_1 (0x87) +define ES_SCANCODE_INTERNATIONAL_2 (0x88) +define ES_SCANCODE_INTERNATIONAL_3 (0x89) +define ES_SCANCODE_INTERNATIONAL_4 (0x8A) +define ES_SCANCODE_INTERNATIONAL_5 (0x8B) +define ES_SCANCODE_INTERNATIONAL_6 (0x8C) +define ES_SCANCODE_INTERNATIONAL_7 (0x8D) +define ES_SCANCODE_INTERNATIONAL_8 (0x8E) +define ES_SCANCODE_INTERNATIONAL_9 (0x8F) + +define ES_SCANCODE_HANGUL_ENGLISH_TOGGLE (0x90) +define ES_SCANCODE_HANJA_CONVERSION (0x91) +define ES_SCANCODE_KATAKANA (0x92) +define ES_SCANCODE_HIRAGANA (0x93) +define ES_SCANCODE_HANKAKU_ZENKAKU_TOGGLE (0x94) +define ES_SCANCODE_ALTERNATE_ERASE (0x99) + +define ES_SCANCODE_THOUSANDS_SEPARATOR (0xB2) +define ES_SCANCODE_DECIMAL_SEPARATOR (0xB3) +define ES_SCANCODE_CURRENCY_UNIT (0xB4) +define ES_SCANCODE_CURRENCY_SUBUNIT (0xB5) + +define ES_SCANCODE_NUM_DIVIDE (0x54) +define ES_SCANCODE_NUM_MULTIPLY (0x55) +define ES_SCANCODE_NUM_SUBTRACT (0x56) +define ES_SCANCODE_NUM_ADD (0x57) +define ES_SCANCODE_NUM_ENTER (0x58) +define ES_SCANCODE_NUM_1 (0x59) +define ES_SCANCODE_NUM_2 (0x5A) +define ES_SCANCODE_NUM_3 (0x5B) +define ES_SCANCODE_NUM_4 (0x5C) +define ES_SCANCODE_NUM_5 (0x5D) +define ES_SCANCODE_NUM_6 (0x5E) +define ES_SCANCODE_NUM_7 (0x5F) +define ES_SCANCODE_NUM_8 (0x60) +define ES_SCANCODE_NUM_9 (0x61) +define ES_SCANCODE_NUM_0 (0x62) +define ES_SCANCODE_NUM_POINT (0x63) +define ES_SCANCODE_NUM_EQUALS (0x67) +define ES_SCANCODE_NUM_COMMA (0x82) +define ES_SCANCODE_NUM_00 (0xB0) +define ES_SCANCODE_NUM_000 (0xB1) +define ES_SCANCODE_NUM_LEFT_PAREN (0xB6) +define ES_SCANCODE_NUM_RIGHT_PAREN (0xB7) +define ES_SCANCODE_NUM_LEFT_BRACE (0xB8) +define ES_SCANCODE_NUM_RIGHT_BRACE (0xB9) +define ES_SCANCODE_NUM_TAB (0xBA) +define ES_SCANCODE_NUM_BACKSPACE (0xBB) +define ES_SCANCODE_NUM_A (0xBC) +define ES_SCANCODE_NUM_B (0xBD) +define ES_SCANCODE_NUM_C (0xBE) +define ES_SCANCODE_NUM_D (0xBF) +define ES_SCANCODE_NUM_E (0xC0) +define ES_SCANCODE_NUM_F (0xC1) +define ES_SCANCODE_NUM_XOR (0xC2) +define ES_SCANCODE_NUM_CARET (0xC3) +define ES_SCANCODE_NUM_PERCENT (0xC4) +define ES_SCANCODE_NUM_LESS_THAN (0xC5) +define ES_SCANCODE_NUM_GREATER_THAN (0xC6) +define ES_SCANCODE_NUM_AMPERSAND (0xC7) +define ES_SCANCODE_NUM_DOUBLE_AMPERSAND (0xC8) +define ES_SCANCODE_NUM_BAR (0xC9) +define ES_SCANCODE_NUM_DOUBLE_BAR (0xCA) +define ES_SCANCODE_NUM_COLON (0xCB) +define ES_SCANCODE_NUM_HASH (0xCC) +define ES_SCANCODE_NUM_SPACE (0xCD) +define ES_SCANCODE_NUM_AT (0xCE) +define ES_SCANCODE_NUM_EXCLAMATION_MARK (0xCF) +define ES_SCANCODE_NUM_MEMORY_STORE (0xD0) +define ES_SCANCODE_NUM_MEMORY_RECALL (0xD1) +define ES_SCANCODE_NUM_MEMORY_CLEAR (0xD2) +define ES_SCANCODE_NUM_MEMORY_ADD (0xD3) +define ES_SCANCODE_NUM_MEMORY_SUBTRACT (0xD4) +define ES_SCANCODE_NUM_MEMORY_MULTIPLY (0xD5) +define ES_SCANCODE_NUM_MEMORY_DIVIDE (0xD6) +define ES_SCANCODE_NUM_NEGATE (0xD7) +define ES_SCANCODE_NUM_CLEAR_ALL (0xD8) +define ES_SCANCODE_NUM_CLEAR (0xD9) +define ES_SCANCODE_NUM_BINARY (0xDA) +define ES_SCANCODE_NUM_OCTAL (0xDB) +define ES_SCANCODE_NUM_DECIMAL (0xDC) +define ES_SCANCODE_NUM_HEXADECIMAL (0xDD) + +define ES_SCANCODE_LEFT_CTRL (0xE0) +define ES_SCANCODE_LEFT_SHIFT (0xE1) +define ES_SCANCODE_LEFT_ALT (0xE2) +define ES_SCANCODE_LEFT_FLAG (0xE3) +define ES_SCANCODE_RIGHT_CTRL (0xE4) +define ES_SCANCODE_RIGHT_SHIFT (0xE5) +define ES_SCANCODE_RIGHT_ALT (0xE6) +define ES_SCANCODE_RIGHT_FLAG (0xE7) + +define ES_SCANCODE_ACPI_POWER (0x100) +define ES_SCANCODE_ACPI_SLEEP (0x101) +define ES_SCANCODE_ACPI_WAKE (0x102) + +define ES_SCANCODE_WWW_SEARCH (0x10B) +define ES_SCANCODE_WWW_HOME (0x10C) +define ES_SCANCODE_WWW_BACK (0x10D) +define ES_SCANCODE_WWW_FORWARD (0x10E) +define ES_SCANCODE_WWW_STOP (0x10F) +define ES_SCANCODE_WWW_REFRESH (0x110) +define ES_SCANCODE_WWW_STARRED (0x111) + +define ES_PROCESS_STATE_ALL_THREADS_TERMINATED (1) +define ES_PROCESS_STATE_TERMINATING (2) +define ES_PROCESS_STATE_CRASHED (4) +define ES_PROCESS_STATE_PINGED (8) + +define ES_FLAGS_DEFAULT (0) +define ES_SUCCESS (-1) + +// TODO Cleanup. +define ES_ERROR_BUFFER_TOO_SMALL (-2) +define ES_ERROR_UNKNOWN (-7) +define ES_ERROR_NO_MESSAGES_AVAILABLE (-9) +define ES_ERROR_MESSAGE_QUEUE_FULL (-10) +define ES_ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME (-14) +define ES_ERROR_PATH_NOT_TRAVERSABLE (-15) +define ES_ERROR_FILE_ALREADY_EXISTS (-19) +define ES_ERROR_FILE_DOES_NOT_EXIST (-20) +define ES_ERROR_DRIVE_ERROR_FILE_DAMAGED (-21) +define ES_ERROR_ACCESS_NOT_WITHIN_FILE_BOUNDS (-22) +define ES_ERROR_FILE_PERMISSION_NOT_GRANTED (-23) +define ES_ERROR_FILE_IN_EXCLUSIVE_USE (-24) +define ES_ERROR_FILE_CANNOT_GET_EXCLUSIVE_USE (-25) +define ES_ERROR_INCORRECT_NODE_TYPE (-26) +define ES_ERROR_EVENT_NOT_SET (-27) +define ES_ERROR_FILE_HAS_WRITERS (-28) +define ES_ERROR_TIMEOUT_REACHED (-29) +define ES_ERROR_FILE_ON_READ_ONLY_VOLUME (-32) +define ES_ERROR_INVALID_DIMENSIONS (-34) +define ES_ERROR_DRIVE_CONTROLLER_REPORTED (-35) +define ES_ERROR_COULD_NOT_ISSUE_PACKET (-36) +define ES_ERROR_HANDLE_TABLE_FULL (-37) +define ES_ERROR_COULD_NOT_RESIZE_FILE (-38) +define ES_ERROR_DIRECTORY_NOT_EMPTY (-39) +define ES_ERROR_NODE_DELETED (-41) +define ES_ERROR_VOLUME_MISMATCH (-43) +define ES_ERROR_TARGET_WITHIN_SOURCE (-44) +define ES_ERROR_TARGET_INVALID_TYPE (-45) +define ES_ERROR_MALFORMED_NODE_PATH (-47) +define ES_ERROR_TARGET_IS_SOURCE (-49) +define ES_ERROR_INVALID_NAME (-50) +define ES_ERROR_CORRUPT_DATA (-51) +define ES_ERROR_INSUFFICIENT_RESOURCES (-52) +define ES_ERROR_UNSUPPORTED_FEATURE (-53) +define ES_ERROR_FILE_TOO_FRAGMENTED (-54) +define ES_ERROR_DRIVE_FULL (-55) +define ES_ERROR_COULD_NOT_RESOLVE_SYMBOL (-56) +define ES_ERROR_ALREADY_EMBEDDED (-57) +define ES_ERROR_EVENT_SINK_OVERFLOW (-58) +define ES_ERROR_EVENT_SINK_DUPLICATE (-59) +define ES_ERROR_UNSUPPORTED_CONVERSION (-60) +define ES_ERROR_SOURCE_EMPTY (-61) +define ES_ERROR_UNSUPPORTED_EXECUTABLE (-62) +define ES_ERROR_NO_ADDRESS_FOR_DOMAIN_NAME (-63) +define ES_ERROR_NO_CONNECTED_NETWORK_INTERFACES (-64) +define ES_ERROR_BAD_DOMAIN_NAME (-65) +define ES_ERROR_LOST_IP_ADDRESS (-66) +define ES_ERROR_CONNECTION_RESET (-67) +define ES_ERROR_CONNECTION_REFUSED (-68) +define ES_ERROR_ILLEGAL_PATH (-69) +define ES_ERROR_INVALID_CLIPBOARD (-70) +define ES_ERROR_NODE_NOT_LOADED (-71) +define ES_ERROR_DIRECTORY_ENTRY_BEING_REMOVED (-72) + +define ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND (0) +define ES_SYSTEM_CONSTANT_NO_FANCY_GRAPHICS (1) +define ES_SYSTEM_CONSTANT_RIGHT_TO_LEFT (2) +define ES_SYSTEM_CONSTANT_WINDOW_INSET (3) +define ES_SYSTEM_CONSTANT_CONTAINER_TAB_BAND_HEIGHT (4) +define ES_SYSTEM_CONSTANT_UI_SCALE (5) +define ES_SYSTEM_CONSTANT_BORDER_THICKNESS (6) +define ES_SYSTEM_CONSTANT_OPTIMAL_WORK_QUEUE_THREAD_COUNT (7) +define ES_SYSTEM_CONSTANT_COUNT (8) + +define ES_INVALID_HANDLE ((EsHandle) (0)) +define ES_CURRENT_THREAD ((EsHandle) (0x10)) +define ES_CURRENT_PROCESS ((EsHandle) (0x11)) + +define ES_WAIT_NO_TIMEOUT (-1) +define ES_MAX_WAIT_COUNT (8) +define ES_MAX_EVENT_FORWARD_COUNT (4) // The maximum number of event sinks an event can be forwarded to. +define ES_MAX_EVENT_SINK_BUFFER_SIZE (256) // The maximum number of events an event sink can capture. TODO Make this configurable in EsEventSinkCreate? + +define ES_MAX_DIRECTORY_CHILD_NAME_LENGTH (256) + +define ES_PROCESS_EXECUTABLE_NOT_LOADED (0) +define ES_PROCESS_EXECUTABLE_FAILED_TO_LOAD (1) +define ES_PROCESS_EXECUTABLE_LOADED (2) + +define ES_SNAPSHOT_MAX_PROCESS_NAME_LENGTH (31) +define ES_SYSTEM_SNAPSHOT_PROCESSES (1) + +define ES_HANDLED (-1) +define ES_REJECTED (-2) + +define ES_LIST_VIEW_COLUMN_SORT_ASCENDING (1) +define ES_LIST_VIEW_COLUMN_SORT_DESCENDING (2) + +define ES_SHARED_MEMORY_NAME_MAX_LENGTH (32) +define ES_MAP_OBJECT_ALL (0) + +define ES_TEXT_H_LEFT (1 << 0) // Keep in sync with designer.c. +define ES_TEXT_H_CENTER (1 << 1) +define ES_TEXT_H_RIGHT (1 << 2) +define ES_TEXT_V_TOP (1 << 3) +define ES_TEXT_V_CENTER (1 << 4) +define ES_TEXT_V_BOTTOM (1 << 5) +define ES_TEXT_ELLIPSIS (1 << 6) +define ES_TEXT_WRAP (1 << 7) +define ES_TEXT_PLAN_SINGLE_USE (1 << 8) +define ES_TEXT_PLAN_TRIM_SPACES (1 << 9) +define ES_TEXT_PLAN_RTL (1 << 10) +define ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES (1 << 11) +define ES_TEXT_PLAN_NO_FONT_SUBSTITUTION (1 << 12) + +define ES_FILE_READ_SHARED (0x1) // Read-only. The file can still be opened for writing. +define ES_FILE_READ (0x2) // Read-only. The file will not openable for writing. This will fail if the file is already opened for writing. +define ES_FILE_WRITE (0x4) // Read-write. The file can still be opened for writing. This will fail if the file is already opened for exclusive writing. +define ES_FILE_WRITE_EXCLUSIVE (0x8) // Read-write. The file will not openable for writing. This will fail if the file is already opened for writing. + +define ES_NODE_FILE (0) +define ES_NODE_DIRECTORY (0x10) +define ES_NODE_INVALID (0x20) + +define ES_NODE_FAIL_IF_FOUND (0x001000) +define ES_NODE_FAIL_IF_NOT_FOUND (0x002000) +define ES_NODE_PREVENT_RESIZE (0x004000) +define ES_NODE_CREATE_DIRECTORIES (0x008000) // Create the directories leading to the file, if they don't already exist. + +define _ES_NODE_FROM_WRITE_EXCLUSIVE (0x020000) +define _ES_NODE_DIRECTORY_WRITE (0x040000) +define _ES_NODE_NO_WRITE_BASE (0x080000) + +define ES_DIRECTORY_CHILDREN_UNKNOWN ((EsFileOffsetDifference) (-1)) + +define ES_MEMORY_OPEN_FAIL_IF_FOUND (0x1000) +define ES_MEMORY_OPEN_FAIL_IF_NOT_FOUND (0x2000) + +define ES_MAP_OBJECT_READ_WRITE (0) +define ES_MAP_OBJECT_READ_ONLY (1) +define ES_MAP_OBJECT_COPY_ON_WRITE (2) + +define ES_STRING_FORMAT_ENOUGH_SPACE ((_EsLongConstant) (-1)) + +// Flags set with %f. +define ES_STRING_FORMAT_SIMPLE (1 << 0) + +define ES_PERMISSION_NETWORKING (1 << 0) +define ES_PERMISSION_PROCESS_CREATE (1 << 2) +define ES_PERMISSION_PROCESS_OPEN (1 << 3) +define ES_PERMISSION_SCREEN_MODIFY (1 << 4) +define ES_PERMISSION_SHUTDOWN (1 << 5) +define ES_PERMISSION_TAKE_SYSTEM_SNAPSHOT (1 << 6) +define ES_PERMISSION_SYSTEM_CONFIGURATION_WRITE (1 << 7) +define ES_PERMISSION_GET_VOLUME_INFORMATION (1 << 8) +define ES_PERMISSION_WINDOW_MANAGER (1 << 9) +define ES_PERMISSION_POSIX_SUBSYSTEM (1 << 10) +define ES_PERMISSION_ALL ((_EsLongConstant) (-1)) +define ES_PERMISSION_INHERIT ((_EsLongConstant) (1) << 63) + +define ES_PANEL_BAND_SIZE_DEFAULT (-1) + +// Element flags - bits 0-31 for custom use; bits 32-63 common to all elements. + +define ES_ELEMENT_FOCUSABLE ((_EsLongConstant) (1) << 32) +define ES_ELEMENT_HIDDEN ((_EsLongConstant) (1) << 33) // Hides the element and descendents. + // Also prevents the element and descendents from taking focus or being hovered/pressed. +define ES_ELEMENT_DISABLED ((_EsLongConstant) (1) << 34) // Prevents the element from taking focus or being pressed. +define ES_ELEMENT_DEBUG ((_EsLongConstant) (1) << 35) // Prints some extra debug information about the element. +define ES_ELEMENT_NO_CLIP ((_EsLongConstant) (1) << 36) // Do not clip children to the element's bounds. +define ES_ELEMENT_NO_HOVER ((_EsLongConstant) (1) << 37) // For z-stacked elements. Can still hover descendents and overlapped siblings/parent. + // Override HIT_TEST to return false to prevent hovering descendents. +define ES_ELEMENT_NO_HOVER_DESCENDENTS ((_EsLongConstant) (1) << 38) // Prevent hovering over any descendents. +define ES_ELEMENT_BLOCK_FOCUS ((_EsLongConstant) (1) << 39) // This element and descendents cannot take focus. +define ES_ELEMENT_NOT_TAB_TRAVERSABLE ((_EsLongConstant) (1) << 40) // Use with ES_ELEMENT_FOCUSABLE to indicate the element cannot be focused from tab traversal. +define ES_ELEMENT_CENTER_ACCESS_KEY_HINT ((_EsLongConstant) (1) << 41) // TODO Make this more customizable with a message perhaps? +define ES_ELEMENT_LAYOUT_HINT_HORIZONTAL ((_EsLongConstant) (1) << 42) // Hint for autoCorners and autoBorders. +define ES_ELEMENT_LAYOUT_HINT_REVERSE ((_EsLongConstant) (1) << 43) // Hint for autoCorners and autoBorders; and tab traversal. +define ES_ELEMENT_STICKY_ACCESS_KEY ((_EsLongConstant) (1) << 44) // Don't exit access key mode after using the access key. +define ES_ELEMENT_NON_CLIENT ((_EsLongConstant) (1) << 45) + +// For children of splitters: +define ES_CELL_COLLAPSABLE ((_EsLongConstant) (1) << 51) + +define ES_CELL_H_PUSH ((_EsLongConstant) (1) << 54) +define ES_CELL_H_EXPAND ((_EsLongConstant) (1) << 55) +define ES_CELL_H_SHRINK ((_EsLongConstant) (1) << 56) +define ES_CELL_H_LEFT ((_EsLongConstant) (1) << 57) +define ES_CELL_H_RIGHT ((_EsLongConstant) (1) << 58) +define ES_CELL_V_PUSH ((_EsLongConstant) (1) << 59) +define ES_CELL_V_EXPAND ((_EsLongConstant) (1) << 60) +define ES_CELL_V_SHRINK ((_EsLongConstant) (1) << 61) +define ES_CELL_V_TOP ((_EsLongConstant) (1) << 62) +define ES_CELL_V_BOTTOM ((_EsLongConstant) (1) << 63) + +define ES_PANEL_STACK (0) // Default. +define ES_PANEL_SWITCHER (1 << 0) +define ES_PANEL_Z_STACK (1 << 1) +define ES_PANEL_TABLE (1 << 2) + +define ES_PANEL_H_SCROLL_FIXED (1 << 4) +define ES_PANEL_V_SCROLL_FIXED (1 << 5) +define ES_PANEL_H_SCROLL_AUTO (1 << 6) +define ES_PANEL_V_SCROLL_AUTO (1 << 7) + +// For ES_PANEL_TABLE and ES_PANEL_STACK. +define ES_PANEL_VERTICAL (0) // Default. +define ES_PANEL_HORIZONTAL (1 << 8) +define ES_PANEL_REVERSE (1 << 9) // Reverse layout is not supported with ES_PANEL_TABLE yet. + +// For ES_PANEL_TABLE. +define ES_PANEL_H_LEFT (1 << 16) +define ES_PANEL_H_RIGHT (1 << 17) +define ES_PANEL_H_CENTER (1 << 18) +define ES_PANEL_H_JUSTIFY (1 << 19) +define ES_PANEL_V_TOP (1 << 20) +define ES_PANEL_V_BOTTOM (1 << 21) +define ES_PANEL_V_CENTER (1 << 22) +define ES_PANEL_V_JUSTIFY (1 << 23) + +define ES_TEXTBOX_MULTILINE (1 << 0) +define ES_TEXTBOX_EDIT_BASED (1 << 1) +define ES_TEXTBOX_MARGIN (1 << 2) +define ES_TEXTBOX_NO_SMART_CONTEXT_MENUS (1 << 3) +define ES_TEXTBOX_ALLOW_TABS (1 << 4) +define ES_TEXTBOX_REJECT_EDIT_IF_LOST_FOCUS (1 << 5) + +define ES_TEXTBOX_FIND_BACKWARDS (1 << 0) + +define ES_TEXTBOX_GET_CONTENTS_SELECTED_ONLY (1 << 0) + +// First few bits reserved for the check state. +define ES_BUTTON_DEFAULT (1 << 3) +define ES_BUTTON_MENU_ITEM (1 << 4) +define ES_BUTTON_NOT_FOCUSABLE (1 << 5) +define ES_BUTTON_TOOLBAR (1 << 6) +define ES_BUTTON_DROPDOWN (1 << 7) +define ES_BUTTON_COMPACT (1 << 8) +define ES_MENU_ITEM_HEADER (1 << 9) +define ES_BUTTON_CHECKBOX (1 << 10) +define ES_BUTTON_RADIOBOX (1 << 11) +define ES_BUTTON_CANCEL (1 << 12) +define ES_BUTTON_PUSH (1 << 13) +define ES_MENU_ITEM_CHECKED (1 << 14) + +define ES_COLOR_WELL_HAS_OPACITY (1 << 0) + +define ES_SCROLLBAR_VERTICAL (0 << 0) +define ES_SCROLLBAR_HORIZONTAL (1 << 0) + +define ES_SPLITTER_VERTICAL (0 << 0) +define ES_SPLITTER_HORIZONTAL (1 << 0) + +define ES_LIST_VIEW_HORIZONTAL (1 << 0) // Layout horizontally instead of vertically. +define ES_LIST_VIEW_VARIABLE_SIZE (1 << 1) // Each item can be a different size. + // You need to respond to the LIST_VIEW_MEASURE_ITEM message. + // The size of items cannot depend on the size of the parent. +define ES_LIST_VIEW_TILED (1 << 2) // Multiple items per band. Incompatible with variable size items, columns mode and non-linear mode. +define ES_LIST_VIEW_NON_LINEAR (1 << 3) // Indices within a group are arbitrary values. They must be stable. + // You need to respond to LIST_VIEW_NEXT_INDEX, LIST_VIEW_PREVIOUS_INDEX, + // LIST_VIEW_COMPARE_INDICES, LIST_VIEW_FIRST_INDEX, and LIST_VIEW_LAST_INDEX messages. + // If the flag is not set, indices are consecutive within a group, starting at 0. + // These are non-stable, but are updated automatically when items are inserted/removed. +define ES_LIST_VIEW_SINGLE_SELECT (1 << 4) // One item can be selected. By default, selections are disabled. +define ES_LIST_VIEW_MULTI_SELECT (1 << 5) // Multiple items can be selected. +define ES_LIST_VIEW_CHOICE_SELECT (1 << 6) // Exactly one item is always selected. Dragging on the list view causes the selection to 'slide' between items. +define ES_LIST_VIEW_COLUMNS (1 << 7) // Display a column header and let items have multiple values. Incompatible with horizontal and tiled layouts. +define ES_LIST_VIEW_FIXED_ITEMS (1 << 8) // Use the fixed item API rather than the callback API. +define ES_LIST_VIEW_CENTER_TILES (1 << 9) // Center tiled items. + +define ES_LIST_VIEW_GROUP_HAS_HEADER (1 << 0) // The first item in the group is a header. +define ES_LIST_VIEW_GROUP_HAS_FOOTER (1 << 1) // The last item in the group is a footer. +define ES_LIST_VIEW_GROUP_INDENT (1 << 2) // Indent the group's items (excluding the header and footer). +define ES_LIST_VIEW_GROUP_COLLAPSABLE (1 << 3) // The group can be collapsed. + +define ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED (1 << 0) // The column's contents is right-aligned. +define ES_LIST_VIEW_COLUMN_HAS_MENU (1 << 1) // The header can be clicked to open a menu. +define ES_LIST_VIEW_COLUMN_TABULAR (1 << 2) // Use tabular text figures (so that digits are aligned). + +define ES_MENU_AT_CURSOR (1 << 0) +define ES_MENU_MAXIMUM_HEIGHT (1 << 1) + +define ES_FONT_SANS (0xFFFF) +define ES_FONT_SERIF (0xFFFE) +define ES_FONT_MONOSPACED (0xFFFD) + +define ES_FONT_REGULAR (4) +define ES_FONT_BOLD (7) + +define ES_TEXT_FIGURE_DEFAULT (0) +define ES_TEXT_FIGURE_OLD (1) +define ES_TEXT_FIGURE_TABULAR (2) + +define ES_TEXT_DECORATION_UNDERLINE (1 << 0) +define ES_TEXT_DECORATION_STRIKE_THROUGH (1 << 1) + +define ES_TEXT_DISPLAY_RICH_TEXT (1 << 0) +define ES_TEXT_DISPLAY_PREFORMATTED (1 << 1) // Prevents trimming of trailing/leading whitespace. +define ES_TEXT_DISPLAY_NO_FONT_SUBSTITUTION (1 << 2) + +define ES_LIST_DISPLAY_BULLETED (0 << 0) // Default. +define ES_LIST_DISPLAY_NUMBERED (1 << 0) +define ES_LIST_DISPLAY_LOWER_ALPHA (2 << 0) +define ES_LIST_DISPLAY_CUSTOM_MARKER (0xFF << 0) // Sends ES_MSG_LIST_DISPLAY_GET_MARKER. +define ES_LIST_DISPLAY_MARKER_TYPE_MASK (0xFF << 0) + +define ES_IMAGE_DISPLAY_DECODE_WHEN_NEEDED (1 << 0) // The image is only kept in its decoded state when the display is on-screen. +define ES_IMAGE_DISPLAY_MANUAL_SIZE (1 << 1) // The display will be manually sized; its size does not depend on the loaded image. + +define ES_COMMAND_SYSTEM_START (0xF0000000) +define ES_COMMAND_DELETE (0xF0000001) +define ES_COMMAND_SELECT_ALL (0xF0000002) +define ES_COMMAND_CUT (0xF0000003) +define ES_COMMAND_COPY (0xF0000004) +define ES_COMMAND_PASTE (0xF0000005) +define ES_COMMAND_UNDO (0xF0000006) +define ES_COMMAND_REDO (0xF0000007) +define ES_COMMAND_SAVE (0xF0000008) +define ES_COMMAND_SHOW_IN_FILE_MANAGER (0xF0000009) + +// Some common layouts... +define ES_CELL_FILL (ES_CELL_H_FILL | ES_CELL_V_FILL) +define ES_CELL_H_FILL (ES_CELL_H_PUSH | ES_CELL_H_EXPAND | ES_CELL_H_SHRINK) +define ES_CELL_V_FILL (ES_CELL_V_PUSH | ES_CELL_V_EXPAND | ES_CELL_V_SHRINK) +define ES_CELL_CENTER (ES_CELL_H_CENTER | ES_CELL_V_CENTER) +define ES_CELL_PUSH (ES_CELL_H_PUSH | ES_CELL_V_PUSH) +define ES_CELL_EXPAND (ES_CELL_H_EXPAND | ES_CELL_V_EXPAND) +define ES_CELL_CORNER (ES_CELL_H_LEFT | ES_CELL_V_TOP) +define ES_CELL_SHRINK (ES_CELL_H_SHRINK | ES_CELL_V_SHRINK) +define ES_CELL_H_CENTER (ES_CELL_H_LEFT | ES_CELL_H_RIGHT) +define ES_CELL_V_CENTER (ES_CELL_V_TOP | ES_CELL_V_BOTTOM) + +// Mask bits for EsThemeMetrics: +define ES_THEME_METRICS_INSETS (1 << 0) +define ES_THEME_METRICS_CLIP_INSETS (1 << 1) +define ES_THEME_METRICS_GLOBAL_OFFSET (1 << 2) +define ES_THEME_METRICS_CLIP_ENABLED (1 << 3) +define ES_THEME_METRICS_CURSOR (1 << 4) +define ES_THEME_METRICS_ENTRANCE_TRANSITION (1 << 5) +define ES_THEME_METRICS_EXIT_TRANSITION (1 << 6) +define ES_THEME_METRICS_ENTRANCE_DURATION (1 << 7) +define ES_THEME_METRICS_EXIT_DURATION (1 << 8) +define ES_THEME_METRICS_PREFERRED_WIDTH (1 << 9) +define ES_THEME_METRICS_PREFERRED_HEIGHT (1 << 10) +define ES_THEME_METRICS_MINIMUM_WIDTH (1 << 11) +define ES_THEME_METRICS_MINIMUM_HEIGHT (1 << 12) +define ES_THEME_METRICS_MAXIMUM_WIDTH (1 << 13) +define ES_THEME_METRICS_MAXIMUM_HEIGHT (1 << 14) +define ES_THEME_METRICS_GAP_MAJOR (1 << 15) +define ES_THEME_METRICS_GAP_MINOR (1 << 16) +define ES_THEME_METRICS_GAP_WRAP (1 << 17) +define ES_THEME_METRICS_GAP_ALL (ES_THEME_METRICS_GAP_MAJOR | ES_THEME_METRICS_GAP_MINOR | ES_THEME_METRICS_GAP_WRAP) +define ES_THEME_METRICS_TEXT_COLOR (1 << 18) +define ES_THEME_METRICS_SELECTED_BACKGROUND (1 << 19) +define ES_THEME_METRICS_SELECTED_TEXT (1 << 20) +define ES_THEME_METRICS_ICON_COLOR (1 << 21) +define ES_THEME_METRICS_TEXT_ALIGN (1 << 22) +define ES_THEME_METRICS_TEXT_SIZE (1 << 23) +define ES_THEME_METRICS_FONT_FAMILY (1 << 24) +define ES_THEME_METRICS_FONT_WEIGHT (1 << 25) +define ES_THEME_METRICS_ICON_SIZE (1 << 26) +define ES_THEME_METRICS_IS_ITALIC (1 << 27) +define ES_THEME_METRICS_ELLIPSIS (1 << 28) +define ES_THEME_METRICS_LAYOUT_VERTICAL (1 << 29) + +define ES_WINDOW_MOVE_MAXIMISED (1 << 0) +define ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN (1 << 1) +define ES_WINDOW_MOVE_HIDDEN (1 << 2) +define ES_WINDOW_MOVE_ALWAYS_ON_TOP (1 << 3) +define ES_WINDOW_MOVE_AT_BOTTOM (1 << 4) +define ES_WINDOW_MOVE_UPDATE_SCREEN (1 << 5) +define ES_WINDOW_MOVE_DYNAMIC (1 << 6) + +define ES_WINDOW_SOLID_TRUE (1 << 0) +define ES_WINDOW_SOLID_NO_ACTIVATE (1 << 1) +define ES_WINDOW_SOLID_NO_BRING_TO_FRONT (1 << 2) + +define ES_THEME_CURSORS_WIDTH (264) +define ES_THEME_CURSORS_HEIGHT (128) +define ES_THEME_CURSORS_NAME "Desktop.ThemeCursors" + +define ES_TEXTBOX_MOVE_CARET_SINGLE (2) +define ES_TEXTBOX_MOVE_CARET_WORD (3) +define ES_TEXTBOX_MOVE_CARET_LINE (4) +define ES_TEXTBOX_MOVE_CARET_VERTICAL (5) +define ES_TEXTBOX_MOVE_CARET_ALL (6) +define ES_TEXTBOX_MOVE_CARET_FIRST_ONLY (1 << 8) +define ES_TEXTBOX_MOVE_CARET_SECOND_ONLY (1 << 9) +define ES_TEXTBOX_MOVE_CARET_BACKWARDS (1 << 10) +define ES_TEXTBOX_MOVE_CARET_STRONG_WHITESPACE (1 << 11) + +define ES_GAME_CONTROLLER_MAX_COUNT (16) + +define ES_DOMAIN_NAME_MAX_LENGTH (255) +define ES_ECHO_REQUEST_MAX_LENGTH (48) + +define ES_CONNECTION_OPEN_WAIT (1 << 0) + +define ES_FILE_CONTROL_NOTIFY_MONITORS (1 << 0) +define ES_FILE_CONTROL_FLUSH (1 << 1) + +define ES_ELEMENT_UPDATE_CONTENT_WIDTH (1 << 0) +define ES_ELEMENT_UPDATE_CONTENT_HEIGHT (1 << 1) + +define ES_DIALOG_ALERT_OK_BUTTON (1 << 0) + +define ES_MEMORY_RESERVE_COMMIT_ALL (1 << 0) + +define ES_PANEL_SWITCHER_DESTROY_PREVIOUS_AFTER_TRANSITION (1 << 0) + +define ES_ELEMENT_TRANSITION_ENTRANCE (1 << 0) + +define ES_TEXT_GET_CHARACTER_AT_POINT_MIDDLE (1 << 0) + +define ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C (1) +define ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI (2) + +define ES_DRAW_LINE_CAP_ROUND (1 << 0) +define ES_DRAW_LINE_CAP_SQUARE (1 << 1) + +define ES_INSTANCE_CLASS_VIEWER (0) // Default. +define ES_INSTANCE_CLASS_EDITOR (1) + +define ES_MOUNT_POINT_MAX_COUNT (256) + +define ES_DRIVE_TYPE_OTHER (0) +define ES_DRIVE_TYPE_HDD (1) +define ES_DRIVE_TYPE_SSD (2) +define ES_DRIVE_TYPE_CDROM (3) +define ES_DRIVE_TYPE_USB_MASS_STORAGE (4) + +define ES_ELEMENT_FOCUS_ENSURE_VISIBLE (1 << 0) +define ES_ELEMENT_FOCUS_FROM_KEYBOARD (1 << 1) + +define ES_APPLICATION_STARTUP_MANUAL_PATH (1 << 0) +define ES_APPLICATION_STARTUP_SINGLE_INSTANCE_IN_PROCESS (1 << 1) + +define ES_LIST_VIEW_INLINE_TEXTBOX_COPY_EXISTING_TEXT (1 << 0) +define ES_LIST_VIEW_INLINE_TEXTBOX_REJECT_EDIT_IF_FOCUS_LOST (1 << 1) + +define ES_DRAW_CONTENT_MARKER_DOWN_ARROW (1 << 0) +define ES_DRAW_CONTENT_MARKER_UP_ARROW (1 << 1) +define ES_DRAW_CONTENT_TABULAR (1 << 2) +define ES_DRAW_CONTENT_RICH_TEXT (1 << 3) + +define ES_MODIFIER_CTRL (1 << 0) +define ES_MODIFIER_SHIFT (1 << 1) +define ES_MODIFIER_ALT (1 << 2) +define ES_MODIFIER_FLAG (1 << 3) + +define ES_PROCESS_CREATE_PAUSED (1 << 0) + +define ES_THREAD_EVENT_MUTEX_ACQUIRE (1) +define ES_THREAD_EVENT_MUTEX_RELEASE (2) + +define ES_WINDOW_PROPERTY_SOLID (0x01) // Standard window properties. +define ES_WINDOW_PROPERTY_OPAQUE_BOUNDS (0x02) +define ES_WINDOW_PROPERTY_BLUR_BOUNDS (0x03) +define ES_WINDOW_PROPERTY_ALPHA (0x04) +define ES_WINDOW_PROPERTY_FOCUSED (0x05) +define ES_WINDOW_PROPERTY_MATERIAL (0x06) +define ES_WINDOW_PROPERTY_EMBED (0x07) +define ES_WINDOW_PROPERTY_OBJECT (0x81) // Embedded window properties. +define ES_WINDOW_PROPERTY_EMBED_OWNER (0x82) +define ES_WINDOW_PROPERTY_RESIZE_CLEAR_COLOR (0x83) + +define ES_DRAW_BITMAP_OPAQUE (0xFFFF) +define ES_DRAW_BITMAP_XOR (0xFFFE) +define ES_DRAW_BITMAP_BLEND (0) + +include desktop/icons.header + +enum EsFatalError { + ES_FATAL_ERROR_ABORT + ES_FATAL_ERROR_INCORRECT_FILE_ACCESS + ES_FATAL_ERROR_INCORRECT_NODE_TYPE + ES_FATAL_ERROR_INSUFFICIENT_PERMISSIONS + ES_FATAL_ERROR_INVALID_BUFFER + ES_FATAL_ERROR_INVALID_HANDLE + ES_FATAL_ERROR_INVALID_MEMORY_REGION + ES_FATAL_ERROR_OUT_OF_RANGE // A parameter exceeds a limit, or is not a valid choice from an enumeration. + ES_FATAL_ERROR_PROCESSOR_EXCEPTION + ES_FATAL_ERROR_RECURSIVE_BATCH + ES_FATAL_ERROR_UNKNOWN_SYSCALL + ES_FATAL_ERROR_COUNT +} + +enum EsSyscallType { + // Memory. + + ES_SYSCALL_MEMORY_ALLOCATE + ES_SYSCALL_MEMORY_FREE + ES_SYSCALL_MEMORY_SHARE + ES_SYSCALL_MEMORY_MAP_OBJECT + ES_SYSCALL_MEMORY_OPEN + ES_SYSCALL_MEMORY_COMMIT + ES_SYSCALL_MEMORY_FAULT_RANGE + + // Processing. + + ES_SYSCALL_PROCESS_CREATE + ES_SYSCALL_PROCESS_GET_CREATION_ARGUMENT + ES_SYSCALL_THREAD_TERMINATE + ES_SYSCALL_THREAD_CREATE + ES_SYSCALL_WAIT + ES_SYSCALL_PROCESS_TERMINATE + ES_SYSCALL_EVENT_CREATE + ES_SYSCALL_EVENT_SET + ES_SYSCALL_EVENT_RESET + ES_SYSCALL_PROCESS_PAUSE + ES_SYSCALL_PROCESS_CRASH + ES_SYSCALL_THREAD_GET_ID + ES_SYSCALL_PROCESS_GET_STATE + ES_SYSCALL_YIELD_SCHEDULER + ES_SYSCALL_SLEEP + ES_SYSCALL_PROCESS_OPEN + ES_SYSCALL_PROCESS_SET_TLS + ES_SYSCALL_PROCESS_GET_TLS + ES_SYSCALL_PROCESS_GET_STATUS + ES_SYSCALL_EVENT_FORWARD + ES_SYSCALL_EVENT_SINK_CREATE + ES_SYSCALL_EVENT_SINK_POP + ES_SYSCALL_EVENT_SINK_PUSH + ES_SYSCALL_THREAD_STACK_SIZE + ES_SYSCALL_PROCESS_SHARE + + // Windowing. + + ES_SYSCALL_MESSAGE_GET + ES_SYSCALL_MESSAGE_POST + ES_SYSCALL_MESSAGE_WAIT + + ES_SYSCALL_CURSOR_POSITION_GET + ES_SYSCALL_CURSOR_POSITION_SET + ES_SYSCALL_GAME_CONTROLLER_STATE_POLL + ES_SYSCALL_EYEDROP_START + + ES_SYSCALL_SCREEN_WORK_AREA_SET + ES_SYSCALL_SCREEN_WORK_AREA_GET + ES_SYSCALL_SCREEN_BOUNDS_GET + ES_SYSCALL_SCREEN_FORCE_UPDATE + + ES_SYSCALL_WINDOW_CREATE + ES_SYSCALL_WINDOW_CLOSE + ES_SYSCALL_WINDOW_REDRAW + ES_SYSCALL_WINDOW_MOVE + ES_SYSCALL_WINDOW_GET_ID + ES_SYSCALL_WINDOW_GET_BOUNDS + ES_SYSCALL_WINDOW_SET_BITS + ES_SYSCALL_WINDOW_SET_CURSOR + ES_SYSCALL_WINDOW_SET_PROPERTY + + ES_SYSCALL_MESSAGE_DESKTOP + + ES_SYSCALL_CLIPBOARD_ADD + ES_SYSCALL_CLIPBOARD_HAS + ES_SYSCALL_CLIPBOARD_READ + + // IO. + + ES_SYSCALL_NODE_OPEN + ES_SYSCALL_FILE_READ_SYNC + ES_SYSCALL_FILE_WRITE_SYNC + ES_SYSCALL_FILE_RESIZE + ES_SYSCALL_FILE_GET_SIZE + ES_SYSCALL_DIRECTORY_ENUMERATE + ES_SYSCALL_NODE_DELETE + ES_SYSCALL_NODE_MOVE + ES_SYSCALL_FILE_CONTROL + ES_SYSCALL_NODE_SHARE + ES_SYSCALL_VOLUME_GET_INFORMATION + + // Networking. + + ES_SYSCALL_DOMAIN_NAME_RESOLVE + ES_SYSCALL_ECHO_REQUEST + ES_SYSCALL_CONNECTION_OPEN + ES_SYSCALL_CONNECTION_POLL + ES_SYSCALL_CONNECTION_NOTIFY + + // IPC. + + ES_SYSCALL_CONSTANT_BUFFER_READ + ES_SYSCALL_CONSTANT_BUFFER_SHARE + ES_SYSCALL_CONSTANT_BUFFER_CREATE + ES_SYSCALL_PIPE_CREATE + ES_SYSCALL_PIPE_WRITE + ES_SYSCALL_PIPE_READ + + // System information. + + ES_SYSCALL_SYSTEM_GET_CONSTANTS + ES_SYSCALL_SYSTEM_TAKE_SNAPSHOT + ES_SYSCALL_SYSTEM_CONFIGURATION_WRITE + ES_SYSCALL_SYSTEM_CONFIGURATION_READ + + // Misc. + + ES_SYSCALL_PRINT + ES_SYSCALL_HANDLE_CLOSE + ES_SYSCALL_BATCH + ES_SYSCALL_SHUTDOWN + ES_SYSCALL_POSIX + ES_SYSCALL_DEBUG_COMMAND + + // End. + + ES_SYSCALL_COUNT +} + +enum EsMessageType { + // Window manager messages (don't rearrange; see SendMessageToWindow in kernel/window_manager.cpp): + ES_MSG_WM_START = 0x1000 + ES_MSG_MOUSE_MOVED = 0x1001 + ES_MSG_WINDOW_ACTIVATED = 0x1002 + ES_MSG_WINDOW_DEACTIVATED = 0x1003 + ES_MSG_WINDOW_DESTROYED = 0x1004 + ES_MSG_MOUSE_EXIT = 0x1006 + ES_MSG_WINDOW_RESIZED = 0x1007 + ES_MSG_MOUSE_LEFT_DOWN = 0x1008 // Return ES_REJECTED to prevent taking focus, even if ES_ELEMENT_FOCUSABLE is set. Propagates. + ES_MSG_MOUSE_LEFT_UP = 0x1009 // Propagates. + ES_MSG_MOUSE_RIGHT_DOWN = 0x100A // Propagates. + ES_MSG_MOUSE_RIGHT_UP = 0x100B // Propagates. + ES_MSG_MOUSE_MIDDLE_DOWN = 0x100C // Propagates. + ES_MSG_MOUSE_MIDDLE_UP = 0x100D // Propagates. + ES_MSG_KEY_DOWN = 0x100E // Propagates to ancestors if unhandled. + ES_MSG_KEY_UP = 0x100F + ES_MSG_UPDATE_WINDOW = 0x1010 + ES_MSG_PRIMARY_CLIPBOARD_UPDATED = 0x1011 + ES_MSG_WM_END = 0x13FF + + // Internal GUI messages: // None of these should be sent directly. + ES_MSG_PAINT = 0x2000 // Paint the element using the painter specified in the message. + ES_MSG_PAINT_BACKGROUND = 0x2001 // Paint the element's background. Sent before ES_MSG_PAINT. + // If unhandled, the background is drawn using the default settings. + // The width/height parameters of EsPainter may be larger than expected - this includes the 'non-client' area. + ES_MSG_GET_CURSOR = 0x2003 // Get the cursor for the element. + ES_MSG_ANIMATE = 0x2004 // Animate the element. Returns the number of microseconds to wait for the next frame, + // or whether the animation is complete. + ES_MSG_Z_ORDER = 0x2005 // Get the child of an element based on its Z-order. + ES_MSG_DESTROY = 0x2006 // The element has been marked to be destroyed. Free any resources allocated. + // Sent after the parent receives its ES_MSG_REMOVE_CHILD message. + ES_MSG_GET_WIDTH = 0x2007 // Measure the element's width. If known, the height is specified. + ES_MSG_GET_HEIGHT = 0x2008 // Measure the element's height. If known, the width is specified. + ES_MSG_LAYOUT = 0x2009 // The size of the element has been updated. Layout the element's children. + ES_MSG_ENSURE_VISIBLE = 0x200A // Center the specified child (where possible) in your scrolled viewport. + ES_MSG_ADD_CHILD = 0x200B // An element has been created with this element as its parent. + ES_MSG_REMOVE_CHILD = 0x200C // An element has been destroyed with this element as its parent. + // Sent before the child receives its ES_MSG_DESTROY message. + // It will be removed from the `children` later (but before the next ES_MSG_LAYOUT message is received). + ES_MSG_PRE_ADD_CHILD = 0x200D // An element has been created with this element as its parent, but is not yet added to the parent. + ES_MSG_HIT_TEST = 0x200E // For non-rectangular elements: test whether a pixel should be considered inside the element. Set response to ES_HANDLED. + ES_MSG_KEY_TYPED = 0x2011 // Sent to the focused element when a key is pressed. + ES_MSG_SCROLL_X = 0x2012 // The element has been horizontally scrolled. + ES_MSG_SCROLL_Y = 0x2013 // The element has been vertically scrolled. + ES_MSG_STRONG_FOCUS_END = 0x2014 // Sent once when the user 'clicks off' the element, even if a new element was not necessarily focused. + ES_MSG_BEFORE_Z_ORDER = 0x2015 // Sent before a batch of Z_ORDER messages. + ES_MSG_AFTER_Z_ORDER = 0x2016 // Sent after a batch of Z_ORDER messages. + ES_MSG_PAINT_CHILDREN = 0x2017 // Paint the element's children. Useful for animations, with EsPaintTargetTake/Return. + ES_MSG_DESTROY_CONTENTS = 0x2018 // Sent after EsElementDestroyContents is called. + ES_MSG_GET_INSPECTOR_INFORMATION = 0x2019 // Get a string containing information about the element to display in the inspector. + ES_MSG_NOT_VISIBLE = 0x2020 // Sent to elements in the check visible list when they move off-screen. + ES_MSG_GET_CHILD_STYLE_VARIANT = 0x2021 // Allows the parent of an element to customize its default style. + ES_MSG_PAINT_ICON = 0x2022 // Sent during EsDrawContent. + ES_MSG_MOUSE_LEFT_CLICK = 0x2023 // Indicates the element has been "clicked" (might be different for other input devices). + ES_MSG_MOUSE_RIGHT_CLICK = 0x2024 // Right click, similar to LEFT_CLICK above. + ES_MSG_MOUSE_MIDDLE_CLICK = 0x2025 // Middle click, similar to LEFT_CLICK above. + ES_MSG_MOUSE_LEFT_DRAG = 0x2026 // Left button is pressed and the mouse is moving. + // Only starts being sent after a threshold is reached. + // This will NOT be sent if the element did not handle LEFT_DOWN. + ES_MSG_MOUSE_RIGHT_DRAG = 0x2027 // Similar to LEFT_DRAG above, but for the right button. + ES_MSG_MOUSE_MIDDLE_DRAG = 0x2028 // Similar to LEFT_DRAG above, but for the middle button. + + // State change messages: (causes a style refresh) + ES_MSG_STATE_CHANGE_MESSAGE_START = 0x2080 + ES_MSG_HOVERED_START = 0x2081 // Sent when the mouse starts hovering over an element. + ES_MSG_HOVERED_END = 0x2082 // Opposite of ES_MSG_HOVERED_START. Sent before ES_MSG_HOVERED_START is sent to the new hovered element. + ES_MSG_PRESSED_START = 0x2083 // Sent when an element is pressed. + ES_MSG_PRESSED_END = 0x2084 // Opposite of ES_MSG_PRESSED_START. + ES_MSG_FOCUSED_START = 0x2085 // Sent when an element is focused. + ES_MSG_FOCUSED_END = 0x2086 // Opposite of ES_MSG_FOCUSED_START. + ES_MSG_FOCUS_WITHIN_START = 0x2087 // Sent when an element is focused. + ES_MSG_FOCUS_WITHIN_END = 0x2088 // Opposite of ES_MSG_FOCUSED_START. + ES_MSG_STATE_CHANGE_MESSAGE_END = 0x20FF + + // Element messages: + ES_MSG_SCROLLBAR_MOVED = 0x3000 // The scrollbar has been moved. + ES_MSG_CHECK_UPDATED = 0x3001 // Button's check state has changed. See message->checkState. + ES_MSG_RADIO_GROUP_UPDATED = 0x3002 // Sent to all siblings of a radiobox when it is checked, so they can uncheck themselves. + ES_MSG_COLOR_CHANGED = 0x3003 // Color well's color has changed. See message->colorChanged. + ES_MSG_LIST_DISPLAY_GET_MARKER = 0x3004 // Get the string for a marker in an EsListDisplay. See message->getContent. + ES_MSG_REORDER_ITEM_TEST = 0x3005 + + // Desktop messages: + ES_MSG_POWER_BUTTON_PRESSED = 0x4801 + ES_MSG_EMBEDDED_WINDOW_DESTROYED = 0x4802 + ES_MSG_SET_SCREEN_RESOLUTION = 0x4803 + ES_MSG_REGISTER_FILE_SYSTEM = 0x4804 + ES_MSG_UNREGISTER_FILE_SYSTEM = 0x4805 + ES_MSG_DESKTOP = 0x4806 + + // Messages sent from Desktop to application instances: + ES_MSG_TAB_INSPECT_UI = 0x4A01 + ES_MSG_TAB_CLOSE_REQUEST = 0x4A02 + ES_MSG_INSTANCE_SAVE_RESPONSE = 0x4A03 // Sent by Desktop after an application requested to save its document. + ES_MSG_INSTANCE_DOCUMENT_RENAMED = 0x4A04 + ES_MSG_INSTANCE_DOCUMENT_UPDATED = 0x4A05 + + // Debugger messages: + ES_MSG_APPLICATION_CRASH = 0x4C00 + ES_MSG_PROCESS_TERMINATED = 0x4C01 + + // Undo item messages: + ES_MSG_UNDO_CANCEL = 0x4D00 + ES_MSG_UNDO_INVOKE = 0x4D01 + ES_MSG_UNDO_TO_STRING = 0x4D02 + + // Misc messages: + ES_MSG_EYEDROP_REPORT = 0x5001 + ES_MSG_SYSTEM_CONSTANT_UPDATED = 0x5002 + ES_MSG_TIMER = 0x5003 + ES_MSG_PING = 0x5004 // Sent by Desktop to check processes are processing messages. + ES_MSG_WAKEUP = 0x5005 // Sent to wakeup the message thread, so that it can process locally posted messages. + + // File Manager messages: + ES_MSG_FILE_MANAGER_FILE_MODIFIED = 0x5100 + + // Textbox messages: + ES_MSG_TEXTBOX_UPDATED = 0x5200 + ES_MSG_TEXTBOX_EDIT_START = 0x5201 // Set ES_TEXTBOX_EDIT_BASED to receive. + ES_MSG_TEXTBOX_EDIT_END = 0x5202 // Set ES_TEXTBOX_EDIT_BASED to receive. + ES_MSG_TEXTBOX_NUMBER_DRAG_START = 0x5203 // For EsTextboxUseNumberOverlay. + ES_MSG_TEXTBOX_NUMBER_DRAG_END = 0x5204 // For EsTextboxUseNumberOverlay. + ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA = 0x5205 // For EsTextboxUseNumberOverlay. + ES_MSG_TEXTBOX_NUMBER_UPDATED = 0x5206 // For EsTextboxUseNumberOverlay with defaultBehaviour=true. + ES_MSG_TEXTBOX_GET_BREADCRUMB = 0x5207 // For EsTextboxUseBreadcrumbOverlay. + ES_MSG_TEXTBOX_ACTIVATE_BREADCRUMB = 0x5208 // For EsTextboxUseBreadcrumbOverlay. + + // List view messages: + ES_MSG_LIST_VIEW_COMPARE_INDICES = 0x5300 + ES_MSG_LIST_VIEW_NEXT_INDEX = 0x5301 + ES_MSG_LIST_VIEW_PREVIOUS_INDEX = 0x5302 + ES_MSG_LIST_VIEW_FIRST_INDEX = 0x5303 + ES_MSG_LIST_VIEW_LAST_INDEX = 0x5304 + ES_MSG_LIST_VIEW_FIND_INDEX = 0x5305 + ES_MSG_LIST_VIEW_COUNT_ITEMS = 0x5306 + ES_MSG_LIST_VIEW_MEASURE_RANGE = 0x5307 + ES_MSG_LIST_VIEW_MEASURE_ITEM = 0x5308 + ES_MSG_LIST_VIEW_CREATE_ITEM = 0x5309 + ES_MSG_LIST_VIEW_GET_CONTENT = 0x530A + ES_MSG_LIST_VIEW_GET_INDENT = 0x530B + ES_MSG_LIST_VIEW_FIND_POSITION = 0x530C + ES_MSG_LIST_VIEW_IS_SELECTED = 0x530D + ES_MSG_LIST_VIEW_SELECT = 0x530E + ES_MSG_LIST_VIEW_SELECT_RANGE = 0x530F + ES_MSG_LIST_VIEW_CHOOSE_ITEM = 0x5310 + ES_MSG_LIST_VIEW_SEARCH = 0x5311 + ES_MSG_LIST_VIEW_CONTEXT_MENU = 0x5312 + ES_MSG_LIST_VIEW_COLUMN_MENU = 0x5313 + ES_MSG_LIST_VIEW_GET_SUMMARY = 0x5314 + ES_MSG_LIST_VIEW_GET_COLUMN_SORT = 0x5315 + + // Application messages: + ES_MSG_APPLICATION_EXIT = 0x7001 + ES_MSG_INSTANCE_CREATE = 0x7002 + ES_MSG_INSTANCE_OPEN = 0x7003 + ES_MSG_INSTANCE_SAVE = 0x7004 + ES_MSG_INSTANCE_DESTROY = 0x7005 + + // User messages: + ES_MSG_USER_START = 0x8000 + ES_MSG_USER_END = 0xBFFF +} + +enum EsCursorStyle { + ES_CURSOR_NORMAL + ES_CURSOR_TEXT + ES_CURSOR_RESIZE_VERTICAL + ES_CURSOR_RESIZE_HORIZONTAL + ES_CURSOR_RESIZE_DIAGONAL_1 // '/' + ES_CURSOR_RESIZE_DIAGONAL_2 // '\' + ES_CURSOR_SPLIT_VERTICAL + ES_CURSOR_SPLIT_HORIZONTAL + ES_CURSOR_HAND_HOVER + ES_CURSOR_HAND_DRAG + ES_CURSOR_HAND_POINT + ES_CURSOR_SCROLL_UP_LEFT + ES_CURSOR_SCROLL_UP + ES_CURSOR_SCROLL_UP_RIGHT + ES_CURSOR_SCROLL_LEFT + ES_CURSOR_SCROLL_CENTER + ES_CURSOR_SCROLL_RIGHT + ES_CURSOR_SCROLL_DOWN_LEFT + ES_CURSOR_SCROLL_DOWN + ES_CURSOR_SCROLL_DOWN_RIGHT + ES_CURSOR_SELECT_LINES + ES_CURSOR_DROP_TEXT + ES_CURSOR_CROSS_HAIR_PICK + ES_CURSOR_CROSS_HAIR_RESIZE + ES_CURSOR_MOVE_HOVER + ES_CURSOR_MOVE_DRAG + ES_CURSOR_ROTATE_HOVER + ES_CURSOR_ROTATE_DRAG + ES_CURSOR_BLANK + ES_CURSOR_COUNT +} + +enum EsWindowStyle { + ES_WINDOW_NORMAL + ES_WINDOW_CONTAINER + ES_WINDOW_MENU + ES_WINDOW_TIP + ES_WINDOW_PLAIN + ES_WINDOW_INSPECTOR +} + +enum EsCheckState { + ES_CHECK_UNCHECKED = 0 + ES_CHECK_CHECKED = 1 + ES_CHECK_INDETERMINATE = 2 +} + +enum EsTransitionType { + ES_TRANSITION_NONE + ES_TRANSITION_SLIDE_UP + ES_TRANSITION_SLIDE_DOWN + ES_TRANSITION_COVER_UP + ES_TRANSITION_COVER_DOWN + ES_TRANSITION_SQUISH_UP + ES_TRANSITION_SQUISH_DOWN + ES_TRANSITION_ZOOM_OUT + ES_TRANSITION_ZOOM_IN + ES_TRANSITION_ZOOM_OUT_LIGHT + ES_TRANSITION_ZOOM_IN_LIGHT + ES_TRANSITION_REVEAL_UP + ES_TRANSITION_REVEAL_DOWN + ES_TRANSITION_FADE_IN + ES_TRANSITION_FADE_OUT + ES_TRANSITION_SLIDE_UP_OVER + ES_TRANSITION_SLIDE_DOWN_OVER + ES_TRANSITION_SLIDE_UP_UNDER + ES_TRANSITION_SLIDE_DOWN_UNDER +} + +enum EsMemoryProtection { + ES_MEMORY_PROTECTION_READ_ONLY + ES_MEMORY_PROTECTION_READ_WRITE + ES_MEMORY_PROTECTION_EXECUTABLE +} + +enum EsClipboard { + ES_CLIPBOARD_PRIMARY +} + +function_pointer int EsUICallbackFunction(struct EsElement *element, struct EsMessage *message); + +struct EsBuffer { + union { const uint8_t *in; uint8_t *out; }; + size_t position, bytes; + void *context; + bool error, canGrow; +}; + +struct EsElementPublic { + EsUICallbackFunction messageUser; + EsCString cName; + EsGeneric userData; + char accessKey; // Upper-case. + + // These fields are read-only! + EsWindow *window; + ES_INSTANCE_TYPE *instance; + uint64_t flags; // Bits 0-31: specific to the type of element; bits 32-63: common to all elements. +}; + +struct EsBatchCall { + EsSyscallType index; + bool stopBatchIfError; + union { uintptr_t argument0, returnValue; }; + uintptr_t argument1, argument2, argument3; +} + +struct EsThreadInformation { + EsHandle handle; + uint64_t tid; +} + +struct EsProcessInformation { + EsHandle handle; + uintptr_t pid; + EsThreadInformation mainThread; +} + +struct EsUniqueIdentifier { + // Don't mess with this structure, it's used in filesystems. + uint8_t d[16]; +} + +struct EsFileInformation { + EsHandle handle; + EsFileOffset size; + EsError error; +} + +struct EsDirectoryChild { + char name[ES_MAX_DIRECTORY_CHILD_NAME_LENGTH]; + size_t nameBytes; + EsNodeType type; + EsFileOffsetDifference fileSize; // -1 if unsupported. + EsFileOffsetDifference directoryChildren; // ES_DIRECTORY_CHILDREN_UNKNOWN if unsupported. +} + +struct EsPoint { + int32_t x; + int32_t y; +} + +struct EsRectangle { + int32_t l; // Inclusive. + int32_t r; // Exclusive. + int32_t t; // Inclusive. + int32_t b; // Exclusive. +} + +struct EsSpinlock { + volatile uint8_t state; +} + +struct EsMutex { + EsHandle event; + EsSpinlock spinlock; + volatile uint8_t state; + volatile uint32_t queued; +} + +struct EsCrashReason { + EsFatalError errorCode; + EsSyscallType duringSystemCall; +} + +struct EsProcessState { + EsCrashReason crashReason; + uint64_t id; + uint8_t executableState; + uint8_t flags; +} + +struct EsPainter { + EsRectangle clip; + int offsetX, offsetY, width, height; + void *style; + EsPaintTarget *target; +} + +struct EsDebuggerMessage { + EsHandle process; + EsCrashReason reason; +} + +struct EsSnapshotProcessesItem { + int64_t pid, memoryUsage, cpuTimeSlices, idleTimeSlices, handleCount; + char name[ES_SNAPSHOT_MAX_PROCESS_NAME_LENGTH]; + uint8_t nameBytes; + bool isKernel; +} + +struct EsSnapshotProcesses { + size_t count; + EsSnapshotProcessesItem processes[]; +} + +struct EsMountPoint { + char prefix[16]; + size_t prefixBytes; + EsHandle base; + bool write; +}; + +struct EsProcessCreationArguments { + EsHandle executable; + + const void *environmentBlock; + size_t environmentBlockBytes; + + EsGeneric creationArgument; + uint64_t permissions; + + EsMountPoint *initialMountPoints; + size_t initialMountPointCount; + + uint64_t flags; +}; + +struct EsInstance { + // Read-only variables. + void *_private; + EsWindow *window; + EsUndoManager *undoManager; +}; + +struct EsPanelBand { + int preferredSize, minimumSize, maximumSize; + int push, pull; // Similar to flex-grow and flex-shrink from CSS. +} + +struct EsThemeMetrics { + uint64_t mask; + EsRectangle insets, clipInsets; + EsRectangle globalOffset; + int clipEnabled, cursor, entranceTransition, exitTransition; + int entranceDuration, exitDuration; + int preferredWidth, preferredHeight; + int minimumWidth, minimumHeight; + int maximumWidth, maximumHeight; + int gapMajor, gapMinor, gapWrap; + uint32_t textColor, selectedBackground, selectedText, iconColor; + int textAlign, textSize, fontFamily, fontWeight, iconSize; + bool isItalic, ellipsis, layoutVertical; +}; + +struct EsThemeAppearance { + bool enabled; + uint32_t backgroundColor; + uint32_t borderColor; + EsRectangle borderSize; +}; + +struct EsStyle { + void *inherit; + EsThemeMetrics metrics; + EsThemeAppearance appearance; +}; + +include desktop/styles.header + +struct EsFont { + EsFontFamily family; + uint8_t weight; + bool italic; +} + +struct EsTextStyle { + // TODO Indicating RTL/vertical writing modes. + // TODO Support for features. + // TODO Support for variable fonts. + + EsFont font; + uint16_t size, baselineOffset; + + int8_t tracking; + uint8_t figures; + bool alternateDirection; + + // Render properties: + uint8_t blur; + uint8_t decorations; + uint32_t color; + uint32_t decorationsColor; +}; + +struct EsTextRun { + EsTextStyle style; + uint32_t offset; +}; + +struct EsTextPlanProperties { + EsCString cLanguage; + uint32_t flags; + int maxLines; // Set to 0 for no limit. +}; + +struct EsTextSelection { + ptrdiff_t caret0, caret1; + bool hideCaret, snapCaretToInsets; + uint32_t foreground, background; +}; + +function_pointer uint32_t EsFragmentShaderCallbackFunction(int x, int y, struct EsStyledBox *box); + +struct EsStyledBox { + EsRectangle bounds, clip; + uint32_t backgroundColor, backgroundColor2; + EsFragmentShaderCallbackFunction fragmentShader; + uint32_t borderColor; + EsRectangle borders; + int cornerRadiusTopLeft, cornerRadiusTopRight, cornerRadiusBottomLeft, cornerRadiusBottomRight; +}; + +struct EsArena { + // Arenas are not thread-safe! + // You can use different arenas in different threads, though. + void *firstEmptySlot, *firstBlock; + size_t slotsPerBlock, slotSize, blockSize; +}; + +struct EsCalculationValue { + bool error; + double number; +}; + +function_pointer void EsCommandCallbackFunction(ES_INSTANCE_TYPE *instance, EsElement *element, struct EsCommand *command); + +struct EsCommand { + EsElement **elements; + EsCommandCallbackFunction callback; + bool disabled, registered, allocated; + EsCheckState check; + uint32_t stableID; + EsCString cKeyboardShortcut; + EsGeneric data; +}; + +struct EsListViewColumn { + STRING title; + uint32_t flags; + int width; +}; + +struct EsApplicationStartupInformation { + int64_t id; + STRING filePath; + EsWindow *targetWindow; + uint32_t flags; + EsHandle readHandle; // Internal use. +}; + +struct EsINIState { + char *buffer, *sectionClass, *section, *key, *value; + size_t bytes, sectionClassBytes, sectionBytes, keyBytes, valueBytes; +}; + +struct EsSystemConfigurationItem { + char *key, *value; + size_t keyBytes, valueBytes; +}; + +struct EsSystemConfigurationGroup { + char *section, *sectionClass; + size_t sectionBytes, sectionClassBytes; + EsSystemConfigurationItem *items; + size_t itemCount; +}; + +struct EsAnalogInput { + uint8_t x, y, z; +}; + +struct EsGameControllerState { + uint64_t id; + uint8_t buttonCount, analogCount; // Number of buttons and analog inputs. + uint8_t directionalPad; // Directions given from 0-7, starting at up, going clockwise. 15 indicates unpressed. + uint32_t buttons; // Bitset of pressed buttons. + EsAnalogInput analog[8]; +}; + +struct EsPCIDevice { + uint32_t deviceID; + uint8_t classCode, subclassCode, progIF; + uint8_t bus, slot, function; + uint8_t interruptPin, interruptLine; + size_t baseAddressesSizes[6]; + uint32_t baseAddresses[6]; + char driverName[64]; + size_t driverNameBytes; +}; + +struct EsAddress { + union { + struct { + uint32_t ipv4; + uint16_t port; + }; + + uint8_t d[20]; + }; +}; + +struct EsConnection { + EsAddress address; + size_t receiveBufferBytes; + size_t sendBufferBytes; + + uint8_t *receiveBuffer; + uint8_t *sendBuffer; + + uintptr_t receiveWritePointer; + uintptr_t sendReadPointer; + bool open; + EsError error; + + uintptr_t receiveReadPointer; + uintptr_t sendWritePointer; + + EsHandle handle; +}; + +struct EsFileMenuSettings { +}; + +struct EsInstanceClassEditorSettings { + STRING newDocumentFileName; // The default file name to use when creating a new document. + STRING newDocumentTitle; // The title to show in the file menu if the document has not been saved. + uint32_t documentIconID; +}; + +struct EsInstanceClassViewerSettings { +}; + +struct _EsNodeInformation { + EsHandle handle; + EsFileOffset fileSize; + EsFileOffsetDifference directoryChildren; + EsNodeType type; +}; + +struct EsVolumeInformation { + char label[64]; + uint8_t labelBytes; + uint8_t driveType; + uint64_t id; + EsFileOffset spaceTotal; + EsFileOffset spaceUsed; +}; + +// User interface messages. + +struct EsMessageMouseMotion { + int newPositionX; + int newPositionY; + int originalPositionX; // For MOUSE_DRAGGED only. + int originalPositionY; +}; + +struct EsMessageMouseButton { + int positionX; + int positionY; + uint8_t clickChainCount; +}; + +struct EsMessageKeyboard { + uint32_t scancode; + uint8_t modifiers; + bool repeat, numpad, numlock; +}; + +struct EsMessageAnimate { + int64_t deltaMs, waitMs; + bool complete; +}; + +struct EsMessageLayout { + bool sizeChanged; +}; + +struct EsMessageWindowResized { + EsRectangle content; + bool hidden; +}; + +struct EsMessageMeasure { + int width, height; +}; + +struct EsMessageHitTest { + int x, y; + bool inside; +}; + +struct EsMessageZOrder { + uintptr_t index; + EsElement *child; +}; + +struct EsMessageBeforeZOrder { + uintptr_t start, end, nonClient; + EsRectangle clip; +}; + +struct EsMessageItemToString { + EsGeneric item; + STRING text; +}; + +// List view messages. + +struct EsMessageCompareIndices { + int32_t group; + EsGeneric left, right; + int result; +}; + +struct EsMessageIterateIndex { + int32_t group; + EsGeneric index; + + // FIND_INDEX and FIND_POSITION: (TODO Pass the reference item?) + int64_t position; +}; + +struct EsMessageItemRange { + int32_t group; + EsGeneric firstIndex, lastIndex; + int64_t result; +}; + +struct EsMessageMeasureItem { + int32_t group; + EsGeneric index; + int64_t result; +}; + +struct EsMessageCreateItem { + int32_t group; + EsGeneric index; + EsElement *item; +}; + +struct EsMessageGetContent { + EsGeneric index; + int32_t group; + uint32_t icon; + EsBuffer *buffer; + bool richText; + uint8_t column; +}; + +struct EsMessageGetIndent { + int32_t group; + EsGeneric index; + + uint8_t indent; +}; + +struct EsMessageSelectRange { + EsGeneric fromIndex, toIndex; + int32_t group; + bool select, toggle; +}; + +struct EsMessageSelectItem { + int32_t group; + EsGeneric index; + bool isSelected; +}; + +struct EsMessageChooseItem { + int32_t group; + EsGeneric index; +}; + +struct EsMessageSearchItem { + int32_t group; + EsGeneric index; + STRING query; +}; + +struct EsMessageFocus { + uint32_t flags; +}; + +struct EsMessageColumnMenu { + uint8_t index; + EsElement *source; +}; + +struct EsMessageGetColumnSort { + uint8_t index; +}; + +// Specific element messages. + +struct EsMessageScrollbarMoved { + int scroll, previous; +}; + +struct EsMessageColorChanged { + uint32_t newColor; + bool pickerClosed; +}; + +struct EsMessageNumberDragDelta { + int delta; + int32_t hoverCharacter; + bool fast; +}; + +struct EsMessageNumberUpdated { + double delta; + double newValue; +}; + +struct EsMessageGetBreadcrumb { + uintptr_t index; // Set response to ES_REJECTED if this equals the number of breadcrumbs. + EsBuffer *buffer; +}; + +struct EsMessageEndEdit { + bool rejected; +}; + +// Instance messages. + +struct EsMessageInstanceOpen { + ES_INSTANCE_TYPE *instance; + EsFileStore *file; + STRING name; + bool update; +}; + +struct EsMessageInstanceSave { + ES_INSTANCE_TYPE *instance; + EsFileStore *file; +}; + +struct EsMessageInstanceDestroy { + ES_INSTANCE_TYPE *instance; +}; + +// Internal system messages. + +struct EsMessageProcessCrash { + EsCrashReason reason; + EsHandle process; + uintptr_t pid; +}; + +struct EsMessageDesktop { + uint64_t windowID; + EsHandle buffer; + size_t bytes; +}; + +struct EsMessageEyedrop { + uint32_t color; + bool cancelled; +}; + +struct EsMessageCreateInstance { + EsHandle window; + EsHandle data; + size_t dataBytes; +}; + +struct EsMessageTabOperation { + uint64_t id; + EsHandle handle; + union { size_t bytes; bool isSource; }; + EsError error; +}; + +struct EsMessageSystemConstantUpdated { + uintptr_t index; + uint64_t newValue; +}; + +struct EsMessageRegisterFileSystem { + EsHandle rootDirectory; + bool isBootFileSystem; + EsMountPoint *mountPoint; +}; + +struct EsMessageUnregisterFileSystem { + uint64_t id; + EsMountPoint *mountPoint; +}; + +// Message structure. + +struct EsMessageUser { + EsGeneric context1, context2, context3, context4; +}; + +struct EsMessage { + EsMessageType type; + + union { + struct { uintptr_t _size[4]; } _size; // EsMessagePost supports messages at most 4 pointers in size. + EsMessageUser user; // For application specific messages. + + // User interface messages: + EsMessageMouseMotion mouseMoved; + EsMessageMouseMotion mouseDragged; + EsMessageMouseButton mouseDown; + EsMessageKeyboard keyboard; + EsMessageWindowResized windowResized; + EsMessageAnimate animate; + EsMessageLayout layout; + EsMessageMeasure measure; + EsMessageHitTest hitTest; + EsPainter *painter; + EsElement *child; + EsCursorStyle cursorStyle; + EsMessageZOrder zOrder; + EsMessageBeforeZOrder beforeZOrder; + EsMessageItemToString itemToString; + EsMessageFocus focus; + const EsStyle *childStyleVariant; + + // List view messages: + EsMessageCompareIndices compareIndices; + EsMessageIterateIndex iterateIndex; + EsMessageItemRange itemRange; + EsMessageMeasureItem measureItem; + EsMessageCreateItem createItem; + EsMessageGetContent getContent; + EsMessageGetIndent getIndent; + EsMessageSelectRange selectRange; + EsMessageSelectItem selectItem; + EsMessageChooseItem chooseItem; + EsMessageSearchItem searchItem; + EsMessageColumnMenu columnMenu; + EsMessageGetColumnSort getColumnSort; + + // Specific element messages: + EsMessageScrollbarMoved scrollbarMoved; + EsMessageColorChanged colorChanged; + EsMessageNumberDragDelta numberDragDelta; + EsMessageNumberUpdated numberUpdated; + EsMessageGetBreadcrumb getBreadcrumb; + EsMessageEndEdit endEdit; + uintptr_t activateBreadcrumb; + EsCheckState checkState; + + // Instance messages: + EsMessageInstanceOpen instanceOpen; + EsMessageInstanceSave instanceSave; + EsMessageInstanceDestroy instanceDestroy; + + // Internal messages: + void *_argument; + EsMessageProcessCrash crash; + EsMessageSystemConstantUpdated systemConstantUpdated; + EsMessageDesktop desktop; + EsMessageEyedrop eyedrop; + EsMessageCreateInstance createInstance; + EsMessageTabOperation tabOperation; + EsMessageRegisterFileSystem registerFileSystem; + EsMessageUnregisterFileSystem unregisterFileSystem; + }; +} + +struct _EsMessageWithObject { + void *object; + EsMessage message; +}; + +struct EsThreadEventLogEntry { + char file[31]; + uint8_t fileBytes; + char expression[31]; + uint8_t expressionBytes; + uint8_t event; + uint16_t line; + uint64_t objectID, threadID; +}; + +struct EsMemoryStatistics { + size_t fixedHeapAllocationCount; + size_t fixedHeapTotalSize; + size_t coreHeapAllocationCount; + size_t coreHeapTotalSize; + size_t cachedNodes; + size_t cachedDirectoryEntries; + size_t totalSurfaceBytes; + size_t commitPageable; + size_t commitFixed; + size_t commitLimit; + size_t commitFixedLimit; + size_t commitRemaining; + size_t maximumObjectCachePages; + size_t approximateObjectCacheSize; +}; + +struct EsFontInformation { + char name[96]; + size_t nameBytes; + char category[32]; + size_t categoryBytes; + EsFontFamily id; + uint16_t availableWeightsNormal; + uint16_t availableWeightsItalic; +}; + +// Function pointer types. + +function_pointer void EsThreadEntryFunction(EsGeneric argument); +function_pointer int EsComparisonCallbackFunction(const void *left, const void *right, EsGeneric context); +function_pointer void EsSwapCallbackFunction(const void *left, const void *right, EsGeneric context); +function_pointer int EsCRTComparisonCallback(const void *left, const void *right); +function_pointer void EsTimerCallbackFunction(EsGeneric argument); +function_pointer void EsMenuCallbackFunction(EsMenu *menu, EsGeneric context); +function_pointer void EsUndoCallback(const void *item, EsUndoManager *manager, EsMessage *message); +function_pointer void EsMountPointEnumerationCallbackFunction(const char *prefix, size_t prefixBytes, EsGeneric context); +function_pointer void EsListViewEnumerateVisibleItemsCallbackFunction(EsListView *view, EsElement *item, uint32_t group, EsGeneric index); +function_pointer void EsFontEnumerationCallbackFunction(const EsFontInformation *information, EsGeneric context); + +// System. + +function void EsApplicationStart(const EsApplicationStartupInformation *information); +function void EsApplicationRunTemporary(ES_INSTANCE_TYPE *instance, STRING path); +function EsHandle EsTakeSystemSnapshot(int type, size_t *bufferSize); +function EsInstance *_EsInstanceCreate(size_t bytes, EsMessage *message, STRING name = BLANK_STRING); +function EsError EsHandleClose(EsHandle handle); +function void EsSystemShowShutdownDialog(ES_INSTANCE_TYPE *instance); + +function void EsPOSIXInitialise(int *argc, char ***argv); +function intptr_t EsPOSIXSystemCall(intptr_t n, intptr_t a1, intptr_t a2, intptr_t a3, intptr_t a4, intptr_t a5, intptr_t a6); +function char *EsPOSIXConvertPath(const char *path, size_t *outNameLength, bool addPOSIXMountPointPrefix); + +function void EsBatch(EsBatchCall *calls, size_t count); +function uintptr_t _EsSyscall(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f); + +// Configuration and settings. + +function uint64_t EsSystemGetConstant(uintptr_t index); + +function EsSystemConfigurationGroup *EsSystemConfigurationReadAll(size_t *groupCount); // Read with the message mutex acquired. +function int64_t EsSystemConfigurationReadInteger(STRING section, STRING key, int64_t defaultValue = 0); +function int64_t EsSystemConfigurationGroupReadInteger(EsSystemConfigurationGroup *group, STRING key, int64_t defaultValue = 0); +function char *EsSystemConfigurationReadString(STRING section, STRING key, size_t *valueBytes = ES_NULL); // Free with EsHeapFree. +function char *EsSystemConfigurationGroupReadString(EsSystemConfigurationGroup *group, STRING key, size_t *valueBytes = ES_NULL); // Free with EsHeapFree. + +// INI files. + +function bool EsINIParse(EsINIState *s); +function bool EsINIPeek(EsINIState *s); +function size_t EsINIFormat(EsINIState *s, char *buffer, size_t bytes); +function void EsINIZeroTerminate(EsINIState *s); + +// File systems. + +function const void *EsEmbeddedFileGet(const char *cName, size_t *byteCount = ES_NULL); + +function ptrdiff_t EsDirectoryEnumerateChildren(STRING path, EsDirectoryChild **buffer); // Free buffer with EsHeapFree. Returns number of children. + +function void *EsFileReadAll(STRING filePath, size_t *fileSize, EsError *error = ES_NULL); // Free with EsHeapFree. +function void *EsFileReadAllFromHandle(EsHandle handle, size_t *fileSize, EsError *error = ES_NULL); // Free with EsHeapFree. +function EsError EsFileWriteAll(STRING filePath, const void *data, size_t fileSize); +function EsError EsFileWriteAllFromHandle(EsHandle handle, const void *data, size_t fileSize); +function EsError EsFileWriteAllGather(STRING filePath, const void **data, size_t *fileSize, size_t gatherCount); +function EsError EsFileWriteAllGatherFromHandle(EsHandle handle, const void **data, size_t *fileSize, size_t gatherCount); +function void *EsFileMap(STRING filePath, size_t *fileSize, uint32_t flags); + +function EsError EsFileControl(EsHandle file, uint32_t flags); +function EsFileInformation EsFileOpen(STRING path, uint32_t flags); +function EsFileOffset EsFileGetSize(EsHandle handle); +function size_t EsFileReadSync(EsHandle file, EsFileOffset offset, size_t size, void *buffer); +function EsError EsFileResize(EsHandle file, EsFileOffset newSize); +function size_t EsFileWriteSync(EsHandle file, EsFileOffset offset, size_t size, const void *buffer); + +function EsError EsPathDelete(STRING path); +function size_t EsPathFindUniqueName(char *buffer, size_t originalBytes, size_t bufferBytes); +function EsError EsPathMove(STRING oldPath, STRING newPath); +function bool EsPathExists(STRING filePath, EsNodeType *type = ES_NULL); // Returns true if the file/directory exists. +function EsError EsPathCreate(STRING filePath, EsNodeType type, bool createLeadingDirectories); +function bool EsPathQueryInformation(STRING filePath, EsDirectoryChild *information); + +function void *EsFileStoreReadAll(EsFileStore *file, size_t *fileSize); // Free with EsHeapFree. +function bool EsFileStoreWriteAll(EsFileStore *file, const void *data, size_t dataBytes); +function EsFileOffsetDifference EsFileStoreGetSize(EsFileStore *file); // Returns -1 on error. +function void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flags); + +// Requires permission_all_files. +function bool EsMountPointGetVolumeInformation(const char *prefix, size_t prefixBytes, EsVolumeInformation *information); // Returns false if the mount point does not exist. +function void EsMountPointEnumerate(EsMountPointEnumerationCallbackFunction callback, EsGeneric context); +function void _EsPathAnnouncePathMoved(EsInstance *instance, STRING oldPath, STRING newPath); + +// Processes and threads. + +function EsError EsProcessCreate(EsProcessCreationArguments *arguments, EsProcessInformation *information); +function int EsProcessGetExitStatus(EsHandle process); +function uintptr_t EsProcessGetID(EsHandle process); +function void EsProcessGetState(EsHandle process, EsProcessState *state); +function EsHandle EsProcessOpen(uint64_t pid); +function void EsProcessPause(EsHandle process, bool resume); +function void EsProcessTerminate(EsHandle process, int status); +function void EsProcessTerminateCurrent(); +function EsError EsThreadCreate(EsThreadEntryFunction entryFunction, EsThreadInformation *information, EsGeneric argument); +function uint64_t EsThreadGetID(EsHandle thread); // TODO Make this 64-bit. +function void EsThreadTerminate(EsHandle thread); + +// Memory. + +function void *EsArenaAllocate(EsArena *arena, bool zero); // Not thread-safe. +function void EsArenaFree(EsArena *arena, void *pointer); // Not thread-safe. +function void EsArenaInitialise(EsArena *arena, size_t blockSize, size_t itemSize); + +function const void *EsBufferRead(struct EsBuffer *buffer, size_t readBytes); +function const void *EsBufferReadMany(struct EsBuffer *buffer, size_t a, size_t b); +function void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writeBytes); +function void EsBufferFormat(EsBuffer *buffer, EsCString format, ...); // Appends. +function void EsBufferFormatV(EsBuffer *buffer, EsCString format, va_list arguments); // Appends. + +function EsHandle EsConstantBufferCreate(const void *data, size_t dataBytes, EsHandle targetProcess); +function void EsConstantBufferRead(EsHandle constantBuffer, void *output); +function EsHandle EsConstantBufferShare(EsHandle constantBuffer, EsHandle targetProcess); +function size_t EsConstantBufferGetSize(EsHandle constantBuffer); + +function_not_in_kernel void *EsHeapAllocate(size_t size, bool zeroMemory, EsHeap *heap = ES_NULL); +function_not_in_kernel void EsHeapFree(void *address, size_t expectedSize = 0, EsHeap *heap = ES_NULL); +function_not_in_kernel void *EsHeapReallocate(void *oldAddress, size_t newAllocationSize, bool zeroNewSpace, EsHeap *heap = ES_NULL); + +function void EsHeapValidate(); + +function bool EsMemoryCommit(void *pointer, size_t bytes); +function int EsMemoryCompare(const void *a, const void *b, size_t bytes); +function void EsMemoryCopy(void *destination, const void *source, size_t bytes); +function void EsMemoryCopyReverse(void *_destination, void *_source, size_t bytes); +function bool EsMemoryDecommit(void *pointer, size_t bytes); // May fail in low-memory conditions when the commit ranges on the region are fragmented. (Cannot fail if you decommit the entire region.) +function void EsMemoryFaultRange(const void *pointer, size_t bytes, uint32_t flags = ES_FLAGS_DEFAULT); // Simulate a page fault in each page in the range. +function void EsMemoryFill(void *from, void *to, uint8_t byte); +function void EsMemoryMove(void *_start, void *_end, intptr_t amount, bool zeroEmptySpace); +function EsHandle EsMemoryOpen(size_t size, STRING name, unsigned flags); +function void *EsMemoryReserve(size_t size, EsMemoryProtection protection = ES_MEMORY_PROTECTION_READ_WRITE, uint32_t flags = ES_MEMORY_RESERVE_COMMIT_ALL); +function EsHandle EsMemoryShare(EsHandle sharedMemoryRegion, EsHandle targetProcess, bool readOnly); +function uint8_t EsMemorySumBytes(uint8_t *data, size_t bytes); +function void EsMemoryUnreserve(void *pointer, size_t size = 0); // Must cover the entire reserved region. Leave size 0 if you don't know the size. +function void EsMemoryZero(void *destination, size_t bytes); + +function void *EsObjectMap(EsHandle object, uintptr_t offset, size_t size, unsigned flags); + +// Standard functions. + +function void EsAssertionFailure(EsCString cFile, int line); +function EsCalculationValue EsCalculateFromUserExpression(EsCString cExpression); // For user input only; do not rely on consistent behaviour across versions; use with message mutex. +function double EsDoubleParse(STRING string, char **endptr); +function uint8_t EsRandomU8(); +function uint64_t EsRandomU64(); +function int64_t EsIntegerParse(STRING text); // Parses in hexadecimal if the first two characters are '0x'. +function_not_in_kernel void EsPanic(EsCString format, ...); +function_not_in_kernel void EsPrint(EsCString format, ...); +function void EsPrintDirect(STRING string); +function void EsPrintHelloWorld(); +function void EsRandomAddEntropy(uint64_t x); +function void EsRandomSeed(uint64_t x); +function EsRectangle EsRectangleAdd(EsRectangle a, EsRectangle b); +function EsRectangle EsRectangleAddBorder(EsRectangle rectangle, EsRectangle border); +function EsRectangle EsRectangleBounding(EsRectangle a, EsRectangle b); +function EsRectangle EsRectangleCenter(EsRectangle parent, EsRectangle child); +function EsRectangle EsRectangleCut(EsRectangle a, int32_t amount, char side); +function EsRectangle EsRectangleFit(EsRectangle parent, EsRectangle child, bool allowScalingUp); // Preserves aspect ratio. +function EsRectangle EsRectangleIntersection(EsRectangle a, EsRectangle b); +function EsRectangle EsRectangleSplit(EsRectangle *a, int32_t amount, char side, int32_t gap = 0); // Same as EsRectangleCut, but the source rectangle is modified. +function EsRectangle EsRectangleSubtract(EsRectangle a, EsRectangle b); +function EsRectangle EsRectangleTranslate(EsRectangle a, EsRectangle b); +function bool EsRectangleEquals(EsRectangle a, EsRectangle b); +function bool EsRectangleContains(EsRectangle a, int32_t x, int32_t y); +function void EsSort(void *_base, size_t nmemb, size_t size, EsComparisonCallbackFunction compar, EsGeneric argument); +function void EsSortWithSwapCallback(void *_base, size_t nmemb, size_t size, EsComparisonCallbackFunction compar, EsGeneric argument, EsSwapCallbackFunction swap); +function uint64_t EsTimeStamp(); +function double EsTimeStampMs(); + +// Graphics. + +function uint32_t EsColorBlend(uint32_t under, uint32_t over, bool fullAlpha); +function uint32_t EsColorConvertToRGB(float h, float s, float v); // 0 <= hue < 6; 0 <= saturation <= 1; 0 <= value <= 1. +function bool EsColorConvertToHSV(uint32_t color, float *h, float *s, float *v); +function uint32_t EsColorParse(STRING string); + +function void EsDrawBitmap(EsPainter *painter, EsRectangle region, uint32_t *bits, uintptr_t stride, uint16_t mode); // OR mode with alpha. +function void EsDrawBitmapScaled(EsPainter *painter, EsRectangle destinationRegion, EsRectangle sourceRegion, uint32_t *bits, uintptr_t stride, uint16_t alpha); // Set alpha to 0xFFFF if source is opaque. +function void EsDrawBlock(EsPainter *painter, EsRectangle bounds, uint32_t mainColor); +function void EsDrawClear(EsPainter *painter, EsRectangle bounds); +function void EsDrawContent(EsPainter *painter, EsElement *element, EsRectangle rectangle, STRING text, uint32_t iconID = 0, uint32_t flags = ES_FLAGS_DEFAULT, EsTextSelection *selectionProperties = nullptr); +function void EsDrawInvert(EsPainter *painter, EsRectangle bounds); +function void EsDrawLine(EsPainter *painter, float *vertices, size_t vertexCount, uint32_t color, float width, uint32_t flags); // Vertices are pairs of x,y coordinates. +function void EsDrawRectangle(EsPainter *painter, EsRectangle bounds, uint32_t mainColor, uint32_t borderColor, EsRectangle borderSize); +function bool EsDrawStandardIcon(EsPainter *painter, uint32_t id, int size, EsRectangle region, uint32_t color); +function void EsDrawPaintTarget(EsPainter *painter, EsPaintTarget *source, EsRectangle destinationRegion, EsRectangle sourceRegion, uint8_t alpha); +function void EsDrawText(EsPainter *painter, EsTextPlan *plan, EsRectangle bounds, EsRectangle *clip = ES_NULL, EsTextSelection *selectionProperties = ES_NULL); +function void EsDrawTextLayers(EsPainter *painter, EsTextPlan *plan, EsRectangle bounds, EsTextSelection *selectionProperties = ES_NULL); +function void EsDrawVectorFile(EsPainter *painter, EsRectangle bounds, const void *data, size_t dataBytes); + +function uint32_t EsIconIDFromString(STRING string = BLANK_STRING); + +function uint8_t *EsImageLoad(const void *file, size_t fileSize, uint32_t *width, uint32_t *height, int imageChannels); + +function EsRectangle EsPainterBoundsClient(EsPainter *painter); +function EsRectangle EsPainterBoundsInset(EsPainter *painter); + +function void EsPaintTargetClear(EsPaintTarget *target); +function void EsPaintTargetEndDirectAccess(EsPaintTarget *target); +function void EsPaintTargetStartDirectAccess(EsPaintTarget *target, uint32_t **bits, size_t *width, size_t *height, size_t *stride); +function EsPaintTarget *EsPaintTargetCreate(size_t width, size_t height, bool hasAlphaChannel); +function EsPaintTarget *EsPaintTargetCreateFromBitmap(uint32_t *bits, size_t width, size_t height, bool hasAlphaChannel); // Do not access the bits again until calling EsPaintTargetDestroy! +function void EsPaintTargetGetSize(EsPaintTarget *target, size_t *width, size_t *height); +function void EsPaintTargetDestroy(EsPaintTarget *target); + +function int EsTextGetLineHeight(const EsTextStyle *style); +function EsTextPlan *EsTextPlanCreate(EsTextPlanProperties *properties, EsRectangle bounds, const char *string, const EsTextRun *textRuns, size_t textRunCount); // textRuns should point to an array of (textRunCount + 1) EsTextRuns; the last one should have its offset set to the total number of bytes in the string. The passed string must remain valid until the plan is destroyed. +function int EsTextPlanGetWidth(EsTextPlan *plan); // TODO Public property? +function int EsTextPlanGetHeight(EsTextPlan *plan); // TODO Public property? +function size_t EsTextPlanGetLineCount(EsTextPlan *plan); // TODO Public property? +function void EsTextPlanDestroy(EsTextPlan *plan); +function void EsTextPlanReplaceStyleRenderProperties(EsTextPlan *plan, EsTextStyle *style); // Only render properties - like color or underline, but not font weight - will be replaced. + +function void EsRichTextParse(const char *inString, ptrdiff_t inStringBytes, + char **outString, EsTextRun **outTextRuns, size_t *outTextRunCount, + EsTextStyle *baseStyle); + +function void EsFontDatabaseEnumerate(EsFontEnumerationCallbackFunction callback, EsGeneric context); +function bool EsFontDatabaseLookupByID(EsFontFamily id, EsFontInformation *information); // Returns false if the font does not exist in the database. +function EsFontFamily EsFontDatabaseInsertFile(const EsFontInformation *information, EsFileStore *store); // Don't set the `id` field in EsFontInformation. The assigned ID will be returned. If nameBytes is 0, then the system will not try to match it with an existing font family. Set the corresponding bit in availableWeightsNormal/availableWeightsItalic for the file being added. The request is ignored if the specific variant is already in the database. + +// Networking. + +function EsError EsAddressResolve(STRING domain, uint32_t flags, EsAddress *address); +function void EsConnectionClose(EsConnection *connection); +function void EsConnectionNotify(EsConnection *connection); +function EsError EsConnectionOpen(EsConnection *connection, uint32_t flags); +function void EsConnectionPoll(EsConnection *connection); +function EsError EsConnectionRead(EsConnection *connection, void *buffer, size_t bufferBytes, size_t *bytesRead); // Returns the number of bytes copied into the buffer. +function EsError EsConnectionWriteSync(EsConnection *connection, const void *data, size_t dataBytes); // Waits until all the data has been written into the send buffer. This does *not* flush the send buffer. + +// Input. + +function size_t EsGameControllerStatePoll(EsGameControllerState *buffer); // Returns number of connected controllers. Buffer must have space for ES_GAME_CONTROLLER_MAX_COUNT. + +function uint8_t EsKeyboardGetModifiers(); // Synchronous with respect to message processing. + +function EsPoint EsMouseGetPosition(EsElement *relativeElement = ES_NULL); +function void EsMouseSetPosition(EsWindow *relativeWindow, int x, int y); +function bool EsMouseIsLeftHeld(); +function bool EsMouseIsRightHeld(); +function bool EsMouseIsMiddleHeld(); + +// Synchronisation and timing. + +function EsHandle EsEventCreate(bool autoReset); +function void EsEventForward(EsHandle event, EsHandle eventSink, EsGeneric data); // TODO Forwarding process/thread killed events. +function void EsEventReset(EsHandle event); +function void EsEventSet(EsHandle event); +function EsHandle EsEventSinkCreate(bool ignoreDuplicates); +function EsError EsEventSinkPop(EsHandle eventSink, EsGeneric *data); // Returns ES_ERROR_EVENT_NOT_SET if empty, and ES_ERROR_EVENT_SINK_OVERFLOW if data lost. +function EsError EsEventSinkPush(EsHandle eventSink, EsGeneric data); // Returns ES_ERROR_EVENT_SINK_DUPLICATE if duplicate, and ES_ERROR_EVENT_SINK_OVERFLOW if data lost. +function void EsMutexAcquire(EsMutex *mutex); +function void EsMutexDestroy(EsMutex *mutex); +function void EsMutexRelease(EsMutex *mutex); +function void EsPerformanceTimerPush(); // Stack size should not exceed 100 values. +function double EsPerformanceTimerPop(); // Returns value in seconds. +function void EsSchedulerYield(); +function void EsSleep(uint64_t milliseconds); +function void EsSpinlockAcquire(EsSpinlock *spinlock); +function void EsSpinlockRelease(EsSpinlock *spinlock); +function EsTimer EsTimerSet(uint64_t afterMs, EsTimerCallbackFunction callback, EsGeneric argument); +function void EsTimerCancel(EsTimer id); +function uintptr_t EsWait(EsHandle *objects, size_t objectCount, uintptr_t timeoutMs); + +// Strings. + +function char *EsCStringDuplicate(EsCString string); +function size_t EsCStringLength(EsCString string); +function char *EsStringAllocateAndFormat(size_t *bytes, EsCString format, ...); // Zero-terminated. +function char *EsStringAllocateAndFormatV(size_t *bytes, EsCString format, va_list arguments); +function int EsStringCompare(STRING s1, STRING s2); +function int EsStringCompareRaw(STRING s1, STRING s2); +function ptrdiff_t EsStringFormat(char *buffer, size_t bufferLength, EsCString format, ...); +function const char *EsStringFormatTemporary(EsCString format, ...); // Not thread safe. The result is valid until the next call. Zero-terminated. +function ptrdiff_t EsStringFormatV(char *buffer, size_t bufferLength, EsCString format, va_list arguments); +function bool EsStringFormatAppend(char *buffer, size_t bufferLength, size_t *bufferPosition, EsCString format, ...); // Return false if buffer filled. +function bool EsStringFormatAppendV(char *buffer, size_t bufferLength, size_t *bufferPosition, EsCString format, va_list arguments); +function size_t EsStringLength(const char *string, uint8_t end); +function bool EsStringStartsWith(STRING string, STRING prefix, bool caseInsensitive); +function bool EsStringEndsWith(STRING string, STRING prefix, bool caseInsensitive); +function char *EsStringZeroTerminate(STRING string); // Free with EsHeapFree. +function bool EsUTF8IsValid(const char *input, ptrdiff_t bytes); // Does not check for surrogate characters or overlong sequences of non-ASCII characters. + +// CRT functions. + +function int EsCRTabs(int n); +function float EsCRTacosf(float x); +function float EsCRTasinf(float x); +function float EsCRTatan2f(float y, float x); +function float EsCRTatanf(float x); +function int EsCRTatoi(const char *string); +function void *EsCRTbsearch(const void *key, const void *base, size_t num, size_t size, EsCRTComparisonCallback compar); +function void *EsCRTcalloc(size_t num, size_t size); +function double EsCRTceil(double x); +function float EsCRTceilf(float x); +function float EsCRTcosf(float x); +function double EsCRTexp(double x); +function float EsCRTexp2f(float x); +function double EsCRTfabs(double x); +function float EsCRTfabsf(float x); +function double EsCRTfloor(double x); +function float EsCRTfloorf(float x); +function float EsCRTfmodf(float x, float y); +function void EsCRTfree(void *ptr); +function char *EsCRTgetenv(const char *name); +function int EsCRTisalpha(int c); +function int EsCRTisdigit(int c); +function bool EsCRTisnanf(float f); +function int EsCRTisspace(int c); +function int EsCRTisupper(int c); +function int EsCRTisxdigit(int c); +function void *EsCRTmalloc(size_t size); +function void *EsCRTmemchr(const void *_s, int _c, size_t n); +function int EsCRTmemcmp(const void *s1, const void *s2, size_t n); +function void *EsCRTmemcpy(void *dest, const void *src, size_t n); +function void *EsCRTmemmove(void *dest, const void *src, size_t n); +function void *EsCRTmemset(void *s, int c, size_t n); +function float EsCRTpowf(float x, float y); +function void EsCRTqsort(void *_base, size_t nmemb, size_t size, EsCRTComparisonCallback compar); +function int EsCRTrand(); +function void *EsCRTrealloc(void *ptr, size_t size); +function float EsCRTsinf(float x); +function int EsCRTsnprintf(char *buffer, size_t bufferSize, const char *format, ...); +function int EsCRTsprintf(char *buffer, const char *format, ...); +function double EsCRTsqrt(double x); +function float EsCRTsqrtf(float x); +function char *EsCRTstrcat(char *dest, const char *src); +function char *EsCRTstrchr(const char *s, int c); +function int EsCRTstrcmp(const char *s1, const char *s2); +function char *EsCRTstrcpy(char *dest, const char *src); +function char *EsCRTstrdup(const char *string); +function char *EsCRTstrerror(int errnum); +function size_t EsCRTstrlen(const char *s); +function int EsCRTstrncmp(const char *s1, const char *s2, size_t n); +function char *EsCRTstrncpy(char *dest, const char *src, size_t n); +function size_t EsCRTstrnlen(const char *s, size_t maxlen); +function char *EsCRTstrstr(const char *haystack, const char *needle); +function long EsCRTstrtol(const char *nptr, char **endptr, int base); +function uint64_t EsCRTstrtoul(const char *nptr, char **endptr, int base); +function int EsCRTtolower(int c); +function int EsCRTvsnprintf(char *buffer, size_t bufferSize, const char *format, va_list arguments); + +// Clipboard and undo. + +function EsError EsClipboardAddText(EsClipboard clipboard, STRING text = BLANK_STRING); +function bool EsClipboardHasText(EsClipboard clipboard); +function char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes); + +function void EsUndoClear(EsUndoManager *manager); +function void EsUndoContinueGroup(EsUndoManager *manager); +function void EsUndoEndGroup(EsUndoManager *manager); +function void EsUndoInvokeGroup(EsUndoManager *manager, bool redo); +function bool EsUndoPeek(EsUndoManager *manager, EsUndoCallback *callback, const void **item); +function void EsUndoPop(EsUndoManager *manager); +function void EsUndoPush(EsUndoManager *manager, EsUndoCallback callback, const void *item, size_t itemBytes); +function bool EsUndoInUndo(EsUndoManager *manager); +function bool EsUndoIsEmpty(EsUndoManager *manager, bool redo); +function ES_INSTANCE_TYPE *EsUndoGetInstance(EsUndoManager *manager); + +// Instances and commands. + +function void EsCommandAddButton(EsCommand *command, EsButton *button); +function EsCommand *EsCommandByID(EsInstance *instance, uint32_t stableID); +function EsCommand *EsCommandRegister(EsCommand *command, EsInstance *instance, EsCommandCallbackFunction callback, uint32_t stableID, EsCString cDefaultKeyboardShortcut = ES_NULL, bool enabled = false); // IDs >= 0xF0000000 reserved by the system. +function void EsCommandSetCallback(EsCommand *command, EsCommandCallbackFunction callback); +function void EsCommandSetDisabled(EsCommand *command, bool disabled); +function void EsCommandSetCheck(EsCommand *command, EsCheckState check, bool sendUpdatedMessage); + +function void EsInstanceDestroy(ES_INSTANCE_TYPE *instance); +function void EsInstanceSetActiveUndoManager(ES_INSTANCE_TYPE *instance, EsUndoManager *manager); +function void EsInstanceSetClassEditor(ES_INSTANCE_TYPE *instance, const EsInstanceClassEditorSettings *settings); +function void EsInstanceSetClassViewer(ES_INSTANCE_TYPE *instance, const EsInstanceClassViewerSettings *settings); +function const EsApplicationStartupInformation *EsInstanceGetStartupInformation(ES_INSTANCE_TYPE *instance); +function void EsInstanceOpenComplete(EsMessage *message, bool success, STRING errorText = BLANK_STRING); +function void EsInstanceSaveComplete(EsMessage *message, bool success); + +// Message processing. + +function size_t EsMessageGetInputText(EsMessage *message, char *buffer); // The buffer should be 64 bytes in size. +function void EsMessageMutexAcquire(); +function void EsMessageMutexCheck(); +function void EsMessageMutexRelease(); +function EsError EsMessagePost(EsElement *target, EsMessage *message); +function EsError EsMessagePostRemote(EsHandle process, EsMessage *message); +function int EsMessageSend(EsElement *object, EsMessage *message); +function EsMessage *EsMessageReceive(); + +// User interface elements. + +function void EsElementDraw(EsElement *element, EsPainter *painter); // Actually draw an element onto a painter. +function void EsElementFocus(EsElement *element, uint32_t flags = ES_FLAGS_DEFAULT); +function void EsElementSetDisabled(EsElement *element, bool disabled = true); +function void EsElementSetHidden(EsElement *element, bool hidden = true); +function void EsElementSetCallback(EsElement *element, EsUICallbackFunction callback); +function void EsElementGetSize(EsElement *element, int *width, int *height); +function void EsElementRepaint(EsElement *element, const EsRectangle *region = nullptr); // Mark an element to be repainted. If region is null, then the whole element is repainted. +function void EsElementRepaintForScroll(EsElement *element, EsMessage *message); // Minimal repaint for ES_MSG_SCROLL_X/Y. +function void EsElementRelayout(EsElement *element); +function void EsElementSetCellRange(EsElement *element, int xFrom, int yFrom, int xTo = -1, int yTo = -1); // Use only if the parent is a ES_PANEL_TABLE. +function EsRectangle EsElementGetInsets(EsElement *element); +function EsRectangle EsElementGetInsetSize(EsElement *element); // Get the size of the element, minus the insets. +function EsThemeMetrics EsElementGetMetrics(EsElement *element); +function EsRectangle EsElementGetPreferredSize(EsElement *element); +function void EsElementMove(EsElement *element, int x, int y, int width, int height, bool applyCellLayout = true); // x, y are given relative to the top-left of the parent. +function EsElement *EsElementGetLayoutParent(EsElement *element); +function void EsElementDestroy(EsElement *element); +function void EsElementDestroyContents(EsElement *element); +function bool EsElementStartAnimating(EsElement *element); // Returns false if the element was already animating. +function void EsElementStartTransition(EsElement *element, EsTransitionType transitionType, uint32_t flags = ES_FLAGS_DEFAULT, uint32_t timeMs = 150); // TODO More customization. +function void EsElementInsertAfter(EsElement *element); // The next element created will be inserted after this element. They must have the same parent. Or, if this is the parent of the next element created, then it will be inserted at the start of the parent. +function void EsElementUpdateContentSize(EsElement *element, uint32_t flags = ES_FLAGS_DEFAULT); +function void EsElementGetTextStyle(EsElement *element, EsTextStyle *style); +function EsRectangle EsElementGetWindowBounds(EsElement *element, bool client = true); +function EsRectangle EsElementGetScreenBounds(EsElement *element, bool client = true); + +function EsElement *EsCustomElementCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL); + +// Windows, menus, popups, toolbars and dialogs. + +function EsWindow *EsWindowCreate(ES_INSTANCE_TYPE *instance, EsWindowStyle style); +function EsRectangle EsWindowGetBounds(EsWindow *window); +function EsElement *EsWindowGetToolbar(EsWindow *window, bool createNew = false); +function void EsWindowSwitchToolbar(EsWindow *window, EsElement *toolbar, EsTransitionType transitionType); +function void EsWindowSetIcon(EsWindow *window, uint32_t iconID); +function void EsWindowSetTitle(EsWindow *window, STRING title = BLANK_STRING); +function void EsWindowAddSizeAlternative(EsWindow *window, EsElement *small, EsElement *big, int widthThreshold, int heightThreshold); // Switch between elements when the window size goes below a threshold. + +function EsMenu *EsMenuCreate(EsElement *source, uint64_t flags = ES_FLAGS_DEFAULT); +function EsElement *EsMenuGetSource(EsMenu *menu); // TODO Public property? +function void EsMenuAddItem(EsMenu *menu, uint64_t flags, STRING label = BLANK_STRING, EsMenuCallbackFunction callback = ES_NULL, EsGeneric context = ES_NULL); +function void EsMenuAddCommand(EsMenu *menu, uint64_t flags, STRING label, EsCommand *command); +function void EsMenuAddSeparator(EsMenu *menu); +function void EsMenuNextColumn(EsMenu *menu, uint64_t flags = ES_FLAGS_DEFAULT); +function void EsMenuShow(EsMenu *menu, int fixedWidth = 0, int fixedHeight = 0); +function void EsMenuCloseAll(); +function void EsMenuAddCommandsFromToolbar(EsMenu *menu, EsElement *element); + +function void EsDialogClose(EsWindow *window); +function EsElement *EsDialogShow(EsWindow *window); +function EsElement *EsDialogShowAlert(EsWindow *window, STRING title, STRING content, uint32_t iconID, uint32_t flags = ES_FLAGS_DEFAULT); // Returns the button container. + +function void EsToolbarAddFileMenu(EsElement *element, const EsFileMenuSettings *settings = ES_NULL); + +// Buttons. + +function EsButton *EsButtonCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL, STRING label = BLANK_STRING); + +function void EsButtonSetIcon(EsButton *button, uint32_t iconID); +function void EsButtonSetCheck(EsButton *button, EsCheckState checkState = ES_CHECK_CHECKED, bool sendUpdatedMessage = true); +function EsCheckState EsButtonGetCheck(EsButton *button); +function void EsButtonOnCommand(EsButton *button, EsCommandCallbackFunction callback, EsCommand *command = ES_NULL); // TODO Public property? +function void EsButtonSetCheckBuddy(EsButton *button, EsElement *checkBuddy); // The buddy element is enabled/disabled when the button is checked/unchecked. +function EsElement *EsButtonGetCheckBuddy(EsButton *button); // TODO Public property? + +// Textboxes. + +function EsTextbox *EsTextboxCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL); + +function bool EsTextboxFind(EsTextbox *textbox, STRING string, int32_t *line, int32_t *byte, uint32_t flags); +function void EsTextboxInsert(EsTextbox *textbox, STRING string = BLANK_STRING, bool sendUpdatedMessage = true); // Deletes existing selection first. +function char *EsTextboxGetContents(EsTextbox *textbox, size_t *bytes = ES_NULL, uint32_t flags = ES_FLAGS_DEFAULT); // Result will be zero-terminated; free with EsHeapFree. +function double EsTextboxGetContentsAsDouble(EsTextbox *textbox, uint32_t flags = ES_FLAGS_DEFAULT); +function size_t EsTextboxGetLineLength(EsTextbox *textbox, uintptr_t line = 0); +function void EsTextboxGetSelection(EsTextbox *textbox, int32_t *fromLine, int32_t *fromByte, int32_t *toLine, int32_t *toByte); +function void EsTextboxMoveCaret(EsTextbox *textbox, int32_t line, int32_t byte); +function void EsTextboxSetSelection(EsTextbox *textbox, int32_t fromLine, int32_t fromByte, int32_t toLine, int32_t toByte); +function void EsTextboxSelectAll(EsTextbox *textbox); +function void EsTextboxClear(EsTextbox *textbox, bool sendUpdatedMessage); +function void EsTextboxUseNumberOverlay(EsTextbox *textbox, bool defaultBehaviour); +function void EsTextboxUseBreadcrumbOverlay(EsTextbox *textbox); +function void EsTextboxMoveCaretRelative(EsTextbox *textbox, uint32_t flags); +function void EsTextboxEnsureCaretVisible(EsTextbox *textbox, bool verticallyCenter = false); +function void EsTextboxSetUndoManager(EsTextbox *textbox, EsUndoManager *manager); +function void EsTextboxGetTextStyle(EsTextbox *textbox, EsTextStyle *textStyle); +function void EsTextboxSetTextStyle(EsTextbox *textbox, const EsTextStyle *textStyle); +function void EsTextboxSetupSyntaxHighlighting(EsTextbox *textbox, uint32_t language, uint32_t *customColors = ES_NULL, size_t customColorCount = 0); +function void EsTextboxStartEdit(EsTextbox *textbox); + +// Panels, spacers and splitters. + +function EsPanel *EsPanelCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL); +function EsElement *EsSpacerCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL, int width = 0, int height = 0); +function EsSplitter *EsSplitterCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL); +function EsCanvasPane *EsCanvasPaneCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL); + +function void EsPanelSetBands(EsPanel *panel, size_t columnCount, size_t rowCount = 0, EsPanelBand *columns = ES_NULL, EsPanelBand *rows = ES_NULL); +function void EsPanelSetBandsAll(EsPanel *panel, EsPanelBand *column = ES_NULL, EsPanelBand *row = ES_NULL); // Set all the columns/rows to have the same properties. This must be called after the final number of bands has been determined/set! +function void EsPanelTableSetChildCells(EsPanel *panel); // Automatically set the child cells for items in a table. This is only necessary if the number of columns/rows is changed after adding items to a table. + +function void EsPanelSwitchTo(EsPanel *panel, EsElement *targetChild, EsTransitionType transitionType, uint32_t flags = ES_FLAGS_DEFAULT, uint32_t timeMs = 150); // TODO More customization of transitions? +function void EsPanelStartMovementAnimation(EsPanel *panel, uint32_t timeMs = 150); // TODO More customization. + +// Static displays. + +function EsIconDisplay *EsIconDisplayCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL, uint32_t iconID = 0); + +function EsImageDisplay *EsImageDisplayCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL); +function void EsImageDisplayLoadBits(EsImageDisplay *display, const uint32_t *bits, size_t width, size_t height, size_t stride); +function void EsImageDisplayLoadFromMemory(EsImageDisplay *display, const void *buffer, size_t bufferBytes); + +function EsTextDisplay *EsTextDisplayCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL, STRING label = BLANK_STRING); +function void EsTextDisplaySetContents(EsTextDisplay *display, STRING contents = BLANK_STRING); +function void EsTextDisplaySetStyledContents(EsTextDisplay *display, const char *string, EsTextRun *runs, size_t runCount); // See EsTextPlanCreate for how runCount works. +function void EsTextDisplaySetupSyntaxHighlighting(EsTextDisplay *display, uint32_t language, uint32_t *customColors = ES_NULL, size_t customColorCount = 0); + +function EsListDisplay *EsListDisplayCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL); +function void EsListDisplaySetCounterContinuation(EsListDisplay *display, EsListDisplay *previous); +function void EsListDisplaySetCounterStart(EsListDisplay *display, uintptr_t index); // If index = 0, then the first item will be "1." or "(a)". + +function void EsAnnouncementShow(EsWindow *window, uint64_t flags, int32_t x, int32_t y, STRING text = BLANK_STRING); + +// Color wells. + +function EsColorWell *EsColorWellCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, uint32_t colorRGB = 0); + +function uint32_t EsColorWellGetRGB(EsColorWell *well); // TODO Public property? +function void EsColorWellSetRGB(EsColorWell *well, uint32_t colorRGB, bool sendChangedMessage); +function void EsColorWellSetIndeterminate(EsColorWell *well); + +// List views. + +function EsListView *EsListViewCreate(EsElement *parent, uint64_t flags = ES_FLAGS_DEFAULT, + const EsStyle *style = ES_NULL, const EsStyle *itemStyle = ES_NULL, + const EsStyle *headerItemStyle = ES_NULL, const EsStyle *footerItemStyle = ES_NULL); + +function EsGeneric EsListViewGetIndexFromItem(EsElement *element, int32_t *group = ES_NULL); +function void EsListViewEnumerateVisibleItems(EsListView *view, EsListViewEnumerateVisibleItemsCallbackFunction callback); + +function void EsListViewSetColumns(EsListView *view, EsListViewColumn *columns, size_t columnCount); +function void EsListViewSetEmptyMessage(EsListView *view, STRING message = BLANK_STRING); +function void EsListViewSetMaximumItemsPerBand(EsListView *view, int maximumItemsPerBand); +function void EsListViewSelect(EsListView *view, int32_t group, EsGeneric index); +function void EsListViewFocusItem(EsListView *view, int32_t group, EsGeneric index); +function bool EsListViewGetFocusedItem(EsListView *view, int32_t *group, EsGeneric *index); // Returns false if not item was focused. +function void EsListViewInvalidateContent(EsListView *view, int32_t group, EsGeneric index); +function void EsListViewInvalidateAll(EsListView *view); +function void EsListViewContentChanged(EsListView *view); +function void EsListViewChangeStyles(EsListView *view, const EsStyle *style, const EsStyle *itemStyle, + const EsStyle *headerItemStyle, const EsStyle *footerItemStyle, uint32_t addFlags, uint32_t removeFlags); +function void EsListViewScrollToEnd(EsListView *view); + +function EsTextbox *EsListViewCreateInlineTextbox(EsListView *view, int32_t group, EsGeneric index, uint32_t flags = ES_FLAGS_DEFAULT); + +// (Callback items.) +function void EsListViewInsertGroup(EsListView *view, int32_t group, uint32_t flags = ES_FLAGS_DEFAULT); +function void EsListViewInsert(EsListView *view, int32_t group, EsGeneric firstIndex, EsGeneric lastIndex, int64_t count = -1); +function void EsListViewRemove(EsListView *view, int32_t group, EsGeneric firstIndex, EsGeneric lastIndex, int64_t count = -1); +function void EsListViewRemoveAll(EsListView *view, int32_t group); +// (Fixed items.) +function void EsListViewInsertFixedItem(EsListView *view, STRING string = BLANK_STRING, EsGeneric data = ES_NULL, intptr_t index = -1); +function bool EsListViewSelectFixedItem(EsListView *view, EsGeneric data); // Returns false if the item was not found. +function bool EsListViewGetSelectedFixedItem(EsListView *view, EsGeneric *data); // Returns false if no item was selected. diff --git a/desktop/posix.cpp b/desktop/posix.cpp new file mode 100644 index 0000000..1f12ab7 --- /dev/null +++ b/desktop/posix.cpp @@ -0,0 +1,775 @@ +#define ES_API +#define ES_FORWARD(x) x +#define ES_EXTERN_FORWARD extern "C" +#define ES_DIRECT_API +#include + +#ifdef ENABLE_POSIX_SUBSYSTEM + +#include + +extern "C" void *ProcessorTLSRead(uintptr_t offset); +extern "C" void ProcessorTLSWrite(uintptr_t offset, void *value); +extern ptrdiff_t tlsStorageOffset; +EsMountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes); +EsProcessStartupInformation *ProcessGetStartupInformation(); + +#define _POSIX_SOURCE +#define _GNU_SOURCE +#define __NEED_struct_iovec +#define __NEED_sigset_t +#define __NEED_struct_timespec +#define __NEED_time_t +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ChildProcess { + uint64_t id; + EsHandle handle; +}; + +char *workingDirectory; +Array childProcesses; + +#ifdef DEBUG_BUILD +double syscallTimeSpent[1024]; +uint64_t syscallCallCount[1024]; +#endif + +const char *syscallNames[] = { + "read", "write", "open", "close", "stat", "fstat", "lstat", "poll", + "lseek", "mmap", "mprotect", "munmap", "brk", "rt_sigaction", "rt_sigprocmask", "rt_sigreturn", + "ioctl", "pread64", "pwrite64", "readv", "writev", "access", "pipe", "select", + "sched_yield", "mremap", "msync", "mincore", "madvise", "shmget", "shmat", "shmctl", + "dup", "dup2", "pause", "nanosleep", "getitimer", "alarm", "setitimer", "getpid", + "sendfile", "socket", "connect", "accept", "sendto", "recvfrom", "sendmsg", "recvmsg", + "shutdown", "bind", "listen", "getsockname", "getpeername", "socketpair", "setsockopt", "getsockopt", + "clone", "fork", "vfork", "execve", "exit", "wait4", "kill", "uname", + "semget", "semop", "semctl", "shmdt", "msgget", "msgsnd", "msgrcv", "msgctl", + "fcntl", "flock", "fsync", "fdatasync", "truncate", "ftruncate", "getdents", "getcwd", + "chdir", "fchdir", "rename", "mkdir", "rmdir", "creat", "link", "unlink", + "symlink", "readlink", "chmod", "fchmod", "chown", "fchown", "lchown", "umask", + "gettimeofday", "getrlimit", "getrusage", "sysinfo", "times", "ptrace", "getuid", "syslog", + "getgid", "setuid", "setgid", "geteuid", "getegid", "setpgid", "getppid", "getpgrp", + "setsid", "setreuid", "setregid", "getgroups", "setgroups", "setresuid", "getresuid", "setresgid", + "getresgid", "getpgid", "setfsuid", "setfsgid", "getsid", "capget", "capset", "rt_sigpending", + "rt_sigtimedwait", "rt_sigqueueinfo", "rt_sigsuspend", "sigaltstack", "utime", "mknod", "uselib", "personality", + "ustat", "statfs", "fstatfs", "sysfs", "getpriority", "setpriority", "sched_setparam", "sched_getparam", + "sched_setscheduler", "sched_getscheduler", "sched_get_priority_max", "sched_get_priority_min", "sched_rr_get_interval", "mlock", "munlock", "mlockall", + "munlockall", "vhangup", "modify_ldt", "pivot_root", "_sysctl", "prctl", "arch_prctl", "adjtimex", + "setrlimit", "chroot", "sync", "acct", "settimeofday", "mount", "umount2", "swapon", + "swapoff", "reboot", "sethostname", "setdomainname", "iopl", "ioperm", "create_module", "init_module", + "delete_module", "get_kernel_syms", "query_module", "quotactl", "nfsservctl", "getpmsg", "putpmsg", "afs_syscall", + "tuxcall", "security", "gettid", "readahead", "setxattr", "lsetxattr", "fsetxattr", "getxattr", + "lgetxattr", "fgetxattr", "listxattr", "llistxattr", "flistxattr", "removexattr", "lremovexattr", "fremovexattr", + "tkill", "time", "futex", "sched_setaffinity", "sched_getaffinity", "set_thread_area", "io_setup", "io_destroy", + "io_getevents", "io_submit", "io_cancel", "get_thread_area", "lookup_dcookie", "epoll_create", "epoll_ctl_old", "epoll_wait_old", + "remap_file_pages", "getdents64", "set_tid_address", "restart_syscall", "semtimedop", "fadvise64", "timer_create", "timer_settime", + "timer_gettime", "timer_getoverrun", "timer_delete", "clock_settime", "clock_gettime", "clock_getres", "clock_nanosleep", "exit_group", + "epoll_wait", "epoll_ctl", "tgkill", "utimes", "vserver", "mbind", "set_mempolicy", "get_mempolicy", + "mq_open", "mq_unlink", "mq_timedsend", "mq_timedreceive", "mq_notify", "mq_getsetattr", "kexec_load", "waitid", + "add_key", "request_key", "keyctl", "ioprio_set", "ioprio_get", "inotify_init", "inotify_add_watch", "inotify_rm_watch", + "migrate_pages", "openat", "mkdirat", "mknodat", "fchownat", "futimesat", "newfstatat", "unlinkat", + "renameat", "linkat", "symlinkat", "readlinkat", "fchmodat", "faccessat", "pselect6", "ppoll", + "unshare", "set_robust_list", "get_robust_list", "splice", "tee", "sync_file_range", "vmsplice", "move_pages", + "utimensat", "epoll_pwait", "signalfd", "timerfd_create", "eventfd", "fallocate", "timerfd_settime", "timerfd_gettime", + "accept4", "signalfd4", "eventfd2", "epoll_create1", "dup3", "pipe2", "inotify_init1", "preadv", + "pwritev", "rt_tgsigqueueinfo", "perf_event_open", "recvmmsg", "fanotify_init", "fanotify_mark", "prlimit64", "name_to_handle_at", + "open_by_handle_at", "clock_adjtime", "syncfs", "sendmmsg", "setns", "getcpu", "process_vm_readv", "process_vm_writev", + "kcmp", "finit_module", "sched_setattr", "sched_getattr", "renameat2", "seccomp", "getrandom", "memfd_create", + "kexec_file_load", "bpf", "execveat", "userfaultfd", "membarrier", "mlock2", "copy_file_range", "preadv2", + "pwritev2", "pkey_mprotect", "pkey_alloc", "pkey_free", "statx", +}; + +extern "C" void ProcessorCheckStackAlignment(); + +char *EsPOSIXConvertPath(const char *path, size_t *outNameLength, bool addPOSIXMountPointPrefix) { + const char *posixNames[2] = { path[0] != '/' ? workingDirectory : nullptr, path }; + size_t posixNameLengths[2] = { path[0] != '/' ? EsCStringLength(workingDirectory) : 0, EsCStringLength(path) }; + + char *name = (char *) EsHeapAllocate(posixNameLengths[0] + posixNameLengths[1] + (addPOSIXMountPointPrefix ? 7 : 0) + 2 /* space for / and NUL; see chdir */, true); + if (!name) return nullptr; + size_t nameLength = 0; + if (addPOSIXMountPointPrefix) name += 7; + + for (uintptr_t i = 0; i < 2; i++) { + while (posixNameLengths[i]) { + const char *entry = posixNames[i]; + size_t entryLength = 0; + + while (posixNameLengths[i]) { + posixNameLengths[i]--; + posixNames[i]++; + if (entry[entryLength] == '/') break; + entryLength++; + } + + if (!entryLength || (entryLength == 1 && entry[0] == '.')) { + // Ignore. + } else if (entryLength == 2 && entry[0] == '.' && entry[1] == '.' && nameLength) { + while (name[--nameLength] != '/'); + } else { + name[nameLength++] = '/'; + EsMemoryCopy(name + nameLength, entry, entryLength); + nameLength += entryLength; + } + } + } + + if (!nameLength) { + nameLength++; + name[0] = '/'; + } + + if (addPOSIXMountPointPrefix) { + name -= 7; + nameLength += 7; + EsMemoryCopy(name, "|POSIX:", 7); + } + + if (outNameLength) *outNameLength = nameLength; + name[nameLength] = 0; + return name; +} + +long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long a6) { +#ifdef DEBUG_BUILD + ProcessorCheckStackAlignment(); +#endif + + long returnValue = 0; + _EsPOSIXSyscall syscall = { n, a1, a2, a3, a4, a5, a6 }; + +#ifdef DEBUG_BUILD + double startTime = EsTimeStampMs(); + static double processStartTime = 0; + + if (!processStartTime) { + processStartTime = startTime; + } + + if (n == SYS_exit_group) { + double processExecutionTime = startTime - processStartTime; + + EsPrint("=== System call performance ===\n"); + + int array[sizeof(syscallNames) / sizeof(syscallNames[0])]; + + for (uintptr_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) { + array[i] = i; + } + + EsCRTqsort(array, sizeof(array) / sizeof(array[0]), sizeof(array[0]), [] (const void *_left, const void *_right) { + int left = *(int *) _left, right = *(int *) _right; + if (syscallTimeSpent[left] > syscallTimeSpent[right]) return -1; + if (syscallTimeSpent[left] < syscallTimeSpent[right]) return 1; + return 0; + }); + + double total = 0; + + for (uintptr_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) { + if (!syscallTimeSpent[array[i]]) break; + EsPrint("%z - %Fms - %d calls\n", syscallNames[array[i]], syscallTimeSpent[array[i]], syscallCallCount[array[i]]); + total += syscallTimeSpent[array[i]]; + } + + EsPrint("Total time in system calls: %Fms\n", total); + EsPrint("Total run time of process: %Fms\n", processExecutionTime); + } +#endif + + switch (n) { + case SYS_open: { + size_t pathBytes; + char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false); + syscall.arguments[0] = (long) path; + syscall.arguments[4] = (long) NodeFindMountPoint(EsLiteral("|POSIX:"))->base; + syscall.arguments[6] = (long) pathBytes; + returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + // EsPrint("SYS_open '%s' with handle %d\n", pathBytes, path, returnValue); + EsHeapFree(path); + } break; + + case SYS_vfork: { + long result = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + + if (result > 0) { + EsHandle handle = result; + ChildProcess pid = { EsProcessGetID(handle), handle }; + childProcesses.Add(pid); + returnValue = pid.id; + } + } break; + + case SYS_pipe: { + syscall.index = SYS_pipe2; + syscall.arguments[1] = 0; + returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + } break; + + case SYS_close: { + // EsPrint("SYS_close handle %d\n", a1); + returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + } break; + + case SYS_pipe2: + case SYS_writev: + case SYS_fcntl: + case SYS_dup2: + case SYS_write: + case SYS_readv: + case SYS_lseek: + case SYS_read: + case SYS_fstat: + case SYS_sysinfo: + case SYS_getdents64: + case SYS_exit_group: + case SYS_ioctl: { + returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + } break; + + case SYS_chdir: { + char *simplified = EsPOSIXConvertPath((const char *) a1, nullptr, false); + EsHeapFree(workingDirectory); + size_t oldLength = EsCStringLength(simplified); + simplified[oldLength] = '/'; + simplified[oldLength + 1] = 0; + workingDirectory = simplified; + } break; + + case SYS_getpid: { + // Run the system call directly, so that the kernel can handle the vfork()'d case. + returnValue = EsSyscall(ES_SYSCALL_THREAD_GET_ID, ES_CURRENT_PROCESS, 0, 0, 0); + } break; + + case SYS_gettid: { + returnValue = EsThreadGetID(ES_CURRENT_THREAD); + } break; + + case SYS_getcwd: { + size_t bytes = EsCStringLength(workingDirectory) + 1; + char *destination = (char *) a1; + + if (bytes > (size_t) a2) { + returnValue = -ERANGE; + } else { + EsMemoryCopy(destination, workingDirectory, bytes); + if (workingDirectory[bytes - 2] == '/' && bytes > 2) destination[bytes - 2] = 0; + returnValue = a1; + } + } break; + + case SYS_getppid: + case SYS_getuid: + case SYS_getgid: + case SYS_getegid: + case SYS_geteuid: { + // TODO. + } break; + + case SYS_getrusage: { + // TODO. + struct rusage *buffer = (struct rusage *) a2; + EsMemoryZero(buffer, sizeof(struct rusage)); + } break; + + case SYS_unlink: { + _EsNodeInformation node; + node.handle = NodeFindMountPoint(EsLiteral("|POSIX:"))->base; + size_t pathBytes; + char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false); + EsError error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE_EXCLUSIVE, (uintptr_t) &node); + EsHeapFree(path); + if (error == ES_ERROR_FILE_DOES_NOT_EXIST) returnValue = -ENOENT; + else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOTDIR; + else if (error == ES_ERROR_FILE_IN_EXCLUSIVE_USE) returnValue = -EBUSY; + else if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO; + else if (error != ES_SUCCESS) returnValue = -EACCES; + else { + error = EsSyscall(ES_SYSCALL_NODE_DELETE, node.handle, 0, 0, 0); + EsHandleClose(node.handle); + if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO; + else if (error != ES_SUCCESS) returnValue = -EACCES; + } + } break; + + case SYS_truncate: { + _EsNodeInformation node; + node.handle = NodeFindMountPoint(EsLiteral("|POSIX:"))->base; + size_t pathBytes; + char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false); + EsError error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE_EXCLUSIVE, (uintptr_t) &node); + EsHeapFree(path); + if (error == ES_ERROR_FILE_DOES_NOT_EXIST) returnValue = -ENOENT; + else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOTDIR; + else if (error == ES_ERROR_FILE_IN_EXCLUSIVE_USE) returnValue = -EBUSY; + else if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO; + else if (error != ES_SUCCESS) returnValue = -EACCES; + else if (node.type == ES_NODE_DIRECTORY) { returnValue = -EISDIR; EsHandleClose(node.handle); } + else { + EsError error = EsFileResize(node.handle, a2); + EsHandleClose(node.handle); + if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO; + else if (error != ES_SUCCESS) returnValue = -EACCES; + } + } break; + + case SYS_execve: { + // NOTE We can't use EsHeapAllocate since the system call never returns. + + size_t pathBytes; + char *_path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false); + char *path = (char *) __builtin_alloca(pathBytes); + EsMemoryCopy(path, _path, pathBytes); + + char **argv = (char **) a2; + char **envp = (char **) a3; + + size_t environmentSize = 2; + + for (uintptr_t i = 0; argv[i]; i++) environmentSize += EsCStringLength(argv[i]) + 1; + for (uintptr_t i = 0; envp[i]; i++) environmentSize += EsCStringLength(envp[i]) + 1; + + bool environmentContainsWorkingDirectory = false; + + for (uintptr_t i = 0; envp[i]; i++) { + if (0 == EsMemoryCompare("PWD=", envp[i], 4)) { + environmentContainsWorkingDirectory = true; + break; + } + } + + if (!environmentContainsWorkingDirectory) { + environmentSize += 4 + EsCStringLength(workingDirectory) + 1; + } + + char newEnvironment[environmentSize]; + char *position = newEnvironment; + EsMemoryZero(newEnvironment, environmentSize); + + for (uintptr_t i = 0; argv[i]; i++) { + size_t length = EsCStringLength(argv[i]) + 1; + EsMemoryCopy(position, argv[i], length); + position += length; + } + + position++; + + for (uintptr_t i = 0; envp[i]; i++) { + size_t length = EsCStringLength(envp[i]) + 1; + EsMemoryCopy(position, envp[i], length); + position += length; + } + + if (!environmentContainsWorkingDirectory) { + size_t length = 4 + EsCStringLength(workingDirectory) + 1; + EsMemoryCopy(position, "PWD=", 4); + EsMemoryCopy(position + 4, workingDirectory, EsCStringLength(workingDirectory) + 1); + position += length; + } + + syscall.arguments[0] = (long) path; + syscall.arguments[1] = (long) pathBytes; + syscall.arguments[2] = (long) newEnvironment; + syscall.arguments[3] = (long) environmentSize; + syscall.arguments[4] = (long) NodeFindMountPoint(EsLiteral("|POSIX:"))->base; + + returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + } break; + + case SYS_access: { + // We don't support file permissions yet, so just check the file exists. + int fd = EsPOSIXSystemCall(SYS_open, a1, O_PATH, 0, 0, 0, 0); + if (fd < 0) returnValue = fd; + else { + returnValue = 0; + EsPOSIXSystemCall(SYS_close, fd, 0, 0, 0, 0, 0); + } + } break; + + case SYS_lstat: + case SYS_stat: { + int fd = EsPOSIXSystemCall(SYS_open, a1, O_PATH, 0, 0, 0, 0); + if (fd < 0) returnValue = fd; + else { + returnValue = EsPOSIXSystemCall(SYS_fstat, fd, a2, 0, 0, 0, 0); + EsPOSIXSystemCall(SYS_close, fd, 0, 0, 0, 0, 0); + } + } break; + + case SYS_readlink: { + if (0 == EsMemoryCompare((void *) a1, EsLiteral("/proc/self/fd/"))) { + // The process is trying to get the path of a file descriptor. + syscall.index = ES_POSIX_SYSCALL_GET_POSIX_FD_PATH; + syscall.arguments[0] = EsCRTatoi((char *) a1 + EsCStringLength("/proc/self/fd/")); + returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + } else { + // We don't support symbolic links, so the output is the same as the input. + int length = EsCStringLength((char *) a1); + EsMemoryZero((void *) a2, a3); + EsMemoryCopy((void *) a2, (void *) a1, length > a3 ? a3 : length); + returnValue = length > a3 ? a3 : length; + } + } break; + + case SYS_set_tid_address: { + // TODO Support set_child_tid and clear_child_tid addresses. + returnValue = EsThreadGetID(ES_CURRENT_THREAD); + } break; + + case SYS_brk: { + returnValue = -1; + } break; + + case SYS_mremap: { + returnValue = -ENOMEM; + } break; + + case SYS_mmap: { + bool read = a3 & PROT_READ, write = a3 & PROT_WRITE, none = a3 == PROT_NONE; + + if (a4 & MAP_FIXED) { + returnValue = -ENOMEM; + } else if ((a4 == (MAP_ANON | MAP_PRIVATE)) && (a5 == -1) && (a6 == 0) && ((read && write) || none)) { + returnValue = (long) EsMemoryReserve(a2, ES_MEMORY_PROTECTION_READ_WRITE, none ? 0 : ES_MEMORY_RESERVE_COMMIT_ALL); + } else { + EsPanic("Unsupported mmap [%x, %x, %x, %x, %x, %x]\n", a1, a2, a3, a4, a5, a6); + } + + } break; + + case SYS_munmap: { + void *address = (void *) a1; + size_t length = (size_t) a2; + + if (length == 0 || ((uintptr_t) address & (ES_PAGE_SIZE - 1))) { + returnValue = -EINVAL; + } else { + EsMemoryUnreserve(address, length); + } + } break; + + case SYS_mprotect: { + void *address = (void *) a1; + size_t length = (size_t) a2; + int protection = (int) a3; + + if (protection == (PROT_READ | PROT_WRITE)) { + returnValue = EsMemoryCommit(address, length) ? 0 : -ENOMEM; + } else if (protection == 0) { + returnValue = EsMemoryDecommit(address, length) ? 0 : -ENOMEM; + } else { + EsPanic("Unsupported mprotect [%x, %x, %x, %x, %x, %x]\n", a1, a2, a3, a4, a5, a6); + } + } break; + + case SYS_prlimit64: { + // You can't access other process's resources. + if (a1 && a1 != (long) EsSyscall(ES_SYSCALL_THREAD_GET_ID, ES_CURRENT_PROCESS, 0, 0, 0)) { + returnValue = -EPERM; + break; + } + + struct rlimit *newLimit = (struct rlimit *) a3; + + if (newLimit && a2 != RLIMIT_STACK) { + returnValue = -EPERM; + break; + } + + struct rlimit *limit = (struct rlimit *) a4; + + if (a2 == RLIMIT_STACK) { + size_t current, maximum; + EsError error = EsSyscall(ES_SYSCALL_THREAD_STACK_SIZE, ES_CURRENT_THREAD, + (uintptr_t) ¤t, (uintptr_t) &maximum, newLimit ? newLimit->rlim_cur : 0); + + if (limit) { + limit->rlim_cur = current; + limit->rlim_max = maximum; + } + + if (error != ES_SUCCESS) returnValue = -EINVAL; + } else if (a2 == RLIMIT_AS) { + if (limit) limit->rlim_cur = limit->rlim_max = RLIM_INFINITY; + } else if (a2 == RLIMIT_RSS) { + if (limit) limit->rlim_cur = limit->rlim_max = 0x10000000; // 256MB. This value is fake. TODO + } else if (a2 == RLIMIT_NOFILE) { + if (limit) limit->rlim_cur = limit->rlim_max = 1048576; + } else { + EsPanic("Unsupported prlimit64 [%x]\n", a2); + } + } break; + + case SYS_setitimer: + case SYS_madvise: + case SYS_umask: + case SYS_chmod: + case SYS_rt_sigaction: + case SYS_rt_sigprocmask: { + // TODO Support signals. + // Ignore. + } break; + + case SYS_clock_gettime: { + // We'll ignore the clockid_t in a1, since we don't have proper timekeeping yet. + struct timespec *tp = (struct timespec *) a2; + uint64_t timeStamp = EsTimeStamp(); + uint64_t unitsPerMicrosecond = EsSystemGetConstant(ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND); + uint64_t microseconds = timeStamp / unitsPerMicrosecond; + tp->tv_sec = microseconds / 1000000; + tp->tv_nsec = (microseconds % 1000000) * 1000; + } break; + + case SYS_wait4: { + if ((a3 & ~3) || a4 || a1 < -1 || !a1) { + EsPanic("Unsupported wait4 [%x/%x/%x/%x]\n", a1, a2, a3, a4); + } + + int *wstatus = (int *) a2; + int options = a3; + + bool foundChild = false; + uintptr_t childIndex = 0; + + if (a1 > 0) { + for (uintptr_t i = 0; i < childProcesses.Length(); i++) { + if (childProcesses[i].id == (uint64_t) a1) { + foundChild = true; + childIndex = i; + break; + } + } + } else if (a1 == -1) { + foundChild = childProcesses.Length(); + } + + if (!foundChild) { + returnValue = -ECHILD; + } else { + returnValue = 0; + + if (~options & 1 /* WNOHANG */) { + if (a1 == -1) { + EsHandle *handles = (EsHandle *) __builtin_alloca(childProcesses.Length() * sizeof(EsHandle)); + + for (uintptr_t i = 0; i < childProcesses.Length(); i++) { + handles[i] = childProcesses[i].handle; + } + + EsWait(handles, childProcesses.Length(), ES_WAIT_NO_TIMEOUT); + } else { + EsWaitSingle(childProcesses[childIndex].handle); + } + } + + for (uintptr_t i = 0; i < childProcesses.Length(); i++) { + if (a1 > 0 && childProcesses[i].id != (uint64_t) a1) { + continue; + } + + EsHandle handle = childProcesses[i].handle; + EsProcessState state; + EsProcessGetState(handle, &state); + + if (state.flags & ES_PROCESS_STATE_ALL_THREADS_TERMINATED) { + returnValue = childProcesses[i].id; + *wstatus = (EsProcessGetExitStatus(handle) & 0xFF) << 8; + EsHandleClose(handle); + childProcesses.Delete(i); + break; + } + } + } + } break; + + case SYS_sched_getaffinity: { + // TODO Getting the correct number of CPUs. + // TODO Getting the affinity for other processes. + cpu_set_t *set = (cpu_set_t *) a3; + EsCRTmemset(set, 0, a2); + CPU_SET(0, set); + } break; + + case SYS_mkdir: { + size_t pathBytes; + char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, true); + EsError error = EsPathCreate(path, pathBytes, ES_NODE_DIRECTORY, false); + if (error == ES_ERROR_INSUFFICIENT_RESOURCES) returnValue = -ENOMEM; + else if (error == ES_ERROR_FILE_ALREADY_EXISTS) returnValue = -EEXIST; + else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOENT; + else if (error == ES_ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME) returnValue = -ENOENT; + else if (error == ES_ERROR_FILE_ON_READ_ONLY_VOLUME) returnValue = -EPERM; + EsHeapFree(path); + } break; + + case SYS_uname: { + struct utsname *buffer = (struct utsname *) a1; + EsCRTstrcpy(buffer->sysname, "Essence"); + EsCRTstrcpy(buffer->release, "0.0.0"); + EsCRTstrcpy(buffer->version, "0.0.0"); + EsCRTstrcpy(buffer->machine, "Unknown"); + } break; + + case SYS_setpgid: { + if (a1 < 0) { + returnValue = -EINVAL; + } else { + EsHandle process = EsProcessOpen(a1); + + if (process != ES_INVALID_HANDLE) { + syscall.arguments[0] = process; + returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0); + EsHandleClose(process); + } else { + returnValue = -ESRCH; + } + } + } break; + + case SYS_rename: { + size_t oldPathBytes; + char *oldPath = EsPOSIXConvertPath((const char *) a1, &oldPathBytes, true); + size_t newPathBytes; + char *newPath = EsPOSIXConvertPath((const char *) a2, &newPathBytes, true); + EsError error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes); + EsHeapFree(oldPath); + EsHeapFree(newPath); + // TODO More return values. + if (error == ES_ERROR_FILE_DOES_NOT_EXIST) returnValue = -ENOENT; + else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOTDIR; + else if (error == ES_ERROR_FILE_IN_EXCLUSIVE_USE) returnValue = -EBUSY; + else if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO; + else if (error != ES_SUCCESS) returnValue = -EACCES; + } break; + + case -1000: { + // Update thread local storage: + void *apiTLS = ProcessorTLSRead(tlsStorageOffset); + EsSyscall(ES_SYSCALL_PROCESS_SET_TLS, a1, 0, 0, 0); + tlsStorageOffset = -a2; + ProcessorTLSWrite(tlsStorageOffset, apiTLS); + } break; + + default: { + EsPanic("Unknown linux syscall %d = %z.\nArguments: %x, %x, %x, %x, %x, %x\n", + n, syscallNames[n], a1, a2, a3, a4, a5, a6); + } break; + } + +#ifdef DEBUG_BUILD + double endTime = EsTimeStampMs(); + syscallTimeSpent[n] += endTime - startTime; + syscallCallCount[n]++; +#endif + + // EsPrint(":: %z %x %x %x -> %x; %Fms\n", syscallNames[n], a1, a2, a3, returnValue, endTime - startTime); + + return returnValue; +} + +void EsPOSIXInitialise(int *argc, char ***argv) { + // Get the arguments and environment. + + EsHandle environmentHandle = EsSyscall(ES_SYSCALL_PROCESS_GET_CREATION_ARGUMENT, ES_CURRENT_PROCESS, CREATION_ARGUMENT_ENVIRONMENT, 0, 0); + char *environmentBuffer = (char *) "./application\0\0LANG=en_US.UTF-8\0PWD=/\0HOME=/\0PATH=/Applications/POSIX/bin\0TMPDIR=/Applications/POSIX/tmp\0\0"; + + if (environmentHandle) { + environmentBuffer = (char *) EsHeapAllocate(ARG_MAX, false); + EsConstantBufferRead((EsHandle) environmentHandle, environmentBuffer); + EsHandleClose((EsHandle) environmentHandle); + } + + // Extract the arguments and environment variables. + + uintptr_t position = 0; + char *start = environmentBuffer; + Array _argv = {}; + *argc = 0; + + for (int i = 0; i < 2; i++) { + while (position < ARG_MAX) { + if (!environmentBuffer[position]) { + _argv.Add(start); + start = environmentBuffer + position + 1; + + if (i == 0) { + *argc = *argc + 1; + } + + if (!environmentBuffer[position + 1]) { + start = environmentBuffer + position + 2; + _argv.Add(nullptr); + break; + } + } + + position++; + } + + position += 2; + } + + // Copy the working directory string. + + for (uintptr_t i = *argc + 1; i < _argv.Length(); i++) { + if (_argv[i] && 0 == EsMemoryCompare("PWD=", _argv[i], 4)) { + size_t length = EsCStringLength((char *) _argv[i]) - 4; + workingDirectory = (char *) EsHeapAllocate(length + 2, false); + workingDirectory[length] = 0, workingDirectory[length + 1] = 0; + EsMemoryCopy(workingDirectory, (char *) _argv[i] + 4, length); + if (workingDirectory[length - 1] != '/') workingDirectory[length] = '/'; + } + } + + // Add the auxillary vectors. + + EsProcessStartupInformation *startupInformation = ProcessGetStartupInformation(); + +#ifdef ARCH_X86_64 + Elf64_Phdr *tlsHeader = (Elf64_Phdr *) EsHeapAllocate(sizeof(Elf64_Phdr), true); + tlsHeader->p_type = PT_TLS; + tlsHeader->p_flags = 4 /* read */; + tlsHeader->p_vaddr = startupInformation->tlsImageStart; + tlsHeader->p_filesz = startupInformation->tlsImageBytes; + tlsHeader->p_memsz = startupInformation->tlsBytes; + tlsHeader->p_align = 8; + + _argv.Add((void *) AT_PHNUM); + _argv.Add((void *) 1); + _argv.Add((void *) AT_PHENT); + _argv.Add((void *) sizeof(Elf64_Phdr)); + _argv.Add((void *) AT_PHDR); + _argv.Add((void *) tlsHeader); +#else +#error "no architecture TLS support" +#endif + + _argv.Add((void *) AT_PAGESZ); + _argv.Add((void *) ES_PAGE_SIZE); + + _argv.Add(nullptr); + + // Return argv. + + *argv = (char **) _argv.array; +} + +#endif diff --git a/desktop/prefix.h b/desktop/prefix.h new file mode 100644 index 0000000..8ca89a4 --- /dev/null +++ b/desktop/prefix.h @@ -0,0 +1,371 @@ +// ----------------- Includes: + +#ifndef IncludedEssenceAPIHeader +#define IncludedEssenceAPIHeader + +#include +#ifndef KERNEL +#include +#include +#include +#endif +#include + +// --------- C++/C differences: + +#ifdef __cplusplus + +#define ES_EXTERN_C extern "C" +#define ES_CONSTRUCTOR(x) x +#define ES_NULL nullptr + +// Scoped defer: http://www.gingerbill.org/article/defer-in-cpp.html +template struct _EsDefer4 { F f; _EsDefer4(F f) : f(f) {} ~_EsDefer4() { f(); } }; +template _EsDefer4 _EsDeferFunction(F f) { return _EsDefer4(f); } +#define EsDEFER_1(x, y) x ## y +#define EsDEFER_2(x, y) EsDEFER_1(x, y) +#define EsDEFER_3(x) EsDEFER_2(x, __COUNTER__) +#define _EsDefer5(code) auto EsDEFER_3(_defer_) = _EsDeferFunction([&](){code;}) +#define EsDefer(code) _EsDefer5(code) + +union EsGeneric { + uintptr_t u; + intptr_t i; + void *p; + + inline EsGeneric() = default; + + inline EsGeneric(uintptr_t y) { u = y; } + inline EsGeneric( intptr_t y) { i = y; } + inline EsGeneric(unsigned y) { u = y; } + inline EsGeneric( int y) { i = y; } + inline EsGeneric( void *y) { p = y; } + + inline bool operator==(EsGeneric r) const { return r.u == u; } +}; + +#else + +#define ES_EXTERN_C extern +#define ES_CONSTRUCTOR(x) +#define ES_NULL 0 + +typedef union { + uintptr_t u; + intptr_t i; + void *p; +} EsGeneric; + +typedef struct EsElementPublic EsElementPublic; + +#endif + +// --------- Macros: + +#ifdef ARCH_X86_64 +#define ES_API_BASE ((void **) 0x1000) +#define ES_SHARED_MEMORY_MAXIMUM_SIZE ((size_t) (1024) * 1024 * 1024 * 1024) +#define ES_PAGE_SIZE (4096) +#define ES_PAGE_BITS (12) + +typedef struct EsCRTjmp_buf { + uintptr_t rsp, rbp, rbx, r12, r13, r14, r15, rip; +} EsCRTjmp_buf; + +ES_EXTERN_C int _EsCRTsetjmp(EsCRTjmp_buf *env); +ES_EXTERN_C __attribute__((noreturn)) void _EsCRTlongjmp(EsCRTjmp_buf *env, int val); +#define EsCRTsetjmp(x) _EsCRTsetjmp(&(x)) +#define EsCRTlongjmp(x, y) _EsCRTlongjmp(&(x), (y)) +#endif + +#define EsContainerOf(type, member, pointer) ((type *) ((uint8_t *) pointer - offsetof(type, member))) + +#define ES_CHECK_ERROR(x) (((intptr_t) (x)) < (ES_SUCCESS)) + +#define ES_RECT_1(x) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (x), (int32_t) (x) }) +#define ES_RECT_1I(x) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (x), (int32_t) -(x) }) +#define ES_RECT_2(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (y), (int32_t) (y) }) +#define ES_RECT_2I(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (y), (int32_t) -(y) }) +#define ES_RECT_2S(x, y) ((EsRectangle) { 0, (int32_t) (x), 0, (int32_t) (y) }) +#define ES_RECT_4(x, y, z, w) ((EsRectangle) { (int32_t) (x), (int32_t) (y), (int32_t) (z), (int32_t) (w) }) +#define ES_RECT_4PD(x, y, w, h) ((EsRectangle) { (int32_t) (x), (int32_t) ((x) + (w)), (int32_t) (y), (int32_t) ((y) + (h)) }) +#define ES_RECT_WIDTH(_r) ((_r).r - (_r).l) +#define ES_RECT_HEIGHT(_r) ((_r).b - (_r).t) +#define ES_RECT_TOTAL_H(_r) ((_r).r + (_r).l) +#define ES_RECT_TOTAL_V(_r) ((_r).b + (_r).t) +#define ES_RECT_SIZE(_r) ES_RECT_WIDTH(_r), ES_RECT_HEIGHT(_r) +#define ES_RECT_TOP_LEFT(_r) (_r).l, (_r).t +#define ES_RECT_BOTTOM_LEFT(_r) (_r).l, (_r).b +#define ES_RECT_BOTTOM_RIGHT(_r) (_r).r, (_r).b +#define ES_RECT_ALL(_r) (_r).l, (_r).r, (_r).t, (_r).b +#define ES_RECT_VALID(_r) (ES_RECT_WIDTH(_r) > 0 && ES_RECT_HEIGHT(_r) > 0) + +#define ES_POINT(x, y) ((EsPoint) { (int32_t) (x), (int32_t) (y) }) + +#define EsKeyboardIsAltHeld() (EsKeyboardGetModifiers() & ES_MODIFIER_ALT) +#define EsKeyboardIsCtrlHeld() (EsKeyboardGetModifiers() & ES_MODIFIER_CTRL) +#define EsKeyboardIsShiftHeld() (EsKeyboardGetModifiers() & ES_MODIFIER_SHIFT) + +#define ES_MEMORY_MOVE_BACKWARDS - + +#define EsWaitSingle(object) EsWait(&object, 1, ES_WAIT_NO_TIMEOUT) +#define EsObjectUnmap EsMemoryUnreserve + +#define EsLiteral(x) (char *) x, EsCStringLength((char *) x) + +#define ES_STYLE_CAST(x) ((EsStyle *) (uintptr_t) (x)) + +#ifndef ES_INSTANCE_TYPE +#define ES_INSTANCE_TYPE struct EsInstance +#else +struct ES_INSTANCE_TYPE; +#endif +#ifdef __cplusplus +#define EsInstanceCreate(_message, ...) (static_cast(_EsInstanceCreate(sizeof(ES_INSTANCE_TYPE), _message, __VA_ARGS__))) +#else +#define EsInstanceCreate(_message, ...) ((ES_INSTANCE_TYPE *) _EsInstanceCreate(sizeof(ES_INSTANCE_TYPE), _message, __VA_ARGS__)) +#endif + +#define ES_SAMPLE_FORMAT_BYTES_PER_SAMPLE(x) \ + ((x) == ES_SAMPLE_FORMAT_U8 ? 1 : (x) == ES_SAMPLE_FORMAT_S16LE ? 2 : 4) + +#define ES_EXTRACT_BITS(value, end, start) (((value) >> (start)) & ((1 << ((end) - (start) + 1)) - 1)) // Moves the bits to the start. +#define ES_ISOLATE_BITS(value, end, start) (((value)) & (((1 << ((end) - (start) + 1)) - 1) << (start))) // Keeps the bits in place. + +#ifndef KERNEL +#ifdef ES_API +ES_EXTERN_C uintptr_t _APISyscall(uintptr_t argument0, uintptr_t argument1, uintptr_t argument2, uintptr_t unused, uintptr_t argument3, uintptr_t argument4); +#define EsSyscall(a, b, c, d, e) _APISyscall((a), (b), (c), 0, (d), (e)) +#define _EsSyscall _APISyscall +#else +#define EsSyscall(a, b, c, d, e) _EsSyscall((a), (b), (c), 0, (d), (e)) +#endif +#endif + +// --------- Algorithms: + +#define ES_MACRO_SORT(_name, _type, _compar, _contextType) void _name(_type *base, size_t nmemb, _contextType context) { \ + (void) context; \ + if (nmemb <= 1) return; \ + \ + if (nmemb <= 16) { \ + for (uintptr_t i = 1; i < nmemb; i++) { \ + for (intptr_t j = i; j > 0; j--) { \ + _type *_left = base + j, *_right = _left - 1; \ + int result; _compar if (result >= 0) break; \ + \ + _type swap = base[j]; \ + base[j] = base[j - 1]; \ + base[j - 1] = swap; \ + } \ + } \ + \ + return; \ + } \ + \ + intptr_t i = -1, j = nmemb; \ + \ + while (true) { \ + _type *_left, *_right = base; \ + int result; \ + \ + while (true) { _left = base + ++i; _compar if (result >= 0) break; } \ + while (true) { _left = base + --j; _compar if (result <= 0) break; } \ + \ + if (i >= j) break; \ + \ + _type swap = base[i]; \ + base[i] = base[j]; \ + base[j] = swap; \ + } \ + \ + _name(base, ++j, context); \ + _name(base + j, nmemb - j, context); \ +} \ + +#define ES_MACRO_SEARCH(_count, _compar, _result, _found) \ + do { \ + if (_count) { \ + intptr_t low = 0; \ + intptr_t high = _count - 1; \ + \ + while (low <= high) { \ + uintptr_t index = ((high - low) >> 1) + low; \ + int result; \ + _compar \ + \ + if (result < 0) { \ + high = index - 1; \ + } else if (result > 0) { \ + low = index + 1; \ + } else { \ + _result = index; \ + _found = true; \ + break; \ + } \ + } \ + \ + if (high < low) { \ + _result = low; \ + _found = false; \ + } \ + } else { \ + _result = 0; \ + _found = false; \ + } \ + } while (0) + +// --------- Misc: + +typedef uint64_t _EsLongConstant; +typedef long double EsLongDouble; +typedef const char *EsCString; + +#ifndef ES_API +ES_EXTERN_C void _init(); +ES_EXTERN_C void _start(); +#endif + +#define EsAssert(x) do { if (!(x)) { EsAssertionFailure(__FILE__, __LINE__); } } while (0) +#define EsCRTassert EsAssert + +#define ES_INFINITY __builtin_inff() +#define ES_PI (3.1415926535897932384626433832795028841971693994) + +// --------- Internal APIs: + +#if defined(ES_API) || defined(KERNEL) + +struct EsProcessStartupInformation { + bool isDesktop; + uintptr_t applicationStartAddress; + uintptr_t tlsImageStart; + uintptr_t tlsImageBytes; + uintptr_t tlsBytes; // All bytes after the image are to be zeroed. +}; + +struct _EsPOSIXSyscall { + intptr_t index; + intptr_t arguments[7]; +}; + +#define BLEND_WINDOW_MATERIAL_NONE (0) +#define BLEND_WINDOW_MATERIAL_GLASS (1) +#define BLEND_WINDOW_MATERIAL_LIGHT_BLUR (2) + +#ifdef ARCH_X86_64 +#define BUNDLE_FILE_MAP_ADDRESS (0x100000000UL) +#endif + +struct BundleHeader { +#define BUNDLE_SIGNATURE (0x63BDAF45) + uint32_t signature; + uint32_t version; + uint32_t fileCount; + uint32_t _unused; + uint64_t mapAddress; +}; + +struct BundleFile { + uint64_t nameCRC64; + uint64_t bytes; + uint64_t offset; +}; + +#ifdef KERNEL +#define K_BOOT_DRIVE "" +#else +#define K_BOOT_DRIVE "0:" +#endif + +#define K_OS_FOLDER K_BOOT_DRIVE "/Essence" +#define K_DESKTOP_EXECUTABLE K_OS_FOLDER "/Desktop.esx" +#define K_SYSTEM_CONFIGURATION K_OS_FOLDER "/System Configuration.ini" + +#define CREATION_ARGUMENT_MAIN (0) +#define CREATION_ARGUMENT_ENVIRONMENT (1) +#define CREATION_ARGUMENT_INITIAL_MOUNT_POINTS (2) + +#define WINDOW_SET_BITS_NORMAL (0) +#define WINDOW_SET_BITS_SCROLL_HORIZONTAL (1) +#define WINDOW_SET_BITS_SCROLL_VERTICAL (2) +#define WINDOW_SET_BITS_AFTER_RESIZE (3) + +#define SHUTDOWN_ACTION_POWER_OFF (1) +#define SHUTDOWN_ACTION_RESTART (2) + +#ifdef __cplusplus +extern "C" const void *EsBufferRead(struct EsBuffer *buffer, size_t readBytes); +extern "C" const void *EsBufferReadMany(struct EsBuffer *buffer, size_t a, size_t b); +extern "C" void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writeBytes); +#define EsBuffer_MEMBER_FUNCTIONS \ + inline const void *Read(size_t readBytes) { return EsBufferRead(this, readBytes); } \ + inline const void *Read(size_t a, size_t b) { return EsBufferReadMany(this, a, b); } \ + inline void *Write(const void *source, size_t writeBytes) { return EsBufferWrite(this, source, writeBytes); } +#endif + +#define ES_POSIX_SYSCALL_GET_POSIX_FD_PATH (0x10000) + +#endif + +// --------- CRT function macros: + +#ifdef ES_CRT_WITHOUT_PREFIX +#define abs EsCRTabs +#define acosf EsCRTacosf +#define asinf EsCRTasinf +#define assert EsCRTassert +#define atan2f EsCRTatan2f +#define atanf EsCRTatanf +#define atoi EsCRTatoi +#define bsearch EsCRTbsearch +#define calloc EsCRTcalloc +#define ceil EsCRTceil +#define ceilf EsCRTceilf +#define cosf EsCRTcosf +#define exp EsCRTexp +#define exp2f EsCRTexp2f +#define fabs EsCRTfabs +#define fabsf EsCRTfabsf +#define floor EsCRTfloor +#define floorf EsCRTfloorf +#define fmodf EsCRTfmodf +#define free EsCRTfree +#define getenv EsCRTgetenv +#define isalpha EsCRTisalpha +#define isdigit EsCRTisdigit +#define isnanf EsCRTisnanf +#define isspace EsCRTisspace +#define isupper EsCRTisupper +#define isxdigit EsCRTisxdigit +#define malloc EsCRTmalloc +#define memchr EsCRTmemchr +#define memcmp EsCRTmemcmp +#define memcpy EsCRTmemcpy +#define memmove EsCRTmemmove +#define memset EsCRTmemset +#define powf EsCRTpowf +#define qsort EsCRTqsort +#define rand EsCRTrand +#define realloc EsCRTrealloc +#define sinf EsCRTsinf +#define snprintf EsCRTsnprintf +#define sprintf EsCRTsprintf +#define sqrt EsCRTsqrt +#define sqrtf EsCRTsqrtf +#define strcat EsCRTstrcat +#define strchr EsCRTstrchr +#define strcmp EsCRTstrcmp +#define strcpy EsCRTstrcpy +#define strdup EsCRTstrdup +#define strerror EsCRTstrerror +#define strlen EsCRTstrlen +#define strncmp EsCRTstrncmp +#define strncpy EsCRTstrncpy +#define strnlen EsCRTstrnlen +#define strstr EsCRTstrstr +#define strtol EsCRTstrtol +#define strtoul EsCRTstrtoul +#define tolower EsCRTtolower +#define vsnprintf EsCRTvsnprintf +#endif diff --git a/desktop/renderer.cpp b/desktop/renderer.cpp new file mode 100644 index 0000000..fbbde89 --- /dev/null +++ b/desktop/renderer.cpp @@ -0,0 +1,925 @@ +// TODO Fix glitches. +// TODO RAST_REPEAT_NORMAL is wrong with negative values. + +#ifdef IN_DESIGNER +#define RAST_ARRAY(x) x * +#define RAST_ARRAY_ADD arrput +#define RAST_ARRAY_CLEAR arrclear +#define RAST_ARRAY_DELETE_SWAP arrdelswap +#define RAST_ARRAY_FREE arrfree +#define RAST_ARRAY_INSERT arrinsn +#define RAST_ARRAY_LAST arrlast +#define RAST_ARRAY_LENGTH arrlen +#define RAST_ARRAY_LENGTH_U arrlenu +#else +#define RAST_ARRAY(x) Array +#define RAST_ARRAY_ADD(x, y) ((x).Add(y)) +#define RAST_ARRAY_CLEAR(x) ((x).SetLength(0)) +#define RAST_ARRAY_DELETE_SWAP(x, y) ((x).DeleteSwap(y)) +#define RAST_ARRAY_FREE(x) ((x).Free()) +#define RAST_ARRAY_INSERT(x, y, z) ((x).InsertMany(y, z)) +#define RAST_ARRAY_LAST(x) ((x).Last()) +#define RAST_ARRAY_LENGTH(x) ((intptr_t) (x).Length()) +#define RAST_ARRAY_LENGTH_U(x) ((x).Length()) +#endif + +typedef struct RastVertex { + float x, y; +} RastVertex; + +typedef struct RastEdge { + float xf, yf, xt, yt; + float dx, dy; + int sign; +} RastEdge; + +typedef struct RastShape { + RAST_ARRAY(RastEdge) edges; /* sorted by yf; yf <= yt */ + int left, right, top, bottom; +} RastShape; + +typedef struct RastSurface { + uint32_t *buffer; + int width, height, stride; + float *area, *areaFill; + bool customBuffer; +} RastSurface; + +typedef enum RastPaintType { + RAST_PAINT_NONE, + RAST_PAINT_SOLID, + RAST_PAINT_CHECKERBOARD, + RAST_PAINT_LINEAR_GRADIENT, + RAST_PAINT_RADIAL_GRADIENT, + RAST_PAINT_ANGULAR_GRADIENT, + RAST_PAINT_NOISE, +} RastPaintType; + +typedef enum RastRepeatMode { + RAST_REPEAT_CLAMP, + RAST_REPEAT_NORMAL, + RAST_REPEAT_MIRROR, +} RastRepeatMode; + +typedef struct RastPaint { + RastPaintType type; + + union { + struct { + uint32_t color; + float alpha; + } solid; + + struct { + uint32_t color1, color2; + float alpha1, alpha2; + int size; + } checkboard; + + struct { + uint32_t *color; + float *alpha; + float transform[6]; + RastRepeatMode repeatMode; + } gradient; + + struct { + uint32_t color; + float minimum, maximum; + } noise; + }; +} RastPaint; + +typedef struct RastGradientStop { + uint32_t color; + float position; +} RastGradientStop; + +typedef struct RastPathSegment { + uintptr_t uptoVertex; +} RastPathSegment; + +typedef struct RastPath { + RAST_ARRAY(RastPathSegment) segments; + RAST_ARRAY(RastVertex) vertices; +} RastPath; + +typedef enum RastLineJoinMode { + // Determines how convex segments are joined. + // Concave segments are always mitered with infinite limit. + + RAST_LINE_JOIN_MITER, // Force bevels with miterLimit = 0. + RAST_LINE_JOIN_ROUND, +} RastLineJoinMode; + +typedef enum RastLineCapMode { + RAST_LINE_CAP_FLAT, + RAST_LINE_CAP_SQUARE, + RAST_LINE_CAP_ROUND, +} RastLineCapMode; + +typedef struct RastContourStyle { + float internalWidth, externalWidth; + RastLineJoinMode joinMode; + float miterLimit; + RastLineCapMode capMode; + // TODO Markers. +} RastContourStyle; + +typedef struct RastDash { + float length, gap; + RastContourStyle *style; +} RastDash; + +#define RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED (0.1f * 0.1f) +#define RAST_GRADIENT_COLORS (256) +#define RAST_ROUND_TOLERANCE (0.25f) +#define RAST_FLATTEN_TOLERANCE (0.25f) +#define RAST_GRADIENT_NOISE (0.005f) + +#define RAST_AVERAGE_VERTICES(a, b) { ((a).x + (b).x) * 0.5f, ((a).y + (b).y) * 0.5f } + +bool RastSurfaceInitialise(RastSurface *surface, int width, int height, bool customBuffer) { + surface->width = width; + surface->height = height; + surface->stride = 4 * width; + surface->area = (float *) EsHeapAllocate(sizeof(float) * width, true); + surface->areaFill = (float *) EsHeapAllocate(sizeof(float) * (width + 1), true); + surface->customBuffer = customBuffer; + + if (!customBuffer) { + surface->buffer = (uint32_t *) EsHeapAllocate(4 * width * height, true); + } + + return surface->buffer && surface->area && surface->areaFill; +} + +void RastSurfaceDestroy(RastSurface *surface) { + EsHeapFree(surface->area); + EsHeapFree(surface->areaFill); + + if (!surface->customBuffer) { + EsHeapFree(surface->buffer); + } +} + +#ifndef IN_DESIGNER +ES_MACRO_SORT(RastEdgesSort, RastEdge, { result = _left->yf > _right->yf ? 1 : _left->yf < _right->yf ? -1 : 0; }, void *); +#else +int _RastEdgeCompare(const void *left, const void *right) { + const RastEdge *_left = (const RastEdge *) left; + const RastEdge *_right = (const RastEdge *) right; + return _left->yf > _right->yf ? 1 : _left->yf < _right->yf ? -1 : 0; +} + +void RastEdgesSort(RastEdge *edges, size_t count, void *_unused) { + (void) _unused; + qsort(edges, count, sizeof(RastEdge), _RastEdgeCompare); +} +#endif + +float _RastRepeat(RastRepeatMode mode, float p) { + if (mode == RAST_REPEAT_CLAMP) { + if (p < 0) return 0; + if (p > 1) return 1; + return p; + } else if (mode == RAST_REPEAT_MIRROR) { + p = EsCRTfabsf(EsCRTfmodf(p, 2.0f)); + if (p > 1) return 2 - p; + return p; + } else { + return EsCRTfabsf(EsCRTfmodf(p, 1.0f)); + } +} + +void _RastShapeDestroy(RastShape shape) { + RAST_ARRAY_FREE(shape.edges); +} + +void RastSurfaceFill(RastSurface surface, RastShape shape, RastPaint paint, bool evenOdd) { + if (paint.type == RAST_PAINT_LINEAR_GRADIENT + || paint.type == RAST_PAINT_RADIAL_GRADIENT + || paint.type == RAST_PAINT_ANGULAR_GRADIENT) { + if (!paint.gradient.color) { + _RastShapeDestroy(shape); + return; + } + } + + RAST_ARRAY(RastEdge) active = { 0 }; + int edgePosition = 0; + + if (shape.left < 0) shape.left = 0; + if (shape.right > surface.width) shape.right = surface.width; + if (shape.top < 0) shape.top = 0; + if (shape.bottom > surface.height) shape.bottom = surface.height; + + // Split edges that cross the left side of the shape. + + int initialShapeEdges = RAST_ARRAY_LENGTH(shape.edges); + + for (int i = 0; i < initialShapeEdges; i++) { + RastEdge *a = &shape.edges[i]; + + float y0 = a->yf; + float y1 = a->yt; + float x0 = a->xf; + float x1 = a->xt; + bool flipped = false; + + if (x0 > x1) { + float t = x0; + x0 = x1, x1 = t; + flipped = true; + } + + if (x0 < shape.left && x1 >= shape.left) { + RastEdge e = *a; + + if (flipped) { + y1 += (shape.left - x0) * a->dy; + a->xt = e.xf = e.xt = shape.left; + e.yf = a->yt = y1; + e.dx = 0; + } else { + y0 += (shape.left - x0) * a->dy; + a->xt = e.xf = a->xf = shape.left; + e.yf = a->yt = y0; + a->dx = 0; + } + + RAST_ARRAY_ADD(shape.edges, e); + } + } + + RastEdgesSort(&shape.edges[0], RAST_ARRAY_LENGTH(shape.edges), NULL); + + if (paint.type == RAST_PAINT_CHECKERBOARD && paint.checkboard.size < 1) paint.checkboard.size = 1; + + for (int scanline = shape.top; scanline < shape.bottom; scanline++) { + // Remove edges above this scanline. + + for (int i = 0; i < RAST_ARRAY_LENGTH(active); i++) { + if (active[i].yt < scanline) { + RAST_ARRAY_DELETE_SWAP(active, i); + i--; + } + } + + // Add edges that start within this scanline. + + while (edgePosition < RAST_ARRAY_LENGTH(shape.edges)) { + RastEdge *e = &shape.edges[edgePosition]; + + if (e->yf < scanline + 1.0f) { + RAST_ARRAY_ADD(active, *e); + edgePosition++; + } else { + break; + } + } + + // If there are no active edges, don't process the scanline. + + if (!RAST_ARRAY_LENGTH(active)) { + continue; + } + + // Calculate the signed area covered by each active edge. + + for (int i = 0; i < RAST_ARRAY_LENGTH(active); i++) { + RastEdge *a = &active[i]; + + // Calculate the range of pixels the edge crosses on the scanline. + + float top = scanline, bottom = scanline + 1; + float y0 = (a->yf > top) ? a->yf : top; + float y1 = (a->yt < bottom) ? a->yt : bottom; + float x0 = (y0 - a->yf) * a->dx + a->xf; + float x1 = (y1 - a->yf) * a->dx + a->xf; + float dy = a->dy; + bool flipped = false; + + if (x1 < x0) { + // Convert NE-SW edge to NW-SE. + // Flipping the edge preserves signed area. + + float t = y0; + y0 = top + bottom - y1; + y1 = top + bottom - t; + t = x0, x0 = x1, x1 = t; + dy = -dy; + flipped = true; + } + + if (x1 < shape.left) { + x0 = x1 = shape.left; + } else if (x0 < shape.left) { + y0 += (shape.left - x0) * dy; + x0 = shape.left; + } + + if (x1 >= shape.right) { + y1 += (x1 - shape.right + 1) * dy; + x1 = shape.right - 1; + } + + if (y1 <= y0 || x0 >= shape.right || x1 < shape.left || EsCRTisnanf(x0) || EsCRTisnanf(x1) || EsCRTisnanf(y0) || EsCRTisnanf(y1)) { + continue; + } + + if (EsCRTfloorf(x0) == EsCRTfloorf(x1)) { + // Edge crosses one pixel on this scanline, + // forming a trapezium. + + float right = EsCRTfloorf(x0 + 1); + float p = a->sign * (y1 - y0); + int xs = (int) x0; + surface.area[xs] += p * (right - x0 + right - x1) * 0.5f; + surface.areaFill[xs + 1] += p; + } else { + // Edge crosses multiple pixels on this scanline. + // The first pixel is a triangle. + + float tx = EsCRTfloorf(x0 + 1); + float th = dy * (tx - x0); + int xs = (int) x0, xf = (int) x1; + surface.area[xs] += a->sign * (tx - x0) * th * 0.5f; + + // The middle pixels are trapeziums. + + float pa = th + 0.5f * dy; + for (int j = xs + 1; j < xf; j++, pa += dy) surface.area[j] += a->sign * pa; + + // The final pixel is a removed triangle. + + float p = a->sign * (y1 - y0); + float fx = EsCRTfloorf(x1); + float fy = a->yf + a->dy * (fx - a->xf); + if (flipped) fy = top + bottom - fy; + surface.area[xf] += p - a->sign * 0.5f * (x1 - fx) * (y1 - fy); + surface.areaFill[xf + 1] += p; + } + } + + // Calculate the final coverage of each pixel. + + float cumulativeArea = 0; + uint32_t *destination = (uint32_t *) ((uint8_t *) surface.buffer + scanline * surface.stride + 4 * shape.left); + + float textureDX = paint.gradient.transform[0]; + float texturePX = shape.left * textureDX + (float) scanline * paint.gradient.transform[1] + paint.gradient.transform[2]; + float textureDY = paint.gradient.transform[3]; + float texturePY = shape.left * textureDY + (float) scanline * paint.gradient.transform[4] + paint.gradient.transform[5]; + + if (paint.type == RAST_PAINT_NOISE) { + textureDX = 1; + texturePX = 0; + } + + for (int i = shape.left; i < shape.right; i++) { + cumulativeArea += surface.areaFill[i]; + float a = surface.area[i] + cumulativeArea; + + if (evenOdd) { + a = EsCRTfmodf(AbsoluteFloat(a), 2); + if (a > 1) a = 2 - a; + } else { + if (a < 0) a = -a; + if (a > 1) a = 1; + } + + if (a > 0.0039f) { + if (paint.type == RAST_PAINT_SOLID) { + uint8_t c = (uint8_t) (a * paint.solid.alpha * 255.0f); + if (c) BlendPixel(destination, (c << 24) | paint.solid.color, true); + } else if (paint.type == RAST_PAINT_CHECKERBOARD) { + if (((i - shape.left) / paint.checkboard.size + (scanline - shape.top) / paint.checkboard.size) & 1) { + uint8_t c = (uint8_t) (a * paint.checkboard.alpha2 * 255.0f); + if (c) BlendPixel(destination, (c << 24) | paint.checkboard.color2, true); + } else { + uint8_t c = (uint8_t) (a * paint.checkboard.alpha1 * 255.0f); + if (c) BlendPixel(destination, (c << 24) | paint.checkboard.color1, true); + } + } else if (paint.type == RAST_PAINT_LINEAR_GRADIENT) { + float p = _RastRepeat(paint.gradient.repeatMode, texturePX); + int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p); + EsAssert(pi >= 0 && pi < RAST_GRADIENT_COLORS); // Invalid gradient index. + uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f); + if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true); + } else if (paint.type == RAST_PAINT_RADIAL_GRADIENT) { + float p = EsCRTsqrtf(texturePX * texturePX + texturePY * texturePY); + if (p < 0) p = 0; + if (p > 1) p = 1; + int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p); + uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f); + if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true); + } else if (paint.type == RAST_PAINT_ANGULAR_GRADIENT) { + float p = EsCRTatan2f(texturePY, texturePX) * 0.159154943091f + 0.5f; + if (p < 0) p = 0; + if (p > 1) p = 1; + int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p); + uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f); + if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true); + } else if (paint.type == RAST_PAINT_NOISE) { + union { float f; uint32_t u; } noise = { texturePX + texturePY }; + noise.u += noise.u << 10; + noise.u ^= noise.u >> 6; + noise.u += noise.u << 3; + noise.u ^= noise.u >> 11; + noise.u += noise.u << 15; + noise.u &= 0x7FFFFF; + noise.u |= 0x3F800000; + noise.f /= 2; + noise.f *= paint.noise.maximum - paint.noise.minimum; + noise.f += paint.noise.minimum; + + uint8_t c = (uint8_t) (a * noise.f * 255.0f); + if (c) BlendPixel(destination, (c << 24) | paint.noise.color, true); + } + } + + surface.area[i] = surface.areaFill[i] = 0; + destination++; + texturePX += textureDX; + texturePY += textureDY; + } + + surface.areaFill[shape.right] = 0; + } + + RAST_ARRAY_FREE(active); + _RastShapeDestroy(shape); +} + +void _RastShapeAddEdges(RastShape *shape, RastVertex *vertices, int sign, bool open, int vertexCount) { + if (vertexCount <= 1) return; + + for (int i = 0; i < vertexCount - (open ? 1 : 0); i++) { + RastVertex *from = &vertices[i]; + RastVertex *to = &vertices[(i + 1) % vertexCount]; + if (from->y == to->y) continue; + + int p = RAST_ARRAY_LENGTH(shape->edges); + RAST_ARRAY_INSERT(shape->edges, p, 1); + RastEdge *edge = &shape->edges[p]; + + if (from->y < to->y) { + edge->xf = from->x, edge->yf = from->y; + edge->xt = to->x, edge->yt = to->y; + edge->sign = sign; + } else { + edge->xf = to->x, edge->yf = to->y; + edge->xt = from->x, edge->yt = from->y; + edge->sign = -sign; + } + + edge->dx = (edge->xt - edge->xf) / (edge->yt - edge->yf); + edge->dy = 1.0f / edge->dx; + + if (edge->xf < shape->left) shape->left = edge->xf; + if (edge->xt < shape->left) shape->left = edge->xt; + if (edge->yf < shape->top) shape->top = edge->yf; + if (edge->xf + 1 > shape->right) shape->right = edge->xf + 1; + if (edge->xt + 1 > shape->right) shape->right = edge->xt + 1; + if (edge->yt + 1 > shape->bottom) shape->bottom = edge->yt + 1; + } +} + +void RastPathCloseSegment(RastPath *path) { + if (!RAST_ARRAY_LENGTH_U(path->segments) || RAST_ARRAY_LAST(path->segments).uptoVertex != RAST_ARRAY_LENGTH_U(path->vertices)) { + RastPathSegment segment = {}; + segment.uptoVertex = RAST_ARRAY_LENGTH_U(path->vertices); + RAST_ARRAY_ADD(path->segments, segment); + } +} + +RastShape RastShapeCreateSolid(RastPath *path) { + if (RAST_ARRAY_LENGTH(path->vertices) < 3) return (RastShape) { 0 }; + RastPathCloseSegment(path); + RastShape shape = { .left = INT_MAX, .top = INT_MAX }; + uintptr_t start = 0; + + for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->segments); i++) { + _RastShapeAddEdges(&shape, &path->vertices[start], 1, false, path->segments[i].uptoVertex - start); + start = path->segments[i].uptoVertex; + } + + return shape; +} + +void _RastJoinMeter(RAST_ARRAY(RastVertex) *output, RastVertex *from1, RastVertex *to1, RastVertex *from2, RastVertex *to2, RastVertex *point, float miterLimit) { + // TODO For internal contours, this can generate vertices forming anticlockwise regions if the contour width is large enough. + // (You can't see it though because we use a non-zero fill rule.) + + float a = to1->x - from1->x, b = from2->x - to2->x, e = from2->x - from1->x; + float c = to1->y - from1->y, d = from2->y - to2->y, f = from2->y - from1->y; + float j = (d * e - b * f) / (d * a - b * c); + RastVertex v = { from1->x + j * (to1->x - from1->x), from1->y + j * (to1->y - from1->y) }; + + if ((v.x - point->x) * (v.x - point->x) + (v.y - point->y) * (v.y - point->y) <= miterLimit * miterLimit) { + RAST_ARRAY_ADD(*output, v); + } else { + // TODO Move bevel to miter limit. + RAST_ARRAY_ADD(*output, *to1); + RAST_ARRAY_ADD(*output, *from2); + } +} + +void _RastJoinArc(RAST_ARRAY(RastVertex) *output, float fromAngle, float toAngle, int divisions, RastVertex *point, float width) { + for (int j = 0; j < divisions; j++) { + float angle = fromAngle + j * (toAngle - fromAngle) / (divisions - 1); + RastVertex v = { point->x + EsCRTcosf(angle) * width, point->y + EsCRTsinf(angle) * width }; + + if (j == 0 || j == divisions - 1) { + RAST_ARRAY_ADD(*output, v); + continue; + } + + RastVertex l = RAST_ARRAY_LAST(*output); + int dx = v.x - l.x, dy = v.y - l.y; + + if (dx * dx + dy * dy > RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) { + RAST_ARRAY_ADD(*output, v); + } + } +} + +void _RastJoinRound(RAST_ARRAY(RastVertex) *output, RastVertex *from1, RastVertex *to1, RastVertex *from2, + RastVertex *to2, RastVertex *point, float width, int divisions, bool internal) { + float fromAngle = EsCRTatan2f(to1->y - point->y, to1->x - point->x); + float toAngle = EsCRTatan2f(from2->y - point->y, from2->x - point->x); + + if (toAngle > fromAngle) { + toAngle -= 6.2831853071f; + } + + if (internal == (fromAngle - toAngle < 3.141592653f)) { + _RastJoinMeter(output, from1, to1, from2, to2, point, ES_INFINITY); + return; + } + + if (internal) { + toAngle += 6.2831853071f; + } + + _RastJoinArc(output, fromAngle, toAngle, divisions, point, width); +} + +void _RastShapeCreateContour(RastShape *shape, RastPath *path, RastContourStyle style, bool open) { + RAST_ARRAY(RastVertex) vertices = path->vertices; + size_t vertexCount = RAST_ARRAY_LENGTH(vertices); + + if (vertexCount < 2) { + return; + } + + if (!open) { + RastVertex first = vertices[0], last = RAST_ARRAY_LAST(vertices); + float dx = first.x - last.x, dy = first.y - last.y; + + if (dx * dx + dy * dy < RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) { + vertexCount--; + } + } + + if (vertexCount <= 1) { + return; + } else if (vertexCount == 2) { + open = true; + } + + RastVertex cap1, cap2, cap3, cap4; + + RAST_ARRAY(RastVertex) path1 = { 0 }; + RAST_ARRAY(RastVertex) path2 = { 0 }; + + for (int internal = 0; internal < 2; internal++) { + float width = internal ? style.internalWidth : style.externalWidth; + RastVertex *capStart = internal ? &cap4 : &cap1; + RastVertex *capEnd = internal ? &cap3 : &cap2; + + for (uintptr_t i = 0; i < vertexCount; i++) { + RastVertex *from = &vertices[(i + 0) % vertexCount], + *to = &vertices[(i + 1) % vertexCount]; + float dx = to->x - from->x, dy = to->y - from->y; + float scale = (internal ? -width : width) / EsCRTsqrtf(dx * dx + dy * dy); + float ox = -dy * scale, oy = dx * scale; + RastVertex cf = { from->x + ox, from->y + oy }; + RastVertex ct = { to->x + ox, to->y + oy }; + RAST_ARRAY_ADD(path1, cf); + RAST_ARRAY_ADD(path1, ct); + } + + if (open) RAST_ARRAY_ADD(path2, (*capStart = path1[0])); + + if (style.joinMode == RAST_LINE_JOIN_MITER) { + for (int i = 0; i < RAST_ARRAY_LENGTH(path1) - (open ? 4 : 0); i += 2) { + RastVertex *p1 = &vertices[(i / 2 + 0) % vertexCount], + *p2 = &vertices[(i / 2 + 1) % vertexCount], + *p3 = &vertices[(i / 2 + 2) % vertexCount]; + float cross = (p2->x - p1->x) * (p3->y - p2->y) - (p2->y - p1->y) * (p3->x - p2->x); + + _RastJoinMeter(&path2, &path1[i], &path1[i + 1], &path1[(i + 2) % RAST_ARRAY_LENGTH(path1)], + &path1[(i + 3) % RAST_ARRAY_LENGTH(path1)], &vertices[(i / 2 + 1) % vertexCount], + (internal ? (cross > 0) : (cross < 0)) ? style.miterLimit : ES_INFINITY); + } + } else if (style.joinMode == RAST_LINE_JOIN_ROUND) { + int divisions = EsCRTceilf(3.14159265358f * 0.5f / EsCRTacosf(width / (width + RAST_ROUND_TOLERANCE))) + 1; + + for (int i = 0; i < RAST_ARRAY_LENGTH(path1) - (open ? 4 : 0); i += 2) { + _RastJoinRound(&path2, &path1[i], &path1[i + 1], &path1[(i + 2) % RAST_ARRAY_LENGTH(path1)], + &path1[(i + 3) % RAST_ARRAY_LENGTH(path1)], &vertices[(i / 2 + 1) % vertexCount], width, divisions, internal); + } + } + + if (open) RAST_ARRAY_ADD(path2, (*capEnd = path1[RAST_ARRAY_LENGTH(path1) - 3])); + + _RastShapeAddEdges(shape, &path2[0], internal ? -1 : 1, open, RAST_ARRAY_LENGTH(path2)); + + RAST_ARRAY_CLEAR(path1); + RAST_ARRAY_CLEAR(path2); + } + + if (open) { + // TODO Cap styles don't work if the contour is not centered (i.e. internalWidth != externalWidth). + + for (int i = 0; i < 2; i++) { + RastVertex c = i ? cap4 : cap2, d = i ? cap1 : cap3; + RastVertex *from = &vertices[(i ? 1 : (vertexCount - 2))]; + RastVertex *to = &vertices[(i ? 0 : (vertexCount - 1))]; + RAST_ARRAY_ADD(path1, c); + + if (style.capMode == RAST_LINE_CAP_SQUARE) { + float dx = to->x - from->x, dy = to->y - from->y; + float scale = 0.5f * (style.internalWidth + style.externalWidth) / EsCRTsqrtf(dx * dx + dy * dy); + RastVertex c0 = { .x = c.x + scale * dx, .y = c.y + scale * dy }; + RastVertex d0 = { .x = d.x + scale * dx, .y = d.y + scale * dy }; + RAST_ARRAY_ADD(path1, c0); + RAST_ARRAY_ADD(path1, d0); + } else if (style.capMode == RAST_LINE_CAP_ROUND) { + float angle = EsCRTatan2f(d.y - to->y, d.x - to->x); + float width = 0.5f * (style.internalWidth + style.externalWidth); + int divisions = EsCRTceilf(3.14159265358f * 0.5f / EsCRTacosf(width / (width + RAST_ROUND_TOLERANCE))) + 1; + _RastJoinArc(&path1, angle + 3.14159265358f, angle, divisions, to, width); + } + + RAST_ARRAY_ADD(path1, d); + _RastShapeAddEdges(shape, &path1[0], 1, true, RAST_ARRAY_LENGTH(path1)); + RAST_ARRAY_CLEAR(path1); + } + } + + RAST_ARRAY_FREE(path1); + RAST_ARRAY_FREE(path2); +} + +RastShape RastShapeCreateContour(RastPath *path, RastContourStyle style, bool open) { + RastShape shape = { .left = INT_MAX, .top = INT_MAX }; + _RastShapeCreateContour(&shape, path, style, open); + if (RAST_ARRAY_LENGTH(shape.edges) == 0) return (RastShape) { 0 }; + return shape; +} + +void _RastPathAddVertex(RastPath *path, RastVertex vertex) { + if (RAST_ARRAY_LENGTH(path->vertices)) { + RastVertex last = RAST_ARRAY_LAST(path->vertices); + float dx = last.x - vertex.x, dy = last.y - vertex.y; + + if (dx * dx + dy * dy < RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) { + return; + } + } + + RAST_ARRAY_ADD(path->vertices, vertex); +} + +RastShape RastShapeCreateDashed(RastPath *path, RastDash *dashStyles, size_t dashStyleCount, bool open) { + RAST_ARRAY(RastVertex) vertices = path->vertices; + size_t vertexCount = RAST_ARRAY_LENGTH(vertices); + + if (dashStyleCount < 1 || vertexCount < 2) { + return (RastShape) { 0 }; + } + + RastDash *style = dashStyles + 0; + RastVertex from = vertices[0]; + RastVertex *to = &vertices[1]; + RastPath dash = {}; + RastShape shape = { .left = INT_MAX, .top = INT_MAX }; + + if (!open) { + from = RAST_ARRAY_LAST(vertices); + to = &vertices[0]; + } + + while (to != &vertices[vertexCount]) { + float accumulatedLength = 0; + + _RastPathAddVertex(&dash, from); + + while (to != &vertices[vertexCount]) { + float dx = to->x - from.x, dy = to->y - from.y; + float distance = EsCRTsqrtf(dx * dx + dy * dy); + + if (accumulatedLength + distance >= style->length) { + float fraction = (style->length - accumulatedLength) / distance; + RastVertex stop = { from.x + fraction * dx, from.y + fraction * dy }; + _RastPathAddVertex(&dash, stop); + from = stop; + break; + } + + accumulatedLength += distance; + from = *to; + _RastPathAddVertex(&dash, from); + to++; + } + + _RastShapeCreateContour(&shape, &dash, *style->style, true); + + accumulatedLength = 0; + + while (to != &vertices[vertexCount]) { + float dx = to->x - from.x, dy = to->y - from.y; + float distance = EsCRTsqrtf(dx * dx + dy * dy); + + if (accumulatedLength + distance >= style->gap) { + float fraction = (style->gap - accumulatedLength) / distance; + RastVertex stop = { from.x + fraction * dx, from.y + fraction * dy }; + from = stop; + break; + } + + accumulatedLength += distance; + from = *to; + to++; + } + + style++; + + if (style == dashStyles + dashStyleCount) { + style = dashStyles + 0; + } + + RAST_ARRAY_CLEAR(dash.vertices); + } + + return shape; +} + +RastVertex _RastVertexScale(RastVertex vertex, RastVertex scale) { + return (RastVertex) { vertex.x * scale.x, vertex.y * scale.y }; +} + +void _RastFlattenBezierRecursive(RastPath *path, RastVertex v1, RastVertex v2, RastVertex v3, RastVertex v4, int level) { + if (level > 8) return; + + RastVertex v12 = RAST_AVERAGE_VERTICES(v1, v2); + RastVertex v23 = RAST_AVERAGE_VERTICES(v2, v3); + RastVertex v34 = RAST_AVERAGE_VERTICES(v3, v4); + + RastVertex delta = { v4.x - v1.x, v4.y - v1.y }; + float d = AbsoluteFloat((v2.x - v4.x) * delta.y + (v4.y - v2.y) * delta.x) + + AbsoluteFloat((v3.x - v4.x) * delta.y + (v4.y - v3.y) * delta.x); + + if (d * d < RAST_FLATTEN_TOLERANCE * (delta.x * delta.x + delta.y * delta.y)) { + _RastPathAddVertex(path, v4); + return; + } + + RastVertex v123 = RAST_AVERAGE_VERTICES(v12, v23); + RastVertex v234 = RAST_AVERAGE_VERTICES(v23, v34); + RastVertex v1234 = RAST_AVERAGE_VERTICES(v123, v234); + + _RastFlattenBezierRecursive(path, v1, v12, v123, v1234, level + 1); + _RastFlattenBezierRecursive(path, v1234, v234, v34, v4, level + 1); +} + +void RastPathAppendBezier(RastPath *path, const RastVertex *vertices, size_t vertexCount, RastVertex scale) { + if (vertexCount < 4) return; + _RastPathAddVertex(path, _RastVertexScale(vertices[0], scale)); + + for (uintptr_t i = 0; i < vertexCount - 3; i += 3) { + // TODO Scale the control points such that the center of the curve is aligned? + _RastFlattenBezierRecursive(path, _RastVertexScale(vertices[i + 0], scale), _RastVertexScale(vertices[i + 1], scale), + _RastVertexScale(vertices[i + 2], scale), _RastVertexScale(vertices[i + 3], scale), 0); + } +} + +void RastPathAppendLinear(RastPath *path, RastVertex *vertices, size_t vertexCount, RastVertex scale) { + if (!vertexCount) return; + + for (uintptr_t i = 0; i < vertexCount; i++) { + _RastPathAddVertex(path, _RastVertexScale(vertices[i], scale)); + } +} + +void RastPathTranslate(RastPath *path, float x, float y) { + if (!x && !y) return; + + for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->vertices); i++) { + path->vertices[i].x += x; + path->vertices[i].y += y; + } +} + +void RastPathTransform(RastPath *path, float *matrix) { + for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->vertices); i++) { + float x = path->vertices[i].x, y = path->vertices[i].y; + path->vertices[i].x = matrix[0] * x + matrix[1] * y + matrix[2]; + path->vertices[i].y = matrix[3] * x + matrix[4] * y + matrix[5]; + } +} + +void RastPathDestroy(RastPath *path) { + RAST_ARRAY_FREE(path->segments); + RAST_ARRAY_FREE(path->vertices); +} + +float _RastInterpolateWithGamma(float from, float to, float progress) { + from = from * from; + to = to * to; + return EsCRTsqrtf(from + progress * (to - from)); +} + +float _RastInterpolateSimple(float from, float to, float progress) { + return from + progress * (to - from); +} + +void RastGradientInitialise(RastPaint *paint, RastGradientStop *stops, size_t stopCount, bool useGammaInterpolation) { + if (!stopCount) { + return; + } + + paint->gradient.color = (uint32_t *) EsHeapAllocate(4 * RAST_GRADIENT_COLORS, false); + paint->gradient.alpha = (float *) EsHeapAllocate(sizeof(float) * RAST_GRADIENT_COLORS, false); + + for (uintptr_t stop = 0; stop < stopCount - 1; stop++) { + float fa = ((stops[stop + 0].color >> 24) & 0xFF) / 255.0f; + float fb = ((stops[stop + 0].color >> 16) & 0xFF) / 255.0f; + float fg = ((stops[stop + 0].color >> 8) & 0xFF) / 255.0f; + float fr = ((stops[stop + 0].color >> 0) & 0xFF) / 255.0f; + float ta = ((stops[stop + 1].color >> 24) & 0xFF) / 255.0f; + float tb = ((stops[stop + 1].color >> 16) & 0xFF) / 255.0f; + float tg = ((stops[stop + 1].color >> 8) & 0xFF) / 255.0f; + float tr = ((stops[stop + 1].color >> 0) & 0xFF) / 255.0f; + + int fi = RAST_GRADIENT_COLORS * (stop == 0 ? 0 : stops[stop + 0].position); + int ti = RAST_GRADIENT_COLORS * (stop == stopCount - 2 ? 1 : stops[stop + 1].position); + if (fi < 0) fi = 0; + if (ti > RAST_GRADIENT_COLORS) ti = RAST_GRADIENT_COLORS; + + for (int i = fi; i < ti; i++) { + float p = (float) (i - fi) / (ti - fi); + paint->gradient.alpha[i] = fa + (ta - fa) * p; + + if (useGammaInterpolation) { + paint->gradient.color[i] = (uint32_t) (_RastInterpolateWithGamma(fr, tr, p) * 255.0f) << 0 + | (uint32_t) (_RastInterpolateWithGamma(fg, tg, p) * 255.0f) << 8 + | (uint32_t) (_RastInterpolateWithGamma(fb, tb, p) * 255.0f) << 16; + } else { + paint->gradient.color[i] = (uint32_t) (_RastInterpolateSimple(fr, tr, p) * 255.0f) << 0 + | (uint32_t) (_RastInterpolateSimple(fg, tg, p) * 255.0f) << 8 + | (uint32_t) (_RastInterpolateSimple(fb, tb, p) * 255.0f) << 16; + } + } + } +} + +void RastGradientDestroy(RastPaint *paint) { + if (paint->type == RAST_PAINT_LINEAR_GRADIENT + || paint->type == RAST_PAINT_RADIAL_GRADIENT + || paint->type == RAST_PAINT_ANGULAR_GRADIENT) { + EsHeapFree(paint->gradient.color); + EsHeapFree(paint->gradient.alpha); + } +} + +#ifndef IN_DESIGNER +void EsDrawLine(EsPainter *painter, float *vertices, size_t vertexCount, uint32_t color, float width, uint32_t flags) { + RastSurface surface = {}; + surface.buffer = (uint32_t *) painter->target->bits; + surface.stride = painter->target->stride; + + if (RastSurfaceInitialise(&surface, painter->target->width, painter->target->height, true)) { + RastPath path = {}; + RastPathAppendLinear(&path, (RastVertex *) vertices, vertexCount, { 1, 1 }); + RastContourStyle style = {}; + style.externalWidth = width / 2.0f; + style.internalWidth = width / 2.0f; + style.capMode = (flags & ES_DRAW_LINE_CAP_ROUND) ? RAST_LINE_CAP_ROUND + : (flags & ES_DRAW_LINE_CAP_SQUARE) ? RAST_LINE_CAP_SQUARE + : RAST_LINE_CAP_FLAT; + RastShape shape = RastShapeCreateContour(&path, style, true); + RastPaint paint = {}; + paint.type = RAST_PAINT_SOLID; + paint.solid.color = color & 0xFFFFFF; + paint.solid.alpha = (color >> 24) / 255.0f; + RastSurfaceFill(surface, shape, paint, false); + RastPathDestroy(&path); + } + + RastSurfaceDestroy(&surface); +} +#endif diff --git a/desktop/styles.header b/desktop/styles.header new file mode 100644 index 0000000..edebe24 --- /dev/null +++ b/desktop/styles.header @@ -0,0 +1,113 @@ +define_private ES_STYLE__TEST_STYLE (ES_STYLE_CAST(1385)) +define_private ES_STYLE_ACCESS_KEY_HINT (ES_STYLE_CAST(1221)) +define_private ES_STYLE_ANNOUNCEMENT (ES_STYLE_CAST(1511)) +define_private ES_STYLE_BREADCRUMB_BAR_CRUMB (ES_STYLE_CAST(1223)) +define_private ES_STYLE_BREADCRUMB_BAR_OVERFLOW_ICON (ES_STYLE_CAST(1225)) +define_private ES_STYLE_BREADCRUMB_BAR_PANEL (ES_STYLE_CAST(1227)) +define ES_STYLE_BUTTON_GROUP_CONTAINER (ES_STYLE_CAST(1229)) +define ES_STYLE_BUTTON_GROUP_ITEM (ES_STYLE_CAST(1231)) +define ES_STYLE_BUTTON_GROUP_SEPARATOR (ES_STYLE_CAST(1233)) +define_private ES_STYLE_CANVAS_SHADOW (ES_STYLE_CAST(1451)) +define_private ES_STYLE_CHECKBOX_NORMAL (ES_STYLE_CAST(1235)) +define_private ES_STYLE_CHECKBOX_RADIOBOX (ES_STYLE_CAST(1237)) +define_private ES_STYLE_COLOR_CHOSEN_POINT (ES_STYLE_CAST(1241)) +define_private ES_STYLE_COLOR_CIRCLE (ES_STYLE_CAST(1243)) +define_private ES_STYLE_COLOR_HEX_TEXTBOX (ES_STYLE_CAST(1245)) +define_private ES_STYLE_COLOR_PICKER_MAIN_PANEL (ES_STYLE_CAST(1247)) +define_private ES_STYLE_COLOR_SLIDER (ES_STYLE_CAST(1249)) +define_private ES_STYLE_CONTAINER_WINDOW_ACTIVE (ES_STYLE_CAST(1251)) +define_private ES_STYLE_CONTAINER_WINDOW_INACTIVE (ES_STYLE_CAST(1255)) +define ES_STYLE_DIALOG_BUTTON_AREA (ES_STYLE_CAST(1259)) +define ES_STYLE_DIALOG_CONTENT (ES_STYLE_CAST(1261)) +define ES_STYLE_DIALOG_HEADING (ES_STYLE_CAST(1263)) +define ES_STYLE_ICON_DISPLAY (ES_STYLE_CAST(1265)) +define ES_STYLE_ICON_DISPLAY_SMALL (ES_STYLE_CAST(1543)) +define ES_STYLE_INSTALLER_ROOT (ES_STYLE_CAST(1267)) +define ES_STYLE_LIST_CHOICE_BORDERED (ES_STYLE_CAST(1429)) +define ES_STYLE_LIST_CHOICE_ITEM (ES_STYLE_CAST(1435)) +define_private ES_STYLE_LIST_COLUMN_HEADER (ES_STYLE_CAST(1269)) +define_private ES_STYLE_LIST_COLUMN_HEADER_ITEM (ES_STYLE_CAST(1271)) +define_private ES_STYLE_LIST_COLUMN_HEADER_ITEM_HAS_MENU (ES_STYLE_CAST(1273)) +define_private ES_STYLE_LIST_COLUMN_HEADER_SPLITTER (ES_STYLE_CAST(1275)) +define_private ES_STYLE_LIST_GROUP_HEADER_CELL (ES_STYLE_CAST(1277)) +define ES_STYLE_LIST_ITEM (ES_STYLE_CAST(1279)) +define ES_STYLE_LIST_ITEM_GROUP_FOOTER (ES_STYLE_CAST(1281)) +define ES_STYLE_LIST_ITEM_GROUP_HEADER (ES_STYLE_CAST(1283)) +define ES_STYLE_LIST_ITEM_TILE (ES_STYLE_CAST(1285)) +define_private ES_STYLE_LIST_PRIMARY_CELL (ES_STYLE_CAST(1287)) +define_private ES_STYLE_LIST_SECONDARY_CELL (ES_STYLE_CAST(1289)) +define_private ES_STYLE_LIST_SELECTION_BOX (ES_STYLE_CAST(1291)) +define ES_STYLE_LIST_VIEW (ES_STYLE_CAST(1293)) +define ES_STYLE_LIST_VIEW_BORDERED (ES_STYLE_CAST(1295)) +define ES_STYLE_LIST_DISPLAY_DEFAULT (ES_STYLE_CAST(1441)) +define_private ES_STYLE_MARKER_DOWN_ARROW (ES_STYLE_CAST(1297)) +define_private ES_STYLE_MARKER_UP_ARROW (ES_STYLE_CAST(1501)) +define_private ES_STYLE_MENU_ITEM_HEADER (ES_STYLE_CAST(1299)) +define_private ES_STYLE_MENU_ITEM_NORMAL (ES_STYLE_CAST(1301)) +define_private ES_STYLE_MENU_SEPARATOR_HORIZONTAL (ES_STYLE_CAST(1303)) +define_private ES_STYLE_MENU_SEPARATOR_VERTICAL (ES_STYLE_CAST(1305)) +define_private ES_STYLE_PANEL_CONTAINER_WINDOW_ROOT (ES_STYLE_CAST(1307)) +define_private ES_STYLE_PANEL_CRASH_INFO (ES_STYLE_CAST(1309)) +define ES_STYLE_PANEL_DIALOG_ROOT (ES_STYLE_CAST(1311)) +define ES_STYLE_PANEL_DOCUMENT (ES_STYLE_CAST(1547)) +define ES_STYLE_PANEL_FILLED (ES_STYLE_CAST(1313)) +define ES_STYLE_PANEL_GROUP_BOX (ES_STYLE_CAST(1315)) +define_private ES_STYLE_PANEL_INSPECTOR_WINDOW_CONTAINER (ES_STYLE_CAST(1317)) +define_private ES_STYLE_PANEL_INSPECTOR_WINDOW_ROOT (ES_STYLE_CAST(1319)) +define_private ES_STYLE_PANEL_MENU_COLUMN (ES_STYLE_CAST(1321)) +define_private ES_STYLE_PANEL_MENU_CONTAINER (ES_STYLE_CAST(1323)) +define_private ES_STYLE_PANEL_MENU_ROOT (ES_STYLE_CAST(1325)) +define_private ES_STYLE_PANEL_MODAL_OVERLAY (ES_STYLE_CAST(1327)) +define_private ES_STYLE_PANEL_NORMAL_WINDOW_ROOT (ES_STYLE_CAST(1329)) +define ES_STYLE_PANEL_POPUP (ES_STYLE_CAST(1331)) +define ES_STYLE_PANEL_SHEET (ES_STYLE_CAST(1333)) +define_private ES_STYLE_PANEL_SHUTDOWN_OVERLAY (ES_STYLE_CAST(1335)) +define ES_STYLE_PANEL_STATUS_BAR (ES_STYLE_CAST(1489)) +define ES_STYLE_PANEL_TOOLBAR (ES_STYLE_CAST(1337)) +define ES_STYLE_PANEL_TOOLBAR_ROOT (ES_STYLE_CAST(1339)) +define ES_STYLE_PANEL_WINDOW_BACKGROUND (ES_STYLE_CAST(1341)) +define ES_STYLE_PANEL_WINDOW_DIVIDER (ES_STYLE_CAST(1343)) +define ES_STYLE_PANEL_WINDOW_WITH_STATUS_BAR_CONTENT (ES_STYLE_CAST(1483)) +define ES_STYLE_PUSH_BUTTON_DANGEROUS (ES_STYLE_CAST(1345)) +define_private ES_STYLE_PUSH_BUTTON_NORMAL (ES_STYLE_CAST(1347)) +define_private ES_STYLE_PUSH_BUTTON_NORMAL_COLOR_WELL (ES_STYLE_CAST(1349)) +define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_DOWN (ES_STYLE_CAST(1351)) +define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_LEFT (ES_STYLE_CAST(1353)) +define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_RIGHT (ES_STYLE_CAST(1355)) +define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_UP (ES_STYLE_CAST(1357)) +define ES_STYLE_PUSH_BUTTON_STATUS_BAR (ES_STYLE_CAST(1495)) +define ES_STYLE_PUSH_BUTTON_TOOLBAR (ES_STYLE_CAST(1359)) +define ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG (ES_STYLE_CAST(1457)) +define ES_STYLE_PUSH_BUTTON_TOOLBAR_MEDIUM (ES_STYLE_CAST(1461)) +define_private ES_STYLE_SCROLLBAR_BAR_HORIZONTAL (ES_STYLE_CAST(1363)) +define_private ES_STYLE_SCROLLBAR_BAR_VERTICAL (ES_STYLE_CAST(1365)) +define_private ES_STYLE_SCROLLBAR_THUMB_HORIZONTAL (ES_STYLE_CAST(1367)) +define_private ES_STYLE_SCROLLBAR_THUMB_VERTICAL (ES_STYLE_CAST(1369)) +define_private ES_STYLE_SCROLLBAR_PAD (ES_STYLE_CAST(1371)) +define ES_STYLE_SEPARATOR_HORIZONTAL (ES_STYLE_CAST(1373)) +define_private ES_STYLE_SPLIT_BAR_HORIZONTAL (ES_STYLE_CAST(1375)) +define_private ES_STYLE_SPLIT_BAR_VERTICAL (ES_STYLE_CAST(1377)) +define_private ES_STYLE_TASK_BAR_BAR (ES_STYLE_CAST(1379)) +define_private ES_STYLE_TASK_BAR_BUTTON (ES_STYLE_CAST(1381)) +define_private ES_STYLE_TASK_BAR_EXTRA (ES_STYLE_CAST(1507)) +define_private ES_STYLE_TASK_BAR_NEW_WINDOW (ES_STYLE_CAST(1383)) +define ES_STYLE_TEXT_HEADING0 (ES_STYLE_CAST(1387)) +define ES_STYLE_TEXT_HEADING2 (ES_STYLE_CAST(1389)) +define_private ES_STYLE_TEXT_HEADING3 (ES_STYLE_CAST(1423)) +define ES_STYLE_TEXT_LABEL (ES_STYLE_CAST(1391)) +define ES_STYLE_TEXT_LABEL_INVERTED (ES_STYLE_CAST(1393)) +define ES_STYLE_TEXT_LABEL_SECONDARY (ES_STYLE_CAST(1395)) +define ES_STYLE_TEXT_PARAGRAPH (ES_STYLE_CAST(1397)) +define ES_STYLE_TEXT_TOOLBAR (ES_STYLE_CAST(1553)) +define ES_STYLE_TEXTBOX_BORDERED_MULTILINE (ES_STYLE_CAST(1399)) +define ES_STYLE_TEXTBOX_BORDERED_SINGLE (ES_STYLE_CAST(1401)) +define ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT (ES_STYLE_CAST(1403)) +define_private ES_STYLE_TEXTBOX_INLINE (ES_STYLE_CAST(1477)) +define_private ES_STYLE_TEXTBOX_MARGIN (ES_STYLE_CAST(1415)) +define ES_STYLE_TEXTBOX_NO_BORDER (ES_STYLE_CAST(1405)) +define ES_STYLE_TEXTBOX_TRANSPARENT (ES_STYLE_CAST(1445)) +define_private ES_STYLE_WINDOW_TAB_ACTIVE (ES_STYLE_CAST(1407)) +define_private ES_STYLE_WINDOW_TAB_CLOSE_BUTTON (ES_STYLE_CAST(1469)) +define_private ES_STYLE_WINDOW_TAB_INACTIVE (ES_STYLE_CAST(1409)) +define_private ES_STYLE_WINDOW_TAB_BAND (ES_STYLE_CAST(1411)) +define_private ES_STYLE_WINDOW_TAB_BAND_NEW (ES_STYLE_CAST(1361)) diff --git a/desktop/syscall.cpp b/desktop/syscall.cpp new file mode 100644 index 0000000..f5f4eb1 --- /dev/null +++ b/desktop/syscall.cpp @@ -0,0 +1,658 @@ +ThreadLocalStorage *GetThreadLocalStorage() { + return (ThreadLocalStorage *) ProcessorTLSRead(tlsStorageOffset); +} + +double EsTimeStampMs() { + if (!api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND]) { + return 0; + } else { + return (double) EsTimeStamp() / api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND] / 1000; + } +} + +void *EsMemoryReserve(size_t size, EsMemoryProtection protection, uint32_t flags) { + intptr_t result = EsSyscall(ES_SYSCALL_MEMORY_ALLOCATE, size, flags, protection, 0); + + if (result >= 0) { + return (void *) result; + } else { + return nullptr; + } +} + +void EsMemoryUnreserve(void *address, size_t size) { + EsSyscall(ES_SYSCALL_MEMORY_FREE, (uintptr_t) address, size, 0, 0); +} + +bool EsMemoryCommit(void *pointer, size_t bytes) { + EsAssert(((uintptr_t) pointer & (ES_PAGE_SIZE - 1)) == 0 && (bytes & (ES_PAGE_SIZE - 1)) == 0); // Misaligned pointer/bytes in EsMemoryCommit. + return ES_SUCCESS == (intptr_t) EsSyscall(ES_SYSCALL_MEMORY_COMMIT, (uintptr_t) pointer >> ES_PAGE_BITS, bytes >> ES_PAGE_BITS, 0, 0); +} + +void EsMemoryFaultRange(const void *pointer, size_t bytes, uint32_t flags) { + EsSyscall(ES_SYSCALL_MEMORY_FAULT_RANGE, (uintptr_t) pointer, bytes, flags, 0); +} + +bool EsMemoryDecommit(void *pointer, size_t bytes) { + EsAssert(((uintptr_t) pointer & (ES_PAGE_SIZE - 1)) == 0 && (bytes & (ES_PAGE_SIZE - 1)) == 0); // Misaligned pointer/bytes in EsMemoryDecommit. + return ES_SUCCESS == (intptr_t) EsSyscall(ES_SYSCALL_MEMORY_COMMIT, (uintptr_t) pointer >> ES_PAGE_BITS, bytes >> ES_PAGE_BITS, 1, 0); +} + +EsError EsProcessCreate(EsProcessCreationArguments *arguments, EsProcessInformation *information) { + EsProcessInformation _information; + if (!information) information = &_information; + + EsError error = EsSyscall(ES_SYSCALL_PROCESS_CREATE, (uintptr_t) arguments, 0, (uintptr_t) information, 0); + + if (error == ES_SUCCESS && information == &_information) { + EsHandleClose(information->handle); + EsHandleClose(information->mainThread.handle); + } + + return error; +} + +EsError EsMessagePost(EsElement *target, EsMessage *message) { + EsMutexAcquire(&api.postBoxMutex); + + _EsMessageWithObject m = { target, *message }; + bool success = api.postBox.Add(m); + + if (api.postBox.Length() == 1 && success) { + EsMessage m; + m.type = ES_MSG_WAKEUP; + success = ES_SUCCESS == (EsError) EsSyscall(ES_SYSCALL_MESSAGE_POST, (uintptr_t) &m, 0, ES_CURRENT_PROCESS, 0); + } + + EsMutexRelease(&api.postBoxMutex); + + return success ? ES_SUCCESS : ES_ERROR_INSUFFICIENT_RESOURCES; +} + +EsError EsMessagePostRemote(EsHandle process, EsMessage *message) { + return EsSyscall(ES_SYSCALL_MESSAGE_POST, (uintptr_t) message, 0, process, 0); +} + +EsHandle EsEventCreate(bool autoReset) { + return EsSyscall(ES_SYSCALL_EVENT_CREATE, autoReset, 0, 0, 0); +} + +void EsEventSet(EsHandle handle) { + EsSyscall(ES_SYSCALL_EVENT_SET, handle, 0, 0, 0); +} + +void EsEventReset(EsHandle handle) { + EsSyscall(ES_SYSCALL_EVENT_RESET, handle, 0, 0, 0); +} + +EsError EsHandleClose(EsHandle handle) { + return EsSyscall(ES_SYSCALL_HANDLE_CLOSE, handle, 0, 0, 0); +} + +void EsThreadTerminate(EsHandle thread) { + EsSyscall(ES_SYSCALL_THREAD_TERMINATE, thread, 0, 0, 0); +} + +void EsProcessTerminate(EsHandle process, int status) { + EsSyscall(ES_SYSCALL_PROCESS_TERMINATE, process, status, 0, 0); +} + +void EsProcessTerminateCurrent() { + EsSyscall(ES_SYSCALL_PROCESS_TERMINATE, ES_CURRENT_PROCESS, 0, 0, 0); +} + +int EsProcessGetExitStatus(EsHandle process) { + return EsSyscall(ES_SYSCALL_PROCESS_GET_STATUS, process, 0, 0, 0); +} + +void ThreadInitialise(); + +void ThreadEntry(EsGeneric argument, EsThreadEntryFunction entryFunction) { + ThreadInitialise(); + entryFunction(argument); + EsThreadTerminate(ES_CURRENT_THREAD); +} + +EsError EsThreadCreate(EsThreadEntryFunction entryFunction, EsThreadInformation *information, EsGeneric argument) { + EsThreadInformation discard = {}; + + if (!information) { + information = &discard; + } + + EsError error = EsSyscall(ES_SYSCALL_THREAD_CREATE, (uintptr_t) ThreadEntry, (uintptr_t) entryFunction, (uintptr_t) information, argument.u); + + if (error == ES_SUCCESS && information == &discard) { + EsHandleClose(information->handle); + } + + return error; +} + +EsError EsFileWriteAll(const char *filePath, ptrdiff_t filePathLength, const void *data, size_t sizes) { + return EsFileWriteAllGather(filePath, filePathLength, &data, &sizes, 1); +} + +EsError EsFileWriteAllGather(const char *filePath, ptrdiff_t filePathLength, const void **data, size_t *sizes, size_t gatherCount) { + if (filePathLength == -1) { + filePathLength = EsCStringLength(filePath); + } + + EsFileInformation information = EsFileOpen((char *) filePath, filePathLength, ES_FILE_WRITE_EXCLUSIVE | ES_NODE_CREATE_DIRECTORIES); + + if (ES_SUCCESS != information.error) { + return information.error; + } + + EsError error = EsFileWriteAllGatherFromHandle(information.handle, data, sizes, gatherCount); + EsHandleClose(information.handle); + return error; +} + +EsError EsFileWriteAllFromHandle(EsHandle handle, const void *data, size_t sizes) { + return EsFileWriteAllGatherFromHandle(handle, &data, &sizes, 1); +} + +EsError EsFileWriteAllGatherFromHandle(EsHandle handle, const void **data, size_t *sizes, size_t gatherCount) { + size_t fileSize = 0; + + for (uintptr_t i = 0; i < gatherCount; i++) { + fileSize += sizes[i]; + } + + EsError error = EsFileResize(handle, fileSize); + if (ES_CHECK_ERROR(error)) return error; + + size_t offset = 0; + + for (uintptr_t i = 0; i < gatherCount; i++) { + error = EsFileWriteSync(handle, offset, sizes[i], data[i]); + if (ES_CHECK_ERROR(error)) return error; + offset += sizes[i]; + } + + error = EsFileControl(handle, ES_FILE_CONTROL_NOTIFY_MONITORS | ES_FILE_CONTROL_FLUSH); + return error; +} + +void *EsFileReadAllFromHandle(EsHandle handle, size_t *fileSize, EsError *error) { + if (error) *error = ES_SUCCESS; + EsFileOffset size = EsFileGetSize(handle); + if (fileSize) *fileSize = size; + +#ifdef KERNEL + void *buffer = EsHeapAllocate(size + 1, false, K_PAGED); +#else + void *buffer = EsHeapAllocate(size + 1, false); +#endif + + if (!buffer) { + if (error) *error = ES_ERROR_INSUFFICIENT_RESOURCES; + return nullptr; + } + + ((char *) buffer)[size] = 0; + + uintptr_t result = EsFileReadSync(handle, 0, size, buffer); + + if (size != result) { +#ifdef KERNEL + EsHeapFree(buffer, size + 1, K_PAGED); +#else + EsHeapFree(buffer); +#endif + buffer = nullptr; + if (error) *error = (EsError) result; + } + + return buffer; +} + +void *EsFileReadAll(const char *filePath, ptrdiff_t filePathLength, size_t *fileSize, EsError *error) { + if (error) *error = ES_SUCCESS; + EsFileInformation information = EsFileOpen((char *) filePath, filePathLength, ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND); + + if (ES_SUCCESS != information.error) { + if (error) *error = information.error; + return nullptr; + } + + void *buffer = EsFileReadAllFromHandle(information.handle, fileSize); + EsHandleClose(information.handle); + return buffer; +} + +EsHandle EsMemoryOpen(size_t size, const char *name, ptrdiff_t nameLength, unsigned flags) { + if (nameLength == -1) nameLength = EsCStringLength(name); + return EsSyscall(ES_SYSCALL_MEMORY_OPEN, size, (uintptr_t) name, nameLength, flags); +} + +EsHandle EsMemoryShare(EsHandle sharedMemoryRegion, EsHandle targetProcess, bool readOnly) { + return EsSyscall(ES_SYSCALL_MEMORY_SHARE, sharedMemoryRegion, targetProcess, readOnly, 0); +} + +void *EsObjectMap(EsHandle sharedMemoryRegion, uintptr_t offset, size_t size, unsigned flags) { + intptr_t result = EsSyscall(ES_SYSCALL_MEMORY_MAP_OBJECT, sharedMemoryRegion, offset, size, flags); + + if (result >= 0) { + return (void *) result; + } else { + return nullptr; + } +} + +EsFileInformation EsFileOpen(const char *path, ptrdiff_t pathLength, uint32_t flags) { + if (pathLength == -1) { + pathLength = EsCStringLength(path); + } + + _EsNodeInformation node; + EsError result = NodeOpen(path, pathLength, flags, &node); + + if (result == ES_SUCCESS && node.type == ES_NODE_DIRECTORY) { + result = ES_ERROR_INCORRECT_NODE_TYPE; + EsHandleClose(node.handle); + } + + EsFileInformation information = {}; + + if (result == ES_SUCCESS) { + information.handle = node.handle; + information.size = node.fileSize; + } + + information.error = result; + return information; +} + +size_t EsFileReadSync(EsHandle handle, EsFileOffset offset, size_t size, void *buffer) { + intptr_t result = EsSyscall(ES_SYSCALL_FILE_READ_SYNC, handle, offset, size, (uintptr_t) buffer); + return result; +} + +size_t EsFileWriteSync(EsHandle handle, EsFileOffset offset, size_t size, const void *buffer) { + intptr_t result = EsSyscall(ES_SYSCALL_FILE_WRITE_SYNC, handle, offset, size, (uintptr_t) buffer); + return result; +} + +EsFileOffset EsFileGetSize(EsHandle handle) { + return EsSyscall(ES_SYSCALL_FILE_GET_SIZE, handle, 0, 0, 0); +} + +EsError EsFileResize(EsHandle handle, EsFileOffset newSize) { + return EsSyscall(ES_SYSCALL_FILE_RESIZE, handle, newSize, 0, 0); +} + +void *EsFileStoreReadAll(EsFileStore *file, size_t *fileSize) { + if (file->error != ES_SUCCESS) return nullptr; + + if (file->type == FILE_STORE_HANDLE) { + return EsFileReadAllFromHandle(file->handle, fileSize, &file->error); + } else if (file->type == FILE_STORE_PATH) { + return EsFileReadAll(file->path, file->pathBytes, fileSize, &file->error); + } else { + EsAssert(false); + return nullptr; + } +} + +bool EsFileStoreWriteAll(EsFileStore *file, const void *data, size_t dataBytes) { + if (file->error == ES_SUCCESS) { + if (file->type == FILE_STORE_HANDLE) { + file->error = EsFileWriteAllFromHandle(file->handle, data, dataBytes); + } else if (file->type == FILE_STORE_PATH) { + file->error = EsFileWriteAll(file->path, file->pathBytes, data, dataBytes); + } else { + EsAssert(false); + } + } + + return file->error == ES_SUCCESS; +} + +EsFileOffsetDifference EsFileStoreGetSize(EsFileStore *file) { + if (file->type == FILE_STORE_HANDLE) { + return EsFileGetSize(file->handle); + } else if (file->type == FILE_STORE_PATH) { + EsDirectoryChild information; + + if (EsPathQueryInformation(file->path, file->pathBytes, &information)) { + return file->pathBytes; + } else { + return -1; + } + } else { + EsAssert(false); + return 0; + } +} + +void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flags) { + if (file->type == FILE_STORE_HANDLE) { + EsFileOffsetDifference size = EsFileStoreGetSize(file); + if (size == -1) return nullptr; + *fileSize = size; + return EsObjectMap(file->handle, 0, size, flags); + } else if (file->type == FILE_STORE_PATH) { + return EsFileMap(file->path, file->pathBytes, fileSize, flags); + } else { + EsAssert(false); + return nullptr; + } +} + +uintptr_t EsWait(EsHandle *handles, size_t count, uintptr_t timeoutMs) { + return EsSyscall(ES_SYSCALL_WAIT, (uintptr_t) handles, count, timeoutMs, 0); +} + +void EsProcessPause(EsHandle process, bool resume) { + EsSyscall(ES_SYSCALL_PROCESS_PAUSE, process, resume, 0, 0); +} + +uint64_t EsThreadGetID(EsHandle thread) { + if (thread == ES_CURRENT_THREAD) { + return GetThreadLocalStorage()->id; + } else { + return EsSyscall(ES_SYSCALL_THREAD_GET_ID, thread, 0, 0, 0); + } +} + +uintptr_t EsProcessGetID(EsHandle process) { + return EsSyscall(ES_SYSCALL_THREAD_GET_ID, process, 0, 0, 0); +} + +ptrdiff_t EsDirectoryEnumerateChildrenFromHandle(EsHandle directory, EsDirectoryChild *buffer, size_t size) { + if (!size) return 0; + return EsSyscall(ES_SYSCALL_DIRECTORY_ENUMERATE, directory, (uintptr_t) buffer, size, 0); +} + +#ifndef KERNEL +ptrdiff_t EsDirectoryEnumerateChildren(const char *path, ptrdiff_t pathBytes, EsDirectoryChild **buffer) { + *buffer = nullptr; + if (pathBytes == -1) pathBytes = EsCStringLength(path); + + _EsNodeInformation node; + EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_NODE_DIRECTORY, &node); + if (error != ES_SUCCESS) return error; + + if (node.directoryChildren == ES_DIRECTORY_CHILDREN_UNKNOWN) { + node.directoryChildren = 4194304 / sizeof(EsDirectoryChild); // TODO Grow the buffer until all entries fit. + } + + *buffer = (EsDirectoryChild *) EsHeapAllocate(sizeof(EsDirectoryChild) * node.directoryChildren, true); + + ptrdiff_t result = EsDirectoryEnumerateChildrenFromHandle(node.handle, *buffer, node.directoryChildren); + if (ES_CHECK_ERROR(result)) { EsHeapFree(*buffer); *buffer = nullptr; } + + EsHandleClose(node.handle); + return result; +} +#endif + +void EsBatch(EsBatchCall *calls, size_t count) { +#if 0 + for (uintptr_t i = 0; i < count; i++) { + EsBatchCall *call = calls + i; + // ... modify system call for version changes ... + } +#endif + + EsSyscall(ES_SYSCALL_BATCH, (uintptr_t) calls, count, 0, 0); +} + +EsError EsPathDelete(const char *path, ptrdiff_t pathBytes) { + _EsNodeInformation node; + if (pathBytes == -1) pathBytes = EsCStringLength(path); + EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE_EXCLUSIVE, &node); + if (ES_CHECK_ERROR(error)) return error; + error = EsSyscall(ES_SYSCALL_NODE_DELETE, node.handle, 0, 0, 0); + EsHandleClose(node.handle); + return error; +} + +void *EsFileMap(const char *path, ptrdiff_t pathBytes, size_t *fileSize, uint32_t flags) { + EsFileInformation information = EsFileOpen(path, pathBytes, + ES_NODE_FAIL_IF_NOT_FOUND | ((flags & ES_MAP_OBJECT_READ_WRITE) ? ES_FILE_WRITE_EXCLUSIVE : ES_FILE_READ)); + + if (ES_CHECK_ERROR(information.error)) { + return nullptr; + } + + void *base = EsObjectMap(information.handle, 0, information.size, flags); + EsHandleClose(information.handle); + if (fileSize) *fileSize = information.size; + return base; +} + +EsError EsPathMove(const char *oldPath, ptrdiff_t oldPathBytes, const char *newPath, ptrdiff_t newPathBytes) { + if (oldPathBytes == -1) oldPathBytes = EsCStringLength(oldPath); + if (newPathBytes == -1) newPathBytes = EsCStringLength(newPath); + + _EsNodeInformation node = {}; + _EsNodeInformation directory = {}; + EsError error; + + error = NodeOpen(oldPath, oldPathBytes, ES_NODE_FAIL_IF_NOT_FOUND, &node); + if (error != ES_SUCCESS) return error; + + uintptr_t s = 0; + for (intptr_t i = 0; i < newPathBytes; i++) if (newPath[i] == '/') s = i + 1; + error = NodeOpen(newPath, s, ES_NODE_DIRECTORY | ES_NODE_FAIL_IF_NOT_FOUND, &directory); + if (error != ES_SUCCESS) { EsHandleClose(node.handle); return error; } + + error = EsSyscall(ES_SYSCALL_NODE_MOVE, node.handle, directory.handle, (uintptr_t) newPath + s, newPathBytes - s); + EsHandleClose(node.handle); + EsHandleClose(directory.handle); + return error; +} + +bool EsPathExists(const char *path, ptrdiff_t pathBytes, EsNodeType *type) { + if (pathBytes == -1) pathBytes = EsCStringLength(path); + _EsNodeInformation node = {}; + EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND, &node); + if (error != ES_SUCCESS) return false; + EsHandleClose(node.handle); + if (type) *type = node.type; + return true; +} + +bool EsPathQueryInformation(const char *path, ptrdiff_t pathBytes, EsDirectoryChild *information) { + if (pathBytes == -1) pathBytes = EsCStringLength(path); + _EsNodeInformation node = {}; + EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND, &node); + if (error != ES_SUCCESS) return false; + EsHandleClose(node.handle); + information->type = node.type; + information->fileSize = node.fileSize; + information->directoryChildren = node.directoryChildren; + return true; +} + +EsError EsPathCreate(const char *path, ptrdiff_t pathBytes, EsNodeType type, bool createLeadingDirectories) { + if (pathBytes == -1) pathBytes = EsCStringLength(path); + _EsNodeInformation node = {}; + EsError error = NodeOpen(path, pathBytes, + ES_NODE_FAIL_IF_FOUND | type | (createLeadingDirectories ? ES_NODE_CREATE_DIRECTORIES : 0), + &node); + if (error != ES_SUCCESS) return error; + EsHandleClose(node.handle); + return ES_SUCCESS; +} + +EsError EsFileControl(EsHandle file, uint32_t flags) { + return EsSyscall(ES_SYSCALL_FILE_CONTROL, file, flags, 0, 0); +} + +void EsConstantBufferRead(EsHandle buffer, void *output) { + EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_READ, buffer, (uintptr_t) output, 0, 0); +} + +void EsProcessGetState(EsHandle process, EsProcessState *state) { + EsSyscall(ES_SYSCALL_PROCESS_GET_STATE, process, (uintptr_t) state, 0, 0); +} + +void EsSchedulerYield() { + EsSyscall(ES_SYSCALL_YIELD_SCHEDULER, 0, 0, 0, 0); +} + +void EsSleep(uint64_t milliseconds) { + EsSyscall(ES_SYSCALL_SLEEP, milliseconds >> 32, milliseconds & 0xFFFFFFFF, 0, 0); +} + +EsHandle EsTakeSystemSnapshot(int type, size_t *bufferSize) { + return EsSyscall(ES_SYSCALL_SYSTEM_TAKE_SNAPSHOT, type, (uintptr_t) bufferSize, 0, 0); +} + +EsHandle EsProcessOpen(uint64_t pid) { + // TODO This won't work correctly if arguments to system call are 32-bit. + return EsSyscall(ES_SYSCALL_PROCESS_OPEN, pid, 0, 0, 0); +} + +#ifndef KERNEL +EsHandle EsConstantBufferShare(EsHandle constantBuffer, EsHandle targetProcess) { + return EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_SHARE, constantBuffer, targetProcess, 0, 0); +} + +EsHandle EsConstantBufferCreate(const void *data, size_t dataBytes, EsHandle targetProcess) { + return EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_CREATE, (uintptr_t) data, targetProcess, dataBytes, 0); +} + +size_t EsConstantBufferGetSize(EsHandle buffer) { + return EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_READ, buffer, 0, 0, 0); +} +#endif + +EsError EsAddressResolve(const char *domain, ptrdiff_t domainBytes, uint32_t flags, EsAddress *address) { + return EsSyscall(ES_SYSCALL_DOMAIN_NAME_RESOLVE, (uintptr_t) domain, domainBytes, (uintptr_t) address, flags); +} + +EsError EsConnectionOpen(EsConnection *connection, uint32_t flags) { + connection->error = ES_SUCCESS; + connection->open = false; + + EsError error = EsSyscall(ES_SYSCALL_CONNECTION_OPEN, (uintptr_t) connection, flags, 0, 0); + + if (error == ES_SUCCESS && (flags & ES_CONNECTION_OPEN_WAIT)) { + while (!connection->open && connection->error == ES_SUCCESS) { + EsConnectionPoll(connection); + } + + return connection->error; + } else { + return error; + } +} + +void EsConnectionPoll(EsConnection *connection) { + EsSyscall(ES_SYSCALL_CONNECTION_POLL, (uintptr_t) connection, 0, 0, connection->handle); +} + +void EsConnectionNotify(EsConnection *connection) { + EsSyscall(ES_SYSCALL_CONNECTION_NOTIFY, 0, connection->sendWritePointer, connection->receiveReadPointer, connection->handle); +} + +void EsConnectionClose(EsConnection *connection) { + EsObjectUnmap(connection->sendBuffer); + EsHandleClose(connection->handle); +} + +EsError EsConnectionWriteSync(EsConnection *connection, const void *_data, size_t dataBytes) { + const uint8_t *data = (const uint8_t *) _data; + + while (dataBytes) { + EsConnectionPoll(connection); + + if (connection->error != ES_SUCCESS) { + return connection->error; + } + + size_t space = connection->sendWritePointer >= connection->sendReadPointer + ? connection->sendBufferBytes - connection->sendWritePointer + : connection->sendReadPointer - connection->sendWritePointer - 1; + + if (!space) { + continue; + } + + size_t bytesToWrite = space > dataBytes ? dataBytes : space; + EsMemoryCopy(connection->sendBuffer + connection->sendWritePointer, data, bytesToWrite); + data += bytesToWrite, dataBytes -= bytesToWrite; + connection->sendWritePointer = (connection->sendWritePointer + bytesToWrite) % connection->sendBufferBytes; + EsConnectionNotify(connection); + } + + return ES_SUCCESS; +} + +EsError EsConnectionRead(EsConnection *connection, void *_buffer, size_t bufferBytes, size_t *bytesRead) { + uint8_t *buffer = (uint8_t *) _buffer; + *bytesRead = 0; + + EsConnectionPoll(connection); + + if (connection->error != ES_SUCCESS) { + return connection->error; + } + + while (bufferBytes && connection->receiveReadPointer != connection->receiveWritePointer) { + size_t bytesAvailable = connection->receiveReadPointer > connection->receiveWritePointer + ? connection->receiveBufferBytes - connection->receiveReadPointer + : connection->receiveWritePointer - connection->receiveReadPointer; + size_t bytesToRead = bufferBytes > bytesAvailable ? bytesAvailable : bufferBytes; + EsMemoryCopy(buffer, connection->receiveBuffer + connection->receiveReadPointer, bytesToRead); + connection->receiveReadPointer = (connection->receiveReadPointer + bytesToRead) % connection->receiveBufferBytes; + buffer += bytesToRead, bufferBytes -= bytesToRead; + *bytesRead += bytesToRead; + EsConnectionNotify(connection); + } + + return ES_SUCCESS; +} + +void EsEventForward(EsHandle event, EsHandle eventSink, EsGeneric data) { + EsSyscall(ES_SYSCALL_EVENT_FORWARD, event, eventSink, data.u, 0); +} + +EsHandle EsEventSinkCreate(bool ignoreDuplicates) { + return EsSyscall(ES_SYSCALL_EVENT_SINK_CREATE, ignoreDuplicates, 0, 0, 0); +} + +EsError EsEventSinkPop(EsHandle eventSink, EsGeneric *data) { + EsGeneric unused; if (!data) data = &unused; + return EsSyscall(ES_SYSCALL_EVENT_SINK_POP, eventSink, (uintptr_t) data, 0, 0); +} + +EsError EsEventSinkPush(EsHandle eventSink, EsGeneric data) { + return EsSyscall(ES_SYSCALL_EVENT_SINK_PUSH, eventSink, data.u, 0, 0); +} + +size_t EsGameControllerStatePoll(EsGameControllerState *buffer) { + return EsSyscall(ES_SYSCALL_GAME_CONTROLLER_STATE_POLL, (uintptr_t) buffer, 0, 0, 0); +} + +#define CLIPBOARD_FORMAT_TEXT (1) + +EsError EsClipboardAddText(EsClipboard clipboard, const char *text, ptrdiff_t textBytes) { + EsMessageMutexCheck(); + + if (textBytes == -1) { + textBytes = EsCStringLength(text); + } + + return EsSyscall(ES_SYSCALL_CLIPBOARD_ADD, clipboard, CLIPBOARD_FORMAT_TEXT, (uintptr_t) text, textBytes); +} + +bool EsClipboardHasText(EsClipboard clipboard) { + return EsSyscall(ES_SYSCALL_CLIPBOARD_HAS, clipboard, CLIPBOARD_FORMAT_TEXT, 0, 0); +} + +char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes) { + *bytes = 0; + EsHandle handle = EsSyscall(ES_SYSCALL_CLIPBOARD_READ, clipboard, CLIPBOARD_FORMAT_TEXT, 0, (uintptr_t) bytes); + if (handle == ES_INVALID_HANDLE) return nullptr; + char *buffer = (char *) EsHeapAllocate(*bytes, false); + if (!buffer) return nullptr; + EsConstantBufferRead(handle, buffer); + EsHandleClose(handle); + return buffer; +} diff --git a/desktop/text.cpp b/desktop/text.cpp new file mode 100644 index 0000000..51345ab --- /dev/null +++ b/desktop/text.cpp @@ -0,0 +1,4984 @@ +#if defined(TEXT_RENDERER) + +// TODO Fallback VGA font. +// TODO If the font size is sufficiently large disable subpixel anti-aliasing. +// TODO Variable font support. + +#ifdef USE_HARFBUZZ +#include +#include +#define HB_SHAPE(plan, features, featureCount) hb_shape(plan->font.hb, plan->buffer, features, featureCount) +#endif + +#ifdef USE_FREETYPE +#include +#include FT_FREETYPE_H +#include +#endif + +#define FREETYPE_UNIT_SCALE (64) + +#define FALLBACK_SCRIPT_LANGUAGE ("en") +#define FALLBACK_SCRIPT (0x4C61746E) // "Latn" + +struct Font { +#ifdef USE_FREETYPE + FT_Face ft; +#else + float scale; + const BasicFontHeader *header; +#endif + +#ifdef USE_HARFBUZZ + hb_font_t *hb; +#endif +}; + +struct GlyphCacheKey { + uint32_t glyphIndex; + uint16_t size; + uint16_t fractionalPosition; + Font font; +}; + +struct GlyphCacheEntry { + uint8_t *data; + size_t dataBytes; + int width, height, xoff, yoff; + int type; + + LinkedItem itemLRU; + GlyphCacheKey key; +}; + +struct FontSubstitutionKey { + EsFontFamily family; + uint16_t _unused0; + uint32_t script; +}; + +struct FontDatabaseEntry : EsFontInformation { + EsFileStore *files[18]; + char *scripts; + size_t scriptsBytes; +}; + +struct { + // Database. + HashStore substitutions; + Array database; + uintptr_t sans, serif, monospaced, fallback; + char *sansName, *serifName, *monospacedName, *fallbackName; + + // Rendering. +#ifdef USE_FREETYPE + FT_Library freetypeLibrary; +#endif + + // Caching. + HashStore loaded; // TODO How many fonts to keep loaded? Reference counting? +#define GLYPH_CACHE_MAX_SIZE (4194304) + HashStore glyphCache; + LinkedList glyphCacheLRU; + size_t glyphCacheBytes; +} fontManagement; + +struct { + EsBuffer pack; + const uint8_t *standardPack; + size_t standardPackSize; + char *buffer; + size_t bufferPosition, bufferAllocated; +} iconManagement; + +Font FontGet(EsFont key); + +// --------------------------------- Glyph cache. + +void RegisterGlyphCacheEntry(GlyphCacheKey key, GlyphCacheEntry *entry) { + entry->itemLRU.thisItem = entry; + entry->key = key; + *fontManagement.glyphCache.Put(&key) = entry; + fontManagement.glyphCacheLRU.InsertStart(&entry->itemLRU); + + fontManagement.glyphCacheBytes += entry->dataBytes; + + while (fontManagement.glyphCacheBytes > GLYPH_CACHE_MAX_SIZE) { + GlyphCacheEntry *leastRecentlyUsedGlyph = fontManagement.glyphCacheLRU.lastItem->thisItem; + fontManagement.glyphCacheLRU.Remove(&leastRecentlyUsedGlyph->itemLRU); + fontManagement.glyphCache.Delete(&leastRecentlyUsedGlyph->key); + EsAssert(fontManagement.glyphCacheBytes >= entry->dataBytes); // Negative glyph cache bytes. + fontManagement.glyphCacheBytes -= entry->dataBytes; + EsHeapFree(leastRecentlyUsedGlyph->data); + EsHeapFree(leastRecentlyUsedGlyph); + } +} + +GlyphCacheEntry *LookupGlyphCacheEntry(GlyphCacheKey key) { + GlyphCacheEntry *entry = fontManagement.glyphCache.Get1(&key); + + if (!entry) { + return (GlyphCacheEntry *) EsHeapAllocate(sizeof(GlyphCacheEntry), true); + } else { + fontManagement.glyphCacheLRU.Remove(&entry->itemLRU); + fontManagement.glyphCacheLRU.InsertStart(&entry->itemLRU); + return entry; + } +} + +// --------------------------------- Font renderer. + +bool FontLoad(Font *font, const void *data, size_t dataBytes) { +#ifdef USE_FREETYPE + if (!fontManagement.freetypeLibrary) { + FT_Init_FreeType(&fontManagement.freetypeLibrary); + } + + if (FT_New_Memory_Face(fontManagement.freetypeLibrary, (uint8_t *) data, dataBytes, 0, &font->ft)) { + return false; + } +#else + if (dataBytes < sizeof(BasicFontHeader)) { + return false; + } + + const BasicFontHeader *header = (const BasicFontHeader *) data; + + if (header->signature != BASIC_FONT_SIGNATURE + || (dataBytes < sizeof(BasicFontHeader) + + header->glyphCount * sizeof(BasicFontGlyph) + + header->kerningEntries * sizeof(BasicFontKerningEntry))) { + return false; + } + + const BasicFontGlyph *glyphs = (const BasicFontGlyph *) (header + 1); + + for (uintptr_t i = 0; i < header->glyphCount; i++) { + if (dataBytes <= glyphs[i].offsetToPoints + || dataBytes < glyphs[i].offsetToPoints + glyphs[i].pointCount * 24) { + return false; + } + } + + font->header = header; +#endif + +#ifdef USE_HARFBUZZ + font->hb = hb_ft_font_create(font->ft, nullptr); +#endif + + return true; +} + +void FontSetSize(Font *font, uint32_t size) { +#ifdef USE_FREETYPE + FT_Set_Char_Size(font->ft, 0, size * FREETYPE_UNIT_SCALE, 100, 100); +#else + font->scale = 1.75f * (float) size / (font->header->ascender - font->header->descender); +#endif + +#ifdef USE_HARFBUZZ + hb_ft_font_changed(font->hb); +#endif +} + +uint32_t FontCodepointToGlyphIndex(Font *font, uint32_t codepoint) { +#ifdef USE_FREETYPE + return FT_Get_Char_Index(font->ft, codepoint); +#else + const BasicFontGlyph *glyphs = (const BasicFontGlyph *) (font->header + 1); + + for (uintptr_t i = 0; i < font->header->glyphCount; i++) { + if (glyphs[i].codepoint == codepoint) { + return i; + } + } + + return 0; +#endif +} + +void FontGetGlyphMetrics(Font *font, uint32_t glyphIndex, uint32_t *xAdvance, uint32_t *yAdvance, uint32_t *xOffset, uint32_t *yOffset) { +#ifdef USE_FREETYPE + FT_Load_Glyph(font->ft, glyphIndex, 0); + *xAdvance = font->ft->glyph->advance.x; + *yAdvance = font->ft->glyph->advance.y; + // *xOffset = font->ft->glyph->bitmap_left; + // *yOffset = font->ft->glyph->bitmap_top; + *xOffset = *yOffset = 0; +#else + const BasicFontGlyph *glyph = ((const BasicFontGlyph *) (font->header + 1)) + glyphIndex; + *xOffset = *yOffset = *yAdvance = 0; + *xAdvance = glyph->xAdvance * font->scale * FREETYPE_UNIT_SCALE; +#endif +} + +int32_t FontGetKerning(Font *font, uint32_t previous, uint32_t next) { +#ifdef USE_FREETYPE + FT_Vector kerning = {}; + if (previous) FT_Get_Kerning(font->ft, previous, next, 0, &kerning); + return kerning.x; +#else + const BasicFontKerningEntry *entries = (const BasicFontKerningEntry *) (((const BasicFontGlyph *) (font->header + 1)) + font->header->glyphCount); + + uintptr_t currentIndex = 0; + bool startFound = false; + ES_MACRO_SEARCH(font->header->kerningEntries, result = previous - entries[index].leftGlyphIndex;, currentIndex, startFound); + int32_t xAdvance = 0; + + if (startFound) { + if (entries[currentIndex].rightGlyphIndex == next) { + xAdvance = entries[currentIndex].xAdvance; + } else if (entries[currentIndex].rightGlyphIndex < next) { + while (currentIndex != font->header->kerningEntries && entries[currentIndex].leftGlyphIndex == previous) { + if (entries[currentIndex].rightGlyphIndex == next) { + xAdvance = entries[currentIndex].xAdvance; + break; + } else { + currentIndex++; + } + } + } else { + while (entries[currentIndex].leftGlyphIndex == previous) { + if (entries[currentIndex].rightGlyphIndex == next) { + xAdvance = entries[currentIndex].xAdvance; + break; + } else if (!currentIndex) { + break; + } else { + currentIndex--; + } + } + } + } + + return xAdvance * FREETYPE_UNIT_SCALE * font->scale; +#endif +} + +int32_t FontGetAscent(Font *font) { +#ifdef USE_FREETYPE + return font->ft->size->metrics.ascender; +#else + return font->header->ascender * font->scale * FREETYPE_UNIT_SCALE; +#endif +} + +int32_t FontGetDescent(Font *font) { +#ifdef USE_FREETYPE + return font->ft->size->metrics.descender; +#else + return font->header->descender * font->scale * FREETYPE_UNIT_SCALE; +#endif +} + +int32_t FontGetEmWidth(Font *font) { +#ifdef USE_FREETYPE + return font->ft->size->metrics.x_ppem; +#else + return font->header->ascender * font->scale; // TODO. +#endif +} + +int EsTextGetLineHeight(const EsTextStyle *textStyle) { + EsMessageMutexCheck(); + Font font = FontGet(textStyle->font); + FontSetSize(&font, textStyle->size); + return (FontGetAscent(&font) - FontGetDescent(&font) + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE; +} + +bool FontRenderGlyph(bool mono, GlyphCacheKey key, GlyphCacheEntry *entry) { +#ifdef USE_FREETYPE + FT_Load_Glyph(key.font.ft, key.glyphIndex, FT_LOAD_DEFAULT); + FT_Outline_Translate(&key.font.ft->glyph->outline, key.fractionalPosition, 0); + + int width; + int height; + int xoff; + int yoff; + uint8_t *output; + + if (mono) { + FT_Render_Glyph(key.font.ft->glyph, FT_RENDER_MODE_MONO); + + FT_Bitmap *bitmap = &key.font.ft->glyph->bitmap; + width = bitmap->width; + height = bitmap->rows; + xoff = key.font.ft->glyph->bitmap_left; + yoff = -key.font.ft->glyph->bitmap_top; + + entry->dataBytes = 1 + (width * height + 7) / 8; + output = (uint8_t *) EsHeapAllocate(entry->dataBytes, true); + + if (!output) { + return false; + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + uintptr_t s = bitmap->pitch * 8 * y + x; + uintptr_t d = width * y + x; + + if (bitmap->buffer[s / 8] & (1 << (7 - (s & 7)))) { + output[d / 8] |= (1 << (d & 7)); + } + } + } + } else { + FT_Render_Glyph(key.font.ft->glyph, FT_RENDER_MODE_LCD); + + FT_Bitmap *bitmap = &key.font.ft->glyph->bitmap; + width = bitmap->width / 3; + height = bitmap->rows; + xoff = key.font.ft->glyph->bitmap_left; + yoff = -key.font.ft->glyph->bitmap_top; + + entry->dataBytes = 1 /*stupid hack for whitespace*/ + width * height * 4; + output = (uint8_t *) EsHeapAllocate(entry->dataBytes, false); + + if (!output) { + return false; + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int32_t r = (int32_t) ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 0]; + int32_t g = (int32_t) ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 1]; + int32_t b = (int32_t) ((uint8_t *) bitmap->buffer)[x * 3 + y * bitmap->pitch + 2]; + + // Reduce how noticible the colour fringes are. + // TODO Make this adjustable? + int32_t average = (r + g + b) / 3; + r -= (r - average) / 3; + g -= (g - average) / 3; + b -= (b - average) / 3; + + output[(x + y * width) * 4 + 0] = (uint8_t) r; + output[(x + y * width) * 4 + 1] = (uint8_t) g; + output[(x + y * width) * 4 + 2] = (uint8_t) b; + output[(x + y * width) * 4 + 3] = 0xFF; + + // EsPrint("\tPixel %d, %d: red %X, green %X, blue %X\n", x, y, r, g, b); + } + } + } + + if (output) { + entry->data = output; + entry->width = width; + entry->height = height; + entry->xoff = xoff; + entry->yoff = yoff; + return true; + } + + return false; +#else + EsAssert(!mono); + + const BasicFontGlyph *glyph = ((const BasicFontGlyph *) (key.font.header + 1)) + key.glyphIndex; + uint32_t width = glyph->width * key.font.scale + 2; + uint32_t height = glyph->height * key.font.scale + 2; + + if (width > 4096 || height > 4096) { + return false; + } + + RastSurface surface = {}; + RastPath path = {}; + RastPaint paint = {}; + paint.type = RAST_PAINT_SOLID; + paint.solid.alpha = 1.0f; + + float vertexScale = key.font.scale * (key.font.header->ascender - key.font.header->descender) * 0.01f; + + entry->data = (uint8_t *) EsHeapAllocate(width * height * 4, true); + + if (!entry->data) { + return false; + } + + if (glyph->pointCount) { + float *points = (float *) ((const uint8_t *) key.font.header + glyph->offsetToPoints); + + for (uintptr_t i = 0, j = 0; i < glyph->pointCount * 3; i += 3) { + if ((int) i == glyph->pointCount * 3 - 3 || points[i * 2 + 2] == -1e6) { + RastPathAppendBezier(&path, (RastVertex *) points + j, i - j + 1, { vertexScale, vertexScale }); + RastPathCloseSegment(&path); + j = i + 3; + } + } + + RastPathTranslate(&path, (float) key.fractionalPosition / FREETYPE_UNIT_SCALE - glyph->xOffset * key.font.scale, -glyph->yOffset * key.font.scale); + RastShape shape = RastShapeCreateSolid(&path); + + if (RastSurfaceInitialise(&surface, width, height, false)) { + RastSurfaceFill(surface, shape, paint, false); + RastPathDestroy(&path); + + uint32_t *in = surface.buffer; + uint8_t *out = entry->data; + + for (uintptr_t i = 0; i < height; i++) { + for (uintptr_t j = 0; j < width; j++) { + int32_t a = in[(height - i - 1) * width + j] >> 24; + *out++ = (uint8_t) a; + *out++ = (uint8_t) a; + *out++ = (uint8_t) a; + *out++ = 0xFF; + } + } + } + + RastSurfaceDestroy(&surface); + } + + entry->width = width; + entry->height = height; + entry->xoff = glyph->xOffset * key.font.scale + 0.25f; + entry->yoff = -glyph->yOffset * key.font.scale - height + 0.25f; + + return true; +#endif +} + +// --------------------------------- Font management. + +void FontInitialise() { + if (fontManagement.database.Length()) { + return; + } + + fontManagement.sansName = EsSystemConfigurationReadString(EsLiteral("ui"), EsLiteral("font_sans")); + fontManagement.serifName = EsSystemConfigurationReadString(EsLiteral("ui"), EsLiteral("font_serif")); + fontManagement.monospacedName = EsSystemConfigurationReadString(EsLiteral("ui"), EsLiteral("font_mono")); + fontManagement.fallbackName = EsSystemConfigurationReadString(EsLiteral("ui"), EsLiteral("font_fallback")); + + FontDatabaseEntry nullFont = {}; + fontManagement.database.Add(nullFont); + + for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) { + EsSystemConfigurationGroup *g = &api.systemConfigurationGroups[i]; + + if (0 == EsStringCompareRaw(g->sectionClass, g->sectionClassBytes, EsLiteral("font"))) { + if (0 == EsStringCompareRaw(g->section, g->sectionBytes, EsLiteral(fontManagement.sansName))) { + fontManagement.sans = fontManagement.database.Length(); + } + + if (0 == EsStringCompareRaw(g->section, g->sectionBytes, EsLiteral(fontManagement.serifName))) { + fontManagement.serif = fontManagement.database.Length(); + } + + if (0 == EsStringCompareRaw(g->section, g->sectionBytes, EsLiteral(fontManagement.monospacedName))) { + fontManagement.monospaced = fontManagement.database.Length(); + } + + if (0 == EsStringCompareRaw(g->section, g->sectionBytes, EsLiteral(fontManagement.fallbackName))) { + fontManagement.fallback = fontManagement.database.Length(); + } + + FontDatabaseEntry entry = {}; + + entry.nameBytes = MinimumInteger(g->sectionBytes, sizeof(entry.name)); + EsMemoryCopy(entry.name, g->section, entry.nameBytes); + entry.id = fontManagement.database.Length(); + + for (uintptr_t i = 0; i < g->itemCount; i++) { + EsSystemConfigurationItem *item = g->items + i; + + if (0 == EsStringCompareRaw(item->key, item->keyBytes, EsLiteral("category"))) { + entry.categoryBytes = MinimumInteger(item->valueBytes, sizeof(entry.category)); + EsMemoryCopy(entry.category, item->value, entry.categoryBytes); + } else if (0 == EsStringCompareRaw(item->key, item->keyBytes, EsLiteral("scripts"))) { + entry.scripts = item->value; + entry.scriptsBytes = item->valueBytes; + } else if ((item->keyBytes == 2 && item->key[0] == '.' && EsCRTisdigit(item->key[1])) + || (item->keyBytes == 3 && item->key[0] == '.' && EsCRTisdigit(item->key[1]) && item->key[2] == 'i') ) { + int weight = item->key[1] - '0'; + bool italic = item->keyBytes == 3; + + if (italic) { + entry.availableWeightsItalic |= 1 << weight; + } else { + entry.availableWeightsNormal |= 1 << weight; + } + + size_t fileIndex = weight - 1 + italic * 9; + entry.files[fileIndex] = FileStoreCreateFromPath(item->value, item->valueBytes); + } + } + + fontManagement.database.Add(entry); + } + } +} + +EsFontFamily FontGetStandardFamily(EsFontFamily family) { + FontInitialise(); + + if (family == 0 || family == ES_FONT_SANS) { + return fontManagement.sans ?: fontManagement.fallback; + } else if (family == ES_FONT_SERIF) { + return fontManagement.serif ?: fontManagement.fallback; + } else if (family == ES_FONT_MONOSPACED) { + return fontManagement.monospaced ?: fontManagement.fallback; + } else { + return family; + } +} + +bool EsFontDatabaseLookupByID(EsFontFamily id, EsFontInformation *information) { + FontInitialise(); + EsMemoryZero(information, sizeof(EsFontInformation)); + + id = FontGetStandardFamily(id); + + if (id >= fontManagement.database.Length()) { + return false; + } + + EsMemoryCopy(information, &fontManagement.database[id], sizeof(EsFontInformation)); + + return true; +} + +EsFontFamily EsFontDatabaseInsertFile(const EsFontInformation *information, EsFileStore *store) { + FontInitialise(); + + EsAssert(store->handles); + store->handles++; + + FontDatabaseEntry *entry = nullptr; + + if (information->nameBytes) { + for (uintptr_t i = 1; i < fontManagement.database.Length(); i++) { + FontDatabaseEntry *entry = &fontManagement.database[i]; + EsAssert(entry->id == i); + + if (0 == EsStringCompareRaw(information->name, information->nameBytes, + fontManagement.database[i].name, fontManagement.database[i].nameBytes)) { + if ((information->availableWeightsItalic & entry->availableWeightsItalic) + || (information->availableWeightsNormal & entry->availableWeightsNormal)) { + // The variant is already in the database. + return entry->id; + } + + goto addFileToFamily; + } + } + } + + { + // The family is not yet in the database; add it. + FontDatabaseEntry e = {}; + EsMemoryCopy(&e, information, sizeof(EsFontInformation)); + e.id = fontManagement.database.Length(); + fontManagement.database.Add(e); + entry = &fontManagement.database.Last(); + } + + addFileToFamily:; + + entry->availableWeightsNormal |= information->availableWeightsNormal; + entry->availableWeightsItalic |= information->availableWeightsItalic; + + for (uintptr_t i = 0; i < 18; i++) { + if ((i < 9 && (information->availableWeightsNormal & (1 << i))) + || (i >= 9 && (information->availableWeightsItalic & (1 << i)))) { + store->handles++; + entry->files[i] = store; + return entry->id; + } + } + + EsAssert(false); + return 0; +} + +void EsFontDatabaseEnumerate(EsFontEnumerationCallbackFunction callback, EsGeneric context) { + FontInitialise(); + + for (uintptr_t i = 1; i < fontManagement.database.Length(); i++) { + EsFontInformation information; + EsFontDatabaseLookupByID(i, &information); + callback(&information, context); + } +} + +bool FontSupportsScript(FontDatabaseEntry *entry, uint32_t _script, bool first) { + if (!entry->scriptsBytes) { + return first; + } + + char script[4]; + script[0] = (char) (_script >> 24); + script[1] = (char) (_script >> 16); + script[2] = (char) (_script >> 8); + script[3] = (char) (_script >> 0); + + for (uintptr_t i = 0; i <= entry->scriptsBytes - 4; i += 5) { + if (script[0] == entry->scripts[i + 0] + && script[1] == entry->scripts[i + 1] + && script[2] == entry->scripts[i + 2] + && script[3] == entry->scripts[i + 3]) { + return true; + } + } + + return false; +} + +EsFontFamily FontApplySubstitution(EsTextPlanProperties *properties, EsFontFamily family, uint32_t script) { + FontInitialise(); + + if (properties->flags & ES_TEXT_PLAN_NO_FONT_SUBSTITUTION) { + return family; + } + + FontSubstitutionKey key = {}; + key.family = FontGetStandardFamily(family); + key.script = script; + EsFontFamily result = fontManagement.substitutions.Get1(&key); + if (result) return result; + + EsAssert(key.family < fontManagement.database.Length()); + FontDatabaseEntry *entry = &fontManagement.database[key.family]; + + if (FontSupportsScript(entry, script, true)) { + *fontManagement.substitutions.Put(&key) = key.family; + return key.family; + } + + EsFontFamily firstMatch = (EsFontFamily) -1; + + for (uintptr_t i = 1; i < fontManagement.database.Length(); i++) { + if (&fontManagement.database[i] == entry) continue; + if (!FontSupportsScript(&fontManagement.database[i], script, false)) continue; + + if (firstMatch == (EsFontFamily) -1) { + firstMatch = i; + } + + if (0 == EsStringCompareRaw(fontManagement.database[i].category, fontManagement.database[i].categoryBytes, + entry->category, entry->categoryBytes)) { + *fontManagement.substitutions.Put(&key) = i; + return i; + } + } + + if (firstMatch != (EsFontFamily) -1) { + *fontManagement.substitutions.Put(&key) = firstMatch; + return firstMatch; + } else { + // No installed font supports the script. + *fontManagement.substitutions.Put(&key) = key.family; + return result; + } +} + +Font FontGet(EsFont key) { + FontInitialise(); + + if (key.weight == 0) { + key.weight = ES_FONT_REGULAR; + } + + key.family = FontGetStandardFamily(key.family); + + Font *_font = fontManagement.loaded.Get(&key); + if (_font) return *_font; + + EsFileStore *file = nullptr; + int matchDistance = 1000; + + EsAssert(key.family < fontManagement.database.Length()); + FontDatabaseEntry *entry = &fontManagement.database[key.family]; + + for (uintptr_t i = 0; i < 18; i++) { + if (entry->files[i]) { + int weight = (i % 9) + 1; + bool italic = i >= 9; + int distance = ((italic != key.italic) ? 10 : 0) + AbsoluteInteger(weight - key.weight); + + if (distance < matchDistance) { + matchDistance = distance; + file = entry->files[i]; + } + } + } + + if (!file) { + EsPrint("Could not load font (f%d/w%d/i%d).\n", key.family, key.weight, key.italic); + return {}; + } + + // EsPrint("Loading font from '%z' (f%d/w%d/i%d).\n", file, key.family, key.weight, key.italic); + + size_t size; + void *data = EsFileStoreMap(file, &size, ES_MAP_OBJECT_READ_ONLY); + + if (!data) { + EsPrint("Could not load font (f%d/w%d/i%d).\n", key.family, key.weight, key.italic); + return {}; + } + + Font font = {}; + + if (!FontLoad(&font, data, size)) { + EsPrint("Could not load font (f%d/w%d/i%d).\n", key.family, key.weight, key.italic); + return {}; + } + + *fontManagement.loaded.Put(&key) = font; + return font; +} + +// --------------------------------- Blitting rendered glyphs. + +inline static void DrawStringPixel(int oX, int oY, void *bitmap, size_t stride, uint32_t textColor, + uint32_t selectionColor, int32_t backgroundColor, uint32_t pixel, bool selected, bool fullAlpha) { + uint32_t *destination = (uint32_t *) ((uint8_t *) bitmap + (oX) * 4 + (oY) * stride); + uint8_t alpha = (textColor & 0xFF000000) >> 24; + + if (pixel == 0xFFFFFF && alpha == 0xFF) { + *destination = 0xFF000000 | textColor; + } else if (pixel && fullAlpha) { + uint32_t original; + + if (selected) { + original = selectionColor; + } else if (backgroundColor < 0) { + original = *destination; + } else { + original = backgroundColor; + } + + uint32_t ga = (((pixel & 0x0000FF00) >> 8) * alpha) >> 8; + uint32_t alphaD2 = (255 - ga) * ((original & 0xFF000000) >> 24); + uint32_t alphaOut = ga + (alphaD2 >> 8); + + if (alphaOut) { + uint32_t m2 = alphaD2 / alphaOut; + uint32_t m1 = (ga << 8) / alphaOut; + if (m2 == 0x100) m2--; + if (m1 == 0x100) m1--; + + uint32_t r2 = m2 * ((original & 0x000000FF) >> 0); + uint32_t g2 = m2 * ((original & 0x0000FF00) >> 8); + uint32_t b2 = m2 * ((original & 0x00FF0000) >> 16); + uint32_t r1 = m1 * ((textColor & 0x000000FF) >> 0); + uint32_t g1 = m1 * ((textColor & 0x0000FF00) >> 8); + uint32_t b1 = m1 * ((textColor & 0x00FF0000) >> 16); + + uint32_t result = + (0x00FF0000 & ((b1 + b2) << 8)) + | (0x0000FF00 & ((g1 + g2) << 0)) + | (0x000000FF & ((r1 + r2) >> 8)) + | (alphaOut << 24); + + *destination = result; + } + } else if (pixel) { + uint32_t original; + + if (selected) { + original = selectionColor; + } else if (backgroundColor < 0) { + original = *destination; + } else { + original = backgroundColor; + } + + uint32_t ra = (((pixel & 0x000000FF) >> 0) * alpha) >> 8; + uint32_t ga = (((pixel & 0x0000FF00) >> 8) * alpha) >> 8; + uint32_t ba = (((pixel & 0x00FF0000) >> 16) * alpha) >> 8; + uint32_t r2 = (255 - ra) * ((original & 0x000000FF) >> 0); + uint32_t g2 = (255 - ga) * ((original & 0x0000FF00) >> 8); + uint32_t b2 = (255 - ba) * ((original & 0x00FF0000) >> 16); + uint32_t r1 = ra * ((textColor & 0x000000FF) >> 0); + uint32_t g1 = ga * ((textColor & 0x0000FF00) >> 8); + uint32_t b1 = ba * ((textColor & 0x00FF0000) >> 16); + + uint32_t result = 0xFF000000 | (0x00FF0000 & ((b1 + b2) << 8)) + | (0x0000FF00 & ((g1 + g2) << 0)) + | (0x000000FF & ((r1 + r2) >> 8)); + + *destination = result; + } +} + +void DrawSingleCharacter(int width, int height, int xoff, int yoff, + EsPoint outputPosition, EsRectangle region, EsPaintTarget *target, + int blur, int type, bool selected, uint8_t *output, + uint32_t color, uint32_t selectionColor, int32_t backgroundColor, bool fullAlpha) { + // TODO Rewrite. + + if (type != CHARACTER_SUBPIXEL) { + blur = 0; + } + + uint8_t alpha = color >> 24; + + int xOut = outputPosition.x + xoff; + int yOut = outputPosition.y + yoff; + int xFrom = xOut, xTo = xOut + width; + int yFrom = yOut, yTo = yOut + height; + + if (blur) { + xFrom -= blur; + yFrom -= blur; + xTo += blur; + yTo += blur; + } + + if (xFrom < region.l) xFrom = region.l; else if (xFrom >= region.r) xFrom = region.r; + if (xFrom < 0) xFrom = 0; else if (xFrom >= (int) target->width) xFrom = target->width; + if (xTo < region.l) xTo = region.l; else if (xTo >= region.r) xTo = region.r; + if (xTo < 0) xTo = 0; else if (xTo >= (int) target->width) xTo = target->width; + + if (yFrom < region.t) yFrom = region.t; else if (yFrom >= region.b) yFrom = region.b; + if (yFrom < 0) yFrom = 0; else if (yFrom >= (int) target->height) yFrom = target->height; + if (yTo < region.t) yTo = region.t; else if (yTo >= region.b) yTo = region.b; + if (yTo < 0) yTo = 0; else if (yTo >= (int) target->height) yTo = target->height; + + float blurExponentDenominator = -1.0f / (2.0f * (blur / 3.0f) * (blur / 3.0f)); + + for (int oY = yFrom; oY < yTo; oY++) { + int y = oY - yOut; + + for (int oX = xFrom; oX < xTo; oX++) { + int x = oX - xOut; + + if (blur) { + float c = 0, d = 0; + + for (int i = y - blur; i <= y + blur; i++) { + for (int j = x - blur; j <= x + blur; j++) { + float weight = EsCRTexpf(blurExponentDenominator * ((i - y) * (i - y) + (j - x) * (j - x))); + d += weight; + + if (i >= 0 && j >= 0 && i < height && j < width) { + uint32_t pixel = *((uint32_t *) (output + (j * 4 + i * width * 4))); + c += (pixel & 0xFF00) * weight; + } + } + } + + uint32_t a = c / (d * 256.0f); + DrawStringPixel(oX, oY, target->bits, target->stride, color, selectionColor, backgroundColor, + a | (a << 8) | (a << 16), selected, fullAlpha); + } else if (type == CHARACTER_MONO) { + uintptr_t n = y * width + x; + + if (output[n / 8] & (1 << (n & 7))) { + uint32_t *destination = (uint32_t *) ((uint8_t *) target->bits + oX * 4 + oY * target->stride); + *destination = 0xFF000000 | color; + } + } else if (type == CHARACTER_IMAGE || type == CHARACTER_RECOLOR) { + uint32_t pixel = *((uint32_t *) (output + (x * 4 + y * width * 4))); + uint32_t *destination = (uint32_t *) ((uint8_t *) target->bits + (oX) * 4 + (oY) * target->stride); + + if (type == CHARACTER_RECOLOR) { + pixel = (pixel & 0xFF000000) | (color & 0x00FFFFFF); + } + + if ((pixel >> 24) == 0xFF && alpha == 0xFF) { + *destination = pixel; + } else if (pixel && fullAlpha) { + uint32_t original = *destination; + uint32_t alphaSource = ((pixel >> 24) * alpha) >> 8; + uint32_t alphaDestination = ((original & 0xFF000000) >> 24) * (255 - alphaSource); + uint32_t alphaOut = alphaSource + (alphaDestination >> 8); + + if (alphaOut) { + uint32_t m2 = alphaDestination / alphaOut; + uint32_t m1 = (alphaSource << 8) / alphaOut; + if (m2 == 0x100) m2--; + if (m1 == 0x100) m1--; + uint32_t r2 = m2 * ((original & 0x000000FF) >> 0); + uint32_t g2 = m2 * ((original & 0x0000FF00) >> 8); + uint32_t b2 = m2 * ((original & 0x00FF0000) >> 16); + uint32_t r1 = m1 * ((pixel & 0x000000FF) >> 0); + uint32_t g1 = m1 * ((pixel & 0x0000FF00) >> 8); + uint32_t b1 = m1 * ((pixel & 0x00FF0000) >> 16); + uint32_t result = (alphaOut << 24) | (0x00FF0000 & ((b1 + b2) << 8)) + | (0x0000FF00 & ((g1 + g2) << 0)) + | (0x000000FF & ((r1 + r2) >> 8)); + *destination = result; + } + } else if (pixel) { + uint32_t original = *destination; + uint32_t a = ((pixel >> 24) * alpha) >> 8; + uint32_t r2 = (255 - a) * ((original & 0x000000FF) >> 0); + uint32_t g2 = (255 - a) * ((original & 0x0000FF00) >> 8); + uint32_t b2 = (255 - a) * ((original & 0x00FF0000) >> 16); + uint32_t r1 = a * ((pixel & 0x000000FF) >> 0); + uint32_t g1 = a * ((pixel & 0x0000FF00) >> 8); + uint32_t b1 = a * ((pixel & 0x00FF0000) >> 16); + uint32_t result = 0xFF000000 + | (0x00FF0000 & ((b1 + b2) << 8)) + | (0x0000FF00 & ((g1 + g2) << 0)) + | (0x000000FF & ((r1 + r2) >> 8)); + *destination = result; + } + } else if (type == CHARACTER_SUBPIXEL) { + uint32_t pixel = *((uint32_t *) (output + (x * 4 + y * width * 4))); + DrawStringPixel(oX, oY, target->bits, target->stride, color, selectionColor, backgroundColor, pixel, selected, fullAlpha); + } + } + } +} + +// --------------------------------- Icons. + +#define ICON_PACK_PAINT_SOLID (1) +#define ICON_PACK_PAINT_LINEAR_GRADIENT (2) +#define ICON_PACK_PAINT_RADIAL_GRADIENT (3) + +struct IconPackGradientStop { + uint32_t color; + float offset; +}; + +struct IconPackGradient { + float transform[6]; + uint8_t repeatMode, stopCount; + float fx, fy; + IconPackGradientStop stops[1]; +}; + +struct IconPackPaint { + uint8_t type; + + union { + uint32_t color; + IconPackGradient *gradient; + }; +}; + +struct IconPackPath { + IconPackPath *next; + float *points; + int pointCount; + bool closed; +}; + +struct IconPackShape { + IconPackShape *next; + IconPackPath *paths; + IconPackPaint fill, stroke; + bool evenOddRule; + float opacity; + float strokeWidth, strokeDashOffset, strokeDashArray[8], miterLimit; + uint8_t strokeLineJoin, strokeLineCap, strokeDashCount; +}; + +struct IconPackImage { + IconPackShape *shapes; + float width, height; +}; + +void *IconBufferAllocate(size_t size) { + // Must allocate adjacent to the previous allocation. + void *memory = iconManagement.buffer + iconManagement.bufferPosition; + iconManagement.bufferPosition += size; + EsAssert(iconManagement.bufferAllocated > iconManagement.bufferPosition); // Icon required more space than is available in iconBuffer. + EsMemoryZero(memory, size); + return memory; +} + +void IconPackReadPaint(IconPackPaint *paint) { + paint->type = EsBufferReadByte(&iconManagement.pack); + + if (paint->type == ICON_PACK_PAINT_SOLID) { + paint->color = EsBufferReadInt(&iconManagement.pack); + } else if (paint->type == ICON_PACK_PAINT_LINEAR_GRADIENT || paint->type == ICON_PACK_PAINT_RADIAL_GRADIENT) { + paint->gradient = (IconPackGradient *) IconBufferAllocate(sizeof(IconPackGradient)); + for (int i = 0; i < 6; i++) paint->gradient->transform[i] = EsBufferReadFloat(&iconManagement.pack); + paint->gradient->repeatMode = EsBufferReadByte(&iconManagement.pack); + paint->gradient->fx = EsBufferReadFloat(&iconManagement.pack); + paint->gradient->fy = EsBufferReadFloat(&iconManagement.pack); + paint->gradient->stopCount = EsBufferReadInt(&iconManagement.pack); + IconBufferAllocate(8 * paint->gradient->stopCount); + + for (int i = 0; i < paint->gradient->stopCount; i++) { + paint->gradient->stops[i].color = EsBufferReadInt(&iconManagement.pack); + paint->gradient->stops[i].offset = EsBufferReadFloat(&iconManagement.pack); + } + } +} + +void IconPackReadPaint(IconPackPath **link) { + if (EsBufferReadByte(&iconManagement.pack) != 0x34) return; + next:; + + IconPackPath *path = *link = (IconPackPath *) IconBufferAllocate(sizeof(IconPackPath)); + + path->pointCount = EsBufferReadInt(&iconManagement.pack); + path->closed = EsBufferReadByte(&iconManagement.pack); + path->points = (float *) IconBufferAllocate(sizeof(float) * 2 * path->pointCount); + link = &path->next; + + for (int i = 0; i < path->pointCount; i++) { + path->points[i * 2 + 0] = EsBufferReadFloat(&iconManagement.pack); + path->points[i * 2 + 1] = EsBufferReadFloat(&iconManagement.pack); + } + + if (EsBufferReadByte(&iconManagement.pack)) goto next; +} + +void IconPackReadShape(IconPackShape **link) { + if (EsBufferReadByte(&iconManagement.pack) != 0x12) return; + next:; + + IconPackShape *shape = *link = (IconPackShape *) IconBufferAllocate(sizeof(IconPackShape)); + + shape->opacity = EsBufferReadFloat(&iconManagement.pack); + shape->strokeWidth = EsBufferReadFloat(&iconManagement.pack); + shape->strokeDashOffset = EsBufferReadFloat(&iconManagement.pack); + for (int i = 0; i < 8; i++) shape->strokeDashArray[i] = EsBufferReadFloat(&iconManagement.pack); + shape->strokeDashCount = EsBufferReadByte(&iconManagement.pack); + shape->strokeLineJoin = EsBufferReadByte(&iconManagement.pack); + shape->strokeLineCap = EsBufferReadByte(&iconManagement.pack); + shape->miterLimit = EsBufferReadFloat(&iconManagement.pack); + shape->evenOddRule = EsBufferReadByte(&iconManagement.pack); + + IconPackReadPaint(&shape->fill); + IconPackReadPaint(&shape->stroke); + IconPackReadPaint(&shape->paths); + link = &shape->next; + + if (EsBufferReadByte(&iconManagement.pack)) goto next; +} + +IconPackImage *IconPackReadImage(uint32_t id, uint32_t size, int *type) { + iconManagement.bufferPosition = 0; + iconManagement.pack.position = 0; + + uint32_t count = EsBufferReadInt(&iconManagement.pack); + if (id >= count) return nullptr; + iconManagement.pack.position = (id + 1) * 4; + uint32_t start = iconManagement.pack.position = EsBufferReadInt(&iconManagement.pack); + *type = (EsBufferReadInt(&iconManagement.pack) == 1) ? CHARACTER_RECOLOR : CHARACTER_IMAGE; + iconManagement.pack.position = start; + + bool rtl = api.systemConstants[ES_SYSTEM_CONSTANT_RIGHT_TO_LEFT]; + bool found = false; + uint32_t variant = 0; + + while (true) { + variant = EsBufferReadInt(&iconManagement.pack); + if (!variant) break; + if ((variant == size || variant == 1) && !rtl) { found = true; break; } + iconManagement.pack.position = EsBufferReadInt(&iconManagement.pack); + } + + if (!found) { + iconManagement.pack.position = start; + + while (true) { + variant = EsBufferReadInt(&iconManagement.pack); + if (!variant) break; + if ((variant & 0x7FFF) > size && !rtl) { found = true; break; } + iconManagement.pack.position = EsBufferReadInt(&iconManagement.pack); + } + } + + if (!found && rtl) { + iconManagement.pack.position = start; + + while (true) { + variant = EsBufferReadInt(&iconManagement.pack); + if (!variant) break; + if ((variant == (size | 0x8000)) || variant == 0x8001) { found = true; break; } + iconManagement.pack.position = EsBufferReadInt(&iconManagement.pack); + } + } + + if (!found && rtl) { + iconManagement.pack.position = start; + + while (true) { + variant = EsBufferReadInt(&iconManagement.pack); + if (!variant) break; + if ((variant & 0x7FFF) > size && (variant & 0x8000)) { found = true; break; } + iconManagement.pack.position = EsBufferReadInt(&iconManagement.pack); + } + } + + // skipSizeSearch:; + + if (!found) { + iconManagement.pack.position = start; + EsBufferReadInt(&iconManagement.pack); + } + + EsBufferReadInt(&iconManagement.pack); + + IconPackImage *image = (IconPackImage *) IconBufferAllocate(sizeof(IconPackImage)); + image->width = EsBufferReadFloat(&iconManagement.pack); + image->height = EsBufferReadFloat(&iconManagement.pack); + IconPackReadShape(&image->shapes); + return image; +} + +void DrawIcon(int width, int height, uint8_t *destination, IconPackImage *icon, int stride, float translateX, float translateY, float scaleX, float scaleY) { + if (width <= 0 || height <= 0) return; + + RastVertex scale2 = { scaleX, scaleY }; + + RastSurface surface = {}; + + surface.buffer = (uint32_t *) destination; + surface.stride = stride; + + if (!RastSurfaceInitialise(&surface, width, height, true)) { + RastSurfaceDestroy(&surface); + return; + } + + // TODO strokeDashOffset, strokeDashArray, strokeDashCount. + + IconPackShape *shape = icon->shapes; + int shapeIndex = 0; + + while (shape) { + RastPaint paintFill = {}, paintStroke = {}; + + RastContourStyle contour = {}; + contour.internalWidth = shape->strokeWidth * scaleX * 0.5f; + contour.externalWidth = shape->strokeWidth * scaleX * 0.5f; + contour.joinMode = (RastLineJoinMode) shape->strokeLineJoin; + contour.capMode = (RastLineCapMode) shape->strokeLineCap; + contour.miterLimit = scaleX * shape->strokeWidth * shape->miterLimit; + + if (shape->opacity == 0) { + goto nextShape; + } + + for (uintptr_t i = 0; i < 2; i++) { + IconPackPaint *p1 = i ? &shape->stroke : &shape->fill; + RastPaint *p2 = i ? &paintStroke : &paintFill; + + if (p1->type == ICON_PACK_PAINT_SOLID) { + p2->type = RAST_PAINT_SOLID; + uint32_t color = p1->color; + color = (color & 0xFF00FF00) | ((color & 0xFF) << 16) | ((color & 0xFF0000) >> 16); + p2->solid.color = 0xFFFFFF & color; + p2->solid.alpha = (float) ((0xFF000000 & color) >> 24) / 255.0f * shape->opacity; + } else if (p1->type == ICON_PACK_PAINT_LINEAR_GRADIENT || p1->type == ICON_PACK_PAINT_RADIAL_GRADIENT) { + IconPackGradient *gradient = p1->gradient; + p2->type = p1->type == ICON_PACK_PAINT_RADIAL_GRADIENT ? RAST_PAINT_RADIAL_GRADIENT : RAST_PAINT_LINEAR_GRADIENT; + + if (p1->type == ICON_PACK_PAINT_RADIAL_GRADIENT) { + p2->gradient.transform[0] = gradient->transform[0] / scale2.x; + p2->gradient.transform[1] = gradient->transform[2] / scale2.y; + p2->gradient.transform[2] = gradient->transform[4] - (p2->gradient.transform[0] * translateX + p2->gradient.transform[1] * translateY); + p2->gradient.transform[3] = gradient->transform[1] / scale2.x; + p2->gradient.transform[4] = gradient->transform[3] / scale2.y; + p2->gradient.transform[5] = gradient->transform[5] - (p2->gradient.transform[3] * translateX + p2->gradient.transform[4] * translateY); + } else { + p2->gradient.transform[0] = gradient->transform[1] / scale2.x; + p2->gradient.transform[1] = gradient->transform[3] / scale2.y; + p2->gradient.transform[2] = gradient->transform[5] - (p2->gradient.transform[0] * translateX + p2->gradient.transform[1] * translateY); + } + + RastGradientStop stops[16]; + size_t stopCount = gradient->stopCount; + if (stopCount > 16) stopCount = 16; + + for (uintptr_t i = 0; i < stopCount; i++) { + uint32_t color = gradient->stops[i].color; + color = (color & 0xFF00FF00) | ((color & 0xFF) << 16) | ((color & 0xFF0000) >> 16); + stops[i].color = (0xFFFFFF & color) + | ((uint32_t) ((float) ((0xFF000000 & color) >> 24) * shape->opacity) << 24); + stops[i].position = gradient->stops[i].offset; + } + + RastGradientInitialise(p2, stops, stopCount, false); + p2->gradient.repeatMode = (RastRepeatMode) gradient->repeatMode; + } + } + + if (paintFill.type) { + RastPath p = {}; + IconPackPath *path = shape->paths; + + while (path) { + RastPathAppendBezier(&p, (RastVertex *) path->points, path->pointCount, scale2); + if (path->closed || path->next) RastPathCloseSegment(&p); + path = path->next; + } + + RastPathTranslate(&p, translateX, translateY); + RastShape s = RastShapeCreateSolid(&p); + RastSurfaceFill(surface, s, paintFill, shape->evenOddRule); + + RastPathDestroy(&p); + } + + if (shape->strokeWidth && paintStroke.type) { + IconPackPath *path = shape->paths; + + int pathCount = 0; + + while (path) { + RastPath p = {}; + RastPathAppendBezier(&p, (RastVertex *) path->points, path->pointCount, scale2); + RastPathTranslate(&p, translateX, translateY); + RastShape s = RastShapeCreateContour(&p, contour, !path->closed); + RastSurfaceFill(surface, s, paintStroke, false); + path = path->next; + RastPathDestroy(&p); + pathCount++; + } + } + + nextShape:; + + RastGradientDestroy(&paintFill); + RastGradientDestroy(&paintStroke); + + shapeIndex++; + shape = shape->next; + } + + RastSurfaceDestroy(&surface); +} + +bool EsDrawStandardIcon(EsPainter *painter, uint32_t id, int size, EsRectangle region, uint32_t color) { + if (!id) return false; + id--; + + if (!iconManagement.standardPack) { + size_t pathBytes; + char *path = EsSystemConfigurationReadString(EsLiteral("ui"), EsLiteral("icon_pack"), &pathBytes); + iconManagement.standardPack = (uint8_t *) EsFileMap(path, pathBytes, &iconManagement.standardPackSize, ES_MAP_OBJECT_READ_ONLY); + EsHeapFree(path); + } + + { + // Center the icon. + + if (region.r - region.l > size) { + int d = region.r - region.l - size; + region.l += d / 2, region.r -= d / 2; + } + + if (region.b - region.t > size) { + int d = region.b - region.t - size; + region.t += d / 2, region.b -= d / 2; + } + } + + int left = region.l, top = region.t; + EsRectangleClip(region, painter->clip, ®ion); + + GlyphCacheKey key = {}; + key.glyphIndex = id; + key.size = size; + + GlyphCacheEntry *cacheEntry = LookupGlyphCacheEntry(key); + + if (!cacheEntry->data) { + iconManagement.buffer = (char *) EsHeapAllocate((iconManagement.bufferAllocated = 131072), false); + if (!iconManagement.buffer) return false; + iconManagement.pack = { .in = iconManagement.standardPack, .bytes = iconManagement.standardPackSize }; + cacheEntry->width = size, cacheEntry->height = size; + cacheEntry->dataBytes = size * size * 4; + cacheEntry->data = (uint8_t *) EsHeapAllocate(cacheEntry->dataBytes, true); + RegisterGlyphCacheEntry(key, cacheEntry); + IconPackImage *image = IconPackReadImage(id, size, &cacheEntry->type); + DrawIcon(size, size, cacheEntry->data, image, size * 4, 0, 0, (float) size / image->width, (float) size / image->height); + EsHeapFree(iconManagement.buffer); + } + + DrawSingleCharacter(cacheEntry->width, cacheEntry->height, 0, 0, + ES_POINT(left, top), region, painter->target, + 0, cacheEntry->type, false, cacheEntry->data, + color, 0, 0, painter->target->fullAlpha); + + return true; +} + +void EsDrawVectorFile(EsPainter *painter, EsRectangle bounds, const void *data, size_t dataBytes) { + iconManagement.bufferPosition = 0; + iconManagement.buffer = (char *) EsHeapAllocate((iconManagement.bufferAllocated = 131072), false); + iconManagement.pack = { .in = (const uint8_t *) data, .bytes = dataBytes }; + + IconPackImage *image = (IconPackImage *) IconBufferAllocate(sizeof(IconPackImage)); + image->width = EsBufferReadFloat(&iconManagement.pack); + image->height = EsBufferReadFloat(&iconManagement.pack); + IconPackReadShape(&image->shapes); + + EsRectangle destination = EsRectangleIntersection(bounds, painter->clip); + EsPaintTarget *target = painter->target; + DrawIcon(Width(destination), Height(destination), (uint8_t *) target->bits + destination.l * 4 + destination.t * target->stride, image, target->stride, + bounds.l - destination.l, bounds.t - destination.t, (float) Width(bounds) / image->width, (float) Height(bounds) / image->height); + + EsHeapFree(iconManagement.buffer); +} + +// --------------------------------- Basic shaping engine. + +#ifndef USE_HARFBUZZ + +#define HB_SCRIPT_COMMON (1) +#define HB_SCRIPT_INHERITED (1) + +#define HB_SHAPE(plan, features, featureCount) hb_shape(plan->font, plan->buffer, features, featureCount) + +struct hb_segment_properties_t { + uint32_t script; +}; + +struct hb_glyph_info_t { + uint32_t cluster; + uint32_t codepoint; +}; + +struct hb_glyph_position_t { + uint32_t x_advance; + uint32_t y_advance; + uint32_t x_offset; + uint32_t y_offset; +}; + +struct hb_feature_t { +}; + +struct hb_buffer_t { + const char *text; + size_t textBytes; + uintptr_t shapeOffset; + size_t shapeBytes; + + Array glyphInfos; + Array glyphPositions; +}; + +void hb_buffer_clear_contents(hb_buffer_t *buffer) { + buffer->glyphInfos.Free(); + buffer->glyphPositions.Free(); +} + +void hb_buffer_set_segment_properties(hb_buffer_t *, hb_segment_properties_t *) { +} + +void hb_buffer_add_utf8(hb_buffer_t *buffer, const char *text, size_t textBytes, uintptr_t shapeOffset, size_t shapeBytes) { + buffer->text = text; + buffer->textBytes = textBytes; + buffer->shapeOffset = shapeOffset; + buffer->shapeBytes = shapeBytes; +} + +hb_glyph_info_t *hb_buffer_get_glyph_infos(hb_buffer_t *buffer, uint32_t *glyphCount) { + *glyphCount = buffer->glyphInfos.Length(); + return buffer->glyphInfos.array; +} + +hb_glyph_position_t *hb_buffer_get_glyph_positions(hb_buffer_t *buffer, uint32_t *glyphCount) { + *glyphCount = buffer->glyphPositions.Length(); + return buffer->glyphPositions.array; +} + +uint32_t hb_unicode_script(struct hb_unicode_funcs_t *, uint32_t) { + return FALLBACK_SCRIPT; +} + +struct hb_unicode_funcs_t *hb_unicode_funcs_get_default() { + return nullptr; +} + +hb_buffer_t *hb_buffer_create() { + return (hb_buffer_t *) EsHeapAllocate(sizeof(hb_buffer_t), true); +} + +void hb_buffer_destroy(hb_buffer_t *buffer) { + hb_buffer_clear_contents(buffer); + EsHeapFree(buffer); +} + +void hb_shape(Font font, hb_buffer_t *buffer, const hb_feature_t *, uint32_t) { + // TODO Cache glyph metrics. + + const char *text = buffer->text + buffer->shapeOffset; + uint32_t previous = 0; + + while (true) { + hb_glyph_info_t info = {}; + hb_glyph_position_t position = {}; + info.cluster = text - buffer->text; + uint32_t codepoint = utf8_value(text, buffer->text + buffer->shapeOffset + buffer->shapeBytes - text, nullptr); + if (!codepoint) break; + text = utf8_advance(text); + info.codepoint = FontCodepointToGlyphIndex(&font, codepoint); + FontGetGlyphMetrics(&font, info.codepoint, &position.x_advance, &position.y_advance, &position.x_offset, &position.y_offset); + position.x_advance += FontGetKerning(&font, previous, info.codepoint); + previous = info.codepoint; + buffer->glyphInfos.Add(info); + buffer->glyphPositions.Add(position); + } +} + +#endif + +// --------------------------------- Text shaping. + +enum TextStyleDifference { + TEXT_STYLE_NEW_FONT, // A new font is selected. + TEXT_STYLE_NEW_SHAPE, // Shaping parameters have changed. + TEXT_STYLE_NEW_RENDER, // Render-only properties have changed. + TEXT_STYLE_IDENTICAL, // The styles are the same. +}; + +struct TextPiece { + // Shaped glyphs, on the same line, and with constant style and script. + int32_t ascent, descent, width; + const EsTextStyle *style; + uintptr_t glyphOffset; + size_t glyphCount; + uintptr_t start, end; + bool isTabPiece; +}; + +struct TextLine { + int32_t ascent, descent, width; + bool hasEllipsis; + uintptr_t ellipsisPieceIndex; + uintptr_t pieceOffset; + size_t pieceCount; +}; + +struct TextRun { + EsTextStyle style; + uint32_t offset; + uint32_t script; +}; + +struct EsTextPlan { + hb_buffer_t *buffer; + hb_segment_properties_t segmentProperties; + + const char *string; + + Array textRuns; + uintptr_t textRunPosition; + + const EsTextStyle *currentTextStyle; + Font font; + + BreakState breaker; + + Array glyphInfos; + Array glyphPositions; + + Array pieces; + Array lines; + + int32_t totalHeight, totalWidth; + + bool singleUse; + + EsTextPlanProperties properties; +}; + +TextStyleDifference CompareTextStyles(const EsTextStyle *style1, const EsTextStyle *style2) { + if (!style1) return TEXT_STYLE_NEW_FONT; + if (style1->font.family != style2->font.family) return TEXT_STYLE_NEW_FONT; + if (style1->font.weight != style2->font.weight) return TEXT_STYLE_NEW_FONT; + if (style1->font.italic != style2->font.italic) return TEXT_STYLE_NEW_FONT; + if (style1->size != style2->size) return TEXT_STYLE_NEW_FONT; + if (style1->baselineOffset != style2->baselineOffset) return TEXT_STYLE_NEW_SHAPE; + if (style1->tracking != style2->tracking) return TEXT_STYLE_NEW_SHAPE; + if (style1->figures != style2->figures) return TEXT_STYLE_NEW_SHAPE; + if (style1->alternateDirection != style2->alternateDirection) return TEXT_STYLE_NEW_SHAPE; + if (style1->color != style2->color) return TEXT_STYLE_NEW_RENDER; + if (style1->blur != style2->blur) return TEXT_STYLE_NEW_RENDER; + if (style1->decorations != style2->decorations) return TEXT_STYLE_NEW_RENDER; + if (style1->decorationsColor != style2->decorationsColor) return TEXT_STYLE_NEW_RENDER; + return TEXT_STYLE_IDENTICAL; +} + +ptrdiff_t TextGetCharacterAtPoint(const EsTextStyle *textStyle, const char *string, size_t stringBytes, int *_pointX, uint32_t flags) { + // TODO Better integration with the EsTextPlan API. + + EsTextPlanProperties properties = {}; + EsTextRun textRuns[2] = {}; + textRuns[0].style = *textStyle; + textRuns[1].offset = stringBytes; + EsTextPlan *plan = EsTextPlanCreate(&properties, {}, string, textRuns, 1); + if (!plan) return 0; + + EsAssert(plan->lines.Length() == 1); + bool useMiddle = flags & ES_TEXT_GET_CHARACTER_AT_POINT_MIDDLE; + int pointX = *_pointX; + pointX *= FREETYPE_UNIT_SCALE; + int currentX = 0, priorMiddle = 0; + ptrdiff_t result = -1; + + for (uintptr_t j = 0; j < plan->lines[0].pieceCount; j++) { + TextPiece *piece = &plan->pieces[plan->lines[0].pieceOffset + j]; + hb_glyph_info_t *glyphs = &plan->glyphInfos[piece->glyphOffset]; + hb_glyph_position_t *glyphPositions = &plan->glyphPositions[piece->glyphOffset]; + + for (uintptr_t i = 0; i < piece->glyphCount; i++) { + int left = useMiddle ? priorMiddle : currentX; + int right = currentX + glyphPositions[i].x_advance / (useMiddle ? 2 : 1); + + priorMiddle = right; + + if (pointX >= left && pointX < right) { + result = glyphs[i].cluster; + goto done; + } + + currentX += glyphPositions[i].x_advance; + } + } + + done:; + *_pointX = currentX / FREETYPE_UNIT_SCALE; + EsTextPlanDestroy(plan); + return result; +} + +int TextGetPartialStringWidth(const EsTextStyle *textStyle, const char *fullString, size_t fullStringBytes, size_t measureBytes) { + // TODO Better integration with the EsTextPlan API. + + EsTextPlanProperties properties = {}; + EsTextRun textRuns[3] = {}; + textRuns[0].style = *textStyle; + textRuns[1].style = *textStyle; + textRuns[1].offset = measureBytes; + textRuns[2].offset = fullStringBytes; + EsTextPlan *plan = EsTextPlanCreate(&properties, {}, fullString, textRuns, 2); + if (!plan) return 0; + + int width = 0; + EsAssert(plan->lines.Length() == 1); + + for (uintptr_t i = 0; i < plan->lines[0].pieceCount; i++) { + TextPiece *piece = &plan->pieces[plan->lines[0].pieceOffset + i]; + + if (piece->start < measureBytes) { + width += piece->width; + } + } + + EsTextPlanDestroy(plan); + return width / FREETYPE_UNIT_SCALE; +} + +int TextGetStringWidth(const EsTextStyle *textStyle, const char *string, size_t stringBytes) { + return TextGetPartialStringWidth(textStyle, string, stringBytes, stringBytes); +} + +void TextTrimSpaces(EsTextPlan *plan) { + if (~plan->properties.flags & ES_TEXT_PLAN_TRIM_SPACES) { + return; + } + + for (uintptr_t i = 0; i < plan->lines.Length(); i++) { + TextLine *line = &plan->lines[i]; + + if (!line->pieceCount) { + continue; + } + + TextPiece *firstPiece = &plan->pieces[line->pieceOffset]; + TextPiece *lastPiece = &plan->pieces[line->pieceOffset + line->pieceCount - 1]; + + while (firstPiece->glyphCount && firstPiece->start != firstPiece->end + && plan->glyphInfos[firstPiece->glyphOffset].cluster == firstPiece->start + && plan->string[firstPiece->start] == ' ') { + line->width -= plan->glyphPositions[firstPiece->glyphOffset].x_advance; + firstPiece->width -= plan->glyphPositions[firstPiece->glyphOffset].x_advance; + firstPiece->glyphOffset++; + firstPiece->glyphCount--; + firstPiece->start++; + } + + while (lastPiece->glyphCount && lastPiece->start != lastPiece->end + && plan->glyphInfos[lastPiece->glyphOffset + lastPiece->glyphCount - 1].cluster == lastPiece->end - 1 + && plan->string[lastPiece->end - 1] == ' ') { + line->width -= plan->glyphPositions[lastPiece->glyphOffset + lastPiece->glyphCount - 1].x_advance; + lastPiece->width -= plan->glyphPositions[lastPiece->glyphOffset + lastPiece->glyphCount - 1].x_advance; + lastPiece->glyphCount--; + lastPiece->end--; + } + } +} + +void TextPlaceEmergencyBreaks(EsTextPlan *plan, int32_t maximumLineWidth) { + if ((plan->properties.flags & ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES) || maximumLineWidth == -1) { + return; + } + + repeat:; + TextLine *line = &plan->lines.Last(); + if (line->width <= maximumLineWidth) return; + EsAssert(line->pieceCount >= 1); + + int32_t x = 0, x0 = 0; + uintptr_t j, piece; + + for (piece = 0; piece < line->pieceCount; piece++) { + TextPiece *p = &plan->pieces[line->pieceOffset + piece]; + x0 = x; + + for (j = 0; j < p->glyphCount; j++) { + int32_t width = plan->glyphPositions[p->glyphOffset + j].x_advance; + + if (x + width > maximumLineWidth && (j || piece)) { + goto foundBreakPoint; + } + + x += width; + } + } + + return; // One glyph on the line; we can't do anything. + foundBreakPoint:; + + // Split the line. + + TextPiece *piece0 = &plan->pieces[line->pieceOffset + piece]; + TextPiece piece1 = *piece0; + piece1.width = piece0->width - (x - x0); + piece1.glyphOffset += j; + piece1.glyphCount = piece0->glyphCount - j; + piece1.start = plan->glyphInfos[piece0->glyphOffset + j].cluster; + piece0->end = piece1.start; + piece0->width = x - x0; + piece0->glyphCount = j; + plan->pieces.Insert(piece1, line->pieceOffset + piece + 1); + + TextLine *line0 = line; + TextLine line1 = *line; + line1.width -= x; + line0->width = x; + line1.pieceOffset += piece + 1; + line1.pieceCount = line0->pieceCount - piece; + line0->pieceCount = piece + 1; + plan->lines.Add(line1); + + goto repeat; +} + +void TextAddEllipsis(EsTextPlan *plan, int32_t maximumLineWidth, bool needFinalEllipsis, int32_t boundsWidth) { + if (!boundsWidth || (~plan->properties.flags & ES_TEXT_ELLIPSIS)) { + return; + } + + bool needEllipsis = false; + + if (maximumLineWidth == -1) { + for (uintptr_t i = 0; i < plan->lines.Length(); i++) { + if (plan->lines[i].width > boundsWidth * FREETYPE_UNIT_SCALE) { + maximumLineWidth = boundsWidth * FREETYPE_UNIT_SCALE; + needEllipsis = true; + } + } + } else { + // Word-wrapping was enabled so lines won't exceed the boundary width. + } + + if (!needEllipsis && !needFinalEllipsis) { + return; + } + + uint8_t ellipsisUTF8[3] = { 0xE2, 0x80, 0xA6 }; + + // Shape and measure the ellipsis character. + + hb_buffer_clear_contents(plan->buffer); + hb_buffer_set_segment_properties(plan->buffer, &plan->segmentProperties); + hb_buffer_add_utf8(plan->buffer, (const char *) ellipsisUTF8, sizeof(ellipsisUTF8), 0, sizeof(ellipsisUTF8)); + HB_SHAPE(plan, nullptr, 0); + + int32_t ellipsisWidth = 0; + + uint32_t glyphCount, glyphCount2; + hb_glyph_info_t *glyphInfos = hb_buffer_get_glyph_infos(plan->buffer, &glyphCount); + hb_glyph_position_t *glyphPositions = hb_buffer_get_glyph_positions(plan->buffer, &glyphCount2); + EsAssert(glyphCount == glyphCount2); + + for (uintptr_t i = 0; i < glyphCount; i++) { + ellipsisWidth += glyphPositions[i].x_advance; + } + + for (uintptr_t i = needEllipsis ? 0 : plan->lines.Length() - 1; i < plan->lines.Length(); i++) { + TextLine *line = &plan->lines[i]; + + if (i == plan->lines.Length() - 1 && needFinalEllipsis) { + // The maximum number of lines was exceeded, and this is the last permitted line, so add an ellipsis. + } else if (line->width > boundsWidth * FREETYPE_UNIT_SCALE) { + // This line exceeds the width boundary (and hence word-wrapping was disabled), so add an ellipsis. + } else { + continue; + } + + // Make space for the ellipsis. + + int32_t spaceNeeded = ellipsisWidth - (maximumLineWidth - line->width); + + while (line->pieceCount && spaceNeeded > 0) { + TextPiece *piece = &plan->pieces[line->pieceOffset + line->pieceCount - 1]; + + if (piece->isTabPiece) { + spaceNeeded -= piece->width; + line->pieceCount--; + } else if (piece->start == piece->end || !piece->glyphCount) { + line->pieceCount--; + } else { + piece->end = plan->glyphInfos[piece->glyphOffset + piece->glyphCount - 1].cluster; + int32_t width = plan->glyphPositions[piece->glyphOffset + piece->glyphCount - 1].x_advance; + spaceNeeded -= width, line->width -= width, piece->width -= width; + piece->glyphCount--; + + while (piece->glyphCount) { + if (plan->glyphInfos[piece->glyphOffset + piece->glyphCount - 1].cluster == piece->end) { + // TODO Test this branch! + int32_t width = plan->glyphPositions[piece->glyphOffset + piece->glyphCount - 1].x_advance; + spaceNeeded -= width, line->width -= width, piece->width -= width; + piece->glyphCount--; + } else { + break; + } + } + } + } + + // Add the ellipsis. + + TextPiece piece = {}; + piece.style = plan->currentTextStyle; + piece.glyphOffset = plan->glyphInfos.Length(); + piece.glyphCount = glyphCount; + piece.ascent = FontGetAscent (&plan->font) + plan->currentTextStyle->baselineOffset, + piece.descent = -FontGetDescent(&plan->font) - plan->currentTextStyle->baselineOffset; + + for (uintptr_t i = 0; i < glyphCount; i++) { + plan->glyphInfos.Add(glyphInfos[i]); + plan->glyphPositions.Add(glyphPositions[i]); + int32_t width = glyphPositions[i].x_advance; + piece.width += width, line->width += width; + } + + line->hasEllipsis = true; + line->ellipsisPieceIndex = plan->pieces.Length(); + plan->pieces.Add(piece); + } +} + +void TextItemizeByScript(EsTextPlan *plan, const EsTextRun *runs, size_t runCount) { + hb_unicode_funcs_t *unicodeFunctions = hb_unicode_funcs_get_default(); + uint32_t lastAssignedScript = FALLBACK_SCRIPT; + + for (uintptr_t i = 0; i < runCount; i++) { + uintptr_t offset = runs[i].offset; + + for (uintptr_t j = offset; j < runs[i + 1].offset;) { + uint32_t codepoint = utf8_value(plan->string + j); + uint32_t script; + + if (codepoint == '\t') { + // Tab characters should go in their own section. + script = '\t'; + } else { + script = hb_unicode_script(unicodeFunctions, codepoint); + } + + if (script == HB_SCRIPT_COMMON || script == HB_SCRIPT_INHERITED) { + // TODO If this is a closing character, restore the last assigned script before the most recent opening character. + script = lastAssignedScript == '\t' ? FALLBACK_SCRIPT : lastAssignedScript; + } + + if (lastAssignedScript != script && j != runs[i].offset) { + TextRun run = {}; + run.style = runs[i].style; + run.offset = offset; + run.script = lastAssignedScript; + run.style.font.family = FontApplySubstitution(&plan->properties, run.style.font.family, run.script); + plan->textRuns.Add(run); + offset = j; + } + + lastAssignedScript = script; + j = utf8_advance(plan->string + j) - plan->string; + } + + TextRun run = {}; + run.style = runs[i].style; + run.offset = offset; + run.script = lastAssignedScript; + run.style.font.family = FontApplySubstitution(&plan->properties, run.style.font.family, run.script); + plan->textRuns.Add(run); + } + + TextRun run = {}; + run.offset = runs[runCount].offset; + plan->textRuns.Add(run); +} + +void TextUpdateFont(EsTextPlan *plan, const EsTextStyle *style) { + if (TEXT_STYLE_NEW_FONT == CompareTextStyles(plan->currentTextStyle, style)) { + plan->font = FontGet(style->font); + FontSetSize(&plan->font, style->size); + } + + plan->currentTextStyle = style; +} + +int32_t TextExpandTabs(EsTextPlan *plan, uintptr_t pieceOffset, int32_t width) { + int32_t addedWidth = 0; + + for (uintptr_t i = pieceOffset; i < plan->pieces.Length(); i++) { + TextPiece *piece = &plan->pieces[i]; + + if (piece->isTabPiece) { + TextUpdateFont(plan, piece->style); + int32_t emWidth = FontGetEmWidth(&plan->font) * FREETYPE_UNIT_SCALE; + int32_t tabWidth = emWidth * 4; // TODO Make spaces-per-tab customizable. + int32_t firstWidth = emWidth + tabWidth - (width + emWidth) % tabWidth; + piece->width = firstWidth + tabWidth * (piece->end - piece->start - 1); + addedWidth += piece->width; + piece->glyphOffset = plan->glyphInfos.Length(); + piece->glyphCount = piece->end - piece->start; + piece->ascent = FontGetAscent(&plan->font); + piece->descent = -FontGetDescent(&plan->font); + + for (uintptr_t i = 0; i < piece->glyphCount; i++) { + hb_glyph_info_t info = {}; + info.cluster = piece->start + i; + info.codepoint = 0xFFFFFFFF; + hb_glyph_position_t position = {}; + position.x_advance = i ? tabWidth : firstWidth; + plan->glyphInfos.Add(info); + plan->glyphPositions.Add(position); + } + } + + width += piece->width; + } + + return addedWidth; +} + +int32_t TextBuildTextPieces(EsTextPlan *plan, uintptr_t sectionStart, uintptr_t sectionEnd) { + // Find the first run that contains the section. + + for (; plan->textRunPosition < plan->textRuns.Length() - 1; plan->textRunPosition++) { + if (plan->textRuns[plan->textRunPosition].offset <= sectionStart && plan->textRuns[plan->textRunPosition + 1].offset > sectionStart) { + break; + } + } + + EsAssert(plan->textRunPosition != plan->textRuns.Length() - 1); + + // Iterate through each run in the section. + + int32_t width = 0; + + while (plan->textRunPosition != plan->textRuns.Length() - 1) { + TextRun *run = &plan->textRuns[plan->textRunPosition]; + + uintptr_t start = sectionStart > run[0].offset ? sectionStart : run[0].offset; + uintptr_t end = sectionEnd < run[1].offset ? sectionEnd : run[1].offset; + + if (end <= start) { + break; + } + + // Update the font to match the run. + + TextUpdateFont(plan, &run->style); + + // Don't shape newline characters. + + while (start < end && plan->string[start] == '\n') start++; + while (end - 1 > start && plan->string[end - 1] == '\n') end--; + + if (end == start) { + plan->textRunPosition++; + continue; + } + + EsAssert(end > start); + + // Handle tab characters specially. + + if (plan->string[start] == '\t') { + TextPiece _piece = {}; + plan->pieces.Add(_piece); + TextPiece *piece = &plan->pieces.Last(); + piece->style = plan->currentTextStyle; + piece->glyphOffset = 0; + piece->glyphCount = 0; + piece->start = start; + piece->end = end; + piece->isTabPiece = true; + plan->textRunPosition++; + continue; + } + + // Shape the run. + + hb_feature_t features[4] = {}; + size_t featureCount = 0; + +#ifdef USE_HARFBUZZ + if (plan->currentTextStyle->figures == ES_TEXT_FIGURE_OLD) hb_feature_from_string("onum", -1, features + (featureCount++)); + if (plan->currentTextStyle->figures == ES_TEXT_FIGURE_TABULAR) hb_feature_from_string("tnum", -1, features + (featureCount++)); + plan->segmentProperties.script = (hb_script_t) run->script; +#endif + + hb_buffer_clear_contents(plan->buffer); + hb_buffer_set_segment_properties(plan->buffer, &plan->segmentProperties); + hb_buffer_add_utf8(plan->buffer, plan->string, plan->breaker.bytes, start, end - start); + + HB_SHAPE(plan, features, featureCount); + + uint32_t glyphCount, glyphCount2; + hb_glyph_info_t *glyphInfos = hb_buffer_get_glyph_infos(plan->buffer, &glyphCount); + hb_glyph_position_t *glyphPositions = hb_buffer_get_glyph_positions(plan->buffer, &glyphCount2); + EsAssert(glyphCount == glyphCount2); + + // Create the text piece. + + TextPiece _piece = {}; + plan->pieces.Add(_piece); + TextPiece *piece = &plan->pieces.Last(); + piece->style = plan->currentTextStyle; + piece->glyphOffset = plan->glyphInfos.Length(); + piece->glyphCount = glyphCount; + piece->ascent = FontGetAscent (&plan->font) + plan->currentTextStyle->baselineOffset; + piece->descent = -FontGetDescent(&plan->font) - plan->currentTextStyle->baselineOffset; + piece->start = start; + piece->end = end; + + for (uintptr_t i = 0; i < glyphCount; i++) { + plan->glyphInfos.Add(glyphInfos[i]); + plan->glyphPositions.Add(glyphPositions[i]); + + piece->width += glyphPositions[i].x_advance; + + if (i == glyphCount - 1 || glyphInfos[i].cluster != glyphInfos[i + 1].cluster) { + piece->width += plan->currentTextStyle->tracking * FREETYPE_UNIT_SCALE; + } + + // EsPrint("\t%d\n", glyphInfos[i].codepoint); + } + + width += piece->width; + + // Go to the next run. + + plan->textRunPosition++; + } + + plan->textRunPosition--; + + return width; +} + +EsTextPlan *EsTextPlanCreate(EsTextPlanProperties *properties, EsRectangle bounds, const char *string, const EsTextRun *formatRuns, size_t formatRunCount) { + // TODO Bidirectional text (UAX9). + // TODO Vertical text layout (UAX50). + // TODO Supporting arbitrary OpenType features. + // TODO Reshaping lines once word wrapping is applied. + + // EsPrint("EsTextPlanCreate... width %d\n", Width(bounds) * FREETYPE_UNIT_SCALE); + + EsMessageMutexCheck(); + + EsTextPlan plan = {}; + + // Initialise the line breaker. + + plan.breaker.string = string; + plan.breaker.bytes = formatRuns[formatRunCount].offset; + EsAssert(plan.breaker.bytes < 0x80000000); + + if (!plan.breaker.bytes) { + return nullptr; // Empty input. + } + + // Initialise the plan. + + plan.string = string; + plan.singleUse = properties->flags & ES_TEXT_PLAN_SINGLE_USE; + plan.properties = *properties; + + TextLine blankLine = {}; + plan.lines.Add(blankLine); + + // Setup the HarfBuzz buffer. + + plan.buffer = hb_buffer_create(); +#ifdef USE_HARFBUZZ + hb_buffer_set_cluster_level(plan.buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); + + plan.segmentProperties.direction = (properties->flags & ES_TEXT_PLAN_RTL) ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; + plan.segmentProperties.script = (hb_script_t) FALLBACK_SCRIPT; + plan.segmentProperties.language = hb_language_from_string(properties->cLanguage ?: FALLBACK_SCRIPT_LANGUAGE, -1); +#endif + + // Subdivide the runs by character script. + + TextItemizeByScript(&plan, formatRuns, formatRunCount); + + // Layout the paragraph. + + int32_t maximumLineWidth = Width(bounds) && (properties->flags & ES_TEXT_WRAP) ? Width(bounds) * FREETYPE_UNIT_SCALE : -1; + Break previousBreak = {}; + bool needEllipsis = false; + + while (previousBreak.position != plan.breaker.bytes) { + // Find the next break opportunity. + + Break nextBreak = plan.breaker.Next(); + + while (!plan.breaker.error && !nextBreak.forced && (~properties->flags & ES_TEXT_WRAP)) { + nextBreak = plan.breaker.Next(); + } + + if (plan.breaker.error) { + break; + } + + // Build the text pieces for this section. + + uintptr_t pieceOffset = plan.pieces.Length(); + int32_t width = TextBuildTextPieces(&plan, previousBreak.position, nextBreak.position); + width += TextExpandTabs(&plan, pieceOffset, plan.lines.Last().width); + + // Should we start a new line? + + if (previousBreak.forced || (maximumLineWidth != -1 && plan.lines.Last().width + width > maximumLineWidth)) { + if (properties->maxLines == (int32_t) plan.lines.Length()) { + needEllipsis = true; + break; + } + + plan.lines.Add(blankLine); + plan.lines.Last().pieceOffset = pieceOffset; + } + +#if 0 + EsPrint("\tadded section '%s' to line %d (%d pieces) at x=%d\n", + nextBreak.position - previousBreak.position, string + previousBreak.position, + ArrayLength(plan.lines) - 1, ArrayLength(plan.pieces) - pieceOffset, + plan.lines.Last().width); +#endif + + // Add the pieces to the line. + + TextLine *line = &plan.lines.Last(); + TextExpandTabs(&plan, pieceOffset, line->width); + + for (uintptr_t i = pieceOffset; i < plan.pieces.Length(); i++) { + line->width += plan.pieces[i].width; + line->pieceCount++; + } + + TextPlaceEmergencyBreaks(&plan, maximumLineWidth); + + // Go to the next section. + + previousBreak = nextBreak; + } + + // Calculate the ascent/descent of each line. + + for (uintptr_t i = 0; i < plan.lines.Length(); i++) { + TextLine *line = &plan.lines[i]; + + if (!line->pieceCount && i) { + // If the line doesn't have any pieces, it must be from a double newline. + // Inherit the ascent/descent of the previous line. + + line->ascent = line[-1].ascent; + line->descent = line[-1].descent; + } + + for (uintptr_t i = line->pieceOffset; i < line->pieceOffset + line->pieceCount; i++) { + if (line->ascent < plan.pieces[i].ascent) line->ascent = plan.pieces[i].ascent; + if (line->descent < plan.pieces[i].descent) line->descent = plan.pieces[i].descent; + } + } + + // Trim leading and trailing spaces. + + TextTrimSpaces(&plan); + + // Add a terminating ellipsis. + + TextAddEllipsis(&plan, maximumLineWidth, needEllipsis, Width(bounds)); + + // Calculate the total width and height. + + for (uintptr_t i = 0; i < plan.lines.Length(); i++) { + plan.totalHeight += plan.lines[i].ascent + plan.lines[i].descent; + + if (plan.lines[i].width > plan.totalWidth) { + plan.totalWidth = plan.lines[i].width; + } + } + + // Destroy the HarfBuzz buffer. + + hb_buffer_destroy(plan.buffer); + plan.buffer = nullptr; + + // Return the plan. + + EsTextPlan *copy = (EsTextPlan *) EsHeapAllocate(sizeof(EsTextPlan), true); + *copy = plan; + return copy; +} + +void EsTextPlanDestroy(EsTextPlan *plan) { + EsMessageMutexCheck(); + EsAssert(!plan->singleUse); + plan->glyphInfos.Free(); + plan->glyphPositions.Free(); + plan->pieces.Free(); + plan->lines.Free(); + plan->textRuns.Free(); + EsHeapFree(plan); +} + +void EsTextPlanReplaceStyleRenderProperties(EsTextPlan *plan, EsTextStyle *style) { + for (uintptr_t i = 0; i < plan->textRuns.Length() - 1; i++) { + plan->textRuns[i].style.color = style->color; + plan->textRuns[i].style.blur = style->blur; + plan->textRuns[i].style.decorations = style->decorations; + plan->textRuns[i].style.decorationsColor = style->decorationsColor; + } +} + +int EsTextPlanGetWidth(EsTextPlan *plan) { + return (plan->totalWidth + FREETYPE_UNIT_SCALE - 1) / FREETYPE_UNIT_SCALE; +} + +int EsTextPlanGetHeight(EsTextPlan *plan) { + return (plan->totalHeight + FREETYPE_UNIT_SCALE - 1) / FREETYPE_UNIT_SCALE; +} + +size_t EsTextPlanGetLineCount(EsTextPlan *plan) { + return plan->lines.Length(); +} + +EsTextStyle TextPlanGetPrimaryStyle(EsTextPlan *plan) { + return plan->textRuns[0].style; +} + +void DrawTextPiece(EsPainter *painter, EsTextPlan *plan, TextPiece *piece, TextLine *line, + int32_t cursorX, int32_t cursorY, + EsTextSelection *selection, uintptr_t caret, int32_t selectionBackgroundBottom) { + if (cursorX / FREETYPE_UNIT_SCALE > painter->clip.r + || (cursorX + piece->width) / FREETYPE_UNIT_SCALE < painter->clip.l + || cursorY > painter->clip.b + || (cursorY + (piece->ascent + piece->descent) / FREETYPE_UNIT_SCALE) < painter->clip.t) { + return; + } + +#if 0 + EsPrint("\tdrawing piece; '%s' on line %d glyphOffset %d and glyphCount %d at %i, %i with caret %d\n", + piece->end - piece->start, plan->string + piece->start, + line - plan->lines, piece->glyphOffset, piece->glyphCount, + cursorX / FREETYPE_UNIT_SCALE, cursorY, caret); +#endif + + // Prevent issues with negative numbers getting rounded differently... + int32_t cursorXIntegerOffset = -(0x40000000 / FREETYPE_UNIT_SCALE); + cursorX += 0x40000000; + int32_t cursorXStart = cursorX; + + hb_glyph_info_t *glyphs = &plan->glyphInfos[piece->glyphOffset]; + hb_glyph_position_t *glyphPositions = &plan->glyphPositions[piece->glyphOffset]; + + // Update the font to match the piece. + + TextUpdateFont(plan, piece->style); + + // Draw the selection background. + + if (selection->caret0 != selection->caret1 && !selection->hideCaret) { + int sCursorX = cursorX, selectionStartX = -1, selectionEndX = -1; + + for (uintptr_t i = 0; i < piece->glyphCount; i++) { + if (selectionStartX == -1 && glyphs[i].cluster >= selection->caret0) { + selectionStartX = sCursorX; + } + + if (selectionEndX == -1 && glyphs[i].cluster >= selection->caret1) { + selectionEndX = sCursorX; + } + + sCursorX += glyphPositions[i].x_advance; + + if (i == piece->glyphCount - 1 || glyphs[i].cluster != glyphs[i + 1].cluster) { + sCursorX += plan->currentTextStyle->tracking; + } + } + + if (selectionStartX == -1 && selection->caret0 >= 0) { + selectionStartX = sCursorX; + } + + if (selectionEndX == -1) { + selectionEndX = sCursorX; + } + + EsRectangle s; + s.l = (selectionStartX + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE + cursorXIntegerOffset; + s.t = cursorY; + s.r = (selectionEndX + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE + cursorXIntegerOffset; + s.b = selectionBackgroundBottom; + EsDrawBlock(painter, s, selection->background); + } + + // Draw each glyph in the piece. + + int32_t caretX = -1, caretY = cursorY; + + for (uintptr_t i = 0; i < piece->glyphCount; i++) { + uint32_t codepoint = glyphs[i].codepoint; + + int positionX = (glyphPositions[i].x_offset + cursorX) / FREETYPE_UNIT_SCALE + cursorXIntegerOffset, + positionY = ((glyphPositions[i].y_offset + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE + cursorY); + uint32_t color = plan->currentTextStyle->color; + + GlyphCacheKey key = {}; + key.glyphIndex = codepoint; + key.size = plan->currentTextStyle->size; + key.font = plan->font; + GlyphCacheEntry *entry = nullptr; + + if (codepoint == 0xFFFFFFFF) { + goto nextCharacter; + } + + if (key.size > 25) { + key.fractionalPosition = 0; + } else if (key.size > 15) { + key.fractionalPosition = ((glyphPositions[i].x_offset + cursorX) & 0x3F) & 0x20; + } else { + key.fractionalPosition = ((glyphPositions[i].x_offset + cursorX) & 0x3F) & 0x30; + } + + entry = LookupGlyphCacheEntry(key); + + if (!entry->data) { + if (!FontRenderGlyph(api.systemConstants[ES_SYSTEM_CONSTANT_NO_FANCY_GRAPHICS], key, entry)) { + EsHeapFree(entry); + goto nextCharacter; + } else { + RegisterGlyphCacheEntry(key, entry); + } + } + + if (selection->caret0 != selection->caret1 && !selection->hideCaret + && glyphs[i].cluster >= selection->caret0 && glyphs[i].cluster < selection->caret1 + && selection->foreground) { + color = selection->foreground; + } + + // EsPrint("\t%c at %i.%i\n", plan->string[glyphs[i].cluster], positionX, (glyphPositions[i].x_offset + cursorX) & 0x3F); + + DrawSingleCharacter(entry->width, entry->height, entry->xoff, entry->yoff, + ES_POINT(positionX, positionY + line->ascent / FREETYPE_UNIT_SCALE), + painter->clip, painter->target, + plan->currentTextStyle->blur, + api.systemConstants[ES_SYSTEM_CONSTANT_NO_FANCY_GRAPHICS] ? CHARACTER_MONO : CHARACTER_SUBPIXEL, + false, entry->data, + color, 0, -1, painter->target->fullAlpha); + + nextCharacter:; + + if (caretX == -1 && glyphs[i].cluster >= caret) { + caretX = (cursorX + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE; + } + + cursorX += glyphPositions[i].x_advance; + cursorY += (glyphPositions[i].y_advance + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE; + + if (i == piece->glyphCount - 1 || glyphs[i].cluster != glyphs[i + 1].cluster) { + cursorX += plan->currentTextStyle->tracking * FREETYPE_UNIT_SCALE; + } + } + + if (caretX == -1) { + caretX = (cursorX + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE; + } + + // Draw the caret. + + if (!selection->hideCaret && caret >= piece->start + && (caret < piece->end || (caret == piece->end && piece == &plan->pieces[line->pieceOffset + line->pieceCount - 1]))) { + caretX += cursorXIntegerOffset; + + if (selection->snapCaretToInsets && selection->caret0 == selection->caret1) { + EsRectangle insets = EsPainterBoundsInset(painter); + // EsPrint("%d, %d, %d\n", caretX + bounds.l, insets.l, insets.r); + + if (caretX >= insets.l - 1 && caretX <= insets.l + 1) { + caretX = insets.l; + } else if (caretX >= insets.r - 2 && caretX <= insets.r) { + caretX = insets.r - 1; + } + } + + int caretWidth = theming.scale; // TODO Make this a system constant. + EsDrawInvert(painter, ES_RECT_4(caretX, caretX + caretWidth, caretY, selectionBackgroundBottom)); + } + + // Draw decorations. + + { + int32_t thickness = piece->style->size / 15 + 1; + uint32_t color = piece->style->decorationsColor ?: piece->style->color; + + EsRectangle bounds; + bounds.l = cursorXStart / FREETYPE_UNIT_SCALE + cursorXIntegerOffset; + bounds.r = cursorX / FREETYPE_UNIT_SCALE + cursorXIntegerOffset; + + if (piece->style->decorations & ES_TEXT_DECORATION_STRIKE_THROUGH) { + int32_t center = cursorY + (line->ascent + line->descent) / FREETYPE_UNIT_SCALE / 2; + bounds.t = center - thickness / 2 + 1; + bounds.b = center + (thickness + 1) / 2 + 1; + EsDrawBlock(painter, bounds, color); + } + + if (piece->style->decorations & ES_TEXT_DECORATION_UNDERLINE) { + int32_t baseline = cursorY + line->ascent / FREETYPE_UNIT_SCALE; + bounds.t = baseline + thickness; + bounds.b = baseline + thickness * 2; + EsDrawBlock(painter, bounds, color); + } + } +} + +void EsDrawText(EsPainter *painter, EsTextPlan *plan, EsRectangle bounds, EsRectangle *_clip, EsTextSelection *_selection) { + EsMessageMutexCheck(); + + if (!plan) return; + + // EsPrint("EsDrawText... '%s' in %R\n", plan->textRuns[plan->textRunCount].offset, plan->string, bounds); + + // TODO Underlined text. + // TODO Inline images and icons. + + // Work out the selection we should display. + + EsTextSelection selection = {}; + if (_selection) selection = *_selection; + uintptr_t caret = selection.caret1; + + if (selection.caret0 > selection.caret1) { + int swap = selection.caret1; + selection.caret1 = selection.caret0; + selection.caret0 = swap; + } else if (!_selection) { + selection.hideCaret = true; + } + + // Calculate the area we're drawing into. + + int32_t maximumLineWidth = Width(bounds), maximumHeight = Height(bounds); + EsRectangle oldClip = painter->clip; + if (_clip) EsRectangleClip(*_clip, painter->clip, &painter->clip); + int32_t cursorY = (plan->properties.flags & ES_TEXT_V_CENTER) ? (maximumHeight - EsTextPlanGetHeight(plan)) / 2 + : (plan->properties.flags & ES_TEXT_V_BOTTOM) ? maximumHeight - EsTextPlanGetHeight(plan) : 0; + + // Iterate through each line. + + for (uintptr_t i = 0; i < plan->lines.Length(); i++) { + TextLine *line = &plan->lines[i]; + + int32_t cursorX = (plan->properties.flags & ES_TEXT_H_CENTER) ? ((maximumLineWidth * FREETYPE_UNIT_SCALE - line->width) / 2) + : (plan->properties.flags & ES_TEXT_H_RIGHT) ? (maximumLineWidth * FREETYPE_UNIT_SCALE - line->width) : 0; + + int32_t selectionBackgroundBottom; + + if (plan->lines.Length() == 1 && (plan->properties.flags & ES_TEXT_V_CENTER)) { + // If this is a single, centered line, make sure that the selection background bottom edge + // is the same distance from the destination bounds as it is for the top edge. + selectionBackgroundBottom = bounds.b - cursorY; + } else { + selectionBackgroundBottom = cursorY + bounds.t + (line->ascent + line->descent + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE; + } + + // Draw each text piece on the line. + + for (uintptr_t j = 0; j < line->pieceCount; j++) { + TextPiece *piece = &plan->pieces[line->pieceOffset + j]; + DrawTextPiece(painter, plan, piece, line, cursorX + bounds.l * FREETYPE_UNIT_SCALE, cursorY + bounds.t, &selection, caret, selectionBackgroundBottom); + cursorX += piece->width; + } + + if (line->hasEllipsis) { + TextPiece *piece = &plan->pieces[line->ellipsisPieceIndex]; + DrawTextPiece(painter, plan, piece, line, cursorX + bounds.l * FREETYPE_UNIT_SCALE, cursorY + bounds.t, &selection, caret, selectionBackgroundBottom); + cursorX += piece->width; + } + + cursorY += (line->ascent + line->descent + FREETYPE_UNIT_SCALE / 2) / FREETYPE_UNIT_SCALE; + } + + // Destroy the plan if it is single use. + + if (plan->singleUse) { + plan->singleUse = false; + EsTextPlanDestroy(plan); + } + + painter->clip = oldClip; +} + +#elif defined(TEXT_ELEMENTS) + +// --------------------------------- Markup parsing. + +void EsRichTextParse(const char *inString, ptrdiff_t inStringBytes, + char **outString, EsTextRun **outTextRuns, size_t *outTextRunCount, + EsTextStyle *baseStyle) { + if (inStringBytes == -1) { + inStringBytes = EsCStringLength(inString); + } + + // Step 1: Count the number of runs, and the number of bytes in the actual string. + + size_t textRunCount = 1; + size_t stringBytes = 0; + + for (ptrdiff_t i = 0; i < inStringBytes; i++) { + if (inString[i] == '\a') { + for (; i < inStringBytes; i++) { + if (inString[i] == ']') { + break; + } + } + + textRunCount++; + } else { + stringBytes++; + } + } + + // Step 2: Allocate the string and text runs array. + + char *string = (char *) EsHeapAllocate(stringBytes, false); + EsTextRun *textRuns = (EsTextRun *) EsHeapAllocate((textRunCount + 1) * sizeof(EsTextRun), false); + + textRuns[0].style = *baseStyle; + textRuns[0].offset = 0; + textRuns[textRunCount].offset = stringBytes; + + // Step 3: Copy the information. + + uintptr_t textRunIndex = 1; + uintptr_t stringIndex = 0; + + for (ptrdiff_t i = 0; i < inStringBytes; i++) { + if (inString[i] == '\a') { + EsTextRun *textRun = textRuns + textRunIndex; + textRun->offset = stringIndex; + textRun->style = *baseStyle; + + for (; i < inStringBytes; i++) { + char c = inString[i]; + + if (c == ']') { + break; + } else if (c == 'w' /* weight */) { + i++; if (i >= inStringBytes || inString[i] == ']') goto parsedFormat; + textRun->style.font.weight = inString[i] - '0'; + } else if (c == 'i' /* italic */) { + textRun->style.font.italic = true; + } else if (c == 's' /* size */) { + textRun->style.size = 0; + + while (true) { + i++; if (i >= inStringBytes || inString[i] == ']') goto parsedFormat; + if (inString[i] < '0' || inString[i] > '9') { i--; break; } + textRun->style.size *= 10; + textRun->style.size += inString[i] - '0'; + } + } else if (c == 'm' /* monospaced */) { + textRun->style.font.family = ES_FONT_MONOSPACED; + } else if (c == '-' /* strike-through */) { + textRun->style.decorations |= ES_TEXT_DECORATION_STRIKE_THROUGH; + } else if (c == '_' /* underline */) { + textRun->style.decorations |= ES_TEXT_DECORATION_UNDERLINE; + } else if (c == '2' /* secondary color */) { + textRun->style.color = GetConstantNumber("textSecondary"); + } + } + + parsedFormat:; + textRunIndex++; + } else { + string[stringIndex++] = inString[i]; + } + } + + EsAssert(textRunIndex == textRunCount && stringIndex == stringBytes); + + // Step 4: Return the parsed information. + + *outString = string; + *outTextRuns = textRuns; + *outTextRunCount = textRunCount; +} + +const char *const keywords_a[] = { "auto", nullptr }; +const char *const keywords_b[] = { "bool", "break", nullptr }; +const char *const keywords_c[] = { "case", "char", "const", "continue", nullptr }; +const char *const keywords_d[] = { "default", "do", "double", nullptr }; +const char *const keywords_e[] = { "else", "enum", "extern", nullptr }; +const char *const keywords_f[] = { "float", "for", nullptr }; +const char *const keywords_g[] = { "goto", nullptr }; +const char *const keywords_h[] = { nullptr }; +const char *const keywords_i[] = { "if", "inline", "int", "int16_t", "int32_t", "int64_t", "int8_t", "intptr_t", nullptr }; +const char *const keywords_j[] = { nullptr }; +const char *const keywords_k[] = { nullptr }; +const char *const keywords_l[] = { "long", nullptr }; +const char *const keywords_m[] = { nullptr }; +const char *const keywords_n[] = { nullptr }; +const char *const keywords_o[] = { nullptr }; +const char *const keywords_p[] = { nullptr }; +const char *const keywords_q[] = { nullptr }; +const char *const keywords_r[] = { "register", "restrict", "return", nullptr }; +const char *const keywords_s[] = { "short", "signed", "sizeof", "static", "struct", "switch", nullptr }; +const char *const keywords_t[] = { "typedef", nullptr }; +const char *const keywords_u[] = { "uint16_t", "uint32_t", "uint64_t", "uint8_t", "uintptr_t", "union", "unsigned", nullptr }; +const char *const keywords_v[] = { "void", "volatile", nullptr }; +const char *const keywords_w[] = { "while", nullptr }; +const char *const keywords_x[] = { nullptr }; +const char *const keywords_y[] = { nullptr }; +const char *const keywords_z[] = { nullptr }; + +const char *const *const keywords[] = { + keywords_a, keywords_b, keywords_c, keywords_d, + keywords_e, keywords_f, keywords_g, keywords_h, + keywords_i, keywords_j, keywords_k, keywords_l, + keywords_m, keywords_n, keywords_o, keywords_p, + keywords_q, keywords_r, keywords_s, keywords_t, + keywords_u, keywords_v, keywords_w, keywords_x, + keywords_y, keywords_z, +}; + +bool CharIsAlphaOrDigitOrUnderscore(char c) { + return EsCRTisalpha(c) || EsCRTisdigit(c) || c == '_'; +} + +Array TextApplySyntaxHighlighting(const EsTextStyle *baseStyle, int language, uint32_t *colors, Array runs, const char *string, size_t bytes) { + // TODO Make these colors customizable! + // TODO Highlight keywords. + + int lexState = 0; + bool inComment = false, inIdentifier = false, inChar = false, startedString = false; + bool seenEquals = false; + uint32_t last = 0; + + for (uintptr_t index = 0; index < bytes; index++) { + char c = index >= bytes ? 0 : string[index]; + char c1 = index >= bytes - 1 ? 0 : string[index + 1]; + last <<= 8; + last |= c; + + if (c == '\n') { + lexState = 0; + inComment = false, inIdentifier = false, inChar = false, startedString = false; + seenEquals = false; + last = 0; + } + + if (language == ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C) { + if (lexState == 4) { + lexState = 0; + } else if (lexState == 1) { + if ((last & 0xFF0000) == ('*' << 16) && (last & 0xFF00) == ('/' << 8) && inComment) { + lexState = 0, inComment = false; + } + } else if (lexState == 3 || lexState == 6) { + if (!CharIsAlphaOrDigitOrUnderscore(c)) { + lexState = 0; + } + } else if (lexState == 2) { + if (!startedString) { + if (!inChar && ((last >> 8) & 0xFF) == '"' && ((last >> 16) & 0xFF) != '\\') { + lexState = 0; + } else if (inChar && ((last >> 8) & 0xFF) == '\'' && ((last >> 16) & 0xFF) != '\\') { + lexState = 0; + } + } + + startedString = false; + } + + if (lexState == 0) { + if (c == '#') { + lexState = 5; + } else if (c == '/' && c1 == '/') { + lexState = 1; + } else if (c == '/' && c1 == '*') { + lexState = 1, inComment = true; + } else if (c == '"') { + lexState = 2; + inChar = false; + startedString = true; + } else if (c == '\'') { + lexState = 2; + inChar = true; + startedString = true; + } else if (EsCRTisdigit(c) && !inIdentifier) { + lexState = 3; + } else if (!CharIsAlphaOrDigitOrUnderscore(c)) { + lexState = 4; + inIdentifier = false; + } else { + inIdentifier = true; + + if (c >= 'a' && c <= 'z' && (!index || !CharIsAlphaOrDigitOrUnderscore(string[index - 1]))) { + const char *const *k = keywords[c - 'a']; + + for (int i = 0; k[i]; i++) { + int j = 0; + + for (; k[i][j]; j++) { + if (index + j >= bytes || string[index + j] != k[i][j]) { + goto next; + } + } + + if (index + j == bytes || !CharIsAlphaOrDigitOrUnderscore(string[index + j])) { + lexState = 6; + } + + next:; + } + } + } + } + } else if (language == ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI) { + if (c == ';' && !index) { + lexState = 1; + } else if (c == '[' && !index) { + lexState = 6; + } else if ((c == ' ' || c == ']') && lexState == 5) { + lexState = 6; + } else if (c == '=' && !seenEquals) { + seenEquals = true; + lexState = 4; + } else if (c == '@' && lexState == 6) { + lexState = 5; + } else if (seenEquals && lexState == 4 && EsCRTisdigit(c)) { + lexState = 3; + } else if (seenEquals && lexState == 4) { + lexState = 2; + } + } + + if (!runs.Length() || runs.Last().style.color != colors[lexState + 1]) { + EsTextRun run = {}; + run.offset = index; + run.style = *baseStyle; + run.style.color = colors[lexState + 1]; + runs.Add(run); + } + } + + EsTextRun run = {}; + run.offset = bytes; + runs.Add(run); + + return runs; +} + +// --------------------------------- Textboxes. + +// TODO Caret blinking. +// TODO Wrapped lines. +// TODO Unicode grapheme/word boundaries. +// TODO Selecting lines with the margin. + +struct DocumentLine { + char *GetBuffer(EsTextbox *textbox); + + int32_t lengthBytes, + lengthWidth, + height, + yPosition, + offset; +}; + +struct TextboxVisibleLine { + int32_t yPosition; +}; + +struct TextboxCaret { + int32_t byte, // Relative to the start of the line. + line; +}; + +struct EsTextbox : EsElement { + ScrollPane scroll; + + char *data; // Call TextboxSetActiveLine(textbox, -1) to access this. + uintptr_t dataAllocated; + int32_t dataBytes; + + bool editing; + char *editStartContent; + int32_t editStartContentBytes; + + EsUICallbackFunction overlayCallback; + EsGeneric overlayData; + + char *activeLine; + uintptr_t activeLineAllocated; + int32_t activeLineIndex, activeLineStart, activeLineOldBytes, activeLineBytes; + + int32_t longestLine, longestLineWidth; // To set the horizontal scrollbar's size. + + TextboxCaret carets[2]; // carets[1] is the actual caret; carets[0] is the selection anchor. + TextboxCaret wordSelectionAnchor, wordSelectionAnchor2; + + Array lines; + Array visibleLines; + int32_t firstVisibleLine; + + int verticalMotionHorizontalDepth; + int oldHorizontalScroll; + + EsUndoManager *undo; + EsUndoManager localUndo; + + EsElement *margin; + + EsRectangle borders, insets; + EsTextStyle textStyle; + + uint32_t syntaxHighlightingLanguage; + uint32_t syntaxHighlightingColors[8]; + + bool inRightClickDrag; + + // For smart context menus: + bool colorUppercase; +}; + +#define MOVE_CARET_SINGLE (2) +#define MOVE_CARET_WORD (3) +#define MOVE_CARET_LINE (4) +#define MOVE_CARET_VERTICAL (5) +#define MOVE_CARET_ALL (6) + +#define MOVE_CARET_BACKWARDS (false) +#define MOVE_CARET_FORWARDS (true) + +void TextboxBufferResize(void **array, uintptr_t *allocated, uintptr_t needed, uintptr_t itemSize) { + if (*allocated >= needed) { + return; + } + + uintptr_t oldAllocated = *allocated; + void *oldArray = *array; + + uintptr_t newAllocated = oldAllocated * 2; + if (newAllocated < needed) newAllocated = needed + 16; + void *newArray = EsHeapAllocate(newAllocated * itemSize, false); + + EsMemoryCopy(newArray, oldArray, oldAllocated * itemSize); + EsHeapFree(oldArray); + + *allocated = newAllocated; + *array = newArray; +} + +bool IsScancodeNonTypeable(unsigned scancode) { + switch (scancode) { + case ES_SCANCODE_CAPS_LOCK: + case ES_SCANCODE_SCROLL_LOCK: + case ES_SCANCODE_NUM_LOCK: + case ES_SCANCODE_LEFT_SHIFT: + case ES_SCANCODE_LEFT_CTRL: + case ES_SCANCODE_LEFT_ALT: + case ES_SCANCODE_LEFT_FLAG: + case ES_SCANCODE_RIGHT_SHIFT: + case ES_SCANCODE_RIGHT_CTRL: + case ES_SCANCODE_RIGHT_ALT: + case ES_SCANCODE_PAUSE: + case ES_SCANCODE_CONTEXT_MENU: + case ES_SCANCODE_PRINT_SCREEN: + case ES_SCANCODE_F1: + case ES_SCANCODE_F2: + case ES_SCANCODE_F3: + case ES_SCANCODE_F4: + case ES_SCANCODE_F5: + case ES_SCANCODE_F6: + case ES_SCANCODE_F7: + case ES_SCANCODE_F8: + case ES_SCANCODE_F9: + case ES_SCANCODE_F10: + case ES_SCANCODE_F11: + case ES_SCANCODE_F12: + case ES_SCANCODE_ACPI_POWER: + case ES_SCANCODE_ACPI_SLEEP: + case ES_SCANCODE_ACPI_WAKE: + case ES_SCANCODE_MM_NEXT: + case ES_SCANCODE_MM_PREVIOUS: + case ES_SCANCODE_MM_STOP: + case ES_SCANCODE_MM_PAUSE: + case ES_SCANCODE_MM_MUTE: + case ES_SCANCODE_MM_QUIETER: + case ES_SCANCODE_MM_LOUDER: + case ES_SCANCODE_MM_SELECT: + case ES_SCANCODE_MM_EMAIL: + case ES_SCANCODE_MM_CALC: + case ES_SCANCODE_MM_FILES: + case ES_SCANCODE_WWW_SEARCH: + case ES_SCANCODE_WWW_HOME: + case ES_SCANCODE_WWW_BACK: + case ES_SCANCODE_WWW_FORWARD: + case ES_SCANCODE_WWW_STOP: + case ES_SCANCODE_WWW_REFRESH: + case ES_SCANCODE_WWW_STARRED: + return true; + + default: + return false; + } +} + +void ConvertScancodeToCharacter(unsigned scancode, int *_ic, int *_isc, bool enableTabs, bool enableNewline) { + int ic = -1, isc = -1; + + switch (scancode) { + case ES_SCANCODE_A: ic = 'a'; isc = 'A'; break; + case ES_SCANCODE_B: ic = 'b'; isc = 'B'; break; + case ES_SCANCODE_C: ic = 'c'; isc = 'C'; break; + case ES_SCANCODE_D: ic = 'd'; isc = 'D'; break; + case ES_SCANCODE_E: ic = 'e'; isc = 'E'; break; + case ES_SCANCODE_F: ic = 'f'; isc = 'F'; break; + case ES_SCANCODE_G: ic = 'g'; isc = 'G'; break; + case ES_SCANCODE_H: ic = 'h'; isc = 'H'; break; + case ES_SCANCODE_I: ic = 'i'; isc = 'I'; break; + case ES_SCANCODE_J: ic = 'j'; isc = 'J'; break; + case ES_SCANCODE_K: ic = 'k'; isc = 'K'; break; + case ES_SCANCODE_L: ic = 'l'; isc = 'L'; break; + case ES_SCANCODE_M: ic = 'm'; isc = 'M'; break; + case ES_SCANCODE_N: ic = 'n'; isc = 'N'; break; + case ES_SCANCODE_O: ic = 'o'; isc = 'O'; break; + case ES_SCANCODE_P: ic = 'p'; isc = 'P'; break; + case ES_SCANCODE_Q: ic = 'q'; isc = 'Q'; break; + case ES_SCANCODE_R: ic = 'r'; isc = 'R'; break; + case ES_SCANCODE_S: ic = 's'; isc = 'S'; break; + case ES_SCANCODE_T: ic = 't'; isc = 'T'; break; + case ES_SCANCODE_U: ic = 'u'; isc = 'U'; break; + case ES_SCANCODE_V: ic = 'v'; isc = 'V'; break; + case ES_SCANCODE_W: ic = 'w'; isc = 'W'; break; + case ES_SCANCODE_X: ic = 'x'; isc = 'X'; break; + case ES_SCANCODE_Y: ic = 'y'; isc = 'Y'; break; + case ES_SCANCODE_Z: ic = 'z'; isc = 'Z'; break; + case ES_SCANCODE_0: ic = '0'; isc = ')'; break; + case ES_SCANCODE_1: ic = '1'; isc = '!'; break; + case ES_SCANCODE_2: ic = '2'; isc = '@'; break; + case ES_SCANCODE_3: ic = '3'; isc = '#'; break; + case ES_SCANCODE_4: ic = '4'; isc = '$'; break; + case ES_SCANCODE_5: ic = '5'; isc = '%'; break; + case ES_SCANCODE_6: ic = '6'; isc = '^'; break; + case ES_SCANCODE_7: ic = '7'; isc = '&'; break; + case ES_SCANCODE_8: ic = '8'; isc = '*'; break; + case ES_SCANCODE_9: ic = '9'; isc = '('; break; + case ES_SCANCODE_SLASH: ic = '/'; isc = '?'; break; + case ES_SCANCODE_PUNCTUATION_1: ic = '\\'; isc = '|'; break; + case ES_SCANCODE_LEFT_BRACE: ic = '['; isc = '{'; break; + case ES_SCANCODE_RIGHT_BRACE: ic = ']'; isc = '}'; break; + case ES_SCANCODE_EQUALS: ic = '='; isc = '+'; break; + case ES_SCANCODE_PUNCTUATION_5: ic = '`'; isc = '~'; break; + case ES_SCANCODE_HYPHEN: ic = '-'; isc = '_'; break; + case ES_SCANCODE_PUNCTUATION_3: ic = ';'; isc = ':'; break; + case ES_SCANCODE_PUNCTUATION_4: ic = '\''; isc = '"'; break; + case ES_SCANCODE_COMMA: ic = ','; isc = '<'; break; + case ES_SCANCODE_PERIOD: ic = '.'; isc = '>'; break; + case ES_SCANCODE_SPACE: ic = ' '; isc = ' '; break; + case ES_SCANCODE_ENTER: if (enableNewline) { ic = '\n'; isc = '\n'; } break; + case ES_SCANCODE_TAB: if (enableTabs) { ic = '\t'; isc = '\t'; } break; + } + + *_ic = ic, *_isc = isc; +} + +size_t EsMessageGetInputText(EsMessage *message, char *buffer) { + int ic, isc; + ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, true, true); + + if (message->keyboard.modifiers & ES_MODIFIER_SHIFT) ic = isc; + if (ic == -1) return 0; + + return utf8_encode(ic, buffer); +} + +enum CharacterType { + CHARACTER_INVALID, + CHARACTER_IDENTIFIER, // A-Z, a-z, 0-9, _, >= 0x7F + CHARACTER_WHITESPACE, // space, tab, newline + CHARACTER_OTHER, +}; + +static CharacterType GetCharacterType(int character) { + if ((character >= '0' && character <= '9') + || (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character == '_') + || (character >= 0x80)) { + return CHARACTER_IDENTIFIER; + } + + if (character == '\n' || character == '\t' || character == ' ') { + return CHARACTER_WHITESPACE; + } + + return CHARACTER_OTHER; +} + +int TextboxCompareCarets(const TextboxCaret *left, const TextboxCaret *right) { + if (left->line < right->line) return -1; + if (left->line > right->line) return 1; + if (left->byte < right->byte) return -1; + if (left->byte > right->byte) return 1; + return 0; +} + +void TextboxSetActiveLine(EsTextbox *textbox, int lineIndex) { + if (textbox->activeLineIndex == lineIndex) { + return; + } + + if (lineIndex == -1) { + int32_t lineBytesDelta = textbox->activeLineBytes - textbox->activeLineOldBytes; + + // Step 1: Resize the data buffer to fit the new contents of the line. + + TextboxBufferResize((void **) &textbox->data, &textbox->dataAllocated, textbox->dataBytes + lineBytesDelta, 1); + + // Step 2: Move everything after the old end of the active line to its new position. + + EsMemoryMove(textbox->data + textbox->activeLineStart + textbox->activeLineOldBytes, + textbox->data + textbox->dataBytes, + lineBytesDelta, + false); + textbox->dataBytes += lineBytesDelta; + + // Step 3: Copy the active line back into the data buffer. + + EsMemoryCopy(textbox->data + textbox->activeLineStart, + textbox->activeLine, + textbox->activeLineBytes); + + // Step 4: Update the line byte offsets. + + for (uintptr_t i = textbox->activeLineIndex + 1; i < textbox->lines.Length(); i++) { + textbox->lines[i].offset += lineBytesDelta; + } + } else { + TextboxSetActiveLine(textbox, -1); + + DocumentLine *line = &textbox->lines[lineIndex]; + + TextboxBufferResize((void **) &textbox->activeLine, &textbox->activeLineAllocated, (textbox->activeLineBytes = line->lengthBytes), 1); + EsMemoryCopy(textbox->activeLine, textbox->data + line->offset, textbox->activeLineBytes); + + textbox->activeLineStart = line->offset; + textbox->activeLineOldBytes = textbox->activeLineBytes; + } + + textbox->activeLineIndex = lineIndex; +} + +void EsTextboxStartEdit(EsTextbox *textbox) { + textbox->state &= ~UI_STATE_LOST_STRONG_FOCUS; + + if ((textbox->flags & ES_TEXTBOX_EDIT_BASED) && !textbox->editing) { + EsMessage m = { ES_MSG_TEXTBOX_EDIT_START }; + + if (0 == EsMessageSend(textbox, &m)) { + EsTextboxSelectAll(textbox); + } + + if (textbox->state & UI_STATE_DESTROYING) { + return; + } + + textbox->editing = true; // Update this after sending the message so overlays can receive it. + TextboxSetActiveLine(textbox, -1); + textbox->editStartContent = (char *) EsHeapAllocate(textbox->dataBytes, false); + textbox->editStartContentBytes = textbox->dataBytes; + EsMemoryCopy(textbox->editStartContent, textbox->data, textbox->editStartContentBytes); + textbox->Repaint(true); + } +} + +void TextboxEndEdit(EsTextbox *textbox, bool reject) { + if ((textbox->flags & ES_TEXTBOX_EDIT_BASED) && textbox->editing) { + textbox->editing = false; + EsMessage m = { ES_MSG_TEXTBOX_EDIT_END }; + m.endEdit.rejected = reject; + + if (reject || ES_REJECTED == EsMessageSend(textbox, &m)) { + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, textbox->editStartContent, textbox->editStartContentBytes); + TextboxSetActiveLine(textbox, -1); + if (reject) EsMessageSend(textbox, &m); + } + + if (textbox->state & UI_STATE_DESTROYING) { + return; + } + + EsTextboxSetSelection(textbox, 0, 0, 0, 0); + EsHeapFree(textbox->editStartContent); + textbox->editStartContent = nullptr; + textbox->scroll.SetX(0); + textbox->Repaint(true); + } +} + +void TextboxUpdateCommands(EsTextbox *textbox, bool caretsMovedOnly) { + if (~textbox->state & UI_STATE_FOCUSED) { + return; + } + + EsCommand *command; + + bool selectionEmpty = !TextboxCompareCarets(textbox->carets + 0, textbox->carets + 1) && textbox->editing; + + command = EsCommandByID(textbox->instance, ES_COMMAND_DELETE); + command->data = textbox; + EsCommandSetDisabled(command, selectionEmpty); + + EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) { + EsTextbox *textbox = (EsTextbox *) command->data.p; + EsTextboxInsert(textbox, "", 0, true); + }); + + command = EsCommandByID(textbox->instance, ES_COMMAND_COPY); + command->data = textbox; + EsCommandSetDisabled(command, selectionEmpty); + + EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) { + EsTextbox *textbox = (EsTextbox *) command->data.p; + size_t textBytes; + char *text = EsTextboxGetContents(textbox, &textBytes, textbox->editing ? ES_TEXTBOX_GET_CONTENTS_SELECTED_ONLY : ES_FLAGS_DEFAULT); + EsError error = EsClipboardAddText(ES_CLIPBOARD_PRIMARY, text, textBytes); + EsHeapFree(text); + + EsRectangle bounds = EsElementGetWindowBounds(textbox); + int32_t x = (bounds.l + bounds.r) / 2; + int32_t y = (bounds.t + bounds.b) / 2; // TODO Position this in the middle of the selection. + + if (error == ES_SUCCESS) { + EsAnnouncementShow(textbox->window, ES_FLAGS_DEFAULT, x, y, INTERFACE_STRING(CommonAnnouncementTextCopied)); + } else if (error == ES_ERROR_INSUFFICIENT_RESOURCES) { + EsAnnouncementShow(textbox->window, ES_FLAGS_DEFAULT, x, y, INTERFACE_STRING(CommonAnnouncementCopyErrorResources)); + } else { + EsAnnouncementShow(textbox->window, ES_FLAGS_DEFAULT, x, y, INTERFACE_STRING(CommonAnnouncementCopyErrorOther)); + } + }); + + command = EsCommandByID(textbox->instance, ES_COMMAND_CUT); + command->data = textbox; + EsCommandSetDisabled(command, selectionEmpty); + + EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) { + EsTextbox *textbox = (EsTextbox *) command->data.p; + size_t textBytes; + char *text = EsTextboxGetContents(textbox, &textBytes, textbox->editing ? ES_TEXTBOX_GET_CONTENTS_SELECTED_ONLY : ES_FLAGS_DEFAULT); + EsClipboardAddText(ES_CLIPBOARD_PRIMARY, text, textBytes); + EsHeapFree(text); + EsTextboxStartEdit(textbox); + EsTextboxInsert(textbox, "", 0, true); + }); + + if (!caretsMovedOnly) { + EsInstanceSetActiveUndoManager(textbox->instance, textbox->undo); + + command = EsCommandByID(textbox->instance, ES_COMMAND_SELECT_ALL); + command->data = textbox; + EsCommandSetDisabled(command, !(textbox->lines.Length() > 1 || textbox->lines[0].lengthBytes)); + + EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) { + EsTextboxSelectAll((EsTextbox *) command->data.p); + }); + + command = EsCommandByID(textbox->instance, ES_COMMAND_PASTE); + command->data = textbox; + EsCommandSetDisabled(command, !EsClipboardHasText(ES_CLIPBOARD_PRIMARY)); + + EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) { + EsTextbox *textbox = (EsTextbox *) command->data.p; + + size_t textBytes = 0; + char *text = EsClipboardReadText(ES_CLIPBOARD_PRIMARY, &textBytes); + EsTextboxInsert(textbox, text, textBytes, true); + EsTextboxEnsureCaretVisible(textbox); + }); + } +} + +char *DocumentLine::GetBuffer(EsTextbox *textbox) { + if (textbox->activeLineIndex == this - textbox->lines.array) { + return textbox->activeLine; + } else { + return textbox->data + offset; + } +} + +void TextboxFindLongestLine(EsTextbox *textbox) { + if (textbox->longestLine == -1) { + textbox->longestLine = 0; + textbox->longestLineWidth = textbox->lines[0].lengthWidth; + + for (uintptr_t i = 1; i < textbox->lines.Length(); i++) { + int32_t width = textbox->lines[i].lengthWidth; + + if (width > textbox->longestLineWidth) { + textbox->longestLine = i, textbox->longestLineWidth = width; + } + } + } +} + +TextboxVisibleLine *TextboxGetVisibleLine(EsTextbox *textbox, int32_t documentLineIndex) { + return textbox->firstVisibleLine > documentLineIndex + || textbox->firstVisibleLine + (int32_t) textbox->visibleLines.Length() <= documentLineIndex + ? nullptr : &textbox->visibleLines[documentLineIndex - textbox->firstVisibleLine]; +} + +void EsTextboxEnsureCaretVisible(EsTextbox *textbox, bool verticallyCenter) { + TextboxCaret caret = textbox->carets[1]; + EsRectangle bounds = textbox->GetBounds(); + + { + DocumentLine *line = &textbox->lines[caret.line]; + int caretY = line->yPosition + textbox->insets.t; + + int scrollY = textbox->scroll.position[1]; + int viewportHeight = bounds.b; + caretY -= scrollY; + + if (viewportHeight > 0) { + if (verticallyCenter) { + scrollY += caretY - viewportHeight / 2; + } else { + if (caretY < textbox->insets.t) { + scrollY += caretY - textbox->insets.t; + } else if (caretY + line->height > viewportHeight - textbox->insets.b) { + scrollY += caretY + line->height - viewportHeight + textbox->insets.b; + } + } + + textbox->scroll.SetY(scrollY); + } + } + + TextboxVisibleLine *visibleLine = TextboxGetVisibleLine(textbox, caret.line); + + if (visibleLine) { + DocumentLine *line = &textbox->lines[caret.line]; + int scrollX = textbox->scroll.position[0]; + int viewportWidth = bounds.r; + int caretX = TextGetPartialStringWidth(&textbox->textStyle, + line->GetBuffer(textbox), line->lengthBytes, caret.byte) - scrollX + textbox->insets.l; + + if (caretX < textbox->insets.l) { + scrollX += caretX - textbox->insets.l; + } else if (caretX + 1 > viewportWidth - textbox->insets.r) { + scrollX += caretX + 1 - viewportWidth + textbox->insets.r; + } + + textbox->scroll.SetX(scrollX); + } +} + +bool TextboxMoveCaret(EsTextbox *textbox, TextboxCaret *caret, bool right, int moveType, bool strongWhitespace = false) { + TextboxCaret old = *caret; + EsDefer(TextboxUpdateCommands(textbox, true)); + + if (moveType == MOVE_CARET_LINE) { + caret->byte = right ? textbox->lines[caret->line].lengthBytes : 0; + } else if (moveType == MOVE_CARET_ALL) { + caret->line = right ? textbox->lines.Length() - 1 : 0; + caret->byte = right ? textbox->lines[caret->line].lengthBytes : 0; + } else if (moveType == MOVE_CARET_VERTICAL) { + if ((right && caret->line + 1 == (int32_t) textbox->lines.Length()) || (!right && !caret->line)) { + return false; + } + + if (textbox->verticalMotionHorizontalDepth == -1) { + textbox->verticalMotionHorizontalDepth = TextGetPartialStringWidth(&textbox->textStyle, + textbox->lines[caret->line].GetBuffer(textbox), textbox->lines[caret->line].lengthBytes, caret->byte); + } + + if (right) caret->line++; else caret->line--; + caret->byte = 0; + + DocumentLine *line = &textbox->lines[caret->line]; + int pointX = textbox->verticalMotionHorizontalDepth ? textbox->verticalMotionHorizontalDepth - 1 : 0; + ptrdiff_t result = TextGetCharacterAtPoint(&textbox->textStyle, + line->GetBuffer(textbox), line->lengthBytes, &pointX, ES_TEXT_GET_CHARACTER_AT_POINT_MIDDLE); + caret->byte = result == -1 ? line->lengthBytes : result; + } else { + CharacterType type = CHARACTER_INVALID; + char *currentLineBuffer = textbox->lines[caret->line].GetBuffer(textbox); + if (moveType == MOVE_CARET_WORD && right) goto checkCharacterType; + + while (true) { + if (!right) { + if (caret->byte || caret->line) { + if (caret->byte) { + caret->byte = utf8_retreat(currentLineBuffer + caret->byte) - currentLineBuffer; + } else { + caret->byte = textbox->lines[--caret->line].lengthBytes; + currentLineBuffer = textbox->lines[caret->line].GetBuffer(textbox); + } + } else { + break; // We cannot move any further left. + } + } else { + if (caret->line < (int32_t) textbox->lines.Length() - 1 || caret->byte < textbox->lines[caret->line].lengthBytes) { + if (caret->byte < textbox->lines[caret->line].lengthBytes) { + caret->byte = utf8_advance(currentLineBuffer + caret->byte) - currentLineBuffer; + } else { + caret->line++; + caret->byte = 0; + currentLineBuffer = textbox->lines[caret->line].GetBuffer(textbox); + } + } else { + break; // We cannot move any further right. + } + } + + if (moveType == MOVE_CARET_SINGLE) { + break; + } + + checkCharacterType:; + + int character; + + if (caret->byte == textbox->lines[caret->line].lengthBytes) { + character = '\n'; + } else { + character = utf8_value(currentLineBuffer + caret->byte); + } + + CharacterType newType = GetCharacterType(character); + + if (type == CHARACTER_INVALID) { + if (newType != CHARACTER_WHITESPACE || strongWhitespace) { + type = newType; + } + } else { + if (newType != type) { + if (!right) { + // We've gone too far. + TextboxMoveCaret(textbox, caret, true, MOVE_CARET_SINGLE); + } + + break; + } + } + } + } + + return caret->line != old.line; +} + +void EsTextboxMoveCaretRelative(EsTextbox *textbox, uint32_t flags) { + if (~flags & ES_TEXTBOX_MOVE_CARET_SECOND_ONLY) { + TextboxMoveCaret(textbox, &textbox->carets[0], ~flags & ES_TEXTBOX_MOVE_CARET_BACKWARDS, + flags & 0xFF, flags & ES_TEXTBOX_MOVE_CARET_STRONG_WHITESPACE); + } + + if (~flags & ES_TEXTBOX_MOVE_CARET_FIRST_ONLY) { + TextboxMoveCaret(textbox, &textbox->carets[1], ~flags & ES_TEXTBOX_MOVE_CARET_BACKWARDS, + flags & 0xFF, flags & ES_TEXTBOX_MOVE_CARET_STRONG_WHITESPACE); + } +} + +void TextboxRepaintLine(EsTextbox *textbox, int line) { + if (line == -1 || (~textbox->flags & ES_TEXTBOX_MULTILINE)) { + textbox->Repaint(true); + } else { + EsRectangle borders = textbox->borders; + int topInset = textbox->insets.t; + + TextboxVisibleLine *visibleLine = TextboxGetVisibleLine(textbox, line); + + if (visibleLine) { + EsRectangle bounds = textbox->GetBounds(); + EsRectangle lineBounds = ES_RECT_4(bounds.l + borders.l, bounds.r - borders.r, + visibleLine->yPosition + topInset - 1 - textbox->scroll.position[1], + visibleLine->yPosition + topInset + textbox->lines[line].height - textbox->scroll.position[1]); + // EsPrint("textbox bounds %R; line bounds %R\n", bounds); + textbox->Repaint(false, lineBounds); + } + } +} + +void TextboxSetHorizontalScroll(EsTextbox *textbox, int scroll) { + textbox->Repaint(true); + textbox->oldHorizontalScroll = scroll; +} + +void TextboxRefreshVisibleLines(EsTextbox *textbox, bool repaint = true) { + if (textbox->visibleLines.Length()) { + textbox->visibleLines.SetLength(0); + } + + int scrollX = textbox->scroll.position[0], scrollY = textbox->scroll.position[1]; + EsRectangle bounds = textbox->GetBounds(); + + int32_t low = 0, high = textbox->lines.Length() - 1, target = scrollY - textbox->insets.t; + + while (low != high) { + int32_t middle = (low + high) / 2; + int32_t position = textbox->lines[middle].yPosition; + + if (position < target && low != middle) low = middle; + else if (position > target && high != middle) high = middle; + else break; + } + + textbox->firstVisibleLine = (low + high) / 2; + if (textbox->firstVisibleLine) textbox->firstVisibleLine--; + + for (int32_t i = textbox->firstVisibleLine; i < (int32_t) textbox->lines.Length(); i++) { + TextboxVisibleLine line = {}; + line.yPosition = textbox->lines[i].yPosition; + textbox->visibleLines.Add(line); + + if (line.yPosition - scrollY > bounds.b) { + break; + } + } + + bool refreshXLimit = false; + + for (uintptr_t i = 0; i < textbox->visibleLines.Length(); i++) { + DocumentLine *line = &textbox->lines[textbox->firstVisibleLine + i]; + + if (line->lengthWidth != -1) { + continue; + } + + line->lengthWidth = TextGetStringWidth(&textbox->textStyle, + line->GetBuffer(textbox), line->lengthBytes); + + if (textbox->longestLine != -1 && line->lengthWidth > textbox->longestLineWidth) { + textbox->longestLine = textbox->firstVisibleLine + i; + textbox->longestLineWidth = line->lengthWidth; + refreshXLimit = true; + } + } + + if (refreshXLimit) { + textbox->scroll.Refresh(); + EsElementRelayout(textbox); + } + + textbox->scroll.SetX(scrollX); + if (repaint) textbox->Repaint(true); +} + +void TextboxLineCountChangeCleanup(EsTextbox *textbox, int32_t offsetDelta, int32_t startLine) { + for (int32_t i = startLine; i < (int32_t) textbox->lines.Length(); i++) { + DocumentLine *line = &textbox->lines[i], *previous = &textbox->lines[i - 1]; + line->yPosition = previous->yPosition + previous->height; + line->offset += offsetDelta; + } + + TextboxRefreshVisibleLines(textbox); +} + +void EsTextboxMoveCaret(EsTextbox *textbox, int32_t line, int32_t byte) { + EsMessageMutexCheck(); + + textbox->carets[0].line = line; + textbox->carets[0].byte = byte; + textbox->carets[1].line = line; + textbox->carets[1].byte = byte; + textbox->Repaint(true); + TextboxUpdateCommands(textbox, true); +} + +void EsTextboxGetSelection(EsTextbox *textbox, int32_t *fromLine, int32_t *fromByte, int32_t *toLine, int32_t *toByte) { + EsMessageMutexCheck(); + + *fromLine = textbox->carets[0].line; + *fromByte = textbox->carets[0].byte; + *toLine = textbox->carets[1].line; + *toByte = textbox->carets[1].byte; +} + +void EsTextboxSetSelection(EsTextbox *textbox, int32_t fromLine, int32_t fromByte, int32_t toLine, int32_t toByte) { + EsMessageMutexCheck(); + + if (fromByte == -1) fromByte = textbox->lines[fromLine].lengthBytes; + if (toByte == -1) toByte = textbox->lines[toLine].lengthBytes; + if (fromByte < 0 || toByte < 0 || fromByte > textbox->lines[fromLine].lengthBytes || toByte > textbox->lines[toLine].lengthBytes) return; + textbox->carets[0].line = fromLine; + textbox->carets[0].byte = fromByte; + textbox->carets[1].line = toLine; + textbox->carets[1].byte = toByte; + textbox->Repaint(true); + TextboxUpdateCommands(textbox, true); + EsTextboxEnsureCaretVisible(textbox); +} + +void EsTextboxSelectAll(EsTextbox *textbox) { + EsMessageMutexCheck(); + + TextboxMoveCaret(textbox, &textbox->carets[0], false, MOVE_CARET_ALL); + TextboxMoveCaret(textbox, &textbox->carets[1], true, MOVE_CARET_ALL); + EsTextboxEnsureCaretVisible(textbox); + textbox->Repaint(true); +} + +void EsTextboxClear(EsTextbox *textbox, bool sendUpdatedMessage) { + EsMessageMutexCheck(); + + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, "", 0, sendUpdatedMessage); +} + +size_t EsTextboxGetLineLength(EsTextbox *textbox, uintptr_t line) { + EsMessageMutexCheck(); + + return textbox->lines[line].lengthBytes; +} + +struct TextboxUndoItemHeader { + EsTextbox *textbox; + TextboxCaret caretsBefore[2]; + size_t insertBytes; + double timeStampMs; + // Followed by insert string. +}; + +void TextboxUndoItemCallback(const void *item, EsUndoManager *manager, EsMessage *message) { + if (message->type == ES_MSG_UNDO_INVOKE) { + TextboxUndoItemHeader *header = (TextboxUndoItemHeader *) item; + EsTextbox *textbox = header->textbox; + EsAssert(textbox->undo == manager); + TextboxRepaintLine(textbox, textbox->carets[0].line); + TextboxRepaintLine(textbox, textbox->carets[0].line); + textbox->carets[0] = header->caretsBefore[0]; + textbox->carets[1] = header->caretsBefore[1]; + EsTextboxInsert(textbox, (const char *) (header + 1), header->insertBytes, true); + } else if (message->type == ES_MSG_UNDO_CANCEL) { + // Nothing to do. + } +} + +void EsTextboxInsert(EsTextbox *textbox, const char *string, ptrdiff_t stringBytes, bool sendUpdatedMessage) { + EsMessageMutexCheck(); + + // EsPerformanceTimerPush(); + // double measureLineTime = 0; + + if (stringBytes == -1) { + stringBytes = EsCStringLength(string); + } + + TextboxUndoItemHeader *undoItem = nullptr; + size_t undoItemBytes = 0; + + textbox->wordSelectionAnchor = textbox->carets[0]; + textbox->wordSelectionAnchor2 = textbox->carets[1]; + + textbox->verticalMotionHorizontalDepth = -1; + + // ::: Delete the selected text. + + // Step 1: Get the range of text we're deleting. + + TextboxCaret deleteFrom, deleteTo; + int comparison = TextboxCompareCarets(textbox->carets + 0, textbox->carets + 1); + + if (comparison < 0) deleteFrom = textbox->carets[0], deleteTo = textbox->carets[1]; + else if (comparison > 0) deleteFrom = textbox->carets[1], deleteTo = textbox->carets[0]; + + if (comparison) { + textbox->carets[0] = textbox->carets[1] = deleteFrom; + + // Step 2: Calculate the number of bytes we are deleting. + + int32_t deltaBytes; + + if (deleteFrom.line == deleteTo.line) { + deltaBytes = deleteFrom.byte - deleteTo.byte; + } else { + TextboxSetActiveLine(textbox, -1); + + deltaBytes = deleteFrom.byte - deleteTo.byte; + + for (int32_t i = deleteFrom.line; i < deleteTo.line; i++) { + deltaBytes -= textbox->lines[i].lengthBytes; + } + } + + if (textbox->undo) { + // Step 3: Allocate space for an undo item. + + undoItemBytes = sizeof(TextboxUndoItemHeader) - deltaBytes + deleteTo.line - deleteFrom.line; + undoItem = (TextboxUndoItemHeader *) EsHeapAllocate(undoItemBytes, false); + EsMemoryZero(undoItem, sizeof(TextboxUndoItemHeader)); + undoItem->insertBytes = undoItemBytes - sizeof(TextboxUndoItemHeader); + } + + if (deleteFrom.line == deleteTo.line) { + EsAssert(deltaBytes < 0); // Expected deleteTo > deleteFrom. + DocumentLine *line = &textbox->lines[deleteFrom.line]; + TextboxSetActiveLine(textbox, deleteFrom.line); + + // Step 4: Update the width of the line and repaint it. + + line->lengthWidth = TextGetStringWidth(&textbox->textStyle, textbox->activeLine, textbox->activeLineBytes); + TextboxRepaintLine(textbox, deleteFrom.line); + + // Step 5: Update the active line buffer. + + if (undoItem) EsMemoryCopy(undoItem + 1, textbox->activeLine + deleteFrom.byte, -deltaBytes); + EsMemoryMove(textbox->activeLine + deleteTo.byte, textbox->activeLine + line->lengthBytes, deltaBytes, false); + textbox->activeLineBytes += deltaBytes; + line->lengthBytes += deltaBytes; + + // Step 6: Update the longest line. + + if (textbox->longestLine == deleteFrom.line && line->lengthWidth < textbox->longestLineWidth) { + textbox->longestLine = -1; + } + } else { + if (undoItem) { + // Step 4: Copy into the undo item. + + char *position = (char *) (undoItem + 1); + + for (int32_t i = deleteFrom.line; i <= deleteTo.line; i++) { + char *from = textbox->data + textbox->lines[i].offset; + char *to = textbox->data + textbox->lines[i].offset + textbox->lines[i].lengthBytes; + if (i == deleteFrom.line) from += deleteFrom.byte; + if (i == deleteTo.line) to += deleteTo.byte - textbox->lines[i].lengthBytes; + EsMemoryCopy(position, from, to - from); + position += to - from; + if (i != deleteTo.line) *position++ = '\n'; + } + } + + // Step 5: Remove the text from the buffer. + + EsMemoryMove(textbox->data + deleteTo.byte + textbox->lines[deleteTo.line].offset, textbox->data + textbox->dataBytes, deltaBytes, false); + textbox->dataBytes += deltaBytes; + + // Step 6: Merged the joined lines. + + DocumentLine *firstLine = &textbox->lines[deleteFrom.line]; + firstLine->lengthBytes = textbox->lines[deleteTo.line].lengthBytes - deleteTo.byte + deleteFrom.byte; + firstLine->lengthWidth = TextGetStringWidth(&textbox->textStyle, textbox->data + firstLine->offset, firstLine->lengthBytes); + + // Step 7: Remove the deleted lines and update the textbox. + + textbox->lines.DeleteMany(deleteFrom.line + 1, deleteTo.line - deleteFrom.line); + textbox->longestLine = -1; + TextboxLineCountChangeCleanup(textbox, deltaBytes, deleteFrom.line + 1); + } + } else { + if (textbox->undo) { + undoItemBytes = sizeof(TextboxUndoItemHeader); + undoItem = (TextboxUndoItemHeader *) EsHeapAllocate(undoItemBytes, false); + EsMemoryZero(undoItem, sizeof(TextboxUndoItemHeader)); + } + } + + if (undoItem) { + undoItem->caretsBefore[0] = undoItem->caretsBefore[1] = textbox->carets[0]; + } + + // ::: Insert the new text. + + if (!stringBytes) goto done; + + { + TextboxCaret insertionPoint = textbox->carets[0]; + + DocumentLine *line = &textbox->lines[insertionPoint.line]; + int32_t lineByteOffset = line->offset, + offsetIntoLine = insertionPoint.byte, + byteOffset = offsetIntoLine + lineByteOffset; + + // Step 1: Count the number of newlines in the input string. + + uintptr_t position = 0, + newlines = 0, + carriageReturns = 0; + + while (position < (size_t) stringBytes) { + int length; + UTF8_LENGTH_CHAR(string + position, length); + if (length == 0) length = 1; + + if (position + length > (size_t) stringBytes) { + break; + } else if (string[position] == '\n') { + newlines++; + } else if (string[position] == '\r' && position != (size_t) stringBytes - 1 && string[position + 1] == '\n') { + carriageReturns++; + } + + position += length; + } + + size_t bytesToInsert = stringBytes - newlines - carriageReturns; + + if (!newlines || (~textbox->flags & ES_TEXTBOX_MULTILINE)) { + // Step 2: Update the active line buffer. + + TextboxSetActiveLine(textbox, insertionPoint.line); + TextboxBufferResize((void **) &textbox->activeLine, &textbox->activeLineAllocated, (textbox->activeLineBytes += bytesToInsert), 1); + EsMemoryMove(textbox->activeLine + offsetIntoLine, textbox->activeLine + line->lengthBytes, bytesToInsert, false); + + const char *dataToInsert = string; + size_t added = 0; + + for (uintptr_t i = 0; i < newlines + 1; i++) { + const char *end = (const char *) EsCRTmemchr(dataToInsert, '\n', stringBytes - (dataToInsert - string)) ?: string + stringBytes; + bool carriageReturn = end != string && end[-1] == '\r'; + if (carriageReturn) end--; + EsMemoryCopy(textbox->activeLine + offsetIntoLine + added, dataToInsert, end - dataToInsert); + added += end - dataToInsert; + dataToInsert = end + (carriageReturn ? 2 : 1); + } + + EsAssert(added == bytesToInsert); // Added incorrect number of bytes in EsTextboxInsert. + + line->lengthBytes += bytesToInsert; + + // Step 3: Update the carets, line width, and repaint it. + + textbox->carets[0].byte += bytesToInsert; + textbox->carets[1].byte += bytesToInsert; + line->lengthWidth = TextGetStringWidth(&textbox->textStyle, textbox->activeLine, line->lengthBytes); + TextboxRepaintLine(textbox, insertionPoint.line); + + // Step 4: Update the longest line. + + if (textbox->longestLine != -1 && line->lengthWidth > textbox->longestLineWidth) { + textbox->longestLine = insertionPoint.line; + textbox->longestLineWidth = line->lengthWidth; + } + } else { + // Step 2: Make room in the buffer for the contents of the string. + + TextboxSetActiveLine(textbox, -1); + TextboxBufferResize((void **) &textbox->data, &textbox->dataAllocated, textbox->dataBytes + bytesToInsert, 1); + EsMemoryMove(textbox->data + byteOffset, textbox->data + textbox->dataBytes, bytesToInsert, false); + textbox->dataBytes += bytesToInsert; + + // Step 3: Truncate the insertion line. + + int32_t truncation = line->lengthBytes - insertionPoint.byte; + line->lengthBytes = insertionPoint.byte; + + // Step 4: Add the new lines. + + textbox->lines.InsertMany(insertionPoint.line + 1, newlines); + const char *dataToInsert = string; + uintptr_t insertedBytes = 0; + + for (uintptr_t i = 0; i < newlines + 1; i++) { + DocumentLine *line = &textbox->lines[insertionPoint.line + i], *previous = line - 1; + + // Step 4a: Initialise the line. + + if (i) { + EsMemoryZero(line, sizeof(*line)); + line->height = EsTextGetLineHeight(&textbox->textStyle); + line->yPosition = previous->yPosition + previous->height; + line->offset = lineByteOffset + insertedBytes; + } + + // Step 4b: Copy the string data into the line. + + const char *end = (const char *) EsCRTmemchr(dataToInsert, '\n', stringBytes - (dataToInsert - string)) ?: string + stringBytes; + bool carriageReturn = end != string && end[-1] == '\r'; + if (carriageReturn) end--; + EsMemoryCopy(textbox->data + line->offset + line->lengthBytes, dataToInsert, end - dataToInsert); + line->lengthBytes += end - dataToInsert; + insertedBytes += line->lengthBytes; + dataToInsert = end + (carriageReturn ? 2 : 1); + + if (i == newlines) { + line->lengthBytes += truncation; + } + + // Step 4c: Update the line's width. + + // EsPerformanceTimerPush(); +#if 0 + line->lengthWidth = EsTextGetPartialStringWidth(&textbox->textStyle, textbox->data + line->offset, line->lengthBytes, 0, line->lengthBytes); +#else + line->lengthWidth = -1; +#endif + // double time = EsPerformanceTimerPop(); + // measureLineTime += time; + // EsPrint("Measured the length of line %d in %Fms.\n", insertionPoint.line + i, time * 1000); + } + + // Step 5: Update the carets. + + textbox->carets[0].line = insertionPoint.line + newlines; + textbox->carets[1].line = insertionPoint.line + newlines; + textbox->carets[0].byte = textbox->lines[insertionPoint.line + newlines].lengthBytes - truncation; + textbox->carets[1].byte = textbox->lines[insertionPoint.line + newlines].lengthBytes - truncation; + + // Step 6: Update the textbox. + + textbox->longestLine = -1; + TextboxLineCountChangeCleanup(textbox, bytesToInsert, insertionPoint.line + 1 + newlines); + } + + if (undoItem) undoItem->caretsBefore[1] = textbox->carets[0]; + } + + done:; + + if (sendUpdatedMessage) { + EsMessage m = { ES_MSG_TEXTBOX_UPDATED }; + EsMessageSend(textbox, &m); + } else if (textbox->overlayCallback) { + EsMessage m = { ES_MSG_TEXTBOX_UPDATED }; + textbox->overlayCallback(textbox, &m); + } + + if (textbox->state & UI_STATE_DESTROYING) { + return; + } + + TextboxFindLongestLine(textbox); + InspectorNotifyElementContentChanged(textbox); + + if (undoItem && (undoItem->insertBytes || TextboxCompareCarets(undoItem->caretsBefore + 0, undoItem->caretsBefore + 1))) { + undoItem->timeStampMs = EsTimeStampMs(); + + EsUndoCallback previousCallback; + const void *previousItem; + + if (!EsUndoInUndo(textbox->undo) + && EsUndoPeek(textbox->undo, &previousCallback, &previousItem) + && previousCallback == TextboxUndoItemCallback) { + TextboxUndoItemHeader *header = (TextboxUndoItemHeader *) previousItem; + +#define TEXTBOX_UNDO_TIMEOUT (500) // TODO Make this configurable. + if (undoItem->timeStampMs - header->timeStampMs < TEXTBOX_UNDO_TIMEOUT) { + if (!undoItem->insertBytes && !header->insertBytes + && undoItem->caretsBefore[0].line == header->caretsBefore[1].line + && undoItem->caretsBefore[0].byte == header->caretsBefore[1].byte) { + // Merge the items. + undoItem->caretsBefore[0] = header->caretsBefore[0]; + EsUndoPop(textbox->undo); + } else { + // Add the new item to the same group as the previous. + EsUndoContinueGroup(textbox->undo); + } + } + } + + undoItem->textbox = textbox; + EsUndoPush(textbox->undo, TextboxUndoItemCallback, undoItem, undoItemBytes); + } + + EsHeapFree(undoItem); + + // double time = EsPerformanceTimerPop(); + // EsPrint("EsTextboxInsert in %Fms (%Fms measuring new lines).\n", time * 1000, measureLineTime * 1000); + + textbox->scroll.Refresh(); + TextboxUpdateCommands(textbox, false); +} + +char *EsTextboxGetContents(EsTextbox *textbox, size_t *_bytes, uint32_t flags) { + EsMessageMutexCheck(); + + TextboxSetActiveLine(textbox, -1); + + bool includeNewline = textbox->flags & ES_TEXTBOX_MULTILINE; + size_t bytes = textbox->dataBytes + (includeNewline ? textbox->lines.Length() : 0); + char *buffer = (char *) EsHeapAllocate(bytes + 1, false); + buffer[bytes] = 0; + + uintptr_t position = 0; + uintptr_t lineFrom = 0, lineTo = textbox->lines.Length() - 1; + + if (flags & ES_TEXTBOX_GET_CONTENTS_SELECTED_ONLY) { + lineFrom = textbox->carets[0].line; + lineTo = textbox->carets[1].line; + + if (lineFrom > lineTo) { + uintptr_t swap = lineFrom; + lineFrom = lineTo, lineTo = swap; + } + } + + for (uintptr_t i = lineFrom; i <= lineTo; i++) { + DocumentLine *line = &textbox->lines[i]; + + uintptr_t offsetFrom = 0; + uintptr_t offsetTo = line->lengthBytes; + + if (flags & ES_TEXTBOX_GET_CONTENTS_SELECTED_ONLY) { + if (i == lineFrom) { + offsetFrom = TextboxCompareCarets(textbox->carets + 0, textbox->carets + 1) < 0 ? textbox->carets[0].byte : textbox->carets[1].byte; + } + + if (i == lineTo) { + offsetTo = TextboxCompareCarets(textbox->carets + 0, textbox->carets + 1) > 0 ? textbox->carets[0].byte : textbox->carets[1].byte; + } + } + + EsMemoryCopy(buffer + position, line->GetBuffer(textbox) + offsetFrom, offsetTo - offsetFrom); + position += offsetTo - offsetFrom; + + if (includeNewline && i != lineTo) { + buffer[position++] = '\n'; + } + } + + buffer[position] = 0; + EsAssert(position <= bytes); + if (_bytes) *_bytes = position; + return (char *) EsHeapReallocate(buffer, position + 1, false); +} + +double EsTextboxGetContentsAsDouble(EsTextbox *textbox, uint32_t flags) { + size_t bytes; + char *text = EsTextboxGetContents(textbox, &bytes, flags); + double result = EsDoubleParse(text, bytes, nullptr); + EsHeapFree(text); + return result; +} + +bool EsTextboxFind(EsTextbox *textbox, const char *needle, intptr_t _needleBytes, int32_t *_line, int32_t *_byte, uint32_t flags) { + EsMessageMutexCheck(); + + if (_needleBytes == 0) { + return false; + } + + uintptr_t needleBytes = _needleBytes == -1 ? EsCStringLength(needle) : _needleBytes; + uint32_t lineIndex = *_line, byteIndex = *_byte; + bool firstLoop = true; + + while (true) { + DocumentLine *line = &textbox->lines[lineIndex]; + const char *buffer = line->GetBuffer(textbox); + size_t bufferBytes = line->lengthBytes; + EsAssert(byteIndex <= bufferBytes); // Invalid find byte offset. + + // TODO Case-insensitive search. + + if (flags & ES_TEXTBOX_FIND_BACKWARDS) { + if (bufferBytes >= needleBytes) { + for (uintptr_t i = byteIndex; i >= needleBytes; i--) { + for (uintptr_t j = 0; j < needleBytes; j++) { + if (buffer[i - needleBytes + j] != needle[j]) { + goto previousPosition; + } + } + + *_line = lineIndex; + *_byte = i - needleBytes; + return true; + + previousPosition:; + } + } + + if ((int32_t) lineIndex <= *_line && !firstLoop) { + return false; + } + + if (lineIndex == 0) { + firstLoop = false; + lineIndex = textbox->lines.Length() - 1; + } else { + lineIndex--; + } + + byteIndex = textbox->lines[lineIndex].lengthBytes; + } else { + if (bufferBytes >= needleBytes) { + for (uintptr_t i = byteIndex; i <= bufferBytes - needleBytes; i++) { + for (uintptr_t j = 0; j < needleBytes; j++) { + if (buffer[i + j] != needle[j]) { + goto nextPosition; + } + } + + *_line = lineIndex; + *_byte = i; + return true; + + nextPosition:; + } + } + + lineIndex++; + + if ((int32_t) lineIndex > *_line && !firstLoop) { + return false; + } + + if (lineIndex == textbox->lines.Length()) { + firstLoop = false; + lineIndex = 0; + } + + byteIndex = 0; + } + } + + return false; +} + +bool TextboxFindCaret(EsTextbox *textbox, int positionX, int positionY, bool secondCaret, int clickChainCount) { + int startLine0 = textbox->carets[0].line, startLine1 = textbox->carets[1].line; + EsRectangle bounds = textbox->GetBounds(); + + if (positionX < 0) { + positionX = 0; + } else if (positionX >= bounds.r) { + positionX = bounds.r - 1; + } + + if (positionY < 0) { + positionY = 0; + } else if (positionY >= bounds.b) { + positionY = bounds.b - 1; + } + + if (clickChainCount >= 4) { + textbox->carets[0].line = 0; + textbox->carets[0].byte = 0; + textbox->carets[1].line = textbox->lines.Length() - 1; + textbox->carets[1].byte = textbox->lines[textbox->lines.Length() - 1].lengthBytes; + } else { + for (uintptr_t i = 0; i < textbox->visibleLines.Length(); i++) { + TextboxVisibleLine *visibleLine = &textbox->visibleLines[i]; + DocumentLine *line = &textbox->lines[textbox->firstVisibleLine + i]; + + EsRectangle lineBounds = ES_RECT_4(textbox->insets.l, bounds.r, + textbox->insets.t + visibleLine->yPosition, + textbox->insets.t + visibleLine->yPosition + line->height); + lineBounds.l -= textbox->scroll.position[0]; + lineBounds.t -= textbox->scroll.position[1]; + lineBounds.b -= textbox->scroll.position[1]; + + if (!((positionY >= lineBounds.t || i + textbox->firstVisibleLine == 0) && (positionY < lineBounds.b + || i + textbox->firstVisibleLine == textbox->lines.Length() - 1))) { + continue; + } + + if (!line->lengthBytes) { + textbox->carets[1].byte = 0; + } else { + DocumentLine *line = &textbox->lines[i + textbox->firstVisibleLine]; + int pointX = positionX + textbox->scroll.position[0] - textbox->insets.l; + if (pointX < 0) pointX = 0; + ptrdiff_t result = TextGetCharacterAtPoint(&textbox->textStyle, + line->GetBuffer(textbox), line->lengthBytes, + &pointX, ES_TEXT_GET_CHARACTER_AT_POINT_MIDDLE); + textbox->carets[1].byte = result == -1 ? line->lengthBytes : result; + } + + textbox->carets[1].line = i + textbox->firstVisibleLine; + + break; + } + + if (!secondCaret) { + textbox->carets[0] = textbox->carets[1]; + + if (clickChainCount == 2) { + TextboxMoveCaret(textbox, textbox->carets + 0, MOVE_CARET_BACKWARDS, MOVE_CARET_WORD, true); + TextboxMoveCaret(textbox, textbox->carets + 1, MOVE_CARET_FORWARDS, MOVE_CARET_WORD, true); + textbox->wordSelectionAnchor = textbox->carets[0]; + textbox->wordSelectionAnchor2 = textbox->carets[1]; + } else if (clickChainCount == 3) { + TextboxMoveCaret(textbox, textbox->carets + 0, MOVE_CARET_BACKWARDS, MOVE_CARET_LINE, true); + TextboxMoveCaret(textbox, textbox->carets + 1, MOVE_CARET_FORWARDS, MOVE_CARET_LINE, true); + textbox->wordSelectionAnchor = textbox->carets[0]; + textbox->wordSelectionAnchor2 = textbox->carets[1]; + } + } else { + if (clickChainCount == 2) { + if (TextboxCompareCarets(textbox->carets + 1, textbox->carets + 0) < 0) { + TextboxMoveCaret(textbox, textbox->carets + 1, MOVE_CARET_BACKWARDS, MOVE_CARET_WORD); + textbox->carets[0] = textbox->wordSelectionAnchor2; + } else { + TextboxMoveCaret(textbox, textbox->carets + 1, MOVE_CARET_FORWARDS, MOVE_CARET_WORD); + textbox->carets[0] = textbox->wordSelectionAnchor; + } + } else if (clickChainCount == 3) { + if (TextboxCompareCarets(textbox->carets + 1, textbox->carets + 0) < 0) { + TextboxMoveCaret(textbox, textbox->carets + 1, MOVE_CARET_BACKWARDS, MOVE_CARET_LINE); + textbox->carets[0] = textbox->wordSelectionAnchor2; + } else { + TextboxMoveCaret(textbox, textbox->carets + 1, MOVE_CARET_FORWARDS, MOVE_CARET_LINE); + textbox->carets[0] = textbox->wordSelectionAnchor; + } + } + } + } + + TextboxUpdateCommands(textbox, true); + return textbox->carets[0].line != startLine0 || textbox->carets[1].line != startLine1; +} + +void TextboxMoveCaretToCursor(EsTextbox *textbox, int x, int y, bool doNotMoveIfNoSelection) { + int oldCompare = TextboxCompareCarets(textbox->carets + 0, textbox->carets + 1); + bool hasSelection = oldCompare != 0; + TextboxCaret old[2] = { textbox->carets[0], textbox->carets[1] }; + bool lineChanged = TextboxFindCaret(textbox, x, y, gui.clickChainCount == 1, gui.clickChainCount); + + if (doNotMoveIfNoSelection && TextboxCompareCarets(&old[0], &old[1]) != 0) { + textbox->carets[0] = old[0]; + textbox->carets[1] = old[1]; + } else if (gui.clickChainCount == 1 && !EsKeyboardIsShiftHeld()) { + textbox->carets[0] = textbox->carets[1]; + } + + TextboxUpdateCommands(textbox, true); + textbox->verticalMotionHorizontalDepth = -1; + TextboxRepaintLine(textbox, lineChanged || hasSelection ? -1 : textbox->carets[0].line); + EsTextboxEnsureCaretVisible(textbox); +} + +int ProcessTextboxMarginMessage(EsElement *element, EsMessage *message) { + EsTextbox *textbox = (EsTextbox *) element->parent; + + if (message->type == ES_MSG_PAINT) { + EsPainter *painter = message->painter; + + for (int32_t i = 0; i < (int32_t) textbox->visibleLines.Length(); i++) { + TextboxVisibleLine *visibleLine = &textbox->visibleLines[i]; + DocumentLine *line = &textbox->lines[i + textbox->firstVisibleLine]; + + EsRectangle bounds; + bounds.l = painter->offsetX + element->currentStyle->insets.l; + bounds.r = painter->offsetX + painter->width - element->currentStyle->insets.r; + bounds.t = painter->offsetY + textbox->insets.t + visibleLine->yPosition - textbox->scroll.position[1]; + bounds.b = bounds.t + line->height; + + char label[64]; + EsTextRun textRun[2] = {}; + element->currentStyle->GetTextStyle(&textRun[0].style); + textRun[0].style.figures = ES_TEXT_FIGURE_TABULAR; + textRun[1].offset = EsStringFormat(label, sizeof(label), "%d", i + textbox->firstVisibleLine + 1); + EsTextPlanProperties properties = {}; + properties.flags = ES_TEXT_V_CENTER | ES_TEXT_H_RIGHT | ES_TEXT_ELLIPSIS | ES_TEXT_PLAN_SINGLE_USE; + EsDrawText(painter, EsTextPlanCreate(&properties, bounds, label, textRun, 1), bounds, nullptr, nullptr); + } + } + + return 0; +} + +int ProcessTextboxMessage(EsElement *element, EsMessage *message) { + EsTextbox *textbox = (EsTextbox *) element; + + if (!textbox->editing && textbox->overlayCallback) { + int response = textbox->overlayCallback(element, message); + if (response != 0 && message->type != ES_MSG_DESTROY) return response; + } + + textbox->scroll.ReceivedMessage(message); + + int response = ES_HANDLED; + + if (message->type == ES_MSG_PAINT) { + EsPainter *painter = message->painter; + + EsTextSelection selectionProperties = {}; + selectionProperties.hideCaret = (~textbox->state & UI_STATE_FOCUSED) || (textbox->flags & ES_ELEMENT_DISABLED) || !textbox->editing; + selectionProperties.snapCaretToInsets = true; + selectionProperties.background = textbox->currentStyle->metrics->selectedBackground; + selectionProperties.foreground = textbox->currentStyle->metrics->selectedText; + + EsRectangle clip; + EsRectangleClip(painter->clip, ES_RECT_4(painter->offsetX + textbox->borders.l, + painter->offsetX + painter->width - textbox->borders.r, + painter->offsetY + textbox->borders.t, + painter->offsetY + painter->height - textbox->borders.b), &clip); + + Array textRuns = {}; + + for (int32_t i = 0; i < (int32_t) textbox->visibleLines.Length(); i++) { + TextboxVisibleLine *visibleLine = &textbox->visibleLines[i]; + DocumentLine *line = &textbox->lines[i + textbox->firstVisibleLine]; + + EsRectangle lineBounds = ES_RECT_4(painter->offsetX + textbox->insets.l, + painter->offsetX + painter->width, + painter->offsetY + textbox->insets.t + visibleLine->yPosition, + painter->offsetY + textbox->insets.t + visibleLine->yPosition + line->height); + lineBounds.l -= textbox->scroll.position[0]; + lineBounds.t -= textbox->scroll.position[1]; + lineBounds.b -= textbox->scroll.position[1]; + + if (~textbox->flags & ES_TEXTBOX_MULTILINE) { + lineBounds.b = painter->offsetY + painter->height - textbox->insets.b; + } + + int32_t caret0 = textbox->carets[0].byte, caret1 = textbox->carets[1].byte; + if (textbox->carets[0].line < i + textbox->firstVisibleLine) caret0 = -2; + if (textbox->carets[0].line > i + textbox->firstVisibleLine) caret0 = line->lengthBytes + 2; + if (textbox->carets[1].line < i + textbox->firstVisibleLine) caret1 = -2; + if (textbox->carets[1].line > i + textbox->firstVisibleLine) caret1 = line->lengthBytes + 2; + + if (textbox->carets[1].line == i + textbox->firstVisibleLine && textbox->syntaxHighlightingLanguage) { + EsRectangle line = ES_RECT_4(painter->offsetX, painter->offsetX + painter->width, lineBounds.t, lineBounds.b); + EsDrawBlock(painter, line, textbox->syntaxHighlightingColors[0]); + } + + if (textbox->syntaxHighlightingLanguage && line->lengthBytes) { + if (textRuns.Length()) textRuns.SetLength(0); + textRuns = TextApplySyntaxHighlighting(&textbox->textStyle, textbox->syntaxHighlightingLanguage, + textbox->syntaxHighlightingColors, textRuns, line->GetBuffer(textbox), line->lengthBytes); + } else { + textRuns.SetLength(2); + textRuns[0].style = textbox->textStyle; + textRuns[0].offset = 0; + textRuns[1].offset = line->lengthBytes; + } + + EsTextPlanProperties properties = {}; + properties.flags = ES_TEXT_V_CENTER | ES_TEXT_H_LEFT | ES_TEXT_PLAN_SINGLE_USE; + selectionProperties.caret0 = caret0; + selectionProperties.caret1 = caret1; + EsTextPlan *plan; + + if (textRuns[1].offset) { + plan = EsTextPlanCreate(&properties, lineBounds, line->GetBuffer(textbox), textRuns.array, textRuns.Length() - 1); + } else { + textRuns[1].offset = 1; // Make sure that the caret and selection is draw correctly, even on empty lines. + plan = EsTextPlanCreate(&properties, lineBounds, " ", textRuns.array, textRuns.Length() - 1); + } + + if (plan) { + EsDrawText(painter, plan, lineBounds, &clip, &selectionProperties); + } + } + + textRuns.Free(); + } else if (message->type == ES_MSG_LAYOUT) { + EsRectangle bounds = textbox->GetBounds(); + + if (textbox->margin) { + int marginWidth = textbox->margin->currentStyle->preferredWidth; + textbox->margin->InternalMove(marginWidth, Height(bounds), bounds.l, bounds.t); + } + + TextboxRefreshVisibleLines(textbox); + + if (textbox->editing && (~textbox->flags & ES_TEXTBOX_MULTILINE)) { + EsTextboxEnsureCaretVisible(textbox); + } + } else if (message->type == ES_MSG_DESTROY) { + textbox->visibleLines.Free(); + textbox->lines.Free(); + UndoManagerDestroy(&textbox->localUndo); + EsHeapFree(textbox->activeLine); + EsHeapFree(textbox->data); + EsHeapFree(textbox->editStartContent); + } else if (message->type == ES_MSG_KEY_TYPED && !IsScancodeNonTypeable(message->keyboard.scancode)) { + bool verticalMotion = false; + bool ctrl = message->keyboard.modifiers & ES_MODIFIER_CTRL; + + if (message->keyboard.scancode == ES_SCANCODE_LEFT_ARROW || message->keyboard.scancode == ES_SCANCODE_RIGHT_ARROW + || message->keyboard.scancode == ES_SCANCODE_HOME || message->keyboard.scancode == ES_SCANCODE_END + || message->keyboard.scancode == ES_SCANCODE_UP_ARROW || message->keyboard.scancode == ES_SCANCODE_DOWN_ARROW) { + bool direction = (message->keyboard.scancode == ES_SCANCODE_LEFT_ARROW || message->keyboard.scancode == ES_SCANCODE_HOME + || message->keyboard.scancode == ES_SCANCODE_UP_ARROW) + ? MOVE_CARET_BACKWARDS : MOVE_CARET_FORWARDS; + int moveType = (message->keyboard.scancode == ES_SCANCODE_HOME || message->keyboard.scancode == ES_SCANCODE_END) + ? (ctrl ? MOVE_CARET_ALL : MOVE_CARET_LINE) + : ((message->keyboard.scancode == ES_SCANCODE_UP_ARROW || message->keyboard.scancode == ES_SCANCODE_DOWN_ARROW) + ? MOVE_CARET_VERTICAL : (ctrl ? MOVE_CARET_WORD : MOVE_CARET_SINGLE)); + if (moveType == MOVE_CARET_VERTICAL) verticalMotion = true; + + int32_t lineFrom = textbox->carets[1].line; + + if (message->keyboard.modifiers & ES_MODIFIER_SHIFT) { + TextboxMoveCaret(textbox, &textbox->carets[1], direction, moveType); + } else { + int caretCompare = TextboxCompareCarets(textbox->carets + 1, textbox->carets + 0); + + if ((caretCompare < 0 && direction == MOVE_CARET_BACKWARDS) || (caretCompare > 0 && direction == MOVE_CARET_FORWARDS)) { + textbox->carets[0] = textbox->carets[1]; + TextboxUpdateCommands(textbox, true); + } else if ((caretCompare > 0 && direction == MOVE_CARET_BACKWARDS) || (caretCompare < 0 && direction == MOVE_CARET_FORWARDS)) { + textbox->carets[1] = textbox->carets[0]; + TextboxUpdateCommands(textbox, true); + } else { + TextboxMoveCaret(textbox, &textbox->carets[1], direction, moveType); + textbox->carets[0] = textbox->carets[1]; + TextboxUpdateCommands(textbox, true); + } + } + + int32_t lineTo = textbox->carets[1].line; + if (lineFrom > lineTo) { int32_t t = lineTo; lineTo = lineFrom; lineFrom = t; } + for (int32_t i = lineFrom; i <= lineTo; i++) TextboxRepaintLine(textbox, i); + } else if (message->keyboard.scancode == ES_SCANCODE_PAGE_UP || message->keyboard.scancode == ES_SCANCODE_PAGE_DOWN) { + for (uintptr_t i = 0; i < 10; i++) { + TextboxMoveCaret(textbox, textbox->carets + 1, + message->keyboard.scancode == ES_SCANCODE_PAGE_UP ? MOVE_CARET_BACKWARDS : MOVE_CARET_FORWARDS, + MOVE_CARET_VERTICAL); + } + + if (~message->keyboard.modifiers & ES_MODIFIER_SHIFT) { + textbox->carets[0] = textbox->carets[1]; + TextboxUpdateCommands(textbox, true); + } + + textbox->Repaint(true); + verticalMotion = true; + } else if (message->keyboard.scancode == ES_SCANCODE_BACKSPACE || message->keyboard.scancode == ES_SCANCODE_DELETE) { + if (!TextboxCompareCarets(textbox->carets + 0, textbox->carets + 1)) { + TextboxMoveCaret(textbox, textbox->carets + 1, message->keyboard.scancode == ES_SCANCODE_BACKSPACE ? MOVE_CARET_BACKWARDS : MOVE_CARET_FORWARDS, + ctrl ? MOVE_CARET_WORD : MOVE_CARET_SINGLE); + } + + EsTextboxInsert(textbox, EsLiteral("")); + } else if (message->keyboard.scancode == ES_SCANCODE_ENTER && (textbox->flags & ES_TEXTBOX_EDIT_BASED)) { + if (textbox->editing) { + TextboxEndEdit(textbox, false); + } else { + EsTextboxStartEdit(textbox); + } + } else if (message->keyboard.scancode == ES_SCANCODE_ESCAPE && (textbox->flags & ES_TEXTBOX_EDIT_BASED)) { + TextboxEndEdit(textbox, true); + } else if (message->keyboard.scancode == ES_SCANCODE_TAB && (~textbox->flags & ES_TEXTBOX_ALLOW_TABS)) { + response = 0; + } else { + if (!textbox->editing) { + EsTextboxStartEdit(textbox); + } + + int ic, isc; + ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, true, textbox->flags & ES_TEXTBOX_MULTILINE); + + if (ic != -1 && (message->keyboard.modifiers & ~ES_MODIFIER_SHIFT) == 0) { + char buffer[4]; + EsTextboxInsert(textbox, buffer, utf8_encode((message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? isc : ic, buffer)); + + if (buffer[0] == '\n' && textbox->carets[0].line) { + // Copy the indentation from the previous line. + + DocumentLine *previousLine = &textbox->lines[textbox->carets[0].line - 1]; + const char *buffer = previousLine->GetBuffer(textbox); + int32_t i = 0; + + for (; i < previousLine->lengthBytes; i++) { + if (buffer[i] != '\t') { + break; + } + } + + EsTextboxInsert(textbox, buffer, i); + } + } else { + response = 0; + } + } + + if (!verticalMotion) { + textbox->verticalMotionHorizontalDepth = -1; + } + + if (response != 0 && (~textbox->state & UI_STATE_DESTROYING)) { + TextboxFindLongestLine(textbox); + textbox->scroll.Refresh(); + EsTextboxEnsureCaretVisible(textbox); + textbox->window->ensureVisible = textbox; + } + } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN || message->type == ES_MSG_MOUSE_RIGHT_DOWN) { + TextboxMoveCaretToCursor(textbox, message->mouseDown.positionX, message->mouseDown.positionY, message->type == ES_MSG_MOUSE_RIGHT_DOWN); + } else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { + EsTextboxStartEdit(textbox); + } else if (message->type == ES_MSG_FOCUSED_START || message->type == ES_MSG_PRIMARY_CLIPBOARD_UPDATED) { + TextboxUpdateCommands(textbox, false); + EsInstanceSetActiveUndoManager(textbox->instance, textbox->undo); + textbox->Repaint(true); + } else if (message->type == ES_MSG_FOCUSED_END) { + EsCommandSetCallback(EsCommandByID(textbox->instance, ES_COMMAND_SELECT_ALL), nullptr); + EsCommandSetCallback(EsCommandByID(textbox->instance, ES_COMMAND_DELETE), nullptr); + EsCommandSetCallback(EsCommandByID(textbox->instance, ES_COMMAND_COPY), nullptr); + EsCommandSetCallback(EsCommandByID(textbox->instance, ES_COMMAND_CUT), nullptr); + EsCommandSetCallback(EsCommandByID(textbox->instance, ES_COMMAND_PASTE), nullptr); + EsInstanceSetActiveUndoManager(textbox->instance, textbox->instance->undoManager); + textbox->Repaint(true); + } else if (message->type == ES_MSG_STRONG_FOCUS_END) { + TextboxEndEdit(textbox, textbox->flags & ES_TEXTBOX_REJECT_EDIT_IF_LOST_FOCUS); + } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_RIGHT_DRAG || (message->type == ES_MSG_ANIMATE && textbox->scroll.dragScrolling)) { + int32_t lineFrom = textbox->carets[1].line; + + if (gui.lastClickButton == ES_MSG_MOUSE_RIGHT_DOWN && !textbox->inRightClickDrag) { + TextboxMoveCaretToCursor(textbox, message->mouseDragged.originalPositionX, message->mouseDragged.originalPositionY, false); + textbox->inRightClickDrag = true; + } + + EsPoint position = EsMouseGetPosition(textbox); + TextboxFindCaret(textbox, position.x, position.y, true, gui.clickChainCount); + + int32_t lineTo = textbox->carets[1].line; + if (lineFrom > lineTo) { int32_t t = lineTo; lineTo = lineFrom; lineFrom = t; } + for (int32_t i = lineFrom; i <= lineTo; i++) TextboxRepaintLine(textbox, i); + } else if (message->type == ES_MSG_GET_CURSOR) { + if (!textbox->editing || (textbox->flags & ES_ELEMENT_DISABLED)) { + message->cursorStyle = ES_CURSOR_NORMAL; + } else { + return 0; + } + } else if (message->type == ES_MSG_MOUSE_RIGHT_UP) { + textbox->inRightClickDrag = false; + EsMenu *menu = EsMenuCreate(textbox, ES_MENU_AT_CURSOR); + + // TODO User customisation of menus. + + if (textbox->editing) { + EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonUndo), EsCommandByID(textbox->instance, ES_COMMAND_UNDO)); + EsMenuAddSeparator(menu); + } + + EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardCut), EsCommandByID(textbox->instance, ES_COMMAND_CUT)); + EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardCopy), EsCommandByID(textbox->instance, ES_COMMAND_COPY)); + EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardPaste), EsCommandByID(textbox->instance, ES_COMMAND_PASTE)); + + if (textbox->editing) { + EsMenuAddSeparator(menu); + EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonSelectionSelectAll), EsCommandByID(textbox->instance, ES_COMMAND_SELECT_ALL)); + EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonSelectionDelete), EsCommandByID(textbox->instance, ES_COMMAND_DELETE)); + + // Add the smart context menu, if necessary. + + if ((~textbox->flags & ES_TEXTBOX_NO_SMART_CONTEXT_MENUS) && textbox->carets[0].line == textbox->carets[1].line) { + int32_t selectionFrom = textbox->carets[0].byte, selectionTo = textbox->carets[1].byte; + + if (selectionTo < selectionFrom) { + int32_t temporary = selectionFrom; + selectionFrom = selectionTo; + selectionTo = temporary; + } + + if (selectionTo - selectionFrom == 7) { + char buffer[7]; + EsMemoryCopy(buffer, textbox->lines[textbox->carets[0].line].GetBuffer(textbox) + selectionFrom, 7); + + if (buffer[0] == '#' && EsCRTisxdigit(buffer[1]) && EsCRTisxdigit(buffer[2]) && EsCRTisxdigit(buffer[3]) + && EsCRTisxdigit(buffer[4]) && EsCRTisxdigit(buffer[5]) && EsCRTisxdigit(buffer[6])) { + // It's a color hex-code! + // TODO Versions with alpha. + EsMenuNextColumn(menu); + ColorPickerCreate(menu, { textbox }, EsColorParse(buffer, 7), false); + + textbox->colorUppercase = true; + + for (uintptr_t i = 1; i <= 6; i++) { + if (buffer[i] >= 'a' && buffer[i] <= 'f') { + textbox->colorUppercase = false; + break; + } + } + } + } + } + } + + EsMenuShow(menu); + } else if (message->type == ES_MSG_COLOR_CHANGED) { + EsAssert(~textbox->flags & ES_TEXTBOX_NO_SMART_CONTEXT_MENUS); // Textbox sent color changed message, but it cannot have smart context menus? + uint32_t color = message->colorChanged.newColor; + + if (message->colorChanged.pickerClosed) { + int32_t selectionFrom = textbox->carets[0].byte, selectionTo = textbox->carets[1].byte; + + if (textbox->carets[0].line == textbox->carets[1].line && AbsoluteInteger(selectionFrom - selectionTo) == 7) { + char buffer[7]; + const char *hexChars = textbox->colorUppercase ? "0123456789ABCDEF" : "0123456789abcedf"; + size_t length = EsStringFormat(buffer, 7, "#%c%c%c%c%c%c", + hexChars[(color >> 20) & 0xF], hexChars[(color >> 16) & 0xF], hexChars[(color >> 12) & 0xF], + hexChars[(color >> 8) & 0xF], hexChars[(color >> 4) & 0xF], hexChars[(color >> 0) & 0xF]); + EsTextboxInsert(textbox, buffer, length, true); + EsTextboxSetSelection(textbox, textbox->carets[1].line, textbox->carets[1].byte - 7, + textbox->carets[1].line, textbox->carets[1].byte); + } + } + } else if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = textbox->longestLineWidth + textbox->insets.l + textbox->insets.r; + } else if (message->type == ES_MSG_GET_HEIGHT) { + DocumentLine *lastLine = &textbox->lines.Last(); + message->measure.height = lastLine->yPosition + lastLine->height + textbox->insets.t + textbox->insets.b; + } else if (message->type == ES_MSG_SCROLL_X) { + TextboxSetHorizontalScroll(textbox, message->scrollbarMoved.scroll); + } else if (message->type == ES_MSG_SCROLL_Y) { + TextboxRefreshVisibleLines(textbox, false); + EsElementRepaintForScroll(textbox, message); + } else if (message->type == ES_MSG_GET_INSPECTOR_INFORMATION) { + DocumentLine *firstLine = &textbox->lines.First(); + EsBufferFormat(message->getContent.buffer, "'%s'", firstLine->lengthBytes, firstLine->GetBuffer(textbox)); + } else { + response = 0; + } + + return response; +} + +EsTextbox *EsTextboxCreate(EsElement *parent, uint64_t flags, const EsStyle *style) { + EsTextbox *textbox = (EsTextbox *) EsHeapAllocate(sizeof(EsTextbox), true); + + if (!style) { + if (flags & ES_TEXTBOX_MULTILINE) { + style = ES_STYLE_TEXTBOX_BORDERED_MULTILINE; + } else { + style = ES_STYLE_TEXTBOX_BORDERED_SINGLE; + } + } + + textbox->Initialise(parent, ES_ELEMENT_FOCUSABLE | flags, ProcessTextboxMessage, style); + textbox->cName = "textbox"; + + textbox->scroll.Setup(textbox, + (flags & ES_TEXTBOX_MULTILINE) ? SCROLL_MODE_AUTO : SCROLL_MODE_HIDDEN, + (flags & ES_TEXTBOX_MULTILINE) ? SCROLL_MODE_AUTO : SCROLL_MODE_NONE, + SCROLL_X_DRAG | SCROLL_Y_DRAG); + + textbox->undo = &textbox->localUndo; + textbox->undo->instance = textbox->instance; + + // TODO Automatically update these when the theme changes. + textbox->borders = textbox->currentStyle->borders; + textbox->insets = textbox->currentStyle->insets; + textbox->currentStyle->GetTextStyle(&textbox->textStyle); + + DocumentLine firstLine = {}; + firstLine.height = EsTextGetLineHeight(&textbox->textStyle); + textbox->lines.Add(firstLine); + + TextboxVisibleLine firstVisibleLine = {}; + textbox->visibleLines.Add(firstVisibleLine); + + textbox->activeLineIndex = textbox->verticalMotionHorizontalDepth = textbox->longestLine = -1; + + if (~flags & ES_TEXTBOX_EDIT_BASED) { + textbox->editing = true; + } + + if (textbox->flags & ES_TEXTBOX_MARGIN) { + textbox->margin = EsCustomElementCreate(textbox, ES_CELL_FILL, ES_STYLE_TEXTBOX_MARGIN); + textbox->margin->cName = "margin"; + textbox->margin->messageUser = ProcessTextboxMarginMessage; + + int marginWidth = textbox->margin->currentStyle->preferredWidth; + textbox->borders.l += marginWidth; + textbox->insets.l += marginWidth + textbox->margin->currentStyle->gapMajor; + } + + return textbox; +} + +void EsTextboxUseNumberOverlay(EsTextbox *textbox, bool defaultBehaviour) { + EsMessageMutexCheck(); + + EsAssert(textbox->flags & ES_TEXTBOX_EDIT_BASED); // Using textbox overlay without edit based mode. + EsAssert(~textbox->flags & ES_TEXTBOX_MULTILINE); // Using number overlay with multiline mode. + + textbox->overlayData = defaultBehaviour; + + textbox->overlayCallback = [] (EsElement *element, EsMessage *message) { + EsTextbox *textbox = (EsTextbox *) element; + bool defaultBehaviour = textbox->overlayData.u; + + if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + if (!gui.draggingStarted) { + EsMessage m = { ES_MSG_TEXTBOX_NUMBER_DRAG_START }; + EsMessageSend(textbox, &m); + } + + TextboxFindCaret(textbox, message->mouseDragged.originalPositionX, message->mouseDragged.originalPositionY, false, 1); + + EsMessage m = { ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA }; + m.numberDragDelta.delta = message->mouseDragged.originalPositionY - message->mouseDragged.newPositionY; + m.numberDragDelta.fast = EsKeyboardIsShiftHeld(); + m.numberDragDelta.hoverCharacter = textbox->carets[1].byte; + EsMessageSend(textbox, &m); + + EsMouseSetPosition(textbox->window, gui.lastClickX, gui.lastClickY); + } else if (message->type == ES_MSG_KEY_TYPED && message->keyboard.scancode == ES_SCANCODE_UP_ARROW) { + EsMessage m = { ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA }; + m.numberDragDelta.delta = 1; + m.numberDragDelta.fast = EsKeyboardIsShiftHeld(); + m.numberDragDelta.hoverCharacter = 0; + EsMessageSend(textbox, &m); + } else if (message->type == ES_MSG_KEY_TYPED && message->keyboard.scancode == ES_SCANCODE_DOWN_ARROW) { + EsMessage m = { ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA }; + m.numberDragDelta.delta = -1; + m.numberDragDelta.fast = EsKeyboardIsShiftHeld(); + m.numberDragDelta.hoverCharacter = 0; + EsMessageSend(textbox, &m); + } else if (message->type == ES_MSG_MOUSE_LEFT_UP) { + if (gui.draggingStarted) { + EsMessage m = { ES_MSG_TEXTBOX_NUMBER_DRAG_END }; + EsMessageSend(textbox, &m); + } + } else if (message->type == ES_MSG_GET_CURSOR) { + if (gui.draggingStarted) { + message->cursorStyle = ES_CURSOR_BLANK; + } else if (~textbox->flags & ES_ELEMENT_DISABLED) { + message->cursorStyle = ES_CURSOR_RESIZE_VERTICAL; + } else { + message->cursorStyle = ES_CURSOR_NORMAL; + } + } else if (message->type == ES_MSG_TEXTBOX_EDIT_END && defaultBehaviour) { + double oldValue = EsDoubleParse(textbox->editStartContent, textbox->editStartContentBytes, nullptr); + + char *expression = EsTextboxGetContents(textbox); + EsCalculationValue value = EsCalculateFromUserExpression(expression); + EsHeapFree(expression); + + if (value.error) { + return ES_REJECTED; + } else { + EsMessage m = { ES_MSG_TEXTBOX_NUMBER_UPDATED }; + m.numberUpdated.delta = value.number - oldValue; + m.numberUpdated.newValue = value.number; + EsMessageSend(textbox, &m); + + char result[64]; + size_t resultBytes = EsStringFormat(result, sizeof(result), "%F", (double) m.numberUpdated.newValue); + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, result, resultBytes); + } + } else if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA && defaultBehaviour) { + TextboxSetActiveLine(textbox, -1); + double oldValue = EsDoubleParse(textbox->data, textbox->lines[0].lengthBytes, nullptr); + double newValue = oldValue + message->numberDragDelta.delta * (message->numberDragDelta.fast ? 10 : 1); + + EsMessage m = { ES_MSG_TEXTBOX_NUMBER_UPDATED }; + m.numberUpdated.delta = newValue - oldValue; + m.numberUpdated.newValue = newValue; + EsMessageSend(textbox, &m); + + char result[64]; + size_t resultBytes = EsStringFormat(result, sizeof(result), "%F", m.numberUpdated.newValue); + EsTextboxSelectAll(textbox); + EsTextboxInsert(textbox, result, resultBytes); + } else { + return 0; + } + + return ES_HANDLED; + }; +} + +void TextboxBreadcrumbOverlayRecreate(EsTextbox *textbox) { + if (textbox->overlayData.p) { + // Remove the old breadcrumb panel. + ((EsElement *) textbox->overlayData.p)->Destroy(); + } + + EsPanel *panel = EsPanelCreate(textbox, ES_PANEL_HORIZONTAL | ES_CELL_FILL | ES_ELEMENT_NO_HOVER, ES_STYLE_BREADCRUMB_BAR_PANEL); + textbox->overlayData = panel; + + uint8_t _buffer[256]; + EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; + EsMessage m = { ES_MSG_TEXTBOX_GET_BREADCRUMB }; + m.getBreadcrumb.buffer = &buffer; + + while (true) { + buffer.position = 0; + int response = EsMessageSend(textbox, &m); + EsAssert(response != 0); // Must handle ES_MSG_TEXTBOX_GET_BREADCRUMB message for breadcrumb overlay. + if (response == ES_REJECTED) break; + + EsButton *crumb = EsButtonCreate(panel, ES_BUTTON_NOT_FOCUSABLE | ES_BUTTON_COMPACT | ES_CELL_V_FILL, + ES_STYLE_BREADCRUMB_BAR_CRUMB, (char *) buffer.out, buffer.position); + crumb->userData = m.getBreadcrumb.index; + + crumb->messageUser = [] (EsElement *element, EsMessage *message) { + if (message->type == ES_MSG_MOUSE_LEFT_CLICK) { + EsMessage m = { ES_MSG_TEXTBOX_ACTIVATE_BREADCRUMB }; + m.activateBreadcrumb = element->userData.u; + EsMessageSend(element->parent->parent, &m); + } else { + return 0; + } + + return ES_HANDLED; + }; + + m.getBreadcrumb.index++; + } +} + +void EsTextboxUseBreadcrumbOverlay(EsTextbox *textbox) { + EsMessageMutexCheck(); + + EsAssert(textbox->flags & ES_TEXTBOX_EDIT_BASED); // Using textbox overlay without edit based mode. + + // Use this to store the panel containing the breadcrumb buttons. + textbox->overlayData = nullptr; + + textbox->overlayCallback = [] (EsElement *element, EsMessage *message) { + EsTextbox *textbox = (EsTextbox *) element; + + if (message->type == ES_MSG_TEXTBOX_UPDATED) { + TextboxBreadcrumbOverlayRecreate(textbox); + } else if (message->type == ES_MSG_TEXTBOX_EDIT_START) { + ((EsElement *) textbox->overlayData.p)->Destroy(); + textbox->overlayData.p = nullptr; + } else if (message->type == ES_MSG_TEXTBOX_EDIT_END) { + TextboxBreadcrumbOverlayRecreate(textbox); + } else if (message->type == ES_MSG_LAYOUT) { + EsRectangle bounds = textbox->GetBounds(); + ((EsElement *) textbox->overlayData.p)->InternalMove(bounds.r, bounds.b, 0, 0); + } else if (message->type == ES_MSG_PAINT) { + return ES_HANDLED; + } + + return 0; + }; + + TextboxBreadcrumbOverlayRecreate(textbox); +} + +void EsTextboxSetUndoManager(EsTextbox *textbox, EsUndoManager *undoManager) { + EsMessageMutexCheck(); + EsAssert(~textbox->state & UI_STATE_FOCUSED); // Can't change undo manager if the textbox is focused. + EsAssert(textbox->undo == &textbox->localUndo); // This can only be set once. + textbox->undo = undoManager; +} + +void EsTextboxSetTextStyle(EsTextbox *textbox, const EsTextStyle *textStyle) { + if (0 == EsMemoryCompare(textStyle, &textbox->textStyle, sizeof(EsTextStyle))) { + return; + } + + EsMemoryCopy(&textbox->textStyle, textStyle, sizeof(EsTextStyle)); + int lineHeight = EsTextGetLineHeight(&textbox->textStyle); + + for (int32_t i = 0; i < (int32_t) textbox->lines.Length(); i++) { + DocumentLine *line = &textbox->lines[i]; + DocumentLine *previous = i ? (&textbox->lines[i - 1]) : nullptr; + line->height = lineHeight; + line->yPosition = previous ? (previous->yPosition + previous->height) : 0; + line->lengthWidth = -1; + textbox->longestLine = -1; + } + + TextboxRefreshVisibleLines(textbox); + TextboxFindLongestLine(textbox); + textbox->scroll.Refresh(); + EsElementRepaint(textbox); +} + +void EsTextboxGetTextStyle(EsTextbox *textbox, EsTextStyle *textStyle) { + EsMemoryCopy(textStyle, &textbox->textStyle, sizeof(EsTextStyle)); +} + +void EsTextboxSetupSyntaxHighlighting(EsTextbox *textbox, uint32_t language, uint32_t *customColors, size_t customColorCount) { + textbox->syntaxHighlightingLanguage = language; + + // TODO Load these from the theme file. + textbox->syntaxHighlightingColors[0] = 0x04000000; // Highlighted line. + textbox->syntaxHighlightingColors[1] = 0xFF000000; // Default. + textbox->syntaxHighlightingColors[2] = 0xFFA11F20; // Comment. + textbox->syntaxHighlightingColors[3] = 0xFF037E01; // String. + textbox->syntaxHighlightingColors[4] = 0xFF213EF1; // Number. + textbox->syntaxHighlightingColors[5] = 0xFF7F0480; // Operator. + textbox->syntaxHighlightingColors[6] = 0xFF545D70; // Preprocessor. + textbox->syntaxHighlightingColors[7] = 0xFF17546D; // Keyword. + + if (customColorCount > sizeof(textbox->syntaxHighlightingColors) / sizeof(uint32_t)) { + customColorCount = sizeof(textbox->syntaxHighlightingColors) / sizeof(uint32_t); + } + + EsMemoryCopy(textbox->syntaxHighlightingColors, customColors, customColorCount * sizeof(uint32_t)); + + textbox->Repaint(true); +} + +// --------------------------------- Text displays. + +// TODO Inline images and icons. +// TODO Links. +// TODO Inline backgrounds. + +void TextDisplayFreeRuns(EsTextDisplay *display) { + if (display->usingSyntaxHighlighting) { + Array textRuns = { display->textRuns }; + textRuns.Free(); + } else { + EsHeapFree(display->textRuns); + } +} + +int ProcessTextDisplayMessage(EsElement *element, EsMessage *message) { + EsTextDisplay *display = (EsTextDisplay *) element; + + if (message->type == ES_MSG_PAINT) { + EsRectangle textBounds = EsPainterBoundsInset(message->painter); + + if (!display->plan || display->planWidth != textBounds.r - textBounds.l || display->planHeight != textBounds.b - textBounds.t) { + if (display->plan) EsTextPlanDestroy(display->plan); + display->properties.flags = display->currentStyle->textAlign; + if (~display->flags & ES_TEXT_DISPLAY_PREFORMATTED) display->properties.flags |= ES_TEXT_PLAN_TRIM_SPACES; + if (display->flags & ES_TEXT_DISPLAY_NO_FONT_SUBSTITUTION) display->properties.flags |= ES_TEXT_PLAN_NO_FONT_SUBSTITUTION; + display->plan = EsTextPlanCreate(&display->properties, textBounds, display->contents, display->textRuns, display->textRunCount); + display->planWidth = textBounds.r - textBounds.l; + display->planHeight = textBounds.b - textBounds.t; + } + + if (display->plan) { + EsDrawTextLayers(message->painter, display->plan, EsPainterBoundsInset(message->painter)); + } + } else if (message->type == ES_MSG_GET_WIDTH || message->type == ES_MSG_GET_HEIGHT) { + if (!display->measurementCache.Get(message, &display->state)) { + if (display->plan) EsTextPlanDestroy(display->plan); + display->properties.flags = display->currentStyle->textAlign | ((display->flags & ES_TEXT_DISPLAY_PREFORMATTED) ? 0 : ES_TEXT_PLAN_TRIM_SPACES); + EsRectangle insets = EsElementGetInsets(element); + display->planWidth = message->type == ES_MSG_GET_HEIGHT && message->measure.width + ? (message->measure.width - insets.l - insets.r) : 0; + display->planHeight = 0; + display->plan = EsTextPlanCreate(&display->properties, + ES_RECT_4(0, display->planWidth, 0, 0), + display->contents, display->textRuns, display->textRunCount); + + if (!display->plan) { + message->measure.width = message->measure.height = 0; + } else { + if (message->type == ES_MSG_GET_WIDTH) { + message->measure.width = EsTextPlanGetWidth(display->plan) + insets.l + insets.r; + } else { + message->measure.height = EsTextPlanGetHeight(display->plan) + insets.t + insets.b; + } + } + + display->measurementCache.Store(message); + } + } else if (message->type == ES_MSG_DESTROY) { + if (display->plan) { + EsTextPlanDestroy(display->plan); + } + + TextDisplayFreeRuns(display); + EsHeapFree(display->contents); + } else if (message->type == ES_MSG_GET_INSPECTOR_INFORMATION) { + EsBufferFormat(message->getContent.buffer, "'%s'", display->textRuns[display->textRunCount].offset, display->contents); + } else { + return 0; + } + + return ES_HANDLED; +} + +void EsTextDisplaySetStyledContents(EsTextDisplay *display, const char *string, EsTextRun *runs, size_t runCount) { + TextDisplayFreeRuns(display); + + display->textRuns = (EsTextRun *) EsHeapAllocate(sizeof(EsTextRun) * (runCount + 1), true); + display->textRunCount = runCount; + HeapDuplicate((void **) &display->contents, string, runs[runCount].offset); + EsMemoryCopy(display->textRuns, runs, sizeof(EsTextRun) * (runCount + 1)); + + display->usingSyntaxHighlighting = false; + EsElementUpdateContentSize(display); + InspectorNotifyElementContentChanged(display); +} + +void EsTextDisplaySetContents(EsTextDisplay *display, const char *string, ptrdiff_t stringBytes) { + if (stringBytes == -1) stringBytes = EsCStringLength(string); + + TextDisplayFreeRuns(display); + + if (display->flags & ES_TEXT_DISPLAY_RICH_TEXT) { + EsHeapFree(display->contents); + EsTextStyle baseStyle = {}; + display->currentStyle->GetTextStyle(&baseStyle); + EsRichTextParse(string, stringBytes, &display->contents, &display->textRuns, &display->textRunCount, &baseStyle); + } else { + HeapDuplicate((void **) &display->contents, string, stringBytes); + display->textRuns = (EsTextRun *) EsHeapAllocate(sizeof(EsTextRun) * 2, true); + display->currentStyle->GetTextStyle(&display->textRuns[0].style); + display->textRuns[1].offset = stringBytes; + display->textRunCount = 1; + } + + display->usingSyntaxHighlighting = false; + EsElementUpdateContentSize(display); + InspectorNotifyElementContentChanged(display); +} + +EsTextDisplay *EsTextDisplayCreate(EsElement *parent, uint64_t flags, const EsStyle *style, const char *label, ptrdiff_t labelBytes) { + EsTextDisplay *display = (EsTextDisplay *) EsHeapAllocate(sizeof(EsTextDisplay), true); + display->Initialise(parent, flags, ProcessTextDisplayMessage, style ?: UIGetDefaultStyleVariant(ES_STYLE_TEXT_LABEL, parent)); + display->cName = "text display"; + if (labelBytes == -1) labelBytes = EsCStringLength(label); + EsTextDisplaySetContents(display, label, labelBytes); + return display; +} + +void EsTextDisplaySetupSyntaxHighlighting(EsTextDisplay *display, uint32_t language, uint32_t *customColors, size_t customColorCount) { + // Copied from EsTextboxSetupSyntaxHighlighting. + uint32_t colors[8]; + colors[0] = 0x04000000; // Highlighted line. + colors[1] = 0xFF000000; // Default. + colors[2] = 0xFFA11F20; // Comment. + colors[3] = 0xFF037E01; // String. + colors[4] = 0xFF213EF1; // Number. + colors[5] = 0xFF7F0480; // Operator. + colors[6] = 0xFF545D70; // Preprocessor. + colors[7] = 0xFF17546D; // Keyword. + + if (customColorCount > sizeof(colors) / sizeof(uint32_t)) customColorCount = sizeof(colors) / sizeof(uint32_t); + EsMemoryCopy(colors, customColors, customColorCount * sizeof(uint32_t)); + + EsTextStyle textStyle = {}; + display->currentStyle->GetTextStyle(&textStyle); + + EsTextRun *newRuns = TextApplySyntaxHighlighting(&textStyle, language, colors, {}, + display->contents, display->textRuns[display->textRunCount].offset).array; + TextDisplayFreeRuns(display); + display->textRuns = newRuns; + display->textRunCount = ArrayLength(display->textRuns) - 1; + display->usingSyntaxHighlighting = true; + display->Repaint(true); +} + +// --------------------------------- List displays. + +struct EsListDisplay : EsElement { + uintptr_t itemCount, startIndex; + EsListDisplay *previous; +}; + +int ProcessListDisplayMessage(EsElement *element, EsMessage *message) { + EsListDisplay *display = (EsListDisplay *) element; + + if (message->type == ES_MSG_GET_HEIGHT) { + int32_t height = 0; + int32_t margin = element->currentStyle->insets.l + element->currentStyle->insets.r + element->currentStyle->gapMinor; + uintptr_t itemCount = 0; + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + EsElement *child = element->GetChild(i); + if (child->flags & ES_ELEMENT_NON_CLIENT) continue; + height += child->GetHeight(message->measure.width - margin); + itemCount++; + } + + if (itemCount) { + height += (itemCount - 1) * element->currentStyle->gapMajor; + } + + message->measure.height = height + element->currentStyle->insets.t + element->currentStyle->insets.b; + } else if (message->type == ES_MSG_LAYOUT) { + int32_t position = element->currentStyle->insets.t; + int32_t margin = element->currentStyle->insets.l + element->currentStyle->gapMinor; + int32_t width = element->width - margin - element->currentStyle->insets.r; + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + EsElement *child = element->GetChild(i); + if (child->flags & ES_ELEMENT_NON_CLIENT) continue; + int height = child->GetHeight(width); + EsElementMove(child, margin, position, width, height); + position += height + element->currentStyle->gapMajor; + } + } else if (message->type == ES_MSG_PAINT) { + char buffer[64]; + EsTextPlanProperties properties = {}; + properties.flags = ES_TEXT_H_RIGHT | ES_TEXT_V_TOP | ES_TEXT_PLAN_SINGLE_USE; + EsTextRun textRun[2] = {}; + + EsRectangle bounds = EsPainterBoundsClient(message->painter); + bounds.r = bounds.l + element->currentStyle->insets.l; + + uintptr_t counter = display->previous ? display->previous->itemCount : display->startIndex; + uint8_t markerType = element->flags & ES_LIST_DISPLAY_MARKER_TYPE_MASK; + + EsMessage m = {}; + m.type = ES_MSG_LIST_DISPLAY_GET_MARKER; + EsBuffer buffer2 = { .out = (uint8_t *) buffer, .bytes = sizeof(buffer) }; + m.getContent.buffer = &buffer2; + + for (uintptr_t i = 0; i < element->GetChildCount(); i++) { + EsElement *child = element->GetChild(i); + if (child->flags & ES_ELEMENT_NON_CLIENT) continue; + + if (markerType == ES_LIST_DISPLAY_BULLETED) { + EsMemoryCopy(buffer, "\xE2\x80\xA2", (textRun[1].offset = 3)); + } else if (markerType == ES_LIST_DISPLAY_NUMBERED) { + textRun[1].offset = EsStringFormat(buffer, sizeof(buffer), "%d.", counter + 1); + } else if (markerType == ES_LIST_DISPLAY_LOWER_ALPHA) { + textRun[1].offset = EsStringFormat(buffer, sizeof(buffer), "(%c)", counter + 'a'); + } else if (markerType == ES_LIST_DISPLAY_CUSTOM_MARKER) { + m.getContent.index = counter; + EsMessageSend(element, &m); + textRun[1].offset = buffer2.position; + } else { + EsAssert(false); + } + + child->currentStyle->GetTextStyle(&textRun[0].style); + textRun[0].style.figures = ES_TEXT_FIGURE_TABULAR; + bounds.t += child->offsetY; + bounds.b = bounds.t + child->height; + EsTextPlan *plan = EsTextPlanCreate(&properties, bounds, buffer, textRun, 1); + EsDrawText(message->painter, plan, bounds); + bounds.t -= child->offsetY; + counter++; + } + } else if (message->type == ES_MSG_ADD_CHILD) { + display->itemCount++; + } else if (message->type == ES_MSG_REMOVE_CHILD) { + display->itemCount--; + } + + return 0; +} + +EsListDisplay *EsListDisplayCreate(EsElement *parent, uint64_t flags, const EsStyle *style) { + EsListDisplay *display = (EsListDisplay *) EsHeapAllocate(sizeof(EsListDisplay), true); + display->Initialise(parent, flags, ProcessListDisplayMessage, style ?: ES_STYLE_LIST_DISPLAY_DEFAULT); + display->cName = "list display"; + return display; +} + +void EsListDisplaySetCounterContinuation(EsListDisplay *display, EsListDisplay *previous) { + display->previous = previous; + EsElementRepaint(display); +} + +void EsListDisplaySetCounterStart(EsListDisplay *display, uintptr_t index) { + display->startIndex = index; + display->previous = nullptr; + EsElementRepaint(display); +} + +#endif diff --git a/desktop/theme.cpp b/desktop/theme.cpp new file mode 100644 index 0000000..3d67507 --- /dev/null +++ b/desktop/theme.cpp @@ -0,0 +1,2209 @@ +#define THEME_LAYER_BOX (1) +#define THEME_LAYER_TEXT (2) +#define THEME_LAYER_METRICS (3) +#define THEME_LAYER_PATH (4) + +#define THEME_PAINT_SOLID (1) +#define THEME_PAINT_LINEAR_GRADIENT (2) +#define THEME_PAINT_CUSTOM (3) +#define THEME_PAINT_OVERWRITE (4) +#define THEME_PAINT_RADIAL_GRADIENT (5) + +#define THEME_LAYER_MODE_BACKGROUND (0) +#define THEME_LAYER_MODE_SHADOW (1) +#define THEME_LAYER_MODE_CONTENT (2) +#define THEME_LAYER_MODE_OVERLAY (3) + +#define THEME_FONT_FAMILY_SANS (0) +#define THEME_FONT_FAMILY_SERIF (1) +#define THEME_FONT_FAMILY_MONO (2) + +#define THEME_OVERRIDE_I8 (1) +#define THEME_OVERRIDE_I16 (2) +#define THEME_OVERRIDE_F32 (3) +#define THEME_OVERRIDE_COLOR (4) + +#define THEME_PRIMARY_STATE_ANY (0) +#define THEME_PRIMARY_STATE_IDLE (1) +#define THEME_PRIMARY_STATE_HOVERED (2) +#define THEME_PRIMARY_STATE_PRESSED (3) +#define THEME_PRIMARY_STATE_DISABLED (4) +#define THEME_PRIMARY_STATE_MASK (0x000F) + +#define THEME_STATE_FOCUSED (1 << 15) +#define THEME_STATE_CHECKED (1 << 14) +#define THEME_STATE_INDETERMINATE (1 << 13) +#define THEME_STATE_DEFAULT_BUTTON (1 << 12) +#define THEME_STATE_SELECTED (1 << 11) +#define THEME_STATE_FOCUSED_ITEM (1 << 10) +#define THEME_STATE_LIST_FOCUSED (1 << 9) +#define THEME_STATE_BEFORE_ENTER (1 << 8) +#define THEME_STATE_AFTER_EXIT (1 << 7) + +#define THEME_STATE_CHECK(sequenceHeaderState, requestedState) \ + (((sequenceHeaderState & THEME_PRIMARY_STATE_MASK) == (requestedState & THEME_PRIMARY_STATE_MASK) \ + || ((sequenceHeaderState & THEME_PRIMARY_STATE_MASK) == THEME_PRIMARY_STATE_ANY)) \ + && !((sequenceHeaderState & ~THEME_PRIMARY_STATE_MASK) & ~(requestedState & ~THEME_PRIMARY_STATE_MASK))) \ + +#define THEME_LAYER_BOX_IS_BLURRED (1 << 0) +#define THEME_LAYER_BOX_AUTO_CORNERS (1 << 1) +#define THEME_LAYER_BOX_AUTO_BORDERS (1 << 2) +#define THEME_LAYER_BOX_SHADOW_HIDING (1 << 3) +#define THEME_LAYER_BOX_SHADOW_CUT (1 << 4) + +#define THEME_LAYER_PATH_FILL_EVEN_ODD (1 << 0) +#define THEME_LAYER_PATH_CLOSED (1 << 1) + +#define THEME_PATH_FILL_SOLID (1 << 4) +#define THEME_PATH_FILL_CONTOUR (2 << 4) +#define THEME_PATH_FILL_DASHED (3 << 4) + +#define THEME_HEADER_SIGNATURE (0x96BC555A) + +#define THEME_CHILD_TYPE_ONLY (0) +#define THEME_CHILD_TYPE_FIRST (1) +#define THEME_CHILD_TYPE_LAST (2) +#define THEME_CHILD_TYPE_NONE (3) +#define THEME_CHILD_TYPE_HORIZONTAL (1 << 4) + +typedef enum ThemeCursor { + THEME_CURSOR_NORMAL, + THEME_CURSOR_TEXT, + THEME_CURSOR_RESIZE_VERTICAL, + THEME_CURSOR_RESIZE_HORIZONTAL, + THEME_CURSOR_RESIZE_DIAGONAL_1, + THEME_CURSOR_RESIZE_DIAGONAL_2, + THEME_CURSOR_SPLIT_VERTICAL, + THEME_CURSOR_SPLIT_HORIZONTAL, + THEME_CURSOR_HAND_HOVER, + THEME_CURSOR_HAND_DRAG, + THEME_CURSOR_HAND_POINT, + THEME_CURSOR_SCROLL_UP_LEFT, + THEME_CURSOR_SCROLL_UP, + THEME_CURSOR_SCROLL_UP_RIGHT, + THEME_CURSOR_SCROLL_LEFT, + THEME_CURSOR_SCROLL_CENTER, + THEME_CURSOR_SCROLL_RIGHT, + THEME_CURSOR_SCROLL_DOWN_LEFT, + THEME_CURSOR_SCROLL_DOWN, + THEME_CURSOR_SCROLL_DOWN_RIGHT, + THEME_CURSOR_SELECT_LINES, + THEME_CURSOR_DROP_TEXT, + THEME_CURSOR_CROSS_HAIR_PICK, + THEME_CURSOR_CROSS_HAIR_RESIZE, + THEME_CURSOR_MOVE_HOVER, + THEME_CURSOR_MOVE_DRAG, + THEME_CURSOR_ROTATE_HOVER, + THEME_CURSOR_ROTATE_DRAG, + THEME_CURSOR_BLANK, +} ThemeCursor; + +typedef struct ThemePaintSolid { + uint32_t color; +} ThemePaintSolid; + +typedef struct ThemeGradientStop { + uint32_t color; + int8_t position; +} ThemeGradientStop; + +typedef struct ThemePaintLinearGradient { + float transform[3]; + uint8_t stopCount; + int8_t useGammaInterpolation : 1, useDithering : 1, useSystemHue : 1; + uint8_t repeatMode; + uint8_t _unused0; + // Followed by gradient stops. +} ThemePaintLinearGradient; + +typedef struct ThemePaintRadialGradient { + float transform[6]; + uint8_t stopCount; + int8_t useGammaInterpolation : 1, useDithering : 1; + uint8_t repeatMode; + uint8_t _unused0; + // Followed by gradient stops. +} ThemePaintRadialGradient; + +typedef struct ThemePaintCustom { +#ifndef IN_DESIGNER + EsFragmentShaderCallbackFunction callback; +#endif +} ThemePaintCustom; + +typedef struct ThemeLayerBox { + Rectangle8 borders; + Corners8 corners; + int8_t flags, mainPaintType, borderPaintType; + uint8_t _unused0; + uint32_t _unused1; // Align to 8 bytes for ThemePaintCustom. + // Followed by main paint data, then border paint data. +} ThemeLayerBox; + +typedef struct ThemeLayerText { + uint32_t color; + uint16_t _unused0; + int8_t blur; + uint8_t _unused1; +} ThemeLayerText; + +typedef struct ThemeLayerIcon { + int8_t align; + uint8_t _unused0; + uint16_t alpha; + Rectangle16 image; +} ThemeLayerIcon; + +typedef struct ThemeLayerImage { + uint16_t alpha; + uint16_t _unused0; + Rectangle16 image, contentRegion; +} ThemeLayerImage; + +typedef struct ThemeLayerPathFillContour { + float miterLimit; + uint8_t internalWidth, externalWidth; + uint8_t mode; +} ThemeLayerPathFillContour; + +typedef struct ThemeLayerPathFillDash { + ThemeLayerPathFillContour contour; + uint8_t length, gap; +} ThemeLayerPathFillDash; + +typedef struct ThemeLayerPathFill { + uint8_t paintAndFillType; + uint8_t dashCount; + uint16_t _unused0; + // Followed by paint data, then fill type specific data. +} ThemeLayerPathFill; + +typedef struct ThemeLayerPath { + uint8_t flags, fillCount; + uint16_t alpha, pointCount, _unused0; + // Followed by points (2*3 floats per point), then fills. +} ThemeLayerPath; + +typedef struct ThemeLayer { + uint32_t sequenceDataOffset; + Rectangle8 offset; // (dpx) + Rectangle8 position; // (percent) + int8_t mode, type; + uint16_t dataByteCount; + // Followed by type-specific data. +} ThemeLayer; + +typedef struct ThemeMetrics { + Rectangle16 insets, clipInsets; + Rectangle16 globalOffset; + uint8_t clipEnabled, cursor, entranceTransition, exitTransition; + uint16_t entranceDuration, exitDuration, fontFamily; + int16_t preferredWidth, preferredHeight; + int16_t minimumWidth, minimumHeight; + int16_t maximumWidth, maximumHeight; + int16_t gapMajor, gapMinor, gapWrap; + uint32_t textColor, selectedBackground, selectedText, iconColor; + int8_t textAlign, textSize, fontWeight, iconSize; + bool isItalic, ellipsis, layoutVertical; +} ThemeMetrics; + +typedef union ThemeVariant { + int8_t i8; + int16_t i16; + uint32_t u32; + float f32; +} ThemeVariant; + +typedef struct ThemeOverride { + uint16_t offset; + uint8_t type, layer; + ThemeVariant data; +} ThemeOverride; + +typedef struct ThemeSequenceHeader { + uint16_t state; // Low 4 bits are the primary state. + uint16_t overrideCount; + uint16_t duration; + uint8_t isLastSequence; + uint8_t _unused; + // Followed by overrides. +} ThemeSequenceHeader; + +typedef struct ThemeStyle { + uint32_t layerListOffset; // A list of uint32_t, giving offsets to ThemeLayer. First is the metrics layer. + uint16_t id; + uint8_t layerCount; + uint8_t _unused1; + Rectangle8 paintOutsets, opaqueInsets, approximateBorders; +} ThemeStyle; + +typedef struct ThemeConstant { + uint64_t hash; + uint32_t valueOffset; + uint32_t valueByteCount; + bool scale; +} ThemeConstant; + +typedef struct ThemeHeader { + uint32_t signature; + uint32_t styleCount, constantCount; + uint32_t bitmapBytes; + // Followed by array of ThemeStyles and then an array of ThemeConstants. + // The bitmap is at the end of the theme file. +} ThemeHeader; + +typedef struct BasicFontKerningEntry { + uint16_t leftGlyphIndex, rightGlyphIndex; + int16_t xAdvance; +} BasicFontKerningEntry; + +typedef struct BasicFontGlyph { + uint32_t codepoint; + int16_t xAdvance, xOffset, yOffset; + uint16_t width, height; + uint16_t pointCount; + uint32_t offsetToPoints; // Cubic bezier points. Contains 3*pointCount-2 of (x,y) float pairs. +} BasicFontGlyph; + +typedef struct BasicFontHeader { +#define BASIC_FONT_SIGNATURE (0x83259919) + uint32_t signature; + int32_t ascender, descender; + uint16_t glyphCount; + uint16_t kerningEntries; + // Followed by array of BasicFontGlyph. + // Followed by array of BasicFontKerningEntry. +} BasicFontHeader; + +////////////////////////////////////////// + +#define THEME_RECT_WIDTH(_r) ((_r).r - (_r).l) +#define THEME_RECT_HEIGHT(_r) ((_r).b - (_r).t) +#define THEME_RECT_4(x, y, z, w) ((EsRectangle) { (x), (y), (z), (w) }) +#define THEME_RECT_VALID(_r) (THEME_RECT_WIDTH(_r) > 0 && THEME_RECT_HEIGHT(_r) > 0) + +EsRectangle ThemeRectangleIntersection(EsRectangle a, EsRectangle b) { + if (a.l < b.l) a.l = b.l; + if (a.t < b.t) a.t = b.t; + if (a.r > b.r) a.r = b.r; + if (a.b > b.b) a.b = b.b; + return a; +} + +typedef struct ThemePaintData { + int8_t type; + + union { + const ThemePaintSolid *solid; + const ThemePaintLinearGradient *linearGradient; + const ThemePaintCustom *custom; + }; +} ThemePaintData; + +typedef struct GradientCache { +#define GRADIENT_CACHE_COUNT (256) + uint32_t colors[GRADIENT_CACHE_COUNT]; +#if 0 + uint32_t ditherColors[GRADIENT_CACHE_COUNT]; + uint32_t ditherThresholds[GRADIENT_CACHE_COUNT]; +#endif +#define GRADIENT_COORD_BASE (16) + int start, dx, dy; + int ox, oy; + bool opaque, dithered; + void *context; +} GradientCache; + +static const float gaussLookup[] = { + 1.000000, 0.999862, 0.999450, 0.998764, 0.997805, 0.996572, 0.995068, 0.993293, + 0.991249, 0.988937, 0.986360, 0.983520, 0.980418, 0.977058, 0.973442, 0.969573, + 0.965454, 0.961089, 0.956480, 0.951633, 0.946549, 0.941235, 0.935693, 0.929928, + 0.923946, 0.917749, 0.911344, 0.904735, 0.897927, 0.890926, 0.883736, 0.876364, + 0.868815, 0.861094, 0.853207, 0.845160, 0.836960, 0.828611, 0.820121, 0.811494, + 0.802738, 0.793858, 0.784861, 0.775752, 0.766539, 0.757227, 0.747823, 0.738333, + 0.728763, 0.719119, 0.709409, 0.699637, 0.689810, 0.679935, 0.670017, 0.660062, + 0.650077, 0.640067, 0.630038, 0.619995, 0.609946, 0.599894, 0.589846, 0.579807, + 0.569782, 0.559777, 0.549797, 0.539846, 0.529930, 0.520053, 0.510220, 0.500435, + 0.490704, 0.481029, 0.471416, 0.461867, 0.452388, 0.442982, 0.433653, 0.424403, + 0.415236, 0.406156, 0.397166, 0.388267, 0.379464, 0.370759, 0.362153, 0.353651, + 0.345253, 0.336962, 0.328780, 0.320708, 0.312749, 0.304903, 0.297173, 0.289559, + 0.282062, 0.274685, 0.267426, 0.260289, 0.253272, 0.246376, 0.239602, 0.232951, + 0.226422, 0.220016, 0.213732, 0.207571, 0.201532, 0.195614, 0.189819, 0.184144, + 0.178591, 0.173157, 0.167842, 0.162646, 0.157567, 0.152605, 0.147759, 0.143027, + 0.138409, 0.133903, 0.129508, 0.125223, 0.121047, 0.116978, 0.113014, 0.109155, + 0.105399, 0.101744, 0.098188, 0.094731, 0.091371, 0.088106, 0.084933, 0.081853, + 0.078863, 0.075961, 0.073146, 0.070415, 0.067769, 0.065203, 0.062718, 0.060310, + 0.057980, 0.055723, 0.053541, 0.051429, 0.049387, 0.047413, 0.045506, 0.043663, + 0.041883, 0.040165, 0.038507, 0.036907, 0.035364, 0.033876, 0.032442, 0.031060, + 0.029729, 0.028447, 0.027212, 0.026025, 0.024882, 0.023782, 0.022726, 0.021710, + 0.020734, 0.019796, 0.018895, 0.018031, 0.017201, 0.016405, 0.015642, 0.014910, + 0.014208, 0.013536, 0.012892, 0.012275, 0.011684, 0.011119, 0.010578, 0.010061, + 0.009567, 0.009094, 0.008642, 0.008211, 0.007798, 0.007405, 0.007029, 0.006671, + 0.006329, 0.006003, 0.005692, 0.005396, 0.005114, 0.004845, 0.004590, 0.004346, + 0.004114, 0.003894, 0.003684, 0.003485, 0.003295, 0.003115, 0.002944, 0.002782, + 0.002628, 0.002482, 0.002343, 0.002211, 0.002086, 0.001968, 0.001856, 0.001750, + 0.001649, 0.001554, 0.001464, 0.001378, 0.001298, 0.001221, 0.001149, 0.001081, + 0.001017, 0.000956, 0.000899, 0.000844, 0.000793, 0.000745, 0.000699, 0.000656, + 0.000616, 0.000578, 0.000542, 0.000508, 0.000476, 0.000446, 0.000418, 0.000391, + 0.000366, 0.000343, 0.000321, 0.000300, 0.000281, 0.000263, 0.000245, 0.000229, + 0.000214, 0.000200, 0.000187, 0.000174, 0.000163, 0.000152, 0.000141, 0.000000, +}; + +#ifndef IN_DESIGNER +struct UIStyleKey { + uintptr_t part; + float scale; + uint16_t stateFlags; + uint16_t _unused1; +}; + +struct { + EsBuffer system; + const ThemeHeader *header; + EsPaintTarget cursors; + float scale; + HashStore loadedStyles; + float systemHue; +} theming; +#endif + +__attribute__((optimize("-O2"))) +void ThemeFillRectangle(EsPainter *painter, EsRectangle bounds, ThemePaintData paint, GradientCache *gradient) { + uint32_t *bits = (uint32_t *) painter->target->bits; + int width = painter->target->width; + bounds = ThemeRectangleIntersection(bounds, painter->clip); + if (!THEME_RECT_VALID(bounds)) return; + + if (paint.type == THEME_PAINT_SOLID) { +#ifndef IN_DESIGNER + _DrawBlock(painter->target->stride, bits, bounds, paint.solid->color, painter->target->fullAlpha); +#else + uint32_t color = paint.solid->color; + + if ((color & 0xFF000000) == 0) { + } else if ((color & 0xFF000000) == 0xFF000000) { + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l; + uint32_t *b = bits + x + y * width; + do { *b = color; x++, b++; } while (x < bounds.r); + } + } else { + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l; + uint32_t *b = bits + x + y * width; + do { BlendPixel(b, color, painter->target->fullAlpha); x++, b++; } while (x < bounds.r); + } + } +#endif + } else if (paint.type == THEME_PAINT_LINEAR_GRADIENT) { +#if 0 + if (gradient->dithered) { + uint32_t random = 0; + + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l, p = (y - gradient->oy) * gradient->dy + (bounds.l - gradient->ox) * gradient->dx + gradient->start; + uint32_t *b = bits + bounds.l + y * width; + + do { + random = (random * 1103515245) + 12345; + uintptr_t index = ClampInteger(0, GRADIENT_CACHE_COUNT - 1, p >> GRADIENT_COORD_BASE); + + if (random > gradient->ditherThresholds[index]) { + *b = gradient->ditherColors[index]; + } else { + *b = gradient->colors[index]; + } + + x++, b++, p += gradient->dx; + } while (x < bounds.r); + } + } else +#endif + if (gradient->opaque) { + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l, p = (y - gradient->oy) * gradient->dy + (bounds.l - gradient->ox) * gradient->dx + gradient->start; + uint32_t *b = bits + bounds.l + y * width; + + do { + *b = gradient->colors[ClampInteger(0, GRADIENT_CACHE_COUNT - 1, p >> GRADIENT_COORD_BASE)]; + x++, b++, p += gradient->dx; + } while (x < bounds.r); + } + } else { + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l, p = (y - gradient->oy) * gradient->dy + (bounds.l - gradient->ox) * gradient->dx + gradient->start; + uint32_t *b = bits + bounds.l + y * width; + + do { + BlendPixel(b, gradient->colors[ClampInteger(0, GRADIENT_CACHE_COUNT - 1, p >> GRADIENT_COORD_BASE)], painter->target->fullAlpha); + x++, b++, p += gradient->dx; + } while (x < bounds.r); + } + } +#ifndef IN_DESIGNER + } else if (paint.type == THEME_PAINT_CUSTOM) { + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l; + uint32_t *b = bits + bounds.l + y * width; + + do { + BlendPixel(b, paint.custom->callback(x, y, (EsStyledBox *) gradient->context), painter->target->fullAlpha); + x++, b++; + } while (x < bounds.r); + } +#endif + } else if (paint.type == THEME_PAINT_OVERWRITE) { + uint32_t color = paint.solid->color; + + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l; + uint32_t *b = bits + x + y * width; + do { *b = color; x++, b++; } while (x < bounds.r); + } + } +} + +#ifndef IN_DESIGNER +__attribute__((optimize("-O2"))) +#endif +void ThemeFillCorner(EsPainter *painter, EsRectangle bounds, int cx, int cy, + int border, int corner, ThemePaintData mainPaint, ThemePaintData borderPaint, + GradientCache *mainGradient, GradientCache *borderGradient) { + uint32_t *bits = (uint32_t *) painter->target->bits; + int width = painter->target->width; + + int oldLeft = bounds.l, oldTop = bounds.t; + bounds = ThemeRectangleIntersection(bounds, painter->clip); + if (!THEME_RECT_VALID(bounds)) return; + cx += oldLeft - bounds.l, cy += oldTop - bounds.t; + +#define STYLE_CORNER_OVERSAMPLING (3) + border <<= STYLE_CORNER_OVERSAMPLING; + corner <<= STYLE_CORNER_OVERSAMPLING; + cx <<= STYLE_CORNER_OVERSAMPLING; + cy <<= STYLE_CORNER_OVERSAMPLING; + + int mainRadius2 = corner > border ? corner - border : 0; + int borderRadius2 = corner * corner; + mainRadius2 *= mainRadius2; + + int oversampledWidth = THEME_RECT_WIDTH(bounds) << STYLE_CORNER_OVERSAMPLING; + int oversampledHeight = THEME_RECT_HEIGHT(bounds) << STYLE_CORNER_OVERSAMPLING; + + uint32_t *b0 = bits + bounds.t * width; + + for (int j = 0; j < oversampledHeight; j += (1 << STYLE_CORNER_OVERSAMPLING), b0 += width) { + int i = 0; + uint32_t *b = b0 + bounds.l; + + do { + int mainCount = 0, borderCount = 0, outsideCount = 0; + + for (int x = 0; x < (1 << STYLE_CORNER_OVERSAMPLING); x++) { + for (int y = 0; y < (1 << STYLE_CORNER_OVERSAMPLING); y++) { + int dx = i + x - cx, dy = j + y - cy; + int radius2 = dx * dx + dy * dy; + if (radius2 <= mainRadius2) mainCount++; + else if (radius2 <= borderRadius2) borderCount++; + else outsideCount++; + } + } + + uint32_t mainColor, borderColor; + + if (mainPaint.type == THEME_PAINT_SOLID || mainPaint.type == THEME_PAINT_OVERWRITE) { + mainColor = mainPaint.solid->color; + } else if (mainPaint.type == THEME_PAINT_LINEAR_GRADIENT) { + mainColor = mainGradient->colors[ClampInteger(0, GRADIENT_CACHE_COUNT - 1, + (((j >> STYLE_CORNER_OVERSAMPLING) - mainGradient->oy + bounds.t) * mainGradient->dy + + ((i >> STYLE_CORNER_OVERSAMPLING) - mainGradient->ox + bounds.l) * mainGradient->dx + mainGradient->start) + >> GRADIENT_COORD_BASE)]; +#ifndef IN_DESIGNER + } else if (mainPaint.type == THEME_PAINT_CUSTOM) { + mainColor = mainPaint.custom->callback((i >> STYLE_CORNER_OVERSAMPLING) - mainGradient->ox + bounds.l, + (j >> STYLE_CORNER_OVERSAMPLING) - mainGradient->oy + bounds.t, (EsStyledBox *) mainGradient->context); +#endif + } else { + mainColor = 0; + } + + if (borderPaint.type == THEME_PAINT_SOLID || borderPaint.type == THEME_PAINT_OVERWRITE) { + borderColor = borderPaint.solid->color; + } else if (borderPaint.type == THEME_PAINT_LINEAR_GRADIENT) { + borderColor = borderGradient->colors[ClampInteger(0, GRADIENT_CACHE_COUNT - 1, + (((j >> STYLE_CORNER_OVERSAMPLING) - borderGradient->oy + bounds.t) * borderGradient->dy + + ((i >> STYLE_CORNER_OVERSAMPLING) - borderGradient->ox + bounds.l) * borderGradient->dx + borderGradient->start) + >> GRADIENT_COORD_BASE)]; + } else { + borderColor = 0; + } + + uint32_t mainAlpha = mainColor >> 24; + uint32_t borderAlpha = borderColor >> 24; + + if (outsideCount == (1 << (2 * STYLE_CORNER_OVERSAMPLING))) { + } else if (outsideCount || ((borderColor & 0xFF000000) != 0xFF000000) || (mainColor & 0xFF000000) != 0xFF000000) { + BlendPixel(b, (mainColor & 0x00FFFFFF) | (((mainAlpha * mainCount) << (24 - STYLE_CORNER_OVERSAMPLING * 2)) & 0xFF000000), + painter->target->fullAlpha); + BlendPixel(b, (borderColor & 0x00FFFFFF) | (((borderAlpha * borderCount) << (24 - STYLE_CORNER_OVERSAMPLING * 2)) & 0xFF000000), + painter->target->fullAlpha); + } else if (mainCount == (1 << (2 * STYLE_CORNER_OVERSAMPLING))) { + BlendPixel(b, mainColor, painter->target->fullAlpha); + } else if (borderCount == (1 << (2 * STYLE_CORNER_OVERSAMPLING))) { + BlendPixel(b, borderColor, painter->target->fullAlpha); + } else { + uint32_t blend = mainColor; + BlendPixel(&blend, (borderColor & 0x00FFFFFF) | (((borderAlpha * borderCount) << (24 - STYLE_CORNER_OVERSAMPLING * 2)) & 0xFF000000), true); + BlendPixel(b, blend, painter->target->fullAlpha); + } + + i += (1 << STYLE_CORNER_OVERSAMPLING), b++; + } while (i < oversampledWidth); + } +} + +#ifndef IN_DESIGNER +__attribute__((optimize("-O2"))) +#endif +void ThemeFillBlurCutCorner(EsPainter *painter, EsRectangle bounds, int cx, int cy, int border, int corner, GradientCache *gradient, ThemePaintData mainPaint) { + uint32_t *bits = (uint32_t *) painter->target->bits; + int width = painter->target->width; + cx += bounds.l, cy += bounds.t; + bounds = ThemeRectangleIntersection(bounds, painter->clip); + + int dp = (GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE) / border; + float mainRadius = corner > border ? corner - border : 0; + + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l; + uint32_t *b = bits + x + y * width; + + do { + int dx = x - cx, dy = y - cy; + float radius = EsCRTsqrtf(dx * dx + dy * dy) - mainRadius; + uint32_t color1 = 0, color2 = 0, color; + + if (radius < 1) { + if (mainPaint.type == THEME_PAINT_SOLID) { + color1 = mainPaint.solid->color; + } + } + + if (radius >= 0) { + color2 = gradient->colors[ClampInteger(0, GRADIENT_CACHE_COUNT - 1, + (int) (radius * dp) >> GRADIENT_COORD_BASE)]; + } + + if (radius < 0) { + color = color1; + } else if (radius < 1) { + int p = (uint32_t) (radius * 0x100); + int c10 = (color1 >> 24) & 0xFF, c11 = (color1 >> 16) & 0xFF, c12 = (color1 >> 8) & 0xFF, c13 = (color1 >> 0) & 0xFF; + int c20 = (color2 >> 24) & 0xFF, c21 = (color2 >> 16) & 0xFF, c22 = (color2 >> 8) & 0xFF, c23 = (color2 >> 0) & 0xFF; + int c30 = (c10 << 8) + p * (c20 - c10), c31 = (c11 << 8) + p * (c21 - c11), c32 = (c12 << 8) + p * (c22 - c12), c33 = (c13 << 8) + p * (c23 - c13); + color = ((c30 & 0xFF00) << 16) | ((c31 & 0xFF00) << 8) | ((c32 & 0xFF00) << 0) | ((c33 & 0xFF00) >> 8); + } else { + color = color2; + } + + BlendPixel(b, color, painter->target->fullAlpha); + + x++, b++; + } while (x < bounds.r); + } +} + +#ifndef IN_DESIGNER +__attribute__((optimize("-O2"))) +#endif +void ThemeFillBlurCorner(EsPainter *painter, EsRectangle bounds, int cx, int cy, int border, int corner, GradientCache *gradient) { + uint32_t *bits = (uint32_t *) painter->target->bits; + int width = painter->target->width; + cx += bounds.l, cy += bounds.t; + bounds = ThemeRectangleIntersection(bounds, painter->clip); + + int dp = (GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE) / border; + int mainRadius = corner > border ? corner - border : 0; + + for (int y = bounds.t; y < bounds.b; y++) { + int x = bounds.l; + uint32_t *b = bits + x + y * width; + + do { + int dx = x - cx, dy = y - cy; + int radius2 = dx * dx + dy * dy; + int p = (EsCRTsqrtf(radius2) - mainRadius) * dp; + BlendPixel(b, gradient->colors[ClampInteger(0, GRADIENT_CACHE_COUNT - 1, p >> GRADIENT_COORD_BASE)], painter->target->fullAlpha); + x++, b++; + } while (x < bounds.r); + } +} + +__attribute__((optimize("-O2"))) +void GradientCacheSetup(GradientCache *cache, const ThemePaintLinearGradient *gradient, int width, int height, EsBuffer *data) { + width--, height--; + + cache->dx = gradient->transform[0] / width * (GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE); + cache->dy = gradient->transform[1] / height * (GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE); + cache->start = gradient->transform[2] * (GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE); + + cache->opaque = true; + cache->dithered = gradient->useDithering; + + if (!gradient->stopCount) { + return; + } + + const ThemeGradientStop *stop0 = (ThemeGradientStop *) EsBufferRead(data, sizeof(ThemeGradientStop)); + + for (uintptr_t stop = 0; stop < (uintptr_t) (gradient->stopCount - 1); stop++) { + const ThemeGradientStop *stop1 = (ThemeGradientStop *) EsBufferRead(data, sizeof(ThemeGradientStop)); + + if (!stop1) { + return; + } + + uint32_t color0 = stop0->color; + uint32_t color1 = stop1->color; + +#ifndef IN_DESIGNER + if (gradient->useSystemHue) { + float h, h2, s, v; + EsColorConvertToHSV(color0, &h, &s, &v); + color0 = (color0 & 0xFF000000) | EsColorConvertToRGB(theming.systemHue, s, v); + EsColorConvertToHSV(color1, &h2, &s, &v); + color1 = (color1 & 0xFF000000) | EsColorConvertToRGB(theming.systemHue + (h2 - h), s, v); + } +#endif + + float fa = ((color0 >> 24) & 0xFF) / 255.0f; + float fb = ((color0 >> 16) & 0xFF) / 255.0f; + float fg = ((color0 >> 8) & 0xFF) / 255.0f; + float fr = ((color0 >> 0) & 0xFF) / 255.0f; + float ta = ((color1 >> 24) & 0xFF) / 255.0f; + float tb = ((color1 >> 16) & 0xFF) / 255.0f; + float tg = ((color1 >> 8) & 0xFF) / 255.0f; + float tr = ((color1 >> 0) & 0xFF) / 255.0f; + + if (fa && !ta) { tr = fr, tg = fg, tb = fb; } + if (ta && !fa) { fr = tr, fg = tg, fb = tb; } + + int fi = GRADIENT_CACHE_COUNT * (stop == 0 ? 0 : stop0->position / 100.0); + int ti = GRADIENT_CACHE_COUNT * (stop == (uintptr_t) (gradient->stopCount - 2) ? 1 : stop1->position / 100.0); + if (fi < 0) fi = 0; + if (ti > GRADIENT_CACHE_COUNT) ti = GRADIENT_CACHE_COUNT; + + for (int i = fi; i < ti; i++) { + float p = (float) (i - fi) / (ti - fi - 1); + + if (p < 0) p = 0; + if (p > 1) p = 1; + + if (gradient->useGammaInterpolation) { + cache->colors[i] = (uint32_t) (GammaInterpolate(fr, tr, p) * 255.0f) << 0 + | (uint32_t) (GammaInterpolate(fg, tg, p) * 255.0f) << 8 + | (uint32_t) (GammaInterpolate(fb, tb, p) * 255.0f) << 16 + | (uint32_t) ((fa + (ta - fa) * p) * 255.0f) << 24; + } else { + cache->colors[i] = (uint32_t) (LinearInterpolate(fr, tr, p) * 255.0f) << 0 + | (uint32_t) (LinearInterpolate(fg, tg, p) * 255.0f) << 8 + | (uint32_t) (LinearInterpolate(fb, tb, p) * 255.0f) << 16 + | (uint32_t) ((fa + (ta - fa) * p) * 255.0f) << 24; + } + + if ((cache->colors[i] & 0xFF000000) != 0xFF000000) { + cache->opaque = false; + } + } + + stop0 = stop1; + } + +#if 0 + if (gradient->useDithering) { + for (uintptr_t i = 0; i < GRADIENT_CACHE_COUNT; i++) { + uint32_t mainColor = cache->colors[i]; + uint32_t ditherColor = mainColor; + int forwardSteps = 0, totalSteps = 0; + + for (uintptr_t j = i + 1; j < GRADIENT_CACHE_COUNT; j++) { + forwardSteps++; + + if (cache->colors[j] != mainColor) { + ditherColor = cache->colors[j]; + break; + } + } + + totalSteps = forwardSteps; + + for (intptr_t j = i - 1; j >= 0; j--) { + totalSteps++; + + if (cache->colors[j] != mainColor) { + break; + } + } + + if (!totalSteps) { + totalSteps = 1; + } + + cache->ditherColors[i] = ditherColor; + cache->ditherThresholds[i] = (uint32_t) ((float) forwardSteps / totalSteps * 65535.0f) << 16; + } + } +#endif +} + +void ThemeDrawBox(EsPainter *painter, EsRectangle rect, EsBuffer *data, float scale, + const ThemeLayer *layer, EsRectangle opaqueRegion, int childType) { + int width = THEME_RECT_WIDTH(rect), height = THEME_RECT_HEIGHT(rect); + if (width <= 0 || height <= 0) return; + + const ThemeLayerBox *box = (const ThemeLayerBox *) EsBufferRead(data, sizeof(ThemeLayerBox)); + if (!box) return; + + if ((box->flags & THEME_LAYER_BOX_SHADOW_HIDING) && layer->offset.l == 0 && layer->offset.r == 0 && layer->offset.t == 0 && layer->offset.b == 0) { + return; + } + + bool isBlurred = box->flags & THEME_LAYER_BOX_IS_BLURRED; + +#if 0 + if (scale == 1 && isBlurred) { + width--, rect.r--; + height--, rect.b--; + } +#endif + + ThemePaintData mainPaint, borderPaint; + GradientCache mainGradient, borderGradient; + + mainPaint.type = box->mainPaintType; + borderPaint.type = box->borderPaintType; + + if (mainPaint.type == 0) { + } else if (mainPaint.type == THEME_PAINT_SOLID || mainPaint.type == THEME_PAINT_OVERWRITE) { + mainPaint.solid = (const ThemePaintSolid *) EsBufferRead(data, sizeof(ThemePaintSolid)); + } else if (mainPaint.type == THEME_PAINT_LINEAR_GRADIENT) { + mainPaint.linearGradient = (const ThemePaintLinearGradient *) EsBufferRead(data, sizeof(ThemePaintLinearGradient)); + GradientCacheSetup(&mainGradient, mainPaint.linearGradient, width, height, data); + mainGradient.ox = rect.l; + mainGradient.oy = rect.t; + } else if (mainPaint.type == THEME_PAINT_CUSTOM && data->context) { + mainPaint.custom = (const ThemePaintCustom *) EsBufferRead(data, sizeof(ThemePaintCustom)); + mainGradient.ox = rect.l; + mainGradient.oy = rect.t; + mainGradient.context = data->context; + } else { + data->error = true; + } + + if (borderPaint.type == 0) { + } else if (borderPaint.type == THEME_PAINT_SOLID || borderPaint.type == THEME_PAINT_OVERWRITE) { + borderPaint.solid = (ThemePaintSolid *) EsBufferRead(data, sizeof(ThemePaintSolid)); + } else if (borderPaint.type == THEME_PAINT_LINEAR_GRADIENT) { + borderPaint.linearGradient = (ThemePaintLinearGradient *) EsBufferRead(data, sizeof(ThemePaintLinearGradient)); + if (!data->error) GradientCacheSetup(&borderGradient, borderPaint.linearGradient, width, height, data); + borderGradient.ox = rect.l; + borderGradient.oy = rect.t; + } else { + data->error = true; + } + + if (data->error) { + return; + } + + if (mainPaint.type == 0 && borderPaint.type == 0) { + return; + } + + if (isBlurred && borderPaint.type == THEME_PAINT_SOLID) { + float alpha = borderPaint.solid->color >> 24; + uint32_t color = borderPaint.solid->color & 0xFFFFFF; + borderGradient.start = 0; + borderGradient.opaque = false; + borderGradient.dithered = false; + + for (uintptr_t i = 0; i < GRADIENT_CACHE_COUNT; i++) { + borderGradient.colors[i] = color | (uint32_t) (alpha * gaussLookup[i]) << 24; + } + + if (~box->flags & THEME_LAYER_BOX_SHADOW_CUT) { + mainPaint.type = THEME_PAINT_SOLID; + mainPaint.solid = borderPaint.solid; + } + + borderPaint.type = THEME_PAINT_LINEAR_GRADIENT; + borderPaint.linearGradient = NULL; + } + + Corners32 corners = { (int) (box->corners.tl * scale + 0.5f), (int) (box->corners.tr * scale + 0.5f), + (int) (box->corners.bl * scale + 0.5f), (int) (box->corners.br * scale + 0.5f) }; + + if (box->flags & THEME_LAYER_BOX_AUTO_CORNERS) { + bool horizontal = childType & THEME_CHILD_TYPE_HORIZONTAL; + int c = childType & ~THEME_CHILD_TYPE_HORIZONTAL; + + if (c == THEME_CHILD_TYPE_FIRST) { + if (horizontal) corners.tr = corners.br = 0; else corners.bl = corners.br = 0; + } else if (c == THEME_CHILD_TYPE_LAST) { + if (horizontal) corners.tl = corners.bl = 0; else corners.tl = corners.tr = 0; + } else if (c != THEME_CHILD_TYPE_ONLY) { + corners.tl = corners.tr = corners.bl = corners.br = 0; + } + } + + if (isBlurred) { + corners.bl = corners.tr = corners.br = corners.tl; + } + + if (corners.tl + corners.tr > width) { float p = (float) corners.tl / (corners.tl + corners.tr); corners.tl = p * width; corners.tr = (1 - p) * width; } + if (corners.bl + corners.br > width) { float p = (float) corners.bl / (corners.bl + corners.br); corners.bl = p * width; corners.br = (1 - p) * width; } + if (corners.tl + corners.bl > height) { float p = (float) corners.tl / (corners.tl + corners.bl); corners.tl = p * height; corners.bl = (1 - p) * height; } + if (corners.tr + corners.br > height) { float p = (float) corners.tr / (corners.tr + corners.br); corners.tr = p * height; corners.br = (1 - p) * height; } + + Rectangle32 borders = { (int) (box->borders.l * scale), (int) (box->borders.r * scale), + (int) (box->borders.t * scale), (int) (box->borders.b * scale) }; + + if (box->flags & THEME_LAYER_BOX_AUTO_BORDERS) { + bool horizontal = childType & THEME_CHILD_TYPE_HORIZONTAL; + int c = childType & ~THEME_CHILD_TYPE_HORIZONTAL; + + if (c == THEME_CHILD_TYPE_FIRST) { + if (horizontal) borders.r = 0; else borders.b = 0; + } else if (c == THEME_CHILD_TYPE_LAST) { + if (horizontal) borders.l = 0; else borders.t = 0; + } else if (c != THEME_CHILD_TYPE_ONLY) { + if (horizontal) borders.l = borders.r = 0; else borders.t = borders.b = 0; + } + } + + if (isBlurred) { + borders.r = borders.t = borders.b = borders.l; + } + + if (borders.l + borders.r > width) { float p = (float) borders.l / (borders.l + borders.r); borders.l = p * width; borders.r = (1 - p) * width; } + if (borders.t + borders.b > height) { float p = (float) borders.t / (borders.t + borders.b); borders.t = p * height; borders.b = (1 - p) * height; } + + if (isBlurred && !borders.l) { + return; + } + + Corners32 cornerBorders = { MaximumInteger(borders.l, borders.t), MaximumInteger(borders.t, borders.r), + MaximumInteger(borders.l, borders.b), MaximumInteger(borders.b, borders.r) }; + + if (isBlurred) { + borderGradient.dx = -(GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE) / borders.l; + borderGradient.ox = rect.l + borders.l; + borderGradient.dy = borderGradient.oy = 0; + } + + ThemeFillRectangle(painter, THEME_RECT_4(rect.l, rect.l + borders.l, rect.t + MaximumInteger(borders.t, corners.tl), + rect.b - MaximumInteger(borders.b, corners.bl)), borderPaint, &borderGradient); + + if (isBlurred) { + borderGradient.dx = (GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE) / borders.r; + borderGradient.ox = rect.r - borders.r - 1; + } + + ThemeFillRectangle(painter, THEME_RECT_4(rect.r - borders.r, rect.r, rect.t + MaximumInteger(borders.t, corners.tr), + rect.b - MaximumInteger(borders.b, corners.br)), borderPaint, &borderGradient); + + if (isBlurred) { + borderGradient.dy = -(GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE) / borders.t; + borderGradient.oy = rect.t + borders.t; + borderGradient.dx = borderGradient.ox = 0; + } + + ThemeFillRectangle(painter, THEME_RECT_4(rect.l + corners.tl, rect.r - corners.tr, rect.t, rect.t + borders.t), borderPaint, &borderGradient); + + if (isBlurred) { + borderGradient.dy = (GRADIENT_CACHE_COUNT << GRADIENT_COORD_BASE) / borders.b; + borderGradient.oy = rect.b - borders.b - 1; + } + + ThemeFillRectangle(painter, THEME_RECT_4(rect.l + corners.bl, rect.r - corners.br, rect.b - borders.b, rect.b), borderPaint, &borderGradient); + + ThemeFillRectangle(painter, THEME_RECT_4(rect.l + corners.tl, rect.r - corners.tr, + rect.t + borders.t, rect.t + MaximumInteger(MinimumInteger(corners.tl, corners.tr), borders.t)), mainPaint, &mainGradient); + ThemeFillRectangle(painter, THEME_RECT_4(rect.l + (corners.tl > corners.tr ? corners.tl : borders.l), + rect.r - (corners.tl > corners.tr ? borders.r : corners.tr), + rect.t + MaximumInteger(MinimumInteger(corners.tl, corners.tr), borders.t), + rect.t + MaximumInteger3(corners.tl, corners.tr, borders.t)), mainPaint, &mainGradient); + + EsRectangle mainBlock = THEME_RECT_4(rect.l + borders.l, rect.r - borders.r, + rect.t + MaximumInteger3(corners.tl, corners.tr, borders.t), + rect.b - MaximumInteger3(corners.bl, corners.br, borders.b)); + + if ((box->flags & THEME_LAYER_BOX_SHADOW_HIDING) && THEME_RECT_VALID(opaqueRegion)) { + EsRectangle neededBorders = THEME_RECT_4(MaximumInteger(opaqueRegion.l - mainBlock.l, 0), MaximumInteger(mainBlock.r - opaqueRegion.r, 0), + MaximumInteger(opaqueRegion.t - mainBlock.t, 0), MaximumInteger(mainBlock.b - opaqueRegion.b, 0)); + ThemeFillRectangle(painter, THEME_RECT_4(mainBlock.l, mainBlock.r, mainBlock.t, mainBlock.t + neededBorders.t), mainPaint, &mainGradient); + ThemeFillRectangle(painter, THEME_RECT_4(mainBlock.l, mainBlock.l + neededBorders.l, + mainBlock.t + neededBorders.t, mainBlock.b - neededBorders.b), mainPaint, &mainGradient); + ThemeFillRectangle(painter, THEME_RECT_4(mainBlock.r - neededBorders.r, mainBlock.r, + mainBlock.t + neededBorders.t, mainBlock.b - neededBorders.b), mainPaint, &mainGradient); + ThemeFillRectangle(painter, THEME_RECT_4(mainBlock.l, mainBlock.r, mainBlock.b - neededBorders.b, mainBlock.b), mainPaint, &mainGradient); + } else { + ThemeFillRectangle(painter, mainBlock, mainPaint, &mainGradient); + } + + ThemeFillRectangle(painter, THEME_RECT_4(rect.l + (corners.bl > corners.br ? corners.bl : borders.l), + rect.r - (corners.bl > corners.br ? borders.r : corners.br), + rect.b - MaximumInteger3(corners.bl, corners.br, borders.b), + rect.b - MaximumInteger(MinimumInteger(corners.bl, corners.br), borders.b)), mainPaint, &mainGradient); + ThemeFillRectangle(painter, THEME_RECT_4(rect.l + corners.bl, rect.r - corners.br, + rect.b - MaximumInteger(MinimumInteger(corners.bl, corners.br), borders.b), + rect.b - borders.b), mainPaint, &mainGradient); + + if (cornerBorders.tl >= corners.tl) { + ThemeFillRectangle(painter, THEME_RECT_4(rect.l, rect.l + corners.tl, rect.t + corners.tl, rect.t + cornerBorders.tl), borderPaint, &borderGradient); + } + + if (cornerBorders.tr >= corners.tr) { + ThemeFillRectangle(painter, THEME_RECT_4(rect.r - corners.tr, rect.r, rect.t + corners.tr, rect.t + cornerBorders.tr), borderPaint, &borderGradient); + } + + if (cornerBorders.bl >= corners.bl) { + ThemeFillRectangle(painter, THEME_RECT_4(rect.l, rect.l + corners.bl, rect.b - cornerBorders.bl, rect.b - corners.bl), borderPaint, &borderGradient); + } + + if (cornerBorders.br >= corners.br) { + ThemeFillRectangle(painter, THEME_RECT_4(rect.r - corners.br, rect.r, rect.b - cornerBorders.br, rect.b - corners.br), borderPaint, &borderGradient); + } + + if (box->flags & THEME_LAYER_BOX_SHADOW_CUT) { + ThemeFillBlurCutCorner(painter, THEME_RECT_4(rect.l, rect.l + corners.tl, rect.t, rect.t + corners.tl), + corners.tl, corners.tl, borders.l, corners.tl, &borderGradient, mainPaint); + ThemeFillBlurCutCorner(painter, THEME_RECT_4(rect.r - corners.tr, rect.r, rect.t, rect.t + corners.tr), + -1, corners.tr, borders.l, corners.tr, &borderGradient, mainPaint); + ThemeFillBlurCutCorner(painter, THEME_RECT_4(rect.l, rect.l + corners.bl, rect.b - corners.bl, rect.b), + corners.bl, -1, borders.l, corners.bl, &borderGradient, mainPaint); + ThemeFillBlurCutCorner(painter, THEME_RECT_4(rect.r - corners.br, rect.r, rect.b - corners.br, rect.b), + -1, -1, borders.l, corners.br, &borderGradient, mainPaint); + } else if (isBlurred) { + ThemeFillBlurCorner(painter, THEME_RECT_4(rect.l, rect.l + corners.tl, rect.t, rect.t + corners.tl), + corners.tl, corners.tl, borders.l, corners.tl, &borderGradient); + ThemeFillBlurCorner(painter, THEME_RECT_4(rect.r - corners.tr, rect.r, rect.t, rect.t + corners.tr), + -1, corners.tr, borders.l, corners.tr, &borderGradient); + ThemeFillBlurCorner(painter, THEME_RECT_4(rect.l, rect.l + corners.bl, rect.b - corners.bl, rect.b), + corners.bl, -1, borders.l, corners.bl, &borderGradient); + ThemeFillBlurCorner(painter, THEME_RECT_4(rect.r - corners.br, rect.r, rect.b - corners.br, rect.b), + -1, -1, borders.l, corners.br, &borderGradient); + } else { + ThemeFillCorner(painter, THEME_RECT_4(rect.l, rect.l + corners.tl, rect.t, rect.t + corners.tl), + corners.tl, corners.tl, cornerBorders.tl, corners.tl, mainPaint, borderPaint, &mainGradient, &borderGradient); + ThemeFillCorner(painter, THEME_RECT_4(rect.r - corners.tr, rect.r, rect.t, rect.t + corners.tr), + 0, corners.tr, cornerBorders.tr, corners.tr, mainPaint, borderPaint, &mainGradient, &borderGradient); + ThemeFillCorner(painter, THEME_RECT_4(rect.l, rect.l + corners.bl, rect.b - corners.bl, rect.b), + corners.bl, 0, cornerBorders.bl, corners.bl, mainPaint, borderPaint, &mainGradient, &borderGradient); + ThemeFillCorner(painter, THEME_RECT_4(rect.r - corners.br, rect.r, rect.b - corners.br, rect.b), + 0, 0, cornerBorders.br, corners.br, mainPaint, borderPaint, &mainGradient, &borderGradient); + } +} + +void ThemeDrawPath(EsPainter *painter, EsRectangle rect, EsBuffer *data, float scale) { + int width = THEME_RECT_WIDTH(rect), height = THEME_RECT_HEIGHT(rect); + if (width <= 0 || height <= 0) return; + + const ThemeLayerPath *layer = (const ThemeLayerPath *) EsBufferRead(data, sizeof(ThemeLayerPath)); + if (!layer) return; + if (!layer->pointCount) return; + if (!layer->alpha) return; + const float *points = (const float *) EsBufferRead(data, layer->pointCount * 6 * sizeof(float)); + if (!points) return; + + EsRectangle bounds; + EsRectangleClip(rect, painter->clip, &bounds); + + if (!THEME_RECT_VALID(bounds)) { + return; + } + + RastVertex scale2 = { width / 100.0f, height / 100.0f }; + + RastPath path = {}; + + for (uintptr_t i = 0, j = 0; i < layer->pointCount * 3; i += 3) { + if ((int) i == layer->pointCount * 3 - 3 || points[i * 2 + 2] == -1e6) { + RastPathAppendBezier(&path, (RastVertex *) points + j, i - j + 1, scale2); + RastPathCloseSegment(&path); + j = i + 3; + } + } + + RastPathTranslate(&path, rect.l - painter->clip.l, rect.t - painter->clip.t); + + RastSurface surface = {}; + + float xOffset = rect.l - painter->clip.l; + float yOffset = rect.t - painter->clip.t; + + if (!RastSurfaceInitialise(&surface, THEME_RECT_WIDTH(painter->clip), THEME_RECT_HEIGHT(painter->clip), false)) { + goto error; + } + + for (uintptr_t i = 0; i < layer->fillCount; i++) { + const ThemeLayerPathFill *fill = (const ThemeLayerPathFill *) EsBufferRead(data, sizeof(ThemeLayerPathFill)); + if (!fill) return; + + RastPaint paint = {}; + RastShape shape = {}; + + if ((fill->paintAndFillType & 0x0F) == THEME_PAINT_SOLID) { + const ThemePaintSolid *p = (const ThemePaintSolid *) EsBufferRead(data, sizeof(ThemePaintSolid)); + if (!p) return; + paint.type = RAST_PAINT_SOLID; + paint.solid.color = p->color & 0xFFFFFF; + paint.solid.alpha = (p->color >> 24) / 255.0f; + } else if ((fill->paintAndFillType & 0x0F) == THEME_PAINT_LINEAR_GRADIENT) { + RastGradientStop stops[16]; + size_t stopCount = 0; + + const ThemePaintLinearGradient *p = (const ThemePaintLinearGradient *) EsBufferRead(data, sizeof(ThemePaintLinearGradient)); + if (!p) return; + + for (uintptr_t i = 0; i < p->stopCount && i < 16; i++, stopCount++) { + const ThemeGradientStop *stop = (const ThemeGradientStop *) EsBufferRead(data, sizeof(ThemeGradientStop)); + if (!stop) return; + stops[i].color = stop->color; + stops[i].position = stop->position / 100.0f; + } + + paint.type = RAST_PAINT_LINEAR_GRADIENT; + paint.gradient.repeatMode = (RastRepeatMode) p->repeatMode; + paint.gradient.transform[0] = p->transform[0] / THEME_RECT_WIDTH(rect); + paint.gradient.transform[1] = p->transform[1] / THEME_RECT_HEIGHT(rect); + paint.gradient.transform[2] = p->transform[2] - (paint.gradient.transform[0] * xOffset + paint.gradient.transform[1] * yOffset); + + RastGradientInitialise(&paint, stops, stopCount, p->useGammaInterpolation); + } else if ((fill->paintAndFillType & 0x0F) == THEME_PAINT_RADIAL_GRADIENT) { + RastGradientStop stops[16]; + size_t stopCount = 0; + + const ThemePaintRadialGradient *p = (const ThemePaintRadialGradient *) EsBufferRead(data, sizeof(ThemePaintRadialGradient)); + if (!p) return; + + for (uintptr_t i = 0; i < p->stopCount && i < 16; i++, stopCount++) { + const ThemeGradientStop *stop = (const ThemeGradientStop *) EsBufferRead(data, sizeof(ThemeGradientStop)); + if (!stop) return; + stops[i].color = stop->color; + stops[i].position = stop->position / 100.0f; + } + + paint.type = RAST_PAINT_RADIAL_GRADIENT; + paint.gradient.repeatMode = (RastRepeatMode) p->repeatMode; + paint.gradient.transform[0] = p->transform[0] / THEME_RECT_WIDTH(rect); + paint.gradient.transform[1] = p->transform[1] / THEME_RECT_HEIGHT(rect); + paint.gradient.transform[2] = p->transform[2] - (paint.gradient.transform[0] * xOffset + paint.gradient.transform[1] * yOffset); + paint.gradient.transform[3] = p->transform[3] / THEME_RECT_WIDTH(rect); + paint.gradient.transform[4] = p->transform[4] / THEME_RECT_HEIGHT(rect); + paint.gradient.transform[5] = p->transform[5] - (paint.gradient.transform[3] * xOffset + paint.gradient.transform[4] * yOffset); + + RastGradientInitialise(&paint, stops, stopCount, p->useGammaInterpolation); + } else { + // TODO Checkboards, angular gradients and noise. + } + + if ((fill->paintAndFillType & 0xF0) == THEME_PATH_FILL_SOLID) { + shape = RastShapeCreateSolid(&path); + } else if ((fill->paintAndFillType & 0xF0) == THEME_PATH_FILL_CONTOUR) { + const ThemeLayerPathFillContour *contour = (const ThemeLayerPathFillContour *) EsBufferRead(data, sizeof(ThemeLayerPathFillContour)); + if (!contour) return; + RastContourStyle style = {}; + style.internalWidth = contour->internalWidth * scale; + style.externalWidth = contour->externalWidth * scale; + style.joinMode = (RastLineJoinMode) ((contour->mode >> 0) & 3); + style.capMode = (RastLineCapMode) ((contour->mode >> 2) & 3); + style.miterLimit = contour->miterLimit * scale; + + if (contour->mode & 0x80) { + style.internalWidth = EsCRTfloorf(style.internalWidth); + style.externalWidth = EsCRTfloorf(style.externalWidth); + } + + shape = RastShapeCreateContour(&path, style, ~layer->flags & THEME_LAYER_PATH_CLOSED); + } else if ((fill->paintAndFillType & 0xF0) == THEME_PATH_FILL_DASHED) { + RastDash dashes[16]; + RastContourStyle styles[16]; + size_t styleCount = 0; + + for (uintptr_t i = 0; i < fill->dashCount && i < 16; i++, styleCount++) { + const ThemeLayerPathFillDash *dash = (const ThemeLayerPathFillDash *) EsBufferRead(data, sizeof(ThemeLayerPathFillDash)); + if (!dash) return; + dashes[i].gap = dash->gap * scale; + dashes[i].length = dash->length * scale; + dashes[i].style = styles + i; + styles[i].internalWidth = dash->contour.internalWidth * scale; + styles[i].externalWidth = dash->contour.externalWidth * scale; + styles[i].joinMode = (RastLineJoinMode) ((dash->contour.mode >> 0) & 3); + styles[i].capMode = (RastLineCapMode) ((dash->contour.mode >> 2) & 3); + styles[i].miterLimit = dash->contour.miterLimit * scale; + + if (dash->contour.mode & 0x80) { + styles[i].internalWidth = EsCRTfloorf(styles[i].internalWidth); + styles[i].externalWidth = EsCRTfloorf(styles[i].externalWidth); + } + } + + shape = RastShapeCreateDashed(&path, dashes, styleCount, ~layer->flags & THEME_LAYER_PATH_CLOSED); + } + + RastSurfaceFill(surface, shape, paint, layer->flags & THEME_LAYER_PATH_FILL_EVEN_ODD); + + RastGradientDestroy(&paint); + } + + // TODO Use drawn bounding box. + + for (int32_t j = painter->clip.t; j < painter->clip.b; j++) { + uint32_t *source = surface.buffer + (j - painter->clip.t) * surface.stride / 4; + uint32_t *destination = (uint32_t *) painter->target->bits + j * painter->target->stride / 4 + painter->clip.l; + + for (int32_t i = painter->clip.l; i < painter->clip.r; i++) { + uint32_t s = *source; + + if (layer->alpha != 0xFF) { + uint32_t alpha = s >> 24; + + if (alpha) { + alpha *= layer->alpha; + alpha >>= 8; + s = (s & 0xFFFFFF) | (alpha << 24); + BlendPixel(destination, s, painter->target->fullAlpha); + } + } else { + BlendPixel(destination, s, painter->target->fullAlpha); + } + + destination++, source++; + } + } + + error:; + RastSurfaceDestroy(&surface); + RastPathDestroy(&path); +} + +#ifdef IN_DESIGNER + +void ThemeDrawLayer(EsPainter *painter, EsRectangle _bounds, EsBuffer *data, float scale, EsRectangle opaqueRegion) { + const ThemeLayer *layer = (const ThemeLayer *) EsBufferRead(data, sizeof(ThemeLayer)); + + if (!layer) { + return; + } + + EsRectangle bounds; + bounds.l = _bounds.l + (int) (scale * layer->offset.l) + THEME_RECT_WIDTH(_bounds) * layer->position.l / 100; + bounds.r = _bounds.l + (int) (scale * layer->offset.r) + THEME_RECT_WIDTH(_bounds) * layer->position.r / 100; + bounds.t = _bounds.t + (int) (scale * layer->offset.t) + THEME_RECT_HEIGHT(_bounds) * layer->position.t / 100; + bounds.b = _bounds.t + (int) (scale * layer->offset.b) + THEME_RECT_HEIGHT(_bounds) * layer->position.b / 100; + + if (THEME_RECT_WIDTH(bounds) <= 0 || THEME_RECT_HEIGHT(bounds) <= 0) { + if (layer->dataByteCount) { + EsBufferRead(data, layer->dataByteCount - sizeof(ThemeLayer)); + } + } else { + if (layer->type == THEME_LAYER_BOX) { + ThemeDrawBox(painter, bounds, data, scale, layer, opaqueRegion, 0); + } else if (layer->type == THEME_LAYER_PATH) { + ThemeDrawPath(painter, bounds, data, scale); + } else if (layer->type == THEME_LAYER_METRICS) { + EsBufferRead(data, sizeof(ThemeMetrics)); + } + } +} + +#endif + +////////////////////////////////////////// + +#ifndef IN_DESIGNER + +typedef struct ThemeAnimatingProperty { + uint16_t offset; // Offset into the theme data. + uint8_t type : 4, beforeEnter : 1, _unused1 : 3; // Interpolation type. + uint8_t _unused0; + uint16_t duration, elapsed; // Milliseconds. + ThemeVariant from; // Value to interpolate from. +} ThemeAnimatingProperty; + +typedef struct ThemeAnimation { + Array properties; +} ThemeAnimation; + +struct UIStyle { + intptr_t referenceCount; + + uint32_t observedStyleStateMask; + bool IsStateChangeObserved(uint16_t state1, uint16_t state2); + + // General information. + + uint8_t textSize, textAlign; + uint32_t textColor; + EsFont font; + + EsRectangle insets, borders; + uint16_t preferredWidth, preferredHeight; + int16_t gapMajor, gapMinor, gapWrap; + + EsRectangle paintOutsets, opaqueInsets; + + float scale; + + EsThemeAppearance *appearance; // An optional, custom appearance provided by the application. + + // Data. + + uint16_t layerDataByteCount; + const ThemeStyle *style; + ThemeMetrics *metrics; // Points to correct position in layer data. + // Followed by overrides, then layer data. + // The overrides store the base value, and the layer data contains the overriden values. + + // Loaded styles management. + + void CloseReference(); + + // Painting. + + void PaintText(EsPainter *painter, EsElement *element, EsRectangle rectangle, const char *text, size_t textBytes, + uint32_t iconID, uint32_t flags, EsTextSelection *selectionProperties = nullptr); + void PaintLayers(EsPainter *painter, EsRectangle rectangle, int childType, int whichLayers); + void PaintTextLayers(EsPainter *painter, EsTextPlan *plan, EsRectangle textBounds, EsTextSelection *selectionProperties); + + // Misc. + + bool IsRegionCompletelyOpaque(EsRectangle region, int width, int height); + + inline void GetTextStyle(EsTextStyle *style); + + inline int GetLineHeight() { + EsTextStyle style; + GetTextStyle(&style); + return EsTextGetLineHeight(&style); + } + + inline int MeasureTextWidth(const char *text, size_t textBytes) { + EsTextStyle style; + GetTextStyle(&style); + return TextGetStringWidth(&style, text, textBytes); + } +}; + +const void *GetConstant(const char *cKey, size_t *byteCount, bool *scale) { + EsBuffer data = theming.system; + const ThemeHeader *header = (const ThemeHeader *) EsBufferRead(&data, sizeof(ThemeHeader)); + EsBufferRead(&data, sizeof(ThemeStyle) * header->styleCount); + + uint64_t hash = CalculateCRC64(EsLiteral(cKey)); + + for (uintptr_t i = 0; i < header->constantCount; i++) { + const ThemeConstant *constant = (const ThemeConstant *) EsBufferRead(&data, sizeof(ThemeConstant)); + + if (!constant) { + EsPrint("Broken theme constants list.\n"); + break; + } + + if (constant->hash == hash) { + data.position = constant->valueOffset; + const void *value = EsBufferRead(&data, (constant->valueByteCount + 3) & ~3); + + if (!value) { + EsPrint("Broken theme constant value.\n"); + return 0; + } + + *byteCount = constant->valueByteCount; + *scale = constant->scale; + return value; + } + } + + EsPrint("Could not find theme constant with key \"%z\".\n", cKey); + return nullptr; +} + +int GetConstantNumber(const char *cKey) { + size_t byteCount; + bool scale = false; + const void *value = GetConstant(cKey, &byteCount, &scale); + int integer = value ? EsIntegerParse((const char *) value, byteCount) : 0; + if (scale) integer *= theming.scale; + return integer; +} + +EsRectangle GetConstantRectangle(const char *cKey) { + size_t byteCount; + bool scale = false; + char *value = (char *) GetConstant(cKey, &byteCount, &scale); + if (!value) return {}; + EsRectangle rectangle; + rectangle.l = EsCRTstrtol(value, &value, 10); + if (*value == ',') value++; + rectangle.r = EsCRTstrtol(value, &value, 10); + if (*value == ',') value++; + rectangle.t = EsCRTstrtol(value, &value, 10); + if (*value == ',') value++; + rectangle.b = EsCRTstrtol(value, &value, 10); + + if (scale) { + rectangle.l *= theming.scale; + rectangle.r *= theming.scale; + rectangle.t *= theming.scale; + rectangle.b *= theming.scale; + } + + return rectangle; +} + +const char *GetConstantString(const char *cKey) { + size_t byteCount; + bool scale; + const char *value = (const char *) GetConstant(cKey, &byteCount, &scale); + return !value || !byteCount || value[byteCount - 1] ? nullptr : value; +} + +bool ThemeLoadData(void *buffer, size_t byteCount) { + EsBuffer data = {}; + data.in = (const uint8_t *) buffer; + data.bytes = byteCount; + + const ThemeHeader *header = (const ThemeHeader *) EsBufferRead(&data, sizeof(ThemeHeader)); + + if (!header || header->signature != THEME_HEADER_SIGNATURE + || !header->styleCount || !EsBufferRead(&data, sizeof(ThemeStyle)) + || byteCount < header->bitmapBytes) { + return false; + } + + theming.system.in = (const uint8_t *) buffer; + theming.system.bytes = byteCount; + theming.header = header; + return true; +} + +void ThemeStyleCopyInlineMetrics(UIStyle *style) { + style->textSize = style->metrics->textSize; + style->textColor = style->metrics->textColor; + style->font.family = style->metrics->fontFamily; + style->font.weight = style->metrics->fontWeight; + style->font.italic = style->metrics->isItalic; + style->preferredWidth = style->metrics->preferredWidth; + style->preferredHeight = style->metrics->preferredHeight; + style->gapMajor = style->metrics->gapMajor; + style->gapMinor = style->metrics->gapMinor; + style->gapWrap = style->metrics->gapWrap; + style->insets.l = style->metrics->insets.l; + style->insets.r = style->metrics->insets.r; + style->insets.t = style->metrics->insets.t; + style->insets.b = style->metrics->insets.b; + style->textAlign = style->metrics->textAlign; +} + +bool ThemeAnimationComplete(ThemeAnimation *animation) { + return !animation->properties.Length(); +} + +bool ThemeAnimationStep(ThemeAnimation *animation, int delta) { + bool repaint = false; + + for (uintptr_t i = 0; i < animation->properties.Length(); i++) { + ThemeAnimatingProperty *property = &animation->properties[i]; + + repaint = true; + property->elapsed += delta; + + if (property->duration < property->elapsed) { + animation->properties.Delete(i); + i--; + } + } + + return repaint; +} + +void ThemeAnimationDestroy(ThemeAnimation *animation) { + animation->properties.Free(); +} + +ThemeVariant ThemeAnimatingPropertyInterpolate(ThemeAnimatingProperty *property, UIStyle *destination, uint8_t *layerData) { + uint32_t dataOffset = property->offset; + EsAssert(dataOffset <= destination->layerDataByteCount - sizeof(ThemeVariant)); + float position = (float) property->elapsed / property->duration; + position = SmoothAnimationTime(position); + + if (property->type == THEME_OVERRIDE_I8) { + int8_t to = *(int8_t *) (layerData + dataOffset); + return (ThemeVariant) { .i8 = (int8_t ) LinearInterpolate(property->from.i8, to, position) }; + } else if (property->type == THEME_OVERRIDE_I16) { + int16_t to = *(int16_t *) (layerData + dataOffset); + return (ThemeVariant) { .i16 = (int16_t) LinearInterpolate(property->from.i16, to, position) }; + } else if (property->type == THEME_OVERRIDE_F32) { + float to = *(float *) (layerData + dataOffset); + return (ThemeVariant) { .f32 = (float) LinearInterpolate(property->from.f32, to, position) }; + } else if (property->type == THEME_OVERRIDE_COLOR) { + uint32_t from = property->from.u32; + uint32_t to = *(uint32_t *) (layerData + dataOffset); + + float fa = ((from >> 24) & 0xFF) / 255.0f; + float fb = ((from >> 16) & 0xFF) / 255.0f; + float fg = ((from >> 8) & 0xFF) / 255.0f; + float fr = ((from >> 0) & 0xFF) / 255.0f; + float ta = ((to >> 24) & 0xFF) / 255.0f; + float tb = ((to >> 16) & 0xFF) / 255.0f; + float tg = ((to >> 8) & 0xFF) / 255.0f; + float tr = ((to >> 0) & 0xFF) / 255.0f; + + if (fa && !ta) { tr = fr, tg = fg, tb = fb; } + if (ta && !fa) { fr = tr, fg = tg, fb = tb; } + + return (ThemeVariant) { .u32 = (uint32_t) (LinearInterpolate(fr, tr, position) * 255.0f) << 0 + | (uint32_t) (LinearInterpolate(fg, tg, position) * 255.0f) << 8 + | (uint32_t) (LinearInterpolate(fb, tb, position) * 255.0f) << 16 + | (uint32_t) ((fa + (ta - fa) * position) * 255.0f) << 24 }; + } else { + EsAssert(false); + return {}; + } +} + +UIStyle *ThemeStyleInterpolate(UIStyle *source, ThemeAnimation *animation) { + size_t byteCount = sizeof(UIStyle) + source->layerDataByteCount; + UIStyle *destination = (UIStyle *) EsHeapAllocate(byteCount, false); + EsMemoryCopy(destination, source, byteCount); + uint8_t *layerData = (uint8_t *) (destination + 1); + destination->metrics = (ThemeMetrics *) (layerData + sizeof(ThemeLayer)); + + for (uintptr_t i = 0; i < animation->properties.Length(); i++) { + ThemeAnimatingProperty *property = &animation->properties[i]; + ThemeVariant result = ThemeAnimatingPropertyInterpolate(property, destination, layerData); + + if (property->type == THEME_OVERRIDE_I8) { + *(int8_t *) (layerData + property->offset) = result.i8; + } else if (property->type == THEME_OVERRIDE_I16) { + *(int16_t *) (layerData + property->offset) = result.i16; + } else if (property->type == THEME_OVERRIDE_F32) { + *(float *) (layerData + property->offset) = result.f32; + } else if (property->type == THEME_OVERRIDE_COLOR) { + *(uint32_t *) (layerData + property->offset) = result.u32; + } + } + + ThemeStyleCopyInlineMetrics(destination); + return destination; +} + +void _ThemeAnimationBuildAddProperties(ThemeAnimation *animation, UIStyle *style, uint16_t stateFlags) { + const uint32_t *layerList = (const uint32_t *) (theming.system.in + style->style->layerListOffset); + // (Layer list was validated during ThemeStyleInitialise.) + uintptr_t layerCumulativeDataOffset = 0; + uint8_t *oldLayerData = (uint8_t *) (style + 1); + + for (uintptr_t i = 0; i < style->style->layerCount; i++) { + const ThemeLayer *layer = (const ThemeLayer *) (theming.system.in + layerList[i]); + + EsBuffer layerData = theming.system; + layerData.position = layer->sequenceDataOffset; + + while (layerData.position) { + const ThemeSequenceHeader *sequenceHeader = (const ThemeSequenceHeader *) EsBufferRead(&layerData, sizeof(ThemeSequenceHeader)); + + if (THEME_STATE_CHECK(sequenceHeader->state, stateFlags) && sequenceHeader->duration) { + for (uintptr_t j = 0; j < sequenceHeader->overrideCount; j++) { + const ThemeOverride *themeOverride = (const ThemeOverride *) EsBufferRead(&layerData, sizeof(ThemeOverride)); + uintptr_t key = themeOverride->offset + layerCumulativeDataOffset; + EsAssert(key <= (uintptr_t) style->layerDataByteCount - sizeof(ThemeVariant)); + + uintptr_t point; + bool alreadyInList; + + // Find where the property is/should be in the animation list. + + ES_MACRO_SEARCH(animation->properties.Length(), { + uintptr_t item = animation->properties[index].offset; + result = key < item ? -1 : key > item ? 1 : 0; + }, point, alreadyInList); + + bool beforeEnter = (sequenceHeader->state & THEME_STATE_BEFORE_ENTER) != 0; + + if (alreadyInList) { + // Update the duration, if the property is already in the list. + // Prioritise before enter sequence durations. + + if (!animation->properties[point].beforeEnter || beforeEnter) { + animation->properties[point].duration = sequenceHeader->duration * ANIMATION_TIME_SCALE; + animation->properties[point].beforeEnter = beforeEnter; + } + } else { + // Add the property to the list. + + if (point < animation->properties.Length()) EsAssert(key < animation->properties[point].offset); + if (point > 0) EsAssert(key > animation->properties[point - 1].offset); + + ThemeAnimatingProperty property = {}; + property.offset = key; + property.type = themeOverride->type; + property.duration = sequenceHeader->duration * ANIMATION_TIME_SCALE; + property.beforeEnter = beforeEnter; + + if (themeOverride->type == THEME_OVERRIDE_I8) { + EsAssert(themeOverride->offset <= (uintptr_t) layer->dataByteCount - 1); + property.from.i8 = *(int8_t *) (oldLayerData + key); + } else if (themeOverride->type == THEME_OVERRIDE_I16) { + EsAssert(themeOverride->offset <= (uintptr_t) layer->dataByteCount - 2); + property.from.i16 = *(int16_t *) (oldLayerData + key); + } else if (themeOverride->type == THEME_OVERRIDE_F32) { + EsAssert(themeOverride->offset <= (uintptr_t) layer->dataByteCount - 4); + property.from.f32 = *(float *) (oldLayerData + key); + } else if (themeOverride->type == THEME_OVERRIDE_COLOR) { + EsAssert(themeOverride->offset <= (uintptr_t) layer->dataByteCount - 4); + property.from.u32 = *(uint32_t *) (oldLayerData + key); + } + + animation->properties.Insert(property, point); + } + } + } else { + EsBufferRead(&layerData, sizeof(ThemeOverride) * sequenceHeader->overrideCount); + } + + if (sequenceHeader->isLastSequence) { + break; + } + } + + layerCumulativeDataOffset += layer->dataByteCount; + } +} + +void ThemeAnimationBuild(ThemeAnimation *animation, UIStyle *oldStyle, uint16_t oldStateFlags, uint16_t newStateFlags) { + // Interpolate all the animating properties using the old style as the target. + + uint8_t *oldLayerData = (uint8_t *) (oldStyle + 1); + + for (uintptr_t i = 0; i < animation->properties.Length(); i++) { + ThemeAnimatingProperty *property = &animation->properties[i]; + property->from = ThemeAnimatingPropertyInterpolate(property, oldStyle, oldLayerData); + property->elapsed = 0; + } + + // Look for the all the sequences that match the old state flags, + // and add properties to return values to base. + + _ThemeAnimationBuildAddProperties(animation, oldStyle, oldStateFlags); + + // Look for the all the sequences that match the new state flags, + // and add properties to interpolate them to the target sequencs. + + _ThemeAnimationBuildAddProperties(animation, oldStyle, newStateFlags); +} + +UIStyle *ThemeStyleInitialise(UIStyleKey key) { + // Find the ThemeStyle entry. + + EsStyle *esStyle = (key.part & 1) || (!key.part) ? nullptr : (EsStyle *) (key.part); + EsThemeMetrics *customMetrics = esStyle ? &esStyle->metrics : nullptr; + uint16_t id = esStyle ? (uint16_t) (uintptr_t) esStyle->inherit : key.part; + if (!id) id = 1; + + EsBuffer data = theming.system; + const ThemeHeader *header = (const ThemeHeader *) EsBufferRead(&data, sizeof(ThemeHeader)); + const ThemeStyle *themeStyle = nullptr; + bool found = false; + + for (uintptr_t i = 0; i < header->styleCount; i++) { + themeStyle = (const ThemeStyle *) EsBufferRead(&data, sizeof(ThemeStyle)); + + if (!themeStyle) { + EsPrint("Broken theme styles list.\n"); + break; + } + + if (themeStyle->id == id) { + found = true; + break; + } + } + + if (!found) { + EsPrint("Could not find theme style with ID %d.\n", id); + data.position = sizeof(ThemeHeader); + themeStyle = (const ThemeStyle *) EsBufferRead(&data, sizeof(ThemeStyle)); + } + + if (!themeStyle->layerCount) { + EsPrint("Style has no layers (must have a metrics layer).\n"); + return nullptr; + } + + // Get information about the layers. + + size_t layerDataByteCount = 0; + data.position = themeStyle->layerListOffset; + + for (uintptr_t i = 0; i < themeStyle->layerCount; i++) { + const uint32_t *offset = (const uint32_t *) EsBufferRead(&data, sizeof(uint32_t)); + + if (!offset) { + EsPrint("Broken style layer list.\n"); + return nullptr; + } + + EsBuffer layerData = data; + layerData.position = *offset; + const ThemeLayer *layer = (const ThemeLayer *) EsBufferRead(&layerData, sizeof(ThemeLayer)); + + if (!layer) { + EsPrint("Broken style layer list.\n"); + return nullptr; + } + + if (layer->dataByteCount < sizeof(ThemeLayer)) { + EsPrint("Broken layer data byte count.\n"); + return nullptr; + } + + layerDataByteCount += layer->dataByteCount; + + if (i == 0) { + if (layer->type != THEME_LAYER_METRICS) { + EsPrint("Style does not have metrics layer.\n"); + return nullptr; + } + + if (layer->dataByteCount != sizeof(ThemeMetrics) + sizeof(ThemeLayer)) { + EsPrint("Broken metrics layer.\n"); + return nullptr; + } + + const ThemeMetrics *metrics = (const ThemeMetrics *) EsBufferRead(&layerData, sizeof(ThemeMetrics)); + + if (!metrics) { + EsPrint("Broken metrics layer.\n"); + return nullptr; + } + } else { + const uint8_t *data = (const uint8_t *) EsBufferRead(&layerData, layer->dataByteCount); + + if (!data) { + EsPrint("Broken layer.\n"); + return nullptr; + } + } + + layerData.position = layer->sequenceDataOffset; + + while (layerData.position) { + const ThemeSequenceHeader *sequenceHeader = (const ThemeSequenceHeader *) EsBufferRead(&layerData, sizeof(ThemeSequenceHeader)); + + if (!sequenceHeader) { + EsPrint("Broken sequence list.\n"); + return nullptr; + } + + if (THEME_STATE_CHECK(sequenceHeader->state, key.stateFlags)) { + for (uintptr_t j = 0; j < sequenceHeader->overrideCount; j++) { + const ThemeOverride *themeOverride = (const ThemeOverride *) EsBufferRead(&layerData, sizeof(ThemeOverride)); + + if (!themeOverride) { + EsPrint("Broken override list.\n"); + return nullptr; + } + + if (themeOverride->offset >= layer->dataByteCount) { + EsPrint("Broken override list.\n"); + return nullptr; + } + + bool valid; + + if (themeOverride->type == THEME_OVERRIDE_I8) { + valid = themeOverride->offset + 1 <= layer->dataByteCount; + } else if (themeOverride->type == THEME_OVERRIDE_I16) { + valid = themeOverride->offset + 2 <= layer->dataByteCount; + } else if (themeOverride->type == THEME_OVERRIDE_F32) { + valid = themeOverride->offset + 4 <= layer->dataByteCount; + } else if (themeOverride->type == THEME_OVERRIDE_COLOR) { + valid = themeOverride->offset + 4 <= layer->dataByteCount; + } else { + EsPrint("Unsupported override type.\n"); + return nullptr; + } + + if (!valid) { + EsPrint("Broken override list.\n"); + return nullptr; + } + } + } else { + if (!EsBufferRead(&layerData, sizeof(ThemeOverride) * sequenceHeader->overrideCount)) { + EsPrint("Broken sequence list.\n"); + return nullptr; + } + } + + if (sequenceHeader->isLastSequence) { + break; + } + } + } + + if (layerDataByteCount > 0xFFFF) { + EsPrint("Layer data too large.\n"); + return nullptr; + } + + // Allocate the style. + + UIStyle *style = (UIStyle *) EsHeapAllocate(sizeof(UIStyle) + layerDataByteCount, true); + + style->referenceCount = 1; + style->layerDataByteCount = layerDataByteCount; + style->style = themeStyle; + + layerDataByteCount = 0; + data.position = themeStyle->layerListOffset; + uint8_t *baseData = (uint8_t *) (style + 1); + style->metrics = (ThemeMetrics *) (baseData + sizeof(ThemeLayer)); + + // Copy overrides and base layer data into the style, and apply overrides. + + for (uintptr_t i = 0; i < themeStyle->layerCount; i++) { + const uint32_t *offset = (const uint32_t *) EsBufferRead(&data, sizeof(uint32_t)); + EsBuffer layerData = data; + layerData.position = *offset; + const ThemeLayer *layer = (const ThemeLayer *) EsBufferRead(&layerData, sizeof(ThemeLayer)); + layerData.position = *offset; + const uint8_t *data = (const uint8_t *) EsBufferRead(&layerData, layer->dataByteCount); + EsMemoryCopy(baseData + layerDataByteCount, data, layer->dataByteCount); + layerData.position = layer->sequenceDataOffset; + + while (layerData.position) { + const ThemeSequenceHeader *sequenceHeader = (const ThemeSequenceHeader *) EsBufferRead(&layerData, sizeof(ThemeSequenceHeader)); + + style->observedStyleStateMask |= 0x10000 << (sequenceHeader->state & THEME_PRIMARY_STATE_MASK); + style->observedStyleStateMask |= sequenceHeader->state & ~THEME_PRIMARY_STATE_MASK; + + if (THEME_STATE_CHECK(sequenceHeader->state, key.stateFlags)) { + for (uintptr_t j = 0; j < sequenceHeader->overrideCount; j++) { + const ThemeOverride *themeOverride = (const ThemeOverride *) EsBufferRead(&layerData, sizeof(ThemeOverride)); + + ThemeVariant overrideValue = themeOverride->data; + + if (themeOverride->type == THEME_OVERRIDE_I8) { + *(int8_t *) (baseData + layerDataByteCount + themeOverride->offset) = overrideValue.i8; + } else if (themeOverride->type == THEME_OVERRIDE_I16) { + *(int16_t *) (baseData + layerDataByteCount + themeOverride->offset) = overrideValue.i16; + } else if (themeOverride->type == THEME_OVERRIDE_F32) { + *(float *) (baseData + layerDataByteCount + themeOverride->offset) = overrideValue.f32; + } else if (themeOverride->type == THEME_OVERRIDE_COLOR) { + *(uint32_t *) (baseData + layerDataByteCount + themeOverride->offset) = overrideValue.u32; + } else { + EsAssert(false); + } + } + } else { + EsBufferRead(&layerData, sizeof(ThemeOverride) * sequenceHeader->overrideCount); + } + + if (sequenceHeader->isLastSequence) { + break; + } + } + + layerDataByteCount += layer->dataByteCount; + } + + // Apply custom metrics and appearance. + + if (customMetrics) { +#define ES_RECTANGLE_TO_RECTANGLE_8(x) { (int8_t) (x).l, (int8_t) (x).r, (int8_t) (x).t, (int8_t) (x).b } + if (customMetrics->mask & ES_THEME_METRICS_INSETS) style->metrics->insets = ES_RECTANGLE_TO_RECTANGLE_8(customMetrics->insets); + if (customMetrics->mask & ES_THEME_METRICS_CLIP_INSETS) style->metrics->clipInsets = ES_RECTANGLE_TO_RECTANGLE_8(customMetrics->clipInsets); + if (customMetrics->mask & ES_THEME_METRICS_GLOBAL_OFFSET) style->metrics->globalOffset = ES_RECTANGLE_TO_RECTANGLE_8(customMetrics->globalOffset); + if (customMetrics->mask & ES_THEME_METRICS_CLIP_ENABLED) style->metrics->clipEnabled = customMetrics->clipEnabled; + if (customMetrics->mask & ES_THEME_METRICS_CURSOR) style->metrics->cursor = customMetrics->cursor; + if (customMetrics->mask & ES_THEME_METRICS_ENTRANCE_TRANSITION) style->metrics->entranceTransition = customMetrics->entranceTransition; + if (customMetrics->mask & ES_THEME_METRICS_EXIT_TRANSITION) style->metrics->exitTransition = customMetrics->exitTransition; + if (customMetrics->mask & ES_THEME_METRICS_ENTRANCE_DURATION) style->metrics->entranceDuration = customMetrics->entranceDuration; + if (customMetrics->mask & ES_THEME_METRICS_EXIT_DURATION) style->metrics->exitDuration = customMetrics->exitDuration; + if (customMetrics->mask & ES_THEME_METRICS_PREFERRED_WIDTH) style->metrics->preferredWidth = customMetrics->preferredWidth; + if (customMetrics->mask & ES_THEME_METRICS_PREFERRED_HEIGHT) style->metrics->preferredHeight = customMetrics->preferredHeight; + if (customMetrics->mask & ES_THEME_METRICS_MINIMUM_WIDTH) style->metrics->minimumWidth = customMetrics->minimumWidth; + if (customMetrics->mask & ES_THEME_METRICS_MINIMUM_HEIGHT) style->metrics->minimumHeight = customMetrics->minimumHeight; + if (customMetrics->mask & ES_THEME_METRICS_MAXIMUM_WIDTH) style->metrics->maximumWidth = customMetrics->maximumWidth; + if (customMetrics->mask & ES_THEME_METRICS_MAXIMUM_HEIGHT) style->metrics->maximumHeight = customMetrics->maximumHeight; + if (customMetrics->mask & ES_THEME_METRICS_GAP_MAJOR) style->metrics->gapMajor = customMetrics->gapMajor; + if (customMetrics->mask & ES_THEME_METRICS_GAP_MINOR) style->metrics->gapMinor = customMetrics->gapMinor; + if (customMetrics->mask & ES_THEME_METRICS_GAP_WRAP) style->metrics->gapWrap = customMetrics->gapWrap; + if (customMetrics->mask & ES_THEME_METRICS_TEXT_COLOR) style->metrics->textColor = customMetrics->textColor; + if (customMetrics->mask & ES_THEME_METRICS_SELECTED_BACKGROUND) style->metrics->selectedBackground = customMetrics->selectedBackground; + if (customMetrics->mask & ES_THEME_METRICS_SELECTED_TEXT) style->metrics->selectedText = customMetrics->selectedText; + if (customMetrics->mask & ES_THEME_METRICS_ICON_COLOR) style->metrics->iconColor = customMetrics->iconColor; + if (customMetrics->mask & ES_THEME_METRICS_TEXT_ALIGN) style->metrics->textAlign = customMetrics->textAlign; + if (customMetrics->mask & ES_THEME_METRICS_TEXT_SIZE) style->metrics->textSize = customMetrics->textSize; + if (customMetrics->mask & ES_THEME_METRICS_FONT_FAMILY) style->metrics->fontFamily = customMetrics->fontFamily; + if (customMetrics->mask & ES_THEME_METRICS_FONT_WEIGHT) style->metrics->fontWeight = customMetrics->fontWeight; + if (customMetrics->mask & ES_THEME_METRICS_ICON_SIZE) style->metrics->iconSize = customMetrics->iconSize; + if (customMetrics->mask & ES_THEME_METRICS_IS_ITALIC) style->metrics->isItalic = customMetrics->isItalic; + if (customMetrics->mask & ES_THEME_METRICS_ELLIPSIS) style->metrics->ellipsis = customMetrics->ellipsis; + if (customMetrics->mask & ES_THEME_METRICS_LAYOUT_VERTICAL) style->metrics->layoutVertical = customMetrics->layoutVertical; + } + + if (esStyle && esStyle->appearance.enabled) { + style->appearance = &esStyle->appearance; + } + + // Apply scaling to the metrics. + + int8_t *scale8[] = { + &style->metrics->textSize, &style->metrics->iconSize, + }; + + for (uintptr_t i = 0; i < sizeof(scale8) / sizeof(scale8[0]); i++) { + *(scale8[i]) = *(scale8[i]) * key.scale; + } + + int16_t *scale16[] = { + &style->metrics->insets.l, &style->metrics->insets.r, &style->metrics->insets.t, &style->metrics->insets.b, + &style->metrics->clipInsets.l, &style->metrics->clipInsets.r, &style->metrics->clipInsets.t, &style->metrics->clipInsets.b, + &style->metrics->globalOffset.l, &style->metrics->globalOffset.r, &style->metrics->globalOffset.t, &style->metrics->globalOffset.b, + &style->metrics->gapMajor, &style->metrics->gapMinor, &style->metrics->gapWrap, + &style->metrics->preferredWidth, &style->metrics->preferredHeight, + &style->metrics->minimumWidth, &style->metrics->minimumHeight, + &style->metrics->maximumWidth, &style->metrics->maximumHeight, + }; + + for (uintptr_t i = 0; i < sizeof(scale16) / sizeof(scale16[0]); i++) { + *(scale16[i]) = *(scale16[i]) * key.scale; + } + + style->scale = key.scale; + + // Copy inline metrics. + + style->borders.l = themeStyle->approximateBorders.l * key.scale; + style->borders.r = themeStyle->approximateBorders.r * key.scale; + style->borders.t = themeStyle->approximateBorders.t * key.scale; + style->borders.b = themeStyle->approximateBorders.b * key.scale; + + style->paintOutsets.l = themeStyle->paintOutsets.l * key.scale; + style->paintOutsets.r = themeStyle->paintOutsets.r * key.scale; + style->paintOutsets.t = themeStyle->paintOutsets.t * key.scale; + style->paintOutsets.b = themeStyle->paintOutsets.b * key.scale; + + if (style->opaqueInsets.l != 0x7F) { + style->opaqueInsets.l = themeStyle->opaqueInsets.l * key.scale; + style->opaqueInsets.r = themeStyle->opaqueInsets.r * key.scale; + style->opaqueInsets.t = themeStyle->opaqueInsets.t * key.scale; + style->opaqueInsets.b = themeStyle->opaqueInsets.b * key.scale; + } + + if (style->appearance) { + if ((style->appearance->backgroundColor & 0xFF000000) == 0xFF000000) { + style->opaqueInsets = ES_RECT_1(0); + } else { + style->opaqueInsets = ES_RECT_1(0x7F); + } + } + + ThemeStyleCopyInlineMetrics(style); + + return style; +} + +UIStyleKey MakeStyleKey(const EsStyle *style, uint16_t stateFlags) { + return { .part = (uintptr_t) style, .stateFlags = stateFlags }; +} + +void FreeUnusedStyles() { + for (uintptr_t i = 0; i < theming.loadedStyles.Count(); i++) { + if (theming.loadedStyles[i]->referenceCount == 0) { + UIStyle *style = theming.loadedStyles[i]; + UIStyleKey key = theming.loadedStyles.KeyAtIndex(i); + theming.loadedStyles.Delete(&key); + EsHeapFree(style); + i--; + } + } +} + +UIStyle *GetStyle(UIStyleKey key, bool keepAround) { + UIStyle **style = theming.loadedStyles.Get(&key); + + if (!style) { + UIStyleKey key2 = key; + key2.scale = theming.scale; // TODO Per-window scaling. + style = theming.loadedStyles.Put(&key); + *style = ThemeStyleInitialise(key2); + EsAssert(style); + } else if ((*style)->referenceCount != -1) { + (*style)->referenceCount++; + } + + if (keepAround || !key.part) { + (*style)->referenceCount = -1; + } + + return *style; +} + +void GetPreferredSizeFromStylePart(const EsStyle *esStyle, int64_t *width, int64_t *height) { + UIStyle *style = GetStyle(MakeStyleKey(esStyle, ES_FLAGS_DEFAULT), true); + if (width) *width = style->preferredWidth; + if (height) *height = style->preferredHeight; + style->CloseReference(); +} + +void UIStyle::CloseReference() { + if (referenceCount == -1) { + return; + } + + referenceCount--; +} + +void UIStyle::PaintTextLayers(EsPainter *painter, EsTextPlan *plan, EsRectangle textBounds, EsTextSelection *selectionProperties) { + EsBuffer data = {}; + data.in = (uint8_t *) (this + 1); + data.bytes = layerDataByteCount; + + EsTextStyle primaryStyle = TextPlanGetPrimaryStyle(plan); + bool restore = false; + + for (uintptr_t i = 0; i < style->layerCount; i++) { + const ThemeLayer *layer = (const ThemeLayer *) EsBufferRead(&data, sizeof(ThemeLayer)); + if (!layer) break; + + if (layer->mode == THEME_LAYER_MODE_CONTENT && layer->type == THEME_LAYER_TEXT) { + EsBuffer data2 = data; + const ThemeLayerText *textLayer = (const ThemeLayerText *) EsBufferRead(&data2, sizeof(ThemeLayerText)); + if (!textLayer) break; + + EsTextStyle textStyle = primaryStyle; + textStyle.color = textLayer->color; + textStyle.blur = textLayer->blur; + EsTextPlanReplaceStyleRenderProperties(plan, &textStyle); + restore = true; + + EsDrawText(painter, plan, Translate(textBounds, layer->offset.l, layer->offset.t)); + } + + EsBufferRead(&data, layer->dataByteCount - sizeof(ThemeLayer)); + } + + if (restore) EsTextPlanReplaceStyleRenderProperties(plan, &primaryStyle); + EsDrawText(painter, plan, textBounds, nullptr, selectionProperties); +} + +void UIStyle::PaintText(EsPainter *painter, EsElement *element, EsRectangle rectangle, + const char *text, size_t textBytes, uint32_t iconID, uint32_t flags, EsTextSelection *selectionProperties) { + EsRectangle bounds = EsRectangleAdd(Translate(EsRectangleAddBorder(rectangle, insets), painter->offsetX, painter->offsetY), RECT16_TO_RECT(metrics->globalOffset)); + EsRectangle textBounds = bounds; + EsRectangle oldClip = painter->clip; + EsRectangleClip(painter->clip, bounds, &painter->clip); + + EsRectangle iconBounds = EsRectangleSplit(&textBounds, metrics->iconSize, metrics->layoutVertical ? 't' : 'l', gapMinor); + EsPainter iconPainter = *painter; + iconPainter.width = Width(iconBounds), iconPainter.height = Height(iconBounds); + iconPainter.offsetX = iconBounds.l, iconPainter.offsetY = iconBounds.t; + EsMessage m = { ES_MSG_PAINT_ICON }; + m.painter = &iconPainter; + + if (element && ES_HANDLED == EsMessageSend(element, &m)) { + // Icon painted by the application. + } else if (iconID) { + EsDrawStandardIcon(painter, iconID, metrics->iconSize, iconBounds, metrics->iconColor); + } else { + // Restore the previous bounds. + textBounds = bounds; + } + + if (flags & (ES_DRAW_CONTENT_MARKER_DOWN_ARROW | ES_DRAW_CONTENT_MARKER_UP_ARROW)) { + EsStyle *part = (flags & ES_DRAW_CONTENT_MARKER_DOWN_ARROW) ? ES_STYLE_MARKER_DOWN_ARROW : ES_STYLE_MARKER_UP_ARROW; + UIStyle *style = GetStyle(MakeStyleKey(part, 0), true); + textBounds.r -= style->preferredWidth + gapMinor; + EsRectangle location = ES_RECT_4PD(bounds.r - style->preferredWidth - painter->offsetX, + bounds.t + Height(bounds) / 2 - style->preferredHeight / 2 - painter->offsetY, + style->preferredWidth, style->preferredHeight); + style->PaintLayers(painter, location, THEME_CHILD_TYPE_ONLY, THEME_LAYER_MODE_BACKGROUND); + } + + if (textBytes == (size_t) -1) { + textBytes = EsCStringLength(text); + } + + if (selectionProperties) { + selectionProperties->foreground = metrics->selectedText; + selectionProperties->background = metrics->selectedBackground; + } + + if (textBytes) { + EsTextPlanProperties properties = {}; + properties.flags = textAlign; + + EsTextRun textRun[2] = {}; + textRun[1].offset = textBytes; + textRun[0].style.font = font; + textRun[0].style.size = textSize; + textRun[0].style.color = textColor; + + if (flags & ES_DRAW_CONTENT_TABULAR) { + textRun[0].style.figures = ES_TEXT_FIGURE_TABULAR; + } + + if (flags & ES_DRAW_CONTENT_RICH_TEXT) { + char *string; + EsTextRun *textRuns; + size_t textRunCount; + EsRichTextParse(text, textBytes, &string, &textRuns, &textRunCount, &textRun[0].style); + EsTextPlan *plan = EsTextPlanCreate(&properties, textBounds, string, textRuns, textRunCount); + EsDrawText(painter, plan, textBounds, nullptr, selectionProperties); + EsTextPlanDestroy(plan); + EsHeapFree(textRuns); + EsHeapFree(string); + } else { + EsTextPlan *plan = EsTextPlanCreate(&properties, textBounds, text, textRun, 1); + PaintTextLayers(painter, plan, textBounds, selectionProperties); + EsTextPlanDestroy(plan); + } + } + + painter->clip = oldClip; +} + +void EsDrawContent(EsPainter *painter, EsElement *element, EsRectangle rectangle, + const char *text, ptrdiff_t textBytes, uint32_t iconID, uint32_t flags, EsTextSelection *selectionProperties) { + if (textBytes == -1) textBytes = EsCStringLength(text); + ((UIStyle *) painter->style)->PaintText(painter, element, rectangle, text, textBytes, iconID, flags, selectionProperties); +} + +void EsDrawTextLayers(EsPainter *painter, EsTextPlan *plan, EsRectangle bounds, EsTextSelection *selectionProperties) { + ((UIStyle *) painter->style)->PaintTextLayers(painter, plan, bounds, selectionProperties); +} + +void UIStyle::PaintLayers(EsPainter *painter, EsRectangle location, int childType, int whichLayers) { + EsBuffer data = {}; + data.in = (uint8_t *) (this + 1); + data.bytes = layerDataByteCount; + + if (!THEME_RECT_VALID(painter->clip)) { + return; + } + + EsRectangle opaqueRegion = {}; + EsRectangle _bounds = Translate(location, painter->offsetX, painter->offsetY); + + if (opaqueInsets.l != 0x7F && opaqueInsets.r != 0x7F + && opaqueInsets.t != 0x7F && opaqueInsets.b != 0x7F) { + opaqueRegion = THEME_RECT_4(_bounds.l + opaqueInsets.l, _bounds.r - opaqueInsets.r, + _bounds.t + opaqueInsets.t, _bounds.b - opaqueInsets.b); + } + + if (appearance && whichLayers == 0) { + EsDrawRectangle(painter, _bounds, appearance->backgroundColor, appearance->borderColor, appearance->borderSize); + return; + } + + for (uintptr_t i = 0; i < style->layerCount; i++) { + const ThemeLayer *layer = (const ThemeLayer *) EsBufferRead(&data, sizeof(ThemeLayer)); + + if (!layer) { + return; + } + + EsRectangle bounds; + bounds.l = _bounds.l + (int) (scale * layer->offset.l) + THEME_RECT_WIDTH(_bounds) * layer->position.l / 100 + metrics->globalOffset.l; + bounds.r = _bounds.l + (int) (scale * layer->offset.r) + THEME_RECT_WIDTH(_bounds) * layer->position.r / 100 + metrics->globalOffset.r; + bounds.t = _bounds.t + (int) (scale * layer->offset.t) + THEME_RECT_HEIGHT(_bounds) * layer->position.t / 100 + metrics->globalOffset.t; + bounds.b = _bounds.t + (int) (scale * layer->offset.b) + THEME_RECT_HEIGHT(_bounds) * layer->position.b / 100 + metrics->globalOffset.b; + + if (layer->mode == whichLayers && THEME_RECT_WIDTH(bounds) > 0 && THEME_RECT_HEIGHT(bounds) > 0 + && THEME_RECT_VALID(EsRectangleIntersection(bounds, painter->clip))) { + EsBuffer data2 = data; + + if (layer->type == THEME_LAYER_BOX) { + ThemeDrawBox(painter, bounds, &data2, scale, layer, opaqueRegion, childType); + } else if (layer->type == THEME_LAYER_PATH) { + ThemeDrawPath(painter, bounds, &data2, scale); + } + } + + EsBufferRead(&data, layer->dataByteCount - sizeof(ThemeLayer)); + } +} + +inline void UIStyle::GetTextStyle(EsTextStyle *style) { + // Also need to update PaintText. + EsMemoryZero(style, sizeof(EsTextStyle)); + style->font = font; + style->size = metrics->textSize; + style->color = metrics->textColor; +} + +bool UIStyle::IsStateChangeObserved(uint16_t state1, uint16_t state2) { + if (((state1 & ~THEME_PRIMARY_STATE_MASK) ^ (state2 & ~THEME_PRIMARY_STATE_MASK)) & observedStyleStateMask) { + return true; + } + + if (((0x10000 << (state1 & THEME_PRIMARY_STATE_MASK)) ^ (0x10000 << (state2 & THEME_PRIMARY_STATE_MASK))) & observedStyleStateMask) { + return true; + } + + return false; +} + +bool UIStyle::IsRegionCompletelyOpaque(EsRectangle region, int width, int height) { + return region.l >= opaqueInsets.l && region.r < width - opaqueInsets.r + && region.t >= opaqueInsets.t && region.b < height - opaqueInsets.b; +} + +void DrawStyledBox(EsPainter *painter, EsStyledBox box) { + ThemeLayer layer = {}; + ThemeLayerBox layerBox = {}; + EsBuffer data = {}; + + layerBox.borders = { (int8_t) box.borders.l, (int8_t) box.borders.r, (int8_t) box.borders.t, (int8_t) box.borders.b }; + layerBox.corners = { (int8_t) box.cornerRadiusTopLeft, (int8_t) box.cornerRadiusTopRight, (int8_t) box.cornerRadiusBottomLeft, (int8_t) box.cornerRadiusBottomRight }; + layerBox.mainPaintType = THEME_PAINT_SOLID; + layerBox.borderPaintType = THEME_PAINT_SOLID; + + uint8_t info[sizeof(ThemeLayerBox) + sizeof(ThemePaintCustom) + sizeof(ThemePaintSolid) * 2]; + + if (box.fragmentShader) { + ThemeLayerBox *infoBox = (ThemeLayerBox *) info; + ThemePaintCustom *infoMain = (ThemePaintCustom *) (infoBox + 1); + ThemePaintSolid *infoBorder = (ThemePaintSolid *) (infoMain + 1); + + *infoBox = layerBox; + infoBox->mainPaintType = THEME_PAINT_CUSTOM; + infoMain->callback = box.fragmentShader; + infoBorder->color = box.borderColor; + + data.in = (const uint8_t *) &info; + data.bytes = sizeof(info); + data.context = &box; + } else { + ThemeLayerBox *infoBox = (ThemeLayerBox *) info; + ThemePaintSolid *infoMain = (ThemePaintSolid *) (infoBox + 1); + ThemePaintSolid *infoBorder = (ThemePaintSolid *) (infoMain + 1); + + *infoBox = layerBox; + infoMain->color = box.backgroundColor; + infoBorder->color = box.borderColor; + + data.in = (const uint8_t *) &info; + data.bytes = sizeof(info); + } + + ThemeDrawBox(painter, box.bounds, &data, 1, &layer, {}, THEME_CHILD_TYPE_ONLY); +} + +#endif diff --git a/drivers/acpi.cpp b/drivers/acpi.cpp new file mode 100644 index 0000000..c3a3700 --- /dev/null +++ b/drivers/acpi.cpp @@ -0,0 +1,1073 @@ +// TODO ACPICA initialisation hangs on my computer when SMP is enabled when it tries to write to IO port 0xB2 (power management, generates SMI). +// This is possibly related to the hang when writing to the keyboard controller IO ports that only occurs with SMP enabled. + +#define SIGNATURE_RSDP (0x2052545020445352) + +#define SIGNATURE_RSDT (0x54445352) +#define SIGNATURE_XSDT (0x54445358) +#define SIGNATURE_MADT (0x43495041) +#define SIGNATURE_FADT (0x50434146) + +struct RootSystemDescriptorPointer { + uint64_t signature; + uint8_t checksum; + char OEMID[6]; + uint8_t revision; + uint32_t rsdtAddress; + uint32_t length; + uint64_t xsdtAddress; + uint8_t extendedChecksum; + uint8_t reserved[3]; +}; + +struct ACPIDescriptorTable { +#define ACPI_DESCRIPTOR_TABLE_HEADER_LENGTH 36 + uint32_t signature; + uint32_t length; + uint64_t id; + uint64_t tableID; + uint32_t oemRevision; + uint32_t creatorID; + uint32_t creatorRevision; + + void Check() { + if (!EsMemorySumBytes((uint8_t *) this, length)) return; + + KernelPanic("ACPI::Initialise - ACPI table with signature %s had invalid checksum: " + "length: %D, ID = %s, table = %s, OEM revision = %d, creator = %s, creator revision = %d.\n", + 4, &signature, length, 8, &id, 8, &tableID, + oemRevision, 4, &creatorID, creatorRevision); + } +}; + +struct MultipleAPICDescriptionTable { + uint32_t lapicAddress; + uint32_t flags; +}; + +struct ACPIProcessor { + uint8_t processorID, kernelProcessorID; + uint8_t apicID; + bool bootstrapProcessor; + void **kernelStack; + CPULocalStorage *local; +}; + +struct ACPIIoApic { + uint32_t ReadRegister(uint32_t reg); + void WriteRegister(uint32_t reg, uint32_t value); + + uint8_t id; + uint32_t volatile *address; + uint32_t gsiBase; +}; + +uint32_t ACPIIoApic::ReadRegister(uint32_t reg) { + address[0] = reg; + return address[4]; +} + +void ACPIIoApic::WriteRegister(uint32_t reg, uint32_t value) { + address[0] = reg; + address[4] = value; +} + +struct ACPIInterruptOverride { + uint8_t sourceIRQ; + uint32_t gsiNumber; + bool activeLow, levelTriggered; +}; + +struct ACPILapicNMI { + uint8_t processor; // 0xFF for all processors + uint8_t lintIndex; + bool activeLow, levelTriggered; +}; + +struct ACPILapic { + uint32_t ReadRegister(uint32_t reg); + void EndOfInterrupt(); + void WriteRegister(uint32_t reg, uint32_t value); + void ArchNextTimer(size_t ms); + + volatile uint32_t *address; + size_t ticksPerMs; +}; + +void ACPILapic::ArchNextTimer(size_t ms) { + WriteRegister(0x320 >> 2, TIMER_INTERRUPT | (1 << 17)); + WriteRegister(0x380 >> 2, ticksPerMs * ms); +} + +void ACPILapic::EndOfInterrupt() { + WriteRegister(0xB0 >> 2, 0); +} + +uint32_t ACPILapic::ReadRegister(uint32_t reg) { + return address[reg]; +} + +void ACPILapic::WriteRegister(uint32_t reg, uint32_t value) { + address[reg] = value; +} + +struct ACPI { + void Initialise(); + void FindRootSystemDescriptorPointer(); + void StartupApplicationProcessors(); + + size_t processorCount; + size_t ioapicCount; + size_t interruptOverrideCount; + size_t lapicNMICount; + + ACPIProcessor processors[256]; // TODO Make this a DS_ARRAY. + ACPIProcessor *bootstrapProcessor; + ACPIIoApic ioApics[16]; + ACPIInterruptOverride interruptOverrides[256]; + ACPILapicNMI lapicNMIs[32]; + ACPILapic lapic; + + RootSystemDescriptorPointer *rsdp; + ACPIDescriptorTable *sdt; bool isXSDT; + ACPIDescriptorTable *madt; + + bool ps2ControllerUnavailable, vgaControllerUnavailable; + + KDevice *computer; +}; + +ACPI acpi; + +#ifdef ARCH_X86_COMMON +void ACPI::FindRootSystemDescriptorPointer() { + PhysicalMemoryRegion searchRegions[2]; + + searchRegions[0].baseAddress = (uintptr_t) (((uint16_t *) LOW_MEMORY_MAP_START)[0x40E] << 4) + LOW_MEMORY_MAP_START; + searchRegions[0].pageCount = 0x400; + searchRegions[1].baseAddress = (uintptr_t) 0xE0000 + LOW_MEMORY_MAP_START; + searchRegions[1].pageCount = 0x20000; + + for (uintptr_t i = 0; i < 2; i++) { + for (uintptr_t address = searchRegions[i].baseAddress; + address < searchRegions[i].baseAddress + searchRegions[i].pageCount; + address += 16) { + rsdp = (RootSystemDescriptorPointer *) address; + + if (rsdp->signature != SIGNATURE_RSDP) { + continue; + } + + if (rsdp->revision == 0) { + if (EsMemorySumBytes((uint8_t *) rsdp, 20)) { + continue; + } + + return; + } else if (rsdp->revision == 2) { + if (EsMemorySumBytes((uint8_t *) rsdp, sizeof(RootSystemDescriptorPointer))) { + continue; + } + + return; + } + } + } + + // We didn't find the RSDP. + rsdp = nullptr; +} +#endif + +void *ACPIMapPhysicalMemory(uintptr_t physicalAddress, size_t length) { +#ifdef ARCH_X86_COMMON + if ((uintptr_t) physicalAddress + (uintptr_t) length < (uintptr_t) LOW_MEMORY_LIMIT) { + return (void *) (LOW_MEMORY_MAP_START + physicalAddress); + } +#endif + + void *address = MMMapPhysical(kernelMMSpace, physicalAddress, length, MM_REGION_NOT_CACHEABLE); + return address; +} + +#ifdef USE_ACPICA + +// TODO Warning: Not all of the OSL has been tested. + +extern "C" { +#pragma GCC diagnostic ignored "-Wunused-parameter" push +#include +#pragma GCC diagnostic pop +} + +bool acpiOSLayerActive = false; + +ES_EXTERN_C ACPI_STATUS AcpiOsInitialize() { + if (acpiOSLayerActive) KernelPanic("AcpiOsInitialize - ACPI has already been initialised.\n"); + acpiOSLayerActive = true; + KernelLog(LOG_INFO, "ACPI", "initialise ACPICA", "AcpiOsInitialize - Initialising ACPICA OS layer...\n"); + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsTerminate() { + if (!acpiOSLayerActive) KernelPanic("AcpiOsTerminate - ACPI has not been initialised.\n"); + acpiOSLayerActive = false; + KernelLog(LOG_INFO, "ACPI", "terminate ACPICA", "AcpiOsTerminate - Terminating ACPICA OS layer...\n"); + return AE_OK; +} + +ES_EXTERN_C ACPI_PHYSICAL_ADDRESS AcpiOsGetRootPointer() { + ACPI_PHYSICAL_ADDRESS address = 0; + + uint64_t uefiRSDP = *((uint64_t *) (LOW_MEMORY_MAP_START + GetBootloaderInformationOffset() + 0x7FE8)); + + if (uefiRSDP) { + return uefiRSDP; + } + + AcpiFindRootPointer(&address); + return address; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsPredefinedOverride(const ACPI_PREDEFINED_NAMES *predefinedObject, ACPI_STRING *newValue) { + (void) predefinedObject; + *newValue = nullptr; + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsTableOverride(ACPI_TABLE_HEADER *existingTable, ACPI_TABLE_HEADER **newTable) { + (void) existingTable; + *newTable = nullptr; + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsPhysicalTableOverride(ACPI_TABLE_HEADER *existingTable, ACPI_PHYSICAL_ADDRESS *newAddress, uint32_t *newTableLength) { + (void) existingTable; + *newAddress = 0; + *newTableLength = 0; + return AE_OK; +} + +ES_EXTERN_C void *AcpiOsMapMemory(ACPI_PHYSICAL_ADDRESS physicalAddress, ACPI_SIZE length) { + return ACPIMapPhysicalMemory(physicalAddress, length); +} + +ES_EXTERN_C void AcpiOsUnmapMemory(void *address, ACPI_SIZE length) { +#ifdef ARCH_X86_COMMON + if ((uintptr_t) address - (uintptr_t) LOW_MEMORY_MAP_START < (uintptr_t) LOW_MEMORY_LIMIT) { + return; + } +#endif + + (void) length; + MMFree(kernelMMSpace, address); +} + +ES_EXTERN_C ACPI_STATUS AcpiOsGetPhysicalAddress(void *virtualAddress, ACPI_PHYSICAL_ADDRESS *physicalAddress) { + if (!virtualAddress || !physicalAddress) { + return AE_BAD_PARAMETER; + } + + *physicalAddress = MMArchTranslateAddress(kernelMMSpace, (uintptr_t) virtualAddress); + return AE_OK; +} + +ES_EXTERN_C void *AcpiOsAllocate(ACPI_SIZE size) { + return EsHeapAllocate(size, false, K_FIXED); +} + +ES_EXTERN_C void AcpiOsFree(void *memory) { + EsHeapFree(memory, 0, K_FIXED); +} + +ES_EXTERN_C BOOLEAN AcpiOsReadable(void *memory, ACPI_SIZE length) { + (void) memory; + (void) length; + // This is only used by the debugger, which we don't use... + return TRUE; +} + +ES_EXTERN_C BOOLEAN AcpiOsWritable(void *memory, ACPI_SIZE length) { + (void) memory; + (void) length; + // This is only used by the debugger, which we don't use... + return TRUE; +} + +ES_EXTERN_C ACPI_THREAD_ID AcpiOsGetThreadId() { + return GetCurrentThread()->id + 1; +} + +Thread *acpiEvents[256]; +size_t acpiEventCount; + +struct ACPICAEvent { + ACPI_OSD_EXEC_CALLBACK function; + void *context; +}; + +void RunACPICAEvent(void *e) { + ACPICAEvent *event = (ACPICAEvent *) e; + event->function(event->context); + EsHeapFree(event, 0, K_FIXED); + scheduler.TerminateThread(GetCurrentThread()); +} + +ES_EXTERN_C ACPI_STATUS AcpiOsExecute(ACPI_EXECUTE_TYPE type, ACPI_OSD_EXEC_CALLBACK function, void *context) { + (void) type; + + if (!function) return AE_BAD_PARAMETER; + + ACPICAEvent *event = (ACPICAEvent *) EsHeapAllocate(sizeof(ACPICAEvent), true, K_FIXED); + event->function = function; + event->context = context; + + Thread *thread = scheduler.SpawnThread("ACPICAEvent", (uintptr_t) RunACPICAEvent, (uintptr_t) event); + + if (acpiEventCount == 256) { + KernelPanic("AcpiOsExecute - Exceeded maximum event count, 256.\n"); + } + + acpiEvents[acpiEventCount++] = thread; + return AE_OK; +} + +ES_EXTERN_C void AcpiOsSleep(UINT64 ms) { + KEvent event = {}; + KEventWait(&event, ms); +} + +ES_EXTERN_C void AcpiOsStall(UINT32 mcs) { + (void) mcs; + uint64_t start = ProcessorReadTimeStamp(); + uint64_t end = start + mcs * (timeStampTicksPerMs / 1000); + while (ProcessorReadTimeStamp() < end); +} + +ES_EXTERN_C void AcpiOsWaitEventsComplete() { + for (uintptr_t i = 0; i < acpiEventCount; i++) { + Thread *thread = acpiEvents[i]; + KEventWait(&thread->killedEvent, ES_WAIT_NO_TIMEOUT); + CloseHandleToObject(thread, KERNEL_OBJECT_THREAD); + } + + acpiEventCount = 0; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsCreateSemaphore(UINT32 maxUnits, UINT32 initialUnits, ACPI_SEMAPHORE *handle) { + if (!handle) return AE_BAD_PARAMETER; + + KSemaphore *semaphore = (KSemaphore *) EsHeapAllocate(sizeof(KSemaphore), true, K_FIXED); + KSemaphoreReturn(semaphore, initialUnits); + semaphore->_custom = maxUnits; + *handle = semaphore; + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsDeleteSemaphore(ACPI_SEMAPHORE handle) { + if (!handle) return AE_BAD_PARAMETER; + EsHeapFree(handle, sizeof(KSemaphore), K_FIXED); + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE handle, UINT32 units, UINT16 timeout) { + (void) timeout; + if (!handle) return AE_BAD_PARAMETER; + KSemaphore *semaphore = (KSemaphore *) handle; + + if (KSemaphoreTake(semaphore, units, timeout == (UINT16) -1 ? ES_WAIT_NO_TIMEOUT : timeout)) { + return AE_OK; + } else { + return AE_TIME; + } +} + +ES_EXTERN_C ACPI_STATUS AcpiOsSignalSemaphore(ACPI_SEMAPHORE handle, UINT32 units) { + if (!handle) return AE_BAD_PARAMETER; + KSemaphore *semaphore = (KSemaphore *) handle; + if (semaphore->units + units > semaphore->_custom) return AE_LIMIT; + KSemaphoreReturn(semaphore, units); + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsCreateLock(ACPI_SPINLOCK *handle) { + if (!handle) return AE_BAD_PARAMETER; + KSpinlock *spinlock = (KSpinlock *) EsHeapAllocate(sizeof(KSpinlock), true, K_FIXED); + *handle = spinlock; + return AE_OK; +} + +ES_EXTERN_C void AcpiOsDeleteLock(ACPI_HANDLE handle) { + EsHeapFree(handle, sizeof(KSpinlock), K_FIXED); +} + +ES_EXTERN_C ACPI_CPU_FLAGS AcpiOsAcquireLock(ACPI_SPINLOCK handle) { + KSpinlock *spinlock = (KSpinlock *) handle; + KSpinlockAcquire(spinlock); + return 0; +} + +ES_EXTERN_C void AcpiOsReleaseLock(ACPI_SPINLOCK handle, ACPI_CPU_FLAGS flags) { + (void) flags; + KSpinlock *spinlock = (KSpinlock *) handle; + KSpinlockRelease(spinlock); +} + +ACPI_OSD_HANDLER acpiInterruptHandlers[256]; +void *acpiInterruptContexts[256]; + +bool ACPIInterrupt(uintptr_t interruptIndex, void *) { + if (acpiInterruptHandlers[interruptIndex]) { + return ACPI_INTERRUPT_HANDLED == acpiInterruptHandlers[interruptIndex](acpiInterruptContexts[interruptIndex]); + } else { + return false; + } +} + +ES_EXTERN_C ACPI_STATUS AcpiOsInstallInterruptHandler(UINT32 interruptLevel, ACPI_OSD_HANDLER handler, void *context) { + if (interruptLevel > 256 || !handler) return AE_BAD_PARAMETER; + + if (acpiInterruptHandlers[interruptLevel]) { + return AE_ALREADY_EXISTS; + } + + acpiInterruptHandlers[interruptLevel] = handler; + acpiInterruptContexts[interruptLevel] = context; + + return KRegisterIRQ(interruptLevel, ACPIInterrupt, nullptr, "ACPICA") ? AE_OK : AE_ERROR; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsRemoveInterruptHandler(UINT32 interruptNumber, ACPI_OSD_HANDLER handler) { + if (interruptNumber > 256 || !handler) return AE_BAD_PARAMETER; + + if (!acpiInterruptHandlers[interruptNumber]) { + return AE_NOT_EXIST; + } + + if (handler != acpiInterruptHandlers[interruptNumber]) { + return AE_BAD_PARAMETER; + } + + acpiInterruptHandlers[interruptNumber] = nullptr; + + return AE_OK; +} + +uint8_t acpicaPageBuffer[K_PAGE_SIZE]; +KMutex acpicaPageBufferMutex; + +ES_EXTERN_C ACPI_STATUS AcpiOsReadMemory(ACPI_PHYSICAL_ADDRESS address, UINT64 *value, UINT32 width) { + KMutexAcquire(&acpicaPageBufferMutex); + EsDefer(KMutexRelease(&acpicaPageBufferMutex)); + + uintptr_t page = (uintptr_t) address & ~(K_PAGE_SIZE - 1); + uintptr_t offset = (uintptr_t) address & (K_PAGE_SIZE - 1); + + PMRead(page, acpicaPageBuffer, 1); + + if (width == 64) { + *value = *((uint64_t *) (acpicaPageBuffer + offset)); + } else if (width == 32) { + *value = *((uint32_t *) (acpicaPageBuffer + offset)); + } else if (width == 16) { + *value = *((uint16_t *) (acpicaPageBuffer + offset)); + } else { + *value = acpicaPageBuffer[offset]; + } + + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsWriteMemory(ACPI_PHYSICAL_ADDRESS address, UINT64 value, UINT32 width) { + KMutexAcquire(&acpicaPageBufferMutex); + EsDefer(KMutexRelease(&acpicaPageBufferMutex)); + + uintptr_t page = (uintptr_t) address & ~(K_PAGE_SIZE - 1); + uintptr_t offset = (uintptr_t) address & (K_PAGE_SIZE - 1); + + PMRead(page, acpicaPageBuffer, 1); + + if (width == 64) { + *((uint64_t *) (acpicaPageBuffer + offset)) = value; + } else if (width == 32) { + *((uint32_t *) (acpicaPageBuffer + offset)) = value; + } else if (width == 16) { + *((uint16_t *) (acpicaPageBuffer + offset)) = value; + } else { + *((uint8_t *) (acpicaPageBuffer + offset)) = value; + } + + PMCopy(page, acpicaPageBuffer, 1); + + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsReadPort(ACPI_IO_ADDRESS address, UINT32 *value, UINT32 width) { + // EsPrint("AcpiOsReadPort - %x, %d", address, width); + + if (width == 8) { + *value = ProcessorIn8(address); + } else if (width == 16) { + *value = ProcessorIn16(address); + } else if (width == 32) { + *value = ProcessorIn32(address); + } else { + return AE_ERROR; + } + + // EsPrint(" - %x\n", *value); + + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsWritePort(ACPI_IO_ADDRESS address, UINT32 value, UINT32 width) { + // EsPrint("AcpiOsWritePort - %x, %x, %d", address, value, width); + + if (width == 8) { + ProcessorOut8(address, (uint8_t) value); + } else if (width == 16) { + ProcessorOut16(address, (uint16_t) value); + } else if (width == 32) { + ProcessorOut32(address, (uint32_t) value); + } else { + return AE_ERROR; + } + + // EsPrint(" - ;;\n"); + + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsReadPciConfiguration(ACPI_PCI_ID *address, UINT32 reg, UINT64 *value, UINT32 width) { + if (width == 64) { + uint64_t x = (uint64_t) KPCIReadConfig(address->Bus, address->Device, address->Function, reg) + | ((uint64_t) KPCIReadConfig(address->Bus, address->Device, address->Function, reg + 4) << 32); + *value = x; + } else { + uint32_t x = KPCIReadConfig(address->Bus, address->Device, address->Function, reg & ~3); + x >>= (reg & 3) * 8; + + if (width == 8) x &= 0xFF; + if (width == 16) x &= 0xFFFF; + + *value = x; + } + + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsWritePciConfiguration(ACPI_PCI_ID *address, UINT32 reg, UINT64 value, UINT32 width) { + if (width == 64) { + KPCIWriteConfig(address->Bus, address->Device, address->Function, reg, value); + KPCIWriteConfig(address->Bus, address->Device, address->Function, reg + 4, value >> 32); + } else if (width == 32) { + KPCIWriteConfig(address->Bus, address->Device, address->Function, reg, value); + } else { + uint32_t x = KPCIReadConfig(address->Bus, address->Device, address->Function, reg & ~3); + uint32_t o = reg & 3; + + if (width == 16) { + if (o == 2) { + x = (x & ~0xFFFF0000) | (value << 16); + } else { + x = (x & ~0x0000FFFF) | (value << 0); + } + } else if (width == 8) { + if (o == 3) { + x = (x & ~0xFF000000) | (value << 24); + } else if (o == 2) { + x = (x & ~0x00FF0000) | (value << 16); + } else if (o == 1) { + x = (x & ~0x0000FF00) | (value << 8); + } else { + x = (x & ~0x000000FF) | (value << 0); + } + } + + KPCIWriteConfig(address->Bus, address->Device, address->Function, reg & ~3, x); + } + + return AE_OK; +} + +char acpiPrintf[4096]; + +#if 1 +#define ENABLE_ACPICA_OUTPUT +#endif + +ES_EXTERN_C void AcpiOsPrintf(const char *format, ...) { + va_list arguments; + va_start(arguments, format); + int x = stbsp_vsnprintf(acpiPrintf, sizeof(acpiPrintf), format, arguments); +#ifdef ENABLE_ACPICA_OUTPUT + EsPrint("%s", x, acpiPrintf); +#else + (void) x; +#endif + va_end(arguments); +} + +ES_EXTERN_C void AcpiOsVprintf(const char *format, va_list arguments) { + int x = stbsp_vsnprintf(acpiPrintf, sizeof(acpiPrintf), format, arguments); +#ifdef ENABLE_ACPICA_OUTPUT + EsPrint("%s", x, acpiPrintf); +#else + (void) x; +#endif +} + +ES_EXTERN_C UINT64 AcpiOsGetTimer() { + uint64_t tick = ProcessorReadTimeStamp(); + uint64_t ticksPerMs = timeStampTicksPerMs; + uint64_t ticksPer100Ns = ticksPerMs / 1000 / 10; + if (ticksPer100Ns == 0) return tick; + return tick / ticksPer100Ns; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsSignal(UINT32 function, void *information) { + (void) function; + (void) information; + KernelPanic("AcpiOsSignal - ACPI requested kernel panic.\n"); + return AE_OK; +} + +ES_EXTERN_C ACPI_STATUS AcpiOsEnterSleep(UINT8 sleepState, UINT32 registerAValue, UINT32 registerBValue) { + (void) sleepState; + (void) registerAValue; + (void) registerBValue; + return AE_OK; +} + +UINT32 ACPIPowerButtonPressed(void *) { + KRegisterAsyncTask([] (EsGeneric) { + _EsMessageWithObject m = { nullptr, ES_MSG_POWER_BUTTON_PRESSED }; + if (scheduler.shutdown) return; + if (desktopProcess) desktopProcess->messageQueue.SendMessage(&m); + }, nullptr, false); + + return 0; +} + +#endif + +void ACPIInitialise2() { +#ifdef USE_ACPICA + AcpiInitializeSubsystem(); + AcpiInitializeTables(nullptr, 256, true); + AcpiLoadTables(); + ProcessorDisableInterrupts(); + ProcessorEnableInterrupts(); + AcpiEnableSubsystem(ACPI_FULL_INITIALIZATION); + AcpiInitializeObjects(ACPI_FULL_INITIALIZATION); + + if (AE_OK == AcpiEnableEvent(ACPI_EVENT_POWER_BUTTON, 0) + && AE_OK == AcpiInstallFixedEventHandler(ACPI_EVENT_POWER_BUTTON, ACPIPowerButtonPressed, nullptr)) { + KDeviceCreate("ACPI power button", acpi.computer, sizeof(KDevice)); + } + + void *result; + + AcpiGetDevices(nullptr, [] (ACPI_HANDLE object, uint32_t, void *, void **) -> ACPI_STATUS { + ACPI_DEVICE_INFO *information; + AcpiGetObjectInfo(object, &information); + KernelLog(LOG_INFO, "ACPI", "device object", "Found device object '%c%c%c%c' with HID '%z' and UID '%z'.\n", + (char) (information->Name >> 0), (char) (information->Name >> 8), (char) (information->Name >> 16), (char) (information->Name >> 24), + (information->Valid & ACPI_VALID_HID) ? information->HardwareId.String : "??", + (information->Valid & ACPI_VALID_UID) ? information->UniqueId.String : "??"); + ACPI_FREE(information); + return AE_OK; + }, nullptr, &result); +#endif + + acpi.StartupApplicationProcessors(); +} + +void KPS2SafeToInitialise() { + if (acpi.ps2ControllerUnavailable) { + return; + } + + // This is only called when either: + // - the PCI driver determines there are no USB controllers + // - the USB controller disables USB emulation + KThreadCreate("InitPS2", [] (uintptr_t) { KDeviceAttachByName(acpi.computer, "PS2"); }); +} + +static void DeviceAttach(KDevice *parentDevice) { + acpi.computer = KDeviceCreate("ACPI computer", parentDevice, sizeof(KDevice)); + +#ifndef SERIAL_STARTUP + KThreadCreate("InitACPI", [] (uintptr_t) { ACPIInitialise2(); }); +#else + ACPIInitialise2(); +#endif + + KDeviceAttachByName(acpi.computer, "PCI"); + + if (!acpi.vgaControllerUnavailable) { + KDeviceAttachByName(acpi.computer, "SVGA"); + } +} + +KDriver driverACPI = { + .attach = DeviceAttach, +}; + +void *KGetRSDP() { + return acpi.rsdp; +} + +inline void ArchInitialise() { + acpi.Initialise(); +} + +#ifdef USE_ACPICA +void ArchShutdown(uintptr_t action) { + if (action == SHUTDOWN_ACTION_RESTART) ArchResetCPU(); + AcpiEnterSleepStatePrep(5); + ProcessorDisableInterrupts(); + AcpiEnterSleepState(5); +} +#else +void ArchShutdown(uintptr_t action) { + if (action == SHUTDOWN_ACTION_RESTART) ArchResetCPU(); + KernelPanic("It's now safe to turn off your computer.\n"); +} +#endif + +void ACPI::Initialise() { + uint64_t uefiRSDP = *((uint64_t *) (LOW_MEMORY_MAP_START + GetBootloaderInformationOffset() + 0x7FE8)); + + if (!uefiRSDP) { +#ifdef USE_ACPICA + AcpiFindRootPointer((ACPI_PHYSICAL_ADDRESS *) &uefiRSDP); + rsdp = (RootSystemDescriptorPointer *) MMMapPhysical(kernelMMSpace, (uintptr_t) uefiRSDP, 16384, ES_FLAGS_DEFAULT); +#else + FindRootSystemDescriptorPointer(); +#endif + } else { + rsdp = (RootSystemDescriptorPointer *) MMMapPhysical(kernelMMSpace, (uintptr_t) uefiRSDP, 16384, ES_FLAGS_DEFAULT); + } + + if (rsdp) { + if (rsdp->revision == 2 && rsdp->xsdtAddress) { + isXSDT = true; + sdt = (ACPIDescriptorTable *) rsdp->xsdtAddress; + } else { + isXSDT = false; + sdt = (ACPIDescriptorTable *) (uintptr_t) rsdp->rsdtAddress; + } + + sdt = (ACPIDescriptorTable *) MMMapPhysical(kernelMMSpace, (uintptr_t) sdt, 16384, ES_FLAGS_DEFAULT); + } else { + KernelPanic("ACPI::Initialise - Could not find supported root system descriptor pointer.\nACPI support is required.\n"); + } + + if (((sdt->signature == SIGNATURE_XSDT && isXSDT) || (sdt->signature == SIGNATURE_RSDT && !isXSDT)) + && sdt->length < 16384 && !EsMemorySumBytes((uint8_t *) sdt, sdt->length)) { + size_t tablesCount = (sdt->length - sizeof(ACPIDescriptorTable)) >> (isXSDT ? 3 : 2); + + if (tablesCount < 1) { + KernelPanic("ACPI::Initialise - The system descriptor table contains an unsupported number of tables (%d).\n", tablesCount); + } + + uintptr_t tableListAddress = (uintptr_t) sdt + ACPI_DESCRIPTOR_TABLE_HEADER_LENGTH; + + KernelLog(LOG_INFO, "ACPI", "table count", "ACPI::Initialise - Found %d tables.\n", tablesCount); + + for (uintptr_t i = 0; i < tablesCount; i++) { + uintptr_t address; + + if (isXSDT) { + address = ((uint64_t *) tableListAddress)[i]; + } else { + address = ((uint32_t *) tableListAddress)[i]; + } + + ACPIDescriptorTable *header = (ACPIDescriptorTable *) MMMapPhysical(kernelMMSpace, address, sizeof(ACPIDescriptorTable), ES_FLAGS_DEFAULT); + + KernelLog(LOG_INFO, "ACPI", "table enumerated", "ACPI::Initialise - Found ACPI table '%s'.\n", 4, &header->signature); + + if (header->signature == SIGNATURE_MADT) { + madt = (ACPIDescriptorTable *) MMMapPhysical(kernelMMSpace, address, header->length, ES_FLAGS_DEFAULT); + madt->Check(); + } else if (header->signature == SIGNATURE_FADT) { + ACPIDescriptorTable *fadt = (ACPIDescriptorTable *) MMMapPhysical(kernelMMSpace, address, header->length, ES_FLAGS_DEFAULT); + fadt->Check(); + + if (header->length > 109) { + uint8_t bootArchitectureFlags = ((uint8_t *) fadt)[109]; + ps2ControllerUnavailable = ~bootArchitectureFlags & (1 << 1); + vgaControllerUnavailable = bootArchitectureFlags & (1 << 2); + KernelLog(LOG_INFO, "ACPI", "FADT", "PS/2 controller is %z; VGA controller is %z.\n", + ps2ControllerUnavailable ? "unavailble" : "present", + vgaControllerUnavailable ? "unavailble" : "present"); + } + + MMFree(kernelMMSpace, fadt); + } + + MMFree(kernelMMSpace, header); + } + } else { + KernelPanic("ACPI::Initialise - Could not find a valid or supported system descriptor table.\nACPI support is required.\n"); + } + + // Set up the APIC. + + ACPIDescriptorTable *header = this->madt; + MultipleAPICDescriptionTable *madt = (MultipleAPICDescriptionTable *) ((uint8_t *) this->madt + ACPI_DESCRIPTOR_TABLE_HEADER_LENGTH); + + if (!madt) { + KernelPanic("ACPI::Initialise - Could not find the MADT table.\nThis is required to use the APIC.\n"); + } + + uintptr_t length = header->length - ACPI_DESCRIPTOR_TABLE_HEADER_LENGTH - sizeof(MultipleAPICDescriptionTable); + uintptr_t startLength = length; + uint8_t *data = (uint8_t *) (madt + 1); + + lapic.address = (uint32_t volatile *) ACPIMapPhysicalMemory(madt->lapicAddress, 0x10000); + + while (length && length <= startLength) { + uint8_t entryType = data[0]; + uint8_t entryLength = data[1]; + + switch (entryType) { + case 0: { + // A processor and its LAPIC. + if ((data[4] & 1) == 0) goto nextEntry; + ACPIProcessor *processor = processors + processorCount; + processor->processorID = data[2]; + processor->apicID = data[3]; + processorCount++; + } break; + + case 1: { + // An I/O APIC. + ioApics[ioapicCount].id = data[2]; + ioApics[ioapicCount].address = (uint32_t volatile *) ACPIMapPhysicalMemory(((uint32_t *) data)[1], 0x10000); + ioApics[ioapicCount].ReadRegister(0); // Make sure it's mapped. + ioApics[ioapicCount].gsiBase = ((uint32_t *) data)[2]; + ioapicCount++; + } break; + + case 2: { + // An interrupt source override structure. + interruptOverrides[interruptOverrideCount].sourceIRQ = data[3]; + interruptOverrides[interruptOverrideCount].gsiNumber = ((uint32_t *) data)[1]; + interruptOverrides[interruptOverrideCount].activeLow = (data[8] & 2) ? true : false; + interruptOverrides[interruptOverrideCount].levelTriggered = (data[8] & 8) ? true : false; + KernelLog(LOG_INFO, "ACPI", "interrupt override", "ACPI::Initialise - Source IRQ %d is mapped to GSI %d%z%z.\n", + interruptOverrides[interruptOverrideCount].sourceIRQ, + interruptOverrides[interruptOverrideCount].gsiNumber, + interruptOverrides[interruptOverrideCount].activeLow ? ", active low" : ", active high", + interruptOverrides[interruptOverrideCount].levelTriggered ? ", level triggered" : ", edge triggered"); + interruptOverrideCount++; + } break; + + case 4: { + // A non-maskable interrupt. + lapicNMIs[lapicNMICount].processor = data[2]; + lapicNMIs[lapicNMICount].lintIndex = data[5]; + lapicNMIs[lapicNMICount].activeLow = (data[3] & 2) ? true : false; + lapicNMIs[lapicNMICount].levelTriggered = (data[3] & 8) ? true : false; + lapicNMICount++; + } break; + + default: { + KernelLog(LOG_ERROR, "ACPI", "unrecognised MADT entry", "ACPI::Initialise - Found unknown entry of type %d in MADT\n", entryType); + } break; + } + + nextEntry: + length -= entryLength; + data += entryLength; + } + + if (processorCount > 256 || ioapicCount > 16 || interruptOverrideCount > 256 || lapicNMICount > 32) { + KernelPanic("ACPI::KernelPanic - Invalid number of processors (%d/%d), \n" + " I/O APICs (%d/%d), interrupt overrides (%d/%d)\n" + " and LAPIC NMIs (%d/%d)\n", + processorCount, 256, ioapicCount, 16, interruptOverrideCount, 256, lapicNMICount, 32); + } + + uint8_t bootstrapLapicID = (lapic.ReadRegister(0x20 >> 2) >> 24); + + for (uintptr_t i = 0; i < processorCount; i++) { + if (processors[i].apicID == bootstrapLapicID) { + // That's us! + bootstrapProcessor = processors + i; + bootstrapProcessor->bootstrapProcessor = true; + } + } + + if (!bootstrapProcessor) { + KernelPanic("ACPI::Initialise - Could not find the bootstrap processor\n"); + } + + // Calibrate the LAPIC's timer and processor's timestamp counter. + ProcessorDisableInterrupts(); + uint64_t start = ProcessorReadTimeStamp(); + acpi.lapic.WriteRegister(0x380 >> 2, (uint32_t) -1); + for (int i = 0; i < 8; i++) ArchDelay1Ms(); // Average over 8ms + acpi.lapic.ticksPerMs = ((uint32_t) -1 - acpi.lapic.ReadRegister(0x390 >> 2)) >> 4; + EsRandomAddEntropy(acpi.lapic.ReadRegister(0x390 >> 2)); + uint64_t end = ProcessorReadTimeStamp(); + timeStampTicksPerMs = (end - start) >> 3; + ProcessorEnableInterrupts(); + + // Add some entropy. + { + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 0); + EsRandomAddEntropy(ProcessorIn8(0x71) << 0); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 2); + EsRandomAddEntropy(ProcessorIn8(0x71) << 1); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 4); + EsRandomAddEntropy(ProcessorIn8(0x71) << 2); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 6); + EsRandomAddEntropy(ProcessorIn8(0x71) << 3); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 7); + EsRandomAddEntropy(ProcessorIn8(0x71) << 4); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 8); + EsRandomAddEntropy(ProcessorIn8(0x71) << 5); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 9); + EsRandomAddEntropy(ProcessorIn8(0x71) << 6); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 10); + EsRandomAddEntropy(ProcessorIn8(0x71) << 7); + for (int i = 0; i < 10; i++) ProcessorOut8(0x70, 11); + EsRandomAddEntropy(ProcessorIn8(0x71) << 8); + } + + // Finish processor initialisation. + // This sets up interrupts, the timer, CPULocalStorage, the GDT and TSS, + // and registers the processor with the scheduler. + + for (uintptr_t i = 0; i <= acpi.processorCount; i++) { + if (i == acpi.processorCount) { + KernelPanic("ACPI::Initialise - Could not find the bootstrap processor to perform second-stage initialisation.\n"); + } + + if (acpi.processors[i].bootstrapProcessor) { + NewProcessorStorage storage = AllocateNewProcessorStorage(acpi.processors + i); + SetupProcessor2(&storage); + break; + } + } +} + +void Wait1Ms() { + if (scheduler.started) { + KEvent event = {}; + KEventWait(&event, 1); + } else { + ArchDelay1Ms(); + } +} + +void ACPI::StartupApplicationProcessors() { +#ifdef USE_SMP + // TODO How do we know that this address is usable? +#define AP_TRAMPOLINE 0x10000 + + uint8_t *startupData = (uint8_t *) (LOW_MEMORY_MAP_START + AP_TRAMPOLINE); + + // Put the trampoline code in memory. + EsMemoryCopy(startupData, (void *) ProcessorAPStartup, 0x1000); // Assume that the AP trampoline code <=4KB. + + // Put the paging table location at AP_TRAMPOLINE + 0xFF0. + *((uint64_t *) (startupData + 0xFF0)) = ProcessorReadCR3(); + + // Put the 64-bit GDTR at AP_TRAMPOLINE + 0xFE0. + EsMemoryCopy(startupData + 0xFE0, (void *) processorGDTR, 0x10); + + // Put the GDT at AP_TRAMPOLINE + 0x1000. + EsMemoryCopy(startupData + 0x1000, (void *) gdt_data, 0x1000); + + // Put the startup flag at AP_TRAMPOLINE + 0xFC0 + uint8_t volatile *startupFlag = (uint8_t *) (LOW_MEMORY_MAP_START + AP_TRAMPOLINE + 0xFC0); + + // Temporarily identity map 2 pages in at 0x10000. + MMArchMapPage(kernelMMSpace, AP_TRAMPOLINE, AP_TRAMPOLINE, MM_MAP_PAGE_COMMIT_TABLES_NOW); + MMArchMapPage(kernelMMSpace, AP_TRAMPOLINE + 0x1000, AP_TRAMPOLINE + 0x1000, MM_MAP_PAGE_COMMIT_TABLES_NOW); + + for (uintptr_t i = 0; i < processorCount; i++) { + ACPIProcessor *processor = processors + i; + if (processor->bootstrapProcessor) continue; + + // Allocate state for the processor. + NewProcessorStorage storage = AllocateNewProcessorStorage(processor); + + // Clear the startup flag. + *startupFlag = 0; + + // Put the stack at AP_TRAMPOLINE + 0xFD0, and the address of the NewProcessorStorage at AP_TRAMPOLINE + 0xFB0. + void *stack = (void *) ((uintptr_t) MMStandardAllocate(kernelMMSpace, 0x1000, MM_REGION_FIXED) + 0x1000); + *((void **) (startupData + 0xFD0)) = stack; + *((NewProcessorStorage **) (startupData + 0xFB0)) = &storage; + + KernelLog(LOG_INFO, "ACPI", "starting processor", "Starting processor %d with local storage %x...\n", i, storage.local); + + // Send an INIT IPI. + ProcessorDisableInterrupts(); // Don't be interrupted between writes... + lapic.WriteRegister(0x310 >> 2, processor->apicID << 24); + lapic.WriteRegister(0x300 >> 2, 0x4500); + ProcessorEnableInterrupts(); + for (uintptr_t i = 0; i < 10; i++) Wait1Ms(); + + // Send a startup IPI. + ProcessorDisableInterrupts(); + lapic.WriteRegister(0x310 >> 2, processor->apicID << 24); + lapic.WriteRegister(0x300 >> 2, 0x4600 | (AP_TRAMPOLINE >> K_PAGE_BITS)); + ProcessorEnableInterrupts(); + for (uintptr_t i = 0; i < 100 && *startupFlag == 0; i++) Wait1Ms(); + + if (*startupFlag) { + // The processor started correctly. + } else { + // Send a startup IPI, again. + ProcessorDisableInterrupts(); + lapic.WriteRegister(0x310 >> 2, processor->apicID << 24); + lapic.WriteRegister(0x300 >> 2, 0x4600 | (AP_TRAMPOLINE >> K_PAGE_BITS)); + ProcessorEnableInterrupts(); + for (uintptr_t i = 0; i < 1000 && *startupFlag == 0; i++) Wait1Ms(); // Wait longer this time. + + if (*startupFlag) { + // The processor started correctly. + } else { + // The processor could not be started. + KernelLog(LOG_ERROR, "ACPI", "processor startup failure", + "ACPI::Initialise - Could not start processor %d\n", processor->processorID); + continue; + } + } + + // EsPrint("Startup flag 1 reached!\n"); + + for (uintptr_t i = 0; i < 10000 && *startupFlag != 2; i++) Wait1Ms(); + + if (*startupFlag == 2) { + // The processor started! + } else { + // The processor did not report it completed initilisation, worringly. + // Don't let it continue. + + KernelLog(LOG_ERROR, "ACPI", "processor startup failure", + "ACPI::Initialise - Could not initialise processor %d\n", processor->processorID); + + // TODO Send IPI to stop the processor. + } + } + + // Remove the identity pages needed for the trampoline code. + MMArchUnmapPages(kernelMMSpace, AP_TRAMPOLINE, 2, ES_FLAGS_DEFAULT); +#endif +} + +size_t KGetCPUCount() { + return acpi.processorCount; +} + +CPULocalStorage *KGetCPULocal(uintptr_t index) { + return acpi.processors[index].local; +} diff --git a/drivers/ahci.cpp b/drivers/ahci.cpp new file mode 100644 index 0000000..b841256 --- /dev/null +++ b/drivers/ahci.cpp @@ -0,0 +1,886 @@ +#include + +// TODO Inserting/removing CDs. + +#define GENERAL_TIMEOUT (5000) + +#define COMMAND_LIST_SIZE (0x400) +#define RECEIVED_FIS_SIZE (0x100) +#define PRDT_ENTRY_COUNT (0x48) // If one page each, this covers more than CC_ACTIVE_SECTION_SIZE. This must be a multiple of 8. +#define COMMAND_TABLE_SIZE (0x80 + PRDT_ENTRY_COUNT * 0x10) + +// Global registers. +#define RD_REGISTER_CAP() pci->ReadBAR32(5, 0x00) // HBA capababilities. +#define RD_REGISTER_GHC() pci->ReadBAR32(5, 0x04) // Global host control. +#define WR_REGISTER_GHC(x) pci->WriteBAR32(5, 0x04, x) +#define RD_REGISTER_IS() pci->ReadBAR32(5, 0x08) // Interrupt status. +#define WR_REGISTER_IS(x) pci->WriteBAR32(5, 0x08, x) +#define RD_REGISTER_PI() pci->ReadBAR32(5, 0x0C) // Ports implemented. +#define RD_REGISTER_CAP2() pci->ReadBAR32(5, 0x24) // HBA capababilities extended. +#define RD_REGISTER_BOHC() pci->ReadBAR32(5, 0x28) // BIOS/OS handoff control and status. +#define WR_REGISTER_BOHC(x) pci->WriteBAR32(5, 0x28, x) + +// Port-specific registers. +#define RD_REGISTER_PCLB(p) pci->ReadBAR32(5, 0x100 + (p) * 0x80) // Command list base address (low DWORD). +#define WR_REGISTER_PCLB(p, x) pci->WriteBAR32(5, 0x100 + (p) * 0x80, x) +#define RD_REGISTER_PCLBU(p) pci->ReadBAR32(5, 0x104 + (p) * 0x80) // Command list base address (high DWORD). +#define WR_REGISTER_PCLBU(p, x) pci->WriteBAR32(5, 0x104 + (p) * 0x80, x) +#define RD_REGISTER_PFB(p) pci->ReadBAR32(5, 0x108 + (p) * 0x80) // FIS base address (low DWORD). +#define WR_REGISTER_PFB(p, x) pci->WriteBAR32(5, 0x108 + (p) * 0x80, x) +#define RD_REGISTER_PFBU(p) pci->ReadBAR32(5, 0x10C + (p) * 0x80) // FIS base address (high DWORD). +#define WR_REGISTER_PFBU(p, x) pci->WriteBAR32(5, 0x10C + (p) * 0x80, x) +#define RD_REGISTER_PIS(p) pci->ReadBAR32(5, 0x110 + (p) * 0x80) // Interrupt status. +#define WR_REGISTER_PIS(p, x) pci->WriteBAR32(5, 0x110 + (p) * 0x80, x) +#define RD_REGISTER_PIE(p) pci->ReadBAR32(5, 0x114 + (p) * 0x80) // Interrupt enable. +#define WR_REGISTER_PIE(p, x) pci->WriteBAR32(5, 0x114 + (p) * 0x80, x) +#define RD_REGISTER_PCMD(p) pci->ReadBAR32(5, 0x118 + (p) * 0x80) // Command and status. +#define WR_REGISTER_PCMD(p, x) pci->WriteBAR32(5, 0x118 + (p) * 0x80, x) +#define RD_REGISTER_PTFD(p) pci->ReadBAR32(5, 0x120 + (p) * 0x80) // Task file data. +#define RD_REGISTER_PSIG(p) pci->ReadBAR32(5, 0x124 + (p) * 0x80) // Signature. +#define RD_REGISTER_PSSTS(p) pci->ReadBAR32(5, 0x128 + (p) * 0x80) // SATA status. +#define RD_REGISTER_PSCTL(p) pci->ReadBAR32(5, 0x12C + (p) * 0x80) // SATA control. +#define WR_REGISTER_PSCTL(p, x) pci->WriteBAR32(5, 0x12C + (p) * 0x80, x) +#define RD_REGISTER_PSERR(p) pci->ReadBAR32(5, 0x130 + (p) * 0x80) // SATA error. +#define WR_REGISTER_PSERR(p, x) pci->WriteBAR32(5, 0x130 + (p) * 0x80, x) +#define RD_REGISTER_PCI(p) pci->ReadBAR32(5, 0x138 + (p) * 0x80) // Command issue. +#define WR_REGISTER_PCI(p, x) pci->WriteBAR32(5, 0x138 + (p) * 0x80, x) + +struct AHCIPort { + bool connected, atapi, ssd; + + uint32_t *commandList; + uint8_t *commandTables; + + size_t sectorBytes; + uint64_t sectorCount; + + KWorkGroup *commandContexts[32]; // Set to indicate command in use. + uint64_t commandStartTimeStamps[32]; + uint32_t runningCommands; + + KSpinlock commandSpinlock; + KEvent commandSlotsAvailable; + + char model[41]; +}; + +struct AHCIController : KDevice { + KPCIDevice *pci; + + uint32_t capabilities, capabilities2; + bool dma64Supported; + size_t commandSlotCount; + + KTimer timeoutTimer; + +#define MAX_PORTS (32) + AHCIPort ports[MAX_PORTS]; + + void Initialise(); + bool Access(uintptr_t port, uint64_t offsetBytes, size_t countBytes, int operation, + KDMABuffer *buffer, uint64_t flags, KWorkGroup *dispatchGroup); + bool HandleIRQ(); + bool SendSingleCommand(uintptr_t port); + void DumpState(); +}; + +struct AHCIDrive : KBlockDevice { + AHCIController *controller; + uintptr_t port; +}; + +struct InterruptEvent { + uint64_t timeStamp; + uint32_t globalInterruptStatus; + uint32_t port0CommandsRunning; + uint32_t port0CommandsIssued; + bool complete; +}; + +volatile uintptr_t recentInterruptEventsPointer; +volatile InterruptEvent recentInterruptEvents[64]; + +void AHCIController::DumpState() { + uint64_t timeStamp = KGetTimeInMs(); + + EsPrint("AHCI controller state:\n"); + + EsPrint("\t--- Registers ---\n"); + EsPrint("\t\tHBA capabilities: %x.\n", RD_REGISTER_CAP()); + EsPrint("\t\tGlobal host control: %x.\n", RD_REGISTER_GHC()); + EsPrint("\t\tInterrupt status: %x.\n", RD_REGISTER_IS()); + EsPrint("\t\tPorts implemented: %x.\n", RD_REGISTER_PI()); + EsPrint("\t\tHBA capabilities extended: %x.\n", RD_REGISTER_CAP2()); + EsPrint("\t\tBIOS/OS handoff control and status: %x.\n", RD_REGISTER_BOHC()); + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + AHCIPort *port = ports + i; + + if (!port->connected) { + continue; + } + + EsPrint("\t--- Port %d ---\n", i); + EsPrint("\t\tCommand list base address (low DWORD): %x.\n", RD_REGISTER_PCLB(i)); + EsPrint("\t\tCommand list base address (high DWORD): %x.\n", RD_REGISTER_PCLBU(i)); + EsPrint("\t\tFIS base address (low DWORD): %x.\n", RD_REGISTER_PFB(i)); + EsPrint("\t\tFIS base address (high DWORD): %x.\n", RD_REGISTER_PFBU(i)); + EsPrint("\t\tInterrupt status: %x.\n", RD_REGISTER_PIS(i)); + EsPrint("\t\tInterrupt enable: %x.\n", RD_REGISTER_PIE(i)); + EsPrint("\t\tCommand and status: %x.\n", RD_REGISTER_PCMD(i)); + EsPrint("\t\tTask file data: %x.\n", RD_REGISTER_PTFD(i)); + EsPrint("\t\tSignature: %x.\n", RD_REGISTER_PSIG(i)); + EsPrint("\t\tSATA status: %x.\n", RD_REGISTER_PSSTS(i)); + EsPrint("\t\tSATA error: %x.\n", RD_REGISTER_PSERR(i)); + EsPrint("\t\tCommand issue: %x.\n", RD_REGISTER_PCI(i)); + EsPrint("\t\tATAPI: %d.\n", port->atapi); + EsPrint("\t\tBytes per sector: %D.\n", port->sectorBytes); + EsPrint("\t\tTotal capacity: %D.\n", port->sectorBytes * port->sectorCount); + EsPrint("\t\tCommand slots available: %d.\n", port->commandSlotsAvailable.state); + EsPrint("\t\tRunning commands: %x.\n", port->runningCommands); + + uint8_t *receivedFIS = (uint8_t *) port->commandList + 0x400; + + EsPrint("\t\tReceived FIS D2H register: type %X, interrupt %X, status %X, error %X, device/head %X, sector %x, sector count %x.\n", + receivedFIS[0x40 + 0], receivedFIS[0x40 + 1], receivedFIS[0x40 + 2], receivedFIS[0x40 + 3], receivedFIS[0x40 + 7], + (uint64_t) receivedFIS[0x40 + 4] | ((uint64_t) receivedFIS[0x40 + 5] << 8) | ((uint64_t) receivedFIS[0x40 + 6] << 16) + | ((uint64_t) receivedFIS[0x40 + 8] << 24) | ((uint64_t) receivedFIS[0x40 + 9] << 32) | ((uint64_t) receivedFIS[0x40 + 10] << 40), + (uint64_t) receivedFIS[0x40 + 12] | ((uint64_t) receivedFIS[0x40 + 13] << 8)); + EsPrint("\t\tReceived FIS set device bits: type %X, interrupt %X, status %X, error %X.\n", + receivedFIS[0x58 + 0], receivedFIS[0x58 + 1], receivedFIS[0x58 + 2], receivedFIS[0x58 + 3]); + EsPrint("\t\tReceived FIS DMA setup: type %X, flags %X, buffer identifier low %x, buffer identifier high %x, buffer offset %x, transfer count %x.\n", + receivedFIS[0], receivedFIS[1], ((uint32_t *) receivedFIS)[1], + ((uint32_t *) receivedFIS)[2], ((uint32_t *) receivedFIS)[4], ((uint32_t *) receivedFIS)[5]); + + for (uintptr_t j = 0; j < 32; j++) { + if (~port->runningCommands & (1 << j)) continue; + + EsPrint("\t\tCommand %d: started %dms ago for dispatch group %x.\n", j, + timeStamp - port->commandStartTimeStamps[j], port->commandContexts[j]); + + EsPrint("\t\t\tDW0 %x: %d FIS bytes, %z, %z, %d PRDT entries.\n", + port->commandList[j * 8 + 0], (port->commandList[j * 8 + 0]) & 31, + ((port->commandList[j * 8 + 0]) & (1 << 5)) ? "SATAPI" : "SATA", + ((port->commandList[j * 8 + 0]) & (1 << 6)) ? "write" : "read", + port->commandList[j * 8 + 0] >> 16); + EsPrint("\t\t\tDW1 transferring %D.\n", port->commandList[j * 8 + 1]); + EsPrint("\t\t\tDW2/3 command table base address: %x.\n", *(uint64_t *) &port->commandList[j * 8 + 2]); + + uint8_t *commandFIS = port->commandTables + COMMAND_TABLE_SIZE * j; + + EsPrint("\t\t\tH2D FIS: type %X, command %X, features %X, device/head %X, features 2 %X, control %X, sector %x, sector count %x.\n", + commandFIS[0], commandFIS[2], commandFIS[3], commandFIS[7], commandFIS[11], commandFIS[15], + (uint64_t) commandFIS[4] | ((uint64_t) commandFIS[5] << 8) | ((uint64_t) commandFIS[6] << 16) + | ((uint64_t) commandFIS[8] << 24) | ((uint64_t) commandFIS[9] << 32) | ((uint64_t) commandFIS[10] << 40), + (uint64_t) commandFIS[12] | ((uint64_t) commandFIS[13] << 8)); + + uint8_t *atapiCommand = commandFIS + 64; + + if (((port->commandList[j * 8 + 0]) & (1 << 5))) { + EsPrint("\t\t\tATAPI command: %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X.\n", + atapiCommand[0], atapiCommand[1], atapiCommand[2], atapiCommand[3], + atapiCommand[4], atapiCommand[5], atapiCommand[6], atapiCommand[7], + atapiCommand[8], atapiCommand[9], atapiCommand[10], atapiCommand[11], + atapiCommand[12], atapiCommand[13], atapiCommand[14], atapiCommand[15]); + } + + uint32_t *prdt = (uint32_t *) (commandFIS + 128); + + for (uintptr_t k = 0; k < (port->commandList[j * 8 + 0] >> 16); k++) { + EsPrint("\t\t\tPRDT entry %d: base address %x, byte count %x%z.\n", + k, *(uint64_t *) (prdt + k * 4), (prdt[k * 4 + 3] & 0xFFFFFFF) + 1, + (prdt[k * 4 + 3] & 0x80000000) ? ", interrupt on completion" : ""); + } + } + } + + EsPrint("\t--- Most recent interrupts ---\n"); + + for (uintptr_t i = 0; i < sizeof(recentInterruptEvents) / sizeof(recentInterruptEvents[0]); i++) { + if (recentInterruptEventsPointer) recentInterruptEventsPointer--; + else recentInterruptEventsPointer = sizeof(recentInterruptEvents) / sizeof(recentInterruptEvents[0]) - 1; + + volatile InterruptEvent *event = recentInterruptEvents + recentInterruptEventsPointer; + + EsPrint("\t\tEvent %d: fired %dms ago, GIS %x, CI0 %x, CR0 %x%z.\n", + i, timeStamp - event->timeStamp, + event->globalInterruptStatus, event->port0CommandsIssued, + event->port0CommandsRunning, event->complete ? "" : ", incomplete"); + } +} + +bool AHCIController::Access(uintptr_t portIndex, uint64_t offsetBytes, size_t countBytes, int operation, + KDMABuffer *buffer, uint64_t, KWorkGroup *dispatchGroup) { + AHCIPort *port = ports + portIndex; + +#if 0 + // TODO Temporary. + + if (operation == K_ACCESS_WRITE) { + KernelPanic("AHCIController::Access - Attempted write.\n"); + } +#endif + + // Find a command slot to use. + + uintptr_t commandIndex = 0; + + while (true) { + KSpinlockAcquire(&port->commandSpinlock); + + uint32_t commandsAvailable = ~RD_REGISTER_PCI(portIndex); + bool found = false; + + for (uintptr_t i = 0; i < commandSlotCount; i++) { + if ((commandsAvailable & (1 << i)) && !port->commandContexts[i]) { + commandIndex = i; + found = true; + break; + } + } + + if (!found) { + KEventReset(&port->commandSlotsAvailable); + } else { + port->commandContexts[commandIndex] = dispatchGroup; + } + + KSpinlockRelease(&port->commandSpinlock); + + if (!found) { + KEventWait(&port->commandSlotsAvailable); + } else { + break; + } + } + + // Setup the command FIS. + + uint32_t countSectors = countBytes / port->sectorBytes; + uint64_t offsetSectors = offsetBytes / port->sectorBytes; + + if (countSectors & ~0xFFFF) { + KernelPanic("AHCIController::Access - Too many sectors to read.\n"); + } + + uint32_t *commandFIS = (uint32_t *) (port->commandTables + COMMAND_TABLE_SIZE * commandIndex); + commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | ((operation == K_ACCESS_WRITE ? 0x35 /* write DMA 48 */ : 0x25 /* read DMA 48 */) << 16); + commandFIS[1] = (offsetSectors & 0xFFFFFF) | (1 << 30); + commandFIS[2] = (offsetSectors >> 24) & 0xFFFFFF; + commandFIS[3] = countSectors & 0xFFFF; + commandFIS[4] = 0; + + // Setup the PRDT. + + size_t prdtEntryCount = 0; + uint32_t *prdt = (uint32_t *) (port->commandTables + COMMAND_TABLE_SIZE * commandIndex + 0x80); + + while (!KDMABufferIsComplete(buffer)) { + if (prdtEntryCount == PRDT_ENTRY_COUNT) { + KernelPanic("AHCIController::Access - Too many PRDT entries.\n"); + } + + KDMASegment segment = KDMABufferNextSegment(buffer); + + prdt[0 + 4 * prdtEntryCount] = segment.physicalAddress; + prdt[1 + 4 * prdtEntryCount] = segment.physicalAddress >> 32; + prdt[2 + 4 * prdtEntryCount] = 0; + prdt[3 + 4 * prdtEntryCount] = (segment.byteCount - 1) | (segment.isLast ? (1 << 31) /* IRQ when done */ : 0); + + prdtEntryCount++; + } + + // Setup the command list entry, and issue the command. + + port->commandList[commandIndex * 8 + 0] = 5 /* FIS is 5 DWORDs */ | (prdtEntryCount << 16) | (operation == K_ACCESS_WRITE ? (1 << 6) : 0); + port->commandList[commandIndex * 8 + 1] = 0; + + // Setup SCSI command if ATAPI. + + if (port->atapi) { + port->commandList[commandIndex * 8 + 0] |= (1 << 5) /* ATAPI */; + commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | (0xA0 /* packet */ << 16); + commandFIS[1] = countBytes << 8; + + uint8_t *scsiCommand = (uint8_t *) commandFIS + 0x40; + EsMemoryZero(scsiCommand, 10); + scsiCommand[0] = 0xA8 /* READ (12) */; + scsiCommand[2] = (offsetSectors >> 0x18) & 0xFF; + scsiCommand[3] = (offsetSectors >> 0x10) & 0xFF; + scsiCommand[4] = (offsetSectors >> 0x08) & 0xFF; + scsiCommand[5] = (offsetSectors >> 0x00) & 0xFF; + scsiCommand[9] = countSectors; + } + + // Start executing the command. + + KSpinlockAcquire(&port->commandSpinlock); + port->runningCommands |= 1 << commandIndex; + __sync_synchronize(); + WR_REGISTER_PCI(portIndex, 1 << commandIndex); + port->commandStartTimeStamps[commandIndex] = KGetTimeInMs(); + KSpinlockRelease(&port->commandSpinlock); + + return true; +} + +bool AHCIController::HandleIRQ() { + uint32_t globalInterruptStatus = RD_REGISTER_IS(); + KernelLog(LOG_VERBOSE, "AHCI", "received IRQ", "Received IRQ with status: %x.\n", globalInterruptStatus); + if (!globalInterruptStatus) return false; + WR_REGISTER_IS(globalInterruptStatus); + + volatile InterruptEvent *event = recentInterruptEvents + recentInterruptEventsPointer; + event->timeStamp = KGetTimeInMs(); + event->globalInterruptStatus = globalInterruptStatus; + event->complete = false; + recentInterruptEventsPointer = (recentInterruptEventsPointer + 1) % (sizeof(recentInterruptEvents) / sizeof(recentInterruptEvents[0])); + + bool commandCompleted = false; + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + if (~globalInterruptStatus & (1 << i)) continue; + + uint32_t interruptStatus = RD_REGISTER_PIS(i); + if (!interruptStatus) continue; + WR_REGISTER_PIS(i, interruptStatus); + + AHCIPort *port = ports + i; + + if (interruptStatus & ((1 << 30) | (1 << 29) | (1 << 28) | (1 << 27) | (1 << 26) | (1 << 24) | (1 << 23))) { + KernelLog(LOG_ERROR, "AHCI", "error IRQ", "Received IRQ error interrupt status bit set: %x.\n", interruptStatus); + + KSpinlockAcquire(&port->commandSpinlock); + + // Stop command processing. + + WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) & ~(1 << 0)); + + // Fail all outstanding commands. + + for (uintptr_t j = 0; j < 32; j++) { + if (port->runningCommands & (1 << j)) { + port->commandContexts[j]->End(false /* failed */); + port->commandContexts[j] = nullptr; + } + } + + port->runningCommands = 0; + KEventSet(&port->commandSlotsAvailable, false, true /* maybe already set */); + + // Restart command processing. + + WR_REGISTER_PSERR(i, 0xFFFFFFFF); + KTimeout timeout(5); + while ((RD_REGISTER_PCMD(i) & (1 << 15)) && !timeout.Hit()); + WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) | (1 << 0)); + + KSpinlockRelease(&port->commandSpinlock); + + continue; + } + + KSpinlockAcquire(&port->commandSpinlock); + + uint32_t commandsIssued = RD_REGISTER_PCI(i); + + if (i == 0) event->port0CommandsIssued = commandsIssued, event->port0CommandsRunning = port->runningCommands; + + for (uintptr_t j = 0; j < 32; j++) { + if (~port->runningCommands & (1 << j)) continue; // Command not started. + if (commandsIssued & (1 << j)) continue; // Command still running. + + // The command has completed. + + port->commandContexts[j]->End(true /* success */); + port->commandContexts[j] = nullptr; + KEventSet(&port->commandSlotsAvailable, false, true /* maybe already set */); + port->runningCommands &= ~(1 << j); + + commandCompleted = true; + } + + KSpinlockRelease(&port->commandSpinlock); + } + + if (commandCompleted) { + KSwitchThreadAfterIRQ(); + } + + event->complete = true; + + return true; +} + +void TimeoutTimerHit(EsGeneric argument) { + AHCIController *controller = (AHCIController *) argument.p; + + uint64_t currentTimeStamp = KGetTimeInMs(); + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + AHCIPort *port = controller->ports + i; + + KSpinlockAcquire(&port->commandSpinlock); + + for (uintptr_t j = 0; j < controller->commandSlotCount; j++) { + if ((port->runningCommands & (1 << j)) + && port->commandStartTimeStamps[j] + GENERAL_TIMEOUT < currentTimeStamp) { + KernelLog(LOG_ERROR, "AHCI", "command timeout", "Command %d on port %d timed out.\n", j, i); + + port->commandContexts[j]->End(false /* failure */); + port->commandContexts[j] = nullptr; + port->runningCommands &= ~(1 << j); + + // Don't set the commandSlotsAvailable event, since the controller still thinks the command is in use. + // TODO What happens if there are no commands left? + } + } + + KSpinlockRelease(&port->commandSpinlock); + } + + KTimerSet(&controller->timeoutTimer, GENERAL_TIMEOUT, TimeoutTimerHit, controller); +} + +bool AHCIController::SendSingleCommand(uintptr_t port) { + KTimeout timeout(GENERAL_TIMEOUT); + + // Wait for the port to be ready, then issue the command. + + while ((RD_REGISTER_PTFD(port) & ((1 << 7) | (1 << 3))) && !timeout.Hit()); + + if (timeout.Hit()) { + KernelLog(LOG_ERROR, "AHCI", "port hung", "Port %d bits DRQ/BSY won't clear.\n", port); + return false; + } + + __sync_synchronize(); + WR_REGISTER_PCI(port, 1 << 0); + + // Wait for command to complete. + + bool complete = false; + + while (!timeout.Hit()) { + if (~RD_REGISTER_PCI(port) & (1 << 0)) { + complete = true; + break; + } + } + + return complete; +} + +void AHCIController::Initialise() { + // Perform BIOS/OS handoff, if necessary. + + if (RD_REGISTER_CAP2() & (1 << 0)) { + KernelLog(LOG_INFO, "AHCI", "perform handoff", "Performing BIOS/OS handoff...\n"); + + WR_REGISTER_BOHC(RD_REGISTER_BOHC() | (1 << 1)); + + KTimeout timeout(25 /* ms */); + uint32_t status; + + while (true) { + status = RD_REGISTER_BOHC(); + if (~status & (1 << 0)) break; + if (timeout.Hit()) break; + } + + if (status & (1 << 0)) { + KEvent event = {}; + KernelLog(LOG_ERROR, "AHCI", "handoff error", "BIOS/OS handoff did not succeed, waiting 2 seconds to proceed...\n"); + KEventWait(&event, 2000 /* ms */); + } + } + + // Reset controller. + + { + KTimeout timeout(GENERAL_TIMEOUT); + WR_REGISTER_GHC(RD_REGISTER_GHC() | (1 << 0)); + while ((RD_REGISTER_GHC() & (1 << 0)) && !timeout.Hit()); + + if (timeout.Hit()) { + KernelLog(LOG_ERROR, "AHCI", "reset controller timeout", "The controller did not reset within the timeout.\n"); + return; + } + } + + // Register IRQ handler. + + KIRQHandler handler = [] (uintptr_t, void *context) { return ((AHCIController *) context)->HandleIRQ(); }; + + if (!pci->EnableSingleInterrupt(handler, this, "AHCI")) { + KernelLog(LOG_ERROR, "AHCI", "IRQ registration failure", "Could not register intrrupt handler.\n"); + return; + } + + // Enable AHCI mode and interrupts. + + WR_REGISTER_GHC(RD_REGISTER_GHC() | (1 << 31) | (1 << 1)); + + capabilities = RD_REGISTER_CAP(); + capabilities2 = RD_REGISTER_CAP2(); + commandSlotCount = ((capabilities >> 8) & 31) + 1; + dma64Supported = capabilities & (1 << 31); + +#ifdef ARCH_64 + if (!dma64Supported) { + KernelLog(LOG_ERROR, "AHCI", "controller cannot DMA", "The controller reports it cannot use 64-bit addresses in DMA transfer.\n"); + return; + } +#endif + + // Work out which ports have drives connected. + + size_t maximumNumberOfPorts = (capabilities & 31) + 1; + size_t portsFound = 0; + + uint32_t portsImplemented = RD_REGISTER_PI(); + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + if (portsImplemented & (1 << i)) { + portsFound++; + + if (portsFound <= maximumNumberOfPorts) { + ports[i].connected = true; + } + } + } + + KernelLog(LOG_INFO, "AHCI", "information", "Capabilities: %x, %x. Command slot count: %d. Implemented ports: %x.\n", + capabilities, capabilities2, commandSlotCount, portsImplemented); + + // Setup the command lists, FISes and command tables. + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + if (!ports[i].connected) continue; + + size_t bytesNeeded = COMMAND_LIST_SIZE + RECEIVED_FIS_SIZE + COMMAND_TABLE_SIZE * commandSlotCount; + + uint8_t *virtualAddress; + uintptr_t physicalAddress; + + if (!MMPhysicalAllocateAndMap(bytesNeeded, K_PAGE_SIZE, dma64Supported ? 64 : 32, true, MM_REGION_NOT_CACHEABLE, &virtualAddress, &physicalAddress)) { + KernelLog(LOG_ERROR, "AHCI", "allocation failure", "Could not allocate physical memory for port %d.\n", i); + break; + } + + ports[i].commandList = (uint32_t *) virtualAddress; + ports[i].commandTables = virtualAddress + COMMAND_LIST_SIZE + RECEIVED_FIS_SIZE; + + // Set the registers to the physical addresses. + + WR_REGISTER_PCLB(i, physicalAddress); + if (dma64Supported) WR_REGISTER_PCLBU(i, physicalAddress >> 32); + WR_REGISTER_PFB(i, (physicalAddress + 0x400)); + if (dma64Supported) WR_REGISTER_PFBU(i, (physicalAddress + 0x400) >> 32); + + // Point each command list entry to the corresponding command table. + + uint32_t *commandList = ports[i].commandList; + + for (uintptr_t j = 0; j < commandSlotCount; j++) { + uintptr_t address = physicalAddress + COMMAND_LIST_SIZE + RECEIVED_FIS_SIZE + COMMAND_TABLE_SIZE * j; + commandList[j * 8 + 2] = address; + commandList[j * 8 + 3] = address >> 32; + } + + // Reset the port. + + KTimeout timeout(GENERAL_TIMEOUT); + + const uint32_t runningBits = ((1 << 0 /* start */) | (1 << 4 /* receive FIS enable */) + | (1 << 15 /* command list running */) | (1 << 14 /* receive FIS running */)); + + while (true) { + uint32_t status = RD_REGISTER_PCMD(i); + + if (!(status & runningBits) || timeout.Hit()) { + break; + } + + // Stop command list processing and receive FIS. + WR_REGISTER_PCMD(i, status & ~((1 << 0) | (1 << 4))); + } + + if (RD_REGISTER_PCMD(i) & runningBits) { + KernelLog(LOG_ERROR, "AHCI", "reset port timeout", "Resetting port %d timed out. PCMD: %x.\n", + i, RD_REGISTER_PCMD(i)); + ports[i].connected = false; + continue; + } + + // Clear IRQs. + + WR_REGISTER_PIE(i, RD_REGISTER_PIE(i) & 0x0E3FFF00); + WR_REGISTER_PIS(i, RD_REGISTER_PIS(i)); + + // Enable receive FIS and activate the drive. + + WR_REGISTER_PSCTL(i, RD_REGISTER_PSCTL(i) | (3 << 8)); // Disable transitions to partial and slumber states. + WR_REGISTER_PCMD(i, (RD_REGISTER_PCMD(i) & 0x0FFFFFFF) + | (1 << 1 /* spin up */) | (1 << 2 /* power on */) | (1 << 4 /* FIS receive */) | (1 << 28 /* activate */)); + + KTimeout linkTimeout(10); + + while ((RD_REGISTER_PSSTS(i) & 0x0F) != 3 && !linkTimeout.Hit()); + + if ((RD_REGISTER_PSSTS(i) & 0x0F) != 3) { + KernelLog(LOG_ERROR, "AHCI", "activate port timeout", "Activating port %d timed out. PSSTS: %x.\n", + i, RD_REGISTER_PSSTS(i)); + ports[i].connected = false; + continue; + } + + // Clear errors. + + WR_REGISTER_PSERR(i, RD_REGISTER_PSERR(i)); + + // Wait for device to be ready. + + while ((RD_REGISTER_PTFD(i) & 0x88 /* BSY and DRQ */) && !timeout.Hit()); + + if (RD_REGISTER_PTFD(i) & 0x88) { + KernelLog(LOG_ERROR, "AHCI", "port ready timeout", "Port %d hung at busy state. PTFD: %x.\n", + i, RD_REGISTER_PTFD(i)); + ports[i].connected = false; + continue; + } + + // Start command list processing. + + KernelLog(LOG_INFO, "AHCI", "start command processing", "Starting command processing for port %d...\n", i); + WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) | (1 << 0)); + + // Enable interrupts. + + KernelLog(LOG_INFO, "AHCI", "enable interrupts", "Enabling interrupts for port %d...\n", i); + WR_REGISTER_PIE(i, RD_REGISTER_PIE(i) | (1 << 5) /* descriptor complete */ | (1 << 0) /* D2H */ + | (1 << 30) | (1 << 29) | (1 << 28) | (1 << 27) | (1 << 26) | (1 << 24) | (1 << 23) /* errors */); + } + + // Read the status and signature for each implemented port to work out if it is connected. + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + if (!ports[i].connected) continue; + + uint32_t status = RD_REGISTER_PSSTS(i); + + if ((status & 0x00F) != 0x003 || (status & 0x0F0) == 0x000 || (status & 0xF00) != 0x100) { + ports[i].connected = false; + KernelLog(LOG_INFO, "AHCI", "no drive", "No drive connected to port %d (1).\n", i); + continue; + } + + uint32_t signature = RD_REGISTER_PSIG(i); + + if (signature == 0x00000101) { + // SATA drive. + KernelLog(LOG_INFO, "AHCI", "found drive", "Found SATA drive on port %d.\n", i); + } else if (signature == 0xEB140101) { + // SATAPI drive. + ports[i].atapi = true; + KernelLog(LOG_INFO, "AHCI", "found drive", "Found SATAPI drive on port %d.\n", i); + } else if (!signature) { + // No drive connected. + ports[i].connected = false; + KernelLog(LOG_INFO, "AHCI", "no drive", "No drive connected to port %d (2).\n", i); + } else { + KernelLog(LOG_ERROR, "AHCI", "unrecognised drive signature", "Unrecognised drive signature %x on port %d.\n", signature, i); + ports[i].connected = false; + } + } + + // Identify each connected drive. + + uint16_t *identifyData; + uintptr_t identifyDataPhysical; + + if (!MMPhysicalAllocateAndMap(0x200, K_PAGE_SIZE, dma64Supported ? 64 : 32, true, MM_REGION_NOT_CACHEABLE, (uint8_t **) &identifyData, &identifyDataPhysical)) { + KernelLog(LOG_ERROR, "AHCI", "allocation failure", "Could not allocate physical memory for identify data buffer.\n"); + return; + } + + KernelLog(LOG_INFO, "AHCI", "identify data", "Identify data buffer allocated at physical address %x.\n", identifyDataPhysical); + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + if (!ports[i].connected) continue; + + EsMemoryZero(identifyData, 0x200); + + // Setup the command list entry. + + ports[i].commandList[0] = 5 /* FIS is 5 DWORDs */ | (1 << 16) /* 1 PRDT entry */; + ports[i].commandList[1] = 0; + + // Setup the command FIS. + + uint8_t opcode = ports[i].atapi ? 0xA1 /* IDENTIFY PACKET */ : 0xEC /* IDENTIFY */; + uint32_t *commandFIS = (uint32_t *) ports[i].commandTables; + commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | (opcode << 16); + commandFIS[1] = commandFIS[2] = commandFIS[3] = commandFIS[4] = 0; + + // Setup the PRDT. + + uint32_t *prdt = (uint32_t *) (ports[i].commandTables + 0x80); + prdt[0] = identifyDataPhysical; + prdt[1] = identifyDataPhysical >> 32; + prdt[2] = 0; + prdt[3] = 0x200 - 1; + + KernelLog(LOG_INFO, "AHCI", "identifying drive", "Sending IDENTIFY command to port %d...\n", i); + + if (!SendSingleCommand(i)) { + KernelLog(LOG_ERROR, "AHCI", "identify failure", "Could not read identify data for port %d.\n", i); + WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) & ~(1 << 0)); // Stop command processing. + ports[i].connected = false; + continue; + } + + ports[i].sectorBytes = 0x200; + + if ((identifyData[106] & (1 << 14)) && (~identifyData[106] & (1 << 15)) && (identifyData[106] & (1 << 12))) { + // Device has a logical sector size larger than 0x200 bytes. + ports[i].sectorBytes = (uint32_t) identifyData[117] | ((uint32_t) identifyData[118] << 16); + } + + ports[i].sectorCount = ((uint64_t) identifyData[100] << 0) + ((uint64_t) identifyData[101] << 16) + + ((uint64_t) identifyData[102] << 32) + ((uint64_t) identifyData[103] << 48); + + if (!((identifyData[49] & (1 << 9)) && (identifyData[49] & (1 << 8)))) { + KernelLog(LOG_ERROR, "AHCI", "unsupported feature", "Drive on port %d does not support a required feature.\n", i); + ports[i].connected = false; + continue; + } + + if (ports[i].atapi) { + // Send a read capacity command. + + ports[i].commandList[0] = 5 /* FIS is 5 DWORDs */ | (1 << 16) /* 1 PRDT entry */ | (1 << 5) /* ATAPI */; + commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | (0xA0 /* packet */ << 16); + commandFIS[1] = 8 /* maximum byte count transfer */ << 8; + prdt[3] = 8 - 1; + + uint8_t *scsiCommand = (uint8_t *) commandFIS + 0x40; + EsMemoryZero(scsiCommand, 10); + scsiCommand[0] = 0x25 /* READ CAPACITY (10) */; + + if (!SendSingleCommand(i)) { + KernelLog(LOG_ERROR, "AHCI", "identify failure", "Could not read SCSI read capacity data for port %d.\n", i); + WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) & ~(1 << 0)); // Stop command processing. + ports[i].connected = false; + continue; + } + + uint8_t *capacity = (uint8_t *) identifyData; + + ports[i].sectorCount = (((uint64_t) capacity[3] << 0) + ((uint64_t) capacity[2] << 8) + + ((uint64_t) capacity[1] << 16) + ((uint64_t) capacity[0] << 24)) + 1; + ports[i].sectorBytes = ((uint64_t) capacity[7] << 0) + ((uint64_t) capacity[6] << 8) + + ((uint64_t) capacity[5] << 16) + ((uint64_t) capacity[4] << 24); + } + + if (ports[i].sectorCount <= 128 || (ports[i].sectorBytes & 0x1FF) || !ports[i].sectorBytes || ports[i].sectorBytes > 0x1000) { + KernelLog(LOG_ERROR, "AHCI", "unsupported feature", "Drive on port %d has invalid sector configuration (count: %d, size: %D).\n", + i, ports[i].sectorCount, ports[i].sectorBytes); + ports[i].connected = false; + continue; + } + + for (uintptr_t j = 0; j < 20; j++) { + ports[i].model[j * 2 + 0] = identifyData[27 + j] >> 8; + ports[i].model[j * 2 + 1] = identifyData[27 + j] & 0xFF; + } + + ports[i].model[40] = 0; + + for (uintptr_t j = 39; j > 0; j--) { + if (ports[i].model[j] == ' ') { + ports[i].model[j] = 0; + } else { + break; + } + } + + ports[i].ssd = identifyData[217] == 1; + + for (uintptr_t i = 10; i < 20; i++) identifyData[i] = (identifyData[i] >> 8) | (identifyData[i] << 8); + for (uintptr_t i = 23; i < 27; i++) identifyData[i] = (identifyData[i] >> 8) | (identifyData[i] << 8); + for (uintptr_t i = 27; i < 47; i++) identifyData[i] = (identifyData[i] >> 8) | (identifyData[i] << 8); + + KernelLog(LOG_INFO, "AHCI", "identified drive", "Identified drive on port %d; serial - '%s', firmware - '%s', model - '%s', sector size - %D, sector count - %d.\n", + i, 20, identifyData + 10, 8, identifyData + 23, 40, identifyData + 27, ports[i].sectorBytes, ports[i].sectorCount); + } + + MMFree(MMGetKernelSpace(), identifyData); + MMPhysicalFree(identifyDataPhysical); + + // Start the timeout timer. + + KTimerSet(&timeoutTimer, GENERAL_TIMEOUT, TimeoutTimerHit, this); + + // Register drives. + + for (uintptr_t i = 0; i < MAX_PORTS; i++) { + if (!ports[i].connected) continue; + + AHCIDrive *device = (AHCIDrive *) KDeviceCreate("AHCI drive", this, sizeof(AHCIDrive)); + + if (!device) { + KernelLog(LOG_ERROR, "AHCI", "allocation failure", "Could not create device for port %d.\n", i); + break; + } + + device->controller = this; + device->port = i; + + device->sectorSize = ports[i].sectorBytes; + device->sectorCount = ports[i].sectorCount; + device->maxAccessSectorCount = ports[i].atapi ? (65535 / device->sectorSize) + : ((PRDT_ENTRY_COUNT - 1 /* need one extra if not page aligned */) * K_PAGE_SIZE / device->sectorSize); + device->readOnly = ports[i].atapi; + device->cModel = ports[i].model; + device->driveType = ports[i].atapi ? ES_DRIVE_TYPE_CDROM : ports[i].ssd ? ES_DRIVE_TYPE_SSD : ES_DRIVE_TYPE_HDD; + + device->access = [] (KBlockDeviceAccessRequest request) { + AHCIDrive *drive = (AHCIDrive *) request.device; + + request.dispatchGroup->Start(); + + if (!drive->controller->Access(drive->port, request.offset, request.count, request.operation, + request.buffer, request.flags, request.dispatchGroup)) { + request.dispatchGroup->End(false); + } + }; + + FSRegisterBlockDevice(device); + } +} + +static void DeviceAttach(KDevice *_parent) { + KPCIDevice *parent = (KPCIDevice *) _parent; + + AHCIController *device = (AHCIController *) KDeviceCreate("AHCI controller", parent, sizeof(AHCIController)); + if (!device) return; + device->pci = parent; + + device->dumpState = [] (KDevice *device) { + ((AHCIController *) device)->DumpState(); + }; + + // Enable PCI features. + parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS + | K_PCI_FEATURE_BUSMASTERING_DMA + | K_PCI_FEATURE_MEMORY_SPACE_ACCESS + | K_PCI_FEATURE_BAR_5); + + // Initialise the controller. + device->Initialise(); +} + +KDriver driverAHCI = { + .attach = DeviceAttach, +}; diff --git a/drivers/bga.cpp b/drivers/bga.cpp new file mode 100644 index 0000000..a17579b --- /dev/null +++ b/drivers/bga.cpp @@ -0,0 +1,81 @@ +#include + +struct BGADisplay : KGraphicsTarget { +}; + +BGADisplay *vboxDisplay; + +void BGAUpdateScreen(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY) { + GraphicsUpdateScreen32(source, sourceWidth, sourceHeight, sourceStride, destinationX, destinationY, + vboxDisplay->screenWidth, vboxDisplay->screenHeight, + vboxDisplay->screenWidth * 4, + ((KPCIDevice *) vboxDisplay->parent)->baseAddressesVirtual[0]); +} + +void BGADebugPutBlock(uintptr_t x, uintptr_t y, bool toggle) { + GraphicsDebugPutBlock32(x, y, toggle, + vboxDisplay->screenWidth, vboxDisplay->screenHeight, + vboxDisplay->screenWidth * 4, + ((KPCIDevice *) vboxDisplay->parent)->baseAddressesVirtual[0]); +} + +void BGADebugClearScreen() { + GraphicsDebugClearScreen32(vboxDisplay->screenWidth, vboxDisplay->screenHeight, + vboxDisplay->screenWidth * 4, + ((KPCIDevice *) vboxDisplay->parent)->baseAddressesVirtual[0]); +} + +void BGADeviceAttached(KDevice *_parent) { + KPCIDevice *parent = (KPCIDevice *) _parent; + + BGADisplay *device = (BGADisplay *) KDeviceCreate("BGA", parent, sizeof(BGADisplay)); + if (!device) return; + + parent->EnableFeatures(K_PCI_FEATURE_IO_PORT_ACCESS | K_PCI_FEATURE_MEMORY_SPACE_ACCESS | K_PCI_FEATURE_BAR_0); + + if (KGraphicsIsTargetRegistered()) { + return; + } + + ProcessorOut16(0x01CE, 0 /* version */); + uint16_t version = ProcessorIn16(0x01CF); + KernelLog(LOG_INFO, "BGA", "version", "Detected version %X%X.\n", version >> 8, version); + + if (version < 0xB0C0 || version > 0xB0C5) { + KernelLog(LOG_INFO, "BGA", "unsupported version", "Currently, the only supported versions are B0C0-B0C5.\n"); + return; + } + + // Set the mode. + ProcessorOut16(0x01CE, 4 /* enable */); + ProcessorOut16(0x01CF, 0); + ProcessorOut16(0x01CE, 1 /* x resolution */); + ProcessorOut16(0x01CF, BGA_RESOLUTION_WIDTH); + ProcessorOut16(0x01CE, 2 /* y resolution */); + ProcessorOut16(0x01CF, BGA_RESOLUTION_HEIGHT); + ProcessorOut16(0x01CE, 3 /* bpp */); + ProcessorOut16(0x01CF, 32); + ProcessorOut16(0x01CE, 4 /* enable */); + ProcessorOut16(0x01CF, 0x41 /* linear frame-buffer */); + + // Setup the graphics target. + device->updateScreen = BGAUpdateScreen; + device->debugPutBlock = BGADebugPutBlock; + device->debugClearScreen = BGADebugClearScreen; + ProcessorOut16(0x01CE, 1 /* x resolution */); + device->screenWidth = ProcessorIn16(0x01CF); + ProcessorOut16(0x01CE, 2 /* y resolution */); + device->screenHeight = ProcessorIn16(0x01CF); + + // Register the display. + KernelLog(LOG_INFO, "BGA", "register target", + "Registering graphics target with resolution %d by %d. Linear framebuffer is at virtual address %x.\n", + device->screenWidth, device->screenHeight, parent->baseAddressesVirtual[0]); + vboxDisplay = device; + KRegisterGraphicsTarget(device); +} + +KDriver driverBGA = { + .attach = BGADeviceAttached, +}; diff --git a/drivers/esfs2.cpp b/drivers/esfs2.cpp new file mode 100644 index 0000000..0fef2dd --- /dev/null +++ b/drivers/esfs2.cpp @@ -0,0 +1,2009 @@ +#include + +// Filesystem structures and constant definitions. +#include + +// TODO Calling FSDirectoryEntryFound on all directory entries seen during scanning, even if they're not the target. +// TODO Informing the block cache when a directory is truncated and its extents are freed. +// TODO Renaming directories does not work? +// TODO ESFS_CHECK_XXX are used to report out of memory errors, which shouldn't report KERNEL_PROBLEM_DAMAGED_FILESYSTEM. + +#define ESFS_CHECK(x, y) if (!(x)) { KernelLog(LOG_ERROR, "EsFS", "damaged file system", y "\n"); return false; } +#define ESFS_CHECK_ERROR(x, y) if ((x) != ES_SUCCESS) { KernelLog(LOG_ERROR, "EsFS", "damaged file system", y "\n"); return x; } +#define ESFS_CHECK_TO_ERROR(x, y, z) if (!(x)) { KernelLog(LOG_ERROR, "EsFS", "damaged file system", y "\n"); return z; } +#define ESFS_CHECK_VA(x, y, ...) if (!(x)) { KernelLog(LOG_ERROR, "EsFS", "damaged file system", y "\n", __VA_ARGS__); return false; } +#define ESFS_CHECK_RETURN(x, y) if (!(x)) { KernelLog(LOG_ERROR, "EsFS", "damaged file system", y "\n"); return; } +#define ESFS_CHECK_CORRUPT(x, y) if (!(x)) { KernelLog(LOG_ERROR, "EsFS", "damaged file system", y "\n"); return ES_ERROR_CORRUPT_DATA; } +#define ESFS_CHECK_FATAL(x, y) if (!(x)) { KernelLog(LOG_ERROR, "EsFS", "damaged file system", "Mount - " y "\n"); return false; } +#define ESFS_CHECK_READ_ONLY(x, y) if (!(x)) { KernelLog(LOG_ERROR, "EsFS", "mount read only", "Mount - " y " Mounting as read only.\n"); volume->readOnly = true; } + +struct Volume : KDevice { + Superblock superblock; + KFileSystem *fileSystem; + struct FSNode *root; + bool readOnly; + KWriterLock blockBitmapLock; + GroupDescriptor *groupDescriptorTable; + KMutex nextIdentifierMutex; +}; + +struct FSNode { + Volume *volume; + DirectoryEntry entry; + DirectoryEntryReference reference; + EsUniqueIdentifier identifier; + EsNodeType type; + bool corrupt; +}; + +static bool AccessBlock(Volume *volume, uint64_t index, uint64_t count, void *buffer, uint64_t flags, int driveAccess) { + // TODO Return EsError. + Superblock *superblock = &volume->superblock; + bool result = volume->fileSystem->Access(index * superblock->blockSize, count * superblock->blockSize, driveAccess, buffer, flags, nullptr); + ESFS_CHECK(result, "AccessBlock - Could not access blocks."); + return result; +} + +static bool ValidateIndexVertex(Superblock *superblock, IndexVertex *vertex) { + uint32_t checksum = vertex->checksum; + vertex->checksum = 0; + uint32_t calculated = CalculateCRC32(vertex, superblock->blockSize); + + ESFS_CHECK(checksum == calculated, "ValidateIndexVertex - Invalid vertex checksum."); + ESFS_CHECK(0 == EsMemoryCompare(vertex->signature, ESFS_INDEX_VERTEX_SIGNATURE, 4), "ValidateIndexVertex - Invalid vertex signature."); + ESFS_CHECK(vertex->offset + vertex->maxCount * sizeof(IndexKey) < superblock->blockSize, "ValidateIndexVertex - Keys do not fit in vertex."); + ESFS_CHECK(vertex->count <= vertex->maxCount, "ValidateIndexVertex - Too many keys in vertex."); + + return true; +} + +static EsError FindDirectoryEntryReferenceFromIndex(Volume *volume, uint8_t *buffer /* superblock->blockSize */, + DirectoryEntryReference *entry, const char *name, size_t nameLength, uint64_t rootBlock) { + // EsPrint("FindDirectoryEntryReferenceFromIndex: %s (%d)\n", nameLength, name, rootBlock); + if (!rootBlock) return ES_ERROR_FILE_DOES_NOT_EXIST; // No index - the directory is empty? + + Superblock *superblock = &volume->superblock; + + // Get the root vertex. + uint64_t nameHash = CalculateCRC64(name, nameLength); + IndexVertex *vertex = (IndexVertex *) buffer; + + if (!AccessBlock(volume, rootBlock, 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + return ES_ERROR_DRIVE_CONTROLLER_REPORTED; + } + + int depth = 0; + + while (true) { + ESFS_CHECK(depth++ < ESFS_INDEX_MAX_DEPTH - 1, "FindDirectoryEntryReferenceFromIndex - Reached tree max depth."); + + if (!ValidateIndexVertex(superblock, vertex)) return ES_ERROR_CORRUPT_DATA; + IndexKey *keys = (IndexKey *) ((uint8_t *) vertex + vertex->offset); + + // For every key... + for (int i = 0; i <= vertex->count; i++) { + if (i == vertex->count || keys[i].value > nameHash) { + if (keys[i].child) { + // The directory is in the child. + if (!AccessBlock(volume, keys[i].child, 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) return ES_ERROR_DRIVE_CONTROLLER_REPORTED; + else goto nextVertex; + } else { + // We couldn't find the entry. + return ES_ERROR_FILE_DOES_NOT_EXIST; + } + } else if (keys[i].value == nameHash) { + // We've found the directory. + ESFS_CHECK_CORRUPT(keys[i].data.block < superblock->blockCount + && keys[i].data.offsetIntoBlock + sizeof(DirectoryEntry) <= superblock->blockSize, + "FindDirectoryEntryReferenceFromIndex - Invalid key entry."); + if (entry) *entry = keys[i].data; + return ES_SUCCESS; + } + } + + nextVertex:; + } +} + +static bool ValidateDirectoryEntry(DirectoryEntry *entry) { + uint32_t checksum = entry->checksum; + entry->checksum = 0; + uint32_t calculated = CalculateCRC32(entry, sizeof(DirectoryEntry)); + entry->checksum = calculated; + + ESFS_CHECK_VA(checksum == calculated, "ValidateDirectoryEntry - Invalid checksum (%x, calculated %x).", checksum, calculated); + ESFS_CHECK(0 == EsMemoryCompare(entry->signature, ESFS_DIRECTORY_ENTRY_SIGNATURE, 8), "ValidateDirectoryEntry - Invalid signature."); + ESFS_CHECK(entry->attributeOffset < sizeof(DirectoryEntry) - sizeof(Attribute), "ValidateDirectoryEntry - Invalid attribute offset."); + + Attribute *attribute = (Attribute *) ((uint8_t *) entry + entry->attributeOffset); + + for (uintptr_t i = 0; i < entry->attributeCount; i++) { + ESFS_CHECK(attribute->size && attribute->type, "ValidateDirectoryEntry - Invalid attribute."); + ESFS_CHECK((uintptr_t) attribute <= (uintptr_t) entry + sizeof(DirectoryEntry), "ValidateDirectoryEntry - Too many attributes."); + + if (attribute->type == ESFS_ATTRIBUTE_DATA) { + AttributeData *data = (AttributeData *) attribute; + + if (data->indirection == ESFS_INDIRECTION_DIRECT) { + ESFS_CHECK(data->dataOffset + data->count <= data->size, "ValidateDirectoryEntry - Data too long."); + ESFS_CHECK(data->count == entry->fileSize, "ValidateDirectoryEntry - Expected direct attribute to cover entire file."); + } + } else if (attribute->type == ESFS_ATTRIBUTE_DIRECTORY) { + } else if (attribute->type == ESFS_ATTRIBUTE_FILENAME) { + AttributeFilename *filename = (AttributeFilename *) attribute; + ESFS_CHECK(filename->length + 8 <= filename->size, "ValidateDirectoryEntry - Filename too long."); + } else { + // Unrecognised attribute. + } + + attribute = (Attribute *) ((uint8_t *) attribute + attribute->size); + } + + return true; +} + +static Attribute *FindAttribute(DirectoryEntry *entry, uint16_t type) { + Attribute *attribute = (Attribute *) ((uint8_t *) entry + entry->attributeOffset); + int count = 0; + + while (attribute->type != type) { + if (!attribute->size) { + KernelLog(LOG_ERROR, "EsFS", "damaged file system", "FindAttribute - Attribute size was 0.\n"); + return nullptr; + } + + attribute = (Attribute *) ((uint8_t *) attribute + attribute->size); + + if (count++ == entry->attributeCount) { + return nullptr; + } + } + + return attribute; +} + +static bool ReadWrite(FSNode *file, uint64_t offset, uint64_t count, uint8_t *buffer, bool needBlockBuffer, bool write, + DirectoryEntryReference *reference = nullptr /* Returns the position of a directory just accessed */) { + // TODO Return EsError. + // TODO Support KWorkGroup. + + Volume *volume = file->volume; + Superblock *superblock = &volume->superblock; + DirectoryEntry *entry = &file->entry; + + uint64_t accessBlockFlags = 0; + + if (file->type == ES_NODE_DIRECTORY) { + accessBlockFlags |= BLOCK_ACCESS_CACHED; + } + + uint8_t *blockBuffer = !needBlockBuffer ? nullptr : (uint8_t *) EsHeapAllocate(superblock->blockSize, false, K_FIXED); + EsDefer(EsHeapFree(blockBuffer, 0, K_FIXED)); + ESFS_CHECK(!needBlockBuffer || blockBuffer, "Read - Could not allocate block buffer."); + + // EsPrint("ReadWrite - %d, %d, %x, %d\n", offset, count, buffer, write); + + if (!count) { + return true; + } + + AttributeData *data = (AttributeData *) FindAttribute(entry, ESFS_ATTRIBUTE_DATA); + ESFS_CHECK(data, "Read - Expected data attribute."); + + if (data->indirection == ESFS_INDIRECTION_DIRECT) { + if (write) { + EsMemoryCopy((uint8_t *) data + data->dataOffset + offset, buffer, count); + } else { + EsMemoryCopy(buffer, (uint8_t *) data + data->dataOffset + offset, count); + } + } else if (data->indirection == ESFS_INDIRECTION_L1) { + uint64_t offsetBlock = offset / superblock->blockSize; + uint64_t offsetIntoCurrentBlock = offset % superblock->blockSize; + uint8_t *extentList = (uint8_t *) data + data->dataOffset; + uint64_t previousExtentStart = 0, positionInExtentList = 0, blockInFile = 0, extentIndex = 0; + + while (count) { + // Find the extent containing offsetBlock. + + uint64_t extentStart = 0, extentCount = 0; + + while (!extentStart) { + if (extentIndex == data->count) { + ESFS_CHECK(false, "Read - Invalid extent."); + } + + uint64_t count = 0; + + if (!DecodeExtent(&previousExtentStart, &count, extentList, &positionInExtentList, data->size - data->dataOffset) + || !count || !previousExtentStart) { + ESFS_CHECK(false, "Read - Invalid extent."); + } + + // EsPrint("\tExtent %d -> %d covers blocks %d -> %d\n", previousExtentStart, previousExtentStart + count, blockInFile, blockInFile + count); + + if (blockInFile + count > offsetBlock) { + uint64_t offsetIntoExtent = offsetBlock - blockInFile; + extentStart = previousExtentStart + offsetIntoExtent; + extentCount = count - offsetIntoExtent; + // EsPrint("\t\tUsing section %d -> %d for reading from block %d\n", extentStart, extentStart + extentCount, offsetBlock); + } + + blockInFile += count; + extentIndex++; + } + + // Read the data. + + repeatExtent:; + + if (offsetIntoCurrentBlock || count < superblock->blockSize) { + if (!needBlockBuffer) { + KernelPanic("EsFS::Read - Need a block buffer, but needBlockBuffer was false.\n"); + } + + uint64_t copyCount = superblock->blockSize - offsetIntoCurrentBlock; + if (copyCount > count) copyCount = count; + + // EsPrint("\tCopying %d bytes through block buffer.\n", copyCount); + + if (!AccessBlock(volume, extentStart, 1, blockBuffer, accessBlockFlags, K_ACCESS_READ)) { + return false; + } + + if (reference) { + reference->block = extentStart; + reference->offsetIntoBlock = offsetIntoCurrentBlock; + } + + if (write) { + EsMemoryCopy(blockBuffer + offsetIntoCurrentBlock, buffer, copyCount); + + if (!AccessBlock(volume, extentStart, 1, blockBuffer, accessBlockFlags, K_ACCESS_WRITE)) { + return false; + } + } else { + EsMemoryCopy(buffer, blockBuffer + offsetIntoCurrentBlock, copyCount); + } + + buffer += copyCount, count -= copyCount; + offsetIntoCurrentBlock = 0, offsetBlock++; + extentStart++, extentCount--; + + // EsPrint("\t\tUsing section %d -> %d for reading from block %d\n", extentStart, extentCount, offsetBlock); + } + + { + uint64_t bytesToRead = extentCount * superblock->blockSize; + if (bytesToRead > count) bytesToRead = count; + bytesToRead -= bytesToRead % superblock->blockSize; + uint64_t blocksRead = bytesToRead / superblock->blockSize; + + // EsPrint("\tReading %d blocks from %d.\n", blocksRead, extentStart); + + if (reference && bytesToRead) { + reference->block = extentStart; + reference->offsetIntoBlock = 0; + } + + if (!AccessBlock(volume, extentStart, blocksRead, buffer, accessBlockFlags, + write ? K_ACCESS_WRITE : K_ACCESS_READ)) { + return false; + } + + buffer += bytesToRead, count -= bytesToRead; + + offsetBlock += blocksRead; + extentStart += blocksRead, extentCount -= blocksRead; + + if (extentCount && count) { + goto repeatExtent; + } + } + } + } else { + ESFS_CHECK(data, "Read - Unrecognised indirection mode."); + return false; + } + + return true; +} + +static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) { + FSNode *file = (FSNode *) node->driverNode; + if (file->corrupt) return ES_ERROR_CORRUPT_DATA; + return ReadWrite(file, offset, count, (uint8_t *) _buffer, true, false) ? count : ES_ERROR_UNKNOWN; +} + +static size_t Write(KNode *node, const void *_buffer, EsFileOffset offset, EsFileOffset count) { + FSNode *file = (FSNode *) node->driverNode; + if (file->corrupt) return ES_ERROR_CORRUPT_DATA; + return ReadWrite(file, offset, count, (uint8_t *) _buffer, true, true) ? count : ES_ERROR_UNKNOWN; +} + +static void Sync(KNode *_directory, KNode *node) { + (void) _directory; + FSNode *file = (FSNode *) node->driverNode; + if (!file) KernelPanic("EsFS::Sync - Node %x has null driver node.\n", node); + if (file->corrupt) return; + Volume *volume = file->volume; + Superblock *superblock = &volume->superblock; + + // EsPrint("SYNC! %d,%d\n", file->reference.block, file->reference.offsetIntoBlock); + + if (file->type == ES_NODE_DIRECTORY) { + // Get the most recent totalSize for the directory. + AttributeDirectory *directoryAttribute = (AttributeDirectory *) FindAttribute(&file->entry, ESFS_ATTRIBUTE_DIRECTORY); + directoryAttribute->totalSize = FSNodeGetTotalSize(node); + } + + { + file->entry.checksum = 0; + file->entry.checksum = CalculateCRC32(&file->entry, sizeof(DirectoryEntry)); + } + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(superblock->blockSize, false, K_FIXED); + EsDefer(EsHeapFree(blockBuffer, 0, K_FIXED)); + ESFS_CHECK_RETURN(blockBuffer, "Sync - Could not allocate block buffer."); + + if (!AccessBlock(volume, file->reference.block, 1, blockBuffer, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + KernelLog(LOG_ERROR, "EsFS", "drive access failure", "Sync - Could not read reference block.\n"); + return; + } + + if (!ValidateDirectoryEntry((DirectoryEntry *) (blockBuffer + file->reference.offsetIntoBlock))) { + return; + } + + EsMemoryCopy(blockBuffer + file->reference.offsetIntoBlock, &file->entry, sizeof(DirectoryEntry)); + + if (!AccessBlock(volume, file->reference.block, 1, blockBuffer, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE)) { + KernelLog(LOG_ERROR, "EsFS", "drive access failure", "Sync - Could not write reference block.\n"); + return; + } +} + +static EsError Enumerate(KNode *node) { + // TODO Support KWorkGroup. + + FSNode *file = (FSNode *) node->driverNode; + if (file->corrupt) return ES_ERROR_CORRUPT_DATA; + Volume *volume = file->volume; + Superblock *superblock = &volume->superblock; + DirectoryEntry *entry = &file->entry; + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(superblock->blockSize, false, K_FIXED); + if (!blockBuffer) return ES_ERROR_INSUFFICIENT_RESOURCES; + EsDefer(EsHeapFree(blockBuffer, 0, K_FIXED)); + + DirectoryEntryReference reference = {}; + AttributeDirectory *directory = (AttributeDirectory *) FindAttribute(entry, ESFS_ATTRIBUTE_DIRECTORY); + uint64_t blocksInDirectory = (directory->childNodes + superblock->directoryEntriesPerBlock - 1) / superblock->directoryEntriesPerBlock; + + for (uint64_t i = 0; i < blocksInDirectory; i++) { + if (!ReadWrite(file, i * superblock->blockSize, superblock->blockSize, blockBuffer, false, false, &reference)) { + return ES_ERROR_UNKNOWN; + } + + uint64_t entriesInThisBlock = superblock->directoryEntriesPerBlock; + + if (i == blocksInDirectory - 1 && directory->childNodes % superblock->directoryEntriesPerBlock) { + entriesInThisBlock = directory->childNodes % superblock->directoryEntriesPerBlock; + } + + for (uint64_t j = 0; j < entriesInThisBlock; j++, reference.offsetIntoBlock += sizeof(DirectoryEntry)) { + DirectoryEntry *entry = (DirectoryEntry *) blockBuffer + j; + + if (!ValidateDirectoryEntry(entry)) { + // Try the entries in the next block. + break; + } + + AttributeFilename *filename = (AttributeFilename *) FindAttribute(entry, ESFS_ATTRIBUTE_FILENAME); + if (!filename) continue; + + KNodeMetadata metadata = {}; + + metadata.type = entry->nodeType == ESFS_NODE_TYPE_DIRECTORY ? ES_NODE_DIRECTORY : ES_NODE_FILE; + + if (metadata.type == ES_NODE_DIRECTORY) { + AttributeDirectory *directory = (AttributeDirectory *) FindAttribute(entry, ESFS_ATTRIBUTE_DIRECTORY); + + if (directory) { + metadata.directoryChildren = directory->childNodes; + metadata.totalSize = directory->totalSize; + } + } else if (metadata.type == ES_NODE_FILE) { + metadata.totalSize = entry->fileSize; + } + + EsError error = FSDirectoryEntryFound(node, &metadata, &reference, + (const char *) filename->filename, filename->length, false); + + if (error != ES_SUCCESS) { + return error; + } + } + } + + return ES_SUCCESS; +} + +static uint64_t FindLargestExtent(uint8_t *bitmap, Superblock *superblock) { + uint64_t largestExtentCount = 0, i = 0; + + while (i < superblock->blocksPerGroup) { + if (bitmap[i / 8] & (1 << (i % 8))) { + i++; + } else { + uint64_t count = 0; + + while (i < superblock->blocksPerGroup) { + if (bitmap[i / 8] & (1 << (i % 8))) break; + else count++, i++; + } + + if (largestExtentCount < count) { + largestExtentCount = count; + } + } + } + + return largestExtentCount; +} + +static bool ValidateGroupDescriptor(GroupDescriptor *descriptor) { + uint32_t checksum = descriptor->checksum; + descriptor->checksum = 0; + uint32_t calculated = CalculateCRC32(descriptor, sizeof(GroupDescriptor)); + ESFS_CHECK(checksum == calculated, "ValidateGroupDescriptor - Invalid checksum."); + ESFS_CHECK(0 == EsMemoryCompare(descriptor->signature, ESFS_GROUP_DESCRIPTOR_SIGNATURE, 4), "ValidateGroupDescriptor - Invalid signature."); + return true; +} + +static bool ValidateBlockBitmap(GroupDescriptor *descriptor, uint8_t *bitmap, Superblock *superblock) { + uint32_t calculated = CalculateCRC32(bitmap, superblock->blocksPerGroupBlockBitmap * superblock->blockSize); + ESFS_CHECK(calculated == descriptor->bitmapChecksum, "ValidateBlockBitmap - Invalid checksum."); + + uint32_t blocksUsed = 0; + + for (uint64_t i = 0; i < superblock->blocksPerGroup; i++) { + if (bitmap[i / 8] & (1 << (i % 8))) { + blocksUsed++; + } + } + + ESFS_CHECK(blocksUsed == descriptor->blocksUsed, "ValidateBlockBitmap - Used block count mismatch."); + + return true; +} + +static bool AllocateExtent(Volume *volume, uint64_t nearby, uint64_t increaseBlocks, uint64_t *extentStart, uint64_t *extentCount, bool zero) { + Superblock *superblock = &volume->superblock; + KWriterLockAssertExclusive(&volume->blockBitmapLock); + + (void) nearby; + // TODO Smarter extent allocation. + + uint8_t *zeroBuffer = nullptr; + EsDefer(EsHeapFree(zeroBuffer, 0, K_FIXED)); + size_t zeroBufferSize = superblock->blockSize * (increaseBlocks > 16 ? 16 : increaseBlocks); + + if (zero) { + zeroBuffer = (uint8_t *) EsHeapAllocate(zeroBufferSize, true, K_FIXED); + ESFS_CHECK(zeroBuffer, "AllocateExtent - Could not allocate buffer for zeroing extent."); + } + + // Find a group to allocate the next extent from. + + GroupDescriptor *target = nullptr; + + { + for (uint64_t i = 0; !target && i < superblock->groupCount; i++) { + GroupDescriptor *group = volume->groupDescriptorTable + i; + if (!group->blocksUsed) group->largestExtent = superblock->blocksPerGroup - superblock->blocksPerGroupBlockBitmap; + if (group->largestExtent >= increaseBlocks) target = group; + } + + for (uint64_t i = 0; !target && i < superblock->groupCount; i++) { + GroupDescriptor *group = volume->groupDescriptorTable + i; + if (superblock->blocksPerGroup - group->blocksUsed >= increaseBlocks) target = group; + } + + for (uint64_t i = 0; !target && i < superblock->groupCount; i++) { + GroupDescriptor *group = volume->groupDescriptorTable + i; + if (superblock->blocksPerGroup != group->blocksUsed) target = group; + } + } + + if (!target) { + return false; + } + + ESFS_CHECK(ValidateGroupDescriptor(target), "AllocateExtent - Invalid group descriptor."); + + // Load the bitmap, find the largest extent, and mark it as in use. + + uint8_t *bitmap = (uint8_t *) EsHeapAllocate(superblock->blocksPerGroupBlockBitmap * superblock->blockSize, false, K_FIXED); + EsDefer(EsHeapFree(bitmap, 0, K_FIXED)); + ESFS_CHECK(bitmap, "AllocateExtent - Could not allocate buffer for block bitmap."); + + { + if (target->blockBitmap) { + if (!AccessBlock(volume, target->blockBitmap, superblock->blocksPerGroupBlockBitmap, bitmap, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + return false; + } + + ESFS_CHECK(ValidateBlockBitmap(target, bitmap, superblock), "AllocateExtent - Invalid block bitmap."); + } else { + EsMemoryZero(bitmap, superblock->blocksPerGroupBlockBitmap * superblock->blockSize); + for (uint64_t i = 0; i < superblock->blocksPerGroupBlockBitmap; i++) bitmap[i / 8] |= 1 << (i % 8); + target->blockBitmap = superblock->blocksPerGroup * (target - volume->groupDescriptorTable); + target->blocksUsed = superblock->blocksPerGroupBlockBitmap; + } + + uint64_t largestExtentStart = 0, largestExtentCount = 0, i = 0; + + while (i < superblock->blocksPerGroup) { + if (bitmap[i / 8] & (1 << (i % 8))) { + i++; + } else { + uint64_t start = i, count = 0; + + while (i < superblock->blocksPerGroup) { + if (bitmap[i / 8] & (1 << (i % 8))) break; + else count++, i++; + } + + if (largestExtentCount < count) { + largestExtentStart = start; + largestExtentCount = count; + } + } + } + + *extentStart = largestExtentStart; + *extentCount = largestExtentCount; + + if (*extentCount > increaseBlocks) { + *extentCount = increaseBlocks; + } + + for (uint64_t i = *extentStart; i < *extentStart + *extentCount; i++) { + bitmap[i / 8] |= 1 << (i % 8); + } + + if (!AccessBlock(volume, target->blockBitmap, superblock->blocksPerGroupBlockBitmap, bitmap, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE)) { + return false; + } + + target->largestExtent = FindLargestExtent(bitmap, superblock); + target->blocksUsed += *extentCount; + target->bitmapChecksum = CalculateCRC32(bitmap, superblock->blocksPerGroupBlockBitmap * superblock->blockSize); + target->checksum = 0; + target->checksum = CalculateCRC32(target, sizeof(GroupDescriptor)); + } + + *extentStart += (target - volume->groupDescriptorTable) * superblock->blocksPerGroup; + superblock->blocksUsed += *extentCount; + volume->fileSystem->spaceUsed += *extentCount * superblock->blockSize; + + if (zero) { + // TODO This is really slow - introduce K_ACCESS_ZERO? + // TODO Support KWorkGroup. + + for (uint64_t i = 0; i < *extentCount * superblock->blockSize; i += zeroBufferSize) { +#if 0 + if ((i / zeroBufferSize) % 100 == 0) { + EsPrint("AllocateExtent zeroing %d/%d\n", i / zeroBufferSize, extentCount * superblock->blockSize / zeroBufferSize); + } +#endif + + uint64_t count = zeroBufferSize; + + if (i + count >= *extentCount * superblock->blockSize) { + count = *extentCount * superblock->blockSize - i; + } + + if (!AccessBlock(volume, *extentStart + i / superblock->blockSize, count / superblock->blockSize, zeroBuffer, ES_FLAGS_DEFAULT, K_ACCESS_WRITE)) { + return false; + } + } + } + + return true; +} + +static bool FreeExtent(Volume *volume, uint64_t extentStart, uint64_t extentCount) { + // TODO Return EsError. + Superblock *superblock = &volume->superblock; + KWriterLockAssertExclusive(&volume->blockBitmapLock); + + uint64_t blockGroup = extentStart / superblock->blocksPerGroup; + + // Validate the extent. + + ESFS_CHECK((extentStart + extentCount - 1) / superblock->blocksPerGroup == blockGroup, "FreeExtent - Extent spans multiple block groups."); + ESFS_CHECK(extentCount < superblock->blocksUsed, "FreeExtent - Extent is larged than the number of used blocks."); + ESFS_CHECK(extentStart + extentCount < superblock->blockCount, "FreeExtent - Extent goes past end of the volume."); + + // Load the block bitmap. + + GroupDescriptor *target = volume->groupDescriptorTable + blockGroup; + ESFS_CHECK(ValidateGroupDescriptor(target), "FreeExtent - Invalid group descriptor."); + uint8_t *bitmap = (uint8_t *) EsHeapAllocate(superblock->blocksPerGroupBlockBitmap * superblock->blockSize, false, K_FIXED); + EsDefer(EsHeapFree(bitmap, 0, K_FIXED)); + ESFS_CHECK(bitmap, "FreeExtent - Could not allocate buffer for block bitmap."); + ESFS_CHECK(target->blockBitmap, "FreeExtent - Group descriptor does not have block bitmap."); + ESFS_CHECK(target->blocksUsed >= extentCount, "FreeExtent - Group descriptor indicates fewer blocks are used than are given in this extent."); + ESFS_CHECK(AccessBlock(volume, target->blockBitmap, superblock->blocksPerGroupBlockBitmap, bitmap, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "FreeExtent - Could not read block bitmap."); + ESFS_CHECK(ValidateBlockBitmap(target, bitmap, superblock), "FreeExtent - Invalid block bitmap."); + + // Clear the bits representing the freed blocks. + + for (uint64_t i = 0; i < extentCount; i++) { + uint64_t blockInGroup = (extentStart % superblock->blocksPerGroup) + i; + uint8_t bit = bitmap[blockInGroup / 8] & (1 << (blockInGroup % 8)); + ESFS_CHECK(bit, "FreeExtent - Attempting to free a block that has not been allocated."); + bitmap[blockInGroup / 8] &= ~(1 << (blockInGroup % 8)); + } + + // Write out the modified bitmap and update the group descriptor. + + if (!AccessBlock(volume, target->blockBitmap, superblock->blocksPerGroupBlockBitmap, bitmap, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE)) { + return false; + } + + target->largestExtent = FindLargestExtent(bitmap, superblock); + target->bitmapChecksum = CalculateCRC32(bitmap, superblock->blocksPerGroupBlockBitmap * superblock->blockSize); + target->blocksUsed -= extentCount; + target->checksum = 0; + target->checksum = CalculateCRC32(target, sizeof(GroupDescriptor)); + superblock->blocksUsed -= extentCount; + volume->fileSystem->spaceUsed -= extentCount * superblock->blockSize; + + return true; +} + +static uint64_t ResizeInternal(FSNode *file, uint64_t newSize, EsError *error, uint64_t newDataAttributeSize = 0) { + if (file->corrupt) return *error = ES_ERROR_CORRUPT_DATA, 0; + + Volume *volume = file->volume; + Superblock *superblock = &volume->superblock; + DirectoryEntry *entry = &file->entry; + + uint64_t oldSize = entry->fileSize; + + if (newDataAttributeSize && newSize != oldSize) { + KernelPanic("EsFS::ResizeInternal - Attempting to change data attribute and node size at the same time.\n"); + } + + if (!newDataAttributeSize && newSize == oldSize) { + // The file size hasn't changed. + return newSize; + } + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(superblock->blockSize, true, K_FIXED); + EsDefer(EsHeapFree(blockBuffer, 0, K_FIXED)); + + *error = ES_ERROR_INSUFFICIENT_RESOURCES; + ESFS_CHECK(blockBuffer, "Resize - Could not allocate block buffer."); + + AttributeData *data = (AttributeData *) FindAttribute(entry, ESFS_ATTRIBUTE_DATA); + uint8_t *dataBuffer = (uint8_t *) data + data->dataOffset; + size_t dataBufferSize = data->size - data->dataOffset; + size_t newDataBufferSize = (newDataAttributeSize ? newDataAttributeSize : data->size) - data->dataOffset; + + if (newDataBufferSize >= newSize && entry->nodeType != ESFS_NODE_TYPE_DIRECTORY) { + if (data->indirection == ESFS_INDIRECTION_DIRECT) { + } else if (data->indirection == ESFS_INDIRECTION_L1) { + // Load the data from the indirect storage. + + if (entry->fileSize) { + if (!ReadWrite(file, 0, superblock->blockSize, blockBuffer, false, false)) { + return *error = ES_ERROR_DRIVE_ERROR_FILE_DAMAGED, entry->fileSize; + } + } + + // Free the extents. + + uint64_t previousExtentStart = 0, extentCount = 0, position = 0; + + for (uintptr_t i = 0; i < data->count; i++) { + if (!DecodeExtent(&previousExtentStart, &extentCount, dataBuffer, &position, dataBufferSize)) { + file->corrupt = true; + *error = ES_ERROR_CORRUPT_DATA; + return (entry->fileSize = 0); + } + + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + + if (!FreeExtent(volume, previousExtentStart, extentCount)) { + file->corrupt = true; + entry->fileSize = 0; + *error = ES_ERROR_DRIVE_ERROR_FILE_DAMAGED; + return (entry->fileSize = 0); + } + } + + // Store the existing data in the entry. + + EsMemoryCopy(dataBuffer, blockBuffer, newSize); + } else { + *error = ES_ERROR_UNSUPPORTED_FEATURE; + return entry->fileSize; // Unrecognised indirection. + } + + data->indirection = ESFS_INDIRECTION_DIRECT; + data->count = newSize; + + // Zero out everything past the new size. + EsMemoryZero(dataBuffer + newSize, newDataBufferSize - newSize); + } else { + uint64_t oldBlocks = (entry->fileSize + superblock->blockSize - 1) / superblock->blockSize; + uint64_t newBlocks = (newSize + superblock->blockSize - 1) / superblock->blockSize; + bool copyData = false; + + if (data->indirection == ESFS_INDIRECTION_DIRECT) { + if (!ReadWrite(file, 0, entry->fileSize, blockBuffer, false, false)) { + *error = ES_ERROR_DRIVE_ERROR_FILE_DAMAGED; + return entry->fileSize; + } + + copyData = entry->fileSize > 0; + data->count = 0; + oldBlocks = 0; + entry->fileSize = 0; + } else if (data->indirection != ESFS_INDIRECTION_L1) { + *error = ES_ERROR_UNSUPPORTED_FEATURE; + return entry->fileSize; // Unrecognised indirection. + } + + if (oldBlocks < newBlocks) { + uint64_t previousExtentStart = 0, oldPreviousExtentStart = 0, extentCount = 0, position = 0, previousPosition = 0; + + for (uintptr_t i = 0; i < data->count; i++) { + previousPosition = position; + oldPreviousExtentStart = previousExtentStart; + + if (!DecodeExtent(&previousExtentStart, &extentCount, dataBuffer, &position, dataBufferSize)) { + *error = ES_ERROR_CORRUPT_DATA; + return entry->fileSize; + } + } + + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + + uint64_t remaining = newBlocks - oldBlocks; + + if (superblock->blocksUsed + remaining >= superblock->blockCount) { + // There isn't enough space to grow the file. + *error = ES_ERROR_DRIVE_FULL; + return entry->fileSize; + } + + while (remaining) { + uint64_t allocatedStart, allocatedCount; + + bool success = AllocateExtent(volume, + previousExtentStart + extentCount /* Attempt to allocate near the end of the last extent */, + remaining /* Attempt to get an extent covering all the remaining blocks */, + &allocatedStart, &allocatedCount, true /* Zero the blocks */); + + if (!success) { + *error = ES_ERROR_DRIVE_ERROR_FILE_DAMAGED; + return entry->fileSize; + } + + if (previousExtentStart + extentCount == allocatedStart) { + // We need to grow the previous extent. + + allocatedStart = previousExtentStart; + previousExtentStart = oldPreviousExtentStart; + remaining += extentCount; + allocatedCount += extentCount; + position = previousPosition; + data->count--; + } + + oldPreviousExtentStart = previousExtentStart; + uint8_t encode[32]; + uint64_t length = EncodeExtent(allocatedStart, previousExtentStart, allocatedCount, encode); + + if (length + position > newDataBufferSize) { + // The data buffer is full. + *error = ES_ERROR_FILE_TOO_FRAGMENTED; + return entry->fileSize; + } else { + EsMemoryCopy(dataBuffer + position, encode, length); + previousPosition = position; + position += length; + data->count++; + remaining -= allocatedCount; + entry->fileSize += allocatedCount * superblock->blockSize; + previousExtentStart = allocatedStart; + } + } + } else if (oldBlocks > newBlocks) { + uint64_t previousExtentStart = 0, extentCount = 0, + position = 0, blockInFile = 0; + + file->corrupt = true; + entry->fileSize = 0; + *error = ES_ERROR_DRIVE_ERROR_FILE_DAMAGED; + + // Free the removed extents. + + for (uintptr_t i = 0; i < data->count; i++) { + if (!DecodeExtent(&previousExtentStart, &extentCount, dataBuffer, &position, dataBufferSize)) { + return 0; + } + + if (blockInFile + extentCount > newBlocks) { + uint64_t extentStart = previousExtentStart, extentCount2 = extentCount; + + if (blockInFile < newBlocks) { + extentStart += newBlocks - blockInFile; + extentCount2 -= newBlocks - blockInFile; + } + + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + + if (!FreeExtent(volume, extentStart, extentCount2)) { + return 0; + } + } + + blockInFile += extentCount; + } + + // Modify the last extent. + + previousExtentStart = 0, position = 0, blockInFile = 0; + + for (uintptr_t i = 0; i < data->count; i++) { + uint64_t previousPosition = position, lastExtentStart = previousExtentStart; + + if (!DecodeExtent(&previousExtentStart, &extentCount, dataBuffer, &position, dataBufferSize)) { + return 0; + } + + if (blockInFile + extentCount > newBlocks) { + uint64_t extentStart = previousExtentStart; + + if (blockInFile < newBlocks) { + uint8_t encode[32]; + uint64_t length = EncodeExtent(extentStart, lastExtentStart, newBlocks - blockInFile, encode); + EsMemoryCopy(dataBuffer + previousPosition, encode, length); + data->count = i + 1; + break; + } else { + data->count = i; + break; + } + } + + blockInFile += extentCount; + } + } else { + // Do nothing. + } + + data->indirection = ESFS_INDIRECTION_L1; + + if (copyData) { + if (!ReadWrite(file, 0, superblock->blockSize, blockBuffer, false, true)) { + // Rollback changes. + data->indirection = ESFS_INDIRECTION_DIRECT; + data->count = entry->fileSize = oldSize; + EsMemoryCopy(dataBuffer, blockBuffer, data->count); + *error = ES_ERROR_DRIVE_ERROR_FILE_DAMAGED; + return (entry->fileSize = oldSize); + } + } + } + + file->corrupt = false; + *error = ES_SUCCESS; + if (newDataAttributeSize) data->size = newDataAttributeSize; + return (entry->fileSize = newSize); +} + +static uint64_t Resize(KNode *node, uint64_t newSize, EsError *error) { + // EsPrint("Resize %s to %d\n", node->name.bytes, node->name.buffer, newSize); + return ResizeInternal((FSNode *) node->driverNode, newSize, error); +} + +static IndexKey *InsertKeyIntoVertex(uint64_t newKey, IndexVertex *vertex) { + // Find where in this vertex we should insert the key. + + int position; + + for (position = 0; position < vertex->count; position++) { + if (newKey < ESFS_VERTEX_KEY(vertex, position)->value) { + break; + } + } + + // Insert the key. + + IndexKey *insertionPosition = ESFS_VERTEX_KEY(vertex, position); + EsMemoryMove(insertionPosition, ESFS_VERTEX_KEY(vertex, vertex->count + 1), sizeof(IndexKey), true); + vertex->count++; + insertionPosition->value = newKey; + + return insertionPosition; +} + +static bool IndexModifyKey(Volume *volume, uint64_t newKey, DirectoryEntryReference reference, uint64_t rootBlock, uint8_t *buffer /* superblock->blockSize */) { + // TODO Return EsError. + if (!rootBlock) return false; // No index - the directory is empty? + + Superblock *superblock = &volume->superblock; + + // Get the root vertex. + IndexVertex *vertex = (IndexVertex *) buffer; + uint64_t block; + + if (!AccessBlock(volume, (block = rootBlock), 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + return false; + } + + int depth = 0; + + while (true) { + ESFS_CHECK(depth++ < ESFS_INDEX_MAX_DEPTH - 1, "IndexModifyKey - Reached tree max depth."); + + if (!ValidateIndexVertex(superblock, vertex)) return false; + IndexKey *keys = (IndexKey *) ((uint8_t *) vertex + vertex->offset); + + // For every key... + for (int i = 0; i <= vertex->count; i++) { + if (i == vertex->count || keys[i].value > newKey) { + if (keys[i].child) { + // The directory is in the child. + if (!AccessBlock(volume, (block = keys[i].child), 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) return false; + else goto nextVertex; + } else { + // We couldn't find the entry. + return false; + } + } else if (keys[i].value == newKey) { + // We've found the key. + ESFS_CHECK_CORRUPT(keys[i].data.block < superblock->blockCount + && keys[i].data.offsetIntoBlock + sizeof(DirectoryEntry) <= superblock->blockSize, + "IndexModifyKey - Invalid key entry."); + keys[i].data = reference; + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + return AccessBlock(volume, block, 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE); + } + } + + nextVertex:; + } +} + +static bool IndexAddKey(Volume *volume, uint64_t newKey, DirectoryEntryReference reference, uint64_t *rootBlock) { + // TODO Return EsError. + // TODO Support KWorkGroup. + + // EsPrint("adding key %x\n", newKey); + + Superblock *superblock = &volume->superblock; + uint8_t *blockBuffers = (uint8_t *) EsHeapAllocate(superblock->blockSize * 3, true, K_FIXED); + EsDefer(EsHeapFree(blockBuffers, 0, K_FIXED)); + ESFS_CHECK(blockBuffers, "IndexAddKey - Could not allocate block buffers."); + + uint64_t _unused; + + // Find the leaf to insert the key into. + + uint8_t *buffer = blockBuffers + 0 * superblock->blockSize; + IndexVertex *vertex = (IndexVertex *) buffer; + uint64_t depth = 0, blocks[ESFS_INDEX_MAX_DEPTH] = { *rootBlock }; + bool skipFirst = false; + + if (blocks[0] == 0) { + // Directory is empty - create the root vertex. + + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + ESFS_CHECK(AllocateExtent(volume, 0 /* TODO */, 1, &blocks[0], &_unused, false), "IndexAddKey - Could not allocate space."); + KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + *rootBlock = blocks[0]; + vertex->maxCount = (superblock->blockSize - ESFS_INDEX_KEY_OFFSET) / sizeof(IndexKey) - 1 /* +1 key */; + vertex->offset = ESFS_INDEX_KEY_OFFSET; + EsMemoryCopy(vertex->signature, ESFS_INDEX_VERTEX_SIGNATURE, 4); + skipFirst = true; + } + + { + next:; + + if (!skipFirst) { + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexAddKey - Could not read index."); + + if (!ValidateIndexVertex(superblock, vertex)) { + return false; + } + } + + for (int i = 0; i < vertex->count; i++) { + if (ESFS_VERTEX_KEY(vertex, i)->value == newKey) { + // The key is already in the tree. + // TODO Hash collisions. + ESFS_CHECK(false, "IndexAddKey - Possible hash collision?"); + } + } + + for (int i = 0; i <= vertex->count; i++) { + IndexKey *key = ESFS_VERTEX_KEY(vertex, i); + + if ((i == vertex->count || newKey < key->value) && key->child) { + ESFS_CHECK(depth < ESFS_INDEX_MAX_DEPTH - 1, "IndexAddKey - Reached tree max depth."); + + blocks[++depth] = key->child; + goto next; + } + } + } + + ESFS_CHECK(vertex->count < vertex->maxCount, "IndexAddKey - Vertex is full; unbalanced tree."); + + // Insert the key into the vertex. + + InsertKeyIntoVertex(newKey, vertex)->data = reference; + + // While the vertex is full... + + if (vertex->count > vertex->maxCount) { + KernelPanic("IndexAddKey - Corrupt vertex."); + } + + while (vertex->count == vertex->maxCount) { + // printf("\tsplit!\n"); + + uint8_t *_buffer0 = blockBuffers + 1 * superblock->blockSize; + uint8_t *_buffer1 = blockBuffers + 2 * superblock->blockSize; + + // Create a new sibling. + + uint64_t siblingBlock = 0; + + { + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + ESFS_CHECK(AllocateExtent(volume, blocks[depth], 1, &siblingBlock, &_unused, false), "IndexAddKey - Could not allocate space."); + } + + IndexVertex *sibling = (IndexVertex *) _buffer0; + sibling->maxCount = (superblock->blockSize - ESFS_INDEX_KEY_OFFSET) / sizeof(IndexKey) - 1 /* +1 key */; + sibling->offset = ESFS_INDEX_KEY_OFFSET; + EsMemoryCopy(sibling->signature, ESFS_INDEX_VERTEX_SIGNATURE, 4); + + // Load the parent vertex. + + bool newRoot = !depth; + IndexVertex *parent = (IndexVertex *) _buffer1; + + if (newRoot) { + // Create a new root block. + + blocks[1] = blocks[0]; + depth++; + + { + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + ESFS_CHECK(AllocateExtent(volume, blocks[1], 1, &blocks[0], &_unused, false), "IndexAddKey - Could not allocate space."); + } + + parent->maxCount = (superblock->blockSize - ESFS_INDEX_KEY_OFFSET) / sizeof(IndexKey) - 1 /* +1 key */; + parent->offset = ESFS_INDEX_KEY_OFFSET; + EsMemoryCopy(parent->signature, ESFS_INDEX_VERTEX_SIGNATURE, 4); + + // The superblock points to the new root, and the +1 key of the new root points to the old root. + // It has no other keys yet. + + parent->keys[0].child = blocks[1]; + *rootBlock = blocks[0]; + } else { + ESFS_CHECK(AccessBlock(volume, blocks[depth - 1], 1, parent, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexAddKey - Could not read index."); + } + + IndexKey *parentKeys = (IndexKey *) ((uint8_t *) parent + parent->offset); + IndexKey *vertexKeys = (IndexKey *) ((uint8_t *) vertex + vertex->offset); + IndexKey *siblingKeys = (IndexKey *) ((uint8_t *) sibling + sibling->offset); + + // Change the link to this vertex to point to the sibling. + + int found = 0; + + for (uint64_t i = 0; i <= parent->count; i++) { + if (parentKeys[i].child == blocks[depth]) { + parentKeys[i].child = siblingBlock; + found++; + } + } + + ESFS_CHECK(found == 1, "IndexAddKey - Could not find current vertex in its parent."); + + // Move the median key to the parent. + // If this makes the parent full we'll fix it next iteration. + + uint64_t median = (vertex->maxCount - 1) / 2; + uint64_t newKey = vertexKeys[median].value; + + for (uint64_t i = 0; i <= parent->count; i++) { + if (i == parent->count || newKey < parentKeys[i].value) { + parent->count++; + EsMemoryMove(parentKeys + i, parentKeys + parent->count, sizeof(IndexKey), true); + parentKeys[i].value = newKey; + parentKeys[i].data = vertexKeys[median].data; + parentKeys[i].child = blocks[depth]; + break; + } + } + + // Move all keys above the median key to the new sibling. + + sibling->count = vertex->count - median /*Kept in the node*/ - 1 /*Added to the parent*/; + vertex->count = median; // The data on the median key becomes the +1 key's data. + EsMemoryCopy(siblingKeys, vertexKeys + median + 1, (sibling->count + 1) * sizeof(IndexKey)); + + // Write the blocks. + + sibling->checksum = 0; sibling->checksum = CalculateCRC32(sibling, superblock->blockSize); + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + ESFS_CHECK(AccessBlock(volume, siblingBlock, 1, sibling, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexAddKey - Could not update index."); + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexAddKey - Could not update index."); + + // Check if the parent vertex is full. + + EsMemoryCopy(vertex, parent, superblock->blockSize); + depth--; + } + + // Write the block. + + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexAddKey - Could not update index."); + + return true; +} + +static bool IndexRemoveKey(Volume *volume, uint64_t removeKey, uint64_t *rootBlock) { + // TODO Return EsError. + // TODO Support KWorkGroup. + + Superblock *superblock = &volume->superblock; + uint8_t *blockBuffers = (uint8_t *) EsHeapAllocate(superblock->blockSize * 3, true, K_FIXED); + EsDefer(EsHeapFree(blockBuffers, 0, K_FIXED)); + ESFS_CHECK(blockBuffers, "IndexRemoveKey - Could not allocate block buffers."); + + // Find the vertex that contains this key. + + uint8_t *buffer = blockBuffers + 0 * superblock->blockSize; + IndexVertex *vertex = (IndexVertex *) buffer; + uint64_t depth = 0, blocks[ESFS_INDEX_MAX_DEPTH] = { *rootBlock }; + ESFS_CHECK(blocks[0], "IndexRemoveKey - Index has no root."); + int position = 0; + + { + next:; + int i; + + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexRemoveKey - Could not read index."); + if (!ValidateIndexVertex(superblock, vertex)) return false; + + for (i = 0; i <= vertex->count; i++) { + IndexKey *key = ESFS_VERTEX_KEY(vertex, i); + + if (i != vertex->count && key->value == removeKey) { + // EsPrint("found key %x at depth %d block %d position %d\n", removeKey, depth, blocks[depth], i); + goto done; + } else if ((i == vertex->count || removeKey < key->value) && key->child) { + ESFS_CHECK(depth < ESFS_INDEX_MAX_DEPTH - 1, "IndexRemoveKey - Reached tree max depth."); + blocks[++depth] = key->child; + // EsPrint("recurse into block %d at depth %d position %d\n", blocks[depth], depth, i); + goto next; + } + } + + ESFS_CHECK(false, "IndexRemoveKey - The key was not in the tree."); + done:; + position = i; + } + + if (ESFS_VERTEX_KEY(vertex, position)->child) { + // If the removed key has children, replace it with the smallest above, then consider that leaf. + + uint64_t startDepth = depth; + uint8_t *buffer2 = blockBuffers + 1 * superblock->blockSize; + IndexVertex *search = (IndexVertex *) buffer2; + blocks[++depth] = ESFS_VERTEX_KEY(vertex, position + 1)->child; + + while (true) { + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, search, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexRemoveKey - Could not read index."); + if (!ValidateIndexVertex(superblock, search)) return false; + + if (ESFS_VERTEX_KEY(search, 0)->child) { + ESFS_CHECK(depth < ESFS_INDEX_MAX_DEPTH - 1, "IndexRemoveKey - Reached tree max depth."); + blocks[++depth] = ESFS_VERTEX_KEY(vertex, 1)->child; + } else break; + } + + ESFS_VERTEX_KEY(vertex, position)->value = ESFS_VERTEX_KEY(search, 0)->value; + ESFS_VERTEX_KEY(vertex, position)->data = ESFS_VERTEX_KEY(search, 0)->data; + + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + ESFS_CHECK(AccessBlock(volume, blocks[startDepth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + + EsMemoryCopy(vertex, search, superblock->blockSize); + position = 0; + } + + // Remove the key. + + // EsPrint("Removing key from vertex...\n"); + EsMemoryMove(ESFS_VERTEX_KEY(vertex, position + 1), ESFS_VERTEX_KEY(vertex, vertex->count + 1), ES_MEMORY_MOVE_BACKWARDS sizeof(IndexKey), true); + vertex->count--; + + repeat:; + + // If the vertex still has enough keys, return. + + if (vertex->count >= (vertex->maxCount - 1) / 2) { + // EsPrint("Vertex has enough keys, exiting...\n"); + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + return true; + } + + // If the we reach the root of the tree, we're done. + + if (depth == 0) { + if (!vertex->count && vertex->keys[0].child) { + // Reduce the height of the tree. + + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + ESFS_CHECK(FreeExtent(volume, blocks[0], 1), "IndexRemoveKey - Could not free the old root index block."); + *rootBlock = vertex->keys[0].child; + } else { + // EsPrint("Vertex is at root, exiting...\n"); + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + } + + return true; + } + + // Find the position of vertex in its parent. + + uint8_t *buffer2 = blockBuffers + 1 * superblock->blockSize; + IndexVertex *parent = (IndexVertex *) buffer2; + ESFS_CHECK(AccessBlock(volume, blocks[depth - 1], 1, parent, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexRemoveKey - Could not read index."); + if (!ValidateIndexVertex(superblock, parent)) return false; + + int positionInParent = -1; + + for (int i = 0; i <= parent->count; i++) { + if (ESFS_VERTEX_KEY(parent, i)->child == blocks[depth]) { + positionInParent = i; + break; + } + } + + ESFS_CHECK(positionInParent != -1, "IndexRemoveKey - Could not find position in parent."); + + uint8_t *buffer3 = blockBuffers + 2 * superblock->blockSize; + IndexVertex *sibling = (IndexVertex *) buffer3; + + if (positionInParent) { + ESFS_CHECK(AccessBlock(volume, ESFS_VERTEX_KEY(parent, positionInParent - 1)->child, 1, sibling, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexRemoveKey - Could not read index."); + + if (sibling->count > (sibling->maxCount - 1) / 2) { + // Steal left. + + EsMemoryMove(ESFS_VERTEX_KEY(vertex, 0), ESFS_VERTEX_KEY(vertex, vertex->count + 1), sizeof(IndexKey), true); + + ESFS_VERTEX_KEY(vertex, 0)-> value = ESFS_VERTEX_KEY(parent, positionInParent - 1)-> value; + ESFS_VERTEX_KEY(vertex, 0)-> data = ESFS_VERTEX_KEY(parent, positionInParent - 1)-> data; + ESFS_VERTEX_KEY(vertex, 0)-> child = ESFS_VERTEX_KEY(sibling, sibling->count)-> child; + ESFS_VERTEX_KEY(parent, positionInParent - 1)-> value = ESFS_VERTEX_KEY(sibling, sibling->count - 1)-> value; + ESFS_VERTEX_KEY(parent, positionInParent - 1)-> data = ESFS_VERTEX_KEY(sibling, sibling->count - 1)-> data; + + sibling->count--, vertex->count++; + + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + sibling->checksum = 0; sibling->checksum = CalculateCRC32(sibling, superblock->blockSize); + parent->checksum = 0; parent->checksum = CalculateCRC32(parent, superblock->blockSize); + + ESFS_CHECK(AccessBlock(volume, ESFS_VERTEX_KEY(parent, positionInParent - 1)->child, 1, sibling, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + ESFS_CHECK(AccessBlock(volume, blocks[depth - 1], 1, parent, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + + return true; + } + } + + if (positionInParent != parent->count) { + ESFS_CHECK(AccessBlock(volume, ESFS_VERTEX_KEY(parent, positionInParent + 1)->child, 1, sibling, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexRemoveKey - Could not read index."); + + if (sibling->count > (sibling->maxCount - 1) / 2) { + // Steal right. + + ESFS_VERTEX_KEY(vertex, vertex->count)-> value = ESFS_VERTEX_KEY(parent, positionInParent)-> value; + ESFS_VERTEX_KEY(vertex, vertex->count)-> data = ESFS_VERTEX_KEY(parent, positionInParent)-> data; + ESFS_VERTEX_KEY(vertex, vertex->count + 1)-> child = ESFS_VERTEX_KEY(sibling, 0)-> child; + ESFS_VERTEX_KEY(parent, positionInParent)-> value = ESFS_VERTEX_KEY(sibling, 0)-> value; + ESFS_VERTEX_KEY(parent, positionInParent)-> data = ESFS_VERTEX_KEY(sibling, 0)-> data; + + EsMemoryMove(ESFS_VERTEX_KEY(sibling, 1), ESFS_VERTEX_KEY(sibling, sibling->count + 1), ES_MEMORY_MOVE_BACKWARDS sizeof(IndexKey), true); + + sibling->count--, vertex->count++; + + vertex->checksum = 0; vertex->checksum = CalculateCRC32(vertex, superblock->blockSize); + sibling->checksum = 0; sibling->checksum = CalculateCRC32(sibling, superblock->blockSize); + parent->checksum = 0; parent->checksum = CalculateCRC32(parent, superblock->blockSize); + + ESFS_CHECK(AccessBlock(volume, ESFS_VERTEX_KEY(parent, positionInParent + 1)->child, 1, sibling, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + ESFS_CHECK(AccessBlock(volume, blocks[depth - 1], 1, parent, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + + return true; + } + } + + { + // Merge nodes. + + if (!positionInParent) { + EsMemoryCopy(sibling, vertex, superblock->blockSize); + positionInParent = 1; + blocks[depth] = ESFS_VERTEX_KEY(parent, positionInParent)->child; + ESFS_CHECK(AccessBlock(volume, blocks[depth], 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexRemoveKey - Could not read index."); + if (!ValidateIndexVertex(superblock, vertex)) return false; + } else { + ESFS_CHECK(AccessBlock(volume, ESFS_VERTEX_KEY(parent, positionInParent - 1)->child, 1, sibling, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "IndexRemoveKey - Could not read index."); + if (!ValidateIndexVertex(superblock, sibling)) return false; + } + + // TODO I'm fairly certain this won't happen (as we should have stolen a key), but it needs to be double-checked. + ESFS_CHECK(sibling->count + vertex->count + 1 < sibling->maxCount, "IndexRemoveKey - Merged node would be too large."); + + ESFS_VERTEX_KEY(sibling, sibling->count)-> value = ESFS_VERTEX_KEY(parent, positionInParent - 1)->value; + ESFS_VERTEX_KEY(sibling, sibling->count)-> data = ESFS_VERTEX_KEY(parent, positionInParent - 1)->data; + ESFS_VERTEX_KEY(parent, positionInParent)-> child = ESFS_VERTEX_KEY(parent, positionInParent - 1)->child; + + EsMemoryCopy(ESFS_VERTEX_KEY(sibling, sibling->count + 1), ESFS_VERTEX_KEY(vertex, 0), (vertex->count + 1) * sizeof(IndexKey)); + EsMemoryMove(ESFS_VERTEX_KEY(parent, positionInParent), ESFS_VERTEX_KEY(parent, parent->count + 1), ES_MEMORY_MOVE_BACKWARDS sizeof(IndexKey), true); + + sibling->count += vertex->count + 1, parent->count--; + + { + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + ESFS_CHECK(FreeExtent(volume, blocks[depth], 1), "IndexRemoveKey - Could not free merged vertex."); + } + + sibling->checksum = 0; sibling->checksum = CalculateCRC32(sibling, superblock->blockSize); + ESFS_CHECK(AccessBlock(volume, ESFS_VERTEX_KEY(parent, positionInParent - 1)->child, 1, sibling, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "IndexRemoveKey - Could not write index."); + + EsMemoryCopy(vertex, parent, superblock->blockSize); + depth--; + + goto repeat; + } +} + +static EsError RemoveDirectoryEntry(FSNode *file, uint8_t *blockBuffers /* superblock->blockSize * 2 */, FSNode *directory, KNode *_directory) { + // EsPrint("RemoveDirectoryEntry for %s from %s\n", node->name.bytes, node->name.buffer, + // node->parent->name.bytes, node->parent->name.buffer); + + Volume *volume = file->volume; + Superblock *superblock = &volume->superblock; + DirectoryEntry *entry = &file->entry; + AttributeDirectory *directoryAttribute = (AttributeDirectory *) FindAttribute(&directory->entry, ESFS_ATTRIBUTE_DIRECTORY); + EsError error; + + // EsPrint("\tChildren = %d\n", directoryAttribute->childNodes); + + // Step 1: Replace the directory entry with the last entry in the directory. + + uint64_t positionOfLastEntry = (directoryAttribute->childNodes - 1) * sizeof(DirectoryEntry); + + // EsPrint("\tpositionOfLastEntry = %d\n\tThis node Reference = %d/%d\n", positionOfLastEntry, file->reference.block, file->reference.offsetIntoBlock); + + ESFS_CHECK_TO_ERROR(AccessBlock(volume, file->reference.block, 1, blockBuffers, BLOCK_ACCESS_CACHED, K_ACCESS_READ), "Remove - Could not load the container block.", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + ESFS_CHECK_TO_ERROR(ReadWrite(directory, positionOfLastEntry & ~(superblock->blockSize - 1), superblock->blockSize, + blockBuffers + superblock->blockSize, false, false), "Remove - Could not load the last block.", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + ESFS_CHECK_TO_ERROR(0 == EsMemoryCompare(blockBuffers + file->reference.offsetIntoBlock, &file->entry, sizeof(DirectoryEntry)), "Remove - Inconsistent file entry.", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + + DirectoryEntry *movedEntry = (DirectoryEntry *) (blockBuffers + superblock->blockSize + (positionOfLastEntry & (superblock->blockSize - 1))); + DirectoryEntry *deletedEntry = (DirectoryEntry *) (blockBuffers + file->reference.offsetIntoBlock); + EsMemoryCopy(deletedEntry, movedEntry, sizeof(DirectoryEntry)); + ESFS_CHECK_TO_ERROR(AccessBlock(volume, file->reference.block, 1, blockBuffers, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE), "Remove - Could not save the container block.", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + + // Step 2: Update the node for the moved entry. + + if (EsMemoryCompare(movedEntry->identifier.d, entry->identifier.d, sizeof(EsUniqueIdentifier))) { + AttributeFilename *filename = (AttributeFilename *) FindAttribute(movedEntry, ESFS_ATTRIBUTE_FILENAME); + + if (filename) { + KNode *node = nullptr; + + FSDirectoryEntryFound(_directory, nullptr, &file->reference, + (const char *) filename->filename, filename->length, true, &node); + + if (node) { + ((FSNode *) node->driverNode)->reference = file->reference; + } + + uint64_t key = CalculateCRC64(filename->filename, filename->length); + // EsPrint("\tModify index key for %s\n", filename->length, filename->filename); + ESFS_CHECK_TO_ERROR(IndexModifyKey(volume, key, file->reference, directoryAttribute->indexRootBlock, blockBuffers + superblock->blockSize), "Remove - Could not update index (2).", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + } + } + + // Step 3: Decrease the size of the directory. + + directoryAttribute->childNodes--; + + if (!(directoryAttribute->childNodes % superblock->directoryEntriesPerBlock)) { + uint64_t newSize = directory->entry.fileSize - superblock->blockSize; + ESFS_CHECK_TO_ERROR(newSize == ResizeInternal(directory, newSize, &error), "Remove - Could not resize directory.", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + } + + // Step 4: Remove the entry from the index. + + AttributeFilename *filename = (AttributeFilename *) FindAttribute(entry, ESFS_ATTRIBUTE_FILENAME); + // EsPrint("\tRemoving %s from index\n", filename->length, filename->filename); + + if (filename) { + uint64_t removeKey = CalculateCRC64(filename->filename, filename->length); + ESFS_CHECK_TO_ERROR(IndexRemoveKey(volume, removeKey, &directoryAttribute->indexRootBlock), "Remove - Could not update index.", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + } + + return ES_SUCCESS; +} + +static EsError Remove(KNode *_directory, KNode *node) { + FSNode *directory = (FSNode *) _directory->driverNode; + FSNode *file = (FSNode *) node->driverNode; + Volume *volume = file->volume; + Superblock *superblock = &volume->superblock; + DirectoryEntry *entry = &file->entry; + AttributeDirectory *directoryAttribute = (AttributeDirectory *) FindAttribute(&directory->entry, ESFS_ATTRIBUTE_DIRECTORY); + ESFS_CHECK_TO_ERROR(directoryAttribute->childNodes, "Remove - Directory is empty.", ES_ERROR_CORRUPT_DATA); + + uint8_t *blockBuffers = (uint8_t *) EsHeapAllocate(superblock->blockSize * 2, false, K_FIXED); + if (!blockBuffers) return ES_ERROR_INSUFFICIENT_RESOURCES; + EsDefer(EsHeapFree(blockBuffers, 0, K_FIXED)); + + // Step 1: If we're deleting a directory, deallocate its empty index. + + if (entry->nodeType == ESFS_NODE_TYPE_DIRECTORY) { + AttributeDirectory *attribute = (AttributeDirectory *) FindAttribute(entry, ESFS_ATTRIBUTE_DIRECTORY); + ESFS_CHECK_TO_ERROR(!attribute->childNodes, "Remove - Directory was not empty.", ES_ERROR_CORRUPT_DATA); + + if (attribute->indexRootBlock) { + IndexVertex *vertex = (IndexVertex *) blockBuffers; + ESFS_CHECK_TO_ERROR(AccessBlock(volume, attribute->indexRootBlock, 1, vertex, BLOCK_ACCESS_CACHED, K_ACCESS_READ), + "Remove - Could not access index root.", ES_ERROR_DRIVE_CONTROLLER_REPORTED); + ESFS_CHECK_TO_ERROR(!vertex->count, "Remove - Index was not empty (although it should be as the directory is empty).", ES_ERROR_CORRUPT_DATA); + KWriterLockTake(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&volume->blockBitmapLock, K_LOCK_EXCLUSIVE)); + ESFS_CHECK_TO_ERROR(FreeExtent(volume, attribute->indexRootBlock, 1), + "Remove - Could not free the root index block.", ES_ERROR_UNKNOWN); + } + } + + // Step 2: Truncate the node to 0 bytes. + + EsError error; + + if (ResizeInternal(file, 0, &error)) { + ESFS_CHECK_TO_ERROR(false, "Remove - Could not resize node.", error); + } + + // Step 3: Sync the file, and remove its directory entry. + + Sync(_directory, node); + return RemoveDirectoryEntry(file, blockBuffers, directory, _directory); +} + +static bool RenameInternal(FSNode *existingNode, DirectoryEntry *entry, const void *name, size_t nameLength) { + // Size of name + size of header, rounded up to the nearest 8 bytes. + size_t newFilenameSize = ((nameLength + ESFS_FILENAME_HEADER_SIZE - 1) & ~7) + 8; + + AttributeFilename *filename = (AttributeFilename *) FindAttribute(entry, ESFS_ATTRIBUTE_FILENAME); + AttributeData *data = (AttributeData *) FindAttribute(entry, ESFS_ATTRIBUTE_DATA); + + if (filename && data && (uint8_t *) filename - (uint8_t *) data == data->size) { + // TODO Improve this. + // TODO Renaming directories does not work! + + intptr_t filenameSizeChange = newFilenameSize - filename->size; + size_t newDataSize = data->size - filenameSizeChange; + + EsError error; + ESFS_CHECK(existingNode->entry.fileSize == ResizeInternal(existingNode, existingNode->entry.fileSize, &error, newDataSize), + "CreateInternal - Could not resize data attribute."); + ESFS_CHECK(error == ES_SUCCESS, "CreateInternal - Could not resize data attribute."); + + AttributeFilename *filename = (AttributeFilename *) ((uint8_t *) data + data->size); + filename->type = ESFS_ATTRIBUTE_FILENAME; + filename->size = newFilenameSize; + filename->length = nameLength; + EsMemoryCopy(filename->filename, name, filename->length); + } else { + ESFS_CHECK(false, "CreateInternal - Could not rename node."); + } + + return true; +} + +static bool CreateInternal(const char *name, size_t nameLength, EsNodeType type, FSNode *parent, uint8_t *buffer /* superblock->blockSize */, + FSNode *existingNode = nullptr, DirectoryEntryReference *outReference = nullptr) { + // TODO Return EsError. + Volume *volume = parent->volume; + Superblock *superblock = &volume->superblock; + EsError error; + + if (type != ES_NODE_DIRECTORY && type != ES_NODE_FILE) { + return false; + } + + AttributeDirectory *directoryAttribute = (AttributeDirectory *) FindAttribute(&parent->entry, ESFS_ATTRIBUTE_DIRECTORY); + + // Resize the directory so that it can fit another directory entry. + + if (!(directoryAttribute->childNodes % superblock->directoryEntriesPerBlock)) { + uint64_t newSize = parent->entry.fileSize + superblock->blockSize; + ESFS_CHECK(newSize == ResizeInternal(parent, newSize, &error), "Create - Could not resize directory."); + } + + DirectoryEntry *entry = nullptr; + size_t newFilenameSize = ((nameLength + ESFS_FILENAME_HEADER_SIZE - 1) & ~7) + 8; // Size of name + size of header, rounded up to the nearest 8 bytes. + + if (!existingNode) { + // Create the directory entry. + + entry = (DirectoryEntry *) buffer; // NOTE This must use the provided buffer; see Create. + EsMemoryZero(entry, sizeof(DirectoryEntry)); + + EsMemoryCopy(entry->signature, ESFS_DIRECTORY_ENTRY_SIGNATURE, 8); + + { + KMutexAcquire(&volume->nextIdentifierMutex); + + entry->identifier = superblock->nextIdentifier; + + for (int i = 0; i < 16; i++) { + superblock->nextIdentifier.d[i]++; + if (superblock->nextIdentifier.d[i]) break; + } + + KMutexRelease(&volume->nextIdentifierMutex); + } + + entry->attributeOffset = ESFS_ATTRIBUTE_OFFSET; + entry->nodeType = type == ES_NODE_DIRECTORY ? ESFS_NODE_TYPE_DIRECTORY : ESFS_NODE_TYPE_FILE; + entry->parent = parent->identifier; + + uint8_t *position = entry->attributes; + + if (entry->nodeType == ESFS_NODE_TYPE_DIRECTORY) { + AttributeDirectory *directory = (AttributeDirectory *) position; + directory->type = ESFS_ATTRIBUTE_DIRECTORY; + directory->size = sizeof(AttributeDirectory); + directory->indexRootBlock = 0; + entry->attributeCount++; + position += directory->size; + } + + AttributeData *data = (AttributeData *) position; + data->type = ESFS_ATTRIBUTE_DATA; + data->size = sizeof(DirectoryEntry) - newFilenameSize - (position - (uint8_t *) entry); + data->indirection = ESFS_INDIRECTION_DIRECT; + data->dataOffset = ESFS_DATA_OFFSET; + entry->attributeCount++; + position += data->size; + + AttributeFilename *filename = (AttributeFilename *) position; + filename->type = ESFS_ATTRIBUTE_FILENAME; + filename->size = newFilenameSize; + filename->length = nameLength; + EsMemoryCopy(filename->filename, name, filename->length); + entry->attributeCount++; + position += filename->size; + + if (position - (uint8_t *) entry != sizeof(DirectoryEntry)) KernelPanic("EsFS::CreateInternal - Directory entry has incorrect size.\n"); + } else { + // Update the existing directory entry. + + entry = &existingNode->entry; + entry->parent = parent->identifier; + + if (!RenameInternal(existingNode, entry, name, nameLength)) { + return false; + } + } + + entry->checksum = 0; + entry->checksum = CalculateCRC32(entry, sizeof(DirectoryEntry)); + if (!ValidateDirectoryEntry(entry)) KernelPanic("EsFS::CreateInternal - Created directory entry is invalid.\n"); + + // Write the directory entry. + + DirectoryEntryReference reference = {}; + ESFS_CHECK(ReadWrite(parent, directoryAttribute->childNodes * sizeof(DirectoryEntry), + sizeof(DirectoryEntry), (uint8_t *) entry, true, true, &reference), "Create - Could not update directory."); + if (existingNode) existingNode->reference = reference; + + // Add the node into the index. + + uint64_t newKey = CalculateCRC64(name, nameLength); + ESFS_CHECK(IndexAddKey(volume, newKey, reference, &directoryAttribute->indexRootBlock), "Create - Could not add file to index."); + + directoryAttribute->childNodes++; + + if (outReference) { + *outReference = reference; + } + + return true; +} + +static EsError Move(KNode *_oldDirectory, KNode *_file, KNode *_newDirectory, const char *newName, size_t newNameLength) { + FSNode *file = (FSNode *) _file->driverNode; + FSNode *newDirectory = (FSNode *) _newDirectory->driverNode; + FSNode *oldDirectory = (FSNode *) _oldDirectory->driverNode; + + Volume *volume = file->volume; + Superblock *superblock = &volume->superblock; + + if (oldDirectory->type != ES_NODE_DIRECTORY || newDirectory->type != ES_NODE_DIRECTORY) KernelPanic("EsFS::Move - Incorrect node types.\n"); + + file->entry.checksum = 0; + file->entry.checksum = CalculateCRC32(&file->entry, sizeof(DirectoryEntry)); + if (!ValidateDirectoryEntry(&file->entry)) KernelPanic("EsFS::Move - Existing entry is invalid.\n"); + + uint8_t *buffers = (uint8_t *) EsHeapAllocate(superblock->blockSize * 2, true, K_FIXED); + if (!buffers) return ES_ERROR_INSUFFICIENT_RESOURCES; + EsDefer(EsHeapFree(buffers, 0, K_FIXED)); + + // Remove the node from the old directory. + + Sync(_oldDirectory, _file); + ESFS_CHECK_ERROR(RemoveDirectoryEntry(file, buffers, oldDirectory, _oldDirectory), "Move - Could not remove old directory entry."); + + // Add the node to the new directory. + + DirectoryEntryReference reference = {}; + ESFS_CHECK_TO_ERROR(CreateInternal(newName, newNameLength, file->type, newDirectory, nullptr, + file, &reference), "Move - Could not create new directory entry.", ES_ERROR_UNKNOWN); + FSNodeUpdateDriverData(_file, &reference); + + return ES_SUCCESS; +} + +static void Close(KNode *node) { + EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED); +} + +static EsError LoadInternal(Volume *volume, KNode *_node, DirectoryEntry *entry, DirectoryEntryReference reference) { + FSNode *node = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!node) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + _node->driverNode = node; + node->reference = reference; + node->volume = volume; + node->identifier = entry->identifier; + node->type = entry->nodeType == ESFS_NODE_TYPE_DIRECTORY ? ES_NODE_DIRECTORY : ES_NODE_FILE; + EsMemoryCopy(&node->entry, entry, sizeof(DirectoryEntry)); + return ES_SUCCESS; +} + +static EsError Load(KNode *_directory, KNode *_node, KNodeMetadata *, const void *entryData) { + DirectoryEntryReference reference = *(DirectoryEntryReference *) entryData; + FSNode *directory = (FSNode *) _directory->driverNode; + Superblock *superblock = &directory->volume->superblock; + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(superblock->blockSize, false, K_FIXED); + if (!blockBuffer) return ES_ERROR_INSUFFICIENT_RESOURCES; + EsDefer(EsHeapFree(blockBuffer, 0, K_FIXED)); + + if (!AccessBlock(directory->volume, reference.block, 1, blockBuffer, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + KernelLog(LOG_ERROR, "EsFS", "drive access failure", "Load - Could not load directory entry.\n"); + return ES_ERROR_UNKNOWN; + } + + DirectoryEntry *entry = (DirectoryEntry *) (blockBuffer + reference.offsetIntoBlock); + + if (!ValidateDirectoryEntry(entry)) { + return ES_ERROR_CORRUPT_DATA; + } + + if ((entry->nodeType == ESFS_NODE_TYPE_DIRECTORY && !FindAttribute(entry, ESFS_ATTRIBUTE_DIRECTORY)) + || (entry->nodeType == ESFS_NODE_TYPE_FILE && !FindAttribute(entry, ESFS_ATTRIBUTE_DATA))) { + KernelLog(LOG_ERROR, "EsFS", "damaged file system", "Load - Node is missing attribute.\n"); + return ES_ERROR_CORRUPT_DATA; + } + + return LoadInternal(directory->volume, _node, entry, reference); +} + +static EsError Create(const char *name, size_t nameLength, EsNodeType type, KNode *_parent, KNode *node, void *driverData) { + FSNode *parent = (FSNode *) _parent->driverNode; + if (!parent) return ES_ERROR_UNKNOWN; + Volume *volume = parent->volume; + Superblock *superblock = &volume->superblock; + + uint8_t *buffer = (uint8_t *) EsHeapAllocate(superblock->blockSize, true, K_FIXED); + if (!buffer) return ES_ERROR_INSUFFICIENT_RESOURCES; + EsDefer(EsHeapFree(buffer, 0, K_FIXED)); + + DirectoryEntryReference reference = {}; + + if (!CreateInternal(name, nameLength, type, parent, buffer, nullptr, &reference)) { + return ES_ERROR_UNKNOWN; + } + + EsMemoryCopy(driverData, &reference, sizeof(DirectoryEntryReference)); + return LoadInternal(volume, node, (DirectoryEntry *) buffer, reference); +} + +static EsError Scan(const char *name, size_t nameLength, KNode *_directory) { + // EsPrint("Scan: %s, %x\n", offsetToName + nameLength, name, _directory); + + DirectoryEntryReference reference = {}; + FSNode *directory = (FSNode *) _directory->driverNode; + if (directory->corrupt) return ES_ERROR_CORRUPT_DATA; + Volume *volume = directory->volume; + Superblock *superblock = &volume->superblock; + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(superblock->blockSize, false, K_FIXED); + if (!blockBuffer) return ES_ERROR_INSUFFICIENT_RESOURCES; + EsDefer(EsHeapFree(blockBuffer, 0, K_FIXED)); + + AttributeDirectory *attributeDirectory = (AttributeDirectory *) FindAttribute(&directory->entry, ESFS_ATTRIBUTE_DIRECTORY); + EsError error = FindDirectoryEntryReferenceFromIndex(volume, blockBuffer, &reference, name, nameLength, attributeDirectory->indexRootBlock); + + if (error != ES_SUCCESS) { + // EsPrint("\tCould not find in directory. (%d - %s)\n", error, nameLength, name); + return error; + } + + // EsPrint("\t%d/%d\n", reference.block, reference.offsetIntoBlock); + + if (!AccessBlock(volume, reference.block, 1, blockBuffer, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + KernelLog(LOG_ERROR, "EsFS", "drive access failure", "Scan - Could not load directory entry.\n"); + return ES_ERROR_UNKNOWN; + } + + DirectoryEntry *entry = (DirectoryEntry *) (blockBuffer + reference.offsetIntoBlock); + if (!ValidateDirectoryEntry(entry)) return ES_ERROR_CORRUPT_DATA; + + if ((entry->nodeType == ESFS_NODE_TYPE_DIRECTORY && !FindAttribute(entry, ESFS_ATTRIBUTE_DIRECTORY)) + || (entry->nodeType == ESFS_NODE_TYPE_FILE && !FindAttribute(entry, ESFS_ATTRIBUTE_DATA))) { + KernelLog(LOG_ERROR, "EsFS", "damaged file system", "Scan - Node is missing attribute.\n"); + return ES_ERROR_CORRUPT_DATA; + } + + KNodeMetadata metadata = {}; + + if (entry->nodeType == ESFS_NODE_TYPE_DIRECTORY) { + AttributeDirectory *directoryAttribute = (AttributeDirectory *) FindAttribute(entry, ESFS_ATTRIBUTE_DIRECTORY); + + if (directoryAttribute) { + metadata.directoryChildren = directoryAttribute->childNodes; + metadata.totalSize = directoryAttribute->totalSize; + } + } else { + metadata.totalSize = entry->fileSize; + } + + metadata.type = entry->nodeType == ESFS_NODE_TYPE_DIRECTORY ? ES_NODE_DIRECTORY : ES_NODE_FILE; + + KNode *_node; + error = FSDirectoryEntryFound(_directory, &metadata, &reference, name, nameLength, false, &_node); + + if (error != ES_SUCCESS) { + return error; + } + + error = LoadInternal(volume, _node, entry, reference); + FSNodeScanAndLoadComplete(_node, error == ES_SUCCESS); + return error; +} + +static bool Mount(Volume *volume, EsFileOffsetDifference *rootDirectoryChildren) { + // TODO Return EsError. + // Load the superblock. + + Superblock *superblock = &volume->superblock; + + if (!volume->fileSystem->Access(ESFS_BOOT_SUPER_BLOCK_SIZE, ESFS_BOOT_SUPER_BLOCK_SIZE, K_ACCESS_READ, (uint8_t *) superblock, ES_FLAGS_DEFAULT)) { + KernelLog(LOG_ERROR, "EsFS", "drive access failure", "Mount - Could not read superblock.\n"); + return false; + } + + if (volume->fileSystem->block->readOnly) { + volume->readOnly = true; + } + + // Check the superblock is valid. + + uint32_t checksum = volume->superblock.checksum; + volume->superblock.checksum = 0; + uint32_t calculated = CalculateCRC32(&volume->superblock, sizeof(Superblock)); + ESFS_CHECK_FATAL(checksum == calculated, "Invalid superblock checksum."); + + ESFS_CHECK_FATAL(0 == EsMemoryCompare(volume->superblock.signature, ESFS_SIGNATURE_STRING, 16), "Invalid superblock signature."); + ESFS_CHECK_FATAL(volume->superblock.requiredReadVersion <= ESFS_DRIVER_VERSION, "Incompatible file system version."); + ESFS_CHECK_FATAL(superblock->blockSize >= 1024 && superblock->blockSize <= 16384 && (superblock->blockSize % volume->fileSystem->block->sectorSize) == 0, "Invalid block size."); + ESFS_CHECK_FATAL(superblock->blockCount * superblock->blockSize / volume->fileSystem->block->sectorSize <= volume->fileSystem->block->sectorCount, "More blocks than drive."); + ESFS_CHECK_FATAL(superblock->blocksUsed <= superblock->blockCount, "More blocks used than exist."); + ESFS_CHECK_FATAL(superblock->blocksPerGroup <= 65536 && superblock->blocksPerGroup < superblock->blockCount && superblock->blocksPerGroup >= 1024, "Invalid block group size."); + ESFS_CHECK_FATAL((superblock->groupCount - 1) * superblock->blocksPerGroup <= superblock->blockCount, "Invalid number of block groups."); + ESFS_CHECK_FATAL(superblock->blocksPerGroupBlockBitmap < superblock->blocksPerGroup, "Invalid number of blocks per group block bitmap."); + ESFS_CHECK_FATAL(superblock->gdtFirstBlock < superblock->blockCount, "First GDT block not within volume.\n"); + ESFS_CHECK_FATAL(superblock->directoryEntriesPerBlock <= superblock->blockSize / sizeof(DirectoryEntry), "Invalid number of directory entries per block."); + ESFS_CHECK_FATAL(superblock->root.block < superblock->blockCount && superblock->root.offsetIntoBlock < superblock->blockSize, "Invalid root directory position in volume.\n"); + ESFS_CHECK_READ_ONLY(!superblock->nextIdentifier.d[15], "Too many nodes created and deleted."); + ESFS_CHECK_READ_ONLY(superblock->requiredWriteVersion <= ESFS_DRIVER_VERSION, "Outdated file system version."); + + // ESFS_CHECK_READ_ONLY(!superblock->mounted, "Volume already mounted."); + + if (superblock->mounted) { + KernelLog(LOG_ERROR, "EsFS", "volume already mounted", + "Superblock indicates that the volume was either unmounted incorrectly, or is mounted by another driver instance.\n"); + } + + if (!volume->readOnly) { + superblock->mounted = true; + superblock->checksum = 0; + superblock->checksum = CalculateCRC32(superblock, sizeof(Superblock)); + ESFS_CHECK_READ_ONLY(volume->fileSystem->Access(ESFS_BOOT_SUPER_BLOCK_SIZE, ESFS_BOOT_SUPER_BLOCK_SIZE, + K_ACCESS_WRITE, (uint8_t *) superblock, ES_FLAGS_DEFAULT), "Could not mark volume as mounted."); + } + + // Load the group descriptor table. + + { + volume->groupDescriptorTable = (GroupDescriptor *) EsHeapAllocate((superblock->groupCount * sizeof(GroupDescriptor) + superblock->blockSize - 1), false, K_FIXED); + + if (!AccessBlock(volume, superblock->gdtFirstBlock, (superblock->groupCount * sizeof(GroupDescriptor) + superblock->blockSize - 1) / superblock->blockSize, + volume->groupDescriptorTable, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + EsHeapFree(volume->groupDescriptorTable, 0, K_FIXED); + ESFS_CHECK_FATAL(false, "Could not read group descriptor table."); + } + } + + // Load the root directory. + + { + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(superblock->blockSize, false, K_FIXED); + EsDefer(if (blockBuffer) EsHeapFree(blockBuffer, superblock->blockSize, K_FIXED)); + FSNode *node = nullptr; + + if (!blockBuffer) { + KernelLog(LOG_ERROR, "EsFS", "allocation failure", "Mount - Could not allocate block buffer.\n"); + goto failure; + } + + { + DirectoryEntryReference rootReference = superblock->root; + + if (!AccessBlock(volume, rootReference.block, 1, blockBuffer, BLOCK_ACCESS_CACHED, K_ACCESS_READ)) { + KernelLog(LOG_ERROR, "EsFS", "drive access failure", "Mount - Could not load root directory.\n"); + goto failure; + } + + DirectoryEntry *entry = (DirectoryEntry *) (blockBuffer + rootReference.offsetIntoBlock); + if (!ValidateDirectoryEntry(entry)) goto failure; + AttributeDirectory *directory = (AttributeDirectory *) FindAttribute(entry, ESFS_ATTRIBUTE_DIRECTORY); + + if (!directory || !FindAttribute(entry, ESFS_ATTRIBUTE_DATA)) { + KernelLog(LOG_ERROR, "EsFS", "damaged file system", "Mount - Root directory is missing attributes.\n"); + goto failure; + } + + volume->root = node = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + node->volume = volume; + node->reference = rootReference; + node->identifier = entry->identifier; + node->type = ES_NODE_DIRECTORY; + *rootDirectoryChildren = directory->childNodes; + EsMemoryCopy(&node->entry, entry, sizeof(DirectoryEntry)); + + goto success; + } + + failure:; + if (node) EsHeapFree(node, sizeof(FSNode), K_FIXED); + return false; + } + + success:; + return true; +} + +static void Unmount(KFileSystem *fileSystem) { + Volume *volume = (Volume *) fileSystem->driverData; + Superblock *superblock = &volume->superblock; + + if (!volume->readOnly) { + AccessBlock(volume, superblock->gdtFirstBlock, (superblock->groupCount * sizeof(GroupDescriptor) + superblock->blockSize - 1) / superblock->blockSize, + volume->groupDescriptorTable, BLOCK_ACCESS_CACHED, K_ACCESS_WRITE); + + superblock->mounted = false; + superblock->checksum = 0; + superblock->checksum = CalculateCRC32(superblock, sizeof(Superblock)); + volume->fileSystem->Access(ESFS_BOOT_SUPER_BLOCK_SIZE, ESFS_BOOT_SUPER_BLOCK_SIZE, K_ACCESS_WRITE, + (uint8_t *) superblock, ES_FLAGS_DEFAULT); + } + + EsHeapFree(volume->groupDescriptorTable, 0, K_FIXED); +} + +static void Register(KDevice *_parent) { + KFileSystem *fileSystem = (KFileSystem *) _parent; + Volume *volume = (Volume *) KDeviceCreate("EssenceFS", fileSystem, sizeof(Volume)); + + if (!volume) { + KernelLog(LOG_ERROR, "EsFS", "allocation failure", "Register - Could not allocate Volume structure.\n"); + return; + } + + volume->fileSystem = fileSystem; + + EsFileOffsetDifference rootDirectoryChildren; + + if (!Mount(volume, &rootDirectoryChildren)) { + KernelLog(LOG_ERROR, "EsFS", "mount error", "Register - Could not mount EsFS volume.\n"); + KDeviceDestroy(volume); + return; + } + + fileSystem->spaceUsed = volume->superblock.blocksUsed * volume->superblock.blockSize; + fileSystem->spaceTotal = volume->superblock.blockCount * volume->superblock.blockSize; + + fileSystem->read = Read; + fileSystem->scan = Scan; + fileSystem->load = Load; + fileSystem->enumerate = Enumerate; + fileSystem->unmount = Unmount; + fileSystem->close = Close; + + if (!volume->readOnly) { + fileSystem->write = Write; + fileSystem->sync = Sync; + fileSystem->resize = Resize; + fileSystem->create = Create; + fileSystem->remove = Remove; + fileSystem->move = Move; + } + + volume->superblock.volumeName[ESFS_MAXIMUM_VOLUME_NAME_LENGTH - 1] = 0; + fileSystem->nameBytes = EsCStringLength(volume->superblock.volumeName); + EsMemoryCopy(fileSystem->name, volume->superblock.volumeName, fileSystem->nameBytes); + + fileSystem->driverData = volume; + fileSystem->rootDirectory->driverNode = volume->root; + fileSystem->rootDirectoryInitialChildren = rootDirectoryChildren; + fileSystem->directoryEntryDataBytes = sizeof(DirectoryEntryReference); + fileSystem->nodeDataBytes = sizeof(FSNode); + + FSRegisterBootFileSystem(fileSystem, volume->superblock.osInstallation); +} + +KDriver driverEssenceFS = { + .attach = Register, +}; diff --git a/drivers/ext2.cpp b/drivers/ext2.cpp new file mode 100644 index 0000000..9b533b9 --- /dev/null +++ b/drivers/ext2.cpp @@ -0,0 +1,650 @@ +// TODO Validation of all fields. +// TODO Contiguous block reading in Scan and Enumerate. +// TODO Make GetDataBlock use (not yet implemented) system block cache. + +#include + +struct SuperBlock { + uint32_t inodeCount; + uint32_t blockCount; + uint32_t reservedBlockCount; + uint32_t unallocatedBlockCount; + uint32_t unallocatedInodeCount; + + uint32_t superBlockContainer; + uint32_t blockSizeExponent; + uint32_t fragmentSizeExponent; + + uint32_t blocksPerBlockGroup; + uint32_t fragmentsPerBlockGroup; + uint32_t inodesPerBlockGroup; + + uint32_t lastMountTime; + uint32_t lastWriteTime; + + uint16_t mountsSinceLastCheck; + uint16_t mountsAllowedBetweenChecks; + + uint16_t signature; + + uint16_t state; + uint16_t errorHandling; + + uint16_t minorVersion; + + uint32_t lastCheckTime; + uint32_t intervalCheckTime; + + uint32_t creatorID; + uint32_t majorVersion; + + uint16_t superUserID; + uint16_t superGroupID; + + uint32_t firstNonReservedInode; + uint16_t inodeStructureBytes; + uint16_t blockGroupOfSuperBlock; + + uint32_t optionalFeatures; + uint32_t requiredFeatures; + uint32_t writeFeatures; + + uint8_t fileSystemID[16]; + uint8_t volumeName[16]; + uint8_t lastMountPath[64]; + + uint32_t compressionAlgorithms; + + uint8_t preallocateFileBlocks; + uint8_t preallocateDirectoryBlocks; + + uint16_t _unused0; + + uint8_t journalID[16]; + uint32_t journalInode; + uint32_t journalDevice; + + uint32_t orphanInodeList; +}; + +struct BlockGroupDescriptor { + uint32_t blockUsageBitmap; + uint32_t inodeUsageBitmap; + uint32_t inodeTable; + + uint16_t unallocatedBlockCount; + uint16_t unallocatedInodeCount; + uint16_t directoryCount; + + uint8_t _unused1[14]; +}; + +struct Inode { +#define INODE_TYPE_DIRECTORY (0x4000) +#define INODE_TYPE_REGULAR (0x8000) + uint16_t type; + + uint16_t userID; + uint32_t fileSizeLow; + + uint32_t accessTime; + uint32_t creationTime; + uint32_t modificationTime; + uint32_t deletionTime; + + uint16_t groupID; + uint16_t hardLinkCount; + uint32_t usedSectorCount; + uint32_t flags; + uint32_t _unused0; + + uint32_t directBlockPointers[12]; + uint32_t indirectBlockPointers[3]; + + uint32_t generation; + uint32_t extendedAttributeBlock; + uint32_t fileSizeHigh; + uint32_t fragmentBlock; + uint8_t osSpecific[12]; +}; + +struct DirectoryEntry { + uint32_t inode; + uint16_t entrySize; + uint8_t nameLengthLow; + + union { + uint8_t nameLengthHigh; + +#define DIRENT_TYPE_REGULAR (1) +#define DIRENT_TYPE_DIRECTORY (2) + uint8_t type; + }; + + // Followed by name. +}; + +struct FSNode { + struct Volume *volume; + Inode inode; +}; + +struct Volume : KDevice { + KFileSystem *fileSystem; + SuperBlock superBlock; + BlockGroupDescriptor *blockGroupDescriptorTable; + size_t blockBytes; +}; + +static bool Mount(Volume *volume) { +#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "mount failure", "Mount - " message); return false; } while (0) + + // Load the superblock. + + uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(volume->fileSystem->block->sectorSize, false, K_FIXED); + + if (!sectorBuffer) { + MOUNT_FAILURE("Could not allocate buffer.\n"); + } + + EsDefer(EsHeapFree(sectorBuffer, volume->fileSystem->block->sectorSize, K_FIXED)); + + { + if (!volume->fileSystem->Access(1024, volume->fileSystem->block->sectorSize, K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) { + MOUNT_FAILURE("Could not read boot sector.\n"); + } + + EsMemoryCopy(&volume->superBlock, sectorBuffer, sizeof(SuperBlock)); + + if (volume->superBlock.majorVersion < 1) { + MOUNT_FAILURE("Volumes below major version 1 not supprted.\n"); + } + + if (volume->superBlock.requiredFeatures != 2) { + MOUNT_FAILURE("Volume uses unsupported features that are required to read it.\n"); + } + + if (volume->superBlock.inodeStructureBytes < sizeof(Inode)) { + MOUNT_FAILURE("Inode structure size too small.\n"); + } + + volume->blockBytes = 1024 << volume->superBlock.blockSizeExponent; + + if (volume->blockBytes < volume->fileSystem->block->sectorSize) { + MOUNT_FAILURE("Block size smaller than drive sector size.\n"); + } + } + + // Load the block group descriptor table. + + { + uint32_t blockGroupCount = (volume->superBlock.blockCount + volume->superBlock.blocksPerBlockGroup - 1) / volume->superBlock.blocksPerBlockGroup; + uint32_t firstBlockContainingBlockGroupDescriptorTable = volume->blockBytes == 1024 ? 2 : 1; + uint32_t blockGroupDescriptorTableLengthInBlocks = (blockGroupCount * sizeof(BlockGroupDescriptor) + volume->blockBytes - 1) / volume->blockBytes; + + volume->blockGroupDescriptorTable = (BlockGroupDescriptor *) EsHeapAllocate(blockGroupDescriptorTableLengthInBlocks * volume->blockBytes, false, K_FIXED); + + if (!volume->blockGroupDescriptorTable) { + MOUNT_FAILURE("Could not allocate the block group descriptor table.\n"); + } + + if (!volume->fileSystem->Access(firstBlockContainingBlockGroupDescriptorTable * volume->blockBytes, + blockGroupDescriptorTableLengthInBlocks * volume->blockBytes, + K_ACCESS_READ, volume->blockGroupDescriptorTable, ES_FLAGS_DEFAULT)) { + MOUNT_FAILURE("Could not read the block group descriptor table from the drive.\n"); + } + } + + // Load the root directory. + + { + uint32_t inode = 2; + + uint32_t blockGroup = (inode - 1) / volume->superBlock.inodesPerBlockGroup; + uint32_t indexInInodeTable = (inode - 1) % volume->superBlock.inodesPerBlockGroup; + uint32_t sectorInInodeTable = (indexInInodeTable * volume->superBlock.inodeStructureBytes) / volume->fileSystem->block->sectorSize; + uint32_t offsetInSector = (indexInInodeTable * volume->superBlock.inodeStructureBytes) % volume->fileSystem->block->sectorSize; + + BlockGroupDescriptor *blockGroupDescriptor = volume->blockGroupDescriptorTable + blockGroup; + + if (!volume->fileSystem->Access(blockGroupDescriptor->inodeTable * volume->blockBytes + + sectorInInodeTable * volume->fileSystem->block->sectorSize, + volume->fileSystem->block->sectorSize, + K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) { + MOUNT_FAILURE("Could not read the inode table.\n"); + } + + volume->fileSystem->rootDirectory->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!volume->fileSystem->rootDirectory->driverNode) { + MOUNT_FAILURE("Could not allocate root node.\n"); + } + + FSNode *root = (FSNode *) volume->fileSystem->rootDirectory->driverNode; + root->volume = volume; + EsMemoryCopy(&root->inode, sectorBuffer + offsetInSector, sizeof(Inode)); + + volume->fileSystem->rootDirectoryInitialChildren = root->inode.fileSizeLow / sizeof(DirectoryEntry); // TODO This is a terrible upper-bound! + + if ((root->inode.type & 0xF000) != INODE_TYPE_DIRECTORY) { + MOUNT_FAILURE("Root directory is not a directory.\n"); + } + } + + return true; +} + +static uint32_t GetDataBlock(Volume *volume, Inode *node, uint64_t blockIndex, uint8_t *blockBuffer) { +#define CHECK_BLOCK_INDEX() if (offset == 0 || offset / volume->fileSystem->block->sectorSize > volume->fileSystem->block->sectorCount) { \ + KernelLog(LOG_ERROR, "Ext2", "invalid block index", "GetDataBlock - Block out of bounds.\n"); return 0; } +#define GET_DATA_BLOCK_ACCESS_FAILURE() do { KernelLog(LOG_ERROR, "Ext2", "block access failure", "GetDataBlock - Could not read block.\n"); return 0; } while (0) + + size_t blockPointersPerBlock = volume->blockBytes / 4; + uint32_t *blockPointers = (uint32_t *) blockBuffer; + uint64_t offset; + + if (blockIndex < 12) { + offset = node->directBlockPointers[blockIndex]; + CHECK_BLOCK_INDEX(); + return offset; + } + + blockIndex -= 12; + + if (blockIndex < blockPointersPerBlock) { + offset = node->indirectBlockPointers[0] * volume->blockBytes; + CHECK_BLOCK_INDEX(); + if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE(); + offset = blockPointers[blockIndex]; + CHECK_BLOCK_INDEX(); + return offset; + } + + blockIndex -= blockPointersPerBlock; + + if (blockIndex < blockPointersPerBlock * blockPointersPerBlock) { + offset = node->indirectBlockPointers[1] * volume->blockBytes; + CHECK_BLOCK_INDEX(); + if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE(); + offset = blockPointers[blockIndex / blockPointersPerBlock]; + CHECK_BLOCK_INDEX(); + if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE(); + offset = blockPointers[blockIndex % blockPointersPerBlock]; + CHECK_BLOCK_INDEX(); + return offset; + } + + blockIndex -= blockPointersPerBlock * blockPointersPerBlock; + + if (blockIndex < blockPointersPerBlock * blockPointersPerBlock * blockPointersPerBlock) { + offset = node->indirectBlockPointers[2] * volume->blockBytes; + CHECK_BLOCK_INDEX(); + if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE(); + offset = blockPointers[blockIndex / blockPointersPerBlock / blockPointersPerBlock]; + CHECK_BLOCK_INDEX(); + if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE(); + offset = blockPointers[(blockIndex / blockPointersPerBlock) % blockPointersPerBlock]; + CHECK_BLOCK_INDEX(); + if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE(); + offset = blockPointers[blockIndex % blockPointersPerBlock]; + CHECK_BLOCK_INDEX(); + return offset; + } + + KernelLog(LOG_ERROR, "Ext2", "invalid index in inode", "GetDataBlock - Index %d out of bounds.\n", blockIndex); + return 0; +} + +static EsError Enumerate(KNode *node) { +#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "enumerate failure", "Enumerate - " message); return ES_ERROR_UNKNOWN; } while (0) + + FSNode *directory = (FSNode *) node->driverNode; + Volume *volume = directory->volume; + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED); + + if (!blockBuffer) { + ENUMERATE_FAILURE("Could not allocate buffer.\n"); + } + + EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED)); + + uint32_t blocksInDirectory = directory->inode.fileSizeLow / volume->blockBytes; + + for (uintptr_t i = 0; i < blocksInDirectory; i++) { + uint32_t block = GetDataBlock(volume, &directory->inode, i, blockBuffer); + + if (!block) { + return ES_ERROR_DRIVE_CONTROLLER_REPORTED; + } + + if (!volume->fileSystem->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) { + ENUMERATE_FAILURE("Could not read block.\n"); + } + + uintptr_t positionInBlock = 0; + + while (positionInBlock + sizeof(DirectoryEntry) < volume->blockBytes) { + DirectoryEntry *entry = (DirectoryEntry *) (blockBuffer + positionInBlock); + + if (entry->entrySize > volume->blockBytes - positionInBlock + || entry->nameLengthLow > volume->blockBytes - positionInBlock - sizeof(DirectoryEntry)) { + ENUMERATE_FAILURE("Invalid directory entry size.\n"); + } + + KNodeMetadata metadata = {}; + + const char *name = (const char *) (blockBuffer + positionInBlock + sizeof(DirectoryEntry)); + size_t nameBytes = entry->nameLengthLow; + + metadata.type = entry->type == DIRENT_TYPE_DIRECTORY ? ES_NODE_DIRECTORY : entry->type == DIRENT_TYPE_REGULAR ? ES_NODE_FILE : ES_NODE_INVALID; + + if (metadata.type == ES_NODE_DIRECTORY) { + metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN; + } + + if (metadata.type != ES_NODE_INVALID + && !(nameBytes == 1 && name[0] == '.') + && !(nameBytes == 2 && name[0] == '.' && name[1] == '.')) { + EsError error = FSDirectoryEntryFound(node, &metadata, &entry->inode, name, nameBytes, false); + + if (error != ES_SUCCESS) { + return error; + } + } + + positionInBlock += entry->entrySize; + } + } + + return ES_SUCCESS; +} + +static EsError Scan(const char *name, size_t nameBytes, KNode *_directory) { +#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "scan failure", "Scan - " message); return ES_ERROR_UNKNOWN; } while (0) + + if (nameBytes == 2 && name[0] == '.' && name[1] == '.') return ES_ERROR_FILE_DOES_NOT_EXIST; + if (nameBytes == 1 && name[0] == '.') return ES_ERROR_FILE_DOES_NOT_EXIST; + + FSNode *directory = (FSNode *) _directory->driverNode; + Volume *volume = directory->volume; + DirectoryEntry *entry = nullptr; + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED); + + if (!blockBuffer) { + SCAN_FAILURE("Could not allocate buffer.\n"); + } + + EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED)); + + uint32_t blocksInDirectory = directory->inode.fileSizeLow / volume->blockBytes; + uint32_t inode = 0; + + for (uintptr_t i = 0; i < blocksInDirectory; i++) { + uint32_t block = GetDataBlock(volume, &directory->inode, i, blockBuffer); + + if (!block) { + return ES_ERROR_UNKNOWN; + } + + if (!volume->fileSystem->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) { + SCAN_FAILURE("Could not read block.\n"); + } + + uintptr_t positionInBlock = 0; + + while (positionInBlock + sizeof(DirectoryEntry) < volume->blockBytes) { + entry = (DirectoryEntry *) (blockBuffer + positionInBlock); + + if (entry->entrySize > volume->blockBytes - positionInBlock + || entry->nameLengthLow > volume->blockBytes - positionInBlock - sizeof(DirectoryEntry)) { + SCAN_FAILURE("Invalid directory entry size.\n"); + } + + if (entry->nameLengthLow == nameBytes && 0 == EsMemoryCompare(name, blockBuffer + positionInBlock + sizeof(DirectoryEntry), nameBytes)) { + inode = entry->inode; + goto foundInode; + } + + positionInBlock += entry->entrySize; + } + } + + return ES_ERROR_FILE_DOES_NOT_EXIST; + + foundInode:; + + if (inode >= volume->superBlock.inodeCount || inode == 0) { + SCAN_FAILURE("Invalid inode index.\n"); + } + + KNodeMetadata metadata = {}; + + if (entry->type == DIRENT_TYPE_DIRECTORY) { + metadata.type = ES_NODE_DIRECTORY; + metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN; + } else if (entry->type == DIRENT_TYPE_REGULAR) { + metadata.type = ES_NODE_FILE; + } else { + SCAN_FAILURE("Unsupported file type.\n"); + } + + return FSDirectoryEntryFound(_directory, &metadata, &inode, name, nameBytes, false); +} + +static EsError Load(KNode *_directory, KNode *node, KNodeMetadata *metadata, const void *entryData) { + uint32_t inode = *(uint32_t *) entryData; + + FSNode *directory = (FSNode *) _directory->driverNode; + Volume *volume = directory->volume; + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED); + + if (!blockBuffer) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED)); + + uint32_t blockGroup = (inode - 1) / volume->superBlock.inodesPerBlockGroup; + uint32_t indexInInodeTable = (inode - 1) % volume->superBlock.inodesPerBlockGroup; + uint32_t sectorInInodeTable = (indexInInodeTable * volume->superBlock.inodeStructureBytes) / volume->fileSystem->block->sectorSize; + uint32_t offsetInSector = (indexInInodeTable * volume->superBlock.inodeStructureBytes) % volume->fileSystem->block->sectorSize; + + BlockGroupDescriptor *blockGroupDescriptor = volume->blockGroupDescriptorTable + blockGroup; + + if (!volume->fileSystem->Access(blockGroupDescriptor->inodeTable * volume->blockBytes + + sectorInInodeTable * volume->fileSystem->block->sectorSize, + volume->fileSystem->block->sectorSize, + K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) { + return ES_ERROR_DRIVE_CONTROLLER_REPORTED; + } + + FSNode *data = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!data) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + data->volume = volume; + node->driverNode = data; + + EsMemoryCopy(&data->inode, blockBuffer + offsetInSector, sizeof(Inode)); + + if ((data->inode.type & 0xF000) == INODE_TYPE_DIRECTORY) { + if (metadata->type != ES_NODE_DIRECTORY) { + EsHeapFree(data, sizeof(FSNode), K_FIXED); + return ES_ERROR_CORRUPT_DATA; + } + + metadata->directoryChildren = data->inode.fileSizeLow / sizeof(DirectoryEntry); // TODO This is a terrible upper-bound! + } else if ((data->inode.type & 0xF000) == INODE_TYPE_REGULAR) { + if (metadata->type != ES_NODE_FILE) { + EsHeapFree(data, sizeof(FSNode), K_FIXED); + return ES_ERROR_CORRUPT_DATA; + } + + metadata->totalSize = (uint64_t) data->inode.fileSizeLow | ((uint64_t) data->inode.fileSizeHigh << 32); + } else { + if (metadata->type != ES_NODE_INVALID) { + EsHeapFree(data, sizeof(FSNode), K_FIXED); + return ES_ERROR_CORRUPT_DATA; + } + } + + return ES_SUCCESS; +} + +struct ReadDispatchGroup : KWorkGroup { + uint64_t extentIndex; + uint64_t extentCount; + uint8_t *extentBuffer; + Volume *volume; + + void QueueExtent() { + if (!extentCount) return; + + volume->fileSystem->Access(extentIndex * volume->blockBytes, + volume->blockBytes * extentCount, K_ACCESS_READ, extentBuffer, ES_FLAGS_DEFAULT, this); + } + + void QueueBlock(Volume *_volume, uint64_t index, uint8_t *buffer) { + if (extentIndex + extentCount == index && extentCount + && extentBuffer + extentCount * volume->blockBytes == buffer) { + extentCount++; + } else { + QueueExtent(); + extentIndex = index; + extentCount = 1; + extentBuffer = buffer; + volume = _volume; + } + } + + bool Read() { + QueueExtent(); + return Wait(); + } +}; + +static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) { +#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0) + + FSNode *file = (FSNode *) node->driverNode; + Volume *volume = file->volume; + + uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED); + + if (!blockBuffer) { + READ_FAILURE("Could not allocate sector buffer.\n"); + } + + EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED)); + + uint8_t *outputBuffer = (uint8_t *) _buffer; + uintptr_t outputPosition = 0; + + uint32_t firstBlock = offset / volume->blockBytes, + lastBlock = (offset + count) / volume->blockBytes, + currentBlock = firstBlock; + + ReadDispatchGroup dispatchGroup = {}; + dispatchGroup.Initialise(); + + while (currentBlock <= lastBlock) { + uint32_t block = GetDataBlock(volume, &file->inode, currentBlock, blockBuffer); + + if (!block) { + return false; + } + + uintptr_t readStart = currentBlock == firstBlock ? (offset % volume->blockBytes) : 0; + uintptr_t readEnd = currentBlock == lastBlock ? ((offset + count) % volume->blockBytes) : volume->blockBytes; + + bool readEntireBlock = readStart == 0 && readEnd == volume->blockBytes; + + if (readEntireBlock) { + dispatchGroup.QueueBlock(volume, block, outputBuffer + outputPosition); + outputPosition += volume->blockBytes; + } else { + if (!volume->fileSystem->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) { + READ_FAILURE("Could not read blocks from drive.\n"); + } + + EsMemoryCopy(outputBuffer + outputPosition, blockBuffer + readStart, readEnd - readStart); + outputPosition += readEnd - readStart; + } + + currentBlock++; + } + + bool success = dispatchGroup.Read(); + + if (!success) { + READ_FAILURE("Could not read blocks from drive.\n"); + return false; + } + + return true; +} + +static void Close(KNode *node) { + EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED); +} + +static void DeviceAttach(KDevice *parent) { + Volume *volume = (Volume *) KDeviceCreate("ext2", parent, sizeof(Volume)); + + if (!volume) { + KernelLog(LOG_ERROR, "Ext2", "allocate error", "Could not allocate Volume structure.\n"); + return; + } + + volume->fileSystem = (KFileSystem *) parent; + + if (volume->fileSystem->block->sectorSize & 0x1FF) { + KernelLog(LOG_ERROR, "Ext2", "incorrect sector size", "Expected sector size to be a multiple of 512, but drive's sectors are %D.\n", + volume->fileSystem->block->sectorSize); + KDeviceDestroy(volume); + return; + } + + if (!Mount(volume)) { + KernelLog(LOG_ERROR, "Ext2", "mount failure", "Could not mount Ext2 volume.\n"); + EsHeapFree(volume->fileSystem->rootDirectory->driverNode, 0, K_FIXED); + EsHeapFree(volume->blockGroupDescriptorTable, 0, K_FIXED); + KDeviceDestroy(volume); + return; + } + + volume->fileSystem->read = Read; + volume->fileSystem->scan = Scan; + volume->fileSystem->load = Load; + volume->fileSystem->enumerate = Enumerate; + volume->fileSystem->close = Close; + + volume->fileSystem->spaceTotal = volume->superBlock.blockCount * volume->blockBytes; + volume->fileSystem->spaceUsed = (volume->superBlock.blockCount - volume->superBlock.unallocatedBlockCount) * volume->blockBytes; + + volume->fileSystem->nameBytes = sizeof(volume->superBlock.volumeName); + if (volume->fileSystem->nameBytes > sizeof(volume->fileSystem->name)) volume->fileSystem->nameBytes = sizeof(volume->fileSystem->name); + EsMemoryCopy(volume->fileSystem->name, volume->superBlock.volumeName, volume->fileSystem->nameBytes); + + for (uintptr_t i = 0; i < volume->fileSystem->nameBytes; i++) { + if (!volume->fileSystem->name[i]) { + volume->fileSystem->nameBytes = i; + } + } + + volume->fileSystem->directoryEntryDataBytes = sizeof(uint32_t); + volume->fileSystem->nodeDataBytes = sizeof(FSNode); + + KernelLog(LOG_INFO, "Ext2", "register file system", "Registering file system with name '%s'.\n", + volume->fileSystem->nameBytes, volume->fileSystem->name); + FSRegisterFileSystem(volume->fileSystem); +} + +KDriver driverExt2 = { + .attach = DeviceAttach, +}; diff --git a/drivers/fat.cpp b/drivers/fat.cpp new file mode 100644 index 0000000..ad267ca --- /dev/null +++ b/drivers/fat.cpp @@ -0,0 +1,514 @@ +// TODO Validation of all fields. +// TODO Don't load entire FAT in memory. +// TODO Long file names. + +#include + +#define SECTOR_SIZE (512) + +struct SuperBlockCommon { + uint8_t _unused0[11]; + uint16_t bytesPerSector; + uint8_t sectorsPerCluster; + uint16_t reservedSectors; + uint8_t fatCount; + uint16_t rootDirectoryEntries; + uint16_t totalSectors; + uint8_t mediaDescriptor; + uint16_t sectorsPerFAT16; + uint16_t sectorsPerTrack; + uint16_t heads; + uint32_t hiddenSectors; + uint32_t largeSectorCount; +} __attribute__((packed)); + +struct SuperBlock16 : SuperBlockCommon { + uint8_t deviceID; + uint8_t flags; + uint8_t signature; + uint32_t serial; + uint8_t label[11]; + uint64_t systemIdentifier; + uint8_t _unused1[450]; +} __attribute__((packed)); + +struct SuperBlock32 : SuperBlockCommon { + uint32_t sectorsPerFAT32; + uint16_t flags; + uint16_t version; + uint32_t rootDirectoryCluster; + uint16_t fsInfoSector; + uint16_t backupBootSector; + uint8_t _unused0[12]; + uint8_t deviceID; + uint8_t flags2; + uint8_t signature; + uint32_t serial; + uint8_t label[11]; + uint64_t systemIdentifier; + uint8_t _unused1[422]; +} __attribute__((packed)); + +struct DirectoryEntry { + uint8_t name[11]; + uint8_t attributes; + uint8_t _reserved0; + uint8_t creationTimeSeconds; + uint16_t creationTime; + uint16_t creationDate; + uint16_t accessedDate; + uint16_t firstClusterHigh; + uint16_t modificationTime; + uint16_t modificationDate; + uint16_t firstClusterLow; + uint32_t fileSizeBytes; +} __attribute__((packed)); + +struct Volume : KDevice { + KFileSystem *fileSystem; + + union { + char _unused0[SECTOR_SIZE]; + SuperBlock16 sb16; + SuperBlock32 sb32; + SuperBlockCommon superBlock; + }; + + KNode *root; + uint8_t *fat; + uintptr_t sectorOffset; + uint32_t terminateCluster; + +#define TYPE_FAT12 (12) +#define TYPE_FAT16 (16) +#define TYPE_FAT32 (32) + int type; + + DirectoryEntry *rootDirectory; +}; + +struct DirectoryEntryReference { + uint32_t cluster, offset; +}; + +struct FSNode { + Volume *volume; + DirectoryEntry entry; + + // The root directory is loaded during fileSystem mount. + // If this is non-null, run directory data from here. + DirectoryEntry *rootDirectory; +}; + +static uint32_t NextCluster(Volume *volume, uint32_t currentCluster) { + if (volume->type == TYPE_FAT12) { + uint8_t byte1 = volume->fat[currentCluster * 3 / 2 + 0], + byte2 = volume->fat[currentCluster * 3 / 2 + 1]; + + if (currentCluster & 1) currentCluster = (byte2 << 4) + (byte1 >> 4); + else currentCluster = (byte2 << 8) + (byte1 >> 0); + + return currentCluster & 0xFFF; + } else if (volume->type == TYPE_FAT16) { + return ((uint16_t *) volume->fat)[currentCluster]; + } else if (volume->type == TYPE_FAT32) { + return ((uint32_t *) volume->fat)[currentCluster]; + } else { + KernelPanic("[FAT] NextCluster - Unsupported FAT type.\n"); + return 0; + } +} + +static uint32_t CountUsedClusters(Volume *volume) { + size_t total = 0; + + if (volume->type == TYPE_FAT12) { + total = volume->sb16.sectorsPerFAT16 * volume->superBlock.bytesPerSector * 2 / 3; + } else if (volume->type == TYPE_FAT16) { + total = volume->sb16.sectorsPerFAT16 * volume->superBlock.bytesPerSector / 2; + } else if (volume->type == TYPE_FAT32) { + total = volume->sb16.sectorsPerFAT16 * volume->superBlock.bytesPerSector / 4; + } + + size_t count = 0; + + for (uintptr_t i = 0; i < total; i++) { + if (NextCluster(volume, i)) { + count++; + } + } + + return count; +} + +static EsError Load(KNode *_directory, KNode *_node, KNodeMetadata *, const void *entryData) { + FSNode *directory = (FSNode *) _directory->driverNode; + Volume *volume = directory->volume; + SuperBlockCommon *superBlock = &volume->superBlock; + + uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED); + if (!clusterBuffer) return ES_ERROR_INSUFFICIENT_RESOURCES; + EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED)); + + DirectoryEntryReference reference = *(DirectoryEntryReference *) entryData; + DirectoryEntry entry; + + if (!directory->rootDirectory) { + if (!volume->fileSystem->Access((reference.cluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE, + superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) { + return ES_ERROR_UNKNOWN; + } + + entry = *(DirectoryEntry *) (clusterBuffer + reference.offset); + } else { + entry = directory->rootDirectory[reference.offset]; + } + + FSNode *node = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!node) { + EsHeapFree(node, 0, K_FIXED); + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + _node->driverNode = node; + node->volume = volume; + node->entry = entry; + + return ES_SUCCESS; +} + +static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) { +#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0) + + FSNode *file = (FSNode *) node->driverNode; + Volume *volume = file->volume; + SuperBlockCommon *superBlock = &volume->superBlock; + + uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED); + EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED)); + if (!clusterBuffer) READ_FAILURE("Could not allocate cluster buffer.\n"); + + uint8_t *outputBuffer = (uint8_t *) _buffer; + uint64_t firstCluster = offset / (SECTOR_SIZE * superBlock->sectorsPerCluster); + uint32_t currentCluster = file->entry.firstClusterLow + (file->entry.firstClusterHigh << 16); + for (uintptr_t i = 0; i < firstCluster; i++) currentCluster = NextCluster(volume, currentCluster); + offset %= (SECTOR_SIZE * superBlock->sectorsPerCluster); + + while (count) { + uint32_t bytesFromThisCluster = superBlock->sectorsPerCluster * SECTOR_SIZE - offset; + if (bytesFromThisCluster > count) bytesFromThisCluster = count; + + if (!volume->fileSystem->Access((currentCluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE, + superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ, + (uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) { + READ_FAILURE("Could not read cluster.\n"); + } + + EsMemoryCopy(outputBuffer, clusterBuffer + offset, bytesFromThisCluster); + count -= bytesFromThisCluster, outputBuffer += bytesFromThisCluster, offset = 0; + currentCluster = NextCluster(volume, currentCluster); + } + + return true; +} + +static EsError Scan(const char *_name, size_t nameLength, KNode *node) { +#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "scan failure", "Scan - " message); return ES_ERROR_UNKNOWN; } while (0) +#define SCAN_FAILURE_2(message) do { KernelLog(LOG_ERROR, "FAT", "scan failure", "Scan - " message); goto failure; } while (0) + + uint8_t name[] = " "; + + { + uintptr_t i = 0, j = 0; + bool inExtension = false; + + while (i < nameLength) { + if (j == 11) return ES_ERROR_FILE_DOES_NOT_EXIST; // Name too long. + uint8_t c = _name[i++]; + if (c == '.' && !inExtension) j = 8, inExtension = true; + else name[j++] = (c >= 'a' && c <= 'z') ? (c + 'A' - 'a') : c; + } + } + + FSNode *directory = (FSNode *) node->driverNode; + Volume *volume = directory->volume; + SuperBlockCommon *superBlock = &volume->superBlock; + + uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED); + EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED)); + if (!clusterBuffer) SCAN_FAILURE("Could not allocate cluster buffer.\n"); + + uint32_t currentCluster = directory->entry.firstClusterLow + (directory->entry.firstClusterHigh << 16); + uintptr_t directoryPosition = 0; + + while (currentCluster < volume->terminateCluster) { + if (!directory->rootDirectory) { + if (!volume->fileSystem->Access((currentCluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE, + superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) { + SCAN_FAILURE("Could not read cluster.\n"); + } + } + + for (uintptr_t i = 0; i < superBlock->sectorsPerCluster * SECTOR_SIZE / sizeof(DirectoryEntry); i++, directoryPosition++) { + DirectoryEntry *entry = directory->rootDirectory ? (directory->rootDirectory + directoryPosition) : ((DirectoryEntry *) clusterBuffer + i); + if (entry->name[0] == 0xE5 || entry->attributes == 0x0F || (entry->attributes & 8)) goto nextEntry; + if (!entry->name[0]) return ES_ERROR_FILE_DOES_NOT_EXIST; + + for (uintptr_t j = 0; j < 11; j++) { + uint8_t c = entry->name[j]; + + if (name[j] != ((c >= 'a' && c <= 'z') ? (c + 'A' - 'a') : c)) { + goto nextEntry; + } + } + + { + KNodeMetadata metadata = {}; + metadata.type = (entry->attributes & 0x10) ? ES_NODE_DIRECTORY : ES_NODE_FILE; + + if (metadata.type == ES_NODE_FILE) { + metadata.totalSize = entry->fileSizeBytes; + } else if (metadata.type == ES_NODE_DIRECTORY) { + uint32_t currentCluster = entry->firstClusterLow + (entry->firstClusterHigh << 16); + + while (currentCluster < volume->terminateCluster) { + currentCluster = NextCluster(volume, currentCluster); + metadata.directoryChildren += SECTOR_SIZE * superBlock->sectorsPerCluster / sizeof(DirectoryEntry); + } + } + + DirectoryEntryReference reference = {}; + reference.cluster = directory->rootDirectory ? 0 : currentCluster; + reference.offset = directory->rootDirectory ? directoryPosition : i; + return FSDirectoryEntryFound(node, &metadata, &reference, _name, nameLength, false); + } + + nextEntry:; + } + + if (!directory->rootDirectory) { + currentCluster = NextCluster(volume, currentCluster); + } + } + + return ES_ERROR_FILE_DOES_NOT_EXIST; +} + +static EsError Enumerate(KNode *node) { +#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "enumerate failure", "Enumerate - " message); return ES_ERROR_UNKNOWN; } while (0) + + FSNode *directory = (FSNode *) node->driverNode; + Volume *volume = directory->volume; + SuperBlockCommon *superBlock = &volume->superBlock; + + uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED); + EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED)); + if (!clusterBuffer) ENUMERATE_FAILURE("Could not allocate cluster buffer.\n"); + + uint32_t currentCluster = directory->entry.firstClusterLow + (directory->entry.firstClusterHigh << 16); + uint64_t directoryPosition = 0; + + while (currentCluster < volume->terminateCluster) { + if (!directory->rootDirectory) { + if (!volume->fileSystem->Access((currentCluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE, + superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) { + ENUMERATE_FAILURE("Could not read cluster.\n"); + } + } + + for (uintptr_t i = 0; i < superBlock->sectorsPerCluster * SECTOR_SIZE / sizeof(DirectoryEntry); i++, directoryPosition++) { + DirectoryEntry *entry = directory->rootDirectory ? (directory->rootDirectory + directoryPosition) : ((DirectoryEntry *) clusterBuffer + i); + if (entry->name[0] == 0xE5 || entry->attributes == 0x0F || (entry->attributes & 8)) continue; + + if (!entry->name[0]) { + return ES_SUCCESS; + } + + uint8_t name[12]; + size_t nameLength = 0; + bool hasExtension = entry->name[8] != ' ' || entry->name[9] != ' ' || entry->name[10] != ' '; + + if (entry->name[0] == '.' && (entry->name[1] == '.' || entry->name[1] == ' ') && entry->name[2] == ' ') { + continue; + } + + for (uintptr_t i = 0; i < 11; i++) { + if (i == 8 && hasExtension) name[nameLength++] = '.'; + if (entry->name[i] != ' ') name[nameLength++] = entry->name[i]; + } + + KNodeMetadata metadata = {}; + + metadata.type = (entry->attributes & 0x10) ? ES_NODE_DIRECTORY : ES_NODE_FILE; + + if (metadata.type == ES_NODE_DIRECTORY) { + metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN; + } else if (metadata.type == ES_NODE_FILE) { + metadata.totalSize = entry->fileSizeBytes; + } + + DirectoryEntryReference reference = {}; + reference.cluster = directory->rootDirectory ? 0 : currentCluster; + reference.offset = directory->rootDirectory ? directoryPosition : i; + + EsError error = FSDirectoryEntryFound(node, &metadata, &reference, + (const char *) name, nameLength, false); + + if (error != ES_SUCCESS) { + return error; + } + } + + if (!directory->rootDirectory) { + currentCluster = NextCluster(volume, currentCluster); + } + } + + return ES_SUCCESS; +} + +static bool Mount(Volume *volume) { +#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "mount failure", "Mount - " message); goto failure; } while (0) + + { + SuperBlockCommon *superBlock = &volume->superBlock; + if (!volume->fileSystem->Access(0, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) superBlock, ES_FLAGS_DEFAULT)) MOUNT_FAILURE("Could not read superBlock.\n"); + + uint32_t sectorCount = superBlock->totalSectors ?: superBlock->largeSectorCount; + uint32_t clusterCount = sectorCount / superBlock->sectorsPerCluster; + uint32_t sectorsPerFAT = 0; + + if (clusterCount < 0x00000FF5) { + volume->type = TYPE_FAT12; + volume->terminateCluster = 0xFF8; + sectorsPerFAT = volume->sb16.sectorsPerFAT16; + } else if (clusterCount < 0x0000FFF5) { + volume->type = TYPE_FAT16; + volume->terminateCluster = 0xFFF8; + sectorsPerFAT = volume->sb16.sectorsPerFAT16; + } else if (clusterCount < 0x0FFFFFF5) { + volume->type = TYPE_FAT32; + volume->terminateCluster = 0xFFFFFF8; + sectorsPerFAT = volume->sb32.sectorsPerFAT32; + } else { + MOUNT_FAILURE("Unsupported cluster count. Maybe ExFAT?\n"); + } + + uint32_t rootDirectoryOffset = superBlock->reservedSectors + superBlock->fatCount * sectorsPerFAT; + uint32_t rootDirectorySectors = (superBlock->rootDirectoryEntries * sizeof(DirectoryEntry) + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + + volume->sectorOffset = rootDirectoryOffset + rootDirectorySectors - 2 * superBlock->sectorsPerCluster; + + volume->fat = (uint8_t *) EsHeapAllocate(sectorsPerFAT * SECTOR_SIZE, true, K_FIXED); + if (!volume->fat) MOUNT_FAILURE("Could not allocate FAT.\n"); + if (!volume->fileSystem->Access(superBlock->reservedSectors * SECTOR_SIZE, + sectorsPerFAT * SECTOR_SIZE, K_ACCESS_READ, volume->fat, ES_FLAGS_DEFAULT)) MOUNT_FAILURE("Could not read FAT.\n"); + + volume->fileSystem->spaceUsed = CountUsedClusters(volume) * superBlock->sectorsPerCluster * superBlock->bytesPerSector; + volume->fileSystem->spaceTotal = volume->fileSystem->block->sectorSize * volume->fileSystem->block->sectorCount; + + volume->fileSystem->rootDirectory->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + if (!volume->fileSystem->rootDirectory->driverNode) MOUNT_FAILURE("Could not allocate root node.\n"); + + FSNode *root = (FSNode *) volume->fileSystem->rootDirectory->driverNode; + root->volume = volume; + + if (volume->type == TYPE_FAT32) { + root->entry.firstClusterLow = volume->sb32.rootDirectoryCluster & 0xFFFF; + root->entry.firstClusterHigh = (volume->sb32.rootDirectoryCluster >> 16) & 0xFFFF; + + uint32_t currentCluster = volume->sb32.rootDirectoryCluster; + + while (currentCluster < volume->terminateCluster) { + currentCluster = NextCluster(volume, currentCluster); + volume->fileSystem->rootDirectoryInitialChildren += SECTOR_SIZE * superBlock->sectorsPerCluster / sizeof(DirectoryEntry); + } + } else { + root->rootDirectory = (DirectoryEntry *) EsHeapAllocate(rootDirectorySectors * SECTOR_SIZE, true, K_FIXED); + volume->rootDirectory = root->rootDirectory; + + if (!volume->fileSystem->Access(rootDirectoryOffset * SECTOR_SIZE, rootDirectorySectors * SECTOR_SIZE, + K_ACCESS_READ, (uint8_t *) root->rootDirectory, ES_FLAGS_DEFAULT)) { + MOUNT_FAILURE("Could not read root directory.\n"); + } + + for (uintptr_t i = 0; i < superBlock->rootDirectoryEntries; i++) { + if (root->rootDirectory[i].name[0] == 0xE5 || root->rootDirectory[i].attributes == 0x0F || (root->rootDirectory[i].attributes & 0x08)) continue; + else if (root->rootDirectory[i].name[0] == 0x00) break; + else volume->fileSystem->rootDirectoryInitialChildren++; + } + } + + return true; + } + + failure: + if (volume->root && volume->root->driverNode) EsHeapFree(((FSNode *) volume->root->driverNode)->rootDirectory, 0, K_FIXED); + if (volume->root) EsHeapFree(volume->root->driverNode, 0, K_FIXED); + EsHeapFree(volume->root, 0, K_FIXED); + EsHeapFree(volume->fat, 0, K_FIXED); + return false; +} + +static void Close(KNode *node) { + EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED); +} + +static void DeviceAttach(KDevice *parent) { + Volume *volume = (Volume *) KDeviceCreate("FAT", parent, sizeof(Volume)); + + if (!volume) { + KernelLog(LOG_ERROR, "FAT", "allocate error", "EntryFAT - Could not allocate Volume structure.\n"); + return; + } + + volume->fileSystem = (KFileSystem *) parent; + + if (!volume->fileSystem) { + KernelLog(LOG_ERROR, "FAT", "device error", "EntryFAT - Could not create file system device.\n"); + KDeviceDestroy(volume); + return; + } + + if (volume->fileSystem->block->sectorSize != SECTOR_SIZE) { + KernelLog(LOG_ERROR, "FAT", "mount failure", "EntryFAT - Unsupported sector size.\n"); + KDeviceDestroy(volume); + return; + } + + if (!Mount(volume)) { + KernelLog(LOG_ERROR, "FAT", "mount failure", "EntryFAT - Could not mount FAT volume.\n"); + KDeviceDestroy(volume); + return; + } + + volume->fileSystem->read = Read; + volume->fileSystem->load = Load; + volume->fileSystem->scan = Scan; + volume->fileSystem->enumerate = Enumerate; + volume->fileSystem->close = Close; + + if (volume->type == TYPE_FAT32) { + volume->fileSystem->nameBytes = sizeof(volume->sb32.label); + EsMemoryCopy(volume->fileSystem->name, volume->sb32.label, volume->fileSystem->nameBytes); + } else { + if (volume->rootDirectory[0].attributes & 8) { + volume->fileSystem->nameBytes = sizeof(volume->rootDirectory[0].name); + EsMemoryCopy(volume->fileSystem->name, volume->rootDirectory[0].name, volume->fileSystem->nameBytes); + } else { + volume->fileSystem->nameBytes = sizeof(volume->sb16.label); + EsMemoryCopy(volume->fileSystem->name, volume->sb16.label, volume->fileSystem->nameBytes); + } + } + + volume->fileSystem->rootDirectory->driverNode = volume->root; + volume->fileSystem->directoryEntryDataBytes = sizeof(DirectoryEntryReference); + volume->fileSystem->nodeDataBytes = sizeof(FSNode); + + FSRegisterFileSystem(volume->fileSystem); +} + +KDriver driverFAT = { + .attach = DeviceAttach, +}; diff --git a/drivers/hda.cpp b/drivers/hda.cpp new file mode 100644 index 0000000..b11f79b --- /dev/null +++ b/drivers/hda.cpp @@ -0,0 +1,520 @@ +#include + +#define RD_REGISTER_GCAP() controller->pci->ReadBAR16(0, 0x00) // Global capabilities. +#define RD_REGISTER_VMIN() controller->pci->ReadBAR8(0, 0x02) // Minor version number. +#define RD_REGISTER_VMAJ() controller->pci->ReadBAR8(0, 0x03) // Major version number. +#define RD_REGISTER_GCTL() controller->pci->ReadBAR32(0, 0x08) // Global control. +#define WR_REGISTER_GCTL(x) controller->pci->WriteBAR32(0, 0x08, x) +#define RD_REGISTER_STATESTS() controller->pci->ReadBAR16(0, 0x0E) // State change status. +#define RD_REGISTER_INTCTL() controller->pci->ReadBAR32(0, 0x20) // Interrupt control. +#define WR_REGISTER_INTCTL(x) controller->pci->WriteBAR32(0, 0x20, x) +#define RD_REGISTER_INTSTS() controller->pci->ReadBAR32(0, 0x24) // Interrupt status. +#define WR_REGISTER_CORBLBASE(x) controller->pci->WriteBAR32(0, 0x40, x) // CORB base address. +#define WR_REGISTER_CORBUBASE(x) controller->pci->WriteBAR32(0, 0x44, x) +#define RD_REGISTER_CORBWP(x) controller->pci->ReadBAR16(0, 0x48) // CORB write pointer. +#define WR_REGISTER_CORBWP(x) controller->pci->WriteBAR16(0, 0x48, x) +#define RD_REGISTER_CORBRP(x) controller->pci->ReadBAR16(0, 0x4A) // CORB read pointer. +#define WR_REGISTER_CORBRP(x) controller->pci->WriteBAR16(0, 0x4A, x) +#define RD_REGISTER_CORBCTL() controller->pci->ReadBAR8(0, 0x4C) // CORB control. +#define WR_REGISTER_CORBCTL(x) controller->pci->WriteBAR8(0, 0x4C, x) +#define RD_REGISTER_CORBSIZE() controller->pci->ReadBAR8(0, 0x4E) // CORB size. +#define WR_REGISTER_CORBSIZE(x) controller->pci->WriteBAR8(0, 0x4E, x) +#define WR_REGISTER_RIRBLBASE(x) controller->pci->WriteBAR32(0, 0x50, x) // RIRB base address. +#define WR_REGISTER_RIRBUBASE(x) controller->pci->WriteBAR32(0, 0x54, x) +#define RD_REGISTER_RIRBWP(x) controller->pci->ReadBAR16(0, 0x58) // RIRB write pointer. +#define WR_REGISTER_RIRBWP(x) controller->pci->WriteBAR16(0, 0x58, x) +#define RD_REGISTER_RINTCNT() controller->pci->ReadBAR16(0, 0x5A) // Response interrupt count. +#define WR_REGISTER_RINTCNT(x) controller->pci->WriteBAR16(0, 0x5A, x) +#define RD_REGISTER_RIRBCTL() controller->pci->ReadBAR8(0, 0x5C) // RIRB control. +#define WR_REGISTER_RIRBCTL(x) controller->pci->WriteBAR8(0, 0x5C, x) +#define RD_REGISTER_RIRBSTS() controller->pci->ReadBAR8(0, 0x5D) // RIRB status. +#define WR_REGISTER_RIRBSTS(x) controller->pci->WriteBAR8(0, 0x5D, x) +#define RD_REGISTER_RIRBSIZE() controller->pci->ReadBAR8(0, 0x5E) // RIRB size. +#define WR_REGISTER_RIRBSIZE(x) controller->pci->WriteBAR8(0, 0x5E, x) +#define WR_REGISTER_ImmComOut(x) controller->pci->WriteBAR32(0, 0x60, x) // Immediate command output. +#define RD_REGISTER_ImmComIn() controller->pci->ReadBAR32(0, 0x64) // Immediate command input. +#define RD_REGISTER_ImmComStat() controller->pci->ReadBAR16(0, 0x68) // Immediate command status. +#define WR_REGISTER_ImmComStat(x) controller->pci->WriteBAR16(0, 0x68, x) +#define RD_REGISTER_SDCTL(n) controller->pci->ReadBAR32(0, 0x80 + 0x20 * (n)) // Stream descriptor control. +#define WR_REGISTER_SDCTL(n, x) controller->pci->WriteBAR32(0, 0x80 + 0x20 * (n), x) +#define RD_REGISTER_SDSTS(n) controller->pci->ReadBAR8(0, 0x83 + 0x20 * (n)) // Stream descriptor status. +#define WR_REGISTER_SDSTS(n, x) controller->pci->WriteBAR8(0, 0x83 + 0x20 * (n), x) +#define RD_REGISTER_SDLPIB(n) controller->pci->ReadBAR32(0, 0x84 + 0x20 * (n)) // Stream descriptor link position in cyclic buffer. +#define WR_REGISTER_SDCBL(n, x) controller->pci->WriteBAR32(0, 0x88 + 0x20 * (n), x) // Stream descriptor cyclic buffer length. +#define RD_REGISTER_SDLVI(n) controller->pci->ReadBAR16(0, 0x8C + 0x20 * (n)) // Stream descriptor last valid index. +#define WR_REGISTER_SDLVI(n, x) controller->pci->WriteBAR16(0, 0x8C + 0x20 * (n), x) +#define RD_REGISTER_SDFMT(n) controller->pci->ReadBAR16(0, 0x92 + 0x20 * (n)) // Stream descriptor format. +#define WR_REGISTER_SDFMT(n, x) controller->pci->WriteBAR16(0, 0x92 + 0x20 * (n), x) +#define WR_REGISTER_SDBDPL(n, x) controller->pci->WriteBAR32(0, 0x98 + 0x20 * (n), x) // Stream descriptor BDL pointer lower base address. +#define WR_REGISTER_SDBDPU(n, x) controller->pci->WriteBAR32(0, 0x9C + 0x20 * (n), x) // Stream descriptor BDL pointer upper base address. + +#define READ_PARAMETER_VENDOR_ID (0xF0000) +#define READ_PARAMETER_REVISION_ID (0xF0002) +#define READ_PARAMETER_CHILD_NODES (0xF0004) +#define READ_PARAMETER_FUNCTION_GROUP_TYPE (0xF0005) +#define READ_PARAMETER_AUDIO_FUNCTION_CAPABILITIES (0xF0008) +#define READ_PARAMETER_AUDIO_WIDGET_CAPABILITIES (0xF0009) +#define READ_PARAMETER_FORMAT_CAPABILITIES (0xF000A) +#define READ_PARAMETER_STREAM_FORMATS (0xF000B) +#define READ_PARAMETER_PIN_CAPABILITIES (0xF000C) +#define READ_PARAMETER_INPUT_AMP_CAPABILITIES (0xF000D) +#define READ_PARAMETER_CONNECTION_LIST_LENGTH (0xF000E) +#define READ_PARAMETER_OUTPUT_AMP_CAPABILITIES (0xF0012) + +#define COMMAND_GET_CONNECTION_LIST_ENTRY(offset) (0xF0200 | (offset)) +#define COMMAND_SET_CONNECTION_SELECT(index) (0x70100 | (index)) +#define COMMAND_GET_AMPLIFIER_GAIN_MUTE(out, left, index) (0xB0000 | ((out) ? (1 << 15) : 0) | ((left) ? (1 << 13) : 0) | (index)) +#define COMMAND_SET_AMPLIFIER_GAIN_MUTE(out, left, index, mute, gain) (0x30000 | ((out) ? (1 << 15) : (1 << 14)) | ((left) ? (1 << 13) : (1 << 12)) \ + | ((index) << 8) | ((mute) ? (1 << 7) : 0) | (gain)) +#define COMMAND_SET_CONVERTER_FORMAT(format) (0x20000 | (format)) +#define COMMAND_SET_STREAM_NUMBER(stream, channel) (0x70600 | ((stream) << 4) | ((channel) << 0)) +#define COMMAND_GET_PIN_WIDGET_CONTROL() (0xF0700) +#define COMMAND_SET_PIN_WIDGET_CONTROL(control) (0x70700 | (control)) +#define COMMAND_PIN_SENSE() (0xF0900) +#define COMMAND_GET_PIN_CONFIGURATION() (0xF1C00) +#define COMMAND_RESET() (0x7FF00) + +#define HDA_WIDGET_AUDIO_OUTPUT (0) +#define HDA_WIDGET_AUDIO_INPUT (1) +#define HDA_WIDGET_PIN_COMPLEX (4) + +#define PIN_MAYBE_CONNECTED (0) +#define PIN_UNCONNECTED (1) +#define PIN_CONNECTED (2) + +struct HDAWidget : KDevice { + uint32_t codec; + uint32_t node; + uint32_t functionGroup; + uint32_t type; + +#define MAXIMUM_INPUTS (32) + uint8_t inputs[MAXIMUM_INPUTS]; + + union { + struct { + uint32_t pinCapabilities, pinConfiguration; + uint8_t pinIsConnected; + bool pinIsInput, pinIsOutput; + }; + }; +}; + +struct HDAController : KDevice { + KPCIDevice *pci; + + size_t outputStreamsSupported; + size_t inputStreamsSupported; + size_t bidirectionalStreamsSupported; + + size_t corbEntries, rirbEntries; + uint8_t *corbVirtual, *rirbVirtual; + uintptr_t corbPhysical, rirbPhysical; + uintptr_t corbWritePointer, rirbReadPointer; + uint32_t rirbLastSolicitedResponse; + KEvent rirbReceivedSolicitedResponse; +}; + +static const char *const widgetTypeStrings[] = { + "Audio output", + "Audio input", + "Audio mixer", + "Audio selector", + "Pin complex", + "Power widget", + "Volume knob", + "Beep generator", +}; + +static const char *const portConnectivityStrings[] = { + "jack", + "none", + "integrated", + "jack and integrated", +}; + +static const char *const locationHighStrings[] = { + "external", + "internal", + "separate", + "other", +}; + +static const char *const locationLowStrings[] = { + "??", + "rear", + "front", + "left", + "right", + "top", + "bottom", + "special", + "special", + "special", + "??", + "??", + "??", + "??", + "??", + "??", +}; + +static const char *const defaultDeviceStrings[] = { + "line out", + "speaker", + "HP out", + "CD", + "SPDIF out", + "digital other out", + "modem line side", + "modem handset side", + "line in", + "AUX", + "microphone in", + "telephony", + "SPDIF in", + "digital other in", + "??", + "??", +}; + +static const char *const connectionTypeStrings[] = { + "unknown", + "1/8\" stereo/mono", + "1/4\" stereo/mono", + "ATAPI internal", + "RCA", + "optical", + "other digital", + "other analog", + "multichannel analog (DIN)", + "XLR/Professional", + "RJ-11 (Modem)", + "combination", + "??", + "??", + "??", + "??", +}; + +static const char *const colorStrings[] = { + "unknown", + "black", + "grey", + "blue", + "green", + "red", + "orange", + "yellow", + "purple", + "pink", + "??", + "??", + "??", + "??", + "white", + "other", +}; + +static bool HDAControllerSendCommand(HDAController *controller, uint32_t codec, uint32_t node, uint32_t data, uint32_t *response) { + // TODO Test wrap-around. + + uint32_t command = (codec << 28) | (node << 20) | data; + uintptr_t corbWritePointer = controller->corbWritePointer; + corbWritePointer = (corbWritePointer + 1) % controller->corbEntries; + ((volatile uint32_t *) controller->corbVirtual)[corbWritePointer] = command; + WR_REGISTER_CORBWP(ES_ISOLATE_BITS(RD_REGISTER_CORBWP(), 15, 8) | corbWritePointer); + controller->corbWritePointer = corbWritePointer; + if (!KEventWait(&controller->rirbReceivedSolicitedResponse, 500 /* half a second timeout */)) return false; + if (response) *response = controller->rirbLastSolicitedResponse; + return true; +} + +static bool HDAControllerHandleIRQ(uintptr_t, void *context) { + HDAController *controller = (HDAController *) context; + uint32_t interruptStatus = RD_REGISTER_INTSTS(); + + if (~interruptStatus & (1 << 31 /* global interrupt status */)) { + return false; + } + + if (interruptStatus & (1 << 30)) { + uint8_t rirbStatus = RD_REGISTER_RIRBSTS(); + WR_REGISTER_RIRBSTS(rirbStatus); + + if (rirbStatus & (1 << 0 /* response interrupt */)) { + uint8_t rirbWritePointer = RD_REGISTER_RIRBWP(); + + while (controller->rirbReadPointer != rirbWritePointer) { + controller->rirbReadPointer = (controller->rirbReadPointer + 1) % controller->rirbEntries; + uint32_t response = ((volatile uint32_t *) controller->rirbVirtual)[controller->rirbReadPointer * 2 + 0]; + uint32_t extended = ((volatile uint32_t *) controller->rirbVirtual)[controller->rirbReadPointer * 2 + 1]; + + if (~extended & (1 << 4)) { + controller->rirbLastSolicitedResponse = response; + KEventSet(&controller->rirbReceivedSolicitedResponse); + } + } + } + + } + + return true; +} + +static void HDAControllerExploreFunctionGroup(HDAController *controller, uint32_t codec, uint32_t functionGroupNode) { + uint32_t type, childNodeCount; + + if (!HDAControllerSendCommand(controller, codec, functionGroupNode, READ_PARAMETER_FUNCTION_GROUP_TYPE, &type) + || !HDAControllerSendCommand(controller, codec, functionGroupNode, READ_PARAMETER_CHILD_NODES, &childNodeCount)) { + return; + } + + uint32_t firstChildNode = ES_EXTRACT_BITS(childNodeCount, 23, 16); + childNodeCount = ES_EXTRACT_BITS(childNodeCount, 7, 0); + type = ES_EXTRACT_BITS(type, 7, 0); + + KernelLog(LOG_INFO, "HDA", "found function group", "Found function group with type %d (%z), and child nodes %d to %d.\n", + type, type == 1 ? "audio" : type == 2 ? "modem" : "??", firstChildNode, firstChildNode + childNodeCount - 1); + + for (uintptr_t j = firstChildNode; j < firstChildNode + childNodeCount; j++) { + uint32_t widgetCapabilities; + + if (!HDAControllerSendCommand(controller, codec, j, READ_PARAMETER_AUDIO_WIDGET_CAPABILITIES, &widgetCapabilities)) { + continue; + } + + uint32_t widgetType = ES_EXTRACT_BITS(widgetCapabilities, 23, 20); + + HDAWidget *widget = (HDAWidget *) KDeviceCreate("HD Audio widget", controller, sizeof(HDAWidget)); + widget->codec = codec; + widget->node = j; + widget->functionGroup = functionGroupNode; + widget->type = widgetType; + + KernelLog(LOG_INFO, "HDA", "found widget", "Widget at node %d has type \"%z\".\n", + widget->node, widgetType >= sizeof(widgetTypeStrings) / sizeof(widgetTypeStrings[0]) ? "Other" : widgetTypeStrings[widgetType]); + + if (widgetCapabilities & (1 << 8)) { + uint32_t connectionListLength, connectionList; + + if (HDAControllerSendCommand(controller, codec, widget->node, READ_PARAMETER_CONNECTION_LIST_LENGTH, &connectionListLength) + && (~connectionListLength & (1 << 7) /* long form not supported */) + && ES_EXTRACT_BITS(connectionListLength, 6, 0)) { + uintptr_t index = 0; + + for (uintptr_t command = 0; command < (connectionListLength + 3) / 4; command++) { + if (!HDAControllerSendCommand(controller, codec, widget->node, COMMAND_GET_CONNECTION_LIST_ENTRY(command * 4), &connectionList)) { + break; + } + + for (uintptr_t i = 0; i < (connectionListLength - command * 4) && index < MAXIMUM_INPUTS; i++) { + uint8_t entry = connectionList >> (i * 8); + + if ((entry & 0x80) && index) { + for (uintptr_t node = widget->inputs[index - 1]; node <= (entry & 0x7F) && index < MAXIMUM_INPUTS; node++) { + widget->inputs[index++] = node; + } + } else { + widget->inputs[index++] = entry; + } + } + } + } + } + + for (uintptr_t i = 0; i < MAXIMUM_INPUTS; i++) { + if (!widget->inputs[i]) break; + KernelLog(LOG_INFO, "HDA", "widget connection", "Widget %d has possible input %d.\n", widget->node, widget->inputs[i]); + } + } + + for (uintptr_t i = 0; i < controller->children.Length(); i++) { + HDAWidget *widget = (HDAWidget *) controller->children[i]; + + if (widget->type == HDA_WIDGET_PIN_COMPLEX) { + if (!HDAControllerSendCommand(controller, codec, widget->node, READ_PARAMETER_PIN_CAPABILITIES, &widget->pinCapabilities) + || !HDAControllerSendCommand(controller, codec, widget->node, COMMAND_GET_PIN_CONFIGURATION(), &widget->pinConfiguration)) { + continue; + } + + widget->pinIsOutput = widget->pinCapabilities & (1 << 4); + widget->pinIsInput = widget->pinCapabilities & (1 << 5); + + KernelLog(LOG_INFO, "HDA", "pin information", "Pin %d has capabilities %x and configuration %x.%z%z\n", + widget->node, widget->pinCapabilities, widget->pinConfiguration, + widget->pinIsOutput ? " Output." : "", widget->pinIsInput ? " Input." : ""); + + if (!widget->pinIsOutput && !widget->pinIsInput) { + continue; + } + + uint32_t portConnectivity = ES_EXTRACT_BITS(widget->pinConfiguration, 31, 30); + uint32_t location = ES_EXTRACT_BITS(widget->pinConfiguration, 29, 24); + uint32_t defaultDevice = ES_EXTRACT_BITS(widget->pinConfiguration, 23, 20); + uint32_t connectionType = ES_EXTRACT_BITS(widget->pinConfiguration, 19, 16); + uint32_t color = ES_EXTRACT_BITS(widget->pinConfiguration, 15, 12); + + KernelLog(LOG_INFO, "HDA", "pin information", "Connectivity: %z; location: %z %z; default device: %z; connection type: %z; color: %z.\n", + portConnectivityStrings[portConnectivity], + locationHighStrings[ES_EXTRACT_BITS(location, 5, 4)], locationLowStrings[ES_EXTRACT_BITS(location, 3, 0)], + defaultDeviceStrings[defaultDevice], connectionTypeStrings[connectionType], colorStrings[color]); + + if (widget->pinCapabilities & (1 << 2)) { + uint32_t pinSense; + + if (HDAControllerSendCommand(controller, codec, widget->node, COMMAND_PIN_SENSE(), &pinSense)) { + widget->pinIsConnected = (pinSense & (1 << 31)) ? PIN_CONNECTED : PIN_UNCONNECTED; + KernelLog(LOG_INFO, "HDA", "pin sense", "Pin sense: %z.\n", widget->pinIsConnected == PIN_CONNECTED ? "connected" : "unconnected"); + } + } + + // TODO Register the device with the audio subsystem. + } + } +} + +static void HDAControllerDestroy(KDevice *_controller) { + HDAController *controller = (HDAController *) _controller; + if (controller->corbVirtual) MMPhysicalFreeAndUnmap(controller->corbVirtual, controller->corbPhysical); + if (controller->rirbVirtual) MMPhysicalFreeAndUnmap(controller->rirbVirtual, controller->rirbPhysical); + // TODO Unregister interrupt handler. +} + +static void HDAControllerAttach(KDevice *_parent) { + HDAController *controller = (HDAController *) KDeviceCreate("HD Audio controller", _parent, sizeof(HDAController)); + + if (!controller) { + return; + } + + controller->destroy = HDAControllerDestroy; + controller->rirbReceivedSolicitedResponse.autoReset = true; + + controller->pci = (KPCIDevice *) _parent; + controller->pci->EnableFeatures(K_PCI_FEATURE_INTERRUPTS | K_PCI_FEATURE_BUSMASTERING_DMA + | K_PCI_FEATURE_MEMORY_SPACE_ACCESS | K_PCI_FEATURE_BAR_0); + + uint16_t globalCapabilities = RD_REGISTER_GCAP(); + bool supports64BitAddresses = globalCapabilities & (1 << 0); + +#ifdef ARCH_64 + if (!supports64BitAddresses) { + KernelLog(LOG_ERROR, "HDA", "controller unsupported", "Controller does not support 64-bit addresses.\n"); + KDeviceDestroy(controller); + return; + } +#endif + + controller->outputStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 15, 12); + controller->inputStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 11, 8); + controller->bidirectionalStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 7, 3); + + KernelLog(LOG_INFO, "HDA", "global capabilities", "Controller supports %d output streams, %d input streams and %d bidi streams.\n", + controller->outputStreamsSupported, controller->inputStreamsSupported, controller->bidirectionalStreamsSupported); + KernelLog(LOG_INFO, "HDA", "version", "Controller reports version %d.%d.\n", + RD_REGISTER_VMAJ(), RD_REGISTER_VMIN()); + + KTimeout timeout(1000); // The initialisation process shouldn't take more than a second. + +#define CHECK_TIMEOUT(message) \ + if (timeout.Hit()) { \ + KernelLog(LOG_ERROR, "HDA", "timeout", "Timeout during initialization: " message ".\n"); \ + KDeviceDestroy(controller); \ + return; \ + } + + // Reset the controller. + + WR_REGISTER_GCTL(RD_REGISTER_GCTL() & ~(1 << 0 /* CRST */)); + while ((RD_REGISTER_GCTL() & (1 << 0)) && timeout.Hit()); + CHECK_TIMEOUT("clear CRST bit"); + WR_REGISTER_GCTL(RD_REGISTER_GCTL() | (1 << 0 /* CRST */)); + while ((~RD_REGISTER_GCTL() & (1 << 0)) && timeout.Hit()); + CHECK_TIMEOUT("set CRST bit"); + + // Setup CORB/RIRB. + + uint8_t corbSize = RD_REGISTER_CORBSIZE(); + controller->corbEntries = (corbSize & (1 << 6)) ? 256 : (corbSize & (1 << 5)) ? 16 : (corbSize & (1 << 4)) ? 2 : 0; + uint8_t rirbSize = RD_REGISTER_RIRBSIZE(); + controller->rirbEntries = (rirbSize & (1 << 6)) ? 256 : (rirbSize & (1 << 5)) ? 16 : (rirbSize & (1 << 4)) ? 2 : 0; + + if (!controller->corbEntries || !controller->rirbEntries) { + KernelLog(LOG_ERROR, "HDA", "unsupported", "Controller does not support any recognised CORB/RIRB sizes.\n"); + KDeviceDestroy(controller); + return; + } + + if (!MMPhysicalAllocateAndMap(controller->corbEntries * 4, 128, 0, true, + MM_REGION_NOT_CACHEABLE, &controller->corbVirtual, &controller->corbPhysical) + || !MMPhysicalAllocateAndMap(controller->rirbEntries * 8, 128, 0, true, + MM_REGION_NOT_CACHEABLE, &controller->rirbVirtual, &controller->rirbPhysical)) { + KernelLog(LOG_ERROR, "HDA", "insufficient resources", "Could not allocate memory for CORB/RIRB.\n"); + KDeviceDestroy(controller); + return; + } + + WR_REGISTER_CORBSIZE(ES_ISOLATE_BITS(RD_REGISTER_CORBSIZE(), 7, 2) | (controller->corbEntries == 16 ? 1 : controller->corbEntries == 256 ? 2 : 0)); + WR_REGISTER_RIRBSIZE(ES_ISOLATE_BITS(RD_REGISTER_RIRBSIZE(), 7, 2) | (controller->rirbEntries == 16 ? 1 : controller->rirbEntries == 256 ? 2 : 0)); + + WR_REGISTER_CORBLBASE(controller->corbPhysical & 0xFFFFFFFF); + if (supports64BitAddresses) WR_REGISTER_CORBUBASE(controller->corbPhysical >> 32); + WR_REGISTER_RIRBLBASE(controller->rirbPhysical & 0xFFFFFFFF); + if (supports64BitAddresses) WR_REGISTER_RIRBUBASE(controller->rirbPhysical >> 32); + + WR_REGISTER_RINTCNT(ES_ISOLATE_BITS(RD_REGISTER_RINTCNT(), 15, 8) | 1 /* interrupt after every response */); + + WR_REGISTER_CORBCTL(ES_ISOLATE_BITS(RD_REGISTER_CORBCTL(), 7, 2) | (1 << 1 /* run */) | (1 << 0 /* interrupt on error */)); + WR_REGISTER_RIRBCTL(ES_ISOLATE_BITS(RD_REGISTER_RIRBCTL(), 7, 3) | (1 << 1 /* run */) | (1 << 0 /* interrupt on response */)); + + if ((~RD_REGISTER_CORBCTL() & (1 << 1)) || (~RD_REGISTER_RIRBCTL() & (1 << 1))) { + KernelLog(LOG_ERROR, "HDA", "start error", "Could not start the CORB/RIRB.\n"); + KDeviceDestroy(controller); + return; + } + + // Setup interrupts. + + if (!controller->pci->EnableSingleInterrupt(HDAControllerHandleIRQ, controller, "HDA")) { + KernelLog(LOG_ERROR, "HDA", "insufficient resources", "Could not register interrupt handler.\n"); + KDeviceDestroy(controller); + return; + } + + WR_REGISTER_INTCTL((1 << 31) | (1 << 30)); + + // Enumerate codecs. + + uint16_t codecs = RD_REGISTER_STATESTS(); + + for (uintptr_t i = 0; i < 15; i++) { + if (~codecs & (1 << i)) { + continue; + } + + uint32_t vendorID, childNodeCount; + + if (HDAControllerSendCommand(controller, i, 0, READ_PARAMETER_VENDOR_ID, &vendorID) + && HDAControllerSendCommand(controller, i, 0, READ_PARAMETER_CHILD_NODES, &childNodeCount)) { + uint32_t firstChildNode = ES_EXTRACT_BITS(childNodeCount, 23, 16); + childNodeCount = ES_EXTRACT_BITS(childNodeCount, 7, 0); + + KernelLog(LOG_INFO, "HDA", "found codec", "Found codec with vendor ID %x, and child nodes %d to %d.\n", + vendorID, firstChildNode, firstChildNode + childNodeCount - 1); + + for (uintptr_t j = firstChildNode; j < firstChildNode + childNodeCount; j++) { + HDAControllerExploreFunctionGroup(controller, i, j); + } + } + } + + // TODO Enable unsolicited responses. + // TODO Support hotplugging. + + KernelLog(LOG_INFO, "HDA", "ready", "Controller %x successfully initialized.\n", controller); +} + +KDriver driverHDAudio = { + .attach = HDAControllerAttach, +}; diff --git a/drivers/i8254x.cpp b/drivers/i8254x.cpp new file mode 100644 index 0000000..36d5f2b --- /dev/null +++ b/drivers/i8254x.cpp @@ -0,0 +1,499 @@ +// TODO Initialise on a separate thread. +// TODO Checksum off-loading. + +#include + +#define RD_REGISTER_CTRL() Read(0x00) // Device control. +#define WR_REGISTER_CTRL(x) Write(0x00, (x) & ~0x20000416) +#define RD_REGISTER_STATUS() Read(0x08) // Device status. +#define RD_REGISTER_EECD() Read(0x10) // EEPROM data. +#define RD_REGISTER_EERD() Read(0x14) // EEPROM read. +#define WR_REGISTER_EERD(x) Write(0x14, x) +#define WR_REGISTER_FCAL(x) Write(0x28, x) // Flow control low. +#define WR_REGISTER_FCAH(x) Write(0x2C, x) // Flow control high. +#define WR_REGISTER_FCT(x) Write(0x30, x) // Flow control type. +#define RD_REGISTER_ICR() Read(0xC0) // Interrupt cause read. +#define RD_REGISTER_IMS() Read(0xD0) // Interrupt mask set/read. +#define WR_REGISTER_IMS(x) Write(0xD0, x) +#define WR_REGISTER_IMC(x) Write(0xD8, x) // Interrupt mask clear. +#define RD_REGISTER_RCTL() Read(0x100) // Receive control. +#define WR_REGISTER_RCTL(x) Write(0x100, (x) & ~0xF9204C01) +#define RD_REGISTER_RDBAL() Read(0x2800) // Receive descriptor base address low. +#define WR_REGISTER_RDBAL(x) Write(0x2800, x) +#define RD_REGISTER_RDBAH() Read(0x2804) // Receive descriptor base address high. +#define WR_REGISTER_RDBAH(x) Write(0x2804, x) +#define RD_REGISTER_RDLEN() Read(0x2808) // Receive descriptor length. +#define WR_REGISTER_RDLEN(x) Write(0x2808, x) +#define RD_REGISTER_RDH() Read(0x2810) // Receive descriptor head. +#define WR_REGISTER_RDH(x) Write(0x2810, x) +#define RD_REGISTER_RDT() Read(0x2818) // Receive descriptor tail. +#define WR_REGISTER_RDT(x) Write(0x2818, x) +#define WR_REGISTER_MTA(x, y) Write(0x5200 + x * 4, y) // Multicast table array. +#define RD_REGISTER_RAL() Read(0x5400) // Receive address low. +#define WR_REGISTER_RAL(x) Write(0x5400, x) +#define RD_REGISTER_RAH() Read(0x5404) // Receive address high. +#define WR_REGISTER_RAH(x) Write(0x5404, x) +#define RD_REGISTER_TCTL() Read(0x400) // Transmit control. +#define WR_REGISTER_TCTL(x) Write(0x400, (x) & ~0xFC800005) +#define RD_REGISTER_TDBAL() Read(0x3800) // Transmit descriptor base address low. +#define WR_REGISTER_TDBAL(x) Write(0x3800, x) +#define RD_REGISTER_TDBAH() Read(0x3804) // Transmit descriptor base address high. +#define WR_REGISTER_TDBAH(x) Write(0x3804, x) +#define RD_REGISTER_TDLEN() Read(0x3808) // Transmit descriptor length. +#define WR_REGISTER_TDLEN(x) Write(0x3808, x) +#define RD_REGISTER_TDH() Read(0x3810) // Transmit descriptor head. +#define WR_REGISTER_TDH(x) Write(0x3810, x) +#define RD_REGISTER_TDT() Read(0x3818) // Transmit descriptor tail. +#define WR_REGISTER_TDT(x) Write(0x3818, x) + +#define RECEIVE_DESCRIPTOR_COUNT (64) +#define TRANSMIT_DESCRIPTOR_COUNT (64) + +#define RECEIVE_BUFFER_SIZE (8192) +#define TRANSMIT_BUFFER_SIZE (8192) +#define TRANSFER_BUFFER_EXTRA (16) // TODO What are these for? + +struct ReceiveDescriptor { + uint64_t address; + uint16_t length; + uint16_t checksum; + uint8_t status, errors; + uint16_t special; +}; + +struct TransmitDescriptor { + uint64_t address; + uint16_t length; + uint8_t checksumOffset; + uint8_t command; + uint8_t status; + uint8_t checksumStartField; + uint16_t special; +}; + +struct Controller : NetInterface { + KPCIDevice *pci; + + bool hasEEPROM; + + ReceiveDescriptor *receiveDescriptors; + TransmitDescriptor *transmitDescriptors; + uint8_t *receiveBuffers[RECEIVE_DESCRIPTOR_COUNT]; + void *transmitBuffers[TRANSMIT_DESCRIPTOR_COUNT]; + uintptr_t receiveTail; + uintptr_t transmitTail; + + // Used by the dispatch thread. + uint8_t *dispatchBuffers[RECEIVE_DESCRIPTOR_COUNT]; + size_t dispatchByteCounts[RECEIVE_DESCRIPTOR_COUNT]; + uintptr_t dispatchPhysicalAddresses[RECEIVE_DESCRIPTOR_COUNT]; + + KMutex transmitMutex; + KEvent receiveEvent; + + uint32_t Read(uintptr_t offset); + void Write(uintptr_t offset, uint32_t value); + bool ReadEEPROM(uint8_t address, uint16_t *data); + + void Initialise(); + bool Transmit(void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes); + bool HandleIRQ(); + void DispatchThread(); + void DumpState(); +}; + +void Controller::DumpState() { + EsPrint("I8254x controller state:\n"); + + EsPrint("\t--- Internal ---\n"); + EsPrint("\t\tHas EEPROM: %z.\n", hasEEPROM ? "yes" : "no"); + EsPrint("\t\tMAC address: %X:%X:%X:%X:%X:%X.\n", macAddress.d[0], macAddress.d[1], macAddress.d[2], + macAddress.d[3], macAddress.d[4], macAddress.d[5]); + + EsPrint("\t--- Registers ---\n"); + EsPrint("\t\tDevice control: %x.\n", RD_REGISTER_CTRL()); + EsPrint("\t\tDevice status: %x.\n", RD_REGISTER_STATUS()); + EsPrint("\t\tEEPROM data: %x.\n", RD_REGISTER_EECD()); + EsPrint("\t\tEEPROM read: %x.\n", RD_REGISTER_EERD()); + EsPrint("\t\tReceive descriptor base address: %x.\n", (uint64_t) RD_REGISTER_RDBAL() | ((uint64_t) RD_REGISTER_RDBAH() << 32)); + EsPrint("\t\tReceive descriptor length: %x.\n", RD_REGISTER_RDLEN()); + EsPrint("\t\tReceive descriptor head: %x.\n", RD_REGISTER_RDH()); + EsPrint("\t\tReceive descriptor tail: %x.\n", RD_REGISTER_RDT()); + EsPrint("\t\tReceive control: %x.\n", RD_REGISTER_RCTL()); + EsPrint("\t\tReceive MAC address: %x.\n", (uint64_t) RD_REGISTER_RAL() | ((uint64_t) RD_REGISTER_RAH() << 32)); + EsPrint("\t\tTransmit descriptor base address: %x.\n", (uint64_t) RD_REGISTER_TDBAL() | ((uint64_t) RD_REGISTER_TDBAH() << 32)); + EsPrint("\t\tTransmit descriptor length: %x.\n", RD_REGISTER_TDLEN()); + EsPrint("\t\tTransmit descriptor head: %x.\n", RD_REGISTER_TDH()); + EsPrint("\t\tTransmit descriptor tail: %x.\n", RD_REGISTER_TDT()); + EsPrint("\t\tTransmit control: %x.\n", RD_REGISTER_TCTL()); +} + +bool Controller::Transmit(void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes) { + if (!dataBytes) { + KernelPanic("Controller::Transmit - dataBytes is zero.\n"); + } + + KMutexAcquire(&transmitMutex); + EsDefer(KMutexRelease(&transmitMutex)); + + // Get a next index to use for the transmit descriptor. + + uint32_t head = RD_REGISTER_TDH(); + uint32_t index = transmitTail; + uint32_t tail = (index + 1) % TRANSMIT_DESCRIPTOR_COUNT; + + if (head == tail) { + // Wait upto 20ms for the head to move. + KTimeout timeout(20); + while (!timeout.Hit() && (RD_REGISTER_TDH() == tail)); + head = RD_REGISTER_TDH(); + } + + if (head == tail) { + KernelLog(LOG_ERROR, "I8254x", "transmit overrun", "Attempting to transmit a packet with the head at the same position as the tail.\n"); + return false; + } + + // Free any unused transmit buffers. + + if (transmitBuffers[index]) { + NetTransmitBufferReturn(transmitBuffers[index]); + } + + for (uintptr_t i = tail; i != head; i = (i + 1) % TRANSMIT_DESCRIPTOR_COUNT) { + if (transmitBuffers[i]) { + NetTransmitBufferReturn(transmitBuffers[i]); + transmitBuffers[i] = nullptr; + } + } + + // Set up transmit descriptor. + + transmitDescriptors[index].length = dataBytes; + transmitDescriptors[index].status = 0; + transmitDescriptors[index].address = dataPhysical; + transmitDescriptors[index].command = (1 << 0 /* end of packet */) | (1 << 1 /* insert CRC in Ethernet packet */) | (1 << 3 /* report status */); + + transmitBuffers[index] = dataVirtual; + + if ((dataPhysical >> K_PAGE_BITS) != ((dataPhysical + dataBytes - 1) >> K_PAGE_BITS)) { + KernelPanic("Controller::Transmit - Data spanned over page boundary.\n"); + } + + // Submit the transmit descriptor to the controller. + + transmitTail = tail; + __sync_synchronize(); + WR_REGISTER_TDT(tail); + + return true; +} + +void Controller::DispatchThread() { + while (true) { + KEvent *events[] = { &receiveEvent }; + KWaitEvents(events, 1); + + NetInterfaceSetConnected(this, RD_REGISTER_STATUS() & (1 << 1)); + + uint32_t tail = receiveTail, head = RD_REGISTER_RDH(); + uintptr_t dispatchCount = 0; + + while (true) { + uint32_t nextTail = (tail + 1) % RECEIVE_DESCRIPTOR_COUNT; + + if (nextTail == head) { + // Keep the tail one behind the head, otherwise controller assumes queue is empty of usable slots. + break; + } + + if (~receiveDescriptors[nextTail].status & (1 << 0 /* descriptor done */)) { + break; + } + + tail = nextTail; + + uint16_t status = receiveDescriptors[tail].status; + receiveDescriptors[tail].status = 0; + + if (~status & (1 << 1 /* end of packet */)) { + KernelLog(LOG_ERROR, "I8254x", "clear EOP bit", "Received descriptor with clear end of packet bit; this is unsupported.\n"); + goto next; + } + + if (receiveDescriptors[tail].errors) { + KernelLog(LOG_ERROR, "I8254x", "received error", "Received descriptor with error bits %X set.\n", receiveDescriptors[tail].errors); + goto next; + } + + if (receiveDescriptors[tail].length < 60) { + KernelLog(LOG_ERROR, "I8254x", "short packet", "Received descriptor with packet less than 60 bytes; this is unsupported.\n"); + goto next; + } + + KernelLog(LOG_VERBOSE, "I8254x", "received packet", "Received packet at index %d with length %D.\n", tail, receiveDescriptors[tail].length); + + uint8_t *newVirtualAddress; + uintptr_t newPhysicalAddress; + + if (!MMPhysicalAllocateAndMap(RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA, + 16, 64, false, 0, &newVirtualAddress, &newPhysicalAddress)) { + // If we couldn't allocate memory, dispatch the buffer immediately. + + NetInterfaceReceive(this, receiveBuffers[tail], receiveDescriptors[tail].length, NET_PACKET_ETHERNET); + } else { + // Queue the buffer to be dispatched. + + dispatchBuffers[dispatchCount] = receiveBuffers[tail]; + dispatchByteCounts[dispatchCount] = receiveDescriptors[tail].length; + dispatchPhysicalAddresses[dispatchCount] = receiveDescriptors[tail].address; + dispatchCount++; + + receiveBuffers[tail] = newVirtualAddress; + receiveDescriptors[tail].address = newPhysicalAddress; + } + + next:; + } + + __sync_synchronize(); + receiveTail = tail; + WR_REGISTER_RDT(tail); + + for (uintptr_t i = 0; i < dispatchCount; i++) { + NetInterfaceReceive(this, dispatchBuffers[i], dispatchByteCounts[i], NET_PACKET_ETHERNET); + MMFree(MMGetKernelSpace(), dispatchBuffers[i], RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA); + MMPhysicalFree(dispatchPhysicalAddresses[i], false, (RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA + K_PAGE_SIZE - 1) / K_PAGE_SIZE); + } + } +} + +bool Controller::HandleIRQ() { + uint32_t cause = RD_REGISTER_ICR(); + + if (!cause) { + return false; + } + + KernelLog(LOG_VERBOSE, "I8254x", "received IRQ", "Received IRQ with cause %x.\n", cause); + + if (cause & (1 << 2)) { + KernelLog(LOG_INFO, "I8254x", "link status change", "Link is now %z.\n", + (RD_REGISTER_STATUS() & (1 << 1)) ? "up" : "down"); + KEventSet(&receiveEvent, false, true); + } + + if (cause & (1 << 6)) { + KernelLog(LOG_ERROR, "I8254x", "receive underrun", "Controller reported receive underrun; packets have been lost.\n"); + } + + if (cause & ((1 << 6) | (1 << 7) | (1 << 4))) { + KEventSet(&receiveEvent, false, true); + } + + return true; +} + +uint32_t Controller::Read(uintptr_t offset) { + if (pci->baseAddresses[0] & 1) { + pci->WriteBAR32(0, 0, offset); + return pci->ReadBAR32(0, 4); + } else { + return pci->ReadBAR32(0, offset); + } +} + +void Controller::Write(uintptr_t offset, uint32_t value) { + if (pci->baseAddresses[0] & 1) { + pci->WriteBAR32(0, 0, offset); + pci->WriteBAR32(0, 4, value); + } else { + pci->WriteBAR32(0, offset, value); + } +} + +bool Controller::ReadEEPROM(uint8_t address, uint16_t *data) { + if (!hasEEPROM) { + KernelPanic("Controller::ReadEEPROM - EEPROM not present.\n"); + } + + WR_REGISTER_EERD(1 | ((uint32_t) address << 8)); + KTimeout timeout(20); + while (!timeout.Hit() && (RD_REGISTER_EERD() & (1 << 4))); + if (data) *data = RD_REGISTER_EERD() >> 16; + return RD_REGISTER_EERD() & (1 << 4); +} + +void Controller::Initialise() { + KEvent wait = {}; + + // Create a thread for dispatching received packets. + + receiveEvent.autoReset = true; + + if (!KThreadCreate("I8254xDispatch", [] (uintptr_t self) { ((Controller *) self)->DispatchThread(); }, (uintptr_t) this)) { + KernelLog(LOG_ERROR, "I8254x", "thread error", "Could not create the dispatch thread.\n"); + return; + } + + // Detect the EEPROM. + + hasEEPROM = true; + hasEEPROM = ReadEEPROM(0, nullptr); + + // Reset the controller. + + uint32_t controlReset = RD_REGISTER_CTRL(); + + WR_REGISTER_CTRL(controlReset | (1 << 31 /* PHY_RST */)); + RD_REGISTER_STATUS(); + KEventWait(&wait, 20 /* specification says minimum time is 10ms, double for safety */); + + WR_REGISTER_CTRL(controlReset | (1 << 26 /* RST */)); + RD_REGISTER_STATUS(); + KEventWait(&wait, 20); + + if (RD_REGISTER_CTRL() & (1 << 26)) { + KernelLog(LOG_ERROR, "I8254x", "reset timeout", "Reset bit in control register did not clear after 20ms.\n"); + return; + } + + // Configure the control register. + + uint32_t controlClearBits = (1 << 3 /* LRST */) | (1 << 31 /* PHY_RST */) | (1 << 7 /* ILOS */) | (1 << 30 /* VME */); + uint32_t controlSetBits = (1 << 5 /* ASDE */) | (1 << 6 /* SLU */); + WR_REGISTER_CTRL((RD_REGISTER_CTRL() & ~controlClearBits) | controlSetBits); + + // Allocate receive and transmit descriptors and their buffers. + + uintptr_t receiveDescriptorsPhysical, transmitDescriptorsPhysical; + + if (!MMPhysicalAllocateAndMap(sizeof(ReceiveDescriptor) * RECEIVE_DESCRIPTOR_COUNT, 16, 64, true, + 0, (uint8_t **) &receiveDescriptors, &receiveDescriptorsPhysical)) { + KernelLog(LOG_ERROR, "I8254x", "allocation failure", "Could not allocate receive descriptors.\n"); + return; + } + + if (!MMPhysicalAllocateAndMap(sizeof(TransmitDescriptor) * TRANSMIT_DESCRIPTOR_COUNT, 16, 64, true, + 0, (uint8_t **) &transmitDescriptors, &transmitDescriptorsPhysical)) { + KernelLog(LOG_ERROR, "I8254x", "allocation failure", "Could not allocate transmit descriptors.\n"); + return; + } + + for (uintptr_t i = 0; i < RECEIVE_DESCRIPTOR_COUNT; i++) { + if (!MMPhysicalAllocateAndMap(RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA, + 16, 64, false, 0, receiveBuffers + i, &receiveDescriptors[i].address)) { + KernelLog(LOG_ERROR, "I8254x", "allocation failure", "Could not allocate receive buffers.\n"); + return; + } + } + + // Disable flow control. + + WR_REGISTER_FCAL(0); + WR_REGISTER_FCAH(0); + WR_REGISTER_FCT(0); + + // Get the MAC address. + + if (hasEEPROM) { + if (!ReadEEPROM(0, (uint16_t *) &macAddress.d[0]) + || !ReadEEPROM(1, (uint16_t *) &macAddress.d[2]) + || !ReadEEPROM(2, (uint16_t *) &macAddress.d[4])) { + KernelLog(LOG_ERROR, "I8254x", "EEPROM error", "Could not read the MAC address from the EEPROM.\n"); + return; + } + } else { + macAddress64 = ((uint64_t) RD_REGISTER_RAL() | ((uint64_t) RD_REGISTER_RAH() << 32)) & 0xFFFFFFFFFFFF; + } + + // Enable interrupts and the register the handler. + + WR_REGISTER_IMC((1 << 17) - 1); + WR_REGISTER_IMS((1 << 6 /* RXO */) | (1 << 7 /* RXT */) | (1 << 4 /* RXDMT */) | (1 << 2 /* LSC */)); + RD_REGISTER_ICR(); + + if (!pci->EnableSingleInterrupt([] (uintptr_t, void *context) { return ((Controller *) context)->HandleIRQ(); }, this, "I8254x")) { + KernelLog(LOG_ERROR, "I8254x", "IRQ registration failure", "Could not register IRQ %d.\n", pci->interruptLine); + return; + } + + // Setup receive registers. + + WR_REGISTER_RAL(macAddress64 & 0xFFFFFFFF); + WR_REGISTER_RAH(((macAddress64 >> 32) & 0xFFFF) | (1 << 31 /* AV */)); + + WR_REGISTER_RDBAL(receiveDescriptorsPhysical & 0xFFFFFFFF); + WR_REGISTER_RDBAH(receiveDescriptorsPhysical >> 32); + WR_REGISTER_RDLEN(sizeof(ReceiveDescriptor) * RECEIVE_DESCRIPTOR_COUNT); + + WR_REGISTER_RDH(0); + WR_REGISTER_RDT(RECEIVE_DESCRIPTOR_COUNT - 1); + receiveTail = RECEIVE_DESCRIPTOR_COUNT - 1; + + for (uintptr_t i = 0; i < 128; i++) { + // Clear the multicast table array. + WR_REGISTER_MTA(i, 0); + } + + uint32_t receiveControlClearBits = (1 << 2) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 16) | (1 << 17) | (1 << 25) | (1 << 26); + uint32_t receiveControlSetBits = (1 << 1) | (1 << 4) | (1 << 15); + WR_REGISTER_RCTL((RD_REGISTER_RCTL() & ~receiveControlClearBits) | receiveControlSetBits); + + // Setup transmit registers. + + WR_REGISTER_TDBAL(transmitDescriptorsPhysical & 0xFFFFFFFF); + WR_REGISTER_TDBAH(transmitDescriptorsPhysical >> 32); + WR_REGISTER_TDLEN(sizeof(TransmitDescriptor) * TRANSMIT_DESCRIPTOR_COUNT); + + WR_REGISTER_TDH(0); + WR_REGISTER_TDT(0); + transmitTail = 0; + + // TODO Is the TCTL.COLD value correct? + WR_REGISTER_TCTL((1 << 1) | (1 << 3) | (0x0F << 4) | (0x40 << 12)); + + // Register the device. + + transmit = [] (NetInterface *self, void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes) { + return ((Controller *) self)->Transmit(dataVirtual, dataPhysical, dataBytes); + }; + + KRegisterNetInterface(this); + + if (RD_REGISTER_STATUS() & (1 << 1 /* link up */)) { + NetInterfaceSetConnected(this, true); + } +} + +static void DeviceAttach(KDevice *_parent) { + KPCIDevice *parent = (KPCIDevice *) _parent; + + Controller *device = (Controller *) KDeviceCreate("I8254x", parent, sizeof(Controller)); + if (!device) return; + + device->shutdown = [] (KDevice *device) { + NetInterfaceShutdown((Controller *) device); + + // Wait a little bit for the transmit descriptors to be processed. + // TODO Can this be done asynchronously? + KEvent wait = {}; + KEventWait(&wait, 50); + }; + + parent->EnableFeatures(K_PCI_FEATURE_MEMORY_SPACE_ACCESS + | K_PCI_FEATURE_BUSMASTERING_DMA + | K_PCI_FEATURE_INTERRUPTS + | K_PCI_FEATURE_IO_PORT_ACCESS + | K_PCI_FEATURE_BAR_0); + + KernelLog(LOG_INFO, "I8254x", "found controller", "Found I8254x controller with ID %x.\n", parent->deviceID); + + device->pci = parent; + device->Initialise(); + // device->DumpState(); +} + +KDriver driverI8254x = { + .attach = DeviceAttach, +}; diff --git a/drivers/ide.cpp b/drivers/ide.cpp new file mode 100644 index 0000000..2d34f1e --- /dev/null +++ b/drivers/ide.cpp @@ -0,0 +1,602 @@ +// TODO Asynchronous timeout. +// TODO Inserting/removing ATAPI devices. + +#include + +#define ATA_BUSES 2 +#define ATA_DRIVES (ATA_BUSES * 2) +#define ATA_SECTOR_SIZE (512) +#define ATA_TIMEOUT (10000) +#define ATAPI_SECTOR_SIZE (2048) + +#define ATA_REGISTER(_bus, _reg) (_reg != -1 ? ((_bus ? 0x170 : 0x1F0) + _reg) : (_bus ? 0x376 : 0x3F6)) +#define ATA_IRQ(_bus) (_bus ? 15 : 14) +#define ATA_DATA 0 +#define ATA_FEATURES 1 +#define ATA_SECTOR_COUNT 2 +#define ATA_LBA1 3 +#define ATA_LBA2 4 +#define ATA_LBA3 5 +#define ATA_DRIVE_SELECT 6 +#define ATA_STATUS 7 +#define ATA_COMMAND 7 +#define ATA_DCR -1 +#define ATA_IDENTIFY 0xEC +#define ATA_IDENTIFY_PACKET 0xA1 +#define ATA_READ_PIO 0x20 +#define ATA_READ_PIO_48 0x24 +#define ATA_READ_DMA 0xC8 +#define ATA_READ_DMA_48 0x25 +#define ATA_WRITE_PIO 0x30 +#define ATA_WRITE_PIO_48 0x34 +#define ATA_PACKET 0xA0 +#define ATA_WRITE_DMA 0xCA +#define ATA_WRITE_DMA_48 0x35 + +#define DMA_REGISTER(_bus, _reg) 4, ((_bus ? (_reg + 8) : _reg)) +#define DMA_COMMAND 0 +#define DMA_STATUS 2 +#define DMA_PRDT 4 + +struct PRD { + volatile uint32_t base; + volatile uint16_t size; + volatile uint16_t end; +}; + +struct ATAOperation { + void *buffer; + uintptr_t offsetIntoSector, readIndex; + size_t countBytes, sectorsNeededToLoad; + uint8_t operation, readingData, bus, slave; + bool pio; +}; + +struct ATAController : KDevice { + void Initialise(); + bool Access(uintptr_t drive, uint64_t sector, size_t count, int operation, uint8_t *buffer); // Returns true on success. + bool AccessStart(int bus, int slave, uint64_t sector, uintptr_t offsetIntoSector, size_t sectorsNeededToLoad, size_t countBytes, int operation, uint8_t *buffer, bool atapi); + bool AccessEnd(int bus, int slave); + + void SetDrive(int bus, int slave, int extra = 0); + void Unblock(); + + KPCIDevice *pci; + + uint64_t sectorCount[ATA_DRIVES]; + bool isATAPI[ATA_DRIVES]; + + KSemaphore semaphore; + + PRD *prdts[ATA_BUSES]; + void *buffers[ATA_BUSES]; + KEvent irqs[ATA_BUSES]; + + uint16_t identifyData[ATA_SECTOR_SIZE / 2]; + + volatile ATAOperation op; + + KMutex blockedPacketsMutex; +}; + +static ATAController *ataController; + +struct ATADrive : KBlockDevice { + uintptr_t index; +}; + +void ATAController::SetDrive(int bus, int slave, int extra) { + ProcessorOut8(ATA_REGISTER(bus, ATA_DRIVE_SELECT), extra | 0xA0 | (slave << 4)); + for (int i = 0; i < 4; i++) ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)); +} + +bool ATAController::AccessStart(int bus, int slave, uint64_t sector, uintptr_t offsetIntoSector, size_t sectorsNeededToLoad, size_t countBytes, + int operation, uint8_t *_buffer, bool atapi) { + uint16_t *buffer = (uint16_t *) _buffer; + + bool s48 = false; + + // Start a timeout. + KTimeout timeout(1000); + + while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit()); + + if (timeout.Hit()) return false; + + if (atapi) { + SetDrive(bus, slave); + ProcessorOut8(ATA_REGISTER(bus, ATA_FEATURES), 1); // Using DMA. + uint32_t maxByteCount = sectorsNeededToLoad * ATAPI_SECTOR_SIZE; + if (maxByteCount > 65535) KernelPanic("ATAController::AccessStart - Access too large for ATAPI drive (max 64KB).\n"); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), maxByteCount & 0xFF); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), (maxByteCount >> 8) & 0xFF); + } else if (sector >= 0x10000000) { + s48 = true; + + SetDrive(bus, slave, 0x40); + + ProcessorOut8(ATA_REGISTER(bus, ATA_SECTOR_COUNT), 0); + ProcessorOut8(ATA_REGISTER(bus, ATA_SECTOR_COUNT), sectorsNeededToLoad); + + // Set the sector to access. + // The drive will keep track of the previous and current values of these registers, + // allowing it to construct a 48-bit sector number. + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), sector >> 40); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), sector >> 32); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), sector >> 24); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), sector >> 16); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), sector >> 8); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), sector >> 0); + } else { + SetDrive(bus, slave, 0x40 | (sector >> 24)); + ProcessorOut8(ATA_REGISTER(bus, ATA_SECTOR_COUNT), sectorsNeededToLoad); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), sector >> 16); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), sector >> 8); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), sector >> 0); + } + + KEvent *event = irqs + bus; + event->autoReset = false; + KEventReset(event); + + // Save the operation information. + op.buffer = buffer; + op.offsetIntoSector = offsetIntoSector; + op.countBytes = countBytes; + op.operation = operation; + op.readingData = false; + op.readIndex = 0; + op.sectorsNeededToLoad = sectorsNeededToLoad; + op.pio = false; + op.bus = bus; + op.slave = slave; + + { + // Make sure the previous request has completed. + ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)); + pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS)); + + // Prepare the PRDT and buffer + prdts[bus]->size = sectorsNeededToLoad * (atapi ? ATAPI_SECTOR_SIZE : ATA_SECTOR_SIZE); + if (operation == K_ACCESS_WRITE) EsMemoryCopy((uint8_t *) buffers[bus] + offsetIntoSector, buffer, countBytes); + + // Set the mode. + pci->WriteBAR8(DMA_REGISTER(bus, DMA_COMMAND), operation == K_ACCESS_WRITE ? 0 : 8); + pci->WriteBAR8(DMA_REGISTER(bus, DMA_STATUS), 6); + + // Wait for the RDY bit to set. + while (!(ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & (1 << 6)) && !timeout.Hit()); + if (timeout.Hit()) return false; + + // Issue the command. + if (atapi) ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_PACKET); + else if (s48) ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), operation == K_ACCESS_READ ? ATA_READ_DMA_48 : ATA_WRITE_DMA_48); + else ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), operation == K_ACCESS_READ ? ATA_READ_DMA : ATA_WRITE_DMA ); + + // Wait for the DRQ bit to set. + while (!(ProcessorIn8(ATA_REGISTER(bus, ATA_DCR)) & (1 << 3)) && !timeout.Hit()); + + if (timeout.Hit()) return false; + + if (atapi) { + uint8_t packet[12] = {}; + packet[0] = 0xA8; // Read sectors. + packet[2] = (sector >> 0x18) & 0xFF; + packet[3] = (sector >> 0x10) & 0xFF; + packet[4] = (sector >> 0x08) & 0xFF; + packet[5] = (sector >> 0x00) & 0xFF; + packet[9] = sectorsNeededToLoad; + + // Wait for the BSY bit to clear. + while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & (1 << 7)) && !timeout.Hit()); + if (timeout.Hit()) return false; + + // Send the ATAPI command. + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[0]); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[1]); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[2]); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[3]); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[4]); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[5]); + } + + pci->WriteBAR8(DMA_REGISTER(bus, DMA_COMMAND), operation == K_ACCESS_WRITE ? 1 : 9); + if (pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS)) & 2) return false; + if (ProcessorIn8(ATA_REGISTER(bus, ATA_DCR)) & 33) return false; + } + + return true; +} + +bool ATAController::AccessEnd(int bus, int slave) { + (void) slave; + KEvent *event = irqs + bus; + + { + // Wait for the command to complete. + KEventWait(event, ATA_TIMEOUT); + + // Copy the data that we read. + ATAOperation *op = (ATAOperation *) &ataController->op; + if (op->buffer && op->operation == K_ACCESS_READ) { + // EsPrint("copying %d to %x\n", op->countBytes, op->buffer); + EsMemoryCopy((void *) op->buffer, (uint8_t *) ataController->buffers[op->bus] + op->offsetIntoSector, op->countBytes); + // EsPrint("done\n"); + } + + // Check for error. + if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 33) return false; + if (pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS)) & 3) return false; + + // Check if the command has completed. + if (!KEventPoll(event)) { + return false; + } + + return true; + } +} + +void ATAController::Unblock() { + KMutexAcquire(&blockedPacketsMutex); + // EsPrint("unblock!\n"); + KSemaphoreReturn(&semaphore, 1); + KMutexRelease(&blockedPacketsMutex); +} + +bool ATAController::Access(uintptr_t drive, uint64_t offset, size_t countBytes, int operation, uint8_t *_buffer) { + bool atapi = isATAPI[drive]; + uint64_t sectorSize = atapi ? ATAPI_SECTOR_SIZE : ATA_SECTOR_SIZE; + uint64_t sector = offset / sectorSize; + uint64_t offsetIntoSector = offset % sectorSize; + uint64_t sectorsNeededToLoad = (countBytes + offsetIntoSector + sectorSize - 1) / sectorSize; + uintptr_t bus = drive >> 1; + uintptr_t slave = drive & 1; + + if (drive >= ATA_DRIVES) KernelPanic("ATAController::Access - Drive %d exceedes the maximum number of ATA driver (%d).\n", drive, ATA_DRIVES); + if (atapi && operation == K_ACCESS_WRITE) KernelPanic("ATAController::Access - Drive %d is an ATAPI drive. ATAPI write operations are currently not supported.\n", drive); + if (!sectorCount[drive] && !atapi) KernelPanic("ATAController::Access - Drive %d is invalid.\n", drive); + if ((sector > sectorCount[drive] || (sector + sectorsNeededToLoad) > sectorCount[drive]) && !atapi) KernelPanic("ATAController::Access - Attempt to access sector %d when drive only has %d sectors.\n", sector, sectorCount[drive]); + if (sectorsNeededToLoad > 64) KernelPanic("ATAController::Access - Attempt to read more than 64 consecutive sectors in 1 function call.\n"); + + // Lock the driver. + { + while (true) { + KEventWait(&semaphore.available, ES_WAIT_NO_TIMEOUT); + KMutexAcquire(&blockedPacketsMutex); + + if (semaphore.units) { + KSemaphoreTake(&semaphore, 1); + break; + } + + KMutexRelease(&blockedPacketsMutex); + } + + KMutexRelease(&blockedPacketsMutex); + } + + // EsPrint("locked (%d/%x)!\n", countBytes, _buffer); + + op.bus = bus; + op.slave = slave; + + if (!AccessStart(bus, slave, sector, offsetIntoSector, sectorsNeededToLoad, countBytes, operation, _buffer, atapi)) { + KSemaphoreReturn(&semaphore, 1); + return false; + } + + bool result = AccessEnd(bus, slave); + Unblock(); + return result; +} + +bool ATAIRQHandler(uintptr_t interruptIndex, void *) { + int bus = interruptIndex - ATA_IRQ(0); + + // Acknowledge the interrupt. + ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)); + ataController->pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS)); + + // *Don't* queue an asynchronous task. + // First of all, we don't need to (it's slower), + // and secondly, we need to Sync() nodes we're closing during process handle table termination, + // which takes place in the asynchronous task thread. (Meaning we'd get deadlock). + // TODO Is there a better way to do this, preventing similar bugs in the future? + + ATAOperation *op = (ATAOperation *) &ataController->op; + KEvent *event = ataController->irqs + op->bus; + + if (op->pio) { + KEventSet(event); + } else if (!(ataController->pci->ReadBAR8(DMA_REGISTER(op->bus, DMA_STATUS)) & 4)) { + // The interrupt bit was not set, so the IRQ must have been generated by a different device. + } else { + if (!event->state) { + // Stop the transfer. + ataController->pci->WriteBAR8(DMA_REGISTER(op->bus, DMA_COMMAND), 0); + KEventSet(event); + } else { + KernelLog(LOG_ERROR, "IDE", "too many IRQs", "ATAIRQHandler - Received more interrupts than expected.\n"); + } + } + + KSwitchThreadAfterIRQ(); + + return true; +} + +inline uint32_t ByteSwap32(uint32_t x) { + return ((x & 0xFF000000) >> 24) + | ((x & 0x000000FF) << 24) + | ((x & 0x00FF0000) >> 8) + | ((x & 0x0000FF00) << 8); +} + +void ATAController::Initialise() { + KSemaphoreReturn(&semaphore, 1); + + KernelLog(LOG_INFO, "IDE", "found controller", "ATAController::Initialise - Found an ATA controller.\n"); + + for (uintptr_t bus = 0; bus < ATA_BUSES; bus++) { + // If the status is 0xFF, then the bus does not exist. + if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) == 0xFF) { + continue; + } + + // Check that the LBA registers are RW. + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), 0xAB); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), 0xCD); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), 0xEF); + + // Otherwise, the bus doesn't exist. + if (ProcessorIn8(ATA_REGISTER(bus, ATA_LBA1) != 0xAB)) continue; + if (ProcessorIn8(ATA_REGISTER(bus, ATA_LBA2) != 0xCD)) continue; + if (ProcessorIn8(ATA_REGISTER(bus, ATA_LBA3) != 0xEF)) continue; + + // Clear the device command register. + ProcessorOut8(ATA_REGISTER(bus, ATA_DCR), 0); + + int dmaDrivesOnBus = 0; + int drivesOnBus = 0; + uint8_t status; + + size_t drivesPerBus = 2; + + for (uintptr_t slave = 0; slave < drivesPerBus; slave++) { + // Issue the IDENTIFY command to the drive. + SetDrive(bus, slave); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), 0); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), 0); + ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_IDENTIFY); + + // Start a timeout. + KTimeout timeout(100); + + // Check for error. + bool atapi = false; + if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 32) continue; + if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 1) { + uint8_t a = ProcessorIn8(ATA_REGISTER(bus, ATA_LBA2)); + uint8_t b = ProcessorIn8(ATA_REGISTER(bus, ATA_LBA3)); + if (a == 0x14 && b == 0xEB) { + atapi = true; + ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_IDENTIFY_PACKET); + } else { + continue; + } + } + + // Wait for the drive to be ready for the data transfer. + while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit()); + if (timeout.Hit()) continue; + while ((!(status = ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 9)) && !timeout.Hit()); + if (timeout.Hit()) continue; + if (status & 33) continue; + + // Transfer the data. + for (uintptr_t i = 0; i < 256; i++) { + identifyData[i] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA)); + } + + // Check if the device supports LBA/DMA. + if (!(identifyData[49] & 0x200)) continue; + if (!(identifyData[49] & 0x100)) continue; + dmaDrivesOnBus |= 1; + drivesOnBus |= 1; + + // Work out the number of sectors in the drive. + uint32_t lba28Sectors = ((uint32_t) identifyData[60] << 0) + ((uint32_t) identifyData[61] << 16); + uint64_t lba48Sectors = ((uint64_t) identifyData[100] << 0) + ((uint64_t) identifyData[101] << 16) + + ((uint64_t) identifyData[102] << 32) + ((uint64_t) identifyData[103] << 48); + bool supportsLBA48 = lba48Sectors && (identifyData[83] & (1 << 10)); + uint64_t sectors = supportsLBA48 ? lba48Sectors : lba28Sectors; + sectorCount[slave + bus * 2] = sectors; + + if (atapi) { + isATAPI[slave + bus * 2] = true; + KernelLog(LOG_INFO, "IDE", "found drive", "ATAController::Initialise - Found ATAPI drive: %d/%d%z.\n", + bus, slave, supportsLBA48 ? "; supports LBA48" : ""); + } else { + KernelLog(LOG_INFO, "IDE", "found drive", "ATAController::Initialise - Found ATA drive: %d/%d with %x sectors%z.\n", + bus, slave, sectors, supportsLBA48 ? "; supports LBA48" : ""); + } + } + + if (dmaDrivesOnBus) { + uint8_t *dataVirtual; + uintptr_t dataPhysical; + + if (!MMPhysicalAllocateAndMap(131072, 131072, 32, true, ES_FLAGS_DEFAULT, &dataVirtual, &dataPhysical)) { + KernelLog(LOG_ERROR, "IDE", "allocation failure", "ATAController::Initialise - Could not allocate memory for DMA on bus %d.\n", bus); + sectorCount[bus * 2 + 0] = sectorCount[bus * 2 + 1] = 0; + drivesOnBus = 0; + continue; + } + + PRD *prdt = (PRD *) dataVirtual; + prdt->end = 0x8000; + prdt->base = dataPhysical + 65536; + prdts[bus] = prdt; + + void *buffer = (void *) (dataVirtual + 65536); + buffers[bus] = buffer; + + pci->WriteBAR32(DMA_REGISTER(bus, DMA_PRDT), dataPhysical); + } + + if (drivesOnBus) { + if (!KRegisterIRQ(ATA_IRQ(bus), ATAIRQHandler, nullptr, "IDE")) { + KernelLog(LOG_ERROR, "IDE", "IRQ registration failure", "ATAController::Initialise - Could not register IRQ for bus %d.\n", bus); + + // Disable the drives on this bus. + sectorCount[bus * 2 + 0] = 0; + sectorCount[bus * 2 + 1] = 0; + isATAPI[bus * 2 + 0] = false; + isATAPI[bus * 2 + 1] = false; + } + } + + for (uintptr_t slave = 0; slave < 2; slave++) { + if (sectorCount[slave + bus * 2]) { + bool success = Access(bus * 2 + slave, 0, ATA_SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) identifyData); + + if (!success) { + KernelLog(LOG_ERROR, "IDE", "test read failure", "ATAController::Initialise - Could not perform test read on drive.\n"); + continue; + } + + success = Access(bus * 2 + slave, 0, ATA_SECTOR_SIZE, K_ACCESS_WRITE, (uint8_t *) identifyData); + + if (!success) { + KernelLog(LOG_ERROR, "IDE", "test write failure", "ATAController::Initialise - Could not perform test write to drive.\n"); + } + } else if (isATAPI[slave + bus * 2]) { + KEvent *event = irqs + bus; + event->autoReset = false; + KEventReset(event); + + op.bus = bus; + op.slave = slave; + op.pio = true; + + uint16_t capacity[4]; + + SetDrive(bus, slave); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), 0); + ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), 8); + ProcessorOut8(ATA_REGISTER(bus, ATA_FEATURES), 0); + ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_PACKET); + + KTimeout timeout(100); + + if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 33) { + goto readCapacityFailure; + } + + while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit()); + if (timeout.Hit()) goto readCapacityFailure; + while ((!(status = ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 9)) && !timeout.Hit()); + if (timeout.Hit()) goto readCapacityFailure; + if (status & 33) goto readCapacityFailure; + + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0025); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000); + ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000); + + KEventWait(event, ATA_TIMEOUT); + KEventReset(event); + + while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit()); + if (timeout.Hit()) goto readCapacityFailure; + while ((!(status = ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 9)) && !timeout.Hit()); + if (timeout.Hit()) goto readCapacityFailure; + if (status & 33) goto readCapacityFailure; + + capacity[0] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA)); + capacity[1] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA)); + capacity[2] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA)); + capacity[3] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA)); + + KEventWait(event, ATA_TIMEOUT); + KEventReset(event); + + { + uint32_t blockCount = ByteSwap32(capacity[0] | ((uint32_t) capacity[1] << 16)) + 1; + uint32_t blockLength = ByteSwap32(capacity[2] | ((uint32_t) capacity[3] << 16)); + + if (blockLength != ATAPI_SECTOR_SIZE) { + KernelLog(LOG_ERROR, "IDE", "unsupported ATAPI block length", + "ATAController::Initialise - ATAPI drive reported block length of %d bytes, which is unsupported.\n", blockLength); + continue; + } + + KernelLog(LOG_INFO, "IDE", "ATAPI capacity", "ATAController::Initialise - ATAPI drive has %d blocks (%D).\n", + blockCount, blockCount * ATAPI_SECTOR_SIZE); + + sectorCount[slave + bus * 2] = blockCount; + continue; + } + + readCapacityFailure:; + KernelLog(LOG_ERROR, "IDE", "read capacity failure", "ATAController::Initialise - Could not read capacity of ATAPI drive %d/%d.\n", bus, slave); + continue; + } + } + } + + for (uintptr_t i = 0; i < ATA_DRIVES; i++) { + if (sectorCount[i]) { + // Register the drive. + ATADrive *device = (ATADrive *) KDeviceCreate("IDE drive", this, sizeof(ATADrive)); + + if (!device) { + KernelLog(LOG_ERROR, "IDE", "allocation failure", "Could not create device for drive %d.\n", i); + break; + } + + device->index = i; + device->sectorSize = isATAPI[i] ? ATAPI_SECTOR_SIZE : ATA_SECTOR_SIZE; + device->sectorCount = sectorCount[i]; + device->maxAccessSectorCount = isATAPI[i] ? 31 : 64; + device->readOnly = isATAPI[i]; + device->driveType = isATAPI[i] ? ES_DRIVE_TYPE_CDROM : ES_DRIVE_TYPE_HDD; + + device->access = [] (KBlockDeviceAccessRequest request) { + request.dispatchGroup->Start(); + bool success = ataController->Access(((ATADrive *) request.device)->index, + request.offset, request.count, request.operation, (uint8_t *) KDMABufferGetVirtualAddress(request.buffer)); + request.dispatchGroup->End(success); + }; + + FSRegisterBlockDevice(device); + } + } +} + +static void DeviceAttach(KDevice *_parent) { + KPCIDevice *parent = (KPCIDevice *) _parent; + + if (ataController) { + KernelLog(LOG_ERROR, "IDE", "multiple controllers", "EntryIDE - Attempt to register multiple IDE controllers; ignored.\n"); + return; + } + + ATAController *device = (ATAController *) KDeviceCreate("IDE controller", parent, sizeof(ATAController)); + if (!device) return; + ataController = device; + device->pci = parent; + + // Enable busmastering DMA and interrupts. + parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS | K_PCI_FEATURE_BUSMASTERING_DMA | K_PCI_FEATURE_BAR_4); + + // Initialise the controller. + device->Initialise(); +} + +KDriver driverIDE = { + .attach = DeviceAttach, +}; diff --git a/drivers/iso9660.cpp b/drivers/iso9660.cpp new file mode 100644 index 0000000..e29ee85 --- /dev/null +++ b/drivers/iso9660.cpp @@ -0,0 +1,545 @@ +// TODO Validation of all fields. + +#include + +#define SECTOR_SIZE (2048) + +struct LBE16 { +#ifdef __BIG_ENDIAN__ + uint16_t _u, x; +#else + uint16_t x, _u; +#endif +}; + +struct LBE32 { +#ifdef __BIG_ENDIAN__ + uint32_t _u, x; +#else + uint32_t x, _u; +#endif +}; + +struct DateTime { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char centiseconds[2]; + int8_t timeZoneOffset; +} __attribute__((packed)); + +struct DateTime2 { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + int8_t timeZoneOffset; +} __attribute__((packed)); + +struct DirectoryRecord { + uint8_t length; + uint8_t extendedAttributeLength; + LBE32 extentStart; + LBE32 extentSize; + DateTime2 recordingTime; + uint8_t flags; + uint8_t interleavedUnitSize; + uint8_t interleavedGapSize; + LBE16 volumeSequenceNumber; + uint8_t fileNameBytes; +} __attribute__((packed)); + +struct PrimaryDescriptor { + uint8_t typeCode; + char signature[5]; + uint8_t version; + uint8_t _unused0; + char systemIdentifier[32]; + char volumeIdentifier[32]; + uint8_t _unused1[8]; + LBE32 volumeSize; + uint8_t _unused2[32]; + LBE16 volumeSetSize; + LBE16 volumeSequenceNumber; + LBE16 logicalBlockSize; + LBE32 pathTableSize; + uint32_t pathTableLittle; + uint32_t optionalPathTableLittle; + uint32_t pathTableBig; + uint32_t optionalPathTableBig; + DirectoryRecord rootDirectory; + char rootDirectoryName; + char volumeSetIdentifier[128]; + char publisherIdentifier[128]; + char dataPreparerIdentifier[128]; + char applicationIdentifier[128]; + char copyrightFileIdentifier[38]; + char abstractFileIdentifier[36]; + char bibliographicFileIdentifier[37]; + DateTime volumeCreationTime; + DateTime volumeModificationTime; + DateTime volumeExpirationTime; + DateTime volumeEffectiveTime; + uint8_t fileStructureVersion; + uint8_t _unused3; + char applicationSpecific[512]; + uint8_t _unused4[653]; +} __attribute__((packed)); + +struct DirectoryRecordReference { + uint32_t sector, offset; +}; + +struct FSNode { + struct Volume *volume; + DirectoryRecord record; +}; + +struct Volume : KDevice { + KFileSystem *fileSystem; + PrimaryDescriptor primaryDescriptor; +}; + +static EsError ScanInternal(const char *name, size_t nameBytes, KNode *_directory, DirectoryRecord *_record = nullptr); + +static bool Mount(Volume *volume) { +#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "mount failure", "Mount - " message); return false; } while (0) + + uintptr_t descriptorIndex = 0; + + while (true) { + if (!volume->fileSystem->Access(32768 + SECTOR_SIZE * descriptorIndex, SECTOR_SIZE, K_ACCESS_READ, &volume->primaryDescriptor, ES_FLAGS_DEFAULT)) { + MOUNT_FAILURE("Could not access descriptor list.\n"); + } + + if (0 != EsMemoryCompare(volume->primaryDescriptor.signature, "CD001", 5)) { + MOUNT_FAILURE("Invalid descriptor signature.\n"); + } + + if (volume->primaryDescriptor.typeCode == 1) { + break; + } + + if (volume->primaryDescriptor.typeCode == 0xFF) { + MOUNT_FAILURE("Could not find primary descriptor in descriptor list.\n"); + } + + if (++descriptorIndex > 16) { + MOUNT_FAILURE("Could not find end of descriptor list.\n"); + } + } + + if (volume->primaryDescriptor.version != 1 || volume->primaryDescriptor.fileStructureVersion != 1) { + MOUNT_FAILURE("Unsupported fileSystem version.\n"); + } + + if (volume->primaryDescriptor.logicalBlockSize.x != SECTOR_SIZE) { + MOUNT_FAILURE("Unsupported block size.\n"); + } + + { + FSNode *root = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!root) { + MOUNT_FAILURE("Could not allocate root node.\n"); + } + + volume->fileSystem->rootDirectory->driverNode = root; + volume->fileSystem->rootDirectoryInitialChildren = volume->primaryDescriptor.rootDirectory.extentSize.x / sizeof(DirectoryRecord); + + root->volume = volume; + root->record = volume->primaryDescriptor.rootDirectory; + } + + { + // Is this the boot disc? + + EsUniqueIdentifier identifier = KGetBootIdentifier(); + + if (0 != EsMemoryCompare("Essence::", volume->primaryDescriptor.applicationSpecific, 9)) { + goto notBoot; + } + + if (EsMemoryCompare(&identifier, volume->primaryDescriptor.applicationSpecific + 9, 16)) { + goto notBoot; + } + + DirectoryRecord record = {}; + ScanInternal(EsLiteral("ESSENCE.DAT;1"), volume->fileSystem->rootDirectory, &record); + record.extentSize.x = (record.extentSize.x + SECTOR_SIZE - 1) / SECTOR_SIZE; + + if (!record.length || record.extentStart.x >= volume->fileSystem->block->sectorCount + || record.extentSize.x >= volume->fileSystem->block->sectorCount - record.extentStart.x) { + goto notBoot; + } + + // Load the first sector to look at the MBR. + + uint8_t *firstSector = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED); + + if (!firstSector) { + KernelLog(LOG_ERROR, "ISO9660", "allocation failure", "Could not allocate sector buffer to check MBR.\n"); + goto notBoot; + } + + EsDefer(EsHeapFree(firstSector, SECTOR_SIZE, K_FIXED)); + + if (!volume->fileSystem->Access(record.extentStart.x * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, firstSector, ES_FLAGS_DEFAULT)) { + goto notBoot; + } + + uint32_t sectorOffset = ((uint32_t) firstSector[0x1BE + 8] << 0) + ((uint32_t) firstSector[0x1BE + 9] << 8) + + ((uint32_t) firstSector[0x1BE + 10] << 16) + ((uint32_t) firstSector[0x1BE + 11] << 24); + sectorOffset /= (SECTOR_SIZE / 512); // Convert to disc sectors. + + if (sectorOffset >= record.extentSize.x) { + goto notBoot; + } + + record.extentStart.x += sectorOffset; + record.extentSize.x -= sectorOffset; + + KernelLog(LOG_INFO, "ISO9660", "found boot disc", "Found boot disc. Image at %d/%d.\n", + record.extentStart.x, record.extentSize.x); + FSPartitionDeviceCreate(volume->fileSystem->block, record.extentStart.x, record.extentSize.x, ES_FLAGS_DEFAULT, "CD-ROM boot partition"); + } + + notBoot:; + + return true; +} + +static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) { +#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0) + + FSNode *file = (FSNode *) node->driverNode; + Volume *volume = file->volume; + + uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED); + + if (!sectorBuffer) { + READ_FAILURE("Could not allocate sector buffer.\n"); + } + + EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED)); + + uint8_t *outputBuffer = (uint8_t *) _buffer; + + uint64_t firstSector = offset / SECTOR_SIZE; + uint32_t lba = file->record.extentStart.x + firstSector; + offset %= SECTOR_SIZE; + + while (count) { + if (offset || count < SECTOR_SIZE) { + if (!volume->fileSystem->Access(lba * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) { + READ_FAILURE("Could not read file sector.\n"); + } + + uint64_t bytesToRead = (count > SECTOR_SIZE - offset) ? (SECTOR_SIZE - offset) : count; + EsMemoryCopy(outputBuffer, sectorBuffer + offset, bytesToRead); + + lba++, count -= bytesToRead, offset = 0, outputBuffer += bytesToRead; + } else { + uint64_t sectorsToRead = count / SECTOR_SIZE; + + if (!volume->fileSystem->Access(lba * SECTOR_SIZE, sectorsToRead * SECTOR_SIZE, K_ACCESS_READ, outputBuffer, ES_FLAGS_DEFAULT)) { + READ_FAILURE("Could not read file sectors.\n"); + } + + lba += sectorsToRead, count -= SECTOR_SIZE * sectorsToRead, outputBuffer += SECTOR_SIZE * sectorsToRead; + } + } + + return true; +} + +static EsError Enumerate(KNode *node) { +#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "enumerate failure", "Enumerate - " message); return false; } while (0) + + FSNode *directory = (FSNode *) node->driverNode; + Volume *volume = directory->volume; + + // TODO Load multiple sectors at once? + + uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED); + + if (!sectorBuffer) { + ENUMERATE_FAILURE("Could not allocate sector buffer.\n"); + } + + EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED)); + + uint32_t currentSector = directory->record.extentStart.x; + uint32_t remainingBytes = directory->record.extentSize.x; + + while (remainingBytes) { + bool accessResult = volume->fileSystem->Access(currentSector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT); + + if (!accessResult) { + ENUMERATE_FAILURE("Could not read sector.\n"); + } + + uintptr_t positionInSector = 0; + + while (positionInSector < SECTOR_SIZE && positionInSector < remainingBytes) { + DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + positionInSector); + + if (!record->length) { + break; + } + + if (positionInSector + record->length > SECTOR_SIZE || record->length < sizeof(DirectoryRecord)) { + ENUMERATE_FAILURE("Invalid directory record.\n"); + } + + if (record->fileNameBytes <= 2) { + goto nextEntry; + } + + { + KNodeMetadata metadata = {}; + + size_t nameBytes = record->fileNameBytes; + + for (uintptr_t i = 0; i < record->fileNameBytes; i++) { + if (((char *) (record + 1))[i] == ';') { + nameBytes = i; + break; + } + } + + metadata.type = (record->flags & (1 << 1)) ? ES_NODE_DIRECTORY : ES_NODE_FILE; + + if (metadata.type == ES_NODE_DIRECTORY) { + metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN; + metadata.totalSize = 0; + } else if (metadata.type == ES_NODE_FILE) { + metadata.totalSize = record->extentSize.x; + } + + DirectoryRecordReference reference = {}; + reference.sector = currentSector; + reference.offset = positionInSector; + + EsError error = FSDirectoryEntryFound(node, &metadata, &reference, + (const char *) (record + 1), nameBytes, false); + + if (error != ES_SUCCESS) { + return error; + } + } + + nextEntry:; + positionInSector += record->length; + } + + if (remainingBytes < SECTOR_SIZE) { + remainingBytes = 0; + } else { + remainingBytes -= SECTOR_SIZE; + } + } + + return ES_SUCCESS; +} + +static EsError ScanInternal(const char *name, size_t nameBytes, KNode *_directory, DirectoryRecord *_record) { +#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "scan failure", "Scan - " message); return ES_ERROR_UNKNOWN; } while (0) + + // Check for invalid characters. + + for (uintptr_t i = 0; i < nameBytes; i++) { + bool validCharacter = name[i] == '.' || name[i] == ';' || name[i] == '_' + || (name[i] >= 'A' && name[i] <= 'Z') + || (name[i] >= '0' && name[i] <= '9'); + + if (!validCharacter) { + return ES_ERROR_FILE_DOES_NOT_EXIST; + } + } + + FSNode *directory = (FSNode *) _directory->driverNode; + Volume *volume = directory->volume; + + // TODO Load multiple sectors at once? + + uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED); + + if (!sectorBuffer) { + SCAN_FAILURE("Could not allocate sector buffer.\n"); + } + + EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED)); + + uint32_t currentSector = directory->record.extentStart.x; + uint32_t remainingBytes = directory->record.extentSize.x; + + while (remainingBytes) { + bool accessResult = volume->fileSystem->Access(currentSector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT); + + if (!accessResult) { + SCAN_FAILURE("Could not read sector.\n"); + } + + uintptr_t positionInSector = 0; + + while (positionInSector < SECTOR_SIZE && positionInSector < remainingBytes) { + DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + positionInSector); + + if (!record->length) { + break; + } + + if (positionInSector + record->length > SECTOR_SIZE || record->length < sizeof(DirectoryRecord)) { + SCAN_FAILURE("Invalid directory record.\n"); + } + + if (record->fileNameBytes <= 2) { + goto nextEntry; + } + + if (!((nameBytes == record->fileNameBytes && 0 == EsMemoryCompare(record + 1, name, nameBytes)) + || (nameBytes + 2 == record->fileNameBytes && 0 == EsMemoryCompare(record + 1, name, nameBytes) + && 0 == EsMemoryCompare((char *) (record + 1) + record->fileNameBytes - 2, ";1", 2)))) { + goto nextEntry; + } + + if (_record) { + EsMemoryCopy(_record, record, sizeof(DirectoryRecord)); + return ES_SUCCESS; + } + + { + KNodeMetadata metadata = {}; + + metadata.type = (record->flags & (1 << 1)) ? ES_NODE_DIRECTORY : ES_NODE_FILE; + + if (metadata.type == ES_NODE_DIRECTORY) { + metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN; + metadata.totalSize = 0; + } else if (metadata.type == ES_NODE_FILE) { + metadata.totalSize = record->extentSize.x; + } + + DirectoryRecordReference reference = {}; + reference.sector = currentSector; + reference.offset = positionInSector; + + return FSDirectoryEntryFound(_directory, &metadata, &reference, + name, nameBytes, false); + } + + nextEntry:; + positionInSector += record->length; + } + + if (remainingBytes < SECTOR_SIZE) { + remainingBytes = 0; + } else { + remainingBytes -= SECTOR_SIZE; + } + } + + return ES_ERROR_FILE_DOES_NOT_EXIST; +} + +static EsError Scan(const char *name, size_t nameBytes, KNode *_directory) { + return ScanInternal(name, nameBytes, _directory); +} + +static EsError Load(KNode *_directory, KNode *_node, KNodeMetadata *, const void *entryData) { + DirectoryRecordReference reference = *(DirectoryRecordReference *) entryData; + FSNode *directory = (FSNode *) _directory->driverNode; + Volume *volume = directory->volume; + + uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED); + + if (!sectorBuffer) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED)); + + if (!volume->fileSystem->Access(reference.sector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT)) { + return ES_ERROR_DRIVE_CONTROLLER_REPORTED; + } + + FSNode *data = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!data) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + data->volume = volume; + _node->driverNode = data; + + DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + reference.offset); + EsMemoryCopy(&data->record, record, sizeof(DirectoryRecord)); + + return ES_SUCCESS; +} + +static void Close(KNode *node) { + EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED); +} + +static void DeviceAttach(KDevice *parent) { + Volume *volume = (Volume *) KDeviceCreate("ISO9660", parent, sizeof(Volume)); + + if (!volume) { + KernelLog(LOG_ERROR, "ISO9660", "allocate error", "EntryISO9660 - Could not allocate Volume structure.\n"); + return; + } + + volume->fileSystem = (KFileSystem *) parent; + + if (volume->fileSystem->block->sectorSize != SECTOR_SIZE) { + KernelLog(LOG_ERROR, "ISO9660", "incorrect sector size", "EntryISO9660 - Expected 2KB sectors, but drive's sectors are %D.\n", + volume->fileSystem->block->sectorSize); + KDeviceDestroy(volume); + return; + } + + if (!Mount(volume)) { + KernelLog(LOG_ERROR, "ISO9660", "mount failure", "EntryISO9660 - Could not mount ISO9660 volume.\n"); + KDeviceDestroy(volume); + return; + } + + volume->fileSystem->read = Read; + volume->fileSystem->scan = Scan; + volume->fileSystem->load = Load; + volume->fileSystem->enumerate = Enumerate; + volume->fileSystem->close = Close; + + volume->fileSystem->spaceUsed = volume->primaryDescriptor.volumeSize.x * volume->primaryDescriptor.logicalBlockSize.x; + volume->fileSystem->spaceTotal = volume->fileSystem->spaceUsed; + + volume->fileSystem->nameBytes = sizeof(volume->primaryDescriptor.volumeIdentifier); + if (volume->fileSystem->nameBytes > sizeof(volume->fileSystem->name)) volume->fileSystem->nameBytes = sizeof(volume->fileSystem->name); + EsMemoryCopy(volume->fileSystem->name, volume->primaryDescriptor.volumeIdentifier, volume->fileSystem->nameBytes); + + for (intptr_t i = volume->fileSystem->nameBytes - 1; i >= 0; i--) { + if (volume->fileSystem->name[i] == ' ') { + volume->fileSystem->nameBytes--; + } else { + break; + } + } + + volume->fileSystem->directoryEntryDataBytes = sizeof(DirectoryRecordReference); + volume->fileSystem->nodeDataBytes = sizeof(FSNode); + + KernelLog(LOG_INFO, "ISO9660", "register file system", "EntryISO9660 - Registering file system with name '%s'.\n", + volume->fileSystem->nameBytes, volume->fileSystem->name); + FSRegisterFileSystem(volume->fileSystem); +} + +KDriver driverISO9660 = { + .attach = DeviceAttach, +}; diff --git a/drivers/ntfs.cpp b/drivers/ntfs.cpp new file mode 100644 index 0000000..9c0b423 --- /dev/null +++ b/drivers/ntfs.cpp @@ -0,0 +1,1265 @@ +// TODO Validation of all fields. +// TODO Update to the latest file subsystem changes. +// - Set spaceUsed and spaceTotal. + +#include + +struct BootSector { + uint8_t jump[3]; + char signature[8]; + uint16_t bytesPerSector; + uint8_t sectorsPerCluster; + uint16_t reservedSectors; + uint8_t unused0[3]; + uint16_t unused1; + uint8_t media; + uint16_t unused2; + uint16_t sectorsPerTrack; + uint16_t headsPerCylinder; + uint32_t hiddenSectors; + uint32_t unused3; + uint32_t unused4; + uint64_t totalSectors; + uint64_t mftStart; + uint64_t mftMirrorStart; + int8_t clustersPerFileRecord; + uint8_t unused5[3]; + int8_t clustersPerIndexBlock; + uint8_t unused6[3]; + uint64_t serialNumber; + uint32_t checksum; + uint8_t bootloader[426]; + uint16_t bootSignature; +} __attribute__((packed)); + +struct FileRecordHeader { + uint32_t magic; + uint16_t updateSequenceOffset; + uint16_t updateSequenceSize; + uint64_t logSequence; + uint16_t sequenceNumber; + uint16_t hardLinkCount; + uint16_t firstAttributeOffset; + uint16_t flags; + uint32_t usedSize; + uint32_t allocatedSize; + uint64_t fileReference; + uint16_t nextAttributeID; + uint16_t unused; + uint32_t recordNumber; +} __attribute__((packed)); + +struct AttributeHeader { + uint32_t attributeType; + uint32_t length; + uint8_t nonResident; + uint8_t nameLength; + uint16_t nameOffset; + uint16_t flags; + uint16_t attributeID; +} __attribute__((packed)); + +struct ResidentAttributeHeader : AttributeHeader { + uint32_t attributeLength; + uint16_t attributeOffset; + uint8_t indexed; + uint8_t unused; +} __attribute__((packed)); + +struct FileNameAttributeHeader { + uint64_t parentRecordNumber : 48; + uint64_t parentSequenceNumber : 16; + uint64_t creationTime; + uint64_t modificationTime; + uint64_t metadataModificationTime; + uint64_t readTime; + uint64_t allocatedSize; + uint64_t realSize; + uint32_t flags; + uint32_t reparse; + uint8_t fileNameLength; + uint8_t namespaceType; +} __attribute__((packed)); + +struct NonResidentAttributeHeader : AttributeHeader { + uint64_t firstCluster; + uint64_t lastCluster; + uint16_t dataRunsOffset; + uint16_t compressionUnit; + uint32_t unused; + uint64_t attributeAllocated; + uint64_t attributeSize; + uint64_t streamDataSize; +} __attribute__((packed)); + +struct RunHeader { + uint8_t lengthFieldBytes : 4; + uint8_t offsetFieldBytes : 4; +} __attribute__((packed)); + +struct IndexBlockHeader { + uint32_t magic; + uint16_t updateSequenceOffset; + uint16_t updateSequenceSize; + uint64_t logSequence; + uint64_t selfVirtualCluster; + uint32_t firstEntryOffset; + uint32_t totalEntrySize; + uint32_t allocatedSize; + uint32_t flags; +} __attribute__((packed)); + +struct IndexRootHeader { + uint32_t attributeType; + uint32_t collationRule; + uint32_t bytesPerIndexRecord; + uint8_t clustersPerIndexRecord; + uint8_t unused[3]; + uint32_t firstEntryOffset; + uint32_t totalEntrySize; + uint32_t allocatedSize; + uint32_t flags; +} __attribute__((packed)); + +struct IndexEntryHeader { + uint64_t fileRecordNumber : 48; + uint64_t fileSequenceNumber : 16; + uint16_t indexEntryLength; + uint16_t streamLength; + uint32_t flags; +} __attribute__((packed)); + +// TODO Support other MFT file record sizes. +#define MFT_FILE_SIZE (1024) + +struct FSNode { + struct Volume *volume; + + union { + uint8_t fileRecord[MFT_FILE_SIZE]; + FileRecordHeader header; + }; +}; + +struct Volume : KDevice { + KFileSystem *fileSystem; + KNode *root; + BootSector bootSector; + + size_t clusterBytes, + mftRecordBytes, + indexBlockBytes, + clusterCount, + sectorBytes; + + FSNode mftNode, volumeNode; + + uint16_t *upCaseLookup[256]; +}; + +static uint16_t ToUpper(Volume *volume, uint16_t character) { + uint16_t *table = volume->upCaseLookup[character >> 8]; + return table ? table[character & 0xFF] : character; +} + +static uint64_t GetDataRunLength(RunHeader *dataRun) { + uint64_t length = 0; + + for (int i = 0; i < dataRun->lengthFieldBytes; i++) { + length |= (uint64_t) (((uint8_t *) dataRun)[1 + i]) << (i * 8); + } + + return length; +} + +static uint64_t GetDataRunOffset(RunHeader *dataRun) { + uint64_t offset = 0; + + for (int i = 0; i < dataRun->offsetFieldBytes; i++) { + offset |= (uint64_t) (((uint8_t *) dataRun)[1 + dataRun->lengthFieldBytes + i]) << (i * 8); + } + + if (offset & ((uint64_t) 1 << (dataRun->offsetFieldBytes * 8 - 1))) { + for (int i = dataRun->offsetFieldBytes; i < 8; i++) { + offset |= (uint64_t) 0xFF << (i * 8); + } + } + + return offset; +} + +static bool ApplyFixup(Volume *volume, uint8_t *buffer, uint16_t fixupOffset, uint16_t fixupSize, size_t bufferBytes) { +#define FIXUP_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "fixup failure", "ApplyFixup - " message); return false; } while (0) + + // TODO Is the aliasing done by this function optimizations-safe? + + size_t sectorCount = bufferBytes / volume->sectorBytes; + + if ((fixupOffset & 1) || (fixupOffset + fixupSize > bufferBytes) || ((size_t) (fixupSize - 1) != sectorCount)) { + FIXUP_FAILURE("Invalid offset/size.\n"); + } + + uint16_t *fixupArray = (uint16_t *) (buffer + fixupOffset); + + for (uintptr_t i = 0; i < sectorCount; i++) { + uint16_t *position = (uint16_t *) (buffer + (i + 1) * volume->sectorBytes - 2); + + if (*position != fixupArray[0]) { + FIXUP_FAILURE("Incorrect sequence number.\n"); + } + + *position = fixupArray[i + 1]; + } + + return true; +} + +static bool ValidateFileRecord(Volume *volume, FSNode *node, bool isDirectory) { +#define VALIDATE_FILE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "validate file record failure", "ValidateFileRecord - " message); return false; } while (0) + + FileRecordHeader *header = &node->header; + + if (header->magic != 0x454C4946) { + VALIDATE_FILE_FAILURE("Invalid magic number.\n"); + } + + if (!ApplyFixup(volume, node->fileRecord, header->updateSequenceOffset, header->updateSequenceSize, MFT_FILE_SIZE)) { + return false; + } + + if (~header->flags & (1 << 0)) { + VALIDATE_FILE_FAILURE("Record not in use.\n"); + } + + if (((header->flags & (1 << 1)) ? true : false) != isDirectory) { + VALIDATE_FILE_FAILURE("Incorrect isDirectory flag.\n"); + } + + if (header->allocatedSize != MFT_FILE_SIZE || header->usedSize > MFT_FILE_SIZE) { + VALIDATE_FILE_FAILURE("Invalid file header allocated/used size.\n"); + } + + if (header->firstAttributeOffset > MFT_FILE_SIZE - sizeof(AttributeHeader)) { + VALIDATE_FILE_FAILURE("Invalid first attribute offset.\n"); + } + + return true; +} + +static AttributeHeader *FindAttribute(Volume *, FSNode *node, uint8_t type, AttributeHeader *startingFrom = nullptr, bool optional = false) { +#define FIND_ATTRIBUTE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "find attribute failure", "FindAttribute - " message); return nullptr; } while (0) + + AttributeHeader *attribute = startingFrom ?: (AttributeHeader *) (node->fileRecord + node->header.firstAttributeOffset); + bool skip = startingFrom ? true : false; + + while (true) { + if (attribute->attributeType == type && !skip) { + return attribute; + } else if (attribute->attributeType == 0xFFFFFFFF) { + if (optional) return nullptr; + FIND_ATTRIBUTE_FAILURE("Could not find attribute.\n"); + } + + skip = false; + + if ((size_t) ((uint8_t *) attribute - node->fileRecord + attribute->length) > MFT_FILE_SIZE - sizeof(AttributeHeader) + || attribute->length < sizeof(AttributeHeader)) { + FIND_ATTRIBUTE_FAILURE("Invalid attribute length.\n"); + } + + attribute = (AttributeHeader *) ((uint8_t *) attribute + attribute->length); + } +} + +static bool ReadFileCluster(Volume *volume, FSNode *node, uint64_t startCluster, void *_buffer) { +#define READ_FILE_CLUSTERS_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "read file clusters failure", "ReadFileClusters - " message); return false; } while (0) + + uint8_t *buffer = (uint8_t *) _buffer; + + // Find the data attribute. + + NonResidentAttributeHeader *dataHeader = (NonResidentAttributeHeader *) FindAttribute(volume, node, 0x80); + + if (!dataHeader) { + // TODO Support $ATTRIBUTE_LIST. + READ_FILE_CLUSTERS_FAILURE("Could not find data attribute.\n"); + } + + if (dataHeader->length < sizeof(NonResidentAttributeHeader)) { + READ_FILE_CLUSTERS_FAILURE("Data attribute is too short.\n"); + } + + if (!dataHeader->nonResident) { + // TODO Resident data attribute. + READ_FILE_CLUSTERS_FAILURE("Data attribute is resident.\n"); + } + + if (dataHeader->flags) { + READ_FILE_CLUSTERS_FAILURE("Data attribute has unsupported flags.\n"); + } + + // Find the data run containing the requested clusters. + + RunHeader *dataRun = (RunHeader *) ((uint8_t *) dataHeader + dataHeader->dataRunsOffset); + uint64_t clusterNumber = 0; + bool foundStart = false; + uint64_t clusterCount = 1; + + KWorkGroup dispatchGroup = {}; + dispatchGroup.Initialise(); + + while (((uint8_t *) dataRun - (uint8_t *) dataHeader) < dataHeader->length && dataRun->lengthFieldBytes) { + uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun); + + clusterNumber += offset; + dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes); + + if (!foundStart) { + if (length <= startCluster) { + startCluster -= length; + continue; + } else { + foundStart = true; + } + } + + if (foundStart) { + uint64_t firstClusterToRead = clusterNumber + startCluster; + uint64_t clustersToRead = 1; + startCluster = 0; + + // Read the clusters. + + volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes, + clustersToRead * volume->clusterBytes, K_ACCESS_READ, + buffer, ES_FLAGS_DEFAULT, &dispatchGroup); + + clusterCount -= clustersToRead; + buffer += clustersToRead * volume->clusterBytes; + + if (!clusterCount) { + break; + } + } + } + + if (clusterCount) { + READ_FILE_CLUSTERS_FAILURE("Trying to read past the end of the data runs.\n"); + } + + // Wait for outstanding IO operations to complete. + + bool success = dispatchGroup.Wait(); + + if (!success) { + READ_FILE_CLUSTERS_FAILURE("Could not read clusters from block device.\n"); + } + + return true; +} + +static bool ReadFileRecord(KNode *kNode, FSNode *node, uint64_t index, EsNodeType nodeType, void *clusterBuffer) { +#define READ_FILE_RECORD_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "read file record failure", "ReadFileRecord - " message); return false; } while (0) + + Volume *volume = node->volume; + + // Work out the cluster containing the file record. + + uint64_t cluster = index * MFT_FILE_SIZE / volume->clusterBytes; + uint64_t offsetInCluster = index * MFT_FILE_SIZE % volume->clusterBytes; + + // Read this cluster. + + if (!ReadFileCluster(volume, &volume->mftNode, cluster, clusterBuffer)) { + READ_FILE_RECORD_FAILURE("Could not read the file record from the MFT.\n"); + } + + // Copy into the node. + + EsMemoryCopy(node->fileRecord, (uint8_t *) clusterBuffer + offsetInCluster, MFT_FILE_SIZE); + + if (!ValidateFileRecord(volume, node, nodeType == ES_NODE_DIRECTORY)) { + return false; + } + + // Store information about the node. + + if (!kNode) { + return true; + } + + if (nodeType == ES_NODE_FILE) { + AttributeHeader *dataHeader = (AttributeHeader *) FindAttribute(volume, node, 0x80); + + if (!dataHeader) { + READ_FILE_RECORD_FAILURE("File did not have a data attribute.\n"); + } + + if (dataHeader->nonResident) { + kNode->data.fileSize = ((NonResidentAttributeHeader *) dataHeader)->attributeSize; + } else { + kNode->data.fileSize = ((ResidentAttributeHeader *) dataHeader)->attributeLength; + } + + kNode->data.type = ES_NODE_FILE; + } else if (nodeType == ES_NODE_DIRECTORY) { + NonResidentAttributeHeader *indexAllocationHeader = (NonResidentAttributeHeader *) FindAttribute(volume, node, 0xA0, nullptr, true); + + if (!indexAllocationHeader || indexAllocationHeader->flags || !indexAllocationHeader->nonResident) { + kNode->data.directoryChildren = MFT_FILE_SIZE / 16; + } else { + // TODO This is a terrible upper estimate! Find a better one? + kNode->data.directoryChildren = indexAllocationHeader->attributeSize / 16; + } + + kNode->data.type = ES_NODE_DIRECTORY; + } + + return true; +} + +static bool Mount(Volume *volume) { +#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "mount failure", "Mount - " message); return false; } while (0) + + // Load the boot sector. + + { + void *buffer = EsHeapAllocate(volume->fileSystem->block->sectorSize, false, K_FIXED); + + if (!buffer) { + MOUNT_FAILURE("Could not allocate buffer.\n"); + } + + EsDefer(EsHeapFree(buffer, volume->fileSystem->block->sectorSize, K_FIXED)); + + if (!volume->fileSystem->Access(0, volume->fileSystem->block->sectorSize, K_ACCESS_READ, buffer, ES_FLAGS_DEFAULT)) { + MOUNT_FAILURE("Could not read boot sector.\n"); + } + + EsMemoryCopy(&volume->bootSector, buffer, sizeof(BootSector)); + + volume->sectorBytes = volume->bootSector.bytesPerSector; + volume->clusterBytes = volume->bootSector.sectorsPerCluster * volume->bootSector.bytesPerSector; + volume->mftRecordBytes = volume->bootSector.clustersPerFileRecord >= 0 + ? volume->bootSector.clustersPerFileRecord * volume->clusterBytes : (1 << -volume->bootSector.clustersPerFileRecord); + volume->indexBlockBytes = volume->bootSector.clustersPerIndexBlock >= 0 + ? volume->bootSector.clustersPerIndexBlock * volume->clusterBytes : (1 << -volume->bootSector.clustersPerIndexBlock); + volume->clusterCount = volume->fileSystem->block->sectorCount / volume->bootSector.sectorsPerCluster; + + if (volume->bootSector.bytesPerSector != volume->fileSystem->block->sectorSize) MOUNT_FAILURE("Invalid bytes per sector.\n"); + if ((volume->bootSector.sectorsPerCluster - 1) & volume->bootSector.sectorsPerCluster) MOUNT_FAILURE("Invalid sectors per cluster.\n"); + if (volume->mftRecordBytes != MFT_FILE_SIZE) MOUNT_FAILURE("Unsupported MFT record size.\n"); + if (volume->mftRecordBytes > volume->clusterBytes) MOUNT_FAILURE("MFT record size bigger than cluster size.\n"); + if (volume->indexBlockBytes % volume->clusterBytes) MOUNT_FAILURE("Index block size is not a multiple of the cluster size.\n"); + if (volume->bootSector.mftStart >= volume->clusterCount) MOUNT_FAILURE("MFT starts past end of volume.\n"); + } + + void *buffer = EsHeapAllocate(volume->clusterBytes, false, K_FIXED); + + if (!buffer) { + MOUNT_FAILURE("Could not allocate buffer.\n"); + } + + EsDefer(EsHeapFree(buffer, volume->clusterBytes, K_FIXED)); + + // Load the MFT. + + { + if (!volume->fileSystem->Access(volume->bootSector.mftStart * volume->clusterBytes, + volume->clusterBytes, K_ACCESS_READ, buffer, ES_FLAGS_DEFAULT)) { + MOUNT_FAILURE("Could not read MFT file.\n"); + } + + EsMemoryCopy(volume->mftNode.fileRecord, buffer, MFT_FILE_SIZE); + + if (!ValidateFileRecord(volume, &volume->mftNode, false)) { + return false; + } + } + + // Load the volume node. + + { + volume->volumeNode.volume = volume; + + if (!ReadFileRecord(nullptr, &volume->volumeNode, 3, ES_NODE_FILE, buffer)) { + return false; + } + } + + // Load the root directory. + + { + volume->root = FSNodeAllocate(); + + if (!volume->root) { + MOUNT_FAILURE("Could not allocate root node.\n"); + } + + volume->root->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!volume->root->driverNode) { + MOUNT_FAILURE("Could not allocate root node.\n"); + } + + FSNode *root = (FSNode *) volume->root->driverNode; + root->volume = volume; + + if (!ReadFileRecord(volume->root, root, 5, ES_NODE_DIRECTORY, buffer)) { + return false; + } + + if (!FSRegisterNewNode(volume->root, nullptr, ES_NODE_DIRECTORY, EsLiteral("$Root"))) { + MOUNT_FAILURE("Could not register root directory node.\n"); + } + } + + // Load $UpCase. + + { + FSNode *upCase = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!upCase) { + MOUNT_FAILURE("Could not allocate upCase node.\n"); + } + + EsDefer(EsHeapFree(upCase, sizeof(FSNode), K_FIXED)); + + upCase->volume = volume; + + if (!ReadFileRecord(nullptr, upCase, 10, ES_NODE_FILE, buffer)) { + return false; + } + + for (uintptr_t i = 0; i < 131072 / volume->clusterBytes; i++) { + if (!ReadFileCluster(volume, upCase, i, buffer)) { + return false; + } + + for (uint16_t j = 0, codepoint = i * volume->clusterBytes / 2; j < volume->clusterBytes / 2; j++, codepoint++) { + uint16_t upperCase = ((uint16_t *) buffer)[j]; + if (codepoint == upperCase) continue; + + uint8_t msb = codepoint >> 8, lsb = codepoint & 0xFF; + + if (!volume->upCaseLookup[msb]) { + volume->upCaseLookup[msb] = (uint16_t *) EsHeapAllocate(512, false, K_FIXED); + + if (!volume->upCaseLookup[msb]) { + MOUNT_FAILURE("Could not allocate upCase table.\n"); + } + + for (uintptr_t i = 0; i < 256; i++) { + volume->upCaseLookup[msb][i] = (msb << 8) | i; + } + } + + volume->upCaseLookup[msb][lsb] = upperCase; + } + } + } + + return true; +} + +#define BAD_ATTRIBUTE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "bad attribute failure", "GetIndexAllocationHeader - " message); return nullptr; } while (0) + +static NonResidentAttributeHeader *GetIndexAllocationHeader(Volume *volume, FSNode *directory) { + NonResidentAttributeHeader *indexAllocationHeader = (NonResidentAttributeHeader *) FindAttribute(volume, directory, 0xA0); + + if (!indexAllocationHeader) { + // TODO Support $ATTRIBUTE_LIST. + BAD_ATTRIBUTE_FAILURE("Could not find index allocation attribute.\n"); + } + + if (indexAllocationHeader->length < sizeof(NonResidentAttributeHeader)) { + BAD_ATTRIBUTE_FAILURE("Index allocation attribute is too short.\n"); + } + + if (!indexAllocationHeader->nonResident) { + BAD_ATTRIBUTE_FAILURE("Index allocation attribute is resident.\n"); + } + + if (indexAllocationHeader->flags) { + BAD_ATTRIBUTE_FAILURE("Index allocation attribute has unsupported flags.\n"); + } + + return indexAllocationHeader; +} + +#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "enumerate failure", "Enumerate - " message); return ES_ERROR_UNKNOWN; } while (0) + +static EsError EnumerateIndexEntry(IndexEntryHeader *entry, uint8_t *bufferLimit, KNode *directory) { + if (entry->indexEntryLength < sizeof(IndexEntryHeader)) { + ENUMERATE_FAILURE("Invalid index entry length.\n"); + } + + if (entry->flags & (1 << 1)) { + return ES_SUCCESS; + } + + FileNameAttributeHeader *fileNameAttribute = (FileNameAttributeHeader *) (entry + 1); + + if ((uint8_t *) fileNameAttribute + fileNameAttribute->fileNameLength * 2 > bufferLimit) { + ENUMERATE_FAILURE("File name attribute has length too large.\n"); + } + + if ((fileNameAttribute->namespaceType == 1 || fileNameAttribute->namespaceType == 3) && entry->fileRecordNumber >= 24) { + KNodeMetadata metadata = {}; + + metadata.type = (fileNameAttribute->flags & 0x10000000) ? ES_NODE_DIRECTORY : ES_NODE_FILE; + + if (metadata.type == ES_NODE_FILE) { + metadata.totalSize = fileNameAttribute->realSize; + } else { + metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN; + } + + uintptr_t namePosition = 0; + uint16_t *fileName = (uint16_t *) (fileNameAttribute + 1); + size_t inputCharactersRemaining = fileNameAttribute->fileNameLength; + char childName[ES_MAX_DIRECTORY_CHILD_NAME_LENGTH]; + size_t childNameBytes = 0; + + while (inputCharactersRemaining) { + uint32_t c = *fileName; + inputCharactersRemaining--; + fileName++; + + if (c >= 0xD800 && c < 0xDC00 && inputCharactersRemaining) { + uint32_t c2 = *fileName; + + if (c2 >= 0xDC00 && c2 < 0xE000) { + inputCharactersRemaining--; + fileName++; + + c = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; + } + } + + size_t encodedBytes = utf8_encode(c, nullptr); + + if (namePosition + encodedBytes <= ES_MAX_DIRECTORY_CHILD_NAME_LENGTH) { + utf8_encode(c, childName + namePosition); + namePosition += encodedBytes; + childNameBytes += encodedBytes; + } else { + // Name too long. + // TODO Now what? + } + } + + EsError error = FSDirectoryEntryFound(directory, &metadata, + &reference, sizeof(DirectoryEntryReference), + childName, childNameBytes, false); + + if (error != ES_SUCCESS) { + return error; + } + } + + return ES_SUCCESS; +} + +static EsError EnumerateIndexNode(Volume *volume, uint8_t *buffer, uint64_t clustersRead, KNode *directory) { + for (uintptr_t indexBlock = 0; indexBlock < volume->clusterBytes * clustersRead / volume->indexBlockBytes; indexBlock++) { + uint8_t *blockStart = buffer + indexBlock * volume->indexBlockBytes; + + IndexBlockHeader *header = (IndexBlockHeader *) blockStart; + + if (header->magic != 0x58444E49) { + ENUMERATE_FAILURE("Index block has incorrect magic number.\n"); + } + + if (!ApplyFixup(volume, blockStart, header->updateSequenceOffset, header->updateSequenceSize, volume->indexBlockBytes)) { + return ES_ERROR_CORRUPT_DATA; + } + + if (header->firstEntryOffset > volume->indexBlockBytes || header->firstEntryOffset + header->totalEntrySize > volume->indexBlockBytes) { + ENUMERATE_FAILURE("Index block has incorrect entries offset/size.\n"); + } + + uint8_t *entriesPosition = blockStart + header->firstEntryOffset + 0x18; + uint8_t *entriesEnd = entriesPosition + header->totalEntrySize; + + while (entriesPosition + sizeof(IndexEntryHeader) <= entriesEnd) { + IndexEntryHeader *entry = (IndexEntryHeader *) entriesPosition; + EsError error = EnumerateIndexEntry(entry, blockStart + header->firstEntryOffset + header->totalEntrySize, directory); + if (error != ES_SUCCESS) return error; + if (entry->flags & (1 << 1)) break; + entriesPosition += entry->indexEntryLength; + } + } + + return ES_SUCCESS; +} + +static EsError Enumerate(KNode *node) { + FSNode *directory = (FSNode *) node->driverNode; + Volume *volume = directory->volume; + + size_t bufferSize = volume->indexBlockBytes > volume->clusterBytes ? volume->indexBlockBytes : volume->clusterBytes; + size_t clustersPerBuffer = bufferSize / volume->clusterBytes; + + uint8_t *buffer = (uint8_t *) EsHeapAllocate(bufferSize, false, K_FIXED); + + if (!buffer) { + ENUMERATE_FAILURE("Could not allocate buffer.\n"); + } + + EsDefer(EsHeapFree(buffer, bufferSize, K_FIXED)); + + // Parse entries in the index root. + + ResidentAttributeHeader *indexRootHeader = (ResidentAttributeHeader *) FindAttribute(volume, directory, 0x90); + + if (!indexRootHeader || indexRootHeader->nonResident || indexRootHeader->attributeLength < sizeof(IndexRootHeader) + || indexRootHeader->attributeOffset > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord) + || indexRootHeader->attributeOffset + indexRootHeader->attributeLength > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord)) { + ENUMERATE_FAILURE("Invalid index root attribute.\n"); + } + + { + IndexRootHeader *header = (IndexRootHeader *) ((uint8_t *) indexRootHeader + indexRootHeader->attributeOffset); + + if (header->firstEntryOffset > indexRootHeader->attributeLength + || header->totalEntrySize > indexRootHeader->attributeLength - header->firstEntryOffset) { + ENUMERATE_FAILURE("Index root has incorrect entries offset/size.\n"); + } + + uint8_t *entriesPosition = (uint8_t *) header + header->firstEntryOffset + 0x10; + uint8_t *entriesEnd = entriesPosition + header->totalEntrySize; + + while (entriesPosition + sizeof(IndexEntryHeader) <= entriesEnd) { + IndexEntryHeader *entry = (IndexEntryHeader *) entriesPosition; + EnumerateIndexEntry(entry, entriesEnd, node); + if (entry->flags & (1 << 1)) break; + entriesPosition += entry->indexEntryLength; + } + } + + if (!FindAttribute(volume, directory, 0xA0, nullptr, true)) { + return true; + } + + // Get the index allocation attribute. + + NonResidentAttributeHeader *indexAllocationHeader = GetIndexAllocationHeader(volume, directory); + + if (!indexAllocationHeader) { + return false; + } + + // For every data run... + + RunHeader *dataRun = (RunHeader *) ((uint8_t *) indexAllocationHeader + indexAllocationHeader->dataRunsOffset); + uint64_t clusterNumber = 0; + uintptr_t clustersInBuffer = 0; + + while (((uint8_t *) dataRun - (uint8_t *) indexAllocationHeader) < indexAllocationHeader->length && dataRun->lengthFieldBytes) { + uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun); + + clusterNumber += offset; + dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes); + + uint64_t positionInRun = 0; + + while (positionInRun != length) { + size_t clustersToRead = (clustersPerBuffer - clustersInBuffer > length - positionInRun) + ? (length - positionInRun) : (clustersPerBuffer - clustersInBuffer); + + volume->fileSystem->Access((clusterNumber + positionInRun) * volume->clusterBytes, + clustersToRead * volume->clusterBytes, K_ACCESS_READ, + buffer + clustersInBuffer * volume->clusterBytes, ES_FLAGS_DEFAULT); + + clustersInBuffer += clustersToRead; + positionInRun += clustersToRead; + + if (clustersInBuffer == clustersPerBuffer) { + EsError error = EnumerateIndexNode(volume, buffer, clustersPerBuffer, node); + if (error != ES_SUCCESS) return error; + clustersInBuffer = 0; + } + } + } + + return ES_SUCCESS; +} + +static KNode *Scan(const char *_name, size_t _nameBytes, KNode *_directory) { +#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "scan failure", "Scan - " message); return nullptr; } while (0) + + uint16_t *searchName = (uint16_t *) EsHeapAllocate(256 * sizeof(uint16_t), false, K_FIXED); + size_t searchNameCharacters = 0; + + if (!searchName) { + SCAN_FAILURE("Could not allocate buffer for search name.\n"); + } + + EsDefer(EsHeapFree(searchName, 256 * sizeof(uint16_t), K_FIXED)); + + const char *_name2 = _name; + size_t _nameBytes2 = _nameBytes; + + while (_nameBytes) { + size_t lengthBytes = utf8_length_char(_name); + if (_nameBytes < lengthBytes) { + SCAN_FAILURE("Invalid search name.\n"); + } + + uint32_t value = utf8_value(_name); + + if (value <= 0xFFFF) { + if (searchNameCharacters >= 255) return nullptr; + searchName[searchNameCharacters++] = value; + } else { + if (searchNameCharacters >= 254) return nullptr; + value -= 0x10000; + searchName[searchNameCharacters++] = 0xD800 | ((value >> 10) & 0x3FF); + searchName[searchNameCharacters++] = 0xDC00 | ((value >> 0) & 0x3FF); + } + + _name += lengthBytes; + _nameBytes -= lengthBytes; + } + + FSNode *directory = (FSNode *) _directory->driverNode; + Volume *volume = directory->volume; + + uint8_t *buffer = (uint8_t *) EsHeapAllocate(volume->indexBlockBytes, false, K_FIXED); + + if (!buffer) { + SCAN_FAILURE("Could not allocate buffer for index block.\n"); + } + + EsDefer(EsHeapFree(buffer, volume->indexBlockBytes, K_FIXED)); + + bool hasIndexAllocation = FindAttribute(volume, directory, 0xA0, nullptr, true) ? true : false; + NonResidentAttributeHeader *indexAllocationHeader = hasIndexAllocation ? GetIndexAllocationHeader(volume, directory) : nullptr; + ResidentAttributeHeader *indexRootHeader = (ResidentAttributeHeader *) FindAttribute(volume, directory, 0x90); + + if ((!indexAllocationHeader && hasIndexAllocation) || !indexRootHeader) { + SCAN_FAILURE("Could not find necessary attributes.\n"); + } + + if (indexRootHeader->nonResident || indexRootHeader->attributeLength < sizeof(IndexRootHeader) + || indexRootHeader->attributeOffset > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord) + || indexRootHeader->attributeOffset + indexRootHeader->attributeLength > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord)) { + SCAN_FAILURE("Invalid index root attribute.\n"); + } + + IndexRootHeader *indexRoot = (IndexRootHeader *) ((uint8_t *) indexRootHeader + indexRootHeader->attributeOffset); + + if (indexRoot->firstEntryOffset > indexRootHeader->attributeLength + || indexRoot->firstEntryOffset + indexRoot->totalEntrySize > indexRootHeader->attributeLength + || indexRoot->totalEntrySize < sizeof(IndexEntryHeader) + || indexRoot->attributeType != 0x30 /* filenames */ + || indexRoot->collationRule != 1 /* Unicode strings */) { + SCAN_FAILURE("Invalid index root attribute (2).\n"); + } + + uint8_t *entriesPosition = (uint8_t *) indexRoot + indexRoot->firstEntryOffset + 0x10; + uint8_t *entriesEnd = entriesPosition + indexRoot->totalEntrySize; + bool hasSubNodes = indexRoot->flags & (1 << 0); + int depth = 0; + + while (entriesPosition + sizeof(IndexEntryHeader) <= entriesEnd) { + IndexEntryHeader *entry = (IndexEntryHeader *) entriesPosition; + + if (entry->streamLength > entry->indexEntryLength + || entriesPosition + entry->indexEntryLength > entriesEnd) { + SCAN_FAILURE("Invalid index entry.\n"); + } + + bool descend = false; + + if (entry->flags & (1 << 1)) { + // Last entry in the node. + descend = true; + } else { + if (entry->streamLength < sizeof(FileNameAttributeHeader)) { + SCAN_FAILURE("Index entry stream too short.\n"); + } + + FileNameAttributeHeader *fileNameHeader = (FileNameAttributeHeader *) (entry + 1); + + if (fileNameHeader->fileNameLength * 2 > entry->streamLength - sizeof(FileNameAttributeHeader)) { + SCAN_FAILURE("Index entry stream too short (2).\n"); + } + + if (fileNameHeader->namespaceType == 1 || fileNameHeader->namespaceType == 3) { + uint16_t *fileName = (uint16_t *) (fileNameHeader + 1); + size_t fileNameCharacters = fileNameHeader->fileNameLength; + + uintptr_t index = 0; + bool match = true; + + while (index != fileNameCharacters || index != searchNameCharacters) { + if (index == fileNameCharacters) { + match = false; + break; + } else if (index == searchNameCharacters) { + descend = true; + match = false; + break; + } + + uint16_t c1 = ToUpper(volume, fileName[index]), c2 = ToUpper(volume, searchName[index]); + + if (c1 != c2) { + if (c1 > c2) { + descend = true; + } + + match = false; + break; + } + + index++; + } + + if (match) { + // Found a match; open the node. + + KNode *child = FSNodeAllocate(); + + if (!child) { + SCAN_FAILURE("Could not allocate node.\n"); + } + + child->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED); + + if (!child->driverNode) { + EsHeapFree(child, 0, K_FIXED); + SCAN_FAILURE("Could not allocate node.\n"); + } + + FSNode *node = (FSNode *) child->driverNode; + node->volume = volume; + + if (!ReadFileRecord(child, node, entry->fileRecordNumber, + (fileNameHeader->flags & 0x10000000) ? ES_NODE_DIRECTORY : ES_NODE_FILE, + buffer)) { + EsHeapFree(node, sizeof(FSNode), K_FIXED); + EsHeapFree(child, 0, K_FIXED); + return nullptr; + } + + if (!FSRegisterNewNode(child, _directory, child->data.type, _name2, _nameBytes2)) { // TODO Case-insensitivity support in the VFS. + EsHeapFree(node, sizeof(FSNode), K_FIXED); + EsHeapFree(child, 0, K_FIXED); + SCAN_FAILURE("Could not register root directory node.\n"); + } + + return child; + } + } + } + + if (descend) { + if (!hasIndexAllocation) { + SCAN_FAILURE("Cannot descend in index without allocation attribute.\n"); + } + + if (!hasSubNodes || (~entry->flags & (1 << 0))) { + // Could not find the entry. + return nullptr; + } + + uint64_t nextVCN = *(uint64_t *) (entriesPosition + entry->indexEntryLength - sizeof(uint64_t)); + + // Descend to the next index level. + + if (++depth > 16) { + SCAN_FAILURE("Directory filename index too deep.\n"); + } + + RunHeader *dataRun = (RunHeader *) ((uint8_t *) indexAllocationHeader + indexAllocationHeader->dataRunsOffset); + + // Load the next index block. + + uint64_t clusterNumber = 0; + uint64_t positionInAllocation = 0; + bool foundStart = false; + + uintptr_t positionInBuffer = 0; + size_t clustersToRead = volume->indexBlockBytes / volume->clusterBytes; // TODO Assumes a cluster fits in an index block. + + while (((uint8_t *) dataRun - (uint8_t *) indexAllocationHeader) < indexAllocationHeader->length && dataRun->lengthFieldBytes) { + uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun); + + clusterNumber += offset; + + uintptr_t clusterIndex = clusterNumber; + + if (nextVCN >= positionInAllocation && nextVCN < positionInAllocation + length) { + foundStart = true; + clusterIndex += nextVCN - positionInAllocation; + length -= nextVCN - positionInAllocation; + } + + if (foundStart) { + size_t read = length > (clustersToRead - positionInBuffer) ? (clustersToRead - positionInBuffer) : length; + + if (!volume->fileSystem->Access(clusterIndex * volume->clusterBytes, + read * volume->clusterBytes, K_ACCESS_READ, + buffer + positionInBuffer * volume->clusterBytes, ES_FLAGS_DEFAULT)) { + SCAN_FAILURE("Could not read the clusters containing VCN to descend to.\n"); + } + + positionInBuffer += read; + + if (positionInBuffer == clustersToRead) { + break; + } + } + + positionInAllocation += length; + dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes); + } + + if (positionInBuffer != clustersToRead) { + SCAN_FAILURE("Could not find all the cluster containing VCN to descend to.\n"); + } + + IndexBlockHeader *header = (IndexBlockHeader *) buffer; + + if (header->magic != 0x58444E49) { + SCAN_FAILURE("Index block has incorrect magic number.\n"); + } + + if (!ApplyFixup(volume, buffer, header->updateSequenceOffset, header->updateSequenceSize, volume->indexBlockBytes)) { + return nullptr; + } + + if (header->firstEntryOffset > volume->indexBlockBytes + || header->totalEntrySize > volume->indexBlockBytes - header->firstEntryOffset) { + SCAN_FAILURE("Index block has incorrect entries offset/size.\n"); + } + + entriesPosition = (uint8_t *) header + header->firstEntryOffset + 0x18; + entriesEnd = entriesPosition + header->totalEntrySize; + hasSubNodes = header->flags & (1 << 0); + } else { + entriesPosition += entry->indexEntryLength; + } + } + + SCAN_FAILURE("The last entry in an index node did not have the last entry flag set.\n"); +} + +static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) { +#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0) + + FSNode *file = (FSNode *) node->driverNode; + Volume *volume = file->volume; + + uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(volume->clusterBytes * 2, false, K_FIXED); + + if (!clusterBuffer) { + READ_FAILURE("Could not allocate cluster buffer.\n"); + } + + EsDefer(EsHeapFree(clusterBuffer, volume->clusterBytes * 2, K_FIXED)); + + uint8_t *clusterBuffer2 = clusterBuffer + volume->clusterBytes; + uint8_t *outputBuffer = (uint8_t *) _buffer; + + // Find the data attribute. + + AttributeHeader *_dataHeader = (AttributeHeader *) FindAttribute(volume, file, 0x80); + + if (!_dataHeader) { + // TODO Support $ATTRIBUTE_LIST. + READ_FAILURE("Could not find data attribute.\n"); + } + + if (!_dataHeader->nonResident) { + // Resident data attribute. + + if (_dataHeader->length < sizeof(ResidentAttributeHeader)) { + READ_FAILURE("Data attribute is too short.\n"); + } + + ResidentAttributeHeader *residentDataHeader = (ResidentAttributeHeader *) _dataHeader; + + if (residentDataHeader->attributeOffset > MFT_FILE_SIZE - ((uint8_t *) residentDataHeader - file->fileRecord) + || residentDataHeader->attributeLength > MFT_FILE_SIZE - ((uint8_t *) residentDataHeader - file->fileRecord) + - residentDataHeader->attributeOffset) { + READ_FAILURE("Data attribute offset/length invalid.\n"); + } + + // Copy data into the buffer. + + uint8_t *residentData = (uint8_t *) residentDataHeader + residentDataHeader->attributeOffset; + + if (offset > residentDataHeader->attributeLength || count > residentDataHeader->attributeLength - offset) { + READ_FAILURE("Data attribute offset/length invalid (2).\n"); + } + + EsMemoryCopy(outputBuffer, residentData + offset, count); + return true; + } + + NonResidentAttributeHeader *dataHeader = (NonResidentAttributeHeader *) _dataHeader; + + if (dataHeader->length < sizeof(NonResidentAttributeHeader)) { + READ_FAILURE("Data attribute is too short.\n"); + } + + if (dataHeader->flags) { + READ_FAILURE("Data attribute has unsupported flags.\n"); + } + + // Find the data run containing the requested clusters. + + RunHeader *dataRun = (RunHeader *) ((uint8_t *) dataHeader + dataHeader->dataRunsOffset); + uint64_t clusterNumber = 0; + bool foundStart = false; + + uint64_t startCluster = offset / volume->clusterBytes; + uint64_t endCluster = (offset + count) / volume->clusterBytes; + uint64_t startOffset = offset - startCluster * volume->clusterBytes; + uint64_t endOffset = (offset + count) - endCluster * volume->clusterBytes; + + uint64_t clusterPosition = 0; + uint8_t *buffer = outputBuffer; + + KWorkGroup dispatchGroup = {}; + dispatchGroup.Initialise(); + + while (((uint8_t *) dataRun - (uint8_t *) dataHeader) < dataHeader->length && dataRun->lengthFieldBytes) { + uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun); + + clusterNumber += offset; + dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes); + + uint64_t runOffset = 0; + + if (!foundStart) { + if (length <= startCluster - clusterPosition) { + clusterPosition += length; + continue; + } else { + runOffset = startCluster - clusterPosition; + clusterPosition = startCluster; + foundStart = true; + } + } + + if (!foundStart) { + continue; + } + + while (runOffset < length && clusterPosition <= endCluster) { + uint64_t firstClusterToRead = clusterNumber + runOffset; + uint64_t clustersToRead = (endCluster - clusterPosition) > (length - runOffset) + ? (length - runOffset) : (endCluster - clusterPosition); + + if (clusterPosition == startCluster || clusterPosition == endCluster) { + clustersToRead = 1; + } + + // Read the clusters. + + if (clusterPosition == startCluster) { + volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes, + clustersToRead * volume->clusterBytes, K_ACCESS_READ, + clusterBuffer, ES_FLAGS_DEFAULT, &dispatchGroup); + buffer += volume->clusterBytes - startOffset; + } else if (clusterPosition == endCluster) { + volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes, + clustersToRead * volume->clusterBytes, K_ACCESS_READ, + clusterBuffer2, ES_FLAGS_DEFAULT, &dispatchGroup); + } else { + volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes, + clustersToRead * volume->clusterBytes, K_ACCESS_READ, + buffer, ES_FLAGS_DEFAULT, &dispatchGroup); + buffer += clustersToRead * volume->clusterBytes; + } + + clusterPosition += clustersToRead; + runOffset += clustersToRead; + } + } + + if (clusterPosition <= endCluster) { + READ_FAILURE("Trying to read past the end of the data runs.\n"); + } + + // Wait for outstanding IO operations to complete. + + bool success = dispatchGroup.Wait(); + + if (!success) { + READ_FAILURE("Could not read clusters from block device.\n"); + } + + // Copy first and last clusters into the buffer. + + if (startCluster == endCluster) { + EsMemoryCopy(outputBuffer, clusterBuffer + startOffset, count); + } else { + EsMemoryCopy(outputBuffer, clusterBuffer + startOffset, volume->clusterBytes - startOffset); + EsMemoryCopy(outputBuffer + count - endOffset, clusterBuffer2, endOffset); + } + + return true; +} + +static void Close(KNode *node) { + EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED); +} + +static void DeviceAttach(KDevice *parent) { + Volume *volume = (Volume *) KDeviceCreate("NTFS", parent, sizeof(Volume)); + + if (!volume) { + KernelLog(LOG_ERROR, "NTFS", "allocate error", "EntryNTFS - Could not allocate Volume structure.\n"); + return; + } + + volume->fileSystem = (KFileSystem *) parent; + + if (!Mount(volume)) { + KernelLog(LOG_ERROR, "NTFS", "mount failure", "EntryNTFS - Could not mount NTFS volume.\n"); + + if (volume->root) EsHeapFree(volume->root->driverNode, 0, K_FIXED); + EsHeapFree(volume->root, 0, K_FIXED); + + for (uintptr_t i = 0; i < 256; i++) { + if (volume->upCaseLookup[i]) { + EsHeapFree(volume->upCaseLookup[i], 512, K_FIXED); + } + } + + KDeviceDestroy(volume); + return; + } + + { + ResidentAttributeHeader *volumeNameHeader = (ResidentAttributeHeader *) FindAttribute(volume, &volume->volumeNode, 0x60); + + if (volumeNameHeader && !volumeNameHeader->nonResident + && (volumeNameHeader->attributeOffset + volumeNameHeader->attributeLength + + ((uint8_t *) volumeNameHeader - volume->volumeNode.fileRecord) <= MFT_FILE_SIZE)) { + volume->fileSystem->nameBytes = volumeNameHeader->attributeLength; + if (volume->fileSystem->nameBytes > sizeof(volume->fileSystem->name)) volume->fileSystem->nameBytes = sizeof(volume->fileSystem->name); + EsMemoryCopy(volume->fileSystem->name, (uint8_t *) volumeNameHeader + volumeNameHeader->attributeOffset, volume->fileSystem->nameBytes); + } + } + + volume->fileSystem->read = Read; + volume->fileSystem->scan = Scan; + volume->fileSystem->load = Load; + volume->fileSystem->enumerate = Enumerate; + volume->fileSystem->close = Close; + + KernelLog(LOG_INFO, "NTFS", "register file system", "EntryNTFS - Registering file system with name '%s'.\n", + volume->fileSystem->nameBytes, volume->fileSystem->name); + FSRegisterFileSystem(volume->fileSystem); +} + +KDriver driverNTFS = { + .attach = DeviceAttach; +}; diff --git a/drivers/nvme.cpp b/drivers/nvme.cpp new file mode 100644 index 0000000..5c209fa --- /dev/null +++ b/drivers/nvme.cpp @@ -0,0 +1,878 @@ +#include + +// TODO Sometimes completion interrupts get missed? +// TODO How many IO completion/submission queues should we create, and how many entries should they contain? +// TODO Command timeout. + +#define GENERAL_TIMEOUT (5000) + +#define RD_REGISTER_CAP() pci-> ReadBAR64(0, 0x00) // Controller capababilities. +#define WR_REGISTER_CAP(x) pci->WriteBAR64(0, 0x00, x) +#define RD_REGISTER_VS() pci-> ReadBAR32(0, 0x08) // Version. +#define WR_REGISTER_VS(x) pci->WriteBAR32(0, 0x08, x) +#define RD_REGISTER_INTMS() pci-> ReadBAR32(0, 0x0C) // Interrupt mask set. +#define WR_REGISTER_INTMS(x) pci->WriteBAR32(0, 0x0C, x) +#define RD_REGISTER_INTMC() pci-> ReadBAR32(0, 0x10) // Interrupt mask clear. +#define WR_REGISTER_INTMC(x) pci->WriteBAR32(0, 0x10, x) +#define RD_REGISTER_CC() pci-> ReadBAR32(0, 0x14) // Controller configuration. +#define WR_REGISTER_CC(x) pci->WriteBAR32(0, 0x14, x) +#define RD_REGISTER_CSTS() pci-> ReadBAR32(0, 0x1C) // Controller status. +#define WR_REGISTER_CSTS(x) pci->WriteBAR32(0, 0x1C, x) +#define RD_REGISTER_AQA() pci-> ReadBAR32(0, 0x24) // Admin queue attributes. +#define WR_REGISTER_AQA(x) pci->WriteBAR32(0, 0x24, x) +#define RD_REGISTER_ASQ() pci-> ReadBAR64(0, 0x28) // Admin submission queue base address. +#define WR_REGISTER_ASQ(x) pci->WriteBAR64(0, 0x28, x) +#define RD_REGISTER_ACQ() pci-> ReadBAR64(0, 0x30) // Admin completion queue base address. +#define WR_REGISTER_ACQ(x) pci->WriteBAR64(0, 0x30, x) + +#define RD_REGISTER_SQTDBL(i) pci-> ReadBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 0)) // Submission queue tail doorbell. +#define WR_REGISTER_SQTDBL(i, x) pci->WriteBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 0), x) +#define RD_REGISTER_CQHDBL(i) pci-> ReadBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 1)) // Completion queue head doorbell. +#define WR_REGISTER_CQHDBL(i, x) pci->WriteBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 1), x) + +#define ADMIN_QUEUE_ENTRY_COUNT (2) +#define IO_QUEUE_ENTRY_COUNT (256) +#define SUBMISSION_QUEUE_ENTRY_BYTES (64) +#define COMPLETION_QUEUE_ENTRY_BYTES (16) + +struct NVMeController : KDevice { + KPCIDevice *pci; + + uint64_t capabilities; + uint32_t version; + + size_t doorbellStride; + uint64_t readyTransitionTimeout; + uint64_t maximumDataTransferBytes; + uint32_t rtd3EntryLatencyUs; + uint16_t maximumOutstandingCommands; + + uint8_t *adminCompletionQueue, *adminSubmissionQueue; + uint32_t adminCompletionQueueHead, adminSubmissionQueueTail; + KEvent adminCompletionQueueReceived; + bool adminCompletionQueuePhase; + uint32_t adminCompletionQueueLastResult; + uint16_t adminCompletionQueueLastStatus; + + uint8_t *ioCompletionQueue, *ioSubmissionQueue; + uint32_t ioCompletionQueueHead, ioSubmissionQueueTail; + volatile uint32_t ioSubmissionQueueHead; + bool ioCompletionQueuePhase; + KEvent ioSubmissionQueueNonFull; + KSpinlock ioQueueSpinlock; + KWorkGroup *dispatchGroups[IO_QUEUE_ENTRY_COUNT]; + uint64_t prpListPages[IO_QUEUE_ENTRY_COUNT]; + uint64_t *prpListVirtual; + + void Initialise(); + void Shutdown(); + + bool HandleIRQ(); + bool IssueAdminCommand(const void *command, uint32_t *result); + bool Access(struct NVMeDrive *drive, uint64_t offsetBytes, size_t countBytes, int operation, + KDMABuffer *buffer, uint64_t flags, KWorkGroup *dispatchGroup); + void DumpState(); +}; + +struct NVMeDrive : KBlockDevice { + NVMeController *controller; + uint32_t nsid; +}; + +const char *genericCommandStatusValues[] = { + "Successful completion", + "Invalid command opcode", + "Invalid field in command", + "Command ID conflict", + "Data transfer error", + "Commands aborted due to powerloss notification", + "Internal error", + "Command abort requested", + "Command aborted due to SQ deletion", + "Command aborted due to failed fused command", + "Command aborted due to missing fused command", + "Invalid namespace or format", + "Command sequence error", + "Invalid SGL segment descriptor", + "Invalid number of SGL descriptors", + "Data SGL length invalid", + "Metadata SGL length invalid", + "SGL descriptor type invalid", + "Invalid use of controller memory buffer", + "PRP offset invalid", + "Atomic write unit exceeded", + "Operation denied", + "SGL offset invalid", + "Reserved", + "Host identifier inconsistent format", + "Keep alive timer expired", + "Keep alive timeout invalid", + "Command aborted due to preempt and abort", + "Sanitize failed", + "Sanitize in progress", + "SGL data block granularity invalid", + "Command not supported for queue in CMB", + "Namespace is write protected", + "Command interrupted", + "Transient transport error", +}; + +const char *genericCommandStatusValuesNVM[] = { + "LBA out of range", + "Capacity exceeded", + "Namespace not ready", + "Reservation conflict", + "Format in progress", +}; + +const char *commandSpecificStatusValues[] = { + "Completion queue invalid", + "Invalid queue identifier", + "Invalid queue size", + "Abort command limit exceeded", + "Reserved", + "Asynchronous event request limit exceeded", + "Invalid firmware slot", + "Invalid firmware image", + "Invalid interrupt vector", + "Invalid log page", + "Invalid format", + "Firmware activation requirse conventional reset", + "Invalid queue deletion", + "Feature identifier not saveable", + "Feature not changeable", + "Feature not namespace specific", + "Firmware activation requires NVM subsystem reset", + "Firmware activation requires controller level reset", + "Firmware activation requires maximum time violation", + "Firmware activation prohibited", + "Overlapping range", + "Namespace insufficient capacity", + "Namespace identifier unavailable", + "Reserved", + "Namespace already attached", + "Namespace is private", + "Namespace not attached", + "Thin provisioning not supported", + "Controller list invalid", + "Device self-test in progress", + "Boot partition write prohibited", + "Invalid controller identifier", + "Invalid secondary controller state", + "Invalid number of controller resources", + "Invalid resource identifier", + "Sanitize prohibited while persistent memory region is enabled", + "ANA group identifier invalid", + "ANA attach failed", +}; + +const char *commandSpecificStatusValuesNVM[] = { + "Confliciting attributes", + "Invalid protection information", + "Attempted write to read only range", +}; + +const char *mediaAndDataIntegrityErrorValuesNVM[] = { + "Write fault", + "Unrecovered read error", + "End-to-end guard check error", + "End-to-end application tag check error", + "End-to-end reference tag check error", + "Compare failure", + "Access denied", + "Dealocated or unwritten logical block", +}; + +const char *pathRelatedStatusValues[] = { + "Internal path error", + "Asymmetric access persistent loss", + "Asymmetric access inaccessible", + "Asymmetric access transition", +}; + +const char *GetErrorMessage(uint8_t statusCodeType, uint8_t statusCode) { + if (statusCodeType == 0) { + if (statusCode < sizeof(genericCommandStatusValues) / sizeof(genericCommandStatusValues[0])) { + return genericCommandStatusValues[statusCode]; + } else if (statusCode > 0x80 && (uint8_t) (statusCode - 0x80) < sizeof(genericCommandStatusValuesNVM) / sizeof(genericCommandStatusValuesNVM[0])) { + return genericCommandStatusValuesNVM[statusCode - 0x80]; + } + } else if (statusCodeType == 1) { + if (statusCode < sizeof(commandSpecificStatusValues) / sizeof(commandSpecificStatusValues[0])) { + return commandSpecificStatusValues[statusCode]; + } else if (statusCode > 0x80 && (uint8_t) (statusCode - 0x80) < sizeof(commandSpecificStatusValuesNVM) / sizeof(commandSpecificStatusValuesNVM[0])) { + return commandSpecificStatusValuesNVM[statusCode - 0x80]; + } + } else if (statusCodeType == 2) { + if (statusCode > 0x80 && (uint8_t) (statusCode - 0x80) < sizeof(mediaAndDataIntegrityErrorValuesNVM) / sizeof(mediaAndDataIntegrityErrorValuesNVM[0])) { + return mediaAndDataIntegrityErrorValuesNVM[statusCode - 0x80]; + } + } else if (statusCodeType == 3) { + if (statusCode < sizeof(pathRelatedStatusValues) / sizeof(pathRelatedStatusValues[0])) { + return pathRelatedStatusValues[statusCode]; + } + } + + return "Unknown error"; +} + +void NVMeController::DumpState() { + EsPrint("NVMe controller state:\n"); + + EsPrint("\t--- Registers ---\n"); + EsPrint("\t\tController capababilities: %x.\n", RD_REGISTER_CAP()); + EsPrint("\t\tVersion: %x.\n", RD_REGISTER_VS()); + EsPrint("\t\tInterrupt mask set: %x.\n", RD_REGISTER_INTMS()); + EsPrint("\t\tInterrupt mask clear: %x.\n", RD_REGISTER_INTMC()); + EsPrint("\t\tController configuration: %x.\n", RD_REGISTER_CC()); + EsPrint("\t\tController status: %x.\n", RD_REGISTER_CSTS()); + EsPrint("\t\tAdmin queue attributes: %x.\n", RD_REGISTER_AQA()); + EsPrint("\t\tAdmin submission queue base address: %x.\n", RD_REGISTER_ASQ()); + EsPrint("\t\tAdmin completion queue base address: %x.\n", RD_REGISTER_ACQ()); + EsPrint("\t\tAdmin submission queue tail doorbell: %x.\n", RD_REGISTER_SQTDBL(0)); + EsPrint("\t\tAdmin completion queue head doorbell: %x.\n", RD_REGISTER_CQHDBL(0)); + EsPrint("\t\tIO submission queue tail doorbell: %x.\n", RD_REGISTER_SQTDBL(1)); + EsPrint("\t\tIO completion queue head doorbell: %x.\n", RD_REGISTER_CQHDBL(1)); + + EsPrint("\t--- Internal ---\n"); + EsPrint("\t\tAdmin completion queue: %x.\n", adminCompletionQueue); + EsPrint("\t\tAdmin completion queue head: %x.\n", adminCompletionQueueHead); + EsPrint("\t\tAdmin completion queue phase: %d.\n", adminCompletionQueuePhase); + EsPrint("\t\tAdmin submission queue: %x.\n", adminSubmissionQueue); + EsPrint("\t\tAdmin submission queue tail: %x.\n", adminSubmissionQueueTail); + EsPrint("\t\tIO completion queue: %x.\n", ioCompletionQueue); + EsPrint("\t\tIO completion queue head: %x.\n", ioCompletionQueueHead); + EsPrint("\t\tIO completion queue phase: %d.\n", ioCompletionQueuePhase); + EsPrint("\t\tIO submission queue: %x.\n", ioSubmissionQueue); + EsPrint("\t\tIO submission queue tail: %x.\n", ioSubmissionQueueTail); + EsPrint("\t\tIO submission queue head: %x.\n", ioSubmissionQueueHead); + EsPrint("\t\tIO submission queue non full: %d.\n", ioSubmissionQueueNonFull.state); + EsPrint("\t\tPRP list virtual: %x.\n", prpListVirtual); + + EsPrint("\t--- Outstanding commands ---\n"); + + for (uintptr_t i = ioSubmissionQueueHead; i != ioSubmissionQueueTail; i = (i + 1) % IO_QUEUE_ENTRY_COUNT) { + EsPrint("\t\t(%d) %x, %x, %x, %x, %x, %x, %x, %x.\n", i, + ((uint64_t *) ioSubmissionQueue)[i * 8 + 0], ((uint64_t *) ioSubmissionQueue)[i * 8 + 1], + ((uint64_t *) ioSubmissionQueue)[i * 8 + 2], ((uint64_t *) ioSubmissionQueue)[i * 8 + 3], + ((uint64_t *) ioSubmissionQueue)[i * 8 + 4], ((uint64_t *) ioSubmissionQueue)[i * 8 + 5], + ((uint64_t *) ioSubmissionQueue)[i * 8 + 6], ((uint64_t *) ioSubmissionQueue)[i * 8 + 7]); + } +} + +bool NVMeController::IssueAdminCommand(const void *command, uint32_t *result) { + EsMemoryCopy(adminSubmissionQueue + adminSubmissionQueueTail * SUBMISSION_QUEUE_ENTRY_BYTES, command, SUBMISSION_QUEUE_ENTRY_BYTES); + adminSubmissionQueueTail = (adminSubmissionQueueTail + 1) % ADMIN_QUEUE_ENTRY_COUNT; + KEventReset(&adminCompletionQueueReceived); + __sync_synchronize(); + WR_REGISTER_SQTDBL(0, adminSubmissionQueueTail); + + if (!KEventWait(&adminCompletionQueueReceived, GENERAL_TIMEOUT)) { + // TODO Timeout. Now what? + KernelLog(LOG_ERROR, "NVMe", "admin command timeout", "Admin command timeout when sending command %x.\n", command); + return false; + } + + if (adminCompletionQueueLastStatus) { + bool doNotRetry = adminCompletionQueueLastStatus & 0x8000; + bool more = adminCompletionQueueLastStatus & 0x4000; + uint8_t commandRetryDelay = (adminCompletionQueueLastStatus >> 12) & 0x03; + uint8_t statusCodeType = (adminCompletionQueueLastStatus >> 9) & 0x07; + uint8_t statusCode = (adminCompletionQueueLastStatus >> 1) & 0xFF; + + KernelLog(LOG_ERROR, "NVMe", "admin command failed", "Admin command failed - '%z': %z%zretry delay - %d, type - %d, code - %d.\n", + GetErrorMessage(statusCodeType, statusCode), + doNotRetry ? "do not retry, " : "", more ? "more info in log page, " : "", commandRetryDelay, statusCodeType, statusCode); + + return false; + } + + if (result) *result = adminCompletionQueueLastResult; + return true; +} + +bool NVMeController::Access(struct NVMeDrive *drive, uint64_t offsetBytes, size_t countBytes, int operation, + KDMABuffer *buffer, uint64_t, KWorkGroup *dispatchGroup) { + // TODO Temporary. + // if (operation == K_ACCESS_WRITE) KernelPanic("NVMeController::Access - Attempted write.\n"); + + // Build the PRPs. + + KDMASegment segment1 = KDMABufferNextSegment(buffer); + uint64_t prp1 = segment1.physicalAddress, prp2 = 0; + + if (!segment1.isLast) { + KDMASegment segment2 = KDMABufferNextSegment(buffer, true /* peek */); + if (segment2.isLast) prp2 = segment2.physicalAddress; + } + + retry:; + KSpinlockAcquire(&ioQueueSpinlock); + + // Is there space in the submission queue? + + uintptr_t newTail = (ioSubmissionQueueTail + 1) % IO_QUEUE_ENTRY_COUNT; + bool submissionQueueFull = newTail == ioSubmissionQueueHead; + + if (!submissionQueueFull) { + KernelLog(LOG_VERBOSE, "NVMe", "start access", "Start access of %d, offset %D, count %D, using slot %d.\n", + drive->nsid, offsetBytes, countBytes, ioSubmissionQueueTail); + + uint64_t offsetSector = offsetBytes / drive->sectorSize; + uint64_t countSectors = countBytes / drive->sectorSize; + + // Build the PRP list. + + if (!prp2) { + prp2 = prpListPages[ioSubmissionQueueTail]; + MMArchRemap(MMGetKernelSpace(), prpListVirtual, prp2); + uintptr_t index = 0; + + while (!KDMABufferIsComplete(buffer)) { + if (index == K_PAGE_SIZE / sizeof(uint64_t)) { + KernelPanic("NVMeController::Access - Out of bounds in PRP list.\n"); + } + + prpListVirtual[index++] = KDMABufferNextSegment(buffer).physicalAddress; + } + } + + // Create the command. + + uint32_t *command = (uint32_t *) (ioSubmissionQueue + ioSubmissionQueueTail * SUBMISSION_QUEUE_ENTRY_BYTES); + command[0] = (ioSubmissionQueueTail << 16) /* command identifier */ | (operation == K_ACCESS_WRITE ? 0x01 : 0x02) /* opcode */; + command[1] = drive->nsid; + command[2] = command[3] = command[4] = command[5] = 0; + command[6] = prp1 & 0xFFFFFFFF; + command[7] = (prp1 >> 32) & 0xFFFFFFFF; + command[8] = prp2 & 0xFFFFFFFF; + command[9] = (prp2 >> 32) & 0xFFFFFFFF; + command[10] = offsetSector & 0xFFFFFFFF; + command[11] = (offsetSector >> 32) & 0xFFFFFFFF; + command[12] = (countSectors - 1) & 0xFFFF; + command[13] = command[14] = command[15] = 0; + + // Store the dispatch group, and update the queue tail. + + dispatchGroups[ioSubmissionQueueTail] = dispatchGroup; + ioSubmissionQueueTail = newTail; + __sync_synchronize(); + WR_REGISTER_SQTDBL(1, newTail); + } else { + KEventReset(&ioSubmissionQueueNonFull); + } + + KSpinlockRelease(&ioQueueSpinlock); + + if (submissionQueueFull) { + // Wait for the controller to consume an entry in the submission queue. + + KEventWait(&ioSubmissionQueueNonFull); + goto retry; + } + + return true; +} + +bool NVMeController::HandleIRQ() { + bool fromAdmin = false, fromIO = false; + + // Check the phase bit of the completion queue head entry. + + if (adminCompletionQueue && (adminCompletionQueue[adminCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14] & (1 << 0)) != adminCompletionQueuePhase) { + fromAdmin = true; + + adminCompletionQueueLastResult = *(uint32_t *) (adminCompletionQueue + adminCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 0); + adminCompletionQueueLastStatus = *(uint16_t *) (adminCompletionQueue + adminCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14) & 0xFFFE; + + // Advance the queue head. + + adminCompletionQueueHead++; + + if (adminCompletionQueueHead == ADMIN_QUEUE_ENTRY_COUNT) { + adminCompletionQueuePhase = !adminCompletionQueuePhase; + adminCompletionQueueHead = 0; + } + + WR_REGISTER_CQHDBL(0, adminCompletionQueueHead); + + // Signal the event. + + KEventSet(&adminCompletionQueueReceived); + } + + // Check the phase bit of the IO completion queue head entry. + + while (ioCompletionQueue && (ioCompletionQueue[ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14] & (1 << 0)) != ioCompletionQueuePhase) { + fromIO = true; + + uint16_t index = *(uint16_t *) (ioCompletionQueue + ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 12); + uint16_t status = *(uint16_t *) (ioCompletionQueue + ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14) & 0xFFFE; + + KernelLog(LOG_VERBOSE, "NVMe", "end access", "End access of slot %d.\n", index); + + if (index >= IO_QUEUE_ENTRY_COUNT) { + KernelLog(LOG_ERROR, "NVMe", "invalid completion entry", "Completion entry reported invalid command index of %d.\n", + index); + } else { + KWorkGroup *dispatchGroup = dispatchGroups[index]; + + if (status) { + uint8_t statusCodeType = (status >> 9) & 0x07, statusCode = (status >> 1) & 0xFF; + KernelLog(LOG_ERROR, "NVMe", "command failed", "Command failed with status %X/%X: %z.\n", + statusCodeType, statusCode, GetErrorMessage(statusCodeType, statusCode)); + dispatchGroup->End(false /* failed */); + } else { + dispatchGroup->End(true /* success */); + } + + dispatchGroups[index] = nullptr; + } + + // Indicate the submission queue entry was consumed. + + __sync_synchronize(); + ioSubmissionQueueHead = *(uint16_t *) (ioCompletionQueue + ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 8); + KEventSet(&ioSubmissionQueueNonFull, false, true); + + // Advance the queue head. + + ioCompletionQueueHead++; + + if (ioCompletionQueueHead == IO_QUEUE_ENTRY_COUNT) { + ioCompletionQueuePhase = !ioCompletionQueuePhase; + ioCompletionQueueHead = 0; + } + + WR_REGISTER_CQHDBL(1, ioCompletionQueueHead); + } + + return fromAdmin || fromIO; +} + +void NVMeController::Initialise() { + capabilities = RD_REGISTER_CAP(); + version = RD_REGISTER_VS(); + + KernelLog(LOG_INFO, "NVMe", "initial register dump", + "Registers at initialisation: capabilities - %x; version - %x, configuration - %x, status - %x, admin queue attributes - %x. Mapped at %x.\n", + capabilities, version, RD_REGISTER_CC(), RD_REGISTER_CSTS(), RD_REGISTER_AQA()); + + // Check the version is acceptable. + + if ((version >> 16) < 1) { + KernelLog(LOG_ERROR, "NVMe", "unsupported version", "Controller reports major version 0, which is not supported.\n"); + return; + } + + if ((version >> 16) == 1 && ((version >> 8) & 0xFF) < 1) { + KernelLog(LOG_ERROR, "NVMe", "unsupported version", "Controller reports version before 1.1, which is not supported.\n"); + return; + } + + // Check the capabilities are acceptable. + + if ((capabilities & 0xFFFF) == 0) { + KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Invalid CAP.MQES value, expected at least 1.\n"); + return; + } + + if (~capabilities & (1UL << 37)) { + KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Controller does not support NVMe command set.\n"); + return; + } + + if (((capabilities >> 48) & 0xF) > K_PAGE_BITS - 12) { + KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Controller requires a minimum page size greater than the host uses.\n"); + return; + } + + if (((capabilities >> 52) & 0xF) < K_PAGE_BITS - 12) { + KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Controller requires a maximum page size less than the host uses.\n"); + return; + } + + doorbellStride = 4 << ((capabilities >> 32) & 0xF); + readyTransitionTimeout = ((capabilities >> 24) & 0xFF) * 500; + + uint32_t previousConfiguration = RD_REGISTER_CC(); + + // Reset the controller. + + if (previousConfiguration & (1 << 0)) { + KTimeout timeout(readyTransitionTimeout); + while ((~RD_REGISTER_CSTS() & (1 << 0)) && !timeout.Hit()); + if (timeout.Hit()) { KernelLog(LOG_ERROR, "NVMe", "reset timeout", "Timeout during reset sequence (1).\n"); return; } + + WR_REGISTER_CC(RD_REGISTER_CC() & ~(1 << 0)); + } + + { + KTimeout timeout(readyTransitionTimeout); + while ((RD_REGISTER_CSTS() & (1 << 0)) && !timeout.Hit()); + if (timeout.Hit()) { KernelLog(LOG_ERROR, "NVMe", "reset timeout", "Timeout during reset sequence (2).\n"); return; } + } + + // Configure the controller to use the NVMe command set, the host page size, the IO queue entry size, and round robin arbitration. + + WR_REGISTER_CC((RD_REGISTER_CC() & (0xFF00000F)) | (0x00460000) | ((K_PAGE_BITS - 12) << 7)); + + // Configure the admin queues to use our desired entry count, and allocate memory for them. + + WR_REGISTER_AQA((RD_REGISTER_AQA() & 0xF000F000) | ((ADMIN_QUEUE_ENTRY_COUNT - 1) << 16) | (ADMIN_QUEUE_ENTRY_COUNT - 1)); + + uint64_t adminSubmissionQueueBytes = ADMIN_QUEUE_ENTRY_COUNT * SUBMISSION_QUEUE_ENTRY_BYTES; + uint64_t adminCompletionQueueBytes = ADMIN_QUEUE_ENTRY_COUNT * COMPLETION_QUEUE_ENTRY_BYTES; + uint64_t adminQueuePages = (adminSubmissionQueueBytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE + (adminCompletionQueueBytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE; + uintptr_t adminQueuePhysicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED, + adminQueuePages, (4096 + K_PAGE_SIZE - 1) / K_PAGE_SIZE); + + if (!adminQueuePhysicalAddress) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate %d pages of contiguous physical memory for admin queues.\n", adminQueuePages); + return; + } + + uintptr_t adminSubmissionQueuePhysicalAddress = adminQueuePhysicalAddress; + uintptr_t adminCompletionQueuePhysicalAddress = adminQueuePhysicalAddress + ((adminSubmissionQueueBytes + K_PAGE_SIZE - 1) & ~(K_PAGE_SIZE - 1)); + + WR_REGISTER_ASQ(adminSubmissionQueuePhysicalAddress); + WR_REGISTER_ACQ(adminCompletionQueuePhysicalAddress); + + adminSubmissionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), adminSubmissionQueuePhysicalAddress, adminSubmissionQueueBytes, ES_FLAGS_DEFAULT); + adminCompletionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), adminCompletionQueuePhysicalAddress, adminCompletionQueueBytes, ES_FLAGS_DEFAULT); + + if (!adminSubmissionQueue || !adminCompletionQueue) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map admin queues.\n"); + return; + } + + KernelLog(LOG_INFO, "NVMe", "admin queue configuration", "Configured admin queues to use physical addresses %x and %x with %d entries each.\n", + adminSubmissionQueuePhysicalAddress, adminCompletionQueuePhysicalAddress, ADMIN_QUEUE_ENTRY_COUNT); + + // Start the controller. + + WR_REGISTER_CC(RD_REGISTER_CC() | (1 << 0)); + + { + KTimeout timeout(readyTransitionTimeout); + + while (!timeout.Hit()) { + uint32_t status = RD_REGISTER_CSTS(); + + if (status & (1 << 1)) { + KernelLog(LOG_ERROR, "NVMe", "fatal error", "Fatal error while enabling controller.\n"); + return; + } else if (status & (1 << 0)) { + break; + } + } + + if (timeout.Hit()) { KernelLog(LOG_ERROR, "NVMe", "reset timeout", "Timeout during reset sequence (3).\n"); return; } + } + + // Enable IRQs for the admin queue, and register our interrupt handler. + + if (!pci->EnableSingleInterrupt([] (uintptr_t, void *context) { return ((NVMeController *) context)->HandleIRQ(); }, this, "NVMe")) { + KernelLog(LOG_ERROR, "NVMe", "IRQ registration failure", "Could not register IRQ %d.\n", pci->interruptLine); + return; + } + + WR_REGISTER_INTMC(1 << 0); + + // Identify controller. + + uintptr_t identifyDataPhysicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED, + (4096 * 2 + K_PAGE_SIZE - 1) / K_PAGE_SIZE); + + if (!identifyDataPhysicalAddress) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate physical memory for receiving identify data.\n"); + return; + } + + uint8_t *identifyData = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), identifyDataPhysicalAddress, 4096 * 2, ES_FLAGS_DEFAULT); + + if (!identifyData) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map memory for receiving identify data.\n"); + return; + } + + { + uint32_t command[16] = {}; + command[0] = 0x06; // Identify opcode. + command[6] = identifyDataPhysicalAddress & 0xFFFFFFFF; + command[7] = (identifyDataPhysicalAddress >> 32) & 0xFFFFFFFF; + command[10] = 0x01; // Identify controller. + + if (!IssueAdminCommand(command, nullptr)) { + KernelLog(LOG_ERROR, "NVMe", "identify controller failure", "The identify controller admin command failed.\n"); + return; + } + + maximumDataTransferBytes = identifyData[77] ? (1 << (12 + identifyData[77] + (((capabilities >> 48) & 0xF)))) : 0; + rtd3EntryLatencyUs = *(uint32_t *) (identifyData + 88); + maximumOutstandingCommands = *(uint16_t *) (identifyData + 514); + + if (rtd3EntryLatencyUs > 250 * 1000) { + rtd3EntryLatencyUs = 250 * 1000; // Maximum shutdown delay: 250ms. + } + + if (identifyData[111] > 0x01) { + KernelLog(LOG_ERROR, "NVMe", "unsupported controller type", "Controller type %X is not supported. Only IO controllers are currently supported.\n", + identifyData[111]); + return; + } + + KernelLog(LOG_INFO, "NVMe", "controller identify data", "Controller identify reported the following information: " + "serial number - '%s', model number - '%s', firmware revision - '%s', " + "maximum data transfer - %D, RTD3 entry latency - %dus, maximum outstanding commands - %d.\n", + 20, identifyData + 4, 40, identifyData + 24, 8, identifyData + 64, + maximumDataTransferBytes, rtd3EntryLatencyUs, maximumOutstandingCommands); + + if (maximumDataTransferBytes == 0 || maximumDataTransferBytes >= 2097152) { + maximumDataTransferBytes = 2097152; + } + } + + // Reset the software progress marker. + + { + // TODO How to check if this feature is supported? + uint32_t command[16] = {}; + command[0] = 0x09; // Set features opcode. + command[10] = 0x80; // Software progress marker feature. + command[11] = 0x00; // Reset to 0. + IssueAdminCommand(command, nullptr); // Ignore errors. + } + + // Create IO completion queue. + + { + uint64_t bytes = IO_QUEUE_ENTRY_COUNT * COMPLETION_QUEUE_ENTRY_BYTES; + uint64_t pages = (bytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE; + uintptr_t physicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED, pages); + + if (!physicalAddress) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate IO completion queue memory.\n"); + return; + } + + ioCompletionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), physicalAddress, bytes, ES_FLAGS_DEFAULT); + + if (!ioCompletionQueue) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map IO completion queue memory.\n"); + return; + } + + uint32_t command[16] = {}; + command[0] = 0x05; // Create IO completion queue opcode. + command[6] = physicalAddress & 0xFFFFFFFF; + command[7] = (physicalAddress >> 32) & 0xFFFFFFFF; + command[10] = 1 /* identifier */ | ((IO_QUEUE_ENTRY_COUNT - 1) << 16); + command[11] = (1 << 0) /* physically contiguous */ | (1 << 1) /* interrupts enabled */; + + if (!IssueAdminCommand(command, nullptr)) { + KernelLog(LOG_ERROR, "NVMe", "create queue failure", "Could not create the IO completion queue.\n"); + return; + } + } + + // Create IO submission queue. + + { + uint64_t bytes = IO_QUEUE_ENTRY_COUNT * SUBMISSION_QUEUE_ENTRY_BYTES; + uint64_t pages = (bytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE; + uintptr_t physicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED, pages); + + if (!physicalAddress) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate IO submission queue memory.\n"); + return; + } + + ioSubmissionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), physicalAddress, bytes, ES_FLAGS_DEFAULT); + + if (!ioSubmissionQueue) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map IO submission queue memory.\n"); + return; + } + + uint32_t command[16] = {}; + command[0] = 0x01; // Create IO submission queue opcode. + command[6] = physicalAddress & 0xFFFFFFFF; + command[7] = (physicalAddress >> 32) & 0xFFFFFFFF; + command[10] = 1 /* identifier */ | ((IO_QUEUE_ENTRY_COUNT - 1) << 16); + command[11] = (1 << 0) /* physically contiguous */ | (1 << 16) /* completion queue identifier */; + + if (!IssueAdminCommand(command, nullptr)) { + KernelLog(LOG_ERROR, "NVMe", "create queue failure", "Could not create the IO submission queue.\n"); + return; + } + } + + // Allocate physical memory for PRP lists. + + { + for (uintptr_t i = 0; i < IO_QUEUE_ENTRY_COUNT; i++) { + prpListPages[i] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW, 1); + + if (!prpListPages[i]) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate physical memory for PRP lists.\n"); + return; + } + } + + prpListVirtual = (uint64_t *) MMMapPhysical(MMGetKernelSpace(), prpListPages[0], K_PAGE_SIZE, ES_FLAGS_DEFAULT); + + if (!prpListVirtual) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate virtual memory to modify PRP lists.\n"); + return; + } + } + + // Identify active namespace IDs. + + uint32_t nsid = 0; + + while (true) { + uint32_t command[16] = {}; + command[0] = 0x06; // Identify opcode. + command[1] = nsid; // List NSIDs greater than the last one we saw. + command[6] = identifyDataPhysicalAddress & 0xFFFFFFFF; + command[7] = (identifyDataPhysicalAddress >> 32) & 0xFFFFFFFF; + command[10] = 0x02; // Identify active namespace IDs. + + if (!IssueAdminCommand(command, nullptr)) { + KernelLog(LOG_ERROR, "NVMe", "identify controller failure", "The identify controller admin command failed.\n"); + return; + } + + for (uintptr_t i = 0; i < 1024; i++) { + nsid = ((uint32_t *) identifyData)[i]; + + if (!nsid) { + goto allNamespacesIdentified; + } + + KernelLog(LOG_INFO, "NVMe", "active namespace ID", "Namespace ID %d is active.\n", nsid); + + // Identify the namespace. + + command[0] = 0x06; // Identify opcode. + command[1] = nsid; + command[6] = (identifyDataPhysicalAddress + 4096) & 0xFFFFFFFF; + command[7] = ((identifyDataPhysicalAddress + 4096) >> 32) & 0xFFFFFFFF; + command[10] = 0x00; // Identify namespace. + + if (!IssueAdminCommand(command, nullptr)) { + KernelLog(LOG_ERROR, "NVMe", "identify namespace failure", "Could not identify namespace %d.\n", nsid); + continue; + } + + uint8_t formattedLBASize = identifyData[4096 + 26]; + uint32_t lbaFormat = *(uint32_t *) (identifyData + 4096 + 128 + 4 * (formattedLBASize & 0xF)); + + if (lbaFormat & 0xFFFF) { + KernelLog(LOG_ERROR, "NVMe", "metadata unsupported", "Namespace %d has %D of metadata per block, which is unsupported.\n", + nsid, lbaFormat & 0xFFFF); + continue; + } + + uint8_t sectorBytesExponent = (lbaFormat >> 16) & 0xFF; + + if (sectorBytesExponent < 9 || sectorBytesExponent > 16) { + KernelLog(LOG_ERROR, "NVMe", "unsupported block size", "Namespace %d uses blocks of size 2^%d bytes, which is unsupported.\n", + nsid, sectorBytesExponent); + continue; + } + + uint64_t sectorBytes = 1 << sectorBytesExponent; + uint64_t capacity = *(uint64_t *) (identifyData + 4096 + 8) * sectorBytes; + + bool readOnly = identifyData[99] & (1 << 0); + + KernelLog(LOG_INFO, "NVMe", "namespace identified", "Identifier namespace %d with sectors of size %D, and a capacity of %D.%z\n", + nsid, sectorBytes, capacity, readOnly ? " The namespace is read-only." : ""); + + NVMeDrive *device = (NVMeDrive *) KDeviceCreate("NVMe namespace", this, sizeof(NVMeDrive)); + + if (!device) { + KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not create device for namespace %d.\n", nsid); + goto allNamespacesIdentified; + } + + device->controller = this; + device->nsid = nsid; + + device->sectorSize = sectorBytes; + device->sectorCount = capacity / sectorBytes; + device->maxAccessSectorCount = maximumDataTransferBytes / sectorBytes; + device->readOnly = readOnly; + device->driveType = ES_DRIVE_TYPE_SSD; + + device->access = [] (KBlockDeviceAccessRequest request) { + NVMeDrive *drive = (NVMeDrive *) request.device; + + request.dispatchGroup->Start(); + + if (!drive->controller->Access(drive, request.offset, request.count, request.operation, + request.buffer, request.flags, request.dispatchGroup)) { + request.dispatchGroup->End(false); + } + }; + + FSRegisterBlockDevice(device); + } + } + + allNamespacesIdentified:; +} + +void NVMeController::Shutdown() { + // Delete the IO queues. + + uint32_t command[16] = {}; + command[0] = 0x00; // Delete IO submission queue opcode. + command[10] = 1 /* identifier */; + IssueAdminCommand(command, nullptr); + command[0] = 0x04; // Delete IO completion queue opcode. + IssueAdminCommand(command, nullptr); + + // Inform the controller of shutdown. + + WR_REGISTER_CC(RD_REGISTER_CC() | (1 << 14)); + + // Wait for shutdown processing to complete. + + KTimeout timeout(rtd3EntryLatencyUs / 1000 + 1); + while (!timeout.Hit() && (RD_REGISTER_CSTS() & 12) != 8); +} + +static void DeviceAttach(KDevice *_parent) { + KPCIDevice *parent = (KPCIDevice *) _parent; + + NVMeController *device = (NVMeController *) KDeviceCreate("NVMe controller", parent, sizeof(NVMeController)); + if (!device) return; + device->pci = parent; + + device->shutdown = [] (KDevice *device) { + ((NVMeController *) device)->Shutdown(); + }; + + device->dumpState = [] (KDevice *device) { + ((NVMeController *) device)->DumpState(); + }; + + KernelLog(LOG_INFO, "NVMe", "found controller", "Found NVMe controller at PCI function %d/%d/%d.\n", parent->bus, parent->slot, parent->function); + + // Enable PCI features. + parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS + | K_PCI_FEATURE_BUSMASTERING_DMA + | K_PCI_FEATURE_MEMORY_SPACE_ACCESS + | K_PCI_FEATURE_BAR_0); + + // Initialise the controller. + device->Initialise(); +}; + +KDriver driverNVMe = { + .attach = DeviceAttach, +}; diff --git a/drivers/pci.cpp b/drivers/pci.cpp new file mode 100644 index 0000000..25a4b44 --- /dev/null +++ b/drivers/pci.cpp @@ -0,0 +1,554 @@ +// TODO Warn on Read/WriteBAR if port IO/memory space access is disabled in the command register. + +#include + +#define PCI_CONFIG (0xCF8) +#define PCI_DATA (0xCFC) + +struct PCIController : KDevice { + inline uint32_t ReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size = 32); + inline void WriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size = 32); + +#define PCI_BUS_DO_NOT_SCAN 0 +#define PCI_BUS_SCAN_NEXT 1 +#define PCI_BUS_SCANNED 2 + uint8_t busScanStates[256]; + + bool foundUSBController; + + void Enumerate(); + void EnumerateFunction(int bus, int device, int function, int *busesToScan); +}; + +const char *classCodeStrings[] = { + "Unknown", + "Mass storage controller", + "Network controller", + "Display controller", + "Multimedia controller", + "Memory controller", + "Bridge controller", + "Simple communication controller", + "Base system peripheral", + "Input device controller", + "Docking station", + "Processor", + "Serial bus controller", + "Wireless controller", + "Intelligent controller", + "Satellite communication controller", + "Encryption controller", + "Signal processing controller", +}; + +const char *subclassCodeStrings1[] = { + "SCSI bus controller", + "IDE controller", + "Floppy disk controller", + "IPI bus controller", + "RAID controller", + "ATA controller", + "Serial ATA", + "Serial attached SCSI", + "Non-volatile memory controller", +}; + +const char *subclassCodeStrings12[] = { + "FireWire (IEEE 1394) controller", + "ACCESS bus", + "SSA", + "USB controller", + "Fibre channel", + "SMBus", + "InfiniBand", + "IPMI interface", + "SERCOS interface (IEC 61491)", + "CANbus", +}; + +const char *progIFStrings12_3[] = { + "UHCI", + "OHCI", + "EHCI", + "XHCI", +}; + +uint8_t KPCIDevice::ReadBAR8(uintptr_t index, uintptr_t offset) { + uint32_t baseAddress = baseAddresses[index]; + uint8_t result; + + if (baseAddress & 1) { + result = ProcessorIn8((baseAddress & ~3) + offset); + } else { + result = *(volatile uint8_t *) (baseAddressesVirtual[index] + offset); + } + + KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR8 - %x, %x, %x, %x\n", this, index, offset, result); + return result; +} + +void KPCIDevice::WriteBAR8(uintptr_t index, uintptr_t offset, uint8_t value) { + uint32_t baseAddress = baseAddresses[index]; + KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR8 - %x, %x, %x, %x\n", this, index, offset, value); + + if (baseAddress & 1) { + ProcessorOut8((baseAddress & ~3) + offset, value); + } else { + *(volatile uint8_t *) (baseAddressesVirtual[index] + offset) = value; + } +} + +uint16_t KPCIDevice::ReadBAR16(uintptr_t index, uintptr_t offset) { + uint32_t baseAddress = baseAddresses[index]; + uint16_t result; + + if (baseAddress & 1) { + result = ProcessorIn16((baseAddress & ~3) + offset); + } else { + result = *(volatile uint16_t *) (baseAddressesVirtual[index] + offset); + } + + KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR16 - %x, %x, %x, %x\n", this, index, offset, result); + return result; +} + +void KPCIDevice::WriteBAR16(uintptr_t index, uintptr_t offset, uint16_t value) { + uint32_t baseAddress = baseAddresses[index]; + KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR16 - %x, %x, %x, %x\n", this, index, offset, value); + + if (baseAddress & 1) { + ProcessorOut16((baseAddress & ~3) + offset, value); + } else { + *(volatile uint16_t *) (baseAddressesVirtual[index] + offset) = value; + } +} + +uint32_t KPCIDevice::ReadBAR32(uintptr_t index, uintptr_t offset) { + uint32_t baseAddress = baseAddresses[index]; + uint32_t result; + + if (baseAddress & 1) { + result = ProcessorIn32((baseAddress & ~3) + offset); + } else { + result = *(volatile uint32_t *) (baseAddressesVirtual[index] + offset); + } + + KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR32 - %x, %x, %x, %x\n", this, index, offset, result); + return result; +} + +void KPCIDevice::WriteBAR32(uintptr_t index, uintptr_t offset, uint32_t value) { + uint32_t baseAddress = baseAddresses[index]; + KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR32 - %x, %x, %x, %x\n", this, index, offset, value); + + if (baseAddress & 1) { + ProcessorOut32((baseAddress & ~3) + offset, value); + } else { + *(volatile uint32_t *) (baseAddressesVirtual[index] + offset) = value; + } +} + +uint64_t KPCIDevice::ReadBAR64(uintptr_t index, uintptr_t offset) { + uint32_t baseAddress = baseAddresses[index]; + uint64_t result; + + if (baseAddress & 1) { + result = (uint64_t) ReadBAR32(index, offset) | ((uint64_t) ReadBAR32(index, offset + 4) << 32); + } else { + result = *(volatile uint64_t *) (baseAddressesVirtual[index] + offset); + } + + KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR64 - %x, %x, %x, %x\n", this, index, offset, result); + return result; +} + +void KPCIDevice::WriteBAR64(uintptr_t index, uintptr_t offset, uint64_t value) { + uint32_t baseAddress = baseAddresses[index]; + KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR64 - %x, %x, %x, %x\n", this, index, offset, value); + + if (baseAddress & 1) { + WriteBAR32(index, offset, value & 0xFFFFFFFF); + WriteBAR32(index, offset + 4, (value >> 32) & 0xFFFFFFFF); + } else { + *(volatile uint64_t *) (baseAddressesVirtual[index] + offset) = value; + } +} + +// Spinlock since some drivers need to access it in IRQs (e.g. ACPICA). +// Also can't be part of PCIController since PCI is initialised after ACPICA. +static KSpinlock configSpaceSpinlock; + +static PCIController *pci; + +uint32_t KPCIReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size) { + return pci->ReadConfig(bus, device, function, offset, size); +} + +void KPCIWriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size) { + pci->WriteConfig(bus, device, function, offset, value, size); +} + +uint32_t PCIController::ReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size) { + KSpinlockAcquire(&configSpaceSpinlock); + EsDefer(KSpinlockRelease(&configSpaceSpinlock)); + if (offset & 3) KernelPanic("PCIController::ReadConfig - offset is not 4-byte aligned."); + ProcessorOut32(PCI_CONFIG, (uint32_t) (0x80000000 | (bus << 16) | (device << 11) | (function << 8) | offset)); + if (size == 8) return ProcessorIn8(PCI_DATA); + if (size == 16) return ProcessorIn16(PCI_DATA); + if (size == 32) return ProcessorIn32(PCI_DATA); + KernelPanic("PCIController::ReadConfig - Invalid size %d.\n", size); + return 0; +} + +void PCIController::WriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size) { + KSpinlockAcquire(&configSpaceSpinlock); + EsDefer(KSpinlockRelease(&configSpaceSpinlock)); + if (offset & 3) KernelPanic("PCIController::WriteConfig - offset is not 4-byte aligned."); + ProcessorOut32(PCI_CONFIG, (uint32_t) (0x80000000 | (bus << 16) | (device << 11) | (function << 8) | offset)); + if (size == 8) ProcessorOut8(PCI_DATA, value); + else if (size == 16) ProcessorOut16(PCI_DATA, value); + else if (size == 32) ProcessorOut32(PCI_DATA, value); + else KernelPanic("PCIController::WriteConfig - Invalid size %d.\n", size); +} + +void KPCIDevice::WriteConfig8(uintptr_t offset, uint8_t value) { + pci->WriteConfig(bus, slot, function, offset, value, 8); +} + +uint8_t KPCIDevice::ReadConfig8(uintptr_t offset) { + return pci->ReadConfig(bus, slot, function, offset, 8); +} + +void KPCIDevice::WriteConfig16(uintptr_t offset, uint16_t value) { + pci->WriteConfig(bus, slot, function, offset, value, 16); +} + +uint16_t KPCIDevice::ReadConfig16(uintptr_t offset) { + return pci->ReadConfig(bus, slot, function, offset, 16); +} + +void KPCIDevice::WriteConfig32(uintptr_t offset, uint32_t value) { + pci->WriteConfig(bus, slot, function, offset, value, 32); +} + +uint32_t KPCIDevice::ReadConfig32(uintptr_t offset) { + return pci->ReadConfig(bus, slot, function, offset, 32); +} + +bool KPCIDevice::EnableSingleInterrupt(KIRQHandler irqHandler, void *context, const char *cOwnerName) { + if (EnableMSI(irqHandler, context, cOwnerName)) { + return true; + } + + EnableFeatures(K_PCI_FEATURE_INTERRUPTS); + + if (KRegisterIRQ(interruptLine, irqHandler, context, cOwnerName)) { + return true; + } + + return false; +} + +bool KPCIDevice::EnableMSI(KIRQHandler irqHandler, void *context, const char *cOwnerName) { + // Find the MSI capability. + + uint16_t status = ReadConfig32(0x04) >> 16; + + if (~status & (1 << 4)) { + KernelLog(LOG_ERROR, "PCI", "no MSI support", "Device does not support MSI.\n"); + return false; + } + + uint8_t pointer = ReadConfig8(0x34); + uintptr_t index = 0; + + while (pointer && index++ < 0xFF) { + uint32_t dw = ReadConfig32(pointer); + uint8_t nextPointer = (dw >> 8) & 0xFF; + uint8_t id = dw & 0xFF; + + if (id != 5) { + pointer = nextPointer; + continue; + } + + KMSIInformation msi = KRegisterMSI(irqHandler, context, cOwnerName); + + if (!msi.address) { + KernelLog(LOG_ERROR, "PCI", "register MSI failure", "Could not register MSI.\n"); + return false; + } + + uint16_t control = (dw >> 16) & 0xFFFF; + + if (msi.data & ~0xFFFF) { + KUnregisterMSI(msi.tag); + KernelLog(LOG_ERROR, "PCI", "unsupported MSI data", "PCI only supports 16 bits of MSI data. Requested: %x.\n", msi.data); + return false; + } + + if (msi.address & 3) { + KUnregisterMSI(msi.tag); + KernelLog(LOG_ERROR, "PCI", "unsupported MSI address", "PCI requires DWORD alignment of MSI address. Requested: %x.\n", msi.address); + return false; + } + +#ifdef ARCH_64 + if ((msi.address & 0xFFFFFFFF00000000) && (~control & (1 << 7))) { + KUnregisterMSI(msi.tag); + KernelLog(LOG_ERROR, "PCI", "unsupported MSI address", "MSI does not support 64-bit addresses. Requested: %x.\n", msi.address); + return false; + } +#endif + + control = (control & ~(7 << 4) /* don't allow modifying data */) + | (1 << 0 /* enable MSI */); + dw = (dw & 0x0000FFFF) | (control << 16); + + WriteConfig32(pointer + 0, dw); + WriteConfig32(pointer + 4, msi.address & 0xFFFFFFFF); + + if (control & (1 << 7)) { + WriteConfig32(pointer + 8, msi.address >> 32); + WriteConfig16(pointer + 12, (ReadConfig16(pointer + 12) & 0x3800) | msi.data); + if (control & (1 << 8)) WriteConfig32(pointer + 16, 0); + } else { + WriteConfig16(pointer + 8, msi.data); + if (control & (1 << 8)) WriteConfig32(pointer + 12, 0); + } + + return true; + } + + KernelLog(LOG_ERROR, "PCI", "no MSI support", "Device does not support MSI (2).\n"); + return false; +} + +bool KPCIDevice::EnableFeatures(uint64_t features) { + uint32_t config = ReadConfig32(4); + if (features & K_PCI_FEATURE_INTERRUPTS) config &= ~(1 << 10); + if (features & K_PCI_FEATURE_BUSMASTERING_DMA) config |= 1 << 2; + if (features & K_PCI_FEATURE_MEMORY_SPACE_ACCESS) config |= 1 << 1; + if (features & K_PCI_FEATURE_IO_PORT_ACCESS) config |= 1 << 0; + WriteConfig32(4, config); + + if (ReadConfig32(4) != config) { + KernelLog(LOG_ERROR, "PCI", "configuration update", "Could not update the configuration for device %x.\n", this); + return false; + } + + for (int i = 0; i < 6; i++) { + if (~features & (1 << i)) { + continue; + } + + if (baseAddresses[i] & 1) { + continue; // The BAR is an IO port. + } + + bool size64 = baseAddresses[i] & 4; + + if (!(baseAddresses[i] & 8)) { + // TODO Not prefetchable. + } + + uint64_t address, size; + + if (size64) { + WriteConfig32(0x10 + 4 * i, 0xFFFFFFFF); + WriteConfig32(0x10 + 4 * (i + 1), 0xFFFFFFFF); + size = ReadConfig32(0x10 + 4 * i); + size |= (uint64_t) ReadConfig32(0x10 + 4 * (i + 1)) << 32; + WriteConfig32(0x10 + 4 * i, baseAddresses[i]); + WriteConfig32(0x10 + 4 * (i + 1), baseAddresses[i + 1]); + address = baseAddresses[i]; + address |= (uint64_t) baseAddresses[i + 1] << 32; + } else { + WriteConfig32(0x10 + 4 * i, 0xFFFFFFFF); + size = ReadConfig32(0x10 + 4 * i); + size |= (uint64_t) 0xFFFFFFFF << 32; + WriteConfig32(0x10 + 4 * i, baseAddresses[i]); + address = baseAddresses[i]; + } + + if (!size || !address) { + KernelLog(LOG_ERROR, "PCI", "enable device BAR", "Could not enable BAR %d for device %x.\n", i, this); + return false; + } + + size &= ~15; + size = ~size + 1; + address &= ~15; + + // TODO Do we sometimes have to allocate the physical address ourselves..? + + // If the driver wants to allow WC caching later, it can, with MMAllowWriteCombiningCaching. + baseAddressesVirtual[i] = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), address, size, MM_REGION_NOT_CACHEABLE); + baseAddressesPhysical[i] = address; + baseAddressesSizes[i] = size; + + MMCheckUnusable(address, size); + + KernelLog(LOG_INFO, "PCI", "enable device BAR", "BAR %d has address %x and size %x, mapped to %x.\n", + i, address, size, baseAddressesVirtual[i]); + } + + return true; +} + +bool EnumeratePCIDrivers(KInstalledDriver *driver, KDevice *_device) { + KPCIDevice *device = (KPCIDevice *) _device; + + int classCode = -1, subclassCode = -1, progIF = -1; + bool foundAnyDeviceIDs = false, foundMatchingDeviceID = false; + + EsINIState s = {}; + s.buffer = driver->config, s.bytes = driver->configBytes; + + while (EsINIParse(&s)) { + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("classCode"))) classCode = EsIntegerParse(s.value, s.valueBytes); + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("subclassCode"))) subclassCode = EsIntegerParse(s.value, s.valueBytes); + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("progIF"))) progIF = EsIntegerParse(s.value, s.valueBytes); + + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("deviceID"))) { + foundAnyDeviceIDs = true; + + if (device->deviceID == EsIntegerParse(s.value, s.valueBytes)) { + foundMatchingDeviceID = true; + } + } + } + + if (classCode != -1 && device->classCode != classCode) return false; + if (subclassCode != -1 && device->subclassCode != subclassCode) return false; + if (progIF != -1 && device->progIF != progIF) return false; + + return !foundAnyDeviceIDs || foundMatchingDeviceID; +} + +void PCIController::EnumerateFunction(int bus, int device, int function, int *busesToScan) { + uint32_t deviceID = ReadConfig(bus, device, function, 0x00); + if ((deviceID & 0xFFFF) == 0xFFFF) return; + + uint32_t deviceClass = ReadConfig(bus, device, function, 0x08); + uint32_t interruptInformation = ReadConfig(bus, device, function, 0x3C); + + KPCIDevice *pciDevice = (KPCIDevice *) KDeviceCreate("PCI function", this, sizeof(KPCIDevice)); + if (!pciDevice) return; + + pciDevice->classCode = (deviceClass >> 24) & 0xFF; + pciDevice->subclassCode = (deviceClass >> 16) & 0xFF; + pciDevice->progIF = (deviceClass >> 8) & 0xFF; + + pciDevice->bus = bus; + pciDevice->slot = device; + pciDevice->function = function; + + pciDevice->interruptPin = (interruptInformation >> 8) & 0xFF; + pciDevice->interruptLine = (interruptInformation >> 0) & 0xFF; + + pciDevice->deviceID = ReadConfig(bus, device, function, 0); + pciDevice->subsystemID = ReadConfig(bus, device, function, 0x2C); + + for (int i = 0; i < 6; i++) { + pciDevice->baseAddresses[i] = pciDevice->ReadConfig32(0x10 + 4 * i); + } + + const char *classCodeString = pciDevice->classCode < sizeof(classCodeStrings) / sizeof(classCodeStrings[0]) + ? classCodeStrings[pciDevice->classCode] : "Unknown"; + const char *subclassCodeString = + pciDevice->classCode == 1 && pciDevice->subclassCode < sizeof(subclassCodeStrings1) / sizeof(const char *) + ? subclassCodeStrings1 [pciDevice->subclassCode] + : pciDevice->classCode == 12 && pciDevice->subclassCode < sizeof(subclassCodeStrings12) / sizeof(const char *) + ? subclassCodeStrings12[pciDevice->subclassCode] : ""; + const char *progIFString = + pciDevice->classCode == 12 && pciDevice->subclassCode == 3 && pciDevice->progIF / 0x10 < sizeof(progIFStrings12_3) / sizeof(const char *) + ? progIFStrings12_3[pciDevice->progIF / 0x10] : ""; + + KernelLog(LOG_INFO, "PCI", "enumerate device", + "Found PCI device at %d/%d/%d with ID %x. Class code: %X '%z'. Subclass code: %X '%z'. Prog IF: %X '%z'. Interrupt pin: %d. Interrupt line: %d.\n", + bus, device, function, deviceID, + pciDevice->classCode, classCodeString, pciDevice->subclassCode, subclassCodeString, pciDevice->progIF, progIFString, + pciDevice->interruptPin, pciDevice->interruptLine); + + if (pciDevice->classCode == 0x06 && pciDevice->subclassCode == 0x04 /* PCI bridge */) { + uint8_t secondaryBus = (ReadConfig(bus, device, function, 0x18) >> 8) & 0xFF; + + if (busScanStates[secondaryBus] == PCI_BUS_DO_NOT_SCAN) { + KernelLog(LOG_INFO, "PCI", "PCI bridge", "PCI bridge to bus %d.\n", secondaryBus); + *busesToScan = *busesToScan + 1; + busScanStates[secondaryBus] = PCI_BUS_SCAN_NEXT; + } + } + + bool foundDriver = KDeviceAttach(pciDevice, "PCI", EnumeratePCIDrivers); + + if (foundDriver && pciDevice->classCode == 12 && pciDevice->subclassCode == 3) { + // The USB controller is responsible for disabling PS/2 emulation, and calling KPS2SafeToInitialise. + KernelLog(LOG_INFO, "PCI", "found USB", "Found USB controller.\n"); + foundUSBController = true; + } +} + +void PCIController::Enumerate() { + uint32_t baseHeaderType = ReadConfig(0, 0, 0, 0x0C); + int baseBuses = (baseHeaderType & 0x80) ? 8 : 1; + + int busesToScan = 0; + + for (int baseBus = 0; baseBus < baseBuses; baseBus++) { + uint32_t deviceID = ReadConfig(0, 0, baseBus, 0x00); + if ((deviceID & 0xFFFF) == 0xFFFF) continue; + busScanStates[baseBus] = PCI_BUS_SCAN_NEXT; + busesToScan++; + } + + if (!busesToScan) { + KernelPanic("PCIController::Enumerate - No buses found\n"); + } + + while (busesToScan) { + for (int bus = 0; bus < 256; bus++) { + if (busScanStates[bus] != PCI_BUS_SCAN_NEXT) continue; + + KernelLog(LOG_INFO, "PCI", "scan bus", "Scanning bus %d...\n", bus); + + busScanStates[bus] = PCI_BUS_SCANNED; + busesToScan--; + + for (int device = 0; device < 32; device++) { + uint32_t deviceID = ReadConfig(bus, device, 0, 0x00); + if ((deviceID & 0xFFFF) == 0xFFFF) continue; + + uint32_t headerType = (ReadConfig(bus, device, 0, 0x0C) >> 16) & 0xFF; + int functions = (headerType & 0x80) ? 8 : 1; + + for (int function = 0; function < functions; function++) { + EnumerateFunction(bus, device, function, &busesToScan); + } + } + } + } +} + +static void DeviceAttach(KDevice *parent) { + if (pci) { + KernelLog(LOG_ERROR, "PCI", "multiple PCI controllers", "EntryPCI - Attempt to register multiple PCI controllers; ignored.\n"); + return; + } + + pci = (PCIController *) KDeviceCreate("PCI controller", parent, sizeof(PCIController)); + + if (pci) { + pci->Enumerate(); + + if (!pci->foundUSBController) { + KernelLog(LOG_INFO, "PCI", "no USB", "No USB controller found; initialising PS/2...\n"); + KPS2SafeToInitialise(); + } + } +} + +KDriver driverPCI = { + .attach = DeviceAttach, +}; diff --git a/drivers/ps2.cpp b/drivers/ps2.cpp new file mode 100644 index 0000000..cad7ffc --- /dev/null +++ b/drivers/ps2.cpp @@ -0,0 +1,566 @@ +// TODO Scrolling. + +#include + +struct PS2Update { + union { + struct { + volatile int xMovement, yMovement; + volatile unsigned buttons; + }; + + struct { + volatile unsigned scancode; + }; + }; +}; + +struct PS2 { + void Initialise(KDevice *parentDevice); + void EnableDevices(unsigned which); + void DisableDevices(unsigned which); + void FlushOutputBuffer(); + void SendCommand(uint8_t command); + uint8_t ReadByte(KTimeout *timeout); + void WriteByte(KTimeout *timeout, uint8_t value); + bool SetupKeyboard(KTimeout *timeout); + bool SetupMouse(KTimeout *timeout); + bool PollRead(uint8_t *value, bool forMouse); + void WaitInputBuffer(); + + uint8_t mouseType, scancodeSet; + size_t channels; + bool registeredIRQs; + bool initialised; + + volatile uintptr_t lastUpdatesIndex; + PS2Update lastUpdates[16]; + KSpinlock lastUpdatesLock; + KMutex mutex; +}; + +PS2 ps2; + +uint16_t scancodeConversionTable1[] = { + 0, ES_SCANCODE_ESCAPE, ES_SCANCODE_1, ES_SCANCODE_2, ES_SCANCODE_3, ES_SCANCODE_4, ES_SCANCODE_5, ES_SCANCODE_6, + ES_SCANCODE_7, ES_SCANCODE_8, ES_SCANCODE_9, ES_SCANCODE_0, ES_SCANCODE_HYPHEN, ES_SCANCODE_EQUALS, ES_SCANCODE_BACKSPACE, ES_SCANCODE_TAB, + ES_SCANCODE_Q, ES_SCANCODE_W, ES_SCANCODE_E, ES_SCANCODE_R, ES_SCANCODE_T, ES_SCANCODE_Y, ES_SCANCODE_U, ES_SCANCODE_I, + ES_SCANCODE_O, ES_SCANCODE_P, ES_SCANCODE_LEFT_BRACE, ES_SCANCODE_RIGHT_BRACE, ES_SCANCODE_ENTER, ES_SCANCODE_LEFT_CTRL, ES_SCANCODE_A, ES_SCANCODE_S, + ES_SCANCODE_D, ES_SCANCODE_F, ES_SCANCODE_G, ES_SCANCODE_H, ES_SCANCODE_J, ES_SCANCODE_K, ES_SCANCODE_L, ES_SCANCODE_PUNCTUATION_3, + ES_SCANCODE_PUNCTUATION_4, ES_SCANCODE_PUNCTUATION_5, ES_SCANCODE_LEFT_SHIFT, ES_SCANCODE_PUNCTUATION_1, ES_SCANCODE_Z, ES_SCANCODE_X, ES_SCANCODE_C, ES_SCANCODE_V, + ES_SCANCODE_B, ES_SCANCODE_N, ES_SCANCODE_M, ES_SCANCODE_COMMA, ES_SCANCODE_PERIOD, ES_SCANCODE_SLASH, ES_SCANCODE_RIGHT_SHIFT, ES_SCANCODE_NUM_MULTIPLY, + ES_SCANCODE_LEFT_ALT, ES_SCANCODE_SPACE, ES_SCANCODE_CAPS_LOCK, ES_SCANCODE_F1, ES_SCANCODE_F2, ES_SCANCODE_F3, ES_SCANCODE_F4, ES_SCANCODE_F5, + ES_SCANCODE_F6, ES_SCANCODE_F7, ES_SCANCODE_F8, ES_SCANCODE_F9, ES_SCANCODE_F10, ES_SCANCODE_NUM_LOCK, ES_SCANCODE_SCROLL_LOCK, ES_SCANCODE_NUM_7, + ES_SCANCODE_NUM_8, ES_SCANCODE_NUM_9, ES_SCANCODE_NUM_SUBTRACT, ES_SCANCODE_NUM_4, ES_SCANCODE_NUM_5, ES_SCANCODE_NUM_6, ES_SCANCODE_NUM_ADD, ES_SCANCODE_NUM_1, + ES_SCANCODE_NUM_2, ES_SCANCODE_NUM_3, ES_SCANCODE_NUM_0, ES_SCANCODE_NUM_POINT, 0, 0, 0, ES_SCANCODE_F11, + ES_SCANCODE_F12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ES_SCANCODE_MM_PREVIOUS, 0, 0, 0, 0, 0, 0, 0, + 0, ES_SCANCODE_MM_NEXT, 0, 0, ES_SCANCODE_NUM_ENTER, ES_SCANCODE_RIGHT_CTRL, 0, 0, + ES_SCANCODE_MM_MUTE, ES_SCANCODE_MM_CALC, ES_SCANCODE_MM_PAUSE, 0, ES_SCANCODE_MM_STOP, 0, 0, 0, + 0, 0, ES_SCANCODE_MM_QUIETER, 0, 0, 0, 0, 0, + ES_SCANCODE_MM_LOUDER, 0, ES_SCANCODE_WWW_HOME, 0, 0, ES_SCANCODE_NUM_DIVIDE, 0, 0, + ES_SCANCODE_RIGHT_ALT, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, ES_SCANCODE_HOME, + ES_SCANCODE_UP_ARROW, ES_SCANCODE_PAGE_UP, 0, ES_SCANCODE_LEFT_ARROW, 0, ES_SCANCODE_RIGHT_ARROW, 0, ES_SCANCODE_END, + ES_SCANCODE_DOWN_ARROW, ES_SCANCODE_PAGE_DOWN, ES_SCANCODE_INSERT, ES_SCANCODE_DELETE, 0, 0, 0, 0, + 0, 0, 0, ES_SCANCODE_LEFT_FLAG, ES_SCANCODE_RIGHT_FLAG, ES_SCANCODE_CONTEXT_MENU, ES_SCANCODE_ACPI_POWER, ES_SCANCODE_ACPI_SLEEP, + 0, 0, 0, ES_SCANCODE_ACPI_WAKE, 0, ES_SCANCODE_WWW_SEARCH, ES_SCANCODE_WWW_STARRED, ES_SCANCODE_WWW_REFRESH, + ES_SCANCODE_WWW_STOP, ES_SCANCODE_WWW_FORWARD, ES_SCANCODE_WWW_BACK, ES_SCANCODE_MM_FILES, ES_SCANCODE_MM_EMAIL, ES_SCANCODE_MM_SELECT, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +uint16_t scancodeConversionTable2[] = { + 0, ES_SCANCODE_F9, 0, ES_SCANCODE_F5, ES_SCANCODE_F3, ES_SCANCODE_F1, ES_SCANCODE_F2, ES_SCANCODE_F12, + 0, ES_SCANCODE_F10, ES_SCANCODE_F8, ES_SCANCODE_F6, ES_SCANCODE_F4, ES_SCANCODE_TAB, ES_SCANCODE_PUNCTUATION_5, 0, + 0, ES_SCANCODE_LEFT_ALT, ES_SCANCODE_LEFT_SHIFT, 0, ES_SCANCODE_LEFT_CTRL, ES_SCANCODE_Q, ES_SCANCODE_1, 0, + 0, 0, ES_SCANCODE_Z, ES_SCANCODE_S, ES_SCANCODE_A, ES_SCANCODE_W, ES_SCANCODE_2, 0, + 0, ES_SCANCODE_C, ES_SCANCODE_X, ES_SCANCODE_D, ES_SCANCODE_E, ES_SCANCODE_4, ES_SCANCODE_3, 0, + 0, ES_SCANCODE_SPACE, ES_SCANCODE_V, ES_SCANCODE_F, ES_SCANCODE_T, ES_SCANCODE_R, ES_SCANCODE_5, 0, + 0, ES_SCANCODE_N, ES_SCANCODE_B, ES_SCANCODE_H, ES_SCANCODE_G, ES_SCANCODE_Y, ES_SCANCODE_6, 0, + 0, 0, ES_SCANCODE_M, ES_SCANCODE_J, ES_SCANCODE_U, ES_SCANCODE_7, ES_SCANCODE_8, 0, + 0, ES_SCANCODE_COMMA, ES_SCANCODE_K, ES_SCANCODE_I, ES_SCANCODE_O, ES_SCANCODE_0, ES_SCANCODE_9, 0, + 0, ES_SCANCODE_PERIOD, ES_SCANCODE_SLASH, ES_SCANCODE_L, ES_SCANCODE_PUNCTUATION_3, ES_SCANCODE_P, ES_SCANCODE_HYPHEN, 0, + 0, 0, ES_SCANCODE_PUNCTUATION_4, 0, ES_SCANCODE_LEFT_BRACE, ES_SCANCODE_EQUALS, 0, 0, + ES_SCANCODE_CAPS_LOCK, ES_SCANCODE_RIGHT_SHIFT, ES_SCANCODE_ENTER, ES_SCANCODE_RIGHT_BRACE, 0, ES_SCANCODE_PUNCTUATION_1, 0, 0, + 0, 0, 0, 0, 0, 0, ES_SCANCODE_BACKSPACE, 0, + 0, ES_SCANCODE_NUM_1, 0, ES_SCANCODE_NUM_4, ES_SCANCODE_NUM_7, 0, 0, 0, + ES_SCANCODE_NUM_0, ES_SCANCODE_NUM_POINT, ES_SCANCODE_NUM_2, ES_SCANCODE_NUM_5, ES_SCANCODE_NUM_6, ES_SCANCODE_NUM_8, ES_SCANCODE_ESCAPE, ES_SCANCODE_NUM_LOCK, + ES_SCANCODE_F11, ES_SCANCODE_NUM_ADD, ES_SCANCODE_NUM_3, ES_SCANCODE_NUM_SUBTRACT, ES_SCANCODE_NUM_MULTIPLY, ES_SCANCODE_NUM_9, ES_SCANCODE_SCROLL_LOCK, 0, + 0, 0, 0, ES_SCANCODE_F7, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, ES_SCANCODE_PAUSE, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ES_SCANCODE_WWW_SEARCH, ES_SCANCODE_RIGHT_ALT, ES_SCANCODE_PRINT_SCREEN, 0, ES_SCANCODE_RIGHT_CTRL, ES_SCANCODE_MM_PREVIOUS, 0, 0, + ES_SCANCODE_WWW_STARRED, 0, 0, 0, 0, 0, 0, ES_SCANCODE_LEFT_FLAG, + ES_SCANCODE_WWW_REFRESH, ES_SCANCODE_MM_QUIETER, 0, ES_SCANCODE_MM_MUTE, 0, 0, 0, ES_SCANCODE_CONTEXT_MENU, + ES_SCANCODE_WWW_STOP, 0, 0, ES_SCANCODE_MM_CALC, 0, 0, 0, 0, + ES_SCANCODE_WWW_FORWARD, 0, ES_SCANCODE_MM_LOUDER, 0, ES_SCANCODE_MM_PAUSE, 0, 0, ES_SCANCODE_ACPI_POWER, + ES_SCANCODE_WWW_BACK, 0, ES_SCANCODE_WWW_HOME, ES_SCANCODE_MM_STOP, 0, 0, 0, ES_SCANCODE_ACPI_SLEEP, + ES_SCANCODE_MM_FILES, 0, 0, 0, 0, 0, 0, 0, + ES_SCANCODE_MM_EMAIL, 0, ES_SCANCODE_NUM_DIVIDE, 0, 0, ES_SCANCODE_MM_NEXT, 0, 0, + ES_SCANCODE_MM_SELECT, 0, 0, 0, 0, 0, 0, 0, + 0, 0, ES_SCANCODE_NUM_ENTER, 0, 0, 0, ES_SCANCODE_ACPI_WAKE, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, ES_SCANCODE_END, 0, ES_SCANCODE_LEFT_ARROW, ES_SCANCODE_HOME, 0, 0, 0, + ES_SCANCODE_INSERT, ES_SCANCODE_DELETE, ES_SCANCODE_DOWN_ARROW, 0, ES_SCANCODE_RIGHT_ARROW, ES_SCANCODE_UP_ARROW, 0, 0, + 0, 0, ES_SCANCODE_PAGE_DOWN, 0, 0, ES_SCANCODE_PAGE_UP, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +// Status register. +#define PS2_OUTPUT_FULL (1 << 0) +#define PS2_INPUT_FULL (1 << 1) +#define PS2_MOUSE_BYTE (1 << 5) + +// Mouse types. +#define PS2_MOUSE_NORMAL (0) +#define PS2_MOUSE_SCROLL (3) +#define PS2_MOUSE_5_BUTTON (4) + +// Controller commands. +#define PS2_DISABLE_FIRST (0xAD) +#define PS2_ENABLE_FIRST (0xAE) +#define PS2_TEST_FIRST (0xAB) +#define PS2_DISABLE_SECOND (0xA7) +#define PS2_ENABLE_SECOND (0xA8) +#define PS2_WRITE_SECOND (0xD4) +#define PS2_TEST_SECOND (0xA9) +#define PS2_TEST_CONTROLER (0xAA) +#define PS2_READ_CONFIG (0x20) +#define PS2_WRITE_CONFIG (0x60) + +// Controller configuration. +#define PS2_FIRST_IRQ_MASK (1 << 0) +#define PS2_SECOND_IRQ_MASK (1 << 1) +#define PS2_SECOND_CLOCK (1 << 5) +#define PS2_TRANSLATION (1 << 6) + +// IRQs. +#define PS2_FIRST_IRQ (1) +#define PS2_SECOND_IRQ (12) + +// Ports. +#define PS2_PORT_DATA (0x60) +#define PS2_PORT_STATUS (0x64) +#define PS2_PORT_COMMAND (0x64) + +// Keyboard commands. +#define PS2_KEYBOARD_RESET (0xFF) +#define PS2_KEYBOARD_ENABLE (0xF4) +#define PS2_KEYBOARD_DISABLE (0xF5) +#define PS2_KEYBOARD_REPEAT (0xF3) +#define PS2_KEYBOARD_SET_LEDS (0xED) +#define PS2_KEYBOARD_SCANCODE_SET (0xF0) + +// Mouse commands. +#define PS2_MOUSE_RESET (0xFF) +#define PS2_MOUSE_ENABLE (0xF4) +#define PS2_MOUSE_DISABLE (0xF5) +#define PS2_MOUSE_SAMPLE_RATE (0xF3) +#define PS2_MOUSE_READ (0xEB) +#define PS2_MOUSE_RESOLUTION (0xE8) + +void PS2MouseUpdated(EsGeneric _update) { + PS2Update *update = (PS2Update *) _update.p; + KCursorUpdate(update->xMovement, update->yMovement, update->buttons); +} + +void PS2KeyboardUpdated(EsGeneric _update) { + PS2Update *update = (PS2Update *) _update.p; + KernelLog(LOG_VERBOSE, "PS/2", "keyboard update", "Received scancode %x.\n", update->scancode); + KKeyPress(update->scancode); +} + +void PS2::WaitInputBuffer() { + while (ProcessorIn8(PS2_PORT_STATUS) & PS2_INPUT_FULL); +} + +bool PS2::PollRead(uint8_t *value, bool forMouse) { + uint8_t status = ProcessorIn8(PS2_PORT_STATUS); + if (status & PS2_MOUSE_BYTE && !forMouse) return false; + if (!(status & PS2_MOUSE_BYTE) && forMouse) return false; + + if (status & PS2_OUTPUT_FULL) { + *value = ProcessorIn8(PS2_PORT_DATA); + + if (*value == 0xE1 && !forMouse) { + KDebugKeyPressed(); + } + + EsRandomAddEntropy(*value); + return true; + } else { + return false; + } +} + +int PS2ReadKey() { + static uint8_t firstByte = 0, secondByte = 0, thirdByte = 0; + static size_t bytesFound = 0; + + if (bytesFound == 0) { + if (!ps2.PollRead(&firstByte, false)) return 0; + bytesFound++; + if (firstByte != 0xE0 && firstByte != 0xF0) { + goto sequenceFinished; + } else return 0; + } else if (bytesFound == 1) { + if (!ps2.PollRead(&secondByte, false)) return 0; + bytesFound++; + if (secondByte != 0xF0) { + goto sequenceFinished; + } else return 0; + } else if (bytesFound == 2) { + if (!ps2.PollRead(&thirdByte, false)) return 0; + bytesFound++; + goto sequenceFinished; + } + + sequenceFinished: + KernelLog(LOG_VERBOSE, "PS/2", "keyboard data", "Keyboard data: %X%X%X\n", firstByte, secondByte, thirdByte); + + int scancode = 0; + + if (ps2.scancodeSet == 1) { + if (firstByte == 0xE0) { + if (secondByte & 0x80) { + scancode = secondByte | K_SCANCODE_KEY_RELEASED; + } else { + scancode = secondByte | 0x80; + } + } else { + if (firstByte & 0x80) { + scancode = (firstByte & ~0x80) | K_SCANCODE_KEY_RELEASED; + } else { + scancode = firstByte; + } + } + } else if (ps2.scancodeSet == 2) { + if (firstByte == 0xE0) { + if (secondByte == 0xF0) { + scancode = K_SCANCODE_KEY_RELEASED | (1 << 8) | thirdByte; + } else { + scancode = K_SCANCODE_KEY_PRESSED | (1 << 8) | secondByte; + } + } else { + if (firstByte == 0xF0) { + scancode = K_SCANCODE_KEY_RELEASED | (0 << 8) | secondByte; + } else { + scancode = K_SCANCODE_KEY_PRESSED | (0 << 8) | firstByte; + } + } + } + + uint16_t *table = ps2.scancodeSet == 2 ? scancodeConversionTable2 : scancodeConversionTable1; + + firstByte = 0; + secondByte = 0; + thirdByte = 0; + bytesFound = 0; + + return (scancode & (1 << 15)) | table[scancode & ~(1 << 15)]; +} + +int KWaitKey() { + if (!ps2.channels) ProcessorHalt(); + int scancode; + while (!(scancode = PS2ReadKey())); + return scancode; +} + +bool PS2IRQHandler(uintptr_t interruptIndex, void *) { + if (!ps2.channels) return false; + if (ps2.channels == 1 && interruptIndex == 12) return false; + + if (interruptIndex == 12) { + static uint8_t firstByte = 0, secondByte = 0, thirdByte = 0; + static size_t bytesFound = 0; + + if (bytesFound == 0) { + if (!ps2.PollRead(&firstByte, true)) return false; + if (!(firstByte & 8)) return false; + bytesFound++; + return true; + } else if (bytesFound == 1) { + if (!ps2.PollRead(&secondByte, true)) return false; + bytesFound++; + return true; + } else if (bytesFound == 2) { + if (!ps2.PollRead(&thirdByte, true)) return false; + bytesFound++; + } + + KernelLog(LOG_VERBOSE, "PS/2", "mouse data", "Mouse data: %X%X%X\n", firstByte, secondByte, thirdByte); + + KSpinlockAcquire(&ps2.lastUpdatesLock); + PS2Update *update = ps2.lastUpdates + ps2.lastUpdatesIndex; + ps2.lastUpdatesIndex = (ps2.lastUpdatesIndex + 1) % 16; + KSpinlockRelease(&ps2.lastUpdatesLock); + + update->xMovement = secondByte - ((firstByte << 4) & 0x100); + update->yMovement = -(thirdByte - ((firstByte << 3) & 0x100)); + update->buttons = ((firstByte & (1 << 0)) ? K_LEFT_BUTTON : 0) + | ((firstByte & (1 << 1)) ? K_RIGHT_BUTTON : 0) + | ((firstByte & (1 << 2)) ? K_MIDDLE_BUTTON : 0); + + KRegisterAsyncTask(PS2MouseUpdated, update, false); + + firstByte = 0; + secondByte = 0; + thirdByte = 0; + bytesFound = 0; + } else if (interruptIndex == 1) { + KernelLog(LOG_VERBOSE, "PS/2", "keyboard IRQ", "Received keyboard IRQ.\n"); + + int scancode = PS2ReadKey(); + + if (scancode) { + KSpinlockAcquire(&ps2.lastUpdatesLock); + PS2Update *update = ps2.lastUpdates + ps2.lastUpdatesIndex; + ps2.lastUpdatesIndex = (ps2.lastUpdatesIndex + 1) % 16; + KSpinlockRelease(&ps2.lastUpdatesLock); + update->scancode = scancode; + KRegisterAsyncTask(PS2KeyboardUpdated, update, false); + } + } else { + KernelPanic("PS2IRQHandler - Incorrect interrupt index.\n", interruptIndex); + } + + return true; +} + +void PS2::DisableDevices(unsigned which) { + WaitInputBuffer(); + // EsPrint("ps2 first write...\n"); + if (which & 1) ProcessorOut8(PS2_PORT_COMMAND, PS2_DISABLE_FIRST); + // EsPrint("ps2 first write end\n"); + WaitInputBuffer(); + if (which & 2) ProcessorOut8(PS2_PORT_COMMAND, PS2_DISABLE_SECOND); +} + +void PS2::EnableDevices(unsigned which) { + WaitInputBuffer(); + if (which & 1) ProcessorOut8(PS2_PORT_COMMAND, PS2_ENABLE_FIRST); + WaitInputBuffer(); + if (which & 2) ProcessorOut8(PS2_PORT_COMMAND, PS2_ENABLE_SECOND); +} + +void PS2::FlushOutputBuffer() { + while (ProcessorIn8(PS2_PORT_STATUS) & PS2_OUTPUT_FULL) { + ProcessorIn8(PS2_PORT_DATA); + } +} + +void PS2::SendCommand(uint8_t command) { + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, command); +} + +uint8_t PS2::ReadByte(KTimeout *timeout) { + while (!(ProcessorIn8(PS2_PORT_STATUS) & PS2_OUTPUT_FULL) && !timeout->Hit()); + return ProcessorIn8(PS2_PORT_DATA); +} + +void PS2::WriteByte(KTimeout *timeout, uint8_t value) { + while ((ProcessorIn8(PS2_PORT_STATUS) & PS2_INPUT_FULL) && !timeout->Hit()); + if (timeout->Hit()) return; + ProcessorOut8(PS2_PORT_DATA, value); +} + +bool PS2::SetupKeyboard(KTimeout *timeout) { + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, PS2_KEYBOARD_ENABLE); + if (ReadByte(timeout) != 0xFA) return false; + + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, PS2_KEYBOARD_SCANCODE_SET); + if (ReadByte(timeout) != 0xFA) return false; + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, 0); + if (ReadByte(timeout) != 0xFA) return false; + scancodeSet = ReadByte(timeout) & 3; + KernelLog(LOG_INFO, "PS/2", "scancode set", "Keyboard reports it is using scancode set %d.\n", scancodeSet); + + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, PS2_KEYBOARD_REPEAT); + if (ReadByte(timeout) != 0xFA) return false; + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, 0); + if (ReadByte(timeout) != 0xFA) return false; + + return true; +} + +bool PS2::SetupMouse(KTimeout *timeout) { + // TODO Mouse with scroll wheel detection. + + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_RESET); + if (ReadByte(timeout) != 0xFA) return false; + if (ReadByte(timeout) != 0xAA) return false; + if (ReadByte(timeout) != 0x00) return false; + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_SAMPLE_RATE); + if (ReadByte(timeout) != 0xFA) return false; + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, 100); + if (ReadByte(timeout) != 0xFA) return false; + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_RESOLUTION); + if (ReadByte(timeout) != 0xFA) return false; + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, 3); + if (ReadByte(timeout) != 0xFA) return false; + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_ENABLE); + if (ReadByte(timeout) != 0xFA) return false; + + return true; +} + +void PS2::Initialise(KDevice *parentDevice) { + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (initialised) { + return; + } + + initialised = true; + channels = 0; + + KTimeout timeout(10000); + + FlushOutputBuffer(); + + // TODO PS/2 detection with ACPI. + + DisableDevices(1 | 2); + FlushOutputBuffer(); + + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_READ_CONFIG); + uint8_t configurationByte = ReadByte(&timeout); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_CONFIG); + WriteByte(&timeout, configurationByte & ~(PS2_FIRST_IRQ_MASK | PS2_SECOND_IRQ_MASK | PS2_TRANSLATION)); + if (timeout.Hit()) return; + + SendCommand(PS2_TEST_CONTROLER); + if (ReadByte(&timeout) != 0x55) return; + + bool hasMouse = false; + if (configurationByte & PS2_SECOND_CLOCK) { + EnableDevices(2); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_READ_CONFIG); + configurationByte = ReadByte(&timeout); + if (!(configurationByte & PS2_SECOND_CLOCK)) { + hasMouse = true; + DisableDevices(2); + } + } + + { + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_TEST_FIRST); + uint8_t b = ReadByte(&timeout); + if (b) return; + if (timeout.Hit()) return; + channels = 1; + } + + if (hasMouse) { + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_TEST_SECOND); + if (!ReadByte(&timeout) && !timeout.Hit()) channels = 2; + } + + EnableDevices(1 | 2); + + if (!SetupKeyboard(&timeout) || timeout.Hit()) { + channels = 0; + return; + } + + if (!SetupMouse(&timeout) || timeout.Hit()) { + channels = 1; + } + + { + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_READ_CONFIG); + uint8_t configurationByte = ReadByte(&timeout); + WaitInputBuffer(); + ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_CONFIG); + WriteByte(&timeout, configurationByte | PS2_FIRST_IRQ_MASK | PS2_SECOND_IRQ_MASK); + } + + if (!registeredIRQs) { + KRegisterIRQ(PS2_FIRST_IRQ, PS2IRQHandler, nullptr, "PS2"); + KRegisterIRQ(PS2_SECOND_IRQ, PS2IRQHandler, nullptr, "PS2"); + registeredIRQs = true; + } + + KDevice *controller = KDeviceCreate("PS/2 controller", parentDevice, sizeof(KDevice)); + KDeviceCreate("PS/2 keyboard", controller, sizeof(KDevice)); + if (channels == 2) KDeviceCreate("PS/2 mouse", controller, sizeof(KDevice)); + + KernelLog(LOG_INFO, "PS/2", "controller initialised", "Setup PS/2 controller%z.\n", channels == 2 ? ", with a mouse" : ""); +} + +static void DeviceAttach(KDevice *parentDevice) { + ps2.Initialise(parentDevice); +} + +KDriver driverPS2 = { + .attach = DeviceAttach, +}; diff --git a/drivers/svga.cpp b/drivers/svga.cpp new file mode 100644 index 0000000..16ecbf7 --- /dev/null +++ b/drivers/svga.cpp @@ -0,0 +1,285 @@ +#include +#include + +struct VideoModeInformation { + uint8_t valid : 1, edidValid : 1; + uint8_t bitsPerPixel; + uint16_t widthPixels, heightPixels; + uint16_t bytesPerScanlineLinear; + uint64_t bufferPhysical; + uint8_t edid[128]; +}; + +VideoModeInformation *vbeMode; +uint32_t screenWidth, screenHeight, strideX, strideY; +volatile uint8_t *linearBuffer; + +#if 0 +float colorBlindnessMatrix[3][9] = { + { + // Protanopia. + 0.171, 0.829, 0, + 0.171, 0.829, 0, + -0.005, 0.005, 1, + }, + + { + // Deuteranopia. + 0.330, 0.670, 0, + 0.330, 0.670, 0, + -0.028, 0.028, 1, + }, + + { + // Tritanopia. + 1, 0.127, -0.127, + 0, 0.874, 0.126, + 0, 0.874, 0.126, + }, +}; + +// #define SIMULATE_COLOR_BLINDNESS (1) +#endif + +void UpdateScreen_32_XRGB(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY) { + GraphicsUpdateScreen32(source, sourceWidth, sourceHeight, sourceStride, + destinationX, destinationY, screenWidth, screenHeight, strideY, linearBuffer); +} + +void DebugPutBlock_32_XRGB(uintptr_t x, uintptr_t y, bool toggle) { + GraphicsDebugPutBlock32(x, y, toggle, screenWidth, screenHeight, strideY, linearBuffer); +} + +void DebugClearScreen_32_XRGB() { + GraphicsDebugClearScreen32(screenWidth, screenHeight, strideY, linearBuffer); +} + +void UpdateScreen_24_RGB(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY) { + GraphicsUpdateScreen24(source, sourceWidth, sourceHeight, sourceStride, + destinationX, destinationY, screenWidth, screenHeight, strideY, linearBuffer); +} + +void DebugPutBlock_24_RGB(uintptr_t x, uintptr_t y, bool toggle) { + if (toggle) { + linearBuffer[y * strideY + x * 3 + 0] += 0x4C; + linearBuffer[y * strideY + x * 3 + 1] += 0x4C; + linearBuffer[y * strideY + x * 3 + 2] += 0x4C; + } else { + linearBuffer[y * strideY + x * 3 + 0] = 0xFF; + linearBuffer[y * strideY + x * 3 + 1] = 0xFF; + linearBuffer[y * strideY + x * 3 + 2] = 0xFF; + } + + linearBuffer[(y + 1) * strideY + (x + 1) * 3 + 0] = 0; + linearBuffer[(y + 1) * strideY + (x + 1) * 3 + 1] = 0; + linearBuffer[(y + 1) * strideY + (x + 1) * 3 + 2] = 0; +} + +void DebugClearScreen_24_RGB() { + for (uintptr_t i = 0; i < screenWidth * screenHeight * 3; i += 3) { + linearBuffer[i + 2] = 0x18; + linearBuffer[i + 1] = 0x7E; + linearBuffer[i + 0] = 0xCF; + } +} + +void InitialiseVBE(KDevice *parent) { + if (KGraphicsIsTargetRegistered()) { + return; + } + + vbeMode = (VideoModeInformation *) MMMapPhysical(MMGetKernelSpace(), 0x7000 + GetBootloaderInformationOffset(), + sizeof(VideoModeInformation), ES_FLAGS_DEFAULT); + + if (!vbeMode->valid) { + return; + } + + if (vbeMode->edidValid) { + for (uintptr_t i = 0; i < 128; i++) { + EsPrint("EDID byte %d: %X.\n", i, vbeMode->edid[i]); + } + } + + KGraphicsTarget *target = (KGraphicsTarget *) KDeviceCreate("VBE", parent, sizeof(KGraphicsTarget)); + + linearBuffer = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), vbeMode->bufferPhysical, + vbeMode->bytesPerScanlineLinear * vbeMode->heightPixels, MM_REGION_WRITE_COMBINING); + screenWidth = target->screenWidth = vbeMode->widthPixels; + screenHeight = target->screenHeight = vbeMode->heightPixels; + strideX = vbeMode->bitsPerPixel >> 3; + strideY = vbeMode->bytesPerScanlineLinear; + + if (vbeMode->bitsPerPixel == 32) { + target->updateScreen = UpdateScreen_32_XRGB; + target->debugPutBlock = DebugPutBlock_32_XRGB; + target->debugClearScreen = DebugClearScreen_32_XRGB; + } else { + target->updateScreen = UpdateScreen_24_RGB; + target->debugPutBlock = DebugPutBlock_24_RGB; + target->debugClearScreen = DebugClearScreen_24_RGB; + } + + // TODO Other color modes. + + KRegisterGraphicsTarget(target); +} + +#if 0 + +#define VGA_AC_INDEX 0x3C0 +#define VGA_AC_WRITE 0x3C0 +#define VGA_AC_READ 0x3C1 + +#define VGA_MISC_WRITE 0x3C2 +#define VGA_MISC_READ 0x3CC + +#define VGA_SEQ_INDEX 0x3C4 +#define VGA_SEQ_DATA 0x3C5 + +#define VGA_DAC_READ_INDEX 0x3C7 +#define VGA_DAC_WRITE_INDEX 0x3C8 +#define VGA_DAC_DATA 0x3C9 + +#define VGA_GC_INDEX 0x3CE +#define VGA_GC_DATA 0x3CF + +#define VGA_CRTC_INDEX 0x3D4 +#define VGA_CRTC_DATA 0x3D5 + +#define VGA_INSTAT_READ 0x3DA + +uint8_t vgaMode18[] = { + 0xE3, 0x03, 0x01, 0x08, 0x00, 0x06, 0x5F, 0x4F, + 0x50, 0x82, 0x54, 0x80, 0x0B, 0x3E, 0x00, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0x0C, + 0xDF, 0x28, 0x00, 0xE7, 0x04, 0xE3, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x0F, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x01, 0x00, 0x0F, 0x00, 0x00, +}; + +volatile uint8_t *vgaAddress; + +#define VGA_SCREEN_WIDTH (640) +#define VGA_SCREEN_HEIGHT (480) + +uint8_t egaPaletteConverter[4][64] = { + { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, }, + { 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, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, + { 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, }, +}; + +void VGAUpdateScreen(uint8_t *_source, uint8_t *modifiedScanlineBitset, KModifiedScanline *modifiedScanlines) { + for (int plane = 0; plane < 4; plane++) { + uint8_t *source = _source; + + ProcessorOut8(VGA_SEQ_INDEX, 2); + ProcessorOut8(VGA_SEQ_DATA, 1 << plane); + + for (uintptr_t y_ = 0; y_ < VGA_SCREEN_HEIGHT / 8; y_++) { + if (modifiedScanlineBitset[y_] == 0) { + source += VGA_SCREEN_WIDTH * 32; + continue; + } + + for (uintptr_t y = 0; y < 8; y++) { + uint8_t *sourceStart = source; + + if ((modifiedScanlineBitset[y_] & (1 << y)) == 0) { + source += VGA_SCREEN_WIDTH * 4; + continue; + } + + KModifiedScanline *scanline = modifiedScanlines + y + (y_ << 3); + + uintptr_t x = scanline->minimumX & ~7; + source += 4 * x; + + while (x < scanline->maximumX) { + uint8_t v = 0; + + for (int i = 7; i >= 0; i--) { + if (egaPaletteConverter[plane][((source[0] >> 6) & 3) | ((source[1] >> 4) & 12) | ((source[2] >> 2) & 48)]) { + v |= 1 << i; + } + + source += 4; + } + + vgaAddress[(y + y_ * 8) * 80 + (x >> 3)] = v; + x += 8; + } + + source = sourceStart + VGA_SCREEN_WIDTH * 4; + } + } + } +} + +void VGAPutBlock(uintptr_t x, uintptr_t y, bool toggle) { + for (int plane = 0; plane < 4; plane++) { + ProcessorOut8(VGA_SEQ_INDEX, 2); + ProcessorOut8(VGA_SEQ_DATA, 1 << plane); + + if (toggle) { + vgaAddress[y * 80 + x / 8] ^= 1 << (7 - (x & 7)); + } else { + vgaAddress[y * 80 + x / 8] |= 1 << (7 - (x & 7)); + } + } +} + +void VGAClearScreen() { + for (int plane = 0; plane < 4; plane++) { + ProcessorOut8(VGA_SEQ_INDEX, 2); + ProcessorOut8(VGA_SEQ_DATA, 1 << plane); + EsMemoryZero((void *) vgaAddress, VGA_SCREEN_WIDTH / 8 * VGA_SCREEN_HEIGHT); + } +} + +void InitialiseVGA(KDevice *parent) { + if (KGraphicsIsTargetRegistered()) { + return; + } + + vgaAddress = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), 0xA0000, 0x10000, MM_REGION_WRITE_COMBINING); + uint8_t *registers = vgaMode18; + ProcessorOut8(VGA_MISC_WRITE, *registers++); + for (int i = 0; i < 5; i++) { ProcessorOut8(VGA_SEQ_INDEX, i); ProcessorOut8(VGA_SEQ_DATA, *registers++); } + ProcessorOut8(VGA_CRTC_INDEX, 0x03); + ProcessorOut8(VGA_CRTC_DATA, ProcessorIn8(VGA_CRTC_DATA) | 0x80); + ProcessorOut8(VGA_CRTC_INDEX, 0x11); + ProcessorOut8(VGA_CRTC_DATA, ProcessorIn8(VGA_CRTC_DATA) & ~0x80); + registers[0x03] |= 0x80; + registers[0x11] &= ~0x80; + for (int i = 0; i < 25; i++) { ProcessorOut8(VGA_CRTC_INDEX, i); ProcessorOut8(VGA_CRTC_DATA, *registers++); } + for (int i = 0; i < 9; i++) { ProcessorOut8(VGA_GC_INDEX, i); ProcessorOut8(VGA_GC_DATA, *registers++); } + for (int i = 0; i < 21; i++) { ProcessorIn8(VGA_INSTAT_READ); ProcessorOut8(VGA_AC_INDEX, i); ProcessorOut8(VGA_AC_WRITE, *registers++); } + ProcessorIn8(VGA_INSTAT_READ); + ProcessorOut8(VGA_AC_INDEX, 0x20); + + KGraphicsTarget *target = (KGraphicsTarget *) KDeviceCreate("VGA", parent, sizeof(KGraphicsTarget)); + target->screenWidth = VGA_SCREEN_WIDTH; + target->screenHeight = VGA_SCREEN_HEIGHT; + target->updateScreen = VGAUpdateScreen; + target->debugPutBlock = VGAPutBlock; + target->debugClearScreen = VGAClearScreen; + target->reducedColors = true; + // TODO Debug callbacks. + KRegisterGraphicsTarget(target); +} + +#endif + +KDriver driverSVGA = { + .attach = InitialiseVBE, +}; diff --git a/drivers/usb.cpp b/drivers/usb.cpp new file mode 100644 index 0000000..ffeeb79 --- /dev/null +++ b/drivers/usb.cpp @@ -0,0 +1,340 @@ +#include + +#define SETUP_FLAG_D2H (0x80) + +#define DESCRIPTOR_DEVICE (1) +#define DESCRIPTOR_CONFIGURATION (2) +#define DESCRIPTOR_STRING (3) +#define DESCRIPTOR_INTERFACE (4) +#define DESCRIPTOR_ENDPOINT (5) + +KUSBDescriptorHeader *KUSBDevice::GetCommonDescriptor(uint8_t type, uintptr_t index) { + uintptr_t position = selectedConfigurationOffset; + + while (position < configurationDescriptorsBytes) { + KUSBDescriptorHeader *header = (KUSBDescriptorHeader *) (configurationDescriptors + position); + + if (header->descriptorType == DESCRIPTOR_INTERFACE || header->descriptorType == DESCRIPTOR_CONFIGURATION) { + return nullptr; + } else if (header->descriptorType == type) { + if (index) { + index--; + } else { + return header; + } + } + + position += header->length; + } + + return nullptr; +} + +struct SynchronousTransfer { + KEvent complete; + size_t *bytesNotTransferred; + bool success; +}; + +void SynchronousTransferCallback(ptrdiff_t bytesNotTransferred, EsGeneric context) { + SynchronousTransfer *transfer = (SynchronousTransfer *) context.p; + + if (bytesNotTransferred != -1) { + if (transfer->bytesNotTransferred) { + transfer->success = true; + *transfer->bytesNotTransferred = bytesNotTransferred; + } else if (!bytesNotTransferred) { + transfer->success = true; + } + } + + KEventSet(&transfer->complete); +} + +bool KUSBDevice::RunTransfer(KUSBEndpointDescriptor *endpoint, void *buffer, size_t bufferBytes, size_t *bytesNotTransferred) { + SynchronousTransfer transfer = {}; + transfer.bytesNotTransferred = bytesNotTransferred; + queueTransfer(this, endpoint, SynchronousTransferCallback, buffer, bufferBytes, &transfer); + KEventWait(&transfer.complete); + return transfer.success; +} + +bool KUSBDevice::GetString(uint8_t index, char *buffer, size_t bufferBytes) { + uint16_t wideBuffer[127]; + + if (!bufferBytes || !index) { + return false; + } + + uint16_t transferred = 0; + + if (!controlTransfer(this, SETUP_FLAG_D2H, 0x06 /* get descriptor */, + ((uint16_t) DESCRIPTOR_STRING << 8) | (uint16_t) index, 0, + wideBuffer, sizeof(wideBuffer), K_ACCESS_READ, &transferred)) { + return false; + } + + if (transferred < 4) { + return false; + } + + size_t inputCharactersRemaining = (*(uint8_t *) wideBuffer) / 2 - 1; + + if ((size_t) (transferred / 2 - 1) < inputCharactersRemaining) { + inputCharactersRemaining = transferred / 2 - 1; + } + + uint16_t *inputPosition = wideBuffer + 1; + uintptr_t bufferPosition = 0; + + while (inputCharactersRemaining) { + uint32_t c = *inputPosition; + inputCharactersRemaining--; + inputPosition++; + + if (c >= 0xD800 && c < 0xDC00 && inputCharactersRemaining) { + uint32_t c2 = *inputPosition; + + if (c2 >= 0xDC00 && c2 < 0xE000) { + inputCharactersRemaining--; + inputPosition++; + + c = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; + } + } + + size_t encodedBytes = utf8_encode(c, nullptr); + + if (bufferPosition + encodedBytes < bufferBytes) { + utf8_encode(c, buffer + bufferPosition); + bufferPosition += encodedBytes; + } else { + break; + } + } + + buffer[bufferPosition] = 0; + + return true; +} + +bool USBInterfaceClassCheck(KInstalledDriver *driver, KDevice *device) { + KUSBInterfaceDescriptor *descriptor = &((KUSBDevice *) device)->interfaceDescriptor; + + int classCode = -1, subclassCode = -1, protocol = -1; + + EsINIState s = {}; + s.buffer = driver->config, s.bytes = driver->configBytes; + + while (EsINIParse(&s)) { + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("classCode"))) classCode = EsIntegerParse(s.value, s.valueBytes); + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("subclassCode"))) subclassCode = EsIntegerParse(s.value, s.valueBytes); + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("protocol"))) protocol = EsIntegerParse(s.value, s.valueBytes); + } + + if (classCode == -1 && subclassCode == -1 && protocol == -1) { + return false; + } + + if (classCode != -1 && descriptor->interfaceClass != classCode) return false; + if (subclassCode != -1 && descriptor->interfaceSubclass != subclassCode) return false; + if (protocol != -1 && descriptor->interfaceProtocol != protocol) return false; + + return true; +} + +bool USBProductIDCheck(KInstalledDriver *driver, KDevice *device) { + KUSBDeviceDescriptor *descriptor = &((KUSBDevice *) device)->deviceDescriptor; + + int productID = -1, vendorID = -1, version = -1; + + EsINIState s = {}; + s.buffer = driver->config, s.bytes = driver->configBytes; + + while (EsINIParse(&s)) { + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("productID"))) productID = EsIntegerParse(s.value, s.valueBytes); + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("vendorID"))) vendorID = EsIntegerParse(s.value, s.valueBytes); + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("version"))) version = EsIntegerParse(s.value, s.valueBytes); + } + + if (productID == -1 && vendorID == -1 && version == -1) { + return false; + } + + if (productID != -1 && descriptor->productID != productID) return false; + if (vendorID != -1 && descriptor->vendorID != vendorID) return false; + if (version != -1 && descriptor->deviceVersion != version) return false; + + return true; +} + +void KRegisterUSBDevice(KUSBDevice *device) { + EsDefer(KDeviceCloseHandle(device)); + + bool foundInterfaceDescriptor = false; + + { + // Get the device descriptor. + + uint16_t transferred; + + if (!device->controlTransfer(device, SETUP_FLAG_D2H, 0x06 /* get descriptor */, + (DESCRIPTOR_DEVICE << 8) | 0x00, 0, &device->deviceDescriptor, + sizeof(KUSBDeviceDescriptor), K_ACCESS_READ, &transferred) + || transferred != sizeof(KUSBDeviceDescriptor)) { + return; + } + + if (device->deviceDescriptor.length < sizeof(KUSBDeviceDescriptor) + || device->deviceDescriptor.descriptorType != DESCRIPTOR_DEVICE + || device->deviceDescriptor.configurationCount == 0) { + KernelLog(LOG_ERROR, "USB", "invalid device descriptor", "Device descriptor is invalid or unsupported.\n"); + return; + } + } + + KernelLog(LOG_INFO, "USB", "device identification", "Device has identification %W/%W/%W.\n", + device->deviceDescriptor.vendorID, device->deviceDescriptor.productID, device->deviceDescriptor.deviceVersion); + + { + // Get strings. + + char buffer[256]; + + if (device->GetString(device->deviceDescriptor.manufacturerString, buffer, sizeof(buffer))) { + KernelLog(LOG_INFO, "USB", "device manufacturer", "Device manufacturer string: '%z'.\n", buffer); + } else { + goto skipStrings; + } + + if (device->GetString(device->deviceDescriptor.productString, buffer, sizeof(buffer))) { + KernelLog(LOG_INFO, "USB", "device product", "Device product string: '%z'.\n", buffer); + } else { + goto skipStrings; + } + + if (device->GetString(device->deviceDescriptor.serialNumberString, buffer, sizeof(buffer))) { + KernelLog(LOG_INFO, "USB", "device serial number", "Device serial number string: '%z'.\n", buffer); + } else { + goto skipStrings; + } + + skipStrings:; + } + + { + // Get the configuration descriptor. + + uint16_t transferred; + + if (!device->controlTransfer(device, SETUP_FLAG_D2H, 0x06 /* get descriptor */, + (DESCRIPTOR_CONFIGURATION << 8) | 0x00, 0, &device->configurationDescriptor, + sizeof(KUSBConfigurationDescriptor), K_ACCESS_READ, &transferred) + || transferred != sizeof(KUSBConfigurationDescriptor)) { + return; + } + + if (device->configurationDescriptor.totalLength < sizeof(KUSBConfigurationDescriptor) + || device->configurationDescriptor.descriptorType != DESCRIPTOR_CONFIGURATION + || !device->configurationDescriptor.interfaceCount + || !device->configurationDescriptor.configurationIndex) { + KernelLog(LOG_ERROR, "USB", "invalid configuration descriptor", "Invalid field in configuration descriptor.\n"); + return; + } + } + + { + // Read the rest of the configuration descriptors. + + uint8_t *buffer = (uint8_t *) EsHeapAllocate(device->configurationDescriptor.totalLength, false, K_FIXED); + + if (!buffer) { + KernelLog(LOG_ERROR, "USB", "allocation failure", "Could not allocate buffer to read all configuration descriptors.\n"); + return; + } + + uint16_t transferred; + + if (!device->controlTransfer(device, SETUP_FLAG_D2H, 0x06 /* get descriptor */, + (DESCRIPTOR_CONFIGURATION << 8) | 0x00, 0, buffer, + device->configurationDescriptor.totalLength, K_ACCESS_READ, &transferred) + || transferred != device->configurationDescriptor.totalLength) { + return; + } + + device->configurationDescriptors = buffer; + device->configurationDescriptorsBytes = device->configurationDescriptor.totalLength; + } + + { + // Check the configuration descriptors are valid. + + uintptr_t position = 0; + uintptr_t configurationsSeen = 0; + + while (position < device->configurationDescriptorsBytes) { + if (position >= device->configurationDescriptorsBytes - sizeof(KUSBDescriptorHeader)) { + KernelLog(LOG_ERROR, "USB", "descriptor invalid length", "Remaining %D, too small for descriptor.\n", + device->configurationDescriptorsBytes - position); + return; + } + + KUSBDescriptorHeader *header = (KUSBDescriptorHeader *) (device->configurationDescriptors + position); + + if (header->length < sizeof(KUSBDescriptorHeader) || header->length > device->configurationDescriptorsBytes - position) { + KernelLog(LOG_ERROR, "USB", "descriptor invalid length", "Given length %D, remaining %D.\n", + header->length, device->configurationDescriptorsBytes - position); + return; + } + + if (header->descriptorType == DESCRIPTOR_CONFIGURATION + && header->length >= sizeof(KUSBConfigurationDescriptor)) { + configurationsSeen++; + } + + if (header->descriptorType == DESCRIPTOR_INTERFACE + && header->length >= sizeof(KUSBInterfaceDescriptor) + && !foundInterfaceDescriptor + && configurationsSeen == 1) { + device->interfaceDescriptor = *(KUSBInterfaceDescriptor *) header; + device->selectedConfigurationOffset = position + header->length; + foundInterfaceDescriptor = true; + } + + position += header->length; + } + } + + { + // Look for a driver that matches the vendor/product. + + if (KDeviceAttach(device, "USB", USBProductIDCheck)) { + return; + } + + // Otherwise, pick the default configuration and interface. + + if (!foundInterfaceDescriptor) { + KernelLog(LOG_ERROR, "USB", "no interface descriptor", + "The device does not have any interface descriptors for the default configuration."); + } + + KernelLog(LOG_INFO, "USB", "device interface", "Device has interface %d with identification %X/%X/%X.\n", + device->interfaceDescriptor.interfaceIndex, device->interfaceDescriptor.interfaceClass, + device->interfaceDescriptor.interfaceSubclass, device->interfaceDescriptor.interfaceProtocol); + + if (!device->selectConfigurationAndInterface(device)) { + KernelLog(LOG_ERROR, "USB", "select interface failure", "Could not select configuration and interface for device.\n"); + return; + } + + if (KDeviceAttach(device, "USB", USBInterfaceClassCheck)) { + return; + } + + KernelLog(LOG_ERROR, "USB", "no driver", "No driver could be found for the device.\n"); + // TODO Show an error message to the user. + } +} + +KDriver driverUSB; diff --git a/drivers/usb_bulk.cpp b/drivers/usb_bulk.cpp new file mode 100644 index 0000000..e41d881 --- /dev/null +++ b/drivers/usb_bulk.cpp @@ -0,0 +1,223 @@ +#include + +// TODO STALL handling. +// TODO Resetting the device on error. +// TODO Command timeout. + +struct Device : KDevice { + KUSBDevice *parent; + KUSBEndpointDescriptor *inputEndpoint, *outputEndpoint; + uint8_t maximumLUN; + KMutex mutex; + + void Initialise(); + bool DoTransfer(struct CommandBlock *block, void *buffer); +}; + +struct Drive : KBlockDevice { + Device *device; + uint8_t lun; +}; + +struct CommandBlock { +#define COMMAND_BLOCK_SIGNATURE (0x43425355) + uint32_t signature; + uint32_t tag; // Returned in the corresponding command status. + uint32_t transferBytes; +#define COMMAND_FLAGS_INPUT (0x80) +#define COMMAND_FLAGS_OUTPUT (0x00) + uint8_t flags; + uint8_t lun; + uint8_t commandBytes; + uint8_t command[16]; +} __attribute__((packed)); + +struct CommandStatus { +#define COMMAND_STATUS_SIGNATURE (0x53425355) + uint32_t signature; + uint32_t tag; + uint32_t residue; +#define STATUS_FAILED (1) +#define STATUS_PHASE_ERROR (2) + uint8_t status; +} __attribute__((packed)); + +bool Device::DoTransfer(CommandBlock *block, void *buffer) { + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + block->signature = COMMAND_BLOCK_SIGNATURE; + block->tag = EsRandomU64() & 0xFFFFFFFF; + + KernelLog(LOG_VERBOSE, "USBBulk", "transfer", "Transferring %D to %x, %z, LUN %d, command %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X (%D).\n", + block->transferBytes, buffer, block->flags == COMMAND_FLAGS_INPUT ? "input" : "output", block->lun, + block->command[0], block->command[1], block->command[2], block->command[3], + block->command[4], block->command[5], block->command[6], block->command[7], + block->command[8], block->command[9], block->command[10], block->command[11], + block->command[12], block->command[13], block->command[14], block->command[15], block->commandBytes); + + // Send the command block to the output endpoint. + + if (!parent->RunTransfer(outputEndpoint, block, sizeof(CommandBlock), nullptr)) { + KernelLog(LOG_ERROR, "USBBulk", "send command block error", "Could not send the command block to the device.\n"); + return false; + } + + // Perform the transfer. + + if (!parent->RunTransfer(block->flags == COMMAND_FLAGS_INPUT ? inputEndpoint : outputEndpoint, buffer, block->transferBytes, nullptr)) { + KernelLog(LOG_ERROR, "USBBulk", "transfer error", "Could not transfer with the device.\n"); + return false; + } + + // Read the command status from the input endpoint. + + CommandStatus status = {}; + + if (!parent->RunTransfer(inputEndpoint, &status, sizeof(CommandStatus), nullptr)) { + KernelLog(LOG_ERROR, "USBBulk", "read command status error", "Could not read the command status from the device.\n"); + return false; + } + + if (status.signature != COMMAND_STATUS_SIGNATURE + || status.tag != block->tag + || status.residue + || status.status) { + KernelLog(LOG_ERROR, "USBBulk", "command unsuccessful", "Command status indicates it was unsuccessful: " + "signature: %x, tag: %x (%x), residue: %D, status: %d.\n", + status.signature, status.tag, block->tag, status.residue, status.status); + return false; + } + + return true; +} + +void DriveAccess(KBlockDeviceAccessRequest request) { + Drive *drive = (Drive *) request.device; + + Device *device = drive->device; + request.dispatchGroup->Start(); + + uint32_t offsetSectors = request.offset / drive->sectorSize; + uint32_t countSectors = request.count / drive->sectorSize; + + CommandBlock command = { + .transferBytes = (uint32_t) request.count, + .flags = (uint8_t) (request.operation == K_ACCESS_WRITE ? COMMAND_FLAGS_OUTPUT : COMMAND_FLAGS_INPUT), + .lun = drive->lun, + .commandBytes = 10, + + .command = { + [0] = (uint8_t) (request.operation == K_ACCESS_WRITE ? 0x2A /* WRITE (12) */ : 0x28 /* READ (12) */), + [1] = 0, + [2] = (uint8_t) (offsetSectors >> 0x18), + [3] = (uint8_t) (offsetSectors >> 0x10), + [4] = (uint8_t) (offsetSectors >> 0x08), + [5] = (uint8_t) (offsetSectors >> 0x00), + [6] = 0, + [7] = (uint8_t) (countSectors >> 0x08), + [8] = (uint8_t) (countSectors >> 0x00), + }, + }; + + request.dispatchGroup->End(device->DoTransfer(&command, (void *) KDMABufferGetVirtualAddress(request.buffer))); +} + +void Device::Initialise() { + uint16_t transferred; + + // Find the input and output endpoints. + + for (uintptr_t i = 0; true; i++) { + KUSBEndpointDescriptor *endpoint = (KUSBEndpointDescriptor *) parent->GetCommonDescriptor(0x05 /* endpoint */, i); + + if (!endpoint) { + break; + } else if (endpoint->IsBulk() && endpoint->IsInput() && !inputEndpoint) { + inputEndpoint = endpoint; + } else if (endpoint->IsBulk() && endpoint->IsOutput() && !outputEndpoint) { + outputEndpoint = endpoint; + } + } + + if (!inputEndpoint || !outputEndpoint) { + KernelLog(LOG_ERROR, "USBBulk", "endpoint missing", "Could not find both bulk endpoints.\n"); + return; + } + + // Reset the mass storage device. + + if (!parent->controlTransfer(parent, 0b00100001, 0b11111111, 0, parent->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, &transferred)) { + KernelLog(LOG_ERROR, "USBBulk", "reset failure", "Could not reset the mass storage device.\n"); + return; + } + + // Get the maximum LUN. + + parent->controlTransfer(parent, 0b10100001, 0b11111110, 0, parent->interfaceDescriptor.interfaceIndex, &maximumLUN, 1, K_ACCESS_READ, &transferred); + KernelLog(LOG_INFO, "USBBulk", "maximum LUN", "Device reports maximum LUN of %d.\n", maximumLUN); + + for (uintptr_t i = 0; i <= maximumLUN; i++) { + // Get the capacity of the LUN. + + CommandBlock command = { + .transferBytes = 8, + .flags = COMMAND_FLAGS_INPUT, + .lun = (uint8_t) i, + .commandBytes = 10, + .command = { [0] = 0x25 /* READ CAPACITY (10) */ }, + }; + + uint8_t capacity[8]; + + if (!DoTransfer(&command, capacity)) { + KernelLog(LOG_ERROR, "USBBulk", "read capacity error", "Could not read the capacity of LUN %d.\n", i); + continue; + } + + uint32_t sectorCount = (((uint32_t) capacity[3] << 0) + ((uint32_t) capacity[2] << 8) + + ((uint32_t) capacity[1] << 16) + ((uint32_t) capacity[0] << 24)) + 1; + uint32_t sectorBytes = ((uint32_t) capacity[7] << 0) + ((uint32_t) capacity[6] << 8) + + ((uint32_t) capacity[5] << 16) + ((uint32_t) capacity[4] << 24); + + KernelLog(LOG_INFO, "USBBulk", "capacity", "LUN %d has capacity of %D (one sector is %D).\n", + i, (uint64_t) sectorCount * sectorBytes, sectorBytes); + + // Register the drive. + + Drive *drive = (Drive *) KDeviceCreate("USB bulk drive", this, sizeof(Drive)); + + if (!drive) { + KernelLog(LOG_ERROR, "USBBulk", "allocation failure", "Could not create drive for LUN %d.\n", i); + break; + } + + drive->device = this; + drive->lun = i; + drive->sectorSize = sectorBytes; + drive->sectorCount = sectorCount; + drive->maxAccessSectorCount = 262144 / sectorBytes; // TODO How to determine this? What does the USB layer support? + drive->readOnly = false; // TODO How to detect this? + drive->access = DriveAccess; + drive->driveType = ES_DRIVE_TYPE_USB_MASS_STORAGE; + + FSRegisterBlockDevice(drive); + } +} + +static void DeviceAttach(KDevice *parent) { + Device *device = (Device *) KDeviceCreate("USB bulk", parent, sizeof(Device)); + + if (!device) { + KernelLog(LOG_ERROR, "USBBulk", "allocation failure", "Could not allocate device structure.\n"); + return; + } + + device->parent = (KUSBDevice *) parent; + device->Initialise(); + KDeviceCloseHandle(device); +} + +KDriver driverUSBBulk = { + .attach = DeviceAttach, +}; diff --git a/drivers/usb_hid.cpp b/drivers/usb_hid.cpp new file mode 100644 index 0000000..806e19d --- /dev/null +++ b/drivers/usb_hid.cpp @@ -0,0 +1,737 @@ +#include + +// TODO Key repeat not working on Qemu. + +struct ReportItem { + uint32_t usage, application, arrayCount; + int32_t logicalMinimum, logicalMaximum; + + uint8_t reportPrefix; + uint8_t bits; + uint8_t group; + +#define REPORT_ITEM_CONSTANT (1 << 0) +#define REPORT_ITEM_RELATIVE (1 << 1) +#define REPORT_ITEM_WRAP (1 << 2) +#define REPORT_ITEM_NON_LINEAR (1 << 3) +#define REPORT_ITEM_SIGNED (1 << 4) +#define REPORT_ITEM_ARRAY (1 << 5) + uint8_t flags; + +#define REPORT_ITEM_INPUT (1) +#define REPORT_ITEM_OUTPUT (2) +#define REPORT_ITEM_FEATURE (3) + uint8_t type; +}; + +struct BitBuffer { + const uint8_t *buffer; + size_t bytes; + uintptr_t index; + + void Discard(size_t count); + uint32_t ReadUnsigned(size_t count); + int32_t ReadSigned(size_t count); +}; + +struct GameController { + uint64_t id; + uint8_t reportPrefix; +}; + +struct HIDDevice : KDevice { + KUSBDevice *device; + + Array reportItems; + bool usesReportPrefixes; + + Array gameControllers; + + KUSBEndpointDescriptor *reportEndpoint; + uint8_t *lastReport; + size_t lastReportBytes; + + void Initialise(); + bool ParseReportDescriptor(const uint8_t *report, size_t reportBytes); + void ReportReceived(BitBuffer *buffer); +}; + +struct HIDDescriptorLink { + uint8_t type; + uint8_t length[2]; +}; + +struct HIDDescriptor : KUSBDescriptorHeader { + uint8_t specification[2]; + uint8_t countryCode; + uint8_t linkCount; + HIDDescriptorLink links[1]; +}; + +struct ReportGlobalState { + int32_t logicalMinimum, logicalMaximum; + uint16_t usagePage; + uint8_t reportSize, reportCount; + uint8_t reportID; +}; + +struct ReportLocalState { +#define USAGE_ARRAY_SIZE (32) + uint32_t usages[USAGE_ARRAY_SIZE]; + uint32_t usageMinimum, usageMaximum; + uint8_t usageCount; + +#define DELIMITER_NONE (0) +#define DELIMITER_FIRST (1) +#define DELIMITER_IGNORE (2) + uint8_t delimiterState; +}; + +struct UsageString { + uint32_t usage; + const char *string; +}; + +#define HID_APPLICATION_MOUSE (0x010002) +#define HID_APPLICATION_JOYSTICK (0x010004) +#define HID_APPLICATION_KEYBOARD (0x010006) +#define HID_USAGE_X_AXIS (0x010030) +#define HID_USAGE_Y_AXIS (0x010031) +#define HID_USAGE_Z_AXIS (0x010032) +#define HID_USAGE_X_ROTATION (0x010033) +#define HID_USAGE_Y_ROTATION (0x010034) +#define HID_USAGE_Z_ROTATION (0x010035) +#define HID_USAGE_HAT_SWITCH (0x010039) +#define HID_USAGE_KEYCODES (0x070000) +#define HID_USAGE_BUTTON_1 (0x090001) +#define HID_USAGE_BUTTON_2 (0x090002) +#define HID_USAGE_BUTTON_3 (0x090003) +#define HID_USAGE_BUTTON_16 (0x090010) + +UsageString usageStrings[] = { + { 0x000000, "padding" }, + + // Generic desktop page. + { 0x010001, "pointer" }, + { 0x010002, "mouse" }, + { 0x010004, "joystick" }, + { 0x010005, "gamepad" }, + { 0x010006, "keyboard" }, + { 0x010007, "keypad" }, + { 0x010008, "multi-axis controller" }, + { 0x010009, "tablet PC system controls" }, + { 0x010030, "X axis" }, + { 0x010031, "Y axis" }, + { 0x010032, "Z axis" }, + { 0x010033, "X rotation" }, + { 0x010034, "Y rotation" }, + { 0x010035, "Z rotation" }, + { 0x010036, "slider" }, + { 0x010037, "dial" }, + { 0x010038, "wheel" }, + { 0x010039, "hat switch" }, + + // Keyboard/keypad page. + { 0x070000, "keycodes" }, + { 0x0700E0, "left ctrl" }, + { 0x0700E1, "left shift" }, + { 0x0700E2, "left alt" }, + { 0x0700E3, "left gui" }, + { 0x0700E4, "right ctrl" }, + { 0x0700E5, "right shift" }, + { 0x0700E6, "right alt" }, + { 0x0700E7, "right gui" }, + + // LED page. + { 0x080001, "num lock" }, + { 0x080002, "caps lock" }, + { 0x080003, "scroll lock" }, + { 0x080004, "compose" }, + { 0x080005, "kana" }, + + // Button page. + { 0x090001, "button 1" }, + { 0x090002, "button 2" }, + { 0x090003, "button 3" }, + { 0x090004, "button 4" }, + { 0x090005, "button 5" }, + { 0x090006, "button 6" }, + { 0x090007, "button 7" }, + { 0x090008, "button 8" }, + { 0x090009, "button 9" }, + { 0x09000A, "button 10" }, + { 0x09000B, "button 11" }, + { 0x09000C, "button 12" }, + { 0x09000D, "button 13" }, + { 0x09000E, "button 14" }, + { 0x09000F, "button 15" }, + { 0x090010, "button 16" }, +}; + +const char *LookupUsageString(uint32_t usage) { + if (usage > 0xFF000000) { + return "vendor-specific"; + } + + for (uintptr_t i = 0; i < sizeof(usageStrings) / sizeof(usageStrings[0]); i++) { + if (usageStrings[i].usage == usage) { + return usageStrings[i].string; + } + } + + EsPrint("unknown usage %x\n", usage); + return "unknown"; +} + +void BitBuffer::Discard(size_t count) { + index += count; +} + +uint32_t BitBuffer::ReadUnsigned(size_t count) { + uint32_t result = 0; + uint32_t bit = 0; + + while (bit != count) { + uintptr_t byte = index >> 3; + + if (byte >= bytes) { + break; + } + + if (buffer[byte] & (1 << (index & 7))) { + result |= 1 << bit; + } + + bit++, index++; + } + + return result; +} + +int32_t BitBuffer::ReadSigned(size_t count) { + if (!count) return 0; + + uint32_t result = ReadUnsigned(count); + + if (result & (1 << (count - 1))) { + for (uintptr_t i = count; i < 32; i++) { + result |= 1 << i; + } + } + + return result; +} + +bool HIDDevice::ParseReportDescriptor(const uint8_t *report, size_t reportBytes) { +#define REPORT_GLOBAL_STACK_SIZE (8) + ReportGlobalState global[REPORT_GLOBAL_STACK_SIZE] = {}; + uintptr_t gIndex = 0; + ReportLocalState local = {}; + uint32_t application = 0; + uint8_t group = 0; + + uintptr_t position = 0; + + while (position < reportBytes) { + uint8_t header = report[position]; + + if (header == 0xFE) { + // Long items, unused. + if (position + 3 > reportBytes) return false; + position += 3 + report[position + 1]; + continue; + } + + uint8_t size = header & 3; + uint8_t type = header & ~3; + position++; + + if (size == 3) size++; + if (position + size > reportBytes) return false; + + uint32_t uData = 0; + int32_t sData = 0; + + for (uintptr_t i = 0; i < size; i++) { + uData |= report[position + i] << (i * 8); + } + + sData = uData; + + if (size && (report[position + size - 1] & 0x80)) { + for (uintptr_t i = size; i < 4; i++) { + sData |= 0xFF << (i * 8); + } + } + + position += size; + + switch (type) { + case 0b00000100: { global[gIndex].usagePage = uData; } break; + case 0b00010100: { global[gIndex].logicalMinimum = sData; } break; + case 0b00100100: { global[gIndex].logicalMaximum = sData; } break; + case 0b01110100: { global[gIndex].reportSize = uData; } break; + case 0b10010100: { global[gIndex].reportCount = uData; } break; + + case 0b10000100: { + global[gIndex].reportID = uData; + if (uData) usesReportPrefixes = true; + } break; + + case 0b10100100: { + if (gIndex + 1 == REPORT_GLOBAL_STACK_SIZE) return false; + gIndex++; + global[gIndex] = global[gIndex - 1]; + } break; + + case 0b10110100: { + if (gIndex == 0) return false; + gIndex--; + } break; + + case 0b00001000: { + if (local.usageCount == USAGE_ARRAY_SIZE) return false; + + if (local.delimiterState != DELIMITER_IGNORE) { + local.usages[local.usageCount++] = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0); + } + + if (local.delimiterState == DELIMITER_FIRST) local.delimiterState = DELIMITER_IGNORE; + } break; + + case 0b00011000: { local.usageMinimum = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0); } break; + case 0b00101000: { local.usageMaximum = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0); } break; + + case 0b10101000: { + if (uData) local.delimiterState = DELIMITER_FIRST; + else local.delimiterState = DELIMITER_NONE; + } break; + + case 0b10100000: { + if (uData == 1) { + if (local.usageCount == 0) return false; + application = local.usages[0]; + group++; + } + } break; + + case 0b10010000: + case 0b10110000: + case 0b10000000: { + for (uintptr_t i = 0; i < global[gIndex].reportCount; i++) { + ReportItem item = { + .application = application, + .logicalMinimum = global[gIndex].logicalMinimum, + .logicalMaximum = global[gIndex].logicalMaximum, + .reportPrefix = global[gIndex].reportID, + .bits = global[gIndex].reportSize, + }; + + if (type == 0b10000000) { + item.type = REPORT_ITEM_INPUT; + } else if (type == 0b10010000) { + item.type = REPORT_ITEM_OUTPUT; + } else if (type == 0b10110000) { + item.type = REPORT_ITEM_FEATURE; + } + + if ( uData & (1 << 0)) item.flags |= REPORT_ITEM_CONSTANT; + if (~uData & (1 << 1)) item.flags |= REPORT_ITEM_ARRAY; + if ( uData & (1 << 2)) item.flags |= REPORT_ITEM_RELATIVE; + if ( uData & (1 << 3)) item.flags |= REPORT_ITEM_WRAP; + if ( uData & (1 << 4)) item.flags |= REPORT_ITEM_NON_LINEAR; + if (item.logicalMinimum < 0 || item.logicalMaximum < 0) item.flags |= REPORT_ITEM_SIGNED; + + if (local.usageCount) { + item.usage = local.usages[i >= local.usageCount ? local.usageCount - 1 : i]; + } else { + item.usage = (i > local.usageMaximum - local.usageMinimum) ? local.usageMaximum : (local.usageMinimum + i); + } + + if (item.flags & REPORT_ITEM_ARRAY) { + item.arrayCount = global[gIndex].reportCount; + } + + if (!reportItems.Add(item)) { + return false; + } + + KernelLog(LOG_INFO, "USBHID", "parsed report item", + "Parsed report item - group: %d, application: '%z', usage: '%z', range: %i->%i, report: %d, bits: %d, " + "flags: %z%z%z%z%z%z, type: %z, array count: %d\n", + group, LookupUsageString(item.application), LookupUsageString(item.usage), + item.logicalMinimum, item.logicalMaximum, + item.reportPrefix, item.bits, + (item.flags & REPORT_ITEM_CONSTANT) ? "constant|" : "", + (item.flags & REPORT_ITEM_RELATIVE) ? "relative|" : "", + (item.flags & REPORT_ITEM_WRAP) ? "wrap|" : "", + (item.flags & REPORT_ITEM_NON_LINEAR) ? "non-linear|" : "", + (item.flags & REPORT_ITEM_ARRAY) ? "array|" : "", + (item.flags & REPORT_ITEM_SIGNED) ? "signed" : "unsigned", + item.type == REPORT_ITEM_INPUT ? "input" : item.type == REPORT_ITEM_OUTPUT ? "output" : "feature", item.arrayCount); + + if (item.application == HID_APPLICATION_KEYBOARD) { + cDebugName = "USB HID keyboard"; + } else if (item.application == HID_APPLICATION_MOUSE) { + cDebugName = "USB HID mouse"; + } else if (item.application == HID_APPLICATION_JOYSTICK) { + cDebugName = "USB HID joystick"; + } + + if (item.flags & REPORT_ITEM_ARRAY) { + break; + } + } + } break; + } + + if ((type & 0b00001100) == 0) { + EsMemoryZero(&local, sizeof(ReportLocalState)); + } + } + + return true; +} + +void ReportReceivedCallback(ptrdiff_t bytesNotTransferred, EsGeneric context) { + HIDDevice *device = (HIDDevice *) context.p; + size_t bytesTransferred = device->reportEndpoint->GetMaximumPacketSize() - bytesNotTransferred; + + if (bytesNotTransferred == -1) { + KernelLog(LOG_ERROR, "USBHID", "report transfer failure", "Report transfer failed.\n"); + bytesTransferred = 0; + } + + BitBuffer buffer = { device->lastReport, bytesTransferred }; + device->ReportReceived(&buffer); +} + +void HIDDevice::ReportReceived(BitBuffer *buffer) { + uint8_t prefix = 0; + + if (usesReportPrefixes) { + prefix = buffer->ReadUnsigned(8); + } + +#ifdef TRACE_REPORTS + EsPrint("-- report (%d) --\n", prefix); +#endif + + bool mouseEvent = false; + int mouseXMovement = 0, mouseYMovement = 0, mouseButtons = 0; + bool keyboardEvent = false; + uint16_t keysDown[32]; + size_t keysDownCount = 0; + bool gameControllerEvent = false; + EsGameControllerState controllerState = {}; + controllerState.directionalPad = 15; + controllerState.analogCount = 2; + + for (uintptr_t i = 0; i < gameControllers.Length(); i++) { + if (gameControllers[i].reportPrefix == prefix) { + controllerState.id = gameControllers[i].id; + } + } + + for (uintptr_t i = 0; i < reportItems.Length(); i++) { + ReportItem *item = &reportItems[i]; + + if (item->type != REPORT_ITEM_INPUT || item->reportPrefix != prefix) { + continue; + } + +#ifdef TRACE_REPORTS + uintptr_t startIndex = buffer->index; + + EsPrint("%d/%z: ", item->group, LookupUsageString(item->usage)); + + size_t count = (item->flags & REPORT_ITEM_ARRAY) ? item->arrayCount : 1; + + for (uintptr_t i = 0; i < count; i++) { + if (item->flags & REPORT_ITEM_SIGNED) { + EsPrint("%i", buffer->ReadSigned(item->bits)); + } else { + EsPrint("%d", buffer->ReadUnsigned(item->bits)); + } + + if (i != count - 1) { + EsPrint(", "); + } + } + + EsPrint("\n"); + + buffer->index = startIndex; +#endif + + if (item->flags & REPORT_ITEM_ARRAY) { + bool handled = false; + + if (item->application == HID_APPLICATION_KEYBOARD) { + if (item->usage == HID_USAGE_KEYCODES) { + for (uintptr_t i = 0; i < item->arrayCount; i++) { + uint32_t scancode = buffer->ReadUnsigned(item->bits); + keyboardEvent = true; + + if (scancode > 0 && scancode < 0x200 && keysDownCount != 32) { + keysDown[keysDownCount++] = scancode; + } + } + } + } + + if (!handled) { + buffer->Discard(item->bits * item->arrayCount); + } + } else { + bool handled = false; + + if (item->application == HID_APPLICATION_MOUSE) { + // TODO Handle unsigned, absolute, and wrapping movements. + + mouseEvent = true; + handled = true; + + if (item->usage == HID_USAGE_X_AXIS) { + mouseXMovement = buffer->ReadSigned(item->bits); + } else if (item->usage == HID_USAGE_Y_AXIS) { + mouseYMovement = buffer->ReadSigned(item->bits); + } else if (item->usage == HID_USAGE_BUTTON_1) { + if (buffer->ReadUnsigned(item->bits)) mouseButtons |= 1 << 0; + } else if (item->usage == HID_USAGE_BUTTON_2) { + if (buffer->ReadUnsigned(item->bits)) mouseButtons |= 1 << 2; + } else if (item->usage == HID_USAGE_BUTTON_3) { + if (buffer->ReadUnsigned(item->bits)) mouseButtons |= 1 << 1; + } else { + handled = false; + } + } else if (item->application == HID_APPLICATION_KEYBOARD) { + handled = true; + + if (item->usage > HID_USAGE_KEYCODES && item->usage < HID_USAGE_KEYCODES + 0x200) { + handled = true; + keyboardEvent = true; + + if (buffer->ReadUnsigned(item->bits) && keysDownCount != 32) { + keysDown[keysDownCount++] = item->usage - HID_USAGE_KEYCODES; + } + } + } else if (item->application == HID_APPLICATION_JOYSTICK) { + handled = true; + + if (item->usage >= HID_USAGE_BUTTON_1 && item->usage <= HID_USAGE_BUTTON_16) { + gameControllerEvent = true; + + if (buffer->ReadUnsigned(item->bits)) { + controllerState.buttons |= 1 << controllerState.buttonCount; + } + + controllerState.buttonCount++; + } else if (item->usage == HID_USAGE_X_AXIS) { + gameControllerEvent = true; + controllerState.analog[0].x = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum; + } else if (item->usage == HID_USAGE_Y_AXIS) { + gameControllerEvent = true; + controllerState.analog[0].y = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum; + } else if (item->usage == HID_USAGE_Z_AXIS) { + gameControllerEvent = true; + controllerState.analog[0].z = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum; + } else if (item->usage == HID_USAGE_X_ROTATION) { + gameControllerEvent = true; + controllerState.analog[1].x = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum; + } else if (item->usage == HID_USAGE_Y_ROTATION) { + gameControllerEvent = true; + controllerState.analog[1].y = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum; + } else if (item->usage == HID_USAGE_Z_ROTATION) { + gameControllerEvent = true; + controllerState.analog[1].z = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum; + } else if (item->usage == HID_USAGE_HAT_SWITCH) { + gameControllerEvent = true; + controllerState.directionalPad = buffer->ReadUnsigned(item->bits); + } else { + handled = false; + } + } + + if (!handled) { + buffer->Discard(item->bits); + } + } + } + + if (mouseEvent) KCursorUpdate(mouseXMovement, mouseYMovement, mouseButtons); + if (keyboardEvent) KKeyboardUpdate(keysDown, keysDownCount); + if (gameControllerEvent) KGameControllerUpdateState(&controllerState); + + if (device->flags & K_DEVICE_REMOVED) { + KDeviceCloseHandle(this); + } else { + if (!device->queueTransfer(device, reportEndpoint, ReportReceivedCallback, + lastReport, reportEndpoint->GetMaximumPacketSize(), this)) { + KernelLog(LOG_ERROR, "USBHID", "setup transfer failure", "Could not setup the interrupt input transfer to receive the next report packet.\n"); + KDeviceCloseHandle(this); + } + } +} + +void HIDDevice::Initialise() { + // Find the HID descriptor. + + HIDDescriptor *hidDescriptor = (HIDDescriptor *) device->GetCommonDescriptor(0x21, 0); + + if (!hidDescriptor) { + KernelLog(LOG_ERROR, "USBHID", "missing descriptor", "Could not find the HID descriptor.\n"); + return; + } else if (hidDescriptor->length < sizeof(HIDDescriptor) || hidDescriptor->linkCount == 0 + || (hidDescriptor->linkCount - 1) * sizeof(HIDDescriptorLink) + sizeof(HIDDescriptor) > hidDescriptor->length) { + KernelLog(LOG_ERROR, "USBHID", "bad descriptor length", "HID descriptor too short (%D) for %d links.\n", + hidDescriptor->length, hidDescriptor->linkCount); + return; + } + + // Get the size of the report descriptor. + + size_t reportBytes = 0; + + for (uintptr_t i = 0; i < hidDescriptor->linkCount; i++) { + if (hidDescriptor->links[i].type == 0x22) { + reportBytes = (size_t) hidDescriptor->links[i].length[0] | ((size_t) hidDescriptor->links[i].length[1] << 8); + } + } + + if (!reportBytes) { + KernelLog(LOG_ERROR, "USBHID", "no report descriptor", "Could not find report descriptor link in HID descriptor.\n"); + return; + } + + // Switch to the report protocol. + + if (!device->controlTransfer(device, 0x21, 0x0B /* set protocol */, 1 /* report protocol */, + device->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, nullptr)) { + KernelLog(LOG_ERROR, "USBHID", "set protocol failure", "Could not switch to the report protocol.\n"); + return; + } + + // Get the report descriptor and parse it. + + uint8_t *report = (uint8_t *) EsHeapAllocate(reportBytes, false, K_FIXED); + + if (!report) { + KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate buffer to store the report descriptor.\n"); + return; + } + + EsDefer(EsHeapFree(report, reportBytes, K_FIXED)); + + uint16_t transferred = 0; + + if (!device->controlTransfer(device, 0x81, 0x06, 0x22 << 8, 0, report, reportBytes, K_ACCESS_READ, &transferred) + || transferred != reportBytes) { + KernelLog(LOG_ERROR, "USBHID", "no report descriptor", "Could not read the report descriptor from the device.\n"); + return; + } + + if (!ParseReportDescriptor(report, reportBytes)) { + KernelLog(LOG_ERROR, "USBHID", "invalid report descriptor", "Could not parse the report descriptor.\n"); + return; + } + + // Set idle. + + if (!device->controlTransfer(device, 0x21, 0x0A /* set idle */, 0 /* infinite duration, apply to all report IDs */, + device->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, nullptr)) { + KernelLog(LOG_ERROR, "USBHID", "enable idle failure", "Could not enable idle mode on the device.\n"); + return; + } + + // Get the interrupt-in endpoint descriptor. + + KUSBEndpointDescriptor *endpoint = nullptr; + + { + uintptr_t index = 0; + + while (true) { + KUSBEndpointDescriptor *e = (KUSBEndpointDescriptor *) device->GetCommonDescriptor(0x05 /* endpoint */, index++); + + if (!e) { + break; + } else if (e->IsInterrupt() && e->IsInput()) { + endpoint = e; + break; + } + } + } + + lastReport = (uint8_t *) EsHeapAllocate(endpoint->GetMaximumPacketSize(), true, K_FIXED); + + if (!lastReport) { + KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate buffer to store received reports.\n"); + return; + } + + // Start receiving interrupt packets. + + reportEndpoint = endpoint; + KDeviceOpenHandle(this); + + if (!device->queueTransfer(device, endpoint, ReportReceivedCallback, lastReport, endpoint->GetMaximumPacketSize(), this)) { + KernelLog(LOG_ERROR, "USBHID", "setup transfer failure", "Could not setup the interrupt input transfer to receive report packets.\n"); + KDeviceCloseHandle(this); + return; + } + + // If this is a game controller, tell the window manager it's been connected. + + { + uint64_t seen = 0; + + for (uintptr_t i = 0; i < reportItems.Length(); i++) { + ReportItem *item = &reportItems[i]; + + if (item->application == HID_APPLICATION_JOYSTICK + && item->reportPrefix < 64 + && (~seen & (1 << item->reportPrefix))) { + seen |= (1 << item->reportPrefix); + + GameController controller = {}; + controller.id = KGameControllerConnect(); + + if (controller.id) { + controller.reportPrefix = item->reportPrefix; + gameControllers.Add(controller); + } + } + } + } +} + +static void DeviceDestroy(KDevice *_device) { + HIDDevice *device = (HIDDevice *) _device; + + for (uintptr_t i = 0; i < device->gameControllers.Length(); i++) { + KGameControllerDisconnect(device->gameControllers[i].id); + } + + device->reportItems.Free(); + device->gameControllers.Free(); + EsHeapFree(device->lastReport, 0, K_FIXED); +} + +static void DeviceAttach(KDevice *parent) { + HIDDevice *device = (HIDDevice *) KDeviceCreate("USB HID", parent, sizeof(HIDDevice)); + + if (!device) { + KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate HIDDevice structure.\n"); + return; + } + + device->destroy = DeviceDestroy; + device->device = (KUSBDevice *) parent; + device->Initialise(); + KDeviceCloseHandle(device); +} + +KDriver driverUSBHID = { + .attach = DeviceAttach, +}; diff --git a/drivers/xhci.cpp b/drivers/xhci.cpp new file mode 100644 index 0000000..5f3e881 --- /dev/null +++ b/drivers/xhci.cpp @@ -0,0 +1,1282 @@ +#include + +// TODO Device attach and detach. +// TODO Babble error recovery. + +#define SPEED_LOW (1) +#define SPEED_FULL (2) +#define SPEED_HIGH (3) +#define SPEED_SUPER (4) + +#define COMMAND_RING_ENTRIES (255) +#define EVENT_RING_ENTRIES (252) +#define TRANSFER_RING_ENTRIES (255) +#define INPUT_CONTEXT_BYTES ((contextSize64 ? 64 : 32) * 33) +#define OUTPUT_CONTEXT_BYTES ((contextSize64 ? 64 : 32) * 32) +#define CONTEXT_INDEX(i) ((i) * (contextSize64 ? 16 : 8)) + +// Capability registers: + +#define RD_REGISTER_CAPLENGTH() pci-> ReadBAR32(0, 0x00) // Capability register length and interface version number. +#define WR_REGISTER_CAPLENGTH(x) pci->WriteBAR32(0, 0x00, x) +#define RD_REGISTER_HCSPARAMS1() pci-> ReadBAR32(0, 0x04) // Structural parameters 1. +#define WR_REGISTER_HCSPARAMS1(x) pci->WriteBAR32(0, 0x04, x) +#define RD_REGISTER_HCSPARAMS2() pci-> ReadBAR32(0, 0x08) // Structural parameters 2. +#define WR_REGISTER_HCSPARAMS2(x) pci->WriteBAR32(0, 0x08, x) +#define RD_REGISTER_HCSPARAMS3() pci-> ReadBAR32(0, 0x0C) // Structural parameters 3. +#define WR_REGISTER_HCSPARAMS3(x) pci->WriteBAR32(0, 0x0C, x) +#define RD_REGISTER_HCCPARAMS1() pci-> ReadBAR32(0, 0x10) // Capability parameters 1. +#define WR_REGISTER_HCCPARAMS1(x) pci->WriteBAR32(0, 0x10, x) +#define RD_REGISTER_DBOFF() pci-> ReadBAR32(0, 0x14) // Doorbell offset. +#define WR_REGISTER_DBOFF(x) pci->WriteBAR32(0, 0x14, x) +#define RD_REGISTER_RTSOFF() pci-> ReadBAR32(0, 0x18) // Runtime register space offset. +#define WR_REGISTER_RTSOFF(x) pci->WriteBAR32(0, 0x18, x) +#define RD_REGISTER_HCCPARAMS2() pci-> ReadBAR32(0, 0x1C) // Capability parameters 2. +#define WR_REGISTER_HCCPARAMS2(x) pci->WriteBAR32(0, 0x1C, x) + +// Host controller operational registers: + +#define RD_REGISTER_USBCMD() pci-> ReadBAR32(0, operationalRegistersOffset + 0x00) // USB command. +#define WR_REGISTER_USBCMD(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x00, x) +#define RD_REGISTER_USBSTS() pci-> ReadBAR32(0, operationalRegistersOffset + 0x04) // USB status. +#define WR_REGISTER_USBSTS(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x04, x) +#define RD_REGISTER_PAGESIZE() pci-> ReadBAR32(0, operationalRegistersOffset + 0x08) // Page size. +#define WR_REGISTER_PAGESIZE(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x08, x) +#define RD_REGISTER_DNCTRL() pci-> ReadBAR32(0, operationalRegistersOffset + 0x14) // Device notification control. +#define WR_REGISTER_DNCTRL(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x14, x) +#define RD_REGISTER_CRCR() pci-> ReadBAR64(0, operationalRegistersOffset + 0x18) // Command ring control. +#define WR_REGISTER_CRCR(x) pci->WriteBAR64(0, operationalRegistersOffset + 0x18, x) +#define RD_REGISTER_DCBAAP() pci-> ReadBAR64(0, operationalRegistersOffset + 0x30) // Device context base address array pointer. +#define WR_REGISTER_DCBAAP(x) pci->WriteBAR64(0, operationalRegistersOffset + 0x30, x) +#define RD_REGISTER_CONFIG() pci-> ReadBAR32(0, operationalRegistersOffset + 0x38) // Configure. +#define WR_REGISTER_CONFIG(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x38, x) + +// Port register sets: + +#define RD_REGISTER_PORTSC(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x400 + (n) * 0x10) // Port status and control. +#define WR_REGISTER_PORTSC(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x400 + (n) * 0x10, x) +#define RD_REGISTER_PORTPMSC(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x404 + (n) * 0x10) // Port port management status and control. +#define WR_REGISTER_PORTPMSC(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x404 + (n) * 0x10, x) +#define RD_REGISTER_PORTLI(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x408 + (n) * 0x10) // Port link info. +#define WR_REGISTER_PORTLI(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x408 + (n) * 0x10, x) +#define RD_REGISTER_PORTHLPMC(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x40C + (n) * 0x10) // Port hardware LPM control. +#define WR_REGISTER_PORTHLPMC(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x40C + (n) * 0x10, x) + +// Host controller runtime registers: + +#define RD_REGISTER_MFINDEX() pci-> ReadBAR32(0, runtimeRegistersOffset + 0x00) // Microframe index. +#define WR_REGISTER_MFINDEX(x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x00, x) + +// Interrupter register sets: + +#define RD_REGISTER_IMAN(n) pci-> ReadBAR32(0, runtimeRegistersOffset + 0x20 + (n) * 0x20) // Interrupter management. +#define WR_REGISTER_IMAN(n, x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x20 + (n) * 0x20, x) +#define RD_REGISTER_IMOD(n) pci-> ReadBAR32(0, runtimeRegistersOffset + 0x24 + (n) * 0x20) // Interrupter moderation. +#define WR_REGISTER_IMOD(n, x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x24 + (n) * 0x20, x) +#define RD_REGISTER_ERSTSZ(n) pci-> ReadBAR32(0, runtimeRegistersOffset + 0x28 + (n) * 0x20) // Event ring segment table size. +#define WR_REGISTER_ERSTSZ(n, x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x28 + (n) * 0x20, x) +#define RD_REGISTER_ERSTBA(n) pci-> ReadBAR64(0, runtimeRegistersOffset + 0x30 + (n) * 0x20) // Event ring segment table base address. +#define WR_REGISTER_ERSTBA(n, x) pci->WriteBAR64(0, runtimeRegistersOffset + 0x30 + (n) * 0x20, x) +#define RD_REGISTER_ERDP(n) pci-> ReadBAR64(0, runtimeRegistersOffset + 0x38 + (n) * 0x20) // Event ring dequeue pointer. +#define WR_REGISTER_ERDP(n, x) pci->WriteBAR64(0, runtimeRegistersOffset + 0x38 + (n) * 0x20, x) + +// Doorbell registers: + +#define RD_REGISTER_DOORBELL(n) pci-> ReadBAR32(0, doorbellsOffset + (n) * 0x04) // Doorbell. +#define WR_REGISTER_DOORBELL(n, x) pci->WriteBAR32(0, doorbellsOffset + (n) * 0x04, x) + +struct XHCIDevice : KUSBDevice { + uintptr_t port; +}; + +struct XHCIEndpoint { + uint32_t *data; + uintptr_t physical; + + uint32_t lastStatus; + uint16_t maximumPacketSize; + uint16_t index : 15, phase : 1; + + KUSBTransferCallback callback; + EsGeneric context; + + bool CreateTransferRing(); + + // TODO Detecting when the transfer ring is full. + void AdvanceTransferRingIndex(); +}; + +struct XHCIPort { + bool usb2, statusChangeEvent, enabled; + uint8_t slotType, slotID, speed; + // uintptr_t maximumPacketSize; + + XHCIEndpoint controlEndpoint; + volatile uint32_t *controlTransferResult; + volatile uintptr_t controlTransferLastTRBAddress; + KEvent controlTransferCompleteEvent; + + XHCIEndpoint ioEndpoints[30]; + + uint32_t *outputContext; + uintptr_t outputContextPhysical; + + XHCIDevice *device; + + KMutex mutex; +}; + +struct XHCIController : KDevice { + KPCIDevice *pci; + + uintptr_t operationalRegistersOffset, + extendedCapabilitiesOffset, + doorbellsOffset, + runtimeRegistersOffset; + size_t maximumDeviceSlots, + maximumInterrupters, + maximumPorts, + maximumEventRingSegments; + bool contextSize64; + + uint64_t *deviceContextBaseAddressArray; + + uint32_t *commandRing; + uintptr_t commandRingIndex; + bool commandRingPhase; + volatile uint32_t *commandResult; + KEvent commandCompleteEvent; + + uint32_t *eventRing; + uintptr_t eventRingPhysical; + uintptr_t eventRingIndex; + bool eventRingPhase; + + uint32_t *inputContext; + uintptr_t inputContextPhysical; + + KEvent portStatusChangeEvent; + KSpinlock portResetSpinlock; + + XHCIPort *ports; + + void Initialise(); + void DumpState(); + bool HandleIRQ(); + + void OnPortEnable(uintptr_t port); + void OnPortDisable(uintptr_t port); + + bool RunCommand(uint32_t *dw); + bool AddTransferDescriptors(uintptr_t buffer, size_t length, int operation, XHCIEndpoint *endpoint, + uint32_t trbType, bool interruptOnLast); + + bool ControlTransfer(uintptr_t port, uint8_t flags, uint8_t request, uint16_t value, uint16_t index, + void *buffer, uint16_t length, int operation, uint16_t *transferred, bool alreadyLocked); + bool SelectConfigurationAndInterface(uintptr_t port, KUSBDevice *device); + bool QueueInterruptTransfer(uintptr_t portIndex, uint8_t endpointAddress, KUSBTransferCallback callback, + void *buffer, size_t bufferBytes, EsGeneric context); +}; + +const char *commandCompletionCodes[] = { + "Invalid", + "Success", + "Data buffer error", + "Babble detected error", + "USB transaction error", + "TRB error", + "Stall error", + "Resource error", + "Bandwidth error", + "No slot available error", + "Invalid stream type error", + "Slot not enabled error", + "Endpoint not enabled error", + "Short packet", + "Ring underrun", + "Ring overrun", + "VF event ring full error", + "Parameter error", + "Bandwidth overrun error", + "Context state error", + "No ping response error", + "Event ring full error", + "Incompatible device error", + "Missed service error", + "Command ring stopped", + "Command aborted", + "Stopped", + "Stopped - length invalid", + "Stopped - short packet", + "Max exit latency too large error", + "Reserved", + "Isochronous buffer overrun", + "Event lost error", + "Undefined error", + "Invalid stream ID error", + "Secondary bandwidth error", + "Split transcation error", +}; + +void XHCIController::DumpState() { + EsPrint("xHCI controller state:\n"); + + EsPrint("\t--- Registers ---\n"); + + EsPrint("\t\tCapability register length: %x.\n", RD_REGISTER_CAPLENGTH()); + EsPrint("\t\tStructural parameters 1: %x.\n", RD_REGISTER_HCSPARAMS1()); + EsPrint("\t\tStructural parameters 2: %x.\n", RD_REGISTER_HCSPARAMS2()); + EsPrint("\t\tStructural parameters 3: %x.\n", RD_REGISTER_HCSPARAMS3()); + EsPrint("\t\tCapability parameters 1: %x.\n", RD_REGISTER_HCCPARAMS1()); + EsPrint("\t\tDoorbell offset: %x.\n", RD_REGISTER_DBOFF()); + EsPrint("\t\tRuntime register space offset: %x.\n", RD_REGISTER_RTSOFF()); + EsPrint("\t\tCapability parameters 2: %x.\n", RD_REGISTER_HCCPARAMS2()); + + EsPrint("\t\tUSB command: %x.\n", RD_REGISTER_USBCMD()); + EsPrint("\t\tUSB status: %x.\n", RD_REGISTER_USBSTS()); + EsPrint("\t\tPage size: %x.\n", RD_REGISTER_PAGESIZE()); + EsPrint("\t\tDevice notification control: %x.\n", RD_REGISTER_DNCTRL()); + EsPrint("\t\tCommand ring control: %x.\n", RD_REGISTER_CRCR()); + EsPrint("\t\tDevice context base address array pointer (64-bit): %x.\n", RD_REGISTER_DCBAAP()); + EsPrint("\t\tConfigure: %x.\n", RD_REGISTER_CONFIG()); + + for (uintptr_t i = 0; i < maximumPorts; i++) { + EsPrint("\t\tPort %d:\n", i); + EsPrint("\t\t\tPort status and control: %x.\n", RD_REGISTER_PORTSC(i)); + EsPrint("\t\t\tPort port management status and control: %x.\n", RD_REGISTER_PORTPMSC(i)); + EsPrint("\t\t\tPort link info: %x.\n", RD_REGISTER_PORTLI(i)); + EsPrint("\t\t\tPort hardware LPM control: %x.\n", RD_REGISTER_PORTHLPMC(i)); + } + + for (uintptr_t i = 0; i < maximumInterrupters; i++) { + EsPrint("\t\tInterrupter %d:\n", i); + EsPrint("\t\t\tInterrupter management: %x.\n", RD_REGISTER_IMAN(i)); + EsPrint("\t\t\tInterrupter moderation: %x.\n", RD_REGISTER_IMOD(i)); + EsPrint("\t\t\tEvent ring segment table size: %x.\n", RD_REGISTER_ERSTSZ(i)); + EsPrint("\t\t\tEvent ring segment table base address: %x.\n", RD_REGISTER_ERSTBA(i)); + EsPrint("\t\t\tEvent ring dequeue pointer: %x.\n", RD_REGISTER_ERDP(i)); + } +} + +bool XHCIEndpoint::CreateTransferRing() { + if (!MMPhysicalAllocateAndMap(16 * (TRANSFER_RING_ENTRIES + 1), 16, 0, true, + 0, (uint8_t **) &data, &physical)) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the transfer ring.\n"); + return false; + } + + index = 0; + phase = 1; + + data[TRANSFER_RING_ENTRIES * 4 + 0] = physical & 0xFFFFFFFF; + data[TRANSFER_RING_ENTRIES * 4 + 1] = (physical >> 32) & 0xFFFFFFFF; + data[TRANSFER_RING_ENTRIES * 4 + 3] = (1 << 1 /* toggle cycle */) | (6 /* link TRB */ << 10); + + return true; +} + +void XHCIEndpoint::AdvanceTransferRingIndex() { + if (phase) { + data[index * 4 + 3] |= 1 << 0; + } + + index++; + + if (index == TRANSFER_RING_ENTRIES) { + // Toggle cycle bit on link TRB. + data[TRANSFER_RING_ENTRIES * 4 + 3] ^= 1 << 0; + index = 0; + phase = phase ? 0 : 1; + } +} + +bool XHCIController::AddTransferDescriptors(uintptr_t buffer, size_t length, int operation, XHCIEndpoint *endpoint, uint32_t trbType, bool interruptOnLast) { + if (this->flags & K_DEVICE_REMOVED) { + return false; + } + + uintptr_t position = 0; + + while (position != length) { + uintptr_t count = K_PAGE_SIZE - ((buffer + position) & (K_PAGE_SIZE - 1)); + bool last = count >= length - position; + if (last) count = length - position; + uintptr_t physical = MMArchTranslateAddress(MMGetKernelSpace(), buffer + position); + physical += (buffer + position) & (K_PAGE_SIZE - 1); + size_t remainingPackets = (length - position + endpoint->maximumPacketSize - 1) / endpoint->maximumPacketSize; + + uint32_t *dw = endpoint->data + endpoint->index * 4; + + dw[0] = (physical >> 0) & 0xFFFFFFFF; + dw[1] = (physical >> 32) & 0xFFFFFFFF; + dw[2] = count | ((last ? 0 : remainingPackets < 31 ? remainingPackets : 31) << 17); + + dw[3] = trbType << 10; + if (operation == K_ACCESS_READ) dw[3] |= 1 << 16 /* input */; + if (last && interruptOnLast) dw[3] |= 1 << 5 /* interrupt on completion */; + if (!last) dw[3] |= 1 << 4 /* chain */; + + endpoint->AdvanceTransferRingIndex(); + position += count; + } + + return true; +} + +bool XHCIController::ControlTransfer(uintptr_t portIndex, uint8_t flags, uint8_t request, uint16_t value, uint16_t index, + void *buffer, uint16_t length, int operation, uint16_t *transferred, bool alreadyLocked) { + XHCIPort *port = ports + portIndex; + + if (!alreadyLocked) KMutexAcquire(&port->mutex); + EsDefer(if (!alreadyLocked) KMutexRelease(&port->mutex)); + if (alreadyLocked) KMutexAssertLocked(&port->mutex); + + if (this->flags & K_DEVICE_REMOVED) { + return false; + } + + KEventReset(&port->controlTransferCompleteEvent); + + uint32_t transferResult[4]; + port->controlTransferResult = transferResult; + + { + uint32_t *dw = port->controlEndpoint.data + port->controlEndpoint.index * 4; + dw[0] = (uint32_t) flags | ((uint32_t) request << 8) | ((uint32_t) value << 16); + dw[1] = (uint32_t) index | ((uint32_t) length << 16); + dw[2] = 8 /* transfer length */; + dw[3] = (1 << 6 /* immediate data */) | (2 /* setup TRB */ << 10); + + if (length) { + dw[3] |= (operation == K_ACCESS_READ ? 3 : 2) << 16; + } + + port->controlEndpoint.AdvanceTransferRingIndex(); + } + + if (!AddTransferDescriptors((uintptr_t) buffer, length, operation, &port->controlEndpoint, 3 /* data TRB */, false)) { + return false; + } + + { + uint32_t *dw = port->controlEndpoint.data + port->controlEndpoint.index * 4; + port->controlTransferLastTRBAddress = port->controlEndpoint.physical + port->controlEndpoint.index * 16; + dw[0] = dw[1] = dw[2] = 0; + dw[3] = (1 << 5 /* interrupt on completion */) | (operation == K_ACCESS_WRITE ? (1 << 16) : 0) | (4 /* status TRB */ << 10); + port->controlEndpoint.AdvanceTransferRingIndex(); + } + + WR_REGISTER_DOORBELL(port->slotID, 1 /* control endpoint */); + + if (!KEventWait(&port->controlTransferCompleteEvent, 1000)) { + // Timeout. + return false; + } + + uint32_t status = transferResult[2]; + uint8_t completionCode = (status >> 24) & 0xFF; + + if (completionCode != 1) { + if (completionCode < sizeof(commandCompletionCodes) / sizeof(commandCompletionCodes[0])) { + KernelLog(LOG_ERROR, "xHCI", "failed control transfer", "Control transfer failed with completion code '%z'.\n", commandCompletionCodes[completionCode]); + } else { + KernelLog(LOG_ERROR, "xHCI", "failed control transfer", "Control transfer failed with unrecognised completion code %d.\n", completionCode); + } + + if ((port->outputContext[CONTEXT_INDEX(1) + 0] & 7) == 2 /* halted */) { + // Reset the endpoint. + + uint32_t dw[4] = {}; + dw[3] = (port->slotID << 24) | (1 /* control endpoint */ << 16) + | (14 /* reset endpoint command */ << 10) | (0 /* reset transfer state */ << 9); + + if (!RunCommand(dw)) { + KernelLog(LOG_ERROR, "xHCI", "reset control endpoint failure", "Could not reset the control endpoint (1).\n"); + // TODO Force detach the device. + return false; + } + + // Reset the dequeue pointer. + + port->controlEndpoint.index = 0; + port->controlEndpoint.phase = 1; + + EsMemoryZero(port->controlEndpoint.data, 16 * (TRANSFER_RING_ENTRIES + 1)); + + dw[0] = (port->controlEndpoint.physical & 0xFFFFFFFF) | (1 << 0 /* phase bit */); + dw[1] = (port->controlEndpoint.physical >> 32) & 0xFFFFFFFF; + dw[2] = 0; + dw[3] = (port->slotID << 24) | (1 /* control endpoint */ << 16) + | (16 /* set TR dequeue pointer command */ << 10); + + if (!RunCommand(dw)) { + KernelLog(LOG_ERROR, "xHCI", "reset control endpoint failure", "Could not reset the control endpoint (2).\n"); + // TODO Force detach the device. + return false; + } + } + + return false; + } + + if (transferred) *transferred = length - (status & 0xFFFFFF); + return true; +} + +bool XHCIController::SelectConfigurationAndInterface(uintptr_t portIndex, KUSBDevice *device) { + XHCIPort *port = ports + portIndex; + + KMutexAcquire(&port->mutex); + EsDefer(KMutexRelease(&port->mutex)); + + if (this->flags & K_DEVICE_REMOVED) { + return false; + } + + // Setup the input context. + + EsMemoryZero(inputContext, INPUT_CONTEXT_BYTES); + + uintptr_t lastIndex = 0; + + for (uintptr_t i = 0; i < device->interfaceDescriptor.endpointCount; i++) { + KUSBEndpointDescriptor *descriptor = (KUSBEndpointDescriptor *) device->GetCommonDescriptor(5 /* endpoint */, i); + + if (!descriptor) { + KernelLog(LOG_ERROR, "xHCI", "endpoint descriptor missing", "Could not find endpoint descriptor %d (%d total).\n", + i, device->interfaceDescriptor.endpointCount); + return false; + } + + KUSBEndpointCompanionDescriptor *companion = nullptr; + KUSBEndpointIsochronousCompanionDescriptor *isochronousCompanion = nullptr; + + if (port->speed == SPEED_SUPER) { + companion = (KUSBEndpointCompanionDescriptor *) ((uint8_t *) descriptor + descriptor->length); + + if (companion->length < sizeof(KUSBEndpointCompanionDescriptor) + || (size_t) ((uint8_t *) companion + companion->length - device->configurationDescriptors) > device->configurationDescriptorsBytes + || companion->descriptorType != 48 /* superspeed endpoint companion */) { + companion = nullptr; + } else if (companion->HasISOCompanion()) { + isochronousCompanion = (KUSBEndpointIsochronousCompanionDescriptor *) ((uint8_t *) companion + companion->length); + + if (isochronousCompanion->length < sizeof(KUSBEndpointCompanionDescriptor) + || (size_t) ((uint8_t *) isochronousCompanion + isochronousCompanion->length - device->configurationDescriptors) + > device->configurationDescriptorsBytes + || isochronousCompanion->descriptorType != 49 /* superspeed isochronous endpoint companion */) { + isochronousCompanion = nullptr; + } + } + } + + if (descriptor->IsControl()) { + // Already enabled. + continue; + } + + uint32_t maximumBurst = companion ? companion->maxBurst : descriptor->IsBulk() ? 0 : ((descriptor->maximumPacketSize & 0x1800) >> 11); + uint32_t maximumPacketSize = descriptor->GetMaximumPacketSize(); + uint32_t maximumESITPayload = descriptor->IsBulk() ? 0 : (companion + ? (isochronousCompanion ? isochronousCompanion->bytesPerInterval : companion->bytesPerInterval) + : (maximumPacketSize * (maximumBurst + 1))); + + uintptr_t index = (descriptor->IsInput() ? 2 : 1) + descriptor->GetAddress() * 2; + inputContext[CONTEXT_INDEX(0) + 1] |= 1 << (index - 1); + if (lastIndex < index - 1) lastIndex = index - 1; + + XHCIEndpoint *endpoint = port->ioEndpoints + index - 3; + + endpoint->maximumPacketSize = maximumPacketSize; + + if (!endpoint->CreateTransferRing()) { + return false; + } + + // See "4.8.2 Endpoint Context Initialization". + inputContext[CONTEXT_INDEX(index) + 0] = 0; + inputContext[CONTEXT_INDEX(index) + 1] = (maximumBurst << 8) | (maximumPacketSize << 16) | (((maximumESITPayload >> 16) & 0xFF) << 24); + inputContext[CONTEXT_INDEX(index) + 2] = (1 << 0 /* phase */) | (endpoint->physical & 0xFFFFFFFF); + inputContext[CONTEXT_INDEX(index) + 3] = ((endpoint->physical >> 32) & 0xFFFFFFFF); + inputContext[CONTEXT_INDEX(index) + 4] = ((maximumESITPayload & 0xFFFF)) << 16; + + if (descriptor->IsInterrupt()) { + inputContext[CONTEXT_INDEX(index) + 1] |= (3 /* error count */ << 1) | ((descriptor->IsInput() ? 7 : 3) << 3 /* endpoint type */); + } else if (descriptor->IsBulk() && (!companion || !companion->GetMaximumStreams())) { + inputContext[CONTEXT_INDEX(index) + 1] |= (3 /* error count */ << 1) | ((descriptor->IsInput() ? 6 : 2) << 3 /* endpoint type */); + } else { + // TODO Isochronous and stream bulk endpoints. + KernelLog(LOG_ERROR, "xHCI", "unsupported endpoint type", "Isochronous and bulk endpoints are currently unsupported.\n"); + return false; + } + } + + inputContext[CONTEXT_INDEX(0) + 1] |= 1 << 0 /* slot context */; + + // See "4.5.2 Slot Context Initialization". + inputContext[CONTEXT_INDEX(1) + 0] = lastIndex << 27; + + // Send the configure endpoint command. + + { + uint32_t dw[4]; + + dw[0] = (inputContextPhysical >> 0) & 0xFFFFFFFF; + dw[1] = (inputContextPhysical >> 32) & 0xFFFFFFFF; + dw[2] = 0; + dw[3] = (port->slotID << 24) | (12 /* configure endpoint command */ << 10); + + if (!RunCommand(dw)) { + KernelLog(LOG_ERROR, "xHCI", "configure endpoint failure", "The configure endpoint command failed.\n"); + return false; + } + } + + // Set the configuration. + + return ControlTransfer(portIndex, 0, 0x09 /* set configuration */, + device->configurationDescriptor.configurationIndex, 0, + nullptr, 0, K_ACCESS_WRITE, nullptr, true /* already locked */); +} + +bool XHCIController::QueueInterruptTransfer(uintptr_t portIndex, uint8_t endpointAddress, KUSBTransferCallback callback, + void *buffer, size_t bufferBytes, EsGeneric context) { + XHCIPort *port = ports + portIndex; + + KMutexAcquire(&port->mutex); + EsDefer(KMutexRelease(&port->mutex)); + + if (this->flags & K_DEVICE_REMOVED) { + return false; + } + + XHCIEndpoint *endpoint = port->ioEndpoints + endpointAddress - 2; + endpoint->callback = callback; + endpoint->context = context; + + if (!AddTransferDescriptors((uintptr_t) buffer, bufferBytes, (endpointAddress & 1) ? K_ACCESS_READ : K_ACCESS_WRITE, + endpoint, 1 /* normal TRB */, true)) { + return false; + } + + WR_REGISTER_DOORBELL(port->slotID, endpointAddress); + return true; +} + +bool XHCIController::HandleIRQ() { + // Clear interrupt status. + + uint32_t usbStatus = RD_REGISTER_USBSTS(); + WR_REGISTER_USBSTS((1 << 3) | (1 << 4)); + + // Check for an interrupt. + + uint32_t status = RD_REGISTER_IMAN(0); + + KernelLog(LOG_VERBOSE, "xHCI", "IRQ", "Received IRQ. USB status: %x. Interrupter status: %x.\n", usbStatus, status); + + // Acknowledge the interrupt. + + WR_REGISTER_IMAN(0, status); + RD_REGISTER_IMAN(0); // Read the value back. + + // Consume events. + + while (true) { + uint32_t dw0 = eventRing[0x10 + 0x04 * eventRingIndex + 0x00]; + uint32_t dw1 = eventRing[0x10 + 0x04 * eventRingIndex + 0x01]; + uint32_t dw2 = eventRing[0x10 + 0x04 * eventRingIndex + 0x02]; + uint32_t dw3 = eventRing[0x10 + 0x04 * eventRingIndex + 0x03]; + + if ((dw3 & (1 << 0)) != eventRingPhase) { + break; + } + + uint32_t type = (dw3 >> 10) & 0x3F; + uint8_t completionCode = (dw2 >> 24) & 0xFF; + + KernelLog(LOG_VERBOSE, "xHCI", "got event", "Received event of type %d with code %d from %x.\n", + type, completionCode, (uintptr_t) dw0 | ((uintptr_t) dw1 << 32)); + + if (type == 32 /* transfer completion event */) { + uint8_t slotID = (dw3 >> 24) & 0xFF; + uint8_t endpointID = (dw3 >> 16) & 0x1F; + + if (endpointID == 1) { + for (uintptr_t i = 0; i < maximumPorts; i++) { + XHCIPort *port = ports + i; + if (port->slotID != slotID) continue; + if (!port->enabled) break; + + if (!port->controlTransferResult) { + KernelLog(LOG_ERROR, "xHCI", "spurious transfer completion event", + "Received an unexpected transfer command completion event on port %d. " + "Contents: %x, %x, %x, %x.\n", i, dw0, dw1, dw2, dw3); + } else { + bool lastTransfer = dw0 == (port->controlTransferLastTRBAddress & 0xFFFFFFFF) + && dw1 == ((port->controlTransferLastTRBAddress >> 32) & 0xFFFFFFFF); + + if (completionCode != 1 || lastTransfer) { + port->controlTransferResult[0] = dw0; + port->controlTransferResult[1] = dw1; + port->controlTransferResult[2] = dw2; + port->controlTransferResult[3] = dw3; + KEventSet(&port->controlTransferCompleteEvent); + } + } + + break; + } + } else if (endpointID >= 2) { + for (uintptr_t i = 0; i < maximumPorts; i++) { + XHCIPort *port = ports + i; + if (port->slotID != slotID) continue; + if (!port->enabled) break; + XHCIEndpoint *endpoint = port->ioEndpoints + endpointID - 2; + endpoint->lastStatus = dw2; + + if (completionCode != 1) { + if (completionCode < sizeof(commandCompletionCodes) / sizeof(commandCompletionCodes[0])) { + KernelLog(LOG_ERROR, "xHCI", "failed transfer", "Transfer failed with completion code '%z' (%x).\n", + commandCompletionCodes[completionCode], dw2); + } else { + KernelLog(LOG_ERROR, "xHCI", "failed transfer", "Transfer failed with unrecognised completion code %d.\n", completionCode); + } + } + + if (endpoint->callback) { + KRegisterAsyncTask([] (EsGeneric argument) { + XHCIEndpoint *endpoint = (XHCIEndpoint *) argument.p; + KUSBTransferCallback callback = endpoint->callback; + endpoint->callback = nullptr; + callback((endpoint->lastStatus >> 24) != 1 ? -1 : endpoint->lastStatus & 0xFFFFFF, endpoint->context); + }, endpoint, true); + } + + break; + } + } + } else if (type == 33 /* command completion event */) { + if (!commandResult) { + KernelLog(LOG_ERROR, "xHCI", "spurious command completion event", + "Received a spurious command completion event (no commands issued since last expected received completion event). " + "Contents: %x, %x, %x, %x.\n", dw0, dw1, dw2, dw3); + } else { + commandResult[0] = dw0; + commandResult[1] = dw1; + commandResult[2] = dw2; + commandResult[3] = dw3; + KEventSet(&commandCompleteEvent); + } + } else if (type == 34 /* port status change event */) { + uintptr_t port = (dw0 >> 24) - 1; + + KSpinlockAcquire(&portResetSpinlock); + + // Acknowledge the status change. + + uint32_t status = RD_REGISTER_PORTSC(port); + uint32_t linkState = (status >> 5) & 0x0F; + WR_REGISTER_PORTSC(port, ((1 << 9) | (1 << 17) | (1 << 18) | (1 << 19) | (1 << 20) | (1 << 21) | (1 << 22) | (1 << 23)) & status); + KernelLog(LOG_INFO, "xHCI", "port status change", "Port %d has new status: %x.\n", port, status); + + if (!ports[port].enabled && (status & (1 << 1 /* port enabled */))) { + KernelLog(LOG_INFO, "xHCI", "port enabled", "Port %d has been enabled.\n", port); + ports[port].statusChangeEvent = true; + ports[port].enabled = true; + KEventSet(&portStatusChangeEvent, false, true); + } else if (ports[port].usb2 && (linkState == 7 || linkState == 4) && (~status & (1 << 4))) { + KernelLog(LOG_INFO, "xHCI", "port reset", "Attempting to reset USB 2 port %d... (1)\n", port); + WR_REGISTER_PORTSC(port, (status & (1 << 9)) | (1 << 4)); + } else if (ports[port].enabled && linkState == 5 && (~status & (1 << 0)) && (status & (1 << 17))) { + KernelLog(LOG_INFO, "xHCI", "port detach", "Device detached from port %d.\n", port); + ports[port].statusChangeEvent = true; + ports[port].enabled = false; + KEventSet(&portStatusChangeEvent, false, true); + } + + KSpinlockRelease(&portResetSpinlock); + } + + eventRingIndex++; + + if (eventRingIndex == EVENT_RING_ENTRIES) { + eventRingIndex = 0; + eventRingPhase = !eventRingPhase; + } + } + + WR_REGISTER_ERDP(0, (eventRingPhysical + 0x40 + (eventRingIndex << 4)) | (1 << 3)); + + return true; +} + +bool XHCIController::RunCommand(uint32_t *dw) { + KEventReset(&commandCompleteEvent); + commandResult = dw; + + commandRing[commandRingIndex * 4 + 0] = dw[0]; + commandRing[commandRingIndex * 4 + 1] = dw[1]; + commandRing[commandRingIndex * 4 + 2] = dw[2]; + commandRing[commandRingIndex * 4 + 3] = dw[3] | (commandRingPhase ? (1 << 0) : 0); + + commandRingIndex++; + + if (commandRingIndex == COMMAND_RING_ENTRIES) { + commandRingIndex = 0; + commandRingPhase = !commandRingPhase; + commandRing[COMMAND_RING_ENTRIES * 4 + 3] ^= 1 << 0; // Toggle phase of link TRB. + } + + // TODO Timeout, and command abortion. + WR_REGISTER_DOORBELL(0, 0); + KEventWait(&commandCompleteEvent); + + commandResult = nullptr; + + uint8_t completionCode = dw[2] >> 24; + + if (completionCode != 1) { + if (completionCode < sizeof(commandCompletionCodes) / sizeof(commandCompletionCodes[0])) { + KernelLog(LOG_ERROR, "xHCI", "failed command", "Command failed with completion code '%z'.\n", commandCompletionCodes[completionCode]); + } else { + KernelLog(LOG_ERROR, "xHCI", "failed command", "Command failed with unrecognised completion code %d.\n", completionCode); + } + } + + return completionCode == 1; +} + +static bool SelectConfigurationAndInterfaceWrapper(KUSBDevice *_device) { + XHCIDevice *device = (XHCIDevice *) _device; + XHCIController *controller = (XHCIController *) device->parent; + return controller->SelectConfigurationAndInterface(device->port, device); +} + +static bool QueueTransferWrapper(KUSBDevice *_device, KUSBEndpointDescriptor *endpoint, KUSBTransferCallback callback, + void *buffer, size_t bufferBytes, EsGeneric context) { + XHCIDevice *device = (XHCIDevice *) _device; + XHCIController *controller = (XHCIController *) device->parent; + return controller->QueueInterruptTransfer(device->port, endpoint->GetAddress() * 2 + (endpoint->IsInput() ? 1 : 0), + callback, buffer, bufferBytes, context); +} + +static bool ControlTransferWrapper(KUSBDevice *_device, uint8_t flags, uint8_t request, uint16_t value, uint16_t index, + void *buffer, uint16_t length, int operation, uint16_t *transferred) { + XHCIDevice *device = (XHCIDevice *) _device; + XHCIController *controller = (XHCIController *) device->parent; + KernelLog(LOG_VERBOSE, "xHCI", "control transfer", "Control transfer: %X, %X, %d, %d, %D.\n", flags, request, value, index, length); + bool success = controller->ControlTransfer(device->port, flags, request, value, index, buffer, length, operation, transferred, false); + KernelLog(LOG_VERBOSE, "xHCI", "control transfer complete", "Control transfer complete. Success = %d.\n", success); + return success; +} + +void XHCIController::OnPortEnable(uintptr_t port) { + uint32_t speed = (RD_REGISTER_PORTSC(port) >> 10) & 0xF; + uint32_t maximumPacketSize = speed == SPEED_LOW ? 8 : speed == SPEED_FULL ? 8 : speed == SPEED_HIGH ? 64 : speed == 4 ? SPEED_SUPER : 0; + ports[port].speed = speed; + ports[port].controlEndpoint.maximumPacketSize = maximumPacketSize; + + KernelLog(LOG_INFO, "xHCI", "initialising port", "Port %d is enabled, initialising. Speed: %z.\n", + port, speed == SPEED_LOW ? "low" : speed == SPEED_FULL ? "full" : speed == SPEED_HIGH ? "high" : speed == SPEED_SUPER ? "super" : "unknown"); + + if (!maximumPacketSize) { + KernelLog(LOG_ERROR, "xHCI", "unrecognised device speed", "Unrecognised device speed %d on port %d.\n", speed, port); + return; + } + + // Assign a device slot. + + uint32_t dw[4] = {}; + dw[3] = (9 /* enable slot */ << 10) | (ports[port].slotType << 16); + + if (!RunCommand(dw) || !(dw[3] >> 24)) { + KernelLog(LOG_ERROR, "xHCI", "enable slot error", "Could not enable a slot for the device on port %d.\n", port); + return; + } + + ports[port].slotID = dw[3] >> 24; + + KernelLog(LOG_INFO, "xHCI", "assign device slot", "Port %d assigned device slot %d.\n", port, ports[port].slotID); + + // Allocate the transfer ring. + + if (!ports[port].controlEndpoint.CreateTransferRing()) { + return; + } + + // Setup the input context. + + EsMemoryZero(inputContext, INPUT_CONTEXT_BYTES); + + inputContext[CONTEXT_INDEX(0) + 1] = (1 << 0 /* slot context valid */) | (1 << 1 /* control endpoint context valid */); + + inputContext[CONTEXT_INDEX(1) + 0] = (1 /* 1 context entry */ << 27) | (speed << 20); + inputContext[CONTEXT_INDEX(1) + 1] = (port + 1) << 16; + + // TODO Update this with the maximum packet size from the device descriptor. + // This field is interpreted differently on USB 2/3. + + inputContext[CONTEXT_INDEX(2) + 1] = (3 /* error count */ << 1) | (4 /* control endpoint */ << 3) | (maximumPacketSize << 16); + inputContext[CONTEXT_INDEX(2) + 2] = (ports[port].controlEndpoint.physical & 0xFFFFFFFF) | (1 << 0 /* phase bit */); + inputContext[CONTEXT_INDEX(2) + 3] = (ports[port].controlEndpoint.physical >> 32) & 0xFFFFFFFF; + inputContext[CONTEXT_INDEX(2) + 4] = 8 /* average TRB length */; + + // Allocate the output context. + + if (!MMPhysicalAllocateAndMap(OUTPUT_CONTEXT_BYTES, K_PAGE_SIZE, 0, true, + 0, (uint8_t **) &ports[port].outputContext, &ports[port].outputContextPhysical)) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the output context for port %d.\n", port); + return; + } + + deviceContextBaseAddressArray[ports[port].slotID] = ports[port].outputContextPhysical; + + // Address the device. + // TODO Full-speed devices need their maximum packet size queried before addressing. + + dw[0] = (inputContextPhysical >> 0) & 0xFFFFFFFF; + dw[1] = (inputContextPhysical >> 32) & 0xFFFFFFFF; + dw[2] = 0; + dw[3] = (ports[port].slotID << 24) | (0 /* send SET_ADDRESS */ << 9) | (11 /* address device command */ << 10); + + if (!RunCommand(dw)) { + KernelLog(LOG_ERROR, "xHCI", "address device error", "Could not address the device on port %d.\n", port); + return; + } + + KernelLog(LOG_INFO, "xHCI", "address device", "Port %d successfully addressed.\n", port); + + // Register the device with USB subsystem. + + XHCIDevice *device = (XHCIDevice *) KDeviceCreate("XHCI device", this, sizeof(XHCIDevice)); + ports[port].device = device; + device->port = port; + device->controlTransfer = ControlTransferWrapper; + device->selectConfigurationAndInterface = SelectConfigurationAndInterfaceWrapper; + device->queueTransfer = QueueTransferWrapper; + KRegisterUSBDevice(device); + + KernelLog(LOG_INFO, "xHCI", "register device", "Port %d registered with USB subsystem.\n", port); +} + +void XHCIController::OnPortDisable(uintptr_t port) { + KMutexAcquire(&ports[port].mutex); + EsDefer(KMutexRelease(&ports[port].mutex)); + + if (ports[port].device) { + KDeviceRemoved(ports[port].device); + ports[port].device = nullptr; + } + + uint32_t dw[4]; + + // Stop endpoints. + + for (uintptr_t i = 0; i < sizeof(ports[port].ioEndpoints) / sizeof(ports[port].ioEndpoints[0]); i++) { + XHCIEndpoint *endpoint = ports[port].ioEndpoints + i; + + if (!endpoint->data) { + continue; + } + + dw[0] = dw[1] = dw[2] = 0; + dw[3] = (15 /* stop endpoint */ << 10) | ((i + 2) << 16) | (ports[port].slotID << 24); + RunCommand(dw); + + MMPhysicalFreeAndUnmap(endpoint->data, endpoint->physical); + endpoint->data = nullptr; + endpoint->physical = 0; + + KUSBTransferCallback callback = endpoint->callback; + endpoint->callback = nullptr; + + if (callback) { + callback(-1, endpoint->context); + } + } + + // Disable slot. + + dw[0] = dw[1] = dw[2] = 0; + dw[3] = (10 /* disable slot */ << 10) | (ports[port].slotID << 24); + RunCommand(dw); + + // Free the output context. + + MMPhysicalFreeAndUnmap(ports[port].outputContext, ports[port].outputContextPhysical); + deviceContextBaseAddressArray[ports[port].slotID] = ports[port].outputContextPhysical; + ports[port].outputContext = nullptr; + ports[port].outputContextPhysical = 0; + + // Free the control endpoint transfer ring. + + MMPhysicalFreeAndUnmap(ports[port].controlEndpoint.data, ports[port].controlEndpoint.physical); + ports[port].controlEndpoint.data = nullptr; + ports[port].controlEndpoint.physical = 0; + + // Zero out remaining fields in the port structure. + + ports[port].slotID = 0; + ports[port].speed = 0; + ports[port].controlTransferResult = nullptr; + ports[port].controlTransferLastTRBAddress = 0; +} + +void XHCIController::Initialise() { + portStatusChangeEvent.autoReset = true; + + // Read capabilities. + + operationalRegistersOffset = RD_REGISTER_CAPLENGTH() & 0xFF; + + if ((RD_REGISTER_CAPLENGTH() & 0xFF000000) == 0) { + KernelLog(LOG_ERROR, "xHCI", "unsupported version", "xHCI controller reports unsupported version number %x.\n", + RD_REGISTER_CAPLENGTH() >> 16); + return; + } + + uint32_t hcsp1 = RD_REGISTER_HCSPARAMS1(); + maximumDeviceSlots = (hcsp1 >> 0) & 0xFF; + maximumInterrupters = (hcsp1 >> 8) & 0x7FF; + maximumPorts = (hcsp1 >> 24) & 0xFF; + + ports = (XHCIPort *) EsHeapAllocate(sizeof(XHCIPort) * maximumPorts, true, K_FIXED); + + if (!ports) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate ports array.\n"); + return; + } + + uint32_t hcsp2 = RD_REGISTER_HCSPARAMS2(); + maximumEventRingSegments = 1 << ((hcsp2 >> 4) & 0xF); + + uint32_t scratchpadBufferCount = ((hcsp2 >> 27) & 0x1F) | (((hcsp2 >> 21) & 0x1F) << 5); + KernelLog(LOG_INFO, "xHCI", "scratchpad buffers", "Controller requests %d scratchpad buffers (HCSP2 = %x).\n", scratchpadBufferCount, hcsp2); + + uint32_t hccp1 = RD_REGISTER_HCCPARAMS1(); + contextSize64 = (hccp1 & (1 << 2)) ? true : false; + extendedCapabilitiesOffset = ((hccp1 >> 16) & 0xFFFF) << 2; + +#ifdef ARCH_64 + if (~hccp1 & (1 << 0)) { + KernelLog(LOG_ERROR, "xHCI", "missing feature", "xHCI controller does not support 64-bit addresses.\n"); + return; + } +#endif + + doorbellsOffset = RD_REGISTER_DBOFF(); + runtimeRegistersOffset = RD_REGISTER_RTSOFF(); + + KernelLog(LOG_INFO, "xHCI", "capabilities", "xHC reports capabilities: maximum ports - %d, maximum interrupts - %d, " + "maximum device slots - %d, maximum event ring segments - %d.\n", + maximumPorts, maximumInterrupters, maximumDeviceSlots, maximumEventRingSegments); + + // Enumerate extended capabilities. + + if (extendedCapabilitiesOffset) { + uintptr_t offset = extendedCapabilitiesOffset; + + for (uintptr_t i = 0; i < 256; i++) { + uint32_t extendedCapability = pci->ReadBAR32(0, offset); + uint8_t id = (extendedCapability >> 0) & 0xFF; + uint8_t next = (extendedCapability >> 8) & 0xFF; + + if (id == 1) { + // Perform BIOS handoff, if necessary. + + if (extendedCapability & (1 << 16)) { + pci->WriteBAR32(0, offset, extendedCapability & (1 << 24)); + + KTimeout timeout(1000); + while (!timeout.Hit() && (pci->ReadBAR32(0, offset) & (1 << 16))); + + if (pci->ReadBAR32(0, offset) & (1 << 16)) { + KernelLog(LOG_ERROR, "xHCI", "BIOS handoff failure", "Could not take control of the xHC from the BIOS.\n"); + return; + } + + uint32_t control = pci->ReadBAR32(0, offset + 4); + + KernelLog(LOG_INFO, "xHCI", "legacy control", "USB legacy control/status register is %x.\n", control); + + // Disable SMIs. + control &= ~((1 << 0) | (1 << 4) | (1 << 13) | (1 << 14) | (1 << 15)); + + // Clear RsvdZ bits. + control &= ~((1 << 21) | (1 << 22) | (1 << 23) | (1 << 24) | (1 << 25) | (1 << 26) | (1 << 27) | (1 << 28)); + + // Acknowledge any status bits. + control |= (1 << 29) | (1 << 30) | (1 << 31); + + KernelLog(LOG_INFO, "xHCI", "legacy control", "Writing legacy control/status %x.\n", control); + + pci->WriteBAR32(0, offset + 4, control); + } + } else if (id == 2) { + uint32_t nameString = pci->ReadBAR32(0, offset + 4); + uint32_t portRange = pci->ReadBAR32(0, offset + 8); + uintptr_t portOffset = (portRange & 0xFF) - 1; + size_t portCount = (portRange >> 8) & 0xFF; + uint32_t slotType = pci->ReadBAR32(0, offset + 12) & 0x1F; + + KernelLog(LOG_INFO, "xHCI", "protocol enumerated", + "Controller supports protocol on ports %d->%d with version %d.%d (%s) and slot type %d.\n", + portOffset, portOffset + portCount - 1, + extendedCapability >> 24, (extendedCapability >> 16) & 0xFF, 4, &nameString, slotType); + + for (uintptr_t i = portOffset; i < portOffset + portCount; i++) { + ports[i].usb2 = (extendedCapability >> 24) == 2; + ports[i].slotType = slotType; + } + } + + if (next) { + offset += next << 2; + } else { + break; + } + } + } + + // Check page size is supported. + + if (~RD_REGISTER_PAGESIZE() & (1 << (K_PAGE_BITS - 12))) { + KernelLog(LOG_ERROR, "xHCI", "page size not supported", "xHC does not support native page size of %x; supported mask is %x.\n", + K_PAGE_SIZE, RD_REGISTER_PAGESIZE()); + return; + } + + // Stop the controller. + + KernelLog(LOG_INFO, "xHCI", "stop controller", "Stopping the controller...\n"); + + WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() & ~(1 << 0)); + + { + KTimeout timeout(20); + while (!timeout.Hit() && (~RD_REGISTER_USBSTS() & (1 << 0))); + + if ((~RD_REGISTER_USBSTS() & (1 << 0))) { + KernelLog(LOG_ERROR, "xHCI", "stop failure", "Could not stop the xHC to perform a reset.\n"); + return; + } + } + + // Reset the controller. + + KernelLog(LOG_INFO, "xHCI", "reset controller", "Resetting the controller...\n"); + + WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() | (1 << 1)); + + { + KTimeout timeout(100); + while (!timeout.Hit() && (RD_REGISTER_USBCMD() & (1 << 1))); + + if (RD_REGISTER_USBCMD() & (1 << 1)) { + KernelLog(LOG_ERROR, "xHCI", "reset failure", "Could not reset the xHC within 100ms.\n"); + return; + } + } + + KernelLog(LOG_INFO, "xHCI", "reset controller", "Controller successfully reset.\n"); + +#ifdef ARCH_X86_COMMON + // Any PS/2 emulation should have been disabled, so its controller is safe to initialise. + KPS2SafeToInitialise(); +#endif + + // Allocate the input context. + + if (!MMPhysicalAllocateAndMap(INPUT_CONTEXT_BYTES, K_PAGE_SIZE, 0, true, + 0, (uint8_t **) &inputContext, &inputContextPhysical)) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the input context.\n"); + return; + } + + // Allocate the device context base address array. + + uintptr_t deviceContextBaseAddressArrayPhysical; + + if (!MMPhysicalAllocateAndMap(256 * 8, 64, 0, true, + 0, (uint8_t **) &deviceContextBaseAddressArray, &deviceContextBaseAddressArrayPhysical)) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the device context base address array.\n"); + return; + } + + WR_REGISTER_DCBAAP(deviceContextBaseAddressArrayPhysical); + + // Allocate the command ring. + + uintptr_t commandRingPhysical; + + if (!MMPhysicalAllocateAndMap(16 * COMMAND_RING_ENTRIES, 64, 0, true, + 0, (uint8_t **) &commandRing, &commandRingPhysical)) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the command ring.\n"); + return; + } + + if (RD_REGISTER_CRCR() & (1 << 3)) { + KernelLog(LOG_ERROR, "xHCI", "command ring running", "Command ring running while controller stopped.\n"); + return; + } + + KernelLog(LOG_INFO, "xHCI", "command ring allocation", "Allocated command ring at physical address %x.\n", commandRingPhysical); + + WR_REGISTER_CRCR(commandRingPhysical | 1); + + commandRing[COMMAND_RING_ENTRIES * 4 + 0] = commandRingPhysical & 0xFFFFFFFF; + commandRing[COMMAND_RING_ENTRIES * 4 + 1] = (commandRingPhysical >> 32) & 0xFFFFFFFF; + commandRing[COMMAND_RING_ENTRIES * 4 + 3] = (1 << 1 /* toggle cycle */) | (6 /* link TRB */ << 10); + + commandRingPhase = true; + + // Allocate the event ring. + + if (!MMPhysicalAllocateAndMap(64 + 16 * EVENT_RING_ENTRIES, 64, 0, true, + 0, (uint8_t **) &eventRing, &eventRingPhysical)) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the command ring.\n"); + return; + } + + eventRing[0] = ((eventRingPhysical + 0x40) >> 0) & 0xFFFFFFFF; + eventRing[1] = ((eventRingPhysical + 0x40) >> 32) & 0xFFFFFFFF; + eventRing[2] = EVENT_RING_ENTRIES; + eventRing[3] = 0; + eventRingPhase = true; + + WR_REGISTER_ERDP(0, (eventRingPhysical + 0x40) | (1 << 3)); + WR_REGISTER_ERSTSZ(0, (RD_REGISTER_ERSTSZ(0) & 0xFFFF0000) | 1); + WR_REGISTER_ERSTBA(0, (RD_REGISTER_ERSTBA(0) & 0x3F) | eventRingPhysical); + + // Setup the CONFIG register. + + WR_REGISTER_CONFIG((RD_REGISTER_CONFIG() & 0xFFFFFC00) | maximumDeviceSlots); + + // Register the interrupt handler and enable interrupts. + + KIRQHandler handler = [] (uintptr_t, void *context) { return ((XHCIController *) context)->HandleIRQ(); }; + + if (!pci->EnableSingleInterrupt(handler, this, "xHCI")) { + KernelLog(LOG_ERROR, "xHCI", "IRQ registration failure", "Could not register interrupt handler.\n"); + return; + } + + WR_REGISTER_IMOD(0, 4000 /* in 250ns increments */); // Interrupts should be sent at most every millisecond. + WR_REGISTER_IMAN(0, RD_REGISTER_IMAN(0) | (1 << 1)); + WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() | (1 << 2)); + + // Allocate scratchpad buffers. + + if (scratchpadBufferCount) { + uint64_t *scratchpadArray; + uintptr_t scratchpadArrayPhysical; + + if (!MMPhysicalAllocateAndMap(scratchpadBufferCount * sizeof(uint64_t), 64, 0, true, + 0, (uint8_t **) &scratchpadArray, &scratchpadArrayPhysical)) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the scratchpad buffer array.\n"); + return; + } + + for (uintptr_t i = 0; i < scratchpadBufferCount; i++) { + scratchpadArray[i] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED); + + if (!scratchpadArray[i]) { + KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate scratchpad buffer %d.\n", i); + return; + } + } + + deviceContextBaseAddressArray[0] = scratchpadArrayPhysical; + } + + // Start the controller! + + KernelLog(LOG_INFO, "xHCI", "start controller", "Starting controller...\n"); + + WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() | (1 << 0)); + + { + KTimeout timeout(20); + while (!timeout.Hit() && (RD_REGISTER_USBSTS() & (1 << 0))); + + if ((RD_REGISTER_USBSTS() & (1 << 0))) { + KernelLog(LOG_ERROR, "xHCI", "start failure", "Could not start the xHC after reset.\n"); + return; + } + } + + KernelLog(LOG_INFO, "xHCI", "start controller", "Controller successfully started.\n"); + + // Reset USB2 ports. + // TODO Will we receive events for connected USB3 ports automatically, or do we need to do something? + + KSpinlockAcquire(&portResetSpinlock); + + for (uintptr_t i = 0; i < maximumPorts; i++) { + uint32_t status = RD_REGISTER_PORTSC(i); + uint32_t linkState = (status >> 5) & 0x0F; + KernelLog(LOG_INFO, "xHCI", "port status", "Port %d (USB %d) has status %x (link state = %d).\n", i, ports[i].usb2 ? 2 : 3, status, linkState); + + if (ports[i].usb2 && (linkState == 7 || linkState == 4) && (~status & (1 << 4))) { + KernelLog(LOG_INFO, "xHCI", "port reset", "Attempting to reset USB 2 port %d... (2)\n", i); + WR_REGISTER_PORTSC(i, (status & (1 << 9)) | (1 << 4)); + } + } + + KSpinlockRelease(&portResetSpinlock); + + // Wait for events to process. + + while (true) { + uintptr_t port = 0; + + for (uintptr_t i = 0; i < maximumPorts; i++) { + if (ports[i].statusChangeEvent) { + port = i; + goto found; + } + } + + KEventWait(&portStatusChangeEvent); + continue; + + found:; + ports[port].statusChangeEvent = false; + + if (ports[port].enabled) { + OnPortEnable(port); + } else { + OnPortDisable(port); + } + } +} + +static void DeviceAttach(KDevice *_parent) { + KPCIDevice *parent = (KPCIDevice *) _parent; + + XHCIController *device = (XHCIController *) KDeviceCreate("XHCI controller", parent, sizeof(XHCIController)); + if (!device) return; + device->pci = parent; + + device->dumpState = [] (KDevice *device) { + ((XHCIController *) device)->DumpState(); + }; + + KernelLog(LOG_INFO, "xHCI", "found controller", "Found XHCI controller at PCI function %d/%d/%d.\n", parent->bus, parent->slot, parent->function); + + // Enable PCI features. + parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS + | K_PCI_FEATURE_BUSMASTERING_DMA + | K_PCI_FEATURE_MEMORY_SPACE_ACCESS + | K_PCI_FEATURE_BAR_0); + + // Initialise the controller on a new thread. + KThreadCreate("XHCIInitialisation", [] (uintptr_t context) { + ((XHCIController *) context)->Initialise(); + }, (uintptr_t) device); +} + +KDriver driverxHCI = { + .attach = DeviceAttach, +}; diff --git a/kernel/audio.cpp b/kernel/audio.cpp new file mode 100644 index 0000000..e69de29 diff --git a/kernel/cache.cpp b/kernel/cache.cpp new file mode 100644 index 0000000..e4e221b --- /dev/null +++ b/kernel/cache.cpp @@ -0,0 +1,1220 @@ +#ifndef IMPLEMENTATION + +// TODO Implement read ahead in CCSpaceAccess. +// TODO Implement dispatch groups in CCSpaceAccess and CCWriteBehindThread. +// TODO Implement better write back algorithm. + +// TODO Check that active.references in the page frame database is used safely with threading. + +// Describes the physical memory covering a section of a file. + +struct CCCachedSection { + EsFileOffset offset, // The offset into the file. + pageCount; // The number of pages in the section. + volatile size_t mappedRegionsCount; // The number of mapped regions that use this section. + uintptr_t *data; // A list of page frames covering the section. +}; + +struct CCActiveSection { + KEvent loadCompleteEvent, writeCompleteEvent; + LinkedItem listItem; // Either in the LRU list, or the modified list. If accessors > 0, it should not be in a list. + + EsFileOffset offset; + struct CCSpace *cache; + + size_t accessors; + volatile bool loading, writing, modified, flush; + + uint16_t referencedPageCount; + uint8_t referencedPages[CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE / 8]; // If accessors > 0, then pages cannot be dereferenced. + + uint8_t modifiedPages[CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE / 8]; +}; + +struct CCActiveSectionReference { + EsFileOffset offset; // Offset into the file; multiple of CC_ACTIVE_SECTION_SIZE. + uintptr_t index; // Index of the active section. +}; + +struct MMActiveSectionManager { + CCActiveSection *sections; + size_t sectionCount; + uint8_t *baseAddress; + KMutex mutex; + LinkedList lruList; + LinkedList modifiedList; + KEvent modifiedNonEmpty, modifiedNonFull; + Thread *writeBackThread; +}; + +// The callbacks for a CCSpace. + +struct CCSpaceCallbacks { + EsError (*readInto)(CCSpace *fileCache, void *buffer, EsFileOffset offset, EsFileOffset count); + EsError (*writeFrom)(CCSpace *fileCache, const void *buffer, EsFileOffset offset, EsFileOffset count); +}; + +// Describes the physical and virtual memory covering a file. + +struct CCSpace { + // A sorted list of the cached sections in the file. + // Maps offset -> physical address. + KMutex cachedSectionsMutex; + Array cachedSections; + + // A sorted list of the active sections. + // Maps offset -> virtual address. + KMutex activeSectionsMutex; + Array activeSections; + + // Used by CCSpaceFlush. + KEvent writeComplete; + + // Callbacks. + const CCSpaceCallbacks *callbacks; +}; + +void CCInitialise(); + +void CCDereferenceActiveSection(CCActiveSection *section, uintptr_t startingPage = 0); + +bool CCSpaceInitialise(CCSpace *cache); +void CCSpaceDestroy(CCSpace *cache); +void CCSpaceFlush(CCSpace *cache); +void CCSpaceTruncate(CCSpace *cache, EsFileOffset newSize); +bool CCSpaceCover(CCSpace *cache, EsFileOffset insertStart, EsFileOffset insertEnd); +void CCSpaceUncover(CCSpace *cache, EsFileOffset removeStart, EsFileOffset removeEnd); + +#define CC_ACCESS_MAP (1 << 0) +#define CC_ACCESS_READ (1 << 1) +#define CC_ACCESS_WRITE (1 << 2) +#define CC_ACCESS_WRITE_BACK (1 << 3) // Wait for the write to complete before returning. +#define CC_ACCESS_PRECISE (1 << 4) // Do not write back bytes not touched by this write. (Usually modified tracking is to page granularity.) Requires WRITE_BACK. +#define CC_ACCESS_USER_BUFFER_MAPPED (1 << 5) // Set if the user buffer is memory-mapped to mirror this or another cache. + +EsError CCSpaceAccess(CCSpace *cache, K_USER_BUFFER void *buffer, EsFileOffset offset, EsFileOffset count, uint32_t flags, + MMSpace *mapSpace = nullptr, unsigned mapFlags = ES_FLAGS_DEFAULT); + +#else + +CCCachedSection *CCFindCachedSectionContaining(CCSpace *cache, EsFileOffset sectionOffset) { + KMutexAssertLocked(&cache->cachedSectionsMutex); + + if (!cache->cachedSections.Length()) { + return nullptr; + } + + CCCachedSection *cachedSection = nullptr; + + bool found = false; + intptr_t low = 0, high = cache->cachedSections.Length() - 1; + + while (low <= high) { + intptr_t i = low + (high - low) / 2; + cachedSection = &cache->cachedSections[i]; + + if (cachedSection->offset + cachedSection->pageCount * K_PAGE_SIZE <= sectionOffset) { + low = i + 1; + } else if (cachedSection->offset > sectionOffset) { + high = i - 1; + } else { + found = true; + break; + } + } + + return found ? cachedSection : nullptr; +} + +bool CCSpaceCover(CCSpace *cache, EsFileOffset insertStart, EsFileOffset insertEnd) { + KMutexAssertLocked(&cache->cachedSectionsMutex); + + // TODO Test this thoroughly. + // TODO Break up really large sections. (maybe into GBs?) + + insertStart = RoundDown(insertStart, K_PAGE_SIZE); + insertEnd = RoundUp(insertEnd, K_PAGE_SIZE); + EsFileOffset position = insertStart, lastEnd = 0; + CCCachedSection *result = nullptr; + + // EsPrint("New: %d, %d\n", insertStart / K_PAGE_SIZE, insertEnd / K_PAGE_SIZE); + + for (uintptr_t i = 0; i < cache->cachedSections.Length(); i++) { + CCCachedSection *section = &cache->cachedSections[i]; + + EsFileOffset sectionStart = section->offset, + sectionEnd = section->offset + section->pageCount * K_PAGE_SIZE; + + // EsPrint("Existing (%d): %d, %d\n", i, sectionStart / K_PAGE_SIZE, sectionEnd / K_PAGE_SIZE); + + if (insertStart > sectionEnd) continue; + + // If the inserted region starts before this section starts, then we need to make a new section before us. + + if (position < sectionStart) { + CCCachedSection newSection = {}; + newSection.mappedRegionsCount = 0; + newSection.offset = position; + newSection.pageCount = ((insertEnd > sectionStart ? sectionStart : insertEnd) - position) / K_PAGE_SIZE; + + if (newSection.pageCount) { + // EsPrint("\tAdded: %d, %d\n", newSection.offset / K_PAGE_SIZE, newSection.pageCount); + newSection.data = (uintptr_t *) EsHeapAllocate(sizeof(uintptr_t) * newSection.pageCount, true, K_CORE); + + if (!newSection.data) { + goto fail; + } + + if (!cache->cachedSections.Insert(newSection, i)) { + EsHeapFree(newSection.data, sizeof(uintptr_t) * newSection.pageCount, K_CORE); + goto fail; + } + + i++; + } + + } + + position = sectionEnd; + if (position > insertEnd) break; + } + + // Insert the final section if necessary. + + if (position < insertEnd) { + CCCachedSection newSection = {}; + newSection.mappedRegionsCount = 0; + newSection.offset = position; + newSection.pageCount = (insertEnd - position) / K_PAGE_SIZE; + newSection.data = (uintptr_t *) EsHeapAllocate(sizeof(uintptr_t) * newSection.pageCount, true, K_CORE); + // EsPrint("\tAdded (at end): %d, %d\n", newSection.offset / K_PAGE_SIZE, newSection.pageCount); + + if (!newSection.data) { + goto fail; + } + + if (!cache->cachedSections.Add(newSection)) { + EsHeapFree(newSection.data, sizeof(uintptr_t) * newSection.pageCount, K_CORE); + goto fail; + } + } + + for (uintptr_t i = 0; i < cache->cachedSections.Length(); i++) { + CCCachedSection *section = &cache->cachedSections[i]; + + EsFileOffset sectionStart = section->offset, + sectionEnd = section->offset + section->pageCount * K_PAGE_SIZE; + + if (sectionStart < lastEnd) KernelPanic("CCSpaceCover - Overlapping MMCachedSections.\n"); + + // If the inserted region ends after this section starts, + // and starts before this section ends, then it intersects it. + + if (insertEnd > sectionStart && insertStart < sectionEnd) { + section->mappedRegionsCount++; + // EsPrint("+ %x %x %d\n", cache, section->data, section->mappedRegionsCount); + if (result && sectionStart != lastEnd) KernelPanic("CCSpaceCover - Incomplete MMCachedSections.\n"); + if (!result) result = section; + } + + lastEnd = sectionEnd; + } + + return true; + + fail:; + return false; // TODO Remove unused cached sections? +} + +void CCSpaceUncover(CCSpace *cache, EsFileOffset removeStart, EsFileOffset removeEnd) { + KMutexAssertLocked(&cache->cachedSectionsMutex); + + removeStart = RoundDown(removeStart, K_PAGE_SIZE); + removeEnd = RoundUp(removeEnd, K_PAGE_SIZE); + + CCCachedSection *first = CCFindCachedSectionContaining(cache, removeStart); + + if (!first) { + KernelPanic("CCSpaceUncover - Range %x->%x was not covered in cache %x.\n", removeStart, removeEnd, cache); + } + + for (uintptr_t i = first - cache->cachedSections.array; i < cache->cachedSections.Length(); i++) { + CCCachedSection *section = &cache->cachedSections[i]; + + EsFileOffset sectionStart = section->offset, + sectionEnd = section->offset + section->pageCount * K_PAGE_SIZE; + + if (removeEnd > sectionStart && removeStart < sectionEnd) { + if (!section->mappedRegionsCount) KernelPanic("CCSpaceUncover - Section wasn't mapped.\n"); + section->mappedRegionsCount--; + // EsPrint("- %x %x %d\n", cache, section->data, section->mappedRegionsCount); + } else { + break; + } + } +} + +void CCWriteSectionPrepare(CCActiveSection *section) { + KMutexAssertLocked(&activeSectionManager.mutex); + if (!section->modified) KernelPanic("CCWriteSectionPrepare - Unmodified section %x on modified list.\n", section); + if (section->accessors) KernelPanic("CCWriteSectionPrepare - Section %x with accessors on modified list.\n", section); + if (section->writing) KernelPanic("CCWriteSectionPrepare - Section %x already being written.\n", section); + activeSectionManager.modifiedList.Remove(§ion->listItem); + section->writing = true; + section->modified = false; + section->flush = false; + KEventReset(§ion->writeCompleteEvent); + section->accessors = 1; +} + +void CCWriteSection(CCActiveSection *section) { + // Write any modified pages to the backing store. + + uint8_t *sectionBase = activeSectionManager.baseAddress + (section - activeSectionManager.sections) * CC_ACTIVE_SECTION_SIZE; + EsError error = ES_SUCCESS; + + for (uintptr_t i = 0; i < CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE; i++) { + uintptr_t from = i, count = 0; + + while (i != CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE + && (section->modifiedPages[i >> 3] & (1 << (i & 7)))) { + count++, i++; + } + + if (!count) continue; + + error = section->cache->callbacks->writeFrom(section->cache, sectionBase + from * K_PAGE_SIZE, + section->offset + from * K_PAGE_SIZE, count * K_PAGE_SIZE); + + if (error != ES_SUCCESS) { + break; + } + } + + // Return the active section. + + KMutexAcquire(&activeSectionManager.mutex); + + if (!section->accessors) KernelPanic("CCWriteSection - Section %x has no accessors while being written.\n", section); + if (section->modified) KernelPanic("CCWriteSection - Section %x was modified while being written.\n", section); + + section->accessors--; + section->writing = false; + EsMemoryZero(section->modifiedPages, sizeof(section->modifiedPages)); + __sync_synchronize(); + KEventSet(§ion->writeCompleteEvent); + KEventSet(§ion->cache->writeComplete, false, true); + + if (!section->accessors) { + if (section->loading) KernelPanic("CCSpaceAccess - Active section %x with no accessors is loading.", section); + activeSectionManager.lruList.InsertEnd(§ion->listItem); + } + + KMutexRelease(&activeSectionManager.mutex); +} + +void CCSpaceFlush(CCSpace *cache) { + while (true) { + bool complete = true; + + KMutexAcquire(&cache->activeSectionsMutex); + KMutexAcquire(&activeSectionManager.mutex); + + for (uintptr_t i = 0; i < cache->activeSections.Length(); i++) { + CCActiveSection *section = activeSectionManager.sections + cache->activeSections[i].index; + + if (section->cache == cache && section->offset == cache->activeSections[i].offset) { + if (section->writing) { + // The section is being written; wait for it to complete. + complete = false; + } else if (section->modified) { + if (section->accessors) { + // Someone is accessing this section; mark it to be written back once they are done. + section->flush = true; + complete = false; + } else { + // Nobody is accessing the section; we can write it ourselves. + complete = false; + CCWriteSectionPrepare(section); + KMutexRelease(&activeSectionManager.mutex); + KMutexRelease(&cache->activeSectionsMutex); + CCWriteSection(section); + KMutexAcquire(&cache->activeSectionsMutex); + KMutexAcquire(&activeSectionManager.mutex); + } + } + } + + } + + KMutexRelease(&activeSectionManager.mutex); + KMutexRelease(&cache->activeSectionsMutex); + + if (!complete) { + KEventWait(&cache->writeComplete); + } else { + break; + } + } +} + +void CCActiveSectionReturnToLists(CCActiveSection *section, bool writeBack) { + bool waitNonFull = false; + + if (section->flush) { + writeBack = true; + } + + while (true) { + // If modified, wait for the modified list to be below a certain size. + + if (section->modified && waitNonFull) { + KEventWait(&activeSectionManager.modifiedNonFull); + } + + // Decrement the accessors count. + + KMutexAcquire(&activeSectionManager.mutex); + EsDefer(KMutexRelease(&activeSectionManager.mutex)); + + if (!section->accessors) KernelPanic("CCSpaceAccess - Active section %x has no accessors.\n", section); + + if (section->accessors == 1) { + if (section->loading) KernelPanic("CCSpaceAccess - Active section %x with no accessors is loading.", section); + + // If nobody is accessing the section, put it at the end of the LRU list. + + if (section->modified) { + if (activeSectionManager.modifiedList.count > CC_MAX_MODIFIED) { + waitNonFull = true; + continue; + } else if (activeSectionManager.modifiedList.count == CC_MAX_MODIFIED) { + KEventReset(&activeSectionManager.modifiedNonFull); + } + + activeSectionManager.modifiedList.InsertEnd(§ion->listItem); + KEventSet(&activeSectionManager.modifiedNonEmpty, false, true); + } else { + activeSectionManager.lruList.InsertEnd(§ion->listItem); + } + } + + section->accessors--; + + if (writeBack && !section->accessors && section->modified) { + CCWriteSectionPrepare(section); + } else { + writeBack = false; + } + + break; + } + + if (writeBack) { + CCWriteSection(section); + } +} + +void CCSpaceTruncate(CCSpace *cache, size_t newSize) { + // Concurrent calls to CCSpaceAccess must be prevented; + // this only handles concurrent calls to CCWriteSection. + + uintptr_t newSizePages = (newSize + K_PAGE_SIZE - 1) / K_PAGE_SIZE; + bool doneActiveSections = false; + + while (!doneActiveSections) { + bool waitForWritingToComplete = false; + CCActiveSection *section = nullptr; + + KMutexAcquire(&cache->activeSectionsMutex); + KMutexAcquire(&activeSectionManager.mutex); + + if (cache->activeSections.Length()) { + // Get the last active section. + CCActiveSectionReference reference = cache->activeSections.Last(); + section = activeSectionManager.sections + reference.index; + + if (section->cache != cache || section->offset != reference.offset) { + // Remove invalid section. + // TODO This code path has not been tested. + cache->activeSections.SetLength(cache->activeSections.Length() - 1); + section = nullptr; + } else { + if (reference.offset + CC_ACTIVE_SECTION_SIZE <= newSize) { + // We've cleared all the active sections that were past the truncation point. + doneActiveSections = true; + section = nullptr; + } else { + if (section->accessors) { + // We want to remove part/all of this section, but it's being written, + // so we'll wait for that to complete first. + + if (!section->writing) { + KernelPanic("CCSpaceTruncate - Active section %x in space %x has accessors but isn't being written.\n", + section, cache); + } + + waitForWritingToComplete = true; + } else { + section->listItem.RemoveFromList(); + } + + if (section->loading) { + // TODO How will this interact with read-ahead, once implemented? + KernelPanic("CCSpaceTruncate - Active section %x in space %x is in the loading state.\n", + section, cache); + } + + section->accessors++; + + if (section->offset >= newSize) { + cache->activeSections.SetLength(cache->activeSections.Length() - 1); + } + } + + } + } else { + doneActiveSections = true; + } + + KMutexRelease(&activeSectionManager.mutex); + KMutexRelease(&cache->activeSectionsMutex); + + if (section) { + if (waitForWritingToComplete) { + KEventWait(§ion->writeCompleteEvent); + } + + if (section->offset >= newSize) { + // Remove active sections completely past the truncation point. + KMutexAcquire(&cache->cachedSectionsMutex); + CCSpaceUncover(cache, section->offset, section->offset + CC_ACTIVE_SECTION_SIZE); + KMutexRelease(&cache->cachedSectionsMutex); + KMutexAcquire(&activeSectionManager.mutex); + CCDereferenceActiveSection(section); + section->cache = nullptr; + section->accessors = 0; + section->modified = false; // Don't try to write this section back! + activeSectionManager.lruList.InsertStart(§ion->listItem); + KMutexRelease(&activeSectionManager.mutex); + } else { + // Remove part of the active section containing the truncation point. + KMutexAcquire(&activeSectionManager.mutex); + CCDereferenceActiveSection(section, newSizePages - section->offset / K_PAGE_SIZE); + KMutexRelease(&activeSectionManager.mutex); + CCActiveSectionReturnToLists(section, false); + break; + } + } + } + + KMutexAcquire(&cache->cachedSectionsMutex); + + while (cache->cachedSections.Length()) { + CCCachedSection *section = &cache->cachedSections.Last(); + + uintptr_t firstPage = 0; + + if (section->offset / K_PAGE_SIZE < newSizePages) { + firstPage = newSizePages - section->offset / K_PAGE_SIZE; + } + + if (firstPage > section->pageCount) { + // Don't early exit if firstPage = section->pageCount, since there could be a partialPage to clear. + break; + } + + for (uintptr_t i = firstPage; i < section->pageCount; i++) { + KMutexAcquire(&pmm.pageFrameMutex); + + if (section->data[i] & MM_SHARED_ENTRY_PRESENT) { + uintptr_t page = section->data[i] & ~(K_PAGE_SIZE - 1); + + if (pmm.pageFrames[page >> K_PAGE_BITS].state != MMPageFrame::ACTIVE) { + MMPhysicalActivatePages(page >> K_PAGE_BITS, 1, ES_FLAGS_DEFAULT); + } + + // Deallocate physical memory no longer in use. + MMPhysicalFree(page, true, 1); + + section->data[i] = 0; + } + + KMutexRelease(&pmm.pageFrameMutex); + } + + if (firstPage) { + if (newSize & (K_PAGE_SIZE - 1)) { + uintptr_t partialPage = (newSize - section->offset) / K_PAGE_SIZE; + + if (partialPage >= section->pageCount) { + KernelPanic("CCSpaceTruncate - partialPage %x outside range in CCSpace %x with new size %x.\n", + partialPage, cache, newSize); + } + + if (section->data[partialPage] & MM_SHARED_ENTRY_PRESENT) { + // Zero the inaccessible part of the last page still accessible after truncation. + PMZeroPartial(section->data[partialPage] & ~(K_PAGE_SIZE - 1), newSize & (K_PAGE_SIZE - 1), K_PAGE_SIZE); + } + } + + break; + } else { + if (section->mappedRegionsCount) { + KernelPanic("CCSpaceTruncate - Section %x has positive mappedRegionsCount in CCSpace %x.", + section, cache); + } + + EsHeapFree(section->data, sizeof(uintptr_t) * section->pageCount, K_CORE); + cache->cachedSections.SetLength(cache->cachedSections.Length() - 1); + } + } + + KMutexRelease(&cache->cachedSectionsMutex); +} + +void CCSpaceDestroy(CCSpace *cache) { + CCSpaceFlush(cache); + + for (uintptr_t i = 0; i < cache->activeSections.Length(); i++) { + KMutexAcquire(&activeSectionManager.mutex); + + CCActiveSection *section = activeSectionManager.sections + cache->activeSections[i].index; + + if (section->cache == cache && section->offset == cache->activeSections[i].offset) { + CCDereferenceActiveSection(section); + section->cache = nullptr; + + if (section->accessors || section->modified || section->listItem.list != &activeSectionManager.lruList) { + KernelPanic("CCSpaceDestroy - Section %x has invalid state to destroy cache space %x.\n", + section, cache); + } + + section->listItem.RemoveFromList(); + activeSectionManager.lruList.InsertStart(§ion->listItem); + } + + KMutexRelease(&activeSectionManager.mutex); + } + + for (uintptr_t i = 0; i < cache->cachedSections.Length(); i++) { + CCCachedSection *section = &cache->cachedSections[i]; + + for (uintptr_t i = 0; i < section->pageCount; i++) { + KMutexAcquire(&pmm.pageFrameMutex); + + if (section->data[i] & MM_SHARED_ENTRY_PRESENT) { + uintptr_t page = section->data[i] & ~(K_PAGE_SIZE - 1); + + if (pmm.pageFrames[page >> K_PAGE_BITS].state != MMPageFrame::ACTIVE) { + MMPhysicalActivatePages(page >> K_PAGE_BITS, 1, ES_FLAGS_DEFAULT); + } + + MMPhysicalFree(page, true, 1); + } + + KMutexRelease(&pmm.pageFrameMutex); + } + + EsHeapFree(section->data, sizeof(uintptr_t) * section->pageCount, K_CORE); + } + + cache->cachedSections.Free(); + cache->activeSections.Free(); +} + +bool CCSpaceInitialise(CCSpace *cache) { + cache->writeComplete.autoReset = true; + return true; +} + +void CCDereferenceActiveSection(CCActiveSection *section, uintptr_t startingPage) { + KMutexAssertLocked(&activeSectionManager.mutex); + + if (!startingPage) { + MMArchUnmapPages(kernelMMSpace, + (uintptr_t) activeSectionManager.baseAddress + (section - activeSectionManager.sections) * CC_ACTIVE_SECTION_SIZE, + CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE, MM_UNMAP_PAGES_BALANCE_FILE); + EsMemoryZero(section->referencedPages, sizeof(section->referencedPages)); + EsMemoryZero(section->modifiedPages, sizeof(section->modifiedPages)); + section->referencedPageCount = 0; + } else { + MMArchUnmapPages(kernelMMSpace, + (uintptr_t) activeSectionManager.baseAddress + + (section - activeSectionManager.sections) * CC_ACTIVE_SECTION_SIZE + + startingPage * K_PAGE_SIZE, + (CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE - startingPage), MM_UNMAP_PAGES_BALANCE_FILE); + + for (uintptr_t i = startingPage; i < CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE; i++) { + if (section->referencedPages[i >> 3] & (1 << (i & 7))) { + section->referencedPages[i >> 3] &= ~(1 << (i & 7)); + section->modifiedPages[i >> 3] &= ~(1 << (i & 7)); + section->referencedPageCount--; + } + } + } +} + +EsError CCSpaceAccess(CCSpace *cache, K_USER_BUFFER void *_buffer, EsFileOffset offset, EsFileOffset count, uint32_t flags, + MMSpace *mapSpace, unsigned mapFlags) { + // TODO Reading in multiple active sections at the same time - will this give better performance on AHCI/NVMe? + // - Each active section needs to be separately committed. + // TODO Read-ahead. + + // Commit CC_ACTIVE_SECTION_SIZE bytes, since we require an active section to be active at a time. + + if (!MMCommit(CC_ACTIVE_SECTION_SIZE, true)) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + EsDefer(MMDecommit(CC_ACTIVE_SECTION_SIZE, true)); + + K_USER_BUFFER uint8_t *buffer = (uint8_t *) _buffer; + + EsFileOffset firstSection = RoundDown(offset, CC_ACTIVE_SECTION_SIZE), + lastSection = RoundUp(offset + count, CC_ACTIVE_SECTION_SIZE); + + uintptr_t guessedActiveSectionIndex = 0; + + bool writeBack = (flags & CC_ACCESS_WRITE_BACK) && (~flags & CC_ACCESS_PRECISE); + bool preciseWriteBack = (flags & CC_ACCESS_WRITE_BACK) && (flags & CC_ACCESS_PRECISE); + + for (EsFileOffset sectionOffset = firstSection; sectionOffset < lastSection; sectionOffset += CC_ACTIVE_SECTION_SIZE) { + if (MM_AVAILABLE_PAGES() < MM_CRITICAL_AVAILABLE_PAGES_THRESHOLD && !GetCurrentThread()->isPageGenerator) { + KernelLog(LOG_ERROR, "Memory", "waiting for non-critical state", "File cache read on non-generator thread, waiting for more available pages.\n"); + KEventWait(&pmm.availableNotCritical); + } + + EsFileOffset start = sectionOffset < offset ? offset - sectionOffset : 0; + EsFileOffset end = sectionOffset + CC_ACTIVE_SECTION_SIZE > offset + count ? offset + count - sectionOffset : CC_ACTIVE_SECTION_SIZE; + + EsFileOffset pageStart = RoundDown(start, K_PAGE_SIZE) / K_PAGE_SIZE; + EsFileOffset pageEnd = RoundUp(end, K_PAGE_SIZE) / K_PAGE_SIZE; + + // Find the section in the active sections list. + + KMutexAcquire(&cache->activeSectionsMutex); + + bool found = false; + uintptr_t index = 0; + + if (guessedActiveSectionIndex < cache->activeSections.Length() + && cache->activeSections[guessedActiveSectionIndex].offset == sectionOffset) { + index = guessedActiveSectionIndex; + found = true; + } + + if (!found && cache->activeSections.Length()) { + intptr_t low = 0, high = cache->activeSections.Length() - 1; + + while (low <= high) { + intptr_t i = low + (high - low) / 2; + + if (cache->activeSections[i].offset < sectionOffset) { + low = i + 1; + } else if (cache->activeSections[i].offset > sectionOffset) { + high = i - 1; + } else { + index = i; + found = true; + break; + } + } + + if (!found) { + index = low; + if (high + 1 != low) KernelPanic("CCSpaceAccess - Bad binary search.\n"); + } + } + + if (found) { + guessedActiveSectionIndex = index + 1; + } + + KMutexAcquire(&activeSectionManager.mutex); + + CCActiveSection *section; + + // Replace active section in list if it has been used for something else. + + bool replace = false; + + if (found) { + CCActiveSection *section = activeSectionManager.sections + cache->activeSections[index].index; + + if (section->cache != cache || section->offset != sectionOffset) { + replace = true, found = false; + } + } + + if (!found) { + // Allocate a new active section. + + if (!activeSectionManager.lruList.count) { + KMutexRelease(&activeSectionManager.mutex); + KMutexRelease(&cache->activeSectionsMutex); + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + section = activeSectionManager.lruList.firstItem->thisItem; + + // Add it to the file cache's list of active sections. + + CCActiveSectionReference reference = { .offset = sectionOffset, .index = (uintptr_t) (section - activeSectionManager.sections) }; + + if (replace) { + cache->activeSections[index] = reference; + } else { + if (!cache->activeSections.Insert(reference, index)) { + KMutexRelease(&activeSectionManager.mutex); + KMutexRelease(&cache->activeSectionsMutex); + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + } + + if (section->cache) { + // Dereference pages. + + if (section->accessors) { + KernelPanic("CCSpaceAccess - Attempting to dereference active section %x with %d accessors.\n", + section, section->accessors); + } + + CCDereferenceActiveSection(section); + + // Uncover the section's previous contents. + + KMutexAcquire(§ion->cache->cachedSectionsMutex); + CCSpaceUncover(section->cache, section->offset, section->offset + CC_ACTIVE_SECTION_SIZE); + KMutexRelease(§ion->cache->cachedSectionsMutex); + + section->cache = nullptr; + } + + // Make sure there are cached sections covering the region of the active section. + + KMutexAcquire(&cache->cachedSectionsMutex); + + if (!CCSpaceCover(cache, sectionOffset, sectionOffset + CC_ACTIVE_SECTION_SIZE)) { + KMutexRelease(&cache->cachedSectionsMutex); + cache->activeSections.Delete(index); + KMutexRelease(&activeSectionManager.mutex); + KMutexRelease(&cache->activeSectionsMutex); + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + KMutexRelease(&cache->cachedSectionsMutex); + + // Remove it from the LRU list. + + activeSectionManager.lruList.Remove(activeSectionManager.lruList.firstItem); + + // Setup the section. + + if (section->accessors) KernelPanic("CCSpaceAccess - Active section %x in the LRU list had accessors.\n", section); + if (section->loading) KernelPanic("CCSpaceAccess - Active section %x in the LRU list was loading.\n", section); + + section->accessors = 1; + section->offset = sectionOffset; + section->cache = cache; + +#if 0 + { + Node *node = EsContainerOf(Node, file.cache, cache); + EsPrint("active section %d: %s, %x\n", reference.index, node->nameBytes, node->nameBuffer, section->offset); + } +#endif + +#ifdef DEBUG_BUILD + for (uintptr_t i = 1; i < cache->activeSections.Length(); i++) { + if (cache->activeSections[i - 1].offset >= cache->activeSections[i].offset) { + KernelPanic("CCSpaceAccess - Active sections list in cache %x unordered.\n", cache); + } + } +#endif + } else { + // Remove the active section from the LRU/modified list, if present, + // and increment the accessors count. + // Don't bother keeping track of its place in the modified list. + + section = activeSectionManager.sections + cache->activeSections[index].index; + + if (!section->accessors) { + if (section->writing) KernelPanic("CCSpaceAccess - Active section %x in list is being written.\n", section); + section->listItem.RemoveFromList(); + } else if (section->listItem.list) { + KernelPanic("CCSpaceAccess - Active section %x in list had accessors (2).\n", section); + } + + section->accessors++; + } + + KMutexRelease(&activeSectionManager.mutex); + KMutexRelease(&cache->activeSectionsMutex); + + if ((flags & CC_ACCESS_WRITE) && section->writing) { + // If writing, wait for any in progress write-behinds to complete. + // Note that, once this event is set, a new write can't be started until accessors is 0. + + KEventWait(§ion->writeCompleteEvent); + } + + uint8_t *sectionBase = activeSectionManager.baseAddress + (section - activeSectionManager.sections) * CC_ACTIVE_SECTION_SIZE; + + // Check if all the pages are already referenced (and hence loaded and mapped). + + bool allReferenced = true; + + for (uintptr_t i = pageStart; i < pageEnd; i++) { + if (~section->referencedPages[i >> 3] & (1 << (i & 7))) { + allReferenced = false; + break; + } + } + + uint8_t alreadyWritten[CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE / 8] = {}; + + if (allReferenced) { + goto copy; + } + + while (true) { + KMutexAcquire(&cache->cachedSectionsMutex); + + // Find the first cached section covering this active section. + + CCCachedSection *cachedSection = CCFindCachedSectionContaining(cache, sectionOffset); + + if (!cachedSection) { + KernelPanic("CCSpaceAccess - Active section %x not covered.\n", section); + } + + // Find where the requested region is located. + + uintptr_t pagesToSkip = pageStart + (sectionOffset - cachedSection->offset) / K_PAGE_SIZE, + pageInCachedSectionIndex = 0; + + while (pagesToSkip) { + if (pagesToSkip >= cachedSection->pageCount) { + pagesToSkip -= cachedSection->pageCount; + cachedSection++; + } else { + pageInCachedSectionIndex = pagesToSkip; + pagesToSkip = 0; + } + } + + if (pageInCachedSectionIndex >= cachedSection->pageCount + || cachedSection >= cache->cachedSections.array + cache->cachedSections.Length()) { + KernelPanic("CCSpaceAccess - Invalid requested region search result.\n"); + } + + // Reference all loaded pages, and record the ones we need to load. + + uintptr_t *pagesToLoad[CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE]; + uintptr_t loadCount = 0; + + for (uintptr_t i = pageStart; i < pageEnd; i++) { + if (cachedSection == cache->cachedSections.array + cache->cachedSections.Length()) { + KernelPanic("CCSpaceAccess - Not enough cached sections.\n"); + } + + KMutexAcquire(&pmm.pageFrameMutex); + + uintptr_t entry = cachedSection->data[pageInCachedSectionIndex]; + pagesToLoad[i] = nullptr; + + if ((entry & MM_SHARED_ENTRY_PRESENT) && (~section->referencedPages[i >> 3] & (1 << (i & 7)))) { + MMPageFrame *frame = pmm.pageFrames + (entry >> K_PAGE_BITS); + + if (frame->state == MMPageFrame::STANDBY) { + // The page was mapped out from all MMSpaces, and therefore was placed on the standby list. + // Mark the page as active before we map it. + MMPhysicalActivatePages(entry / K_PAGE_SIZE, 1, ES_FLAGS_DEFAULT); + frame->cacheReference = cachedSection->data + pageInCachedSectionIndex; + } else if (!frame->active.references) { + KernelPanic("CCSpaceAccess - Active page frame %x had no references.\n", frame); + } + + frame->active.references++; + + MMArchMapPage(kernelMMSpace, entry & ~(K_PAGE_SIZE - 1), (uintptr_t) sectionBase + i * K_PAGE_SIZE, MM_MAP_PAGE_FRAME_LOCK_ACQUIRED); + __sync_synchronize(); + section->referencedPages[i >> 3] |= 1 << (i & 7); + section->referencedPageCount++; + } else if (~entry & MM_SHARED_ENTRY_PRESENT) { + if (section->referencedPages[i >> 3] & (1 << (i & 7))) { + KernelPanic("CCSpaceAccess - Referenced page was not present.\n"); + } + + pagesToLoad[i] = cachedSection->data + pageInCachedSectionIndex; + loadCount++; + } + + KMutexRelease(&pmm.pageFrameMutex); + + pageInCachedSectionIndex++; + + if (pageInCachedSectionIndex == cachedSection->pageCount) { + pageInCachedSectionIndex = 0; + cachedSection++; + } + } + + if (!loadCount) { + KMutexRelease(&cache->cachedSectionsMutex); + goto copy; + } + + // If another thread is already trying to load pages into the active section, + // then wait for it to complete. + + bool loadCollision = section->loading; + + if (!loadCollision) { + section->loading = true; + KEventReset(§ion->loadCompleteEvent); + } + + KMutexRelease(&cache->cachedSectionsMutex); + + if (loadCollision) { + KEventWait(§ion->loadCompleteEvent); + continue; + } + + // Allocate, reference and map physical pages. + + uintptr_t pageFrames[CC_ACTIVE_SECTION_SIZE / K_PAGE_SIZE]; + + for (uintptr_t i = pageStart; i < pageEnd; i++) { + if (!pagesToLoad[i]) { + continue; + } + + pageFrames[i] = MMPhysicalAllocate(ES_FLAGS_DEFAULT); + + MMPageFrame *frame = pmm.pageFrames + (pageFrames[i] >> K_PAGE_BITS); + frame->active.references = 1; + frame->cacheReference = pagesToLoad[i]; + + MMArchMapPage(kernelMMSpace, pageFrames[i], (uintptr_t) sectionBase + i * K_PAGE_SIZE, ES_FLAGS_DEFAULT); + } + + // Read from the cache's backing store. + + EsError error = ES_SUCCESS; + + if ((flags & CC_ACCESS_WRITE) && (~flags & CC_ACCESS_USER_BUFFER_MAPPED)) { + bool loadedStart = false; + + if (error == ES_SUCCESS && (start & (K_PAGE_SIZE - 1)) && pagesToLoad[pageStart]) { + // Left side of the accessed region is not page aligned, so we need to load in the page. + + error = cache->callbacks->readInto(cache, sectionBase + pageStart * K_PAGE_SIZE, + section->offset + pageStart * K_PAGE_SIZE, K_PAGE_SIZE); + loadedStart = true; + } + + if (error == ES_SUCCESS && (end & (K_PAGE_SIZE - 1)) && !(pageStart == pageEnd - 1 && loadedStart) && pagesToLoad[pageEnd - 1]) { + // Right side of the accessed region is not page aligned, so we need to load in the page. + + error = cache->callbacks->readInto(cache, sectionBase + (pageEnd - 1) * K_PAGE_SIZE, + section->offset + (pageEnd - 1) * K_PAGE_SIZE, K_PAGE_SIZE); + } + + K_USER_BUFFER uint8_t *buffer2 = buffer; + + // Initialise the rest of the contents HERE, before referencing the pages. + // The user buffer cannot be mapped otherwise we could deadlock while reading from it, + // as we have marked the active section in the loading state. + + for (uintptr_t i = pageStart; i < pageEnd; i++) { + uintptr_t left = i == pageStart ? (start & (K_PAGE_SIZE - 1)) : 0; + uintptr_t right = i == pageEnd - 1 ? (end & (K_PAGE_SIZE - 1)) : K_PAGE_SIZE; + if (!right) right = K_PAGE_SIZE; + + if (pagesToLoad[i]) { + EsMemoryCopy(sectionBase + i * K_PAGE_SIZE + left, buffer2, right - left); + alreadyWritten[i >> 3] |= 1 << (i & 7); + } + + buffer2 += right - left; + } + + if (buffer + (end - start) != buffer2) { + KernelPanic("CCSpaceAccess - Incorrect page left/right calculation.\n"); + } + } else { + for (uintptr_t i = pageStart; i < pageEnd; i++) { + uintptr_t from = i, count = 0; + + while (i != pageEnd && pagesToLoad[i]) { + count++, i++; + } + + if (!count) continue; + + error = cache->callbacks->readInto(cache, sectionBase + from * K_PAGE_SIZE, + section->offset + from * K_PAGE_SIZE, count * K_PAGE_SIZE); + + if (error != ES_SUCCESS) { + break; + } + } + } + + if (error != ES_SUCCESS) { + // Free and unmap the pages we allocated if there was an error. + + for (uintptr_t i = pageStart; i < pageEnd; i++) { + if (!pagesToLoad[i]) continue; + MMArchUnmapPages(kernelMMSpace, (uintptr_t) sectionBase + i * K_PAGE_SIZE, 1, ES_FLAGS_DEFAULT); + MMPhysicalFree(pageFrames[i], false, 1); + } + } + + KMutexAcquire(&cache->cachedSectionsMutex); + + // Write the pages to the cached sections, and mark them as referenced. + + if (error == ES_SUCCESS) { + for (uintptr_t i = pageStart; i < pageEnd; i++) { + if (pagesToLoad[i]) { + *pagesToLoad[i] = pageFrames[i] | MM_SHARED_ENTRY_PRESENT; + section->referencedPages[i >> 3] |= 1 << (i & 7); + section->referencedPageCount++; + } + } + } + + // Return active section to normal state, and set the load complete event. + + section->loading = false; + KEventSet(§ion->loadCompleteEvent); + + KMutexRelease(&cache->cachedSectionsMutex); + + if (error != ES_SUCCESS) { + return error; + } + + break; + } + + copy:; + + // Copy into/from the user's buffer. + + if (buffer) { + if (flags & CC_ACCESS_MAP) { + if ((start & (K_PAGE_SIZE - 1)) || (end & (K_PAGE_SIZE - 1)) || ((uintptr_t) buffer & (K_PAGE_SIZE - 1))) { + KernelPanic("CCSpaceAccess - Passed READ_MAP flag, but start/end/buffer misaligned.\n"); + } + + for (uintptr_t i = start; i < end; i += K_PAGE_SIZE) { + uintptr_t physicalAddress = MMArchTranslateAddress(kernelMMSpace, (uintptr_t) sectionBase + i, false); + KMutexAcquire(&pmm.pageFrameMutex); + pmm.pageFrames[physicalAddress / K_PAGE_SIZE].active.references++; + KMutexRelease(&pmm.pageFrameMutex); + MMArchMapPage(mapSpace, physicalAddress, (uintptr_t) buffer, + mapFlags | MM_MAP_PAGE_IGNORE_IF_MAPPED /* since this isn't locked */); + buffer += K_PAGE_SIZE; + } + } else if (flags & CC_ACCESS_READ) { + EsMemoryCopy(buffer, sectionBase + start, end - start); + buffer += end - start; + } else if (flags & CC_ACCESS_WRITE) { + for (uintptr_t i = pageStart; i < pageEnd; i++) { + uintptr_t left = i == pageStart ? (start & (K_PAGE_SIZE - 1)) : 0; + uintptr_t right = i == pageEnd - 1 ? (end & (K_PAGE_SIZE - 1)) : K_PAGE_SIZE; + if (!right) right = K_PAGE_SIZE; + + if (~alreadyWritten[i >> 3] & (1 << (i & 7))) { + EsMemoryCopy(sectionBase + i * K_PAGE_SIZE + left, buffer, right - left); + } + + buffer += right - left; + + if (!preciseWriteBack) { + __sync_fetch_and_or(section->modifiedPages + (i >> 3), 1 << (i & 7)); + } + } + + if (!preciseWriteBack) { + section->modified = true; + } else { + uint8_t *sectionBase = activeSectionManager.baseAddress + (section - activeSectionManager.sections) * CC_ACTIVE_SECTION_SIZE; + EsError error = section->cache->callbacks->writeFrom(section->cache, sectionBase + start, section->offset + start, end - start); + + if (error != ES_SUCCESS) { + CCActiveSectionReturnToLists(section, writeBack); + return error; + } + } + } + } + + CCActiveSectionReturnToLists(section, writeBack); + } + + return ES_SUCCESS; +} + +void CCWriteBehindThread() { + while (true) { + // Wait for an active section to be modified, and have no accessors. + + KEventWait(&activeSectionManager.modifiedNonEmpty); + + if (MM_AVAILABLE_PAGES() > MM_LOW_AVAILABLE_PAGES_THRESHOLD && !scheduler.shutdown) { + // If there are sufficient available pages, wait before we start writing sections. + KEventWait(&pmm.availableLow, CC_WAIT_FOR_WRITE_BEHIND); + } + + while (true) { + // Take a section, and mark it as being written. + + CCActiveSection *section = nullptr; + KMutexAcquire(&activeSectionManager.mutex); + + if (activeSectionManager.modifiedList.count) { + section = activeSectionManager.modifiedList.firstItem->thisItem; + CCWriteSectionPrepare(section); + } + + KEventSet(&activeSectionManager.modifiedNonFull, false, true); + KMutexRelease(&activeSectionManager.mutex); + + if (!section) { + break; + } else { + CCWriteSection(section); + } + } + } +} + +void CCInitialise() { + activeSectionManager.sectionCount = CC_SECTION_BYTES / CC_ACTIVE_SECTION_SIZE; + activeSectionManager.sections = (CCActiveSection *) EsHeapAllocate(activeSectionManager.sectionCount * sizeof(CCActiveSection), true, K_FIXED); + + KMutexAcquire(&kernelMMSpace->reserveMutex); + activeSectionManager.baseAddress = (uint8_t *) MMReserve(kernelMMSpace, activeSectionManager.sectionCount * CC_ACTIVE_SECTION_SIZE, MM_REGION_CACHE)->baseAddress; + KMutexRelease(&kernelMMSpace->reserveMutex); + + for (uintptr_t i = 0; i < activeSectionManager.sectionCount; i++) { + activeSectionManager.sections[i].listItem.thisItem = &activeSectionManager.sections[i]; + activeSectionManager.lruList.InsertEnd(&activeSectionManager.sections[i].listItem); + } + + KernelLog(LOG_INFO, "Memory", "cache initialised", "MMInitialise - Active section manager initialised with a maximum of %d of entries.\n", + activeSectionManager.sectionCount); + + activeSectionManager.modifiedNonEmpty.autoReset = true; + KEventSet(&activeSectionManager.modifiedNonFull); + activeSectionManager.writeBackThread = scheduler.SpawnThread("CCWriteBehind", (uintptr_t) CCWriteBehindThread, 0, ES_FLAGS_DEFAULT); + activeSectionManager.writeBackThread->isPageGenerator = true; +} + +#endif diff --git a/kernel/config.ini b/kernel/config.ini new file mode 100644 index 0000000..dddc149 --- /dev/null +++ b/kernel/config.ini @@ -0,0 +1,146 @@ +[@driver Root] +builtin=1 + +; Subsystems. + +[@driver Networking] +parent=Root +builtin=1 + +[@driver USB] +source=drivers/usb.cpp +parent=Root +builtin=1 + +; Architectures. + +[@driver ACPI] +arch=x86_common +parent=Root +builtin=1 + +; Base devices. + +[@driver PCI] +source=drivers/pci.cpp +arch=x86_common +builtin=1 + +[@driver SVGA] +source=drivers/svga.cpp +arch=x86_common +builtin=1 + +[@driver PS2] +source=drivers/ps2.cpp +arch=x86_common +builtin=1 + +; PCI devices. + +[@driver IDE] +source=drivers/ide.cpp +builtin=1 +arch=x86_common +parent=PCI +classCode=0x01 +subclassCode=0x01 + +[@driver AHCI] +source=drivers/ahci.cpp +builtin=1 +parent=PCI +classCode=0x01 +subclassCode=0x06 + +[@driver NVMe] +source=drivers/nvme.cpp +builtin=1 +parent=PCI +classCode=0x01 +subclassCode=0x08 +progIF=0x02 + +[@driver HDAudio] +name=HDAudio +source=drivers/hda.cpp +builtin=1 +parent=PCI +classCode=0x04 +subclassCode=0x03 + +[@driver xHCI] +source=drivers/xhci.cpp +builtin=1 +parent=PCI +classCode=0x0C +subclassCode=0x03 +progIF=0x30 + +[@driver BGA] +source=drivers/bga.cpp +builtin=1 +parent=PCI +deviceID=0xBEEF80EE +deviceID=0x11111234 + +[@driver I8254x] +source=drivers/i8254x.cpp +builtin=1 +parent=PCI +deviceID=0x100E8086 + +; USB devices. + +[@driver USBHID] +source=drivers/usb_hid.cpp +builtin=1 +parent=USB +classCode=0x03 + +[@driver USBBulk] +source=drivers/usb_bulk.cpp +builtin=1 +parent=USB +classCode=0x08 +subclassCode=0x06 +protocol=0x50 + +; File systems. + +[@driver EssenceFS] +source=drivers/esfs2.cpp +builtin=1 +parent=Files +signature_offset=0x2000 +signature=!EssenceFS2----- + +[@driver FAT] +source=drivers/fat.cpp +builtin=1 +parent=Files +signature_offset=0x26 +signature=) +signature_offset=0x42 +signature=) + +[@driver ISO9660] +source=drivers/iso9660.cpp +builtin=1 +parent=Files +signature_offset=0x8001 +signature=CD001 + +[@driver NTFS] +source=drivers/ntfs.cpp +builtin=1 +parent=Files +signature_offset=3 +signature=NTFS + +[@driver Ext2] +source=drivers/ext2.cpp +builtin=1 +parent=Files +signature_offset=1080 +signature=S diff --git a/kernel/drivers.cpp b/kernel/drivers.cpp new file mode 100644 index 0000000..44fc5e9 --- /dev/null +++ b/kernel/drivers.cpp @@ -0,0 +1,324 @@ +#ifndef IMPLEMENTATION + +struct DeviceAttachData { + KDevice *parentDevice; + KInstalledDriver *installedDriver; +}; + +KDevice *deviceTreeRoot; +KMutex deviceTreeMutex; + +Array delayedDevices; +Array installedDrivers; + +#endif + +#ifdef IMPLEMENTATION + +void *ResolveKernelSymbol(const char *name, size_t nameBytes); + +KDevice *KDeviceCreate(const char *cDebugName, KDevice *parent, size_t bytes) { + if (bytes < sizeof(KDevice)) { + KernelPanic("KDeviceCreate - Device structure size is too small (less than KDevice).\n"); + } + + KDevice *device = (KDevice *) EsHeapAllocate(bytes, true, K_FIXED); + if (!device) return nullptr; + + device->parent = parent; + device->cDebugName = cDebugName; + device->handles = 2; // One handle for the creator, and another closed when the device is removed (by the parent). + + if (parent) { + KMutexAcquire(&deviceTreeMutex); + + if (!parent->children.Add(device)) { + EsHeapFree(device, bytes, K_FIXED); + device = nullptr; + } + + KMutexRelease(&deviceTreeMutex); + return device; + } else { + if (deviceTreeRoot) { + KernelPanic("KDeviceCreate - Root device already created.\n"); + } + + return (deviceTreeRoot = device); + } +} + +void DeviceDestroy(KDevice *device) { + device->children.Free(); + if (device->destroy) device->destroy(device); + EsHeapFree(device, 0, K_FIXED); +} + +void KDeviceDestroy(KDevice *device) { + KMutexAcquire(&deviceTreeMutex); + + device->handles = 0; + + if (device->children.Length()) { + KernelPanic("KDeviceDestroy - Device %x has children.\n", device); + } + + while (!device->handles && !device->children.Length()) { + device->parent->children.FindAndDeleteSwap(device, true /* fail if not found */); + KDevice *parent = device->parent; + DeviceDestroy(device); + device = parent; + } + + KMutexRelease(&deviceTreeMutex); +} + +void KDeviceOpenHandle(KDevice *device) { + KMutexAcquire(&deviceTreeMutex); + if (!device->handles) KernelPanic("KDeviceOpenHandle - Device %s has no handles.\n", device); + device->handles++; + KMutexRelease(&deviceTreeMutex); +} + +void KDeviceCloseHandle(KDevice *device) { + KMutexAcquire(&deviceTreeMutex); + + if (!device->handles) KernelPanic("KDeviceCloseHandle - Device %s has no handles.\n", device); + device->handles--; + + while (!device->handles && !device->children.Length()) { + device->parent->children.FindAndDeleteSwap(device, true /* fail if not found */); + KDevice *parent = device->parent; + DeviceDestroy(device); + device = parent; + } + + KMutexRelease(&deviceTreeMutex); +} + +void DeviceRemovedRecurse(KDevice *device) { + if (device->flags & K_DEVICE_REMOVED) KernelPanic("DeviceRemovedRecurse - Device %x already removed.\n", device); + device->flags |= K_DEVICE_REMOVED; + + for (uintptr_t i = 0; i < device->children.Length(); i++) { + KDevice *child = device->children[i]; + DeviceRemovedRecurse(child); + if (!child->handles) KernelPanic("DeviceRemovedRecurse - Child device %s has no handles.\n", child); + child->handles--; + if (child->handles || child->children.Length()) continue; + device->children.DeleteSwap(i); + DeviceDestroy(child); + i--; + } + + if (device->removed) { + device->removed(device); + } +} + +void KDeviceRemoved(KDevice *device) { + KMutexAcquire(&deviceTreeMutex); + DeviceRemovedRecurse(device); + KMutexRelease(&deviceTreeMutex); + KDeviceCloseHandle(device); +} + +const KDriver *DriverLoad(KInstalledDriver *installedDriver) { + static KMutex mutex = {}; + + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (installedDriver->loadedDriver) { + return installedDriver->loadedDriver; + } + + KDriver *driver = nullptr; + + char *driverVariable = nullptr; + size_t driverVariableBytes = 0; + + EsINIState s = {}; + s.buffer = installedDriver->config; + s.bytes = installedDriver->configBytes; + + while (EsINIParse(&s)) { + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("driver"))) { + driverVariable = s.value; + driverVariableBytes = s.valueBytes; + } + } + + char *buffer = (char *) EsHeapAllocate(K_MAX_PATH, true, K_FIXED); + if (!buffer) return nullptr; + EsDefer(EsHeapFree(buffer, K_MAX_PATH, K_FIXED)); + + KModule *module = (KModule *) EsHeapAllocate(sizeof(KModule), true, K_FIXED); + if (!module) return nullptr; + + module->path = buffer; + module->pathBytes = EsStringFormat(buffer, K_MAX_PATH, K_OS_FOLDER "/Modules/%s.ekm", + installedDriver->nameBytes, installedDriver->name); + module->resolveSymbol = ResolveKernelSymbol; + + EsError error = KLoadELFModule(module); + + if (error == ES_SUCCESS) { + KernelLog(LOG_INFO, "Modules", "module loaded", "Successfully loaded module '%s'.\n", + installedDriver->nameBytes, installedDriver->name); + + driver = (KDriver *) KFindSymbol(module, driverVariable, driverVariableBytes); + + if (!driver) { + KernelLog(LOG_ERROR, "Modules", "bad module", "DriverLoad - Could not find driver symbol in module '%s'.\n", + installedDriver->nameBytes, installedDriver->name); + } + } else { + KernelLog(LOG_ERROR, "Modules", "module load failure", "Could not load module '%s' (error = %d).\n", + installedDriver->nameBytes, installedDriver->name, error); + EsHeapFree(module, sizeof(KModule), K_FIXED); + } + + return (installedDriver->loadedDriver = driver); +} + +void DeviceAttach(DeviceAttachData attach) { + TS("DeviceAttach to %s\n", attach.installedDriver->nameBytes, attach.installedDriver->name); + + if (attach.parentDevice) { + KDeviceOpenHandle(attach.parentDevice); + } + + KMutexAcquire(&deviceTreeMutex); + + if (!attach.installedDriver->builtin && !fs.bootFileSystem) { + KernelLog(LOG_INFO, "Modules", "delayed device", "Delaying attach device to driver '%s' until boot file system mounted.\n", + attach.installedDriver->nameBytes, attach.installedDriver->name); + delayedDevices.Add(attach); + KMutexRelease(&deviceTreeMutex); + return; + } + + KernelLog(LOG_INFO, "Modules", "device attach", "Attaching device to driver '%s'.\n", + attach.installedDriver->nameBytes, attach.installedDriver->name); + + const KDriver *driver = DriverLoad(attach.installedDriver); + KMutexRelease(&deviceTreeMutex); + + if (driver && driver->attach) { + driver->attach(attach.parentDevice); + } + + if (attach.parentDevice) { + KDeviceCloseHandle(attach.parentDevice); + } +} + +bool KDeviceAttach(KDevice *parentDevice, const char *cName, KDriverIsImplementorCallback callback) { + size_t nameBytes = EsCStringLength(cName); + + for (uintptr_t i = installedDrivers.Length(); i > 0; i--) { + if (0 == EsStringCompareRaw(cName, nameBytes, installedDrivers[i - 1].parent, installedDrivers[i - 1].parentBytes) + && callback(&installedDrivers[i - 1], parentDevice)) { + DeviceAttach({ .parentDevice = parentDevice, .installedDriver = &installedDrivers[i - 1] }); + return true; + } + } + + return false; +} + +void KDeviceAttachAll(KDevice *parentDevice, const char *cName) { + size_t nameBytes = EsCStringLength(cName); + + for (uintptr_t i = 0; i < installedDrivers.Length(); i++) { + if (0 == EsStringCompareRaw(cName, nameBytes, installedDrivers[i].parent, installedDrivers[i].parentBytes)) { + DeviceAttach({ .parentDevice = parentDevice, .installedDriver = &installedDrivers[i] }); + } + } +} + +bool KDeviceAttachByName(KDevice *parentDevice, const char *cName) { + size_t nameBytes = EsCStringLength(cName); + + for (uintptr_t i = 0; i < installedDrivers.Length(); i++) { + if (0 == EsStringCompareRaw(cName, nameBytes, installedDrivers[i].name, installedDrivers[i].nameBytes)) { + DeviceAttach({ .parentDevice = parentDevice, .installedDriver = &installedDrivers[i] }); + return true; + } + } + + return false; +} + +void DeviceRootAttach(KDevice *parentDevice) { + // Load all the root drivers and create their devices. + + KDeviceAttachAll(KDeviceCreate("root", parentDevice, sizeof(KDevice)), "Root"); + + // Check we have found the drive from which we booted. + // TODO Decide the timeout. + + if (!KEventWait(&fs.foundBootFileSystemEvent, 10000)) { + KernelPanic("DeviceRootAttach - Could not find the boot file system.\n"); + } + + // Load any devices that were waiting for the boot file system to be loaded. + + for (uintptr_t i = 0; i < delayedDevices.Length(); i++) { + DeviceAttach(delayedDevices[i]); + KDeviceCloseHandle(delayedDevices[i].parentDevice); + } + + delayedDevices.Free(); +} + +KDriver driverRoot = { + .attach = DeviceRootAttach, +}; + +void DriversInitialise() { + // Add the builtin drivers to the database. + + for (uintptr_t i = 0; i < sizeof(builtinDrivers) / sizeof(builtinDrivers[0]); i++) { + installedDrivers.Add(builtinDrivers[i]); + } + + // Attach to the root device. + + DeviceAttach({ .parentDevice = nullptr, .installedDriver = &installedDrivers[0] }); +} + +void DriversDumpStateRecurse(KDevice *device) { + if (device->dumpState) { + device->dumpState(device); + } + + for (uintptr_t i = 0; i < device->children.Length(); i++) { + DriversDumpStateRecurse(device->children[i]); + } +} + +void DriversDumpState() { + KMutexAcquire(&deviceTreeMutex); + DriversDumpStateRecurse(deviceTreeRoot); + KMutexRelease(&deviceTreeMutex); +} + +void DriversShutdownRecurse(KDevice *device) { + for (uintptr_t i = 0; i < device->children.Length(); i++) { + DriversShutdownRecurse(device->children[i]); + } + + if (device->shutdown) { + device->shutdown(device); + } +} + +void DriversShutdown() { + KMutexAcquire(&deviceTreeMutex); + DriversShutdownRecurse(deviceTreeRoot); + KMutexRelease(&deviceTreeMutex); +} + +#endif diff --git a/kernel/elf.cpp b/kernel/elf.cpp new file mode 100644 index 0000000..53ef859 --- /dev/null +++ b/kernel/elf.cpp @@ -0,0 +1,422 @@ +// TODO Use a custom executable format? + +#define MEMORY_MAPPED_EXECUTABLES + +#ifndef IMPLEMENTATION + +struct ElfHeader { + uint32_t magicNumber; // 0x7F followed by 'ELF' + uint8_t bits; // 1 = 32 bit, 2 = 64 bit + uint8_t endianness; // 1 = LE, 2 = BE + uint8_t version1; + uint8_t abi; // 0 = System V + uint8_t _unused0[8]; + uint16_t type; // 1 = relocatable, 2 = executable, 3 = shared + uint16_t instructionSet; // 0x03 = x86, 0x28 = ARM, 0x3E = x86-64, 0xB7 = AArch64 + uint32_t version2; + +#ifdef ARCH_32 + uint32_t entry; + uint32_t programHeaderTable; + uint32_t sectionHeaderTable; + uint32_t flags; + uint16_t headerSize; + uint16_t programHeaderEntrySize; + uint16_t programHeaderEntries; + uint16_t sectionHeaderEntrySize; + uint16_t sectionHeaderEntries; + uint16_t sectionNameIndex; +#else + uint64_t entry; + uint64_t programHeaderTable; + uint64_t sectionHeaderTable; + uint32_t flags; + uint16_t headerSize; + uint16_t programHeaderEntrySize; + uint16_t programHeaderEntries; + uint16_t sectionHeaderEntrySize; + uint16_t sectionHeaderEntries; + uint16_t sectionNameIndex; +#endif +}; + +#ifdef ARCH_32 +struct ElfSectionHeader { + uint32_t name; + uint32_t type; + uint32_t flags; + uint32_t address; + uint32_t offset; + uint32_t size; + uint32_t link; + uint32_t info; + uint32_t align; + uint32_t entrySize; +}; + +struct ElfProgramHeader { + uint32_t type; // 0 = unused, 1 = load, 2 = dynamic, 3 = interp, 4 = note + uint32_t fileOffset; + uint32_t virtualAddress; + uint32_t _unused0; + uint32_t dataInFile; + uint32_t segmentSize; + uint32_t flags; // 1 = executable, 2 = writable, 4 = readable + uint32_t alignment; +}; + +struct ElfRelocation { + uint32_t offset; + uint32_t info; + int32_t addend; +}; + +struct ElfSymbol { + uint32_t name, value, size; + uint8_t info, _reserved1; + uint16_t sectionIndex; +}; +#else +struct ElfSectionHeader { + uint32_t name; // Offset into section header->sectionNameIndex. + uint32_t type; // 4 = rela + uint64_t flags; + uint64_t address; + uint64_t offset; + uint64_t size; + uint32_t link; + uint32_t info; + uint64_t align; + uint64_t entrySize; +}; + +struct ElfProgramHeader{ + uint32_t type; // 0 = unused, 1 = load, 2 = dynamic, 3 = interp, 4 = note + uint32_t flags; // 1 = executable, 2 = writable, 4 = readable + uint64_t fileOffset; + uint64_t virtualAddress; + uint64_t _unused0; + uint64_t dataInFile; + uint64_t segmentSize; + uint64_t alignment; +}; + +struct ElfRelocation { + uint64_t offset; + uint64_t info; + int64_t addend; +}; + +struct ElfSymbol { + uint32_t name; + uint8_t info, _reserved1; + uint16_t sectionIndex; + uint64_t value, size; +}; +#endif + +#else + +EsError KLoadELF(KNode *node, KLoadedExecutable *executable) { + Process *thisProcess = GetCurrentThread()->process; + + uintptr_t executableOffset = 0; + size_t fileSize = FSNodeGetTotalSize(node); + + { + BundleHeader header; + size_t bytesRead = FSFileReadSync(node, (uint8_t *) &header, 0, sizeof(BundleHeader), 0); + if (bytesRead != sizeof(BundleHeader)) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + + if (header.signature == BUNDLE_SIGNATURE + && header.fileCount < 0x100000 + && header.fileCount * sizeof(BundleFile) + sizeof(BundleHeader) < fileSize) { + if (!header.mapAddress) { + header.mapAddress = BUNDLE_FILE_MAP_ADDRESS; + } + +#ifdef ARCH_X86_64 + if (header.mapAddress > 0x8000000000000000UL || header.mapAddress < 0x1000 || fileSize > 0x1000000000000UL + || header.mapAddress & (K_PAGE_SIZE - 1)) { + return ES_ERROR_UNSUPPORTED_EXECUTABLE; + } +#else +#error Unimplemented. +#endif + + if (header.version != 1) { + return ES_ERROR_UNSUPPORTED_EXECUTABLE; + } + + // Map the bundle file. + + if (!MMMapFile(thisProcess->vmm, (FSFile *) node, + 0, fileSize, ES_MAP_OBJECT_READ_ONLY, + (uint8_t *) header.mapAddress)) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + // Look for the executable in the bundle. + +#ifdef ARCH_X86_64 + uint64_t name = CalculateCRC64(EsLiteral("Executable (x86_64)")); +#endif + + BundleFile *files = (BundleFile *) ((BundleHeader *) header.mapAddress + 1); + + bool found = false; + + for (uintptr_t i = 0; i < header.fileCount; i++) { + if (files[i].nameCRC64 == name) { + executableOffset = files[i].offset; + found = true; + break; + } + } + + if (executableOffset >= fileSize || !found) { + return ES_ERROR_UNSUPPORTED_EXECUTABLE; + } + } + } + + // EsPrint("executableOffset: %x\n", executableOffset); + + ElfHeader header; + size_t bytesRead = FSFileReadSync(node, (uint8_t *) &header, executableOffset, sizeof(ElfHeader), 0); + if (bytesRead != sizeof(ElfHeader)) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + + size_t programHeaderEntrySize = header.programHeaderEntrySize; + + if (header.magicNumber != 0x464C457F) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + if (header.bits != 2) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + if (header.endianness != 1) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + if (header.abi != 0) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + if (header.type != 2) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + if (header.instructionSet != 0x3E) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + + ElfProgramHeader *programHeaders = (ElfProgramHeader *) EsHeapAllocate(programHeaderEntrySize * header.programHeaderEntries, false, K_PAGED); + EsDefer(EsHeapFree(programHeaders, 0, K_PAGED)); + + bytesRead = FSFileReadSync(node, (uint8_t *) programHeaders, executableOffset + header.programHeaderTable, programHeaderEntrySize * header.programHeaderEntries, 0); + if (bytesRead != programHeaderEntrySize * header.programHeaderEntries) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + + for (uintptr_t i = 0; i < header.programHeaderEntries; i++) { + ElfProgramHeader *header = (ElfProgramHeader *) ((uint8_t *) programHeaders + programHeaderEntrySize * i); + + if (header->type == 1 /* PT_LOAD */) { +#ifdef ARCH_X86_64 + if (header->virtualAddress > 0x8000000000000000UL || header->virtualAddress < 0x1000 || header->segmentSize > 0x1000000000000UL) { + return ES_ERROR_UNSUPPORTED_EXECUTABLE; + } +#else +#error Unimplemented. +#endif + +#if 0 + EsPrint("FileOffset %x VirtualAddress %x SegmentSize %x DataInFile %x\n", + header->fileOffset, header->virtualAddress, header->segmentSize, header->dataInFile); +#endif + + void *success; + +#ifndef MEMORY_MAPPED_EXECUTABLES + success = MMStandardAllocate(thisProcess->vmm, RoundUp(header->segmentSize, K_PAGE_SIZE), ES_FLAGS_DEFAULT, + (uint8_t *) RoundDown(header->virtualAddress, K_PAGE_SIZE)); + + if (success) { + bytesRead = FSFileReadSync(node, (void *) header->virtualAddress, executableOffset + header->fileOffset, header->dataInFile, 0); + if (bytesRead != header->dataInFile) return ES_ERROR_UNSUPPORTED_EXECUTABLE; + } +#else + uintptr_t fileStart = RoundDown(header->virtualAddress, K_PAGE_SIZE); + uintptr_t fileOffset = RoundDown(header->fileOffset, K_PAGE_SIZE); + uintptr_t zeroStart = RoundUp(header->virtualAddress + header->dataInFile, K_PAGE_SIZE); + uintptr_t end = RoundUp(header->virtualAddress + header->segmentSize, K_PAGE_SIZE); + + // TODO This doesn't need to be all COPY_ON_WRITE. + + // EsPrint("MMMapFile - %x, %x, %x, %x\n", fileStart, fileOffset, zeroStart, end); + + success = MMMapFile(thisProcess->vmm, (FSFile *) node, + executableOffset + fileOffset, zeroStart - fileStart, + ES_MAP_OBJECT_COPY_ON_WRITE, + (uint8_t *) fileStart, end - zeroStart); + + if (success) { + uint8_t *from = (uint8_t *) header->virtualAddress + header->dataInFile; + EsMemoryZero(from, (uint8_t *) zeroStart - from); + } +#endif + + if (!success) return ES_ERROR_INSUFFICIENT_RESOURCES; + } else if (header->type == 7 /* PT_TLS */) { + executable->tlsImageStart = header->virtualAddress; + executable->tlsImageBytes = header->dataInFile; + executable->tlsBytes = header->segmentSize; + KernelLog(LOG_INFO, "ELF", "executable TLS", "Executable requests %d bytes of TLS, with %d bytes from the image at %x.\n", + executable->tlsBytes, executable->tlsImageBytes, executable->tlsImageStart); + } + } + + executable->startAddress = header.entry; + return ES_SUCCESS; +} + +uintptr_t KFindSymbol(KModule *module, const char *name, size_t nameBytes) { + uint8_t *buffer = module->buffer; + ElfHeader *header = (ElfHeader *) buffer; + + for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) { + ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i); + if (section->type != 2 /* SHT_SYMTAB */) continue; + + ElfSectionHeader *strings = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->link); + + for (uintptr_t i = 0; i < section->size / sizeof(ElfSymbol); i++) { + ElfSymbol *symbol = (ElfSymbol *) (buffer + section->offset + i * sizeof(ElfSymbol)); + if (!symbol->name) continue; + if (symbol->sectionIndex == 0 /* SHN_UNDEF */ || (symbol->info >> 4) != 1 /* STB_GLOBAL */) continue; + + uint8_t *symbolName = buffer + symbol->name + strings->offset; + + if (0 == EsStringCompareRaw(name, nameBytes, (const char *) symbolName, -1)) { + return symbol->value; + } + } + } + + return 0; +} + +uint8_t *modulesLocation = (uint8_t *) MM_MODULES_START; +KMutex modulesMutex; + +uint8_t *AllocateForModule(size_t size) { + KMutexAssertLocked(&modulesMutex); + if ((uintptr_t) modulesLocation + RoundUp(size, K_PAGE_SIZE) > MM_MODULES_START + MM_MODULES_SIZE) return nullptr; + uint8_t *buffer = (uint8_t *) MMStandardAllocate(kernelMMSpace, size, MM_REGION_FIXED, modulesLocation); + if (!buffer) return nullptr; + modulesLocation += RoundUp(size, K_PAGE_SIZE); + return (uint8_t *) buffer; +} + +EsError KLoadELFModule(KModule *module) { + KMutexAcquire(&modulesMutex); + EsDefer(KMutexRelease(&modulesMutex)); + + uint64_t fileFlags = ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND; + KNodeInformation node = FSNodeOpen(module->path, module->pathBytes, fileFlags); + if (node.error != ES_SUCCESS) return node.error; + + uint8_t *buffer = AllocateForModule(node.node->directoryEntry->totalSize); + module->buffer = buffer; + + { + // TODO Free module buffer on error. + + EsDefer(CloseHandleToObject(node.node, KERNEL_OBJECT_NODE, fileFlags)); + + if (!buffer) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + if (node.node->directoryEntry->totalSize != (size_t) FSFileReadSync(node.node, buffer, 0, node.node->directoryEntry->totalSize, 0)) { + return ES_ERROR_UNKNOWN; + } + } + + ElfHeader *header = (ElfHeader *) buffer; + uint8_t *sectionStringTable = buffer + ((ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * header->sectionNameIndex))->offset; + (void) sectionStringTable; + + for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) { + ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i); + if (section->type != 8 /* SHT_NOBITS */ || !section->size) continue; + uint8_t *memory = AllocateForModule(section->size); + if (!memory) return ES_ERROR_INSUFFICIENT_RESOURCES; // TODO Free allocations. + section->offset = memory - buffer; + } + + bool unresolvedSymbols = false; + + for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) { + ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i); + if (section->type != 2 /* SHT_SYMTAB */) continue; + + // EsPrint("%d: '%z' - symbol table\n", i, sectionStringTable + section->name); + ElfSectionHeader *strings = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->link); + + for (uintptr_t i = 0; i < section->size / sizeof(ElfSymbol); i++) { + ElfSymbol *symbol = (ElfSymbol *) (buffer + section->offset + i * sizeof(ElfSymbol)); + + uint8_t *name = buffer + symbol->name + strings->offset; + + if (symbol->sectionIndex == 0 /* SHN_UNDEF */) { + if (!symbol->name) continue; + + // TODO Check that EsCStringLength stays within bounds. + void *address = module->resolveSymbol((const char *) name, EsCStringLength((const char *) name)); + + if (!address) { + unresolvedSymbols = true; + } else { + symbol->value = (uintptr_t) address; + } + } else if (symbol->sectionIndex < header->sectionHeaderEntries) { + ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * symbol->sectionIndex); + symbol->value += (uintptr_t) buffer + section->offset; + } + + // EsPrint("'%z' -> %x\n", name, symbol->value); + } + } + + if (unresolvedSymbols) { + return ES_ERROR_COULD_NOT_RESOLVE_SYMBOL; + } + + for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) { + ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i); + if (section->type != 4 /* SHT_RELA */) continue; + + ElfSectionHeader *target = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->info); + ElfSectionHeader *symbols = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->link); + ElfSectionHeader *strings = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * symbols->link); + (void) strings; + + // EsPrint("%d: '%z' - relocation table (for %x)\n", i, sectionStringTable + section->name, target->offset); + + for (uintptr_t i = 0; i < section->size / sizeof(ElfRelocation); i++) { + ElfRelocation *relocation = (ElfRelocation *) (buffer + section->offset + i * sizeof(ElfRelocation)); + ElfSymbol *symbol = (ElfSymbol *) (buffer + symbols->offset + (relocation->info >> 32) * sizeof(ElfSymbol)); + uintptr_t offset = relocation->offset + target->offset, type = relocation->info & 0xFF; + // EsPrint("\t%d: %z (%x), %d, %x, %x\n", i, buffer + symbol->name + strings->offset, symbol->value, type, offset, relocation->addend); + + uintptr_t result = symbol->value + relocation->addend; + +#ifdef ARCH_X86_64 + if (type == 0) {} + else if (type == 10 /* R_X86_64_32 */) *((uint32_t *) (buffer + offset)) = result; + else if (type == 11 /* R_X86_64_32S */) *((uint32_t *) (buffer + offset)) = result; + else if (type == 1 /* R_X86_64_64 */) *((uint64_t *) (buffer + offset)) = result; + else if (type == 2 /* R_X86_64_PC32 */) *((uint32_t *) (buffer + offset)) = result - ((uint64_t) buffer + offset); + else if (type == 24 /* R_X86_64_PC64 */) *((uint64_t *) (buffer + offset)) = result - ((uint64_t) buffer + offset); + else if (type == 4 /* R_X86_64_PLT32 */) *((uint32_t *) (buffer + offset)) = result - ((uint64_t) buffer + offset); +#endif + else return ES_ERROR_UNSUPPORTED_FEATURE; + } + } + + KGetKernelVersionCallback getVersion = (KGetKernelVersionCallback) KFindSymbol(module, EsLiteral("GetKernelVersion")); + + if (!getVersion || getVersion() != KERNEL_VERSION) { + KernelLog(LOG_ERROR, "Modules", "invalid module kernel version", + "KLoadELFModule - Attempted to load module '%s' for invalid kernel version.\n", module->pathBytes, module->path); + return ES_ERROR_UNSUPPORTED_FEATURE; + } + + return ES_SUCCESS; +} + +#endif diff --git a/kernel/files.cpp b/kernel/files.cpp new file mode 100644 index 0000000..af433a0 --- /dev/null +++ b/kernel/files.cpp @@ -0,0 +1,1916 @@ +// TODO Features: +// - Handling errors creating files (prevent further file system operations). +// - Limiting the size of the directory/node cache. +// +// TODO Permissions: +// - Prevent modifications to directories without write permission. +// - Prevent launching executables without read permission. +// +// TODO Drivers: +// - Parsing GPT partition tables. +// - Get NTFS driver working again. +// +// TODO Allocate nodes/directory entries from arenas? +// TODO Check that the MODIFIED tracking is correct. + +#ifndef IMPLEMENTATION + +#define NODE_MAX_ACCESSORS (16777216) + +// KNode flags: +#define NODE_HAS_EXCLUSIVE_WRITER (1 << 0) +#define NODE_ENUMERATED_ALL_DIRECTORY_ENTRIES (1 << 1) +#define NODE_CREATED_ON_FILE_SYSTEM (1 << 2) +#define NODE_DELETED (1 << 3) +#define NODE_MODIFIED (1 << 4) +#define NODE_IN_CACHE_LIST (1 << 5) // Node has no handles and no directory entries, so it can be freed. + +// Modes for opening a node handle. +#define FS_NODE_OPEN_HANDLE_STANDARD (0) +#define FS_NODE_OPEN_HANDLE_FIRST (1) +#define FS_NODE_OPEN_HANDLE_DIRECTORY_TEMPORARY (2) + +struct FSDirectoryEntry : KNodeMetadata { + MMObjectCacheItem cacheItem; + AVLItem item; // item.key.longKey contains the entry's name. + struct FSDirectory *parent; // The directory containing this entry. + KNode *volatile node; // nullptr if the node hasn't been loaded. + char inlineName[16]; // Store the name of the entry inline if it is small enough. + // Followed by driver data. +}; + +struct FSDirectory : KNode { + AVLTree entries; + size_t entryCount; +}; + +struct FSFile : KNode { + int32_t countWrite /* negative indicates non-shared readers */, blockResize; + EsFileOffset fsFileSize; // Files are lazily resized; this is the size the file system thinks the file is. + EsFileOffset fsZeroAfter; // This is the smallest size the file has reached without telling the file system. + CCSpace cache; + KWriterLock resizeLock; // Take exclusive for resizing or flushing. +}; + +EsError FSNodeOpenHandle(KNode *node, uint32_t flags, uint8_t mode); +void FSNodeCloseHandle(KNode *node, uint32_t flags); +EsError FSNodeDelete(KNode *node); +EsError FSNodeMove(KNode *node, KNode *destination, const char *newName, size_t nameNameBytes); +EsError FSFileResize(KNode *node, EsFileOffset newSizeBytes); +ptrdiff_t FSDirectoryEnumerateChildren(KNode *node, K_USER_BUFFER EsDirectoryChild *buffer, size_t bufferSize); +EsError FSFileControl(KNode *node, uint32_t flags); +bool FSTrimCachedNode(MMObjectCache *); +bool FSTrimCachedDirectoryEntry(MMObjectCache *); + +struct { + KWriterLock fileSystemsLock; + + KFileSystem *bootFileSystem; + KEvent foundBootFileSystemEvent; + + KSpinlock updateNodeHandles; // Also used for node/directory entry cache operations. + + bool shutdown; + + volatile uint64_t totalHandleCount; + volatile uint64_t fileSystemsUnmounting; + KEvent fileSystemUnmounted; +} fs = { + .fileSystemUnmounted = { .autoReset = true }, +}; + +#else + +EsFileOffset FSNodeGetTotalSize(KNode *node) { + return node->directoryEntry->totalSize; +} + +char *FSNodeGetName(KNode *node, size_t *bytes) { + KWriterLockAssertLocked(&node->writerLock); + *bytes = node->directoryEntry->item.key.longKeyBytes; + return (char *) node->directoryEntry->item.key.longKey; +} + +bool FSCheckPathForIllegalCharacters(const char *path, size_t pathBytes) { + // Control ASCII characters are not allowed. + + for (uintptr_t i = 0; i < pathBytes; i++) { + char c = path[i]; + + if ((c >= 0x00 && c < 0x20) || c == 0x7F) { + return false; + } + } + + // Invalid UTF-8 sequences are not allowed. + // Surrogate characters are fine. + // Overlong sequences are fine, except for ASCII characters. + + if (!EsUTF8IsValid(path, pathBytes)) { + return false; + } + + return true; +} + +////////////////////////////////////////// +// Accessing files. +////////////////////////////////////////// + +EsError FSReadIntoCache(CCSpace *fileCache, void *buffer, EsFileOffset offset, EsFileOffset count) { + FSFile *node = EsContainerOf(FSFile, cache, fileCache); + + KWriterLockTake(&node->writerLock, K_LOCK_SHARED); + EsDefer(KWriterLockReturn(&node->writerLock, K_LOCK_SHARED)); + + if (node->flags & NODE_DELETED) { + return ES_ERROR_NODE_DELETED; + } + + if (offset > node->directoryEntry->totalSize) { + KernelPanic("FSReadIntoCache - Read out of bounds in node %x.\n", node); + } + + if (node->fsZeroAfter < offset + count) { + if (offset >= node->fsZeroAfter) { + EsMemoryZero(buffer, count); + } else { + if (~node->flags & NODE_CREATED_ON_FILE_SYSTEM) { + KernelPanic("FSReadIntoCache - Node %x has not been created on the file system.\n", node); + } + + size_t realBytes = node->fsZeroAfter - offset, fakeBytes = count - realBytes; + EsMemoryZero((uint8_t *) buffer + realBytes, fakeBytes); + count = node->fileSystem->read(node, buffer, offset, realBytes); + } + } else { + if (~node->flags & NODE_CREATED_ON_FILE_SYSTEM) { + KernelPanic("FSReadIntoCache - Node %x has not been created on the file system.\n", node); + } + + count = node->fileSystem->read(node, buffer, offset, count); + + if (ES_CHECK_ERROR(count)) { + node->error = count; + } + } + + return ES_CHECK_ERROR(count) ? count : ES_SUCCESS; +} + +EsError FSFileCreateAndResizeOnFileSystem(FSFile *node, EsFileOffset fileSize) { + KWriterLockAssertExclusive(&node->writerLock); + + FSDirectoryEntry *entry = node->directoryEntry; + + if (node->flags & NODE_DELETED) { + return ES_ERROR_NODE_DELETED; + } + + if (~node->flags & NODE_CREATED_ON_FILE_SYSTEM) { + KWriterLockTake(&entry->parent->writerLock, K_LOCK_EXCLUSIVE); + + EsError error = ES_SUCCESS; + + if (~entry->parent->flags & NODE_CREATED_ON_FILE_SYSTEM) { + // TODO Get the node error mark? + error = ES_ERROR_UNKNOWN; + } + + if (error == ES_SUCCESS) { + error = node->fileSystem->create((const char *) entry->item.key.longKey, entry->item.key.longKeyBytes, + ES_NODE_FILE, entry->parent, node, entry + 1); + } + + if (error == ES_SUCCESS) { + __sync_fetch_and_or(&node->flags, NODE_CREATED_ON_FILE_SYSTEM); + } else { + // TODO Mark the node with an error. + } + + KWriterLockReturn(&entry->parent->writerLock, K_LOCK_EXCLUSIVE); + + if (error != ES_SUCCESS) { + return error; + } + } + + if (node->fsFileSize != fileSize || node->fsZeroAfter != fileSize) { + // Resize the file on the file system to match the cache size. + EsError error = ES_ERROR_UNKNOWN; + KWriterLockTake(&entry->parent->writerLock, K_LOCK_EXCLUSIVE); + + if (node->fsZeroAfter != node->fsFileSize) { + // TODO Combined truncate-and-grow file operation. + node->fsFileSize = node->fileSystem->resize(node, node->fsZeroAfter, &error); + } + + // TODO Hint about where to zero upto - since we'll likely be about to write over the sectors! + node->fsFileSize = node->fileSystem->resize(node, fileSize, &error); + KWriterLockReturn(&entry->parent->writerLock, K_LOCK_EXCLUSIVE); + + if (node->fsFileSize != fileSize) { + return ES_ERROR_COULD_NOT_RESIZE_FILE; + } + + node->fsZeroAfter = fileSize; + } + + return ES_SUCCESS; +} + +EsError FSWriteFromCache(CCSpace *fileCache, const void *buffer, EsFileOffset offset, EsFileOffset count) { + FSFile *node = EsContainerOf(FSFile, cache, fileCache); + + KWriterLockTake(&node->writerLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&node->writerLock, K_LOCK_EXCLUSIVE)); + + FSDirectoryEntry *entry = node->directoryEntry; + volatile EsFileOffset fileSize = entry->totalSize; + + EsError error = FSFileCreateAndResizeOnFileSystem(node, fileSize); + + if (error != ES_SUCCESS) { + return error; + } + + if (offset > fileSize) { + KernelPanic("VFSWriteFromCache - Write out of bounds in node %x.\n", node); + } + + if (count > fileSize - offset) { + count = fileSize - offset; + } + + if (node->flags & NODE_DELETED) { + return ES_ERROR_NODE_DELETED; + } + + count = node->fileSystem->write(node, buffer, offset, count); + + if (ES_CHECK_ERROR(count)) { + node->error = count; + } + + return ES_CHECK_ERROR(count) ? count : ES_SUCCESS; +} + +const CCSpaceCallbacks fsFileCacheCallbacks = { + .readInto = FSReadIntoCache, + .writeFrom = FSWriteFromCache, +}; + +ptrdiff_t FSFileReadSync(KNode *node, K_USER_BUFFER void *buffer, EsFileOffset offset, EsFileOffset bytes, uint32_t accessFlags) { + if (fs.shutdown) KernelPanic("FSFileReadSync - Attempting to read from a file after FSShutdown called.\n"); + + FSFile *file = (FSFile *) node; + KWriterLockTake(&file->resizeLock, K_LOCK_SHARED); + EsDefer(KWriterLockReturn(&file->resizeLock, K_LOCK_SHARED)); + + if (offset > file->directoryEntry->totalSize) return ES_ERROR_ACCESS_NOT_WITHIN_FILE_BOUNDS; + if (bytes > file->directoryEntry->totalSize - offset) bytes = file->directoryEntry->totalSize - offset; + if (!bytes) return 0; + + EsError error = CCSpaceAccess(&file->cache, buffer, offset, bytes, + CC_ACCESS_READ | ((accessFlags & FS_FILE_ACCESS_USER_BUFFER_MAPPED) ? CC_ACCESS_USER_BUFFER_MAPPED : 0)); + return error == ES_SUCCESS ? bytes : error; +} + +void _FSFileResize(FSFile *file, EsFileOffset newSize) { + KWriterLockAssertExclusive(&file->resizeLock); + + EsFileOffsetDifference delta = newSize - file->directoryEntry->totalSize; + + if (!delta) { + return; + } + + if (delta < 0) { + // Truncate the space first, so that any pending write-backs past this point can complete. + CCSpaceTruncate(&file->cache, newSize); + } + + // No more writes-back can be issued past newSize until the resize lock is returned. Since: + // - CCSpaceTruncate waits for any queued writes past newSize to finish. + // - FSFileWriteSync waits for shared access on the resize lock before it can queue any more writes. + // This means we are safe to possible decrease sizing information. + + if (newSize < file->fsZeroAfter) { + file->fsZeroAfter = newSize; + } + + // Take the move lock before we write to the directoryEntry, + // because FSNodeMove needs to also update ancestors with the file's size when it is moved. + KMutexAcquire(&file->fileSystem->moveMutex); + + // We'll get the filesystem to resize the file during write-back. + file->directoryEntry->totalSize = newSize; + + KNode *ancestor = file->directoryEntry->parent; + + while (ancestor) { + __sync_fetch_and_or(&ancestor->flags, NODE_MODIFIED); + ancestor->directoryEntry->totalSize += delta; + ancestor = ancestor->directoryEntry->parent; + } + + __sync_fetch_and_or(&file->flags, NODE_MODIFIED); + + KMutexRelease(&file->fileSystem->moveMutex); +} + +EsError FSFileResize(KNode *node, EsFileOffset newSize) { + if (fs.shutdown) KernelPanic("FSFileResize - Attempting to resize a file after FSShutdown called.\n"); + + if (newSize > 1UL << 60) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + if (node->directoryEntry->type != ES_NODE_FILE) { + KernelPanic("FSFileResize - Node %x is not a file.\n", node); + } + + FSFile *file = (FSFile *) node; + EsError error = ES_SUCCESS; + KWriterLockTake(&file->resizeLock, K_LOCK_EXCLUSIVE); + + if (file->blockResize) { + error = ES_ERROR_FILE_IN_EXCLUSIVE_USE; + } else if (!file->fileSystem->resize) { + error = ES_ERROR_FILE_ON_READ_ONLY_VOLUME; + } else { + _FSFileResize(file, newSize); + } + + KWriterLockReturn(&file->resizeLock, K_LOCK_EXCLUSIVE); + return error; +} + +ptrdiff_t FSFileWriteSync(KNode *node, K_USER_BUFFER const void *buffer, EsFileOffset offset, EsFileOffset bytes, uint32_t flags) { + if (fs.shutdown) KernelPanic("FSFileWriteSync - Attempting to write to a file after FSShutdown called.\n"); + + if (offset + bytes > node->directoryEntry->totalSize) { + if (ES_SUCCESS != FSFileResize(node, offset + bytes)) { + return ES_ERROR_COULD_NOT_RESIZE_FILE; + } + } + + FSFile *file = (FSFile *) node; + KWriterLockTake(&file->resizeLock, K_LOCK_SHARED); + EsDefer(KWriterLockReturn(&file->resizeLock, K_LOCK_SHARED)); + + if (!file->fileSystem->write) return ES_ERROR_FILE_ON_READ_ONLY_VOLUME; + if (offset > file->directoryEntry->totalSize) return ES_ERROR_ACCESS_NOT_WITHIN_FILE_BOUNDS; + if (bytes > file->directoryEntry->totalSize - offset) bytes = file->directoryEntry->totalSize - offset; + if (!bytes) return 0; + + EsError error = CCSpaceAccess(&file->cache, (void *) buffer, offset, bytes, + CC_ACCESS_WRITE | ((flags & FS_FILE_ACCESS_USER_BUFFER_MAPPED) ? CC_ACCESS_USER_BUFFER_MAPPED : 0)); + __sync_fetch_and_or(&file->flags, NODE_MODIFIED); + return error == ES_SUCCESS ? bytes : error; +} + +EsError FSFileControl(KNode *node, uint32_t flags) { + FSFile *file = (FSFile *) node; + + if (flags & ES_FILE_CONTROL_FLUSH) { + KWriterLockTake(&file->resizeLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&file->resizeLock, K_LOCK_EXCLUSIVE)); + + CCSpaceFlush(&file->cache); + + KWriterLockTake(&file->writerLock, K_LOCK_EXCLUSIVE); + EsDefer(KWriterLockReturn(&file->writerLock, K_LOCK_EXCLUSIVE)); + + __sync_fetch_and_and(&file->flags, ~NODE_MODIFIED); + + EsError error = FSFileCreateAndResizeOnFileSystem(file, file->directoryEntry->totalSize); + if (error != ES_SUCCESS) return error; + + if (file->fileSystem->sync) { + // TODO Should we also sync the parent? + FSDirectory *parent = file->directoryEntry->parent; + + if (parent) KWriterLockTake(&parent->writerLock, K_LOCK_EXCLUSIVE); + file->fileSystem->sync(parent, file); + if (parent) KWriterLockReturn(&parent->writerLock, K_LOCK_EXCLUSIVE); + } + + if (file->error != ES_SUCCESS) { + EsError error = file->error; + file->error = ES_SUCCESS; + return error; + } + } + + return ES_SUCCESS; +} + +////////////////////////////////////////// +// Directories. +////////////////////////////////////////// + +EsError FSNodeDelete(KNode *node) { + if (fs.shutdown) KernelPanic("FSNodeDelete - Attempting to delete a file after FSShutdown called.\n"); + + EsError error = ES_SUCCESS; + + FSDirectoryEntry *entry = node->directoryEntry; + FSDirectory *parent = entry->parent; + FSFile *file = entry->type == ES_NODE_FILE ? (FSFile *) node : nullptr; + + if (!parent) return ES_ERROR_FILE_PERMISSION_NOT_GRANTED; + + // Open a handle to the parent, so that if its directory entry count drops to zero after the operation, + // it is put on the node cache list when the handle is closed. + + if (ES_SUCCESS != FSNodeOpenHandle(parent, ES_FLAGS_DEFAULT, FS_NODE_OPEN_HANDLE_DIRECTORY_TEMPORARY)) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + EsDefer(FSNodeCloseHandle(parent, ES_FLAGS_DEFAULT)); + + if (file) { + KWriterLockTake(&file->resizeLock, K_LOCK_EXCLUSIVE); + + if (file->blockResize) { + KWriterLockReturn(&file->resizeLock, K_LOCK_EXCLUSIVE); + return ES_ERROR_FILE_IN_EXCLUSIVE_USE; + } + + _FSFileResize(file, 0); + } + + KWriterLockTake(&node->writerLock, K_LOCK_EXCLUSIVE); + KWriterLockTake(&parent->writerLock, K_LOCK_EXCLUSIVE); + + if (node->flags & NODE_DELETED) { + error = ES_ERROR_NODE_DELETED; + } else if (entry->type == ES_NODE_DIRECTORY && ((FSDirectory *) node)->entryCount) { + error = ES_ERROR_DIRECTORY_NOT_EMPTY; + } else if (!node->fileSystem->remove) { + error = ES_ERROR_FILE_ON_READ_ONLY_VOLUME; + } else if (node->flags & NODE_CREATED_ON_FILE_SYSTEM) { + error = node->fileSystem->remove(parent, node); + } + + if (error == ES_SUCCESS) { + __sync_fetch_and_or(&node->flags, NODE_DELETED); + TreeRemove(&parent->entries, &entry->item); + parent->entryCount--; + } + + __sync_fetch_and_or(&parent->flags, NODE_MODIFIED); + KWriterLockReturn(&parent->writerLock, K_LOCK_EXCLUSIVE); + KWriterLockReturn(&node->writerLock, K_LOCK_EXCLUSIVE); + if (file) KWriterLockReturn(&file->resizeLock, K_LOCK_EXCLUSIVE); + + return error; +} + +EsError FSNodeMove(KNode *node, KNode *_newParent, const char *newName, size_t newNameBytes) { + if (fs.shutdown) KernelPanic("FSNodeMove - Attempting to move a file after FSShutdown called.\n"); + + if (!FSCheckPathForIllegalCharacters(newName, newNameBytes)) { + return ES_ERROR_ILLEGAL_PATH; + } + + FSDirectoryEntry *entry = node->directoryEntry; + FSDirectory *newParent = (FSDirectory *) _newParent; + FSDirectory *oldParent = entry->parent; + + // Check the move is valid. + + if (newParent->directoryEntry->type != ES_NODE_DIRECTORY) { + return ES_ERROR_TARGET_INVALID_TYPE; + } + + if (!oldParent || oldParent->fileSystem != newParent->fileSystem || oldParent->fileSystem != node->fileSystem) { + return ES_ERROR_VOLUME_MISMATCH; + } + + if (!newNameBytes || newNameBytes > ES_MAX_DIRECTORY_CHILD_NAME_LENGTH) { + return ES_ERROR_INVALID_NAME; + } + + for (uintptr_t i = 0; i < newNameBytes; i++) { + if (newName[i] == '/') { + return ES_ERROR_INVALID_NAME; + } + } + + if (!node->fileSystem->move) { + return ES_ERROR_FILE_ON_READ_ONLY_VOLUME; + } + + // Open a handle to the parent, so that if its directory entry count drops to zero after the operation, + // it is put on the node cache list when the handle is closed. + + if (ES_SUCCESS != FSNodeOpenHandle(oldParent, ES_FLAGS_DEFAULT, FS_NODE_OPEN_HANDLE_DIRECTORY_TEMPORARY)) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + EsDefer(FSNodeCloseHandle(oldParent, ES_FLAGS_DEFAULT)); + + EsError error = ES_SUCCESS; + bool alreadyExists = false; + void *newKeyBuffer = nullptr; + + KWriterLock *locks[] = { &node->writerLock, &oldParent->writerLock, &newParent->writerLock }; + KWriterLockTakeMultiple(locks, oldParent == newParent ? 2 : 3, K_LOCK_EXCLUSIVE); + + KMutexAcquire(&node->fileSystem->moveMutex); + EsDefer(KMutexRelease(&node->fileSystem->moveMutex)); + + KNode *newAncestor = newParent, *oldAncestor; + + while (newAncestor) { + if (newAncestor == node) { + // We are trying to move this node into a folder within itself. + error = ES_ERROR_TARGET_WITHIN_SOURCE; + goto fail; + } + + newAncestor = newAncestor->directoryEntry->parent; + } + + if ((node->flags | newParent->flags) & NODE_DELETED) { + error = ES_ERROR_NODE_DELETED; + goto fail; + } + + // Check a node with the same name doesn't already exist in the new directory. + + alreadyExists = TreeFind(&newParent->entries, MakeLongKey(newName, newNameBytes), TREE_SEARCH_EXACT); + + if (!alreadyExists && (~newParent->flags & NODE_ENUMERATED_ALL_DIRECTORY_ENTRIES)) { + // The entry is not cached; load it from the file system. + node->fileSystem->scan(newName, newNameBytes, newParent); + alreadyExists = TreeFind(&newParent->entries, MakeLongKey(newName, newNameBytes), TREE_SEARCH_EXACT); + } + + if (alreadyExists) { + error = ES_ERROR_FILE_ALREADY_EXISTS; + goto fail; + } + + // Allocate a buffer for the new key before we try to do anything permanent... + + newKeyBuffer = nullptr; + + if (newNameBytes > sizeof(entry->inlineName)) { + newKeyBuffer = EsHeapAllocate(newNameBytes, false, K_FIXED); + + if (!newKeyBuffer) { + error = ES_ERROR_INSUFFICIENT_RESOURCES; + goto fail; + } + } + + // Move the node on the file system, if it has been created. + + if (entry->node && (entry->node->flags & NODE_CREATED_ON_FILE_SYSTEM)) { + error = node->fileSystem->move(oldParent, node, newParent, newName, newNameBytes); + + if (error != ES_SUCCESS) { + goto fail; + } + } + + // Update the node's parent in our cache. + + entry->parent = newParent; + + TreeRemove(&oldParent->entries, &entry->item); + + entry->item.key.longKey = newKeyBuffer ?: entry->inlineName; + EsMemoryCopy(entry->item.key.longKey, newName, newNameBytes); + entry->item.key.longKeyBytes = newNameBytes; + + TreeInsert(&newParent->entries, &entry->item, entry, entry->item.key); + + oldParent->entryCount--; + + if (oldParent->directoryEntry->directoryChildren != ES_DIRECTORY_CHILDREN_UNKNOWN) { + oldParent->directoryEntry->directoryChildren--; + } + + newParent->entryCount++; + + if (newParent->directoryEntry->directoryChildren != ES_DIRECTORY_CHILDREN_UNKNOWN) { + newParent->directoryEntry->directoryChildren++; + } + + // Move the size of the node from the old to the new ancestors. + + oldAncestor = oldParent; + + while (oldAncestor) { + oldAncestor->directoryEntry->totalSize -= entry->totalSize; + oldAncestor = oldAncestor->directoryEntry->parent; + } + + newAncestor = newParent; + + while (newAncestor) { + newAncestor->directoryEntry->totalSize += entry->totalSize; + newAncestor = newAncestor->directoryEntry->parent; + } + + fail:; + + if (error != ES_SUCCESS) { + if (newKeyBuffer) { + EsHeapFree(newKeyBuffer, newNameBytes, K_FIXED); + } + } + + if (oldParent != newParent) { + KWriterLockReturn(&oldParent->writerLock, K_LOCK_EXCLUSIVE); + } + + __sync_fetch_and_or(&node->flags, NODE_MODIFIED); + __sync_fetch_and_or(&newParent->flags, NODE_MODIFIED); + __sync_fetch_and_or(&oldParent->flags, NODE_MODIFIED); + + KWriterLockReturn(&newParent->writerLock, K_LOCK_EXCLUSIVE); + KWriterLockReturn(&node->writerLock, K_LOCK_EXCLUSIVE); + + return error; +} + +void _FSDirectoryEnumerateChildrenVisit(AVLItem *item, K_USER_BUFFER EsDirectoryChild *buffer, size_t bufferSize, uintptr_t *position) { + if (!item || *position == bufferSize) { + return; + } + + FSDirectoryEntry *entry = item->thisItem; + EsDirectoryChild *output = buffer + *position; + *position = *position + 1; + + if (entry->node && (entry->node->flags & NODE_DELETED)) { + KernelPanic("_FSDirectoryEnumerateChildrenVisit - Deleted node %x found in directory tree.\n"); + } + + size_t nameBytes = entry->item.key.longKeyBytes > ES_MAX_DIRECTORY_CHILD_NAME_LENGTH ? ES_MAX_DIRECTORY_CHILD_NAME_LENGTH : entry->item.key.longKeyBytes; + EsMemoryCopy(output->name, entry->item.key.longKey, nameBytes); + output->type = entry->type; + output->fileSize = entry->totalSize; + output->directoryChildren = entry->directoryChildren; + output->nameBytes = nameBytes; + + _FSDirectoryEnumerateChildrenVisit(item->children[0], buffer, bufferSize, position); + _FSDirectoryEnumerateChildrenVisit(item->children[1], buffer, bufferSize, position); +} + +ptrdiff_t FSDirectoryEnumerateChildren(KNode *node, K_USER_BUFFER EsDirectoryChild *buffer, size_t bufferSize) { + // uint64_t start = ProcessorReadTimeStamp(); + + if (node->directoryEntry->type != ES_NODE_DIRECTORY) { + KernelPanic("FSDirectoryEnumerateChildren - Node %x is not a directory.\n", node); + } + + FSDirectory *directory = (FSDirectory *) node; + + // I think it's safe to modify the user's buffer with this lock. + KWriterLockTake(&directory->writerLock, K_LOCK_EXCLUSIVE); + + if (~directory->flags & NODE_ENUMERATED_ALL_DIRECTORY_ENTRIES) { + EsError error = directory->fileSystem->enumerate(directory); + + if (error != ES_SUCCESS) { + KWriterLockReturn(&directory->writerLock, K_LOCK_EXCLUSIVE); + return error; + } + + __sync_fetch_and_or(&directory->flags, NODE_ENUMERATED_ALL_DIRECTORY_ENTRIES); + directory->directoryEntry->directoryChildren = directory->entryCount; + } + + uintptr_t position = 0; + _FSDirectoryEnumerateChildrenVisit(directory->entries.root, buffer, bufferSize, &position); + + KWriterLockReturn(&directory->writerLock, K_LOCK_EXCLUSIVE); + + // uint64_t end = ProcessorReadTimeStamp(); + // EsPrint("FSDirectoryEnumerateChildren took %dmcs for %d items.\n", (end - start) / KGetTimeStampTicksPerUs(), position); + + return position; +} + +void FSNodeFree(KNode *node) { + FSDirectoryEntry *entry = node->directoryEntry; + + if (entry->node != node) { + KernelPanic("FSNodeFree - FSDirectoryEntry node mismatch for node %x.\n", node); + } else if (node->flags & NODE_IN_CACHE_LIST) { + KernelPanic("FSNodeFree - Node %x is in the cache list.\n", node); + } + + if (entry->type == ES_NODE_FILE) { + CCSpaceDestroy(&((FSFile *) node)->cache); + } else if (entry->type == ES_NODE_DIRECTORY) { + if (((FSDirectory *) node)->entries.root) { + KernelPanic("FSNodeFree - Directory %x still had items in its tree.\n", node); + } + } + + if (node->driverNode) { + node->fileSystem->close(node); + } + + // EsPrint("Freeing node with name '%s'...\n", entry->item.key.longKeyBytes, entry->item.key.longKey); + + bool deleted = node->flags & NODE_DELETED; + + KFileSystem *fileSystem = node->fileSystem; + EsHeapFree(node, entry->type == ES_NODE_DIRECTORY ? sizeof(FSDirectory) : sizeof(FSFile), K_FIXED); + + if (!deleted) { + KSpinlockAcquire(&fs.updateNodeHandles); + MMObjectCacheInsert(&fileSystem->cachedDirectoryEntries, &entry->cacheItem); + entry->node = nullptr; + entry->removingNodeFromCache = false; + KSpinlockRelease(&fs.updateNodeHandles); + } else { + // The node has been deleted, and we're about to deallocate the directory entry anyway. + // See FSNodeCloseHandle. + } +} + +void FSNodeScanAndLoadComplete(KNode *node, bool success) { + if (success) { + if (node->flags & NODE_IN_CACHE_LIST) { + KernelPanic("FSNodeScanAndLoadComplete - Node %x is already in the cache list.\n", node); + } else if (node->directoryEntry->type == ES_NODE_DIRECTORY && ((FSDirectory *) node)->entryCount) { + KernelPanic("FSNodeScanAndLoadComplete - Node %x has entries.\n", node); + } + + // The driver just scanned and loaded the node. + // Put it in the cache list; this is similar to what's done in FSNodeCreate. + // This is because we haven't opened any handles to the node yet, + // so for correctness it must be on the cache list. + // However, we do not expect it to be freed (although it would not be a problem if it were), + // since the parent's writer lock should be taken. + MMObjectCacheInsert(&node->fileSystem->cachedNodes, &node->cacheItem); + __sync_fetch_and_or(&node->flags, NODE_IN_CACHE_LIST); + } else { + FSNodeFree(node); + } +} + +void FSDirectoryEntryFree(FSDirectoryEntry *entry) { + if (entry->cacheItem.previous || entry->cacheItem.next) { + KernelPanic("FSDirectoryEntryFree - Entry %x is in cache.\n", entry); +#ifdef TREE_VALIDATE + } else if (entry->item.tree) { + KernelPanic("FSDirectoryEntryFree - Entry %x is in parent's tree.\n", entry); +#endif + } + + // EsPrint("Freeing directory entry with name '%s'...\n", entry->item.key.longKeyBytes, entry->item.key.longKey); + + if (entry->item.key.longKey != entry->inlineName) { + EsHeapFree((void *) entry->item.key.longKey, entry->item.key.longKeyBytes, K_FIXED); + } + + EsHeapFree(entry, 0, K_FIXED); +} + +EsError FSNodeCreate(FSDirectory *parent, const char *name, size_t nameBytes, EsNodeType type) { + KWriterLockAssertExclusive(&parent->writerLock); + KFileSystem *fileSystem = parent->fileSystem; + + if (!fileSystem->create) { + // Read-only file system. + return ES_ERROR_FILE_ON_READ_ONLY_VOLUME; + } + + KNodeMetadata metadata = {}; + metadata.type = type; + + KNode *node; + EsError error = FSDirectoryEntryFound(parent, &metadata, nullptr, name, nameBytes, false, &node); + if (error != ES_SUCCESS) return error; + + if (parent->directoryEntry->directoryChildren != ES_DIRECTORY_CHILDREN_UNKNOWN) { + parent->directoryEntry->directoryChildren++; + } + + __sync_fetch_and_or(&parent->flags, NODE_MODIFIED); + __sync_fetch_and_or(&node->flags, NODE_MODIFIED); + + // Only create directories immediately; files are created in FSWriteFromCache. + + if (type != ES_NODE_FILE) { + error = fileSystem->create(name, nameBytes, type, parent, node, node->directoryEntry + 1); + + if (error == ES_SUCCESS) { + __sync_fetch_and_or(&node->flags, NODE_CREATED_ON_FILE_SYSTEM); + } else { + // TODO Mark the node with an error. + } + } + + // Put the node onto the object cache list. + // Since the parent directory is locked, it should stay around for long enough to be immediately found FSNodeTraverseLayer. + if (node->flags & NODE_IN_CACHE_LIST) KernelPanic("FSNodeCreate - Node %x is already in the cache list.\n", node); + MMObjectCacheInsert(&node->fileSystem->cachedNodes, &node->cacheItem); + __sync_fetch_and_or(&node->flags, NODE_IN_CACHE_LIST); + + return ES_SUCCESS; +} + +EsError FSDirectoryEntryAllocateNode(FSDirectoryEntry *entry, KFileSystem *fileSystem, bool createdOnFileSystem, bool inDirectoryEntryCache) { + { + KSpinlockAcquire(&fs.updateNodeHandles); + EsDefer(KSpinlockRelease(&fs.updateNodeHandles)); + + if (entry->removingThisFromCache) { + if (!inDirectoryEntryCache) { + KernelPanic("FSDirectoryEntryAllocateNode - Entry %x is being removed from the cache, " + "but the caller did not expect it to have ever been in the cache.\n", entry); + } + + return ES_ERROR_DIRECTORY_ENTRY_BEING_REMOVED; + } + + if (inDirectoryEntryCache) { + MMObjectCacheRemove(&fileSystem->cachedDirectoryEntries, &entry->cacheItem); + } + } + + KNode *node = (KNode *) EsHeapAllocate(entry->type == ES_NODE_DIRECTORY ? sizeof(FSDirectory) : sizeof(FSFile), true, K_FIXED); + + if (!node) { + MMObjectCacheInsert(&fileSystem->cachedDirectoryEntries, &entry->cacheItem); + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + if (entry->type == ES_NODE_DIRECTORY) { + FSDirectory *directory = (FSDirectory *) node; + directory->entries.longKeys = true; + + if (!createdOnFileSystem) { + // We just created the directory, so we've definitely got all the entries. + directory->flags |= NODE_ENUMERATED_ALL_DIRECTORY_ENTRIES; + } + } else if (entry->type == ES_NODE_FILE) { + FSFile *file = (FSFile *) node; + file->fsFileSize = entry->totalSize; + file->fsZeroAfter = entry->totalSize; + file->cache.callbacks = &fsFileCacheCallbacks; + + if (!CCSpaceInitialise(&file->cache)) { + MMObjectCacheInsert(&fileSystem->cachedDirectoryEntries, &entry->cacheItem); + EsHeapFree(node, 0, K_FIXED); + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + } + + static uint64_t nextNodeID = 1; + node->id = __sync_fetch_and_add(&nextNodeID, 1); + + // TODO On file systems that support it, use their stable unique ID for the node. + // - What happens with delay-created files? + // - This ID should be unique among all volumes. + // - Maybe this should be a separate ID? + + if (createdOnFileSystem) { + node->flags |= NODE_CREATED_ON_FILE_SYSTEM; + } + + node->directoryEntry = entry; + node->fileSystem = fileSystem; + node->error = ES_SUCCESS; + entry->node = node; + return ES_SUCCESS; +} + +EsError FSDirectoryEntryFound(KNode *_parent, KNodeMetadata *metadata, + const void *driverData, const void *name, size_t nameBytes, + bool update, KNode **node) { + FSDirectory *parent = (FSDirectory *) _parent; + size_t driverDataBytes = parent->fileSystem->directoryEntryDataBytes; + KWriterLockAssertExclusive(&parent->writerLock); + + AVLItem *existingEntry = TreeFind(&parent->entries, MakeLongKey(name, nameBytes), TREE_SEARCH_EXACT); + + if (existingEntry) { + if (!driverData) { + KernelPanic("FSDirectoryEntryFound - Directory entry '%s' in %x already exists, but no driverData is provided.\n", + nameBytes, name, parent); + } + + if (node) { + if (!update) { + if (existingEntry->thisItem->node) { + KernelPanic("FSDirectoryEntryFound - Entry exists and is created on file system.\n"); + } + + EsError error = FSDirectoryEntryAllocateNode(existingEntry->thisItem, parent->fileSystem, true, true); + + if (error != ES_SUCCESS) { + return error; + } + } + + *node = existingEntry->thisItem->node; + } else if (update) { + EsMemoryCopy(existingEntry->thisItem + 1, driverData, driverDataBytes); + } else if (EsMemoryCompare(existingEntry->thisItem + 1, driverData, driverDataBytes)) { + // NOTE This can be caused by a directory containing an entry with the same name multiple times. + KernelLog(LOG_ERROR, "FS", "directory entry driverData changed", "FSDirectoryEntryFound - 'update' is false but driverData has changed.\n"); + } + + return ES_SUCCESS; + } else if (update) { + return ES_ERROR_FILE_DOES_NOT_EXIST; + } + + FSDirectoryEntry *entry = (FSDirectoryEntry *) EsHeapAllocate(sizeof(FSDirectoryEntry) + driverDataBytes, true, K_FIXED); + + if (!entry) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + if (nameBytes > sizeof(entry->inlineName)) { + entry->item.key.longKey = EsHeapAllocate(nameBytes, false, K_FIXED); + + if (!entry->item.key.longKey) { + EsHeapFree(entry, sizeof(FSDirectoryEntry) + driverDataBytes, K_FIXED); + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + } else { + entry->item.key.longKey = entry->inlineName; + } + + EsMemoryCopy(entry->item.key.longKey, name, nameBytes); + entry->item.key.longKeyBytes = nameBytes; + + EsMemoryCopy(entry, metadata, sizeof(KNodeMetadata)); + if (driverData) EsMemoryCopy(entry + 1, driverData, driverDataBytes); + entry->parent = parent; + + TreeInsert(&parent->entries, &entry->item, entry, entry->item.key, AVL_DUPLICATE_KEYS_PANIC); + parent->entryCount++; + + if (node) { + if (!update) { + EsError error = FSDirectoryEntryAllocateNode(entry, parent->fileSystem, driverData, false); + + if (error != ES_SUCCESS) { + return error; + } + } + + *node = entry->node; + } else { + MMObjectCacheInsert(&parent->fileSystem->cachedDirectoryEntries, &entry->cacheItem); + } + + return ES_SUCCESS; +} + +void FSNodeUpdateDriverData(KNode *node, const void *newDriverData) { + KWriterLockAssertExclusive(&node->writerLock); + EsMemoryCopy(node->directoryEntry + 1, newDriverData, node->fileSystem->directoryEntryDataBytes); +} + +void FSNodeSynchronize(KNode *node) { + if (node->directoryEntry->type == ES_NODE_FILE) { + FSFile *file = (FSFile *) node; + CCSpaceFlush(&file->cache); + KWriterLockTake(&node->writerLock, K_LOCK_EXCLUSIVE); + FSFileCreateAndResizeOnFileSystem(file, file->directoryEntry->totalSize); + KWriterLockReturn(&node->writerLock, K_LOCK_EXCLUSIVE); + } + + if (node->flags & NODE_MODIFIED) { + if (node->fileSystem->sync && (node->flags & NODE_CREATED_ON_FILE_SYSTEM /* might be false if create() failed */)) { + FSDirectory *parent = node->directoryEntry->parent; + + if (parent) KWriterLockTake(&parent->writerLock, K_LOCK_EXCLUSIVE); + node->fileSystem->sync(parent, node); + if (parent) KWriterLockReturn(&parent->writerLock, K_LOCK_EXCLUSIVE); + } + } +} + +void FSUnmountFileSystem(uintptr_t argument) { + KFileSystem *fileSystem = (KFileSystem *) argument; + KernelLog(LOG_INFO, "FS", "unmount start", "Unmounting file system %x...\n", fileSystem); + + MMObjectCacheUnregister(&fileSystem->cachedNodes); + MMObjectCacheUnregister(&fileSystem->cachedDirectoryEntries); + + while (fileSystem->cachedNodes.count || fileSystem->cachedDirectoryEntries.count) { + MMObjectCacheFlush(&fileSystem->cachedNodes); + MMObjectCacheFlush(&fileSystem->cachedDirectoryEntries); + } + + if (fileSystem->unmount) { + fileSystem->unmount(fileSystem); + } + + KernelLog(LOG_INFO, "FS", "unmount complete", "Unmounted file system %x.\n", fileSystem); + KDeviceCloseHandle(fileSystem->children[0]); + __sync_fetch_and_sub(&fs.fileSystemsUnmounting, 1); + KEventSet(&fs.fileSystemUnmounted, false, true); +} + +////////////////////////////////////////// +// Opening nodes. +////////////////////////////////////////// + +#define NODE_INCREMENT_HANDLE_COUNT(node) \ + node->handles++; \ + node->fileSystem->totalHandleCount++; \ + fs.totalHandleCount++; + +EsError FSDirectoryEntryOpenHandleToNode(FSDirectoryEntry *directoryEntry) { + KSpinlockAcquire(&fs.updateNodeHandles); + EsDefer(KSpinlockRelease(&fs.updateNodeHandles)); + + if (!directoryEntry->node || directoryEntry->removingNodeFromCache) { + return ES_ERROR_NODE_NOT_LOADED; + } + + if (directoryEntry->node->handles == NODE_MAX_ACCESSORS) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + KNode *node = directoryEntry->node; + + if (!node->handles) { + if (node->directoryEntry->type == ES_NODE_DIRECTORY && ((FSDirectory *) node)->entryCount) { + if (node->flags & NODE_IN_CACHE_LIST) KernelPanic("FSNodeOpenHandle - Directory %x with entries is in the cache list.\n", node); + } else { + if (~node->flags & NODE_IN_CACHE_LIST) KernelPanic("FSNodeOpenHandle - Node %x is not in the cache list.\n", node); + MMObjectCacheRemove(&node->fileSystem->cachedNodes, &node->cacheItem); + __sync_fetch_and_and(&node->flags, ~NODE_IN_CACHE_LIST); + } + } + + NODE_INCREMENT_HANDLE_COUNT(node); + return ES_SUCCESS; +} + +EsError FSNodeOpenHandle(KNode *node, uint32_t flags, uint8_t mode) { + { + // See comment in FSNodeCloseHandle for why we use the spinlock. + KSpinlockAcquire(&fs.updateNodeHandles); + EsDefer(KSpinlockRelease(&fs.updateNodeHandles)); + + if (node->handles && mode == FS_NODE_OPEN_HANDLE_FIRST) { + KernelPanic("FSNodeOpenHandle - Trying to open first handle to %x, but it already has handles.\n", node); + } else if (!node->handles && mode == FS_NODE_OPEN_HANDLE_STANDARD) { + KernelPanic("FSNodeOpenHandle - Trying to open handle to %x, but it has no handles.\n", node); + } + + if (node->handles == NODE_MAX_ACCESSORS) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + if (node->directoryEntry->type == ES_NODE_FILE) { + FSFile *file = (FSFile *) node; + + if (flags & ES_FILE_READ) { + if (file->countWrite > 0) return ES_ERROR_FILE_HAS_WRITERS; + } else if (flags & ES_FILE_WRITE_EXCLUSIVE) { + if (flags & _ES_NODE_FROM_WRITE_EXCLUSIVE) { + if (!file->countWrite || (~file->flags & NODE_HAS_EXCLUSIVE_WRITER)) { + KernelPanic("FSNodeOpenHandle - File %x is invalid state for a handle to have the _ES_NODE_FROM_WRITE_EXCLUSIVE flag.\n", file); + } + } else { + if (file->countWrite) { + return ES_ERROR_FILE_CANNOT_GET_EXCLUSIVE_USE; + } + } + } else if (flags & ES_FILE_WRITE) { + if ((file->flags & NODE_HAS_EXCLUSIVE_WRITER) || file->countWrite < 0) return ES_ERROR_FILE_IN_EXCLUSIVE_USE; + } + + if (flags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE)) { + if (!file->fileSystem->write) { + return ES_ERROR_FILE_ON_READ_ONLY_VOLUME; + } + } + + if (flags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE)) file->countWrite++; + if (flags & ES_FILE_READ) file->countWrite--; + if (flags & ES_FILE_WRITE_EXCLUSIVE) __sync_fetch_and_or(&node->flags, NODE_HAS_EXCLUSIVE_WRITER); + } + + NODE_INCREMENT_HANDLE_COUNT(node); + + // EsPrint("Open handle to %s (%d; %d).\n", node->directoryEntry->item.key.longKeyBytes, + // node->directoryEntry->item.key.longKey, node->handles, fs.totalHandleCount); + } + + if (node->directoryEntry->type == ES_NODE_FILE && (flags & ES_NODE_PREVENT_RESIZE)) { + // Modify blockResize with the resizeLock, to prevent a resize being in progress when blockResize becomes positive. + FSFile *file = (FSFile *) node; + KWriterLockTake(&file->resizeLock, K_LOCK_EXCLUSIVE); + file->blockResize++; + KWriterLockReturn(&file->resizeLock, K_LOCK_EXCLUSIVE); + } + + return ES_SUCCESS; +} + +void FSNodeCloseHandle(KNode *node, uint32_t flags) { + if (node->directoryEntry->type == ES_NODE_FILE && (flags & ES_NODE_PREVENT_RESIZE)) { + FSFile *file = (FSFile *) node; + KWriterLockTake(&file->resizeLock, K_LOCK_EXCLUSIVE); + file->blockResize--; + KWriterLockReturn(&file->resizeLock, K_LOCK_EXCLUSIVE); + } + + // Don't use the node's writer lock for this. + // It'd be unnecessarily require getting exclusive access. + // There's not much to do, so just use a global spinlock. + KSpinlockAcquire(&fs.updateNodeHandles); + + if (node->handles) { + node->handles--; + node->fileSystem->totalHandleCount--; + fs.totalHandleCount--; + + // EsPrint("Close handle to %s (%d; %d).\n", node->directoryEntry->item.key.longKeyBytes, + // node->directoryEntry->item.key.longKey, node->handles, fs.totalHandleCount); + } else { + KernelPanic("FSNodeCloseHandle - Node %x had no handles.\n", node); + } + + if (node->directoryEntry->type == ES_NODE_FILE) { + FSFile *file = (FSFile *) node; + + if ((flags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE))) { + if (file->countWrite <= 0) KernelPanic("FSNodeCloseHandle - Invalid countWrite on node %x.\n", node); + file->countWrite--; + } + + if ((flags & ES_FILE_READ)) { + if (file->countWrite >= 0) KernelPanic("FSNodeCloseHandle - Invalid countWrite on node %x.\n", node); + file->countWrite++; + } + + if ((flags & ES_FILE_WRITE_EXCLUSIVE) && file->countWrite == 0) { + if (~file->flags & NODE_HAS_EXCLUSIVE_WRITER) KernelPanic("FSNodeCloseHandle - Missing exclusive flag on node %x.\n", node); + __sync_fetch_and_and(&node->flags, ~NODE_HAS_EXCLUSIVE_WRITER); + } + } + + bool deleted = (node->flags & NODE_DELETED) && !node->handles; + bool unmounted = !node->fileSystem->totalHandleCount; + bool hasEntries = node->directoryEntry->type == ES_NODE_DIRECTORY && ((FSDirectory *) node)->entryCount; + if (unmounted && node->handles) KernelPanic("FSNodeCloseHandle - File system has no handles but this node %x has handles.\n", node); + KFileSystem *fileSystem = node->fileSystem; + + if (!node->handles && !deleted && !hasEntries) { + if (node->flags & NODE_IN_CACHE_LIST) KernelPanic("FSNodeCloseHandle - Node %x is already in the cache list.\n", node); + MMObjectCacheInsert(&node->fileSystem->cachedNodes, &node->cacheItem); + __sync_fetch_and_or(&node->flags, NODE_IN_CACHE_LIST); + node = nullptr; // The node could be freed at any time after MMObjectCacheInsert. + } + + KSpinlockRelease(&fs.updateNodeHandles); + + if (unmounted && !fileSystem->unmounting) { + // All handles to all nodes in the file system have been closed. + // Spawn a thread to unmount it. + fileSystem->unmounting = true; + __sync_fetch_and_add(&fs.fileSystemsUnmounting, 1); + KThreadCreate("FSUnmount", FSUnmountFileSystem, (uintptr_t) fileSystem); + } + + if (deleted) { + if (!node->directoryEntry->parent) KernelPanic("FSNodeCloseHandle - A root directory %x was deleted.\n", node); + + // The node has been deleted, and no handles remain. + // When it was deleted, it should have been removed from its parent directory, + // both on the file system and in the directory lookup structures. + // So, we are free to deallocate the node. + + FSDirectoryEntry *entry = node->directoryEntry; + FSNodeFree(node); + FSDirectoryEntryFree(entry); + } +} + +EsError FSNodeTraverseLayer(uintptr_t *sectionEnd, + const char *path, size_t pathBytes, bool isFinalPath, + KFileSystem *fileSystem, FSDirectory *directory, + uint32_t flags, KNode **node, bool *createdNode) { + EsError error = ES_SUCCESS; + + *sectionEnd = *sectionEnd + 1; + uintptr_t sectionStart = *sectionEnd; + + while (*sectionEnd != pathBytes && path[*sectionEnd] != '/') { + *sectionEnd = *sectionEnd + 1; + } + + const char *name = path + sectionStart; + size_t nameBytes = *sectionEnd - sectionStart; + AVLKey key = MakeLongKey(name, nameBytes); + FSDirectoryEntry *entry = nullptr; + AVLItem *treeItem = nullptr; + + // First, try to get the cached directory entry with shared access. + + { + KWriterLockTake(&directory->writerLock, K_LOCK_SHARED); + + treeItem = TreeFind(&directory->entries, key, TREE_SEARCH_EXACT); + bool needExclusiveAccess = true; + + if (treeItem) { + error = FSDirectoryEntryOpenHandleToNode(treeItem->thisItem); + + if (error == ES_ERROR_NODE_NOT_LOADED) { + error = ES_SUCCESS; // Proceed to use exclusive access. + } else { + entry = treeItem->thisItem; + needExclusiveAccess = false; + } + } + + KWriterLockReturn(&directory->writerLock, K_LOCK_SHARED); + + if (!needExclusiveAccess) { + goto usedSharedAccess; + } + } + + tryAgain:; + + KWriterLockTake(&directory->writerLock, K_LOCK_EXCLUSIVE); + + treeItem = TreeFind(&directory->entries, key, TREE_SEARCH_EXACT); + + if (!treeItem && (~directory->flags & NODE_ENUMERATED_ALL_DIRECTORY_ENTRIES)) { + // The entry is not cached; load it from the file system. + fileSystem->scan(name, nameBytes, directory); + treeItem = TreeFind(&directory->entries, key, TREE_SEARCH_EXACT); + } + + if (!treeItem) { + // The node does not exist. + + if (flags & _ES_NODE_NO_WRITE_BASE) { + error = ES_ERROR_FILE_PERMISSION_NOT_GRANTED; + goto failed; + } + + if (*sectionEnd == pathBytes && isFinalPath) { + if (~flags & ES_NODE_FAIL_IF_NOT_FOUND) { + error = FSNodeCreate(directory, name, nameBytes, flags & ES_NODE_DIRECTORY); + if (error != ES_SUCCESS) goto failed; + treeItem = TreeFind(&directory->entries, key, TREE_SEARCH_EXACT); + flags &= ~ES_NODE_FAIL_IF_FOUND; + *createdNode = true; + } + + if (!treeItem) { + error = ES_ERROR_FILE_DOES_NOT_EXIST; + goto failed; + } + } else { + if (flags & ES_NODE_CREATE_DIRECTORIES) { + error = FSNodeCreate(directory, name, nameBytes, ES_NODE_DIRECTORY); + if (error != ES_SUCCESS) goto failed; + treeItem = TreeFind(&directory->entries, key, TREE_SEARCH_EXACT); + } + + if (!treeItem) { + error = ES_ERROR_PATH_NOT_TRAVERSABLE; + goto failed; + } + } + } + + entry = treeItem->thisItem; + + if (!entry->node) { + // The node has not be loaded; load it from the file system. + + error = FSDirectoryEntryAllocateNode(entry, fileSystem, true, true); + + if (error == ES_ERROR_DIRECTORY_ENTRY_BEING_REMOVED) { + // The directory entry is being removed. + // Since this will take the directory's writer lock, we shouldn't need an additional synchronisation object. + // Instead we can just yield to wait for a bit, then try again. + // Similar to the strategy for the ES_ERROR_NODE_NOT_LOADED race below. + KWriterLockReturn(&directory->writerLock, K_LOCK_EXCLUSIVE); + KYield(); + goto tryAgain; + } + + if (error != ES_SUCCESS) { + goto failed; + } + + error = fileSystem->load(directory, entry->node, entry, entry + 1); + + if (error != ES_SUCCESS) { + FSNodeFree(entry->node); + goto failed; + } + + if (ES_SUCCESS != FSNodeOpenHandle(entry->node, 0, FS_NODE_OPEN_HANDLE_FIRST)) { + KernelPanic("FSNodeTraverseLayer - FSNodeOpenHandle failed while opening first handle for %x.\n", entry->node); + } + } else { + error = FSDirectoryEntryOpenHandleToNode(entry); + + if (error == ES_ERROR_NODE_NOT_LOADED) { + // The node is being removed, or was just removed. + // The main bottleneck in removing a node from the cache is CCSpaceFlush. + // Since this will take the directory's writer lock, we shouldn't need an additional synchronisation object. + // Instead we can just yield to wait for a bit, then try again. + KWriterLockReturn(&directory->writerLock, K_LOCK_EXCLUSIVE); + KYield(); + goto tryAgain; + } + } + + failed:; + KWriterLockReturn(&directory->writerLock, K_LOCK_EXCLUSIVE); + usedSharedAccess:; + FSNodeCloseHandle(directory, 0); + + if (error != ES_SUCCESS) { + return error; + } + + if (*sectionEnd != pathBytes || !isFinalPath) { + if (entry->node->directoryEntry->type != ES_NODE_DIRECTORY) { + FSNodeCloseHandle(directory, 0); + return ES_ERROR_PATH_NOT_TRAVERSABLE; + } + } + + *node = entry->node; + return ES_SUCCESS; +} + +KNodeInformation FSNodeOpen(const char *path, size_t pathBytes, uint32_t flags, KNode *baseDirectory) { + if ((1 << (flags & 0xF)) & ~(0x117)) { + // You should only pass one access flag! (or none) + return { ES_ERROR_FILE_PERMISSION_NOT_GRANTED }; + } + + if (fs.shutdown) return { ES_ERROR_PATH_NOT_TRAVERSABLE }; + if (pathBytes && path[pathBytes - 1] == '/') pathBytes--; + + if (!FSCheckPathForIllegalCharacters(path, pathBytes)) { + return { ES_ERROR_ILLEGAL_PATH }; + } + + KFileSystem *fileSystem = nullptr; + FSDirectory *directory = nullptr; + EsError error = ES_ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME; + + KWriterLockTake(&fs.fileSystemsLock, K_LOCK_SHARED); + + if (!baseDirectory) { + fileSystem = fs.bootFileSystem; + directory = fileSystem ? (FSDirectory *) fileSystem->rootDirectory : nullptr; + } else { + fileSystem = baseDirectory->fileSystem; + directory = (FSDirectory *) baseDirectory; + + if (directory->directoryEntry->type != ES_NODE_DIRECTORY) { + KernelPanic("FSNodeOpen - Base directory %x was not a directory.\n", directory); + } + } + + KWriterLockReturn(&fs.fileSystemsLock, K_LOCK_SHARED); + + KNode *node = pathBytes ? nullptr : directory; + + error = FSNodeOpenHandle(directory, 0, FS_NODE_OPEN_HANDLE_STANDARD); + if (error != ES_SUCCESS) return { error }; + + bool createdNode = false; + + for (uintptr_t sectionEnd = 0; sectionEnd < pathBytes; ) { + error = FSNodeTraverseLayer(§ionEnd, path, pathBytes, true, fileSystem, directory, flags, &node, &createdNode); + if (error != ES_SUCCESS) return { error }; + if (sectionEnd != pathBytes) directory = (FSDirectory *) node; + } + + if (node->directoryEntry->type != ES_NODE_DIRECTORY && (flags & ES_NODE_DIRECTORY)) { + return { ES_ERROR_INCORRECT_NODE_TYPE }; + } + + if ((flags & ES_NODE_FAIL_IF_FOUND) && !createdNode) { + error = ES_ERROR_FILE_ALREADY_EXISTS; + } else { + error = FSNodeOpenHandle(node, flags, FS_NODE_OPEN_HANDLE_STANDARD); + } + + FSNodeCloseHandle(node, 0); + if (error != ES_SUCCESS) node = nullptr; + return { error, node }; +} + +bool FSTrimCachedDirectoryEntry(MMObjectCache *cache) { + FSDirectoryEntry *entry = nullptr; + + KSpinlockAcquire(&fs.updateNodeHandles); + + MMObjectCacheItem *item = MMObjectCacheRemoveLRU(cache); + + if (item) { + entry = EsContainerOf(FSDirectoryEntry, cacheItem, item); + entry->removingThisFromCache = true; + } + + KSpinlockRelease(&fs.updateNodeHandles); + + if (entry) { + if (!entry->parent) { + // This is the root of the file system. + FSDirectoryEntryFree(entry); + } else if (ES_SUCCESS == FSNodeOpenHandle(entry->parent, ES_FLAGS_DEFAULT, FS_NODE_OPEN_HANDLE_DIRECTORY_TEMPORARY)) { + KWriterLockTake(&entry->parent->writerLock, K_LOCK_EXCLUSIVE); + TreeRemove(&entry->parent->entries, &entry->item); + entry->parent->entryCount--; + __sync_fetch_and_and(&entry->parent->flags, ~NODE_ENUMERATED_ALL_DIRECTORY_ENTRIES); + KWriterLockReturn(&entry->parent->writerLock, K_LOCK_EXCLUSIVE); + FSNodeCloseHandle(entry->parent, ES_FLAGS_DEFAULT); // This will put the parent in the node cache if needed. + FSDirectoryEntryFree(entry); + } else { + // A very rare case where the parent directory had so many handles open that a temporary handle couldn't be opened. + // Put it back in the cache, and hopefully next time we try to get rid of it there won't be 16 million handles open on the parent. + // TODO Test this branch! + KSpinlockAcquire(&fs.updateNodeHandles); + entry->removingThisFromCache = false; + MMObjectCacheInsert(cache, &entry->cacheItem); + KSpinlockRelease(&fs.updateNodeHandles); + } + } + + return entry != nullptr; +} + +bool FSTrimCachedNode(MMObjectCache *cache) { + KNode *node = nullptr; + + KSpinlockAcquire(&fs.updateNodeHandles); + + MMObjectCacheItem *item = MMObjectCacheRemoveLRU(cache); + + if (item) { + node = EsContainerOf(KNode, cacheItem, item); + __sync_fetch_and_and(&node->flags, ~NODE_IN_CACHE_LIST); + node->directoryEntry->removingNodeFromCache = true; + } + + KSpinlockRelease(&fs.updateNodeHandles); + + if (node) { + FSNodeSynchronize(node); + FSNodeFree(node); + } + + return node != nullptr; +} + +////////////////////////////////////////// +// DMA transfer buffers. +////////////////////////////////////////// + +struct KDMABuffer { + uintptr_t virtualAddress; + size_t totalByteCount; + uintptr_t offsetBytes; +}; + +uintptr_t KDMABufferGetVirtualAddress(KDMABuffer *buffer) { + return buffer->virtualAddress; +} + +size_t KDMABufferGetTotalByteCount(KDMABuffer *buffer) { + return buffer->totalByteCount; +} + +bool KDMABufferIsComplete(KDMABuffer *buffer) { + return buffer->offsetBytes == buffer->totalByteCount; +} + +KDMASegment KDMABufferNextSegment(KDMABuffer *buffer, bool peek) { + if (buffer->offsetBytes >= buffer->totalByteCount || !buffer->virtualAddress) { + KernelPanic("KDMABufferNextSegment - Invalid state in buffer %x.\n", buffer); + } + + size_t transferByteCount = K_PAGE_SIZE; + uintptr_t virtualAddress = buffer->virtualAddress + buffer->offsetBytes; + uintptr_t physicalAddress = MMArchTranslateAddress(MMGetKernelSpace(), virtualAddress); + uintptr_t offsetIntoPage = virtualAddress & (K_PAGE_SIZE - 1); + + if (!physicalAddress) { + KernelPanic("KDMABufferNextSegment - Page in buffer %x unmapped.\n", buffer); + } + + if (offsetIntoPage) { + transferByteCount = K_PAGE_SIZE - offsetIntoPage; + physicalAddress += offsetIntoPage; + } + + if (transferByteCount > buffer->totalByteCount - buffer->offsetBytes) { + transferByteCount = buffer->totalByteCount - buffer->offsetBytes; + } + + bool isLast = buffer->offsetBytes + transferByteCount == buffer->totalByteCount; + if (!peek) buffer->offsetBytes += transferByteCount; + return { physicalAddress, transferByteCount, isLast }; +} + +////////////////////////////////////////// +// Block devices. +////////////////////////////////////////// + +bool FSBlockDeviceAccess(KBlockDeviceAccessRequest request) { + KBlockDevice *device = request.device; + + if (!request.count) { + return true; + } + + if (device->readOnly && request.operation == K_ACCESS_WRITE) { + KernelPanic("FSBlockDeviceAccess - Drive %x is read-only.\n", device); + } + + if (request.offset / device->sectorSize > device->sectorCount + || (request.offset + request.count) / device->sectorSize > device->sectorCount) { + KernelPanic("FSBlockDeviceAccess - Access out of bounds on drive %x.\n", device); + } + + if ((request.offset % device->sectorSize) || (request.count % device->sectorSize)) { + KernelPanic("FSBlockDeviceAccess - Misaligned access.\n"); + } + + KDMABuffer buffer = *request.buffer; + + if (buffer.virtualAddress & 3) { + KernelPanic("FSBlockDeviceAccess - Buffer must be DWORD aligned.\n"); + } + + KWorkGroup fakeDispatchGroup = {}; + + if (!request.dispatchGroup) { + fakeDispatchGroup.Initialise(); + request.dispatchGroup = &fakeDispatchGroup; + } + + KBlockDeviceAccessRequest r = {}; + r.device = request.device; + r.buffer = &buffer; + r.flags = request.flags; + r.dispatchGroup = request.dispatchGroup; + r.operation = request.operation; + r.offset = request.offset; + + while (request.count) { + r.count = device->maxAccessSectorCount * device->sectorSize; + if (r.count > request.count) r.count = request.count; + buffer.offsetBytes = 0; + buffer.totalByteCount = r.count; + r.count = r.count; + device->access(r); + r.offset += r.count; + buffer.virtualAddress += r.count; + request.count -= r.count; + } + + if (request.dispatchGroup == &fakeDispatchGroup) { + return fakeDispatchGroup.Wait(); + } else { + return true; + } +} + +EsError FSReadIntoBlockCache(CCSpace *fileCache, void *buffer, EsFileOffset offset, EsFileOffset count) { + KFileSystem *fileSystem = (KFileSystem *) fileCache - 1; + return fileSystem->Access(offset, count, K_ACCESS_READ, buffer, ES_FLAGS_DEFAULT, nullptr) ? ES_SUCCESS : ES_ERROR_DRIVE_CONTROLLER_REPORTED; +} + +EsError FSWriteFromBlockCache(CCSpace *fileCache, const void *buffer, EsFileOffset offset, EsFileOffset count) { + KFileSystem *fileSystem = (KFileSystem *) fileCache - 1; + return fileSystem->Access(offset, count, K_ACCESS_WRITE, (void *) buffer, ES_FLAGS_DEFAULT, nullptr) ? ES_SUCCESS : ES_ERROR_DRIVE_CONTROLLER_REPORTED; +} + +const CCSpaceCallbacks fsBlockCacheCallbacks = { + .readInto = FSReadIntoBlockCache, + .writeFrom = FSWriteFromBlockCache, +}; + +bool KFileSystem::Access(EsFileOffset offset, size_t count, int operation, void *buffer, uint32_t flags, KWorkGroup *dispatchGroup) { + if (this->flags & K_DEVICE_REMOVED) { + if (dispatchGroup) { + dispatchGroup->Start(); + dispatchGroup->End(false); + } + + return false; + } + + bool blockDeviceCachedEnabled = true; + + if (blockDeviceCachedEnabled && (flags & BLOCK_ACCESS_CACHED)) { + if (dispatchGroup) { + dispatchGroup->Start(); + } + + // TODO Use the dispatch group. + + // We use the CC_ACCESS_PRECISE flag for file systems that have a block size less than the page size. + // Otherwise, we might end up trashing file blocks (which aren't kept in the block device cache). + + EsError result = CCSpaceAccess((CCSpace *) (this + 1), buffer, offset, count, + operation == K_ACCESS_READ ? CC_ACCESS_READ : (CC_ACCESS_WRITE | CC_ACCESS_WRITE_BACK | CC_ACCESS_PRECISE)); + + if (dispatchGroup) { + dispatchGroup->End(result == ES_SUCCESS); + } + + return result == ES_SUCCESS; + } else { + KDMABuffer dmaBuffer = { (uintptr_t) buffer, count }; + KBlockDeviceAccessRequest request = {}; + request.device = block; + request.offset = offset; + request.count = count; + request.operation = operation; + request.buffer = &dmaBuffer; + request.flags = flags; + request.dispatchGroup = dispatchGroup; + return FSBlockDeviceAccess(request); + } +} + +////////////////////////////////////////// +// Partition devices. +////////////////////////////////////////// + +struct PartitionDevice : KBlockDevice { + EsFileOffset sectorOffset; +}; + +void FSPartitionDeviceAccess(KBlockDeviceAccessRequest request) { + PartitionDevice *_device = (PartitionDevice *) request.device; + request.device = (KBlockDevice *) _device->parent; + request.offset += _device->sectorOffset * _device->sectorSize; + FSBlockDeviceAccess(request); +} + +void FSPartitionDeviceCreate(KBlockDevice *parent, EsFileOffset offset, EsFileOffset sectorCount, unsigned flags, const char *cName) { + PartitionDevice *child = (PartitionDevice *) KDeviceCreate(cName, parent, sizeof(PartitionDevice)); + if (!child) return; + + child->parent = parent; + child->sectorSize = parent->sectorSize; + child->maxAccessSectorCount = parent->maxAccessSectorCount; + child->sectorOffset = offset; + child->sectorCount = sectorCount; + child->noMBR = flags & FS_PARTITION_DEVICE_NO_MBR ? true : false; + child->readOnly = parent->readOnly; + child->access = FSPartitionDeviceAccess; + child->cModel = cName; + child->nestLevel = parent->nestLevel + 1; + child->driveType = parent->driveType; + + FSRegisterBlockDevice(child); +} + +////////////////////////////////////////// +// File system and partition table detection. +////////////////////////////////////////// + +bool FSSignatureCheck(KInstalledDriver *driver, KDevice *device) { + uint8_t *block = ((KFileSystem *) device)->block->information; + + EsINIState s = {}; + s.buffer = driver->config; + s.bytes = driver->configBytes; + + int64_t offset = -1; + + while (EsINIParse(&s)) { + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("signature_offset"))) { + offset = EsIntegerParse(s.value, s.valueBytes); + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("signature"))) { + if (offset >= K_SIGNATURE_BLOCK_SIZE || offset >= K_SIGNATURE_BLOCK_SIZE - (int64_t) s.valueBytes || offset < 0) { + KernelPanic("FSSignatureCheck - Filesystem '%s' has invalid signature detection information.\n", driver->nameBytes, driver->name); + } + + if (0 == EsMemoryCompare(block + offset, s.value, s.valueBytes)) { + return true; + } + } + } + + return false; +} + +bool FSCheckMBR(KBlockDevice *device) { + if (device->information[510] != 0x55 || device->information[511] != 0xAA) { + return false; + } + + KernelLog(LOG_INFO, "FS", "probing MBR", "First sector on device looks like an MBR...\n"); + + uint32_t offsets[4], counts[4]; + bool present[4] = {}; + + for (uintptr_t i = 0; i < 4; i++) { + if (!device->information[4 + 0x1BE + i * 0x10]) { + continue; + } + + offsets[i] = + ((uint32_t) device->information[0x1BE + i * 0x10 + 8 ] << 0 ) + + ((uint32_t) device->information[0x1BE + i * 0x10 + 9 ] << 8 ) + + ((uint32_t) device->information[0x1BE + i * 0x10 + 10] << 16) + + ((uint32_t) device->information[0x1BE + i * 0x10 + 11] << 24); + counts[i] = + ((uint32_t) device->information[0x1BE + i * 0x10 + 12] << 0 ) + + ((uint32_t) device->information[0x1BE + i * 0x10 + 13] << 8 ) + + ((uint32_t) device->information[0x1BE + i * 0x10 + 14] << 16) + + ((uint32_t) device->information[0x1BE + i * 0x10 + 15] << 24); + present[i] = true; + + if (offsets[i] > device->sectorCount || counts[i] > device->sectorCount - offsets[i] || counts[i] < 32) { + KernelLog(LOG_INFO, "FS", "invalid MBR", "Partition %d has offset %d and count %d, which is invalid. Ignoring the rest of the MBR...\n", + i, offsets[i], counts[i]); + return false; + } + } + + for (uintptr_t i = 0; i < 4; i++) { + if (present[i]) { + KernelLog(LOG_INFO, "FS", "MBR partition", "Found MBR partition %d with offset %d and count %d.\n", + i, offsets[i], counts[i]); + FSPartitionDeviceCreate(device, offsets[i], counts[i], FS_PARTITION_DEVICE_NO_MBR, "MBR partition"); + } + } + + return true; +} + +void FSDetectFileSystem(KBlockDevice *device) { + KernelLog(LOG_INFO, "FS", "detect file system", "Detecting file system on block device '%z'.\n", device->cModel); + + if (device->nestLevel > 4) { + KernelLog(LOG_ERROR, "FS", "file system nest limit", "Reached file system nest limit (4), ignoring device.\n"); + } + + uint64_t sectorsToRead = (K_SIGNATURE_BLOCK_SIZE + device->sectorSize - 1) / device->sectorSize; + + if (sectorsToRead > device->sectorCount) { + KernelLog(LOG_ERROR, "FS", "drive too small", "The drive must be at least %D (K_SIGNATURE_BLOCK_SIZE).\n", K_SIGNATURE_BLOCK_SIZE); + return; + } + + uint8_t *information = (uint8_t *) EsHeapAllocate(sectorsToRead * device->sectorSize, false, K_FIXED); + device->information = information; + + KDMABuffer dmaBuffer = { (uintptr_t) information }; + KBlockDeviceAccessRequest request = {}; + request.device = device; + request.count = sectorsToRead * device->sectorSize; + request.operation = K_ACCESS_READ; + request.buffer = &dmaBuffer; + + if (!FSBlockDeviceAccess(request)) { + // We could not access the block device. + KernelLog(LOG_ERROR, "FS", "detect fileSystem read failure", "The signature block could not be read on block device %x.\n", device); + } else { + if (!device->noMBR && FSCheckMBR(device)) { + // Found an MBR. + } else { + FSDirectoryEntry *rootEntry = nullptr; + KFileSystem *fileSystem = nullptr; + CCSpace *cache = nullptr; + + rootEntry = (FSDirectoryEntry *) EsHeapAllocate(sizeof(FSDirectoryEntry), true, K_FIXED); + if (!rootEntry) goto error; + + rootEntry->type = ES_NODE_DIRECTORY; + if (ES_SUCCESS != FSDirectoryEntryAllocateNode(rootEntry, nullptr, true, false)) goto error; + + fileSystem = (KFileSystem *) KDeviceCreate("file system", device, sizeof(KFileSystem) + sizeof(CCSpace)); + if (!fileSystem) goto error; + + fileSystem->rootDirectory = rootEntry->node; + fileSystem->rootDirectory->fileSystem = fileSystem; + fileSystem->block = device; + cache = (CCSpace *) (fileSystem + 1); + if (!CCSpaceInitialise(cache)) goto error; + + cache->callbacks = &fsBlockCacheCallbacks; + KDeviceAttach(fileSystem, "Files", FSSignatureCheck); + KDeviceCloseHandle(fileSystem); + goto success; + + error:; + if (fileSystem) KDeviceDestroy(fileSystem); + if (rootEntry && rootEntry->node) FSNodeFree(rootEntry->node); + if (rootEntry) EsHeapFree(rootEntry, sizeof(FSDirectoryEntry), K_FIXED); + success:; + } + } + + EsHeapFree(information, sectorsToRead * device->sectorSize, K_FIXED); + KDeviceCloseHandle(device); +} + +////////////////////////////////////////// +// Device management. +////////////////////////////////////////// + +void FSRegisterBootFileSystem(KFileSystem *fileSystem, EsUniqueIdentifier identifier) { + if (!EsMemoryCompare(&identifier, &installationID, sizeof(EsUniqueIdentifier))) { + KWriterLockTake(&fs.fileSystemsLock, K_LOCK_EXCLUSIVE); + + if (!fs.bootFileSystem) { + fs.bootFileSystem = fileSystem; + KEventSet(&fs.foundBootFileSystemEvent); + fileSystem->isBootFileSystem = true; + FSNodeOpenHandle(fileSystem->rootDirectory, ES_FLAGS_DEFAULT, FS_NODE_OPEN_HANDLE_FIRST); + } else { + KernelLog(LOG_ERROR, "FS", "duplicate boot file system", "Found multiple boot file systems; the first registered will be used.\n"); + } + + KWriterLockReturn(&fs.fileSystemsLock, K_LOCK_EXCLUSIVE); + } + + FSRegisterFileSystem(fileSystem); +} + +void FSFileSystemDeviceRemoved(KDevice *device) { + KFileSystem *fileSystem = (KFileSystem *) device; + EsMessage m; + EsMemoryZero(&m, sizeof(EsMessage)); + m.type = ES_MSG_UNREGISTER_FILE_SYSTEM; + m.unregisterFileSystem.id = fileSystem->id; + desktopProcess->messageQueue.SendMessage(nullptr, &m); +} + +void FSRegisterFileSystem(KFileSystem *fileSystem) { + if (fileSystem->children.Length() != 1) { + KernelPanic("FSRegisterFileSystem - File system %x does not have a child device.\n", fileSystem); + } + + static volatile uint64_t id = 1; + fileSystem->id = __sync_fetch_and_add(&id, 1); + + fileSystem->removed = FSFileSystemDeviceRemoved; + + MMObjectCacheRegister(&fileSystem->cachedDirectoryEntries, FSTrimCachedDirectoryEntry, + sizeof(FSDirectoryEntry) + 16 /* approximate average name bytes */ + fileSystem->directoryEntryDataBytes); + MMObjectCacheRegister(&fileSystem->cachedNodes, FSTrimCachedNode, + sizeof(FSFile) + fileSystem->nodeDataBytes); + fileSystem->rootDirectory->directoryEntry->directoryChildren = fileSystem->rootDirectoryInitialChildren; + FSNodeOpenHandle(fileSystem->rootDirectory, ES_FLAGS_DEFAULT, fileSystem->isBootFileSystem ? FS_NODE_OPEN_HANDLE_STANDARD : FS_NODE_OPEN_HANDLE_FIRST); + + EsMessage m; + EsMemoryZero(&m, sizeof(EsMessage)); + m.type = ES_MSG_REGISTER_FILE_SYSTEM; + m.registerFileSystem.rootDirectory = desktopProcess->handleTable.OpenHandle(fileSystem->rootDirectory, _ES_NODE_DIRECTORY_WRITE, KERNEL_OBJECT_NODE); + m.registerFileSystem.isBootFileSystem = fileSystem->isBootFileSystem; + desktopProcess->messageQueue.SendMessage(nullptr, &m); +} + +void FSRegisterBlockDevice(KBlockDevice *device) { +#ifdef SERIAL_STARTUP + FSDetectFileSystem(device); +#else + KThreadCreate("FSDetect", [] (uintptr_t context) { + FSDetectFileSystem((KBlockDevice *) context); + }, (uintptr_t) device); +#endif +} + +void FSShutdown() { + // A file system is unmounted when the last open handle to its nodes is closed. + // When a file system is registered, a handle is opened to its root directory and given to Desktop. + // Therefore, when the Desktop process is terminated, that handle is closed. + // However, we additionally have a handle open to the boot file system, which we need to close. + // Then, we wait for all file system unmounting threads to complete. + + // By this point there should be one open handle to the root directory, + // and any handles temporarily opened by object cache trimming threads. + // (FSTrimCachedDirectoryEntry opens a handle on the parent directory.) + + fs.shutdown = true; + CloseHandleToObject(fs.bootFileSystem->rootDirectory, KERNEL_OBJECT_NODE); + while (fs.fileSystemsUnmounting) KEventWait(&fs.fileSystemUnmounted); + if (fs.totalHandleCount) KernelPanic("FSShutdown - Expected no open handles, got %d.\n", fs.totalHandleCount); +} + +#endif diff --git a/kernel/graphics.cpp b/kernel/graphics.cpp new file mode 100644 index 0000000..ac92262 --- /dev/null +++ b/kernel/graphics.cpp @@ -0,0 +1,562 @@ +#ifndef IMPLEMENTATION + +struct Surface : EsPaintTarget { + bool Resize(size_t newResX, size_t newResY, uint32_t clearColor = 0, bool copyOldBits = false); + void Copy(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, bool addToModifiedRegion); + void Draw(Surface *source, EsRectangle destinationRegion, int sourceX, int sourceY, uint16_t alpha); + void BlendWindow(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, int material, uint8_t alpha, EsRectangle materialRegion); + void Blur(EsRectangle region, EsRectangle clip); + void SetBits(K_USER_BUFFER const void *bits, uintptr_t stride, EsRectangle region); + void Scroll(EsRectangle region, ptrdiff_t delta, bool vertical); + + EsRectangle modifiedRegion; +}; + +struct Graphics { + KGraphicsTarget *target; + size_t width, height; + Surface frameBuffer; + bool debuggerActive; + size_t totalSurfaceBytes; +}; + +void GraphicsUpdateScreen(K_USER_BUFFER void *bits = nullptr, EsRectangle *bounds = nullptr, uintptr_t stride = 0); + +Graphics graphics; + +#else + +void GraphicsUpdateScreen(K_USER_BUFFER void *bits, EsRectangle *bounds, uintptr_t bitsStride) { + KMutexAssertLocked(&windowManager.mutex); + + if (windowManager.resizeWindow && windowManager.resizeStartTimeStampMs + RESIZE_FLICKER_TIMEOUT_MS > KGetTimeInMs() + && !windowManager.inspectorWindowCount /* HACK see note in the SET_BITS syscall */) { + return; + } + + if (bounds && (Width(*bounds) <= 0 || Height(*bounds) <= 0)) { + return; + } + + int cursorX = windowManager.cursorX + windowManager.cursorImageOffsetX - (bounds ? bounds->l : 0); + int cursorY = windowManager.cursorY + windowManager.cursorImageOffsetY - (bounds ? bounds->t : 0); + + Surface *sourceSurface; + Surface _sourceSurface; + EsRectangle _bounds; + + if (bits) { + sourceSurface = &_sourceSurface; + EsMemoryZero(sourceSurface, sizeof(Surface)); + sourceSurface->bits = bits; + sourceSurface->width = Width(*bounds); + sourceSurface->height = Height(*bounds); + sourceSurface->stride = bitsStride; + } else { + sourceSurface = &graphics.frameBuffer; + _bounds = ES_RECT_4(0, sourceSurface->width, 0, sourceSurface->height); + bounds = &_bounds; + } + + EsRectangle cursorBounds = ES_RECT_4(cursorX, cursorX + windowManager.cursorSwap.width, cursorY, cursorY + windowManager.cursorSwap.height); + EsRectangleClip(ES_RECT_4(0, Width(*bounds), 0, Height(*bounds)), cursorBounds, &cursorBounds); + + windowManager.cursorSwap.Copy(sourceSurface, ES_POINT(0, 0), cursorBounds, true); + windowManager.changedCursorImage = false; + + int cursorImageWidth = windowManager.cursorSurface.width, cursorImageHeight = windowManager.cursorSurface.height; + sourceSurface->Draw(&windowManager.cursorSurface, ES_RECT_4(cursorX, cursorX + cursorImageWidth, cursorY, cursorY + cursorImageHeight), 0, 0, + windowManager.cursorXOR ? ES_DRAW_BITMAP_XOR : 0xFF); + + if (bits) { + graphics.target->updateScreen((K_USER_BUFFER const uint8_t *) bits, + sourceSurface->width, sourceSurface->height, + sourceSurface->stride, bounds->l, bounds->t); + } else { + if (Width(sourceSurface->modifiedRegion) > 0 && Height(sourceSurface->modifiedRegion) > 0) { + uint8_t *bits = (uint8_t *) sourceSurface->bits + + sourceSurface->modifiedRegion.l * 4 + + sourceSurface->modifiedRegion.t * sourceSurface->stride; + graphics.target->updateScreen(bits, Width(sourceSurface->modifiedRegion), Height(sourceSurface->modifiedRegion), + sourceSurface->width * 4, sourceSurface->modifiedRegion.l, sourceSurface->modifiedRegion.t); + sourceSurface->modifiedRegion = { (int32_t) graphics.width, 0, (int32_t) graphics.height, 0 }; + } + } + + sourceSurface->Copy(&windowManager.cursorSwap, ES_POINT(cursorBounds.l, cursorBounds.t), ES_RECT_4(0, Width(cursorBounds), 0, Height(cursorBounds)), true); +} + +bool KGraphicsIsTargetRegistered() { + return graphics.target ? true : false; +} + +void KRegisterGraphicsTarget(KGraphicsTarget *target) { + // TODO Locking. + if (graphics.target) return; + + graphics.target = target; + + graphics.width = target->screenWidth; + graphics.height = target->screenHeight; + + graphics.frameBuffer.Resize(graphics.width, graphics.height); + + windowManager.Initialise(); + + EsMessage m; + EsMemoryZero(&m, sizeof(EsMessage)); + m.type = ES_MSG_SET_SCREEN_RESOLUTION; + desktopProcess->messageQueue.SendMessage(nullptr, &m); +} + +bool Surface::Resize(size_t newResX, size_t newResY, uint32_t clearColor, bool copyOldBits) { + // Check the surface is within our working size limits. + if (!newResX || !newResY || newResX >= 32767 || newResY >= 32767) { + return false; + } + + if (width == newResX && height == newResY) { + return true; + } + + uint8_t *newBits = (uint8_t *) EsHeapAllocate(newResX * newResY * 4, !copyOldBits, K_PAGED); + + if (!newBits) { + return false; + } + + int oldWidth = width, oldHeight = height, oldStride = stride; + void *oldBits = bits; + + width = newResX, height = newResY, bits = newBits; + stride = newResX * 4; + + EsPainter painter; + painter.clip = ES_RECT_4(0, width, 0, height); + painter.target = this; + + if (copyOldBits) { + EsDrawBitmap(&painter, ES_RECT_4(0, oldWidth, 0, oldHeight), (uint32_t *) oldBits, oldStride, ES_DRAW_BITMAP_OPAQUE); + + if (clearColor) { + EsDrawBlock(&painter, ES_RECT_4(oldWidth, width, 0, height), clearColor); + EsDrawBlock(&painter, ES_RECT_4(0, oldWidth, oldHeight, height), clearColor); + } else { + EsDrawClear(&painter, ES_RECT_4(oldWidth, width, 0, height)); + EsDrawClear(&painter, ES_RECT_4(0, oldWidth, oldHeight, height)); + } + } + + EsHeapFree(oldBits, 0, K_PAGED); + + __sync_fetch_and_add(&graphics.totalSurfaceBytes, newResX * newResY * 4 - oldWidth * oldHeight * 4); + + return true; +} + +void Surface::Copy(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, bool addToModifiedRegion) { + EsRectangle destinationRegion = ES_RECT_4(destinationPoint.x, destinationPoint.x + Width(sourceRegion), + destinationPoint.y, destinationPoint.y + Height(sourceRegion)); + + if (addToModifiedRegion) { + modifiedRegion = EsRectangleBounding(destinationRegion, modifiedRegion); + EsRectangleClip(modifiedRegion, ES_RECT_4(0, width, 0, height), &modifiedRegion); + } + + EsPainter painter; + painter.clip = ES_RECT_4(0, width, 0, height); + painter.target = this; + uint8_t *sourceBits = (uint8_t *) source->bits + source->stride * sourceRegion.t + 4 * sourceRegion.l; + EsDrawBitmap(&painter, destinationRegion, (uint32_t *) sourceBits, source->stride, ES_DRAW_BITMAP_OPAQUE); +} + +void Surface::SetBits(K_USER_BUFFER const void *_bits, uintptr_t sourceStride, EsRectangle bounds) { + if (Width(bounds) < 0 || Height(bounds) < 0 || bounds.l < 0 || bounds.t < 0 || bounds.r > (int32_t) width || bounds.b > (int32_t) height) { + KernelPanic("Surface::SetBits - Invalid bounds %R for surface %x.\n", bounds, this); + } + + if (Width(bounds) == 0 || Height(bounds) == 0) { + return; + } + + modifiedRegion = EsRectangleBounding(bounds, modifiedRegion); + + uint32_t *rowStart = (uint32_t *) bits + bounds.l + bounds.t * stride / 4; + K_USER_BUFFER const uint32_t *sourceRowStart = (K_USER_BUFFER const uint32_t *) _bits; + + for (uintptr_t i = bounds.t; i < (uintptr_t) bounds.b; i++, rowStart += stride / 4, sourceRowStart += sourceStride / 4) { + size_t count = Width(bounds); + uint32_t *destination = rowStart; + K_USER_BUFFER const uint32_t *bits = sourceRowStart; + + do { + *destination = *bits; + destination++, bits++, count--; + } while (count); + } +} + +void Surface::Scroll(EsRectangle region, ptrdiff_t delta, bool vertical) { + if (vertical) { + if (delta > 0) { + for (intptr_t i = region.t; i < region.b; i++) { + for (intptr_t j = region.l; j < region.r; j++) { + ((uint32_t *) bits)[j + (i - delta) * stride / 4] = ((uint32_t *) bits)[j + i * stride / 4]; + } + } + } else { + for (intptr_t i = region.b - 1; i >= region.t; i--) { + for (intptr_t j = region.l; j < region.r; j++) { + ((uint32_t *) bits)[j + (i - delta) * stride / 4] = ((uint32_t *) bits)[j + i * stride / 4]; + } + } + } + } else { + if (delta > 0) { + for (intptr_t i = region.t; i < region.b; i++) { + for (intptr_t j = region.l; j < region.r; j++) { + ((uint32_t *) bits)[j - delta + i * stride / 4] = ((uint32_t *) bits)[j + i * stride / 4]; + } + } + } else { + // TODO. + } + } +} + +#define C0(p) ((p & 0x000000FF) >> 0x00) +#define C1(p) ((p & 0x0000FF00) >> 0x08) +#define C2(p) ((p & 0x00FF0000) >> 0x10) +#define C3(p) ((p & 0xFF000000) >> 0x18) + +__attribute__((optimize("-O2"))) +void BlurRegionOfImage(uint32_t *image, int width, int height, int stride, uint16_t *kernel, uintptr_t repeat) { + if (width <= 3 || height <= 3) { + return; + } + + for (int y = 0; y < height; y++) { + for (uintptr_t i = 0; i < repeat; i++) { + uint32_t *start = image + stride * y; + uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[1], f = start[2], g = 0; + uint32_t *u = start, *v = start + 3; + + for (int i = 0; i < width; i++, u++, v++) { + if (i + 3 < width) g = *v; + *u = (((C0(a) * kernel[0] + C0(b) * kernel[1] + C0(c) * kernel[2] + C0(d) * kernel[3] + C0(e) + * kernel[4] + C0(f) * kernel[5] + C0(g) * kernel[6]) >> 8) << 0x00) + + (((C1(a) * kernel[0] + C1(b) * kernel[1] + C1(c) * kernel[2] + C1(d) * kernel[3] + C1(e) + * kernel[4] + C1(f) * kernel[5] + C1(g) * kernel[6]) >> 8) << 0x08) + + (((C2(a) * kernel[0] + C2(b) * kernel[1] + C2(c) * kernel[2] + C2(d) * kernel[3] + C2(e) + * kernel[4] + C2(f) * kernel[5] + C2(g) * kernel[6]) >> 8) << 0x10) + + (C3(d) << 0x18); + a = b, b = c, c = d, d = e, e = f, f = g; + } + } + } + + for (int x = 0; x < width; x++) { + for (uintptr_t i = 0; i < repeat; i++) { + uint32_t *start = image + x; + uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[stride], f = start[stride * 2], g = 0; + uint32_t *u = start, *v = start + 3 * stride; + + for (int i = 0; i < height; i++, u += stride, v += stride) { + if (i + 3 < height) g = *v; + *u = (((C0(a) * kernel[0] + C0(b) * kernel[1] + C0(c) * kernel[2] + C0(d) * kernel[3] + C0(e) + * kernel[4] + C0(f) * kernel[5] + C0(g) * kernel[6]) >> 8) << 0x00) + + (((C1(a) * kernel[0] + C1(b) * kernel[1] + C1(c) * kernel[2] + C1(d) * kernel[3] + C1(e) + * kernel[4] + C1(f) * kernel[5] + C1(g) * kernel[6]) >> 8) << 0x08) + + (((C2(a) * kernel[0] + C2(b) * kernel[1] + C2(c) * kernel[2] + C2(d) * kernel[3] + C2(e) + * kernel[4] + C2(f) * kernel[5] + C2(g) * kernel[6]) >> 8) << 0x10) + + (C3(d) << 0x18); + a = b, b = c, c = d, d = e, e = f, f = g; + } + } + } +} + +__attribute__((optimize("-O2"))) +void BlurRegionOfImage(uint32_t *image, int width, int height, int stride, uintptr_t repeat) { + if (width <= 3 || height <= 3) { + return; + } + + for (uintptr_t i = 0; i < repeat; i++) { + uint32_t *start = image; + + for (int y = 0; y < height; y++) { + uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[1], f = start[2], g = 0; + uint32_t *u = start, *v = start + 3; + + for (int i = 0; i < width; i++, u++, v++) { + if (i + 3 < width) g = *v; + *u = (((C0(a) * 0x07 + C0(b) * 0x1A + C0(c) * 0x38 + C0(d) * 0x49 + C0(e) * 0x38 + C0(f) * 0x1A + C0(g) * 0x07) >> 8) << 0x00) + + (((C1(a) * 0x07 + C1(b) * 0x1A + C1(c) * 0x38 + C1(d) * 0x49 + C1(e) * 0x38 + C1(f) * 0x1A + C1(g) * 0x07) >> 8) << 0x08) + + (((C2(a) * 0x07 + C2(b) * 0x1A + C2(c) * 0x38 + C2(d) * 0x49 + C2(e) * 0x38 + C2(f) * 0x1A + C2(g) * 0x07) >> 8) << 0x10) + + (C3(d) << 0x18); + a = b, b = c, c = d, d = e, e = f, f = g; + } + + start += stride; + } + + start = image; + + for (int x = 0; x < width; x++) { + uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[stride], f = start[stride * 2], g = 0; + uint32_t *u = start, *v = start + 3 * stride; + + for (int i = 0; i < height; i++, u += stride, v += stride) { + if (i + 3 < height) g = *v; + *u = (((C0(a) * 0x07 + C0(b) * 0x1A + C0(c) * 0x38 + C0(d) * 0x49 + C0(e) * 0x38 + C0(f) * 0x1A + C0(g) * 0x07) >> 8) << 0x00) + + (((C1(a) * 0x07 + C1(b) * 0x1A + C1(c) * 0x38 + C1(d) * 0x49 + C1(e) * 0x38 + C1(f) * 0x1A + C1(g) * 0x07) >> 8) << 0x08) + + (((C2(a) * 0x07 + C2(b) * 0x1A + C2(c) * 0x38 + C2(d) * 0x49 + C2(e) * 0x38 + C2(f) * 0x1A + C2(g) * 0x07) >> 8) << 0x10) + + (C3(d) << 0x18); + a = b, b = c, c = d, d = e, e = f, f = g; + } + + start++; + } + } +} + +void Surface::Blur(EsRectangle region, EsRectangle clip) { +#if 1 + if (!EsRectangleClip(region, ES_RECT_4(0, width, 0, height), ®ion)) { + return; + } + + if (!EsRectangleClip(region, clip, ®ion)) { + return; + } + + BlurRegionOfImage((uint32_t *) ((uint8_t *) bits + region.l * 4 + region.t * stride), Width(region), Height(region), width, 1); +#else + EsPainter painter; + painter.clip = ES_RECT_4(0, width, 0, height); + painter.target = this; + EsDrawInvert(&painter, EsRectangleIntersection(region, clip)); +#endif +} + +__attribute__((optimize("-O2"))) +void Surface::BlendWindow(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, int material, uint8_t alpha, EsRectangle materialRegion) { + if (destinationPoint.x < 0) { sourceRegion.l -= destinationPoint.x; destinationPoint.x = 0; } + if (destinationPoint.y < 0) { sourceRegion.t -= destinationPoint.y; destinationPoint.y = 0; } + if (destinationPoint.x + sourceRegion.r - sourceRegion.l >= (int) width) sourceRegion.r -= destinationPoint.x + sourceRegion.r - sourceRegion.l - width; + if (destinationPoint.y + sourceRegion.b - sourceRegion.t >= (int) height) sourceRegion.b -= destinationPoint.y + sourceRegion.b - sourceRegion.t - height; + if (sourceRegion.r > (int) source->width) sourceRegion.r = source->width; + if (sourceRegion.b > (int) source->height) sourceRegion.b = source->height; + if (sourceRegion.r <= sourceRegion.l) return; + if (sourceRegion.b <= sourceRegion.t) return; + if (sourceRegion.l < 0) return; + if (sourceRegion.t < 0) return; + + EsRectangle destinationRegion = ES_RECT_4(destinationPoint.x, destinationPoint.x + Width(sourceRegion), + destinationPoint.y, destinationPoint.y + Height(sourceRegion)); + modifiedRegion = EsRectangleBounding(destinationRegion, modifiedRegion); + + if (material == BLEND_WINDOW_MATERIAL_GLASS || material == BLEND_WINDOW_MATERIAL_LIGHT_BLUR) { + int repeat = material == BLEND_WINDOW_MATERIAL_GLASS ? 3 : 1; + +#ifndef SIMPLE_GRAPHICS + if (alpha == 0xFF) { + BlurRegionOfImage((uint32_t *) bits + materialRegion.l + materialRegion.t * width, + Width(materialRegion), Height(materialRegion), width, repeat); + } else { + uint16_t kernel[] = { 0x07, 0x1A, 0x38, 0, 0x38, 0x1A, 0x07 }; + uint16_t sum = 0; + + for (uintptr_t i = 0; i < 7; i++) { + kernel[i] = kernel[i] * alpha / 0xFF; + sum += kernel[i]; + } + + kernel[3] = 0xFF - sum; + + BlurRegionOfImage((uint32_t *) bits + materialRegion.l + materialRegion.t * width, + Width(materialRegion), Height(materialRegion), width, kernel, repeat); + } +#else + (void) materialRegion; + (void) alpha; +#endif + } + + intptr_t y = sourceRegion.t; + + uint8_t *destinationPixel = (uint8_t *) bits + destinationPoint.y * stride + destinationPoint.x * 4; + uint8_t *sourcePixel = (uint8_t *) source->bits + sourceRegion.t * source->stride + sourceRegion.l * 4; + +#ifndef SIMPLE_GRAPHICS + __m128i constantAlphaMask = _mm_set1_epi32(0xFF000000); + __m128i constantAlpha = _mm_set1_epi32(alpha); + __m128i constant255 = _mm_set1_epi32(0xFF); +#endif + + while (y < sourceRegion.b) { + size_t countX = sourceRegion.r - sourceRegion.l; + + uint8_t *a = destinationPixel, *b = sourcePixel; + + while (countX >= 4) { + __m128i sourceValue = _mm_loadu_si128((__m128i *) sourcePixel); + +#ifndef SIMPLE_GRAPHICS + __m128i destinationValue = _mm_loadu_si128((__m128i *) destinationPixel); + + if (alpha != 0xFF) { + sourceValue = _mm_or_si128(_mm_andnot_si128(constantAlphaMask, sourceValue), + _mm_and_si128(_mm_slli_epi32(_mm_mullo_epi16(_mm_srli_epi32(sourceValue, 24), constantAlpha), 16), constantAlphaMask)); + } + + __m128i alpha = _mm_srli_epi32(sourceValue, 24); + + __m128i red = _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(sourceValue, 0), constant255), alpha); + __m128i green = _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(sourceValue, 8), constant255), alpha); + __m128i blue = _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(sourceValue, 16), constant255), alpha); + + alpha = _mm_sub_epi32(constant255, alpha); + + red = _mm_srli_epi32(_mm_add_epi32(red, _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(destinationValue, 0), constant255), alpha)), 8); + green = _mm_srli_epi32(_mm_add_epi32(green, _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(destinationValue, 8), constant255), alpha)), 8); + blue = _mm_srli_epi32(_mm_add_epi32(blue, _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(destinationValue, 16), constant255), alpha)), 8); + + sourceValue = _mm_or_si128(_mm_slli_epi32(red, 0), _mm_or_si128(_mm_slli_epi32(green, 8), _mm_slli_epi32(blue, 16))); +#endif + + _mm_storeu_si128((__m128i *) destinationPixel, sourceValue); + + destinationPixel += 16; + sourcePixel += 16; + countX -= 4; + } + + while (countX >= 1) { + uint32_t modified = *(uint32_t *) sourcePixel; +#ifndef SIMPLE_GRAPHICS + uint32_t original = *(uint32_t *) destinationPixel; + if (alpha != 0xFF) modified = (modified & 0xFFFFFF) | (((((modified & 0xFF000000) >> 24) * alpha) << 16) & 0xFF000000); + uint32_t m1 = (modified & 0xFF000000) >> 24; + uint32_t m2 = 255 - m1; + uint32_t r2 = m2 * (original & 0x00FF00FF); + uint32_t g2 = m2 * (original & 0x0000FF00); + uint32_t r1 = m1 * (modified & 0x00FF00FF); + uint32_t g1 = m1 * (modified & 0x0000FF00); + uint32_t result = (0x0000FF00 & ((g1 + g2) >> 8)) | (0x00FF00FF & ((r1 + r2) >> 8)); +#else + uint32_t result = modified; +#endif + + *(uint32_t *) destinationPixel = result; + + destinationPixel += 4; + sourcePixel += 4; + countX -= 1; + } + + y++; + destinationPixel = a + stride; + sourcePixel = b + source->stride; + } +} + +void Surface::Draw(Surface *source, EsRectangle destinationRegion, int sourceX, int sourceY, uint16_t alpha) { + modifiedRegion = EsRectangleBounding(destinationRegion, modifiedRegion); + EsRectangleClip(modifiedRegion, ES_RECT_4(0, width, 0, height), &modifiedRegion); + EsPainter painter; + painter.clip = ES_RECT_4(0, width, 0, height); + painter.target = this; + uint8_t *sourceBits = (uint8_t *) source->bits + source->stride * sourceY + 4 * sourceX; + EsDrawBitmap(&painter, destinationRegion, (uint32_t *) sourceBits, source->stride, alpha); +} + +void GraphicsUpdateScreen32(K_USER_BUFFER const uint8_t *_source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY, + uint32_t screenWidth, uint32_t screenHeight, uint32_t stride, volatile uint8_t *pixel) { + uint32_t *destinationRowStart = (uint32_t *) (pixel + destinationX * 4 + destinationY * stride); + const uint32_t *sourceRowStart = (const uint32_t *) _source; + + if (destinationX > screenWidth || sourceWidth > screenWidth - destinationX + || destinationY > screenHeight || sourceHeight > screenHeight - destinationY) { + KernelPanic("GraphicsUpdateScreen32 - Update region outside graphics target bounds.\n"); + } + + for (uintptr_t y = 0; y < sourceHeight; y++, destinationRowStart += stride / 4, sourceRowStart += sourceStride / 4) { + uint32_t *destination = destinationRowStart; + const uint32_t *source = sourceRowStart; + + for (uintptr_t x = 0; x < sourceWidth; x++) { + *destination = *source; + destination++, source++; + } + } +} + +void GraphicsUpdateScreen24(K_USER_BUFFER const uint8_t *_source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY, + uint32_t screenWidth, uint32_t screenHeight, uint32_t stride, volatile uint8_t *pixel) { + uint8_t *destinationRowStart = (uint8_t *) (pixel + destinationX * 3 + destinationY * stride); + const uint8_t *sourceRowStart = _source; + + if (destinationX > screenWidth || sourceWidth > screenWidth - destinationX + || destinationY > screenHeight || sourceHeight > screenHeight - destinationY) { + KernelPanic("GraphicsUpdateScreen32 - Update region outside graphics target bounds.\n"); + } + + for (uintptr_t y = 0; y < sourceHeight; y++, destinationRowStart += stride, sourceRowStart += sourceStride) { + uint8_t *destination = destinationRowStart; + const uint8_t *source = sourceRowStart; + + for (uintptr_t x = 0; x < sourceWidth; x++) { + *destination++ = *source++; + *destination++ = *source++; + *destination++ = *source++; + source++; + } + } +} + +void GraphicsDebugPutBlock32(uintptr_t x, uintptr_t y, bool toggle, + unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer) { + (void) screenWidth; + (void) screenHeight; + + if (toggle) { + linearBuffer[y * stride + x * 4 + 0] += 0x4C; + linearBuffer[y * stride + x * 4 + 1] += 0x4C; + linearBuffer[y * stride + x * 4 + 2] += 0x4C; + } else { + linearBuffer[y * stride + x * 4 + 0] = 0xFF; + linearBuffer[y * stride + x * 4 + 1] = 0xFF; + linearBuffer[y * stride + x * 4 + 2] = 0xFF; + } + + linearBuffer[(y + 1) * stride + (x + 1) * 4 + 0] = 0; + linearBuffer[(y + 1) * stride + (x + 1) * 4 + 1] = 0; + linearBuffer[(y + 1) * stride + (x + 1) * 4 + 2] = 0; +} + +void GraphicsDebugClearScreen32(unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer) { + for (uintptr_t i = 0; i < screenHeight; i++) { + for (uintptr_t j = 0; j < screenWidth * 4; j += 4) { + if (graphics.debuggerActive) { + linearBuffer[i * stride + j + 2] = 0x18; + linearBuffer[i * stride + j + 1] = 0x7E; + linearBuffer[i * stride + j + 0] = 0xCF; + } else { + linearBuffer[i * stride + j + 2] >>= 1; + linearBuffer[i * stride + j + 1] >>= 1; + linearBuffer[i * stride + j + 0] >>= 1; + } + } + } +} + +#undef C0 +#undef C1 +#undef C2 +#undef C3 + +#endif diff --git a/kernel/kernel.h b/kernel/kernel.h new file mode 100644 index 0000000..e3e879c --- /dev/null +++ b/kernel/kernel.h @@ -0,0 +1,194 @@ +#ifndef IMPLEMENTATION + +#define K_IN_CORE_KERNEL +#define K_PRIVATE +#include "module.h" + +////////////////////////////////// + +// TODO Determine the best values for these constants. + +// Wait 1 second before running the write behind thread. (Ignored if MM_AVAILABLE_PAGES() is below the low threshold.) +#define CC_WAIT_FOR_WRITE_BEHIND (1000) + +// Describes the virtual memory covering a section of a file. +#define CC_ACTIVE_SECTION_SIZE ((EsFileOffset) 262144) + +// Maximum number of active sections on the modified list. If exceeded, writers will wait for it to drop before retrying. +#define CC_MAX_MODIFIED (33554432 / CC_ACTIVE_SECTION_SIZE) + +// The size of the kernel's address space used for mapping active sections. +#if defined(ARCH_32) +#define CC_SECTION_BYTES (ClampIntptr(0, 64L * 1024 * 1024, pmm.commitFixedLimit * K_PAGE_SIZE / 4)) +#elif defined(ARCH_64) +#define CC_SECTION_BYTES (ClampIntptr(0, 1024L * 1024 * 1024, pmm.commitFixedLimit * K_PAGE_SIZE / 4)) +#endif + +// When we reach a critical number of pages, FIXED allocations start failing, +// and page faults are blocked, unless you are on a page generating thread (the modified page writer or the balancer). +#define MM_CRITICAL_AVAILABLE_PAGES_THRESHOLD (1048576 / K_PAGE_SIZE) + +// The number of pages at which balancing starts. +#define MM_LOW_AVAILABLE_PAGES_THRESHOLD (16777216 / K_PAGE_SIZE) + +// The number of pages past MM_LOW_AVAILABLE_PAGES_THRESHOLD to aim for when balancing. +#define MM_PAGES_TO_FIND_BALANCE (4194304 / K_PAGE_SIZE) + +// The number of pages in the zero list before signaling the page zeroing thread. +#define MM_ZERO_PAGE_THRESHOLD (16) + +// The amount of commit reserved specifically for page generating threads. +#define MM_CRITICAL_REMAINING_COMMIT_THRESHOLD (1048576 / K_PAGE_SIZE) + +// The number of objects that are trimmed from a MMObjectCache at a time. +#define MM_OBJECT_CACHE_TRIM_GROUP_COUNT (1024) + +// The current target maximum size for the object caches. (This uses the approximate sizes of objects.) +// We want to keep a reasonable amount of commit available at all times, +// since when the kernel is allocating memory it might not be able to wait for the caches to be trimmed without deadlock. +// So, try to keep the commit quota used by the object caches at most half the available space. +#define MM_NON_CACHE_MEMORY_PAGES() (pmm.commitFixed + pmm.commitPageable - pmm.approximateTotalObjectCacheBytes / K_PAGE_SIZE) +#define MM_OBJECT_CACHE_PAGES_MAXIMUM() ((pmm.commitLimit - MM_NON_CACHE_MEMORY_PAGES()) / 2) + +#define PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES (16) +#define POOL_CACHE_COUNT (16) + +////////////////////////////////// + +#include + +#define EsAssertionFailure(file, line) KernelPanic("%z:%d - EsAssertionFailure called.\n", file, line) + +#ifdef ARCH_X86_64 +#include "x86_64.cpp" +#endif + +struct AsyncTask { + KAsyncTaskCallback callback; + void *argument; + struct MMSpace *addressSpace; +}; + +struct CPULocalStorage { + // Must be the first fields; used in __cyg_profile_func_enter. + uintptr_t kernelFunctionCallCount; + + struct Thread *currentThread, + *idleThread, + *asyncTaskThread; + + struct InterruptContext *panicContext; + + bool irqSwitchThread, schedulerReady, inIRQ; + + unsigned processorID; + size_t spinlockCount; + + ArchCPU *archCPU; + + // TODO Have separate interrupt task threads and system worker threads (with no task limit). +#define MAX_ASYNC_TASKS (256) + volatile AsyncTask asyncTasks[MAX_ASYNC_TASKS]; + volatile uint8_t asyncTasksRead, asyncTasksWrite; +}; + +////////////////////////////////// + +void KernelInitialise(); +void KernelShutdown(uintptr_t action); + +void ArchInitialise(); +void ArchShutdown(uintptr_t action); + +extern "C" void ArchResetCPU(); +extern "C" void ArchSpeakerBeep(); +extern "C" void ArchNextTimer(size_t ms); // Receive a TIMER_INTERRUPT in ms ms. +InterruptContext *ArchInitialiseThread(uintptr_t kernelStack, uintptr_t kernelStackSize, struct Thread *thread, + uintptr_t startAddress, uintptr_t argument1, uintptr_t argument2, + bool userland, uintptr_t stack, uintptr_t userStackSize); + +uint64_t timeStampTicksPerMs; + +EsUniqueIdentifier installationID; // The identifier of this OS installation, given to us by the bootloader. +struct Process *desktopProcess; + +KSpinlock ipiLock; + +////////////////////////////////// + +#endif + +#include +#include +#include + +#include "memory.cpp" + +#ifndef IMPLEMENTATION +#include +#include +#else +#include +#endif + +#include "objects.cpp" +#include "syscall.cpp" +#include "scheduler.cpp" +#include "synchronisation.cpp" +#include "drivers.cpp" +#include "elf.cpp" +#include "graphics.cpp" +#include "cache.cpp" +#include "files.cpp" +#include "windows.cpp" +#include "networking.cpp" + +#ifdef ENABLE_POSIX_SUBSYSTEM +#include "posix.cpp" +#endif + +#include "terminal.cpp" + +#ifdef IMPLEMENTATION + +////////////////////////////////// + +extern "C" uint64_t KGetTimeInMs() { + if (!timeStampTicksPerMs) return 0; + return scheduler.timeMs; +} + +uint64_t KGetTimeStampTicksPerMs() { + return timeStampTicksPerMs; +} + +uint64_t KGetTimeStampTicksPerUs() { + return timeStampTicksPerMs / 1000; +} + +bool KInIRQ() { + return GetLocalStorage()->inIRQ; +} + +bool KBootedFromEFI() { + extern uint32_t bootloaderID; + return bootloaderID == 2; +} + +void KSwitchThreadAfterIRQ() { + GetLocalStorage()->irqSwitchThread = true; +} + +EsUniqueIdentifier KGetBootIdentifier() { + return installationID; +} + +////////////////////////////////// + +#ifdef ARCH_X86_64 +#include "x86_64.h" +#include +#include "x86_64.cpp" +#endif + +#endif diff --git a/kernel/main.cpp b/kernel/main.cpp new file mode 100644 index 0000000..6a0f265 --- /dev/null +++ b/kernel/main.cpp @@ -0,0 +1,29 @@ +// TODO Prevent Meltdown/Spectre exploits. +// TODO Kernel debugger. +// TODO Passing data to userspace - zeroing padding bits of structures. +// TODO Remove file extensions? +// TODO Thread-local variables for native applications (already working under the POSIX subsystem). + +#include "kernel.h" +#define IMPLEMENTATION +#include "kernel.h" + +extern "C" void KernelMain() { + scheduler.SpawnProcess(PROCESS_KERNEL); // Spawn the kernel process. + ArchInitialise(); // Start processors and initialise CPULocalStorage. + scheduler.started = true; // Start the pre-emptive scheduler. + // Continues in KernelInitialise. +} + +void KernelInitialise() { + desktopProcess = scheduler.SpawnProcess(PROCESS_DESKTOP); // Spawn the desktop process. + DriversInitialise(); // Load the root device. + desktopProcess->Start(EsLiteral(K_DESKTOP_EXECUTABLE)); // Start the desktop process. +} + +void KernelShutdown(uintptr_t action) { + scheduler.Shutdown(); // Kill user processes. + FSShutdown(); // Flush file cache and unmount filesystems. + DriversShutdown(); // Inform drivers of shutdown. + ArchShutdown(action); // Power off or restart the computer. +} diff --git a/kernel/memory.cpp b/kernel/memory.cpp new file mode 100644 index 0000000..c155410 --- /dev/null +++ b/kernel/memory.cpp @@ -0,0 +1,2305 @@ +// TODO Soft page faults. +// TODO Paging file. +// TODO Large pages. +// TODO Locking memory. +// TODO No execute permissions. +// TODO NUMA? +// TODO Cache coloring. + +#define GUARD_PAGES + +#ifndef IMPLEMENTATION + +// A contiguous range of addresses in an MMSpace. + +struct MMRegion { + uintptr_t baseAddress; + size_t pageCount; + uint32_t flags; + + struct { + union { + struct { + uintptr_t offset; + } physical; + + struct { + struct MMSharedRegion *region; + uintptr_t offset; + } shared; + + struct { + struct FSFile *node; + EsFileOffset offset; + size_t zeroedBytes; + uint64_t fileHandleFlags; + } file; + + struct { + RangeSet commit; // TODO Currently guarded by MMSpace::reserveMutex, maybe give it its own mutex? + size_t commitPageCount; + MMRegion *guardBefore, *guardAfter; + } normal; + }; + + KWriterLock pin; // Take SHARED when using the region in a system call. Take EXCLUSIVE to free the region. + KMutex mapMutex; // Access the page tables for a specific region, e.g. to atomically check if a mapping exists and if not, create it. + } data; + + union { + struct { + AVLItem itemBase; + + union { + AVLItem itemSize; + LinkedItem itemNonGuard; + }; + }; + + struct { + bool used; + } core; + }; +}; + +// A virtual address space. +// One per process. + +struct MMSpace { + VIRTUAL_ADDRESS_SPACE_DATA(); // Architecture specific data. + + AVLTree // Key = + freeRegionsBase, // Base address + freeRegionsSize, // Page count + usedRegions; // Base address + LinkedList usedRegionsNonGuard; + + KMutex reserveMutex; // Acquire to Access the region trees. + + bool user; // Regions in the space may be accessed from userspace. + uint64_t commit; // An *approximate* commit in pages. TODO Better memory usage tracking. +}; + +// A physical page of memory. + +struct MMPageFrame { + volatile enum : uint8_t { + // Frames that can't be used. + UNUSABLE, // Not connected to RAM. + BAD, // Connected to RAM with errors. TODO + + // Frames that aren't referenced. + ZEROED, // Cleared with zeros. + FREE, // Contains whatever data is had when it was freed. + + // Frames that are referenced by an invalid [shared] page mapping. + // In shared regions, each invalid page mapping points to the shared page mapping. + // This means only one variable must be updated to reuse the frame. + STANDBY, // Data has been written to page file or backing file. + + // Frames that are referenced by one or more valid page mappings. + ACTIVE, + } state; + + volatile uint8_t flags; + + // The reference to this frame in a CCCachedSection. + volatile uintptr_t *cacheReference; + + union { + struct { + // For STANDBY, MODIFIED, UPDATED, ZEROED and FREE. + // The frame's position in a list. + volatile uintptr_t next, *previous; + } list; + + struct { + // For ACTIVE. + // For file pages, this tracks how many valid page table entries point to this frame. + volatile uintptr_t references; + } active; + }; +}; + +// A shared memory region. + +struct MMSharedRegion { + size_t sizeBytes; + volatile size_t handles; + KMutex mutex; + LinkedItem namedItem; + char cName[ES_SHARED_MEMORY_NAME_MAX_LENGTH + 1]; + void *data; +}; + +// An object pool, for fast allocation and deallocation of objects of constant size. +// (There is no guarantee that the objects will be contiguous in memory.) + +struct Pool { + void *Add(size_t elementSize); // Aligned to the size of a pointer. + void Remove(void *element); + + size_t elementSize; + void *cache[POOL_CACHE_COUNT]; + size_t cacheEntries; + KMutex mutex; +}; + +// Physical memory manager state. + +struct PMM { + MMPageFrame *pageFrames; + + uintptr_t firstFreePage; + uintptr_t firstZeroedPage; + uintptr_t firstStandbyPage, lastStandbyPage; + Bitset freeOrZeroedPageBitset; // Used for allocating large pages. + + uintptr_t countZeroedPages, countFreePages, countStandbyPages, countActivePages; + +#define MM_REMAINING_COMMIT() (pmm.commitLimit - pmm.commitPageable - pmm.commitFixed) + int64_t commitFixed, commitPageable, + commitFixedLimit, commitLimit; + + // Acquire to: + KMutex commitMutex, // (Un)commit pages. + pageFrameMutex; // Allocate or free pages. + + KMutex pmManipulationLock; + KSpinlock pmManipulationProcessorLock; + void *pmManipulationRegion; + + Thread *zeroPageThread, *balanceThread; + KEvent zeroPageEvent; + + LinkedList objectCacheList; + KMutex objectCacheListMutex; + + // Events for when the number of available pages is low. +#define MM_AVAILABLE_PAGES() (pmm.countZeroedPages + pmm.countFreePages + pmm.countStandbyPages) + KEvent availableCritical, availableLow; + KEvent availableNotCritical; + + // Event for when the object cache should be trimmed. +#define MM_OBJECT_CACHE_SHOULD_TRIM() (pmm.approximateTotalObjectCacheBytes / K_PAGE_SIZE > MM_OBJECT_CACHE_PAGES_MAXIMUM()) + uintptr_t approximateTotalObjectCacheBytes; + KEvent trimObjectCaches; + + // These variables will be cleared if the object they point to is removed. + // See MMUnreserve and Scheduler::RemoveProcess. + Process *nextProcessToBalance; + MMRegion *nextRegionToBalance; + uintptr_t balanceResumePosition; +}; + +// See MMPhysicalAllocate. +alignas(K_PAGE_SIZE) uint8_t earlyZeroBuffer[K_PAGE_SIZE]; + +// Memory spaces. +// kernelMMSpace - Whence the kernel allocates memory. +// coreMMSpace - Whence other memory managers allocate memory. + +extern MMSpace _kernelMMSpace, _coreMMSpace; +#define kernelMMSpace (&_kernelMMSpace) +#define coreMMSpace (&_coreMMSpace) + +// Constants. + +// MMArchMapPage. +#define MM_MAP_PAGE_NOT_CACHEABLE (1 << 0) +#define MM_MAP_PAGE_USER (1 << 1) +#define MM_MAP_PAGE_OVERWRITE (1 << 2) +#define MM_MAP_PAGE_COMMIT_TABLES_NOW (1 << 3) +#define MM_MAP_PAGE_READ_ONLY (1 << 4) +#define MM_MAP_PAGE_COPIED (1 << 5) +#define MM_MAP_PAGE_NO_NEW_TABLES (1 << 6) +#define MM_MAP_PAGE_FRAME_LOCK_ACQUIRED (1 << 7) +#define MM_MAP_PAGE_WRITE_COMBINING (1 << 8) +#define MM_MAP_PAGE_IGNORE_IF_MAPPED (1 << 9) + +// MMArchUnmapPages. +#define MM_UNMAP_PAGES_FREE (1 << 0) +#define MM_UNMAP_PAGES_FREE_COPIED (1 << 1) +#define MM_UNMAP_PAGES_BALANCE_FILE (1 << 2) + +// MMPhysicalAllocate. +// --> Moved to module.h. + +// MMHandlePageFault. +#define MM_HANDLE_PAGE_FAULT_WRITE (1 << 0) +#define MM_HANDLE_PAGE_FAULT_LOCK_ACQUIRED (1 << 1) +#define MM_HANDLE_PAGE_FAULT_FOR_SUPERVISOR (1 << 2) + +// MMStandardAllocate - flags passed through into MMReserve. +// --> Moved to module.h. + +// MMReserve - region types. +#define MM_REGION_PHYSICAL (0x0100) // The region is mapped to device memory. +#define MM_REGION_NORMAL (0x0200) // A normal region. +#define MM_REGION_SHARED (0x0400) // The region's contents is shared via a MMSharedRegion. +#define MM_REGION_GUARD (0x0800) // A guard region, to make sure we don't accidentally go into other regions. +#define MM_REGION_CACHE (0x1000) // Used for the file cache. +#define MM_REGION_FILE (0x2000) // A mapped file. + +#define MM_SHARED_ENTRY_PRESENT (1) + +// Architecture-dependent functions. + +void MMArchMapPage(MMSpace *space, uintptr_t physicalAddress, uintptr_t virtualAddress, unsigned flags); +void MMArchUnmapPages(MMSpace *space, uintptr_t virtualAddressStart, uintptr_t pageCount, unsigned flags, size_t unmapMaximum = 0, uintptr_t *resumePosition = nullptr); +void MMArchInvalidatePages(uintptr_t virtualAddressStart, uintptr_t pageCount); +bool MMArchHandlePageFault(uintptr_t address, uint32_t flags); +uintptr_t MMArchTranslateAddress(MMSpace *space, uintptr_t virtualAddress, bool writeAccess); +void MMArchInitialiseVAS(); +bool MMArchInitialiseUserSpace(MMSpace *space); +bool MMArchCommitPageTables(MMSpace *space, MMRegion *region); +bool MMArchMakePageWritable(MMSpace *space, uintptr_t virtualAddress); +void MMFreeVAS(MMSpace *space); +void MMFinalizeVAS(MMSpace *space); + +// Forward declarations. + +bool MMHandlePageFault(MMSpace *space, uintptr_t address, unsigned flags); +bool MMUnmapFilePage(uintptr_t frameNumber, bool justLoaded = false); // Returns true if the page became inactive. + +// Public memory manager functions. + +void *MMMapPhysical(MMSpace *space, uintptr_t address, size_t bytes, uint64_t caching); +void *MMStandardAllocate(MMSpace *space, size_t bytes, uint32_t flags, void *baseAddress, bool commitAll); +bool MMFree(MMSpace *space, void *address, size_t expectedSize, bool userOnly); +MMRegion *MMReserve(MMSpace *space, size_t bytes, unsigned flags, uintptr_t forcedAddress = 0, bool generateGuardPages = false); +bool MMCommit(uint64_t bytes, bool fixed); +void MMDecommit(uint64_t bytes, bool fixed); +bool MMCommitRange(MMSpace *space, MMRegion *region, uintptr_t offset, size_t pages); +bool MMDecommitRange(MMSpace *space, MMRegion *region, uintptr_t offset, size_t pages); +uintptr_t MMPhysicalAllocate(unsigned flags, uintptr_t count, uintptr_t align, uintptr_t below); +void MMInitialise(); +MMSharedRegion *MMSharedCreateRegion(size_t sizeBytes, bool fixed, uintptr_t below); +MMSharedRegion *MMSharedOpenRegion(const char *name, size_t nameBytes, + size_t fallbackSizeBytes /* If the region doesn't exist, the size it should be created with. If 0, it'll fail. */, uint64_t flags); +MMRegion *MMFindAndPinRegion(MMSpace *space, uintptr_t address, uintptr_t size); +void MMUnpinRegion(MMSpace *space, MMRegion *region); +void MMSpaceDestroy(MMSpace *space); +bool MMSpaceInitialise(MMSpace *space); +void MMPhysicalFree(uintptr_t page, bool mutexAlreadyAcquired, size_t count); +void MMUnreserve(MMSpace *space, MMRegion *remove, bool unmapPages, bool guardRegion = false); +MMRegion *MMFindRegion(MMSpace *space, uintptr_t address); +void *MMMapFile(MMSpace *space, struct FSFile *node, EsFileOffset offset, size_t bytes, + int protection, void *baseAddress, size_t zeroedBytes = 0, uint32_t additionalFlags = ES_FLAGS_DEFAULT); + +// Physical memory information from the bootloader. + +struct PhysicalMemoryRegion { + uint64_t baseAddress; + + // The memory map the BIOS provides gives the information in pages. + uint64_t pageCount; +}; + +extern PhysicalMemoryRegion *physicalMemoryRegions; +extern size_t physicalMemoryRegionsCount; +extern size_t physicalMemoryRegionsPagesCount; +extern size_t physicalMemoryOriginalPagesCount; +extern size_t physicalMemoryRegionsIndex; +extern uintptr_t physicalMemoryHighest; + +// Physical memory manipulation. + +void PMZero(uintptr_t *pages, size_t pageCount, + bool contiguous /* True if `pages[0]` contains the base address and all other are contiguous, false if `pages` contains an array of physical addresses. */); +void PMZeroPartial(uintptr_t page, uintptr_t start, uintptr_t end); +void PMCheckZeroed(uintptr_t page); +void PMCopy(uintptr_t page, void *source, size_t pageCount); + +#endif + +#ifdef IMPLEMENTATION + +// Globals. + +MMSpace _kernelMMSpace, _coreMMSpace; +PMM pmm; +MMActiveSectionManager activeSectionManager; + +MMRegion *mmCoreRegions = (MMRegion *) MM_CORE_REGIONS_START; +size_t mmCoreRegionCount, mmCoreRegionArrayCommit; + +LinkedList mmNamedSharedRegions; +KMutex mmNamedSharedRegionsMutex; + +// Code! + +void MMUpdateAvailablePageCount(bool increase) { + if (MM_AVAILABLE_PAGES() >= MM_CRITICAL_AVAILABLE_PAGES_THRESHOLD) { + KEventSet(&pmm.availableNotCritical, false, true); + KEventReset(&pmm.availableCritical); + } else { + KEventReset(&pmm.availableNotCritical); + KEventSet(&pmm.availableCritical, false, true); + + if (!increase) { + KernelLog(LOG_ERROR, "Memory", "critical page limit hit", + "Critical number of available pages remain: %d (%dKB).\n", MM_AVAILABLE_PAGES(), MM_AVAILABLE_PAGES() * K_PAGE_SIZE / 1024); + } + } + + if (MM_AVAILABLE_PAGES() >= MM_LOW_AVAILABLE_PAGES_THRESHOLD) { + KEventReset(&pmm.availableLow); + } else { + KEventSet(&pmm.availableLow, false, true); + } +} + +void MMPhysicalInsertZeroedPage(uintptr_t page) { + if (GetCurrentThread() != pmm.zeroPageThread) { + KernelPanic("MMPhysicalInsertZeroedPage - Inserting a zeroed page not on the MMZeroPageThread.\n"); + } + + MMPageFrame *frame = pmm.pageFrames + page; + frame->state = MMPageFrame::ZEROED; + + { + frame->list.next = pmm.firstZeroedPage; + frame->list.previous = &pmm.firstZeroedPage; + if (pmm.firstZeroedPage) pmm.pageFrames[pmm.firstZeroedPage].list.previous = &frame->list.next; + pmm.firstZeroedPage = page; + } + + pmm.countZeroedPages++; + pmm.freeOrZeroedPageBitset.Put(page); + + MMUpdateAvailablePageCount(true); +} + +void MMPhysicalInsertFreePage(uintptr_t page) { + MMPageFrame *frame = pmm.pageFrames + page; + frame->state = MMPageFrame::FREE; + + { + frame->list.next = pmm.firstFreePage; + frame->list.previous = &pmm.firstFreePage; + if (pmm.firstFreePage) pmm.pageFrames[pmm.firstFreePage].list.previous = &frame->list.next; + pmm.firstFreePage = page; + } + + pmm.freeOrZeroedPageBitset.Put(page); + pmm.countFreePages++; + + if (pmm.countFreePages > MM_ZERO_PAGE_THRESHOLD) { + KEventSet(&pmm.zeroPageEvent, false, true); + } + + MMUpdateAvailablePageCount(true); +} + +void MMPhysicalActivatePages(uintptr_t pages, uintptr_t count, unsigned flags) { + (void) flags; + + KMutexAssertLocked(&pmm.pageFrameMutex); + + for (uintptr_t i = 0; i < count; i++) { + MMPageFrame *frame = pmm.pageFrames + pages + i; + + if (frame->state == MMPageFrame::FREE) { + pmm.countFreePages--; + } else if (frame->state == MMPageFrame::ZEROED) { + pmm.countZeroedPages--; + } else if (frame->state == MMPageFrame::STANDBY) { + pmm.countStandbyPages--; + + if (pmm.lastStandbyPage == pages + i) { + if (frame->list.previous == &pmm.firstStandbyPage) { + // There are no more pages in the list. + pmm.lastStandbyPage = 0; + } else { + pmm.lastStandbyPage = ((uintptr_t) frame->list.previous - (uintptr_t) pmm.pageFrames) / sizeof(MMPageFrame); + } + } + } else { + KernelPanic("MMPhysicalActivatePages - Corrupt page frame database (4).\n"); + } + + // Unlink the frame from its list. + *frame->list.previous = frame->list.next; + if (frame->list.next) pmm.pageFrames[frame->list.next].list.previous = frame->list.previous; + + EsMemoryZero(frame, sizeof(MMPageFrame)); + frame->state = MMPageFrame::ACTIVE; + } + + pmm.countActivePages += count; + MMUpdateAvailablePageCount(false); +} + +uintptr_t MMPhysicalAllocate(unsigned flags, uintptr_t count, uintptr_t align, uintptr_t below) { + bool mutexAlreadyAcquired = flags & MM_PHYSICAL_ALLOCATE_LOCK_ACQUIRED; + if (!mutexAlreadyAcquired) KMutexAcquire(&pmm.pageFrameMutex); + else KMutexAssertLocked(&pmm.pageFrameMutex); + EsDefer(if (!mutexAlreadyAcquired) KMutexRelease(&pmm.pageFrameMutex);); + + intptr_t commitNow = count * K_PAGE_SIZE; + + if (flags & MM_PHYSICAL_ALLOCATE_COMMIT_NOW) { + if (!MMCommit(commitNow, true)) return 0; + } else commitNow = 0; + + bool simple = count == 1 && align == 1 && below == 0; + + if (physicalMemoryRegionsPagesCount) { + // Early page allocation before the page frame database is initialised. + + if (!simple) { + KernelPanic("MMPhysicalAllocate - Non-simple allocation before initialisation of the page frame database.\n"); + } + + uintptr_t i = physicalMemoryRegionsIndex; + + while (!physicalMemoryRegions[i].pageCount) { + i++; + + if (i == physicalMemoryRegionsCount) { + KernelPanic("MMPhysicalAllocate - Expected more pages in physical regions.\n"); + } + } + + PhysicalMemoryRegion *region = physicalMemoryRegions + i; + uintptr_t returnValue = region->baseAddress; + + region->baseAddress += K_PAGE_SIZE; + region->pageCount--; + physicalMemoryRegionsPagesCount--; + physicalMemoryRegionsIndex = i; + + if (flags & MM_PHYSICAL_ALLOCATE_ZEROED) { + // TODO Hack! + MMArchMapPage(coreMMSpace, returnValue, (uintptr_t) earlyZeroBuffer, + MM_MAP_PAGE_OVERWRITE | MM_MAP_PAGE_NO_NEW_TABLES | MM_MAP_PAGE_FRAME_LOCK_ACQUIRED); + EsMemoryZero(earlyZeroBuffer, K_PAGE_SIZE); + } + + return returnValue; + } else if (!simple) { + // Slow path. + // TODO Use standby pages. + + uintptr_t pages = pmm.freeOrZeroedPageBitset.Get(count, align, below); + if (pages == (uintptr_t) -1) goto fail; + MMPhysicalActivatePages(pages, count, flags); + uintptr_t address = pages << K_PAGE_BITS; + if (flags & MM_PHYSICAL_ALLOCATE_ZEROED) PMZero(&address, count, true); + return address; + } else { + uintptr_t page = 0; + bool notZeroed = false; + + if (!page) page = pmm.firstZeroedPage; + if (!page) page = pmm.firstFreePage, notZeroed = true; + if (!page) page = pmm.lastStandbyPage, notZeroed = true; + if (!page) goto fail; + + MMPageFrame *frame = pmm.pageFrames + page; + + if (frame->state == MMPageFrame::ACTIVE) { + KernelPanic("MMPhysicalAllocate - Corrupt page frame database (2).\n"); + } + + if (frame->state == MMPageFrame::STANDBY) { + // EsPrint("Clear RT %x\n", frame); + + if (*frame->cacheReference != ((page << K_PAGE_BITS) | MM_SHARED_ENTRY_PRESENT)) { + KernelPanic("MMPhysicalAllocate - Corrupt shared reference back pointer in frame %x.\n", frame); + } + + // Clear the entry in the CCCachedSection that referenced this standby frame. + *frame->cacheReference = 0; + + // TODO If the CCCachedSection is empty, remove it from its CCSpace. + } else { + pmm.freeOrZeroedPageBitset.Take(page); + } + + MMPhysicalActivatePages(page, 1, flags); + + // EsPrint("PAGE FRAME ALLOCATE: %x\n", page << K_PAGE_BITS); + + uintptr_t address = page << K_PAGE_BITS; + if (notZeroed && (flags & MM_PHYSICAL_ALLOCATE_ZEROED)) PMZero(&address, 1, false); + // if (!notZeroed) PMCheckZeroed(address); + return address; + } + + fail:; + + if (!(flags & MM_PHYSICAL_ALLOCATE_CAN_FAIL)) { + EsPrint("Out of memory. Committed %d/%d fixed and %d pageable out of a maximum %d.\n", pmm.commitFixed, pmm.commitFixedLimit, pmm.commitPageable, pmm.commitLimit); + KernelPanic("MMPhysicalAllocate - Out of memory.\n"); + } + + MMDecommit(commitNow, true); + return 0; +} + +void MMPhysicalFree(uintptr_t page, bool mutexAlreadyAcquired, size_t count) { + if (!page) KernelPanic("MMPhysicalFree - Invalid page.\n"); + if (mutexAlreadyAcquired) KMutexAssertLocked(&pmm.pageFrameMutex); + else KMutexAcquire(&pmm.pageFrameMutex); + if (physicalMemoryRegionsPagesCount) KernelPanic("MMPhysicalFree - PMM not yet initialised.\n"); + + page >>= K_PAGE_BITS; + + for (uintptr_t i = 0; i < count; i++, page++) { + MMPageFrame *frame = pmm.pageFrames + page; + + if (frame->state == MMPageFrame::FREE) { + KernelPanic("MMPhysicalFree - Attempting to free a FREE page.\n"); + } + + if (pmm.commitFixedLimit) { + pmm.countActivePages--; + } + + MMPhysicalInsertFreePage(page); + } + + if (!mutexAlreadyAcquired) KMutexRelease(&pmm.pageFrameMutex); +} + +void MMCheckUnusable(uintptr_t physicalStart, size_t bytes) { + for (uintptr_t i = physicalStart / K_PAGE_SIZE; i < (physicalStart + bytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE + && i < physicalMemoryHighest / K_PAGE_SIZE; i++) { + if (pmm.pageFrames[i].state != MMPageFrame::UNUSABLE) { + KernelPanic("MMCheckUnusable - Page frame at address %x should be unusable.\n", i * K_PAGE_SIZE); + } + } +} + +MMRegion *MMFindRegion(MMSpace *space, uintptr_t address) { + KMutexAssertLocked(&space->reserveMutex); + + if (space == coreMMSpace) { + for (uintptr_t i = 0; i < mmCoreRegionCount; i++) { + MMRegion *region = mmCoreRegions + i; + + if (region->core.used && region->baseAddress <= address + && region->baseAddress + region->pageCount * K_PAGE_SIZE > address) { + return region; + } + } + } else { + AVLItem *item = TreeFind(&space->usedRegions, MakeShortKey(address), TREE_SEARCH_LARGEST_BELOW_OR_EQUAL); + if (!item) return nullptr; + + MMRegion *region = item->thisItem; + if (region->baseAddress > address) KernelPanic("MMFindRegion - Broken usedRegions tree.\n"); + if (region->baseAddress + region->pageCount * K_PAGE_SIZE <= address) return nullptr; + return region; + } + + return nullptr; +} + +uintptr_t MMSharedLookupPage(MMSharedRegion *region, uintptr_t pageIndex) { + KMutexAcquire(®ion->mutex); + if (pageIndex >= region->sizeBytes >> K_PAGE_BITS) KernelPanic("MMSharedLookupPage - Page %d out of range in region %x.\n", pageIndex, region); + uintptr_t entry = ((uintptr_t *) region->data)[pageIndex]; + KMutexRelease(®ion->mutex); + return entry; +} + +bool MMHandlePageFault(MMSpace *space, uintptr_t address, unsigned faultFlags) { + // EsPrint("HandlePageFault: %x/%x/%x\n", space, address, faultFlags); + + address &= ~(K_PAGE_SIZE - 1); + + bool lockAcquired = faultFlags & MM_HANDLE_PAGE_FAULT_LOCK_ACQUIRED; + MMRegion *region; + + if (!lockAcquired && MM_AVAILABLE_PAGES() < MM_CRITICAL_AVAILABLE_PAGES_THRESHOLD && GetCurrentThread() && !GetCurrentThread()->isPageGenerator) { + KernelLog(LOG_ERROR, "Memory", "waiting for non-critical state", "Page fault on non-generator thread, waiting for more available pages.\n"); + KEventWait(&pmm.availableNotCritical); + } + + { + if (!lockAcquired) KMutexAcquire(&space->reserveMutex); + else KMutexAssertLocked(&space->reserveMutex); + EsDefer(if (!lockAcquired) KMutexRelease(&space->reserveMutex)); + + // Find the region, and pin it (so it can't be freed). + region = MMFindRegion(space, address); + if (!region) return false; + if (!KWriterLockTake(®ion->data.pin, K_LOCK_SHARED, true /* poll */)) return false; + } + + EsDefer(KWriterLockReturn(®ion->data.pin, K_LOCK_SHARED)); + KMutexAcquire(®ion->data.mapMutex); + EsDefer(KMutexRelease(®ion->data.mapMutex)); + + if (MMArchTranslateAddress(space, address, faultFlags & MM_HANDLE_PAGE_FAULT_WRITE)) { + // Spurious page fault. + return true; + } + + bool copyOnWrite = false, markModified = false; + + if (faultFlags & MM_HANDLE_PAGE_FAULT_WRITE) { + if (region->flags & MM_REGION_COPY_ON_WRITE) { + // This is copy-on-write page that needs to be copied. + copyOnWrite = true; + } else if (region->flags & MM_REGION_READ_ONLY) { + // The page was read-only. + KernelLog(LOG_ERROR, "Memory", "read only page fault", "MMHandlePageFault - Page was read only.\n"); + return false; + } else { + // We mapped the page as read-only so we could track whether it has been written to. + // It has now been written to. + markModified = true; + } + } + + uintptr_t offsetIntoRegion = address - region->baseAddress; + uint64_t needZeroPages = 0; + bool zeroPage = true; + + if (space->user) { + needZeroPages = MM_PHYSICAL_ALLOCATE_ZEROED; + zeroPage = false; + } + + unsigned flags = ES_FLAGS_DEFAULT; + + if (space->user) flags |= MM_MAP_PAGE_USER; + if (region->flags & MM_REGION_NOT_CACHEABLE) flags |= MM_MAP_PAGE_NOT_CACHEABLE; + if (region->flags & MM_REGION_WRITE_COMBINING) flags |= MM_MAP_PAGE_WRITE_COMBINING; + if (!markModified && !(region->flags & MM_REGION_FIXED) && (region->flags & MM_REGION_FILE)) flags |= MM_MAP_PAGE_READ_ONLY; + + if (region->flags & MM_REGION_PHYSICAL) { + MMArchMapPage(space, region->data.physical.offset + address - region->baseAddress, address, flags); + return true; + } else if (region->flags & MM_REGION_SHARED) { + MMSharedRegion *sharedRegion = region->data.shared.region; + + if (!sharedRegion->handles) { + KernelPanic("MMHandlePageFault - Shared region has no handles.\n"); + } + + KMutexAcquire(&sharedRegion->mutex); + + uintptr_t offset = address - region->baseAddress + region->data.shared.offset; + + if (offset >= sharedRegion->sizeBytes) { + KMutexRelease(&sharedRegion->mutex); + KernelLog(LOG_ERROR, "Memory", "outside shared size", "MMHandlePageFault - Attempting to access shared memory past end of region.\n"); + return false; + } + + uintptr_t *entry = (uintptr_t *) sharedRegion->data + (offset / K_PAGE_SIZE); + + if (*entry & MM_SHARED_ENTRY_PRESENT) zeroPage = false; + else *entry = MMPhysicalAllocate(needZeroPages) | MM_SHARED_ENTRY_PRESENT; + + MMArchMapPage(space, *entry & ~(K_PAGE_SIZE - 1), address, flags); + if (zeroPage) EsMemoryZero((void *) address, K_PAGE_SIZE); + KMutexRelease(&sharedRegion->mutex); + return true; + } else if (region->flags & MM_REGION_FILE) { + if (address >= region->baseAddress + (region->pageCount << K_PAGE_BITS) - region->data.file.zeroedBytes) { + // EsPrint("%x:%d\n", address, needZeroPages); + MMArchMapPage(space, MMPhysicalAllocate(needZeroPages), address, (flags & ~MM_MAP_PAGE_READ_ONLY) | MM_MAP_PAGE_COPIED); + if (zeroPage) EsMemoryZero((void *) address, K_PAGE_SIZE); + return true; + } + + if (copyOnWrite) { + doCopyOnWrite:; + uintptr_t existingTranslation = MMArchTranslateAddress(space, address); + + if (existingTranslation) { + // We need to use PMCopy, because as soon as the page is mapped for the user, + // other threads can access it. + uintptr_t page = MMPhysicalAllocate(ES_FLAGS_DEFAULT); + PMCopy(page, (void *) address, 1); + MMArchUnmapPages(space, address, 1, MM_UNMAP_PAGES_BALANCE_FILE); + MMArchMapPage(space, page, address, (flags & ~MM_MAP_PAGE_READ_ONLY) | MM_MAP_PAGE_COPIED); + return true; + } else { + // EsPrint("Write to unmapped, %x.\n", address); + } + } + + KMutexRelease(®ion->data.mapMutex); + + size_t pagesToRead = 16; + + if (region->pageCount - (offsetIntoRegion + region->data.file.zeroedBytes) / K_PAGE_SIZE < pagesToRead) { + pagesToRead = region->pageCount - (offsetIntoRegion + region->data.file.zeroedBytes) / K_PAGE_SIZE; + } + + EsError error = CCSpaceAccess(®ion->data.file.node->cache, (void *) address, + offsetIntoRegion + region->data.file.offset, pagesToRead * K_PAGE_SIZE, + CC_ACCESS_MAP, space, flags); + + KMutexAcquire(®ion->data.mapMutex); + + if (error != ES_SUCCESS) { + KernelLog(LOG_ERROR, "Memory", "mapped file read error", "MMHandlePageFault - Could not read from file %x. Error: %d.\n", + region->data.file.node, error); + return false; + } + + if (copyOnWrite) { + goto doCopyOnWrite; + } + + return true; + } else if (region->flags & MM_REGION_NORMAL) { + if (!(region->flags & MM_REGION_NO_COMMIT_TRACKING)) { + if (!region->data.normal.commit.Contains(offsetIntoRegion >> K_PAGE_BITS)) { + KernelLog(LOG_ERROR, "Memory", "outside commit range", "MMHandlePageFault - Attempting to access uncommitted memory in reserved region.\n"); + return false; + } + } + + MMArchMapPage(space, MMPhysicalAllocate(needZeroPages), address, flags); + if (zeroPage) EsMemoryZero((void *) address, K_PAGE_SIZE); + return true; + } else if (region->flags & MM_REGION_GUARD) { + KernelLog(LOG_ERROR, "Memory", "guard page hit", "MMHandlePageFault - Guard page hit!\n"); + return false; + } else { + KernelLog(LOG_ERROR, "Memory", "cannot fault in region", "MMHandlePageFault - Unsupported region type (flags: %x).\n", region->flags); + return false; + } +} + +MMRegion *MMReserve(MMSpace *space, size_t bytes, unsigned flags, uintptr_t forcedAddress, bool generateGuardPages) { + MMRegion *outputRegion = nullptr; + size_t pagesNeeded = ((bytes + K_PAGE_SIZE - 1) & ~(K_PAGE_SIZE - 1)) / K_PAGE_SIZE; + + if (!pagesNeeded) return nullptr; + + KMutexAssertLocked(&space->reserveMutex); + + if (space == coreMMSpace) { + if (mmCoreRegionCount == MM_CORE_REGIONS_COUNT) { + return nullptr; + } + + if (forcedAddress) { + KernelPanic("MMReserve - Using a forced address in coreMMSpace.\n"); + } + + { + uintptr_t newRegionCount = mmCoreRegionCount + 1; + uintptr_t commitPagesNeeded = newRegionCount * sizeof(MMRegion) / K_PAGE_SIZE + 1; + + while (mmCoreRegionArrayCommit < commitPagesNeeded) { + if (!MMCommit(K_PAGE_SIZE, true)) return nullptr; + mmCoreRegionArrayCommit++; + } + } + + for (uintptr_t i = 0; i < mmCoreRegionCount; i++) { + MMRegion *region = mmCoreRegions + i; + + if (!region->core.used && region->pageCount >= pagesNeeded) { + if (region->pageCount > pagesNeeded) { + MMRegion *split = mmCoreRegions + mmCoreRegionCount++; + EsMemoryCopy(split, region, sizeof(MMRegion)); + split->baseAddress += pagesNeeded * K_PAGE_SIZE; + split->pageCount -= pagesNeeded; + } + + region->core.used = true; + region->pageCount = pagesNeeded; + + region->flags = flags; + EsMemoryZero(®ion->data, sizeof(region->data)); + outputRegion = region; + goto done; + } + } + } else if (forcedAddress) { + AVLItem *item; + + // EsPrint("reserve forced: %x\n", forcedAddress); + + // Check for a collision. + item = TreeFind(&space->usedRegions, MakeShortKey(forcedAddress), TREE_SEARCH_EXACT); + if (item) return nullptr; + item = TreeFind(&space->usedRegions, MakeShortKey(forcedAddress), TREE_SEARCH_SMALLEST_ABOVE_OR_EQUAL); + if (item && item->thisItem->baseAddress < forcedAddress + pagesNeeded * K_PAGE_SIZE) return nullptr; + item = TreeFind(&space->usedRegions, MakeShortKey(forcedAddress + pagesNeeded * K_PAGE_SIZE - 1), TREE_SEARCH_LARGEST_BELOW_OR_EQUAL); + if (item && item->thisItem->baseAddress + item->thisItem->pageCount * K_PAGE_SIZE > forcedAddress) return nullptr; + item = TreeFind(&space->freeRegionsBase, MakeShortKey(forcedAddress), TREE_SEARCH_EXACT); + if (item) return nullptr; + item = TreeFind(&space->freeRegionsBase, MakeShortKey(forcedAddress), TREE_SEARCH_SMALLEST_ABOVE_OR_EQUAL); + if (item && item->thisItem->baseAddress < forcedAddress + pagesNeeded * K_PAGE_SIZE) return nullptr; + item = TreeFind(&space->freeRegionsBase, MakeShortKey(forcedAddress + pagesNeeded * K_PAGE_SIZE - 1), TREE_SEARCH_LARGEST_BELOW_OR_EQUAL); + if (item && item->thisItem->baseAddress + item->thisItem->pageCount * K_PAGE_SIZE > forcedAddress) return nullptr; + + // EsPrint("(no collisions)\n"); + + MMRegion *region = (MMRegion *) EsHeapAllocate(sizeof(MMRegion), true, K_CORE); + region->baseAddress = forcedAddress; + region->pageCount = pagesNeeded; + region->flags = flags; + TreeInsert(&space->usedRegions, ®ion->itemBase, region, MakeShortKey(region->baseAddress)); + + EsMemoryZero(®ion->data, sizeof(region->data)); + outputRegion = region; + } else { +#ifdef GUARD_PAGES + size_t guardPagesNeeded = generateGuardPages ? 2 : 0; +#else + size_t guardPagesNeeded = 0; +#endif + + AVLItem *item = TreeFind(&space->freeRegionsSize, MakeShortKey(pagesNeeded + guardPagesNeeded), TREE_SEARCH_SMALLEST_ABOVE_OR_EQUAL); + + if (!item) { + goto done; + } + + MMRegion *region = item->thisItem; + TreeRemove(&space->freeRegionsBase, ®ion->itemBase); + TreeRemove(&space->freeRegionsSize, ®ion->itemSize); + + if (region->pageCount > pagesNeeded + guardPagesNeeded) { + MMRegion *split = (MMRegion *) EsHeapAllocate(sizeof(MMRegion), true, K_CORE); + EsMemoryCopy(split, region, sizeof(MMRegion)); + + split->baseAddress += (pagesNeeded + guardPagesNeeded) * K_PAGE_SIZE; + split->pageCount -= (pagesNeeded + guardPagesNeeded); + + TreeInsert(&space->freeRegionsBase, &split->itemBase, split, MakeShortKey(split->baseAddress)); + TreeInsert(&space->freeRegionsSize, &split->itemSize, split, MakeShortKey(split->pageCount), AVL_DUPLICATE_KEYS_ALLOW); + } + + EsMemoryZero(®ion->data, sizeof(region->data)); + + region->pageCount = pagesNeeded; + region->flags = flags; + + if (guardPagesNeeded) { + MMRegion *guardBefore = (MMRegion *) EsHeapAllocate(sizeof(MMRegion), true, K_CORE); + MMRegion *guardAfter = (MMRegion *) EsHeapAllocate(sizeof(MMRegion), true, K_CORE); + + EsMemoryCopy(guardBefore, region, sizeof(MMRegion)); + EsMemoryCopy(guardAfter, region, sizeof(MMRegion)); + + guardAfter->baseAddress += K_PAGE_SIZE * (pagesNeeded + 1); + guardBefore->pageCount = guardAfter->pageCount = 1; + guardBefore->flags = guardAfter->flags = MM_REGION_GUARD; + + region->baseAddress += K_PAGE_SIZE; + region->data.normal.guardBefore = guardBefore; + region->data.normal.guardAfter = guardAfter; + + EsMemoryZero(&guardBefore->itemNonGuard, sizeof(guardBefore->itemNonGuard)); + EsMemoryZero(&guardAfter->itemNonGuard, sizeof(guardAfter->itemNonGuard)); + + TreeInsert(&space->usedRegions, &guardBefore->itemBase, guardBefore, MakeShortKey(guardBefore->baseAddress)); + TreeInsert(&space->usedRegions, &guardAfter ->itemBase, guardAfter, MakeShortKey(guardAfter->baseAddress)); + +#if 0 + EsPrint("Guarded region: %x->%x/%x->%x/%x->%x\n", guardBefore->baseAddress, guardBefore->pageCount * K_PAGE_SIZE + guardBefore->baseAddress, + region->baseAddress, region->pageCount * K_PAGE_SIZE + region->baseAddress, + guardAfter->baseAddress, guardAfter->pageCount * K_PAGE_SIZE + guardAfter->baseAddress); +#endif + } + + TreeInsert(&space->usedRegions, ®ion->itemBase, region, MakeShortKey(region->baseAddress)); + + outputRegion = region; + goto done; + } + + done:; + // EsPrint("reserve: %x -> %x\n", address, (uintptr_t) address + pagesNeeded * K_PAGE_SIZE); + + if (outputRegion) { + // We've now got an address range for the region. + // So we should commit the page tables that will be needed to map it. + + if (!MMArchCommitPageTables(space, outputRegion)) { + // We couldn't commit the leading page tables. + // So we'll have to unreserve the region. + MMUnreserve(space, outputRegion, false); + return nullptr; + } + + if (space != coreMMSpace) { + EsMemoryZero(&outputRegion->itemNonGuard, sizeof(outputRegion->itemNonGuard)); + outputRegion->itemNonGuard.thisItem = outputRegion; + space->usedRegionsNonGuard.InsertEnd(&outputRegion->itemNonGuard); + } + } + + // EsPrint("Reserve: %x->%x\n", outputRegion->baseAddress, outputRegion->pageCount * K_PAGE_SIZE + outputRegion->baseAddress); + return outputRegion; +} + +void MMUnreserve(MMSpace *space, MMRegion *remove, bool unmapPages, bool guardRegion) { + // EsPrint("unreserve: %x, %x, %d, %d\n", remove->baseAddress, remove->flags, unmapPages, guardRegion); + // EsDefer(EsPrint("unreserve complete\n");); + + KMutexAssertLocked(&space->reserveMutex); + + if (pmm.nextRegionToBalance == remove) { + // If the balance thread paused while balancing this region, + // switch to the next region. + pmm.nextRegionToBalance = remove->itemNonGuard.nextItem ? remove->itemNonGuard.nextItem->thisItem : nullptr; + pmm.balanceResumePosition = 0; + } + + if (!remove) { + KernelPanic("MMUnreserve - Region to remove was null.\n"); + } + + if (remove->flags & MM_REGION_NORMAL) { + if (remove->data.normal.guardBefore) MMUnreserve(space, remove->data.normal.guardBefore, false, true); + if (remove->data.normal.guardAfter) MMUnreserve(space, remove->data.normal.guardAfter, false, true); + } else if ((remove->flags & MM_REGION_GUARD) && !guardRegion) { + // You can't free a guard region! + // TODO Error. + KernelLog(LOG_ERROR, "Memory", "attempt to unreserve guard page", "MMUnreserve - Attempting to unreserve a guard page.\n"); + return; + } + + if (remove->itemNonGuard.list && !guardRegion) { + // EsPrint("Remove item non guard...\n"); + remove->itemNonGuard.RemoveFromList(); + // EsPrint("Removed.\n"); + } + + if (unmapPages) { + MMArchUnmapPages(space, remove->baseAddress, + remove->pageCount, ES_FLAGS_DEFAULT); + } + + if (space == coreMMSpace) { + remove->core.used = false; + + intptr_t remove1 = -1, remove2 = -1; + + for (uintptr_t i = 0; i < mmCoreRegionCount && (remove1 != -1 || remove2 != 1); i++) { + MMRegion *r = mmCoreRegions + i; + + if (r->core.used) continue; + if (r == remove) continue; + + if (r->baseAddress == remove->baseAddress + (remove->pageCount << K_PAGE_BITS)) { + remove->pageCount += r->pageCount; + remove1 = i; + } else if (remove->baseAddress == r->baseAddress + (r->pageCount << K_PAGE_BITS)) { + remove->pageCount += r->pageCount; + remove->baseAddress = r->baseAddress; + remove2 = i; + } + } + + if (remove1 != -1) { + mmCoreRegions[remove1] = mmCoreRegions[--mmCoreRegionCount]; + if ((uintptr_t) remove2 == mmCoreRegionCount) remove2 = remove1; + } + + if (remove2 != -1) { + mmCoreRegions[remove2] = mmCoreRegions[--mmCoreRegionCount]; + } + } else { + TreeRemove(&space->usedRegions, &remove->itemBase); + uintptr_t address = remove->baseAddress; + + { + AVLItem *before = TreeFind(&space->freeRegionsBase, MakeShortKey(address), TREE_SEARCH_LARGEST_BELOW_OR_EQUAL); + + if (before && before->thisItem->baseAddress + before->thisItem->pageCount * K_PAGE_SIZE == remove->baseAddress) { + remove->baseAddress = before->thisItem->baseAddress; + remove->pageCount += before->thisItem->pageCount; + TreeRemove(&space->freeRegionsBase, before); + TreeRemove(&space->freeRegionsSize, &before->thisItem->itemSize); + EsHeapFree(before->thisItem, sizeof(MMRegion), K_CORE); + } + } + + { + AVLItem *after = TreeFind(&space->freeRegionsBase, MakeShortKey(address), TREE_SEARCH_SMALLEST_ABOVE_OR_EQUAL); + + if (after && remove->baseAddress + remove->pageCount * K_PAGE_SIZE == after->thisItem->baseAddress) { + remove->pageCount += after->thisItem->pageCount; + TreeRemove(&space->freeRegionsBase, after); + TreeRemove(&space->freeRegionsSize, &after->thisItem->itemSize); + EsHeapFree(after->thisItem, sizeof(MMRegion), K_CORE); + } + } + + TreeInsert(&space->freeRegionsBase, &remove->itemBase, remove, MakeShortKey(remove->baseAddress)); + TreeInsert(&space->freeRegionsSize, &remove->itemSize, remove, MakeShortKey(remove->pageCount), AVL_DUPLICATE_KEYS_ALLOW); + } +} + +void *MMMapFile(MMSpace *space, FSFile *node, EsFileOffset offset, size_t bytes, int protection, void *baseAddress, size_t zeroedBytes, uint32_t additionalFlags) { + if (protection != ES_MAP_OBJECT_READ_ONLY && protection != ES_MAP_OBJECT_COPY_ON_WRITE) { + return nullptr; + } + + if (node->directoryEntry->type != ES_NODE_FILE) { + return nullptr; + } + + MMRegion *region = nullptr; + uint64_t fileHandleFlags = ES_NODE_PREVENT_RESIZE + | (protection == ES_MAP_OBJECT_READ_WRITE ? ES_FILE_WRITE : ES_FILE_READ_SHARED); + bool decommit = false; + + // Register a handle to the node. + // If this successes, past this point we file cannot be resized. + + if (!OpenHandleToObject(node, KERNEL_OBJECT_NODE, fileHandleFlags)) { + return nullptr; + } + + // Acquire the file's cache mutex before the space's reserve mutex, + // so that we can handle page faults without locking the reserve mutex. + + KMutexAcquire(&node->cache.cachedSectionsMutex); + EsDefer(KMutexRelease(&node->cache.cachedSectionsMutex)); + KMutexAcquire(&space->reserveMutex); + EsDefer(KMutexRelease(&space->reserveMutex)); + + // Check the parameters. + + uintptr_t offsetIntoPage = offset & (K_PAGE_SIZE - 1); + + if (node->directoryEntry->totalSize <= offset) { + goto fail; + } + + if (offsetIntoPage) bytes += offset & (K_PAGE_SIZE - 1); + + // Reserve the region. + + region = MMReserve(space, bytes + zeroedBytes, MM_REGION_FILE | additionalFlags + | ((protection == ES_MAP_OBJECT_READ_ONLY || protection == ES_MAP_OBJECT_COPY_ON_WRITE) ? MM_REGION_READ_ONLY : 0) + | (protection == ES_MAP_OBJECT_COPY_ON_WRITE ? MM_REGION_COPY_ON_WRITE : 0), + baseAddress ? (uintptr_t) baseAddress - offsetIntoPage : 0); + + if (!region) { + goto fail; + } + + // Commit copy on write regions. + + if (protection == ES_MAP_OBJECT_COPY_ON_WRITE) { + if (!MMCommit(bytes + zeroedBytes, false)) { + goto fail; + } + + decommit = true; + } + + // Initialise data. + + region->data.file.node = node; + region->data.file.offset = offset - offsetIntoPage; + region->data.file.zeroedBytes = zeroedBytes; + region->data.file.fileHandleFlags = fileHandleFlags; + + // Make sure the region is covered by MMCachedSections in the file. + // (This is why we needed the file's cache mutex.) + // We'll allocate in coreMMSpace as needed. + + if (!CCSpaceCover(&node->cache, region->data.file.offset, region->data.file.offset + bytes)) { + goto fail; + } + + return (uint8_t *) region->baseAddress + offsetIntoPage; + + fail:; + if (region) MMUnreserve(space, region, false /* No pages have been mapped. */); + if (decommit) MMDecommit(bytes + zeroedBytes, false); + CloseHandleToObject(node, KERNEL_OBJECT_NODE, fileHandleFlags); + return nullptr; +} + +void *MMMapShared(MMSpace *space, MMSharedRegion *sharedRegion, uintptr_t offset, size_t bytes, uint32_t additionalFlags, void *baseAddress) { + MMRegion *region; + OpenHandleToObject(sharedRegion, KERNEL_OBJECT_SHMEM); + + KMutexAcquire(&space->reserveMutex); + EsDefer(KMutexRelease(&space->reserveMutex)); + + if (offset & (K_PAGE_SIZE - 1)) bytes += offset & (K_PAGE_SIZE - 1); + if (sharedRegion->sizeBytes <= offset) goto fail; + if (sharedRegion->sizeBytes < offset + bytes) goto fail; + + region = MMReserve(space, bytes, MM_REGION_SHARED | additionalFlags, (uintptr_t) baseAddress); + if (!region) goto fail; + + if (!(region->flags & MM_REGION_SHARED)) KernelPanic("MMMapShared - Cannot commit into non-shared region.\n"); + if (region->data.shared.region) KernelPanic("MMMapShared - A shared region has already been bound.\n"); + + region->data.shared.region = sharedRegion; + region->data.shared.offset = offset & ~(K_PAGE_SIZE - 1); + + return (uint8_t *) region->baseAddress + (offset & (K_PAGE_SIZE - 1)); + + fail:; + CloseHandleToObject(sharedRegion, KERNEL_OBJECT_SHMEM); + return nullptr; +} + +void MMDecommit(uint64_t bytes, bool fixed) { + // EsPrint("De-Commit %d %d\n", bytes, fixed); + + if (bytes & (K_PAGE_SIZE - 1)) KernelPanic("MMDecommit - Expected multiple of K_PAGE_SIZE bytes.\n"); + int64_t pagesNeeded = bytes / K_PAGE_SIZE; + + KMutexAcquire(&pmm.commitMutex); + EsDefer(KMutexRelease(&pmm.commitMutex)); + + if (fixed) { + if (pmm.commitFixed < pagesNeeded) KernelPanic("MMDecommit - Decommitted too many pages.\n"); + pmm.commitFixed -= pagesNeeded; + } else { + if (pmm.commitPageable < pagesNeeded) KernelPanic("MMDecommit - Decommitted too many pages.\n"); + pmm.commitPageable -= pagesNeeded; + } + + KernelLog(LOG_VERBOSE, "Memory", "decommit", "Decommit %D%z. Now at %D.\n", bytes, fixed ? ", fixed" : "", (pmm.commitFixed + pmm.commitPageable) << K_PAGE_BITS); +} + +bool MMCommit(uint64_t bytes, bool fixed) { + if (bytes & (K_PAGE_SIZE - 1)) KernelPanic("MMCommit - Expected multiple of K_PAGE_SIZE bytes.\n"); + int64_t pagesNeeded = bytes / K_PAGE_SIZE; + + KMutexAcquire(&pmm.commitMutex); + EsDefer(KMutexRelease(&pmm.commitMutex)); + + if (pmm.commitLimit) { + if (fixed) { + if (pagesNeeded > pmm.commitFixedLimit - pmm.commitFixed) { + KernelLog(LOG_ERROR, "Memory", "failed fixed commit", "Failed fixed commit %d pages (currently %d/%d).\n", + pagesNeeded, pmm.commitFixed, pmm.commitFixedLimit); + return false; + } + + if (MM_AVAILABLE_PAGES() - pagesNeeded < MM_CRITICAL_AVAILABLE_PAGES_THRESHOLD && !GetCurrentThread()->isPageGenerator) { + KernelLog(LOG_ERROR, "Memory", "failed fixed commit", + "Failed fixed commit %d as the available pages (%d, %d, %d) would cross the critical threshold, %d.\n.", + pagesNeeded, pmm.countZeroedPages, pmm.countFreePages, pmm.countStandbyPages, MM_CRITICAL_AVAILABLE_PAGES_THRESHOLD); + return false; + } + + pmm.commitFixed += pagesNeeded; + } else { + if (pagesNeeded > MM_REMAINING_COMMIT() - (intptr_t) (GetCurrentThread()->isPageGenerator ? 0 : MM_CRITICAL_REMAINING_COMMIT_THRESHOLD)) { + KernelLog(LOG_ERROR, "Memory", "failed pageable commit", + "Failed pageable commit %d pages (currently %d/%d).\n", + pagesNeeded, pmm.commitPageable + pmm.commitFixed, pmm.commitLimit); + return false; + } + + pmm.commitPageable += pagesNeeded; + } + + if (MM_OBJECT_CACHE_SHOULD_TRIM()) { + KEventSet(&pmm.trimObjectCaches, false, true); + } + } else { + // We haven't started tracking commit counts yet. + } + + KernelLog(LOG_VERBOSE, "Memory", "commit", "Commit %D%z. Now at %D.\n", bytes, fixed ? ", fixed" : "", (pmm.commitFixed + pmm.commitPageable) << K_PAGE_BITS); + + return true; +} + +bool MMCommitRange(MMSpace *space, MMRegion *region, uintptr_t pageOffset, size_t pageCount) { + KMutexAssertLocked(&space->reserveMutex); + + if (region->flags & MM_REGION_NO_COMMIT_TRACKING) { + KernelPanic("MMCommitRange - Region does not support commit tracking.\n"); + } + + if (pageOffset >= region->pageCount || pageCount > region->pageCount - pageOffset) { + KernelPanic("MMCommitRange - Invalid region offset and page count.\n"); + } + + if (~region->flags & MM_REGION_NORMAL) { + KernelPanic("MMCommitRange - Cannot commit into non-normal region.\n"); + } + + intptr_t delta = 0; + region->data.normal.commit.Set(pageOffset, pageOffset + pageCount, &delta, false); + + if (delta < 0) { + KernelPanic("MMCommitRange - Invalid delta calculation adding %x, %x to %x.\n", pageOffset, pageCount, region); + } + + if (delta == 0) { + return true; + } + + { + if (!MMCommit(delta * K_PAGE_SIZE, region->flags & MM_REGION_FIXED)) { + return false; + } + + region->data.normal.commitPageCount += delta; + space->commit += delta; + + if (region->data.normal.commitPageCount > region->pageCount) { + KernelPanic("MMCommitRange - Invalid delta calculation increases region %x commit past page count.\n", region); + } + } + + if (!region->data.normal.commit.Set(pageOffset, pageOffset + pageCount, nullptr, true)) { + MMDecommit(delta * K_PAGE_SIZE, region->flags & MM_REGION_FIXED); + region->data.normal.commitPageCount -= delta; + space->commit -= delta; + return false; + } + + if (region->flags & MM_REGION_FIXED) { + for (uintptr_t i = pageOffset; i < pageOffset + pageCount; i++) { + // TODO Don't call into MMHandlePageFault. I don't like MM_HANDLE_PAGE_FAULT_LOCK_ACQUIRED. + + if (!MMHandlePageFault(space, region->baseAddress + i * K_PAGE_SIZE, MM_HANDLE_PAGE_FAULT_LOCK_ACQUIRED)) { + KernelPanic("MMCommitRange - Unable to fix pages.\n"); + } + } + } + + return true; +} + +bool MMDecommitRange(MMSpace *space, MMRegion *region, uintptr_t pageOffset, size_t pageCount) { + KMutexAssertLocked(&space->reserveMutex); + + if (region->flags & MM_REGION_NO_COMMIT_TRACKING) { + KernelPanic("MMDecommitRange - Region does not support commit tracking.\n"); + } + + if (pageOffset >= region->pageCount || pageCount > region->pageCount - pageOffset) { + KernelPanic("MMDecommitRange - Invalid region offset and page count.\n"); + } + + if (~region->flags & MM_REGION_NORMAL) { + KernelPanic("MMDecommitRange - Cannot decommit from non-normal region.\n"); + } + + intptr_t delta = 0; + + if (!region->data.normal.commit.Clear(pageOffset, pageOffset + pageCount, &delta, true)) { + return false; + } + + if (delta > 0) { + KernelPanic("MMDecommitRange - Invalid delta calculation removing %x, %x from %x.\n", pageOffset, pageCount, region); + } + + delta = -delta; + + if (region->data.normal.commitPageCount < (size_t) delta) { + KernelPanic("MMDecommitRange - Invalid delta calculation decreases region %x commit below zero.\n", region); + } + + // EsPrint("\tdecommit = %x\n", pagesRemoved); + + MMDecommit(delta * K_PAGE_SIZE, region->flags & MM_REGION_FIXED); + space->commit -= delta; + region->data.normal.commitPageCount -= delta; + MMArchUnmapPages(space, region->baseAddress + pageOffset * K_PAGE_SIZE, pageCount, MM_UNMAP_PAGES_FREE); + + return true; +} + +void MMAllowWriteCombiningCaching(MMSpace *space, void *virtualAddress) { + if (!space) space = kernelMMSpace; + + KMutexAcquire(&space->reserveMutex); + + MMRegion *region = MMFindRegion(space, (uintptr_t) virtualAddress); + + if (!(region->flags & MM_REGION_NOT_CACHEABLE)) { + KernelPanic("MMAllowWriteCombiningCaching - Region was cachable.\n"); + } + + region->flags &= ~MM_REGION_NOT_CACHEABLE; + region->flags |= MM_REGION_WRITE_COMBINING; + MMArchUnmapPages(space, region->baseAddress, region->pageCount, ES_FLAGS_DEFAULT); + + KMutexRelease(&space->reserveMutex); + + for (uintptr_t i = 0; i < region->pageCount; i++) { + MMHandlePageFault(space, region->baseAddress + i * K_PAGE_SIZE, ES_FLAGS_DEFAULT); + } +} + +size_t MMGetRegionPageCount(MMSpace *space, void *address) { + if (!space) space = kernelMMSpace; + + KMutexAcquire(&space->reserveMutex); + EsDefer(KMutexRelease(&space->reserveMutex)); + + MMRegion *region = MMFindRegion(space, (uintptr_t) address); + return region->pageCount; +} + +void *MMMapPhysical(MMSpace *space, uintptr_t offset, size_t bytes, uint64_t caching) { + if (!space) space = kernelMMSpace; + + uintptr_t offset2 = offset & (K_PAGE_SIZE - 1); + offset -= offset2; + if (offset2) bytes += K_PAGE_SIZE; + + MMRegion *region; + + { + KMutexAcquire(&space->reserveMutex); + EsDefer(KMutexRelease(&space->reserveMutex)); + + region = MMReserve(space, bytes, MM_REGION_PHYSICAL | MM_REGION_FIXED | caching); + if (!region) return nullptr; + + region->data.physical.offset = offset; + } + + for (uintptr_t i = 0; i < region->pageCount; i++) { + MMHandlePageFault(space, region->baseAddress + i * K_PAGE_SIZE, ES_FLAGS_DEFAULT); + } + + return (uint8_t *) region->baseAddress + offset2; +} + +bool MMSharedResizeRegion(MMSharedRegion *region, size_t sizeBytes) { + KMutexAcquire(®ion->mutex); + EsDefer(KMutexRelease(®ion->mutex)); + + sizeBytes = (sizeBytes + K_PAGE_SIZE - 1) & ~(K_PAGE_SIZE - 1); + + size_t pages = sizeBytes / K_PAGE_SIZE; + size_t oldPages = region->sizeBytes / K_PAGE_SIZE; + void *oldData = region->data; + + void *newData = EsHeapAllocate(pages * sizeof(void *), true, K_CORE); + + if (!newData && pages) { + return false; + } + + if (oldPages > pages) { + MMDecommit(K_PAGE_SIZE * (oldPages - pages), true); + } else if (pages > oldPages) { + if (!MMCommit(K_PAGE_SIZE * (pages - oldPages), true)) { + EsHeapFree(newData, pages * sizeof(void *), K_CORE); + return false; + } + } + + region->sizeBytes = sizeBytes; + region->data = newData; + + // The old shared memory region was empty. + if (!oldData) return true; + + if (oldPages > pages) { + for (uintptr_t i = pages; i < oldPages; i++) { + uintptr_t *addresses = (uintptr_t *) oldData; + uintptr_t address = addresses[i]; + if (address & MM_SHARED_ENTRY_PRESENT) MMPhysicalFree(address); + } + } + + uintptr_t copy = oldPages > pages ? pages : oldPages; + EsMemoryCopy(region->data, oldData, sizeof(void *) * copy); + EsHeapFree(oldData, oldPages * sizeof(void *), K_CORE); + + return true; +} + +MMSharedRegion *MMSharedOpenRegion(const char *name, size_t nameBytes, size_t fallbackSizeBytes, uint64_t flags) { + if (nameBytes > ES_SHARED_MEMORY_NAME_MAX_LENGTH) return nullptr; + + KMutexAcquire(&mmNamedSharedRegionsMutex); + EsDefer(KMutexRelease(&mmNamedSharedRegionsMutex)); + + LinkedItem *item = mmNamedSharedRegions.firstItem; + + while (item) { + MMSharedRegion *region = item->thisItem; + + if (EsCStringLength(region->cName) == nameBytes && 0 == EsMemoryCompare(region->cName, name, nameBytes)) { + if (flags & ES_MEMORY_OPEN_FAIL_IF_FOUND) return nullptr; + OpenHandleToObject(region, KERNEL_OBJECT_SHMEM); + return region; + } + + item = item->nextItem; + } + + if (flags & ES_MEMORY_OPEN_FAIL_IF_NOT_FOUND) return nullptr; + + MMSharedRegion *region = MMSharedCreateRegion(fallbackSizeBytes); + if (!region) return nullptr; + EsMemoryCopy(region->cName, name, nameBytes); + + region->namedItem.thisItem = region; + mmNamedSharedRegions.InsertEnd(®ion->namedItem); + + return region; +} + +MMSharedRegion *MMSharedCreateRegion(size_t sizeBytes, bool fixed, uintptr_t below) { + if (!sizeBytes) return nullptr; + + MMSharedRegion *region = (MMSharedRegion *) EsHeapAllocate(sizeof(MMSharedRegion), true, K_CORE); + if (!region) return nullptr; + region->handles = 1; + + if (!MMSharedResizeRegion(region, sizeBytes)) { + EsHeapFree(region, 0, K_CORE); + return nullptr; + } + + if (fixed) { + for (uintptr_t i = 0; i < region->sizeBytes >> K_PAGE_BITS; i++) { + ((uintptr_t *) region->data)[i] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_ZEROED, 1, 1, below) | MM_SHARED_ENTRY_PRESENT; + } + } + + return region; +} + +void MMSharedDestroyRegion(MMSharedRegion *region) { + MMSharedResizeRegion(region, 0); // TODO Check leaks. + EsHeapFree(region, 0, K_CORE); +} + +void *MMStandardAllocate(MMSpace *space, size_t bytes, unsigned flags, void *baseAddress, bool commitAll) { + if (!space) space = kernelMMSpace; + + KMutexAcquire(&space->reserveMutex); + EsDefer(KMutexRelease(&space->reserveMutex)); + + MMRegion *region = MMReserve(space, bytes, flags | MM_REGION_NORMAL, (uintptr_t) baseAddress, true); + if (!region) return nullptr; + + if (commitAll) { + if (!MMCommitRange(space, region, 0, region->pageCount)) { + MMUnreserve(space, region, false /* No pages have been mapped. */); + return nullptr; + } + } + + return (void *) region->baseAddress; +} + +bool MMFree(MMSpace *space, void *address, size_t expectedSize, bool userOnly) { + if (!space) space = kernelMMSpace; + + MMSharedRegion *sharedRegionToFree = nullptr; + FSFile *nodeToFree = nullptr; + uint64_t fileHandleFlags = 0; + + { + KMutexAcquire(&space->reserveMutex); + EsDefer(KMutexRelease(&space->reserveMutex)); + + MMRegion *region = MMFindRegion(space, (uintptr_t) address); + + if (!region) { + return false; + } + + if (userOnly && (~region->flags & MM_REGION_USER)) { + KernelLog(LOG_ERROR, "Memory", "attempt to free non-user region", + "MMFree - A user process is attempting to free a region %x that is not marked with MM_REGION_USER.\n", region); + return false; + } + + if (!KWriterLockTake(®ion->data.pin, K_LOCK_EXCLUSIVE, true /* poll */)) { + KernelLog(LOG_ERROR, "Memory", "attempt to free in use region", + "MMFree - Attempting to free a region %x that is currently in by a system call.\n", region); + return false; + } + + if (region->baseAddress != (uintptr_t) address && (~region->flags & MM_REGION_PHYSICAL /* physical region bases are not page aligned */)) { + KernelLog(LOG_ERROR, "Memory", "incorrect base address", "MMFree - Passed the address %x to free region %x, which has baseAddress of %x.\n", + address, region, region->baseAddress); + return false; + } + + if (expectedSize && (expectedSize + K_PAGE_SIZE - 1) / K_PAGE_SIZE != region->pageCount) { + KernelLog(LOG_ERROR, "Memory", "incorrect free size", "MMFree - The region page count is %d, but the expected free size is %d.\n", + region->pageCount, (expectedSize + K_PAGE_SIZE - 1) / K_PAGE_SIZE); + return false; + } + + bool unmapPages = true; + + if (region->flags & MM_REGION_NORMAL) { + if (!MMDecommitRange(space, region, 0, region->pageCount)) { + KernelPanic("MMFree - Could not decommit entire region %x (should not fail).\n", region); + } + + if (region->data.normal.commitPageCount) { + KernelPanic("MMFree - After decommiting range covering the entire region (%x), some pages were still commited.\n", region); + } + + region->data.normal.commit.ranges.Free(); + unmapPages = false; + } else if (region->flags & MM_REGION_SHARED) { + sharedRegionToFree = region->data.shared.region; + } else if (region->flags & MM_REGION_FILE) { + MMArchUnmapPages(space, region->baseAddress, + region->pageCount, MM_UNMAP_PAGES_FREE_COPIED | MM_UNMAP_PAGES_BALANCE_FILE); + unmapPages = false; + + FSFile *node = region->data.file.node; + EsFileOffset removeStart = RoundDown(region->data.file.offset, K_PAGE_SIZE); + EsFileOffset removeEnd = RoundUp(removeStart + (region->pageCount << K_PAGE_BITS) - region->data.file.zeroedBytes, K_PAGE_SIZE); + nodeToFree = node; + fileHandleFlags = region->data.file.fileHandleFlags; + + KMutexAcquire(&node->cache.cachedSectionsMutex); + CCSpaceUncover(&node->cache, removeStart, removeEnd); + KMutexRelease(&node->cache.cachedSectionsMutex); + + if (region->flags & MM_REGION_COPY_ON_WRITE) { + MMDecommit(region->pageCount << K_PAGE_BITS, false); + } + } else if (region->flags & MM_REGION_PHYSICAL) { + } else if (region->flags & MM_REGION_GUARD) { + KernelLog(LOG_ERROR, "Memory", "attempt to free guard region", "MMFree - Attempting to free a guard region!\n"); + return false; + } else { + KernelPanic("MMFree - Unsupported region type.\n"); + } + + MMUnreserve(space, region, unmapPages); + } + + if (sharedRegionToFree) CloseHandleToObject(sharedRegionToFree, KERNEL_OBJECT_SHMEM); + if (nodeToFree && fileHandleFlags) CloseHandleToObject(nodeToFree, KERNEL_OBJECT_NODE, fileHandleFlags); + + return true; +} + +bool MMSpaceInitialise(MMSpace *space) { + // EsPrint("... Committed %d/%d fixed and %d pageable out of a maximum %d.\n", pmm.commitFixed, pmm.commitFixedLimit, pmm.commitPageable, pmm.commitLimit); + + space->user = true; + + MMRegion *region = (MMRegion *) EsHeapAllocate(sizeof(MMRegion), true, K_CORE); + + if (!region) { + return false; + } + + if (!MMArchInitialiseUserSpace(space)) { + EsHeapFree(region, sizeof(MMRegion), K_CORE); + return false; + } + + region->baseAddress = MM_USER_SPACE_START; + region->pageCount = MM_USER_SPACE_SIZE / K_PAGE_SIZE; + TreeInsert(&space->freeRegionsBase, ®ion->itemBase, region, MakeShortKey(region->baseAddress)); + TreeInsert(&space->freeRegionsSize, ®ion->itemSize, region, MakeShortKey(region->pageCount), AVL_DUPLICATE_KEYS_ALLOW); + + return true; +} + +void MMSpaceDestroy(MMSpace *space) { + LinkedItem *item = space->usedRegionsNonGuard.firstItem; + + while (item) { + MMRegion *region = item->thisItem; + item = item->nextItem; + MMFree(space, (void *) region->baseAddress); + } + + while (true) { + AVLItem *item = TreeFind(&space->freeRegionsBase, MakeShortKey(0), TREE_SEARCH_SMALLEST_ABOVE_OR_EQUAL); + if (!item) break; + TreeRemove(&space->freeRegionsBase, &item->thisItem->itemBase); + TreeRemove(&space->freeRegionsSize, &item->thisItem->itemSize); + EsHeapFree(item->thisItem, sizeof(MMRegion), K_CORE); + } + + MMFreeVAS(space); +} + +bool MMUnmapFilePage(uintptr_t frameNumber, bool justLoaded) { + KMutexAssertLocked(&pmm.pageFrameMutex); + + MMPageFrame *frame = pmm.pageFrames + frameNumber; + + if (!justLoaded) { + if (frame->state != MMPageFrame::ACTIVE || !frame->active.references) { + KernelPanic("MMUnmapFilePage - Corrupt page frame database (%d/%x).\n", frameNumber, frame); + } + + // Decrease the reference count. + frame->active.references--; + } + + if (!frame->active.references) { + // If there are no more references, then the frame can be moved to the standby or modified list. + + // EsPrint("Unmap file page: %x\n", frameNumber << K_PAGE_BITS); + + { + frame->state = MMPageFrame::STANDBY; + pmm.countStandbyPages++; + + if (*frame->cacheReference != ((frameNumber << K_PAGE_BITS) | MM_SHARED_ENTRY_PRESENT)) { + KernelPanic("MMUnmapFilePage - Corrupt shared reference back pointer in frame %x.\n", frame); + } + + frame->list.next = pmm.firstStandbyPage; + frame->list.previous = &pmm.firstStandbyPage; + if (pmm.firstStandbyPage) pmm.pageFrames[pmm.firstStandbyPage].list.previous = &frame->list.next; + if (!pmm.lastStandbyPage) pmm.lastStandbyPage = frameNumber; + pmm.firstStandbyPage = frameNumber; + + MMUpdateAvailablePageCount(true); + } + + pmm.countActivePages--; + return true; + } + + return false; +} + +void MMBalanceThread() { + size_t targetAvailablePages = 0; + + while (true) { + if (MM_AVAILABLE_PAGES() >= targetAvailablePages) { + // Wait for there to be a low number of available pages. + KEventWait(&pmm.availableLow); + targetAvailablePages = MM_LOW_AVAILABLE_PAGES_THRESHOLD + MM_PAGES_TO_FIND_BALANCE; + } + + // EsPrint("--> Balance!\n"); + + // Find a process to balance. + + KSpinlockAcquire(&scheduler.lock); + + Process *process = nullptr; + + while (true) { + if (pmm.nextProcessToBalance) { + process = pmm.nextProcessToBalance; + } else { + process = scheduler.allProcesses.firstItem->thisItem; + } + + pmm.nextProcessToBalance = process->allItem.nextItem ? process->allItem.nextItem->thisItem : nullptr; + + if (process->handles) { + process->handles++; + break; + } + } + + KSpinlockRelease(&scheduler.lock); + + // For every memory region... + + MMSpace *space = process->vmm; + GetCurrentThread()->SetAddressSpace(space); + KMutexAcquire(&space->reserveMutex); + LinkedItem *item = pmm.nextRegionToBalance ? &pmm.nextRegionToBalance->itemNonGuard : space->usedRegionsNonGuard.firstItem; + + while (item && MM_AVAILABLE_PAGES() < targetAvailablePages) { + MMRegion *region = item->thisItem; + + // EsPrint("process = %x, region = %x, offset = %x\n", process, region, pmm.balanceResumePosition); + + KMutexAcquire(®ion->data.mapMutex); + + bool canResume = false; + + if (region->flags & MM_REGION_FILE) { + canResume = true; + MMArchUnmapPages(space, + region->baseAddress, region->pageCount, + MM_UNMAP_PAGES_BALANCE_FILE, + targetAvailablePages - MM_AVAILABLE_PAGES(), &pmm.balanceResumePosition); + } else if (region->flags & MM_REGION_CACHE) { + // TODO Trim the cache's active sections and cached sections lists. + + KMutexAcquire(&activeSectionManager.mutex); + + LinkedItem *item = activeSectionManager.lruList.firstItem; + + while (item && MM_AVAILABLE_PAGES() < targetAvailablePages) { + CCActiveSection *section = item->thisItem; + if (section->cache && section->referencedPageCount) CCDereferenceActiveSection(section); + item = item->nextItem; + } + + KMutexRelease(&activeSectionManager.mutex); + } + + KMutexRelease(®ion->data.mapMutex); + + if (MM_AVAILABLE_PAGES() >= targetAvailablePages && canResume) { + // We have enough to pause. + break; + } + + item = item->nextItem; + pmm.balanceResumePosition = 0; + } + + if (item) { + // Continue with this region next time. + pmm.nextRegionToBalance = item->thisItem; + pmm.nextProcessToBalance = process; + // EsPrint("will resume at %x\n", pmm.balanceResumePosition); + } else { + // Go to the next process. + pmm.nextRegionToBalance = nullptr; + pmm.balanceResumePosition = 0; + } + + KMutexRelease(&space->reserveMutex); + GetCurrentThread()->SetAddressSpace(nullptr); + CloseHandleToObject(process, KERNEL_OBJECT_PROCESS); + } +} + +void MMZeroPageThread() { + while (true) { + KEventWait(&pmm.zeroPageEvent); + KEventWait(&pmm.availableNotCritical); + + bool done = false; + + while (!done) { + uintptr_t pages[PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES]; + int i = 0; + + { + KMutexAcquire(&pmm.pageFrameMutex); + EsDefer(KMutexRelease(&pmm.pageFrameMutex)); + + for (; i < PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES; i++) { + if (pmm.firstFreePage) { + pages[i] = pmm.firstFreePage; + MMPhysicalActivatePages(pages[i], 1, ES_FLAGS_DEFAULT); + } else { + done = true; + break; + } + + MMPageFrame *frame = pmm.pageFrames + pages[i]; + frame->state = MMPageFrame::ACTIVE; + pmm.freeOrZeroedPageBitset.Take(pages[i]); + } + } + + for (int j = 0; j < i; j++) pages[j] <<= K_PAGE_BITS; + if (i) PMZero(pages, i, false); + + { + KMutexAcquire(&pmm.pageFrameMutex); + pmm.countActivePages -= i; + while (i--) MMPhysicalInsertZeroedPage(pages[i] >> K_PAGE_BITS); + KMutexRelease(&pmm.pageFrameMutex); + } + } + } +} + +#if 0 +void PMCheckZeroed(uintptr_t page) { + pmm.pmManipulationLock.Acquire(); + + { + MMSpace *vas = coreMMSpace; + void *region = pmm.pmManipulationRegion; + + vas->data.mapMutex.Acquire(); + MMArchMapPage(vas, page, (uintptr_t) region, MM_MAP_PAGE_OVERWRITE); + vas->data.mapMutex.Release(); + + pmm.pmManipulationProcessorLock.Acquire(); + ProcessorInvalidatePage((uintptr_t) region); + + for (uintptr_t i = 0; i < K_PAGE_SIZE; i++) { + if (((uint8_t *) region)[i]) { + KernelPanic("PMCheckZeroed - Supposedly zeroed page %x was not zeroed.\n", page); + } + } + + pmm.pmManipulationProcessorLock.Release(); + } + + pmm.pmManipulationLock.Release(); +} +#endif + +void PMZeroPartial(uintptr_t page, uintptr_t start, uintptr_t end) { + KMutexAcquire(&pmm.pmManipulationLock); + MMSpace *vas = coreMMSpace; + void *region = pmm.pmManipulationRegion; + MMArchMapPage(vas, page, (uintptr_t) region, MM_MAP_PAGE_OVERWRITE | MM_MAP_PAGE_NO_NEW_TABLES); + KSpinlockAcquire(&pmm.pmManipulationProcessorLock); + ProcessorInvalidatePage((uintptr_t) region); + EsMemoryZero((uint8_t *) region + start, end - start); + KSpinlockRelease(&pmm.pmManipulationProcessorLock); + KMutexRelease(&pmm.pmManipulationLock); +} + +void PMZero(uintptr_t *pages, size_t pageCount, bool contiguous) { + KMutexAcquire(&pmm.pmManipulationLock); + + repeat:; + size_t doCount = pageCount > PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES ? PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES : pageCount; + pageCount -= doCount; + + { + MMSpace *vas = coreMMSpace; + void *region = pmm.pmManipulationRegion; + + for (uintptr_t i = 0; i < doCount; i++) { + MMArchMapPage(vas, contiguous ? pages[0] + (i << K_PAGE_BITS) : pages[i], + (uintptr_t) region + K_PAGE_SIZE * i, MM_MAP_PAGE_OVERWRITE | MM_MAP_PAGE_NO_NEW_TABLES); + } + + KSpinlockAcquire(&pmm.pmManipulationProcessorLock); + + for (uintptr_t i = 0; i < doCount; i++) { + ProcessorInvalidatePage((uintptr_t) region + i * K_PAGE_SIZE); + } + + EsMemoryZero(region, doCount * K_PAGE_SIZE); + + KSpinlockRelease(&pmm.pmManipulationProcessorLock); + } + + if (pageCount) { + if (!contiguous) pages += PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES; + goto repeat; + } + + // if (pageNumbers) EsPrint("w%d\n", pmm.pmManipulationLock.blockedThreads.count); + KMutexRelease(&pmm.pmManipulationLock); +} + +void PMCopy(uintptr_t page, void *_source, size_t pageCount) { + uint8_t *source = (uint8_t *) _source; + KMutexAcquire(&pmm.pmManipulationLock); + + repeat:; + size_t doCount = pageCount > PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES ? PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES : pageCount; + pageCount -= doCount; + + { + MMSpace *vas = coreMMSpace; + void *region = pmm.pmManipulationRegion; + + for (uintptr_t i = 0; i < doCount; i++) { + MMArchMapPage(vas, page + K_PAGE_SIZE * i, (uintptr_t) region + K_PAGE_SIZE * i, MM_MAP_PAGE_OVERWRITE | MM_MAP_PAGE_NO_NEW_TABLES); + } + + KSpinlockAcquire(&pmm.pmManipulationProcessorLock); + + for (uintptr_t i = 0; i < doCount; i++) { + ProcessorInvalidatePage((uintptr_t) region + i * K_PAGE_SIZE); + } + + EsMemoryCopy(region, source, doCount * K_PAGE_SIZE); + + KSpinlockRelease(&pmm.pmManipulationProcessorLock); + } + + if (pageCount) { + page += PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES * K_PAGE_SIZE; + source += PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES * K_PAGE_SIZE; + goto repeat; + } + + KMutexRelease(&pmm.pmManipulationLock); +} + +void PMRead(uintptr_t page, void *_source, size_t pageCount) { + uint8_t *source = (uint8_t *) _source; + KMutexAcquire(&pmm.pmManipulationLock); + + repeat:; + size_t doCount = pageCount > PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES ? PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES : pageCount; + pageCount -= doCount; + + { + MMSpace *vas = coreMMSpace; + void *region = pmm.pmManipulationRegion; + + for (uintptr_t i = 0; i < doCount; i++) { + MMArchMapPage(vas, page + K_PAGE_SIZE * i, (uintptr_t) region + K_PAGE_SIZE * i, MM_MAP_PAGE_OVERWRITE | MM_MAP_PAGE_NO_NEW_TABLES); + } + + KSpinlockAcquire(&pmm.pmManipulationProcessorLock); + + for (uintptr_t i = 0; i < doCount; i++) { + ProcessorInvalidatePage((uintptr_t) region + i * K_PAGE_SIZE); + } + + EsMemoryCopy(source, region, doCount * K_PAGE_SIZE); + + KSpinlockRelease(&pmm.pmManipulationProcessorLock); + } + + if (pageCount) { + page += PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES * K_PAGE_SIZE; + source += PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES * K_PAGE_SIZE; + goto repeat; + } + + KMutexRelease(&pmm.pmManipulationLock); +} + +void *Pool::Add(size_t _elementSize) { + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (elementSize && _elementSize != elementSize) KernelPanic("Pool::Add - Pool element size mismatch.\n"); + elementSize = _elementSize; + + void *address; + +#if 1 + if (cacheEntries) { + address = cache[--cacheEntries]; + EsMemoryZero(address, elementSize); + } else { + address = EsHeapAllocate(elementSize, true, K_FIXED); + } +#else + address = EsHeapAllocate(elementSize, true); +#endif + + return address; +} + +void Pool::Remove(void *address) { + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (!address) return; + +#if 1 + if (cacheEntries == POOL_CACHE_COUNT) { + EsHeapFree(address, elementSize, K_FIXED); + } else { + cache[cacheEntries++] = address; + } +#else + EsHeapFree(address, elementSize); +#endif +} + +MMRegion *MMFindAndPinRegion(MMSpace *space, uintptr_t address, uintptr_t size) { + if (address + size < address) { + return nullptr; + } + + KMutexAcquire(&space->reserveMutex); + EsDefer(KMutexRelease(&space->reserveMutex)); + + MMRegion *region = MMFindRegion(space, address); + + if (!region) { + return nullptr; + } + + if (region->baseAddress > address) { + return nullptr; + } + + if (region->baseAddress + region->pageCount * K_PAGE_SIZE < address + size) { + return nullptr; + } + + if (!KWriterLockTake(®ion->data.pin, K_LOCK_SHARED, true /* poll */)) { + return nullptr; + } + + return region; +} + +void MMUnpinRegion(MMSpace *space, MMRegion *region) { + KMutexAcquire(&space->reserveMutex); + KWriterLockReturn(®ion->data.pin, K_LOCK_SHARED); + KMutexRelease(&space->reserveMutex); +} + +bool MMPhysicalAllocateAndMap(size_t sizeBytes, size_t alignmentBytes, size_t maximumBits, bool zeroed, + uint64_t caching, uint8_t **_virtualAddress, uintptr_t *_physicalAddress) { + if (!sizeBytes) sizeBytes = 1; + if (!alignmentBytes) alignmentBytes = 1; + + bool noBelow = false; + +#ifdef ARCH_32 + if (!maximumBits || maximumBits >= 32) noBelow = true; +#endif + +#ifdef ARCH_64 + if (!maximumBits || maximumBits >= 64) noBelow = true; +#endif + + uintptr_t sizePages = (sizeBytes + K_PAGE_SIZE - 1) >> K_PAGE_BITS; + + uintptr_t physicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW, + sizePages, (alignmentBytes + K_PAGE_SIZE - 1) >> K_PAGE_BITS, + noBelow ? 0 : ((size_t) 1 << maximumBits)); + + if (!physicalAddress) { + return false; + } + + void *virtualAddress = MMMapPhysical(kernelMMSpace, physicalAddress, sizeBytes, caching); + + if (!virtualAddress) { + MMPhysicalFree(physicalAddress, false, sizePages); + return false; + } + + if (zeroed) { + EsMemoryZero(virtualAddress, sizeBytes); + } + + *_virtualAddress = (uint8_t *) virtualAddress; + *_physicalAddress = physicalAddress; + return true; +} + +void MMPhysicalFreeAndUnmap(void *virtualAddress, uintptr_t physicalAddress) { + KMutexAcquire(&kernelMMSpace->reserveMutex); + MMRegion *region = MMFindRegion(kernelMMSpace, (uintptr_t) virtualAddress); + + if (!region || (~region->flags & MM_REGION_PHYSICAL) || region->data.physical.offset != physicalAddress) { + KernelPanic("MMPhysicalFreeAndUnmap - Virtual address %x did not point to a region of physical memory mapping %x.\n", virtualAddress, physicalAddress); + } + + size_t pageCount = region->pageCount; + MMUnreserve(kernelMMSpace, region, true); + KMutexRelease(&kernelMMSpace->reserveMutex); + + MMPhysicalFree(physicalAddress, false, pageCount); +} + +MMSpace *MMGetKernelSpace() { + return kernelMMSpace; +} + +MMSpace *MMGetCurrentProcessSpace() { + return GetCurrentThread()->process->vmm; +} + +void MMArchRemap(MMSpace *space, const void *virtualAddress, uintptr_t newPhysicalAddress) { + if (ProcessorAreInterruptsEnabled()) { + KernelPanic("MMArchRemap - Cannot remap address with interrupts enabled (does not invalidate the page on other processors).\n"); + } + + MMArchMapPage(space, newPhysicalAddress, (uintptr_t) virtualAddress, MM_MAP_PAGE_OVERWRITE | MM_MAP_PAGE_NO_NEW_TABLES); +} + +void MMObjectCacheInsert(MMObjectCache *cache, MMObjectCacheItem *item) { + KSpinlockAcquire(&cache->lock); + cache->items.Insert(item, false /* end */); + cache->count++; + __sync_fetch_and_add(&pmm.approximateTotalObjectCacheBytes, cache->averageObjectBytes); + + if (MM_OBJECT_CACHE_SHOULD_TRIM()) { + KEventSet(&pmm.trimObjectCaches, false, true); + } + + KSpinlockRelease(&cache->lock); +} + +void MMObjectCacheRemove(MMObjectCache *cache, MMObjectCacheItem *item, bool alreadyLocked) { + if (!alreadyLocked) KSpinlockAcquire(&cache->lock); + else KSpinlockAssertLocked(&cache->lock); + item->Remove(); + cache->count--; + __sync_fetch_and_sub(&pmm.approximateTotalObjectCacheBytes, cache->averageObjectBytes); + + if (!alreadyLocked) KSpinlockRelease(&cache->lock); +} + +MMObjectCacheItem *MMObjectCacheRemoveLRU(MMObjectCache *cache) { + MMObjectCacheItem *item = nullptr; + KSpinlockAcquire(&cache->lock); + + if (cache->count) { + item = cache->items.first; + MMObjectCacheRemove(cache, item, true); + } + + KSpinlockRelease(&cache->lock); + return item; +} + +void MMObjectCacheRegister(MMObjectCache *cache, bool (*trim)(MMObjectCache *), size_t averageObjectBytes) { + KMutexAcquire(&pmm.objectCacheListMutex); + cache->trim = trim; + cache->averageObjectBytes = averageObjectBytes; + cache->item.thisItem = cache; + pmm.objectCacheList.InsertEnd(&cache->item); + KMutexRelease(&pmm.objectCacheListMutex); +} + +void MMObjectCacheUnregister(MMObjectCache *cache) { + KMutexAcquire(&pmm.objectCacheListMutex); + pmm.objectCacheList.Remove(&cache->item); + KMutexRelease(&pmm.objectCacheListMutex); + + // Wait for any trim threads still using the cache to finish. + KWriterLockTake(&cache->trimLock, K_LOCK_EXCLUSIVE); + KWriterLockReturn(&cache->trimLock, K_LOCK_EXCLUSIVE); +} + +void MMObjectCacheFlush(MMObjectCache *cache) { + if (cache->item.list) KernelPanic("MMObjectCacheFlush - Cache %x must be unregistered before flushing.\n", cache); + + // Wait for any trim threads still using the cache to finish. + KWriterLockTake(&cache->trimLock, K_LOCK_EXCLUSIVE); + + // Trim the cache until it is empty. + // The trim callback is allowed to increase cache->count, + // but nobody else should be increasing it once it has been unregistered. + while (cache->count) cache->trim(cache); + + // Return the trim lock. + KWriterLockReturn(&cache->trimLock, K_LOCK_EXCLUSIVE); +} + +void MMObjectCacheTrimThread() { + MMObjectCache *cache = nullptr; + + while (true) { + while (!MM_OBJECT_CACHE_SHOULD_TRIM()) { + KEventReset(&pmm.trimObjectCaches); + KEventWait(&pmm.trimObjectCaches); + } + + KMutexAcquire(&pmm.objectCacheListMutex); + + // TODO Is there a faster way to find the next cache? + // This needs to work with multiple producers and consumers. + // And I don't want to put the caches in an array, because then registering a cache could fail. + + MMObjectCache *previousCache = cache; + cache = nullptr; + + LinkedItem *item = pmm.objectCacheList.firstItem; + + while (item) { + if (item->thisItem == previousCache && item->nextItem) { + cache = item->nextItem->thisItem; + break; + } + + item = item->nextItem; + } + + if (!cache && pmm.objectCacheList.firstItem) { + cache = pmm.objectCacheList.firstItem->thisItem; + } + + if (cache) { + KWriterLockTake(&cache->trimLock, K_LOCK_SHARED); + } + + KMutexRelease(&pmm.objectCacheListMutex); + + if (!cache) { + continue; + } + + for (uintptr_t i = 0; i < MM_OBJECT_CACHE_TRIM_GROUP_COUNT; i++) { + if (!cache->trim(cache)) { + break; + } + } + + KWriterLockReturn(&cache->trimLock, K_LOCK_SHARED); + } +} + +void MMInitialise() { + { + // Initialise coreMMSpace. + + MMArchInitialiseVAS(); + mmCoreRegions[0].baseAddress = MM_CORE_SPACE_START; + mmCoreRegions[0].pageCount = MM_CORE_SPACE_SIZE / K_PAGE_SIZE; + mmCoreRegions[0].core.used = false; + mmCoreRegionCount = 1; + } + + { + // Initialise kernelMMSpace. + + KMutexAcquire(&coreMMSpace->reserveMutex); + kernelMMSpace->data.l1Commit = (uint8_t *) MMReserve(coreMMSpace, L1_COMMIT_SIZE_BYTES, MM_REGION_NORMAL | MM_REGION_NO_COMMIT_TRACKING | MM_REGION_FIXED)->baseAddress; + KMutexRelease(&coreMMSpace->reserveMutex); + + MMRegion *region = (MMRegion *) EsHeapAllocate(sizeof(MMRegion), true, K_CORE); + region->baseAddress = MM_KERNEL_SPACE_START; + region->pageCount = MM_KERNEL_SPACE_SIZE / K_PAGE_SIZE; + TreeInsert(&kernelMMSpace->freeRegionsBase, ®ion->itemBase, region, MakeShortKey(region->baseAddress)); + TreeInsert(&kernelMMSpace->freeRegionsSize, ®ion->itemSize, region, MakeShortKey(region->pageCount), AVL_DUPLICATE_KEYS_ALLOW); + } + + { + // Initialise physical memory management. + + KMutexAcquire(&kernelMMSpace->reserveMutex); + pmm.pmManipulationRegion = (void *) MMReserve(kernelMMSpace, PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES * K_PAGE_SIZE, ES_FLAGS_DEFAULT)->baseAddress; + KMutexRelease(&kernelMMSpace->reserveMutex); + + physicalMemoryHighest += K_PAGE_SIZE << 3; + pmm.pageFrames = (MMPageFrame *) MMStandardAllocate(kernelMMSpace, (physicalMemoryHighest >> K_PAGE_BITS) * sizeof(MMPageFrame), MM_REGION_FIXED); + pmm.freeOrZeroedPageBitset.Initialise(physicalMemoryHighest >> K_PAGE_BITS, true); + + uint64_t commitLimit = 0; + + while (physicalMemoryRegionsPagesCount) { + // TODO This loop is a bit slow... + MMPhysicalInsertFreePage(MMPhysicalAllocate(ES_FLAGS_DEFAULT) >> K_PAGE_BITS); + commitLimit++; + } + + pmm.commitLimit = pmm.commitFixedLimit = commitLimit; + KernelLog(LOG_INFO, "Memory", "pmm initialised", "MMInitialise - PMM initialised with a fixed commit limit of %d pages.\n", pmm.commitLimit); + } + + { + // Initialise file cache. + + CCInitialise(); + } + + { + // Thread initialisation. + + pmm.zeroPageEvent.autoReset = true; + MMCommit(PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES * K_PAGE_SIZE, true); + pmm.zeroPageThread = scheduler.SpawnThread("MMZero", (uintptr_t) MMZeroPageThread, 0, SPAWN_THREAD_LOW_PRIORITY); + pmm.balanceThread = scheduler.SpawnThread("MMBalance", (uintptr_t) MMBalanceThread, 0, ES_FLAGS_DEFAULT); + pmm.balanceThread->isPageGenerator = true; + scheduler.SpawnThread("MMObjTrim", (uintptr_t) MMObjectCacheTrimThread, 0, ES_FLAGS_DEFAULT); + } +} + +#endif diff --git a/kernel/module.h b/kernel/module.h new file mode 100644 index 0000000..1638970 --- /dev/null +++ b/kernel/module.h @@ -0,0 +1,1014 @@ +// TODO Include more functions and definitions. +// TODO Add K- prefix to more identifiers. + +#define KERNEL + +#ifndef K_PRIVATE +#define K_PRIVATE private: +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#define alloca __builtin_alloca + +#define KERNEL_VERSION (1) +typedef uint64_t (*KGetKernelVersionCallback)(); +#ifdef KERNEL_MODULE +extern "C" uint64_t GetKernelVersion() { return KERNEL_VERSION; } +#endif + +// --------------------------------------------------------------------------------------------------------------- +// API header. +// --------------------------------------------------------------------------------------------------------------- + +#define ES_DIRECT_API +#define ES_FORWARD(x) x +#define ES_EXTERN_FORWARD ES_EXTERN_C +#include + +// TODO stb's behaviour with null termination is non-standard. +extern "C" int EsCRTsprintf(char *buffer, const char *format, ...); +extern "C" int EsCRTsnprintf(char *buffer, size_t bufferSize, const char *format, ...); +extern "C" int EsCRTvsnprintf(char *buffer, size_t bufferSize, const char *format, va_list arguments); + +// --------------------------------------------------------------------------------------------------------------- +// Global defines. +// --------------------------------------------------------------------------------------------------------------- + +#define K_VERSION (0x010000) +#define K_USER_BUFFER // Used to mark pointers that (might) point to non-kernel memory. +#define K_MAX_PROCESSORS (256) // See cpu_local_storage_size in x86_64.s. +#define K_MAX_PATH (4096) +#define K_ACCESS_IMPLEMENTATION_DEFINED (2) + +// --------------------------------------------------------------------------------------------------------------- +// Heap allocations. +// --------------------------------------------------------------------------------------------------------------- + +struct EsHeap; + +extern EsHeap heapCore; +extern EsHeap heapFixed; + +#define K_CORE (&heapCore) +#define K_FIXED (&heapFixed) +#define K_PAGED (&heapFixed) + +void *EsHeapAllocate(size_t size, bool zeroMemory, EsHeap *kernelHeap); +void EsHeapFree(void *address, size_t expectedSize, EsHeap *kernelHeap); + +// --------------------------------------------------------------------------------------------------------------- +// Debug output. +// --------------------------------------------------------------------------------------------------------------- + +enum KLogLevel { + LOG_VERBOSE, + LOG_INFO, + LOG_ERROR, +}; + +void KernelLog(KLogLevel level, const char *subsystem, const char *event, const char *format, ...); +void KernelPanic(const char *format, ...); +void EsPrint(const char *format, ...); + +#define EsPanic KernelPanic + +// --------------------------------------------------------------------------------------------------------------- +// IRQs. +// --------------------------------------------------------------------------------------------------------------- + +typedef bool (*KIRQHandler)(uintptr_t interruptIndex /* tag for MSI */, void *context); + +// Interrupts are active high and level triggered, unless overridden by the ACPI MADT table. +bool KRegisterIRQ(uintptr_t interruptIndex, KIRQHandler handler, void *context, const char *cOwnerName); + +struct KMSIInformation { + // Both fields are zeroed if the MSI could not be registered. + uintptr_t address; + uintptr_t data; + uintptr_t tag; +}; + +KMSIInformation KRegisterMSI(KIRQHandler handler, void *context, const char *cOwnerName); +void KUnregisterMSI(uintptr_t tag); + +// --------------------------------------------------------------------------------------------------------------- +// Async tasks. +// --------------------------------------------------------------------------------------------------------------- + +// Async tasks are executed on the same processor that registered it. +// They can be registered with interrupts disabled (e.g. in IRQ handlers). +// They are executed in the order they were registered. +// They can acquire mutexes, but cannot perform IO. + +typedef void (*KAsyncTaskCallback)(EsGeneric argument); +void KRegisterAsyncTask(KAsyncTaskCallback callback, EsGeneric argument, bool needed = true); + +// --------------------------------------------------------------------------------------------------------------- +// Common data types, algorithms and things. +// --------------------------------------------------------------------------------------------------------------- + +#ifndef K_IN_CORE_KERNEL +#define SHARED_DEFINITIONS_ONLY +#endif + +#include +#include +#include +#include + +#ifdef K_IN_CORE_KERNEL +#define SHARED_COMMON_WANT_ALL +#include +#endif + +// --------------------------------------------------------------------------------------------------------------- +// Processor instruction wrappers. +// --------------------------------------------------------------------------------------------------------------- + +extern "C" struct CPULocalStorage *GetLocalStorage(); +extern "C" struct Thread *GetCurrentThread(); + +extern "C" void ProcessorDisableInterrupts(); +extern "C" void ProcessorEnableInterrupts(); +extern "C" bool ProcessorAreInterruptsEnabled(); +extern "C" void ProcessorHalt(); +extern "C" void ProcessorIdle(); +extern "C" void ProcessorOut8(uint16_t port, uint8_t value); +extern "C" uint8_t ProcessorIn8(uint16_t port); +extern "C" void ProcessorOut16(uint16_t port, uint16_t value); +extern "C" uint16_t ProcessorIn16(uint16_t port); +extern "C" void ProcessorOut32(uint16_t port, uint32_t value); +extern "C" uint32_t ProcessorIn32(uint16_t port); +extern "C" void ProcessorInvalidatePage(uintptr_t virtualAddress); +extern "C" void ProcessorInvalidateAllPages(); +extern "C" void ProcessorAPStartup(); +extern "C" void ProcessorMagicBreakpoint(...); +extern "C" void ProcessorBreakpointHelper(...); +extern "C" void ProcessorSetLocalStorage(struct CPULocalStorage *cls); +extern "C" void ProcessorSetThreadStorage(uintptr_t tls); +extern "C" size_t ProcessorSendIPI(uintptr_t interrupt, bool nmi = false, int processorID = -1); // Returns the number of processors the IPI was *not* sent to. +extern "C" void ProcessorDebugOutputByte(uint8_t byte); +extern "C" void ProcessorFakeTimerInterrupt(); +extern "C" uint64_t ProcessorReadTimeStamp(); +extern "C" void DoContextSwitch(struct InterruptContext *context, + uintptr_t virtualAddressSpace, uintptr_t threadKernelStack, struct Thread *newThread); +extern "C" void ProcessorSetAddressSpace(uintptr_t virtualAddressSpaceIdentifier); +extern "C" uintptr_t ProcessorGetAddressSpace(); +extern "C" void ProcessorFlushCodeCache(); +extern "C" void ProcessorFlushCache(); + +#ifdef ARCH_X86_64 +extern "C" uintptr_t ProcessorGetRSP(); +extern "C" uintptr_t ProcessorGetRBP(); +extern "C" uint64_t ProcessorReadMXCSR(); +#endif + +// --------------------------------------------------------------------------------------------------------------- +// Kernel core. +// --------------------------------------------------------------------------------------------------------------- + +extern "C" uint64_t KGetTimeInMs(); // Scheduler time. + +bool KCopyToUser(K_USER_BUFFER void *destination, const void *source, size_t bytes); +bool KCopyFromUser(void *destination, K_USER_BUFFER const void *source, size_t bytes); + +void *KGetRSDP(); +size_t KGetCPUCount(); +CPULocalStorage *KGetCPULocal(uintptr_t index); +uint64_t KCPUCurrentID(); +uint64_t KGetTimeStampTicksPerMs(); +uint64_t KGetTimeStampTicksPerUs(); + +bool KBootedFromEFI(); +bool KInIRQ(); +void KSwitchThreadAfterIRQ(); + +void KDebugKeyPressed(); +int KWaitKey(); + +#ifdef ARCH_X86_COMMON +void KPS2SafeToInitialise(); +#endif + +EsUniqueIdentifier KGetBootIdentifier(); + +struct KTimeout { + uint64_t end; + inline KTimeout(int ms) { end = KGetTimeInMs() + ms; } + inline bool Hit() { return KGetTimeInMs() >= end; } +}; + +enum KernelObjectType : uint32_t { + COULD_NOT_RESOLVE_HANDLE = 0x00000000, + KERNEL_OBJECT_NONE = 0x80000000, + + KERNEL_OBJECT_PROCESS = 0x00000001, // A process. + KERNEL_OBJECT_THREAD = 0x00000002, // A thread. + KERNEL_OBJECT_WINDOW = 0x00000004, // A window. + KERNEL_OBJECT_SHMEM = 0x00000008, // A region of shared memory. + KERNEL_OBJECT_NODE = 0x00000010, // A file system node (file or directory). + KERNEL_OBJECT_EVENT = 0x00000020, // A synchronisation event. + KERNEL_OBJECT_CONSTANT_BUFFER = 0x00000040, // A buffer of unmodifiable data stored in the kernel's address space. +#ifdef ENABLE_POSIX_SUBSYSTEM + KERNEL_OBJECT_POSIX_FD = 0x00000100, // A POSIX file descriptor, used in the POSIX subsystem. +#endif + KERNEL_OBJECT_PIPE = 0x00000200, // A pipe through which data can be sent between processes, blocking when full or empty. + KERNEL_OBJECT_EMBEDDED_WINDOW = 0x00000400, // An embedded window object, referencing its container Window. + KERNEL_OBJECT_DIRECTORY_MONITOR = 0x00000100, // Monitors a directory, sending messages to the owner process. + KERNEL_OBJECT_EVENT_SINK = 0x00002000, // An event sink. Events can be forwarded to it, allowing waiting on many objects. + KERNEL_OBJECT_CONNECTION = 0x00004000, // A network connection. +}; + +void CloseHandleToObject(void *object, KernelObjectType type, uint32_t flags = 0); +bool OpenHandleToObject(void *object, KernelObjectType type, uint32_t flags = 0, bool maybeHasNoHandles = false); + +// --------------------------------------------------------------------------------------------------------------- +// Module loading. +// --------------------------------------------------------------------------------------------------------------- + +#define KModuleResolveSymbolCallback (const char *name, size_t nameBytes) +typedef void *(*KModuleResolveSymbolCallbackFunction) KModuleResolveSymbolCallback; + +struct KModule { + const char *path; + size_t pathBytes; + KModuleResolveSymbolCallbackFunction resolveSymbol; + + uint8_t *buffer; +}; + +struct KLoadedExecutable { + uintptr_t startAddress; + + uintptr_t tlsImageStart; + uintptr_t tlsImageBytes; + uintptr_t tlsBytes; // All bytes after the image are to be zeroed. +}; + +EsError KLoadELF(struct KNode *node, KLoadedExecutable *executable); +EsError KLoadELFModule(KModule *module); +uintptr_t KFindSymbol(KModule *module, const char *name, size_t nameBytes); + +// --------------------------------------------------------------------------------------------------------------- +// Synchronisation primitives. +// --------------------------------------------------------------------------------------------------------------- + +struct KSpinlock { // Mutual exclusion. CPU-owned. Disables interrupts. The only synchronisation primitive that can be acquired with interrupts disabled. + K_PRIVATE + volatile uint8_t state, ownerCPU; + volatile bool interruptsEnabled; +#ifdef DEBUG_BUILD + struct Thread *volatile owner; + volatile uintptr_t acquireAddress, releaseAddress; +#endif +}; + +void KSpinlockAcquire(KSpinlock *spinlock); +void KSpinlockRelease(KSpinlock *spinlock, bool force = false); +void KSpinlockAssertLocked(KSpinlock *spinlock); + +struct KMutex { // Mutual exclusion. Thread-owned. + K_PRIVATE + struct Thread *volatile owner; +#ifdef DEBUG_BUILD + uintptr_t acquireAddress, releaseAddress, id; +#endif + LinkedList blockedThreads; +}; + +#ifdef DEBUG_BUILD +bool _KMutexAcquire(KMutex *mutex, const char *cMutexString, const char *cFile, int line); +void _KMutexRelease(KMutex *mutex, const char *cMutexString, const char *cFile, int line); +#define KMutexAcquire(mutex) _KMutexAcquire(mutex, #mutex, __FILE__, __LINE__) +#define KMutexRelease(mutex) _KMutexRelease(mutex, #mutex, __FILE__, __LINE__) +#else +bool KMutexAcquire(KMutex *mutex); +void KMutexRelease(KMutex *mutex); +#endif +void KMutexAssertLocked(KMutex *mutex); + +struct KEvent { // Waiting and notifying. Can wait on multiple at once. Can be set and reset with interrupts disabled. + volatile bool autoReset; // This should be first field in the structure, + // so that the type of KEvent can be easily declared with {autoReset}. + volatile uintptr_t state; + + K_PRIVATE + + LinkedList blockedThreads; + volatile size_t handles; + struct EventSinkTable *sinkTable; +}; + +bool KEventSet(KEvent *event, bool schedulerAlreadyLocked = false, bool maybeAlreadySet = false); +void KEventReset(KEvent *event); +bool KEventPoll(KEvent *event); // TODO Remove this! Currently it is only used by KAudioFillBuffersFromMixer. +bool KEventWait(KEvent *event, uint64_t timeoutMs = ES_WAIT_NO_TIMEOUT); // See KWaitEvents to wait for multiple events. Returns false if the wait timed out. + +struct KWriterLock { // One writer or many readers. + K_PRIVATE + LinkedList blockedThreads; + volatile int64_t state; // -1: exclusive; >0: shared owners. +#ifdef DEBUG_BUILD + volatile Thread *exclusiveOwner; +#endif +}; + +#define K_LOCK_EXCLUSIVE (true) +#define K_LOCK_SHARED (false) +bool KWriterLockTake(KWriterLock *lock, bool write, bool poll = false); +void KWriterLockReturn(KWriterLock *lock, bool write); +void KWriterLockConvertExclusiveToShared(KWriterLock *lock); +void KWriterLockAssertExclusive(KWriterLock *lock); +void KWriterLockAssertShared(KWriterLock *lock); +void KWriterLockAssertLocked(KWriterLock *lock); + +struct KSemaphore { // Exclusion with a multiple units. + KEvent available; + volatile uintptr_t units; + + K_PRIVATE + + KMutex mutex; // TODO Make this a spinlock? + uintptr_t _custom; + uintptr_t lastTaken; +}; + +bool KSemaphoreTake(KSemaphore *semaphore, uintptr_t units = 1, uintptr_t timeoutMs = ES_WAIT_NO_TIMEOUT); +void KSemaphoreReturn(KSemaphore *semaphore, uintptr_t units = 1); +bool KSemaphorePoll(KSemaphore *semaphore); +void KSemaphoreSet(KSemaphore *semaphore, uintptr_t units = 1); + +struct KTimer { + KEvent event; + K_PRIVATE + LinkedItem item; + uint64_t triggerTimeMs; + KAsyncTaskCallback callback; + EsGeneric argument; +}; + +void KTimerSet(KTimer *timer, uint64_t triggerInMs, KAsyncTaskCallback callback = nullptr, EsGeneric argument = 0); +void KTimerRemove(KTimer *timer); // Timers with callbacks cannot be removed (it'd race with async task delivery). + +// --------------------------------------------------------------------------------------------------------------- +// Window manager. +// --------------------------------------------------------------------------------------------------------------- + +void KCursorUpdate(int xMovement, int yMovement, unsigned buttons); +void KKeyboardUpdate(uint16_t *keysDown, size_t keysDownCount); +void KKeyPress(unsigned scancode); + +uint64_t KGameControllerConnect(); +void KGameControllerDisconnect(uint64_t id); +void KGameControllerUpdateState(EsGameControllerState *state); + +#define K_SCANCODE_KEY_RELEASED (1 << 15) +#define K_SCANCODE_KEY_PRESSED (0 << 15) + +#define K_LEFT_BUTTON (1) +#define K_MIDDLE_BUTTON (2) +#define K_RIGHT_BUTTON (4) + +// --------------------------------------------------------------------------------------------------------------- +// Memory manager. +// --------------------------------------------------------------------------------------------------------------- + +#ifdef ARCH_X86_64 +#define K_PAGE_BITS (12) +#define K_PAGE_SIZE ((uintptr_t) 1 << K_PAGE_BITS) +#define K_USER_ADDRESS_SPACE_START (0x0000000000000000ULL) +#define K_USER_ADDRESS_SPACE_END (0x0000800000000000ULL) +#define K_KERNEL_ADDRESS_SPACE_START (0xFFFF800000000000ULL) +#define K_KERNEL_ADDRESS_SPACE_END (0xFFFFFFFFFFFFFFFFULL) +#define K_STACK_GROWS_DOWN +#endif + +struct MMSpace; +MMSpace *MMGetKernelSpace(); +MMSpace *MMGetCurrentProcessSpace(); + +#define MM_REGION_FIXED (0x01) // A region where all the physical pages are allocated up-front, and cannot be removed from the working set. +#define MM_REGION_NOT_CACHEABLE (0x02) // Do not cache the pages in the region. +#define MM_REGION_NO_COMMIT_TRACKING (0x04) // Page committing is manually tracked. +#define MM_REGION_READ_ONLY (0x08) // Generate page faults when written to. +#define MM_REGION_COPY_ON_WRITE (0x10) // Copy on write. +#define MM_REGION_WRITE_COMBINING (0x20) // Write combining caching is enabled. Incompatible with MM_REGION_NOT_CACHEABLE. +#define MM_REGION_EXECUTABLE (0x40) +#define MM_REGION_USER (0x80) // The application created it, and is therefore allowed to modify it. +// Limited by region type flags. + +void *MMMapPhysical(MMSpace *space, uintptr_t address, size_t bytes, uint64_t caching); +void *MMStandardAllocate(MMSpace *space, size_t bytes, uint32_t flags, void *baseAddress = nullptr, bool commitAll = true); +bool MMFree(MMSpace *space, void *address, size_t expectedSize = 0, bool userOnly = false); +void MMAllowWriteCombiningCaching(MMSpace *space, void *virtualAddress); +size_t MMGetRegionPageCount(MMSpace *space, void *virtualAddress); + +uint64_t MMNumberOfUsablePhysicalPages(); + +// Returns 0 if not mapped. Rounds address down to nearest page. +uintptr_t MMArchTranslateAddress(MMSpace *space, uintptr_t virtualAddress, bool writeAccess = false /* if true, return 0 if address not writable */); + +// Must be done with interrupts disabled; does not invalidate the page on other processors. +void MMArchRemap(MMSpace *space, const void *virtualAddress, uintptr_t newPhysicalAddress); + +#define MM_PHYSICAL_ALLOCATE_CAN_FAIL (1 << 0) // Don't panic if the allocation fails. +#define MM_PHYSICAL_ALLOCATE_COMMIT_NOW (1 << 1) // Commit (fixed) the allocated pages. +#define MM_PHYSICAL_ALLOCATE_ZEROED (1 << 2) // Zero the pages. +#define MM_PHYSICAL_ALLOCATE_LOCK_ACQUIRED (1 << 3) // The page frame mutex is already acquired. + +uintptr_t /* Returns physical address of first page, or 0 if none were available. */ MMPhysicalAllocate(unsigned flags, + uintptr_t count = 1 /* Number of contiguous pages to allocate. */, + uintptr_t align = 1 /* Alignment, in pages. */, + uintptr_t below = 0 /* Upper limit of physical address, in pages. E.g. for 32-bit pages only, pass (0x100000000 >> K_PAGE_BITS). */); +void MMPhysicalFree(uintptr_t page /* Physical address. */, + bool mutexAlreadyAcquired = false /* Internal use. Pass false. */, + size_t count = 1 /* Number of consecutive pages to free. */); + +bool MMPhysicalAllocateAndMap(size_t sizeBytes, size_t alignmentBytes, size_t maximumBits, bool zeroed, + uint64_t caching, uint8_t **virtualAddress, uintptr_t *physicalAddress); +void MMPhysicalFreeAndUnmap(void *virtualAddress, uintptr_t physicalAddress); + +#define MM_SHARED_ENTRY_PRESENT (1) +struct MMSharedRegion; +MMSharedRegion *MMSharedCreateRegion(size_t sizeBytes, bool fixed = false, uintptr_t below = 0 /* See fixed = true, passed to MMPhysicalAllocate. */); +uintptr_t MMSharedLookupPage(MMSharedRegion *region, uintptr_t pageIndex); +void *MMMapShared(MMSpace *space, MMSharedRegion *sharedRegion, uintptr_t offset, size_t bytes, uint32_t additionalFlags = ES_FLAGS_DEFAULT, void *baseAddresses = nullptr); + +// Check that the range of physical memory is unusable. +// Panics on failure. +void MMCheckUnusable(uintptr_t physicalStart, size_t bytes); + +#include + +typedef SimpleList MMObjectCacheItem; + +struct MMObjectCache { + K_PRIVATE + KSpinlock lock; // Used instead of a mutex to keep accesses to the list lightweight. + SimpleList items; + size_t count; + bool (*trim)(MMObjectCache *cache); // Return true if an object was trimmed. + KWriterLock trimLock; // Open in shared access to trim the cache. + LinkedItem item; + size_t averageObjectBytes; +}; + +void MMObjectCacheInsert(MMObjectCache *cache, MMObjectCacheItem *item); +void MMObjectCacheRemove(MMObjectCache *cache, MMObjectCacheItem *item, bool alreadyLocked = false); +MMObjectCacheItem *MMObjectCacheRemoveLRU(MMObjectCache *cache); +void MMObjectCacheRegister(MMObjectCache *cache, bool (*trimCallback)(MMObjectCache *), size_t averageObjectBytes); +void MMObjectCacheUnregister(MMObjectCache *cache); +void MMObjectCacheFlush(MMObjectCache *cache); + +// --------------------------------------------------------------------------------------------------------------- +// Scheduler. +// --------------------------------------------------------------------------------------------------------------- + +uint64_t KProcessCurrentID(); +uint64_t KThreadCurrentID(); + +bool KThreadCreate(const char *cName, void (*startAddress)(uintptr_t), uintptr_t argument = 0); +extern "C" void KThreadTerminate(); // Terminates the current thread. Kernel threads can only be terminated by themselves. +void KYield(); + +uintptr_t KWaitEvents(KEvent **events, size_t count); + +struct KWorkGroup { + inline void Initialise() { + remaining = 1; + success = 1; + KEventReset(&event); + } + + inline bool Wait() { + if (__sync_fetch_and_sub(&remaining, 1) != 1) { + KEventWait(&event); + } + + if (remaining) { + KernelPanic("KWorkGroup::Wait - Expected remaining operations to be 0 after event set.\n"); + } + + return success ? true : false; + } + + inline void Start() { + if (__sync_fetch_and_add(&remaining, 1) == 0) { + KernelPanic("KWorkGroup::Start - Could not start operation on completed dispatch group.\n"); + } + } + + inline void End(bool _success) { + if (!_success) { + success = false; + __sync_synchronize(); + } + + if (__sync_fetch_and_sub(&remaining, 1) == 1) { + KEventSet(&event); + } + } + + K_PRIVATE + + volatile uintptr_t remaining; + volatile uintptr_t success; + KEvent event; +}; + +// --------------------------------------------------------------------------------------------------------------- +// Device management. +// --------------------------------------------------------------------------------------------------------------- + +struct KInstalledDriver { + char *name; // The name of the driver. + size_t nameBytes; + char *parent; // The name of the parent driver. + size_t parentBytes; + char *config; // The driver's configuration, taken from kernel/config.ini. + size_t configBytes; + bool builtin; // True if the driver is builtin to the kernel executable. + struct KDriver *loadedDriver; // The corresponding driver, if it has been loaded. +}; + +struct KDevice { + const char *cDebugName; + + KDevice *parent; // The parent device. + Array children; // Child devices. + +#define K_DEVICE_REMOVED (1 << 0) + uint8_t flags; + + uint32_t handles; + + // These callbacks are called with the deviceTreeMutex locked. + void (*shutdown)(KDevice *device); // Called when the computer is about to shutdown. Optional. + void (*dumpState)(KDevice *device); // Dump the entire state of the device for debugging. Optional. + void (*removed)(KDevice *device); // Called when the device is removed. Called after the children are informed. Optional. + void (*destroy)(KDevice *device); // Called just before the device is destroyed. +}; + +struct KDriver { + // Called when a new device the driver implements is attached. + // You should pass this the parent device to `KDeviceCreate`. + // The parent device pointer cannot be used after the function returns. + void (*attach)(KDevice *parent); +}; + +typedef bool KDriverIsImplementorCallback(KInstalledDriver *driver, KDevice *device); // Return true if the child driver implements the device. + +// Searches for a driver that implements the device. Loads the driver if necessary (possibly asynchronously), and calls `attach`. +// Returns true if a matching driver was found; a maximum of one driver will be called. +// Example usage: +// - A bus driver finds a function with a connected device. +// - It creates a device for that function with KDeviceCreate, and sets the parent to the bus controller device. +// - It calls KDeviceAttach on the function device. +// - A suitable driver is found, which creates a device with that as its parent. +bool KDeviceAttach(KDevice *parentDevice, const char *cParentDriver /* match the parent field in the config */, KDriverIsImplementorCallback callback); + +// Similar to KDeviceAttach, except it calls `attach` for every driver that matches the parent field. +void KDeviceAttachAll(KDevice *parentDevice, const char *cParentDriver); + +// Similar to KDeviceAttach, except it calls `attach` only for the driver matching the provided name. Returns true if the driver was found. +bool KDeviceAttachByName(KDevice *parentDevice, const char *cName); + +KDevice *KDeviceCreate(const char *cDebugName, KDevice *parent, size_t bytes /* must be at least the size of a KDevice */); +void KDeviceOpenHandle(KDevice *device); +void KDeviceDestroy(KDevice *device); // Call if initialisation of the device failed. Otherwise use KDeviceCloseHandle. +void KDeviceCloseHandle(KDevice *device); // The device creator is responsible for one handle after the creating it. The device is destroyed once all handles are closed. +void KDeviceRemoved(KDevice *device); // Call when a child device is removed. Must be called only once! + +#include + +// --------------------------------------------------------------------------------------------------------------- +// Direct memory access. +// --------------------------------------------------------------------------------------------------------------- + +struct KDMASegment { + uintptr_t physicalAddress; + size_t byteCount; + bool isLast; +}; + +struct KDMABuffer; +uintptr_t KDMABufferGetVirtualAddress(KDMABuffer *buffer); // TODO Temporary. +size_t KDMABufferGetTotalByteCount(KDMABuffer *buffer); +KDMASegment KDMABufferNextSegment(KDMABuffer *buffer, bool peek = false); +bool KDMABufferIsComplete(KDMABuffer *buffer); // Returns true if the end of the transfer buffer has been reached. + +// --------------------------------------------------------------------------------------------------------------- +// Block devices. +// --------------------------------------------------------------------------------------------------------------- + +#define K_ACCESS_READ (0) +#define K_ACCESS_WRITE (1) + +struct KBlockDeviceAccessRequest { + struct KBlockDevice *device; + EsFileOffset offset; + size_t count; + int operation; + KDMABuffer *buffer; + uint64_t flags; + KWorkGroup *dispatchGroup; +}; + +typedef void (*KDeviceAccessCallbackFunction)(KBlockDeviceAccessRequest request); + +struct KBlockDevice : KDevice { + KDeviceAccessCallbackFunction access; // Don't call directly; see KFileSystem::Access. + size_t sectorSize, maxAccessSectorCount; + EsFileOffset sectorCount; + bool noMBR; // Set to `true` if this device cannot contain a MBR. + bool readOnly; + uint8_t nestLevel; + uint8_t driveType; + const char *cModel; + + K_PRIVATE + + uint8_t *information; // Signature block. Only valid during fileSystem detection. +}; + +#define FS_PARTITION_DEVICE_NO_MBR (1 << 0) +void FSPartitionDeviceCreate(KBlockDevice *parent, EsFileOffset offset, EsFileOffset sectorCount, unsigned flags, const char *cName); + +// --------------------------------------------------------------------------------------------------------------- +// PCI. +// --------------------------------------------------------------------------------------------------------------- + +struct KPCIDevice : KDevice { + void WriteBAR8(uintptr_t index, uintptr_t offset, uint8_t value); + uint8_t ReadBAR8(uintptr_t index, uintptr_t offset); + void WriteBAR16(uintptr_t index, uintptr_t offset, uint16_t value); + uint16_t ReadBAR16(uintptr_t index, uintptr_t offset); + void WriteBAR32(uintptr_t index, uintptr_t offset, uint32_t value); + uint32_t ReadBAR32(uintptr_t index, uintptr_t offset); + void WriteBAR64(uintptr_t index, uintptr_t offset, uint64_t value); + uint64_t ReadBAR64(uintptr_t index, uintptr_t offset); + + void WriteConfig8(uintptr_t offset, uint8_t value); + uint8_t ReadConfig8(uintptr_t offset); + void WriteConfig16(uintptr_t offset, uint16_t value); + uint16_t ReadConfig16(uintptr_t offset); + void WriteConfig32(uintptr_t offset, uint32_t value); + uint32_t ReadConfig32(uintptr_t offset); + +#define K_PCI_FEATURE_BAR_0 (1 << 0) +#define K_PCI_FEATURE_BAR_1 (1 << 1) +#define K_PCI_FEATURE_BAR_2 (1 << 2) +#define K_PCI_FEATURE_BAR_3 (1 << 3) +#define K_PCI_FEATURE_BAR_4 (1 << 4) +#define K_PCI_FEATURE_BAR_5 (1 << 5) +#define K_PCI_FEATURE_INTERRUPTS (1 << 8) +#define K_PCI_FEATURE_BUSMASTERING_DMA (1 << 9) +#define K_PCI_FEATURE_MEMORY_SPACE_ACCESS (1 << 10) +#define K_PCI_FEATURE_IO_PORT_ACCESS (1 << 11) + bool EnableFeatures(uint64_t features); + bool EnableSingleInterrupt(KIRQHandler irqHandler, void *context, const char *cOwnerName); + + uint32_t deviceID, subsystemID, domain; + uint8_t classCode, subclassCode, progIF; + uint8_t bus, slot, function; + uint8_t interruptPin, interruptLine; + + uint8_t *baseAddressesVirtual[6]; + uintptr_t baseAddressesPhysical[6]; + size_t baseAddressesSizes[6]; + + uint32_t baseAddresses[6]; + + K_PRIVATE + bool EnableMSI(KIRQHandler irqHandler, void *context, const char *cOwnerName); +}; + +uint32_t KPCIReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size = 32); +void KPCIWriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size = 32); + +// --------------------------------------------------------------------------------------------------------------- +// USB. +// --------------------------------------------------------------------------------------------------------------- + +struct KUSBDescriptorHeader { + uint8_t length; + uint8_t descriptorType; +} __attribute__((packed)); + +struct KUSBConfigurationDescriptor : KUSBDescriptorHeader { + uint16_t totalLength; + uint8_t interfaceCount; + uint8_t configurationIndex; + uint8_t configurationString; + uint8_t attributes; + uint8_t maximumPower; +} __attribute__((packed)); + +struct KUSBInterfaceDescriptor : KUSBDescriptorHeader { + uint8_t interfaceIndex; + uint8_t alternateSetting; + uint8_t endpointCount; + uint8_t interfaceClass; + uint8_t interfaceSubclass; + uint8_t interfaceProtocol; + uint8_t interfaceString; +} __attribute__((packed)); + +struct KUSBDeviceDescriptor : KUSBDescriptorHeader { + uint16_t specificationVersion; + uint8_t deviceClass; + uint8_t deviceSubclass; + uint8_t deviceProtocol; + uint8_t maximumPacketSize; + uint16_t vendorID; + uint16_t productID; + uint16_t deviceVersion; + uint8_t manufacturerString; + uint8_t productString; + uint8_t serialNumberString; + uint8_t configurationCount; +} __attribute__((packed)); + +struct KUSBEndpointCompanionDescriptor : KUSBDescriptorHeader { + uint8_t maxBurst; + uint8_t attributes; + uint16_t bytesPerInterval; + + inline uint8_t GetMaximumStreams() { return attributes & 0x1F; } + inline bool HasISOCompanion() { return attributes & (1 << 7); } +} __attribute__((packed)); + +struct KUSBEndpointIsochronousCompanionDescriptor : KUSBDescriptorHeader { + uint16_t reserved; + uint32_t bytesPerInterval; +} __attribute__((packed)); + +struct KUSBEndpointDescriptor : KUSBDescriptorHeader { + uint8_t address; + uint8_t attributes; + uint16_t maximumPacketSize; + uint8_t pollInterval; + + inline bool IsControl() { return (attributes & 3) == 0; } + inline bool IsIsochronous() { return (attributes & 3) == 1; } + inline bool IsBulk() { return (attributes & 3) == 2; } + inline bool IsInterrupt() { return (attributes & 3) == 3; } + inline bool IsInput() { return (address & 0x80); } + inline bool IsOutput() { return !(address & 0x80); } + inline uint8_t GetAddress() { return address & 0x0F; } + inline uint16_t GetMaximumPacketSize() { return maximumPacketSize & 0x7FF; } +} __attribute__((packed)); + +typedef void (*KUSBTransferCallback)(ptrdiff_t bytesNotTransferred /* -1 if error */, EsGeneric context); + +struct KUSBDevice : KDevice { + bool GetString(uint8_t index, char *buffer, size_t bufferBytes); + KUSBDescriptorHeader *GetCommonDescriptor(uint8_t type, uintptr_t index); + bool RunTransfer(KUSBEndpointDescriptor *endpoint, void *buffer, size_t bufferBytes, size_t *bytesNotTransferred /* if null, fails if positive */); + + // Callbacks provided by the host controller: + // NOTE These do not provide mutual exclusion; you must ensure this manually. + bool (*controlTransfer)(KUSBDevice *device, uint8_t flags, uint8_t request, uint16_t value, uint16_t index, + void *buffer, uint16_t length, int operation /* K_ACCESS_READ/WRITE */, uint16_t *transferred); + bool (*queueTransfer)(KUSBDevice *device, KUSBEndpointDescriptor *endpoint, KUSBTransferCallback callback, + void *buffer, size_t bufferBytes, EsGeneric context); + bool (*selectConfigurationAndInterface)(KUSBDevice *device); + + uint8_t *configurationDescriptors; + size_t configurationDescriptorsBytes; + uintptr_t selectedConfigurationOffset; + + KUSBDeviceDescriptor deviceDescriptor; + KUSBConfigurationDescriptor configurationDescriptor; + KUSBInterfaceDescriptor interfaceDescriptor; +}; + +void KRegisterUSBDevice(KUSBDevice *device); // Takes ownership of the device's main handle. + +// --------------------------------------------------------------------------------------------------------------- +// File systems. +// --------------------------------------------------------------------------------------------------------------- + +struct KNodeMetadata { + // Metadata stored in the node's directory entry. + EsNodeType type; + bool removingNodeFromCache, removingThisFromCache; + EsFileOffset totalSize; + EsFileOffsetDifference directoryChildren; // ES_DIRECTORY_CHILDREN_UNKNOWN if not supported by the file system. +}; + +struct KNode { + void *driverNode; + + K_PRIVATE + + volatile size_t handles; + struct FSDirectoryEntry *directoryEntry; + struct KFileSystem *fileSystem; + uint64_t id; + KWriterLock writerLock; // Acquire before the parent's. + EsError error; + volatile uint32_t flags; + MMObjectCacheItem cacheItem; +}; + +struct KFileSystem : KDevice { + KBlockDevice *block; // Gives the sector size and count. + + KNode *rootDirectory; + + // Only use this for file system metadata that isn't cached in a Node. + // This must be used consistently, i.e. if you ever read a region cached, then you must always write that region cached, and vice versa. +#define BLOCK_ACCESS_CACHED (1) + // Access the block device. Returns true on success. + // Offset and count must be sector aligned. Buffer must be DWORD aligned. + // TODO Make this return EsError. + bool Access(EsFileOffset offset, size_t count, int operation, void *buffer, uint32_t flags, KWorkGroup *dispatchGroup = nullptr); + + // Fill these fields in before registering the file system: + + void *driverData; + + char name[64]; + size_t nameBytes; + + size_t directoryEntryDataBytes; // The size of the driverData passed to FSDirectoryEntryFound and received in the load callback. + size_t nodeDataBytes; // The average bytes allocated by the driver per node (used for managing cache sizes). + + EsFileOffsetDifference rootDirectoryInitialChildren; + EsFileOffset spaceTotal, spaceUsed; + + size_t (*read) (KNode *node, void *buffer, EsFileOffset offset, EsFileOffset count); + size_t (*write) (KNode *node, const void *buffer, EsFileOffset offset, EsFileOffset count); + void (*sync) (KNode *directory, KNode *node); // TODO Error reporting? + EsError (*scan) (const char *name, size_t nameLength, KNode *directory); // Add the entry with FSDirectoryEntryFound. + EsError (*load) (KNode *directory, KNode *node, KNodeMetadata *metadata /* for if you need to update it */, + const void *entryData /* driverData passed to FSDirectoryEntryFound */); + EsFileOffset (*resize) (KNode *file, EsFileOffset newSize, EsError *error); + EsError (*create) (const char *name, size_t nameLength, EsNodeType type, KNode *parent, KNode *node, void *driverData); + EsError (*enumerate) (KNode *directory); // Add the entries with FSDirectoryEntryFound. + EsError (*remove) (KNode *directory, KNode *file); + EsError (*move) (KNode *oldDirectory, KNode *file, KNode *newDirectory, const char *newName, size_t newNameLength); + void (*close) (KNode *node); + void (*unmount) (KFileSystem *fileSystem); + + // TODO Normalizing file names, for case-insensitive filesystems. + // void * (*normalize) (const char *name, size_t nameLength, size_t *resultLength); + + // Internals. + + KMutex moveMutex; + bool isBootFileSystem, unmounting; + volatile uint64_t totalHandleCount; + uint64_t id; + + MMObjectCache cachedDirectoryEntries, // Directory entries without a loaded node. + cachedNodes; // Nodes with no handles or directory entries. +}; + +EsError FSDirectoryEntryFound(KNode *parentDirectory, KNodeMetadata *metadata /* ignored if the entry is already cached */, + const void *driverData /* if update is false and the entry is already cached, this must match the previous driverData */, + const void *name, size_t nameBytes, + bool update /* set to true if you don't want to insert an new entry if it isn't already cached; returns ES_SUCCESS or ES_ERROR_FILE_DOES_NOT_EXIST only */, + KNode **node = nullptr /* set if scanning to immediately load; call FSNodeScanAndLoadComplete afterwards */); + +// Call if you are scanning and used immediate load with FSDirectoryEntryFound. +void FSNodeScanAndLoadComplete(KNode *node, bool success); + +// Equivalent to FSDirectoryEntryFound with update set to true, +// but lets you pass an arbitrary KNode instead of a [directory, file name] pair. +void FSNodeUpdateDriverData(KNode *node, const void *newDriverData); + +// All these functions take ownership of the device's main handle. +void FSRegisterBlockDevice(KBlockDevice *blockDevice); +void FSRegisterFileSystem(KFileSystem *fileSystem); +void FSRegisterBootFileSystem(KFileSystem *fileSystem, EsUniqueIdentifier identifier); + +#define K_SIGNATURE_BLOCK_SIZE (65536) + +struct KNodeInformation { + EsError error; + KNode *node; +}; + +KNodeInformation FSNodeOpen(const char *path, size_t pathBytes, uint32_t flags, KNode *baseDirectory = nullptr); + +EsFileOffset FSNodeGetTotalSize(KNode *node); + +char *FSNodeGetName(KNode *node, size_t *bytes); // For debugging use only. + +// Do not pass memory-mapped buffers. +#define FS_FILE_ACCESS_USER_BUFFER_MAPPED (1 << 0) +ptrdiff_t FSFileReadSync(KNode *node, K_USER_BUFFER void *buffer, EsFileOffset offset, EsFileOffset bytes, uint32_t flags); +ptrdiff_t FSFileWriteSync(KNode *node, const K_USER_BUFFER void *buffer, EsFileOffset offset, EsFileOffset bytes, uint32_t flags); + +// --------------------------------------------------------------------------------------------------------------- +// Graphics. +// --------------------------------------------------------------------------------------------------------------- + +struct KGraphicsTarget : KDevice { + size_t screenWidth, screenHeight; + bool reducedColors; // Set to true if using less than 15 bit color. + + void (*updateScreen)(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY); + void (*debugPutBlock)(uintptr_t x, uintptr_t y, bool toggle); + void (*debugClearScreen)(); +}; + +// TODO Locking for these functions? +void KRegisterGraphicsTarget(KGraphicsTarget *target); +bool KGraphicsIsTargetRegistered(); + +// Shared implementation of updating the screen for targets that use 32-bit linear buffers. +void GraphicsUpdateScreen32(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY, + uint32_t width, uint32_t height, uint32_t stride, volatile uint8_t *pixel); +void GraphicsUpdateScreen24(K_USER_BUFFER const uint8_t *_source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride, + uint32_t destinationX, uint32_t destinationY, + uint32_t width, uint32_t height, uint32_t stride, volatile uint8_t *pixel); +void GraphicsDebugPutBlock32(uintptr_t x, uintptr_t y, bool toggle, + unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer); +void GraphicsDebugClearScreen32(unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer); + +// --------------------------------------------------------------------------------------------------------------- +// Networking. +// --------------------------------------------------------------------------------------------------------------- + +struct KIPAddress { + uint8_t d[4]; +}; + +struct KMACAddress { + uint8_t d[6]; +}; + +struct NetTask { + void (*callback)(NetTask *task, void *receivedData); + struct NetInterface *interface; + uint16_t index; + int16_t error; + uint8_t step; + bool completed; +}; + +struct NetAddressSetupTask : NetTask { + uint32_t dhcpTransactionID; + bool changedState; +}; + +struct NetInterface : KDevice { + KIPAddress ipAddress; + + // Set by driver before registering: + + bool (*transmit)(NetInterface *self, void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes); + + union { + KMACAddress macAddress; + uint64_t macAddress64; + }; + + // Internals: + + K_PRIVATE + + SimpleList item; + NetAddressSetupTask addressSetupTask; + + Array arpTable; + Array arpRequests; + KWriterLock arpTableLock; + + // Changing the connection status and cancelling packets requires exclusive access. + // NetTaskBegin and NetInterfaceReceive (and hence all NetTask callbacks) run with shared access. + KWriterLock connectionLock; + + bool connected, hasIP; + uint16_t ipIdentification; + KIPAddress serverIdentifier; + KIPAddress dnsServerIP; + KIPAddress routerIP; +}; + +enum NetPacketType { + NET_PACKET_ETHERNET, +}; + +void NetTransmitBufferReturn(void *data); // Once a driver is finished with a transmit buffer, it should return it here. If the driver returns false from the transmit callback, then the driver must *not* return the buffer. + +void NetTaskBegin(NetTask *task); +void NetTaskComplete(NetTask *task, EsError error); + +void KRegisterNetInterface(NetInterface *interface); +void NetInterfaceReceive(NetInterface *interface, const uint8_t *data, size_t dataBytes, NetPacketType packetType); // NOTE Currently this can be only called on one thread for each NetInterface. (This restriction will hopefully be removed soon.) +void NetInterfaceSetConnected(NetInterface *interface, bool connected); // NOTE This shouldn't be called by more than one thread. +void NetInterfaceShutdown(NetInterface *interface); // NOTE This doesn't do any disconnecting/cancelling of tasks. Currently it only sends a DHCP request to release the IP address, and is expected to be called at the final stages of system shutdown. diff --git a/kernel/networking.cpp b/kernel/networking.cpp new file mode 100644 index 0000000..91feac7 --- /dev/null +++ b/kernel/networking.cpp @@ -0,0 +1,2167 @@ +// TODO Event-based API for userland. + +// TODO Locking: In NetTCPReceive, lock on getting task so it can't be destroyed by NetConnectionDestroy. + +// TODO Limiting the size of the ARP table; LRU. +// TODO Sending ARP requests not working in VBox. + +// TODO Domain name resolve button doesn't work in the test program. +// TODO Resolved domain name cache. + +// TODO UDP and TCP: checking packets are received from the correct NetInterface, MAC and IP. +// TODO UDP and TCP: checking source port matches expected value on received packets. +// TODO UDP and TCP (and possibly others): lock in the NetTask callback when processing a received packet, +// to allow for a NetInterface to have multiple dispatcher threads. +// TODO TCP: merging ACK responses. +// TODO TCP: high performance extensions. +// TODO TCP: reducing duplication of non-reply code. + +// TODO Cancelling tasks after losing connection; retrying tasks; timeout tasks. +// TODO Retrying the NetAddressSetupTask if it completes with error. + +// TODO Handling more ICMP messages. +// TODO Sending ICMP messages on certain bad packets. +// TODO IPv6. + +// TODO Check receive buffer is treated as empty when read pointer == write pointer. + +// TODO Make most log messages LOG_VERBOSE. + +#ifndef IMPLEMENTATION + +struct EthernetHeader { + KMACAddress destinationMAC; + KMACAddress sourceMAC; + uint16_t type; +} __attribute__((packed)); + +#define ETHERNET_HEADER(ethernet, _type, _destinationMAC) \ + if (ethernet) { \ + ethernet->destinationMAC = _destinationMAC; \ + ethernet->sourceMAC = interface->macAddress; \ + ethernet->type = SwapBigEndian16(_type); \ + } + +#define ETHERNET_TYPE_IPV4 (0x0800) +#define ETHERNET_TYPE_ARP (0x0806) + +struct IPHeader { + uint8_t versionAndLength; + uint8_t serviceType; + uint16_t totalLength; + uint16_t identification; + uint16_t flagsAndFragmentOffset; + uint8_t timeToLive; + uint8_t protocol; + uint16_t headerChecksum; + KIPAddress sourceAddress; + KIPAddress destinationAddress; + + uint16_t CalculateHeaderChecksum() const { + const uint8_t *in = (const uint8_t *) this; + uint32_t sum = 0; + + for (uintptr_t i = 0; i < 20; i += 2) { + if (i == 10) continue; + sum += ((uint16_t) in[i] << 8) + (uint16_t) in[i + 1]; + } + + while (sum > 0xFFFF) { + sum = (sum >> 16) + (sum & 0xFFFF); + } + + return SwapBigEndian16(~sum); + } +} __attribute__((packed)); + +#define IP_HEADER(ip, _destinationAddress, _protocol) \ + if (ip) { \ + ip->versionAndLength = (4 << 4) | (5 << 0); /* We're using IPv4 with a 5 DWORD header. */ \ + ip->identification = ByteSwap16(++interface->ipIdentification); \ + ip->timeToLive = 64; /* Live for at most 64 seconds. */ \ + ip->protocol = _protocol; \ + ip->sourceAddress = interface->ipAddress; \ + ip->destinationAddress = _destinationAddress; \ + } + +#define IP_PROTOCOL_ICMP (1) +#define IP_PROTOCOL_TCP (6) +#define IP_PROTOCOL_UDP (17) + +struct UDPHeader { + uint16_t sourcePort; + uint16_t destinationPort; + uint16_t length; + uint16_t checksum; + + uint16_t CalculateChecksum() const { + const IPHeader *ipHeader = (const IPHeader *) this - 1; + + struct { + KIPAddress sourceAddress; + KIPAddress destinationAddress; + uint8_t zero; + uint8_t protocol; + uint16_t udpLength; + } pseudoHeader = { + .sourceAddress = ipHeader->sourceAddress, + .destinationAddress = ipHeader->destinationAddress, + .zero = 0, + .protocol = ipHeader->protocol, + .udpLength = length, + }; + + const uint8_t *in = (const uint8_t *) this; + const uint8_t *in2 = (const uint8_t *) &pseudoHeader; + const uint8_t *data = (const uint8_t *) (this + 1); + + uint32_t sum = 0; + + for (uintptr_t i = 0; i < 8; i += 2) { + if (i == 6) continue; + sum += ((uint16_t) in[i] << 8) + (uint16_t) in[i + 1]; + } + + for (uintptr_t i = 0; i < 12; i += 2) { + sum += ((uint16_t) in2[i] << 8) + (uint16_t) in2[i + 1]; + } + + uintptr_t dataBytes = ByteSwap16(length) - 8; + + for (uintptr_t i = 0; i < dataBytes; i += 2) { + if (i + 1 == dataBytes) { + sum += (uint16_t) data[i] << 8; + } else { + sum += ((uint16_t) data[i] << 8) + (uint16_t) data[i + 1]; + } + } + + while (sum > 0xFFFF) { + sum = (sum >> 16) + (sum & 0xFFFF); + } + + return (sum == 0xFFFF) ? 0xFFFF : SwapBigEndian16(~sum); + } +} __attribute__((packed)); + +#define UDP_HEADER(udp, _sourcePort, _destinationPort) \ + if (udp) { \ + udp->sourcePort = SwapBigEndian16(_sourcePort); \ + udp->destinationPort = SwapBigEndian16(_destinationPort); \ + } + +#define UDP_PORT_BASE (49152) + +struct TCPHeader { + uint16_t sourcePort; + uint16_t destinationPort; + uint32_t sequenceNumber; + uint32_t ackNumber; + uint16_t flags; + uint16_t window; + uint16_t checksum; + uint16_t urgentPointer; + + uint16_t CalculateChecksum(uint16_t length) const { + const IPHeader *ipHeader = (const IPHeader *) this - 1; + + struct { + KIPAddress sourceAddress; + KIPAddress destinationAddress; + uint8_t zero; + uint8_t protocol; + uint16_t tcpLength; + } pseudoHeader = { + .sourceAddress = ipHeader->sourceAddress, + .destinationAddress = ipHeader->destinationAddress, + .zero = 0, + .protocol = ipHeader->protocol, + .tcpLength = ByteSwap16(length), + }; + + const uint8_t *in = (const uint8_t *) this; + const uint8_t *in2 = (const uint8_t *) &pseudoHeader; + + uint32_t sum = 0; + + for (uintptr_t i = 0; i < length; i += 2) { + if (i == 16) { + // Checksum field; skip. + } else if (i + 1 == length) { + sum += (uint16_t) in[i] << 8; + } else { + sum += ((uint16_t) in[i] << 8) + (uint16_t) in[i + 1]; + } + } + + for (uintptr_t i = 0; i < 12; i += 2) { + sum += ((uint16_t) in2[i] << 8) + (uint16_t) in2[i + 1]; + } + + while (sum > 0xFFFF) { + sum = (sum >> 16) + (sum & 0xFFFF); + } + + return SwapBigEndian16(~sum); + } +} __attribute__((packed)); + +struct TCPReceivedData { + uint16_t flags; + uint16_t segmentLength; + uint32_t ackNumber; + uint32_t sequenceNumber; + + const EthernetHeader *ethernet; + const IPHeader *ip; + const TCPHeader *tcp; + const void *segment; +}; + +// NOTE Keep these in order! +// We've sent a packet asking to start a connection. We're expecting the server to ACK it with a matching request. +#define TCP_STEP_SYN_SENT (1) +// We've received a packet asking to start a connection. We've sent a matching request back. We're waiting for that to be ACK'd. +#define TCP_STEP_SYN_RECEIVED (2) +// We're in normal data communication. +#define TCP_STEP_ESTABLISHED (3) +// Closing steps: +#define TCP_STEP_FIN_WAIT_1 (4) +#define TCP_STEP_FIN_WAIT_2 (5) +#define TCP_STEP_CLOSE_WAIT (6) +#define TCP_STEP_CLOSING (7) +#define TCP_STEP_LAST_ACK (8) + +#define TCP_FIN (1 << 0) +#define TCP_SYN (1 << 1) +#define TCP_RST (1 << 2) +#define TCP_PSH (1 << 3) +#define TCP_ACK (1 << 4) + +#define TCP_PORT_BASE (49152) + +#define TCP_PREPARE_REPLY(_data1, _data1Bytes, _data2, _data2Bytes) \ + EthernetHeader *ethernetReply = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); \ + ETHERNET_HEADER(ethernetReply, ETHERNET_TYPE_IPV4, data->ethernet->sourceMAC); \ + IPHeader *ipReply = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); \ + IP_HEADER(ipReply, data->ip->sourceAddress, IP_PROTOCOL_TCP); \ + TCPHeader *tcpReply = (TCPHeader *) buffer.Write(nullptr, sizeof(TCPHeader)); \ + buffer.Write(_data1, _data1Bytes); \ + buffer.Write(_data2, _data2Bytes); \ + \ + if (buffer.error) { \ + KernelPanic("TCP_PREPARE_REPLY - Network interface buffer size too small.\n"); \ + } \ + \ + ipReply->totalLength = ByteSwap16(buffer.position - sizeof(*ethernetReply)); \ + ipReply->flagsAndFragmentOffset = SwapBigEndian16(1 << 14 /* do not fragment */); \ + ipReply->headerChecksum = ipReply->CalculateHeaderChecksum(); \ + \ + tcpReply->sourcePort = data->tcp->destinationPort; \ + tcpReply->destinationPort = data->tcp->sourcePort; + +#define TCP_PREPARE_REPLY_2(_data1, _data1Bytes, _data2, _data2Bytes, _flags, _headerDWORDs) \ + TCP_PREPARE_REPLY(_data1, _data1Bytes, _data2, _data2Bytes); \ + tcpReply->flags = SwapBigEndian16((_flags) | ((_headerDWORDs) << 12 /* header is 5 DWORDs */)); \ + tcpReply->sequenceNumber = SwapBigEndian32(task->sendNext); \ + tcpReply->ackNumber = SwapBigEndian32(task->receiveNext); \ + tcpReply->window = SwapBigEndian16(task->receiveWindow); \ + +#define TCP_MAKE_STANDARD_REPLY(_flags) \ + { \ + EsBuffer buffer = NetTransmitBufferGet(); \ + if (buffer.error) { NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); return; } \ + TCP_PREPARE_REPLY_2(nullptr, 0, nullptr, 0, _flags, 5); \ + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); \ + } + +struct DHCPHeader { + uint8_t opCode; + uint8_t hardwareAddressType; + uint8_t hardwareAddressLength; + uint8_t hops; + uint32_t transactionID; + uint16_t seconds; + uint16_t flags; + KIPAddress clientIPAddress; + KIPAddress yourIPAddress; + KIPAddress nextServerIPAddress; + KIPAddress relayAgentIPAddress; + uint8_t clientHardwareAddress[16]; + uint8_t serverHostName[64]; + uint8_t bootFileName[128]; + uint8_t optionsMagic[4]; +} __attribute__((packed)); + +#define DHCP_HEADER(dhcp, _opCode) \ + if (dhcp) { \ + dhcp->opCode = _opCode; \ + dhcp->hardwareAddressType = 1; /* Ethernet. */ \ + dhcp->hardwareAddressLength = 6; \ + dhcp->transactionID = (uint32_t) EsRandomU64(); \ + EsMemoryCopy(&dhcp->clientHardwareAddress, &interface->macAddress, sizeof(KMACAddress)); \ + dhcp->optionsMagic[0] = 99; /* Magic cookie. */ \ + dhcp->optionsMagic[1] = 130; \ + dhcp->optionsMagic[2] = 83; \ + dhcp->optionsMagic[3] = 99; \ + } + +#define DHCP_START() \ + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); \ + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_IPV4, broadcastMAC); \ + IPHeader *ip = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); \ + IP_HEADER(ip, broadcastIP, IP_PROTOCOL_UDP); \ + UDPHeader *udp = (UDPHeader *) buffer.Write(nullptr, sizeof(UDPHeader)); \ + UDP_HEADER(udp, 68, 67); \ + DHCPHeader *dhcp = (DHCPHeader *) buffer.Write(nullptr, sizeof(DHCPHeader)); \ + DHCP_HEADER(dhcp, DHCP_BOOTREQUEST); + +#define DHCP_END() \ + if (!buffer.error) { \ + ip->totalLength = ByteSwap16(buffer.position - sizeof(*ethernet)); \ + udp->length = ByteSwap16(buffer.position - sizeof(*ethernet) - sizeof(*ip)); \ + } + +#define DHCP_OPTION_MESSAGE_TYPE(x) 53, 1, (x) +#define DHCP_OPTION_REQUESTED_IP(x) 50, 4, (x).d[0], (x).d[1], (x).d[2], (x).d[3] +#define DHCP_OPTION_SERVER_IDENTIFIER(x) 54, 4, (x).d[0], (x).d[1], (x).d[2], (x).d[3] + +#define DHCPDISCOVER (1) +#define DHCPOFFER (2) +#define DHCPREQUEST (3) +#define DHCPACK (5) +#define DHCPNAK (6) +#define DHCPRELEASE (7) + +#define DHCP_BOOTREQUEST (1) +#define DHCP_BOOTREPLY (2) + +struct ARPHeader { + uint16_t hardwareAddressSpace; + uint16_t protocolAddressSpace; + uint8_t hardwareAddressLength; + uint8_t protocolAddressLength; + uint16_t opCode; +} __attribute__((packed)); + +#define ARP_ETHERNET (0x0001) +#define ARP_IPV4 (0x0800) + +#define ARP_REQUEST (1) +#define ARP_REPLY (2) + +struct ARPEntry { + KIPAddress ip; + KMACAddress mac; +}; + +struct ARPRequest { + KIPAddress ip; + NetTask *task; +}; + +struct ICMPHeader { + uint8_t type; + uint8_t code; + uint16_t checksum; + + uint16_t CalculateChecksum(size_t dataBytes) const { + const uint8_t *in = (const uint8_t *) this; + uint32_t sum = 0; + + for (uintptr_t i = 0; i < 4 + dataBytes; i += 2) { + if (i == 2) continue; + sum += ((uint16_t) in[i] << 8) + (uint16_t) in[i + 1]; + } + + while (sum > 0xFFFF) { + sum = (sum >> 16) + (sum & 0xFFFF); + } + + return SwapBigEndian16(~sum); + } +} __attribute__((packed)); + +struct DNSHeader { + uint16_t identifier; + uint16_t flags; + uint16_t questionCount; + uint16_t answerCount; + uint16_t authorityCount; + uint16_t additionalCount; +} __attribute__((packed)); + +struct Networking { + KMutex interfacesListMutex; + SimpleList interfaces; + +#define MAX_UDP_TASKS (1024) + NetTask *udpTasks[MAX_UDP_TASKS]; + KMutex udpTaskBitsetMutex; + Bitset udpTaskBitset; + +#define MAX_TCP_TASKS (1024) + uintptr_t tcpTasks[MAX_TCP_TASKS]; // If (1 << 0) set, task is in use. + uint16_t tcpTaskLRU, tcpTaskMRU; + KMutex tcpTaskListMutex; + + KMutex echoRequestTaskMutex; + NetTask *echoRequestTask; + + KMutex transmitBufferPoolMutex; + EsArena transmitBufferPool; +}; + +struct NetDomainNameResolveTask : NetTask { + const char *name; + size_t nameBytes; + EsAddress *address; + uint16_t identifier; + KEvent *event; +}; + +struct NetEchoRequestTask : NetTask { + uint8_t *data; + EsAddress *address; + KEvent *event; +}; + +struct NetTCPConnectionTask : NetTask { + uint32_t sendUnacknowledged; // Points at the end of the data the server has acknowledged receiving from us. + uint32_t sendNext; // Points at the end of data we've sent. + uint32_t sendWindow; // The maximum distance sendNext can be past sendUnacknowledged. + uint32_t receiveNext; // Points at the end of data we've acknowledged receiving from the server. + uint16_t receiveWindow; // The maximum distance the server can sent data past receiveNext. + + uint32_t initialSend, initialReceive; + uint32_t finSequence; + uint32_t sendWL1; + uint32_t sendWL2; + + KMACAddress destinationMAC; +}; + +struct NetConnection { + NetTCPConnectionTask task; + + MMSharedRegion *bufferRegion; + uint8_t *sendBuffer; + uint8_t *receiveBuffer; + size_t sendBufferBytes; + size_t receiveBufferBytes; + + uintptr_t sendReadPointer; // The end of the data that we've sent to the server (possibly unacknolwedged). + uintptr_t sendWritePointer; // The end of the data that the application has written for us to send. + uintptr_t receiveWritePointer; // The end of the data that we've received from the server with no missing segments. + uintptr_t receiveReadPointer; // The end of the data that the user has processed from the receive buffer. + + RangeSet receivedData; + + EsAddress address; + KMutex mutex; + + volatile uintptr_t handles; +}; + +void NetDomainNameResolve(NetTask *_task, void *data); +void NetEchoRequest(NetTask *_task, void *data); +void NetTCPConnection(NetTask *_task, void *data); +void NetAddressSetup(NetTask *_task, void *data); + +NetConnection *NetConnectionOpen(EsAddress *address, size_t sendBufferBytes, size_t receiveBufferBytes, uint32_t flags); +void NetConnectionClose(NetConnection *connection); +void NetConnectionNotify(NetConnection *connection, uintptr_t sendWritePointer, uintptr_t receiveReadPointer); +void NetConnectionDestroy(NetConnection *connection); + +extern Networking networking; + +#else + +Networking networking; + +const KIPAddress broadcastIP = { 255, 255, 255, 255 }; +const KMACAddress broadcastMAC = { 255, 255, 255, 255, 255, 255 }; + +void NetPrintPacket(const char *cName, const void *packet, size_t bytes) { + EsPrint("%z packet: ", cName); + + for (uintptr_t i = 0; i < bytes; i++) { + EsPrint("%X ", ((uint8_t *) packet)[i]); + } + + EsPrint("\n"); +} + +EsBuffer NetTransmitBufferGet() { + KMutexAcquire(&networking.transmitBufferPoolMutex); + EsBuffer buffer = {}; + buffer.out = (uint8_t *) EsArenaAllocate(&networking.transmitBufferPool, false); + buffer.bytes = networking.transmitBufferPool.slotSize; + + if (!buffer.out) { + buffer.error = true, buffer.bytes = 0; + KernelLog(LOG_ERROR, "Networking", "out of memory", "Could not allocate a transmit buffer.\n"); + } + + KMutexRelease(&networking.transmitBufferPoolMutex); + return buffer; +} + +void NetTransmitBufferReturn(void *data) { + KMutexAcquire(&networking.transmitBufferPoolMutex); + EsArenaFree(&networking.transmitBufferPool, data); + KMutexRelease(&networking.transmitBufferPoolMutex); +} + +bool NetTransmit(NetInterface *interface, EsBuffer *buffer, NetPacketType packetType) { + if (buffer->error) { + KernelPanic("NetTransmit - Trying to transmit a write buffer with errors.\n"); + } + + if (packetType == NET_PACKET_ETHERNET) { + if (buffer->position < 64) { + buffer->Write(nullptr, 64 - buffer->position); + } + + EthernetHeader *ethernet = (EthernetHeader *) buffer->out; + + if (ethernet->type == SwapBigEndian16(ETHERNET_TYPE_IPV4)) { + IPHeader *ip = (IPHeader *) (ethernet + 1); + + if (ip->protocol == IP_PROTOCOL_UDP) { + UDPHeader *udp = (UDPHeader *) (ip + 1); + udp->checksum = udp->CalculateChecksum(); + } else if (ip->protocol == IP_PROTOCOL_TCP) { + TCPHeader *tcp = (TCPHeader *) (ip + 1); + tcp->checksum = tcp->CalculateChecksum(ByteSwap16(ip->totalLength) - sizeof(*ip)); + } else if (ip->protocol == IP_PROTOCOL_ICMP) { + ICMPHeader *icmp = (ICMPHeader *) (ip + 1); + icmp->checksum = icmp->CalculateChecksum(ByteSwap16(ip->totalLength) - sizeof(*ip)); + } + + ip->headerChecksum = ip->CalculateHeaderChecksum(); + } + } + + uintptr_t address = (uintptr_t) buffer->out; + uintptr_t physical = (address & (K_PAGE_SIZE - 1)) + MMArchTranslateAddress(kernelMMSpace, address); + + if (!interface->transmit(interface, buffer->out, physical, buffer->position)) { + NetTransmitBufferReturn(buffer->out); + return false; + } + + return true; +} + +bool NetARPLookup(NetTask *task, KIPAddress targetIP, KMACAddress *targetMAC) { + NetInterface *interface = task->interface; + + KWriterLockTake(&interface->arpTableLock, K_LOCK_SHARED); + + for (uintptr_t i = 0; i < interface->arpTable.Length(); i++) { + if (0 == EsMemoryCompare(&interface->arpTable[i].ip, &targetIP, sizeof(KIPAddress))) { + *targetMAC = interface->arpTable[i].mac; + KWriterLockReturn(&interface->arpTableLock, K_LOCK_SHARED); + return true; + } + } + + KWriterLockReturn(&interface->arpTableLock, K_LOCK_SHARED); + + KernelLog(LOG_INFO, "Networking", "send ARP", "Sending ARP to find MAC address of IP %d.%d.%d.%d.\n", + targetIP.d[0], targetIP.d[1], targetIP.d[2], targetIP.d[3]); + + // TODO Prevent sending multiple requests for a given MAC address at the same time. + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return false; + } + + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_ARP, broadcastMAC); + ARPHeader *arp = (ARPHeader *) buffer.Write(nullptr, sizeof(ARPHeader)); + + if (arp) { + arp->hardwareAddressSpace = SwapBigEndian16(ARP_ETHERNET); + arp->protocolAddressSpace = SwapBigEndian16(ARP_IPV4); + arp->hardwareAddressLength = 6; + arp->protocolAddressLength = 4; + arp->opCode = SwapBigEndian16(ARP_REQUEST); + } + + buffer.Write(&interface->macAddress, sizeof(KMACAddress)); + buffer.Write(&interface->ipAddress, sizeof(KIPAddress)); + buffer.Write(nullptr, sizeof(KMACAddress)); + buffer.Write(&targetIP, sizeof(KIPAddress)); + + if (buffer.error) { + KernelPanic("NetARPLookup - Network interface buffer size too small.\n"); + } + + KWriterLockTake(&interface->arpTableLock, K_LOCK_EXCLUSIVE); + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } else { + ARPRequest request = { targetIP, task }; + interface->arpRequests.Add(request); + } + + KWriterLockReturn(&interface->arpTableLock, K_LOCK_EXCLUSIVE); + + return false; +} + +void NetARPReceive(NetInterface *interface, EsBuffer *buffer) { + const ARPHeader *arp = (const ARPHeader *) buffer->Read(sizeof(ARPHeader)); + + if (!arp) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing ARP header.\n"); + return; + } + + if (SwapBigEndian16(arp->hardwareAddressSpace) != ARP_ETHERNET + || SwapBigEndian16(arp->protocolAddressSpace) != ARP_IPV4) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "ARP packet has unrecognised address space(s).\n"); + return; + } + + const KMACAddress *senderMAC = (const KMACAddress *) buffer->Read(sizeof(KMACAddress)); + const KIPAddress *senderIP = (const KIPAddress *) buffer->Read(sizeof(KIPAddress)); + const KMACAddress *targetMAC = (const KMACAddress *) buffer->Read(sizeof(KMACAddress)); + const KIPAddress *targetIP = (const KIPAddress *) buffer->Read(sizeof(KIPAddress)); + + if (!senderMAC || !senderIP || !targetMAC || !targetIP) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "ARP packet too short.\n"); + return; + } + + KernelLog(LOG_INFO, "Networking", "received ARP packet", + "Received ARP packet. Sender: %d.%d.%d.%d (%X:%X:%X:%X:%X:%X). Destination: %d.%d.%d.%d (%X:%X:%X:%X:%X:%X). Op code %d.\n", + senderIP->d[0], senderIP->d[1], senderIP->d[2], senderIP->d[3], + senderMAC->d[0], senderMAC->d[1], senderMAC->d[2], senderMAC->d[3], senderMAC->d[4], senderMAC->d[5], + targetIP->d[0], targetIP->d[1], targetIP->d[2], targetIP->d[3], + targetMAC->d[0], targetMAC->d[1], targetMAC->d[2], targetMAC->d[3], targetMAC->d[4], targetMAC->d[5], + SwapBigEndian16(arp->opCode)); + + bool merged = false; + + Array completedRequests = {}; + + KWriterLockTake(&interface->arpTableLock, K_LOCK_EXCLUSIVE); + + for (uintptr_t i = 0; i < interface->arpTable.Length(); i++) { + if (0 == EsMemoryCompare(&interface->arpTable[i].ip, senderIP, sizeof(KIPAddress))) { + interface->arpTable[i].mac = *senderMAC; + merged = true; + } + } + + if (interface->hasIP && 0 == EsMemoryCompare(targetIP, &interface->ipAddress, sizeof(KIPAddress))) { + if (!merged) { + ARPEntry entry = {}; + entry.ip = *senderIP; + entry.mac = *senderMAC; + + if (!interface->arpTable.Add(entry)) { + KernelLog(LOG_ERROR, "Networking", "allocation error", "Could not add entry to ARP table.\n"); + } + + for (uintptr_t i = 0; i < interface->arpRequests.Length(); i++) { + if (0 == EsMemoryCompare(&interface->arpRequests[i].ip, &entry.ip, sizeof(KIPAddress))) { + completedRequests.Add(interface->arpRequests[i].task); + interface->arpRequests.DeleteSwap(i); + i--; + } + } + } + + if (SwapBigEndian16(arp->opCode) == ARP_REQUEST) { + // Reply with our MAC address. + EsBuffer buffer = NetTransmitBufferGet(); + + if (!buffer.error) { + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_ARP, broadcastMAC); + ARPHeader *arpReply = (ARPHeader *) buffer.Write(arp, sizeof(ARPHeader)); + buffer.Write(&interface->macAddress, sizeof(KMACAddress)); + buffer.Write(targetIP, sizeof(KIPAddress)); + buffer.Write(senderMAC, sizeof(KMACAddress)); + buffer.Write(senderIP, sizeof(KIPAddress)); + + if (buffer.error) { + KernelPanic("NetARPReceive - Network interface buffer size too small.\n"); + } else { + arpReply->opCode = ARP_REPLY; + NetTransmit(interface, &buffer, NET_PACKET_ETHERNET); // Don't care about errors. + } + } + } else if (SwapBigEndian16(arp->opCode) == ARP_REPLY) { + // Don't need to do anything. + } else { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Unrecognised ARP op code %d.\n", SwapBigEndian16(arp->opCode)); + } + } else { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "ARP packet not destined for us.\n"); + } + + KWriterLockReturn(&interface->arpTableLock, K_LOCK_EXCLUSIVE); + + for (uintptr_t i = 0; i < completedRequests.Length(); i++) { + completedRequests[i]->callback(completedRequests[i], nullptr); + } + + completedRequests.Free(); +} + +void NetICMPReceive(NetInterface *interface, EsBuffer *buffer, const IPHeader *ip, const EthernetHeader *ethernet) { + const ICMPHeader *icmp = (const ICMPHeader *) buffer->Read(sizeof(ICMPHeader)); + + if (!icmp) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing ICMP header.\n"); + return; + } + + if (icmp->checksum != icmp->CalculateChecksum(buffer->bytes - buffer->position)) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Incorrect ICMP checksum.\n"); + return; + } + + if (icmp->type == 8 /* echo request */) { + // Send echo reply. + // TODO Test this. + + EsBuffer bufferReply = NetTransmitBufferGet(); + if (bufferReply.error) return; + + EthernetHeader *ethernetReply = (EthernetHeader *) bufferReply.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernetReply, ETHERNET_TYPE_IPV4, ethernet->sourceMAC); + IPHeader *ipReply = (IPHeader *) bufferReply.Write(nullptr, sizeof(IPHeader)); + IP_HEADER(ipReply, ip->sourceAddress, IP_PROTOCOL_ICMP); + ICMPHeader *icmpReply = (ICMPHeader *) bufferReply.Write(icmp, sizeof(ICMPHeader)); + bufferReply.Write(icmp + 1, buffer->bytes - buffer->position); + + if (bufferReply.error) { + KernelPanic("NetICMPReceive - Network interface buffer size too small.\n"); + } else { + icmpReply->type = 0; // Echo reply. + ipReply->totalLength = ByteSwap16(bufferReply.position - sizeof(*ethernetReply)); + NetTransmit(interface, &bufferReply, NET_PACKET_ETHERNET); // Don't care about errors. + } + } else if (icmp->type == 0 /* echo reply */) { + if (networking.echoRequestTask) { + networking.echoRequestTask->callback(networking.echoRequestTask, buffer); + } else { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Unexpected ICMP echo reply.\n"); + } + } +} + +void NetTCPReceive(NetInterface *interface, EsBuffer *buffer, const IPHeader *ip, const EthernetHeader *ethernet) { + // Validate the TCP header. + + size_t tcpPosition = buffer->position; + const TCPHeader *tcp = (const TCPHeader *) buffer->Read(sizeof(TCPHeader)); + + if (!tcp) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing TCP header.\n"); + return; + } + + uint16_t flags = SwapBigEndian16(tcp->flags); + uint32_t headerDWORDs = flags >> 12; + + if (headerDWORDs < 5) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "TCP header DWORDs was less than 5.\n"); + return; + } + + if (tcp->CalculateChecksum(buffer->bytes - tcpPosition) != tcp->checksum) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "TCP header has incorrect checksum.\n"); + return; + } + + if (!buffer->Read((headerDWORDs - 5) * sizeof(uint32_t))) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "TCP header is shorter than expected.\n"); + return; + } + + uint32_t segmentLength = buffer->bytes - buffer->position; + + // If a task exists at the destination port, send it the segment. + // TODO Locking on getting the task. + + NetTCPConnectionTask *task = nullptr; + uint16_t destinationPort = SwapBigEndian16(tcp->destinationPort); + + if (destinationPort >= TCP_PORT_BASE + && destinationPort < TCP_PORT_BASE + MAX_TCP_TASKS + && (networking.tcpTasks[destinationPort - TCP_PORT_BASE] & 1)) { + task = (NetTCPConnectionTask *) (networking.tcpTasks[destinationPort - TCP_PORT_BASE] & ~1); + OpenHandleToObject(task, KERNEL_OBJECT_CONNECTION); + } + + TCPReceivedData _data = {}; + _data.ethernet = ethernet; + _data.ip = ip; + _data.tcp = tcp; + _data.flags = flags; + _data.segmentLength = segmentLength; + _data.segment = buffer->Read(segmentLength); + _data.sequenceNumber = SwapBigEndian32(tcp->sequenceNumber); + _data.ackNumber = SwapBigEndian32(tcp->ackNumber); + TCPReceivedData *data = &_data; + + if (task) { + task->callback(task, data); + CloseHandleToObject(task, KERNEL_OBJECT_CONNECTION); + return; + } + + // The destination port is closed. + // If the segment does not have the RST flag, then we should send a RST segment in response. + + if (~flags & (1 << 2 /* RST */)) { + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + return; // Ignore. + } + + TCP_PREPARE_REPLY(nullptr, 0, nullptr, 0); + + tcpReply->sourcePort = tcp->destinationPort; + tcpReply->destinationPort = tcp->sourcePort; + tcpReply->flags = SwapBigEndian16(TCP_RST | ~(flags & TCP_ACK) | (5 << 12 /* header is 5 DWORDs */)); + tcpReply->sequenceNumber = (flags & TCP_ACK) ? tcp->ackNumber : 0; + tcpReply->ackNumber = (flags & TCP_ACK) ? 0 : SwapBigEndian32(SwapBigEndian32(tcp->sequenceNumber) + segmentLength); + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } + } +} + +void NetUDPReceive(NetInterface *interface, EsBuffer *buffer) { + const UDPHeader *udp = (const UDPHeader *) buffer->Read(sizeof(UDPHeader)); + + if (!udp) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing UDP header.\n"); + return; + } + + if (!interface->hasIP && udp->destinationPort != SwapBigEndian16(68)) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Only expecting DHCP packets until IP address accepted.\n"); + return; + } + + if (SwapBigEndian16(udp->length) > buffer->bytes - buffer->position + sizeof(UDPHeader)) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "UDP length was longer than remaining buffer size.\n"); + return; + } + + if (udp->CalculateChecksum() != udp->checksum) { // NOTE Don't compute the checksum until the length field has been validated! + KernelLog(LOG_ERROR, "Networking", "bad packet", "Incorrect checksum in UDP header.\n"); + return; + } + + uint16_t destinationPort = SwapBigEndian16(udp->destinationPort); + + if (destinationPort == 68 /* DHCP */) { + interface->addressSetupTask.callback(&interface->addressSetupTask, buffer); + } else if (destinationPort >= UDP_PORT_BASE + && destinationPort < UDP_PORT_BASE + MAX_UDP_TASKS + && !networking.udpTaskBitset.Read(destinationPort - UDP_PORT_BASE)) { + NetTask *task = networking.udpTasks[destinationPort - UDP_PORT_BASE]; + task->callback(task, buffer); + } else { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Unknown destination UDP port %d.\n", udp->destinationPort); + } +} + +void NetIPReceive(NetInterface *interface, EsBuffer *buffer, const EthernetHeader *ethernet) { + const IPHeader *ip = (const IPHeader *) buffer->Read(sizeof(IPHeader)); + + if (!ip) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing IP header.\n"); + return; + } + + if ((ip->versionAndLength >> 4) != 4) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Unsupported IP version %d.\n", ip->versionAndLength >> 4); + return; + } + + size_t headerLength = (ip->versionAndLength & 0xF) * 4; + + if (headerLength < 20) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "IP header too short.\n"); + return; + } + + uint16_t totalLength = SwapBigEndian16(ip->totalLength); + + if (totalLength > buffer->bytes - buffer->position + sizeof(IPHeader)) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "IP header total length was longer than remaining buffer size.\n"); + return; + } + + buffer->bytes = buffer->position + totalLength - sizeof(IPHeader); + + if (totalLength < headerLength) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "IP header total length was less than header length.\n"); + return; + } + + if (interface->hasIP && EsMemoryCompare(&interface->ipAddress, &ip->destinationAddress, 4) + && EsMemoryCompare(&broadcastIP, &ip->destinationAddress, 4)) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Destination IP address mismatch (%d.%d.%d.%d).\n", + ip->destinationAddress.d[0], ip->destinationAddress.d[1], ip->destinationAddress.d[2], ip->destinationAddress.d[3]); + return; + } + + if (ip->CalculateHeaderChecksum() != ip->headerChecksum) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Incorrect checksum in IP header.\n"); + return; + } + + buffer->Read(headerLength - 20); + + if (!interface->hasIP && ip->protocol != IP_PROTOCOL_UDP) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Only expecting DHCP packets over UDP until IP address accepted.\n"); + return; + } + + if (ip->protocol == IP_PROTOCOL_ICMP) { + NetICMPReceive(interface, buffer, ip, ethernet); + } else if (ip->protocol == IP_PROTOCOL_TCP) { + NetTCPReceive(interface, buffer, ip, ethernet); + } else if (ip->protocol == IP_PROTOCOL_UDP) { + NetUDPReceive(interface, buffer); + } else { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Unrecognised IP protocol type %d.\n", ip->protocol); + } +} + +void NetEthernetReceive(NetInterface *interface, EsBuffer *buffer) { + const EthernetHeader *ethernet = (const EthernetHeader *) buffer->Read(sizeof(EthernetHeader)); + + if (!ethernet) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing ethernet header.\n"); + return; + } + + if (EsMemoryCompare(ðernet->destinationMAC, &interface->macAddress, sizeof(KMACAddress)) + && EsMemoryCompare(ðernet->destinationMAC, &broadcastMAC, sizeof(KMACAddress))) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Destination MAC address mismatch.\n"); + return; + } + + if (SwapBigEndian16(ethernet->type) == ETHERNET_TYPE_IPV4) { + NetIPReceive(interface, buffer, ethernet); + } else if (SwapBigEndian16(ethernet->type) == ETHERNET_TYPE_ARP) { + NetARPReceive(interface, buffer); + } else { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Unrecognised ethernet packet type %x.\n", SwapBigEndian16(ethernet->type)); + } +} + +void NetInterfaceReceive(NetInterface *interface, const uint8_t *data, size_t dataBytes, NetPacketType packetType) { + KWriterLockTake(&interface->connectionLock, K_LOCK_SHARED); + + EsBuffer buffer = { .in = data, .bytes = dataBytes }; + + if (!interface->connected) { + KernelLog(LOG_ERROR, "Networking", "packet while disconnected", "Interface %x is disconnected.\n", interface); + } else if (packetType == NET_PACKET_ETHERNET) { + NetEthernetReceive(interface, &buffer); + } else { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Unsupported packet type %d.\n", packetType); + } + + KWriterLockReturn(&interface->connectionLock, K_LOCK_SHARED); + + if (interface->addressSetupTask.changedState) { + interface->addressSetupTask.changedState = false; + KWriterLockTake(&interface->connectionLock, K_LOCK_EXCLUSIVE); + + interface->hasIP = interface->addressSetupTask.error == ES_SUCCESS; + + if (!interface->hasIP) { + // TODO Report connection lost and cancel in progress tasks. + // TODO Retry the addressSetupTask. + KernelLog(LOG_INFO, "Networking", "interface disconnected", "NetInterfaceReceive - Interface %x has lost IP address.\n", interface); + } + + KWriterLockReturn(&interface->connectionLock, K_LOCK_EXCLUSIVE); + } +} + +void NetInterfaceShutdown(NetInterface *interface) { + if (!interface->hasIP) { + return; + } + + // Release our IP address. + + EsBuffer buffer = NetTransmitBufferGet(); + if (buffer.error) return; + DHCP_START(); + + uint8_t dhcpOptions[] = { + DHCP_OPTION_MESSAGE_TYPE(DHCPRELEASE), + DHCP_OPTION_SERVER_IDENTIFIER(interface->serverIdentifier), + 255, // End of options. + }; + + buffer.Write(dhcpOptions, sizeof(dhcpOptions)); + DHCP_END(); + + if (buffer.error) { + KernelPanic("NetInterface::ReceiveDHCPReply - Network interface buffer size too small.\n"); + } + + NetTransmit(interface, &buffer, NET_PACKET_ETHERNET); // Don't care about errors. +} + +void NetInterfaceSetConnected(NetInterface *interface, bool connected) { + if (interface->connected == connected) { + return; + } + + KWriterLockTake(&interface->connectionLock, K_LOCK_EXCLUSIVE); + + interface->connected = connected; + + if (!connected) { + // TODO Report connection lost and cancel in progress tasks. + KernelLog(LOG_INFO, "Networking", "interface disconnected", "NetInterface::SetConnected - Interface %x has disconnected.\n", interface); + interface->hasIP = false; + interface->arpTable.SetLength(0); + interface->arpRequests.SetLength(0); + } else { + KernelLog(LOG_INFO, "Networking", "interface connected", "NetInterface::SetConnected - Interface %x has connected. Requesting an IP address...\n", interface); + } + + KWriterLockReturn(&interface->connectionLock, K_LOCK_EXCLUSIVE); + + if (connected) { + NetTaskBegin(&interface->addressSetupTask); + } +} + +void NetDomainNameResolve(NetTask *_task, void *_buffer) { + EsBuffer *buffer = (EsBuffer *) _buffer; + NetDomainNameResolveTask *task = (NetDomainNameResolveTask *) _task; + NetInterface *interface = task->interface; + + if (task->completed) { + if (task->index != 0xFFFF) { + KMutexAcquire(&networking.udpTaskBitsetMutex); + networking.udpTaskBitset.Put(task->index); + KMutexRelease(&networking.udpTaskBitsetMutex); + } + + if (task->event) { + // This must be the last thing we do, otherwise the NetTask might be freed. + KEventSet(task->event); + } + } else if (task->step == 0) { + KMACAddress dnsServerMAC; + + if (!NetARPLookup(task, interface->dnsServerIP, &dnsServerMAC)) { + return; + } + + KMutexAcquire(&networking.udpTaskBitsetMutex); + ptrdiff_t taskIndex = networking.udpTaskBitset.Get(); + + if (taskIndex == -1) { + KMutexRelease(&networking.udpTaskBitsetMutex); + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + + networking.udpTasks[taskIndex] = task; + task->index = taskIndex; + KMutexRelease(&networking.udpTaskBitsetMutex); + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_IPV4, dnsServerMAC); + IPHeader *ip = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); + IP_HEADER(ip, interface->dnsServerIP, IP_PROTOCOL_UDP); + UDPHeader *udp = (UDPHeader *) buffer.Write(nullptr, sizeof(UDPHeader)); + UDP_HEADER(udp, taskIndex + UDP_PORT_BASE, 53 /* DNS server */); + + DNSHeader *dns = (DNSHeader *) buffer.Write(nullptr, sizeof(DNSHeader)); + dns->identifier = task->identifier = EsRandomU64(); + dns->flags = SwapBigEndian16(1 << 8 /* recursion desired */); + dns->questionCount = SwapBigEndian16(1); + + for (uintptr_t i = 0, j = 0; i <= task->nameBytes; i++) { + if (i == task->nameBytes || task->name[i] == '.') { + uint8_t bytes = i - j; + buffer.Write(&bytes, 1); + buffer.Write(task->name + j, bytes); + + j = i + 1; + } + } + + { + uint8_t zero = 0; + buffer.Write(&zero, 1); + + uint16_t queryType = SwapBigEndian16(1 /* A - IPv4 address */); + buffer.Write(&queryType, 2); + + uint16_t queryClass = SwapBigEndian16(1 /* IN - the internet */); + buffer.Write(&queryClass, 2); + } + + if (buffer.error) { + KernelPanic("NetInterface::DomainNameResolve - Network interface buffer size too small.\n"); + } + + ip->totalLength = ByteSwap16(buffer.position - sizeof(*ethernet)); + udp->length = ByteSwap16(buffer.position - sizeof(*ethernet) - sizeof(*ip)); + + task->step++; + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + } else if (task->step == 1) { + const DNSHeader *header = (const DNSHeader *) buffer->Read(sizeof(DNSHeader)); + + if (!header) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing DNS header.\n"); + return; + } + + if (header->identifier != task->identifier) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Received DNS packet with wrong identifier.\n"); + return; + } + + uint16_t flags = SwapBigEndian16(header->flags); + + if (~flags & (1 << 15)) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Received DNS request (expecting reponse).\n"); + return; + } + + EsError error = ES_ERROR_UNKNOWN; + + if ((flags & 15) == 3) { + error = ES_ERROR_NO_ADDRESS_FOR_DOMAIN_NAME; + } else if ((flags & 15) == 0) { + error = ES_SUCCESS; + } + + for (uintptr_t i = 0; i < SwapBigEndian16(header->questionCount); i++) { + while (true) { + const uint8_t *length = (const uint8_t *) buffer->Read(1); + if (!length) break; + + if ((*length & 0xC0) == 0xC0) { + buffer->Read(1); + break; + } else if (*length == 0) { + break; + } + + buffer->Read(*length); + } + + buffer->Read(4); + } + + bool foundAddress = false; + + for (uintptr_t i = 0; i < SwapBigEndian16(header->answerCount); i++) { + while (true) { + const uint8_t *length = (const uint8_t *) buffer->Read(1); + if (!length) break; + + if ((*length & 0xC0) == 0xC0) { + buffer->Read(1); + break; + } else if (*length == 0) { + break; + } + + buffer->Read(*length); + } + + const uint16_t *type = (const uint16_t *) buffer->Read(2); + const uint16_t *classType = (const uint16_t *) buffer->Read(2); + const uint32_t *timeToLive = (const uint32_t *) buffer->Read(4); + const uint16_t *dataLength = (const uint16_t *) buffer->Read(2); + + if (!type || !classType || !timeToLive || !dataLength) { + break; + } + + const void *data = (const void *) buffer->Read(SwapBigEndian16(*dataLength)); + + if (!data) { + break; + } + + if (SwapBigEndian16(*type) == 1 /* A - IPv4 address */ + && SwapBigEndian16(*classType) == 1 /* IN - the internet */) { + if (SwapBigEndian16(*dataLength) != 4) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "IPv4 address was not 4 bytes.\n"); + return; + } + + EsMemoryCopy(&task->address->ipv4, data, 4); + foundAddress = true; + break; + } + } + + if (buffer->error) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing data after DNS header.\n"); + return; + } + + if (!foundAddress) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Could not find IP address in DNS packet.\n"); + error = ES_ERROR_UNKNOWN; + } + + NetTaskComplete(task, error); + } else { + KernelPanic("NetDomainNameResolve - Invalid step.\n"); + } +} + +void NetEchoRequest(NetTask *_task, void *_buffer) { + EsBuffer *buffer = (EsBuffer *) _buffer; + NetEchoRequestTask *task = (NetEchoRequestTask *) _task; + NetInterface *interface = task->interface; + + if (task->completed) { + KMutexAcquire(&networking.echoRequestTaskMutex); + + if (networking.echoRequestTask == task) { + networking.echoRequestTask = nullptr; + } + + KMutexRelease(&networking.echoRequestTaskMutex); + + if (task->event) { + // This must be the last thing we do, otherwise the NetTask might be freed. + KEventSet(task->event); + } + } else if (task->step == 0) { + KMACAddress routerMAC; + + if (!NetARPLookup(task, interface->routerIP, &routerMAC)) { + return; + } + + KMutexAcquire(&networking.echoRequestTaskMutex); + + if (networking.echoRequestTask) { + KMutexRelease(&networking.echoRequestTaskMutex); + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + + networking.echoRequestTask = task; + KMutexRelease(&networking.echoRequestTaskMutex); + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_IPV4, routerMAC); + IPHeader *ip = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); + KIPAddress destinationIP; + EsMemoryCopy(&destinationIP, &task->address->ipv4, sizeof(KIPAddress)); + IP_HEADER(ip, destinationIP, IP_PROTOCOL_ICMP); + ICMPHeader *icmp = (ICMPHeader *) buffer.Write(nullptr, sizeof(ICMPHeader)); + buffer.Write(task->data, ES_ECHO_REQUEST_MAX_LENGTH); + + if (buffer.error) { + KernelPanic("NetInterface::DomainNameResolve - Network interface buffer size too small.\n"); + } + + icmp->type = 8; // Echo request. + ip->totalLength = ByteSwap16(buffer.position - sizeof(*ethernet)); + task->step++; + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + } else if (task->step == 1) { + size_t dataBytes = buffer->bytes - buffer->position; + const uint8_t *data = (const uint8_t *) buffer->Read(dataBytes); + + if (dataBytes > ES_ECHO_REQUEST_MAX_LENGTH) { + dataBytes = ES_ECHO_REQUEST_MAX_LENGTH; + } + + EsMemoryZero(task->data, ES_ECHO_REQUEST_MAX_LENGTH); + EsMemoryCopy(task->data, data, dataBytes); + + NetTaskComplete(task, ES_SUCCESS); + } else { + KernelPanic("NetEchoRequest - Invalid step.\n"); + } +} + +bool NetTCPIsBetween(uint32_t low, uint32_t value, uint32_t high) { + if (low <= high) { + return low <= value && value <= high; + } else { + return high <= value && value <= low; + } +} + +bool NetTCPIsLessThan(uint32_t left, uint32_t right) { + return left - right > 0x80000000; +} + +bool NetTCPIsLessThanOrEqual(uint32_t left, uint32_t right) { + return left - right - 1 > 0x80000000; +} + +void NetTCPFreeTaskIndex(uint16_t index, bool initialFree = false) { + if (index != 0xFFFF) { + // Free the port index. + + KMutexAcquire(&networking.tcpTaskListMutex); + + if ((~networking.tcpTasks[index] & 1) && !initialFree) { + KernelPanic("NetTCPFreeTaskIndex - TCP task list double-free.\n"); + } else if (networking.tcpTaskLRU == 0xFFFF && networking.tcpTaskMRU == 0xFFFF) { + networking.tcpTaskLRU = index; + networking.tcpTaskMRU = index; + networking.tcpTasks[index] = 0xFFFF << 1; + } else if (networking.tcpTaskLRU == 0xFFFF || networking.tcpTaskMRU == 0xFFFF + || networking.tcpTasks[networking.tcpTaskMRU] != (0xFFFF << 1)) { + KernelPanic("NetTCPFreeTaskIndex - Broken TCP task list.\n"); + } else { + networking.tcpTasks[networking.tcpTaskMRU] = index << 1; + networking.tcpTaskMRU = index; + networking.tcpTasks[index] = 0xFFFF << 1; + } + + KMutexRelease(&networking.tcpTaskListMutex); + } +} + +bool NetTCPAllocateTaskIndex(NetTask *task) { + KMutexAcquire(&networking.tcpTaskListMutex); + uint16_t taskIndex = networking.tcpTaskLRU; + + if (taskIndex == 0xFFFF) { + KMutexRelease(&networking.tcpTaskListMutex); + return false; + } + + networking.tcpTaskLRU = networking.tcpTasks[taskIndex] >> 1; + if (networking.tcpTaskLRU == 0xFFFF) networking.tcpTaskMRU = 0xFFFF; + networking.tcpTasks[taskIndex] = (uintptr_t) task | 1; + task->index = taskIndex; + KMutexRelease(&networking.tcpTaskListMutex); + return true; +} + +bool NetConnectionTransmitData(NetConnection *connection) { + // TODO Send segments as a NetTask, so that they can be retransmitted. + + NetTCPConnectionTask *task = &connection->task; + NetInterface *interface = task->interface; + KWriterLockAssertShared(&interface->connectionLock); + + bool sent = false; + + while (connection->sendReadPointer != connection->sendWritePointer) { + uintptr_t bytesAvailable = connection->sendWritePointer - connection->sendReadPointer; + + if (connection->sendReadPointer > connection->sendWritePointer) { + bytesAvailable += connection->sendBufferBytes; + } + + uintptr_t maximumBytesPerSegment = 1000; // TODO Parse the maximum segment size option. + uintptr_t bytesToSendInSegment = MinimumInteger(maximumBytesPerSegment, bytesAvailable); + + const void *data1 = connection->sendBuffer + connection->sendReadPointer; + const void *data2 = connection->sendBuffer; + size_t dataBytes1 = bytesToSendInSegment; + size_t dataBytes2 = 0; + + if (connection->sendReadPointer + bytesToSendInSegment > connection->sendBufferBytes) { + dataBytes1 = connection->sendBufferBytes - connection->sendReadPointer; + dataBytes2 = bytesToSendInSegment - dataBytes1; + } + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return true; + } + + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_IPV4, task->destinationMAC); + IPHeader *ip = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); + IP_HEADER(ip, *(KIPAddress *) &connection->address.ipv4, IP_PROTOCOL_TCP); + TCPHeader *tcp = (TCPHeader *) buffer.Write(nullptr, sizeof(TCPHeader)); + buffer.Write(data1, dataBytes1); + buffer.Write(data2, dataBytes2); + + if (buffer.error) { + KernelPanic("NetConnectionTransmitData - Network interface buffer size too small.\n"); + } + + ip->totalLength = ByteSwap16(buffer.position - sizeof(*ethernet)); + ip->flagsAndFragmentOffset = SwapBigEndian16(1 << 14 /* do not fragment */); + ip->headerChecksum = ip->CalculateHeaderChecksum(); + + tcp->sourcePort = SwapBigEndian16(task->index + TCP_PORT_BASE); + tcp->destinationPort = SwapBigEndian16(connection->address.port); + tcp->flags = SwapBigEndian16(TCP_ACK | (5 << 12 /* header is 5 DWORDs */)); + tcp->sequenceNumber = SwapBigEndian32(task->sendNext); + tcp->ackNumber = SwapBigEndian32(task->receiveNext); + tcp->window = SwapBigEndian16(task->receiveWindow); + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return true; + } + + sent = true; + connection->sendReadPointer = (connection->sendReadPointer + bytesToSendInSegment) % connection->sendBufferBytes; + task->sendNext += bytesToSendInSegment; + } + + return sent; +} + +bool NetConnectionUpdateReceiveWindow(NetConnection *connection) { + NetTCPConnectionTask *task = &connection->task; + uint16_t oldReceiveWindow = task->receiveWindow; + + if (connection->receiveReadPointer == connection->receiveWritePointer) { + task->receiveWindow = MinimumInteger(0xF000, connection->receiveBufferBytes - 1); + } else if (connection->receiveReadPointer < connection->receiveWritePointer) { + task->receiveWindow = MinimumInteger(0xF000, connection->receiveReadPointer - connection->receiveWritePointer + connection->receiveBufferBytes); + } else { + task->receiveWindow = MinimumInteger(0xF000, connection->receiveReadPointer - connection->receiveWritePointer); + } + + EsPrint("ORW: %d; RW: %d; RRP: %d; RWP: %d; RBB: %d\n", oldReceiveWindow, task->receiveWindow, + connection->receiveReadPointer, connection->receiveWritePointer, connection->receiveBufferBytes); + + return oldReceiveWindow != task->receiveWindow; +} + +void NetConnectionNotify(NetConnection *connection, uintptr_t sendWritePointer, uintptr_t receiveReadPointer) { + NetTCPConnectionTask *task = &connection->task; + NetInterface *interface = task->interface; + KWriterLockTake(&interface->connectionLock, K_LOCK_SHARED); + KMutexAcquire(&connection->mutex); + + connection->sendWritePointer = sendWritePointer % connection->sendBufferBytes; + connection->receiveReadPointer = receiveReadPointer % connection->receiveBufferBytes; + + bool receiveWindowModified = NetConnectionUpdateReceiveWindow(connection); + + if (task->step == TCP_STEP_ESTABLISHED + && !NetConnectionTransmitData(connection) + && receiveWindowModified) { + // ACK the new window size. + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } else { + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_IPV4, task->destinationMAC); + IPHeader *ip = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); + IP_HEADER(ip, *(KIPAddress *) &connection->address.ipv4, IP_PROTOCOL_TCP); + TCPHeader *tcp = (TCPHeader *) buffer.Write(nullptr, sizeof(TCPHeader)); + + if (buffer.error) { + KernelPanic("NetConnectionNotify - Network interface buffer size too small.\n"); + } + + ip->totalLength = ByteSwap16(buffer.position - sizeof(*ethernet)); + ip->flagsAndFragmentOffset = SwapBigEndian16(1 << 14 /* do not fragment */); + ip->headerChecksum = ip->CalculateHeaderChecksum(); + + tcp->sourcePort = SwapBigEndian16(task->index + TCP_PORT_BASE); + tcp->destinationPort = SwapBigEndian16(connection->address.port); + tcp->flags = SwapBigEndian16(TCP_ACK | (5 << 12 /* header is 5 DWORDs */)); + tcp->sequenceNumber = SwapBigEndian32(task->sendNext); + tcp->ackNumber = SwapBigEndian32(task->receiveNext); + tcp->window = SwapBigEndian16(task->receiveWindow); + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } + } + } + + KMutexRelease(&connection->mutex); + KWriterLockReturn(&interface->connectionLock, K_LOCK_SHARED); +} + +void NetTCPConnection(NetTask *_task, void *_data) { + TCPReceivedData *data = (TCPReceivedData *) _data; + NetTCPConnectionTask *task = (NetTCPConnectionTask *) _task; + NetInterface *interface = task->interface; + NetConnection *connection = EsContainerOf(NetConnection, task, task); + + if (task->completed) { + NetTCPFreeTaskIndex(task->index); + CloseHandleToObject(connection, KERNEL_OBJECT_CONNECTION); + return; + } + + KMutexAcquire(&connection->mutex); + EsDefer(KMutexRelease(&connection->mutex)); + + if (task->step == 0) { + if (!NetARPLookup(task, interface->routerIP, &task->destinationMAC)) { + return; + } + + if (!NetTCPAllocateTaskIndex(task)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + + task->initialSend = (uint32_t) EsRandomU64() & 0x0FFFFFFF; + task->sendUnacknowledged = task->initialSend; + task->sendNext = task->initialSend + 1; + task->step = TCP_STEP_SYN_SENT; + + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_IPV4, task->destinationMAC); + IPHeader *ip = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); + KIPAddress destinationIP = {}; + EsMemoryCopy(&destinationIP, &connection->address.ipv4, sizeof(KIPAddress)); + IP_HEADER(ip, destinationIP, IP_PROTOCOL_TCP); + TCPHeader *tcp = (TCPHeader *) buffer.Write(nullptr, sizeof(TCPHeader)); + + if (buffer.error) { + KernelPanic("NetTCPConnection - Network interface buffer size too small.\n"); + } + + ip->totalLength = ByteSwap16(buffer.position - sizeof(*ethernet)); + ip->flagsAndFragmentOffset = SwapBigEndian16(1 << 14 /* do not fragment */); + + tcp->window = SwapBigEndian16(task->receiveWindow); + tcp->flags = SwapBigEndian16(TCP_SYN | (5 << 12 /* header is 5 DWORDs */)); + tcp->sourcePort = SwapBigEndian16(task->index + TCP_PORT_BASE); + tcp->destinationPort = SwapBigEndian16(connection->address.port); + tcp->sequenceNumber = SwapBigEndian32(task->initialSend); + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } + } else if (task->step == TCP_STEP_SYN_SENT) { + if ((data->flags & TCP_ACK) && !NetTCPIsBetween(task->sendUnacknowledged, data->ackNumber, task->sendNext)) { + if (data->flags & TCP_RST) return; + EsBuffer buffer = NetTransmitBufferGet(); + if (buffer.error) return; + TCP_PREPARE_REPLY(nullptr, 0, nullptr, 0); + tcpReply->flags = SwapBigEndian16(TCP_RST | (5 << 12 /* header is 5 DWORDs */)); + tcpReply->sequenceNumber = data->tcp->ackNumber; + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } else if (data->flags & TCP_RST) { + NetTaskComplete(task, ES_ERROR_CONNECTION_RESET); + } else if (data->flags & TCP_SYN) { + task->initialReceive = data->sequenceNumber; + task->receiveNext = data->sequenceNumber + 1; + + if (data->flags & TCP_ACK) { + task->sendUnacknowledged = data->ackNumber; + task->step = TCP_STEP_ESTABLISHED; + TCP_MAKE_STANDARD_REPLY(TCP_ACK); + NetConnectionTransmitData(connection); + } else { + task->step = TCP_STEP_SYN_RECEIVED; + TCP_MAKE_STANDARD_REPLY(TCP_SYN | TCP_ACK); + } + } + } else { + bool acceptable = NetTCPIsBetween(task->receiveNext, data->sequenceNumber, task->receiveNext + task->receiveWindow - 1); + + if (!data->segmentLength && !task->receiveWindow) { + acceptable = data->sequenceNumber == task->receiveNext; + } else if (!data->segmentLength && task->receiveWindow) { + acceptable = NetTCPIsBetween(task->receiveNext, data->sequenceNumber, task->receiveNext + task->receiveWindow - 1); + } else if (data->segmentLength && task->receiveWindow) { + acceptable = NetTCPIsBetween(task->receiveNext, data->sequenceNumber, task->receiveNext + task->receiveWindow - 1) + || NetTCPIsBetween(task->receiveNext, data->sequenceNumber + data->segmentLength - 1, task->receiveNext + task->receiveWindow - 1); + } + + if (!acceptable) { + // ACK the unacceptable segment (if the RST flag wasn't set). + if (~data->flags & TCP_RST) TCP_MAKE_STANDARD_REPLY(TCP_ACK); + return; + } + + { + // Truncate segments that are partially outside the window. + // TODO Test this! + + if (NetTCPIsLessThan(data->sequenceNumber, task->receiveNext)) { + data->segment = (uint8_t *) data->segment + task->receiveNext - data->sequenceNumber; + data->segmentLength -= task->receiveNext - data->sequenceNumber; + data->sequenceNumber = task->receiveNext; + } + + if (NetTCPIsLessThan(task->receiveNext + task->receiveWindow, data->sequenceNumber + data->segmentLength)) { + data->segmentLength = (task->receiveNext + task->receiveWindow) - data->sequenceNumber; + } + } + + if (data->flags & TCP_RST) { + if (task->step == TCP_STEP_SYN_RECEIVED) { + NetTaskComplete(task, ES_ERROR_CONNECTION_REFUSED); + } else if (task->step >= TCP_STEP_ESTABLISHED && task->step <= TCP_STEP_CLOSE_WAIT) { + NetTaskComplete(task, ES_ERROR_CONNECTION_RESET); + } else { + NetTaskComplete(task, ES_SUCCESS); + } + } else if (data->flags & TCP_SYN) { + TCP_MAKE_STANDARD_REPLY(TCP_RST); + NetTaskComplete(task, ES_ERROR_CONNECTION_RESET); + } else if (data->flags & TCP_ACK) { + if (task->step == TCP_STEP_SYN_RECEIVED) { + if (NetTCPIsBetween(task->sendUnacknowledged, data->ackNumber, task->sendNext)) { + task->step = TCP_STEP_ESTABLISHED; + task->sendUnacknowledged = data->ackNumber; + task->sendWindow = SwapBigEndian16(data->tcp->window); + TCP_MAKE_STANDARD_REPLY(TCP_ACK); + NetConnectionTransmitData(connection); + } else { + task->sendNext = data->ackNumber; + task->receiveNext = 0; + TCP_MAKE_STANDARD_REPLY(TCP_RST); + return; + } + } else if (task->step == TCP_STEP_LAST_ACK) { + if (NetTCPIsLessThanOrEqual(task->finSequence, task->sendUnacknowledged)) { + NetTaskComplete(task, ES_SUCCESS); + } + } else { + if (NetTCPIsBetween(task->sendUnacknowledged + 1, data->ackNumber, task->sendNext)) { + task->sendUnacknowledged = data->ackNumber; + + // Don't update the window using old packets. + if (NetTCPIsLessThan(task->sendWL1, data->sequenceNumber) + || (task->sendWL1 == data->sequenceNumber && NetTCPIsLessThanOrEqual(task->sendWL2, data->ackNumber))) { + task->sendWindow = SwapBigEndian16(data->tcp->window); + task->sendWL1 = data->sequenceNumber; + task->sendWL2 = data->ackNumber; + } + } else if (NetTCPIsLessThanOrEqual(data->ackNumber, task->sendUnacknowledged)) { + TCP_MAKE_STANDARD_REPLY(TCP_ACK); + return; + } + + if (NetTCPIsLessThanOrEqual(task->finSequence, task->sendUnacknowledged)) { + if (task->step == TCP_STEP_FIN_WAIT_1) { + task->step = TCP_STEP_FIN_WAIT_2; + } else if (task->step == TCP_STEP_CLOSING) { + NetTaskComplete(task, ES_SUCCESS); + return; + } + } + } + } + + if (task->step >= TCP_STEP_ESTABLISHED && task->step <= TCP_STEP_FIN_WAIT_2 && data->segmentLength) { + uint32_t start = data->sequenceNumber - task->receiveNext; + uint32_t end = data->sequenceNumber + data->segmentLength - task->receiveNext; + + if (start >= task->receiveWindow || end > task->receiveWindow || (end - start) >= connection->receiveBufferBytes) { + KernelPanic("NetTCPConnection - Segment %x incorrectly truncated to fit inside receive window of task %x.\n", data, task); + } + + uintptr_t startWrite = (start + connection->receiveWritePointer) % connection->receiveBufferBytes; + uintptr_t endWrite = (end + connection->receiveWritePointer) % connection->receiveBufferBytes; + + if (startWrite < endWrite) { + EsMemoryCopy(connection->receiveBuffer + startWrite, data->segment, endWrite - startWrite); +#if 0 + EsPrint("Writing segment RSN %d to %d->%d: %s\n", + data->sequenceNumber - task->initialReceive, startWrite, endWrite, + data->segmentLength, data->segment); +#endif + } else { + uintptr_t firstCopy = connection->receiveBufferBytes - startWrite; + EsMemoryCopy(connection->receiveBuffer + startWrite, data->segment, firstCopy); + EsMemoryCopy(connection->receiveBuffer, (uint8_t *) data->segment + firstCopy, endWrite); +#if 0 + EsPrint("Writing segment RSN %d to %d->%d and %d->%d: %s\n", + data->sequenceNumber - task->initialReceive, + startWrite, firstCopy, 0, endWrite, + data->segmentLength, data->segment); +#endif + } + + if (start) { + if (!connection->receivedData.Set(start, end, nullptr, true)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + return; + } + } else { + uintptr_t advanceBy = end; + + if (connection->receivedData.contiguous) { + KernelPanic("NetTCPConnection - Received data range set in %x was unexpectedly contiguous.\n", connection); + } + + if (connection->receivedData.ranges.Length()) { + if (connection->receivedData.ranges.Length() + && connection->receivedData.ranges[0].from <= advanceBy) { + advanceBy = connection->receivedData.ranges[0].to; + connection->receivedData.ranges.Delete(0); + } + + for (uintptr_t i = 0; i < connection->receivedData.ranges.Length(); i++) { + connection->receivedData.ranges[0].from -= advanceBy; + connection->receivedData.ranges[0].to -= advanceBy; + } + + connection->receivedData.Validate(); + } + + task->receiveNext += advanceBy; + connection->receiveWritePointer = (connection->receiveWritePointer + advanceBy) % connection->receiveBufferBytes; + NetConnectionUpdateReceiveWindow(connection); + + TCP_MAKE_STANDARD_REPLY(TCP_ACK); + } + } + + if (data->flags & TCP_FIN) { + task->receiveNext = data->sequenceNumber + 1; + TCP_MAKE_STANDARD_REPLY(TCP_ACK); + + if (task->step == TCP_STEP_SYN_RECEIVED || task->step == TCP_STEP_ESTABLISHED) { + task->step = TCP_STEP_CLOSE_WAIT; + } else if (task->step == TCP_STEP_FIN_WAIT_1) { + if (NetTCPIsLessThanOrEqual(task->finSequence, task->sendUnacknowledged)) { + NetTaskComplete(task, ES_SUCCESS); + } else { + task->step = TCP_STEP_CLOSING; + } + } else if (task->step == TCP_STEP_FIN_WAIT_2) { + NetTaskComplete(task, ES_SUCCESS); + } + } + } +} + +void NetAddressSetup(NetTask *_task, void *_buffer) { + EsBuffer *buffer = (EsBuffer *) _buffer; + NetAddressSetupTask *task = (NetAddressSetupTask *) _task; + NetInterface *interface = task->interface; + const DHCPHeader *dhcp; + + if (task->completed) { + return; + } else if (task->step == 0) { + // Broadcast a DHCPDISCOVER request to get an IP address. + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + return; + } + + DHCP_START(); + + uint8_t dhcpOptions[] = { + DHCP_OPTION_MESSAGE_TYPE(DHCPDISCOVER), + 55, 2, 3 /* get the router's IP */, 6 /* get the DNS server's IP */, + 255, // End of options. + }; + + buffer.Write(dhcpOptions, sizeof(dhcpOptions)); + DHCP_END(); + + if (buffer.error) { + KernelPanic("NetAddressSetup - Network interface buffer size too small.\n"); + } + + task->dhcpTransactionID = dhcp->transactionID; + task->step++; + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } + + return; + } + + dhcp = (const DHCPHeader *) buffer->Read(sizeof(DHCPHeader)); + + if (dhcp->opCode != DHCP_BOOTREPLY /* boot reply */ + || dhcp->hardwareAddressType != 0x01 /* ethernet */ + || dhcp->hardwareAddressLength != 6 + || dhcp->transactionID != task->dhcpTransactionID + || dhcp->optionsMagic[0] != 99 + || dhcp->optionsMagic[1] != 130 + || dhcp->optionsMagic[2] != 83 + || dhcp->optionsMagic[3] != 99) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Unsupported DHCP packet.\n"); + return; + } + + task->dhcpTransactionID = 0; + + const KIPAddress *dnsServerOption = nullptr; + const KIPAddress *serverIdentifierOption = nullptr; + const KIPAddress *routerIPOption = nullptr; + const uint8_t *messageTypeOption = nullptr; + + while (true) { + const uint8_t *optionType = (const uint8_t *) buffer->Read(sizeof(uint8_t)); + + if (!optionType) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing DHCP option type.\n"); + return; + } + + if (*optionType == 0) { + continue; + } else if (*optionType == 255) { + break; + } else { + const uint8_t *optionLength = (const uint8_t *) buffer->Read(sizeof(uint8_t)); + + if (!optionLength) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing DHCP option length.\n"); + return; + } + + const uint8_t *optionValue = (const uint8_t *) buffer->Read(*optionLength); + + if (!optionValue) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing DHCP option value.\n"); + return; + } + + if (*optionType == 6 /* DNS server */) { + if (*optionLength < 4) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "DNS server IP in DHCP option too short.\n"); + return; + } + + dnsServerOption = (const KIPAddress *) optionValue; + } else if (*optionType == 3 /* Router IP */) { + if (*optionLength < 4) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Router IP in DHCP option too short.\n"); + return; + } + + routerIPOption = (const KIPAddress *) optionValue; + } else if (*optionType == 54 /* server identifier */) { + if (*optionLength < 4) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Server identifier in DHCP option too short.\n"); + return; + } + + serverIdentifierOption = (const KIPAddress *) optionValue; + } else if (*optionType == 53 /* message type */) { + if (*optionLength < 1) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Message type in DHCP option too short.\n"); + return; + } + + messageTypeOption = (const uint8_t *) optionValue; + } + } + } + + if (!messageTypeOption) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Message type option in DHCP missing.\n"); + return; + } + + uint8_t messageType = *messageTypeOption; + + if (messageType == DHCPOFFER) { + if (task->step != 1) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Received DHCPOFFER, but task step is %d.\n", task->step); + return; + } + + if (!serverIdentifierOption) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Server identifier option missing in DHCPOFFER.\n"); + return; + } + + KIPAddress offer = dhcp->yourIPAddress; + KernelLog(LOG_INFO, "Networking", "received IP", "Network interface %x has been offered IP address %d.%d.%d.%d.\n", + interface, offer.d[0], offer.d[1], offer.d[2], offer.d[3]); + + // Broadcast a DHCPREQUEST message to accept this IP address. + + EsBuffer buffer = NetTransmitBufferGet(); + if (buffer.error) return; + DHCP_START(); + + uint8_t dhcpOptions[] = { + DHCP_OPTION_MESSAGE_TYPE(DHCPREQUEST), + DHCP_OPTION_REQUESTED_IP(offer), + DHCP_OPTION_SERVER_IDENTIFIER(*serverIdentifierOption), + 55, 2, 3 /* get the router's IP */, 6 /* get the DNS server's IP */, + 255, // End of options. + }; + + buffer.Write(dhcpOptions, sizeof(dhcpOptions)); + DHCP_END(); + + if (buffer.error) { + KernelPanic("KNetworkInterface::ReceiveDHCPReply - Network interface buffer size too small.\n"); + } + + task->dhcpTransactionID = dhcp->transactionID; + task->step++; + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } + } else if (messageType == DHCPACK) { + if (task->step != 2) { + KernelLog(LOG_ERROR, "Networking", "ignored packet", "Received DHCPACK, but task step is %d.\n", task->step); + return; + } + + if (!dnsServerOption || !serverIdentifierOption || !routerIPOption) { + KernelLog(LOG_ERROR, "Networking", "bad packet", "Missing options in DHCPACK.\n"); + return; + } + + interface->ipAddress = dhcp->yourIPAddress; + interface->routerIP = *routerIPOption; + interface->dnsServerIP = *dnsServerOption; + interface->serverIdentifier = *serverIdentifierOption; + + KernelLog(LOG_INFO, "Networking", "accepted IP", + "Network interface %x has accepted IP address %d.%d.%d.%d with DNS server %d.%d.%d.%d and router %d.%d.%d.%d.\n", + interface, dhcp->yourIPAddress.d[0], dhcp->yourIPAddress.d[1], dhcp->yourIPAddress.d[2], dhcp->yourIPAddress.d[3], + dnsServerOption->d[0], dnsServerOption->d[1], dnsServerOption->d[2], dnsServerOption->d[3], + routerIPOption->d[0], routerIPOption->d[1], routerIPOption->d[2], routerIPOption->d[3]); + + task->changedState = true; + NetTaskComplete(task, ES_SUCCESS); + } else if (messageType == DHCPNAK) { + KernelLog(LOG_INFO, "Networking", "lost IP", "Network interface %x has lost IP address %d.%d.%d.%d.\n", + interface, interface->ipAddress.d[0], interface->ipAddress.d[1], interface->ipAddress.d[2], interface->ipAddress.d[3]); + task->changedState = true; + NetTaskComplete(task, ES_ERROR_LOST_IP_ADDRESS); + } +} + +void NetTaskBegin(NetTask *task) { + if (!task->interface) { + KMutexAcquire(&networking.interfacesListMutex); + SimpleList *item = networking.interfaces.first; + + while (item && item != &networking.interfaces) { + NetInterface *interface = EsContainerOf(NetInterface, item, item); + KWriterLockTake(&interface->connectionLock, K_LOCK_SHARED); + + if (interface->connected && interface->hasIP) { + task->interface = interface; + break; + } + + KWriterLockReturn(&interface->connectionLock, K_LOCK_SHARED); + item = item->next; + } + + KMutexRelease(&networking.interfacesListMutex); + } else { + KWriterLockTake(&task->interface->connectionLock, K_LOCK_SHARED); + } + + if (!task->interface) { + NetTaskComplete(task, ES_ERROR_NO_CONNECTED_NETWORK_INTERFACES); + } else { + task->index = 0xFFFF; + task->callback(task, nullptr); + KWriterLockReturn(&task->interface->connectionLock, K_LOCK_SHARED); + } +} + +void NetTaskComplete(NetTask *task, EsError error) { + KWriterLockAssertShared(&task->interface->connectionLock); + + if (task->completed) { + KernelPanic("NetTaskComplete - Task already completed.\n"); + } + + task->error = error; + task->completed = true; + task->callback(task, nullptr); +} + +void NetConnectionDestroy(NetConnection *connection) { + MMFree(kernelMMSpace, connection->sendBuffer, connection->sendBufferBytes + connection->receiveBufferBytes); + connection->receivedData.ranges.Free(); + CloseHandleToObject(connection->bufferRegion, KERNEL_OBJECT_SHMEM); + EsHeapFree(connection, sizeof(NetConnection), K_FIXED); +} + +NetConnection *NetConnectionOpen(EsAddress *address, size_t sendBufferBytes, size_t receiveBufferBytes, uint32_t flags) { + (void) flags; + + NetConnection *connection = (NetConnection *) EsHeapAllocate(sizeof(NetConnection), true, K_FIXED); + + if (!connection) { + return nullptr; + } + + connection->sendBufferBytes = sendBufferBytes; + connection->receiveBufferBytes = receiveBufferBytes; + connection->address = *address; + connection->handles = 2; + + connection->bufferRegion = MMSharedCreateRegion(sendBufferBytes + receiveBufferBytes, true); + + if (!connection->bufferRegion) { + EsHeapFree(connection, sizeof(NetConnection), K_FIXED); + return nullptr; + } + + connection->sendBuffer = (uint8_t *) MMMapShared(kernelMMSpace, connection->bufferRegion, 0, sendBufferBytes + receiveBufferBytes); + connection->receiveBuffer = connection->sendBuffer + sendBufferBytes; + + connection->task.callback = NetTCPConnection; + connection->task.receiveWindow = MinimumInteger(receiveBufferBytes - 1, 0xF000); + NetTaskBegin(&connection->task); + + return connection; +} + +void NetConnectionClose(NetConnection *connection) { + bool destroy = false; + + NetTCPConnectionTask *task = &connection->task; + NetInterface *interface = task->interface; + KWriterLockTake(&interface->connectionLock, K_LOCK_SHARED); + KMutexAcquire(&connection->mutex); + connection->handles++; // Prevent NetTaskComplete from destroying the connection. + + if (task->completed) { + destroy = true; + } else if (task->step == TCP_STEP_SYN_RECEIVED || task->step == TCP_STEP_ESTABLISHED || task->step == TCP_STEP_CLOSE_WAIT) { + // Send a FIN packet. + + EsBuffer buffer = NetTransmitBufferGet(); + + if (buffer.error) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } else { + EthernetHeader *ethernet = (EthernetHeader *) buffer.Write(nullptr, sizeof(EthernetHeader)); + ETHERNET_HEADER(ethernet, ETHERNET_TYPE_IPV4, task->destinationMAC); + IPHeader *ip = (IPHeader *) buffer.Write(nullptr, sizeof(IPHeader)); + IP_HEADER(ip, *(KIPAddress *) &connection->address.ipv4, IP_PROTOCOL_TCP); + TCPHeader *tcp = (TCPHeader *) buffer.Write(nullptr, sizeof(TCPHeader)); + + if (buffer.error) { + KernelPanic("NetConnectionClose - Network interface buffer size too small.\n"); + } + + ip->totalLength = ByteSwap16(buffer.position - sizeof(*ethernet)); + ip->flagsAndFragmentOffset = SwapBigEndian16(1 << 14 /* do not fragment */); + ip->headerChecksum = ip->CalculateHeaderChecksum(); + + tcp->sourcePort = SwapBigEndian16(task->index + TCP_PORT_BASE); + tcp->destinationPort = SwapBigEndian16(connection->address.port); + tcp->flags = SwapBigEndian16(TCP_FIN | TCP_ACK | (5 << 12 /* header is 5 DWORDs */)); + tcp->sequenceNumber = SwapBigEndian32(task->sendNext); + tcp->ackNumber = SwapBigEndian32(task->receiveNext); + tcp->window = SwapBigEndian16(task->receiveWindow); + + task->finSequence = task->sendNext; + task->sendNext++; + task->step = TCP_STEP_FIN_WAIT_1; + + if (!NetTransmit(interface, &buffer, NET_PACKET_ETHERNET)) { + NetTaskComplete(task, ES_ERROR_INSUFFICIENT_RESOURCES); + } + } + } + + connection->handles--; + KMutexRelease(&connection->mutex); + KWriterLockReturn(&interface->connectionLock, K_LOCK_SHARED); + + if (destroy) { + NetConnectionDestroy(connection); + } +} + +void KRegisterNetInterface(NetInterface *interface) { + KernelLog(LOG_INFO, "Networking", "register interface", "Registered interface with MAC address %X:%X:%X:%X:%X:%X and name '%z'.\n", + interface->macAddress.d[0], interface->macAddress.d[1], interface->macAddress.d[2], + interface->macAddress.d[3], interface->macAddress.d[4], interface->macAddress.d[5], + interface->cDebugName); + + KMutexAcquire(&networking.interfacesListMutex); + networking.interfaces.Insert(&interface->item, false /* end */); + KMutexRelease(&networking.interfacesListMutex); + + interface->addressSetupTask.interface = interface; + interface->addressSetupTask.callback = NetAddressSetup; +} + +void NetInitialise(KDevice *) { + networking.udpTaskBitset.Initialise(MAX_UDP_TASKS); + networking.udpTaskBitset.PutAll(); + EsArenaInitialise(&networking.transmitBufferPool, 1048576, 2048); + + networking.tcpTaskLRU = networking.tcpTaskMRU = 0xFFFF; + + for (uintptr_t i = 0; i < MAX_TCP_TASKS; i++) { + NetTCPFreeTaskIndex(i, true); + } +} + +KDriver driverNetworking = { + .attach = NetInitialise, +}; + +#endif diff --git a/kernel/objects.cpp b/kernel/objects.cpp new file mode 100644 index 0000000..ed3da60 --- /dev/null +++ b/kernel/objects.cpp @@ -0,0 +1,701 @@ +// TODO Reject opening handles if the handle table has been destroyed!! + +#ifndef IMPLEMENTATION + +inline KernelObjectType operator|(KernelObjectType a, KernelObjectType b) { + return (KernelObjectType) ((int) a | (int) b); +} + +struct Handle { + void *object; + uint32_t flags; + KernelObjectType type; +}; + +struct ConstantBuffer { + volatile size_t handles; + size_t bytes : 48, + isPaged : 1; + // Data follows. +}; + +struct Pipe { +#define PIPE_READER (1) +#define PIPE_WRITER (2) +#define PIPE_BUFFER_SIZE (K_PAGE_SIZE) +#define PIPE_CLOSED (0) + + volatile char buffer[PIPE_BUFFER_SIZE]; + volatile size_t writers, readers; + volatile uintptr_t writePosition, readPosition, unreadData; + KEvent canWrite, canRead; + KMutex mutex; + + size_t Access(void *buffer, size_t bytes, bool write, bool userBlockRequest); +}; + +struct EventSink { + KEvent available; + KSpinlock spinlock; // Take after the scheduler's spinlock. + volatile size_t handles; + uintptr_t queuePosition; + size_t queueCount; + bool overflow, ignoreDuplicates; + EsGeneric queue[ES_MAX_EVENT_SINK_BUFFER_SIZE]; + + EsError Push(EsGeneric data); +}; + +struct EventSinkTable { + EventSink *sink; + EsGeneric data; +}; + +size_t totalHandleCount; + +struct HandleTableL2 { +#define HANDLE_TABLE_L2_ENTRIES (256) + Handle t[HANDLE_TABLE_L2_ENTRIES]; +}; + +struct HandleTableL1 { +#define HANDLE_TABLE_L1_ENTRIES (256) + HandleTableL2 *t[HANDLE_TABLE_L1_ENTRIES]; + uint16_t u[HANDLE_TABLE_L1_ENTRIES]; +}; + +struct HandleTable { + HandleTableL1 l1r; + KMutex lock; + Process *process; + bool destroyed; + uint32_t handleCount; + + EsHandle OpenHandle(void *_object, uint32_t _flags, KernelObjectType _type, EsHandle at = ES_INVALID_HANDLE); + bool CloseHandle(EsHandle handle); + + // Resolve the handle if it is valid and return the type in type. + // The initial value of type is used as a mask of expected object types for the handle. + void ResolveHandle(struct KObject *object, EsHandle handle); + + void Destroy(); +}; + +struct KObject { + void Initialise(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type); + + KObject() { EsMemoryZero(this, sizeof(*this)); } + KObject(Process *process, EsHandle _handle, KernelObjectType _type); + KObject(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type); + + ~KObject() { + if (!checked && valid) { + KernelPanic("KObject - Object not checked!\n"); + } + + if (parentObject && close) { + CloseHandleToObject(parentObject, parentType, parentFlags); + } + } + + void *object, *parentObject; + uint32_t flags, parentFlags; + KernelObjectType type, parentType; + bool valid, checked, close, softFailure; +}; + +void InitialiseObjectManager(); + +#endif + +#ifdef IMPLEMENTATION + +KObject::KObject(Process *process, EsHandle _handle, KernelObjectType _type) { + EsMemoryZero(this, sizeof(*this)); + Initialise(&process->handleTable, _handle, _type); +} + +KObject::KObject(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type) { + EsMemoryZero(this, sizeof(*this)); + Initialise(handleTable, _handle, _type); +} + +void KObject::Initialise(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type) { + type = _type; + + handleTable->ResolveHandle(this, _handle); + parentObject = object, parentType = type, parentFlags = flags; + + if (!valid) { + KernelLog(LOG_ERROR, "Object Manager", "invalid handle", "KObject::Initialise - Invalid handle %d for type mask %x.\n", _handle, _type); + } +} + +// A lock used to change the handle count on several objects. +// TODO Make changing handle count lockless wherever possible? +KMutex objectHandleCountChange; + +// TODO Use uint64_t for handle counts, or restrict OpenHandleToObject to some maximum (...but most callers don't check if OpenHandleToObject succeeds). + +bool OpenHandleToObject(void *object, KernelObjectType type, uint32_t flags, bool maybeHasNoHandles) { + bool hadNoHandles = false, failed = false; + + switch (type) { + case KERNEL_OBJECT_EVENT: { + KMutexAcquire(&objectHandleCountChange); + KEvent *event = (KEvent *) object; + if (!event->handles) hadNoHandles = true; + else event->handles++; + KMutexRelease(&objectHandleCountChange); + } break; + + case KERNEL_OBJECT_PROCESS: { + KSpinlockAcquire(&scheduler.lock); + Process *process = (Process *) object; + if (!process->handles) hadNoHandles = true; + else process->handles++; // NOTE Scheduler::OpenProcess and MMBalanceThread also adjust process handles. + KernelLog(LOG_VERBOSE, "Scheduler", "open process handle", "Opened handle to process %d; %d handles.\n", process->id, process->handles); + KSpinlockRelease(&scheduler.lock); + } break; + + case KERNEL_OBJECT_THREAD: { + KSpinlockAcquire(&scheduler.lock); + Thread *thread = (Thread *) object; + if (!thread->handles) hadNoHandles = true; + else thread->handles++; + KSpinlockRelease(&scheduler.lock); + } break; + + case KERNEL_OBJECT_SHMEM: { + MMSharedRegion *region = (MMSharedRegion *) object; + KMutexAcquire(®ion->mutex); + if (!region->handles) hadNoHandles = true; + else region->handles++; + KMutexRelease(®ion->mutex); + } break; + + case KERNEL_OBJECT_WINDOW: { + // NOTE The handle count of Window object is modified elsewhere. + Window *window = (Window *) object; + hadNoHandles = 0 == __sync_fetch_and_add(&window->handles, 1); + } break; + + case KERNEL_OBJECT_EMBEDDED_WINDOW: { + EmbeddedWindow *window = (EmbeddedWindow *) object; + hadNoHandles = 0 == __sync_fetch_and_add(&window->handles, 1); + } break; + + case KERNEL_OBJECT_CONSTANT_BUFFER: { + ConstantBuffer *buffer = (ConstantBuffer *) object; + KMutexAcquire(&objectHandleCountChange); + if (!buffer->handles) hadNoHandles = true; + else buffer->handles++; + KMutexRelease(&objectHandleCountChange); + } break; + +#ifdef ENABLE_POSIX_SUBSYSTEM + case KERNEL_OBJECT_POSIX_FD: { + POSIXFile *file = (POSIXFile *) object; + KMutexAcquire(&file->mutex); + if (!file->handles) hadNoHandles = true; + else file->handles++; + KMutexRelease(&file->mutex); + } break; +#endif + + case KERNEL_OBJECT_NODE: { + failed = ES_SUCCESS != FSNodeOpenHandle((KNode *) object, flags, FS_NODE_OPEN_HANDLE_STANDARD); + } break; + + case KERNEL_OBJECT_PIPE: { + Pipe *pipe = (Pipe *) object; + KMutexAcquire(&pipe->mutex); + + if (((flags & PIPE_READER) && !pipe->readers) + || ((flags & PIPE_WRITER) && !pipe->writers)) { + hadNoHandles = true; + } else { + if (flags & PIPE_READER) { + pipe->readers++; + } + + if (flags & PIPE_WRITER) { + pipe->writers++; + } + } + + KMutexRelease(&pipe->mutex); + } break; + + case KERNEL_OBJECT_EVENT_SINK: { + EventSink *sink = (EventSink *) object; + KSpinlockAcquire(&sink->spinlock); + if (!sink->handles) hadNoHandles = true; + else sink->handles++; + KSpinlockRelease(&sink->spinlock); + } break; + + case KERNEL_OBJECT_CONNECTION: { + NetConnection *connection = (NetConnection *) object; + hadNoHandles = 0 == __sync_fetch_and_add(&connection->handles, 1); + } break; + + default: { + KernelPanic("OpenHandleToObject - Cannot open object of type %x.\n", type); + } break; + } + + if (hadNoHandles) { + if (maybeHasNoHandles) { + return false; + } else { + KernelPanic("OpenHandleToObject - Object %x of type %x had no handles.\n", object, type); + } + } + + return !failed; +} + +void CloseHandleToObject(void *object, KernelObjectType type, uint32_t flags) { + switch (type) { + case KERNEL_OBJECT_PROCESS: { + CloseHandleToProcess(object); + } break; + + case KERNEL_OBJECT_THREAD: { + CloseHandleToThread(object); + } break; + + case KERNEL_OBJECT_NODE: { + FSNodeCloseHandle((KNode *) object, flags); + } break; + + case KERNEL_OBJECT_EVENT: { + KEvent *event = (KEvent *) object; + KMutexAcquire(&objectHandleCountChange); + bool destroy = event->handles == 1; + event->handles--; + KMutexRelease(&objectHandleCountChange); + + if (destroy) { + if (event->sinkTable) { + for (uintptr_t i = 0; i < ES_MAX_EVENT_FORWARD_COUNT; i++) { + if (event->sinkTable[i].sink) { + CloseHandleToObject(event->sinkTable[i].sink, KERNEL_OBJECT_EVENT_SINK, 0); + } + } + + EsHeapFree(event->sinkTable, sizeof(EventSinkTable) * ES_MAX_EVENT_FORWARD_COUNT, K_FIXED); + } + + EsHeapFree(event, sizeof(KEvent), K_FIXED); + } + } break; + + case KERNEL_OBJECT_CONSTANT_BUFFER: { + ConstantBuffer *buffer = (ConstantBuffer *) object; + KMutexAcquire(&objectHandleCountChange); + bool destroy = buffer->handles == 1; + buffer->handles--; + KMutexRelease(&objectHandleCountChange); + + if (destroy) { + EsHeapFree(object, sizeof(ConstantBuffer) + buffer->bytes, buffer->isPaged ? K_PAGED : K_FIXED); + } + } break; + + case KERNEL_OBJECT_SHMEM: { + MMSharedRegion *region = (MMSharedRegion *) object; + KMutexAcquire(®ion->mutex); + bool destroy = region->handles == 1; + region->handles--; + KMutexRelease(®ion->mutex); + + if (destroy) { + MMSharedDestroyRegion(region); + } + } break; + + case KERNEL_OBJECT_WINDOW: { + Window *window = (Window *) object; + unsigned previous = __sync_fetch_and_sub(&window->handles, 1); + if (!previous) KernelPanic("CloseHandleToObject - Window %x has no handles.\n", window); + + if (previous == 2) { + KEventSet(&windowManager.windowsToCloseEvent, false, true /* maybe already set */); + } else if (previous == 1) { + window->Destroy(); + } + } break; + + case KERNEL_OBJECT_EMBEDDED_WINDOW: { + EmbeddedWindow *window = (EmbeddedWindow *) object; + unsigned previous = __sync_fetch_and_sub(&window->handles, 1); + if (!previous) KernelPanic("CloseHandleToObject - EmbeddedWindow %x has no handles.\n", window); + + if (previous == 2) { + KEventSet(&windowManager.windowsToCloseEvent, false, true /* maybe already set */); + } else if (previous == 1) { + window->Destroy(); + } + } break; + +#ifdef ENABLE_POSIX_SUBSYSTEM + case KERNEL_OBJECT_POSIX_FD: { + POSIXFile *file = (POSIXFile *) object; + KMutexAcquire(&file->mutex); + file->handles--; + bool destroy = !file->handles; + KMutexRelease(&file->mutex); + + if (destroy) { + if (file->type == POSIX_FILE_NORMAL || file->type == POSIX_FILE_DIRECTORY) CloseHandleToObject(file->node, KERNEL_OBJECT_NODE, file->openFlags); + if (file->type == POSIX_FILE_PIPE) CloseHandleToObject(file->pipe, KERNEL_OBJECT_PIPE, file->openFlags); + EsHeapFree(file->path, 0, K_FIXED); + EsHeapFree(file->directoryBuffer, file->directoryBufferLength, K_PAGED); + EsHeapFree(file, sizeof(POSIXFile), K_FIXED); + } + } break; +#endif + + case KERNEL_OBJECT_PIPE: { + Pipe *pipe = (Pipe *) object; + KMutexAcquire(&pipe->mutex); + + if (flags & PIPE_READER) { + pipe->readers--; + + if (!pipe->readers) { + // If there are no more readers, wake up any blocking writers. + KEventSet(&pipe->canWrite, false, true); + } + } + + if (flags & PIPE_WRITER) { + pipe->writers--; + + if (!pipe->writers) { + // If there are no more writers, wake up any blocking readers. + KEventSet(&pipe->canRead, false, true); + } + } + + bool destroy = pipe->readers == 0 && pipe->writers == 0; + + KMutexRelease(&pipe->mutex); + + if (destroy) { + EsHeapFree(pipe, sizeof(Pipe), K_PAGED); + } + } break; + + case KERNEL_OBJECT_EVENT_SINK: { + EventSink *sink = (EventSink *) object; + KSpinlockAcquire(&sink->spinlock); + bool destroy = sink->handles == 1; + sink->handles--; + KSpinlockRelease(&sink->spinlock); + + if (destroy) { + EsHeapFree(sink, sizeof(EventSink), K_FIXED); + } + } break; + + case KERNEL_OBJECT_CONNECTION: { + NetConnection *connection = (NetConnection *) object; + unsigned previous = __sync_fetch_and_sub(&connection->handles, 1); + if (!previous) KernelPanic("CloseHandleToObject - NetConnection %x has no handles.\n", connection); + if (previous == 1) NetConnectionClose(connection); + } break; + + default: { + KernelPanic("CloseHandleToObject - Cannot close object of type %x.\n", type); + } break; + } +} + +bool HandleTable::CloseHandle(EsHandle handle) { + if (handle > HANDLE_TABLE_L1_ENTRIES * HANDLE_TABLE_L2_ENTRIES) { + return false; + } + + KMutexAcquire(&lock); + HandleTableL1 *l1 = &l1r; + HandleTableL2 *l2 = l1->t[handle / HANDLE_TABLE_L2_ENTRIES]; + if (!l2) { KMutexRelease(&lock); return false; } + Handle *_handle = l2->t + (handle % HANDLE_TABLE_L2_ENTRIES); + KernelObjectType type = _handle->type; + uint64_t flags = _handle->flags; + void *object = _handle->object; + if (!object) { KMutexRelease(&lock); return false; } + EsMemoryZero(_handle, sizeof(Handle)); + l1->u[handle / HANDLE_TABLE_L2_ENTRIES]--; + handleCount--; + KMutexRelease(&lock); + + __sync_fetch_and_sub(&totalHandleCount, 1); + CloseHandleToObject(object, type, flags); + return true; +} + +void HandleTable::ResolveHandle(KObject *object, EsHandle handle) { + KernelObjectType requestedType = object->type; + object->type = COULD_NOT_RESOLVE_HANDLE; + + // Special handles. + if (handle == ES_CURRENT_THREAD && (requestedType & KERNEL_OBJECT_THREAD)) { + object->type = KERNEL_OBJECT_THREAD, object->valid = true, object->object = GetCurrentThread(); + return; + } else if (handle == ES_CURRENT_PROCESS && (requestedType & KERNEL_OBJECT_PROCESS)) { + object->type = KERNEL_OBJECT_PROCESS, object->valid = true, object->object = GetCurrentThread()->process; + return; + } else if (handle == ES_INVALID_HANDLE && (requestedType & KERNEL_OBJECT_NONE)) { + object->type = KERNEL_OBJECT_NONE, object->valid = true; + return; + } + + // Check that the handle is within the correct bounds. + if ((!handle) || handle >= HANDLE_TABLE_L1_ENTRIES * HANDLE_TABLE_L2_ENTRIES) { + return; + } + + KMutexAcquire(&lock); + EsDefer(KMutexRelease(&lock)); + + HandleTableL2 *l2 = l1r.t[handle / HANDLE_TABLE_L2_ENTRIES]; + + if (l2) { + Handle *_handle = l2->t + (handle % HANDLE_TABLE_L2_ENTRIES); + + if ((_handle->type & requestedType) && (_handle->object)) { + // Open a handle to the object so that it can't be destroyed while the system call is still using it. + // The handle is closed in the KObject's destructor. + if (OpenHandleToObject(_handle->object, _handle->type, _handle->flags)) { + object->type = _handle->type, object->object = _handle->object, object->flags = _handle->flags; + object->valid = object->close = true; + } + } + } +} + +// TODO Switch the order of flags and type, so that the default value of flags can be 0. +EsHandle HandleTable::OpenHandle(void *object, uint32_t flags, KernelObjectType type, EsHandle at) { + KMutexAcquire(&lock); + EsDefer(KMutexRelease(&lock)); + + Handle handle = {}; + handle.object = object; + handle.flags = flags; + handle.type = type; + + { + if (destroyed) goto error; + + if (!handle.object) { + KernelPanic("HandleTable::OpenHandle - Invalid object.\n"); + } + + HandleTableL1 *l1 = &l1r; + uintptr_t l1Index = HANDLE_TABLE_L1_ENTRIES; + + for (uintptr_t i = 1 /* The first set of handles are reserved. */; i < HANDLE_TABLE_L1_ENTRIES; i++) { + if (at) i = at / HANDLE_TABLE_L2_ENTRIES; + + if (l1->u[i] != HANDLE_TABLE_L2_ENTRIES) { + l1->u[i]++; + handleCount++; + l1Index = i; + break; + } + + if (at) goto error; + } + + if (l1Index == HANDLE_TABLE_L1_ENTRIES) goto error; + + if (!l1->t[l1Index]) l1->t[l1Index] = (HandleTableL2 *) EsHeapAllocate(sizeof(HandleTableL2), true, K_FIXED); + HandleTableL2 *l2 = l1->t[l1Index]; + uintptr_t l2Index = HANDLE_TABLE_L2_ENTRIES; + + for (uintptr_t i = 0; i < HANDLE_TABLE_L2_ENTRIES; i++) { + if (at) i = at % HANDLE_TABLE_L2_ENTRIES; + + if (!l2->t[i].object) { + l2Index = i; + break; + } + + if (at) goto error; + } + + if (l2Index == HANDLE_TABLE_L2_ENTRIES) KernelPanic("HandleTable::OpenHandle - Unexpected lack of free handles.\n"); + Handle *_handle = l2->t + l2Index; + *_handle = handle; + + __sync_fetch_and_add(&totalHandleCount, 1); + + EsHandle index = l2Index + (l1Index * HANDLE_TABLE_L2_ENTRIES); + return index; + } + + error:; + // TODO Close the handle to the object with CloseHandleToObject? + return ES_INVALID_HANDLE; +} + +void HandleTable::Destroy() { + KMutexAcquire(&lock); + EsDefer(KMutexRelease(&lock)); + + if (destroyed) { + return; + } + + destroyed = true; + HandleTableL1 *l1 = &l1r; + + for (uintptr_t i = 0; i < HANDLE_TABLE_L1_ENTRIES; i++) { + if (!l1->u[i]) continue; + + for (uintptr_t k = 0; k < HANDLE_TABLE_L2_ENTRIES; k++) { + Handle *handle = &l1->t[i]->t[k]; + if (handle->object) CloseHandleToObject(handle->object, handle->type, handle->flags); + } + + EsHeapFree(l1->t[i], 0, K_FIXED); + } +} + +ConstantBuffer *MakeConstantBuffer(K_USER_BUFFER const void *data, size_t bytes) { + ConstantBuffer *buffer = (ConstantBuffer *) EsHeapAllocate(sizeof(ConstantBuffer) + bytes, false, K_FIXED); + if (!buffer) return nullptr; + EsMemoryZero(buffer, sizeof(ConstantBuffer)); + buffer->handles = 1; + buffer->bytes = bytes; + EsMemoryCopy(buffer + 1, data, buffer->bytes); + return buffer; +} + +EsHandle MakeConstantBuffer(K_USER_BUFFER const void *data, size_t bytes, Process *process) { + void *object = MakeConstantBuffer(data, bytes); + + if (!object) { + return ES_INVALID_HANDLE; + } + + EsHandle h = process->handleTable.OpenHandle(object, 0, KERNEL_OBJECT_CONSTANT_BUFFER); + + if (h == ES_INVALID_HANDLE) { + CloseHandleToObject(object, KERNEL_OBJECT_CONSTANT_BUFFER, 0); + return ES_INVALID_HANDLE; + } + + return h; +} + +EsHandle MakeConstantBufferForDesktop(K_USER_BUFFER const void *data, size_t bytes) { + return MakeConstantBuffer(data, bytes, desktopProcess); +} + +size_t Pipe::Access(void *_buffer, size_t bytes, bool write, bool user) { + size_t amount = 0; + Thread *currentThread = GetCurrentThread(); + // EsPrint("--> %z %d\n", write ? "Write" : "Read", bytes); + + while (bytes) { + if (user) currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST; + + if (write) { + // Wait until we can write to the pipe. + KEventWait(&canWrite, ES_WAIT_NO_TIMEOUT); + } else { + // Wait until we can read from the pipe. + KEventWait(&canRead, ES_WAIT_NO_TIMEOUT); + } + + if (user) { + currentThread->terminatableState = THREAD_IN_SYSCALL; + if (currentThread->terminating) goto done; + } + + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (write) { + // EsPrint("Write:\n"); + + size_t spaceAvailable = PIPE_BUFFER_SIZE - unreadData; + size_t toWrite = bytes > spaceAvailable ? spaceAvailable : bytes; + + size_t spaceAvailableRight = PIPE_BUFFER_SIZE - writePosition; + size_t toWriteRight = toWrite > spaceAvailableRight ? spaceAvailableRight : toWrite; + size_t toWriteLeft = toWrite - toWriteRight; + + // EsPrint("\tunread: %d; wp: %d\n", unreadData, writePosition); + // EsPrint("\t%d, %d, %d, %d, %d\n", spaceAvailable, spaceAvailableRight, toWrite, toWriteRight, toWriteLeft); + + EsMemoryCopy((uint8_t *) buffer + writePosition, _buffer, toWriteRight); + EsMemoryCopy((uint8_t *) buffer, (uint8_t *) _buffer + toWriteRight, toWriteLeft); + + writePosition += toWrite; + writePosition %= PIPE_BUFFER_SIZE; + unreadData += toWrite; + bytes -= toWrite; + _buffer = (uint8_t *) _buffer + toWrite; + amount += toWrite; + + KEventSet(&canRead, false, true); + + if (!readers) { + // EsPrint("\tPipe closed\n"); + // Nobody is reading from the pipe, so there's no point writing to it. + goto done; + } else if (PIPE_BUFFER_SIZE == unreadData) { + KEventReset(&canWrite); + // EsPrint("\treset canWrite\n"); + } + } else { + // EsPrint("Read:\n"); + + size_t dataAvailable = unreadData; + size_t toRead = bytes > dataAvailable ? dataAvailable : bytes; + + size_t spaceAvailableRight = PIPE_BUFFER_SIZE - readPosition; + size_t toReadRight = toRead > spaceAvailableRight ? spaceAvailableRight : toRead; + size_t toReadLeft = toRead - toReadRight; + + // EsPrint("\tunread: %d; rp: %d\n", unreadData, readPosition); + // EsPrint("\t%d, %d, %d, %d, %d\n", dataAvailable, spaceAvailableRight, toRead, toReadRight, toReadLeft); + + EsMemoryCopy(_buffer, (uint8_t *) buffer + readPosition, toReadRight); + EsMemoryCopy((uint8_t *) _buffer + toReadRight, (uint8_t *) buffer, toReadLeft); + + readPosition += toRead; + readPosition %= PIPE_BUFFER_SIZE; + unreadData -= toRead; + bytes -= toRead; + _buffer = (uint8_t *) _buffer + toRead; + amount += toRead; + + KEventSet(&canWrite, false, true); + + if (!writers) { + // Nobody is writing to the pipe, so there's no point reading from it. + // EsPrint("\tPipe closed\n"); + goto done; + } else if (!unreadData) { + KEventReset(&canRead); + } + + // Don't block when reading from pipes after the first chunk of data. + // TODO Change this behaviour? + goto done; + } + } + + done:; + // EsPrint("<-- %d (%d remaining, %z)\n", amount, bytes, write ? "Write" : "Read"); + return amount; +} + +#endif diff --git a/kernel/posix.cpp b/kernel/posix.cpp new file mode 100644 index 0000000..7fd0436 --- /dev/null +++ b/kernel/posix.cpp @@ -0,0 +1,821 @@ +#ifndef IMPLEMENTATION + +struct POSIXFile { + uint64_t posixFlags; // The flags given in SYS_open. + volatile size_t handles; + char *path; // Resolved path. + EsFileOffset offsetIntoFile; + uint64_t openFlags; // The flags used to open the object. + KMutex mutex; + KNode *node; + Pipe *pipe; + void *directoryBuffer; + size_t directoryBufferLength; + +#define POSIX_FILE_TERMINAL (1) +#define POSIX_FILE_NORMAL (2) +#define POSIX_FILE_PIPE (3) +#define POSIX_FILE_ZERO (4) +#define POSIX_FILE_NULL (5) +#define POSIX_FILE_DIRECTORY (6) + int type; +}; + +namespace POSIX { + uintptr_t DoSyscall(_EsPOSIXSyscall syscall, uintptr_t *userStackPointer); + KMutex forkMutex; + KMutex threadPOSIXDataMutex; +} + +struct POSIXThread { + void *forkStack; + size_t forkStackSize; + uintptr_t forkUSP; + Process *forkProcess; +}; + +#define SYSCALL_BUFFER_POSIX(address, length, index, write) \ + MMRegion *_region ## index = MMFindAndPinRegion(currentVMM, (address), (length)); \ + if (!_region ## index && !fromKernel) { KernelLog(LOG_ERROR, "POSIX", "EFAULT", "POSIX application EFAULT at %x.\n", address); return -EFAULT; } \ + EsDefer(if (_region ## index) MMUnpinRegion(currentVMM, _region ## index)); \ + if (write && (_region ## index->flags & MM_REGION_READ_ONLY) && (~_region ## index->flags & MM_REGION_COPY_ON_WRITE)) \ + { KernelLog(LOG_ERROR, "POSIX", "EFAULT", "POSIX application EFAULT (2) at %x.\n", address); return -EFAULT; } +#define SYSCALL_BUFFER_ALLOW_NULL_POSIX(address, length, index) \ + MMRegion *_region ## index = MMFindAndPinRegion(currentVMM, (address), (length)); \ + EsDefer(if (_region ## index) MMUnpinRegion(currentVMM, _region ## index)); + +#define SYSCALL_HANDLE_POSIX(handle, __object, index) \ + KObject _object ## index(handleTable, ConvertStandardInputTo3(handle), KERNEL_OBJECT_POSIX_FD); \ + *((void **) &__object) = _object ## index .object; \ + if (! _object ## index .valid) return -EBADF; else _object ## index .checked = true; \ + +#endif + +#ifdef IMPLEMENTATION + +#define _POSIX_SOURCE +#define _GNU_SOURCE + +namespace POSIX { + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + EsHandle ConvertStandardInputTo3(EsHandle handle) { + if (handle) { + return handle; + } else { + return 3; + } + } + + intptr_t Read(POSIXFile *file, K_USER_BUFFER void *base, size_t length, bool baseMappedToFile) { + if (!length) return 0; + + // EsPrint("Read %d bytes from %x into %x\n", length, file, base); + + if (file->type == POSIX_FILE_TERMINAL) { + return 0; + } else if (file->type == POSIX_FILE_PIPE) { + return file->pipe->Access(base, length, false, true); + } else if (file->type == POSIX_FILE_NORMAL) { + // EsPrint("READ '%z' %d/%d\n", file->path, file->offsetIntoFile, length); + KMutexAcquire(&file->mutex); + EsDefer(KMutexRelease(&file->mutex)); + size_t count = FSFileReadSync(file->node, (void *) base, file->offsetIntoFile, length, baseMappedToFile ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0); + if (ES_CHECK_ERROR(count)) return -EIO; + else { file->offsetIntoFile += count; return count; } + } else if (file->type == POSIX_FILE_ZERO) { + EsMemoryZero(base, length); + return length; + } else if (file->type == POSIX_FILE_NULL) { + return 0; + } else if (file->type == POSIX_FILE_DIRECTORY) { + return 0; + } + + return -EACCES; + } + + intptr_t Write(POSIXFile *file, K_USER_BUFFER void *base, size_t length, bool baseMappedToFile) { + if (file->posixFlags & O_APPEND) { + // TODO Make this atomic. + file->offsetIntoFile = file->node->directoryEntry->totalSize; + } + + if (!length) return 0; + + if (file->type == POSIX_FILE_TERMINAL) { + EsPrint("%s", length, base); + return length; + } else if (file->type == POSIX_FILE_PIPE) { + // EsPrint("Write %d bytes to pipe %x...\n", length, file->pipe); + return file->pipe->Access(base, length, true, true); + } else if (file->type == POSIX_FILE_NORMAL) { + // EsPrint("WRITE '%z' %d/%d\n", file->path, file->offsetIntoFile, length); + KMutexAcquire(&file->mutex); + EsDefer(KMutexRelease(&file->mutex)); + size_t count = FSFileWriteSync(file->node, (void *) base, file->offsetIntoFile, length, + (baseMappedToFile ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0)); + if (ES_CHECK_ERROR(count)) return -EIO; + else { file->offsetIntoFile += count; return count; } + } else if (file->type == POSIX_FILE_ZERO || file->type == POSIX_FILE_NULL) { + return length; + } else if (file->type == POSIX_FILE_DIRECTORY) { + return length; + } + + return -EACCES; + } + + void Stat(int type, KNode *node, struct stat *buffer) { + EsMemoryZero(buffer, sizeof(struct stat)); + + if (type == POSIX_FILE_NORMAL) { + buffer->st_ino = node->id; + buffer->st_mode = S_IFREG; + buffer->st_nlink = 1; + + if (node->directoryEntry->type == ES_NODE_FILE) { + buffer->st_size = node->directoryEntry->totalSize; + buffer->st_blksize = 512; + buffer->st_blocks = buffer->st_size / 512; + } + } else if (type == POSIX_FILE_DIRECTORY) { + buffer->st_ino = node->id; + buffer->st_mode = S_IFDIR; + buffer->st_nlink = 1; + } else if (type == POSIX_FILE_PIPE) { + buffer->st_mode = S_IFIFO; + } else { + buffer->st_mode = S_IFCHR; + } + } + + void CloneFileDescriptorTable(Process *forkProcess, HandleTable *handleTable) { + HandleTable *source = handleTable, + *destination = &forkProcess->handleTable; + KMutexAcquire(&source->lock); + EsDefer(KMutexRelease(&source->lock)); + HandleTableL1 *l1 = &source->l1r; + + for (uintptr_t i = 0; i < HANDLE_TABLE_L1_ENTRIES; i++) { + if (!l1->u[i]) continue; + HandleTableL2 *l2 = l1->t[i]; + + for (uintptr_t k = 0; k < HANDLE_TABLE_L2_ENTRIES; k++) { + Handle *handle = l2->t + k; + if (!handle->object) continue; + if (handle->type != KERNEL_OBJECT_POSIX_FD) continue; + if (handle->flags & FD_CLOEXEC) continue; + + POSIXFile *file = (POSIXFile *) handle->object; + OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, handle->flags); + destination->OpenHandle(handle->object, handle->flags, handle->type, k + i * HANDLE_TABLE_L2_ENTRIES); + } + } + } + + uintptr_t DoSyscall(_EsPOSIXSyscall syscall, uintptr_t *userStackPointer) { + Thread *currentThread = GetCurrentThread(); + Process *currentProcess = currentThread->process; + MMSpace *currentVMM = currentProcess->vmm; + HandleTable *handleTable = ¤tProcess->handleTable; + bool fromKernel = false; + + if (!currentThread->posixData) { + KMutexAcquire(&threadPOSIXDataMutex); + if (!currentThread->posixData) currentThread->posixData = (POSIXThread *) EsHeapAllocate(sizeof(POSIXThread), true, K_FIXED); + KMutexRelease(&threadPOSIXDataMutex); + } + + if (currentThread->posixData->forkProcess) { + handleTable = ¤tThread->posixData->forkProcess->handleTable; + } + + // EsPrint("%x, %x, %x, %x, %x, %x, %x, %x\n", syscall.index, syscall.arguments[0], syscall.arguments[1], syscall.arguments[2], + // syscall.arguments[3], syscall.arguments[4], syscall.arguments[5], syscall.arguments[6]); + + switch (syscall.index) { + case SYS_open: { + if (syscall.arguments[6] > SYSCALL_BUFFER_LIMIT) return -ENOMEM; + SYSCALL_BUFFER_POSIX(syscall.arguments[0], syscall.arguments[6], 1, false); + char *path2 = (char *) syscall.arguments[0]; + size_t pathLength = syscall.arguments[6]; + char *path = (char *) EsHeapAllocate(pathLength, false, K_FIXED); + if (!path) return -ENOMEM; + EsMemoryCopy(path, path2, pathLength); + EsDefer(EsHeapFree(path, 0, K_FIXED)); + int flags = syscall.arguments[1]; + uint64_t openFlags = ES_FLAGS_DEFAULT; + + POSIXFile *file = (POSIXFile *) EsHeapAllocate(sizeof(POSIXFile), true, K_FIXED); + if (!file) return -ENOMEM; + file->posixFlags = flags; + file->handles = 1; + + const char *devZero = "/dev/zero"; + const char *devNull = "/dev/null"; + + // EsPrint("Open: %s, %x\n", pathLength, path, flags); + + if ((EsCStringLength(devZero) == pathLength && 0 == EsMemoryCompare(path, devZero, pathLength))) { + file->type = POSIX_FILE_ZERO; + } else if (EsCStringLength(devNull) == pathLength && 0 == EsMemoryCompare(path, devNull, pathLength)) { + file->type = POSIX_FILE_NULL; + } else { + if (flags & O_EXCL) openFlags |= ES_NODE_FAIL_IF_FOUND; + else if (flags & O_CREAT) {} + else openFlags |= ES_NODE_FAIL_IF_NOT_FOUND; + if (flags & O_DIRECTORY) openFlags |= ES_NODE_DIRECTORY; + if (flags & O_APPEND) openFlags |= ES_FILE_WRITE; + else if (flags & O_RDWR) openFlags |= ES_FILE_WRITE; + else if (flags & O_WRONLY) openFlags |= ES_FILE_WRITE; + else if (!(flags & O_PATH)) openFlags |= ES_FILE_READ; + + KNodeInformation information = FSNodeOpen(path, pathLength, openFlags, (KNode *) syscall.arguments[4]); + + if (!information.node) { + EsHeapFree(file, sizeof(POSIXFile), K_FIXED); + return (information.error == ES_ERROR_FILE_DOES_NOT_EXIST || information.error == ES_ERROR_PATH_NOT_TRAVERSABLE) ? -ENOENT : -EACCES; + } + + if (information.node->directoryEntry->type == ES_NODE_DIRECTORY && !(flags & O_DIRECTORY) && !(flags & O_PATH)) { + EsHeapFree(file, sizeof(POSIXFile), K_FIXED); + CloseHandleToObject(information.node, KERNEL_OBJECT_NODE, openFlags); + return -EISDIR; + } + + file->node = information.node; + file->openFlags = openFlags; + file->type = file->node->directoryEntry->type == ES_NODE_DIRECTORY ? POSIX_FILE_DIRECTORY : POSIX_FILE_NORMAL; + } + + file->path = (char *) EsHeapAllocate(pathLength + 1, true, K_FIXED); + EsMemoryCopy(file->path, path, pathLength); + + EsHandle fd = handleTable->OpenHandle(file, (flags & O_CLOEXEC) ? FD_CLOEXEC : 0, KERNEL_OBJECT_POSIX_FD); + + // EsPrint("OPEN '%s' %z\n", pathLength, path, (openFlags & ES_NODE_WRITE_ACCESS) ? "Write" : ((openFlags & ES_NODE_READ_ACCESS) ? "Read" : "None")); + + if (!fd) { + if (file->type == POSIX_FILE_NORMAL || file->type == POSIX_FILE_DIRECTORY) { + CloseHandleToObject(file->node, KERNEL_OBJECT_NODE, openFlags); + } + + EsHeapFree(file->path, pathLength + 1, K_FIXED); + EsHeapFree(file, sizeof(POSIXFile), K_FIXED); + return -ENFILE; + } + + if ((flags & O_TRUNC) && file->type == POSIX_FILE_NORMAL) { + FSFileResize(file->node, 0); + } + + return fd; + } break; + + case ES_POSIX_SYSCALL_GET_POSIX_FD_PATH: { + if (syscall.arguments[2] > SYSCALL_BUFFER_LIMIT) return -ENOMEM; + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 2, true); + KMutexAcquire(&file->mutex); + EsDefer(KMutexRelease(&file->mutex)); + int length = EsCStringLength(file->path); + if (length > syscall.arguments[2]) length = syscall.arguments[2]; + EsMemoryZero((void *) syscall.arguments[1], syscall.arguments[2]); + EsMemoryCopy((void *) syscall.arguments[1], file->path, length); + return length; + } break; + + case SYS_close: { + if (!handleTable->CloseHandle(ConvertStandardInputTo3(syscall.arguments[0]))) { + return -EBADF; + } + + return 0; + } break; + + case SYS_fstat: { + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + SYSCALL_BUFFER_POSIX(syscall.arguments[1], sizeof(struct stat), 1, true); + struct stat temp; + KMutexAcquire(&file->mutex); + Stat(file->type, file->node, &temp); + KMutexRelease(&file->mutex); + EsMemoryCopy((struct stat *) syscall.arguments[1], &temp, sizeof(struct stat)); + return 0; + } break; + + case SYS_fcntl: { + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + + if (syscall.arguments[1] == F_GETFD) { + return _object1.flags; + } else if (syscall.arguments[1] == F_SETFD) { + KObject object; + uint64_t newFlags = syscall.arguments[2]; + handleTable->ResolveHandle(&object, syscall.arguments[0], &newFlags); + } else if (syscall.arguments[1] == F_GETFL) { + return file->posixFlags; + } else if (syscall.arguments[1] == F_DUPFD) { + OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags); + int fd = handleTable->OpenHandle(_object1.object, _object1.flags, _object1.type); + + if (!fd) { + CloseHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags); + return -EBUSY; + } else return fd; + } else if (syscall.arguments[1] == F_DUPFD_CLOEXEC) { + KObject object; + uint64_t newFlags = syscall.arguments[2]; + handleTable->ResolveHandle(&object, syscall.arguments[0], &newFlags); + + OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags); + int fd = handleTable->OpenHandle(_object1.object, _object1.flags, _object1.type); + + if (!fd) { + CloseHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags); + return -EBUSY; + } else return fd; + } else { + KernelPanic("POSIX::DoSyscall - Unimplemented fcntl %d.\n", syscall.arguments[1]); + } + } break; + + case SYS_lseek: { + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + + KMutexAcquire(&file->mutex); + EsDefer(KMutexRelease(&file->mutex)); + + if (syscall.arguments[2] == SEEK_SET) { + file->offsetIntoFile = syscall.arguments[1]; + } else if (syscall.arguments[2] == SEEK_CUR) { + file->offsetIntoFile += syscall.arguments[1]; + } else if (syscall.arguments[2] == SEEK_END && file->type == POSIX_FILE_NORMAL) { + file->offsetIntoFile = file->node->directoryEntry->totalSize + syscall.arguments[1]; + } else { + return -EINVAL; + } + + return file->offsetIntoFile; + } break; + + case SYS_ioctl: { + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + + KMutexAcquire(&file->mutex); + EsDefer(KMutexRelease(&file->mutex)); + + if (syscall.arguments[1] == TIOCGWINSZ) { + if (file->type == POSIX_FILE_TERMINAL || file->type == POSIX_FILE_PIPE) { + SYSCALL_BUFFER_POSIX(syscall.arguments[1], sizeof(struct winsize), 1, true); + struct winsize *size = (struct winsize *) syscall.arguments[2]; + size->ws_row = 80; + size->ws_col = 25; + size->ws_xpixel = 800; + size->ws_ypixel = 800; + return 0; + } else { + return -EINVAL; + } + } + } break; + + case SYS_read: { + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 3, false); + return Read(file, (void *) syscall.arguments[1], syscall.arguments[2], _region3->flags & MM_REGION_FILE); + } break; + + case SYS_readv: { + POSIXFile *file; + if (syscall.arguments[2] > 1024) return -EINVAL; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec *), 2, false); + + struct iovec *vectors = (struct iovec *) EsHeapAllocate(syscall.arguments[2] * sizeof(struct iovec), false, K_FIXED); + if (!vectors) return -ENOMEM; + EsMemoryCopy(vectors, (void *) syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec)); + EsDefer(EsHeapFree(vectors, syscall.arguments[2] * sizeof(struct iovec), K_FIXED)); + + size_t bytesRead = 0; + + for (uintptr_t i = 0; i < (uintptr_t) syscall.arguments[2]; i++) { + if (!vectors[i].iov_len) continue; + SYSCALL_BUFFER_POSIX((uintptr_t) vectors[i].iov_base, vectors[i].iov_len, 3, false); + intptr_t result = Read(file, vectors[i].iov_base, vectors[i].iov_len, _region3->flags & MM_REGION_FILE); + if (result < 0) return result; + bytesRead += result; + } + + return bytesRead; + } break; + + case SYS_write: { + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 3, true); + + if (file->type == POSIX_FILE_NORMAL && !(file->openFlags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE))) { + return -EACCES; + } + + return Write(file, (void *) syscall.arguments[1], syscall.arguments[2], _region3->flags & MM_REGION_FILE); + } break; + + case SYS_writev: { + POSIXFile *file; + if (syscall.arguments[2] > 1024) return -EINVAL; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec *), 2, false); + + struct iovec *vectors = (struct iovec *) EsHeapAllocate(syscall.arguments[2] * sizeof(struct iovec), false, K_FIXED); + if (!vectors) return -ENOMEM; + EsMemoryCopy(vectors, (void *) syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec)); + EsDefer(EsHeapFree(vectors, syscall.arguments[2] * sizeof(struct iovec), K_FIXED)); + + size_t bytesWritten = 0; + + if (file->type == POSIX_FILE_NORMAL && !(file->openFlags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE))) { + return -EACCES; + } + + for (uintptr_t i = 0; i < (uintptr_t) syscall.arguments[2]; i++) { + if (!vectors[i].iov_len) continue; + // EsPrint("writev %d: %x/%d\n", i, vectors[i].iov_base, vectors[i].iov_len); + SYSCALL_BUFFER_POSIX((uintptr_t) vectors[i].iov_base, vectors[i].iov_len, 3, true); + intptr_t result = Write(file, vectors[i].iov_base, vectors[i].iov_len, _region3->flags & MM_REGION_FILE); + if (result < 0) return result; + bytesWritten += result; + } + + return bytesWritten; + } break; + + case SYS_vfork: { + // To vfork: save the stack and return 0. + // To exec*: create the new process, restore the state of our stack, then return the new process's ID. + + KMutexAcquire(&forkMutex); + EsDefer(KMutexRelease(&forkMutex)); + + // Did we complete the last vfork? + if (currentThread->posixData->forkStack) return -ENOMEM; + + // EsPrint("vfork->\n"); + + // Allocate the process. + Process *forkProcess = currentThread->posixData->forkProcess = scheduler.SpawnProcess(); + forkProcess->pgid = currentProcess->pgid; + + // Clone our FDs. + CloneFileDescriptorTable(forkProcess, handleTable); + + // Save the state of the user's stack. + currentThread->posixData->forkStackSize = currentThread->userStackCommit; + currentThread->posixData->forkStack = (void *) EsHeapAllocate(currentThread->posixData->forkStackSize, false, K_PAGED); +#ifdef K_STACK_GROWS_DOWN + EsMemoryCopy(currentThread->posixData->forkStack, + (void *) (currentThread->userStackBase + currentThread->userStackReserve - currentThread->posixData->forkStackSize), + currentThread->posixData->forkStackSize); +#else + EsMemoryCopy(currentThread->posixData->forkStack, (void *) currentThread->userStackBase, currentThread->posixData->forkStackSize); +#endif + currentThread->posixData->forkUSP = *userStackPointer; + + // Return 0. + return 0; + } break; + + case SYS_execve: { + KMutexAcquire(&forkMutex); + EsDefer(KMutexRelease(&forkMutex)); + + // Are we vforking? + if (!currentThread->posixData->forkStack) return -ENOMEM; + if (syscall.arguments[1] > K_MAX_PATH) return -ENOMEM; + if (syscall.arguments[3] > SYSCALL_BUFFER_LIMIT) return -ENOMEM; + + // EsPrint("<-execve\n"); + + SYSCALL_BUFFER_POSIX(syscall.arguments[0], syscall.arguments[1], 1, false); + SYSCALL_BUFFER_POSIX(syscall.arguments[2], syscall.arguments[3], 2, false); + + // Setup the process object. + + char *path = (char *) EsHeapAllocate(syscall.arguments[1], false, K_FIXED); + if (!path) return -ENOMEM; + EsMemoryCopy(path, (void *) syscall.arguments[0], syscall.arguments[1]); + + Process *process = currentThread->posixData->forkProcess; + process->creationArguments[CREATION_ARGUMENT_ENVIRONMENT] = MakeConstantBuffer((void *) syscall.arguments[2], syscall.arguments[3], process); + process->posixForking = true; + process->permissions = currentProcess->permissions; + + EsMountPoint mountPoint = {}; + OpenHandleToObject((void *) syscall.arguments[4], KERNEL_OBJECT_NODE, _ES_NODE_DIRECTORY_WRITE); + mountPoint.base = process->handleTable.OpenHandle((void *) syscall.arguments[4], _ES_NODE_DIRECTORY_WRITE, KERNEL_OBJECT_NODE); + mountPoint.prefixBytes = EsStringFormat(mountPoint.prefix, sizeof(mountPoint.prefix), "|POSIX:"); + process->creationArguments[CREATION_ARGUMENT_INITIAL_MOUNT_POINTS] = MakeConstantBuffer(&mountPoint, sizeof(EsMountPoint), process); + + // Start the process. + + if (!process->Start(path, syscall.arguments[1])) { + EsHeapFree(path, 0, K_FIXED); + return -ENOMEM; + } + + KSpinlockAcquire(&scheduler.lock); + + process->posixForking = false; + + if (process->allThreadsTerminated) { + KEventSet(&process->killedEvent, true); + } + + KSpinlockRelease(&scheduler.lock); + + EsHeapFree(path, 0, K_FIXED); + CloseHandleToObject(process->executableMainThread, KERNEL_OBJECT_THREAD); + + // Restore the state of our stack. +#ifdef K_STACK_GROWS_DOWN + EsMemoryCopy((void *) (currentThread->userStackBase + currentThread->userStackReserve - currentThread->posixData->forkStackSize), + currentThread->posixData->forkStack, currentThread->posixData->forkStackSize); +#else + EsMemoryCopy((void *) currentThread->userStackBase, currentThread->posixData->forkStack, currentThread->posixData->forkStackSize); +#endif + EsHeapFree(currentThread->posixData->forkStack, currentThread->posixData->forkStackSize, K_PAGED); + *userStackPointer = currentThread->posixData->forkUSP; + + // Fork complete. + currentThread->posixData->forkProcess = nullptr; + currentThread->posixData->forkStack = nullptr; + currentThread->posixData->forkUSP = 0; + + if (!process) return -ENOMEM; + + EsHandle processHandle = currentProcess->handleTable.OpenHandle(process, 0, KERNEL_OBJECT_PROCESS); + if (!processHandle) CloseHandleToObject(process, KERNEL_OBJECT_PROCESS); + + return processHandle; + } break; + + case SYS_exit_group: { + EsHandle processHandle = 0; + + KMutexAcquire(&forkMutex); + + // Are we vforking? + if (currentThread->posixData->forkStack) { + // Restore the state of our stack. +#ifdef K_STACK_GROWS_DOWN + EsMemoryCopy((void *) (currentThread->userStackBase + currentThread->userStackReserve - currentThread->posixData->forkStackSize), + currentThread->posixData->forkStack, currentThread->posixData->forkStackSize); +#else + EsMemoryCopy((void *) currentThread->userStackBase, currentThread->posixData->forkStack, currentThread->posixData->forkStackSize); +#endif + EsHeapFree(currentThread->posixData->forkStack, currentThread->posixData->forkStackSize, K_PAGED); + *userStackPointer = currentThread->posixData->forkUSP; + + // Set the exit status. + Process *process = currentThread->posixData->forkProcess; + process->exitStatus = syscall.arguments[0]; + process->allThreadsTerminated = true; // Set in case execve() was not attempted. + KEventSet(&process->killedEvent); + + processHandle = currentProcess->handleTable.OpenHandle(currentThread->posixData->forkProcess, 0, KERNEL_OBJECT_PROCESS); + if (!processHandle) CloseHandleToObject(currentThread->posixData->forkProcess, KERNEL_OBJECT_PROCESS); + + // Close any handles the process owned. + process->handleTable.Destroy(); + + // Cancel the vfork. + currentThread->posixData->forkProcess = nullptr; + currentThread->posixData->forkStack = nullptr; + currentThread->posixData->forkUSP = 0; + } + + KMutexRelease(&forkMutex); + + if (processHandle) { + return processHandle; + } else { + scheduler.TerminateProcess(currentProcess, syscall.arguments[0]); + } + } break; + + case SYS_sysinfo: { + SYSCALL_BUFFER_POSIX(syscall.arguments[0], sizeof(struct sysinfo), 1, true); + struct sysinfo *buffer = (struct sysinfo *) syscall.arguments[0]; + EsMemoryZero(buffer, sizeof(struct sysinfo)); + + // TODO Incomplete. + buffer->totalram = K_PAGE_SIZE * pmm.commitFixedLimit; + buffer->freeram = K_PAGE_SIZE * (pmm.countZeroedPages + pmm.countFreePages); + buffer->procs = scheduler.allProcesses.count; + buffer->mem_unit = 1; + + return 0; + } break; + + case SYS_dup2: { + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + + // Try to close the newfd. + + handleTable->CloseHandle(ConvertStandardInputTo3(syscall.arguments[1])); + + // Clone the oldfd as newfd. + + OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags); + + if (!handleTable->OpenHandle(_object1.object, _object1.flags, _object1.type, ConvertStandardInputTo3(syscall.arguments[1]))) { + CloseHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags); + return -EBUSY; + } else return 0; + } break; + + case SYS_pipe2: { + if (syscall.arguments[1] & ~O_CLOEXEC) { + return -EINVAL; + } + + SYSCALL_BUFFER_POSIX(syscall.arguments[0], sizeof(int) * 2, 1, true); + int *fildes = (int *) syscall.arguments[0]; + + Pipe *pipe = (Pipe *) EsHeapAllocate(sizeof(Pipe), true, K_PAGED); + POSIXFile *reader = (POSIXFile *) EsHeapAllocate(sizeof(POSIXFile), true, K_FIXED); + POSIXFile *writer = (POSIXFile *) EsHeapAllocate(sizeof(POSIXFile), true, K_FIXED); + + if (!reader || !writer || !pipe) { + EsHeapFree(pipe, sizeof(Pipe), K_PAGED); + EsHeapFree(reader, 0, K_FIXED); + EsHeapFree(writer, 0, K_FIXED); + return -ENOMEM; + } + + KEventSet(&pipe->canWrite); + + reader->type = POSIX_FILE_PIPE; + reader->openFlags = PIPE_READER; + reader->handles = 1; + reader->pipe = pipe; + + writer->type = POSIX_FILE_PIPE; + writer->openFlags = PIPE_WRITER; + writer->handles = 1; + writer->pipe = pipe; + + EsHandle fd0, fd1; + fd0 = handleTable->OpenHandle(reader, (syscall.arguments[1] & O_CLOEXEC) ? FD_CLOEXEC : 0, KERNEL_OBJECT_POSIX_FD); + + if (!fd0) { + EsHeapFree(pipe, sizeof(Pipe), K_PAGED); + EsHeapFree(reader, 0, K_FIXED); + EsHeapFree(writer, 0, K_FIXED); + return -EMFILE; + } + + pipe->readers = 1; + + fd1 = handleTable->OpenHandle(writer, (syscall.arguments[1] & O_CLOEXEC) ? FD_CLOEXEC : 0, KERNEL_OBJECT_POSIX_FD); + + if (!fd1) { + handleTable->CloseHandle(ConvertStandardInputTo3(fd0)); + EsHeapFree(writer, 0, K_FIXED); + return -EMFILE; + } + + pipe->writers = 1; + + fildes[0] = fd0, fildes[1] = fd1; + return 0; + } break; + + case SYS_getdents64: { + // TODO This is a bit of a hack. + // Especially allocating the entire directory list in the kernel's memory space. + + if (syscall.arguments[2] > SYSCALL_BUFFER_LIMIT) return -ENOMEM; + + POSIXFile *file; + SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1); + SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 3, true); + + KMutexAcquire(&file->mutex); + EsDefer(KMutexRelease(&file->mutex)); + + if (file->type != POSIX_FILE_DIRECTORY) { + return -EACCES; + } + + if (!file->offsetIntoFile || !file->directoryBuffer) { + EsHeapFree(file->directoryBuffer, file->directoryBufferLength, K_PAGED); + + file->directoryBuffer = nullptr; + file->offsetIntoFile = 0; + file->directoryBufferLength = 0; + + uintptr_t bufferSize = file->node->directoryEntry->directoryChildren; + EsDirectoryChild *buffer = (EsDirectoryChild *) EsHeapAllocate(sizeof(EsDirectoryChild) * bufferSize, false, K_FIXED); + + if (!buffer) { + return -ENOMEM; + } + + size_t count = FSDirectoryEnumerateChildren(file->node, buffer, bufferSize); + + if (ES_CHECK_ERROR(count)) { + EsHeapFree(buffer, sizeof(EsDirectoryChild) * bufferSize, K_FIXED); + return -EIO; + } + + size_t neededSize = 0; + + for (uintptr_t i = 0; i < count; i++) { + neededSize += RoundUp(19 + buffer[i].nameBytes + 1, (size_t) 8); + } + + file->directoryBuffer = EsHeapAllocate(neededSize, true, K_PAGED); + + if (!file->directoryBuffer) { + EsHeapFree(buffer, bufferSize * sizeof(EsDirectoryChild), K_FIXED); + return -ENOMEM; + } + + file->directoryBufferLength = neededSize; + + uint8_t *position = (uint8_t *) file->directoryBuffer; + + for (uintptr_t i = 0; i < count; i++) { + // EsPrint("%d - %d\n", i, position - (uint8_t *) file->directoryBuffer); + size_t size = RoundUp(19 + buffer[i].nameBytes + 1, (size_t) 8); + EsDirectoryChild *entry = buffer + i; + ((uint64_t *) position)[0] = EsRandomU64(); // We don't have a concept of inodes. + ((uint64_t *) position)[1] = position - (uint8_t *) file->directoryBuffer + size; + ((uint16_t *) position)[8] = size; + ((uint8_t *) position)[18] = entry->type == ES_NODE_DIRECTORY ? 4 /* DT_DIR */ + : entry->type == ES_NODE_FILE ? 8 /* DT_REG */ : 0 /* DT_UNKNOWN */; + EsMemoryCopy(position + 19, entry->name, entry->nameBytes); + *(position + 19 + entry->nameBytes) = 0; + position += size; + } + + EsHeapFree(buffer, bufferSize * sizeof(EsDirectoryChild), K_FIXED); + } + + uint64_t offset = file->offsetIntoFile; + size_t bufferSize = syscall.arguments[2]; + + while (offset + 19 < file->directoryBufferLength) { + uint8_t *position = (uint8_t *) file->directoryBuffer + offset; + uint64_t nextOffset = ((uint64_t *) position)[1]; + + if (nextOffset > file->directoryBufferLength || nextOffset < offset + 19 + || nextOffset - file->offsetIntoFile >= bufferSize) { + break; + } + + offset = nextOffset; + } + + size_t bytesToReturn = offset - file->offsetIntoFile; + + if (bytesToReturn > bufferSize) { + bytesToReturn = bufferSize; + } + + EsMemoryCopy((void *) syscall.arguments[1], (uint8_t *) file->directoryBuffer + file->offsetIntoFile, bytesToReturn); + file->offsetIntoFile += bytesToReturn; + return bytesToReturn; + } break; + + case SYS_setpgid: { + Process *process = (Process *) syscall.arguments[0]; + process->pgid = syscall.arguments[1]; + return 0; + } break; + } + + return -1; + } +}; + +#endif diff --git a/kernel/scheduler.cpp b/kernel/scheduler.cpp new file mode 100644 index 0000000..7128be4 --- /dev/null +++ b/kernel/scheduler.cpp @@ -0,0 +1,1423 @@ +#ifndef IMPLEMENTATION + +#define PREEMPT_AFTER_MUTEX_RELEASE + +#define THREAD_PRIORITY_NORMAL (0) // Lower value = higher priority. +#define THREAD_PRIORITY_LOW (1) +#define THREAD_PRIORITY_COUNT (2) + +void CloseHandleToThread(void *_thread); +void CloseHandleToProcess(void *_thread); +void KillThread(EsGeneric _thread); + +void RegisterAsyncTask(KAsyncTaskCallback callback, EsGeneric argument, struct Process *targetProcess, + bool needed /*If false, the task may not be registered if there are many queued tasks.*/, + bool unlocked = false /*Set to true if you haven't acquired the scheduler's lock.*/); + +enum ThreadState : int8_t { + THREAD_ACTIVE, // An active thread. Not necessarily executing; `executing` determines if it executing. + THREAD_WAITING_MUTEX, // Waiting for a mutex to be released. + THREAD_WAITING_EVENT, // Waiting for a event to be notified. + THREAD_WAITING_WRITER_LOCK, // Waiting for a writer lock to be notified. + THREAD_TERMINATED, // The thread has been terminated. It will be deallocated when all handles are closed. + // I believe this is called a "zombie thread" in UNIX terminology. +}; + +enum ThreadType : int8_t { + THREAD_NORMAL, // A normal thread. + THREAD_IDLE, // The CPU's idle thread. + THREAD_ASYNC_TASK, // A thread that processes the CPU's asynchronous tasks. +}; + +enum ThreadTerminatableState : int8_t { + THREAD_INVALID_TS, + THREAD_TERMINATABLE, // The thread is currently executing user code. + THREAD_IN_SYSCALL, // The thread is currently executing kernel code from a system call. + // It cannot be terminated/paused until it returns from the system call. + THREAD_USER_BLOCK_REQUEST, // The thread is sleeping because of a user system call to sleep. + // It can be unblocked, and then terminated when it returns from the system call. +}; + +struct Thread { + void SetAddressSpace(MMSpace *space); // Set a temporary address space for the thread. + // Used by the asynchronous task threads, + // and the memory manager's balancer. + + LinkedItem item; // Entry in relevent thread queue or blockedThreads list for mutexes/writer locks. + LinkedItem allItem; // Entry in the allThreads list. + LinkedItem processItem; // Entry in the process's list of threads. + LinkedItem *blockedItems; // Entries in the blockedThreads lists for events (not mutexes). + + struct Process *process; + + uint64_t id; + volatile uintptr_t cpuTimeSlices; + volatile size_t handles; + int executingProcessorID; + + uintptr_t userStackBase; + uintptr_t kernelStackBase; + uintptr_t kernelStack; + uintptr_t tlsAddress; + size_t userStackReserve; + volatile size_t userStackCommit; + + ThreadType type; + bool isKernelThread, isPageGenerator; + int8_t priority; + int32_t blockedThreadPriorities[THREAD_PRIORITY_COUNT]; // The number of threads blocking on this thread at each priority level. + + volatile ThreadState state; + volatile ThreadTerminatableState terminatableState; + volatile bool executing; + volatile bool terminating; // Set when a request to terminate the thread has been registered. + volatile bool paused; // Set to pause a thread, usually when it has crashed or being debugged. The scheduler will skip threads marked as paused when deciding what to run. + volatile bool receivedYieldIPI; // Used to terminate a thread executing on a different processor. + + union { + KMutex *volatile mutex; + + struct { + KWriterLock *volatile writerLock; + bool writerLockType; + }; + + struct { + KEvent *volatile events[ES_MAX_WAIT_COUNT]; + volatile size_t eventCount; + }; + } blocking; + + KEvent killedEvent; + + // If the type of the thread is THREAD_ASYNC_TASK, + // then this is the virtual address space that should be loaded + // when the task is being executed. + MMSpace *volatile temporaryAddressSpace; + + InterruptContext *interruptContext; // TODO Store the userland interrupt context instead? + uintptr_t lastKnownExecutionAddress; // For debugging. + +#ifdef ENABLE_POSIX_SUBSYSTEM + struct POSIXThread *posixData; +#endif + + const char *cName; +}; + +enum ProcessType { + PROCESS_NORMAL, + PROCESS_KERNEL, + PROCESS_DESKTOP, +}; + +struct Process { + bool StartWithNode(KNode *node); + bool Start(char *imagePath, size_t imagePathLength); + + MMSpace *vmm; + HandleTable handleTable; + MessageQueue messageQueue; + LinkedList threads; + + // Creation information: + KNode *executableNode; + char cExecutableName[ES_SNAPSHOT_MAX_PROCESS_NAME_LENGTH + 1]; + uintptr_t creationArguments[4]; + uint64_t permissions; + uint64_t creationFlags; + ProcessType type; + + // Object management: + uint64_t id; + volatile size_t handles; + LinkedItem allItem; + + // Crashing: + KMutex crashMutex; + EsCrashReason crashReason; + bool crashed; + + // Termination: + bool allThreadsTerminated; + bool terminating; + int exitStatus; + KEvent killedEvent; + + // Executable state: + uint8_t executableState; + bool executableStartRequest; + KEvent executableLoadAttemptComplete; + Thread *executableMainThread; + + // Statistics: + uintptr_t cpuTimeSlices, idleTimeSlices; + +#ifdef ENABLE_POSIX_SUBSYSTEM + bool posixForking; + int pgid; +#endif +}; + +Process _kernelProcess; +Process *kernelProcess = &_kernelProcess; + +struct Scheduler { + // External API: + + void Yield(InterruptContext *context); + +#define SPAWN_THREAD_MANUALLY_ACTIVATED (1) +#define SPAWN_THREAD_USERLAND (2) +#define SPAWN_THREAD_LOW_PRIORITY (4) +#define SPAWN_THREAD_PAUSED (8) + Thread *SpawnThread(const char *cName, uintptr_t startAddress, uintptr_t argument1 = 0, + unsigned flags = ES_FLAGS_DEFAULT, Process *process = kernelProcess, uintptr_t argument2 = 0); + void TerminateThread(Thread *thread, bool lockAlreadyAcquired = false); + void PauseThread(Thread *thread, bool resume /*true to resume, false to pause*/, bool lockAlreadyAcquired = false); + + Process *SpawnProcess(ProcessType processType = PROCESS_NORMAL); + void TerminateProcess(Process *process, int status); + void PauseProcess(Process *process, bool resume); + void CrashProcess(Process *process, EsCrashReason *reason); + + Process *OpenProcess(uint64_t id); + + void WaitMutex(KMutex *mutex); + uintptr_t WaitEvents(KEvent **events, size_t count); // Returns index of notified object. + + void Shutdown(); + + // Internal functions: + + void CreateProcessorThreads(CPULocalStorage *local); + + void RemoveProcess(Process *process); // Do not call. Use TerminateProcess/CloseHandleToObject. + void RemoveThread(Thread *thread); // Do not call. Use TerminateThread/CloseHandleToObject. + void AddActiveThread(Thread *thread, bool start /*Put it at the start*/); // Add an active thread into the queue. + void InsertNewThread(Thread *thread, bool addToActiveList, Process *owner); // Used during thread creation. + void MaybeUpdateActiveList(Thread *thread); // After changing the priority of a thread, call this to move it to the correct active thread queue if needed. + + void NotifyObject(LinkedList *blockedThreads, bool unblockAll, Thread *previousMutexOwner = nullptr); + void UnblockThread(Thread *unblockedThread, Thread *previousMutexOwner = nullptr); + + Thread *PickThread(CPULocalStorage *local); // Pick the next thread to execute. + int8_t GetThreadEffectivePriority(Thread *thread); + + // Variables: + + KSpinlock lock; + + KEvent killedEvent; // Set during shutdown when all processes have been terminated. + uintptr_t blockShutdownProcessCount; + + LinkedList activeThreads[THREAD_PRIORITY_COUNT], pausedThreads; + LinkedList activeTimers; + + Pool threadPool, processPool, mmSpacePool; + LinkedList allThreads; + LinkedList allProcesses; + uint64_t nextThreadID; + uint64_t nextProcessID; + size_t activeProcessCount; + + volatile bool started, panic, shutdown; + + uint64_t timeMs, lastTimeStamp; + + unsigned currentProcessorID; + +#ifdef DEBUG_BUILD + EsThreadEventLogEntry *volatile threadEventLog; + volatile uintptr_t threadEventLogPosition; + volatile size_t threadEventLogAllocated; +#endif +}; + +extern Scheduler scheduler; + +#endif + +#ifdef IMPLEMENTATION + +Scheduler scheduler; + +int8_t Scheduler::GetThreadEffectivePriority(Thread *thread) { + KSpinlockAssertLocked(&lock); + + for (int8_t i = 0; i < thread->priority; i++) { + if (thread->blockedThreadPriorities[i]) { + // A thread is blocking on a resource owned by this thread, + // and the blocking thread has a higher priority than this thread. + // Therefore, this thread should assume that higher priority, + // until it releases the resource. + return i; + } + } + + return thread->priority; +} + +void Scheduler::AddActiveThread(Thread *thread, bool start) { + if (thread->type == THREAD_ASYNC_TASK) { + // An asynchronous task thread was unblocked. + // It will be run immediately, so there's no need to add it to the active thread list. + return; + } + + KSpinlockAssertLocked(&lock); + + if (thread->state != THREAD_ACTIVE) { + KernelPanic("Scheduler::AddActiveThread - Thread %d not active\n", thread->id); + } else if (thread->executing) { + KernelPanic("Scheduler::AddActiveThread - Thread %d executing\n", thread->id); + } else if (thread->type != THREAD_NORMAL) { + KernelPanic("Scheduler::AddActiveThread - Thread %d has type %d\n", thread->id, thread->type); + } else if (thread->item.list) { + KernelPanic("Scheduler::AddActiveThread - Thread %d is already in queue %x.\n", thread->id, thread->item.list); + } + + if (thread->paused && thread->terminatableState == THREAD_TERMINATABLE) { + // The thread is paused, so we can put it into the paused queue until it is resumed. + pausedThreads.InsertStart(&thread->item); + } else { + int8_t effectivePriority = GetThreadEffectivePriority(thread); + + if (start) { + activeThreads[effectivePriority].InsertStart(&thread->item); + } else { + activeThreads[effectivePriority].InsertEnd(&thread->item); + } + } +} + +void Scheduler::MaybeUpdateActiveList(Thread *thread) { + // TODO Is this correct with regards to paused threads? + + if (thread->type == THREAD_ASYNC_TASK) { + // Asynchronous task threads do not go in the activeThreads lists. + return; + } + + if (thread->type != THREAD_NORMAL) { + KernelPanic("Scheduler::MaybeUpdateActiveList - Trying to update the active list of a non-normal thread %x.\n", thread); + } + + KSpinlockAssertLocked(&lock); + + if (thread->state != THREAD_ACTIVE || thread->executing) { + // The thread is not currently in an active list, + // so it'll end up in the correct activeThreads list when it becomes active. + return; + } + + if (!thread->item.list) { + KernelPanic("Scheduler::MaybeUpdateActiveList - Despite thread %x being active and not executing, it is not in an activeThreads lists.\n", thread); + } + + int8_t effectivePriority = GetThreadEffectivePriority(thread); + + if (&activeThreads[effectivePriority] == thread->item.list) { + // The thread's effective priority has not changed. + // We don't need to do anything. + return; + } + + // Remove the thread from its previous active list. + thread->item.RemoveFromList(); + + // Add it to the start of its new active list. + // TODO I'm not 100% sure we want to always put it at the start. + activeThreads[effectivePriority].InsertStart(&thread->item); +} + +void Scheduler::InsertNewThread(Thread *thread, bool addToActiveList, Process *owner) { + KSpinlockAcquire(&lock); + EsDefer(KSpinlockRelease(&lock)); + + // New threads are initialised here. + thread->id = nextThreadID++; + thread->process = owner; + + owner->handles++; // Each thread owns a handles to the owner process. + // This makes sure the process isn't destroyed before all its threads have been destroyed. + // EsPrint("Open handle to process %d/%x (new thread). New handle count: %d.\n", owner->id, owner, owner->handles); + + thread->item.thisItem = thread; + thread->allItem.thisItem = thread; + thread->processItem.thisItem = thread; + owner->threads.InsertEnd(&thread->processItem); + + KernelLog(LOG_INFO, "Scheduler", "create thread", "Create thread ID %d, type %d, owner process %d\n", thread->id, thread->type, owner->id); + + if (addToActiveList) { + // Add the thread to the start of the active thread list to make sure that it runs immediately. + AddActiveThread(thread, true); + } else { + // Some threads (such as idle threads) do this themselves. + } + + allThreads.InsertStart(&thread->allItem); +} + +Thread *Scheduler::SpawnThread(const char *cName, uintptr_t startAddress, uintptr_t argument1, unsigned flags, Process *process, uintptr_t argument2) { + bool userland = flags & SPAWN_THREAD_USERLAND; + + if (userland && process == kernelProcess) { + KernelPanic("Scheduler::SpawnThread - Cannot add userland thread to kernel process.\n"); + } + + KSpinlockAcquire(&scheduler.lock); + bool terminating = process->terminating; + KSpinlockRelease(&scheduler.lock); + + if (shutdown && userland) return nullptr; + if (terminating) return nullptr; + + Thread *thread = (Thread *) threadPool.Add(sizeof(Thread)); + KernelLog(LOG_INFO, "Scheduler", "spawn thread", "Created thread, %x to start at %x\n", thread, startAddress); + thread->isKernelThread = !userland; + thread->priority = (flags & SPAWN_THREAD_LOW_PRIORITY) ? THREAD_PRIORITY_LOW : THREAD_PRIORITY_NORMAL; + thread->cName = cName; + + // 2 handles to the thread: + // One for spawning the thread, + // and the other for remaining during the thread's life. + thread->handles = 2; + + // Allocate the thread's stacks. + uintptr_t kernelStackSize = userland ? 0x4000 /* 16KB */ : 0x10000 /* 64KB */; + uintptr_t userStackReserve = userland ? 0x400000 /* 4MB */ : kernelStackSize; + uintptr_t userStackCommit = userland ? 0x20000 /* 128KB */ : 0; + uintptr_t stack = 0, kernelStack = (uintptr_t) MMStandardAllocate(kernelMMSpace, kernelStackSize, MM_REGION_FIXED); + + if (!kernelStack) goto fail; + + if (userland) { + stack = (uintptr_t) MMStandardAllocate(process->vmm, userStackReserve, ES_FLAGS_DEFAULT, nullptr, false); + + MMRegion *region = MMFindAndPinRegion(process->vmm, stack, userStackReserve); + KMutexAcquire(&process->vmm->reserveMutex); +#ifdef K_STACK_GROWS_DOWN + bool success = MMCommitRange(process->vmm, region, (userStackReserve - userStackCommit) / K_PAGE_SIZE, userStackCommit / K_PAGE_SIZE); +#else + bool success = MMCommitRange(process->vmm, region, 0, userStackCommit / K_PAGE_SIZE); +#endif + KMutexRelease(&process->vmm->reserveMutex); + MMUnpinRegion(process->vmm, region); + if (!success) goto fail; + } else { + stack = kernelStack; + } + + if (!stack) goto fail; + + KernelLog(LOG_INFO, "Scheduler", "thread stacks", + "Spawning thread with stacks (k,u): %x->%x, %x->%x\n", kernelStack, kernelStack + kernelStackSize, stack, stack + userStackReserve); + + thread->kernelStackBase = kernelStack; + thread->userStackBase = userland ? stack : 0; + thread->userStackReserve = userStackReserve; + thread->userStackCommit = userStackCommit; + thread->paused = flags & SPAWN_THREAD_PAUSED; + thread->terminatableState = userland ? THREAD_TERMINATABLE : THREAD_IN_SYSCALL; + thread->interruptContext = ArchInitialiseThread(kernelStack, kernelStackSize, thread, + startAddress, argument1, argument2, + userland, stack, userStackReserve); + + InsertNewThread(thread, ~flags & SPAWN_THREAD_MANUALLY_ACTIVATED, process); + return thread; + + fail:; + if (stack) MMFree(process->vmm, (void *) stack); + if (kernelStack) MMFree(kernelMMSpace, (void *) kernelStack); + threadPool.Remove(thread); + return nullptr; +} + +void Scheduler::TerminateProcess(Process *process, int status) { + KSpinlockAcquire(&scheduler.lock); + + KernelLog(LOG_INFO, "Scheduler", "terminate process", "Terminating process %d '%z' with status %i...\n", + process->id, process->cExecutableName, status); + process->exitStatus = status; + process->terminating = true; + + Thread *currentThread = GetCurrentThread(); + bool isCurrentProcess = process == currentThread->process; + bool foundCurrentThread = false; + + LinkedItem *thread = process->threads.firstItem; + + while (thread) { + Thread *threadObject = thread->thisItem; + thread = thread->nextItem; + + if (threadObject != currentThread) { + TerminateThread(threadObject, true); + } else if (isCurrentProcess) { + foundCurrentThread = true; + } else { + KernelPanic("Scheduler::TerminateProcess - Found current thread in the wrong process?!\n"); + } + } + + if (!foundCurrentThread && isCurrentProcess) { + KernelPanic("Scheduler::TerminateProcess - Could not find current thread in the current process?!\n"); + } else if (isCurrentProcess) { + // This doesn't return. + TerminateThread(currentThread, true); + } + + KSpinlockRelease(&scheduler.lock); + ProcessorFakeTimerInterrupt(); // Process the asynchronous tasks. +} + +void Scheduler::TerminateThread(Thread *thread, bool terminatingProcess) { + // Overview: + // Set terminating true, and paused false. + // Is this the current thread? + // Mark as terminatable, then yield. + // Else, is thread->terminating not set? + // Set thread->terminating. + // + // Is this the current thread? + // Mark as terminatable, then yield. + // Else, are we executing user code? + // If we aren't currently executing the thread, remove the thread from its scheduling queue and kill it. + // Else, is the user waiting on a mutex/event? + // If we aren't currently executing the thread, unblock the thread. + + if (!terminatingProcess) { + KSpinlockAcquire(&scheduler.lock); + } else { + KSpinlockAssertLocked(&scheduler.lock); + } + + bool yield = false; + + if (thread->terminating) { + KernelLog(LOG_INFO, "Scheduler", "thread already terminating", "Already terminating thread %d.\n", thread->id); + if (thread == GetCurrentThread()) goto terminateThisThread; + else goto done; + } + + KernelLog(LOG_INFO, "Scheduler", "terminate thread", "Terminating thread %d...\n", thread->id); + thread->terminating = true; + thread->paused = false; + + if (thread == GetCurrentThread()) { + terminateThisThread:; + + // Mark the thread as terminatable. + thread->terminatableState = THREAD_TERMINATABLE; + KSpinlockRelease(&scheduler.lock); + + // We cannot return to the previous function as it expects to be killed. + ProcessorFakeTimerInterrupt(); + KernelPanic("Scheduler::TerminateThread - ProcessorFakeTimerInterrupt returned.\n"); + } else { + if (thread->terminatableState == THREAD_TERMINATABLE) { + // We're in user code.. + + if (thread->executing) { + // The thread is executing, so the next time it tries to make a system call or + // is pre-empted, it will be terminated. + } else { + if (thread->state != THREAD_ACTIVE) { + KernelPanic("Scheduler::TerminateThread - Terminatable thread non-active.\n"); + } + + // The thread is terminatable and it isn't executing. + // Remove it from its queue, and then remove the thread. + thread->item.RemoveFromList(); + RegisterAsyncTask(KillThread, thread, thread->process, true); + yield = true; + } + } else if (thread->terminatableState == THREAD_USER_BLOCK_REQUEST) { + // We're in the kernel, but because the user wanted to block on a mutex/event. + + if (thread->executing) { + // The mutex and event waiting code is designed to recognise when a thread is in this state, + // and exit to the system call handler immediately. + // If the thread however is pre-empted while in a blocked state before this code can execute, + // Scheduler::Yield will automatically force the thread to be active again. + } else { + // Unblock the thread. + // See comment above. + if (thread->state == THREAD_WAITING_MUTEX || thread->state == THREAD_WAITING_EVENT) { + UnblockThread(thread); + } + } + } else { + // The thread is executing kernel code. + // Therefore, we can't simply terminate the thread. + // The thread will set its state to THREAD_TERMINATABLE whenever it can be terminated. + } + } + + done:; + + if (!terminatingProcess) { + KSpinlockRelease(&scheduler.lock); + if (yield) ProcessorFakeTimerInterrupt(); // Process the asynchronous task. + } +} + +void NewProcess() { + Process *thisProcess = GetCurrentThread()->process; + + KernelLog(LOG_INFO, "Scheduler", "new process", + "New process %d %x, '%z'.\n", thisProcess->id, thisProcess, thisProcess->cExecutableName); + + KLoadedExecutable api = {}; + EsError loadError = ES_SUCCESS; + + { + uint64_t fileFlags = ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND; + KNodeInformation node = FSNodeOpen(EsLiteral(K_DESKTOP_EXECUTABLE), fileFlags); + loadError = node.error; + + if (node.error == ES_SUCCESS) { + if (node.node->directoryEntry->type != ES_NODE_FILE) { + loadError = ES_ERROR_INCORRECT_NODE_TYPE; + } else { + loadError = KLoadELF(node.node, &api); + } + + CloseHandleToObject(node.node, KERNEL_OBJECT_NODE, fileFlags); + } + } + + KLoadedExecutable application = {}; + + if (thisProcess != desktopProcess && loadError == ES_SUCCESS) { + loadError = KLoadELF(thisProcess->executableNode, &application); + } + + bool success = loadError == ES_SUCCESS; + + if (success) { + // We "link" the API by putting its table of function pointers at a known address. + + MMSharedRegion *tableRegion = MMSharedOpenRegion(EsLiteral("Desktop.APITable"), 0xF000, ES_FLAGS_DEFAULT); + // TODO Write protection. + + if (!MMMapShared(thisProcess->vmm, tableRegion, 0, 0xF000, ES_FLAGS_DEFAULT, ES_API_BASE)) { + success = false; + } + } + + EsProcessStartupInformation *startupInformation; + + if (success) { + startupInformation = (EsProcessStartupInformation *) MMStandardAllocate( + thisProcess->vmm, sizeof(EsProcessStartupInformation), ES_FLAGS_DEFAULT); + + if (!startupInformation) { + success = false; + } else { + startupInformation->isDesktop = thisProcess == desktopProcess; + startupInformation->applicationStartAddress = application.startAddress; + startupInformation->tlsImageStart = application.tlsImageStart; + startupInformation->tlsImageBytes = application.tlsImageBytes; + startupInformation->tlsBytes = application.tlsBytes; + } + } + + if (success) { + uint64_t threadFlags = SPAWN_THREAD_USERLAND; + if (thisProcess->creationFlags & ES_PROCESS_CREATE_PAUSED) threadFlags |= SPAWN_THREAD_PAUSED; + + thisProcess->executableState = ES_PROCESS_EXECUTABLE_LOADED; + thisProcess->executableMainThread = scheduler.SpawnThread("MainThread", api.startAddress, + (uintptr_t) startupInformation, threadFlags, thisProcess); + + if (!thisProcess->executableMainThread) { + success = false; + } + } + + if (!success) { + if (thisProcess->type != PROCESS_NORMAL) { + KernelPanic("NewProcess - Failed to start the critical process %z.\n", thisProcess->cExecutableName); + } + + thisProcess->executableState = ES_PROCESS_EXECUTABLE_FAILED_TO_LOAD; + } + + KEventSet(&thisProcess->executableLoadAttemptComplete); +} + +bool Process::Start(char *imagePath, size_t imagePathLength) { + uint64_t flags = ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND; + KNodeInformation node = FSNodeOpen(imagePath, imagePathLength, flags); + + if (ES_CHECK_ERROR(node.error)) { + return false; + } + + if (node.node->directoryEntry->type != ES_NODE_FILE) { + CloseHandleToObject(node.node, KERNEL_OBJECT_NODE, flags); + return false; + } + + bool result = StartWithNode(node.node); + CloseHandleToObject(node.node, KERNEL_OBJECT_NODE, flags); + return result; +} + +bool Process::StartWithNode(KNode *node) { + KSpinlockAcquire(&scheduler.lock); + + if (executableStartRequest) { + KSpinlockRelease(&scheduler.lock); + return false; + } + + executableStartRequest = true; + + KSpinlockRelease(&scheduler.lock); + + KWriterLockTake(&node->writerLock, K_LOCK_SHARED); + size_t byteCount = node->directoryEntry->item.key.longKeyBytes; + if (byteCount > ES_SNAPSHOT_MAX_PROCESS_NAME_LENGTH) byteCount = ES_SNAPSHOT_MAX_PROCESS_NAME_LENGTH; + EsMemoryCopy(cExecutableName, node->directoryEntry->item.key.longKey, byteCount); + cExecutableName[byteCount] = 0; + KWriterLockReturn(&node->writerLock, K_LOCK_SHARED); + + bool success = MMSpaceInitialise(vmm); + if (!success) return false; + + // NOTE If you change these flags, make sure to update the flags when the handle is closed! + + if (!OpenHandleToObject(node, KERNEL_OBJECT_NODE, ES_FILE_READ)) { + KernelPanic("Process::StartWithNode - Could not open read handle to node %x.\n", node); + } + + executableNode = node; + + KSpinlockAcquire(&scheduler.lock); + scheduler.allProcesses.InsertEnd(&allItem); + scheduler.activeProcessCount++; + scheduler.blockShutdownProcessCount++; + KSpinlockRelease(&scheduler.lock); + + Thread *newProcessThread = scheduler.SpawnThread("NewProcess", (uintptr_t) NewProcess, 0, ES_FLAGS_DEFAULT, this); + + if (!newProcessThread) { + CloseHandleToObject(this, KERNEL_OBJECT_PROCESS); + return false; + } + + CloseHandleToObject(newProcessThread, KERNEL_OBJECT_THREAD); + KEventWait(&executableLoadAttemptComplete, ES_WAIT_NO_TIMEOUT); + + if (executableState == ES_PROCESS_EXECUTABLE_FAILED_TO_LOAD) { + KernelLog(LOG_ERROR, "Scheduler", "executable load failure", "Executable failed to load.\n"); + return false; + } + + return true; +} + +Process *Scheduler::SpawnProcess(ProcessType processType) { + if (shutdown) return nullptr; + + Process *process = processType == PROCESS_KERNEL ? kernelProcess : (Process *) processPool.Add(sizeof(Process)); + + if (!process) { + return nullptr; + } + + process->vmm = processType == PROCESS_KERNEL ? kernelMMSpace : (MMSpace *) mmSpacePool.Add(sizeof(MMSpace)); + + if (!process->vmm) { + processPool.Remove(process); + return nullptr; + } + + KSpinlockAcquire(&scheduler.lock); + process->id = nextProcessID++; + KSpinlockRelease(&scheduler.lock); + + process->allItem.thisItem = process; + process->handles = 1; + process->handleTable.process = process; + process->permissions = ES_PERMISSION_ALL; + process->type = processType; + + if (processType == PROCESS_KERNEL) { + EsCRTstrcpy(process->cExecutableName, "Kernel"); + scheduler.allProcesses.InsertEnd(&process->allItem); + MMInitialise(); + scheduler.SpawnThread("InitKernel", (uintptr_t) KernelInitialise); + } + + return process; +} + +void Thread::SetAddressSpace(MMSpace *space) { + temporaryAddressSpace = space; + + if (this == GetCurrentThread()) { + ProcessorSetAddressSpace(VIRTUAL_ADDRESS_SPACE_IDENTIFIER(space ? space : kernelMMSpace)); + } +} + +void AsyncTaskThread() { + CPULocalStorage *local = GetLocalStorage(); + + while (true) { + if (local->asyncTasksRead == local->asyncTasksWrite) { + ProcessorFakeTimerInterrupt(); + } else { + volatile AsyncTask *task = local->asyncTasks + local->asyncTasksRead; + if (task->addressSpace) local->currentThread->SetAddressSpace(task->addressSpace); + task->callback(task->argument); + local->currentThread->SetAddressSpace(nullptr); + local->asyncTasksRead++; + } + } +} + +void Scheduler::CreateProcessorThreads(CPULocalStorage *local) { + Thread *idleThread = (Thread *) threadPool.Add(sizeof(Thread)); + idleThread->isKernelThread = true; + idleThread->state = THREAD_ACTIVE; + idleThread->executing = true; + idleThread->type = THREAD_IDLE; + idleThread->terminatableState = THREAD_IN_SYSCALL; + idleThread->cName = "Idle"; + local->currentThread = local->idleThread = idleThread; + + KSpinlockAcquire(&lock); + + if (currentProcessorID >= K_MAX_PROCESSORS) { + KernelPanic("Scheduler::CreateProcessorThreads - Maximum processor count (%d) exceeded.\n", currentProcessorID); + } + + local->processorID = currentProcessorID++; + + // Force release the lock because we've changed our currentThread value. + KSpinlockRelease(&lock); + + InsertNewThread(idleThread, false, kernelProcess); + + local->asyncTaskThread = SpawnThread("AsyncTasks", (uintptr_t) AsyncTaskThread, 0, SPAWN_THREAD_MANUALLY_ACTIVATED); + local->asyncTaskThread->type = THREAD_ASYNC_TASK; +} + +void RegisterAsyncTask(KAsyncTaskCallback callback, EsGeneric argument, Process *targetProcess, bool needed, bool unlocked) { + if (!unlocked) KSpinlockAssertLocked(&scheduler.lock); + else KSpinlockAcquire(&scheduler.lock); + EsDefer(if (unlocked) KSpinlockRelease(&scheduler.lock)); + + if (targetProcess == nullptr) { + targetProcess = kernelProcess; + } + + CPULocalStorage *local = GetLocalStorage(); + + int difference = local->asyncTasksWrite - local->asyncTasksRead; + if (difference < 0) difference += MAX_ASYNC_TASKS; + + if (difference >= MAX_ASYNC_TASKS / 2 && !needed) { + return; + } + + if (difference == MAX_ASYNC_TASKS - 1) { + KernelPanic("RegisterAsyncTask - Maximum number of queued asynchronous tasks reached.\n"); + } + + // We need to register tasks for terminating processes. +#if 0 + if (!targetProcess->handles) { + KernelPanic("RegisterAsyncTask - Process has no handles.\n"); + } +#endif + + volatile AsyncTask *task = local->asyncTasks + local->asyncTasksWrite; + task->callback = callback; + task->argument = argument.p; + task->addressSpace = targetProcess->vmm; + local->asyncTasksWrite++; +} + +void KRegisterAsyncTask(KAsyncTaskCallback callback, EsGeneric argument, bool needed) { + RegisterAsyncTask(callback, argument, kernelProcess, needed, true); +} + +void Scheduler::RemoveProcess(Process *process) { + KernelLog(LOG_INFO, "Scheduler", "remove process", "Removing process %d.\n", process->id); + + bool started = process->executableStartRequest; + + if (started) { + // Make sure that the process cannot be opened. + + KSpinlockAcquire(&lock); + + allProcesses.Remove(&process->allItem); + + if (pmm.nextProcessToBalance == process) { + // If the balance thread got interrupted while balancing this process, + // start at the beginning of the next process. + + pmm.nextProcessToBalance = process->allItem.nextItem ? process->allItem.nextItem->thisItem : nullptr; + pmm.nextRegionToBalance = nullptr; + pmm.balanceResumePosition = 0; + } + + KSpinlockRelease(&lock); + + // At this point, no pointers to the process (should) remain (I think). + + if (!process->allThreadsTerminated) { + KernelPanic("Scheduler::RemoveProcess - The process is being removed before all its threads have terminated?!\n"); + } + + // Close the handle to the executable node. + + CloseHandleToObject(process->executableNode, KERNEL_OBJECT_NODE, ES_FILE_READ); + } + + // Destroy the process's handle table, if it has already been destroyed. + // For most processes, the handle table is destroyed when the last thread terminates. + + process->handleTable.Destroy(); + + // Free all the remaining messages in the message queue. + // This is done after closing all handles, since closing handles can generate messages. + + process->messageQueue.messages.Free(); + + // Free the process. + + KRegisterAsyncTask([] (EsGeneric process) { + MMSpace *space = ((Process *) process.p)->vmm; + MMFinalizeVAS(space); + scheduler.mmSpacePool.Remove(space); + scheduler.processPool.Remove(process.p); + }, process); + + if (started) { + // If all processes (except the kernel process) have terminated, set the scheduler's killedEvent. + + KSpinlockAcquire(&scheduler.lock); + scheduler.blockShutdownProcessCount--; + + if (!scheduler.blockShutdownProcessCount) { + KEventSet(&scheduler.killedEvent, true, false); + } + + KSpinlockRelease(&scheduler.lock); + } +} + +void Scheduler::RemoveThread(Thread *thread) { + KernelLog(LOG_INFO, "Scheduler", "remove thread", "Removing thread %d.\n", thread->id); + + // The last handle to the thread has been closed, + // so we can finally deallocate the thread. + +#ifdef ENABLE_POSIX_SUBSYSTEM + if (thread->posixData) { + if (thread->posixData->forkStack) { + EsHeapFree(thread->posixData->forkStack, thread->posixData->forkStackSize, K_PAGED); + CloseHandleToObject(thread->posixData->forkProcess, KERNEL_OBJECT_PROCESS); + } + + EsHeapFree(thread->posixData, sizeof(POSIXThread), K_PAGED); + } +#endif + + // TODO Deallocate user and kernel stacks. + + scheduler.threadPool.Remove(thread); +} + +void Scheduler::CrashProcess(Process *process, EsCrashReason *crashReason) { + if (process == kernelProcess) { + KernelPanic("Scheduler::CrashProcess - Kernel process has crashed (%d).\n", crashReason->errorCode); + } + + if (process->type != PROCESS_NORMAL) { + KernelPanic("Scheduler::CrashProcess - A critical process has crashed (%d).\n", crashReason->errorCode); + } + + if (GetCurrentThread()->process != process) { + KernelPanic("Scheduler::CrashProcess - Attempt to crash process from different process.\n"); + } + + KMutexAcquire(&process->crashMutex); + + if (process->crashed) { + KMutexRelease(&process->crashMutex); + return; + } + + process->crashed = true; + + KernelLog(LOG_ERROR, "Scheduler", "process crashed", "Process %x has crashed! (%d)\n", process, crashReason->errorCode); + + EsMemoryCopy(&process->crashReason, crashReason, sizeof(EsCrashReason)); + + if (!shutdown) { + OpenHandleToObject(process, KERNEL_OBJECT_PROCESS); + _EsMessageWithObject m; + EsMemoryZero(&m, sizeof(m)); + m.message.type = ES_MSG_APPLICATION_CRASH; + m.message.crash.process = desktopProcess->handleTable.OpenHandle(process, 0, KERNEL_OBJECT_PROCESS); + m.message.crash.pid = process->id; + EsMemoryCopy(&m.message.crash.reason, crashReason, sizeof(EsCrashReason)); + desktopProcess->messageQueue.SendMessage(&m); + } + + KMutexRelease(&process->crashMutex); + + // TODO Shouldn't this be done before sending the desktop message? + scheduler.PauseProcess(GetCurrentThread()->process, false); +} + +void Scheduler::PauseThread(Thread *thread, bool resume, bool lockAlreadyAcquired) { + if (!lockAlreadyAcquired) KSpinlockAcquire(&lock); + + if (thread->paused == !resume) { + return; + } + + thread->paused = !resume; + + if (!resume && thread->terminatableState == THREAD_TERMINATABLE) { + if (thread->state == THREAD_ACTIVE) { + if (thread->executing) { + if (thread == GetCurrentThread()) { + KSpinlockRelease(&lock); + + // Yield. + ProcessorFakeTimerInterrupt(); + + if (thread->paused) { + KernelPanic("Scheduler::PauseThread - Current thread incorrectly resumed.\n"); + } + } else { + // The thread is executing, but on a different processor. + // Send them an IPI to stop. + thread->receivedYieldIPI = false; + KSpinlockAcquire(&ipiLock); + ProcessorSendIPI(YIELD_IPI, false); + KSpinlockRelease(&ipiLock); + while (!thread->receivedYieldIPI); // Spin until the thread gets the IPI. + // TODO The interrupt context might not be set at this point. + } + } else { + // Remove the thread from the active queue, and put it into the paused queue. + thread->item.RemoveFromList(); + AddActiveThread(thread, false); + } + } else { + // The thread doesn't need to be in the paused queue as it won't run anyway. + // If it is unblocked, then AddActiveThread will put it into the correct queue. + } + } else if (resume && thread->item.list == &pausedThreads) { + // Remove the thread from the paused queue, and put it into the active queue. + pausedThreads.Remove(&thread->item); + AddActiveThread(thread, false); + } + + if (!lockAlreadyAcquired) KSpinlockRelease(&lock); +} + +void Scheduler::PauseProcess(Process *process, bool resume) { + Thread *currentThread = GetCurrentThread(); + bool isCurrentProcess = process == currentThread->process; + bool foundCurrentThread = false; + + { + KSpinlockAcquire(&scheduler.lock); + EsDefer(KSpinlockRelease(&scheduler.lock)); + + LinkedItem *thread = process->threads.firstItem; + + while (thread) { + Thread *threadObject = thread->thisItem; + thread = thread->nextItem; + + if (threadObject != currentThread) { + PauseThread(threadObject, resume, true); + } else if (isCurrentProcess) { + foundCurrentThread = true; + } else { + KernelPanic("Scheduler::PauseProcess - Found current thread in the wrong process?!\n"); + } + } + } + + if (!foundCurrentThread && isCurrentProcess) { + KernelPanic("Scheduler::PauseProcess - Could not find current thread in the current process?!\n"); + } else if (isCurrentProcess) { + PauseThread(currentThread, resume, false); + } +} + +void _RemoveProcess(EsGeneric process) { + scheduler.RemoveProcess((Process *) process.p); +} + +void CloseHandleToProcess(void *_process) { + KSpinlockAcquire(&scheduler.lock); + Process *process = (Process *) _process; + if (!process->handles) KernelPanic("CloseHandleToProcess - All handles to the process have been closed.\n"); + process->handles--; + bool removeProcess = !process->handles; + + KernelLog(LOG_VERBOSE, "Scheduler", "close process handle", "Closed handle to process %d; %d handles remain.\n", process->id, process->handles); + + if (removeProcess && process->executableStartRequest) { + // This must be done in the correct virtual address space! + RegisterAsyncTask(_RemoveProcess, process, process, true); + } + + KSpinlockRelease(&scheduler.lock); + + if (removeProcess && !process->executableStartRequest) { + // The process was never started, so we can't make a RemoveProcess task, because it doesn't have an MMSpace yet. + scheduler.RemoveProcess(process); + } + + ProcessorFakeTimerInterrupt(); // Process the asynchronous task. +} + +void CloseHandleToThread(void *_thread) { + KSpinlockAcquire(&scheduler.lock); + Thread *thread = (Thread *) _thread; + if (!thread->handles) KernelPanic("CloseHandleToThread - All handles to the thread have been closed.\n"); + thread->handles--; + bool removeThread = thread->handles == 0; + // EsPrint("Thread %d has %d handles\n", thread->id, thread->handles); + KSpinlockRelease(&scheduler.lock); + + if (removeThread) { + scheduler.RemoveThread(thread); + } +} + +void KillThread(EsGeneric _thread) { + Thread *thread = (Thread *) _thread.p; + + KSpinlockAcquire(&scheduler.lock); + scheduler.allThreads.Remove(&thread->allItem); + thread->process->threads.Remove(&thread->processItem); + + KernelLog(LOG_INFO, "Scheduler", "killing thread", + "Killing thread (ID %d, %d remain in process %d) %x...\n", thread->id, thread->process->threads.count, thread->process->id, _thread); + + if (thread->process->threads.count == 0) { + Process *process = thread->process; + KernelLog(LOG_INFO, "Scheduler", "killing process", + "Killing process (%d) %x...\n", process->id, process); + + process->allThreadsTerminated = true; + scheduler.activeProcessCount--; + + bool setProcessKilledEvent = true; + +#ifdef ENABLE_POSIX_SUBSYSTEM + if (process->posixForking) { + // If the process is from an incomplete vfork(), + // then the parent process gets to set the killed event + // and the exit status. + setProcessKilledEvent = false; + } +#endif + + if (setProcessKilledEvent) { + // We can now also set the killed event on the process. + KEventSet(&process->killedEvent, true); + } + + KSpinlockRelease(&scheduler.lock); + + // There are no threads left in this process. + // We should destroy the handle table at this point. + // Otherwise, the process might never be freed + // because of a cyclic-dependency. + process->handleTable.Destroy(); + + // Destroy the virtual memory space. + // Don't actually deallocate it yet though; that is done on an async task queued by RemoveProcess. + // This must be destroyed after the handle table! + MMSpaceDestroy(process->vmm); + + // Tell Desktop the process has terminated. + if (!scheduler.shutdown) { + _EsMessageWithObject m; + EsMemoryZero(&m, sizeof(m)); + m.message.type = ES_MSG_PROCESS_TERMINATED; + m.message.crash.pid = process->id; + desktopProcess->messageQueue.SendMessage(&m); + } + } else { + KSpinlockRelease(&scheduler.lock); + } + + MMFree(kernelMMSpace, (void *) thread->kernelStackBase); + if (thread->userStackBase) MMFree(thread->process->vmm, (void *) thread->userStackBase); + + KEventSet(&thread->killedEvent); + + // Close the handle that this thread owns of its owner process. + CloseHandleToObject(thread->process, KERNEL_OBJECT_PROCESS); + + CloseHandleToThread(thread); +} + +Thread *Scheduler::PickThread(CPULocalStorage *local) { + KSpinlockAssertLocked(&lock); + + if (local->asyncTasksRead != local->asyncTasksWrite + && local->asyncTaskThread->state == THREAD_ACTIVE) { + // If the asynchronous task thread for this processor isn't blocked, and has tasks to process, execute it. + return local->asyncTaskThread; + } + + for (int i = 0; i < THREAD_PRIORITY_COUNT; i++) { + // For every priority, check if there is a thread available. If so, execute it. + LinkedItem *item = activeThreads[i].firstItem; + if (!item) continue; + item->RemoveFromList(); + return item->thisItem; + } + + // If we couldn't find a thread to execute, idle. + return local->idleThread; +} + +void Scheduler::Yield(InterruptContext *context) { + CPULocalStorage *local = GetLocalStorage(); + + if (!started || !local || !local->schedulerReady) { + return; + } + + if (local->spinlockCount) { + KernelPanic("Scheduler::Yield - Spinlocks acquired while attempting to yield (2).\n"); + } + + if (local->spinlockCount) { + KernelPanic("Scheduler::Yield - Spinlocks acquired while attempting to yield.\n"); + } + + ProcessorDisableInterrupts(); // We don't want interrupts to get reenabled after the context switch. + KSpinlockAcquire(&lock); + + local->currentThread->interruptContext = context; + + if (lock.interruptsEnabled) { + KernelPanic("Scheduler::Yield - Interrupts were enabled when scheduler lock was acquired.\n"); + } + + local->currentThread->executing = false; + + bool killThread = local->currentThread->terminatableState == THREAD_TERMINATABLE + && local->currentThread->terminating; + bool keepThreadAlive = local->currentThread->terminatableState == THREAD_USER_BLOCK_REQUEST + && local->currentThread->terminating; // The user can't make the thread block if it is terminating. + + if (killThread) { + local->currentThread->state = THREAD_TERMINATED; + KernelLog(LOG_INFO, "Scheduler", "terminate yielded thread", "Terminated yielded thread %x\n", local->currentThread); + RegisterAsyncTask(KillThread, local->currentThread, local->currentThread->process, true); + } + + // If the thread is waiting for an object to be notified, put it in the relevant blockedThreads list. + // But if the object has been notified yet hasn't made itself active yet, do that for it. + + else if (local->currentThread->state == THREAD_WAITING_MUTEX) { + KMutex *mutex = local->currentThread->blocking.mutex; + + if (!keepThreadAlive && mutex->owner) { + mutex->owner->blockedThreadPriorities[local->currentThread->priority]++; + MaybeUpdateActiveList(mutex->owner); + mutex->blockedThreads.InsertEnd(&local->currentThread->item); + } else { + local->currentThread->state = THREAD_ACTIVE; + } + } + + else if (local->currentThread->state == THREAD_WAITING_EVENT) { + if (keepThreadAlive) { + local->currentThread->state = THREAD_ACTIVE; + } else { + bool unblocked = false; + + for (uintptr_t i = 0; i < local->currentThread->blocking.eventCount; i++) { + if (local->currentThread->blocking.events[i]->state) { + local->currentThread->state = THREAD_ACTIVE; + unblocked = true; + break; + } + } + + if (!unblocked) { + for (uintptr_t i = 0; i < local->currentThread->blocking.eventCount; i++) { + local->currentThread->blocking.events[i]->blockedThreads.InsertEnd(&local->currentThread->blockedItems[i]); + } + } + } + } + + else if (local->currentThread->state == THREAD_WAITING_WRITER_LOCK) { + KWriterLock *lock = local->currentThread->blocking.writerLock; + + if ((local->currentThread->blocking.writerLockType == K_LOCK_SHARED && lock->state >= 0) + || (local->currentThread->blocking.writerLockType == K_LOCK_EXCLUSIVE && lock->state == 0)) { + local->currentThread->state = THREAD_ACTIVE; + } else { + local->currentThread->blocking.writerLock->blockedThreads.InsertEnd(&local->currentThread->item); + } + } + + // Put the current thread at the end of the activeThreads list. + if (!killThread && local->currentThread->state == THREAD_ACTIVE) { + if (local->currentThread->type == THREAD_NORMAL) { + AddActiveThread(local->currentThread, false); + } else if (local->currentThread->type == THREAD_IDLE || local->currentThread->type == THREAD_ASYNC_TASK) { + // Do nothing. + } else { + KernelPanic("Scheduler::Yield - Unrecognised thread type\n"); + } + } + + // Notify any triggered timers. + + LinkedItem *_timer = activeTimers.firstItem; + + while (_timer) { + KTimer *timer = _timer->thisItem; + LinkedItem *next = _timer->nextItem; + + if (timer->triggerTimeMs <= timeMs) { + activeTimers.Remove(_timer); + KEventSet(&timer->event, true /* scheduler already locked */); + + if (timer->callback) { + RegisterAsyncTask(timer->callback, timer->argument, nullptr, true); + } + } else { + break; // Timers are kept sorted, so there's no point continuing. + } + + _timer = next; + } + + // Get the next thread to execute. + Thread *newThread = local->currentThread = PickThread(local); + + if (!newThread) { + KernelPanic("Scheduler::Yield - Could not find a thread to execute.\n"); + } + + if (newThread->executing) { + KernelPanic("Scheduler::Yield - Thread (ID %d) in active queue already executing with state %d, type %d.\n", + local->currentThread->id, local->currentThread->state, local->currentThread->type); + } + + // Store information about the thread. + newThread->executing = true; + newThread->executingProcessorID = local->processorID; + newThread->cpuTimeSlices++; + if (newThread->type == THREAD_IDLE) newThread->process->idleTimeSlices++; + else newThread->process->cpuTimeSlices++; + + // Prepare the next timer interrupt. + uint64_t time = 1; + ArchNextTimer(time); + + InterruptContext *newContext = newThread->interruptContext; + + if (!local->processorID) { + // Update the scheduler's time. + timeMs = ProcessorReadTimeStamp() / KGetTimeStampTicksPerMs(); + + // Update the time stamp counter synchronization value. + extern volatile uint64_t timeStampCounterSynchronizationValue; + timeStampCounterSynchronizationValue = ((timeStampCounterSynchronizationValue & 0x8000000000000000) + ^ 0x8000000000000000) | ProcessorReadTimeStamp(); + } + + MMSpace *addressSpace = newThread->process->vmm; + if (newThread->temporaryAddressSpace) addressSpace = newThread->temporaryAddressSpace; + DoContextSwitch(newContext, VIRTUAL_ADDRESS_SPACE_IDENTIFIER(addressSpace), newThread->kernelStack, newThread); +} + +void Scheduler::Shutdown() { + // Prevent the creation of new proceses or threads, + // or terminate this thread if we're already shutting down. + if (__sync_val_compare_and_swap(&scheduler.shutdown, false, true)) KThreadTerminate(); + + // Close our handle to the desktop process. + CloseHandleToObject(desktopProcess->executableMainThread, KERNEL_OBJECT_THREAD); + CloseHandleToObject(desktopProcess, KERNEL_OBJECT_PROCESS); + + KernelLog(LOG_INFO, "Scheduler", "killing all processes", "Scheduler::Destroy - Killing all processes....\n"); + + while (true) { + KSpinlockAcquire(&lock); + Process *process = allProcesses.firstItem->thisItem; + + while (process && (process->terminating || process == kernelProcess)) { + LinkedItem *item = process->allItem.nextItem; + process = item ? item->thisItem : nullptr; + } + + KSpinlockRelease(&lock); + if (!process) break; + + TerminateProcess(process, -1); + } + + KEventWait(&killedEvent); +} + +Process *Scheduler::OpenProcess(uint64_t id) { + KSpinlockAcquire(&scheduler.lock); + + LinkedItem *item = scheduler.allProcesses.firstItem; + + while (item) { + Process *process = item->thisItem; + + if (process->id == id + && process->handles /* if the process has no handles, it's about to be removed */ + && process->type != PROCESS_KERNEL /* the kernel process cannot be opened */) { + process->handles++; + break; + } + + item = item->nextItem; + } + + KSpinlockRelease(&scheduler.lock); + + return item ? item->thisItem : nullptr; +} + +bool KThreadCreate(const char *cName, void (*startAddress)(uintptr_t), uintptr_t argument) { + return scheduler.SpawnThread(cName, (uintptr_t) startAddress, argument) ? true : false; +} + +void KThreadTerminate() { + scheduler.TerminateThread(GetCurrentThread()); +} + +void KYield() { + ProcessorFakeTimerInterrupt(); +} + +uint64_t KCPUCurrentID() { return GetLocalStorage() ->processorID; } +uint64_t KProcessCurrentID() { return GetCurrentThread()->process->id; } +uint64_t KThreadCurrentID() { return GetCurrentThread() ->id; } + +#endif diff --git a/kernel/symbols.cpp b/kernel/symbols.cpp new file mode 100644 index 0000000..af20c53 --- /dev/null +++ b/kernel/symbols.cpp @@ -0,0 +1,46 @@ +#include +#include + +extern "C" int EsStringCompareRaw(const char *s1, size_t b1, const char *s2, size_t b2); +void EsPrint(const char *format, ...); + +extern "C" void KernelMain(); +extern "C" void ProcessorHalt(); + +struct ExportedKernelFunction { + void *address; + const char *name; +}; + +ExportedKernelFunction exportedKernelFunctions[] = { +#include +}; + +static bool linked; + +void *ResolveKernelSymbol(const char *name, size_t nameBytes) { + // EsPrint("Resolve: '%s'.\n", nameBytes, name); + + if (!linked) { + linked = true; + + // As we get the function addresses before the kernel is linked (this file needs to be linked with the kernel), + // they are relative to wherever the kernel_all.o's text is placed in the executable's text section. + + uintptr_t offset = (uintptr_t) ResolveKernelSymbol("KernelMain", 10) - (uintptr_t) KernelMain; + + for (uintptr_t i = 0; i < sizeof(exportedKernelFunctions) / sizeof(exportedKernelFunctions[0]); i++) { + exportedKernelFunctions[i].address = (void *) ((uintptr_t) exportedKernelFunctions[i].address - offset); + } + } + + for (uintptr_t i = 0; i < sizeof(exportedKernelFunctions) / sizeof(exportedKernelFunctions[0]); i++) { + if (0 == EsStringCompareRaw(exportedKernelFunctions[i].name, -1, name, nameBytes)) { + return exportedKernelFunctions[i].address; + } + } + + EsPrint("ResolveKernelSymbol - Could not find symbol '%s'.\n", nameBytes, name); + + return nullptr; +} diff --git a/kernel/synchronisation.cpp b/kernel/synchronisation.cpp new file mode 100644 index 0000000..04a69f9 --- /dev/null +++ b/kernel/synchronisation.cpp @@ -0,0 +1,862 @@ +#ifdef IMPLEMENTATION + +#ifdef DEBUG_BUILD +uintptr_t nextMutexID; +#endif + +void KSpinlockAcquire(KSpinlock *spinlock) { + if (scheduler.panic) return; + + bool _interruptsEnabled = ProcessorAreInterruptsEnabled(); + ProcessorDisableInterrupts(); + + CPULocalStorage *storage = GetLocalStorage(); + +#ifdef DEBUG_BUILD + if (storage && storage->currentThread && spinlock->owner && spinlock->owner == storage->currentThread) { + KernelPanic("KSpinlock::Acquire - Attempt to acquire a spinlock owned by the current thread (%x/%x, CPU: %d/%d).\nAcquired at %x.\n", + storage->currentThread, spinlock->owner, storage->processorID, spinlock->ownerCPU, spinlock->acquireAddress); + } +#endif + + if (storage) { + storage->spinlockCount++; + } + + while (__sync_val_compare_and_swap(&spinlock->state, 0, 1)); + __sync_synchronize(); + + spinlock->interruptsEnabled = _interruptsEnabled; + + if (storage) { +#ifdef DEBUG_BUILD + spinlock->owner = storage->currentThread; +#endif + spinlock->ownerCPU = storage->processorID; + } else { + // Because spinlocks can be accessed very early on in initialisation there may not be + // a CPULocalStorage available for the current processor. Therefore, just set this field to nullptr. + +#ifdef DEBUG_BUILD + spinlock->owner = nullptr; +#endif + } + +#ifdef DEBUG_BUILD + spinlock->acquireAddress = (uintptr_t) __builtin_return_address(0); +#endif +} + +void KSpinlockRelease(KSpinlock *spinlock, bool force) { + if (scheduler.panic) return; + + CPULocalStorage *storage = GetLocalStorage(); + + if (storage) { + storage->spinlockCount--; + } + + if (!force) { + KSpinlockAssertLocked(spinlock); + } + + volatile bool wereInterruptsEnabled = spinlock->interruptsEnabled; + +#ifdef DEBUG_BUILD + spinlock->owner = nullptr; +#endif + __sync_synchronize(); + spinlock->state = 0; + + if (wereInterruptsEnabled) ProcessorEnableInterrupts(); + +#ifdef DEBUG_BUILD + spinlock->releaseAddress = (uintptr_t) __builtin_return_address(0); +#endif +} + +void KSpinlockAssertLocked(KSpinlock *spinlock) { + if (scheduler.panic) return; + +#ifdef DEBUG_BUILD + CPULocalStorage *storage = GetLocalStorage(); + + if (!spinlock->state || ProcessorAreInterruptsEnabled() + || (storage && spinlock->owner != storage->currentThread)) { +#else + if (!spinlock->state || ProcessorAreInterruptsEnabled()) { +#endif + KernelPanic("KSpinlock::AssertLocked - KSpinlock not correctly acquired\n" + "Return address = %x.\n" + "state = %d, ProcessorAreInterruptsEnabled() = %d, this = %x\n", + __builtin_return_address(0), spinlock->state, + ProcessorAreInterruptsEnabled(), spinlock); + } +} + +Thread *AttemptMutexAcquisition(KMutex *mutex, Thread *currentThread) { + KSpinlockAcquire(&scheduler.lock); + + Thread *old = mutex->owner; + + if (!old) { + mutex->owner = currentThread; + } + + KSpinlockRelease(&scheduler.lock); + + return old; +} + +#ifdef DEBUG_BUILD +bool _KMutexAcquire(KMutex *mutex, const char *cMutexString, const char *cFile, int line) { +#else +bool KMutexAcquire(KMutex *mutex) { +#endif + if (scheduler.panic) return false; + + Thread *currentThread = GetCurrentThread(); + bool hasThread = currentThread; + + if (!currentThread) { + currentThread = (Thread *) 1; + } else { + if (currentThread->terminatableState == THREAD_TERMINATABLE) { + KernelPanic("KMutex::Acquire - Thread is terminatable.\n"); + } + } + + if (hasThread && mutex->owner && mutex->owner == currentThread) { +#ifdef DEBUG_BUILD + KernelPanic("KMutex::Acquire - Attempt to acquire mutex (%x) at %x owned by current thread (%x) acquired at %x.\n", + mutex, __builtin_return_address(0), currentThread, mutex->acquireAddress); +#else + KernelPanic("KMutex::Acquire - Attempt to acquire mutex (%x) at %x owned by current thread (%x).\n", + mutex, __builtin_return_address(0), currentThread); +#endif + } + + if (!ProcessorAreInterruptsEnabled()) { + KernelPanic("KMutex::Acquire - Trying to acquire a mutex while interrupts are disabled.\n"); + } + + while (AttemptMutexAcquisition(mutex, currentThread)) { + __sync_synchronize(); + + if (GetLocalStorage() && GetLocalStorage()->schedulerReady) { + // Instead of spinning on the lock, + // let's tell the scheduler to not schedule this thread + // until it's released. + scheduler.WaitMutex(mutex); + + if (currentThread->terminating && currentThread->terminatableState == THREAD_USER_BLOCK_REQUEST) { + // We didn't acquire the mutex because the thread is terminating. + return false; + } + } + } + + __sync_synchronize(); + + if (mutex->owner != currentThread) { + KernelPanic("KMutex::Acquire - Invalid owner thread (%x, expected %x).\n", mutex->owner, currentThread); + } + +#ifdef DEBUG_BUILD + mutex->acquireAddress = (uintptr_t) __builtin_return_address(0); + KMutexAssertLocked(mutex); + + if (!mutex->id) { + mutex->id = __sync_fetch_and_add(&nextMutexID, 1); + } + + if (currentThread && scheduler.threadEventLog) { + uintptr_t position = __sync_fetch_and_add(&scheduler.threadEventLogPosition, 1); + + if (position < scheduler.threadEventLogAllocated) { + EsThreadEventLogEntry *entry = scheduler.threadEventLog + position; + entry->event = ES_THREAD_EVENT_MUTEX_ACQUIRE; + entry->objectID = mutex->id; + entry->threadID = currentThread->id; + entry->line = line; + entry->fileBytes = EsCStringLength(cFile); + if (entry->fileBytes > sizeof(entry->file)) entry->fileBytes = sizeof(entry->file); + entry->expressionBytes = EsCStringLength(cMutexString); + if (entry->expressionBytes > sizeof(entry->expression)) entry->expressionBytes = sizeof(entry->expression); + EsMemoryCopy(entry->file, cFile, entry->fileBytes); + EsMemoryCopy(entry->expression, cMutexString, entry->expressionBytes); + } + } +#endif + + return true; +} + +#ifdef DEBUG_BUILD +void _KMutexRelease(KMutex *mutex, const char *cMutexString, const char *cFile, int line) { +#else +void KMutexRelease(KMutex *mutex) { +#endif + if (scheduler.panic) return; + + KMutexAssertLocked(mutex); + Thread *currentThread = GetCurrentThread(); + KSpinlockAcquire(&scheduler.lock); + +#ifdef DEBUG_BUILD + // EsPrint("$%x:%x:0\n", owner, id); +#endif + + if (currentThread) { + Thread *temp = __sync_val_compare_and_swap(&mutex->owner, currentThread, nullptr); + if (currentThread != temp) KernelPanic("KMutex::Release - Invalid owner thread (%x, expected %x).\n", temp, currentThread); + } else mutex->owner = nullptr; + + volatile bool preempt = mutex->blockedThreads.count; + + if (scheduler.started) { + // NOTE We unblock all waiting threads, because of how blockedThreadPriorities works. + scheduler.NotifyObject(&mutex->blockedThreads, true, currentThread); + } + + KSpinlockRelease(&scheduler.lock); + __sync_synchronize(); + +#ifdef DEBUG_BUILD + mutex->releaseAddress = (uintptr_t) __builtin_return_address(0); + + if (currentThread && scheduler.threadEventLog) { + uintptr_t position = __sync_fetch_and_add(&scheduler.threadEventLogPosition, 1); + + if (position < scheduler.threadEventLogAllocated) { + EsThreadEventLogEntry *entry = scheduler.threadEventLog + position; + entry->event = ES_THREAD_EVENT_MUTEX_RELEASE; + entry->objectID = mutex->id; + entry->threadID = currentThread->id; + entry->line = line; + entry->fileBytes = EsCStringLength(cFile); + if (entry->fileBytes > sizeof(entry->file)) entry->fileBytes = sizeof(entry->file); + entry->expressionBytes = EsCStringLength(cMutexString); + if (entry->expressionBytes > sizeof(entry->expression)) entry->expressionBytes = sizeof(entry->expression); + EsMemoryCopy(entry->file, cFile, entry->fileBytes); + EsMemoryCopy(entry->expression, cMutexString, entry->expressionBytes); + } + } +#endif + +#ifdef PREEMPT_AFTER_MUTEX_RELEASE + if (preempt) ProcessorFakeTimerInterrupt(); +#endif +} + +void KMutexAssertLocked(KMutex *mutex) { + Thread *currentThread = GetCurrentThread(); + + if (!currentThread) { + currentThread = (Thread *) 1; + } + + if (mutex->owner != currentThread) { +#ifdef DEBUG_BUILD + KernelPanic("KMutex::AssertLocked - Mutex not correctly acquired\n" + "currentThread = %x, owner = %x\nthis = %x\nReturn %x/%x\nLast used from %x->%x\n", + currentThread, mutex->owner, mutex, __builtin_return_address(0), __builtin_return_address(1), + mutex->acquireAddress, mutex->releaseAddress); +#else + KernelPanic("KMutex::AssertLocked - Mutex not correctly acquired\n" + "currentThread = %x, owner = %x\nthis = %x\nReturn %x\n", + currentThread, mutex->owner, mutex, __builtin_return_address(0)); +#endif + } +} + +bool KSemaphorePoll(KSemaphore *semaphore) { + bool success = false; + KMutexAcquire(&semaphore->mutex); + if (semaphore->units) { success = true; semaphore->units--; } + if (!semaphore->units && semaphore->available.state) KEventReset(&semaphore->available); + KMutexRelease(&semaphore->mutex); + return success; +} + +bool KSemaphoreTake(KSemaphore *semaphore, uintptr_t u, uintptr_t timeoutMs) { + // All-or-nothing approach to prevent deadlocks. + + uintptr_t taken = 0; + + while (u) { + if (!KEventWait(&semaphore->available, timeoutMs)) { + KSemaphoreReturn(semaphore, taken); + return false; + } + + KMutexAcquire(&semaphore->mutex); + if (semaphore->units >= u) { semaphore->units -= u; u = 0; taken += u; } + if (!semaphore->units && semaphore->available.state) KEventReset(&semaphore->available); + KMutexRelease(&semaphore->mutex); + + semaphore->lastTaken = (uintptr_t) __builtin_return_address(0); + } + + return true; +} + +void KSemaphoreReturn(KSemaphore *semaphore, uintptr_t u) { + KMutexAcquire(&semaphore->mutex); + if (!semaphore->available.state) KEventSet(&semaphore->available); + semaphore->units += u; + KMutexRelease(&semaphore->mutex); +} + +void KSemaphoreSet(KSemaphore *semaphore, uintptr_t u) { + KMutexAcquire(&semaphore->mutex); + if (!semaphore->available.state && u) KEventSet(&semaphore->available); + else if (semaphore->available.state && !u) KEventReset(&semaphore->available); + semaphore->units = u; + KMutexRelease(&semaphore->mutex); +} + +EsError EventSink::Push(EsGeneric data) { + EsError result = ES_SUCCESS; + + KSpinlockAssertLocked(&scheduler.lock); + + KSpinlockAcquire(&spinlock); + + if (queueCount == ES_MAX_EVENT_SINK_BUFFER_SIZE) { + overflow = true; + result = ES_ERROR_EVENT_SINK_OVERFLOW; + KernelLog(LOG_VERBOSE, "Event Sinks", "push overflow", "Push %d into %x.\n", data.i, this); + } else { + bool ignored = false; + + if (ignoreDuplicates) { + uintptr_t index = queuePosition; + + for (uintptr_t i = 0; i < queueCount; i++) { + if (queue[index] == data) { + ignored = true; + result = ES_ERROR_EVENT_SINK_DUPLICATE; + KernelLog(LOG_VERBOSE, "Event Sinks", "push ignored", "Push %d into %x.\n", data.i, this); + break; + } + + index++; + + if (index == ES_MAX_EVENT_SINK_BUFFER_SIZE) { + index = 0; + } + } + } + + if (!ignored) { + uintptr_t writeIndex = queuePosition + queueCount; + if (writeIndex >= ES_MAX_EVENT_SINK_BUFFER_SIZE) writeIndex -= ES_MAX_EVENT_SINK_BUFFER_SIZE; + if (writeIndex >= ES_MAX_EVENT_SINK_BUFFER_SIZE) KernelPanic("EventSink::Push - Invalid event sink (%x) queue.\n", this); + KernelLog(LOG_VERBOSE, "Event Sinks", "push", "Push %d into %x at %d (%d/%d).\n", data.i, this, writeIndex, queuePosition, queueCount); + queue[writeIndex] = data; + queueCount++; + KEventSet(&available, true, true); + } + } + + KSpinlockRelease(&spinlock); + + return result; +} + +bool KEventSet(KEvent *event, bool schedulerAlreadyLocked, bool maybeAlreadySet) { + if (event->state && !maybeAlreadySet) { + KernelLog(LOG_ERROR, "Synchronisation", "event already set", "KEvent::Set - Attempt to set a event that had already been set\n"); + } + + if (!schedulerAlreadyLocked) { + KSpinlockAcquire(&scheduler.lock); + } else { + KSpinlockAssertLocked(&scheduler.lock); + } + + volatile bool unblockedThreads = false; + + if (!event->state) { + if (event->sinkTable) { + for (uintptr_t i = 0; i < ES_MAX_EVENT_FORWARD_COUNT; i++) { + if (!event->sinkTable[i].sink) continue; + event->sinkTable[i].sink->Push(event->sinkTable[i].data); + } + } + + event->state = true; + + if (scheduler.started) { + if (event->blockedThreads.count) { + unblockedThreads = true; + } + + scheduler.NotifyObject(&event->blockedThreads, !event->autoReset /* if this is a manually reset event, unblock all the waiting threads */); + } + } + + if (!schedulerAlreadyLocked) { + KSpinlockRelease(&scheduler.lock); + } + + return unblockedThreads; +} + +void KEventReset(KEvent *event) { + if (event->blockedThreads.firstItem && event->state) { + // TODO Is it possible for this to happen? + KernelLog(LOG_ERROR, "Synchronisation", "reset event with threads blocking", + "KEvent::Reset - Attempt to reset a event while threads are blocking on the event\n"); + } + + event->state = false; +} + +bool KEventPoll(KEvent *event) { + if (event->autoReset) { + return __sync_val_compare_and_swap(&event->state, true, false); + } else { + return event->state; + } +} + +bool KEventWait(KEvent *_this, uint64_t timeoutMs) { + KEvent *events[2]; + events[0] = _this; + + if (timeoutMs == (uint64_t) ES_WAIT_NO_TIMEOUT) { + int index = scheduler.WaitEvents(events, 1); + return index == 0; + } else { + KTimer timer = {}; + KTimerSet(&timer, timeoutMs); + events[1] = &timer.event; + int index = scheduler.WaitEvents(events, 2); + KTimerRemove(&timer); + return index == 0; + } +} + +void KWriterLockAssertLocked(KWriterLock *lock) { + if (lock->state == 0) { + KernelPanic("KWriterLock::AssertLocked - Unlocked.\n"); + } +} + +void KWriterLockAssertShared(KWriterLock *lock) { + if (lock->state == 0) { + KernelPanic("KWriterLock::AssertShared - Unlocked.\n"); + } else if (lock->state < 0) { + KernelPanic("KWriterLock::AssertShared - In exclusive mode.\n"); + } +} + +void KWriterLockAssertExclusive(KWriterLock *lock) { + if (lock->state == 0) { + KernelPanic("KWriterLock::AssertExclusive - Unlocked.\n"); + } else if (lock->state > 0) { + KernelPanic("KWriterLock::AssertExclusive - In shared mode, with %d readers.\n", lock->state); + } +} + +void KWriterLockReturn(KWriterLock *lock, bool write) { + KSpinlockAcquire(&scheduler.lock); + + if (lock->state == -1) { + if (!write) { + KernelPanic("KWriterLock::Return - Attempting to return shared access to an exclusively owned lock.\n"); + } + + lock->state = 0; + } else if (lock->state == 0) { + KernelPanic("KWriterLock::Return - Attempting to return access to an unowned lock.\n"); + } else { + if (write) { + KernelPanic("KWriterLock::Return - Attempting to return exclusive access to an shared lock.\n"); + } + + lock->state--; + } + + if (!lock->state) { + scheduler.NotifyObject(&lock->blockedThreads, true); + } + + KSpinlockRelease(&scheduler.lock); +} + +bool KWriterLockTake(KWriterLock *lock, bool write, bool poll) { + // TODO Preventing exclusive access starvation. + // TODO Do this without taking the scheduler's lock? + + bool done = false; + + Thread *thread = GetCurrentThread(); + + if (thread) { + thread->blocking.writerLock = lock; + thread->blocking.writerLockType = write; + } + + while (true) { + KSpinlockAcquire(&scheduler.lock); + + if (write) { + if (lock->state == 0) { + lock->state = -1; + done = true; +#ifdef DEBUG_BUILD + lock->exclusiveOwner = thread; +#endif + } + } else { + if (lock->state >= 0) { + lock->state++; + done = true; + } + } + + KSpinlockRelease(&scheduler.lock); + + if (poll || done) { + break; + } else { + if (!thread) { + KernelPanic("KWriterLock::Take - Scheduler not ready yet.\n"); + } + + thread->state = THREAD_WAITING_WRITER_LOCK; + ProcessorFakeTimerInterrupt(); + thread->state = THREAD_ACTIVE; + } + } + + return done; +} + +void KWriterLockTakeMultiple(KWriterLock **locks, size_t lockCount, bool write) { + uintptr_t i = 0, taken = 0; + + while (taken != lockCount) { + if (KWriterLockTake(locks[i], write, taken)) { + taken++, i++; + if (i == lockCount) i = 0; + } else { + intptr_t j = i - 1; + + while (taken) { + if (j == -1) j = lockCount - 1; + KWriterLockReturn(locks[j], write); + j--, taken--; + } + } + } +} + +void KWriterLockConvertExclusiveToShared(KWriterLock *lock) { + KSpinlockAcquire(&scheduler.lock); + KWriterLockAssertExclusive(lock); + lock->state = 1; + scheduler.NotifyObject(&lock->blockedThreads, true); + KSpinlockRelease(&scheduler.lock); +} + +#if 0 + +volatile int testState; +KWriterLock testWriterLock; + +void TestWriterLocksThread1(uintptr_t) { + KEvent wait = {}; + testWriterLock.Take(K_LOCK_SHARED); + EsPrint("-->1\n"); + testState = 1; + while (testState != 2); + wait.Wait(1000); + EsPrint("-->3\n"); + testWriterLock.Return(K_LOCK_SHARED); + testState = 3; +} + +void TestWriterLocksThread2(uintptr_t) { + while (testState != 1); + testWriterLock.Take(K_LOCK_SHARED); + EsPrint("-->2\n"); + testState = 2; + while (testState != 3); + testWriterLock.Return(K_LOCK_SHARED); +} + +void TestWriterLocksThread3(uintptr_t) { + while (testState < 1); + testWriterLock.Take(K_LOCK_EXCLUSIVE); + EsPrint("!!!!!!!!!!!!!!!!!!! %d\n", testState); + testWriterLock.Return(K_LOCK_EXCLUSIVE); + testState = 5; +} + +#define TEST_WRITER_LOCK_THREADS (4) + +void TestWriterLocksThread4(uintptr_t) { + __sync_fetch_and_add(&testState, 1); + + while (testState < 6 + TEST_WRITER_LOCK_THREADS) { + bool type = EsRandomU8() < 0xC0; + testWriterLock.Take(type); + testWriterLock.Return(type); + } + + __sync_fetch_and_add(&testState, 1); +} + +void TestWriterLocks() { + testState = 0; + EsPrint("TestWriterLocks...\n"); + KThreadCreate("Test1", TestWriterLocksThread1); + KThreadCreate("Test2", TestWriterLocksThread2); + KThreadCreate("Test3", TestWriterLocksThread3); + EsPrint("waiting for state 5...\n"); + while (testState != 5); + while (true) { + testState = 5; + for (int i = 0; i < TEST_WRITER_LOCK_THREADS; i++) { + KThreadCreate("Test", TestWriterLocksThread4, i); + } + while (testState != TEST_WRITER_LOCK_THREADS + 5); + EsPrint("All threads ready.\n"); + KEvent wait = {}; + wait.Wait(10000); + testState++; + while (testState != TEST_WRITER_LOCK_THREADS * 2 + 6); + EsPrint("Test complete!\n"); + } +} + +#endif + +void KTimerSet(KTimer *timer, uint64_t triggerInMs, KAsyncTaskCallback _callback, EsGeneric _argument) { + KSpinlockAcquire(&scheduler.lock); + EsDefer(KSpinlockRelease(&scheduler.lock)); + + // Reset the timer state. + + if (timer->item.list) { + scheduler.activeTimers.Remove(&timer->item); + } + + KEventReset(&timer->event); + + // Set the timer information. + + timer->triggerTimeMs = triggerInMs + scheduler.timeMs; + timer->callback = _callback; + timer->argument = _argument; + timer->item.thisItem = timer; + + // Add the timer to the list of active timers, keeping the list sorted by trigger time. + + LinkedItem *_timer = scheduler.activeTimers.firstItem; + + while (_timer) { + KTimer *timer2 = _timer->thisItem; + LinkedItem *next = _timer->nextItem; + + if (timer2->triggerTimeMs > timer->triggerTimeMs) { + break; // Insert before this timer. + } + + _timer = next; + } + + if (_timer) { + scheduler.activeTimers.InsertBefore(&timer->item, _timer); + } else { + scheduler.activeTimers.InsertEnd(&timer->item); + } +} + +void KTimerRemove(KTimer *timer) { + KSpinlockAcquire(&scheduler.lock); + EsDefer(KSpinlockRelease(&scheduler.lock)); + + if (timer->callback) { + KernelPanic("KTimer::Remove - Timers with callbacks cannot be removed.\n"); + } + + if (timer->item.list) { + scheduler.activeTimers.Remove(&timer->item); + } +} + +void Scheduler::WaitMutex(KMutex *mutex) { + Thread *thread = GetCurrentThread(); + + if (thread->state != THREAD_ACTIVE) { + KernelPanic("Scheduler::WaitMutex - Attempting to wait on a mutex in a non-active thread.\n"); + } + + KSpinlockAcquire(&lock); + + thread->state = THREAD_WAITING_MUTEX; + thread->blocking.mutex = mutex; + + // Is the owner of this mutex executing? + // If not, there's no point in spinning on it. + bool spin = mutex && mutex->owner && mutex->owner->executing; + + KSpinlockRelease(&lock); + + if (!spin && thread->blocking.mutex->owner) { + ProcessorFakeTimerInterrupt(); + } + + // Early exit if this is a user request to block the thread and the thread is terminating. + while ((!thread->terminating || thread->terminatableState != THREAD_USER_BLOCK_REQUEST) && mutex->owner) { + thread->state = THREAD_WAITING_MUTEX; + } + + thread->state = THREAD_ACTIVE; +} + +uintptr_t Scheduler::WaitEvents(KEvent **events, size_t count) { + if (count > ES_MAX_WAIT_COUNT) { + KernelPanic("Scheduler::WaitEvents - count (%d) > ES_MAX_WAIT_COUNT (%d)\n", count, ES_MAX_WAIT_COUNT); + } else if (!count) { + KernelPanic("Scheduler::WaitEvents - Count is 0.\n"); + } else if (!ProcessorAreInterruptsEnabled()) { + KernelPanic("Scheduler::WaitEvents - Interrupts disabled.\n"); + } + + Thread *thread = GetCurrentThread(); + thread->blocking.eventCount = count; + + LinkedItem blockedItems[count]; // Max size 16 * 32 = 512. + EsMemoryZero(blockedItems, count * sizeof(LinkedItem)); + thread->blockedItems = blockedItems; + EsDefer(thread->blockedItems = nullptr); + + for (uintptr_t i = 0; i < count; i++) { + thread->blockedItems[i].thisItem = thread; + thread->blocking.events[i] = events[i]; + } + + while (!thread->terminating || thread->terminatableState != THREAD_USER_BLOCK_REQUEST) { + thread->state = THREAD_WAITING_EVENT; + + for (uintptr_t i = 0; i < count; i++) { + if (events[i]->autoReset) { + if (events[i]->state) { + thread->state = THREAD_ACTIVE; + + if (__sync_val_compare_and_swap(&events[i]->state, true, false)) { + return i; + } + + thread->state = THREAD_WAITING_EVENT; + } + } else { + if (events[i]->state) { + thread->state = THREAD_ACTIVE; + return i; + } + } + } + + ProcessorFakeTimerInterrupt(); + } + + return -1; // Exited from termination. +} + +uintptr_t KWaitEvents(KEvent **events, size_t count) { + return scheduler.WaitEvents(events, count); +} + +void Scheduler::UnblockThread(Thread *unblockedThread, Thread *previousMutexOwner) { + KSpinlockAssertLocked(&lock); + + if (unblockedThread->state == THREAD_WAITING_MUTEX) { + if (unblockedThread->item.list) { + // If we get here from KMutex::Release -> Scheduler::NotifyObject -> Scheduler::UnblockedThread, + // the mutex owner has already been cleared to nullptr, so use the previousMutexOwner parameter. + // But if we get here from Scheduler::TerminateThread, the mutex wasn't released; + // rather, the waiting thread was unblocked as it is in the WAIT system call, but needs to terminate. + + if (!previousMutexOwner) { + KMutex *mutex = EsContainerOf(KMutex, blockedThreads, unblockedThread->item.list); + + if (&mutex->blockedThreads != unblockedThread->item.list) { + KernelPanic("Scheduler::UnblockThread - Unblocked thread %x was not in a mutex blockedThreads list.\n", + unblockedThread); + } + + previousMutexOwner = mutex->owner; + } + + if (!previousMutexOwner->blockedThreadPriorities[unblockedThread->priority]) { + KernelPanic("Scheduler::UnblockThread - blockedThreadPriorities was zero (%x/%x).\n", + unblockedThread, previousMutexOwner); + } + + previousMutexOwner->blockedThreadPriorities[unblockedThread->priority]--; + MaybeUpdateActiveList(previousMutexOwner); + + unblockedThread->item.RemoveFromList(); + } + } else if (unblockedThread->state == THREAD_WAITING_EVENT) { + for (uintptr_t i = 0; i < unblockedThread->blocking.eventCount; i++) { + if (unblockedThread->blockedItems[i].list) { + unblockedThread->blockedItems[i].RemoveFromList(); + } + } + } else if (unblockedThread->state == THREAD_WAITING_WRITER_LOCK) { + if (unblockedThread->item.list) { + KWriterLock *lock = EsContainerOf(KWriterLock, blockedThreads, unblockedThread->item.list); + + if (&lock->blockedThreads != unblockedThread->item.list) { + KernelPanic("Scheduler::UnblockThread - Unblocked thread %x was not in a writer lock blockedThreads list.\n", + unblockedThread); + } + + if ((unblockedThread->blocking.writerLockType == K_LOCK_SHARED && lock->state >= 0) + || (unblockedThread->blocking.writerLockType == K_LOCK_EXCLUSIVE && lock->state == 0)) { + unblockedThread->item.RemoveFromList(); + } + } + } else { + KernelPanic("Scheduler::UnblockedThread - Blocked thread in invalid state %d.\n", + unblockedThread->state); + } + + unblockedThread->state = THREAD_ACTIVE; + + if (!unblockedThread->executing) { + // Put the unblocked thread at the start of the activeThreads list + // so that it is immediately executed when the scheduler yields. + AddActiveThread(unblockedThread, true); + } + + // TODO If any processors are idleing, send them a yield IPI. +} + +void Scheduler::NotifyObject(LinkedList *blockedThreads, bool unblockAll, Thread *previousMutexOwner) { + KSpinlockAssertLocked(&lock); + + LinkedItem *unblockedItem = blockedThreads->firstItem; + + if (!unblockedItem) { + // There weren't any threads blocking on the object. + return; + } + + do { + LinkedItem *nextUnblockedItem = unblockedItem->nextItem; + Thread *unblockedThread = unblockedItem->thisItem; + UnblockThread(unblockedThread, previousMutexOwner); + unblockedItem = nextUnblockedItem; + } while (unblockAll && unblockedItem); +} + +#endif diff --git a/kernel/syscall.cpp b/kernel/syscall.cpp new file mode 100644 index 0000000..c00f39a --- /dev/null +++ b/kernel/syscall.cpp @@ -0,0 +1,2095 @@ +// TODO Replace ES_ERROR_UNKNOWN with proper errors. +// TODO Clean up the return values for system calls; with FATAL_ERRORs there should need to be less error codes returned. +// TODO Close handles if OpenHandle fails or SendMessage fails. +// TODO If a file system call fails with an error indicating the file system is corrupted, or a drive is failing, report the problem to the user. + +#ifndef IMPLEMENTATION + +KMutex eventForwardMutex; + +uintptr_t DoSyscall(EsSyscallType index, + uintptr_t argument0, uintptr_t argument1, + uintptr_t argument2, uintptr_t argument3, + uint64_t flags, bool *fatal, uintptr_t *userStackPointer); + +#define DO_SYSCALL_BATCHED (2) + +struct MessageQueue { + bool SendMessage(void *target, EsMessage *message); // Returns false if the message queue is full. + bool SendMessage(_EsMessageWithObject *message); // Returns false if the message queue is full. + bool GetMessage(_EsMessageWithObject *message); + +#define MESSAGE_QUEUE_MAX_LENGTH (4096) + Array<_EsMessageWithObject, K_FIXED> messages; + + uintptr_t mouseMovedMessage, + windowResizedMessage, + eyedropResultMessage, + keyRepeatMessage; + + bool pinged; + + KMutex mutex; + KEvent notEmpty; +}; + +#endif + +#ifdef IMPLEMENTATION + +bool MessageQueue::SendMessage(void *object, EsMessage *_message) { + // TODO Remove unnecessary copy. + _EsMessageWithObject message = { object, *_message }; + return SendMessage(&message); +} + +bool MessageQueue::SendMessage(_EsMessageWithObject *_message) { + // TODO Don't send messages if the process has been terminated. + + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (messages.Length() == MESSAGE_QUEUE_MAX_LENGTH) { + KernelLog(LOG_ERROR, "Messages", "message dropped", "Message of type %d and target %x has been dropped because queue %x was full.\n", + _message->message.type, _message->object, this); + return false; + } + +#define MERGE_MESSAGES(variable, change) \ + do { \ + if (variable && messages[variable - 1].object == _message->object) { \ + if (change) EsMemoryCopy(&messages[variable - 1], _message, sizeof(_EsMessageWithObject)); \ + } else if (messages.AddPointer(_message)) { \ + variable = messages.Length(); \ + } else { \ + return false; \ + } \ + } while (0) + + // NOTE Don't forget to update GetMessage with the merged messages! + + if (_message->message.type == ES_MSG_MOUSE_MOVED) { + MERGE_MESSAGES(mouseMovedMessage, true); + } else if (_message->message.type == ES_MSG_WINDOW_RESIZED) { + MERGE_MESSAGES(windowResizedMessage, true); + } else if (_message->message.type == ES_MSG_EYEDROP_REPORT) { + MERGE_MESSAGES(eyedropResultMessage, true); + } else if (_message->message.type == ES_MSG_KEY_DOWN && _message->message.keyboard.repeat) { + MERGE_MESSAGES(keyRepeatMessage, false); + } else { + if (!messages.AddPointer(_message)) { + return false; + } + + if (_message->message.type == ES_MSG_PING) { + pinged = true; + } + } + + KEventSet(¬Empty, false, true); + + return true; +} + +bool MessageQueue::GetMessage(_EsMessageWithObject *_message) { + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (!messages.Length()) { + return false; + } + + *_message = messages[0]; + messages.Delete(0); + + if (mouseMovedMessage) mouseMovedMessage--; + if (windowResizedMessage) windowResizedMessage--; + if (eyedropResultMessage) eyedropResultMessage--; + if (keyRepeatMessage) keyRepeatMessage--; + + pinged = false; + + if (!messages.Length()) { + KEventReset(¬Empty); + } + + return true; +} + +#define CHECK_OBJECT(x) if (!x.valid) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_HANDLE, !x.softFailure); else x.checked = true +#define SYSCALL_BUFFER_LIMIT (64 * 1024 * 1024) // To prevent overflow and DOS attacks. +#define SYSCALL_BUFFER(address, length, index, write) \ + MMRegion *_region ## index = MMFindAndPinRegion(currentVMM, (address), (length)); \ + if (!_region ## index) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); \ + EsDefer(if (_region ## index) MMUnpinRegion(currentVMM, _region ## index)); \ + if (write && (_region ## index->flags & MM_REGION_READ_ONLY) && (~_region ## index->flags & MM_REGION_COPY_ON_WRITE)) \ + SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); +#define SYSCALL_HANDLE(handle, type, __object, index) \ + KObject _object ## index(currentProcess, handle, type); \ + CHECK_OBJECT(_object ## index); \ + *((void **) &__object) = (_object ## index).object; +#define SYSCALL_READ(destination, source, length) \ + do { if ((length) > 0) { \ + SYSCALL_BUFFER((uintptr_t) (source), (length), 0, false); \ + EsMemoryCopy((void *) (destination), (const void *) (source), (length)); \ + __sync_synchronize(); \ + }} while (0) +#define SYSCALL_READ_HEAP(destination, source, length) \ + if ((length) > 0) { \ + void *x = EsHeapAllocate((length), false, K_FIXED); \ + if (!x) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); \ + bool success = false; \ + EsDefer(if (!success) EsHeapFree(x, 0, K_FIXED);); \ + SYSCALL_BUFFER((uintptr_t) (source), (length), 0, false); \ + *((void **) &(destination)) = x; \ + EsMemoryCopy((void *) (destination), (const void *) (source), (length)); \ + success = true; \ + __sync_synchronize(); \ + } else destination = nullptr; EsDefer(EsHeapFree(destination, 0, K_FIXED)) +#define SYSCALL_WRITE(destination, source, length) \ + do { \ + __sync_synchronize(); \ + SYSCALL_BUFFER((uintptr_t) (destination), (length), 0, true); \ + EsMemoryCopy((void *) (destination), (const void *) (source), (length)); \ + } while (0) +#define SYSCALL_ARGUMENTS uintptr_t argument0, uintptr_t argument1, uintptr_t argument2, uintptr_t argument3, \ + Thread *currentThread, Process *currentProcess, MMSpace *currentVMM, uintptr_t *userStackPointer, bool *fatalError +#define SYSCALL_IMPLEMENT(_type) uintptr_t Do ## _type ( SYSCALL_ARGUMENTS ) +#define SYSCALL_RETURN(value, fatal) do { *fatalError = fatal; return (value); } while (0) +#define SYSCALL_PERMISSION(x) do { if ((x) != (currentProcess->permissions & (x))) { *fatalError = true; return ES_FATAL_ERROR_INSUFFICIENT_PERMISSIONS; } } while (0) +typedef uintptr_t (*SyscallFunction)(SYSCALL_ARGUMENTS); +#pragma GCC diagnostic ignored "-Wunused-parameter" push + +SYSCALL_IMPLEMENT(ES_SYSCALL_COUNT) { + SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PRINT) { + char *buffer; + if (argument1 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_READ_HEAP(buffer, argument0, argument1); + EsPrint("%s", argument1, buffer); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_ALLOCATE) { + EsMemoryProtection protection = (EsMemoryProtection) argument2; + uint32_t flags = MM_REGION_USER; + if (protection == ES_MEMORY_PROTECTION_READ_ONLY) flags |= MM_REGION_READ_ONLY; + if (protection == ES_MEMORY_PROTECTION_EXECUTABLE) flags |= MM_REGION_EXECUTABLE; + uintptr_t address = (uintptr_t) MMStandardAllocate(currentVMM, argument0, flags, nullptr, argument1 & ES_MEMORY_RESERVE_COMMIT_ALL); + SYSCALL_RETURN(address, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_FREE) { + if (!MMFree(currentVMM, (void *) argument0, argument1, true /* only allow freeing regions marked with MM_REGION_USER */)) { + SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_MEMORY_REGION, true); + } + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_COMMIT) { + SYSCALL_BUFFER(argument0 << K_PAGE_BITS, argument1 << K_PAGE_BITS, 0, false); + + argument0 -= _region0->baseAddress >> K_PAGE_BITS; + + if (argument0 >= _region0->pageCount + || argument1 > _region0->pageCount - argument0 + || (~_region0->flags & MM_REGION_NORMAL) + || (~_region0->flags & MM_REGION_USER) + || !argument1) { + SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_MEMORY_REGION, true); + } + + bool success = false; + + if (argument2 == 0) { + KMutexAcquire(¤tVMM->reserveMutex); + success = MMCommitRange(currentVMM, _region0, argument0, argument1); + KMutexRelease(¤tVMM->reserveMutex); + } else if (argument2 == 1) { + KMutexAcquire(¤tVMM->reserveMutex); + success = MMDecommitRange(currentVMM, _region0, argument0, argument1); + KMutexRelease(¤tVMM->reserveMutex); + } else { + SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true); + } + + SYSCALL_RETURN(success ? ES_SUCCESS : ES_ERROR_INSUFFICIENT_RESOURCES, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_FAULT_RANGE) { + uintptr_t start = argument0 & ~(K_PAGE_SIZE - 1); + uintptr_t end = (argument0 + argument1 - 1) & ~(K_PAGE_SIZE - 1); + + for (uintptr_t page = start; page <= end; page += K_PAGE_SIZE) { + if (!MMArchHandlePageFault(page, ES_FLAGS_DEFAULT)) { + SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + } + } + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_CREATE) { + SYSCALL_PERMISSION(ES_PERMISSION_PROCESS_CREATE); + + EsProcessCreationArguments arguments; + SYSCALL_READ(&arguments, argument0, sizeof(EsProcessCreationArguments)); + + if (arguments.permissions == ES_PERMISSION_INHERIT) { + arguments.permissions = currentProcess->permissions; + } + + if (arguments.permissions & ~currentProcess->permissions) { + SYSCALL_RETURN(ES_FATAL_ERROR_INSUFFICIENT_PERMISSIONS, true); + } + + KObject executableObject(currentProcess, arguments.executable, KERNEL_OBJECT_NODE); + CHECK_OBJECT(executableObject); + + if (((KNode *) executableObject.object)->directoryEntry->type != ES_NODE_FILE) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + } + + EsProcessInformation processInformation; + EsMemoryZero(&processInformation, sizeof(EsProcessInformation)); + + Process *process = scheduler.SpawnProcess(); + + if (!process) { + SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + } + + process->creationFlags = arguments.flags; + process->creationArguments[CREATION_ARGUMENT_MAIN] = arguments.creationArgument.u; + process->permissions = arguments.permissions; + + // TODO Free the process object if something fails here. + + if (arguments.environmentBlockBytes) { + if (arguments.environmentBlockBytes > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_BUFFER((uintptr_t) arguments.environmentBlock, arguments.environmentBlockBytes, 1, false); + process->creationArguments[CREATION_ARGUMENT_ENVIRONMENT] = MakeConstantBuffer(arguments.environmentBlock, arguments.environmentBlockBytes, process); + } + + if (arguments.initialMountPointCount) { + if (arguments.initialMountPointCount > ES_MOUNT_POINT_MAX_COUNT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + + EsMountPoint *mountPoints = (EsMountPoint *) EsHeapAllocate(arguments.initialMountPointCount * sizeof(EsMountPoint), false, K_FIXED); + EsDefer(EsHeapFree(mountPoints, arguments.initialMountPointCount * sizeof(EsMountPoint), K_FIXED)); + SYSCALL_READ(mountPoints, (uintptr_t) arguments.initialMountPoints, arguments.initialMountPointCount * sizeof(EsMountPoint)); + + for (uintptr_t i = 0; i < arguments.initialMountPointCount; i++) { + // Open handles to the mount points for the new process. + // TODO Handling errors when opening handles. + KObject object(currentProcess, mountPoints[i].base, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + if (!mountPoints[i].write) object.flags &= ~_ES_NODE_DIRECTORY_WRITE; + OpenHandleToObject(object.object, object.type, object.flags); + mountPoints[i].base = process->handleTable.OpenHandle(object.object, object.flags, object.type); + } + + process->creationArguments[CREATION_ARGUMENT_INITIAL_MOUNT_POINTS] + = MakeConstantBuffer(mountPoints, arguments.initialMountPointCount * sizeof(EsMountPoint), process); + } + + if (!process->StartWithNode((KNode *) executableObject.object)) { + CloseHandleToObject(process, KERNEL_OBJECT_PROCESS); + SYSCALL_RETURN(ES_ERROR_UNKNOWN, false); + } + + processInformation.handle = currentProcess->handleTable.OpenHandle(process, 0, KERNEL_OBJECT_PROCESS); + processInformation.pid = process->id; + + processInformation.mainThread.handle = currentProcess->handleTable.OpenHandle(process->executableMainThread, 0, KERNEL_OBJECT_THREAD); + processInformation.mainThread.tid = process->executableMainThread->id; + + SYSCALL_WRITE(argument2, &processInformation, sizeof(EsProcessInformation)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_CREATION_ARGUMENT) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(object); + + Process *process = (Process *) object.object; + + if (argument1 >= sizeof(process->creationArguments) / sizeof(process->creationArguments[0])) { + SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + } + + SYSCALL_RETURN(process->creationArguments[argument1], false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_FORCE_UPDATE) { + if (argument0) { + SYSCALL_PERMISSION(ES_PERMISSION_SCREEN_MODIFY); + } + + KMutexAcquire(&windowManager.mutex); + + if (argument0) { + windowManager.Redraw(ES_POINT(0, 0), graphics.frameBuffer.width, graphics.frameBuffer.height, nullptr); + } + + GraphicsUpdateScreen(); + KMutexRelease(&windowManager.mutex); + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EYEDROP_START) { + KObject _avoid(currentProcess, argument1, KERNEL_OBJECT_WINDOW); + CHECK_OBJECT(_avoid); + Window *avoid = (Window *) _avoid.object; + + windowManager.StartEyedrop(argument0, avoid, argument2); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_GET) { + _EsMessageWithObject message; + EsMemoryZero(&message, sizeof(_EsMessageWithObject)); + + if (currentProcess->messageQueue.GetMessage(&message)) { + SYSCALL_WRITE(argument0, &message, sizeof(_EsMessageWithObject)); + SYSCALL_RETURN(ES_SUCCESS, false); + } else { + SYSCALL_RETURN(ES_ERROR_NO_MESSAGES_AVAILABLE, false); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_WAIT) { + currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST; + KEventWait(¤tProcess->messageQueue.notEmpty, argument0 /* timeout */); + currentThread->terminatableState = THREAD_IN_SYSCALL; + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_CREATE) { + SYSCALL_PERMISSION(ES_PERMISSION_WINDOW_MANAGER); + + if (argument0 == ES_WINDOW_NORMAL) { + void *_window = windowManager.CreateEmbeddedWindow(currentProcess, (void *) argument2); + SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(_window, 0, KERNEL_OBJECT_EMBEDDED_WINDOW), false); + } else { + void *_window = windowManager.CreateWindow(currentProcess, (void *) argument2, (EsWindowStyle) argument0); + + if (!_window) { + SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + } else { + SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(_window, 0, KERNEL_OBJECT_WINDOW), false); + } + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_CLOSE) { + KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); + CHECK_OBJECT(_window); + KMutexAcquire(&windowManager.mutex); + + if (_window.type == KERNEL_OBJECT_EMBEDDED_WINDOW) { + EmbeddedWindow *window = (EmbeddedWindow *) _window.object; + window->Close(); + } else { + Window *window = (Window *) _window.object; + window->Close(); + } + + KMutexRelease(&windowManager.mutex); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_SET_OBJECT) { + EmbeddedWindow *window; + SYSCALL_HANDLE(argument0, KERNEL_OBJECT_EMBEDDED_WINDOW, window, 1); + + if (window->owner != currentProcess) { + // TODO Permissions. + } + + void *old = window->apiWindow; + window->apiWindow = (void *) argument2; + __sync_synchronize(); + + KMutexAcquire(&windowManager.mutex); + + if (window->container) { + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_WINDOW_RESIZED; + int embedWidth = window->container->width - WINDOW_INSET * 2; + int embedHeight = window->container->height - WINDOW_INSET * 2 - CONTAINER_TAB_BAND_HEIGHT; + message.windowResized.content = ES_RECT_4(0, embedWidth, 0, embedHeight); + window->owner->messageQueue.SendMessage((void *) argument2, &message); + } + + KMutexRelease(&windowManager.mutex); + + SYSCALL_RETURN((uintptr_t) old, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_SET_PROPERTY) { + uint8_t property = argument3; + + KObject _window(currentProcess, argument0, (property & 0x80) ? KERNEL_OBJECT_EMBEDDED_WINDOW : KERNEL_OBJECT_WINDOW); + CHECK_OBJECT(_window); + Window *window = (Window *) _window.object; + EmbeddedWindow *embed = (EmbeddedWindow *) _window.object; + + if (property == ES_WINDOW_PROPERTY_SOLID) { + window->solid = (argument1 & ES_WINDOW_SOLID_TRUE) != 0; + window->noClickActivate = (argument1 & ES_WINDOW_SOLID_NO_ACTIVATE) != 0; + window->noBringToFront = (argument1 & ES_WINDOW_SOLID_NO_BRING_TO_FRONT) != 0; + window->solidInsets = ES_RECT_1I(argument2); + KMutexAcquire(&windowManager.mutex); + windowManager.MoveCursor(0, 0); + KMutexRelease(&windowManager.mutex); + } else if (property == ES_WINDOW_PROPERTY_OPAQUE_BOUNDS) { + SYSCALL_READ(&window->opaqueBounds, argument1, sizeof(EsRectangle)); + } else if (property == ES_WINDOW_PROPERTY_BLUR_BOUNDS) { + SYSCALL_READ(&window->blurBounds, argument1, sizeof(EsRectangle)); + } else if (property == ES_WINDOW_PROPERTY_ALPHA) { + window->alpha = argument1 & 0xFF; + } else if (property == ES_WINDOW_PROPERTY_FOCUSED) { + KMutexAcquire(&windowManager.mutex); + windowManager.ActivateWindow(window); + windowManager.MoveCursor(0, 0); + KMutexRelease(&windowManager.mutex); + } else if (property == ES_WINDOW_PROPERTY_MATERIAL) { + window->material = argument1; + } else if (property == ES_WINDOW_PROPERTY_EMBED) { + KObject _embed(currentProcess, argument1, KERNEL_OBJECT_EMBEDDED_WINDOW | KERNEL_OBJECT_NONE); + CHECK_OBJECT(_embed); + EmbeddedWindow *embed = (EmbeddedWindow *) _embed.object; + KMutexAcquire(&windowManager.mutex); + window->SetEmbed(embed); + KMutexRelease(&windowManager.mutex); + } else if (property == ES_WINDOW_PROPERTY_OBJECT) { + if (embed->owner != currentProcess) { + // TODO Permissions. + } + + embed->apiWindow = (void *) argument2; + __sync_synchronize(); + + KMutexAcquire(&windowManager.mutex); + + if (embed->container) { + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_WINDOW_RESIZED; + int embedWidth = embed->container->width - WINDOW_INSET * 2; + int embedHeight = embed->container->height - WINDOW_INSET * 2 - CONTAINER_TAB_BAND_HEIGHT; + message.windowResized.content = ES_RECT_4(0, embedWidth, 0, embedHeight); + embed->owner->messageQueue.SendMessage((void *) argument2, &message); + } + + KMutexRelease(&windowManager.mutex); + } else if (property == ES_WINDOW_PROPERTY_EMBED_OWNER) { + Process *process; + SYSCALL_HANDLE(argument1, KERNEL_OBJECT_PROCESS, process, 2); + OpenHandleToObject(embed, KERNEL_OBJECT_EMBEDDED_WINDOW); + EsHandle handle = process->handleTable.OpenHandle(embed, 0, KERNEL_OBJECT_EMBEDDED_WINDOW); + KMutexAcquire(&windowManager.mutex); + embed->SetEmbedOwner(process); + KMutexRelease(&windowManager.mutex); + SYSCALL_RETURN(handle, false); + } else if (property == ES_WINDOW_PROPERTY_RESIZE_CLEAR_COLOR) { + embed->resizeClearColor = argument1; + } else { + SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + } + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_REDRAW) { + KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW); + CHECK_OBJECT(_window); + Window *window = (Window *) _window.object; + KMutexAcquire(&windowManager.mutex); + window->Update(nullptr, true); + GraphicsUpdateScreen(); + KMutexRelease(&windowManager.mutex); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_SET_BITS) { + KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); + CHECK_OBJECT(_window); + + EsRectangle region; + SYSCALL_READ(®ion, argument1, sizeof(EsRectangle)); + + if (!ES_RECT_VALID(region)) { + SYSCALL_RETURN(ES_SUCCESS, false); + } + + if (region.l < 0 || region.r > (int32_t) graphics.width * 2 + || region.t < 0 || region.b > (int32_t) graphics.height * 2 + || region.l >= region.r || region.t >= region.b) { + SYSCALL_RETURN(ES_SUCCESS, false); + } + + bool isEmbed = _window.type == KERNEL_OBJECT_EMBEDDED_WINDOW; + Window *window = isEmbed ? ((EmbeddedWindow *) _window.object)->container : ((Window *) _window.object); + Surface *surface = &window->surface; + + if (!window || (isEmbed && currentProcess != ((EmbeddedWindow *) _window.object)->owner)) { + SYSCALL_RETURN(ES_SUCCESS, false); + } + + if (isEmbed) { + region = Translate(region, WINDOW_INSET, WINDOW_INSET + CONTAINER_TAB_BAND_HEIGHT); + } + + if (argument3 == WINDOW_SET_BITS_SCROLL_VERTICAL || argument3 == WINDOW_SET_BITS_SCROLL_HORIZONTAL) { + ptrdiff_t scrollDelta = argument2; + bool scrollVertical = argument3 == WINDOW_SET_BITS_SCROLL_VERTICAL; + + if (scrollVertical) { + if (scrollDelta < 0) region.b += scrollDelta; + else region.t += scrollDelta; + } else { + if (scrollDelta < 0) region.r += scrollDelta; + else region.l += scrollDelta; + } + + KMutexAcquire(&windowManager.mutex); + + if (window->closed + || region.l < 0 || region.r > (int32_t) surface->width + || region.t < 0 || region.b > (int32_t) surface->height + || region.l >= region.r || region.t >= region.b) { + } else { + surface->Scroll(region, scrollDelta, scrollVertical); + window->Update(®ion, true); + window->queuedScrollUpdate = true; + // Don't update the screen until the rest of the window is painted. + } + + KMutexRelease(&windowManager.mutex); + } else { + bool skipUpdate = false; + SYSCALL_BUFFER(argument2, Width(region) * Height(region) * 4, 1, false); + KMutexAcquire(&windowManager.mutex); + + bool resizeQueued = false; + + if (argument3 == WINDOW_SET_BITS_AFTER_RESIZE && windowManager.resizeWindow == window) { + if (isEmbed) windowManager.resizeReceivedBitsFromEmbed = true; + else windowManager.resizeReceivedBitsFromContainer = true; + + if (windowManager.resizeReceivedBitsFromContainer && windowManager.resizeReceivedBitsFromEmbed) { + // Resize complete. + resizeQueued = windowManager.resizeQueued; + windowManager.resizeQueued = false; + windowManager.resizeWindow = nullptr; + windowManager.resizeSlow = KGetTimeInMs() - windowManager.resizeStartTimeStampMs >= RESIZE_SLOW_THRESHOLD + || windowManager.inspectorWindowCount /* HACK anti-flicker logic interfers with the inspector's logging */; + +#if 0 + EsPrint("Resize complete in %dms%z.\n", KGetTimeInMs() - windowManager.resizeStartTimeStampMs, windowManager.resizeSlow ? " (slow)" : ""); +#endif + } + } + + if (window->closed) { + skipUpdate = true; + } else { + uintptr_t stride = Width(region) * 4; + EsRectangle clippedRegion = EsRectangleIntersection(region, ES_RECT_2S(surface->width, surface->height)); + + if (argument3 != WINDOW_SET_BITS_AFTER_RESIZE) { + skipUpdate = window->UpdateDirect((K_USER_BUFFER uint32_t *) argument2, stride, clippedRegion); + } + +#define SET_BITS_REGION(...) { \ + EsRectangle subRegion = EsRectangleIntersection(clippedRegion, ES_RECT_4(__VA_ARGS__)); \ + if (ES_RECT_VALID(subRegion)) { surface->SetBits((K_USER_BUFFER const uint8_t *) argument2 \ + + stride * (subRegion.t - clippedRegion.t) + 4 * (subRegion.l - clippedRegion.l), stride, subRegion); } } + + if (window->style == ES_WINDOW_CONTAINER && !isEmbed) { + SET_BITS_REGION(0, window->width, 0, CONTAINER_TAB_BAND_HEIGHT + WINDOW_INSET); + SET_BITS_REGION(0, WINDOW_INSET, CONTAINER_TAB_BAND_HEIGHT + WINDOW_INSET, window->height - WINDOW_INSET); + SET_BITS_REGION(window->width - WINDOW_INSET, window->width, CONTAINER_TAB_BAND_HEIGHT + WINDOW_INSET, window->height - WINDOW_INSET); + SET_BITS_REGION(0, window->width, window->height - WINDOW_INSET, window->height); + } else if (window->style == ES_WINDOW_CONTAINER && isEmbed) { + SET_BITS_REGION(WINDOW_INSET, window->width - WINDOW_INSET, WINDOW_INSET + CONTAINER_TAB_BAND_HEIGHT, window->height - WINDOW_INSET); + } else { + SET_BITS_REGION(0, window->width, 0, window->height); + } + } + + window->Update(®ion, !skipUpdate); + + if (!skipUpdate || window->queuedScrollUpdate) { + window->queuedScrollUpdate = false; + GraphicsUpdateScreen(); + } + + if (resizeQueued) { + window->Move(windowManager.resizeQueuedRectangle, ES_WINDOW_MOVE_DYNAMIC); + } + + KMutexRelease(&windowManager.mutex); + } + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CLIPBOARD_ADD) { + SYSCALL_PERMISSION(ES_PERMISSION_WINDOW_MANAGER); + + if (argument0 != ES_CLIPBOARD_PRIMARY) { + SYSCALL_RETURN(ES_ERROR_INVALID_CLIPBOARD, false); + } + + EsError error = primaryClipboard.Add((uintptr_t) argument1, (const void *) argument2, argument3); + SYSCALL_RETURN(error, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CLIPBOARD_HAS) { + SYSCALL_PERMISSION(ES_PERMISSION_WINDOW_MANAGER); + + if (argument0 != ES_CLIPBOARD_PRIMARY) { + SYSCALL_RETURN(ES_ERROR_INVALID_CLIPBOARD, false); + } + + bool result = primaryClipboard.Has((uintptr_t) argument1); + SYSCALL_RETURN(result, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CLIPBOARD_READ) { + SYSCALL_PERMISSION(ES_PERMISSION_WINDOW_MANAGER); + + if (argument0 != ES_CLIPBOARD_PRIMARY) { + SYSCALL_RETURN(ES_ERROR_INVALID_CLIPBOARD, false); + } + + EsHandle handle = primaryClipboard.Read((uintptr_t) argument1, (size_t *) argument3, currentProcess); + SYSCALL_RETURN(handle, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_CREATE) { + KEvent *event = (KEvent *) EsHeapAllocate(sizeof(KEvent), true, K_FIXED); + if (!event) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + event->handles = 1; + event->autoReset = argument0; + SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(event, 0, KERNEL_OBJECT_EVENT), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_HANDLE_CLOSE) { + if (!currentProcess->handleTable.CloseHandle(argument0)) { + SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_HANDLE, true); + } + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_TERMINATE) { + bool self = false; + + { + KObject _thread(currentProcess, argument0, KERNEL_OBJECT_THREAD); + CHECK_OBJECT(_thread); + Thread *thread = (Thread *) _thread.object; + + if (thread == currentThread) self = true; + else scheduler.TerminateThread(thread); + } + + if (self) scheduler.TerminateThread(currentThread); + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_TERMINATE) { + // TODO Prevent the termination of the kernel/desktop. + + bool self = false; + + { + KObject _process(currentProcess, argument0, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(_process); + Process *process = (Process *) _process.object; + + if (process == currentProcess) self = true; + else scheduler.TerminateProcess(process, argument1); + } + + if (self) scheduler.TerminateProcess(currentProcess, argument1); + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_CREATE) { + EsThreadInformation thread; + EsMemoryZero(&thread, sizeof(EsThreadInformation)); + Thread *threadObject = scheduler.SpawnThread("Syscall", argument0, argument3, SPAWN_THREAD_USERLAND, currentProcess, argument1); + + if (!threadObject) { + SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + } + + // Register processObject as a handle. + thread.handle = currentProcess->handleTable.OpenHandle(threadObject, 0, KERNEL_OBJECT_THREAD); + thread.tid = threadObject->id; + + SYSCALL_WRITE(argument2, &thread, sizeof(EsThreadInformation)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_OPEN) { + if (argument0 > ES_SHARED_MEMORY_MAXIMUM_SIZE) SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + if (argument1 && !argument2) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + if (argument2 > ES_SHARED_MEMORY_NAME_MAX_LENGTH) SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + + char *name; + if (argument2 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_READ_HEAP(name, argument1, argument2); + + MMSharedRegion *region = MMSharedOpenRegion(name, argument2, argument0, argument3); + if (!region) SYSCALL_RETURN(ES_INVALID_HANDLE, false); + + SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(region, 0, KERNEL_OBJECT_SHMEM), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_MAP_OBJECT) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_SHMEM | KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + + if (object.type == KERNEL_OBJECT_SHMEM) { + // TODO Access permissions and modes. + MMSharedRegion *region = (MMSharedRegion *) object.object; + + if (argument2 == ES_MAP_OBJECT_ALL) { + argument2 = region->sizeBytes; + } + + uintptr_t address = (uintptr_t) MMMapShared(currentVMM, region, argument1, argument2, MM_REGION_USER); + SYSCALL_RETURN(address, false); + } else if (object.type == KERNEL_OBJECT_NODE) { + KNode *file = (KNode *) object.object; + + if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + + if (argument3 == ES_MAP_OBJECT_READ_WRITE) { + if (!(object.flags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE))) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); + } + } else { + if (!(object.flags & (ES_FILE_READ | ES_FILE_READ_SHARED))) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); + } + } + + if (argument2 == ES_MAP_OBJECT_ALL) { + argument2 = file->directoryEntry->totalSize; + } + + uintptr_t address = (uintptr_t) MMMapFile(currentVMM, (FSFile *) file, argument1, argument2, argument3, nullptr, 0, MM_REGION_USER); + SYSCALL_RETURN(address, false); + } + + KernelPanic("ES_SYSCALL_MEMORY_MAP_OBJECT - Unhandled case.\n"); + SYSCALL_RETURN(0, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CONSTANT_BUFFER_SHARE) { + KObject buffer(currentProcess, argument0, KERNEL_OBJECT_CONSTANT_BUFFER); + CHECK_OBJECT(buffer); + ConstantBuffer *object = (ConstantBuffer *) buffer.object; + + KObject process(currentProcess, argument1, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(process); + + SYSCALL_RETURN(MakeConstantBuffer(object + 1, object->bytes, (Process *) process.object), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CONSTANT_BUFFER_CREATE) { + if (argument2 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_BUFFER(argument0, argument2, 1, false); + + KObject process(currentProcess, argument1, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(process); + + SYSCALL_RETURN(MakeConstantBuffer((void *) argument0, argument2, (Process *) process.object), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_SHARE) { + // TODO Sort out flags. + + KObject _region(currentProcess, argument0, KERNEL_OBJECT_SHMEM); + CHECK_OBJECT(_region); + MMSharedRegion *region = (MMSharedRegion *) _region.object; + + KObject _process(currentProcess, argument1, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(_process); + Process *process = (Process *) _process.object; + + OpenHandleToObject(region, KERNEL_OBJECT_SHMEM); + + SYSCALL_RETURN(process->handleTable.OpenHandle(region, argument2, KERNEL_OBJECT_SHMEM), false); +} + +#define SYSCALL_SHARE_OBJECT(syscallName, objectType) \ +SYSCALL_IMPLEMENT(syscallName) { \ + KObject share(currentProcess, argument0, objectType); \ + CHECK_OBJECT(share); \ + KObject _process(currentProcess, argument1, KERNEL_OBJECT_PROCESS); \ + CHECK_OBJECT(_process); \ + Process *process = (Process *) _process.object; \ + OpenHandleToObject(share.object, objectType, share.flags); \ + SYSCALL_RETURN(process->handleTable.OpenHandle(share.object, share.flags, objectType), false); \ +} + +SYSCALL_SHARE_OBJECT(ES_SYSCALL_PROCESS_SHARE, KERNEL_OBJECT_PROCESS); +SYSCALL_SHARE_OBJECT(ES_SYSCALL_NODE_SHARE, KERNEL_OBJECT_NODE); + +SYSCALL_IMPLEMENT(ES_SYSCALL_VOLUME_GET_INFORMATION) { + if (~currentProcess->permissions & ES_PERMISSION_GET_VOLUME_INFORMATION) { + SYSCALL_RETURN(0, false); + } + + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *node = (KNode *) object.object; + KFileSystem *fileSystem = node->fileSystem; + + EsVolumeInformation information; + EsMemoryZero(&information, sizeof(EsVolumeInformation)); + EsMemoryCopy(information.label, fileSystem->name, sizeof(fileSystem->name)); + information.labelBytes = fileSystem->nameBytes; + information.driveType = fileSystem->block->driveType; + information.spaceUsed = fileSystem->spaceUsed; + information.spaceTotal = fileSystem->spaceTotal; + information.id = fileSystem->id; + + SYSCALL_WRITE(argument1, &information, sizeof(EsVolumeInformation)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_NODE_OPEN) { + size_t pathLength = (size_t) argument1; + uint64_t flags = (uint64_t) argument2; + + flags &= ~_ES_NODE_FROM_WRITE_EXCLUSIVE | _ES_NODE_NO_WRITE_BASE; + + bool needWritePermission = flags & (ES_FILE_WRITE_EXCLUSIVE | ES_FILE_WRITE | _ES_NODE_DIRECTORY_WRITE); + + char *path; + if (argument1 > K_MAX_PATH) SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + SYSCALL_READ_HEAP(path, argument0, argument1); + + _EsNodeInformation information; + SYSCALL_READ(&information, argument3, sizeof(_EsNodeInformation)); + + KNode *directory = nullptr; + + KObject _directory(currentProcess, information.handle, KERNEL_OBJECT_NODE); + CHECK_OBJECT(_directory); + + directory = (KNode *) _directory.object; + + if (directory->directoryEntry->type != ES_NODE_DIRECTORY) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + } + + if ((~_directory.flags & _ES_NODE_DIRECTORY_WRITE) && needWritePermission) { + SYSCALL_RETURN(ES_ERROR_FILE_PERMISSION_NOT_GRANTED, false); + } + + if (~_directory.flags & _ES_NODE_DIRECTORY_WRITE) { + flags |= _ES_NODE_NO_WRITE_BASE; + } + + KNodeInformation _information = FSNodeOpen(path, pathLength, flags, directory); + + if (!_information.node) { + SYSCALL_RETURN(_information.error, false); + } + + if (flags & ES_FILE_WRITE_EXCLUSIVE) { + // Mark this handle as being the exclusive writer for this file. + // This way, when the handle is used, OpenHandleToObject succeeds. + // The exclusive writer flag will only be removed from the file where countWrite drops to zero. + flags |= _ES_NODE_FROM_WRITE_EXCLUSIVE; + } + + EsMemoryZero(&information, sizeof(_EsNodeInformation)); + information.type = _information.node->directoryEntry->type; + information.fileSize = _information.node->directoryEntry->totalSize; + information.directoryChildren = _information.node->directoryEntry->directoryChildren; + information.handle = currentProcess->handleTable.OpenHandle(_information.node, flags, KERNEL_OBJECT_NODE); + SYSCALL_WRITE(argument3, &information, sizeof(_EsNodeInformation)); + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_NODE_DELETE) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *node = (KNode *) object.object; + + if (object.flags & _ES_NODE_NO_WRITE_BASE) { + SYSCALL_RETURN(ES_ERROR_FILE_PERMISSION_NOT_GRANTED, false); + } + + if (node->directoryEntry->type == ES_NODE_FILE && (~object.flags & ES_FILE_WRITE_EXCLUSIVE)) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); + } + + if (node->directoryEntry->type == ES_NODE_DIRECTORY || node->directoryEntry->type == ES_NODE_FILE) { + SYSCALL_RETURN(FSNodeDelete(node), false); + } else { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_NODE_MOVE) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *file = (KNode *) object.object; + + KObject object2(currentProcess, argument1, KERNEL_OBJECT_NODE | KERNEL_OBJECT_NONE); + CHECK_OBJECT(object2); + KNode *directory = (KNode *) object2.object; + + char *newPath; + if (argument3 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_READ_HEAP(newPath, argument2, argument3); + SYSCALL_RETURN(FSNodeMove(file, directory, newPath, (size_t) argument3), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_READ_SYNC) { + if (!argument2) SYSCALL_RETURN(0, false); + + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *file = (KNode *) object.object; + + if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + + SYSCALL_BUFFER(argument3, argument2, 1, false); + + size_t result = FSFileReadSync(file, (void *) argument3, argument1, argument2, + (_region1->flags & MM_REGION_FILE) ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0); + SYSCALL_RETURN(result, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_WRITE_SYNC) { + if (!argument2) SYSCALL_RETURN(0, false); + + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *file = (KNode *) object.object; + + if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + + SYSCALL_BUFFER(argument3, argument2, 1, true /* write */); + + if (object.flags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE)) { + size_t result = FSFileWriteSync(file, (void *) argument3, argument1, argument2, + (_region1->flags & MM_REGION_FILE) ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0); + SYSCALL_RETURN(result, false); + } else { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_GET_SIZE) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *file = (KNode *) object.object; + if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + SYSCALL_RETURN(file->directoryEntry->totalSize, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_RESIZE) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *file = (KNode *) object.object; + + if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + + if (object.flags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE)) { + SYSCALL_RETURN(FSFileResize(file, argument1), false); + } else { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SET) { + KObject _event(currentProcess, argument0, KERNEL_OBJECT_EVENT); + CHECK_OBJECT(_event); + KEvent *event = (KEvent *) _event.object; + KEventSet(event, false, true); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_RESET) { + KObject _event(currentProcess, argument0, KERNEL_OBJECT_EVENT); + CHECK_OBJECT(_event); + KEvent *event = (KEvent *) _event.object; + KEventReset(event); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SLEEP) { + KTimer timer = {}; + KTimerSet(&timer, (argument0 << 32) | argument1); + currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST; + KEventWait(&timer.event, ES_WAIT_NO_TIMEOUT); + currentThread->terminatableState = THREAD_IN_SYSCALL; + KTimerRemove(&timer); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WAIT) { + if (argument1 >= ES_MAX_WAIT_COUNT - 1 /* leave room for timeout timer */) { + SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + } + + EsHandle handles[ES_MAX_WAIT_COUNT]; + SYSCALL_READ(handles, argument0, argument1 * sizeof(EsHandle)); + + KEvent *events[ES_MAX_WAIT_COUNT]; + KObject _objects[ES_MAX_WAIT_COUNT] = {}; + + for (uintptr_t i = 0; i < argument1; i++) { + _objects[i].Initialise(¤tProcess->handleTable, handles[i], + KERNEL_OBJECT_PROCESS | KERNEL_OBJECT_THREAD | KERNEL_OBJECT_EVENT | KERNEL_OBJECT_EVENT_SINK); + CHECK_OBJECT(_objects[i]); + + void *object = _objects[i].object; + + switch (_objects[i].type) { + case KERNEL_OBJECT_PROCESS: { + events[i] = &((Process *) object)->killedEvent; + } break; + + case KERNEL_OBJECT_THREAD: { + events[i] = &((Thread *) object)->killedEvent; + } break; + + case KERNEL_OBJECT_EVENT_SINK: { + events[i] = &((EventSink *) object)->available; + } break; + + case KERNEL_OBJECT_EVENT: { + events[i] = (KEvent *) object; + } break; + + default: { + KernelPanic("DoSyscall - Unexpected wait object type %d.\n", _objects[i].type); + } break; + } + } + + size_t waitObjectCount = argument1; + KTimer timer = {}; + + if (argument2 != (uintptr_t) ES_WAIT_NO_TIMEOUT) { + KTimerSet(&timer, argument2); + events[waitObjectCount++] = &timer.event; + } + + uintptr_t waitReturnValue; + currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST; + waitReturnValue = scheduler.WaitEvents(events, waitObjectCount); + currentThread->terminatableState = THREAD_IN_SYSCALL; + + if (waitReturnValue == argument1) { + waitReturnValue = ES_ERROR_TIMEOUT_REACHED; + } + + if (argument2 != (uintptr_t) ES_WAIT_NO_TIMEOUT) { + KTimerRemove(&timer); + } + + SYSCALL_RETURN(waitReturnValue, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_SET_CURSOR) { + KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); + CHECK_OBJECT(_window); + + uint32_t imageWidth = (argument2 >> 16) & 0xFF; + uint32_t imageHeight = (argument2 >> 24) & 0xFF; + + SYSCALL_BUFFER(argument1, imageWidth * imageHeight * 4, 1, false); + + KMutexAcquire(&windowManager.mutex); + Window *window; + + if (_window.type == KERNEL_OBJECT_EMBEDDED_WINDOW) { + EmbeddedWindow *embeddedWindow = (EmbeddedWindow *) _window.object; + window = embeddedWindow->container; + + if (!window || !window->hoveringOverEmbed || embeddedWindow->owner != currentProcess) { + KMutexRelease(&windowManager.mutex); + SYSCALL_RETURN(ES_SUCCESS, false); + } + } else { + window = (Window *) _window.object; + + if (window->hoveringOverEmbed) { + KMutexRelease(&windowManager.mutex); + SYSCALL_RETURN(ES_SUCCESS, false); + } + } + + bool changedCursor = false; + + if (!window->closed && argument1 != windowManager.cursorID && !windowManager.eyedropping && (windowManager.hoverWindow == window || !windowManager.hoverWindow)) { + windowManager.cursorID = argument1; + windowManager.cursorImageOffsetX = (int8_t) ((argument2 >> 0) & 0xFF); + windowManager.cursorImageOffsetY = (int8_t) ((argument2 >> 8) & 0xFF); + windowManager.cursorXOR = argument3 >> 31; + + if (windowManager.cursorSurface.Resize(imageWidth, imageHeight)) { + windowManager.cursorSwap.Resize(imageWidth, imageHeight); + windowManager.cursorSurface.SetBits((K_USER_BUFFER const void *) argument1, argument3 & 0xFFFFFF, + ES_RECT_4(0, windowManager.cursorSurface.width, 0, windowManager.cursorSurface.height)); + } + + windowManager.changedCursorImage = true; + changedCursor = true; + } + + KMutexRelease(&windowManager.mutex); + + SYSCALL_RETURN(changedCursor, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_MOVE) { + KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW); + CHECK_OBJECT(_window); + + Window *window = (Window *) _window.object; + + bool success = true; + + EsRectangle rectangle; + + if (argument1) { + SYSCALL_READ(&rectangle, argument1, sizeof(EsRectangle)); + } else { + EsMemoryZero(&rectangle, sizeof(EsRectangle)); + } + + KMutexAcquire(&windowManager.mutex); + + if (argument3 & ES_WINDOW_MOVE_HIDDEN) { + windowManager.HideWindow(window); + } else { + window->Move(rectangle, argument3); + } + + if (argument3 & ES_WINDOW_MOVE_UPDATE_SCREEN) { + GraphicsUpdateScreen(); + } + + KMutexRelease(&windowManager.mutex); + + SYSCALL_RETURN(success ? ES_SUCCESS : ES_ERROR_INVALID_DIMENSIONS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CURSOR_POSITION_GET) { + EsPoint point = ES_POINT(windowManager.cursorX, windowManager.cursorY); + SYSCALL_WRITE(argument0, &point, sizeof(EsPoint)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CURSOR_POSITION_SET) { + KMutexAcquire(&windowManager.mutex); + windowManager.cursorX = argument0; + windowManager.cursorY = argument1; + windowManager.cursorXPrecise = argument0 * 10; + windowManager.cursorYPrecise = argument1 * 10; + KMutexRelease(&windowManager.mutex); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_GAME_CONTROLLER_STATE_POLL) { + EsGameControllerState gameControllers[ES_GAME_CONTROLLER_MAX_COUNT]; + size_t gameControllerCount; + + KMutexAcquire(&windowManager.gameControllersMutex); + gameControllerCount = windowManager.gameControllerCount; + EsMemoryCopy(gameControllers, windowManager.gameControllers, sizeof(EsGameControllerState) * gameControllerCount); + KMutexRelease(&windowManager.gameControllersMutex); + + SYSCALL_WRITE(argument0, gameControllers, sizeof(EsGameControllerState) * gameControllerCount); + SYSCALL_RETURN(gameControllerCount, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_GET_BOUNDS) { + KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); + CHECK_OBJECT(_window); + + EsRectangle rectangle; + EsMemoryZero(&rectangle, sizeof(EsRectangle)); + KMutexAcquire(&windowManager.mutex); + + if (_window.type == KERNEL_OBJECT_WINDOW) { + Window *window = (Window *) _window.object; + rectangle.l = window->position.x; + rectangle.t = window->position.y; + rectangle.r = window->position.x + window->width; + rectangle.b = window->position.y + window->height; + } else if (_window.type == KERNEL_OBJECT_EMBEDDED_WINDOW) { + EmbeddedWindow *embed = (EmbeddedWindow *) _window.object; + Window *window = embed->container; + + if (window) { + rectangle.l = window->position.x + WINDOW_INSET; + rectangle.t = window->position.y + WINDOW_INSET + CONTAINER_TAB_BAND_HEIGHT; + rectangle.r = window->position.x + window->width - WINDOW_INSET; + rectangle.b = window->position.y + window->height - WINDOW_INSET; + } else { + rectangle.l = 0; + rectangle.t = 0; + rectangle.r = 0; + rectangle.b = 0; + } + } + + KMutexRelease(&windowManager.mutex); + SYSCALL_WRITE(argument1, &rectangle, sizeof(EsRectangle)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_PAUSE) { + KObject _process(currentProcess, argument0, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(_process); + Process *process = (Process *) _process.object; + + scheduler.PauseProcess(process, (bool) argument1); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_CRASH) { + KernelLog(LOG_ERROR, "Syscall", "process crash request", "Process crash request, reason %d\n", argument0); + SYSCALL_RETURN(argument0, true); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_POST) { + KObject object(currentProcess, argument2, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(object); + void *process = object.object; + + _EsMessageWithObject message; + SYSCALL_READ(&message.message, argument0, sizeof(EsMessage)); + message.object = (void *) argument1; + + if (((Process *) process)->messageQueue.SendMessage(&message)) { + SYSCALL_RETURN(ES_SUCCESS, false); + } else { + SYSCALL_RETURN(ES_ERROR_MESSAGE_QUEUE_FULL, false); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_GET_ID) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_THREAD | KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(object); + + if (object.type == KERNEL_OBJECT_THREAD) { + SYSCALL_RETURN(((Thread *) object.object)->id, false); + } else if (object.type == KERNEL_OBJECT_PROCESS) { + Process *process = (Process *) object.object; + +#ifdef ENABLE_POSIX_SUBSYSTEM + if (currentThread->posixData && currentThread->posixData->forkProcess) { + SYSCALL_RETURN(currentThread->posixData->forkProcess->id, false); + } +#endif + + SYSCALL_RETURN(process->id, false); + } + + KernelPanic("ES_SYSCALL_THREAD_GET_ID - Unhandled case.\n"); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_STACK_SIZE) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_THREAD); + CHECK_OBJECT(object); + Thread *thread = (Thread *) object.object; + + SYSCALL_WRITE(argument1, &thread->userStackCommit, sizeof(thread->userStackCommit)); + SYSCALL_WRITE(argument2, &thread->userStackReserve, sizeof(thread->userStackReserve)); + + bool success = true; + + if (argument3) { + MMRegion *region = MMFindAndPinRegion(currentVMM, thread->userStackBase, thread->userStackReserve); + KMutexAcquire(¤tVMM->reserveMutex); + + if (thread->userStackCommit <= argument3 && argument3 <= thread->userStackReserve && !(argument3 & (K_PAGE_BITS - 1)) && region) { +#ifdef K_STACK_GROWS_DOWN + success = MMCommitRange(currentVMM, region, (thread->userStackReserve - argument3) / K_PAGE_SIZE, argument3 / K_PAGE_SIZE); +#else + success = MMCommitRange(currentVMM, region, 0, argument3 / K_PAGE_SIZE); +#endif + } else { + success = false; + } + + if (success) thread->userStackCommit = argument3; + KMutexRelease(¤tVMM->reserveMutex); + if (region) MMUnpinRegion(currentVMM, region); + } + + SYSCALL_RETURN(success ? ES_SUCCESS : ES_ERROR_INSUFFICIENT_RESOURCES, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_DIRECTORY_ENUMERATE) { + KObject _node(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(_node); + KNode *node = (KNode *) _node.object; + + if (node->directoryEntry->type != ES_NODE_DIRECTORY) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + + if (argument2 > SYSCALL_BUFFER_LIMIT / sizeof(EsDirectoryChild)) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_BUFFER(argument1, argument2 * sizeof(EsDirectoryChild), 1, true /* write */); + + SYSCALL_RETURN(FSDirectoryEnumerateChildren(node, (K_USER_BUFFER EsDirectoryChild *) argument1, argument2), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_CONTROL) { + KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); + CHECK_OBJECT(object); + KNode *file = (KNode *) object.object; + + if (file->directoryEntry->type != ES_NODE_FILE) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); + } + + SYSCALL_RETURN(FSFileControl(file, argument1), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_BATCH) { + EsBatchCall *calls; + if (argument1 > SYSCALL_BUFFER_LIMIT / sizeof(EsBatchCall)) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_READ_HEAP(calls, argument0, sizeof(EsBatchCall) * argument1); + + size_t count = argument1; + + for (uintptr_t i = 0; i < count; i++) { + EsBatchCall call = calls[i]; + bool fatal = false; + uintptr_t _returnValue = calls[i].returnValue = DoSyscall(call.index, call.argument0, call.argument1, call.argument2, call.argument3, + DO_SYSCALL_BATCHED, &fatal, userStackPointer); + if (fatal) SYSCALL_RETURN(_returnValue, true); + if (calls->stopBatchIfError && ES_CHECK_ERROR(_returnValue)) break; + if (currentThread->terminating) break; + } + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CONSTANT_BUFFER_READ) { + KObject _buffer(currentProcess, argument0, KERNEL_OBJECT_CONSTANT_BUFFER); + CHECK_OBJECT(_buffer); + ConstantBuffer *buffer = (ConstantBuffer *) _buffer.object; + if (!argument1) SYSCALL_RETURN(buffer->bytes, false); + SYSCALL_WRITE(argument1, buffer + 1, buffer->bytes); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_STATE) { + KObject _process(currentProcess, argument0, KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(_process); + Process *process = (Process *) _process.object; + + EsProcessState state; + EsMemoryZero(&state, sizeof(EsProcessState)); + state.crashReason = process->crashReason; + state.id = process->id; + state.executableState = process->executableState; + state.flags = (process->allThreadsTerminated ? ES_PROCESS_STATE_ALL_THREADS_TERMINATED : 0) + | (process->terminating ? ES_PROCESS_STATE_TERMINATING : 0) + | (process->crashed ? ES_PROCESS_STATE_CRASHED : 0) + | (process->messageQueue.pinged ? ES_PROCESS_STATE_PINGED : 0); + + SYSCALL_WRITE(argument1, &state, sizeof(EsProcessState)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SHUTDOWN) { + SYSCALL_PERMISSION(ES_PERMISSION_SHUTDOWN); + KThreadCreate("Shutdown", [] (uintptr_t action) { KernelShutdown(action); }, argument0); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_GET_ID) { + KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); + CHECK_OBJECT(_window); + + if (_window.type == KERNEL_OBJECT_WINDOW) { + SYSCALL_RETURN(((Window *) _window.object)->id, false); + } else { + SYSCALL_RETURN(((EmbeddedWindow *) _window.object)->id, false); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_YIELD_SCHEDULER) { + ProcessorFakeTimerInterrupt(); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SYSTEM_GET_CONSTANTS) { + SYSCALL_BUFFER(argument0, sizeof(uint64_t) * ES_SYSTEM_CONSTANT_COUNT, 1, true /* write */); + uint64_t *systemConstants = (uint64_t *) argument0; + systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND] = timeStampTicksPerMs / 1000; + systemConstants[ES_SYSTEM_CONSTANT_WINDOW_INSET] = WINDOW_INSET; + systemConstants[ES_SYSTEM_CONSTANT_CONTAINER_TAB_BAND_HEIGHT] = CONTAINER_TAB_BAND_HEIGHT; + systemConstants[ES_SYSTEM_CONSTANT_NO_FANCY_GRAPHICS] = graphics.target ? graphics.target->reducedColors : false; + systemConstants[ES_SYSTEM_CONSTANT_UI_SCALE] = UI_SCALE; + systemConstants[ES_SYSTEM_CONSTANT_BORDER_THICKNESS] = BORDER_THICKNESS; + systemConstants[ES_SYSTEM_CONSTANT_OPTIMAL_WORK_QUEUE_THREAD_COUNT] = scheduler.currentProcessorID; // TODO Update this as processors are added/removed. + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SYSTEM_TAKE_SNAPSHOT) { + SYSCALL_PERMISSION(ES_PERMISSION_TAKE_SYSTEM_SNAPSHOT); + + int type = argument0; + void *buffer = nullptr; + size_t bufferSize = 0; + EsDefer(EsHeapFree(buffer, 0, K_FIXED)); + + switch (type) { + case ES_SYSTEM_SNAPSHOT_PROCESSES: { + KSpinlockAcquire(&scheduler.lock); + size_t count = scheduler.allProcesses.count + 8; + bufferSize = sizeof(EsSnapshotProcesses) + sizeof(EsSnapshotProcessesItem) * count; + KSpinlockRelease(&scheduler.lock); + + buffer = EsHeapAllocate(bufferSize, true, K_FIXED); + EsMemoryZero(buffer, bufferSize); + + KSpinlockAcquire(&scheduler.lock); + + if (scheduler.allProcesses.count < count) { + count = scheduler.allProcesses.count; + } + + EsSnapshotProcesses *snapshot = (EsSnapshotProcesses *) buffer; + + LinkedItem *item = scheduler.allProcesses.firstItem; + uintptr_t index = 0; + + while (item && index < count) { + Process *process = item->thisItem; + if (process->terminating) goto next; + + { + snapshot->processes[index].pid = process->id; + snapshot->processes[index].memoryUsage = process->vmm->commit * K_PAGE_SIZE; + snapshot->processes[index].cpuTimeSlices = process->cpuTimeSlices; + snapshot->processes[index].idleTimeSlices = process->idleTimeSlices; + snapshot->processes[index].handleCount = process->handleTable.handleCount; + snapshot->processes[index].isKernel = process->type == PROCESS_KERNEL; + + snapshot->processes[index].nameBytes = EsCStringLength(process->cExecutableName); + EsMemoryCopy(snapshot->processes[index].name, process->cExecutableName, snapshot->processes[index].nameBytes); + + index++; + } + + next:; + item = item->nextItem; + } + + snapshot->count = index; + bufferSize = sizeof(EsSnapshotProcesses) + sizeof(EsSnapshotProcessesItem) * index; + KSpinlockRelease(&scheduler.lock); + } break; + + default: { + SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + } break; + } + + EsHandle constantBuffer = MakeConstantBuffer(buffer, bufferSize, currentProcess); + SYSCALL_WRITE(argument1, &bufferSize, sizeof(size_t)); + SYSCALL_RETURN(constantBuffer, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_OPEN) { + SYSCALL_PERMISSION(ES_PERMISSION_PROCESS_OPEN); + + Process *process = scheduler.OpenProcess(argument0); + + if (process) { + SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(process, 0, KERNEL_OBJECT_PROCESS), false); + } else { + SYSCALL_RETURN(ES_INVALID_HANDLE, false); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_SET_TLS) { + currentThread->tlsAddress = argument0; // Set this first, otherwise we could get pre-empted and restore without TLS set. + ProcessorSetThreadStorage(argument0); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_TLS) { + SYSCALL_RETURN(currentThread->tlsAddress, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_BOUNDS_GET) { + EsRectangle rectangle; + EsMemoryZero(&rectangle, sizeof(EsRectangle)); + + rectangle.l = 0; + rectangle.t = 0; + rectangle.r = graphics.width; + rectangle.b = graphics.height; + + SYSCALL_WRITE(argument1, &rectangle, sizeof(EsRectangle)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_WORK_AREA_SET) { + SYSCALL_PERMISSION(ES_PERMISSION_SCREEN_MODIFY); + EsRectangle rectangle; + SYSCALL_READ(&rectangle, argument1, sizeof(EsRectangle)); + windowManager.workArea = rectangle; + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_WORK_AREA_GET) { + EsRectangle rectangle = windowManager.workArea; + SYSCALL_WRITE(argument1, &rectangle, sizeof(EsRectangle)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_DESKTOP) { + char *buffer; + if (argument1 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + SYSCALL_READ_HEAP(buffer, argument0, argument1); + + KObject _window(currentProcess, argument2, KERNEL_OBJECT_EMBEDDED_WINDOW | KERNEL_OBJECT_NONE); + CHECK_OBJECT(_window); + + EmbeddedWindow *window = (EmbeddedWindow *) _window.object; + + if (!scheduler.shutdown) { + _EsMessageWithObject m = {}; + m.message.type = ES_MSG_DESKTOP; + m.message.desktop.buffer = MakeConstantBufferForDesktop(buffer, argument1); + m.message.desktop.bytes = argument1; + m.message.desktop.windowID = window ? window->id : 0; + desktopProcess->messageQueue.SendMessage(&m); + } + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +#ifdef ENABLE_POSIX_SUBSYSTEM +SYSCALL_IMPLEMENT(ES_SYSCALL_POSIX) { + SYSCALL_PERMISSION(ES_PERMISSION_POSIX_SUBSYSTEM); + + _EsPOSIXSyscall syscall; + SYSCALL_READ(&syscall, argument0, sizeof(_EsPOSIXSyscall)); + + if (syscall.index == 2 /* open */ || syscall.index == 59 /* execve */) { + KObject node(currentProcess, syscall.arguments[4], KERNEL_OBJECT_NODE); + CHECK_OBJECT(node); + syscall.arguments[4] = (long) node.object; + if (~node.flags & _ES_NODE_DIRECTORY_WRITE) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_HANDLE, true); + long result = POSIX::DoSyscall(syscall, userStackPointer); + SYSCALL_RETURN(result, false); + } else if (syscall.index == 109 /* setpgid */) { + KObject process(currentProcess, syscall.arguments[0], KERNEL_OBJECT_PROCESS); + CHECK_OBJECT(process); + syscall.arguments[0] = (long) process.object; + long result = POSIX::DoSyscall(syscall, userStackPointer); + SYSCALL_RETURN(result, false); + } else { + long result = POSIX::DoSyscall(syscall, userStackPointer); + SYSCALL_RETURN(result, false); + } +} +#else +SYSCALL_IMPLEMENT(ES_SYSCALL_POSIX) { + SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true); +} +#endif + +SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_STATUS) { + Process *process; + SYSCALL_HANDLE(argument0, KERNEL_OBJECT_PROCESS, process, 1); + SYSCALL_RETURN(process->exitStatus, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PIPE_CREATE) { + Pipe *pipe = (Pipe *) EsHeapAllocate(sizeof(Pipe), true, K_PAGED); + if (!pipe) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + pipe->writers = pipe->readers = 1; + KEventSet(&pipe->canWrite); + SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(pipe, PIPE_READER | PIPE_WRITER, KERNEL_OBJECT_PIPE), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PIPE_READ) { + Pipe *pipe; + SYSCALL_HANDLE(argument0, KERNEL_OBJECT_PIPE, pipe, 1); + SYSCALL_BUFFER(argument1, argument2, 2, false); + SYSCALL_RETURN(pipe->Access((void *) argument1, argument2, false, true), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_PIPE_WRITE) { + Pipe *pipe; + SYSCALL_HANDLE(argument0, KERNEL_OBJECT_PIPE, pipe, 1); + SYSCALL_BUFFER(argument1, argument2, 2, true /* write */); + SYSCALL_RETURN(pipe->Access((void *) argument1, argument2, true, true), false); +} + +KMutex systemConfigurationMutex; +ConstantBuffer *systemConfiguration; + +SYSCALL_IMPLEMENT(ES_SYSCALL_SYSTEM_CONFIGURATION_WRITE) { + SYSCALL_PERMISSION(ES_PERMISSION_SYSTEM_CONFIGURATION_WRITE); + + // TODO Broadcast message? + + if (argument1 > SYSCALL_BUFFER_LIMIT) { + SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + } + + ConstantBuffer *buffer = (ConstantBuffer *) EsHeapAllocate(sizeof(ConstantBuffer) + argument1, false, K_PAGED); + if (!buffer) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + EsMemoryZero(buffer, sizeof(ConstantBuffer)); + buffer->handles = 1; + buffer->bytes = argument1; + buffer->isPaged = true; + EsDefer(CloseHandleToObject(buffer, KERNEL_OBJECT_CONSTANT_BUFFER)); + SYSCALL_READ(buffer + 1, argument0, argument1); + + KMutexAcquire(&systemConfigurationMutex); + if (systemConfiguration) CloseHandleToObject(systemConfiguration, KERNEL_OBJECT_CONSTANT_BUFFER); + OpenHandleToObject(buffer, KERNEL_OBJECT_CONSTANT_BUFFER); + systemConfiguration = buffer; + KMutexRelease(&systemConfigurationMutex); + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_SYSTEM_CONFIGURATION_READ) { + EsHandle handle = ES_INVALID_HANDLE; + size_t bytes = 0; + + KMutexAcquire(&systemConfigurationMutex); + + if (systemConfiguration && OpenHandleToObject(systemConfiguration, KERNEL_OBJECT_CONSTANT_BUFFER)) { + bytes = systemConfiguration->bytes; + handle = currentProcess->handleTable.OpenHandle(systemConfiguration, 0, KERNEL_OBJECT_CONSTANT_BUFFER); + } + + KMutexRelease(&systemConfigurationMutex); + + SYSCALL_WRITE(argument2, &bytes, sizeof(bytes)); + SYSCALL_RETURN(handle, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SINK_CREATE) { + EventSink *sink = (EventSink *) EsHeapAllocate(sizeof(EventSink), true, K_FIXED); + + if (!sink) { + SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + } + + sink->ignoreDuplicates = argument0; + sink->handles = 1; + + SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(sink, 0, KERNEL_OBJECT_EVENT_SINK), false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_FORWARD) { + KEvent *event; + SYSCALL_HANDLE(argument0, KERNEL_OBJECT_EVENT, event, 1); + EventSink *sink; + SYSCALL_HANDLE(argument1, KERNEL_OBJECT_EVENT_SINK, sink, 2); + EsGeneric data = argument2; + + bool error = false, limitExceeded = false; + + KMutexAcquire(&eventForwardMutex); + + if (!event->sinkTable) { + event->sinkTable = (EventSinkTable *) EsHeapAllocate(sizeof(EventSinkTable) * ES_MAX_EVENT_FORWARD_COUNT, true, K_FIXED); + + if (!event->sinkTable) { + error = true; + } + } + + if (!error) { + limitExceeded = true; + + for (uintptr_t i = 0; i < ES_MAX_EVENT_FORWARD_COUNT; i++) { + if (!event->sinkTable[i].sink) { + if (!OpenHandleToObject(sink, KERNEL_OBJECT_EVENT_SINK, 0, false)) { + error = true; + break; + } + + KSpinlockAcquire(&scheduler.lock); + event->sinkTable[i].sink = sink; + event->sinkTable[i].data = data; + KSpinlockRelease(&scheduler.lock); + + limitExceeded = false; + break; + } + } + } + + KMutexRelease(&eventForwardMutex); + + if (limitExceeded) { + SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); + } else if (error) { + SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + } else { + SYSCALL_RETURN(0, false); + } +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SINK_POP) { + EventSink *sink; + SYSCALL_HANDLE(argument0, KERNEL_OBJECT_EVENT_SINK, sink, 1); + + bool empty = false, overflow = false; + EsGeneric data = {}; + + KSpinlockAcquire(&sink->spinlock); + + if (!sink->queueCount) { + if (sink->overflow) { + overflow = true; + sink->overflow = false; + } else { + empty = true; + } + } else { + data = sink->queue[sink->queuePosition]; + sink->queuePosition++; + sink->queueCount--; + + if (sink->queuePosition == ES_MAX_EVENT_SINK_BUFFER_SIZE) { + sink->queuePosition = 0; + } + } + + if (!sink->queueCount && !sink->overflow) { + KEventReset(&sink->available); // KEvent::Reset doesn't take the scheduler lock, so this won't deadlock! + } + + KSpinlockRelease(&sink->spinlock); + + SYSCALL_WRITE(argument1, &data, sizeof(EsGeneric)); + SYSCALL_RETURN(overflow ? ES_ERROR_EVENT_SINK_OVERFLOW : empty ? ES_ERROR_EVENT_NOT_SET : ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SINK_PUSH) { + EventSink *sink; + SYSCALL_HANDLE(argument0, KERNEL_OBJECT_EVENT_SINK, sink, 1); + KSpinlockAcquire(&scheduler.lock); + EsError result = sink->Push(argument1); + KSpinlockRelease(&scheduler.lock); + SYSCALL_RETURN(result, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_DOMAIN_NAME_RESOLVE) { + SYSCALL_PERMISSION(ES_PERMISSION_NETWORKING); + + if (argument1 > ES_DOMAIN_NAME_MAX_LENGTH) { + SYSCALL_RETURN(ES_ERROR_BAD_DOMAIN_NAME, false); + } + + char domainName[ES_DOMAIN_NAME_MAX_LENGTH]; + SYSCALL_READ(domainName, argument0, argument1); + + EsAddress address; + EsMemoryZero(&address, sizeof(EsAddress)); + + KEvent completeEvent = {}; + + NetDomainNameResolveTask task = {}; + task.event = &completeEvent; + task.name = domainName; + task.nameBytes = (size_t) argument1; + task.address = &address; + task.callback = NetDomainNameResolve; + NetTaskBegin(&task); + + KEventWait(&completeEvent); + + if (task.error == ES_SUCCESS) { + SYSCALL_WRITE(argument2, &address, sizeof(EsAddress)); + } + + SYSCALL_RETURN(task.error, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_ECHO_REQUEST) { + SYSCALL_PERMISSION(ES_PERMISSION_NETWORKING); + + if (argument1 > ES_ECHO_REQUEST_MAX_LENGTH) { + SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); + } + + uint8_t data[48]; + EsMemoryZero(data, sizeof(data)); + SYSCALL_READ(data, argument0, argument1); + + EsAddress address; + SYSCALL_READ(&address, argument2, sizeof(EsAddress)); + + KEvent completeEvent = {}; + + NetEchoRequestTask task = {}; + task.event = &completeEvent; + task.address = &address; + task.data = data; + task.callback = NetEchoRequest; + NetTaskBegin(&task); + + KEventWait(&completeEvent); + + if (task.error == ES_SUCCESS) { + SYSCALL_WRITE(argument0, data, argument1); + } + + SYSCALL_RETURN(task.error, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CONNECTION_OPEN) { + SYSCALL_PERMISSION(ES_PERMISSION_NETWORKING); + + EsConnection connection; + SYSCALL_READ(&connection, argument0, sizeof(EsConnection)); + + if (connection.sendBufferBytes < 1024 || connection.receiveBufferBytes < 1024) { + SYSCALL_RETURN(ES_ERROR_BUFFER_TOO_SMALL, false); + } + + // TODO Upper limit on buffer sizes? + + NetConnection *netConnection = NetConnectionOpen(&connection.address, connection.sendBufferBytes, connection.receiveBufferBytes, argument1); + + if (!netConnection) { + SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + } + + connection.sendBuffer = (uint8_t *) MMMapShared(currentVMM, netConnection->bufferRegion, 0, connection.sendBufferBytes + connection.receiveBufferBytes); + connection.receiveBuffer = connection.sendBuffer + connection.sendBufferBytes; + + if (!connection.sendBuffer) { + CloseHandleToObject(netConnection, KERNEL_OBJECT_CONNECTION); + SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); + } + + connection.handle = currentProcess->handleTable.OpenHandle(netConnection, 0, KERNEL_OBJECT_CONNECTION); + connection.error = ES_SUCCESS; + + SYSCALL_WRITE(argument0, &connection, sizeof(EsConnection)); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CONNECTION_POLL) { + SYSCALL_BUFFER(argument0, sizeof(EsConnection), 0, true /* write */); + EsConnection *connection = (EsConnection *) argument0; + NetConnection *netConnection; + SYSCALL_HANDLE(argument3, KERNEL_OBJECT_CONNECTION, netConnection, 1); + + connection->receiveWritePointer = netConnection->receiveWritePointer; + connection->sendReadPointer = netConnection->sendReadPointer; + connection->open = netConnection->task.step == TCP_STEP_ESTABLISHED; + connection->error = netConnection->task.completed ? netConnection->task.error : ES_SUCCESS; + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_CONNECTION_NOTIFY) { + NetConnection *netConnection; + SYSCALL_HANDLE(argument3, KERNEL_OBJECT_CONNECTION, netConnection, 1); + NetConnectionNotify(netConnection, argument1, argument2); + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SYSCALL_IMPLEMENT(ES_SYSCALL_DEBUG_COMMAND) { +#ifdef DEBUG_BUILD + if (argument0 == 1) { + ArchResetCPU(); + } else if (argument0 == 2) { + KernelPanic("Debug command 2.\n"); + } else if (argument0 == 3) { + extern char kernelLog[]; + extern uintptr_t kernelLogPosition; + size_t bytes = kernelLogPosition; + if (argument2 < bytes) bytes = argument2; + EsMemoryCopy((void *) argument1, kernelLog, bytes); + SYSCALL_RETURN(bytes, false); + } else if (argument0 == 4) { + SYSCALL_BUFFER(argument1, 1, 0, false); + + if (_region0->data.normal.commitPageCount != (argument3 & 0x7FFFFFFFFFFFFFFF)) { + KernelPanic("Commit page count mismatch.\n"); + } + + if (_region0->data.normal.commit.Contains(argument2) != (argument3 >> 63)) { + KernelPanic("Commit contains mismatch at %x.\n", argument1); + } + } else if (argument0 == 6) { + // SYSCALL_RETURN(DriversDebugGetEnumeratedPCIDevices((EsPCIDevice *) argument1, argument2), false); + } else if (argument0 == 7) { + EsAssert(!scheduler.threadEventLog); + EsThreadEventLogEntry *buffer = (EsThreadEventLogEntry *) EsHeapAllocate(argument0 * sizeof(EsThreadEventLogEntry), false, K_FIXED); + scheduler.threadEventLogAllocated = argument0; + scheduler.threadEventLogPosition = 0; + __sync_synchronize(); + scheduler.threadEventLog = buffer; + } else if (argument0 == 8) { + SYSCALL_RETURN(scheduler.threadEventLogPosition, false); + } else if (argument0 == 9) { + SYSCALL_WRITE(argument1, scheduler.threadEventLog, scheduler.threadEventLogPosition * sizeof(EsThreadEventLogEntry)); + } else if (argument0 == 10) { + scheduler.threadEventLogAllocated = 0; + // HACK Wait for threads to stop writing... + KEvent event = {}; + KEventWait(&event, 1000); + EsHeapFree(scheduler.threadEventLog, 0, K_FIXED); + scheduler.threadEventLog = nullptr; + } else if (argument0 == 12) { + EsMemoryStatistics statistics; + EsMemoryZero(&statistics, sizeof(statistics)); + statistics.fixedHeapAllocationCount = K_FIXED->allocationsCount; + statistics.fixedHeapTotalSize = K_FIXED->size; + statistics.coreHeapAllocationCount = K_CORE->allocationsCount; + statistics.coreHeapTotalSize = K_CORE->size; + statistics.cachedNodes = fs.bootFileSystem->cachedNodes.count; + statistics.cachedDirectoryEntries = fs.bootFileSystem->cachedDirectoryEntries.count; + statistics.totalSurfaceBytes = graphics.totalSurfaceBytes; + statistics.commitPageable = pmm.commitPageable; + statistics.commitFixed = pmm.commitFixed; + statistics.commitLimit = pmm.commitLimit; + statistics.commitFixedLimit = pmm.commitFixedLimit; + statistics.commitRemaining = MM_REMAINING_COMMIT(); + statistics.maximumObjectCachePages = MM_OBJECT_CACHE_PAGES_MAXIMUM(); + statistics.approximateObjectCacheSize = pmm.approximateTotalObjectCacheBytes; + SYSCALL_WRITE(argument1, &statistics, sizeof(statistics)); + } +#endif + + SYSCALL_RETURN(ES_SUCCESS, false); +} + +SyscallFunction syscallFunctions[ES_SYSCALL_COUNT + 1] { +#include +}; + +#pragma GCC diagnostic pop + +uintptr_t DoSyscall(EsSyscallType index, + uintptr_t argument0, uintptr_t argument1, + uintptr_t argument2, uintptr_t argument3, + uint64_t flags, bool *fatal, uintptr_t *userStackPointer) { + bool batched = flags & DO_SYSCALL_BATCHED; + + // Interrupts need to be enabled during system calls, + // because many of them block on mutexes or events. + ProcessorEnableInterrupts(); + + Thread *currentThread = GetCurrentThread(); + Process *currentProcess = currentThread->process; + MMSpace *currentVMM = currentProcess->vmm; + + if (!batched) { + if (currentThread->terminating) { + // The thread has been terminated. + // Yield the scheduler so it can be removed. + ProcessorFakeTimerInterrupt(); + } + + if (currentThread->terminatableState != THREAD_TERMINATABLE) { + KernelPanic("DoSyscall - Current thread %x was not terminatable (was %d).\n", + currentThread, currentThread->terminatableState); + } + + currentThread->terminatableState = THREAD_IN_SYSCALL; + } + + EsError returnValue = ES_FATAL_ERROR_UNKNOWN_SYSCALL; + bool fatalError = true; + + if (index < ES_SYSCALL_COUNT) { + SyscallFunction function = syscallFunctions[index]; + + if (batched && index == ES_SYSCALL_BATCH) { + // This could cause a stack overflow, so it's a fatal error. + } else if (function) { + returnValue = (EsError) function(argument0, argument1, argument2, argument3, + currentThread, currentProcess, currentVMM, userStackPointer, &fatalError); + } + } + + if (fatal) *fatal = false; + + if (fatalError) { + if (fatal) { + *fatal = true; + } else { + EsCrashReason reason; + EsMemoryZero(&reason, sizeof(EsCrashReason)); + reason.errorCode = (EsFatalError) returnValue; + reason.duringSystemCall = index; + KernelLog(LOG_ERROR, "Syscall", "syscall failure", + "Process crashed during system call [%x, %x, %x, %x, %x]\n", index, argument0, argument1, argument2, argument3); + scheduler.CrashProcess(currentProcess, &reason); + } + } + + if (!batched) { + currentThread->terminatableState = THREAD_TERMINATABLE; + + if (currentThread->terminating || currentThread->paused) { + // The thread has been terminated or paused. + // Yield the scheduler so it can be removed or sent to the paused thread queue. + ProcessorFakeTimerInterrupt(); + } + } + + return returnValue; +} + +bool KCopyToUser(K_USER_BUFFER void *destination, const void *source, size_t length) { + __sync_synchronize(); + Thread *currentThread = GetCurrentThread(); + MMRegion *region = MMFindAndPinRegion(currentThread->process->vmm, (uintptr_t) destination, length); + if (!region) return false; + EsMemoryCopy(destination, source, length); + MMUnpinRegion(currentThread->process->vmm, region); + return true; +} + +bool KCopyFromUser(void *destination, K_USER_BUFFER const void *source, size_t length) { + __sync_synchronize(); + Thread *currentThread = GetCurrentThread(); + MMRegion *region = MMFindAndPinRegion(currentThread->process->vmm, (uintptr_t) source, length); + if (!region) return false; + EsMemoryCopy(destination, source, length); + MMUnpinRegion(currentThread->process->vmm, region); + return true; +} + +#endif diff --git a/kernel/terminal.cpp b/kernel/terminal.cpp new file mode 100644 index 0000000..ae40540 --- /dev/null +++ b/kernel/terminal.cpp @@ -0,0 +1,431 @@ +// TODO Everything in this file is a hack just so I can debug the kernel. +// Replace all of it!!! + +#ifdef IMPLEMENTATION + +#include +#include "x86_64.h" + +#define TERMINAL_ADDRESS ((uint16_t *) (LOW_MEMORY_MAP_START + 0xB8000)) + +#if 1 +KSpinlock terminalLock; +KSpinlock printLock; +#else +KMutex terminalLock; +KMutex printLock; +#endif + +void DebugWriteCharacter(uintptr_t character); + +#ifdef ARCH_X86_COMMON +bool printToDebugger = false; +uintptr_t terminalPosition = 80; + +#define EARLY_DEBUGGING +// #define VGA_TEXT_MODE + +#ifdef EARLY_DEBUGGING +char kernelLog[262144]; +uintptr_t kernelLogPosition; +#endif + +static void TerminalCallback(int character, void *) { + if (!character) return; + + KSpinlockAcquire(&terminalLock); + EsDefer(KSpinlockRelease(&terminalLock)); + +#ifdef EARLY_DEBUGGING + { + kernelLog[kernelLogPosition] = character; + kernelLogPosition++; + if (kernelLogPosition == sizeof(kernelLog)) kernelLogPosition = 0; + } +#endif + +#ifdef VGA_TEXT_MODE + { + if (character == '\n') { + terminalPosition = terminalPosition - (terminalPosition % 80) + 80; + } else { + TERMINAL_ADDRESS[terminalPosition] = (uint16_t) character | 0x0700; + terminalPosition++; + } + + if (terminalPosition >= 80 * 25) { + for (int i = 80; i < 80 * 25; i++) { + TERMINAL_ADDRESS[i - 80] = TERMINAL_ADDRESS[i]; + } + + for (int i = 80 * 24; i < 80 * 25; i++) { + TERMINAL_ADDRESS[i] = 0x700; + } + + terminalPosition -= 80; + + // uint64_t start = ProcessorReadTimeStamp(); + // uint64_t end = start + 250 * KGetTimeStampTicksPerMs(); + // while (ProcessorReadTimeStamp() < end); + } + + { + ProcessorOut8(0x3D4, 0x0F); + ProcessorOut8(0x3D5, terminalPosition); + ProcessorOut8(0x3D4, 0x0E); + ProcessorOut8(0x3D5, terminalPosition >> 8); + } + } +#endif + + { + ProcessorDebugOutputByte((uint8_t) character); + + if (character == '\n') { + ProcessorDebugOutputByte((uint8_t) 13); + } + } + + if (printToDebugger) { + DebugWriteCharacter(character); + if (character == '\t') DebugWriteCharacter(' '); + } +} +#endif + +size_t debugRows, debugColumns, debugCurrentRow, debugCurrentColumn; + +void DebugWriteCharacter(uintptr_t character) { + if (!graphics.target || !graphics.target->debugPutBlock) return; + + if (debugCurrentRow == debugRows) { + debugCurrentRow = 0; + + // uint64_t start = ProcessorReadTimeStamp(); + // uint64_t end = start + 3000 * KGetTimeStampTicksPerMs(); + // while (ProcessorReadTimeStamp() < end); + + graphics.target->debugClearScreen(); + } + + uintptr_t row = debugCurrentRow; + uintptr_t column = debugCurrentColumn; + + if (character == '\n') { + debugCurrentRow++; + debugCurrentColumn = 0; + return; + } + + if (character > 127) character = ' '; + if (row >= debugRows) return; + if (column >= debugColumns) return; + + for (int j = 0; j < VGA_FONT_HEIGHT; j++) { + uint8_t byte = ((uint8_t *) vgaFont)[character * 16 + j]; + + for (int i = 0; i < 8; i++) { + uint8_t bit = byte & (1 << i); + if (bit) graphics.target->debugPutBlock((column + 1) * 9 + i, row * 16 + j, false); + } + } + + debugCurrentColumn++; + + if (debugCurrentColumn == debugColumns) { + debugCurrentRow++; + debugCurrentColumn = 4; + } +} + +void StartDebugOutput() { + if (graphics.target && graphics.target->debugClearScreen && graphics.target->debugPutBlock) { + debugRows = (graphics.height - 1) / VGA_FONT_HEIGHT; + debugColumns = (graphics.width - 1) / VGA_FONT_WIDTH - 2; + debugCurrentRow = debugCurrentColumn = 0; + printToDebugger = true; + graphics.target->debugClearScreen(); + } +} + +bool debugKeyPressed; + +void KDebugKeyPressed() { + if (debugKeyPressed) return; + debugKeyPressed = true; + KernelPanic("Debug key pressed.\n"); +} + +uintptr_t DebugReadNumber() { + uintptr_t value = 0; + + for (uintptr_t i = 0; i < 2 * sizeof(uintptr_t); i++) { + value <<= 4; + + while (true) { + int key = KWaitKey(); + if (key == ES_SCANCODE_0) { EsPrint("0"); value |= 0; } + else if (key == ES_SCANCODE_1) { EsPrint("1"); value |= 1; } + else if (key == ES_SCANCODE_2) { EsPrint("2"); value |= 2; } + else if (key == ES_SCANCODE_3) { EsPrint("3"); value |= 3; } + else if (key == ES_SCANCODE_4) { EsPrint("4"); value |= 4; } + else if (key == ES_SCANCODE_5) { EsPrint("5"); value |= 5; } + else if (key == ES_SCANCODE_6) { EsPrint("6"); value |= 6; } + else if (key == ES_SCANCODE_7) { EsPrint("7"); value |= 7; } + else if (key == ES_SCANCODE_8) { EsPrint("8"); value |= 8; } + else if (key == ES_SCANCODE_9) { EsPrint("9"); value |= 9; } + else if (key == ES_SCANCODE_A) { EsPrint("A"); value |= 10; } + else if (key == ES_SCANCODE_B) { EsPrint("B"); value |= 11; } + else if (key == ES_SCANCODE_C) { EsPrint("C"); value |= 12; } + else if (key == ES_SCANCODE_D) { EsPrint("D"); value |= 13; } + else if (key == ES_SCANCODE_E) { EsPrint("E"); value |= 14; } + else if (key == ES_SCANCODE_F) { EsPrint("F"); value |= 15; } + else if (key == ES_SCANCODE_ENTER) { value >>= 4; return value; } + else continue; + break; + } + } + + return value; +} + +void KernelPanic(const char *format, ...) { + ProcessorDisableInterrupts(); + ProcessorSendIPI(KERNEL_PANIC_IPI, true); + + // Disable synchronisation objects. The panic IPI must be sent before this, + // so other processors don't start getting "mutex not correctly acquired" panics. + scheduler.panic = true; + + if (debugKeyPressed) { + DriversDumpState(); + } + + if (!printToDebugger) StartDebugOutput(); + + EsPrint("\n--- System Error ---\n>> "); + + va_list arguments; + va_start(arguments, format); + _StringFormat(TerminalCallback, (void *) 0x4F00, format, arguments); + va_end(arguments); + + EsPrint("Current thread = %x\n", GetCurrentThread()); + EsPrint("Trace: %x\n", __builtin_return_address(0)); + EsPrint("RSP: %x; RBP: %x\n", ProcessorGetRSP(), ProcessorGetRBP()); + // EsPrint("Memory: %x/%x\n", pmm.pagesAllocated, pmm.startPageCount); + + { + EsPrint("Threads:\n"); + + LinkedItem *item = scheduler.allThreads.firstItem; + + while (item) { + Thread *thread = item->thisItem; + + EsPrint("%z %d %x @%x:%x ", (GetCurrentThread() == thread) ? "=>" : " ", + thread->id, thread, thread->interruptContext->rip, thread->interruptContext->rbp); + + if (thread->state == THREAD_WAITING_EVENT) { + EsPrint("WaitEvent(Count:%d, %x) ", thread->blocking.eventCount, thread->blocking.events[0]); + } else if (thread->state == THREAD_WAITING_MUTEX) { + EsPrint("WaitMutex(%x, Owner:%d) ", thread->blocking.mutex, thread->blocking.mutex->owner->id); + } else if (thread->state == THREAD_WAITING_WRITER_LOCK) { + EsPrint("WaitWriterLock(%x, %d) ", thread->blocking.writerLock, thread->blocking.writerLockType); + } + + Process *process = thread->process; + EsPrint("%z:%z\n", process->cExecutableName, thread->cName); + + item = item->nextItem; + } + } + + for (uintptr_t i = 0; i < KGetCPUCount(); i++) { + CPULocalStorage *local = KGetCPULocal(i); + + if (local->panicContext) { + EsPrint("CPU %d LS %x RIP/RBP %x:%x TID %d\n", local->processorID, local, + local->panicContext->rip, local->panicContext->rbp, + local->currentThread ? local->currentThread->id : 0); + } + } + +#ifdef EARLY_DEBUGGING + uintptr_t kernelLogEnd = kernelLogPosition; + EsPrint("Press 'D' to enter debugger.\n"); + while (KWaitKey() != ES_SCANCODE_D); + graphics.debuggerActive = true; + + while (true) { +#ifdef VGA_TEXT_MODE + for (uintptr_t i = 0; i < 80 * 25; i++) { + TERMINAL_ADDRESS[i] = 0x0700; + } + + terminalPosition = 80; +#else + graphics.target->debugClearScreen(); + + debugCurrentRow = debugCurrentColumn = 0; +#endif + + + EsPrint("0 - view log\n1 - reset\n2 - view pmem\n3 - view vmem\n4 - stack trace\n"); + + int key = KWaitKey(); + + if (key == ES_SCANCODE_0) { + uintptr_t position = 0, nextPosition = 0; + uintptr_t x = 0, y = 0; + + + +#ifdef VGA_TEXT_MODE + for (uintptr_t i = 0; i < 80 * 25; i++) { + TERMINAL_ADDRESS[i] = 0x0700; + } +#else + graphics.target->debugClearScreen(); +#endif + + while (position < kernelLogEnd) { + char c = kernelLog[position]; + + if (c != '\n') { +#ifdef VGA_TEXT_MODE + TERMINAL_ADDRESS[x + y * 80] = c | 0x0700; +#else + debugCurrentRow = y, debugCurrentColumn = x; + DebugWriteCharacter(c); +#endif + } + + x++; + + if (x == +#ifdef VGA_TEXT_MODE + 80 +#else + debugColumns +#endif + || c == '\n') { + x = 0; + y++; + + if (y == 1) { + nextPosition = position; + } + } + + if (y == +#ifdef VGA_TEXT_MODE + 25 +#else + debugRows +#endif + ) { + while (true) { + int key = KWaitKey(); + + if (key == ES_SCANCODE_SPACE || key == ES_SCANCODE_DOWN_ARROW) { + position = nextPosition; + break; + } else if (key == ES_SCANCODE_UP_ARROW) { + position = nextPosition; + if (position < 240) position = 0; + else position -= 240; + break; + } + } + +#ifdef VGA_TEXT_MODE + for (uintptr_t i = 0; i < 80 * 25; i++) { + TERMINAL_ADDRESS[i] = 0x0700; + } +#else + graphics.target->debugClearScreen(); +#endif + + y = 0; + } + + position++; + } + + KWaitKey(); + } else if (key == ES_SCANCODE_1) { + ArchResetCPU(); + } else if (key == ES_SCANCODE_2) { + EsPrint("Enter address: "); + uintptr_t address = DebugReadNumber(); + uintptr_t offset = address & (K_PAGE_SIZE - 1); + MMArchRemap(kernelMMSpace, pmm.pmManipulationRegion, address - offset); + uintptr_t *data = (uintptr_t *) ((uint8_t *) pmm.pmManipulationRegion + offset); + + for (uintptr_t i = 0; i < 8 && (offset + 8 * sizeof(uintptr_t) < K_PAGE_SIZE); i++) { + EsPrint("\n%x - %x\n", address + 8 * sizeof(uintptr_t), data[i]); + } + + while (KWaitKey() != ES_SCANCODE_ENTER); + } else if (key == ES_SCANCODE_3) { + EsPrint("Enter address: "); + uintptr_t address = DebugReadNumber(); + uintptr_t offset = address & (K_PAGE_SIZE - 1); + uintptr_t *data = (uintptr_t *) address; + + for (uintptr_t i = 0; i < 8 && (offset + i * sizeof(uintptr_t) < K_PAGE_SIZE); i++) { + EsPrint("\n%x - %x", address + i * sizeof(uintptr_t), data[i]); + } + + while (KWaitKey() != ES_SCANCODE_ENTER); + } else if (key == ES_SCANCODE_4) { + EsPrint("Enter RBP: "); + uintptr_t address = DebugReadNumber(); + + while (address) { + EsPrint("\n%x", ((uintptr_t *) address)[1]); + address = ((uintptr_t *) address)[0]; + } + + while (KWaitKey() != ES_SCANCODE_ENTER); + } + } +#endif + + ProcessorHalt(); +} + +void EsPrint(const char *format, ...) { + KSpinlockAcquire(&printLock); + EsDefer(KSpinlockRelease(&printLock)); + + va_list arguments; + va_start(arguments, format); + _StringFormat(TerminalCallback, (void *) 0x0700, format, arguments); + va_end(arguments); +} + +void __KernelLog(const char *format, ...) { + va_list arguments; + va_start(arguments, format); + _StringFormat(TerminalCallback, nullptr, format, arguments); + va_end(arguments); +} + +void KernelLog(KLogLevel level, const char *subsystem, const char *event, const char *format, ...) { + if (level == LOG_VERBOSE) return; + (void) event; + + KSpinlockAcquire(&printLock); + EsDefer(KSpinlockRelease(&printLock)); + + __KernelLog("[%z:%z] ", level == LOG_INFO ? "Info" : level == LOG_ERROR ? "**Error**" : level == LOG_VERBOSE ? "Verbose" : "", subsystem); + + va_list arguments; + va_start(arguments, format); + _StringFormat(TerminalCallback, nullptr, format, arguments); + va_end(arguments); +} + +#endif diff --git a/kernel/windows.cpp b/kernel/windows.cpp new file mode 100644 index 0000000..5924e80 --- /dev/null +++ b/kernel/windows.cpp @@ -0,0 +1,1407 @@ +#ifndef IMPLEMENTATION + +// TODO Don't send key released messages if the focused window has changed. +// TODO Blur clamping is incorrect with minimal repainting! + +struct EmbeddedWindow { + void Destroy(); + void Close(); + void SetEmbedOwner(Process *process); + + Process *volatile owner; + void *volatile apiWindow; + volatile uint32_t handles; + struct Window *container; + uint64_t id; + uint32_t resizeClearColor; + bool closed; +}; + +struct Window { + void Update(EsRectangle *region, bool addToModifiedRegion); + bool UpdateDirect(K_USER_BUFFER void *bits, uintptr_t stride, EsRectangle region); + void Destroy(); + void Close(); + bool Move(EsRectangle newBounds, uint32_t flags); + void SetEmbed(EmbeddedWindow *window); + bool IsVisible(); + + // State: + EsWindowStyle style; + EsRectangle solidInsets; + bool solid, noClickActivate, hidden, isMaximised, alwaysOnTop, hoveringOverEmbed, queuedScrollUpdate, activationClick, noBringToFront; + volatile bool closed; + + // Appearance: + Surface surface; + EsRectangle opaqueBounds, blurBounds; + uint8_t alpha, material; + uint32_t resizeClearColor; + + // Owner and children: + Process *owner; + void *apiWindow; + EmbeddedWindow *embed; + volatile uint64_t handles; + uint64_t id; + + // Location: + EsPoint position; + size_t width, height; +}; + +struct WindowManager { + void *CreateWindow(Process *process, void *apiWindow, EsWindowStyle style); + void *CreateEmbeddedWindow(Process *process, void *apiWindow); + Window *FindWindowAtPosition(int cursorX, int cursorY); + + void Initialise(); + + void MoveCursor(int xMovement, int yMovement); + void ClickCursor(unsigned buttons); + void UpdateCursor(int xMovement, int yMovement, unsigned buttons); + void PressKey(unsigned scancode); + + void Redraw(EsPoint position, int width, int height, Window *except = nullptr, int startingAt = 0, bool addToModifiedRegion = true); + + bool ActivateWindow(Window *window); // Returns true if any menus were closed. + void HideWindow(Window *window); + Window *FindWindowToActivate(Window *excluding = nullptr); + uintptr_t GetActivationZIndex(); + void ChangeWindowDepth(Window *window, bool alwaysRedraw, ptrdiff_t newZDepth); + intptr_t FindWindowDepth(Window *window); + bool CloseMenus(); // Returns true if any menus were closed. + + void StartEyedrop(uintptr_t object, Window *avoid, uint32_t cancelColor); + void EndEyedrop(bool cancelled); + + bool initialised; + + // Windows: + + Array windows; // Sorted by z. + Array embeddedWindows; + Window *pressedWindow, *activeWindow, *hoverWindow; + KMutex mutex; + KEvent windowsToCloseEvent; + uint64_t currentWindowID; + size_t inspectorWindowCount; + + // Cursor: + + int cursorX, cursorY; + int cursorXPrecise, cursorYPrecise; // Scaled up by a factor of 10. + unsigned lastButtons; + + Surface cursorSurface, cursorSwap; + int cursorImageOffsetX, cursorImageOffsetY; + uintptr_t cursorID; + bool cursorXOR; + bool changedCursorImage; + + // Keyboard: + + bool shift, alt, ctrl, flag; + bool shift2, alt2, ctrl2, flag2; + bool numlock; + uint8_t modifiers; + + // Eyedropper: + + uintptr_t eyedropObject; + bool eyedropping; + Process *eyedropProcess; + uint64_t eyedropAvoidID; + uint32_t eyedropCancelColor; + + // Miscellaneous: + + EsRectangle workArea; + + // Game controllers: + + KMutex gameControllersMutex; + EsGameControllerState gameControllers[ES_GAME_CONTROLLER_MAX_COUNT]; + size_t gameControllerCount; + uint64_t gameControllerID; + + // Flicker-free resizing: + +#define RESIZE_FLICKER_TIMEOUT_MS (40) +#define RESIZE_SLOW_THRESHOLD (RESIZE_FLICKER_TIMEOUT_MS * 3 / 4) + Window *resizeWindow; + bool resizeReceivedBitsFromContainer; + bool resizeReceivedBitsFromEmbed; + uint64_t resizeStartTimeStampMs; + EsRectangle resizeQueuedRectangle; + bool resizeQueued; + bool resizeSlow; // Set if the previous resize went past RESIZE_FLICKER_SLOW_THRESHOLD; + // when set, the old surface bits are copied on resize, so that if the resize times out the result will be reasonable. +}; + +struct ClipboardItem { + uint64_t format; + ConstantBuffer *buffer; +}; + +struct Clipboard { + KMutex mutex; +#define CLIPBOARD_ITEM_COUNT (1) + ClipboardItem items[CLIPBOARD_ITEM_COUNT]; + +#define CLIPBOARD_ITEM_SIZE_LIMIT (64 * 1024 * 1024) + EsError Add(uint64_t format, K_USER_BUFFER const void *data, size_t dataBytes); + bool Has(uint64_t format); + EsHandle Read(uint64_t format, K_USER_BUFFER size_t *bytes, Process *process); +}; + +WindowManager windowManager; +Clipboard primaryClipboard; + +void SendMessageToWindow(Window *window, EsMessage *message); + +#ifdef TEST_HIGH_UI_SCALE +#define UI_SCALE (140) +#else +#define UI_SCALE (100) +#endif + +#define WINDOW_INSET (19 * UI_SCALE / 100) +#define CONTAINER_TAB_BAND_HEIGHT (33 * UI_SCALE / 100) +#define BORDER_THICKNESS (9 * UI_SCALE / 100) + +#else + +bool Window::IsVisible() { + return !hidden && !closed && (id != windowManager.eyedropAvoidID || !windowManager.eyedropping); +} + +void SendMessageToWindow(Window *window, EsMessage *message) { + KMutexAssertLocked(&windowManager.mutex); + + if (window->closed) { + return; + } + + if (!window->owner->handles) { + KernelPanic("SendMessageToWindow - (%x:%d/%x:%d) No handles.\n", window, window->handles, window->owner, window->owner->handles); + } + + if (window->style != ES_WINDOW_CONTAINER || !window->embed) { + window->owner->messageQueue.SendMessage(window->apiWindow, message); + return; + } + + if (message->type == ES_MSG_WINDOW_RESIZED) { + message->windowResized.content = ES_RECT_4(0, window->width, 0, window->height); + window->owner->messageQueue.SendMessage(window->apiWindow, message); + + message->windowResized.content = ES_RECT_4(0, window->width - WINDOW_INSET * 2, 0, window->height - WINDOW_INSET * 2 - CONTAINER_TAB_BAND_HEIGHT); + window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message); + } else if (message->type == ES_MSG_WINDOW_DEACTIVATED || message->type == ES_MSG_WINDOW_ACTIVATED) { + window->owner->messageQueue.SendMessage(window->apiWindow, message); + window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message); + } else if (message->type == ES_MSG_MOUSE_MOVED) { + EsRectangle embedRegion = ES_RECT_4(WINDOW_INSET, window->width - WINDOW_INSET, WINDOW_INSET + CONTAINER_TAB_BAND_HEIGHT, window->height - WINDOW_INSET); + bool inEmbed = windowManager.pressedWindow + ? window->hoveringOverEmbed + : EsRectangleContains(embedRegion, message->mouseMoved.newPositionX, message->mouseMoved.newPositionY); + + if (inEmbed) { + message->mouseMoved.newPositionX -= WINDOW_INSET; + message->mouseMoved.newPositionY -= WINDOW_INSET + CONTAINER_TAB_BAND_HEIGHT; + + window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message); + + if (!windowManager.pressedWindow && !window->hoveringOverEmbed) { + message->type = ES_MSG_MOUSE_EXIT; + window->owner->messageQueue.SendMessage(window->apiWindow, message); + } + } else { + window->owner->messageQueue.SendMessage(window->apiWindow, message); + + if (!windowManager.pressedWindow && window->hoveringOverEmbed) { + message->type = ES_MSG_MOUSE_EXIT; + window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message); + } + } + + if (!windowManager.pressedWindow) { + window->hoveringOverEmbed = inEmbed; + } + } else if (message->type >= ES_MSG_MOUSE_LEFT_DOWN && message->type <= ES_MSG_MOUSE_MIDDLE_UP) { + if (window->hoveringOverEmbed) { + message->mouseDown.positionX -= WINDOW_INSET; + message->mouseDown.positionY -= WINDOW_INSET + CONTAINER_TAB_BAND_HEIGHT; + + if (!window->activationClick) { + window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message); + } + } else { + window->owner->messageQueue.SendMessage(window->apiWindow, message); + } + } else if (message->type == ES_MSG_KEY_DOWN || message->type == ES_MSG_KEY_UP) { + window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message); + + // TODO Better method of handling keyboard shortcuts that Desktop needs to process? + // By sending the message to both processes, two threads will wake up, + // which could increase latency of handling the message. + window->owner->messageQueue.SendMessage(window->apiWindow, message); + } else if (message->type == ES_MSG_MOUSE_EXIT || message->type == ES_MSG_PRIMARY_CLIPBOARD_UPDATED) { + window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message); + window->owner->messageQueue.SendMessage(window->apiWindow, message); + } else { + window->owner->messageQueue.SendMessage(window->apiWindow, message); + } +} + +Window *WindowManager::FindWindowAtPosition(int cursorX, int cursorY) { + KMutexAssertLocked(&mutex); + + for (intptr_t i = windows.Length() - 1; i >= 0; i--) { + Window *window = windows[i]; + EsRectangle bounds = ES_RECT_4PD(window->position.x, window->position.y, window->width, window->height); + + if (window->solid && !window->hidden && EsRectangleContains(EsRectangleAdd(bounds, window->solidInsets), cursorX, cursorY) + && (!window->isMaximised || EsRectangleContains(workArea, cursorX, cursorY))) { + return window; + } + } + + return nullptr; +} + +void WindowManager::UpdateCursor(int xMovement, int yMovement, unsigned buttons) { + if (!initialised) { + return; + } + + if (xMovement || yMovement) { + if (xMovement * xMovement + yMovement * yMovement < 10 && buttons != lastButtons) { + // This seems to be movement noise generated when the buttons were pressed/released. + } else { + KMutexAcquire(&mutex); + MoveCursor(xMovement, yMovement); + KMutexRelease(&mutex); + } + } + + ClickCursor(buttons); +} + +void WindowManager::EndEyedrop(bool cancelled) { + KMutexAssertLocked(&mutex); + + eyedropping = false; + + EsMessage m = { ES_MSG_EYEDROP_REPORT }; + uint32_t color = *(uint32_t *) ((uint8_t *) graphics.frameBuffer.bits + 4 * cursorX + graphics.frameBuffer.stride * cursorY) | 0xFF000000; + m.eyedrop.color = cancelled ? eyedropCancelColor : color; + m.eyedrop.cancelled = cancelled; + eyedropProcess->messageQueue.SendMessage((void *) eyedropObject, &m); + CloseHandleToObject(eyedropProcess, KERNEL_OBJECT_PROCESS); + eyedropProcess = nullptr; + + Redraw(ES_POINT(0, 0), graphics.width, graphics.height); +} + +void WindowManager::PressKey(unsigned scancode) { + if (!initialised) { + return; + } + + bool moveCursorNone = false; + + KMutexAcquire(&mutex); + + if (scancode == ES_SCANCODE_NUM_DIVIDE) { + KernelPanic("WindowManager::PressKey - Panic key pressed.\n"); + } + + if (eyedropping) { + if (scancode == (ES_SCANCODE_ESCAPE | K_SCANCODE_KEY_RELEASED)) { + EndEyedrop(true); + moveCursorNone = true; + } + + goto done; + } + + // TODO Caps lock. + + if (scancode == ES_SCANCODE_LEFT_CTRL) ctrl = true; + if (scancode == (ES_SCANCODE_LEFT_CTRL | K_SCANCODE_KEY_RELEASED)) ctrl = false; + if (scancode == ES_SCANCODE_LEFT_SHIFT) shift = true; + if (scancode == (ES_SCANCODE_LEFT_SHIFT | K_SCANCODE_KEY_RELEASED)) shift = false; + if (scancode == ES_SCANCODE_LEFT_ALT) alt = true; + if (scancode == (ES_SCANCODE_LEFT_ALT | K_SCANCODE_KEY_RELEASED)) alt = false; + if (scancode == ES_SCANCODE_LEFT_FLAG) flag = true; + if (scancode == (ES_SCANCODE_LEFT_FLAG | K_SCANCODE_KEY_RELEASED)) flag = false; + + if (scancode == ES_SCANCODE_RIGHT_CTRL) ctrl2 = true; + if (scancode == (ES_SCANCODE_RIGHT_CTRL | K_SCANCODE_KEY_RELEASED)) ctrl2 = false; + if (scancode == ES_SCANCODE_RIGHT_SHIFT) shift2 = true; + if (scancode == (ES_SCANCODE_RIGHT_SHIFT | K_SCANCODE_KEY_RELEASED)) shift2 = false; + if (scancode == ES_SCANCODE_RIGHT_ALT) alt2 = true; + if (scancode == (ES_SCANCODE_RIGHT_ALT | K_SCANCODE_KEY_RELEASED)) alt2 = false; + if (scancode == ES_SCANCODE_RIGHT_FLAG) flag2 = true; + if (scancode == (ES_SCANCODE_RIGHT_FLAG | K_SCANCODE_KEY_RELEASED)) flag2 = false; + + modifiers = ((alt | alt2) ? ES_MODIFIER_ALT : 0) + | ((ctrl | ctrl2) ? ES_MODIFIER_CTRL : 0) + | ((shift | shift2) ? ES_MODIFIER_SHIFT : 0) + | ((flag | flag2) ? ES_MODIFIER_FLAG : 0); + + if (activeWindow) { + Window *window = activeWindow; + + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = (scancode & K_SCANCODE_KEY_RELEASED) ? ES_MSG_KEY_UP : ES_MSG_KEY_DOWN; + message.keyboard.modifiers = modifiers; + message.keyboard.scancode = scancode & ~K_SCANCODE_KEY_RELEASED; + message.keyboard.numlock = numlock; + + static uint8_t heldKeys[512 / 8] = {}; + + if (message.keyboard.scancode >= 512) { + KernelPanic("WindowManager::PressKey - Scancode outside valid range.\n"); + } + + if (message.type == ES_MSG_KEY_DOWN && (heldKeys[message.keyboard.scancode / 8] & (1 << (message.keyboard.scancode % 8)))) { + message.keyboard.repeat = true; + } + + if (message.type == ES_MSG_KEY_DOWN) { + heldKeys[message.keyboard.scancode / 8] |= (1 << (message.keyboard.scancode % 8)); + } else { + heldKeys[message.keyboard.scancode / 8] &= ~(1 << (message.keyboard.scancode % 8)); + } + + SendMessageToWindow(window, &message); + } + + done:; + if (moveCursorNone) MoveCursor(0, 0); + KMutexRelease(&mutex); +} + +Window *WindowManager::FindWindowToActivate(Window *excluding) { + for (uintptr_t i = windows.Length(); i > 0; i--) { + if (!windows[i - 1]->hidden + && windows[i - 1]->style == ES_WINDOW_CONTAINER + && windows[i - 1] != excluding) { + return windows[i - 1]; + } + } + + return nullptr; +} + +uintptr_t WindowManager::GetActivationZIndex() { + for (uintptr_t i = windows.Length(); i > 0; i--) { + if (!windows[i - 1]->alwaysOnTop) { + return i; + } + } + + return 0; +} + +void WindowManager::HideWindow(Window *window) { + KMutexAssertLocked(&mutex); + + if (window->hidden) return; + window->hidden = true; + + if (window == activeWindow) { + ActivateWindow(FindWindowToActivate()); + } + + Redraw(ES_POINT(window->position.x, window->position.y), window->width, window->height); +} + +intptr_t WindowManager::FindWindowDepth(Window *window) { + KMutexAssertLocked(&windowManager.mutex); + + for (uintptr_t i = 0; i < windows.Length(); i++) { + if (windows[i] == window) { + return i; + } + } + + return -1; +} + +void WindowManager::ChangeWindowDepth(Window *window, bool redraw, ptrdiff_t _newZDepth) { + KMutexAssertLocked(&windowManager.mutex); + + // Reorder the windows in the array, and update the depth buffer. + intptr_t oldZDepth = FindWindowDepth(window); + if (oldZDepth == -1) KernelPanic("WindowManager::ChangeWindowDepth - Window %x was not in array.\n", window); + windows.Delete(oldZDepth); + intptr_t newZDepth = _newZDepth != -1 ? _newZDepth : GetActivationZIndex(); + windows.Insert(window, newZDepth); + + if (oldZDepth != newZDepth || redraw) { + // Redraw the modified area of the screen. + windowManager.Redraw(ES_POINT(window->position.x, window->position.y), window->width, window->height); + } +} + +bool WindowManager::CloseMenus() { + KMutexAssertLocked(&mutex); + + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + + bool result = false; + + for (uintptr_t i = 0; i < windows.Length(); i++) { + // Close any open menus. + + if (windows[i]->style == ES_WINDOW_MENU) { + message.type = ES_MSG_WINDOW_DEACTIVATED; + SendMessageToWindow(windows[i], &message); + result = true; + } + } + + return result; +} + +bool WindowManager::ActivateWindow(Window *window) { + KMutexAssertLocked(&mutex); + + if (window && (window->style == ES_WINDOW_TIP || window->style == ES_WINDOW_MENU || window->closed)) { + return false; + } + + Window *oldWindow = activeWindow; + + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + + bool result = CloseMenus(); + + // Set the active window, unless it hasn't changed. + + if (activeWindow == window && (!window || !window->hidden)) { + // Bring the window to the front anyway. + if (window && !window->noBringToFront) ChangeWindowDepth(window, false, -1); + return result; + } + + activeWindow = window; + if (window) window->hidden = false; + + if (window) { + // Bring the window to the front. + if (!window->noBringToFront) ChangeWindowDepth(window, true, -1); + + // Activate the new window. + message.type = ES_MSG_WINDOW_ACTIVATED; + SendMessageToWindow(window, &message); + } else { + // No window is active. + } + + if (oldWindow && oldWindow != window) { + // Deactivate the old window. + + if (oldWindow != FindWindowAtPosition(cursorX, cursorY)) { + message.type = ES_MSG_MOUSE_EXIT; + SendMessageToWindow(oldWindow, &message); + } + + message.type = ES_MSG_WINDOW_DEACTIVATED; + SendMessageToWindow(oldWindow, &message); + } + + return result; +} + +void WindowManager::ClickCursor(unsigned buttons) { + KMutexAcquire(&mutex); + + unsigned delta = lastButtons ^ buttons; + lastButtons = buttons; + + bool moveCursorNone = false; + + if (eyedropping && delta) { + if ((delta & K_LEFT_BUTTON) && (~buttons & K_LEFT_BUTTON)) { + EndEyedrop(false); + moveCursorNone = true; + } + } else if (delta) { + // Send a mouse pressed message to the window the cursor is over. + Window *window = FindWindowAtPosition(cursorX, cursorY); + + bool activationClick = false, activationClickFromMenu = false; + + if (buttons == delta) { + if (activeWindow != window) { + activationClick = true; + } + + if (window && window->noClickActivate) { + if (!window->noBringToFront) { + ChangeWindowDepth(window, false, -1); + } + + if (window->style != ES_WINDOW_MENU) { + activationClickFromMenu = CloseMenus(); + } + } else { + activationClickFromMenu = ActivateWindow(window); + } + } + + { + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + + if (delta & K_LEFT_BUTTON) message.type = (buttons & K_LEFT_BUTTON) ? ES_MSG_MOUSE_LEFT_DOWN : ES_MSG_MOUSE_LEFT_UP; + if (delta & K_RIGHT_BUTTON) message.type = (buttons & K_RIGHT_BUTTON) ? ES_MSG_MOUSE_RIGHT_DOWN : ES_MSG_MOUSE_RIGHT_UP; + if (delta & K_MIDDLE_BUTTON) message.type = (buttons & K_MIDDLE_BUTTON) ? ES_MSG_MOUSE_MIDDLE_DOWN : ES_MSG_MOUSE_MIDDLE_UP; + + if (message.type == ES_MSG_MOUSE_LEFT_DOWN) { + pressedWindow = window; + } else if (message.type == ES_MSG_MOUSE_LEFT_UP) { + if (pressedWindow) { + // Always send the messages to the pressed window, if there is one. + window = pressedWindow; + } + + pressedWindow = nullptr; + moveCursorNone = true; // We might have moved outside the window. + } + + if (window) { + message.mouseDown.positionX = cursorX - window->position.x; + message.mouseDown.positionY = cursorY - window->position.y; + window->activationClick = activationClick || activationClickFromMenu; + SendMessageToWindow(window, &message); + } + } + } + + if (moveCursorNone) { + MoveCursor(0, 0); + } else { + GraphicsUpdateScreen(); + } + + KMutexRelease(&mutex); +} + +void WindowManager::MoveCursor(int xMovement, int yMovement) { + KMutexAssertLocked(&mutex); + + xMovement *= alt ? 2 : 10; + yMovement *= alt ? 2 : 10; + + int oldCursorX = cursorXPrecise; + int oldCursorY = cursorYPrecise; + int _cursorX = oldCursorX + xMovement; + int _cursorY = oldCursorY + yMovement; + + // if (!lastButtons) { + if (_cursorX < 0) { + _cursorX = 0; + } + + if (_cursorY < 0) { + _cursorY = 0; + } + + if (_cursorX >= (int) graphics.width * 10) { + _cursorX = graphics.width * 10 - 1; + } + + if (_cursorY >= (int) graphics.height * 10) { + _cursorY = graphics.height * 10 - 1; + } + // } + + cursorXPrecise = _cursorX; + cursorYPrecise = _cursorY; + cursorX = _cursorX / 10; + cursorY = _cursorY / 10; + oldCursorX /= 10; + oldCursorY /= 10; + + if (eyedropping) { + EsMessage m = { ES_MSG_EYEDROP_REPORT }; + uint32_t color = *(uint32_t *) ((uint8_t *) graphics.frameBuffer.bits + 4 * cursorX + graphics.frameBuffer.stride * cursorY); + m.eyedrop.color = color; + m.eyedrop.cancelled = false; + eyedropProcess->messageQueue.SendMessage((void *) eyedropObject, &m); + } else { + Window *window = pressedWindow; + + if (!window) { + // Work out which window the mouse is now over. + window = FindWindowAtPosition(cursorX, cursorY); + + if (hoverWindow != window && hoverWindow) { + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_MOUSE_EXIT; + SendMessageToWindow(hoverWindow, &message); + } + + hoverWindow = window; + } + + if (window) { + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_MOUSE_MOVED; + message.mouseMoved.newPositionX = cursorX - window->position.x; + message.mouseMoved.newPositionY = cursorY - window->position.y; + SendMessageToWindow(window, &message); + } + } + + GraphicsUpdateScreen(); +} + +void _CloseWindows(uintptr_t) { + while (true) { + KEventWait(&windowManager.windowsToCloseEvent); + KMutexAcquire(&windowManager.mutex); + + for (uintptr_t i = 0; i < windowManager.windows.Length(); i++) { + Window *window = windowManager.windows[i]; + + if (window->handles > 1) { + continue; + } + + // Remove the window from the array. + windowManager.windows.Delete(i); + + if (!window->closed) { + // Only the window manager's handle to the window remains, + // but the window has not been closed. + // This probably means the process crashed before it could close its windows; + // we should close the window ourselves. + window->Close(); + } + + // Close the window manager's handle to the window. + CloseHandleToObject(window, KERNEL_OBJECT_WINDOW); + + i--; + } + + for (uintptr_t i = 0; i < windowManager.embeddedWindows.Length(); i++) { + // Apply a similar set of operations to embedded windows. + EmbeddedWindow *window = windowManager.embeddedWindows[i]; + if (window->handles > 1) continue; + if (!window->closed) window->Close(); + windowManager.embeddedWindows.Delete(i); + CloseHandleToObject(window, KERNEL_OBJECT_EMBEDDED_WINDOW); + i--; + } + + KMutexRelease(&windowManager.mutex); + } +} + +void WindowManager::Initialise() { + windowManager.windowsToCloseEvent.autoReset = true; + KThreadCreate("CloseWindows", _CloseWindows); + KMutexAcquire(&mutex); + MoveCursor(graphics.width / 2, graphics.height / 2); + GraphicsUpdateScreen(); + initialised = true; + KMutexRelease(&mutex); +} + +void *WindowManager::CreateEmbeddedWindow(Process *process, void *apiWindow) { + EmbeddedWindow *window = (EmbeddedWindow *) EsHeapAllocate(sizeof(EmbeddedWindow), true, K_PAGED); + if (!window) return nullptr; + + if (!embeddedWindows.Add(window)) { + EsHeapFree(window, sizeof(EmbeddedWindow), K_PAGED); + return nullptr; + } + + window->apiWindow = apiWindow; + window->owner = process; + window->handles = 2; // One handle for the window manager, and one handle for the calling process. + + KMutexAcquire(&mutex); + window->id = ++currentWindowID; + KMutexRelease(&mutex); + + OpenHandleToObject(window->owner, KERNEL_OBJECT_PROCESS); + + return window; +} + +void *WindowManager::CreateWindow(Process *process, void *apiWindow, EsWindowStyle style) { + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + // Allocate and initialise the window object. + + Window *window = (Window *) EsHeapAllocate(sizeof(Window), true, K_PAGED); + if (!window) return nullptr; + + window->style = style; + window->apiWindow = apiWindow; + window->alpha = 0xFF; + window->position = ES_POINT(-8, -8); + window->owner = process; + window->handles = 2; // One handle for the window manager, and one handle for the calling process. + window->hidden = true; + window->id = ++currentWindowID; + + if (style == ES_WINDOW_INSPECTOR) windowManager.inspectorWindowCount++; + + // Insert the window into the window array. + + uintptr_t insertionPoint = GetActivationZIndex(); + + if (!windows.Insert(window, insertionPoint)) { + EsHeapFree(window->surface.bits, 0, K_PAGED); + EsHeapFree(window, sizeof(Window), K_PAGED); + return nullptr; + } + + // Get a handle to the owner process. + + OpenHandleToObject(window->owner, KERNEL_OBJECT_PROCESS); + + MoveCursor(0, 0); + return window; +} + +bool Window::Move(EsRectangle rectangle, uint32_t flags) { + KMutexAssertLocked(&windowManager.mutex); + + if (closed) { + return false; + } + + if ((flags & ES_WINDOW_MOVE_DYNAMIC) + && windowManager.resizeWindow == this + && windowManager.resizeStartTimeStampMs + RESIZE_FLICKER_TIMEOUT_MS > KGetTimeInMs()) { + windowManager.resizeQueued = true; + windowManager.resizeQueuedRectangle = rectangle; + return false; + } + + bool result = true; + + isMaximised = flags & ES_WINDOW_MOVE_MAXIMISED; + alwaysOnTop = flags & ES_WINDOW_MOVE_ALWAYS_ON_TOP; + + // TS("Move window\n"); + + if (flags & ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN) { + if (rectangle.r > (int32_t) graphics.frameBuffer.width) { + rectangle.l -= rectangle.r - graphics.frameBuffer.width; + rectangle.r -= rectangle.r - graphics.frameBuffer.width; + } + + if (rectangle.b > (int32_t) graphics.frameBuffer.height) { + rectangle.t -= rectangle.b - graphics.frameBuffer.height; + rectangle.b -= rectangle.b - graphics.frameBuffer.height; + } + + if (rectangle.l < 0) { + rectangle.r -= rectangle.l - 0; + rectangle.l = 0; + } + + if (rectangle.t < 0) { + rectangle.b -= rectangle.t - 0; + rectangle.t = 0; + } + } + + size_t newWidth = rectangle.r - rectangle.l; + size_t newHeight = rectangle.b - rectangle.t; + + if (newWidth < 4 || newHeight < 4 + || rectangle.l > rectangle.r + || rectangle.t > rectangle.b + || newWidth > graphics.width * 2 + || newHeight > graphics.height * 2) { + return false; + } + + if (!hidden) { + // TS("Clear previous image\n"); + windowManager.Redraw(ES_POINT(position.x, position.y), width, height, this); + } + + hidden = false; + position = ES_POINT(rectangle.l, rectangle.t); + bool changedSize = width != newWidth || height != newHeight; + width = newWidth, height = newHeight; + + if (changedSize) { + if (style == ES_WINDOW_CONTAINER && embed) { + surface.Resize(width, height, 0, windowManager.resizeSlow); + } else { + surface.Resize(width, height); + } + + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_WINDOW_RESIZED; + message.windowResized.content = ES_RECT_4(0, width, 0, height); + SendMessageToWindow(this, &message); + + if (flags & ES_WINDOW_MOVE_DYNAMIC) { + // Don't redraw the screen until both the container and embedded window have painted + // (or the timeout completes). + windowManager.resizeWindow = this; + windowManager.resizeReceivedBitsFromContainer = false; + windowManager.resizeReceivedBitsFromEmbed = embed == nullptr; + windowManager.resizeStartTimeStampMs = KGetTimeInMs(); + } + } + + if (flags & ES_WINDOW_MOVE_AT_BOTTOM) { + windowManager.ChangeWindowDepth(this, false, 0); + } + + if ((flags & ES_WINDOW_MOVE_DYNAMIC) && changedSize && style == ES_WINDOW_CONTAINER && !windowManager.resizeSlow) { + windowManager.Redraw(ES_POINT(position.x, position.y), WINDOW_INSET, height, nullptr); + windowManager.Redraw(ES_POINT(position.x + width - WINDOW_INSET, position.y), WINDOW_INSET, height, nullptr); + windowManager.Redraw(ES_POINT(position.x + WINDOW_INSET, position.y), width - WINDOW_INSET * 2, WINDOW_INSET, nullptr); + windowManager.Redraw(ES_POINT(position.x + WINDOW_INSET, position.y + height - WINDOW_INSET), width - WINDOW_INSET * 2, WINDOW_INSET, nullptr); + } else { + windowManager.Redraw(position, width, height, nullptr); + } + + return result; +} + +void EmbeddedWindow::Destroy() { + KernelLog(LOG_VERBOSE, "Window Manager", "destroy embedded window", "EmbeddedWindow::Destroy - Destroying embedded window.\n"); + EsHeapFree(this, sizeof(EmbeddedWindow), K_PAGED); +} + +void EmbeddedWindow::Close() { + KMutexAssertLocked(&windowManager.mutex); + + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_WINDOW_DESTROYED; + owner->messageQueue.SendMessage(apiWindow, &message); + SetEmbedOwner(nullptr); + + if (!scheduler.shutdown) { + message.type = ES_MSG_EMBEDDED_WINDOW_DESTROYED; + message.desktop.windowID = id; + desktopProcess->messageQueue.SendMessage(nullptr, &message); + } + + if (container && container->embed == this) { + container->SetEmbed(nullptr); + } + + closed = true; +} + +void Window::Destroy() { + if (!closed) { + KernelPanic("Window::Destroy - Window %x has not been closed.\n", this); + } + + EsHeapFree(this, sizeof(Window), K_PAGED); +} + +void Window::Close() { + KMutexAssertLocked(&windowManager.mutex); + + SetEmbed(nullptr); + + // Send the destroy message - the last message sent to the window. + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_WINDOW_DESTROYED; + owner->messageQueue.SendMessage(apiWindow, &message); + + Process *_owner = owner; + owner = nullptr; + CloseHandleToObject(_owner, KERNEL_OBJECT_PROCESS); + + hidden = true; + solid = false; + + bool findActiveWindow = false; + + if (windowManager.pressedWindow == this) windowManager.pressedWindow = nullptr; + if (windowManager.hoverWindow == this) windowManager.hoverWindow = nullptr; + if (windowManager.resizeWindow == this) windowManager.resizeWindow = nullptr; + if (windowManager.activeWindow == this) windowManager.activeWindow = nullptr, findActiveWindow = true; + + if (style == ES_WINDOW_INSPECTOR) windowManager.inspectorWindowCount--; + + windowManager.Redraw(ES_POINT(position.x, position.y), width, height); + + if (findActiveWindow) { + windowManager.ActivateWindow(windowManager.FindWindowToActivate()); + } + + windowManager.MoveCursor(0, 0); + GraphicsUpdateScreen(); + + __sync_fetch_and_sub(&graphics.totalSurfaceBytes, surface.width * surface.height * 4); + EsHeapFree(surface.bits, 0, K_PAGED); + surface.bits = nullptr; + + __sync_synchronize(); + closed = true; +} + +void WindowManager::Redraw(EsPoint position, int width, int height, Window *except, int startingAt, bool addToModifiedRegion) { + // TS("Window manager redraw at [%d, %d] with size [%d, %d]\n", position.x, position.y, width, height); + + KMutexAssertLocked(&mutex); + + if (!width || !height) return; + if (scheduler.shutdown) return; + + for (int index = startingAt; index < (int) windows.Length(); index++) { + Window *window = windows[index]; + + if (!window->IsVisible() || window == except) { + continue; + } + + if (position.x >= window->position.x + window->opaqueBounds.l + && position.x + width <= window->position.x + window->opaqueBounds.r + && position.y >= window->position.y + window->opaqueBounds.t + && position.y + height <= window->position.y + window->opaqueBounds.b) { + startingAt = index; + } + } + + for (int index = startingAt; index < (int) windows.Length(); index++) { + Window *window = windows[index]; + + if (!window->IsVisible() || window == except) continue; + + EsRectangle rectangle = ES_RECT_4PD(window->position.x, window->position.y, window->width, window->height); + rectangle = EsRectangleIntersection(rectangle, ES_RECT_2S(graphics.frameBuffer.width, graphics.frameBuffer.height)); + rectangle = EsRectangleIntersection(rectangle, ES_RECT_4PD(position.x, position.y, width, height)); + if (window->isMaximised) rectangle = EsRectangleIntersection(rectangle, workArea); + rectangle = Translate(rectangle, -window->position.x, -window->position.y); + + if (!ES_RECT_VALID(rectangle)) continue; + + EsPoint point = ES_POINT(window->position.x + rectangle.l, window->position.y + rectangle.t); + Surface *surface = &window->surface; + + if (window->opaqueBounds.l <= rectangle.l && window->opaqueBounds.r >= rectangle.r + && window->opaqueBounds.t <= rectangle.t && window->opaqueBounds.b >= rectangle.b) { + graphics.frameBuffer.Copy(surface, point, rectangle, addToModifiedRegion); + } else { + EsRectangle blurRegion = Translate(EsRectangleIntersection(window->blurBounds, rectangle), window->position.x, window->position.y); + graphics.frameBuffer.BlendWindow(surface, point, rectangle, window->material, window->alpha, blurRegion); + } + } +} + +bool Window::UpdateDirect(K_USER_BUFFER void *bits, uintptr_t stride, EsRectangle region) { + KMutexAssertLocked(&windowManager.mutex); + + intptr_t z = windowManager.FindWindowDepth(this); + + if (z == -1) { + return false; + } + + if (hidden) { + return false; + } + + if (windowManager.changedCursorImage) { + // The entire cursor needs to be redrawn about its image changes, + // which might not happen with a direct update. + return false; + } + + if (region.l + position.x < 0) { + bits = (K_USER_BUFFER uint8_t *) bits + 4 * (-position.x - region.l); + region.l = -position.x; + } + + if (region.t + position.y < 0) { + bits = (K_USER_BUFFER uint8_t *) bits + stride * (-position.y - region.t); + region.t = -position.y; + } + + if ((uint32_t) (region.r + position.x) > graphics.width) { + region.r = graphics.width - position.x; + } + + if ((uint32_t) (region.b + position.y) > graphics.height) { + region.b = graphics.height - position.y; + } + + // If the update region is completely obscured, then we don't need to update. + + for (uintptr_t i = z + 1; i < windowManager.windows.Length(); i++) { + Window *other = windowManager.windows[i]; + EsRectangle otherBounds = Translate(other->opaqueBounds, other->position.x, other->position.y); + + if (region.l + position.x > otherBounds.l && region.r + position.x < otherBounds.r + && region.t + position.y > otherBounds.t && region.b + position.y < otherBounds.b + && other->alpha == 0xFF && other->IsVisible()) { + return true; + } + } + + // If the update region isn't opaque, we cannot do a direct update. + + if (region.l < opaqueBounds.l || region.r > opaqueBounds.r || region.t < opaqueBounds.t || region.b > opaqueBounds.b + || alpha != 0xFF) { + return false; + } + + // If any window overlaps the update region, we cannot do a direct update. + + EsRectangle thisBounds = ES_RECT_4(position.x, position.x + width, position.y, position.y + height); + + for (uintptr_t i = z + 1; i < windowManager.windows.Length(); i++) { + Window *other = windowManager.windows[i]; + EsRectangle otherBounds = ES_RECT_4(other->position.x, other->position.x + other->width, + other->position.y, other->position.y + other->height); + + if (EsRectangleClip(thisBounds, otherBounds, nullptr)) { + return false; + } + } + + region = Translate(region, position.x, position.y); + + // Write the updated bits directly to the frame buffer! + GraphicsUpdateScreen(bits, ®ion, stride); + return true; +} + +void Window::Update(EsRectangle *_region, bool addToModifiedRegion) { + KMutexAssertLocked(&windowManager.mutex); + + intptr_t z = windowManager.FindWindowDepth(this); + + if (z == -1) { + return; + } + + // TS("Update window %x within %R\n", this, _region ? *_region : ES_RECT_1(0)); + + EsRectangle region = _region ? *_region : ES_RECT_4(0, width, 0, height); + + // Is this updated region completely obscured? + for (uintptr_t i = z + 1; i < windowManager.windows.Length(); i++) { + Window *other = windowManager.windows[i]; + EsRectangle otherBounds = Translate(other->opaqueBounds, other->position.x, other->position.y); + + if (region.l + position.x > otherBounds.l && region.r + position.x < otherBounds.r + && region.t + position.y > otherBounds.t && region.b + position.y < otherBounds.b + && other->alpha == 0xFF && other->IsVisible()) { + return; + } + } + + EsRectangle r; + // EsPrint("Update window width region %d/%d/%d/%d\n", region.left, region.right, region.top, region.bottom); + + if (alpha == 0xFF) { + EsRectangle translucentBorder = opaqueBounds; + + // Draw the opaque region, then everything above it. + EsRectangleClip(region, ES_RECT_4(translucentBorder.l, translucentBorder.r, translucentBorder.t, translucentBorder.b), &r); + windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, z, addToModifiedRegion); + + // Draw the transparent regions after everything below them, then everything above them. + EsRectangleClip(region, ES_RECT_4(0, translucentBorder.l, 0, height), &r); + windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion); + EsRectangleClip(region, ES_RECT_4(translucentBorder.r, width, 0, height), &r); + windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion); + EsRectangleClip(region, ES_RECT_4(translucentBorder.l, translucentBorder.r, 0, translucentBorder.t), &r); + windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion); + EsRectangleClip(region, ES_RECT_4(translucentBorder.l, translucentBorder.r, translucentBorder.b, height), &r); + windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion); + } else { + // The whole window is translucent; draw it. + EsRectangleClip(region, ES_RECT_4(0, width, 0, height), &r); + windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion); + } +} + +void EmbeddedWindow::SetEmbedOwner(Process *process) { + KMutexAssertLocked(&windowManager.mutex); + + apiWindow = nullptr; + + if (process) { + OpenHandleToObject(process, KERNEL_OBJECT_PROCESS); + } + + if (owner) { + CloseHandleToObject(owner, KERNEL_OBJECT_PROCESS); + } + + owner = process; +} + +void Window::SetEmbed(EmbeddedWindow *newEmbed) { + KMutexAssertLocked(&windowManager.mutex); + + if (newEmbed && (newEmbed->container || newEmbed->closed)) { + return; + } + + if (newEmbed == embed) { + return; + } + + if (newEmbed) { + newEmbed->container = this; + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_WINDOW_RESIZED; + int embedWidth = width - WINDOW_INSET * 2; + int embedHeight = height - WINDOW_INSET * 2 - CONTAINER_TAB_BAND_HEIGHT; + message.windowResized.content = ES_RECT_4(0, embedWidth, 0, embedHeight); + newEmbed->owner->messageQueue.SendMessage(newEmbed->apiWindow, &message); + } + + if (embed) { + if (embed->closed) { + KernelPanic("Window::SetEmbed - Previous embed %x was closed.\n"); + } + + embed->container = nullptr; + + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_WINDOW_RESIZED; + message.windowResized.content = ES_RECT_4(0, 0, 0, 0); + message.windowResized.hidden = true; + + if (embed->owner) { + embed->owner->messageQueue.SendMessage(embed->apiWindow, &message); + } + } + + embed = newEmbed; +} + +void WindowManager::StartEyedrop(uintptr_t object, Window *avoid, uint32_t cancelColor) { + KMutexAcquire(&mutex); + + if (!eyedropping) { + eyedropObject = object; + eyedropping = true; + eyedropAvoidID = avoid->id; + eyedropCancelColor = cancelColor; + Redraw(avoid->position, avoid->width, avoid->height); + + if (hoverWindow) { + EsMessage message; + EsMemoryZero(&message, sizeof(EsMessage)); + message.type = ES_MSG_MOUSE_EXIT; + SendMessageToWindow(hoverWindow, &message); + } + + hoverWindow = pressedWindow = nullptr; + + eyedropProcess = GetCurrentThread()->process; + OpenHandleToObject(eyedropProcess, KERNEL_OBJECT_PROCESS); + } + + GraphicsUpdateScreen(); + KMutexRelease(&mutex); +} + +void KCursorUpdate(int xMovement, int yMovement, unsigned buttons) { + windowManager.UpdateCursor(xMovement, yMovement, buttons); +} + +void KKeyPress(unsigned scancode) { + windowManager.PressKey(scancode); +} + +void KKeyboardUpdate(uint16_t *keysDown, size_t keysDownCount) { + // TODO Key repeat. + + static uint16_t previousKeysDown[32] = {}; + static size_t previousKeysDownCount = 0; + static KMutex mutex = {}; + + KMutexAcquire(&mutex); + EsDefer(KMutexRelease(&mutex)); + + if (keysDownCount > 32) { + keysDownCount = 32; + } + + for (uintptr_t i = 0; i < keysDownCount; i++) { + bool found = false; + + for (uintptr_t j = 0; j < previousKeysDownCount; j++) { + if (keysDown[i] == previousKeysDown[j]) { + found = true; + break; + } + } + + if (!found && keysDown[i]) { + if (keysDown[i] == ES_SCANCODE_PAUSE) { + KDebugKeyPressed(); // TODO Doesn't work if scheduler not functioning correctly. + } + + KKeyPress(keysDown[i] | K_SCANCODE_KEY_PRESSED); + } + } + + for (uintptr_t i = 0; i < previousKeysDownCount; i++) { + bool found = false; + + for (uintptr_t j = 0; j < keysDownCount; j++) { + if (keysDown[j] == previousKeysDown[i]) { + found = true; + break; + } + } + + if (!found) { + KKeyPress(previousKeysDown[i] | K_SCANCODE_KEY_RELEASED); + } + } + + previousKeysDownCount = keysDownCount; + EsMemoryCopy(previousKeysDown, keysDown, sizeof(uint16_t) * keysDownCount); +} + +uint64_t KGameControllerConnect() { + KMutexAcquire(&windowManager.gameControllersMutex); + + uint64_t id = ++windowManager.gameControllerID; + + if (windowManager.gameControllerCount != ES_GAME_CONTROLLER_MAX_COUNT) { + windowManager.gameControllers[windowManager.gameControllerCount++].id = id; + } else { + id = 0; + } + + KMutexRelease(&windowManager.gameControllersMutex); + + return id; +} + +void KGameControllerDisconnect(uint64_t id) { + KMutexAcquire(&windowManager.gameControllersMutex); + + for (uintptr_t i = 0; i < windowManager.gameControllerCount; i++) { + if (windowManager.gameControllers[i].id == id) { + EsMemoryMove(windowManager.gameControllers + i + 1, + windowManager.gameControllers + windowManager.gameControllerCount, + -sizeof(EsGameControllerState), false); + windowManager.gameControllerCount--; + break; + } + } + + KMutexRelease(&windowManager.gameControllersMutex); +} + +void KGameControllerUpdateState(EsGameControllerState *state) { + KMutexAcquire(&windowManager.gameControllersMutex); + + for (uintptr_t i = 0; i < windowManager.gameControllerCount; i++) { + if (windowManager.gameControllers[i].id == state->id) { + windowManager.gameControllers[i] = *state; +#if 0 + EsPrint("game controller %d: buttons %x analog %d %d %d %d %d %d dpad %d\n", + state->id, state->buttons, state->analog[0].x, state->analog[0].y, state->analog[0].z, + state->analog[1].x, state->analog[1].y, state->analog[1].z, state->directionalPad); +#endif + break; + } + } + + KMutexRelease(&windowManager.gameControllersMutex); +} + +EsError Clipboard::Add(uint64_t format, const void *data, size_t dataBytes) { + if (dataBytes > CLIPBOARD_ITEM_SIZE_LIMIT) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + ClipboardItem item = {}; + item.buffer = MakeConstantBuffer(data, dataBytes); + item.format = format; + + if (!item.buffer) { + return ES_ERROR_INSUFFICIENT_RESOURCES; + } + + KMutexAcquire(&mutex); + + if (items[CLIPBOARD_ITEM_COUNT - 1].buffer) { + CloseHandleToObject(items[CLIPBOARD_ITEM_COUNT - 1].buffer, KERNEL_OBJECT_CONSTANT_BUFFER); + } + + EsMemoryMove(items, items + CLIPBOARD_ITEM_COUNT - 1, sizeof(ClipboardItem), false); + items[0] = item; + + KMutexRelease(&mutex); + + if (this == &primaryClipboard) { + KMutexAcquire(&windowManager.mutex); + + if (windowManager.activeWindow) { + EsMessage m; + EsMemoryZero(&m, sizeof(EsMessage)); + m.type = ES_MSG_PRIMARY_CLIPBOARD_UPDATED; + SendMessageToWindow(windowManager.activeWindow, &m); + } + + KMutexRelease(&windowManager.mutex); + } + + return ES_SUCCESS; +} + +bool Clipboard::Has(uint64_t format) { + KMutexAcquire(&mutex); + bool result = items[0].format == format; + KMutexRelease(&mutex); + return result; +} + +EsHandle Clipboard::Read(uint64_t format, K_USER_BUFFER size_t *_bytes, Process *process) { + ConstantBuffer *buffer = nullptr; + KMutexAcquire(&mutex); + + if (items[0].format == format) { + buffer = items[0].buffer; + OpenHandleToObject(buffer, KERNEL_OBJECT_CONSTANT_BUFFER); + } + + KMutexRelease(&mutex); + + if (buffer) { + *_bytes = buffer->bytes; + return process->handleTable.OpenHandle(buffer, 0, KERNEL_OBJECT_CONSTANT_BUFFER); + } else { + return ES_INVALID_HANDLE; + } +} + +#endif diff --git a/kernel/x86_64.cpp b/kernel/x86_64.cpp new file mode 100644 index 0000000..20863f3 --- /dev/null +++ b/kernel/x86_64.cpp @@ -0,0 +1,1166 @@ +#ifndef IMPLEMENTATION + +typedef struct ACPIProcessor ArchCPU; + +// Interrupt vectors: +// 0x00 - 0x1F: CPU exceptions +// 0x20 - 0x2F: PIC (disabled, spurious) +// 0x30 - 0x4F: Timers and low-priority IPIs. +// 0x50 - 0x6F: APIC (standard) +// 0x70 - 0xAF: MSI +// 0xF0 - 0xFE: High-priority IPIs +// 0xFF: APIC (spurious interrupt) + +#define TIMER_INTERRUPT (0x40) +#define YIELD_IPI (0x41) +// Note: IRQ_BASE is currently 0x50. +#define CALL_FUNCTION_ON_ALL_PROCESSORS_IPI (0xF0) +#define KERNEL_PANIC_IPI (0) // NMIs ignore the interrupt vector. + +#define INTERRUPT_VECTOR_MSI_START (0x70) +#define INTERRUPT_VECTOR_MSI_COUNT (0x40) + +struct InterruptContext { + uint64_t cr2, ds; + uint8_t fxsave[512 + 16]; + uint64_t _check, cr8; + uint64_t r15, r14, r13, r12, r11, r10, r9, r8; + uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax; + uint64_t interruptNumber, errorCode; + uint64_t rip, cs, flags, rsp, ss; +}; + +struct VirtualAddressSpaceData { + uintptr_t cr3; + + // Each process has a 47-bit address space. + // That's 2^35 pages. + // That's 2^26 L1 page tables. 2^23 bytes of bitset. + // That's 2^17 L2 page tables. 2^14 bytes of bitset. + // That's 2^ 8 L3 page tables. 2^ 5 bytes of bitset. + // Tracking of the committed L1 tables is done in l1Commit, a region of coreMMSpace. + // (This array is committed as needed, tracked using l1CommitCommit.) + // Tracking of the committed L2 tables is done in l2Commit. + // Tracking of the committed L3 tables is done in l3Commit. +#define L1_COMMIT_SIZE_BYTES (1 << 23) +#define L1_COMMIT_COMMIT_SIZE_BYTES (1 << 8) +#define L2_COMMIT_SIZE_BYTES (1 << 14) +#define L3_COMMIT_SIZE_BYTES (1 << 5) + uint8_t *l1Commit; + uint8_t l1CommitCommit[L1_COMMIT_COMMIT_SIZE_BYTES]; + uint8_t l2Commit[L2_COMMIT_SIZE_BYTES]; + uint8_t l3Commit[L3_COMMIT_SIZE_BYTES]; + size_t pageTablesCommitted; + + // TODO Consider core/kernel mutex consistency? I think it's fine, but... + KMutex mutex; // Acquire to modify the page tables. +}; + +#define VIRTUAL_ADDRESS_SPACE_DATA() VirtualAddressSpaceData data +#define VIRTUAL_ADDRESS_SPACE_IDENTIFIER(x) ((x)->data.cr3) + +#define MM_CORE_SPACE_START (0xFFFF800100000000) +#define MM_CORE_SPACE_SIZE (0xFFFF8001F0000000 - 0xFFFF800100000000) +#define MM_CORE_REGIONS_START (0xFFFF8001F0000000) +#define MM_CORE_REGIONS_COUNT ((0xFFFF800200000000 - 0xFFFF8001F0000000) / sizeof(MMRegion)) +#define MM_KERNEL_SPACE_START (0xFFFF900000000000) +#define MM_KERNEL_SPACE_SIZE (0xFFFFF00000000000 - 0xFFFF900000000000) +#define MM_MODULES_START (0xFFFFFFFF90000000) +#define MM_MODULES_SIZE (0xFFFFFFFFC0000000 - 0xFFFFFFFF90000000) +#define MM_USER_SPACE_START (0x100000000000) +#define MM_USER_SPACE_SIZE (0xF00000000000 - 0x100000000000) + +#define ArchIsAddressInKernelSpace(x) ((uintptr_t) (x) >= 0xFFFF800000000000) + +uint8_t coreL1Commit[(0xFFFF800200000000 - 0xFFFF800100000000) >> (/* ENTRIES_PER_PAGE_TABLE_BITS */ 9 + K_PAGE_BITS + 3)]; + +#endif + +#ifdef IMPLEMENTATION + +extern uintptr_t bootloaderInformationOffset; +extern "C" bool simdSSE3Support; +extern "C" bool simdSSSE3Support; + +volatile uintptr_t tlbShootdownVirtualAddress; +volatile size_t tlbShootdownPageCount; + +extern "C" uintptr_t _KThreadTerminate; + +typedef void (*CallFunctionOnAllProcessorsCallbackFunction)(); +volatile CallFunctionOnAllProcessorsCallbackFunction callFunctionOnAllProcessorsCallback; +volatile uintptr_t callFunctionOnAllProcessorsRemaining; + +// Recursive page table mapping in slot 0x1FE, so that the top 2GB are available for mcmodel kernel. +#define PAGE_TABLE_L4 ((volatile uint64_t *) 0xFFFFFF7FBFDFE000) +#define PAGE_TABLE_L3 ((volatile uint64_t *) 0xFFFFFF7FBFC00000) +#define PAGE_TABLE_L2 ((volatile uint64_t *) 0xFFFFFF7F80000000) +#define PAGE_TABLE_L1 ((volatile uint64_t *) 0xFFFFFF0000000000) +#define ENTRIES_PER_PAGE_TABLE (512) +#define ENTRIES_PER_PAGE_TABLE_BITS (9) + +bool MMArchCommitPageTables(MMSpace *space, MMRegion *region) { + KMutexAssertLocked(&space->reserveMutex); + + VirtualAddressSpaceData *data = &space->data; + + uintptr_t base = (region->baseAddress - (space == coreMMSpace ? MM_CORE_SPACE_START : 0)) & 0x7FFFFFFFF000; + uintptr_t end = base + (region->pageCount << K_PAGE_BITS); + uintptr_t needed = 0; + + for (uintptr_t i = base; i < end; i += 1L << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3)) { + uintptr_t indexL4 = i >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3); + if (!(data->l3Commit[indexL4 >> 3] & (1 << (indexL4 & 7)))) needed++; + i = indexL4 << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3); + } + + for (uintptr_t i = base; i < end; i += 1L << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2)) { + uintptr_t indexL3 = i >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2); + if (!(data->l2Commit[indexL3 >> 3] & (1 << (indexL3 & 7)))) needed++; + i = indexL3 << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2); + } + + uintptr_t previousIndexL2I = -1; + + for (uintptr_t i = base; i < end; i += 1L << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1)) { + uintptr_t indexL2 = i >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1); + uintptr_t indexL2I = indexL2 >> 15; + if (!(data->l1CommitCommit[indexL2I >> 3] & (1 << (indexL2I & 7)))) needed += previousIndexL2I != indexL2I ? 2 : 1; + else if (!(data->l1Commit[indexL2 >> 3] & (1 << (indexL2 & 7)))) needed++; + previousIndexL2I = indexL2I; + i = indexL2 << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1); + } + + if (needed) { + if (!MMCommit(needed * K_PAGE_SIZE, true)) { + return false; + } + + data->pageTablesCommitted += needed; + } + + for (uintptr_t i = base; i < end; i += 1L << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3)) { + uintptr_t indexL4 = i >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3); + data->l3Commit[indexL4 >> 3] |= (1 << (indexL4 & 7)); + i = indexL4 << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3); + } + + for (uintptr_t i = base; i < end; i += 1L << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2)) { + uintptr_t indexL3 = i >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2); + data->l2Commit[indexL3 >> 3] |= (1 << (indexL3 & 7)); + i = indexL3 << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2); + } + + for (uintptr_t i = base; i < end; i += 1L << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1)) { + uintptr_t indexL2 = i >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1); + uintptr_t indexL2I = indexL2 >> 15; + data->l1CommitCommit[indexL2I >> 3] |= (1 << (indexL2I & 7)); + data->l1Commit[indexL2 >> 3] |= (1 << (indexL2 & 7)); + i = indexL2 << (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1); + } + + return true; +} + +bool MMArchMakePageWritable(MMSpace *space, uintptr_t virtualAddress) { + KMutexAcquire(&space->data.mutex); + EsDefer(KMutexRelease(&space->data.mutex)); + + virtualAddress &= 0x0000FFFFFFFFF000; + + uintptr_t indexL4 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3); + uintptr_t indexL3 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2); + uintptr_t indexL2 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1); + uintptr_t indexL1 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 0); + + if ((PAGE_TABLE_L4[indexL4] & 1) == 0) return false; + if ((PAGE_TABLE_L3[indexL3] & 1) == 0) return false; + if ((PAGE_TABLE_L2[indexL2] & 1) == 0) return false; + if ((PAGE_TABLE_L1[indexL1] & 1) == 0) return false; + + PAGE_TABLE_L1[indexL1] |= 2; + return true; +} + +void MMArchMapPage(MMSpace *space, uintptr_t physicalAddress, uintptr_t virtualAddress, unsigned flags) { + // TODO Use the no-execute bit. + + if (physicalAddress & (K_PAGE_SIZE - 1)) { + KernelPanic("MMArchMapPage - Physical address not page aligned.\n"); + } + + bool acquireFrameLock = !(flags & (MM_MAP_PAGE_NO_NEW_TABLES | MM_MAP_PAGE_FRAME_LOCK_ACQUIRED)); + if (acquireFrameLock) KMutexAcquire(&pmm.pageFrameMutex); + EsDefer(if (acquireFrameLock) KMutexRelease(&pmm.pageFrameMutex);); + + bool acquireSpaceLock = ~flags & MM_MAP_PAGE_NO_NEW_TABLES; + if (acquireSpaceLock) KMutexAcquire(&space->data.mutex); + EsDefer(if (acquireSpaceLock) KMutexRelease(&space->data.mutex)); + + uintptr_t cr3 = space->data.cr3; + + if (!ArchIsAddressInKernelSpace(virtualAddress) + && ProcessorReadCR3() != cr3) { + KernelPanic("MMArchMapPage - Attempt to map page into other address space.\n"); + } else if (!physicalAddress) { + KernelPanic("MMArchMapPage - Attempt to map physical page 0.\n"); + } else if (!virtualAddress) { + KernelPanic("MMArchMapPage - Attempt to map virtual page 0.\n"); + } + + // EsPrint("\tMap, %x -> %x\n", virtualAddress, physicalAddress); + + uintptr_t oldVirtualAddress = virtualAddress; + physicalAddress &= 0xFFFFFFFFFFFFF000; + virtualAddress &= 0x0000FFFFFFFFF000; + + uintptr_t indexL4 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3); + uintptr_t indexL3 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2); + uintptr_t indexL2 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1); + uintptr_t indexL1 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 0); + + if (space != coreMMSpace && space != kernelMMSpace /* Don't check the kernel's space since the bootloader's tables won't be committed. */) { + if (!(space->data.l3Commit[indexL4 >> 3] & (1 << (indexL4 & 7)))) KernelPanic("MMArchMapPage - Attempt to map using uncommitted L3 page table.\n"); + if (!(space->data.l2Commit[indexL3 >> 3] & (1 << (indexL3 & 7)))) KernelPanic("MMArchMapPage - Attempt to map using uncommitted L2 page table.\n"); + if (!(space->data.l1Commit[indexL2 >> 3] & (1 << (indexL2 & 7)))) KernelPanic("MMArchMapPage - Attempt to map using uncommitted L1 page table.\n"); + } + + if ((PAGE_TABLE_L4[indexL4] & 1) == 0) { + if (flags & MM_MAP_PAGE_NO_NEW_TABLES) KernelPanic("MMArchMapPage - NO_NEW_TABLES flag set, but a table was missing.\n"); + PAGE_TABLE_L4[indexL4] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_LOCK_ACQUIRED) | 7; + ProcessorInvalidatePage((uintptr_t) (PAGE_TABLE_L3 + indexL3)); // Not strictly necessary. + EsMemoryZero((void *) ((uintptr_t) (PAGE_TABLE_L3 + indexL3) & ~(K_PAGE_SIZE - 1)), K_PAGE_SIZE); + } + + if ((PAGE_TABLE_L3[indexL3] & 1) == 0) { + if (flags & MM_MAP_PAGE_NO_NEW_TABLES) KernelPanic("MMArchMapPage - NO_NEW_TABLES flag set, but a table was missing.\n"); + PAGE_TABLE_L3[indexL3] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_LOCK_ACQUIRED) | 7; + ProcessorInvalidatePage((uintptr_t) (PAGE_TABLE_L2 + indexL2)); // Not strictly necessary. + EsMemoryZero((void *) ((uintptr_t) (PAGE_TABLE_L2 + indexL2) & ~(K_PAGE_SIZE - 1)), K_PAGE_SIZE); + } + + if ((PAGE_TABLE_L2[indexL2] & 1) == 0) { + if (flags & MM_MAP_PAGE_NO_NEW_TABLES) KernelPanic("MMArchMapPage - NO_NEW_TABLES flag set, but a table was missing.\n"); + PAGE_TABLE_L2[indexL2] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_LOCK_ACQUIRED) | 7; + ProcessorInvalidatePage((uintptr_t) (PAGE_TABLE_L1 + indexL1)); // Not strictly necessary. + EsMemoryZero((void *) ((uintptr_t) (PAGE_TABLE_L1 + indexL1) & ~(K_PAGE_SIZE - 1)), K_PAGE_SIZE); + } + + uintptr_t oldValue = PAGE_TABLE_L1[indexL1]; + uintptr_t value = physicalAddress | 3; + + if (flags & MM_MAP_PAGE_WRITE_COMBINING) value |= 16; // This only works because we modified the PAT in SetupProcessor1. + if (flags & MM_MAP_PAGE_NOT_CACHEABLE) value |= 24; + if (flags & MM_MAP_PAGE_USER) value |= 7; else value |= 0x100; + if (flags & MM_MAP_PAGE_READ_ONLY) value &= ~2; + if (flags & MM_MAP_PAGE_COPIED) value |= 1 << 9; + + if ((oldValue & 1) && !(flags & MM_MAP_PAGE_OVERWRITE)) { + if (flags & MM_MAP_PAGE_IGNORE_IF_MAPPED) { + return; + } + + if ((oldValue & ~(K_PAGE_SIZE - 1)) != physicalAddress) { + KernelPanic("MMArchMapPage - Attempt to map %x to %x that has already been mapped to %x.\n", + virtualAddress, physicalAddress, oldValue & (~(K_PAGE_SIZE - 1))); + } + + if (oldValue == value) { + KernelPanic("MMArchMapPage - Attempt to rewrite page translation.\n", + physicalAddress, virtualAddress, oldValue & (K_PAGE_SIZE - 1), value & (K_PAGE_SIZE - 1)); + } else if (!(oldValue & 2) && (value & 2)) { + // The page has become writable. + } else { + KernelPanic("MMArchMapPage - Attempt to change flags mapping %x address %x from %x to %x.\n", + physicalAddress, virtualAddress, oldValue & (K_PAGE_SIZE - 1), value & (K_PAGE_SIZE - 1)); + } + } + + PAGE_TABLE_L1[indexL1] = value; + + // We rely on this page being invalidated on this CPU in some places. + ProcessorInvalidatePage(oldVirtualAddress); +} + +bool MMArchHandlePageFault(uintptr_t address, uint32_t flags) { + // EsPrint("Fault %x\n", address); + address &= ~(K_PAGE_SIZE - 1); + bool forSupervisor = flags & MM_HANDLE_PAGE_FAULT_FOR_SUPERVISOR; + + if (!ProcessorAreInterruptsEnabled()) { + KernelPanic("MMArchHandlePageFault - Page fault with interrupts disabled.\n"); + } + + if (address < K_PAGE_SIZE) { + } else if (address >= LOW_MEMORY_MAP_START && address < LOW_MEMORY_MAP_START + 0x100000000 && forSupervisor) { + // We want to access a physical page within the first 4GB. + // This is used for device IO, so the page can't be cacheable. + MMArchMapPage(kernelMMSpace, address - LOW_MEMORY_MAP_START, address, MM_MAP_PAGE_NOT_CACHEABLE | MM_MAP_PAGE_COMMIT_TABLES_NOW); + return true; + } else if (address >= MM_CORE_REGIONS_START && address < MM_CORE_REGIONS_START + MM_CORE_REGIONS_COUNT * sizeof(MMRegion) && forSupervisor) { + // This is where coreMMSpace stores its regions. + // Allocate physical memory and map it. + MMArchMapPage(kernelMMSpace, MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_ZEROED), address, MM_MAP_PAGE_COMMIT_TABLES_NOW); + return true; + } else if (address >= MM_CORE_SPACE_START && address < MM_CORE_SPACE_START + MM_CORE_SPACE_SIZE && forSupervisor) { + return MMHandlePageFault(coreMMSpace, address, flags); + } else if (address >= MM_KERNEL_SPACE_START && address < MM_KERNEL_SPACE_START + MM_KERNEL_SPACE_SIZE && forSupervisor) { + return MMHandlePageFault(kernelMMSpace, address, flags); + } else if (address >= MM_MODULES_START && address < MM_MODULES_START + MM_MODULES_SIZE && forSupervisor) { + return MMHandlePageFault(kernelMMSpace, address, flags); + } else { + Thread *thread = GetCurrentThread(); + MMSpace *space = thread->temporaryAddressSpace; + if (!space) space = thread->process->vmm; + return MMHandlePageFault(space, address, flags); + } + + return false; +} + +void MMArchInitialiseVAS() { + coreMMSpace->data.cr3 = kernelMMSpace->data.cr3 = ProcessorReadCR3(); + coreMMSpace->data.l1Commit = coreL1Commit; + + for (uintptr_t i = 0x100; i < 0x200; i++) { + if (PAGE_TABLE_L4[i] == 0) { + // We don't need to commit anything because the PMM isn't ready yet. + PAGE_TABLE_L4[i] = MMPhysicalAllocate(ES_FLAGS_DEFAULT) | 3; + EsMemoryZero((void *) (PAGE_TABLE_L3 + i * 0x200), K_PAGE_SIZE); + } + } +} + +uintptr_t MMArchTranslateAddress(MMSpace *, uintptr_t virtualAddress, bool writeAccess) { + // TODO I don't think this mutex was ever necessary? + // space->data.mutex.Acquire(); + // EsDefer(space->data.mutex.Release()); + + virtualAddress &= 0x0000FFFFFFFFF000; + if ((PAGE_TABLE_L4[virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3)] & 1) == 0) return 0; + if ((PAGE_TABLE_L3[virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2)] & 1) == 0) return 0; + if ((PAGE_TABLE_L2[virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1)] & 1) == 0) return 0; + uintptr_t physicalAddress = PAGE_TABLE_L1[virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 0)]; + if (writeAccess && !(physicalAddress & 2)) return 0; + return (physicalAddress & 1) ? (physicalAddress & 0x0000FFFFFFFFF000) : 0; +} + +void ArchCallFunctionOnAllProcessors(CallFunctionOnAllProcessorsCallbackFunction callback, bool includingThisProcessor) { + KSpinlockAssertLocked(&ipiLock); + + if (KGetCPUCount() > 1) { + callFunctionOnAllProcessorsCallback = callback; + callFunctionOnAllProcessorsRemaining = KGetCPUCount(); + size_t ignored = ProcessorSendIPI(CALL_FUNCTION_ON_ALL_PROCESSORS_IPI); + __sync_fetch_and_sub(&callFunctionOnAllProcessorsRemaining, ignored); + while (callFunctionOnAllProcessorsRemaining); + static volatile size_t totalIgnored = 0; + totalIgnored += ignored; + } + + if (includingThisProcessor) callback(); +} + +// TODO How should this be determined? +#define INVALIDATE_ALL_PAGES_THRESHOLD (1024) + +void TLBShootdownCallback() { + uintptr_t page = tlbShootdownVirtualAddress; + + if (tlbShootdownPageCount > INVALIDATE_ALL_PAGES_THRESHOLD) { + ProcessorInvalidateAllPages(); + } else { + for (uintptr_t i = 0; i < tlbShootdownPageCount; i++, page += K_PAGE_SIZE) { + ProcessorInvalidatePage(page); + } + } +} + +void MMArchInvalidatePages(uintptr_t virtualAddressStart, uintptr_t pageCount) { + // This must be done with spinlock acquired, otherwise this processor could change. + + // TODO Only send the IPI to the processors that are actually executing threads with the virtual address space. + // Currently we only support the kernel's virtual address space, so this'll apply to all processors. + // If we use Intel's PCID then we may have to send this to all processors anyway. + // And we'll probably also have to be careful with shared memory regions. + // ...actually I think we might not bother doing this. + + KSpinlockAcquire(&ipiLock); + tlbShootdownVirtualAddress = virtualAddressStart; + tlbShootdownPageCount = pageCount; + ArchCallFunctionOnAllProcessors(TLBShootdownCallback, true); + KSpinlockRelease(&ipiLock); +} + +void MMArchUnmapPages(MMSpace *space, uintptr_t virtualAddressStart, uintptr_t pageCount, unsigned flags, size_t unmapMaximum, uintptr_t *resumePosition) { + // We can't let anyone use the unmapped pages until they've been invalidated on all processors. + // This also synchronises modified bit updating. + KMutexAcquire(&pmm.pageFrameMutex); + EsDefer(KMutexRelease(&pmm.pageFrameMutex)); + + KMutexAcquire(&space->data.mutex); + EsDefer(KMutexRelease(&space->data.mutex)); + + uintptr_t tableBase = virtualAddressStart & 0x0000FFFFFFFFF000; + uintptr_t start = resumePosition ? *resumePosition : 0; + + // TODO Freeing newly empty page tables. + // - What do we need to invalidate when we do this? + + for (uintptr_t i = start; i < pageCount; i++) { + // if (flags & MM_UNMAP_PAGES_BALANCE_FILE) EsPrint(",%d", i); + + uintptr_t virtualAddress = (i << K_PAGE_BITS) + tableBase; + + if ((PAGE_TABLE_L4[virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3)] & 1) == 0) { + i -= (virtualAddress >> K_PAGE_BITS) % (1 << (ENTRIES_PER_PAGE_TABLE_BITS * 3)); + i += (1 << (ENTRIES_PER_PAGE_TABLE_BITS * 3)); + continue; + } + + if ((PAGE_TABLE_L3[virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2)] & 1) == 0) { + i -= (virtualAddress >> K_PAGE_BITS) % (1 << (ENTRIES_PER_PAGE_TABLE_BITS * 2)); + i += (1 << (ENTRIES_PER_PAGE_TABLE_BITS * 2)); + continue; + } + + if ((PAGE_TABLE_L2[virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1)] & 1) == 0) { + i -= (virtualAddress >> K_PAGE_BITS) % (1 << (ENTRIES_PER_PAGE_TABLE_BITS * 1)); + i += (1 << (ENTRIES_PER_PAGE_TABLE_BITS * 1)); + continue; + } + + uintptr_t indexL1 = virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 0); + + uintptr_t translation = PAGE_TABLE_L1[indexL1]; + if (!(translation & 1)) continue; + bool copy = translation & (1 << 9); + + if (copy && (flags & MM_UNMAP_PAGES_BALANCE_FILE)) { + // Ignore copied pages when balancing file mappings. + // EsPrint("Ignore copied page %x\n", virtualAddress); + } else { + PAGE_TABLE_L1[indexL1] = 0; + + // NOTE MMArchInvalidatePages invalidates the page on all processors now, + // which I think makes this unnecessary? + // uint64_t invalidateAddress = (i << K_PAGE_BITS) + virtualAddressStart; + // ProcessorInvalidatePage(invalidateAddress); + + if ((flags & MM_UNMAP_PAGES_FREE) || ((flags & MM_UNMAP_PAGES_FREE_COPIED) && copy)) { + MMPhysicalFree(translation & 0x0000FFFFFFFFF000, true); + } else if (flags & MM_UNMAP_PAGES_BALANCE_FILE) { + // EsPrint("Balance %x\n", virtualAddress); + + // It's safe to do this before invalidation, + // because the page fault handler is synchronisation with mutexes acquired above. + + if (MMUnmapFilePage((translation & 0x0000FFFFFFFFF000) >> K_PAGE_BITS)) { + if (resumePosition) { + if (!unmapMaximum--) { + *resumePosition = i; + break; + } + } + } + } + } + } + + MMArchInvalidatePages(virtualAddressStart, pageCount); +} + +InterruptContext *ArchInitialiseThread(uintptr_t kernelStack, uintptr_t kernelStackSize, Thread *thread, + uintptr_t startAddress, uintptr_t argument1, uintptr_t argument2, + bool userland, uintptr_t stack, uintptr_t userStackSize) { + InterruptContext *context = ((InterruptContext *) (kernelStack + kernelStackSize - 8)) - 1; + thread->kernelStack = kernelStack + kernelStackSize - 8; + + // Terminate the thread when the outermost function exists. + *((uintptr_t *) (kernelStack + kernelStackSize - 8)) = (uintptr_t) &_KThreadTerminate; + + context->fxsave[32] = 0x80; + context->fxsave[33] = 0x1F; + + if (userland) { + context->cs = 0x5B; + context->ds = 0x63; + context->ss = 0x63; + } else { + context->cs = 0x48; + context->ds = 0x50; + context->ss = 0x50; + } + + context->_check = 0x123456789ABCDEF; // Stack corruption detection. + context->flags = 1 << 9; // Interrupt flag + context->rip = startAddress; + context->rsp = stack + userStackSize - 8; // The stack should be 16-byte aligned before the call instruction. + context->rdi = argument1; + context->rsi = argument2; + + return context; +} + +bool MMArchInitialiseUserSpace(MMSpace *space) { + if (!MMCommit(K_PAGE_SIZE, true)) { + return false; + } + + space->data.cr3 = MMPhysicalAllocate(ES_FLAGS_DEFAULT); + + KMutexAcquire(&coreMMSpace->reserveMutex); + MMRegion *l1Region = MMReserve(coreMMSpace, L1_COMMIT_SIZE_BYTES, MM_REGION_NORMAL | MM_REGION_NO_COMMIT_TRACKING | MM_REGION_FIXED); + if (l1Region) space->data.l1Commit = (uint8_t *) l1Region->baseAddress; + KMutexRelease(&coreMMSpace->reserveMutex); + + if (!space->data.l1Commit) { + return false; + } + + uint64_t *pageTable = (uint64_t *) MMMapPhysical(kernelMMSpace, (uintptr_t) space->data.cr3, K_PAGE_SIZE, ES_FLAGS_DEFAULT); + EsMemoryZero(pageTable + 0x000, K_PAGE_SIZE / 2); + EsMemoryCopy(pageTable + 0x100, (uint64_t *) (PAGE_TABLE_L4 + 0x100), K_PAGE_SIZE / 2); + pageTable[512 - 2] = space->data.cr3 | 3; + MMFree(kernelMMSpace, pageTable); + + return true; +} + +void ArchCleanupVirtualAddressSpace(void *argument) { + KernelLog(LOG_VERBOSE, "Arch", "remove virtual address space page", "Removing virtual address space page %x...\n", argument); + MMPhysicalFree((uintptr_t) argument); + MMDecommit(K_PAGE_SIZE, true); +} + +void MMFreeVAS(MMSpace *space) { + for (uintptr_t i = 0; i < 256; i++) { + if (!PAGE_TABLE_L4[i]) continue; + + for (uintptr_t j = i * 512; j < (i + 1) * 512; j++) { + if (!PAGE_TABLE_L3[j]) continue; + + for (uintptr_t k = j * 512; k < (j + 1) * 512; k++) { + if (!PAGE_TABLE_L2[k]) continue; + MMPhysicalFree(PAGE_TABLE_L2[k] & (~0xFFF)); + } + + MMPhysicalFree(PAGE_TABLE_L3[j] & (~0xFFF)); + } + + MMPhysicalFree(PAGE_TABLE_L4[i] & (~0xFFF)); + } + + KMutexAcquire(&coreMMSpace->reserveMutex); + MMUnreserve(coreMMSpace, MMFindRegion(coreMMSpace, (uintptr_t) space->data.l1Commit), true); + KMutexRelease(&coreMMSpace->reserveMutex); + MMDecommit(space->data.pageTablesCommitted * K_PAGE_SIZE, true); +} + +void MMFinalizeVAS(MMSpace *space) { + // Freeing the L4 page table has to be done in the kernel process, since it's the page CR3 currently points to!! + // This function is called in an async task. + MMPhysicalFree(space->data.cr3); + MMDecommit(K_PAGE_SIZE, true); +} + +void ArchCheckAddressInRange(int type, uintptr_t address) { + if (type == 1) { + if ((uintptr_t) address < 0xFFFF900000000000) { + KernelPanic("ArchCheckAddressInRange - Address out of expected range.\n"); + } + } else if (type == 2) { + if ((uintptr_t) address < 0xFFFF8F8000000000 || (uintptr_t) address >= 0xFFFF900000000000) { + KernelPanic("ArchCheckAddressInRange - Address out of expected range.\n"); + } + } else { + if ((uintptr_t) address >= 0xFFFF800000000000) { + KernelPanic("ArchCheckAddressInRange - Address out of expected range.\n"); + } + } +} + +void ArchDelay1Ms() { + ProcessorOut8(0x43, 0x30); + ProcessorOut8(0x40, 0xA9); + ProcessorOut8(0x40, 0x04); + + while (true) { + ProcessorOut8(0x43, 0xE2); + + if (ProcessorIn8(0x40) & (1 << 7)) { + break; + } + } +} + +struct MSIHandler { + KIRQHandler callback; + void *context; +}; + +MSIHandler msiHandlers[INTERRUPT_VECTOR_MSI_COUNT]; + +void KUnregisterMSI(uintptr_t tag) { + KSpinlockAcquire(&scheduler.lock); + EsDefer(KSpinlockRelease(&scheduler.lock)); + msiHandlers[tag].callback = nullptr; +} + +KMSIInformation KRegisterMSI(KIRQHandler handler, void *context, const char *cOwnerName) { + KSpinlockAcquire(&scheduler.lock); + EsDefer(KSpinlockRelease(&scheduler.lock)); + + for (uintptr_t i = 0; i < INTERRUPT_VECTOR_MSI_COUNT; i++) { + if (msiHandlers[i].callback) continue; + msiHandlers[i] = { handler, context }; + + // TODO Selecting the best target processor. + // Currently this sends everything to processor 0. + + KernelLog(LOG_INFO, "Arch", "register MSI", "Register MSI with vector %X for '%z'.\n", + INTERRUPT_VECTOR_MSI_START + i, cOwnerName); + + return { + .address = 0xFEE00000, + .data = INTERRUPT_VECTOR_MSI_START + i, + .tag = i, + }; + } + + return {}; +} + +#define IRQ_BASE 0x50 +KIRQHandler irqHandlers[0x20][0x10]; +void *irqHandlerContext[0x20][0x10]; +size_t usedIrqHandlers[0x20]; + +bool KRegisterIRQ(uintptr_t interrupt, KIRQHandler handler, void *context, const char *cOwnerName) { + KSpinlockAcquire(&scheduler.lock); + EsDefer(KSpinlockRelease(&scheduler.lock)); + + // Work out which interrupt the IoApic will sent to the processor. + // TODO Use the upper 4 bits for IRQ priority. + uintptr_t thisProcessorIRQ = interrupt + IRQ_BASE; + + // Register the IRQ handler. + if (interrupt > 0x20) KernelPanic("KRegisterIRQ - Unexpected IRQ %d\n", interrupt); + if (usedIrqHandlers[interrupt] == 0x10) { + // There are too many overloaded interrupts. + return false; + } + irqHandlers[interrupt][usedIrqHandlers[interrupt]] = handler; + irqHandlerContext[interrupt][usedIrqHandlers[interrupt]] = context; + + KernelLog(LOG_INFO, "Arch", "register IRQ", "KRegisterIRQ - Registering IRQ %d to '%z'.\n", + interrupt, cOwnerName); + + if (usedIrqHandlers[interrupt]) { + // IRQ already registered. + usedIrqHandlers[interrupt]++; + return true; + } + + usedIrqHandlers[interrupt]++; + + bool activeLow = false; + bool levelTriggered = true; + + // If there was an interrupt override entry in the MADT table, + // then we'll have to use that number instead. + for (uintptr_t i = 0; i < acpi.interruptOverrideCount; i++) { + ACPIInterruptOverride *interruptOverride = acpi.interruptOverrides + i; + if (interruptOverride->sourceIRQ == interrupt) { + interrupt = interruptOverride->gsiNumber; + activeLow = interruptOverride->activeLow; + levelTriggered = interruptOverride->levelTriggered; + break; + } + } + + KernelLog(LOG_INFO, "Arch", "IRQ flags", "KRegisterIRQ - IRQ %d is active %z, %z triggered.\n", + interrupt, activeLow ? "low" : "high", levelTriggered ? "level" : "edge"); + + ACPIIoApic *ioApic; + bool foundIoApic = false; + + // Look for the IoApic to which this interrupt is sent. + for (uintptr_t i = 0; i < acpi.ioapicCount; i++) { + ioApic = acpi.ioApics + i; + if (interrupt >= ioApic->gsiBase + && interrupt < (ioApic->gsiBase + (0xFF & (ioApic->ReadRegister(1) >> 16)))) { + foundIoApic = true; + interrupt -= ioApic->gsiBase; + break; + } + } + + // We couldn't find the IoApic that handles this interrupt. + if (!foundIoApic) { + return false; + } + + // A normal priority interrupt. + uintptr_t redirectionTableIndex = interrupt * 2 + 0x10; + uint32_t redirectionEntry = thisProcessorIRQ; + if (activeLow) redirectionEntry |= (1 << 13); + if (levelTriggered) redirectionEntry |= (1 << 15); + + // Mask the interrupt while we modify the entry. + ioApic->WriteRegister(redirectionTableIndex, 1 << 16); + + // Send the interrupt to the processor that registered the interrupt. + ioApic->WriteRegister(redirectionTableIndex + 1, GetLocalStorage()->archCPU->apicID << 24); + ioApic->WriteRegister(redirectionTableIndex, redirectionEntry); + + return true; +} + +size_t ProcessorSendIPI(uintptr_t interrupt, bool nmi, int processorID) { + // It's possible that another CPU is trying to send an IPI at the same time we want to send the panic IPI. + // TODO What should we do in this case? + if (interrupt != KERNEL_PANIC_IPI) KSpinlockAssertLocked(&ipiLock); + + // Note: We send IPIs at a special priority that ProcessorDisableInterrupts doesn't mask. + + size_t ignored = 0; + + for (uintptr_t i = 0; i < acpi.processorCount; i++) { + ACPIProcessor *processor = acpi.processors + i; + + if (processorID != -1) { + if (processorID != processor->kernelProcessorID) { + ignored++; + continue; + } + } else { + if (processor == GetLocalStorage()->archCPU || !processor->local || !processor->local->schedulerReady) { + ignored++; + continue; + } + } + + uint32_t destination = acpi.processors[i].apicID << 24; + uint32_t command = interrupt | (1 << 14) | (nmi ? 0x400 : 0); + acpi.lapic.WriteRegister(0x310 >> 2, destination); + acpi.lapic.WriteRegister(0x300 >> 2, command); + + // Wait for the interrupt to be sent. + while (acpi.lapic.ReadRegister(0x300 >> 2) & (1 << 12)); + } + + return ignored; +} + +void ArchNextTimer(size_t ms) { + while (!scheduler.started); // Wait until the scheduler is ready. + GetLocalStorage()->schedulerReady = true; // Make sure this CPU can be scheduled. + acpi.lapic.ArchNextTimer(ms); // Set the next timer. +} + +NewProcessorStorage AllocateNewProcessorStorage(ACPIProcessor *archCPU) { + NewProcessorStorage storage = {}; + storage.local = (CPULocalStorage *) EsHeapAllocate(sizeof(CPULocalStorage), true, K_FIXED); + storage.gdt = (uint32_t *) MMMapPhysical(kernelMMSpace, MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_COMMIT_NOW), K_PAGE_SIZE, ES_FLAGS_DEFAULT); + storage.local->archCPU = archCPU; + archCPU->local = storage.local; + scheduler.CreateProcessorThreads(storage.local); + archCPU->kernelProcessorID = storage.local->processorID; + return storage; +} + +extern "C" void SetupProcessor2(NewProcessorStorage *storage) { + // Setup the local interrupts for the current processor. + + for (uintptr_t i = 0; i < acpi.lapicNMICount; i++) { + if (acpi.lapicNMIs[i].processor == 0xFF + || acpi.lapicNMIs[i].processor == storage->local->archCPU->processorID) { + uint32_t registerIndex = (0x350 + (acpi.lapicNMIs[i].lintIndex << 4)) >> 2; + uint32_t value = 2 | (1 << 10); // NMI exception interrupt vector. + if (acpi.lapicNMIs[i].activeLow) value |= 1 << 13; + if (acpi.lapicNMIs[i].levelTriggered) value |= 1 << 15; + acpi.lapic.WriteRegister(registerIndex, value); + } + } + + acpi.lapic.WriteRegister(0x350 >> 2, acpi.lapic.ReadRegister(0x350 >> 2) & ~(1 << 16)); + acpi.lapic.WriteRegister(0x360 >> 2, acpi.lapic.ReadRegister(0x360 >> 2) & ~(1 << 16)); + acpi.lapic.WriteRegister(0x080 >> 2, 0); + if (acpi.lapic.ReadRegister(0x30 >> 2) & 0x80000000) acpi.lapic.WriteRegister(0x410 >> 2, 0); + acpi.lapic.EndOfInterrupt(); + + // Configure the LAPIC's timer. + + acpi.lapic.WriteRegister(0x3E0 >> 2, 2); // Divisor = 16 + + // Create the processor's local storage. + + ProcessorSetLocalStorage(storage->local); + + // Setup a GDT and TSS for the processor. + + uint32_t *gdt = storage->gdt; + void *bootstrapGDT = (void *) (((uint64_t *) ((uint16_t *) processorGDTR + 1))[0]); + EsMemoryCopy(gdt, bootstrapGDT, 2048); + uint32_t *tss = (uint32_t *) ((uint8_t *) storage->gdt + 2048); + storage->local->archCPU->kernelStack = (void **) (tss + 1); + ProcessorInstallTSS(gdt, tss); +} + +const char *exceptionInformation[] = { + "0x00: Divide Error (Fault)", + "0x01: Debug Exception (Fault/Trap)", + "0x02: Non-Maskable External Interrupt (Interrupt)", + "0x03: Breakpoint (Trap)", + "0x04: Overflow (Trap)", + "0x05: BOUND Range Exceeded (Fault)", + "0x06: Invalid Opcode (Fault)", + "0x07: x87 Coprocessor Unavailable (Fault)", + "0x08: Double Fault (Abort)", + "0x09: x87 Coprocessor Segment Overrun (Fault)", + "0x0A: Invalid TSS (Fault)", + "0x0B: Segment Not Present (Fault)", + "0x0C: Stack Protection (Fault)", + "0x0D: General Protection (Fault)", + "0x0E: Page Fault (Fault)", + "0x0F: Reserved/Unknown", + "0x10: x87 FPU Floating-Point Error (Fault)", + "0x11: Alignment Check (Fault)", + "0x12: Machine Check (Abort)", + "0x13: SIMD Floating-Point Exception (Fault)", + "0x14: Virtualization Exception (Fault)", + "0x15: Reserved/Unknown", + "0x16: Reserved/Unknown", + "0x17: Reserved/Unknown", + "0x18: Reserved/Unknown", + "0x19: Reserved/Unknown", + "0x1A: Reserved/Unknown", + "0x1B: Reserved/Unknown", + "0x1C: Reserved/Unknown", + "0x1D: Reserved/Unknown", + "0x1E: Reserved/Unknown", + "0x1F: Reserved/Unknown", +}; + +void ContextSanityCheck(InterruptContext *context) { + if (!context || context->cs > 0x100 || context->ds > 0x100 || context->ss > 0x100 + || (context->rip >= 0x1000000000000 && context->rip < 0xFFFF000000000000) + || (context->rip < 0xFFFF800000000000 && context->cs == 0x48)) { + KernelPanic("ContextSanityCheck - Corrupt context (%x/%x/%x/%x)\nRIP = %x, RSP = %x\n", context, context->cs, context->ds, context->ss, context->rip, context->rsp); + } +} + +extern "C" void InterruptHandler(InterruptContext *context) { + if (scheduler.panic && context->interruptNumber != 2) { + return; + } + + if (ProcessorAreInterruptsEnabled()) { + KernelPanic("InterruptHandler - Interrupts were enabled at the start of an interrupt handler.\n"); + } + + CPULocalStorage *local = GetLocalStorage(); + uintptr_t interrupt = context->interruptNumber; + +#if 0 +#ifdef EARLY_DEBUGGING +#ifdef VGA_TEXT_MODE + if (local) { + TERMINAL_ADDRESS[local->processorID] += 0x1000; + } +#else + if (graphics.target && graphics.target->debugPutBlock) { + graphics.target->debugPutBlock(local->processorID * 3 + 3, 3, true); + graphics.target->debugPutBlock(local->processorID * 3 + 4, 3, true); + graphics.target->debugPutBlock(local->processorID * 3 + 3, 4, true); + graphics.target->debugPutBlock(local->processorID * 3 + 4, 4, true); + } +#endif +#endif +#endif + + if (interrupt < 0x20) { + // If we received a non-maskable interrupt, halt execution. + if (interrupt == 2) { + GetLocalStorage()->panicContext = context; + ProcessorHalt(); + } + + bool supervisor = (context->cs & 3) == 0; + + if (!supervisor) { + // EsPrint("User interrupt: %x/%x/%x\n", interrupt, context->cr2, context->errorCode); + + if (context->cs != 0x5B && context->cs != 0x6B) { + KernelPanic("InterruptHandler - Unexpected value of CS 0x%X\n", context->cs); + } + + if (GetCurrentThread()->isKernelThread) { + KernelPanic("InterruptHandler - Kernel thread executing user code. (1)\n"); + } + + // User-code exceptions are *basically* the same thing as system calls. + Thread *currentThread = GetCurrentThread(); + ThreadTerminatableState previousTerminatableState; + previousTerminatableState = currentThread->terminatableState; + currentThread->terminatableState = THREAD_IN_SYSCALL; + + if (local && local->spinlockCount) { + KernelPanic("InterruptHandler - User exception occurred with spinlock acquired.\n"); + } + + // Re-enable interrupts during exception handling. + ProcessorEnableInterrupts(); + + if (interrupt == 14) { + bool success = MMArchHandlePageFault(context->cr2, (context->errorCode & 2) ? MM_HANDLE_PAGE_FAULT_WRITE : 0); + + if (success) { + goto resolved; + } + } + + if (interrupt == 0x13) { + EsPrint("ProcessorReadMXCSR() = %x\n", ProcessorReadMXCSR()); + } + + // TODO Usermode exceptions and debugging. + KernelLog(LOG_ERROR, "Arch", "unhandled userland exception", + "InterruptHandler - Exception (%z) in userland process (%z).\nRIP = %x (CPU %d)\nRSP = %x\nX86_64 error codes: [err] %x, [cr2] %x\n", + exceptionInformation[interrupt], + currentThread->process->cExecutableName, + context->rip, local->processorID, context->rsp, context->errorCode, context->cr2); + +#ifndef __OPTIMIZE__ + EsPrint("Attempting to make a stack trace...\n"); + + { + // TODO Temporary, may crash kernel. + // Attempt to make a stack trace. + + uint64_t *rbp = (uint64_t *) context->rbp; + int i = 0; + + while (rbp && i < 32) { + EsPrint("\t%d: %x\n", ++i, rbp[1]); + if (!rbp[1]) break; + rbp = (uint64_t *) (rbp[0]); + } + } + + EsPrint("Stack trace complete.\n"); +#else + EsPrint("Disable optimisations to get a stack trace.\n"); +#endif + + EsCrashReason crashReason; + EsMemoryZero(&crashReason, sizeof(EsCrashReason)); + crashReason.errorCode = ES_FATAL_ERROR_PROCESSOR_EXCEPTION; + crashReason.duringSystemCall = (EsSyscallType) -1; + scheduler.CrashProcess(currentThread->process, &crashReason); + + resolved:; + + if (currentThread->terminatableState != THREAD_IN_SYSCALL) { + KernelPanic("InterruptHandler - Thread changed terminatable status during interrupt.\n"); + } + + currentThread->terminatableState = previousTerminatableState; + + if (currentThread->terminating || currentThread->paused) { + ProcessorFakeTimerInterrupt(); + } + + // Disable interrupts when we're done. + ProcessorDisableInterrupts(); + + // EsPrint("User interrupt complete.\n", interrupt, context->cr2); + } else { + if (context->cs != 0x48) { + KernelPanic("InterruptHandler - Unexpected value of CS 0x%X\n", context->cs); + } + + if (interrupt == 14) { + // EsPrint("PF: %x\n", context->cr2); + + if ((context->errorCode & (1 << 3))) { + goto fault; + } + + if ((context->flags & 0x200) && context->cr8 != 0xE) { + ProcessorEnableInterrupts(); + } + + { + CPULocalStorage *storage = GetLocalStorage(); + + if (storage && storage->spinlockCount && ((context->cr2 >= 0xFFFF900000000000 && context->cr2 < 0xFFFFF00000000000) + || context->cr2 < 0x8000000000000000)) { + KernelPanic("HandlePageFault - Page fault occurred in critical section at %x (S = %x, B = %x, LG = %x) (CR2 = %x).\n", + context->rip, context->rsp, context->rbp, storage->currentThread->lastKnownExecutionAddress, context->cr2); + } + } + + + if (!MMArchHandlePageFault(context->cr2, MM_HANDLE_PAGE_FAULT_FOR_SUPERVISOR + | ((context->errorCode & 2) ? MM_HANDLE_PAGE_FAULT_WRITE : 0))) { + goto fault; + } + + ProcessorDisableInterrupts(); + } else { + fault: + KernelPanic("Unresolvable processor exception encountered in supervisor mode.\n%z\nRIP = %x (CPU %d)\nX86_64 error codes: [err] %x, [cr2] %x\n" + "Stack: [rsp] %x, [rbp] %x\nRegisters: [rax] %x, [rbx] %x, [rsi] %x, [rdi] %x.\nThread ID = %d\n", + exceptionInformation[interrupt], context->rip, local ? local->processorID : -1, context->errorCode, context->cr2, + context->rsp, context->rbp, context->rax, context->rbx, context->rsi, context->rdi, + local && local->currentThread ? local->currentThread->id : -1); + } + } + } else if (interrupt == 0xFF) { + // Spurious interrupt (APIC), ignore. + } else if (interrupt >= 0x20 && interrupt < 0x30) { + // Spurious interrupt (PIC), ignore. + } else if (interrupt >= 0xF0 && interrupt < 0xFE) { + // IPI. + // Warning: This code executes at a special IRQL! Do not acquire spinlocks!! + + if (interrupt == CALL_FUNCTION_ON_ALL_PROCESSORS_IPI) { + if (!callFunctionOnAllProcessorsRemaining) KernelPanic("InterruptHandler - callFunctionOnAllProcessorsRemaining is 0 (a).\n"); + callFunctionOnAllProcessorsCallback(); + if (!callFunctionOnAllProcessorsRemaining) KernelPanic("InterruptHandler - callFunctionOnAllProcessorsRemaining is 0 (b).\n"); + __sync_fetch_and_sub(&callFunctionOnAllProcessorsRemaining, 1); + } + + acpi.lapic.EndOfInterrupt(); + } else if (interrupt >= INTERRUPT_VECTOR_MSI_START && interrupt < INTERRUPT_VECTOR_MSI_START + INTERRUPT_VECTOR_MSI_COUNT && local) { + MSIHandler handler = msiHandlers[interrupt - INTERRUPT_VECTOR_MSI_START]; + local->irqSwitchThread = false; + + if (!handler.callback) { + KernelLog(LOG_ERROR, "Arch", "unexpected MSI", "Unexpected MSI vector %X (no handler).\n", interrupt); + } else { + handler.callback(interrupt - INTERRUPT_VECTOR_MSI_START, handler.context); + } + + + acpi.lapic.EndOfInterrupt(); + + if (local->irqSwitchThread && scheduler.started && local->schedulerReady) { + scheduler.Yield(context); + KernelPanic("InterruptHandler - Returned from Scheduler::Yield.\n"); + } + } else if (local) { + // IRQ. + + local->irqSwitchThread = false; + + if (interrupt == TIMER_INTERRUPT) { + local->irqSwitchThread = true; + } else if (interrupt == YIELD_IPI) { + local->irqSwitchThread = true; + GetCurrentThread()->receivedYieldIPI = true; + } else if (interrupt >= IRQ_BASE && interrupt < IRQ_BASE + 0x20) { + GetLocalStorage()->inIRQ = true; + + size_t overloads = usedIrqHandlers[interrupt - IRQ_BASE]; + bool handledInterrupt = false; + + KernelLog(LOG_VERBOSE, "Arch", "IRQ start", "IRQ start %d.\n", interrupt - IRQ_BASE); + + for (uintptr_t i = 0; i < overloads; i++) { + KIRQHandler handler = irqHandlers[interrupt - IRQ_BASE][i]; + + if (handler(interrupt - IRQ_BASE, irqHandlerContext[interrupt - IRQ_BASE][i])) { + handledInterrupt = true; + } + } + + KernelLog(LOG_VERBOSE, "Arch", "IRQ end", "IRQ end %d.\n", interrupt - IRQ_BASE); + + bool rejectedByAll = !handledInterrupt; + + if (rejectedByAll) { + // TODO Now what? + // KernelLog(LOG_ERROR, "Arch", "unhandled IRQ", + // "InterruptHandler - Unhandled IRQ %d, rejected by %d %z\n", + // interrupt, overloads, (overloads != 1) ? "overloads" : "overload"); + } + + GetLocalStorage()->inIRQ = false; + } + + acpi.lapic.EndOfInterrupt(); + + if (local->irqSwitchThread && scheduler.started && local->schedulerReady) { + scheduler.Yield(context); + KernelPanic("InterruptHandler - Returned from Scheduler::Yield.\n"); + } + } + + // Sanity check. + ContextSanityCheck(context); + + if (ProcessorAreInterruptsEnabled()) { + KernelPanic("InterruptHandler - Interrupts were enabled while returning from an interrupt handler.\n"); + } +} + +extern "C" bool PostContextSwitch(InterruptContext *context) { + CPULocalStorage *local = GetLocalStorage(); + Thread *currentThread = GetCurrentThread(); + + void *kernelStack = (void *) currentThread->kernelStack; + *local->archCPU->kernelStack = kernelStack; + + bool newThread = currentThread->cpuTimeSlices == 1; + + KernelLog(LOG_VERBOSE, "Arch", "context switch", "Context switch to thread %x at %x\n", currentThread, context->rip); + + if (newThread) { + ContextSanityCheck(context); + KernelLog(LOG_VERBOSE, "Arch", "executing new thread", "Executing new thread %x at %x\n", currentThread, context->rip); + } + + acpi.lapic.EndOfInterrupt(); + ContextSanityCheck(context); + + if (ProcessorAreInterruptsEnabled()) { + KernelPanic("PostContextSwitch - Interrupts were enabled. (1)\n"); + } + + currentThread->lastKnownExecutionAddress = context->rip; + + if (scheduler.lock.interruptsEnabled) { + KernelPanic("PostContextSwitch - Interrupts were enabled. (3)\n"); + } + + ProcessorSetThreadStorage(currentThread->tlsAddress); + + // We can only free the scheduler's spinlock when we are no longer using the stack + // from the previous thread. See DoContextSwitch in x86_64.s. + KSpinlockRelease(&scheduler.lock, true); + + if (ProcessorAreInterruptsEnabled()) { + KernelPanic("PostContextSwitch - Interrupts were enabled. (2)\n"); + } + + return newThread; +} + +extern "C" uintptr_t Syscall(uintptr_t argument0, uintptr_t argument1, uintptr_t argument2, + uintptr_t returnAddress, uintptr_t argument3, uintptr_t argument4, uintptr_t *userStackPointer) { + (void) returnAddress; + return DoSyscall((EsSyscallType) argument0, argument1, argument2, argument3, argument4, false, nullptr, userStackPointer); +} + +bool HasSSSE3Support() { + return simdSSSE3Support; +} + +uintptr_t GetBootloaderInformationOffset() { + return bootloaderInformationOffset; +} + +#endif diff --git a/kernel/x86_64.h b/kernel/x86_64.h new file mode 100644 index 0000000..d97e9f7 --- /dev/null +++ b/kernel/x86_64.h @@ -0,0 +1,33 @@ +#ifndef ARCH_X86_64_HEADER +#define ARCH_X86_64_HEADER + +#define LOW_MEMORY_MAP_START (0xFFFFFE0000000000) +#define LOW_MEMORY_LIMIT (0x100000) // The first 1MB is mapped here. + +extern "C" uint64_t ProcessorReadCR3(); +extern "C" void gdt_data(); + +extern "C" void SSSE3Framebuffer32To24Copy(volatile uint8_t *destination, volatile uint8_t *source, size_t pixelGroups); + +extern bool pagingNXESupport; +extern bool pagingPCIDSupport; +extern bool pagingSMEPSupport; +extern bool pagingTCESupport; + +extern "C" void processorGDTR(); +extern "C" void SetupProcessor2(struct NewProcessorStorage *); +extern "C" void ProcessorInstallTSS(uint32_t *gdt, uint32_t *tss); + +bool HasSSSE3Support(); +uintptr_t GetBootloaderInformationOffset(); + +void ArchDelay1Ms(); // Spin for approximately 1ms. Use only during initialisation. Not thread-safe. + +struct NewProcessorStorage { + struct CPULocalStorage *local; + uint32_t *gdt; +}; + +NewProcessorStorage AllocateNewProcessorStorage(struct ACPIProcessor *archCPU); + +#endif diff --git a/kernel/x86_64.s b/kernel/x86_64.s new file mode 100644 index 0000000..34780fd --- /dev/null +++ b/kernel/x86_64.s @@ -0,0 +1,1078 @@ +[bits 64] + +[section .bss] + +[extern ArchNextTimer] + +align 16 + +%define stack_size 16384 +stack: resb stack_size + +%define idt_size 4096 +idt_data: resb idt_size + +%define cpu_local_storage_size 8192 +; Array of pointers to the CPU local states +[global cpu_local_storage] +cpu_local_storage: resb cpu_local_storage_size + +[section .data] + +idt: + .limit: dw idt_size - 1 + .base: dq idt_data + +cpu_local_storage_index: + dq 0 + +[global physicalMemoryRegions] +physicalMemoryRegions: + dq 0xFFFFFE0000060000 +[global physicalMemoryRegionsCount] +physicalMemoryRegionsCount: + dq 0 +[global physicalMemoryRegionsPagesCount] +physicalMemoryRegionsPagesCount: + dq 0 +[global physicalMemoryOriginalPagesCount] +physicalMemoryOriginalPagesCount: + dq 0 +[global physicalMemoryRegionsIndex] +physicalMemoryRegionsIndex: + dq 0 +[global physicalMemoryHighest] +physicalMemoryHighest: + dq 0 + +[global pagingNXESupport] +pagingNXESupport: + dd 1 +[global pagingPCIDSupport] +pagingPCIDSupport: + dd 1 +[global pagingSMEPSupport] +pagingSMEPSupport: + dd 1 +[global pagingTCESupport] +pagingTCESupport: + dd 1 +[global simdSSE3Support] +simdSSE3Support: + dd 1 +[global simdSSSE3Support] +simdSSSE3Support: + dd 1 + +[global bootloaderID] +bootloaderID: + dd 0 +[global bootloaderInformationOffset] +bootloaderInformationOffset: + dq 0 + +align 16 +[global processorGDTR] +processorGDTR: + dq 0 + dq 0 + +[section .text] + +[global _start] +_start: + cli + mov rax,0x63 + mov fs,ax + mov gs,ax + + ; Save the bootloader ID. + mov rax,bootloaderID + mov [rax],rsi + + cmp rdi,0 + jne .standard_acpi + mov [0x7FE8],rdi + .standard_acpi: + + ; Install a stack + mov rsp,stack + stack_size + + ; Save the bootloader information offset. + mov rax,bootloaderInformationOffset + mov [rax],rdi + + ; Load the installation ID. +[extern installationID] + mov rbx,installationID + mov rax,[rdi + 0x7FF0] + mov [rbx],rax + mov rax,[rdi + 0x7FF8] + mov [rbx + 8],rax + + ; Unmap the identity paging the bootloader used + mov rax,0xFFFFFF7FBFDFE000 + mov qword [rax],0 + mov rax,cr3 + mov cr3,rax + +SetupCOM1: + ; Setup the serial COM1 port for debug output. +%ifdef COM_OUTPUT + mov dx,0x3F8 + 1 + mov al,0x00 + out dx,al + mov dx,0x3F8 + 3 + mov al,0x80 + out dx,al + mov dx,0x3F8 + 0 + mov al,0x03 + out dx,al + mov dx,0x3F8 + 1 + mov al,0x00 + out dx,al + mov dx,0x3F8 + 3 + mov al,0x03 + out dx,al + mov dx,0x3F8 + 2 + mov al,0xC7 + out dx,al + mov dx,0x3F8 + 4 + mov al,0x0B + out dx,al +%endif + +InstallIDT: + ; Remap the ISRs sent by the PIC to 0x20 - 0x2F + ; Even though we'll mask the PIC to use the APIC, + ; we have to do this so that the spurious interrupts + ; are set to a sane vector range. + mov al,0x11 + out 0x20,al + mov al,0x11 + out 0xA0,al + mov al,0x20 + out 0x21,al + mov al,0x28 + out 0xA1,al + mov al,0x04 + out 0x21,al + mov al,0x02 + out 0xA1,al + mov al,0x01 + out 0x21,al + mov al,0x01 + out 0xA1,al + mov al,0x00 + out 0x21,al + mov al,0x00 + out 0xA1,al + + ; Install the interrupt handlers +%macro INSTALL_INTERRUPT_HANDLER 1 + mov rbx,(%1 * 16) + idt_data + mov rdx,InterruptHandler%1 + call InstallInterruptHandler +%endmacro +%assign i 0 +%rep 256 + INSTALL_INTERRUPT_HANDLER i +%assign i i+1 +%endrep + + ; Save the location of the bootstrap GDT + mov rcx,processorGDTR + sgdt [rcx] + +MemoryCalculations: + ; Work out basic information about the physical memory map we got from the bootloader + mov rax,bootloaderInformationOffset + mov rax,[rax] + mov rbx,physicalMemoryRegions + add [rbx],rax + mov rdi,0xFFFFFE0000060000 - 0x10 + add rdi,rax + mov rsi,0xFFFFFE0000060000 + add rsi,rax + xor rax,rax + xor r8,r8 + .loop: + add rdi,0x10 + mov r9,[rdi + 8] + shl r9,12 + add r9,[rdi] + cmp r9,r8 + jb .lower + mov r8,r9 + .lower: + add rax,[rdi + 8] + cmp qword [rdi],0 + jne .loop + mov rbx,[rdi + 8] + sub rax,rbx + sub rdi,rsi + shr rdi,4 + mov rsi,physicalMemoryRegionsCount + mov [rsi],rdi + mov rsi,physicalMemoryRegionsPagesCount + mov [rsi],rax + mov rsi,physicalMemoryOriginalPagesCount + mov [rsi],rbx + mov rsi,physicalMemoryHighest + mov [rsi],r8 + +DisablePIC: + ; Disable the PIC by masking all its interrupts, as we're going to use the APIC instead. + ; For some reason, it'll still generate spurious interrupts, so we'll have to ignore those. + mov al,0xFF + out 0xA1,al + out 0x21,al + +StartKernel: + ; First stage of processor initilisation + call SetupProcessor1 + + ; Print a divider line. + mov rdi,'-' + mov rcx,10 + .line: call ProcessorDebugOutputByte + loop .line + mov rdi,10 + call ProcessorDebugOutputByte + mov rdi,13 + call ProcessorDebugOutputByte + + ; Call the KernelMain function + and rsp,~0xF + extern KernelMain + call KernelMain + +ProcessorReady: + ; Set the timer and become this CPU's idle thread. + mov rdi,1 + call ArchNextTimer + jmp ProcessorIdle + +SetupProcessor1: +EnableCPUFeatures: + ; Enable no-execute support, if available + mov eax,0x80000001 + cpuid + and edx,1 << 20 + shr edx,20 + mov rax,pagingNXESupport + and [rax],edx + cmp edx,0 + je .no_paging_nxe_support + mov ecx,0xC0000080 + rdmsr + or eax,1 << 11 + wrmsr + .no_paging_nxe_support: + + ; x87 FPU + fninit + mov rax,.cw + fldcw [rax] + jmp .cwa + .cw: dw 0x037A + .cwa: + + ; Enable SMEP support, if available + ; This prevents the kernel from executing userland pages + ; TODO Test this: neither Bochs or Qemu seem to support it? + xor eax,eax + cpuid + cmp eax,7 + jb .no_smep_support + mov eax,7 + xor ecx,ecx + cpuid + and ebx,1 << 7 + shr ebx,7 + mov rax,pagingSMEPSupport + and [rax],ebx + cmp ebx,0 + je .no_smep_support + mov word [rax],2 + mov rax,cr4 + or rax,1 << 20 + mov cr4,rax + .no_smep_support: + + ; Enable PCID support, if available + mov eax,1 + xor ecx,ecx + cpuid + and ecx,1 << 17 + shr ecx,17 + mov rax,pagingPCIDSupport + and [rax],ecx + cmp ecx,0 + je .no_pcid_support + mov rax,cr4 + or rax,1 << 17 + mov cr4,rax + .no_pcid_support: + + ; Enable global pages + mov rax,cr4 + or rax,1 << 7 + mov cr4,rax + + ; Enable TCE support, if available + mov eax,0x80000001 + xor ecx,ecx + cpuid + and ecx,1 << 17 + shr ecx,17 + mov rax,pagingTCESupport + and [rax],ecx + cmp ecx,0 + je .no_tce_support + mov ecx,0xC0000080 + rdmsr + or eax,1 << 15 + wrmsr + .no_tce_support: + + ; Enable write protect, so copy-on-write works in the kernel. + mov rax,cr0 + or rax,1 << 16 + mov cr0,rax + + ; Enable MMX, SSE and SSE2 + ; These features are all guaranteed to be present on a x86_64 CPU + mov rax,cr0 + mov rbx,cr4 + and rax,~4 + or rax,2 + or rbx,512 + 1024 + mov cr0,rax + mov cr4,rbx + + ; Detect SSE3 and SSSE3, if available. + mov eax,1 + cpuid + test ecx,1 << 0 + jnz .has_sse3 + mov rax,simdSSE3Support + and byte [rax],0 + .has_sse3: + test ecx,1 << 9 + jnz .has_ssse3 + mov rax,simdSSSE3Support + and byte [rax],0 + .has_ssse3: + + ; Enable system-call extensions (SYSCALL and SYSRET). + mov ecx,0xC0000080 + rdmsr + or eax,1 + wrmsr + add ecx,1 + rdmsr + mov edx,0x005B0048 + wrmsr + add ecx,1 + mov rdx,SyscallEntry + mov rax,rdx + shr rdx,32 + wrmsr + add ecx,2 + rdmsr + mov eax,(1 << 10) | (1 << 9) ; Clear direction and interrupt flag when we enter ring 0. + wrmsr + + ; Assign PAT2 to WC. + mov ecx,0x277 + xor rax,rax + xor rdx,rdx + rdmsr + and eax,0xFFF8FFFF + or eax,0x00010000 + wrmsr + +SetupCPULocalStorage: + mov ecx,0xC0000101 + mov rax,cpu_local_storage + mov rdx,cpu_local_storage + shr rdx,32 + mov rdi,cpu_local_storage_index + add rax,[rdi] + add qword [rdi],32 ; Space for 4 8-byte values at gs:0 - gs:31 + wrmsr + +LoadIDTR: + ; Load the IDTR + mov rax,idt + lidt [rax] + sti + +EnableAPIC: + ; Enable the APIC! + ; Since we're on AMD64, we know that the APIC will be present. + mov ecx,0x1B + rdmsr + and eax,~0xFFF + mov edi,eax + test eax,1 << 8 + jne $ + or eax,0x900 + wrmsr + + ; Set the spurious interrupt vector to 0xFF + mov rax,0xFFFFFE00000000F0 + add rax,rdi + mov ebx,[rax] + or ebx,0x1FF + mov [rax],ebx + + ; Use the flat processor addressing model + mov rax,0xFFFFFE00000000E0 + add rax,rdi + mov dword [rax],0xFFFFFFFF + + ; Make sure that no external interrupts are masked + xor rax,rax + mov cr8,rax + + ret + +SyscallEntry: + mov rsp,[gs:8] + sti + + mov ax,0x50 + mov ds,ax + mov es,ax + + ; Preserve RCX, R11, R12 and RBX. + push rcx + push r11 + push r12 + mov rax,rsp + push rbx + push rax + + ; Arguments in RDI, RSI, RDX, R8, R9. (RCX contains return address). + ; Return value in RAX. + [extern Syscall] + mov rbx,rsp + and rsp,~0xF + call Syscall + mov rsp,rbx + + ; Disable maskable interrupts. + cli + + ; Return to long mode. (Address in RCX). + add rsp,8 + push rax + mov ax,0x63 + mov ds,ax + mov es,ax + pop rax + pop rbx + pop r12 ; User RSP + pop r11 + pop rcx ; Return address + db 0x48 + sysret + +[global ProcessorFakeTimerInterrupt] +ProcessorFakeTimerInterrupt: + int 0x40 + ret + +[global ProcessorDisableInterrupts] +ProcessorDisableInterrupts: + mov rax,14 ; Still allow important IPIs to go through. + mov cr8,rax + sti ; TODO Where is this necessary? Is is a performance issue? + ret + +[global ProcessorEnableInterrupts] +ProcessorEnableInterrupts: + ; WARNING: Changing this mechanism also requires update in x86_64.cpp, when deciding if we should re-enable interrupts on exception. + mov rax,0 + mov cr8,rax + sti ; TODO Where is this necessary? Is is a performance issue? + ret + +[global ProcessorAreInterruptsEnabled] +ProcessorAreInterruptsEnabled: + pushf + pop rax + and rax,0x200 + shr rax,9 + + mov rdx,cr8 + cmp rdx,0 + je .done + mov rax,0 + .done: + + ; pushf + ; pop rax + ; and rax,0x200 + ; shr rax,9 + ret + +[global ProcessorHalt] +ProcessorHalt: + cli + hlt + jmp ProcessorHalt + +[global ProcessorOut8] +ProcessorOut8: + mov rdx,rdi + mov rax,rsi + out dx,al + ret + +[global ProcessorIn8] +ProcessorIn8: + mov rdx,rdi + xor rax,rax + in al,dx + ret + +[global ProcessorOut16] +ProcessorOut16: + mov rdx,rdi + mov rax,rsi + out dx,ax + ret + +[global ProcessorIn16] +ProcessorIn16: + mov rdx,rdi + xor rax,rax + in ax,dx + ret + +[global ProcessorOut32] +ProcessorOut32: + mov rdx,rdi + mov rax,rsi + out dx,eax + ret + +[global ProcessorIn32] +ProcessorIn32: + mov rdx,rdi + xor rax,rax + in eax,dx + ret + +[global ProcessorInvalidatePage] +ProcessorInvalidatePage: + invlpg [rdi] + ret + +[global ProcessorInvalidateAllPages] +ProcessorInvalidateAllPages: + ; Toggle CR4.PGE to invalidate all TLB entries, including global entries. + mov rax,cr4 + and rax,~(1 << 7) + mov cr4,rax + or rax,1 << 7 + mov cr4,rax + ret + +[global ProcessorIdle] +ProcessorIdle: + sti + hlt + jmp ProcessorIdle + +[global GetLocalStorage] +GetLocalStorage: + mov rax,[gs:0] + ret + +[global GetCurrentThread] +GetCurrentThread: + mov rax,[gs:16] + ret + +[global ProcessorSetLocalStorage] +ProcessorSetLocalStorage: + mov [gs:0],rdi + ret + +[global ProcessorSetThreadStorage] +ProcessorSetThreadStorage: + push rdx + push rcx + mov rcx,0xC0000100 ; set fs base + mov rdx,rdi + mov rax,rdi + shr rdx,32 + wrmsr ; to edx:eax (from rdi) + pop rcx + pop rdx + ret + +InstallInterruptHandler: + mov word [rbx + 0],dx + mov word [rbx + 2],0x48 + mov word [rbx + 4],0x8E00 + shr rdx,16 + mov word [rbx + 6],dx + shr rdx,16 + mov qword [rbx + 8],rdx + + ret + +%macro INTERRUPT_HANDLER 1 +InterruptHandler%1: + push dword 0 ; A fake error code + push dword %1 ; The interrupt number + jmp ASMInterruptHandler +%endmacro + +%macro INTERRUPT_HANDLER_EC 1 +InterruptHandler%1: + ; The CPU already pushed an error code + push dword %1 ; The interrupt number + jmp ASMInterruptHandler +%endmacro + +INTERRUPT_HANDLER 0 +INTERRUPT_HANDLER 1 +INTERRUPT_HANDLER 2 +INTERRUPT_HANDLER 3 +INTERRUPT_HANDLER 4 +INTERRUPT_HANDLER 5 +INTERRUPT_HANDLER 6 +INTERRUPT_HANDLER 7 +INTERRUPT_HANDLER_EC 8 +INTERRUPT_HANDLER 9 +INTERRUPT_HANDLER_EC 10 +INTERRUPT_HANDLER_EC 11 +INTERRUPT_HANDLER_EC 12 +INTERRUPT_HANDLER_EC 13 +INTERRUPT_HANDLER_EC 14 +INTERRUPT_HANDLER 15 +INTERRUPT_HANDLER 16 +INTERRUPT_HANDLER_EC 17 +INTERRUPT_HANDLER 18 +INTERRUPT_HANDLER 19 +INTERRUPT_HANDLER 20 +INTERRUPT_HANDLER 21 +INTERRUPT_HANDLER 22 +INTERRUPT_HANDLER 23 +INTERRUPT_HANDLER 24 +INTERRUPT_HANDLER 25 +INTERRUPT_HANDLER 26 +INTERRUPT_HANDLER 27 +INTERRUPT_HANDLER 28 +INTERRUPT_HANDLER 29 +INTERRUPT_HANDLER 30 +INTERRUPT_HANDLER 31 + +%assign i 32 +%rep 224 +INTERRUPT_HANDLER i +%assign i i+1 +%endrep + +ASMInterruptHandler: + cld + + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + mov rax,cr8 + push rax + + mov rax,0x123456789ABCDEF + push rax + + mov rbx,rsp + and rsp,~0xF + fxsave [rsp - 512] + mov rsp,rbx + sub rsp,512 + 16 + + xor rax,rax + mov ax,ds + push rax + mov ax,0x10 + mov ds,ax + mov es,ax + mov rax,cr2 + push rax + + mov rdi,rsp + mov rbx,rsp + and rsp,~0xF + extern InterruptHandler + call InterruptHandler + mov rsp,rbx + xor rax,rax + +ReturnFromInterruptHandler: + add rsp,8 + pop rbx + mov ds,bx + mov es,bx + + add rsp,512 + 16 + mov rbx,rsp + and rbx,~0xF + fxrstor [rbx - 512] + + cmp al,0 + je .oldThread + fninit ; New thread - initialise FPU. + .oldThread: + + pop rax + mov rbx,0x123456789ABCDEF + cmp rax,rbx + jne $ + + cli + pop rax + mov cr8,rax + + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + add rsp,16 + iretq + +[global ProcessorSetAddressSpace] +ProcessorSetAddressSpace: + mov rax,cr3 + cmp rax,rdi + je .cont + mov cr3,rdi + .cont: + ret + +[global ProcessorGetAddressSpace] +ProcessorGetAddressSpace: + mov rax,cr3 + ret + +[global ProcessorGetRSP] +ProcessorGetRSP: + mov rax,rsp + ret + +[global ProcessorGetRBP] +ProcessorGetRBP: + mov rax,rbp + ret + +[extern PostContextSwitch] +[global DoContextSwitch] +DoContextSwitch: + cli + mov [gs:16],rcx + mov [gs:8],rdx + mov rax,cr3 + cmp rax,rsi + je .cont + mov cr3,rsi + .cont: + mov rsp,rdi + call PostContextSwitch + jmp ReturnFromInterruptHandler + +[global ProcessorMagicBreakpoint] +ProcessorMagicBreakpoint: + xchg bx,bx +[global ProcessorBreakpointHelper] +ProcessorBreakpointHelper: + ret + +[global ProcessorReadCR3] +ProcessorReadCR3: + mov rax,cr3 + ret + +[global ArchSpeakerBeep] +ArchSpeakerBeep: + ; Beep!!! + mov rdi,0x43 + mov rsi,0xB6 + call ProcessorOut8 + mov rdi,0x42 + mov rsi,0x97 + call ProcessorOut8 + mov rdi,0x42 + mov rsi,0x0A + call ProcessorOut8 + mov rdi,0x61 + call ProcessorIn8 + mov rsi,rax + or rsi,3 + mov rdi,0x61 + call ProcessorOut8 + ret + +[global ProcessorDebugOutputByte] +ProcessorDebugOutputByte: +%ifdef COM_OUTPUT + mov dx,0x3F8 + 5 + .WaitRead: + in al,dx + and al,0x20 + cmp al,0 + je .WaitRead + mov dx,0x3F8 + 0 + mov rax,rdi + out dx,al +%endif + ret + +[global ProcessorReadTimeStamp] +ProcessorReadTimeStamp: + rdtsc + shl rdx,32 + or rax,rdx + ret + +[global ProcessorFlushCodeCache] +ProcessorFlushCodeCache: + wbinvd + ret + +[global ProcessorReadMXCSR] +ProcessorReadMXCSR: + mov rax,.buffer + stmxcsr [rax] + mov rax,.buffer + mov rax,[rax] + ret + .buffer: dq 0 + +[global ProcessorInstallTSS] +ProcessorInstallTSS: + push rbx + + ; Set the location of the TSS in the GDT. + mov rax,rdi + mov rbx,rsi + mov [rax + 56 + 2],bx + shr rbx,16 + mov [rax + 56 + 4],bl + shr rbx,8 + mov [rax + 56 + 7],bl + shr rbx,8 + mov [rax + 56 + 8],rbx + + ; Flush the GDT. + mov rax,gdt_data.gdt2 + mov rdx,[rax] + mov [rax],rdi + mov rdi,gdt_data.gdt + lgdt [rdi] + mov [rax],rdx + + ; Flush the TSS. + mov ax,0x38 + ltr ax + + pop rbx + ret + +[global ArchResetCPU] +ArchResetCPU: + in al,0x64 + test al,2 + jne ArchResetCPU + mov al,0xFE + out 0x64,al + jmp $ + +[global _KThreadTerminate] +[extern KThreadTerminate] +_KThreadTerminate: + sub rsp,8 + jmp KThreadTerminate + +SynchronizeTimeStampCounter: + mov rdx,[timeStampCounterSynchronizationValue] + mov rcx,0x8000000000000000 + .loop: + mov rbx,rdx + mov rax,[timeStampCounterSynchronizationValue] + xor rbx,rax + test rbx,rcx + jz .loop + sub rcx,1 + and rax,rcx + mov ecx,0x10 + mov rdx,rax + shr rdx,32 + wrmsr + ret +[global timeStampCounterSynchronizationValue] + timeStampCounterSynchronizationValue: dq 0 + +[global ProcessorAPStartup] +[bits 16] +ProcessorAPStartup: ; This function must be less than 4KB in length (see drivers/acpi.cpp) + mov ax,0x1000 + mov ds,ax + mov byte [0xFC0],1 ; Indicate we've started. + mov eax,[0xFF0] + mov cr3,eax + lgdt [0x1000 + gdt_data.gdt - gdt_data] + mov eax,cr0 + or eax,1 + mov cr0,eax + jmp 0x8:dword (.pmode - ProcessorAPStartup + 0x10000) +[bits 32] + .pmode: + mov eax,cr4 + or eax,32 + mov cr4,eax + mov ecx,0xC0000080 + rdmsr + or eax,256 + wrmsr + mov eax,cr0 + or eax,0x80000000 + mov cr0,eax + jmp 0x48:(.start_64_bit_mode - ProcessorAPStartup + 0x10000) +[bits 64] + .start_64_bit_mode: + mov rax,.start_64_bit_mode2 + jmp rax + .start_64_bit_mode2: + mov rax,0x50 + mov ds,rax + mov es,rax + mov ss,rax + mov rax,0x63 + mov fs,rax + mov gs,rax + lgdt [0x10FE0] + mov rsp,[0x10FD0] + call SetupProcessor1 + call SynchronizeTimeStampCounter + [extern SetupProcessor2] + mov rdi,[0x10FB0] + call SetupProcessor2 + mov byte [0x10FC0],2 ; Indicate the BSP can start the next processor. + and rsp,~0xF + jmp ProcessorReady + +[global gdt_data] +gdt_data: + .null_entry: dq 0 + .code_entry: dd 0xFFFF ; 0x08 + db 0 + dw 0xCF9A + db 0 + .data_entry: dd 0xFFFF ; 0x10 + db 0 + dw 0xCF92 + db 0 + .code_entry_16: dd 0xFFFF ; 0x18 + db 0 + dw 0x0F9A + db 0 + .data_entry_16: dd 0xFFFF ; 0x20 + db 0 + dw 0x0F92 + db 0 + .user_code: dd 0xFFFF ; 0x2B + db 0 + dw 0xCFFA + db 0 + .user_data: dd 0xFFFF ; 0x33 + db 0 + dw 0xCFF2 + db 0 + .tss: dd 0x68 ; 0x38 + db 0 + dw 0xE9 + db 0 + dq 0 + .code_entry64: dd 0xFFFF ; 0x48 + db 0 + dw 0xAF9A + db 0 + .data_entry64: dd 0xFFFF ; 0x50 + db 0 + dw 0xAF92 + db 0 + .user_code64: dd 0xFFFF ; 0x5B + db 0 + dw 0xAFFA + db 0 + .user_data64: dd 0xFFFF ; 0x63 + db 0 + dw 0xAFF2 + db 0 + .user_code64c: dd 0xFFFF ; 0x6B + db 0 + dw 0xAFFA + db 0 + .gdt: dw (gdt_data.gdt - gdt_data - 1) + .gdt2: dq 0x11000 + +%macro CALL_REGISTER_INDIRECT 1 +[global __x86_indirect_thunk_%1] +__x86_indirect_thunk_%1: + jmp %1 +%endmacro + +CALL_REGISTER_INDIRECT rax +CALL_REGISTER_INDIRECT rbx +CALL_REGISTER_INDIRECT rcx +CALL_REGISTER_INDIRECT rdx +CALL_REGISTER_INDIRECT rsi +CALL_REGISTER_INDIRECT rdi +CALL_REGISTER_INDIRECT rbp +CALL_REGISTER_INDIRECT r8 +CALL_REGISTER_INDIRECT r9 +CALL_REGISTER_INDIRECT r10 +CALL_REGISTER_INDIRECT r11 +CALL_REGISTER_INDIRECT r12 +CALL_REGISTER_INDIRECT r13 +CALL_REGISTER_INDIRECT r14 +CALL_REGISTER_INDIRECT r15 + +[global __cyg_profile_func_enter] +__cyg_profile_func_enter: + ret + +[global __cyg_profile_func_exit] +__cyg_profile_func_exit: + ret diff --git a/ports/acpica/build.sh b/ports/acpica/build.sh new file mode 100755 index 0000000..64005cb --- /dev/null +++ b/ports/acpica/build.sh @@ -0,0 +1,165 @@ +CC=x86_64-elf-gcc +CFLAGS=-I. -O0 -D_ACPICA_ESSENCE -D_GNU_SOURCE -std=c99 -Wall -Wextra -ffreestanding -mcmodel=large -g -Wno-unused-parameter -mno-red-zone + +$CC -c dsargs.c $CFLAGS +$CC -c dscontrol.c $CFLAGS +$CC -c dsdebug.c $CFLAGS +$CC -c dsfield.c $CFLAGS +$CC -c dsinit.c $CFLAGS +$CC -c dsmethod.c $CFLAGS +$CC -c dsmthdat.c $CFLAGS +$CC -c dsobject.c $CFLAGS +$CC -c dsopcode.c $CFLAGS +$CC -c dspkginit.c $CFLAGS +$CC -c dsutils.c $CFLAGS +$CC -c dswexec.c $CFLAGS +$CC -c dswload.c $CFLAGS +$CC -c dswload2.c $CFLAGS +$CC -c dswscope.c $CFLAGS +$CC -c dswstate.c $CFLAGS +$CC -c evevent.c $CFLAGS +$CC -c evglock.c $CFLAGS +$CC -c evgpe.c $CFLAGS +$CC -c evgpeblk.c $CFLAGS +$CC -c evgpeinit.c $CFLAGS +$CC -c evgpeutil.c $CFLAGS +$CC -c evhandler.c $CFLAGS +$CC -c evmisc.c $CFLAGS +$CC -c evregion.c $CFLAGS +$CC -c evrgnini.c $CFLAGS +$CC -c evsci.c $CFLAGS +$CC -c evxface.c $CFLAGS +$CC -c evxfevnt.c $CFLAGS +$CC -c evxfgpe.c $CFLAGS +$CC -c evxfregn.c $CFLAGS +$CC -c exconcat.c $CFLAGS +$CC -c exconfig.c $CFLAGS +$CC -c exconvrt.c $CFLAGS +$CC -c excreate.c $CFLAGS +$CC -c exdebug.c $CFLAGS +$CC -c exdump.c $CFLAGS +$CC -c exfield.c $CFLAGS +$CC -c exfldio.c $CFLAGS +$CC -c exmisc.c $CFLAGS +$CC -c exmutex.c $CFLAGS +$CC -c exnames.c $CFLAGS +$CC -c exoparg1.c $CFLAGS +$CC -c exoparg2.c $CFLAGS +$CC -c exoparg3.c $CFLAGS +$CC -c exoparg6.c $CFLAGS +$CC -c exprep.c $CFLAGS +$CC -c exregion.c $CFLAGS +$CC -c exresnte.c $CFLAGS +$CC -c exresolv.c $CFLAGS +$CC -c exresop.c $CFLAGS +$CC -c exstore.c $CFLAGS +$CC -c exstoren.c $CFLAGS +$CC -c exstorob.c $CFLAGS +$CC -c exsystem.c $CFLAGS +$CC -c extrace.c $CFLAGS +$CC -c exutils.c $CFLAGS +$CC -c hwacpi.c $CFLAGS +$CC -c hwesleep.c $CFLAGS +$CC -c hwgpe.c $CFLAGS +$CC -c hwpci.c $CFLAGS +$CC -c hwregs.c $CFLAGS +$CC -c hwsleep.c $CFLAGS +$CC -c hwtimer.c $CFLAGS +$CC -c hwvalid.c $CFLAGS +$CC -c hwxface.c $CFLAGS +$CC -c hwxfsleep.c $CFLAGS +$CC -c nsaccess.c $CFLAGS +$CC -c nsalloc.c $CFLAGS +$CC -c nsarguments.c $CFLAGS +$CC -c nsconvert.c $CFLAGS +$CC -c nsdump.c $CFLAGS +$CC -c nsdumpdv.c $CFLAGS +$CC -c nseval.c $CFLAGS +$CC -c nsinit.c $CFLAGS +$CC -c nsload.c $CFLAGS +$CC -c nsnames.c $CFLAGS +$CC -c nsobject.c $CFLAGS +$CC -c nsparse.c $CFLAGS +$CC -c nspredef.c $CFLAGS +$CC -c nsprepkg.c $CFLAGS +$CC -c nsrepair.c $CFLAGS +$CC -c nsrepair2.c $CFLAGS +$CC -c nssearch.c $CFLAGS +$CC -c nsutils.c $CFLAGS +$CC -c nswalk.c $CFLAGS +$CC -c nsxfeval.c $CFLAGS +$CC -c nsxfname.c $CFLAGS +$CC -c nsxfobj.c $CFLAGS +$CC -c psargs.c $CFLAGS +$CC -c psloop.c $CFLAGS +$CC -c psobject.c $CFLAGS +$CC -c psopcode.c $CFLAGS +$CC -c psopinfo.c $CFLAGS +$CC -c psparse.c $CFLAGS +$CC -c psscope.c $CFLAGS +$CC -c pstree.c $CFLAGS +$CC -c psutils.c $CFLAGS +$CC -c pswalk.c $CFLAGS +$CC -c psxface.c $CFLAGS +$CC -c rsaddr.c $CFLAGS +$CC -c rscalc.c $CFLAGS +$CC -c rscreate.c $CFLAGS +$CC -c rsinfo.c $CFLAGS +$CC -c rsio.c $CFLAGS +$CC -c rsirq.c $CFLAGS +$CC -c rslist.c $CFLAGS +$CC -c rsmemory.c $CFLAGS +$CC -c rsmisc.c $CFLAGS +$CC -c rsserial.c $CFLAGS +$CC -c rsutils.c $CFLAGS +$CC -c rsxface.c $CFLAGS +$CC -c tbdata.c $CFLAGS +$CC -c tbfadt.c $CFLAGS +$CC -c tbfind.c $CFLAGS +$CC -c tbinstal.c $CFLAGS +$CC -c tbprint.c $CFLAGS +$CC -c tbutils.c $CFLAGS +$CC -c tbxface.c $CFLAGS +$CC -c tbxfload.c $CFLAGS +$CC -c tbxfroot.c $CFLAGS +$CC -c utaddress.c $CFLAGS +$CC -c utalloc.c $CFLAGS +$CC -c utascii.c $CFLAGS +$CC -c utbuffer.c $CFLAGS +$CC -c utcache.c $CFLAGS +$CC -c utclib.c $CFLAGS +$CC -c utcopy.c $CFLAGS +$CC -c utdebug.c $CFLAGS +$CC -c utdecode.c $CFLAGS +$CC -c utdelete.c $CFLAGS +$CC -c uterror.c $CFLAGS +$CC -c uteval.c $CFLAGS +$CC -c utexcep.c $CFLAGS +$CC -c utglobal.c $CFLAGS +$CC -c uthex.c $CFLAGS +$CC -c utids.c $CFLAGS +$CC -c utinit.c $CFLAGS +$CC -c utlock.c $CFLAGS +$CC -c utmath.c $CFLAGS +$CC -c utmisc.c $CFLAGS +$CC -c utmutex.c $CFLAGS +$CC -c utnonansi.c $CFLAGS +$CC -c utobject.c $CFLAGS +$CC -c utosi.c $CFLAGS +$CC -c utownerid.c $CFLAGS +$CC -c utpredef.c $CFLAGS +$CC -c utprint.c $CFLAGS +$CC -c utresdecode.c $CFLAGS +$CC -c utresrc.c $CFLAGS +$CC -c utstate.c $CFLAGS +$CC -c utstring.c $CFLAGS +$CC -c utstrsuppt.c $CFLAGS +$CC -c utstrtoul64.c $CFLAGS +$CC -c uttrack.c $CFLAGS +$CC -c utuuid.c $CFLAGS +$CC -c utxface.c $CFLAGS +$CC -c utxferror.c $CFLAGS +$CC -c utxfinit.c $CFLAGS +$CC -c utxfmutex.c $CFLAGS + +x86_64-elf-ar -rcs libacpica.a dsargs.o dscontrol.o dsdebug.o dsfield.o dsinit.o dsmethod.o dsmthdat.o dsobject.o dsopcode.o dspkginit.o dsutils.o dswexec.o dswload.o dswload2.o dswscope.o dswstate.o evevent.o evglock.o evgpe.o evgpeblk.o evgpeinit.o evgpeutil.o evhandler.o evmisc.o evregion.o evrgnini.o evsci.o evxface.o evxfevnt.o evxfgpe.o evxfregn.o exconcat.o exconfig.o exconvrt.o excreate.o exdebug.o exdump.o exfield.o exfldio.o exmisc.o exmutex.o exnames.o exoparg1.o exoparg2.o exoparg3.o exoparg6.o exprep.o exregion.o exresnte.o exresolv.o exresop.o exstore.o exstoren.o exstorob.o exsystem.o extrace.o exutils.o hwacpi.o hwesleep.o hwgpe.o hwpci.o hwregs.o hwsleep.o hwtimer.o hwvalid.o hwxface.o hwxfsleep.o nsaccess.o nsalloc.o nsarguments.o nsconvert.o nsdump.o nsdumpdv.o nseval.o nsinit.o nsload.o nsnames.o nsobject.o nsparse.o nspredef.o nsprepkg.o nsrepair.o nsrepair2.o nssearch.o nsutils.o nswalk.o nsxfeval.o nsxfname.o nsxfobj.o psargs.o psloop.o psobject.o psopcode.o psopinfo.o psparse.o psscope.o pstree.o psutils.o pswalk.o psxface.o rsaddr.o rscalc.o rscreate.o rsinfo.o rsio.o rsirq.o rslist.o rsmemory.o rsmisc.o rsserial.o rsutils.o rsxface.o tbdata.o tbfadt.o tbfind.o tbinstal.o tbprint.o tbutils.o tbxface.o tbxfload.o tbxfroot.o utaddress.o utalloc.o utascii.o utbuffer.o utcache.o utclib.o utcopy.o utdebug.o utdecode.o utdelete.o uterror.o uteval.o utexcep.o utglobal.o uthex.o utids.o utinit.o utlock.o utmath.o utmisc.o utmutex.o utnonansi.o utobject.o utosi.o utownerid.o utpredef.o utprint.o utresdecode.o utresrc.o utstate.o utstring.o utstrsuppt.o utstrtoul64.o uttrack.o utuuid.o utxface.o utxferror.o utxfinit.o utxfmutex.o diff --git a/ports/acpica/include/acapps.h b/ports/acpica/include/acapps.h new file mode 100644 index 0000000..dcaebd7 --- /dev/null +++ b/ports/acpica/include/acapps.h @@ -0,0 +1,343 @@ +/****************************************************************************** + * + * Module Name: acapps - common include for ACPI applications/tools + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef _ACAPPS +#define _ACAPPS + +#ifdef ACPI_USE_STANDARD_HEADERS +#include +#endif /* ACPI_USE_STANDARD_HEADERS */ + +/* Common info for tool signons */ + +#define ACPICA_NAME "Intel ACPI Component Architecture" +#define ACPICA_COPYRIGHT "Copyright (c) 2000 - 2018 Intel Corporation" + +#if ACPI_MACHINE_WIDTH == 64 +#define ACPI_WIDTH " (64-bit version)" + +#elif ACPI_MACHINE_WIDTH == 32 +#define ACPI_WIDTH " (32-bit version)" + +#else +#error unknown ACPI_MACHINE_WIDTH +#define ACPI_WIDTH " (unknown bit width, not 32 or 64)" + +#endif + +/* Macros for signons and file headers */ + +#define ACPI_COMMON_SIGNON(UtilityName) \ + "\n%s\n%s version %8.8X\n%s\n\n", \ + ACPICA_NAME, \ + UtilityName, ((UINT32) ACPI_CA_VERSION), \ + ACPICA_COPYRIGHT + +#define ACPI_COMMON_HEADER(UtilityName, Prefix) \ + "%s%s\n%s%s version %8.8X%s\n%s%s\n%s\n", \ + Prefix, ACPICA_NAME, \ + Prefix, UtilityName, ((UINT32) ACPI_CA_VERSION), ACPI_WIDTH, \ + Prefix, ACPICA_COPYRIGHT, \ + Prefix + +#define ACPI_COMMON_BUILD_TIME \ + "Build date/time: %s %s\n", __DATE__, __TIME__ + +/* Macros for usage messages */ + +#define ACPI_USAGE_HEADER(Usage) \ + printf ("Usage: %s\nOptions:\n", Usage); + +#define ACPI_USAGE_TEXT(Description) \ + printf (Description); + +#define ACPI_OPTION(Name, Description) \ + printf (" %-20s%s\n", Name, Description); + + +/* Check for unexpected exceptions */ + +#define ACPI_CHECK_STATUS(Name, Status, Expected) \ + if (Status != Expected) \ + { \ + AcpiOsPrintf ("Unexpected %s from %s (%s-%d)\n", \ + AcpiFormatException (Status), #Name, _AcpiModuleName, __LINE__); \ + } + +/* Check for unexpected non-AE_OK errors */ + + +#define ACPI_CHECK_OK(Name, Status) ACPI_CHECK_STATUS (Name, Status, AE_OK); + +#define FILE_SUFFIX_DISASSEMBLY "dsl" +#define FILE_SUFFIX_BINARY_TABLE ".dat" /* Needs the dot */ + + +/* acfileio */ + +ACPI_STATUS +AcGetAllTablesFromFile ( + char *Filename, + UINT8 GetOnlyAmlTables, + ACPI_NEW_TABLE_DESC **ReturnListHead); + +void +AcDeleteTableList ( + ACPI_NEW_TABLE_DESC *ListHead); + +BOOLEAN +AcIsFileBinary ( + FILE *File); + +ACPI_STATUS +AcValidateTableHeader ( + FILE *File, + long TableOffset); + + +/* Values for GetOnlyAmlTables */ + +#define ACPI_GET_ONLY_AML_TABLES TRUE +#define ACPI_GET_ALL_TABLES FALSE + + +/* + * getopt + */ +int +AcpiGetopt( + int argc, + char **argv, + char *opts); + +int +AcpiGetoptArgument ( + int argc, + char **argv); + +extern int AcpiGbl_Optind; +extern int AcpiGbl_Opterr; +extern int AcpiGbl_SubOptChar; +extern char *AcpiGbl_Optarg; + + +/* + * cmfsize - Common get file size function + */ +UINT32 +CmGetFileSize ( + ACPI_FILE File); + + +/* + * adwalk + */ +void +AcpiDmCrossReferenceNamespace ( + ACPI_PARSE_OBJECT *ParseTreeRoot, + ACPI_NAMESPACE_NODE *NamespaceRoot, + ACPI_OWNER_ID OwnerId); + +void +AcpiDmDumpTree ( + ACPI_PARSE_OBJECT *Origin); + +void +AcpiDmFindOrphanMethods ( + ACPI_PARSE_OBJECT *Origin); + +void +AcpiDmFinishNamespaceLoad ( + ACPI_PARSE_OBJECT *ParseTreeRoot, + ACPI_NAMESPACE_NODE *NamespaceRoot, + ACPI_OWNER_ID OwnerId); + +void +AcpiDmConvertParseObjects ( + ACPI_PARSE_OBJECT *ParseTreeRoot, + ACPI_NAMESPACE_NODE *NamespaceRoot); + + +/* + * adfile + */ +ACPI_STATUS +AdInitialize ( + void); + +char * +FlGenerateFilename ( + char *InputFilename, + char *Suffix); + +ACPI_STATUS +FlSplitInputPathname ( + char *InputPath, + char **OutDirectoryPath, + char **OutFilename); + +char * +FlGetFileBasename ( + char *FilePathname); + +char * +AdGenerateFilename ( + char *Prefix, + char *TableId); + +void +AdWriteTable ( + ACPI_TABLE_HEADER *Table, + UINT32 Length, + char *TableName, + char *OemTableId); + +#endif /* _ACAPPS */ diff --git a/ports/acpica/include/acbuffer.h b/ports/acpica/include/acbuffer.h new file mode 100644 index 0000000..857d8bc --- /dev/null +++ b/ports/acpica/include/acbuffer.h @@ -0,0 +1,363 @@ +/****************************************************************************** + * + * Name: acbuffer.h - Support for buffers returned by ACPI predefined names + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACBUFFER_H__ +#define __ACBUFFER_H__ + +/* + * Contains buffer structures for these predefined names: + * _FDE, _GRT, _GTM, _PLD, _SRT + */ + +/* + * Note: C bitfields are not used for this reason: + * + * "Bitfields are great and easy to read, but unfortunately the C language + * does not specify the layout of bitfields in memory, which means they are + * essentially useless for dealing with packed data in on-disk formats or + * binary wire protocols." (Or ACPI tables and buffers.) "If you ask me, + * this decision was a design error in C. Ritchie could have picked an order + * and stuck with it." Norman Ramsey. + * See http://stackoverflow.com/a/1053662/41661 + */ + + +/* _FDE return value */ + +typedef struct acpi_fde_info +{ + UINT32 Floppy0; + UINT32 Floppy1; + UINT32 Floppy2; + UINT32 Floppy3; + UINT32 Tape; + +} ACPI_FDE_INFO; + +/* + * _GRT return value + * _SRT input value + */ +typedef struct acpi_grt_info +{ + UINT16 Year; + UINT8 Month; + UINT8 Day; + UINT8 Hour; + UINT8 Minute; + UINT8 Second; + UINT8 Valid; + UINT16 Milliseconds; + UINT16 Timezone; + UINT8 Daylight; + UINT8 Reserved[3]; + +} ACPI_GRT_INFO; + +/* _GTM return value */ + +typedef struct acpi_gtm_info +{ + UINT32 PioSpeed0; + UINT32 DmaSpeed0; + UINT32 PioSpeed1; + UINT32 DmaSpeed1; + UINT32 Flags; + +} ACPI_GTM_INFO; + +/* + * Formatted _PLD return value. The minimum size is a package containing + * one buffer. + * Revision 1: Buffer is 16 bytes (128 bits) + * Revision 2: Buffer is 20 bytes (160 bits) + * + * Note: This structure is returned from the AcpiDecodePldBuffer + * interface. + */ +typedef struct acpi_pld_info +{ + UINT8 Revision; + UINT8 IgnoreColor; + UINT8 Red; + UINT8 Green; + UINT8 Blue; + UINT16 Width; + UINT16 Height; + UINT8 UserVisible; + UINT8 Dock; + UINT8 Lid; + UINT8 Panel; + UINT8 VerticalPosition; + UINT8 HorizontalPosition; + UINT8 Shape; + UINT8 GroupOrientation; + UINT8 GroupToken; + UINT8 GroupPosition; + UINT8 Bay; + UINT8 Ejectable; + UINT8 OspmEjectRequired; + UINT8 CabinetNumber; + UINT8 CardCageNumber; + UINT8 Reference; + UINT8 Rotation; + UINT8 Order; + UINT8 Reserved; + UINT16 VerticalOffset; + UINT16 HorizontalOffset; + +} ACPI_PLD_INFO; + + +/* + * Macros to: + * 1) Convert a _PLD buffer to internal ACPI_PLD_INFO format - ACPI_PLD_GET* + * (Used by AcpiDecodePldBuffer) + * 2) Construct a _PLD buffer - ACPI_PLD_SET* + * (Intended for BIOS use only) + */ +#define ACPI_PLD_REV1_BUFFER_SIZE 16 /* For Revision 1 of the buffer (From ACPI spec) */ +#define ACPI_PLD_REV2_BUFFER_SIZE 20 /* For Revision 2 of the buffer (From ACPI spec) */ +#define ACPI_PLD_BUFFER_SIZE 20 /* For Revision 2 of the buffer (From ACPI spec) */ + +/* First 32-bit dword, bits 0:32 */ + +#define ACPI_PLD_GET_REVISION(dword) ACPI_GET_BITS (dword, 0, ACPI_7BIT_MASK) +#define ACPI_PLD_SET_REVISION(dword,value) ACPI_SET_BITS (dword, 0, ACPI_7BIT_MASK, value) /* Offset 0, Len 7 */ + +#define ACPI_PLD_GET_IGNORE_COLOR(dword) ACPI_GET_BITS (dword, 7, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_IGNORE_COLOR(dword,value) ACPI_SET_BITS (dword, 7, ACPI_1BIT_MASK, value) /* Offset 7, Len 1 */ + +#define ACPI_PLD_GET_RED(dword) ACPI_GET_BITS (dword, 8, ACPI_8BIT_MASK) +#define ACPI_PLD_SET_RED(dword,value) ACPI_SET_BITS (dword, 8, ACPI_8BIT_MASK, value) /* Offset 8, Len 8 */ + +#define ACPI_PLD_GET_GREEN(dword) ACPI_GET_BITS (dword, 16, ACPI_8BIT_MASK) +#define ACPI_PLD_SET_GREEN(dword,value) ACPI_SET_BITS (dword, 16, ACPI_8BIT_MASK, value) /* Offset 16, Len 8 */ + +#define ACPI_PLD_GET_BLUE(dword) ACPI_GET_BITS (dword, 24, ACPI_8BIT_MASK) +#define ACPI_PLD_SET_BLUE(dword,value) ACPI_SET_BITS (dword, 24, ACPI_8BIT_MASK, value) /* Offset 24, Len 8 */ + +/* Second 32-bit dword, bits 33:63 */ + +#define ACPI_PLD_GET_WIDTH(dword) ACPI_GET_BITS (dword, 0, ACPI_16BIT_MASK) +#define ACPI_PLD_SET_WIDTH(dword,value) ACPI_SET_BITS (dword, 0, ACPI_16BIT_MASK, value) /* Offset 32+0=32, Len 16 */ + +#define ACPI_PLD_GET_HEIGHT(dword) ACPI_GET_BITS (dword, 16, ACPI_16BIT_MASK) +#define ACPI_PLD_SET_HEIGHT(dword,value) ACPI_SET_BITS (dword, 16, ACPI_16BIT_MASK, value) /* Offset 32+16=48, Len 16 */ + +/* Third 32-bit dword, bits 64:95 */ + +#define ACPI_PLD_GET_USER_VISIBLE(dword) ACPI_GET_BITS (dword, 0, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_USER_VISIBLE(dword,value) ACPI_SET_BITS (dword, 0, ACPI_1BIT_MASK, value) /* Offset 64+0=64, Len 1 */ + +#define ACPI_PLD_GET_DOCK(dword) ACPI_GET_BITS (dword, 1, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_DOCK(dword,value) ACPI_SET_BITS (dword, 1, ACPI_1BIT_MASK, value) /* Offset 64+1=65, Len 1 */ + +#define ACPI_PLD_GET_LID(dword) ACPI_GET_BITS (dword, 2, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_LID(dword,value) ACPI_SET_BITS (dword, 2, ACPI_1BIT_MASK, value) /* Offset 64+2=66, Len 1 */ + +#define ACPI_PLD_GET_PANEL(dword) ACPI_GET_BITS (dword, 3, ACPI_3BIT_MASK) +#define ACPI_PLD_SET_PANEL(dword,value) ACPI_SET_BITS (dword, 3, ACPI_3BIT_MASK, value) /* Offset 64+3=67, Len 3 */ + +#define ACPI_PLD_GET_VERTICAL(dword) ACPI_GET_BITS (dword, 6, ACPI_2BIT_MASK) +#define ACPI_PLD_SET_VERTICAL(dword,value) ACPI_SET_BITS (dword, 6, ACPI_2BIT_MASK, value) /* Offset 64+6=70, Len 2 */ + +#define ACPI_PLD_GET_HORIZONTAL(dword) ACPI_GET_BITS (dword, 8, ACPI_2BIT_MASK) +#define ACPI_PLD_SET_HORIZONTAL(dword,value) ACPI_SET_BITS (dword, 8, ACPI_2BIT_MASK, value) /* Offset 64+8=72, Len 2 */ + +#define ACPI_PLD_GET_SHAPE(dword) ACPI_GET_BITS (dword, 10, ACPI_4BIT_MASK) +#define ACPI_PLD_SET_SHAPE(dword,value) ACPI_SET_BITS (dword, 10, ACPI_4BIT_MASK, value) /* Offset 64+10=74, Len 4 */ + +#define ACPI_PLD_GET_ORIENTATION(dword) ACPI_GET_BITS (dword, 14, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_ORIENTATION(dword,value) ACPI_SET_BITS (dword, 14, ACPI_1BIT_MASK, value) /* Offset 64+14=78, Len 1 */ + +#define ACPI_PLD_GET_TOKEN(dword) ACPI_GET_BITS (dword, 15, ACPI_8BIT_MASK) +#define ACPI_PLD_SET_TOKEN(dword,value) ACPI_SET_BITS (dword, 15, ACPI_8BIT_MASK, value) /* Offset 64+15=79, Len 8 */ + +#define ACPI_PLD_GET_POSITION(dword) ACPI_GET_BITS (dword, 23, ACPI_8BIT_MASK) +#define ACPI_PLD_SET_POSITION(dword,value) ACPI_SET_BITS (dword, 23, ACPI_8BIT_MASK, value) /* Offset 64+23=87, Len 8 */ + +#define ACPI_PLD_GET_BAY(dword) ACPI_GET_BITS (dword, 31, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_BAY(dword,value) ACPI_SET_BITS (dword, 31, ACPI_1BIT_MASK, value) /* Offset 64+31=95, Len 1 */ + +/* Fourth 32-bit dword, bits 96:127 */ + +#define ACPI_PLD_GET_EJECTABLE(dword) ACPI_GET_BITS (dword, 0, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_EJECTABLE(dword,value) ACPI_SET_BITS (dword, 0, ACPI_1BIT_MASK, value) /* Offset 96+0=96, Len 1 */ + +#define ACPI_PLD_GET_OSPM_EJECT(dword) ACPI_GET_BITS (dword, 1, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_OSPM_EJECT(dword,value) ACPI_SET_BITS (dword, 1, ACPI_1BIT_MASK, value) /* Offset 96+1=97, Len 1 */ + +#define ACPI_PLD_GET_CABINET(dword) ACPI_GET_BITS (dword, 2, ACPI_8BIT_MASK) +#define ACPI_PLD_SET_CABINET(dword,value) ACPI_SET_BITS (dword, 2, ACPI_8BIT_MASK, value) /* Offset 96+2=98, Len 8 */ + +#define ACPI_PLD_GET_CARD_CAGE(dword) ACPI_GET_BITS (dword, 10, ACPI_8BIT_MASK) +#define ACPI_PLD_SET_CARD_CAGE(dword,value) ACPI_SET_BITS (dword, 10, ACPI_8BIT_MASK, value) /* Offset 96+10=106, Len 8 */ + +#define ACPI_PLD_GET_REFERENCE(dword) ACPI_GET_BITS (dword, 18, ACPI_1BIT_MASK) +#define ACPI_PLD_SET_REFERENCE(dword,value) ACPI_SET_BITS (dword, 18, ACPI_1BIT_MASK, value) /* Offset 96+18=114, Len 1 */ + +#define ACPI_PLD_GET_ROTATION(dword) ACPI_GET_BITS (dword, 19, ACPI_4BIT_MASK) +#define ACPI_PLD_SET_ROTATION(dword,value) ACPI_SET_BITS (dword, 19, ACPI_4BIT_MASK, value) /* Offset 96+19=115, Len 4 */ + +#define ACPI_PLD_GET_ORDER(dword) ACPI_GET_BITS (dword, 23, ACPI_5BIT_MASK) +#define ACPI_PLD_SET_ORDER(dword,value) ACPI_SET_BITS (dword, 23, ACPI_5BIT_MASK, value) /* Offset 96+23=119, Len 5 */ + +/* Fifth 32-bit dword, bits 128:159 (Revision 2 of _PLD only) */ + +#define ACPI_PLD_GET_VERT_OFFSET(dword) ACPI_GET_BITS (dword, 0, ACPI_16BIT_MASK) +#define ACPI_PLD_SET_VERT_OFFSET(dword,value) ACPI_SET_BITS (dword, 0, ACPI_16BIT_MASK, value) /* Offset 128+0=128, Len 16 */ + +#define ACPI_PLD_GET_HORIZ_OFFSET(dword) ACPI_GET_BITS (dword, 16, ACPI_16BIT_MASK) +#define ACPI_PLD_SET_HORIZ_OFFSET(dword,value) ACPI_SET_BITS (dword, 16, ACPI_16BIT_MASK, value) /* Offset 128+16=144, Len 16 */ + + +#endif /* ACBUFFER_H */ diff --git a/ports/acpica/include/acclib.h b/ports/acpica/include/acclib.h new file mode 100644 index 0000000..7c4c9a2 --- /dev/null +++ b/ports/acpica/include/acclib.h @@ -0,0 +1,431 @@ +/****************************************************************************** + * + * Name: acclib.h -- C library support. Prototypes for the (optional) local + * implementations of required C library functions. + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef _ACCLIB_H +#define _ACCLIB_H + + +/* + * Prototypes and macros for local implementations of C library functions + */ + +/* is* functions. The AcpiGbl_Ctypes array is defined in utclib.c */ + +extern const UINT8 AcpiGbl_Ctypes[]; + +#define _ACPI_XA 0x00 /* extra alphabetic - not supported */ +#define _ACPI_XS 0x40 /* extra space */ +#define _ACPI_BB 0x00 /* BEL, BS, etc. - not supported */ +#define _ACPI_CN 0x20 /* CR, FF, HT, NL, VT */ +#define _ACPI_DI 0x04 /* '0'-'9' */ +#define _ACPI_LO 0x02 /* 'a'-'z' */ +#define _ACPI_PU 0x10 /* punctuation */ +#define _ACPI_SP 0x08 /* space, tab, CR, LF, VT, FF */ +#define _ACPI_UP 0x01 /* 'A'-'Z' */ +#define _ACPI_XD 0x80 /* '0'-'9', 'A'-'F', 'a'-'f' */ + +#define isdigit(c) (AcpiGbl_Ctypes[(unsigned char)(c)] & (_ACPI_DI)) +#define isspace(c) (AcpiGbl_Ctypes[(unsigned char)(c)] & (_ACPI_SP)) +#define isxdigit(c) (AcpiGbl_Ctypes[(unsigned char)(c)] & (_ACPI_XD)) +#define isupper(c) (AcpiGbl_Ctypes[(unsigned char)(c)] & (_ACPI_UP)) +#define islower(c) (AcpiGbl_Ctypes[(unsigned char)(c)] & (_ACPI_LO)) +#define isprint(c) (AcpiGbl_Ctypes[(unsigned char)(c)] & (_ACPI_LO | _ACPI_UP | _ACPI_DI | _ACPI_XS | _ACPI_PU)) +#define isalpha(c) (AcpiGbl_Ctypes[(unsigned char)(c)] & (_ACPI_LO | _ACPI_UP)) + +/* Error code */ + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define EBADF 9 /* Bad file number */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define ENODEV 19 /* No such device */ +#define EINVAL 22 /* Invalid argument */ +#define EPIPE 32 /* Broken pipe */ +#define ERANGE 34 /* Math result not representable */ + +/* Strings */ + +char * +strcat ( + char *DstString, + const char *SrcString); + +char * +strchr ( + const char *String, + int ch); + +char * +strpbrk ( + const char *String, + const char *Delimiters); + +char * +strtok ( + char *String, + const char *Delimiters); + +char * +strcpy ( + char *DstString, + const char *SrcString); + +int +strcmp ( + const char *String1, + const char *String2); + +ACPI_SIZE +strlen ( + const char *String); + +char * +strncat ( + char *DstString, + const char *SrcString, + ACPI_SIZE Count); + +int +strncmp ( + const char *String1, + const char *String2, + ACPI_SIZE Count); + +char * +strncpy ( + char *DstString, + const char *SrcString, + ACPI_SIZE Count); + +char * +strstr ( + char *String1, + char *String2); + + +/* Conversion */ + +UINT32 +strtoul ( + const char *String, + char **Terminator, + UINT32 Base); + + +/* Memory */ + +int +memcmp ( + void *Buffer1, + void *Buffer2, + ACPI_SIZE Count); + +void * +memcpy ( + void *Dest, + const void *Src, + ACPI_SIZE Count); + +void * +memmove ( + void *Dest, + const void *Src, + ACPI_SIZE Count); + +void * +memset ( + void *Dest, + int Value, + ACPI_SIZE Count); + + +/* upper/lower case */ + +int +tolower ( + int c); + +int +toupper ( + int c); + +/* + * utprint - printf/vprintf output functions + */ +const char * +AcpiUtScanNumber ( + const char *String, + UINT64 *NumberPtr); + +const char * +AcpiUtPrintNumber ( + char *String, + UINT64 Number); + +int +vsnprintf ( + char *String, + ACPI_SIZE Size, + const char *Format, + va_list Args); + +int +snprintf ( + char *String, + ACPI_SIZE Size, + const char *Format, + ...); + +int +sprintf ( + char *String, + const char *Format, + ...); + +#ifdef ACPI_APPLICATION +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +/* + * NOTE: Currently we only need to update errno for file IOs. Other + * Clibrary invocations in ACPICA do not make descisions according to + * the errno. + */ +extern int errno; + +#ifndef EOF +#define EOF (-1) +#endif + +#define putchar(c) fputc(stdout, c) +#define getchar(c) fgetc(stdin) + +int +vprintf ( + const char *Format, + va_list Args); + +int +printf ( + const char *Format, + ...); + +int +vfprintf ( + FILE *File, + const char *Format, + va_list Args); + +int +fprintf ( + FILE *File, + const char *Format, + ...); + +FILE * +fopen ( + const char *Path, + const char *Modes); + +void +fclose ( + FILE *File); + +int +fread ( + void *Buffer, + ACPI_SIZE Size, + ACPI_SIZE Count, + FILE *File); + +int +fwrite ( + void *Buffer, + ACPI_SIZE Size, + ACPI_SIZE Count, + FILE *File); + +int +fseek ( + FILE *File, + long Offset, + int From); + +long +ftell ( + FILE *File); + +int +fgetc ( + FILE *File); + +int +fputc ( + FILE *File, + char c); + +char * +fgets ( + char *s, + ACPI_SIZE Size, + FILE *File); +#endif + +#endif /* _ACCLIB_H */ diff --git a/ports/acpica/include/accommon.h b/ports/acpica/include/accommon.h new file mode 100644 index 0000000..357df16 --- /dev/null +++ b/ports/acpica/include/accommon.h @@ -0,0 +1,175 @@ +/****************************************************************************** + * + * Name: accommon.h - Common include files for generation of ACPICA source + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACCOMMON_H__ +#define __ACCOMMON_H__ + +/* + * Common set of includes for all ACPICA source files. + * We put them here because we don't want to duplicate them + * in the the source code again and again. + * + * Note: The order of these include files is important. + */ +#include "acconfig.h" /* Global configuration constants */ +#include "acmacros.h" /* C macros */ +#include "aclocal.h" /* Internal data types */ +#include "acobject.h" /* ACPI internal object */ +#include "acstruct.h" /* Common structures */ +#include "acglobal.h" /* All global variables */ +#include "achware.h" /* Hardware defines and interfaces */ +#include "acutils.h" /* Utility interfaces */ +#ifndef ACPI_USE_SYSTEM_CLIBRARY +#include "acclib.h" /* C library interfaces */ +#endif /* !ACPI_USE_SYSTEM_CLIBRARY */ + + +#endif /* __ACCOMMON_H__ */ diff --git a/ports/acpica/include/acconfig.h b/ports/acpica/include/acconfig.h new file mode 100644 index 0000000..c5a67ca --- /dev/null +++ b/ports/acpica/include/acconfig.h @@ -0,0 +1,365 @@ +/****************************************************************************** + * + * Name: acconfig.h - Global configuration constants + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef _ACCONFIG_H +#define _ACCONFIG_H + + +/****************************************************************************** + * + * Configuration options + * + *****************************************************************************/ + +/* + * ACPI_DEBUG_OUTPUT - This switch enables all the debug facilities of the + * ACPI subsystem. This includes the DEBUG_PRINT output + * statements. When disabled, all DEBUG_PRINT + * statements are compiled out. + * + * ACPI_APPLICATION - Use this switch if the subsystem is going to be run + * at the application level. + * + */ + +/* + * OS name, used for the _OS object. The _OS object is essentially obsolete, + * but there is a large base of ASL/AML code in existing machines that check + * for the string below. The use of this string usually guarantees that + * the ASL will execute down the most tested code path. Also, there is some + * code that will not execute the _OSI method unless _OS matches the string + * below. Therefore, change this string at your own risk. + */ +#define ACPI_OS_NAME "Microsoft Windows NT" + +/* Maximum objects in the various object caches */ + +#define ACPI_MAX_STATE_CACHE_DEPTH 96 /* State objects */ +#define ACPI_MAX_PARSE_CACHE_DEPTH 96 /* Parse tree objects */ +#define ACPI_MAX_EXTPARSE_CACHE_DEPTH 96 /* Parse tree objects */ +#define ACPI_MAX_OBJECT_CACHE_DEPTH 96 /* Interpreter operand objects */ +#define ACPI_MAX_NAMESPACE_CACHE_DEPTH 96 /* Namespace objects */ +#define ACPI_MAX_COMMENT_CACHE_DEPTH 96 /* Comments for the -ca option */ + +/* + * Should the subsystem abort the loading of an ACPI table if the + * table checksum is incorrect? + */ +#ifndef ACPI_CHECKSUM_ABORT +#define ACPI_CHECKSUM_ABORT FALSE +#endif + +/* + * Generate a version of ACPICA that only supports "reduced hardware" + * platforms (as defined in ACPI 5.0). Set to TRUE to generate a specialized + * version of ACPICA that ONLY supports the ACPI 5.0 "reduced hardware" + * model. In other words, no ACPI hardware is supported. + * + * If TRUE, this means no support for the following: + * PM Event and Control registers + * SCI interrupt (and handler) + * Fixed Events + * General Purpose Events (GPEs) + * Global Lock + * ACPI PM timer + * FACS table (Waking vectors and Global Lock) + */ +#ifndef ACPI_REDUCED_HARDWARE +#define ACPI_REDUCED_HARDWARE FALSE +#endif + + +/****************************************************************************** + * + * Subsystem Constants + * + *****************************************************************************/ + +/* Version of ACPI supported */ + +#define ACPI_CA_SUPPORT_LEVEL 5 + +/* Maximum count for a semaphore object */ + +#define ACPI_MAX_SEMAPHORE_COUNT 256 + +/* Maximum object reference count (detects object deletion issues) */ + +#define ACPI_MAX_REFERENCE_COUNT 0x800 + +/* Default page size for use in mapping memory for operation regions */ + +#define ACPI_DEFAULT_PAGE_SIZE 4096 /* Must be power of 2 */ + +/* OwnerId tracking. 8 entries allows for 255 OwnerIds */ + +#define ACPI_NUM_OWNERID_MASKS 8 + +/* Size of the root table array is increased by this increment */ + +#define ACPI_ROOT_TABLE_SIZE_INCREMENT 4 + +/* Maximum sleep allowed via Sleep() operator */ + +#define ACPI_MAX_SLEEP 2000 /* 2000 millisec == two seconds */ + +/* Address Range lists are per-SpaceId (Memory and I/O only) */ + +#define ACPI_ADDRESS_RANGE_MAX 2 + +/* Maximum time (default 30s) of While() loops before abort */ + +#define ACPI_MAX_LOOP_TIMEOUT 30 + + +/****************************************************************************** + * + * ACPI Specification constants (Do not change unless the specification changes) + * + *****************************************************************************/ + +/* Method info (in WALK_STATE), containing local variables and argumetns */ + +#define ACPI_METHOD_NUM_LOCALS 8 +#define ACPI_METHOD_MAX_LOCAL 7 + +#define ACPI_METHOD_NUM_ARGS 7 +#define ACPI_METHOD_MAX_ARG 6 + +/* + * Operand Stack (in WALK_STATE), Must be large enough to contain METHOD_MAX_ARG + */ +#define ACPI_OBJ_NUM_OPERANDS 8 +#define ACPI_OBJ_MAX_OPERAND 7 + +/* Number of elements in the Result Stack frame, can be an arbitrary value */ + +#define ACPI_RESULTS_FRAME_OBJ_NUM 8 + +/* + * Maximal number of elements the Result Stack can contain, + * it may be an arbitray value not exceeding the types of + * ResultSize and ResultCount (now UINT8). + */ +#define ACPI_RESULTS_OBJ_NUM_MAX 255 + +/* Constants used in searching for the RSDP in low memory */ + +#define ACPI_EBDA_PTR_LOCATION 0x0000040E /* Physical Address */ +#define ACPI_EBDA_PTR_LENGTH 2 +#define ACPI_EBDA_WINDOW_SIZE 1024 +#define ACPI_HI_RSDP_WINDOW_BASE 0x000E0000 /* Physical Address */ +#define ACPI_HI_RSDP_WINDOW_SIZE 0x00020000 +#define ACPI_RSDP_SCAN_STEP 16 + +/* Operation regions */ + +#define ACPI_USER_REGION_BEGIN 0x80 + +/* Maximum SpaceIds for Operation Regions */ + +#define ACPI_MAX_ADDRESS_SPACE 255 +#define ACPI_NUM_DEFAULT_SPACES 4 + +/* Array sizes. Used for range checking also */ + +#define ACPI_MAX_MATCH_OPCODE 5 + +/* RSDP checksums */ + +#define ACPI_RSDP_CHECKSUM_LENGTH 20 +#define ACPI_RSDP_XCHECKSUM_LENGTH 36 + +/* SMBus, GSBus and IPMI bidirectional buffer size */ + +#define ACPI_SMBUS_BUFFER_SIZE 34 +#define ACPI_GSBUS_BUFFER_SIZE 34 +#define ACPI_IPMI_BUFFER_SIZE 66 + +/* _SxD and _SxW control methods */ + +#define ACPI_NUM_SxD_METHODS 4 +#define ACPI_NUM_SxW_METHODS 5 + + +/****************************************************************************** + * + * Miscellaneous constants + * + *****************************************************************************/ + +/* UUID constants */ + +#define UUID_BUFFER_LENGTH 16 /* Length of UUID in memory */ +#define UUID_STRING_LENGTH 36 /* Total length of a UUID string */ + +/* Positions for required hyphens (dashes) in UUID strings */ + +#define UUID_HYPHEN1_OFFSET 8 +#define UUID_HYPHEN2_OFFSET 13 +#define UUID_HYPHEN3_OFFSET 18 +#define UUID_HYPHEN4_OFFSET 23 + + +/****************************************************************************** + * + * ACPI AML Debugger + * + *****************************************************************************/ + +#define ACPI_DEBUGGER_MAX_ARGS ACPI_METHOD_NUM_ARGS + 4 /* Max command line arguments */ +#define ACPI_DB_LINE_BUFFER_SIZE 512 + +#define ACPI_DEBUGGER_COMMAND_PROMPT '-' +#define ACPI_DEBUGGER_EXECUTE_PROMPT '%' + + +#endif /* _ACCONFIG_H */ diff --git a/ports/acpica/include/acconvert.h b/ports/acpica/include/acconvert.h new file mode 100644 index 0000000..cdefc68 --- /dev/null +++ b/ports/acpica/include/acconvert.h @@ -0,0 +1,312 @@ +/****************************************************************************** + * + * Module Name: acapps - common include for ACPI applications/tools + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef _ACCONVERT +#define _ACCONVERT + +/* Definitions for comment state */ + +#define ASL_COMMENT_STANDARD 1 +#define ASLCOMMENT_INLINE 2 +#define ASL_COMMENT_OPEN_PAREN 3 +#define ASL_COMMENT_CLOSE_PAREN 4 +#define ASL_COMMENT_CLOSE_BRACE 5 + +/* Definitions for comment print function*/ + +#define AML_COMMENT_STANDARD 1 +#define AMLCOMMENT_INLINE 2 +#define AML_COMMENT_END_NODE 3 +#define AML_NAMECOMMENT 4 +#define AML_COMMENT_CLOSE_BRACE 5 +#define AML_COMMENT_ENDBLK 6 +#define AML_COMMENT_INCLUDE 7 + + +#ifdef ACPI_ASL_COMPILER +/* + * cvcompiler + */ +void +CvProcessComment ( + ASL_COMMENT_STATE CurrentState, + char *StringBuffer, + int c1); + +void +CvProcessCommentType2 ( + ASL_COMMENT_STATE CurrentState, + char *StringBuffer); + +UINT32 +CvCalculateCommentLengths( + ACPI_PARSE_OBJECT *Op); + +void +CvProcessCommentState ( + char input); + +char* +CvAppendInlineComment ( + char *InlineComment, + char *ToAdd); + +void +CvAddToCommentList ( + char* ToAdd); + +void +CvPlaceComment ( + UINT8 Type, + char *CommentString); + +UINT32 +CvParseOpBlockType ( + ACPI_PARSE_OBJECT *Op); + +ACPI_COMMENT_NODE* +CvCommentNodeCalloc ( + void); + +void +CgWriteAmlDefBlockComment ( + ACPI_PARSE_OBJECT *Op); + +void +CgWriteOneAmlComment ( + ACPI_PARSE_OBJECT *Op, + char* CommentToPrint, + UINT8 InputOption); + +void +CgWriteAmlComment ( + ACPI_PARSE_OBJECT *Op); + + +/* + * cvparser + */ +void +CvInitFileTree ( + ACPI_TABLE_HEADER *Table, + UINT8 *AmlStart, + UINT32 AmlLength); + +void +CvClearOpComments ( + ACPI_PARSE_OBJECT *Op); + +ACPI_FILE_NODE* +CvFilenameExists ( + char *Filename, + ACPI_FILE_NODE *Head); + +void +CvLabelFileNode ( + ACPI_PARSE_OBJECT *Op); + +void +CvCaptureListComments ( + ACPI_PARSE_STATE *ParserState, + ACPI_COMMENT_NODE *ListHead, + ACPI_COMMENT_NODE *ListTail); + +void +CvCaptureCommentsOnly ( + ACPI_PARSE_STATE *ParserState); + +void +CvCaptureComments ( + ACPI_WALK_STATE *WalkState); + +void +CvTransferComments ( + ACPI_PARSE_OBJECT *Op); + +/* + * cvdisasm + */ +void +CvSwitchFiles ( + UINT32 level, + ACPI_PARSE_OBJECT *op); + +BOOLEAN +CvFileHasSwitched ( + ACPI_PARSE_OBJECT *Op); + + +void +CvCloseParenWriteComment ( + ACPI_PARSE_OBJECT *Op, + UINT32 Level); + +void +CvCloseBraceWriteComment ( + ACPI_PARSE_OBJECT *Op, + UINT32 Level); + +void +CvPrintOneCommentList ( + ACPI_COMMENT_NODE *CommentList, + UINT32 Level); + +void +CvPrintOneCommentType ( + ACPI_PARSE_OBJECT *Op, + UINT8 CommentType, + char* EndStr, + UINT32 Level); + + +#endif + +#endif /* _ACCONVERT */ diff --git a/ports/acpica/include/acdebug.h b/ports/acpica/include/acdebug.h new file mode 100644 index 0000000..51a85f3 --- /dev/null +++ b/ports/acpica/include/acdebug.h @@ -0,0 +1,611 @@ +/****************************************************************************** + * + * Name: acdebug.h - ACPI/AML debugger + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACDEBUG_H__ +#define __ACDEBUG_H__ + +/* The debugger is used in conjunction with the disassembler most of time */ + +#ifdef ACPI_DISASSEMBLER +#include "acdisasm.h" +#endif + + +#define ACPI_DEBUG_BUFFER_SIZE 0x4000 /* 16K buffer for return objects */ + +typedef struct acpi_db_command_info +{ + const char *Name; /* Command Name */ + UINT8 MinArgs; /* Minimum arguments required */ + +} ACPI_DB_COMMAND_INFO; + +typedef struct acpi_db_command_help +{ + UINT8 LineCount; /* Number of help lines */ + char *Invocation; /* Command Invocation */ + char *Description; /* Command Description */ + +} ACPI_DB_COMMAND_HELP; + +typedef struct acpi_db_argument_info +{ + const char *Name; /* Argument Name */ + +} ACPI_DB_ARGUMENT_INFO; + +typedef struct acpi_db_execute_walk +{ + UINT32 Count; + UINT32 MaxCount; + +} ACPI_DB_EXECUTE_WALK; + + +#define PARAM_LIST(pl) pl + +#define EX_NO_SINGLE_STEP 1 +#define EX_SINGLE_STEP 2 + + +/* + * dbxface - external debugger interfaces + */ +ACPI_DBR_DEPENDENT_RETURN_OK ( +ACPI_STATUS +AcpiDbSingleStep ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + UINT32 OpType)) + +ACPI_DBR_DEPENDENT_RETURN_VOID ( +void +AcpiDbSignalBreakPoint ( + ACPI_WALK_STATE *WalkState)) + + +/* + * dbcmds - debug commands and output routines + */ +ACPI_NAMESPACE_NODE * +AcpiDbConvertToNode ( + char *InString); + +void +AcpiDbDisplayTableInfo ( + char *TableArg); + +void +AcpiDbDisplayTemplate ( + char *BufferArg); + +void +AcpiDbUnloadAcpiTable ( + char *Name); + +void +AcpiDbSendNotify ( + char *Name, + UINT32 Value); + +void +AcpiDbDisplayInterfaces ( + char *ActionArg, + char *InterfaceNameArg); + +ACPI_STATUS +AcpiDbSleep ( + char *ObjectArg); + +void +AcpiDbTrace ( + char *EnableArg, + char *MethodArg, + char *OnceArg); + +void +AcpiDbDisplayLocks ( + void); + +void +AcpiDbDisplayResources ( + char *ObjectArg); + +ACPI_HW_DEPENDENT_RETURN_VOID ( +void +AcpiDbDisplayGpes ( + void)) + +void +AcpiDbDisplayHandlers ( + void); + +ACPI_HW_DEPENDENT_RETURN_VOID ( +void +AcpiDbGenerateGpe ( + char *GpeArg, + char *BlockArg)) + +ACPI_HW_DEPENDENT_RETURN_VOID ( +void +AcpiDbGenerateSci ( + void)) + +void +AcpiDbExecuteTest ( + char *TypeArg); + + +/* + * dbconvert - miscellaneous conversion routines + */ +ACPI_STATUS +AcpiDbHexCharToValue ( + int HexChar, + UINT8 *ReturnValue); + +ACPI_STATUS +AcpiDbConvertToPackage ( + char *String, + ACPI_OBJECT *Object); + +ACPI_STATUS +AcpiDbConvertToObject ( + ACPI_OBJECT_TYPE Type, + char *String, + ACPI_OBJECT *Object); + +UINT8 * +AcpiDbEncodePldBuffer ( + ACPI_PLD_INFO *PldInfo); + +void +AcpiDbDumpPldBuffer ( + ACPI_OBJECT *ObjDesc); + + +/* + * dbmethod - control method commands + */ +void +AcpiDbSetMethodBreakpoint ( + char *Location, + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +void +AcpiDbSetMethodCallBreakpoint ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDbSetMethodData ( + char *TypeArg, + char *IndexArg, + char *ValueArg); + +ACPI_STATUS +AcpiDbDisassembleMethod ( + char *Name); + +void +AcpiDbDisassembleAml ( + char *Statements, + ACPI_PARSE_OBJECT *Op); + +void +AcpiDbEvaluatePredefinedNames ( + void); + + +/* + * dbnames - namespace commands + */ +void +AcpiDbSetScope ( + char *Name); + +void +AcpiDbDumpNamespace ( + char *StartArg, + char *DepthArg); + +void +AcpiDbDumpNamespacePaths ( + void); + +void +AcpiDbDumpNamespaceByOwner ( + char *OwnerArg, + char *DepthArg); + +ACPI_STATUS +AcpiDbFindNameInNamespace ( + char *NameArg); + +void +AcpiDbCheckPredefinedNames ( + void); + +ACPI_STATUS +AcpiDbDisplayObjects ( + char *ObjTypeArg, + char *DisplayCountArg); + +void +AcpiDbCheckIntegrity ( + void); + +void +AcpiDbFindReferences ( + char *ObjectArg); + +void +AcpiDbGetBusInfo ( + void); + + +/* + * dbdisply - debug display commands + */ +void +AcpiDbDisplayMethodInfo ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDbDecodeAndDisplayObject ( + char *Target, + char *OutputType); + +ACPI_DBR_DEPENDENT_RETURN_VOID ( +void +AcpiDbDisplayResultObject ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState)) + +ACPI_STATUS +AcpiDbDisplayAllMethods ( + char *DisplayCountArg); + +void +AcpiDbDisplayArguments ( + void); + +void +AcpiDbDisplayLocals ( + void); + +void +AcpiDbDisplayResults ( + void); + +void +AcpiDbDisplayCallingTree ( + void); + +void +AcpiDbDisplayObjectType ( + char *ObjectArg); + +ACPI_DBR_DEPENDENT_RETURN_VOID ( +void +AcpiDbDisplayArgumentObject ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState)) + + +/* + * dbexec - debugger control method execution + */ +void +AcpiDbExecute ( + char *Name, + char **Args, + ACPI_OBJECT_TYPE *Types, + UINT32 Flags); + +void +AcpiDbCreateExecutionThread ( + char *MethodNameArg, + char **Arguments, + ACPI_OBJECT_TYPE *Types); + +void +AcpiDbCreateExecutionThreads ( + char *NumThreadsArg, + char *NumLoopsArg, + char *MethodNameArg); + +void +AcpiDbDeleteObjects ( + UINT32 Count, + ACPI_OBJECT *Objects); + +#ifdef ACPI_DBG_TRACK_ALLOCATIONS +UINT32 +AcpiDbGetCacheInfo ( + ACPI_MEMORY_LIST *Cache); +#endif + + +/* + * dbfileio - Debugger file I/O commands + */ +ACPI_OBJECT_TYPE +AcpiDbMatchArgument ( + char *UserArgument, + ACPI_DB_ARGUMENT_INFO *Arguments); + +void +AcpiDbCloseDebugFile ( + void); + +void +AcpiDbOpenDebugFile ( + char *Name); + +ACPI_STATUS +AcpiDbLoadAcpiTable ( + char *Filename); + +ACPI_STATUS +AcpiDbLoadTables ( + ACPI_NEW_TABLE_DESC *ListHead); + + +/* + * dbhistry - debugger HISTORY command + */ +void +AcpiDbAddToHistory ( + char *CommandLine); + +void +AcpiDbDisplayHistory ( + void); + +char * +AcpiDbGetFromHistory ( + char *CommandNumArg); + +char * +AcpiDbGetHistoryByIndex ( + UINT32 CommanddNum); + + +/* + * dbinput - user front-end to the AML debugger + */ +ACPI_STATUS +AcpiDbCommandDispatch ( + char *InputBuffer, + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +void ACPI_SYSTEM_XFACE +AcpiDbExecuteThread ( + void *Context); + +ACPI_STATUS +AcpiDbUserCommands ( + void); + +char * +AcpiDbGetNextToken ( + char *String, + char **Next, + ACPI_OBJECT_TYPE *ReturnType); + + +/* + * dbobject + */ +void +AcpiDbDecodeInternalObject ( + ACPI_OPERAND_OBJECT *ObjDesc); + +void +AcpiDbDisplayInternalObject ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState); + +void +AcpiDbDecodeArguments ( + ACPI_WALK_STATE *WalkState); + +void +AcpiDbDecodeLocals ( + ACPI_WALK_STATE *WalkState); + +void +AcpiDbDumpMethodInfo ( + ACPI_STATUS Status, + ACPI_WALK_STATE *WalkState); + + +/* + * dbstats - Generation and display of ACPI table statistics + */ +void +AcpiDbGenerateStatistics ( + ACPI_PARSE_OBJECT *Root, + BOOLEAN IsMethod); + +ACPI_STATUS +AcpiDbDisplayStatistics ( + char *TypeArg); + + +/* + * dbutils - AML debugger utilities + */ +void +AcpiDbSetOutputDestination ( + UINT32 Where); + +void +AcpiDbDumpExternalObject ( + ACPI_OBJECT *ObjDesc, + UINT32 Level); + +void +AcpiDbPrepNamestring ( + char *Name); + +ACPI_NAMESPACE_NODE * +AcpiDbLocalNsLookup ( + char *Name); + +void +AcpiDbUint32ToHexString ( + UINT32 Value, + char *Buffer); + +#endif /* __ACDEBUG_H__ */ diff --git a/ports/acpica/include/acdisasm.h b/ports/acpica/include/acdisasm.h new file mode 100644 index 0000000..7f7f7bf --- /dev/null +++ b/ports/acpica/include/acdisasm.h @@ -0,0 +1,1343 @@ +/****************************************************************************** + * + * Name: acdisasm.h - AML disassembler + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACDISASM_H__ +#define __ACDISASM_H__ + +#include "amlresrc.h" + + +#define BLOCK_NONE 0 +#define BLOCK_PAREN 1 +#define BLOCK_BRACE 2 +#define BLOCK_COMMA_LIST 4 +#define ACPI_DEFAULT_RESNAME *(UINT32 *) "__RD" + +/* + * Raw table data header. Used by disassembler and data table compiler. + * Do not change. + */ +#define ACPI_RAW_TABLE_DATA_HEADER "Raw Table Data" + + +typedef struct acpi_dmtable_info +{ + UINT8 Opcode; + UINT16 Offset; + char *Name; + UINT8 Flags; + +} ACPI_DMTABLE_INFO; + +/* Values for Flags field above */ + +#define DT_LENGTH 0x01 /* Field is a subtable length */ +#define DT_FLAG 0x02 /* Field is a flag value */ +#define DT_NON_ZERO 0x04 /* Field must be non-zero */ +#define DT_OPTIONAL 0x08 /* Field is optional */ +#define DT_DESCRIBES_OPTIONAL 0x10 /* Field describes an optional field (length, etc.) */ +#define DT_COUNT 0x20 /* Currently not used */ + +/* + * Values for Opcode above. + * Note: 0-7 must not change, they are used as a flag shift value. Other + * than those, new values can be added wherever appropriate. + */ +typedef enum +{ + /* Simple Data Types */ + + ACPI_DMT_FLAG0 = 0, + ACPI_DMT_FLAG1 = 1, + ACPI_DMT_FLAG2 = 2, + ACPI_DMT_FLAG3 = 3, + ACPI_DMT_FLAG4 = 4, + ACPI_DMT_FLAG5 = 5, + ACPI_DMT_FLAG6 = 6, + ACPI_DMT_FLAG7 = 7, + ACPI_DMT_FLAGS0, + ACPI_DMT_FLAGS1, + ACPI_DMT_FLAGS2, + ACPI_DMT_FLAGS4, + ACPI_DMT_FLAGS4_0, + ACPI_DMT_FLAGS4_4, + ACPI_DMT_FLAGS4_8, + ACPI_DMT_FLAGS4_12, + ACPI_DMT_FLAGS16_16, + ACPI_DMT_UINT8, + ACPI_DMT_UINT16, + ACPI_DMT_UINT24, + ACPI_DMT_UINT32, + ACPI_DMT_UINT40, + ACPI_DMT_UINT48, + ACPI_DMT_UINT56, + ACPI_DMT_UINT64, + ACPI_DMT_BUF7, + ACPI_DMT_BUF10, + ACPI_DMT_BUF12, + ACPI_DMT_BUF16, + ACPI_DMT_BUF128, + ACPI_DMT_SIG, + ACPI_DMT_STRING, + ACPI_DMT_NAME4, + ACPI_DMT_NAME6, + ACPI_DMT_NAME8, + + /* Types that are decoded to strings and miscellaneous */ + + ACPI_DMT_ACCWIDTH, + ACPI_DMT_CHKSUM, + ACPI_DMT_GAS, + ACPI_DMT_SPACEID, + ACPI_DMT_UNICODE, + ACPI_DMT_UUID, + + /* Types used only for the Data Table Compiler */ + + ACPI_DMT_BUFFER, + ACPI_DMT_RAW_BUFFER, /* Large, multiple line buffer */ + ACPI_DMT_DEVICE_PATH, + ACPI_DMT_LABEL, + ACPI_DMT_PCI_PATH, + + /* Types that are specific to particular ACPI tables */ + + ACPI_DMT_ASF, + ACPI_DMT_DMAR, + ACPI_DMT_DMAR_SCOPE, + ACPI_DMT_EINJACT, + ACPI_DMT_EINJINST, + ACPI_DMT_ERSTACT, + ACPI_DMT_ERSTINST, + ACPI_DMT_FADTPM, + ACPI_DMT_GTDT, + ACPI_DMT_HEST, + ACPI_DMT_HESTNTFY, + ACPI_DMT_HESTNTYP, + ACPI_DMT_HMAT, + ACPI_DMT_IORTMEM, + ACPI_DMT_IVRS, + ACPI_DMT_LPIT, + ACPI_DMT_MADT, + ACPI_DMT_NFIT, + ACPI_DMT_PCCT, + ACPI_DMT_PMTT, + ACPI_DMT_PPTT, + ACPI_DMT_SDEI, + ACPI_DMT_SDEV, + ACPI_DMT_SLIC, + ACPI_DMT_SRAT, + ACPI_DMT_TPM2, + + /* Special opcodes */ + + ACPI_DMT_EXTRA_TEXT, + ACPI_DMT_EXIT + +} ACPI_ENTRY_TYPES; + +typedef +void (*ACPI_DMTABLE_HANDLER) ( + ACPI_TABLE_HEADER *Table); + +typedef +ACPI_STATUS (*ACPI_CMTABLE_HANDLER) ( + void **PFieldList); + +typedef struct acpi_dmtable_data +{ + char *Signature; + ACPI_DMTABLE_INFO *TableInfo; + ACPI_DMTABLE_HANDLER TableHandler; + ACPI_CMTABLE_HANDLER CmTableHandler; + const unsigned char *Template; + +} ACPI_DMTABLE_DATA; + + +typedef struct acpi_op_walk_info +{ + ACPI_WALK_STATE *WalkState; + ACPI_PARSE_OBJECT *MappingOp; + UINT8 *PreviousAml; + UINT8 *StartAml; + UINT32 Level; + UINT32 LastLevel; + UINT32 Count; + UINT32 BitOffset; + UINT32 Flags; + UINT32 AmlOffset; + +} ACPI_OP_WALK_INFO; + +/* + * TBD - another copy of this is in asltypes.h, fix + */ +#ifndef ASL_WALK_CALLBACK_DEFINED +typedef +ACPI_STATUS (*ASL_WALK_CALLBACK) ( + ACPI_PARSE_OBJECT *Op, + UINT32 Level, + void *Context); +#define ASL_WALK_CALLBACK_DEFINED +#endif + +typedef +void (*ACPI_RESOURCE_HANDLER) ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +typedef struct acpi_resource_tag +{ + UINT32 BitIndex; + char *Tag; + +} ACPI_RESOURCE_TAG; + +/* Strings used for decoding flags to ASL keywords */ + +extern const char *AcpiGbl_WordDecode[]; +extern const char *AcpiGbl_IrqDecode[]; +extern const char *AcpiGbl_LockRule[]; +extern const char *AcpiGbl_AccessTypes[]; +extern const char *AcpiGbl_UpdateRules[]; +extern const char *AcpiGbl_MatchOps[]; + +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsf0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsf1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsf1a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsf2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsf2a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsf3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsf4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoAsfHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoBoot[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoBert[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoBgrt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoCpep[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoCpep0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoCsrt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoCsrt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoCsrt2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoCsrt2a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDbg2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDbg2Device[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDbg2Addr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDbg2Size[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDbg2Name[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDbg2OemData[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDbgp[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmar[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmarHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmarScope[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmar0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmar1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmar2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmar3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDmar4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDrtm[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDrtm0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDrtm0a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDrtm1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDrtm1a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoDrtm2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoEcdt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoEinj[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoEinj0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoErst[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoErst0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFacs[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFadt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFadt2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFadt3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFadt5[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFadt6[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFpdt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFpdtHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFpdt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoFpdt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoGas[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoGtdt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoGtdtHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoGtdt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoGtdt0a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoGtdt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHeader[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest6[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest7[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest8[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest9[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest10[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHest11[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHestNotify[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHestBank[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHpet[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoLpitHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoLpit0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoLpit1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat1a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat1b[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat1c[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmat2a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoHmatHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort0a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort1a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort3a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort3b[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort3c[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIort4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIortAcc[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIortHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIortMap[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIortPad[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrs[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrs0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrs1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrs4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrs8a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrs8b[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrs8c[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoIvrsHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt5[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt6[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt7[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt8[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt9[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt10[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt11[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt12[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt13[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt14[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadt15[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMadtHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMcfg[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMcfg0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMchi[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMpst[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMpst0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMpst0A[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMpst0B[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMpst1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMpst2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMsct[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMsct0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMtmr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoMtmr0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfitHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit2a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit3a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit5[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit6[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit6a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoNfit7[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPdtt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPmtt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPmtt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPmtt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPmtt1a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPmtt2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPmttHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPcct[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPcctHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPcct0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPcct1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPcct2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPcct3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPcct4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPdtt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPptt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPptt0a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPptt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPptt2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoPpttHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoRasf[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoRsdp1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoRsdp2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoS3pt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoS3ptHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoS3pt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoS3pt1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSbst[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdei[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdev[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdevHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdev0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdev0a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdev1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdev1a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSdev1b[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSlic[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSlit[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSpcr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSpmi[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSrat[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSratHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSrat0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSrat1[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSrat2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSrat3[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoSrat4[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoStao[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoStaoStr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoTcpaHdr[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoTcpaClient[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoTcpaServer[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoTpm2[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoTpm2a[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoTpm211[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoUefi[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoVrtc[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoVrtc0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWaet[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWdat[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWdat0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWddt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWdrt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWpbt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWpbt0[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoWsmt[]; +extern ACPI_DMTABLE_INFO AcpiDmTableInfoXenv[]; + +extern ACPI_DMTABLE_INFO AcpiDmTableInfoGeneric[][2]; + +/* + * dmtable and ahtable + */ +extern const ACPI_DMTABLE_DATA AcpiDmTableData[]; +extern const AH_TABLE Gbl_AcpiSupportedTables[]; + +UINT8 +AcpiDmGenerateChecksum ( + void *Table, + UINT32 Length, + UINT8 OriginalChecksum); + +const ACPI_DMTABLE_DATA * +AcpiDmGetTableData ( + char *Signature); + +void +AcpiDmDumpDataTable ( + ACPI_TABLE_HEADER *Table); + +ACPI_STATUS +AcpiDmDumpTable ( + UINT32 TableLength, + UINT32 TableOffset, + void *Table, + UINT32 SubtableLength, + ACPI_DMTABLE_INFO *Info); + +void +AcpiDmLineHeader ( + UINT32 Offset, + UINT32 ByteLength, + char *Name); + +void +AcpiDmLineHeader2 ( + UINT32 Offset, + UINT32 ByteLength, + char *Name, + UINT32 Value); + + +/* + * dmtbdump + */ +void +AcpiDmDumpBuffer ( + void *Table, + UINT32 BufferOffset, + UINT32 Length, + UINT32 AbsoluteOffset, + char *Header); + +void +AcpiDmDumpUnicode ( + void *Table, + UINT32 BufferOffset, + UINT32 ByteLength); + +void +AcpiDmDumpAsf ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpCpep ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpCsrt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpDbg2 ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpDmar ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpDrtm ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpEinj ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpErst ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpFadt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpFpdt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpGtdt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpHest ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpHmat ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpIort ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpIvrs ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpLpit ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpMadt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpMcfg ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpMpst ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpMsct ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpMtmr ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpNfit ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpPcct ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpPdtt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpPmtt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpPptt ( + ACPI_TABLE_HEADER *Table); + +UINT32 +AcpiDmDumpRsdp ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpRsdt ( + ACPI_TABLE_HEADER *Table); + +UINT32 +AcpiDmDumpS3pt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpSdev ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpSlic ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpSlit ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpSrat ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpStao ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpTcpa ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpTpm2 ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpVrtc ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpWdat ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpWpbt ( + ACPI_TABLE_HEADER *Table); + +void +AcpiDmDumpXsdt ( + ACPI_TABLE_HEADER *Table); + + +/* + * dmwalk + */ +void +AcpiDmDisassemble ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Origin, + UINT32 NumOpcodes); + +void +AcpiDmWalkParseTree ( + ACPI_PARSE_OBJECT *Op, + ASL_WALK_CALLBACK DescendingCallback, + ASL_WALK_CALLBACK AscendingCallback, + void *Context); + + +/* + * dmopcode + */ +void +AcpiDmDisassembleOneOp ( + ACPI_WALK_STATE *WalkState, + ACPI_OP_WALK_INFO *Info, + ACPI_PARSE_OBJECT *Op); + +UINT32 +AcpiDmListType ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmMethodFlags ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmDisplayTargetPathname ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmNotifyDescription ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmPredefinedDescription ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmFieldPredefinedDescription ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmFieldFlags ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmAddressSpace ( + UINT8 SpaceId); + +void +AcpiDmRegionFlags ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmMatchOp ( + ACPI_PARSE_OBJECT *Op); + + +/* + * dmnames + */ +UINT32 +AcpiDmDumpName ( + UINT32 Name); + +ACPI_STATUS +AcpiPsDisplayObjectPathname ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmNamestring ( + char *Name); + + +/* + * dmbuffer + */ +void +AcpiDmDisasmByteList ( + UINT32 Level, + UINT8 *ByteData, + UINT32 ByteCount); + +void +AcpiDmByteList ( + ACPI_OP_WALK_INFO *Info, + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmCheckForHardwareId ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmDecompressEisaId ( + UINT32 EncodedId); + +BOOLEAN +AcpiDmIsUuidBuffer ( + ACPI_PARSE_OBJECT *Op); + +BOOLEAN +AcpiDmIsUnicodeBuffer ( + ACPI_PARSE_OBJECT *Op); + +BOOLEAN +AcpiDmIsStringBuffer ( + ACPI_PARSE_OBJECT *Op); + +BOOLEAN +AcpiDmIsPldBuffer ( + ACPI_PARSE_OBJECT *Op); + + +/* + * dmdeferred + */ +ACPI_STATUS +AcpiDmParseDeferredOps ( + ACPI_PARSE_OBJECT *Root); + + +/* + * dmextern + */ +ACPI_STATUS +AcpiDmAddToExternalFileList ( + char *PathList); + +void +AcpiDmClearExternalFileList ( + void); + +void +AcpiDmAddOpToExternalList ( + ACPI_PARSE_OBJECT *Op, + char *Path, + UINT8 Type, + UINT32 Value, + UINT16 Flags); + +void +AcpiDmCreateSubobjectForExternal ( + UINT8 Type, + ACPI_NAMESPACE_NODE **Node, + UINT32 Value); + +void +AcpiDmAddNodeToExternalList ( + ACPI_NAMESPACE_NODE *Node, + UINT8 Type, + UINT32 Value, + UINT16 Flags); + +void +AcpiDmAddExternalListToNamespace ( + void); + +void +AcpiDmAddOneExternalToNamespace ( + char *Path, + UINT8 Type, + UINT32 Value); + +UINT32 +AcpiDmGetUnresolvedExternalMethodCount ( + void); + +void +AcpiDmClearExternalList ( + void); + +void +AcpiDmEmitExternals ( + void); + +void +AcpiDmEmitExternal ( + ACPI_PARSE_OBJECT *NameOp, + ACPI_PARSE_OBJECT *TypeOp); + +void +AcpiDmUnresolvedWarning ( + UINT8 Type); + +void +AcpiDmGetExternalsFromFile ( + void); + +void +AcpiDmMarkExternalConflict ( + ACPI_NAMESPACE_NODE *Node); + + +/* + * dmresrc + */ +void +AcpiDmDumpInteger8 ( + UINT8 Value, + const char *Name); + +void +AcpiDmDumpInteger16 ( + UINT16 Value, + const char *Name); + +void +AcpiDmDumpInteger32 ( + UINT32 Value, + const char *Name); + +void +AcpiDmDumpInteger64 ( + UINT64 Value, + const char *Name); + +void +AcpiDmResourceTemplate ( + ACPI_OP_WALK_INFO *Info, + ACPI_PARSE_OBJECT *Op, + UINT8 *ByteData, + UINT32 ByteCount); + +ACPI_STATUS +AcpiDmIsResourceTemplate ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmBitList ( + UINT16 Mask); + +void +AcpiDmDescriptorName ( + void); + + +/* + * dmresrcl + */ +void +AcpiDmWordDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmDwordDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmExtendedDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmQwordDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmMemory24Descriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmMemory32Descriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmFixedMemory32Descriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmGenericRegisterDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmInterruptDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmVendorLargeDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmGpioDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmPinFunctionDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmPinConfigDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmPinGroupDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmPinGroupFunctionDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmPinGroupConfigDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmSerialBusDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmVendorCommon ( + const char *Name, + UINT8 *ByteData, + UINT32 Length, + UINT32 Level); + + +/* + * dmresrcs + */ +void +AcpiDmIrqDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmDmaDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmFixedDmaDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmIoDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmFixedIoDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmStartDependentDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmEndDependentDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + +void +AcpiDmVendorSmallDescriptor ( + ACPI_OP_WALK_INFO *Info, + AML_RESOURCE *Resource, + UINT32 Length, + UINT32 Level); + + +/* + * dmutils + */ +void +AcpiDmDecodeAttribute ( + UINT8 Attribute); + +void +AcpiDmIndent ( + UINT32 Level); + +BOOLEAN +AcpiDmCommaIfListMember ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmCommaIfFieldMember ( + ACPI_PARSE_OBJECT *Op); + + +/* + * dmrestag + */ +void +AcpiDmFindResources ( + ACPI_PARSE_OBJECT *Root); + +void +AcpiDmCheckResourceReference ( + ACPI_PARSE_OBJECT *Op, + ACPI_WALK_STATE *WalkState); + + +/* + * dmcstyle + */ +BOOLEAN +AcpiDmCheckForSymbolicOpcode ( + ACPI_PARSE_OBJECT *Op, + ACPI_OP_WALK_INFO *Info); + +void +AcpiDmCloseOperator ( + ACPI_PARSE_OBJECT *Op); + + +/* + * dmtables + */ +ACPI_STATUS +AcpiDmProcessSwitch ( + ACPI_PARSE_OBJECT *Op); + +void +AcpiDmClearTempList( + void); + +/* + * dmtables + */ +void +AdDisassemblerHeader ( + char *Filename, + UINT8 TableType); + +#define ACPI_IS_AML_TABLE 0 +#define ACPI_IS_DATA_TABLE 1 + + +/* + * adisasm + */ +ACPI_STATUS +AdAmlDisassemble ( + BOOLEAN OutToFile, + char *Filename, + char *Prefix, + char **OutFilename); + +ACPI_STATUS +AdGetLocalTables ( + void); + +ACPI_STATUS +AdParseTable ( + ACPI_TABLE_HEADER *Table, + ACPI_OWNER_ID *OwnerId, + BOOLEAN LoadTable, + BOOLEAN External); + +ACPI_STATUS +AdDisplayTables ( + char *Filename, + ACPI_TABLE_HEADER *Table); + +ACPI_STATUS +AdDisplayStatistics ( + void); + + +/* + * dmwalk + */ +UINT32 +AcpiDmBlockType ( + ACPI_PARSE_OBJECT *Op); + + +#endif /* __ACDISASM_H__ */ diff --git a/ports/acpica/include/acdispat.h b/ports/acpica/include/acdispat.h new file mode 100644 index 0000000..fd6a1e8 --- /dev/null +++ b/ports/acpica/include/acdispat.h @@ -0,0 +1,599 @@ +/****************************************************************************** + * + * Name: acdispat.h - dispatcher (parser to interpreter interface) + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef _ACDISPAT_H_ +#define _ACDISPAT_H_ + + +#define NAMEOF_LOCAL_NTE "__L0" +#define NAMEOF_ARG_NTE "__A0" + + +/* + * dsargs - execution of dynamic arguments for static objects + */ +ACPI_STATUS +AcpiDsGetBufferFieldArguments ( + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiDsGetBankFieldArguments ( + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiDsGetRegionArguments ( + ACPI_OPERAND_OBJECT *RgnDesc); + +ACPI_STATUS +AcpiDsGetBufferArguments ( + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiDsGetPackageArguments ( + ACPI_OPERAND_OBJECT *ObjDesc); + + +/* + * dscontrol - support for execution control opcodes + */ +ACPI_STATUS +AcpiDsExecBeginControlOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +ACPI_STATUS +AcpiDsExecEndControlOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + + +/* + * dsopcode - support for late operand evaluation + */ +ACPI_STATUS +AcpiDsEvalBufferFieldOperands ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +ACPI_STATUS +AcpiDsEvalRegionOperands ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +ACPI_STATUS +AcpiDsEvalTableRegionOperands ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +ACPI_STATUS +AcpiDsEvalDataObjectOperands ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiDsEvalBankFieldOperands ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +ACPI_STATUS +AcpiDsInitializeRegion ( + ACPI_HANDLE ObjHandle); + + +/* + * dsexec - Parser/Interpreter interface, method execution callbacks + */ +ACPI_STATUS +AcpiDsGetPredicateValue ( + ACPI_WALK_STATE *WalkState, + ACPI_OPERAND_OBJECT *ResultObj); + +ACPI_STATUS +AcpiDsExecBeginOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT **OutOp); + +ACPI_STATUS +AcpiDsExecEndOp ( + ACPI_WALK_STATE *State); + + +/* + * dsfield - Parser/Interpreter interface for AML fields + */ +ACPI_STATUS +AcpiDsCreateField ( + ACPI_PARSE_OBJECT *Op, + ACPI_NAMESPACE_NODE *RegionNode, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsCreateBankField ( + ACPI_PARSE_OBJECT *Op, + ACPI_NAMESPACE_NODE *RegionNode, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsCreateIndexField ( + ACPI_PARSE_OBJECT *Op, + ACPI_NAMESPACE_NODE *RegionNode, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsCreateBufferField ( + ACPI_PARSE_OBJECT *Op, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsInitFieldObjects ( + ACPI_PARSE_OBJECT *Op, + ACPI_WALK_STATE *WalkState); + + +/* + * dsload - Parser/Interpreter interface + */ +ACPI_STATUS +AcpiDsInitCallbacks ( + ACPI_WALK_STATE *WalkState, + UINT32 PassNumber); + +/* dsload - pass 1 namespace load callbacks */ + +ACPI_STATUS +AcpiDsLoad1BeginOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT **OutOp); + +ACPI_STATUS +AcpiDsLoad1EndOp ( + ACPI_WALK_STATE *WalkState); + + +/* dsload - pass 2 namespace load callbacks */ + +ACPI_STATUS +AcpiDsLoad2BeginOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT **OutOp); + +ACPI_STATUS +AcpiDsLoad2EndOp ( + ACPI_WALK_STATE *WalkState); + + +/* + * dsmthdat - method data (locals/args) + */ +ACPI_STATUS +AcpiDsStoreObjectToLocal ( + UINT8 Type, + UINT32 Index, + ACPI_OPERAND_OBJECT *SrcDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsMethodDataGetEntry ( + UINT16 Opcode, + UINT32 Index, + ACPI_WALK_STATE *WalkState, + ACPI_OPERAND_OBJECT ***Node); + +void +AcpiDsMethodDataDeleteAll ( + ACPI_WALK_STATE *WalkState); + +BOOLEAN +AcpiDsIsMethodValue ( + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiDsMethodDataGetValue ( + UINT8 Type, + UINT32 Index, + ACPI_WALK_STATE *WalkState, + ACPI_OPERAND_OBJECT **DestDesc); + +ACPI_STATUS +AcpiDsMethodDataInitArgs ( + ACPI_OPERAND_OBJECT **Params, + UINT32 MaxParamCount, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsMethodDataGetNode ( + UINT8 Type, + UINT32 Index, + ACPI_WALK_STATE *WalkState, + ACPI_NAMESPACE_NODE **Node); + +void +AcpiDsMethodDataInit ( + ACPI_WALK_STATE *WalkState); + + +/* + * dsmethod - Parser/Interpreter interface - control method parsing + */ +ACPI_STATUS +AcpiDsAutoSerializeMethod ( + ACPI_NAMESPACE_NODE *Node, + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiDsCallControlMethod ( + ACPI_THREAD_STATE *Thread, + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +ACPI_STATUS +AcpiDsRestartControlMethod ( + ACPI_WALK_STATE *WalkState, + ACPI_OPERAND_OBJECT *ReturnDesc); + +void +AcpiDsTerminateControlMethod ( + ACPI_OPERAND_OBJECT *MethodDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsBeginMethodExecution ( + ACPI_NAMESPACE_NODE *MethodNode, + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsMethodError ( + ACPI_STATUS Status, + ACPI_WALK_STATE *WalkState); + +/* + * dsinit + */ +ACPI_STATUS +AcpiDsInitializeObjects ( + UINT32 TableIndex, + ACPI_NAMESPACE_NODE *StartNode); + + +/* + * dsobject - Parser/Interpreter interface - object initialization and conversion + */ +ACPI_STATUS +AcpiDsBuildInternalObject ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + ACPI_OPERAND_OBJECT **ObjDescPtr); + +ACPI_STATUS +AcpiDsBuildInternalBufferObj ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + UINT32 BufferLength, + ACPI_OPERAND_OBJECT **ObjDescPtr); + +ACPI_STATUS +AcpiDsBuildInternalPackageObj ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *op, + UINT32 PackageLength, + ACPI_OPERAND_OBJECT **ObjDesc); + +ACPI_STATUS +AcpiDsInitObjectFromOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + UINT16 Opcode, + ACPI_OPERAND_OBJECT **ObjDesc); + +ACPI_STATUS +AcpiDsCreateNode ( + ACPI_WALK_STATE *WalkState, + ACPI_NAMESPACE_NODE *Node, + ACPI_PARSE_OBJECT *Op); + + +/* + * dspkginit - Package object initialization + */ +ACPI_STATUS +AcpiDsInitPackageElement ( + UINT8 ObjectType, + ACPI_OPERAND_OBJECT *SourceObject, + ACPI_GENERIC_STATE *State, + void *Context); + + +/* + * dsutils - Parser/Interpreter interface utility routines + */ +void +AcpiDsClearImplicitReturn ( + ACPI_WALK_STATE *WalkState); + +BOOLEAN +AcpiDsDoImplicitReturn ( + ACPI_OPERAND_OBJECT *ReturnDesc, + ACPI_WALK_STATE *WalkState, + BOOLEAN AddReference); + +BOOLEAN +AcpiDsIsResultUsed ( + ACPI_PARSE_OBJECT *Op, + ACPI_WALK_STATE *WalkState); + +void +AcpiDsDeleteResultIfNotUsed ( + ACPI_PARSE_OBJECT *Op, + ACPI_OPERAND_OBJECT *ResultObj, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsCreateOperand ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Arg, + UINT32 ArgsRemaining); + +ACPI_STATUS +AcpiDsCreateOperands ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *FirstArg); + +ACPI_STATUS +AcpiDsResolveOperands ( + ACPI_WALK_STATE *WalkState); + +void +AcpiDsClearOperands ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsEvaluateNamePath ( + ACPI_WALK_STATE *WalkState); + + +/* + * dswscope - Scope Stack manipulation + */ +ACPI_STATUS +AcpiDsScopeStackPush ( + ACPI_NAMESPACE_NODE *Node, + ACPI_OBJECT_TYPE Type, + ACPI_WALK_STATE *WalkState); + + +ACPI_STATUS +AcpiDsScopeStackPop ( + ACPI_WALK_STATE *WalkState); + +void +AcpiDsScopeStackClear ( + ACPI_WALK_STATE *WalkState); + + +/* + * dswstate - parser WALK_STATE management routines + */ +ACPI_STATUS +AcpiDsObjStackPush ( + void *Object, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsObjStackPop ( + UINT32 PopCount, + ACPI_WALK_STATE *WalkState); + +ACPI_WALK_STATE * +AcpiDsCreateWalkState ( + ACPI_OWNER_ID OwnerId, + ACPI_PARSE_OBJECT *Origin, + ACPI_OPERAND_OBJECT *MthDesc, + ACPI_THREAD_STATE *Thread); + +ACPI_STATUS +AcpiDsInitAmlWalk ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + ACPI_NAMESPACE_NODE *MethodNode, + UINT8 *AmlStart, + UINT32 AmlLength, + ACPI_EVALUATE_INFO *Info, + UINT8 PassNumber); + +void +AcpiDsObjStackPopAndDelete ( + UINT32 PopCount, + ACPI_WALK_STATE *WalkState); + +void +AcpiDsDeleteWalkState ( + ACPI_WALK_STATE *WalkState); + +ACPI_WALK_STATE * +AcpiDsPopWalkState ( + ACPI_THREAD_STATE *Thread); + +void +AcpiDsPushWalkState ( + ACPI_WALK_STATE *WalkState, + ACPI_THREAD_STATE *Thread); + +ACPI_STATUS +AcpiDsResultStackClear ( + ACPI_WALK_STATE *WalkState); + +ACPI_WALK_STATE * +AcpiDsGetCurrentWalkState ( + ACPI_THREAD_STATE *Thread); + +ACPI_STATUS +AcpiDsResultPop ( + ACPI_OPERAND_OBJECT **Object, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiDsResultPush ( + ACPI_OPERAND_OBJECT *Object, + ACPI_WALK_STATE *WalkState); + + +/* + * dsdebug - parser debugging routines + */ +void +AcpiDsDumpMethodStack ( + ACPI_STATUS Status, + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +#endif /* _ACDISPAT_H_ */ diff --git a/ports/acpica/include/acevents.h b/ports/acpica/include/acevents.h new file mode 100644 index 0000000..3dc3660 --- /dev/null +++ b/ports/acpica/include/acevents.h @@ -0,0 +1,495 @@ +/****************************************************************************** + * + * Name: acevents.h - Event subcomponent prototypes and defines + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACEVENTS_H__ +#define __ACEVENTS_H__ + + +/* + * Conditions to trigger post enabling GPE polling: + * It is not sufficient to trigger edge-triggered GPE with specific GPE + * chips, software need to poll once after enabling. + */ +#ifdef ACPI_USE_GPE_POLLING +#define ACPI_GPE_IS_POLLING_NEEDED(__gpe__) \ + ((__gpe__)->RuntimeCount == 1 && \ + (__gpe__)->Flags & ACPI_GPE_INITIALIZED && \ + ((__gpe__)->Flags & ACPI_GPE_XRUPT_TYPE_MASK) == ACPI_GPE_EDGE_TRIGGERED) +#else +#define ACPI_GPE_IS_POLLING_NEEDED(__gpe__) FALSE +#endif + + +/* + * evevent + */ +ACPI_STATUS +AcpiEvInitializeEvents ( + void); + +ACPI_STATUS +AcpiEvInstallXruptHandlers ( + void); + +UINT32 +AcpiEvFixedEventDetect ( + void); + + +/* + * evmisc + */ +BOOLEAN +AcpiEvIsNotifyObject ( + ACPI_NAMESPACE_NODE *Node); + +UINT32 +AcpiEvGetGpeNumberIndex ( + UINT32 GpeNumber); + +ACPI_STATUS +AcpiEvQueueNotifyRequest ( + ACPI_NAMESPACE_NODE *Node, + UINT32 NotifyValue); + + +/* + * evglock - Global Lock support + */ +ACPI_STATUS +AcpiEvInitGlobalLockHandler ( + void); + +ACPI_HW_DEPENDENT_RETURN_OK ( +ACPI_STATUS +AcpiEvAcquireGlobalLock( + UINT16 Timeout)) + +ACPI_HW_DEPENDENT_RETURN_OK ( +ACPI_STATUS +AcpiEvReleaseGlobalLock( + void)) + +ACPI_STATUS +AcpiEvRemoveGlobalLockHandler ( + void); + + +/* + * evgpe - Low-level GPE support + */ +UINT32 +AcpiEvGpeDetect ( + ACPI_GPE_XRUPT_INFO *GpeXruptList); + +ACPI_STATUS +AcpiEvUpdateGpeEnableMask ( + ACPI_GPE_EVENT_INFO *GpeEventInfo); + +ACPI_STATUS +AcpiEvEnableGpe ( + ACPI_GPE_EVENT_INFO *GpeEventInfo); + +ACPI_STATUS +AcpiEvMaskGpe ( + ACPI_GPE_EVENT_INFO *GpeEventInfo, + BOOLEAN IsMasked); + +ACPI_STATUS +AcpiEvAddGpeReference ( + ACPI_GPE_EVENT_INFO *GpeEventInfo); + +ACPI_STATUS +AcpiEvRemoveGpeReference ( + ACPI_GPE_EVENT_INFO *GpeEventInfo); + +ACPI_GPE_EVENT_INFO * +AcpiEvGetGpeEventInfo ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber); + +ACPI_GPE_EVENT_INFO * +AcpiEvLowGetGpeInfo ( + UINT32 GpeNumber, + ACPI_GPE_BLOCK_INFO *GpeBlock); + +ACPI_STATUS +AcpiEvFinishGpe ( + ACPI_GPE_EVENT_INFO *GpeEventInfo); + +UINT32 +AcpiEvDetectGpe ( + ACPI_NAMESPACE_NODE *GpeDevice, + ACPI_GPE_EVENT_INFO *GpeEventInfo, + UINT32 GpeNumber); + + +/* + * evgpeblk - Upper-level GPE block support + */ +ACPI_STATUS +AcpiEvCreateGpeBlock ( + ACPI_NAMESPACE_NODE *GpeDevice, + UINT64 Address, + UINT8 SpaceId, + UINT32 RegisterCount, + UINT16 GpeBlockBaseNumber, + UINT32 InterruptNumber, + ACPI_GPE_BLOCK_INFO **ReturnGpeBlock); + +ACPI_STATUS +AcpiEvInitializeGpeBlock ( + ACPI_GPE_XRUPT_INFO *GpeXruptInfo, + ACPI_GPE_BLOCK_INFO *GpeBlock, + void *Context); + +ACPI_HW_DEPENDENT_RETURN_OK ( +ACPI_STATUS +AcpiEvDeleteGpeBlock ( + ACPI_GPE_BLOCK_INFO *GpeBlock)) + +UINT32 +AcpiEvGpeDispatch ( + ACPI_NAMESPACE_NODE *GpeDevice, + ACPI_GPE_EVENT_INFO *GpeEventInfo, + UINT32 GpeNumber); + + +/* + * evgpeinit - GPE initialization and update + */ +ACPI_STATUS +AcpiEvGpeInitialize ( + void); + +ACPI_HW_DEPENDENT_RETURN_VOID ( +void +AcpiEvUpdateGpes ( + ACPI_OWNER_ID TableOwnerId)) + +ACPI_STATUS +AcpiEvMatchGpeMethod ( + ACPI_HANDLE ObjHandle, + UINT32 Level, + void *Context, + void **ReturnValue); + + +/* + * evgpeutil - GPE utilities + */ +ACPI_STATUS +AcpiEvWalkGpeList ( + ACPI_GPE_CALLBACK GpeWalkCallback, + void *Context); + +ACPI_STATUS +AcpiEvGetGpeDevice ( + ACPI_GPE_XRUPT_INFO *GpeXruptInfo, + ACPI_GPE_BLOCK_INFO *GpeBlock, + void *Context); + +ACPI_STATUS +AcpiEvGetGpeXruptBlock ( + UINT32 InterruptNumber, + ACPI_GPE_XRUPT_INFO **GpeXruptBlock); + +ACPI_STATUS +AcpiEvDeleteGpeXrupt ( + ACPI_GPE_XRUPT_INFO *GpeXrupt); + +ACPI_STATUS +AcpiEvDeleteGpeHandlers ( + ACPI_GPE_XRUPT_INFO *GpeXruptInfo, + ACPI_GPE_BLOCK_INFO *GpeBlock, + void *Context); + + +/* + * evhandler - Address space handling + */ +ACPI_OPERAND_OBJECT * +AcpiEvFindRegionHandler ( + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_OPERAND_OBJECT *HandlerObj); + +BOOLEAN +AcpiEvHasDefaultHandler ( + ACPI_NAMESPACE_NODE *Node, + ACPI_ADR_SPACE_TYPE SpaceId); + +ACPI_STATUS +AcpiEvInstallRegionHandlers ( + void); + +ACPI_STATUS +AcpiEvInstallSpaceHandler ( + ACPI_NAMESPACE_NODE *Node, + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_ADR_SPACE_HANDLER Handler, + ACPI_ADR_SPACE_SETUP Setup, + void *Context); + + +/* + * evregion - Operation region support + */ +ACPI_STATUS +AcpiEvInitializeOpRegions ( + void); + +ACPI_STATUS +AcpiEvAddressSpaceDispatch ( + ACPI_OPERAND_OBJECT *RegionObj, + ACPI_OPERAND_OBJECT *FieldObj, + UINT32 Function, + UINT32 RegionOffset, + UINT32 BitWidth, + UINT64 *Value); + +ACPI_STATUS +AcpiEvAttachRegion ( + ACPI_OPERAND_OBJECT *HandlerObj, + ACPI_OPERAND_OBJECT *RegionObj, + BOOLEAN AcpiNsIsLocked); + +void +AcpiEvDetachRegion ( + ACPI_OPERAND_OBJECT *RegionObj, + BOOLEAN AcpiNsIsLocked); + +void +AcpiEvExecuteRegMethods ( + ACPI_NAMESPACE_NODE *Node, + ACPI_ADR_SPACE_TYPE SpaceId, + UINT32 Function); + +ACPI_STATUS +AcpiEvExecuteRegMethod ( + ACPI_OPERAND_OBJECT *RegionObj, + UINT32 Function); + + +/* + * evregini - Region initialization and setup + */ +ACPI_STATUS +AcpiEvSystemMemoryRegionSetup ( + ACPI_HANDLE Handle, + UINT32 Function, + void *HandlerContext, + void **RegionContext); + +ACPI_STATUS +AcpiEvIoSpaceRegionSetup ( + ACPI_HANDLE Handle, + UINT32 Function, + void *HandlerContext, + void **RegionContext); + +ACPI_STATUS +AcpiEvPciConfigRegionSetup ( + ACPI_HANDLE Handle, + UINT32 Function, + void *HandlerContext, + void **RegionContext); + +ACPI_STATUS +AcpiEvCmosRegionSetup ( + ACPI_HANDLE Handle, + UINT32 Function, + void *HandlerContext, + void **RegionContext); + +ACPI_STATUS +AcpiEvPciBarRegionSetup ( + ACPI_HANDLE Handle, + UINT32 Function, + void *HandlerContext, + void **RegionContext); + +ACPI_STATUS +AcpiEvDefaultRegionSetup ( + ACPI_HANDLE Handle, + UINT32 Function, + void *HandlerContext, + void **RegionContext); + +ACPI_STATUS +AcpiEvInitializeRegion ( + ACPI_OPERAND_OBJECT *RegionObj); + + +/* + * evsci - SCI (System Control Interrupt) handling/dispatch + */ +UINT32 ACPI_SYSTEM_XFACE +AcpiEvGpeXruptHandler ( + void *Context); + +UINT32 +AcpiEvSciDispatch ( + void); + +UINT32 +AcpiEvInstallSciHandler ( + void); + +ACPI_STATUS +AcpiEvRemoveAllSciHandlers ( + void); + +ACPI_HW_DEPENDENT_RETURN_VOID ( +void +AcpiEvTerminate ( + void)) + +#endif /* __ACEVENTS_H__ */ diff --git a/ports/acpica/include/acexcep.h b/ports/acpica/include/acexcep.h new file mode 100644 index 0000000..828f9fb --- /dev/null +++ b/ports/acpica/include/acexcep.h @@ -0,0 +1,470 @@ +/****************************************************************************** + * + * Name: acexcep.h - Exception codes returned by the ACPI subsystem + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACEXCEP_H__ +#define __ACEXCEP_H__ + + +/* This module contains all possible exception codes for ACPI_STATUS */ + +/* + * Exception code classes + */ +#define AE_CODE_ENVIRONMENTAL 0x0000 /* General ACPICA environment */ +#define AE_CODE_PROGRAMMER 0x1000 /* External ACPICA interface caller */ +#define AE_CODE_ACPI_TABLES 0x2000 /* ACPI tables */ +#define AE_CODE_AML 0x3000 /* From executing AML code */ +#define AE_CODE_CONTROL 0x4000 /* Internal control codes */ + +#define AE_CODE_MAX 0x4000 +#define AE_CODE_MASK 0xF000 + +/* + * Macros to insert the exception code classes + */ +#define EXCEP_ENV(code) ((ACPI_STATUS) (code | AE_CODE_ENVIRONMENTAL)) +#define EXCEP_PGM(code) ((ACPI_STATUS) (code | AE_CODE_PROGRAMMER)) +#define EXCEP_TBL(code) ((ACPI_STATUS) (code | AE_CODE_ACPI_TABLES)) +#define EXCEP_AML(code) ((ACPI_STATUS) (code | AE_CODE_AML)) +#define EXCEP_CTL(code) ((ACPI_STATUS) (code | AE_CODE_CONTROL)) + +/* + * Exception info table. The "Description" field is used only by the + * ACPICA help application (acpihelp). + */ +typedef struct acpi_exception_info +{ + char *Name; + +#ifdef ACPI_HELP_APP + char *Description; +#endif +} ACPI_EXCEPTION_INFO; + +#ifdef ACPI_HELP_APP +#define EXCEP_TXT(Name,Description) {Name, Description} +#else +#define EXCEP_TXT(Name,Description) {Name} +#endif + + +/* + * Success is always zero, failure is non-zero + */ +#define ACPI_SUCCESS(a) (!(a)) +#define ACPI_FAILURE(a) (a) + +#define AE_OK (ACPI_STATUS) 0x0000 + +/* + * Environmental exceptions + */ +#define AE_ERROR EXCEP_ENV (0x0001) +#define AE_NO_ACPI_TABLES EXCEP_ENV (0x0002) +#define AE_NO_NAMESPACE EXCEP_ENV (0x0003) +#define AE_NO_MEMORY EXCEP_ENV (0x0004) +#define AE_NOT_FOUND EXCEP_ENV (0x0005) +#define AE_NOT_EXIST EXCEP_ENV (0x0006) +#define AE_ALREADY_EXISTS EXCEP_ENV (0x0007) +#define AE_TYPE EXCEP_ENV (0x0008) +#define AE_NULL_OBJECT EXCEP_ENV (0x0009) +#define AE_NULL_ENTRY EXCEP_ENV (0x000A) +#define AE_BUFFER_OVERFLOW EXCEP_ENV (0x000B) +#define AE_STACK_OVERFLOW EXCEP_ENV (0x000C) +#define AE_STACK_UNDERFLOW EXCEP_ENV (0x000D) +#define AE_NOT_IMPLEMENTED EXCEP_ENV (0x000E) +#define AE_SUPPORT EXCEP_ENV (0x000F) +#define AE_LIMIT EXCEP_ENV (0x0010) +#define AE_TIME EXCEP_ENV (0x0011) +#define AE_ACQUIRE_DEADLOCK EXCEP_ENV (0x0012) +#define AE_RELEASE_DEADLOCK EXCEP_ENV (0x0013) +#define AE_NOT_ACQUIRED EXCEP_ENV (0x0014) +#define AE_ALREADY_ACQUIRED EXCEP_ENV (0x0015) +#define AE_NO_HARDWARE_RESPONSE EXCEP_ENV (0x0016) +#define AE_NO_GLOBAL_LOCK EXCEP_ENV (0x0017) +#define AE_ABORT_METHOD EXCEP_ENV (0x0018) +#define AE_SAME_HANDLER EXCEP_ENV (0x0019) +#define AE_NO_HANDLER EXCEP_ENV (0x001A) +#define AE_OWNER_ID_LIMIT EXCEP_ENV (0x001B) +#define AE_NOT_CONFIGURED EXCEP_ENV (0x001C) +#define AE_ACCESS EXCEP_ENV (0x001D) +#define AE_IO_ERROR EXCEP_ENV (0x001E) +#define AE_NUMERIC_OVERFLOW EXCEP_ENV (0x001F) +#define AE_HEX_OVERFLOW EXCEP_ENV (0x0020) +#define AE_DECIMAL_OVERFLOW EXCEP_ENV (0x0021) +#define AE_OCTAL_OVERFLOW EXCEP_ENV (0x0022) +#define AE_END_OF_TABLE EXCEP_ENV (0x0023) + +#define AE_CODE_ENV_MAX 0x0023 + + +/* + * Programmer exceptions + */ +#define AE_BAD_PARAMETER EXCEP_PGM (0x0001) +#define AE_BAD_CHARACTER EXCEP_PGM (0x0002) +#define AE_BAD_PATHNAME EXCEP_PGM (0x0003) +#define AE_BAD_DATA EXCEP_PGM (0x0004) +#define AE_BAD_HEX_CONSTANT EXCEP_PGM (0x0005) +#define AE_BAD_OCTAL_CONSTANT EXCEP_PGM (0x0006) +#define AE_BAD_DECIMAL_CONSTANT EXCEP_PGM (0x0007) +#define AE_MISSING_ARGUMENTS EXCEP_PGM (0x0008) +#define AE_BAD_ADDRESS EXCEP_PGM (0x0009) + +#define AE_CODE_PGM_MAX 0x0009 + + +/* + * Acpi table exceptions + */ +#define AE_BAD_SIGNATURE EXCEP_TBL (0x0001) +#define AE_BAD_HEADER EXCEP_TBL (0x0002) +#define AE_BAD_CHECKSUM EXCEP_TBL (0x0003) +#define AE_BAD_VALUE EXCEP_TBL (0x0004) +#define AE_INVALID_TABLE_LENGTH EXCEP_TBL (0x0005) + +#define AE_CODE_TBL_MAX 0x0005 + + +/* + * AML exceptions. These are caused by problems with + * the actual AML byte stream + */ +#define AE_AML_BAD_OPCODE EXCEP_AML (0x0001) +#define AE_AML_NO_OPERAND EXCEP_AML (0x0002) +#define AE_AML_OPERAND_TYPE EXCEP_AML (0x0003) +#define AE_AML_OPERAND_VALUE EXCEP_AML (0x0004) +#define AE_AML_UNINITIALIZED_LOCAL EXCEP_AML (0x0005) +#define AE_AML_UNINITIALIZED_ARG EXCEP_AML (0x0006) +#define AE_AML_UNINITIALIZED_ELEMENT EXCEP_AML (0x0007) +#define AE_AML_NUMERIC_OVERFLOW EXCEP_AML (0x0008) +#define AE_AML_REGION_LIMIT EXCEP_AML (0x0009) +#define AE_AML_BUFFER_LIMIT EXCEP_AML (0x000A) +#define AE_AML_PACKAGE_LIMIT EXCEP_AML (0x000B) +#define AE_AML_DIVIDE_BY_ZERO EXCEP_AML (0x000C) +#define AE_AML_BAD_NAME EXCEP_AML (0x000D) +#define AE_AML_NAME_NOT_FOUND EXCEP_AML (0x000E) +#define AE_AML_INTERNAL EXCEP_AML (0x000F) +#define AE_AML_INVALID_SPACE_ID EXCEP_AML (0x0010) +#define AE_AML_STRING_LIMIT EXCEP_AML (0x0011) +#define AE_AML_NO_RETURN_VALUE EXCEP_AML (0x0012) +#define AE_AML_METHOD_LIMIT EXCEP_AML (0x0013) +#define AE_AML_NOT_OWNER EXCEP_AML (0x0014) +#define AE_AML_MUTEX_ORDER EXCEP_AML (0x0015) +#define AE_AML_MUTEX_NOT_ACQUIRED EXCEP_AML (0x0016) +#define AE_AML_INVALID_RESOURCE_TYPE EXCEP_AML (0x0017) +#define AE_AML_INVALID_INDEX EXCEP_AML (0x0018) +#define AE_AML_REGISTER_LIMIT EXCEP_AML (0x0019) +#define AE_AML_NO_WHILE EXCEP_AML (0x001A) +#define AE_AML_ALIGNMENT EXCEP_AML (0x001B) +#define AE_AML_NO_RESOURCE_END_TAG EXCEP_AML (0x001C) +#define AE_AML_BAD_RESOURCE_VALUE EXCEP_AML (0x001D) +#define AE_AML_CIRCULAR_REFERENCE EXCEP_AML (0x001E) +#define AE_AML_BAD_RESOURCE_LENGTH EXCEP_AML (0x001F) +#define AE_AML_ILLEGAL_ADDRESS EXCEP_AML (0x0020) +#define AE_AML_LOOP_TIMEOUT EXCEP_AML (0x0021) +#define AE_AML_UNINITIALIZED_NODE EXCEP_AML (0x0022) +#define AE_AML_TARGET_TYPE EXCEP_AML (0x0023) + +#define AE_CODE_AML_MAX 0x0023 + + +/* + * Internal exceptions used for control + */ +#define AE_CTRL_RETURN_VALUE EXCEP_CTL (0x0001) +#define AE_CTRL_PENDING EXCEP_CTL (0x0002) +#define AE_CTRL_TERMINATE EXCEP_CTL (0x0003) +#define AE_CTRL_TRUE EXCEP_CTL (0x0004) +#define AE_CTRL_FALSE EXCEP_CTL (0x0005) +#define AE_CTRL_DEPTH EXCEP_CTL (0x0006) +#define AE_CTRL_END EXCEP_CTL (0x0007) +#define AE_CTRL_TRANSFER EXCEP_CTL (0x0008) +#define AE_CTRL_BREAK EXCEP_CTL (0x0009) +#define AE_CTRL_CONTINUE EXCEP_CTL (0x000A) +#define AE_CTRL_PARSE_CONTINUE EXCEP_CTL (0x000B) +#define AE_CTRL_PARSE_PENDING EXCEP_CTL (0x000C) + +#define AE_CODE_CTRL_MAX 0x000C + + +/* Exception strings for AcpiFormatException */ + +#ifdef ACPI_DEFINE_EXCEPTION_TABLE + +/* + * String versions of the exception codes above + * These strings must match the corresponding defines exactly + */ +static const ACPI_EXCEPTION_INFO AcpiGbl_ExceptionNames_Env[] = +{ + EXCEP_TXT ("AE_OK", "No error"), + EXCEP_TXT ("AE_ERROR", "Unspecified error"), + EXCEP_TXT ("AE_NO_ACPI_TABLES", "ACPI tables could not be found"), + EXCEP_TXT ("AE_NO_NAMESPACE", "A namespace has not been loaded"), + EXCEP_TXT ("AE_NO_MEMORY", "Insufficient dynamic memory"), + EXCEP_TXT ("AE_NOT_FOUND", "A requested entity is not found"), + EXCEP_TXT ("AE_NOT_EXIST", "A required entity does not exist"), + EXCEP_TXT ("AE_ALREADY_EXISTS", "An entity already exists"), + EXCEP_TXT ("AE_TYPE", "The object type is incorrect"), + EXCEP_TXT ("AE_NULL_OBJECT", "A required object was missing"), + EXCEP_TXT ("AE_NULL_ENTRY", "The requested object does not exist"), + EXCEP_TXT ("AE_BUFFER_OVERFLOW", "The buffer provided is too small"), + EXCEP_TXT ("AE_STACK_OVERFLOW", "An internal stack overflowed"), + EXCEP_TXT ("AE_STACK_UNDERFLOW", "An internal stack underflowed"), + EXCEP_TXT ("AE_NOT_IMPLEMENTED", "The feature is not implemented"), + EXCEP_TXT ("AE_SUPPORT", "The feature is not supported"), + EXCEP_TXT ("AE_LIMIT", "A predefined limit was exceeded"), + EXCEP_TXT ("AE_TIME", "A time limit or timeout expired"), + EXCEP_TXT ("AE_ACQUIRE_DEADLOCK", "Internal error, attempt was made to acquire a mutex in improper order"), + EXCEP_TXT ("AE_RELEASE_DEADLOCK", "Internal error, attempt was made to release a mutex in improper order"), + EXCEP_TXT ("AE_NOT_ACQUIRED", "An attempt to release a mutex or Global Lock without a previous acquire"), + EXCEP_TXT ("AE_ALREADY_ACQUIRED", "Internal error, attempt was made to acquire a mutex twice"), + EXCEP_TXT ("AE_NO_HARDWARE_RESPONSE", "Hardware did not respond after an I/O operation"), + EXCEP_TXT ("AE_NO_GLOBAL_LOCK", "There is no FACS Global Lock"), + EXCEP_TXT ("AE_ABORT_METHOD", "A control method was aborted"), + EXCEP_TXT ("AE_SAME_HANDLER", "Attempt was made to install the same handler that is already installed"), + EXCEP_TXT ("AE_NO_HANDLER", "A handler for the operation is not installed"), + EXCEP_TXT ("AE_OWNER_ID_LIMIT", "There are no more Owner IDs available for ACPI tables or control methods"), + EXCEP_TXT ("AE_NOT_CONFIGURED", "The interface is not part of the current subsystem configuration"), + EXCEP_TXT ("AE_ACCESS", "Permission denied for the requested operation"), + EXCEP_TXT ("AE_IO_ERROR", "An I/O error occurred"), + EXCEP_TXT ("AE_NUMERIC_OVERFLOW", "Overflow during string-to-integer conversion"), + EXCEP_TXT ("AE_HEX_OVERFLOW", "Overflow during ASCII hex-to-binary conversion"), + EXCEP_TXT ("AE_DECIMAL_OVERFLOW", "Overflow during ASCII decimal-to-binary conversion"), + EXCEP_TXT ("AE_OCTAL_OVERFLOW", "Overflow during ASCII octal-to-binary conversion"), + EXCEP_TXT ("AE_END_OF_TABLE", "Reached the end of table") +}; + +static const ACPI_EXCEPTION_INFO AcpiGbl_ExceptionNames_Pgm[] = +{ + EXCEP_TXT (NULL, NULL), + EXCEP_TXT ("AE_BAD_PARAMETER", "A parameter is out of range or invalid"), + EXCEP_TXT ("AE_BAD_CHARACTER", "An invalid character was found in a name"), + EXCEP_TXT ("AE_BAD_PATHNAME", "An invalid character was found in a pathname"), + EXCEP_TXT ("AE_BAD_DATA", "A package or buffer contained incorrect data"), + EXCEP_TXT ("AE_BAD_HEX_CONSTANT", "Invalid character in a Hex constant"), + EXCEP_TXT ("AE_BAD_OCTAL_CONSTANT", "Invalid character in an Octal constant"), + EXCEP_TXT ("AE_BAD_DECIMAL_CONSTANT", "Invalid character in a Decimal constant"), + EXCEP_TXT ("AE_MISSING_ARGUMENTS", "Too few arguments were passed to a control method"), + EXCEP_TXT ("AE_BAD_ADDRESS", "An illegal null I/O address") +}; + +static const ACPI_EXCEPTION_INFO AcpiGbl_ExceptionNames_Tbl[] = +{ + EXCEP_TXT (NULL, NULL), + EXCEP_TXT ("AE_BAD_SIGNATURE", "An ACPI table has an invalid signature"), + EXCEP_TXT ("AE_BAD_HEADER", "Invalid field in an ACPI table header"), + EXCEP_TXT ("AE_BAD_CHECKSUM", "An ACPI table checksum is not correct"), + EXCEP_TXT ("AE_BAD_VALUE", "An invalid value was found in a table"), + EXCEP_TXT ("AE_INVALID_TABLE_LENGTH", "The FADT or FACS has improper length") +}; + +static const ACPI_EXCEPTION_INFO AcpiGbl_ExceptionNames_Aml[] = +{ + EXCEP_TXT (NULL, NULL), + EXCEP_TXT ("AE_AML_BAD_OPCODE", "Invalid AML opcode encountered"), + EXCEP_TXT ("AE_AML_NO_OPERAND", "A required operand is missing"), + EXCEP_TXT ("AE_AML_OPERAND_TYPE", "An operand of an incorrect type was encountered"), + EXCEP_TXT ("AE_AML_OPERAND_VALUE", "The operand had an inappropriate or invalid value"), + EXCEP_TXT ("AE_AML_UNINITIALIZED_LOCAL", "Method tried to use an uninitialized local variable"), + EXCEP_TXT ("AE_AML_UNINITIALIZED_ARG", "Method tried to use an uninitialized argument"), + EXCEP_TXT ("AE_AML_UNINITIALIZED_ELEMENT", "Method tried to use an empty package element"), + EXCEP_TXT ("AE_AML_NUMERIC_OVERFLOW", "Overflow during BCD conversion or other"), + EXCEP_TXT ("AE_AML_REGION_LIMIT", "Tried to access beyond the end of an Operation Region"), + EXCEP_TXT ("AE_AML_BUFFER_LIMIT", "Tried to access beyond the end of a buffer"), + EXCEP_TXT ("AE_AML_PACKAGE_LIMIT", "Tried to access beyond the end of a package"), + EXCEP_TXT ("AE_AML_DIVIDE_BY_ZERO", "During execution of AML Divide operator"), + EXCEP_TXT ("AE_AML_BAD_NAME", "An ACPI name contains invalid character(s)"), + EXCEP_TXT ("AE_AML_NAME_NOT_FOUND", "Could not resolve a named reference"), + EXCEP_TXT ("AE_AML_INTERNAL", "An internal error within the interprete"), + EXCEP_TXT ("AE_AML_INVALID_SPACE_ID", "An Operation Region SpaceID is invalid"), + EXCEP_TXT ("AE_AML_STRING_LIMIT", "String is longer than 200 characters"), + EXCEP_TXT ("AE_AML_NO_RETURN_VALUE", "A method did not return a required value"), + EXCEP_TXT ("AE_AML_METHOD_LIMIT", "A control method reached the maximum reentrancy limit of 255"), + EXCEP_TXT ("AE_AML_NOT_OWNER", "A thread tried to release a mutex that it does not own"), + EXCEP_TXT ("AE_AML_MUTEX_ORDER", "Mutex SyncLevel release mismatch"), + EXCEP_TXT ("AE_AML_MUTEX_NOT_ACQUIRED", "Attempt to release a mutex that was not previously acquired"), + EXCEP_TXT ("AE_AML_INVALID_RESOURCE_TYPE", "Invalid resource type in resource list"), + EXCEP_TXT ("AE_AML_INVALID_INDEX", "Invalid Argx or Localx (x too large)"), + EXCEP_TXT ("AE_AML_REGISTER_LIMIT", "Bank value or Index value beyond range of register"), + EXCEP_TXT ("AE_AML_NO_WHILE", "Break or Continue without a While"), + EXCEP_TXT ("AE_AML_ALIGNMENT", "Non-aligned memory transfer on platform that does not support this"), + EXCEP_TXT ("AE_AML_NO_RESOURCE_END_TAG", "No End Tag in a resource list"), + EXCEP_TXT ("AE_AML_BAD_RESOURCE_VALUE", "Invalid value of a resource element"), + EXCEP_TXT ("AE_AML_CIRCULAR_REFERENCE", "Two references refer to each other"), + EXCEP_TXT ("AE_AML_BAD_RESOURCE_LENGTH", "The length of a Resource Descriptor in the AML is incorrect"), + EXCEP_TXT ("AE_AML_ILLEGAL_ADDRESS", "A memory, I/O, or PCI configuration address is invalid"), + EXCEP_TXT ("AE_AML_LOOP_TIMEOUT", "An AML While loop exceeded the maximum execution time"), + EXCEP_TXT ("AE_AML_UNINITIALIZED_NODE", "A namespace node is uninitialized or unresolved"), + EXCEP_TXT ("AE_AML_TARGET_TYPE", "A target operand of an incorrect type was encountered") +}; + +static const ACPI_EXCEPTION_INFO AcpiGbl_ExceptionNames_Ctrl[] = +{ + EXCEP_TXT (NULL, NULL), + EXCEP_TXT ("AE_CTRL_RETURN_VALUE", "A Method returned a value"), + EXCEP_TXT ("AE_CTRL_PENDING", "Method is calling another method"), + EXCEP_TXT ("AE_CTRL_TERMINATE", "Terminate the executing method"), + EXCEP_TXT ("AE_CTRL_TRUE", "An If or While predicate result"), + EXCEP_TXT ("AE_CTRL_FALSE", "An If or While predicate result"), + EXCEP_TXT ("AE_CTRL_DEPTH", "Maximum search depth has been reached"), + EXCEP_TXT ("AE_CTRL_END", "An If or While predicate is false"), + EXCEP_TXT ("AE_CTRL_TRANSFER", "Transfer control to called method"), + EXCEP_TXT ("AE_CTRL_BREAK", "A Break has been executed"), + EXCEP_TXT ("AE_CTRL_CONTINUE", "A Continue has been executed"), + EXCEP_TXT ("AE_CTRL_PARSE_CONTINUE", "Used to skip over bad opcodes"), + EXCEP_TXT ("AE_CTRL_PARSE_PENDING", "Used to implement AML While loops") +}; + +#endif /* EXCEPTION_TABLE */ + +#endif /* __ACEXCEP_H__ */ diff --git a/ports/acpica/include/acglobal.h b/ports/acpica/include/acglobal.h new file mode 100644 index 0000000..3c2a4ed --- /dev/null +++ b/ports/acpica/include/acglobal.h @@ -0,0 +1,533 @@ +/****************************************************************************** + * + * Name: acglobal.h - Declarations for global variables + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACGLOBAL_H__ +#define __ACGLOBAL_H__ + + +/***************************************************************************** + * + * Globals related to the incoming ACPI tables + * + ****************************************************************************/ + +/* Master list of all ACPI tables that were found in the RSDT/XSDT */ + +ACPI_GLOBAL (ACPI_TABLE_LIST, AcpiGbl_RootTableList); + +/* DSDT information. Used to check for DSDT corruption */ + +ACPI_GLOBAL (ACPI_TABLE_HEADER *, AcpiGbl_DSDT); +ACPI_GLOBAL (ACPI_TABLE_HEADER, AcpiGbl_OriginalDsdtHeader); +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_DsdtIndex, ACPI_INVALID_TABLE_INDEX); +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_FacsIndex, ACPI_INVALID_TABLE_INDEX); +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_XFacsIndex, ACPI_INVALID_TABLE_INDEX); +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_FadtIndex, ACPI_INVALID_TABLE_INDEX); + +#if (!ACPI_REDUCED_HARDWARE) +ACPI_GLOBAL (ACPI_TABLE_FACS *, AcpiGbl_FACS); + +#endif /* !ACPI_REDUCED_HARDWARE */ + +/* These addresses are calculated from the FADT Event Block addresses */ + +ACPI_GLOBAL (ACPI_GENERIC_ADDRESS, AcpiGbl_XPm1aStatus); +ACPI_GLOBAL (ACPI_GENERIC_ADDRESS, AcpiGbl_XPm1aEnable); + +ACPI_GLOBAL (ACPI_GENERIC_ADDRESS, AcpiGbl_XPm1bStatus); +ACPI_GLOBAL (ACPI_GENERIC_ADDRESS, AcpiGbl_XPm1bEnable); + +/* + * Handle both ACPI 1.0 and ACPI 2.0+ Integer widths. The integer width is + * determined by the revision of the DSDT: If the DSDT revision is less than + * 2, use only the lower 32 bits of the internal 64-bit Integer. + */ +ACPI_GLOBAL (UINT8, AcpiGbl_IntegerBitWidth); +ACPI_GLOBAL (UINT8, AcpiGbl_IntegerByteWidth); +ACPI_GLOBAL (UINT8, AcpiGbl_IntegerNybbleWidth); + + +/***************************************************************************** + * + * Mutual exclusion within the ACPICA subsystem + * + ****************************************************************************/ + +/* + * Predefined mutex objects. This array contains the + * actual OS mutex handles, indexed by the local ACPI_MUTEX_HANDLEs. + * (The table maps local handles to the real OS handles) + */ +ACPI_GLOBAL (ACPI_MUTEX_INFO, AcpiGbl_MutexInfo[ACPI_NUM_MUTEX]); + +/* + * Global lock mutex is an actual AML mutex object + * Global lock semaphore works in conjunction with the actual global lock + * Global lock spinlock is used for "pending" handshake + */ +ACPI_GLOBAL (ACPI_OPERAND_OBJECT *, AcpiGbl_GlobalLockMutex); +ACPI_GLOBAL (ACPI_SEMAPHORE, AcpiGbl_GlobalLockSemaphore); +ACPI_GLOBAL (ACPI_SPINLOCK, AcpiGbl_GlobalLockPendingLock); +ACPI_GLOBAL (UINT16, AcpiGbl_GlobalLockHandle); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_GlobalLockAcquired); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_GlobalLockPresent); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_GlobalLockPending); + +/* + * Spinlocks are used for interfaces that can be possibly called at + * interrupt level + */ +ACPI_GLOBAL (ACPI_SPINLOCK, AcpiGbl_GpeLock); /* For GPE data structs and registers */ +ACPI_GLOBAL (ACPI_SPINLOCK, AcpiGbl_HardwareLock); /* For ACPI H/W except GPE registers */ +ACPI_GLOBAL (ACPI_SPINLOCK, AcpiGbl_ReferenceCountLock); + +/* Mutex for _OSI support */ + +ACPI_GLOBAL (ACPI_MUTEX, AcpiGbl_OsiMutex); + +/* Reader/Writer lock is used for namespace walk and dynamic table unload */ + +ACPI_GLOBAL (ACPI_RW_LOCK, AcpiGbl_NamespaceRwLock); + + +/***************************************************************************** + * + * Miscellaneous globals + * + ****************************************************************************/ + +/* Object caches */ + +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_NamespaceCache); +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_StateCache); +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_PsNodeCache); +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_PsNodeExtCache); +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_OperandCache); + +/* System */ + +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_StartupFlags, 0); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_Shutdown, TRUE); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_EarlyInitialization, TRUE); + +/* Global handlers */ + +ACPI_GLOBAL (ACPI_GLOBAL_NOTIFY_HANDLER,AcpiGbl_GlobalNotify[2]); +ACPI_GLOBAL (ACPI_EXCEPTION_HANDLER, AcpiGbl_ExceptionHandler); +ACPI_GLOBAL (ACPI_INIT_HANDLER, AcpiGbl_InitHandler); +ACPI_GLOBAL (ACPI_TABLE_HANDLER, AcpiGbl_TableHandler); +ACPI_GLOBAL (void *, AcpiGbl_TableHandlerContext); +ACPI_GLOBAL (ACPI_INTERFACE_HANDLER, AcpiGbl_InterfaceHandler); +ACPI_GLOBAL (ACPI_SCI_HANDLER_INFO *, AcpiGbl_SciHandlerList); + +/* Owner ID support */ + +ACPI_GLOBAL (UINT32, AcpiGbl_OwnerIdMask[ACPI_NUM_OWNERID_MASKS]); +ACPI_GLOBAL (UINT8, AcpiGbl_LastOwnerIdIndex); +ACPI_GLOBAL (UINT8, AcpiGbl_NextOwnerIdOffset); + +/* Initialization sequencing */ + +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_NamespaceInitialized, FALSE); + +/* Miscellaneous */ + +ACPI_GLOBAL (UINT32, AcpiGbl_OriginalMode); +ACPI_GLOBAL (UINT32, AcpiGbl_NsLookupCount); +ACPI_GLOBAL (UINT32, AcpiGbl_PsFindCount); +ACPI_GLOBAL (UINT16, AcpiGbl_Pm1EnableRegisterSave); +ACPI_GLOBAL (UINT8, AcpiGbl_DebuggerConfiguration); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_StepToNextCall); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_AcpiHardwarePresent); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_EventsInitialized); +ACPI_GLOBAL (ACPI_INTERFACE_INFO *, AcpiGbl_SupportedInterfaces); +ACPI_GLOBAL (ACPI_ADDRESS_RANGE *, AcpiGbl_AddressRangeList[ACPI_ADDRESS_RANGE_MAX]); + +/* Other miscellaneous, declared and initialized in utglobal */ + +extern const char *AcpiGbl_SleepStateNames[ACPI_S_STATE_COUNT]; +extern const char *AcpiGbl_LowestDstateNames[ACPI_NUM_SxW_METHODS]; +extern const char *AcpiGbl_HighestDstateNames[ACPI_NUM_SxD_METHODS]; +extern const char *AcpiGbl_RegionTypes[ACPI_NUM_PREDEFINED_REGIONS]; +extern const char AcpiGbl_LowerHexDigits[]; +extern const char AcpiGbl_UpperHexDigits[]; +extern const ACPI_OPCODE_INFO AcpiGbl_AmlOpInfo[AML_NUM_OPCODES]; + +/* Lists for tracking memory allocations (debug only) */ + +#ifdef ACPI_DBG_TRACK_ALLOCATIONS +ACPI_GLOBAL (ACPI_MEMORY_LIST *, AcpiGbl_GlobalList); +ACPI_GLOBAL (ACPI_MEMORY_LIST *, AcpiGbl_NsNodeList); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DisplayFinalMemStats); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DisableMemTracking); +#endif + + +/***************************************************************************** + * + * ACPI Namespace + * + ****************************************************************************/ + +#if !defined (ACPI_NO_METHOD_EXECUTION) || defined (ACPI_CONSTANT_EVAL_ONLY) +#define NUM_PREDEFINED_NAMES 10 +#else +#define NUM_PREDEFINED_NAMES 9 +#endif + +ACPI_GLOBAL (ACPI_NAMESPACE_NODE, AcpiGbl_RootNodeStruct); +ACPI_GLOBAL (ACPI_NAMESPACE_NODE *, AcpiGbl_RootNode); +ACPI_GLOBAL (ACPI_NAMESPACE_NODE *, AcpiGbl_FadtGpeDevice); +ACPI_GLOBAL (ACPI_OPERAND_OBJECT *, AcpiGbl_ModuleCodeList); + +extern const UINT8 AcpiGbl_NsProperties [ACPI_NUM_NS_TYPES]; +extern const ACPI_PREDEFINED_NAMES AcpiGbl_PreDefinedNames [NUM_PREDEFINED_NAMES]; + +#ifdef ACPI_DEBUG_OUTPUT +ACPI_GLOBAL (UINT32, AcpiGbl_CurrentNodeCount); +ACPI_GLOBAL (UINT32, AcpiGbl_CurrentNodeSize); +ACPI_GLOBAL (UINT32, AcpiGbl_MaxConcurrentNodeCount); +ACPI_GLOBAL (ACPI_SIZE *, AcpiGbl_EntryStackPointer); +ACPI_GLOBAL (ACPI_SIZE *, AcpiGbl_LowestStackPointer); +ACPI_GLOBAL (UINT32, AcpiGbl_DeepestNesting); +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_NestingLevel, 0); +#endif + + +/***************************************************************************** + * + * Interpreter/Parser globals + * + ****************************************************************************/ + +/* Control method single step flag */ + +ACPI_GLOBAL (UINT8, AcpiGbl_CmSingleStep); +ACPI_GLOBAL (ACPI_THREAD_STATE *, AcpiGbl_CurrentWalkList); +ACPI_INIT_GLOBAL (ACPI_PARSE_OBJECT, *AcpiGbl_CurrentScope, NULL); + +/* ASL/ASL+ converter */ + +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_CaptureComments, FALSE); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_LastListHead, NULL); + + +/***************************************************************************** + * + * Hardware globals + * + ****************************************************************************/ + +extern ACPI_BIT_REGISTER_INFO AcpiGbl_BitRegisterInfo[ACPI_NUM_BITREG]; +ACPI_GLOBAL (UINT8, AcpiGbl_SleepTypeA); +ACPI_GLOBAL (UINT8, AcpiGbl_SleepTypeB); + + +/***************************************************************************** + * + * Event and GPE globals + * + ****************************************************************************/ + +#if (!ACPI_REDUCED_HARDWARE) +ACPI_GLOBAL (UINT8, AcpiGbl_AllGpesInitialized); +ACPI_GLOBAL (ACPI_GPE_XRUPT_INFO *, AcpiGbl_GpeXruptListHead); +ACPI_GLOBAL (ACPI_GPE_BLOCK_INFO *, AcpiGbl_GpeFadtBlocks[ACPI_MAX_GPE_BLOCKS]); +ACPI_GLOBAL (ACPI_GBL_EVENT_HANDLER, AcpiGbl_GlobalEventHandler); +ACPI_GLOBAL (void *, AcpiGbl_GlobalEventHandlerContext); +ACPI_GLOBAL (ACPI_FIXED_EVENT_HANDLER, AcpiGbl_FixedEventHandlers[ACPI_NUM_FIXED_EVENTS]); +extern ACPI_FIXED_EVENT_INFO AcpiGbl_FixedEventInfo[ACPI_NUM_FIXED_EVENTS]; +#endif /* !ACPI_REDUCED_HARDWARE */ + + +/***************************************************************************** + * + * Debug support + * + ****************************************************************************/ + +/* Event counters */ + +ACPI_GLOBAL (UINT32, AcpiMethodCount); +ACPI_GLOBAL (UINT32, AcpiGpeCount); +ACPI_GLOBAL (UINT32, AcpiSciCount); +ACPI_GLOBAL (UINT32, AcpiFixedEventCount[ACPI_NUM_FIXED_EVENTS]); + +/* Dynamic control method tracing mechanism */ + +ACPI_GLOBAL (UINT32, AcpiGbl_OriginalDbgLevel); +ACPI_GLOBAL (UINT32, AcpiGbl_OriginalDbgLayer); + + +/***************************************************************************** + * + * Debugger and Disassembler + * + ****************************************************************************/ + +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_DbOutputFlags, ACPI_DB_CONSOLE_OUTPUT); + + +#ifdef ACPI_DISASSEMBLER + +/* Do not disassemble buffers to resource descriptors */ + +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_NoResourceDisassembly, FALSE); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_IgnoreNoopOperator, FALSE); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_CstyleDisassembly, TRUE); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_ForceAmlDisassembly, FALSE); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_DmOpt_Verbose, TRUE); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_DmEmitExternalOpcodes, FALSE); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_DoDisassemblerOptimizations, TRUE); +ACPI_INIT_GLOBAL (ACPI_PARSE_OBJECT_LIST, *AcpiGbl_TempListHead, NULL); + +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DmOpt_Disasm); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DmOpt_Listing); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_NumExternalMethods); +ACPI_GLOBAL (UINT32, AcpiGbl_ResolvedExternalMethods); +ACPI_GLOBAL (ACPI_EXTERNAL_LIST *, AcpiGbl_ExternalList); +ACPI_GLOBAL (ACPI_EXTERNAL_FILE *, AcpiGbl_ExternalFileList); +#endif + +#ifdef ACPI_DEBUGGER +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_AbortMethod, FALSE); +ACPI_INIT_GLOBAL (ACPI_THREAD_ID, AcpiGbl_DbThreadId, ACPI_INVALID_THREAD_ID); + +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DbOpt_NoIniMethods); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DbOpt_NoRegionSupport); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DbOutputToFile); +ACPI_GLOBAL (char *, AcpiGbl_DbBuffer); +ACPI_GLOBAL (char *, AcpiGbl_DbFilename); +ACPI_GLOBAL (UINT32, AcpiGbl_DbDebugLevel); +ACPI_GLOBAL (UINT32, AcpiGbl_DbConsoleDebugLevel); +ACPI_GLOBAL (ACPI_NAMESPACE_NODE *, AcpiGbl_DbScopeNode); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DbTerminateLoop); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_DbThreadsTerminated); +ACPI_GLOBAL (char *, AcpiGbl_DbArgs[ACPI_DEBUGGER_MAX_ARGS]); +ACPI_GLOBAL (ACPI_OBJECT_TYPE, AcpiGbl_DbArgTypes[ACPI_DEBUGGER_MAX_ARGS]); + +/* These buffers should all be the same size */ + +ACPI_GLOBAL (char, AcpiGbl_DbParsedBuf[ACPI_DB_LINE_BUFFER_SIZE]); +ACPI_GLOBAL (char, AcpiGbl_DbScopeBuf[ACPI_DB_LINE_BUFFER_SIZE]); +ACPI_GLOBAL (char, AcpiGbl_DbDebugFilename[ACPI_DB_LINE_BUFFER_SIZE]); + +/* Statistics globals */ + +ACPI_GLOBAL (UINT16, AcpiGbl_ObjTypeCount[ACPI_TOTAL_TYPES]); +ACPI_GLOBAL (UINT16, AcpiGbl_NodeTypeCount[ACPI_TOTAL_TYPES]); +ACPI_GLOBAL (UINT16, AcpiGbl_ObjTypeCountMisc); +ACPI_GLOBAL (UINT16, AcpiGbl_NodeTypeCountMisc); +ACPI_GLOBAL (UINT32, AcpiGbl_NumNodes); +ACPI_GLOBAL (UINT32, AcpiGbl_NumObjects); +#endif /* ACPI_DEBUGGER */ + +#if defined (ACPI_DISASSEMBLER) || defined (ACPI_ASL_COMPILER) +ACPI_GLOBAL (const char, *AcpiGbl_PldPanelList[]); +ACPI_GLOBAL (const char, *AcpiGbl_PldVerticalPositionList[]); +ACPI_GLOBAL (const char, *AcpiGbl_PldHorizontalPositionList[]); +ACPI_GLOBAL (const char, *AcpiGbl_PldShapeList[]); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_DisasmFlag, FALSE); +#endif + + +/***************************************************************************** + * + * ACPICA application-specific globals + * + ****************************************************************************/ + +/* ASL-to-ASL+ conversion utility (implemented within the iASL compiler) */ + +#ifdef ACPI_ASL_COMPILER +ACPI_INIT_GLOBAL (char *, AcpiGbl_CurrentInlineComment, NULL); +ACPI_INIT_GLOBAL (char *, AcpiGbl_CurrentEndNodeComment, NULL); +ACPI_INIT_GLOBAL (char *, AcpiGbl_CurrentOpenBraceComment, NULL); +ACPI_INIT_GLOBAL (char *, AcpiGbl_CurrentCloseBraceComment, NULL); + +ACPI_INIT_GLOBAL (char *, AcpiGbl_RootFilename, NULL); +ACPI_INIT_GLOBAL (char *, AcpiGbl_CurrentFilename, NULL); +ACPI_INIT_GLOBAL (char *, AcpiGbl_CurrentParentFilename, NULL); +ACPI_INIT_GLOBAL (char *, AcpiGbl_CurrentIncludeFilename, NULL); + +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_DefBlkCommentListHead, NULL); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_DefBlkCommentListTail, NULL); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_RegCommentListHead, NULL); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_RegCommentListTail, NULL); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_IncCommentListHead, NULL); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_IncCommentListTail, NULL); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_EndBlkCommentListHead, NULL); +ACPI_INIT_GLOBAL (ACPI_COMMENT_NODE, *AcpiGbl_EndBlkCommentListTail, NULL); + +ACPI_INIT_GLOBAL (ACPI_COMMENT_ADDR_NODE, *AcpiGbl_CommentAddrListHead, NULL); +ACPI_INIT_GLOBAL (ACPI_FILE_NODE, *AcpiGbl_FileTreeRoot, NULL); + +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_RegCommentCache); +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_CommentAddrCache); +ACPI_GLOBAL (ACPI_CACHE_T *, AcpiGbl_FileCache); + +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_DebugAslConversion, FALSE); +ACPI_INIT_GLOBAL (ACPI_FILE, AcpiGbl_ConvDebugFile, NULL); +ACPI_GLOBAL (char, AcpiGbl_TableSig[4]); +#endif + +#ifdef ACPI_APPLICATION +ACPI_INIT_GLOBAL (ACPI_FILE, AcpiGbl_DebugFile, NULL); +ACPI_INIT_GLOBAL (ACPI_FILE, AcpiGbl_OutputFile, NULL); +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_DebugTimeout, FALSE); + +/* Print buffer */ + +ACPI_GLOBAL (ACPI_SPINLOCK, AcpiGbl_PrintLock); /* For print buffer */ +ACPI_GLOBAL (char, AcpiGbl_PrintBuffer[1024]); +#endif /* ACPI_APPLICATION */ + +#endif /* __ACGLOBAL_H__ */ diff --git a/ports/acpica/include/achware.h b/ports/acpica/include/achware.h new file mode 100644 index 0000000..1c43926 --- /dev/null +++ b/ports/acpica/include/achware.h @@ -0,0 +1,335 @@ +/****************************************************************************** + * + * Name: achware.h -- hardware specific interfaces + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACHWARE_H__ +#define __ACHWARE_H__ + + +/* Values for the _SST predefined method */ + +#define ACPI_SST_INDICATOR_OFF 0 +#define ACPI_SST_WORKING 1 +#define ACPI_SST_WAKING 2 +#define ACPI_SST_SLEEPING 3 +#define ACPI_SST_SLEEP_CONTEXT 4 + + +/* + * hwacpi - high level functions + */ +ACPI_STATUS +AcpiHwSetMode ( + UINT32 Mode); + +UINT32 +AcpiHwGetMode ( + void); + + +/* + * hwregs - ACPI Register I/O + */ +ACPI_STATUS +AcpiHwValidateRegister ( + ACPI_GENERIC_ADDRESS *Reg, + UINT8 MaxBitWidth, + UINT64 *Address); + +ACPI_STATUS +AcpiHwRead ( + UINT64 *Value, + ACPI_GENERIC_ADDRESS *Reg); + +ACPI_STATUS +AcpiHwWrite ( + UINT64 Value, + ACPI_GENERIC_ADDRESS *Reg); + +ACPI_BIT_REGISTER_INFO * +AcpiHwGetBitRegisterInfo ( + UINT32 RegisterId); + +ACPI_STATUS +AcpiHwWritePm1Control ( + UINT32 Pm1aControl, + UINT32 Pm1bControl); + +ACPI_STATUS +AcpiHwRegisterRead ( + UINT32 RegisterId, + UINT32 *ReturnValue); + +ACPI_STATUS +AcpiHwRegisterWrite ( + UINT32 RegisterId, + UINT32 Value); + +ACPI_STATUS +AcpiHwClearAcpiStatus ( + void); + + +/* + * hwsleep - sleep/wake support (Legacy sleep registers) + */ +ACPI_STATUS +AcpiHwLegacySleep ( + UINT8 SleepState); + +ACPI_STATUS +AcpiHwLegacyWakePrep ( + UINT8 SleepState); + +ACPI_STATUS +AcpiHwLegacyWake ( + UINT8 SleepState); + + +/* + * hwesleep - sleep/wake support (Extended FADT-V5 sleep registers) + */ +void +AcpiHwExecuteSleepMethod ( + char *MethodName, + UINT32 IntegerArgument); + +ACPI_STATUS +AcpiHwExtendedSleep ( + UINT8 SleepState); + +ACPI_STATUS +AcpiHwExtendedWakePrep ( + UINT8 SleepState); + +ACPI_STATUS +AcpiHwExtendedWake ( + UINT8 SleepState); + + +/* + * hwvalid - Port I/O with validation + */ +ACPI_STATUS +AcpiHwReadPort ( + ACPI_IO_ADDRESS Address, + UINT32 *Value, + UINT32 Width); + +ACPI_STATUS +AcpiHwWritePort ( + ACPI_IO_ADDRESS Address, + UINT32 Value, + UINT32 Width); + + +/* + * hwgpe - GPE support + */ +UINT32 +AcpiHwGetGpeRegisterBit ( + ACPI_GPE_EVENT_INFO *GpeEventInfo); + +ACPI_STATUS +AcpiHwLowSetGpe ( + ACPI_GPE_EVENT_INFO *GpeEventInfo, + UINT32 Action); + +ACPI_STATUS +AcpiHwDisableGpeBlock ( + ACPI_GPE_XRUPT_INFO *GpeXruptInfo, + ACPI_GPE_BLOCK_INFO *GpeBlock, + void *Context); + +ACPI_STATUS +AcpiHwClearGpe ( + ACPI_GPE_EVENT_INFO *GpeEventInfo); + +ACPI_STATUS +AcpiHwClearGpeBlock ( + ACPI_GPE_XRUPT_INFO *GpeXruptInfo, + ACPI_GPE_BLOCK_INFO *GpeBlock, + void *Context); + +ACPI_STATUS +AcpiHwGetGpeStatus ( + ACPI_GPE_EVENT_INFO *GpeEventInfo, + ACPI_EVENT_STATUS *EventStatus); + +ACPI_STATUS +AcpiHwDisableAllGpes ( + void); + +ACPI_STATUS +AcpiHwEnableAllRuntimeGpes ( + void); + +ACPI_STATUS +AcpiHwEnableAllWakeupGpes ( + void); + +ACPI_STATUS +AcpiHwEnableRuntimeGpeBlock ( + ACPI_GPE_XRUPT_INFO *GpeXruptInfo, + ACPI_GPE_BLOCK_INFO *GpeBlock, + void *Context); + + +/* + * hwpci - PCI configuration support + */ +ACPI_STATUS +AcpiHwDerivePciId ( + ACPI_PCI_ID *PciId, + ACPI_HANDLE RootPciDevice, + ACPI_HANDLE PciRegion); + + +#endif /* __ACHWARE_H__ */ diff --git a/ports/acpica/include/acinterp.h b/ports/acpica/include/acinterp.h new file mode 100644 index 0000000..fde066d --- /dev/null +++ b/ports/acpica/include/acinterp.h @@ -0,0 +1,861 @@ +/****************************************************************************** + * + * Name: acinterp.h - Interpreter subcomponent prototypes and defines + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACINTERP_H__ +#define __ACINTERP_H__ + + +#define ACPI_WALK_OPERANDS (&(WalkState->Operands [WalkState->NumOperands -1])) + +/* Macros for tables used for debug output */ + +#define ACPI_EXD_OFFSET(f) (UINT8) ACPI_OFFSET (ACPI_OPERAND_OBJECT,f) +#define ACPI_EXD_NSOFFSET(f) (UINT8) ACPI_OFFSET (ACPI_NAMESPACE_NODE,f) +#define ACPI_EXD_TABLE_SIZE(name) (sizeof(name) / sizeof (ACPI_EXDUMP_INFO)) + +/* + * If possible, pack the following structures to byte alignment, since we + * don't care about performance for debug output. Two cases where we cannot + * pack the structures: + * + * 1) Hardware does not support misaligned memory transfers + * 2) Compiler does not support pointers within packed structures + */ +#if (!defined(ACPI_MISALIGNMENT_NOT_SUPPORTED) && !defined(ACPI_PACKED_POINTERS_NOT_SUPPORTED)) +#pragma pack(1) +#endif + +typedef const struct acpi_exdump_info +{ + UINT8 Opcode; + UINT8 Offset; + const char *Name; + +} ACPI_EXDUMP_INFO; + +/* Values for the Opcode field above */ + +#define ACPI_EXD_INIT 0 +#define ACPI_EXD_TYPE 1 +#define ACPI_EXD_UINT8 2 +#define ACPI_EXD_UINT16 3 +#define ACPI_EXD_UINT32 4 +#define ACPI_EXD_UINT64 5 +#define ACPI_EXD_LITERAL 6 +#define ACPI_EXD_POINTER 7 +#define ACPI_EXD_ADDRESS 8 +#define ACPI_EXD_STRING 9 +#define ACPI_EXD_BUFFER 10 +#define ACPI_EXD_PACKAGE 11 +#define ACPI_EXD_FIELD 12 +#define ACPI_EXD_REFERENCE 13 +#define ACPI_EXD_LIST 14 /* Operand object list */ +#define ACPI_EXD_HDLR_LIST 15 /* Address Handler list */ +#define ACPI_EXD_RGN_LIST 16 /* Region list */ +#define ACPI_EXD_NODE 17 /* Namespace Node */ + +/* restore default alignment */ + +#pragma pack() + + +/* + * exconvrt - object conversion + */ +ACPI_STATUS +AcpiExConvertToInteger ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT **ResultDesc, + UINT32 ImplicitConversion); + +ACPI_STATUS +AcpiExConvertToBuffer ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT **ResultDesc); + +ACPI_STATUS +AcpiExConvertToString ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT **ResultDesc, + UINT32 Type); + +/* Types for ->String conversion */ + +#define ACPI_EXPLICIT_BYTE_COPY 0x00000000 +#define ACPI_EXPLICIT_CONVERT_HEX 0x00000001 +#define ACPI_IMPLICIT_CONVERT_HEX 0x00000002 +#define ACPI_EXPLICIT_CONVERT_DECIMAL 0x00000003 + +ACPI_STATUS +AcpiExConvertToTargetType ( + ACPI_OBJECT_TYPE DestinationType, + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT **ResultDesc, + ACPI_WALK_STATE *WalkState); + + +/* + * exdebug - AML debug object + */ +void +AcpiExDoDebugObject ( + ACPI_OPERAND_OBJECT *SourceDesc, + UINT32 Level, + UINT32 Index); + +void +AcpiExStartTraceMethod ( + ACPI_NAMESPACE_NODE *MethodNode, + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState); + +void +AcpiExStopTraceMethod ( + ACPI_NAMESPACE_NODE *MethodNode, + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState); + +void +AcpiExStartTraceOpcode ( + ACPI_PARSE_OBJECT *Op, + ACPI_WALK_STATE *WalkState); + +void +AcpiExStopTraceOpcode ( + ACPI_PARSE_OBJECT *Op, + ACPI_WALK_STATE *WalkState); + +void +AcpiExTracePoint ( + ACPI_TRACE_EVENT_TYPE Type, + BOOLEAN Begin, + UINT8 *Aml, + char *Pathname); + + +/* + * exfield - ACPI AML (p-code) execution - field manipulation + */ +ACPI_STATUS +AcpiExCommonBufferSetup ( + ACPI_OPERAND_OBJECT *ObjDesc, + UINT32 BufferLength, + UINT32 *DatumCount); + +ACPI_STATUS +AcpiExWriteWithUpdateRule ( + ACPI_OPERAND_OBJECT *ObjDesc, + UINT64 Mask, + UINT64 FieldValue, + UINT32 FieldDatumByteOffset); + +void +AcpiExGetBufferDatum( + UINT64 *Datum, + void *Buffer, + UINT32 BufferLength, + UINT32 ByteGranularity, + UINT32 BufferOffset); + +void +AcpiExSetBufferDatum ( + UINT64 MergedDatum, + void *Buffer, + UINT32 BufferLength, + UINT32 ByteGranularity, + UINT32 BufferOffset); + +ACPI_STATUS +AcpiExReadDataFromField ( + ACPI_WALK_STATE *WalkState, + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT **RetBufferDesc); + +ACPI_STATUS +AcpiExWriteDataToField ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT **ResultDesc); + + +/* + * exfldio - low level field I/O + */ +ACPI_STATUS +AcpiExExtractFromField ( + ACPI_OPERAND_OBJECT *ObjDesc, + void *Buffer, + UINT32 BufferLength); + +ACPI_STATUS +AcpiExInsertIntoField ( + ACPI_OPERAND_OBJECT *ObjDesc, + void *Buffer, + UINT32 BufferLength); + +ACPI_STATUS +AcpiExAccessRegion ( + ACPI_OPERAND_OBJECT *ObjDesc, + UINT32 FieldDatumByteOffset, + UINT64 *Value, + UINT32 ReadWrite); + + +/* + * exmisc - misc support routines + */ +ACPI_STATUS +AcpiExGetObjectReference ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT **ReturnDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExConcatTemplate ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT *ObjDesc2, + ACPI_OPERAND_OBJECT **ActualReturnDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExDoConcatenate ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT *ObjDesc2, + ACPI_OPERAND_OBJECT **ActualReturnDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExDoLogicalNumericOp ( + UINT16 Opcode, + UINT64 Integer0, + UINT64 Integer1, + BOOLEAN *LogicalResult); + +ACPI_STATUS +AcpiExDoLogicalOp ( + UINT16 Opcode, + ACPI_OPERAND_OBJECT *Operand0, + ACPI_OPERAND_OBJECT *Operand1, + BOOLEAN *LogicalResult); + +UINT64 +AcpiExDoMathOp ( + UINT16 Opcode, + UINT64 Operand0, + UINT64 Operand1); + +ACPI_STATUS +AcpiExCreateMutex ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExCreateProcessor ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExCreatePowerResource ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExCreateRegion ( + UINT8 *AmlStart, + UINT32 AmlLength, + UINT8 RegionSpace, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExCreateEvent ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExCreateAlias ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExCreateMethod ( + UINT8 *AmlStart, + UINT32 AmlLength, + ACPI_WALK_STATE *WalkState); + + +/* + * exconfig - dynamic table load/unload + */ +ACPI_STATUS +AcpiExLoadOp ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_OPERAND_OBJECT *Target, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExLoadTableOp ( + ACPI_WALK_STATE *WalkState, + ACPI_OPERAND_OBJECT **ReturnDesc); + +ACPI_STATUS +AcpiExUnloadTable ( + ACPI_OPERAND_OBJECT *DdbHandle); + + +/* + * exmutex - mutex support + */ +ACPI_STATUS +AcpiExAcquireMutex ( + ACPI_OPERAND_OBJECT *TimeDesc, + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExAcquireMutexObject ( + UINT16 Timeout, + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_THREAD_ID ThreadId); + +ACPI_STATUS +AcpiExReleaseMutex ( + ACPI_OPERAND_OBJECT *ObjDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExReleaseMutexObject ( + ACPI_OPERAND_OBJECT *ObjDesc); + +void +AcpiExReleaseAllMutexes ( + ACPI_THREAD_STATE *Thread); + +void +AcpiExUnlinkMutex ( + ACPI_OPERAND_OBJECT *ObjDesc); + + +/* + * exprep - ACPI AML execution - prep utilities + */ +ACPI_STATUS +AcpiExPrepCommonFieldObject ( + ACPI_OPERAND_OBJECT *ObjDesc, + UINT8 FieldFlags, + UINT8 FieldAttribute, + UINT32 FieldBitPosition, + UINT32 FieldBitLength); + +ACPI_STATUS +AcpiExPrepFieldValue ( + ACPI_CREATE_FIELD_INFO *Info); + + +/* + * exsystem - Interface to OS services + */ +ACPI_STATUS +AcpiExSystemDoNotifyOp ( + ACPI_OPERAND_OBJECT *Value, + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiExSystemDoSleep( + UINT64 Time); + +ACPI_STATUS +AcpiExSystemDoStall ( + UINT32 Time); + +ACPI_STATUS +AcpiExSystemSignalEvent( + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiExSystemWaitEvent( + ACPI_OPERAND_OBJECT *Time, + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiExSystemResetEvent( + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiExSystemWaitSemaphore ( + ACPI_SEMAPHORE Semaphore, + UINT16 Timeout); + +ACPI_STATUS +AcpiExSystemWaitMutex ( + ACPI_MUTEX Mutex, + UINT16 Timeout); + +/* + * exoparg1 - ACPI AML execution, 1 operand + */ +ACPI_STATUS +AcpiExOpcode_0A_0T_1R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_1A_0T_0R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_1A_0T_1R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_1A_1T_1R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_1A_1T_0R ( + ACPI_WALK_STATE *WalkState); + +/* + * exoparg2 - ACPI AML execution, 2 operands + */ +ACPI_STATUS +AcpiExOpcode_2A_0T_0R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_2A_0T_1R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_2A_1T_1R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_2A_2T_1R ( + ACPI_WALK_STATE *WalkState); + + +/* + * exoparg3 - ACPI AML execution, 3 operands + */ +ACPI_STATUS +AcpiExOpcode_3A_0T_0R ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExOpcode_3A_1T_1R ( + ACPI_WALK_STATE *WalkState); + + +/* + * exoparg6 - ACPI AML execution, 6 operands + */ +ACPI_STATUS +AcpiExOpcode_6A_0T_1R ( + ACPI_WALK_STATE *WalkState); + + +/* + * exresolv - Object resolution and get value functions + */ +ACPI_STATUS +AcpiExResolveToValue ( + ACPI_OPERAND_OBJECT **StackPtr, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExResolveMultiple ( + ACPI_WALK_STATE *WalkState, + ACPI_OPERAND_OBJECT *Operand, + ACPI_OBJECT_TYPE *ReturnType, + ACPI_OPERAND_OBJECT **ReturnDesc); + + +/* + * exresnte - resolve namespace node + */ +ACPI_STATUS +AcpiExResolveNodeToValue ( + ACPI_NAMESPACE_NODE **StackPtr, + ACPI_WALK_STATE *WalkState); + + +/* + * exresop - resolve operand to value + */ +ACPI_STATUS +AcpiExResolveOperands ( + UINT16 Opcode, + ACPI_OPERAND_OBJECT **StackPtr, + ACPI_WALK_STATE *WalkState); + + +/* + * exdump - Interpreter debug output routines + */ +void +AcpiExDumpOperand ( + ACPI_OPERAND_OBJECT *ObjDesc, + UINT32 Depth); + +void +AcpiExDumpOperands ( + ACPI_OPERAND_OBJECT **Operands, + const char *OpcodeName, + UINT32 NumOpcodes); + +void +AcpiExDumpObjectDescriptor ( + ACPI_OPERAND_OBJECT *Object, + UINT32 Flags); + +void +AcpiExDumpNamespaceNode ( + ACPI_NAMESPACE_NODE *Node, + UINT32 Flags); + + +/* + * exnames - AML namestring support + */ +ACPI_STATUS +AcpiExGetNameString ( + ACPI_OBJECT_TYPE DataType, + UINT8 *InAmlAddress, + char **OutNameString, + UINT32 *OutNameLength); + + +/* + * exstore - Object store support + */ +ACPI_STATUS +AcpiExStore ( + ACPI_OPERAND_OBJECT *ValDesc, + ACPI_OPERAND_OBJECT *DestDesc, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExStoreObjectToNode ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_NAMESPACE_NODE *Node, + ACPI_WALK_STATE *WalkState, + UINT8 ImplicitConversion); + + +/* + * exstoren - resolve/store object + */ +ACPI_STATUS +AcpiExResolveObject ( + ACPI_OPERAND_OBJECT **SourceDescPtr, + ACPI_OBJECT_TYPE TargetType, + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiExStoreObjectToObject ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT *DestDesc, + ACPI_OPERAND_OBJECT **NewDesc, + ACPI_WALK_STATE *WalkState); + + +/* + * exstorob - store object - buffer/string + */ +ACPI_STATUS +AcpiExStoreBufferToBuffer ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT *TargetDesc); + +ACPI_STATUS +AcpiExStoreStringToString ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT *TargetDesc); + + +/* + * excopy - object copy + */ +ACPI_STATUS +AcpiExCopyIntegerToIndexField ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT *TargetDesc); + +ACPI_STATUS +AcpiExCopyIntegerToBankField ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT *TargetDesc); + +ACPI_STATUS +AcpiExCopyDataToNamedField ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_NAMESPACE_NODE *Node); + +ACPI_STATUS +AcpiExCopyIntegerToBufferField ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT *TargetDesc); + + +/* + * exutils - interpreter/scanner utilities + */ +void +AcpiExEnterInterpreter ( + void); + +void +AcpiExExitInterpreter ( + void); + +BOOLEAN +AcpiExTruncateFor32bitTable ( + ACPI_OPERAND_OBJECT *ObjDesc); + +void +AcpiExAcquireGlobalLock ( + UINT32 Rule); + +void +AcpiExReleaseGlobalLock ( + UINT32 Rule); + +void +AcpiExEisaIdToString ( + char *Dest, + UINT64 CompressedId); + +void +AcpiExIntegerToString ( + char *Dest, + UINT64 Value); + +void +AcpiExPciClsToString ( + char *Dest, + UINT8 ClassCode[3]); + +BOOLEAN +AcpiIsValidSpaceId ( + UINT8 SpaceId); + + +/* + * exregion - default OpRegion handlers + */ +ACPI_STATUS +AcpiExSystemMemorySpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +ACPI_STATUS +AcpiExSystemIoSpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +ACPI_STATUS +AcpiExPciConfigSpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +ACPI_STATUS +AcpiExCmosSpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +ACPI_STATUS +AcpiExPciBarSpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +ACPI_STATUS +AcpiExEmbeddedControllerSpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +ACPI_STATUS +AcpiExSmBusSpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + + +ACPI_STATUS +AcpiExDataTableSpaceHandler ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +#endif /* __INTERP_H__ */ diff --git a/ports/acpica/include/aclocal.h b/ports/acpica/include/aclocal.h new file mode 100644 index 0000000..f487a4e --- /dev/null +++ b/ports/acpica/include/aclocal.h @@ -0,0 +1,1651 @@ +/****************************************************************************** + * + * Name: aclocal.h - Internal data types used across the ACPI subsystem + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACLOCAL_H__ +#define __ACLOCAL_H__ + + +/* acpisrc:StructDefs -- for acpisrc conversion */ + +#define ACPI_SERIALIZED 0xFF + +typedef UINT32 ACPI_MUTEX_HANDLE; +#define ACPI_GLOBAL_LOCK (ACPI_SEMAPHORE) (-1) + +/* Total number of aml opcodes defined */ + +#define AML_NUM_OPCODES 0x83 + + +/* Forward declarations */ + +struct acpi_walk_state; +struct acpi_obj_mutex; +union acpi_parse_object; + + +/***************************************************************************** + * + * Mutex typedefs and structs + * + ****************************************************************************/ + + +/* + * Predefined handles for the mutex objects used within the subsystem + * All mutex objects are automatically created by AcpiUtMutexInitialize. + * + * The acquire/release ordering protocol is implied via this list. Mutexes + * with a lower value must be acquired before mutexes with a higher value. + * + * NOTE: any changes here must be reflected in the AcpiGbl_MutexNames + * table below also! + */ +#define ACPI_MTX_INTERPRETER 0 /* AML Interpreter, main lock */ +#define ACPI_MTX_NAMESPACE 1 /* ACPI Namespace */ +#define ACPI_MTX_TABLES 2 /* Data for ACPI tables */ +#define ACPI_MTX_EVENTS 3 /* Data for ACPI events */ +#define ACPI_MTX_CACHES 4 /* Internal caches, general purposes */ +#define ACPI_MTX_MEMORY 5 /* Debug memory tracking lists */ + +#define ACPI_MAX_MUTEX 5 +#define ACPI_NUM_MUTEX (ACPI_MAX_MUTEX+1) + + +/* Lock structure for reader/writer interfaces */ + +typedef struct acpi_rw_lock +{ + ACPI_MUTEX WriterMutex; + ACPI_MUTEX ReaderMutex; + UINT32 NumReaders; + +} ACPI_RW_LOCK; + + +/* + * Predefined handles for spinlocks used within the subsystem. + * These spinlocks are created by AcpiUtMutexInitialize + */ +#define ACPI_LOCK_GPES 0 +#define ACPI_LOCK_HARDWARE 1 + +#define ACPI_MAX_LOCK 1 +#define ACPI_NUM_LOCK (ACPI_MAX_LOCK+1) + + +/* This Thread ID means that the mutex is not in use (unlocked) */ + +#define ACPI_MUTEX_NOT_ACQUIRED ((ACPI_THREAD_ID) -1) + +/* This Thread ID means an invalid thread ID */ + +#ifdef ACPI_OS_INVALID_THREAD_ID +#define ACPI_INVALID_THREAD_ID ACPI_OS_INVALID_THREAD_ID +#else +#define ACPI_INVALID_THREAD_ID ((ACPI_THREAD_ID) 0xFFFFFFFF) +#endif + +/* Table for the global mutexes */ + +typedef struct acpi_mutex_info +{ + ACPI_MUTEX Mutex; + UINT32 UseCount; + ACPI_THREAD_ID ThreadId; + +} ACPI_MUTEX_INFO; + + +/* Lock flag parameter for various interfaces */ + +#define ACPI_MTX_DO_NOT_LOCK 0 +#define ACPI_MTX_LOCK 1 + + +/* Field access granularities */ + +#define ACPI_FIELD_BYTE_GRANULARITY 1 +#define ACPI_FIELD_WORD_GRANULARITY 2 +#define ACPI_FIELD_DWORD_GRANULARITY 4 +#define ACPI_FIELD_QWORD_GRANULARITY 8 + + +#define ACPI_ENTRY_NOT_FOUND NULL + + +/***************************************************************************** + * + * Namespace typedefs and structs + * + ****************************************************************************/ + +/* Operational modes of the AML interpreter/scanner */ + +typedef enum +{ + ACPI_IMODE_LOAD_PASS1 = 0x01, + ACPI_IMODE_LOAD_PASS2 = 0x02, + ACPI_IMODE_EXECUTE = 0x03 + +} ACPI_INTERPRETER_MODE; + + +/* + * The Namespace Node describes a named object that appears in the AML. + * DescriptorType is used to differentiate between internal descriptors. + * + * The node is optimized for both 32-bit and 64-bit platforms: + * 20 bytes for the 32-bit case, 32 bytes for the 64-bit case. + * + * Note: The DescriptorType and Type fields must appear in the identical + * position in both the ACPI_NAMESPACE_NODE and ACPI_OPERAND_OBJECT + * structures. + */ +typedef struct acpi_namespace_node +{ + union acpi_operand_object *Object; /* Interpreter object */ + UINT8 DescriptorType; /* Differentiate object descriptor types */ + UINT8 Type; /* ACPI Type associated with this name */ + UINT8 Flags; /* Miscellaneous flags */ + ACPI_OWNER_ID OwnerId; /* Node creator */ + ACPI_NAME_UNION Name; /* ACPI Name, always 4 chars per ACPI spec */ + struct acpi_namespace_node *Parent; /* Parent node */ + struct acpi_namespace_node *Child; /* First child */ + struct acpi_namespace_node *Peer; /* First peer */ + + /* + * The following fields are used by the ASL compiler and disassembler only + */ +#ifdef ACPI_LARGE_NAMESPACE_NODE + union acpi_parse_object *Op; + void *MethodLocals; + void *MethodArgs; + UINT32 Value; + UINT32 Length; + UINT8 ArgCount; + +#endif + +} ACPI_NAMESPACE_NODE; + + +/* Namespace Node flags */ + +#define ANOBJ_RESERVED 0x01 /* Available for use */ +#define ANOBJ_TEMPORARY 0x02 /* Node is create by a method and is temporary */ +#define ANOBJ_METHOD_ARG 0x04 /* Node is a method argument */ +#define ANOBJ_METHOD_LOCAL 0x08 /* Node is a method local */ +#define ANOBJ_SUBTREE_HAS_INI 0x10 /* Used to optimize device initialization */ +#define ANOBJ_EVALUATED 0x20 /* Set on first evaluation of node */ +#define ANOBJ_ALLOCATED_BUFFER 0x40 /* Method AML buffer is dynamic (InstallMethod) */ + +#define IMPLICIT_EXTERNAL 0x02 /* iASL only: This object created implicitly via External */ +#define ANOBJ_IS_EXTERNAL 0x08 /* iASL only: This object created via External() */ +#define ANOBJ_METHOD_NO_RETVAL 0x10 /* iASL only: Method has no return value */ +#define ANOBJ_METHOD_SOME_NO_RETVAL 0x20 /* iASL only: Method has at least one return value */ +#define ANOBJ_IS_REFERENCED 0x80 /* iASL only: Object was referenced */ + + +/* Internal ACPI table management - master table list */ + +typedef struct acpi_table_list +{ + ACPI_TABLE_DESC *Tables; /* Table descriptor array */ + UINT32 CurrentTableCount; /* Tables currently in the array */ + UINT32 MaxTableCount; /* Max tables array will hold */ + UINT8 Flags; + +} ACPI_TABLE_LIST; + +/* Flags for above */ + +#define ACPI_ROOT_ORIGIN_UNKNOWN (0) /* ~ORIGIN_ALLOCATED */ +#define ACPI_ROOT_ORIGIN_ALLOCATED (1) +#define ACPI_ROOT_ALLOW_RESIZE (2) + + +/* List to manage incoming ACPI tables */ + +typedef struct acpi_new_table_desc +{ + ACPI_TABLE_HEADER *Table; + struct acpi_new_table_desc *Next; + +} ACPI_NEW_TABLE_DESC; + + +/* Predefined table indexes */ + +#define ACPI_INVALID_TABLE_INDEX (0xFFFFFFFF) + + +typedef struct acpi_find_context +{ + char *SearchFor; + ACPI_HANDLE *List; + UINT32 *Count; + +} ACPI_FIND_CONTEXT; + + +typedef struct acpi_ns_search_data +{ + ACPI_NAMESPACE_NODE *Node; + +} ACPI_NS_SEARCH_DATA; + + +/* Object types used during package copies */ + +#define ACPI_COPY_TYPE_SIMPLE 0 +#define ACPI_COPY_TYPE_PACKAGE 1 + + +/* Info structure used to convert external<->internal namestrings */ + +typedef struct acpi_namestring_info +{ + const char *ExternalName; + const char *NextExternalChar; + char *InternalName; + UINT32 Length; + UINT32 NumSegments; + UINT32 NumCarats; + BOOLEAN FullyQualified; + +} ACPI_NAMESTRING_INFO; + + +/* Field creation info */ + +typedef struct acpi_create_field_info +{ + ACPI_NAMESPACE_NODE *RegionNode; + ACPI_NAMESPACE_NODE *FieldNode; + ACPI_NAMESPACE_NODE *RegisterNode; + ACPI_NAMESPACE_NODE *DataRegisterNode; + ACPI_NAMESPACE_NODE *ConnectionNode; + UINT8 *ResourceBuffer; + UINT32 BankValue; + UINT32 FieldBitPosition; + UINT32 FieldBitLength; + UINT16 ResourceLength; + UINT16 PinNumberIndex; + UINT8 FieldFlags; + UINT8 Attribute; + UINT8 FieldType; + UINT8 AccessLength; + +} ACPI_CREATE_FIELD_INFO; + + +typedef +ACPI_STATUS (*ACPI_INTERNAL_METHOD) ( + struct acpi_walk_state *WalkState); + + +/* + * Bitmapped ACPI types. Used internally only + */ +#define ACPI_BTYPE_ANY 0x00000000 +#define ACPI_BTYPE_INTEGER 0x00000001 +#define ACPI_BTYPE_STRING 0x00000002 +#define ACPI_BTYPE_BUFFER 0x00000004 +#define ACPI_BTYPE_PACKAGE 0x00000008 +#define ACPI_BTYPE_FIELD_UNIT 0x00000010 +#define ACPI_BTYPE_DEVICE 0x00000020 +#define ACPI_BTYPE_EVENT 0x00000040 +#define ACPI_BTYPE_METHOD 0x00000080 +#define ACPI_BTYPE_MUTEX 0x00000100 +#define ACPI_BTYPE_REGION 0x00000200 +#define ACPI_BTYPE_POWER 0x00000400 +#define ACPI_BTYPE_PROCESSOR 0x00000800 +#define ACPI_BTYPE_THERMAL 0x00001000 +#define ACPI_BTYPE_BUFFER_FIELD 0x00002000 +#define ACPI_BTYPE_DDB_HANDLE 0x00004000 +#define ACPI_BTYPE_DEBUG_OBJECT 0x00008000 +#define ACPI_BTYPE_REFERENCE_OBJECT 0x00010000 /* From Index(), RefOf(), etc (Type6Opcodes) */ +#define ACPI_BTYPE_RESOURCE 0x00020000 +#define ACPI_BTYPE_NAMED_REFERENCE 0x00040000 /* Generic unresolved Name or Namepath */ + +#define ACPI_BTYPE_COMPUTE_DATA (ACPI_BTYPE_INTEGER | ACPI_BTYPE_STRING | ACPI_BTYPE_BUFFER) + +#define ACPI_BTYPE_DATA (ACPI_BTYPE_COMPUTE_DATA | ACPI_BTYPE_PACKAGE) + + /* Used by Copy, DeRefOf, Store, Printf, Fprintf */ + +#define ACPI_BTYPE_DATA_REFERENCE (ACPI_BTYPE_DATA | ACPI_BTYPE_REFERENCE_OBJECT | ACPI_BTYPE_DDB_HANDLE) +#define ACPI_BTYPE_DEVICE_OBJECTS (ACPI_BTYPE_DEVICE | ACPI_BTYPE_THERMAL | ACPI_BTYPE_PROCESSOR) +#define ACPI_BTYPE_OBJECTS_AND_REFS 0x0001FFFF /* ARG or LOCAL */ +#define ACPI_BTYPE_ALL_OBJECTS 0x0000FFFF + +#pragma pack(1) + +/* + * Information structure for ACPI predefined names. + * Each entry in the table contains the following items: + * + * Name - The ACPI reserved name + * ParamCount - Number of arguments to the method + * ExpectedReturnBtypes - Allowed type(s) for the return value + */ +typedef struct acpi_name_info +{ + char Name[ACPI_NAME_SIZE]; + UINT16 ArgumentList; + UINT8 ExpectedBtypes; + +} ACPI_NAME_INFO; + +/* + * Secondary information structures for ACPI predefined objects that return + * package objects. This structure appears as the next entry in the table + * after the NAME_INFO structure above. + * + * The reason for this is to minimize the size of the predefined name table. + */ + +/* + * Used for ACPI_PTYPE1_FIXED, ACPI_PTYPE1_VAR, ACPI_PTYPE2, + * ACPI_PTYPE2_MIN, ACPI_PTYPE2_PKG_COUNT, ACPI_PTYPE2_COUNT, + * ACPI_PTYPE2_FIX_VAR + */ +typedef struct acpi_package_info +{ + UINT8 Type; + UINT8 ObjectType1; + UINT8 Count1; + UINT8 ObjectType2; + UINT8 Count2; + UINT16 Reserved; + +} ACPI_PACKAGE_INFO; + +/* Used for ACPI_PTYPE2_FIXED */ + +typedef struct acpi_package_info2 +{ + UINT8 Type; + UINT8 Count; + UINT8 ObjectType[4]; + UINT8 Reserved; + +} ACPI_PACKAGE_INFO2; + +/* Used for ACPI_PTYPE1_OPTION */ + +typedef struct acpi_package_info3 +{ + UINT8 Type; + UINT8 Count; + UINT8 ObjectType[2]; + UINT8 TailObjectType; + UINT16 Reserved; + +} ACPI_PACKAGE_INFO3; + +typedef struct acpi_package_info4 +{ + UINT8 Type; + UINT8 ObjectType1; + UINT8 Count1; + UINT8 SubObjectTypes; + UINT8 PkgCount; + UINT16 Reserved; + +} ACPI_PACKAGE_INFO4; + +typedef union acpi_predefined_info +{ + ACPI_NAME_INFO Info; + ACPI_PACKAGE_INFO RetInfo; + ACPI_PACKAGE_INFO2 RetInfo2; + ACPI_PACKAGE_INFO3 RetInfo3; + ACPI_PACKAGE_INFO4 RetInfo4; + +} ACPI_PREDEFINED_INFO; + +/* Reset to default packing */ + +#pragma pack() + + +/* Return object auto-repair info */ + +typedef ACPI_STATUS (*ACPI_OBJECT_CONVERTER) ( + struct acpi_namespace_node *Scope, + union acpi_operand_object *OriginalObject, + union acpi_operand_object **ConvertedObject); + +typedef struct acpi_simple_repair_info +{ + char Name[ACPI_NAME_SIZE]; + UINT32 UnexpectedBtypes; + UINT32 PackageIndex; + ACPI_OBJECT_CONVERTER ObjectConverter; + +} ACPI_SIMPLE_REPAIR_INFO; + + +/* + * Bitmapped return value types + * Note: the actual data types must be contiguous, a loop in nspredef.c + * depends on this. + */ +#define ACPI_RTYPE_ANY 0x00 +#define ACPI_RTYPE_NONE 0x01 +#define ACPI_RTYPE_INTEGER 0x02 +#define ACPI_RTYPE_STRING 0x04 +#define ACPI_RTYPE_BUFFER 0x08 +#define ACPI_RTYPE_PACKAGE 0x10 +#define ACPI_RTYPE_REFERENCE 0x20 +#define ACPI_RTYPE_ALL 0x3F + +#define ACPI_NUM_RTYPES 5 /* Number of actual object types */ + + +/* Info for running the _REG methods */ + +typedef struct acpi_reg_walk_info +{ + ACPI_ADR_SPACE_TYPE SpaceId; + UINT32 Function; + UINT32 RegRunCount; + +} ACPI_REG_WALK_INFO; + + +/***************************************************************************** + * + * Event typedefs and structs + * + ****************************************************************************/ + +/* Dispatch info for each host-installed SCI handler */ + +typedef struct acpi_sci_handler_info +{ + struct acpi_sci_handler_info *Next; + ACPI_SCI_HANDLER Address; /* Address of handler */ + void *Context; /* Context to be passed to handler */ + +} ACPI_SCI_HANDLER_INFO; + +/* Dispatch info for each GPE -- either a method or handler, cannot be both */ + +typedef struct acpi_gpe_handler_info +{ + ACPI_GPE_HANDLER Address; /* Address of handler, if any */ + void *Context; /* Context to be passed to handler */ + ACPI_NAMESPACE_NODE *MethodNode; /* Method node for this GPE level (saved) */ + UINT8 OriginalFlags; /* Original (pre-handler) GPE info */ + BOOLEAN OriginallyEnabled; /* True if GPE was originally enabled */ + +} ACPI_GPE_HANDLER_INFO; + +/* Notify info for implicit notify, multiple device objects */ + +typedef struct acpi_gpe_notify_info +{ + ACPI_NAMESPACE_NODE *DeviceNode; /* Device to be notified */ + struct acpi_gpe_notify_info *Next; + +} ACPI_GPE_NOTIFY_INFO; + +/* + * GPE dispatch info. At any time, the GPE can have at most one type + * of dispatch - Method, Handler, or Implicit Notify. + */ +typedef union acpi_gpe_dispatch_info +{ + ACPI_NAMESPACE_NODE *MethodNode; /* Method node for this GPE level */ + ACPI_GPE_HANDLER_INFO *Handler; /* Installed GPE handler */ + ACPI_GPE_NOTIFY_INFO *NotifyList; /* List of _PRW devices for implicit notifies */ + +} ACPI_GPE_DISPATCH_INFO; + +/* + * Information about a GPE, one per each GPE in an array. + * NOTE: Important to keep this struct as small as possible. + */ +typedef struct acpi_gpe_event_info +{ + union acpi_gpe_dispatch_info Dispatch; /* Either Method, Handler, or NotifyList */ + struct acpi_gpe_register_info *RegisterInfo; /* Backpointer to register info */ + UINT8 Flags; /* Misc info about this GPE */ + UINT8 GpeNumber; /* This GPE */ + UINT8 RuntimeCount; /* References to a run GPE */ + BOOLEAN DisableForDispatch; /* Masked during dispatching */ + +} ACPI_GPE_EVENT_INFO; + +/* Information about a GPE register pair, one per each status/enable pair in an array */ + +typedef struct acpi_gpe_register_info +{ + ACPI_GENERIC_ADDRESS StatusAddress; /* Address of status reg */ + ACPI_GENERIC_ADDRESS EnableAddress; /* Address of enable reg */ + UINT16 BaseGpeNumber; /* Base GPE number for this register */ + UINT8 EnableForWake; /* GPEs to keep enabled when sleeping */ + UINT8 EnableForRun; /* GPEs to keep enabled when running */ + UINT8 MaskForRun; /* GPEs to keep masked when running */ + UINT8 EnableMask; /* Current mask of enabled GPEs */ + +} ACPI_GPE_REGISTER_INFO; + +/* + * Information about a GPE register block, one per each installed block -- + * GPE0, GPE1, and one per each installed GPE Block Device. + */ +typedef struct acpi_gpe_block_info +{ + ACPI_NAMESPACE_NODE *Node; + struct acpi_gpe_block_info *Previous; + struct acpi_gpe_block_info *Next; + struct acpi_gpe_xrupt_info *XruptBlock; /* Backpointer to interrupt block */ + ACPI_GPE_REGISTER_INFO *RegisterInfo; /* One per GPE register pair */ + ACPI_GPE_EVENT_INFO *EventInfo; /* One for each GPE */ + UINT64 Address; /* Base address of the block */ + UINT32 RegisterCount; /* Number of register pairs in block */ + UINT16 GpeCount; /* Number of individual GPEs in block */ + UINT16 BlockBaseNumber;/* Base GPE number for this block */ + UINT8 SpaceId; + BOOLEAN Initialized; /* TRUE if this block is initialized */ + +} ACPI_GPE_BLOCK_INFO; + +/* Information about GPE interrupt handlers, one per each interrupt level used for GPEs */ + +typedef struct acpi_gpe_xrupt_info +{ + struct acpi_gpe_xrupt_info *Previous; + struct acpi_gpe_xrupt_info *Next; + ACPI_GPE_BLOCK_INFO *GpeBlockListHead; /* List of GPE blocks for this xrupt */ + UINT32 InterruptNumber; /* System interrupt number */ + +} ACPI_GPE_XRUPT_INFO; + +typedef struct acpi_gpe_walk_info +{ + ACPI_NAMESPACE_NODE *GpeDevice; + ACPI_GPE_BLOCK_INFO *GpeBlock; + UINT16 Count; + ACPI_OWNER_ID OwnerId; + BOOLEAN ExecuteByOwnerId; + +} ACPI_GPE_WALK_INFO; + +typedef struct acpi_gpe_device_info +{ + UINT32 Index; + UINT32 NextBlockBaseIndex; + ACPI_STATUS Status; + ACPI_NAMESPACE_NODE *GpeDevice; + +} ACPI_GPE_DEVICE_INFO; + +typedef ACPI_STATUS (*ACPI_GPE_CALLBACK) ( + ACPI_GPE_XRUPT_INFO *GpeXruptInfo, + ACPI_GPE_BLOCK_INFO *GpeBlock, + void *Context); + + +/* Information about each particular fixed event */ + +typedef struct acpi_fixed_event_handler +{ + ACPI_EVENT_HANDLER Handler; /* Address of handler. */ + void *Context; /* Context to be passed to handler */ + +} ACPI_FIXED_EVENT_HANDLER; + +typedef struct acpi_fixed_event_info +{ + UINT8 StatusRegisterId; + UINT8 EnableRegisterId; + UINT16 StatusBitMask; + UINT16 EnableBitMask; + +} ACPI_FIXED_EVENT_INFO; + +/* Information used during field processing */ + +typedef struct acpi_field_info +{ + UINT8 SkipField; + UINT8 FieldFlag; + UINT32 PkgLength; + +} ACPI_FIELD_INFO; + + +/***************************************************************************** + * + * Generic "state" object for stacks + * + ****************************************************************************/ + +#define ACPI_CONTROL_NORMAL 0xC0 +#define ACPI_CONTROL_CONDITIONAL_EXECUTING 0xC1 +#define ACPI_CONTROL_PREDICATE_EXECUTING 0xC2 +#define ACPI_CONTROL_PREDICATE_FALSE 0xC3 +#define ACPI_CONTROL_PREDICATE_TRUE 0xC4 + + +#define ACPI_STATE_COMMON \ + void *Next; \ + UINT8 DescriptorType; /* To differentiate various internal objs */\ + UINT8 Flags; \ + UINT16 Value; \ + UINT16 State; + + /* There are 2 bytes available here until the next natural alignment boundary */ + +typedef struct acpi_common_state +{ + ACPI_STATE_COMMON +} ACPI_COMMON_STATE; + + +/* + * Update state - used to traverse complex objects such as packages + */ +typedef struct acpi_update_state +{ + ACPI_STATE_COMMON + union acpi_operand_object *Object; + +} ACPI_UPDATE_STATE; + + +/* + * Pkg state - used to traverse nested package structures + */ +typedef struct acpi_pkg_state +{ + ACPI_STATE_COMMON + UINT32 Index; + union acpi_operand_object *SourceObject; + union acpi_operand_object *DestObject; + struct acpi_walk_state *WalkState; + void *ThisTargetObj; + UINT32 NumPackages; + +} ACPI_PKG_STATE; + + +/* + * Control state - one per if/else and while constructs. + * Allows nesting of these constructs + */ +typedef struct acpi_control_state +{ + ACPI_STATE_COMMON + UINT16 Opcode; + union acpi_parse_object *PredicateOp; + UINT8 *AmlPredicateStart; /* Start of if/while predicate */ + UINT8 *PackageEnd; /* End of if/while block */ + UINT64 LoopTimeout; /* While() loop timeout */ + +} ACPI_CONTROL_STATE; + + +/* + * Scope state - current scope during namespace lookups + */ +typedef struct acpi_scope_state +{ + ACPI_STATE_COMMON + ACPI_NAMESPACE_NODE *Node; + +} ACPI_SCOPE_STATE; + + +typedef struct acpi_pscope_state +{ + ACPI_STATE_COMMON + UINT32 ArgCount; /* Number of fixed arguments */ + union acpi_parse_object *Op; /* Current op being parsed */ + UINT8 *ArgEnd; /* Current argument end */ + UINT8 *PkgEnd; /* Current package end */ + UINT32 ArgList; /* Next argument to parse */ + +} ACPI_PSCOPE_STATE; + + +/* + * Thread state - one per thread across multiple walk states. Multiple walk + * states are created when there are nested control methods executing. + */ +typedef struct acpi_thread_state +{ + ACPI_STATE_COMMON + UINT8 CurrentSyncLevel; /* Mutex Sync (nested acquire) level */ + struct acpi_walk_state *WalkStateList; /* Head of list of WalkStates for this thread */ + union acpi_operand_object *AcquiredMutexList; /* List of all currently acquired mutexes */ + ACPI_THREAD_ID ThreadId; /* Running thread ID */ + +} ACPI_THREAD_STATE; + + +/* + * Result values - used to accumulate the results of nested + * AML arguments + */ +typedef struct acpi_result_values +{ + ACPI_STATE_COMMON + union acpi_operand_object *ObjDesc [ACPI_RESULTS_FRAME_OBJ_NUM]; + +} ACPI_RESULT_VALUES; + + +typedef +ACPI_STATUS (*ACPI_PARSE_DOWNWARDS) ( + struct acpi_walk_state *WalkState, + union acpi_parse_object **OutOp); + +typedef +ACPI_STATUS (*ACPI_PARSE_UPWARDS) ( + struct acpi_walk_state *WalkState); + + +/* Global handlers for AML Notifies */ + +typedef struct acpi_global_notify_handler +{ + ACPI_NOTIFY_HANDLER Handler; + void *Context; + +} ACPI_GLOBAL_NOTIFY_HANDLER; + +/* + * Notify info - used to pass info to the deferred notify + * handler/dispatcher. + */ +typedef struct acpi_notify_info +{ + ACPI_STATE_COMMON + UINT8 HandlerListId; + ACPI_NAMESPACE_NODE *Node; + union acpi_operand_object *HandlerListHead; + ACPI_GLOBAL_NOTIFY_HANDLER *Global; + +} ACPI_NOTIFY_INFO; + + +/* Generic state is union of structs above */ + +typedef union acpi_generic_state +{ + ACPI_COMMON_STATE Common; + ACPI_CONTROL_STATE Control; + ACPI_UPDATE_STATE Update; + ACPI_SCOPE_STATE Scope; + ACPI_PSCOPE_STATE ParseScope; + ACPI_PKG_STATE Pkg; + ACPI_THREAD_STATE Thread; + ACPI_RESULT_VALUES Results; + ACPI_NOTIFY_INFO Notify; + +} ACPI_GENERIC_STATE; + + +/***************************************************************************** + * + * Interpreter typedefs and structs + * + ****************************************************************************/ + +typedef +ACPI_STATUS (*ACPI_EXECUTE_OP) ( + struct acpi_walk_state *WalkState); + +/* Address Range info block */ + +typedef struct acpi_address_range +{ + struct acpi_address_range *Next; + ACPI_NAMESPACE_NODE *RegionNode; + ACPI_PHYSICAL_ADDRESS StartAddress; + ACPI_PHYSICAL_ADDRESS EndAddress; + +} ACPI_ADDRESS_RANGE; + + +/***************************************************************************** + * + * Parser typedefs and structs + * + ****************************************************************************/ + +/* + * AML opcode, name, and argument layout + */ +typedef struct acpi_opcode_info +{ +#if defined(ACPI_DISASSEMBLER) || defined(ACPI_DEBUG_OUTPUT) + char *Name; /* Opcode name (disassembler/debug only) */ +#endif + UINT32 ParseArgs; /* Grammar/Parse time arguments */ + UINT32 RuntimeArgs; /* Interpret time arguments */ + UINT16 Flags; /* Misc flags */ + UINT8 ObjectType; /* Corresponding internal object type */ + UINT8 Class; /* Opcode class */ + UINT8 Type; /* Opcode type */ + +} ACPI_OPCODE_INFO; + +/* Structure for Resource Tag information */ + +typedef struct acpi_tag_info +{ + UINT32 BitOffset; + UINT32 BitLength; + +} ACPI_TAG_INFO; + +/* Value associated with the parse object */ + +typedef union acpi_parse_value +{ + UINT64 Integer; /* Integer constant (Up to 64 bits) */ + UINT32 Size; /* bytelist or field size */ + char *String; /* NULL terminated string */ + UINT8 *Buffer; /* buffer or string */ + char *Name; /* NULL terminated string */ + union acpi_parse_object *Arg; /* arguments and contained ops */ + ACPI_TAG_INFO Tag; /* Resource descriptor tag info */ + +} ACPI_PARSE_VALUE; + + +#if defined(ACPI_DISASSEMBLER) || defined(ACPI_DEBUG_OUTPUT) +#define ACPI_DISASM_ONLY_MEMBERS(a) a; +#else +#define ACPI_DISASM_ONLY_MEMBERS(a) +#endif + +#if defined(ACPI_ASL_COMPILER) +#define ACPI_CONVERTER_ONLY_MEMBERS(a) a; +#else +#define ACPI_CONVERTER_ONLY_MEMBERS(a) +#endif + +#define ACPI_PARSE_COMMON \ + union acpi_parse_object *Parent; /* Parent op */\ + UINT8 DescriptorType; /* To differentiate various internal objs */\ + UINT8 Flags; /* Type of Op */\ + UINT16 AmlOpcode; /* AML opcode */\ + UINT8 *Aml; /* Address of declaration in AML */\ + union acpi_parse_object *Next; /* Next op */\ + ACPI_NAMESPACE_NODE *Node; /* For use by interpreter */\ + ACPI_PARSE_VALUE Value; /* Value or args associated with the opcode */\ + UINT8 ArgListLength; /* Number of elements in the arg list */\ + ACPI_DISASM_ONLY_MEMBERS (\ + UINT16 DisasmFlags; /* Used during AML disassembly */\ + UINT8 DisasmOpcode; /* Subtype used for disassembly */\ + char *OperatorSymbol; /* Used for C-style operator name strings */\ + char AmlOpName[16]) /* Op name (debug only) */\ + ACPI_CONVERTER_ONLY_MEMBERS (\ + char *InlineComment; /* Inline comment */\ + char *EndNodeComment; /* End of node comment */\ + char *NameComment; /* Comment associated with the first parameter of the name node */\ + char *CloseBraceComment; /* Comments that come after } on the same as } */\ + ACPI_COMMENT_NODE *CommentList; /* comments that appears before this node */\ + ACPI_COMMENT_NODE *EndBlkComment; /* comments that at the end of a block but before ) or } */\ + char *CvFilename; /* Filename associated with this node. Used for ASL/ASL+ converter */\ + char *CvParentFilename) /* Parent filename associated with this node. Used for ASL/ASL+ converter */ + + +/* categories of comments */ + +typedef enum +{ + STANDARD_COMMENT = 1, + INLINE_COMMENT, + ENDNODE_COMMENT, + OPENBRACE_COMMENT, + CLOSE_BRACE_COMMENT, + STD_DEFBLK_COMMENT, + END_DEFBLK_COMMENT, + FILENAME_COMMENT, + PARENTFILENAME_COMMENT, + ENDBLK_COMMENT, + INCLUDE_COMMENT + +} ASL_COMMENT_TYPES; + + +/* Internal opcodes for DisasmOpcode field above */ + +#define ACPI_DASM_BUFFER 0x00 /* Buffer is a simple data buffer */ +#define ACPI_DASM_RESOURCE 0x01 /* Buffer is a Resource Descriptor */ +#define ACPI_DASM_STRING 0x02 /* Buffer is a ASCII string */ +#define ACPI_DASM_UNICODE 0x03 /* Buffer is a Unicode string */ +#define ACPI_DASM_PLD_METHOD 0x04 /* Buffer is a _PLD method bit-packed buffer */ +#define ACPI_DASM_UUID 0x05 /* Buffer is a UUID/GUID */ +#define ACPI_DASM_EISAID 0x06 /* Integer is an EISAID */ +#define ACPI_DASM_MATCHOP 0x07 /* Parent opcode is a Match() operator */ +#define ACPI_DASM_LNOT_PREFIX 0x08 /* Start of a LNotEqual (etc.) pair of opcodes */ +#define ACPI_DASM_LNOT_SUFFIX 0x09 /* End of a LNotEqual (etc.) pair of opcodes */ +#define ACPI_DASM_HID_STRING 0x0A /* String is a _HID or _CID */ +#define ACPI_DASM_IGNORE_SINGLE 0x0B /* Ignore the opcode but not it's children */ +#define ACPI_DASM_SWITCH 0x0C /* While is a Switch */ +#define ACPI_DASM_SWITCH_PREDICATE 0x0D /* Object is a predicate for a Switch or Case block */ +#define ACPI_DASM_CASE 0x0E /* If/Else is a Case in a Switch/Case block */ +#define ACPI_DASM_DEFAULT 0x0F /* Else is a Default in a Switch/Case block */ + + +/* + * List struct used in the -ca option + */ +typedef struct acpi_comment_node +{ + char *Comment; + struct acpi_comment_node *Next; + +} ACPI_COMMENT_NODE; + + +typedef struct acpi_comment_addr_node +{ + UINT8 *Addr; + struct acpi_comment_addr_node *Next; +} ACPI_COMMENT_ADDR_NODE; + +/* + * File node - used for "Include" operator file stack and + * depdendency tree for the -ca option + */ +typedef struct acpi_file_node +{ + void *File; + char *Filename; + char *FileStart; /* Points to AML and indicates when the AML for this particular file starts. */ + char *FileEnd; /* Points to AML and indicates when the AML for this particular file ends. */ + struct acpi_file_node *Next; + struct acpi_file_node *Parent; + BOOLEAN IncludeWritten; + ACPI_COMMENT_NODE *IncludeComment; + +} ACPI_FILE_NODE; + + +/* + * Generic operation (for example: If, While, Store) + */ +typedef struct acpi_parse_obj_common +{ + ACPI_PARSE_COMMON +} ACPI_PARSE_OBJ_COMMON; + + +/* + * Extended Op for named ops (Scope, Method, etc.), deferred ops (Methods and OpRegions), + * and bytelists. + */ +typedef struct acpi_parse_obj_named +{ + ACPI_PARSE_COMMON + char *Path; + UINT8 *Data; /* AML body or bytelist data */ + UINT32 Length; /* AML length */ + UINT32 Name; /* 4-byte name or zero if no name */ + +} ACPI_PARSE_OBJ_NAMED; + + +/* This version is used by the iASL compiler only */ + +#define ACPI_MAX_PARSEOP_NAME 20 + +typedef struct acpi_parse_obj_asl +{ + ACPI_PARSE_COMMON + union acpi_parse_object *Child; + union acpi_parse_object *ParentMethod; + char *Filename; + BOOLEAN FileChanged; + char *ParentFilename; + char *ExternalName; + char *Namepath; + char NameSeg[4]; + UINT32 ExtraValue; + UINT32 Column; + UINT32 LineNumber; + UINT32 LogicalLineNumber; + UINT32 LogicalByteOffset; + UINT32 EndLine; + UINT32 EndLogicalLine; + UINT32 AcpiBtype; + UINT32 AmlLength; + UINT32 AmlSubtreeLength; + UINT32 FinalAmlLength; + UINT32 FinalAmlOffset; + UINT32 CompileFlags; + UINT16 ParseOpcode; + UINT8 AmlOpcodeLength; + UINT8 AmlPkgLenBytes; + UINT8 Extra; + char ParseOpName[ACPI_MAX_PARSEOP_NAME]; + +} ACPI_PARSE_OBJ_ASL; + +typedef union acpi_parse_object +{ + ACPI_PARSE_OBJ_COMMON Common; + ACPI_PARSE_OBJ_NAMED Named; + ACPI_PARSE_OBJ_ASL Asl; + +} ACPI_PARSE_OBJECT; + +typedef struct asl_comment_state +{ + UINT8 CommentType; + UINT32 SpacesBefore; + ACPI_PARSE_OBJECT *LatestParseOp; + ACPI_PARSE_OBJECT *ParsingParenBraceNode; + BOOLEAN CaptureComments; + +} ASL_COMMENT_STATE; + + +/* + * Parse state - one state per parser invocation and each control + * method. + */ +typedef struct acpi_parse_state +{ + UINT8 *AmlStart; /* First AML byte */ + UINT8 *Aml; /* Next AML byte */ + UINT8 *AmlEnd; /* (last + 1) AML byte */ + UINT8 *PkgStart; /* Current package begin */ + UINT8 *PkgEnd; /* Current package end */ + union acpi_parse_object *StartOp; /* Root of parse tree */ + struct acpi_namespace_node *StartNode; + union acpi_generic_state *Scope; /* Current scope */ + union acpi_parse_object *StartScope; + UINT32 AmlSize; + +} ACPI_PARSE_STATE; + + +/* Parse object flags */ + +#define ACPI_PARSEOP_GENERIC 0x01 +#define ACPI_PARSEOP_NAMED_OBJECT 0x02 +#define ACPI_PARSEOP_DEFERRED 0x04 +#define ACPI_PARSEOP_BYTELIST 0x08 +#define ACPI_PARSEOP_IN_STACK 0x10 +#define ACPI_PARSEOP_TARGET 0x20 +#define ACPI_PARSEOP_IN_CACHE 0x80 + +/* Parse object DisasmFlags */ + +#define ACPI_PARSEOP_IGNORE 0x0001 +#define ACPI_PARSEOP_PARAMETER_LIST 0x0002 +#define ACPI_PARSEOP_EMPTY_TERMLIST 0x0004 +#define ACPI_PARSEOP_PREDEFINED_CHECKED 0x0008 +#define ACPI_PARSEOP_CLOSING_PAREN 0x0010 +#define ACPI_PARSEOP_COMPOUND_ASSIGNMENT 0x0020 +#define ACPI_PARSEOP_ASSIGNMENT 0x0040 +#define ACPI_PARSEOP_ELSEIF 0x0080 +#define ACPI_PARSEOP_LEGACY_ASL_ONLY 0x0100 + + +/***************************************************************************** + * + * Hardware (ACPI registers) and PNP + * + ****************************************************************************/ + +typedef struct acpi_bit_register_info +{ + UINT8 ParentRegister; + UINT8 BitPosition; + UINT16 AccessBitMask; + +} ACPI_BIT_REGISTER_INFO; + + +/* + * Some ACPI registers have bits that must be ignored -- meaning that they + * must be preserved. + */ +#define ACPI_PM1_STATUS_PRESERVED_BITS 0x0800 /* Bit 11 */ + +/* Write-only bits must be zeroed by software */ + +#define ACPI_PM1_CONTROL_WRITEONLY_BITS 0x2004 /* Bits 13, 2 */ + +/* For control registers, both ignored and reserved bits must be preserved */ + +/* + * For PM1 control, the SCI enable bit (bit 0, SCI_EN) is defined by the + * ACPI specification to be a "preserved" bit - "OSPM always preserves this + * bit position", section 4.7.3.2.1. However, on some machines the OS must + * write a one to this bit after resume for the machine to work properly. + * To enable this, we no longer attempt to preserve this bit. No machines + * are known to fail if the bit is not preserved. (May 2009) + */ +#define ACPI_PM1_CONTROL_IGNORED_BITS 0x0200 /* Bit 9 */ +#define ACPI_PM1_CONTROL_RESERVED_BITS 0xC1F8 /* Bits 14-15, 3-8 */ +#define ACPI_PM1_CONTROL_PRESERVED_BITS \ + (ACPI_PM1_CONTROL_IGNORED_BITS | ACPI_PM1_CONTROL_RESERVED_BITS) + +#define ACPI_PM2_CONTROL_PRESERVED_BITS 0xFFFFFFFE /* All except bit 0 */ + +/* + * Register IDs + * These are the full ACPI registers + */ +#define ACPI_REGISTER_PM1_STATUS 0x01 +#define ACPI_REGISTER_PM1_ENABLE 0x02 +#define ACPI_REGISTER_PM1_CONTROL 0x03 +#define ACPI_REGISTER_PM2_CONTROL 0x04 +#define ACPI_REGISTER_PM_TIMER 0x05 +#define ACPI_REGISTER_PROCESSOR_BLOCK 0x06 +#define ACPI_REGISTER_SMI_COMMAND_BLOCK 0x07 + + +/* Masks used to access the BitRegisters */ + +#define ACPI_BITMASK_TIMER_STATUS 0x0001 +#define ACPI_BITMASK_BUS_MASTER_STATUS 0x0010 +#define ACPI_BITMASK_GLOBAL_LOCK_STATUS 0x0020 +#define ACPI_BITMASK_POWER_BUTTON_STATUS 0x0100 +#define ACPI_BITMASK_SLEEP_BUTTON_STATUS 0x0200 +#define ACPI_BITMASK_RT_CLOCK_STATUS 0x0400 +#define ACPI_BITMASK_PCIEXP_WAKE_STATUS 0x4000 /* ACPI 3.0 */ +#define ACPI_BITMASK_WAKE_STATUS 0x8000 + +#define ACPI_BITMASK_ALL_FIXED_STATUS (\ + ACPI_BITMASK_TIMER_STATUS | \ + ACPI_BITMASK_BUS_MASTER_STATUS | \ + ACPI_BITMASK_GLOBAL_LOCK_STATUS | \ + ACPI_BITMASK_POWER_BUTTON_STATUS | \ + ACPI_BITMASK_SLEEP_BUTTON_STATUS | \ + ACPI_BITMASK_RT_CLOCK_STATUS | \ + ACPI_BITMASK_PCIEXP_WAKE_STATUS | \ + ACPI_BITMASK_WAKE_STATUS) + +#define ACPI_BITMASK_TIMER_ENABLE 0x0001 +#define ACPI_BITMASK_GLOBAL_LOCK_ENABLE 0x0020 +#define ACPI_BITMASK_POWER_BUTTON_ENABLE 0x0100 +#define ACPI_BITMASK_SLEEP_BUTTON_ENABLE 0x0200 +#define ACPI_BITMASK_RT_CLOCK_ENABLE 0x0400 +#define ACPI_BITMASK_PCIEXP_WAKE_DISABLE 0x4000 /* ACPI 3.0 */ + +#define ACPI_BITMASK_SCI_ENABLE 0x0001 +#define ACPI_BITMASK_BUS_MASTER_RLD 0x0002 +#define ACPI_BITMASK_GLOBAL_LOCK_RELEASE 0x0004 +#define ACPI_BITMASK_SLEEP_TYPE 0x1C00 +#define ACPI_BITMASK_SLEEP_ENABLE 0x2000 + +#define ACPI_BITMASK_ARB_DISABLE 0x0001 + + +/* Raw bit position of each BitRegister */ + +#define ACPI_BITPOSITION_TIMER_STATUS 0x00 +#define ACPI_BITPOSITION_BUS_MASTER_STATUS 0x04 +#define ACPI_BITPOSITION_GLOBAL_LOCK_STATUS 0x05 +#define ACPI_BITPOSITION_POWER_BUTTON_STATUS 0x08 +#define ACPI_BITPOSITION_SLEEP_BUTTON_STATUS 0x09 +#define ACPI_BITPOSITION_RT_CLOCK_STATUS 0x0A +#define ACPI_BITPOSITION_PCIEXP_WAKE_STATUS 0x0E /* ACPI 3.0 */ +#define ACPI_BITPOSITION_WAKE_STATUS 0x0F + +#define ACPI_BITPOSITION_TIMER_ENABLE 0x00 +#define ACPI_BITPOSITION_GLOBAL_LOCK_ENABLE 0x05 +#define ACPI_BITPOSITION_POWER_BUTTON_ENABLE 0x08 +#define ACPI_BITPOSITION_SLEEP_BUTTON_ENABLE 0x09 +#define ACPI_BITPOSITION_RT_CLOCK_ENABLE 0x0A +#define ACPI_BITPOSITION_PCIEXP_WAKE_DISABLE 0x0E /* ACPI 3.0 */ + +#define ACPI_BITPOSITION_SCI_ENABLE 0x00 +#define ACPI_BITPOSITION_BUS_MASTER_RLD 0x01 +#define ACPI_BITPOSITION_GLOBAL_LOCK_RELEASE 0x02 +#define ACPI_BITPOSITION_SLEEP_TYPE 0x0A +#define ACPI_BITPOSITION_SLEEP_ENABLE 0x0D + +#define ACPI_BITPOSITION_ARB_DISABLE 0x00 + + +/* Structs and definitions for _OSI support and I/O port validation */ + +#define ACPI_ALWAYS_ILLEGAL 0x00 + +typedef struct acpi_interface_info +{ + char *Name; + struct acpi_interface_info *Next; + UINT8 Flags; + UINT8 Value; + +} ACPI_INTERFACE_INFO; + +#define ACPI_OSI_INVALID 0x01 +#define ACPI_OSI_DYNAMIC 0x02 +#define ACPI_OSI_FEATURE 0x04 +#define ACPI_OSI_DEFAULT_INVALID 0x08 +#define ACPI_OSI_OPTIONAL_FEATURE (ACPI_OSI_FEATURE | ACPI_OSI_DEFAULT_INVALID | ACPI_OSI_INVALID) + +typedef struct acpi_port_info +{ + char *Name; + UINT16 Start; + UINT16 End; + UINT8 OsiDependency; + +} ACPI_PORT_INFO; + + +/***************************************************************************** + * + * Resource descriptors + * + ****************************************************************************/ + +/* ResourceType values */ + +#define ACPI_ADDRESS_TYPE_MEMORY_RANGE 0 +#define ACPI_ADDRESS_TYPE_IO_RANGE 1 +#define ACPI_ADDRESS_TYPE_BUS_NUMBER_RANGE 2 + +/* Resource descriptor types and masks */ + +#define ACPI_RESOURCE_NAME_LARGE 0x80 +#define ACPI_RESOURCE_NAME_SMALL 0x00 + +#define ACPI_RESOURCE_NAME_SMALL_MASK 0x78 /* Bits 6:3 contain the type */ +#define ACPI_RESOURCE_NAME_SMALL_LENGTH_MASK 0x07 /* Bits 2:0 contain the length */ +#define ACPI_RESOURCE_NAME_LARGE_MASK 0x7F /* Bits 6:0 contain the type */ + + +/* + * Small resource descriptor "names" as defined by the ACPI specification. + * Note: Bits 2:0 are used for the descriptor length + */ +#define ACPI_RESOURCE_NAME_IRQ 0x20 +#define ACPI_RESOURCE_NAME_DMA 0x28 +#define ACPI_RESOURCE_NAME_START_DEPENDENT 0x30 +#define ACPI_RESOURCE_NAME_END_DEPENDENT 0x38 +#define ACPI_RESOURCE_NAME_IO 0x40 +#define ACPI_RESOURCE_NAME_FIXED_IO 0x48 +#define ACPI_RESOURCE_NAME_FIXED_DMA 0x50 +#define ACPI_RESOURCE_NAME_RESERVED_S2 0x58 +#define ACPI_RESOURCE_NAME_RESERVED_S3 0x60 +#define ACPI_RESOURCE_NAME_RESERVED_S4 0x68 +#define ACPI_RESOURCE_NAME_VENDOR_SMALL 0x70 +#define ACPI_RESOURCE_NAME_END_TAG 0x78 + +/* + * Large resource descriptor "names" as defined by the ACPI specification. + * Note: includes the Large Descriptor bit in bit[7] + */ +#define ACPI_RESOURCE_NAME_MEMORY24 0x81 +#define ACPI_RESOURCE_NAME_GENERIC_REGISTER 0x82 +#define ACPI_RESOURCE_NAME_RESERVED_L1 0x83 +#define ACPI_RESOURCE_NAME_VENDOR_LARGE 0x84 +#define ACPI_RESOURCE_NAME_MEMORY32 0x85 +#define ACPI_RESOURCE_NAME_FIXED_MEMORY32 0x86 +#define ACPI_RESOURCE_NAME_ADDRESS32 0x87 +#define ACPI_RESOURCE_NAME_ADDRESS16 0x88 +#define ACPI_RESOURCE_NAME_EXTENDED_IRQ 0x89 +#define ACPI_RESOURCE_NAME_ADDRESS64 0x8A +#define ACPI_RESOURCE_NAME_EXTENDED_ADDRESS64 0x8B +#define ACPI_RESOURCE_NAME_GPIO 0x8C +#define ACPI_RESOURCE_NAME_PIN_FUNCTION 0x8D +#define ACPI_RESOURCE_NAME_SERIAL_BUS 0x8E +#define ACPI_RESOURCE_NAME_PIN_CONFIG 0x8F +#define ACPI_RESOURCE_NAME_PIN_GROUP 0x90 +#define ACPI_RESOURCE_NAME_PIN_GROUP_FUNCTION 0x91 +#define ACPI_RESOURCE_NAME_PIN_GROUP_CONFIG 0x92 +#define ACPI_RESOURCE_NAME_LARGE_MAX 0x92 + + +/***************************************************************************** + * + * Miscellaneous + * + ****************************************************************************/ + +#define ACPI_ASCII_ZERO 0x30 + + +/***************************************************************************** + * + * Disassembler + * + ****************************************************************************/ + +typedef struct acpi_external_list +{ + char *Path; + char *InternalPath; + struct acpi_external_list *Next; + UINT32 Value; + UINT16 Length; + UINT16 Flags; + UINT8 Type; + +} ACPI_EXTERNAL_LIST; + +/* Values for Flags field above */ + +#define ACPI_EXT_RESOLVED_REFERENCE 0x01 /* Object was resolved during cross ref */ +#define ACPI_EXT_ORIGIN_FROM_FILE 0x02 /* External came from a file */ +#define ACPI_EXT_INTERNAL_PATH_ALLOCATED 0x04 /* Deallocate internal path on completion */ +#define ACPI_EXT_EXTERNAL_EMITTED 0x08 /* External() statement has been emitted */ +#define ACPI_EXT_ORIGIN_FROM_OPCODE 0x10 /* External came from a External() opcode */ +#define ACPI_EXT_CONFLICTING_DECLARATION 0x20 /* External has a conflicting declaration within AML */ + + +typedef struct acpi_external_file +{ + char *Path; + struct acpi_external_file *Next; + +} ACPI_EXTERNAL_FILE; + + +typedef struct acpi_parse_object_list +{ + ACPI_PARSE_OBJECT *Op; + struct acpi_parse_object_list *Next; + +} ACPI_PARSE_OBJECT_LIST; + +/***************************************************************************** + * + * Debugger + * + ****************************************************************************/ + +typedef struct acpi_db_method_info +{ + ACPI_HANDLE Method; + ACPI_HANDLE MainThreadGate; + ACPI_HANDLE ThreadCompleteGate; + ACPI_HANDLE InfoGate; + ACPI_THREAD_ID *Threads; + UINT32 NumThreads; + UINT32 NumCreated; + UINT32 NumCompleted; + + char *Name; + UINT32 Flags; + UINT32 NumLoops; + char Pathname[ACPI_DB_LINE_BUFFER_SIZE]; + char **Args; + ACPI_OBJECT_TYPE *Types; + + /* + * Arguments to be passed to method for the commands Threads and + * Background. Note, ACPI specifies a maximum of 7 arguments (0 - 6). + * + * For the Threads command, the Number of threads, ID of current + * thread and Index of current thread inside all them created. + */ + char InitArgs; +#ifdef ACPI_DEBUGGER + ACPI_OBJECT_TYPE ArgTypes[ACPI_METHOD_NUM_ARGS]; +#endif + char *Arguments[ACPI_METHOD_NUM_ARGS]; + char NumThreadsStr[11]; + char IdOfThreadStr[11]; + char IndexOfThreadStr[11]; + +} ACPI_DB_METHOD_INFO; + +typedef struct acpi_integrity_info +{ + UINT32 Nodes; + UINT32 Objects; + +} ACPI_INTEGRITY_INFO; + + +#define ACPI_DB_DISABLE_OUTPUT 0x00 +#define ACPI_DB_REDIRECTABLE_OUTPUT 0x01 +#define ACPI_DB_CONSOLE_OUTPUT 0x02 +#define ACPI_DB_DUPLICATE_OUTPUT 0x03 + + +typedef struct acpi_object_info +{ + UINT32 Types[ACPI_TOTAL_TYPES]; + +} ACPI_OBJECT_INFO; + + +/***************************************************************************** + * + * Debug + * + ****************************************************************************/ + +/* Entry for a memory allocation (debug only) */ + +#define ACPI_MEM_MALLOC 0 +#define ACPI_MEM_CALLOC 1 +#define ACPI_MAX_MODULE_NAME 16 + +#define ACPI_COMMON_DEBUG_MEM_HEADER \ + struct acpi_debug_mem_block *Previous; \ + struct acpi_debug_mem_block *Next; \ + UINT32 Size; \ + UINT32 Component; \ + UINT32 Line; \ + char Module[ACPI_MAX_MODULE_NAME]; \ + UINT8 AllocType; + +typedef struct acpi_debug_mem_header +{ + ACPI_COMMON_DEBUG_MEM_HEADER + +} ACPI_DEBUG_MEM_HEADER; + +typedef struct acpi_debug_mem_block +{ + ACPI_COMMON_DEBUG_MEM_HEADER + UINT64 UserSpace; + +} ACPI_DEBUG_MEM_BLOCK; + + +#define ACPI_MEM_LIST_GLOBAL 0 +#define ACPI_MEM_LIST_NSNODE 1 +#define ACPI_MEM_LIST_MAX 1 +#define ACPI_NUM_MEM_LISTS 2 + + +/***************************************************************************** + * + * Info/help support + * + ****************************************************************************/ + +typedef struct ah_predefined_name +{ + char *Name; + char *Description; +#ifndef ACPI_ASL_COMPILER + char *Action; +#endif + +} AH_PREDEFINED_NAME; + +typedef struct ah_device_id +{ + char *Name; + char *Description; + +} AH_DEVICE_ID; + +typedef struct ah_uuid +{ + char *Description; + char *String; + +} AH_UUID; + +typedef struct ah_table +{ + char *Signature; + char *Description; + +} AH_TABLE; + +#endif /* __ACLOCAL_H__ */ diff --git a/ports/acpica/include/acmacros.h b/ports/acpica/include/acmacros.h new file mode 100644 index 0000000..f13523a --- /dev/null +++ b/ports/acpica/include/acmacros.h @@ -0,0 +1,647 @@ +/****************************************************************************** + * + * Name: acmacros.h - C macros for the entire subsystem. + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACMACROS_H__ +#define __ACMACROS_H__ + + +/* + * Extract data using a pointer. Any more than a byte and we + * get into potential alignment issues -- see the STORE macros below. + * Use with care. + */ +#define ACPI_CAST8(ptr) ACPI_CAST_PTR (UINT8, (ptr)) +#define ACPI_CAST16(ptr) ACPI_CAST_PTR (UINT16, (ptr)) +#define ACPI_CAST32(ptr) ACPI_CAST_PTR (UINT32, (ptr)) +#define ACPI_CAST64(ptr) ACPI_CAST_PTR (UINT64, (ptr)) +#define ACPI_GET8(ptr) (*ACPI_CAST8 (ptr)) +#define ACPI_GET16(ptr) (*ACPI_CAST16 (ptr)) +#define ACPI_GET32(ptr) (*ACPI_CAST32 (ptr)) +#define ACPI_GET64(ptr) (*ACPI_CAST64 (ptr)) +#define ACPI_SET8(ptr, val) (*ACPI_CAST8 (ptr) = (UINT8) (val)) +#define ACPI_SET16(ptr, val) (*ACPI_CAST16 (ptr) = (UINT16) (val)) +#define ACPI_SET32(ptr, val) (*ACPI_CAST32 (ptr) = (UINT32) (val)) +#define ACPI_SET64(ptr, val) (*ACPI_CAST64 (ptr) = (UINT64) (val)) + +/* + * printf() format helper. This macro is a workaround for the difficulties + * with emitting 64-bit integers and 64-bit pointers with the same code + * for both 32-bit and 64-bit hosts. + */ +#define ACPI_FORMAT_UINT64(i) ACPI_HIDWORD(i), ACPI_LODWORD(i) + + +/* + * Macros for moving data around to/from buffers that are possibly unaligned. + * If the hardware supports the transfer of unaligned data, just do the store. + * Otherwise, we have to move one byte at a time. + */ +#ifdef ACPI_BIG_ENDIAN +/* + * Macros for big-endian machines + */ + +/* These macros reverse the bytes during the move, converting little-endian to big endian */ + + /* Big Endian <== Little Endian */ + /* Hi...Lo Lo...Hi */ +/* 16-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_16_TO_16(d, s) {(( UINT8 *)(void *)(d))[0] = ((UINT8 *)(void *)(s))[1];\ + (( UINT8 *)(void *)(d))[1] = ((UINT8 *)(void *)(s))[0];} + +#define ACPI_MOVE_16_TO_32(d, s) {(*(UINT32 *)(void *)(d))=0;\ + ((UINT8 *)(void *)(d))[2] = ((UINT8 *)(void *)(s))[1];\ + ((UINT8 *)(void *)(d))[3] = ((UINT8 *)(void *)(s))[0];} + +#define ACPI_MOVE_16_TO_64(d, s) {(*(UINT64 *)(void *)(d))=0;\ + ((UINT8 *)(void *)(d))[6] = ((UINT8 *)(void *)(s))[1];\ + ((UINT8 *)(void *)(d))[7] = ((UINT8 *)(void *)(s))[0];} + +/* 32-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_32_TO_16(d, s) ACPI_MOVE_16_TO_16(d, s) /* Truncate to 16 */ + +#define ACPI_MOVE_32_TO_32(d, s) {(( UINT8 *)(void *)(d))[0] = ((UINT8 *)(void *)(s))[3];\ + (( UINT8 *)(void *)(d))[1] = ((UINT8 *)(void *)(s))[2];\ + (( UINT8 *)(void *)(d))[2] = ((UINT8 *)(void *)(s))[1];\ + (( UINT8 *)(void *)(d))[3] = ((UINT8 *)(void *)(s))[0];} + +#define ACPI_MOVE_32_TO_64(d, s) {(*(UINT64 *)(void *)(d))=0;\ + ((UINT8 *)(void *)(d))[4] = ((UINT8 *)(void *)(s))[3];\ + ((UINT8 *)(void *)(d))[5] = ((UINT8 *)(void *)(s))[2];\ + ((UINT8 *)(void *)(d))[6] = ((UINT8 *)(void *)(s))[1];\ + ((UINT8 *)(void *)(d))[7] = ((UINT8 *)(void *)(s))[0];} + +/* 64-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_64_TO_16(d, s) ACPI_MOVE_16_TO_16(d, s) /* Truncate to 16 */ + +#define ACPI_MOVE_64_TO_32(d, s) ACPI_MOVE_32_TO_32(d, s) /* Truncate to 32 */ + +#define ACPI_MOVE_64_TO_64(d, s) {(( UINT8 *)(void *)(d))[0] = ((UINT8 *)(void *)(s))[7];\ + (( UINT8 *)(void *)(d))[1] = ((UINT8 *)(void *)(s))[6];\ + (( UINT8 *)(void *)(d))[2] = ((UINT8 *)(void *)(s))[5];\ + (( UINT8 *)(void *)(d))[3] = ((UINT8 *)(void *)(s))[4];\ + (( UINT8 *)(void *)(d))[4] = ((UINT8 *)(void *)(s))[3];\ + (( UINT8 *)(void *)(d))[5] = ((UINT8 *)(void *)(s))[2];\ + (( UINT8 *)(void *)(d))[6] = ((UINT8 *)(void *)(s))[1];\ + (( UINT8 *)(void *)(d))[7] = ((UINT8 *)(void *)(s))[0];} +#else +/* + * Macros for little-endian machines + */ + +#ifndef ACPI_MISALIGNMENT_NOT_SUPPORTED + +/* The hardware supports unaligned transfers, just do the little-endian move */ + +/* 16-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_16_TO_16(d, s) *(UINT16 *)(void *)(d) = *(UINT16 *)(void *)(s) +#define ACPI_MOVE_16_TO_32(d, s) *(UINT32 *)(void *)(d) = *(UINT16 *)(void *)(s) +#define ACPI_MOVE_16_TO_64(d, s) *(UINT64 *)(void *)(d) = *(UINT16 *)(void *)(s) + +/* 32-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_32_TO_16(d, s) ACPI_MOVE_16_TO_16(d, s) /* Truncate to 16 */ +#define ACPI_MOVE_32_TO_32(d, s) *(UINT32 *)(void *)(d) = *(UINT32 *)(void *)(s) +#define ACPI_MOVE_32_TO_64(d, s) *(UINT64 *)(void *)(d) = *(UINT32 *)(void *)(s) + +/* 64-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_64_TO_16(d, s) ACPI_MOVE_16_TO_16(d, s) /* Truncate to 16 */ +#define ACPI_MOVE_64_TO_32(d, s) ACPI_MOVE_32_TO_32(d, s) /* Truncate to 32 */ +#define ACPI_MOVE_64_TO_64(d, s) *(UINT64 *)(void *)(d) = *(UINT64 *)(void *)(s) + +#else +/* + * The hardware does not support unaligned transfers. We must move the + * data one byte at a time. These macros work whether the source or + * the destination (or both) is/are unaligned. (Little-endian move) + */ + +/* 16-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_16_TO_16(d, s) {(( UINT8 *)(void *)(d))[0] = ((UINT8 *)(void *)(s))[0];\ + (( UINT8 *)(void *)(d))[1] = ((UINT8 *)(void *)(s))[1];} + +#define ACPI_MOVE_16_TO_32(d, s) {(*(UINT32 *)(void *)(d)) = 0; ACPI_MOVE_16_TO_16(d, s);} +#define ACPI_MOVE_16_TO_64(d, s) {(*(UINT64 *)(void *)(d)) = 0; ACPI_MOVE_16_TO_16(d, s);} + +/* 32-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_32_TO_16(d, s) ACPI_MOVE_16_TO_16(d, s) /* Truncate to 16 */ + +#define ACPI_MOVE_32_TO_32(d, s) {(( UINT8 *)(void *)(d))[0] = ((UINT8 *)(void *)(s))[0];\ + (( UINT8 *)(void *)(d))[1] = ((UINT8 *)(void *)(s))[1];\ + (( UINT8 *)(void *)(d))[2] = ((UINT8 *)(void *)(s))[2];\ + (( UINT8 *)(void *)(d))[3] = ((UINT8 *)(void *)(s))[3];} + +#define ACPI_MOVE_32_TO_64(d, s) {(*(UINT64 *)(void *)(d)) = 0; ACPI_MOVE_32_TO_32(d, s);} + +/* 64-bit source, 16/32/64 destination */ + +#define ACPI_MOVE_64_TO_16(d, s) ACPI_MOVE_16_TO_16(d, s) /* Truncate to 16 */ +#define ACPI_MOVE_64_TO_32(d, s) ACPI_MOVE_32_TO_32(d, s) /* Truncate to 32 */ +#define ACPI_MOVE_64_TO_64(d, s) {(( UINT8 *)(void *)(d))[0] = ((UINT8 *)(void *)(s))[0];\ + (( UINT8 *)(void *)(d))[1] = ((UINT8 *)(void *)(s))[1];\ + (( UINT8 *)(void *)(d))[2] = ((UINT8 *)(void *)(s))[2];\ + (( UINT8 *)(void *)(d))[3] = ((UINT8 *)(void *)(s))[3];\ + (( UINT8 *)(void *)(d))[4] = ((UINT8 *)(void *)(s))[4];\ + (( UINT8 *)(void *)(d))[5] = ((UINT8 *)(void *)(s))[5];\ + (( UINT8 *)(void *)(d))[6] = ((UINT8 *)(void *)(s))[6];\ + (( UINT8 *)(void *)(d))[7] = ((UINT8 *)(void *)(s))[7];} +#endif +#endif + + +/* + * Fast power-of-two math macros for non-optimized compilers + */ +#define _ACPI_DIV(value, PowerOf2) ((UINT32) ((value) >> (PowerOf2))) +#define _ACPI_MUL(value, PowerOf2) ((UINT32) ((value) << (PowerOf2))) +#define _ACPI_MOD(value, Divisor) ((UINT32) ((value) & ((Divisor) -1))) + +#define ACPI_DIV_2(a) _ACPI_DIV(a, 1) +#define ACPI_MUL_2(a) _ACPI_MUL(a, 1) +#define ACPI_MOD_2(a) _ACPI_MOD(a, 2) + +#define ACPI_DIV_4(a) _ACPI_DIV(a, 2) +#define ACPI_MUL_4(a) _ACPI_MUL(a, 2) +#define ACPI_MOD_4(a) _ACPI_MOD(a, 4) + +#define ACPI_DIV_8(a) _ACPI_DIV(a, 3) +#define ACPI_MUL_8(a) _ACPI_MUL(a, 3) +#define ACPI_MOD_8(a) _ACPI_MOD(a, 8) + +#define ACPI_DIV_16(a) _ACPI_DIV(a, 4) +#define ACPI_MUL_16(a) _ACPI_MUL(a, 4) +#define ACPI_MOD_16(a) _ACPI_MOD(a, 16) + +#define ACPI_DIV_32(a) _ACPI_DIV(a, 5) +#define ACPI_MUL_32(a) _ACPI_MUL(a, 5) +#define ACPI_MOD_32(a) _ACPI_MOD(a, 32) + +/* Test for ASCII character */ + +#define ACPI_IS_ASCII(c) ((c) < 0x80) + +/* Signed integers */ + +#define ACPI_SIGN_POSITIVE 0 +#define ACPI_SIGN_NEGATIVE 1 + + +/* + * Rounding macros (Power of two boundaries only) + */ +#define ACPI_ROUND_DOWN(value, boundary) (((ACPI_SIZE)(value)) & \ + (~(((ACPI_SIZE) boundary)-1))) + +#define ACPI_ROUND_UP(value, boundary) ((((ACPI_SIZE)(value)) + \ + (((ACPI_SIZE) boundary)-1)) & \ + (~(((ACPI_SIZE) boundary)-1))) + +/* Note: sizeof(ACPI_SIZE) evaluates to either 4 or 8 (32- vs 64-bit mode) */ + +#define ACPI_ROUND_DOWN_TO_32BIT(a) ACPI_ROUND_DOWN(a, 4) +#define ACPI_ROUND_DOWN_TO_64BIT(a) ACPI_ROUND_DOWN(a, 8) +#define ACPI_ROUND_DOWN_TO_NATIVE_WORD(a) ACPI_ROUND_DOWN(a, sizeof(ACPI_SIZE)) + +#define ACPI_ROUND_UP_TO_32BIT(a) ACPI_ROUND_UP(a, 4) +#define ACPI_ROUND_UP_TO_64BIT(a) ACPI_ROUND_UP(a, 8) +#define ACPI_ROUND_UP_TO_NATIVE_WORD(a) ACPI_ROUND_UP(a, sizeof(ACPI_SIZE)) + +#define ACPI_ROUND_BITS_UP_TO_BYTES(a) ACPI_DIV_8((a) + 7) +#define ACPI_ROUND_BITS_DOWN_TO_BYTES(a) ACPI_DIV_8((a)) + +#define ACPI_ROUND_UP_TO_1K(a) (((a) + 1023) >> 10) + +/* Generic (non-power-of-two) rounding */ + +#define ACPI_ROUND_UP_TO(value, boundary) (((value) + ((boundary)-1)) / (boundary)) + +#define ACPI_IS_MISALIGNED(value) (((ACPI_SIZE) value) & (sizeof(ACPI_SIZE)-1)) + +/* Generic bit manipulation */ + +#ifndef ACPI_USE_NATIVE_BIT_FINDER + +#define __ACPI_FIND_LAST_BIT_2(a, r) ((((UINT8) (a)) & 0x02) ? (r)+1 : (r)) +#define __ACPI_FIND_LAST_BIT_4(a, r) ((((UINT8) (a)) & 0x0C) ? \ + __ACPI_FIND_LAST_BIT_2 ((a)>>2, (r)+2) : \ + __ACPI_FIND_LAST_BIT_2 ((a), (r))) +#define __ACPI_FIND_LAST_BIT_8(a, r) ((((UINT8) (a)) & 0xF0) ? \ + __ACPI_FIND_LAST_BIT_4 ((a)>>4, (r)+4) : \ + __ACPI_FIND_LAST_BIT_4 ((a), (r))) +#define __ACPI_FIND_LAST_BIT_16(a, r) ((((UINT16) (a)) & 0xFF00) ? \ + __ACPI_FIND_LAST_BIT_8 ((a)>>8, (r)+8) : \ + __ACPI_FIND_LAST_BIT_8 ((a), (r))) +#define __ACPI_FIND_LAST_BIT_32(a, r) ((((UINT32) (a)) & 0xFFFF0000) ? \ + __ACPI_FIND_LAST_BIT_16 ((a)>>16, (r)+16) : \ + __ACPI_FIND_LAST_BIT_16 ((a), (r))) +#define __ACPI_FIND_LAST_BIT_64(a, r) ((((UINT64) (a)) & 0xFFFFFFFF00000000) ? \ + __ACPI_FIND_LAST_BIT_32 ((a)>>32, (r)+32) : \ + __ACPI_FIND_LAST_BIT_32 ((a), (r))) + +#define ACPI_FIND_LAST_BIT_8(a) ((a) ? __ACPI_FIND_LAST_BIT_8 (a, 1) : 0) +#define ACPI_FIND_LAST_BIT_16(a) ((a) ? __ACPI_FIND_LAST_BIT_16 (a, 1) : 0) +#define ACPI_FIND_LAST_BIT_32(a) ((a) ? __ACPI_FIND_LAST_BIT_32 (a, 1) : 0) +#define ACPI_FIND_LAST_BIT_64(a) ((a) ? __ACPI_FIND_LAST_BIT_64 (a, 1) : 0) + +#define __ACPI_FIND_FIRST_BIT_2(a, r) ((((UINT8) (a)) & 0x01) ? (r) : (r)+1) +#define __ACPI_FIND_FIRST_BIT_4(a, r) ((((UINT8) (a)) & 0x03) ? \ + __ACPI_FIND_FIRST_BIT_2 ((a), (r)) : \ + __ACPI_FIND_FIRST_BIT_2 ((a)>>2, (r)+2)) +#define __ACPI_FIND_FIRST_BIT_8(a, r) ((((UINT8) (a)) & 0x0F) ? \ + __ACPI_FIND_FIRST_BIT_4 ((a), (r)) : \ + __ACPI_FIND_FIRST_BIT_4 ((a)>>4, (r)+4)) +#define __ACPI_FIND_FIRST_BIT_16(a, r) ((((UINT16) (a)) & 0x00FF) ? \ + __ACPI_FIND_FIRST_BIT_8 ((a), (r)) : \ + __ACPI_FIND_FIRST_BIT_8 ((a)>>8, (r)+8)) +#define __ACPI_FIND_FIRST_BIT_32(a, r) ((((UINT32) (a)) & 0x0000FFFF) ? \ + __ACPI_FIND_FIRST_BIT_16 ((a), (r)) : \ + __ACPI_FIND_FIRST_BIT_16 ((a)>>16, (r)+16)) +#define __ACPI_FIND_FIRST_BIT_64(a, r) ((((UINT64) (a)) & 0x00000000FFFFFFFF) ? \ + __ACPI_FIND_FIRST_BIT_32 ((a), (r)) : \ + __ACPI_FIND_FIRST_BIT_32 ((a)>>32, (r)+32)) + +#define ACPI_FIND_FIRST_BIT_8(a) ((a) ? __ACPI_FIND_FIRST_BIT_8 (a, 1) : 0) +#define ACPI_FIND_FIRST_BIT_16(a) ((a) ? __ACPI_FIND_FIRST_BIT_16 (a, 1) : 0) +#define ACPI_FIND_FIRST_BIT_32(a) ((a) ? __ACPI_FIND_FIRST_BIT_32 (a, 1) : 0) +#define ACPI_FIND_FIRST_BIT_64(a) ((a) ? __ACPI_FIND_FIRST_BIT_64 (a, 1) : 0) + +#endif /* ACPI_USE_NATIVE_BIT_FINDER */ + +/* Generic (power-of-two) rounding */ + +#define ACPI_ROUND_UP_POWER_OF_TWO_8(a) ((UINT8) \ + (((UINT16) 1) << ACPI_FIND_LAST_BIT_8 ((a) - 1))) +#define ACPI_ROUND_DOWN_POWER_OF_TWO_8(a) ((UINT8) \ + (((UINT16) 1) << (ACPI_FIND_LAST_BIT_8 ((a)) - 1))) +#define ACPI_ROUND_UP_POWER_OF_TWO_16(a) ((UINT16) \ + (((UINT32) 1) << ACPI_FIND_LAST_BIT_16 ((a) - 1))) +#define ACPI_ROUND_DOWN_POWER_OF_TWO_16(a) ((UINT16) \ + (((UINT32) 1) << (ACPI_FIND_LAST_BIT_16 ((a)) - 1))) +#define ACPI_ROUND_UP_POWER_OF_TWO_32(a) ((UINT32) \ + (((UINT64) 1) << ACPI_FIND_LAST_BIT_32 ((a) - 1))) +#define ACPI_ROUND_DOWN_POWER_OF_TWO_32(a) ((UINT32) \ + (((UINT64) 1) << (ACPI_FIND_LAST_BIT_32 ((a)) - 1))) +#define ACPI_IS_ALIGNED(a, s) (((a) & ((s) - 1)) == 0) +#define ACPI_IS_POWER_OF_TWO(a) ACPI_IS_ALIGNED(a, a) + +/* + * Bitmask creation + * Bit positions start at zero. + * MASK_BITS_ABOVE creates a mask starting AT the position and above + * MASK_BITS_BELOW creates a mask starting one bit BELOW the position + * MASK_BITS_ABOVE/BELOW accepts a bit offset to create a mask + * MASK_BITS_ABOVE/BELOW_32/64 accepts a bit width to create a mask + * Note: The ACPI_INTEGER_BIT_SIZE check is used to bypass compiler + * differences with the shift operator + */ +#define ACPI_MASK_BITS_ABOVE(position) (~((ACPI_UINT64_MAX) << ((UINT32) (position)))) +#define ACPI_MASK_BITS_BELOW(position) ((ACPI_UINT64_MAX) << ((UINT32) (position))) +#define ACPI_MASK_BITS_ABOVE_32(width) ((UINT32) ACPI_MASK_BITS_ABOVE(width)) +#define ACPI_MASK_BITS_BELOW_32(width) ((UINT32) ACPI_MASK_BITS_BELOW(width)) +#define ACPI_MASK_BITS_ABOVE_64(width) ((width) == ACPI_INTEGER_BIT_SIZE ? \ + ACPI_UINT64_MAX : \ + ACPI_MASK_BITS_ABOVE(width)) +#define ACPI_MASK_BITS_BELOW_64(width) ((width) == ACPI_INTEGER_BIT_SIZE ? \ + (UINT64) 0 : \ + ACPI_MASK_BITS_BELOW(width)) + +/* Bitfields within ACPI registers */ + +#define ACPI_REGISTER_PREPARE_BITS(Val, Pos, Mask) \ + ((Val << Pos) & Mask) + +#define ACPI_REGISTER_INSERT_VALUE(Reg, Pos, Mask, Val) \ + Reg = (Reg & (~(Mask))) | ACPI_REGISTER_PREPARE_BITS(Val, Pos, Mask) + +#define ACPI_INSERT_BITS(Target, Mask, Source) \ + Target = ((Target & (~(Mask))) | (Source & Mask)) + +/* Generic bitfield macros and masks */ + +#define ACPI_GET_BITS(SourcePtr, Position, Mask) \ + ((*(SourcePtr) >> (Position)) & (Mask)) + +#define ACPI_SET_BITS(TargetPtr, Position, Mask, Value) \ + (*(TargetPtr) |= (((Value) & (Mask)) << (Position))) + +#define ACPI_1BIT_MASK 0x00000001 +#define ACPI_2BIT_MASK 0x00000003 +#define ACPI_3BIT_MASK 0x00000007 +#define ACPI_4BIT_MASK 0x0000000F +#define ACPI_5BIT_MASK 0x0000001F +#define ACPI_6BIT_MASK 0x0000003F +#define ACPI_7BIT_MASK 0x0000007F +#define ACPI_8BIT_MASK 0x000000FF +#define ACPI_16BIT_MASK 0x0000FFFF +#define ACPI_24BIT_MASK 0x00FFFFFF + +/* Macros to extract flag bits from position zero */ + +#define ACPI_GET_1BIT_FLAG(Value) ((Value) & ACPI_1BIT_MASK) +#define ACPI_GET_2BIT_FLAG(Value) ((Value) & ACPI_2BIT_MASK) +#define ACPI_GET_3BIT_FLAG(Value) ((Value) & ACPI_3BIT_MASK) +#define ACPI_GET_4BIT_FLAG(Value) ((Value) & ACPI_4BIT_MASK) + +/* Macros to extract flag bits from position one and above */ + +#define ACPI_EXTRACT_1BIT_FLAG(Field, Position) (ACPI_GET_1BIT_FLAG ((Field) >> Position)) +#define ACPI_EXTRACT_2BIT_FLAG(Field, Position) (ACPI_GET_2BIT_FLAG ((Field) >> Position)) +#define ACPI_EXTRACT_3BIT_FLAG(Field, Position) (ACPI_GET_3BIT_FLAG ((Field) >> Position)) +#define ACPI_EXTRACT_4BIT_FLAG(Field, Position) (ACPI_GET_4BIT_FLAG ((Field) >> Position)) + +/* ACPI Pathname helpers */ + +#define ACPI_IS_ROOT_PREFIX(c) ((c) == (UINT8) 0x5C) /* Backslash */ +#define ACPI_IS_PARENT_PREFIX(c) ((c) == (UINT8) 0x5E) /* Carat */ +#define ACPI_IS_PATH_SEPARATOR(c) ((c) == (UINT8) 0x2E) /* Period (dot) */ + +/* + * An object of type ACPI_NAMESPACE_NODE can appear in some contexts + * where a pointer to an object of type ACPI_OPERAND_OBJECT can also + * appear. This macro is used to distinguish them. + * + * The "DescriptorType" field is the second field in both structures. + */ +#define ACPI_GET_DESCRIPTOR_PTR(d) (((ACPI_DESCRIPTOR *)(void *)(d))->Common.CommonPointer) +#define ACPI_SET_DESCRIPTOR_PTR(d, p) (((ACPI_DESCRIPTOR *)(void *)(d))->Common.CommonPointer = (p)) +#define ACPI_GET_DESCRIPTOR_TYPE(d) (((ACPI_DESCRIPTOR *)(void *)(d))->Common.DescriptorType) +#define ACPI_SET_DESCRIPTOR_TYPE(d, t) (((ACPI_DESCRIPTOR *)(void *)(d))->Common.DescriptorType = (t)) + +/* + * Macros for the master AML opcode table + */ +#if defined (ACPI_DISASSEMBLER) || defined (ACPI_DEBUG_OUTPUT) +#define ACPI_OP(Name, PArgs, IArgs, ObjType, Class, Type, Flags) \ + {Name, (UINT32)(PArgs), (UINT32)(IArgs), (UINT32)(Flags), ObjType, Class, Type} +#else +#define ACPI_OP(Name, PArgs, IArgs, ObjType, Class, Type, Flags) \ + {(UINT32)(PArgs), (UINT32)(IArgs), (UINT32)(Flags), ObjType, Class, Type} +#endif + +#define ARG_TYPE_WIDTH 5 +#define ARG_1(x) ((UINT32)(x)) +#define ARG_2(x) ((UINT32)(x) << (1 * ARG_TYPE_WIDTH)) +#define ARG_3(x) ((UINT32)(x) << (2 * ARG_TYPE_WIDTH)) +#define ARG_4(x) ((UINT32)(x) << (3 * ARG_TYPE_WIDTH)) +#define ARG_5(x) ((UINT32)(x) << (4 * ARG_TYPE_WIDTH)) +#define ARG_6(x) ((UINT32)(x) << (5 * ARG_TYPE_WIDTH)) + +#define ARGI_LIST1(a) (ARG_1(a)) +#define ARGI_LIST2(a, b) (ARG_1(b)|ARG_2(a)) +#define ARGI_LIST3(a, b, c) (ARG_1(c)|ARG_2(b)|ARG_3(a)) +#define ARGI_LIST4(a, b, c, d) (ARG_1(d)|ARG_2(c)|ARG_3(b)|ARG_4(a)) +#define ARGI_LIST5(a, b, c, d, e) (ARG_1(e)|ARG_2(d)|ARG_3(c)|ARG_4(b)|ARG_5(a)) +#define ARGI_LIST6(a, b, c, d, e, f) (ARG_1(f)|ARG_2(e)|ARG_3(d)|ARG_4(c)|ARG_5(b)|ARG_6(a)) + +#define ARGP_LIST1(a) (ARG_1(a)) +#define ARGP_LIST2(a, b) (ARG_1(a)|ARG_2(b)) +#define ARGP_LIST3(a, b, c) (ARG_1(a)|ARG_2(b)|ARG_3(c)) +#define ARGP_LIST4(a, b, c, d) (ARG_1(a)|ARG_2(b)|ARG_3(c)|ARG_4(d)) +#define ARGP_LIST5(a, b, c, d, e) (ARG_1(a)|ARG_2(b)|ARG_3(c)|ARG_4(d)|ARG_5(e)) +#define ARGP_LIST6(a, b, c, d, e, f) (ARG_1(a)|ARG_2(b)|ARG_3(c)|ARG_4(d)|ARG_5(e)|ARG_6(f)) + +#define GET_CURRENT_ARG_TYPE(List) (List & ((UINT32) 0x1F)) +#define INCREMENT_ARG_LIST(List) (List >>= ((UINT32) ARG_TYPE_WIDTH)) + +/* + * Ascii error messages can be configured out + */ +#ifndef ACPI_NO_ERROR_MESSAGES +/* + * Error reporting. The callers module and line number are inserted by AE_INFO, + * the plist contains a set of parens to allow variable-length lists. + * These macros are used for both the debug and non-debug versions of the code. + */ +#define ACPI_ERROR_NAMESPACE(s, p, e) AcpiUtPrefixedNamespaceError (AE_INFO, s, p, e); +#define ACPI_ERROR_METHOD(s, n, p, e) AcpiUtMethodError (AE_INFO, s, n, p, e); +#define ACPI_WARN_PREDEFINED(plist) AcpiUtPredefinedWarning plist +#define ACPI_INFO_PREDEFINED(plist) AcpiUtPredefinedInfo plist +#define ACPI_BIOS_ERROR_PREDEFINED(plist) AcpiUtPredefinedBiosError plist +#define ACPI_ERROR_ONLY(s) s + +#else + +/* No error messages */ + +#define ACPI_ERROR_NAMESPACE(s, p, e) +#define ACPI_ERROR_METHOD(s, n, p, e) +#define ACPI_WARN_PREDEFINED(plist) +#define ACPI_INFO_PREDEFINED(plist) +#define ACPI_BIOS_ERROR_PREDEFINED(plist) +#define ACPI_ERROR_ONLY(s) + +#endif /* ACPI_NO_ERROR_MESSAGES */ + +#if (!ACPI_REDUCED_HARDWARE) +#define ACPI_HW_OPTIONAL_FUNCTION(addr) addr +#else +#define ACPI_HW_OPTIONAL_FUNCTION(addr) NULL +#endif + + +/* + * Macros used for ACPICA utilities only + */ + +/* Generate a UUID */ + +#define ACPI_INIT_UUID(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7) \ + (a) & 0xFF, ((a) >> 8) & 0xFF, ((a) >> 16) & 0xFF, ((a) >> 24) & 0xFF, \ + (b) & 0xFF, ((b) >> 8) & 0xFF, \ + (c) & 0xFF, ((c) >> 8) & 0xFF, \ + (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) + +#define ACPI_IS_OCTAL_DIGIT(d) (((char)(d) >= '0') && ((char)(d) <= '7')) + + +/* + * Macors used for the ASL-/ASL+ converter utility + */ +#ifdef ACPI_ASL_COMPILER + +#define ASL_CV_LABEL_FILENODE(a) CvLabelFileNode(a); +#define ASL_CV_CAPTURE_COMMENTS_ONLY(a) CvCaptureCommentsOnly (a); +#define ASL_CV_CAPTURE_COMMENTS(a) CvCaptureComments (a); +#define ASL_CV_TRANSFER_COMMENTS(a) CvTransferComments (a); +#define ASL_CV_CLOSE_PAREN(a,b) CvCloseParenWriteComment(a,b); +#define ASL_CV_CLOSE_BRACE(a,b) CvCloseBraceWriteComment(a,b); +#define ASL_CV_SWITCH_FILES(a,b) CvSwitchFiles(a,b); +#define ASL_CV_CLEAR_OP_COMMENTS(a) CvClearOpComments(a); +#define ASL_CV_PRINT_ONE_COMMENT(a,b,c,d) CvPrintOneCommentType (a,b,c,d); +#define ASL_CV_PRINT_ONE_COMMENT_LIST(a,b) CvPrintOneCommentList (a,b); +#define ASL_CV_FILE_HAS_SWITCHED(a) CvFileHasSwitched(a) +#define ASL_CV_INIT_FILETREE(a,b,c) CvInitFileTree(a,b,c); + +#else + +#define ASL_CV_LABEL_FILENODE(a) +#define ASL_CV_CAPTURE_COMMENTS_ONLY(a) +#define ASL_CV_CAPTURE_COMMENTS(a) +#define ASL_CV_TRANSFER_COMMENTS(a) +#define ASL_CV_CLOSE_PAREN(a,b) AcpiOsPrintf (")"); +#define ASL_CV_CLOSE_BRACE(a,b) AcpiOsPrintf ("}"); +#define ASL_CV_SWITCH_FILES(a,b) +#define ASL_CV_CLEAR_OP_COMMENTS(a) +#define ASL_CV_PRINT_ONE_COMMENT(a,b,c,d) +#define ASL_CV_PRINT_ONE_COMMENT_LIST(a,b) +#define ASL_CV_FILE_HAS_SWITCHED(a) 0 +#define ASL_CV_INIT_FILETREE(a,b,c) + +#endif + +#endif /* ACMACROS_H */ diff --git a/ports/acpica/include/acnames.h b/ports/acpica/include/acnames.h new file mode 100644 index 0000000..506ff62 --- /dev/null +++ b/ports/acpica/include/acnames.h @@ -0,0 +1,204 @@ +/****************************************************************************** + * + * Name: acnames.h - Global names and strings + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACNAMES_H__ +#define __ACNAMES_H__ + +/* Method names - these methods can appear anywhere in the namespace */ + +#define METHOD_NAME__ADR "_ADR" +#define METHOD_NAME__AEI "_AEI" +#define METHOD_NAME__BBN "_BBN" +#define METHOD_NAME__CBA "_CBA" +#define METHOD_NAME__CID "_CID" +#define METHOD_NAME__CLS "_CLS" +#define METHOD_NAME__CRS "_CRS" +#define METHOD_NAME__DDN "_DDN" +#define METHOD_NAME__DMA "_DMA" +#define METHOD_NAME__HID "_HID" +#define METHOD_NAME__INI "_INI" +#define METHOD_NAME__PLD "_PLD" +#define METHOD_NAME__DSD "_DSD" +#define METHOD_NAME__PRS "_PRS" +#define METHOD_NAME__PRT "_PRT" +#define METHOD_NAME__PRW "_PRW" +#define METHOD_NAME__PS0 "_PS0" +#define METHOD_NAME__PS1 "_PS1" +#define METHOD_NAME__PS2 "_PS2" +#define METHOD_NAME__PS3 "_PS3" +#define METHOD_NAME__REG "_REG" +#define METHOD_NAME__SB_ "_SB_" +#define METHOD_NAME__SEG "_SEG" +#define METHOD_NAME__SRS "_SRS" +#define METHOD_NAME__STA "_STA" +#define METHOD_NAME__SUB "_SUB" +#define METHOD_NAME__UID "_UID" + +/* Method names - these methods must appear at the namespace root */ + +#define METHOD_PATHNAME__PTS "\\_PTS" +#define METHOD_PATHNAME__SST "\\_SI._SST" +#define METHOD_PATHNAME__WAK "\\_WAK" + +/* Definitions of the predefined namespace names */ + +#define ACPI_UNKNOWN_NAME (UINT32) 0x3F3F3F3F /* Unknown name is "????" */ +#define ACPI_PREFIX_MIXED (UINT32) 0x69706341 /* "Acpi" */ +#define ACPI_PREFIX_LOWER (UINT32) 0x69706361 /* "acpi" */ + +/* Root name stuff */ + +#define ACPI_ROOT_NAME (UINT32) 0x5F5F5F5C /* Root name is "\___" */ +#define ACPI_ROOT_PATHNAME "\\___" +#define ACPI_NAMESPACE_ROOT "Namespace Root" +#define ACPI_NS_ROOT_PATH "\\" + +#endif /* __ACNAMES_H__ */ diff --git a/ports/acpica/include/acnamesp.h b/ports/acpica/include/acnamesp.h new file mode 100644 index 0000000..529fc6e --- /dev/null +++ b/ports/acpica/include/acnamesp.h @@ -0,0 +1,691 @@ +/****************************************************************************** + * + * Name: acnamesp.h - Namespace subcomponent prototypes and defines + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACNAMESP_H__ +#define __ACNAMESP_H__ + + +/* To search the entire name space, pass this as SearchBase */ + +#define ACPI_NS_ALL ((ACPI_HANDLE)0) + +/* + * Elements of AcpiNsProperties are bit significant + * and should be one-to-one with values of ACPI_OBJECT_TYPE + */ +#define ACPI_NS_NORMAL 0 +#define ACPI_NS_NEWSCOPE 1 /* a definition of this type opens a name scope */ +#define ACPI_NS_LOCAL 2 /* suppress search of enclosing scopes */ + +/* Flags for AcpiNsLookup, AcpiNsSearchAndEnter */ + +#define ACPI_NS_NO_UPSEARCH 0 +#define ACPI_NS_SEARCH_PARENT 0x01 +#define ACPI_NS_DONT_OPEN_SCOPE 0x02 +#define ACPI_NS_NO_PEER_SEARCH 0x04 +#define ACPI_NS_ERROR_IF_FOUND 0x08 +#define ACPI_NS_PREFIX_IS_SCOPE 0x10 +#define ACPI_NS_EXTERNAL 0x20 +#define ACPI_NS_TEMPORARY 0x40 +#define ACPI_NS_OVERRIDE_IF_FOUND 0x80 + +/* Flags for AcpiNsWalkNamespace */ + +#define ACPI_NS_WALK_NO_UNLOCK 0 +#define ACPI_NS_WALK_UNLOCK 0x01 +#define ACPI_NS_WALK_TEMP_NODES 0x02 + +/* Object is not a package element */ + +#define ACPI_NOT_PACKAGE_ELEMENT ACPI_UINT32_MAX +#define ACPI_ALL_PACKAGE_ELEMENTS (ACPI_UINT32_MAX-1) + +/* Always emit warning message, not dependent on node flags */ + +#define ACPI_WARN_ALWAYS 0 + + +/* + * nsinit - Namespace initialization + */ +ACPI_STATUS +AcpiNsInitializeObjects ( + void); + +ACPI_STATUS +AcpiNsInitializeDevices ( + UINT32 Flags); + +ACPI_STATUS +AcpiNsInitOnePackage ( + ACPI_HANDLE ObjHandle, + UINT32 Level, + void *Context, + void **ReturnValue); + +/* + * nsload - Namespace loading + */ +ACPI_STATUS +AcpiNsLoadNamespace ( + void); + +ACPI_STATUS +AcpiNsLoadTable ( + UINT32 TableIndex, + ACPI_NAMESPACE_NODE *Node); + + +/* + * nswalk - walk the namespace + */ +ACPI_STATUS +AcpiNsWalkNamespace ( + ACPI_OBJECT_TYPE Type, + ACPI_HANDLE StartObject, + UINT32 MaxDepth, + UINT32 Flags, + ACPI_WALK_CALLBACK DescendingCallback, + ACPI_WALK_CALLBACK AscendingCallback, + void *Context, + void **ReturnValue); + +ACPI_NAMESPACE_NODE * +AcpiNsGetNextNode ( + ACPI_NAMESPACE_NODE *Parent, + ACPI_NAMESPACE_NODE *Child); + +ACPI_NAMESPACE_NODE * +AcpiNsGetNextNodeTyped ( + ACPI_OBJECT_TYPE Type, + ACPI_NAMESPACE_NODE *Parent, + ACPI_NAMESPACE_NODE *Child); + +/* + * nsparse - table parsing + */ +ACPI_STATUS +AcpiNsParseTable ( + UINT32 TableIndex, + ACPI_NAMESPACE_NODE *StartNode); + +ACPI_STATUS +AcpiNsExecuteTable ( + UINT32 TableIndex, + ACPI_NAMESPACE_NODE *StartNode); + +ACPI_STATUS +AcpiNsOneCompleteParse ( + UINT32 PassNumber, + UINT32 TableIndex, + ACPI_NAMESPACE_NODE *StartNode); + + +/* + * nsaccess - Top-level namespace access + */ +ACPI_STATUS +AcpiNsRootInitialize ( + void); + +ACPI_STATUS +AcpiNsLookup ( + ACPI_GENERIC_STATE *ScopeInfo, + char *Name, + ACPI_OBJECT_TYPE Type, + ACPI_INTERPRETER_MODE InterpreterMode, + UINT32 Flags, + ACPI_WALK_STATE *WalkState, + ACPI_NAMESPACE_NODE **RetNode); + + +/* + * nsalloc - Named object allocation/deallocation + */ +ACPI_NAMESPACE_NODE * +AcpiNsCreateNode ( + UINT32 Name); + +void +AcpiNsDeleteNode ( + ACPI_NAMESPACE_NODE *Node); + +void +AcpiNsRemoveNode ( + ACPI_NAMESPACE_NODE *Node); + +void +AcpiNsDeleteNamespaceSubtree ( + ACPI_NAMESPACE_NODE *ParentHandle); + +void +AcpiNsDeleteNamespaceByOwner ( + ACPI_OWNER_ID OwnerId); + +void +AcpiNsDetachObject ( + ACPI_NAMESPACE_NODE *Node); + +void +AcpiNsDeleteChildren ( + ACPI_NAMESPACE_NODE *Parent); + +int +AcpiNsCompareNames ( + char *Name1, + char *Name2); + + +/* + * nsconvert - Dynamic object conversion routines + */ +ACPI_STATUS +AcpiNsConvertToInteger ( + ACPI_OPERAND_OBJECT *OriginalObject, + ACPI_OPERAND_OBJECT **ReturnObject); + +ACPI_STATUS +AcpiNsConvertToString ( + ACPI_OPERAND_OBJECT *OriginalObject, + ACPI_OPERAND_OBJECT **ReturnObject); + +ACPI_STATUS +AcpiNsConvertToBuffer ( + ACPI_OPERAND_OBJECT *OriginalObject, + ACPI_OPERAND_OBJECT **ReturnObject); + +ACPI_STATUS +AcpiNsConvertToUnicode ( + ACPI_NAMESPACE_NODE *Scope, + ACPI_OPERAND_OBJECT *OriginalObject, + ACPI_OPERAND_OBJECT **ReturnObject); + +ACPI_STATUS +AcpiNsConvertToResource ( + ACPI_NAMESPACE_NODE *Scope, + ACPI_OPERAND_OBJECT *OriginalObject, + ACPI_OPERAND_OBJECT **ReturnObject); + +ACPI_STATUS +AcpiNsConvertToReference ( + ACPI_NAMESPACE_NODE *Scope, + ACPI_OPERAND_OBJECT *OriginalObject, + ACPI_OPERAND_OBJECT **ReturnObject); + + +/* + * nsdump - Namespace dump/print utilities + */ +void +AcpiNsDumpTables ( + ACPI_HANDLE SearchBase, + UINT32 MaxDepth); + +void +AcpiNsDumpEntry ( + ACPI_HANDLE Handle, + UINT32 DebugLevel); + +void +AcpiNsDumpPathname ( + ACPI_HANDLE Handle, + const char *Msg, + UINT32 Level, + UINT32 Component); + +void +AcpiNsPrintPathname ( + UINT32 NumSegments, + const char *Pathname); + +ACPI_STATUS +AcpiNsDumpOneObject ( + ACPI_HANDLE ObjHandle, + UINT32 Level, + void *Context, + void **ReturnValue); + +void +AcpiNsDumpObjects ( + ACPI_OBJECT_TYPE Type, + UINT8 DisplayType, + UINT32 MaxDepth, + ACPI_OWNER_ID OwnerId, + ACPI_HANDLE StartHandle); + +void +AcpiNsDumpObjectPaths ( + ACPI_OBJECT_TYPE Type, + UINT8 DisplayType, + UINT32 MaxDepth, + ACPI_OWNER_ID OwnerId, + ACPI_HANDLE StartHandle); + + +/* + * nseval - Namespace evaluation functions + */ +ACPI_STATUS +AcpiNsEvaluate ( + ACPI_EVALUATE_INFO *Info); + +void +AcpiNsExecModuleCodeList ( + void); + + +/* + * nsarguments - Argument count/type checking for predefined/reserved names + */ +void +AcpiNsCheckArgumentCount ( + char *Pathname, + ACPI_NAMESPACE_NODE *Node, + UINT32 UserParamCount, + const ACPI_PREDEFINED_INFO *Info); + +void +AcpiNsCheckAcpiCompliance ( + char *Pathname, + ACPI_NAMESPACE_NODE *Node, + const ACPI_PREDEFINED_INFO *Predefined); + +void +AcpiNsCheckArgumentTypes ( + ACPI_EVALUATE_INFO *Info); + + +/* + * nspredef - Return value checking for predefined/reserved names + */ +ACPI_STATUS +AcpiNsCheckReturnValue ( + ACPI_NAMESPACE_NODE *Node, + ACPI_EVALUATE_INFO *Info, + UINT32 UserParamCount, + ACPI_STATUS ReturnStatus, + ACPI_OPERAND_OBJECT **ReturnObject); + +ACPI_STATUS +AcpiNsCheckObjectType ( + ACPI_EVALUATE_INFO *Info, + ACPI_OPERAND_OBJECT **ReturnObjectPtr, + UINT32 ExpectedBtypes, + UINT32 PackageIndex); + + +/* + * nsprepkg - Validation of predefined name packages + */ +ACPI_STATUS +AcpiNsCheckPackage ( + ACPI_EVALUATE_INFO *Info, + ACPI_OPERAND_OBJECT **ReturnObjectPtr); + + +/* + * nsnames - Name and Scope manipulation + */ +UINT32 +AcpiNsOpensScope ( + ACPI_OBJECT_TYPE Type); + +char * +AcpiNsGetExternalPathname ( + ACPI_NAMESPACE_NODE *Node); + +UINT32 +AcpiNsBuildNormalizedPath ( + ACPI_NAMESPACE_NODE *Node, + char *FullPath, + UINT32 PathSize, + BOOLEAN NoTrailing); + +char * +AcpiNsGetNormalizedPathname ( + ACPI_NAMESPACE_NODE *Node, + BOOLEAN NoTrailing); + +char * +AcpiNsBuildPrefixedPathname ( + ACPI_GENERIC_STATE *PrefixScope, + const char *InternalPath); + +char * +AcpiNsNameOfCurrentScope ( + ACPI_WALK_STATE *WalkState); + +ACPI_STATUS +AcpiNsHandleToName ( + ACPI_HANDLE TargetHandle, + ACPI_BUFFER *Buffer); + +ACPI_STATUS +AcpiNsHandleToPathname ( + ACPI_HANDLE TargetHandle, + ACPI_BUFFER *Buffer, + BOOLEAN NoTrailing); + +BOOLEAN +AcpiNsPatternMatch ( + ACPI_NAMESPACE_NODE *ObjNode, + char *SearchFor); + +ACPI_STATUS +AcpiNsGetNodeUnlocked ( + ACPI_NAMESPACE_NODE *PrefixNode, + const char *ExternalPathname, + UINT32 Flags, + ACPI_NAMESPACE_NODE **OutNode); + +ACPI_STATUS +AcpiNsGetNode ( + ACPI_NAMESPACE_NODE *PrefixNode, + const char *ExternalPathname, + UINT32 Flags, + ACPI_NAMESPACE_NODE **OutNode); + +ACPI_SIZE +AcpiNsGetPathnameLength ( + ACPI_NAMESPACE_NODE *Node); + + +/* + * nsobject - Object management for namespace nodes + */ +ACPI_STATUS +AcpiNsAttachObject ( + ACPI_NAMESPACE_NODE *Node, + ACPI_OPERAND_OBJECT *Object, + ACPI_OBJECT_TYPE Type); + +ACPI_OPERAND_OBJECT * +AcpiNsGetAttachedObject ( + ACPI_NAMESPACE_NODE *Node); + +ACPI_OPERAND_OBJECT * +AcpiNsGetSecondaryObject ( + ACPI_OPERAND_OBJECT *ObjDesc); + +ACPI_STATUS +AcpiNsAttachData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_OBJECT_HANDLER Handler, + void *Data); + +ACPI_STATUS +AcpiNsDetachData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_OBJECT_HANDLER Handler); + +ACPI_STATUS +AcpiNsGetAttachedData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_OBJECT_HANDLER Handler, + void **Data); + + +/* + * nsrepair - General return object repair for all + * predefined methods/objects + */ +ACPI_STATUS +AcpiNsSimpleRepair ( + ACPI_EVALUATE_INFO *Info, + UINT32 ExpectedBtypes, + UINT32 PackageIndex, + ACPI_OPERAND_OBJECT **ReturnObjectPtr); + +ACPI_STATUS +AcpiNsWrapWithPackage ( + ACPI_EVALUATE_INFO *Info, + ACPI_OPERAND_OBJECT *OriginalObject, + ACPI_OPERAND_OBJECT **ObjDescPtr); + +ACPI_STATUS +AcpiNsRepairNullElement ( + ACPI_EVALUATE_INFO *Info, + UINT32 ExpectedBtypes, + UINT32 PackageIndex, + ACPI_OPERAND_OBJECT **ReturnObjectPtr); + +void +AcpiNsRemoveNullElements ( + ACPI_EVALUATE_INFO *Info, + UINT8 PackageType, + ACPI_OPERAND_OBJECT *ObjDesc); + + +/* + * nsrepair2 - Return object repair for specific + * predefined methods/objects + */ +ACPI_STATUS +AcpiNsComplexRepairs ( + ACPI_EVALUATE_INFO *Info, + ACPI_NAMESPACE_NODE *Node, + ACPI_STATUS ValidateStatus, + ACPI_OPERAND_OBJECT **ReturnObjectPtr); + + +/* + * nssearch - Namespace searching and entry + */ +ACPI_STATUS +AcpiNsSearchAndEnter ( + UINT32 EntryName, + ACPI_WALK_STATE *WalkState, + ACPI_NAMESPACE_NODE *Node, + ACPI_INTERPRETER_MODE InterpreterMode, + ACPI_OBJECT_TYPE Type, + UINT32 Flags, + ACPI_NAMESPACE_NODE **RetNode); + +ACPI_STATUS +AcpiNsSearchOneScope ( + UINT32 EntryName, + ACPI_NAMESPACE_NODE *Node, + ACPI_OBJECT_TYPE Type, + ACPI_NAMESPACE_NODE **RetNode); + +void +AcpiNsInstallNode ( + ACPI_WALK_STATE *WalkState, + ACPI_NAMESPACE_NODE *ParentNode, + ACPI_NAMESPACE_NODE *Node, + ACPI_OBJECT_TYPE Type); + + +/* + * nsutils - Utility functions + */ +ACPI_OBJECT_TYPE +AcpiNsGetType ( + ACPI_NAMESPACE_NODE *Node); + +UINT32 +AcpiNsLocal ( + ACPI_OBJECT_TYPE Type); + +void +AcpiNsPrintNodePathname ( + ACPI_NAMESPACE_NODE *Node, + const char *Msg); + +ACPI_STATUS +AcpiNsBuildInternalName ( + ACPI_NAMESTRING_INFO *Info); + +void +AcpiNsGetInternalNameLength ( + ACPI_NAMESTRING_INFO *Info); + +ACPI_STATUS +AcpiNsInternalizeName ( + const char *DottedName, + char **ConvertedName); + +ACPI_STATUS +AcpiNsExternalizeName ( + UINT32 InternalNameLength, + const char *InternalName, + UINT32 *ConvertedNameLength, + char **ConvertedName); + +ACPI_NAMESPACE_NODE * +AcpiNsValidateHandle ( + ACPI_HANDLE Handle); + +void +AcpiNsTerminate ( + void); + +#endif /* __ACNAMESP_H__ */ diff --git a/ports/acpica/include/acobject.h b/ports/acpica/include/acobject.h new file mode 100644 index 0000000..824d208 --- /dev/null +++ b/ports/acpica/include/acobject.h @@ -0,0 +1,703 @@ +/****************************************************************************** + * + * Name: acobject.h - Definition of ACPI_OPERAND_OBJECT (Internal object only) + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef _ACOBJECT_H +#define _ACOBJECT_H + +/* acpisrc:StructDefs -- for acpisrc conversion */ + + +/* + * The ACPI_OPERAND_OBJECT is used to pass AML operands from the dispatcher + * to the interpreter, and to keep track of the various handlers such as + * address space handlers and notify handlers. The object is a constant + * size in order to allow it to be cached and reused. + * + * Note: The object is optimized to be aligned and will not work if it is + * byte-packed. + */ +#if ACPI_MACHINE_WIDTH == 64 +#pragma pack(8) +#else +#pragma pack(4) +#endif + +/******************************************************************************* + * + * Common Descriptors + * + ******************************************************************************/ + +/* + * Common area for all objects. + * + * DescriptorType is used to differentiate between internal descriptors, and + * must be in the same place across all descriptors + * + * Note: The DescriptorType and Type fields must appear in the identical + * position in both the ACPI_NAMESPACE_NODE and ACPI_OPERAND_OBJECT + * structures. + */ +#define ACPI_OBJECT_COMMON_HEADER \ + union acpi_operand_object *NextObject; /* Objects linked to parent NS node */\ + UINT8 DescriptorType; /* To differentiate various internal objs */\ + UINT8 Type; /* ACPI_OBJECT_TYPE */\ + UINT16 ReferenceCount; /* For object deletion management */\ + UINT8 Flags; + /* + * Note: There are 3 bytes available here before the + * next natural alignment boundary (for both 32/64 cases) + */ + +/* Values for Flag byte above */ + +#define AOPOBJ_AML_CONSTANT 0x01 /* Integer is an AML constant */ +#define AOPOBJ_STATIC_POINTER 0x02 /* Data is part of an ACPI table, don't delete */ +#define AOPOBJ_DATA_VALID 0x04 /* Object is initialized and data is valid */ +#define AOPOBJ_OBJECT_INITIALIZED 0x08 /* Region is initialized */ +#define AOPOBJ_REG_CONNECTED 0x10 /* _REG was run */ +#define AOPOBJ_SETUP_COMPLETE 0x20 /* Region setup is complete */ +#define AOPOBJ_INVALID 0x40 /* Host OS won't allow a Region address */ + + +/****************************************************************************** + * + * Basic data types + * + *****************************************************************************/ + +typedef struct acpi_object_common +{ + ACPI_OBJECT_COMMON_HEADER + +} ACPI_OBJECT_COMMON; + + +typedef struct acpi_object_integer +{ + ACPI_OBJECT_COMMON_HEADER + UINT8 Fill[3]; /* Prevent warning on some compilers */ + UINT64 Value; + +} ACPI_OBJECT_INTEGER; + + +/* + * Note: The String and Buffer object must be identical through the + * pointer and length elements. There is code that depends on this. + * + * Fields common to both Strings and Buffers + */ +#define ACPI_COMMON_BUFFER_INFO(_Type) \ + _Type *Pointer; \ + UINT32 Length; + + +/* Null terminated, ASCII characters only */ + +typedef struct acpi_object_string +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_BUFFER_INFO (char) /* String in AML stream or allocated string */ + +} ACPI_OBJECT_STRING; + + +typedef struct acpi_object_buffer +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_BUFFER_INFO (UINT8) /* Buffer in AML stream or allocated buffer */ + UINT32 AmlLength; + UINT8 *AmlStart; + ACPI_NAMESPACE_NODE *Node; /* Link back to parent node */ + +} ACPI_OBJECT_BUFFER; + + +typedef struct acpi_object_package +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_NAMESPACE_NODE *Node; /* Link back to parent node */ + union acpi_operand_object **Elements; /* Array of pointers to AcpiObjects */ + UINT8 *AmlStart; + UINT32 AmlLength; + UINT32 Count; /* # of elements in package */ + +} ACPI_OBJECT_PACKAGE; + + +/****************************************************************************** + * + * Complex data types + * + *****************************************************************************/ + +typedef struct acpi_object_event +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_SEMAPHORE OsSemaphore; /* Actual OS synchronization object */ + +} ACPI_OBJECT_EVENT; + + +typedef struct acpi_object_mutex +{ + ACPI_OBJECT_COMMON_HEADER + UINT8 SyncLevel; /* 0-15, specified in Mutex() call */ + UINT16 AcquisitionDepth; /* Allow multiple Acquires, same thread */ + ACPI_MUTEX OsMutex; /* Actual OS synchronization object */ + ACPI_THREAD_ID ThreadId; /* Current owner of the mutex */ + struct acpi_thread_state *OwnerThread; /* Current owner of the mutex */ + union acpi_operand_object *Prev; /* Link for list of acquired mutexes */ + union acpi_operand_object *Next; /* Link for list of acquired mutexes */ + ACPI_NAMESPACE_NODE *Node; /* Containing namespace node */ + UINT8 OriginalSyncLevel; /* Owner's original sync level (0-15) */ + +} ACPI_OBJECT_MUTEX; + + +typedef struct acpi_object_region +{ + ACPI_OBJECT_COMMON_HEADER + UINT8 SpaceId; + ACPI_NAMESPACE_NODE *Node; /* Containing namespace node */ + union acpi_operand_object *Handler; /* Handler for region access */ + union acpi_operand_object *Next; + ACPI_PHYSICAL_ADDRESS Address; + UINT32 Length; + +} ACPI_OBJECT_REGION; + + +typedef struct acpi_object_method +{ + ACPI_OBJECT_COMMON_HEADER + UINT8 InfoFlags; + UINT8 ParamCount; + UINT8 SyncLevel; + union acpi_operand_object *Mutex; + union acpi_operand_object *Node; + UINT8 *AmlStart; + union + { + ACPI_INTERNAL_METHOD Implementation; + union acpi_operand_object *Handler; + } Dispatch; + + UINT32 AmlLength; + UINT8 ThreadCount; + ACPI_OWNER_ID OwnerId; + +} ACPI_OBJECT_METHOD; + +/* Flags for InfoFlags field above */ + +#define ACPI_METHOD_MODULE_LEVEL 0x01 /* Method is actually module-level code */ +#define ACPI_METHOD_INTERNAL_ONLY 0x02 /* Method is implemented internally (_OSI) */ +#define ACPI_METHOD_SERIALIZED 0x04 /* Method is serialized */ +#define ACPI_METHOD_SERIALIZED_PENDING 0x08 /* Method is to be marked serialized */ +#define ACPI_METHOD_IGNORE_SYNC_LEVEL 0x10 /* Method was auto-serialized at table load time */ +#define ACPI_METHOD_MODIFIED_NAMESPACE 0x20 /* Method modified the namespace */ + + +/****************************************************************************** + * + * Objects that can be notified. All share a common NotifyInfo area. + * + *****************************************************************************/ + +/* + * Common fields for objects that support ASL notifications + */ +#define ACPI_COMMON_NOTIFY_INFO \ + union acpi_operand_object *NotifyList[2]; /* Handlers for system/device notifies */\ + union acpi_operand_object *Handler; /* Handler for Address space */ + +/* COMMON NOTIFY for POWER, PROCESSOR, DEVICE, and THERMAL */ + +typedef struct acpi_object_notify_common +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_NOTIFY_INFO + +} ACPI_OBJECT_NOTIFY_COMMON; + + +typedef struct acpi_object_device +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_NOTIFY_INFO + ACPI_GPE_BLOCK_INFO *GpeBlock; + +} ACPI_OBJECT_DEVICE; + + +typedef struct acpi_object_power_resource +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_NOTIFY_INFO + UINT32 SystemLevel; + UINT32 ResourceOrder; + +} ACPI_OBJECT_POWER_RESOURCE; + + +typedef struct acpi_object_processor +{ + ACPI_OBJECT_COMMON_HEADER + + /* The next two fields take advantage of the 3-byte space before NOTIFY_INFO */ + + UINT8 ProcId; + UINT8 Length; + ACPI_COMMON_NOTIFY_INFO + ACPI_IO_ADDRESS Address; + +} ACPI_OBJECT_PROCESSOR; + + +typedef struct acpi_object_thermal_zone +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_NOTIFY_INFO + +} ACPI_OBJECT_THERMAL_ZONE; + + +/****************************************************************************** + * + * Fields. All share a common header/info field. + * + *****************************************************************************/ + +/* + * Common bitfield for the field objects + * "Field Datum" -- a datum from the actual field object + * "Buffer Datum" -- a datum from a user buffer, read from or to be written to the field + */ +#define ACPI_COMMON_FIELD_INFO \ + UINT8 FieldFlags; /* Access, update, and lock bits */\ + UINT8 Attribute; /* From AccessAs keyword */\ + UINT8 AccessByteWidth; /* Read/Write size in bytes */\ + ACPI_NAMESPACE_NODE *Node; /* Link back to parent node */\ + UINT32 BitLength; /* Length of field in bits */\ + UINT32 BaseByteOffset; /* Byte offset within containing object */\ + UINT32 Value; /* Value to store into the Bank or Index register */\ + UINT8 StartFieldBitOffset;/* Bit offset within first field datum (0-63) */\ + UINT8 AccessLength; /* For serial regions/fields */ + +/* COMMON FIELD (for BUFFER, REGION, BANK, and INDEX fields) */ + +typedef struct acpi_object_field_common +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_FIELD_INFO + union acpi_operand_object *RegionObj; /* Parent Operation Region object (REGION/BANK fields only) */ + +} ACPI_OBJECT_FIELD_COMMON; + + +typedef struct acpi_object_region_field +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_FIELD_INFO + UINT16 ResourceLength; + union acpi_operand_object *RegionObj; /* Containing OpRegion object */ + UINT8 *ResourceBuffer; /* ResourceTemplate for serial regions/fields */ + UINT16 PinNumberIndex; /* Index relative to previous Connection/Template */ + +} ACPI_OBJECT_REGION_FIELD; + + +typedef struct acpi_object_bank_field +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_FIELD_INFO + union acpi_operand_object *RegionObj; /* Containing OpRegion object */ + union acpi_operand_object *BankObj; /* BankSelect Register object */ + +} ACPI_OBJECT_BANK_FIELD; + + +typedef struct acpi_object_index_field +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_FIELD_INFO + + /* + * No "RegionObj" pointer needed since the Index and Data registers + * are each field definitions unto themselves. + */ + union acpi_operand_object *IndexObj; /* Index register */ + union acpi_operand_object *DataObj; /* Data register */ + +} ACPI_OBJECT_INDEX_FIELD; + + +/* The BufferField is different in that it is part of a Buffer, not an OpRegion */ + +typedef struct acpi_object_buffer_field +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_COMMON_FIELD_INFO + union acpi_operand_object *BufferObj; /* Containing Buffer object */ + +} ACPI_OBJECT_BUFFER_FIELD; + + +/****************************************************************************** + * + * Objects for handlers + * + *****************************************************************************/ + +typedef struct acpi_object_notify_handler +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_NAMESPACE_NODE *Node; /* Parent device */ + UINT32 HandlerType; /* Type: Device/System/Both */ + ACPI_NOTIFY_HANDLER Handler; /* Handler address */ + void *Context; + union acpi_operand_object *Next[2]; /* Device and System handler lists */ + +} ACPI_OBJECT_NOTIFY_HANDLER; + + +typedef struct acpi_object_addr_handler +{ + ACPI_OBJECT_COMMON_HEADER + UINT8 SpaceId; + UINT8 HandlerFlags; + ACPI_ADR_SPACE_HANDLER Handler; + ACPI_NAMESPACE_NODE *Node; /* Parent device */ + void *Context; + ACPI_ADR_SPACE_SETUP Setup; + union acpi_operand_object *RegionList; /* Regions using this handler */ + union acpi_operand_object *Next; + +} ACPI_OBJECT_ADDR_HANDLER; + +/* Flags for address handler (HandlerFlags) */ + +#define ACPI_ADDR_HANDLER_DEFAULT_INSTALLED 0x01 + + +/****************************************************************************** + * + * Special internal objects + * + *****************************************************************************/ + +/* + * The Reference object is used for these opcodes: + * Arg[0-6], Local[0-7], IndexOp, NameOp, RefOfOp, LoadOp, LoadTableOp, DebugOp + * The Reference.Class differentiates these types. + */ +typedef struct acpi_object_reference +{ + ACPI_OBJECT_COMMON_HEADER + UINT8 Class; /* Reference Class */ + UINT8 TargetType; /* Used for Index Op */ + UINT8 Resolved; /* Reference has been resolved to a value */ + void *Object; /* NameOp=>HANDLE to obj, IndexOp=>ACPI_OPERAND_OBJECT */ + ACPI_NAMESPACE_NODE *Node; /* RefOf or Namepath */ + union acpi_operand_object **Where; /* Target of Index */ + UINT8 *IndexPointer; /* Used for Buffers and Strings */ + UINT8 *Aml; /* Used for deferred resolution of the ref */ + UINT32 Value; /* Used for Local/Arg/Index/DdbHandle */ + +} ACPI_OBJECT_REFERENCE; + +/* Values for Reference.Class above */ + +typedef enum +{ + ACPI_REFCLASS_LOCAL = 0, /* Method local */ + ACPI_REFCLASS_ARG = 1, /* Method argument */ + ACPI_REFCLASS_REFOF = 2, /* Result of RefOf() TBD: Split to Ref/Node and Ref/OperandObj? */ + ACPI_REFCLASS_INDEX = 3, /* Result of Index() */ + ACPI_REFCLASS_TABLE = 4, /* DdbHandle - Load(), LoadTable() */ + ACPI_REFCLASS_NAME = 5, /* Reference to a named object */ + ACPI_REFCLASS_DEBUG = 6, /* Debug object */ + + ACPI_REFCLASS_MAX = 6 + +} ACPI_REFERENCE_CLASSES; + +/* + * Extra object is used as additional storage for types that + * have AML code in their declarations (TermArgs) that must be + * evaluated at run time. + * + * Currently: Region and FieldUnit types + */ +typedef struct acpi_object_extra +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_NAMESPACE_NODE *Method_REG; /* _REG method for this region (if any) */ + ACPI_NAMESPACE_NODE *ScopeNode; + void *RegionContext; /* Region-specific data */ + UINT8 *AmlStart; + UINT32 AmlLength; + +} ACPI_OBJECT_EXTRA; + + +/* Additional data that can be attached to namespace nodes */ + +typedef struct acpi_object_data +{ + ACPI_OBJECT_COMMON_HEADER + ACPI_OBJECT_HANDLER Handler; + void *Pointer; + +} ACPI_OBJECT_DATA; + + +/* Structure used when objects are cached for reuse */ + +typedef struct acpi_object_cache_list +{ + ACPI_OBJECT_COMMON_HEADER + union acpi_operand_object *Next; /* Link for object cache and internal lists*/ + +} ACPI_OBJECT_CACHE_LIST; + + +/****************************************************************************** + * + * ACPI_OPERAND_OBJECT Descriptor - a giant union of all of the above + * + *****************************************************************************/ + +typedef union acpi_operand_object +{ + ACPI_OBJECT_COMMON Common; + ACPI_OBJECT_INTEGER Integer; + ACPI_OBJECT_STRING String; + ACPI_OBJECT_BUFFER Buffer; + ACPI_OBJECT_PACKAGE Package; + ACPI_OBJECT_EVENT Event; + ACPI_OBJECT_METHOD Method; + ACPI_OBJECT_MUTEX Mutex; + ACPI_OBJECT_REGION Region; + ACPI_OBJECT_NOTIFY_COMMON CommonNotify; + ACPI_OBJECT_DEVICE Device; + ACPI_OBJECT_POWER_RESOURCE PowerResource; + ACPI_OBJECT_PROCESSOR Processor; + ACPI_OBJECT_THERMAL_ZONE ThermalZone; + ACPI_OBJECT_FIELD_COMMON CommonField; + ACPI_OBJECT_REGION_FIELD Field; + ACPI_OBJECT_BUFFER_FIELD BufferField; + ACPI_OBJECT_BANK_FIELD BankField; + ACPI_OBJECT_INDEX_FIELD IndexField; + ACPI_OBJECT_NOTIFY_HANDLER Notify; + ACPI_OBJECT_ADDR_HANDLER AddressSpace; + ACPI_OBJECT_REFERENCE Reference; + ACPI_OBJECT_EXTRA Extra; + ACPI_OBJECT_DATA Data; + ACPI_OBJECT_CACHE_LIST Cache; + + /* + * Add namespace node to union in order to simplify code that accepts both + * ACPI_OPERAND_OBJECTs and ACPI_NAMESPACE_NODEs. The structures share + * a common DescriptorType field in order to differentiate them. + */ + ACPI_NAMESPACE_NODE Node; + +} ACPI_OPERAND_OBJECT; + + +/****************************************************************************** + * + * ACPI_DESCRIPTOR - objects that share a common descriptor identifier + * + *****************************************************************************/ + +/* Object descriptor types */ + +#define ACPI_DESC_TYPE_CACHED 0x01 /* Used only when object is cached */ +#define ACPI_DESC_TYPE_STATE 0x02 +#define ACPI_DESC_TYPE_STATE_UPDATE 0x03 +#define ACPI_DESC_TYPE_STATE_PACKAGE 0x04 +#define ACPI_DESC_TYPE_STATE_CONTROL 0x05 +#define ACPI_DESC_TYPE_STATE_RPSCOPE 0x06 +#define ACPI_DESC_TYPE_STATE_PSCOPE 0x07 +#define ACPI_DESC_TYPE_STATE_WSCOPE 0x08 +#define ACPI_DESC_TYPE_STATE_RESULT 0x09 +#define ACPI_DESC_TYPE_STATE_NOTIFY 0x0A +#define ACPI_DESC_TYPE_STATE_THREAD 0x0B +#define ACPI_DESC_TYPE_WALK 0x0C +#define ACPI_DESC_TYPE_PARSER 0x0D +#define ACPI_DESC_TYPE_OPERAND 0x0E +#define ACPI_DESC_TYPE_NAMED 0x0F +#define ACPI_DESC_TYPE_MAX 0x0F + + +typedef struct acpi_common_descriptor +{ + void *CommonPointer; + UINT8 DescriptorType; /* To differentiate various internal objs */ + +} ACPI_COMMON_DESCRIPTOR; + +typedef union acpi_descriptor +{ + ACPI_COMMON_DESCRIPTOR Common; + ACPI_OPERAND_OBJECT Object; + ACPI_NAMESPACE_NODE Node; + ACPI_PARSE_OBJECT Op; + +} ACPI_DESCRIPTOR; + +#pragma pack() + +#endif /* _ACOBJECT_H */ diff --git a/ports/acpica/include/acopcode.h b/ports/acpica/include/acopcode.h new file mode 100644 index 0000000..6dbeac6 --- /dev/null +++ b/ports/acpica/include/acopcode.h @@ -0,0 +1,441 @@ +/****************************************************************************** + * + * Name: acopcode.h - AML opcode information for the AML parser and interpreter + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACOPCODE_H__ +#define __ACOPCODE_H__ + +#define MAX_EXTENDED_OPCODE 0x88 +#define NUM_EXTENDED_OPCODE (MAX_EXTENDED_OPCODE + 1) +#define MAX_INTERNAL_OPCODE +#define NUM_INTERNAL_OPCODE (MAX_INTERNAL_OPCODE + 1) + +/* Used for non-assigned opcodes */ + +#define _UNK 0x6B + +/* + * Reserved ASCII characters. Do not use any of these for + * internal opcodes, since they are used to differentiate + * name strings from AML opcodes + */ +#define _ASC 0x6C +#define _NAM 0x6C +#define _PFX 0x6D + + +/* + * All AML opcodes and the parse-time arguments for each. Used by the AML + * parser Each list is compressed into a 32-bit number and stored in the + * master opcode table (in psopcode.c). + */ +#define ARGP_ACCESSFIELD_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_ACQUIRE_OP ARGP_LIST2 (ARGP_SUPERNAME, ARGP_WORDDATA) +#define ARGP_ADD_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_ALIAS_OP ARGP_LIST2 (ARGP_NAMESTRING, ARGP_NAME) +#define ARGP_ARG0 ARG_NONE +#define ARGP_ARG1 ARG_NONE +#define ARGP_ARG2 ARG_NONE +#define ARGP_ARG3 ARG_NONE +#define ARGP_ARG4 ARG_NONE +#define ARGP_ARG5 ARG_NONE +#define ARGP_ARG6 ARG_NONE +#define ARGP_BANK_FIELD_OP ARGP_LIST6 (ARGP_PKGLENGTH, ARGP_NAMESTRING, ARGP_NAMESTRING,ARGP_TERMARG, ARGP_BYTEDATA, ARGP_FIELDLIST) +#define ARGP_BIT_AND_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_BIT_NAND_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_BIT_NOR_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_BIT_NOT_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_BIT_OR_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_BIT_XOR_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_BREAK_OP ARG_NONE +#define ARGP_BREAK_POINT_OP ARG_NONE +#define ARGP_BUFFER_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_TERMARG, ARGP_BYTELIST) +#define ARGP_BYTE_OP ARGP_LIST1 (ARGP_BYTEDATA) +#define ARGP_BYTELIST_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_COMMENT_OP ARGP_LIST2 (ARGP_BYTEDATA, ARGP_COMMENT) +#define ARGP_CONCAT_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_CONCAT_RES_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_COND_REF_OF_OP ARGP_LIST2 (ARGP_SIMPLENAME, ARGP_TARGET) +#define ARGP_CONNECTFIELD_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_CONTINUE_OP ARG_NONE +#define ARGP_COPY_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_SIMPLENAME) +#define ARGP_CREATE_BIT_FIELD_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_NAME) +#define ARGP_CREATE_BYTE_FIELD_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_NAME) +#define ARGP_CREATE_DWORD_FIELD_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_NAME) +#define ARGP_CREATE_FIELD_OP ARGP_LIST4 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TERMARG, ARGP_NAME) +#define ARGP_CREATE_QWORD_FIELD_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_NAME) +#define ARGP_CREATE_WORD_FIELD_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_NAME) +#define ARGP_DATA_REGION_OP ARGP_LIST4 (ARGP_NAME, ARGP_TERMARG, ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_DEBUG_OP ARG_NONE +#define ARGP_DECREMENT_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_DEREF_OF_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_DEVICE_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_NAME, ARGP_OBJLIST) +#define ARGP_DIVIDE_OP ARGP_LIST4 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET, ARGP_TARGET) +#define ARGP_DWORD_OP ARGP_LIST1 (ARGP_DWORDDATA) +#define ARGP_ELSE_OP ARGP_LIST2 (ARGP_PKGLENGTH, ARGP_TERMLIST) +#define ARGP_EVENT_OP ARGP_LIST1 (ARGP_NAME) +#define ARGP_EXTERNAL_OP ARGP_LIST3 (ARGP_NAME, ARGP_BYTEDATA, ARGP_BYTEDATA) +#define ARGP_FATAL_OP ARGP_LIST3 (ARGP_BYTEDATA, ARGP_DWORDDATA, ARGP_TERMARG) +#define ARGP_FIELD_OP ARGP_LIST4 (ARGP_PKGLENGTH, ARGP_NAMESTRING, ARGP_BYTEDATA, ARGP_FIELDLIST) +#define ARGP_FIND_SET_LEFT_BIT_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_FIND_SET_RIGHT_BIT_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_FROM_BCD_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_IF_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_TERMARG, ARGP_TERMLIST) +#define ARGP_INCREMENT_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_INDEX_FIELD_OP ARGP_LIST5 (ARGP_PKGLENGTH, ARGP_NAMESTRING, ARGP_NAMESTRING,ARGP_BYTEDATA, ARGP_FIELDLIST) +#define ARGP_INDEX_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_LAND_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LEQUAL_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LGREATER_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LGREATEREQUAL_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LLESS_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LLESSEQUAL_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LNOT_OP ARGP_LIST1 (ARGP_TERMARG) +#define ARGP_LNOTEQUAL_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LOAD_OP ARGP_LIST2 (ARGP_NAMESTRING, ARGP_SUPERNAME) +#define ARGP_LOAD_TABLE_OP ARGP_LIST6 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TERMARG, ARGP_TERMARG, ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_LOCAL0 ARG_NONE +#define ARGP_LOCAL1 ARG_NONE +#define ARGP_LOCAL2 ARG_NONE +#define ARGP_LOCAL3 ARG_NONE +#define ARGP_LOCAL4 ARG_NONE +#define ARGP_LOCAL5 ARG_NONE +#define ARGP_LOCAL6 ARG_NONE +#define ARGP_LOCAL7 ARG_NONE +#define ARGP_LOR_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_MATCH_OP ARGP_LIST6 (ARGP_TERMARG, ARGP_BYTEDATA, ARGP_TERMARG, ARGP_BYTEDATA, ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_METHOD_OP ARGP_LIST4 (ARGP_PKGLENGTH, ARGP_NAME, ARGP_BYTEDATA, ARGP_TERMLIST) +#define ARGP_METHODCALL_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_MID_OP ARGP_LIST4 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_MOD_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_MULTIPLY_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_MUTEX_OP ARGP_LIST2 (ARGP_NAME, ARGP_BYTEDATA) +#define ARGP_NAME_OP ARGP_LIST2 (ARGP_NAME, ARGP_DATAOBJ) +#define ARGP_NAMEDFIELD_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_NAMEPATH_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_NOOP_OP ARG_NONE +#define ARGP_NOTIFY_OP ARGP_LIST2 (ARGP_SUPERNAME, ARGP_TERMARG) +#define ARGP_OBJECT_TYPE_OP ARGP_LIST1 (ARGP_SIMPLENAME) +#define ARGP_ONE_OP ARG_NONE +#define ARGP_ONES_OP ARG_NONE +#define ARGP_PACKAGE_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_BYTEDATA, ARGP_DATAOBJLIST) +#define ARGP_POWER_RES_OP ARGP_LIST5 (ARGP_PKGLENGTH, ARGP_NAME, ARGP_BYTEDATA, ARGP_WORDDATA, ARGP_OBJLIST) +#define ARGP_PROCESSOR_OP ARGP_LIST6 (ARGP_PKGLENGTH, ARGP_NAME, ARGP_BYTEDATA, ARGP_DWORDDATA, ARGP_BYTEDATA, ARGP_OBJLIST) +#define ARGP_QWORD_OP ARGP_LIST1 (ARGP_QWORDDATA) +#define ARGP_REF_OF_OP ARGP_LIST1 (ARGP_SIMPLENAME) +#define ARGP_REGION_OP ARGP_LIST4 (ARGP_NAME, ARGP_BYTEDATA, ARGP_TERMARG, ARGP_TERMARG) +#define ARGP_RELEASE_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_RESERVEDFIELD_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_RESET_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_RETURN_OP ARGP_LIST1 (ARGP_TERMARG) +#define ARGP_REVISION_OP ARG_NONE +#define ARGP_SCOPE_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_NAME, ARGP_TERMLIST) +#define ARGP_SERIALFIELD_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_SHIFT_LEFT_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_SHIFT_RIGHT_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_SIGNAL_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_SIZE_OF_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_SLEEP_OP ARGP_LIST1 (ARGP_TERMARG) +#define ARGP_STALL_OP ARGP_LIST1 (ARGP_TERMARG) +#define ARGP_STATICSTRING_OP ARGP_LIST1 (ARGP_NAMESTRING) +#define ARGP_STORE_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_SUPERNAME) +#define ARGP_STRING_OP ARGP_LIST1 (ARGP_CHARLIST) +#define ARGP_SUBTRACT_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_THERMAL_ZONE_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_NAME, ARGP_OBJLIST) +#define ARGP_TIMER_OP ARG_NONE +#define ARGP_TO_BCD_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_TO_BUFFER_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_TO_DEC_STR_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_TO_HEX_STR_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_TO_INTEGER_OP ARGP_LIST2 (ARGP_TERMARG, ARGP_TARGET) +#define ARGP_TO_STRING_OP ARGP_LIST3 (ARGP_TERMARG, ARGP_TERMARG, ARGP_TARGET) +#define ARGP_UNLOAD_OP ARGP_LIST1 (ARGP_SUPERNAME) +#define ARGP_VAR_PACKAGE_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_TERMARG, ARGP_DATAOBJLIST) +#define ARGP_WAIT_OP ARGP_LIST2 (ARGP_SUPERNAME, ARGP_TERMARG) +#define ARGP_WHILE_OP ARGP_LIST3 (ARGP_PKGLENGTH, ARGP_TERMARG, ARGP_TERMLIST) +#define ARGP_WORD_OP ARGP_LIST1 (ARGP_WORDDATA) +#define ARGP_ZERO_OP ARG_NONE + + +/* + * All AML opcodes and the runtime arguments for each. Used by the AML + * interpreter Each list is compressed into a 32-bit number and stored + * in the master opcode table (in psopcode.c). + * + * (Used by PrepOperands procedure and the ASL Compiler) + */ +#define ARGI_ACCESSFIELD_OP ARGI_INVALID_OPCODE +#define ARGI_ACQUIRE_OP ARGI_LIST2 (ARGI_MUTEX, ARGI_INTEGER) +#define ARGI_ADD_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_ALIAS_OP ARGI_INVALID_OPCODE +#define ARGI_ARG0 ARG_NONE +#define ARGI_ARG1 ARG_NONE +#define ARGI_ARG2 ARG_NONE +#define ARGI_ARG3 ARG_NONE +#define ARGI_ARG4 ARG_NONE +#define ARGI_ARG5 ARG_NONE +#define ARGI_ARG6 ARG_NONE +#define ARGI_BANK_FIELD_OP ARGI_LIST1 (ARGI_INTEGER) +#define ARGI_BIT_AND_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_BIT_NAND_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_BIT_NOR_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_BIT_NOT_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_BIT_OR_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_BIT_XOR_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_BREAK_OP ARG_NONE +#define ARGI_BREAK_POINT_OP ARG_NONE +#define ARGI_BUFFER_OP ARGI_LIST1 (ARGI_INTEGER) +#define ARGI_BYTE_OP ARGI_INVALID_OPCODE +#define ARGI_BYTELIST_OP ARGI_INVALID_OPCODE +#define ARGI_COMMENT_OP ARGI_INVALID_OPCODE +#define ARGI_CONCAT_OP ARGI_LIST3 (ARGI_ANYTYPE, ARGI_ANYTYPE, ARGI_TARGETREF) +#define ARGI_CONCAT_RES_OP ARGI_LIST3 (ARGI_BUFFER, ARGI_BUFFER, ARGI_TARGETREF) +#define ARGI_COND_REF_OF_OP ARGI_LIST2 (ARGI_OBJECT_REF, ARGI_TARGETREF) +#define ARGI_CONNECTFIELD_OP ARGI_INVALID_OPCODE +#define ARGI_CONTINUE_OP ARGI_INVALID_OPCODE +#define ARGI_COPY_OP ARGI_LIST2 (ARGI_ANYTYPE, ARGI_SIMPLE_TARGET) +#define ARGI_CREATE_BIT_FIELD_OP ARGI_LIST3 (ARGI_BUFFER, ARGI_INTEGER, ARGI_REFERENCE) +#define ARGI_CREATE_BYTE_FIELD_OP ARGI_LIST3 (ARGI_BUFFER, ARGI_INTEGER, ARGI_REFERENCE) +#define ARGI_CREATE_DWORD_FIELD_OP ARGI_LIST3 (ARGI_BUFFER, ARGI_INTEGER, ARGI_REFERENCE) +#define ARGI_CREATE_FIELD_OP ARGI_LIST4 (ARGI_BUFFER, ARGI_INTEGER, ARGI_INTEGER, ARGI_REFERENCE) +#define ARGI_CREATE_QWORD_FIELD_OP ARGI_LIST3 (ARGI_BUFFER, ARGI_INTEGER, ARGI_REFERENCE) +#define ARGI_CREATE_WORD_FIELD_OP ARGI_LIST3 (ARGI_BUFFER, ARGI_INTEGER, ARGI_REFERENCE) +#define ARGI_DATA_REGION_OP ARGI_LIST3 (ARGI_STRING, ARGI_STRING, ARGI_STRING) +#define ARGI_DEBUG_OP ARG_NONE +#define ARGI_DECREMENT_OP ARGI_LIST1 (ARGI_TARGETREF) +#define ARGI_DEREF_OF_OP ARGI_LIST1 (ARGI_REF_OR_STRING) +#define ARGI_DEVICE_OP ARGI_INVALID_OPCODE +#define ARGI_DIVIDE_OP ARGI_LIST4 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF, ARGI_TARGETREF) +#define ARGI_DWORD_OP ARGI_INVALID_OPCODE +#define ARGI_ELSE_OP ARGI_INVALID_OPCODE +#define ARGI_EVENT_OP ARGI_INVALID_OPCODE +#define ARGI_EXTERNAL_OP ARGI_LIST3 (ARGI_STRING, ARGI_INTEGER, ARGI_INTEGER) +#define ARGI_FATAL_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_INTEGER) +#define ARGI_FIELD_OP ARGI_INVALID_OPCODE +#define ARGI_FIND_SET_LEFT_BIT_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_FIND_SET_RIGHT_BIT_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_FROM_BCD_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_FIXED_TARGET) +#define ARGI_IF_OP ARGI_INVALID_OPCODE +#define ARGI_INCREMENT_OP ARGI_LIST1 (ARGI_TARGETREF) +#define ARGI_INDEX_FIELD_OP ARGI_INVALID_OPCODE +#define ARGI_INDEX_OP ARGI_LIST3 (ARGI_COMPLEXOBJ, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_LAND_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_INTEGER) +#define ARGI_LEQUAL_OP ARGI_LIST2 (ARGI_COMPUTEDATA,ARGI_COMPUTEDATA) +#define ARGI_LGREATER_OP ARGI_LIST2 (ARGI_COMPUTEDATA,ARGI_COMPUTEDATA) +#define ARGI_LGREATEREQUAL_OP ARGI_INVALID_OPCODE +#define ARGI_LLESS_OP ARGI_LIST2 (ARGI_COMPUTEDATA,ARGI_COMPUTEDATA) +#define ARGI_LLESSEQUAL_OP ARGI_INVALID_OPCODE +#define ARGI_LNOT_OP ARGI_LIST1 (ARGI_INTEGER) +#define ARGI_LNOTEQUAL_OP ARGI_INVALID_OPCODE +#define ARGI_LOAD_OP ARGI_LIST2 (ARGI_REGION_OR_BUFFER,ARGI_TARGETREF) +#define ARGI_LOAD_TABLE_OP ARGI_LIST6 (ARGI_STRING, ARGI_STRING, ARGI_STRING, ARGI_STRING, ARGI_STRING, ARGI_ANYTYPE) +#define ARGI_LOCAL0 ARG_NONE +#define ARGI_LOCAL1 ARG_NONE +#define ARGI_LOCAL2 ARG_NONE +#define ARGI_LOCAL3 ARG_NONE +#define ARGI_LOCAL4 ARG_NONE +#define ARGI_LOCAL5 ARG_NONE +#define ARGI_LOCAL6 ARG_NONE +#define ARGI_LOCAL7 ARG_NONE +#define ARGI_LOR_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_INTEGER) +#define ARGI_MATCH_OP ARGI_LIST6 (ARGI_PACKAGE, ARGI_INTEGER, ARGI_COMPUTEDATA, ARGI_INTEGER,ARGI_COMPUTEDATA,ARGI_INTEGER) +#define ARGI_METHOD_OP ARGI_INVALID_OPCODE +#define ARGI_METHODCALL_OP ARGI_INVALID_OPCODE +#define ARGI_MID_OP ARGI_LIST4 (ARGI_BUFFER_OR_STRING,ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_MOD_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_MULTIPLY_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_MUTEX_OP ARGI_INVALID_OPCODE +#define ARGI_NAME_OP ARGI_INVALID_OPCODE +#define ARGI_NAMEDFIELD_OP ARGI_INVALID_OPCODE +#define ARGI_NAMEPATH_OP ARGI_INVALID_OPCODE +#define ARGI_NOOP_OP ARG_NONE +#define ARGI_NOTIFY_OP ARGI_LIST2 (ARGI_DEVICE_REF, ARGI_INTEGER) +#define ARGI_OBJECT_TYPE_OP ARGI_LIST1 (ARGI_ANYTYPE) +#define ARGI_ONE_OP ARG_NONE +#define ARGI_ONES_OP ARG_NONE +#define ARGI_PACKAGE_OP ARGI_LIST1 (ARGI_INTEGER) +#define ARGI_POWER_RES_OP ARGI_INVALID_OPCODE +#define ARGI_PROCESSOR_OP ARGI_INVALID_OPCODE +#define ARGI_QWORD_OP ARGI_INVALID_OPCODE +#define ARGI_REF_OF_OP ARGI_LIST1 (ARGI_OBJECT_REF) +#define ARGI_REGION_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_INTEGER) +#define ARGI_RELEASE_OP ARGI_LIST1 (ARGI_MUTEX) +#define ARGI_RESERVEDFIELD_OP ARGI_INVALID_OPCODE +#define ARGI_RESET_OP ARGI_LIST1 (ARGI_EVENT) +#define ARGI_RETURN_OP ARGI_INVALID_OPCODE +#define ARGI_REVISION_OP ARG_NONE +#define ARGI_SCOPE_OP ARGI_INVALID_OPCODE +#define ARGI_SERIALFIELD_OP ARGI_INVALID_OPCODE +#define ARGI_SHIFT_LEFT_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_SHIFT_RIGHT_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_SIGNAL_OP ARGI_LIST1 (ARGI_EVENT) +#define ARGI_SIZE_OF_OP ARGI_LIST1 (ARGI_DATAOBJECT) +#define ARGI_SLEEP_OP ARGI_LIST1 (ARGI_INTEGER) +#define ARGI_STALL_OP ARGI_LIST1 (ARGI_INTEGER) +#define ARGI_STATICSTRING_OP ARGI_INVALID_OPCODE +#define ARGI_STORE_OP ARGI_LIST2 (ARGI_DATAREFOBJ, ARGI_STORE_TARGET) +#define ARGI_STRING_OP ARGI_INVALID_OPCODE +#define ARGI_SUBTRACT_OP ARGI_LIST3 (ARGI_INTEGER, ARGI_INTEGER, ARGI_TARGETREF) +#define ARGI_THERMAL_ZONE_OP ARGI_INVALID_OPCODE +#define ARGI_TIMER_OP ARG_NONE +#define ARGI_TO_BCD_OP ARGI_LIST2 (ARGI_INTEGER, ARGI_FIXED_TARGET) +#define ARGI_TO_BUFFER_OP ARGI_LIST2 (ARGI_COMPUTEDATA,ARGI_FIXED_TARGET) +#define ARGI_TO_DEC_STR_OP ARGI_LIST2 (ARGI_COMPUTEDATA,ARGI_FIXED_TARGET) +#define ARGI_TO_HEX_STR_OP ARGI_LIST2 (ARGI_COMPUTEDATA,ARGI_FIXED_TARGET) +#define ARGI_TO_INTEGER_OP ARGI_LIST2 (ARGI_COMPUTEDATA,ARGI_FIXED_TARGET) +#define ARGI_TO_STRING_OP ARGI_LIST3 (ARGI_BUFFER, ARGI_INTEGER, ARGI_FIXED_TARGET) +#define ARGI_UNLOAD_OP ARGI_LIST1 (ARGI_DDBHANDLE) +#define ARGI_VAR_PACKAGE_OP ARGI_LIST1 (ARGI_INTEGER) +#define ARGI_WAIT_OP ARGI_LIST2 (ARGI_EVENT, ARGI_INTEGER) +#define ARGI_WHILE_OP ARGI_INVALID_OPCODE +#define ARGI_WORD_OP ARGI_INVALID_OPCODE +#define ARGI_ZERO_OP ARG_NONE + +#endif /* __ACOPCODE_H__ */ diff --git a/ports/acpica/include/acoutput.h b/ports/acpica/include/acoutput.h new file mode 100644 index 0000000..49b8505 --- /dev/null +++ b/ports/acpica/include/acoutput.h @@ -0,0 +1,608 @@ +/****************************************************************************** + * + * Name: acoutput.h -- debug output + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACOUTPUT_H__ +#define __ACOUTPUT_H__ + +/* + * Debug levels and component IDs. These are used to control the + * granularity of the output of the ACPI_DEBUG_PRINT macro -- on a + * per-component basis and a per-exception-type basis. + */ + +/* Component IDs are used in the global "DebugLayer" */ + +#define ACPI_UTILITIES 0x00000001 +#define ACPI_HARDWARE 0x00000002 +#define ACPI_EVENTS 0x00000004 +#define ACPI_TABLES 0x00000008 +#define ACPI_NAMESPACE 0x00000010 +#define ACPI_PARSER 0x00000020 +#define ACPI_DISPATCHER 0x00000040 +#define ACPI_EXECUTER 0x00000080 +#define ACPI_RESOURCES 0x00000100 +#define ACPI_CA_DEBUGGER 0x00000200 +#define ACPI_OS_SERVICES 0x00000400 +#define ACPI_CA_DISASSEMBLER 0x00000800 + +/* Component IDs for ACPI tools and utilities */ + +#define ACPI_COMPILER 0x00001000 +#define ACPI_TOOLS 0x00002000 +#define ACPI_EXAMPLE 0x00004000 +#define ACPI_DRIVER 0x00008000 +#define DT_COMPILER 0x00010000 +#define ASL_PREPROCESSOR 0x00020000 + +#define ACPI_ALL_COMPONENTS 0x0001FFFF +#define ACPI_COMPONENT_DEFAULT (ACPI_ALL_COMPONENTS) + +/* Component IDs reserved for ACPI drivers */ + +#define ACPI_ALL_DRIVERS 0xFFFF0000 + + +/* + * Raw debug output levels, do not use these in the ACPI_DEBUG_PRINT macros + */ +#define ACPI_LV_INIT 0x00000001 +#define ACPI_LV_DEBUG_OBJECT 0x00000002 +#define ACPI_LV_INFO 0x00000004 +#define ACPI_LV_REPAIR 0x00000008 +#define ACPI_LV_TRACE_POINT 0x00000010 +#define ACPI_LV_ALL_EXCEPTIONS 0x0000001F + +/* Trace verbosity level 1 [Standard Trace Level] */ + +#define ACPI_LV_INIT_NAMES 0x00000020 +#define ACPI_LV_PARSE 0x00000040 +#define ACPI_LV_LOAD 0x00000080 +#define ACPI_LV_DISPATCH 0x00000100 +#define ACPI_LV_EXEC 0x00000200 +#define ACPI_LV_NAMES 0x00000400 +#define ACPI_LV_OPREGION 0x00000800 +#define ACPI_LV_BFIELD 0x00001000 +#define ACPI_LV_TABLES 0x00002000 +#define ACPI_LV_VALUES 0x00004000 +#define ACPI_LV_OBJECTS 0x00008000 +#define ACPI_LV_RESOURCES 0x00010000 +#define ACPI_LV_USER_REQUESTS 0x00020000 +#define ACPI_LV_PACKAGE 0x00040000 +#define ACPI_LV_VERBOSITY1 0x0007FF40 | ACPI_LV_ALL_EXCEPTIONS + +/* Trace verbosity level 2 [Function tracing and memory allocation] */ + +#define ACPI_LV_ALLOCATIONS 0x00100000 +#define ACPI_LV_FUNCTIONS 0x00200000 +#define ACPI_LV_OPTIMIZATIONS 0x00400000 +#define ACPI_LV_VERBOSITY2 0x00700000 | ACPI_LV_VERBOSITY1 +#define ACPI_LV_ALL ACPI_LV_VERBOSITY2 + +/* Trace verbosity level 3 [Threading, I/O, and Interrupts] */ + +#define ACPI_LV_MUTEX 0x01000000 +#define ACPI_LV_THREADS 0x02000000 +#define ACPI_LV_IO 0x04000000 +#define ACPI_LV_INTERRUPTS 0x08000000 +#define ACPI_LV_VERBOSITY3 0x0F000000 | ACPI_LV_VERBOSITY2 + +/* Exceptionally verbose output -- also used in the global "DebugLevel" */ + +#define ACPI_LV_AML_DISASSEMBLE 0x10000000 +#define ACPI_LV_VERBOSE_INFO 0x20000000 +#define ACPI_LV_FULL_TABLES 0x40000000 +#define ACPI_LV_EVENTS 0x80000000 +#define ACPI_LV_VERBOSE 0xF0000000 + + +/* + * Debug level macros that are used in the DEBUG_PRINT macros + */ +#define ACPI_DEBUG_LEVEL(dl) (UINT32) dl,ACPI_DEBUG_PARAMETERS + +/* + * Exception level -- used in the global "DebugLevel" + * + * Note: For errors, use the ACPI_ERROR or ACPI_EXCEPTION interfaces. + * For warnings, use ACPI_WARNING. + */ +#define ACPI_DB_INIT ACPI_DEBUG_LEVEL (ACPI_LV_INIT) +#define ACPI_DB_DEBUG_OBJECT ACPI_DEBUG_LEVEL (ACPI_LV_DEBUG_OBJECT) +#define ACPI_DB_INFO ACPI_DEBUG_LEVEL (ACPI_LV_INFO) +#define ACPI_DB_REPAIR ACPI_DEBUG_LEVEL (ACPI_LV_REPAIR) +#define ACPI_DB_TRACE_POINT ACPI_DEBUG_LEVEL (ACPI_LV_TRACE_POINT) +#define ACPI_DB_ALL_EXCEPTIONS ACPI_DEBUG_LEVEL (ACPI_LV_ALL_EXCEPTIONS) + +/* Trace level -- also used in the global "DebugLevel" */ + +#define ACPI_DB_INIT_NAMES ACPI_DEBUG_LEVEL (ACPI_LV_INIT_NAMES) +#define ACPI_DB_THREADS ACPI_DEBUG_LEVEL (ACPI_LV_THREADS) +#define ACPI_DB_PARSE ACPI_DEBUG_LEVEL (ACPI_LV_PARSE) +#define ACPI_DB_DISPATCH ACPI_DEBUG_LEVEL (ACPI_LV_DISPATCH) +#define ACPI_DB_LOAD ACPI_DEBUG_LEVEL (ACPI_LV_LOAD) +#define ACPI_DB_EXEC ACPI_DEBUG_LEVEL (ACPI_LV_EXEC) +#define ACPI_DB_NAMES ACPI_DEBUG_LEVEL (ACPI_LV_NAMES) +#define ACPI_DB_OPREGION ACPI_DEBUG_LEVEL (ACPI_LV_OPREGION) +#define ACPI_DB_BFIELD ACPI_DEBUG_LEVEL (ACPI_LV_BFIELD) +#define ACPI_DB_TABLES ACPI_DEBUG_LEVEL (ACPI_LV_TABLES) +#define ACPI_DB_FUNCTIONS ACPI_DEBUG_LEVEL (ACPI_LV_FUNCTIONS) +#define ACPI_DB_OPTIMIZATIONS ACPI_DEBUG_LEVEL (ACPI_LV_OPTIMIZATIONS) +#define ACPI_DB_VALUES ACPI_DEBUG_LEVEL (ACPI_LV_VALUES) +#define ACPI_DB_OBJECTS ACPI_DEBUG_LEVEL (ACPI_LV_OBJECTS) +#define ACPI_DB_ALLOCATIONS ACPI_DEBUG_LEVEL (ACPI_LV_ALLOCATIONS) +#define ACPI_DB_RESOURCES ACPI_DEBUG_LEVEL (ACPI_LV_RESOURCES) +#define ACPI_DB_IO ACPI_DEBUG_LEVEL (ACPI_LV_IO) +#define ACPI_DB_INTERRUPTS ACPI_DEBUG_LEVEL (ACPI_LV_INTERRUPTS) +#define ACPI_DB_USER_REQUESTS ACPI_DEBUG_LEVEL (ACPI_LV_USER_REQUESTS) +#define ACPI_DB_PACKAGE ACPI_DEBUG_LEVEL (ACPI_LV_PACKAGE) +#define ACPI_DB_MUTEX ACPI_DEBUG_LEVEL (ACPI_LV_MUTEX) +#define ACPI_DB_EVENTS ACPI_DEBUG_LEVEL (ACPI_LV_EVENTS) + +#define ACPI_DB_ALL ACPI_DEBUG_LEVEL (ACPI_LV_ALL) + +/* Defaults for DebugLevel, debug and normal */ + +#define ACPI_DEBUG_DEFAULT (ACPI_LV_INIT | ACPI_LV_DEBUG_OBJECT | ACPI_LV_REPAIR) +#define ACPI_NORMAL_DEFAULT (ACPI_LV_INIT | ACPI_LV_DEBUG_OBJECT | ACPI_LV_REPAIR) +#define ACPI_DEBUG_ALL (ACPI_LV_AML_DISASSEMBLE | ACPI_LV_ALL_EXCEPTIONS | ACPI_LV_ALL) + + +/* + * Global trace flags + */ +#define ACPI_TRACE_ENABLED ((UINT32) 4) +#define ACPI_TRACE_ONESHOT ((UINT32) 2) +#define ACPI_TRACE_OPCODE ((UINT32) 1) + +/* Defaults for trace debugging level/layer */ + +#define ACPI_TRACE_LEVEL_ALL ACPI_LV_ALL +#define ACPI_TRACE_LAYER_ALL 0x000001FF +#define ACPI_TRACE_LEVEL_DEFAULT ACPI_LV_TRACE_POINT +#define ACPI_TRACE_LAYER_DEFAULT ACPI_EXECUTER + + +#if defined (ACPI_DEBUG_OUTPUT) || !defined (ACPI_NO_ERROR_MESSAGES) +/* + * The module name is used primarily for error and debug messages. + * The __FILE__ macro is not very useful for this, because it + * usually includes the entire pathname to the module making the + * debug output difficult to read. + */ +#define ACPI_MODULE_NAME(Name) static const char ACPI_UNUSED_VAR _AcpiModuleName[] = Name; +#else +/* + * For the no-debug and no-error-msg cases, we must at least define + * a null module name. + */ +#define ACPI_MODULE_NAME(Name) +#define _AcpiModuleName "" +#endif + +/* + * Ascii error messages can be configured out + */ +#ifndef ACPI_NO_ERROR_MESSAGES +#define AE_INFO _AcpiModuleName, __LINE__ + +/* + * Error reporting. Callers module and line number are inserted by AE_INFO, + * the plist contains a set of parens to allow variable-length lists. + * These macros are used for both the debug and non-debug versions of the code. + */ +#define ACPI_INFO(plist) AcpiInfo plist +#define ACPI_WARNING(plist) AcpiWarning plist +#define ACPI_EXCEPTION(plist) AcpiException plist +#define ACPI_ERROR(plist) AcpiError plist +#define ACPI_BIOS_WARNING(plist) AcpiBiosWarning plist +#define ACPI_BIOS_ERROR(plist) AcpiBiosError plist +#define ACPI_DEBUG_OBJECT(obj,l,i) AcpiExDoDebugObject(obj,l,i) + +#else + +/* No error messages */ + +#define ACPI_INFO(plist) +#define ACPI_WARNING(plist) +#define ACPI_EXCEPTION(plist) +#define ACPI_ERROR(plist) +#define ACPI_BIOS_WARNING(plist) +#define ACPI_BIOS_ERROR(plist) +#define ACPI_DEBUG_OBJECT(obj,l,i) + +#endif /* ACPI_NO_ERROR_MESSAGES */ + + +/* + * Debug macros that are conditionally compiled + */ +#ifdef ACPI_DEBUG_OUTPUT + +/* + * If ACPI_GET_FUNCTION_NAME was not defined in the compiler-dependent header, + * define it now. This is the case where there the compiler does not support + * a __FUNCTION__ macro or equivalent. + */ +#ifndef ACPI_GET_FUNCTION_NAME +#define ACPI_GET_FUNCTION_NAME _AcpiFunctionName + +/* + * The Name parameter should be the procedure name as a non-quoted string. + * The function name is also used by the function exit macros below. + * Note: (const char) is used to be compatible with the debug interfaces + * and macros such as __FUNCTION__. + */ +#define ACPI_FUNCTION_NAME(Name) static const char _AcpiFunctionName[] = #Name; + +#else +/* Compiler supports __FUNCTION__ (or equivalent) -- Ignore this macro */ + +#define ACPI_FUNCTION_NAME(Name) +#endif /* ACPI_GET_FUNCTION_NAME */ + +/* + * Common parameters used for debug output functions: + * line number, function name, module(file) name, component ID + */ +#define ACPI_DEBUG_PARAMETERS \ + __LINE__, ACPI_GET_FUNCTION_NAME, _AcpiModuleName, _COMPONENT + +/* Check if debug output is currently dynamically enabled */ + +#define ACPI_IS_DEBUG_ENABLED(Level, Component) \ + ((Level & AcpiDbgLevel) && (Component & AcpiDbgLayer)) + +/* + * Master debug print macros + * Print message if and only if: + * 1) Debug print for the current component is enabled + * 2) Debug error level or trace level for the print statement is enabled + * + * November 2012: Moved the runtime check for whether to actually emit the + * debug message outside of the print function itself. This improves overall + * performance at a relatively small code cost. Implementation involves the + * use of variadic macros supported by C99. + * + * Note: the ACPI_DO_WHILE0 macro is used to prevent some compilers from + * complaining about these constructs. On other compilers the do...while + * adds some extra code, so this feature is optional. + */ +#ifdef ACPI_USE_DO_WHILE_0 +#define ACPI_DO_WHILE0(a) do a while(0) +#else +#define ACPI_DO_WHILE0(a) a +#endif + +/* DEBUG_PRINT functions */ + +#ifndef COMPILER_VA_MACRO + +#define ACPI_DEBUG_PRINT(plist) AcpiDebugPrint plist +#define ACPI_DEBUG_PRINT_RAW(plist) AcpiDebugPrintRaw plist + +#else + +/* Helper macros for DEBUG_PRINT */ + +#define ACPI_DO_DEBUG_PRINT(Function, Level, Line, Filename, Modulename, Component, ...) \ + ACPI_DO_WHILE0 ({ \ + if (ACPI_IS_DEBUG_ENABLED (Level, Component)) \ + { \ + Function (Level, Line, Filename, Modulename, Component, __VA_ARGS__); \ + } \ + }) + +#define ACPI_ACTUAL_DEBUG(Level, Line, Filename, Modulename, Component, ...) \ + ACPI_DO_DEBUG_PRINT (AcpiDebugPrint, Level, Line, \ + Filename, Modulename, Component, __VA_ARGS__) + +#define ACPI_ACTUAL_DEBUG_RAW(Level, Line, Filename, Modulename, Component, ...) \ + ACPI_DO_DEBUG_PRINT (AcpiDebugPrintRaw, Level, Line, \ + Filename, Modulename, Component, __VA_ARGS__) + +#define ACPI_DEBUG_PRINT(plist) ACPI_ACTUAL_DEBUG plist +#define ACPI_DEBUG_PRINT_RAW(plist) ACPI_ACTUAL_DEBUG_RAW plist + +#endif + + +/* + * Function entry tracing + * + * The name of the function is emitted as a local variable that is + * intended to be used by both the entry trace and the exit trace. + */ + +/* Helper macro */ + +#define ACPI_TRACE_ENTRY(Name, Function, Type, Param) \ + ACPI_FUNCTION_NAME (Name) \ + Function (ACPI_DEBUG_PARAMETERS, (Type) (Param)) + +/* The actual entry trace macros */ + +#define ACPI_FUNCTION_TRACE(Name) \ + ACPI_FUNCTION_NAME(Name) \ + AcpiUtTrace (ACPI_DEBUG_PARAMETERS) + +#define ACPI_FUNCTION_TRACE_PTR(Name, Pointer) \ + ACPI_TRACE_ENTRY (Name, AcpiUtTracePtr, void *, Pointer) + +#define ACPI_FUNCTION_TRACE_U32(Name, Value) \ + ACPI_TRACE_ENTRY (Name, AcpiUtTraceU32, UINT32, Value) + +#define ACPI_FUNCTION_TRACE_STR(Name, String) \ + ACPI_TRACE_ENTRY (Name, AcpiUtTraceStr, const char *, String) + +#define ACPI_FUNCTION_ENTRY() \ + AcpiUtTrackStackPtr() + + +/* + * Function exit tracing + * + * These macros include a return statement. This is usually considered + * bad form, but having a separate exit macro before the actual return + * is very ugly and difficult to maintain. + * + * One of the FUNCTION_TRACE macros above must be used in conjunction + * with these macros so that "_AcpiFunctionName" is defined. + * + * There are two versions of most of the return macros. The default version is + * safer, since it avoids side-effects by guaranteeing that the argument will + * not be evaluated twice. + * + * A less-safe version of the macros is provided for optional use if the + * compiler uses excessive CPU stack (for example, this may happen in the + * debug case if code optimzation is disabled.) + */ + +/* Exit trace helper macro */ + +#ifndef ACPI_SIMPLE_RETURN_MACROS + +#define ACPI_TRACE_EXIT(Function, Type, Param) \ + ACPI_DO_WHILE0 ({ \ + register Type _Param = (Type) (Param); \ + Function (ACPI_DEBUG_PARAMETERS, _Param); \ + return (_Param); \ + }) + +#else /* Use original less-safe macros */ + +#define ACPI_TRACE_EXIT(Function, Type, Param) \ + ACPI_DO_WHILE0 ({ \ + Function (ACPI_DEBUG_PARAMETERS, (Type) (Param)); \ + return (Param); \ + }) + +#endif /* ACPI_SIMPLE_RETURN_MACROS */ + +/* The actual exit macros */ + +#define return_VOID \ + ACPI_DO_WHILE0 ({ \ + AcpiUtExit (ACPI_DEBUG_PARAMETERS); \ + return; \ + }) + +#define return_ACPI_STATUS(Status) \ + ACPI_TRACE_EXIT (AcpiUtStatusExit, ACPI_STATUS, Status) + +#define return_PTR(Pointer) \ + ACPI_TRACE_EXIT (AcpiUtPtrExit, void *, Pointer) + +#define return_STR(String) \ + ACPI_TRACE_EXIT (AcpiUtStrExit, const char *, String) + +#define return_VALUE(Value) \ + ACPI_TRACE_EXIT (AcpiUtValueExit, UINT64, Value) + +#define return_UINT32(Value) \ + ACPI_TRACE_EXIT (AcpiUtValueExit, UINT32, Value) + +#define return_UINT8(Value) \ + ACPI_TRACE_EXIT (AcpiUtValueExit, UINT8, Value) + +/* Conditional execution */ + +#define ACPI_DEBUG_EXEC(a) a +#define ACPI_DEBUG_ONLY_MEMBERS(a) a; +#define _VERBOSE_STRUCTURES + + +/* Various object display routines for debug */ + +#define ACPI_DUMP_STACK_ENTRY(a) AcpiExDumpOperand((a), 0) +#define ACPI_DUMP_OPERANDS(a, b ,c) AcpiExDumpOperands(a, b, c) +#define ACPI_DUMP_ENTRY(a, b) AcpiNsDumpEntry (a, b) +#define ACPI_DUMP_PATHNAME(a, b, c, d) AcpiNsDumpPathname(a, b, c, d) +#define ACPI_DUMP_BUFFER(a, b) AcpiUtDebugDumpBuffer((UINT8 *) a, b, DB_BYTE_DISPLAY, _COMPONENT) + +#define ACPI_TRACE_POINT(a, b, c, d) AcpiTracePoint (a, b, c, d) + +#else /* ACPI_DEBUG_OUTPUT */ +/* + * This is the non-debug case -- make everything go away, + * leaving no executable debug code! + */ +#define ACPI_DEBUG_PRINT(pl) +#define ACPI_DEBUG_PRINT_RAW(pl) +#define ACPI_DEBUG_EXEC(a) +#define ACPI_DEBUG_ONLY_MEMBERS(a) +#define ACPI_FUNCTION_NAME(a) +#define ACPI_FUNCTION_TRACE(a) +#define ACPI_FUNCTION_TRACE_PTR(a, b) +#define ACPI_FUNCTION_TRACE_U32(a, b) +#define ACPI_FUNCTION_TRACE_STR(a, b) +#define ACPI_FUNCTION_ENTRY() +#define ACPI_DUMP_STACK_ENTRY(a) +#define ACPI_DUMP_OPERANDS(a, b, c) +#define ACPI_DUMP_ENTRY(a, b) +#define ACPI_DUMP_PATHNAME(a, b, c, d) +#define ACPI_DUMP_BUFFER(a, b) +#define ACPI_IS_DEBUG_ENABLED(Level, Component) 0 +#define ACPI_TRACE_POINT(a, b, c, d) + +/* Return macros must have a return statement at the minimum */ + +#define return_VOID return +#define return_ACPI_STATUS(s) return(s) +#define return_PTR(s) return(s) +#define return_STR(s) return(s) +#define return_VALUE(s) return(s) +#define return_UINT8(s) return(s) +#define return_UINT32(s) return(s) + +#endif /* ACPI_DEBUG_OUTPUT */ + + +#endif /* __ACOUTPUT_H__ */ diff --git a/ports/acpica/include/acparser.h b/ports/acpica/include/acparser.h new file mode 100644 index 0000000..8acea23 --- /dev/null +++ b/ports/acpica/include/acparser.h @@ -0,0 +1,476 @@ +/****************************************************************************** + * + * Module Name: acparser.h - AML Parser subcomponent prototypes and defines + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACPARSER_H__ +#define __ACPARSER_H__ + + +#define OP_HAS_RETURN_VALUE 1 + +/* Variable number of arguments. This field must be 32 bits */ + +#define ACPI_VAR_ARGS ACPI_UINT32_MAX + + +#define ACPI_PARSE_DELETE_TREE 0x0001 +#define ACPI_PARSE_NO_TREE_DELETE 0x0000 +#define ACPI_PARSE_TREE_MASK 0x0001 + +#define ACPI_PARSE_LOAD_PASS1 0x0010 +#define ACPI_PARSE_LOAD_PASS2 0x0020 +#define ACPI_PARSE_EXECUTE 0x0030 +#define ACPI_PARSE_MODE_MASK 0x0030 + +#define ACPI_PARSE_DEFERRED_OP 0x0100 +#define ACPI_PARSE_DISASSEMBLE 0x0200 + +#define ACPI_PARSE_MODULE_LEVEL 0x0400 + +/****************************************************************************** + * + * Parser interfaces + * + *****************************************************************************/ + +extern const UINT8 AcpiGbl_ShortOpIndex[]; +extern const UINT8 AcpiGbl_LongOpIndex[]; + + +/* + * psxface - Parser external interfaces + */ +ACPI_STATUS +AcpiPsExecuteMethod ( + ACPI_EVALUATE_INFO *Info); + +ACPI_STATUS +AcpiPsExecuteTable ( + ACPI_EVALUATE_INFO *Info); + + +/* + * psargs - Parse AML opcode arguments + */ +UINT8 * +AcpiPsGetNextPackageEnd ( + ACPI_PARSE_STATE *ParserState); + +char * +AcpiPsGetNextNamestring ( + ACPI_PARSE_STATE *ParserState); + +void +AcpiPsGetNextSimpleArg ( + ACPI_PARSE_STATE *ParserState, + UINT32 ArgType, + ACPI_PARSE_OBJECT *Arg); + +ACPI_STATUS +AcpiPsGetNextNamepath ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_STATE *ParserState, + ACPI_PARSE_OBJECT *Arg, + BOOLEAN PossibleMethodCall); + +/* Values for BOOLEAN above */ + +#define ACPI_NOT_METHOD_CALL FALSE +#define ACPI_POSSIBLE_METHOD_CALL TRUE + +ACPI_STATUS +AcpiPsGetNextArg ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_STATE *ParserState, + UINT32 ArgType, + ACPI_PARSE_OBJECT **ReturnArg); + + +/* + * psfind + */ +ACPI_PARSE_OBJECT * +AcpiPsFindName ( + ACPI_PARSE_OBJECT *Scope, + UINT32 Name, + UINT32 Opcode); + +ACPI_PARSE_OBJECT* +AcpiPsGetParent ( + ACPI_PARSE_OBJECT *Op); + + +/* + * psobject - support for parse object processing + */ +ACPI_STATUS +AcpiPsBuildNamedOp ( + ACPI_WALK_STATE *WalkState, + UINT8 *AmlOpStart, + ACPI_PARSE_OBJECT *UnnamedOp, + ACPI_PARSE_OBJECT **Op); + +ACPI_STATUS +AcpiPsCreateOp ( + ACPI_WALK_STATE *WalkState, + UINT8 *AmlOpStart, + ACPI_PARSE_OBJECT **NewOp); + +ACPI_STATUS +AcpiPsCompleteOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT **Op, + ACPI_STATUS Status); + +ACPI_STATUS +AcpiPsCompleteFinalOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + ACPI_STATUS Status); + + +/* + * psopinfo - AML Opcode information + */ +const ACPI_OPCODE_INFO * +AcpiPsGetOpcodeInfo ( + UINT16 Opcode); + +const char * +AcpiPsGetOpcodeName ( + UINT16 Opcode); + +UINT8 +AcpiPsGetArgumentCount ( + UINT32 OpType); + + +/* + * psparse - top level parsing routines + */ +ACPI_STATUS +AcpiPsParseAml ( + ACPI_WALK_STATE *WalkState); + +UINT32 +AcpiPsGetOpcodeSize ( + UINT32 Opcode); + +UINT16 +AcpiPsPeekOpcode ( + ACPI_PARSE_STATE *state); + +ACPI_STATUS +AcpiPsCompleteThisOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op); + +ACPI_STATUS +AcpiPsNextParseState ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + ACPI_STATUS CallbackStatus); + + +/* + * psloop - main parse loop + */ +ACPI_STATUS +AcpiPsParseLoop ( + ACPI_WALK_STATE *WalkState); + + +/* + * psscope - Scope stack management routines + */ +ACPI_STATUS +AcpiPsInitScope ( + ACPI_PARSE_STATE *ParserState, + ACPI_PARSE_OBJECT *Root); + +ACPI_PARSE_OBJECT * +AcpiPsGetParentScope ( + ACPI_PARSE_STATE *state); + +BOOLEAN +AcpiPsHasCompletedScope ( + ACPI_PARSE_STATE *ParserState); + +void +AcpiPsPopScope ( + ACPI_PARSE_STATE *ParserState, + ACPI_PARSE_OBJECT **Op, + UINT32 *ArgList, + UINT32 *ArgCount); + +ACPI_STATUS +AcpiPsPushScope ( + ACPI_PARSE_STATE *ParserState, + ACPI_PARSE_OBJECT *Op, + UINT32 RemainingArgs, + UINT32 ArgCount); + +void +AcpiPsCleanupScope ( + ACPI_PARSE_STATE *state); + + +/* + * pstree - parse tree manipulation routines + */ +void +AcpiPsAppendArg( + ACPI_PARSE_OBJECT *op, + ACPI_PARSE_OBJECT *arg); + +ACPI_PARSE_OBJECT* +AcpiPsFind ( + ACPI_PARSE_OBJECT *Scope, + char *Path, + UINT16 Opcode, + UINT32 Create); + +ACPI_PARSE_OBJECT * +AcpiPsGetArg( + ACPI_PARSE_OBJECT *op, + UINT32 argn); + +ACPI_PARSE_OBJECT * +AcpiPsGetDepthNext ( + ACPI_PARSE_OBJECT *Origin, + ACPI_PARSE_OBJECT *Op); + + +/* + * pswalk - parse tree walk routines + */ +ACPI_STATUS +AcpiPsWalkParsedAml ( + ACPI_PARSE_OBJECT *StartOp, + ACPI_PARSE_OBJECT *EndOp, + ACPI_OPERAND_OBJECT *MthDesc, + ACPI_NAMESPACE_NODE *StartNode, + ACPI_OPERAND_OBJECT **Params, + ACPI_OPERAND_OBJECT **CallerReturnDesc, + ACPI_OWNER_ID OwnerId, + ACPI_PARSE_DOWNWARDS DescendingCallback, + ACPI_PARSE_UPWARDS AscendingCallback); + +ACPI_STATUS +AcpiPsGetNextWalkOp ( + ACPI_WALK_STATE *WalkState, + ACPI_PARSE_OBJECT *Op, + ACPI_PARSE_UPWARDS AscendingCallback); + +ACPI_STATUS +AcpiPsDeleteCompletedOp ( + ACPI_WALK_STATE *WalkState); + +void +AcpiPsDeleteParseTree ( + ACPI_PARSE_OBJECT *root); + + +/* + * psutils - parser utilities + */ +ACPI_PARSE_OBJECT * +AcpiPsCreateScopeOp ( + UINT8 *Aml); + +void +AcpiPsInitOp ( + ACPI_PARSE_OBJECT *op, + UINT16 opcode); + +ACPI_PARSE_OBJECT * +AcpiPsAllocOp ( + UINT16 Opcode, + UINT8 *Aml); + +void +AcpiPsFreeOp ( + ACPI_PARSE_OBJECT *Op); + +BOOLEAN +AcpiPsIsLeadingChar ( + UINT32 c); + +UINT32 +AcpiPsGetName( + ACPI_PARSE_OBJECT *op); + +void +AcpiPsSetName( + ACPI_PARSE_OBJECT *op, + UINT32 name); + + +/* + * psdump - display parser tree + */ +UINT32 +AcpiPsSprintPath ( + char *BufferStart, + UINT32 BufferSize, + ACPI_PARSE_OBJECT *Op); + +UINT32 +AcpiPsSprintOp ( + char *BufferStart, + UINT32 BufferSize, + ACPI_PARSE_OBJECT *Op); + +void +AcpiPsShow ( + ACPI_PARSE_OBJECT *op); + + +#endif /* __ACPARSER_H__ */ diff --git a/ports/acpica/include/acpi.h b/ports/acpica/include/acpi.h new file mode 100644 index 0000000..69dbedf --- /dev/null +++ b/ports/acpica/include/acpi.h @@ -0,0 +1,175 @@ +/****************************************************************************** + * + * Name: acpi.h - Master public include file used to interface to ACPICA + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACPI_H__ +#define __ACPI_H__ + +/* + * Public include files for use by code that will interface to ACPICA. + * + * Information includes the ACPICA data types, names, exceptions, and + * external interface prototypes. Also included are the definitions for + * all ACPI tables (FADT, MADT, etc.) + * + * Note: The order of these include files is important. + */ +#include "platform/acenv.h" /* Environment-specific items */ +#include "actypes.h" /* ACPICA data types and structures */ +#include "platform/acenvex.h" /* Extra environment-specific items */ +#include "acnames.h" /* Common ACPI names and strings */ +#include "acexcep.h" /* ACPICA exceptions */ +#include "actbl.h" /* ACPI table definitions */ +#include "acoutput.h" /* Error output and Debug macros */ +#include "acrestyp.h" /* Resource Descriptor structs */ +#include "acpiosxf.h" /* OSL interfaces (ACPICA-to-OS) */ +#include "acpixf.h" /* ACPI core subsystem external interfaces */ + +#endif /* __ACPI_H__ */ diff --git a/ports/acpica/include/acpiosxf.h b/ports/acpica/include/acpiosxf.h new file mode 100644 index 0000000..c5bbddf --- /dev/null +++ b/ports/acpica/include/acpiosxf.h @@ -0,0 +1,704 @@ +/****************************************************************************** + * + * Name: acpiosxf.h - All interfaces to the OS Services Layer (OSL). These + * interfaces must be implemented by OSL to interface the + * ACPI components to the host operating system. + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACPIOSXF_H__ +#define __ACPIOSXF_H__ + +#include "platform/acenv.h" +#include "actypes.h" + + +/* Types for AcpiOsExecute */ + +typedef enum +{ + OSL_GLOBAL_LOCK_HANDLER, + OSL_NOTIFY_HANDLER, + OSL_GPE_HANDLER, + OSL_DEBUGGER_MAIN_THREAD, + OSL_DEBUGGER_EXEC_THREAD, + OSL_EC_POLL_HANDLER, + OSL_EC_BURST_HANDLER + +} ACPI_EXECUTE_TYPE; + +#define ACPI_NO_UNIT_LIMIT ((UINT32) -1) +#define ACPI_MUTEX_SEM 1 + + +/* Functions for AcpiOsSignal */ + +#define ACPI_SIGNAL_FATAL 0 +#define ACPI_SIGNAL_BREAKPOINT 1 + +typedef struct acpi_signal_fatal_info +{ + UINT32 Type; + UINT32 Code; + UINT32 Argument; + +} ACPI_SIGNAL_FATAL_INFO; + + +/* + * OSL Initialization and shutdown primitives + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsInitialize +ACPI_STATUS +AcpiOsInitialize ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsTerminate +ACPI_STATUS +AcpiOsTerminate ( + void); +#endif + + +/* + * ACPI Table interfaces + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetRootPointer +ACPI_PHYSICAL_ADDRESS +AcpiOsGetRootPointer ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsPredefinedOverride +ACPI_STATUS +AcpiOsPredefinedOverride ( + const ACPI_PREDEFINED_NAMES *InitVal, + ACPI_STRING *NewVal); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsTableOverride +ACPI_STATUS +AcpiOsTableOverride ( + ACPI_TABLE_HEADER *ExistingTable, + ACPI_TABLE_HEADER **NewTable); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsPhysicalTableOverride +ACPI_STATUS +AcpiOsPhysicalTableOverride ( + ACPI_TABLE_HEADER *ExistingTable, + ACPI_PHYSICAL_ADDRESS *NewAddress, + UINT32 *NewTableLength); +#endif + + +/* + * Spinlock primitives + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsCreateLock +ACPI_STATUS +AcpiOsCreateLock ( + ACPI_SPINLOCK *OutHandle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsDeleteLock +void +AcpiOsDeleteLock ( + ACPI_SPINLOCK Handle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsAcquireLock +ACPI_CPU_FLAGS +AcpiOsAcquireLock ( + ACPI_SPINLOCK Handle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsReleaseLock +void +AcpiOsReleaseLock ( + ACPI_SPINLOCK Handle, + ACPI_CPU_FLAGS Flags); +#endif + + +/* + * Semaphore primitives + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsCreateSemaphore +ACPI_STATUS +AcpiOsCreateSemaphore ( + UINT32 MaxUnits, + UINT32 InitialUnits, + ACPI_SEMAPHORE *OutHandle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsDeleteSemaphore +ACPI_STATUS +AcpiOsDeleteSemaphore ( + ACPI_SEMAPHORE Handle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsWaitSemaphore +ACPI_STATUS +AcpiOsWaitSemaphore ( + ACPI_SEMAPHORE Handle, + UINT32 Units, + UINT16 Timeout); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsSignalSemaphore +ACPI_STATUS +AcpiOsSignalSemaphore ( + ACPI_SEMAPHORE Handle, + UINT32 Units); +#endif + + +/* + * Mutex primitives. May be configured to use semaphores instead via + * ACPI_MUTEX_TYPE (see platform/acenv.h) + */ +#if (ACPI_MUTEX_TYPE != ACPI_BINARY_SEMAPHORE) + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsCreateMutex +ACPI_STATUS +AcpiOsCreateMutex ( + ACPI_MUTEX *OutHandle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsDeleteMutex +void +AcpiOsDeleteMutex ( + ACPI_MUTEX Handle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsAcquireMutex +ACPI_STATUS +AcpiOsAcquireMutex ( + ACPI_MUTEX Handle, + UINT16 Timeout); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsReleaseMutex +void +AcpiOsReleaseMutex ( + ACPI_MUTEX Handle); +#endif + +#endif + + +/* + * Memory allocation and mapping + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsAllocate +void * +AcpiOsAllocate ( + ACPI_SIZE Size); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsAllocateZeroed +void * +AcpiOsAllocateZeroed ( + ACPI_SIZE Size); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsFree +void +AcpiOsFree ( + void * Memory); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsMapMemory +void * +AcpiOsMapMemory ( + ACPI_PHYSICAL_ADDRESS Where, + ACPI_SIZE Length); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsUnmapMemory +void +AcpiOsUnmapMemory ( + void *LogicalAddress, + ACPI_SIZE Size); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetPhysicalAddress +ACPI_STATUS +AcpiOsGetPhysicalAddress ( + void *LogicalAddress, + ACPI_PHYSICAL_ADDRESS *PhysicalAddress); +#endif + + +/* + * Memory/Object Cache + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsCreateCache +ACPI_STATUS +AcpiOsCreateCache ( + char *CacheName, + UINT16 ObjectSize, + UINT16 MaxDepth, + ACPI_CACHE_T **ReturnCache); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsDeleteCache +ACPI_STATUS +AcpiOsDeleteCache ( + ACPI_CACHE_T *Cache); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsPurgeCache +ACPI_STATUS +AcpiOsPurgeCache ( + ACPI_CACHE_T *Cache); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsAcquireObject +void * +AcpiOsAcquireObject ( + ACPI_CACHE_T *Cache); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsReleaseObject +ACPI_STATUS +AcpiOsReleaseObject ( + ACPI_CACHE_T *Cache, + void *Object); +#endif + + +/* + * Interrupt handlers + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsInstallInterruptHandler +ACPI_STATUS +AcpiOsInstallInterruptHandler ( + UINT32 InterruptNumber, + ACPI_OSD_HANDLER ServiceRoutine, + void *Context); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsRemoveInterruptHandler +ACPI_STATUS +AcpiOsRemoveInterruptHandler ( + UINT32 InterruptNumber, + ACPI_OSD_HANDLER ServiceRoutine); +#endif + + +/* + * Threads and Scheduling + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetThreadId +ACPI_THREAD_ID +AcpiOsGetThreadId ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsExecute +ACPI_STATUS +AcpiOsExecute ( + ACPI_EXECUTE_TYPE Type, + ACPI_OSD_EXEC_CALLBACK Function, + void *Context); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsWaitEventsComplete +void +AcpiOsWaitEventsComplete ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsSleep +void +AcpiOsSleep ( + UINT64 Milliseconds); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsStall +void +AcpiOsStall ( + UINT32 Microseconds); +#endif + + +/* + * Platform and hardware-independent I/O interfaces + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsReadPort +ACPI_STATUS +AcpiOsReadPort ( + ACPI_IO_ADDRESS Address, + UINT32 *Value, + UINT32 Width); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsWritePort +ACPI_STATUS +AcpiOsWritePort ( + ACPI_IO_ADDRESS Address, + UINT32 Value, + UINT32 Width); +#endif + + +/* + * Platform and hardware-independent physical memory interfaces + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsReadMemory +ACPI_STATUS +AcpiOsReadMemory ( + ACPI_PHYSICAL_ADDRESS Address, + UINT64 *Value, + UINT32 Width); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsWriteMemory +ACPI_STATUS +AcpiOsWriteMemory ( + ACPI_PHYSICAL_ADDRESS Address, + UINT64 Value, + UINT32 Width); +#endif + + +/* + * Platform and hardware-independent PCI configuration space access + * Note: Can't use "Register" as a parameter, changed to "Reg" -- + * certain compilers complain. + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsReadPciConfiguration +ACPI_STATUS +AcpiOsReadPciConfiguration ( + ACPI_PCI_ID *PciId, + UINT32 Reg, + UINT64 *Value, + UINT32 Width); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsWritePciConfiguration +ACPI_STATUS +AcpiOsWritePciConfiguration ( + ACPI_PCI_ID *PciId, + UINT32 Reg, + UINT64 Value, + UINT32 Width); +#endif + + +/* + * Miscellaneous + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsReadable +BOOLEAN +AcpiOsReadable ( + void *Pointer, + ACPI_SIZE Length); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsWritable +BOOLEAN +AcpiOsWritable ( + void *Pointer, + ACPI_SIZE Length); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetTimer +UINT64 +AcpiOsGetTimer ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsSignal +ACPI_STATUS +AcpiOsSignal ( + UINT32 Function, + void *Info); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsEnterSleep +ACPI_STATUS +AcpiOsEnterSleep ( + UINT8 SleepState, + UINT32 RegaValue, + UINT32 RegbValue); +#endif + + +/* + * Debug print routines + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsPrintf +void ACPI_INTERNAL_VAR_XFACE +AcpiOsPrintf ( + const char *Format, + ...); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsVprintf +void +AcpiOsVprintf ( + const char *Format, + va_list Args); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsRedirectOutput +void +AcpiOsRedirectOutput ( + void *Destination); +#endif + + +/* + * Debug IO + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetLine +ACPI_STATUS +AcpiOsGetLine ( + char *Buffer, + UINT32 BufferLength, + UINT32 *BytesRead); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsInitializeDebugger +ACPI_STATUS +AcpiOsInitializeDebugger ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsTerminateDebugger +void +AcpiOsTerminateDebugger ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsWaitCommandReady +ACPI_STATUS +AcpiOsWaitCommandReady ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsNotifyCommandComplete +ACPI_STATUS +AcpiOsNotifyCommandComplete ( + void); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsTracePoint +void +AcpiOsTracePoint ( + ACPI_TRACE_EVENT_TYPE Type, + BOOLEAN Begin, + UINT8 *Aml, + char *Pathname); +#endif + + +/* + * Obtain ACPI table(s) + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetTableByName +ACPI_STATUS +AcpiOsGetTableByName ( + char *Signature, + UINT32 Instance, + ACPI_TABLE_HEADER **Table, + ACPI_PHYSICAL_ADDRESS *Address); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetTableByIndex +ACPI_STATUS +AcpiOsGetTableByIndex ( + UINT32 Index, + ACPI_TABLE_HEADER **Table, + UINT32 *Instance, + ACPI_PHYSICAL_ADDRESS *Address); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetTableByAddress +ACPI_STATUS +AcpiOsGetTableByAddress ( + ACPI_PHYSICAL_ADDRESS Address, + ACPI_TABLE_HEADER **Table); +#endif + + +/* + * Directory manipulation + */ +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsOpenDirectory +void * +AcpiOsOpenDirectory ( + char *Pathname, + char *WildcardSpec, + char RequestedFileType); +#endif + +/* RequesteFileType values */ + +#define REQUEST_FILE_ONLY 0 +#define REQUEST_DIR_ONLY 1 + + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsGetNextFilename +char * +AcpiOsGetNextFilename ( + void *DirHandle); +#endif + +#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsCloseDirectory +void +AcpiOsCloseDirectory ( + void *DirHandle); +#endif + + +#endif /* __ACPIOSXF_H__ */ diff --git a/ports/acpica/include/acpixf.h b/ports/acpica/include/acpixf.h new file mode 100644 index 0000000..6e10c85 --- /dev/null +++ b/ports/acpica/include/acpixf.h @@ -0,0 +1,1408 @@ +/****************************************************************************** + * + * Name: acpixf.h - External interfaces to the ACPI subsystem + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACXFACE_H__ +#define __ACXFACE_H__ + +/* Current ACPICA subsystem version in YYYYMMDD format */ + +#define ACPI_CA_VERSION 0x20180508 + +#include "acconfig.h" +#include "actypes.h" +#include "actbl.h" +#include "acbuffer.h" + + +/***************************************************************************** + * + * Macros used for ACPICA globals and configuration + * + ****************************************************************************/ + +/* + * Ensure that global variables are defined and initialized only once. + * + * The use of these macros allows for a single list of globals (here) + * in order to simplify maintenance of the code. + */ +#ifdef DEFINE_ACPI_GLOBALS +#define ACPI_GLOBAL(type,name) \ + extern type name; \ + type name + +#define ACPI_INIT_GLOBAL(type,name,value) \ + type name=value + +#else +#ifndef ACPI_GLOBAL +#define ACPI_GLOBAL(type,name) \ + extern type name +#endif + +#ifndef ACPI_INIT_GLOBAL +#define ACPI_INIT_GLOBAL(type,name,value) \ + extern type name +#endif +#endif + +/* + * These macros configure the various ACPICA interfaces. They are + * useful for generating stub inline functions for features that are + * configured out of the current kernel or ACPICA application. + */ +#ifndef ACPI_EXTERNAL_RETURN_STATUS +#define ACPI_EXTERNAL_RETURN_STATUS(Prototype) \ + Prototype; +#endif + +#ifndef ACPI_EXTERNAL_RETURN_OK +#define ACPI_EXTERNAL_RETURN_OK(Prototype) \ + Prototype; +#endif + +#ifndef ACPI_EXTERNAL_RETURN_VOID +#define ACPI_EXTERNAL_RETURN_VOID(Prototype) \ + Prototype; +#endif + +#ifndef ACPI_EXTERNAL_RETURN_UINT32 +#define ACPI_EXTERNAL_RETURN_UINT32(Prototype) \ + Prototype; +#endif + +#ifndef ACPI_EXTERNAL_RETURN_PTR +#define ACPI_EXTERNAL_RETURN_PTR(Prototype) \ + Prototype; +#endif + + +/***************************************************************************** + * + * Public globals and runtime configuration options + * + ****************************************************************************/ + +/* + * Enable "slack mode" of the AML interpreter? Default is FALSE, and the + * interpreter strictly follows the ACPI specification. Setting to TRUE + * allows the interpreter to ignore certain errors and/or bad AML constructs. + * + * Currently, these features are enabled by this flag: + * + * 1) Allow "implicit return" of last value in a control method + * 2) Allow access beyond the end of an operation region + * 3) Allow access to uninitialized locals/args (auto-init to integer 0) + * 4) Allow ANY object type to be a source operand for the Store() operator + * 5) Allow unresolved references (invalid target name) in package objects + * 6) Enable warning messages for behavior that is not ACPI spec compliant + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_EnableInterpreterSlack, FALSE); + +/* + * Automatically serialize all methods that create named objects? Default + * is TRUE, meaning that all NonSerialized methods are scanned once at + * table load time to determine those that create named objects. Methods + * that create named objects are marked Serialized in order to prevent + * possible run-time problems if they are entered by more than one thread. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_AutoSerializeMethods, TRUE); + +/* + * Create the predefined _OSI method in the namespace? Default is TRUE + * because ACPICA is fully compatible with other ACPI implementations. + * Changing this will revert ACPICA (and machine ASL) to pre-OSI behavior. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_CreateOsiMethod, TRUE); + +/* + * Optionally use default values for the ACPI register widths. Set this to + * TRUE to use the defaults, if an FADT contains incorrect widths/lengths. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_UseDefaultRegisterWidths, TRUE); + +/* + * Whether or not to validate (map) an entire table to verify + * checksum/duplication in early stage before install. Set this to TRUE to + * allow early table validation before install it to the table manager. + * Note that enabling this option causes errors to happen in some OSPMs + * during early initialization stages. Default behavior is to allow such + * validation. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_EnableTableValidation, TRUE); + +/* + * Optionally enable output from the AML Debug Object. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_EnableAmlDebugObject, FALSE); + +/* + * Optionally copy the entire DSDT to local memory (instead of simply + * mapping it.) There are some BIOSs that corrupt or replace the original + * DSDT, creating the need for this option. Default is FALSE, do not copy + * the DSDT. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_CopyDsdtLocally, FALSE); + +/* + * Optionally ignore an XSDT if present and use the RSDT instead. + * Although the ACPI specification requires that an XSDT be used instead + * of the RSDT, the XSDT has been found to be corrupt or ill-formed on + * some machines. Default behavior is to use the XSDT if present. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_DoNotUseXsdt, FALSE); + +/* + * Optionally support group module level code. + * NOTE, this is essentially obsolete and will be removed soon + * (01/2018). + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_GroupModuleLevelCode, FALSE); + +/* + * Optionally support module level code by parsing an entire table as + * a method as it is loaded. Default is TRUE. + * NOTE, this is essentially obsolete and will be removed soon + * (01/2018). + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_ExecuteTablesAsMethods, TRUE); + +/* + * Optionally use 32-bit FADT addresses if and when there is a conflict + * (address mismatch) between the 32-bit and 64-bit versions of the + * address. Although ACPICA adheres to the ACPI specification which + * requires the use of the corresponding 64-bit address if it is non-zero, + * some machines have been found to have a corrupted non-zero 64-bit + * address. Default is FALSE, do not favor the 32-bit addresses. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_Use32BitFadtAddresses, FALSE); + +/* + * Optionally use 32-bit FACS table addresses. + * It is reported that some platforms fail to resume from system suspending + * if 64-bit FACS table address is selected: + * https://bugzilla.kernel.org/show_bug.cgi?id=74021 + * Default is TRUE, favor the 32-bit addresses. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_Use32BitFacsAddresses, TRUE); + +/* + * Optionally truncate I/O addresses to 16 bits. Provides compatibility + * with other ACPI implementations. NOTE: During ACPICA initialization, + * this value is set to TRUE if any Windows OSI strings have been + * requested by the BIOS. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_TruncateIoAddresses, FALSE); + +/* + * Disable runtime checking and repair of values returned by control methods. + * Use only if the repair is causing a problem on a particular machine. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_DisableAutoRepair, FALSE); + +/* + * Optionally do not install any SSDTs from the RSDT/XSDT during initialization. + * This can be useful for debugging ACPI problems on some machines. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_DisableSsdtTableInstall, FALSE); + +/* + * Optionally enable runtime namespace override. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_RuntimeNamespaceOverride, TRUE); + +/* + * We keep track of the latest version of Windows that has been requested by + * the BIOS. ACPI 5.0. + */ +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_OsiData, 0); + +/* + * ACPI 5.0 introduces the concept of a "reduced hardware platform", meaning + * that the ACPI hardware is no longer required. A flag in the FADT indicates + * a reduced HW machine, and that flag is duplicated here for convenience. + */ +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_ReducedHardware, FALSE); + +/* + * Maximum timeout for While() loop iterations before forced method abort. + * This mechanism is intended to prevent infinite loops during interpreter + * execution within a host kernel. + */ +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_MaxLoopIterations, ACPI_MAX_LOOP_TIMEOUT); + +/* + * Optionally ignore AE_NOT_FOUND errors from named reference package elements + * during DSDT/SSDT table loading. This reduces error "noise" in platforms + * whose firmware is carrying around a bunch of unused package objects that + * refer to non-existent named objects. However, If the AML actually tries to + * use such a package, the unresolved element(s) will be replaced with NULL + * elements. + */ +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_IgnorePackageResolutionErrors, FALSE); + +/* + * This mechanism is used to trace a specified AML method. The method is + * traced each time it is executed. + */ +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_TraceFlags, 0); +ACPI_INIT_GLOBAL (const char *, AcpiGbl_TraceMethodName, NULL); +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_TraceDbgLevel, ACPI_TRACE_LEVEL_DEFAULT); +ACPI_INIT_GLOBAL (UINT32, AcpiGbl_TraceDbgLayer, ACPI_TRACE_LAYER_DEFAULT); + +/* + * Runtime configuration of debug output control masks. We want the debug + * switches statically initialized so they are already set when the debugger + * is entered. + */ +#ifdef ACPI_DEBUG_OUTPUT +ACPI_INIT_GLOBAL (UINT32, AcpiDbgLevel, ACPI_DEBUG_DEFAULT); +#else +ACPI_INIT_GLOBAL (UINT32, AcpiDbgLevel, ACPI_NORMAL_DEFAULT); +#endif +ACPI_INIT_GLOBAL (UINT32, AcpiDbgLayer, ACPI_COMPONENT_DEFAULT); + +/* Optionally enable timer output with Debug Object output */ + +ACPI_INIT_GLOBAL (UINT8, AcpiGbl_DisplayDebugTimer, FALSE); + +/* + * Debugger command handshake globals. Host OSes need to access these + * variables to implement their own command handshake mechanism. + */ +#ifdef ACPI_DEBUGGER +ACPI_INIT_GLOBAL (BOOLEAN, AcpiGbl_MethodExecuting, FALSE); +ACPI_GLOBAL (char, AcpiGbl_DbLineBuf[ACPI_DB_LINE_BUFFER_SIZE]); +#endif + +/* + * Other miscellaneous globals + */ +ACPI_GLOBAL (ACPI_TABLE_FADT, AcpiGbl_FADT); +ACPI_GLOBAL (UINT32, AcpiCurrentGpeCount); +ACPI_GLOBAL (BOOLEAN, AcpiGbl_SystemAwakeAndRunning); + + +/***************************************************************************** + * + * ACPICA public interface configuration. + * + * Interfaces that are configured out of the ACPICA build are replaced + * by inlined stubs by default. + * + ****************************************************************************/ + +/* + * Hardware-reduced prototypes (default: Not hardware reduced). + * + * All ACPICA hardware-related interfaces that use these macros will be + * configured out of the ACPICA build if the ACPI_REDUCED_HARDWARE flag + * is set to TRUE. + * + * Note: This static build option for reduced hardware is intended to + * reduce ACPICA code size if desired or necessary. However, even if this + * option is not specified, the runtime behavior of ACPICA is dependent + * on the actual FADT reduced hardware flag (HW_REDUCED_ACPI). If set, + * the flag will enable similar behavior -- ACPICA will not attempt + * to access any ACPI-relate hardware (SCI, GPEs, Fixed Events, etc.) + */ +#if (!ACPI_REDUCED_HARDWARE) +#define ACPI_HW_DEPENDENT_RETURN_STATUS(Prototype) \ + ACPI_EXTERNAL_RETURN_STATUS(Prototype) + +#define ACPI_HW_DEPENDENT_RETURN_OK(Prototype) \ + ACPI_EXTERNAL_RETURN_OK(Prototype) + +#define ACPI_HW_DEPENDENT_RETURN_VOID(Prototype) \ + ACPI_EXTERNAL_RETURN_VOID(Prototype) + +#else +#define ACPI_HW_DEPENDENT_RETURN_STATUS(Prototype) \ + static ACPI_INLINE Prototype {return(AE_NOT_CONFIGURED);} + +#define ACPI_HW_DEPENDENT_RETURN_OK(Prototype) \ + static ACPI_INLINE Prototype {return(AE_OK);} + +#define ACPI_HW_DEPENDENT_RETURN_VOID(Prototype) \ + static ACPI_INLINE Prototype {return;} + +#endif /* !ACPI_REDUCED_HARDWARE */ + + +/* + * Error message prototypes (default: error messages enabled). + * + * All interfaces related to error and warning messages + * will be configured out of the ACPICA build if the + * ACPI_NO_ERROR_MESSAGE flag is defined. + */ +#ifndef ACPI_NO_ERROR_MESSAGES +#define ACPI_MSG_DEPENDENT_RETURN_VOID(Prototype) \ + Prototype; + +#else +#define ACPI_MSG_DEPENDENT_RETURN_VOID(Prototype) \ + static ACPI_INLINE Prototype {return;} + +#endif /* ACPI_NO_ERROR_MESSAGES */ + + +/* + * Debugging output prototypes (default: no debug output). + * + * All interfaces related to debug output messages + * will be configured out of the ACPICA build unless the + * ACPI_DEBUG_OUTPUT flag is defined. + */ +#ifdef ACPI_DEBUG_OUTPUT +#define ACPI_DBG_DEPENDENT_RETURN_VOID(Prototype) \ + Prototype; + +#else +#define ACPI_DBG_DEPENDENT_RETURN_VOID(Prototype) \ + static ACPI_INLINE Prototype {return;} + +#endif /* ACPI_DEBUG_OUTPUT */ + + +/* + * Application prototypes + * + * All interfaces used by application will be configured + * out of the ACPICA build unless the ACPI_APPLICATION + * flag is defined. + */ +#ifdef ACPI_APPLICATION +#define ACPI_APP_DEPENDENT_RETURN_VOID(Prototype) \ + Prototype; + +#else +#define ACPI_APP_DEPENDENT_RETURN_VOID(Prototype) \ + static ACPI_INLINE Prototype {return;} + +#endif /* ACPI_APPLICATION */ + + +/* + * Debugger prototypes + * + * All interfaces used by debugger will be configured + * out of the ACPICA build unless the ACPI_DEBUGGER + * flag is defined. + */ +#ifdef ACPI_DEBUGGER +#define ACPI_DBR_DEPENDENT_RETURN_OK(Prototype) \ + ACPI_EXTERNAL_RETURN_OK(Prototype) + +#define ACPI_DBR_DEPENDENT_RETURN_VOID(Prototype) \ + ACPI_EXTERNAL_RETURN_VOID(Prototype) + +#else +#define ACPI_DBR_DEPENDENT_RETURN_OK(Prototype) \ + static ACPI_INLINE Prototype {return(AE_OK);} + +#define ACPI_DBR_DEPENDENT_RETURN_VOID(Prototype) \ + static ACPI_INLINE Prototype {return;} + +#endif /* ACPI_DEBUGGER */ + + +/***************************************************************************** + * + * ACPICA public interface prototypes + * + ****************************************************************************/ + +/* + * Initialization + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiInitializeTables ( + ACPI_TABLE_DESC *InitialStorage, + UINT32 InitialTableCount, + BOOLEAN AllowResize)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiInitializeSubsystem ( + void)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiEnableSubsystem ( + UINT32 Flags)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiInitializeObjects ( + UINT32 Flags)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiTerminate ( + void)) + + +/* + * Miscellaneous global interfaces + */ +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiEnable ( + void)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiDisable ( + void)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiSubsystemStatus ( + void)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetSystemInfo ( + ACPI_BUFFER *RetBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetStatistics ( + ACPI_STATISTICS *Stats)) + +ACPI_EXTERNAL_RETURN_PTR ( +const char * +AcpiFormatException ( + ACPI_STATUS Exception)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiPurgeCachedObjects ( + void)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallInterface ( + ACPI_STRING InterfaceName)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveInterface ( + ACPI_STRING InterfaceName)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiUpdateInterfaces ( + UINT8 Action)) + +ACPI_EXTERNAL_RETURN_UINT32 ( +UINT32 +AcpiCheckAddressRange ( + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_PHYSICAL_ADDRESS Address, + ACPI_SIZE Length, + BOOLEAN Warn)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiDecodePldBuffer ( + UINT8 *InBuffer, + ACPI_SIZE Length, + ACPI_PLD_INFO **ReturnBuffer)) + + +/* + * ACPI table load/unload interfaces + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiInstallTable ( + ACPI_PHYSICAL_ADDRESS Address, + BOOLEAN Physical)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiLoadTable ( + ACPI_TABLE_HEADER *Table)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiUnloadParentTable ( + ACPI_HANDLE Object)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiLoadTables ( + void)) + + +/* + * ACPI table manipulation interfaces + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiReallocateRootTable ( + void)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS ACPI_INIT_FUNCTION +AcpiFindRootPointer ( + ACPI_PHYSICAL_ADDRESS *RsdpAddress)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetTableHeader ( + ACPI_STRING Signature, + UINT32 Instance, + ACPI_TABLE_HEADER *OutTableHeader)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetTable ( + ACPI_STRING Signature, + UINT32 Instance, + ACPI_TABLE_HEADER **OutTable)) + +ACPI_EXTERNAL_RETURN_VOID ( +void +AcpiPutTable ( + ACPI_TABLE_HEADER *Table)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetTableByIndex ( + UINT32 TableIndex, + ACPI_TABLE_HEADER **OutTable)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallTableHandler ( + ACPI_TABLE_HANDLER Handler, + void *Context)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveTableHandler ( + ACPI_TABLE_HANDLER Handler)) + + +/* + * Namespace and name interfaces + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiWalkNamespace ( + ACPI_OBJECT_TYPE Type, + ACPI_HANDLE StartObject, + UINT32 MaxDepth, + ACPI_WALK_CALLBACK DescendingCallback, + ACPI_WALK_CALLBACK AscendingCallback, + void *Context, + void **ReturnValue)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetDevices ( + char *HID, + ACPI_WALK_CALLBACK UserFunction, + void *Context, + void **ReturnValue)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetName ( + ACPI_HANDLE Object, + UINT32 NameType, + ACPI_BUFFER *RetPathPtr)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetHandle ( + ACPI_HANDLE Parent, + ACPI_STRING Pathname, + ACPI_HANDLE *RetHandle)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiAttachData ( + ACPI_HANDLE Object, + ACPI_OBJECT_HANDLER Handler, + void *Data)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiDetachData ( + ACPI_HANDLE Object, + ACPI_OBJECT_HANDLER Handler)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetData ( + ACPI_HANDLE Object, + ACPI_OBJECT_HANDLER Handler, + void **Data)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiDebugTrace ( + const char *Name, + UINT32 DebugLevel, + UINT32 DebugLayer, + UINT32 Flags)) + + +/* + * Object manipulation and enumeration + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiEvaluateObject ( + ACPI_HANDLE Object, + ACPI_STRING Pathname, + ACPI_OBJECT_LIST *ParameterObjects, + ACPI_BUFFER *ReturnObjectBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiEvaluateObjectTyped ( + ACPI_HANDLE Object, + ACPI_STRING Pathname, + ACPI_OBJECT_LIST *ExternalParams, + ACPI_BUFFER *ReturnBuffer, + ACPI_OBJECT_TYPE ReturnType)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetObjectInfo ( + ACPI_HANDLE Object, + ACPI_DEVICE_INFO **ReturnBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallMethod ( + UINT8 *Buffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetNextObject ( + ACPI_OBJECT_TYPE Type, + ACPI_HANDLE Parent, + ACPI_HANDLE Child, + ACPI_HANDLE *OutHandle)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetType ( + ACPI_HANDLE Object, + ACPI_OBJECT_TYPE *OutType)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetParent ( + ACPI_HANDLE Object, + ACPI_HANDLE *OutHandle)) + + +/* + * Handler interfaces + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallInitializationHandler ( + ACPI_INIT_HANDLER Handler, + UINT32 Function)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallSciHandler ( + ACPI_SCI_HANDLER Address, + void *Context)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveSciHandler ( + ACPI_SCI_HANDLER Address)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallGlobalEventHandler ( + ACPI_GBL_EVENT_HANDLER Handler, + void *Context)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallFixedEventHandler ( + UINT32 AcpiEvent, + ACPI_EVENT_HANDLER Handler, + void *Context)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveFixedEventHandler ( + UINT32 AcpiEvent, + ACPI_EVENT_HANDLER Handler)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallGpeHandler ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + UINT32 Type, + ACPI_GPE_HANDLER Address, + void *Context)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallGpeRawHandler ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + UINT32 Type, + ACPI_GPE_HANDLER Address, + void *Context)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveGpeHandler ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + ACPI_GPE_HANDLER Address)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallNotifyHandler ( + ACPI_HANDLE Device, + UINT32 HandlerType, + ACPI_NOTIFY_HANDLER Handler, + void *Context)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveNotifyHandler ( + ACPI_HANDLE Device, + UINT32 HandlerType, + ACPI_NOTIFY_HANDLER Handler)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallAddressSpaceHandler ( + ACPI_HANDLE Device, + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_ADR_SPACE_HANDLER Handler, + ACPI_ADR_SPACE_SETUP Setup, + void *Context)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveAddressSpaceHandler ( + ACPI_HANDLE Device, + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_ADR_SPACE_HANDLER Handler)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallExceptionHandler ( + ACPI_EXCEPTION_HANDLER Handler)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallInterfaceHandler ( + ACPI_INTERFACE_HANDLER Handler)) + + +/* + * Global Lock interfaces + */ +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiAcquireGlobalLock ( + UINT16 Timeout, + UINT32 *Handle)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiReleaseGlobalLock ( + UINT32 Handle)) + + +/* + * Interfaces to AML mutex objects + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiAcquireMutex ( + ACPI_HANDLE Handle, + ACPI_STRING Pathname, + UINT16 Timeout)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiReleaseMutex ( + ACPI_HANDLE Handle, + ACPI_STRING Pathname)) + + +/* + * Fixed Event interfaces + */ +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiEnableEvent ( + UINT32 Event, + UINT32 Flags)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiDisableEvent ( + UINT32 Event, + UINT32 Flags)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiClearEvent ( + UINT32 Event)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiGetEventStatus ( + UINT32 Event, + ACPI_EVENT_STATUS *EventStatus)) + + +/* + * General Purpose Event (GPE) Interfaces + */ +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiUpdateAllGpes ( + void)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiEnableGpe ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiDisableGpe ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiClearGpe ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiSetGpe ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + UINT8 Action)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiFinishGpe ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiMaskGpe ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + BOOLEAN IsMasked)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiMarkGpeForWake ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiSetupGpeForWake ( + ACPI_HANDLE ParentDevice, + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiSetGpeWakeMask ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + UINT8 Action)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiGetGpeStatus ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + ACPI_EVENT_STATUS *EventStatus)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiDisableAllGpes ( + void)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiEnableAllRuntimeGpes ( + void)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiEnableAllWakeupGpes ( + void)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiGetGpeDevice ( + UINT32 GpeIndex, + ACPI_HANDLE *GpeDevice)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiInstallGpeBlock ( + ACPI_HANDLE GpeDevice, + ACPI_GENERIC_ADDRESS *GpeBlockAddress, + UINT32 RegisterCount, + UINT32 InterruptNumber)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiRemoveGpeBlock ( + ACPI_HANDLE GpeDevice)) + + +/* + * Resource interfaces + */ +typedef +ACPI_STATUS (*ACPI_WALK_RESOURCE_CALLBACK) ( + ACPI_RESOURCE *Resource, + void *Context); + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetVendorResource ( + ACPI_HANDLE Device, + char *Name, + ACPI_VENDOR_UUID *Uuid, + ACPI_BUFFER *RetBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetCurrentResources ( + ACPI_HANDLE Device, + ACPI_BUFFER *RetBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetPossibleResources ( + ACPI_HANDLE Device, + ACPI_BUFFER *RetBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetEventResources ( + ACPI_HANDLE DeviceHandle, + ACPI_BUFFER *RetBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiWalkResourceBuffer ( + ACPI_BUFFER *Buffer, + ACPI_WALK_RESOURCE_CALLBACK UserFunction, + void *Context)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiWalkResources ( + ACPI_HANDLE Device, + char *Name, + ACPI_WALK_RESOURCE_CALLBACK UserFunction, + void *Context)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiSetCurrentResources ( + ACPI_HANDLE Device, + ACPI_BUFFER *InBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetIrqRoutingTable ( + ACPI_HANDLE Device, + ACPI_BUFFER *RetBuffer)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiResourceToAddress64 ( + ACPI_RESOURCE *Resource, + ACPI_RESOURCE_ADDRESS64 *Out)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiBufferToResource ( + UINT8 *AmlBuffer, + UINT16 AmlBufferLength, + ACPI_RESOURCE **ResourcePtr)) + + +/* + * Hardware (ACPI device) interfaces + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiReset ( + void)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiRead ( + UINT64 *Value, + ACPI_GENERIC_ADDRESS *Reg)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiWrite ( + UINT64 Value, + ACPI_GENERIC_ADDRESS *Reg)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiReadBitRegister ( + UINT32 RegisterId, + UINT32 *ReturnValue)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiWriteBitRegister ( + UINT32 RegisterId, + UINT32 Value)) + + +/* + * Sleep/Wake interfaces + */ +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiGetSleepTypeData ( + UINT8 SleepState, + UINT8 *Slp_TypA, + UINT8 *Slp_TypB)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiEnterSleepStatePrep ( + UINT8 SleepState)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiEnterSleepState ( + UINT8 SleepState)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiEnterSleepStateS4bios ( + void)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiLeaveSleepStatePrep ( + UINT8 SleepState)) + +ACPI_EXTERNAL_RETURN_STATUS ( +ACPI_STATUS +AcpiLeaveSleepState ( + UINT8 SleepState)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiSetFirmwareWakingVector ( + ACPI_PHYSICAL_ADDRESS PhysicalAddress, + ACPI_PHYSICAL_ADDRESS PhysicalAddress64)) + + +/* + * ACPI Timer interfaces + */ +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiGetTimerResolution ( + UINT32 *Resolution)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiGetTimer ( + UINT32 *Ticks)) + +ACPI_HW_DEPENDENT_RETURN_STATUS ( +ACPI_STATUS +AcpiGetTimerDuration ( + UINT32 StartTicks, + UINT32 EndTicks, + UINT32 *TimeElapsed)) + + +/* + * Error/Warning output + */ +ACPI_MSG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(3) +void ACPI_INTERNAL_VAR_XFACE +AcpiError ( + const char *ModuleName, + UINT32 LineNumber, + const char *Format, + ...)) + +ACPI_MSG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(4) +void ACPI_INTERNAL_VAR_XFACE +AcpiException ( + const char *ModuleName, + UINT32 LineNumber, + ACPI_STATUS Status, + const char *Format, + ...)) + +ACPI_MSG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(3) +void ACPI_INTERNAL_VAR_XFACE +AcpiWarning ( + const char *ModuleName, + UINT32 LineNumber, + const char *Format, + ...)) + +ACPI_MSG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(1) +void ACPI_INTERNAL_VAR_XFACE +AcpiInfo ( + const char *Format, + ...)) + +ACPI_MSG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(3) +void ACPI_INTERNAL_VAR_XFACE +AcpiBiosError ( + const char *ModuleName, + UINT32 LineNumber, + const char *Format, + ...)) + +ACPI_MSG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(3) +void ACPI_INTERNAL_VAR_XFACE +AcpiBiosWarning ( + const char *ModuleName, + UINT32 LineNumber, + const char *Format, + ...)) + + +/* + * Debug output + */ +ACPI_DBG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(6) +void ACPI_INTERNAL_VAR_XFACE +AcpiDebugPrint ( + UINT32 RequestedDebugLevel, + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + const char *Format, + ...)) + +ACPI_DBG_DEPENDENT_RETURN_VOID ( +ACPI_PRINTF_LIKE(6) +void ACPI_INTERNAL_VAR_XFACE +AcpiDebugPrintRaw ( + UINT32 RequestedDebugLevel, + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + const char *Format, + ...)) + +ACPI_DBG_DEPENDENT_RETURN_VOID ( +void +AcpiTracePoint ( + ACPI_TRACE_EVENT_TYPE Type, + BOOLEAN Begin, + UINT8 *Aml, + char *Pathname)) + +ACPI_STATUS +AcpiInitializeDebugger ( + void); + +void +AcpiTerminateDebugger ( + void); + +void +AcpiRunDebugger ( + char *BatchBuffer); + +void +AcpiSetDebuggerThreadId ( + ACPI_THREAD_ID ThreadId); + +#endif /* __ACXFACE_H__ */ diff --git a/ports/acpica/include/acpredef.h b/ports/acpica/include/acpredef.h new file mode 100644 index 0000000..cd674d2 --- /dev/null +++ b/ports/acpica/include/acpredef.h @@ -0,0 +1,1251 @@ +/****************************************************************************** + * + * Name: acpredef - Information table for ACPI predefined methods and objects + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACPREDEF_H__ +#define __ACPREDEF_H__ + + +/****************************************************************************** + * + * Return Package types + * + * 1) PTYPE1 packages do not contain subpackages. + * + * ACPI_PTYPE1_FIXED: Fixed-length length, 1 or 2 object types: + * object type + * count + * object type + * count + * + * ACPI_PTYPE1_VAR: Variable-length length. Zero-length package is allowed: + * object type (Int/Buf/Ref) + * + * ACPI_PTYPE1_OPTION: Package has some required and some optional elements + * (Used for _PRW) + * + * + * 2) PTYPE2 packages contain a Variable-length number of subpackages. Each + * of the different types describe the contents of each of the subpackages. + * + * ACPI_PTYPE2: Each subpackage contains 1 or 2 object types. Zero-length + * parent package is allowed: + * object type + * count + * object type + * count + * (Used for _ALR,_MLS,_PSS,_TRT,_TSS) + * + * ACPI_PTYPE2_COUNT: Each subpackage has a count as first element. + * Zero-length parent package is allowed: + * object type + * (Used for _CSD,_PSD,_TSD) + * + * ACPI_PTYPE2_PKG_COUNT: Count of subpackages at start, 1 or 2 object types: + * object type + * count + * object type + * count + * (Used for _CST) + * + * ACPI_PTYPE2_FIXED: Each subpackage is of Fixed-length. Zero-length + * parent package is allowed. + * (Used for _PRT) + * + * ACPI_PTYPE2_MIN: Each subpackage has a Variable-length but minimum length. + * Zero-length parent package is allowed: + * (Used for _HPX) + * + * ACPI_PTYPE2_REV_FIXED: Revision at start, each subpackage is Fixed-length + * (Used for _ART, _FPS) + * + * ACPI_PTYPE2_FIX_VAR: Each subpackage consists of some fixed-length elements + * followed by an optional element. Zero-length parent package is allowed. + * object type + * count + * object type + * count = 0 (optional) + * (Used for _DLM) + * + * ACPI_PTYPE2_VAR_VAR: Variable number of subpackages, each of either a + * constant or variable length. The subpackages are preceded by a + * constant number of objects. + * (Used for _LPI, _RDI) + * + * ACPI_PTYPE2_UUID_PAIR: Each subpackage is preceded by a UUID Buffer. The UUID + * defines the format of the package. Zero-length parent package is + * allowed. + * (Used for _DSD) + * + *****************************************************************************/ + +enum AcpiReturnPackageTypes +{ + ACPI_PTYPE1_FIXED = 1, + ACPI_PTYPE1_VAR = 2, + ACPI_PTYPE1_OPTION = 3, + ACPI_PTYPE2 = 4, + ACPI_PTYPE2_COUNT = 5, + ACPI_PTYPE2_PKG_COUNT = 6, + ACPI_PTYPE2_FIXED = 7, + ACPI_PTYPE2_MIN = 8, + ACPI_PTYPE2_REV_FIXED = 9, + ACPI_PTYPE2_FIX_VAR = 10, + ACPI_PTYPE2_VAR_VAR = 11, + ACPI_PTYPE2_UUID_PAIR = 12, + ACPI_PTYPE_CUSTOM = 13 +}; + + +/* Support macros for users of the predefined info table */ + +#define METHOD_PREDEF_ARGS_MAX 4 +#define METHOD_ARG_BIT_WIDTH 3 +#define METHOD_ARG_MASK 0x0007 +#define ARG_COUNT_IS_MINIMUM 0x8000 +#define METHOD_MAX_ARG_TYPE ACPI_TYPE_PACKAGE + +#define METHOD_GET_ARG_COUNT(ArgList) ((ArgList) & METHOD_ARG_MASK) +#define METHOD_GET_NEXT_TYPE(ArgList) (((ArgList) >>= METHOD_ARG_BIT_WIDTH) & METHOD_ARG_MASK) + +/* Macros used to build the predefined info table */ + +#define METHOD_0ARGS 0 +#define METHOD_1ARGS(a1) (1 | (a1 << 3)) +#define METHOD_2ARGS(a1,a2) (2 | (a1 << 3) | (a2 << 6)) +#define METHOD_3ARGS(a1,a2,a3) (3 | (a1 << 3) | (a2 << 6) | (a3 << 9)) +#define METHOD_4ARGS(a1,a2,a3,a4) (4 | (a1 << 3) | (a2 << 6) | (a3 << 9) | (a4 << 12)) + +#define METHOD_RETURNS(type) (type) +#define METHOD_NO_RETURN_VALUE 0 + +#define PACKAGE_INFO(a,b,c,d,e,f) {{{(a),(b),(c),(d)}, ((((UINT16)(f)) << 8) | (e)), 0}} + + +/* Support macros for the resource descriptor info table */ + +#define WIDTH_1 0x0001 +#define WIDTH_2 0x0002 +#define WIDTH_3 0x0004 +#define WIDTH_8 0x0008 +#define WIDTH_16 0x0010 +#define WIDTH_32 0x0020 +#define WIDTH_64 0x0040 +#define VARIABLE_DATA 0x0080 +#define NUM_RESOURCE_WIDTHS 8 + +#define WIDTH_ADDRESS WIDTH_16 | WIDTH_32 | WIDTH_64 + + +#ifdef ACPI_CREATE_PREDEFINED_TABLE +/****************************************************************************** + * + * Predefined method/object information table. + * + * These are the names that can actually be evaluated via AcpiEvaluateObject. + * Not present in this table are the following: + * + * 1) Predefined/Reserved names that are not usually evaluated via + * AcpiEvaluateObject: + * _Lxx and _Exx GPE methods + * _Qxx EC methods + * _T_x compiler temporary variables + * _Wxx wake events + * + * 2) Predefined names that never actually exist within the AML code: + * Predefined resource descriptor field names + * + * 3) Predefined names that are implemented within ACPICA: + * _OSI + * + * The main entries in the table each contain the following items: + * + * Name - The ACPI reserved name + * ArgumentList - Contains (in 16 bits), the number of required + * arguments to the method (3 bits), and a 3-bit type + * field for each argument (up to 4 arguments). The + * METHOD_?ARGS macros generate the correct packed data. + * ExpectedBtypes - Allowed type(s) for the return value. + * 0 means that no return value is expected. + * + * For methods that return packages, the next entry in the table contains + * information about the expected structure of the package. This information + * is saved here (rather than in a separate table) in order to minimize the + * overall size of the stored data. + * + * Note: The additional braces are intended to promote portability. + * + * Note2: Table is used by the kernel-resident subsystem, the iASL compiler, + * and the AcpiHelp utility. + * + * TBD: _PRT - currently ignore reversed entries. Attempt to fix in nsrepair. + * Possibly fixing package elements like _BIF, etc. + * + *****************************************************************************/ + +const ACPI_PREDEFINED_INFO AcpiGbl_PredefinedMethods[] = +{ + {{"_AC0", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC1", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC2", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC3", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC4", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC5", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC6", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC7", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC8", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AC9", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_ADR", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_AEI", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_AL0", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL1", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL2", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL3", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL4", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL5", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL6", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL7", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL8", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_AL9", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_ALC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_ALI", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_ALP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_ALR", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 2 (Ints) */ + PACKAGE_INFO (ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 2,0,0,0), + + {{"_ALT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_ART", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(rev), n Pkg (2 Ref/11 Int) */ + PACKAGE_INFO (ACPI_PTYPE2_REV_FIXED, ACPI_RTYPE_REFERENCE, 2, ACPI_RTYPE_INTEGER, 11,0), + + {{"_BBN", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_BCL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0,0,0,0), + + {{"_BCM", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BCT", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_BDN", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_BFS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BIF", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (9 Int),(4 Str) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 9, ACPI_RTYPE_STRING, 4,0), + + {{"_BIX", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (16 Int),(4 Str) */ + PACKAGE_INFO (ACPI_PTYPE_CUSTOM, ACPI_RTYPE_INTEGER, 16, ACPI_RTYPE_STRING, 4,0), + + {{"_BLT", METHOD_3ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BMA", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_BMC", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BMD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (5 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 5,0,0,0), + + {{"_BMS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_BQC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_BST", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4,0,0,0), + + {{"_BTH", METHOD_1ARGS (ACPI_TYPE_INTEGER), /* ACPI 6.0 */ + METHOD_NO_RETURN_VALUE}}, + + {{"_BTM", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_BTP", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_CBA", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* See PCI firmware spec 3.0 */ + + {{"_CCA", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* ACPI 5.1 */ + + {{"_CDM", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_CID", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints/Strs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING, 0,0,0,0), + + {{"_CLS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (3 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3,0,0,0), + + {{"_CPC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints/Bufs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER | ACPI_RTYPE_BUFFER, 0,0,0,0), + + {{"_CR3", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_CRS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_CRT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_CSD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(n), n-1 Int) */ + PACKAGE_INFO (ACPI_PTYPE2_COUNT, ACPI_RTYPE_INTEGER, 0,0,0,0), + + {{"_CST", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(n), n Pkg (1 Buf/3 Int) */ + PACKAGE_INFO (ACPI_PTYPE2_PKG_COUNT,ACPI_RTYPE_BUFFER, 1, ACPI_RTYPE_INTEGER, 3,0), + + {{"_CWS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_DCK", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_DCS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_DDC", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER | ACPI_RTYPE_BUFFER)}}, + + {{"_DDN", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_STRING)}}, + + {{"_DEP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_DGS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_DIS", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_DLM", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (1 Ref, 0/1 Optional Buf/Ref) */ + PACKAGE_INFO (ACPI_PTYPE2_FIX_VAR, ACPI_RTYPE_REFERENCE, 1, ACPI_RTYPE_REFERENCE | ACPI_RTYPE_BUFFER, 0,0), + + {{"_DMA", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_DOD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0,0,0,0), + + {{"_DOS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_DSD", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each: 1 Buf, 1 Pkg */ + PACKAGE_INFO (ACPI_PTYPE2_UUID_PAIR, ACPI_RTYPE_BUFFER, 1, ACPI_RTYPE_PACKAGE, 1,0), + + {{"_DSM", METHOD_4ARGS (ACPI_TYPE_BUFFER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_PACKAGE), + METHOD_RETURNS (ACPI_RTYPE_ALL)}}, /* Must return a value, but it can be of any type */ + + {{"_DSS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_DSW", METHOD_3ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_DTI", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EC_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_EDL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs)*/ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_EJ0", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ1", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ2", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ3", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ4", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_STRING)}}, + + {{"_ERR", METHOD_3ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_STRING, ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* Internal use only, used by ACPICA test suites */ + + {{"_EVT", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_FDE", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_FDI", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (16 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 16,0,0,0), + + {{"_FDM", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_FIF", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4,0,0,0), + + {{"_FIT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, /* ACPI 6.0 */ + + {{"_FIX", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0,0,0,0), + + {{"_FPS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(rev), n Pkg (5 Int) */ + PACKAGE_INFO (ACPI_PTYPE2_REV_FIXED,ACPI_RTYPE_INTEGER, 5, 0,0,0), + + {{"_FSL", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_FST", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (3 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3,0,0,0), + + {{"_GAI", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_GCP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_GHL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_GLK", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_GPD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_GPE", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* _GPE method, not _GPE scope */ + + {{"_GRT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_GSB", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_GTF", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_GTM", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_GTS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_GWS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_HID", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING)}}, + + {{"_HMA", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_HOT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_HPP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4,0,0,0), + + /* + * For _HPX, a single package is returned, containing a variable-length number + * of subpackages. Each subpackage contains a PCI record setting. + * There are several different type of record settings, of different + * lengths, but all elements of all settings are Integers. + */ + {{"_HPX", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (var Ints) */ + PACKAGE_INFO (ACPI_PTYPE2_MIN, ACPI_RTYPE_INTEGER, 5,0,0,0), + + {{"_HRV", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_IFT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* See IPMI spec */ + + {{"_INI", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_IRC", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_LCK", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_LID", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_LPD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(rev), n Pkg (2 Int) */ + PACKAGE_INFO (ACPI_PTYPE2_REV_FIXED, ACPI_RTYPE_INTEGER, 2,0,0,0), + + {{"_LPI", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (3 Int, n Pkg (10 Int/Buf) */ + PACKAGE_INFO (ACPI_PTYPE2_VAR_VAR, ACPI_RTYPE_INTEGER, 3, + ACPI_RTYPE_INTEGER | ACPI_RTYPE_BUFFER | ACPI_RTYPE_STRING, 10,0), + + {{"_LSI", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3,0,0,0), + + {{"_LSR", METHOD_2ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 1, ACPI_RTYPE_BUFFER, 1,0), + + {{"_LSW", METHOD_3ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_BUFFER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_MAT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_MBM", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (8 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 8,0,0,0), + + {{"_MLS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (1 Str/1 Buf) */ + PACKAGE_INFO (ACPI_PTYPE2, ACPI_RTYPE_STRING, 1, ACPI_RTYPE_BUFFER, 1,0), + + {{"_MSG", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_MSM", METHOD_4ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_MTL", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_NTT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_OFF", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_ON_", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_OS_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_STRING)}}, + + {{"_OSC", METHOD_4ARGS (ACPI_TYPE_BUFFER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_BUFFER), + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_OST", METHOD_3ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PAI", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PCL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PCT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (2 Buf) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_BUFFER, 2,0,0,0), + + {{"_PDC", METHOD_1ARGS (ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PDL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PIC", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PIF", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (3 Int),(3 Str) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3, ACPI_RTYPE_STRING, 3,0), + + {{"_PLD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Bufs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_BUFFER, 0,0,0,0), + + {{"_PMC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (11 Int),(3 Str) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 11, ACPI_RTYPE_STRING, 3,0), + + {{"_PMD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PMM", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PPC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PPE", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* See dig64 spec */ + + {{"_PR0", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PR1", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PR2", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PR3", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PRE", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PRL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PRR", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Ref) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_REFERENCE, 1,0,0,0), + + {{"_PRS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + /* + * For _PRT, many BIOSs reverse the 3rd and 4th Package elements (Source + * and SourceIndex). This bug is so prevalent that there is code in the + * ACPICA Resource Manager to detect this and switch them back. For now, + * do not allow and issue a warning. To allow this and eliminate the + * warning, add the ACPI_RTYPE_REFERENCE type to the 4th element (index 3) + * in the statement below. + */ + {{"_PRT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (4): Int,Int,Int/Ref,Int */ + PACKAGE_INFO (ACPI_PTYPE2_FIXED, 4, ACPI_RTYPE_INTEGER, ACPI_RTYPE_INTEGER, + ACPI_RTYPE_INTEGER | ACPI_RTYPE_REFERENCE, ACPI_RTYPE_INTEGER), + + {{"_PRW", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each: Pkg/Int,Int,[Variable-length Refs] (Pkg is Ref/Int) */ + PACKAGE_INFO (ACPI_PTYPE1_OPTION, 2, ACPI_RTYPE_INTEGER | ACPI_RTYPE_PACKAGE, + ACPI_RTYPE_INTEGER, ACPI_RTYPE_REFERENCE, 0), + + {{"_PS0", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PS1", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PS2", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PS3", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PSC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PSD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (5 Int) with count */ + PACKAGE_INFO (ACPI_PTYPE2_COUNT, ACPI_RTYPE_INTEGER, 0,0,0,0), + + {{"_PSE", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PSL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_PSR", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PSS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (6 Int) */ + PACKAGE_INFO (ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 6,0,0,0), + + {{"_PSV", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PSW", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PTC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (2 Buf) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_BUFFER, 2,0,0,0), + + {{"_PTP", METHOD_2ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_PTS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PUR", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (2 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 2,0,0,0), + + {{"_PXM", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_RDI", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int, n Pkg (m Ref)) */ + PACKAGE_INFO (ACPI_PTYPE2_VAR_VAR, ACPI_RTYPE_INTEGER, 1, + ACPI_RTYPE_REFERENCE,0,0), + + {{"_REG", METHOD_2ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_REV", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_RMV", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_ROM", METHOD_2ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_RST", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_NO_RETURN_VALUE}}, + + {{"_RTV", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + /* + * For _S0_ through _S5_, the ACPI spec defines a return Package + * containing 1 Integer, but most DSDTs have it wrong - 2,3, or 4 integers. + * Allow this by making the objects "Variable-length length", but all elements + * must be Integers. + */ + {{"_S0_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0,0,0), + + {{"_S1_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0,0,0), + + {{"_S2_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0,0,0), + + {{"_S3_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0,0,0), + + {{"_S4_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0,0,0), + + {{"_S5_", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0,0,0), + + {{"_S1D", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S2D", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S3D", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S4D", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S0W", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S1W", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S2W", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S3W", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_S4W", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SBS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SCP", METHOD_1ARGS (ACPI_TYPE_INTEGER) | ARG_COUNT_IS_MINIMUM, + METHOD_NO_RETURN_VALUE}}, /* Acpi 1.0 allowed 1 integer arg. Acpi 3.0 expanded to 3 args. Allow both. */ + + {{"_SDD", METHOD_1ARGS (ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_SEG", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SHL", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SLI", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_SPD", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SRS", METHOD_1ARGS (ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_SRT", METHOD_1ARGS (ACPI_TYPE_BUFFER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SRV", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* See IPMI spec */ + + {{"_SST", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_STA", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_STM", METHOD_3ARGS (ACPI_TYPE_BUFFER, ACPI_TYPE_BUFFER, ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_STP", METHOD_2ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_STR", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_STV", METHOD_2ARGS (ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SUB", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_STRING)}}, + + {{"_SUN", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_SWS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TC1", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TC2", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TDL", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TFP", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TIP", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TIV", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TMP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TPC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TPT", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_TRT", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 2 Ref/6 Int */ + PACKAGE_INFO (ACPI_PTYPE2, ACPI_RTYPE_REFERENCE, 2, ACPI_RTYPE_INTEGER, 6, 0), + + {{"_TSD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 5 Int with count */ + PACKAGE_INFO (ACPI_PTYPE2_COUNT,ACPI_RTYPE_INTEGER, 5,0,0,0), + + {{"_TSN", METHOD_0ARGS, /* ACPI 6.0 */ + METHOD_RETURNS (ACPI_RTYPE_REFERENCE)}}, + + {{"_TSP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TSS", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 5 Int */ + PACKAGE_INFO (ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 5,0,0,0), + + {{"_TST", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_TTS", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_TZD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO (ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0,0,0), + + {{"_TZM", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_REFERENCE)}}, + + {{"_TZP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_UID", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING)}}, + + {{"_UPC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4,0,0,0), + + {{"_UPD", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_UPP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + {{"_VPO", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, + + /* Acpi 1.0 defined _WAK with no return value. Later, it was changed to return a package */ + + {{"_WAK", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_NONE | ACPI_RTYPE_INTEGER | ACPI_RTYPE_PACKAGE)}}, + PACKAGE_INFO (ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 2,0,0,0), /* Fixed-length (2 Int), but is optional */ + + /* _WDG/_WED are MS extensions defined by "Windows Instrumentation" */ + + {{"_WDG", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_BUFFER)}}, + + {{"_WED", METHOD_1ARGS (ACPI_TYPE_INTEGER), + METHOD_RETURNS (ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_BUFFER)}}, + + {{"_WPC", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* ACPI 6.1 */ + + {{"_WPP", METHOD_0ARGS, + METHOD_RETURNS (ACPI_RTYPE_INTEGER)}}, /* ACPI 6.1 */ + + PACKAGE_INFO (0,0,0,0,0,0) /* Table terminator */ +}; +#else +extern const ACPI_PREDEFINED_INFO AcpiGbl_PredefinedMethods[]; +#endif + + +#if (defined ACPI_CREATE_RESOURCE_TABLE && defined ACPI_APPLICATION) +/****************************************************************************** + * + * Predefined names for use in Resource Descriptors. These names do not + * appear in the global Predefined Name table (since these names never + * appear in actual AML byte code, only in the original ASL) + * + * Note: Used by iASL compiler and AcpiHelp utility only. + * + *****************************************************************************/ + +const ACPI_PREDEFINED_INFO AcpiGbl_ResourceNames[] = +{ + {{"_ADR", WIDTH_16 | WIDTH_64, 0}}, + {{"_ALN", WIDTH_8 | WIDTH_16 | WIDTH_32, 0}}, + {{"_ASI", WIDTH_8, 0}}, + {{"_ASZ", WIDTH_8, 0}}, + {{"_ATT", WIDTH_64, 0}}, + {{"_BAS", WIDTH_16 | WIDTH_32, 0}}, + {{"_BM_", WIDTH_1, 0}}, + {{"_DBT", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_DEC", WIDTH_1, 0}}, + {{"_DMA", WIDTH_8, 0}}, + {{"_DPL", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_DRS", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_END", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_FLC", WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_GRA", WIDTH_ADDRESS, 0}}, + {{"_HE_", WIDTH_1, 0}}, + {{"_INT", WIDTH_16 | WIDTH_32, 0}}, + {{"_IOR", WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_LEN", WIDTH_8 | WIDTH_ADDRESS, 0}}, + {{"_LIN", WIDTH_8, 0}}, /* Acpi 5.0 */ + {{"_LL_", WIDTH_1, 0}}, + {{"_MAF", WIDTH_1, 0}}, + {{"_MAX", WIDTH_ADDRESS, 0}}, + {{"_MEM", WIDTH_2, 0}}, + {{"_MIF", WIDTH_1, 0}}, + {{"_MIN", WIDTH_ADDRESS, 0}}, + {{"_MOD", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_MTP", WIDTH_2, 0}}, + {{"_PAR", WIDTH_8, 0}}, /* Acpi 5.0 */ + {{"_PHA", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_PIN", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_PPI", WIDTH_8, 0}}, /* Acpi 5.0 */ + {{"_POL", WIDTH_1 | WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_RBO", WIDTH_8, 0}}, + {{"_RBW", WIDTH_8, 0}}, + {{"_RNG", WIDTH_1, 0}}, + {{"_RT_", WIDTH_8, 0}}, /* Acpi 3.0 */ + {{"_RW_", WIDTH_1, 0}}, + {{"_RXL", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_SHR", WIDTH_2, 0}}, + {{"_SIZ", WIDTH_2, 0}}, + {{"_SLV", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_SPE", WIDTH_32, 0}}, /* Acpi 5.0 */ + {{"_STB", WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_TRA", WIDTH_ADDRESS, 0}}, + {{"_TRS", WIDTH_1, 0}}, + {{"_TSF", WIDTH_8, 0}}, /* Acpi 3.0 */ + {{"_TTP", WIDTH_1, 0}}, + {{"_TXL", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_TYP", WIDTH_2 | WIDTH_16, 0}}, + {{"_VEN", VARIABLE_DATA, 0}}, /* Acpi 5.0 */ + PACKAGE_INFO (0,0,0,0,0,0) /* Table terminator */ +}; + +const ACPI_PREDEFINED_INFO AcpiGbl_ScopeNames[] = { + {{"_GPE", 0, 0}}, + {{"_PR_", 0, 0}}, + {{"_SB_", 0, 0}}, + {{"_SI_", 0, 0}}, + {{"_TZ_", 0, 0}}, + PACKAGE_INFO (0,0,0,0,0,0) /* Table terminator */ +}; +#else +extern const ACPI_PREDEFINED_INFO AcpiGbl_ResourceNames[]; +#endif + +#endif diff --git a/ports/acpica/include/acresrc.h b/ports/acpica/include/acresrc.h new file mode 100644 index 0000000..ebcc110 --- /dev/null +++ b/ports/acpica/include/acresrc.h @@ -0,0 +1,563 @@ +/****************************************************************************** + * + * Name: acresrc.h - Resource Manager function prototypes + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACRESRC_H__ +#define __ACRESRC_H__ + +/* Need the AML resource descriptor structs */ + +#include "amlresrc.h" + + +/* + * If possible, pack the following structures to byte alignment, since we + * don't care about performance for debug output. Two cases where we cannot + * pack the structures: + * + * 1) Hardware does not support misaligned memory transfers + * 2) Compiler does not support pointers within packed structures + */ +#if (!defined(ACPI_MISALIGNMENT_NOT_SUPPORTED) && !defined(ACPI_PACKED_POINTERS_NOT_SUPPORTED)) +#pragma pack(1) +#endif + +/* + * Individual entry for the resource conversion tables + */ +typedef const struct acpi_rsconvert_info +{ + UINT8 Opcode; + UINT8 ResourceOffset; + UINT8 AmlOffset; + UINT8 Value; + +} ACPI_RSCONVERT_INFO; + +/* Resource conversion opcodes */ + +typedef enum +{ + ACPI_RSC_INITGET = 0, + ACPI_RSC_INITSET, + ACPI_RSC_FLAGINIT, + ACPI_RSC_1BITFLAG, + ACPI_RSC_2BITFLAG, + ACPI_RSC_3BITFLAG, + ACPI_RSC_ADDRESS, + ACPI_RSC_BITMASK, + ACPI_RSC_BITMASK16, + ACPI_RSC_COUNT, + ACPI_RSC_COUNT16, + ACPI_RSC_COUNT_GPIO_PIN, + ACPI_RSC_COUNT_GPIO_RES, + ACPI_RSC_COUNT_GPIO_VEN, + ACPI_RSC_COUNT_SERIAL_RES, + ACPI_RSC_COUNT_SERIAL_VEN, + ACPI_RSC_DATA8, + ACPI_RSC_EXIT_EQ, + ACPI_RSC_EXIT_LE, + ACPI_RSC_EXIT_NE, + ACPI_RSC_LENGTH, + ACPI_RSC_MOVE_GPIO_PIN, + ACPI_RSC_MOVE_GPIO_RES, + ACPI_RSC_MOVE_SERIAL_RES, + ACPI_RSC_MOVE_SERIAL_VEN, + ACPI_RSC_MOVE8, + ACPI_RSC_MOVE16, + ACPI_RSC_MOVE32, + ACPI_RSC_MOVE64, + ACPI_RSC_SET8, + ACPI_RSC_SOURCE, + ACPI_RSC_SOURCEX + +} ACPI_RSCONVERT_OPCODES; + +/* Resource Conversion sub-opcodes */ + +#define ACPI_RSC_COMPARE_AML_LENGTH 0 +#define ACPI_RSC_COMPARE_VALUE 1 + +#define ACPI_RSC_TABLE_SIZE(d) (sizeof (d) / sizeof (ACPI_RSCONVERT_INFO)) + +#define ACPI_RS_OFFSET(f) (UINT8) ACPI_OFFSET (ACPI_RESOURCE,f) +#define AML_OFFSET(f) (UINT8) ACPI_OFFSET (AML_RESOURCE,f) + + +/* + * Individual entry for the resource dump tables + */ +typedef const struct acpi_rsdump_info +{ + UINT8 Opcode; + UINT8 Offset; + const char *Name; + const char **Pointer; + +} ACPI_RSDUMP_INFO; + +/* Values for the Opcode field above */ + +typedef enum +{ + ACPI_RSD_TITLE = 0, + ACPI_RSD_1BITFLAG, + ACPI_RSD_2BITFLAG, + ACPI_RSD_3BITFLAG, + ACPI_RSD_ADDRESS, + ACPI_RSD_DWORDLIST, + ACPI_RSD_LITERAL, + ACPI_RSD_LONGLIST, + ACPI_RSD_SHORTLIST, + ACPI_RSD_SHORTLISTX, + ACPI_RSD_SOURCE, + ACPI_RSD_STRING, + ACPI_RSD_UINT8, + ACPI_RSD_UINT16, + ACPI_RSD_UINT32, + ACPI_RSD_UINT64, + ACPI_RSD_WORDLIST, + ACPI_RSD_LABEL, + ACPI_RSD_SOURCE_LABEL, + +} ACPI_RSDUMP_OPCODES; + +/* restore default alignment */ + +#pragma pack() + + +/* Resource tables indexed by internal resource type */ + +extern const UINT8 AcpiGbl_AmlResourceSizes[]; +extern const UINT8 AcpiGbl_AmlResourceSerialBusSizes[]; +extern ACPI_RSCONVERT_INFO *AcpiGbl_SetResourceDispatch[]; + +/* Resource tables indexed by raw AML resource descriptor type */ + +extern const UINT8 AcpiGbl_ResourceStructSizes[]; +extern const UINT8 AcpiGbl_ResourceStructSerialBusSizes[]; +extern ACPI_RSCONVERT_INFO *AcpiGbl_GetResourceDispatch[]; + +extern ACPI_RSCONVERT_INFO *AcpiGbl_ConvertResourceSerialBusDispatch[]; + +typedef struct acpi_vendor_walk_info +{ + ACPI_VENDOR_UUID *Uuid; + ACPI_BUFFER *Buffer; + ACPI_STATUS Status; + +} ACPI_VENDOR_WALK_INFO; + + +/* + * rscreate + */ +ACPI_STATUS +AcpiRsCreateResourceList ( + ACPI_OPERAND_OBJECT *AmlBuffer, + ACPI_BUFFER *OutputBuffer); + +ACPI_STATUS +AcpiRsCreateAmlResources ( + ACPI_BUFFER *ResourceList, + ACPI_BUFFER *OutputBuffer); + +ACPI_STATUS +AcpiRsCreatePciRoutingTable ( + ACPI_OPERAND_OBJECT *PackageObject, + ACPI_BUFFER *OutputBuffer); + + +/* + * rsutils + */ +ACPI_STATUS +AcpiRsGetPrtMethodData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_BUFFER *RetBuffer); + +ACPI_STATUS +AcpiRsGetCrsMethodData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_BUFFER *RetBuffer); + +ACPI_STATUS +AcpiRsGetPrsMethodData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_BUFFER *RetBuffer); + +ACPI_STATUS +AcpiRsGetMethodData ( + ACPI_HANDLE Handle, + const char *Path, + ACPI_BUFFER *RetBuffer); + +ACPI_STATUS +AcpiRsSetSrsMethodData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_BUFFER *RetBuffer); + +ACPI_STATUS +AcpiRsGetAeiMethodData ( + ACPI_NAMESPACE_NODE *Node, + ACPI_BUFFER *RetBuffer); + +/* + * rscalc + */ +ACPI_STATUS +AcpiRsGetListLength ( + UINT8 *AmlBuffer, + UINT32 AmlBufferLength, + ACPI_SIZE *SizeNeeded); + +ACPI_STATUS +AcpiRsGetAmlLength ( + ACPI_RESOURCE *ResourceList, + ACPI_SIZE ResourceListSize, + ACPI_SIZE *SizeNeeded); + +ACPI_STATUS +AcpiRsGetPciRoutingTableLength ( + ACPI_OPERAND_OBJECT *PackageObject, + ACPI_SIZE *BufferSizeNeeded); + +ACPI_STATUS +AcpiRsConvertAmlToResources ( + UINT8 *Aml, + UINT32 Length, + UINT32 Offset, + UINT8 ResourceIndex, + void **Context); + +ACPI_STATUS +AcpiRsConvertResourcesToAml ( + ACPI_RESOURCE *Resource, + ACPI_SIZE AmlSizeNeeded, + UINT8 *OutputBuffer); + + +/* + * rsaddr + */ +void +AcpiRsSetAddressCommon ( + AML_RESOURCE *Aml, + ACPI_RESOURCE *Resource); + +BOOLEAN +AcpiRsGetAddressCommon ( + ACPI_RESOURCE *Resource, + AML_RESOURCE *Aml); + + +/* + * rsmisc + */ +ACPI_STATUS +AcpiRsConvertAmlToResource ( + ACPI_RESOURCE *Resource, + AML_RESOURCE *Aml, + ACPI_RSCONVERT_INFO *Info); + +ACPI_STATUS +AcpiRsConvertResourceToAml ( + ACPI_RESOURCE *Resource, + AML_RESOURCE *Aml, + ACPI_RSCONVERT_INFO *Info); + + +/* + * rsutils + */ +void +AcpiRsMoveData ( + void *Destination, + void *Source, + UINT16 ItemCount, + UINT8 MoveType); + +UINT8 +AcpiRsDecodeBitmask ( + UINT16 Mask, + UINT8 *List); + +UINT16 +AcpiRsEncodeBitmask ( + UINT8 *List, + UINT8 Count); + +ACPI_RS_LENGTH +AcpiRsGetResourceSource ( + ACPI_RS_LENGTH ResourceLength, + ACPI_RS_LENGTH MinimumLength, + ACPI_RESOURCE_SOURCE *ResourceSource, + AML_RESOURCE *Aml, + char *StringPtr); + +ACPI_RSDESC_SIZE +AcpiRsSetResourceSource ( + AML_RESOURCE *Aml, + ACPI_RS_LENGTH MinimumLength, + ACPI_RESOURCE_SOURCE *ResourceSource); + +void +AcpiRsSetResourceHeader ( + UINT8 DescriptorType, + ACPI_RSDESC_SIZE TotalLength, + AML_RESOURCE *Aml); + +void +AcpiRsSetResourceLength ( + ACPI_RSDESC_SIZE TotalLength, + AML_RESOURCE *Aml); + + +/* + * rsdump - Debugger support + */ +#ifdef ACPI_DEBUGGER +void +AcpiRsDumpResourceList ( + ACPI_RESOURCE *Resource); + +void +AcpiRsDumpIrqList ( + UINT8 *RouteTable); +#endif + + +/* + * Resource conversion tables + */ +extern ACPI_RSCONVERT_INFO AcpiRsConvertDma[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertEndDpf[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertIo[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertFixedIo[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertEndTag[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertMemory24[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertGenericReg[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertMemory32[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertFixedMemory32[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertAddress32[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertAddress16[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertExtIrq[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertAddress64[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertExtAddress64[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertGpio[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertFixedDma[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertI2cSerialBus[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertSpiSerialBus[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertUartSerialBus[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertPinFunction[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertPinConfig[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertPinGroup[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertPinGroupFunction[]; +extern ACPI_RSCONVERT_INFO AcpiRsConvertPinGroupConfig[]; + +/* These resources require separate get/set tables */ + +extern ACPI_RSCONVERT_INFO AcpiRsGetIrq[]; +extern ACPI_RSCONVERT_INFO AcpiRsGetStartDpf[]; +extern ACPI_RSCONVERT_INFO AcpiRsGetVendorSmall[]; +extern ACPI_RSCONVERT_INFO AcpiRsGetVendorLarge[]; + +extern ACPI_RSCONVERT_INFO AcpiRsSetIrq[]; +extern ACPI_RSCONVERT_INFO AcpiRsSetStartDpf[]; +extern ACPI_RSCONVERT_INFO AcpiRsSetVendor[]; + + +#if defined(ACPI_DEBUG_OUTPUT) || defined(ACPI_DEBUGGER) +/* + * rsinfo + */ +extern ACPI_RSDUMP_INFO *AcpiGbl_DumpResourceDispatch[]; +extern ACPI_RSDUMP_INFO *AcpiGbl_DumpSerialBusDispatch[]; + +/* + * rsdumpinfo + */ +extern ACPI_RSDUMP_INFO AcpiRsDumpIrq[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpPrt[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpDma[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpStartDpf[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpEndDpf[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpIo[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpIoFlags[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpFixedIo[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpVendor[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpEndTag[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpMemory24[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpMemory32[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpMemoryFlags[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpFixedMemory32[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpAddress16[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpAddress32[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpAddress64[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpExtAddress64[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpExtIrq[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpGenericReg[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpGpio[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpPinFunction[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpFixedDma[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpCommonSerialBus[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpI2cSerialBus[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpSpiSerialBus[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpUartSerialBus[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpGeneralFlags[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpPinConfig[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpPinGroup[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpPinGroupFunction[]; +extern ACPI_RSDUMP_INFO AcpiRsDumpPinGroupConfig[]; +#endif + +#endif /* __ACRESRC_H__ */ diff --git a/ports/acpica/include/acrestyp.h b/ports/acpica/include/acrestyp.h new file mode 100644 index 0000000..2d485f7 --- /dev/null +++ b/ports/acpica/include/acrestyp.h @@ -0,0 +1,924 @@ +/****************************************************************************** + * + * Name: acrestyp.h - Defines, types, and structures for resource descriptors + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACRESTYP_H__ +#define __ACRESTYP_H__ + + +/* + * Definitions for Resource Attributes + */ +typedef UINT16 ACPI_RS_LENGTH; /* Resource Length field is fixed at 16 bits */ +typedef UINT32 ACPI_RSDESC_SIZE; /* Max Resource Descriptor size is (Length+3) = (64K-1)+3 */ + +/* + * Memory Attributes + */ +#define ACPI_READ_ONLY_MEMORY (UINT8) 0x00 +#define ACPI_READ_WRITE_MEMORY (UINT8) 0x01 + +#define ACPI_NON_CACHEABLE_MEMORY (UINT8) 0x00 +#define ACPI_CACHABLE_MEMORY (UINT8) 0x01 +#define ACPI_WRITE_COMBINING_MEMORY (UINT8) 0x02 +#define ACPI_PREFETCHABLE_MEMORY (UINT8) 0x03 + +/*! [Begin] no source code translation */ +/* + * IO Attributes + * The ISA IO ranges are: n000-n0FFh, n400-n4FFh, n800-n8FFh, nC00-nCFFh. + * The non-ISA IO ranges are: n100-n3FFh, n500-n7FFh, n900-nBFFh, nCD0-nFFFh. + */ +/*! [End] no source code translation !*/ + +#define ACPI_NON_ISA_ONLY_RANGES (UINT8) 0x01 +#define ACPI_ISA_ONLY_RANGES (UINT8) 0x02 +#define ACPI_ENTIRE_RANGE (ACPI_NON_ISA_ONLY_RANGES | ACPI_ISA_ONLY_RANGES) + +/* Type of translation - 1=Sparse, 0=Dense */ + +#define ACPI_SPARSE_TRANSLATION (UINT8) 0x01 + +/* + * IO Port Descriptor Decode + */ +#define ACPI_DECODE_10 (UINT8) 0x00 /* 10-bit IO address decode */ +#define ACPI_DECODE_16 (UINT8) 0x01 /* 16-bit IO address decode */ + +/* + * Interrupt attributes - used in multiple descriptors + */ + +/* Triggering */ + +#define ACPI_LEVEL_SENSITIVE (UINT8) 0x00 +#define ACPI_EDGE_SENSITIVE (UINT8) 0x01 + +/* Polarity */ + +#define ACPI_ACTIVE_HIGH (UINT8) 0x00 +#define ACPI_ACTIVE_LOW (UINT8) 0x01 +#define ACPI_ACTIVE_BOTH (UINT8) 0x02 + +/* Sharing */ + +#define ACPI_EXCLUSIVE (UINT8) 0x00 +#define ACPI_SHARED (UINT8) 0x01 + +/* Wake */ + +#define ACPI_NOT_WAKE_CAPABLE (UINT8) 0x00 +#define ACPI_WAKE_CAPABLE (UINT8) 0x01 + +/* + * DMA Attributes + */ +#define ACPI_COMPATIBILITY (UINT8) 0x00 +#define ACPI_TYPE_A (UINT8) 0x01 +#define ACPI_TYPE_B (UINT8) 0x02 +#define ACPI_TYPE_F (UINT8) 0x03 + +#define ACPI_NOT_BUS_MASTER (UINT8) 0x00 +#define ACPI_BUS_MASTER (UINT8) 0x01 + +#define ACPI_TRANSFER_8 (UINT8) 0x00 +#define ACPI_TRANSFER_8_16 (UINT8) 0x01 +#define ACPI_TRANSFER_16 (UINT8) 0x02 + +/* + * Start Dependent Functions Priority definitions + */ +#define ACPI_GOOD_CONFIGURATION (UINT8) 0x00 +#define ACPI_ACCEPTABLE_CONFIGURATION (UINT8) 0x01 +#define ACPI_SUB_OPTIMAL_CONFIGURATION (UINT8) 0x02 + +/* + * 16, 32 and 64-bit Address Descriptor resource types + */ +#define ACPI_MEMORY_RANGE (UINT8) 0x00 +#define ACPI_IO_RANGE (UINT8) 0x01 +#define ACPI_BUS_NUMBER_RANGE (UINT8) 0x02 + +#define ACPI_ADDRESS_NOT_FIXED (UINT8) 0x00 +#define ACPI_ADDRESS_FIXED (UINT8) 0x01 + +#define ACPI_POS_DECODE (UINT8) 0x00 +#define ACPI_SUB_DECODE (UINT8) 0x01 + +/* Producer/Consumer */ + +#define ACPI_PRODUCER (UINT8) 0x00 +#define ACPI_CONSUMER (UINT8) 0x01 + + +/* + * If possible, pack the following structures to byte alignment + */ +#ifndef ACPI_MISALIGNMENT_NOT_SUPPORTED +#pragma pack(1) +#endif + +/* UUID data structures for use in vendor-defined resource descriptors */ + +typedef struct acpi_uuid +{ + UINT8 Data[ACPI_UUID_LENGTH]; +} ACPI_UUID; + +typedef struct acpi_vendor_uuid +{ + UINT8 Subtype; + UINT8 Data[ACPI_UUID_LENGTH]; + +} ACPI_VENDOR_UUID; + +/* + * Structures used to describe device resources + */ +typedef struct acpi_resource_irq +{ + UINT8 DescriptorLength; + UINT8 Triggering; + UINT8 Polarity; + UINT8 Sharable; + UINT8 WakeCapable; + UINT8 InterruptCount; + UINT8 Interrupts[1]; + +} ACPI_RESOURCE_IRQ; + +typedef struct acpi_resource_dma +{ + UINT8 Type; + UINT8 BusMaster; + UINT8 Transfer; + UINT8 ChannelCount; + UINT8 Channels[1]; + +} ACPI_RESOURCE_DMA; + +typedef struct acpi_resource_start_dependent +{ + UINT8 DescriptorLength; + UINT8 CompatibilityPriority; + UINT8 PerformanceRobustness; + +} ACPI_RESOURCE_START_DEPENDENT; + + +/* + * The END_DEPENDENT_FUNCTIONS_RESOURCE struct is not + * needed because it has no fields + */ + + +typedef struct acpi_resource_io +{ + UINT8 IoDecode; + UINT8 Alignment; + UINT8 AddressLength; + UINT16 Minimum; + UINT16 Maximum; + +} ACPI_RESOURCE_IO; + +typedef struct acpi_resource_fixed_io +{ + UINT16 Address; + UINT8 AddressLength; + +} ACPI_RESOURCE_FIXED_IO; + +typedef struct acpi_resource_fixed_dma +{ + UINT16 RequestLines; + UINT16 Channels; + UINT8 Width; + +} ACPI_RESOURCE_FIXED_DMA; + +/* Values for Width field above */ + +#define ACPI_DMA_WIDTH8 0 +#define ACPI_DMA_WIDTH16 1 +#define ACPI_DMA_WIDTH32 2 +#define ACPI_DMA_WIDTH64 3 +#define ACPI_DMA_WIDTH128 4 +#define ACPI_DMA_WIDTH256 5 + + +typedef struct acpi_resource_vendor +{ + UINT16 ByteLength; + UINT8 ByteData[1]; + +} ACPI_RESOURCE_VENDOR; + +/* Vendor resource with UUID info (introduced in ACPI 3.0) */ + +typedef struct acpi_resource_vendor_typed +{ + UINT16 ByteLength; + UINT8 UuidSubtype; + UINT8 Uuid[ACPI_UUID_LENGTH]; + UINT8 ByteData[1]; + +} ACPI_RESOURCE_VENDOR_TYPED; + +typedef struct acpi_resource_end_tag +{ + UINT8 Checksum; + +} ACPI_RESOURCE_END_TAG; + +typedef struct acpi_resource_memory24 +{ + UINT8 WriteProtect; + UINT16 Minimum; + UINT16 Maximum; + UINT16 Alignment; + UINT16 AddressLength; + +} ACPI_RESOURCE_MEMORY24; + +typedef struct acpi_resource_memory32 +{ + UINT8 WriteProtect; + UINT32 Minimum; + UINT32 Maximum; + UINT32 Alignment; + UINT32 AddressLength; + +} ACPI_RESOURCE_MEMORY32; + +typedef struct acpi_resource_fixed_memory32 +{ + UINT8 WriteProtect; + UINT32 Address; + UINT32 AddressLength; + +} ACPI_RESOURCE_FIXED_MEMORY32; + +typedef struct acpi_memory_attribute +{ + UINT8 WriteProtect; + UINT8 Caching; + UINT8 RangeType; + UINT8 Translation; + +} ACPI_MEMORY_ATTRIBUTE; + +typedef struct acpi_io_attribute +{ + UINT8 RangeType; + UINT8 Translation; + UINT8 TranslationType; + UINT8 Reserved1; + +} ACPI_IO_ATTRIBUTE; + +typedef union acpi_resource_attribute +{ + ACPI_MEMORY_ATTRIBUTE Mem; + ACPI_IO_ATTRIBUTE Io; + + /* Used for the *WordSpace macros */ + + UINT8 TypeSpecific; + +} ACPI_RESOURCE_ATTRIBUTE; + +typedef struct acpi_resource_label +{ + UINT16 StringLength; + char *StringPtr; + +} ACPI_RESOURCE_LABEL; + +typedef struct acpi_resource_source +{ + UINT8 Index; + UINT16 StringLength; + char *StringPtr; + +} ACPI_RESOURCE_SOURCE; + +/* Fields common to all address descriptors, 16/32/64 bit */ + +#define ACPI_RESOURCE_ADDRESS_COMMON \ + UINT8 ResourceType; \ + UINT8 ProducerConsumer; \ + UINT8 Decode; \ + UINT8 MinAddressFixed; \ + UINT8 MaxAddressFixed; \ + ACPI_RESOURCE_ATTRIBUTE Info; + +typedef struct acpi_address16_attribute +{ + UINT16 Granularity; + UINT16 Minimum; + UINT16 Maximum; + UINT16 TranslationOffset; + UINT16 AddressLength; + +} ACPI_ADDRESS16_ATTRIBUTE; + +typedef struct acpi_address32_attribute +{ + UINT32 Granularity; + UINT32 Minimum; + UINT32 Maximum; + UINT32 TranslationOffset; + UINT32 AddressLength; + +} ACPI_ADDRESS32_ATTRIBUTE; + +typedef struct acpi_address64_attribute +{ + UINT64 Granularity; + UINT64 Minimum; + UINT64 Maximum; + UINT64 TranslationOffset; + UINT64 AddressLength; + +} ACPI_ADDRESS64_ATTRIBUTE; + +typedef struct acpi_resource_address +{ + ACPI_RESOURCE_ADDRESS_COMMON + +} ACPI_RESOURCE_ADDRESS; + +typedef struct acpi_resource_address16 +{ + ACPI_RESOURCE_ADDRESS_COMMON + ACPI_ADDRESS16_ATTRIBUTE Address; + ACPI_RESOURCE_SOURCE ResourceSource; + +} ACPI_RESOURCE_ADDRESS16; + +typedef struct acpi_resource_address32 +{ + ACPI_RESOURCE_ADDRESS_COMMON + ACPI_ADDRESS32_ATTRIBUTE Address; + ACPI_RESOURCE_SOURCE ResourceSource; + +} ACPI_RESOURCE_ADDRESS32; + +typedef struct acpi_resource_address64 +{ + ACPI_RESOURCE_ADDRESS_COMMON + ACPI_ADDRESS64_ATTRIBUTE Address; + ACPI_RESOURCE_SOURCE ResourceSource; + +} ACPI_RESOURCE_ADDRESS64; + +typedef struct acpi_resource_extended_address64 +{ + ACPI_RESOURCE_ADDRESS_COMMON + UINT8 RevisionID; + ACPI_ADDRESS64_ATTRIBUTE Address; + UINT64 TypeSpecific; + +} ACPI_RESOURCE_EXTENDED_ADDRESS64; + +typedef struct acpi_resource_extended_irq +{ + UINT8 ProducerConsumer; + UINT8 Triggering; + UINT8 Polarity; + UINT8 Sharable; + UINT8 WakeCapable; + UINT8 InterruptCount; + ACPI_RESOURCE_SOURCE ResourceSource; + UINT32 Interrupts[1]; + +} ACPI_RESOURCE_EXTENDED_IRQ; + +typedef struct acpi_resource_generic_register +{ + UINT8 SpaceId; + UINT8 BitWidth; + UINT8 BitOffset; + UINT8 AccessSize; + UINT64 Address; + +} ACPI_RESOURCE_GENERIC_REGISTER; + +typedef struct acpi_resource_gpio +{ + UINT8 RevisionId; + UINT8 ConnectionType; + UINT8 ProducerConsumer; /* For values, see Producer/Consumer above */ + UINT8 PinConfig; + UINT8 Sharable; /* For values, see Interrupt Attributes above */ + UINT8 WakeCapable; /* For values, see Interrupt Attributes above */ + UINT8 IoRestriction; + UINT8 Triggering; /* For values, see Interrupt Attributes above */ + UINT8 Polarity; /* For values, see Interrupt Attributes above */ + UINT16 DriveStrength; + UINT16 DebounceTimeout; + UINT16 PinTableLength; + UINT16 VendorLength; + ACPI_RESOURCE_SOURCE ResourceSource; + UINT16 *PinTable; + UINT8 *VendorData; + +} ACPI_RESOURCE_GPIO; + +/* Values for GPIO ConnectionType field above */ + +#define ACPI_RESOURCE_GPIO_TYPE_INT 0 +#define ACPI_RESOURCE_GPIO_TYPE_IO 1 + +/* Values for PinConfig field above */ + +#define ACPI_PIN_CONFIG_DEFAULT 0 +#define ACPI_PIN_CONFIG_PULLUP 1 +#define ACPI_PIN_CONFIG_PULLDOWN 2 +#define ACPI_PIN_CONFIG_NOPULL 3 + +/* Values for IoRestriction field above */ + +#define ACPI_IO_RESTRICT_NONE 0 +#define ACPI_IO_RESTRICT_INPUT 1 +#define ACPI_IO_RESTRICT_OUTPUT 2 +#define ACPI_IO_RESTRICT_NONE_PRESERVE 3 + + +/* Common structure for I2C, SPI, and UART serial descriptors */ + +#define ACPI_RESOURCE_SERIAL_COMMON \ + UINT8 RevisionId; \ + UINT8 Type; \ + UINT8 ProducerConsumer; /* For values, see Producer/Consumer above */\ + UINT8 SlaveMode; \ + UINT8 ConnectionSharing; \ + UINT8 TypeRevisionId; \ + UINT16 TypeDataLength; \ + UINT16 VendorLength; \ + ACPI_RESOURCE_SOURCE ResourceSource; \ + UINT8 *VendorData; + +typedef struct acpi_resource_common_serialbus +{ + ACPI_RESOURCE_SERIAL_COMMON + +} ACPI_RESOURCE_COMMON_SERIALBUS; + +/* Values for the Type field above */ + +#define ACPI_RESOURCE_SERIAL_TYPE_I2C 1 +#define ACPI_RESOURCE_SERIAL_TYPE_SPI 2 +#define ACPI_RESOURCE_SERIAL_TYPE_UART 3 + +/* Values for SlaveMode field above */ + +#define ACPI_CONTROLLER_INITIATED 0 +#define ACPI_DEVICE_INITIATED 1 + + +typedef struct acpi_resource_i2c_serialbus +{ + ACPI_RESOURCE_SERIAL_COMMON + UINT8 AccessMode; + UINT16 SlaveAddress; + UINT32 ConnectionSpeed; + +} ACPI_RESOURCE_I2C_SERIALBUS; + +/* Values for AccessMode field above */ + +#define ACPI_I2C_7BIT_MODE 0 +#define ACPI_I2C_10BIT_MODE 1 + + +typedef struct acpi_resource_spi_serialbus +{ + ACPI_RESOURCE_SERIAL_COMMON + UINT8 WireMode; + UINT8 DevicePolarity; + UINT8 DataBitLength; + UINT8 ClockPhase; + UINT8 ClockPolarity; + UINT16 DeviceSelection; + UINT32 ConnectionSpeed; + +} ACPI_RESOURCE_SPI_SERIALBUS; + +/* Values for WireMode field above */ + +#define ACPI_SPI_4WIRE_MODE 0 +#define ACPI_SPI_3WIRE_MODE 1 + +/* Values for DevicePolarity field above */ + +#define ACPI_SPI_ACTIVE_LOW 0 +#define ACPI_SPI_ACTIVE_HIGH 1 + +/* Values for ClockPhase field above */ + +#define ACPI_SPI_FIRST_PHASE 0 +#define ACPI_SPI_SECOND_PHASE 1 + +/* Values for ClockPolarity field above */ + +#define ACPI_SPI_START_LOW 0 +#define ACPI_SPI_START_HIGH 1 + + +typedef struct acpi_resource_uart_serialbus +{ + ACPI_RESOURCE_SERIAL_COMMON + UINT8 Endian; + UINT8 DataBits; + UINT8 StopBits; + UINT8 FlowControl; + UINT8 Parity; + UINT8 LinesEnabled; + UINT16 RxFifoSize; + UINT16 TxFifoSize; + UINT32 DefaultBaudRate; + +} ACPI_RESOURCE_UART_SERIALBUS; + +/* Values for Endian field above */ + +#define ACPI_UART_LITTLE_ENDIAN 0 +#define ACPI_UART_BIG_ENDIAN 1 + +/* Values for DataBits field above */ + +#define ACPI_UART_5_DATA_BITS 0 +#define ACPI_UART_6_DATA_BITS 1 +#define ACPI_UART_7_DATA_BITS 2 +#define ACPI_UART_8_DATA_BITS 3 +#define ACPI_UART_9_DATA_BITS 4 + +/* Values for StopBits field above */ + +#define ACPI_UART_NO_STOP_BITS 0 +#define ACPI_UART_1_STOP_BIT 1 +#define ACPI_UART_1P5_STOP_BITS 2 +#define ACPI_UART_2_STOP_BITS 3 + +/* Values for FlowControl field above */ + +#define ACPI_UART_FLOW_CONTROL_NONE 0 +#define ACPI_UART_FLOW_CONTROL_HW 1 +#define ACPI_UART_FLOW_CONTROL_XON_XOFF 2 + +/* Values for Parity field above */ + +#define ACPI_UART_PARITY_NONE 0 +#define ACPI_UART_PARITY_EVEN 1 +#define ACPI_UART_PARITY_ODD 2 +#define ACPI_UART_PARITY_MARK 3 +#define ACPI_UART_PARITY_SPACE 4 + +/* Values for LinesEnabled bitfield above */ + +#define ACPI_UART_CARRIER_DETECT (1<<2) +#define ACPI_UART_RING_INDICATOR (1<<3) +#define ACPI_UART_DATA_SET_READY (1<<4) +#define ACPI_UART_DATA_TERMINAL_READY (1<<5) +#define ACPI_UART_CLEAR_TO_SEND (1<<6) +#define ACPI_UART_REQUEST_TO_SEND (1<<7) + +typedef struct acpi_resource_pin_function +{ + UINT8 RevisionId; + UINT8 PinConfig; + UINT8 Sharable; /* For values, see Interrupt Attributes above */ + UINT16 FunctionNumber; + UINT16 PinTableLength; + UINT16 VendorLength; + ACPI_RESOURCE_SOURCE ResourceSource; + UINT16 *PinTable; + UINT8 *VendorData; + +} ACPI_RESOURCE_PIN_FUNCTION; + +typedef struct acpi_resource_pin_config +{ + UINT8 RevisionId; + UINT8 ProducerConsumer; /* For values, see Producer/Consumer above */ + UINT8 Sharable; /* For values, see Interrupt Attributes above */ + UINT8 PinConfigType; + UINT32 PinConfigValue; + UINT16 PinTableLength; + UINT16 VendorLength; + ACPI_RESOURCE_SOURCE ResourceSource; + UINT16 *PinTable; + UINT8 *VendorData; + +} ACPI_RESOURCE_PIN_CONFIG; + +/* Values for PinConfigType field above */ + +#define ACPI_PIN_CONFIG_DEFAULT 0 +#define ACPI_PIN_CONFIG_BIAS_PULL_UP 1 +#define ACPI_PIN_CONFIG_BIAS_PULL_DOWN 2 +#define ACPI_PIN_CONFIG_BIAS_DEFAULT 3 +#define ACPI_PIN_CONFIG_BIAS_DISABLE 4 +#define ACPI_PIN_CONFIG_BIAS_HIGH_IMPEDANCE 5 +#define ACPI_PIN_CONFIG_BIAS_BUS_HOLD 6 +#define ACPI_PIN_CONFIG_DRIVE_OPEN_DRAIN 7 +#define ACPI_PIN_CONFIG_DRIVE_OPEN_SOURCE 8 +#define ACPI_PIN_CONFIG_DRIVE_PUSH_PULL 9 +#define ACPI_PIN_CONFIG_DRIVE_STRENGTH 10 +#define ACPI_PIN_CONFIG_SLEW_RATE 11 +#define ACPI_PIN_CONFIG_INPUT_DEBOUNCE 12 +#define ACPI_PIN_CONFIG_INPUT_SCHMITT_TRIGGER 13 + +typedef struct acpi_resource_pin_group +{ + UINT8 RevisionId; + UINT8 ProducerConsumer; /* For values, see Producer/Consumer above */ + UINT16 PinTableLength; + UINT16 VendorLength; + UINT16 *PinTable; + ACPI_RESOURCE_LABEL ResourceLabel; + UINT8 *VendorData; + +} ACPI_RESOURCE_PIN_GROUP; + +typedef struct acpi_resource_pin_group_function +{ + UINT8 RevisionId; + UINT8 ProducerConsumer; /* For values, see Producer/Consumer above */ + UINT8 Sharable; /* For values, see Interrupt Attributes above */ + UINT16 FunctionNumber; + UINT16 VendorLength; + ACPI_RESOURCE_SOURCE ResourceSource; + ACPI_RESOURCE_LABEL ResourceSourceLabel; + UINT8 *VendorData; + +} ACPI_RESOURCE_PIN_GROUP_FUNCTION; + +typedef struct acpi_resource_pin_group_config +{ + UINT8 RevisionId; + UINT8 ProducerConsumer; /* For values, see Producer/Consumer above */ + UINT8 Sharable; /* For values, see Interrupt Attributes above */ + UINT8 PinConfigType; /* For values, see PinConfigType above */ + UINT32 PinConfigValue; + UINT16 VendorLength; + ACPI_RESOURCE_SOURCE ResourceSource; + ACPI_RESOURCE_LABEL ResourceSourceLabel; + UINT8 *VendorData; + +} ACPI_RESOURCE_PIN_GROUP_CONFIG; + +/* ACPI_RESOURCE_TYPEs */ + +#define ACPI_RESOURCE_TYPE_IRQ 0 +#define ACPI_RESOURCE_TYPE_DMA 1 +#define ACPI_RESOURCE_TYPE_START_DEPENDENT 2 +#define ACPI_RESOURCE_TYPE_END_DEPENDENT 3 +#define ACPI_RESOURCE_TYPE_IO 4 +#define ACPI_RESOURCE_TYPE_FIXED_IO 5 +#define ACPI_RESOURCE_TYPE_VENDOR 6 +#define ACPI_RESOURCE_TYPE_END_TAG 7 +#define ACPI_RESOURCE_TYPE_MEMORY24 8 +#define ACPI_RESOURCE_TYPE_MEMORY32 9 +#define ACPI_RESOURCE_TYPE_FIXED_MEMORY32 10 +#define ACPI_RESOURCE_TYPE_ADDRESS16 11 +#define ACPI_RESOURCE_TYPE_ADDRESS32 12 +#define ACPI_RESOURCE_TYPE_ADDRESS64 13 +#define ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64 14 /* ACPI 3.0 */ +#define ACPI_RESOURCE_TYPE_EXTENDED_IRQ 15 +#define ACPI_RESOURCE_TYPE_GENERIC_REGISTER 16 +#define ACPI_RESOURCE_TYPE_GPIO 17 /* ACPI 5.0 */ +#define ACPI_RESOURCE_TYPE_FIXED_DMA 18 /* ACPI 5.0 */ +#define ACPI_RESOURCE_TYPE_SERIAL_BUS 19 /* ACPI 5.0 */ +#define ACPI_RESOURCE_TYPE_PIN_FUNCTION 20 /* ACPI 6.2 */ +#define ACPI_RESOURCE_TYPE_PIN_CONFIG 21 /* ACPI 6.2 */ +#define ACPI_RESOURCE_TYPE_PIN_GROUP 22 /* ACPI 6.2 */ +#define ACPI_RESOURCE_TYPE_PIN_GROUP_FUNCTION 23 /* ACPI 6.2 */ +#define ACPI_RESOURCE_TYPE_PIN_GROUP_CONFIG 24 /* ACPI 6.2 */ +#define ACPI_RESOURCE_TYPE_MAX 24 + +/* Master union for resource descriptors */ + +typedef union acpi_resource_data +{ + ACPI_RESOURCE_IRQ Irq; + ACPI_RESOURCE_DMA Dma; + ACPI_RESOURCE_START_DEPENDENT StartDpf; + ACPI_RESOURCE_IO Io; + ACPI_RESOURCE_FIXED_IO FixedIo; + ACPI_RESOURCE_FIXED_DMA FixedDma; + ACPI_RESOURCE_VENDOR Vendor; + ACPI_RESOURCE_VENDOR_TYPED VendorTyped; + ACPI_RESOURCE_END_TAG EndTag; + ACPI_RESOURCE_MEMORY24 Memory24; + ACPI_RESOURCE_MEMORY32 Memory32; + ACPI_RESOURCE_FIXED_MEMORY32 FixedMemory32; + ACPI_RESOURCE_ADDRESS16 Address16; + ACPI_RESOURCE_ADDRESS32 Address32; + ACPI_RESOURCE_ADDRESS64 Address64; + ACPI_RESOURCE_EXTENDED_ADDRESS64 ExtAddress64; + ACPI_RESOURCE_EXTENDED_IRQ ExtendedIrq; + ACPI_RESOURCE_GENERIC_REGISTER GenericReg; + ACPI_RESOURCE_GPIO Gpio; + ACPI_RESOURCE_I2C_SERIALBUS I2cSerialBus; + ACPI_RESOURCE_SPI_SERIALBUS SpiSerialBus; + ACPI_RESOURCE_UART_SERIALBUS UartSerialBus; + ACPI_RESOURCE_COMMON_SERIALBUS CommonSerialBus; + ACPI_RESOURCE_PIN_FUNCTION PinFunction; + ACPI_RESOURCE_PIN_CONFIG PinConfig; + ACPI_RESOURCE_PIN_GROUP PinGroup; + ACPI_RESOURCE_PIN_GROUP_FUNCTION PinGroupFunction; + ACPI_RESOURCE_PIN_GROUP_CONFIG PinGroupConfig; + + /* Common fields */ + + ACPI_RESOURCE_ADDRESS Address; /* Common 16/32/64 address fields */ + +} ACPI_RESOURCE_DATA; + + +/* Common resource header */ + +typedef struct acpi_resource +{ + UINT32 Type; + UINT32 Length; + ACPI_RESOURCE_DATA Data; + +} ACPI_RESOURCE; + +/* restore default alignment */ + +#pragma pack() + + +#define ACPI_RS_SIZE_NO_DATA 8 /* Id + Length fields */ +#define ACPI_RS_SIZE_MIN (UINT32) ACPI_ROUND_UP_TO_NATIVE_WORD (12) +#define ACPI_RS_SIZE(Type) (UINT32) (ACPI_RS_SIZE_NO_DATA + sizeof (Type)) + +/* Macro for walking resource templates with multiple descriptors */ + +#define ACPI_NEXT_RESOURCE(Res) \ + ACPI_ADD_PTR (ACPI_RESOURCE, (Res), (Res)->Length) + + +typedef struct acpi_pci_routing_table +{ + UINT32 Length; + UINT32 Pin; + UINT64 Address; /* here for 64-bit alignment */ + UINT32 SourceIndex; + char Source[4]; /* pad to 64 bits so sizeof() works in all cases */ + +} ACPI_PCI_ROUTING_TABLE; + +#endif /* __ACRESTYP_H__ */ diff --git a/ports/acpica/include/acstruct.h b/ports/acpica/include/acstruct.h new file mode 100644 index 0000000..9c55d35 --- /dev/null +++ b/ports/acpica/include/acstruct.h @@ -0,0 +1,377 @@ +/****************************************************************************** + * + * Name: acstruct.h - Internal structs + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACSTRUCT_H__ +#define __ACSTRUCT_H__ + +/* acpisrc:StructDefs -- for acpisrc conversion */ + +/***************************************************************************** + * + * Tree walking typedefs and structs + * + ****************************************************************************/ + + +/* + * Walk state - current state of a parse tree walk. Used for both a leisurely + * stroll through the tree (for whatever reason), and for control method + * execution. + */ +#define ACPI_NEXT_OP_DOWNWARD 1 +#define ACPI_NEXT_OP_UPWARD 2 + +/* + * Groups of definitions for WalkType used for different implementations of + * walkers (never simultaneously) - flags for interpreter: + */ +#define ACPI_WALK_NON_METHOD 0 +#define ACPI_WALK_METHOD 0x01 +#define ACPI_WALK_METHOD_RESTART 0x02 + + +typedef struct acpi_walk_state +{ + struct acpi_walk_state *Next; /* Next WalkState in list */ + UINT8 DescriptorType; /* To differentiate various internal objs */ + UINT8 WalkType; + UINT16 Opcode; /* Current AML opcode */ + UINT8 NextOpInfo; /* Info about NextOp */ + UINT8 NumOperands; /* Stack pointer for Operands[] array */ + UINT8 OperandIndex; /* Index into operand stack, to be used by AcpiDsObjStackPush */ + ACPI_OWNER_ID OwnerId; /* Owner of objects created during the walk */ + BOOLEAN LastPredicate; /* Result of last predicate */ + UINT8 CurrentResult; + UINT8 ReturnUsed; + UINT8 ScopeDepth; + UINT8 PassNumber; /* Parse pass during table load */ + BOOLEAN NamespaceOverride; /* Override existing objects */ + UINT8 ResultSize; /* Total elements for the result stack */ + UINT8 ResultCount; /* Current number of occupied elements of result stack */ + UINT8 *Aml; + UINT32 ArgTypes; + UINT32 MethodBreakpoint; /* For single stepping */ + UINT32 UserBreakpoint; /* User AML breakpoint */ + UINT32 ParseFlags; + + ACPI_PARSE_STATE ParserState; /* Current state of parser */ + UINT32 PrevArgTypes; + UINT32 ArgCount; /* push for fixed or var args */ + + struct acpi_namespace_node Arguments[ACPI_METHOD_NUM_ARGS]; /* Control method arguments */ + struct acpi_namespace_node LocalVariables[ACPI_METHOD_NUM_LOCALS]; /* Control method locals */ + union acpi_operand_object *Operands[ACPI_OBJ_NUM_OPERANDS + 1]; /* Operands passed to the interpreter (+1 for NULL terminator) */ + union acpi_operand_object **Params; + + UINT8 *AmlLastWhile; + union acpi_operand_object **CallerReturnDesc; + ACPI_GENERIC_STATE *ControlState; /* List of control states (nested IFs) */ + struct acpi_namespace_node *DeferredNode; /* Used when executing deferred opcodes */ + union acpi_operand_object *ImplicitReturnObj; + struct acpi_namespace_node *MethodCallNode; /* Called method Node*/ + ACPI_PARSE_OBJECT *MethodCallOp; /* MethodCall Op if running a method */ + union acpi_operand_object *MethodDesc; /* Method descriptor if running a method */ + struct acpi_namespace_node *MethodNode; /* Method node if running a method. */ + ACPI_PARSE_OBJECT *Op; /* Current parser op */ + const ACPI_OPCODE_INFO *OpInfo; /* Info on current opcode */ + ACPI_PARSE_OBJECT *Origin; /* Start of walk [Obsolete] */ + union acpi_operand_object *ResultObj; + ACPI_GENERIC_STATE *Results; /* Stack of accumulated results */ + union acpi_operand_object *ReturnDesc; /* Return object, if any */ + ACPI_GENERIC_STATE *ScopeInfo; /* Stack of nested scopes */ + ACPI_PARSE_OBJECT *PrevOp; /* Last op that was processed */ + ACPI_PARSE_OBJECT *NextOp; /* next op to be processed */ + ACPI_THREAD_STATE *Thread; + ACPI_PARSE_DOWNWARDS DescendingCallback; + ACPI_PARSE_UPWARDS AscendingCallback; + +} ACPI_WALK_STATE; + + +/* Info used by AcpiNsInitializeObjects and AcpiDsInitializeObjects */ + +typedef struct acpi_init_walk_info +{ + UINT32 TableIndex; + UINT32 ObjectCount; + UINT32 MethodCount; + UINT32 SerialMethodCount; + UINT32 NonSerialMethodCount; + UINT32 SerializedMethodCount; + UINT32 DeviceCount; + UINT32 OpRegionCount; + UINT32 FieldCount; + UINT32 BufferCount; + UINT32 PackageCount; + UINT32 OpRegionInit; + UINT32 FieldInit; + UINT32 BufferInit; + UINT32 PackageInit; + ACPI_OWNER_ID OwnerId; + +} ACPI_INIT_WALK_INFO; + + +typedef struct acpi_get_devices_info +{ + ACPI_WALK_CALLBACK UserFunction; + void *Context; + char *Hid; + +} ACPI_GET_DEVICES_INFO; + + +typedef union acpi_aml_operands +{ + ACPI_OPERAND_OBJECT *Operands[7]; + + struct + { + ACPI_OBJECT_INTEGER *Type; + ACPI_OBJECT_INTEGER *Code; + ACPI_OBJECT_INTEGER *Argument; + + } Fatal; + + struct + { + ACPI_OPERAND_OBJECT *Source; + ACPI_OBJECT_INTEGER *Index; + ACPI_OPERAND_OBJECT *Target; + + } Index; + + struct + { + ACPI_OPERAND_OBJECT *Source; + ACPI_OBJECT_INTEGER *Index; + ACPI_OBJECT_INTEGER *Length; + ACPI_OPERAND_OBJECT *Target; + + } Mid; + +} ACPI_AML_OPERANDS; + + +/* + * Structure used to pass object evaluation information and parameters. + * Purpose is to reduce CPU stack use. + */ +typedef struct acpi_evaluate_info +{ + /* The first 3 elements are passed by the caller to AcpiNsEvaluate */ + + ACPI_NAMESPACE_NODE *PrefixNode; /* Input: starting node */ + const char *RelativePathname; /* Input: path relative to PrefixNode */ + ACPI_OPERAND_OBJECT **Parameters; /* Input: argument list */ + + ACPI_NAMESPACE_NODE *Node; /* Resolved node (PrefixNode:RelativePathname) */ + ACPI_OPERAND_OBJECT *ObjDesc; /* Object attached to the resolved node */ + char *FullPathname; /* Full pathname of the resolved node */ + + const ACPI_PREDEFINED_INFO *Predefined; /* Used if Node is a predefined name */ + ACPI_OPERAND_OBJECT *ReturnObject; /* Object returned from the evaluation */ + union acpi_operand_object *ParentPackage; /* Used if return object is a Package */ + + UINT32 ReturnFlags; /* Used for return value analysis */ + UINT32 ReturnBtype; /* Bitmapped type of the returned object */ + UINT16 ParamCount; /* Count of the input argument list */ + UINT8 PassNumber; /* Parser pass number */ + UINT8 ReturnObjectType; /* Object type of the returned object */ + UINT8 NodeFlags; /* Same as Node->Flags */ + UINT8 Flags; /* General flags */ + +} ACPI_EVALUATE_INFO; + +/* Values for Flags above */ + +#define ACPI_IGNORE_RETURN_VALUE 1 + +/* Defines for ReturnFlags field above */ + +#define ACPI_OBJECT_REPAIRED 1 +#define ACPI_OBJECT_WRAPPED 2 + + +/* Info used by AcpiNsInitializeDevices */ + +typedef struct acpi_device_walk_info +{ + ACPI_TABLE_DESC *TableDesc; + ACPI_EVALUATE_INFO *EvaluateInfo; + UINT32 DeviceCount; + UINT32 Num_STA; + UINT32 Num_INI; + +} ACPI_DEVICE_WALK_INFO; + + +/* TBD: [Restructure] Merge with struct above */ + +typedef struct acpi_walk_info +{ + UINT32 DebugLevel; + UINT32 Count; + ACPI_OWNER_ID OwnerId; + UINT8 DisplayType; + +} ACPI_WALK_INFO; + +/* Display Types */ + +#define ACPI_DISPLAY_SUMMARY (UINT8) 0 +#define ACPI_DISPLAY_OBJECTS (UINT8) 1 +#define ACPI_DISPLAY_MASK (UINT8) 1 + +#define ACPI_DISPLAY_SHORT (UINT8) 2 + + +#endif diff --git a/ports/acpica/include/actables.h b/ports/acpica/include/actables.h new file mode 100644 index 0000000..59a9a39 --- /dev/null +++ b/ports/acpica/include/actables.h @@ -0,0 +1,390 @@ +/****************************************************************************** + * + * Name: actables.h - ACPI table management + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACTABLES_H__ +#define __ACTABLES_H__ + + +ACPI_STATUS +AcpiAllocateRootTable ( + UINT32 InitialTableCount); + +/* + * tbxfroot - Root pointer utilities + */ +UINT32 +AcpiTbGetRsdpLength ( + ACPI_TABLE_RSDP *Rsdp); + +ACPI_STATUS +AcpiTbValidateRsdp ( + ACPI_TABLE_RSDP *Rsdp); + +UINT8 * +AcpiTbScanMemoryForRsdp ( + UINT8 *StartAddress, + UINT32 Length); + + +/* + * tbdata - table data structure management + */ +ACPI_STATUS +AcpiTbGetNextTableDescriptor ( + UINT32 *TableIndex, + ACPI_TABLE_DESC **TableDesc); + +void +AcpiTbInitTableDescriptor ( + ACPI_TABLE_DESC *TableDesc, + ACPI_PHYSICAL_ADDRESS Address, + UINT8 Flags, + ACPI_TABLE_HEADER *Table); + +ACPI_STATUS +AcpiTbAcquireTempTable ( + ACPI_TABLE_DESC *TableDesc, + ACPI_PHYSICAL_ADDRESS Address, + UINT8 Flags); + +void +AcpiTbReleaseTempTable ( + ACPI_TABLE_DESC *TableDesc); + +ACPI_STATUS +AcpiTbValidateTempTable ( + ACPI_TABLE_DESC *TableDesc); + +ACPI_STATUS +AcpiTbVerifyTempTable ( + ACPI_TABLE_DESC *TableDesc, + char *Signature, + UINT32 *TableIndex); + +BOOLEAN +AcpiTbIsTableLoaded ( + UINT32 TableIndex); + +void +AcpiTbSetTableLoadedFlag ( + UINT32 TableIndex, + BOOLEAN IsLoaded); + + +/* + * tbfadt - FADT parse/convert/validate + */ +void +AcpiTbParseFadt ( + void); + +void +AcpiTbCreateLocalFadt ( + ACPI_TABLE_HEADER *Table, + UINT32 Length); + + +/* + * tbfind - find ACPI table + */ +ACPI_STATUS +AcpiTbFindTable ( + char *Signature, + char *OemId, + char *OemTableId, + UINT32 *TableIndex); + + +/* + * tbinstal - Table removal and deletion + */ +ACPI_STATUS +AcpiTbResizeRootTableList ( + void); + +ACPI_STATUS +AcpiTbValidateTable ( + ACPI_TABLE_DESC *TableDesc); + +void +AcpiTbInvalidateTable ( + ACPI_TABLE_DESC *TableDesc); + +void +AcpiTbOverrideTable ( + ACPI_TABLE_DESC *OldTableDesc); + +ACPI_STATUS +AcpiTbAcquireTable ( + ACPI_TABLE_DESC *TableDesc, + ACPI_TABLE_HEADER **TablePtr, + UINT32 *TableLength, + UINT8 *TableFlags); + +void +AcpiTbReleaseTable ( + ACPI_TABLE_HEADER *Table, + UINT32 TableLength, + UINT8 TableFlags); + +ACPI_STATUS +AcpiTbInstallStandardTable ( + ACPI_PHYSICAL_ADDRESS Address, + UINT8 Flags, + BOOLEAN Reload, + BOOLEAN Override, + UINT32 *TableIndex); + +void +AcpiTbUninstallTable ( + ACPI_TABLE_DESC *TableDesc); + +ACPI_STATUS +AcpiTbLoadTable ( + UINT32 TableIndex, + ACPI_NAMESPACE_NODE *ParentNode); + +ACPI_STATUS +AcpiTbInstallAndLoadTable ( + ACPI_PHYSICAL_ADDRESS Address, + UINT8 Flags, + BOOLEAN Override, + UINT32 *TableIndex); + +ACPI_STATUS +AcpiTbUnloadTable ( + UINT32 TableIndex); + +void +AcpiTbNotifyTable ( + UINT32 Event, + void *Table); + +void +AcpiTbTerminate ( + void); + +ACPI_STATUS +AcpiTbDeleteNamespaceByOwner ( + UINT32 TableIndex); + +ACPI_STATUS +AcpiTbAllocateOwnerId ( + UINT32 TableIndex); + +ACPI_STATUS +AcpiTbReleaseOwnerId ( + UINT32 TableIndex); + +ACPI_STATUS +AcpiTbGetOwnerId ( + UINT32 TableIndex, + ACPI_OWNER_ID *OwnerId); + + +/* + * tbutils - table manager utilities + */ +ACPI_STATUS +AcpiTbInitializeFacs ( + void); + +void +AcpiTbPrintTableHeader( + ACPI_PHYSICAL_ADDRESS Address, + ACPI_TABLE_HEADER *Header); + +UINT8 +AcpiTbChecksum ( + UINT8 *Buffer, + UINT32 Length); + +ACPI_STATUS +AcpiTbVerifyChecksum ( + ACPI_TABLE_HEADER *Table, + UINT32 Length); + +void +AcpiTbCheckDsdtHeader ( + void); + +ACPI_TABLE_HEADER * +AcpiTbCopyDsdt ( + UINT32 TableIndex); + +void +AcpiTbInstallTableWithOverride ( + ACPI_TABLE_DESC *NewTableDesc, + BOOLEAN Override, + UINT32 *TableIndex); + +ACPI_STATUS +AcpiTbParseRootTable ( + ACPI_PHYSICAL_ADDRESS RsdpAddress); + +ACPI_STATUS +AcpiTbGetTable ( + ACPI_TABLE_DESC *TableDesc, + ACPI_TABLE_HEADER **OutTable); + +void +AcpiTbPutTable ( + ACPI_TABLE_DESC *TableDesc); + + +/* + * tbxfload + */ +ACPI_STATUS +AcpiTbLoadNamespace ( + void); + +#endif /* __ACTABLES_H__ */ diff --git a/ports/acpica/include/actbinfo.h b/ports/acpica/include/actbinfo.h new file mode 100644 index 0000000..5a84f35 --- /dev/null +++ b/ports/acpica/include/actbinfo.h @@ -0,0 +1,411 @@ +/****************************************************************************** + * + * Module Name: actbinfo - Table disassembly info for non-AML tables + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +/* + * Macros used to generate offsets to specific table fields + */ +#define ACPI_FACS_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_FACS,f) +#define ACPI_GAS_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_GENERIC_ADDRESS,f) +#define ACPI_HDR_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_HEADER,f) +#define ACPI_RSDP_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_RSDP,f) +#define ACPI_BERT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_BERT,f) +#define ACPI_BGRT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_BGRT,f) +#define ACPI_BOOT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_BOOT,f) +#define ACPI_CPEP_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_CPEP,f) +#define ACPI_DBG2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_DBG2,f) +#define ACPI_DBGP_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_DBGP,f) +#define ACPI_DMAR_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_DMAR,f) +#define ACPI_DRTM_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_DRTM,f) +#define ACPI_ECDT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_ECDT,f) +#define ACPI_EINJ_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_EINJ,f) +#define ACPI_ERST_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_ERST,f) +#define ACPI_GTDT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_GTDT,f) +#define ACPI_HEST_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_HEST,f) +#define ACPI_HPET_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_HPET,f) +#define ACPI_HMAT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_HMAT,f) +#define ACPI_IORT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_IORT,f) +#define ACPI_IVRS_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_IVRS,f) +#define ACPI_MADT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_MADT,f) +#define ACPI_MCFG_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_MCFG,f) +#define ACPI_MCHI_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_MCHI,f) +#define ACPI_MPST_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_MPST,f) +#define ACPI_MSCT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_MSCT,f) +#define ACPI_NFIT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_NFIT,f) +#define ACPI_PCCT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_PCCT,f) +#define ACPI_PDTT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_PDTT,f) +#define ACPI_PMTT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_PMTT,f) +#define ACPI_RASF_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_RASF,f) +#define ACPI_S3PT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_S3PT,f) +#define ACPI_SBST_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SBST,f) +#define ACPI_SDEI_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SDEI,f) +#define ACPI_SDEV_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SDEV,f) +#define ACPI_SLIT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SLIT,f) +#define ACPI_SPCR_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SPCR,f) +#define ACPI_SPMI_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SPMI,f) +#define ACPI_SRAT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SRAT,f) +#define ACPI_STAO_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_STAO,f) +#define ACPI_TCPA_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_TCPA_HDR,f) +#define ACPI_TPM2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_TPM2,f) +#define ACPI_UEFI_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_UEFI,f) +#define ACPI_WAET_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_WAET,f) +#define ACPI_WDAT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_WDAT,f) +#define ACPI_WDDT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_WDDT,f) +#define ACPI_WDRT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_WDRT,f) +#define ACPI_WPBT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_WPBT,f) +#define ACPI_WSMT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_WSMT,f) +#define ACPI_XENV_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_XENV,f) + +/* Subtables */ + +#define ACPI_ASF0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_ASF_INFO,f) +#define ACPI_ASF1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_ASF_ALERT,f) +#define ACPI_ASF1a_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_ASF_ALERT_DATA,f) +#define ACPI_ASF2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_ASF_REMOTE,f) +#define ACPI_ASF2a_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_ASF_CONTROL_DATA,f) +#define ACPI_ASF3_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_ASF_RMCP,f) +#define ACPI_ASF4_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_ASF_ADDRESS,f) +#define ACPI_CPEP0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_CPEP_POLLING,f) +#define ACPI_CSRT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_CSRT_GROUP,f) +#define ACPI_CSRT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_CSRT_SHARED_INFO,f) +#define ACPI_CSRT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_CSRT_DESCRIPTOR,f) +#define ACPI_DBG20_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DBG2_DEVICE,f) +#define ACPI_DMARS_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DMAR_DEVICE_SCOPE,f) +#define ACPI_DMAR0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DMAR_HARDWARE_UNIT,f) +#define ACPI_DMAR1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DMAR_RESERVED_MEMORY,f) +#define ACPI_DMAR2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DMAR_ATSR,f) +#define ACPI_DMAR3_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DMAR_RHSA,f) +#define ACPI_DMAR4_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DMAR_ANDD,f) +#define ACPI_DRTM0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DRTM_VTABLE_LIST,f) +#define ACPI_DRTM1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DRTM_RESOURCE_LIST,f) +#define ACPI_DRTM1a_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DRTM_RESOURCE,f) +#define ACPI_DRTM2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_DRTM_DPS_ID,f) +#define ACPI_EINJ0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_WHEA_HEADER,f) +#define ACPI_ERST0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_WHEA_HEADER,f) +#define ACPI_FPDTH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_FPDT_HEADER,f) +#define ACPI_FPDT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_FPDT_BOOT_POINTER,f) +#define ACPI_FPDT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_FPDT_S3PT_POINTER,f) +#define ACPI_GTDT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_GTDT_TIMER_BLOCK,f) +#define ACPI_GTDT0a_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_GTDT_TIMER_ENTRY,f) +#define ACPI_GTDT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_GTDT_WATCHDOG,f) +#define ACPI_GTDTH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_GTDT_HEADER,f) +#define ACPI_HEST0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_IA_MACHINE_CHECK,f) +#define ACPI_HEST1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_IA_CORRECTED,f) +#define ACPI_HEST2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_IA_NMI,f) +#define ACPI_HEST6_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_AER_ROOT,f) +#define ACPI_HEST7_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_AER,f) +#define ACPI_HEST8_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_AER_BRIDGE,f) +#define ACPI_HEST9_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_GENERIC,f) +#define ACPI_HEST10_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_GENERIC_V2,f) +#define ACPI_HEST11_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_IA_DEFERRED_CHECK,f) +#define ACPI_HESTN_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_NOTIFY,f) +#define ACPI_HESTB_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HEST_IA_ERROR_BANK,f) +#define ACPI_HMAT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HMAT_ADDRESS_RANGE,f) +#define ACPI_HMAT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HMAT_LOCALITY,f) +#define ACPI_HMAT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HMAT_CACHE,f) +#define ACPI_HMATH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_HMAT_STRUCTURE,f) +#define ACPI_IORT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_ITS_GROUP,f) +#define ACPI_IORT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_NAMED_COMPONENT,f) +#define ACPI_IORT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_ROOT_COMPLEX,f) +#define ACPI_IORT3_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_SMMU,f) +#define ACPI_IORT3A_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_SMMU_GSI,f) +#define ACPI_IORT4_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_SMMU_V3,f) +#define ACPI_IORTA_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_MEMORY_ACCESS,f) +#define ACPI_IORTH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_NODE,f) +#define ACPI_IORTM_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IORT_ID_MAPPING,f) +#define ACPI_IVRSH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IVRS_HEADER,f) +#define ACPI_IVRS0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IVRS_HARDWARE,f) +#define ACPI_IVRS1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IVRS_MEMORY,f) +#define ACPI_IVRSD_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IVRS_DE_HEADER,f) +#define ACPI_IVRS8A_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IVRS_DEVICE8A,f) +#define ACPI_IVRS8B_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IVRS_DEVICE8B,f) +#define ACPI_IVRS8C_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_IVRS_DEVICE8C,f) +#define ACPI_LPITH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_LPIT_HEADER,f) +#define ACPI_LPIT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_LPIT_NATIVE,f) +#define ACPI_MADT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_LOCAL_APIC,f) +#define ACPI_MADT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_IO_APIC,f) +#define ACPI_MADT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_INTERRUPT_OVERRIDE,f) +#define ACPI_MADT3_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_NMI_SOURCE,f) +#define ACPI_MADT4_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_LOCAL_APIC_NMI,f) +#define ACPI_MADT5_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_LOCAL_APIC_OVERRIDE,f) +#define ACPI_MADT6_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_IO_SAPIC,f) +#define ACPI_MADT7_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_LOCAL_SAPIC,f) +#define ACPI_MADT8_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_INTERRUPT_SOURCE,f) +#define ACPI_MADT9_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_LOCAL_X2APIC,f) +#define ACPI_MADT10_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_LOCAL_X2APIC_NMI,f) +#define ACPI_MADT11_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_GENERIC_INTERRUPT,f) +#define ACPI_MADT12_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_GENERIC_DISTRIBUTOR,f) +#define ACPI_MADT13_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_GENERIC_MSI_FRAME,f) +#define ACPI_MADT14_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_GENERIC_REDISTRIBUTOR,f) +#define ACPI_MADT15_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MADT_GENERIC_TRANSLATOR,f) +#define ACPI_MADTH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SUBTABLE_HEADER,f) +#define ACPI_MCFG0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MCFG_ALLOCATION,f) +#define ACPI_MPST0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MPST_POWER_NODE,f) +#define ACPI_MPST0A_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MPST_POWER_STATE,f) +#define ACPI_MPST0B_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MPST_COMPONENT,f) +#define ACPI_MPST1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MPST_DATA_HDR,f) +#define ACPI_MPST2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MPST_POWER_DATA,f) +#define ACPI_MSCT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MSCT_PROXIMITY,f) +#define ACPI_MTMR0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_MTMR_ENTRY,f) +#define ACPI_NFITH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_HEADER,f) +#define ACPI_NFIT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_SYSTEM_ADDRESS,f) +#define ACPI_NFIT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_MEMORY_MAP,f) +#define ACPI_NFIT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_INTERLEAVE,f) +#define ACPI_NFIT3_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_SMBIOS,f) +#define ACPI_NFIT4_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_CONTROL_REGION,f) +#define ACPI_NFIT5_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_DATA_REGION,f) +#define ACPI_NFIT6_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_FLUSH_ADDRESS,f) +#define ACPI_NFIT7_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_NFIT_CAPABILITIES,f) +#define ACPI_PCCT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PCCT_SUBSPACE,f) +#define ACPI_PCCT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PCCT_HW_REDUCED,f) +#define ACPI_PCCT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PCCT_HW_REDUCED_TYPE2,f) +#define ACPI_PCCT3_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PCCT_EXT_PCC_MASTER,f) +#define ACPI_PCCT4_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PCCT_EXT_PCC_SLAVE,f) +#define ACPI_PDTT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PDTT_CHANNEL,f) +#define ACPI_PMTT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PMTT_SOCKET,f) +#define ACPI_PMTT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PMTT_CONTROLLER,f) +#define ACPI_PMTT1A_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PMTT_DOMAIN,f) +#define ACPI_PMTT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PMTT_PHYSICAL_COMPONENT,f) +#define ACPI_PMTTH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PMTT_HEADER,f) +#define ACPI_PPTTH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SUBTABLE_HEADER,f) +#define ACPI_PPTT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PPTT_PROCESSOR,f) +#define ACPI_PPTT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PPTT_CACHE,f) +#define ACPI_PPTT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_PPTT_ID,f) +#define ACPI_S3PTH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_FPDT_HEADER,f) +#define ACPI_S3PT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_S3PT_RESUME,f) +#define ACPI_S3PT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_S3PT_SUSPEND,f) +#define ACPI_SDEVH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SDEV_HEADER,f) +#define ACPI_SDEV0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SDEV_NAMESPACE,f) +#define ACPI_SDEV1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SDEV_PCIE,f) +#define ACPI_SDEV1A_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SDEV_PCIE_PATH,f) +#define ACPI_SLIC_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_SLIC,f) +#define ACPI_SRATH_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SUBTABLE_HEADER,f) +#define ACPI_SRAT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SRAT_CPU_AFFINITY,f) +#define ACPI_SRAT1_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SRAT_MEM_AFFINITY,f) +#define ACPI_SRAT2_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SRAT_X2APIC_CPU_AFFINITY,f) +#define ACPI_SRAT3_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SRAT_GICC_AFFINITY,f) +#define ACPI_SRAT4_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_SRAT_GIC_ITS_AFFINITY,f) +#define ACPI_TCPA_CLIENT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_TCPA_CLIENT,f) +#define ACPI_TCPA_SERVER_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_TCPA_SERVER,f) +#define ACPI_TPM2A_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TPM2_TRAILER,f) +#define ACPI_TPM211_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TPM2_ARM_SMC,f) +#define ACPI_VRTC0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_VRTC_ENTRY,f) +#define ACPI_WDAT0_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_WDAT_ENTRY,f) + +/* + * Simplify access to flag fields by breaking them up into bytes + */ +#define ACPI_FLAG_OFFSET(d,f,o) (UINT16) (ACPI_OFFSET (d,f) + o) + +/* Flags */ + +#define ACPI_BGRT_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_BGRT,f,o) +#define ACPI_DRTM_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_DRTM,f,o) +#define ACPI_DRTM1a_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_DRTM_RESOURCE,f,o) +#define ACPI_FADT_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_FADT,f,o) +#define ACPI_FACS_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_FACS,f,o) +#define ACPI_HPET_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_HPET,f,o) +#define ACPI_PPTT0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PPTT_PROCESSOR,f,o) +#define ACPI_PPTT1_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PPTT_CACHE,f,o) +#define ACPI_SRAT0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_SRAT_CPU_AFFINITY,f,o) +#define ACPI_SRAT1_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_SRAT_MEM_AFFINITY,f,o) +#define ACPI_SRAT2_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_SRAT_X2APIC_CPU_AFFINITY,f,o) +#define ACPI_SRAT3_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_SRAT_GICC_AFFINITY,f,o) +#define ACPI_GTDT_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_GTDT,f,o) +#define ACPI_GTDT0a_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_GTDT_TIMER_ENTRY,f,o) +#define ACPI_GTDT1_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_GTDT_WATCHDOG,f,o) +#define ACPI_HMAT0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_HMAT_ADDRESS_RANGE,f,o) +#define ACPI_HMAT1_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_HMAT_LOCALITY,f,o) +#define ACPI_HMAT2_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_HMAT_CACHE,f,o) +#define ACPI_IORT3_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_IORT_SMMU,f,o) +#define ACPI_IORT3a_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_IORT_SMMU_GSI,f,o) +#define ACPI_IORT4_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_IORT_SMMU_V3,f,o) +#define ACPI_IORTA_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_IORT_MEMORY_ACCESS,f,o) +#define ACPI_IORTM_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_IORT_ID_MAPPING,f,o) +#define ACPI_LPITH_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_LPIT_HEADER,f,o) +#define ACPI_MADT_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_MADT,f,o) +#define ACPI_MADT0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_LOCAL_APIC,f,o) +#define ACPI_MADT2_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_INTERRUPT_OVERRIDE,f,o) +#define ACPI_MADT3_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_NMI_SOURCE,f,o) +#define ACPI_MADT4_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_LOCAL_APIC_NMI,f,o) +#define ACPI_MADT7_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_LOCAL_SAPIC,f,o) +#define ACPI_MADT8_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_INTERRUPT_SOURCE,f,o) +#define ACPI_MADT9_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_LOCAL_X2APIC,f,o) +#define ACPI_MADT10_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_LOCAL_X2APIC_NMI,f,o) +#define ACPI_MADT11_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_GENERIC_INTERRUPT,f,o) +#define ACPI_MADT13_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MADT_GENERIC_MSI_FRAME,f,o) +#define ACPI_MPST0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MPST_POWER_NODE,f,o) +#define ACPI_MPST2_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_MPST_POWER_DATA,f,o) +#define ACPI_NFIT0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_NFIT_SYSTEM_ADDRESS,f,o) +#define ACPI_NFIT1_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_NFIT_MEMORY_MAP,f,o) +#define ACPI_NFIT4_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_NFIT_CONTROL_REGION,f,o) +#define ACPI_NFIT7_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_NFIT_CAPABILITIES,f,o) +#define ACPI_PCCT_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_PCCT,f,o) +#define ACPI_PCCT1_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PCCT_HW_REDUCED,f,o) +#define ACPI_PCCT2_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PCCT_HW_REDUCED_TYPE2,f,o) +#define ACPI_PCCT3_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PCCT_EXT_PCC_MASTER,f,o) +#define ACPI_PCCT4_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PCCT_EXT_PCC_SLAVE,f,o) +#define ACPI_PDTT0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PDTT_CHANNEL,f,o) +#define ACPI_PMTTH_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_PMTT_HEADER,f,o) +#define ACPI_SDEVH_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_SDEV_HEADER,f,o) +#define ACPI_WDDT_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_WDDT,f,o) +#define ACPI_WSMT_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_TABLE_WSMT,f,o) +#define ACPI_EINJ0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_WHEA_HEADER,f,o) +#define ACPI_ERST0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_WHEA_HEADER,f,o) +#define ACPI_HEST0_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_HEST_IA_MACHINE_CHECK,f,o) +#define ACPI_HEST1_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_HEST_IA_CORRECTED,f,o) +#define ACPI_HEST6_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_HEST_AER_ROOT,f,o) +#define ACPI_HEST11_FLAG_OFFSET(f,o) ACPI_FLAG_OFFSET (ACPI_HEST_IA_DEFERRED_CHECK,f,o) + +/* + * Required terminator for all tables below + */ +#define ACPI_DMT_TERMINATOR {ACPI_DMT_EXIT, 0, NULL, 0} +#define ACPI_DMT_NEW_LINE {ACPI_DMT_EXTRA_TEXT, 0, "\n", 0} diff --git a/ports/acpica/include/actbl.h b/ports/acpica/include/actbl.h new file mode 100644 index 0000000..58a9b73 --- /dev/null +++ b/ports/acpica/include/actbl.h @@ -0,0 +1,578 @@ +/****************************************************************************** + * + * Name: actbl.h - Basic ACPI Table Definitions + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACTBL_H__ +#define __ACTBL_H__ + + +/******************************************************************************* + * + * Fundamental ACPI tables + * + * This file contains definitions for the ACPI tables that are directly consumed + * by ACPICA. All other tables are consumed by the OS-dependent ACPI-related + * device drivers and other OS support code. + * + * The RSDP and FACS do not use the common ACPI table header. All other ACPI + * tables use the header. + * + ******************************************************************************/ + + +/* + * Values for description table header signatures for tables defined in this + * file. Useful because they make it more difficult to inadvertently type in + * the wrong signature. + */ +#define ACPI_SIG_DSDT "DSDT" /* Differentiated System Description Table */ +#define ACPI_SIG_FADT "FACP" /* Fixed ACPI Description Table */ +#define ACPI_SIG_FACS "FACS" /* Firmware ACPI Control Structure */ +#define ACPI_SIG_OSDT "OSDT" /* Override System Description Table */ +#define ACPI_SIG_PSDT "PSDT" /* Persistent System Description Table */ +#define ACPI_SIG_RSDP "RSD PTR " /* Root System Description Pointer */ +#define ACPI_SIG_RSDT "RSDT" /* Root System Description Table */ +#define ACPI_SIG_XSDT "XSDT" /* Extended System Description Table */ +#define ACPI_SIG_SSDT "SSDT" /* Secondary System Description Table */ +#define ACPI_RSDP_NAME "RSDP" /* Short name for RSDP, not signature */ + + +/* + * All tables and structures must be byte-packed to match the ACPI + * specification, since the tables are provided by the system BIOS + */ +#pragma pack(1) + +/* + * Note: C bitfields are not used for this reason: + * + * "Bitfields are great and easy to read, but unfortunately the C language + * does not specify the layout of bitfields in memory, which means they are + * essentially useless for dealing with packed data in on-disk formats or + * binary wire protocols." (Or ACPI tables and buffers.) "If you ask me, + * this decision was a design error in C. Ritchie could have picked an order + * and stuck with it." Norman Ramsey. + * See http://stackoverflow.com/a/1053662/41661 + */ + + +/******************************************************************************* + * + * Master ACPI Table Header. This common header is used by all ACPI tables + * except the RSDP and FACS. + * + ******************************************************************************/ + +typedef struct acpi_table_header +{ + char Signature[ACPI_NAME_SIZE]; /* ASCII table signature */ + UINT32 Length; /* Length of table in bytes, including this header */ + UINT8 Revision; /* ACPI Specification minor version number */ + UINT8 Checksum; /* To make sum of entire table == 0 */ + char OemId[ACPI_OEM_ID_SIZE]; /* ASCII OEM identification */ + char OemTableId[ACPI_OEM_TABLE_ID_SIZE]; /* ASCII OEM table identification */ + UINT32 OemRevision; /* OEM revision number */ + char AslCompilerId[ACPI_NAME_SIZE]; /* ASCII ASL compiler vendor ID */ + UINT32 AslCompilerRevision; /* ASL compiler version */ + +} ACPI_TABLE_HEADER; + + +/******************************************************************************* + * + * GAS - Generic Address Structure (ACPI 2.0+) + * + * Note: Since this structure is used in the ACPI tables, it is byte aligned. + * If misaligned access is not supported by the hardware, accesses to the + * 64-bit Address field must be performed with care. + * + ******************************************************************************/ + +typedef struct acpi_generic_address +{ + UINT8 SpaceId; /* Address space where struct or register exists */ + UINT8 BitWidth; /* Size in bits of given register */ + UINT8 BitOffset; /* Bit offset within the register */ + UINT8 AccessWidth; /* Minimum Access size (ACPI 3.0) */ + UINT64 Address; /* 64-bit address of struct or register */ + +} ACPI_GENERIC_ADDRESS; + + +/******************************************************************************* + * + * RSDP - Root System Description Pointer (Signature is "RSD PTR ") + * Version 2 + * + ******************************************************************************/ + +typedef struct acpi_table_rsdp +{ + char Signature[8]; /* ACPI signature, contains "RSD PTR " */ + UINT8 Checksum; /* ACPI 1.0 checksum */ + char OemId[ACPI_OEM_ID_SIZE]; /* OEM identification */ + UINT8 Revision; /* Must be (0) for ACPI 1.0 or (2) for ACPI 2.0+ */ + UINT32 RsdtPhysicalAddress; /* 32-bit physical address of the RSDT */ + UINT32 Length; /* Table length in bytes, including header (ACPI 2.0+) */ + UINT64 XsdtPhysicalAddress; /* 64-bit physical address of the XSDT (ACPI 2.0+) */ + UINT8 ExtendedChecksum; /* Checksum of entire table (ACPI 2.0+) */ + UINT8 Reserved[3]; /* Reserved, must be zero */ + +} ACPI_TABLE_RSDP; + +/* Standalone struct for the ACPI 1.0 RSDP */ + +typedef struct acpi_rsdp_common +{ + char Signature[8]; + UINT8 Checksum; + char OemId[ACPI_OEM_ID_SIZE]; + UINT8 Revision; + UINT32 RsdtPhysicalAddress; + +} ACPI_RSDP_COMMON; + +/* Standalone struct for the extended part of the RSDP (ACPI 2.0+) */ + +typedef struct acpi_rsdp_extension +{ + UINT32 Length; + UINT64 XsdtPhysicalAddress; + UINT8 ExtendedChecksum; + UINT8 Reserved[3]; + +} ACPI_RSDP_EXTENSION; + + +/******************************************************************************* + * + * RSDT/XSDT - Root System Description Tables + * Version 1 (both) + * + ******************************************************************************/ + +typedef struct acpi_table_rsdt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 TableOffsetEntry[1]; /* Array of pointers to ACPI tables */ + +} ACPI_TABLE_RSDT; + +typedef struct acpi_table_xsdt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT64 TableOffsetEntry[1]; /* Array of pointers to ACPI tables */ + +} ACPI_TABLE_XSDT; + +#define ACPI_RSDT_ENTRY_SIZE (sizeof (UINT32)) +#define ACPI_XSDT_ENTRY_SIZE (sizeof (UINT64)) + + +/******************************************************************************* + * + * FACS - Firmware ACPI Control Structure (FACS) + * + ******************************************************************************/ + +typedef struct acpi_table_facs +{ + char Signature[4]; /* ASCII table signature */ + UINT32 Length; /* Length of structure, in bytes */ + UINT32 HardwareSignature; /* Hardware configuration signature */ + UINT32 FirmwareWakingVector; /* 32-bit physical address of the Firmware Waking Vector */ + UINT32 GlobalLock; /* Global Lock for shared hardware resources */ + UINT32 Flags; + UINT64 XFirmwareWakingVector; /* 64-bit version of the Firmware Waking Vector (ACPI 2.0+) */ + UINT8 Version; /* Version of this table (ACPI 2.0+) */ + UINT8 Reserved[3]; /* Reserved, must be zero */ + UINT32 OspmFlags; /* Flags to be set by OSPM (ACPI 4.0) */ + UINT8 Reserved1[24]; /* Reserved, must be zero */ + +} ACPI_TABLE_FACS; + +/* Masks for GlobalLock flag field above */ + +#define ACPI_GLOCK_PENDING (1) /* 00: Pending global lock ownership */ +#define ACPI_GLOCK_OWNED (1<<1) /* 01: Global lock is owned */ + +/* Masks for Flags field above */ + +#define ACPI_FACS_S4_BIOS_PRESENT (1) /* 00: S4BIOS support is present */ +#define ACPI_FACS_64BIT_WAKE (1<<1) /* 01: 64-bit wake vector supported (ACPI 4.0) */ + +/* Masks for OspmFlags field above */ + +#define ACPI_FACS_64BIT_ENVIRONMENT (1) /* 00: 64-bit wake environment is required (ACPI 4.0) */ + + +/******************************************************************************* + * + * FADT - Fixed ACPI Description Table (Signature "FACP") + * Version 6 + * + ******************************************************************************/ + +/* Fields common to all versions of the FADT */ + +typedef struct acpi_table_fadt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Facs; /* 32-bit physical address of FACS */ + UINT32 Dsdt; /* 32-bit physical address of DSDT */ + UINT8 Model; /* System Interrupt Model (ACPI 1.0) - not used in ACPI 2.0+ */ + UINT8 PreferredProfile; /* Conveys preferred power management profile to OSPM. */ + UINT16 SciInterrupt; /* System vector of SCI interrupt */ + UINT32 SmiCommand; /* 32-bit Port address of SMI command port */ + UINT8 AcpiEnable; /* Value to write to SMI_CMD to enable ACPI */ + UINT8 AcpiDisable; /* Value to write to SMI_CMD to disable ACPI */ + UINT8 S4BiosRequest; /* Value to write to SMI_CMD to enter S4BIOS state */ + UINT8 PstateControl; /* Processor performance state control*/ + UINT32 Pm1aEventBlock; /* 32-bit port address of Power Mgt 1a Event Reg Blk */ + UINT32 Pm1bEventBlock; /* 32-bit port address of Power Mgt 1b Event Reg Blk */ + UINT32 Pm1aControlBlock; /* 32-bit port address of Power Mgt 1a Control Reg Blk */ + UINT32 Pm1bControlBlock; /* 32-bit port address of Power Mgt 1b Control Reg Blk */ + UINT32 Pm2ControlBlock; /* 32-bit port address of Power Mgt 2 Control Reg Blk */ + UINT32 PmTimerBlock; /* 32-bit port address of Power Mgt Timer Ctrl Reg Blk */ + UINT32 Gpe0Block; /* 32-bit port address of General Purpose Event 0 Reg Blk */ + UINT32 Gpe1Block; /* 32-bit port address of General Purpose Event 1 Reg Blk */ + UINT8 Pm1EventLength; /* Byte Length of ports at Pm1xEventBlock */ + UINT8 Pm1ControlLength; /* Byte Length of ports at Pm1xControlBlock */ + UINT8 Pm2ControlLength; /* Byte Length of ports at Pm2ControlBlock */ + UINT8 PmTimerLength; /* Byte Length of ports at PmTimerBlock */ + UINT8 Gpe0BlockLength; /* Byte Length of ports at Gpe0Block */ + UINT8 Gpe1BlockLength; /* Byte Length of ports at Gpe1Block */ + UINT8 Gpe1Base; /* Offset in GPE number space where GPE1 events start */ + UINT8 CstControl; /* Support for the _CST object and C-States change notification */ + UINT16 C2Latency; /* Worst case HW latency to enter/exit C2 state */ + UINT16 C3Latency; /* Worst case HW latency to enter/exit C3 state */ + UINT16 FlushSize; /* Processor memory cache line width, in bytes */ + UINT16 FlushStride; /* Number of flush strides that need to be read */ + UINT8 DutyOffset; /* Processor duty cycle index in processor P_CNT reg */ + UINT8 DutyWidth; /* Processor duty cycle value bit width in P_CNT register */ + UINT8 DayAlarm; /* Index to day-of-month alarm in RTC CMOS RAM */ + UINT8 MonthAlarm; /* Index to month-of-year alarm in RTC CMOS RAM */ + UINT8 Century; /* Index to century in RTC CMOS RAM */ + UINT16 BootFlags; /* IA-PC Boot Architecture Flags (see below for individual flags) */ + UINT8 Reserved; /* Reserved, must be zero */ + UINT32 Flags; /* Miscellaneous flag bits (see below for individual flags) */ + ACPI_GENERIC_ADDRESS ResetRegister; /* 64-bit address of the Reset register */ + UINT8 ResetValue; /* Value to write to the ResetRegister port to reset the system */ + UINT16 ArmBootFlags; /* ARM-Specific Boot Flags (see below for individual flags) (ACPI 5.1) */ + UINT8 MinorRevision; /* FADT Minor Revision (ACPI 5.1) */ + UINT64 XFacs; /* 64-bit physical address of FACS */ + UINT64 XDsdt; /* 64-bit physical address of DSDT */ + ACPI_GENERIC_ADDRESS XPm1aEventBlock; /* 64-bit Extended Power Mgt 1a Event Reg Blk address */ + ACPI_GENERIC_ADDRESS XPm1bEventBlock; /* 64-bit Extended Power Mgt 1b Event Reg Blk address */ + ACPI_GENERIC_ADDRESS XPm1aControlBlock; /* 64-bit Extended Power Mgt 1a Control Reg Blk address */ + ACPI_GENERIC_ADDRESS XPm1bControlBlock; /* 64-bit Extended Power Mgt 1b Control Reg Blk address */ + ACPI_GENERIC_ADDRESS XPm2ControlBlock; /* 64-bit Extended Power Mgt 2 Control Reg Blk address */ + ACPI_GENERIC_ADDRESS XPmTimerBlock; /* 64-bit Extended Power Mgt Timer Ctrl Reg Blk address */ + ACPI_GENERIC_ADDRESS XGpe0Block; /* 64-bit Extended General Purpose Event 0 Reg Blk address */ + ACPI_GENERIC_ADDRESS XGpe1Block; /* 64-bit Extended General Purpose Event 1 Reg Blk address */ + ACPI_GENERIC_ADDRESS SleepControl; /* 64-bit Sleep Control register (ACPI 5.0) */ + ACPI_GENERIC_ADDRESS SleepStatus; /* 64-bit Sleep Status register (ACPI 5.0) */ + UINT64 HypervisorId; /* Hypervisor Vendor ID (ACPI 6.0) */ + +} ACPI_TABLE_FADT; + + +/* Masks for FADT IA-PC Boot Architecture Flags (boot_flags) [Vx]=Introduced in this FADT revision */ + +#define ACPI_FADT_LEGACY_DEVICES (1) /* 00: [V2] System has LPC or ISA bus devices */ +#define ACPI_FADT_8042 (1<<1) /* 01: [V3] System has an 8042 controller on port 60/64 */ +#define ACPI_FADT_NO_VGA (1<<2) /* 02: [V4] It is not safe to probe for VGA hardware */ +#define ACPI_FADT_NO_MSI (1<<3) /* 03: [V4] Message Signaled Interrupts (MSI) must not be enabled */ +#define ACPI_FADT_NO_ASPM (1<<4) /* 04: [V4] PCIe ASPM control must not be enabled */ +#define ACPI_FADT_NO_CMOS_RTC (1<<5) /* 05: [V5] No CMOS real-time clock present */ + +/* Masks for FADT ARM Boot Architecture Flags (arm_boot_flags) ACPI 5.1 */ + +#define ACPI_FADT_PSCI_COMPLIANT (1) /* 00: [V5+] PSCI 0.2+ is implemented */ +#define ACPI_FADT_PSCI_USE_HVC (1<<1) /* 01: [V5+] HVC must be used instead of SMC as the PSCI conduit */ + +/* Masks for FADT flags */ + +#define ACPI_FADT_WBINVD (1) /* 00: [V1] The WBINVD instruction works properly */ +#define ACPI_FADT_WBINVD_FLUSH (1<<1) /* 01: [V1] WBINVD flushes but does not invalidate caches */ +#define ACPI_FADT_C1_SUPPORTED (1<<2) /* 02: [V1] All processors support C1 state */ +#define ACPI_FADT_C2_MP_SUPPORTED (1<<3) /* 03: [V1] C2 state works on MP system */ +#define ACPI_FADT_POWER_BUTTON (1<<4) /* 04: [V1] Power button is handled as a control method device */ +#define ACPI_FADT_SLEEP_BUTTON (1<<5) /* 05: [V1] Sleep button is handled as a control method device */ +#define ACPI_FADT_FIXED_RTC (1<<6) /* 06: [V1] RTC wakeup status is not in fixed register space */ +#define ACPI_FADT_S4_RTC_WAKE (1<<7) /* 07: [V1] RTC alarm can wake system from S4 */ +#define ACPI_FADT_32BIT_TIMER (1<<8) /* 08: [V1] ACPI timer width is 32-bit (0=24-bit) */ +#define ACPI_FADT_DOCKING_SUPPORTED (1<<9) /* 09: [V1] Docking supported */ +#define ACPI_FADT_RESET_REGISTER (1<<10) /* 10: [V2] System reset via the FADT RESET_REG supported */ +#define ACPI_FADT_SEALED_CASE (1<<11) /* 11: [V3] No internal expansion capabilities and case is sealed */ +#define ACPI_FADT_HEADLESS (1<<12) /* 12: [V3] No local video capabilities or local input devices */ +#define ACPI_FADT_SLEEP_TYPE (1<<13) /* 13: [V3] Must execute native instruction after writing SLP_TYPx register */ +#define ACPI_FADT_PCI_EXPRESS_WAKE (1<<14) /* 14: [V4] System supports PCIEXP_WAKE (STS/EN) bits (ACPI 3.0) */ +#define ACPI_FADT_PLATFORM_CLOCK (1<<15) /* 15: [V4] OSPM should use platform-provided timer (ACPI 3.0) */ +#define ACPI_FADT_S4_RTC_VALID (1<<16) /* 16: [V4] Contents of RTC_STS valid after S4 wake (ACPI 3.0) */ +#define ACPI_FADT_REMOTE_POWER_ON (1<<17) /* 17: [V4] System is compatible with remote power on (ACPI 3.0) */ +#define ACPI_FADT_APIC_CLUSTER (1<<18) /* 18: [V4] All local APICs must use cluster model (ACPI 3.0) */ +#define ACPI_FADT_APIC_PHYSICAL (1<<19) /* 19: [V4] All local xAPICs must use physical dest mode (ACPI 3.0) */ +#define ACPI_FADT_HW_REDUCED (1<<20) /* 20: [V5] ACPI hardware is not implemented (ACPI 5.0) */ +#define ACPI_FADT_LOW_POWER_S0 (1<<21) /* 21: [V5] S0 power savings are equal or better than S3 (ACPI 5.0) */ + + +/* Values for PreferredProfile (Preferred Power Management Profiles) */ + +enum AcpiPreferredPmProfiles +{ + PM_UNSPECIFIED = 0, + PM_DESKTOP = 1, + PM_MOBILE = 2, + PM_WORKSTATION = 3, + PM_ENTERPRISE_SERVER = 4, + PM_SOHO_SERVER = 5, + PM_APPLIANCE_PC = 6, + PM_PERFORMANCE_SERVER = 7, + PM_TABLET = 8 +}; + +/* Values for SleepStatus and SleepControl registers (V5+ FADT) */ + +#define ACPI_X_WAKE_STATUS 0x80 +#define ACPI_X_SLEEP_TYPE_MASK 0x1C +#define ACPI_X_SLEEP_TYPE_POSITION 0x02 +#define ACPI_X_SLEEP_ENABLE 0x20 + + +/* Reset to default packing */ + +#pragma pack() + + +/* + * Internal table-related structures + */ +typedef union acpi_name_union +{ + UINT32 Integer; + char Ascii[4]; + +} ACPI_NAME_UNION; + + +/* Internal ACPI Table Descriptor. One per ACPI table. */ + +typedef struct acpi_table_desc +{ + ACPI_PHYSICAL_ADDRESS Address; + ACPI_TABLE_HEADER *Pointer; + UINT32 Length; /* Length fixed at 32 bits (fixed in table header) */ + ACPI_NAME_UNION Signature; + ACPI_OWNER_ID OwnerId; + UINT8 Flags; + UINT16 ValidationCount; + +} ACPI_TABLE_DESC; + +/* + * Maximum value of the ValidationCount field in ACPI_TABLE_DESC. + * When reached, ValidationCount cannot be changed any more and the table will + * be permanently regarded as validated. + * + * This is to prevent situations in which unbalanced table get/put operations + * may cause premature table unmapping in the OS to happen. + * + * The maximum validation count can be defined to any value, but should be + * greater than the maximum number of OS early stage mapping slots to avoid + * leaking early stage table mappings to the late stage. + */ +#define ACPI_MAX_TABLE_VALIDATIONS ACPI_UINT16_MAX + +/* Masks for Flags field above */ + +#define ACPI_TABLE_ORIGIN_EXTERNAL_VIRTUAL (0) /* Virtual address, external maintained */ +#define ACPI_TABLE_ORIGIN_INTERNAL_PHYSICAL (1) /* Physical address, internally mapped */ +#define ACPI_TABLE_ORIGIN_INTERNAL_VIRTUAL (2) /* Virtual address, internallly allocated */ +#define ACPI_TABLE_ORIGIN_MASK (3) +#define ACPI_TABLE_IS_VERIFIED (4) +#define ACPI_TABLE_IS_LOADED (8) + + +/* + * Get the remaining ACPI tables + */ +#include "actbl1.h" +#include "actbl2.h" +#include "actbl3.h" + +/* Macros used to generate offsets to specific table fields */ + +#define ACPI_FADT_OFFSET(f) (UINT16) ACPI_OFFSET (ACPI_TABLE_FADT, f) + +/* + * Sizes of the various flavors of FADT. We need to look closely + * at the FADT length because the version number essentially tells + * us nothing because of many BIOS bugs where the version does not + * match the expected length. In other words, the length of the + * FADT is the bottom line as to what the version really is. + * + * For reference, the values below are as follows: + * FADT V1 size: 0x074 + * FADT V2 size: 0x084 + * FADT V3 size: 0x0F4 + * FADT V4 size: 0x0F4 + * FADT V5 size: 0x10C + * FADT V6 size: 0x114 + */ +#define ACPI_FADT_V1_SIZE (UINT32) (ACPI_FADT_OFFSET (Flags) + 4) +#define ACPI_FADT_V2_SIZE (UINT32) (ACPI_FADT_OFFSET (MinorRevision) + 1) +#define ACPI_FADT_V3_SIZE (UINT32) (ACPI_FADT_OFFSET (SleepControl)) +#define ACPI_FADT_V5_SIZE (UINT32) (ACPI_FADT_OFFSET (HypervisorId)) +#define ACPI_FADT_V6_SIZE (UINT32) (sizeof (ACPI_TABLE_FADT)) + +#define ACPI_FADT_CONFORMANCE "ACPI 6.1 (FADT version 6)" + +#endif /* __ACTBL_H__ */ diff --git a/ports/acpica/include/actbl1.h b/ports/acpica/include/actbl1.h new file mode 100644 index 0000000..4f03ab0 --- /dev/null +++ b/ports/acpica/include/actbl1.h @@ -0,0 +1,2046 @@ +/****************************************************************************** + * + * Name: actbl1.h - Additional ACPI table definitions + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACTBL1_H__ +#define __ACTBL1_H__ + + +/******************************************************************************* + * + * Additional ACPI Tables + * + * These tables are not consumed directly by the ACPICA subsystem, but are + * included here to support device drivers and the AML disassembler. + * + ******************************************************************************/ + + +/* + * Values for description table header signatures for tables defined in this + * file. Useful because they make it more difficult to inadvertently type in + * the wrong signature. + */ +#define ACPI_SIG_ASF "ASF!" /* Alert Standard Format table */ +#define ACPI_SIG_BERT "BERT" /* Boot Error Record Table */ +#define ACPI_SIG_BGRT "BGRT" /* Boot Graphics Resource Table */ +#define ACPI_SIG_BOOT "BOOT" /* Simple Boot Flag Table */ +#define ACPI_SIG_CPEP "CPEP" /* Corrected Platform Error Polling table */ +#define ACPI_SIG_CSRT "CSRT" /* Core System Resource Table */ +#define ACPI_SIG_DBG2 "DBG2" /* Debug Port table type 2 */ +#define ACPI_SIG_DBGP "DBGP" /* Debug Port table */ +#define ACPI_SIG_DMAR "DMAR" /* DMA Remapping table */ +#define ACPI_SIG_DRTM "DRTM" /* Dynamic Root of Trust for Measurement table */ +#define ACPI_SIG_ECDT "ECDT" /* Embedded Controller Boot Resources Table */ +#define ACPI_SIG_EINJ "EINJ" /* Error Injection table */ +#define ACPI_SIG_ERST "ERST" /* Error Record Serialization Table */ +#define ACPI_SIG_FPDT "FPDT" /* Firmware Performance Data Table */ +#define ACPI_SIG_GTDT "GTDT" /* Generic Timer Description Table */ +#define ACPI_SIG_HEST "HEST" /* Hardware Error Source Table */ +#define ACPI_SIG_HMAT "HMAT" /* Heterogeneous Memory Attributes Table */ +#define ACPI_SIG_HPET "HPET" /* High Precision Event Timer table */ +#define ACPI_SIG_IBFT "IBFT" /* iSCSI Boot Firmware Table */ + +#define ACPI_SIG_S3PT "S3PT" /* S3 Performance (sub)Table */ +#define ACPI_SIG_PCCS "PCC" /* PCC Shared Memory Region */ + + +/* Reserved table signatures */ + +#define ACPI_SIG_MATR "MATR" /* Memory Address Translation Table */ +#define ACPI_SIG_MSDM "MSDM" /* Microsoft Data Management Table */ + +/* + * These tables have been seen in the field, but no definition has been found + */ +#ifdef ACPI_UNDEFINED_TABLES +#define ACPI_SIG_ATKG "ATKG" +#define ACPI_SIG_GSCI "GSCI" /* GMCH SCI table */ +#define ACPI_SIG_IEIT "IEIT" +#endif + +/* + * All tables must be byte-packed to match the ACPI specification, since + * the tables are provided by the system BIOS. + */ +#pragma pack(1) + +/* + * Note: C bitfields are not used for this reason: + * + * "Bitfields are great and easy to read, but unfortunately the C language + * does not specify the layout of bitfields in memory, which means they are + * essentially useless for dealing with packed data in on-disk formats or + * binary wire protocols." (Or ACPI tables and buffers.) "If you ask me, + * this decision was a design error in C. Ritchie could have picked an order + * and stuck with it." Norman Ramsey. + * See http://stackoverflow.com/a/1053662/41661 + */ + + +/******************************************************************************* + * + * Common subtable headers + * + ******************************************************************************/ + +/* Generic subtable header (used in MADT, SRAT, etc.) */ + +typedef struct acpi_subtable_header +{ + UINT8 Type; + UINT8 Length; + +} ACPI_SUBTABLE_HEADER; + + +/* Subtable header for WHEA tables (EINJ, ERST, WDAT) */ + +typedef struct acpi_whea_header +{ + UINT8 Action; + UINT8 Instruction; + UINT8 Flags; + UINT8 Reserved; + ACPI_GENERIC_ADDRESS RegisterRegion; + UINT64 Value; /* Value used with Read/Write register */ + UINT64 Mask; /* Bitmask required for this register instruction */ + +} ACPI_WHEA_HEADER; + + +/******************************************************************************* + * + * ASF - Alert Standard Format table (Signature "ASF!") + * Revision 0x10 + * + * Conforms to the Alert Standard Format Specification V2.0, 23 April 2003 + * + ******************************************************************************/ + +typedef struct acpi_table_asf +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_ASF; + + +/* ASF subtable header */ + +typedef struct acpi_asf_header +{ + UINT8 Type; + UINT8 Reserved; + UINT16 Length; + +} ACPI_ASF_HEADER; + + +/* Values for Type field above */ + +enum AcpiAsfType +{ + ACPI_ASF_TYPE_INFO = 0, + ACPI_ASF_TYPE_ALERT = 1, + ACPI_ASF_TYPE_CONTROL = 2, + ACPI_ASF_TYPE_BOOT = 3, + ACPI_ASF_TYPE_ADDRESS = 4, + ACPI_ASF_TYPE_RESERVED = 5 +}; + +/* + * ASF subtables + */ + +/* 0: ASF Information */ + +typedef struct acpi_asf_info +{ + ACPI_ASF_HEADER Header; + UINT8 MinResetValue; + UINT8 MinPollInterval; + UINT16 SystemId; + UINT32 MfgId; + UINT8 Flags; + UINT8 Reserved2[3]; + +} ACPI_ASF_INFO; + +/* Masks for Flags field above */ + +#define ACPI_ASF_SMBUS_PROTOCOLS (1) + + +/* 1: ASF Alerts */ + +typedef struct acpi_asf_alert +{ + ACPI_ASF_HEADER Header; + UINT8 AssertMask; + UINT8 DeassertMask; + UINT8 Alerts; + UINT8 DataLength; + +} ACPI_ASF_ALERT; + +typedef struct acpi_asf_alert_data +{ + UINT8 Address; + UINT8 Command; + UINT8 Mask; + UINT8 Value; + UINT8 SensorType; + UINT8 Type; + UINT8 Offset; + UINT8 SourceType; + UINT8 Severity; + UINT8 SensorNumber; + UINT8 Entity; + UINT8 Instance; + +} ACPI_ASF_ALERT_DATA; + + +/* 2: ASF Remote Control */ + +typedef struct acpi_asf_remote +{ + ACPI_ASF_HEADER Header; + UINT8 Controls; + UINT8 DataLength; + UINT16 Reserved2; + +} ACPI_ASF_REMOTE; + +typedef struct acpi_asf_control_data +{ + UINT8 Function; + UINT8 Address; + UINT8 Command; + UINT8 Value; + +} ACPI_ASF_CONTROL_DATA; + + +/* 3: ASF RMCP Boot Options */ + +typedef struct acpi_asf_rmcp +{ + ACPI_ASF_HEADER Header; + UINT8 Capabilities[7]; + UINT8 CompletionCode; + UINT32 EnterpriseId; + UINT8 Command; + UINT16 Parameter; + UINT16 BootOptions; + UINT16 OemParameters; + +} ACPI_ASF_RMCP; + + +/* 4: ASF Address */ + +typedef struct acpi_asf_address +{ + ACPI_ASF_HEADER Header; + UINT8 EpromAddress; + UINT8 Devices; + +} ACPI_ASF_ADDRESS; + + +/******************************************************************************* + * + * BERT - Boot Error Record Table (ACPI 4.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_bert +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 RegionLength; /* Length of the boot error region */ + UINT64 Address; /* Physical address of the error region */ + +} ACPI_TABLE_BERT; + + +/* Boot Error Region (not a subtable, pointed to by Address field above) */ + +typedef struct acpi_bert_region +{ + UINT32 BlockStatus; /* Type of error information */ + UINT32 RawDataOffset; /* Offset to raw error data */ + UINT32 RawDataLength; /* Length of raw error data */ + UINT32 DataLength; /* Length of generic error data */ + UINT32 ErrorSeverity; /* Severity code */ + +} ACPI_BERT_REGION; + +/* Values for BlockStatus flags above */ + +#define ACPI_BERT_UNCORRECTABLE (1) +#define ACPI_BERT_CORRECTABLE (1<<1) +#define ACPI_BERT_MULTIPLE_UNCORRECTABLE (1<<2) +#define ACPI_BERT_MULTIPLE_CORRECTABLE (1<<3) +#define ACPI_BERT_ERROR_ENTRY_COUNT (0xFF<<4) /* 8 bits, error count */ + +/* Values for ErrorSeverity above */ + +enum AcpiBertErrorSeverity +{ + ACPI_BERT_ERROR_CORRECTABLE = 0, + ACPI_BERT_ERROR_FATAL = 1, + ACPI_BERT_ERROR_CORRECTED = 2, + ACPI_BERT_ERROR_NONE = 3, + ACPI_BERT_ERROR_RESERVED = 4 /* 4 and greater are reserved */ +}; + +/* + * Note: The generic error data that follows the ErrorSeverity field above + * uses the ACPI_HEST_GENERIC_DATA defined under the HEST table below + */ + + +/******************************************************************************* + * + * BGRT - Boot Graphics Resource Table (ACPI 5.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_bgrt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT16 Version; + UINT8 Status; + UINT8 ImageType; + UINT64 ImageAddress; + UINT32 ImageOffsetX; + UINT32 ImageOffsetY; + +} ACPI_TABLE_BGRT; + +/* Flags for Status field above */ + +#define ACPI_BGRT_DISPLAYED (1) +#define ACPI_BGRT_ORIENTATION_OFFSET (3 << 1) + + +/******************************************************************************* + * + * BOOT - Simple Boot Flag Table + * Version 1 + * + * Conforms to the "Simple Boot Flag Specification", Version 2.1 + * + ******************************************************************************/ + +typedef struct acpi_table_boot +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 CmosIndex; /* Index in CMOS RAM for the boot register */ + UINT8 Reserved[3]; + +} ACPI_TABLE_BOOT; + + +/******************************************************************************* + * + * CPEP - Corrected Platform Error Polling table (ACPI 4.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_cpep +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT64 Reserved; + +} ACPI_TABLE_CPEP; + + +/* Subtable */ + +typedef struct acpi_cpep_polling +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 Id; /* Processor ID */ + UINT8 Eid; /* Processor EID */ + UINT32 Interval; /* Polling interval (msec) */ + +} ACPI_CPEP_POLLING; + + +/******************************************************************************* + * + * CSRT - Core System Resource Table + * Version 0 + * + * Conforms to the "Core System Resource Table (CSRT)", November 14, 2011 + * + ******************************************************************************/ + +typedef struct acpi_table_csrt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_CSRT; + + +/* Resource Group subtable */ + +typedef struct acpi_csrt_group +{ + UINT32 Length; + UINT32 VendorId; + UINT32 SubvendorId; + UINT16 DeviceId; + UINT16 SubdeviceId; + UINT16 Revision; + UINT16 Reserved; + UINT32 SharedInfoLength; + + /* Shared data immediately follows (Length = SharedInfoLength) */ + +} ACPI_CSRT_GROUP; + +/* Shared Info subtable */ + +typedef struct acpi_csrt_shared_info +{ + UINT16 MajorVersion; + UINT16 MinorVersion; + UINT32 MmioBaseLow; + UINT32 MmioBaseHigh; + UINT32 GsiInterrupt; + UINT8 InterruptPolarity; + UINT8 InterruptMode; + UINT8 NumChannels; + UINT8 DmaAddressWidth; + UINT16 BaseRequestLine; + UINT16 NumHandshakeSignals; + UINT32 MaxBlockSize; + + /* Resource descriptors immediately follow (Length = Group Length - SharedInfoLength) */ + +} ACPI_CSRT_SHARED_INFO; + +/* Resource Descriptor subtable */ + +typedef struct acpi_csrt_descriptor +{ + UINT32 Length; + UINT16 Type; + UINT16 Subtype; + UINT32 Uid; + + /* Resource-specific information immediately follows */ + +} ACPI_CSRT_DESCRIPTOR; + + +/* Resource Types */ + +#define ACPI_CSRT_TYPE_INTERRUPT 0x0001 +#define ACPI_CSRT_TYPE_TIMER 0x0002 +#define ACPI_CSRT_TYPE_DMA 0x0003 + +/* Resource Subtypes */ + +#define ACPI_CSRT_XRUPT_LINE 0x0000 +#define ACPI_CSRT_XRUPT_CONTROLLER 0x0001 +#define ACPI_CSRT_TIMER 0x0000 +#define ACPI_CSRT_DMA_CHANNEL 0x0000 +#define ACPI_CSRT_DMA_CONTROLLER 0x0001 + + +/******************************************************************************* + * + * DBG2 - Debug Port Table 2 + * Version 0 (Both main table and subtables) + * + * Conforms to "Microsoft Debug Port Table 2 (DBG2)", December 10, 2015 + * + ******************************************************************************/ + +typedef struct acpi_table_dbg2 +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 InfoOffset; + UINT32 InfoCount; + +} ACPI_TABLE_DBG2; + + +typedef struct acpi_dbg2_header +{ + UINT32 InfoOffset; + UINT32 InfoCount; + +} ACPI_DBG2_HEADER; + + +/* Debug Device Information Subtable */ + +typedef struct acpi_dbg2_device +{ + UINT8 Revision; + UINT16 Length; + UINT8 RegisterCount; /* Number of BaseAddress registers */ + UINT16 NamepathLength; + UINT16 NamepathOffset; + UINT16 OemDataLength; + UINT16 OemDataOffset; + UINT16 PortType; + UINT16 PortSubtype; + UINT16 Reserved; + UINT16 BaseAddressOffset; + UINT16 AddressSizeOffset; + /* + * Data that follows: + * BaseAddress (required) - Each in 12-byte Generic Address Structure format. + * AddressSize (required) - Array of UINT32 sizes corresponding to each BaseAddress register. + * Namepath (required) - Null terminated string. Single dot if not supported. + * OemData (optional) - Length is OemDataLength. + */ +} ACPI_DBG2_DEVICE; + +/* Types for PortType field above */ + +#define ACPI_DBG2_SERIAL_PORT 0x8000 +#define ACPI_DBG2_1394_PORT 0x8001 +#define ACPI_DBG2_USB_PORT 0x8002 +#define ACPI_DBG2_NET_PORT 0x8003 + +/* Subtypes for PortSubtype field above */ + +#define ACPI_DBG2_16550_COMPATIBLE 0x0000 +#define ACPI_DBG2_16550_SUBSET 0x0001 +#define ACPI_DBG2_ARM_PL011 0x0003 +#define ACPI_DBG2_ARM_SBSA_32BIT 0x000D +#define ACPI_DBG2_ARM_SBSA_GENERIC 0x000E +#define ACPI_DBG2_ARM_DCC 0x000F +#define ACPI_DBG2_BCM2835 0x0010 + +#define ACPI_DBG2_1394_STANDARD 0x0000 + +#define ACPI_DBG2_USB_XHCI 0x0000 +#define ACPI_DBG2_USB_EHCI 0x0001 + + +/******************************************************************************* + * + * DBGP - Debug Port table + * Version 1 + * + * Conforms to the "Debug Port Specification", Version 1.00, 2/9/2000 + * + ******************************************************************************/ + +typedef struct acpi_table_dbgp +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 Type; /* 0=full 16550, 1=subset of 16550 */ + UINT8 Reserved[3]; + ACPI_GENERIC_ADDRESS DebugPort; + +} ACPI_TABLE_DBGP; + + +/******************************************************************************* + * + * DMAR - DMA Remapping table + * Version 1 + * + * Conforms to "Intel Virtualization Technology for Directed I/O", + * Version 2.3, October 2014 + * + ******************************************************************************/ + +typedef struct acpi_table_dmar +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 Width; /* Host Address Width */ + UINT8 Flags; + UINT8 Reserved[10]; + +} ACPI_TABLE_DMAR; + +/* Masks for Flags field above */ + +#define ACPI_DMAR_INTR_REMAP (1) +#define ACPI_DMAR_X2APIC_OPT_OUT (1<<1) +#define ACPI_DMAR_X2APIC_MODE (1<<2) + + +/* DMAR subtable header */ + +typedef struct acpi_dmar_header +{ + UINT16 Type; + UINT16 Length; + +} ACPI_DMAR_HEADER; + +/* Values for subtable type in ACPI_DMAR_HEADER */ + +enum AcpiDmarType +{ + ACPI_DMAR_TYPE_HARDWARE_UNIT = 0, + ACPI_DMAR_TYPE_RESERVED_MEMORY = 1, + ACPI_DMAR_TYPE_ROOT_ATS = 2, + ACPI_DMAR_TYPE_HARDWARE_AFFINITY = 3, + ACPI_DMAR_TYPE_NAMESPACE = 4, + ACPI_DMAR_TYPE_RESERVED = 5 /* 5 and greater are reserved */ +}; + + +/* DMAR Device Scope structure */ + +typedef struct acpi_dmar_device_scope +{ + UINT8 EntryType; + UINT8 Length; + UINT16 Reserved; + UINT8 EnumerationId; + UINT8 Bus; + +} ACPI_DMAR_DEVICE_SCOPE; + +/* Values for EntryType in ACPI_DMAR_DEVICE_SCOPE - device types */ + +enum AcpiDmarScopeType +{ + ACPI_DMAR_SCOPE_TYPE_NOT_USED = 0, + ACPI_DMAR_SCOPE_TYPE_ENDPOINT = 1, + ACPI_DMAR_SCOPE_TYPE_BRIDGE = 2, + ACPI_DMAR_SCOPE_TYPE_IOAPIC = 3, + ACPI_DMAR_SCOPE_TYPE_HPET = 4, + ACPI_DMAR_SCOPE_TYPE_NAMESPACE = 5, + ACPI_DMAR_SCOPE_TYPE_RESERVED = 6 /* 6 and greater are reserved */ +}; + +typedef struct acpi_dmar_pci_path +{ + UINT8 Device; + UINT8 Function; + +} ACPI_DMAR_PCI_PATH; + + +/* + * DMAR Subtables, correspond to Type in ACPI_DMAR_HEADER + */ + +/* 0: Hardware Unit Definition */ + +typedef struct acpi_dmar_hardware_unit +{ + ACPI_DMAR_HEADER Header; + UINT8 Flags; + UINT8 Reserved; + UINT16 Segment; + UINT64 Address; /* Register Base Address */ + +} ACPI_DMAR_HARDWARE_UNIT; + +/* Masks for Flags field above */ + +#define ACPI_DMAR_INCLUDE_ALL (1) + + +/* 1: Reserved Memory Defininition */ + +typedef struct acpi_dmar_reserved_memory +{ + ACPI_DMAR_HEADER Header; + UINT16 Reserved; + UINT16 Segment; + UINT64 BaseAddress; /* 4K aligned base address */ + UINT64 EndAddress; /* 4K aligned limit address */ + +} ACPI_DMAR_RESERVED_MEMORY; + +/* Masks for Flags field above */ + +#define ACPI_DMAR_ALLOW_ALL (1) + + +/* 2: Root Port ATS Capability Reporting Structure */ + +typedef struct acpi_dmar_atsr +{ + ACPI_DMAR_HEADER Header; + UINT8 Flags; + UINT8 Reserved; + UINT16 Segment; + +} ACPI_DMAR_ATSR; + +/* Masks for Flags field above */ + +#define ACPI_DMAR_ALL_PORTS (1) + + +/* 3: Remapping Hardware Static Affinity Structure */ + +typedef struct acpi_dmar_rhsa +{ + ACPI_DMAR_HEADER Header; + UINT32 Reserved; + UINT64 BaseAddress; + UINT32 ProximityDomain; + +} ACPI_DMAR_RHSA; + + +/* 4: ACPI Namespace Device Declaration Structure */ + +typedef struct acpi_dmar_andd +{ + ACPI_DMAR_HEADER Header; + UINT8 Reserved[3]; + UINT8 DeviceNumber; + char DeviceName[1]; + +} ACPI_DMAR_ANDD; + + +/******************************************************************************* + * + * DRTM - Dynamic Root of Trust for Measurement table + * Conforms to "TCG D-RTM Architecture" June 17 2013, Version 1.0.0 + * Table version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_drtm +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT64 EntryBaseAddress; + UINT64 EntryLength; + UINT32 EntryAddress32; + UINT64 EntryAddress64; + UINT64 ExitAddress; + UINT64 LogAreaAddress; + UINT32 LogAreaLength; + UINT64 ArchDependentAddress; + UINT32 Flags; + +} ACPI_TABLE_DRTM; + +/* Flag Definitions for above */ + +#define ACPI_DRTM_ACCESS_ALLOWED (1) +#define ACPI_DRTM_ENABLE_GAP_CODE (1<<1) +#define ACPI_DRTM_INCOMPLETE_MEASUREMENTS (1<<2) +#define ACPI_DRTM_AUTHORITY_ORDER (1<<3) + + +/* 1) Validated Tables List (64-bit addresses) */ + +typedef struct acpi_drtm_vtable_list +{ + UINT32 ValidatedTableCount; + UINT64 ValidatedTables[1]; + +} ACPI_DRTM_VTABLE_LIST; + +/* 2) Resources List (of Resource Descriptors) */ + +/* Resource Descriptor */ + +typedef struct acpi_drtm_resource +{ + UINT8 Size[7]; + UINT8 Type; + UINT64 Address; + +} ACPI_DRTM_RESOURCE; + +typedef struct acpi_drtm_resource_list +{ + UINT32 ResourceCount; + ACPI_DRTM_RESOURCE Resources[1]; + +} ACPI_DRTM_RESOURCE_LIST; + +/* 3) Platform-specific Identifiers List */ + +typedef struct acpi_drtm_dps_id +{ + UINT32 DpsIdLength; + UINT8 DpsId[16]; + +} ACPI_DRTM_DPS_ID; + + +/******************************************************************************* + * + * ECDT - Embedded Controller Boot Resources Table + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_ecdt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + ACPI_GENERIC_ADDRESS Control; /* Address of EC command/status register */ + ACPI_GENERIC_ADDRESS Data; /* Address of EC data register */ + UINT32 Uid; /* Unique ID - must be same as the EC _UID method */ + UINT8 Gpe; /* The GPE for the EC */ + UINT8 Id[1]; /* Full namepath of the EC in the ACPI namespace */ + +} ACPI_TABLE_ECDT; + + +/******************************************************************************* + * + * EINJ - Error Injection Table (ACPI 4.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_einj +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 HeaderLength; + UINT8 Flags; + UINT8 Reserved[3]; + UINT32 Entries; + +} ACPI_TABLE_EINJ; + + +/* EINJ Injection Instruction Entries (actions) */ + +typedef struct acpi_einj_entry +{ + ACPI_WHEA_HEADER WheaHeader; /* Common header for WHEA tables */ + +} ACPI_EINJ_ENTRY; + +/* Masks for Flags field above */ + +#define ACPI_EINJ_PRESERVE (1) + +/* Values for Action field above */ + +enum AcpiEinjActions +{ + ACPI_EINJ_BEGIN_OPERATION = 0, + ACPI_EINJ_GET_TRIGGER_TABLE = 1, + ACPI_EINJ_SET_ERROR_TYPE = 2, + ACPI_EINJ_GET_ERROR_TYPE = 3, + ACPI_EINJ_END_OPERATION = 4, + ACPI_EINJ_EXECUTE_OPERATION = 5, + ACPI_EINJ_CHECK_BUSY_STATUS = 6, + ACPI_EINJ_GET_COMMAND_STATUS = 7, + ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS = 8, + ACPI_EINJ_GET_EXECUTE_TIMINGS = 9, + ACPI_EINJ_ACTION_RESERVED = 10, /* 10 and greater are reserved */ + ACPI_EINJ_TRIGGER_ERROR = 0xFF /* Except for this value */ +}; + +/* Values for Instruction field above */ + +enum AcpiEinjInstructions +{ + ACPI_EINJ_READ_REGISTER = 0, + ACPI_EINJ_READ_REGISTER_VALUE = 1, + ACPI_EINJ_WRITE_REGISTER = 2, + ACPI_EINJ_WRITE_REGISTER_VALUE = 3, + ACPI_EINJ_NOOP = 4, + ACPI_EINJ_FLUSH_CACHELINE = 5, + ACPI_EINJ_INSTRUCTION_RESERVED = 6 /* 6 and greater are reserved */ +}; + +typedef struct acpi_einj_error_type_with_addr +{ + UINT32 ErrorType; + UINT32 VendorStructOffset; + UINT32 Flags; + UINT32 ApicId; + UINT64 Address; + UINT64 Range; + UINT32 PcieId; + +} ACPI_EINJ_ERROR_TYPE_WITH_ADDR; + +typedef struct acpi_einj_vendor +{ + UINT32 Length; + UINT32 PcieId; + UINT16 VendorId; + UINT16 DeviceId; + UINT8 RevisionId; + UINT8 Reserved[3]; + +} ACPI_EINJ_VENDOR; + + +/* EINJ Trigger Error Action Table */ + +typedef struct acpi_einj_trigger +{ + UINT32 HeaderSize; + UINT32 Revision; + UINT32 TableSize; + UINT32 EntryCount; + +} ACPI_EINJ_TRIGGER; + +/* Command status return values */ + +enum AcpiEinjCommandStatus +{ + ACPI_EINJ_SUCCESS = 0, + ACPI_EINJ_FAILURE = 1, + ACPI_EINJ_INVALID_ACCESS = 2, + ACPI_EINJ_STATUS_RESERVED = 3 /* 3 and greater are reserved */ +}; + + +/* Error types returned from ACPI_EINJ_GET_ERROR_TYPE (bitfield) */ + +#define ACPI_EINJ_PROCESSOR_CORRECTABLE (1) +#define ACPI_EINJ_PROCESSOR_UNCORRECTABLE (1<<1) +#define ACPI_EINJ_PROCESSOR_FATAL (1<<2) +#define ACPI_EINJ_MEMORY_CORRECTABLE (1<<3) +#define ACPI_EINJ_MEMORY_UNCORRECTABLE (1<<4) +#define ACPI_EINJ_MEMORY_FATAL (1<<5) +#define ACPI_EINJ_PCIX_CORRECTABLE (1<<6) +#define ACPI_EINJ_PCIX_UNCORRECTABLE (1<<7) +#define ACPI_EINJ_PCIX_FATAL (1<<8) +#define ACPI_EINJ_PLATFORM_CORRECTABLE (1<<9) +#define ACPI_EINJ_PLATFORM_UNCORRECTABLE (1<<10) +#define ACPI_EINJ_PLATFORM_FATAL (1<<11) +#define ACPI_EINJ_VENDOR_DEFINED (1<<31) + + +/******************************************************************************* + * + * ERST - Error Record Serialization Table (ACPI 4.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_erst +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 HeaderLength; + UINT32 Reserved; + UINT32 Entries; + +} ACPI_TABLE_ERST; + + +/* ERST Serialization Entries (actions) */ + +typedef struct acpi_erst_entry +{ + ACPI_WHEA_HEADER WheaHeader; /* Common header for WHEA tables */ + +} ACPI_ERST_ENTRY; + +/* Masks for Flags field above */ + +#define ACPI_ERST_PRESERVE (1) + +/* Values for Action field above */ + +enum AcpiErstActions +{ + ACPI_ERST_BEGIN_WRITE = 0, + ACPI_ERST_BEGIN_READ = 1, + ACPI_ERST_BEGIN_CLEAR = 2, + ACPI_ERST_END = 3, + ACPI_ERST_SET_RECORD_OFFSET = 4, + ACPI_ERST_EXECUTE_OPERATION = 5, + ACPI_ERST_CHECK_BUSY_STATUS = 6, + ACPI_ERST_GET_COMMAND_STATUS = 7, + ACPI_ERST_GET_RECORD_ID = 8, + ACPI_ERST_SET_RECORD_ID = 9, + ACPI_ERST_GET_RECORD_COUNT = 10, + ACPI_ERST_BEGIN_DUMMY_WRIITE = 11, + ACPI_ERST_NOT_USED = 12, + ACPI_ERST_GET_ERROR_RANGE = 13, + ACPI_ERST_GET_ERROR_LENGTH = 14, + ACPI_ERST_GET_ERROR_ATTRIBUTES = 15, + ACPI_ERST_EXECUTE_TIMINGS = 16, + ACPI_ERST_ACTION_RESERVED = 17 /* 17 and greater are reserved */ +}; + +/* Values for Instruction field above */ + +enum AcpiErstInstructions +{ + ACPI_ERST_READ_REGISTER = 0, + ACPI_ERST_READ_REGISTER_VALUE = 1, + ACPI_ERST_WRITE_REGISTER = 2, + ACPI_ERST_WRITE_REGISTER_VALUE = 3, + ACPI_ERST_NOOP = 4, + ACPI_ERST_LOAD_VAR1 = 5, + ACPI_ERST_LOAD_VAR2 = 6, + ACPI_ERST_STORE_VAR1 = 7, + ACPI_ERST_ADD = 8, + ACPI_ERST_SUBTRACT = 9, + ACPI_ERST_ADD_VALUE = 10, + ACPI_ERST_SUBTRACT_VALUE = 11, + ACPI_ERST_STALL = 12, + ACPI_ERST_STALL_WHILE_TRUE = 13, + ACPI_ERST_SKIP_NEXT_IF_TRUE = 14, + ACPI_ERST_GOTO = 15, + ACPI_ERST_SET_SRC_ADDRESS_BASE = 16, + ACPI_ERST_SET_DST_ADDRESS_BASE = 17, + ACPI_ERST_MOVE_DATA = 18, + ACPI_ERST_INSTRUCTION_RESERVED = 19 /* 19 and greater are reserved */ +}; + +/* Command status return values */ + +enum AcpiErstCommandStatus +{ + ACPI_ERST_SUCESS = 0, + ACPI_ERST_NO_SPACE = 1, + ACPI_ERST_NOT_AVAILABLE = 2, + ACPI_ERST_FAILURE = 3, + ACPI_ERST_RECORD_EMPTY = 4, + ACPI_ERST_NOT_FOUND = 5, + ACPI_ERST_STATUS_RESERVED = 6 /* 6 and greater are reserved */ +}; + + +/* Error Record Serialization Information */ + +typedef struct acpi_erst_info +{ + UINT16 Signature; /* Should be "ER" */ + UINT8 Data[48]; + +} ACPI_ERST_INFO; + + +/******************************************************************************* + * + * FPDT - Firmware Performance Data Table (ACPI 5.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_fpdt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_FPDT; + + +/* FPDT subtable header (Performance Record Structure) */ + +typedef struct acpi_fpdt_header +{ + UINT16 Type; + UINT8 Length; + UINT8 Revision; + +} ACPI_FPDT_HEADER; + +/* Values for Type field above */ + +enum AcpiFpdtType +{ + ACPI_FPDT_TYPE_BOOT = 0, + ACPI_FPDT_TYPE_S3PERF = 1 +}; + + +/* + * FPDT subtables + */ + +/* 0: Firmware Basic Boot Performance Record */ + +typedef struct acpi_fpdt_boot_pointer +{ + ACPI_FPDT_HEADER Header; + UINT8 Reserved[4]; + UINT64 Address; + +} ACPI_FPDT_BOOT_POINTER; + + +/* 1: S3 Performance Table Pointer Record */ + +typedef struct acpi_fpdt_s3pt_pointer +{ + ACPI_FPDT_HEADER Header; + UINT8 Reserved[4]; + UINT64 Address; + +} ACPI_FPDT_S3PT_POINTER; + + +/* + * S3PT - S3 Performance Table. This table is pointed to by the + * S3 Pointer Record above. + */ +typedef struct acpi_table_s3pt +{ + UINT8 Signature[4]; /* "S3PT" */ + UINT32 Length; + +} ACPI_TABLE_S3PT; + + +/* + * S3PT Subtables (Not part of the actual FPDT) + */ + +/* Values for Type field in S3PT header */ + +enum AcpiS3ptType +{ + ACPI_S3PT_TYPE_RESUME = 0, + ACPI_S3PT_TYPE_SUSPEND = 1, + ACPI_FPDT_BOOT_PERFORMANCE = 2 +}; + +typedef struct acpi_s3pt_resume +{ + ACPI_FPDT_HEADER Header; + UINT32 ResumeCount; + UINT64 FullResume; + UINT64 AverageResume; + +} ACPI_S3PT_RESUME; + +typedef struct acpi_s3pt_suspend +{ + ACPI_FPDT_HEADER Header; + UINT64 SuspendStart; + UINT64 SuspendEnd; + +} ACPI_S3PT_SUSPEND; + + +/* + * FPDT Boot Performance Record (Not part of the actual FPDT) + */ +typedef struct acpi_fpdt_boot +{ + ACPI_FPDT_HEADER Header; + UINT8 Reserved[4]; + UINT64 ResetEnd; + UINT64 LoadStart; + UINT64 StartupStart; + UINT64 ExitServicesEntry; + UINT64 ExitServicesExit; + +} ACPI_FPDT_BOOT; + + +/******************************************************************************* + * + * GTDT - Generic Timer Description Table (ACPI 5.1) + * Version 2 + * + ******************************************************************************/ + +typedef struct acpi_table_gtdt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT64 CounterBlockAddresss; + UINT32 Reserved; + UINT32 SecureEl1Interrupt; + UINT32 SecureEl1Flags; + UINT32 NonSecureEl1Interrupt; + UINT32 NonSecureEl1Flags; + UINT32 VirtualTimerInterrupt; + UINT32 VirtualTimerFlags; + UINT32 NonSecureEl2Interrupt; + UINT32 NonSecureEl2Flags; + UINT64 CounterReadBlockAddress; + UINT32 PlatformTimerCount; + UINT32 PlatformTimerOffset; + +} ACPI_TABLE_GTDT; + +/* Flag Definitions: Timer Block Physical Timers and Virtual timers */ + +#define ACPI_GTDT_INTERRUPT_MODE (1) +#define ACPI_GTDT_INTERRUPT_POLARITY (1<<1) +#define ACPI_GTDT_ALWAYS_ON (1<<2) + + +/* Common GTDT subtable header */ + +typedef struct acpi_gtdt_header +{ + UINT8 Type; + UINT16 Length; + +} ACPI_GTDT_HEADER; + +/* Values for GTDT subtable type above */ + +enum AcpiGtdtType +{ + ACPI_GTDT_TYPE_TIMER_BLOCK = 0, + ACPI_GTDT_TYPE_WATCHDOG = 1, + ACPI_GTDT_TYPE_RESERVED = 2 /* 2 and greater are reserved */ +}; + + +/* GTDT Subtables, correspond to Type in acpi_gtdt_header */ + +/* 0: Generic Timer Block */ + +typedef struct acpi_gtdt_timer_block +{ + ACPI_GTDT_HEADER Header; + UINT8 Reserved; + UINT64 BlockAddress; + UINT32 TimerCount; + UINT32 TimerOffset; + +} ACPI_GTDT_TIMER_BLOCK; + +/* Timer Sub-Structure, one per timer */ + +typedef struct acpi_gtdt_timer_entry +{ + UINT8 FrameNumber; + UINT8 Reserved[3]; + UINT64 BaseAddress; + UINT64 El0BaseAddress; + UINT32 TimerInterrupt; + UINT32 TimerFlags; + UINT32 VirtualTimerInterrupt; + UINT32 VirtualTimerFlags; + UINT32 CommonFlags; + +} ACPI_GTDT_TIMER_ENTRY; + +/* Flag Definitions: TimerFlags and VirtualTimerFlags above */ + +#define ACPI_GTDT_GT_IRQ_MODE (1) +#define ACPI_GTDT_GT_IRQ_POLARITY (1<<1) + +/* Flag Definitions: CommonFlags above */ + +#define ACPI_GTDT_GT_IS_SECURE_TIMER (1) +#define ACPI_GTDT_GT_ALWAYS_ON (1<<1) + + +/* 1: SBSA Generic Watchdog Structure */ + +typedef struct acpi_gtdt_watchdog +{ + ACPI_GTDT_HEADER Header; + UINT8 Reserved; + UINT64 RefreshFrameAddress; + UINT64 ControlFrameAddress; + UINT32 TimerInterrupt; + UINT32 TimerFlags; + +} ACPI_GTDT_WATCHDOG; + +/* Flag Definitions: TimerFlags above */ + +#define ACPI_GTDT_WATCHDOG_IRQ_MODE (1) +#define ACPI_GTDT_WATCHDOG_IRQ_POLARITY (1<<1) +#define ACPI_GTDT_WATCHDOG_SECURE (1<<2) + + +/******************************************************************************* + * + * HEST - Hardware Error Source Table (ACPI 4.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_hest +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 ErrorSourceCount; + +} ACPI_TABLE_HEST; + + +/* HEST subtable header */ + +typedef struct acpi_hest_header +{ + UINT16 Type; + UINT16 SourceId; + +} ACPI_HEST_HEADER; + + +/* Values for Type field above for subtables */ + +enum AcpiHestTypes +{ + ACPI_HEST_TYPE_IA32_CHECK = 0, + ACPI_HEST_TYPE_IA32_CORRECTED_CHECK = 1, + ACPI_HEST_TYPE_IA32_NMI = 2, + ACPI_HEST_TYPE_NOT_USED3 = 3, + ACPI_HEST_TYPE_NOT_USED4 = 4, + ACPI_HEST_TYPE_NOT_USED5 = 5, + ACPI_HEST_TYPE_AER_ROOT_PORT = 6, + ACPI_HEST_TYPE_AER_ENDPOINT = 7, + ACPI_HEST_TYPE_AER_BRIDGE = 8, + ACPI_HEST_TYPE_GENERIC_ERROR = 9, + ACPI_HEST_TYPE_GENERIC_ERROR_V2 = 10, + ACPI_HEST_TYPE_IA32_DEFERRED_CHECK = 11, + ACPI_HEST_TYPE_RESERVED = 12 /* 12 and greater are reserved */ +}; + + +/* + * HEST substructures contained in subtables + */ + +/* + * IA32 Error Bank(s) - Follows the ACPI_HEST_IA_MACHINE_CHECK and + * ACPI_HEST_IA_CORRECTED structures. + */ +typedef struct acpi_hest_ia_error_bank +{ + UINT8 BankNumber; + UINT8 ClearStatusOnInit; + UINT8 StatusFormat; + UINT8 Reserved; + UINT32 ControlRegister; + UINT64 ControlData; + UINT32 StatusRegister; + UINT32 AddressRegister; + UINT32 MiscRegister; + +} ACPI_HEST_IA_ERROR_BANK; + + +/* Common HEST sub-structure for PCI/AER structures below (6,7,8) */ + +typedef struct acpi_hest_aer_common +{ + UINT16 Reserved1; + UINT8 Flags; + UINT8 Enabled; + UINT32 RecordsToPreallocate; + UINT32 MaxSectionsPerRecord; + UINT32 Bus; /* Bus and Segment numbers */ + UINT16 Device; + UINT16 Function; + UINT16 DeviceControl; + UINT16 Reserved2; + UINT32 UncorrectableMask; + UINT32 UncorrectableSeverity; + UINT32 CorrectableMask; + UINT32 AdvancedCapabilities; + +} ACPI_HEST_AER_COMMON; + +/* Masks for HEST Flags fields */ + +#define ACPI_HEST_FIRMWARE_FIRST (1) +#define ACPI_HEST_GLOBAL (1<<1) +#define ACPI_HEST_GHES_ASSIST (1<<2) + +/* + * Macros to access the bus/segment numbers in Bus field above: + * Bus number is encoded in bits 7:0 + * Segment number is encoded in bits 23:8 + */ +#define ACPI_HEST_BUS(Bus) ((Bus) & 0xFF) +#define ACPI_HEST_SEGMENT(Bus) (((Bus) >> 8) & 0xFFFF) + + +/* Hardware Error Notification */ + +typedef struct acpi_hest_notify +{ + UINT8 Type; + UINT8 Length; + UINT16 ConfigWriteEnable; + UINT32 PollInterval; + UINT32 Vector; + UINT32 PollingThresholdValue; + UINT32 PollingThresholdWindow; + UINT32 ErrorThresholdValue; + UINT32 ErrorThresholdWindow; + +} ACPI_HEST_NOTIFY; + +/* Values for Notify Type field above */ + +enum AcpiHestNotifyTypes +{ + ACPI_HEST_NOTIFY_POLLED = 0, + ACPI_HEST_NOTIFY_EXTERNAL = 1, + ACPI_HEST_NOTIFY_LOCAL = 2, + ACPI_HEST_NOTIFY_SCI = 3, + ACPI_HEST_NOTIFY_NMI = 4, + ACPI_HEST_NOTIFY_CMCI = 5, /* ACPI 5.0 */ + ACPI_HEST_NOTIFY_MCE = 6, /* ACPI 5.0 */ + ACPI_HEST_NOTIFY_GPIO = 7, /* ACPI 6.0 */ + ACPI_HEST_NOTIFY_SEA = 8, /* ACPI 6.1 */ + ACPI_HEST_NOTIFY_SEI = 9, /* ACPI 6.1 */ + ACPI_HEST_NOTIFY_GSIV = 10, /* ACPI 6.1 */ + ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED = 11, /* ACPI 6.2 */ + ACPI_HEST_NOTIFY_RESERVED = 12 /* 12 and greater are reserved */ +}; + +/* Values for ConfigWriteEnable bitfield above */ + +#define ACPI_HEST_TYPE (1) +#define ACPI_HEST_POLL_INTERVAL (1<<1) +#define ACPI_HEST_POLL_THRESHOLD_VALUE (1<<2) +#define ACPI_HEST_POLL_THRESHOLD_WINDOW (1<<3) +#define ACPI_HEST_ERR_THRESHOLD_VALUE (1<<4) +#define ACPI_HEST_ERR_THRESHOLD_WINDOW (1<<5) + + +/* + * HEST subtables + */ + +/* 0: IA32 Machine Check Exception */ + +typedef struct acpi_hest_ia_machine_check +{ + ACPI_HEST_HEADER Header; + UINT16 Reserved1; + UINT8 Flags; /* See flags ACPI_HEST_GLOBAL, etc. above */ + UINT8 Enabled; + UINT32 RecordsToPreallocate; + UINT32 MaxSectionsPerRecord; + UINT64 GlobalCapabilityData; + UINT64 GlobalControlData; + UINT8 NumHardwareBanks; + UINT8 Reserved3[7]; + +} ACPI_HEST_IA_MACHINE_CHECK; + + +/* 1: IA32 Corrected Machine Check */ + +typedef struct acpi_hest_ia_corrected +{ + ACPI_HEST_HEADER Header; + UINT16 Reserved1; + UINT8 Flags; /* See flags ACPI_HEST_GLOBAL, etc. above */ + UINT8 Enabled; + UINT32 RecordsToPreallocate; + UINT32 MaxSectionsPerRecord; + ACPI_HEST_NOTIFY Notify; + UINT8 NumHardwareBanks; + UINT8 Reserved2[3]; + +} ACPI_HEST_IA_CORRECTED; + + +/* 2: IA32 Non-Maskable Interrupt */ + +typedef struct acpi_hest_ia_nmi +{ + ACPI_HEST_HEADER Header; + UINT32 Reserved; + UINT32 RecordsToPreallocate; + UINT32 MaxSectionsPerRecord; + UINT32 MaxRawDataLength; + +} ACPI_HEST_IA_NMI; + + +/* 3,4,5: Not used */ + +/* 6: PCI Express Root Port AER */ + +typedef struct acpi_hest_aer_root +{ + ACPI_HEST_HEADER Header; + ACPI_HEST_AER_COMMON Aer; + UINT32 RootErrorCommand; + +} ACPI_HEST_AER_ROOT; + + +/* 7: PCI Express AER (AER Endpoint) */ + +typedef struct acpi_hest_aer +{ + ACPI_HEST_HEADER Header; + ACPI_HEST_AER_COMMON Aer; + +} ACPI_HEST_AER; + + +/* 8: PCI Express/PCI-X Bridge AER */ + +typedef struct acpi_hest_aer_bridge +{ + ACPI_HEST_HEADER Header; + ACPI_HEST_AER_COMMON Aer; + UINT32 UncorrectableMask2; + UINT32 UncorrectableSeverity2; + UINT32 AdvancedCapabilities2; + +} ACPI_HEST_AER_BRIDGE; + + +/* 9: Generic Hardware Error Source */ + +typedef struct acpi_hest_generic +{ + ACPI_HEST_HEADER Header; + UINT16 RelatedSourceId; + UINT8 Reserved; + UINT8 Enabled; + UINT32 RecordsToPreallocate; + UINT32 MaxSectionsPerRecord; + UINT32 MaxRawDataLength; + ACPI_GENERIC_ADDRESS ErrorStatusAddress; + ACPI_HEST_NOTIFY Notify; + UINT32 ErrorBlockLength; + +} ACPI_HEST_GENERIC; + + +/* 10: Generic Hardware Error Source, version 2 */ + +typedef struct acpi_hest_generic_v2 +{ + ACPI_HEST_HEADER Header; + UINT16 RelatedSourceId; + UINT8 Reserved; + UINT8 Enabled; + UINT32 RecordsToPreallocate; + UINT32 MaxSectionsPerRecord; + UINT32 MaxRawDataLength; + ACPI_GENERIC_ADDRESS ErrorStatusAddress; + ACPI_HEST_NOTIFY Notify; + UINT32 ErrorBlockLength; + ACPI_GENERIC_ADDRESS ReadAckRegister; + UINT64 ReadAckPreserve; + UINT64 ReadAckWrite; + +} ACPI_HEST_GENERIC_V2; + + +/* Generic Error Status block */ + +typedef struct acpi_hest_generic_status +{ + UINT32 BlockStatus; + UINT32 RawDataOffset; + UINT32 RawDataLength; + UINT32 DataLength; + UINT32 ErrorSeverity; + +} ACPI_HEST_GENERIC_STATUS; + +/* Values for BlockStatus flags above */ + +#define ACPI_HEST_UNCORRECTABLE (1) +#define ACPI_HEST_CORRECTABLE (1<<1) +#define ACPI_HEST_MULTIPLE_UNCORRECTABLE (1<<2) +#define ACPI_HEST_MULTIPLE_CORRECTABLE (1<<3) +#define ACPI_HEST_ERROR_ENTRY_COUNT (0xFF<<4) /* 8 bits, error count */ + + +/* Generic Error Data entry */ + +typedef struct acpi_hest_generic_data +{ + UINT8 SectionType[16]; + UINT32 ErrorSeverity; + UINT16 Revision; + UINT8 ValidationBits; + UINT8 Flags; + UINT32 ErrorDataLength; + UINT8 FruId[16]; + UINT8 FruText[20]; + +} ACPI_HEST_GENERIC_DATA; + +/* Extension for revision 0x0300 */ + +typedef struct acpi_hest_generic_data_v300 +{ + UINT8 SectionType[16]; + UINT32 ErrorSeverity; + UINT16 Revision; + UINT8 ValidationBits; + UINT8 Flags; + UINT32 ErrorDataLength; + UINT8 FruId[16]; + UINT8 FruText[20]; + UINT64 TimeStamp; + +} ACPI_HEST_GENERIC_DATA_V300; + +/* Values for ErrorSeverity above */ + +#define ACPI_HEST_GEN_ERROR_RECOVERABLE 0 +#define ACPI_HEST_GEN_ERROR_FATAL 1 +#define ACPI_HEST_GEN_ERROR_CORRECTED 2 +#define ACPI_HEST_GEN_ERROR_NONE 3 + +/* Flags for ValidationBits above */ + +#define ACPI_HEST_GEN_VALID_FRU_ID (1) +#define ACPI_HEST_GEN_VALID_FRU_STRING (1<<1) +#define ACPI_HEST_GEN_VALID_TIMESTAMP (1<<2) + + +/* 11: IA32 Deferred Machine Check Exception (ACPI 6.2) */ + +typedef struct acpi_hest_ia_deferred_check +{ + ACPI_HEST_HEADER Header; + UINT16 Reserved1; + UINT8 Flags; /* See flags ACPI_HEST_GLOBAL, etc. above */ + UINT8 Enabled; + UINT32 RecordsToPreallocate; + UINT32 MaxSectionsPerRecord; + ACPI_HEST_NOTIFY Notify; + UINT8 NumHardwareBanks; + UINT8 Reserved2[3]; + +} ACPI_HEST_IA_DEFERRED_CHECK; + + +/******************************************************************************* + * + * HMAT - Heterogeneous Memory Attributes Table (ACPI 6.2) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_hmat +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Reserved; + +} ACPI_TABLE_HMAT; + + +/* Values for HMAT structure types */ + +enum AcpiHmatType +{ + ACPI_HMAT_TYPE_ADDRESS_RANGE = 0, /* Memory subystem address range */ + ACPI_HMAT_TYPE_LOCALITY = 1, /* System locality latency and bandwidth information */ + ACPI_HMAT_TYPE_CACHE = 2, /* Memory side cache information */ + ACPI_HMAT_TYPE_RESERVED = 3 /* 3 and greater are reserved */ +}; + +typedef struct acpi_hmat_structure +{ + UINT16 Type; + UINT16 Reserved; + UINT32 Length; + +} ACPI_HMAT_STRUCTURE; + + +/* + * HMAT Structures, correspond to Type in ACPI_HMAT_STRUCTURE + */ + +/* 0: Memory subystem address range */ + +typedef struct acpi_hmat_address_range +{ + ACPI_HMAT_STRUCTURE Header; + UINT16 Flags; + UINT16 Reserved1; + UINT32 ProcessorPD; /* Processor proximity domain */ + UINT32 MemoryPD; /* Memory proximity domain */ + UINT32 Reserved2; + UINT64 PhysicalAddressBase; /* Physical address range base */ + UINT64 PhysicalAddressLength; /* Physical address range length */ + +} ACPI_HMAT_ADDRESS_RANGE; + +/* Masks for Flags field above */ + +#define ACPI_HMAT_PROCESSOR_PD_VALID (1) /* 1: ProcessorPD field is valid */ +#define ACPI_HMAT_MEMORY_PD_VALID (1<<1) /* 1: MemoryPD field is valid */ +#define ACPI_HMAT_RESERVATION_HINT (1<<2) /* 1: Reservation hint */ + + +/* 1: System locality latency and bandwidth information */ + +typedef struct acpi_hmat_locality +{ + ACPI_HMAT_STRUCTURE Header; + UINT8 Flags; + UINT8 DataType; + UINT16 Reserved1; + UINT32 NumberOfInitiatorPDs; + UINT32 NumberOfTargetPDs; + UINT32 Reserved2; + UINT64 EntryBaseUnit; + +} ACPI_HMAT_LOCALITY; + +/* Masks for Flags field above */ + +#define ACPI_HMAT_MEMORY_HIERARCHY (0x0F) + +/* Values for Memory Hierarchy flag */ + +#define ACPI_HMAT_MEMORY 0 +#define ACPI_HMAT_LAST_LEVEL_CACHE 1 +#define ACPI_HMAT_1ST_LEVEL_CACHE 2 +#define ACPI_HMAT_2ND_LEVEL_CACHE 3 +#define ACPI_HMAT_3RD_LEVEL_CACHE 4 + +/* Values for DataType field above */ + +#define ACPI_HMAT_ACCESS_LATENCY 0 +#define ACPI_HMAT_READ_LATENCY 1 +#define ACPI_HMAT_WRITE_LATENCY 2 +#define ACPI_HMAT_ACCESS_BANDWIDTH 3 +#define ACPI_HMAT_READ_BANDWIDTH 4 +#define ACPI_HMAT_WRITE_BANDWIDTH 5 + + +/* 2: Memory side cache information */ + +typedef struct acpi_hmat_cache +{ + ACPI_HMAT_STRUCTURE Header; + UINT32 MemoryPD; + UINT32 Reserved1; + UINT64 CacheSize; + UINT32 CacheAttributes; + UINT16 Reserved2; + UINT16 NumberOfSMBIOSHandles; + +} ACPI_HMAT_CACHE; + +/* Masks for CacheAttributes field above */ + +#define ACPI_HMAT_TOTAL_CACHE_LEVEL (0x0000000F) +#define ACPI_HMAT_CACHE_LEVEL (0x000000F0) +#define ACPI_HMAT_CACHE_ASSOCIATIVITY (0x00000F00) +#define ACPI_HMAT_WRITE_POLICY (0x0000F000) +#define ACPI_HMAT_CACHE_LINE_SIZE (0xFFFF0000) + +/* Values for cache associativity flag */ + +#define ACPI_HMAT_CA_NONE (0) +#define ACPI_HMAT_CA_DIRECT_MAPPED (1) +#define ACPI_HMAT_CA_COMPLEX_CACHE_INDEXING (2) + +/* Values for write policy flag */ + +#define ACPI_HMAT_CP_NONE (0) +#define ACPI_HMAT_CP_WB (1) +#define ACPI_HMAT_CP_WT (2) + + +/******************************************************************************* + * + * HPET - High Precision Event Timer table + * Version 1 + * + * Conforms to "IA-PC HPET (High Precision Event Timers) Specification", + * Version 1.0a, October 2004 + * + ******************************************************************************/ + +typedef struct acpi_table_hpet +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Id; /* Hardware ID of event timer block */ + ACPI_GENERIC_ADDRESS Address; /* Address of event timer block */ + UINT8 Sequence; /* HPET sequence number */ + UINT16 MinimumTick; /* Main counter min tick, periodic mode */ + UINT8 Flags; + +} ACPI_TABLE_HPET; + +/* Masks for Flags field above */ + +#define ACPI_HPET_PAGE_PROTECT_MASK (3) + +/* Values for Page Protect flags */ + +enum AcpiHpetPageProtect +{ + ACPI_HPET_NO_PAGE_PROTECT = 0, + ACPI_HPET_PAGE_PROTECT4 = 1, + ACPI_HPET_PAGE_PROTECT64 = 2 +}; + + +/******************************************************************************* + * + * IBFT - Boot Firmware Table + * Version 1 + * + * Conforms to "iSCSI Boot Firmware Table (iBFT) as Defined in ACPI 3.0b + * Specification", Version 1.01, March 1, 2007 + * + * Note: It appears that this table is not intended to appear in the RSDT/XSDT. + * Therefore, it is not currently supported by the disassembler. + * + ******************************************************************************/ + +typedef struct acpi_table_ibft +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 Reserved[12]; + +} ACPI_TABLE_IBFT; + + +/* IBFT common subtable header */ + +typedef struct acpi_ibft_header +{ + UINT8 Type; + UINT8 Version; + UINT16 Length; + UINT8 Index; + UINT8 Flags; + +} ACPI_IBFT_HEADER; + +/* Values for Type field above */ + +enum AcpiIbftType +{ + ACPI_IBFT_TYPE_NOT_USED = 0, + ACPI_IBFT_TYPE_CONTROL = 1, + ACPI_IBFT_TYPE_INITIATOR = 2, + ACPI_IBFT_TYPE_NIC = 3, + ACPI_IBFT_TYPE_TARGET = 4, + ACPI_IBFT_TYPE_EXTENSIONS = 5, + ACPI_IBFT_TYPE_RESERVED = 6 /* 6 and greater are reserved */ +}; + + +/* IBFT subtables */ + +typedef struct acpi_ibft_control +{ + ACPI_IBFT_HEADER Header; + UINT16 Extensions; + UINT16 InitiatorOffset; + UINT16 Nic0Offset; + UINT16 Target0Offset; + UINT16 Nic1Offset; + UINT16 Target1Offset; + +} ACPI_IBFT_CONTROL; + +typedef struct acpi_ibft_initiator +{ + ACPI_IBFT_HEADER Header; + UINT8 SnsServer[16]; + UINT8 SlpServer[16]; + UINT8 PrimaryServer[16]; + UINT8 SecondaryServer[16]; + UINT16 NameLength; + UINT16 NameOffset; + +} ACPI_IBFT_INITIATOR; + +typedef struct acpi_ibft_nic +{ + ACPI_IBFT_HEADER Header; + UINT8 IpAddress[16]; + UINT8 SubnetMaskPrefix; + UINT8 Origin; + UINT8 Gateway[16]; + UINT8 PrimaryDns[16]; + UINT8 SecondaryDns[16]; + UINT8 Dhcp[16]; + UINT16 Vlan; + UINT8 MacAddress[6]; + UINT16 PciAddress; + UINT16 NameLength; + UINT16 NameOffset; + +} ACPI_IBFT_NIC; + +typedef struct acpi_ibft_target +{ + ACPI_IBFT_HEADER Header; + UINT8 TargetIpAddress[16]; + UINT16 TargetIpSocket; + UINT8 TargetBootLun[8]; + UINT8 ChapType; + UINT8 NicAssociation; + UINT16 TargetNameLength; + UINT16 TargetNameOffset; + UINT16 ChapNameLength; + UINT16 ChapNameOffset; + UINT16 ChapSecretLength; + UINT16 ChapSecretOffset; + UINT16 ReverseChapNameLength; + UINT16 ReverseChapNameOffset; + UINT16 ReverseChapSecretLength; + UINT16 ReverseChapSecretOffset; + +} ACPI_IBFT_TARGET; + + +/* Reset to default packing */ + +#pragma pack() + +#endif /* __ACTBL1_H__ */ diff --git a/ports/acpica/include/actbl2.h b/ports/acpica/include/actbl2.h new file mode 100644 index 0000000..4b3d460 --- /dev/null +++ b/ports/acpica/include/actbl2.h @@ -0,0 +1,2150 @@ +/****************************************************************************** + * + * Name: actbl2.h - ACPI Table Definitions (tables not in ACPI spec) + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACTBL2_H__ +#define __ACTBL2_H__ + + +/******************************************************************************* + * + * Additional ACPI Tables (2) + * + * These tables are not consumed directly by the ACPICA subsystem, but are + * included here to support device drivers and the AML disassembler. + * + ******************************************************************************/ + + +/* + * Values for description table header signatures for tables defined in this + * file. Useful because they make it more difficult to inadvertently type in + * the wrong signature. + */ +#define ACPI_SIG_IORT "IORT" /* IO Remapping Table */ +#define ACPI_SIG_IVRS "IVRS" /* I/O Virtualization Reporting Structure */ +#define ACPI_SIG_LPIT "LPIT" /* Low Power Idle Table */ +#define ACPI_SIG_MADT "APIC" /* Multiple APIC Description Table */ +#define ACPI_SIG_MCFG "MCFG" /* PCI Memory Mapped Configuration table */ +#define ACPI_SIG_MCHI "MCHI" /* Management Controller Host Interface table */ +#define ACPI_SIG_MPST "MPST" /* Memory Power State Table */ +#define ACPI_SIG_MSCT "MSCT" /* Maximum System Characteristics Table */ +#define ACPI_SIG_MSDM "MSDM" /* Microsoft Data Management Table */ +#define ACPI_SIG_MTMR "MTMR" /* MID Timer table */ +#define ACPI_SIG_NFIT "NFIT" /* NVDIMM Firmware Interface Table */ +#define ACPI_SIG_PCCT "PCCT" /* Platform Communications Channel Table */ +#define ACPI_SIG_PDTT "PDTT" /* Platform Debug Trigger Table */ +#define ACPI_SIG_PMTT "PMTT" /* Platform Memory Topology Table */ +#define ACPI_SIG_PPTT "PPTT" /* Processor Properties Topology Table */ +#define ACPI_SIG_RASF "RASF" /* RAS Feature table */ +#define ACPI_SIG_SBST "SBST" /* Smart Battery Specification Table */ +#define ACPI_SIG_SDEI "SDEI" /* Software Delegated Exception Interface Table */ +#define ACPI_SIG_SDEV "SDEV" /* Secure Devices table */ + + +/* + * All tables must be byte-packed to match the ACPI specification, since + * the tables are provided by the system BIOS. + */ +#pragma pack(1) + +/* + * Note: C bitfields are not used for this reason: + * + * "Bitfields are great and easy to read, but unfortunately the C language + * does not specify the layout of bitfields in memory, which means they are + * essentially useless for dealing with packed data in on-disk formats or + * binary wire protocols." (Or ACPI tables and buffers.) "If you ask me, + * this decision was a design error in C. Ritchie could have picked an order + * and stuck with it." Norman Ramsey. + * See http://stackoverflow.com/a/1053662/41661 + */ + + +/******************************************************************************* + * + * IORT - IO Remapping Table + * + * Conforms to "IO Remapping Table System Software on ARM Platforms", + * Document number: ARM DEN 0049C, May 2017 + * + ******************************************************************************/ + +typedef struct acpi_table_iort +{ + ACPI_TABLE_HEADER Header; + UINT32 NodeCount; + UINT32 NodeOffset; + UINT32 Reserved; + +} ACPI_TABLE_IORT; + + +/* + * IORT subtables + */ +typedef struct acpi_iort_node +{ + UINT8 Type; + UINT16 Length; + UINT8 Revision; + UINT32 Reserved; + UINT32 MappingCount; + UINT32 MappingOffset; + char NodeData[1]; + +} ACPI_IORT_NODE; + +/* Values for subtable Type above */ + +enum AcpiIortNodeType +{ + ACPI_IORT_NODE_ITS_GROUP = 0x00, + ACPI_IORT_NODE_NAMED_COMPONENT = 0x01, + ACPI_IORT_NODE_PCI_ROOT_COMPLEX = 0x02, + ACPI_IORT_NODE_SMMU = 0x03, + ACPI_IORT_NODE_SMMU_V3 = 0x04 +}; + + +typedef struct acpi_iort_id_mapping +{ + UINT32 InputBase; /* Lowest value in input range */ + UINT32 IdCount; /* Number of IDs */ + UINT32 OutputBase; /* Lowest value in output range */ + UINT32 OutputReference; /* A reference to the output node */ + UINT32 Flags; + +} ACPI_IORT_ID_MAPPING; + +/* Masks for Flags field above for IORT subtable */ + +#define ACPI_IORT_ID_SINGLE_MAPPING (1) + + +typedef struct acpi_iort_memory_access +{ + UINT32 CacheCoherency; + UINT8 Hints; + UINT16 Reserved; + UINT8 MemoryFlags; + +} ACPI_IORT_MEMORY_ACCESS; + +/* Values for CacheCoherency field above */ + +#define ACPI_IORT_NODE_COHERENT 0x00000001 /* The device node is fully coherent */ +#define ACPI_IORT_NODE_NOT_COHERENT 0x00000000 /* The device node is not coherent */ + +/* Masks for Hints field above */ + +#define ACPI_IORT_HT_TRANSIENT (1) +#define ACPI_IORT_HT_WRITE (1<<1) +#define ACPI_IORT_HT_READ (1<<2) +#define ACPI_IORT_HT_OVERRIDE (1<<3) + +/* Masks for MemoryFlags field above */ + +#define ACPI_IORT_MF_COHERENCY (1) +#define ACPI_IORT_MF_ATTRIBUTES (1<<1) + + +/* + * IORT node specific subtables + */ +typedef struct acpi_iort_its_group +{ + UINT32 ItsCount; + UINT32 Identifiers[1]; /* GIC ITS identifier arrary */ + +} ACPI_IORT_ITS_GROUP; + + +typedef struct acpi_iort_named_component +{ + UINT32 NodeFlags; + UINT64 MemoryProperties; /* Memory access properties */ + UINT8 MemoryAddressLimit; /* Memory address size limit */ + char DeviceName[1]; /* Path of namespace object */ + +} ACPI_IORT_NAMED_COMPONENT; + + +typedef struct acpi_iort_root_complex +{ + UINT64 MemoryProperties; /* Memory access properties */ + UINT32 AtsAttribute; + UINT32 PciSegmentNumber; + +} ACPI_IORT_ROOT_COMPLEX; + +/* Values for AtsAttribute field above */ + +#define ACPI_IORT_ATS_SUPPORTED 0x00000001 /* The root complex supports ATS */ +#define ACPI_IORT_ATS_UNSUPPORTED 0x00000000 /* The root complex doesn't support ATS */ + + +typedef struct acpi_iort_smmu +{ + UINT64 BaseAddress; /* SMMU base address */ + UINT64 Span; /* Length of memory range */ + UINT32 Model; + UINT32 Flags; + UINT32 GlobalInterruptOffset; + UINT32 ContextInterruptCount; + UINT32 ContextInterruptOffset; + UINT32 PmuInterruptCount; + UINT32 PmuInterruptOffset; + UINT64 Interrupts[1]; /* Interrupt array */ + +} ACPI_IORT_SMMU; + +/* Values for Model field above */ + +#define ACPI_IORT_SMMU_V1 0x00000000 /* Generic SMMUv1 */ +#define ACPI_IORT_SMMU_V2 0x00000001 /* Generic SMMUv2 */ +#define ACPI_IORT_SMMU_CORELINK_MMU400 0x00000002 /* ARM Corelink MMU-400 */ +#define ACPI_IORT_SMMU_CORELINK_MMU500 0x00000003 /* ARM Corelink MMU-500 */ +#define ACPI_IORT_SMMU_CORELINK_MMU401 0x00000004 /* ARM Corelink MMU-401 */ +#define ACPI_IORT_SMMU_CAVIUM_THUNDERX 0x00000005 /* Cavium ThunderX SMMUv2 */ + +/* Masks for Flags field above */ + +#define ACPI_IORT_SMMU_DVM_SUPPORTED (1) +#define ACPI_IORT_SMMU_COHERENT_WALK (1<<1) + +/* Global interrupt format */ + +typedef struct acpi_iort_smmu_gsi +{ + UINT32 NSgIrpt; + UINT32 NSgIrptFlags; + UINT32 NSgCfgIrpt; + UINT32 NSgCfgIrptFlags; + +} ACPI_IORT_SMMU_GSI; + + +typedef struct acpi_iort_smmu_v3 +{ + UINT64 BaseAddress; /* SMMUv3 base address */ + UINT32 Flags; + UINT32 Reserved; + UINT64 VatosAddress; + UINT32 Model; + UINT32 EventGsiv; + UINT32 PriGsiv; + UINT32 GerrGsiv; + UINT32 SyncGsiv; + UINT8 Pxm; + UINT8 Reserved1; + UINT16 Reserved2; + UINT32 IdMappingIndex; + +} ACPI_IORT_SMMU_V3; + +/* Values for Model field above */ + +#define ACPI_IORT_SMMU_V3_GENERIC 0x00000000 /* Generic SMMUv3 */ +#define ACPI_IORT_SMMU_V3_HISILICON_HI161X 0x00000001 /* HiSilicon Hi161x SMMUv3 */ +#define ACPI_IORT_SMMU_V3_CAVIUM_CN99XX 0x00000002 /* Cavium CN99xx SMMUv3 */ + +/* Masks for Flags field above */ + +#define ACPI_IORT_SMMU_V3_COHACC_OVERRIDE (1) +#define ACPI_IORT_SMMU_V3_HTTU_OVERRIDE (1<<1) +#define ACPI_IORT_SMMU_V3_PXM_VALID (1<<3) + + +/******************************************************************************* + * + * IVRS - I/O Virtualization Reporting Structure + * Version 1 + * + * Conforms to "AMD I/O Virtualization Technology (IOMMU) Specification", + * Revision 1.26, February 2009. + * + ******************************************************************************/ + +typedef struct acpi_table_ivrs +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Info; /* Common virtualization info */ + UINT64 Reserved; + +} ACPI_TABLE_IVRS; + +/* Values for Info field above */ + +#define ACPI_IVRS_PHYSICAL_SIZE 0x00007F00 /* 7 bits, physical address size */ +#define ACPI_IVRS_VIRTUAL_SIZE 0x003F8000 /* 7 bits, virtual address size */ +#define ACPI_IVRS_ATS_RESERVED 0x00400000 /* ATS address translation range reserved */ + + +/* IVRS subtable header */ + +typedef struct acpi_ivrs_header +{ + UINT8 Type; /* Subtable type */ + UINT8 Flags; + UINT16 Length; /* Subtable length */ + UINT16 DeviceId; /* ID of IOMMU */ + +} ACPI_IVRS_HEADER; + +/* Values for subtable Type above */ + +enum AcpiIvrsType +{ + ACPI_IVRS_TYPE_HARDWARE = 0x10, + ACPI_IVRS_TYPE_MEMORY1 = 0x20, + ACPI_IVRS_TYPE_MEMORY2 = 0x21, + ACPI_IVRS_TYPE_MEMORY3 = 0x22 +}; + +/* Masks for Flags field above for IVHD subtable */ + +#define ACPI_IVHD_TT_ENABLE (1) +#define ACPI_IVHD_PASS_PW (1<<1) +#define ACPI_IVHD_RES_PASS_PW (1<<2) +#define ACPI_IVHD_ISOC (1<<3) +#define ACPI_IVHD_IOTLB (1<<4) + +/* Masks for Flags field above for IVMD subtable */ + +#define ACPI_IVMD_UNITY (1) +#define ACPI_IVMD_READ (1<<1) +#define ACPI_IVMD_WRITE (1<<2) +#define ACPI_IVMD_EXCLUSION_RANGE (1<<3) + + +/* + * IVRS subtables, correspond to Type in ACPI_IVRS_HEADER + */ + +/* 0x10: I/O Virtualization Hardware Definition Block (IVHD) */ + +typedef struct acpi_ivrs_hardware +{ + ACPI_IVRS_HEADER Header; + UINT16 CapabilityOffset; /* Offset for IOMMU control fields */ + UINT64 BaseAddress; /* IOMMU control registers */ + UINT16 PciSegmentGroup; + UINT16 Info; /* MSI number and unit ID */ + UINT32 Reserved; + +} ACPI_IVRS_HARDWARE; + +/* Masks for Info field above */ + +#define ACPI_IVHD_MSI_NUMBER_MASK 0x001F /* 5 bits, MSI message number */ +#define ACPI_IVHD_UNIT_ID_MASK 0x1F00 /* 5 bits, UnitID */ + + +/* + * Device Entries for IVHD subtable, appear after ACPI_IVRS_HARDWARE structure. + * Upper two bits of the Type field are the (encoded) length of the structure. + * Currently, only 4 and 8 byte entries are defined. 16 and 32 byte entries + * are reserved for future use but not defined. + */ +typedef struct acpi_ivrs_de_header +{ + UINT8 Type; + UINT16 Id; + UINT8 DataSetting; + +} ACPI_IVRS_DE_HEADER; + +/* Length of device entry is in the top two bits of Type field above */ + +#define ACPI_IVHD_ENTRY_LENGTH 0xC0 + +/* Values for device entry Type field above */ + +enum AcpiIvrsDeviceEntryType +{ + /* 4-byte device entries, all use ACPI_IVRS_DEVICE4 */ + + ACPI_IVRS_TYPE_PAD4 = 0, + ACPI_IVRS_TYPE_ALL = 1, + ACPI_IVRS_TYPE_SELECT = 2, + ACPI_IVRS_TYPE_START = 3, + ACPI_IVRS_TYPE_END = 4, + + /* 8-byte device entries */ + + ACPI_IVRS_TYPE_PAD8 = 64, + ACPI_IVRS_TYPE_NOT_USED = 65, + ACPI_IVRS_TYPE_ALIAS_SELECT = 66, /* Uses ACPI_IVRS_DEVICE8A */ + ACPI_IVRS_TYPE_ALIAS_START = 67, /* Uses ACPI_IVRS_DEVICE8A */ + ACPI_IVRS_TYPE_EXT_SELECT = 70, /* Uses ACPI_IVRS_DEVICE8B */ + ACPI_IVRS_TYPE_EXT_START = 71, /* Uses ACPI_IVRS_DEVICE8B */ + ACPI_IVRS_TYPE_SPECIAL = 72 /* Uses ACPI_IVRS_DEVICE8C */ +}; + +/* Values for Data field above */ + +#define ACPI_IVHD_INIT_PASS (1) +#define ACPI_IVHD_EINT_PASS (1<<1) +#define ACPI_IVHD_NMI_PASS (1<<2) +#define ACPI_IVHD_SYSTEM_MGMT (3<<4) +#define ACPI_IVHD_LINT0_PASS (1<<6) +#define ACPI_IVHD_LINT1_PASS (1<<7) + + +/* Types 0-4: 4-byte device entry */ + +typedef struct acpi_ivrs_device4 +{ + ACPI_IVRS_DE_HEADER Header; + +} ACPI_IVRS_DEVICE4; + +/* Types 66-67: 8-byte device entry */ + +typedef struct acpi_ivrs_device8a +{ + ACPI_IVRS_DE_HEADER Header; + UINT8 Reserved1; + UINT16 UsedId; + UINT8 Reserved2; + +} ACPI_IVRS_DEVICE8A; + +/* Types 70-71: 8-byte device entry */ + +typedef struct acpi_ivrs_device8b +{ + ACPI_IVRS_DE_HEADER Header; + UINT32 ExtendedData; + +} ACPI_IVRS_DEVICE8B; + +/* Values for ExtendedData above */ + +#define ACPI_IVHD_ATS_DISABLED (1<<31) + +/* Type 72: 8-byte device entry */ + +typedef struct acpi_ivrs_device8c +{ + ACPI_IVRS_DE_HEADER Header; + UINT8 Handle; + UINT16 UsedId; + UINT8 Variety; + +} ACPI_IVRS_DEVICE8C; + +/* Values for Variety field above */ + +#define ACPI_IVHD_IOAPIC 1 +#define ACPI_IVHD_HPET 2 + + +/* 0x20, 0x21, 0x22: I/O Virtualization Memory Definition Block (IVMD) */ + +typedef struct acpi_ivrs_memory +{ + ACPI_IVRS_HEADER Header; + UINT16 AuxData; + UINT64 Reserved; + UINT64 StartAddress; + UINT64 MemoryLength; + +} ACPI_IVRS_MEMORY; + + +/******************************************************************************* + * + * LPIT - Low Power Idle Table + * + * Conforms to "ACPI Low Power Idle Table (LPIT)" July 2014. + * + ******************************************************************************/ + +typedef struct acpi_table_lpit +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_LPIT; + + +/* LPIT subtable header */ + +typedef struct acpi_lpit_header +{ + UINT32 Type; /* Subtable type */ + UINT32 Length; /* Subtable length */ + UINT16 UniqueId; + UINT16 Reserved; + UINT32 Flags; + +} ACPI_LPIT_HEADER; + +/* Values for subtable Type above */ + +enum AcpiLpitType +{ + ACPI_LPIT_TYPE_NATIVE_CSTATE = 0x00, + ACPI_LPIT_TYPE_RESERVED = 0x01 /* 1 and above are reserved */ +}; + +/* Masks for Flags field above */ + +#define ACPI_LPIT_STATE_DISABLED (1) +#define ACPI_LPIT_NO_COUNTER (1<<1) + +/* + * LPIT subtables, correspond to Type in ACPI_LPIT_HEADER + */ + +/* 0x00: Native C-state instruction based LPI structure */ + +typedef struct acpi_lpit_native +{ + ACPI_LPIT_HEADER Header; + ACPI_GENERIC_ADDRESS EntryTrigger; + UINT32 Residency; + UINT32 Latency; + ACPI_GENERIC_ADDRESS ResidencyCounter; + UINT64 CounterFrequency; + +} ACPI_LPIT_NATIVE; + + +/******************************************************************************* + * + * MADT - Multiple APIC Description Table + * Version 3 + * + ******************************************************************************/ + +typedef struct acpi_table_madt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Address; /* Physical address of local APIC */ + UINT32 Flags; + +} ACPI_TABLE_MADT; + +/* Masks for Flags field above */ + +#define ACPI_MADT_PCAT_COMPAT (1) /* 00: System also has dual 8259s */ + +/* Values for PCATCompat flag */ + +#define ACPI_MADT_DUAL_PIC 1 +#define ACPI_MADT_MULTIPLE_APIC 0 + + +/* Values for MADT subtable type in ACPI_SUBTABLE_HEADER */ + +enum AcpiMadtType +{ + ACPI_MADT_TYPE_LOCAL_APIC = 0, + ACPI_MADT_TYPE_IO_APIC = 1, + ACPI_MADT_TYPE_INTERRUPT_OVERRIDE = 2, + ACPI_MADT_TYPE_NMI_SOURCE = 3, + ACPI_MADT_TYPE_LOCAL_APIC_NMI = 4, + ACPI_MADT_TYPE_LOCAL_APIC_OVERRIDE = 5, + ACPI_MADT_TYPE_IO_SAPIC = 6, + ACPI_MADT_TYPE_LOCAL_SAPIC = 7, + ACPI_MADT_TYPE_INTERRUPT_SOURCE = 8, + ACPI_MADT_TYPE_LOCAL_X2APIC = 9, + ACPI_MADT_TYPE_LOCAL_X2APIC_NMI = 10, + ACPI_MADT_TYPE_GENERIC_INTERRUPT = 11, + ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR = 12, + ACPI_MADT_TYPE_GENERIC_MSI_FRAME = 13, + ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR = 14, + ACPI_MADT_TYPE_GENERIC_TRANSLATOR = 15, + ACPI_MADT_TYPE_RESERVED = 16 /* 16 and greater are reserved */ +}; + + +/* + * MADT Subtables, correspond to Type in ACPI_SUBTABLE_HEADER + */ + +/* 0: Processor Local APIC */ + +typedef struct acpi_madt_local_apic +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 ProcessorId; /* ACPI processor id */ + UINT8 Id; /* Processor's local APIC id */ + UINT32 LapicFlags; + +} ACPI_MADT_LOCAL_APIC; + + +/* 1: IO APIC */ + +typedef struct acpi_madt_io_apic +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 Id; /* I/O APIC ID */ + UINT8 Reserved; /* Reserved - must be zero */ + UINT32 Address; /* APIC physical address */ + UINT32 GlobalIrqBase; /* Global system interrupt where INTI lines start */ + +} ACPI_MADT_IO_APIC; + + +/* 2: Interrupt Override */ + +typedef struct acpi_madt_interrupt_override +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 Bus; /* 0 - ISA */ + UINT8 SourceIrq; /* Interrupt source (IRQ) */ + UINT32 GlobalIrq; /* Global system interrupt */ + UINT16 IntiFlags; + +} ACPI_MADT_INTERRUPT_OVERRIDE; + + +/* 3: NMI Source */ + +typedef struct acpi_madt_nmi_source +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 IntiFlags; + UINT32 GlobalIrq; /* Global system interrupt */ + +} ACPI_MADT_NMI_SOURCE; + + +/* 4: Local APIC NMI */ + +typedef struct acpi_madt_local_apic_nmi +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 ProcessorId; /* ACPI processor id */ + UINT16 IntiFlags; + UINT8 Lint; /* LINTn to which NMI is connected */ + +} ACPI_MADT_LOCAL_APIC_NMI; + + +/* 5: Address Override */ + +typedef struct acpi_madt_local_apic_override +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* Reserved, must be zero */ + UINT64 Address; /* APIC physical address */ + +} ACPI_MADT_LOCAL_APIC_OVERRIDE; + + +/* 6: I/O Sapic */ + +typedef struct acpi_madt_io_sapic +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 Id; /* I/O SAPIC ID */ + UINT8 Reserved; /* Reserved, must be zero */ + UINT32 GlobalIrqBase; /* Global interrupt for SAPIC start */ + UINT64 Address; /* SAPIC physical address */ + +} ACPI_MADT_IO_SAPIC; + + +/* 7: Local Sapic */ + +typedef struct acpi_madt_local_sapic +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 ProcessorId; /* ACPI processor id */ + UINT8 Id; /* SAPIC ID */ + UINT8 Eid; /* SAPIC EID */ + UINT8 Reserved[3]; /* Reserved, must be zero */ + UINT32 LapicFlags; + UINT32 Uid; /* Numeric UID - ACPI 3.0 */ + char UidString[1]; /* String UID - ACPI 3.0 */ + +} ACPI_MADT_LOCAL_SAPIC; + + +/* 8: Platform Interrupt Source */ + +typedef struct acpi_madt_interrupt_source +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 IntiFlags; + UINT8 Type; /* 1=PMI, 2=INIT, 3=corrected */ + UINT8 Id; /* Processor ID */ + UINT8 Eid; /* Processor EID */ + UINT8 IoSapicVector; /* Vector value for PMI interrupts */ + UINT32 GlobalIrq; /* Global system interrupt */ + UINT32 Flags; /* Interrupt Source Flags */ + +} ACPI_MADT_INTERRUPT_SOURCE; + +/* Masks for Flags field above */ + +#define ACPI_MADT_CPEI_OVERRIDE (1) + + +/* 9: Processor Local X2APIC (ACPI 4.0) */ + +typedef struct acpi_madt_local_x2apic +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* Reserved - must be zero */ + UINT32 LocalApicId; /* Processor x2APIC ID */ + UINT32 LapicFlags; + UINT32 Uid; /* ACPI processor UID */ + +} ACPI_MADT_LOCAL_X2APIC; + + +/* 10: Local X2APIC NMI (ACPI 4.0) */ + +typedef struct acpi_madt_local_x2apic_nmi +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 IntiFlags; + UINT32 Uid; /* ACPI processor UID */ + UINT8 Lint; /* LINTn to which NMI is connected */ + UINT8 Reserved[3]; /* Reserved - must be zero */ + +} ACPI_MADT_LOCAL_X2APIC_NMI; + + +/* 11: Generic Interrupt (ACPI 5.0 + ACPI 6.0 changes) */ + +typedef struct acpi_madt_generic_interrupt +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* Reserved - must be zero */ + UINT32 CpuInterfaceNumber; + UINT32 Uid; + UINT32 Flags; + UINT32 ParkingVersion; + UINT32 PerformanceInterrupt; + UINT64 ParkedAddress; + UINT64 BaseAddress; + UINT64 GicvBaseAddress; + UINT64 GichBaseAddress; + UINT32 VgicInterrupt; + UINT64 GicrBaseAddress; + UINT64 ArmMpidr; + UINT8 EfficiencyClass; + UINT8 Reserved2[3]; + +} ACPI_MADT_GENERIC_INTERRUPT; + +/* Masks for Flags field above */ + +/* ACPI_MADT_ENABLED (1) Processor is usable if set */ +#define ACPI_MADT_PERFORMANCE_IRQ_MODE (1<<1) /* 01: Performance Interrupt Mode */ +#define ACPI_MADT_VGIC_IRQ_MODE (1<<2) /* 02: VGIC Maintenance Interrupt mode */ + + +/* 12: Generic Distributor (ACPI 5.0 + ACPI 6.0 changes) */ + +typedef struct acpi_madt_generic_distributor +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* Reserved - must be zero */ + UINT32 GicId; + UINT64 BaseAddress; + UINT32 GlobalIrqBase; + UINT8 Version; + UINT8 Reserved2[3]; /* Reserved - must be zero */ + +} ACPI_MADT_GENERIC_DISTRIBUTOR; + +/* Values for Version field above */ + +enum AcpiMadtGicVersion +{ + ACPI_MADT_GIC_VERSION_NONE = 0, + ACPI_MADT_GIC_VERSION_V1 = 1, + ACPI_MADT_GIC_VERSION_V2 = 2, + ACPI_MADT_GIC_VERSION_V3 = 3, + ACPI_MADT_GIC_VERSION_V4 = 4, + ACPI_MADT_GIC_VERSION_RESERVED = 5 /* 5 and greater are reserved */ +}; + + +/* 13: Generic MSI Frame (ACPI 5.1) */ + +typedef struct acpi_madt_generic_msi_frame +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* Reserved - must be zero */ + UINT32 MsiFrameId; + UINT64 BaseAddress; + UINT32 Flags; + UINT16 SpiCount; + UINT16 SpiBase; + +} ACPI_MADT_GENERIC_MSI_FRAME; + +/* Masks for Flags field above */ + +#define ACPI_MADT_OVERRIDE_SPI_VALUES (1) + + +/* 14: Generic Redistributor (ACPI 5.1) */ + +typedef struct acpi_madt_generic_redistributor +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* reserved - must be zero */ + UINT64 BaseAddress; + UINT32 Length; + +} ACPI_MADT_GENERIC_REDISTRIBUTOR; + + +/* 15: Generic Translator (ACPI 6.0) */ + +typedef struct acpi_madt_generic_translator +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* reserved - must be zero */ + UINT32 TranslationId; + UINT64 BaseAddress; + UINT32 Reserved2; + +} ACPI_MADT_GENERIC_TRANSLATOR; + + +/* + * Common flags fields for MADT subtables + */ + +/* MADT Local APIC flags */ + +#define ACPI_MADT_ENABLED (1) /* 00: Processor is usable if set */ + +/* MADT MPS INTI flags (IntiFlags) */ + +#define ACPI_MADT_POLARITY_MASK (3) /* 00-01: Polarity of APIC I/O input signals */ +#define ACPI_MADT_TRIGGER_MASK (3<<2) /* 02-03: Trigger mode of APIC input signals */ + +/* Values for MPS INTI flags */ + +#define ACPI_MADT_POLARITY_CONFORMS 0 +#define ACPI_MADT_POLARITY_ACTIVE_HIGH 1 +#define ACPI_MADT_POLARITY_RESERVED 2 +#define ACPI_MADT_POLARITY_ACTIVE_LOW 3 + +#define ACPI_MADT_TRIGGER_CONFORMS (0) +#define ACPI_MADT_TRIGGER_EDGE (1<<2) +#define ACPI_MADT_TRIGGER_RESERVED (2<<2) +#define ACPI_MADT_TRIGGER_LEVEL (3<<2) + + +/******************************************************************************* + * + * MCFG - PCI Memory Mapped Configuration table and subtable + * Version 1 + * + * Conforms to "PCI Firmware Specification", Revision 3.0, June 20, 2005 + * + ******************************************************************************/ + +typedef struct acpi_table_mcfg +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 Reserved[8]; + +} ACPI_TABLE_MCFG; + + +/* Subtable */ + +typedef struct acpi_mcfg_allocation +{ + UINT64 Address; /* Base address, processor-relative */ + UINT16 PciSegment; /* PCI segment group number */ + UINT8 StartBusNumber; /* Starting PCI Bus number */ + UINT8 EndBusNumber; /* Final PCI Bus number */ + UINT32 Reserved; + +} ACPI_MCFG_ALLOCATION; + + +/******************************************************************************* + * + * MCHI - Management Controller Host Interface Table + * Version 1 + * + * Conforms to "Management Component Transport Protocol (MCTP) Host + * Interface Specification", Revision 1.0.0a, October 13, 2009 + * + ******************************************************************************/ + +typedef struct acpi_table_mchi +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 InterfaceType; + UINT8 Protocol; + UINT64 ProtocolData; + UINT8 InterruptType; + UINT8 Gpe; + UINT8 PciDeviceFlag; + UINT32 GlobalInterrupt; + ACPI_GENERIC_ADDRESS ControlRegister; + UINT8 PciSegment; + UINT8 PciBus; + UINT8 PciDevice; + UINT8 PciFunction; + +} ACPI_TABLE_MCHI; + + +/******************************************************************************* + * + * MPST - Memory Power State Table (ACPI 5.0) + * Version 1 + * + ******************************************************************************/ + +#define ACPI_MPST_CHANNEL_INFO \ + UINT8 ChannelId; \ + UINT8 Reserved1[3]; \ + UINT16 PowerNodeCount; \ + UINT16 Reserved2; + +/* Main table */ + +typedef struct acpi_table_mpst +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + ACPI_MPST_CHANNEL_INFO /* Platform Communication Channel */ + +} ACPI_TABLE_MPST; + + +/* Memory Platform Communication Channel Info */ + +typedef struct acpi_mpst_channel +{ + ACPI_MPST_CHANNEL_INFO /* Platform Communication Channel */ + +} ACPI_MPST_CHANNEL; + + +/* Memory Power Node Structure */ + +typedef struct acpi_mpst_power_node +{ + UINT8 Flags; + UINT8 Reserved1; + UINT16 NodeId; + UINT32 Length; + UINT64 RangeAddress; + UINT64 RangeLength; + UINT32 NumPowerStates; + UINT32 NumPhysicalComponents; + +} ACPI_MPST_POWER_NODE; + +/* Values for Flags field above */ + +#define ACPI_MPST_ENABLED 1 +#define ACPI_MPST_POWER_MANAGED 2 +#define ACPI_MPST_HOT_PLUG_CAPABLE 4 + + +/* Memory Power State Structure (follows POWER_NODE above) */ + +typedef struct acpi_mpst_power_state +{ + UINT8 PowerState; + UINT8 InfoIndex; + +} ACPI_MPST_POWER_STATE; + + +/* Physical Component ID Structure (follows POWER_STATE above) */ + +typedef struct acpi_mpst_component +{ + UINT16 ComponentId; + +} ACPI_MPST_COMPONENT; + + +/* Memory Power State Characteristics Structure (follows all POWER_NODEs) */ + +typedef struct acpi_mpst_data_hdr +{ + UINT16 CharacteristicsCount; + UINT16 Reserved; + +} ACPI_MPST_DATA_HDR; + +typedef struct acpi_mpst_power_data +{ + UINT8 StructureId; + UINT8 Flags; + UINT16 Reserved1; + UINT32 AveragePower; + UINT32 PowerSaving; + UINT64 ExitLatency; + UINT64 Reserved2; + +} ACPI_MPST_POWER_DATA; + +/* Values for Flags field above */ + +#define ACPI_MPST_PRESERVE 1 +#define ACPI_MPST_AUTOENTRY 2 +#define ACPI_MPST_AUTOEXIT 4 + + +/* Shared Memory Region (not part of an ACPI table) */ + +typedef struct acpi_mpst_shared +{ + UINT32 Signature; + UINT16 PccCommand; + UINT16 PccStatus; + UINT32 CommandRegister; + UINT32 StatusRegister; + UINT32 PowerStateId; + UINT32 PowerNodeId; + UINT64 EnergyConsumed; + UINT64 AveragePower; + +} ACPI_MPST_SHARED; + + +/******************************************************************************* + * + * MSCT - Maximum System Characteristics Table (ACPI 4.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_msct +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 ProximityOffset; /* Location of proximity info struct(s) */ + UINT32 MaxProximityDomains;/* Max number of proximity domains */ + UINT32 MaxClockDomains; /* Max number of clock domains */ + UINT64 MaxAddress; /* Max physical address in system */ + +} ACPI_TABLE_MSCT; + + +/* Subtable - Maximum Proximity Domain Information. Version 1 */ + +typedef struct acpi_msct_proximity +{ + UINT8 Revision; + UINT8 Length; + UINT32 RangeStart; /* Start of domain range */ + UINT32 RangeEnd; /* End of domain range */ + UINT32 ProcessorCapacity; + UINT64 MemoryCapacity; /* In bytes */ + +} ACPI_MSCT_PROXIMITY; + + +/******************************************************************************* + * + * MSDM - Microsoft Data Management table + * + * Conforms to "Microsoft Software Licensing Tables (SLIC and MSDM)", + * November 29, 2011. Copyright 2011 Microsoft + * + ******************************************************************************/ + +/* Basic MSDM table is only the common ACPI header */ + +typedef struct acpi_table_msdm +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_MSDM; + + +/******************************************************************************* + * + * MTMR - MID Timer Table + * Version 1 + * + * Conforms to "Simple Firmware Interface Specification", + * Draft 0.8.2, Oct 19, 2010 + * NOTE: The ACPI MTMR is equivalent to the SFI MTMR table. + * + ******************************************************************************/ + +typedef struct acpi_table_mtmr +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_MTMR; + +/* MTMR entry */ + +typedef struct acpi_mtmr_entry +{ + ACPI_GENERIC_ADDRESS PhysicalAddress; + UINT32 Frequency; + UINT32 Irq; + +} ACPI_MTMR_ENTRY; + + +/******************************************************************************* + * + * NFIT - NVDIMM Interface Table (ACPI 6.0+) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_nfit +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Reserved; /* Reserved, must be zero */ + +} ACPI_TABLE_NFIT; + +/* Subtable header for NFIT */ + +typedef struct acpi_nfit_header +{ + UINT16 Type; + UINT16 Length; + +} ACPI_NFIT_HEADER; + + +/* Values for subtable type in ACPI_NFIT_HEADER */ + +enum AcpiNfitType +{ + ACPI_NFIT_TYPE_SYSTEM_ADDRESS = 0, + ACPI_NFIT_TYPE_MEMORY_MAP = 1, + ACPI_NFIT_TYPE_INTERLEAVE = 2, + ACPI_NFIT_TYPE_SMBIOS = 3, + ACPI_NFIT_TYPE_CONTROL_REGION = 4, + ACPI_NFIT_TYPE_DATA_REGION = 5, + ACPI_NFIT_TYPE_FLUSH_ADDRESS = 6, + ACPI_NFIT_TYPE_CAPABILITIES = 7, + ACPI_NFIT_TYPE_RESERVED = 8 /* 8 and greater are reserved */ +}; + +/* + * NFIT Subtables + */ + +/* 0: System Physical Address Range Structure */ + +typedef struct acpi_nfit_system_address +{ + ACPI_NFIT_HEADER Header; + UINT16 RangeIndex; + UINT16 Flags; + UINT32 Reserved; /* Reserved, must be zero */ + UINT32 ProximityDomain; + UINT8 RangeGuid[16]; + UINT64 Address; + UINT64 Length; + UINT64 MemoryMapping; + +} ACPI_NFIT_SYSTEM_ADDRESS; + +/* Flags */ + +#define ACPI_NFIT_ADD_ONLINE_ONLY (1) /* 00: Add/Online Operation Only */ +#define ACPI_NFIT_PROXIMITY_VALID (1<<1) /* 01: Proximity Domain Valid */ + +/* Range Type GUIDs appear in the include/acuuid.h file */ + + +/* 1: Memory Device to System Address Range Map Structure */ + +typedef struct acpi_nfit_memory_map +{ + ACPI_NFIT_HEADER Header; + UINT32 DeviceHandle; + UINT16 PhysicalId; + UINT16 RegionId; + UINT16 RangeIndex; + UINT16 RegionIndex; + UINT64 RegionSize; + UINT64 RegionOffset; + UINT64 Address; + UINT16 InterleaveIndex; + UINT16 InterleaveWays; + UINT16 Flags; + UINT16 Reserved; /* Reserved, must be zero */ + +} ACPI_NFIT_MEMORY_MAP; + +/* Flags */ + +#define ACPI_NFIT_MEM_SAVE_FAILED (1) /* 00: Last SAVE to Memory Device failed */ +#define ACPI_NFIT_MEM_RESTORE_FAILED (1<<1) /* 01: Last RESTORE from Memory Device failed */ +#define ACPI_NFIT_MEM_FLUSH_FAILED (1<<2) /* 02: Platform flush failed */ +#define ACPI_NFIT_MEM_NOT_ARMED (1<<3) /* 03: Memory Device is not armed */ +#define ACPI_NFIT_MEM_HEALTH_OBSERVED (1<<4) /* 04: Memory Device observed SMART/health events */ +#define ACPI_NFIT_MEM_HEALTH_ENABLED (1<<5) /* 05: SMART/health events enabled */ +#define ACPI_NFIT_MEM_MAP_FAILED (1<<6) /* 06: Mapping to SPA failed */ + + +/* 2: Interleave Structure */ + +typedef struct acpi_nfit_interleave +{ + ACPI_NFIT_HEADER Header; + UINT16 InterleaveIndex; + UINT16 Reserved; /* Reserved, must be zero */ + UINT32 LineCount; + UINT32 LineSize; + UINT32 LineOffset[1]; /* Variable length */ + +} ACPI_NFIT_INTERLEAVE; + + +/* 3: SMBIOS Management Information Structure */ + +typedef struct acpi_nfit_smbios +{ + ACPI_NFIT_HEADER Header; + UINT32 Reserved; /* Reserved, must be zero */ + UINT8 Data[1]; /* Variable length */ + +} ACPI_NFIT_SMBIOS; + + +/* 4: NVDIMM Control Region Structure */ + +typedef struct acpi_nfit_control_region +{ + ACPI_NFIT_HEADER Header; + UINT16 RegionIndex; + UINT16 VendorId; + UINT16 DeviceId; + UINT16 RevisionId; + UINT16 SubsystemVendorId; + UINT16 SubsystemDeviceId; + UINT16 SubsystemRevisionId; + UINT8 ValidFields; + UINT8 ManufacturingLocation; + UINT16 ManufacturingDate; + UINT8 Reserved[2]; /* Reserved, must be zero */ + UINT32 SerialNumber; + UINT16 Code; + UINT16 Windows; + UINT64 WindowSize; + UINT64 CommandOffset; + UINT64 CommandSize; + UINT64 StatusOffset; + UINT64 StatusSize; + UINT16 Flags; + UINT8 Reserved1[6]; /* Reserved, must be zero */ + +} ACPI_NFIT_CONTROL_REGION; + +/* Flags */ + +#define ACPI_NFIT_CONTROL_BUFFERED (1) /* Block Data Windows implementation is buffered */ + +/* ValidFields bits */ + +#define ACPI_NFIT_CONTROL_MFG_INFO_VALID (1) /* Manufacturing fields are valid */ + + +/* 5: NVDIMM Block Data Window Region Structure */ + +typedef struct acpi_nfit_data_region +{ + ACPI_NFIT_HEADER Header; + UINT16 RegionIndex; + UINT16 Windows; + UINT64 Offset; + UINT64 Size; + UINT64 Capacity; + UINT64 StartAddress; + +} ACPI_NFIT_DATA_REGION; + + +/* 6: Flush Hint Address Structure */ + +typedef struct acpi_nfit_flush_address +{ + ACPI_NFIT_HEADER Header; + UINT32 DeviceHandle; + UINT16 HintCount; + UINT8 Reserved[6]; /* Reserved, must be zero */ + UINT64 HintAddress[1]; /* Variable length */ + +} ACPI_NFIT_FLUSH_ADDRESS; + + +/* 7: Platform Capabilities Structure */ + +typedef struct acpi_nfit_capabilities +{ + ACPI_NFIT_HEADER Header; + UINT8 HighestCapability; + UINT8 Reserved[3]; /* Reserved, must be zero */ + UINT32 Capabilities; + UINT32 Reserved2; + +} ACPI_NFIT_CAPABILITIES; + +/* Capabilities Flags */ + +#define ACPI_NFIT_CAPABILITY_CACHE_FLUSH (1) /* 00: Cache Flush to NVDIMM capable */ +#define ACPI_NFIT_CAPABILITY_MEM_FLUSH (1<<1) /* 01: Memory Flush to NVDIMM capable */ +#define ACPI_NFIT_CAPABILITY_MEM_MIRRORING (1<<2) /* 02: Memory Mirroring capable */ + + +/* + * NFIT/DVDIMM device handle support - used as the _ADR for each NVDIMM + */ +typedef struct nfit_device_handle +{ + UINT32 Handle; + +} NFIT_DEVICE_HANDLE; + +/* Device handle construction and extraction macros */ + +#define ACPI_NFIT_DIMM_NUMBER_MASK 0x0000000F +#define ACPI_NFIT_CHANNEL_NUMBER_MASK 0x000000F0 +#define ACPI_NFIT_MEMORY_ID_MASK 0x00000F00 +#define ACPI_NFIT_SOCKET_ID_MASK 0x0000F000 +#define ACPI_NFIT_NODE_ID_MASK 0x0FFF0000 + +#define ACPI_NFIT_DIMM_NUMBER_OFFSET 0 +#define ACPI_NFIT_CHANNEL_NUMBER_OFFSET 4 +#define ACPI_NFIT_MEMORY_ID_OFFSET 8 +#define ACPI_NFIT_SOCKET_ID_OFFSET 12 +#define ACPI_NFIT_NODE_ID_OFFSET 16 + +/* Macro to construct a NFIT/NVDIMM device handle */ + +#define ACPI_NFIT_BUILD_DEVICE_HANDLE(dimm, channel, memory, socket, node) \ + ((dimm) | \ + ((channel) << ACPI_NFIT_CHANNEL_NUMBER_OFFSET) | \ + ((memory) << ACPI_NFIT_MEMORY_ID_OFFSET) | \ + ((socket) << ACPI_NFIT_SOCKET_ID_OFFSET) | \ + ((node) << ACPI_NFIT_NODE_ID_OFFSET)) + +/* Macros to extract individual fields from a NFIT/NVDIMM device handle */ + +#define ACPI_NFIT_GET_DIMM_NUMBER(handle) \ + ((handle) & ACPI_NFIT_DIMM_NUMBER_MASK) + +#define ACPI_NFIT_GET_CHANNEL_NUMBER(handle) \ + (((handle) & ACPI_NFIT_CHANNEL_NUMBER_MASK) >> ACPI_NFIT_CHANNEL_NUMBER_OFFSET) + +#define ACPI_NFIT_GET_MEMORY_ID(handle) \ + (((handle) & ACPI_NFIT_MEMORY_ID_MASK) >> ACPI_NFIT_MEMORY_ID_OFFSET) + +#define ACPI_NFIT_GET_SOCKET_ID(handle) \ + (((handle) & ACPI_NFIT_SOCKET_ID_MASK) >> ACPI_NFIT_SOCKET_ID_OFFSET) + +#define ACPI_NFIT_GET_NODE_ID(handle) \ + (((handle) & ACPI_NFIT_NODE_ID_MASK) >> ACPI_NFIT_NODE_ID_OFFSET) + + +/******************************************************************************* + * + * PCCT - Platform Communications Channel Table (ACPI 5.0) + * Version 2 (ACPI 6.2) + * + ******************************************************************************/ + +typedef struct acpi_table_pcct +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Flags; + UINT64 Reserved; + +} ACPI_TABLE_PCCT; + +/* Values for Flags field above */ + +#define ACPI_PCCT_DOORBELL 1 + +/* Values for subtable type in ACPI_SUBTABLE_HEADER */ + +enum AcpiPcctType +{ + ACPI_PCCT_TYPE_GENERIC_SUBSPACE = 0, + ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE = 1, + ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2 = 2, /* ACPI 6.1 */ + ACPI_PCCT_TYPE_EXT_PCC_MASTER_SUBSPACE = 3, /* ACPI 6.2 */ + ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE = 4, /* ACPI 6.2 */ + ACPI_PCCT_TYPE_RESERVED = 5 /* 5 and greater are reserved */ +}; + +/* + * PCCT Subtables, correspond to Type in ACPI_SUBTABLE_HEADER + */ + +/* 0: Generic Communications Subspace */ + +typedef struct acpi_pcct_subspace +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 Reserved[6]; + UINT64 BaseAddress; + UINT64 Length; + ACPI_GENERIC_ADDRESS DoorbellRegister; + UINT64 PreserveMask; + UINT64 WriteMask; + UINT32 Latency; + UINT32 MaxAccessRate; + UINT16 MinTurnaroundTime; + +} ACPI_PCCT_SUBSPACE; + + +/* 1: HW-reduced Communications Subspace (ACPI 5.1) */ + +typedef struct acpi_pcct_hw_reduced +{ + ACPI_SUBTABLE_HEADER Header; + UINT32 PlatformInterrupt; + UINT8 Flags; + UINT8 Reserved; + UINT64 BaseAddress; + UINT64 Length; + ACPI_GENERIC_ADDRESS DoorbellRegister; + UINT64 PreserveMask; + UINT64 WriteMask; + UINT32 Latency; + UINT32 MaxAccessRate; + UINT16 MinTurnaroundTime; + +} ACPI_PCCT_HW_REDUCED; + + +/* 2: HW-reduced Communications Subspace Type 2 (ACPI 6.1) */ + +typedef struct acpi_pcct_hw_reduced_type2 +{ + ACPI_SUBTABLE_HEADER Header; + UINT32 PlatformInterrupt; + UINT8 Flags; + UINT8 Reserved; + UINT64 BaseAddress; + UINT64 Length; + ACPI_GENERIC_ADDRESS DoorbellRegister; + UINT64 PreserveMask; + UINT64 WriteMask; + UINT32 Latency; + UINT32 MaxAccessRate; + UINT16 MinTurnaroundTime; + ACPI_GENERIC_ADDRESS PlatformAckRegister; + UINT64 AckPreserveMask; + UINT64 AckWriteMask; + +} ACPI_PCCT_HW_REDUCED_TYPE2; + + +/* 3: Extended PCC Master Subspace Type 3 (ACPI 6.2) */ + +typedef struct acpi_pcct_ext_pcc_master +{ + ACPI_SUBTABLE_HEADER Header; + UINT32 PlatformInterrupt; + UINT8 Flags; + UINT8 Reserved1; + UINT64 BaseAddress; + UINT32 Length; + ACPI_GENERIC_ADDRESS DoorbellRegister; + UINT64 PreserveMask; + UINT64 WriteMask; + UINT32 Latency; + UINT32 MaxAccessRate; + UINT32 MinTurnaroundTime; + ACPI_GENERIC_ADDRESS PlatformAckRegister; + UINT64 AckPreserveMask; + UINT64 AckSetMask; + UINT64 Reserved2; + ACPI_GENERIC_ADDRESS CmdCompleteRegister; + UINT64 CmdCompleteMask; + ACPI_GENERIC_ADDRESS CmdUpdateRegister; + UINT64 CmdUpdatePreserveMask; + UINT64 CmdUpdateSetMask; + ACPI_GENERIC_ADDRESS ErrorStatusRegister; + UINT64 ErrorStatusMask; + +} ACPI_PCCT_EXT_PCC_MASTER; + + +/* 4: Extended PCC Slave Subspace Type 4 (ACPI 6.2) */ + +typedef struct acpi_pcct_ext_pcc_slave +{ + ACPI_SUBTABLE_HEADER Header; + UINT32 PlatformInterrupt; + UINT8 Flags; + UINT8 Reserved1; + UINT64 BaseAddress; + UINT32 Length; + ACPI_GENERIC_ADDRESS DoorbellRegister; + UINT64 PreserveMask; + UINT64 WriteMask; + UINT32 Latency; + UINT32 MaxAccessRate; + UINT32 MinTurnaroundTime; + ACPI_GENERIC_ADDRESS PlatformAckRegister; + UINT64 AckPreserveMask; + UINT64 AckSetMask; + UINT64 Reserved2; + ACPI_GENERIC_ADDRESS CmdCompleteRegister; + UINT64 CmdCompleteMask; + ACPI_GENERIC_ADDRESS CmdUpdateRegister; + UINT64 CmdUpdatePreserveMask; + UINT64 CmdUpdateSetMask; + ACPI_GENERIC_ADDRESS ErrorStatusRegister; + UINT64 ErrorStatusMask; + +} ACPI_PCCT_EXT_PCC_SLAVE; + + +/* Values for doorbell flags above */ + +#define ACPI_PCCT_INTERRUPT_POLARITY (1) +#define ACPI_PCCT_INTERRUPT_MODE (1<<1) + + +/* + * PCC memory structures (not part of the ACPI table) + */ + +/* Shared Memory Region */ + +typedef struct acpi_pcct_shared_memory +{ + UINT32 Signature; + UINT16 Command; + UINT16 Status; + +} ACPI_PCCT_SHARED_MEMORY; + + +/* Extended PCC Subspace Shared Memory Region (ACPI 6.2) */ + +typedef struct acpi_pcct_ext_pcc_shared_memory +{ + UINT32 Signature; + UINT32 Flags; + UINT32 Length; + UINT32 Command; + +} ACPI_PCCT_EXT_PCC_SHARED_MEMORY; + + +/******************************************************************************* + * + * PDTT - Platform Debug Trigger Table (ACPI 6.2) + * Version 0 + * + ******************************************************************************/ + +typedef struct acpi_table_pdtt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 TriggerCount; + UINT8 Reserved[3]; + UINT32 ArrayOffset; + +} ACPI_TABLE_PDTT; + + +/* + * PDTT Communication Channel Identifier Structure. + * The number of these structures is defined by TriggerCount above, + * starting at ArrayOffset. + */ +typedef struct acpi_pdtt_channel +{ + UINT8 SubchannelId; + UINT8 Flags; + +} ACPI_PDTT_CHANNEL; + +/* Flags for above */ + +#define ACPI_PDTT_RUNTIME_TRIGGER (1) +#define ACPI_PDTT_WAIT_COMPLETION (1<<1) + + +/******************************************************************************* + * + * PMTT - Platform Memory Topology Table (ACPI 5.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_pmtt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Reserved; + +} ACPI_TABLE_PMTT; + + +/* Common header for PMTT subtables that follow main table */ + +typedef struct acpi_pmtt_header +{ + UINT8 Type; + UINT8 Reserved1; + UINT16 Length; + UINT16 Flags; + UINT16 Reserved2; + +} ACPI_PMTT_HEADER; + +/* Values for Type field above */ + +#define ACPI_PMTT_TYPE_SOCKET 0 +#define ACPI_PMTT_TYPE_CONTROLLER 1 +#define ACPI_PMTT_TYPE_DIMM 2 +#define ACPI_PMTT_TYPE_RESERVED 3 /* 0x03-0xFF are reserved */ + +/* Values for Flags field above */ + +#define ACPI_PMTT_TOP_LEVEL 0x0001 +#define ACPI_PMTT_PHYSICAL 0x0002 +#define ACPI_PMTT_MEMORY_TYPE 0x000C + + +/* + * PMTT subtables, correspond to Type in acpi_pmtt_header + */ + + +/* 0: Socket Structure */ + +typedef struct acpi_pmtt_socket +{ + ACPI_PMTT_HEADER Header; + UINT16 SocketId; + UINT16 Reserved; + +} ACPI_PMTT_SOCKET; + + +/* 1: Memory Controller subtable */ + +typedef struct acpi_pmtt_controller +{ + ACPI_PMTT_HEADER Header; + UINT32 ReadLatency; + UINT32 WriteLatency; + UINT32 ReadBandwidth; + UINT32 WriteBandwidth; + UINT16 AccessWidth; + UINT16 Alignment; + UINT16 Reserved; + UINT16 DomainCount; + +} ACPI_PMTT_CONTROLLER; + +/* 1a: Proximity Domain substructure */ + +typedef struct acpi_pmtt_domain +{ + UINT32 ProximityDomain; + +} ACPI_PMTT_DOMAIN; + + +/* 2: Physical Component Identifier (DIMM) */ + +typedef struct acpi_pmtt_physical_component +{ + ACPI_PMTT_HEADER Header; + UINT16 ComponentId; + UINT16 Reserved; + UINT32 MemorySize; + UINT32 BiosHandle; + +} ACPI_PMTT_PHYSICAL_COMPONENT; + + +/******************************************************************************* + * + * PPTT - Processor Properties Topology Table (ACPI 6.2) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_pptt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_PPTT; + +/* Values for Type field above */ + +enum AcpiPpttType +{ + ACPI_PPTT_TYPE_PROCESSOR = 0, + ACPI_PPTT_TYPE_CACHE = 1, + ACPI_PPTT_TYPE_ID = 2, + ACPI_PPTT_TYPE_RESERVED = 3 +}; + + +/* 0: Processor Hierarchy Node Structure */ + +typedef struct acpi_pptt_processor +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; + UINT32 Flags; + UINT32 Parent; + UINT32 AcpiProcessorId; + UINT32 NumberOfPrivResources; + +} ACPI_PPTT_PROCESSOR; + +/* Flags */ + +#define ACPI_PPTT_PHYSICAL_PACKAGE (1) /* Physical package */ +#define ACPI_PPTT_ACPI_PROCESSOR_ID_VALID (2) /* ACPI Processor ID valid */ + + +/* 1: Cache Type Structure */ + +typedef struct acpi_pptt_cache +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; + UINT32 Flags; + UINT32 NextLevelOfCache; + UINT32 Size; + UINT32 NumberOfSets; + UINT8 Associativity; + UINT8 Attributes; + UINT16 LineSize; + +} ACPI_PPTT_CACHE; + +/* Flags */ + +#define ACPI_PPTT_SIZE_PROPERTY_VALID (1) /* Physical property valid */ +#define ACPI_PPTT_NUMBER_OF_SETS_VALID (1<<1) /* Number of sets valid */ +#define ACPI_PPTT_ASSOCIATIVITY_VALID (1<<2) /* Associativity valid */ +#define ACPI_PPTT_ALLOCATION_TYPE_VALID (1<<3) /* Allocation type valid */ +#define ACPI_PPTT_CACHE_TYPE_VALID (1<<4) /* Cache type valid */ +#define ACPI_PPTT_WRITE_POLICY_VALID (1<<5) /* Write policy valid */ +#define ACPI_PPTT_LINE_SIZE_VALID (1<<6) /* Line size valid */ + +/* Masks for Attributes */ + +#define ACPI_PPTT_MASK_ALLOCATION_TYPE (0x03) /* Allocation type */ +#define ACPI_PPTT_MASK_CACHE_TYPE (0x0C) /* Cache type */ +#define ACPI_PPTT_MASK_WRITE_POLICY (0x10) /* Write policy */ + +/* Attributes describing cache */ +#define ACPI_PPTT_CACHE_READ_ALLOCATE (0x0) /* Cache line is allocated on read */ +#define ACPI_PPTT_CACHE_WRITE_ALLOCATE (0x01) /* Cache line is allocated on write */ +#define ACPI_PPTT_CACHE_RW_ALLOCATE (0x02) /* Cache line is allocated on read and write */ +#define ACPI_PPTT_CACHE_RW_ALLOCATE_ALT (0x03) /* Alternate representation of above */ + +#define ACPI_PPTT_CACHE_TYPE_DATA (0x0) /* Data cache */ +#define ACPI_PPTT_CACHE_TYPE_INSTR (1<<2) /* Instruction cache */ +#define ACPI_PPTT_CACHE_TYPE_UNIFIED (2<<2) /* Unified I & D cache */ +#define ACPI_PPTT_CACHE_TYPE_UNIFIED_ALT (3<<2) /* Alternate representation of above */ + +#define ACPI_PPTT_CACHE_POLICY_WB (0x0) /* Cache is write back */ +#define ACPI_PPTT_CACHE_POLICY_WT (1<<4) /* Cache is write through */ + +/* 2: ID Structure */ + +typedef struct acpi_pptt_id +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; + UINT32 VendorId; + UINT64 Level1Id; + UINT64 Level2Id; + UINT16 MajorRev; + UINT16 MinorRev; + UINT16 SpinRev; + +} ACPI_PPTT_ID; + + +/******************************************************************************* + * + * RASF - RAS Feature Table (ACPI 5.0) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_rasf +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 ChannelId[12]; + +} ACPI_TABLE_RASF; + +/* RASF Platform Communication Channel Shared Memory Region */ + +typedef struct acpi_rasf_shared_memory +{ + UINT32 Signature; + UINT16 Command; + UINT16 Status; + UINT16 Version; + UINT8 Capabilities[16]; + UINT8 SetCapabilities[16]; + UINT16 NumParameterBlocks; + UINT32 SetCapabilitiesStatus; + +} ACPI_RASF_SHARED_MEMORY; + +/* RASF Parameter Block Structure Header */ + +typedef struct acpi_rasf_parameter_block +{ + UINT16 Type; + UINT16 Version; + UINT16 Length; + +} ACPI_RASF_PARAMETER_BLOCK; + +/* RASF Parameter Block Structure for PATROL_SCRUB */ + +typedef struct acpi_rasf_patrol_scrub_parameter +{ + ACPI_RASF_PARAMETER_BLOCK Header; + UINT16 PatrolScrubCommand; + UINT64 RequestedAddressRange[2]; + UINT64 ActualAddressRange[2]; + UINT16 Flags; + UINT8 RequestedSpeed; + +} ACPI_RASF_PATROL_SCRUB_PARAMETER; + +/* Masks for Flags and Speed fields above */ + +#define ACPI_RASF_SCRUBBER_RUNNING 1 +#define ACPI_RASF_SPEED (7<<1) +#define ACPI_RASF_SPEED_SLOW (0<<1) +#define ACPI_RASF_SPEED_MEDIUM (4<<1) +#define ACPI_RASF_SPEED_FAST (7<<1) + +/* Channel Commands */ + +enum AcpiRasfCommands +{ + ACPI_RASF_EXECUTE_RASF_COMMAND = 1 +}; + +/* Platform RAS Capabilities */ + +enum AcpiRasfCapabiliities +{ + ACPI_HW_PATROL_SCRUB_SUPPORTED = 0, + ACPI_SW_PATROL_SCRUB_EXPOSED = 1 +}; + +/* Patrol Scrub Commands */ + +enum AcpiRasfPatrolScrubCommands +{ + ACPI_RASF_GET_PATROL_PARAMETERS = 1, + ACPI_RASF_START_PATROL_SCRUBBER = 2, + ACPI_RASF_STOP_PATROL_SCRUBBER = 3 +}; + +/* Channel Command flags */ + +#define ACPI_RASF_GENERATE_SCI (1<<15) + +/* Status values */ + +enum AcpiRasfStatus +{ + ACPI_RASF_SUCCESS = 0, + ACPI_RASF_NOT_VALID = 1, + ACPI_RASF_NOT_SUPPORTED = 2, + ACPI_RASF_BUSY = 3, + ACPI_RASF_FAILED = 4, + ACPI_RASF_ABORTED = 5, + ACPI_RASF_INVALID_DATA = 6 +}; + +/* Status flags */ + +#define ACPI_RASF_COMMAND_COMPLETE (1) +#define ACPI_RASF_SCI_DOORBELL (1<<1) +#define ACPI_RASF_ERROR (1<<2) +#define ACPI_RASF_STATUS (0x1F<<3) + + +/******************************************************************************* + * + * SBST - Smart Battery Specification Table + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_sbst +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 WarningLevel; + UINT32 LowLevel; + UINT32 CriticalLevel; + +} ACPI_TABLE_SBST; + + +/******************************************************************************* + * + * SDEI - Software Delegated Exception Interface Descriptor Table + * + * Conforms to "Software Delegated Exception Interface (SDEI)" ARM DEN0054A, + * May 8th, 2017. Copyright 2017 ARM Ltd. + * + ******************************************************************************/ + +typedef struct acpi_table_sdei +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_SDEI; + + +/******************************************************************************* + * + * SDEV - Secure Devices Table (ACPI 6.2) + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_sdev +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_SDEV; + + +typedef struct acpi_sdev_header +{ + UINT8 Type; + UINT8 Flags; + UINT16 Length; + +} ACPI_SDEV_HEADER; + + +/* Values for subtable type above */ + +enum AcpiSdevType +{ + ACPI_SDEV_TYPE_NAMESPACE_DEVICE = 0, + ACPI_SDEV_TYPE_PCIE_ENDPOINT_DEVICE = 1, + ACPI_SDEV_TYPE_RESERVED = 2 /* 2 and greater are reserved */ +}; + +/* Values for flags above */ + +#define ACPI_SDEV_HANDOFF_TO_UNSECURE_OS (1) + +/* + * SDEV subtables + */ + +/* 0: Namespace Device Based Secure Device Structure */ + +typedef struct acpi_sdev_namespace +{ + ACPI_SDEV_HEADER Header; + UINT16 DeviceIdOffset; + UINT16 DeviceIdLength; + UINT16 VendorDataOffset; + UINT16 VendorDataLength; + +} ACPI_SDEV_NAMESPACE; + +/* 1: PCIe Endpoint Device Based Device Structure */ + +typedef struct acpi_sdev_pcie +{ + ACPI_SDEV_HEADER Header; + UINT16 Segment; + UINT16 StartBus; + UINT16 PathOffset; + UINT16 PathLength; + UINT16 VendorDataOffset; + UINT16 VendorDataLength; + +} ACPI_SDEV_PCIE; + +/* 1a: PCIe Endpoint path entry */ + +typedef struct acpi_sdev_pcie_path +{ + UINT8 Device; + UINT8 Function; + +} ACPI_SDEV_PCIE_PATH; + + +/* Reset to default packing */ + +#pragma pack() + +#endif /* __ACTBL2_H__ */ diff --git a/ports/acpica/include/actbl3.h b/ports/acpica/include/actbl3.h new file mode 100644 index 0000000..d4c1583 --- /dev/null +++ b/ports/acpica/include/actbl3.h @@ -0,0 +1,906 @@ +/****************************************************************************** + * + * Name: actbl3.h - ACPI Table Definitions + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACTBL3_H__ +#define __ACTBL3_H__ + + +/******************************************************************************* + * + * Additional ACPI Tables + * + * These tables are not consumed directly by the ACPICA subsystem, but are + * included here to support device drivers and the AML disassembler. + * + ******************************************************************************/ + + +/* + * Values for description table header signatures for tables defined in this + * file. Useful because they make it more difficult to inadvertently type in + * the wrong signature. + */ +#define ACPI_SIG_SLIC "SLIC" /* Software Licensing Description Table */ +#define ACPI_SIG_SLIT "SLIT" /* System Locality Distance Information Table */ +#define ACPI_SIG_SPCR "SPCR" /* Serial Port Console Redirection table */ +#define ACPI_SIG_SPMI "SPMI" /* Server Platform Management Interface table */ +#define ACPI_SIG_SRAT "SRAT" /* System Resource Affinity Table */ +#define ACPI_SIG_STAO "STAO" /* Status Override table */ +#define ACPI_SIG_TCPA "TCPA" /* Trusted Computing Platform Alliance table */ +#define ACPI_SIG_TPM2 "TPM2" /* Trusted Platform Module 2.0 H/W interface table */ +#define ACPI_SIG_UEFI "UEFI" /* Uefi Boot Optimization Table */ +#define ACPI_SIG_VRTC "VRTC" /* Virtual Real Time Clock Table */ +#define ACPI_SIG_WAET "WAET" /* Windows ACPI Emulated devices Table */ +#define ACPI_SIG_WDAT "WDAT" /* Watchdog Action Table */ +#define ACPI_SIG_WDDT "WDDT" /* Watchdog Timer Description Table */ +#define ACPI_SIG_WDRT "WDRT" /* Watchdog Resource Table */ +#define ACPI_SIG_WPBT "WPBT" /* Windows Platform Binary Table */ +#define ACPI_SIG_WSMT "WSMT" /* Windows SMM Security Migrations Table */ +#define ACPI_SIG_XENV "XENV" /* Xen Environment table */ +#define ACPI_SIG_XXXX "XXXX" /* Intermediate AML header for ASL/ASL+ converter */ + +/* + * All tables must be byte-packed to match the ACPI specification, since + * the tables are provided by the system BIOS. + */ +#pragma pack(1) + +/* + * Note: C bitfields are not used for this reason: + * + * "Bitfields are great and easy to read, but unfortunately the C language + * does not specify the layout of bitfields in memory, which means they are + * essentially useless for dealing with packed data in on-disk formats or + * binary wire protocols." (Or ACPI tables and buffers.) "If you ask me, + * this decision was a design error in C. Ritchie could have picked an order + * and stuck with it." Norman Ramsey. + * See http://stackoverflow.com/a/1053662/41661 + */ + + +/******************************************************************************* + * + * SLIC - Software Licensing Description Table + * + * Conforms to "Microsoft Software Licensing Tables (SLIC and MSDM)", + * November 29, 2011. Copyright 2011 Microsoft + * + ******************************************************************************/ + +/* Basic SLIC table is only the common ACPI header */ + +typedef struct acpi_table_slic +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_SLIC; + + +/******************************************************************************* + * + * SLIT - System Locality Distance Information Table + * Version 1 + * + ******************************************************************************/ + +typedef struct acpi_table_slit +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT64 LocalityCount; + UINT8 Entry[1]; /* Real size = localities^2 */ + +} ACPI_TABLE_SLIT; + + +/******************************************************************************* + * + * SPCR - Serial Port Console Redirection table + * Version 2 + * + * Conforms to "Serial Port Console Redirection Table", + * Version 1.03, August 10, 2015 + * + ******************************************************************************/ + +typedef struct acpi_table_spcr +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 InterfaceType; /* 0=full 16550, 1=subset of 16550 */ + UINT8 Reserved[3]; + ACPI_GENERIC_ADDRESS SerialPort; + UINT8 InterruptType; + UINT8 PcInterrupt; + UINT32 Interrupt; + UINT8 BaudRate; + UINT8 Parity; + UINT8 StopBits; + UINT8 FlowControl; + UINT8 TerminalType; + UINT8 Reserved1; + UINT16 PciDeviceId; + UINT16 PciVendorId; + UINT8 PciBus; + UINT8 PciDevice; + UINT8 PciFunction; + UINT32 PciFlags; + UINT8 PciSegment; + UINT32 Reserved2; + +} ACPI_TABLE_SPCR; + +/* Masks for PciFlags field above */ + +#define ACPI_SPCR_DO_NOT_DISABLE (1) + +/* Values for Interface Type: See the definition of the DBG2 table */ + + +/******************************************************************************* + * + * SPMI - Server Platform Management Interface table + * Version 5 + * + * Conforms to "Intelligent Platform Management Interface Specification + * Second Generation v2.0", Document Revision 1.0, February 12, 2004 with + * June 12, 2009 markup. + * + ******************************************************************************/ + +typedef struct acpi_table_spmi +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 InterfaceType; + UINT8 Reserved; /* Must be 1 */ + UINT16 SpecRevision; /* Version of IPMI */ + UINT8 InterruptType; + UINT8 GpeNumber; /* GPE assigned */ + UINT8 Reserved1; + UINT8 PciDeviceFlag; + UINT32 Interrupt; + ACPI_GENERIC_ADDRESS IpmiRegister; + UINT8 PciSegment; + UINT8 PciBus; + UINT8 PciDevice; + UINT8 PciFunction; + UINT8 Reserved2; + +} ACPI_TABLE_SPMI; + +/* Values for InterfaceType above */ + +enum AcpiSpmiInterfaceTypes +{ + ACPI_SPMI_NOT_USED = 0, + ACPI_SPMI_KEYBOARD = 1, + ACPI_SPMI_SMI = 2, + ACPI_SPMI_BLOCK_TRANSFER = 3, + ACPI_SPMI_SMBUS = 4, + ACPI_SPMI_RESERVED = 5 /* 5 and above are reserved */ +}; + + +/******************************************************************************* + * + * SRAT - System Resource Affinity Table + * Version 3 + * + ******************************************************************************/ + +typedef struct acpi_table_srat +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 TableRevision; /* Must be value '1' */ + UINT64 Reserved; /* Reserved, must be zero */ + +} ACPI_TABLE_SRAT; + +/* Values for subtable type in ACPI_SUBTABLE_HEADER */ + +enum AcpiSratType +{ + ACPI_SRAT_TYPE_CPU_AFFINITY = 0, + ACPI_SRAT_TYPE_MEMORY_AFFINITY = 1, + ACPI_SRAT_TYPE_X2APIC_CPU_AFFINITY = 2, + ACPI_SRAT_TYPE_GICC_AFFINITY = 3, + ACPI_SRAT_TYPE_GIC_ITS_AFFINITY = 4, /* ACPI 6.2 */ + ACPI_SRAT_TYPE_RESERVED = 5 /* 5 and greater are reserved */ +}; + +/* + * SRAT Subtables, correspond to Type in ACPI_SUBTABLE_HEADER + */ + +/* 0: Processor Local APIC/SAPIC Affinity */ + +typedef struct acpi_srat_cpu_affinity +{ + ACPI_SUBTABLE_HEADER Header; + UINT8 ProximityDomainLo; + UINT8 ApicId; + UINT32 Flags; + UINT8 LocalSapicEid; + UINT8 ProximityDomainHi[3]; + UINT32 ClockDomain; + +} ACPI_SRAT_CPU_AFFINITY; + +/* Flags */ + +#define ACPI_SRAT_CPU_USE_AFFINITY (1) /* 00: Use affinity structure */ + + +/* 1: Memory Affinity */ + +typedef struct acpi_srat_mem_affinity +{ + ACPI_SUBTABLE_HEADER Header; + UINT32 ProximityDomain; + UINT16 Reserved; /* Reserved, must be zero */ + UINT64 BaseAddress; + UINT64 Length; + UINT32 Reserved1; + UINT32 Flags; + UINT64 Reserved2; /* Reserved, must be zero */ + +} ACPI_SRAT_MEM_AFFINITY; + +/* Flags */ + +#define ACPI_SRAT_MEM_ENABLED (1) /* 00: Use affinity structure */ +#define ACPI_SRAT_MEM_HOT_PLUGGABLE (1<<1) /* 01: Memory region is hot pluggable */ +#define ACPI_SRAT_MEM_NON_VOLATILE (1<<2) /* 02: Memory region is non-volatile */ + + +/* 2: Processor Local X2_APIC Affinity (ACPI 4.0) */ + +typedef struct acpi_srat_x2apic_cpu_affinity +{ + ACPI_SUBTABLE_HEADER Header; + UINT16 Reserved; /* Reserved, must be zero */ + UINT32 ProximityDomain; + UINT32 ApicId; + UINT32 Flags; + UINT32 ClockDomain; + UINT32 Reserved2; + +} ACPI_SRAT_X2APIC_CPU_AFFINITY; + +/* Flags for ACPI_SRAT_CPU_AFFINITY and ACPI_SRAT_X2APIC_CPU_AFFINITY */ + +#define ACPI_SRAT_CPU_ENABLED (1) /* 00: Use affinity structure */ + + +/* 3: GICC Affinity (ACPI 5.1) */ + +typedef struct acpi_srat_gicc_affinity +{ + ACPI_SUBTABLE_HEADER Header; + UINT32 ProximityDomain; + UINT32 AcpiProcessorUid; + UINT32 Flags; + UINT32 ClockDomain; + +} ACPI_SRAT_GICC_AFFINITY; + +/* Flags for ACPI_SRAT_GICC_AFFINITY */ + +#define ACPI_SRAT_GICC_ENABLED (1) /* 00: Use affinity structure */ + + +/* 4: GCC ITS Affinity (ACPI 6.2) */ + +typedef struct acpi_srat_gic_its_affinity +{ + ACPI_SUBTABLE_HEADER Header; + UINT32 ProximityDomain; + UINT16 Reserved; + UINT32 ItsId; + +} ACPI_SRAT_GIC_ITS_AFFINITY; + + +/******************************************************************************* + * + * STAO - Status Override Table (_STA override) - ACPI 6.0 + * Version 1 + * + * Conforms to "ACPI Specification for Status Override Table" + * 6 January 2015 + * + ******************************************************************************/ + +typedef struct acpi_table_stao +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 IgnoreUart; + +} ACPI_TABLE_STAO; + + +/******************************************************************************* + * + * TCPA - Trusted Computing Platform Alliance table + * Version 2 + * + * TCG Hardware Interface Table for TPM 1.2 Clients and Servers + * + * Conforms to "TCG ACPI Specification, Family 1.2 and 2.0", + * Version 1.2, Revision 8 + * February 27, 2017 + * + * NOTE: There are two versions of the table with the same signature -- + * the client version and the server version. The common PlatformClass + * field is used to differentiate the two types of tables. + * + ******************************************************************************/ + +typedef struct acpi_table_tcpa_hdr +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT16 PlatformClass; + +} ACPI_TABLE_TCPA_HDR; + +/* + * Values for PlatformClass above. + * This is how the client and server subtables are differentiated + */ +#define ACPI_TCPA_CLIENT_TABLE 0 +#define ACPI_TCPA_SERVER_TABLE 1 + + +typedef struct acpi_table_tcpa_client +{ + UINT32 MinimumLogLength; /* Minimum length for the event log area */ + UINT64 LogAddress; /* Address of the event log area */ + +} ACPI_TABLE_TCPA_CLIENT; + +typedef struct acpi_table_tcpa_server +{ + UINT16 Reserved; + UINT64 MinimumLogLength; /* Minimum length for the event log area */ + UINT64 LogAddress; /* Address of the event log area */ + UINT16 SpecRevision; + UINT8 DeviceFlags; + UINT8 InterruptFlags; + UINT8 GpeNumber; + UINT8 Reserved2[3]; + UINT32 GlobalInterrupt; + ACPI_GENERIC_ADDRESS Address; + UINT32 Reserved3; + ACPI_GENERIC_ADDRESS ConfigAddress; + UINT8 Group; + UINT8 Bus; /* PCI Bus/Segment/Function numbers */ + UINT8 Device; + UINT8 Function; + +} ACPI_TABLE_TCPA_SERVER; + +/* Values for DeviceFlags above */ + +#define ACPI_TCPA_PCI_DEVICE (1) +#define ACPI_TCPA_BUS_PNP (1<<1) +#define ACPI_TCPA_ADDRESS_VALID (1<<2) + +/* Values for InterruptFlags above */ + +#define ACPI_TCPA_INTERRUPT_MODE (1) +#define ACPI_TCPA_INTERRUPT_POLARITY (1<<1) +#define ACPI_TCPA_SCI_VIA_GPE (1<<2) +#define ACPI_TCPA_GLOBAL_INTERRUPT (1<<3) + + +/******************************************************************************* + * + * TPM2 - Trusted Platform Module (TPM) 2.0 Hardware Interface Table + * Version 4 + * + * TCG Hardware Interface Table for TPM 2.0 Clients and Servers + * + * Conforms to "TCG ACPI Specification, Family 1.2 and 2.0", + * Version 1.2, Revision 8 + * February 27, 2017 + * + ******************************************************************************/ + +typedef struct acpi_table_tpm2 +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT16 PlatformClass; + UINT16 Reserved; + UINT64 ControlAddress; + UINT32 StartMethod; + + /* Platform-specific data follows */ + +} ACPI_TABLE_TPM2; + +/* Values for StartMethod above */ + +#define ACPI_TPM2_NOT_ALLOWED 0 +#define ACPI_TPM2_RESERVED1 1 +#define ACPI_TPM2_START_METHOD 2 +#define ACPI_TPM2_RESERVED3 3 +#define ACPI_TPM2_RESERVED4 4 +#define ACPI_TPM2_RESERVED5 5 +#define ACPI_TPM2_MEMORY_MAPPED 6 +#define ACPI_TPM2_COMMAND_BUFFER 7 +#define ACPI_TPM2_COMMAND_BUFFER_WITH_START_METHOD 8 +#define ACPI_TPM2_RESERVED9 9 +#define ACPI_TPM2_RESERVED10 10 +#define ACPI_TPM2_COMMAND_BUFFER_WITH_ARM_SMC 11 /* V1.2 Rev 8 */ +#define ACPI_TPM2_RESERVED 12 + + +/* Optional trailer appears after any StartMethod subtables */ + +typedef struct acpi_tpm2_trailer +{ + UINT8 MethodParameters[12]; + UINT32 MinimumLogLength; /* Minimum length for the event log area */ + UINT64 LogAddress; /* Address of the event log area */ + +} ACPI_TPM2_TRAILER; + + +/* + * Subtables (StartMethod-specific) + */ + +/* 11: Start Method for ARM SMC (V1.2 Rev 8) */ + +typedef struct acpi_tpm2_arm_smc +{ + UINT32 GlobalInterrupt; + UINT8 InterruptFlags; + UINT8 OperationFlags; + UINT16 Reserved; + UINT32 FunctionId; + +} ACPI_TPM2_ARM_SMC; + +/* Values for InterruptFlags above */ + +#define ACPI_TPM2_INTERRUPT_SUPPORT (1) + +/* Values for OperationFlags above */ + +#define ACPI_TPM2_IDLE_SUPPORT (1) + + +/******************************************************************************* + * + * UEFI - UEFI Boot optimization Table + * Version 1 + * + * Conforms to "Unified Extensible Firmware Interface Specification", + * Version 2.3, May 8, 2009 + * + ******************************************************************************/ + +typedef struct acpi_table_uefi +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT8 Identifier[16]; /* UUID identifier */ + UINT16 DataOffset; /* Offset of remaining data in table */ + +} ACPI_TABLE_UEFI; + + +/******************************************************************************* + * + * VRTC - Virtual Real Time Clock Table + * Version 1 + * + * Conforms to "Simple Firmware Interface Specification", + * Draft 0.8.2, Oct 19, 2010 + * NOTE: The ACPI VRTC is equivalent to The SFI MRTC table. + * + ******************************************************************************/ + +typedef struct acpi_table_vrtc +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + +} ACPI_TABLE_VRTC; + +/* VRTC entry */ + +typedef struct acpi_vrtc_entry +{ + ACPI_GENERIC_ADDRESS PhysicalAddress; + UINT32 Irq; + +} ACPI_VRTC_ENTRY; + + +/******************************************************************************* + * + * WAET - Windows ACPI Emulated devices Table + * Version 1 + * + * Conforms to "Windows ACPI Emulated Devices Table", version 1.0, April 6, 2009 + * + ******************************************************************************/ + +typedef struct acpi_table_waet +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 Flags; + +} ACPI_TABLE_WAET; + +/* Masks for Flags field above */ + +#define ACPI_WAET_RTC_NO_ACK (1) /* RTC requires no int acknowledge */ +#define ACPI_WAET_TIMER_ONE_READ (1<<1) /* PM timer requires only one read */ + + +/******************************************************************************* + * + * WDAT - Watchdog Action Table + * Version 1 + * + * Conforms to "Hardware Watchdog Timers Design Specification", + * Copyright 2006 Microsoft Corporation. + * + ******************************************************************************/ + +typedef struct acpi_table_wdat +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 HeaderLength; /* Watchdog Header Length */ + UINT16 PciSegment; /* PCI Segment number */ + UINT8 PciBus; /* PCI Bus number */ + UINT8 PciDevice; /* PCI Device number */ + UINT8 PciFunction; /* PCI Function number */ + UINT8 Reserved[3]; + UINT32 TimerPeriod; /* Period of one timer count (msec) */ + UINT32 MaxCount; /* Maximum counter value supported */ + UINT32 MinCount; /* Minimum counter value */ + UINT8 Flags; + UINT8 Reserved2[3]; + UINT32 Entries; /* Number of watchdog entries that follow */ + +} ACPI_TABLE_WDAT; + +/* Masks for Flags field above */ + +#define ACPI_WDAT_ENABLED (1) +#define ACPI_WDAT_STOPPED 0x80 + + +/* WDAT Instruction Entries (actions) */ + +typedef struct acpi_wdat_entry +{ + UINT8 Action; + UINT8 Instruction; + UINT16 Reserved; + ACPI_GENERIC_ADDRESS RegisterRegion; + UINT32 Value; /* Value used with Read/Write register */ + UINT32 Mask; /* Bitmask required for this register instruction */ + +} ACPI_WDAT_ENTRY; + +/* Values for Action field above */ + +enum AcpiWdatActions +{ + ACPI_WDAT_RESET = 1, + ACPI_WDAT_GET_CURRENT_COUNTDOWN = 4, + ACPI_WDAT_GET_COUNTDOWN = 5, + ACPI_WDAT_SET_COUNTDOWN = 6, + ACPI_WDAT_GET_RUNNING_STATE = 8, + ACPI_WDAT_SET_RUNNING_STATE = 9, + ACPI_WDAT_GET_STOPPED_STATE = 10, + ACPI_WDAT_SET_STOPPED_STATE = 11, + ACPI_WDAT_GET_REBOOT = 16, + ACPI_WDAT_SET_REBOOT = 17, + ACPI_WDAT_GET_SHUTDOWN = 18, + ACPI_WDAT_SET_SHUTDOWN = 19, + ACPI_WDAT_GET_STATUS = 32, + ACPI_WDAT_SET_STATUS = 33, + ACPI_WDAT_ACTION_RESERVED = 34 /* 34 and greater are reserved */ +}; + +/* Values for Instruction field above */ + +enum AcpiWdatInstructions +{ + ACPI_WDAT_READ_VALUE = 0, + ACPI_WDAT_READ_COUNTDOWN = 1, + ACPI_WDAT_WRITE_VALUE = 2, + ACPI_WDAT_WRITE_COUNTDOWN = 3, + ACPI_WDAT_INSTRUCTION_RESERVED = 4, /* 4 and greater are reserved */ + ACPI_WDAT_PRESERVE_REGISTER = 0x80 /* Except for this value */ +}; + + +/******************************************************************************* + * + * WDDT - Watchdog Descriptor Table + * Version 1 + * + * Conforms to "Using the Intel ICH Family Watchdog Timer (WDT)", + * Version 001, September 2002 + * + ******************************************************************************/ + +typedef struct acpi_table_wddt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT16 SpecVersion; + UINT16 TableVersion; + UINT16 PciVendorId; + ACPI_GENERIC_ADDRESS Address; + UINT16 MaxCount; /* Maximum counter value supported */ + UINT16 MinCount; /* Minimum counter value supported */ + UINT16 Period; + UINT16 Status; + UINT16 Capability; + +} ACPI_TABLE_WDDT; + +/* Flags for Status field above */ + +#define ACPI_WDDT_AVAILABLE (1) +#define ACPI_WDDT_ACTIVE (1<<1) +#define ACPI_WDDT_TCO_OS_OWNED (1<<2) +#define ACPI_WDDT_USER_RESET (1<<11) +#define ACPI_WDDT_WDT_RESET (1<<12) +#define ACPI_WDDT_POWER_FAIL (1<<13) +#define ACPI_WDDT_UNKNOWN_RESET (1<<14) + +/* Flags for Capability field above */ + +#define ACPI_WDDT_AUTO_RESET (1) +#define ACPI_WDDT_ALERT_SUPPORT (1<<1) + + +/******************************************************************************* + * + * WDRT - Watchdog Resource Table + * Version 1 + * + * Conforms to "Watchdog Timer Hardware Requirements for Windows Server 2003", + * Version 1.01, August 28, 2006 + * + ******************************************************************************/ + +typedef struct acpi_table_wdrt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + ACPI_GENERIC_ADDRESS ControlRegister; + ACPI_GENERIC_ADDRESS CountRegister; + UINT16 PciDeviceId; + UINT16 PciVendorId; + UINT8 PciBus; /* PCI Bus number */ + UINT8 PciDevice; /* PCI Device number */ + UINT8 PciFunction; /* PCI Function number */ + UINT8 PciSegment; /* PCI Segment number */ + UINT16 MaxCount; /* Maximum counter value supported */ + UINT8 Units; + +} ACPI_TABLE_WDRT; + + +/******************************************************************************* + * + * WPBT - Windows Platform Environment Table (ACPI 6.0) + * Version 1 + * + * Conforms to "Windows Platform Binary Table (WPBT)" 29 November 2011 + * + ******************************************************************************/ + +typedef struct acpi_table_wpbt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 HandoffSize; + UINT64 HandoffAddress; + UINT8 Layout; + UINT8 Type; + UINT16 ArgumentsLength; + +} ACPI_TABLE_WPBT; + + +/******************************************************************************* + * + * WSMT - Windows SMM Security Migrations Table + * Version 1 + * + * Conforms to "Windows SMM Security Migrations Table", + * Version 1.0, April 18, 2016 + * + ******************************************************************************/ + +typedef struct acpi_table_wsmt +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT32 ProtectionFlags; + +} ACPI_TABLE_WSMT; + +/* Flags for ProtectionFlags field above */ + +#define ACPI_WSMT_FIXED_COMM_BUFFERS (1) +#define ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION (2) +#define ACPI_WSMT_SYSTEM_RESOURCE_PROTECTION (4) + + +/******************************************************************************* + * + * XENV - Xen Environment Table (ACPI 6.0) + * Version 1 + * + * Conforms to "ACPI Specification for Xen Environment Table" 4 January 2015 + * + ******************************************************************************/ + +typedef struct acpi_table_xenv +{ + ACPI_TABLE_HEADER Header; /* Common ACPI table header */ + UINT64 GrantTableAddress; + UINT64 GrantTableSize; + UINT32 EventInterrupt; + UINT8 EventFlags; + +} ACPI_TABLE_XENV; + + +/* Reset to default packing */ + +#pragma pack() + +#endif /* __ACTBL3_H__ */ diff --git a/ports/acpica/include/actypes.h b/ports/acpica/include/actypes.h new file mode 100644 index 0000000..4d2438c --- /dev/null +++ b/ports/acpica/include/actypes.h @@ -0,0 +1,1522 @@ +/****************************************************************************** + * + * Name: actypes.h - Common data types for the entire ACPI subsystem + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACTYPES_H__ +#define __ACTYPES_H__ + +/* acpisrc:StructDefs -- for acpisrc conversion */ + +/* + * ACPI_MACHINE_WIDTH must be specified in an OS- or compiler-dependent + * header and must be either 32 or 64. 16-bit ACPICA is no longer + * supported, as of 12/2006. + */ +#ifndef ACPI_MACHINE_WIDTH +#error ACPI_MACHINE_WIDTH not defined +#endif + +/* + * Data type ranges + * Note: These macros are designed to be compiler independent as well as + * working around problems that some 32-bit compilers have with 64-bit + * constants. + */ +#define ACPI_UINT8_MAX (UINT8) (~((UINT8) 0)) /* 0xFF */ +#define ACPI_UINT16_MAX (UINT16)(~((UINT16) 0)) /* 0xFFFF */ +#define ACPI_UINT32_MAX (UINT32)(~((UINT32) 0)) /* 0xFFFFFFFF */ +#define ACPI_UINT64_MAX (UINT64)(~((UINT64) 0)) /* 0xFFFFFFFFFFFFFFFF */ +#define ACPI_ASCII_MAX 0x7F + + +/* + * Architecture-specific ACPICA Subsystem Data Types + * + * The goal of these types is to provide source code portability across + * 16-bit, 32-bit, and 64-bit targets. + * + * 1) The following types are of fixed size for all targets (16/32/64): + * + * BOOLEAN Logical boolean + * + * UINT8 8-bit (1 byte) unsigned value + * UINT16 16-bit (2 byte) unsigned value + * UINT32 32-bit (4 byte) unsigned value + * UINT64 64-bit (8 byte) unsigned value + * + * INT16 16-bit (2 byte) signed value + * INT32 32-bit (4 byte) signed value + * INT64 64-bit (8 byte) signed value + * + * COMPILER_DEPENDENT_UINT64/INT64 - These types are defined in the + * compiler-dependent header(s) and were introduced because there is no + * common 64-bit integer type across the various compilation models, as + * shown in the table below. + * + * Datatype LP64 ILP64 LLP64 ILP32 LP32 16bit + * char 8 8 8 8 8 8 + * short 16 16 16 16 16 16 + * _int32 32 + * int 32 64 32 32 16 16 + * long 64 64 32 32 32 32 + * long long 64 64 + * pointer 64 64 64 32 32 32 + * + * Note: ILP64 and LP32 are currently not supported. + * + * + * 2) These types represent the native word size of the target mode of the + * processor, and may be 16-bit, 32-bit, or 64-bit as required. They are + * usually used for memory allocation, efficient loop counters, and array + * indexes. The types are similar to the size_t type in the C library and + * are required because there is no C type that consistently represents the + * native data width. ACPI_SIZE is needed because there is no guarantee + * that a kernel-level C library is present. + * + * ACPI_SIZE 16/32/64-bit unsigned value + * ACPI_NATIVE_INT 16/32/64-bit signed value + */ + +/******************************************************************************* + * + * Common types for all compilers, all targets + * + ******************************************************************************/ + +#ifndef ACPI_USE_SYSTEM_INTTYPES + +typedef unsigned char BOOLEAN; +typedef unsigned char UINT8; +typedef unsigned short UINT16; +typedef short INT16; +typedef COMPILER_DEPENDENT_UINT64 UINT64; +typedef COMPILER_DEPENDENT_INT64 INT64; + +#endif /* ACPI_USE_SYSTEM_INTTYPES */ + +/* + * Value returned by AcpiOsGetThreadId. There is no standard "thread_id" + * across operating systems or even the various UNIX systems. Since ACPICA + * only needs the thread ID as a unique thread identifier, we use a UINT64 + * as the only common data type - it will accommodate any type of pointer or + * any type of integer. It is up to the host-dependent OSL to cast the + * native thread ID type to a UINT64 (in AcpiOsGetThreadId). + */ +#define ACPI_THREAD_ID UINT64 + + +/******************************************************************************* + * + * Types specific to 64-bit targets + * + ******************************************************************************/ + +#if ACPI_MACHINE_WIDTH == 64 + +#ifndef ACPI_USE_SYSTEM_INTTYPES + +typedef unsigned int UINT32; +typedef int INT32; + +#endif /* ACPI_USE_SYSTEM_INTTYPES */ + + +typedef INT64 ACPI_NATIVE_INT; +typedef UINT64 ACPI_SIZE; +typedef UINT64 ACPI_IO_ADDRESS; +typedef UINT64 ACPI_PHYSICAL_ADDRESS; + +#define ACPI_MAX_PTR ACPI_UINT64_MAX +#define ACPI_SIZE_MAX ACPI_UINT64_MAX +#define ACPI_USE_NATIVE_DIVIDE /* Has native 64-bit integer support */ +#define ACPI_USE_NATIVE_MATH64 /* Has native 64-bit integer support */ + +/* + * In the case of the Itanium Processor Family (IPF), the hardware does not + * support misaligned memory transfers. Set the MISALIGNMENT_NOT_SUPPORTED + * flag to indicate that special precautions must be taken to avoid alignment + * faults. (IA64 or ia64 is currently used by existing compilers to indicate + * IPF.) + * + * Note: EM64T and other X86-64 processors support misaligned transfers, + * so there is no need to define this flag. + */ +#if defined (__IA64__) || defined (__ia64__) +#define ACPI_MISALIGNMENT_NOT_SUPPORTED +#endif + + +/******************************************************************************* + * + * Types specific to 32-bit targets + * + ******************************************************************************/ + +#elif ACPI_MACHINE_WIDTH == 32 + +#ifndef ACPI_USE_SYSTEM_INTTYPES + +typedef unsigned int UINT32; +typedef int INT32; + +#endif /* ACPI_USE_SYSTEM_INTTYPES */ + + +typedef INT32 ACPI_NATIVE_INT; +typedef UINT32 ACPI_SIZE; + +#ifdef ACPI_32BIT_PHYSICAL_ADDRESS + +/* + * OSPMs can define this to shrink the size of the structures for 32-bit + * none PAE environment. ASL compiler may always define this to generate + * 32-bit OSPM compliant tables. + */ +typedef UINT32 ACPI_IO_ADDRESS; +typedef UINT32 ACPI_PHYSICAL_ADDRESS; + +#else /* ACPI_32BIT_PHYSICAL_ADDRESS */ + +/* + * It is reported that, after some calculations, the physical addresses can + * wrap over the 32-bit boundary on 32-bit PAE environment. + * https://bugzilla.kernel.org/show_bug.cgi?id=87971 + */ +typedef UINT64 ACPI_IO_ADDRESS; +typedef UINT64 ACPI_PHYSICAL_ADDRESS; + +#endif /* ACPI_32BIT_PHYSICAL_ADDRESS */ + +#define ACPI_MAX_PTR ACPI_UINT32_MAX +#define ACPI_SIZE_MAX ACPI_UINT32_MAX + +#else + +/* ACPI_MACHINE_WIDTH must be either 64 or 32 */ + +#error unknown ACPI_MACHINE_WIDTH +#endif + + +/******************************************************************************* + * + * OS-dependent types + * + * If the defaults below are not appropriate for the host system, they can + * be defined in the OS-specific header, and this will take precedence. + * + ******************************************************************************/ + +/* Flags for AcpiOsAcquireLock/AcpiOsReleaseLock */ + +#ifndef ACPI_CPU_FLAGS +#define ACPI_CPU_FLAGS ACPI_SIZE +#endif + +/* Object returned from AcpiOsCreateCache */ + +#ifndef ACPI_CACHE_T +#ifdef ACPI_USE_LOCAL_CACHE +#define ACPI_CACHE_T ACPI_MEMORY_LIST +#else +#define ACPI_CACHE_T void * +#endif +#endif + +/* + * Synchronization objects - Mutexes, Semaphores, and SpinLocks + */ +#if (ACPI_MUTEX_TYPE == ACPI_BINARY_SEMAPHORE) +/* + * These macros are used if the host OS does not support a mutex object. + * Map the OSL Mutex interfaces to binary semaphores. + */ +#define ACPI_MUTEX ACPI_SEMAPHORE +#define AcpiOsCreateMutex(OutHandle) AcpiOsCreateSemaphore (1, 1, OutHandle) +#define AcpiOsDeleteMutex(Handle) (void) AcpiOsDeleteSemaphore (Handle) +#define AcpiOsAcquireMutex(Handle,Time) AcpiOsWaitSemaphore (Handle, 1, Time) +#define AcpiOsReleaseMutex(Handle) (void) AcpiOsSignalSemaphore (Handle, 1) +#endif + +/* Configurable types for synchronization objects */ + +#ifndef ACPI_SPINLOCK +#define ACPI_SPINLOCK void * +#endif + +#ifndef ACPI_SEMAPHORE +#define ACPI_SEMAPHORE void * +#endif + +#ifndef ACPI_MUTEX +#define ACPI_MUTEX void * +#endif + + +/******************************************************************************* + * + * Compiler-dependent types + * + * If the defaults below are not appropriate for the host compiler, they can + * be defined in the compiler-specific header, and this will take precedence. + * + ******************************************************************************/ + +/* Use C99 uintptr_t for pointer casting if available, "void *" otherwise */ + +#ifndef ACPI_UINTPTR_T +#define ACPI_UINTPTR_T void * +#endif + +/* + * ACPI_PRINTF_LIKE is used to tag functions as "printf-like" because + * some compilers can catch printf format string problems + */ +#ifndef ACPI_PRINTF_LIKE +#define ACPI_PRINTF_LIKE(c) +#endif + +/* + * Some compilers complain about unused variables. Sometimes we don't want + * to use all the variables (for example, _AcpiModuleName). This allows us + * to tell the compiler in a per-variable manner that a variable + * is unused + */ +#ifndef ACPI_UNUSED_VAR +#define ACPI_UNUSED_VAR +#endif + +/* + * All ACPICA external functions that are available to the rest of the + * kernel are tagged with these macros which can be defined as appropriate + * for the host. + * + * Notes: + * ACPI_EXPORT_SYMBOL_INIT is used for initialization and termination + * interfaces that may need special processing. + * ACPI_EXPORT_SYMBOL is used for all other public external functions. + */ +#ifndef ACPI_EXPORT_SYMBOL_INIT +#define ACPI_EXPORT_SYMBOL_INIT(Symbol) +#endif + +#ifndef ACPI_EXPORT_SYMBOL +#define ACPI_EXPORT_SYMBOL(Symbol) +#endif + +/* + * Compiler/Clibrary-dependent debug initialization. Used for ACPICA + * utilities only. + */ +#ifndef ACPI_DEBUG_INITIALIZE +#define ACPI_DEBUG_INITIALIZE() +#endif + + +/******************************************************************************* + * + * Configuration + * + ******************************************************************************/ + +#ifdef ACPI_NO_MEM_ALLOCATIONS + +#define ACPI_ALLOCATE(a) NULL +#define ACPI_ALLOCATE_ZEROED(a) NULL +#define ACPI_FREE(a) +#define ACPI_MEM_TRACKING(a) + +#else /* ACPI_NO_MEM_ALLOCATIONS */ + +#ifdef ACPI_DBG_TRACK_ALLOCATIONS +/* + * Memory allocation tracking (used by AcpiExec to detect memory leaks) + */ +#define ACPI_MEM_PARAMETERS _COMPONENT, _AcpiModuleName, __LINE__ +#define ACPI_ALLOCATE(a) AcpiUtAllocateAndTrack ((ACPI_SIZE) (a), ACPI_MEM_PARAMETERS) +#define ACPI_ALLOCATE_ZEROED(a) AcpiUtAllocateZeroedAndTrack ((ACPI_SIZE) (a), ACPI_MEM_PARAMETERS) +#define ACPI_FREE(a) AcpiUtFreeAndTrack (a, ACPI_MEM_PARAMETERS) +#define ACPI_MEM_TRACKING(a) a + +#else +/* + * Normal memory allocation directly via the OS services layer + */ +#define ACPI_ALLOCATE(a) AcpiOsAllocate ((ACPI_SIZE) (a)) +#define ACPI_ALLOCATE_ZEROED(a) AcpiOsAllocateZeroed ((ACPI_SIZE) (a)) +#define ACPI_FREE(a) AcpiOsFree (a) +#define ACPI_MEM_TRACKING(a) + +#endif /* ACPI_DBG_TRACK_ALLOCATIONS */ + +#endif /* ACPI_NO_MEM_ALLOCATIONS */ + + +/****************************************************************************** + * + * ACPI Specification constants (Do not change unless the specification + * changes) + * + *****************************************************************************/ + +/* Number of distinct FADT-based GPE register blocks (GPE0 and GPE1) */ + +#define ACPI_MAX_GPE_BLOCKS 2 + +/* Default ACPI register widths */ + +#define ACPI_GPE_REGISTER_WIDTH 8 +#define ACPI_PM1_REGISTER_WIDTH 16 +#define ACPI_PM2_REGISTER_WIDTH 8 +#define ACPI_PM_TIMER_WIDTH 32 +#define ACPI_RESET_REGISTER_WIDTH 8 + +/* Names within the namespace are 4 bytes long */ + +#define ACPI_NAME_SIZE 4 +#define ACPI_PATH_SEGMENT_LENGTH 5 /* 4 chars for name + 1 char for separator */ +#define ACPI_PATH_SEPARATOR '.' + +/* Sizes for ACPI table headers */ + +#define ACPI_OEM_ID_SIZE 6 +#define ACPI_OEM_TABLE_ID_SIZE 8 + +/* ACPI/PNP hardware IDs */ + +#define PCI_ROOT_HID_STRING "PNP0A03" +#define PCI_EXPRESS_ROOT_HID_STRING "PNP0A08" + +/* PM Timer ticks per second (HZ) */ + +#define ACPI_PM_TIMER_FREQUENCY 3579545 + + +/******************************************************************************* + * + * Independent types + * + ******************************************************************************/ + +/* Logical defines and NULL */ + +#ifdef FALSE +#undef FALSE +#endif +#define FALSE (1 == 0) + +#ifdef TRUE +#undef TRUE +#endif +#define TRUE (1 == 1) + +#ifndef NULL +#define NULL (void *) 0 +#endif + + +/* + * Miscellaneous types + */ +typedef UINT32 ACPI_STATUS; /* All ACPI Exceptions */ +typedef UINT32 ACPI_NAME; /* 4-byte ACPI name */ +typedef char * ACPI_STRING; /* Null terminated ASCII string */ +typedef void * ACPI_HANDLE; /* Actually a ptr to a NS Node */ + + +/* Time constants for timer calculations */ + +#define ACPI_MSEC_PER_SEC 1000L + +#define ACPI_USEC_PER_MSEC 1000L +#define ACPI_USEC_PER_SEC 1000000L + +#define ACPI_100NSEC_PER_USEC 10L +#define ACPI_100NSEC_PER_MSEC 10000L +#define ACPI_100NSEC_PER_SEC 10000000L + +#define ACPI_NSEC_PER_USEC 1000L +#define ACPI_NSEC_PER_MSEC 1000000L +#define ACPI_NSEC_PER_SEC 1000000000L + +#define ACPI_TIME_AFTER(a, b) ((INT64)((b) - (a)) < 0) + + +/* Owner IDs are used to track namespace nodes for selective deletion */ + +typedef UINT8 ACPI_OWNER_ID; +#define ACPI_OWNER_ID_MAX 0xFF + + +#define ACPI_INTEGER_BIT_SIZE 64 +#define ACPI_MAX_DECIMAL_DIGITS 20 /* 2^64 = 18,446,744,073,709,551,616 */ +#define ACPI_MAX64_DECIMAL_DIGITS 20 +#define ACPI_MAX32_DECIMAL_DIGITS 10 +#define ACPI_MAX16_DECIMAL_DIGITS 5 +#define ACPI_MAX8_DECIMAL_DIGITS 3 + +/* + * Constants with special meanings + */ +#define ACPI_ROOT_OBJECT ((ACPI_HANDLE) ACPI_TO_POINTER (ACPI_MAX_PTR)) +#define ACPI_WAIT_FOREVER 0xFFFF /* UINT16, as per ACPI spec */ +#define ACPI_DO_NOT_WAIT 0 + +/* + * Obsolete: Acpi integer width. In ACPI version 1 (1996), integers are + * 32 bits. In ACPI version 2 (2000) and later, integers are max 64 bits. + * Note that this pertains to the ACPI integer type only, not to other + * integers used in the implementation of the ACPICA subsystem. + * + * 01/2010: This type is obsolete and has been removed from the entire ACPICA + * code base. It remains here for compatibility with device drivers that use + * the type. However, it will be removed in the future. + */ +typedef UINT64 ACPI_INTEGER; +#define ACPI_INTEGER_MAX ACPI_UINT64_MAX + + +/******************************************************************************* + * + * Commonly used macros + * + ******************************************************************************/ + +/* Data manipulation */ + +#define ACPI_LOBYTE(Integer) ((UINT8) (UINT16)(Integer)) +#define ACPI_HIBYTE(Integer) ((UINT8) (((UINT16)(Integer)) >> 8)) +#define ACPI_LOWORD(Integer) ((UINT16) (UINT32)(Integer)) +#define ACPI_HIWORD(Integer) ((UINT16)(((UINT32)(Integer)) >> 16)) +#define ACPI_LODWORD(Integer64) ((UINT32) (UINT64)(Integer64)) +#define ACPI_HIDWORD(Integer64) ((UINT32)(((UINT64)(Integer64)) >> 32)) + +#define ACPI_SET_BIT(target,bit) ((target) |= (bit)) +#define ACPI_CLEAR_BIT(target,bit) ((target) &= ~(bit)) +#define ACPI_MIN(a,b) (((a)<(b))?(a):(b)) +#define ACPI_MAX(a,b) (((a)>(b))?(a):(b)) + +/* Size calculation */ + +#define ACPI_ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) + +/* Pointer manipulation */ + +#define ACPI_CAST_PTR(t, p) ((t *) (ACPI_UINTPTR_T) (p)) +#define ACPI_CAST_INDIRECT_PTR(t, p) ((t **) (ACPI_UINTPTR_T) (p)) +#define ACPI_ADD_PTR(t, a, b) ACPI_CAST_PTR (t, (ACPI_CAST_PTR (UINT8, (a)) + (ACPI_SIZE)(b))) +#define ACPI_SUB_PTR(t, a, b) ACPI_CAST_PTR (t, (ACPI_CAST_PTR (UINT8, (a)) - (ACPI_SIZE)(b))) +#define ACPI_PTR_DIFF(a, b) ((ACPI_SIZE) (ACPI_CAST_PTR (UINT8, (a)) - ACPI_CAST_PTR (UINT8, (b)))) + +/* Pointer/Integer type conversions */ + +#define ACPI_TO_POINTER(i) ACPI_ADD_PTR (void, (void *) 0, (ACPI_SIZE) (i)) +#define ACPI_TO_INTEGER(p) ACPI_PTR_DIFF (p, (void *) 0) +#define ACPI_OFFSET(d, f) ACPI_PTR_DIFF (&(((d *) 0)->f), (void *) 0) +#define ACPI_PHYSADDR_TO_PTR(i) ACPI_TO_POINTER(i) +#define ACPI_PTR_TO_PHYSADDR(i) ACPI_TO_INTEGER(i) + +/* Optimizations for 4-character (32-bit) ACPI_NAME manipulation */ + +#ifndef ACPI_MISALIGNMENT_NOT_SUPPORTED +#define ACPI_COMPARE_NAME(a,b) (*ACPI_CAST_PTR (UINT32, (a)) == *ACPI_CAST_PTR (UINT32, (b))) +#define ACPI_MOVE_NAME(dest,src) (*ACPI_CAST_PTR (UINT32, (dest)) = *ACPI_CAST_PTR (UINT32, (src))) +#else +#define ACPI_COMPARE_NAME(a,b) (!strncmp (ACPI_CAST_PTR (char, (a)), ACPI_CAST_PTR (char, (b)), ACPI_NAME_SIZE)) +#define ACPI_MOVE_NAME(dest,src) (strncpy (ACPI_CAST_PTR (char, (dest)), ACPI_CAST_PTR (char, (src)), ACPI_NAME_SIZE)) +#endif + +/* Support for the special RSDP signature (8 characters) */ + +#define ACPI_VALIDATE_RSDP_SIG(a) (!strncmp (ACPI_CAST_PTR (char, (a)), ACPI_SIG_RSDP, 8)) +#define ACPI_MAKE_RSDP_SIG(dest) (memcpy (ACPI_CAST_PTR (char, (dest)), ACPI_SIG_RSDP, 8)) + +/* + * Algorithm to obtain access bit width. + * Can be used with AccessWidth of ACPI_GENERIC_ADDRESS and AccessSize of + * ACPI_RESOURCE_GENERIC_REGISTER. + */ +#define ACPI_ACCESS_BIT_WIDTH(size) (1 << ((size) + 2)) + + +/******************************************************************************* + * + * Miscellaneous constants + * + ******************************************************************************/ + +/* + * Initialization sequence options + */ +#define ACPI_FULL_INITIALIZATION 0x0000 +#define ACPI_NO_FACS_INIT 0x0001 +#define ACPI_NO_ACPI_ENABLE 0x0002 +#define ACPI_NO_HARDWARE_INIT 0x0004 +#define ACPI_NO_EVENT_INIT 0x0008 +#define ACPI_NO_HANDLER_INIT 0x0010 +#define ACPI_NO_OBJECT_INIT 0x0020 +#define ACPI_NO_DEVICE_INIT 0x0040 +#define ACPI_NO_ADDRESS_SPACE_INIT 0x0080 + +/* + * Initialization state + */ +#define ACPI_SUBSYSTEM_INITIALIZE 0x01 +#define ACPI_INITIALIZED_OK 0x02 + +/* + * Power state values + */ +#define ACPI_STATE_UNKNOWN (UINT8) 0xFF + +#define ACPI_STATE_S0 (UINT8) 0 +#define ACPI_STATE_S1 (UINT8) 1 +#define ACPI_STATE_S2 (UINT8) 2 +#define ACPI_STATE_S3 (UINT8) 3 +#define ACPI_STATE_S4 (UINT8) 4 +#define ACPI_STATE_S5 (UINT8) 5 +#define ACPI_S_STATES_MAX ACPI_STATE_S5 +#define ACPI_S_STATE_COUNT 6 + +#define ACPI_STATE_D0 (UINT8) 0 +#define ACPI_STATE_D1 (UINT8) 1 +#define ACPI_STATE_D2 (UINT8) 2 +#define ACPI_STATE_D3 (UINT8) 3 +#define ACPI_D_STATES_MAX ACPI_STATE_D3 +#define ACPI_D_STATE_COUNT 4 + +#define ACPI_STATE_C0 (UINT8) 0 +#define ACPI_STATE_C1 (UINT8) 1 +#define ACPI_STATE_C2 (UINT8) 2 +#define ACPI_STATE_C3 (UINT8) 3 +#define ACPI_C_STATES_MAX ACPI_STATE_C3 +#define ACPI_C_STATE_COUNT 4 + +/* + * Sleep type invalid value + */ +#define ACPI_SLEEP_TYPE_MAX 0x7 +#define ACPI_SLEEP_TYPE_INVALID 0xFF + +/* + * Standard notify values + */ +#define ACPI_NOTIFY_BUS_CHECK (UINT8) 0x00 +#define ACPI_NOTIFY_DEVICE_CHECK (UINT8) 0x01 +#define ACPI_NOTIFY_DEVICE_WAKE (UINT8) 0x02 +#define ACPI_NOTIFY_EJECT_REQUEST (UINT8) 0x03 +#define ACPI_NOTIFY_DEVICE_CHECK_LIGHT (UINT8) 0x04 +#define ACPI_NOTIFY_FREQUENCY_MISMATCH (UINT8) 0x05 +#define ACPI_NOTIFY_BUS_MODE_MISMATCH (UINT8) 0x06 +#define ACPI_NOTIFY_POWER_FAULT (UINT8) 0x07 +#define ACPI_NOTIFY_CAPABILITIES_CHECK (UINT8) 0x08 +#define ACPI_NOTIFY_DEVICE_PLD_CHECK (UINT8) 0x09 +#define ACPI_NOTIFY_RESERVED (UINT8) 0x0A +#define ACPI_NOTIFY_LOCALITY_UPDATE (UINT8) 0x0B +#define ACPI_NOTIFY_SHUTDOWN_REQUEST (UINT8) 0x0C +#define ACPI_NOTIFY_AFFINITY_UPDATE (UINT8) 0x0D +#define ACPI_NOTIFY_MEMORY_UPDATE (UINT8) 0x0E + +#define ACPI_GENERIC_NOTIFY_MAX 0x0E +#define ACPI_SPECIFIC_NOTIFY_MAX 0x84 + +/* + * Types associated with ACPI names and objects. The first group of + * values (up to ACPI_TYPE_EXTERNAL_MAX) correspond to the definition + * of the ACPI ObjectType() operator (See the ACPI Spec). Therefore, + * only add to the first group if the spec changes. + * + * NOTE: Types must be kept in sync with the global AcpiNsProperties + * and AcpiNsTypeNames arrays. + */ +typedef UINT32 ACPI_OBJECT_TYPE; + +#define ACPI_TYPE_ANY 0x00 +#define ACPI_TYPE_INTEGER 0x01 /* Byte/Word/Dword/Zero/One/Ones */ +#define ACPI_TYPE_STRING 0x02 +#define ACPI_TYPE_BUFFER 0x03 +#define ACPI_TYPE_PACKAGE 0x04 /* ByteConst, multiple DataTerm/Constant/SuperName */ +#define ACPI_TYPE_FIELD_UNIT 0x05 +#define ACPI_TYPE_DEVICE 0x06 /* Name, multiple Node */ +#define ACPI_TYPE_EVENT 0x07 +#define ACPI_TYPE_METHOD 0x08 /* Name, ByteConst, multiple Code */ +#define ACPI_TYPE_MUTEX 0x09 +#define ACPI_TYPE_REGION 0x0A +#define ACPI_TYPE_POWER 0x0B /* Name,ByteConst,WordConst,multi Node */ +#define ACPI_TYPE_PROCESSOR 0x0C /* Name,ByteConst,DWordConst,ByteConst,multi NmO */ +#define ACPI_TYPE_THERMAL 0x0D /* Name, multiple Node */ +#define ACPI_TYPE_BUFFER_FIELD 0x0E +#define ACPI_TYPE_DDB_HANDLE 0x0F +#define ACPI_TYPE_DEBUG_OBJECT 0x10 + +#define ACPI_TYPE_EXTERNAL_MAX 0x10 +#define ACPI_NUM_TYPES (ACPI_TYPE_EXTERNAL_MAX + 1) + +/* + * These are object types that do not map directly to the ACPI + * ObjectType() operator. They are used for various internal purposes + * only. If new predefined ACPI_TYPEs are added (via the ACPI + * specification), these internal types must move upwards. (There + * is code that depends on these values being contiguous with the + * external types above.) + */ +#define ACPI_TYPE_LOCAL_REGION_FIELD 0x11 +#define ACPI_TYPE_LOCAL_BANK_FIELD 0x12 +#define ACPI_TYPE_LOCAL_INDEX_FIELD 0x13 +#define ACPI_TYPE_LOCAL_REFERENCE 0x14 /* Arg#, Local#, Name, Debug, RefOf, Index */ +#define ACPI_TYPE_LOCAL_ALIAS 0x15 +#define ACPI_TYPE_LOCAL_METHOD_ALIAS 0x16 +#define ACPI_TYPE_LOCAL_NOTIFY 0x17 +#define ACPI_TYPE_LOCAL_ADDRESS_HANDLER 0x18 +#define ACPI_TYPE_LOCAL_RESOURCE 0x19 +#define ACPI_TYPE_LOCAL_RESOURCE_FIELD 0x1A +#define ACPI_TYPE_LOCAL_SCOPE 0x1B /* 1 Name, multiple ObjectList Nodes */ + +#define ACPI_TYPE_NS_NODE_MAX 0x1B /* Last typecode used within a NS Node */ +#define ACPI_TOTAL_TYPES (ACPI_TYPE_NS_NODE_MAX + 1) + +/* + * These are special object types that never appear in + * a Namespace node, only in an object of ACPI_OPERAND_OBJECT + */ +#define ACPI_TYPE_LOCAL_EXTRA 0x1C +#define ACPI_TYPE_LOCAL_DATA 0x1D + +#define ACPI_TYPE_LOCAL_MAX 0x1D + +/* All types above here are invalid */ + +#define ACPI_TYPE_INVALID 0x1E +#define ACPI_TYPE_NOT_FOUND 0xFF + +#define ACPI_NUM_NS_TYPES (ACPI_TYPE_INVALID + 1) + + +/* + * All I/O + */ +#define ACPI_READ 0 +#define ACPI_WRITE 1 +#define ACPI_IO_MASK 1 + +/* + * Event Types: Fixed & General Purpose + */ +typedef UINT32 ACPI_EVENT_TYPE; + +/* + * Fixed events + */ +#define ACPI_EVENT_PMTIMER 0 +#define ACPI_EVENT_GLOBAL 1 +#define ACPI_EVENT_POWER_BUTTON 2 +#define ACPI_EVENT_SLEEP_BUTTON 3 +#define ACPI_EVENT_RTC 4 +#define ACPI_EVENT_MAX 4 +#define ACPI_NUM_FIXED_EVENTS ACPI_EVENT_MAX + 1 + +/* + * Event Status - Per event + * ------------- + * The encoding of ACPI_EVENT_STATUS is illustrated below. + * Note that a set bit (1) indicates the property is TRUE + * (e.g. if bit 0 is set then the event is enabled). + * +-------------+-+-+-+-+-+-+ + * | Bits 31:6 |5|4|3|2|1|0| + * +-------------+-+-+-+-+-+-+ + * | | | | | | | + * | | | | | | +- Enabled? + * | | | | | +--- Enabled for wake? + * | | | | +----- Status bit set? + * | | | +------- Enable bit set? + * | | +--------- Has a handler? + * | +----------- Masked? + * +----------------- + */ +typedef UINT32 ACPI_EVENT_STATUS; + +#define ACPI_EVENT_FLAG_DISABLED (ACPI_EVENT_STATUS) 0x00 +#define ACPI_EVENT_FLAG_ENABLED (ACPI_EVENT_STATUS) 0x01 +#define ACPI_EVENT_FLAG_WAKE_ENABLED (ACPI_EVENT_STATUS) 0x02 +#define ACPI_EVENT_FLAG_STATUS_SET (ACPI_EVENT_STATUS) 0x04 +#define ACPI_EVENT_FLAG_ENABLE_SET (ACPI_EVENT_STATUS) 0x08 +#define ACPI_EVENT_FLAG_HAS_HANDLER (ACPI_EVENT_STATUS) 0x10 +#define ACPI_EVENT_FLAG_MASKED (ACPI_EVENT_STATUS) 0x20 +#define ACPI_EVENT_FLAG_SET ACPI_EVENT_FLAG_STATUS_SET + +/* Actions for AcpiSetGpe, AcpiGpeWakeup, AcpiHwLowSetGpe */ + +#define ACPI_GPE_ENABLE 0 +#define ACPI_GPE_DISABLE 1 +#define ACPI_GPE_CONDITIONAL_ENABLE 2 + +/* + * GPE info flags - Per GPE + * +---+-+-+-+---+ + * |7:6|5|4|3|2:0| + * +---+-+-+-+---+ + * | | | | | + * | | | | +-- Type of dispatch:to method, handler, notify, or none + * | | | +----- Interrupt type: edge or level triggered + * | | +------- Is a Wake GPE + * | +--------- Has been enabled automatically at init time + * +------------ + */ +#define ACPI_GPE_DISPATCH_NONE (UINT8) 0x00 +#define ACPI_GPE_DISPATCH_METHOD (UINT8) 0x01 +#define ACPI_GPE_DISPATCH_HANDLER (UINT8) 0x02 +#define ACPI_GPE_DISPATCH_NOTIFY (UINT8) 0x03 +#define ACPI_GPE_DISPATCH_RAW_HANDLER (UINT8) 0x04 +#define ACPI_GPE_DISPATCH_MASK (UINT8) 0x07 +#define ACPI_GPE_DISPATCH_TYPE(flags) ((UINT8) ((flags) & ACPI_GPE_DISPATCH_MASK)) + +#define ACPI_GPE_LEVEL_TRIGGERED (UINT8) 0x08 +#define ACPI_GPE_EDGE_TRIGGERED (UINT8) 0x00 +#define ACPI_GPE_XRUPT_TYPE_MASK (UINT8) 0x08 + +#define ACPI_GPE_CAN_WAKE (UINT8) 0x10 +#define ACPI_GPE_AUTO_ENABLED (UINT8) 0x20 +#define ACPI_GPE_INITIALIZED (UINT8) 0x40 + +/* + * Flags for GPE and Lock interfaces + */ +#define ACPI_NOT_ISR 0x1 +#define ACPI_ISR 0x0 + + +/* Notify types */ + +#define ACPI_SYSTEM_NOTIFY 0x1 +#define ACPI_DEVICE_NOTIFY 0x2 +#define ACPI_ALL_NOTIFY (ACPI_SYSTEM_NOTIFY | ACPI_DEVICE_NOTIFY) +#define ACPI_MAX_NOTIFY_HANDLER_TYPE 0x3 +#define ACPI_NUM_NOTIFY_TYPES 2 + +#define ACPI_MAX_SYS_NOTIFY 0x7F +#define ACPI_MAX_DEVICE_SPECIFIC_NOTIFY 0xBF + +#define ACPI_SYSTEM_HANDLER_LIST 0 /* Used as index, must be SYSTEM_NOTIFY -1 */ +#define ACPI_DEVICE_HANDLER_LIST 1 /* Used as index, must be DEVICE_NOTIFY -1 */ + + +/* Address Space (Operation Region) Types */ + +typedef UINT8 ACPI_ADR_SPACE_TYPE; + +#define ACPI_ADR_SPACE_SYSTEM_MEMORY (ACPI_ADR_SPACE_TYPE) 0 +#define ACPI_ADR_SPACE_SYSTEM_IO (ACPI_ADR_SPACE_TYPE) 1 +#define ACPI_ADR_SPACE_PCI_CONFIG (ACPI_ADR_SPACE_TYPE) 2 +#define ACPI_ADR_SPACE_EC (ACPI_ADR_SPACE_TYPE) 3 +#define ACPI_ADR_SPACE_SMBUS (ACPI_ADR_SPACE_TYPE) 4 +#define ACPI_ADR_SPACE_CMOS (ACPI_ADR_SPACE_TYPE) 5 +#define ACPI_ADR_SPACE_PCI_BAR_TARGET (ACPI_ADR_SPACE_TYPE) 6 +#define ACPI_ADR_SPACE_IPMI (ACPI_ADR_SPACE_TYPE) 7 +#define ACPI_ADR_SPACE_GPIO (ACPI_ADR_SPACE_TYPE) 8 +#define ACPI_ADR_SPACE_GSBUS (ACPI_ADR_SPACE_TYPE) 9 +#define ACPI_ADR_SPACE_PLATFORM_COMM (ACPI_ADR_SPACE_TYPE) 10 + +#define ACPI_NUM_PREDEFINED_REGIONS 11 + +/* + * Special Address Spaces + * + * Note: A Data Table region is a special type of operation region + * that has its own AML opcode. However, internally, the AML + * interpreter simply creates an operation region with an an address + * space type of ACPI_ADR_SPACE_DATA_TABLE. + */ +#define ACPI_ADR_SPACE_DATA_TABLE (ACPI_ADR_SPACE_TYPE) 0x7E /* Internal to ACPICA only */ +#define ACPI_ADR_SPACE_FIXED_HARDWARE (ACPI_ADR_SPACE_TYPE) 0x7F + +/* Values for _REG connection code */ + +#define ACPI_REG_DISCONNECT 0 +#define ACPI_REG_CONNECT 1 + +/* + * BitRegister IDs + * + * These values are intended to be used by the hardware interfaces + * and are mapped to individual bitfields defined within the ACPI + * registers. See the AcpiGbl_BitRegisterInfo global table in utglobal.c + * for this mapping. + */ + +/* PM1 Status register */ + +#define ACPI_BITREG_TIMER_STATUS 0x00 +#define ACPI_BITREG_BUS_MASTER_STATUS 0x01 +#define ACPI_BITREG_GLOBAL_LOCK_STATUS 0x02 +#define ACPI_BITREG_POWER_BUTTON_STATUS 0x03 +#define ACPI_BITREG_SLEEP_BUTTON_STATUS 0x04 +#define ACPI_BITREG_RT_CLOCK_STATUS 0x05 +#define ACPI_BITREG_WAKE_STATUS 0x06 +#define ACPI_BITREG_PCIEXP_WAKE_STATUS 0x07 + +/* PM1 Enable register */ + +#define ACPI_BITREG_TIMER_ENABLE 0x08 +#define ACPI_BITREG_GLOBAL_LOCK_ENABLE 0x09 +#define ACPI_BITREG_POWER_BUTTON_ENABLE 0x0A +#define ACPI_BITREG_SLEEP_BUTTON_ENABLE 0x0B +#define ACPI_BITREG_RT_CLOCK_ENABLE 0x0C +#define ACPI_BITREG_PCIEXP_WAKE_DISABLE 0x0D + +/* PM1 Control register */ + +#define ACPI_BITREG_SCI_ENABLE 0x0E +#define ACPI_BITREG_BUS_MASTER_RLD 0x0F +#define ACPI_BITREG_GLOBAL_LOCK_RELEASE 0x10 +#define ACPI_BITREG_SLEEP_TYPE 0x11 +#define ACPI_BITREG_SLEEP_ENABLE 0x12 + +/* PM2 Control register */ + +#define ACPI_BITREG_ARB_DISABLE 0x13 + +#define ACPI_BITREG_MAX 0x13 +#define ACPI_NUM_BITREG ACPI_BITREG_MAX + 1 + + +/* Status register values. A 1 clears a status bit. 0 = no effect */ + +#define ACPI_CLEAR_STATUS 1 + +/* Enable and Control register values */ + +#define ACPI_ENABLE_EVENT 1 +#define ACPI_DISABLE_EVENT 0 + + +/* Sleep function dispatch */ + +typedef ACPI_STATUS (*ACPI_SLEEP_FUNCTION) ( + UINT8 SleepState); + +typedef struct acpi_sleep_functions +{ + ACPI_SLEEP_FUNCTION LegacyFunction; + ACPI_SLEEP_FUNCTION ExtendedFunction; + +} ACPI_SLEEP_FUNCTIONS; + + +/* + * External ACPI object definition + */ + +/* + * Note: Type == ACPI_TYPE_ANY (0) is used to indicate a NULL package + * element or an unresolved named reference. + */ +typedef union acpi_object +{ + ACPI_OBJECT_TYPE Type; /* See definition of AcpiNsType for values */ + struct + { + ACPI_OBJECT_TYPE Type; /* ACPI_TYPE_INTEGER */ + UINT64 Value; /* The actual number */ + } Integer; + + struct + { + ACPI_OBJECT_TYPE Type; /* ACPI_TYPE_STRING */ + UINT32 Length; /* # of bytes in string, excluding trailing null */ + char *Pointer; /* points to the string value */ + } String; + + struct + { + ACPI_OBJECT_TYPE Type; /* ACPI_TYPE_BUFFER */ + UINT32 Length; /* # of bytes in buffer */ + UINT8 *Pointer; /* points to the buffer */ + } Buffer; + + struct + { + ACPI_OBJECT_TYPE Type; /* ACPI_TYPE_PACKAGE */ + UINT32 Count; /* # of elements in package */ + union acpi_object *Elements; /* Pointer to an array of ACPI_OBJECTs */ + } Package; + + struct + { + ACPI_OBJECT_TYPE Type; /* ACPI_TYPE_LOCAL_REFERENCE */ + ACPI_OBJECT_TYPE ActualType; /* Type associated with the Handle */ + ACPI_HANDLE Handle; /* object reference */ + } Reference; + + struct + { + ACPI_OBJECT_TYPE Type; /* ACPI_TYPE_PROCESSOR */ + UINT32 ProcId; + ACPI_IO_ADDRESS PblkAddress; + UINT32 PblkLength; + } Processor; + + struct + { + ACPI_OBJECT_TYPE Type; /* ACPI_TYPE_POWER */ + UINT32 SystemLevel; + UINT32 ResourceOrder; + } PowerResource; + +} ACPI_OBJECT; + + +/* + * List of objects, used as a parameter list for control method evaluation + */ +typedef struct acpi_object_list +{ + UINT32 Count; + ACPI_OBJECT *Pointer; + +} ACPI_OBJECT_LIST; + + +/* + * Miscellaneous common Data Structures used by the interfaces + */ +#define ACPI_NO_BUFFER 0 + +#ifdef ACPI_NO_MEM_ALLOCATIONS + +#define ACPI_ALLOCATE_BUFFER (ACPI_SIZE) (0) +#define ACPI_ALLOCATE_LOCAL_BUFFER (ACPI_SIZE) (0) + +#else /* ACPI_NO_MEM_ALLOCATIONS */ + +#define ACPI_ALLOCATE_BUFFER (ACPI_SIZE) (-1) /* Let ACPICA allocate buffer */ +#define ACPI_ALLOCATE_LOCAL_BUFFER (ACPI_SIZE) (-2) /* For internal use only (enables tracking) */ + +#endif /* ACPI_NO_MEM_ALLOCATIONS */ + +typedef struct acpi_buffer +{ + ACPI_SIZE Length; /* Length in bytes of the buffer */ + void *Pointer; /* pointer to buffer */ + +} ACPI_BUFFER; + + +/* + * NameType for AcpiGetName + */ +#define ACPI_FULL_PATHNAME 0 +#define ACPI_SINGLE_NAME 1 +#define ACPI_FULL_PATHNAME_NO_TRAILING 2 +#define ACPI_NAME_TYPE_MAX 2 + + +/* + * Predefined Namespace items + */ +typedef struct acpi_predefined_names +{ + const char *Name; + UINT8 Type; + char *Val; + +} ACPI_PREDEFINED_NAMES; + + +/* + * Structure and flags for AcpiGetSystemInfo + */ +#define ACPI_SYS_MODE_UNKNOWN 0x0000 +#define ACPI_SYS_MODE_ACPI 0x0001 +#define ACPI_SYS_MODE_LEGACY 0x0002 +#define ACPI_SYS_MODES_MASK 0x0003 + + +/* + * System info returned by AcpiGetSystemInfo() + */ +typedef struct acpi_system_info +{ + UINT32 AcpiCaVersion; + UINT32 Flags; + UINT32 TimerResolution; + UINT32 Reserved1; + UINT32 Reserved2; + UINT32 DebugLevel; + UINT32 DebugLayer; + +} ACPI_SYSTEM_INFO; + + +/* + * System statistics returned by AcpiGetStatistics() + */ +typedef struct acpi_statistics +{ + UINT32 SciCount; + UINT32 GpeCount; + UINT32 FixedEventCount[ACPI_NUM_FIXED_EVENTS]; + UINT32 MethodCount; + +} ACPI_STATISTICS; + + +/* + * Types specific to the OS service interfaces + */ +typedef UINT32 +(ACPI_SYSTEM_XFACE *ACPI_OSD_HANDLER) ( + void *Context); + +typedef void +(ACPI_SYSTEM_XFACE *ACPI_OSD_EXEC_CALLBACK) ( + void *Context); + +/* + * Various handlers and callback procedures + */ +typedef +UINT32 (*ACPI_SCI_HANDLER) ( + void *Context); + +typedef +void (*ACPI_GBL_EVENT_HANDLER) ( + UINT32 EventType, + ACPI_HANDLE Device, + UINT32 EventNumber, + void *Context); + +#define ACPI_EVENT_TYPE_GPE 0 +#define ACPI_EVENT_TYPE_FIXED 1 + +typedef +UINT32 (*ACPI_EVENT_HANDLER) ( + void *Context); + +typedef +UINT32 (*ACPI_GPE_HANDLER) ( + ACPI_HANDLE GpeDevice, + UINT32 GpeNumber, + void *Context); + +typedef +void (*ACPI_NOTIFY_HANDLER) ( + ACPI_HANDLE Device, + UINT32 Value, + void *Context); + +typedef +void (*ACPI_OBJECT_HANDLER) ( + ACPI_HANDLE Object, + void *Data); + +typedef +ACPI_STATUS (*ACPI_INIT_HANDLER) ( + ACPI_HANDLE Object, + UINT32 Function); + +#define ACPI_INIT_DEVICE_INI 1 + +typedef +ACPI_STATUS (*ACPI_EXCEPTION_HANDLER) ( + ACPI_STATUS AmlStatus, + ACPI_NAME Name, + UINT16 Opcode, + UINT32 AmlOffset, + void *Context); + +/* Table Event handler (Load, LoadTable, etc.) and types */ + +typedef +ACPI_STATUS (*ACPI_TABLE_HANDLER) ( + UINT32 Event, + void *Table, + void *Context); + + +/* Table Event Types */ + +#define ACPI_TABLE_EVENT_LOAD 0x0 +#define ACPI_TABLE_EVENT_UNLOAD 0x1 +#define ACPI_TABLE_EVENT_INSTALL 0x2 +#define ACPI_TABLE_EVENT_UNINSTALL 0x3 +#define ACPI_NUM_TABLE_EVENTS 4 + + +/* Address Spaces (For Operation Regions) */ + +typedef +ACPI_STATUS (*ACPI_ADR_SPACE_HANDLER) ( + UINT32 Function, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 BitWidth, + UINT64 *Value, + void *HandlerContext, + void *RegionContext); + +#define ACPI_DEFAULT_HANDLER NULL + +/* Special Context data for GenericSerialBus/GeneralPurposeIo (ACPI 5.0) */ + +typedef struct acpi_connection_info +{ + UINT8 *Connection; + UINT16 Length; + UINT8 AccessLength; + +} ACPI_CONNECTION_INFO; + + +typedef +ACPI_STATUS (*ACPI_ADR_SPACE_SETUP) ( + ACPI_HANDLE RegionHandle, + UINT32 Function, + void *HandlerContext, + void **RegionContext); + +#define ACPI_REGION_ACTIVATE 0 +#define ACPI_REGION_DEACTIVATE 1 + +typedef +ACPI_STATUS (*ACPI_WALK_CALLBACK) ( + ACPI_HANDLE Object, + UINT32 NestingLevel, + void *Context, + void **ReturnValue); + +typedef +UINT32 (*ACPI_INTERFACE_HANDLER) ( + ACPI_STRING InterfaceName, + UINT32 Supported); + + +/* Interrupt handler return values */ + +#define ACPI_INTERRUPT_NOT_HANDLED 0x00 +#define ACPI_INTERRUPT_HANDLED 0x01 + +/* GPE handler return values */ + +#define ACPI_REENABLE_GPE 0x80 + + +/* Length of 32-bit EISAID values when converted back to a string */ + +#define ACPI_EISAID_STRING_SIZE 8 /* Includes null terminator */ + +/* Length of UUID (string) values */ + +#define ACPI_UUID_LENGTH 16 + +/* Length of 3-byte PCI class code values when converted back to a string */ + +#define ACPI_PCICLS_STRING_SIZE 7 /* Includes null terminator */ + + +/* Structures used for device/processor HID, UID, CID */ + +typedef struct acpi_pnp_device_id +{ + UINT32 Length; /* Length of string + null */ + char *String; + +} ACPI_PNP_DEVICE_ID; + +typedef struct acpi_pnp_device_id_list +{ + UINT32 Count; /* Number of IDs in Ids array */ + UINT32 ListSize; /* Size of list, including ID strings */ + ACPI_PNP_DEVICE_ID Ids[1]; /* ID array */ + +} ACPI_PNP_DEVICE_ID_LIST; + +/* + * Structure returned from AcpiGetObjectInfo. + * Optimized for both 32-bit and 64-bit builds. + */ +typedef struct acpi_device_info +{ + UINT32 InfoSize; /* Size of info, including ID strings */ + UINT32 Name; /* ACPI object Name */ + ACPI_OBJECT_TYPE Type; /* ACPI object Type */ + UINT8 ParamCount; /* If a method, required parameter count */ + UINT16 Valid; /* Indicates which optional fields are valid */ + UINT8 Flags; /* Miscellaneous info */ + UINT8 HighestDstates[4]; /* _SxD values: 0xFF indicates not valid */ + UINT8 LowestDstates[5]; /* _SxW values: 0xFF indicates not valid */ + UINT64 Address; /* _ADR value */ + ACPI_PNP_DEVICE_ID HardwareId; /* _HID value */ + ACPI_PNP_DEVICE_ID UniqueId; /* _UID value */ + ACPI_PNP_DEVICE_ID ClassCode; /* _CLS value */ + ACPI_PNP_DEVICE_ID_LIST CompatibleIdList; /* _CID list */ + +} ACPI_DEVICE_INFO; + +/* Values for Flags field above (AcpiGetObjectInfo) */ + +#define ACPI_PCI_ROOT_BRIDGE 0x01 + +/* Flags for Valid field above (AcpiGetObjectInfo) */ + +#define ACPI_VALID_ADR 0x0002 +#define ACPI_VALID_HID 0x0004 +#define ACPI_VALID_UID 0x0008 +#define ACPI_VALID_CID 0x0020 +#define ACPI_VALID_CLS 0x0040 +#define ACPI_VALID_SXDS 0x0100 +#define ACPI_VALID_SXWS 0x0200 + +/* Flags for _STA method */ + +#define ACPI_STA_DEVICE_PRESENT 0x01 +#define ACPI_STA_DEVICE_ENABLED 0x02 +#define ACPI_STA_DEVICE_UI 0x04 +#define ACPI_STA_DEVICE_FUNCTIONING 0x08 +#define ACPI_STA_DEVICE_OK 0x08 /* Synonym */ +#define ACPI_STA_BATTERY_PRESENT 0x10 + + +/* Context structs for address space handlers */ + +typedef struct acpi_pci_id +{ + UINT16 Segment; + UINT16 Bus; + UINT16 Device; + UINT16 Function; + +} ACPI_PCI_ID; + +typedef struct acpi_mem_space_context +{ + UINT32 Length; + ACPI_PHYSICAL_ADDRESS Address; + ACPI_PHYSICAL_ADDRESS MappedPhysicalAddress; + UINT8 *MappedLogicalAddress; + ACPI_SIZE MappedLength; + +} ACPI_MEM_SPACE_CONTEXT; + + +/* + * ACPI_MEMORY_LIST is used only if the ACPICA local cache is enabled + */ +typedef struct acpi_memory_list +{ + const char *ListName; + void *ListHead; + UINT16 ObjectSize; + UINT16 MaxDepth; + UINT16 CurrentDepth; + +#ifdef ACPI_DBG_TRACK_ALLOCATIONS + + /* Statistics for debug memory tracking only */ + + UINT32 TotalAllocated; + UINT32 TotalFreed; + UINT32 MaxOccupied; + UINT32 TotalSize; + UINT32 CurrentTotalSize; + UINT32 Requests; + UINT32 Hits; +#endif + +} ACPI_MEMORY_LIST; + + +/* Definitions of trace event types */ + +typedef enum +{ + ACPI_TRACE_AML_METHOD, + ACPI_TRACE_AML_OPCODE, + ACPI_TRACE_AML_REGION + +} ACPI_TRACE_EVENT_TYPE; + + +/* Definitions of _OSI support */ + +#define ACPI_VENDOR_STRINGS 0x01 +#define ACPI_FEATURE_STRINGS 0x02 +#define ACPI_ENABLE_INTERFACES 0x00 +#define ACPI_DISABLE_INTERFACES 0x04 + +#define ACPI_DISABLE_ALL_VENDOR_STRINGS (ACPI_DISABLE_INTERFACES | ACPI_VENDOR_STRINGS) +#define ACPI_DISABLE_ALL_FEATURE_STRINGS (ACPI_DISABLE_INTERFACES | ACPI_FEATURE_STRINGS) +#define ACPI_DISABLE_ALL_STRINGS (ACPI_DISABLE_INTERFACES | ACPI_VENDOR_STRINGS | ACPI_FEATURE_STRINGS) +#define ACPI_ENABLE_ALL_VENDOR_STRINGS (ACPI_ENABLE_INTERFACES | ACPI_VENDOR_STRINGS) +#define ACPI_ENABLE_ALL_FEATURE_STRINGS (ACPI_ENABLE_INTERFACES | ACPI_FEATURE_STRINGS) +#define ACPI_ENABLE_ALL_STRINGS (ACPI_ENABLE_INTERFACES | ACPI_VENDOR_STRINGS | ACPI_FEATURE_STRINGS) + +#define ACPI_OSI_WIN_2000 0x01 +#define ACPI_OSI_WIN_XP 0x02 +#define ACPI_OSI_WIN_XP_SP1 0x03 +#define ACPI_OSI_WINSRV_2003 0x04 +#define ACPI_OSI_WIN_XP_SP2 0x05 +#define ACPI_OSI_WINSRV_2003_SP1 0x06 +#define ACPI_OSI_WIN_VISTA 0x07 +#define ACPI_OSI_WINSRV_2008 0x08 +#define ACPI_OSI_WIN_VISTA_SP1 0x09 +#define ACPI_OSI_WIN_VISTA_SP2 0x0A +#define ACPI_OSI_WIN_7 0x0B +#define ACPI_OSI_WIN_8 0x0C +#define ACPI_OSI_WIN_10 0x0D +#define ACPI_OSI_WIN_10_RS1 0x0E +#define ACPI_OSI_WIN_10_RS2 0x0F +#define ACPI_OSI_WIN_10_RS3 0x10 + + +/* Definitions of getopt */ + +#define ACPI_OPT_END -1 + + +#endif /* __ACTYPES_H__ */ diff --git a/ports/acpica/include/acutils.h b/ports/acpica/include/acutils.h new file mode 100644 index 0000000..8dfce90 --- /dev/null +++ b/ports/acpica/include/acutils.h @@ -0,0 +1,1264 @@ +/****************************************************************************** + * + * Name: acutils.h -- prototypes for the common (subsystem-wide) procedures + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef _ACUTILS_H +#define _ACUTILS_H + + +extern const UINT8 AcpiGbl_ResourceAmlSizes[]; +extern const UINT8 AcpiGbl_ResourceAmlSerialBusSizes[]; + +/* Strings used by the disassembler and debugger resource dump routines */ + +#if defined(ACPI_DEBUG_OUTPUT) || defined (ACPI_DISASSEMBLER) || defined (ACPI_DEBUGGER) + +extern const char *AcpiGbl_BmDecode[]; +extern const char *AcpiGbl_ConfigDecode[]; +extern const char *AcpiGbl_ConsumeDecode[]; +extern const char *AcpiGbl_DecDecode[]; +extern const char *AcpiGbl_HeDecode[]; +extern const char *AcpiGbl_IoDecode[]; +extern const char *AcpiGbl_LlDecode[]; +extern const char *AcpiGbl_MaxDecode[]; +extern const char *AcpiGbl_MemDecode[]; +extern const char *AcpiGbl_MinDecode[]; +extern const char *AcpiGbl_MtpDecode[]; +extern const char *AcpiGbl_RngDecode[]; +extern const char *AcpiGbl_RwDecode[]; +extern const char *AcpiGbl_ShrDecode[]; +extern const char *AcpiGbl_SizDecode[]; +extern const char *AcpiGbl_TrsDecode[]; +extern const char *AcpiGbl_TtpDecode[]; +extern const char *AcpiGbl_TypDecode[]; +extern const char *AcpiGbl_PpcDecode[]; +extern const char *AcpiGbl_IorDecode[]; +extern const char *AcpiGbl_DtsDecode[]; +extern const char *AcpiGbl_CtDecode[]; +extern const char *AcpiGbl_SbtDecode[]; +extern const char *AcpiGbl_AmDecode[]; +extern const char *AcpiGbl_SmDecode[]; +extern const char *AcpiGbl_WmDecode[]; +extern const char *AcpiGbl_CphDecode[]; +extern const char *AcpiGbl_CpoDecode[]; +extern const char *AcpiGbl_DpDecode[]; +extern const char *AcpiGbl_EdDecode[]; +extern const char *AcpiGbl_BpbDecode[]; +extern const char *AcpiGbl_SbDecode[]; +extern const char *AcpiGbl_FcDecode[]; +extern const char *AcpiGbl_PtDecode[]; +extern const char *AcpiGbl_PtypDecode[]; +#endif + +/* + * For the iASL compiler case, the output is redirected to stderr so that + * any of the various ACPI errors and warnings do not appear in the output + * files, for either the compiler or disassembler portions of the tool. + */ +#ifdef ACPI_ASL_COMPILER + +#include + +#define ACPI_MSG_REDIRECT_BEGIN \ + FILE *OutputFile = AcpiGbl_OutputFile; \ + AcpiOsRedirectOutput (stderr); + +#define ACPI_MSG_REDIRECT_END \ + AcpiOsRedirectOutput (OutputFile); + +#else +/* + * non-iASL case - no redirection, nothing to do + */ +#define ACPI_MSG_REDIRECT_BEGIN +#define ACPI_MSG_REDIRECT_END +#endif + +/* + * Common error message prefixes + */ +#ifndef ACPI_MSG_ERROR +#define ACPI_MSG_ERROR "ACPI Error: " +#endif +#ifndef ACPI_MSG_WARNING +#define ACPI_MSG_WARNING "ACPI Warning: " +#endif +#ifndef ACPI_MSG_INFO +#define ACPI_MSG_INFO "ACPI: " +#endif + +#ifndef ACPI_MSG_BIOS_ERROR +#define ACPI_MSG_BIOS_ERROR "Firmware Error (ACPI): " +#endif +#ifndef ACPI_MSG_BIOS_WARNING +#define ACPI_MSG_BIOS_WARNING "Firmware Warning (ACPI): " +#endif + +/* + * Common message suffix + */ +#define ACPI_MSG_SUFFIX \ + AcpiOsPrintf (" (%8.8X/%s-%u)\n", ACPI_CA_VERSION, ModuleName, LineNumber) + +/* Flags to indicate implicit or explicit string-to-integer conversion */ + +#define ACPI_IMPLICIT_CONVERSION TRUE +#define ACPI_NO_IMPLICIT_CONVERSION FALSE + +/* Types for Resource descriptor entries */ + +#define ACPI_INVALID_RESOURCE 0 +#define ACPI_FIXED_LENGTH 1 +#define ACPI_VARIABLE_LENGTH 2 +#define ACPI_SMALL_VARIABLE_LENGTH 3 + +typedef +ACPI_STATUS (*ACPI_WALK_AML_CALLBACK) ( + UINT8 *Aml, + UINT32 Length, + UINT32 Offset, + UINT8 ResourceIndex, + void **Context); + +typedef +ACPI_STATUS (*ACPI_PKG_CALLBACK) ( + UINT8 ObjectType, + ACPI_OPERAND_OBJECT *SourceObject, + ACPI_GENERIC_STATE *State, + void *Context); + +typedef struct acpi_pkg_info +{ + UINT8 *FreeSpace; + ACPI_SIZE Length; + UINT32 ObjectSpace; + UINT32 NumPackages; + +} ACPI_PKG_INFO; + +/* Object reference counts */ + +#define REF_INCREMENT (UINT16) 0 +#define REF_DECREMENT (UINT16) 1 + +/* AcpiUtDumpBuffer */ + +#define DB_BYTE_DISPLAY 1 +#define DB_WORD_DISPLAY 2 +#define DB_DWORD_DISPLAY 4 +#define DB_QWORD_DISPLAY 8 + + +/* + * utascii - ASCII utilities + */ +BOOLEAN +AcpiUtValidNameseg ( + char *Signature); + +BOOLEAN +AcpiUtValidNameChar ( + char Character, + UINT32 Position); + +void +AcpiUtCheckAndRepairAscii ( + UINT8 *Name, + char *RepairedName, + UINT32 Count); + + +/* + * utnonansi - Non-ANSI C library functions + */ +void +AcpiUtStrupr ( + char *SrcString); + +void +AcpiUtStrlwr ( + char *SrcString); + +int +AcpiUtStricmp ( + char *String1, + char *String2); + + +/* + * utstrsuppt - string-to-integer conversion support functions + */ +ACPI_STATUS +AcpiUtConvertOctalString ( + char *String, + UINT64 *ReturnValue); + +ACPI_STATUS +AcpiUtConvertDecimalString ( + char *String, + UINT64 *ReturnValuePtr); + +ACPI_STATUS +AcpiUtConvertHexString ( + char *String, + UINT64 *ReturnValuePtr); + +char +AcpiUtRemoveWhitespace ( + char **String); + +char +AcpiUtRemoveLeadingZeros ( + char **String); + +BOOLEAN +AcpiUtDetectHexPrefix ( + char **String); + +BOOLEAN +AcpiUtDetectOctalPrefix ( + char **String); + + +/* + * utstrtoul64 - string-to-integer conversion functions + */ +ACPI_STATUS +AcpiUtStrtoul64 ( + char *String, + UINT64 *RetInteger); + +UINT64 +AcpiUtExplicitStrtoul64 ( + char *String); + +UINT64 +AcpiUtImplicitStrtoul64 ( + char *String); + + +/* + * utglobal - Global data structures and procedures + */ +ACPI_STATUS +AcpiUtInitGlobals ( + void); + +const char * +AcpiUtGetMutexName ( + UINT32 MutexId); + +#if defined(ACPI_DEBUG_OUTPUT) || defined(ACPI_DEBUGGER) + +const char * +AcpiUtGetNotifyName ( + UINT32 NotifyValue, + ACPI_OBJECT_TYPE Type); +#endif + +const char * +AcpiUtGetTypeName ( + ACPI_OBJECT_TYPE Type); + +const char * +AcpiUtGetNodeName ( + void *Object); + +const char * +AcpiUtGetDescriptorName ( + void *Object); + +const char * +AcpiUtGetReferenceName ( + ACPI_OPERAND_OBJECT *Object); + +const char * +AcpiUtGetObjectTypeName ( + ACPI_OPERAND_OBJECT *ObjDesc); + +const char * +AcpiUtGetRegionName ( + UINT8 SpaceId); + +const char * +AcpiUtGetEventName ( + UINT32 EventId); + +const char * +AcpiUtGetArgumentTypeName ( + UINT32 ArgType); + +char +AcpiUtHexToAsciiChar ( + UINT64 Integer, + UINT32 Position); + +ACPI_STATUS +AcpiUtAsciiToHexByte ( + char *TwoAsciiChars, + UINT8 *ReturnByte); + +UINT8 +AcpiUtAsciiCharToHex ( + int HexChar); + +BOOLEAN +AcpiUtValidObjectType ( + ACPI_OBJECT_TYPE Type); + + +/* + * utinit - miscellaneous initialization and shutdown + */ +ACPI_STATUS +AcpiUtHardwareInitialize ( + void); + +void +AcpiUtSubsystemShutdown ( + void); + + +/* + * utcopy - Object construction and conversion interfaces + */ +ACPI_STATUS +AcpiUtBuildSimpleObject( + ACPI_OPERAND_OBJECT *Obj, + ACPI_OBJECT *UserObj, + UINT8 *DataSpace, + UINT32 *BufferSpaceUsed); + +ACPI_STATUS +AcpiUtBuildPackageObject ( + ACPI_OPERAND_OBJECT *Obj, + UINT8 *Buffer, + UINT32 *SpaceUsed); + +ACPI_STATUS +AcpiUtCopyIobjectToEobject ( + ACPI_OPERAND_OBJECT *Obj, + ACPI_BUFFER *RetBuffer); + +ACPI_STATUS +AcpiUtCopyEobjectToIobject ( + ACPI_OBJECT *Obj, + ACPI_OPERAND_OBJECT **InternalObj); + +ACPI_STATUS +AcpiUtCopyISimpleToIsimple ( + ACPI_OPERAND_OBJECT *SourceObj, + ACPI_OPERAND_OBJECT *DestObj); + +ACPI_STATUS +AcpiUtCopyIobjectToIobject ( + ACPI_OPERAND_OBJECT *SourceDesc, + ACPI_OPERAND_OBJECT **DestDesc, + ACPI_WALK_STATE *WalkState); + + +/* + * utcreate - Object creation + */ +ACPI_STATUS +AcpiUtUpdateObjectReference ( + ACPI_OPERAND_OBJECT *Object, + UINT16 Action); + + +/* + * utdebug - Debug interfaces + */ +void +AcpiUtInitStackPtrTrace ( + void); + +void +AcpiUtTrackStackPtr ( + void); + +void +AcpiUtTrace ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId); + +void +AcpiUtTracePtr ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + const void *Pointer); + +void +AcpiUtTraceU32 ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + UINT32 Integer); + +void +AcpiUtTraceStr ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + const char *String); + +void +AcpiUtExit ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId); + +void +AcpiUtStatusExit ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + ACPI_STATUS Status); + +void +AcpiUtValueExit ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + UINT64 Value); + +void +AcpiUtPtrExit ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + UINT8 *Ptr); + +void +AcpiUtStrExit ( + UINT32 LineNumber, + const char *FunctionName, + const char *ModuleName, + UINT32 ComponentId, + const char *String); + +void +AcpiUtDebugDumpBuffer ( + UINT8 *Buffer, + UINT32 Count, + UINT32 Display, + UINT32 ComponentId); + +void +AcpiUtDumpBuffer ( + UINT8 *Buffer, + UINT32 Count, + UINT32 Display, + UINT32 Offset); + +#ifdef ACPI_APPLICATION +void +AcpiUtDumpBufferToFile ( + ACPI_FILE File, + UINT8 *Buffer, + UINT32 Count, + UINT32 Display, + UINT32 BaseOffset); +#endif + +void +AcpiUtReportError ( + char *ModuleName, + UINT32 LineNumber); + +void +AcpiUtReportInfo ( + char *ModuleName, + UINT32 LineNumber); + +void +AcpiUtReportWarning ( + char *ModuleName, + UINT32 LineNumber); + + +/* + * utdelete - Object deletion and reference counts + */ +void +AcpiUtAddReference ( + ACPI_OPERAND_OBJECT *Object); + +void +AcpiUtRemoveReference ( + ACPI_OPERAND_OBJECT *Object); + +void +AcpiUtDeleteInternalPackageObject ( + ACPI_OPERAND_OBJECT *Object); + +void +AcpiUtDeleteInternalSimpleObject ( + ACPI_OPERAND_OBJECT *Object); + +void +AcpiUtDeleteInternalObjectList ( + ACPI_OPERAND_OBJECT **ObjList); + + +/* + * uteval - object evaluation + */ +ACPI_STATUS +AcpiUtEvaluateObject ( + ACPI_NAMESPACE_NODE *PrefixNode, + const char *Path, + UINT32 ExpectedReturnBtypes, + ACPI_OPERAND_OBJECT **ReturnDesc); + +ACPI_STATUS +AcpiUtEvaluateNumericObject ( + const char *ObjectName, + ACPI_NAMESPACE_NODE *DeviceNode, + UINT64 *Value); + +ACPI_STATUS +AcpiUtExecute_STA ( + ACPI_NAMESPACE_NODE *DeviceNode, + UINT32 *StatusFlags); + +ACPI_STATUS +AcpiUtExecutePowerMethods ( + ACPI_NAMESPACE_NODE *DeviceNode, + const char **MethodNames, + UINT8 MethodCount, + UINT8 *OutValues); + + +/* + * utids - device ID support + */ +ACPI_STATUS +AcpiUtExecute_HID ( + ACPI_NAMESPACE_NODE *DeviceNode, + ACPI_PNP_DEVICE_ID **ReturnId); + +ACPI_STATUS +AcpiUtExecute_UID ( + ACPI_NAMESPACE_NODE *DeviceNode, + ACPI_PNP_DEVICE_ID **ReturnId); + +ACPI_STATUS +AcpiUtExecute_CID ( + ACPI_NAMESPACE_NODE *DeviceNode, + ACPI_PNP_DEVICE_ID_LIST **ReturnCidList); + +ACPI_STATUS +AcpiUtExecute_CLS ( + ACPI_NAMESPACE_NODE *DeviceNode, + ACPI_PNP_DEVICE_ID **ReturnId); + + +/* + * utlock - reader/writer locks + */ +ACPI_STATUS +AcpiUtCreateRwLock ( + ACPI_RW_LOCK *Lock); + +void +AcpiUtDeleteRwLock ( + ACPI_RW_LOCK *Lock); + +ACPI_STATUS +AcpiUtAcquireReadLock ( + ACPI_RW_LOCK *Lock); + +ACPI_STATUS +AcpiUtReleaseReadLock ( + ACPI_RW_LOCK *Lock); + +ACPI_STATUS +AcpiUtAcquireWriteLock ( + ACPI_RW_LOCK *Lock); + +void +AcpiUtReleaseWriteLock ( + ACPI_RW_LOCK *Lock); + + +/* + * utobject - internal object create/delete/cache routines + */ +ACPI_OPERAND_OBJECT * +AcpiUtCreateInternalObjectDbg ( + const char *ModuleName, + UINT32 LineNumber, + UINT32 ComponentId, + ACPI_OBJECT_TYPE Type); + +void * +AcpiUtAllocateObjectDescDbg ( + const char *ModuleName, + UINT32 LineNumber, + UINT32 ComponentId); + +#define AcpiUtCreateInternalObject(t) AcpiUtCreateInternalObjectDbg (_AcpiModuleName,__LINE__,_COMPONENT,t) +#define AcpiUtAllocateObjectDesc() AcpiUtAllocateObjectDescDbg (_AcpiModuleName,__LINE__,_COMPONENT) + +void +AcpiUtDeleteObjectDesc ( + ACPI_OPERAND_OBJECT *Object); + +BOOLEAN +AcpiUtValidInternalObject ( + void *Object); + +ACPI_OPERAND_OBJECT * +AcpiUtCreatePackageObject ( + UINT32 Count); + +ACPI_OPERAND_OBJECT * +AcpiUtCreateIntegerObject ( + UINT64 Value); + +ACPI_OPERAND_OBJECT * +AcpiUtCreateBufferObject ( + ACPI_SIZE BufferSize); + +ACPI_OPERAND_OBJECT * +AcpiUtCreateStringObject ( + ACPI_SIZE StringSize); + +ACPI_STATUS +AcpiUtGetObjectSize( + ACPI_OPERAND_OBJECT *Obj, + ACPI_SIZE *ObjLength); + + +/* + * utosi - Support for the _OSI predefined control method + */ +ACPI_STATUS +AcpiUtInitializeInterfaces ( + void); + +ACPI_STATUS +AcpiUtInterfaceTerminate ( + void); + +ACPI_STATUS +AcpiUtInstallInterface ( + ACPI_STRING InterfaceName); + +ACPI_STATUS +AcpiUtRemoveInterface ( + ACPI_STRING InterfaceName); + +ACPI_STATUS +AcpiUtUpdateInterfaces ( + UINT8 Action); + +ACPI_INTERFACE_INFO * +AcpiUtGetInterface ( + ACPI_STRING InterfaceName); + +ACPI_STATUS +AcpiUtOsiImplementation ( + ACPI_WALK_STATE *WalkState); + + +/* + * utpredef - support for predefined names + */ +const ACPI_PREDEFINED_INFO * +AcpiUtGetNextPredefinedMethod ( + const ACPI_PREDEFINED_INFO *ThisName); + +const ACPI_PREDEFINED_INFO * +AcpiUtMatchPredefinedMethod ( + char *Name); + +void +AcpiUtGetExpectedReturnTypes ( + char *Buffer, + UINT32 ExpectedBtypes); + +#if (defined ACPI_ASL_COMPILER || defined ACPI_HELP_APP) +const ACPI_PREDEFINED_INFO * +AcpiUtMatchResourceName ( + char *Name); + +void +AcpiUtDisplayPredefinedMethod ( + char *Buffer, + const ACPI_PREDEFINED_INFO *ThisName, + BOOLEAN MultiLine); + +UINT32 +AcpiUtGetResourceBitWidth ( + char *Buffer, + UINT16 Types); +#endif + + +/* + * utstate - Generic state creation/cache routines + */ +void +AcpiUtPushGenericState ( + ACPI_GENERIC_STATE **ListHead, + ACPI_GENERIC_STATE *State); + +ACPI_GENERIC_STATE * +AcpiUtPopGenericState ( + ACPI_GENERIC_STATE **ListHead); + + +ACPI_GENERIC_STATE * +AcpiUtCreateGenericState ( + void); + +ACPI_THREAD_STATE * +AcpiUtCreateThreadState ( + void); + +ACPI_GENERIC_STATE * +AcpiUtCreateUpdateState ( + ACPI_OPERAND_OBJECT *Object, + UINT16 Action); + +ACPI_GENERIC_STATE * +AcpiUtCreatePkgState ( + void *InternalObject, + void *ExternalObject, + UINT32 Index); + +ACPI_STATUS +AcpiUtCreateUpdateStateAndPush ( + ACPI_OPERAND_OBJECT *Object, + UINT16 Action, + ACPI_GENERIC_STATE **StateList); + +ACPI_GENERIC_STATE * +AcpiUtCreateControlState ( + void); + +void +AcpiUtDeleteGenericState ( + ACPI_GENERIC_STATE *State); + + +/* + * utmath + */ +ACPI_STATUS +AcpiUtDivide ( + UINT64 InDividend, + UINT64 InDivisor, + UINT64 *OutQuotient, + UINT64 *OutRemainder); + +ACPI_STATUS +AcpiUtShortDivide ( + UINT64 InDividend, + UINT32 Divisor, + UINT64 *OutQuotient, + UINT32 *OutRemainder); + +ACPI_STATUS +AcpiUtShortMultiply ( + UINT64 InMultiplicand, + UINT32 Multiplier, + UINT64 *Outproduct); + +ACPI_STATUS +AcpiUtShortShiftLeft ( + UINT64 Operand, + UINT32 Count, + UINT64 *OutResult); + +ACPI_STATUS +AcpiUtShortShiftRight ( + UINT64 Operand, + UINT32 Count, + UINT64 *OutResult); + + +/* + * utmisc + */ +const ACPI_EXCEPTION_INFO * +AcpiUtValidateException ( + ACPI_STATUS Status); + +BOOLEAN +AcpiUtIsPciRootBridge ( + char *Id); + +#if (defined ACPI_ASL_COMPILER || defined ACPI_EXEC_APP || defined ACPI_NAMES_APP) +BOOLEAN +AcpiUtIsAmlTable ( + ACPI_TABLE_HEADER *Table); +#endif + +ACPI_STATUS +AcpiUtWalkPackageTree ( + ACPI_OPERAND_OBJECT *SourceObject, + void *TargetObject, + ACPI_PKG_CALLBACK WalkCallback, + void *Context); + +/* Values for Base above (16=Hex, 10=Decimal) */ + +#define ACPI_ANY_BASE 0 + + +UINT32 +AcpiUtDwordByteSwap ( + UINT32 Value); + +void +AcpiUtSetIntegerWidth ( + UINT8 Revision); + +#ifdef ACPI_DEBUG_OUTPUT +void +AcpiUtDisplayInitPathname ( + UINT8 Type, + ACPI_NAMESPACE_NODE *ObjHandle, + const char *Path); +#endif + + +/* + * utownerid - Support for Table/Method Owner IDs + */ +ACPI_STATUS +AcpiUtAllocateOwnerId ( + ACPI_OWNER_ID *OwnerId); + +void +AcpiUtReleaseOwnerId ( + ACPI_OWNER_ID *OwnerId); + + +/* + * utresrc + */ +ACPI_STATUS +AcpiUtWalkAmlResources ( + ACPI_WALK_STATE *WalkState, + UINT8 *Aml, + ACPI_SIZE AmlLength, + ACPI_WALK_AML_CALLBACK UserFunction, + void **Context); + +ACPI_STATUS +AcpiUtValidateResource ( + ACPI_WALK_STATE *WalkState, + void *Aml, + UINT8 *ReturnIndex); + +UINT32 +AcpiUtGetDescriptorLength ( + void *Aml); + +UINT16 +AcpiUtGetResourceLength ( + void *Aml); + +UINT8 +AcpiUtGetResourceHeaderLength ( + void *Aml); + +UINT8 +AcpiUtGetResourceType ( + void *Aml); + +ACPI_STATUS +AcpiUtGetResourceEndTag ( + ACPI_OPERAND_OBJECT *ObjDesc, + UINT8 **EndTag); + + +/* + * utstring - String and character utilities + */ +void +AcpiUtPrintString ( + char *String, + UINT16 MaxLength); + +#if defined ACPI_ASL_COMPILER || defined ACPI_EXEC_APP +void +UtConvertBackslashes ( + char *Pathname); +#endif + +void +AcpiUtRepairName ( + char *Name); + +#if defined (ACPI_DEBUGGER) || defined (ACPI_APPLICATION) || defined (ACPI_DEBUG_OUTPUT) +BOOLEAN +AcpiUtSafeStrcpy ( + char *Dest, + ACPI_SIZE DestSize, + char *Source); + +void +AcpiUtSafeStrncpy ( + char *Dest, + char *Source, + ACPI_SIZE DestSize); + +BOOLEAN +AcpiUtSafeStrcat ( + char *Dest, + ACPI_SIZE DestSize, + char *Source); + +BOOLEAN +AcpiUtSafeStrncat ( + char *Dest, + ACPI_SIZE DestSize, + char *Source, + ACPI_SIZE MaxTransferLength); +#endif + + +/* + * utmutex - mutex support + */ +ACPI_STATUS +AcpiUtMutexInitialize ( + void); + +void +AcpiUtMutexTerminate ( + void); + +ACPI_STATUS +AcpiUtAcquireMutex ( + ACPI_MUTEX_HANDLE MutexId); + +ACPI_STATUS +AcpiUtReleaseMutex ( + ACPI_MUTEX_HANDLE MutexId); + + +/* + * utalloc - memory allocation and object caching + */ +ACPI_STATUS +AcpiUtCreateCaches ( + void); + +ACPI_STATUS +AcpiUtDeleteCaches ( + void); + +ACPI_STATUS +AcpiUtValidateBuffer ( + ACPI_BUFFER *Buffer); + +ACPI_STATUS +AcpiUtInitializeBuffer ( + ACPI_BUFFER *Buffer, + ACPI_SIZE RequiredLength); + +#ifdef ACPI_DBG_TRACK_ALLOCATIONS +void * +AcpiUtAllocateAndTrack ( + ACPI_SIZE Size, + UINT32 Component, + const char *Module, + UINT32 Line); + +void * +AcpiUtAllocateZeroedAndTrack ( + ACPI_SIZE Size, + UINT32 Component, + const char *Module, + UINT32 Line); + +void +AcpiUtFreeAndTrack ( + void *Address, + UINT32 Component, + const char *Module, + UINT32 Line); + +void +AcpiUtDumpAllocationInfo ( + void); + +void +AcpiUtDumpAllocations ( + UINT32 Component, + const char *Module); + +ACPI_STATUS +AcpiUtCreateList ( + const char *ListName, + UINT16 ObjectSize, + ACPI_MEMORY_LIST **ReturnCache); + +#endif /* ACPI_DBG_TRACK_ALLOCATIONS */ + + +/* + * utaddress - address range check + */ +ACPI_STATUS +AcpiUtAddAddressRange ( + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 Length, + ACPI_NAMESPACE_NODE *RegionNode); + +void +AcpiUtRemoveAddressRange ( + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_NAMESPACE_NODE *RegionNode); + +UINT32 +AcpiUtCheckAddressRange ( + ACPI_ADR_SPACE_TYPE SpaceId, + ACPI_PHYSICAL_ADDRESS Address, + UINT32 Length, + BOOLEAN Warn); + +void +AcpiUtDeleteAddressLists ( + void); + + +/* + * utxferror - various error/warning output functions + */ +void ACPI_INTERNAL_VAR_XFACE +AcpiUtPredefinedWarning ( + const char *ModuleName, + UINT32 LineNumber, + char *Pathname, + UINT8 NodeFlags, + const char *Format, + ...); + +void ACPI_INTERNAL_VAR_XFACE +AcpiUtPredefinedInfo ( + const char *ModuleName, + UINT32 LineNumber, + char *Pathname, + UINT8 NodeFlags, + const char *Format, + ...); + +void ACPI_INTERNAL_VAR_XFACE +AcpiUtPredefinedBiosError ( + const char *ModuleName, + UINT32 LineNumber, + char *Pathname, + UINT8 NodeFlags, + const char *Format, + ...); + +void +AcpiUtPrefixedNamespaceError ( + const char *ModuleName, + UINT32 LineNumber, + ACPI_GENERIC_STATE *PrefixScope, + const char *InternalName, + ACPI_STATUS LookupStatus); + +void +AcpiUtMethodError ( + const char *ModuleName, + UINT32 LineNumber, + const char *Message, + ACPI_NAMESPACE_NODE *Node, + const char *Path, + ACPI_STATUS LookupStatus); + + +/* + * Utility functions for ACPI names and IDs + */ +const AH_PREDEFINED_NAME * +AcpiAhMatchPredefinedName ( + char *Nameseg); + +const AH_DEVICE_ID * +AcpiAhMatchHardwareId ( + char *Hid); + +const char * +AcpiAhMatchUuid ( + UINT8 *Data); + + +/* + * utuuid -- UUID support functions + */ +#if (defined ACPI_ASL_COMPILER || defined ACPI_EXEC_APP || defined ACPI_HELP_APP) +void +AcpiUtConvertStringToUuid ( + char *InString, + UINT8 *UuidBuffer); +#endif + +#endif /* _ACUTILS_H */ diff --git a/ports/acpica/include/acuuid.h b/ports/acpica/include/acuuid.h new file mode 100644 index 0000000..9b8ba69 --- /dev/null +++ b/ports/acpica/include/acuuid.h @@ -0,0 +1,203 @@ +/****************************************************************************** + * + * Name: acuuid.h - ACPI-related UUID/GUID definitions + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACUUID_H__ +#define __ACUUID_H__ + +/* + * Note1: UUIDs and GUIDs are defined to be identical in ACPI. + * + * Note2: This file is standalone and should remain that way. + */ + +/* Controllers */ + +#define UUID_GPIO_CONTROLLER "4f248f40-d5e2-499f-834c-27758ea1cd3f" +#define UUID_USB_CONTROLLER "ce2ee385-00e6-48cb-9f05-2edb927c4899" +#define UUID_SATA_CONTROLLER "e4db149b-fcfe-425b-a6d8-92357d78fc7f" + +/* Devices */ + +#define UUID_PCI_HOST_BRIDGE "33db4d5b-1ff7-401c-9657-7441c03dd766" +#define UUID_I2C_DEVICE "3cdff6f7-4267-4555-ad05-b30a3d8938de" +#define UUID_POWER_BUTTON "dfbcf3c5-e7a5-44e6-9c1f-29c76f6e059c" + +/* Interfaces */ + +#define UUID_DEVICE_LABELING "e5c937d0-3553-4d7a-9117-ea4d19c3434d" +#define UUID_PHYSICAL_PRESENCE "3dddfaa6-361b-4eb4-a424-8d10089d1653" + +/* NVDIMM - NFIT table */ + +#define UUID_VOLATILE_MEMORY "7305944f-fdda-44e3-b16c-3f22d252e5d0" +#define UUID_PERSISTENT_MEMORY "66f0d379-b4f3-4074-ac43-0d3318b78cdb" +#define UUID_CONTROL_REGION "92f701f6-13b4-405d-910b-299367e8234c" +#define UUID_DATA_REGION "91af0530-5d86-470e-a6b0-0a2db9408249" +#define UUID_VOLATILE_VIRTUAL_DISK "77ab535a-45fc-624b-5560-f7b281d1f96e" +#define UUID_VOLATILE_VIRTUAL_CD "3d5abd30-4175-87ce-6d64-d2ade523c4bb" +#define UUID_PERSISTENT_VIRTUAL_DISK "5cea02c9-4d07-69d3-269f-4496fbe096f9" +#define UUID_PERSISTENT_VIRTUAL_CD "08018188-42cd-bb48-100f-5387d53ded3d" + +/* Processor Properties (ACPI 6.2) */ + +#define UUID_CACHE_PROPERTIES "6DC63E77-257E-4E78-A973-A21F2796898D" +#define UUID_PHYSICAL_PROPERTY "DDE4D59A-AA42-4349-B407-EA40F57D9FB7" + +/* Miscellaneous */ + +#define UUID_PLATFORM_CAPABILITIES "0811b06e-4a27-44f9-8d60-3cbbc22e7b48" +#define UUID_DYNAMIC_ENUMERATION "d8c1a3a6-be9b-4c9b-91bf-c3cb81fc5daf" +#define UUID_BATTERY_THERMAL_LIMIT "4c2067e3-887d-475c-9720-4af1d3ed602e" +#define UUID_THERMAL_EXTENSIONS "14d399cd-7a27-4b18-8fb4-7cb7b9f4e500" +#define UUID_DEVICE_PROPERTIES "daffd814-6eba-4d8c-8a91-bc9bbf4aa301" + + +#endif /* __AUUID_H__ */ diff --git a/ports/acpica/include/amlcode.h b/ports/acpica/include/amlcode.h new file mode 100644 index 0000000..6507ae6 --- /dev/null +++ b/ports/acpica/include/amlcode.h @@ -0,0 +1,618 @@ +/****************************************************************************** + * + * Name: amlcode.h - Definitions for AML, as included in "definition blocks" + * Declarations and definitions contained herein are derived + * directly from the ACPI specification. + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __AMLCODE_H__ +#define __AMLCODE_H__ + +/* primary opcodes */ + +#define AML_ZERO_OP (UINT16) 0x00 +#define AML_ONE_OP (UINT16) 0x01 +#define AML_ALIAS_OP (UINT16) 0x06 +#define AML_NAME_OP (UINT16) 0x08 +#define AML_BYTE_OP (UINT16) 0x0a +#define AML_WORD_OP (UINT16) 0x0b +#define AML_DWORD_OP (UINT16) 0x0c +#define AML_STRING_OP (UINT16) 0x0d +#define AML_QWORD_OP (UINT16) 0x0e /* ACPI 2.0 */ +#define AML_SCOPE_OP (UINT16) 0x10 +#define AML_BUFFER_OP (UINT16) 0x11 +#define AML_PACKAGE_OP (UINT16) 0x12 +#define AML_VARIABLE_PACKAGE_OP (UINT16) 0x13 /* ACPI 2.0 */ +#define AML_METHOD_OP (UINT16) 0x14 +#define AML_EXTERNAL_OP (UINT16) 0x15 /* ACPI 6.0 */ +#define AML_DUAL_NAME_PREFIX (UINT16) 0x2e +#define AML_MULTI_NAME_PREFIX (UINT16) 0x2f +#define AML_EXTENDED_PREFIX (UINT16) 0x5b +#define AML_ROOT_PREFIX (UINT16) 0x5c +#define AML_PARENT_PREFIX (UINT16) 0x5e +#define AML_FIRST_LOCAL_OP (UINT16) 0x60 /* Used for Local op # calculations */ +#define AML_LOCAL0 (UINT16) 0x60 +#define AML_LOCAL1 (UINT16) 0x61 +#define AML_LOCAL2 (UINT16) 0x62 +#define AML_LOCAL3 (UINT16) 0x63 +#define AML_LOCAL4 (UINT16) 0x64 +#define AML_LOCAL5 (UINT16) 0x65 +#define AML_LOCAL6 (UINT16) 0x66 +#define AML_LOCAL7 (UINT16) 0x67 +#define AML_FIRST_ARG_OP (UINT16) 0x68 /* Used for Arg op # calculations */ +#define AML_ARG0 (UINT16) 0x68 +#define AML_ARG1 (UINT16) 0x69 +#define AML_ARG2 (UINT16) 0x6a +#define AML_ARG3 (UINT16) 0x6b +#define AML_ARG4 (UINT16) 0x6c +#define AML_ARG5 (UINT16) 0x6d +#define AML_ARG6 (UINT16) 0x6e +#define AML_STORE_OP (UINT16) 0x70 +#define AML_REF_OF_OP (UINT16) 0x71 +#define AML_ADD_OP (UINT16) 0x72 +#define AML_CONCATENATE_OP (UINT16) 0x73 +#define AML_SUBTRACT_OP (UINT16) 0x74 +#define AML_INCREMENT_OP (UINT16) 0x75 +#define AML_DECREMENT_OP (UINT16) 0x76 +#define AML_MULTIPLY_OP (UINT16) 0x77 +#define AML_DIVIDE_OP (UINT16) 0x78 +#define AML_SHIFT_LEFT_OP (UINT16) 0x79 +#define AML_SHIFT_RIGHT_OP (UINT16) 0x7a +#define AML_BIT_AND_OP (UINT16) 0x7b +#define AML_BIT_NAND_OP (UINT16) 0x7c +#define AML_BIT_OR_OP (UINT16) 0x7d +#define AML_BIT_NOR_OP (UINT16) 0x7e +#define AML_BIT_XOR_OP (UINT16) 0x7f +#define AML_BIT_NOT_OP (UINT16) 0x80 +#define AML_FIND_SET_LEFT_BIT_OP (UINT16) 0x81 +#define AML_FIND_SET_RIGHT_BIT_OP (UINT16) 0x82 +#define AML_DEREF_OF_OP (UINT16) 0x83 +#define AML_CONCATENATE_TEMPLATE_OP (UINT16) 0x84 /* ACPI 2.0 */ +#define AML_MOD_OP (UINT16) 0x85 /* ACPI 2.0 */ +#define AML_NOTIFY_OP (UINT16) 0x86 +#define AML_SIZE_OF_OP (UINT16) 0x87 +#define AML_INDEX_OP (UINT16) 0x88 +#define AML_MATCH_OP (UINT16) 0x89 +#define AML_CREATE_DWORD_FIELD_OP (UINT16) 0x8a +#define AML_CREATE_WORD_FIELD_OP (UINT16) 0x8b +#define AML_CREATE_BYTE_FIELD_OP (UINT16) 0x8c +#define AML_CREATE_BIT_FIELD_OP (UINT16) 0x8d +#define AML_OBJECT_TYPE_OP (UINT16) 0x8e +#define AML_CREATE_QWORD_FIELD_OP (UINT16) 0x8f /* ACPI 2.0 */ +#define AML_LOGICAL_AND_OP (UINT16) 0x90 +#define AML_LOGICAL_OR_OP (UINT16) 0x91 +#define AML_LOGICAL_NOT_OP (UINT16) 0x92 +#define AML_LOGICAL_EQUAL_OP (UINT16) 0x93 +#define AML_LOGICAL_GREATER_OP (UINT16) 0x94 +#define AML_LOGICAL_LESS_OP (UINT16) 0x95 +#define AML_TO_BUFFER_OP (UINT16) 0x96 /* ACPI 2.0 */ +#define AML_TO_DECIMAL_STRING_OP (UINT16) 0x97 /* ACPI 2.0 */ +#define AML_TO_HEX_STRING_OP (UINT16) 0x98 /* ACPI 2.0 */ +#define AML_TO_INTEGER_OP (UINT16) 0x99 /* ACPI 2.0 */ +#define AML_TO_STRING_OP (UINT16) 0x9c /* ACPI 2.0 */ +#define AML_COPY_OBJECT_OP (UINT16) 0x9d /* ACPI 2.0 */ +#define AML_MID_OP (UINT16) 0x9e /* ACPI 2.0 */ +#define AML_CONTINUE_OP (UINT16) 0x9f /* ACPI 2.0 */ +#define AML_IF_OP (UINT16) 0xa0 +#define AML_ELSE_OP (UINT16) 0xa1 +#define AML_WHILE_OP (UINT16) 0xa2 +#define AML_NOOP_OP (UINT16) 0xa3 +#define AML_RETURN_OP (UINT16) 0xa4 +#define AML_BREAK_OP (UINT16) 0xa5 +#define AML_COMMENT_OP (UINT16) 0xa9 +#define AML_BREAKPOINT_OP (UINT16) 0xcc +#define AML_ONES_OP (UINT16) 0xff + + +/* + * Combination opcodes (actually two one-byte opcodes) + * Used by the disassembler and iASL compiler + */ +#define AML_LOGICAL_GREATER_EQUAL_OP (UINT16) 0x9295 /* LNot (LLess) */ +#define AML_LOGICAL_LESS_EQUAL_OP (UINT16) 0x9294 /* LNot (LGreater) */ +#define AML_LOGICAL_NOT_EQUAL_OP (UINT16) 0x9293 /* LNot (LEqual) */ + + +/* Prefixed (2-byte) opcodes (with AML_EXTENDED_PREFIX) */ + +#define AML_EXTENDED_OPCODE (UINT16) 0x5b00 /* Prefix for 2-byte opcodes */ + +#define AML_MUTEX_OP (UINT16) 0x5b01 +#define AML_EVENT_OP (UINT16) 0x5b02 +#define AML_SHIFT_RIGHT_BIT_OP (UINT16) 0x5b10 /* Obsolete, not in ACPI spec */ +#define AML_SHIFT_LEFT_BIT_OP (UINT16) 0x5b11 /* Obsolete, not in ACPI spec */ +#define AML_CONDITIONAL_REF_OF_OP (UINT16) 0x5b12 +#define AML_CREATE_FIELD_OP (UINT16) 0x5b13 +#define AML_LOAD_TABLE_OP (UINT16) 0x5b1f /* ACPI 2.0 */ +#define AML_LOAD_OP (UINT16) 0x5b20 +#define AML_STALL_OP (UINT16) 0x5b21 +#define AML_SLEEP_OP (UINT16) 0x5b22 +#define AML_ACQUIRE_OP (UINT16) 0x5b23 +#define AML_SIGNAL_OP (UINT16) 0x5b24 +#define AML_WAIT_OP (UINT16) 0x5b25 +#define AML_RESET_OP (UINT16) 0x5b26 +#define AML_RELEASE_OP (UINT16) 0x5b27 +#define AML_FROM_BCD_OP (UINT16) 0x5b28 +#define AML_TO_BCD_OP (UINT16) 0x5b29 +#define AML_UNLOAD_OP (UINT16) 0x5b2a +#define AML_REVISION_OP (UINT16) 0x5b30 +#define AML_DEBUG_OP (UINT16) 0x5b31 +#define AML_FATAL_OP (UINT16) 0x5b32 +#define AML_TIMER_OP (UINT16) 0x5b33 /* ACPI 3.0 */ +#define AML_REGION_OP (UINT16) 0x5b80 +#define AML_FIELD_OP (UINT16) 0x5b81 +#define AML_DEVICE_OP (UINT16) 0x5b82 +#define AML_PROCESSOR_OP (UINT16) 0x5b83 +#define AML_POWER_RESOURCE_OP (UINT16) 0x5b84 +#define AML_THERMAL_ZONE_OP (UINT16) 0x5b85 +#define AML_INDEX_FIELD_OP (UINT16) 0x5b86 +#define AML_BANK_FIELD_OP (UINT16) 0x5b87 +#define AML_DATA_REGION_OP (UINT16) 0x5b88 /* ACPI 2.0 */ + + +/* + * Opcodes for "Field" operators + */ +#define AML_FIELD_OFFSET_OP (UINT8) 0x00 +#define AML_FIELD_ACCESS_OP (UINT8) 0x01 +#define AML_FIELD_CONNECTION_OP (UINT8) 0x02 /* ACPI 5.0 */ +#define AML_FIELD_EXT_ACCESS_OP (UINT8) 0x03 /* ACPI 5.0 */ + + +/* + * Internal opcodes + * Use only "Unknown" AML opcodes, don't attempt to use + * any valid ACPI ASCII values (A-Z, 0-9, '-') + */ +#define AML_INT_NAMEPATH_OP (UINT16) 0x002d +#define AML_INT_NAMEDFIELD_OP (UINT16) 0x0030 +#define AML_INT_RESERVEDFIELD_OP (UINT16) 0x0031 +#define AML_INT_ACCESSFIELD_OP (UINT16) 0x0032 +#define AML_INT_BYTELIST_OP (UINT16) 0x0033 +#define AML_INT_METHODCALL_OP (UINT16) 0x0035 +#define AML_INT_RETURN_VALUE_OP (UINT16) 0x0036 +#define AML_INT_EVAL_SUBTREE_OP (UINT16) 0x0037 +#define AML_INT_CONNECTION_OP (UINT16) 0x0038 +#define AML_INT_EXTACCESSFIELD_OP (UINT16) 0x0039 + +#define ARG_NONE 0x0 + +/* + * Argument types for the AML Parser + * Each field in the ArgTypes UINT32 is 5 bits, allowing for a maximum of 6 arguments. + * There can be up to 31 unique argument types + * Zero is reserved as end-of-list indicator + */ +#define ARGP_BYTEDATA 0x01 +#define ARGP_BYTELIST 0x02 +#define ARGP_CHARLIST 0x03 +#define ARGP_DATAOBJ 0x04 +#define ARGP_DATAOBJLIST 0x05 +#define ARGP_DWORDDATA 0x06 +#define ARGP_FIELDLIST 0x07 +#define ARGP_NAME 0x08 +#define ARGP_NAMESTRING 0x09 +#define ARGP_OBJLIST 0x0A +#define ARGP_PKGLENGTH 0x0B +#define ARGP_SUPERNAME 0x0C +#define ARGP_TARGET 0x0D +#define ARGP_TERMARG 0x0E +#define ARGP_TERMLIST 0x0F +#define ARGP_WORDDATA 0x10 +#define ARGP_QWORDDATA 0x11 +#define ARGP_SIMPLENAME 0x12 /* NameString | LocalTerm | ArgTerm */ +#define ARGP_NAME_OR_REF 0x13 /* For ObjectType only */ +#define ARGP_MAX 0x13 +#define ARGP_COMMENT 0x14 + +/* + * Resolved argument types for the AML Interpreter + * Each field in the ArgTypes UINT32 is 5 bits, allowing for a maximum of 6 arguments. + * There can be up to 31 unique argument types (0 is end-of-arg-list indicator) + * + * Note1: These values are completely independent from the ACPI_TYPEs + * i.e., ARGI_INTEGER != ACPI_TYPE_INTEGER + * + * Note2: If and when 5 bits becomes insufficient, it would probably be best + * to convert to a 6-byte array of argument types, allowing 8 bits per argument. + */ + +/* Single, simple types */ + +#define ARGI_ANYTYPE 0x01 /* Don't care */ +#define ARGI_PACKAGE 0x02 +#define ARGI_EVENT 0x03 +#define ARGI_MUTEX 0x04 +#define ARGI_DDBHANDLE 0x05 + +/* Interchangeable types (via implicit conversion) */ + +#define ARGI_INTEGER 0x06 +#define ARGI_STRING 0x07 +#define ARGI_BUFFER 0x08 +#define ARGI_BUFFER_OR_STRING 0x09 /* Used by MID op only */ +#define ARGI_COMPUTEDATA 0x0A /* Buffer, String, or Integer */ + +/* Reference objects */ + +#define ARGI_INTEGER_REF 0x0B +#define ARGI_OBJECT_REF 0x0C +#define ARGI_DEVICE_REF 0x0D +#define ARGI_REFERENCE 0x0E +#define ARGI_TARGETREF 0x0F /* Target, subject to implicit conversion */ +#define ARGI_FIXED_TARGET 0x10 /* Target, no implicit conversion */ +#define ARGI_SIMPLE_TARGET 0x11 /* Name, Local, Arg -- no implicit conversion */ +#define ARGI_STORE_TARGET 0x12 /* Target for store is TARGETREF + package objects */ + +/* Multiple/complex types */ + +#define ARGI_DATAOBJECT 0x13 /* Buffer, String, package or reference to a Node - Used only by SizeOf operator*/ +#define ARGI_COMPLEXOBJ 0x14 /* Buffer, String, or package (Used by INDEX op only) */ +#define ARGI_REF_OR_STRING 0x15 /* Reference or String (Used by DEREFOF op only) */ +#define ARGI_REGION_OR_BUFFER 0x16 /* Used by LOAD op only */ +#define ARGI_DATAREFOBJ 0x17 + +/* Note: types above can expand to 0x1F maximum */ + +#define ARGI_INVALID_OPCODE 0xFFFFFFFF + + +/* + * Some of the flags and types below are of the form: + * + * AML_FLAGS_EXEC_#A_#T,#R, or + * AML_TYPE_EXEC_#A_#T,#R where: + * + * #A is the number of required arguments + * #T is the number of target operands + * #R indicates whether there is a return value + * + * These types are used for the top-level dispatch of the AML + * opcode. They group similar operators that can share common + * front-end code before dispatch to the final code that implements + * the operator. + */ + +/* + * Opcode information flags + */ +#define AML_LOGICAL 0x0001 +#define AML_LOGICAL_NUMERIC 0x0002 +#define AML_MATH 0x0004 +#define AML_CREATE 0x0008 +#define AML_FIELD 0x0010 +#define AML_DEFER 0x0020 +#define AML_NAMED 0x0040 +#define AML_NSNODE 0x0080 +#define AML_NSOPCODE 0x0100 +#define AML_NSOBJECT 0x0200 +#define AML_HAS_RETVAL 0x0400 +#define AML_HAS_TARGET 0x0800 +#define AML_HAS_ARGS 0x1000 +#define AML_CONSTANT 0x2000 +#define AML_NO_OPERAND_RESOLVE 0x4000 + +/* Convenient flag groupings of the flags above */ + +#define AML_FLAGS_EXEC_0A_0T_1R AML_HAS_RETVAL +#define AML_FLAGS_EXEC_1A_0T_0R AML_HAS_ARGS /* Monadic1 */ +#define AML_FLAGS_EXEC_1A_0T_1R AML_HAS_ARGS | AML_HAS_RETVAL /* Monadic2 */ +#define AML_FLAGS_EXEC_1A_1T_0R AML_HAS_ARGS | AML_HAS_TARGET +#define AML_FLAGS_EXEC_1A_1T_1R AML_HAS_ARGS | AML_HAS_TARGET | AML_HAS_RETVAL /* Monadic2R */ +#define AML_FLAGS_EXEC_2A_0T_0R AML_HAS_ARGS /* Dyadic1 */ +#define AML_FLAGS_EXEC_2A_0T_1R AML_HAS_ARGS | AML_HAS_RETVAL /* Dyadic2 */ +#define AML_FLAGS_EXEC_2A_1T_1R AML_HAS_ARGS | AML_HAS_TARGET | AML_HAS_RETVAL /* Dyadic2R */ +#define AML_FLAGS_EXEC_2A_2T_1R AML_HAS_ARGS | AML_HAS_TARGET | AML_HAS_RETVAL +#define AML_FLAGS_EXEC_3A_0T_0R AML_HAS_ARGS +#define AML_FLAGS_EXEC_3A_1T_1R AML_HAS_ARGS | AML_HAS_TARGET | AML_HAS_RETVAL +#define AML_FLAGS_EXEC_6A_0T_1R AML_HAS_ARGS | AML_HAS_RETVAL + + +/* + * The opcode Type is used in a dispatch table, do not change + * or add anything new without updating the table. + */ +#define AML_TYPE_EXEC_0A_0T_1R 0x00 /* 0 Args, 0 Target, 1 RetVal */ +#define AML_TYPE_EXEC_1A_0T_0R 0x01 /* 1 Args, 0 Target, 0 RetVal */ +#define AML_TYPE_EXEC_1A_0T_1R 0x02 /* 1 Args, 0 Target, 1 RetVal */ +#define AML_TYPE_EXEC_1A_1T_0R 0x03 /* 1 Args, 1 Target, 0 RetVal */ +#define AML_TYPE_EXEC_1A_1T_1R 0x04 /* 1 Args, 1 Target, 1 RetVal */ +#define AML_TYPE_EXEC_2A_0T_0R 0x05 /* 2 Args, 0 Target, 0 RetVal */ +#define AML_TYPE_EXEC_2A_0T_1R 0x06 /* 2 Args, 0 Target, 1 RetVal */ +#define AML_TYPE_EXEC_2A_1T_1R 0x07 /* 2 Args, 1 Target, 1 RetVal */ +#define AML_TYPE_EXEC_2A_2T_1R 0x08 /* 2 Args, 2 Target, 1 RetVal */ +#define AML_TYPE_EXEC_3A_0T_0R 0x09 /* 3 Args, 0 Target, 0 RetVal */ +#define AML_TYPE_EXEC_3A_1T_1R 0x0A /* 3 Args, 1 Target, 1 RetVal */ +#define AML_TYPE_EXEC_6A_0T_1R 0x0B /* 6 Args, 0 Target, 1 RetVal */ +/* End of types used in dispatch table */ + +#define AML_TYPE_LITERAL 0x0C +#define AML_TYPE_CONSTANT 0x0D +#define AML_TYPE_METHOD_ARGUMENT 0x0E +#define AML_TYPE_LOCAL_VARIABLE 0x0F +#define AML_TYPE_DATA_TERM 0x10 + +/* Generic for an op that returns a value */ + +#define AML_TYPE_METHOD_CALL 0x11 + +/* Miscellaneous types */ + +#define AML_TYPE_CREATE_FIELD 0x12 +#define AML_TYPE_CREATE_OBJECT 0x13 +#define AML_TYPE_CONTROL 0x14 +#define AML_TYPE_NAMED_NO_OBJ 0x15 +#define AML_TYPE_NAMED_FIELD 0x16 +#define AML_TYPE_NAMED_SIMPLE 0x17 +#define AML_TYPE_NAMED_COMPLEX 0x18 +#define AML_TYPE_RETURN 0x19 +#define AML_TYPE_UNDEFINED 0x1A +#define AML_TYPE_BOGUS 0x1B + +/* AML Package Length encodings */ + +#define ACPI_AML_PACKAGE_TYPE1 0x40 +#define ACPI_AML_PACKAGE_TYPE2 0x4000 +#define ACPI_AML_PACKAGE_TYPE3 0x400000 +#define ACPI_AML_PACKAGE_TYPE4 0x40000000 + +/* + * Opcode classes + */ +#define AML_CLASS_EXECUTE 0x00 +#define AML_CLASS_CREATE 0x01 +#define AML_CLASS_ARGUMENT 0x02 +#define AML_CLASS_NAMED_OBJECT 0x03 +#define AML_CLASS_CONTROL 0x04 +#define AML_CLASS_ASCII 0x05 +#define AML_CLASS_PREFIX 0x06 +#define AML_CLASS_INTERNAL 0x07 +#define AML_CLASS_RETURN_VALUE 0x08 +#define AML_CLASS_METHOD_CALL 0x09 +#define AML_CLASS_UNKNOWN 0x0A + + +/* Comparison operation codes for MatchOp operator */ + +typedef enum +{ + MATCH_MTR = 0, + MATCH_MEQ = 1, + MATCH_MLE = 2, + MATCH_MLT = 3, + MATCH_MGE = 4, + MATCH_MGT = 5 + +} AML_MATCH_OPERATOR; + +#define MAX_MATCH_OPERATOR 5 + + +/* + * FieldFlags + * + * This byte is extracted from the AML and includes three separate + * pieces of information about the field: + * 1) The field access type + * 2) The field update rule + * 3) The lock rule for the field + * + * Bits 00 - 03 : AccessType (AnyAcc, ByteAcc, etc.) + * 04 : LockRule (1 == Lock) + * 05 - 06 : UpdateRule + */ +#define AML_FIELD_ACCESS_TYPE_MASK 0x0F +#define AML_FIELD_LOCK_RULE_MASK 0x10 +#define AML_FIELD_UPDATE_RULE_MASK 0x60 + + +/* 1) Field Access Types */ + +typedef enum +{ + AML_FIELD_ACCESS_ANY = 0x00, + AML_FIELD_ACCESS_BYTE = 0x01, + AML_FIELD_ACCESS_WORD = 0x02, + AML_FIELD_ACCESS_DWORD = 0x03, + AML_FIELD_ACCESS_QWORD = 0x04, /* ACPI 2.0 */ + AML_FIELD_ACCESS_BUFFER = 0x05 /* ACPI 2.0 */ + +} AML_ACCESS_TYPE; + + +/* 2) Field Lock Rules */ + +typedef enum +{ + AML_FIELD_LOCK_NEVER = 0x00, + AML_FIELD_LOCK_ALWAYS = 0x10 + +} AML_LOCK_RULE; + + +/* 3) Field Update Rules */ + +typedef enum +{ + AML_FIELD_UPDATE_PRESERVE = 0x00, + AML_FIELD_UPDATE_WRITE_AS_ONES = 0x20, + AML_FIELD_UPDATE_WRITE_AS_ZEROS = 0x40 + +} AML_UPDATE_RULE; + + +/* + * Field Access Attributes. + * This byte is extracted from the AML via the + * AccessAs keyword + */ +typedef enum +{ + AML_FIELD_ATTRIB_QUICK = 0x02, + AML_FIELD_ATTRIB_SEND_RCV = 0x04, + AML_FIELD_ATTRIB_BYTE = 0x06, + AML_FIELD_ATTRIB_WORD = 0x08, + AML_FIELD_ATTRIB_BLOCK = 0x0A, + AML_FIELD_ATTRIB_MULTIBYTE = 0x0B, + AML_FIELD_ATTRIB_WORD_CALL = 0x0C, + AML_FIELD_ATTRIB_BLOCK_CALL = 0x0D, + AML_FIELD_ATTRIB_RAW_BYTES = 0x0E, + AML_FIELD_ATTRIB_RAW_PROCESS = 0x0F + +} AML_ACCESS_ATTRIBUTE; + + +/* Bit fields in the AML MethodFlags byte */ + +#define AML_METHOD_ARG_COUNT 0x07 +#define AML_METHOD_SERIALIZED 0x08 +#define AML_METHOD_SYNC_LEVEL 0xF0 + + +#endif /* __AMLCODE_H__ */ diff --git a/ports/acpica/include/amlresrc.h b/ports/acpica/include/amlresrc.h new file mode 100644 index 0000000..e9ee794 --- /dev/null +++ b/ports/acpica/include/amlresrc.h @@ -0,0 +1,857 @@ +/****************************************************************************** + * + * Module Name: amlresrc.h - AML resource descriptors + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +/* acpisrc:StructDefs -- for acpisrc conversion */ + +#ifndef __AMLRESRC_H +#define __AMLRESRC_H + + +/* + * Resource descriptor tags, as defined in the ACPI specification. + * Used to symbolically reference fields within a descriptor. + */ +#define ACPI_RESTAG_ADDRESS "_ADR" +#define ACPI_RESTAG_ALIGNMENT "_ALN" +#define ACPI_RESTAG_ADDRESSSPACE "_ASI" +#define ACPI_RESTAG_ACCESSSIZE "_ASZ" +#define ACPI_RESTAG_TYPESPECIFICATTRIBUTES "_ATT" +#define ACPI_RESTAG_BASEADDRESS "_BAS" +#define ACPI_RESTAG_BUSMASTER "_BM_" /* Master(1), Slave(0) */ +#define ACPI_RESTAG_DEBOUNCETIME "_DBT" +#define ACPI_RESTAG_DECODE "_DEC" +#define ACPI_RESTAG_DEVICEPOLARITY "_DPL" +#define ACPI_RESTAG_DMA "_DMA" +#define ACPI_RESTAG_DMATYPE "_TYP" /* Compatible(0), A(1), B(2), F(3) */ +#define ACPI_RESTAG_DRIVESTRENGTH "_DRS" +#define ACPI_RESTAG_ENDIANNESS "_END" +#define ACPI_RESTAG_FLOWCONTROL "_FLC" +#define ACPI_RESTAG_FUNCTION "_FUN" +#define ACPI_RESTAG_GRANULARITY "_GRA" +#define ACPI_RESTAG_INTERRUPT "_INT" +#define ACPI_RESTAG_INTERRUPTLEVEL "_LL_" /* ActiveLo(1), ActiveHi(0) */ +#define ACPI_RESTAG_INTERRUPTSHARE "_SHR" /* Shareable(1), NoShare(0) */ +#define ACPI_RESTAG_INTERRUPTTYPE "_HE_" /* Edge(1), Level(0) */ +#define ACPI_RESTAG_IORESTRICTION "_IOR" +#define ACPI_RESTAG_LENGTH "_LEN" +#define ACPI_RESTAG_LINE "_LIN" +#define ACPI_RESTAG_MEMATTRIBUTES "_MTP" /* Memory(0), Reserved(1), ACPI(2), NVS(3) */ +#define ACPI_RESTAG_MEMTYPE "_MEM" /* NonCache(0), Cacheable(1) Cache+combine(2), Cache+prefetch(3) */ +#define ACPI_RESTAG_MAXADDR "_MAX" +#define ACPI_RESTAG_MINADDR "_MIN" +#define ACPI_RESTAG_MAXTYPE "_MAF" +#define ACPI_RESTAG_MINTYPE "_MIF" +#define ACPI_RESTAG_MODE "_MOD" +#define ACPI_RESTAG_PARITY "_PAR" +#define ACPI_RESTAG_PHASE "_PHA" +#define ACPI_RESTAG_PIN "_PIN" +#define ACPI_RESTAG_PINCONFIG "_PPI" +#define ACPI_RESTAG_PINCONFIG_TYPE "_TYP" +#define ACPI_RESTAG_PINCONFIG_VALUE "_VAL" +#define ACPI_RESTAG_POLARITY "_POL" +#define ACPI_RESTAG_REGISTERBITOFFSET "_RBO" +#define ACPI_RESTAG_REGISTERBITWIDTH "_RBW" +#define ACPI_RESTAG_RANGETYPE "_RNG" +#define ACPI_RESTAG_READWRITETYPE "_RW_" /* ReadOnly(0), Writeable (1) */ +#define ACPI_RESTAG_LENGTH_RX "_RXL" +#define ACPI_RESTAG_LENGTH_TX "_TXL" +#define ACPI_RESTAG_SLAVEMODE "_SLV" +#define ACPI_RESTAG_SPEED "_SPE" +#define ACPI_RESTAG_STOPBITS "_STB" +#define ACPI_RESTAG_TRANSLATION "_TRA" +#define ACPI_RESTAG_TRANSTYPE "_TRS" /* Sparse(1), Dense(0) */ +#define ACPI_RESTAG_TYPE "_TTP" /* Translation(1), Static (0) */ +#define ACPI_RESTAG_XFERTYPE "_SIZ" /* 8(0), 8And16(1), 16(2) */ +#define ACPI_RESTAG_VENDORDATA "_VEN" + + +/* Default sizes for "small" resource descriptors */ + +#define ASL_RDESC_IRQ_SIZE 0x02 +#define ASL_RDESC_DMA_SIZE 0x02 +#define ASL_RDESC_ST_DEPEND_SIZE 0x00 +#define ASL_RDESC_END_DEPEND_SIZE 0x00 +#define ASL_RDESC_IO_SIZE 0x07 +#define ASL_RDESC_FIXED_IO_SIZE 0x03 +#define ASL_RDESC_FIXED_DMA_SIZE 0x05 +#define ASL_RDESC_END_TAG_SIZE 0x01 + + +typedef struct asl_resource_node +{ + UINT32 BufferLength; + void *Buffer; + struct asl_resource_node *Next; + +} ASL_RESOURCE_NODE; + +typedef struct asl_resource_info +{ + ACPI_PARSE_OBJECT *DescriptorTypeOp; /* Resource descriptor parse node */ + ACPI_PARSE_OBJECT *MappingOp; /* Used for mapfile support */ + UINT32 CurrentByteOffset; /* Offset in resource template */ + +} ASL_RESOURCE_INFO; + + +/* Macros used to generate AML resource length fields */ + +#define ACPI_AML_SIZE_LARGE(r) (sizeof (r) - sizeof (AML_RESOURCE_LARGE_HEADER)) +#define ACPI_AML_SIZE_SMALL(r) (sizeof (r) - sizeof (AML_RESOURCE_SMALL_HEADER)) + +/* + * Resource descriptors defined in the ACPI specification. + * + * Packing/alignment must be BYTE because these descriptors + * are used to overlay the raw AML byte stream. + */ +#pragma pack(1) + +/* + * SMALL descriptors + */ +#define AML_RESOURCE_SMALL_HEADER_COMMON \ + UINT8 DescriptorType; + +typedef struct aml_resource_small_header +{ + AML_RESOURCE_SMALL_HEADER_COMMON + +} AML_RESOURCE_SMALL_HEADER; + + +typedef struct aml_resource_irq +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT16 IrqMask; + UINT8 Flags; + +} AML_RESOURCE_IRQ; + + +typedef struct aml_resource_irq_noflags +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT16 IrqMask; + +} AML_RESOURCE_IRQ_NOFLAGS; + + +typedef struct aml_resource_dma +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT8 DmaChannelMask; + UINT8 Flags; + +} AML_RESOURCE_DMA; + + +typedef struct aml_resource_start_dependent +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT8 Flags; + +} AML_RESOURCE_START_DEPENDENT; + + +typedef struct aml_resource_start_dependent_noprio +{ + AML_RESOURCE_SMALL_HEADER_COMMON + +} AML_RESOURCE_START_DEPENDENT_NOPRIO; + + +typedef struct aml_resource_end_dependent +{ + AML_RESOURCE_SMALL_HEADER_COMMON + +} AML_RESOURCE_END_DEPENDENT; + + +typedef struct aml_resource_io +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT8 Flags; + UINT16 Minimum; + UINT16 Maximum; + UINT8 Alignment; + UINT8 AddressLength; + +} AML_RESOURCE_IO; + + +typedef struct aml_resource_fixed_io +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT16 Address; + UINT8 AddressLength; + +} AML_RESOURCE_FIXED_IO; + + +typedef struct aml_resource_vendor_small +{ + AML_RESOURCE_SMALL_HEADER_COMMON + +} AML_RESOURCE_VENDOR_SMALL; + + +typedef struct aml_resource_end_tag +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT8 Checksum; + +} AML_RESOURCE_END_TAG; + + +typedef struct aml_resource_fixed_dma +{ + AML_RESOURCE_SMALL_HEADER_COMMON + UINT16 RequestLines; + UINT16 Channels; + UINT8 Width; + +} AML_RESOURCE_FIXED_DMA; + + +/* + * LARGE descriptors + */ +#define AML_RESOURCE_LARGE_HEADER_COMMON \ + UINT8 DescriptorType;\ + UINT16 ResourceLength; + +typedef struct aml_resource_large_header +{ + AML_RESOURCE_LARGE_HEADER_COMMON + +} AML_RESOURCE_LARGE_HEADER; + + +/* General Flags for address space resource descriptors */ + +#define ACPI_RESOURCE_FLAG_DEC 2 +#define ACPI_RESOURCE_FLAG_MIF 4 +#define ACPI_RESOURCE_FLAG_MAF 8 + +typedef struct aml_resource_memory24 +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 Flags; + UINT16 Minimum; + UINT16 Maximum; + UINT16 Alignment; + UINT16 AddressLength; + +} AML_RESOURCE_MEMORY24; + + +typedef struct aml_resource_vendor_large +{ + AML_RESOURCE_LARGE_HEADER_COMMON + +} AML_RESOURCE_VENDOR_LARGE; + + +typedef struct aml_resource_memory32 +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 Flags; + UINT32 Minimum; + UINT32 Maximum; + UINT32 Alignment; + UINT32 AddressLength; + +} AML_RESOURCE_MEMORY32; + + +typedef struct aml_resource_fixed_memory32 +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 Flags; + UINT32 Address; + UINT32 AddressLength; + +} AML_RESOURCE_FIXED_MEMORY32; + + +#define AML_RESOURCE_ADDRESS_COMMON \ + UINT8 ResourceType; \ + UINT8 Flags; \ + UINT8 SpecificFlags; + + +typedef struct aml_resource_address +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_ADDRESS_COMMON + +} AML_RESOURCE_ADDRESS; + + +typedef struct aml_resource_extended_address64 +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_ADDRESS_COMMON + UINT8 RevisionID; + UINT8 Reserved; + UINT64 Granularity; + UINT64 Minimum; + UINT64 Maximum; + UINT64 TranslationOffset; + UINT64 AddressLength; + UINT64 TypeSpecific; + +} AML_RESOURCE_EXTENDED_ADDRESS64; + +#define AML_RESOURCE_EXTENDED_ADDRESS_REVISION 1 /* ACPI 3.0 */ + + +typedef struct aml_resource_address64 +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_ADDRESS_COMMON + UINT64 Granularity; + UINT64 Minimum; + UINT64 Maximum; + UINT64 TranslationOffset; + UINT64 AddressLength; + +} AML_RESOURCE_ADDRESS64; + + +typedef struct aml_resource_address32 +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_ADDRESS_COMMON + UINT32 Granularity; + UINT32 Minimum; + UINT32 Maximum; + UINT32 TranslationOffset; + UINT32 AddressLength; + +} AML_RESOURCE_ADDRESS32; + + +typedef struct aml_resource_address16 +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_ADDRESS_COMMON + UINT16 Granularity; + UINT16 Minimum; + UINT16 Maximum; + UINT16 TranslationOffset; + UINT16 AddressLength; + +} AML_RESOURCE_ADDRESS16; + + +typedef struct aml_resource_extended_irq +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 Flags; + UINT8 InterruptCount; + UINT32 Interrupts[1]; + /* ResSourceIndex, ResSource optional fields follow */ + +} AML_RESOURCE_EXTENDED_IRQ; + + +typedef struct aml_resource_generic_register +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 AddressSpaceId; + UINT8 BitWidth; + UINT8 BitOffset; + UINT8 AccessSize; /* ACPI 3.0, was previously Reserved */ + UINT64 Address; + +} AML_RESOURCE_GENERIC_REGISTER; + + +/* Common descriptor for GpioInt and GpioIo (ACPI 5.0) */ + +typedef struct aml_resource_gpio +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 RevisionId; + UINT8 ConnectionType; + UINT16 Flags; + UINT16 IntFlags; + UINT8 PinConfig; + UINT16 DriveStrength; + UINT16 DebounceTimeout; + UINT16 PinTableOffset; + UINT8 ResSourceIndex; + UINT16 ResSourceOffset; + UINT16 VendorOffset; + UINT16 VendorLength; + /* + * Optional fields follow immediately: + * 1) PIN list (Words) + * 2) Resource Source String + * 3) Vendor Data bytes + */ + +} AML_RESOURCE_GPIO; + +#define AML_RESOURCE_GPIO_REVISION 1 /* ACPI 5.0 */ + +/* Values for ConnectionType above */ + +#define AML_RESOURCE_GPIO_TYPE_INT 0 +#define AML_RESOURCE_GPIO_TYPE_IO 1 +#define AML_RESOURCE_MAX_GPIOTYPE 1 + + +/* Common preamble for all serial descriptors (ACPI 5.0) */ + +#define AML_RESOURCE_SERIAL_COMMON \ + UINT8 RevisionId; \ + UINT8 ResSourceIndex; \ + UINT8 Type; \ + UINT8 Flags; \ + UINT16 TypeSpecificFlags; \ + UINT8 TypeRevisionId; \ + UINT16 TypeDataLength; \ + +/* Values for the type field above */ + +#define AML_RESOURCE_I2C_SERIALBUSTYPE 1 +#define AML_RESOURCE_SPI_SERIALBUSTYPE 2 +#define AML_RESOURCE_UART_SERIALBUSTYPE 3 +#define AML_RESOURCE_MAX_SERIALBUSTYPE 3 +#define AML_RESOURCE_VENDOR_SERIALBUSTYPE 192 /* Vendor defined is 0xC0-0xFF (NOT SUPPORTED) */ + +typedef struct aml_resource_common_serialbus +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_SERIAL_COMMON + +} AML_RESOURCE_COMMON_SERIALBUS; + +typedef struct aml_resource_i2c_serialbus +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_SERIAL_COMMON + UINT32 ConnectionSpeed; + UINT16 SlaveAddress; + /* + * Optional fields follow immediately: + * 1) Vendor Data bytes + * 2) Resource Source String + */ + +} AML_RESOURCE_I2C_SERIALBUS; + +#define AML_RESOURCE_I2C_REVISION 1 /* ACPI 5.0 */ +#define AML_RESOURCE_I2C_TYPE_REVISION 1 /* ACPI 5.0 */ +#define AML_RESOURCE_I2C_MIN_DATA_LEN 6 + +typedef struct aml_resource_spi_serialbus +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_SERIAL_COMMON + UINT32 ConnectionSpeed; + UINT8 DataBitLength; + UINT8 ClockPhase; + UINT8 ClockPolarity; + UINT16 DeviceSelection; + /* + * Optional fields follow immediately: + * 1) Vendor Data bytes + * 2) Resource Source String + */ + +} AML_RESOURCE_SPI_SERIALBUS; + +#define AML_RESOURCE_SPI_REVISION 1 /* ACPI 5.0 */ +#define AML_RESOURCE_SPI_TYPE_REVISION 1 /* ACPI 5.0 */ +#define AML_RESOURCE_SPI_MIN_DATA_LEN 9 + + +typedef struct aml_resource_uart_serialbus +{ + AML_RESOURCE_LARGE_HEADER_COMMON + AML_RESOURCE_SERIAL_COMMON + UINT32 DefaultBaudRate; + UINT16 RxFifoSize; + UINT16 TxFifoSize; + UINT8 Parity; + UINT8 LinesEnabled; + /* + * Optional fields follow immediately: + * 1) Vendor Data bytes + * 2) Resource Source String + */ + +} AML_RESOURCE_UART_SERIALBUS; + +#define AML_RESOURCE_UART_REVISION 1 /* ACPI 5.0 */ +#define AML_RESOURCE_UART_TYPE_REVISION 1 /* ACPI 5.0 */ +#define AML_RESOURCE_UART_MIN_DATA_LEN 10 + +typedef struct aml_resource_pin_function +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 RevisionId; + UINT16 Flags; + UINT8 PinConfig; + UINT16 FunctionNumber; + UINT16 PinTableOffset; + UINT8 ResSourceIndex; + UINT16 ResSourceOffset; + UINT16 VendorOffset; + UINT16 VendorLength; + /* + * Optional fields follow immediately: + * 1) PIN list (Words) + * 2) Resource Source String + * 3) Vendor Data bytes + */ + +} AML_RESOURCE_PIN_FUNCTION; + +#define AML_RESOURCE_PIN_FUNCTION_REVISION 1 /* ACPI 6.2 */ + +typedef struct aml_resource_pin_config +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 RevisionId; + UINT16 Flags; + UINT8 PinConfigType; + UINT32 PinConfigValue; + UINT16 PinTableOffset; + UINT8 ResSourceIndex; + UINT16 ResSourceOffset; + UINT16 VendorOffset; + UINT16 VendorLength; + /* + * Optional fields follow immediately: + * 1) PIN list (Words) + * 2) Resource Source String + * 3) Vendor Data bytes + */ + +} AML_RESOURCE_PIN_CONFIG; + +#define AML_RESOURCE_PIN_CONFIG_REVISION 1 /* ACPI 6.2 */ + +typedef struct aml_resource_pin_group +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 RevisionId; + UINT16 Flags; + UINT16 PinTableOffset; + UINT16 LabelOffset; + UINT16 VendorOffset; + UINT16 VendorLength; + /* + * Optional fields follow immediately: + * 1) PIN list (Words) + * 2) Resource Label String + * 3) Vendor Data bytes + */ + +} AML_RESOURCE_PIN_GROUP; + +#define AML_RESOURCE_PIN_GROUP_REVISION 1 /* ACPI 6.2 */ + +typedef struct aml_resource_pin_group_function +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 RevisionId; + UINT16 Flags; + UINT16 FunctionNumber; + UINT8 ResSourceIndex; + UINT16 ResSourceOffset; + UINT16 ResSourceLabelOffset; + UINT16 VendorOffset; + UINT16 VendorLength; + /* + * Optional fields follow immediately: + * 1) Resource Source String + * 2) Resource Source Label String + * 3) Vendor Data bytes + */ + +} AML_RESOURCE_PIN_GROUP_FUNCTION; + +#define AML_RESOURCE_PIN_GROUP_FUNCTION_REVISION 1 /* ACPI 6.2 */ + +typedef struct aml_resource_pin_group_config +{ + AML_RESOURCE_LARGE_HEADER_COMMON + UINT8 RevisionId; + UINT16 Flags; + UINT8 PinConfigType; + UINT32 PinConfigValue; + UINT8 ResSourceIndex; + UINT16 ResSourceOffset; + UINT16 ResSourceLabelOffset; + UINT16 VendorOffset; + UINT16 VendorLength; + /* + * Optional fields follow immediately: + * 1) Resource Source String + * 2) Resource Source Label String + * 3) Vendor Data bytes + */ + +} AML_RESOURCE_PIN_GROUP_CONFIG; + +#define AML_RESOURCE_PIN_GROUP_CONFIG_REVISION 1 /* ACPI 6.2 */ + +/* restore default alignment */ + +#pragma pack() + +/* Union of all resource descriptors, so we can allocate the worst case */ + +typedef union aml_resource +{ + /* Descriptor headers */ + + UINT8 DescriptorType; + AML_RESOURCE_SMALL_HEADER SmallHeader; + AML_RESOURCE_LARGE_HEADER LargeHeader; + + /* Small resource descriptors */ + + AML_RESOURCE_IRQ Irq; + AML_RESOURCE_DMA Dma; + AML_RESOURCE_START_DEPENDENT StartDpf; + AML_RESOURCE_END_DEPENDENT EndDpf; + AML_RESOURCE_IO Io; + AML_RESOURCE_FIXED_IO FixedIo; + AML_RESOURCE_FIXED_DMA FixedDma; + AML_RESOURCE_VENDOR_SMALL VendorSmall; + AML_RESOURCE_END_TAG EndTag; + + /* Large resource descriptors */ + + AML_RESOURCE_MEMORY24 Memory24; + AML_RESOURCE_GENERIC_REGISTER GenericReg; + AML_RESOURCE_VENDOR_LARGE VendorLarge; + AML_RESOURCE_MEMORY32 Memory32; + AML_RESOURCE_FIXED_MEMORY32 FixedMemory32; + AML_RESOURCE_ADDRESS16 Address16; + AML_RESOURCE_ADDRESS32 Address32; + AML_RESOURCE_ADDRESS64 Address64; + AML_RESOURCE_EXTENDED_ADDRESS64 ExtAddress64; + AML_RESOURCE_EXTENDED_IRQ ExtendedIrq; + AML_RESOURCE_GPIO Gpio; + AML_RESOURCE_I2C_SERIALBUS I2cSerialBus; + AML_RESOURCE_SPI_SERIALBUS SpiSerialBus; + AML_RESOURCE_UART_SERIALBUS UartSerialBus; + AML_RESOURCE_COMMON_SERIALBUS CommonSerialBus; + AML_RESOURCE_PIN_FUNCTION PinFunction; + AML_RESOURCE_PIN_CONFIG PinConfig; + AML_RESOURCE_PIN_GROUP PinGroup; + AML_RESOURCE_PIN_GROUP_FUNCTION PinGroupFunction; + AML_RESOURCE_PIN_GROUP_CONFIG PinGroupConfig; + + /* Utility overlays */ + + AML_RESOURCE_ADDRESS Address; + UINT32 DwordItem; + UINT16 WordItem; + UINT8 ByteItem; + +} AML_RESOURCE; + + +/* Interfaces used by both the disassembler and compiler */ + +void +MpSaveGpioInfo ( + ACPI_PARSE_OBJECT *Op, + AML_RESOURCE *Resource, + UINT32 PinCount, + UINT16 *PinList, + char *DeviceName); + +void +MpSaveSerialInfo ( + ACPI_PARSE_OBJECT *Op, + AML_RESOURCE *Resource, + char *DeviceName); + +char * +MpGetHidFromParseTree ( + ACPI_NAMESPACE_NODE *HidNode); + +char * +MpGetHidViaNamestring ( + char *DeviceName); + +char * +MpGetConnectionInfo ( + ACPI_PARSE_OBJECT *Op, + UINT32 PinIndex, + ACPI_NAMESPACE_NODE **TargetNode, + char **TargetName); + +char * +MpGetParentDeviceHid ( + ACPI_PARSE_OBJECT *Op, + ACPI_NAMESPACE_NODE **TargetNode, + char **ParentDeviceName); + +char * +MpGetDdnValue ( + char *DeviceName); + +char * +MpGetHidValue ( + ACPI_NAMESPACE_NODE *DeviceNode); + +#endif diff --git a/ports/acpica/include/platform/acenv.h b/ports/acpica/include/platform/acenv.h new file mode 100644 index 0000000..09f74fb --- /dev/null +++ b/ports/acpica/include/platform/acenv.h @@ -0,0 +1,504 @@ +/****************************************************************************** + * + * Name: acenv.h - Host and compiler configuration + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACENV_H__ +#define __ACENV_H__ + +/* + * Environment configuration. The purpose of this file is to interface ACPICA + * to the local environment. This includes compiler-specific, OS-specific, + * and machine-specific configuration. + */ + +/* Types for ACPI_MUTEX_TYPE */ + +#define ACPI_BINARY_SEMAPHORE 0 +#define ACPI_OSL_MUTEX 1 + +/* Types for DEBUGGER_THREADING */ + +#define DEBUGGER_SINGLE_THREADED 0 +#define DEBUGGER_MULTI_THREADED 1 + + +/****************************************************************************** + * + * Configuration for ACPI tools and utilities + * + *****************************************************************************/ + +/* Common application configuration. All single threaded except for AcpiExec. */ + +#if (defined ACPI_ASL_COMPILER) || \ + (defined ACPI_BIN_APP) || \ + (defined ACPI_DUMP_APP) || \ + (defined ACPI_HELP_APP) || \ + (defined ACPI_NAMES_APP) || \ + (defined ACPI_SRC_APP) || \ + (defined ACPI_XTRACT_APP) || \ + (defined ACPI_EXAMPLE_APP) || \ + (defined ACPI_EFI_HELLO) +#define ACPI_APPLICATION +#define ACPI_SINGLE_THREADED +#define USE_NATIVE_ALLOCATE_ZEROED +#endif + +/* iASL configuration */ + +#ifdef ACPI_ASL_COMPILER +#define ACPI_DEBUG_OUTPUT +#define ACPI_CONSTANT_EVAL_ONLY +#define ACPI_LARGE_NAMESPACE_NODE +#define ACPI_DATA_TABLE_DISASSEMBLY +#define ACPI_32BIT_PHYSICAL_ADDRESS +#define ACPI_DISASSEMBLER 1 +#endif + +/* AcpiExec configuration. Multithreaded with full AML debugger */ + +#ifdef ACPI_EXEC_APP +#define ACPI_APPLICATION +#define ACPI_FULL_DEBUG +#define ACPI_MUTEX_DEBUG +#define ACPI_DBG_TRACK_ALLOCATIONS +#endif + +/* AcpiHelp configuration. Error messages disabled. */ + +#ifdef ACPI_HELP_APP +#define ACPI_NO_ERROR_MESSAGES +#endif + +/* AcpiNames configuration. Debug output enabled. */ + +#ifdef ACPI_NAMES_APP +#define ACPI_DEBUG_OUTPUT +#endif + +/* AcpiExec/AcpiNames/Example configuration. Native RSDP used. */ + +#if (defined ACPI_EXEC_APP) || \ + (defined ACPI_EXAMPLE_APP) || \ + (defined ACPI_NAMES_APP) +#define ACPI_USE_NATIVE_RSDP_POINTER +#endif + +/* AcpiDump configuration. Native mapping used if provided by the host */ + +#ifdef ACPI_DUMP_APP +#define ACPI_USE_NATIVE_MEMORY_MAPPING +#endif + +/* AcpiNames/Example configuration. Hardware disabled */ + +#if (defined ACPI_EXAMPLE_APP) || \ + (defined ACPI_NAMES_APP) +#define ACPI_REDUCED_HARDWARE 1 +#endif + +/* Linkable ACPICA library. Two versions, one with full debug. */ + +#ifdef ACPI_LIBRARY +#define ACPI_USE_LOCAL_CACHE +#define ACPI_DEBUGGER 1 +#define ACPI_DISASSEMBLER 1 + +#ifdef _DEBUG +#define ACPI_DEBUG_OUTPUT +#endif +#endif + +/* Common for all ACPICA applications */ + +#ifdef ACPI_APPLICATION +#define ACPI_USE_LOCAL_CACHE +#endif + +/* Common debug/disassembler support */ + +#ifdef ACPI_FULL_DEBUG +#define ACPI_DEBUG_OUTPUT +#define ACPI_DEBUGGER 1 +#define ACPI_DISASSEMBLER 1 +#endif + +/*! [Begin] no source code translation */ + +/****************************************************************************** + * + * Host configuration files. The compiler configuration files are included + * first. + * + *****************************************************************************/ + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#include "acgcc.h" + +#elif defined(_MSC_VER) +#include "acmsvc.h" + +#elif defined(__INTEL_COMPILER) +#include "acintel.h" + +#endif + +#include "acessence.h" + +#if defined(_LINUX) || defined(__linux__) +#include "aclinux.h" + +#elif defined(_APPLE) || defined(__APPLE__) +#include "acmacosx.h" + +#elif defined(__DragonFly__) +#include "acdragonfly.h" + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#include "acfreebsd.h" + +#elif defined(__NetBSD__) +#include "acnetbsd.h" + +#elif defined(__sun) +#include "acsolaris.h" + +#elif defined(MODESTO) +#include "acmodesto.h" + +#elif defined(NETWARE) +#include "acnetware.h" + +#elif defined(_CYGWIN) +#include "accygwin.h" + +#elif defined(WIN32) +#include "acwin.h" + +#elif defined(WIN64) +#include "acwin64.h" + +#elif defined(_WRS_LIB_BUILD) +#include "acvxworks.h" + +#elif defined(__OS2__) +#include "acos2.h" + +#elif defined(__HAIKU__) +#include "achaiku.h" + +#elif defined(__QNX__) +#include "acqnx.h" + +/* + * EFI applications can be built with -nostdlib, in this case, it must be + * included after including all other host environmental definitions, in + * order to override the definitions. + */ +#elif defined(_AED_EFI) || defined(_GNU_EFI) || defined(_EDK2_EFI) +#include "acefi.h" + +#else + +/* Unknown environment */ + +/*#error Unknown target environment*/ +#endif + +/*! [End] no source code translation !*/ + + +/****************************************************************************** + * + * Setup defaults for the required symbols that were not defined in one of + * the host/compiler files above. + * + *****************************************************************************/ + +/* 64-bit data types */ + +#ifndef COMPILER_DEPENDENT_INT64 +#define COMPILER_DEPENDENT_INT64 long long +#endif + +#ifndef COMPILER_DEPENDENT_UINT64 +#define COMPILER_DEPENDENT_UINT64 unsigned long long +#endif + +/* Type of mutex supported by host. Default is binary semaphores. */ + +#ifndef ACPI_MUTEX_TYPE +#define ACPI_MUTEX_TYPE ACPI_BINARY_SEMAPHORE +#endif + +/* Global Lock acquire/release */ + +#ifndef ACPI_ACQUIRE_GLOBAL_LOCK +#define ACPI_ACQUIRE_GLOBAL_LOCK(GLptr, Acquired) Acquired = 1 +#endif + +#ifndef ACPI_RELEASE_GLOBAL_LOCK +#define ACPI_RELEASE_GLOBAL_LOCK(GLptr, Pending) Pending = 0 +#endif + +/* Flush CPU cache - used when going to sleep. Wbinvd or similar. */ + +#ifndef ACPI_FLUSH_CPU_CACHE +#define ACPI_FLUSH_CPU_CACHE() +#endif + +/* "inline" keywords - configurable since inline is not standardized */ + +#ifndef ACPI_INLINE +#define ACPI_INLINE +#endif + +/* Use ordered initialization if compiler doesn't support designated. */ +#ifndef ACPI_STRUCT_INIT +#define ACPI_STRUCT_INIT(field, value) value +#endif + +/* + * Configurable calling conventions: + * + * ACPI_SYSTEM_XFACE - Interfaces to host OS (handlers, threads) + * ACPI_EXTERNAL_XFACE - External ACPI interfaces + * ACPI_INTERNAL_XFACE - Internal ACPI interfaces + * ACPI_INTERNAL_VAR_XFACE - Internal variable-parameter list interfaces + */ +#ifndef ACPI_SYSTEM_XFACE +#define ACPI_SYSTEM_XFACE +#endif + +#ifndef ACPI_EXTERNAL_XFACE +#define ACPI_EXTERNAL_XFACE +#endif + +#ifndef ACPI_INTERNAL_XFACE +#define ACPI_INTERNAL_XFACE +#endif + +#ifndef ACPI_INTERNAL_VAR_XFACE +#define ACPI_INTERNAL_VAR_XFACE +#endif + + +/* + * Debugger threading model + * Use single threaded if the entire subsystem is contained in an application + * Use multiple threaded when the subsystem is running in the kernel. + * + * By default the model is single threaded if ACPI_APPLICATION is set, + * multi-threaded if ACPI_APPLICATION is not set. + */ +#ifndef DEBUGGER_THREADING +#if !defined (ACPI_APPLICATION) || defined (ACPI_EXEC_APP) +#define DEBUGGER_THREADING DEBUGGER_MULTI_THREADED + +#else +#define DEBUGGER_THREADING DEBUGGER_SINGLE_THREADED +#endif +#endif /* !DEBUGGER_THREADING */ + + +/****************************************************************************** + * + * C library configuration + * + *****************************************************************************/ + +/* + * ACPI_USE_SYSTEM_CLIBRARY - Define this if linking to an actual C library. + * Otherwise, local versions of string/memory functions will be used. + * ACPI_USE_STANDARD_HEADERS - Define this if linking to a C library and + * the standard header files may be used. Defining this implies that + * ACPI_USE_SYSTEM_CLIBRARY has been defined. + * + * The ACPICA subsystem only uses low level C library functions that do not + * call operating system services and may therefore be inlined in the code. + * + * It may be necessary to tailor these include files to the target + * generation environment. + */ + +/* Use the standard C library headers. We want to keep these to a minimum. */ + +#ifdef ACPI_USE_STANDARD_HEADERS + +/* Use the standard headers from the standard locations */ + +#include +#include +#include +#if defined (ACPI_APPLICATION) || defined(ACPI_LIBRARY) +#include +#include +#include +#include +#include +#endif + +#endif /* ACPI_USE_STANDARD_HEADERS */ + +#ifdef ACPI_APPLICATION +#define ACPI_FILE FILE * +#define ACPI_FILE_OUT stdout +#define ACPI_FILE_ERR stderr +#else +#define ACPI_FILE void * +#define ACPI_FILE_OUT NULL +#define ACPI_FILE_ERR NULL +#endif /* ACPI_APPLICATION */ + +#ifndef ACPI_INIT_FUNCTION +#define ACPI_INIT_FUNCTION +#endif + +#endif /* __ACENV_H__ */ diff --git a/ports/acpica/include/platform/acenvex.h b/ports/acpica/include/platform/acenvex.h new file mode 100644 index 0000000..65573d7 --- /dev/null +++ b/ports/acpica/include/platform/acenvex.h @@ -0,0 +1,190 @@ +/****************************************************************************** + * + * Name: acenvex.h - Extra host and compiler configuration + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACENVEX_H__ +#define __ACENVEX_H__ + +/*! [Begin] no source code translation */ + +/****************************************************************************** + * + * Extra host configuration files. All ACPICA headers are included before + * including these files. + * + *****************************************************************************/ + +#if defined(_LINUX) || defined(__linux__) +#include "aclinuxex.h" + +#elif defined(__DragonFly__) +#include "acdragonflyex.h" + +/* + * EFI applications can be built with -nostdlib, in this case, it must be + * included after including all other host environmental definitions, in + * order to override the definitions. + */ +#elif defined(_AED_EFI) || defined(_GNU_EFI) || defined(_EDK2_EFI) +#include "acefiex.h" + +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#include "acgccex.h" + +#elif defined(_MSC_VER) +#include "acmsvcex.h" + +#endif + +/*! [End] no source code translation !*/ + +#endif /* __ACENVEX_H__ */ diff --git a/ports/acpica/include/platform/acessence.h b/ports/acpica/include/platform/acessence.h new file mode 100644 index 0000000..56a15c0 --- /dev/null +++ b/ports/acpica/include/platform/acessence.h @@ -0,0 +1,11 @@ +#ifndef _included_acessence +#define _included_acessence + +void ProcessorFlushCodeCache(); + +#define ACPI_MACHINE_WIDTH (64) +#define ACPI_CACHE_T ACPI_MEMORY_LIST +#define ACPI_USE_LOCAL_CACHE 1 +#define ACPI_FLUSH_CPU_CACHE() ProcessorFlushCodeCache() + +#endif diff --git a/ports/acpica/include/platform/acgcc.h b/ports/acpica/include/platform/acgcc.h new file mode 100644 index 0000000..7de4a66 --- /dev/null +++ b/ports/acpica/include/platform/acgcc.h @@ -0,0 +1,199 @@ +/****************************************************************************** + * + * Name: acgcc.h - GCC specific defines, etc. + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACGCC_H__ +#define __ACGCC_H__ + +/* + * Use compiler specific is a good practice for even when + * -nostdinc is specified (i.e., ACPI_USE_STANDARD_HEADERS undefined. + */ +#ifndef va_arg +#ifdef ACPI_USE_BUILTIN_STDARG +typedef __builtin_va_list va_list; +#define va_start(v, l) __builtin_va_start(v, l) +#define va_end(v) __builtin_va_end(v) +#define va_arg(v, l) __builtin_va_arg(v, l) +#define va_copy(d, s) __builtin_va_copy(d, s) +#else +#include +#endif +#endif + +#define ACPI_INLINE __inline__ + +/* Function name is used for debug output. Non-ANSI, compiler-dependent */ + +#define ACPI_GET_FUNCTION_NAME __func__ + +/* + * This macro is used to tag functions as "printf-like" because + * some compilers (like GCC) can catch printf format string problems. + */ +#define ACPI_PRINTF_LIKE(c) __attribute__ ((__format__ (__printf__, c, c+1))) + +/* + * Some compilers complain about unused variables. Sometimes we don't want to + * use all the variables (for example, _AcpiModuleName). This allows us + * to tell the compiler warning in a per-variable manner that a variable + * is unused. + */ +#define ACPI_UNUSED_VAR __attribute__ ((unused)) + +/* GCC supports __VA_ARGS__ in macros */ + +#define COMPILER_VA_MACRO 1 + +/* GCC supports native multiply/shift on 32-bit platforms */ + +#define ACPI_USE_NATIVE_MATH64 + +#endif /* __ACGCC_H__ */ diff --git a/ports/acpica/include/platform/acgccex.h b/ports/acpica/include/platform/acgccex.h new file mode 100644 index 0000000..f262c72 --- /dev/null +++ b/ports/acpica/include/platform/acgccex.h @@ -0,0 +1,166 @@ +/****************************************************************************** + * + * Name: acgccex.h - Extra GCC specific defines, etc. + * + *****************************************************************************/ + +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999 - 2018, Intel Corp. + * All rights reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + ***************************************************************************** + * + * Alternatively, you may choose to be licensed under the terms of the + * following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, you may choose to be licensed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + *****************************************************************************/ + +#ifndef __ACGCCEX_H__ +#define __ACGCCEX_H__ + +/* + * Some versions of gcc implement strchr() with a buggy macro. So, + * undef it here. Prevents error messages of this form (usually from the + * file getopt.c): + * + * error: logical '&&' with non-zero constant will always evaluate as true + */ +#ifdef strchr +#undef strchr +#endif + +#endif /* __ACGCCEX_H__ */ diff --git a/ports/acpica/libacpica.a b/ports/acpica/libacpica.a new file mode 100644 index 0000000000000000000000000000000000000000..1fb5b219db92cba8d3e4926cefd78d717876ba08 GIT binary patch literal 10059998 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%0D2&qB-RAb@h8A^CDycStF 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≦_R*e;aQa(J$q*Eo;r>G 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
eklHIOIa(5^E}c59Qb#^J*R`&;##Xsa=~KI# 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�+bK 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�DA$gcZmp5)5h4A#PMxWus+Q7FhH={Hw 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

74QZ>ZWPhA$V|wcs>Vev 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≶x2-5#^~9c1%vo&P8ZI^P)-W@#b{ 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ÐZ+NAUAt!L+N-W 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

j88{7IS^JxidwVJs;?d6$!i; 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 IQ{wB8uYM%{H}tn| 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 z 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 z zKh`0Y9_Od|mnj4s@2880Td>$qj~-#c34VGq?R-hL6aDna(H1Q6(Dr+{%f>{nYX% z8pQVZ0tUtn2E=N-WP%>qkS06CD`EgNL!z2Qq}WVBXxv%i2E1gso=TFE5A%XYsm?@# zYZmGbuV56Fm`Lz@4$U;Ugf0sBKO*@hhi3o!wIJ{A?`Frh^s=c*X#E8CTvv z3>YRU&l<3cfag+M4W>OT0neM1B?4YB;1mHbrpA#2EZ>2Yisbz(HJQLFPtZ$6{0*q} z@^kBX|K6F6JuHy4SB!XLfL=9esmm%YoiAg`5NQz7gyapbt#iDxeQd z+KoUTnNscm`q-fRfIcbWFM6#3`ZTqO+;J_?XQ}H6tqaIeIXI!iH2TRA6oaM1H2Fmg zfR6T4yc|b|ndh5z7e5>fM$ak_IFffyx zL~OZVz>aRhSnPDUTfj=cs3+wNzi4}Qz(j(zNhDsR|6#2Q{Nz$f5)Ci%Q}0j`(8Yey z_ml(;FA<%f;iZDMl>06dB&*ig<)UwYsbWyh{C6O3TH;L7r^#jSfA5rkvPyFO@x#~ajQ@m$|>NlWA;O;N{pwiwKkA3AQ`5Ocx z_BWy@(6@p-N&8ML;tTp-P!whU;0McXnaS98L2@j01j)HW5Zi}D=|PnQUH|W3V*)b(!41Yr?eO7U1rt%?@#oe~rO5>3scrw;^N6HVEBz&WezZgd81#pd%qiM$Pp6=ViI!11cI1zgB0I~rf(9bMAIJ~a^HE<*oBa+B?$QCSH^&q zv92J=uS5|8&JRgO$dL>Pk`Zzw1A=4=f+8+58Oi7AMl)TS?YcbFzbFX!rBmWPi-Q7w zBU2)A2K{g5)vM^j%&QCdic;7yCG*LFnS>k3ldFqhnSVMt>_j>bUHn2@6}5H!9h)D{=RVWoykLTzzrP{frNl3pyl z=yiDz@avZ-QqXy1Y*kQDK=WDj#URD88+|cI?L)>8aBYy{m@DAAAjREI!1Y0j`^3P? zd5w|S4MB=SrKH>#q}UAw+!Um^Z3O%^NS(k>xQRfw1i?x|#$bi92ZCfh2_)(~6bxYi z^l&hQ=W`f89tj5DLrHMLHG+63-I4B)k{=5K257?PgXHVEl3$Vzgp&U)C@QtSEO8F? zydvht$n>fptleJ|WXGvDf`H$68{=wBrHR-(L2{cNtpe*qmHr2nLfH4IUFbg3E{WL3 zLBW}HpV;eD=~DteliLJr2+SdvfX^jF2>3!$1{QZvyL>5uWnfJmfv8wx+n#Kkr4YqVXg+Y6apiqahWzS9vuzdv65BBj)^9D zc#-=$Ly{44Bm;tEgdE8TIg$}#{U8}3M>0Z=WQ2@lQhS%2UXB;k9W|rp1VQtoW_C~} zXn~+9u=t{Au#l{4Y`r$NB$_NKv>Kfp(uk0w5fC&YV%;}?)&!WL#y3go5AT}$Oye(JCVX+~fMd%}?93C6ONznO-*kE>K=zOH0ElIvc z$|Ld}6$=>fLOha;znq!-XD0o_q^;v|FNl+5 z#*4k87`v3kck+^# z(5ZmbmkQNK$f*wq^$~LFLmKKUlKt^MQs&NHigU`EBTmOF`>ekMV*ht(N2CyLm}I?vQ%+2x&>k z(Gmz+5^}Ue8noOqq~*AfFN_yyq1Rr5px54B>K@V=dhH{=fQe3gKQFkMOfV=BFZW`n zlEh{f7(d8M4<>yDO!0=BP-Pl$e0+kJqnI8q%OzNl3L)L1@5? zpaQ9HlBkB}oE%cSEYt<2dUTk_slkRY+764iddUxS z+O~(ZCFE!e1Z@d9+9D0w9^=KhNa#R_ty?GyQ9c$kBQy@e& zkTirGX@DRNAx9ddL7GJ&X$To_PW6JbdL&!nCHdS?BwHDhjF2N45F{hyNQN{>c6P`^ z&k1?xxgn_uIZ~hJrEcQR2j9BD3x<(oy@-koi(l*|i-%h|FAd2_$dMBWauRanL>lD0 zEF=pd@vS&l7{9^`*4f&}#P9Wze1jx^MMzubpF41|6Rgnmqj z11kCeA!o=1!jKD8C~^{F$%ckdO3(um4Xmvn@`7o@Ve1L;HFN?`hs2Wev5>Ze9BqN1 zEg?r+q(R%qL)rpCTOepl$kCRNqb(4$1%kGO9BqN1?GrN06^TYqLU%z=NhHMJ^|a)~ zesBC4FW8sb-T2`6_&>cQACb#_&xh_W(~3({bh7eaOcf?a@M7ebC*2sw5E zf?a@M7ebC*fMAz(Axpd{$PRZec|i?%0d#-GOY#!|qWfzh-3d9m13`B}j_yc;9e>R8mVD-N(c~_LBUZgy{ZhNOwYx z?m*C;kfS@&p!;Vb-GQJx5OgQx=uXJd9SFJuL3cuq?m*Ce19V5E{lW{TP}>@9C&Yj7 zlKi-bX!}!0TSAVuK+u+uqb<^)?a!eJ*e^03VFLE6XEu5v^>3azeZfrRcdv*?f9sJO zy(QvtPx9k5qS26$MuZ%VfS?f}Mo?QtHKX1Aj=!i zDva0o$y;-B9ukt1kRvA$@#AbG_QRzTdfi)I3xw`5$wxA2-+e{M=&s z?$9uXa$ofJB1-Og&0Cby@OAThXu`kYT)*jYD+vFVdFWHF-*&Fwajrjb?)}iY{>Zuh z%(?#3L611P|rJoQu6t!-aBoU7!rqh>8owx z^ENSx#I%=w*d}hUi2)Lad+9fAVqzR>-U5juy!20;_#^3CNQr-n#I~kCBG4{gnmGX@ z1>|#wt{~PZaB?JeKrY89FU<*(ogS9vhbaY2_B@`W2q<$wxo7%J{^3;rb+*(&k=TW~ zf@u4M6Y}ShRi3svdPQRQ=5kE&(wrcr*SRe;+w=G-IZ2^MvH8h90gY}8(R10a(Fs*P z?{^O$57qi3=P_P-d*bA_4!8797c6$c37&T#JuH*Y&lw$O3$&f%M9-U-D{zVDT}6sX z=_g4S$ma)%{$_8pZG5ul{e`%Eep=`Sow(FXKSJC9vSK9RKK`9J$MxlNU2q=RBUklB zZpy_jSS5;?7Mi%1@sM%!XIrkS*A;1|n*O?ih1Kz$I*rHm3SmJkFE>(M(^rg z%=e-`E+4Wyv5$C<1M{D)Njm;`S1jB+B0taXtuYUl`{y1$#H2hri2cexx3bW{#|QHL z{<+ON8TbndpfxUI{%}b-aEc4gb-|xq@K+by>w+g-@S+Realr-x5!xA)Pw$sK+7iR{ z1g{6=M?9}5*XvHgo=&dbr4H!xnctJG@A+=ZmrhFlb@XLoZ%Z%Lw}0=T?+$&p^!nu` zsnz_Uk>0)cCyKrbQb3pV{g7=ULEpW5@6&s)-sS!27`-UMf8tto!w-b(j-j<6^ zGQ)Yc%#}}~G0_j@8%epi{w7DPM}nur^c+qgN%k9T*JKVZ9kx1$~f%4i|f zcDZ^W$@WkzC&>;W(Wv4bLpg{T2m%Hjw5{iw?oZqs3SD7*>e+)wc?WD~8;8xrtvF%8 z#S@KvhQu7%Ir~mqn0?clqS2 z=F-EFeG^@=^xD`Se~$GkPi&p&9~?C^mI#Ixa%wEdpBd}>KrEKHDoEb{K+N0E_sLdM z>lT(~+q>qHdD@$5=QJ(MHq>V4(@(R=&%79xrLOI6ZE9yO>?+IFHRQ;uwzjTmZ4KFK zW6sFT($0pi$h7X3D$11Y=!{fl=Qnklx=g{9B^9Na+VW|WDk7D29VFP)Y}eej29b9` zUGu!!&aS$ytcX#Ssh&QiruLBHDbq96xf-XmEy#A1WfztDmqA}*1G1>*^N`k?9GtJ_*QLe zTUXPZMYVJ5S{s_FI8$iN+zw+*$LLj=$>kN(dNjdp=4X1O{G26B5#pJJpX*IRes7WHFO`Yu9liE7yiafV|Ze&VZTYF7Y zOSY}Mi0ZVn!7vak_&MLc!d0^C38e3w`a#rq^>+KGO?*^O18C;9GnX#G3Vyw zDXlK8F<=sVeTJRBq^+BN0in9S$=IT!yS*!tX>FL&*4RW*VG930?mC%;T_tt(bF-0(Y)g4VWKvUewv{WXY-**BG&|d2Jk3axb1bUD zsR8}9G;>IKNro&ezs59^HFk1Mo;am8a|pM1S@E>eDVZu$**)IASiSBHq+C&bHQsi3=)W#G?D7me>qdp6-p~sQQ zI_+fPj$B}3N3@~C#P`}}HbIRE^B=)JTgJ{d4vfY_{H^ZFw%4>xqaXI- zXcr;3>8KOUC6rbn=H&$Bg6cA@tVo2*MAws2b2cK~E4d@lbDw0v4({s|IoNl^`_LgL zQly&K-qu7t${L`grJ9;0rh0;P9rbhfTR48tk$di5 zII^*DWCaz_+10RLJ(V|d&YX^HmPQQm2`iVVY#Na4(?macmK%VoX4FopC^;zP;1j1$ znuN~4zM2bJ<*iL!O?3aUq(Mn{2ai_L?<3ZmIOLU2qi#K^xFpllt6G}Hkaau=b62K- zTA!r>fTEk>LZc21(>#V$)^&Em;8u?kGsTHiH#N4_ku7a#3Veoy5UehXS$|uc|PEJn6 zgF=UR5sHxlrjsqk?kTs)NWejHTJh9ODUbG@*$(NV6kofj2no*;BBxYLES_TPvEfPV zC&Q*0{|wR2CDoD%YwJyKqX6I5o{dbbYn>dG{v(ZjDCie}8E zU}Fb8QJ;G+cYf|3r7U1>yntV^9e3L`QbG>b-Lc5Xk6vs&qo#u#DmNIEG?VXeg`$G> zPK1q~@eqyGNeVS}v@EFW$j+#nM?Lru(yOh5ny7wWU1OGpNb%BonG#|~CcYEtZtWy@ z%r+F(&#jXQ6DDA0tR+q0!#s+W%;nz>?3o@>$SKXvA+K#PeJs~7MjI)H6R0UWY1Y-1 zt(Y^1f_td1%Iv6iQh8=dDM!F;2TixkP1ZCbCt@!&<}r;%R?dQXPOfg?;_j}tDwws+8kvZK10 z3`_%8M+2L_r&n9aDTq`@dPX`^JD!UY zEKj#(Nsy_c=?K;wW`OMR7#b4osAjBS?&t|E#nn?n0kw{LUv7e3TDPc}Mwpg}#K1a| zilYYkI(c*l&ml(7ZEMMnZmpZw*)^IwEe++PdGg#jnlsnejh;hJ+|$cxBI)>(Oc2B& zcoG|_ZfWAiAur*XNJkfqs}-H?EfN~57q!;Qk|1X>nnjTvOKE*nkM$11tPOu+9$AV_ zES`1{yxta2I=iB~tG&Am!P`u~51NeT!X($}MXvtsJo}(ElFW@FGicpqRIo+s*&rH} zCyLZB%;3;j+$lY_$3DCilu?N1Pc-2$0}Xl!hi8tZwu;KmNlmQ{G6cAyA*S1GG8d95 zjIl>e@x&=K)MkoHX~-_+pEoV-?pg!~FQazop!H^32YNe?=LpGF9E;54*0dSRLf$3Q zOf!NQ2g5ioi)adEh>4`6@l|OI)lMYiuA!-w$;-3;TubH*SWl~S>c;RRGb?jvdQK%% zr`Kd=ig$O_;b&S)+M2stXxUE1Wfo?5n&yO;P)}%pTcKiiVOqC{x*i$7u9-&+39;n3 zJSlcu36uY+nVPbSQdxmf$0vcqs+y*2qy=|Y?K03jQI?-F86hCZWA*M6o7y_7vd47OJW=ZA zmYK-2K$*}O6?!HWtuz8PL}=QI=?M+vvf?!Py3B&zZc@Oya2lD1n8xQhPe{@7X{DK& z(37RZ`cTFQT6EJuOqM};X1)On5LHUnurpDyqx3l*|4n0>D$6Tsi%Uys%cPpuk-624 zk(xUdnNen-xmtL2z%!EWPFYKcz|z4=Gu0(D@wClloKegL*ER7X)0{=~Kyr+x)wBRK zk)XV_vy0a%GLmv9p+QL;m18vp-CFbe!XaB>6>TK%Zl~FMR@s})VSA9(Ml4AlUCO^? zEN03Xw4|+l5id$vv1l>M0aRjy3~u5SCcaB^^8`+o zq&%&g%_F?+Y|V>AsD?;+OM7!uJ&$EP=i|_0N-(211tB#&^9D?F-6ErinQG^Rn%vRW z-7dk|_&l%IL*0H3FD2}}I2VRyR!y(0k=C>3vfFmKqRHWDZdOOT8rCdE?Vjk~GifNU zKc>5h7HqkD&HN3q1CC&Vn&Y>$zA-Ztx~;OKjmK45!SEE_tazt3wYK#v$jPK#P6MKM zQKxZ63z^=};VmXwS=6<3(!_{2dyLN2CFQvYVHM`Tb|yli&|nWg#7eUk{uuH^GtIQS zI=PbASIl5x)-)CEX8P&uDydbR=?KBW`oa)Z#=KNJ=|OsolRye#o7)L zZ@TGxv=2zV=8(FMCfO0m#a84pCdY75Suun5xF`r!Os}GyC|f6H&(6|I=5W(1|F{*~ zA}dyBB-EoQx$17aQn%rKC=Bk-EX~aJWU>ffl;Fbw3tOS>oxEbSL4|8Fi}-1@0>yZ@ z$@P&@w4`EMO;yDdOtVTlX_FBf=cp~WG&7-@Cnpj=>L|UoV?ix9Rn9QwjpX=QOfsry zKGsY}4{fcPj*d3kJC|)Cxjkpf&Zh91ep)B()6VOr4g8${qcKX0Ylg( zX2YNSfFnO#nTxNPUEV44H?xZnazyf@gNi3-WEn5MW5(Ndn)El(U9^$IeU>K*PA_HO zT*x~~;+t40Qk*nv4XcvdK|(`>49cByAOQ37Lamy%hRnW9ZU*Xji&@m?4BQj7M0H-@ zjOS?=nhicI1)0UT7)*vf)X66fon56y5bo@qH1}tP%@}CIrKrtD5yMR?pP4Deeycg} zFb-g&jVQ(Af*C<&hYC$%GPZCx5VPgtxa?P$!mRD=Si_?mpUcqH&cwq%dP7ZFrfMpA z_@NckoQ{bD3mQ|hk)FLW+zs{VP>GKM%4tGp0uaS2N5Ns=GJ|#P&yo1mC=1y~P z&zekA_2#zL#&F!@Va}X0nCz~(bcd%+sSfI}9bHXi`ijo#Y)f4`#Zg*vaX*&1x-|%Q zBHr+&5nbG5Vs|GuW+%I*S)lV8AasOf))=sTaYxHUTE>Wn%c%rUg3M-zX%||`H^?4_ z?CfFqmF)qnFNJH#wzPFDlFei*of*?U`9?1|Y?&S#B_wbKTV#7maf3^6pV zGhKz|E7M!UVFZ&EyPm;*a&GQI=Bwvf4!28XRi-pEseBqarx_uzO>I_2R!r$NW(>!` z73!DVtsBsKa8bBQPj|CpY_7=mtT~SeS-)m>aWg4GGdyfm(RM)A;z!x=}?3cPUd_o9rF5 zI0u%XsLyxQNhS$d^UZk#t4_iO zu4Y%@){|YQ2<`QDw`2BXgAXOUGSZxx*2S^}boqAj1DbQwUSH3Oq5(zGfGP)xPUkHt zE-A~%T!SWubSOYOcN{4&>YCP?*kv|9%xGfEFm-mB9oV@Nlx&=`(+-+JcG8-rjW$5} zxZ0Xq_FT;g1oaZ4-4hn^Us<2@%zw}Zt>M#E=>drDJw4hrMm?Xop?eygXV4)}ZF#B8 z&5fRJc4`IQ9d(5;8+N9bnE8gu&3$98IpeZt&*&OuO^tKaC{m0hjy>q?m?`y4yG!cY zdF!8dmdrxfR3cs`n)6|M?yjjc8JR1)UdE%vSd)tRXq0s{^Bi+n(K95?Zt7CU<*wSZ zY2OAX6ghF{c6T+jEoj9Kd8ER03^N!`tf-ihDV|0T7Ua&^$TelAAagR7f(MJ~)w#ZI zr{$)@SDWZ9Me?$Yh8k)`UWSoP_~x3jD%$+PfCEP~T||?IF|n%3(0Ng28XvXJ&DPKB>~6{Fg42Oq2+tjRyEf$#*}Zwn zO`6IuCLK%6V;;Mpm|**v%wRbyg{{u4tUS$#`p01vG0=<@J>z)I>?(Syfp&0mftG73 zrqzWk5%iGT6Uci;Tk~j#_}{!nclJE7yk;{8+?l$L=0&+!BeNW{v5_+?wJ=2$b688= zfKDG>Lv_u~cC^$r*Ye3=&(lA-sKEYF-95XD&H&xD$n;8@`PwOo+isOr6(zJkQc)$9 z$X26O1bvYg71(+)ZPBxolm#@;l;|lLuM`XZc;B#u5hn3^VyPJr%-fhtzRU*Llt#$ zxXtF)`BG_zStrz*k;{obbm&WiGp#vf)Z>YS3?QajJK67XMPASvlgk3nJh~$lxN9y~ z%?@+OLKc-x1v5=ILww0@R&cvniAJswuQ-&QLaa|4Xw>m?tzt~U@u=sqg&Z}JC;xS! zDFIIs!VKAm7EPwV<@P&KIkhBj9n2sZ=vdD@wt*>9&&-A$r?PBTb$JQRx9z4JKW~Da z0Xvy0{=?Q0OJW8T93^o{&PKAhV6F?p38%Ny?6c>ADC-bC%jEjbo@KHa%mx;>AuxhU zuduB|WQz5;_vaWYj)bVU6Ut zj>KBysvUG3J2n)7u>+5tLJ5ziUYW*Q5siB7x%1*W`Y}3d9XsRT5Mq`Gm7Qi3#%eya zLt~`E!2_P0p^0NNFPP}D6E2Y#;dKor=F+oF{FoHarQnuE3inw&%n!_+bf_fJfcqpX z8j&p`(~;1?URM^L7QspnvPX=^L}qj}Q8roT(vr;?0E{#7xgGp3=czJ9>=}7r zRx>w{BSvd3KFWsQNpv(ywaJuNx-dc&y~49(@4{j9$VXb9_n>zP$;UgkD&j3ndOPIj z(F5i5JS)-sk3Z&d(Bi(yQ~i=Y@gV6vMR_FUkN?xH^eZIaZ2V&iJzPnTiEio>Z1OEm zoS(kivx!e&-i8)V;O|O#4xi^F2NClZ1rdH|_K%++LSKW} z#3zUpY=Y(T>1k~B*`9De$3FHE+~LG0RbGbt?zTU+e^W2*+Mn}L!vY&n;4 zho_Hi7t5E^m!0{;{hL3QUs7mbWQzRB@$&YSNaSF}4~BrV6@LPZ(XRNb(Kxo2^5zZB)X zqxdzD^E<`Qr0>4*=Xd#&v-6eEcMEQC`dIsn!@IW!D*gzRGeYt8f!x2B;upZL4p;mj z48rFt-a~t>Qv3i6nfBe~R?al+{D#VZDuP5DPHgk9hCHJb{|q|o!HPfCd>@OxeWm!< z5o~T#{2}nGHxxg7h}dTb*vZOsV3F`uif_e0zew??!TzTy{@>s)RD97eDgPS9Ukdr} zRJ`PlL>^ImG3@!g;@?Jly{Y)q21!YuDE?*W^1U1;Ao6#p^m z{Y3F+ATIo%_#+S>q8PVryUgB6^zEnk%h6tiis#oO@@JIdkB7edD*k1}&jS@d2=<(* z_gj@_^L4EoDj#h(iMyrTG5 z&@LY+ekJ<#w~F7{PL%RQKigghL;k*spMddYN5$U*`wv$<|4u!B#w&g%^qr*mcJ!|r z#rGd7<;+(6Kf$*vej4KB35vfD_BliG-`lvS_;Vr8jf&5QAKs(*NTJC2gyNrreg38R zZO|XrE8h0^FBNa&iFtD{Rb|_2XV_s2%pWcPE#l??#TUaqMT)-{{c(529|HL&D*hx< zH&UtiF7&%275^mSMYH1ffWIB9_$0=c<%;h{zq?5B^}yFE{yOyczbQTs{2ImY0ll7A zd=VBmZ!3Ny{O1eB_b!xzeo;J{J(7T3ZF@b9@osCykA^%uDSj{5XN=-w!22uSj;B); zUxhexsN(JTI9Ks@Je{xj57CaNDBk+*d5Yf;>#Q3Tzdzapc!p}ce{QnRizgPUXkjLYK>0`?;z#?0If~tp~hWIRR1?u5nMSl7DtRDUl z_>a8Bw1qC_Q*2^@|>gcFT?ul zO2tn_`^t}d_mp!Z;_AIBe=FMM3B}(A{G#I5L*I84e;mr+p!m1YE^6^;>Vy~^Syq@e=didlT`lSc9!<7R(x;7k)srUnq-f(D&CIM z$146wjQgi4ert?t7bw0Q_PJW|hr$lGE4~_j^?>5f!g%(K;txkVzN+|U`1yy5x9jom z6mR2@q4?_{&t8hZ3Ajx0ofu~iQTzoM2kRAYKLq2##ftv{es!(lpTNB6PQ_b)cv$iGK(A*NzXW={ruck}Yac59 zCglHC@kb$UZ&dt;;O+d&_S5&^2iw7aEq~B3u}_iWZ^FE2H^u*o{xw1I&7PDqRq{4+~`pJp1>z6 zeqY3&GZcRj+VN7we~$^=^@^VbKm5DmZTwlI_yG2QPVuu4C*M%~8JM4ZtoW}GAM85D z+W7&L<6*vR`E$|V`zpQ!_S`}7{V>1UL-AuVPnf9q>5#KR@ed*{9j^E~l;5QI%g`U^ zEBM% zmyTEbT+Dw?SNz`apGy=!4*FlO_}$T8?oxa+2y?|B$kZF}vG zat15@7_|3j#ovOsx}V~oL!3WI@pinMrT8T*0DT%2e?9c=R{Y+GZ}N8Lo^_Lb1mQfD z-^09amEwnBzIvPD?Y#3r#oqxt{zLKKVBYnb;!j7tA1VG8tb=}4{L6?x3HX<7mvayw zwpRR?*q7c(@phiWzoy0?Th6l>Uk*^b^|z^t?*l(MO!2mTn-t%P{m`G+CSZ&19o|IdoI^VmG}16#h0Gg~XZ753ao@xQ~* zcU8PyuO6WIf1=+`RlJq+FvVLrn-p*5T%dR>=Q71xInP(T-5>ai;v=xsf!lH#r1&Q`p&+m(v9cDq^e)^4j6Z`Wl{D}Dsx z&&!Ix6Yc)K;`f7I-za`6###9V-JWseJ;eFGSdZFvwEH$YDE=|bGxt^e5y0h&zX;>$ z48>bJXBBVl+^KkL=aUp~?R>W4t(~t_yd6((QG6@n>T1Og27X%cR{ob2Z{>eq@mBtC z6mR9{w>7+YQ>+Aes`4O565`ds(5RM;}vi1 zV8>@GzqP|9D!;YE^@_K4xJ&UHF~5CG@y9{_b&9_qe)x{!?Y{08ihrljgons4ir)j} zBrxt-y{sL!QM|RoP{mt2?5cQchY5-Tlx3Ky!Q;1{|e}DpU1TMZ5+Nq<-Z2;{4T{$ zLpwgI`2WKG&nx~v*q8W3@peDqzlyi}8@`<{la6dMyFZ*%d==&c0~LQg=3OHcZ`*w@ z#ZN=KOjdj?`ulXn{}Xy0t@z&yNm%+UQ2cQiXO}5{Ao|z&im%6n`)b8oKfGP>-G!#w z$U}<%0p&caczf>ihT<=mVj`a_{zc4xeo?%A4lRLrY5i(F{AXLmw@0Odp^85qf{ZQ{sijP6Bdlhf}?VpMt2K&6L z_;=BdzEHe9NBdRrHeU6D{jB}1pKPo6@mR0zrg$r7vEpr9s!+U*dq*gK4*F5E;@bmh z_eF}|9pnCKinsb+sCcXIHHx?T-l=%2?{kW``o5`ntM8|ZxBC95c&l#=>sM?4ap>>; z6>ra>1}ok^A2M3;)<5@G{4)5-L5eTHx^l2_FSrXYtO3`Z|!-j z;;lU&P`tJ0Gm5wNd{y!Gy!b=K--3Aco#LN>zj;`HSbb0D3;Gl&emL57pyIzo{t=4* z4E=X6#b1r_XtLrz2R~i$?;@_+=Si)cmt)*NM&&;i>*Ny^-wWqhXDa@)Jkj@Z#Seyk zZc=<3?0B!@tI$uMRQy*%r2Ll@{|e&ge-v-eQNB|Ac z#lHZ&v*PW!@E(e9g*|H&zaHz*I>oQSe4tD5&tM*Zvf`J){(n~d-SD?t6mOr$y5=V{+6z7gX>1oIke=PwaYwpRRc@c$u-AB}RyDE>$I|Ne^S zSB~>%isJ7@{=*c14E$}b;+MgH<}2RD&r=j{Bs(3q3`$6$9pr6|32yA;<`L~3fEWaA_mVt`D4CUJIquBgb&T%Tg zm9tdwR?dSJZ{<8v@m9`xif@9yEmXX<|EY>U73+ud6i+X%F`ugxZ}qxG@m86>s&j z=cu+`tJk3_ztwB5;;mlu6>rCpQxv}paq>LH+xf%Qiccf|ZHliz+*);|AGytU73 zinsRpNb%M_-z(nQhu%YFKGtql{-omD&>sgW-r8Xo#ap}Wt@vNDzg@2QsThwAQT#T( z$k(8FYo89qTl*|gytU6+insQ;Lh;rPS39A%;Gy3~ciuYlMR>j+K z?RdpoKRjLWb{%z<;vd1h^cKb60DIl9cx(T)insQEMe)}DA1L11|69dd`$sVUvh8Be zbNedZ>bs-j?Y`o0#ap|LSG-*>P0~E#&veD#i}td5TRCsX{@Wsz-^z2E;%8ufc%kC0 zov%^6t?y37TRT6Zcx&h96mRYPrs8KIZhWfvDEjY@inskDhVk6m!9EAuU-7oR1}lCU z>^xfWx#vj~Z{y@ainq@t%u>94o^Y<>Gnl7!D}D_0T&j56F6S!Vw#!wDx9xJP;%&Rw z?=4xo*>-tG<+sn_ysCKXSN1t&Th3dMKfpe*<$p(i-%{~buRkf?>NQgFRF=K3^!_+UHlr zTl@4H9&RsdpKTRy?UPcx)ps|=TYV=gz5wyLLh;{Yy?=z_tzPpKZ}nQFc&pcGinn@Q zsCcW_HHzOgA$GY_@uk>Lenjzk*av@3@pc?}Q}OmZ;#0*xwT0CCqvGv)Gd3dJUN^yR z{T1H}>yE*Se--QNF^Vt2zSDk+KN|h~AjR8unWcE!E{%$}?b5Ay+b&BLZ`+je`H?R%!;ZTnuXc-y`=Dc-j4 zy^6Q(`=sJ+`@W=j+rIx%ylvmF6mQ%2cg5TG?LAWL#AfVG|7`nir+C}GJ1gF{;~t8? z4C8*O;-Bjy^&PDEH5eCa6>rjlNz_Ig|Kw!Jne-nQ4zinr~RH)_-EwYB1Hd+nt76y~2}6@MGz=LE%{iSvl5ivLTV z$Z?qBZF@B--nQ2Q#oP8;rg+<4=PTZ}*VT%*?RC52ZF@bWc-vmjD&Dr&>x#GS^|9h@ zd;M4Ow!MPUn{KZy6>rz9>^{{idP zOBHYJe52x@!2IfN#ed0z34I<{ytVU-inn%tSMk=)Unt(%`B%kTJNFtZ_1kgHKA*F# z;^$(&D5dz4UUL6#inq^o7c1V{ze4d%*k3zB@z-L#vq)p@`2HD*kA+<28!6 zcD_^b*3OS8-rD&&#albSsd#JWPZj?x_OE_a{5_CAwrjXuEsg+t$1snrHZ%qIal$v|6Zkdd%km<;yvtRKcM(R ztb?9Wyw&Sf#aq2TRJ_&eJH=bQyxqd>W%Vjhyp0co6kmezXoTW#LmXbB`0l<^-x-Sk z4t{ls;y*?Iy;kujz#r~V{6k48|3Sq+yruAK6~8y;rT)9O@Z@-r^Oz}IQz4lQ25zu#{;+Lbn zY8C%03r3%2#b1p1(jvvb1AjhM@n>u;<(#khTM@5TDgJrvo8PMV>3yV}b&CJU6aH<* zAB=sQ&lDepAO5KLeKC$i_muK%y9~pA-Ij{q0Dl;y_`je(j!?Wkj~}o2OVCd@n|x%uZ5e-Zk{8;ZBT zxA?K*Uqt-*Uh%CMN4)W2yZr<9?5p_c(07F5*TSF2E8d=?XB59B>|CXI`@BW1;?INp z$1DC&#M>2$-xlkUixh947yrBB^I^|*ir*gdz_%6Oj{Kh~{^+4%pdS@~8{$cHudtoz z$MDT(OT{lhe;K6sB<2Gn6#oJIs!Z`iP;ZUme?dGsO7Tx2{+y(E`@76%foJg$8jSoG ztNdN4_b*O<3{fWkYL&kk@~>6AU8lUN_=mSM#Y8?*{3nQGUw~&hx!$KFd*lbj7lIG= zmJ6G|7<^Lkv%wEk{7K-4EBP{|o*|#Yb6S`pi>&68s|Y?5;co z+-m^mjzku3hF_-mo6+x9Y=%Ei@%!0+w;BE_#eWNbyJj=|&5Az&dfmPm{yxQTjd{g` zo8g~S{6`p%p4kllFU7xz`1#6a_;(e5Cf0ZFZ-)O|@uLynzTOOvX}bA+2AI;cFT#ZR zISaSnyc}?NjWRFI9%WwYH)?igXJk}I zn*fvAz%RU(_aBau>$=&q`7OYsoMd`Oa%&^KLe1rx>5W7Y+(Pw5M&UK#$dsL1JBQw^ zoQ2f?|9=YUR(2_CM_bYaT-f)JSRU?YVT8jCH_?O~BNsE9@v&^)%p zJca?eiT5)2qD@}@Yd`zbg_eoa?0E4r{MP2z zc{v<+$Q_^5%3ro0w~@BrfyC)D`Ht!P^8b;@7^Rrbdj#dt9N)K$6{8Qf>jcpM zI`2&4HYI=Ab8?HlpXKK*IxYV(#OX5m&bv|n{wh(y2)o&)>R*WZt)FrI9RGFwi;3eh zt(`*%6HP*E4sO6`uj)8moEM( z(w%poN5kocI?A-p{NBIHxgzuZ(#79Lx?(Fc-*4E5a{Z(-P`zv*s%1i=Y~}P%R`y$w z%;BYLqBJ=*TI7s0!OFE_-SW&oDeLBi+rkL<>6v7af_tWO-k(;y_S1v|52%wKl9qBS8^`aeV@$QuI*N4e%mMWU}v9| z(|;p!O_{gusmQ3MncqB<<;=?bvNJO;v1JSH$=tU*^W)Q*pSa)+W%qIa8bOk-DY&OJ z_R{jqt4kLD80p$#$>LXQyP{8JevB~g-Vqffa}_*BCB3@b=Q@^Ve)7l{g86?lwXK|x zWC=-y*CJip_V|0*%1GH>nb!)IWj1cyxG8}T=fWgm!Qn;?at*>|XI5@B(BmGne#2M- z)GHz+EMHNwSGHn0Inng>1^4t@&fc@$+_Qp->8~2*6Ef8Ljwd$+=WOuK*;MTj)SmM* z({)#Ty*%>)74iYs^#AgmpGarZC5&xPq+2%O>~OI0oEqxOybGlJvhfx!t$5=)bG;!A zVSZvy>t^31vHsv?M1L)cR8tTdZ@sMS>C7iw8vR{|zw7z$)0wvnT)KE|r0YPsc`fzE zWZAMcUHe(^Fb6;7%iJ2I^7=^EHk56>N?nn8tL)6_>nLSS87x<};;Bcd@i+E3#m05y zD(oo*%Q&d7JmM|NPt6>THXeD?8eg*H{gnGt>ESnL8{bn8c#=eJl9c(X;?3%>B zLGY8ZKXhYaNryEa-E+#Cu7M_#36RDhN{&s4w$f<0iGnvo+0#U-{-%~q6h;>2v_B}! zqAq=6{9#AfkztGpdM4J%n4yh9DrkyZ$23cZeQrJ3=^M5a`JFLP_ZYe4{_-{Tk)@fh zJ^Jq_Z^O2mNwBvb+4atbxld<4XA^DM#YDm0Hm$a+-)8P!n)#f<#OJ{RoA;D8-H+Sb zMs@!`Dy7V4Qs%E|I6z}k*w-zbR}>gf%;g{khs+2$SB zqaqU~$aIS)LT09r&-QUYXF3Z8KVg6$HGhHx&(k9QhIBdq@ytw0sLu@*t0Z2CFVZcX z$Cvwkv(Y3eU!_mu6qKAWDQ4~F_wv$w#*r6|@#{&2OM2rFXb}V4cUClIs_9K{c#ZgHM~hg%Bvs2>K_b?< z(V&h%Y!&^N&ze0(7kx(3MU?WbCD51ISLvdFWx#FEM7Q9KQpR)9#5V*Ii680z%DMAZ_s%z?Ta2LWiNsj?-&AxU-@VBj{}WBjFnN!l|C1e?&G&IMaiYn$ zjPqTX%jd}Od9>g!Cd-ZVf8TW^>w~#$EHCu>E?V%6$@YTH_AO`Q`~`3Ge|7wyR`B;| z!A5gej3yJN@IiFZlg%|_L9Ac|C6fL&@2p(j$8&`nj>VT-6N%&KT1=mc6*QXyI&1;_ z>z&Gg1?Cq$6A3OtZrd}q#Wm)(oA@@?;32;4Gp@rJ0KWCtSh62=Ch^HzVy z6>G%srJqj-GG6jqB8^Rh$)4CsP?;yj5>zfIA?QFsa%d48BuLsRm?CH^$vM?C!VC~J z%`l6T3i zZQek(FHn1^*kc3@lbjtQ&MtyF1&tKcXqKQ; z1RW}9nIQS~uHaNb@{XC{G(ks5&gFuR6tqH6t)SDzW=9EHDM;Rs8k`}hLDJ3?G)K@` zQp#LGXA6=y*#zeZI$F}s6*N!Kd4ie+oiAm!2)aPh+5}xFc4-%MkvEWgSeKxSCFc@B zmq;lm3A$9$VO<6BO~xv2Sp_h?ei6yWHpn1=NMMCWnqhgKxcpM(QowFbcl+ zQXCTn{MSpFXlVj^@S_*->!nQCE)0J2k{{;!*#ErL<9xfwve7Hzq=cY|Z%RxG^86H6 zwv`~?Pi;d3IKn~To2kThk{0z%xZj>eF7Ce~L);I#==N>7vgl79N3j9s+VdQca6w-e z3~<3f7o=P;(gnM_U|$zZazTX)X1L%87u368t_xaS(CvBq<&-<#^SDt&(^K7)Gu)Ij zJ&z}5lI<)vVnH%aFr{;t)9n0R7$(e&33y;V1}mwvFDP1qn%tc)WlvG$m2i2 zIs+1t@}e0%k}?RzUJ4e|Jt*L-B)`>1vVD_s3i#GI4hr}##iN)l;J?AcbPtI~V?r$D zCC{Llzj2$`&R)?MeTWt`%#+!vptPV~f`$v)LeL1$xMFWXyLiS;^97CcioT;V#f4*| z1c}qeMhhyG-c%NE@e8IZfiFo|F9aDWfbn*-vjj#)74O`tuw(#ZUip ztW8-)_3UcFseby~95~HS&!#8+r0BE!^v~R)i3GQDBC(NL-b91g{$9YqxWRx}jhCEC zd*ae$hj>K{fM!TklZX_XDF}@_OWc5$EE}jKDfuujh|}$f1lKIo9bQ4Fp%&5qX3o8w zE(-WR%6At1*^aIvuLXH`C&!X&8;8!jx5#7=aGyA%fYk=b6f^IB6YFG(nfHK6$(L*o z8qi0;Lk9E}@UU^%0s)VtI9$lIG;fW`wxy&zYJmJ8VBTW}^p}*!4cJP+6Q+Qz1w3g| zwh{1@0o!h0+|nG$d%9>R4l$Cl)^yN90nZp94{+xF!+>Fu@~i>72zV|vYcQQh2zcJ4 zED`X60jCIfF%{7AdHD`((!77A`V&~?33{n$CqnDxr#JKdy)zqoSRiSy81cpcy=v0N z1HD$X8{Ibn=ylVj%Yfc6L8B7rO_Meg=&ho^6-FXQ0ljT<&INkMsmm%YoiAg`5NQz7gyapbt#iDxeQd+KoUTnNscm`q-fRfIcZYfa+TV z^l6G8Nm&c@S!x5Jbpbgl2PbrxMnBn3wv!IiXN%rR<~bt6!jIv()lvvBbF}kjT3H%8W#Bhzc1d@ zkc=(%lkDCi?TJzf&=S9hlM*D~hdKF9_5%iHl9Pxn_Y3$P9VVj0PM5m{tn`a|QqJ&; zYS{r33Dzc&;4T1bUEn9z5|a8a@>4r;K0z1zMSIcO0~%f;Izhur1#KxgFB2rI*4X8u zZ-1#`Q0!_y$>m7aYy1??N`bBwgqU@m%#C2S>!r}`<-QvP$(tx+HwxNO(rywokThdm zq#>{LgKQ4m><64;bS{hCNjBfgitzW4&F>_I{MOWH%C$OJ(TiyezetyyG0xa1RllBJL= zPl-rCPYZ(ZYyBdAzXXJTMuZr^txs$sF@>8p!EYE5d#&*Ut|gIZqhz6;^#jR!GEwl? zPyFN>LQ?%_ev0?3fHt5<;O;N{;6i&}Joc5J+=`r5?)ygc1o~Exyp%Ndomj+|oZkzI zqRb!s;5l1nGPYfioS!Rmhak2OiGnf-@hlpq0KyE3P#tHK8i4Xq!;V2Pn94-&7!o8u z%GEG5R0AQW1|ZZx$f*GcPM!)(But{dor7Sht#3kXWRSdq`mabfI#eGar#>LmN64uU z2=$E#%m4&87%R2Ihj$fJDD~|Ym{A=*ynA4xBYb!dDP=sQ9v1}PS*gdw_79S!w04nF zCWNFWPk`Zzw1A=4= zf+8+58Oi7A#+`I&w(Igx|Dqt+pE&WJ#X-Td1QLlC>3=h?{*W%ryt;s|D1{wUGM^ln zNw|?b8CxDCciP_C^zEO6in1&WN2E1V2$(=DuOE^Mm9=x%(~*X-vq`7zi2@ zax?~l#utU!;$k?gwDu*TwzxDX;>rt2FBV?(x;zNR5THmAiB!f`1qC%UpG98`QXIPl zToa_0kTC>Y8>Beq3b-yvakmq2eURclF|cx8V8j318#gLxwePPj%852bZVP0tAen0e@&1br``yH2dKUnS7Rzo z#NG*#hvh0=AFA{}s1(A!PwhfEO}ivw9|r~hA|&?uRQi;F&*U}%8v=6(Cg5`k5dyxD zl!3(^)Gl92U>R6bN8qa<#Ui2FZ-QVhA=s!in%sh-vDjf!bkL8K4`_095GV1>mw%#N zELz zgTh=5Y$*grO5-wZU_3e+>|`aYj2#nA^6(<}b%rD()#&7qMuZ%V zfS?f}M}*PK}ziSEcB8nxxGXv|P|p zf>sEcE9ms7*&}Wjv{LLMTj{Yg1TB=bGX)(lh|Y~KB%fF!=xjlL6=C{g@UdSbdjJ{f-V+xt)NSyDYpELf-aR4?Y|1TOwi4OE*Er* zpev#&Zs=R(6z>wcG{anfc*UQi!P(^5(kZWx20UX*B(9@urc+kN?u{m^2U|y3E$$YP zu6DnmgjDo^AauM31r3n2hXfT0dRUrxn4m|ZW`AOgpf%AVu5P@bM?=o=m>8st1mw6c zGCvUw?z7THV;@A5SLLMrNDL8?vOX4sg~=ykP9LRw77gameMaX2v01U?cezpyiw$vt z+2OGvoCKYZhz({(hR#O{+LGkEh4P4eN5ujLv{I*h@z;~Q*UGE%Nc_#1*#x%0^f?#E zwi{)uqujFXMu4;B%NBL~?I2F2nW6{8*C%=BRtk8}E&4s_&NhV$K+*5HMN2l>(Tl&I z;$u$P(TjiJZfnOs3hHd7h4CMfyhAKS|KwKslUu0(RQi)!sidINpJQxj*&mGm9OJGx z0Gj?9ykaXI6aQb5ccM+J#v`88nnT8nAta%Ta~ea2(qv{7_dF+%F*0>aPG5FnWp^ST zcqZVAC�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ʘd2+mmj`+3;kh+G8#CaXht?Yl0Vn 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%0DqigNqT3 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

)-G(SPO4%;VxAEOI(L7 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%0DCC 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%0Di?H9c$nZxYpWM~( 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

C@izk< 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

N?8sDf6MdA>DJbAwrpy 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
3ieHa-^@8Fb!FYLB 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 *xU?*YiN5w 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

bM3p%_xGIdA3ERv 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

E4q;Xk2Yt~H!KEaqN!82)=G{4v8d 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

cV$nsAwDRqq}ew86s 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

!4Jd!fUcXB|6=I#3{zj_y~N-u@AU>(dG9c| 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

vgw_zMCUgPW>~unF3DNIQyB89wqqK_% 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⁢NDwd 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

;3pM7koVuVE54cS`J&<<#joY|U9Wht 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