前言:诫命是是灯,法则是光,训诲的责备是生命的道。
作为计算机,除了运算能力,还要求有存储能力。就像一个人一样,有逻辑思维能力还不行,还需要有上佳的记忆能力。只有这样,才能凭着人生丰富的阅历,对未来的事情做出聪颖的判断。
SATA就是为计算机存储所做的一个接口。INTEL SATA控制器一般可以支持三种操作模式:IDE,AHCI,RAID。
对于RAID操作模式,BIOS代码中有一个专门的Legacy OptionROM和一个二进制的EFI driver来初始化和操作,具体细节无法看到。但IDE模式和AHCI模式没有那种限制,我们可以看到其所有的代码和操作,下面就谈一下IDE和AHCI吧。
1. 初始化控制器
1.1 设置模式
主要设置两方面。一方面为PI寄存器,详情如下:
MODE | Base Class | Sub Class | Program Interface |
AHCI | 01 | 06 | 01 |
Legacy IDE | 01 | 01 | 8X |
Enhanced IDE | 01 | 01 | 8F |
另一方面,要设置MAP寄存器的SATA Mode Select位。
MODE | Value |
00 | IDE |
01 | AHCI |
10 | RAID |
1.2 使能SATA端口
将PCS的PxE位置1,然后检查相应的PxP位。如果PxP为1,则说明PortX有设备存在。否则,说明没有此PortX没有下挂任何设备。
如果挂载设备,我们要保持使PxE的使能状态。如果没有下挂设备,我们要将其禁掉,以保证平台其它信号稳定。但如果用户将此PortX设为HotPlug功能或设为TestMode,我们也需要保留其使能状态。以保证测试正常进行或HotPlug功能的正常使用。
1.3 AHCIMemory Bar初始化
(1) 设定MemBAR值,将MemBar值写入SATA Config Space的offset 0x24 Bar处,并将offset 0x04 Command寄存器的BIT1置1,表明SATA Controller支持Memory space的访问。
(2) 设置Cap寄存器(Px register)。
(3) 设置PxCMD寄存器(Px register)。
(4) 设置AHCI Enable为1(ABAR global register)。
2. ATA和ATAPI命令
ATA/ATAPI是HOST端与存储设备间的接口,是都遵守的标准。系统制造商,系统集成开发者,软件人员和存储设备商均按照此规范开发自己的产品。可以认为AT/ATAPI是一组命令集,HOST端发送命令,DEVICE响应命令,将HOST端所需的数据传给HOST端。
来看看两个Command吧:
READ SECTORS,command code为0x20,描述如下:
Register | Description |
Features |
|
Sector Count | 要读的扇区数 |
Sector Number | 扇区地址 |
Cylinder Low | 柱面地址低位 |
Cylinder High | 柱面地址高位 |
Device/Head | 磁头地址 |
Command | 命令寄存器=0x20 |
命令执行完后,就可以通过数据寄存器接收该扇区的值了。
IDENTIFY DEVICE:command code为0xEC,描述如下:
Register | Description |
Features |
|
Sector Count |
|
Sector Number |
|
Cylinder Low |
|
Cylinder High |
|
Device/Head | 磁头地址 |
Command | 命令寄存器=0xEC |
命令执行后,就可以通过相应的寄存器读取Identify数据了,从中可以截取自己想要的信息。
IDE模式和AHCI模式虽然都遵循ATA/ATAPI标准,但实现ATA/ATAPI的方式是不同的。下面将分别以Identify Device command介绍。
2.1 IDE方式
无论是Legacy IDE模式,还是EnhancedIDE模式,都是通过IO方式进行访问。不同的是,LegacyIDE的IO是固定的,而Enhanced IDE的IO要从Bar0和Bar1中读取。
下表是IDE IO寄存器的说明:
Data register(Base+ 0):
通过这个寄存器,可以实现HOST端和Device端的数据交换。一般用于发送命令后,从Device端读数据。
CmdOrStatus register(Base + 7):
读时为Status寄存器时,写时为Command寄存器。
作为Status寄存器,可以反映出设备的状态,也可以反映出命令执行的进度状态。
作为Command寄存器时,ATA/ATAPI命令集所包含的命令需要写入这个寄存器中,来实现其功能。
我们来看一下UDK2014是如何做的:
从上图可以看到,此IDENTIFY DEVICE传了两个参数。除了必须要有的Command寄存器外,还传了DeviceHead寄存器,用于区分primary和secondary。AtaIdentify()调用了AtaPiodDataInOut(),AtaPioDataInOut()调用AtaIssueCommand(),下面看一下AtaIssueCommand()的原型:
EFI_STATUS
EFIAPI
AtaIssueCommand(
IN EFI_PCI_IO_PROTOCOL *PciIo,
IN EFI_IDE_REGISTERS *IdeRegisters,
IN EFI_ATA_COMMAND_BLOCK *AtaCommandBlock,
IN UINT64 Timeout
)
{
EFI_STATUS Status;
UINT8 DeviceHead;
UINT8 AtaCommand;
ASSERT (PciIo != NULL);
ASSERT (IdeRegisters != NULL);
ASSERT (AtaCommandBlock != NULL);
DeviceHead = AtaCommandBlock->AtaDeviceHead;
AtaCommand = AtaCommandBlock->AtaCommand;
Status = WaitForBSYClear (PciIo,IdeRegisters, Timeout);
if (EFI_ERROR (Status)) {
return EFI_DEVICE_ERROR;
}
IdeWritePortB (PciIo, IdeRegisters->Head,(UINT8) (0xe0 | DeviceHead));
Status = DRQClear2 (PciIo, IdeRegisters,Timeout);
if (EFI_ERROR (Status)) {
return EFI_DEVICE_ERROR;
}
//此处填写IO寄存器,略去一些行
IdeWritePortB (PciIo, IdeRegisters->CmdOrStatus,AtaCommand);
MicroSecondDelay (400);
return EFI_SUCCESS;
}
将命令发出后,我们就可以一点点地检查DRQ=1,然后读出命令所需要相应长度的数据了。
2.2 AHCI方式
使用AHCI模式,首先要设置好环境。SATA Controller配置空间的Bar5要设置好,这样才可以映射到ATA设备的寄存器上。然后需要对ABAR初始化。为了实现ATA/ATAPI command,我们首先需要构建好CommandList和ReceiveFIS。然后将其首地址填入Command List Base address和FIS Base address中。这里面要构建好三个重要的数据结构:COMMAND_LIST,COMMAND_TABE和RECEIVED_FIS。
我们来看一下他们的结构:
EFI_AHCI_COMMAND_LIST的结构
typedef struct {
UINT32 AhciCmdCfl:5; //Command FIS Length
UINT32 AhciCmdA:1; //ATAPI
UINT32 AhciCmdW:1; //Write
UINT32 AhciCmdP:1; //Prefetchable
UINT32 AhciCmdR:1; //Reset
UINT32 AhciCmdB:1; //BIST
UINT32 AhciCmdC:1; //Clear Busy upon R_OK
UINT32 AhciCmdRsvd:1;
UINT32 AhciCmdPmp:4; //Port Multiplier Port
UINT32 AhciCmdPrdtl:16; //Physical Region Descriptor Table Length
UINT32 AhciCmdPrdbc; //Physical Region Descriptor Byte Count
UINT32 AhciCmdCtba; //Command Table Descriptor Base Address
UINT32 AhciCmdCtbau; //Command Table Descriptor Base AddressUpper 32-BITs
UINT32 AhciCmdRsvd1[4];
} EFI_AHCI_COMMAND_LIST;
我们可以看到其有两字段AhciCmdCtba和AhciCmdCtbau。这两个字段指示Ata Command table的地址。
EFI_AHCI_COMMAND_TABLE的结构
typedef struct {
EFI_AHCI_COMMAND_FIS CommandFis; // A softwareconstructed FIS.
EFI_AHCI_ATAPI_COMMAND AtapiCmd; // 12 or 16 bytes ATAPI cmd.
UINT8 Reserved[0x30];
EFI_AHCI_COMMAND_PRDT PrdtTable[65535]; // Thescatter/gather list for data transfer
} EFI_AHCI_COMMAND_TABLE;
第一个字段落CommandFis是我们重点构建的对象,它的作用和我们IDE模式下的几个IO端口的作用类似。
PrdtTable字段相当于我们IDE模式下的Data Register。所不同的是,IDE模式下,我们需要借助Data Register一次一个WORD地读,而AHCI模式下,不需要这么麻烦,我们填好PrdtTable的值,不再需要软件的参与,ATA Device会自动帮我们填好数据。
EFI_AHCI_RECEIVED_FIS的结构
typedef struct {
UINT8 AhciDmaSetupFis[0x1C]; // Dma Setup Fis: offset 0x00
UINT8 AhciDmaSetupFisRsvd[0x04];
UINT8 AhciPioSetupFis[0x14]; // Pio Setup Fis: offset 0x20
UINT8 AhciPioSetupFisRsvd[0x0C];
UINT8 AhciD2HRegisterFis[0x14]; // D2H Register Fis: offset 0x40
UINT8 AhciD2HRegisterFisRsvd[0x04];
UINT64 AhciSetDeviceBitsFis; // Set Device Bits Fix: offset 0x58
UINT8 AhciUnknownFis[0x40]; // Unkonwn Fis: offset 0x60
UINT8 AhciUnknownFisRsvd[0x60];
} EFI_AHCI_RECEIVED_FIS;
从这个结构中,我们可以看出,ATA/ATAPI Command的传输方式有多种。BIOS常用的读扇区和发IDENTIFY_DEVICE命令属于PIO的方式。除此外,还有DMA的传输方式。PIO一般用于小数据的传输,DMA用于大数据的传输。就像HDA Controller一样,要是读一下设备VID和DID,只需要使用PIO即可,但你要是录音或播放音乐,肯定要使用DMA了。
下面来看下UDK2014如何在AHCI模式下做IDENTIFY_DEVICE命令的:
传进来的几个参数中,Buffer代表了Identify_device命令结束后数据存储的位置。在此函数中,会使用PIO方式传输。此函数调用AhciPioTransfer(),AhciPioTransfer()调用AhciBuildCommand(),AhciBuildCommand()是整个Transfer中重要的一环,我们来看一下函数体,它是如何Build Command的:
构建Command前,要清空Received_FIS和CommandFis,因为它们靠内存传输的。如果不清空,它们的字段会带着前一次Command的残留值。RECEIVED_FIS中有字段DMA SETUP和PIO SETUP,我们可以认为它们是IDE模式下的Status寄存器。
IDENTIFY_DEVICE命令结束后,ATA device会往AhciPrdtDba所指向的地址输出数据。我们可以认为它是IDE模式下的Data寄存器。
Command Table属于Command List中的字段。AHCI通过Port Register的CLB寄存器,构建起Command List,然后Command List中的字段可以链接Command Table,Command Table中再构建Command FIS,然后整个ABAR就将SATA控制器的内存空间和SATA device的寄存器映射起来了。