Marvell 88SE9215 AHCI驱动笔记

禁止转载!禁止转载!禁止转载!

 

一、Marvell 88SE9215、AHCI与SATA简介

1.Marvell 88SE9215

1)概述

88SE9215是一个四端口,兼容3 Gbps和6 Gbps的SATA主机总线适配器,提供一个单线PCle 2.0接口、SATA控制器功能和4个6 Gbps SATA端口。下文将以PCIe EP设备的配置、HBA的初始化、Port的初始化、Command Slot的填充来介绍88SE9215驱动。系统框图如图1-1所示。

                                                                                             

 

 

2)PCIe控制器

  88SE9215是PCle 2.0 EP设备,符合PCle 2.0规范,支持2.5 Gbps和5 Gbps的通信速度,支持SATA控制器的AHCI编程接口寄存器,支持积极电源管理,支持错误报告、恢复和纠正,支持消息提示中断(MSI)。

3)SATA控制器

 符合串行ATA规范3.1。支持通信速度6gbps, 3gbps和1.5 Gbps。支持可编程输出信号电平。支持Gen 1i, Gen 1x, Gen 2i, Gen 2m, Gen 2x和Gen 3i支持AHCI 1.0编程接口。支持4个SATA接口。支持原生命令队列(NCQ)。支持多端口FIS或命令交换。支持分部和休眠电源管理状态。支持交错Spin-up。

2.AHCI与SATA

1)AHCI

AHCI(Advance Host Controller Interface)由 Intel 开发,以方便处理 SATA 设备。 AHCI 规范强调 AHCI 控制器(称为host bus adapter 或 HBA)旨在成为系统内存和 SATA 设备之间的数据移动引擎。它封装了 SATA 设备,并为主机提供了标准的 PCI 接口。系统设计人员可以使用系统内存和内存映射寄存器轻松访问 SATA 驱动器,而无需像 IDE 那样操作烦人的任务文件。AHCI 控制器最多可支持 32 个端口,这些端口可以连接不同的 SATA 设备。 AHCI 支持所有原生 SATA 功能,例如命令队列、热插拔、电源管理等。对于软件开发人员来说,AHCI 控制器只是具有总线主控能力的 PCI 设备。

2)SATA

SATA标准分别由T13和SATA-IO维护。 SATA-IO 侧重于串行 ATA,而 T13 也包含传统的并行 ATA 规范。

虽然 IDE 和 SATA 的硬件规格(甚至在实现它们的不同设备之间)差异很大,但 API 和 ABI 非常相似。对于软件开发者来说,SATA 和PATA 的最大区别在于 SATA 使用 FIS(帧信息结构)数据包在主机与设备之间传输数据。 FIS 可以被视为传统任务文件的数据集,或 ATA 命令的封装。 SATA 使用与PATA 相同的命令集。

二、PCIe EP设备的配置

1.Command 寄存器(Offset 04h)与Status 寄存器(Offset 06h)

                                           

 

 

Command 寄存器需要将以下状态位,设置为1:

Memory Space位,该位表示PCI设备是否相应存储器请求。

Bus Master位,该位表示PCI设备是否可以作为主设备。

Status寄存器写入~0UL,将错误位清除。

2.Cache Line Size 寄存器 (Offset 0Ch)与Latency Timer 寄存器 (Offset 0Dh)

需要注意,在PCIe设备中,Cache Line Size 寄存器的值并无意义,因为PCIe设备在进行数据输送时,在其报文中含有一次数据传送的大小,PCIe总线控制器可以使用这个“大小”,判断数据区域与Cache行的对应关系。

而Latency Timer 寄存器的值则必须设置为0,因为PCIe设备并不需要使用该寄存器,PCIe总线的仲裁方式与PCI总线不同,使用的连接方法也与PCI总线不同。

3.Base Address Register 0~5寄存器

BAR是PCI配置空间中从0x10h 到 0x24h 的6个register,用来定义PCI需要的配置空间大小以及配置PCI设备占用的地址空间。

每个PCI设备在BAR中描述自己需要占用多少地址空间,bios通过所有设备的这些信息构建一张address map,描述系统中资源的分配情况,然后在合理的将地址空间配置给每个PCI设备。 一旦BAR的值确定了(Have been programmed),其指定范围内的当前设备中的内部寄存器(或内部存储空间)就可以被访问了。当该设备确认某一个请求(Request)中的地址在自己的BAR的范围内,便会接受这请求。如果某个设备的BAR没有被全部使用,则对应的BAR应被硬件全被设置为0,并且告知软件这些BAR是不可以操作的。

HBA使用BAR5来访问,所以必须配置BAR5的值。本例中,BAR5的地址被分配为0x40400000到0x4FFFFFFF,需要注意的是,处理器使用的存储器域的地址,而BAR寄存器存放PCI总线域的地址,需要将PCI总线域的地址转换为存储器域的地址,才可以访问PCI设备的寄存器空间。因为在Vivado中配置了动态从桥地址转换,所以通过对BAR5的首地址0x40400000进行偏移,即可对HBA进行配置。

如下图所示,在AXI Memory Mapped To PIC Express中,勾选Enbale Dynamic Address Translation,即可实现动态从桥地址转换。并在Address Editor中,配置合适的PCIe控制器地址与BAR地址。

 

三、HBA初始化

HBA寄存器被分成两部分,全局寄存器和端口控制寄存器。所有100h以下地址的寄存器都是全局的,并适用于整个HBA。所有端口的端口控制寄存器都是相同的,并且有多少端口就有多少寄存器组。所有未定义的寄存器和寄存器内所有保留位在读取时返回"o"。

1.重置HBA

需要将GHC(Offset 04h: GHC – Global HBA Control)中的HBA Reset位置1,延迟200ms后检测GHC的HBA Reset位是否为0,如依然为1则重置失败,返回错误。成功后,注册并使能中断使能。

2.使能AHCI,获取端口信息

将GHC中的AHCI Enable位置1,即可使能AHCI

读取CAP(Offset 00h: CAP – HBA Capabilities),读取低5位再加1,即可得到端口数

接着读取CAP的8~12位再加1,即可得到最大支持的Command slot数。

 之后读取CAP的30位,得到控制器是否具有支持NCQ的能力。

读取PI(Offset 0Ch: PI – Ports Implemented),其对应的某一位为1,则说明对应位的端口可供使用

3.分配内存并使能端口

AHCI的端口地址从100h开始,之后的端口地址在前一个端口地址基础上偏移80h,如端口0的地址为100h,端口1则为180h,以此类推。

1)停止端口

首先需要确保端口不在运行状态,否则后续的配置将会无效。检测端口的PxCMD(Offset 18h: PxCMD( Port x Command and Status)位状态,确保CR(Command List Running)、FR(FIS Receive Running)、FRE (FIS Receive Enable)、ST(Start)位为0,每有一位由1置0,则需要等待500毫秒。将中断关闭,清除挂起的中断位,即可开始分配内存。

2)分配内存

 

   

 

 

AHCI端口寄存器并不直接保存需要发送或接收的命令与数据,而是在PxCLB(Offset 00h: PxCLB – Port x Command List Base Address )与PxFB(Offset 08h: PxFB – Port x FIS Base Address)中保存命令与数据的首地址。

上图中每个端口都拥有一个Command list与Recived FIS Structure,Command List由 1 到 32 个命令头组成,每个头称为一个槽(slot)。命令槽是一个32字节的结构,详细说明了命令的方向类型以及Command table的地址

Command table中保存Command FIS(CFIS)、ATAPI Command(ACMD)与Physical Region Descriptor Table (PRDT),CFIS可以存储需要发送的命令,PRDT用来保存要传输数据地址的列表,ACMD包含要传输的ATAPI命令,仅当在命令头中设置了‘a’位时需要将其填充。

以上图为例,每个端口的Command list可以分配32个Command slot,每个slot为32字节,即分配1024字节即可,需要注意的是,Command list的地址需要1024字节对齐,否则无法正确发送命令。

Received FIS分配256字节,需要256字节对齐,用来接收从device端发来的FIS。Command table则分为前80h固定大小,与至多65535字节的PRDT部分,这里分配56个PRD,即总大小为1024字节,同样进行1024字节对齐。内存分配完成后,将地址分别赋值给PxCLB与PxFB。

3)使能端口

打开端口的SATA链路,将PxCMD的SUD置为1,如果在2毫秒PxSSTS( Port x Serial ATA Status (SCR0: SStatus))低3位没有变为3,即可判定此端口的链路不通,释放第二步分配的内存,继续检测其他端口。链路打开后,将POD、FRE、ICC置为1,检测PxTFD(Port x Task File Data)的BSY与 DRQ位,如100毫秒内置0则初始化成功,否则释放第二步分配的内存。读PxSIG(Offset 24h: PxSIG – Port x Signature)寄存器,0x101代表sata设备,0xEB140101为 SATAPI 驱动器,一些有问题的 AHCI 控制器可能无法正确设置PxSIG,最可靠的方法是从设备读回的Identify数据中判断。最后打开中断

四、如何发送命令

为了发送命令,我们需要构造一个Command header,之后设置端口命令发出寄存器 (PxCI) 中的相应位, AHCI 控制器会自动向设备发送命令并等待响应。如果发生错误,端口中断寄存器 (PORTxIS) 中的错误位将被置1,并且可以从端口任务文件寄存器 (PxTFD)、PxSSTS 寄存器  和 PxSERR寄存器 中获取信息。如果发送成功,则对应PxCI位将被清除,接收到的数据(如果有)将由 AHCI 控制器从设备复制到主机存储器。

SATA 支持queued commands以增加吞吐量。不同于传统的并行ATA驱动器,当之前的命令仍在运行时,SATA 驱动器可以处理新命令。使用 AHCI,主机最多可以同时向设备发送 32 个命令。

1.选择Command Slot与填充FIS

读取PxSACT与PxCI的值,并进行或运算,或运算后其最低为0的位即为空闲可用的Slot,根据Slot对Command list地址进行偏移,可以得到可用的Command header地址。

下图为Host to Device的FIS格式,以写块设备为例:

FIS Type为27h,代表主机到设备

C为1表示Command, 0表示Control;

Command填写需要的命令,这里为写块设备,所以填ATA_CMD_WRITE,即0xCA;

LBA为block的偏移量

Count为需要写入的扇区数量

此时,FIS即填充完成。

 

 

 

2.构造Command header

首先。根据我们传入的数据内存块大小,对cache进行flush,将cache的内容刷新到内存中,确保DMA可以正确搬移数据,如果为读,则是invalidate。需要注意的是,对于设备上的dma,分配的地址必须是cache line的整数倍。要写入的数据内存块也必须确保对齐cache line

接下来,参考分配内存时的结构图来构造Command Table。首先,将FIS拷贝到CFIS中。之后,根据数据块大小(最大不超过4MB),计算所需要的PRDT长度,即PRDTL,并将数据分段填入各个PRD中。完成后执行cache_flush(<address of Command Table>, sizeof(<Command Table>))

最终,构造Command header,将FIS的长度与PRDTL根据结构图分别填入对应位置,即低4位与16~31位。因为是写操作,将第6位‘W’置1。将Command table的地址写入Command header的CTBA中,最后执行cache_flush(<address of Command Header>, sizeof(<Command Header>))。

3.命令的发送与检测

此时,只需要将PxCI中与slot对应的位写入1,AHCI 控制器会自动向设备发送命令并等待响应。此时可以通过轮询或中断的方式检测任务是否完成,这里以轮询为例。如PxCMD的CR位为1,说明此时已启动dma,检测PxSERR查看是否存在错误位。接着轮询检查对应的PxCI位是否为0,当为0时,说明命令发送成功,任务执行完成。

五、总结

初始化

- 在 PCI 命令寄存器中启用中断、DMA 和内存空间访问
- 内存映射 BAR 5 寄存器。
- 重置控制器
- 在全局主机控制寄存器中启用 AHCI 模式和中断。
- 读取capabilities寄存器。如果需要,检查是否支持 64 位 DMA。
- 对于所有已实现的端口:
- 为其命令列表、接收到的 FIS 及其命令表分配物理内存。确保命令表是 1k 字节对齐的。
- 设置命令列表和接收到的 FIS 地址寄存器(和高位寄存器,如果支持)。
- 设置命令列表条目以指向相应的命令表。
- 重置端口。
- 使用端口的命令寄存器启动命令列表处理。
- 读取端口的签名/状态以查看它是否连接到驱动器。
- 向连接的驱动器发送 IDENTIFY ATA 命令。获取他们的扇区大小和数量。

启动 读/写命令

- 选择要使用的可用命令槽。
- 设置命令 FIS。
- 设置 PRDT。
- 设置命令头。
- 发送命令.