From 6523c4ae872b4780886f0fad7a694ba70647ac58 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:13:04 +0900 Subject: [PATCH 01/26] Add USB support with mass storage and HID drivers This commit introduces a comprehensive USB stack, including support for USB mass storage devices and Human Interface Devices (HID) such as keyboards and mice. Key changes include: - New files for USB core functionality, xHCI host controller, and device enumeration. - Implementation of bulk-only transport for mass storage devices. - Integration of HID protocols for keyboard and mouse input. - Updates to existing block device handling to accommodate USB devices. This enhancement significantly expands the system's capabilities for handling USB peripherals. --- src/Kernel/BlkDev/DiskAddDev.ZC | 18 +- src/Kernel/BlkDev/DiskBlk.ZC | 14 + src/Kernel/BlkDev/DiskBlkDev.ZC | 7 + src/Kernel/BlkDev/DiskDrive.ZC | 4 +- src/Kernel/KGlobals.ZC | 3 + src/Kernel/KernelA.HH | 3 +- src/Kernel/KernelC.HH | 2 + src/System/MakeSystem.ZC | 1 + src/System/Usb/MakeUsb.ZC | 155 +++++++ src/System/Usb/Usb.ZC | 270 +++++++++++++ src/System/Usb/UsbDefs.ZC | 306 ++++++++++++++ src/System/Usb/UsbHid.ZC | 251 ++++++++++++ src/System/Usb/UsbMsd.ZC | 382 +++++++++++++++++ src/System/Usb/Xhci.ZC | 697 ++++++++++++++++++++++++++++++++ 14 files changed, 2108 insertions(+), 5 deletions(-) create mode 100644 src/System/Usb/MakeUsb.ZC create mode 100644 src/System/Usb/Usb.ZC create mode 100644 src/System/Usb/UsbDefs.ZC create mode 100644 src/System/Usb/UsbHid.ZC create mode 100644 src/System/Usb/UsbMsd.ZC create mode 100644 src/System/Usb/Xhci.ZC diff --git a/src/Kernel/BlkDev/DiskAddDev.ZC b/src/Kernel/BlkDev/DiskAddDev.ZC index 3b9531309..b4b2a99e2 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -30,6 +30,17 @@ U0 BlkDevLockFwdingSet(CBlkDev *bd) } } +Bool BlkDevAddBlksRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +{//Partition-scan read during $LK,"BlkDevAdd",A="MN:BlkDevAdd"$(), before drive is validated. + 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); +} + 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 +79,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 +95,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); + BlkDevAddBlksRead(bd, &mbr, 0, 1); break; } offset = 0; ext_base = INVALID_CLUS; while (prt_num < 0 || num <= prt_num) { - AHCIAtaBlksRead(bd, &mbr, offset, 1); + BlkDevAddBlksRead(bd, &mbr, offset, 1); if (mbr.signature != 0xAA55) break; j = -1; @@ -121,7 +133,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); + BlkDevAddBlksRead(bd, &br, drive->drv_offset, 1); drive->drive_signature = DRIVE_SIGNATURE_VAL; drive->prt_num = num; 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..4975522da 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; } 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/KernelA.HH b/src/Kernel/KernelA.HH index d9d731b23..0520d0689 100755 --- a/src/Kernel/KernelA.HH +++ b/src/Kernel/KernelA.HH @@ -3319,7 +3319,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/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/System/MakeSystem.ZC b/src/System/MakeSystem.ZC index c62ccd7d6..70a5b2de9 100755 --- a/src/System/MakeSystem.ZC +++ b/src/System/MakeSystem.ZC @@ -25,4 +25,5 @@ LBts(&sys_run_level, RLf_DOC); #include "Define" #include "WallPaper" #include "Mouse" +#include "Usb/MakeUsb" Cd("..");; diff --git a/src/System/Usb/MakeUsb.ZC b/src/System/Usb/MakeUsb.ZC new file mode 100644 index 000000000..f17eaa43e --- /dev/null +++ b/src/System/Usb/MakeUsb.ZC @@ -0,0 +1,155 @@ +//USB stack build + boot task. +Cd(__DIR__);; +#include "UsbDefs" +#include "Xhci" +#include "Usb" +#include "UsbHid" +#include "UsbMsd" +Cd("..");; + +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.\n"; + return; + } + "xHCI: %d ports, %d slots.\n", usb_xhci.max_ports, usb_xhci.max_slots; + 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; +} + +U0 UsbTaskMain(I64 dummy=0) +{ + I64 tick = 0; + + no_warn dummy; + if (!XhciInit) + return; + Sleep(100); //let port connect status settle after reset + UsbPortScan; + while (TRUE) + { + XhciEventPoll; + UsbHidTick; + tick++; + if (usb_xhci.port_change || tick & 0x7F == 0) + { + usb_xhci.port_change = FALSE; + UsbPortScan; + } + Sleep(2); + } +} + +U0 UsbInit() +{ + MemSet(&usb_xhci, 0, sizeof(CXhciGlobals)); + MemSet(usb_devs, 0, sizeof(CUsbDev) * USB_DEVS_MAX); + MemSet(usb_hids, 0, sizeof(CUsbHid) * USB_HIDS_MAX); + MemSet(usb_msds, 0, sizeof(CUsbMsd) * USB_MSDS_MAX); + usb_task = Spawn(&UsbTaskMain, NULL, "USB"); +} + +UsbInit; diff --git a/src/System/Usb/Usb.ZC b/src/System/Usb/Usb.ZC new file mode 100644 index 000000000..f1e96bfaa --- /dev/null +++ b/src/System/Usb/Usb.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->eps[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_cb = NULL; + 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->eps[i].valid = FALSE; + dev->eps[i].auto_cb = NULL; + } + 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/System/Usb/UsbDefs.ZC b/src/System/Usb/UsbDefs.ZC new file mode 100644 index 000000000..9ce582ee9 --- /dev/null +++ b/src/System/Usb/UsbDefs.ZC @@ -0,0 +1,306 @@ +//USB + xHCI definitions. +//ZealOS USB stack: xHCI host controller, HID boot keyboard/mouse, mass storage. + +#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 + +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 +{ + 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) + U8 *auto_buf; + I64 auto_len; + U0 (*auto_cb)(U8 *dev, U8 *ep); //really (CUsbDev*,CUsbEP*); report in auto_buf + 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 eps[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 +//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; +}; + +CXhciGlobals usb_xhci; +CUsbDev usb_devs[USB_DEVS_MAX]; +CTask *usb_task = NULL; +Bool usb_verbose = FALSE; diff --git a/src/System/Usb/UsbHid.ZC b/src/System/Usb/UsbHid.ZC new file mode 100644 index 000000000..3748bab58 --- /dev/null +++ b/src/System/Usb/UsbHid.ZC @@ -0,0 +1,251 @@ +//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]; + +//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; + } + + mouse_hard.timestamp = TSCGet; + MouseHardSetPre; + 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.prescale.x += dx; + mouse_hard.prescale.y += dy; + mouse_hard.prescale.z += dz; + mouse.show = TRUE; + MouseHardSetPost; +} + +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->eps[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_cb = &UsbKbdCB; + SysLog("USB: HID keyboard on port %d.\n", dev->port); + } + else + { + ep->auto_cb = &UsbMouseCB; + mouse_hard.installed = TRUE; //menu bar + cursor paths check this + 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/System/Usb/UsbMsd.ZC b/src/System/Usb/UsbMsd.ZC new file mode 100644 index 000000000..4bc404bed --- /dev/null +++ b/src/System/Usb/UsbMsd.ZC @@ -0,0 +1,382 @@ +//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); +} + +Bool UsbMsdKernelHooksSet() +{//Resolve kernel USB hooks at runtime so an old Kernel.ZXE degrades gracefully +//(HID still works, storage disabled) instead of hard-failing the System compile. + CHashGlobalVar *r, *w; + U64 *p; + + r = HashFind("fp_usb_msd_blks_read", Fs->hash_table, HTT_GLOBAL_VAR); + w = HashFind("fp_usb_msd_blks_write", Fs->hash_table, HTT_GLOBAL_VAR); + if (!r || !w || !r->data_addr || !w->data_addr) + return FALSE; + p = r->data_addr; + *p = &UsbMsdBlksRead; + p = w->data_addr; + *p = &UsbMsdBlksWrite; + + return TRUE; +} + +U0 UsbMsdAttach(CUsbDev *dev, CUsbIf *uif) +{//Bind a Bulk-Only SCSI mass storage interface, mount partitions. + CUsbMsd *msd = NULL; + CUsbEP *ep; + CBlkDev *bd; + U8 inq[40]; + I64 i, tries, res, msd_idx = 0; + + 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->eps[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; + + if (!UsbMsdKernelHooksSet) + { + SysLog("USB: MSD port %d: kernel lacks USB block hooks (rebuild kernel); storage not mounted.\n", + dev->port); + return; + } + + 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; + res = BlkDevAdd(bd, , FALSE, FALSE); + if (res) + SysLog("USB: MSD port %d: \"%s\" %d MB mounted, %d partition(s), first drive %C:.\n", + dev->port, &inq[8], ((msd->max_lba + 1) * BLK_SIZE) / 0x100000, res, + msd->first_drive_let); + else + { + SysLog("USB: MSD port %d: no mountable partitions.\n", dev->port); + msd->bd = NULL; + } +} + +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/System/Usb/Xhci.ZC b/src/System/Usb/Xhci.ZC new file mode 100644 index 000000000..9aec2af24 --- /dev/null +++ b/src/System/Usb/Xhci.ZC @@ -0,0 +1,697 @@ +//xHCI (USB3) host controller driver. Polled, no MSI. +//All USB commands are issued from the USB task only. +//Sync transfers may be awaited from any task (event poll runs in USB task). + +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; +} + +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->eps[i].valid && dev->eps[i].dci == dci) + return &dev->eps[i]; + + return NULL; +} + +U0 XhciIntQueue(CUsbDev *dev, CUsbEP *ep) +{//Queue one interrupt IN TRB on ep, report delivered via ep->auto_cb. + 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 (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_cb) + {//HID interrupt IN: deliver + requeue + if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) + (*ep->auto_cb)(dev, ep); + 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 + } +} + +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) + 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); + XhciRingInit(&dev->ep0.ring); + dev->ep0.dci = 1; + dev->ep0.valid = TRUE; + dev->ep0.auto_cb = NULL; + + 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->eps[i]; + if (!ep->valid) + goto next_ep; + XhciRingInit(&ep->ring); + 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)); + XhciRingInit(&ep->ring); + 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); +} + +Bool XhciInit() +{//Find + init first xHCI controller. Returns TRUE on success. + I64 bdf, bar_lo, bar_hi, addr, i; + I64 hcs1, hcs2, hcc, cmd; + F64 timeout; + U8 *page; + + bdf = PCIClassFind(XHCI_PCI_CLASS, 0); + if (bdf == -1) + { + "xHCI: no controller found.\n"; + return FALSE; + } + 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); + if (bar_hi) + { + "xHCI: MMIO above 4GB not supported.\n"; + return FALSE; + } + addr = bar_lo & ~0xF; + if (!addr) + { + "xHCI: BAR not assigned.\n"; + return FALSE; + } +//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); + + 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); + + usb_xhci.present = TRUE; + usb_xhci.running = TRUE; + "xHCI: %d ports, %d slots, ctx size %d, MMIO 0x%X.\n", + usb_xhci.max_ports, usb_xhci.max_slots, usb_xhci.ctx_size, bar_lo & ~0xF; + + return TRUE; +} From ace4434c12420afba69e06f1d373f037c32045f7 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:31:53 +0900 Subject: [PATCH 02/26] Refactor USB endpoint handling to use auto_kind instead of auto_cb This update modifies the USB stack to replace the function pointer auto_cb with an integer auto_kind for handling HID report callbacks. Changes include: - Updated endpoint descriptor structure to use auto_kind for specifying report handler types (USB_AUTO_NONE, USB_AUTO_KBD, USB_AUTO_MOUSE). - Adjusted relevant functions to accommodate this change, enhancing clarity and maintainability of the code. This refactor streamlines the dispatching of report handlers in the USB HID implementation. --- src/System/Usb/Usb.ZC | 4 ++-- src/System/Usb/UsbDefs.ZC | 18 +++++++++++++++--- src/System/Usb/UsbHid.ZC | 4 ++-- src/System/Usb/Xhci.ZC | 18 ++++++++++++++---- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/System/Usb/Usb.ZC b/src/System/Usb/Usb.ZC index f1e96bfaa..1059884d5 100644 --- a/src/System/Usb/Usb.ZC +++ b/src/System/Usb/Usb.ZC @@ -116,7 +116,7 @@ U0 UsbParseConfig(CUsbDev *dev, U8 *buf, I64 len) if (epd->addr & 0x80) ep->dci |= 1; ep->valid = TRUE; - ep->auto_cb = NULL; + ep->auto_kind = USB_AUTO_NONE; cur_if->ep_idxs[cur_if->ep_count] = dev->ep_count; cur_if->ep_count++; dev->ep_count++; @@ -143,7 +143,7 @@ U0 UsbDevDetachCore(CUsbDev *dev) for (i = 0; i < USB_EPS_MAX; i++) { dev->eps[i].valid = FALSE; - dev->eps[i].auto_cb = NULL; + dev->eps[i].auto_kind = USB_AUTO_NONE; } dev->drv_data = NULL; } diff --git a/src/System/Usb/UsbDefs.ZC b/src/System/Usb/UsbDefs.ZC index 9ce582ee9..953ab2af3 100644 --- a/src/System/Usb/UsbDefs.ZC +++ b/src/System/Usb/UsbDefs.ZC @@ -216,6 +216,16 @@ class CUsbEPDesc #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; @@ -241,10 +251,12 @@ class CUsbEP Bool xfer_done; I64 xfer_code, //xHCI completion code xfer_residue; //untransferred bytes -//Async auto-requeue (HID interrupt IN) +//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; - U0 (*auto_cb)(U8 *dev, U8 *ep); //really (CUsbDev*,CUsbEP*); report in auto_buf + I64 auto_len, + auto_kind; CXhciTRB *pending_trb; }; diff --git a/src/System/Usb/UsbHid.ZC b/src/System/Usb/UsbHid.ZC index 3748bab58..e2c4f8340 100644 --- a/src/System/Usb/UsbHid.ZC +++ b/src/System/Usb/UsbHid.ZC @@ -229,12 +229,12 @@ U0 UsbHidAttach(CUsbDev *dev, CUsbIf *uif) if (uif->if_proto == USB_HID_PROTO_KBD) { ep->auto_len = 8; - ep->auto_cb = &UsbKbdCB; + ep->auto_kind = USB_AUTO_KBD; SysLog("USB: HID keyboard on port %d.\n", dev->port); } else { - ep->auto_cb = &UsbMouseCB; + ep->auto_kind = USB_AUTO_MOUSE; mouse_hard.installed = TRUE; //menu bar + cursor paths check this SysLog("USB: HID mouse on port %d.\n", dev->port); } diff --git a/src/System/Usb/Xhci.ZC b/src/System/Usb/Xhci.ZC index 9aec2af24..2f9a7a895 100644 --- a/src/System/Usb/Xhci.ZC +++ b/src/System/Usb/Xhci.ZC @@ -143,7 +143,7 @@ CUsbEP *UsbDevEPByDCI(CUsbDev *dev, I64 dci) } U0 XhciIntQueue(CUsbDev *dev, CUsbEP *ep) -{//Queue one interrupt IN TRB on ep, report delivered via ep->auto_cb. +{//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); @@ -188,10 +188,20 @@ U0 XhciEventPoll() ep = UsbDevEPByDCI(dev, dci); if (ep) { - if (ep->auto_cb) + if (ep->auto_kind) {//HID interrupt IN: deliver + requeue if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) - (*ep->auto_cb)(dev, ep); + { + switch (ep->auto_kind) + { + case USB_AUTO_KBD: + UsbKbdCB(dev, ep); + break; + case USB_AUTO_MOUSE: + UsbMouseCB(dev, ep); + break; + } + } XhciIntQueue(dev, ep); } else @@ -414,7 +424,7 @@ I64 XhciAddressDev(CUsbDev *dev) XhciRingInit(&dev->ep0.ring); dev->ep0.dci = 1; dev->ep0.valid = TRUE; - dev->ep0.auto_cb = NULL; + dev->ep0.auto_kind = USB_AUTO_NONE; switch (dev->speed) { From 96b40847014bfd64ed3c252010acc6f3fcf84bae Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:56:22 +0900 Subject: [PATCH 03/26] Refactor CUsbEP to use pointer for CXhciRing This update modifies the CUsbEP class to store a pointer to CXhciRing instead of embedding it directly. This change addresses limitations in the ZealC JIT class parser regarding nested class structures. Additionally, new initialization functions for endpoint rings have been introduced, ensuring proper memory allocation and initialization on first use. The code has been updated accordingly to reflect these changes, enhancing the overall structure and maintainability of the USB endpoint handling. --- src/System/Usb/UsbDefs.ZC | 5 ++++- src/System/Usb/Xhci.ZC | 31 ++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/System/Usb/UsbDefs.ZC b/src/System/Usb/UsbDefs.ZC index 953ab2af3..24e624692 100644 --- a/src/System/Usb/UsbDefs.ZC +++ b/src/System/Usb/UsbDefs.ZC @@ -240,7 +240,10 @@ class CXhciRing class CUsbEP { - CXhciRing ring; +//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 diff --git a/src/System/Usb/Xhci.ZC b/src/System/Usb/Xhci.ZC index 2f9a7a895..5d90c6302 100644 --- a/src/System/Usb/Xhci.ZC +++ b/src/System/Usb/Xhci.ZC @@ -82,6 +82,15 @@ U0 XhciRingInit(CXhciRing *ring) 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; @@ -145,7 +154,7 @@ CUsbEP *UsbDevEPByDCI(CUsbDev *dev, I64 dci) 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, + 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; @@ -291,7 +300,7 @@ I64 XhciCtrlXfer(CUsbDev *dev, I64 rt, I64 rq, I64 val, I64 idx, U8 *buf, I64 le 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, + XhciRingEnq(ep->ring, rt | (rq << 8) | (val << 16), idx | (len << 16), 8, (XHCI_TRB_SETUP << 10) | XHCI_TRBF_IDT | trt); if (len) { @@ -299,12 +308,12 @@ I64 XhciCtrlXfer(CUsbDev *dev, I64 rt, I64 rq, I64 val, I64 idx, U8 *buf, I64 le if (rt & USB_RT_DEV_TO_HOST) ctl |= XHCI_TRBF_DIR_IN; addr = buf; - XhciRingEnq(&ep->ring, addr & 0xFFFFFFFF, addr >> 32, len, ctl); + 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); + XhciRingEnq(ep->ring, 0, 0, 0, ctl); usb_xhci.db[dev->slot] = 1; XhciUnlock; @@ -319,7 +328,7 @@ I64 XhciBulkXfer(CUsbDev *dev, CUsbEP *ep, U8 *buf, I64 len, F64 timeout_s, I64 ep->xfer_done = FALSE; ep->xfer_code = XHCI_CC_TIMEOUT; ep->xfer_residue = 0; - XhciRingEnq(&ep->ring, addr & 0xFFFFFFFF, addr >> 32, len, + 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; @@ -421,7 +430,7 @@ I64 XhciAddressDev(CUsbDev *dev) 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); - XhciRingInit(&dev->ep0.ring); + XhciEPRingInit(&dev->ep0); dev->ep0.dci = 1; dev->ep0.valid = TRUE; dev->ep0.auto_kind = USB_AUTO_NONE; @@ -448,7 +457,7 @@ I64 XhciAddressDev(CUsbDev *dev) 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; + addr = dev->ep0.ring->trbs; ep0_ctx[2] = (addr & 0xFFFFFFFF) | 1; //DCS=1 ep0_ctx[3] = addr >> 32; ep0_ctx[4] = 8; @@ -494,7 +503,7 @@ I64 XhciConfigureEPs(CUsbDev *dev) ep = &dev->eps[i]; if (!ep->valid) goto next_ep; - XhciRingInit(&ep->ring); + XhciEPRingInit(ep); icc[1] |= 1 << ep->dci; if (ep->dci > max_dci) max_dci = ep->dci; @@ -503,7 +512,7 @@ I64 XhciConfigureEPs(CUsbDev *dev) ival = XhciEPInterval(dev, ep); ep_ctx[0] = ival << 16; ep_ctx[1] = (3 << 1) | (type << 3) | (ep->max_packet << 16); - addr = ep->ring.trbs; + addr = ep->ring->trbs; ep_ctx[2] = (addr & 0xFFFFFFFF) | 1; ep_ctx[3] = addr >> 32; if ((ep->attr & 3) == USB_EP_TYPE_INT) @@ -526,8 +535,8 @@ I64 XhciResetEP(CUsbDev *dev, CUsbEP *ep) I64 code, addr; code = XhciCmd(0, 0, 0, (XHCI_TRB_RESET_EP << 10) | (ep->dci << 16) | (dev->slot << 24)); - XhciRingInit(&ep->ring); - addr = ep->ring.trbs; + 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)); From bc2b7ba3308a6565bdd32f33c3d55b66cbc53679 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:14:07 +0900 Subject: [PATCH 04/26] Refactor USB endpoint management to use ep_list instead of eps This update modifies the USB device structure and related functions to replace the eps array with ep_list for managing USB endpoints. Changes include updating references in the parsing, attachment, and configuration functions to ensure consistency and improve code clarity. This refactor enhances maintainability and aligns with recent structural changes in the USB stack. --- src/System/Usb/Usb.ZC | 6 +++--- src/System/Usb/UsbDefs.ZC | 2 +- src/System/Usb/UsbHid.ZC | 2 +- src/System/Usb/UsbMsd.ZC | 2 +- src/System/Usb/Xhci.ZC | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/System/Usb/Usb.ZC b/src/System/Usb/Usb.ZC index 1059884d5..69c1033cd 100644 --- a/src/System/Usb/Usb.ZC +++ b/src/System/Usb/Usb.ZC @@ -105,7 +105,7 @@ U0 UsbParseConfig(CUsbDev *dev, U8 *buf, I64 len) ttype = epd->attr & 3; if (ttype == USB_EP_TYPE_BULK || ttype == USB_EP_TYPE_INT) { - ep = &dev->eps[dev->ep_count]; + 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; @@ -142,8 +142,8 @@ U0 UsbDevDetachCore(CUsbDev *dev) dev->ep0.valid = FALSE; for (i = 0; i < USB_EPS_MAX; i++) { - dev->eps[i].valid = FALSE; - dev->eps[i].auto_kind = USB_AUTO_NONE; + dev->ep_list[i].valid = FALSE; + dev->ep_list[i].auto_kind = USB_AUTO_NONE; } dev->drv_data = NULL; } diff --git a/src/System/Usb/UsbDefs.ZC b/src/System/Usb/UsbDefs.ZC index 24e624692..1cb8d4a0a 100644 --- a/src/System/Usb/UsbDefs.ZC +++ b/src/System/Usb/UsbDefs.ZC @@ -280,7 +280,7 @@ class CUsbDev U8 *dev_ctx, *in_ctx; CUsbEP ep0; - CUsbEP eps[USB_EPS_MAX]; + CUsbEP ep_list[USB_EPS_MAX]; I64 ep_count; CUsbIf ifs[USB_IFS_MAX]; I64 if_count; diff --git a/src/System/Usb/UsbHid.ZC b/src/System/Usb/UsbHid.ZC index e2c4f8340..df8861893 100644 --- a/src/System/Usb/UsbHid.ZC +++ b/src/System/Usb/UsbHid.ZC @@ -190,7 +190,7 @@ U0 UsbHidAttach(CUsbDev *dev, CUsbIf *uif) for (i = 0; i < uif->ep_count; i++) { - tmp = &dev->eps[uif->ep_idxs[i]]; + tmp = &dev->ep_list[uif->ep_idxs[i]]; if ((tmp->addr & 0x80) && (tmp->attr & 3) == USB_EP_TYPE_INT) { ep = tmp; diff --git a/src/System/Usb/UsbMsd.ZC b/src/System/Usb/UsbMsd.ZC index 4bc404bed..0d15c1aa3 100644 --- a/src/System/Usb/UsbMsd.ZC +++ b/src/System/Usb/UsbMsd.ZC @@ -291,7 +291,7 @@ U0 UsbMsdAttach(CUsbDev *dev, CUsbIf *uif) msd->if_num = uif->if_num; for (i = 0; i < uif->ep_count; i++) { - ep = &dev->eps[uif->ep_idxs[i]]; + ep = &dev->ep_list[uif->ep_idxs[i]]; if ((ep->attr & 3) == USB_EP_TYPE_BULK) { if (ep->addr & 0x80) diff --git a/src/System/Usb/Xhci.ZC b/src/System/Usb/Xhci.ZC index 5d90c6302..afb5655eb 100644 --- a/src/System/Usb/Xhci.ZC +++ b/src/System/Usb/Xhci.ZC @@ -145,8 +145,8 @@ CUsbEP *UsbDevEPByDCI(CUsbDev *dev, I64 dci) if (dci == 1) return &dev->ep0; for (i = 0; i < dev->ep_count; i++) - if (dev->eps[i].valid && dev->eps[i].dci == dci) - return &dev->eps[i]; + if (dev->ep_list[i].valid && dev->ep_list[i].dci == dci) + return &dev->ep_list[i]; return NULL; } @@ -500,7 +500,7 @@ I64 XhciConfigureEPs(CUsbDev *dev) icc[1] = 1; //A0 = slot for (i = 0; i < dev->ep_count; i++) { - ep = &dev->eps[i]; + ep = &dev->ep_list[i]; if (!ep->valid) goto next_ep; XhciEPRingInit(ep); From 2f69b00603af5ea24eb843e29914645fe1e6c490 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:19:14 +0900 Subject: [PATCH 05/26] Fix Mounting USB Drives --- src/Kernel/BlkDev/DiskDrive.ZC | 2 +- src/Kernel/KDefine.ZC | 3 ++- src/System/Usb/UsbHid.ZC | 36 +++++++++++++++++++++++----------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Kernel/BlkDev/DiskDrive.ZC b/src/Kernel/BlkDev/DiskDrive.ZC index 4975522da..55ff72c9d 100755 --- a/src/Kernel/BlkDev/DiskDrive.ZC +++ b/src/Kernel/BlkDev/DiskDrive.ZC @@ -349,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/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/System/Usb/UsbHid.ZC b/src/System/Usb/UsbHid.ZC index df8861893..89880211e 100644 --- a/src/System/Usb/UsbHid.ZC +++ b/src/System/Usb/UsbHid.ZC @@ -20,6 +20,9 @@ class CUsbHid 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 @@ -151,16 +154,22 @@ U0 UsbMouseCB(U8 *d, U8 *e) dz -= 256; } - mouse_hard.timestamp = TSCGet; - MouseHardSetPre; - 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.prescale.x += dx; - mouse_hard.prescale.y += dy; - mouse_hard.prescale.z += dz; - mouse.show = TRUE; - MouseHardSetPost; +//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() @@ -235,7 +244,12 @@ U0 UsbHidAttach(CUsbDev *dev, CUsbIf *uif) else { ep->auto_kind = USB_AUTO_MOUSE; - mouse_hard.installed = TRUE; //menu bar + cursor paths check this +//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); From a73013f453f7abfacbeb7ad4b43d52b85e24f635 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 02:01:26 +0900 Subject: [PATCH 06/26] Add USB support for booting and mass storage integration This commit enhances the USB stack by integrating USB boot functionality and mass storage support. Key changes include: - Inclusion of USB boot initialization and fallback mechanisms. - Implementation of USB mass storage device handling with SCSI commands. - Updates to the block device management to accommodate USB drives. - Refactoring of existing USB endpoint management to improve clarity and maintainability. These enhancements significantly expand the system's capabilities for handling USB peripherals and booting from USB media. --- src/Kernel/BlkDev/DiskAddDev.ZC | 35 ++++++++-- src/Kernel/KExterns.ZC | 2 + src/Kernel/KMain.ZC | 2 + src/Kernel/Kernel.PRJ | 1 + .../Usb/UsbDefs.ZC => Kernel/Usb/KUsb.HH} | 23 +++++-- .../Usb/MakeUsb.ZC => Kernel/Usb/KUsbBoot.ZC} | 66 +++++++++++++------ .../Usb/Usb.ZC => Kernel/Usb/KUsbCore.ZC} | 0 .../Usb/UsbHid.ZC => Kernel/Usb/KUsbHid.ZC} | 0 .../Usb/UsbMsd.ZC => Kernel/Usb/KUsbMsd.ZC} | 65 +++++++++--------- .../Usb/Xhci.ZC => Kernel/Usb/KXhci.ZC} | 16 ++++- src/Kernel/Usb/MakeKUsb.ZC | 8 +++ src/StartOS.ZC | 1 + src/System/BlkDev/Mount.ZC | 14 +++- src/System/Boot/BootHDIns.ZC | 1 + src/System/Boot/BootMHDIns.ZC | 10 +-- src/System/Boot/LimineMHDIns.ZC | 4 +- src/System/MakeSystem.ZC | 1 - 17 files changed, 171 insertions(+), 78 deletions(-) rename src/{System/Usb/UsbDefs.ZC => Kernel/Usb/KUsb.HH} (91%) rename src/{System/Usb/MakeUsb.ZC => Kernel/Usb/KUsbBoot.ZC} (65%) rename src/{System/Usb/Usb.ZC => Kernel/Usb/KUsbCore.ZC} (100%) rename src/{System/Usb/UsbHid.ZC => Kernel/Usb/KUsbHid.ZC} (100%) rename src/{System/Usb/UsbMsd.ZC => Kernel/Usb/KUsbMsd.ZC} (86%) rename src/{System/Usb/Xhci.ZC => Kernel/Usb/KXhci.ZC} (97%) create mode 100644 src/Kernel/Usb/MakeKUsb.ZC diff --git a/src/Kernel/BlkDev/DiskAddDev.ZC b/src/Kernel/BlkDev/DiskAddDev.ZC index b4b2a99e2..30696061c 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -30,8 +30,9 @@ U0 BlkDevLockFwdingSet(CBlkDev *bd) } } -Bool BlkDevAddBlksRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) -{//Partition-scan read during $LK,"BlkDevAdd",A="MN:BlkDevAdd"$(), before drive is validated. +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) @@ -41,6 +42,17 @@ Bool BlkDevAddBlksRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) return AHCIAtaBlksRead(bd, buf, blk, count); } +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. @@ -95,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. - BlkDevAddBlksRead(bd, &mbr, 0, 1); + BlkDevRawBlksRead(bd, &mbr, 0, 1); break; } offset = 0; ext_base = INVALID_CLUS; while (prt_num < 0 || num <= prt_num) { - BlkDevAddBlksRead(bd, &mbr, offset, 1); + BlkDevRawBlksRead(bd, &mbr, offset, 1); if (mbr.signature != 0xAA55) break; j = -1; @@ -133,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: - BlkDevAddBlksRead(bd, &br, drive->drv_offset, 1); + BlkDevRawBlksRead(bd, &br, drive->drv_offset, 1); drive->drive_signature = DRIVE_SIGNATURE_VAL; drive->prt_num = num; @@ -250,9 +262,12 @@ U0 BlkDevsInitAll() "\nAHCIInit;\n"; AHCIInit; - if (!blkdev.ahci_hba) + "\nUsbBootInit;\n"; + i = UsbBootInit; + + if (!blkdev.ahci_hba && !i) { - "\nZealOS requires AHCI.\n\n" + "\nZealOS requires AHCI or a USB boot drive.\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"; @@ -260,11 +275,17 @@ U0 BlkDevsInitAll() Reboot; } +//Config mount code may reference missing hardware (e.g. DVD-configured kernel +//booted from a USB stick); failures fall through to UsbBootDriveFallback(). #exe { + StreamPrint("try {"); 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);}"); + StreamPrint("} catch { Fs->catch_except = TRUE; \"Boot mount code failed.\\n\"; }"); }; + + UsbBootDriveFallback; } 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/KMain.ZC b/src/Kernel/KMain.ZC index 8b1df95de..9b0bb6e92 100755 --- a/src/Kernel/KMain.ZC +++ b/src/Kernel/KMain.ZC @@ -217,6 +217,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/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/System/Usb/UsbDefs.ZC b/src/Kernel/Usb/KUsb.HH similarity index 91% rename from src/System/Usb/UsbDefs.ZC rename to src/Kernel/Usb/KUsb.HH index 1cb8d4a0a..a4e62ce80 100644 --- a/src/System/Usb/UsbDefs.ZC +++ b/src/Kernel/Usb/KUsb.HH @@ -1,5 +1,7 @@ -//USB + xHCI definitions. -//ZealOS USB stack: xHCI host controller, HID boot keyboard/mouse, mass storage. +//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" @@ -306,7 +308,8 @@ class CXhciGlobals CXhciRing cmd_ring; CXhciTRB *evt_ring; I64 evt_deq, evt_cycle; - I64 lock; //ring enqueue lock + 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; @@ -315,7 +318,13 @@ class CXhciGlobals I64 pci_bus, pci_dev, pci_fun; }; -CXhciGlobals usb_xhci; -CUsbDev usb_devs[USB_DEVS_MAX]; -CTask *usb_task = NULL; -Bool usb_verbose = FALSE; +//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); diff --git a/src/System/Usb/MakeUsb.ZC b/src/Kernel/Usb/KUsbBoot.ZC similarity index 65% rename from src/System/Usb/MakeUsb.ZC rename to src/Kernel/Usb/KUsbBoot.ZC index f17eaa43e..44a24375e 100644 --- a/src/System/Usb/MakeUsb.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -1,11 +1,5 @@ -//USB stack build + boot task. -Cd(__DIR__);; -#include "UsbDefs" -#include "Xhci" -#include "Usb" -#include "UsbHid" -#include "UsbMsd" -Cd("..");; +//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. @@ -121,14 +115,13 @@ public U0 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; - if (!XhciInit) + usb_task = Fs; + if (!usb_xhci.running) return; - Sleep(100); //let port connect status settle after reset - UsbPortScan; while (TRUE) { XhciEventPoll; @@ -143,13 +136,46 @@ U0 UsbTaskMain(I64 dummy=0) } } -U0 UsbInit() -{ - MemSet(&usb_xhci, 0, sizeof(CXhciGlobals)); - MemSet(usb_devs, 0, sizeof(CUsbDev) * USB_DEVS_MAX); - MemSet(usb_hids, 0, sizeof(CUsbHid) * USB_HIDS_MAX); - MemSet(usb_msds, 0, sizeof(CUsbMsd) * USB_MSDS_MAX); - usb_task = Spawn(&UsbTaskMain, NULL, "USB"); +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; } -UsbInit; +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); + if (drive && drive->drive_signature == DRIVE_SIGNATURE_VAL) + { + 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/System/Usb/Usb.ZC b/src/Kernel/Usb/KUsbCore.ZC similarity index 100% rename from src/System/Usb/Usb.ZC rename to src/Kernel/Usb/KUsbCore.ZC diff --git a/src/System/Usb/UsbHid.ZC b/src/Kernel/Usb/KUsbHid.ZC similarity index 100% rename from src/System/Usb/UsbHid.ZC rename to src/Kernel/Usb/KUsbHid.ZC diff --git a/src/System/Usb/UsbMsd.ZC b/src/Kernel/Usb/KUsbMsd.ZC similarity index 86% rename from src/System/Usb/UsbMsd.ZC rename to src/Kernel/Usb/KUsbMsd.ZC index 0d15c1aa3..14e75cef1 100644 --- a/src/System/Usb/UsbMsd.ZC +++ b/src/Kernel/Usb/KUsbMsd.ZC @@ -245,31 +245,15 @@ Bool UsbMsdBlksWrite(CBlkDev *bd, U8 *buf, I64 blk, I64 count) return UsbMsdBlksRW(bd, buf, blk, count, TRUE); } -Bool UsbMsdKernelHooksSet() -{//Resolve kernel USB hooks at runtime so an old Kernel.ZXE degrades gracefully -//(HID still works, storage disabled) instead of hard-failing the System compile. - CHashGlobalVar *r, *w; - U64 *p; - - r = HashFind("fp_usb_msd_blks_read", Fs->hash_table, HTT_GLOBAL_VAR); - w = HashFind("fp_usb_msd_blks_write", Fs->hash_table, HTT_GLOBAL_VAR); - if (!r || !w || !r->data_addr || !w->data_addr) - return FALSE; - p = r->data_addr; - *p = &UsbMsdBlksRead; - p = w->data_addr; - *p = &UsbMsdBlksWrite; - - return TRUE; -} - U0 UsbMsdAttach(CUsbDev *dev, CUsbIf *uif) {//Bind a Bulk-Only SCSI mass storage interface, mount partitions. - CUsbMsd *msd = NULL; - CUsbEP *ep; - CBlkDev *bd; - U8 inq[40]; - I64 i, tries, res, msd_idx = 0; + CUsbMsd *msd = NULL; + CUsbEP *ep; + CBlkDev *bd; + CDrive *drive; + CRedSeaBoot *rsb; + U8 inq[40]; + I64 i, tries, res, msd_idx = 0, iso_off, is_iso = FALSE; if (uif->if_proto != USB_MSD_PROTO_BOT) { @@ -340,19 +324,40 @@ U0 UsbMsdAttach(CUsbDev *dev, CUsbIf *uif) } msd->ready = TRUE; - if (!UsbMsdKernelHooksSet) - { - SysLog("USB: MSD port %d: kernel lacks USB block hooks (rebuild kernel); storage not mounted.\n", - dev->port); - return; - } + 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; - res = BlkDevAdd(bd, , FALSE, FALSE); + +//Install-media probe: a ZealOS distro ISO written raw to a stick carries a +//RedSea filesystem at the fixed DVD-image offset (same one MountFile uses). +//If found, mount whole-drive read-only so the OS can boot/install from USB. + iso_off = 19 << 2 + (DVD_BLK_SIZE * 2 + DVD_BOOT_LOADER_SIZE) / BLK_SIZE; + rsb = msd->bounce; + if (msd->max_lba > iso_off && UsbMsdBlksRead(bd, rsb, iso_off, 1)) + if (rsb->signature == MBR_PT_REDSEA && rsb->signature2 == 0xAA55) + is_iso = TRUE; + if (is_iso) + { + bd->drv_offset = iso_off; + bd->flags |= BDF_READ_ONLY; + res = BlkDevAdd(bd, , TRUE, FALSE); + if (res) + { + drive = Letter2Drive(msd->first_drive_let, FALSE); + if (drive && RedSeaValidate(msd->first_drive_let)) + RedSeaInit(drive); + usb_boot_iso_drv_let = msd->first_drive_let; + SysLog("USB: MSD port %d: ZealOS install media, RedSea drive %C:.\n", + dev->port, msd->first_drive_let); + } + } + else + res = BlkDevAdd(bd, , FALSE, FALSE); if (res) SysLog("USB: MSD port %d: \"%s\" %d MB mounted, %d partition(s), first drive %C:.\n", dev->port, &inq[8], ((msd->max_lba + 1) * BLK_SIZE) / 0x100000, res, diff --git a/src/System/Usb/Xhci.ZC b/src/Kernel/Usb/KXhci.ZC similarity index 97% rename from src/System/Usb/Xhci.ZC rename to src/Kernel/Usb/KXhci.ZC index afb5655eb..d414ff07e 100644 --- a/src/System/Usb/Xhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -1,6 +1,13 @@ //xHCI (USB3) host controller driver. Polled, no MSI. -//All USB commands are issued from the USB task only. -//Sync transfers may be awaited from any task (event poll runs in USB task). +//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) { @@ -170,6 +177,8 @@ U0 XhciEventPoll() if (!usb_xhci.running) return; + while (LBts(&usb_xhci.poll_lock, 0)) + Yield; while (TRUE) { e = &usb_xhci.evt_ring[usb_xhci.evt_deq]; @@ -240,6 +249,7 @@ U0 XhciEventPoll() 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) @@ -272,7 +282,7 @@ I64 XhciXferWait(CUsbEP *ep, F64 timeout_s) while (!ep->xfer_done && tS < timeout) { - if (Fs == usb_task) + if (Fs == usb_task || !usb_task) XhciEventPoll; Yield; } 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/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/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..b22396e3d 100755 --- a/src/System/Boot/BootMHDIns.ZC +++ b/src/System/Boot/BootMHDIns.ZC @@ -29,7 +29,7 @@ public U0 BootMHDOldRead(U8 src_drive, U8 dst_drive, I64 size=1) { //Bypass partition bounds-checking BlkDevLock(bd); - AHCIAtaBlksRead(bd, mbr, 0, size); + BlkDevRawBlksRead(bd, mbr, 0, size); BlkDevUnlock(bd); Drive(dst_drive); @@ -52,7 +52,7 @@ public U0 BootMHDOldWrite(U8 src_drive, U8 dst_drive) { //Bypass partition bounds-checking BlkDevLock(bd); - AHCIAtaBlksWrite(bd, mbr, 0, size / BLK_SIZE); + BlkDevRawBlksWrite(bd, mbr, 0, size / BLK_SIZE); BlkDevUnlock(bd); } Free(mbr); @@ -72,7 +72,7 @@ public U0 BootMHDZero(U8 dst_drive) MemSet(&mbr, 0, BLK_SIZE); //Bypass partition bounds-checking BlkDevLock(bd); - AHCIAtaBlksWrite(bd, &mbr, 0, 1); + BlkDevRawBlksWrite(bd, &mbr, 0, 1); BlkDevUnlock(bd); } @@ -157,7 +157,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); + BlkDevRawBlksRead(bd, &mbr, 0, 1); for (i = 0; i < BMHD_END - BMHD_CODE; i++) mbr.code[i] = BMHD_CODE(U8 *)[i]; @@ -173,7 +173,7 @@ public Bool BootMHDIns(U8 drv_let, U8 *drv_list=NULL) mbr.zero = 0; mbr.signature = 0xAA55; - AHCIAtaBlksWrite(bd, &mbr, 0, 1); + BlkDevRawBlksWrite(bd, &mbr, 0, 1); BlkDevUnlock(bd); res = TRUE; diff --git a/src/System/Boot/LimineMHDIns.ZC b/src/System/Boot/LimineMHDIns.ZC index 7f18236b4..e583c6c39 100755 --- a/src/System/Boot/LimineMHDIns.ZC +++ b/src/System/Boot/LimineMHDIns.ZC @@ -87,13 +87,13 @@ Bool LimineMHDIns(U8 drv_let) BlkDevLock(bd); - AHCIAtaBlksRead(bd, &old_mbr, 0, 1); // read old MBR currently on disk + BlkDevRawBlksRead(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 + BlkDevRawBlksWrite(bd, limine, 0, 63); // write Limine to MBR and post-MBR gap BlkDevUnlock(bd); diff --git a/src/System/MakeSystem.ZC b/src/System/MakeSystem.ZC index 70a5b2de9..c62ccd7d6 100755 --- a/src/System/MakeSystem.ZC +++ b/src/System/MakeSystem.ZC @@ -25,5 +25,4 @@ LBts(&sys_run_level, RLf_DOC); #include "Define" #include "WallPaper" #include "Mouse" -#include "Usb/MakeUsb" Cd("..");; From e838db5e55da36a6323094464d5f6a270a788719 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 03:26:34 +0900 Subject: [PATCH 07/26] Enhance block device management with runtime-dispatched raw read/write functions This commit introduces runtime-dispatched functions for raw block read and write operations, allowing compatibility with both USB-capable and pre-USB bootstrap kernels. Key changes include: - Added `DiskPartRawWrite` and `BootMHDRawRead` functions to facilitate whole-device operations. - Updated existing code to utilize these new functions for improved maintainability and clarity. - Enhanced USB and boot loader integration by ensuring proper handling of raw block operations. These modifications streamline the block device management process and improve the overall robustness of the system. --- src/Kernel/BlkDev/DiskAddDev.ZC | 4 +- src/Kernel/BlkDev/DiskFormat.ZC | 5 +- src/Kernel/Usb/KUsbBoot.ZC | 5 +- src/Kernel/Usb/KUsbMsd.ZC | 94 ++++++++++++++++++++++++--------- src/System/BlkDev/DiskPart.ZC | 16 ++++-- src/System/Boot/BootMHDIns.ZC | 31 +++++++++-- src/System/Boot/LimineMHDIns.ZC | 22 +++++++- zealbooter/src/lib/print.c | 14 +++-- 8 files changed, 146 insertions(+), 45 deletions(-) diff --git a/src/Kernel/BlkDev/DiskAddDev.ZC b/src/Kernel/BlkDev/DiskAddDev.ZC index 30696061c..c73436742 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -30,7 +30,7 @@ U0 BlkDevLockFwdingSet(CBlkDev *bd) } } -Bool BlkDevRawBlksRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) +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) @@ -42,7 +42,7 @@ Bool BlkDevRawBlksRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) return AHCIAtaBlksRead(bd, buf, blk, count); } -Bool BlkDevRawBlksWrite(CBlkDev *bd, U8 *buf, I64 blk, I64 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) { 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/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC index 44a24375e..54684faac 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -171,7 +171,10 @@ U0 UsbBootDriveFallback() if (usb_msds[i].valid && usb_msds[i].ready && usb_msds[i].bd) { drive = Letter2Drive(usb_msds[i].first_drive_let, FALSE); - if (drive && drive->drive_signature == DRIVE_SIGNATURE_VAL) +//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; diff --git a/src/Kernel/Usb/KUsbMsd.ZC b/src/Kernel/Usb/KUsbMsd.ZC index 14e75cef1..5e293a257 100644 --- a/src/Kernel/Usb/KUsbMsd.ZC +++ b/src/Kernel/Usb/KUsbMsd.ZC @@ -251,9 +251,9 @@ U0 UsbMsdAttach(CUsbDev *dev, CUsbIf *uif) CUsbEP *ep; CBlkDev *bd; CDrive *drive; - CRedSeaBoot *rsb; + CMasterBoot *mbr; U8 inq[40]; - I64 i, tries, res, msd_idx = 0, iso_off, is_iso = FALSE; + I64 i, tries, res, msd_idx = 0, is_iso = FALSE, partitioned = FALSE; if (uif->if_proto != USB_MSD_PROTO_BOT) { @@ -333,39 +333,81 @@ U0 UsbMsdAttach(CUsbDev *dev, CUsbIf *uif) bd->port_num = msd_idx; msd->bd = bd; -//Install-media probe: a ZealOS distro ISO written raw to a stick carries a -//RedSea filesystem at the fixed DVD-image offset (same one MountFile uses). -//If found, mount whole-drive read-only so the OS can boot/install from USB. - iso_off = 19 << 2 + (DVD_BLK_SIZE * 2 + DVD_BOOT_LOADER_SIZE) / BLK_SIZE; - rsb = msd->bounce; - if (msd->max_lba > iso_off && UsbMsdBlksRead(bd, rsb, iso_off, 1)) - if (rsb->signature == MBR_PT_REDSEA && rsb->signature2 == 0xAA55) +//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) { - bd->drv_offset = iso_off; - bd->flags |= BDF_READ_ONLY; - res = BlkDevAdd(bd, , TRUE, FALSE); - if (res) +//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)) { - drive = Letter2Drive(msd->first_drive_let, FALSE); - if (drive && RedSeaValidate(msd->first_drive_let)) - RedSeaInit(drive); + res = 1; usb_boot_iso_drv_let = msd->first_drive_let; - SysLog("USB: MSD port %d: ZealOS install media, RedSea drive %C:.\n", - dev->port, 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 - res = BlkDevAdd(bd, , FALSE, FALSE); - if (res) - SysLog("USB: MSD port %d: \"%s\" %d MB mounted, %d partition(s), first drive %C:.\n", - dev->port, &inq[8], ((msd->max_lba + 1) * BLK_SIZE) / 0x100000, res, - msd->first_drive_let); else { - SysLog("USB: MSD port %d: no mountable partitions.\n", dev->port); - msd->bd = NULL; +//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); + } } } diff --git a/src/System/BlkDev/DiskPart.ZC b/src/System/BlkDev/DiskPart.ZC index be9310c20..e7e78e31d 100755 --- a/src/System/BlkDev/DiskPart.ZC +++ b/src/System/BlkDev/DiskPart.ZC @@ -1,5 +1,15 @@ #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. + CHashFun *f = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN); + + if (f) + return CallInd(f->exe_addr, bd, buf, blk, count); + return AHCIAtaBlksWrite(bd, buf, blk, count); +} + #define ROUND_DRIVE_TO (63 * 255) #define DRIVE_HEADER 63 @@ -40,7 +50,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 +140,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 +169,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/Boot/BootMHDIns.ZC b/src/System/Boot/BootMHDIns.ZC index b22396e3d..c253fb300 100755 --- a/src/System/Boot/BootMHDIns.ZC +++ b/src/System/Boot/BootMHDIns.ZC @@ -12,6 +12,27 @@ //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. + CHashFun *f = HashFind("BlkDevRawBlksRead", Fs->hash_table, HTT_FUN); + + if (f) + return CallInd(f->exe_addr, 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"$(). + CHashFun *f = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN); + + if (f) + return CallInd(f->exe_addr, 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 +50,7 @@ public U0 BootMHDOldRead(U8 src_drive, U8 dst_drive, I64 size=1) { //Bypass partition bounds-checking BlkDevLock(bd); - BlkDevRawBlksRead(bd, mbr, 0, size); + BootMHDRawRead(bd, mbr, 0, size); BlkDevUnlock(bd); Drive(dst_drive); @@ -52,7 +73,7 @@ public U0 BootMHDOldWrite(U8 src_drive, U8 dst_drive) { //Bypass partition bounds-checking BlkDevLock(bd); - BlkDevRawBlksWrite(bd, mbr, 0, size / BLK_SIZE); + BootMHDRawWrite(bd, mbr, 0, size / BLK_SIZE); BlkDevUnlock(bd); } Free(mbr); @@ -72,7 +93,7 @@ public U0 BootMHDZero(U8 dst_drive) MemSet(&mbr, 0, BLK_SIZE); //Bypass partition bounds-checking BlkDevLock(bd); - BlkDevRawBlksWrite(bd, &mbr, 0, 1); + BootMHDRawWrite(bd, &mbr, 0, 1); BlkDevUnlock(bd); } @@ -157,7 +178,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); - BlkDevRawBlksRead(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 +194,7 @@ public Bool BootMHDIns(U8 drv_let, U8 *drv_list=NULL) mbr.zero = 0; mbr.signature = 0xAA55; - BlkDevRawBlksWrite(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 e583c6c39..eef8be240 100755 --- a/src/System/Boot/LimineMHDIns.ZC +++ b/src/System/Boot/LimineMHDIns.ZC @@ -3,6 +3,24 @@ 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). + CHashFun *f = HashFind("BlkDevRawBlksRead", Fs->hash_table, HTT_FUN); + + if (f) + return CallInd(f->exe_addr, 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). + CHashFun *f = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN); + + if (f) + return CallInd(f->exe_addr, 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 +105,13 @@ Bool LimineMHDIns(U8 drv_let) BlkDevLock(bd); - BlkDevRawBlksRead(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); - BlkDevRawBlksWrite(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); From 8bacee9774c26cbc69c7590a23466ac4ec549db7 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 03:46:02 +0900 Subject: [PATCH 08/26] Refactor raw block read/write functions for runtime dispatching This commit updates the raw block read and write functions across multiple components to utilize a unified runtime dispatch mechanism. Key changes include: - Replaced direct function pointer retrieval with a more flexible approach using `CHash` to accommodate both public kernel functions and JIT-local functions. - Enhanced compatibility with USB-capable and pre-USB bootstrap kernels by ensuring proper resolution of function addresses at runtime. These modifications improve the maintainability and robustness of block device operations in the system. --- src/System/BlkDev/DiskPart.ZC | 12 +++++++++--- src/System/Boot/BootMHDIns.ZC | 28 +++++++++++++++++++++------- src/System/Boot/LimineMHDIns.ZC | 30 ++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/System/BlkDev/DiskPart.ZC b/src/System/BlkDev/DiskPart.ZC index e7e78e31d..423052f20 100755 --- a/src/System/BlkDev/DiskPart.ZC +++ b/src/System/BlkDev/DiskPart.ZC @@ -3,10 +3,16 @@ 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. - CHashFun *f = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN); + CHash *h = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN | HTT_EXPORT_SYS_SYM); + I64 fp = 0; - if (f) - return CallInd(f->exe_addr, bd, buf, blk, count); + 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); } diff --git a/src/System/Boot/BootMHDIns.ZC b/src/System/Boot/BootMHDIns.ZC index c253fb300..6b8f44118 100755 --- a/src/System/Boot/BootMHDIns.ZC +++ b/src/System/Boot/BootMHDIns.ZC @@ -17,19 +17,33 @@ 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. - CHashFun *f = HashFind("BlkDevRawBlksRead", Fs->hash_table, HTT_FUN); - - if (f) - return CallInd(f->exe_addr, bd, buf, blk, count); + 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"$(). - CHashFun *f = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN); + CHash *h = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN | HTT_EXPORT_SYS_SYM); + I64 fp = 0; - if (f) - return CallInd(f->exe_addr, bd, buf, blk, count); + 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); } diff --git a/src/System/Boot/LimineMHDIns.ZC b/src/System/Boot/LimineMHDIns.ZC index eef8be240..66de26061 100755 --- a/src/System/Boot/LimineMHDIns.ZC +++ b/src/System/Boot/LimineMHDIns.ZC @@ -5,19 +5,33 @@ U8 binary_limine_hdd_bin_data[0]; Bool LimineRawRead(CBlkDev *bd, U8 *buf, I64 blk, I64 count) {//Runtime-dispatched raw read (parses on pre-USB bootstrap kernels). - CHashFun *f = HashFind("BlkDevRawBlksRead", Fs->hash_table, HTT_FUN); - - if (f) - return CallInd(f->exe_addr, bd, buf, blk, count); + 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). - CHashFun *f = HashFind("BlkDevRawBlksWrite", Fs->hash_table, HTT_FUN); - - if (f) - return CallInd(f->exe_addr, bd, buf, blk, count); + 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); } From 0039a23f068f9fdc6d7ba92196522a67b8d962e9 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 03:53:35 +0900 Subject: [PATCH 09/26] Add USB boot guide documentation This commit introduces a new markdown file, `usb-boot-guide.md`, providing detailed instructions for creating a bootable USB disk using QEMU. Key steps include: - Creating a disk image and partitioning it. - Formatting the partition with FAT32. - Using QEMU to boot from the USB image. - Copying necessary files and configuring the boot process. This guide enhances user accessibility for booting from USB media and complements recent USB support enhancements in the system. --- build/usb-boot-guide.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 build/usb-boot-guide.md diff --git a/build/usb-boot-guide.md b/build/usb-boot-guide.md new file mode 100644 index 000000000..41448a7a5 --- /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 — wait for it, takes a bit +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 From 9a389085ab6649a1f3143058e3f2bd35f792816e Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:33:17 +0900 Subject: [PATCH 10/26] Add RAM-distro ISO guide and enhance boot progress markers This commit introduces a new markdown file, `ventoy-ram-iso-guide.md`, detailing the process for building a self-contained ZealOS RAM-distro ISO using Ventoy, Limine, or PXE. Key steps include: - Cloning the repository and checking out the appropriate branch. - Building base ISOs and creating a ZealOS VM. - Syncing source code into the VM and recompiling the kernel. - Packaging the RAM ISO and testing it in QEMU. Additionally, boot progress markers have been added to the kernel startup sequence to improve debugging visibility during the boot process. These enhancements improve user guidance for creating bootable RAM ISOs and facilitate better debugging during kernel initialization. --- build/usb-boot-guide.md | 2 +- build/ventoy-ram-iso-guide.md | 131 ++++++++++++++++++++++++++++++++ src/Kernel/BlkDev/DiskAddDev.ZC | 22 +++--- src/Kernel/KMain.ZC | 1 + src/Kernel/KStart32.ZC | 10 +++ src/Kernel/KStart64.ZC | 4 + src/Misc/DoDistroRAM.ZC | 110 +++++++++++++++++++++++++++ zealbooter/src/zealbooter.c | 36 ++++++--- 8 files changed, 296 insertions(+), 20 deletions(-) create mode 100644 build/ventoy-ram-iso-guide.md create mode 100644 src/Misc/DoDistroRAM.ZC diff --git a/build/usb-boot-guide.md b/build/usb-boot-guide.md index 41448a7a5..c5b271c16 100644 --- a/build/usb-boot-guide.md +++ b/build/usb-boot-guide.md @@ -13,7 +13,7 @@ qemu-system-x86_64 -machine q35,accel=kvm -m 2G \ -device usb-storage,bus=xhci.0,drive=ub -CopyTree("::/", "V:/"); # copies the whole CD to the stick — wait for it, takes a bit +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 diff --git a/build/ventoy-ram-iso-guide.md b/build/ventoy-ram-iso-guide.md new file mode 100644 index 000000000..41293686a --- /dev/null +++ b/build/ventoy-ram-iso-guide.md @@ -0,0 +1,131 @@ +# 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=none,id=hd \ + -device ich9-ahci,id=ahci -device ide-hd,drive=hd,bus=ahci.0 +``` + +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=none,id=hd \ + -device ich9-ahci,id=ahci -device ide-hd,drive=hd,bus=ahci.0 +``` + +In ZealOS: + +``` +BootHDIns('C'); +``` + +Enter `C` at the boot-drive prompt, ENTER through the rest. Reboot the VM after. + +## 6. Build the RAM kernel in the VM + +In ZealOS: + +``` +#include "::/Misc/DoDistroRAM" +``` + +Runs two kernel compiles (several minutes). Output: `::/Tmp/RAMKernel.ZXE`. +Shut down. + +## 7. Extract the kernel to the host + +``` +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/RAMKernel.ZXE ./RAMKernel.ZXE +sudo umount /tmp/zealtmp +sudo qemu-nbd -d /dev/nbd0 +``` + +## 8. Package the RAM ISO + +``` +./build-ram-iso.sh RAMKernel.ZXE +``` + +Output: `build/ZealOS-RAM.iso`. + +## 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 c73436742..f3dfb9cbe 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -265,16 +265,6 @@ U0 BlkDevsInitAll() "\nUsbBootInit;\n"; i = UsbBootInit; - if (!blkdev.ahci_hba && !i) - { - "\nZealOS requires AHCI or a USB boot drive.\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"; - Sleep(10*1000); - Reboot; - } - //Config mount code may reference missing hardware (e.g. DVD-configured kernel //booted from a USB stick); failures fall through to UsbBootDriveFallback(). #exe { @@ -288,4 +278,16 @@ U0 BlkDevsInitAll() }; 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", blkdev.boot_drive_let; + Sleep(10*1000); + Reboot; + } } diff --git a/src/Kernel/KMain.ZC b/src/Kernel/KMain.ZC index 9b0bb6e92..686f27744 100755 --- a/src/Kernel/KMain.ZC +++ b/src/Kernel/KMain.ZC @@ -131,6 +131,7 @@ U0 KMain() {//Continued from $LK,"KStart64.ZC",A="FF:::/Kernel/KStart64.ZC,I32 &KMain"$ CBlkDev *bd; + OutU8(0xE9, 'G'); //boot progress marker -> QEMU debugcon OutU8(PCSPKR, InU8(PCSPKR) & ~3); //Sound; sys_task = Fs; BlkPoolsInit; diff --git a/src/Kernel/KStart32.ZC b/src/Kernel/KStart32.ZC index 4f296a0dd..0d5de0766 100755 --- a/src/Kernel/KStart32.ZC +++ b/src/Kernel/KStart32.ZC @@ -105,6 +105,8 @@ CORE0_32BIT_INIT:: //Entry point for $LK,"BootRAM",A="MN:BootRAM"$. DU64 ZEALBOOTER_LIMINE_SIGNATURE_2; @@02: + MOV AL, 0x30 //boot progress marker '0' -> QEMU debugcon (-debugcon stdio) + OUT 0xE9, AL //Patch abs addresses MOV ECX, U32 CPatchTableAbsAddr.abs_address_count[ESI] LEA ESI, U32 CPatchTableAbsAddr.abs_address[ESI] @@ -118,11 +120,19 @@ CORE0_32BIT_INIT:: //Entry point for $LK,"BootRAM",A="MN:BootRAM"$. DU16 CGDT.cs32; @@10: BTS U32 [SYS_RUN_LEVEL], RLf_PATCHED + MOV AL, 0x41 //boot progress marker 'A' -> QEMU debugcon (-debugcon stdio) + OUT 0xE9, AL CALL SYS_FIND_PCIBIOS_SERVICE_DIR CALL SYS_FIND_PCI_SERVICES + MOV AL, 0x42 //boot progress marker 'B' -> QEMU debugcon (-debugcon stdio) + OUT 0xE9, AL CALL SYS_INIT_PAGE_TABLES + MOV AL, 0x43 //boot progress marker 'C' -> QEMU debugcon (-debugcon stdio) + OUT 0xE9, AL CALL SYS_INIT_16MEG_SYS_CODE_BP + MOV AL, 0x44 //boot progress marker 'D' -> QEMU debugcon (-debugcon stdio) + OUT 0xE9, AL PUSH U32 0 //Return from next call will be 64-bit CALL SYS_ENTER_LONG_MODE diff --git a/src/Kernel/KStart64.ZC b/src/Kernel/KStart64.ZC index f814e440c..112cefbe4 100755 --- a/src/Kernel/KStart64.ZC +++ b/src/Kernel/KStart64.ZC @@ -1,6 +1,8 @@ asm { USE64 BTS U32 [SYS_RUN_LEVEL], RLf_64BIT + MOV AL, 0x45 //boot progress marker 'E' -> QEMU debugcon (-debugcon stdio) + OUT 0xE9, AL //Set required bits for SSE instruction execution MOV_RAX_CR4 @@ -46,6 +48,8 @@ USE64 CALL &TaskInit CALL SET_FS_BASE MOV RSP, U64 CTask.rsp[RAX] + MOV AL, 0x46 //boot progress marker 'F' -> QEMU debugcon (-debugcon stdio) + OUT 0xE9, AL JMP I32 &KMain diff --git a/src/Misc/DoDistroRAM.ZC b/src/Misc/DoDistroRAM.ZC new file mode 100644 index 000000000..4338b36ec --- /dev/null +++ b/src/Misc/DoDistroRAM.ZC @@ -0,0 +1,110 @@ +//Build a SELF-CONTAINED kernel: the whole OS embedded as a RedSea distro image +//inside Kernel.ZXE (Terry's DebugDistro mechanism), mounted at boot as RAM +//drive B:. The result needs no disk driver after load, so any loader that +//places the kernel in memory boots a full ZealOS: ZealBooter/Limine ISO +//(BIOS+UEFI), Ventoy, PXE. +// +//Run from an installed, writable system: #include "::/Misc/DoDistroRAM" +//Output: ::/Tmp/RAMKernel.ZXE (kernel + embedded OS image) +//Package on the host with build/build-ram-iso.sh. +// +//Too big for the native 16-bit boot loaders (they load below 640K); +//ZealBooter/Limine load anywhere, any size. + +#define RAMDISTRO_IMAGE "::/Tmp/RAMDistro.ISO.C" +#define RAMDISTRO_KERNEL "::/Tmp/RAMKernel.ZXE" +//File offset of the embedded image within Kernel.ZXE. Must exceed the kernel +//code size (~0x60000). Embedded image runtime addr = mem_boot_base - sizeof(CZXE) +//+ RAMDISTRO_OFFSET; the matching DebugDistro start value = RAMDISTRO_OFFSET + +//BOOT_RAM_BASE (KMain's BINLOAD pad formula subtracts BOOT_RAM_BASE + sizeof(CZXE)). +#define RAMDISTRO_OFFSET 0x100000 +#define RAMDISTRO_BOOT_DIR "/Boot" +#define RAMDISTRO_KERNEL_ZXE RAMDISTRO_BOOT_DIR "/Kernel.ZXE" +#define RAMDISTRO_DVD_KERNEL RAMDISTRO_BOOT_DIR "/DVDKernel.ZXE" +//Standard DVD-distro build config (boot drive T, mount RAM B + booted DVD T). +#define RAMDISTRO_DVD_CONFIG "TB\nScale2Mem(2048,0x40000)\nT\n\n\n\n" + +U0 MakeRAMKernel() +{//Two kernel compiles: a stock DVD kernel that goes INTO the image, then the +//outer DebugDistro kernel that carries the image. Takes several minutes. + U8 *cfg; + CDirEntry de; + + if (!DriveIsWritable) + { + "Drive must be writable. Install on hard drive or USB first.\n"; + return; + } + +//--- Build the distro tree + a bootable RedSea/ISO image (same as MakeMyISO) --- + DelTree("/Distro"); + Del(RAMDISTRO_IMAGE); + DirMake("/Distro"); + In(RAMDISTRO_DVD_CONFIG); + BootDVDIns; //compiles DVD kernel -> /Boot/DVDKernel.ZXE + Copy("/*", "/Distro"); //root files (StartOS.ZC, MakeHome.ZC, ...) + DirMake("/Distro" RAMDISTRO_BOOT_DIR); + Move(RAMDISTRO_DVD_KERNEL, "/Distro" RAMDISTRO_KERNEL_ZXE); + CopyTree("/Home", "/Distro/Home"); + Del("/Distro/Home/Registry.ZC"); + CopyTree("/System", "/Distro/System"); + CopyTree("/Apps", "/Distro/Apps"); + CopyTree("/Compiler", "/Distro/Compiler"); + CopyTree("/Demo", "/Distro/Demo"); + CopyTree("/Doc", "/Distro/Doc"); + CopyTree("/Kernel", "/Distro/Kernel"); + CopyTree("/Misc", "/Distro/Misc"); + DirMake("/Distro/Tmp"); + DirMake("/Distro/Tmp/ScreenShots"); + RedSeaISO(RAMDISTRO_IMAGE, "/Distro", "/Distro" RAMDISTRO_KERNEL_ZXE); + DelTree("/Distro"); + + if (!FileFind(RAMDISTRO_IMAGE, &de, FUF_JUST_FILES)) + { + "FAILED: distro image not built.\n"; + return; + } + "Distro image: %s (%d MB).\n", RAMDISTRO_IMAGE, de.size / 0x100000; + Free(de.full_name); + +//--- Compile the outer kernel: boot drive B = RAM disk at the embedded image +// (drv_offset auto-set to the DVD offset by the Mount RAM path), and +// DebugDistro = append the image inside Kernel.ZXE at RAMDISTRO_OFFSET. +// Addr expression evaluates at BOOT, so it is correct wherever the loader +// placed the kernel (native low-mem or Limine high-mem). --- + "Compiling self-contained kernel...\n"; + cfg = MStrPrint( + "BB" //boot drive B, then Mount: select B + "mem_boot_base - sizeof(CZXE) + 0x%X\n" //RAM disk addr = embedded image + "0x%X\n" //blocks (image size, rounded up) + "\n" //exit Mount menu + "\n" //Disk Cache default + "DebugDistro\n" //toggle DebugDistro option on + "%s\n" //Debug Distro File + "0x%X\n" //Debug Distro Start + "\n", //done with options + RAMDISTRO_OFFSET, + (de.size + BLK_SIZE - 1) / BLK_SIZE, + RAMDISTRO_IMAGE, + RAMDISTRO_OFFSET + BOOT_RAM_BASE); + In(cfg); + Free(cfg); + if (Comp("/Kernel/Kernel", "Kernel",, ':')) + { + "Kernel compile failed.\n"; + return; + } + Del(RAMDISTRO_KERNEL); + Move("/Kernel/Kernel.ZXE", RAMDISTRO_KERNEL); + + if (FileFind(RAMDISTRO_KERNEL, &de, FUF_JUST_FILES)) + { + "\nDone: %s (%d MB).\n", RAMDISTRO_KERNEL, de.size / 0x100000; + "Copy it to the host and run build/build-ram-iso.sh.\n"; + Free(de.full_name); + } + else + "FAILED: no output kernel.\n"; +} + +MakeRAMKernel; diff --git a/zealbooter/src/zealbooter.c b/zealbooter/src/zealbooter.c index 66fa857d6..120825e86 100644 --- a/zealbooter/src/zealbooter.c +++ b/zealbooter/src/zealbooter.c @@ -145,6 +145,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 +202,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) { From 1ad5f87a8df33f145a32495b9049e19b905c2c55 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:51:21 +0900 Subject: [PATCH 11/26] Update RAM-distro build process and documentation This commit modifies the RAM-distro build process in the `ventoy-ram-iso-guide.md` to reflect changes in output file names and steps. The section titles have been updated for clarity, and the output now specifies `RAMDistro.BIN` as the OS image, alongside the normal kernel. Additionally, the kernel has been updated to handle the RAM-distro image, with new variables introduced for its base address and size. The `MakeRAMDistro` function has been refactored to streamline the image creation process, ensuring the entire OS is copied to a RAM drive and properly snapshot as a single file. These changes enhance the usability and accuracy of the RAM-distro ISO creation process. --- build/ventoy-ram-iso-guide.md | 20 +++-- src/Kernel/BlkDev/DiskAddDev.ZC | 16 ++++ src/Kernel/KStart16.ZC | 3 + src/Kernel/KernelA.HH | 2 + src/Kernel/KernelB.HH | 2 + src/Misc/DoDistroRAM.ZC | 128 +++++++++++--------------------- zealbooter/src/zealbooter.c | 21 +++++- 7 files changed, 99 insertions(+), 93 deletions(-) diff --git a/build/ventoy-ram-iso-guide.md b/build/ventoy-ram-iso-guide.md index 41293686a..40ea86014 100644 --- a/build/ventoy-ram-iso-guide.md +++ b/build/ventoy-ram-iso-guide.md @@ -79,7 +79,7 @@ BootHDIns('C'); Enter `C` at the boot-drive prompt, ENTER through the rest. Reboot the VM after. -## 6. Build the RAM kernel in the VM +## 6. Build the OS image in the VM In ZealOS: @@ -87,10 +87,13 @@ In ZealOS: #include "::/Misc/DoDistroRAM" ``` -Runs two kernel compiles (several minutes). Output: `::/Tmp/RAMKernel.ZXE`. -Shut down. +Copies the OS into a RAM drive and snapshots it. Output: `::/Tmp/RAMDistro.BIN` +(~48MB). Shut down. -## 7. Extract the kernel to the host +## 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). ``` sudo modprobe nbd @@ -98,7 +101,8 @@ 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/RAMKernel.ZXE ./RAMKernel.ZXE +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 ``` @@ -106,10 +110,12 @@ sudo qemu-nbd -d /dev/nbd0 ## 8. Package the RAM ISO ``` -./build-ram-iso.sh RAMKernel.ZXE +./build-ram-iso.sh RAMDistro.BIN Kernel.ZXE ``` -Output: `build/ZealOS-RAM.iso`. +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 diff --git a/src/Kernel/BlkDev/DiskAddDev.ZC b/src/Kernel/BlkDev/DiskAddDev.ZC index f3dfb9cbe..84427ba06 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -259,6 +259,22 @@ 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; 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/KernelA.HH b/src/Kernel/KernelA.HH index 0520d0689..33d2a2fbf 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 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/Misc/DoDistroRAM.ZC b/src/Misc/DoDistroRAM.ZC index 4338b36ec..d9675b166 100644 --- a/src/Misc/DoDistroRAM.ZC +++ b/src/Misc/DoDistroRAM.ZC @@ -1,33 +1,20 @@ -//Build a SELF-CONTAINED kernel: the whole OS embedded as a RedSea distro image -//inside Kernel.ZXE (Terry's DebugDistro mechanism), mounted at boot as RAM -//drive B:. The result needs no disk driver after load, so any loader that -//places the kernel in memory boots a full ZealOS: ZealBooter/Limine ISO -//(BIOS+UEFI), Ventoy, PXE. +//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/RAMKernel.ZXE (kernel + embedded OS image) -//Package on the host with build/build-ram-iso.sh. +//Output: ::/Tmp/RAMDistro.BIN (copy to the host, feed to build-ram-iso.sh) // -//Too big for the native 16-bit boot loaders (they load below 640K); -//ZealBooter/Limine load anywhere, any size. +//Destroys the contents of RAM drive A: while building. -#define RAMDISTRO_IMAGE "::/Tmp/RAMDistro.ISO.C" -#define RAMDISTRO_KERNEL "::/Tmp/RAMKernel.ZXE" -//File offset of the embedded image within Kernel.ZXE. Must exceed the kernel -//code size (~0x60000). Embedded image runtime addr = mem_boot_base - sizeof(CZXE) -//+ RAMDISTRO_OFFSET; the matching DebugDistro start value = RAMDISTRO_OFFSET + -//BOOT_RAM_BASE (KMain's BINLOAD pad formula subtracts BOOT_RAM_BASE + sizeof(CZXE)). -#define RAMDISTRO_OFFSET 0x100000 -#define RAMDISTRO_BOOT_DIR "/Boot" -#define RAMDISTRO_KERNEL_ZXE RAMDISTRO_BOOT_DIR "/Kernel.ZXE" -#define RAMDISTRO_DVD_KERNEL RAMDISTRO_BOOT_DIR "/DVDKernel.ZXE" -//Standard DVD-distro build config (boot drive T, mount RAM B + booted DVD T). -#define RAMDISTRO_DVD_CONFIG "TB\nScale2Mem(2048,0x40000)\nT\n\n\n\n" +#define RAMDISTRO_IMAGE "::/Tmp/RAMDistro.BIN" -U0 MakeRAMKernel() -{//Two kernel compiles: a stock DVD kernel that goes INTO the image, then the -//outer DebugDistro kernel that carries the image. Takes several minutes. - U8 *cfg; +U0 MakeRAMDistro(I64 blks=0x18000) +{//blks: image size in 512-byte blocks (default 48MB). Must exceed the OS tree. + CBlkDev *bd; CDirEntry de; if (!DriveIsWritable) @@ -36,75 +23,46 @@ U0 MakeRAMKernel() return; } -//--- Build the distro tree + a bootable RedSea/ISO image (same as MakeMyISO) --- - DelTree("/Distro"); - Del(RAMDISTRO_IMAGE); - DirMake("/Distro"); - In(RAMDISTRO_DVD_CONFIG); - BootDVDIns; //compiles DVD kernel -> /Boot/DVDKernel.ZXE - Copy("/*", "/Distro"); //root files (StartOS.ZC, MakeHome.ZC, ...) - DirMake("/Distro" RAMDISTRO_BOOT_DIR); - Move(RAMDISTRO_DVD_KERNEL, "/Distro" RAMDISTRO_KERNEL_ZXE); - CopyTree("/Home", "/Distro/Home"); - Del("/Distro/Home/Registry.ZC"); - CopyTree("/System", "/Distro/System"); - CopyTree("/Apps", "/Distro/Apps"); - CopyTree("/Compiler", "/Distro/Compiler"); - CopyTree("/Demo", "/Distro/Demo"); - CopyTree("/Doc", "/Distro/Doc"); - CopyTree("/Kernel", "/Distro/Kernel"); - CopyTree("/Misc", "/Distro/Misc"); - DirMake("/Distro/Tmp"); - DirMake("/Distro/Tmp/ScreenShots"); - RedSeaISO(RAMDISTRO_IMAGE, "/Distro", "/Distro" RAMDISTRO_KERNEL_ZXE); - DelTree("/Distro"); - - if (!FileFind(RAMDISTRO_IMAGE, &de, FUF_JUST_FILES)) +//--- 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)) { - "FAILED: distro image not built.\n"; + "Could not create RAM drive A:.\n"; return; } - "Distro image: %s (%d MB).\n", RAMDISTRO_IMAGE, de.size / 0x100000; - Free(de.full_name); -//--- Compile the outer kernel: boot drive B = RAM disk at the embedded image -// (drv_offset auto-set to the DVD offset by the Mount RAM path), and -// DebugDistro = append the image inside Kernel.ZXE at RAMDISTRO_OFFSET. -// Addr expression evaluates at BOOT, so it is correct wherever the loader -// placed the kernel (native low-mem or Limine high-mem). --- - "Compiling self-contained kernel...\n"; - cfg = MStrPrint( - "BB" //boot drive B, then Mount: select B - "mem_boot_base - sizeof(CZXE) + 0x%X\n" //RAM disk addr = embedded image - "0x%X\n" //blocks (image size, rounded up) - "\n" //exit Mount menu - "\n" //Disk Cache default - "DebugDistro\n" //toggle DebugDistro option on - "%s\n" //Debug Distro File - "0x%X\n" //Debug Distro Start - "\n", //done with options - RAMDISTRO_OFFSET, - (de.size + BLK_SIZE - 1) / BLK_SIZE, - RAMDISTRO_IMAGE, - RAMDISTRO_OFFSET + BOOT_RAM_BASE); - In(cfg); - Free(cfg); - if (Comp("/Kernel/Kernel", "Kernel",, ':')) - { - "Kernel compile failed.\n"; - return; - } - Del(RAMDISTRO_KERNEL); - Move("/Kernel/Kernel.ZXE", RAMDISTRO_KERNEL); +//--- 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"); + +//--- Snapshot the RAM drive memory as the image file --- + "Writing %s...\n", RAMDISTRO_IMAGE; + Del(RAMDISTRO_IMAGE); + FileWrite(RAMDISTRO_IMAGE, bd->RAM_disk, blks * BLK_SIZE); - if (FileFind(RAMDISTRO_KERNEL, &de, FUF_JUST_FILES)) + if (FileFind(RAMDISTRO_IMAGE, &de, FUF_JUST_FILES)) { - "\nDone: %s (%d MB).\n", RAMDISTRO_KERNEL, de.size / 0x100000; + "\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: no output kernel.\n"; + "FAILED: image not written.\n"; } -MakeRAMKernel; +MakeRAMDistro; diff --git a/zealbooter/src/zealbooter.c b/zealbooter/src/zealbooter.c index 120825e86..e82e1eabe 100644 --- a/zealbooter/src/zealbooter.c +++ b/zealbooter/src/zealbooter.c @@ -137,6 +137,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 @@ -235,6 +237,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++) { @@ -309,8 +322,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: "); From 8ba2171212b07319edcd37b9b3b56ada5af8eb0a Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:01:22 +0900 Subject: [PATCH 12/26] Update ventoy-ram-iso-guide.md and DiskAddDev.ZC for improved clarity and functionality This commit modifies the `ventoy-ram-iso-guide.md` to clarify the requirements for the CD and disk to be on the same AHCI controller, ensuring proper detection during installation. Additionally, it updates the QEMU command for disk configuration to use `if=ide` for better compatibility. In `DiskAddDev.ZC`, the error handling for the mount code is enhanced to prevent panic during boot if hardware is missing. The try/catch structure is refined to ensure a clean lexical scope while providing informative error messages. These changes improve the robustness and user experience during the boot process. --- build/ventoy-ram-iso-guide.md | 12 ++++++------ src/Kernel/BlkDev/DiskAddDev.ZC | 11 +++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/build/ventoy-ram-iso-guide.md b/build/ventoy-ram-iso-guide.md index 40ea86014..f6d811ce8 100644 --- a/build/ventoy-ram-iso-guide.md +++ b/build/ventoy-ram-iso-guide.md @@ -47,12 +47,13 @@ Produces `ZealOS-PublicDomain-BIOS-*.iso` (native) and `ZealOS-BSD2-UEFI-*.iso` 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=none,id=hd \ - -device ich9-ahci,id=ahci -device ide-hd,drive=hd,bus=ahci.0 + -drive file=ZealOS.qcow2,format=qcow2,if=ide ``` -At the ZealOS prompt `Install onto hard drive (y or n)?` answer `y` and follow -the installer (target drive `C`). Shut down when done. +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 @@ -67,8 +68,7 @@ shut down. ``` qemu-system-x86_64 -machine q35,accel=kvm -m 2G \ - -drive file=ZealOS.qcow2,format=qcow2,if=none,id=hd \ - -device ich9-ahci,id=ahci -device ide-hd,drive=hd,bus=ahci.0 + -drive file=ZealOS.qcow2,format=qcow2,if=ide ``` In ZealOS: diff --git a/src/Kernel/BlkDev/DiskAddDev.ZC b/src/Kernel/BlkDev/DiskAddDev.ZC index 84427ba06..93953f42e 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -281,16 +281,19 @@ U0 BlkDevsInitAll() "\nUsbBootInit;\n"; i = UsbBootInit; -//Config mount code may reference missing hardware (e.g. DVD-configured kernel -//booted from a USB stick); failures fall through to UsbBootDriveFallback(). +//Config mount code may reference missing hardware (e.g. a DVD-configured kernel +//booted from USB or as a RAM-distro); wrap it so a failed mount doesn't panic +//boot -- it falls through to UsbBootDriveFallback() / the boot-drive check. +//The #exe compiler directives stay OUTSIDE the runtime try so the try/catch +//lexical scope is clean. #exe { + StreamPrint("#exe {Option(OPTf_WARN_PAREN, OFF);}"); StreamPrint("try {"); if (kernel_config->opts[CONFIG_MOUNT_AHCI_AUTO]) StreamPrint("MountAHCIAuto;"); - StreamPrint("#exe {Option(OPTf_WARN_PAREN, OFF);}"); 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);}"); - StreamPrint("} catch { Fs->catch_except = TRUE; \"Boot mount code failed.\\n\"; }"); }; UsbBootDriveFallback; From 5f2dd62ac5c59cccd5adb1949c243824f82d86c6 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:27:20 +0900 Subject: [PATCH 13/26] fix --- build/ventoy-ram-iso-guide.md | 2 +- src/Kernel/BlkDev/DiskAddDev.ZC | 32 ++++++++++++++++++-------------- src/Misc/DoDistroRAM.ZC | 7 ++++++- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/build/ventoy-ram-iso-guide.md b/build/ventoy-ram-iso-guide.md index f6d811ce8..452421782 100644 --- a/build/ventoy-ram-iso-guide.md +++ b/build/ventoy-ram-iso-guide.md @@ -84,7 +84,7 @@ Enter `C` at the boot-drive prompt, ENTER through the rest. Reboot the VM after. In ZealOS: ``` -#include "::/Misc/DoDistroRAM" +#include "::/Misc/DoDistroRAM"; ``` Copies the OS into a RAM drive and snapshots it. Output: `::/Tmp/RAMDistro.BIN` diff --git a/src/Kernel/BlkDev/DiskAddDev.ZC b/src/Kernel/BlkDev/DiskAddDev.ZC index 93953f42e..20df81a43 100755 --- a/src/Kernel/BlkDev/DiskAddDev.ZC +++ b/src/Kernel/BlkDev/DiskAddDev.ZC @@ -281,20 +281,24 @@ U0 BlkDevsInitAll() "\nUsbBootInit;\n"; i = UsbBootInit; -//Config mount code may reference missing hardware (e.g. a DVD-configured kernel -//booted from USB or as a RAM-distro); wrap it so a failed mount doesn't panic -//boot -- it falls through to UsbBootDriveFallback() / the boot-drive check. -//The #exe compiler directives stay OUTSIDE the runtime try so the try/catch -//lexical scope is clean. - #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);}"); - }; +//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) + { + #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; diff --git a/src/Misc/DoDistroRAM.ZC b/src/Misc/DoDistroRAM.ZC index d9675b166..dcb0fdd44 100644 --- a/src/Misc/DoDistroRAM.ZC +++ b/src/Misc/DoDistroRAM.ZC @@ -51,9 +51,14 @@ U0 MakeRAMDistro(I64 blks=0x18000) DirMake("A:/Tmp/ScreenShots"); //--- 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, blks * BLK_SIZE); + FileWrite(RAMDISTRO_IMAGE, bd->RAM_disk, (bd->max_blk + 1) * BLK_SIZE); if (FileFind(RAMDISTRO_IMAGE, &de, FUF_JUST_FILES)) { From 16721a121b7874499150bc9019b81d37f83bb0a3 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:43:30 +0900 Subject: [PATCH 14/26] Update MakeRAMDistro function to increase default image size and add sanity check This commit modifies the `MakeRAMDistro` function to change the default image size from 48MB to 96MB, ensuring it exceeds the entire OS tree to prevent file skipping during the copy process. Additionally, a sanity check is introduced to alert users if files are missing due to insufficient RAM drive size, improving the reliability of the RAM-distro image creation. --- src/Misc/DoDistroRAM.ZC | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Misc/DoDistroRAM.ZC b/src/Misc/DoDistroRAM.ZC index dcb0fdd44..8190aab44 100644 --- a/src/Misc/DoDistroRAM.ZC +++ b/src/Misc/DoDistroRAM.ZC @@ -12,8 +12,11 @@ #define RAMDISTRO_IMAGE "::/Tmp/RAMDistro.BIN" -U0 MakeRAMDistro(I64 blks=0x18000) -{//blks: image size in 512-byte blocks (default 48MB). Must exceed the OS tree. +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; @@ -50,6 +53,16 @@ U0 MakeRAMDistro(I64 blks=0x18000) 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, From df2c780db255dc07c31c647b92e5d95735123de9 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:06:26 +0900 Subject: [PATCH 15/26] Enhance BlkPool memory management for RAM-distro image handling This commit updates the `BlkPoolsInit` function to improve memory allocation for the code heap, accommodating scenarios where the RAM-distro image module is placed within the code heap range. It introduces logic to split the code heap into two chunks around the RAM-distro image, preventing potential clobbering of the image during allocation. This change ensures more reliable memory management and enhances the overall stability of the system. --- src/Kernel/Memory/BlkPool.ZC | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Kernel/Memory/BlkPool.ZC b/src/Kernel/Memory/BlkPool.ZC index 3df086a46..2a64f3632 100755 --- a/src/Kernel/Memory/BlkPool.ZC +++ b/src/Kernel/Memory/BlkPool.ZC @@ -43,9 +43,30 @@ U0 BlkPoolsInit() else 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; + mem_heap_limit = code_heap_limit - 1; //See $LK,"RLf_16MEG_SYS_CODE_BP",A="FF:::/Kernel/Memory/PageTables.ZC,RLf_16MEG_SYS_CODE_BP"$ + +//The code heap is normally one contiguous 16MB..code_heap_limit block, but it +//does NOT consult E820. When ZealBooter placed the RAM-distro image module in +//that range (Limine picks the address, often low), add the code heap as two +//chunks straddling the module so the allocator never hands out -- and clobbers +//-- the image. The hole is still mapped RAM (RAM drive B: reads it directly), +//it just isn't a free block. Both chunks together = full heap minus the image. + if (sys_ram_distro_base && + SYS_16MEG_AREA_LIMIT < sys_ram_distro_base + sys_ram_distro_size && + sys_ram_distro_base < code_heap_limit) + { + lo = sys_ram_distro_base & ~(MEM_PAG_SIZE - 1); + hi = (sys_ram_distro_base + sys_ram_distro_size + MEM_PAG_SIZE - 1) & ~(MEM_PAG_SIZE - 1); + if (lo > SYS_16MEG_AREA_LIMIT) + BlkPoolAdd(sys_code_bp, SYS_16MEG_AREA_LIMIT, (lo - SYS_16MEG_AREA_LIMIT) >> MEM_PAG_BITS); + if (hi < code_heap_limit) + BlkPoolAdd(sys_code_bp, hi, (code_heap_limit - hi) >> MEM_PAG_BITS); + } + else + { + i = code_heap_limit - SYS_16MEG_AREA_LIMIT; + BlkPoolAdd(sys_code_bp, SYS_16MEG_AREA_LIMIT, i >> MEM_PAG_BITS); + } if (code_heap_limit Date: Fri, 24 Jul 2026 18:26:12 +0900 Subject: [PATCH 16/26] Implement LAPIC timer initialization and diagnostics This commit introduces the `LapicTimerInit` function to replace the legacy 8254 PIT with the LAPIC timer for system heartbeat management. It includes logic to calibrate the LAPIC timer against the TSC and sets up periodic interrupts. Additionally, diagnostic checks are added in `KMain` to verify the functionality of the timer, ensuring that the system does not hang due to a non-functional timer. This change enhances the reliability of timekeeping in the kernel, particularly on modern UEFI hardware. --- src/Kernel/KInterrupts.ZC | 64 +++++++++++++++++++++++- src/Kernel/KMain.ZC | 19 ++++++++ src/Kernel/KMisc.ZC | 2 +- src/Kernel/KernelA.HH | 3 ++ src/Kernel/Memory/BlkPool.ZC | 91 ++++++++++++++++++++--------------- src/Kernel/SerialDev/Mouse.ZC | 11 ++++- zealbooter/src/zealbooter.c | 12 +++++ 7 files changed, 157 insertions(+), 45 deletions(-) diff --git a/src/Kernel/KInterrupts.ZC b/src/Kernel/KInterrupts.ZC index 2108971a6..d870c9e44 100755 --- a/src/Kernel/KInterrupts.ZC +++ b/src/Kernel/KInterrupts.ZC @@ -157,8 +157,68 @@ 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 (8254 PIT) is + OutU8(PIC_2_DATA, 0xFF); //masked -- the heartbeat is the LAPIC timer now + //($LK,"LapicTimerInit",A="MN:LapicTimerInit"$), because modern UEFI + //firmware clock-gates the legacy PIT (IRQ0 never fires). + +//Soft-enable the local APIC and set the virtual-wire LVTs (LINT0=ExtINT so any +//still-live 8259 line can reach this CPU, LINT1=NMI, TPR=0 = accept all). + *(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; +} + +U0 LapicTimerInit() +{//System heartbeat. Drives $LK,"I_TIMER",A="MN:I_TIMER"$ (jiffies + preemption + the AP +//timer IPIs in $LK,"IntCore0TimerHandler",A="MN:IntCore0TimerHandler"$) from the local APIC timer +//instead of the 8254 PIT. The PIT is dead on modern UEFI hardware (Raptor Lake +//et al clock-gate it) -> IRQ0 never fires -> counts.jiffies freezes -> every +//Sleep() hangs -> desktop comes up white and dead. The LAPIC timer is per-core +//and always present. Runs on the boot CPU; APs are still ticked via IPI. +//Calibrated against the TSC (via CPUID) because the PIT can't be used as a ref. + CRAXRBXRCXRDX regs; + I64 tsc_hz, max_leaf, t0, t1, cur, lapic_ticks, init_count; + +//TSC frequency: CPUID.16H base MHz (Skylake+), else CPUID.15H crystal*ratio, +//else 1GHz. Only the tick RATE depends on this being right, not whether it ticks. + 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 && max_leaf >= 0x15) + { + CPUId(0x15, ®s); + if (regs.rcx.u32[0] && regs.rax.u32[0]) + tsc_hz = regs.rcx.u32[0] * regs.rbx.u32[0] / regs.rax.u32[0]; + } + if (tsc_hz < 100000000 || tsc_hz > 10000000000) + tsc_hz = 1000000000; //sane clamp / fallback + +//Measure the LAPIC timer: count down from max (divide-by-1, one-shot, masked) +//over a TSC-timed ~10ms window, then scale to JIFFY_FREQ. + *(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; + t0 = TSCGet; + t1 = t0; + while (t1 - t0 < tsc_hz / 100) + t1 = TSCGet; + cur = *(dev.uncached_alias + LAPIC_TIMER_CUR_CNT)(U32 *); + lapic_ticks = 0xFFFFFFFF - cur; + + init_count = lapic_ticks * 100 / JIFFY_FREQ; //(ticks/sec) / JIFFY_FREQ + if (init_count < 16) + init_count = 16; + +//Arm periodic on the heartbeat vector. + *(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 686f27744..70b23ebb0 100755 --- a/src/Kernel/KMain.ZC +++ b/src/Kernel/KMain.ZC @@ -130,6 +130,7 @@ U0 Reboot(Bool format_ramdisks=FALSE) U0 KMain() {//Continued from $LK,"KStart64.ZC",A="FF:::/Kernel/KStart64.ZC,I32 &KMain"$ CBlkDev *bd; + I64 jiffies0; OutU8(0xE9, 'G'); //boot progress marker -> QEMU debugcon OutU8(PCSPKR, InU8(PCSPKR) & ~3); //Sound; @@ -174,9 +175,27 @@ U0 KMain() IntInit2; LBts(&sys_run_level, RLf_INTERRUPTS); + "LapicTimerInit;\n"; + LapicTimerInit; + "TimeCal;\n"; TimeCal; +//DIAGNOSTIC (harmless to keep): verify the PIT tick actually reaches the CPU. +//0 in 250ms means the timer is dead (clock-gated 8254 or interrupt routing) -- +//the machine will boot but freeze at the desktop, since Sleep() never wakes. + jiffies0 = counts.jiffies; + Busy(250000); + if (counts.jiffies == jiffies0) + {//Timer never ticked -> jiffies frozen -> every Sleep() hangs -> white desktop. + //Halt here (Busy() uses the TSC, not jiffies) so the diagnosis is readable on + //real HW instead of scrolling away into a frozen white screen. + "\n\n$$RED$$PIT check: TIMER DEAD$$FG$$ -- jiffies frozen (0 ticks in 250ms).\n" + "PIT IRQ0 is not reaching the CPU. Desktop would hang white. Halting.\n"; + while (TRUE) + Busy(1000000); + } + "BlkDevsInitAll;\n"; BlkDevsInitAll; "\nDiskChange(':');\n"; diff --git a/src/Kernel/KMisc.ZC b/src/Kernel/KMisc.ZC index bde981092..a36c86cf7 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; //LAPIC EOI -- BSP heartbeat is the LAPIC timer now } I64 SysTimerRead() diff --git a/src/Kernel/KernelA.HH b/src/Kernel/KernelA.HH index 33d2a2fbf..b3ddb2018 100755 --- a/src/Kernel/KernelA.HH +++ b/src/Kernel/KernelA.HH @@ -669,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) diff --git a/src/Kernel/Memory/BlkPool.ZC b/src/Kernel/Memory/BlkPool.ZC index 2a64f3632..711730669 100755 --- a/src/Kernel/Memory/BlkPool.ZC +++ b/src/Kernel/Memory/BlkPool.ZC @@ -43,60 +43,71 @@ U0 BlkPoolsInit() else code_heap_limit = 0x80000000; - mem_heap_limit = code_heap_limit - 1; //See $LK,"RLf_16MEG_SYS_CODE_BP",A="FF:::/Kernel/Memory/PageTables.ZC,RLf_16MEG_SYS_CODE_BP"$ + 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"$ + mem_heap_limit = i + SYS_16MEG_AREA_LIMIT - 1; -//The code heap is normally one contiguous 16MB..code_heap_limit block, but it -//does NOT consult E820. When ZealBooter placed the RAM-distro image module in -//that range (Limine picks the address, often low), add the code heap as two -//chunks straddling the module so the allocator never hands out -- and clobbers -//-- the image. The hole is still mapped RAM (RAM drive B: reads it directly), -//it just isn't a free block. Both chunks together = full heap minus the image. - if (sys_ram_distro_base && - SYS_16MEG_AREA_LIMIT < sys_ram_distro_base + sys_ram_distro_size && - sys_ram_distro_base < code_heap_limit) + if (sys_bootloader_id == BL_LIMINE) { - lo = sys_ram_distro_base & ~(MEM_PAG_SIZE - 1); - hi = (sys_ram_distro_base + sys_ram_distro_size + MEM_PAG_SIZE - 1) & ~(MEM_PAG_SIZE - 1); - if (lo > SYS_16MEG_AREA_LIMIT) - BlkPoolAdd(sys_code_bp, SYS_16MEG_AREA_LIMIT, (lo - SYS_16MEG_AREA_LIMIT) >> MEM_PAG_BITS); - if (hi < code_heap_limit) - BlkPoolAdd(sys_code_bp, hi, (code_heap_limit - hi) >> MEM_PAG_BITS); +//Limine boot (ZealBooter: UEFI ISO, Ventoy RAM-distro): real firmware fragments +//the memory map heavily, so the stock contiguous 16MB..code_heap_limit block +//would hand out reserved holes, MMIO, and the RAM-distro image module (marked +//reserved by ZealBooter) -- corrupting them and hanging the allocator. Add the +//code heap per USABLE E820 region instead, clipped to [16MB,code_heap_limit). +//No data pool on this path: MemBIOSTotal is a SUM of usable bytes, not a top +//address, so comparing region addresses against it mis-sizes the pool -- a +//sliver pool OOMs the moment Fs->data_heap switches to it. The code heap cap +//(<=2GB) is plenty for the RAM-distro. + while (m20->type) + { + if (m20->type == MEM_E820t_USABLE) + { + 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 { - i = code_heap_limit - SYS_16MEG_AREA_LIMIT; +//Native boot: stock behavior. BlkPoolAdd(sys_code_bp, SYS_16MEG_AREA_LIMIT, i >> MEM_PAG_BITS); - } - if (code_heap_limittype) + if (code_heap_limittype == MEM_E820t_USABLE) + while (m20->type) { - 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) + if (m20->type == MEM_E820t_USABLE) { - if (first) + 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) { - BlkPoolInit(lo, (hi - lo) >> MEM_PAG_BITS); - sys_data_bp = lo; - Fs->data_heap = HeapCtrlInit(, Fs, sys_data_bp); - first = FALSE; + 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..b77107394 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,22 @@ U0 KbdMouseHandler(Bool poll_kbd, Bool poll_mouse) KbdMouseReset; else { +//A status of 0xFF means no 8042 controller (floating bus -- machines with no +//PS/2, e.g. modern laptops, return 0xFF for absent ports). A real 8042 never +//reports 0xFF. Without this check the drain loops below spin forever ("output +//buffer full" bit permanently set) -- and the mouse one runs under CLI, hanging +//the whole machine 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/zealbooter/src/zealbooter.c b/zealbooter/src/zealbooter.c index e82e1eabe..bd2469c21 100644 --- a/zealbooter/src/zealbooter.c +++ b/zealbooter/src/zealbooter.c @@ -373,6 +373,18 @@ void kmain(void) { kernel->mem_E820[mem_count].type = 0; + // The kernel identity-maps 0..mem_physical_space and draws to the framebuffer + // through that map. Limine's memmap does NOT list the GOP framebuffer, and on + // UEFI (resizable BAR / high MMIO window) it can sit above 4GB, past the RAM + // top. Extend mem_physical_space to cover it so the framebuffer stays mapped; + // otherwise every draw lands in unmapped space -> black screen on real HW. + { + 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; + } + 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; From 0cdb747593caf96ba1b72c902f755f3ef68beaa7 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 18:36:14 +0900 Subject: [PATCH 17/26] Add scripts for building RAM-distro ISO and extracting kernel image This commit introduces two new scripts: `build-ram-iso.sh` for creating a self-contained RAM-distro ISO from a specified OS image and kernel, and `extract-kernel.sh` for robustly extracting the kernel and optionally the RAM-distro image from a ZealOS virtual disk. The `build-ram-iso.sh` script automates the packaging process, ensuring compatibility with various boot methods, while `extract-kernel.sh` provides a reliable way to retrieve necessary files from the VM's disk image. These additions enhance the usability and functionality of the build process for ZealOS. --- build/build-ram-iso.sh | 71 +++++++++++++++++++++++++++++++++++ build/extract-kernel.sh | 38 +++++++++++++++++++ src/Kernel/KInterrupts.ZC | 29 ++++++-------- src/Kernel/KMain.ZC | 18 --------- src/Kernel/KMisc.ZC | 2 +- src/Kernel/KStart32.ZC | 10 ----- src/Kernel/KStart64.ZC | 4 -- src/Kernel/Memory/BlkPool.ZC | 14 +++---- src/Kernel/SerialDev/Mouse.ZC | 8 ++-- zealbooter/src/zealbooter.c | 8 ++-- 10 files changed, 132 insertions(+), 70 deletions(-) create mode 100755 build/build-ram-iso.sh create mode 100755 build/extract-kernel.sh 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/src/Kernel/KInterrupts.ZC b/src/Kernel/KInterrupts.ZC index d870c9e44..f875ac2db 100755 --- a/src/Kernel/KInterrupts.ZC +++ b/src/Kernel/KInterrupts.ZC @@ -157,13 +157,10 @@ 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, 0xFB); //Mask all but IRQ2 Cascade. IRQ0 (8254 PIT) is - OutU8(PIC_2_DATA, 0xFF); //masked -- the heartbeat is the LAPIC timer now - //($LK,"LapicTimerInit",A="MN:LapicTimerInit"$), because modern UEFI - //firmware clock-gates the legacy PIT (IRQ0 never fires). + 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 and set the virtual-wire LVTs (LINT0=ExtINT so any -//still-live 8259 line can reach this CPU, LINT1=NMI, TPR=0 = accept all). +//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; @@ -171,18 +168,15 @@ U0 IntPICInit() } U0 LapicTimerInit() -{//System heartbeat. Drives $LK,"I_TIMER",A="MN:I_TIMER"$ (jiffies + preemption + the AP -//timer IPIs in $LK,"IntCore0TimerHandler",A="MN:IntCore0TimerHandler"$) from the local APIC timer -//instead of the 8254 PIT. The PIT is dead on modern UEFI hardware (Raptor Lake -//et al clock-gate it) -> IRQ0 never fires -> counts.jiffies freezes -> every -//Sleep() hangs -> desktop comes up white and dead. The LAPIC timer is per-core -//and always present. Runs on the boot CPU; APs are still ticked via IPI. -//Calibrated against the TSC (via CPUID) because the PIT can't be used as a ref. +{//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. Calibrated with the TSC (the PIT can't). CRAXRBXRCXRDX regs; I64 tsc_hz, max_leaf, t0, t1, cur, lapic_ticks, init_count; -//TSC frequency: CPUID.16H base MHz (Skylake+), else CPUID.15H crystal*ratio, -//else 1GHz. Only the tick RATE depends on this being right, not whether it ticks. +//TSC Hz from CPUID.16H (base MHz), else CPUID.15H crystal*ratio, else 1GHz. +//Only the tick rate rides on this, not whether it ticks. tsc_hz = 0; CPUId(0, ®s); max_leaf = regs.rax.u32[0]; @@ -198,10 +192,9 @@ U0 LapicTimerInit() tsc_hz = regs.rcx.u32[0] * regs.rbx.u32[0] / regs.rax.u32[0]; } if (tsc_hz < 100000000 || tsc_hz > 10000000000) - tsc_hz = 1000000000; //sane clamp / fallback + tsc_hz = 1000000000; //clamp / fallback -//Measure the LAPIC timer: count down from max (divide-by-1, one-shot, masked) -//over a TSC-timed ~10ms window, then scale to JIFFY_FREQ. +//Count the timer down from max over a TSC-timed ~10ms, then scale to JIFFY_FREQ. *(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; diff --git a/src/Kernel/KMain.ZC b/src/Kernel/KMain.ZC index 70b23ebb0..7cf71cacb 100755 --- a/src/Kernel/KMain.ZC +++ b/src/Kernel/KMain.ZC @@ -130,9 +130,7 @@ U0 Reboot(Bool format_ramdisks=FALSE) U0 KMain() {//Continued from $LK,"KStart64.ZC",A="FF:::/Kernel/KStart64.ZC,I32 &KMain"$ CBlkDev *bd; - I64 jiffies0; - OutU8(0xE9, 'G'); //boot progress marker -> QEMU debugcon OutU8(PCSPKR, InU8(PCSPKR) & ~3); //Sound; sys_task = Fs; BlkPoolsInit; @@ -180,22 +178,6 @@ U0 KMain() "TimeCal;\n"; TimeCal; - -//DIAGNOSTIC (harmless to keep): verify the PIT tick actually reaches the CPU. -//0 in 250ms means the timer is dead (clock-gated 8254 or interrupt routing) -- -//the machine will boot but freeze at the desktop, since Sleep() never wakes. - jiffies0 = counts.jiffies; - Busy(250000); - if (counts.jiffies == jiffies0) - {//Timer never ticked -> jiffies frozen -> every Sleep() hangs -> white desktop. - //Halt here (Busy() uses the TSC, not jiffies) so the diagnosis is readable on - //real HW instead of scrolling away into a frozen white screen. - "\n\n$$RED$$PIT check: TIMER DEAD$$FG$$ -- jiffies frozen (0 ticks in 250ms).\n" - "PIT IRQ0 is not reaching the CPU. Desktop would hang white. Halting.\n"; - while (TRUE) - Busy(1000000); - } - "BlkDevsInitAll;\n"; BlkDevsInitAll; "\nDiskChange(':');\n"; diff --git a/src/Kernel/KMisc.ZC b/src/Kernel/KMisc.ZC index a36c86cf7..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); - *(dev.uncached_alias + LAPIC_EOI)(U32 *) = 0; //LAPIC EOI -- BSP heartbeat is the LAPIC timer now + *(dev.uncached_alias + LAPIC_EOI)(U32 *) = 0; //Acknowledge LAPIC timer interrupt } I64 SysTimerRead() diff --git a/src/Kernel/KStart32.ZC b/src/Kernel/KStart32.ZC index 0d5de0766..4f296a0dd 100755 --- a/src/Kernel/KStart32.ZC +++ b/src/Kernel/KStart32.ZC @@ -105,8 +105,6 @@ CORE0_32BIT_INIT:: //Entry point for $LK,"BootRAM",A="MN:BootRAM"$. DU64 ZEALBOOTER_LIMINE_SIGNATURE_2; @@02: - MOV AL, 0x30 //boot progress marker '0' -> QEMU debugcon (-debugcon stdio) - OUT 0xE9, AL //Patch abs addresses MOV ECX, U32 CPatchTableAbsAddr.abs_address_count[ESI] LEA ESI, U32 CPatchTableAbsAddr.abs_address[ESI] @@ -120,19 +118,11 @@ CORE0_32BIT_INIT:: //Entry point for $LK,"BootRAM",A="MN:BootRAM"$. DU16 CGDT.cs32; @@10: BTS U32 [SYS_RUN_LEVEL], RLf_PATCHED - MOV AL, 0x41 //boot progress marker 'A' -> QEMU debugcon (-debugcon stdio) - OUT 0xE9, AL CALL SYS_FIND_PCIBIOS_SERVICE_DIR CALL SYS_FIND_PCI_SERVICES - MOV AL, 0x42 //boot progress marker 'B' -> QEMU debugcon (-debugcon stdio) - OUT 0xE9, AL CALL SYS_INIT_PAGE_TABLES - MOV AL, 0x43 //boot progress marker 'C' -> QEMU debugcon (-debugcon stdio) - OUT 0xE9, AL CALL SYS_INIT_16MEG_SYS_CODE_BP - MOV AL, 0x44 //boot progress marker 'D' -> QEMU debugcon (-debugcon stdio) - OUT 0xE9, AL PUSH U32 0 //Return from next call will be 64-bit CALL SYS_ENTER_LONG_MODE diff --git a/src/Kernel/KStart64.ZC b/src/Kernel/KStart64.ZC index 112cefbe4..f814e440c 100755 --- a/src/Kernel/KStart64.ZC +++ b/src/Kernel/KStart64.ZC @@ -1,8 +1,6 @@ asm { USE64 BTS U32 [SYS_RUN_LEVEL], RLf_64BIT - MOV AL, 0x45 //boot progress marker 'E' -> QEMU debugcon (-debugcon stdio) - OUT 0xE9, AL //Set required bits for SSE instruction execution MOV_RAX_CR4 @@ -48,8 +46,6 @@ USE64 CALL &TaskInit CALL SET_FS_BASE MOV RSP, U64 CTask.rsp[RAX] - MOV AL, 0x46 //boot progress marker 'F' -> QEMU debugcon (-debugcon stdio) - OUT 0xE9, AL JMP I32 &KMain diff --git a/src/Kernel/Memory/BlkPool.ZC b/src/Kernel/Memory/BlkPool.ZC index 711730669..c1aa2bfa0 100755 --- a/src/Kernel/Memory/BlkPool.ZC +++ b/src/Kernel/Memory/BlkPool.ZC @@ -48,15 +48,11 @@ U0 BlkPoolsInit() if (sys_bootloader_id == BL_LIMINE) { -//Limine boot (ZealBooter: UEFI ISO, Ventoy RAM-distro): real firmware fragments -//the memory map heavily, so the stock contiguous 16MB..code_heap_limit block -//would hand out reserved holes, MMIO, and the RAM-distro image module (marked -//reserved by ZealBooter) -- corrupting them and hanging the allocator. Add the -//code heap per USABLE E820 region instead, clipped to [16MB,code_heap_limit). -//No data pool on this path: MemBIOSTotal is a SUM of usable bytes, not a top -//address, so comparing region addresses against it mis-sizes the pool -- a -//sliver pool OOMs the moment Fs->data_heap switches to it. The code heap cap -//(<=2GB) is plenty for the RAM-distro. +//UEFI firmware fragments RAM, so build the code heap only from USABLE E820 +//regions (clipped to 16MB..code_heap_limit) instead of one blind block. Skips +//reserved holes, MMIO and the RAM-distro image. No separate data pool here: +//MemBIOSTotal is a sum of usable bytes, not a top address, so the stock loop +//mis-sizes it. code_heap_limit caps at 2GB, plenty for the live system. while (m20->type) { if (m20->type == MEM_E820t_USABLE) diff --git a/src/Kernel/SerialDev/Mouse.ZC b/src/Kernel/SerialDev/Mouse.ZC index b77107394..0ba2fb177 100755 --- a/src/Kernel/SerialDev/Mouse.ZC +++ b/src/Kernel/SerialDev/Mouse.ZC @@ -499,11 +499,9 @@ U0 KbdMouseHandler(Bool poll_kbd, Bool poll_mouse) KbdMouseReset; else { -//A status of 0xFF means no 8042 controller (floating bus -- machines with no -//PS/2, e.g. modern laptops, return 0xFF for absent ports). A real 8042 never -//reports 0xFF. Without this check the drain loops below spin forever ("output -//buffer full" bit permanently set) -- and the mouse one runs under CLI, hanging -//the whole machine the first time the window manager polls. +//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 diff --git a/zealbooter/src/zealbooter.c b/zealbooter/src/zealbooter.c index bd2469c21..0947ae435 100644 --- a/zealbooter/src/zealbooter.c +++ b/zealbooter/src/zealbooter.c @@ -373,11 +373,9 @@ void kmain(void) { kernel->mem_E820[mem_count].type = 0; - // The kernel identity-maps 0..mem_physical_space and draws to the framebuffer - // through that map. Limine's memmap does NOT list the GOP framebuffer, and on - // UEFI (resizable BAR / high MMIO window) it can sit above 4GB, past the RAM - // top. Extend mem_physical_space to cover it so the framebuffer stays mapped; - // otherwise every draw lands in unmapped space -> black screen on real HW. + // 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; From 24f280f45f06a39a6b8b3ffefa34622b5fcd63d5 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:04:33 +0900 Subject: [PATCH 18/26] Refactor LAPIC timer initialization for improved calibration and fallback logic This commit enhances the `LapicTimerInit` function by introducing a new `PitCh2Read` function to accurately latch and read the 8254 channel-2 counter for LAPIC timer calibration. The calibration process is refined to ensure reliable timing even when the PIT is clock-gated, with a fallback mechanism to derive TSC frequency from CPUID if necessary. Additionally, the initialization count is clamped to a safe default to prevent excessive CPU load from erroneous measurements. These changes improve the accuracy and reliability of the LAPIC timer in the kernel. --- src/Kernel/KInterrupts.ZC | 78 ++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/src/Kernel/KInterrupts.ZC b/src/Kernel/KInterrupts.ZC index f875ac2db..f2187f562 100755 --- a/src/Kernel/KInterrupts.ZC +++ b/src/Kernel/KInterrupts.ZC @@ -167,16 +167,27 @@ U0 IntPICInit() *(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. Calibrated with the TSC (the PIT can't). +//Boot CPU only; APs are ticked via IPI. CRAXRBXRCXRDX regs; - I64 tsc_hz, max_leaf, t0, t1, cur, lapic_ticks, init_count; + 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; -//TSC Hz from CPUID.16H (base MHz), else CPUID.15H crystal*ratio, else 1GHz. -//Only the tick rate rides on this, not whether it ticks. +//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]; @@ -185,31 +196,46 @@ U0 LapicTimerInit() CPUId(0x16, ®s); tsc_hz = (regs.rax.u32[0] & 0xFFFF) * 1000000; } - if (!tsc_hz && max_leaf >= 0x15) + if (tsc_hz >= 100000000 && tsc_hz <= 10000000000) { - CPUId(0x15, ®s); - if (regs.rcx.u32[0] && regs.rax.u32[0]) - tsc_hz = regs.rcx.u32[0] * regs.rbx.u32[0] / regs.rax.u32[0]; + 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; } - if (tsc_hz < 100000000 || tsc_hz > 10000000000) - tsc_hz = 1000000000; //clamp / fallback -//Count the timer down from max over a TSC-timed ~10ms, then scale to JIFFY_FREQ. - *(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; - t0 = TSCGet; - t1 = t0; - while (t1 - t0 < tsc_hz / 100) - t1 = TSCGet; - cur = *(dev.uncached_alias + LAPIC_TIMER_CUR_CNT)(U32 *); - lapic_ticks = 0xFFFFFFFF - cur; - - init_count = lapic_ticks * 100 / JIFFY_FREQ; //(ticks/sec) / JIFFY_FREQ - if (init_count < 16) - init_count = 16; - -//Arm periodic on the heartbeat vector. +//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; } From 0e29eb389c556bdf5ba7166116be8a61923e58ab Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:25:39 +0900 Subject: [PATCH 19/26] Refactor xHCI controller initialization to support multiple controllers This commit updates the `XhciInit` function to iterate through all available xHCI controllers, allowing the system to find and initialize the first usable controller. It improves error handling and logging for cases where controllers are unassigned or have unsupported memory configurations. These changes enhance the robustness of USB controller detection and initialization in the kernel. --- src/Kernel/Usb/KXhci.ZC | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC index d414ff07e..456dfa3ed 100644 --- a/src/Kernel/Usb/KXhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -601,18 +601,19 @@ Bool XhciPortReset(I64 port) } Bool XhciInit() -{//Find + init first xHCI controller. Returns TRUE on success. - I64 bdf, bar_lo, bar_hi, addr, i; +{//Find + init the first usable xHCI controller. A machine often has several (a +//Thunderbolt/USB4 one plus the PCH one that owns the real ports), so walk them +//all and keep the first that resets and runs. Returns TRUE on success. + I64 n, bdf, bar_lo, bar_hi, addr, i; I64 hcs1, hcs2, hcc, cmd; F64 timeout; U8 *page; - bdf = PCIClassFind(XHCI_PCI_CLASS, 0); - if (bdf == -1) + for (n = 0; TRUE; n++) { - "xHCI: no controller found.\n"; - return FALSE; - } + 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]; @@ -621,16 +622,13 @@ Bool XhciInit() 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); - if (bar_hi) - { - "xHCI: MMIO above 4GB not supported.\n"; - return FALSE; - } addr = bar_lo & ~0xF; - if (!addr) + "xHCI #%d at %02X:%02X:%01X bar_lo=0x%08X bar_hi=0x%08X\n", + n, usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, bar_lo, bar_hi; + if (bar_hi || !addr) //MMIO above 4GB or unassigned -- skip { - "xHCI: BAR not assigned.\n"; - return FALSE; + " skip: unassigned or 64-bit MMIO above 4GB\n"; + goto xi_next; } //Enable memory space + bus master cmd = PCIReadU16(usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, 4); @@ -716,6 +714,11 @@ Bool XhciInit() timeout = tS + 1.0; while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) && tS < timeout) Sleep(1); + if (XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) + { + " skip: controller stayed halted after reset/run\n"; + goto xi_next; //never came out of halt -- try the next controller + } usb_xhci.present = TRUE; usb_xhci.running = TRUE; @@ -723,4 +726,8 @@ Bool XhciInit() usb_xhci.max_ports, usb_xhci.max_slots, usb_xhci.ctx_size, bar_lo & ~0xF; return TRUE; +xi_next:; + } + "xHCI: no usable controller found.\n"; + return FALSE; } From 7b18ec98b5f3183addf3f2ce54c3c26655e4b24a Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:00:30 +0900 Subject: [PATCH 20/26] Enhance xHCI controller initialization and memory mapping This commit introduces improvements to the `XhciInit` function, including enhanced logging for controller scanning and better handling of 64-bit BARs in UEFI. A new `XhciMmioMap` function is added to manage memory mapping for xHCI controllers located above 4GB, ensuring proper access to their registers. Additionally, the `UsbRep` function is updated to include a re-probing mechanism for xHCI controllers, improving the robustness of USB device detection in the kernel. --- src/Kernel/Usb/KUsbBoot.ZC | 8 ++++++-- src/Kernel/Usb/KXhci.ZC | 41 ++++++++++++++++++++++++++++--------- zealbooter/src/zealbooter.c | 35 +++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/Kernel/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC index 54684faac..016f484a4 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -77,11 +77,15 @@ public U0 UsbRep() CUsbIf *uif; I64 i, j; + "UsbRep BUILD MARKER: xhci-scan-K\n"; //bump each build to prove a fresh kernel + if (!usb_xhci.present) { - "No xHCI controller.\n"; - return; + "No xHCI controller. Re-probing:\n"; + XhciInit; } + if (!usb_xhci.present) + return; "xHCI: %d ports, %d slots.\n", usb_xhci.max_ports, usb_xhci.max_slots; for (i = 0; i < USB_DEVS_MAX; i++) { diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC index 456dfa3ed..ca1b4acac 100644 --- a/src/Kernel/Usb/KXhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -600,7 +600,21 @@ Bool XhciPortReset(I64 port) return ToBool(sc & XHCI_PORTSC_PED); } -Bool XhciInit() +U8 *XhciMmioMap(I64 phys) +{//UEFI parks xHCI BARs above 4GB. ZealBooter stretches mem_physical_space to +//cover them, so the BAR is already in the identity map -- just flip that page to +//uncached (PCD) and return the identity pointer. (Can't use the dev-alias region +//here: it lives in high memory and collides with the framebuffer's mapping.) +//No TLB flush needed -- nothing has touched this address yet, so there is no +//stale entry; the first access walks the table fresh and sees PCD. + I64 *pte, base = phys & ~(mem_page_size - 1); + + pte = MemPageTable(base); + *pte = *pte | 0x10; //PCD: uncached + return phys; +} + +public Bool XhciInit() {//Find + init the first usable xHCI controller. A machine often has several (a //Thunderbolt/USB4 one plus the PCH one that owns the real ports), so walk them //all and keep the first that resets and runs. Returns TRUE on success. @@ -609,6 +623,8 @@ Bool XhciInit() F64 timeout; U8 *page; + "XhciInit: scanning xHCI controllers...\n"; //build TEST02 + for (n = 0; TRUE; n++) { bdf = PCIClassFind(XHCI_PCI_CLASS, n); @@ -622,19 +638,20 @@ Bool XhciInit() 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_lo & ~0xF; - "xHCI #%d at %02X:%02X:%01X bar_lo=0x%08X bar_hi=0x%08X\n", - n, usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, bar_lo, bar_hi; - if (bar_hi || !addr) //MMIO above 4GB or unassigned -- skip - { - " skip: unassigned or 64-bit MMIO above 4GB\n"; - goto xi_next; - } + addr = bar_hi << 32 | bar_lo & ~0xF; + "xHCI #%d at %02X:%02X:%01X MMIO phys 0x%X\n", + n, usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, addr; + 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); - usb_xhci.mmio = dev.uncached_alias + addr; + if (bar_hi) + usb_xhci.mmio = XhciMmioMap(addr); //BAR above 4GB -- own mapping + else + usb_xhci.mmio = dev.uncached_alias + addr; + " mapped, mmio virt=0x%X. reading CAPLENGTH...\n", usb_xhci.mmio; 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); @@ -648,8 +665,11 @@ Bool XhciInit() usb_xhci.ctx_size = 64; else usb_xhci.ctx_size = 32; + " caps: hcs1=0x%08X %d ports %d slots. handoff...\n", + hcs1, usb_xhci.max_ports, usb_xhci.max_slots; XhciBiosHandoff; + " handoff done. resetting...\n"; //Stop + reset controller XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD) & ~XHCI_CMD_RS); @@ -663,6 +683,7 @@ Bool XhciInit() timeout = tS + 1.0; while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_CNR) && tS < timeout) Sleep(1); + " reset done, usbsts=0x%08X. setting up rings...\n", XhciOpR32(XHCI_OP_USBSTS); XhciOpW32(XHCI_OP_CONFIG, usb_xhci.max_slots); diff --git a/zealbooter/src/zealbooter.c b/zealbooter/src/zealbooter.c index 0947ae435..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; @@ -383,6 +392,32 @@ void kmain(void) { 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; From 8aeffe7b747b6b7af5a49a1378f1df7db5993d87 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:04:08 +0900 Subject: [PATCH 21/26] Update build markers and enhance xHCI controller initialization logging This commit updates the build marker in the `UsbRep` function to reflect a new version and modifies the `XhciInit` function to include an optional starting index for scanning xHCI controllers. This change improves the logging output, making it clearer which controller index is being scanned, and enhances the flexibility of the initialization process for systems with multiple xHCI controllers. --- src/Kernel/Usb/KUsbBoot.ZC | 2 +- src/Kernel/Usb/KXhci.ZC | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Kernel/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC index 016f484a4..cd8b243a7 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -77,7 +77,7 @@ public U0 UsbRep() CUsbIf *uif; I64 i, j; - "UsbRep BUILD MARKER: xhci-scan-K\n"; //bump each build to prove a fresh kernel + "UsbRep BUILD MARKER: xhci-scan-L\n"; //bump each build to prove a fresh kernel if (!usb_xhci.present) { diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC index ca1b4acac..09320ae38 100644 --- a/src/Kernel/Usb/KXhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -614,18 +614,18 @@ U8 *XhciMmioMap(I64 phys) return phys; } -public Bool XhciInit() -{//Find + init the first usable xHCI controller. A machine often has several (a -//Thunderbolt/USB4 one plus the PCH one that owns the real ports), so walk them -//all and keep the first that resets and runs. Returns TRUE on success. +public Bool XhciInit(I64 start_n=0) +{//Find + init the first usable xHCI controller at/after index start_n. A machine +//often has several (a Thunderbolt/USB4 one plus the PCH one that owns the real +//ports); pass start_n=1,2,... to skip to the next one. Returns TRUE on success. I64 n, bdf, bar_lo, bar_hi, addr, i; I64 hcs1, hcs2, hcc, cmd; F64 timeout; U8 *page; - "XhciInit: scanning xHCI controllers...\n"; //build TEST02 + "XhciInit: scanning xHCI controllers from #%d...\n", start_n; - for (n = 0; TRUE; n++) + for (n = start_n; TRUE; n++) { bdf = PCIClassFind(XHCI_PCI_CLASS, n); if (bdf == -1) From c97b99379bd1361d923f4b94bb01c00f656c82f7 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:56:46 +0900 Subject: [PATCH 22/26] Add xHCI controller switching and enhance initialization logging This commit introduces the `XhciSwitch` function to allow dynamic switching between multiple xHCI controllers, improving support for systems with multiple USB interfaces. Additionally, the `UsbRep` function is updated to include enhanced logging for port scanning, and the `XhciInit` function is modified to power root ports during initialization, ensuring devices are correctly recognized. These changes enhance the flexibility and robustness of USB device management in the kernel. --- src/Kernel/Usb/KUsb.HH | 2 ++ src/Kernel/Usb/KUsbBoot.ZC | 24 +++++++++++++++++++++++- src/Kernel/Usb/KXhci.ZC | 7 +++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Kernel/Usb/KUsb.HH b/src/Kernel/Usb/KUsb.HH index a4e62ce80..da1903c3f 100644 --- a/src/Kernel/Usb/KUsb.HH +++ b/src/Kernel/Usb/KUsb.HH @@ -328,3 +328,5 @@ 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); +public extern U0 XhciSwitch(); diff --git a/src/Kernel/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC index cd8b243a7..0ed22fd5a 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -77,7 +77,7 @@ public U0 UsbRep() CUsbIf *uif; I64 i, j; - "UsbRep BUILD MARKER: xhci-scan-L\n"; //bump each build to prove a fresh kernel + "UsbRep BUILD MARKER: xhci-scan-P\n"; //bump each build to prove a fresh kernel if (!usb_xhci.present) { @@ -86,7 +86,11 @@ public U0 UsbRep() } 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]; @@ -118,6 +122,24 @@ public U0 UsbRep() usb_msds[i].first_drive_let; } +public U0 XhciSwitch() +{//Re-init the NEXT xHCI controller and rescan its ports. On a machine with more +//than one (Thunderbolt + PCH), call this repeatedly from the prompt to cycle +//until the one owning your physical port comes up, then plug a device in. + static I64 idx = 0; + + idx++; + if (!XhciInit(idx)) + { + idx = 0; + XhciInit(idx); + } + "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; diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC index 09320ae38..5b6a0517b 100644 --- a/src/Kernel/Usb/KXhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -743,6 +743,13 @@ public Bool XhciInit(I64 start_n=0) usb_xhci.present = TRUE; usb_xhci.running = TRUE; + +//Power the root ports. They come up unpowered, so a device never reports +//"connected" (CCS) until PP is set -- QEMU powers them for us, real HW does not. + for (i = 0; i < usb_xhci.max_ports; i++) + XhciPortSCWrite(i, XHCI_PORTSC_PP); + Sleep(20); //let power + connect status settle + "xHCI: %d ports, %d slots, ctx size %d, MMIO 0x%X.\n", usb_xhci.max_ports, usb_xhci.max_slots, usb_xhci.ctx_size, bar_lo & ~0xF; From 9e1ddbd903bcc73b1f3d03de67e5eb066bb8cc8c Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:05:28 +0900 Subject: [PATCH 23/26] Enhance xHCI controller initialization and logging This commit updates the `XhciInit` function to include an optional parameter for forcing initialization of any xHCI controller, improving flexibility in systems with multiple controllers. Additionally, the `XhciSwitch` function is refined to better handle controller switching, and the `UsbRep` function is updated to include a kernel extraction script reference in the documentation. These changes enhance the robustness and usability of USB device management in the kernel. --- build/ventoy-ram-iso-guide.md | 1 + src/Kernel/Usb/KUsb.HH | 2 +- src/Kernel/Usb/KUsbBoot.ZC | 11 ++---- src/Kernel/Usb/KXhci.ZC | 72 ++++++++++++++++++----------------- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/build/ventoy-ram-iso-guide.md b/build/ventoy-ram-iso-guide.md index 452421782..6d41d6408 100644 --- a/build/ventoy-ram-iso-guide.md +++ b/build/ventoy-ram-iso-guide.md @@ -94,6 +94,7 @@ Copies the OS into a RAM drive and snapshots it. Output: `::/Tmp/RAMDistro.BIN` `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 diff --git a/src/Kernel/Usb/KUsb.HH b/src/Kernel/Usb/KUsb.HH index da1903c3f..a9e34ca3d 100644 --- a/src/Kernel/Usb/KUsb.HH +++ b/src/Kernel/Usb/KUsb.HH @@ -328,5 +328,5 @@ 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); +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 index 0ed22fd5a..13d5369a7 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -77,8 +77,6 @@ public U0 UsbRep() CUsbIf *uif; I64 i, j; - "UsbRep BUILD MARKER: xhci-scan-P\n"; //bump each build to prove a fresh kernel - if (!usb_xhci.present) { "No xHCI controller. Re-probing:\n"; @@ -123,16 +121,15 @@ public U0 UsbRep() } public U0 XhciSwitch() -{//Re-init the NEXT xHCI controller and rescan its ports. On a machine with more -//than one (Thunderbolt + PCH), call this repeatedly from the prompt to cycle -//until the one owning your physical port comes up, then plug a device in. +{//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)) + if (!XhciInit(idx, TRUE)) { idx = 0; - XhciInit(idx); + XhciInit(idx, TRUE); } "XhciSwitch: now on controller #%d.\n", idx; Sleep(200); diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC index 5b6a0517b..17bf9ecc7 100644 --- a/src/Kernel/Usb/KXhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -601,12 +601,10 @@ Bool XhciPortReset(I64 port) } U8 *XhciMmioMap(I64 phys) -{//UEFI parks xHCI BARs above 4GB. ZealBooter stretches mem_physical_space to -//cover them, so the BAR is already in the identity map -- just flip that page to -//uncached (PCD) and return the identity pointer. (Can't use the dev-alias region -//here: it lives in high memory and collides with the framebuffer's mapping.) -//No TLB flush needed -- nothing has touched this address yet, so there is no -//stale entry; the first access walks the table fresh and sees PCD. +{//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); @@ -614,16 +612,16 @@ U8 *XhciMmioMap(I64 phys) return phys; } -public Bool XhciInit(I64 start_n=0) -{//Find + init the first usable xHCI controller at/after index start_n. A machine -//often has several (a Thunderbolt/USB4 one plus the PCH one that owns the real -//ports); pass start_n=1,2,... to skip to the next one. Returns TRUE on success. - I64 n, bdf, bar_lo, bar_hi, addr, i; - I64 hcs1, hcs2, hcc, cmd; - F64 timeout; - U8 *page; - - "XhciInit: scanning xHCI controllers from #%d...\n", start_n; +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++) { @@ -639,8 +637,6 @@ public Bool XhciInit(I64 start_n=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; - "xHCI #%d at %02X:%02X:%01X MMIO phys 0x%X\n", - n, usb_xhci.pci_bus, usb_xhci.pci_dev, usb_xhci.pci_fun, addr; if (!addr) goto xi_next; //BAR not assigned //Enable memory space + bus master @@ -648,10 +644,9 @@ public Bool XhciInit(I64 start_n=0) 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 -- own mapping + usb_xhci.mmio = XhciMmioMap(addr); //BAR above 4GB gets its own mapping else usb_xhci.mmio = dev.uncached_alias + addr; - " mapped, mmio virt=0x%X. reading CAPLENGTH...\n", usb_xhci.mmio; 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); @@ -665,11 +660,8 @@ public Bool XhciInit(I64 start_n=0) usb_xhci.ctx_size = 64; else usb_xhci.ctx_size = 32; - " caps: hcs1=0x%08X %d ports %d slots. handoff...\n", - hcs1, usb_xhci.max_ports, usb_xhci.max_slots; XhciBiosHandoff; - " handoff done. resetting...\n"; //Stop + reset controller XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD) & ~XHCI_CMD_RS); @@ -683,7 +675,6 @@ public Bool XhciInit(I64 start_n=0) timeout = tS + 1.0; while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_CNR) && tS < timeout) Sleep(1); - " reset done, usbsts=0x%08X. setting up rings...\n", XhciOpR32(XHCI_OP_USBSTS); XhciOpW32(XHCI_OP_CONFIG, usb_xhci.max_slots); @@ -736,26 +727,37 @@ public Bool XhciInit(I64 start_n=0) while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) && tS < timeout) Sleep(1); if (XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) - { - " skip: controller stayed halted after reset/run\n"; - goto xi_next; //never came out of halt -- try the next controller - } + 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 unpowered, so a device never reports -//"connected" (CCS) until PP is set -- QEMU powers them for us, real HW does not. +//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); //let power + connect status settle + Sleep(20); - "xHCI: %d ports, %d slots, ctx size %d, MMIO 0x%X.\n", - usb_xhci.max_ports, usb_xhci.max_slots, usb_xhci.ctx_size, bar_lo & ~0xF; - - return TRUE; +//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; } From 9b10a8d723f27fdd764b87e96fb4b7de17009fd0 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Sat, 25 Jul 2026 01:25:16 +0900 Subject: [PATCH 24/26] Add CDC-ECM and USB Hub support to USB subsystem This commit introduces the CDC-ECM (Ethernet Networking Control Model) class driver, enabling USB Ethernet NICs to operate within the kernel. It includes functionality for frame transmission and reception over bulk endpoints, along with MAC address parsing. Additionally, a USB hub class driver is added to manage devices connected to downstream ports, enhancing support for multi-tier USB configurations. The USB subsystem is updated to accommodate these new features, improving overall device management and connectivity. --- src/Home/Net/Drivers/Run.ZC | 7 + src/Home/Net/Drivers/UsbEcm.ZC | 132 ++++++++++++++ src/Kernel/Usb/KUsb.HH | 70 +++++++- src/Kernel/Usb/KUsbBoot.ZC | 20 ++- src/Kernel/Usb/KUsbCore.ZC | 143 +++++++++++++-- src/Kernel/Usb/KUsbEcm.ZC | 320 +++++++++++++++++++++++++++++++++ src/Kernel/Usb/KUsbHub.ZC | 138 ++++++++++++++ src/Kernel/Usb/KXhci.ZC | 26 ++- src/Kernel/Usb/MakeKUsb.ZC | 2 + 9 files changed, 829 insertions(+), 29 deletions(-) create mode 100644 src/Home/Net/Drivers/UsbEcm.ZC create mode 100644 src/Kernel/Usb/KUsbEcm.ZC create mode 100644 src/Kernel/Usb/KUsbHub.ZC diff --git a/src/Home/Net/Drivers/Run.ZC b/src/Home/Net/Drivers/Run.ZC index 13bcd720c..ebc0246c6 100644 --- a/src/Home/Net/Drivers/Run.ZC +++ b/src/Home/Net/Drivers/Run.ZC @@ -76,6 +76,13 @@ U0 NetDriverInit() net_driver = net_driver->next; } +//No supported PCI NIC: fall back to a USB CDC-ECM adapter if one is bound. + if (!found && UsbEcmPresent) + { + found = TRUE; + NetDriverInclude("UsbEcm"); + } + if (!found) { ClassRep(net_pci); diff --git a/src/Home/Net/Drivers/UsbEcm.ZC b/src/Home/Net/Drivers/UsbEcm.ZC new file mode 100644 index 000000000..a1f30ec9a --- /dev/null +++ b/src/Home/Net/Drivers/UsbEcm.ZC @@ -0,0 +1,132 @@ +/* USB CDC-ECM network driver (Zeal net stack side). + + Bridges the kernel USB CDC-ECM class driver (src/Kernel/Usb/KUsbEcm.ZC) + into the net stack's driver contract. The kernel side owns the xHCI bulk + transfers and a receive frame ring; this file just implements the + Ethernet + NetDriver entry points the stack calls, and runs a task that + drains received frames into the NetQueue (the USB stack is polled - there + is no NIC interrupt to hook). + + Selected by NetDriverInit (Run.ZC) when a USB NIC is bound and no PCI NIC + matched. Works for QEMU `-device usb-net` (its CDC-ECM config) and the + RTL8153 USB-C adapter. +*/ + +#define USB_ECM_TX_BUFFERS 8 //power of two; TX frames in flight + +//The kernel USB ECM interface (UsbEcmPresent/MacAddr/Send/Recv) is provided by +//public AOT funcs in $LK,"KUsbEcm.ZC",A="FF:::/Kernel/Usb/KUsbEcm.ZC"$; they are +//already in the global symbol table, so call them directly (no extern - an +//extern decl creates an unresolved JIT import instead of binding the public). + +class CUsbEcmNet +{ + U8 mac[MAC_ADDRESS_LENGTH]; + U8 *tx_buffers; //USB_ECM_TX_BUFFERS * ETHERNET_FRAME_SIZE + I64 tx_lengths[USB_ECM_TX_BUFFERS]; + I64 tx_index; + Bool running; + CTask *rx_task; +} usb_ecm_net; + +U8 *EthernetMACGet() +{ + return usb_ecm_net.mac; +} + +I64 NetDriverPacketBufferGet(I64 de_index) +{//Start of the built TX frame for this descriptor. + return usb_ecm_net.tx_buffers + de_index * ETHERNET_FRAME_SIZE; +} + +I64 NetDriverPacketLengthGet(I64 de_index) +{//Total frame length (header + payload) stored at allocate time. + return usb_ecm_net.tx_lengths[de_index]; +} + +U0 NetDriverTransmitPacketFinish(I64 de_index) +{//Hand the finished frame to the kernel for a bulk-OUT transfer. + U8 *frame = usb_ecm_net.tx_buffers + de_index * ETHERNET_FRAME_SIZE; + I64 len = usb_ecm_net.tx_lengths[de_index]; + + UsbEcmSend(frame, len); +} + +I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, + U8 *destination_address, U16 ethertype, I64 packet_length) +{/* Build an Ethernet frame header into the next TX buffer and hand back the + payload pointer + descriptor index. Mirrors the PCNet driver: pad the + payload up to the minimum, cap the whole frame to the buffer size. */ + + U8 *frame; + I64 de_index, total; + + if (packet_length < ETHERNET_MIN_FRAME_SIZE) + packet_length = ETHERNET_MIN_FRAME_SIZE; + total = ETHERNET_MAC_HEADER_LENGTH + packet_length; + if (total > ETHERNET_FRAME_SIZE) + total = ETHERNET_FRAME_SIZE; + + de_index = usb_ecm_net.tx_index; + usb_ecm_net.tx_index = (usb_ecm_net.tx_index + 1) & (USB_ECM_TX_BUFFERS - 1); + + frame = usb_ecm_net.tx_buffers + de_index * ETHERNET_FRAME_SIZE; + MemSet(frame, 0, ETHERNET_FRAME_SIZE); + MemCopy(frame, destination_address, MAC_ADDRESS_LENGTH); + MemCopy(frame + MAC_ADDRESS_LENGTH, source_address, MAC_ADDRESS_LENGTH); + frame[ETHERNET_ETHERTYPE_OFFSET] = ethertype >> 8; + frame[ETHERNET_ETHERTYPE_OFFSET + 1] = ethertype & 0xFF; + + usb_ecm_net.tx_lengths[de_index] = total; + *packet_buffer_out = frame + ETHERNET_MAC_HEADER_LENGTH; + + return de_index; +} + +U0 NetStop() +{//Pause RX delivery (the kernel keeps draining USB into its ring regardless). + usb_ecm_net.running = FALSE; +} + +U0 NetStart() +{ + usb_ecm_net.running = TRUE; +} + +U0 UsbEcmNetRxTask(I64 dummy=0) +{//Drain received frames from the kernel ECM ring into the NetQueue. + U8 *buf; + I64 len; + + no_warn dummy; + usb_ecm_net.rx_task = Fs; + buf = MAlloc(ETHERNET_FRAME_SIZE); + while (TRUE) + { + len = UsbEcmRecv(buf, ETHERNET_FRAME_SIZE); + if (len > 0) + { + if (usb_ecm_net.running) + NetQueuePush(buf, len); //copies + fires I_NETHANDLER + } + else + Sleep(1); + } +} + +U0 UsbEcmNetInit() +{ + MemSet(&usb_ecm_net, 0, sizeof(CUsbEcmNet)); + UsbEcmMacAddr(usb_ecm_net.mac); + usb_ecm_net.tx_buffers = CAlloc(ETHERNET_FRAME_SIZE * USB_ECM_TX_BUFFERS); + usb_ecm_net.tx_index = 0; + usb_ecm_net.running = TRUE; + + NetLog("USB ECM NET: driver up, MAC %02X:%02X:%02X:%02X:%02X:%02X", + usb_ecm_net.mac[0], usb_ecm_net.mac[1], usb_ecm_net.mac[2], + usb_ecm_net.mac[3], usb_ecm_net.mac[4], usb_ecm_net.mac[5]); + + Spawn(&UsbEcmNetRxTask,, "UsbEcmRx"); +} + +UsbEcmNetInit; diff --git a/src/Kernel/Usb/KUsb.HH b/src/Kernel/Usb/KUsb.HH index a9e34ca3d..d63fa81d9 100644 --- a/src/Kernel/Usb/KUsb.HH +++ b/src/Kernel/Usb/KUsb.HH @@ -38,6 +38,7 @@ #define USB_RT_RECIP_DEV 0x00 #define USB_RT_RECIP_IF 0x01 #define USB_RT_RECIP_EP 0x02 +#define USB_RT_RECIP_OTHER 0x03 //hub port requests //USB feature selectors #define USB_FEAT_EP_HALT 0 @@ -61,6 +62,37 @@ #define USB_MSD_RQ_RESET 0xFF #define USB_MSD_RQ_GET_MAX_LUN 0xFE +//CDC / Ethernet Networking Control Model (ECM) +#define USB_CLASS_COMM 0x02 //CDC communications (control interface) +#define USB_CLASS_CDC_DATA 0x0A //CDC data interface (bulk endpoints) +#define USB_CDC_SUBCLASS_ECM 0x06 //Ethernet Networking Control Model +#define USB_DESC_CS_IF 0x24 //class-specific interface descriptor +#define USB_CDC_ST_ETHERNET 0x0F //ECM ethernet-networking functional subtype +#define USB_CDC_RQ_SET_ETH_PACKET_FILTER 0x43 +//Packet filter bits (SetEthernetPacketFilter wValue) +#define USB_CDC_PACKET_TYPE_PROMISCUOUS 0x01 +#define USB_CDC_PACKET_TYPE_ALL_MULTICAST 0x02 +#define USB_CDC_PACKET_TYPE_DIRECTED 0x04 +#define USB_CDC_PACKET_TYPE_BROADCAST 0x08 + +//Hub class +#define USB_DESC_HUB 0x29 //hub descriptor (USB2) +#define USB_DESC_SSHUB 0x2A //SuperSpeed hub descriptor +//Hub class feature selectors (port) +#define USB_HUB_F_PORT_CONNECTION 0 +#define USB_HUB_F_PORT_ENABLE 1 +#define USB_HUB_F_PORT_RESET 4 +#define USB_HUB_F_PORT_POWER 8 +#define USB_HUB_F_C_PORT_CONNECTION 16 +#define USB_HUB_F_C_PORT_RESET 20 +//wPortStatus bits +#define USB_HUB_PORT_CONNECTION 0x0001 +#define USB_HUB_PORT_ENABLE 0x0002 +#define USB_HUB_PORT_RESET 0x0010 +#define USB_HUB_PORT_POWER 0x0100 +#define USB_HUB_PORT_LOW_SPEED 0x0200 +#define USB_HUB_PORT_HIGH_SPEED 0x0400 //USB2 hub: set = HS, clear+!LS = FS + //Device speeds, xHCI PORTSC encoding #define USB_SPEED_FULL 1 #define USB_SPEED_LOW 2 @@ -218,15 +250,17 @@ class CUsbEPDesc #define XHCI_RING_TRBS 256 //4096 / 16 #define XHCI_EVT_RING_TRBS 256 -//Interrupt-IN auto-requeue report handler kinds (see CUsbEP.auto_kind). +//Interrupt/bulk-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 +#define USB_AUTO_ECM 3 //CDC-ECM bulk-IN: receive ethernet frame -//Forward decls: report handlers live in UsbHid.ZC (compiled after Xhci.ZC), -//but XhciEventPoll dispatches to them. Single-pass compiler needs these. +//Forward decls: report handlers live in UsbHid.ZC / KUsbEcm.ZC (compiled after +//KXhci.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); +extern U0 UsbEcmRxCB(U8 *dev, U8 *ep); class CXhciTRB {//16 bytes @@ -268,7 +302,9 @@ class CUsbEP class CUsbIf { Bool valid; - I64 if_num, if_class, if_subclass, if_proto; + I64 if_num, if_class, if_subclass, if_proto, + alt, //alternate setting that carries the endpoints (0 = default) + ecm_mac_str_idx; //iMACAddress string index from CDC-ECM functional desc (0 = none) I64 ep_idxs[4], //indices into dev->eps ep_count; }; @@ -289,7 +325,17 @@ class CUsbDev I64 vendor_id, product_id, dev_class, dev_subclass, dev_proto, config_val; - U8 *drv_data; //class driver private (CUsbMsd / CUsbHid) +//Topology: root devices have route 0, depth 0, no parent. Devices behind hubs +//carry an xHCI route string + the parent hub's slot/port (for TT of LS/FS). + I64 route, //xHCI route string (20 bits) + depth, //hub tier (0 = attached to a root port) + parent_slot, //parent hub xHCI slot id (0 = root) + parent_port; //1-based port number on the parent hub + Bool is_hub; + I64 hub_ports, //downstream port count (hubs only) + hub_tt_think, //TT think time field (0..3) + hub_mtt; //multi-TT enabled + U8 *drv_data; //class driver private (CUsbMsd / CUsbHid / CUsbEcm) U8 desc_name[64]; //product string, ASCII }; @@ -318,6 +364,12 @@ class CXhciGlobals I64 pci_bus, pci_dev, pci_fun; }; +//Hub driver (KUsbHub.ZC) enumerates downstream devices then binds them via +//UsbDrvBind (KUsbBoot.ZC, compiled later): forward decls for the single-pass +//compiler. Declared here (after CUsbDev/CUsbIf) so the struct types are known. +extern U0 UsbDrvBind(CUsbDev *dev); +extern U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif); + //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]; @@ -330,3 +382,11 @@ 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(); + +//CDC-ECM USB NIC interface, consumed by the /System net driver +//$LK,"UsbEcm.ZC",A="FF:::/Home/Net/Drivers/UsbEcm.ZC"$. Operates on the first NIC. +public extern U0 UsbEcmRep(); +public extern Bool UsbEcmPresent(); +public extern U0 UsbEcmMacAddr(U8 *out6); +public extern I64 UsbEcmSend(U8 *frame, I64 len); //0 = ok, <0 = error +public extern I64 UsbEcmRecv(U8 *dst, I64 max); //bytes, 0 = none, <0 = error diff --git a/src/Kernel/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC index 13d5369a7..36656897d 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -27,8 +27,11 @@ U0 UsbDrvBind(CUsbDev *dev) SysLog("USB: port %d: MSD subclass 0x%02X unsupported.\n", dev->port, uif->if_subclass); } + else if (uif->if_class == USB_CLASS_COMM && + uif->if_subclass == USB_CDC_SUBCLASS_ECM) + UsbEcmAttach(dev, uif); else if (uif->if_class == USB_CLASS_HUB) - SysLog("USB: port %d: hubs not yet supported.\n", dev->port); + UsbHubAttach(dev, uif); next_if:; } } @@ -37,6 +40,7 @@ U0 UsbDrvUnbind(CUsbDev *dev) { UsbHidDetach(dev); UsbMsdDetach(dev); + UsbEcmDetach(dev); } U0 UsbPortScan() @@ -94,8 +98,8 @@ public U0 UsbRep() dev = &usb_devs[i]; if (dev->present) { - "Port %2d: %04X:%04X \"%s\"", dev->port, dev->vendor_id, - dev->product_id, dev->desc_name; + "Port %2d: %04X:%04X devcls=%d \"%s\"", dev->port, dev->vendor_id, + dev->product_id, dev->dev_class, dev->desc_name; for (j = 0; j < dev->if_count; j++) { uif = &dev->ifs[j]; @@ -105,12 +109,22 @@ public U0 UsbRep() " [mouse]"; else if (uif->if_class == USB_CLASS_MSD) " [storage]"; + else if (uif->if_class == USB_CLASS_COMM && + uif->if_subclass == USB_CDC_SUBCLASS_ECM) + " [net]"; else if (uif->if_class == USB_CLASS_HUB) " [hub]"; else " [class %d]", uif->if_class; } "\n"; + for (j = 0; j < dev->if_count; j++) + { + uif = &dev->ifs[j]; + " if%d: cls=%d sub=%d proto=%d alt=%d eps=%d mac_str=%d\n", + uif->if_num, uif->if_class, uif->if_subclass, uif->if_proto, + uif->alt, uif->ep_count, uif->ecm_mac_str_idx; + } } } for (i = 0; i < USB_MSDS_MAX; i++) diff --git a/src/Kernel/Usb/KUsbCore.ZC b/src/Kernel/Usb/KUsbCore.ZC index 69c1033cd..488ff05a5 100644 --- a/src/Kernel/Usb/KUsbCore.ZC +++ b/src/Kernel/Usb/KUsbCore.ZC @@ -64,6 +64,73 @@ CUsbDev *UsbDevAlloc() return NULL; } +CUsbIf *UsbIfByNum(CUsbDev *dev, I64 if_num) +{//Return the recorded (alt-0) interface with this bInterfaceNumber, else NULL. + I64 i; + + for (i = 0; i < dev->if_count; i++) + if (dev->ifs[i].valid && dev->ifs[i].if_num == if_num) + return &dev->ifs[i]; + + return NULL; +} + +I64 UsbSetInterface(CUsbDev *dev, I64 if_num, I64 alt) +{//SET_INTERFACE: select an alternate setting to activate its endpoints. + return UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_RECIP_IF, USB_RQ_SET_INTERFACE, + alt, if_num, NULL, 0); +} + +I64 UsbPickEcmConfig(CUsbDev *dev, I64 num_configs) +{//Scan each configuration for a CDC-ECM control interface; return its config +//descriptor index (for GET/SET_CONFIGURATION), else 0. RTL8153 and QEMU usb-net +//are multi-config and their ECM configuration is not index 0. + CUsbConfigDesc *cd; + CUsbIfDesc *ifd; + U8 *buf; + I64 i, off, dlen, dtype, total, code, pick = 0; + Bool found = FALSE; + + buf = CAlloc(512, sys_task->code_heap); + for (i = 0; i < num_configs && !found; i++) + { + code = UsbGetDesc(dev, USB_DESC_CONFIG, i, buf, 9); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + goto next_cfg; + cd = buf; + total = cd->total_len; + if (total > 512) + total = 512; + code = UsbGetDesc(dev, USB_DESC_CONFIG, i, buf, total); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + goto next_cfg; + off = 0; + while (off + 2 <= total) + { + dlen = buf[off]; + dtype = buf[off + 1]; + if (!dlen) + break; + if (dtype == USB_DESC_IF) + { + ifd = &buf[off]; + if (ifd->if_class == USB_CLASS_COMM && + ifd->if_subclass == USB_CDC_SUBCLASS_ECM) + { + pick = i; + found = TRUE; + break; + } + } + off += dlen; + } +next_cfg:; + } + Free(buf); + + return pick; +} + U0 UsbParseConfig(CUsbDev *dev, U8 *buf, I64 len) {//Walk config descriptor, record interfaces + INT/BULK endpoints. CUsbIfDesc *ifd; @@ -83,19 +150,35 @@ U0 UsbParseConfig(CUsbDev *dev, U8 *buf, I64 len) if (dtype == USB_DESC_IF) { ifd = &buf[off]; - if (ifd->alt == 0 && dev->if_count < USB_IFS_MAX) + if (ifd->alt == 0) { - 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++; + if (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; } else - cur_if = NULL; //alternate setting; skip its endpoints + {//Alternate setting: attach its endpoints to the matching alt-0 interface + //and remember the alt number (CDC-ECM data endpoints live under alt 1). + cur_if = UsbIfByNum(dev, ifd->if_num); + if (cur_if) + cur_if->alt = ifd->alt; + } + } + else if (dtype == USB_DESC_CS_IF) + {//Class-specific interface descriptor. Grab the CDC-ECM Ethernet functional + //descriptor's iMACAddress string index (it follows the comm interface). + if (cur_if && dlen >= 4 && buf[off + 2] == USB_CDC_ST_ETHERNET) + cur_if->ecm_mac_str_idx = buf[off + 3]; } else if (dtype == USB_DESC_EP) { @@ -148,15 +231,17 @@ U0 UsbDevDetachCore(CUsbDev *dev) dev->drv_data = NULL; } -CUsbDev *UsbDevEnum(I64 port) -{//Enumerate device on root port (already reset + enabled). USB task only. +CUsbDev *UsbDevEnumEx(I64 root_port, I64 speed, I64 route, I64 depth, + I64 parent_slot, I64 parent_port) +{//Enumerate an already-reset+enabled device. Root-port devices pass route 0 / +//depth 0 / no parent; hub children pass the xHCI route string + parent hub +//slot/port (see KUsbHub.ZC). USB task only. CUsbDev *dev; CUsbDevDesc *dd; CUsbConfigDesc *cd; U8 *buf; - I64 slot, code, sc, total, mps0; + I64 slot, code, total, mps0, cfg_idx, port = root_port; - sc = XhciPortSC(port); slot = XhciEnableSlot; if (!slot) { @@ -170,8 +255,16 @@ CUsbDev *UsbDevEnum(I64 port) return NULL; } dev->slot = slot; - dev->port = port; - dev->speed = (sc >> 10) & 0xF; + dev->port = root_port; + dev->speed = speed; + dev->route = route; + dev->depth = depth; + dev->parent_slot = parent_slot; + dev->parent_port = parent_port; + dev->is_hub = FALSE; + dev->hub_ports = 0; + dev->hub_tt_think = 0; + dev->hub_mtt = 0; dev->present = TRUE; dev->configured = FALSE; dev->drv_data = NULL; @@ -219,8 +312,17 @@ CUsbDev *UsbDevEnum(I64 port) dev->dev_proto = dd->dev_proto; UsbStrGet(dev, dd->i_product, dev->desc_name, 64); +//Pick a configuration. Most devices are single-config (index 0); multi-config +//USB NICs (RTL8153, QEMU usb-net) hide their CDC-ECM config behind a later index, +//so prefer that one when present. + cfg_idx = 0; + if (dd->num_configs > 1) + cfg_idx = UsbPickEcmConfig(dev, dd->num_configs); + SysLog("USB: port %d: %04X:%04X num_configs=%d picked cfg_idx=%d\n", + port, dd->vendor_id, dd->product_id, dd->num_configs, cfg_idx); + //Config descriptor: header first for total length, then full. - code = UsbGetDesc(dev, USB_DESC_CONFIG, 0, buf, 9); + code = UsbGetDesc(dev, USB_DESC_CONFIG, cfg_idx, buf, 9); if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) { "USB: port %d: GET_CONFIG failed (%d).\n", port, code; @@ -232,7 +334,7 @@ CUsbDev *UsbDevEnum(I64 port) total = cd->total_len; if (total > 512) total = 512; - code = UsbGetDesc(dev, USB_DESC_CONFIG, 0, buf, total); + code = UsbGetDesc(dev, USB_DESC_CONFIG, cfg_idx, buf, total); if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) { Free(buf); @@ -268,3 +370,8 @@ CUsbDev *UsbDevEnum(I64 port) return dev; } + +CUsbDev *UsbDevEnum(I64 port) +{//Enumerate a device on a root port (already reset + enabled). USB task only. + return UsbDevEnumEx(port, (XhciPortSC(port) >> 10) & 0xF, 0, 0, 0, 0); +} diff --git a/src/Kernel/Usb/KUsbEcm.ZC b/src/Kernel/Usb/KUsbEcm.ZC new file mode 100644 index 000000000..b2a9e3b70 --- /dev/null +++ b/src/Kernel/Usb/KUsbEcm.ZC @@ -0,0 +1,320 @@ +//USB CDC-ECM (Ethernet Networking Control Model) class driver. +//Binds a USB Ethernet NIC (RTL8153 in its ECM config, QEMU usb-net CDC config), +//exposes raw Ethernet frame RX/TX over the data interface's bulk endpoints. +//The /System net driver $LK,"UsbEcm.ZC",A="FF:::/Home/Net/Drivers/UsbEcm.ZC"$ +//drives it via the public UsbEcm* interface (declared in KUsb.HH). +// +//RX is event-driven like HID: a bulk-IN TRB auto-requeues in XhciEventPoll +//(auto_kind == USB_AUTO_ECM), and UsbEcmRxCB pushes each received frame into a +//single-producer/single-consumer ring the net task drains via UsbEcmRecv. + +#define USB_ECMS_MAX 2 +#define USB_ECM_FRAME_MAX 1600 //>= max ethernet frame (1514) + slack +#define USB_ECM_RXQ 16 //RX ring depth (power of two) + +class CUsbEcmFrame +{ + I64 len; + U8 data[USB_ECM_FRAME_MAX]; +}; + +class CUsbEcm +{ + Bool valid, link_up; + CUsbDev *dev; + CUsbEP *bulk_in, *bulk_out, *int_ep; + I64 data_if_num, comm_if_num, data_alt, + lock; + U8 mac[6]; + U8 *tx_buf; //bulk OUT DMA staging +//RX ring: producer = UsbEcmRxCB (USB task), consumer = UsbEcmRecv (net task). + CUsbEcmFrame *rxq; + I64 rxq_head, //consumer index + rxq_tail, //producer index + rx_drops; +}; + +CUsbEcm usb_ecms[USB_ECMS_MAX]; + +U0 UsbEcmLock(CUsbEcm *ecm) +{ + while (LBts(&ecm->lock, 0)) + Yield; +} + +U0 UsbEcmUnlock(CUsbEcm *ecm) +{ + LBtr(&ecm->lock, 0); +} + +CUsbEcm *UsbEcmFirst() +{//First bound NIC, else NULL. v1 supports a single active NIC. + I64 i; + + for (i = 0; i < USB_ECMS_MAX; i++) + if (usb_ecms[i].valid) + return &usb_ecms[i]; + + return NULL; +} + +I64 UsbEcmHexNib(U8 c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + return -1; +} + +Bool UsbEcmMacParse(CUsbDev *dev, I64 str_idx, U8 *mac) +{//iMACAddress is a 12-char hex ASCII string descriptor, e.g. "001122334455". + U8 s[32]; + I64 i, hi, lo; + + UsbStrGet(dev, str_idx, s, sizeof(s)); + if (StrLen(s) < 12) + return FALSE; + for (i = 0; i < 6; i++) + { + hi = UsbEcmHexNib(s[i * 2]); + lo = UsbEcmHexNib(s[i * 2 + 1]); + if (hi < 0 || lo < 0) + return FALSE; + mac[i] = hi << 4 | lo; + } + + return TRUE; +} + +U0 UsbEcmRxCB(U8 *_dev, U8 *_ep) +{//Bulk-IN completion (USB task). Copy the frame into the RX ring and requeue +//happens back in XhciEventPoll. Received length = auto_len - residue. + CUsbDev *dev = _dev; + CUsbEP *ep = _ep; + CUsbEcm *ecm = dev->drv_data; + CUsbEcmFrame *slot; + I64 len, next; + + if (!ecm || !ecm->valid) + return; + len = ep->auto_len - ep->xfer_residue; + if (len <= 0) + return; + if (len > USB_ECM_FRAME_MAX) + len = USB_ECM_FRAME_MAX; + next = (ecm->rxq_tail + 1) & (USB_ECM_RXQ - 1); + if (next == ecm->rxq_head) + {//ring full: drop + ecm->rx_drops++; + return; + } + slot = &ecm->rxq[ecm->rxq_tail]; + MemCopy(slot->data, ep->auto_buf, len); + slot->len = len; + ecm->rxq_tail = next; +} + +public I64 UsbEcmSend(U8 *frame, I64 len) +{//Send one ethernet frame over bulk OUT. 0 = ok, <0 = error. + CUsbEcm *ecm = UsbEcmFirst; + I64 code; + + if (!ecm || !ecm->valid || len <= 0 || len > USB_ECM_FRAME_MAX) + return -1; + UsbEcmLock(ecm); + MemCopy(ecm->tx_buf, frame, len); + code = XhciBulkXfer(ecm->dev, ecm->bulk_out, ecm->tx_buf, len, 1.0); + if (code == XHCI_CC_STALL) + { + UsbClearHalt(ecm->dev, ecm->bulk_out); + code = XhciBulkXfer(ecm->dev, ecm->bulk_out, ecm->tx_buf, len, 1.0); + } + UsbEcmUnlock(ecm); + if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) + return 0; + + return -1; +} + +public I64 UsbEcmRecv(U8 *dst, I64 max) +{//Pull one received ethernet frame. Returns bytes, 0 = none, <0 = error. + CUsbEcm *ecm = UsbEcmFirst; + CUsbEcmFrame *slot; + I64 len; + + if (!ecm || !ecm->valid) + return -1; + if (ecm->rxq_head == ecm->rxq_tail) + return 0; + slot = &ecm->rxq[ecm->rxq_head]; + len = slot->len; + if (len > max) + len = max; + MemCopy(dst, slot->data, len); + ecm->rxq_head = (ecm->rxq_head + 1) & (USB_ECM_RXQ - 1); + + return len; +} + +public Bool UsbEcmPresent() +{ + CUsbEcm *ecm = UsbEcmFirst; + + return ecm && ecm->link_up; +} + +public U0 UsbEcmRep() +{//Report USB NIC state: presence, MAC, ring stats. On-demand (boot log scrolls). + CUsbEcm *ecm = UsbEcmFirst; + I64 pending; + + if (!ecm) + { + "USB ECM: no NIC bound.\n"; + return; + } + pending = (ecm->rxq_tail - ecm->rxq_head) & (USB_ECM_RXQ - 1); + "USB ECM: port %d link=%d MAC %02X:%02X:%02X:%02X:%02X:%02X\n", + ecm->dev->port, ecm->link_up, + ecm->mac[0], ecm->mac[1], ecm->mac[2], + ecm->mac[3], ecm->mac[4], ecm->mac[5]; + " data_if=%d alt=%d bulk_in=0x%02X bulk_out=0x%02X rxq=%d rx_drops=%d\n", + ecm->data_if_num, ecm->data_alt, + ecm->bulk_in->addr, ecm->bulk_out->addr, pending, ecm->rx_drops; +} + +public U0 UsbEcmMacAddr(U8 *out6) +{ + CUsbEcm *ecm = UsbEcmFirst; + + if (ecm) + MemCopy(out6, ecm->mac, 6); + else + MemSet(out6, 0, 6); +} + +U0 UsbEcmDetach(CUsbDev *dev) +{//Release NIC state on disconnect. Keep DMA/ring allocations for reuse. +//Scan our own array by dev (UsbDrvUnbind calls every class detacher). + I64 i; + + for (i = 0; i < USB_ECMS_MAX; i++) + if (usb_ecms[i].valid && usb_ecms[i].dev == dev) + { + usb_ecms[i].valid = FALSE; + usb_ecms[i].link_up = FALSE; + usb_ecms[i].dev = NULL; + } +} + +U0 UsbEcmAttach(CUsbDev *dev, CUsbIf *uif) +{//uif = the CDC-ECM control (comm) interface. Pair it with the CDC-Data +//interface, activate its bulk endpoints, read the MAC, start RX. + CUsbEcm *ecm = NULL; + CUsbIf *data_if = NULL; + CUsbEP *ep; + I64 i, code; + + for (i = 0; i < USB_ECMS_MAX; i++) + if (!usb_ecms[i].valid) + { + ecm = &usb_ecms[i]; + break; + } + if (!ecm) + { + SysLog("USB: ECM port %d: no free NIC slot.\n", dev->port); + return; + } + +//Locate the CDC-Data interface (class 0x0A) that carries the bulk endpoints. + for (i = 0; i < dev->if_count; i++) + if (dev->ifs[i].valid && dev->ifs[i].if_class == USB_CLASS_CDC_DATA && + dev->ifs[i].ep_count) + { + data_if = &dev->ifs[i]; + break; + } + if (!data_if) + { + SysLog("USB: ECM port %d: no data interface with endpoints.\n", dev->port); + return; + } + +//Clear state but keep persistent DMA + ring allocations. + MemSet(ecm, 0, offset(CUsbEcm.tx_buf)); + ecm->dev = dev; + ecm->comm_if_num = uif->if_num; + ecm->data_if_num = data_if->if_num; + ecm->data_alt = data_if->alt; + + for (i = 0; i < data_if->ep_count; i++) + { + ep = &dev->ep_list[data_if->ep_idxs[i]]; + if ((ep->attr & 3) == USB_EP_TYPE_BULK) + { + if (ep->addr & 0x80) + ecm->bulk_in = ep; + else + ecm->bulk_out = ep; + } + } +//Notification endpoint (INT IN) on the comm interface; optional for v1. + for (i = 0; i < uif->ep_count; i++) + { + ep = &dev->ep_list[uif->ep_idxs[i]]; + if ((ep->attr & 3) == USB_EP_TYPE_INT && ep->addr & 0x80) + ecm->int_ep = ep; + } + if (!ecm->bulk_in || !ecm->bulk_out) + { + SysLog("USB: ECM port %d: missing bulk endpoints.\n", dev->port); + return; + } + +//Activate the data interface's alt setting that exposes the bulk endpoints. + if (ecm->data_alt) + { + code = UsbSetInterface(dev, ecm->data_if_num, ecm->data_alt); + if (code != XHCI_CC_SUCCESS) + SysLog("USB: ECM port %d: SET_INTERFACE alt %d failed (%d).\n", + dev->port, ecm->data_alt, code); + } + +//MAC from the ECM functional descriptor's iMACAddress string. + if (!uif->ecm_mac_str_idx || !UsbEcmMacParse(dev, uif->ecm_mac_str_idx, ecm->mac)) + MemSet(ecm->mac, 0, 6); + +//Tell the device which frames to deliver: directed + broadcast + multicast. + UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_IF, + USB_CDC_RQ_SET_ETH_PACKET_FILTER, + USB_CDC_PACKET_TYPE_DIRECTED | USB_CDC_PACKET_TYPE_BROADCAST | + USB_CDC_PACKET_TYPE_ALL_MULTICAST, ecm->comm_if_num, NULL, 0); + + if (!ecm->tx_buf) + ecm->tx_buf = CAlloc(USB_ECM_FRAME_MAX, sys_task->code_heap); + if (!ecm->rxq) + ecm->rxq = CAlloc(sizeof(CUsbEcmFrame) * USB_ECM_RXQ, sys_task->code_heap); + ecm->rxq_head = 0; + ecm->rxq_tail = 0; + +//Arm the bulk-IN endpoint for auto-requeued receive (like HID interrupt IN). + if (!ecm->bulk_in->auto_buf) + ecm->bulk_in->auto_buf = CAlloc(USB_ECM_FRAME_MAX, sys_task->code_heap); + ecm->bulk_in->auto_len = USB_ECM_FRAME_MAX; + ecm->bulk_in->auto_kind = USB_AUTO_ECM; + + ecm->valid = TRUE; + ecm->link_up = TRUE; //assume up; notification EP can refine later + dev->drv_data = ecm; + + XhciIntQueue(dev, ecm->bulk_in); //kick off first receive + + SysLog("USB: ECM port %d: NIC ready, MAC %02X:%02X:%02X:%02X:%02X:%02X\n", + dev->port, ecm->mac[0], ecm->mac[1], ecm->mac[2], + ecm->mac[3], ecm->mac[4], ecm->mac[5]); +} diff --git a/src/Kernel/Usb/KUsbHub.ZC b/src/Kernel/Usb/KUsbHub.ZC new file mode 100644 index 000000000..2296bf4d3 --- /dev/null +++ b/src/Kernel/Usb/KUsbHub.ZC @@ -0,0 +1,138 @@ +//USB hub class driver: enumerate devices attached to downstream hub ports. +//Needed because USB NICs (and many devices) sit behind hubs - both QEMU's +//usb-net and the RTL8153 USB-C adapter enumerate through a hub tier. +// +//v1: at attach time we power every downstream port, then reset + enumerate +//whatever is already connected. No status-change-endpoint hotplug polling yet. +//Downstream devices are addressed with an xHCI route string + the parent hub's +//slot/port for TT (see UsbDevEnumEx / XhciAddressDev). + +I64 UsbHubGetPortStatus(CUsbDev *hub, I64 port, U32 *_status) +{//wPortStatus (low 16) + wPortChange (high 16). + U8 buf[4]; + I64 code; + + buf[0] = buf[1] = buf[2] = buf[3] = 0; + code = UsbCtrl(hub, USB_RT_DEV_TO_HOST | USB_RT_CLASS | USB_RT_RECIP_OTHER, + USB_RQ_GET_STATUS, 0, port, buf, 4); + *_status = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + + return code; +} + +I64 UsbHubSetPortFeature(CUsbDev *hub, I64 port, I64 feat) +{ + return UsbCtrl(hub, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_OTHER, + USB_RQ_SET_FEATURE, feat, port, NULL, 0); +} + +I64 UsbHubClearPortFeature(CUsbDev *hub, I64 port, I64 feat) +{ + return UsbCtrl(hub, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_OTHER, + USB_RQ_CLEAR_FEATURE, feat, port, NULL, 0); +} + +Bool UsbHubPortReset(CUsbDev *hub, I64 port, I64 *_speed) +{//Reset a downstream port, wait for enable, derive the device speed. + U32 st = 0; + I64 code; + F64 timeout; + + UsbHubSetPortFeature(hub, port, USB_HUB_F_PORT_RESET); + timeout = tS + 1.0; + while (tS < timeout) + { + Sleep(10); + code = UsbHubGetPortStatus(hub, port, &st); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + goto keep_waiting; + if (!(st & USB_HUB_PORT_RESET) && (st & USB_HUB_PORT_ENABLE)) + break; +keep_waiting:; + } + UsbHubClearPortFeature(hub, port, USB_HUB_F_C_PORT_RESET); + if (!(st & USB_HUB_PORT_ENABLE)) + return FALSE; + + if (hub->speed == USB_SPEED_SUPER) + *_speed = USB_SPEED_SUPER; + else if (st & USB_HUB_PORT_LOW_SPEED) + *_speed = USB_SPEED_LOW; + else if (st & USB_HUB_PORT_HIGH_SPEED) + *_speed = USB_SPEED_HIGH; + else + *_speed = USB_SPEED_FULL; + + return TRUE; +} + +U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif) +{//Configure the hub, power its ports, enumerate connected downstream devices. + CUsbDev *ndev; + U8 buf[16]; + U32 st; + I64 i, code, num_ports, hub_char, desc_type, child_speed, child_route; + + no_warn uif; +//xHCI route strings are 5 nibbles: a hub deeper than tier 5 can't route children. + if (dev->depth >= 5) + { + SysLog("USB: hub port %d: max hub depth reached, ignoring.\n", dev->port); + return; + } + + if (dev->speed == USB_SPEED_SUPER) + desc_type = USB_DESC_SSHUB; + else + desc_type = USB_DESC_HUB; + code = UsbCtrl(dev, USB_RT_DEV_TO_HOST | USB_RT_CLASS | USB_RT_RECIP_DEV, + USB_RQ_GET_DESCRIPTOR, desc_type << 8, 0, buf, 8); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + SysLog("USB: hub port %d: GET hub descriptor failed (%d).\n", dev->port, code); + return; + } + num_ports = buf[2]; + hub_char = buf[3] | (buf[4] << 8); + dev->is_hub = TRUE; + dev->hub_ports = num_ports; + dev->hub_tt_think = (hub_char >> 5) & 3; + dev->hub_mtt = 0; //v1: single-TT (default hub alt setting) + +//Re-issue Configure Endpoint so the HC learns this slot is a hub (Hub bit + +//port count + TT think time now set in XhciConfigureEPs). + code = XhciConfigureEPs(dev); + if (code != XHCI_CC_SUCCESS) + SysLog("USB: hub port %d: configure-as-hub failed (%d).\n", dev->port, code); + + SysLog("USB: hub port %d: %d ports, TTT=%d, depth=%d.\n", + dev->port, num_ports, dev->hub_tt_think, dev->depth); + + for (i = 1; i <= num_ports; i++) + UsbHubSetPortFeature(dev, i, USB_HUB_F_PORT_POWER); + Sleep(100); //power-good settle + + for (i = 1; i <= num_ports; i++) + { + code = UsbHubGetPortStatus(dev, i, &st); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + goto next_port; + if (!(st & USB_HUB_PORT_CONNECTION)) + goto next_port; + UsbHubClearPortFeature(dev, i, USB_HUB_F_C_PORT_CONNECTION); + Sleep(100); //connect debounce + if (!UsbHubPortReset(dev, i, &child_speed)) + { + SysLog("USB: hub port %d.%d: reset failed.\n", dev->port, i); + goto next_port; + } + Sleep(10); +//Child route: append this port number at the hub's tier nibble. + child_route = dev->route | (i << (4 * dev->depth)); + ndev = UsbDevEnumEx(dev->port, child_speed, child_route, dev->depth + 1, + dev->slot, i); + if (ndev) + UsbDrvBind(ndev); +next_port:; + } +} diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC index 17bf9ecc7..d9ad24e2a 100644 --- a/src/Kernel/Usb/KXhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -207,7 +207,10 @@ U0 XhciEventPoll() if (ep) { if (ep->auto_kind) - {//HID interrupt IN: deliver + requeue + {//HID interrupt IN / ECM bulk IN: deliver + requeue. + //Stash residue so byte-count-sensitive handlers (ECM) can + //compute the received length (auto_len - residue). + ep->xfer_residue = e->status & 0xFFFFFF; if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) { switch (ep->auto_kind) @@ -218,6 +221,9 @@ U0 XhciEventPoll() case USB_AUTO_MOUSE: UsbMouseCB(dev, ep); break; + case USB_AUTO_ECM: + UsbEcmRxCB(dev, ep); + break; } } XhciIntQueue(dev, ep); @@ -463,8 +469,13 @@ I64 XhciAddressDev(CUsbDev *dev) 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; + slot_ctx[0] = (1 << 27) | (dev->speed << 20) | (dev->route & 0xFFFFF); + slot_ctx[1] = (dev->port + 1) << 16; //root hub port (top of the tree) +//TT: a LS/FS device behind a HS hub gets its transactions translated by that +//hub. Point the slot at the parent hub's slot + port so the HC targets its TT. + if (dev->parent_slot && + (dev->speed == USB_SPEED_LOW || dev->speed == USB_SPEED_FULL)) + slot_ctx[2] = (dev->parent_slot & 0xFF) | ((dev->parent_port & 0xFF) << 8); ep0_ctx = XhciInputCtx(dev, 2); ep0_ctx[1] = (3 << 1) | (XHCI_EP_TYPE_CTRL << 3) | (mps << 16); addr = dev->ep0.ring->trbs; @@ -534,6 +545,15 @@ 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); +//Hub: advertise it to the HC so it routes traffic to downstream ports. + if (dev->is_hub) + { + slot_ctx[0] = slot_ctx[0] | (1 << 26); //Hub + if (dev->hub_mtt) + slot_ctx[0] = slot_ctx[0] | (1 << 25); //Multi-TT + slot_ctx[1] = (slot_ctx[1] & 0x00FFFFFF) | ((dev->hub_ports & 0xFF) << 24); + slot_ctx[2] = (slot_ctx[2] & ~(3 << 16)) | ((dev->hub_tt_think & 3) << 16); + } addr = dev->in_ctx; return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, diff --git a/src/Kernel/Usb/MakeKUsb.ZC b/src/Kernel/Usb/MakeKUsb.ZC index 173bac7f7..dda776920 100644 --- a/src/Kernel/Usb/MakeKUsb.ZC +++ b/src/Kernel/Usb/MakeKUsb.ZC @@ -4,5 +4,7 @@ #include "KUsbCore" #include "KUsbHid" #include "KUsbMsd" +#include "KUsbEcm" +#include "KUsbHub" #include "KUsbBoot" #exe {Cd("..");}; From 81e04cb2f09b4e11684ba611675992b199a81dcf Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:13:21 +0900 Subject: [PATCH 25/26] update --- src/Kernel/Usb/KUsb.HH | 4 ++++ src/Kernel/Usb/KUsbBoot.ZC | 46 +++++++++++++++++++++++++++++++++++++- src/Kernel/Usb/KUsbCore.ZC | 9 ++++++++ src/Kernel/Usb/KUsbHub.ZC | 22 ++++++++++++------ 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/Kernel/Usb/KUsb.HH b/src/Kernel/Usb/KUsb.HH index d63fa81d9..146ac5b8a 100644 --- a/src/Kernel/Usb/KUsb.HH +++ b/src/Kernel/Usb/KUsb.HH @@ -78,6 +78,9 @@ //Hub class #define USB_DESC_HUB 0x29 //hub descriptor (USB2) #define USB_DESC_SSHUB 0x2A //SuperSpeed hub descriptor +//SuperSpeed hubs must be told their tier before they can route packets +//(USB 3.x 10.14.2.7). Without it, downstream Address Device fails. +#define USB_HUB_RQ_SET_DEPTH 12 //Hub class feature selectors (port) #define USB_HUB_F_PORT_CONNECTION 0 #define USB_HUB_F_PORT_ENABLE 1 @@ -382,6 +385,7 @@ 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(); +public extern U0 XhciFindNic(I64 max_tries=8); //CDC-ECM USB NIC interface, consumed by the /System net driver //$LK,"UsbEcm.ZC",A="FF:::/Home/Net/Drivers/UsbEcm.ZC"$. Operates on the first NIC. diff --git a/src/Kernel/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC index 36656897d..4e6913760 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -77,10 +77,12 @@ U0 UsbPortScan() public U0 UsbRep() {//Report attached USB devices. - CUsbDev *dev; + CUsbDev *dev, *ndev; CUsbIf *uif; I64 i, j; + "$$FG,GREEN$$UsbRep BUILD MARKER: ss-hubdepth-5$$FG$$\n"; //bump each rebuild to catch stale kernels + if (!usb_xhci.present) { "No xHCI controller. Re-probing:\n"; @@ -132,6 +134,29 @@ public U0 UsbRep() "Storage %d: %d MB, drives from %C:.\n", i, ((usb_msds[i].max_lba + 1) * BLK_SIZE) / 0x100000, usb_msds[i].first_drive_let; + +//Diagnose root ports that are connected but have no bound device (enumeration +//gaps, e.g. SuperSpeed). Re-attempt enumeration HERE so the enum trace + direct +//failure prints land in this terminal (usb_task SysLogs are hidden single-user). + for (i = 0; i < usb_xhci.max_ports; i++) + { + if ((XhciPortSC(i) & XHCI_PORTSC_CCS) && !UsbDevByPort(i)) + { + "UNBOUND port %d: PORTSC=0x%08X speed=%d - retrying enum:\n", + i, XhciPortSC(i), (XhciPortSC(i) >> 10) & 0xF; + if (XhciPortReset(i)) + { + ndev = UsbDevEnum(i); + if (ndev) + { + UsbDrvBind(ndev); + " port %d: enumerated OK.\n", i; + } + } + else + " port %d: XhciPortReset did not enable the port.\n", i; + } + } } public U0 XhciSwitch() @@ -151,6 +176,25 @@ public U0 XhciSwitch() UsbRep; } +public U0 XhciFindNic(I64 max_tries=8) +{//Cycle controllers until one has a USB ethernet NIC bound. A USB-C dock puts +//the SuperSpeed NIC on the Thunderbolt controller while HID sits on the PCH one, +//and this stack drives a single controller at a time - this hunts for the NIC. + I64 i; + + for (i = 0; i < max_tries; i++) + { + if (UsbEcmPresent) + { + "XhciFindNic: NIC found.\n"; + UsbEcmRep; + return; + } + XhciSwitch; + } + "XhciFindNic: no USB NIC found after %d controller switches.\n", max_tries; +} + U0 UsbTaskMain(I64 dummy=0) {//Hotplug + HID poll loop. Spawned from $LK,"KMain",A="FF:::/Kernel/KMain.ZC,UsbTaskMain"$. I64 tick = 0; diff --git a/src/Kernel/Usb/KUsbCore.ZC b/src/Kernel/Usb/KUsbCore.ZC index 488ff05a5..9b629ab03 100644 --- a/src/Kernel/Usb/KUsbCore.ZC +++ b/src/Kernel/Usb/KUsbCore.ZC @@ -270,8 +270,17 @@ CUsbDev *UsbDevEnumEx(I64 root_port, I64 speed, I64 route, I64 depth, dev->drv_data = NULL; dev->desc_name[0] = 0; +//Direct print (not SysLog): visible from any task, incl. UsbRep's re-enum. + "USB: enum port %d speed %d route 0x%05X depth %d slot %d\n", + root_port, speed, route, depth, slot; + code = XhciAddressDev(dev); if (code != XHCI_CC_SUCCESS) + {//One retry: a SuperSpeed device behind a hub can be slow to settle after reset. + Sleep(100); + code = XhciAddressDev(dev); + } + if (code != XHCI_CC_SUCCESS) { "USB: port %d: Address Device failed (%d).\n", port, code; UsbDevDetachCore(dev); diff --git a/src/Kernel/Usb/KUsbHub.ZC b/src/Kernel/Usb/KUsbHub.ZC index 2296bf4d3..819072f6a 100644 --- a/src/Kernel/Usb/KUsbHub.ZC +++ b/src/Kernel/Usb/KUsbHub.ZC @@ -100,13 +100,21 @@ U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif) dev->hub_mtt = 0; //v1: single-TT (default hub alt setting) //Re-issue Configure Endpoint so the HC learns this slot is a hub (Hub bit + -//port count + TT think time now set in XhciConfigureEPs). +//port count + TT think time now set in XhciConfigureEPs). Direct-print the +//result: if this fails, downstream Address Device gets a Transaction Error. code = XhciConfigureEPs(dev); - if (code != XHCI_CC_SUCCESS) - SysLog("USB: hub port %d: configure-as-hub failed (%d).\n", dev->port, code); + "USB: hub port %d: cfg-as-hub code=%d, ports=%d, TTT=%d, depth=%d, speed=%d\n", + dev->port, code, num_ports, dev->hub_tt_think, dev->depth, dev->speed; - SysLog("USB: hub port %d: %d ports, TTT=%d, depth=%d.\n", - dev->port, num_ports, dev->hub_tt_think, dev->depth); +//SuperSpeed hubs route by route string only after being told their tier +//(USB 3.x 10.14.2.7 SetHubDepth). Skipping this makes every downstream +//Address Device fail with a Transaction Error. USB2 hubs don't use it. + if (dev->speed == USB_SPEED_SUPER) + { + code = UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_DEV, + USB_HUB_RQ_SET_DEPTH, dev->depth, 0, NULL, 0); + "USB: hub port %d: SET_HUB_DEPTH(%d) code=%d\n", dev->port, dev->depth, code; + } for (i = 1; i <= num_ports; i++) UsbHubSetPortFeature(dev, i, USB_HUB_F_PORT_POWER); @@ -123,10 +131,10 @@ U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif) Sleep(100); //connect debounce if (!UsbHubPortReset(dev, i, &child_speed)) { - SysLog("USB: hub port %d.%d: reset failed.\n", dev->port, i); + "USB: hub port %d.%d: reset failed.\n", dev->port, i; goto next_port; } - Sleep(10); + Sleep(50); //post-reset recovery (SS link training needs more than 10ms) //Child route: append this port number at the hub's tier nibble. child_route = dev->route | (i << (4 * dev->depth)); ndev = UsbDevEnumEx(dev->port, child_speed, child_route, dev->depth + 1, From 9260a3a46b5fcd3f280ba8e2732d4b1c28e25616 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:49:36 +0900 Subject: [PATCH 26/26] Usb multi controller (#194) * Refactor USB subsystem to support multiple xHCI controllers This commit introduces a new structure for managing multiple xHCI controllers, allowing the system to handle up to four controllers simultaneously. The `usb_xhci` variable is replaced with an array of `CXhciGlobals`, and related functions are updated to reference the active controller correctly. Additionally, logging improvements are made to enhance visibility during USB device enumeration and hub attachment processes. These changes enhance the flexibility and robustness of USB device management in the kernel. * Refactor USB CDC-ECM driver and related components for clarity and efficiency This commit refines the USB CDC-ECM driver by simplifying comments and enhancing code clarity. It updates the documentation to better reflect the functionality of the driver, including the handling of Ethernet frames and the interaction with the net stack. Additionally, improvements are made to the USB hub and xHCI controller management, ensuring better integration and performance across multiple controllers. These changes enhance the maintainability and readability of the USB subsystem. * Enhance USB CDC-ECM driver with transmit queue management This commit introduces a transmit queue for the USB CDC-ECM driver, allowing for non-blocking frame transmission from interrupt context. The `UsbEcmSend` function is updated to queue frames for transmission, while the new `UsbEcmTick` function processes the transmit queue, ensuring efficient handling of outgoing data. Additionally, the maximum frame size is increased to accommodate larger Ethernet frames, and the driver structure is refined for better clarity and maintainability. --- src/Home/Net/Drivers/Run.ZC | 2 +- src/Home/Net/Drivers/UsbEcm.ZC | 41 +-- src/Kernel/Usb/KUsb.HH | 82 +++--- src/Kernel/Usb/KUsbBoot.ZC | 160 ++++------ src/Kernel/Usb/KUsbCore.ZC | 50 ++-- src/Kernel/Usb/KUsbEcm.ZC | 156 ++++++---- src/Kernel/Usb/KUsbHub.ZC | 44 ++- src/Kernel/Usb/KXhci.ZC | 515 ++++++++++++++++++--------------- 8 files changed, 541 insertions(+), 509 deletions(-) diff --git a/src/Home/Net/Drivers/Run.ZC b/src/Home/Net/Drivers/Run.ZC index ebc0246c6..b403212b5 100644 --- a/src/Home/Net/Drivers/Run.ZC +++ b/src/Home/Net/Drivers/Run.ZC @@ -76,7 +76,7 @@ U0 NetDriverInit() net_driver = net_driver->next; } -//No supported PCI NIC: fall back to a USB CDC-ECM adapter if one is bound. +//No PCI NIC: fall back to a bound USB CDC-ECM adapter. if (!found && UsbEcmPresent) { found = TRUE; diff --git a/src/Home/Net/Drivers/UsbEcm.ZC b/src/Home/Net/Drivers/UsbEcm.ZC index a1f30ec9a..baea6dddd 100644 --- a/src/Home/Net/Drivers/UsbEcm.ZC +++ b/src/Home/Net/Drivers/UsbEcm.ZC @@ -1,23 +1,15 @@ -/* USB CDC-ECM network driver (Zeal net stack side). - - Bridges the kernel USB CDC-ECM class driver (src/Kernel/Usb/KUsbEcm.ZC) - into the net stack's driver contract. The kernel side owns the xHCI bulk - transfers and a receive frame ring; this file just implements the - Ethernet + NetDriver entry points the stack calls, and runs a task that - drains received frames into the NetQueue (the USB stack is polled - there - is no NIC interrupt to hook). - - Selected by NetDriverInit (Run.ZC) when a USB NIC is bound and no PCI NIC - matched. Works for QEMU `-device usb-net` (its CDC-ECM config) and the - RTL8153 USB-C adapter. +/* USB CDC-ECM network driver. + + Net stack side of the kernel CDC-ECM class driver (Kernel/Usb/KUsbEcm.ZC), + which owns the bulk transfers and the receive ring. Selected by + NetDriverInit when no PCI NIC matched. RX is drained by a task; the USB + stack is polled, so there is no NIC interrupt. */ -#define USB_ECM_TX_BUFFERS 8 //power of two; TX frames in flight +#define USB_ECM_TX_BUFFERS 8 //power of two -//The kernel USB ECM interface (UsbEcmPresent/MacAddr/Send/Recv) is provided by -//public AOT funcs in $LK,"KUsbEcm.ZC",A="FF:::/Kernel/Usb/KUsbEcm.ZC"$; they are -//already in the global symbol table, so call them directly (no extern - an -//extern decl creates an unresolved JIT import instead of binding the public). +//UsbEcm* are public kernel AOT funcs, already in the global symbol table. +//Do not extern them: that makes an unresolved JIT import instead of binding. class CUsbEcmNet { @@ -35,17 +27,17 @@ U8 *EthernetMACGet() } I64 NetDriverPacketBufferGet(I64 de_index) -{//Start of the built TX frame for this descriptor. +{//Built TX frame for this descriptor. return usb_ecm_net.tx_buffers + de_index * ETHERNET_FRAME_SIZE; } I64 NetDriverPacketLengthGet(I64 de_index) -{//Total frame length (header + payload) stored at allocate time. +{//Frame length recorded at allocate time. return usb_ecm_net.tx_lengths[de_index]; } U0 NetDriverTransmitPacketFinish(I64 de_index) -{//Hand the finished frame to the kernel for a bulk-OUT transfer. +{//Hand the frame to the kernel for a bulk-OUT transfer. U8 *frame = usb_ecm_net.tx_buffers + de_index * ETHERNET_FRAME_SIZE; I64 len = usb_ecm_net.tx_lengths[de_index]; @@ -54,9 +46,8 @@ U0 NetDriverTransmitPacketFinish(I64 de_index) I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, U8 *destination_address, U16 ethertype, I64 packet_length) -{/* Build an Ethernet frame header into the next TX buffer and hand back the - payload pointer + descriptor index. Mirrors the PCNet driver: pad the - payload up to the minimum, cap the whole frame to the buffer size. */ +{/* Build a frame header in the next TX buffer, return the payload pointer + and descriptor index. Pads the payload to the minimum, caps to buffer. */ U8 *frame; I64 de_index, total; @@ -84,7 +75,7 @@ I64 EthernetFrameAllocate(U8 **packet_buffer_out, U8 *source_address, } U0 NetStop() -{//Pause RX delivery (the kernel keeps draining USB into its ring regardless). +{//Pause RX delivery; the kernel keeps filling its ring. usb_ecm_net.running = FALSE; } @@ -94,7 +85,7 @@ U0 NetStart() } U0 UsbEcmNetRxTask(I64 dummy=0) -{//Drain received frames from the kernel ECM ring into the NetQueue. +{//Drain the kernel ECM ring into the NetQueue. U8 *buf; I64 len; diff --git a/src/Kernel/Usb/KUsb.HH b/src/Kernel/Usb/KUsb.HH index 146ac5b8a..7d1305f4b 100644 --- a/src/Kernel/Usb/KUsb.HH +++ b/src/Kernel/Usb/KUsb.HH @@ -8,6 +8,7 @@ #define USB_DEVS_MAX 16 #define USB_EPS_MAX 8 #define USB_IFS_MAX 6 +#define USB_HCS_MAX 4 //xHCI controllers driven at once //USB descriptor types #define USB_DESC_DEV 1 @@ -62,7 +63,7 @@ #define USB_MSD_RQ_RESET 0xFF #define USB_MSD_RQ_GET_MAX_LUN 0xFE -//CDC / Ethernet Networking Control Model (ECM) +//CDC / Ethernet Networking Control Model #define USB_CLASS_COMM 0x02 //CDC communications (control interface) #define USB_CLASS_CDC_DATA 0x0A //CDC data interface (bulk endpoints) #define USB_CDC_SUBCLASS_ECM 0x06 //Ethernet Networking Control Model @@ -78,8 +79,7 @@ //Hub class #define USB_DESC_HUB 0x29 //hub descriptor (USB2) #define USB_DESC_SSHUB 0x2A //SuperSpeed hub descriptor -//SuperSpeed hubs must be told their tier before they can route packets -//(USB 3.x 10.14.2.7). Without it, downstream Address Device fails. +//SuperSpeed hubs must be told their tier before they can route (USB 3.x 10.14.2.7). #define USB_HUB_RQ_SET_DEPTH 12 //Hub class feature selectors (port) #define USB_HUB_F_PORT_CONNECTION 0 @@ -253,14 +253,13 @@ class CUsbEPDesc #define XHCI_RING_TRBS 256 //4096 / 16 #define XHCI_EVT_RING_TRBS 256 -//Interrupt/bulk-IN auto-requeue report handler kinds (see CUsbEP.auto_kind). +//Auto-requeue handler kinds (CUsbEP.auto_kind). #define USB_AUTO_NONE 0 #define USB_AUTO_KBD 1 #define USB_AUTO_MOUSE 2 #define USB_AUTO_ECM 3 //CDC-ECM bulk-IN: receive ethernet frame -//Forward decls: report handlers live in UsbHid.ZC / KUsbEcm.ZC (compiled after -//KXhci.ZC), but XhciEventPoll dispatches to them. Single-pass compiler needs these. +//XhciEventPoll dispatches to handlers compiled after it. extern U0 UsbKbdCB(U8 *dev, U8 *ep); extern U0 UsbMouseCB(U8 *dev, U8 *ep); extern U0 UsbEcmRxCB(U8 *dev, U8 *ep); @@ -312,9 +311,35 @@ class CUsbIf ep_count; }; +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; +}; + class CUsbDev { Bool present, configured; + CXhciGlobals *hc; //owning controller; slot/port are unique only within it I64 slot, //xHCI slot id port, //root port index (0-based) speed; //USB_SPEED_* @@ -328,8 +353,7 @@ class CUsbDev I64 vendor_id, product_id, dev_class, dev_subclass, dev_proto, config_val; -//Topology: root devices have route 0, depth 0, no parent. Devices behind hubs -//carry an xHCI route string + the parent hub's slot/port (for TT of LS/FS). +//Topology. Root devices: route 0, depth 0, no parent. I64 route, //xHCI route string (20 bits) depth, //hub tier (0 = attached to a root port) parent_slot, //parent hub xHCI slot id (0 = root) @@ -342,39 +366,15 @@ class CUsbDev 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; -}; - -//Hub driver (KUsbHub.ZC) enumerates downstream devices then binds them via -//UsbDrvBind (KUsbBoot.ZC, compiled later): forward decls for the single-pass -//compiler. Declared here (after CUsbDev/CUsbIf) so the struct types are known. +//KUsbHub.ZC calls UsbDrvBind from KUsbBoot.ZC, compiled later. Declared after +//CUsbDev/CUsbIf so the types are known. extern U0 UsbDrvBind(CUsbDev *dev); extern U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif); //Globals defined in $LK,"KXhci.ZC",A="FF:::/Kernel/Usb/KXhci.ZC,usb_xhci"$. -extern CXhciGlobals usb_xhci; +extern CXhciGlobals usb_hcs[USB_HCS_MAX]; +extern I64 usb_hc_count; +extern CXhciGlobals *usb_xhci; //active controller 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 @@ -383,12 +383,10 @@ 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(); -public extern U0 XhciFindNic(I64 max_tries=8); +public extern I64 XhciInitAll(); -//CDC-ECM USB NIC interface, consumed by the /System net driver -//$LK,"UsbEcm.ZC",A="FF:::/Home/Net/Drivers/UsbEcm.ZC"$. Operates on the first NIC. +//CDC-ECM NIC interface for $LK,"UsbEcm.ZC",A="FF:::/Home/Net/Drivers/UsbEcm.ZC"$. +//Operates on the first bound NIC. public extern U0 UsbEcmRep(); public extern Bool UsbEcmPresent(); public extern U0 UsbEcmMacAddr(U8 *out6); diff --git a/src/Kernel/Usb/KUsbBoot.ZC b/src/Kernel/Usb/KUsbBoot.ZC index 4e6913760..2b3bb7624 100644 --- a/src/Kernel/Usb/KUsbBoot.ZC +++ b/src/Kernel/Usb/KUsbBoot.ZC @@ -43,31 +43,41 @@ U0 UsbDrvUnbind(CUsbDev *dev) UsbEcmDetach(dev); } -U0 UsbPortScan() -{//Check all root ports for connects/disconnects. USB task only. +U0 UsbPortScan(CXhciGlobals *hc=NULL) +{//Check one controller's root ports for connects/disconnects. USB task only. CUsbDev *dev, *ndev; - I64 port, sc; + I64 port, sc, i; - for (port = 0; port < usb_xhci.max_ports; port++) + if (!hc) hc = usb_xhci; + for (port = 0; port < hc->max_ports; port++) { - sc = XhciPortSC(port); + sc = XhciPortSC(port, hc); if (sc & XHCI_PORTSC_CHANGE_BITS) - XhciPortSCWrite(port, XHCI_PORTSC_PP | (sc & XHCI_PORTSC_CHANGE_BITS)); - dev = UsbDevByPort(port); + XhciPortSCWrite(port, XHCI_PORTSC_PP | (sc & XHCI_PORTSC_CHANGE_BITS), hc); + dev = UsbDevByPort(port, hc); if (sc & XHCI_PORTSC_CCS) { if (!dev) { - if (XhciPortReset(port)) + if (XhciPortReset(port, hc)) { - ndev = UsbDevEnum(port); + ndev = UsbDevEnum(port, hc); if (ndev) UsbDrvBind(ndev); } } } else if (dev) - { + {//Drop the device and anything a hub on this port was carrying; hub + //children share the root port number and have no connect status of + //their own, so nothing else would ever reap them. + for (i = 0; i < USB_DEVS_MAX; i++) + if (usb_devs[i].present && usb_devs[i].hc == hc && + usb_devs[i].port == port && usb_devs[i].depth) + { + UsbDrvUnbind(&usb_devs[i]); + UsbDevDetachCore(&usb_devs[i]); + } UsbDrvUnbind(dev); UsbDevDetachCore(dev); SysLog("USB: port %d: device removed.\n", port); @@ -75,33 +85,45 @@ U0 UsbPortScan() } } +U0 UsbPortScanAll() +{//Scan every running controller. + I64 n; + + for (n = 0; n < usb_hc_count; n++) + UsbPortScan(&usb_hcs[n]); +} + public U0 UsbRep() {//Report attached USB devices. - CUsbDev *dev, *ndev; - CUsbIf *uif; - I64 i, j; + CUsbDev *dev; + CUsbIf *uif; + CXhciGlobals *hc; + I64 i, j, n; - "$$FG,GREEN$$UsbRep BUILD MARKER: ss-hubdepth-5$$FG$$\n"; //bump each rebuild to catch stale kernels - - if (!usb_xhci.present) + if (!usb_hc_count) { "No xHCI controller. Re-probing:\n"; - XhciInit; + XhciInitAll; } - if (!usb_xhci.present) + if (!usb_hc_count) 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; + UsbPortScanAll; //rescan so a just-plugged device shows up + for (n = 0; n < usb_hc_count; n++) + { + hc = &usb_hcs[n]; + "xHCI #%d: %d ports, %d slots.\n", n, hc->max_ports, hc->max_slots; + for (i = 0; i < hc->max_ports; i++) + " port %d: PORTSC=0x%08X CCS=%d PED=%d PP=%d\n", i, XhciPortSC(i, hc), + XhciPortSC(i, hc) & 1, XhciPortSC(i, hc) >> 1 & 1, + XhciPortSC(i, hc) >> 9 & 1; + } for (i = 0; i < USB_DEVS_MAX; i++) { dev = &usb_devs[i]; if (dev->present) { - "Port %2d: %04X:%04X devcls=%d \"%s\"", dev->port, dev->vendor_id, - dev->product_id, dev->dev_class, dev->desc_name; + "hc%d port %2d: %04X:%04X \"%s\"", XhciHcIdx(dev->hc), dev->port, + dev->vendor_id, dev->product_id, dev->desc_name; for (j = 0; j < dev->if_count; j++) { uif = &dev->ifs[j]; @@ -120,13 +142,6 @@ public U0 UsbRep() " [class %d]", uif->if_class; } "\n"; - for (j = 0; j < dev->if_count; j++) - { - uif = &dev->ifs[j]; - " if%d: cls=%d sub=%d proto=%d alt=%d eps=%d mac_str=%d\n", - uif->if_num, uif->if_class, uif->if_subclass, uif->if_proto, - uif->alt, uif->ep_count, uif->ecm_mac_str_idx; - } } } for (i = 0; i < USB_MSDS_MAX; i++) @@ -135,84 +150,29 @@ public U0 UsbRep() ((usb_msds[i].max_lba + 1) * BLK_SIZE) / 0x100000, usb_msds[i].first_drive_let; -//Diagnose root ports that are connected but have no bound device (enumeration -//gaps, e.g. SuperSpeed). Re-attempt enumeration HERE so the enum trace + direct -//failure prints land in this terminal (usb_task SysLogs are hidden single-user). - for (i = 0; i < usb_xhci.max_ports; i++) - { - if ((XhciPortSC(i) & XHCI_PORTSC_CCS) && !UsbDevByPort(i)) - { - "UNBOUND port %d: PORTSC=0x%08X speed=%d - retrying enum:\n", - i, XhciPortSC(i), (XhciPortSC(i) >> 10) & 0xF; - if (XhciPortReset(i)) - { - ndev = UsbDevEnum(i); - if (ndev) - { - UsbDrvBind(ndev); - " port %d: enumerated OK.\n", i; - } - } - else - " port %d: XhciPortReset did not enable the port.\n", i; - } - } -} - -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; -} - -public U0 XhciFindNic(I64 max_tries=8) -{//Cycle controllers until one has a USB ethernet NIC bound. A USB-C dock puts -//the SuperSpeed NIC on the Thunderbolt controller while HID sits on the PCH one, -//and this stack drives a single controller at a time - this hunts for the NIC. - I64 i; - - for (i = 0; i < max_tries; i++) - { - if (UsbEcmPresent) - { - "XhciFindNic: NIC found.\n"; - UsbEcmRep; - return; - } - XhciSwitch; - } - "XhciFindNic: no USB NIC found after %d controller switches.\n", max_tries; } U0 UsbTaskMain(I64 dummy=0) {//Hotplug + HID poll loop. Spawned from $LK,"KMain",A="FF:::/Kernel/KMain.ZC,UsbTaskMain"$. - I64 tick = 0; + I64 tick = 0, n; no_warn dummy; usb_task = Fs; - if (!usb_xhci.running) + if (!usb_hc_count) return; while (TRUE) { - XhciEventPoll; + for (n = 0; n < usb_hc_count; n++) + XhciEventPoll(&usb_hcs[n]); UsbHidTick; + UsbEcmTick; tick++; - if (usb_xhci.port_change || tick & 0x7F == 0) - { - usb_xhci.port_change = FALSE; - UsbPortScan; - } + for (n = 0; n < usb_hc_count; n++) + if (usb_hcs[n].port_change || tick & 0x7F == 0) + { + usb_hcs[n].port_change = FALSE; + UsbPortScan(&usb_hcs[n]); + } Sleep(2); } } @@ -223,10 +183,10 @@ I64 UsbBootInit() //the kernel config's mount code runs, so USB drives exist at boot. I64 i, count = 0; - if (!XhciInit) + if (!XhciInitAll) return 0; Sleep(100); //let root port connect status settle - UsbPortScan; + UsbPortScanAll; for (i = 0; i < USB_MSDS_MAX; i++) if (usb_msds[i].valid && usb_msds[i].ready && usb_msds[i].bd) count++; diff --git a/src/Kernel/Usb/KUsbCore.ZC b/src/Kernel/Usb/KUsbCore.ZC index 9b629ab03..6dc167053 100644 --- a/src/Kernel/Usb/KUsbCore.ZC +++ b/src/Kernel/Usb/KUsbCore.ZC @@ -65,7 +65,7 @@ CUsbDev *UsbDevAlloc() } CUsbIf *UsbIfByNum(CUsbDev *dev, I64 if_num) -{//Return the recorded (alt-0) interface with this bInterfaceNumber, else NULL. +{//Recorded (alt-0) interface with this bInterfaceNumber, else NULL. I64 i; for (i = 0; i < dev->if_count; i++) @@ -76,15 +76,14 @@ CUsbIf *UsbIfByNum(CUsbDev *dev, I64 if_num) } I64 UsbSetInterface(CUsbDev *dev, I64 if_num, I64 alt) -{//SET_INTERFACE: select an alternate setting to activate its endpoints. +{//Select an alternate setting to activate its endpoints. return UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_RECIP_IF, USB_RQ_SET_INTERFACE, alt, if_num, NULL, 0); } I64 UsbPickEcmConfig(CUsbDev *dev, I64 num_configs) -{//Scan each configuration for a CDC-ECM control interface; return its config -//descriptor index (for GET/SET_CONFIGURATION), else 0. RTL8153 and QEMU usb-net -//are multi-config and their ECM configuration is not index 0. +{//Config descriptor index holding a CDC-ECM control interface, else 0. +//RTL8153 and QEMU usb-net are multi-config; ECM is not index 0. CUsbConfigDesc *cd; CUsbIfDesc *ifd; U8 *buf; @@ -167,16 +166,15 @@ U0 UsbParseConfig(CUsbDev *dev, U8 *buf, I64 len) cur_if = NULL; } else - {//Alternate setting: attach its endpoints to the matching alt-0 interface - //and remember the alt number (CDC-ECM data endpoints live under alt 1). + {//Alternate setting: its endpoints belong to the matching alt-0 + //interface. CDC-ECM data endpoints live under alt 1. cur_if = UsbIfByNum(dev, ifd->if_num); if (cur_if) cur_if->alt = ifd->alt; } } else if (dtype == USB_DESC_CS_IF) - {//Class-specific interface descriptor. Grab the CDC-ECM Ethernet functional - //descriptor's iMACAddress string index (it follows the comm interface). + {//CDC-ECM Ethernet functional descriptor: iMACAddress string index. if (cur_if && dlen >= 4 && buf[off + 2] == USB_CDC_ST_ETHERNET) cur_if->ecm_mac_str_idx = buf[off + 3]; } @@ -218,8 +216,8 @@ U0 UsbDevDetachCore(CUsbDev *dev) dev->configured = FALSE; if (dev->slot) { - XhciDisableSlot(dev->slot); - usb_xhci.dcbaa[dev->slot] = 0; + XhciDisableSlot(dev->slot, dev->hc); + dev->hc->dcbaa[dev->slot] = 0; } dev->slot = 0; dev->ep0.valid = FALSE; @@ -232,17 +230,18 @@ U0 UsbDevDetachCore(CUsbDev *dev) } CUsbDev *UsbDevEnumEx(I64 root_port, I64 speed, I64 route, I64 depth, - I64 parent_slot, I64 parent_port) -{//Enumerate an already-reset+enabled device. Root-port devices pass route 0 / -//depth 0 / no parent; hub children pass the xHCI route string + parent hub -//slot/port (see KUsbHub.ZC). USB task only. + I64 parent_slot, I64 parent_port, CXhciGlobals *hc=NULL) +{//Enumerate an already-reset+enabled device. Root-port devices pass route 0, +//depth 0, no parent; hub children pass a route string + parent slot/port. +//USB task only. CUsbDev *dev; CUsbDevDesc *dd; CUsbConfigDesc *cd; U8 *buf; I64 slot, code, total, mps0, cfg_idx, port = root_port; - slot = XhciEnableSlot; + if (!hc) hc = usb_xhci; + slot = XhciEnableSlot(hc); if (!slot) { "USB: port %d: Enable Slot failed.\n", port; @@ -251,9 +250,10 @@ CUsbDev *UsbDevEnumEx(I64 root_port, I64 speed, I64 route, I64 depth, dev = UsbDevAlloc; if (!dev) { - XhciDisableSlot(slot); + XhciDisableSlot(slot, hc); return NULL; } + dev->hc = hc; dev->slot = slot; dev->port = root_port; dev->speed = speed; @@ -270,13 +270,9 @@ CUsbDev *UsbDevEnumEx(I64 root_port, I64 speed, I64 route, I64 depth, dev->drv_data = NULL; dev->desc_name[0] = 0; -//Direct print (not SysLog): visible from any task, incl. UsbRep's re-enum. - "USB: enum port %d speed %d route 0x%05X depth %d slot %d\n", - root_port, speed, route, depth, slot; - code = XhciAddressDev(dev); if (code != XHCI_CC_SUCCESS) - {//One retry: a SuperSpeed device behind a hub can be slow to settle after reset. + {//A SuperSpeed device behind a hub can be slow to settle after reset. Sleep(100); code = XhciAddressDev(dev); } @@ -321,14 +317,10 @@ CUsbDev *UsbDevEnumEx(I64 root_port, I64 speed, I64 route, I64 depth, dev->dev_proto = dd->dev_proto; UsbStrGet(dev, dd->i_product, dev->desc_name, 64); -//Pick a configuration. Most devices are single-config (index 0); multi-config -//USB NICs (RTL8153, QEMU usb-net) hide their CDC-ECM config behind a later index, -//so prefer that one when present. +//Multi-config USB NICs hide CDC-ECM behind a later index; prefer it. cfg_idx = 0; if (dd->num_configs > 1) cfg_idx = UsbPickEcmConfig(dev, dd->num_configs); - SysLog("USB: port %d: %04X:%04X num_configs=%d picked cfg_idx=%d\n", - port, dd->vendor_id, dd->product_id, dd->num_configs, cfg_idx); //Config descriptor: header first for total length, then full. code = UsbGetDesc(dev, USB_DESC_CONFIG, cfg_idx, buf, 9); @@ -380,7 +372,7 @@ CUsbDev *UsbDevEnumEx(I64 root_port, I64 speed, I64 route, I64 depth, return dev; } -CUsbDev *UsbDevEnum(I64 port) +CUsbDev *UsbDevEnum(I64 port, CXhciGlobals *hc=NULL) {//Enumerate a device on a root port (already reset + enabled). USB task only. - return UsbDevEnumEx(port, (XhciPortSC(port) >> 10) & 0xF, 0, 0, 0, 0); + return UsbDevEnumEx(port, (XhciPortSC(port, hc) >> 10) & 0xF, 0, 0, 0, 0, hc); } diff --git a/src/Kernel/Usb/KUsbEcm.ZC b/src/Kernel/Usb/KUsbEcm.ZC index b2a9e3b70..97d0b6ccb 100644 --- a/src/Kernel/Usb/KUsbEcm.ZC +++ b/src/Kernel/Usb/KUsbEcm.ZC @@ -1,16 +1,16 @@ -//USB CDC-ECM (Ethernet Networking Control Model) class driver. -//Binds a USB Ethernet NIC (RTL8153 in its ECM config, QEMU usb-net CDC config), -//exposes raw Ethernet frame RX/TX over the data interface's bulk endpoints. -//The /System net driver $LK,"UsbEcm.ZC",A="FF:::/Home/Net/Drivers/UsbEcm.ZC"$ -//drives it via the public UsbEcm* interface (declared in KUsb.HH). +//USB CDC-ECM class driver. Raw ethernet frames over the data interface's bulk +//endpoints. Consumed by the net driver +//$LK,"UsbEcm.ZC",A="FF:::/Home/Net/Drivers/UsbEcm.ZC"$ via the public UsbEcm*. // -//RX is event-driven like HID: a bulk-IN TRB auto-requeues in XhciEventPoll -//(auto_kind == USB_AUTO_ECM), and UsbEcmRxCB pushes each received frame into a -//single-producer/single-consumer ring the net task drains via UsbEcmRecv. +//Both directions are queues drained by the USB task, because the net stack +//transmits from the NetHandler interrupt (TCP ACKs) where blocking on a USB +//transfer is not possible. RX auto-requeues a bulk-IN TRB in XhciEventPoll +//(USB_AUTO_ECM); TX is enqueued by UsbEcmSend and sent from UsbEcmTick. #define USB_ECMS_MAX 2 -#define USB_ECM_FRAME_MAX 1600 //>= max ethernet frame (1514) + slack +#define USB_ECM_FRAME_MAX 2048 //>= ETHERNET_FRAME_SIZE, the net stack's buffer #define USB_ECM_RXQ 16 //RX ring depth (power of two) +#define USB_ECM_TXQ 16 //TX ring depth (power of two) class CUsbEcmFrame { @@ -23,32 +23,25 @@ class CUsbEcm Bool valid, link_up; CUsbDev *dev; CUsbEP *bulk_in, *bulk_out, *int_ep; - I64 data_if_num, comm_if_num, data_alt, - lock; + I64 data_if_num, comm_if_num, data_alt; U8 mac[6]; - U8 *tx_buf; //bulk OUT DMA staging -//RX ring: producer = UsbEcmRxCB (USB task), consumer = UsbEcmRecv (net task). +//RX ring: UsbEcmRxCB produces (USB task), UsbEcmRecv consumes (net task). CUsbEcmFrame *rxq; I64 rxq_head, //consumer index rxq_tail, //producer index rx_drops; +//TX ring: any caller produces (incl. interrupt context), UsbEcmTick consumes. +//A slot's len is written last and cleared when sent, so it doubles as "ready". + CUsbEcmFrame *txq; + I64 txq_head, //consumer index + txq_tail, //producer index + tx_drops; }; CUsbEcm usb_ecms[USB_ECMS_MAX]; -U0 UsbEcmLock(CUsbEcm *ecm) -{ - while (LBts(&ecm->lock, 0)) - Yield; -} - -U0 UsbEcmUnlock(CUsbEcm *ecm) -{ - LBtr(&ecm->lock, 0); -} - CUsbEcm *UsbEcmFirst() -{//First bound NIC, else NULL. v1 supports a single active NIC. +{//First bound NIC, else NULL. I64 i; for (i = 0; i < USB_ECMS_MAX; i++) @@ -91,8 +84,7 @@ Bool UsbEcmMacParse(CUsbDev *dev, I64 str_idx, U8 *mac) } U0 UsbEcmRxCB(U8 *_dev, U8 *_ep) -{//Bulk-IN completion (USB task). Copy the frame into the RX ring and requeue -//happens back in XhciEventPoll. Received length = auto_len - residue. +{//Bulk-IN completion. Length = auto_len - residue; XhciEventPoll requeues. CUsbDev *dev = _dev; CUsbEP *ep = _ep; CUsbEcm *ecm = dev->drv_data; @@ -119,25 +111,67 @@ U0 UsbEcmRxCB(U8 *_dev, U8 *_ep) } public I64 UsbEcmSend(U8 *frame, I64 len) -{//Send one ethernet frame over bulk OUT. 0 = ok, <0 = error. - CUsbEcm *ecm = UsbEcmFirst; - I64 code; +{//Queue one ethernet frame for transmit. Returns 0 if queued, <0 if not. +//Callable from interrupt context (the net stack ACKs from NetHandler), so this +//never blocks: UsbEcmTick performs the transfer. + CUsbEcm *ecm = UsbEcmFirst; + CUsbEcmFrame *slot; + I64 idx, next; if (!ecm || !ecm->valid || len <= 0 || len > USB_ECM_FRAME_MAX) return -1; - UsbEcmLock(ecm); - MemCopy(ecm->tx_buf, frame, len); - code = XhciBulkXfer(ecm->dev, ecm->bulk_out, ecm->tx_buf, len, 1.0); - if (code == XHCI_CC_STALL) + PUSHFD + CLI + idx = ecm->txq_tail; + next = (idx + 1) & (USB_ECM_TXQ - 1); + if (next == ecm->txq_head) { - UsbClearHalt(ecm->dev, ecm->bulk_out); - code = XhciBulkXfer(ecm->dev, ecm->bulk_out, ecm->tx_buf, len, 1.0); + POPFD + ecm->tx_drops++; + return -1; } - UsbEcmUnlock(ecm); - if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) - return 0; + ecm->txq_tail = next; + POPFD + slot = &ecm->txq[idx]; + MemCopy(slot->data, frame, len); + slot->len = len; //publish last: the consumer waits on a non-zero len - return -1; + return 0; +} + +U0 UsbEcmTick() +{//Drain the TX ring onto bulk OUT. USB task only. + CUsbEcm *ecm; + CUsbEcmFrame *slot; + I64 i, len, code; + + for (i = 0; i < USB_ECMS_MAX; i++) + { + ecm = &usb_ecms[i]; + if (!ecm->valid) + goto next_ecm; + while (ecm->txq_head != ecm->txq_tail) + { + slot = &ecm->txq[ecm->txq_head]; + len = slot->len; + if (!len) + break; //producer still copying + code = XhciBulkXfer(ecm->dev, ecm->bulk_out, slot->data, len, 1.0); + if (code == XHCI_CC_STALL) + { + UsbClearHalt(ecm->dev, ecm->bulk_out); + code = XhciBulkXfer(ecm->dev, ecm->bulk_out, slot->data, len, 1.0); + } +//A frame that exactly fills the last packet needs a zero-length packet to mark +//the end of the transfer, or the device keeps waiting for more. + if ((code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) && + !(len % ecm->bulk_out->max_packet)) + XhciBulkXfer(ecm->dev, ecm->bulk_out, slot->data, 0, 1.0); + slot->len = 0; + ecm->txq_head = (ecm->txq_head + 1) & (USB_ECM_TXQ - 1); + } +next_ecm:; + } } public I64 UsbEcmRecv(U8 *dst, I64 max) @@ -168,7 +202,7 @@ public Bool UsbEcmPresent() } public U0 UsbEcmRep() -{//Report USB NIC state: presence, MAC, ring stats. On-demand (boot log scrolls). +{//Report NIC state: link, MAC, endpoints, ring stats. CUsbEcm *ecm = UsbEcmFirst; I64 pending; @@ -182,9 +216,11 @@ public U0 UsbEcmRep() ecm->dev->port, ecm->link_up, ecm->mac[0], ecm->mac[1], ecm->mac[2], ecm->mac[3], ecm->mac[4], ecm->mac[5]; - " data_if=%d alt=%d bulk_in=0x%02X bulk_out=0x%02X rxq=%d rx_drops=%d\n", + " data_if=%d alt=%d bulk_in=0x%02X bulk_out=0x%02X\n", ecm->data_if_num, ecm->data_alt, - ecm->bulk_in->addr, ecm->bulk_out->addr, pending, ecm->rx_drops; + ecm->bulk_in->addr, ecm->bulk_out->addr; + " rxq=%d rx_drops=%d txq=%d tx_drops=%d\n", pending, ecm->rx_drops, + (ecm->txq_tail - ecm->txq_head) & (USB_ECM_TXQ - 1), ecm->tx_drops; } public U0 UsbEcmMacAddr(U8 *out6) @@ -198,8 +234,8 @@ public U0 UsbEcmMacAddr(U8 *out6) } U0 UsbEcmDetach(CUsbDev *dev) -{//Release NIC state on disconnect. Keep DMA/ring allocations for reuse. -//Scan our own array by dev (UsbDrvUnbind calls every class detacher). +{//Release NIC state on disconnect; keep allocations. Matched by dev, since +//UsbDrvUnbind calls every class detacher. I64 i; for (i = 0; i < USB_ECMS_MAX; i++) @@ -212,8 +248,8 @@ U0 UsbEcmDetach(CUsbDev *dev) } U0 UsbEcmAttach(CUsbDev *dev, CUsbIf *uif) -{//uif = the CDC-ECM control (comm) interface. Pair it with the CDC-Data -//interface, activate its bulk endpoints, read the MAC, start RX. +{//uif = CDC-ECM control interface. Pairs it with the CDC-Data interface, +//activates its bulk endpoints, reads the MAC, starts RX. CUsbEcm *ecm = NULL; CUsbIf *data_if = NULL; CUsbEP *ep; @@ -231,7 +267,7 @@ U0 UsbEcmAttach(CUsbDev *dev, CUsbIf *uif) return; } -//Locate the CDC-Data interface (class 0x0A) that carries the bulk endpoints. +//CDC-Data interface carries the bulk endpoints. for (i = 0; i < dev->if_count; i++) if (dev->ifs[i].valid && dev->ifs[i].if_class == USB_CLASS_CDC_DATA && dev->ifs[i].ep_count) @@ -245,8 +281,8 @@ U0 UsbEcmAttach(CUsbDev *dev, CUsbIf *uif) return; } -//Clear state but keep persistent DMA + ring allocations. - MemSet(ecm, 0, offset(CUsbEcm.tx_buf)); +//Clear state, keep allocations. + MemSet(ecm, 0, offset(CUsbEcm.rxq)); ecm->dev = dev; ecm->comm_if_num = uif->if_num; ecm->data_if_num = data_if->if_num; @@ -263,7 +299,7 @@ U0 UsbEcmAttach(CUsbDev *dev, CUsbIf *uif) ecm->bulk_out = ep; } } -//Notification endpoint (INT IN) on the comm interface; optional for v1. +//Notification endpoint; unused. for (i = 0; i < uif->ep_count; i++) { ep = &dev->ep_list[uif->ep_idxs[i]]; @@ -276,7 +312,7 @@ U0 UsbEcmAttach(CUsbDev *dev, CUsbIf *uif) return; } -//Activate the data interface's alt setting that exposes the bulk endpoints. +//Bulk endpoints live on a non-zero alt setting. if (ecm->data_alt) { code = UsbSetInterface(dev, ecm->data_if_num, ecm->data_alt); @@ -285,34 +321,36 @@ U0 UsbEcmAttach(CUsbDev *dev, CUsbIf *uif) dev->port, ecm->data_alt, code); } -//MAC from the ECM functional descriptor's iMACAddress string. +//MAC from the ECM functional descriptor. if (!uif->ecm_mac_str_idx || !UsbEcmMacParse(dev, uif->ecm_mac_str_idx, ecm->mac)) MemSet(ecm->mac, 0, 6); -//Tell the device which frames to deliver: directed + broadcast + multicast. +//Frames to deliver: directed + broadcast + multicast. UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_IF, USB_CDC_RQ_SET_ETH_PACKET_FILTER, USB_CDC_PACKET_TYPE_DIRECTED | USB_CDC_PACKET_TYPE_BROADCAST | USB_CDC_PACKET_TYPE_ALL_MULTICAST, ecm->comm_if_num, NULL, 0); - if (!ecm->tx_buf) - ecm->tx_buf = CAlloc(USB_ECM_FRAME_MAX, sys_task->code_heap); if (!ecm->rxq) ecm->rxq = CAlloc(sizeof(CUsbEcmFrame) * USB_ECM_RXQ, sys_task->code_heap); + if (!ecm->txq) + ecm->txq = CAlloc(sizeof(CUsbEcmFrame) * USB_ECM_TXQ, sys_task->code_heap); ecm->rxq_head = 0; ecm->rxq_tail = 0; + ecm->txq_head = 0; + ecm->txq_tail = 0; -//Arm the bulk-IN endpoint for auto-requeued receive (like HID interrupt IN). +//Arm bulk-IN for auto-requeued receive. if (!ecm->bulk_in->auto_buf) ecm->bulk_in->auto_buf = CAlloc(USB_ECM_FRAME_MAX, sys_task->code_heap); ecm->bulk_in->auto_len = USB_ECM_FRAME_MAX; ecm->bulk_in->auto_kind = USB_AUTO_ECM; ecm->valid = TRUE; - ecm->link_up = TRUE; //assume up; notification EP can refine later + ecm->link_up = TRUE; //no notification-endpoint link tracking dev->drv_data = ecm; - XhciIntQueue(dev, ecm->bulk_in); //kick off first receive + XhciIntQueue(dev, ecm->bulk_in); //first receive SysLog("USB: ECM port %d: NIC ready, MAC %02X:%02X:%02X:%02X:%02X:%02X\n", dev->port, ecm->mac[0], ecm->mac[1], ecm->mac[2], diff --git a/src/Kernel/Usb/KUsbHub.ZC b/src/Kernel/Usb/KUsbHub.ZC index 819072f6a..754adbf53 100644 --- a/src/Kernel/Usb/KUsbHub.ZC +++ b/src/Kernel/Usb/KUsbHub.ZC @@ -1,11 +1,5 @@ -//USB hub class driver: enumerate devices attached to downstream hub ports. -//Needed because USB NICs (and many devices) sit behind hubs - both QEMU's -//usb-net and the RTL8153 USB-C adapter enumerate through a hub tier. -// -//v1: at attach time we power every downstream port, then reset + enumerate -//whatever is already connected. No status-change-endpoint hotplug polling yet. -//Downstream devices are addressed with an xHCI route string + the parent hub's -//slot/port for TT (see UsbDevEnumEx / XhciAddressDev). +//USB hub class driver. Enumerates devices on downstream ports at attach time; +//no status-change-endpoint hotplug polling. I64 UsbHubGetPortStatus(CUsbDev *hub, I64 port, U32 *_status) {//wPortStatus (low 16) + wPortChange (high 16). @@ -67,14 +61,14 @@ keep_waiting:; } U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif) -{//Configure the hub, power its ports, enumerate connected downstream devices. +{//Configure the hub, power its ports, enumerate what's connected. CUsbDev *ndev; U8 buf[16]; U32 st; I64 i, code, num_ports, hub_char, desc_type, child_speed, child_route; no_warn uif; -//xHCI route strings are 5 nibbles: a hub deeper than tier 5 can't route children. +//Route strings are 5 nibbles. if (dev->depth >= 5) { SysLog("USB: hub port %d: max hub depth reached, ignoring.\n", dev->port); @@ -97,25 +91,27 @@ U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif) dev->is_hub = TRUE; dev->hub_ports = num_ports; dev->hub_tt_think = (hub_char >> 5) & 3; - dev->hub_mtt = 0; //v1: single-TT (default hub alt setting) + dev->hub_mtt = 0; //single-TT -//Re-issue Configure Endpoint so the HC learns this slot is a hub (Hub bit + -//port count + TT think time now set in XhciConfigureEPs). Direct-print the -//result: if this fails, downstream Address Device gets a Transaction Error. +//Re-Configure so the HC sees the Hub bit, port count and TT think time. code = XhciConfigureEPs(dev); - "USB: hub port %d: cfg-as-hub code=%d, ports=%d, TTT=%d, depth=%d, speed=%d\n", - dev->port, code, num_ports, dev->hub_tt_think, dev->depth, dev->speed; + if (code != XHCI_CC_SUCCESS) + SysLog("USB: hub port %d: configure-as-hub failed (%d).\n", dev->port, code); -//SuperSpeed hubs route by route string only after being told their tier -//(USB 3.x 10.14.2.7 SetHubDepth). Skipping this makes every downstream -//Address Device fail with a Transaction Error. USB2 hubs don't use it. +//A SuperSpeed hub won't route by route string until told its tier (USB 3.x +//10.14.2.7). Without this every downstream Address Device fails with a +//Transaction Error. USB2 hubs don't use it. if (dev->speed == USB_SPEED_SUPER) { code = UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_DEV, USB_HUB_RQ_SET_DEPTH, dev->depth, 0, NULL, 0); - "USB: hub port %d: SET_HUB_DEPTH(%d) code=%d\n", dev->port, dev->depth, code; + if (code != XHCI_CC_SUCCESS) + SysLog("USB: hub port %d: SET_HUB_DEPTH failed (%d).\n", dev->port, code); } + SysLog("USB: hub port %d: %d ports, TTT=%d, depth=%d.\n", + dev->port, num_ports, dev->hub_tt_think, dev->depth); + for (i = 1; i <= num_ports; i++) UsbHubSetPortFeature(dev, i, USB_HUB_F_PORT_POWER); Sleep(100); //power-good settle @@ -131,14 +127,14 @@ U0 UsbHubAttach(CUsbDev *dev, CUsbIf *uif) Sleep(100); //connect debounce if (!UsbHubPortReset(dev, i, &child_speed)) { - "USB: hub port %d.%d: reset failed.\n", dev->port, i; + SysLog("USB: hub port %d.%d: reset failed.\n", dev->port, i); goto next_port; } - Sleep(50); //post-reset recovery (SS link training needs more than 10ms) -//Child route: append this port number at the hub's tier nibble. + Sleep(50); //post-reset recovery; SS link training needs >10ms +//Append this port number at the hub's tier nibble. child_route = dev->route | (i << (4 * dev->depth)); ndev = UsbDevEnumEx(dev->port, child_speed, child_route, dev->depth + 1, - dev->slot, i); + dev->slot, i, dev->hc); if (ndev) UsbDrvBind(ndev); next_port:; diff --git a/src/Kernel/Usb/KXhci.ZC b/src/Kernel/Usb/KXhci.ZC index d9ad24e2a..8ddcd5f89 100644 --- a/src/Kernel/Usb/KXhci.ZC +++ b/src/Kernel/Usb/KXhci.ZC @@ -1,82 +1,108 @@ //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; +//Sync transfers may be awaited from any task; a per-controller poll_lock keeps +//the boot task and the USB task from draining an event ring concurrently. +// +//All controllers run at once: a machine has several (Thunderbolt/USB4 plus PCH) +//and a USB-C dock can land an ethernet NIC on one while HID sits on another. +//usb_xhci is only the default for calls that don't name a controller. +CXhciGlobals usb_hcs[USB_HCS_MAX]; +I64 usb_hc_count = 0; +CXhciGlobals *usb_xhci = &usb_hcs[0]; CUsbDev usb_devs[USB_DEVS_MAX]; CTask *usb_task = NULL; U8 usb_boot_iso_drv_let = 0; -U32 XhciCapR32(I64 off) +//Controller-scoped calls take an hc; NULL means the active controller. Code +//holding a CUsbDev passes dev->hc, since slot ids and port numbers are only +//unique within one controller. +U32 XhciCapR32(I64 off, CXhciGlobals *hc=NULL) { - U32 *p = usb_xhci.mmio + off; + U32 *p; + + if (!hc) hc = usb_xhci; + p = hc->mmio + off; return *p; } -U32 XhciOpR32(I64 off) +U32 XhciOpR32(I64 off, CXhciGlobals *hc=NULL) { - U32 *p = usb_xhci.op + off; + U32 *p; + + if (!hc) hc = usb_xhci; + p = hc->op + off; return *p; } -U0 XhciOpW32(I64 off, I64 val) +U0 XhciOpW32(I64 off, I64 val, CXhciGlobals *hc=NULL) { - U32 *p = usb_xhci.op + off; + U32 *p; + if (!hc) hc = usb_xhci; + p = hc->op + off; *p = val; } -U0 XhciOpW64(I64 off, I64 val) +U0 XhciOpW64(I64 off, I64 val, CXhciGlobals *hc=NULL) { - U64 *p = usb_xhci.op + off; + U64 *p; + if (!hc) hc = usb_xhci; + p = hc->op + off; *p = val; } -U32 XhciRtR32(I64 off) +U32 XhciRtR32(I64 off, CXhciGlobals *hc=NULL) { - U32 *p = usb_xhci.rt + off; + U32 *p; + + if (!hc) hc = usb_xhci; + p = hc->rt + off; return *p; } -U0 XhciRtW32(I64 off, I64 val) +U0 XhciRtW32(I64 off, I64 val, CXhciGlobals *hc=NULL) { - U32 *p = usb_xhci.rt + off; + U32 *p; + if (!hc) hc = usb_xhci; + p = hc->rt + off; *p = val; } -U0 XhciRtW64(I64 off, I64 val) +U0 XhciRtW64(I64 off, I64 val, CXhciGlobals *hc=NULL) { - U64 *p = usb_xhci.rt + off; + U64 *p; + if (!hc) hc = usb_xhci; + p = hc->rt + off; *p = val; } -U32 XhciPortSC(I64 port) +U32 XhciPortSC(I64 port, CXhciGlobals *hc=NULL) { - return XhciOpR32(XHCI_OP_PORTSC_BASE + 0x10 * port); + return XhciOpR32(XHCI_OP_PORTSC_BASE + 0x10 * port, hc); } -U0 XhciPortSCWrite(I64 port, I64 val) +U0 XhciPortSCWrite(I64 port, I64 val, CXhciGlobals *hc=NULL) { - XhciOpW32(XHCI_OP_PORTSC_BASE + 0x10 * port, val); + XhciOpW32(XHCI_OP_PORTSC_BASE + 0x10 * port, val, hc); } -U0 XhciLock() +U0 XhciLock(CXhciGlobals *hc=NULL) { - while (LBts(&usb_xhci.lock, 0)) + if (!hc) hc = usb_xhci; + while (LBts(&hc->lock, 0)) Yield; } -U0 XhciUnlock() +U0 XhciUnlock(CXhciGlobals *hc=NULL) { - LBtr(&usb_xhci.lock, 0); + if (!hc) hc = usb_xhci; + LBtr(&hc->lock, 0); } U0 XhciRingInit(CXhciRing *ring) @@ -123,23 +149,37 @@ CXhciTRB *XhciRingEnq(CXhciRing *ring, I64 lo, I64 hi, I64 status, I64 control) return trb; } -CUsbDev *UsbDevBySlot(I64 slot) +I64 XhciHcIdx(CXhciGlobals *hc) +{//Index of a controller in usb_hcs, or -1. + I64 n; + + for (n = 0; n < usb_hc_count; n++) + if (&usb_hcs[n] == hc) + return n; + + return -1; +} + +CUsbDev *UsbDevBySlot(I64 slot, CXhciGlobals *hc=NULL) { I64 i; + if (!hc) hc = usb_xhci; for (i = 0; i < USB_DEVS_MAX; i++) - if (usb_devs[i].present && usb_devs[i].slot == slot) + if (usb_devs[i].present && usb_devs[i].hc == hc && usb_devs[i].slot == slot) return &usb_devs[i]; return NULL; } -CUsbDev *UsbDevByPort(I64 port) -{ +CUsbDev *UsbDevByPort(I64 port, CXhciGlobals *hc=NULL) +{//Root-port devices only; hub children share their root port number. I64 i; + if (!hc) hc = usb_xhci; for (i = 0; i < USB_DEVS_MAX; i++) - if (usb_devs[i].present && usb_devs[i].port == port) + if (usb_devs[i].present && usb_devs[i].hc == hc && + usb_devs[i].port == port && !usb_devs[i].depth) return &usb_devs[i]; return NULL; @@ -159,57 +199,58 @@ CUsbEP *UsbDevEPByDCI(CUsbDev *dev, I64 dci) } U0 XhciIntQueue(CUsbDev *dev, CUsbEP *ep) -{//Queue one interrupt IN TRB on ep, report dispatched via ep->auto_kind. - XhciLock; +{//Queue one interrupt IN TRB on ep; report dispatched via ep->auto_kind. + CXhciGlobals *hc = dev->hc; + + XhciLock(hc); 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; + hc->db[dev->slot] = ep->dci; + XhciUnlock(hc); } -U0 XhciEventPoll() -{//Drain event ring. Called ONLY in USB task context. +U0 XhciEventPoll(CXhciGlobals *hc=NULL) +{//Drain one controller's event ring. USB task, or an inline waiter. CXhciTRB *e; CUsbDev *dev; CUsbEP *ep; I64 type, code, slot, dci, addr, work = FALSE; U64 *erdp; - if (!usb_xhci.running) + if (!hc) hc = usb_xhci; + if (!hc->running) return; - while (LBts(&usb_xhci.poll_lock, 0)) + while (LBts(&hc->poll_lock, 0)) Yield; while (TRUE) { - e = &usb_xhci.evt_ring[usb_xhci.evt_deq]; - if ((e->control & 1) != usb_xhci.evt_cycle) + e = &hc->evt_ring[hc->evt_deq]; + if ((e->control & 1) != hc->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; + hc->cmd_code = (e->status >> 24) & 0xFF; + hc->cmd_slot = (e->control >> 24) & 0xFF; + addr = hc->cmd_trb; + if (hc->cmd_trb && e->lo == addr & 0xFFFFFFFF) + hc->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); + dev = UsbDevBySlot(slot, hc); if (dev) { ep = UsbDevEPByDCI(dev, dci); if (ep) { if (ep->auto_kind) - {//HID interrupt IN / ECM bulk IN: deliver + requeue. - //Stash residue so byte-count-sensitive handlers (ECM) can - //compute the received length (auto_len - residue). + {//Deliver + requeue. Residue lets ECM derive the length. ep->xfer_residue = e->status & 0xFFFFFF; if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) { @@ -239,57 +280,59 @@ U0 XhciEventPoll() break; case XHCI_TRB_EVT_PORT_SC: - usb_xhci.port_change = TRUE; + hc->port_change = TRUE; break; } - usb_xhci.evt_deq++; - if (usb_xhci.evt_deq == XHCI_EVT_RING_TRBS) + hc->evt_deq++; + if (hc->evt_deq == XHCI_EVT_RING_TRBS) { - usb_xhci.evt_deq = 0; - usb_xhci.evt_cycle ^= 1; + hc->evt_deq = 0; + hc->evt_cycle ^= 1; } } if (work) { - addr = usb_xhci.evt_ring + usb_xhci.evt_deq; - erdp = usb_xhci.rt + XHCI_RT_ERDP0; + addr = hc->evt_ring + hc->evt_deq; + erdp = hc->rt + XHCI_RT_ERDP0; *erdp = addr | 8; //8 = clear Event Handler Busy } - LBtr(&usb_xhci.poll_lock, 0); + LBtr(&hc->poll_lock, 0); } -I64 XhciCmd(I64 lo, I64 hi, I64 status, I64 control, I64 *_slot=NULL) +I64 XhciCmd(I64 lo, I64 hi, I64 status, I64 control, I64 *_slot=NULL, + CXhciGlobals *hc=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; + if (!hc) hc = usb_xhci; + XhciLock(hc); + hc->cmd_done = FALSE; + hc->cmd_code = XHCI_CC_TIMEOUT; + hc->cmd_trb = XhciRingEnq(&hc->cmd_ring, lo, hi, status, control); + hc->db[0] = 0; + XhciUnlock(hc); timeout = tS + 1.0; - while (!usb_xhci.cmd_done && tS < timeout) + while (!hc->cmd_done && tS < timeout) { - XhciEventPoll; + XhciEventPoll(hc); Yield; } if (_slot) - *_slot = usb_xhci.cmd_slot; - if (!usb_xhci.cmd_done) + *_slot = hc->cmd_slot; + if (!hc->cmd_done) return XHCI_CC_TIMEOUT; - return usb_xhci.cmd_code; + return hc->cmd_code; } -I64 XhciXferWait(CUsbEP *ep, F64 timeout_s) +I64 XhciXferWait(CUsbEP *ep, F64 timeout_s, CXhciGlobals *hc=NULL) { F64 timeout = tS + timeout_s; while (!ep->xfer_done && tS < timeout) { if (Fs == usb_task || !usb_task) - XhciEventPoll; + XhciEventPoll(hc); Yield; } if (!ep->xfer_done) @@ -300,8 +343,9 @@ I64 XhciXferWait(CUsbEP *ep, F64 timeout_s) 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; + CUsbEP *ep = &dev->ep0; + CXhciGlobals *hc = dev->hc; + I64 trt, ctl, addr; if (len) { @@ -312,7 +356,7 @@ I64 XhciCtrlXfer(CUsbDev *dev, I64 rt, I64 rq, I64 val, I64 idx, U8 *buf, I64 le } else trt = XHCI_TRT_NO_DATA; - XhciLock; + XhciLock(hc); ep->xfer_done = FALSE; ep->xfer_code = XHCI_CC_TIMEOUT; ep->xfer_residue = 0; @@ -330,25 +374,26 @@ I64 XhciCtrlXfer(CUsbDev *dev, I64 rt, I64 rq, I64 val, I64 idx, U8 *buf, I64 le 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; + hc->db[dev->slot] = 1; + XhciUnlock(hc); - return XhciXferWait(ep, 1.0); + return XhciXferWait(ep, 1.0, hc); } 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; + CXhciGlobals *hc = dev->hc; + I64 addr = buf, code; - XhciLock; + XhciLock(hc); 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); + hc->db[dev->slot] = ep->dci; + XhciUnlock(hc); + code = XhciXferWait(ep, timeout_s, hc); if (_residue) *_residue = ep->xfer_residue; @@ -357,14 +402,14 @@ I64 XhciBulkXfer(CUsbDev *dev, CUsbEP *ep, U8 *buf, I64 len, F64 timeout_s, I64 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; + U32 *p = dev->in_ctx + idx * dev->hc->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; + U32 *p = dev->dev_ctx + idx * dev->hc->ctx_size; return p; } @@ -417,35 +462,36 @@ I64 XhciEPCtxType(CUsbEP *ep) return XHCI_EP_TYPE_CTRL; } -I64 XhciEnableSlot() +I64 XhciEnableSlot(CXhciGlobals *hc=NULL) {//Returns slot id, or 0 on failure. I64 slot = 0, code; - code = XhciCmd(0, 0, 0, XHCI_TRB_ENABLE_SLOT << 10, &slot); + code = XhciCmd(0, 0, 0, XHCI_TRB_ENABLE_SLOT << 10, &slot, hc); if (code != XHCI_CC_SUCCESS) return 0; return slot; } -U0 XhciDisableSlot(I64 slot) +U0 XhciDisableSlot(I64 slot, CXhciGlobals *hc=NULL) { - XhciCmd(0, 0, 0, (XHCI_TRB_DISABLE_SLOT << 10) | (slot << 24)); + XhciCmd(0, 0, 0, (XHCI_TRB_DISABLE_SLOT << 10) | (slot << 24),, hc); } I64 XhciAddressDev(CUsbDev *dev) {//Alloc contexts + EP0 ring, issue Address Device. Returns completion code. - U32 *icc, *slot_ctx, *ep0_ctx; - I64 mps, addr; + CXhciGlobals *hc = dev->hc; + 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); + dev->dev_ctx = CAllocAligned(hc->ctx_size * 32, 0x1000, sys_task->code_heap); else - MemSet(dev->dev_ctx, 0, usb_xhci.ctx_size * 32); + MemSet(dev->dev_ctx, 0, hc->ctx_size * 32); if (!dev->in_ctx) - dev->in_ctx = CAllocAligned(usb_xhci.ctx_size * 33, 0x1000, sys_task->code_heap); + dev->in_ctx = CAllocAligned(hc->ctx_size * 33, 0x1000, sys_task->code_heap); else - MemSet(dev->in_ctx, 0, usb_xhci.ctx_size * 33); + MemSet(dev->in_ctx, 0, hc->ctx_size * 33); XhciEPRingInit(&dev->ep0); dev->ep0.dci = 1; dev->ep0.valid = TRUE; @@ -483,39 +529,41 @@ I64 XhciAddressDev(CUsbDev *dev) ep0_ctx[3] = addr >> 32; ep0_ctx[4] = 8; - usb_xhci.dcbaa[dev->slot] = dev->dev_ctx; + hc->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)); + (XHCI_TRB_ADDRESS_DEV << 10) | (dev->slot << 24),, hc); } I64 XhciEvalCtxMPS0(CUsbDev *dev, I64 mps) {//Update EP0 max packet size after reading device descriptor. - U32 *icc, *ep0_ctx; - I64 addr; + CXhciGlobals *hc = dev->hc; + U32 *icc, *ep0_ctx; + I64 addr; - MemSet(dev->in_ctx, 0, usb_xhci.ctx_size * 33); + MemSet(dev->in_ctx, 0, hc->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); + MemCopy(ep0_ctx, XhciDevCtxGet(dev, 1), hc->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)); + (XHCI_TRB_EVAL_CTX << 10) | (dev->slot << 24),, hc); } 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; + CXhciGlobals *hc = dev->hc; + 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); + MemSet(dev->in_ctx, 0, hc->ctx_size * 33); icc = XhciInputCtx(dev, 0); icc[0] = 0; icc[1] = 1; //A0 = slot @@ -543,7 +591,7 @@ I64 XhciConfigureEPs(CUsbDev *dev) next_ep:; } slot_ctx = XhciInputCtx(dev, 1); - MemCopy(slot_ctx, XhciDevCtxGet(dev, 0), usb_xhci.ctx_size); + MemCopy(slot_ctx, XhciDevCtxGet(dev, 0), hc->ctx_size); slot_ctx[0] = (slot_ctx[0] & 0x07FFFFFF) | (max_dci << 27); //Hub: advertise it to the HC so it routes traffic to downstream ports. if (dev->is_hub) @@ -557,32 +605,36 @@ next_ep:; addr = dev->in_ctx; return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, - (XHCI_TRB_CONFIG_EP << 10) | (dev->slot << 24)); + (XHCI_TRB_CONFIG_EP << 10) | (dev->slot << 24),, hc); } I64 XhciResetEP(CUsbDev *dev, CUsbEP *ep) {//Recover a halted endpoint: Reset EP + Set TR Dequeue to fresh ring. - I64 code, addr; + CXhciGlobals *hc = dev->hc; + I64 code, addr; - code = XhciCmd(0, 0, 0, (XHCI_TRB_RESET_EP << 10) | (ep->dci << 16) | (dev->slot << 24)); + code = XhciCmd(0, 0, 0, + (XHCI_TRB_RESET_EP << 10) | (ep->dci << 16) | (dev->slot << 24),, hc); 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)); + (XHCI_TRB_SET_TR_DEQ << 10) | (ep->dci << 16) | (dev->slot << 24),, hc); return code; } -U0 XhciBiosHandoff() +U0 XhciBiosHandoff(CXhciGlobals *hc=NULL) { - I64 hcc = XhciCapR32(XHCI_CAP_HCCPARAMS1), xecp, id, next; + I64 hcc, xecp, id, next; U32 *cap; F64 timeout; + if (!hc) hc = usb_xhci; + hcc = XhciCapR32(XHCI_CAP_HCCPARAMS1, hc); xecp = ((hcc >> 16) & 0xFFFF) * 4; while (xecp) { - cap = usb_xhci.mmio + xecp; + cap = hc->mmio + xecp; id = *cap & 0xFF; if (id == 1) {//USB Legacy Support: claim OS ownership @@ -599,32 +651,31 @@ U0 XhciBiosHandoff() } } -Bool XhciPortReset(I64 port) +Bool XhciPortReset(I64 port, CXhciGlobals *hc=NULL) {//Reset root port, wait for enabled. Returns TRUE if port enabled. I64 sc; F64 timeout; - XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_PR); + XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_PR, hc); timeout = tS + 0.5; while (tS < timeout) { - sc = XhciPortSC(port); + sc = XhciPortSC(port, hc); if (sc & XHCI_PORTSC_PED) break; Sleep(10); } //Clear change bits - XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_CHANGE_BITS); - sc = XhciPortSC(port); + XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_CHANGE_BITS, hc); + sc = XhciPortSC(port, hc); 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. +//stretches mem_physical_space to cover them, so the BAR is already identity +//mapped: flip its page to uncached. No TLB flush; the page is untouched. I64 *pte, base = phys & ~(mem_page_size - 1); pte = MemPageTable(base); @@ -632,152 +683,158 @@ U8 *XhciMmioMap(I64 phys) 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; +Bool XhciHcStart(CXhciGlobals *hc, I64 bdf) +{//Bring one controller up: map BAR, reset, alloc rings/contexts, run, power +//ports. TRUE if it is running. + I64 bar_lo, bar_hi, addr, i, hcs1, hcs2, hcc, cmd; + F64 timeout; + U8 *page; - 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); + MemSet(hc, 0, sizeof(CXhciGlobals)); + hc->pci_bus = bdf.u8[2]; + hc->pci_dev = bdf.u8[1]; + hc->pci_fun = bdf.u8[0]; + + bar_lo = PCIReadU32(hc->pci_bus, hc->pci_dev, hc->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); + bar_hi = PCIReadU32(hc->pci_bus, hc->pci_dev, hc->pci_fun, 0x14); addr = bar_hi << 32 | bar_lo & ~0xF; if (!addr) - goto xi_next; //BAR not assigned + return FALSE; //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); + cmd = PCIReadU16(hc->pci_bus, hc->pci_dev, hc->pci_fun, 4); + PCIWriteU16(hc->pci_bus, hc->pci_dev, hc->pci_fun, 4, cmd | 6); if (bar_hi) - usb_xhci.mmio = XhciMmioMap(addr); //BAR above 4GB gets its own mapping + hc->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; + hc->mmio = dev.uncached_alias + addr; + hc->op = hc->mmio + (XhciCapR32(XHCI_CAP_CAPLENGTH, hc) & 0xFF); + hc->rt = hc->mmio + (XhciCapR32(XHCI_CAP_RTSOFF, hc) & ~0x1F); + hc->db = hc->mmio + (XhciCapR32(XHCI_CAP_DBOFF, hc) & ~3); + + hcs1 = XhciCapR32(XHCI_CAP_HCSPARAMS1, hc); + hcs2 = XhciCapR32(XHCI_CAP_HCSPARAMS2, hc); + hcc = XhciCapR32(XHCI_CAP_HCCPARAMS1, hc); + hc->max_slots = hcs1 & 0xFF; + hc->max_ports = (hcs1 >> 24) & 0xFF; if (hcc & 4) - usb_xhci.ctx_size = 64; + hc->ctx_size = 64; else - usb_xhci.ctx_size = 32; + hc->ctx_size = 32; - XhciBiosHandoff; + XhciBiosHandoff(hc); //Stop + reset controller - XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD) & ~XHCI_CMD_RS); + XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD, hc) & ~XHCI_CMD_RS, hc); timeout = tS + 1.0; - while (!(XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) && tS < timeout) + while (!(XhciOpR32(XHCI_OP_USBSTS, hc) & XHCI_STS_HCH) && tS < timeout) Sleep(1); - XhciOpW32(XHCI_OP_USBCMD, XHCI_CMD_HCRST); + XhciOpW32(XHCI_OP_USBCMD, XHCI_CMD_HCRST, hc); timeout = tS + 1.0; - while ((XhciOpR32(XHCI_OP_USBCMD) & XHCI_CMD_HCRST) && tS < timeout) + while ((XhciOpR32(XHCI_OP_USBCMD, hc) & XHCI_CMD_HCRST) && tS < timeout) Sleep(1); timeout = tS + 1.0; - while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_CNR) && tS < timeout) + while ((XhciOpR32(XHCI_OP_USBSTS, hc) & XHCI_STS_CNR) && tS < timeout) Sleep(1); - XhciOpW32(XHCI_OP_CONFIG, usb_xhci.max_slots); + XhciOpW32(XHCI_OP_CONFIG, hc->max_slots, hc); //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); + hc->dcbaa = CAllocAligned((hc->max_slots + 1) * 8, 0x1000, sys_task->code_heap); //Scratchpad buffers - usb_xhci.scratch_count = (((hcs2 >> 21) & 0x1F) << 5) | ((hcs2 >> 27) & 0x1F); - if (usb_xhci.scratch_count) + hc->scratch_count = (((hcs2 >> 21) & 0x1F) << 5) | ((hcs2 >> 27) & 0x1F); + if (hc->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++) + hc->scratch_arr = CAllocAligned(hc->scratch_count * 8, 0x1000, sys_task->code_heap); + for (i = 0; i < hc->scratch_count; i++) { page = CAllocAligned(0x1000, 0x1000, sys_task->code_heap); - usb_xhci.scratch_arr[i] = page; + hc->scratch_arr[i] = page; } - usb_xhci.dcbaa[0] = usb_xhci.scratch_arr; + hc->dcbaa[0] = hc->scratch_arr; } - XhciOpW64(XHCI_OP_DCBAAP, usb_xhci.dcbaa); + XhciOpW64(XHCI_OP_DCBAAP, hc->dcbaa, hc); //Command ring - XhciRingInit(&usb_xhci.cmd_ring); - addr = usb_xhci.cmd_ring.trbs; - XhciOpW64(XHCI_OP_CRCR, addr | 1); + XhciRingInit(&hc->cmd_ring); + addr = hc->cmd_ring.trbs; + XhciOpW64(XHCI_OP_CRCR, addr | 1, hc); //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); + hc->evt_ring = CAllocAligned(XHCI_EVT_RING_TRBS * sizeof(CXhciTRB), 0x1000, sys_task->code_heap); + hc->evt_deq = 0; + hc->evt_cycle = 1; + hc->erst = CAllocAligned(64, 64, sys_task->code_heap); + addr = hc->evt_ring; + hc->erst[0] = addr; + hc->erst[1] = XHCI_EVT_RING_TRBS; + XhciRtW32(XHCI_RT_ERSTSZ0, 1, hc); + XhciRtW64(XHCI_RT_ERDP0, addr, hc); + addr = hc->erst; + XhciRtW64(XHCI_RT_ERSTBA0, addr, hc); //Interrupts stay disabled; we poll. //Run - XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD) | XHCI_CMD_RS); + XhciOpW32(XHCI_OP_USBCMD, XhciOpR32(XHCI_OP_USBCMD, hc) | XHCI_CMD_RS, hc); timeout = tS + 1.0; - while ((XhciOpR32(XHCI_OP_USBSTS) & XHCI_STS_HCH) && tS < timeout) + while ((XhciOpR32(XHCI_OP_USBSTS, hc) & 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) + if (XhciOpR32(XHCI_OP_USBSTS, hc) & XHCI_STS_HCH) + return FALSE; //never left halt + + hc->present = TRUE; + hc->running = TRUE; + +//Ports come up unpowered on real hardware; no CCS until PP is set. + for (i = 0; i < hc->max_ports; i++) + XhciPortSCWrite(i, XHCI_PORTSC_PP, hc); + + return TRUE; +} + +public I64 XhciInitAll() +{//Bring up every xHCI controller. Devices are spread across them: a USB-C dock +//can put an ethernet NIC on the Thunderbolt controller while HID sits on the +//PCH one. Returns the number running. + CXhciGlobals *hc; + I64 n, i, bdf; + + usb_hc_count = 0; + for (n = 0; usb_hc_count < USB_HCS_MAX; n++) { - "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; + bdf = PCIClassFind(XHCI_PCI_CLASS, n); + if (bdf == -1) + break; + hc = &usb_hcs[usb_hc_count]; + if (XhciHcStart(hc, bdf)) + { + "xHCI #%d at %02X:%02X:%01X: %d ports, %d slots.\n", + usb_hc_count, hc->pci_bus, hc->pci_dev, hc->pci_fun, + hc->max_ports, hc->max_slots; + usb_hc_count++; + } } - if (fallback == -1) - fallback = n; -xi_next:; + if (!usb_hc_count) + { + "xHCI: no usable controller found.\n"; + usb_xhci = &usb_hcs[0]; + return 0; } -//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; + Sleep(20); //port power settle +//Default target for calls that don't name a controller: prefer one with a +//device connected. + usb_xhci = &usb_hcs[0]; + for (n = 0; n < usb_hc_count; n++) + for (i = 0; i < usb_hcs[n].max_ports; i++) + if (XhciPortSC(i, &usb_hcs[n]) & XHCI_PORTSC_CCS) + { + usb_xhci = &usb_hcs[n]; + return usb_hc_count; + } + + return usb_hc_count; } +