fix(usb): uhci 控制传输逐 TD 校验并暴露错误位,补描述符落盘探针
- _wait_for_complete 原先只查 status TD 的 bit21|bit22,DATA 阶段的 CRC/超时(bit18)、bitstuff(bit17)、babble(bit20) 会被当成成功 ⇒ GET_DESCRIPTOR 拿到零字节缓冲区仍返回 0,最终表现为 [usb] device class=00 proto=00 ep=0、usb_kbd 找不到键盘(现场)。改为对齐 Linux usb-uhci.h:新增 TD_CTRL_ERR_MASK/NAK_MASK 与 _td_error(),control_transfer 收尾逐个校验 setup/data/status 三个 TD,失败时打印三个状态字。 - usb.vp 增加两条探针:[usb] devdesc blen/btype/maxpkt0 与 [usb] cfgdesc total/b0/t0/iface/proto/ep,用于区分数据未落盘与解析未命中。
This commit is contained in:
1 parent
89907682ec
commit
1b2f6853fc
2 files changed
+72
-5
No files matched your search
@@ -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
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user