diff --git a/VKernel/Kernel/drivers/usb/uhci.vp b/VKernel/Kernel/drivers/usb/uhci.vp index 519319d..4f379cc 100644 --- a/VKernel/Kernel/drivers/usb/uhci.vp +++ b/VKernel/Kernel/drivers/usb/uhci.vp @@ -48,6 +48,14 @@ define TD_CTRL_SPD = 0x20000000 define TD_CTRL_CERR_SHIFT = 27 define TD_CTRL_CERR_MASK = 0x18000000 +# TD 状态字里的错误位(对照 Linux usb-uhci.h 的 TD_CTRL_ANY_ERROR / uhci_status_bits): +# bit17 Bitstuff / bit18 CRC-Timeout / bit20 Babble / bit21 DataBufErr / bit22 Stalled +# 原先只查 bit21|bit22,DATA 阶段若有 CRC/超时错误会被当成"成功但零字节", +# 于是 config 描述符读回来全 0 → iface_class=0 → usb_kbd 永远找不到键盘(现场)。 +define TD_CTRL_ERR_MASK = 0x00760000 +# bit19 NAK:C_ERR(3) 用尽后仍被 NAK ⇒ 设备一直不给数据 +define TD_CTRL_NAK_MASK = 0x00080000 + define TD_TOKEN_SETUP = 0x2D define TD_TOKEN_IN = 0x69 define TD_TOKEN_OUT = 0xE1 @@ -225,8 +233,10 @@ def _wait_for_complete(td: uhci_td*, timeout_ms: int) -> int: while timer.timer_get_ticks() < deadline: status: uint32 = td.ctrl_status if (status & TD_CTRL_ACTIVE) == 0: - if (status & 0x00600000) != 0: + if (status & TD_CTRL_ERR_MASK) != 0: return -1 + if (status & TD_CTRL_NAK_MASK) != 0: + return -3 return 0 poll_cnt += 1 if poll_cnt >= 100: @@ -235,6 +245,14 @@ def _wait_for_complete(td: uhci_td*, timeout_ms: int) -> int: asm.hlt() return -2 +# 单个 TD 的完成判定(fail-fast):0=正常 1=仍在 ACTIVE 2=错误位 3=NAK +def _td_error(td: uhci_td*) -> int: + st: uint32 = td.ctrl_status + if (st & TD_CTRL_ACTIVE) != 0: return 1 + if (st & TD_CTRL_ERR_MASK) != 0: return 2 + if (st & TD_CTRL_NAK_MASK) != 0: return 3 + return 0 + def reset(io_base: uint16) -> int: global _io_base _io_base = io_base @@ -386,8 +404,11 @@ def control_transfer(dev_addr: uint8, endpoint: uint8, ls: int, max_pkt: uint8, _td_set_token(setup_td, TD_TOKEN_SETUP, dev_addr, endpoint, TD_TOKEN_DATA0, 8) setup_td.buffer = uint32(uint64(setup_buf)) + # data/status 两个 TD 事先声明:收尾时要逐个校验完成状态(见下方 fail-fast 段) + data_td: uhci_td* = None + status_td: uhci_td* = None if data_len > 0: - data_td: uhci_td* = uhci_td*(uint64(tds) + uhci_td.__sizeof__()) + data_td = uhci_td*(uint64(tds) + uhci_td.__sizeof__()) _td_set_link(data_td, uint32(uint64(data_td) + uhci_td.__sizeof__()), 0, 0) _td_set_ctrl(data_td, ls, max_pkt) if data_in: @@ -396,7 +417,7 @@ def control_transfer(dev_addr: uint8, endpoint: uint8, ls: int, max_pkt: uint8, _td_set_token(data_td, TD_TOKEN_OUT, dev_addr, endpoint, TD_TOKEN_DATA1, data_len) data_td.buffer = uint32(uint64(data_buf)) - status_td: uhci_td* = uhci_td*(uint64(data_td) + uhci_td.__sizeof__()) + status_td = uhci_td*(uint64(data_td) + uhci_td.__sizeof__()) _td_set_link(status_td, 0, 0, 1) _td_set_ctrl(status_td, ls, max_pkt) if data_in: @@ -405,7 +426,7 @@ def control_transfer(dev_addr: uint8, endpoint: uint8, ls: int, max_pkt: uint8, _td_set_token(status_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, 0) status_td.buffer = 0 else: - status_td: uhci_td* = uhci_td*(uint64(tds) + uhci_td.__sizeof__()) + status_td = uhci_td*(uint64(tds) + uhci_td.__sizeof__()) _td_set_link(status_td, 0, 0, 1) _td_set_ctrl(status_td, ls, max_pkt) _td_set_token(status_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, 0) @@ -416,5 +437,36 @@ def control_transfer(dev_addr: uint8, endpoint: uint8, ls: int, max_pkt: uint8, result: int = _wait_for_complete(status_td, 500) _qh_set_element(_ctrl_qh, 0, 1) + + # fail-fast:不只等 status TD —— DATA 阶段(以及 setup/status)的完成状态逐个校验。 + # 只等 status TD 会漏掉"DATA 阶段 CRC/超时但 status 正常"的情形 ⇒ 返回 0 却零字节, + # 调用方(GET_DESCRIPTOR)拿到空缓冲区继续走,最终表现为 iface_class=0(现场)。 + fail: int = 0 + which: int = 0 + st_setup: uint32 = setup_td.ctrl_status + st_data: uint32 = 0 + st_status: uint32 = status_td.ctrl_status + e_setup: int = _td_error(setup_td) + if e_setup != 0: + fail = 0 - 10 - e_setup + which = 0 + if data_td is not None: + st_data = data_td.ctrl_status + e_data: int = _td_error(data_td) + if e_data != 0: + fail = 0 - 20 - e_data + which = 1 + e_status: int = _td_error(status_td) + if e_status != 0: + fail = 0 - 30 - e_status + which = 2 + if fail != 0: + _db: [128]char + viperlib.snprintf(&_db, 128, "[uhci] ctrl fail dev=%d ep=%d which=%d setup=0x%x data=0x%x status=0x%x\n", + uint32(dev_addr), uint32(endpoint), uint32(which), + uint32(st_setup), uint32(st_data), uint32(st_status)) + serial.puts(&_db) mm.free(tds) + if fail != 0: + return fail return result diff --git a/VKernel/Kernel/drivers/usb/usb.vp b/VKernel/Kernel/drivers/usb/usb.vp index 25b1717..7767ca0 100644 --- a/VKernel/Kernel/drivers/usb/usb.vp +++ b/VKernel/Kernel/drivers/usb/usb.vp @@ -204,6 +204,12 @@ def enumerate_port(port: int) -> int: desc: usb_dev_desc* = dev_buf max_pkt: uint8 = desc.bMaxPacketSize0 if max_pkt < 8: max_pkt = 8 + # 探针:设备描述符只要读回 18/8 字节,bLength 必为 18、bDescriptorType 必为 1。 + # 全 0 ⇒ 控制传输的 DATA 阶段没落盘(而非解析问题)。 + _db0: [96]char + viperlib.snprintf(&_db0, 96, "[usb] devdesc blen=%d btype=%d maxpkt0=%d\n", + int(desc.bLength), int(desc.bDescriptorType), int(desc.bMaxPacketSize0)) + serial.puts(&_db0) new_addr: uint8 = uint8(_device_count + 1) result = set_address(0, ls, max_pkt, new_addr) @@ -260,7 +266,16 @@ def enumerate_port(port: int) -> int: mm.free(dev_buf) return -8 - _parse_config(dev, config_buf, total_len) + _parse_config(dev, config_buf, total_len) + + # 探针:配置描述符读回后,wTotalLength 应为 0x22/0x23、首字节应为 09 02(bLength=9, CONFIG)。 + # total=0 或首字节为 0 ⇒ DATA 阶段没传回来;有内容但 iface 仍为 0 ⇒ 解析/布局问题。 + _cb: uint8* = config_buf + _db1: [128]char + viperlib.snprintf(&_db1, 128, "[usb] cfgdesc total=%d b0=%02X t0=%02X iface=%02X proto=%02X ep=%d\n", + int(total_len), int(_cb[0]), int(_cb[1]), + int(dev.iface_class), int(dev.iface_protocol), int(dev.ep_in)) + serial.puts(&_db1) result = set_configuration(new_addr, ls, max_pkt, cfg.bConfigurationValue) if result != 0: