Bios工程师手边事—SATA

news/2024/11/29 0:56:01/

前言:诫命是是灯,法则是光,训诲的责备是生命的道。


作为计算机,除了运算能力,还要求有存储能力。就像一个人一样,有逻辑思维能力还不行,还需要有上佳的记忆能力。只有这样,才能凭着人生丰富的阅历,对未来的事情做出聪颖的判断。

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的寄存器映射起来了。


http://www.ppmy.cn/news/545975.html

相关文章

USB大容量存储类规范概述

文章目录 1 简述2 子类代码3 协议代码参考资料 1 简述 USB Mass Storage Class Working Group (CWG)在发展4种大容量存储类的标准规范,包括: USB Mass Storage Class Control/Bulk/Interrupt (CBI) TransportUSB Mass Storage Class Bulk-Only Transpo…

基于UEFI的BIOS怎么识别不同设备(SataHdd、SataCdrom、USB、BMC)

基于UEFI的BIOS怎么识别不同设备(SataHdd、SataCdrom、USB、BMC) 参考:UEFI_SPEC 第一种方法: SATA设备根据EFI_ATA_DEVICE_TYPE类型来细分   ATAPI接口是SCSI和IDE总线的结合产物。该接口使用IDE接口和协议机型ATA和SCSI总线命…

linux ata模式,LIBATA - Linux 内核引导选项简介

[LIBATA] libata.noacpi 在libata驱动休眠/唤醒过程中禁止使用ACPI。主要用于解决某些有缺陷的BIOS导致的硬盘假死问题。 [LIBATA] libata.dma整数 控制DMA特性的使用 libata.dma0 表示完全禁止所有SATA/PATA端口使用DMA libata.dma1 表示仅允许SATA/PATA硬盘使用DMA libata.dm…

服务器换主板后找不到磁盘,服务器硬盘频繁丢失的非常奇怪无解问题

附错误日志(典型的) 事件类型: 错误 事件来源: Disk 事件种类: 无 事件 ID: 11 日期: 2009-9-14 事件: 11:11:14 用户: N/A 计算机: CNQK-JSFOOVENFE 描述: 驱动程序在 \Device\Harddisk1 上检测到控制器错误。 有关更多信息,请参阅在 http://go.microsoft.com/fwli…

ATAPI(磁盘端口驱动)级文件保护简单实现

ATAPI(磁盘端口驱动)级文件保护简单实现 #define IoGetIrpStackLocation( Irp , Level) (\ (Irp)->Tail.Overlay.CurrentStackLocation Level ) BOOL IfIrpHasFobj(PIRP pIrp , LPCWSTR FileName) { ULONG i , j ; PIO_STACK_LOCATION irpStack…

linux打开cd驱动器命令,在Linux中,如何挂载cdrom

CD和DVD使用ISO9660文件系统。 ISO9660的目的是提供各种操作系统之间的数据交换标准。结果,任何Linux操作系统都能够处理ISO9660文件系统。本指南介绍了如何在Linux中安装/卸载ISO9660 file-system,从而使用户能够从CD或DVD介质读取数据的方法。 如果您在阅读本文后仍有疑问,…

Linux环境下ATAPI MO的使用方法(转)

Linux环境下ATAPI MO的使用方法(转)[more]ATAPI MO在Linux操作系统上使用前一般需要对LINUX系统内核作一点修改,然后重新编译LINUX系统内核。 另外,Linux操作系统内核版本小于2.2.2时可能不支持ATAPI MO,因此要在Linux上使用ATAPI MO时&#…

reactos操作系统实现(110)

AtapiStartIo函数主要处理同步的IO请求包。具代的实现代码如下: #001 BOOLEAN #002 NTAPI #003 AtapiStartIo( #004 IN PVOID HwDeviceExtension, #005 IN PSCSI_REQUEST_BLOCK Srb #006 ) #007 #008 /* #009 #010 Routine Description: #0…