4. SATA 传输层与命令协议

好,咱们进入传输层。这一层说白了,就是 FIS(Frame Information Structure)的组装与拆解。嗯,我个人觉得,传输层是排查很多“莫名其妙”故障的第一现场。你信号测试全 pass,链路层训练也过了,但死活读不回数据。这时候,十有八九是传输层或者命令协议出了问题。

4.1 FIS 结构:一切通信的基石

SATA 里没有“裸数据”在线上跑。所有信息——数据、命令、状态——都要装进 FIS 这个“信封”里。FIS 结构是固定的,Host 和 Device 都得按这个格式来。

FIS 类型 方向 主要用途
Register – Host to Device Host → Device 发送命令、写寄存器
Register – Device to Host Device → Host 回传状态、PIO 设置
DMA Activate Device → Host 通知 Host 可以 DMA 了
Data 双向 传输用户数据
BIST Activate 双向 进入 BIST 测试模式
PIO Setup Device → Host PIO 模式下的数据段描述
Set Device Bits Device → Host 异步状态更新
小提示: 抓 trace 时,我最先看的就是 FIS Type 字段。有一次客户反应写盘很慢,我一看 trace,全是 Register FIS 来回跑,数据 FIS 根本没几帧。说白了,设备把 DMA 退化成 PIO 了。后来发现是 NCQ 队列冲突了。

每个 FIS 的开头都是 DW0(4 字节),它决定了这个帧的类型和属性。举个例子,一个 Host 发出去的 Register – Host to Device FIS,结构是这样的:

DW0: FIS Type (27h) | C (1 bit) | PM Port (4 bits) | Rsvd
DW1: Feature (7:0) | Command (7:0)
DW2: LBA (7:0) | LBA (15:8) | LBA (23:16) | Device
DW3: LBA (31:24) | LBA (39:32) | LBA (47:40) | Feature (15:8)
DW4: Count (7:0) | Count (15:8) | ICC | Control

你注意看那个 C bit。这玩意儿是“C”还是“S”,直接决定了 FIS 的角色。C=1 表示这是从 Shadow Register Block(也就是 Host 视角)发出的命令。我见过有 FPGA 原型把这位搞反了,结果设备解析出来的命令码全是乱码。

4.2 命令协议:非 NCQ vs NCQ

SATA 命令执行方式分两大类:非 NCQ 和 NCQ。我建议你把它们理解成单线程和多线程的关系。

4.2.1 非 NCQ 协议

这是最老、也是最“死板”的方式。Host 发一个命令,等着 Device 回完状态,才能发下一个。常见的就是 Read DMA (25h) 和 Write DMA (35h)。流程差不多就是:

  1. Host 发送 Register FIS(命令+参数)
  2. Device 准备数据 -> 发送 DMA Activate FIS
  3. Host 收到 Activate 后,发送 Data FIS(如果是写)或者接收 Data FIS(如果是读)
  4. Device 发送 Register FIS(D2H)带状态
关键点: 非 NCQ 下,你读个 8KB 的扇区,和读一个 128KB 的扇区,协议开销是一样的。但每次命令的“握手”都特别重。说白了,对于随机小 I/O,效率很低。

4.2.2 NCQ 协议

NCQ(Native Command Queuing)是 SATA 协议里一个很漂亮的改进。Host 可以一次性扔 32 个命令下去,设备自己排序、优化寻道,再逐个完成。你想想看,这对于机械硬盘来说,能省多少来回寻道的时间。

我遇到过一例:机械盘做视频监控,写入小文件多,不开 NCQ 的时候,I/O 深度稍微上去一点,盘就开始“哗啦哗啦”响,而且掉速。开了 NCQ 之后,盘内部重新排序,磁头臂来回次数少了一半多,性能直接翻倍。

NCQ 的关键在于 Tag。每个命令都挂一个 Tag 编号(0~31)。设备完成某个命令后,回送的 Register D2H FIS 里必须带那个 Tag。Host 靠这个才知道“哦,是 Tag 3 的命令做完了”。

避坑指南: 我曾经在一个国产主控的项目上吃过亏。设备在处理 NCQ 命令时,发生了内部错误,于是它发了一个 Set Device Bits FIS,只把 SDBC(Status/Error)写回来,但无意中把其他正常 Tag 的完成状态给覆盖了。Host 侧一看,以为多个 Tag 都出错了,结果整个队列都被清空了。那个问题是查了好几天才定位到——就是 Tag 管理没做好,一错毁全部。

咱们可以把非 NCQ 和 NCQ 的差异做个对比:

对比项 非 NCQ NCQ
并发命令数 1 最多 32
命令 Tag 无 必须
适用场景 顺序大块传输 随机小块 & 多线程
排查侧重点 超时 & 状态机 Tag 冲突 & 队列溢出

4.3 传输层故障根因清单

嗯,这里我结合自己几十次排查经验,列个清单。你在现场遇到问题时,对着这个清单先过一遍,能省很多时间。

  • FIS 类型不对齐: Host 发出的是 Register H2D,设备却回了一个 Data FIS。我见过有人把 Flash 控制器内部的 FIS 类型映射表写错了。
  • CRC 校验错误: 传输层的 CRC 是跟在每个 FIS 后面的。CRC 错,设备或 Host 会发 ERR,然后重传。如果重传次数超过协议限制(通常是 3 次),链路就要挂了。排查时先看 ERR 计数器。
  • Tag 冲突: 两个不同的命令用了一样的 Tag。这种情况 NCQ 下尤其要命。你查 trace 时,如果发现同一个 Tag 在完成前又被下发,那就是 bug。
  • 状态机死锁: Host 发了命令,等着 Device 回 DMA Activate。但 Device 因为内部缓存满了,一直没发。Host 也不超时重试——就卡死了。我曾经在一个老平台上看过这种,最后加了 Watchdog 才解决。
  • 命令参数错位: LBA 地址、扇区数写错了。比如要写 LBA 1000,但写成了 LBA 1。这种表现就是“写盘成功,但读出来是错的”。数据一致性就被破坏了。
SATA 命令执行与 FIS 交互流程 Host Device 1. Register H2D (27h) + 命令 2. 准备数据 3. DMA Activate 4. Data FIS (46h) 传输数据 5. Register D2H 注:NCQ 下,步骤 1 可重复 32 次(不同 Tag);步骤 5 可乱序完成
小习惯: 我每次排查 SATA 问题,第一步不是看示波器,而是先打开 SATA Analyzer 抓一次 FIS trace。从 trace 里一眼就能看出是卡在“等命令下发”、“等 Activate”还是“等 Status”。这个“三部曲”定位法,可以快速把范围从整个协议栈缩小到某一个环节。

总结一下第 4 章:传输层是 SATA 的灵魂。FIS 是它说话的语言,命令协议是它办事的规矩。非 NCQ 简单可靠,NCQ 高效复杂。你先搞懂 FIS 结构,再记住那套完整的命令交互流程,后面排查任何挂死、超时、数据错误,脑子里就有一张清晰的地图了。