sometime back in 2009... Introduction: AHCI is the next step in IDE controllers. It takes the idea that the CPU no longer has to issue read/write commands out to the drive individually and do a lot of hand-holding while waiting for the drive to return an OK status. The controller can now be given a "todo" list for the drive, and when told to do start, it just starts processing the list of tasks and merrily issues commands out to the drive, and then pushes data to and from various buffers in main memory, all while the CPU is off doing more important thing like updating your high score in solitaire. ----------------------------------------------------------------------------- Tables anyone? AHCI is all about tables and lists. We've got pointers to blocks which point to lists of pointers to tables of pointers to buffers which all have to be set up exactly right before a single byte of data can be transferred. This stuff will take awhile to sink in, so don't worry too much if you don't comprehend the overview right away, and hopefully my code will help you figure it out as well. First up, PCI. The AHCI controller is a PCI (or PCIe) device. It's normally in the south- bridge, such as the Intel ICH9, but can also be a stand-alone device on the PCI bus, or even a plug-in card I suppose. Before we can talk to AHCI, we need to see if there is an AHCI controller in the system. The method I've been using is to look at the class and subclass codes of each PCI device. PCI registers 9,a,b should be 01, 06, 01 respectively. You will need to scan the entire PCI system, not just BUS 0. If you are unfamiliar with reading/writing PCI registers, you should read this tutorial. Now that we've found the device in PCI-space, we need to check the base address register 24h. (BAR 24h for short) BAR 24h points to the AHCI workspace memory. The address of this workspace should be programmed by the BIOS during POST, and can point to anywhere in memory. Usually it's pointing to the area between 3 and 4gig, above your system memory, assuming you have less than 4 gig of memory in your machine. You'll need to read this BAR out of PCI space and save it in your program. If the value returned is 0, then it hasn't been programmed, and we should bail out. (or you can attempt to program it yourself) Summary: PCI BAR register at 24h --> AHCI memory. programmed by BIOS. --------------------------------------------------------------------------- So far so good. To read out the data in the AHCI memory space, we need to get into protected mode. I like Unreal mode myself, as I'm not actually very good at Pmode programming, so we stay in real mode, but give ourselves access to memory above 1mb by switching to unreal and enabling the A20 line. The first 100h bytes of AHCI memory are the global configuration registers. You can turn on/off AHCI here, check to see how many ports (drives) the system supports, get the AHCI revision#, etc. Each port (drive) gets its own set of control registers in this area too. (80h bytes worth to be exact) For whatever reasons, I've been calling this area a drive's workspace. Since up to 32 ports are supported by AHCI, there's 32 copies of the drive configuration registers. Port (drive) 0's config registers start at 100h and go up to 17F. Drive 1's config registers start at 180h and go up to 1ff, etc. The drive config registers (technically called Port Configuration registers) contain status on if a drive is attached, is it powered up, its transfer speed capabilities, etc. This area also controls how many commands are to be sent to the drive to chew on and a start/stop bit for the drive command processor. This area also has 2 pointers. The first one points to a chunk of memory that contains the "todo" list for the drive to work on. It's officially called the Command List, and is a maximum of 1k in size. The second pointer is to a scratchpad buffer of memory for status from the drive. The status changes based off of the command that was issued to the drive, so this data area changes depending on what you're asking the drive to do. Technically this is called the Receive FIS buffer. FIS stands for "Frame Information Structure" whoop-de-do. This area is optional and you can turn off the feature in one of the drive control bits in the workspace. I don't know if there is a perfomance improvement by turning this off. The recieve FIS can sometimes be useful for determining what might have gone wrong during a transfer. This recieve FIS data buffer needs to be 256 bytes. You will need to program these registers with pointers to the FIS recieve buffer (if you use them) and the command list. That's all you need to do here, for now, but we'll come back to this memory area when we want to have the drive actually do something. Summary: AHCI memory ----------- Bytes 0-FF are gobal configuration/status Bytes 100-17f are for port (drive) 0's workspace Bytes 180-200 are for port (drive) 1's workspace etc. Up to port 31 Each Drive workspace: --------------- Offset 0-3 --> 32 bit pointer to command list memory (1k, 256 byte aligned) Offset 7-4 64bit pointer (not used) Offset b-8 --> 32 bit pointer to FIS receive structure (256Bytes, 256 byte aligned) Offset f-c 64bit pointer (not used) + other drive registers, like the GO bit ----------------------------------------------------------------------------- The command list. This is the "todo" list for the drive to operate on. It's only a list, and contains no real details as to what the commands should do. This list is more for the IDE controller itself to know how to send the individual commands out to the drive, than for the drive itself. The command list contains a few flags and values for each command you want to send out, as well as (natrually) another pointer to the actual command. Each entry in the list is 4 dwords (32 bytes) in length, and there is a max of 32 entries in the list. *?* There may be restrictions here on the drive's command queue depth as to how many commands can be put into the list. Each entry in the list contains flags for read/write (from the host's persepective), if the type of command (is it prefetchable, or an ATAPI type of command?), and of course, another pointer! Offset 2 and 3 contain a 16bit value of the number of entries in the PRD table. We'll get into PRD tables in a bit, if your head doesn't explode. Offset 8 contains a 32bit pointer which now points to the specific command you want the drive to do. remember, this is just a list of commands, and each entry in the list then points to the details of each command. This pointer points to what is defined as the "Command Table". Offset C is the upper 32bit address for this pointer, which we won't use. The rest of the registers here are reserved, yay! In each entry of the command list, you will need to program the pointer to each command table, as well as how many entries you've got in the PRD table, as well as the direction flags and a few other bits. You'll spend a lot of time here. Summary: Command List ------------ Up to 32 entries, each 20h bytes Offset 0-1 Flags for types of xfers, read/write bit Offset 2,3 PRD table length in entries. offset 8-b --> command table memory (up to 1MB in size, 128byte aligned) offset 1f-c = 0 ---------------------------------------------------------------------------- FIS recieve buffer: Remember, this is the optional buffer of memory that you can use to see what details are coming back from the drive. I won't spend any time here. This buffer needs to be 256bytes in length, and needs to be aligned on a 256byte boundry (bits 7:0 of the address must be 0) ---------------------------------------------------------------------------- The command table: Here's where we start to get technical, probably more technical than you really want. Each table (remember, there is a table for every single entry in the "todo" list) has a rigid structure which is defined in the AHCI spec. It looks like this: Command Table ------------- offset 00-3f Command FIS send structures. Depends on the xfer type. offset 40-4f ATAPI command FIS structure offset 50-7f reserved offset 80-x PRD Table (up to 65535 entries) Each table can be upwards of meg each, depending on how many entries you have in the PRD table, which we STILL haven't gotten to yet! Most commonly though the PRD table will have at most a couple entries, making the command table a pretty small amount of memory, under 1/2 a kilobyte each. Some detail: 00-4F contain the FIS (Frame Info Structure) for the command you want the drive to perform. You likely have no idea what this data is, because in the past, the hardware took care of it for you. This is the "packet" of data that gets sent down the wire to the drive itself. The packet contains the actual drive commnd opcode (see the ATA spec for those details) and such information as the starting LBA, the number of LBAs to read/write, and a few flags thrown in for good measure. The actual details of the send FIS are defined in the SATA spec, starting somewhere around 318. The code has details on this structure as well. Depending on the type of drive you're talking to (ATAPI or SATA hard drive) depends on where you stuff the FIS data into this memory buffer. Offset 80h contains....at last, wait for it....the final table, the PRD!!! --------------------------------------------------------------------------- The PRD Table. Funny how they call the PRD Table a table instead of a list, which is really what it is. That's one of the reasons I call the Command Table a Command Block, because it's a block of memory with some stuff in it. Not a table, which I think of as a list. Confused? I'm just getting started! Ok, the PRD table (list) contains yet another pointer, but this one is the end of the line. Each entry in the PRD points to the phsyical block of memory where the drive data will be stored to, or read from. Offset 0-7 (2 dwords) are a 64bit pointer to the memory block buffer. we're of course not using 64bit pointers here, so bytes 4-7 are 0. Offsets 8-B are reserved, so jam them to 0. Offsets c-f contain the length of the buffer you're pointing to in bytes 0-3. The goofy part of this register is that it's transfer length-1. That rather makes sense, since if you want to transfer 64k of data, you'd put in FFFF which is 1 byte shy of 64k. The way it is worded in the specs makes it seem like something much more complex than it is. The maximum transfer size here is 4MB. The uppermost bit of this register is for interrupts. If you want the machine to fire an interrupt when the transfer is finished, set this bit. there are plenty of other bits in the hardware you'll also have to set if you want IRQ's so good luck with that. We won't be using IRQs. Another funny thing about the PRD table is that it can contain up to 65535 entries. Originally I believe this was to allow a number of smaller buffers to be used to hold/receive data for/from the drive rather than having software try and keep huge buffers of memory for large transfers. I would suspect that you could actually waste more memory by trying to point to a ton of small buffers in the PRD table instead of using a smaller table with bigger buffers. I do not know what happens if you transfer more data from the drive than you have allocated buffer sizes for in the PRD table. ok, that's it. If you haven't melted down by now, you're doing better than me. Summary PRD table --------- each entry is 4 DWORDs. offset 0-3 --> phsyical block of memory for data to/from drive offset 4-7 64 bit pointer, not used here. offset 8-B reservd offset c-f BIT31=IRQ enable BIT21:0 = length of bytes of xfer, 0 based. Note BIT0 always set to force at least 2 bytes of data to xfer.