diff --git a/build/build-ram-iso.sh b/build/build-ram-iso.sh new file mode 100755 index 000000000..ab462e819 --- /dev/null +++ b/build/build-ram-iso.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Package the self-contained RAM-distro ISO: the normal Kernel.ZXE plus the OS +# image (RAMDistro.BIN, made in-OS by src/Misc/DoDistroRAM.ZC) as a second +# Limine module. ZealBooter hands the image address to the kernel, which mounts +# it as RAM drive B: and boots -- works from CD, dd'd USB, Ventoy, PXE. +# +# Usage: ./build-ram-iso.sh [Kernel.ZXE] +# RAMDistro.BIN : OS image from the VM (see sync.sh for mounting the vdisk) +# Kernel.ZXE : normal kernel; defaults to the one build-iso.sh produced +# (build/limine ISO), else supply a path. +set -e +cd "$(dirname "$0")" + +IMG="${1:-RAMDistro.BIN}" +KZXE="${2:-Kernel.ZXE}" + +if [ ! -f "$IMG" ]; then + echo "ERROR: OS image not found: $IMG" + echo "Run src/Misc/DoDistroRAM.ZC inside ZealOS, then copy Tmp/RAMDistro.BIN here." + exit 1 +fi +if [ ! -f "$KZXE" ]; then + echo "ERROR: kernel not found: $KZXE" + echo "Copy a normal Kernel.ZXE here (e.g. from build-iso.sh output or the vdisk)." + exit 1 +fi + +LIMINE_BINARY_BRANCH="v10.x-binary" +[ -d limine ] || git clone https://github.com/limine-bootloader/limine.git --branch=$LIMINE_BINARY_BRANCH --depth=1 +make -C limine +[ -f ../zealbooter/bin/kernel ] || make -C ../zealbooter distclean all + +TMPISODIR=$(mktemp -d) +trap 'rm -rf "$TMPISODIR"' EXIT +mkdir -p "$TMPISODIR/Boot" "$TMPISODIR/EFI/BOOT" + +cp limine/BOOTX64.EFI "$TMPISODIR/EFI/BOOT/BOOTX64.EFI" +cp limine/limine-uefi-cd.bin "$TMPISODIR/Boot/Limine-UEFI-CD.BIN" +cp limine/limine-bios-cd.bin "$TMPISODIR/Boot/Limine-BIOS-CD.BIN" +cp limine/limine-bios.sys "$TMPISODIR/Boot/Limine-BIOS.SYS" +cp ../zealbooter/bin/kernel "$TMPISODIR/Boot/ZealBooter.ELF" +cp "$KZXE" "$TMPISODIR/Boot/Kernel.ZXE" +cp "$IMG" "$TMPISODIR/Boot/RAMDistro.BIN" + +# limine.conf: kernel = module 0, OS image = module 1 +cat > "$TMPISODIR/Boot/Limine.CONF" <<'EOF' +timeout: 1 +interface_resolution: 1024x768 + +/ZealOS (RAM) + protocol: limine + resolution: 1024x768 + path: boot():/Boot/ZealBooter.ELF + module_path: boot():/Boot/Kernel.ZXE + module_path: boot():/Boot/RAMDistro.BIN +EOF + +truncate -s 32K bios_boot.img +xorriso -as mkisofs -R -r -J -b Boot/Limine-BIOS-CD.BIN \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + --efi-boot Boot/Limine-UEFI-CD.BIN \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + -append_partition 4 21686148-6449-6E6F-744E-656564454649 bios_boot.img \ + -appended_part_as_gpt \ + "$TMPISODIR" -o ZealOS-RAM.iso +rm -f bios_boot.img + +./limine/limine bios-install ZealOS-RAM.iso --no-gpt-to-mbr-isohybrid-conversion + +echo +echo "Built: build/ZealOS-RAM.iso (self-contained; CD / dd-to-USB / Ventoy / PXE)" diff --git a/build/extract-kernel.sh b/build/extract-kernel.sh new file mode 100755 index 000000000..57b5cfa76 --- /dev/null +++ b/build/extract-kernel.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Robust extract of Kernel.ZXE (and optionally RAMDistro.BIN) from ZealOS.qcow2. +# Run from build/ with the VM SHUT DOWN. +set -e +DISK=ZealOS.qcow2 +MNT=/tmp/zealtmp + +[ -f "$DISK" ] || { echo "No $DISK in $(pwd)"; exit 1; } + +# Fail if QEMU still has the disk open (would give a stale read). +if command -v fuser >/dev/null 2>&1 && sudo fuser "$DISK" >/dev/null 2>&1; then + echo "ERROR: $DISK is in use (VM still running?). Shut down the VM first." + exit 1 +fi + +sudo modprobe nbd +# Tear down any stale connection/mount from a previous run. +sudo umount "$MNT" 2>/dev/null || true +sudo qemu-nbd -d /dev/nbd0 2>/dev/null || true +sleep 1 + +sudo qemu-nbd -c /dev/nbd0 "$DISK" +sudo partprobe /dev/nbd0 +sleep 1 +sudo mkdir -p "$MNT" +sudo mount /dev/nbd0p1 "$MNT" + +echo -n "Kernel.ZXE: "; sudo stat -c '%y (%s bytes)' "$MNT/Boot/Kernel.ZXE" + +sudo cp "$MNT/Boot/Kernel.ZXE" ./Kernel.ZXE +[ "$1" = "--image" ] && sudo cp "$MNT/Tmp/RAMDistro.BIN" ./RAMDistro.BIN +sudo chown "$(id -u):$(id -g)" ./Kernel.ZXE +[ -f ./RAMDistro.BIN ] && sudo chown "$(id -u):$(id -g)" ./RAMDistro.BIN || true + +sync +sudo umount "$MNT" +sudo qemu-nbd -d /dev/nbd0 +echo "Extracted Kernel.ZXE: $(stat -c '%y (%s bytes)' ./Kernel.ZXE)" diff --git a/build/usb-boot-guide.md b/build/usb-boot-guide.md new file mode 100644 index 000000000..c5b271c16 --- /dev/null +++ b/build/usb-boot-guide.md @@ -0,0 +1,27 @@ +dd if=/dev/zero of=usbdisk.img bs=1M count=512 +echo 'start=2048, type=c' | sfdisk usbdisk.img +LOOP=$(sudo losetup -f --show -P usbdisk.img) +sudo mkfs.vfat -F 32 ${LOOP}p1 +sudo losetup -d $LOOP + + +qemu-system-x86_64 -machine q35,accel=kvm -m 2G \ + -boot d \ + -cdrom ZealOS-PublicDomain-BIOS-2026-07-24-03_43_40.iso \ + -device qemu-xhci,id=xhci \ + -drive if=none,id=ub,format=raw,file=usbdisk.img \ + -device usb-storage,bus=xhci.0,drive=ub + + +CopyTree("::/", "V:/"); # copies the whole CD to the stick +Dir("V:/"); # should now list Boot, Kernel, System, Compiler, Home, etc. +BootHDIns('V'); # recompiles kernel on V: (few min), writes FAT32 boot record +BootMHDIns('V'); # MBR chain-loader + +# At the BootHDIns config prompt, enter V for boot drive, ENTER through the rest. + +qemu-system-x86_64 -machine q35,accel=kvm -m 2G \ + -device qemu-xhci,id=xhci \ + -drive if=none,id=ub,format=raw,file=usbdisk.img \ + -device usb-storage,bus=xhci.0,drive=ub \ + -device usb-kbd,bus=xhci.0 -device usb-mouse,bus=xhci.0 \ No newline at end of file diff --git a/build/ventoy-ram-iso-guide.md b/build/ventoy-ram-iso-guide.md new file mode 100644 index 000000000..6d41d6408 --- /dev/null +++ b/build/ventoy-ram-iso-guide.md @@ -0,0 +1,138 @@ +# ZealOS RAM-distro ISO (Ventoy / limine / PXE) + +Builds a self-contained `ZealOS-RAM.iso`. The kernel embeds the whole OS as a +RedSea RAM disk, so it boots with no post-load disk access from any loader that +places a kernel in memory. Same repo/branch also supports native USB-drive +install (see `usb-boot-guide.md`); the two are independent. + +Branch: `usb-boot-ventoy`. + +## Status / prerequisite + +The RAM ISO is loaded by ZealBooter → limine. The limine boot path must reach +the desktop for this ISO to boot. If a plain limine ISO black-screens in your +setup, fix that first (kernel boot markers are emitted to QEMU debug port 0xE9; +boot with `-debugcon stdio`). Native CD boot and native USB install do not use +this path and work independently. + +## Host packages + +``` +git qemu-system-x86 xorriso gcc make sudo +# nbd kernel module (for qemu-nbd mounting) +``` + +## 1. Clone + branch + +``` +git clone git@github.com:Zeal-Operating-System/ZealOS.git +cd ZealOS +git checkout usb-boot-ventoy +``` + +## 2. Build the base ISOs + +``` +cd build +./build-iso.sh +``` + +Produces `ZealOS-PublicDomain-BIOS-*.iso` (native) and `ZealOS-BSD2-UEFI-*.iso` +(limine). Bootstrap uses the tracked `build/AUTO.ISO`; needs `sudo` for +`qemu-nbd`. + +## 3. Create a working ZealOS VM + +``` +qemu-img create -f qcow2 ZealOS.qcow2 2G +qemu-system-x86_64 -machine q35,accel=kvm -m 2G -boot d \ + -cdrom ZealOS-PublicDomain-BIOS-*.iso \ + -drive file=ZealOS.qcow2,format=qcow2,if=ide +``` + +The CD and the disk must be on the same AHCI controller or ZealOS won't see the +CD (do NOT put them on separate `-device ich9-ahci` controllers). At the ZealOS +prompt `Install onto hard drive (y or n)?` answer `y` and follow the installer +(target drive `C`). Shut down when done. + +## 4. Sync branch source into the VM + +`sync.sh` uses `ZEALDISK=ZealOS.qcow2` by default (edit if elsewhere). VM must be +shut down. + +``` +./sync.sh vm +``` + +## 5. Recompile kernel in the VM + +``` +qemu-system-x86_64 -machine q35,accel=kvm -m 2G \ + -drive file=ZealOS.qcow2,format=qcow2,if=ide +``` + +In ZealOS: + +``` +BootHDIns('C'); +``` + +Enter `C` at the boot-drive prompt, ENTER through the rest. Reboot the VM after. + +## 6. Build the OS image in the VM + +In ZealOS: + +``` +#include "::/Misc/DoDistroRAM"; +``` + +Copies the OS into a RAM drive and snapshots it. Output: `::/Tmp/RAMDistro.BIN` +(~48MB). Shut down. + +## 7. Extract image + kernel to the host + +`RAMDistro.BIN` is the OS image; `/Boot/Kernel.ZXE` is the normal kernel that +step 5 recompiled (it carries the RAM-distro mount code). +(or use the kernel extraction script `./extract-kernel.sh`) + +``` +sudo modprobe nbd +sudo qemu-nbd -c /dev/nbd0 ZealOS.qcow2 +sudo partprobe /dev/nbd0 +sudo mkdir -p /tmp/zealtmp +sudo mount /dev/nbd0p1 /tmp/zealtmp +sudo cp /tmp/zealtmp/Tmp/RAMDistro.BIN ./RAMDistro.BIN +sudo cp /tmp/zealtmp/Boot/Kernel.ZXE ./Kernel.ZXE +sudo umount /tmp/zealtmp +sudo qemu-nbd -d /dev/nbd0 +``` + +## 8. Package the RAM ISO + +``` +./build-ram-iso.sh RAMDistro.BIN Kernel.ZXE +``` + +Output: `build/ZealOS-RAM.iso`. The kernel is Limine module 0, the OS image is +module 1; ZealBooter passes the image address to the kernel, which mounts it as +RAM drive B: and boots. + +## 9. Test in QEMU + +``` +qemu-system-x86_64 -machine q35,accel=kvm -m 2G -cdrom ZealOS-RAM.iso +``` + +## 10. Ventoy + +Copy `ZealOS-RAM.iso` to the Ventoy partition of the USB stick. Boot the stick, +select it from the Ventoy menu. + +## Notes + +- Bump `RAMDISTRO_OFFSET` in `src/Misc/DoDistroRAM.ZC` if the kernel ever grows + past 1MB (`Kernel compile` will assert on a negative BINLOAD pad). +- Non-RAM ISOs on Ventoy do not work: ZealOS cannot read Ventoy's exFAT data + partition to find the ISO. The RAM ISO avoids all post-load disk access. +- PXE: chainload limine over the network with `Boot/Kernel.ZXE` = the RAM kernel. diff --git a/src/Kernel/BlkDev/DiskAddDev.ZC b/src/Kernel/BlkDev/DiskAddDev.ZC index 3b9531309..20df81a43 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -30,6 +30,29 @@ U0 BlkDevLockFwdingSet(CBlkDev *bd) } } +public Bool BlkDevRawBlksRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Whole-device read, bypassing partition bounds. Dispatches on BlkDev type. +//Used by $LK,"BlkDevAdd",A="MN:BlkDevAdd"$() partition scans and boot-loader installers. + if (bd->type == BDT_USB) + { + if (fp_usb_msd_blks_read) + return (*fp_usb_msd_blks_read)(bd, buf, blk, count); + return FALSE; + } + return AHCIAtaBlksRead(bd, buf, blk, count); +} + +public Bool BlkDevRawBlksWrite(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Whole-device write, bypassing partition bounds. Dispatches on BlkDev type. + if (bd->type == BDT_USB) + { + if (fp_usb_msd_blks_write) + return (*fp_usb_msd_blks_write)(bd, buf, blk, count); + return FALSE; + } + return AHCIAtaBlksWrite(bd, buf, blk, count); +} + I64 BlkDevAdd(CBlkDev *bd, I64 prt_num=I64_MIN, Bool whole_drive, Bool make_free) {//It will mount just one partition of prt_num>=0. //When repartitioing whole drive, whole_drive=TRUE. @@ -68,6 +91,7 @@ I64 BlkDevAdd(CBlkDev *bd, I64 prt_num=I64_MIN, Bool whole_drive, Bool make_free break; case BDT_ATA: + case BDT_USB: drive->drive_signature = DRIVE_SIGNATURE_VAL; //Temporarily validate if (!BlkDevInit(bd)) drive->drive_signature = 0; //Revoke validation @@ -83,14 +107,14 @@ I64 BlkDevAdd(CBlkDev *bd, I64 prt_num=I64_MIN, Bool whole_drive, Bool make_free drive->size = bd->max_blk + 1 - bd->drv_offset; //The following read is a test read. //if it hangs, the drive is not supported. - AHCIAtaBlksRead(bd, &mbr, 0, 1); + BlkDevRawBlksRead(bd, &mbr, 0, 1); break; } offset = 0; ext_base = INVALID_CLUS; while (prt_num < 0 || num <= prt_num) { - AHCIAtaBlksRead(bd, &mbr, offset, 1); + BlkDevRawBlksRead(bd, &mbr, offset, 1); if (mbr.signature != 0xAA55) break; j = -1; @@ -121,7 +145,7 @@ I64 BlkDevAdd(CBlkDev *bd, I64 prt_num=I64_MIN, Bool whole_drive, Bool make_free case MBR_PT_FAT32d: case MBR_PT_FAT32e: case MBR_PT_FAT32f: - AHCIAtaBlksRead(bd, &br, drive->drv_offset, 1); + BlkDevRawBlksRead(bd, &br, drive->drv_offset, 1); drive->drive_signature = DRIVE_SIGNATURE_VAL; drive->prt_num = num; @@ -235,24 +259,58 @@ U0 BlkDevsInitAll() for (i = 0; i < DRIVES_NUM; i++) blkdev.let_to_drive[i] = &blkdev.drvs[i]; +//Self-contained (RAM-distro) boot: ZealBooter loaded the OS image as a limine +//module and passed its physical address. Mount it as RAM drive B: (already a +//RedSea filesystem) and boot from it -- no disk controller needed, so this +//boots from any loader (Limine ISO/CD/USB, Ventoy, PXE). + if (sys_ram_distro_base) + { + bd = BlkDevNextFreeSlot('B', BDT_RAM); + bd->RAM_disk = sys_ram_distro_base; + bd->max_blk = (sys_ram_distro_size >> BLK_SIZE_BITS) - 1; + if (BlkDevAdd(bd, , TRUE, TRUE)) + { + blkdev.boot_drive_let = 'B'; + "\nRAM-distro: booting from embedded image, drive B:.\n"; + } + } + "\nAHCIInit;\n"; AHCIInit; - if (!blkdev.ahci_hba) + "\nUsbBootInit;\n"; + i = UsbBootInit; + +//A RAM-distro already has its boot drive (B: above); the kernel_config mount +//code targets install hardware (a specific AHCI/DVD drive that isn't here), so +//skip it entirely. Otherwise run it, wrapped so a failed mount (e.g. missing +//hardware when booting a DVD-configured kernel from USB) doesn't panic boot -- +//it falls through to UsbBootDriveFallback() / the boot-drive check. The #exe +//compiler directives stay OUTSIDE the runtime try so its lexical scope is clean. + if (!sys_ram_distro_base) { - "\nZealOS requires AHCI.\n\n" + #exe { + StreamPrint("#exe {Option(OPTf_WARN_PAREN, OFF);}"); + StreamPrint("try {"); + if (kernel_config->opts[CONFIG_MOUNT_AHCI_AUTO]) + StreamPrint("MountAHCIAuto;"); + StreamDoc(kernel_config->add_dev); + StreamPrint("} catch { \"\\nBoot mount code failed (missing hardware?).\\n\"; Fs->catch_except = TRUE; }"); + StreamPrint("#exe {Option(OPTf_WARN_PAREN, ON);}"); + }; + } + + UsbBootDriveFallback; + +//A RAM-distro kernel boots with no disk controller at all, so only give up +//if the boot drive is actually missing after all mount attempts. + if (!DriveCheck(Letter2Drive(blkdev.boot_drive_let, FALSE), FALSE)) + { + "\nBoot drive %C: did not mount (no AHCI/USB/RAM boot device).\n\n" "If in IDE compatibility mode, switch to SATA mode.\n" "If running in a VM, ensure disks are organized under a SATA controller.\n\n" - "Rebooting in 10 seconds...\n"; + "Rebooting in 10 seconds...\n", blkdev.boot_drive_let; Sleep(10*1000); Reboot; } - - #exe { - if (kernel_config->opts[CONFIG_MOUNT_AHCI_AUTO]) - StreamPrint("MountAHCIAuto;"); - StreamPrint("#exe {Option(OPTf_WARN_PAREN, OFF);}"); - StreamDoc(kernel_config->add_dev); - StreamPrint("#exe {Option(OPTf_WARN_PAREN, ON);}"); - }; } diff --git a/src/Kernel/BlkDev/DiskBlk.ZC b/src/Kernel/BlkDev/DiskBlk.ZC index 5a6d2f6c4..0bd018a39 100755 --- a/src/Kernel/BlkDev/DiskBlk.ZC +++ b/src/Kernel/BlkDev/DiskBlk.ZC @@ -69,6 +69,13 @@ Bool BlkRead(CDrive *drive, U8 *buf, I64 blk, I64 count) case BDT_ATAPI: res = AHCIAtapiRBlks(drive, buf, blk, count); break; + + case BDT_USB: + if (fp_usb_msd_blks_read) + res = (*fp_usb_msd_blks_read)(bd, buf, blk, count); + else + res = FALSE; + break; } bd->last_time = tS; if (bd->flags & BDF_READ_CACHE) @@ -117,6 +124,13 @@ Bool BlkWrite(CDrive *drive, U8 *buf, I64 blk, I64 count) case BDT_ATAPI: res = AHCIAtaWBlks(drive, buf, blk, count); break; + + case BDT_USB: + if (fp_usb_msd_blks_write) + res = (*fp_usb_msd_blks_write)(bd, buf, blk, count); + else + res = FALSE; + break; } bd->last_time = tS; if (bd->flags & BDF_READ_CACHE) diff --git a/src/Kernel/BlkDev/DiskBlkDev.ZC b/src/Kernel/BlkDev/DiskBlkDev.ZC index 85448bc4a..ca3c13078 100755 --- a/src/Kernel/BlkDev/DiskBlkDev.ZC +++ b/src/Kernel/BlkDev/DiskBlkDev.ZC @@ -125,6 +125,13 @@ Bool BlkDevInit(CBlkDev *bd) if (res = AHCIAtaInit(bd)) drive->size = bd->max_blk + 1; break; + + case BDT_USB: +//USB mass storage driver fills in max_blk before BlkDevAdd(). + bd->max_reads = 64; + bd->max_writes = 64; + res = TRUE; + break; } if (res && bd->flags & BDF_READ_CACHE) DiskCacheInvalidate(drive); diff --git a/src/Kernel/BlkDev/DiskDrive.ZC b/src/Kernel/BlkDev/DiskDrive.ZC index 13508206e..55ff72c9d 100755 --- a/src/Kernel/BlkDev/DiskDrive.ZC +++ b/src/Kernel/BlkDev/DiskDrive.ZC @@ -202,8 +202,10 @@ I64 Letter2BlkDevType(U8 drv_let) return BDT_ISO_FILE_READ; if ('Q' <= drv_let <= 'S') return BDT_ISO_FILE_WRITE; - if ('T' <= drv_let <= 'Z') + if ('T' <= drv_let <= 'U') return BDT_ATAPI; + if ('V' <= drv_let <= 'Z') + return BDT_USB; return BDT_NULL; } @@ -347,7 +349,7 @@ U8 *DriveModelNum(U8 drv_let=0) return res; } -U8 blkdev_text_attr[BDT_TYPES_NUM] = {BLACK, LTCYAN, WHITE, LTGREEN, LTRED, LTBLUE}; +U8 blkdev_text_attr[BDT_TYPES_NUM] = {BLACK, LTCYAN, WHITE, LTGREEN, LTRED, LTBLUE, LTPURPLE}; U8 drv_text_attr[3] = {BLACK, BLUE, RED}; U8 DriveTextAttrGet(U8 drv_let=0) diff --git a/src/Kernel/BlkDev/DiskFormat.ZC b/src/Kernel/BlkDev/DiskFormat.ZC index a66a86147..17b56e1b2 100755 --- a/src/Kernel/BlkDev/DiskFormat.ZC +++ b/src/Kernel/BlkDev/DiskFormat.ZC @@ -10,11 +10,12 @@ Bool DriveTypeSet(U8 drv_let, I64 type=FSt_REDSEA) switch (bd->type) { case BDT_ATA: + case BDT_USB: offset = 0; ext_base = INVALID_CLUS; while (TRUE) { - AHCIAtaBlksRead(bd, &mbr, offset, 1); + BlkDevRawBlksRead(bd, &mbr, offset, 1); j =- 1; for (i = 0; i < 4; i++) { @@ -45,7 +46,7 @@ Bool DriveTypeSet(U8 drv_let, I64 type=FSt_REDSEA) throw('Drive'); } mbr.p[i].active = 0x80; - AHCIAtaBlksWrite(bd, &mbr, offset, 1); + BlkDevRawBlksWrite(bd, &mbr, offset, 1); return TRUE; } drv_num++; diff --git a/src/Kernel/KDefine.ZC b/src/Kernel/KDefine.ZC index 235b628cc..2e5c56192 100755 --- a/src/Kernel/KDefine.ZC +++ b/src/Kernel/KDefine.ZC @@ -215,7 +215,8 @@ U0 SysDefinesLoad() "ATA\0" "FILE_READ\0" "FILE_WRITE\0" - "ATAPI\0"); + "ATAPI\0" + "USB\0"); DefineListLoad("ST_DRIVE_TYPES", "NULL\0" diff --git a/src/Kernel/KExterns.ZC b/src/Kernel/KExterns.ZC index 918d887fb..49574dee5 100755 --- a/src/Kernel/KExterns.ZC +++ b/src/Kernel/KExterns.ZC @@ -10,6 +10,8 @@ import Bool Ed(U8 *link_st=NULL, I64 edf_dof_flags=0); extern U0 SysErr(U8 *format, ...); extern U0 SysLog(U8 *format, ...); +extern I64 UsbBootInit(); +extern U0 UsbBootDriveFallback(); extern I64 MemBIOSTotal(); diff --git a/src/Kernel/KGlobals.ZC b/src/Kernel/KGlobals.ZC index 8499b3c2c..e48ebd36a 100755 --- a/src/Kernel/KGlobals.ZC +++ b/src/Kernel/KGlobals.ZC @@ -33,6 +33,9 @@ CMouseHardStateGlobals mouse_hard, mouse_hard_last; CScreenCastGlobals screencast; CTextGlobals text; +Bool (*fp_usb_msd_blks_read )(CBlkDev *bd, U8 *buf, I64 blk, I64 count); //Set by USB mass storage driver. +Bool (*fp_usb_msd_blks_write)(CBlkDev *bd, U8 *buf, I64 blk, I64 count); + U8 *(*fp_getstr2)(I64 flags=0); U0 (*fp_update_ctrls)(CTask *task); CDoc *(*fp_doc_put)(CTask *task=NULL); diff --git a/src/Kernel/KInterrupts.ZC b/src/Kernel/KInterrupts.ZC index 2108971a6..f2187f562 100755 --- a/src/Kernel/KInterrupts.ZC +++ b/src/Kernel/KInterrupts.ZC @@ -157,8 +157,87 @@ U0 IntPICInit() OutU8(PIC_2_DATA, 0x02); //IW3 Telling PIC_2 its cascade identity. OutU8(PIC_1_DATA, 0x0D); //IW4 8086 Mode, Buffered Mode (Master PIC) OutU8(PIC_2_DATA, 0x09); //IW4 8086 Mode, Buffered Mode (Slave PIC) - OutU8(PIC_1_DATA, 0xFA); //Mask all but IRQ0 (timer) and IRQ2 Cascade. - OutU8(PIC_2_DATA, 0xFF); + OutU8(PIC_1_DATA, 0xFB); //Mask all but IRQ2 Cascade. IRQ0 (PIT) masked - + OutU8(PIC_2_DATA, 0xFF); //the heartbeat is the $LK,"LapicTimerInit",A="MN:LapicTimerInit"$ timer. + +//Soft-enable the local APIC, virtual-wire LVTs (LINT0=ExtINT, LINT1=NMI, TPR=0). + *(dev.uncached_alias + LAPIC_SVR)(U32 *) |= LAPICF_APIC_ENABLED; + *(dev.uncached_alias + LAPIC_LVT_LINT0)(U32 *) = 0x700; + *(dev.uncached_alias + LAPIC_LVT_LINT1)(U32 *) = 0x400; + *(dev.uncached_alias + LAPIC_TASK_PRIORITY)(U32 *) = 0; +} + +U16 PitCh2Read() +{//Latch and read the 8254 channel-2 counter (used only for LAPIC-timer cal). + OutU8(PIT_CMD, PIT_CMDF_CHANNEL2); //latch ch2 + return InU8(PIT_2) + InU8(PIT_2) << 8; +} + +U0 LapicTimerInit() +{//Run the system heartbeat ($LK,"I_TIMER",A="MN:I_TIMER"$: jiffies, preemption, AP timer IPIs) +//off the local APIC timer, not the 8254 PIT. Modern UEFI firmware clock-gates +//the PIT, so IRQ0 never fires and jiffies freeze - every Sleep() then hangs. +//Boot CPU only; APs are ticked via IPI. + CRAXRBXRCXRDX regs; + I64 lapic0, lapic1, pit0, pit1, guard; + I64 tsc_hz, max_leaf, lapic_freq, init_count; + + lapic_freq = 0; + *(dev.uncached_alias + LAPIC_TIMER_DIVIDE)(U32 *) = 0x0B; //divide by 1 + *(dev.uncached_alias + LAPIC_LVT_TIMER)(U32 *) = 0x10000; //masked, one-shot + *(dev.uncached_alias + LAPIC_TIMER_INIT_CNT)(U32 *) = 0xFFFFFFFF; + +//Prefer CPUID.16H base MHz + a TSC-timed window -- accurate on real Intel. + tsc_hz = 0; + CPUId(0, ®s); + max_leaf = regs.rax.u32[0]; + if (max_leaf >= 0x16) + { + CPUId(0x16, ®s); + tsc_hz = (regs.rax.u32[0] & 0xFFFF) * 1000000; + } + if (tsc_hz >= 100000000 && tsc_hz <= 10000000000) + { + pit0 = TSCGet; + pit1 = pit0; + while (pit1 - pit0 < tsc_hz / 100) + pit1 = TSCGet; + lapic1 = *(dev.uncached_alias + LAPIC_TIMER_CUR_CNT)(U32 *); + lapic_freq = (0xFFFFFFFF - lapic1) * 100; + } + +//No usable CPUID freq (e.g. QEMU): fall back to PIT channel 2, whose counter +//runs even with IRQ0 masked, timed over ~10ms. + if (!lapic_freq) + { + OutU8(0x61, InU8(0x61) & ~0x02 | 0x01); //gate on, speaker off + OutU8(PIT_CMD, PIT_CMDF_CHANNEL2 | PIT_CMDF_ACCESS_WORD);//mode 0, load 0xFFFF + OutU8(PIT_2, 0xFF); + OutU8(PIT_2, 0xFF); + pit0 = PitCh2Read; + lapic0 = *(dev.uncached_alias + LAPIC_TIMER_CUR_CNT)(U32 *); + guard = 0; + do + { + pit1 = PitCh2Read; + guard++; + } + while (pit0 - pit1 < SYS_TIMER_FREQ / 100 && guard < 2000000); + if (pit0 - pit1 >= SYS_TIMER_FREQ / 100) + { + lapic1 = *(dev.uncached_alias + LAPIC_TIMER_CUR_CNT)(U32 *); + lapic_freq = (lapic0 - lapic1) * SYS_TIMER_FREQ / (pit0 - pit1); + } + OutU8(0x61, InU8(0x61) & ~0x03); //gate off + } + +//Clamp to a sane LAPIC rate so a bad measurement can never storm the CPU. + if (lapic_freq < 1000000 || lapic_freq > 10000000000) + lapic_freq = 100000000; //100MHz safe default + init_count = lapic_freq / JIFFY_FREQ; + + *(dev.uncached_alias + LAPIC_LVT_TIMER)(U32 *) = I_TIMER + 0x20000; //periodic + *(dev.uncached_alias + LAPIC_TIMER_INIT_CNT)(U32 *) = init_count; } interrupt U0 IntNop() diff --git a/src/Kernel/KMain.ZC b/src/Kernel/KMain.ZC index 8b1df95de..7cf71cacb 100755 --- a/src/Kernel/KMain.ZC +++ b/src/Kernel/KMain.ZC @@ -173,9 +173,11 @@ U0 KMain() IntInit2; LBts(&sys_run_level, RLf_INTERRUPTS); + "LapicTimerInit;\n"; + LapicTimerInit; + "TimeCal;\n"; TimeCal; - "BlkDevsInitAll;\n"; BlkDevsInitAll; "\nDiskChange(':');\n"; @@ -217,6 +219,8 @@ U0 KMain() Spawn(&MouseHardDriverInstall); "Spawn(&MouseRawWatcher,, \"MouseRawWatcher\");\n"; Spawn(&MouseRawWatcher,, "MouseRawWatcher"); + "Spawn(&UsbTaskMain,, \"USB\");\n"; + Spawn(&UsbTaskMain,, "USB"); "Load(\"Compiler\");\n"; Cd("/Compiler"); diff --git a/src/Kernel/KMisc.ZC b/src/Kernel/KMisc.ZC index bde981092..207a62dda 100755 --- a/src/Kernel/KMisc.ZC +++ b/src/Kernel/KMisc.ZC @@ -16,7 +16,7 @@ U0 IntCore0TimerHandler(CTask *) LBtr(&sys_semas[SEMA_SYS_TIMER], 0); for (i = 1; i < mp_count; i++) MPInt(I_TIMER, i); - OutU8(PIC_1, PIC_EOI); //Acknowledge PIC Interrupt + *(dev.uncached_alias + LAPIC_EOI)(U32 *) = 0; //Acknowledge LAPIC timer interrupt } I64 SysTimerRead() diff --git a/src/Kernel/KStart16.ZC b/src/Kernel/KStart16.ZC index 13fef09e0..55aa45288 100755 --- a/src/Kernel/KStart16.ZC +++ b/src/Kernel/KStart16.ZC @@ -68,6 +68,9 @@ SYS_BOOTLOADER_ID:: DU8 BL_ZEAL; SYS_FRAMEBUFFER_LIST:: DU8 sizeof(CVideoInfo) * VBE_MODES_NUM DUP(0); +SYS_RAM_DISTRO_BASE:: DU64 0; //Set by ZealBooter to the RAM-distro limine module (else 0) +SYS_RAM_DISTRO_SIZE:: DU64 0; + #assert $$ - SYS_KERNEL == sizeof(CKernel) - sizeof(CZXE) REQUESTED_SCREEN_WIDTH: DU16 1024; diff --git a/src/Kernel/Kernel.PRJ b/src/Kernel/Kernel.PRJ index 7dda5c80e..a16a4e9be 100755 --- a/src/Kernel/Kernel.PRJ +++ b/src/Kernel/Kernel.PRJ @@ -55,6 +55,7 @@ #include "MultiProc" #include "EdLite" #include "BlkDev/MakeBlkDev" +#include "Usb/MakeKUsb" #include "FunSeg" #include "SMBIOS" #include "KMain" diff --git a/src/Kernel/KernelA.HH b/src/Kernel/KernelA.HH index d9d731b23..b3ddb2018 100755 --- a/src/Kernel/KernelA.HH +++ b/src/Kernel/KernelA.HH @@ -576,6 +576,8 @@ class CKernel Bool sys_is_uefi_booted; U8 sys_bootloader_id; CVideoInfo sys_framebuffer_list[VBE_MODES_NUM]; + U64 sys_ram_distro_base, + sys_ram_distro_size; }; // Bootloader IDs @@ -667,6 +669,9 @@ class CKernel #define LAPIC_ICR_HIGH (LAPIC_BASE + 0x310) #define LAPIC_LVT_TIMER (LAPIC_BASE + 0x320) +#define LAPIC_TIMER_INIT_CNT (LAPIC_BASE + 0x380) +#define LAPIC_TIMER_CUR_CNT (LAPIC_BASE + 0x390) +#define LAPIC_TIMER_DIVIDE (LAPIC_BASE + 0x3E0) #define LAPIC_LVT_THERMAL (LAPIC_BASE + 0x330) #define LAPIC_LVT_PERF (LAPIC_BASE + 0x340) #define LAPIC_LVT_LINT0 (LAPIC_BASE + 0x350) @@ -3319,7 +3324,8 @@ public class CDirEntry #define BDT_ISO_FILE_READ 3 #define BDT_ISO_FILE_WRITE 4 #define BDT_ATAPI 5 -#define BDT_TYPES_NUM 6 +#define BDT_USB 6 +#define BDT_TYPES_NUM 7 #define BDf_REMOVABLE 0 #define BDf_INITIALIZED 1 diff --git a/src/Kernel/KernelB.HH b/src/Kernel/KernelB.HH index 7ccb0348c..665b4d1cf 100755 --- a/src/Kernel/KernelB.HH +++ b/src/Kernel/KernelB.HH @@ -259,5 +259,7 @@ public _extern SYS_SMBIOS_ENTRY U8 *sys_smbios_entry; public _extern SYS_DISK_UUID U64 sys_disk_uuid[2]; public _extern SYS_IS_UEFI_BOOTED Bool sys_is_uefi_booted; public _extern SYS_BOOTLOADER_ID U8 sys_bootloader_id; +_extern SYS_RAM_DISTRO_BASE U64 sys_ram_distro_base; +_extern SYS_RAM_DISTRO_SIZE U64 sys_ram_distro_size; #help_index "" diff --git a/src/Kernel/KernelC.HH b/src/Kernel/KernelC.HH index 18c28999b..75b6bee2c 100755 --- a/src/Kernel/KernelC.HH +++ b/src/Kernel/KernelC.HH @@ -303,6 +303,8 @@ public extern CBlkDev *BlkDevCheck(CBlkDev *bd, Bool except=TRUE); public extern U0 BlkDevDel( CBlkDev *bd); public extern Bool BlkDevLock( CBlkDev *bd); public extern CBlkDev *BlkDevNextFreeSlot(U8 first_drive_let, I64 type); +public extern Bool (*fp_usb_msd_blks_read )(CBlkDev *bd, U8 *buf, I64 blk, I64 count); +public extern Bool (*fp_usb_msd_blks_write)(CBlkDev *bd, U8 *buf, I64 blk, I64 count); public extern Bool BlkDevUnlock(CBlkDev *bd, Bool reset=FALSE); public extern U0 BlkDevsRelease(); public extern U0 BlkDevRep(); diff --git a/src/Kernel/Memory/BlkPool.ZC b/src/Kernel/Memory/BlkPool.ZC index 3df086a46..c1aa2bfa0 100755 --- a/src/Kernel/Memory/BlkPool.ZC +++ b/src/Kernel/Memory/BlkPool.ZC @@ -44,38 +44,66 @@ U0 BlkPoolsInit() code_heap_limit = 0x80000000; i = code_heap_limit - SYS_16MEG_AREA_LIMIT; //See $LK,"RLf_16MEG_SYS_CODE_BP",A="FF:::/Kernel/Memory/PageTables.ZC,RLf_16MEG_SYS_CODE_BP"$ - BlkPoolAdd(sys_code_bp, SYS_16MEG_AREA_LIMIT, i >> MEM_PAG_BITS); mem_heap_limit = i + SYS_16MEG_AREA_LIMIT - 1; - if (code_heap_limittype) { if (m20->type == MEM_E820t_USABLE) { - lo = m20->base; - hi = m20->base + m20->len; - if (lo code_heap_limit) - lo = code_heap_limit; - else - hi = lo; //cancel - } - if (code_heap_limit <= lo < hi) + lo = (m20->base(I64) + MEM_PAG_SIZE - 1) & ~(MEM_PAG_SIZE - 1); + hi = (m20->base(I64) + m20->len) & ~(MEM_PAG_SIZE - 1); + if (lo < SYS_16MEG_AREA_LIMIT) + lo = SYS_16MEG_AREA_LIMIT; //1..16MB added by $LK,"SYS_INIT_16MEG_SYS_CODE_BP",A="FF:::/Kernel/Memory/PageTables.ZC,SYS_INIT_16MEG_SYS_CODE_BP"$ + if (hi > code_heap_limit) + hi = code_heap_limit; + if (lo < hi) + BlkPoolAdd(sys_code_bp, lo, (hi - lo) >> MEM_PAG_BITS); + } + m20++; + } + } + else + { +//Native boot: stock behavior. + BlkPoolAdd(sys_code_bp, SYS_16MEG_AREA_LIMIT, i >> MEM_PAG_BITS); + + if (code_heap_limittype) + { + if (m20->type == MEM_E820t_USABLE) { - if (first) + lo = m20->base; + hi = m20->base + m20->len; + if (lo> MEM_PAG_BITS); - sys_data_bp = lo; - Fs->data_heap = HeapCtrlInit(, Fs, sys_data_bp); - first = FALSE; + if (hi > code_heap_limit) + lo = code_heap_limit; + else + hi = lo; //cancel + } + if (code_heap_limit <= lo < hi) + { + if (first) + { + BlkPoolInit(lo, (hi - lo) >> MEM_PAG_BITS); + sys_data_bp = lo; + Fs->data_heap = HeapCtrlInit(, Fs, sys_data_bp); + first = FALSE; + } + else + BlkPoolAdd(sys_data_bp, lo, (hi - lo) >> MEM_PAG_BITS); } - else - BlkPoolAdd(sys_data_bp, lo, (hi - lo) >> MEM_PAG_BITS); } + m20++; } - m20++; } } LBts(&sys_run_level, RLf_FULL_HEAPS); diff --git a/src/Kernel/SerialDev/Mouse.ZC b/src/Kernel/SerialDev/Mouse.ZC index 244af4221..0ba2fb177 100755 --- a/src/Kernel/SerialDev/Mouse.ZC +++ b/src/Kernel/SerialDev/Mouse.ZC @@ -488,6 +488,8 @@ Bool MouseHardDriverInstall(I64 dummy=0) //can be spawned U0 KbdMouseHandler(Bool poll_kbd, Bool poll_mouse) { + I64 status; + if (mouse_hard.install_in_progress) { Yield; @@ -497,17 +499,20 @@ U0 KbdMouseHandler(Bool poll_kbd, Bool poll_mouse) KbdMouseReset; else { +//status 0xFF = no 8042 (absent port floats high); a real one never reads 0xFF. +//Guard the drains or they spin forever on PS/2-less machines - and the mouse +//one runs under CLI, wedging the box the first time the window manager polls. if (poll_mouse && mouse_hard.installed && !mouse_hard.irqs_working) { PUSHFD CLI - while (InU8(KBD_CTRL) & 1) + while ((status = InU8(KBD_CTRL)) & 1 && status != 0xFF) MouseHardPacketRead; POPFD } if (poll_kbd) - while (InU8(KBD_CTRL) & 1) + while ((status = InU8(KBD_CTRL)) & 1 && status != 0xFF) KbdPacketRead; if (kbd.reset) diff --git a/src/Kernel/Usb/KUsb.HH b/src/Kernel/Usb/KUsb.HH new file mode 100644 index 000000000..a9e34ca3d --- /dev/null +++ b/src/Kernel/Usb/KUsb.HH @@ -0,0 +1,332 @@ +//USB + xHCI definitions. Kernel AOT USB stack: +//xHCI host controller, HID boot keyboard/mouse, mass storage, USB boot media. +//Included by Kernel.PRJ (AOT). May also be JIT-included for class defs; +//globals are extern-declared here and defined in KXhci.ZC. + +#help_index "USB" + +#define USB_DEVS_MAX 16 +#define USB_EPS_MAX 8 +#define USB_IFS_MAX 6 + +//USB descriptor types +#define USB_DESC_DEV 1 +#define USB_DESC_CONFIG 2 +#define USB_DESC_STR 3 +#define USB_DESC_IF 4 +#define USB_DESC_EP 5 +#define USB_DESC_HID 0x21 + +//USB standard requests +#define USB_RQ_GET_STATUS 0 +#define USB_RQ_CLEAR_FEATURE 1 +#define USB_RQ_SET_FEATURE 3 +#define USB_RQ_SET_ADDRESS 5 +#define USB_RQ_GET_DESCRIPTOR 6 +#define USB_RQ_SET_DESCRIPTOR 7 +#define USB_RQ_GET_CONFIG 8 +#define USB_RQ_SET_CONFIG 9 +#define USB_RQ_GET_INTERFACE 10 +#define USB_RQ_SET_INTERFACE 11 + +//bmRequestType bits +#define USB_RT_DEV_TO_HOST 0x80 +#define USB_RT_HOST_TO_DEV 0x00 +#define USB_RT_STD 0x00 +#define USB_RT_CLASS 0x20 +#define USB_RT_VENDOR 0x40 +#define USB_RT_RECIP_DEV 0x00 +#define USB_RT_RECIP_IF 0x01 +#define USB_RT_RECIP_EP 0x02 + +//USB feature selectors +#define USB_FEAT_EP_HALT 0 + +//USB classes +#define USB_CLASS_HID 3 +#define USB_CLASS_MSD 8 +#define USB_CLASS_HUB 9 + +//HID +#define USB_HID_SUBCLASS_BOOT 1 +#define USB_HID_PROTO_KBD 1 +#define USB_HID_PROTO_MOUSE 2 +#define USB_HID_RQ_GET_REPORT 1 +#define USB_HID_RQ_SET_IDLE 10 +#define USB_HID_RQ_SET_PROTOCOL 11 + +//MSD +#define USB_MSD_SUBCLASS_SCSI 6 +#define USB_MSD_PROTO_BOT 0x50 +#define USB_MSD_RQ_RESET 0xFF +#define USB_MSD_RQ_GET_MAX_LUN 0xFE + +//Device speeds, xHCI PORTSC encoding +#define USB_SPEED_FULL 1 +#define USB_SPEED_LOW 2 +#define USB_SPEED_HIGH 3 +#define USB_SPEED_SUPER 4 + +//Endpoint attribute transfer types +#define USB_EP_TYPE_CTRL 0 +#define USB_EP_TYPE_ISOCH 1 +#define USB_EP_TYPE_BULK 2 +#define USB_EP_TYPE_INT 3 + +class CUsbDevDesc +{//18 bytes. HolyC classes are packed. + U8 len, type; + U16 usb_ver; + U8 dev_class, dev_subclass, dev_proto, max_packet0; + U16 vendor_id, product_id, dev_ver; + U8 i_manufacturer, i_product, i_serial, num_configs; +}; + +class CUsbConfigDesc +{//9 bytes + U8 len, type; + U16 total_len; + U8 num_ifs, config_val, i_config, attr, max_power; +}; + +class CUsbIfDesc +{//9 bytes + U8 len, type, if_num, alt, num_eps, if_class, if_subclass, if_proto, i_if; +}; + +class CUsbEPDesc +{//7 bytes + U8 len, type, addr, attr; + U16 max_packet; + U8 interval; +}; + +//======================== xHCI ======================== + +#define XHCI_PCI_CLASS 0x0C0330 + +//Capability register byte offsets +#define XHCI_CAP_CAPLENGTH 0x00 +#define XHCI_CAP_HCIVERSION 0x02 +#define XHCI_CAP_HCSPARAMS1 0x04 +#define XHCI_CAP_HCSPARAMS2 0x08 +#define XHCI_CAP_HCSPARAMS3 0x0C +#define XHCI_CAP_HCCPARAMS1 0x10 +#define XHCI_CAP_DBOFF 0x14 +#define XHCI_CAP_RTSOFF 0x18 + +//Operational register byte offsets +#define XHCI_OP_USBCMD 0x00 +#define XHCI_OP_USBSTS 0x04 +#define XHCI_OP_PAGESIZE 0x08 +#define XHCI_OP_DNCTRL 0x14 +#define XHCI_OP_CRCR 0x18 +#define XHCI_OP_DCBAAP 0x30 +#define XHCI_OP_CONFIG 0x38 +#define XHCI_OP_PORTSC_BASE 0x400 //+ 0x10 * port_index + +//USBCMD bits +#define XHCI_CMD_RS 0x0001 +#define XHCI_CMD_HCRST 0x0002 +#define XHCI_CMD_INTE 0x0004 + +//USBSTS bits +#define XHCI_STS_HCH 0x0001 +#define XHCI_STS_CNR 0x0800 + +//PORTSC bits +#define XHCI_PORTSC_CCS 0x00000001 //Current connect status +#define XHCI_PORTSC_PED 0x00000002 //Port enabled +#define XHCI_PORTSC_PR 0x00000010 //Port reset +#define XHCI_PORTSC_PP 0x00000200 //Port power +#define XHCI_PORTSC_CSC 0x00020000 //Connect status change +#define XHCI_PORTSC_PEC 0x00040000 //Enable change +#define XHCI_PORTSC_WRC 0x00080000 //Warm reset change +#define XHCI_PORTSC_OCC 0x00100000 +#define XHCI_PORTSC_PRC 0x00200000 //Reset change +#define XHCI_PORTSC_PLC 0x00400000 +#define XHCI_PORTSC_CEC 0x00800000 +#define XHCI_PORTSC_CHANGE_BITS 0x00FE0000 +//Write-1-to-clear + preserve mask: RW bits we must not clobber when writing. +//Writing PORTSC: preserve PP, write change bits only to clear them. + +//Runtime register byte offsets (interrupter 0) +#define XHCI_RT_IMAN0 0x20 +#define XHCI_RT_IMOD0 0x24 +#define XHCI_RT_ERSTSZ0 0x28 +#define XHCI_RT_ERSTBA0 0x30 +#define XHCI_RT_ERDP0 0x38 + +//TRB types +#define XHCI_TRB_NORMAL 1 +#define XHCI_TRB_SETUP 2 +#define XHCI_TRB_DATA 3 +#define XHCI_TRB_STATUS 4 +#define XHCI_TRB_ISOCH 5 +#define XHCI_TRB_LINK 6 +#define XHCI_TRB_EVENT_DATA 7 +#define XHCI_TRB_NOOP 8 +#define XHCI_TRB_ENABLE_SLOT 9 +#define XHCI_TRB_DISABLE_SLOT 10 +#define XHCI_TRB_ADDRESS_DEV 11 +#define XHCI_TRB_CONFIG_EP 12 +#define XHCI_TRB_EVAL_CTX 13 +#define XHCI_TRB_RESET_EP 14 +#define XHCI_TRB_STOP_EP 15 +#define XHCI_TRB_SET_TR_DEQ 16 +#define XHCI_TRB_RESET_DEV 17 +#define XHCI_TRB_NOOP_CMD 23 +#define XHCI_TRB_EVT_TRANSFER 32 +#define XHCI_TRB_EVT_CMD_DONE 33 +#define XHCI_TRB_EVT_PORT_SC 34 +#define XHCI_TRB_EVT_HOST 37 + +//TRB control bits +#define XHCI_TRBF_CYCLE 0x0001 +#define XHCI_TRBF_TC 0x0002 //Toggle cycle (link TRB) +#define XHCI_TRBF_ISP 0x0004 //Interrupt on short packet +#define XHCI_TRBF_CH 0x0010 //Chain +#define XHCI_TRBF_IOC 0x0020 //Interrupt on completion +#define XHCI_TRBF_IDT 0x0040 //Immediate data +#define XHCI_TRBF_BSR 0x0200 //Block set address request +#define XHCI_TRBF_DIR_IN 0x10000 //Data/status stage direction + +//Setup stage TRT values (control bits 17:16) +#define XHCI_TRT_NO_DATA 0 +#define XHCI_TRT_OUT 0x20000 +#define XHCI_TRT_IN 0x30000 + +//Completion codes +#define XHCI_CC_SUCCESS 1 +#define XHCI_CC_DATA_BUF 2 +#define XHCI_CC_BABBLE 3 +#define XHCI_CC_TRANS_ERR 4 +#define XHCI_CC_TRB_ERR 5 +#define XHCI_CC_STALL 6 +#define XHCI_CC_SHORT_PKT 13 +#define XHCI_CC_TIMEOUT -1 //ours, not a hardware code + +//Endpoint context EP types +#define XHCI_EP_TYPE_ISOCH_OUT 1 +#define XHCI_EP_TYPE_BULK_OUT 2 +#define XHCI_EP_TYPE_INT_OUT 3 +#define XHCI_EP_TYPE_CTRL 4 +#define XHCI_EP_TYPE_ISOCH_IN 5 +#define XHCI_EP_TYPE_BULK_IN 6 +#define XHCI_EP_TYPE_INT_IN 7 + +#define XHCI_RING_TRBS 256 //4096 / 16 +#define XHCI_EVT_RING_TRBS 256 + +//Interrupt-IN auto-requeue report handler kinds (see CUsbEP.auto_kind). +#define USB_AUTO_NONE 0 +#define USB_AUTO_KBD 1 +#define USB_AUTO_MOUSE 2 + +//Forward decls: report handlers live in UsbHid.ZC (compiled after Xhci.ZC), +//but XhciEventPoll dispatches to them. Single-pass compiler needs these. +extern U0 UsbKbdCB(U8 *dev, U8 *ep); +extern U0 UsbMouseCB(U8 *dev, U8 *ep); + +class CXhciTRB +{//16 bytes + U32 lo, hi, status, control; +}; + +class CXhciRing +{//Producer ring (command or transfer) + CXhciTRB *trbs; + I64 enq, //enqueue index + cycle; //producer cycle state +}; + +class CUsbEP +{ +//ring is a POINTER, not embedded: CUsbEP is itself embedded by value in CUsbDev, +//and the ZealC JIT class parser rejects two-level by-value class nesting +//(CUsbDev -> CUsbEP -> CXhciRing). Keeping ring a pointer flattens CUsbEP. + CXhciRing *ring; + I64 dci, //xHCI device context index + addr, //USB endpoint address (with 0x80 dir bit) + attr, //descriptor attributes + max_packet, + interval; //descriptor bInterval + Bool valid; +//Sync transfer completion, set by event poll + Bool xfer_done; + I64 xfer_code, //xHCI completion code + xfer_residue; //untransferred bytes +//Async auto-requeue (HID interrupt IN). auto_kind selects the report handler +//(USB_AUTO_*); dispatched in XhciEventPoll. Avoids a function-pointer struct +//member (which the ZealC JIT class parser rejects) and keeps late binding. + U8 *auto_buf; + I64 auto_len, + auto_kind; + CXhciTRB *pending_trb; +}; + +class CUsbIf +{ + Bool valid; + I64 if_num, if_class, if_subclass, if_proto; + I64 ep_idxs[4], //indices into dev->eps + ep_count; +}; + +class CUsbDev +{ + Bool present, configured; + I64 slot, //xHCI slot id + port, //root port index (0-based) + speed; //USB_SPEED_* + U8 *dev_ctx, + *in_ctx; + CUsbEP ep0; + CUsbEP ep_list[USB_EPS_MAX]; + I64 ep_count; + CUsbIf ifs[USB_IFS_MAX]; + I64 if_count; + I64 vendor_id, product_id, + dev_class, dev_subclass, dev_proto, + config_val; + U8 *drv_data; //class driver private (CUsbMsd / CUsbHid) + U8 desc_name[64]; //product string, ASCII +}; + +class CXhciGlobals +{ + Bool present, running; + U8 *mmio, //capability base (uncached alias) + *op, //operational base + *rt; //runtime base + U32 *db; //doorbell array + I64 max_slots, max_ports, ctx_size, + scratch_count; + U64 *dcbaa, + *scratch_arr, + *erst; + CXhciRing cmd_ring; + CXhciTRB *evt_ring; + I64 evt_deq, evt_cycle; + I64 lock, //ring enqueue lock + poll_lock; //event ring consumer lock (boot inline poll vs USB task) +//Command completion, set by event poll + Bool cmd_done; + I64 cmd_code, cmd_slot; + CXhciTRB *cmd_trb; //pending command TRB + Bool port_change; //port status change event seen + I64 pci_bus, pci_dev, pci_fun; +}; + +//Globals defined in $LK,"KXhci.ZC",A="FF:::/Kernel/Usb/KXhci.ZC,usb_xhci"$. +extern CXhciGlobals usb_xhci; +extern CUsbDev usb_devs[USB_DEVS_MAX]; +extern CTask *usb_task; +extern U8 usb_boot_iso_drv_let; //Drive letter of mounted USB install media, else 0 + +public extern U0 UsbRep(); +public extern I64 UsbBootInit(); +public extern U0 UsbBootDriveFallback(); +public extern U0 UsbTaskMain(I64 dummy=0); +public extern Bool XhciInit(I64 start_n=0, Bool any=FALSE); +public extern U0 XhciSwitch(); diff --git a/src/Kernel/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC new file mode 100644 index 000000000..13d5369a7 --- /dev/null +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -0,0 +1,207 @@ +//USB boot integration: driver binding, hotplug scan, boot-time init, +//boot-drive fallback, USB task. + +U0 UsbDrvBind(CUsbDev *dev) +{//Attach class drivers to a freshly configured device. + CUsbIf *uif; + I64 i; + + for (i = 0; i < dev->if_count; i++) + { + uif = &dev->ifs[i]; + if (!uif->valid) + goto next_if; + if (uif->if_class == USB_CLASS_HID) + { + if (uif->if_subclass == USB_HID_SUBCLASS_BOOT && + (uif->if_proto == USB_HID_PROTO_KBD || uif->if_proto == USB_HID_PROTO_MOUSE)) + UsbHidAttach(dev, uif); + else + SysLog("USB: port %d: non-boot HID interface ignored.\n", dev->port); + } + else if (uif->if_class == USB_CLASS_MSD) + { + if (uif->if_subclass == USB_MSD_SUBCLASS_SCSI) + UsbMsdAttach(dev, uif); + else + SysLog("USB: port %d: MSD subclass 0x%02X unsupported.\n", + dev->port, uif->if_subclass); + } + else if (uif->if_class == USB_CLASS_HUB) + SysLog("USB: port %d: hubs not yet supported.\n", dev->port); +next_if:; + } +} + +U0 UsbDrvUnbind(CUsbDev *dev) +{ + UsbHidDetach(dev); + UsbMsdDetach(dev); +} + +U0 UsbPortScan() +{//Check all root ports for connects/disconnects. USB task only. + CUsbDev *dev, *ndev; + I64 port, sc; + + for (port = 0; port < usb_xhci.max_ports; port++) + { + sc = XhciPortSC(port); + if (sc & XHCI_PORTSC_CHANGE_BITS) + XhciPortSCWrite(port, XHCI_PORTSC_PP | (sc & XHCI_PORTSC_CHANGE_BITS)); + dev = UsbDevByPort(port); + if (sc & XHCI_PORTSC_CCS) + { + if (!dev) + { + if (XhciPortReset(port)) + { + ndev = UsbDevEnum(port); + if (ndev) + UsbDrvBind(ndev); + } + } + } + else if (dev) + { + UsbDrvUnbind(dev); + UsbDevDetachCore(dev); + SysLog("USB: port %d: device removed.\n", port); + } + } +} + +public U0 UsbRep() +{//Report attached USB devices. + CUsbDev *dev; + CUsbIf *uif; + I64 i, j; + + if (!usb_xhci.present) + { + "No xHCI controller. Re-probing:\n"; + XhciInit; + } + if (!usb_xhci.present) + return; + UsbPortScan; //rescan so a just-plugged device shows up + "xHCI: %d ports, %d slots.\n", usb_xhci.max_ports, usb_xhci.max_slots; + for (i = 0; i < usb_xhci.max_ports; i++) + " port %d: PORTSC=0x%08X CCS=%d PED=%d PP=%d\n", i, XhciPortSC(i), + XhciPortSC(i) & 1, XhciPortSC(i) >> 1 & 1, XhciPortSC(i) >> 9 & 1; + for (i = 0; i < USB_DEVS_MAX; i++) + { + dev = &usb_devs[i]; + if (dev->present) + { + "Port %2d: %04X:%04X \"%s\"", dev->port, dev->vendor_id, + dev->product_id, dev->desc_name; + for (j = 0; j < dev->if_count; j++) + { + uif = &dev->ifs[j]; + if (uif->if_class == USB_CLASS_HID && uif->if_proto == USB_HID_PROTO_KBD) + " [keyboard]"; + else if (uif->if_class == USB_CLASS_HID && uif->if_proto == USB_HID_PROTO_MOUSE) + " [mouse]"; + else if (uif->if_class == USB_CLASS_MSD) + " [storage]"; + else if (uif->if_class == USB_CLASS_HUB) + " [hub]"; + else + " [class %d]", uif->if_class; + } + "\n"; + } + } + for (i = 0; i < USB_MSDS_MAX; i++) + if (usb_msds[i].valid && usb_msds[i].ready && usb_msds[i].bd) + "Storage %d: %d MB, drives from %C:.\n", i, + ((usb_msds[i].max_lba + 1) * BLK_SIZE) / 0x100000, + usb_msds[i].first_drive_let; +} + +public U0 XhciSwitch() +{//Force the NEXT xHCI controller and rescan (cycles through them). The boot init +//auto-picks the one with a device plugged in, so this is only for overriding. + static I64 idx = 0; + + idx++; + if (!XhciInit(idx, TRUE)) + { + idx = 0; + XhciInit(idx, TRUE); + } + "XhciSwitch: now on controller #%d.\n", idx; + Sleep(200); + UsbPortScan; + UsbRep; +} + +U0 UsbTaskMain(I64 dummy=0) +{//Hotplug + HID poll loop. Spawned from $LK,"KMain",A="FF:::/Kernel/KMain.ZC,UsbTaskMain"$. + I64 tick = 0; + + no_warn dummy; + usb_task = Fs; + if (!usb_xhci.running) + return; + while (TRUE) + { + XhciEventPoll; + UsbHidTick; + tick++; + if (usb_xhci.port_change || tick & 0x7F == 0) + { + usb_xhci.port_change = FALSE; + UsbPortScan; + } + Sleep(2); + } +} + +I64 UsbBootInit() +{//Init xHCI + enumerate + auto-mount USB storage. Called from +//$LK,"BlkDevsInitAll",A="FF:::/Kernel/BlkDev/DiskAddDev.ZC,UsbBootInit"$ before +//the kernel config's mount code runs, so USB drives exist at boot. + I64 i, count = 0; + + if (!XhciInit) + return 0; + Sleep(100); //let root port connect status settle + UsbPortScan; + for (i = 0; i < USB_MSDS_MAX; i++) + if (usb_msds[i].valid && usb_msds[i].ready && usb_msds[i].bd) + count++; + + return count; +} + +U0 UsbBootDriveFallback() +{//If the configured boot drive did not mount, fall back to USB media. +//Called at the end of $LK,"BlkDevsInitAll",A="FF:::/Kernel/BlkDev/DiskAddDev.ZC,UsbBootDriveFallback"$. + CDrive *drive = Letter2Drive(blkdev.boot_drive_let, FALSE); + I64 i; + + if (drive && drive->drive_signature == DRIVE_SIGNATURE_VAL) + return; + if (usb_boot_iso_drv_let) + { + blkdev.boot_drive_let = usb_boot_iso_drv_let; + "USB: boot drive falling back to install media %C:.\n", usb_boot_iso_drv_let; + return; + } + for (i = 0; i < USB_MSDS_MAX; i++) + if (usb_msds[i].valid && usb_msds[i].ready && usb_msds[i].bd) + { + drive = Letter2Drive(usb_msds[i].first_drive_let, FALSE); +//Only a drive with a real, mountable filesystem can be a boot drive; +//skip raw/unformatted sticks (FSt_UNKNOWN) exposed just for DiskPart. + if (drive && drive->drive_signature == DRIVE_SIGNATURE_VAL && + (drive->fs_type == FSt_REDSEA || drive->fs_type == FSt_FAT32)) + { + blkdev.boot_drive_let = usb_msds[i].first_drive_let; + "USB: boot drive falling back to %C:.\n", blkdev.boot_drive_let; + return; + } + } +} diff --git a/src/Kernel/Usb/KUsbCore.ZC b/src/Kernel/Usb/KUsbCore.ZC new file mode 100644 index 000000000..69c1033cd --- /dev/null +++ b/src/Kernel/Usb/KUsbCore.ZC @@ -0,0 +1,270 @@ +//USB core: device enumeration over xHCI. + +I64 UsbCtrl(CUsbDev *dev, I64 rt, I64 rq, I64 val, I64 idx, U8 *buf, I64 len) +{//Control request with one retry on failure. + I64 code = XhciCtrlXfer(dev, rt, rq, val, idx, buf, len); + + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + Sleep(10); + code = XhciCtrlXfer(dev, rt, rq, val, idx, buf, len); + } + + return code; +} + +I64 UsbGetDesc(CUsbDev *dev, I64 type, I64 idx, U8 *buf, I64 len, I64 lang=0) +{ + return UsbCtrl(dev, USB_RT_DEV_TO_HOST, USB_RQ_GET_DESCRIPTOR, + (type << 8) | idx, lang, buf, len); +} + +I64 UsbClearHalt(CUsbDev *dev, CUsbEP *ep) +{//Recover halted endpoint: xHCI reset + USB CLEAR_FEATURE(HALT). + XhciResetEP(dev, ep); + + return UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_RECIP_EP, USB_RQ_CLEAR_FEATURE, + USB_FEAT_EP_HALT, ep->addr, NULL, 0); +} + +U0 UsbStrGet(CUsbDev *dev, I64 idx, U8 *dst, I64 dst_len) +{//Fetch string descriptor as ASCII. + U8 *buf; + I64 i, n, code; + + *dst = 0; + if (!idx) + return; + buf = CAlloc(256, sys_task->code_heap); + code = UsbGetDesc(dev, USB_DESC_STR, idx, buf, 255, 0x0409); + if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) + { + n = buf[0]; + if (n > 2) + { + n = (n - 2) / 2; + if (n > dst_len - 1) + n = dst_len - 1; + for (i = 0; i < n; i++) + dst[i] = buf[2 + i * 2]; + dst[n] = 0; + } + } + Free(buf); +} + +CUsbDev *UsbDevAlloc() +{ + I64 i; + + for (i = 0; i < USB_DEVS_MAX; i++) + if (!usb_devs[i].present) + return &usb_devs[i]; + + return NULL; +} + +U0 UsbParseConfig(CUsbDev *dev, U8 *buf, I64 len) +{//Walk config descriptor, record interfaces + INT/BULK endpoints. + CUsbIfDesc *ifd; + CUsbEPDesc *epd; + CUsbIf *cur_if = NULL; + CUsbEP *ep; + I64 off = 0, dlen, dtype, ttype; + + dev->if_count = 0; + dev->ep_count = 0; + while (off + 2 <= len) + { + dlen = buf[off]; + dtype = buf[off + 1]; + if (!dlen) + break; + if (dtype == USB_DESC_IF) + { + ifd = &buf[off]; + if (ifd->alt == 0 && dev->if_count < USB_IFS_MAX) + { + cur_if = &dev->ifs[dev->if_count]; + MemSet(cur_if, 0, sizeof(CUsbIf)); + cur_if->valid = TRUE; + cur_if->if_num = ifd->if_num; + cur_if->if_class = ifd->if_class; + cur_if->if_subclass = ifd->if_subclass; + cur_if->if_proto = ifd->if_proto; + dev->if_count++; + } + else + cur_if = NULL; //alternate setting; skip its endpoints + } + else if (dtype == USB_DESC_EP) + { + if (cur_if && dev->ep_count < USB_EPS_MAX && cur_if->ep_count < 4) + { + epd = &buf[off]; + ttype = epd->attr & 3; + if (ttype == USB_EP_TYPE_BULK || ttype == USB_EP_TYPE_INT) + { + ep = &dev->ep_list[dev->ep_count]; +//Clear all fields but keep any prior ring allocation (ring is first member). + MemSet(&ep->dci, 0, sizeof(CUsbEP) - offset(CUsbEP.dci)); + ep->addr = epd->addr; + ep->attr = epd->attr; + ep->max_packet = epd->max_packet & 0x7FF; + ep->interval = epd->interval; + ep->dci = (epd->addr & 0xF) << 1; + if (epd->addr & 0x80) + ep->dci |= 1; + ep->valid = TRUE; + ep->auto_kind = USB_AUTO_NONE; + cur_if->ep_idxs[cur_if->ep_count] = dev->ep_count; + cur_if->ep_count++; + dev->ep_count++; + } + } + } + off += dlen; + } +} + +U0 UsbDevDetachCore(CUsbDev *dev) +{//Release xHCI slot; class driver cleanup handled by caller. + I64 i; + + dev->present = FALSE; + dev->configured = FALSE; + if (dev->slot) + { + XhciDisableSlot(dev->slot); + usb_xhci.dcbaa[dev->slot] = 0; + } + dev->slot = 0; + dev->ep0.valid = FALSE; + for (i = 0; i < USB_EPS_MAX; i++) + { + dev->ep_list[i].valid = FALSE; + dev->ep_list[i].auto_kind = USB_AUTO_NONE; + } + dev->drv_data = NULL; +} + +CUsbDev *UsbDevEnum(I64 port) +{//Enumerate device on root port (already reset + enabled). USB task only. + CUsbDev *dev; + CUsbDevDesc *dd; + CUsbConfigDesc *cd; + U8 *buf; + I64 slot, code, sc, total, mps0; + + sc = XhciPortSC(port); + slot = XhciEnableSlot; + if (!slot) + { + "USB: port %d: Enable Slot failed.\n", port; + return NULL; + } + dev = UsbDevAlloc; + if (!dev) + { + XhciDisableSlot(slot); + return NULL; + } + dev->slot = slot; + dev->port = port; + dev->speed = (sc >> 10) & 0xF; + dev->present = TRUE; + dev->configured = FALSE; + dev->drv_data = NULL; + dev->desc_name[0] = 0; + + code = XhciAddressDev(dev); + if (code != XHCI_CC_SUCCESS) + { + "USB: port %d: Address Device failed (%d).\n", port, code; + UsbDevDetachCore(dev); + return NULL; + } + Sleep(5); + + buf = CAlloc(512, sys_task->code_heap); + dd = buf; + +//First 8 bytes to learn EP0 max packet size. + code = UsbGetDesc(dev, USB_DESC_DEV, 0, buf, 8); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + "USB: port %d: GET_DESCRIPTOR(8) failed (%d).\n", port, code; + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + mps0 = dd->max_packet0; + if (dev->speed == USB_SPEED_SUPER) + mps0 = 1 << mps0; //SS stores exponent + if (mps0 && mps0 != dev->ep0.max_packet) + XhciEvalCtxMPS0(dev, mps0); + + code = UsbGetDesc(dev, USB_DESC_DEV, 0, buf, 18); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + "USB: port %d: GET_DESCRIPTOR failed (%d).\n", port, code; + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + dev->vendor_id = dd->vendor_id; + dev->product_id = dd->product_id; + dev->dev_class = dd->dev_class; + dev->dev_subclass = dd->dev_subclass; + dev->dev_proto = dd->dev_proto; + UsbStrGet(dev, dd->i_product, dev->desc_name, 64); + +//Config descriptor: header first for total length, then full. + code = UsbGetDesc(dev, USB_DESC_CONFIG, 0, buf, 9); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + "USB: port %d: GET_CONFIG failed (%d).\n", port, code; + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + cd = buf; + total = cd->total_len; + if (total > 512) + total = 512; + code = UsbGetDesc(dev, USB_DESC_CONFIG, 0, buf, total); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + dev->config_val = cd->config_val; + UsbParseConfig(dev, buf, total); + Free(buf); + + if (dev->ep_count) + { + code = XhciConfigureEPs(dev); + if (code != XHCI_CC_SUCCESS) + { + "USB: port %d: Configure Endpoint failed (%d).\n", port, code; + UsbDevDetachCore(dev); + return NULL; + } + } + code = UsbCtrl(dev, USB_RT_HOST_TO_DEV, USB_RQ_SET_CONFIG, dev->config_val, 0, NULL, 0); + if (code != XHCI_CC_SUCCESS) + { + "USB: port %d: SET_CONFIGURATION failed (%d).\n", port, code; + UsbDevDetachCore(dev); + return NULL; + } + dev->configured = TRUE; + + SysLog("USB: port %d: %04X:%04X class %d \"%s\" (%d ifs, %d eps)\n", + port, dev->vendor_id, dev->product_id, dev->dev_class, + dev->desc_name, dev->if_count, dev->ep_count); + + return dev; +} diff --git a/src/Kernel/Usb/KUsbHid.ZC b/src/Kernel/Usb/KUsbHid.ZC new file mode 100644 index 000000000..89880211e --- /dev/null +++ b/src/Kernel/Usb/KUsbHid.ZC @@ -0,0 +1,265 @@ +//USB HID boot-protocol keyboard + mouse. +//Keyboard: HID usages -> PS/2 set-1 scancodes -> kbd.fifo2 (reuses kernel KbdHandler). +//Mouse: boot reports -> mouse_hard state (reuses kernel mouse pipeline). + +#define USB_HIDS_MAX 8 + +#define USB_HID_REPEAT_DELAY 0.400 +#define USB_HID_REPEAT_RATE 0.035 + +class CUsbHid +{ + Bool valid; + CUsbDev *dev; + CUsbEP *ep; + I64 proto; //1=kbd, 2=mouse + U8 prev[8]; + I64 repeat_sc; //held key scancode for software typematic + F64 repeat_time; +}; + +CUsbHid usb_hids[USB_HIDS_MAX]; + +//Absolute cursor position maintained by the USB mouse (pixels). +I64 usb_mouse_x = 0, usb_mouse_y = 0; + +//HID usage (page 7) -> set-1 scancode. 0x100 flag = 0xE0-prefixed. +I64 usb_hid_to_sc[0x66] = { + 0, 0, 0, 0, 0x1E, 0x30, 0x2E, 0x20, //00-07: ---- a b c d + 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, //08-0F: e f g h i j k l + 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, //10-17: m n o p q r s t + 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x02, 0x03, //18-1F: u v w x y z 1 2 + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, //20-27: 3 4 5 6 7 8 9 0 + 0x1C, 0x01, 0x0E, 0x0F, 0x39, 0x0C, 0x0D, 0x1A, //28-2F: Ent Esc BS Tab Sp - = [ + 0x1B, 0x2B, 0x2B, 0x27, 0x28, 0x29, 0x33, 0x34, //30-37: ] \ # ; ' ` , . + 0x35, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, //38-3F: / Caps F1-F6 + 0x41, 0x42, 0x43, 0x44, 0x57, 0x58, 0x137, 0x46, //40-47: F7-F12 PrtSc ScrLk + 0, 0x152, 0x147, 0x149, 0x153, 0x14F, 0x151, 0x14D, //48-4F: Pause Ins Home PgUp Del End PgDn Right + 0x14B, 0x150, 0x148, 0x45, 0x135, 0x37, 0x4A, 0x4E, //50-57: Left Down Up NumLk KP/ KP* KP- KP+ + 0x11C, 0x4F, 0x50, 0x51, 0x4B, 0x4C, 0x4D, 0x47, //58-5F: KPEnt KP1-KP7 + 0x48, 0x49, 0x52, 0x53, 0x56, 0x15D //60-65: KP8 KP9 KP0 KP. NonUS\ App +}; + +//Modifier bit -> scancode: LCtrl LShift LAlt LGUI RCtrl RShift RAlt RGUI +I64 usb_hid_mod_sc[8] = {0x1D, 0x2A, 0x38, 0x15B, 0x11D, 0x36, 0x138, 0x15C}; + +U0 UsbKbdEmit(I64 sc, I64 up) +{//Push set-1 bytes into kernel keyboard fifo. + if (!sc) + return; + if (sc & 0x100) + FifoU8Insert(kbd.fifo2, 0xE0); + if (up) + FifoU8Insert(kbd.fifo2, (sc & 0x7F) | 0x80); + else + FifoU8Insert(kbd.fifo2, sc & 0x7F); +} + +CUsbHid *UsbHidByEP(CUsbEP *ep) +{ + I64 i; + + for (i = 0; i < USB_HIDS_MAX; i++) + if (usb_hids[i].valid && usb_hids[i].ep == ep) + return &usb_hids[i]; + + return NULL; +} + +Bool UsbKbdReportHas(U8 *report, I64 usage) +{ + I64 i; + + for (i = 2; i < 8; i++) + if (report[i] == usage) + return TRUE; + + return FALSE; +} + +U0 UsbKbdCB(U8 *d, U8 *e) +{//Interrupt IN report arrived (8-byte boot keyboard report). + CUsbDev *dev = d; + CUsbEP *ep = e; + CUsbHid *hid = UsbHidByEP(ep); + U8 *rep; + I64 i, mods, prev_mods, sc; + + if (!hid || !dev->present) + return; + rep = ep->auto_buf; + if (rep[2] == 1) + return; //error rollover + mods = rep[0]; + prev_mods = hid->prev[0]; + for (i = 0; i < 8; i++) + { + if ((mods & (1 << i)) && !(prev_mods & (1 << i))) + UsbKbdEmit(usb_hid_mod_sc[i], FALSE); + if (!(mods & (1 << i)) && (prev_mods & (1 << i))) + UsbKbdEmit(usb_hid_mod_sc[i], TRUE); + } +//Released keys + for (i = 2; i < 8; i++) + { + if (hid->prev[i] && !UsbKbdReportHas(rep, hid->prev[i])) + { + if (hid->prev[i] < 0x66) + { + sc = usb_hid_to_sc[hid->prev[i]]; + UsbKbdEmit(sc, TRUE); + if (hid->repeat_sc == sc) + hid->repeat_sc = 0; + } + } + } +//Pressed keys + for (i = 2; i < 8; i++) + { + if (rep[i] && !UsbKbdReportHas(hid->prev, rep[i])) + { + if (rep[i] < 0x66) + { + sc = usb_hid_to_sc[rep[i]]; + UsbKbdEmit(sc, FALSE); + hid->repeat_sc = sc; + hid->repeat_time = tS + USB_HID_REPEAT_DELAY; + } + } + } + MemCopy(hid->prev, rep, 8); +} + +U0 UsbMouseCB(U8 *d, U8 *e) +{//Interrupt IN report arrived (boot mouse report: buttons dx dy [wheel]). + CUsbDev *dev = d; + CUsbEP *ep = e; + U8 *rep; + I64 dx, dy, dz = 0; + + if (!dev->present) + return; + rep = ep->auto_buf; + + dx = rep[1]; + if (dx & 0x80) + dx -= 256; + dy = rep[2]; + if (dy & 0x80) + dy -= 256; + if (ep->auto_len >= 4) + { + dz = rep[3]; + if (dz & 0x80) + dz -= 256; + } + +//Drive the cursor directly. The kernel prescale/MouseHardSetPost path is meant +//for the PS/2 handler and gets clobbered by WinMgr's per-frame PS/2 polling; +//we own an absolute position and write mouse_hard.pos ourselves. + usb_mouse_x = ClampI64(usb_mouse_x + dx, 0, sys_framebuffer_width - 1); + usb_mouse_y = ClampI64(usb_mouse_y + dy, 0, sys_framebuffer_height - 1); + + mouse_hard.timestamp = TSCGet; + MemCopy(&mouse_hard_last, &mouse_hard, sizeof(CMouseHardStateGlobals)); + mouse_hard.pos.x = usb_mouse_x; + mouse_hard.pos.y = usb_mouse_y; + mouse_hard.pos.z += dz; + mouse_hard.bttns[0] = rep[0] & 1; + mouse_hard.bttns[1] = (rep[0] & 2) >> 1; + mouse_hard.bttns[2] = (rep[0] & 4) >> 2; + mouse_hard.event = TRUE; + mouse.show = TRUE; +} + +U0 UsbHidTick() +{//Software typematic repeat; called from USB task main loop. + CUsbHid *hid; + I64 i; + + for (i = 0; i < USB_HIDS_MAX; i++) + { + hid = &usb_hids[i]; + if (hid->valid && hid->proto == USB_HID_PROTO_KBD && hid->repeat_sc) + { + if (tS >= hid->repeat_time) + { + UsbKbdEmit(hid->repeat_sc, FALSE); + hid->repeat_time = tS + USB_HID_REPEAT_RATE; + } + } + } +} + +U0 UsbHidAttach(CUsbDev *dev, CUsbIf *uif) +{//Bind boot keyboard or mouse interface. + CUsbHid *hid = NULL; + CUsbEP *ep = NULL, *tmp; + I64 i; + + for (i = 0; i < uif->ep_count; i++) + { + tmp = &dev->ep_list[uif->ep_idxs[i]]; + if ((tmp->addr & 0x80) && (tmp->attr & 3) == USB_EP_TYPE_INT) + { + ep = tmp; + break; + } + } + if (!ep) + return; + for (i = 0; i < USB_HIDS_MAX; i++) + if (!usb_hids[i].valid) + { + hid = &usb_hids[i]; + break; + } + if (!hid) + return; + MemSet(hid, 0, sizeof(CUsbHid)); + hid->dev = dev; + hid->ep = ep; + hid->proto = uif->if_proto; + hid->valid = TRUE; + +//Boot protocol; idle=0 -> reports only on change. + UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_IF, + USB_HID_RQ_SET_PROTOCOL, 0, uif->if_num, NULL, 0); + UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_IF, + USB_HID_RQ_SET_IDLE, 0, uif->if_num, NULL, 0); + + if (!ep->auto_buf) + ep->auto_buf = CAlloc(8, sys_task->code_heap); + ep->auto_len = ep->max_packet; + if (ep->auto_len > 8) + ep->auto_len = 8; + if (ep->auto_len < 3) + ep->auto_len = 3; + if (uif->if_proto == USB_HID_PROTO_KBD) + { + ep->auto_len = 8; + ep->auto_kind = USB_AUTO_KBD; + SysLog("USB: HID keyboard on port %d.\n", dev->port); + } + else + { + ep->auto_kind = USB_AUTO_MOUSE; +//Own the cursor: mark installed so WinMouseUpdate consumes our events, and +//irqs_working so WinMgr does NOT poll (and clobber) the PS/2 mouse state. + mouse_hard.installed = TRUE; + mouse_hard.irqs_working = TRUE; + usb_mouse_x = sys_framebuffer_width >> 1; + usb_mouse_y = sys_framebuffer_height >> 1; + SysLog("USB: HID mouse on port %d.\n", dev->port); + } + XhciIntQueue(dev, ep); +} + +U0 UsbHidDetach(CUsbDev *dev) +{ + I64 i; + + for (i = 0; i < USB_HIDS_MAX; i++) + if (usb_hids[i].valid && usb_hids[i].dev == dev) + usb_hids[i].valid = FALSE; +} diff --git a/src/Kernel/Usb/KUsbMsd.ZC b/src/Kernel/Usb/KUsbMsd.ZC new file mode 100644 index 000000000..5e293a257 --- /dev/null +++ b/src/Kernel/Usb/KUsbMsd.ZC @@ -0,0 +1,429 @@ +//USB Mass Storage: Bulk-Only Transport + SCSI, mounted as BDT_USB block devices. + +#define USB_MSDS_MAX 4 +#define USB_MSD_CHUNK_BLKS 64 //32KB bounce per transfer + +#define USB_MSD_CBW_SIG 0x43425355 +#define USB_MSD_CSW_SIG 0x53425355 + +class CUsbMsdCBW +{//31 bytes + U32 sig, tag, data_len; + U8 flags, lun, cb_len; + U8 cb[16]; +}; + +class CUsbMsdCSW +{//13 bytes + U32 sig, tag, residue; + U8 status; +}; + +class CUsbMsd +{ + Bool valid, ready; + CUsbDev *dev; + CUsbEP *bulk_in, *bulk_out; + I64 if_num, + next_tag, + max_lba, blk_size, + lock; + U8 first_drive_let; + CUsbMsdCBW *cbw; + CUsbMsdCSW *csw; + U8 *bounce; + CBlkDev *bd; +}; + +CUsbMsd usb_msds[USB_MSDS_MAX]; + +U0 UsbMsdLock(CUsbMsd *msd) +{ + while (LBts(&msd->lock, 0)) + Yield; +} + +U0 UsbMsdUnlock(CUsbMsd *msd) +{ + LBtr(&msd->lock, 0); +} + +U0 UsbMsdBotReset(CUsbMsd *msd) +{//Bulk-Only Mass Storage Reset + clear both endpoint halts. + UsbCtrl(msd->dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_IF, + USB_MSD_RQ_RESET, 0, msd->if_num, NULL, 0); + UsbClearHalt(msd->dev, msd->bulk_in); + UsbClearHalt(msd->dev, msd->bulk_out); +} + +I64 UsbMsdCmd(CUsbMsd *msd, U8 *cb, I64 cb_len, U8 *data, I64 data_len, I64 dir_in) +{//One BOT command. Returns CSW status (0 ok, 1 check condition), or -1 transport error. + I64 code, i, residue; + CUsbEP *data_ep; + + MemSet(msd->cbw, 0, sizeof(CUsbMsdCBW)); + msd->cbw->sig = USB_MSD_CBW_SIG; + msd->next_tag++; + msd->cbw->tag = msd->next_tag; + msd->cbw->data_len = data_len; + if (dir_in) + msd->cbw->flags = 0x80; + msd->cbw->lun = 0; + msd->cbw->cb_len = cb_len; + for (i = 0; i < cb_len; i++) + msd->cbw->cb[i] = cb[i]; + + code = XhciBulkXfer(msd->dev, msd->bulk_out, msd->cbw, sizeof(CUsbMsdCBW), 2.0); + if (code == XHCI_CC_STALL) + { + UsbMsdBotReset(msd); + return -1; + } + if (code != XHCI_CC_SUCCESS) + return -1; + + if (data_len) + { + if (dir_in) + data_ep = msd->bulk_in; + else + data_ep = msd->bulk_out; + code = XhciBulkXfer(msd->dev, data_ep, data, data_len, 4.0, &residue); + if (code == XHCI_CC_STALL) + UsbClearHalt(msd->dev, data_ep); + else if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + return -1; + } + + MemSet(msd->csw, 0, sizeof(CUsbMsdCSW)); + code = XhciBulkXfer(msd->dev, msd->bulk_in, msd->csw, sizeof(CUsbMsdCSW), 2.0); + if (code == XHCI_CC_STALL) + {//Retry CSW once after clearing halt. + UsbClearHalt(msd->dev, msd->bulk_in); + code = XhciBulkXfer(msd->dev, msd->bulk_in, msd->csw, sizeof(CUsbMsdCSW), 2.0); + } + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + return -1; + if (msd->csw->sig != USB_MSD_CSW_SIG || msd->csw->tag != msd->cbw->tag) + return -1; + if (msd->csw->status == 2) + {//Phase error + UsbMsdBotReset(msd); + return -1; + } + + return msd->csw->status; +} + +I64 UsbMsdRequestSense(CUsbMsd *msd) +{//Returns sense key, or -1. + U8 cb[6], *buf = msd->bounce; + I64 res; + + MemSet(cb, 0, 6); + cb[0] = 0x03; + cb[4] = 18; + res = UsbMsdCmd(msd, cb, 6, buf, 18, TRUE); + if (res) + return -1; + + return buf[2] & 0xF; +} + +Bool UsbMsdTestReady(CUsbMsd *msd) +{ + U8 cb[6]; + + MemSet(cb, 0, 6); + + return UsbMsdCmd(msd, cb, 6, NULL, 0, TRUE) == 0; +} + +Bool UsbMsdInquiry(CUsbMsd *msd, U8 *buf36) +{ + U8 cb[6]; + I64 res; + + MemSet(cb, 0, 6); + cb[0] = 0x12; + cb[4] = 36; + res = UsbMsdCmd(msd, cb, 6, msd->bounce, 36, TRUE); + if (!res) + MemCopy(buf36, msd->bounce, 36); + + return res == 0; +} + +Bool UsbMsdReadCapacity(CUsbMsd *msd) +{ + U8 cb[10], *buf = msd->bounce; + I64 res; + + MemSet(cb, 0, 10); + cb[0] = 0x25; + res = UsbMsdCmd(msd, cb, 10, buf, 8, TRUE); + if (res) + return FALSE; + msd->max_lba = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + msd->blk_size = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + + return TRUE; +} + +I64 UsbMsdRW10(CUsbMsd *msd, I64 write, I64 lba, I64 count, U8 *buf) +{//READ(10)/WRITE(10) through bounce buffer. count <= USB_MSD_CHUNK_BLKS. + U8 cb[10]; + I64 res, bytes = count * msd->blk_size; + + MemSet(cb, 0, 10); + if (write) + { + cb[0] = 0x2A; + MemCopy(msd->bounce, buf, bytes); + } + else + cb[0] = 0x28; + cb[2] = (lba >> 24) & 0xFF; + cb[3] = (lba >> 16) & 0xFF; + cb[4] = (lba >> 8) & 0xFF; + cb[5] = lba & 0xFF; + cb[7] = (count >> 8) & 0xFF; + cb[8] = count & 0xFF; + res = UsbMsdCmd(msd, cb, 10, msd->bounce, bytes, !write); + if (!res && !write) + MemCopy(buf, msd->bounce, bytes); + + return res; +} + +Bool UsbMsdBlksRW(CBlkDev *bd, U8 *buf, I64 blk, I64 count, I64 write) +{ + CUsbMsd *msd; + I64 n, res, retries; + + if (bd->port_num < 0 || bd->port_num >= USB_MSDS_MAX) + return FALSE; + msd = &usb_msds[bd->port_num]; + if (!msd->valid || !msd->dev || !msd->dev->present) + return FALSE; + UsbMsdLock(msd); + while (count > 0) + { + n = count; + if (n > USB_MSD_CHUNK_BLKS) + n = USB_MSD_CHUNK_BLKS; + retries = 2; + do + { + res = UsbMsdRW10(msd, write, blk, n, buf); + if (res > 0) + UsbMsdRequestSense(msd); + retries--; + } + while (res && retries > 0); + if (res) + { + UsbMsdUnlock(msd); + return FALSE; + } + blk += n; + buf += n * msd->blk_size; + count -= n; + } + UsbMsdUnlock(msd); + + return TRUE; +} + +Bool UsbMsdBlksRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{ + return UsbMsdBlksRW(bd, buf, blk, count, FALSE); +} + +Bool UsbMsdBlksWrite(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{ + return UsbMsdBlksRW(bd, buf, blk, count, TRUE); +} + +U0 UsbMsdAttach(CUsbDev *dev, CUsbIf *uif) +{//Bind a Bulk-Only SCSI mass storage interface, mount partitions. + CUsbMsd *msd = NULL; + CUsbEP *ep; + CBlkDev *bd; + CDrive *drive; + CMasterBoot *mbr; + U8 inq[40]; + I64 i, tries, res, msd_idx = 0, is_iso = FALSE, partitioned = FALSE; + + if (uif->if_proto != USB_MSD_PROTO_BOT) + { + SysLog("USB: MSD port %d: unsupported protocol 0x%02X.\n", dev->port, uif->if_proto); + return; + } + for (i = 0; i < USB_MSDS_MAX; i++) + if (!usb_msds[i].valid) + { + msd = &usb_msds[i]; + msd_idx = i; + break; + } + if (!msd) + return; +//Clear state fields but keep the persistent DMA buffer allocations. + MemSet(msd, 0, offset(CUsbMsd.cbw)); + msd->dev = dev; + msd->if_num = uif->if_num; + for (i = 0; i < uif->ep_count; i++) + { + ep = &dev->ep_list[uif->ep_idxs[i]]; + if ((ep->attr & 3) == USB_EP_TYPE_BULK) + { + if (ep->addr & 0x80) + msd->bulk_in = ep; + else + msd->bulk_out = ep; + } + } + if (!msd->bulk_in || !msd->bulk_out) + { + SysLog("USB: MSD port %d: missing bulk endpoints.\n", dev->port); + return; + } + if (!msd->cbw) + msd->cbw = CAlloc(sizeof(CUsbMsdCBW), sys_task->code_heap); + if (!msd->csw) + msd->csw = CAlloc(sizeof(CUsbMsdCSW), sys_task->code_heap); + if (!msd->bounce) + msd->bounce = CAlloc(USB_MSD_CHUNK_BLKS * BLK_SIZE, sys_task->code_heap); + msd->next_tag = 1; + msd->valid = TRUE; + dev->drv_data = msd; + + MemSet(inq, 0, 40); + UsbMsdInquiry(msd, inq); + inq[36] = 0; + + tries = 10; + while (tries > 0 && !UsbMsdTestReady(msd)) + { + UsbMsdRequestSense(msd); + Sleep(100); + tries--; + } + if (!UsbMsdReadCapacity(msd)) + { + SysLog("USB: MSD port %d: READ CAPACITY failed.\n", dev->port); + msd->valid = FALSE; + return; + } + if (msd->blk_size != BLK_SIZE) + { + SysLog("USB: MSD port %d: unsupported block size %d.\n", dev->port, msd->blk_size); + msd->valid = FALSE; + return; + } + msd->ready = TRUE; + + fp_usb_msd_blks_read = &UsbMsdBlksRead; + fp_usb_msd_blks_write = &UsbMsdBlksWrite; + + msd->first_drive_let = DriveNextFreeLet('V'); + bd = BlkDevNextFreeSlot(msd->first_drive_let, BDT_USB); + bd->max_blk = msd->max_lba; + bd->port_num = msd_idx; + msd->bd = bd; + +//Install-media probe: a distro ISO written raw to a stick has an ISO9660 +//volume descriptor "CD001" at logical block 16 (2048-byte sectors = block 64 +//of 512). If present, mount it via the kernel's ISO path, which also detects a +//"ZealOS RedSea" ISO and mounts the embedded RedSea filesystem. So a raw-dd'd +//ZealOS ISO (limine ISO9660 OR native RedSea) becomes a bootable USB drive. + if (msd->max_lba >= 68 && UsbMsdBlksRead(bd, msd->bounce, 64, 4)) + if (!MemCompare(msd->bounce + 1, "CD001", 5)) + is_iso = TRUE; + if (is_iso) + { +//ISO9660 uses 2048-byte logical sectors: make ISOInit compute spc = 4. +//Reads still go through BlkRead in 512-byte units (blk_size only sets spc). + bd->bd_signature = BD_SIGNATURE_VAL; + bd->blk_size = DVD_BLK_SIZE; + bd->flags |= BDF_READ_ONLY; + bd->flags &= ~BDF_READ_CACHE; + drive = DriveMakeFreeSlot(msd->first_drive_let); + drive->bd = bd; + drive->drive_signature = DRIVE_SIGNATURE_VAL; + drive->drv_offset = 0; + drive->size = msd->max_lba + 1; + if (ISOInit(drive, (32767 / bd->blk_size + 1) * bd->blk_size >> BLK_SIZE_BITS)) + { + res = 1; + usb_boot_iso_drv_let = msd->first_drive_let; + BlkDevLockFwdingSet(bd); + SysLog("USB: MSD port %d: ZealOS install media (%Z) drive %C:.\n", + dev->port, drive->fs_type, "ST_DRIVE_TYPES", msd->first_drive_let); + } + else + { + drive->drive_signature = 0; + bd->blk_size = BLK_SIZE; + BlkDevDel(bd); + msd->bd = NULL; + } + } + else + { +//Check for a partition table ourselves. BlkDevAdd() calls BlkDevDel() (which +//zeroes the bd) when it finds no partitions, so only call it when there IS one. + if (UsbMsdBlksRead(bd, msd->bounce, 0, 1)) + { + mbr = msd->bounce; + if (mbr->signature == 0xAA55) + for (i = 0; i < 4; i++) + if (mbr->p[i].type) + partitioned = TRUE; + } + if (partitioned) + res = BlkDevAdd(bd, , FALSE, FALSE); + if (res) + SysLog("USB: MSD port %d: \"%s\" %d MB mounted, first drive %C:.\n", + dev->port, &inq[8], ((msd->max_lba + 1) * BLK_SIZE) / 0x100000, + msd->first_drive_let); + else + { +//Blank/unformatted (or BlkDevAdd deleted bd): expose a fresh whole-device raw +//drive so DiskPart/Format can target it (DriveCheck requires a drive_signature). +//Marked FSt_UNKNOWN so UsbBootDriveFallback won't pick it as a boot drive. + bd = BlkDevNextFreeSlot(msd->first_drive_let, BDT_USB); + bd->max_blk = msd->max_lba; + bd->port_num = msd_idx; + bd->bd_signature = BD_SIGNATURE_VAL; + msd->bd = bd; + drive = DriveMakeFreeSlot(msd->first_drive_let); + drive->bd = bd; + drive->drv_offset = 0; + drive->size = msd->max_lba + 1; + drive->fs_type = FSt_UNKNOWN; + drive->drive_signature = DRIVE_SIGNATURE_VAL; + BlkDevLockFwdingSet(bd); + SysLog("USB: MSD port %d: unformatted %d MB, raw drive %C: (DiskPart/Format to use).\n", + dev->port, ((msd->max_lba + 1) * BLK_SIZE) / 0x100000, msd->first_drive_let); + } + } +} + +U0 UsbMsdDetach(CUsbDev *dev) +{ + I64 i; + + for (i = 0; i < USB_MSDS_MAX; i++) + if (usb_msds[i].valid && usb_msds[i].dev == dev) + { + usb_msds[i].valid = FALSE; + usb_msds[i].ready = FALSE; + if (usb_msds[i].bd) + { + BlkDevDel(usb_msds[i].bd); + usb_msds[i].bd = NULL; + } + } +} diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC new file mode 100644 index 000000000..17bf9ecc7 --- /dev/null +++ b/src/Kernel/Usb/KXhci.ZC @@ -0,0 +1,763 @@ +//xHCI (USB3) host controller driver. Polled, no MSI. +//Commands are issued from the USB task, or from the boot task before it exists. +//Sync transfers may be awaited from any task; XhciEventPoll is the single +//event ring consumer, guarded by usb_xhci.poll_lock so the boot task and the +//USB task can never drain it concurrently. + +CXhciGlobals usb_xhci; +CUsbDev usb_devs[USB_DEVS_MAX]; +CTask *usb_task = NULL; +U8 usb_boot_iso_drv_let = 0; + +U32 XhciCapR32(I64 off) +{ + U32 *p = usb_xhci.mmio + off; + + return *p; +} + +U32 XhciOpR32(I64 off) +{ + U32 *p = usb_xhci.op + off; + + return *p; +} + +U0 XhciOpW32(I64 off, I64 val) +{ + U32 *p = usb_xhci.op + off; + + *p = val; +} + +U0 XhciOpW64(I64 off, I64 val) +{ + U64 *p = usb_xhci.op + off; + + *p = val; +} + +U32 XhciRtR32(I64 off) +{ + U32 *p = usb_xhci.rt + off; + + return *p; +} + +U0 XhciRtW32(I64 off, I64 val) +{ + U32 *p = usb_xhci.rt + off; + + *p = val; +} + +U0 XhciRtW64(I64 off, I64 val) +{ + U64 *p = usb_xhci.rt + off; + + *p = val; +} + +U32 XhciPortSC(I64 port) +{ + return XhciOpR32(XHCI_OP_PORTSC_BASE + 0x10 * port); +} + +U0 XhciPortSCWrite(I64 port, I64 val) +{ + XhciOpW32(XHCI_OP_PORTSC_BASE + 0x10 * port, val); +} + +U0 XhciLock() +{ + while (LBts(&usb_xhci.lock, 0)) + Yield; +} + +U0 XhciUnlock() +{ + LBtr(&usb_xhci.lock, 0); +} + +U0 XhciRingInit(CXhciRing *ring) +{ + if (!ring->trbs) + ring->trbs = CAllocAligned(XHCI_RING_TRBS * sizeof(CXhciTRB), 0x1000, sys_task->code_heap); + else + MemSet(ring->trbs, 0, XHCI_RING_TRBS * sizeof(CXhciTRB)); + ring->enq = 0; + ring->cycle = 1; +} + +CXhciRing *XhciEPRingInit(CUsbEP *ep) +{//Alloc ep's ring struct (pointer member) on first use, then (re)init it. + if (!ep->ring) + ep->ring = CAlloc(sizeof(CXhciRing), sys_task->code_heap); + XhciRingInit(ep->ring); + + return ep->ring; +} + +CXhciTRB *XhciRingEnq(CXhciRing *ring, I64 lo, I64 hi, I64 status, I64 control) +{//control passed without cycle bit; caller holds lock. + CXhciTRB *trb = &ring->trbs[ring->enq], *link; + I64 addr; + + trb->lo = lo; + trb->hi = hi; + trb->status = status; + trb->control = (control & ~1) | ring->cycle; + ring->enq++; + if (ring->enq == XHCI_RING_TRBS - 1) + {//Place link TRB back to start, toggle cycle. + link = &ring->trbs[XHCI_RING_TRBS - 1]; + addr = ring->trbs; + link->lo = addr & 0xFFFFFFFF; + link->hi = addr >> 32; + link->status = 0; + link->control = (XHCI_TRB_LINK << 10) | XHCI_TRBF_TC | ring->cycle; + ring->enq = 0; + ring->cycle ^= 1; + } + + return trb; +} + +CUsbDev *UsbDevBySlot(I64 slot) +{ + I64 i; + + for (i = 0; i < USB_DEVS_MAX; i++) + if (usb_devs[i].present && usb_devs[i].slot == slot) + return &usb_devs[i]; + + return NULL; +} + +CUsbDev *UsbDevByPort(I64 port) +{ + I64 i; + + for (i = 0; i < USB_DEVS_MAX; i++) + if (usb_devs[i].present && usb_devs[i].port == port) + return &usb_devs[i]; + + return NULL; +} + +CUsbEP *UsbDevEPByDCI(CUsbDev *dev, I64 dci) +{ + I64 i; + + if (dci == 1) + return &dev->ep0; + for (i = 0; i < dev->ep_count; i++) + if (dev->ep_list[i].valid && dev->ep_list[i].dci == dci) + return &dev->ep_list[i]; + + return NULL; +} + +U0 XhciIntQueue(CUsbDev *dev, CUsbEP *ep) +{//Queue one interrupt IN TRB on ep, report dispatched via ep->auto_kind. + XhciLock; + ep->pending_trb = XhciRingEnq(ep->ring, ep->auto_buf, 0, ep->auto_len, + (XHCI_TRB_NORMAL << 10) | XHCI_TRBF_IOC | XHCI_TRBF_ISP); + usb_xhci.db[dev->slot] = ep->dci; + XhciUnlock; +} + +U0 XhciEventPoll() +{//Drain event ring. Called ONLY in USB task context. + CXhciTRB *e; + CUsbDev *dev; + CUsbEP *ep; + I64 type, code, slot, dci, addr, work = FALSE; + U64 *erdp; + + if (!usb_xhci.running) + return; + while (LBts(&usb_xhci.poll_lock, 0)) + Yield; + while (TRUE) + { + e = &usb_xhci.evt_ring[usb_xhci.evt_deq]; + if ((e->control & 1) != usb_xhci.evt_cycle) + break; + work = TRUE; + type = (e->control >> 10) & 0x3F; + switch (type) + { + case XHCI_TRB_EVT_CMD_DONE: + usb_xhci.cmd_code = (e->status >> 24) & 0xFF; + usb_xhci.cmd_slot = (e->control >> 24) & 0xFF; + addr = usb_xhci.cmd_trb; + if (usb_xhci.cmd_trb && e->lo == addr & 0xFFFFFFFF) + usb_xhci.cmd_done = TRUE; + break; + + case XHCI_TRB_EVT_TRANSFER: + slot = (e->control >> 24) & 0xFF; + dci = (e->control >> 16) & 0x1F; + code = (e->status >> 24) & 0xFF; + dev = UsbDevBySlot(slot); + if (dev) + { + ep = UsbDevEPByDCI(dev, dci); + if (ep) + { + if (ep->auto_kind) + {//HID interrupt IN: deliver + requeue + if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) + { + switch (ep->auto_kind) + { + case USB_AUTO_KBD: + UsbKbdCB(dev, ep); + break; + case USB_AUTO_MOUSE: + UsbMouseCB(dev, ep); + break; + } + } + XhciIntQueue(dev, ep); + } + else + { + ep->xfer_code = code; + ep->xfer_residue = e->status & 0xFFFFFF; + ep->xfer_done = TRUE; + } + } + } + break; + + case XHCI_TRB_EVT_PORT_SC: + usb_xhci.port_change = TRUE; + break; + } + usb_xhci.evt_deq++; + if (usb_xhci.evt_deq == XHCI_EVT_RING_TRBS) + { + usb_xhci.evt_deq = 0; + usb_xhci.evt_cycle ^= 1; + } + } + if (work) + { + addr = usb_xhci.evt_ring + usb_xhci.evt_deq; + erdp = usb_xhci.rt + XHCI_RT_ERDP0; + *erdp = addr | 8; //8 = clear Event Handler Busy + } + LBtr(&usb_xhci.poll_lock, 0); +} + +I64 XhciCmd(I64 lo, I64 hi, I64 status, I64 control, I64 *_slot=NULL) +{//Issue one command TRB, wait completion. USB task only. + F64 timeout; + + XhciLock; + usb_xhci.cmd_done = FALSE; + usb_xhci.cmd_code = XHCI_CC_TIMEOUT; + usb_xhci.cmd_trb = XhciRingEnq(&usb_xhci.cmd_ring, lo, hi, status, control); + usb_xhci.db[0] = 0; + XhciUnlock; + timeout = tS + 1.0; + while (!usb_xhci.cmd_done && tS < timeout) + { + XhciEventPoll; + Yield; + } + if (_slot) + *_slot = usb_xhci.cmd_slot; + if (!usb_xhci.cmd_done) + return XHCI_CC_TIMEOUT; + + return usb_xhci.cmd_code; +} + +I64 XhciXferWait(CUsbEP *ep, F64 timeout_s) +{ + F64 timeout = tS + timeout_s; + + while (!ep->xfer_done && tS < timeout) + { + if (Fs == usb_task || !usb_task) + XhciEventPoll; + Yield; + } + if (!ep->xfer_done) + return XHCI_CC_TIMEOUT; + + return ep->xfer_code; +} + +I64 XhciCtrlXfer(CUsbDev *dev, I64 rt, I64 rq, I64 val, I64 idx, U8 *buf, I64 len) +{//Control transfer on EP0. Returns completion code. + CUsbEP *ep = &dev->ep0; + I64 trt, ctl, addr; + + if (len) + { + if (rt & USB_RT_DEV_TO_HOST) + trt = XHCI_TRT_IN; + else + trt = XHCI_TRT_OUT; + } + else + trt = XHCI_TRT_NO_DATA; + XhciLock; + ep->xfer_done = FALSE; + ep->xfer_code = XHCI_CC_TIMEOUT; + ep->xfer_residue = 0; + XhciRingEnq(ep->ring, rt | (rq << 8) | (val << 16), idx | (len << 16), 8, + (XHCI_TRB_SETUP << 10) | XHCI_TRBF_IDT | trt); + if (len) + { + ctl = XHCI_TRB_DATA << 10; + if (rt & USB_RT_DEV_TO_HOST) + ctl |= XHCI_TRBF_DIR_IN; + addr = buf; + XhciRingEnq(ep->ring, addr & 0xFFFFFFFF, addr >> 32, len, ctl); + } + ctl = (XHCI_TRB_STATUS << 10) | XHCI_TRBF_IOC; + if (!len || !(rt & USB_RT_DEV_TO_HOST)) + ctl |= XHCI_TRBF_DIR_IN; //status stage opposite dir; IN if no data + XhciRingEnq(ep->ring, 0, 0, 0, ctl); + usb_xhci.db[dev->slot] = 1; + XhciUnlock; + + return XhciXferWait(ep, 1.0); +} + +I64 XhciBulkXfer(CUsbDev *dev, CUsbEP *ep, U8 *buf, I64 len, F64 timeout_s, I64 *_residue=NULL) +{//Single-TRB bulk transfer (max 64KB). Returns completion code. + I64 addr = buf, code; + + XhciLock; + ep->xfer_done = FALSE; + ep->xfer_code = XHCI_CC_TIMEOUT; + ep->xfer_residue = 0; + XhciRingEnq(ep->ring, addr & 0xFFFFFFFF, addr >> 32, len, + (XHCI_TRB_NORMAL << 10) | XHCI_TRBF_IOC | XHCI_TRBF_ISP); + usb_xhci.db[dev->slot] = ep->dci; + XhciUnlock; + code = XhciXferWait(ep, timeout_s); + if (_residue) + *_residue = ep->xfer_residue; + + return code; +} + +U32 *XhciInputCtx(CUsbDev *dev, I64 idx) +{//idx: 0=input control ctx, 1=slot ctx, 1+dci=EP ctx + U32 *p = dev->in_ctx + idx * usb_xhci.ctx_size; + + return p; +} + +U32 *XhciDevCtxGet(CUsbDev *dev, I64 idx) +{//idx: 0=slot ctx, dci=EP ctx + U32 *p = dev->dev_ctx + idx * usb_xhci.ctx_size; + + return p; +} + +I64 XhciEPInterval(CUsbDev *dev, CUsbEP *ep) +{//EP context Interval field, units of 125us * 2^n. + I64 v = ep->interval, n; + + if (dev->speed == USB_SPEED_HIGH || dev->speed == USB_SPEED_SUPER) + { + if (v < 1) + v = 1; + if (v > 16) + v = 16; + return v - 1; + } +//FS/LS: bInterval is in ms; pick n with 2^(n-3) ms >= v. + n = 3; + while ((1 << (n - 3)) < v && n < 10) + n++; + + return n; +} + +I64 XhciEPCtxType(CUsbEP *ep) +{ + I64 t = ep->attr & 3, in = FALSE; + + if (ep->addr & 0x80) + in = TRUE; + if (t == USB_EP_TYPE_BULK) + { + if (in) + return XHCI_EP_TYPE_BULK_IN; + return XHCI_EP_TYPE_BULK_OUT; + } + if (t == USB_EP_TYPE_INT) + { + if (in) + return XHCI_EP_TYPE_INT_IN; + return XHCI_EP_TYPE_INT_OUT; + } + if (t == USB_EP_TYPE_ISOCH) + { + if (in) + return XHCI_EP_TYPE_ISOCH_IN; + return XHCI_EP_TYPE_ISOCH_OUT; + } + + return XHCI_EP_TYPE_CTRL; +} + +I64 XhciEnableSlot() +{//Returns slot id, or 0 on failure. + I64 slot = 0, code; + + code = XhciCmd(0, 0, 0, XHCI_TRB_ENABLE_SLOT << 10, &slot); + if (code != XHCI_CC_SUCCESS) + return 0; + + return slot; +} + +U0 XhciDisableSlot(I64 slot) +{ + XhciCmd(0, 0, 0, (XHCI_TRB_DISABLE_SLOT << 10) | (slot << 24)); +} + +I64 XhciAddressDev(CUsbDev *dev) +{//Alloc contexts + EP0 ring, issue Address Device. Returns completion code. + U32 *icc, *slot_ctx, *ep0_ctx; + I64 mps, addr; + + if (!dev->dev_ctx) + dev->dev_ctx = CAllocAligned(usb_xhci.ctx_size * 32, 0x1000, sys_task->code_heap); + else + MemSet(dev->dev_ctx, 0, usb_xhci.ctx_size * 32); + if (!dev->in_ctx) + dev->in_ctx = CAllocAligned(usb_xhci.ctx_size * 33, 0x1000, sys_task->code_heap); + else + MemSet(dev->in_ctx, 0, usb_xhci.ctx_size * 33); + XhciEPRingInit(&dev->ep0); + dev->ep0.dci = 1; + dev->ep0.valid = TRUE; + dev->ep0.auto_kind = USB_AUTO_NONE; + + switch (dev->speed) + { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + mps = 8; + break; + case USB_SPEED_HIGH: + mps = 64; + break; + default: + mps = 512; + } + dev->ep0.max_packet = mps; + + icc = XhciInputCtx(dev, 0); + icc[0] = 0; + icc[1] = 3; //add slot + EP0 + slot_ctx = XhciInputCtx(dev, 1); + slot_ctx[0] = (1 << 27) | (dev->speed << 20); + slot_ctx[1] = (dev->port + 1) << 16; + ep0_ctx = XhciInputCtx(dev, 2); + ep0_ctx[1] = (3 << 1) | (XHCI_EP_TYPE_CTRL << 3) | (mps << 16); + addr = dev->ep0.ring->trbs; + ep0_ctx[2] = (addr & 0xFFFFFFFF) | 1; //DCS=1 + ep0_ctx[3] = addr >> 32; + ep0_ctx[4] = 8; + + usb_xhci.dcbaa[dev->slot] = dev->dev_ctx; + addr = dev->in_ctx; + + return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, + (XHCI_TRB_ADDRESS_DEV << 10) | (dev->slot << 24)); +} + +I64 XhciEvalCtxMPS0(CUsbDev *dev, I64 mps) +{//Update EP0 max packet size after reading device descriptor. + U32 *icc, *ep0_ctx; + I64 addr; + + MemSet(dev->in_ctx, 0, usb_xhci.ctx_size * 33); + icc = XhciInputCtx(dev, 0); + icc[0] = 0; + icc[1] = 2; //A1 = EP0 + ep0_ctx = XhciInputCtx(dev, 2); + MemCopy(ep0_ctx, XhciDevCtxGet(dev, 1), usb_xhci.ctx_size); + ep0_ctx[1] = (ep0_ctx[1] & 0xFFFF) | (mps << 16); + dev->ep0.max_packet = mps; + addr = dev->in_ctx; + + return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, + (XHCI_TRB_EVAL_CTX << 10) | (dev->slot << 24)); +} + +I64 XhciConfigureEPs(CUsbDev *dev) +{//Build input context with all parsed EPs, issue Configure Endpoint. + U32 *icc, *slot_ctx, *ep_ctx; + CUsbEP *ep; + I64 i, addr, max_dci = 1, type, ival; + + MemSet(dev->in_ctx, 0, usb_xhci.ctx_size * 33); + icc = XhciInputCtx(dev, 0); + icc[0] = 0; + icc[1] = 1; //A0 = slot + for (i = 0; i < dev->ep_count; i++) + { + ep = &dev->ep_list[i]; + if (!ep->valid) + goto next_ep; + XhciEPRingInit(ep); + icc[1] |= 1 << ep->dci; + if (ep->dci > max_dci) + max_dci = ep->dci; + ep_ctx = XhciInputCtx(dev, 1 + ep->dci); + type = XhciEPCtxType(ep); + ival = XhciEPInterval(dev, ep); + ep_ctx[0] = ival << 16; + ep_ctx[1] = (3 << 1) | (type << 3) | (ep->max_packet << 16); + addr = ep->ring->trbs; + ep_ctx[2] = (addr & 0xFFFFFFFF) | 1; + ep_ctx[3] = addr >> 32; + if ((ep->attr & 3) == USB_EP_TYPE_INT) + ep_ctx[4] = ep->max_packet | (ep->max_packet << 16); + else + ep_ctx[4] = 1024; +next_ep:; + } + slot_ctx = XhciInputCtx(dev, 1); + MemCopy(slot_ctx, XhciDevCtxGet(dev, 0), usb_xhci.ctx_size); + slot_ctx[0] = (slot_ctx[0] & 0x07FFFFFF) | (max_dci << 27); + addr = dev->in_ctx; + + return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, + (XHCI_TRB_CONFIG_EP << 10) | (dev->slot << 24)); +} + +I64 XhciResetEP(CUsbDev *dev, CUsbEP *ep) +{//Recover a halted endpoint: Reset EP + Set TR Dequeue to fresh ring. + I64 code, addr; + + code = XhciCmd(0, 0, 0, (XHCI_TRB_RESET_EP << 10) | (ep->dci << 16) | (dev->slot << 24)); + XhciEPRingInit(ep); + addr = ep->ring->trbs; + code = XhciCmd((addr & 0xFFFFFFFF) | 1, addr >> 32, 0, + (XHCI_TRB_SET_TR_DEQ << 10) | (ep->dci << 16) | (dev->slot << 24)); + + return code; +} + +U0 XhciBiosHandoff() +{ + I64 hcc = XhciCapR32(XHCI_CAP_HCCPARAMS1), xecp, id, next; + U32 *cap; + F64 timeout; + + xecp = ((hcc >> 16) & 0xFFFF) * 4; + while (xecp) + { + cap = usb_xhci.mmio + xecp; + id = *cap & 0xFF; + if (id == 1) + {//USB Legacy Support: claim OS ownership + *cap = *cap | (1 << 24); + timeout = tS + 1.0; + while ((*cap & (1 << 16)) && tS < timeout) + Sleep(10); + break; + } + next = (*cap >> 8) & 0xFF; + if (!next) + break; + xecp += next * 4; + } +} + +Bool XhciPortReset(I64 port) +{//Reset root port, wait for enabled. Returns TRUE if port enabled. + I64 sc; + F64 timeout; + + XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_PR); + timeout = tS + 0.5; + while (tS < timeout) + { + sc = XhciPortSC(port); + if (sc & XHCI_PORTSC_PED) + break; + Sleep(10); + } +//Clear change bits + XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_CHANGE_BITS); + sc = XhciPortSC(port); + + return ToBool(sc & XHCI_PORTSC_PED); +} + +U8 *XhciMmioMap(I64 phys) +{//UEFI parks xHCI BARs above 4GB, past the low-4GB uncached alias. ZealBooter +//stretches mem_physical_space to cover them, so the BAR is in the identity map -- +//just flip its page to uncached (PCD) and return the identity pointer. No flush: +//nothing has touched this address, so there's no stale TLB entry. + I64 *pte, base = phys & ~(mem_page_size - 1); + + pte = MemPageTable(base); + *pte = *pte | 0x10; //PCD: uncached + return phys; +} + +public Bool XhciInit(I64 start_n=0, Bool any=FALSE) +{//Init a usable xHCI controller at/after index start_n. A machine has several (a +//Thunderbolt/USB4 one and the PCH one wired to the real ports), so prefer one +//with a device already connected, else fall back to the first that runs. Pass a +//start_n to force a specific controller (e.g. XhciInit(1)). TRUE on success. + I64 n, bdf, bar_lo, bar_hi, addr, i, fallback = -1; + I64 hcs1, hcs2, hcc, cmd; + F64 timeout; + U8 *page; + Bool connected; + + for (n = start_n; TRUE; n++) + { + bdf = PCIClassFind(XHCI_PCI_CLASS, n); + if (bdf == -1) + break; + usb_xhci.pci_bus = bdf.u8[2]; + usb_xhci.pci_dev = bdf.u8[1]; + usb_xhci.pci_fun = bdf.u8[0]; + + bar_lo = PCIReadU32(usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, 0x10); + bar_hi = 0; + if ((bar_lo & 6) == 4) //64-bit BAR + bar_hi = PCIReadU32(usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, 0x14); + addr = bar_hi << 32 | bar_lo & ~0xF; + if (!addr) + goto xi_next; //BAR not assigned +//Enable memory space + bus master + cmd = PCIReadU16(usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, 4); + PCIWriteU16(usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, 4, cmd | 6); + + if (bar_hi) + usb_xhci.mmio = XhciMmioMap(addr); //BAR above 4GB gets its own mapping + else + usb_xhci.mmio = dev.uncached_alias + addr; + usb_xhci.op = usb_xhci.mmio + (XhciCapR32(XHCI_CAP_CAPLENGTH) & 0xFF); + usb_xhci.rt = usb_xhci.mmio + (XhciCapR32(XHCI_CAP_RTSOFF) & ~0x1F); + usb_xhci.db = usb_xhci.mmio + (XhciCapR32(XHCI_CAP_DBOFF) & ~3); + + hcs1 = XhciCapR32(XHCI_CAP_HCSPARAMS1); + hcs2 = XhciCapR32(XHCI_CAP_HCSPARAMS2); + hcc = XhciCapR32(XHCI_CAP_HCCPARAMS1); + usb_xhci.max_slots = hcs1 & 0xFF; + usb_xhci.max_ports = (hcs1 >> 24) & 0xFF; + if (hcc & 4) + usb_xhci.ctx_size = 64; + else + usb_xhci.ctx_size = 32; + + XhciBiosHandoff; + +//Stop + reset controller + XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD) & ~XHCI_CMD_RS); + timeout = tS + 1.0; + while (!(XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) && tS < timeout) + Sleep(1); + XhciOpW32(XHCI_OP_USBCMD, XHCI_CMD_HCRST); + timeout = tS + 1.0; + while ((XhciOpR32(XHCI_OP_USBCMD) & XHCI_CMD_HCRST) && tS < timeout) + Sleep(1); + timeout = tS + 1.0; + while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_CNR) && tS < timeout) + Sleep(1); + + XhciOpW32(XHCI_OP_CONFIG, usb_xhci.max_slots); + +//Device context base address array + if (!usb_xhci.dcbaa) + usb_xhci.dcbaa = CAllocAligned((usb_xhci.max_slots + 1) * 8, 0x1000, sys_task->code_heap); + else + MemSet(usb_xhci.dcbaa, 0, (usb_xhci.max_slots + 1) * 8); + +//Scratchpad buffers + usb_xhci.scratch_count = (((hcs2 >> 21) & 0x1F) << 5) | ((hcs2 >> 27) & 0x1F); + if (usb_xhci.scratch_count) + { + usb_xhci.scratch_arr = CAllocAligned(usb_xhci.scratch_count * 8, 0x1000, sys_task->code_heap); + for (i = 0; i < usb_xhci.scratch_count; i++) + { + page = CAllocAligned(0x1000, 0x1000, sys_task->code_heap); + usb_xhci.scratch_arr[i] = page; + } + usb_xhci.dcbaa[0] = usb_xhci.scratch_arr; + } + XhciOpW64(XHCI_OP_DCBAAP, usb_xhci.dcbaa); + +//Command ring + XhciRingInit(&usb_xhci.cmd_ring); + addr = usb_xhci.cmd_ring.trbs; + XhciOpW64(XHCI_OP_CRCR, addr | 1); + +//Event ring, single segment, interrupter 0 + if (!usb_xhci.evt_ring) + usb_xhci.evt_ring = CAllocAligned(XHCI_EVT_RING_TRBS * sizeof(CXhciTRB), 0x1000, sys_task->code_heap); + else + MemSet(usb_xhci.evt_ring, 0, XHCI_EVT_RING_TRBS * sizeof(CXhciTRB)); + usb_xhci.evt_deq = 0; + usb_xhci.evt_cycle = 1; + if (!usb_xhci.erst) + usb_xhci.erst = CAllocAligned(64, 64, sys_task->code_heap); + addr = usb_xhci.evt_ring; + usb_xhci.erst[0] = addr; + usb_xhci.erst[1] = XHCI_EVT_RING_TRBS; + XhciRtW32(XHCI_RT_ERSTSZ0, 1); + XhciRtW64(XHCI_RT_ERDP0, addr); + addr = usb_xhci.erst; + XhciRtW64(XHCI_RT_ERSTBA0, addr); +//Interrupts stay disabled; we poll. + +//Run + XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD) | XHCI_CMD_RS); + timeout = tS + 1.0; + while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) && tS < timeout) + Sleep(1); + if (XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) + goto xi_next; //never left halt -- try the next controller + + usb_xhci.present = TRUE; + usb_xhci.running = TRUE; + +//Power the root ports. They come up off, so a device never reports connected +//(CCS) until PP is set -- QEMU powers them for us, real hardware does not. + for (i = 0; i < usb_xhci.max_ports; i++) + XhciPortSCWrite(i, XHCI_PORTSC_PP); + Sleep(20); + +//Prefer a controller with something already plugged in. If nothing is connected +//and we weren't told to take any, remember it and keep looking. + connected = FALSE; + for (i = 0; i < usb_xhci.max_ports; i++) + if (XhciPortSC(i) & XHCI_PORTSC_CCS) + connected = TRUE; + if (connected || any) + { + "xHCI #%d at %02X:%02X:%01X: %d ports, %d slots.\n", + n, usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, + usb_xhci.max_ports, usb_xhci.max_slots; + return TRUE; + } + if (fallback == -1) + fallback = n; +xi_next:; + } +//Nothing had a device connected; use the first controller that ran. + if (!any && fallback >= 0) + return XhciInit(fallback, TRUE); + "xHCI: no usable controller found.\n"; + return FALSE; +} diff --git a/src/Kernel/Usb/MakeKUsb.ZC b/src/Kernel/Usb/MakeKUsb.ZC new file mode 100644 index 000000000..173bac7f7 --- /dev/null +++ b/src/Kernel/Usb/MakeKUsb.ZC @@ -0,0 +1,8 @@ +#exe {Cd(__DIR__);}; +#include "KUsb.HH" +#include "KXhci" +#include "KUsbCore" +#include "KUsbHid" +#include "KUsbMsd" +#include "KUsbBoot" +#exe {Cd("..");}; diff --git a/src/Misc/DoDistroRAM.ZC b/src/Misc/DoDistroRAM.ZC new file mode 100644 index 000000000..8190aab44 --- /dev/null +++ b/src/Misc/DoDistroRAM.ZC @@ -0,0 +1,86 @@ +//Build the RAM-distro OS image: a RedSea filesystem holding the whole OS, +//snapshotted from a RAM drive. It ships as a separate Limine module alongside +//the normal Kernel.ZXE (see build/build-ram-iso.sh). ZealBooter passes the +//module address to the kernel, which mounts it as RAM drive B: and boots from +//it -- no disk controller needed, so it boots from any loader (Limine ISO/CD/ +//USB, Ventoy, PXE). +// +//Run from an installed, writable system: #include "::/Misc/DoDistroRAM" +//Output: ::/Tmp/RAMDistro.BIN (copy to the host, feed to build-ram-iso.sh) +// +//Destroys the contents of RAM drive A: while building. + +#define RAMDISTRO_IMAGE "::/Tmp/RAMDistro.BIN" + +U0 MakeRAMDistro(I64 blks=0x30000) +{//blks: image size in 512-byte blocks (default 96MB). MUST exceed the whole OS +//tree or the copy silently runs out of space and skips files (scattered), +//producing an image that boots but crashes reading a missing file. Pass a +//larger value if the sanity check below fails. + CBlkDev *bd; + CDirEntry de; + + if (!DriveIsWritable) + { + "Drive must be writable. Install on hard drive or USB first.\n"; + return; + } + +//--- Fresh RedSea RAM drive A: to assemble the image in --- + "Building %d MB RedSea image in RAM drive A:...\n", (blks * BLK_SIZE) / 0x100000; + bd = Letter2BlkDev('A', FALSE); + if (bd) + BlkDevDel(bd); + bd = BlkDevNextFreeSlot('A', BDT_RAM); + bd->max_blk = blks - 1; + if (!BlkDevAdd(bd,, TRUE, TRUE)) + { + "Could not create RAM drive A:.\n"; + return; + } + +//--- Copy the whole OS tree in (same set as the DVD distro) --- + CopyTree("::/Home", "A:/Home"); + Del("A:/Home/Registry.ZC"); //fresh registry on first boot + CopyTree("::/System", "A:/System"); + CopyTree("::/Apps", "A:/Apps"); + CopyTree("::/Compiler", "A:/Compiler"); + CopyTree("::/Demo", "A:/Demo"); + CopyTree("::/Doc", "A:/Doc"); + CopyTree("::/Kernel", "A:/Kernel"); + CopyTree("::/Misc", "A:/Misc"); + Copy("::/*", "A:/"); //root files (StartOS.ZC, MakeHome.ZC, ...) + DirMake("A:/Tmp"); + DirMake("A:/Tmp/ScreenShots"); + +//--- Sanity check: if a file present on the source is missing on A:, the copy +// ran out of space. Bail rather than ship a broken image. --- + if (FileFind("::/Doc/Start.DD") && !FileFind("A:/Doc/Start.DD") || + FileFind("::/StartOS.ZC") && !FileFind("A:/StartOS.ZC")) + { + "\n$$RED$$RAM drive too small: files were skipped during copy.$$FG$$\n" + "Re-run with more blocks, e.g. MakeRAMDistro(0x40000); (128MB).\n"; + return; + } + +//--- Snapshot the RAM drive memory as the image file --- +//Use the drive's ACTUAL block count: BlkDevInit resets max_blk from MSize() of +//the allocation (rounds up), so the RedSea filesystem spans (max_blk+1) blocks, +//which is >= blks. Capturing only blks*BLK_SIZE would truncate the tail and +//corrupt files copied late. The kernel mounts B: with max_blk = imagesize/512-1, +//so the image size must equal the full filesystem. + "Writing %s...\n", RAMDISTRO_IMAGE; + Del(RAMDISTRO_IMAGE); + FileWrite(RAMDISTRO_IMAGE, bd->RAM_disk, (bd->max_blk + 1) * BLK_SIZE); + + if (FileFind(RAMDISTRO_IMAGE, &de, FUF_JUST_FILES)) + { + "\nDone: %s (%d MB).\n", RAMDISTRO_IMAGE, de.size / 0x100000; + "Copy it to the host and run build/build-ram-iso.sh.\n"; + Free(de.full_name); + } + else + "FAILED: image not written.\n"; +} + +MakeRAMDistro; diff --git a/src/StartOS.ZC b/src/StartOS.ZC index 0409e0dcc..dac61b7a8 100755 --- a/src/StartOS.ZC +++ b/src/StartOS.ZC @@ -10,6 +10,7 @@ Option(0,0); //(0,0)=EchoOff (0,1)=EchoOn #include "/Kernel/KernelB.HH" #include "/Kernel/KernelC.HH" #include "/Compiler/CompilerB.HH" +#include "/Kernel/Usb/KUsb.HH" Option(OPTf_WARN_PAREN, ON); Option(OPTf_WARN_DUP_TYPES, ON); diff --git a/src/System/BlkDev/DiskPart.ZC b/src/System/BlkDev/DiskPart.ZC index be9310c20..423052f20 100755 --- a/src/System/BlkDev/DiskPart.ZC +++ b/src/System/BlkDev/DiskPart.ZC @@ -1,5 +1,21 @@ #help_index "Install;File/Cmd Line (Typically);Cmd Line (Typically)" +Bool DiskPartRawWrite(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Whole-device raw write. USB-capable kernels export BlkDevRawBlksWrite; +//resolve at runtime so this file still parses on pre-USB bootstrap kernels. + CHash *h = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN | HTT_EXPORT_SYS_SYM); + I64 fp = 0; + + if (h) + if (h->type & HTT_FUN) + fp = h(CHashFun *)->exe_addr; + else + fp = h(CHashExport *)->val; + if (fp) + return CallInd(fp, bd, buf, blk, count); + return AHCIAtaBlksWrite(bd, buf, blk, count); +} + #define ROUND_DRIVE_TO (63 * 255) #define DRIVE_HEADER 63 @@ -40,7 +56,7 @@ drv_let=0 means add new drive that is not already mounted. if (drv_let && !Letter2BlkDev(drv_let, FALSE)) drv_let = 0; - if (!drv_let && !(drv_let = Mount(TRUE)) || !(bd = Letter2BlkDev(drv_let, FALSE)) || bd->type != BDT_ATA) + if (!drv_let && !(drv_let = Mount(TRUE)) || !(bd = Letter2BlkDev(drv_let, FALSE)) || (bd->type != BDT_ATA && bd->type != BDT_USB)) return 0; total = bd->max_blk + 1; @@ -130,7 +146,7 @@ drv_let=0 means add new drive that is not already mounted. mbr.p[i].size = remaining; ext_base = offset; } - AHCIAtaBlksWrite(bd, &mbr, 0, 1); + DiskPartRawWrite(bd, &mbr, 0, 1); while (tmppp != &head) { @@ -159,7 +175,7 @@ drv_let=0 means add new drive that is not already mounted. mbr.p[1].offset = offset - ext_base; mbr.p[1].size = tmppp->size; } - AHCIAtaBlksWrite(bd, &mbr, start_offset, 1); + DiskPartRawWrite(bd, &mbr, start_offset, 1); } bd->flags &= ~(BDF_INITIALIZED | BDF_INIT_IN_PROGRESS); diff --git a/src/System/BlkDev/Mount.ZC b/src/System/BlkDev/Mount.ZC index e66d18cbf..8cee2d66c 100755 --- a/src/System/BlkDev/Mount.ZC +++ b/src/System/BlkDev/Mount.ZC @@ -167,7 +167,8 @@ U8 Mount2(U8 boot_drive_let, CDoc *_doc, Bool _caller_is_prtdisk) "$$GREEN$$C$$FG$$-$$GREEN$$L$$FG$$ are ATA hard drives.\n" "$$GREEN$$M$$FG$$-$$GREEN$$P$$FG$$ are ISO file read drives.\n" "$$GREEN$$Q$$FG$$-$$GREEN$$S$$FG$$ are ISO file write drives.\n" - "$$GREEN$$T$$FG$$-$$GREEN$$Z$$FG$$ are ATAPI CD/DVD drives.\n" + "$$GREEN$$T$$FG$$-$$GREEN$$U$$FG$$ are ATAPI CD/DVD drives.\n" + "$$GREEN$$V$$FG$$-$$GREEN$$Z$$FG$$ are USB drives.\n" "\nDrive Letter ($$PURPLE$$$$FG$$ to exit):"; else "\n****** Mount Drives ******\n" @@ -175,12 +176,19 @@ U8 Mount2(U8 boot_drive_let, CDoc *_doc, Bool _caller_is_prtdisk) "C-L are ATA hard drives.\n" "M-P are ISO file read drives.\n" "Q-S are ISO file write drives.\n" - "T-Z are ATAPI CD/DVD drives.\n" + "T-U are ATAPI CD/DVD drives.\n" + "V-Z are USB drives.\n" "\nDrive Letter ( to exit):"; drv_let = Letter2Letter(CharGet); '\n'; - if (type = Letter2BlkDevType(drv_let)) + if (Letter2BlkDevType(drv_let) == BDT_USB) + { + "USB drives are auto-mounted at boot by the kernel USB driver\n" + "(and on hot-plug). Nothing to configure here.\n"; + count++; + } + else if (type = Letter2BlkDevType(drv_let)) { whole_drive = FALSE; if (_doc) diff --git a/src/System/Boot/BootHDIns.ZC b/src/System/Boot/BootHDIns.ZC index 8a4bb96bd..767da9611 100755 --- a/src/System/Boot/BootHDIns.ZC +++ b/src/System/Boot/BootHDIns.ZC @@ -41,6 +41,7 @@ public U0 BootHDIns(U8 drv_let=0) { case BDT_RAM: case BDT_ATA: + case BDT_USB: //BIOS INT13 USB emulation boots the stick's partition record Move("/Kernel/Kernel.ZXE", BOOT_DIR_KERNEL_ZXE); if (!FileFind(BOOT_DIR_KERNEL_ZXE, &de, FUF_JUST_FILES)) diff --git a/src/System/Boot/BootMHDIns.ZC b/src/System/Boot/BootMHDIns.ZC index 8a2595144..6b8f44118 100755 --- a/src/System/Boot/BootMHDIns.ZC +++ b/src/System/Boot/BootMHDIns.ZC @@ -12,6 +12,41 @@ //Old master boot record #define BOOT_DIR_OLDMBR_BIN BOOT_DIR "/OldMBR.BIN" + +Bool BootMHDRawRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Whole-device raw read for boot-loader installs. USB-capable kernels export +//BlkDevRawBlksRead; resolve it at RUNTIME so this file still parses on older +//kernels during distro bootstrap. Fall back to direct AHCI. + CHash *h = HashFind("BlkDevRawBlksRead", Fs->hash_table, HTT_FUN | HTT_EXPORT_SYS_SYM); + I64 fp = 0; + +//Public kernel funcs are hashed as sys exports (CHashExport.val), JIT-local as +//CHashFun.exe_addr; handle both so this resolves on the running (new) kernel. + if (h) + if (h->type & HTT_FUN) + fp = h(CHashFun *)->exe_addr; + else + fp = h(CHashExport *)->val; + if (fp) + return CallInd(fp, bd, buf, blk, count); + return AHCIAtaBlksRead(bd, buf, blk, count); +} + +Bool BootMHDRawWrite(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Whole-device raw write; see $LK,"BootMHDRawRead",A="MN:BootMHDRawRead"$(). + CHash *h = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN | HTT_EXPORT_SYS_SYM); + I64 fp = 0; + + if (h) + if (h->type & HTT_FUN) + fp = h(CHashFun *)->exe_addr; + else + fp = h(CHashExport *)->val; + if (fp) + return CallInd(fp, bd, buf, blk, count); + return AHCIAtaBlksWrite(bd, buf, blk, count); +} + public U0 BootMHDOldRead(U8 src_drive, U8 dst_drive, I64 size=1) {//Reads MBR from disk drive containing src partition. //Writes MBR file to dst BOOT_DIR. @@ -29,7 +64,7 @@ public U0 BootMHDOldRead(U8 src_drive, U8 dst_drive, I64 size=1) { //Bypass partition bounds-checking BlkDevLock(bd); - AHCIAtaBlksRead(bd, mbr, 0, size); + BootMHDRawRead(bd, mbr, 0, size); BlkDevUnlock(bd); Drive(dst_drive); @@ -52,7 +87,7 @@ public U0 BootMHDOldWrite(U8 src_drive, U8 dst_drive) { //Bypass partition bounds-checking BlkDevLock(bd); - AHCIAtaBlksWrite(bd, mbr, 0, size / BLK_SIZE); + BootMHDRawWrite(bd, mbr, 0, size / BLK_SIZE); BlkDevUnlock(bd); } Free(mbr); @@ -72,7 +107,7 @@ public U0 BootMHDZero(U8 dst_drive) MemSet(&mbr, 0, BLK_SIZE); //Bypass partition bounds-checking BlkDevLock(bd); - AHCIAtaBlksWrite(bd, &mbr, 0, 1); + BootMHDRawWrite(bd, &mbr, 0, 1); BlkDevUnlock(bd); } @@ -157,7 +192,7 @@ public Bool BootMHDIns(U8 drv_let, U8 *drv_list=NULL) *BMHD_DAP_BLK(I64 *) = Clus2Blk(drive, de.clus); //Bypass partition bounds-checking BlkDevLock(bd); - AHCIAtaBlksRead(bd, &mbr, 0, 1); + BootMHDRawRead(bd, &mbr, 0, 1); for (i = 0; i < BMHD_END - BMHD_CODE; i++) mbr.code[i] = BMHD_CODE(U8 *)[i]; @@ -173,7 +208,7 @@ public Bool BootMHDIns(U8 drv_let, U8 *drv_list=NULL) mbr.zero = 0; mbr.signature = 0xAA55; - AHCIAtaBlksWrite(bd, &mbr, 0, 1); + BootMHDRawWrite(bd, &mbr, 0, 1); BlkDevUnlock(bd); res = TRUE; diff --git a/src/System/Boot/LimineMHDIns.ZC b/src/System/Boot/LimineMHDIns.ZC index 7f18236b4..66de26061 100755 --- a/src/System/Boot/LimineMHDIns.ZC +++ b/src/System/Boot/LimineMHDIns.ZC @@ -3,6 +3,38 @@ U8 binary_limine_hdd_bin_data[0]; #endif +Bool LimineRawRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Runtime-dispatched raw read (parses on pre-USB bootstrap kernels). + CHash *h = HashFind("BlkDevRawBlksRead", Fs->hash_table, HTT_FUN | HTT_EXPORT_SYS_SYM); + I64 fp = 0; + +//Public kernel funcs are hashed as sys exports (CHashExport.val), JIT-local as +//CHashFun.exe_addr; handle both so this resolves on the running (new) kernel. + if (h) + if (h->type & HTT_FUN) + fp = h(CHashFun *)->exe_addr; + else + fp = h(CHashExport *)->val; + if (fp) + return CallInd(fp, bd, buf, blk, count); + return AHCIAtaBlksRead(bd, buf, blk, count); +} + +Bool LimineRawWrite(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Runtime-dispatched raw write (parses on pre-USB bootstrap kernels). + CHash *h = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN | HTT_EXPORT_SYS_SYM); + I64 fp = 0; + + if (h) + if (h->type & HTT_FUN) + fp = h(CHashFun *)->exe_addr; + else + fp = h(CHashExport *)->val; + if (fp) + return CallInd(fp, bd, buf, blk, count); + return AHCIAtaBlksWrite(bd, buf, blk, count); +} + Bool LimineCFGMake(U8 drv_let) { U8 *filename_cfg = MStrPrint("%C:/Boot/Limine.CONF", drv_let); @@ -87,13 +119,13 @@ Bool LimineMHDIns(U8 drv_let) BlkDevLock(bd); - AHCIAtaBlksRead(bd, &old_mbr, 0, 1); // read old MBR currently on disk + LimineRawRead(bd, &old_mbr, 0, 1); // read old MBR currently on disk MemCopy(new_mbr->p, old_mbr.p, sizeof(CMasterBoot.p)); // copy old MBR partition table to new Limine MBR // read the whole MBR + post-MBR-gap that Limine wants to overwrite, save all of that as an OldMBR.BIN BootMHDOldRead(drv_let, drv_let, 63); - AHCIAtaBlksWrite(bd, limine, 0, 63); // write Limine to MBR and post-MBR gap + LimineRawWrite(bd, limine, 0, 63); // write Limine to MBR and post-MBR gap BlkDevUnlock(bd); diff --git a/zealbooter/src/lib/print.c b/zealbooter/src/lib/print.c index d419836f5..fa7034384 100644 --- a/zealbooter/src/lib/print.c +++ b/zealbooter/src/lib/print.c @@ -11,18 +11,24 @@ #define PRINT_BUFFER_SIZE 8192 +// Emit a byte to QEMU's debug console (I/O port 0xE9). View with -debugcon stdio. +// Limine dropped the terminal-write feature, so this is the only ZealBooter output. +static inline void debugcon_putc(char c) +{ + __asm__ volatile ("outb %0, %1" : : "a"((unsigned char)c), "Nd"((unsigned short)0xE9)); +} + int printf(const char *format, ...) { va_list args; char buffer[PRINT_BUFFER_SIZE]; - //struct limine_terminal *terminal; - size_t length; + size_t length, i; va_start(args, format); length = vsnprintf(buffer, PRINT_BUFFER_SIZE, format, args); - //terminal = terminal_request.response->terminals[0]; - //terminal_request.response->write(terminal, buffer, length); + for (i = 0; i < length; i++) + debugcon_putc(buffer[i]); va_end(args); diff --git a/zealbooter/src/zealbooter.c b/zealbooter/src/zealbooter.c index 66fa857d6..43c79383d 100644 --- a/zealbooter/src/zealbooter.c +++ b/zealbooter/src/zealbooter.c @@ -4,6 +4,15 @@ #include #include +static inline void outl(uint16_t port, uint32_t val) { + __asm__ volatile ("outl %0, %1" : : "a"(val), "Nd"(port)); +} +static inline uint32_t inl(uint16_t port) { + uint32_t val; + __asm__ volatile ("inl %1, %0" : "=a"(val) : "Nd"(port)); + return val; +} + __attribute__((used, section(".limine_requests_start"))) static volatile uint64_t limine_requests_start_marker[] = LIMINE_REQUESTS_START_MARKER; @@ -137,6 +146,8 @@ struct CKernel { uint8_t sys_is_uefi_booted; uint8_t sys_bootloader_id; struct CVideoInfo sys_framebuffer_list[VBE_MODES_NUM]; + uint64_t sys_ram_distro_base; + uint64_t sys_ram_distro_size; } __attribute__((packed)); #define BL_ZEAL 0 @@ -145,6 +156,17 @@ struct CKernel { #define BOOT_SRC_RAM 2 #define BOOT_SRC_HDD 3 #define BOOT_SRC_DVD 4 + +// The kernel runs with a fixed low-memory layout: kernel code below +// SYS_FIXED_AREA (0x100000), and page tables + system heap growing up from +// 0x100000. So the kernel MUST load below 0x100000, exactly like the native +// boot loaders (which use BOOT_RAM_BASE). If loaded at 0x100000, the very +// first thing KStart32's SYS_INIT_PAGE_TABLES does is write page tables over +// the running kernel -> crash. Match native: place small kernels at +// BOOT_RAM_BASE. Kernels too big for low memory fall back to the usable-region +// search (they can't boot this way -- ship the OS image as a separate module). +#define BOOT_RAM_BASE 0x7C00 +#define LOW_MEM_LIMIT 0x9F000 #define RLF_16BIT 0b001 #define RLF_VESA 0b010 #define RLF_32BIT 0b100 @@ -191,16 +213,23 @@ void kmain(void) { const size_t final_size = align_up_u64(module_kernel->size + trampoline_size, 16) + boot_stack_size; uintptr_t final_address = (uintptr_t)-1; - for (size_t i = 0; i < memmap_request.response->entry_count; i++) { - struct limine_memmap_entry *entry = memmap_request.response->entries[i]; - - if (entry->type != LIMINE_MEMMAP_USABLE) { - continue; - } + if (BOOT_RAM_BASE + final_size <= LOW_MEM_LIMIT) { + // Small kernel: place it low, below SYS_FIXED_AREA, like the native + // loaders. This low memory is limine's own (bootloader-reclaimable) but + // free to use now that limine has handed off. + final_address = BOOT_RAM_BASE; + } else { + for (size_t i = 0; i < memmap_request.response->entry_count; i++) { + struct limine_memmap_entry *entry = memmap_request.response->entries[i]; + + if (entry->type != LIMINE_MEMMAP_USABLE) { + continue; + } - if (entry->length >= final_size) { - final_address = entry->base; - break; + if (entry->length >= final_size) { + final_address = entry->base; + break; + } } } if (final_address == (uintptr_t)-1) { @@ -217,6 +246,17 @@ void kmain(void) { kernel->sys_framebuffer_bpp = fb->bpp; kernel->sys_framebuffer_addr = (uintptr_t)fb->address - hhdm_request.response->offset; + // A second limine module is the self-contained OS image (RAM-distro). Pass + // its physical address + size to the kernel, which mounts it as RAM drive B:. + // The kernel is loaded low, so a large image module is fine up in high mem. + kernel->sys_ram_distro_base = 0; + kernel->sys_ram_distro_size = 0; + if (module_request.response->module_count >= 2) { + struct limine_file *img = module_request.response->modules[1]; + kernel->sys_ram_distro_base = (uintptr_t)img->address - hhdm_request.response->offset; + kernel->sys_ram_distro_size = img->size; + } + struct limine_video_mode *mode; for (size_t i = 0, j = 0; i < fb->mode_count && i < VBE_MODES_NUM; i++) { @@ -291,8 +331,14 @@ void kmain(void) { printf(" "); switch (entry->type) { - case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: + // Modules (incl. the RAM-distro image) must NOT be handed to the + // kernel heap or it would clobber them. The kernel was copied + // low, so the high kernel-module copy is dead here too. + zeal_mem_type = MEM_E820T_RESERVED; + printf("MODULES : "); + break; + case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: case LIMINE_MEMMAP_USABLE: zeal_mem_type = MEM_E820T_USABLE; printf(" USABLE: "); @@ -336,6 +382,42 @@ void kmain(void) { kernel->mem_E820[mem_count].type = 0; + // The kernel identity-maps 0..mem_physical_space and draws through it. The + // memmap omits the GOP framebuffer, which on UEFI can sit above RAM top, so + // stretch mem_physical_space to cover it or every draw lands unmapped (black). + { + uint64_t fb_top = kernel->sys_framebuffer_addr + + (uint64_t)fb->pitch * fb->height; + if (kernel->mem_physical_space < fb_top) + kernel->mem_physical_space = fb_top; + } + + // Same problem for xHCI MMIO: UEFI parks its 64-bit BARs far above RAM, so + // stretch mem_physical_space to cover them (+2GB headroom so the kernel's + // top-of-space uncached alias doesn't overlap the BAR). The kernel then just + // flips the BAR's identity page to uncached (see XhciMmioMap). Legacy 0xCF8 + // config access works on UEFI here, so a plain brute scan finds them. + for (uint32_t bus = 0; bus < 256; bus++) + for (uint32_t dev = 0; dev < 32; dev++) + for (uint32_t fun = 0; fun < 8; fun++) { + uint32_t sel = 0x80000000u | (bus << 16) | (dev << 11) | (fun << 8); + outl(0xCF8, sel | 0x00); + if (inl(0xCFC) == 0xFFFFFFFFu) + continue; + outl(0xCF8, sel | 0x08); + if ((inl(0xCFC) >> 8) != 0x0C0330u) //class/subclass/prog-if != xHCI + continue; + outl(0xCF8, sel | 0x10); + uint32_t bar_lo = inl(0xCFC); + if ((bar_lo & 0x6u) != 0x4u) //not a 64-bit BAR + continue; + outl(0xCF8, sel | 0x14); + uint64_t bar = ((uint64_t)inl(0xCFC) << 32) | (bar_lo & ~0xFu); + uint64_t top = bar + 0x80000000ull; //BAR + 2GB headroom + if (kernel->mem_physical_space < top) + kernel->mem_physical_space = top; + } + kernel->mem_physical_space = align_up_u64(kernel->mem_physical_space, 0x200000); void *sys_gdt_ptr = (void *)&kernel->sys_gdt_ptr - (uintptr_t)module_kernel->address;