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..55ff72c9d 100755 --- a/src/Kernel/BlkDev/DiskDrive.ZC +++ b/src/Kernel/BlkDev/DiskDrive.ZC @@ -202,8 +202,10 @@ I64 Letter2BlkDevType(U8 drv_let) return BDT_ISO_FILE_READ; if ('Q' <= drv_let <= 'S') return BDT_ISO_FILE_WRITE; - if ('T' <= drv_let <= 'Z') + if ('T' <= drv_let <= 'U') return BDT_ATAPI; + if ('V' <= drv_let <= 'Z') + return BDT_USB; return BDT_NULL; } @@ -347,7 +349,7 @@ U8 *DriveModelNum(U8 drv_let=0) return res; } -U8 blkdev_text_attr[BDT_TYPES_NUM] = {BLACK, LTCYAN, WHITE, LTGREEN, LTRED, LTBLUE}; +U8 blkdev_text_attr[BDT_TYPES_NUM] = {BLACK, LTCYAN, WHITE, LTGREEN, LTRED, LTBLUE, LTPURPLE}; U8 drv_text_attr[3] = {BLACK, BLUE, RED}; U8 DriveTextAttrGet(U8 drv_let=0) diff --git a/src/Kernel/KDefine.ZC b/src/Kernel/KDefine.ZC index 235b628cc..2e5c56192 100755 --- a/src/Kernel/KDefine.ZC +++ b/src/Kernel/KDefine.ZC @@ -215,7 +215,8 @@ U0 SysDefinesLoad() "ATA\0" "FILE_READ\0" "FILE_WRITE\0" - "ATAPI\0"); + "ATAPI\0" + "USB\0"); DefineListLoad("ST_DRIVE_TYPES", "NULL\0" diff --git a/src/Kernel/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..69c1033cd --- /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->ep_list[dev->ep_count]; +//Clear all fields but keep any prior ring allocation (ring is first member). + MemSet(&ep->dci, 0, sizeof(CUsbEP) - offset(CUsbEP.dci)); + ep->addr = epd->addr; + ep->attr = epd->attr; + ep->max_packet = epd->max_packet & 0x7FF; + ep->interval = epd->interval; + ep->dci = (epd->addr & 0xF) << 1; + if (epd->addr & 0x80) + ep->dci |= 1; + ep->valid = TRUE; + ep->auto_kind = USB_AUTO_NONE; + cur_if->ep_idxs[cur_if->ep_count] = dev->ep_count; + cur_if->ep_count++; + dev->ep_count++; + } + } + } + off += dlen; + } +} + +U0 UsbDevDetachCore(CUsbDev *dev) +{//Release xHCI slot; class driver cleanup handled by caller. + I64 i; + + dev->present = FALSE; + dev->configured = FALSE; + if (dev->slot) + { + XhciDisableSlot(dev->slot); + usb_xhci.dcbaa[dev->slot] = 0; + } + dev->slot = 0; + dev->ep0.valid = FALSE; + for (i = 0; i < USB_EPS_MAX; i++) + { + dev->ep_list[i].valid = FALSE; + dev->ep_list[i].auto_kind = USB_AUTO_NONE; + } + dev->drv_data = NULL; +} + +CUsbDev *UsbDevEnum(I64 port) +{//Enumerate device on root port (already reset + enabled). USB task only. + CUsbDev *dev; + CUsbDevDesc *dd; + CUsbConfigDesc *cd; + U8 *buf; + I64 slot, code, sc, total, mps0; + + sc = XhciPortSC(port); + slot = XhciEnableSlot; + if (!slot) + { + "USB: port %d: Enable Slot failed.\n", port; + return NULL; + } + dev = UsbDevAlloc; + if (!dev) + { + XhciDisableSlot(slot); + return NULL; + } + dev->slot = slot; + dev->port = port; + dev->speed = (sc >> 10) & 0xF; + dev->present = TRUE; + dev->configured = FALSE; + dev->drv_data = NULL; + dev->desc_name[0] = 0; + + code = XhciAddressDev(dev); + if (code != XHCI_CC_SUCCESS) + { + "USB: port %d: Address Device failed (%d).\n", port, code; + UsbDevDetachCore(dev); + return NULL; + } + Sleep(5); + + buf = CAlloc(512, sys_task->code_heap); + dd = buf; + +//First 8 bytes to learn EP0 max packet size. + code = UsbGetDesc(dev, USB_DESC_DEV, 0, buf, 8); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + "USB: port %d: GET_DESCRIPTOR(8) failed (%d).\n", port, code; + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + mps0 = dd->max_packet0; + if (dev->speed == USB_SPEED_SUPER) + mps0 = 1 << mps0; //SS stores exponent + if (mps0 && mps0 != dev->ep0.max_packet) + XhciEvalCtxMPS0(dev, mps0); + + code = UsbGetDesc(dev, USB_DESC_DEV, 0, buf, 18); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + "USB: port %d: GET_DESCRIPTOR failed (%d).\n", port, code; + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + dev->vendor_id = dd->vendor_id; + dev->product_id = dd->product_id; + dev->dev_class = dd->dev_class; + dev->dev_subclass = dd->dev_subclass; + dev->dev_proto = dd->dev_proto; + UsbStrGet(dev, dd->i_product, dev->desc_name, 64); + +//Config descriptor: header first for total length, then full. + code = UsbGetDesc(dev, USB_DESC_CONFIG, 0, buf, 9); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + "USB: port %d: GET_CONFIG failed (%d).\n", port, code; + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + cd = buf; + total = cd->total_len; + if (total > 512) + total = 512; + code = UsbGetDesc(dev, USB_DESC_CONFIG, 0, buf, total); + if (code != XHCI_CC_SUCCESS && code != XHCI_CC_SHORT_PKT) + { + Free(buf); + UsbDevDetachCore(dev); + return NULL; + } + dev->config_val = cd->config_val; + UsbParseConfig(dev, buf, total); + Free(buf); + + if (dev->ep_count) + { + code = XhciConfigureEPs(dev); + if (code != XHCI_CC_SUCCESS) + { + "USB: port %d: Configure Endpoint failed (%d).\n", port, code; + UsbDevDetachCore(dev); + return NULL; + } + } + code = UsbCtrl(dev, USB_RT_HOST_TO_DEV, USB_RQ_SET_CONFIG, dev->config_val, 0, NULL, 0); + if (code != XHCI_CC_SUCCESS) + { + "USB: port %d: SET_CONFIGURATION failed (%d).\n", port, code; + UsbDevDetachCore(dev); + return NULL; + } + dev->configured = TRUE; + + SysLog("USB: port %d: %04X:%04X class %d \"%s\" (%d ifs, %d eps)\n", + port, dev->vendor_id, dev->product_id, dev->dev_class, + dev->desc_name, dev->if_count, dev->ep_count); + + return dev; +} diff --git a/src/System/Usb/UsbDefs.ZC b/src/System/Usb/UsbDefs.ZC new file mode 100644 index 000000000..1cb8d4a0a --- /dev/null +++ b/src/System/Usb/UsbDefs.ZC @@ -0,0 +1,321 @@ +//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 + +//Interrupt-IN auto-requeue report handler kinds (see CUsbEP.auto_kind). +#define USB_AUTO_NONE 0 +#define USB_AUTO_KBD 1 +#define USB_AUTO_MOUSE 2 + +//Forward decls: report handlers live in UsbHid.ZC (compiled after Xhci.ZC), +//but XhciEventPoll dispatches to them. Single-pass compiler needs these. +extern U0 UsbKbdCB(U8 *dev, U8 *ep); +extern U0 UsbMouseCB(U8 *dev, U8 *ep); + +class CXhciTRB +{//16 bytes + U32 lo, hi, status, control; +}; + +class CXhciRing +{//Producer ring (command or transfer) + CXhciTRB *trbs; + I64 enq, //enqueue index + cycle; //producer cycle state +}; + +class CUsbEP +{ +//ring is a POINTER, not embedded: CUsbEP is itself embedded by value in CUsbDev, +//and the ZealC JIT class parser rejects two-level by-value class nesting +//(CUsbDev -> CUsbEP -> CXhciRing). Keeping ring a pointer flattens CUsbEP. + CXhciRing *ring; + I64 dci, //xHCI device context index + addr, //USB endpoint address (with 0x80 dir bit) + attr, //descriptor attributes + max_packet, + interval; //descriptor bInterval + Bool valid; +//Sync transfer completion, set by event poll + Bool xfer_done; + I64 xfer_code, //xHCI completion code + xfer_residue; //untransferred bytes +//Async auto-requeue (HID interrupt IN). auto_kind selects the report handler +//(USB_AUTO_*); dispatched in XhciEventPoll. Avoids a function-pointer struct +//member (which the ZealC JIT class parser rejects) and keeps late binding. + U8 *auto_buf; + I64 auto_len, + auto_kind; + CXhciTRB *pending_trb; +}; + +class CUsbIf +{ + Bool valid; + I64 if_num, if_class, if_subclass, if_proto; + I64 ep_idxs[4], //indices into dev->eps + ep_count; +}; + +class CUsbDev +{ + Bool present, configured; + I64 slot, //xHCI slot id + port, //root port index (0-based) + speed; //USB_SPEED_* + U8 *dev_ctx, + *in_ctx; + CUsbEP ep0; + CUsbEP ep_list[USB_EPS_MAX]; + I64 ep_count; + CUsbIf ifs[USB_IFS_MAX]; + I64 if_count; + I64 vendor_id, product_id, + dev_class, dev_subclass, dev_proto, + config_val; + U8 *drv_data; //class driver private (CUsbMsd / CUsbHid) + U8 desc_name[64]; //product string, ASCII +}; + +class CXhciGlobals +{ + Bool present, running; + U8 *mmio, //capability base (uncached alias) + *op, //operational base + *rt; //runtime base + U32 *db; //doorbell array + I64 max_slots, max_ports, ctx_size, + scratch_count; + U64 *dcbaa, + *scratch_arr, + *erst; + CXhciRing cmd_ring; + CXhciTRB *evt_ring; + I64 evt_deq, evt_cycle; + I64 lock; //ring enqueue lock +//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..89880211e --- /dev/null +++ b/src/System/Usb/UsbHid.ZC @@ -0,0 +1,265 @@ +//USB HID boot-protocol keyboard + mouse. +//Keyboard: HID usages -> PS/2 set-1 scancodes -> kbd.fifo2 (reuses kernel KbdHandler). +//Mouse: boot reports -> mouse_hard state (reuses kernel mouse pipeline). + +#define USB_HIDS_MAX 8 + +#define USB_HID_REPEAT_DELAY 0.400 +#define USB_HID_REPEAT_RATE 0.035 + +class CUsbHid +{ + Bool valid; + CUsbDev *dev; + CUsbEP *ep; + I64 proto; //1=kbd, 2=mouse + U8 prev[8]; + I64 repeat_sc; //held key scancode for software typematic + F64 repeat_time; +}; + +CUsbHid usb_hids[USB_HIDS_MAX]; + +//Absolute cursor position maintained by the USB mouse (pixels). +I64 usb_mouse_x = 0, usb_mouse_y = 0; + +//HID usage (page 7) -> set-1 scancode. 0x100 flag = 0xE0-prefixed. +I64 usb_hid_to_sc[0x66] = { + 0, 0, 0, 0, 0x1E, 0x30, 0x2E, 0x20, //00-07: ---- a b c d + 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, //08-0F: e f g h i j k l + 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, //10-17: m n o p q r s t + 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x02, 0x03, //18-1F: u v w x y z 1 2 + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, //20-27: 3 4 5 6 7 8 9 0 + 0x1C, 0x01, 0x0E, 0x0F, 0x39, 0x0C, 0x0D, 0x1A, //28-2F: Ent Esc BS Tab Sp - = [ + 0x1B, 0x2B, 0x2B, 0x27, 0x28, 0x29, 0x33, 0x34, //30-37: ] \ # ; ' ` , . + 0x35, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, //38-3F: / Caps F1-F6 + 0x41, 0x42, 0x43, 0x44, 0x57, 0x58, 0x137, 0x46, //40-47: F7-F12 PrtSc ScrLk + 0, 0x152, 0x147, 0x149, 0x153, 0x14F, 0x151, 0x14D, //48-4F: Pause Ins Home PgUp Del End PgDn Right + 0x14B, 0x150, 0x148, 0x45, 0x135, 0x37, 0x4A, 0x4E, //50-57: Left Down Up NumLk KP/ KP* KP- KP+ + 0x11C, 0x4F, 0x50, 0x51, 0x4B, 0x4C, 0x4D, 0x47, //58-5F: KPEnt KP1-KP7 + 0x48, 0x49, 0x52, 0x53, 0x56, 0x15D //60-65: KP8 KP9 KP0 KP. NonUS\ App +}; + +//Modifier bit -> scancode: LCtrl LShift LAlt LGUI RCtrl RShift RAlt RGUI +I64 usb_hid_mod_sc[8] = {0x1D, 0x2A, 0x38, 0x15B, 0x11D, 0x36, 0x138, 0x15C}; + +U0 UsbKbdEmit(I64 sc, I64 up) +{//Push set-1 bytes into kernel keyboard fifo. + if (!sc) + return; + if (sc & 0x100) + FifoU8Insert(kbd.fifo2, 0xE0); + if (up) + FifoU8Insert(kbd.fifo2, (sc & 0x7F) | 0x80); + else + FifoU8Insert(kbd.fifo2, sc & 0x7F); +} + +CUsbHid *UsbHidByEP(CUsbEP *ep) +{ + I64 i; + + for (i = 0; i < USB_HIDS_MAX; i++) + if (usb_hids[i].valid && usb_hids[i].ep == ep) + return &usb_hids[i]; + + return NULL; +} + +Bool UsbKbdReportHas(U8 *report, I64 usage) +{ + I64 i; + + for (i = 2; i < 8; i++) + if (report[i] == usage) + return TRUE; + + return FALSE; +} + +U0 UsbKbdCB(U8 *d, U8 *e) +{//Interrupt IN report arrived (8-byte boot keyboard report). + CUsbDev *dev = d; + CUsbEP *ep = e; + CUsbHid *hid = UsbHidByEP(ep); + U8 *rep; + I64 i, mods, prev_mods, sc; + + if (!hid || !dev->present) + return; + rep = ep->auto_buf; + if (rep[2] == 1) + return; //error rollover + mods = rep[0]; + prev_mods = hid->prev[0]; + for (i = 0; i < 8; i++) + { + if ((mods & (1 << i)) && !(prev_mods & (1 << i))) + UsbKbdEmit(usb_hid_mod_sc[i], FALSE); + if (!(mods & (1 << i)) && (prev_mods & (1 << i))) + UsbKbdEmit(usb_hid_mod_sc[i], TRUE); + } +//Released keys + for (i = 2; i < 8; i++) + { + if (hid->prev[i] && !UsbKbdReportHas(rep, hid->prev[i])) + { + if (hid->prev[i] < 0x66) + { + sc = usb_hid_to_sc[hid->prev[i]]; + UsbKbdEmit(sc, TRUE); + if (hid->repeat_sc == sc) + hid->repeat_sc = 0; + } + } + } +//Pressed keys + for (i = 2; i < 8; i++) + { + if (rep[i] && !UsbKbdReportHas(hid->prev, rep[i])) + { + if (rep[i] < 0x66) + { + sc = usb_hid_to_sc[rep[i]]; + UsbKbdEmit(sc, FALSE); + hid->repeat_sc = sc; + hid->repeat_time = tS + USB_HID_REPEAT_DELAY; + } + } + } + MemCopy(hid->prev, rep, 8); +} + +U0 UsbMouseCB(U8 *d, U8 *e) +{//Interrupt IN report arrived (boot mouse report: buttons dx dy [wheel]). + CUsbDev *dev = d; + CUsbEP *ep = e; + U8 *rep; + I64 dx, dy, dz = 0; + + if (!dev->present) + return; + rep = ep->auto_buf; + + dx = rep[1]; + if (dx & 0x80) + dx -= 256; + dy = rep[2]; + if (dy & 0x80) + dy -= 256; + if (ep->auto_len >= 4) + { + dz = rep[3]; + if (dz & 0x80) + dz -= 256; + } + +//Drive the cursor directly. The kernel prescale/MouseHardSetPost path is meant +//for the PS/2 handler and gets clobbered by WinMgr's per-frame PS/2 polling; +//we own an absolute position and write mouse_hard.pos ourselves. + usb_mouse_x = ClampI64(usb_mouse_x + dx, 0, sys_framebuffer_width - 1); + usb_mouse_y = ClampI64(usb_mouse_y + dy, 0, sys_framebuffer_height - 1); + + mouse_hard.timestamp = TSCGet; + MemCopy(&mouse_hard_last, &mouse_hard, sizeof(CMouseHardStateGlobals)); + mouse_hard.pos.x = usb_mouse_x; + mouse_hard.pos.y = usb_mouse_y; + mouse_hard.pos.z += dz; + mouse_hard.bttns[0] = rep[0] & 1; + mouse_hard.bttns[1] = (rep[0] & 2) >> 1; + mouse_hard.bttns[2] = (rep[0] & 4) >> 2; + mouse_hard.event = TRUE; + mouse.show = TRUE; +} + +U0 UsbHidTick() +{//Software typematic repeat; called from USB task main loop. + CUsbHid *hid; + I64 i; + + for (i = 0; i < USB_HIDS_MAX; i++) + { + hid = &usb_hids[i]; + if (hid->valid && hid->proto == USB_HID_PROTO_KBD && hid->repeat_sc) + { + if (tS >= hid->repeat_time) + { + UsbKbdEmit(hid->repeat_sc, FALSE); + hid->repeat_time = tS + USB_HID_REPEAT_RATE; + } + } + } +} + +U0 UsbHidAttach(CUsbDev *dev, CUsbIf *uif) +{//Bind boot keyboard or mouse interface. + CUsbHid *hid = NULL; + CUsbEP *ep = NULL, *tmp; + I64 i; + + for (i = 0; i < uif->ep_count; i++) + { + tmp = &dev->ep_list[uif->ep_idxs[i]]; + if ((tmp->addr & 0x80) && (tmp->attr & 3) == USB_EP_TYPE_INT) + { + ep = tmp; + break; + } + } + if (!ep) + return; + for (i = 0; i < USB_HIDS_MAX; i++) + if (!usb_hids[i].valid) + { + hid = &usb_hids[i]; + break; + } + if (!hid) + return; + MemSet(hid, 0, sizeof(CUsbHid)); + hid->dev = dev; + hid->ep = ep; + hid->proto = uif->if_proto; + hid->valid = TRUE; + +//Boot protocol; idle=0 -> reports only on change. + UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_IF, + USB_HID_RQ_SET_PROTOCOL, 0, uif->if_num, NULL, 0); + UsbCtrl(dev, USB_RT_HOST_TO_DEV | USB_RT_CLASS | USB_RT_RECIP_IF, + USB_HID_RQ_SET_IDLE, 0, uif->if_num, NULL, 0); + + if (!ep->auto_buf) + ep->auto_buf = CAlloc(8, sys_task->code_heap); + ep->auto_len = ep->max_packet; + if (ep->auto_len > 8) + ep->auto_len = 8; + if (ep->auto_len < 3) + ep->auto_len = 3; + if (uif->if_proto == USB_HID_PROTO_KBD) + { + ep->auto_len = 8; + ep->auto_kind = USB_AUTO_KBD; + SysLog("USB: HID keyboard on port %d.\n", dev->port); + } + else + { + ep->auto_kind = USB_AUTO_MOUSE; +//Own the cursor: mark installed so WinMouseUpdate consumes our events, and +//irqs_working so WinMgr does NOT poll (and clobber) the PS/2 mouse state. + mouse_hard.installed = TRUE; + mouse_hard.irqs_working = TRUE; + usb_mouse_x = sys_framebuffer_width >> 1; + usb_mouse_y = sys_framebuffer_height >> 1; + SysLog("USB: HID mouse on port %d.\n", dev->port); + } + XhciIntQueue(dev, ep); +} + +U0 UsbHidDetach(CUsbDev *dev) +{ + I64 i; + + for (i = 0; i < USB_HIDS_MAX; i++) + if (usb_hids[i].valid && usb_hids[i].dev == dev) + usb_hids[i].valid = FALSE; +} diff --git a/src/System/Usb/UsbMsd.ZC b/src/System/Usb/UsbMsd.ZC new file mode 100644 index 000000000..0d15c1aa3 --- /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->ep_list[uif->ep_idxs[i]]; + if ((ep->attr & 3) == USB_EP_TYPE_BULK) + { + if (ep->addr & 0x80) + msd->bulk_in = ep; + else + msd->bulk_out = ep; + } + } + if (!msd->bulk_in || !msd->bulk_out) + { + SysLog("USB: MSD port %d: missing bulk endpoints.\n", dev->port); + return; + } + if (!msd->cbw) + msd->cbw = CAlloc(sizeof(CUsbMsdCBW), sys_task->code_heap); + if (!msd->csw) + msd->csw = CAlloc(sizeof(CUsbMsdCSW), sys_task->code_heap); + if (!msd->bounce) + msd->bounce = CAlloc(USB_MSD_CHUNK_BLKS * BLK_SIZE, sys_task->code_heap); + msd->next_tag = 1; + msd->valid = TRUE; + dev->drv_data = msd; + + MemSet(inq, 0, 40); + UsbMsdInquiry(msd, inq); + inq[36] = 0; + + tries = 10; + while (tries > 0 && !UsbMsdTestReady(msd)) + { + UsbMsdRequestSense(msd); + Sleep(100); + tries--; + } + if (!UsbMsdReadCapacity(msd)) + { + SysLog("USB: MSD port %d: READ CAPACITY failed.\n", dev->port); + msd->valid = FALSE; + return; + } + if (msd->blk_size != BLK_SIZE) + { + SysLog("USB: MSD port %d: unsupported block size %d.\n", dev->port, msd->blk_size); + msd->valid = FALSE; + return; + } + msd->ready = TRUE; + + 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..afb5655eb --- /dev/null +++ b/src/System/Usb/Xhci.ZC @@ -0,0 +1,716 @@ +//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; +} + +CXhciRing *XhciEPRingInit(CUsbEP *ep) +{//Alloc ep's ring struct (pointer member) on first use, then (re)init it. + if (!ep->ring) + ep->ring = CAlloc(sizeof(CXhciRing), sys_task->code_heap); + XhciRingInit(ep->ring); + + return ep->ring; +} + +CXhciTRB *XhciRingEnq(CXhciRing *ring, I64 lo, I64 hi, I64 status, I64 control) +{//control passed without cycle bit; caller holds lock. + CXhciTRB *trb = &ring->trbs[ring->enq], *link; + I64 addr; + + trb->lo = lo; + trb->hi = hi; + trb->status = status; + trb->control = (control & ~1) | ring->cycle; + ring->enq++; + if (ring->enq == XHCI_RING_TRBS - 1) + {//Place link TRB back to start, toggle cycle. + link = &ring->trbs[XHCI_RING_TRBS - 1]; + addr = ring->trbs; + link->lo = addr & 0xFFFFFFFF; + link->hi = addr >> 32; + link->status = 0; + link->control = (XHCI_TRB_LINK << 10) | XHCI_TRBF_TC | ring->cycle; + ring->enq = 0; + ring->cycle ^= 1; + } + + return trb; +} + +CUsbDev *UsbDevBySlot(I64 slot) +{ + I64 i; + + for (i = 0; i < USB_DEVS_MAX; i++) + if (usb_devs[i].present && usb_devs[i].slot == slot) + return &usb_devs[i]; + + return NULL; +} + +CUsbDev *UsbDevByPort(I64 port) +{ + I64 i; + + for (i = 0; i < USB_DEVS_MAX; i++) + if (usb_devs[i].present && usb_devs[i].port == port) + return &usb_devs[i]; + + return NULL; +} + +CUsbEP *UsbDevEPByDCI(CUsbDev *dev, I64 dci) +{ + I64 i; + + if (dci == 1) + return &dev->ep0; + for (i = 0; i < dev->ep_count; i++) + if (dev->ep_list[i].valid && dev->ep_list[i].dci == dci) + return &dev->ep_list[i]; + + return NULL; +} + +U0 XhciIntQueue(CUsbDev *dev, CUsbEP *ep) +{//Queue one interrupt IN TRB on ep, report dispatched via ep->auto_kind. + XhciLock; + ep->pending_trb = XhciRingEnq(ep->ring, ep->auto_buf, 0, ep->auto_len, + (XHCI_TRB_NORMAL << 10) | XHCI_TRBF_IOC | XHCI_TRBF_ISP); + usb_xhci.db[dev->slot] = ep->dci; + XhciUnlock; +} + +U0 XhciEventPoll() +{//Drain event ring. Called ONLY in USB task context. + CXhciTRB *e; + CUsbDev *dev; + CUsbEP *ep; + I64 type, code, slot, dci, addr, work = FALSE; + U64 *erdp; + + if (!usb_xhci.running) + return; + while (TRUE) + { + e = &usb_xhci.evt_ring[usb_xhci.evt_deq]; + if ((e->control & 1) != usb_xhci.evt_cycle) + break; + work = TRUE; + type = (e->control >> 10) & 0x3F; + switch (type) + { + case XHCI_TRB_EVT_CMD_DONE: + usb_xhci.cmd_code = (e->status >> 24) & 0xFF; + usb_xhci.cmd_slot = (e->control >> 24) & 0xFF; + addr = usb_xhci.cmd_trb; + if (usb_xhci.cmd_trb && e->lo == addr & 0xFFFFFFFF) + usb_xhci.cmd_done = TRUE; + break; + + case XHCI_TRB_EVT_TRANSFER: + slot = (e->control >> 24) & 0xFF; + dci = (e->control >> 16) & 0x1F; + code = (e->status >> 24) & 0xFF; + dev = UsbDevBySlot(slot); + if (dev) + { + ep = UsbDevEPByDCI(dev, dci); + if (ep) + { + if (ep->auto_kind) + {//HID interrupt IN: deliver + requeue + if (code == XHCI_CC_SUCCESS || code == XHCI_CC_SHORT_PKT) + { + switch (ep->auto_kind) + { + case USB_AUTO_KBD: + UsbKbdCB(dev, ep); + break; + case USB_AUTO_MOUSE: + UsbMouseCB(dev, ep); + break; + } + } + XhciIntQueue(dev, ep); + } + else + { + ep->xfer_code = code; + ep->xfer_residue = e->status & 0xFFFFFF; + ep->xfer_done = TRUE; + } + } + } + break; + + case XHCI_TRB_EVT_PORT_SC: + usb_xhci.port_change = TRUE; + break; + } + usb_xhci.evt_deq++; + if (usb_xhci.evt_deq == XHCI_EVT_RING_TRBS) + { + usb_xhci.evt_deq = 0; + usb_xhci.evt_cycle ^= 1; + } + } + if (work) + { + addr = usb_xhci.evt_ring + usb_xhci.evt_deq; + erdp = usb_xhci.rt + XHCI_RT_ERDP0; + *erdp = addr | 8; //8 = clear Event Handler Busy + } +} + +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); + XhciEPRingInit(&dev->ep0); + dev->ep0.dci = 1; + dev->ep0.valid = TRUE; + dev->ep0.auto_kind = USB_AUTO_NONE; + + switch (dev->speed) + { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + mps = 8; + break; + case USB_SPEED_HIGH: + mps = 64; + break; + default: + mps = 512; + } + dev->ep0.max_packet = mps; + + icc = XhciInputCtx(dev, 0); + icc[0] = 0; + icc[1] = 3; //add slot + EP0 + slot_ctx = XhciInputCtx(dev, 1); + slot_ctx[0] = (1 << 27) | (dev->speed << 20); + slot_ctx[1] = (dev->port + 1) << 16; + ep0_ctx = XhciInputCtx(dev, 2); + ep0_ctx[1] = (3 << 1) | (XHCI_EP_TYPE_CTRL << 3) | (mps << 16); + addr = dev->ep0.ring->trbs; + ep0_ctx[2] = (addr & 0xFFFFFFFF) | 1; //DCS=1 + ep0_ctx[3] = addr >> 32; + ep0_ctx[4] = 8; + + usb_xhci.dcbaa[dev->slot] = dev->dev_ctx; + addr = dev->in_ctx; + + return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, + (XHCI_TRB_ADDRESS_DEV << 10) | (dev->slot << 24)); +} + +I64 XhciEvalCtxMPS0(CUsbDev *dev, I64 mps) +{//Update EP0 max packet size after reading device descriptor. + U32 *icc, *ep0_ctx; + I64 addr; + + MemSet(dev->in_ctx, 0, usb_xhci.ctx_size * 33); + icc = XhciInputCtx(dev, 0); + icc[0] = 0; + icc[1] = 2; //A1 = EP0 + ep0_ctx = XhciInputCtx(dev, 2); + MemCopy(ep0_ctx, XhciDevCtxGet(dev, 1), usb_xhci.ctx_size); + ep0_ctx[1] = (ep0_ctx[1] & 0xFFFF) | (mps << 16); + dev->ep0.max_packet = mps; + addr = dev->in_ctx; + + return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, + (XHCI_TRB_EVAL_CTX << 10) | (dev->slot << 24)); +} + +I64 XhciConfigureEPs(CUsbDev *dev) +{//Build input context with all parsed EPs, issue Configure Endpoint. + U32 *icc, *slot_ctx, *ep_ctx; + CUsbEP *ep; + I64 i, addr, max_dci = 1, type, ival; + + MemSet(dev->in_ctx, 0, usb_xhci.ctx_size * 33); + icc = XhciInputCtx(dev, 0); + icc[0] = 0; + icc[1] = 1; //A0 = slot + for (i = 0; i < dev->ep_count; i++) + { + ep = &dev->ep_list[i]; + if (!ep->valid) + goto next_ep; + XhciEPRingInit(ep); + icc[1] |= 1 << ep->dci; + if (ep->dci > max_dci) + max_dci = ep->dci; + ep_ctx = XhciInputCtx(dev, 1 + ep->dci); + type = XhciEPCtxType(ep); + ival = XhciEPInterval(dev, ep); + ep_ctx[0] = ival << 16; + ep_ctx[1] = (3 << 1) | (type << 3) | (ep->max_packet << 16); + addr = ep->ring->trbs; + ep_ctx[2] = (addr & 0xFFFFFFFF) | 1; + ep_ctx[3] = addr >> 32; + if ((ep->attr & 3) == USB_EP_TYPE_INT) + ep_ctx[4] = ep->max_packet | (ep->max_packet << 16); + else + ep_ctx[4] = 1024; +next_ep:; + } + slot_ctx = XhciInputCtx(dev, 1); + MemCopy(slot_ctx, XhciDevCtxGet(dev, 0), usb_xhci.ctx_size); + slot_ctx[0] = (slot_ctx[0] & 0x07FFFFFF) | (max_dci << 27); + addr = dev->in_ctx; + + return XhciCmd(addr & 0xFFFFFFFF, addr >> 32, 0, + (XHCI_TRB_CONFIG_EP << 10) | (dev->slot << 24)); +} + +I64 XhciResetEP(CUsbDev *dev, CUsbEP *ep) +{//Recover a halted endpoint: Reset EP + Set TR Dequeue to fresh ring. + I64 code, addr; + + code = XhciCmd(0, 0, 0, (XHCI_TRB_RESET_EP << 10) | (ep->dci << 16) | (dev->slot << 24)); + XhciEPRingInit(ep); + addr = ep->ring->trbs; + code = XhciCmd((addr & 0xFFFFFFFF) | 1, addr >> 32, 0, + (XHCI_TRB_SET_TR_DEQ << 10) | (ep->dci << 16) | (dev->slot << 24)); + + return code; +} + +U0 XhciBiosHandoff() +{ + I64 hcc = XhciCapR32(XHCI_CAP_HCCPARAMS1), xecp, id, next; + U32 *cap; + F64 timeout; + + xecp = ((hcc >> 16) & 0xFFFF) * 4; + while (xecp) + { + cap = usb_xhci.mmio + xecp; + id = *cap & 0xFF; + if (id == 1) + {//USB Legacy Support: claim OS ownership + *cap = *cap | (1 << 24); + timeout = tS + 1.0; + while ((*cap & (1 << 16)) && tS < timeout) + Sleep(10); + break; + } + next = (*cap >> 8) & 0xFF; + if (!next) + break; + xecp += next * 4; + } +} + +Bool XhciPortReset(I64 port) +{//Reset root port, wait for enabled. Returns TRUE if port enabled. + I64 sc; + F64 timeout; + + XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_PR); + timeout = tS + 0.5; + while (tS < timeout) + { + sc = XhciPortSC(port); + if (sc & XHCI_PORTSC_PED) + break; + Sleep(10); + } +//Clear change bits + XhciPortSCWrite(port, XHCI_PORTSC_PP | XHCI_PORTSC_CHANGE_BITS); + sc = XhciPortSC(port); + + return ToBool(sc & XHCI_PORTSC_PED); +} + +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; +}