972
edits
[unchecked revision] | [unchecked revision] |
mNo edit summary |
m (Bot: Replace deprecated source tag with syntaxhighlight) |
||
(20 intermediate revisions by 8 users not shown) | |||
Line 1:
The specification:
https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/serial-ata-ahci-spec-rev1-3-1.pdf
== Introduction ==
AHCI (Advance Host Controller Interface) is developed by Intel to facilitate handling [[SATA]] devices. The AHCI specification emphasizes that an AHCI controller (referred to as host bus adapter, or HBA) is designed to be a data movement engine between system memory and SATA devices. It encapsulates SATA devices and provides a standard [[PCI]] interface to the host. System designers can easily access SATA drives using system memory and memory mapped registers, without the need for manipulating the annoying task files as IDE do.
An AHCI controller may support up to 32 ports which can attach different SATA devices such as disk drives, port multipliers, or an enclosure management bridge. AHCI supports all native SATA features such as command queueing, hot plugging, power management, etc. To a software developer, an AHCI controller is just a PCI device with bus master capability.
AHCI is a new standard compared to [[IDE]], which has been around for twenty years. There exists little documentation about its programming tips and tricks. Possibly the only available resource is the Intel AHCI specification (see [[#External Links|External Links]]) and some open source operating systems such as Linux. This article shows the minimal steps an OS (not BIOS) should do to put AHCI controller into a workable state, how to identify drives attached, and how to read physical sectors from a SATA disk. To keep concise, many technical details and deep explanations of some data structures have been omitted.
It should be noted that IDE also supports SATA devices and there are still debates about which one, IDE or AHCI, is better. Some tests even show that a SATA disk acts better in IDE mode than AHCI mode. But the common idea is that AHCI performs better and will be the standard PC to SATA interface, though some driver software should be enhanced to fully cultivate AHCI capability.
Line 21 ⟶ 24:
Following code defines different kinds of FIS specified in Serial ATA Revision 3.0.
<
typedef enum
{
Line 33 ⟶ 36:
FIS_TYPE_DEV_BITS = 0xA1, // Set device bits FIS - device to host
} FIS_TYPE;
</syntaxhighlight>
'''2) Register FIS – Host to Device'''
Line 39 ⟶ 42:
A host to device register FIS is used by the host to send command or control to a device. As illustrated in the following data structure, it contains the IDE registers such as command, LBA, device, feature, count and control. An ATA command is constructed in this structure and issued to the device. All reserved fields in an FIS should be cleared to zero.
<
typedef struct tagFIS_REG_H2D
{
// DWORD 0
// DWORD 1
// DWORD 2
// DWORD 3
// DWORD 4
} FIS_REG_H2D;
</syntaxhighlight>
'''3) Register FIS – Device to Host'''
Line 79 ⟶ 82:
A device to host register FIS is used by the device to notify the host that some ATA register has changed. It contains the updated task files such as status, error and other registers.
<
typedef struct tagFIS_REG_D2H
{
// DWORD 0
// DWORD 1
// DWORD 2
// DWORD 3
// DWORD 4
} FIS_REG_D2H;
</syntaxhighlight>
'''4) Data FIS – Bidirectional'''
Line 119 ⟶ 122:
This FIS is used by the host or device to send data payload. The data size can be varied.
<
typedef struct tagFIS_DATA
{
// DWORD 0
// DWORD 1 ~ N
} FIS_DATA;
</syntaxhighlight>
'''5) PIO Setup – Device to Host'''
Line 139 ⟶ 142:
This FIS is used by the device to tell the host that it’s about to send or ready to receive a PIO data payload.
<
typedef struct tagFIS_PIO_SETUP
{
// DWORD 0
// DWORD 1
// DWORD 2
// DWORD 3
// DWORD 4
} FIS_PIO_SETUP;
</syntaxhighlight>
'''6) DMA Setup – Device to Host'''
<
typedef struct tagFIS_DMA_SETUP
{
// DWORD 0
//DWORD 1&2
// SATA Spec says host specific and not in Spec. Trying AHCI spec might work.
//DWORD 3
//DWORD 4
//DWORD 5
//DWORD 6
} FIS_DMA_SETUP;
</syntaxhighlight>
'''7) Example'''
Line 219 ⟶ 223:
To issue an ATA Identify command to the device, the FIS is constructed at follows.
<
FIS_REG_H2D fis;
memset(&fis, 0, sizeof(FIS_REG_H2D));
fis
fis
fis
fis
</syntaxhighlight>
After the device receives this FIS and successfully read the 256 words data into its internal buffer, it sends a PIO Setup FIS – Device to Host to tell the host that it’s ready to transfer data and the data size (FIS_PIO_SETUP.tc).
Line 236 ⟶ 240:
== Find an AHCI controller ==
An AHCI controller can be found by enumerating the PCI bus. It has a class id 0x01 (mass storage device) and normally a subclass id 0x06 (serial ATA, but can be IDE Interface 0x01). The vendor id and device id should also be checked to ensure it’s really an AHCI controller.
=== Determining what mode the controller is in ===
As you may be aware, a SATA controller can either be in IDE emulation mode or in AHCI mode. The problem that enters here is simple: <br />
'''How to find what mode the controller is in'''. The documentation is really obscure on this. Perhaps the best way is to initialize a SATA controller as both IDE and AHCI. In this way, as long as you are careful about non-existent ports, you cannot go wrong.
One possible way of doing this is by checking the bit 31 of GHC register. It's labeled as AHCI Enable.
== AHCI Registers and Memory Structures ==
Line 252 ⟶ 258:
[[Image:HBA_registers.jpg]]
<
typedef volatile struct tagHBA_MEM
{
// 0x00 - 0x2B, Generic Host Control
// 0x2C - 0x9F, Reserved
// 0xA0 - 0xFF, Vendor specific registers
// 0x100 - 0x10FF, Port control registers
Line 280 ⟶ 286:
typedef volatile struct tagHBA_PORT
{
} HBA_PORT;
</syntaxhighlight>
This memory area should be configured as uncacheable as they are memory mapped hardware registers, not normal prefetchable RAM. For the same reason, the data structures are declared as "volatile" to prevent the compiler from over optimizing the code.
Line 316 ⟶ 322:
Data FIS – Device to Host is not copied to this structure. Data payload is sent and received through PRDT (Physical Region Descriptor Table) in Command List, as will be introduced later.
<
typedef volatile struct tagHBA_FIS
{
// 0x00
FIS_DMA_SETUP dsfis; // DMA Setup FIS
// 0x20
FIS_PIO_SETUP psfis; // PIO Setup FIS
// 0x40
FIS_REG_D2H rfis; // Register – Device to Host FIS
// 0x58
Line 335 ⟶ 341:
// 0x60
// 0xA0
} HBA_FIS;
</syntaxhighlight>
'''4) Command List'''
Line 352 ⟶ 358:
[[Image:Command_list.jpg]]
<
typedef struct tagHBA_CMD_HEADER
{
// DW0
// DW1
volatile
// DW2, 3
// DW4 - 7
} HBA_CMD_HEADER;
</syntaxhighlight>
'''5) Command Table and Physical Region Descriptor Table'''
Line 392 ⟶ 398:
[[Image:Command_table.jpg]]
<
typedef struct tagHBA_CMD_TBL
{
// 0x00
// 0x40
// 0x50
// 0x80
Line 410 ⟶ 416:
typedef struct tagHBA_PRDT_ENTRY
{
// DW3
} HBA_PRDT_ENTRY;
</syntaxhighlight>
== Detect attached SATA devices ==
'''1) Which port
As specified in the AHCI specification, firmware (BIOS) should initialize the AHCI controller into a minimal workable state. OS usually needn’t reinitialize it from the bottom. Much information is already there when the OS boots.
The Port Implemented register (HBA_MEM.pi) is a 32 bit value and each bit represents a port. If the bit is set in this register, then the
'''2) What kind of device is attached'''
Line 433 ⟶ 439:
There are four kinds of SATA devices, and their signatures are defined as below. The Port Signature register (HBA_PORT.sig) contains the device signature, just read this register to find which kind of device is attached at the port. Some buggy AHCI controllers may not set the Signature register correctly. The most reliable way is to judge from the Identify data read back from the device.
<
#define SATA_SIG_ATA 0x00000101 // SATA drive
#define SATA_SIG_ATAPI 0xEB140101 // SATAPI drive
Line 450 ⟶ 456:
void probe_port(HBA_MEM *abar)
{
// Search disk in
int i = 0;
while (i<32)
Line 488 ⟶ 494:
static int check_type(HBA_PORT *port)
{
if (det != HBA_PORT_DET_PRESENT) // Check drive status
Line 510 ⟶ 516:
}
}
</syntaxhighlight>
== AHCI port memory space initialization ==
Line 518 ⟶ 524:
Before rebasing Port memory space, OS must wait for current pending commands to finish and tell HBA to stop receiving FIS from the port. Otherwise an accidently incoming FIS may be written into a partially configured memory area. This is done by checking and setting corresponding bits at the Port Command And Status register (HBA_PORT.cmd). The example subroutines stop_cmd() and start_cmd() do the job.
The following example assumes that the HBA has 32 ports implemented and each port contains 32 command slots, and will allocate 8 PRDTs for each command slot. (Note: unlike in the above struct definitions, this is using 8 instead of 1)
<
#define AHCI_BASE 0x400000 // 4M
Line 566 ⟶ 572:
{
// Wait until CR (bit15) is cleared
while (port->cmd & HBA_PxCMD_CR)
;
// Set FRE (bit4) and ST (bit0)
Line 578 ⟶ 585:
// Clear ST (bit0)
port->cmd &= ~HBA_PxCMD_ST;
// Clear FRE (bit4)▼
port->cmd &= ~HBA_PxCMD_FRE;▼
// Wait until FR (bit14), CR (bit15) are cleared
Line 589 ⟶ 599:
}
▲ // Clear FRE (bit4)
▲ port->cmd &= ~HBA_PxCMD_FRE;
}
</syntaxhighlight>
== AHCI & ATAPI ==
Line 602 ⟶ 610:
The code example shows how to read "count" sectors from sector offset "starth:startl" to "buf" with LBA48 mode from HBA "port". Every PRDT entry contains 8K bytes data payload at most.
<
#define ATA_DEV_BUSY 0x80
#define ATA_DEV_DRQ 0x08
{
port->is = (
int spin = 0; // Spin lock timeout counter
int slot = find_cmdslot(port);
if (slot == -1)
return
HBA_CMD_HEADER *cmdheader = (HBA_CMD_HEADER*)port->clb;
cmdheader += slot;
cmdheader->cfl = sizeof(FIS_REG_H2D)/sizeof(
cmdheader->w = 0; // Read from device
cmdheader->prdtl = (
HBA_CMD_TBL *cmdtbl = (HBA_CMD_TBL*)(cmdheader->ctba);
Line 627 ⟶ 635:
for (int i=0; i<cmdheader->prdtl-1; i++)
{
cmdtbl->prdt_entry[i].dba = (
cmdtbl->prdt_entry[i].dbc = 8*1024-1; // 8K bytes (this value should always be set to 1 less than the actual value)
cmdtbl->prdt_entry[i].i = 1;
buf += 4*1024; // 4K words
Line 634 ⟶ 642:
}
// Last entry
cmdtbl->prdt_entry[i].dba = (
cmdtbl->prdt_entry[i].dbc = (count<<9)-1; // 512 bytes per sector
cmdtbl->prdt_entry[i].i = 1;
Line 645 ⟶ 653:
cmdfis->command = ATA_CMD_READ_DMA_EX;
cmdfis->lba0 = (
cmdfis->lba1 = (
cmdfis->lba2 = (
cmdfis->device = 1<<6; // LBA mode
cmdfis->lba3 = (
cmdfis->lba4 = (
cmdfis->lba5 = (
cmdfis->countl =
cmdfis->counth =
// The below loop waits until the port is no longer busy before issuing a new command
Line 691 ⟶ 699:
}
return
}
Line 698 ⟶ 706:
{
// If not set in SACT and CI, the slot is free
for (int i=0; i<cmdslots; i++)
{
Line 708 ⟶ 716:
return -1;
}
</syntaxhighlight>
== Checklist ==
=== Initialisation ===
* Enable interrupts, DMA, and memory space access in the PCI command register
* Memory map BAR 5 register as uncacheable.
* Perform BIOS/OS handoff (if the bit in the extended capabilities is set)
* Reset controller
* Register IRQ handler, using interrupt line given in the PCI register. This interrupt line may be shared with other devices, so the usual implications of this apply.
* Enable AHCI mode and interrupts in global host control register.
* Read capabilities registers. Check 64-bit DMA is supported if you need it.
* For all the implemented ports:
** Allocate physical memory for its command list, the received FIS, and its command tables. Make sure the command tables are 128 byte aligned.
** Memory map these as uncacheable.
** Set command list and received FIS address registers (and upper registers, if supported).
** Setup command list entries to point to the corresponding command table.
** Reset the port.
** Start command list processing with the port's command register.
** Enable interrupts for the port. The D2H bit will signal completed commands.
** Read signature/status of the port to see if it connected to a drive.
** Send IDENTIFY ATA command to connected drives. Get their sector size and count.
=== Start read/write command ===
* Select an available command slot to use.
* Setup command FIS.
* Setup PRDT.
* Setup command list entry.
* Issue the command, and record separately that you have issued it.
=== IRQ handler ===
* Check global interrupt status. Write back its value. For all the ports that have a corresponding set bit...
* Check the port interrupt status. Write back its value. If zero, continue to the next port.
* If error bit set, reset port/retry commands as necessary.
* Compare issued commands register to the commands you have recorded as issuing. For any bits where a command was issued but is no longer running, this means that the command has completed.
* Once done, continue checking if any other devices sharing the IRQ also need servicing.
== External Links ==
Line 718 ⟶ 764:
*[https://github.com/omarrx024/xos/blob/master/kernel/blkdev/ahci.asm xOS AHCI implementation (assembly language)]
*[https://github.com/rajesh5310/SBUnix/blob/master/sys/ahci.c SBUnix implementation]
[[Category:ATA]]
[[Category:Storage]]
|