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 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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