PCI IDE Controller: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (Bot: fixing lint errors, replacing obsolete HTML tags: <font> (2x))
 
(48 intermediate revisions by 21 users not shown)
Line 1: Line 1:
{{Disputed|Talk:IDE}}
IDE is a keyword points to the semi-conductors on the mother-board that controls ATA Drives(like ATA Hard-Disks). ATA (AT-Attachment) is the interface of this drives. IDE also can be an IDE card connected to PCI.
IDE is a keyword which refers to the electrical specification of the cables which connect ATA drives (like hard drives) to another device. The drives use the ATA (Advanced Technology Attachment) interface. An IDE cable also can terminate at an IDE card connected to PCI.


ATAPI is an extension to ATA. ATAPI (ATA Packet Interface) adds the support of Drives wich uses SCSI Command-Set (like ODDs (Optical Disk Drives .e.g CD-ROMs, DVD-ROMs), Tape Drives, and ZIP Drives).
[[ATAPI]] is an extension to ATA (recently renamed to PATA) which adds support for the SCSI command set.
== Parallel/Serial ATA/ATAPI ==
IDE can connect up to 4 drives. Each drive can be one of the following:
* ATA (Serial): Used for most modern hard drives.
* ATA (Parallel): Commonly used for hard drives.
* ATAPI (Serial): Used for most modern optical drives.
* ATAPI (Parallel): Commonly used for optical drives.
Accessing an ATA/PATA drive works the same way as accessing a SATA drive. This also implicitly states that accessing a PATAPI ODD is the same as accessing a SATAPI ODD. An IDE driver does not need to know whether a drive is parallel or serial, it only has to know whether it's using ATA or ATAPI.
== IDE Interface ==
If you open your case up and take a look at your motherboard, you will most likely see one or two (or possibly more) of the slots.


The white and green ports are IDE ports, also known as ''channels''. In this example there are both primary and secondary IDE channels which only PATA can be connected to; this means that it only supports PATA/PATAPI drives.
===Parallel/Serial ATA/ATAPI===
IDE can allow even 4 drives to be connected to. Each drive may be:


Each port can have a PATA cable connected to it. One master drive, or two drives (master and slave), can be connected to one PATA cable. So that leaves us with the following possibilities:
* 1. Parallel AT-Attachment [PATA]: Like PATA HDDs.
* 2. Parallel AT-Attachment Packet-Interface [PATAPI]: Like PATAPI ODDs.
* 3. Serial ATA [SATA]: Like SATA HDDs.
* 4. Serial ATAPI [SATAPI]: Like SATAPI ODDs.

We can ignore Tape Drives and ZIP Drives as they are obsolete.
The Way of accessing ATA Drives is one, means that the way of accessing PATA HDDs is the same of SATA HDDs. also the way of accessing PATAPI ODDs is the same of SATAPI ODDs.
For that, for IDE Device Driver, it is not required to know if a drive is Parallel or Serial, but it is important to know if it is ATA or ATAPI.

===IDE Interface===
[[Image:Ide-motherboard-connectors.jpg|thumb|IDE on motherboard|256px|The white and green ports are the Parallel IDE ports on the motherboard.]]
[[Image:PATA-Cable.jpg|thumb|PATA Cable|256px|PATA Cable which is connected to Parallel IDE Ports on motherboard.]]
[[Image:SATA-motherboard.jpg|thumb|SATA Ports|256px|4 Serial IDE Ports on the motherboard.]]
[[Image:SATA-Cable.gif|thumb|SATA Cable|256px|SATA Cable connected to SATA Ports on the motherboard, or on a Serial IDE Card Connected to PCI.]]

If you open your case and look at the mother board, we will see a port or two like these in the picture to the right.

The white and green ports are IDE Ports, each port of them is called channel. so there is:
* Primary IDE Channel.
* Secondary IDE Channel.
These Ports allows only Parallel Drives to be connected to, means that it supports only PATA/PATAPI Drives.




Each Port can has a PATA cable connected to, it is like this in the photo to the right. One master drive, or two drives [Master and Slave] can be connected to one PATA Cable.
So we can have:
* Primary Master Drive.
* Primary Master Drive.
* Primary Slave Drive.
* Primary Slave Drive.
* Secondary Master Drive.
* Secondary Master Drive.
* Secondary Slave Drive.
* Secondary Slave Drive.
Each Drive May be: PATA or PATAPI.
Each drive can be either PATA or PATAPI.
== Serial IDE ==
Almost every modern (this article is probably written in early 2010 so it assumes motherboards still have ide/ahci modes) motherboard has a Serial IDE channel which allows [[SATA]] and SATAPI Drives to be connected to it. There are 4 Serial IDE Ports. Each port is connected to a drive with a SATA Cable. Basically you can only have one drive connected to the Serial IDE port. Each pair of ports (every 2 ports) form one channel.


Serial IDE also has a few possibilities:
* Primary Master, also called SATA1.
* Primary Slave, also called SATA2.
* Secondary Master, also called SATA3.
* Secondary Slave, also called SATA4.
== Detecting a PCI IDE Controller ==
Each IDE controller appears as a device on the [[PCI]] bus and can be identified by reading the configuration space. If the class code is 0x01 (Mass Storage Controller) and the subclass code is 0x01 (IDE), the device is an IDE controller. The programming interface byte(Prog If) determines how you'll access it.
* Bit 0: When set, the primary channel is in PCI native mode. When clear, the primary channel is in compatibility mode (ports 0x1F0-0x1F7, 0x3F6, IRQ14).
* Bit 1: When set, you can modify bit 0 to switch between PCI native and compatibility mode. When clear, you cannot modify bit 0.
* Bit 2: When set, the secondary channel is in PCI native mode. When clear, the secondary channel is in compatibility mode (ports 0x170-0x177, 0x376, IRQ15).
* Bit 3: When set, you can modify bit 2 to switch between PCI native and compatibility mode. When clear, you cannot modify bit 2.
* Bit 7: When set, this is a bus master IDE controller. When clear, this controller doesn't support DMA.
If you want to access an IDE channel in PCI native mode or use the bus master function, you must additionally read the BARs to find which I/O ports to use.
* BAR0: Base address of primary channel in PCI native mode (8 ports)
* BAR1: Base address of primary channel control port in PCI native mode (4 ports)
* BAR2: Base address of secondary channel in PCI native mode (8 ports)
* BAR3: Base address of secondary channel control port in PCI native mode (4 ports)
* BAR4: Bus master IDE (16 ports, 8 for each channel)
Note that BAR1 and BAR3 specify 4 ports, but only the port at offset 2 is used. Offsets 0, 1, and 3 should not be accessed.


If either IDE channel is in PCI native mode, you must also read the interrupt line or interrupt pin register to determine which interrupt to use. If both channels are in PCI native mode, they'll both share the same interrupt. The interrupt line field is only valid when using the [[8259 PIC|PIC]].

== Detecting IDE Drives ==

To initialise the IDE driver, we call ide_initialise:

<syntaxhighlight lang="c">

But What about Serial IDE?
Almost many of modern motherboards have a Serial IDE which allows SATA and SATAPI Drives to be connected to.
Serial IDE Ports are 4, like these appear in the photo to the right, Each Port is conducted with a Serial ATA (SATA) Cable.
So from the pictures we can understand that only one drive can be connected to Serial IDE Port, each two ports make a channel, and also Serial IDE has:

* Primary Master Drive [Port1, or Port 2], also called [SATA1] in BIOS Setup Utility.
* Primary Slave Drive [Port 1 or Port 2], also called [SATA2] in BIOS Setup Utility.
* Secondary Master Drive [Port 3 or Port 4], also called [SATA3] in BIOS Setup Utility.
* Secondary Slave Drive [Port 3 or Port 4], also called [SATA4] in BIOS Setup Utility.

===Detecting an IDE===
Please if you wanna support only the Parallel IDE, skip the part of [Detecting an IDE].
Each IDE appears as a device [in PCI World, it is called a function] on PCI Bus. If you don't know about PCI, please refer to [http://wiki.osdev.org/PCI PCI].
When you find a device on PCI, you should determine whether it is an IDE Device or not, this is determined according to Class Code and Subclass code.
If Class code is: 0x01 [Mass Storage Controller] and Subclass Code is: 0x01 [IDE], so this device is an IDE Device.
We know all that each PCI Device has 6 BARs, ok, only 5 BARs are used by IDE Device:
* BAR0: Base Address of Primary Channel I/O Ports, if it is 0x0 or 0x1, this means [0x1F0].
* BAR1: Base Address of Priamry Channel Control Ports, if it is 0x0 or 0x1, this means [0x3F4].
* BAR2: Base Address of Secondary Channel I/O Ports, if it is 0x0 or 0x1, this means [0x170].
* BAR3: Base Address of Secondary Channel Control Ports, if it is 0x0 or 0x1, this means [0x374].
* BAR4: Bus Master IDE, this I/O Address refers to the base of I/O range consists of 16 ports, each 8 ports controls DMA on a channel.

IRQs are really a problem for IDEs, because the IDE uses IRQs 14 and 15, if it is a Parallel IDE.
If it is a Serial IDE, it uses another IRQ and only one IRQ, but how does we know the IRQs used by IDE? In Quafios it is quite easy:

<source lang="c">
outl((1<<31) | (bus<<16) | (device<<11) | (func<<8) | 8, 0xCF8); // Send the parameters.
if ((class = inl(0xCFC)>>16) != 0xFFFF) { // If there is exactly a device
// Check if this device need an IRQ assignment:
outl((1<<31) | (bus<<16) | (device<<11) | (func<<8) | 0x3C, 0xCF8);
outb(0xFE, 0xCFC); // Change the IRQ field to 0xFE
outl((1<<31) | (bus<<16) | (device<<11) | (func<<8) | 0x3C, 0xCF8); // Read the IRQ Field Again.
if ((inl(0xCFC) & 0xFF)==0xFE) {
// This Device needs IRQ assignment.
} else {
// The Device doesn't use IRQs, check if this is an Parallel IDE:
if (class == 0x01 && subclass == 0x01 && (ProgIF == 0x8A || ProgIF == 0x80)) {
// This is a Parallel IDE Controller which use IRQ 14 and IRQ 15.
}
}
}
</source>

By this way, you can make a structure with PCI Devices, each device has IRQ0 and IRQ1, the both should have an initial value of 0xFF [No IRQ]. if we detect a PCI Device, if the Device needs an IRQ, we can change IRQ0. if the device on PCI doesn't need, but it is a Parallel IDE, we can edit IRQ0 to 14 and IRQ1 to 15.

When an IRQ is invoked, ISR should read the IRQ number from PIC, then it searches for the device which has this IRQ [in IRQ0 or IRQ1], and if the device is found, call the device driver to inform it that an IRQ is invoked.

===Detecting IDE Drives===

In Quafios, when an IDE Device is found, Quafios reserves a space in memory and copies Generic IDE Device Driver to this space, And then calls the device driver with a function number of 1. function number 1 is initialization, which is:

<source lang="c">
void ide_initialize(unsigned int BAR0, unsigned int BAR1, unsigned int BAR2, unsigned int BAR3,
void ide_initialize(unsigned int BAR0, unsigned int BAR1, unsigned int BAR2, unsigned int BAR3,
unsigned int BAR4) {
unsigned int BAR4) {
</syntaxhighlight>
</source>
If you only want to support the parallel IDE, you can use these parameters:

<syntaxhighlight lang="c">
If you wanna support only the parallel IDE, you can put this command in kernel:
ide_initialize(0x1F0, 0x3F6, 0x170, 0x376, 0x000);

</syntaxhighlight>
<source lang="c">
ide_initialize(0x1F0, 0x3F4, 0x170, 0x374, 0x000);
</source>

We can assume that BAR4 is 0x0 because we are not going to use it yet.
We can assume that BAR4 is 0x0 because we are not going to use it yet.
We will return to ide_initialize function which searches for drives connected to the IDE, before we are going into this function, we should write some functions which will help us a lot.
We will return to ide_initialize, which searches for drives connected to the IDE. Before we go into this function, we should write some support functions and definitions which will help us a lot.
=== Status ===
First We should write some Definitions:
The Command/Status Port returns a bit mask referring to the status of a channel when read.

<source lang="c">
<syntaxhighlight lang="c">
#define ATA_SR_BSY 0x80
#define ATA_SR_BSY 0x80 // Busy
#define ATA_SR_DRDY 0x40
#define ATA_SR_DRDY 0x40 // Drive ready
#define ATA_SR_DF 0x20
#define ATA_SR_DF 0x20 // Drive write fault
#define ATA_SR_DSC 0x10
#define ATA_SR_DSC 0x10 // Drive seek complete
#define ATA_SR_DRQ 0x08
#define ATA_SR_DRQ 0x08 // Data request ready
#define ATA_SR_CORR 0x04
#define ATA_SR_CORR 0x04 // Corrected data
#define ATA_SR_IDX 0x02
#define ATA_SR_IDX 0x02 // Index
#define ATA_SR_ERR 0x01
#define ATA_SR_ERR 0x01 // Error
</syntaxhighlight>
</source>
=== Errors ===

The Features/Error Port, which returns the most recent error upon read, has these possible bit masks
There is a port is called Command/Status Port, when it is read, you read the status of channel, the above bit masks express these states.
<syntaxhighlight lang="c">

#define ATA_ER_BBK 0x80 // Bad block
<source lang="c">
#define ATA_ER_BBK 0x80
#define ATA_ER_UNC 0x40 // Uncorrectable data
#define ATA_ER_UNC 0x40
#define ATA_ER_MC 0x20 // Media changed
#define ATA_ER_MC 0x20
#define ATA_ER_IDNF 0x10 // ID mark not found
#define ATA_ER_IDNF 0x10
#define ATA_ER_MCR 0x08 // Media change request
#define ATA_ER_MCR 0x08
#define ATA_ER_ABRT 0x04 // Command aborted
#define ATA_ER_ABRT 0x04
#define ATA_ER_TK0NF 0x02 // Track 0 not found
#define ATA_ER_TK0NF 0x02
#define ATA_ER_AMNF 0x01 // No address mark
</syntaxhighlight>
#define ATA_ER_AMNF 0x01
=== Commands ===
</source>
When you write to the Command/Status port, you are executing one of the commands below.

<syntaxhighlight lang="c">
There is a port is called Features/Error Port, if it is read, you are reading the errors of the last operation, the bit masks above express these errors.
#define ATA_CMD_READ_PIO 0x20

#define ATA_CMD_READ_PIO_EXT 0x24
<source lang="c">
#define ATA_CMD_READ_DMA 0xC8
// ATA-Commands:
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_READ_DMA_EXT 0x25
#define ATA_CMD_READ_PIO_EXT 0x24
#define ATA_CMD_WRITE_PIO 0x30
#define ATA_CMD_READ_DMA 0xC8
#define ATA_CMD_WRITE_PIO_EXT 0x34
#define ATA_CMD_READ_DMA_EXT 0x25
#define ATA_CMD_WRITE_DMA 0xCA
#define ATA_CMD_WRITE_PIO 0x30
#define ATA_CMD_WRITE_DMA_EXT 0x35
#define ATA_CMD_WRITE_PIO_EXT 0x34
#define ATA_CMD_CACHE_FLUSH 0xE7
#define ATA_CMD_CACHE_FLUSH_EXT 0xEA
#define ATA_CMD_WRITE_DMA 0xCA
#define ATA_CMD_WRITE_DMA_EXT 0x35
#define ATA_CMD_PACKET 0xA0
#define ATA_CMD_IDENTIFY_PACKET 0xA1
#define ATA_CMD_CACHE_FLUSH 0xE7
#define ATA_CMD_CACHE_FLUSH_EXT 0xEA
#define ATA_CMD_IDENTIFY 0xEC
</syntaxhighlight>
#define ATA_CMD_PACKET 0xA0
The commands below are for ATAPI devices, which will be understood soon.
#define ATA_CMD_IDENTIFY_PACKET 0xA1
<syntaxhighlight lang="c">
#define ATA_CMD_IDENTIFY 0xEC
</source>

When you write to Command/Status Port, You are executing a command, which can be one of the commands above.

<source lang="c">
#define ATAPI_CMD_READ 0xA8
#define ATAPI_CMD_READ 0xA8
#define ATAPI_CMD_EJECT 0x1B
#define ATAPI_CMD_EJECT 0x1B
</syntaxhighlight>
</source>
ATA_CMD_IDENTIFY_PACKET and ATA_CMD_IDENTIFY return a buffer of 512 bytes called the identification space; the following definitions are used to read information from the identification space.

<syntaxhighlight lang="c">
The Command above are for ATAPI Devices which will be understood soon.
#define ATA_IDENT_DEVICETYPE 0

#define ATA_IDENT_CYLINDERS 2
The Commands ATA_CMD_IDENTIFY_PACKET, and ATA_CMD_IDENTIFY, returns a buffer of 512 byte, the buffer is called Identification space, the following definitions are used to read information from the identification space.
#define ATA_IDENT_HEADS 6

#define ATA_IDENT_SECTORS 12
<source lang="c">
#define ATA_IDENT_DEVICETYPE 0
#define ATA_IDENT_SERIAL 20
#define ATA_IDENT_CYLINDERS 2
#define ATA_IDENT_MODEL 54
#define ATA_IDENT_CAPABILITIES 98
#define ATA_IDENT_HEADS 6
#define ATA_IDENT_FIELDVALID 106
#define ATA_IDENT_SECTORS 12
#define ATA_IDENT_SERIAL 20
#define ATA_IDENT_MAX_LBA 120
#define ATA_IDENT_COMMANDSETS 164
#define ATA_IDENT_MODEL 54
#define ATA_IDENT_MAX_LBA_EXT 200
#define ATA_IDENT_CAPABILITIES 98
</syntaxhighlight>
#define ATA_IDENT_FIELDVALID 106
When you select a drive, you should specify the interface type and whether it is the master or slave:
#define ATA_IDENT_MAX_LBA 120
<syntaxhighlight lang="c">
#define ATA_IDENT_COMMANDSETS 164
#define ATA_IDENT_MAX_LBA_EXT 200
#define IDE_ATA 0x00
#define IDE_ATAPI 0x01
</source>

When you select a drive, you should specify if it is the master drive or the slave one:

<source lang="c">
#define ATA_MASTER 0x00
#define ATA_SLAVE 0x01
</source>

<source lang="c">
#define IDE_ATA 0x00
#define IDE_ATAPI 0x01
</source>

<source lang="c">
// ATA-ATAPI Task-File:
#define ATA_REG_DATA 0x00
#define ATA_REG_ERROR 0x01
#define ATA_REG_FEATURES 0x01
#define ATA_REG_SECCOUNT0 0x02
#define ATA_REG_LBA0 0x03
#define ATA_REG_LBA1 0x04
#define ATA_REG_LBA2 0x05
#define ATA_REG_HDDEVSEL 0x06
#define ATA_REG_COMMAND 0x07
#define ATA_REG_STATUS 0x07
#define ATA_REG_SECCOUNT1 0x08
#define ATA_REG_LBA3 0x09
#define ATA_REG_LBA4 0x0A
#define ATA_REG_LBA5 0x0B
#define ATA_REG_CONTROL 0x0C
#define ATA_REG_ALTSTATUS 0x0C
#define ATA_REG_DEVADDRESS 0x0D
</source>


Task File is a range of ports [8 ports] which are used by primary channel [BAR0] or Secondary Channel [BAR2].


#define ATA_MASTER 0x00
#define ATA_SLAVE 0x01
</syntaxhighlight>
Task File is a range of 8 ports which are offsets from BAR0 (primary channel) and/or BAR2 (secondary channel). To exemplify:
* BAR0 + 0 is first port.
* BAR0 + 0 is first port.
* BAR0 + 1 is second port.
* BAR0 + 1 is second port.
* BAR0 + 3 is the third ... etc ...
* BAR0 + 2 is the third
<syntaxhighlight lang="c">

#define ATA_REG_DATA 0x00
if BAR0 is 0x1F0:
#define ATA_REG_ERROR 0x01
* The Data Port of the Primary Channel is 0x1F0.
#define ATA_REG_FEATURES 0x01
* The Features/Error Port of the Primary Channel is 0x1F1.
#define ATA_REG_SECCOUNT0 0x02
* etc ...
#define ATA_REG_LBA0 0x03
The same with the secondary channel.
#define ATA_REG_LBA1 0x04

#define ATA_REG_LBA2 0x05
There is a port which is called "ALTSTATUS/CONTROL PORT", when is read, you read alternate status, when this port is written to, you are controlling a channel.
#define ATA_REG_HDDEVSEL 0x06

#define ATA_REG_COMMAND 0x07
* For the Primary Channel, ALTSTATUS/CONTROL Port is BAR1 + 2.
#define ATA_REG_STATUS 0x07
* For the Secondary Channel, ALTSTATUS/CONTROL Port is BAR3 + 2.
#define ATA_REG_SECCOUNT1 0x08

#define ATA_REG_LBA3 0x09
We can know say that Each Channel has 13 Register, for a primary channel:
#define ATA_REG_LBA4 0x0A

#define ATA_REG_LBA5 0x0B
* Data Register: BAR0[0]; // Read and Write
#define ATA_REG_CONTROL 0x0C
* Error Register: BAR0[1]; // Read Only
#define ATA_REG_ALTSTATUS 0x0C
* Features Register: BAR0[1]; // Write Only
#define ATA_REG_DEVADDRESS 0x0D
* SECCOUNT0: BAR0[2]; // Read and Write
</syntaxhighlight>
* LBA0: BAR0[3]; // Read and Write
The ALTSTATUS/CONTROL port returns the alternate status when read and controls a channel when written to.
* LBA1: BAR0[4]; // Read and Write
* For the primary channel, ALTSTATUS/CONTROL port is BAR1 + 2.
* LBA2: BAR0[5]; // Read and Write
* For the secondary channel, ALTSTATUS/CONTROL port is BAR3 + 2.
* HDDEVSEL: BAR0[6]; // Read and Write, this port is used to select a drive in the channel.
We can now say that each channel has 13 registers. For the primary channel, we use these values:
* Command Register: BAR0[7]; // Write Only.
* Status Register: BAR0[7]; // Read Only.
* Data Register: BAR0 + 0; // Read-Write
* Alternate Status Register: BAR1[2]; // Read Only.
* Error Register: BAR0 + 1; // Read Only
* Control Register: BAR1[2]; // Write Only.
* Features Register: BAR0 + 1; // Write Only
* SECCOUNT0: BAR0 + 2; // Read-Write
* DEVADDRESS: BAR1[2]; // I don't know what is the benefit from this register.
* LBA0: BAR0 + 3; // Read-Write

* LBA1: BAR0 + 4; // Read-Write
The map above is the same with the secondary channel, but it is using BAR2 and BAR3 instead of BAR0 and BAR1.
* LBA2: BAR0 + 5; // Read-Write

* HDDEVSEL: BAR0 + 6; // Read-Write, used to select a drive in the channel.
<source lang="c">
* Command Register: BAR0 + 7; // Write Only.
* Status Register: BAR0 + 7; // Read Only.
* Alternate Status Register: BAR1 + 2; // Read Only.
* Control Register: BAR1 + 2; // Write Only.
* DEVADDRESS: BAR1 + 3; // I don't know what is the benefit from this register.
The map above is the same with the secondary channel, but it uses BAR2 and BAR3 instead of BAR0 and BAR1.
<syntaxhighlight lang="c">
// Channels:
// Channels:
#define ATA_PRIMARY 0x00
#define ATA_PRIMARY 0x00
#define ATA_SECONDARY 0x01
#define ATA_SECONDARY 0x01
</source>


<source lang="c">
// Directions:
// Directions:
#define ATA_READ 0x00
#define ATA_READ 0x00
#define ATA_WRITE 0x01
#define ATA_WRITE 0x01
</syntaxhighlight>
</source>
We have defined everything needed by the driver, now lets move to an important part. We said that

* BAR0 is the start of the I/O ports used by the primary channel.
We have had defined all definitions needed by the driver, now lets move to an important part, we said that
* BAR0 is the Base of I/O Ports used by Primary Channel.
* BAR1 is the start of the I/O ports which control the primary channel.
* BAR1 is the Base of I/O Ports which control Primary Channel.
* BAR2 is the start of the I/O ports used by secondary channel.
* BAR2 is the Base of I/O Ports used by Secondary Channel.
* BAR3 is the start of the I/O ports which control secondary channel.
* BAR3 is the Base of I/O Ports which control Secondary Channel.
* BAR4 is the start of 8 I/O ports controls the primary channel's Bus Master IDE.
* BAR4 is the Base of 8 I/O Ports controls Primary Channel's Bus Master IDE [BMIDE].
* BAR4 + 8 is the Base of 8 I/O ports controls secondary channel's Bus Master IDE.
* BAR4 + 8 is the Base of 8 I/O Ports controls Secondary Channel's Bus Master IDE [BMIDE].

So we can make this global structure:
So we can make this global structure:
<syntaxhighlight lang="c">

struct IDEChannelRegisters {
<source lang="c">
struct channel {
unsigned short base; // I/O Base.
unsigned short base; // I/O Base.
unsigned short ctrl; // Control Base
unsigned short ctrl; // Control Base
Line 276: Line 190:
unsigned char nIEN; // nIEN (No Interrupt);
unsigned char nIEN; // nIEN (No Interrupt);
} channels[2];
} channels[2];
</syntaxhighlight>
</source>
We also need a buffer to read the identification space into, we need a variable that indicates if an irq is invoked or not, and finally we need an array of 6 words [12 bytes] for ATAPI Drives:

<syntaxhighlight lang="c">
We also need a buffer to read the identification space in it, we need a variable that indicates if an irq is invoked or not, and finally we need an array of 6 words [12 bytes] for ATAPI Drives:

<source lang="c">
unsigned char ide_buf[2048] = {0};
unsigned char ide_buf[2048] = {0};
unsigned static char ide_irq_invoked = 0;
volatile unsigned static char ide_irq_invoked = 0;
unsigned static char atapi_packet[12] = {0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned static char atapi_packet[12] = {0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
</syntaxhighlight>
</source>

We said the the IDE can contain up to 4 drives:
We said the the IDE can contain up to 4 drives:
<syntaxhighlight lang="c">

<source lang="c">
struct ide_device {
struct ide_device {
unsigned char reserved; // 0 (Empty) or 1 (This Drive really exists).
unsigned char Reserved; // 0 (Empty) or 1 (This Drive really exists).
unsigned char channel; // 0 (Primary Channel) or 1 (Secondary Channel).
unsigned char Channel; // 0 (Primary Channel) or 1 (Secondary Channel).
unsigned char drive; // 0 (Master Drive) or 1 (Slave Drive).
unsigned char Drive; // 0 (Master Drive) or 1 (Slave Drive).
unsigned short type; // 0: ATA, 1:ATAPI.
unsigned short Type; // 0: ATA, 1:ATAPI.
unsigned short sign; // Drive Signature
unsigned short Signature; // Drive Signature
unsigned short capabilities;// Features.
unsigned short Capabilities;// Features.
unsigned int commandsets; // Command Sets Supported.
unsigned int CommandSets; // Command Sets Supported.
unsigned int size; // Size in Sectors.
unsigned int Size; // Size in Sectors.
unsigned char model[41]; // Model in string.
unsigned char Model[41]; // Model in string.
} ide_devices[4];
} ide_devices[4];
</syntaxhighlight>
</source>

When we read a register in a channel, like STATUS Register, it is easy to execute:
When we read a register in a channel, like STATUS Register, it is easy to execute:
<syntaxhighlight lang="c">

ide_read(channel, ATA_REG_STATUS);
ide_read(channel, ATA_REG_STATUS);


<source lang="c">
unsigned char ide_read(unsigned char channel, unsigned char reg) {
unsigned char ide_read(unsigned char channel, unsigned char reg) {
unsigned char result;
unsigned char result;
if (reg > 0x07 && reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
if (reg > 0x07 && reg < 0x0C)
if (reg < 0x08) result = inb(channels[channel].base + reg - 0x00);
ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
if (reg < 0x08)
else if (reg < 0x0C) result = inb(channels[channel].base + reg - 0x06);
else if (reg < 0x0E) result = inb(channels[channel].ctrl + reg - 0x0A);
result = inb(channels[channel].base + reg - 0x00);
else if (reg < 0x16) result = inb(channels[channel].bmide + reg - 0x0E);
else if (reg < 0x0C)
if (reg > 0x07 && reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
result = inb(channels[channel].base + reg - 0x06);
else if (reg < 0x0E)
result = inb(channels[channel].ctrl + reg - 0x0A);
else if (reg < 0x16)
result = inb(channels[channel].bmide + reg - 0x0E);
if (reg > 0x07 && reg < 0x0C)
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
return result;
return result;
}
}
</syntaxhighlight>
</source>
We also need a function for writing to registers:

<syntaxhighlight lang="c">
And Also there is a function for writing to registers:

<source lang="c">
void ide_write(unsigned char channel, unsigned char reg, unsigned char data) {
void ide_write(unsigned char channel, unsigned char reg, unsigned char data) {
if (reg > 0x07 && reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
if (reg > 0x07 && reg < 0x0C)
if (reg < 0x08) outb(data, channels[channel].base + reg - 0x00);
ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
if (reg < 0x08)
else if (reg < 0x0C) outb(data, channels[channel].base + reg - 0x06);
else if (reg < 0x0E) outb(data, channels[channel].ctrl + reg - 0x0A);
outb(channels[channel].base + reg - 0x00, data);
else if (reg < 0x16) outb(data, channels[channel].bmide + reg - 0x0E);
else if (reg < 0x0C)
outb(channels[channel].base + reg - 0x06, data);
if (reg > 0x07 && reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
else if (reg < 0x0E)
outb(channels[channel].ctrl + reg - 0x0A, data);
else if (reg < 0x16)
outb(channels[channel].bmide + reg - 0x0E, data);
if (reg > 0x07 && reg < 0x0C)
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
}
}
</syntaxhighlight>
</source>
To read the identification space, we should read the Data Register as a double word 128 times. We can then copy them to our buffer.

<syntaxhighlight lang="c">
If We want to read the identification space, we should read Data Register as Double Word for 128 times. the first read is the first dword, the second read is the second dword, and so on. we can read the 128 dwords and copy them to our buffer.

<source lang="c">
void ide_read_buffer(unsigned char channel, unsigned char reg, unsigned int buffer,
void ide_read_buffer(unsigned char channel, unsigned char reg, unsigned int buffer,
unsigned int quads) {
unsigned int quads) {
/* WARNING: This code contains a serious bug. The inline assembly trashes ES and
if (reg > 0x07 && reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
* ESP for all of the code the compiler generates between the inline
* assembly blocks.
*/
if (reg > 0x07 && reg < 0x0C)
ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
asm("pushw %es; movw %ds, %ax; movw %ax, %es");
asm("pushw %es; movw %ds, %ax; movw %ax, %es");
if (reg < 0x08) insl(channels[channel].base + reg - 0x00, buffer, quads);
if (reg < 0x08)
else if (reg < 0x0C) insl(channels[channel].base + reg - 0x06, buffer, quads);
insl(channels[channel].base + reg - 0x00, buffer, quads);
else if (reg < 0x0E) insl(channels[channel].ctrl + reg - 0x0A, buffer, quads);
else if (reg < 0x0C)
else if (reg < 0x16) insl(channels[channel].bmide + reg - 0x0E, buffer, quads);
insl(channels[channel].base + reg - 0x06, buffer, quads);
else if (reg < 0x0E)
insl(channels[channel].ctrl + reg - 0x0A, buffer, quads);
else if (reg < 0x16)
insl(channels[channel].bmide + reg - 0x0E, buffer, quads);
asm("popw %es;");
asm("popw %es;");
if (reg > 0x07 && reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
if (reg > 0x07 && reg < 0x0C)
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
}
}
</syntaxhighlight>
</source>
When we send a command, we should wait for 400 nanosecond, then read the Status port. If the Busy bit is on, we should read the status port again until the Busy bit is 0; then we can read the results of the command. This operation is called "Polling". We can also use IRQs instead of polling.

When we send a command, we should wait for 400 nanosecond, then we should read Status Port, if Busy Bit is on, so we should read status port again, until Busy Bit is 0, in this case, we can read the results of the command. this operation is called "Polling", we can use IRQs instead of polling, and IRQs are suitable for
Multi-Tasking Environments, but i think Polling is much faster than IRQs.

After Many Commands, if DF is set [Device Fault Bit], so there is a failure, and if DRQ is not set, so there is an error. if ERR bit is set, so there is an error which is described in Error Port.


After many commands, if the Device Fault bit is set, there is a failure; if DRQ is not set, there is an error. If the ERR bit is set, there is an error which is described in Error port.
<source lang="c">
<syntaxhighlight lang="c">
unsigned char ide_polling(unsigned char channel, unsigned int advanced_check) {
unsigned char ide_polling(unsigned char channel, unsigned int advanced_check) {


// (I) Delay 400 nanosecond for BSY to be set:
// (I) Delay 400 nanosecond for BSY to be set:
// -------------------------------------------------
// -------------------------------------------------
for(int i = 0; i < 4; i++)
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading the Alternate Status port wastes 100ns; loop four times.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.


// (II) Wait for BSY to be cleared:
// (II) Wait for BSY to be cleared:
// -------------------------------------------------
// -------------------------------------------------
while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY); // Wait for BSY to be zero.
while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY)
; // Wait for BSY to be zero.


if (advanced_check) {
if (advanced_check) {

unsigned char state = ide_read(channel, ATA_REG_STATUS); // Read Status Register.
unsigned char state = ide_read(channel, ATA_REG_STATUS); // Read Status Register.


// (III) Check For Errors:
// (III) Check For Errors:
// -------------------------------------------------
// -------------------------------------------------
if (state & ATA_SR_ERR) return 2; // Error.
if (state & ATA_SR_ERR)
return 2; // Error.


// (IV) Check If Device fault:
// (IV) Check If Device fault:
// -------------------------------------------------
// -------------------------------------------------
if (state & ATA_SR_DF ) return 1; // Device Fault.
if (state & ATA_SR_DF)
return 1; // Device Fault.


// (V) Check DRQ:
// (V) Check DRQ:
// -------------------------------------------------
// -------------------------------------------------
// BSY = 0; DF = 0; ERR = 0 so we should check for DRQ now.
// BSY = 0; DF = 0; ERR = 0 so we should check for DRQ now.
if (!(state & ATA_SR_DRQ)) return 3; // DRQ should be set
if ((state & ATA_SR_DRQ) == 0)
return 3; // DRQ should be set


}
}
Line 389: Line 313:


}
}
</syntaxhighlight>
</source>
If there is an error, we have a function which prints errors on screen:

<syntaxhighlight lang="c">
if there is an error, we have a functions which print errors on screen:

<source lang="c">
unsigned char ide_print_error(unsigned int drive, unsigned char err) {
unsigned char ide_print_error(unsigned int drive, unsigned char err) {
if (err == 0)
if (err == 0) return err;
return err;


printk(" IDE:");
printk("IDE:");
if (err == 1) {printk("- Device Fault\n "); err = 19;}
if (err == 1) {printk("- Device Fault\n "); err = 19;}
else if (err == 2) {
else if (err == 2) {
Line 413: Line 335:
else if (err == 4) {printk("- Write Protected\n "); err = 8;}
else if (err == 4) {printk("- Write Protected\n "); err = 8;}
printk("- [%s %s] %s\n",
printk("- [%s %s] %s\n",
(const char *[]){"Primary","Secondary"}[ide_devices[drive].channel],
(const char *[]){"Primary", "Secondary"}[ide_devices[drive].channel], // Use the channel as an index into the array
(const char *[]){"Master", "Slave"}[ide_devices[drive].drive],
(const char *[]){"Master", "Slave"}[ide_devices[drive].drive], // Same as above, using the drive
ide_devices[drive].model);
ide_devices[drive].model);


return err;
return err;
}
}
</syntaxhighlight>
</source>
Now let's return to the initialization function:

<syntaxhighlight lang="c">
Now lets return to the initialization function:

<source lang="c">
void ide_initialize(unsigned int BAR0, unsigned int BAR1, unsigned int BAR2, unsigned int BAR3,
void ide_initialize(unsigned int BAR0, unsigned int BAR1, unsigned int BAR2, unsigned int BAR3,
unsigned int BAR4) {
unsigned int BAR4) {
Line 430: Line 350:


// 1- Detect I/O Ports which interface IDE Controller:
// 1- Detect I/O Ports which interface IDE Controller:
channels[ATA_PRIMARY ].base = (BAR0 &= 0xFFFFFFFC) + 0x1F0*(!BAR0);
channels[ATA_PRIMARY ].base = (BAR0 & 0xFFFFFFFC) + 0x1F0 * (!BAR0);
channels[ATA_PRIMARY ].ctrl = (BAR1 &= 0xFFFFFFFC) + 0x3F4*(!BAR1);
channels[ATA_PRIMARY ].ctrl = (BAR1 & 0xFFFFFFFC) + 0x3F6 * (!BAR1);
channels[ATA_SECONDARY].base = (BAR2 &= 0xFFFFFFFC) + 0x170*(!BAR2);
channels[ATA_SECONDARY].base = (BAR2 & 0xFFFFFFFC) + 0x170 * (!BAR2);
channels[ATA_SECONDARY].ctrl = (BAR3 &= 0xFFFFFFFC) + 0x374*(!BAR3);
channels[ATA_SECONDARY].ctrl = (BAR3 & 0xFFFFFFFC) + 0x376 * (!BAR3);
channels[ATA_PRIMARY ].bmide = (BAR4 &= 0xFFFFFFFC) + 0; // Bus Master IDE
channels[ATA_PRIMARY ].bmide = (BAR4 & 0xFFFFFFFC) + 0; // Bus Master IDE
channels[ATA_SECONDARY].bmide = (BAR4 &= 0xFFFFFFFC) + 8; // Bus Master IDE
channels[ATA_SECONDARY].bmide = (BAR4 & 0xFFFFFFFC) + 8; // Bus Master IDE
</syntaxhighlight>

Then we should disable IRQs in both channels by setting bit 1 (nIEN) in the Control port:

<syntaxhighlight lang="c">
Then We Should Disable IRQs in the both channels [This is temporary]:
This happens by setting bit 1 [nIEN] in Control Port:

Code:
// 2- Disable IRQs:
// 2- Disable IRQs:
ide_write(ATA_PRIMARY , ATA_REG_CONTROL, 2);
ide_write(ATA_PRIMARY , ATA_REG_CONTROL, 2);
ide_write(ATA_SECONDARY, ATA_REG_CONTROL, 2);
ide_write(ATA_SECONDARY, ATA_REG_CONTROL, 2);
</syntaxhighlight>
</source>
Now we need to check for drives which could be connected to each channel. We will select the master drive of each channel, and send the ATA_IDENTIFY command (which is supported by ATA Drives). If there's no error, there are values returned in registers which determine the type of Drive; if no drive is present, there will be strange values.

Now we need to check for drives connected to each channel, we will select the master drive of each channel, and send the command ATA_IDENTIFY (Which is supported by ATA Drives). if error, there is values returned in registers determines the type of Drive, if no drive, there will be strange values.

Notice that bit4 in HDDEVSEL, if set to 1, we are selecting the slave drive, if set to 0, we are selecting the master drive.


Notice that if bit 4 in HDDEVSEL is set to 1, we are selecting the slave drive, if set to 0, we are selecting the master drive.
<source lang="c">
<syntaxhighlight lang="c">
// 3- Detect ATA-ATAPI Devices:
// 3- Detect ATA-ATAPI Devices:
for (i = 0; i < 2; i++)
for (i = 0; i < 2; i++)
Line 457: Line 372:


unsigned char err = 0, type = IDE_ATA, status;
unsigned char err = 0, type = IDE_ATA, status;
ide_devices[count].reserved = 0; // Assuming that no drive here.
ide_devices[count].Reserved = 0; // Assuming that no drive here.


// (I) Select Drive:
// (I) Select Drive:
ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j<<4)); // Select Drive.
ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j << 4)); // Select Drive.
sleep(1); // Wait 1ms for drive select to work.
sleep(1); // Wait 1ms for drive select to work.


Line 469: Line 384:


// (III) Polling:
// (III) Polling:
if (!(ide_read(i, ATA_REG_STATUS))) continue; // If Status = 0, No Device.
if (ide_read(i, ATA_REG_STATUS) == 0) continue; // If Status = 0, No Device.


while(1) {
while(1) {
status = ide_read(i, ATA_REG_STATUS);
status = ide_read(i, ATA_REG_STATUS);
if ( (status & ATA_SR_ERR)) {err = 1; break;} // If Err, Device is not ATA.
if ((status & ATA_SR_ERR)) {err = 1; break;} // If Err, Device is not ATA.
if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break; // Everything is right.
if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break; // Everything is right.
}
}
Line 479: Line 394:
// (IV) Probe for ATAPI Devices:
// (IV) Probe for ATAPI Devices:


if (err) {
if (err != 0) {
unsigned char cl = ide_read(i,ATA_REG_LBA1);
unsigned char cl = ide_read(i, ATA_REG_LBA1);
unsigned char ch = ide_read(i,ATA_REG_LBA2);
unsigned char ch = ide_read(i, ATA_REG_LBA2);


if (cl == 0x14 && ch ==0xEB) type = IDE_ATAPI;
if (cl == 0x14 && ch == 0xEB)
else if (cl == 0x69 && ch ==0x96) type = IDE_ATAPI;
type = IDE_ATAPI;
else continue; // Unknown Type (And always not be a device).
else if (cl == 0x69 && ch == 0x96)
type = IDE_ATAPI;
else
continue; // Unknown Type (may not be a device).


ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);
ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);
Line 495: Line 413:


// (VI) Read Device Parameters:
// (VI) Read Device Parameters:
ide_devices[count].reserved = 1;
ide_devices[count].Reserved = 1;
ide_devices[count].type = type;
ide_devices[count].Type = type;
ide_devices[count].channel = i;
ide_devices[count].Channel = i;
ide_devices[count].drive = j;
ide_devices[count].Drive = j;
ide_devices[count].sign = ((unsigned short *)(ide_buf+ATA_IDENT_DEVICETYPE ))[0];
ide_devices[count].Signature = *((unsigned short *)(ide_buf + ATA_IDENT_DEVICETYPE));
ide_devices[count].capabilities = ((unsigned short *)(ide_buf+ATA_IDENT_CAPABILITIES))[0];
ide_devices[count].Capabilities = *((unsigned short *)(ide_buf + ATA_IDENT_CAPABILITIES));
ide_devices[count].commandsets = ((unsigned int *)(ide_buf+ATA_IDENT_COMMANDSETS ))[0];
ide_devices[count].CommandSets = *((unsigned int *)(ide_buf + ATA_IDENT_COMMANDSETS));


// (VII) Get Size:
// (VII) Get Size:
if (ide_devices[count].commandsets & (1<<26)){
if (ide_devices[count].CommandSets & (1 << 26))
// Device uses 48-Bit Addressing:
// Device uses 48-Bit Addressing:
ide_devices[count].size = ((unsigned int *) (ide_buf + ATA_IDENT_MAX_LBA_EXT ))[0];
ide_devices[count].Size = *((unsigned int *)(ide_buf + ATA_IDENT_MAX_LBA_EXT));
else
// Note that Quafios is 32-Bit Operating System, So last 2 Words are ignored.
} else {
// Device uses CHS or 28-bit Addressing:
// Device uses CHS or 28-bit Addressing:
ide_devices[count].size = ((unsigned int *) (ide_buf + ATA_IDENT_MAX_LBA ))[0];
ide_devices[count].Size = *((unsigned int *)(ide_buf + ATA_IDENT_MAX_LBA));
}


// (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...):
// (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...):
for(k = ATA_IDENT_MODEL; k < (ATA_IDENT_MODEL+40); k+=2) {
for(k = 0; k < 40; k += 2) {
ide_devices[count].model[k - ATA_IDENT_MODEL] = ide_buf[k+1];
ide_devices[count].Model[k] = ide_buf[ATA_IDENT_MODEL + k + 1];
ide_devices[count].model[(k+1) - ATA_IDENT_MODEL] = ide_buf[k];}
ide_devices[count].Model[k + 1] = ide_buf[ATA_IDENT_MODEL + k];}
ide_devices[count].model[40] = 0; // Terminate String.
ide_devices[count].Model[40] = 0; // Terminate String.


count++;
count++;
Line 524: Line 440:
// 4- Print Summary:
// 4- Print Summary:
for (i = 0; i < 4; i++)
for (i = 0; i < 4; i++)
if (ide_devices[i].reserved == 1) {
if (ide_devices[i].Reserved == 1) {
printk(" Found %s Drive %dGB - %s\n",
printk(" Found %s Drive %dGB - %s\n",
(const char *[]){"ATA", "ATAPI"}[ide_devices[i].type], /* Type */
(const char *[]){"ATA", "ATAPI"}[ide_devices[i].Type], /* Type */
ide_devices[i].size/1024/1024/2, /* Size */
ide_devices[i].Size / 1024 / 1024 / 2, /* Size */
ide_devices[i].model);
ide_devices[i].Model);
}
}
}
}
</syntaxhighlight>
</source>
== Read/Write From ATA Drive ==

Now we're moving to a slightly more advanced part, it is to read and write from/to an ATA drive.
===Read/Write From ATA Drive===
Now we are moving to a bit more advanced part, it is to read and write from/to an ATA Drive.
There is 3 ways of addressing a sector:
There is 3 ways of addressing a sector:
* CHS (Cylinder-Head-Sector): an old way of addressing sectors in ATA drives, I think all ATA-Drives should support this way of addressing.
* CHS (Cylinder-Head-Sector): an old way of addressing sectors in ATA drives, all ATA drives should support this way of addressing.
* LBA28: Accessing a sector by its LBA Address. but the address should be 28-bit long. i think all ATA-Drives should support this way of addressing, the problem of LBA28 Addressing is that it allows only to access 128GB from the ATA-Disk, so if ATA-Disk is more than 128GB, it should support LBA48 Feature Set.
* LBA28: Accessing a sector by its 28-bit LBA address. All ATA drives should support this way of addressing, the problem with LBA28 Addressing is that it only allows access 128GB to be accessed, so if the disk is bigger than 128GB, it should support the LBA48 Feature Set.
* LBA48: Accessing a sector by its LBA Address. but the address should be 48-bit long. as we use integers in GCC, so our maximum address in this tutorial is 32-bit long, which allows accessing an ATA-Drive up to 2TB.
* LBA48: Accessing a sector by its 48-bit LBA address. As we use integers in GCC, our maximum address in this tutorial is 32-bit long, which allows accessing a drive with a size of up to 2TB.
So we can conclude an algorithm to determine which type of Addressing we are going to use:

<pre>
So We can conclude an algorithm to determine which type of Addressing we are going to use:
if (No LBA support)

Use CHS.
Code:
else if (the LBA Sector Address > 0x0FFFFFFF)
If (Drive doesn't Support LBA)
// Use CHS.
Use LBA48.
else (if the LBA Sector Address > 0x0FFFFFFF)
// The Sector We are going to read is above 128GB Boundary, Use LBA48.
else
else
// Use LBA28.
Use LBA28.
</pre>


Reading the buffer may be done by polling or DMA.
Reading the buffer may be done by polling or DMA.
PIO: After sending the command [Read or Write Sectors], we read Data Port [as words], or write to Data Port [as words]. this is the same way of reading identification space.
PIO: After sending the command to read or write sectors, we read or write to the Data Port (as words). This is the same way of reading identification space.
DMA: After sending the command, you should wait for an IRQ, while you are waiting, Buffer is written directly to memory automatically.
DMA: After sending the command, you should wait for an IRQ, while you are waiting, Buffer is written directly to memory automatically.


We are going to use PIO as it isn't going to be complix, and i want to be far from IRQs as they are very slower than Polling after PIO.
We are going to use PIO as it is less complex.


We can conclude also this table:
We can conclude also this table:
<syntaxhighlight lang="c">

<source lang="c">
/* ATA/ATAPI Read/Write Modes:
/* ATA/ATAPI Read/Write Modes:
* ++++++++++++++++++++++++++++++++
* ++++++++++++++++++++++++++++++++
* Addressing Modes:
* Addressing Modes:
* ================
* ================
* - LBA28 Mode. (+)
* - LBA28 Mode. (+)
* - LBA48 Mode. (+)
* - LBA48 Mode. (+)
* - CHS. (+)
* - CHS. (+)
* Reading Modes:
* Reading Modes:
Line 576: Line 487:
* ================
* ================
* - IRQs
* - IRQs
* - Polling Status (+) // Suitable for Singletasking
* - Polling Status (+) // Suitable for Singletasking
*/
*/
</syntaxhighlight>
</source>

There is something needed to be expressed here, I have told before that Task-File is like that:
There is something needed to be expressed here, I have told before that Task-File is like that:
* Register 0: [Word] Data Register. [Readable & Writable].
* Register 0: [Word] Data Register. (Read-Write).
* Register 1: [Byte] Error Register. [Readable].
* Register 1: [Byte] Error Register. (Read).
* Register 1: [Byte] Features Register. [Writable].
* Register 1: [Byte] Features Register. (Write).
* Register 2: [Byte] SECCOUNT0 Register. [Readable & Writable].
* Register 2: [Byte] SECCOUNT0 Register. (Read-Write).
* Register 3: [Byte] LBA0 Register. [Readable & Writable].
* Register 3: [Byte] LBA0 Register. (Read-Write).
* Register 4: [Byte] LBA1 Register. [Readable & Writable].
* Register 4: [Byte] LBA1 Register. (Read-Write).
* Register 5: [Byte] LBA2 Register. [Readable & Writable].
* Register 5: [Byte] LBA2 Register. (Read-Write).
* Register 6: [Byte] HDDEVSEL Register. [Readable & Writable].
* Register 6: [Byte] HDDEVSEL Register. (Read-Write).
* Register 7: [Byte] Command Register. [Writable].
* Register 7: [Byte] Command Register. (Write).
* Register 7: [Byte] Status Register. [Readable].
* Register 7: [Byte] Status Register. (Read).
So each register between 2 to 5 should be 8-bits long. Really each of them are 16-bits long.

So each one of Registers from 2 to 5 should be 8-bits long. but really each one of them is 16-bit long.

* Register 2: [Bits 0-7] SECCOUNT0, [Bits 8-15] SECOUNT1
* Register 2: [Bits 0-7] SECCOUNT0, [Bits 8-15] SECOUNT1
* Register 3: [Bits 0-7] LBA0, [Bits 8-15] LBA3
* Register 3: [Bits 0-7] LBA0, [Bits 8-15] LBA3
* Register 4: [Bits 0-7] LBA1, [Bits 8-15] LBA4
* Register 4: [Bits 0-7] LBA1, [Bits 8-15] LBA4
* Register 5: [Bits 0-7] LBA2, [Bits 8-15] LBA5
* Register 5: [Bits 0-7] LBA2, [Bits 8-15] LBA5
The word [(SECCOUNT1 << 8) | SECCOUNT0] expresses the number of sectors which can be read when you access by LBA48.

The word [(SECCOUNT1<<8) | SECCOUNT0] expresses number of sectors which can be read when you access by LBA48.
When you access in CHS or LBA28, SECCOUNT0 only expresses number of sectors.
When you access in CHS or LBA28, SECCOUNT0 only expresses number of sectors.
* LBA0 makes up bits 0 : 7 of the LBA address when you read in LBA28 or LBA48; it can also be the sector number of CHS.
* LBA1 makes up bits 8 : 15 of the LBA address when you read in LBA28 or LBA48; it can also be the low byte of the cylinder number of CHS.
* LBA2 makes up bits 16 : 23 of the LBA address when you read in LBA28 or LBA48; it can also be the high byte of the cylinder number of CHS.
* LBA3 makes up bits 24 : 31 of the LBA48 address.
* LBA4 makes up bits 32 : 39 of the LBA48 address.
* LBA5 makes up bits 40 : 47 of LBA48 address.
Notice that the LBA0, 1 and 2 registers are 24 bits long in total, which is not enough for LBA28; the higher 4-bits can be written to the lower 4-bits of the HDDEVSEL register.


Also notice that if bit 6 of this register is set, we are going to use LBA, if not, we are going to use CHS. There is a mode which is called extended CHS.
* LBA0 is Bits[0-7] of LBA Address when you read in LBA28 or LBA48, it can be sector number of CHS.
* LBA1 is Bits[8-15] of LBA Address when you read in LBA28 or LBA48, it can be low 8 bits of cylinder number of CHS.
* LBA2 is Bits[16-23] of LBA Address when you read in LBA28 or LBA48, it can be high 8 bits of cylinder number of CHS.
* LBA3 is Bits[24-31] of LBA Address when you read in LBA48.
* LBA4 is Bits[32-39] of LBA Address when you read in LBA48.
* LBA5 is Bits[40-47] of LBA Address when you read in LBA48.

Notice that according to that, LBA0,1,2 registers [8-bits + 8-bits + 8-bits] are 24-bit long, which is not enough for LBA28, so the higher 4-bits can be written to the lower 4-bits of HDDEVSEL Register.

Also notice that if we set bit 6 of this register, we are going to use LBA, if not, we are going to use CHS.
notice that there is a mode which is called extended CHS, but i don't wanna be exposed to that.



Lets go into the code:
Lets go into the code:
<syntaxhighlight lang="c">

<source lang="c">
unsigned char ide_ata_access(unsigned char direction, unsigned char drive, unsigned int lba,
unsigned char ide_ata_access(unsigned char direction, unsigned char drive, unsigned int lba,
unsigned char numsects, unsigned short selector, unsigned int edi) {
unsigned char numsects, unsigned short selector, unsigned int edi) {
</syntaxhighlight>
</source>
This function reads/writes sectors from ATA-Drive. If direction is 0 we are reading, else we are writing.

* drive is the drive number which can be from 0 to 3.
This Function reads/writes sectors from ATA-Drive. if (direction = 0) so we are reading, else we are writing.
* drive, is drive number which can be from 0 to 3.
* lba is the LBA address which allows us to access disks up to 2TB.
* numsects is the number of sectors to be read, it is a char, as reading more than 256 sector immediately may performance issues. If numsects is 0, the ATA controller will know that we want 256 sectors.
* lba, is the LBA Address which allows us to access disks up to 2TB.
* selector is the segment selector to read from, or write to.
* numsects, number of sectors to be read, it is a char, as reading more than 256 sector immediately may cause the OS to hang. notice that if numsects = 0, controller will know that we want 256 sectors.
* selector, segment selector to read from, or write to.
* edi is the offset in that segment. (the memory address for the data buffer)
<syntaxhighlight lang="c">
* edi, offset in the segment.

<source lang="c">
unsigned char lba_mode /* 0: CHS, 1:LBA28, 2: LBA48 */, dma /* 0: No DMA, 1: DMA */, cmd;
unsigned char lba_mode /* 0: CHS, 1:LBA28, 2: LBA48 */, dma /* 0: No DMA, 1: DMA */, cmd;
unsigned char lba_io[6];
unsigned char lba_io[6];
unsigned int channel = ide_devices[drive].channel; // Read the Channel.
unsigned int channel = ide_devices[drive].Channel; // Read the Channel.
unsigned int slavebit = ide_devices[drive].drive; // Read the Drive [Master/Slave]
unsigned int slavebit = ide_devices[drive].Drive; // Read the Drive [Master/Slave]
unsigned int bus = channels[channel].base; // Bus Base, like 0x1F0 which is also data port.
unsigned int bus = channels[channel].Base; // Bus Base, like 0x1F0 which is also data port.
unsigned int words = 256; // Approximatly all ATA-Drives has sector-size of 512-byte.
unsigned int words = 256; // Almost every ATA drive has a sector-size of 512-byte.
unsigned short cyl, i; unsigned char head, sect, err;
unsigned short cyl, i;
unsigned char head, sect, err;
</source>
</syntaxhighlight>

We don't need IRQs, so we should disable it to disallow problems to happen, we said before that bit 1 of Control Register (Which is called nIEN bit), if it is set, so no IRQs will be invoked from this channel, either from Master Drive or from Slave Drive.
We don't need IRQs, so we should disable it to prevent problems from happening. We said before that if bit 1 of the Control Register (which is called nIEN bit), is set, no IRQs will be invoked from any drives on this channel, either master or slave.
<syntaxhighlight lang="c">

<source lang="c">
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = (ide_irq_invoked = 0x0) + 0x02);
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = (ide_irq_invoked = 0x0) + 0x02);
</syntaxhighlight>
</source>

Now lets read the parameters:
Now lets read the parameters:
<syntaxhighlight lang="c">

<source lang="c">
// (I) Select one from LBA28, LBA48 or CHS;
// (I) Select one from LBA28, LBA48 or CHS;
if (lba >= 0x10000000) { // Sure Drive should support LBA in this case, or you are
if (lba >= 0x10000000) { // Sure Drive should support LBA in this case, or you are
Line 653: Line 550:
// LBA48:
// LBA48:
lba_mode = 2;
lba_mode = 2;
lba_io[0] = (lba & 0x000000FF)>> 0;
lba_io[0] = (lba & 0x000000FF) >> 0;
lba_io[1] = (lba & 0x0000FF00)>> 8;
lba_io[1] = (lba & 0x0000FF00) >> 8;
lba_io[2] = (lba & 0x00FF0000)>>16;
lba_io[2] = (lba & 0x00FF0000) >> 16;
lba_io[3] = (lba & 0xFF000000)>>24;
lba_io[3] = (lba & 0xFF000000) >> 24;
lba_io[4] = 0; // We said that we lba is integer, so 32-bit are enough to access 2TB.
lba_io[4] = 0; // LBA28 is integer, so 32-bits are enough to access 2TB.
lba_io[5] = 0; // We said that we lba is integer, so 32-bit are enough to access 2TB.
lba_io[5] = 0; // LBA28 is integer, so 32-bits are enough to access 2TB.
head = 0; // Lower 4-bits of HDDEVSEL are not used here.
head = 0; // Lower 4-bits of HDDEVSEL are not used here.
} else if (ide_devices[drive].capabilities & 0x200) { // Drive supports LBA?
} else if (ide_devices[drive].Capabilities & 0x200) { // Drive supports LBA?
// LBA28:
// LBA28:
lba_mode = 1;
lba_mode = 1;
lba_io[0] = (lba & 0x00000FF)>> 0;
lba_io[0] = (lba & 0x00000FF) >> 0;
lba_io[1] = (lba & 0x000FF00)>> 8;
lba_io[1] = (lba & 0x000FF00) >> 8;
lba_io[2] = (lba & 0x0FF0000)>>16;
lba_io[2] = (lba & 0x0FF0000) >> 16;
lba_io[3] = 0; // These Registers are not used here.
lba_io[3] = 0; // These Registers are not used here.
lba_io[4] = 0; // These Registers are not used here.
lba_io[4] = 0; // These Registers are not used here.
lba_io[5] = 0; // These Registers are not used here.
lba_io[5] = 0; // These Registers are not used here.
head = (lba & 0xF000000)>>24;
head = (lba & 0xF000000) >> 24;
} else {
} else {
// CHS:
// CHS:
lba_mode = 0;
lba_mode = 0;
sect = (lba % 63) + 1;
sect = (lba % 63) + 1;
cyl = (lba + 1 - sect)/(16*63);
cyl = (lba + 1 - sect) / (16 * 63);
lba_io[0] = sect;
lba_io[0] = sect;
lba_io[1] = (cyl>>0) & 0xFF;
lba_io[1] = (cyl >> 0) & 0xFF;
lba_io[2] = (cyl>>8) & 0xFF;
lba_io[2] = (cyl >> 8) & 0xFF;
lba_io[3] = 0;
lba_io[3] = 0;
lba_io[4] = 0;
lba_io[4] = 0;
lba_io[5] = 0;
lba_io[5] = 0;
head = (lba + 1 - sect)%(16*63)/(63); // Head number is written to HDDEVSEL lower 4-bits.
head = (lba + 1 - sect) % (16 * 63) / (63); // Head number is written to HDDEVSEL lower 4-bits.
}
}
</syntaxhighlight>
</source>

Now we are going to choose the way of reading the buffer [PIO or DMA]:
Now we are going to choose the way of reading the buffer [PIO or DMA]:
<syntaxhighlight lang="c">

// (II) See if drive supports DMA or not;
<source lang="c">
// (II) See if Drive Supports DMA or not;
dma = 0; // We don't support DMA
</syntaxhighlight>
dma = 0; // Supports or doesn't, we don't support !!!
Lets poll the Status port while the channel is busy:
</source>
<syntaxhighlight lang="c">

Lets Poll the status port if the channel is busy:

<source lang="c">
// (III) Wait if the drive is busy;
// (III) Wait if the drive is busy;
while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY); // Wait if Busy.
while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY){
</source>
} // Wait if busy.

</syntaxhighlight>
HDDDEVSEL Register now looks like this:
The HDDDEVSEL register now looks like this:

* Bits 0-3: Head Number for CHS.
* Bits 0 : 3: Head Number for CHS.
* Bit 4: Slave Bit. (0: Selecting Master Drive, 1: Selecting Slave Drive).
* Bit 4: Slave Bit. (0: Selecting Master Drive, 1: Selecting Slave Drive).
* Bit 5: Obselete and isn't used, but should be set.
* Bit 5: Obsolete and isn't used, but should be set.
* Bit 6: LBA (0: CHS, 1: LBA).
* Bit 6: LBA (0: CHS, 1: LBA).
* Bit 7: Obselete and isn't used, but should be set.
* Bit 7: Obsolete and isn't used, but should be set.
Lets write all these information to the register, while the obsolete bits are set (0xA0):

<syntaxhighlight lang="c">
Lets write all these information to the register, while the obselete bits are set (0xA0):

<source lang="c">
// (IV) Select Drive from the controller;
// (IV) Select Drive from the controller;
if (lba_mode == 0)
if (lba_mode==0) ide_write(channel,ATA_REG_HDDEVSEL,0xA0|(slavebit<<4)|head); // Drive & CHS.
else ide_write(channel,ATA_REG_HDDEVSEL,0xE0|(slavebit<<4)|head); // Drive & LBA
ide_write(channel, ATA_REG_HDDEVSEL, 0xA0 | (slavebit << 4) | head); // Drive & CHS.
else
</source>
ide_write(channel, ATA_REG_HDDEVSEL, 0xE0 | (slavebit << 4) | head); // Drive & LBA

</syntaxhighlight>
Let's write the parameters to registers:
Let's write the parameters to registers:
<syntaxhighlight lang="c">

<source lang="c">
// (V) Write Parameters;
// (V) Write Parameters;
if (lba_mode == 2) {
if (lba_mode == 2) {
Line 729: Line 620:
ide_write(channel, ATA_REG_LBA1, lba_io[1]);
ide_write(channel, ATA_REG_LBA1, lba_io[1]);
ide_write(channel, ATA_REG_LBA2, lba_io[2]);
ide_write(channel, ATA_REG_LBA2, lba_io[2]);
</syntaxhighlight>
</source>
If you are using LBA48 and want to write to the LBA0 and LBA3 registers, you should write LBA3 to Register 3, then write LBA0 to Register 3. ide_write function makes it quite simple, refer to the function and you will fully understand the code.

By this way, if you are using LBA48 and want to write to LBA0 Register [Register 3 in Task-File], and want to write to LBA3 Register [Register 3 also in Task-File], you should write LBA3 to Register 3, then write LBA0 to Register 3. ide_write function makes it quite simple, refer to the function and you will full-understand the code.


Now, we have a great set of commands described in ATA/ATAPI-8 Specification, we should choose the suitable command to execute:
Now, we have a great set of commands described in ATA/ATAPI-8 Specification, we should choose the suitable command to execute:
<syntaxhighlight lang="c">

<source lang="c">
// (VI) Select the command and send it;
// (VI) Select the command and send it;
// Routine that is followed:
// Routine that is followed:
Line 744: Line 633:
// If (!DMA & LBA28) DO_PIO_LBA;
// If (!DMA & LBA28) DO_PIO_LBA;
// If (!DMA & !LBA#) DO_PIO_CHS;
// If (!DMA & !LBA#) DO_PIO_CHS;
</syntaxhighlight>
</source>
There isn't a command for doing CHS with DMA.

<syntaxhighlight lang="c">
There isn't a command for Doing in CHS with DMA.

<source lang="c">
if (lba_mode == 0 && dma == 0 && direction == 0) cmd = ATA_CMD_READ_PIO;
if (lba_mode == 0 && dma == 0 && direction == 0) cmd = ATA_CMD_READ_PIO;
if (lba_mode == 1 && dma == 0 && direction == 0) cmd = ATA_CMD_READ_PIO;
if (lba_mode == 1 && dma == 0 && direction == 0) cmd = ATA_CMD_READ_PIO;
Line 762: Line 649:
if (lba_mode == 2 && dma == 1 && direction == 1) cmd = ATA_CMD_WRITE_DMA_EXT;
if (lba_mode == 2 && dma == 1 && direction == 1) cmd = ATA_CMD_WRITE_DMA_EXT;
ide_write(channel, ATA_REG_COMMAND, cmd); // Send the Command.
ide_write(channel, ATA_REG_COMMAND, cmd); // Send the Command.
</syntaxhighlight>
</source>
This ATA_CMD_READ_PIO command is used for reading in LBA28 or CHS, and the IDE controller refers to bit 6 of the HDDEVSEL register to find out the mode of reading (LBA or CHS).


After sending the command, we should poll, then we read/write a sector, then we should poll, then we read/write a sector, until we read/write all sectors needed, if an error has happened, the function will return a specific error code.
This Command "ATA_CMD_READ_PIO" is right for rading in LBA28 or CHS, and controller refers to bit 6 of HDDEVSEL Register to know the mode of reading (LBA or CHS).


Notice that after writing, we should execute the CACHE FLUSH command, and we should poll after it, but without checking for errors.
After sending the command, we should poll, then we read/write a sector then we should poll, then we read/write a sector, until we read/write all sectors needed, if an error is happened, we the function will return a specific error code.
<syntaxhighlight lang="c">

Notice that after writing, we should execute the CACHE FLUSH Command, and we should poll after it, but without checking for errors.

<source lang="c">
if (dma)
if (dma)
if (direction == 0);
if (direction == 0);
// DMA Read.
// DMA Read.
else; // DMA Write.
else;
// DMA Write.
else
else
if (direction == 0)
if (direction == 0)
// PIO Read.
// PIO Read.
for (i = 0; i < numsects; i++) {
for (i = 0; i < numsects; i++) {
if (err = ide_polling(channel, 1)) return err; // Polling, set error and exit if there is.
if (err = ide_polling(channel, 1))
return err; // Polling, set error and exit if there is.
asm("pushw %es");
asm("pushw %es");
asm("mov %%ax, %%es"::"a"(selector));
asm("mov %%ax, %%es" : : "a"(selector));
asm("rep insw"::"c"(words), "d"(bus), "D"(edi)); // Receive Data.
asm("rep insw" : : "c"(words), "d"(bus), "D"(edi)); // Receive Data.
asm("popw %es");
asm("popw %es");
edi += (words*2);
edi += (words*2);
} else {
} else {
// PIO Write.
// PIO Write.
for (i = 0; i < numsects; i++) {
for (i = 0; i < numsects; i++) {
ide_polling(channel, 0); // Polling.
ide_polling(channel, 0); // Polling.
asm("pushw %ds");
asm("pushw %ds");
asm("mov %%ax, %%ds"::"a"(selector));
asm("mov %%ax, %%ds"::"a"(selector));
asm("rep outsw"::"c"(words), "d"(bus), "S"(edi)); // Send Data
asm("rep outsw"::"c"(words), "d"(bus), "S"(edi)); // Send Data
asm("popw %ds");
asm("popw %ds");
edi += (words*2);
edi += (words*2);
}
}
ide_write(channel, ATA_REG_COMMAND, (char []) { ATA_CMD_CACHE_FLUSH,
ide_write(channel, ATA_REG_COMMAND, (char []) { ATA_CMD_CACHE_FLUSH,
ATA_CMD_CACHE_FLUSH,
ATA_CMD_CACHE_FLUSH,
ATA_CMD_CACHE_FLUSH_EXT}[lba_mode]);
ATA_CMD_CACHE_FLUSH_EXT}[lba_mode]);
ide_polling(channel, 0); // Polling.
ide_polling(channel, 0); // Polling.
}
}


return 0; // Easy, ... Isn't it?
return 0; // Easy, isn't it?
}
}
</syntaxhighlight>
</source>
== Reading from an ATAPI Drive ==
Let's move to an easier part - reading from an ATAPI drive. I will not make the function that writes to an ATAPI drive, because writing to it is very complex and is outside of the scope of this tutorial.


An ATAPI drive is different from an ATA drive, as it uses the SCSI command set instead of the ATA command set. Parameters are sent as packets, therefore it's called the ATA Packet Interface [ATAPI].
===Read From ATAPI Drive===
Let's move to a part which is quite easier, it is to read from ATAPI Drive, i will not make the function write to ATAPI Drive, because the write Operation is very complix and it should done by third-party tools (like Nero in Windows, and Brasero in Linux).


Notice also that ATAPI drives always use IRQs and you can't disable them. We should create a function that waits for an IRQ:
ATAPI Drive is different from ATA Drives, as it doesn't use ATA Commands, but it use the SCSI-Command-Set. Parameters are sent into a Packet, so it is Called: ATA-Packet Interface [ATAPI].
<syntaxhighlight lang="c">

Notice also that ATAPI drives should always use IRQs, you can't disable them, so we should create a function which waits for an IRQ to be caused:

<source lang="c">
void ide_wait_irq() {
void ide_wait_irq() {
while (!ide_irq_invoked);
while (!ide_irq_invoked)
;
ide_irq_invoked = 0;
ide_irq_invoked = 0;
}
}
</syntaxhighlight>
</source>
When an IRQ happens, the following function should be executed by ISR:

<syntaxhighlight lang="c">
when an IRQ happens, the following function should be executed by ISR:

<source lang="c">
void ide_irq() {
void ide_irq() {
ide_irq_invoked = 1;
ide_irq_invoked = 1;
}
}
</syntaxhighlight>
</source>
ide_wait_irq will go into a while loop, which waits for the variable ide_irq_invoked to be set, then clears it.

<syntaxhighlight lang="c">
by this way, ide_wait_irq() will go into a while loop, which waits for the variable ide_irq_invoked to be set, then it reclears it.

<source lang="c">
unsigned char ide_atapi_read(unsigned char drive, unsigned int lba, unsigned char numsects,
unsigned char ide_atapi_read(unsigned char drive, unsigned int lba, unsigned char numsects,
unsigned short selector, unsigned int edi) {
unsigned short selector, unsigned int edi) {
</syntaxhighlight>
</source>
* drive is the drive number, which is from 0 to 3.

* drive, is the drive number, which is from 0 to 3.
* lba is the LBA address.
* numsects is the number of sectors. It should always be 1, and if you want to read more than one sector, re-execute this function with th updated LBA address.
* lba, the lba address.
* selector is the Segment Selector.
* numsects, number of sectors, it should always be 1, and if you wanna read more than one sector, re-execute this fucntion with updated LBA address.
* edi is the offset in the selector.
* selector, Segment Selector.
* edi, offset in the selector.

Let's read the parameters of the drive:
Let's read the parameters of the drive:
<syntaxhighlight lang="c">

unsigned int channel = ide_devices[drive].Channel;
<source lang="c">
unsigned int channel = ide_devices[drive].channel;
unsigned int slavebit = ide_devices[drive].Drive;
unsigned int slavebit = ide_devices[drive].drive;
unsigned int bus = channels[channel].Base;
unsigned int bus = channels[channel].base;
unsigned int words = 1024; // Sector Size. ATAPI drives have a sector size of 2048 bytes.
unsigned char err;
unsigned int words = 1024; // Sector Size. ATAPI Drives has a sector size of 2048 bytes.
unsigned char err; int i;
int i;
</syntaxhighlight>
</source>

We need IRQs:
We need IRQs:
<syntaxhighlight lang="c">

<source lang="c">
// Enable IRQs:
// Enable IRQs:
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);
</syntaxhighlight>
</source>
Let's setup the SCSI Packet, which is 6 words (12 bytes) long:

<syntaxhighlight lang="c">
Let's setup the SCSI Packet, Which is 6-Words long [12-Bytes]:

<source lang="c">
// (I): Setup SCSI Packet:
// (I): Setup SCSI Packet:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
atapi_packet[ 0] = ATAPI_CMD_READ;
atapi_packet[ 0] = ATAPI_CMD_READ;
atapi_packet[ 1] = 0x0;
atapi_packet[ 1] = 0x0;
atapi_packet[ 2] = (lba>>24) & 0xFF;
atapi_packet[ 2] = (lba >> 24) & 0xFF;
atapi_packet[ 3] = (lba>>16) & 0xFF;
atapi_packet[ 3] = (lba >> 16) & 0xFF;
atapi_packet[ 4] = (lba>> 8) & 0xFF;
atapi_packet[ 4] = (lba >> 8) & 0xFF;
atapi_packet[ 5] = (lba>> 0) & 0xFF;
atapi_packet[ 5] = (lba >> 0) & 0xFF;
atapi_packet[ 6] = 0x0;
atapi_packet[ 6] = 0x0;
atapi_packet[ 7] = 0x0;
atapi_packet[ 7] = 0x0;
Line 874: Line 750:
atapi_packet[10] = 0x0;
atapi_packet[10] = 0x0;
atapi_packet[11] = 0x0;
atapi_packet[11] = 0x0;
</syntaxhighlight>
</source>

Now we should select the drive:
Now we should select the drive:
<syntaxhighlight lang="c">

// (II): Select the drive:
<source lang="c">
// (II): Select the Drive:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
ide_write(channel, ATA_REG_HDDEVSEL, slavebit<<4);
ide_write(channel, ATA_REG_HDDEVSEL, slavebit << 4);
</syntaxhighlight>
</source>

400 nanoseconds delay after this select is a good idea:
400 nanoseconds delay after this select is a good idea:
<syntaxhighlight lang="c">

<source lang="c">
// (III): Delay 400 nanoseconds for select to complete:
// (III): Delay 400 nanoseconds for select to complete:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
for(int i = 0; i < 4; i++)
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading the Alternate Status port wastes 100ns.
</syntaxhighlight>
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
<syntaxhighlight lang="c">
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
</source>

<source lang="c">
// (IV): Inform the Controller that we use PIO mode:
// (IV): Inform the Controller that we use PIO mode:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
ide_write(channel, ATA_REG_FEATURES, 0); // PIO mode.
ide_write(channel, ATA_REG_FEATURES, 0); // PIO mode.
</syntaxhighlight>
</source>
Tell the controller the size of the buffer

<syntaxhighlight lang="c">
Controller wants to know what do we think of the size of buffer!, we will allow him to know that:

<source lang="c">
// (V): Tell the Controller the size of buffer:
// (V): Tell the Controller the size of buffer:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
ide_write(channel, ATA_REG_LBA1, (words * 2) & 0xFF); // Lower Byte of Sector Size.
ide_write(channel, ATA_REG_LBA1, (words * 2) & 0xFF); // Lower Byte of Sector Size.
ide_write(channel, ATA_REG_LBA2, (words * 2)>>8); // Upper Byte of Sector Size.
ide_write(channel, ATA_REG_LBA2, (words * 2) >> 8); // Upper Byte of Sector Size.
</syntaxhighlight>
</source>
Now that we want to send the packet, we should first send the command "Packet":

<syntaxhighlight lang="c">
Now we want to send the packet, we should first send the command "Packet":

<source lang="c">
// (VI): Send the Packet Command:
// (VI): Send the Packet Command:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
ide_write(channel, ATA_REG_COMMAND, ATA_CMD_PACKET); // Send the Command.
ide_write(channel, ATA_REG_COMMAND, ATA_CMD_PACKET); // Send the Command.
</source>


// (VII): Waiting for the driver to finish or return an error code:
<source lang="c">
// (VII): Waiting for the driver to finish or invoke an error:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
if (err = ide_polling(channel, 1)) return err; // Polling and return if error.
if (err = ide_polling(channel, 1)) return err; // Polling and return if error.
</source>


<source lang="c">
// (VIII): Sending the packet data:
// (VIII): Sending the packet data:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
asm("rep outsw"::"c"(6), "d"(bus), "S"(atapi_packet)); // Send Packet Data
asm("rep outsw" : : "c"(6), "d"(bus), "S"(atapi_packet)); // Send Packet Data
</syntaxhighlight>
</source>
Here we cannot poll. We should wait for an IRQ, then read the sectors. These two operations should be repeated for each sector.

<syntaxhighlight lang="c">
Here we cannot Poll, We should wait for an IRQ, then read the sectors. these two operations should be repeated as the number of sectors, but we are said before that numsects should be 1. But I have put a for loop, i don't know why.
// (IX): Receiving Data:

<source lang="c">
// (IX): Recieving Data:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
for (i = 0; i < numsects; i++) {
for (i = 0; i < numsects; i++) {
ide_wait_irq(); // Wait for an IRQ.
ide_wait_irq(); // Wait for an IRQ.
if (err = ide_polling(channel, 1)) return err; // Polling and return if error.
if (err = ide_polling(channel, 1))
return err; // Polling and return if error.
asm("pushw %es");
asm("pushw %es");
asm("mov %%ax, %%es"::"a"(selector));
asm("mov %%ax, %%es"::"a"(selector));
asm("rep insw"::"c"(words), "d"(bus), "D"(edi));// Receive Data.
asm("rep insw"::"c"(words), "d"(bus), "D"(edi));// Receive Data.
asm("popw %es");
asm("popw %es");
edi += (words*2);
edi += (words * 2);
}
}
</syntaxhighlight>
</source>
Now we should wait for an IRQ and poll until the Busy and DRQ bits are clear:

<syntaxhighlight lang="c">
Now we should wait for an IRQ and Poll for Busy and DRQ bits to be clear:

<source lang="c">
// (X): Waiting for an IRQ:
// (X): Waiting for an IRQ:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
Line 955: Line 813:
// (XI): Waiting for BSY & DRQ to clear:
// (XI): Waiting for BSY & DRQ to clear:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
while (ide_read(channel, ATA_REG_STATUS) & (ATA_SR_BSY | ATA_SR_DRQ));
while (ide_read(channel, ATA_REG_STATUS) & (ATA_SR_BSY | ATA_SR_DRQ))
;


return 0; // Easy, ... Isn't it?
return 0; // Easy, ... Isn't it?
}
}
</syntaxhighlight>
</source>
== Reading from an ATA/ATAPI Drive ==

<syntaxhighlight lang="c">
===Standard Function For Reading from ATA/ATAPI Drive===
<source lang="c">
void ide_read_sectors(unsigned char drive, unsigned char numsects, unsigned int lba,
void ide_read_sectors(unsigned char drive, unsigned char numsects, unsigned int lba,
unsigned short es, unsigned int edi) {
unsigned short es, unsigned int edi) {
Line 968: Line 826:
// 1: Check if the drive presents:
// 1: Check if the drive presents:
// ==================================
// ==================================
if (drive > 3 || ide_devices[drive].reserved == 0) package[0] = 0x1; // Drive Not Found!
if (drive > 3 || ide_devices[drive].Reserved == 0) package[0] = 0x1; // Drive Not Found!


// 2: Check if inputs are valid:
// 2: Check if inputs are valid:
// ==================================
// ==================================
else if (((lba + numsects) > ide_devices[drive].size) && (ide_devices[drive].type == IDE_ATA))
else if (((lba + numsects) > ide_devices[drive].Size) && (ide_devices[drive].Type == IDE_ATA))
package[0] = 0x2; // Seeking to invalid position.
package[0] = 0x2; // Seeking to invalid position.


Line 979: Line 837:
else {
else {
unsigned char err;
unsigned char err;
if (ide_devices[drive].type == IDE_ATA)
if (ide_devices[drive].Type == IDE_ATA)
err = ide_ata_access(ATA_READ, drive, lba, numsects, es, edi);
err = ide_ata_access(ATA_READ, drive, lba, numsects, es, edi);
else if (ide_devices[drive].type == IDE_ATAPI)
else if (ide_devices[drive].Type == IDE_ATAPI)
for (i = 0; i < numsects; i++)
for (i = 0; i < numsects; i++)
err = ide_atapi_read(drive, lba + i, 1, es, edi + (i*2048));
err = ide_atapi_read(drive, lba + i, 1, es, edi + (i*2048));
Line 987: Line 845:
}
}
}
}
// package[0] is an entry of array, this entry specifies the Error Code, you can replace that.
// package[0] is an entry of an array. It contains the Error Code.
</syntaxhighlight>
</source>
== Writing to an ATA drive ==

<syntaxhighlight lang="c">
===Standard Function to write to ATA Drive===
<source lang="c">
void ide_write_sectors(unsigned char drive, unsigned char numsects, unsigned int lba,
void ide_write_sectors(unsigned char drive, unsigned char numsects, unsigned int lba,
unsigned short es, unsigned int edi) {
unsigned short es, unsigned int edi) {
Line 997: Line 854:
// 1: Check if the drive presents:
// 1: Check if the drive presents:
// ==================================
// ==================================
if (drive > 3 || ide_devices[drive].reserved == 0) package[0] = 0x1; // Drive Not Found!
if (drive > 3 || ide_devices[drive].Reserved == 0)
package[0] = 0x1; // Drive Not Found!
// 2: Check if inputs are valid:
// 2: Check if inputs are valid:
// ==================================
// ==================================
else if (((lba + numsects) > ide_devices[drive].size) && (ide_devices[drive].type == IDE_ATA))
else if (((lba + numsects) > ide_devices[drive].Size) && (ide_devices[drive].Type == IDE_ATA))
package[0] = 0x2; // Seeking to invalid position.
package[0] = 0x2; // Seeking to invalid position.
// 3: Read in PIO Mode through Polling & IRQs:
// 3: Read in PIO Mode through Polling & IRQs:
Line 1,006: Line 864:
else {
else {
unsigned char err;
unsigned char err;
if (ide_devices[drive].type == IDE_ATA)
if (ide_devices[drive].Type == IDE_ATA)
err = ide_ata_access(ATA_WRITE, drive, lba, numsects, es, edi);
err = ide_ata_access(ATA_WRITE, drive, lba, numsects, es, edi);
else if (ide_devices[drive].type == IDE_ATAPI)
else if (ide_devices[drive].Type == IDE_ATAPI)
err = 4; // Write-Protected.
err = 4; // Write-Protected.
package[0] = ide_print_error(drive, err);
package[0] = ide_print_error(drive, err);
}
}
}
}
</syntaxhighlight>
</source>
== Ejecting an ATAPI Drive ==

<syntaxhighlight lang="c">
===Standard Function to eject ATAPI Drive===
<source lang="c">
void ide_atapi_eject(unsigned char drive) {
void ide_atapi_eject(unsigned char drive) {
unsigned int channel = ide_devices[drive].channel;
unsigned int channel = ide_devices[drive].Channel;
unsigned int slavebit = ide_devices[drive].drive;
unsigned int slavebit = ide_devices[drive].Drive;
unsigned int bus = channels[channel].base;
unsigned int bus = channels[channel].Base;
unsigned int words = 2048 / 2; // Sector Size in Words.
unsigned int words = 2048 / 2; // Sector Size in Words.
unsigned char err = 0;
unsigned char err = 0;
Line 1,027: Line 884:
// 1: Check if the drive presents:
// 1: Check if the drive presents:
// ==================================
// ==================================
if (drive > 3 || ide_devices[drive].reserved == 0) package[0] = 0x1; // Drive Not Found!
if (drive > 3 || ide_devices[drive].Reserved == 0)
package[0] = 0x1; // Drive Not Found!
// 2: Check if drive isn't ATAPI:
// 2: Check if drive isn't ATAPI:
// ==================================
// ==================================
else if (ide_devices[drive].type == IDE_ATA) package[0] = 20; // Command Aborted.
else if (ide_devices[drive].Type == IDE_ATA)
package[0] = 20; // Command Aborted.
// 3: Eject ATAPI Driver:
// 3: Eject ATAPI Driver:
// ============================================
// ============================================
Line 1,054: Line 913:
// (II): Select the Drive:
// (II): Select the Drive:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
ide_write(channel, ATA_REG_HDDEVSEL, slavebit<<4);
ide_write(channel, ATA_REG_HDDEVSEL, slavebit << 4);


// (III): Delay 400 nanosecond for select to complete:
// (III): Delay 400 nanosecond for select to complete:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
for(int i = 0; i < 4; i++)
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.
ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.


// (IV): Send the Packet Command:
// (IV): Send the Packet Command:
Line 1,069: Line 926:
// (V): Waiting for the driver to finish or invoke an error:
// (V): Waiting for the driver to finish or invoke an error:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
if (err = ide_polling(channel, 1)); // Polling and stop if error.
err = ide_polling(channel, 1); // Polling and stop if error.


// (VI): Sending the packet data:
// (VI): Sending the packet data:
Line 1,080: Line 937:
}
}
package[0] = ide_print_error(drive, err); // Return;
package[0] = ide_print_error(drive, err); // Return;

}
}
}
}
</syntaxhighlight>
</source>
When this method is invoked, the optical device on the given channel is ejected.

== See Also ==
Now you can have your ODD is ejected:
=== Wiki Pages ===

* [[MBR_(x86)|Master Boot Record (x86)]]
[[Image:ODD-Eject.jpg‎]]
* [[Partition_Table|Partition Table (x86)]]

==See Also==
=== Threads ===
* [http://www.osdev.org/phpBB2/viewtopic.php?t=12268 How to w/r harddisk in pmode? (ASM Code from Dex)]
===Threads===
*[http://www.osdev.org/phpBB2/viewtopic.php?t=12268 How to w/r harddisk in pmode? (ASM Code from Dex)]
* [http://www.osdev.org/phpBB2/viewtopic.php?t=15314 ATA PIO code library (ASM code from XCHG)]
*[http://www.osdev.org/phpBB2/viewtopic.php?t=15314 ATA PIO code library (ASM code from XCHG)]
* [http://forum.osdev.org/viewtopic.php?f=1&p=167798#p167798 IDE Tutorial (C code from ''mostafazizo'')]
=== External Links ===
*[http://forum.osdev.org/viewtopic.php?f=1&p=167798#p167798 IDE Tutorial (C code from ''mostafazizo'')]
* [http://www.t13.org T13] -- The group that creates the ATA standard

* [http://www.ata-atapi.com ATA-ATAPI] -- Public Domain C driver sources (including SATA, Busmatering DMA, ATAPI), fairly good.
===External Links===
* http://www.t13.org -- T13, the group that creates the ATA standard
* [http://hddguru.com/content/en/documentation/ HDD Guru] -- The actual ATA specs from the first one that was released in 1994 to the 7th one in 2003.
* [http://www.ranish.com/part/primer.htm Partitioning Primer] -- A .HTM file containing some information about partitioning.
* http://www.ata-atapi.com -- Public Domain C driver sourcecode, including SATA, Busmatering DMA, ATAPI -- not perfect, but good.
* [http://www.bswd.com/pciide.pdf PCI IDE Controller Specification] -- Specification containing information on "compatibility mode" and "PCI native mode" (and switching between them)

* [http://bswd.com/idems100.pdf Programming Interface for Bus Master IDE Controller] -- Bus Master IDE specification
[[Category:ATA]]
[[Category:ATA]]
[[Category:Storage]]
[[de:AT Attachment]]

Latest revision as of 15:43, 9 June 2024

The factual accuracy of this article is disputed.
Please see the relevant discussion on the talk page.

IDE is a keyword which refers to the electrical specification of the cables which connect ATA drives (like hard drives) to another device. The drives use the ATA (Advanced Technology Attachment) interface. An IDE cable also can terminate at an IDE card connected to PCI.

ATAPI is an extension to ATA (recently renamed to PATA) which adds support for the SCSI command set.

Parallel/Serial ATA/ATAPI

IDE can connect up to 4 drives. Each drive can be one of the following:

  • ATA (Serial): Used for most modern hard drives.
  • ATA (Parallel): Commonly used for hard drives.
  • ATAPI (Serial): Used for most modern optical drives.
  • ATAPI (Parallel): Commonly used for optical drives.

Accessing an ATA/PATA drive works the same way as accessing a SATA drive. This also implicitly states that accessing a PATAPI ODD is the same as accessing a SATAPI ODD. An IDE driver does not need to know whether a drive is parallel or serial, it only has to know whether it's using ATA or ATAPI.

IDE Interface

If you open your case up and take a look at your motherboard, you will most likely see one or two (or possibly more) of the slots.

The white and green ports are IDE ports, also known as channels. In this example there are both primary and secondary IDE channels which only PATA can be connected to; this means that it only supports PATA/PATAPI drives.

Each port can have a PATA cable connected to it. One master drive, or two drives (master and slave), can be connected to one PATA cable. So that leaves us with the following possibilities:

  • Primary Master Drive.
  • Primary Slave Drive.
  • Secondary Master Drive.
  • Secondary Slave Drive.

Each drive can be either PATA or PATAPI.

Serial IDE

Almost every modern (this article is probably written in early 2010 so it assumes motherboards still have ide/ahci modes) motherboard has a Serial IDE channel which allows SATA and SATAPI Drives to be connected to it. There are 4 Serial IDE Ports. Each port is connected to a drive with a SATA Cable. Basically you can only have one drive connected to the Serial IDE port. Each pair of ports (every 2 ports) form one channel.

Serial IDE also has a few possibilities:

  • Primary Master, also called SATA1.
  • Primary Slave, also called SATA2.
  • Secondary Master, also called SATA3.
  • Secondary Slave, also called SATA4.

Detecting a PCI IDE Controller

Each IDE controller appears as a device on the PCI bus and can be identified by reading the configuration space. If the class code is 0x01 (Mass Storage Controller) and the subclass code is 0x01 (IDE), the device is an IDE controller. The programming interface byte(Prog If) determines how you'll access it.

  • Bit 0: When set, the primary channel is in PCI native mode. When clear, the primary channel is in compatibility mode (ports 0x1F0-0x1F7, 0x3F6, IRQ14).
  • Bit 1: When set, you can modify bit 0 to switch between PCI native and compatibility mode. When clear, you cannot modify bit 0.
  • Bit 2: When set, the secondary channel is in PCI native mode. When clear, the secondary channel is in compatibility mode (ports 0x170-0x177, 0x376, IRQ15).
  • Bit 3: When set, you can modify bit 2 to switch between PCI native and compatibility mode. When clear, you cannot modify bit 2.
  • Bit 7: When set, this is a bus master IDE controller. When clear, this controller doesn't support DMA.

If you want to access an IDE channel in PCI native mode or use the bus master function, you must additionally read the BARs to find which I/O ports to use.

  • BAR0: Base address of primary channel in PCI native mode (8 ports)
  • BAR1: Base address of primary channel control port in PCI native mode (4 ports)
  • BAR2: Base address of secondary channel in PCI native mode (8 ports)
  • BAR3: Base address of secondary channel control port in PCI native mode (4 ports)
  • BAR4: Bus master IDE (16 ports, 8 for each channel)

Note that BAR1 and BAR3 specify 4 ports, but only the port at offset 2 is used. Offsets 0, 1, and 3 should not be accessed.

If either IDE channel is in PCI native mode, you must also read the interrupt line or interrupt pin register to determine which interrupt to use. If both channels are in PCI native mode, they'll both share the same interrupt. The interrupt line field is only valid when using the PIC.

Detecting IDE Drives

To initialise the IDE driver, we call ide_initialise:

void ide_initialize(unsigned int BAR0, unsigned int BAR1, unsigned int BAR2, unsigned int BAR3,
unsigned int BAR4) {

If you only want to support the parallel IDE, you can use these parameters:

ide_initialize(0x1F0, 0x3F6, 0x170, 0x376, 0x000);

We can assume that BAR4 is 0x0 because we are not going to use it yet. We will return to ide_initialize, which searches for drives connected to the IDE. Before we go into this function, we should write some support functions and definitions which will help us a lot.

Status

The Command/Status Port returns a bit mask referring to the status of a channel when read.

#define ATA_SR_BSY     0x80    // Busy
#define ATA_SR_DRDY    0x40    // Drive ready
#define ATA_SR_DF      0x20    // Drive write fault
#define ATA_SR_DSC     0x10    // Drive seek complete
#define ATA_SR_DRQ     0x08    // Data request ready
#define ATA_SR_CORR    0x04    // Corrected data
#define ATA_SR_IDX     0x02    // Index
#define ATA_SR_ERR     0x01    // Error

Errors

The Features/Error Port, which returns the most recent error upon read, has these possible bit masks

#define ATA_ER_BBK      0x80    // Bad block
#define ATA_ER_UNC      0x40    // Uncorrectable data
#define ATA_ER_MC       0x20    // Media changed
#define ATA_ER_IDNF     0x10    // ID mark not found
#define ATA_ER_MCR      0x08    // Media change request
#define ATA_ER_ABRT     0x04    // Command aborted
#define ATA_ER_TK0NF    0x02    // Track 0 not found
#define ATA_ER_AMNF     0x01    // No address mark

Commands

When you write to the Command/Status port, you are executing one of the commands below.

#define ATA_CMD_READ_PIO          0x20
#define ATA_CMD_READ_PIO_EXT      0x24
#define ATA_CMD_READ_DMA          0xC8
#define ATA_CMD_READ_DMA_EXT      0x25
#define ATA_CMD_WRITE_PIO         0x30
#define ATA_CMD_WRITE_PIO_EXT     0x34
#define ATA_CMD_WRITE_DMA         0xCA
#define ATA_CMD_WRITE_DMA_EXT     0x35
#define ATA_CMD_CACHE_FLUSH       0xE7
#define ATA_CMD_CACHE_FLUSH_EXT   0xEA
#define ATA_CMD_PACKET            0xA0
#define ATA_CMD_IDENTIFY_PACKET   0xA1
#define ATA_CMD_IDENTIFY          0xEC

The commands below are for ATAPI devices, which will be understood soon.

#define      ATAPI_CMD_READ       0xA8
#define      ATAPI_CMD_EJECT      0x1B

ATA_CMD_IDENTIFY_PACKET and ATA_CMD_IDENTIFY return a buffer of 512 bytes called the identification space; the following definitions are used to read information from the identification space.

#define ATA_IDENT_DEVICETYPE   0
#define ATA_IDENT_CYLINDERS    2
#define ATA_IDENT_HEADS        6
#define ATA_IDENT_SECTORS      12
#define ATA_IDENT_SERIAL       20
#define ATA_IDENT_MODEL        54
#define ATA_IDENT_CAPABILITIES 98
#define ATA_IDENT_FIELDVALID   106
#define ATA_IDENT_MAX_LBA      120
#define ATA_IDENT_COMMANDSETS  164
#define ATA_IDENT_MAX_LBA_EXT  200

When you select a drive, you should specify the interface type and whether it is the master or slave:

#define IDE_ATA        0x00
#define IDE_ATAPI      0x01

#define ATA_MASTER     0x00
#define ATA_SLAVE      0x01

Task File is a range of 8 ports which are offsets from BAR0 (primary channel) and/or BAR2 (secondary channel). To exemplify:

  • BAR0 + 0 is first port.
  • BAR0 + 1 is second port.
  • BAR0 + 2 is the third
#define ATA_REG_DATA       0x00
#define ATA_REG_ERROR      0x01
#define ATA_REG_FEATURES   0x01
#define ATA_REG_SECCOUNT0  0x02
#define ATA_REG_LBA0       0x03
#define ATA_REG_LBA1       0x04
#define ATA_REG_LBA2       0x05
#define ATA_REG_HDDEVSEL   0x06
#define ATA_REG_COMMAND    0x07
#define ATA_REG_STATUS     0x07
#define ATA_REG_SECCOUNT1  0x08
#define ATA_REG_LBA3       0x09
#define ATA_REG_LBA4       0x0A
#define ATA_REG_LBA5       0x0B
#define ATA_REG_CONTROL    0x0C
#define ATA_REG_ALTSTATUS  0x0C
#define ATA_REG_DEVADDRESS 0x0D

The ALTSTATUS/CONTROL port returns the alternate status when read and controls a channel when written to.

  • For the primary channel, ALTSTATUS/CONTROL port is BAR1 + 2.
  • For the secondary channel, ALTSTATUS/CONTROL port is BAR3 + 2.

We can now say that each channel has 13 registers. For the primary channel, we use these values:

  • Data Register: BAR0 + 0; // Read-Write
  • Error Register: BAR0 + 1; // Read Only
  • Features Register: BAR0 + 1; // Write Only
  • SECCOUNT0: BAR0 + 2; // Read-Write
  • LBA0: BAR0 + 3; // Read-Write
  • LBA1: BAR0 + 4; // Read-Write
  • LBA2: BAR0 + 5; // Read-Write
  • HDDEVSEL: BAR0 + 6; // Read-Write, used to select a drive in the channel.
  • Command Register: BAR0 + 7; // Write Only.
  • Status Register: BAR0 + 7; // Read Only.
  • Alternate Status Register: BAR1 + 2; // Read Only.
  • Control Register: BAR1 + 2; // Write Only.
  • DEVADDRESS: BAR1 + 3; // I don't know what is the benefit from this register.

The map above is the same with the secondary channel, but it uses BAR2 and BAR3 instead of BAR0 and BAR1.

// Channels:
#define      ATA_PRIMARY      0x00
#define      ATA_SECONDARY    0x01

// Directions:
#define      ATA_READ      0x00
#define      ATA_WRITE     0x01

We have defined everything needed by the driver, now lets move to an important part. We said that

  • BAR0 is the start of the I/O ports used by the primary channel.
  • BAR1 is the start of the I/O ports which control the primary channel.
  • BAR2 is the start of the I/O ports used by secondary channel.
  • BAR3 is the start of the I/O ports which control secondary channel.
  • BAR4 is the start of 8 I/O ports controls the primary channel's Bus Master IDE.
  • BAR4 + 8 is the Base of 8 I/O ports controls secondary channel's Bus Master IDE.

So we can make this global structure:

struct IDEChannelRegisters {
   unsigned short base;  // I/O Base.
   unsigned short ctrl;  // Control Base
   unsigned short bmide; // Bus Master IDE
   unsigned char  nIEN;  // nIEN (No Interrupt);
} channels[2];

We also need a buffer to read the identification space into, we need a variable that indicates if an irq is invoked or not, and finally we need an array of 6 words [12 bytes] for ATAPI Drives:

unsigned char ide_buf[2048] = {0};
volatile unsigned static char ide_irq_invoked = 0;
unsigned static char atapi_packet[12] = {0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

We said the the IDE can contain up to 4 drives:

struct ide_device {
   unsigned char  Reserved;    // 0 (Empty) or 1 (This Drive really exists).
   unsigned char  Channel;     // 0 (Primary Channel) or 1 (Secondary Channel).
   unsigned char  Drive;       // 0 (Master Drive) or 1 (Slave Drive).
   unsigned short Type;        // 0: ATA, 1:ATAPI.
   unsigned short Signature;   // Drive Signature
   unsigned short Capabilities;// Features.
   unsigned int   CommandSets; // Command Sets Supported.
   unsigned int   Size;        // Size in Sectors.
   unsigned char  Model[41];   // Model in string.
} ide_devices[4];

When we read a register in a channel, like STATUS Register, it is easy to execute:

ide_read(channel, ATA_REG_STATUS);

unsigned char ide_read(unsigned char channel, unsigned char reg) {
   unsigned char result;
   if (reg > 0x07 && reg < 0x0C)
      ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
   if (reg < 0x08)
      result = inb(channels[channel].base + reg - 0x00);
   else if (reg < 0x0C)
      result = inb(channels[channel].base  + reg - 0x06);
   else if (reg < 0x0E)
      result = inb(channels[channel].ctrl  + reg - 0x0A);
   else if (reg < 0x16)
      result = inb(channels[channel].bmide + reg - 0x0E);
   if (reg > 0x07 && reg < 0x0C)
      ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
   return result;
}

We also need a function for writing to registers:

void ide_write(unsigned char channel, unsigned char reg, unsigned char data) {
   if (reg > 0x07 && reg < 0x0C)
      ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
   if (reg < 0x08)
      outb(channels[channel].base  + reg - 0x00, data);
   else if (reg < 0x0C)
      outb(channels[channel].base  + reg - 0x06, data);
   else if (reg < 0x0E)
      outb(channels[channel].ctrl  + reg - 0x0A, data);
   else if (reg < 0x16)
      outb(channels[channel].bmide + reg - 0x0E, data);
   if (reg > 0x07 && reg < 0x0C)
      ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
}

To read the identification space, we should read the Data Register as a double word 128 times. We can then copy them to our buffer.

void ide_read_buffer(unsigned char channel, unsigned char reg, unsigned int buffer,
                     unsigned int quads) {
   /* WARNING: This code contains a serious bug. The inline assembly trashes ES and
    *           ESP for all of the code the compiler generates between the inline
    *           assembly blocks.
    */
   if (reg > 0x07 && reg < 0x0C)
      ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
   asm("pushw %es; movw %ds, %ax; movw %ax, %es");
   if (reg < 0x08)
      insl(channels[channel].base  + reg - 0x00, buffer, quads);
   else if (reg < 0x0C)
      insl(channels[channel].base  + reg - 0x06, buffer, quads);
   else if (reg < 0x0E)
      insl(channels[channel].ctrl  + reg - 0x0A, buffer, quads);
   else if (reg < 0x16)
      insl(channels[channel].bmide + reg - 0x0E, buffer, quads);
   asm("popw %es;");
   if (reg > 0x07 && reg < 0x0C)
      ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
}

When we send a command, we should wait for 400 nanosecond, then read the Status port. If the Busy bit is on, we should read the status port again until the Busy bit is 0; then we can read the results of the command. This operation is called "Polling". We can also use IRQs instead of polling.

After many commands, if the Device Fault bit is set, there is a failure; if DRQ is not set, there is an error. If the ERR bit is set, there is an error which is described in Error port.

unsigned char ide_polling(unsigned char channel, unsigned int advanced_check) {

   // (I) Delay 400 nanosecond for BSY to be set:
   // -------------------------------------------------
   for(int i = 0; i < 4; i++)
      ide_read(channel, ATA_REG_ALTSTATUS); // Reading the Alternate Status port wastes 100ns; loop four times.

   // (II) Wait for BSY to be cleared:
   // -------------------------------------------------
   while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY)
      ; // Wait for BSY to be zero.

   if (advanced_check) {
      unsigned char state = ide_read(channel, ATA_REG_STATUS); // Read Status Register.

      // (III) Check For Errors:
      // -------------------------------------------------
      if (state & ATA_SR_ERR)
         return 2; // Error.

      // (IV) Check If Device fault:
      // -------------------------------------------------
      if (state & ATA_SR_DF)
         return 1; // Device Fault.

      // (V) Check DRQ:
      // -------------------------------------------------
      // BSY = 0; DF = 0; ERR = 0 so we should check for DRQ now.
      if ((state & ATA_SR_DRQ) == 0)
         return 3; // DRQ should be set

   }

   return 0; // No Error.

}

If there is an error, we have a function which prints errors on screen:

unsigned char ide_print_error(unsigned int drive, unsigned char err) {
   if (err == 0)
      return err;

   printk("IDE:");
   if (err == 1) {printk("- Device Fault\n     "); err = 19;}
   else if (err == 2) {
      unsigned char st = ide_read(ide_devices[drive].channel, ATA_REG_ERROR);
      if (st & ATA_ER_AMNF)   {printk("- No Address Mark Found\n     ");   err = 7;}
      if (st & ATA_ER_TK0NF)   {printk("- No Media or Media Error\n     ");   err = 3;}
      if (st & ATA_ER_ABRT)   {printk("- Command Aborted\n     ");      err = 20;}
      if (st & ATA_ER_MCR)   {printk("- No Media or Media Error\n     ");   err = 3;}
      if (st & ATA_ER_IDNF)   {printk("- ID mark not Found\n     ");      err = 21;}
      if (st & ATA_ER_MC)   {printk("- No Media or Media Error\n     ");   err = 3;}
      if (st & ATA_ER_UNC)   {printk("- Uncorrectable Data Error\n     ");   err = 22;}
      if (st & ATA_ER_BBK)   {printk("- Bad Sectors\n     ");       err = 13;}
   } else  if (err == 3)           {printk("- Reads Nothing\n     "); err = 23;}
     else  if (err == 4)  {printk("- Write Protected\n     "); err = 8;}
   printk("- [%s %s] %s\n",
      (const char *[]){"Primary", "Secondary"}[ide_devices[drive].channel], // Use the channel as an index into the array
      (const char *[]){"Master", "Slave"}[ide_devices[drive].drive], // Same as above, using the drive
      ide_devices[drive].model);

   return err;
}

Now let's return to the initialization function:

void ide_initialize(unsigned int BAR0, unsigned int BAR1, unsigned int BAR2, unsigned int BAR3,
unsigned int BAR4) {

   int j, k, count = 0;

   // 1- Detect I/O Ports which interface IDE Controller:
   channels[ATA_PRIMARY  ].base  = (BAR0 & 0xFFFFFFFC) + 0x1F0 * (!BAR0);
   channels[ATA_PRIMARY  ].ctrl  = (BAR1 & 0xFFFFFFFC) + 0x3F6 * (!BAR1);
   channels[ATA_SECONDARY].base  = (BAR2 & 0xFFFFFFFC) + 0x170 * (!BAR2);
   channels[ATA_SECONDARY].ctrl  = (BAR3 & 0xFFFFFFFC) + 0x376 * (!BAR3);
   channels[ATA_PRIMARY  ].bmide = (BAR4 & 0xFFFFFFFC) + 0; // Bus Master IDE
   channels[ATA_SECONDARY].bmide = (BAR4 & 0xFFFFFFFC) + 8; // Bus Master IDE

Then we should disable IRQs in both channels by setting bit 1 (nIEN) in the Control port:

   // 2- Disable IRQs:
   ide_write(ATA_PRIMARY  , ATA_REG_CONTROL, 2);
   ide_write(ATA_SECONDARY, ATA_REG_CONTROL, 2);

Now we need to check for drives which could be connected to each channel. We will select the master drive of each channel, and send the ATA_IDENTIFY command (which is supported by ATA Drives). If there's no error, there are values returned in registers which determine the type of Drive; if no drive is present, there will be strange values.

Notice that if bit 4 in HDDEVSEL is set to 1, we are selecting the slave drive, if set to 0, we are selecting the master drive.

   // 3- Detect ATA-ATAPI Devices:
   for (i = 0; i < 2; i++)
      for (j = 0; j < 2; j++) {

         unsigned char err = 0, type = IDE_ATA, status;
         ide_devices[count].Reserved = 0; // Assuming that no drive here.

         // (I) Select Drive:
         ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j << 4)); // Select Drive.
         sleep(1); // Wait 1ms for drive select to work.

         // (II) Send ATA Identify Command:
         ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY);
         sleep(1); // This function should be implemented in your OS. which waits for 1 ms.
                   // it is based on System Timer Device Driver.

         // (III) Polling:
         if (ide_read(i, ATA_REG_STATUS) == 0) continue; // If Status = 0, No Device.

         while(1) {
            status = ide_read(i, ATA_REG_STATUS);
            if ((status & ATA_SR_ERR)) {err = 1; break;} // If Err, Device is not ATA.
            if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break; // Everything is right.
         }

         // (IV) Probe for ATAPI Devices:

         if (err != 0) {
            unsigned char cl = ide_read(i, ATA_REG_LBA1);
            unsigned char ch = ide_read(i, ATA_REG_LBA2);

            if (cl == 0x14 && ch == 0xEB)
               type = IDE_ATAPI;
            else if (cl == 0x69 && ch == 0x96)
               type = IDE_ATAPI;
            else
               continue; // Unknown Type (may not be a device).

            ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);
            sleep(1);
         }

         // (V) Read Identification Space of the Device:
         ide_read_buffer(i, ATA_REG_DATA, (unsigned int) ide_buf, 128);

         // (VI) Read Device Parameters:
         ide_devices[count].Reserved     = 1;
         ide_devices[count].Type         = type;
         ide_devices[count].Channel      = i;
         ide_devices[count].Drive        = j;
         ide_devices[count].Signature    = *((unsigned short *)(ide_buf + ATA_IDENT_DEVICETYPE));
         ide_devices[count].Capabilities = *((unsigned short *)(ide_buf + ATA_IDENT_CAPABILITIES));
         ide_devices[count].CommandSets  = *((unsigned int *)(ide_buf + ATA_IDENT_COMMANDSETS));

         // (VII) Get Size:
         if (ide_devices[count].CommandSets & (1 << 26))
            // Device uses 48-Bit Addressing:
            ide_devices[count].Size   = *((unsigned int *)(ide_buf + ATA_IDENT_MAX_LBA_EXT));
         else
            // Device uses CHS or 28-bit Addressing:
            ide_devices[count].Size   = *((unsigned int *)(ide_buf + ATA_IDENT_MAX_LBA));

         // (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...):
         for(k = 0; k < 40; k += 2) {
            ide_devices[count].Model[k] = ide_buf[ATA_IDENT_MODEL + k + 1];
            ide_devices[count].Model[k + 1] = ide_buf[ATA_IDENT_MODEL + k];}
         ide_devices[count].Model[40] = 0; // Terminate String.

         count++;
      }

   // 4- Print Summary:
   for (i = 0; i < 4; i++)
      if (ide_devices[i].Reserved == 1) {
         printk(" Found %s Drive %dGB - %s\n",
            (const char *[]){"ATA", "ATAPI"}[ide_devices[i].Type],         /* Type */
            ide_devices[i].Size / 1024 / 1024 / 2,               /* Size */
            ide_devices[i].Model);
      }
}

Read/Write From ATA Drive

Now we're moving to a slightly more advanced part, it is to read and write from/to an ATA drive. There is 3 ways of addressing a sector:

  • CHS (Cylinder-Head-Sector): an old way of addressing sectors in ATA drives, all ATA drives should support this way of addressing.
  • LBA28: Accessing a sector by its 28-bit LBA address. All ATA drives should support this way of addressing, the problem with LBA28 Addressing is that it only allows access 128GB to be accessed, so if the disk is bigger than 128GB, it should support the LBA48 Feature Set.
  • LBA48: Accessing a sector by its 48-bit LBA address. As we use integers in GCC, our maximum address in this tutorial is 32-bit long, which allows accessing a drive with a size of up to 2TB.

So we can conclude an algorithm to determine which type of Addressing we are going to use:

if (No LBA support)
   Use CHS.
else if (the LBA Sector Address > 0x0FFFFFFF)
   Use LBA48.
else
   Use LBA28.

Reading the buffer may be done by polling or DMA. PIO: After sending the command to read or write sectors, we read or write to the Data Port (as words). This is the same way of reading identification space. DMA: After sending the command, you should wait for an IRQ, while you are waiting, Buffer is written directly to memory automatically.

We are going to use PIO as it is less complex.

We can conclude also this table:

   /* ATA/ATAPI Read/Write Modes:
    * ++++++++++++++++++++++++++++++++
    *  Addressing Modes:
    *  ================
    *   - LBA28 Mode.     (+)
    *   - LBA48 Mode.     (+)
    *   - CHS.            (+)
    *  Reading Modes:
    *  ================
    *   - PIO Modes (0 : 6)       (+) // Slower than DMA, but not a problem.
    *   - Single Word DMA Modes (0, 1, 2).
    *   - Double Word DMA Modes (0, 1, 2).
    *   - Ultra DMA Modes (0 : 6).
    *  Polling Modes:
    *  ================
    *   - IRQs
    *   - Polling Status   (+) // Suitable for Singletasking   
    */

There is something needed to be expressed here, I have told before that Task-File is like that:

  • Register 0: [Word] Data Register. (Read-Write).
  • Register 1: [Byte] Error Register. (Read).
  • Register 1: [Byte] Features Register. (Write).
  • Register 2: [Byte] SECCOUNT0 Register. (Read-Write).
  • Register 3: [Byte] LBA0 Register. (Read-Write).
  • Register 4: [Byte] LBA1 Register. (Read-Write).
  • Register 5: [Byte] LBA2 Register. (Read-Write).
  • Register 6: [Byte] HDDEVSEL Register. (Read-Write).
  • Register 7: [Byte] Command Register. (Write).
  • Register 7: [Byte] Status Register. (Read).

So each register between 2 to 5 should be 8-bits long. Really each of them are 16-bits long.

  • Register 2: [Bits 0-7] SECCOUNT0, [Bits 8-15] SECOUNT1
  • Register 3: [Bits 0-7] LBA0, [Bits 8-15] LBA3
  • Register 4: [Bits 0-7] LBA1, [Bits 8-15] LBA4
  • Register 5: [Bits 0-7] LBA2, [Bits 8-15] LBA5

The word [(SECCOUNT1 << 8) | SECCOUNT0] expresses the number of sectors which can be read when you access by LBA48. When you access in CHS or LBA28, SECCOUNT0 only expresses number of sectors.

  • LBA0 makes up bits 0 : 7 of the LBA address when you read in LBA28 or LBA48; it can also be the sector number of CHS.
  • LBA1 makes up bits 8 : 15 of the LBA address when you read in LBA28 or LBA48; it can also be the low byte of the cylinder number of CHS.
  • LBA2 makes up bits 16 : 23 of the LBA address when you read in LBA28 or LBA48; it can also be the high byte of the cylinder number of CHS.
  • LBA3 makes up bits 24 : 31 of the LBA48 address.
  • LBA4 makes up bits 32 : 39 of the LBA48 address.
  • LBA5 makes up bits 40 : 47 of LBA48 address.

Notice that the LBA0, 1 and 2 registers are 24 bits long in total, which is not enough for LBA28; the higher 4-bits can be written to the lower 4-bits of the HDDEVSEL register.

Also notice that if bit 6 of this register is set, we are going to use LBA, if not, we are going to use CHS. There is a mode which is called extended CHS.

Lets go into the code:

unsigned char ide_ata_access(unsigned char direction, unsigned char drive, unsigned int lba, 
                             unsigned char numsects, unsigned short selector, unsigned int edi) {

This function reads/writes sectors from ATA-Drive. If direction is 0 we are reading, else we are writing.

  • drive is the drive number which can be from 0 to 3.
  • lba is the LBA address which allows us to access disks up to 2TB.
  • numsects is the number of sectors to be read, it is a char, as reading more than 256 sector immediately may performance issues. If numsects is 0, the ATA controller will know that we want 256 sectors.
  • selector is the segment selector to read from, or write to.
  • edi is the offset in that segment. (the memory address for the data buffer)
   unsigned char lba_mode /* 0: CHS, 1:LBA28, 2: LBA48 */, dma /* 0: No DMA, 1: DMA */, cmd;
   unsigned char lba_io[6];
   unsigned int  channel      = ide_devices[drive].Channel; // Read the Channel.
   unsigned int  slavebit      = ide_devices[drive].Drive; // Read the Drive [Master/Slave]
   unsigned int  bus = channels[channel].Base; // Bus Base, like 0x1F0 which is also data port.
   unsigned int  words      = 256; // Almost every ATA drive has a sector-size of 512-byte.
   unsigned short cyl, i;
   unsigned char head, sect, err;

We don't need IRQs, so we should disable it to prevent problems from happening. We said before that if bit 1 of the Control Register (which is called nIEN bit), is set, no IRQs will be invoked from any drives on this channel, either master or slave.

ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = (ide_irq_invoked = 0x0) + 0x02);

Now lets read the parameters:

   // (I) Select one from LBA28, LBA48 or CHS;
   if (lba >= 0x10000000) { // Sure Drive should support LBA in this case, or you are
                            // giving a wrong LBA.
      // LBA48:
      lba_mode  = 2;
      lba_io[0] = (lba & 0x000000FF) >> 0;
      lba_io[1] = (lba & 0x0000FF00) >> 8;
      lba_io[2] = (lba & 0x00FF0000) >> 16;
      lba_io[3] = (lba & 0xFF000000) >> 24;
      lba_io[4] = 0; // LBA28 is integer, so 32-bits are enough to access 2TB.
      lba_io[5] = 0; // LBA28 is integer, so 32-bits are enough to access 2TB.
      head      = 0; // Lower 4-bits of HDDEVSEL are not used here.
   } else if (ide_devices[drive].Capabilities & 0x200)  { // Drive supports LBA?
      // LBA28:
      lba_mode  = 1;
      lba_io[0] = (lba & 0x00000FF) >> 0;
      lba_io[1] = (lba & 0x000FF00) >> 8;
      lba_io[2] = (lba & 0x0FF0000) >> 16;
      lba_io[3] = 0; // These Registers are not used here.
      lba_io[4] = 0; // These Registers are not used here.
      lba_io[5] = 0; // These Registers are not used here.
      head      = (lba & 0xF000000) >> 24;
   } else {
      // CHS:
      lba_mode  = 0;
      sect      = (lba % 63) + 1;
      cyl       = (lba + 1  - sect) / (16 * 63);
      lba_io[0] = sect;
      lba_io[1] = (cyl >> 0) & 0xFF;
      lba_io[2] = (cyl >> 8) & 0xFF;
      lba_io[3] = 0;
      lba_io[4] = 0;
      lba_io[5] = 0;
      head      = (lba + 1  - sect) % (16 * 63) / (63); // Head number is written to HDDEVSEL lower 4-bits.
   }

Now we are going to choose the way of reading the buffer [PIO or DMA]:

   // (II) See if drive supports DMA or not;
   dma = 0; // We don't support DMA

Lets poll the Status port while the channel is busy:

   // (III) Wait if the drive is busy;
   while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY){   
   
   } // Wait if busy.

The HDDDEVSEL register now looks like this:

  • Bits 0 : 3: Head Number for CHS.
  • Bit 4: Slave Bit. (0: Selecting Master Drive, 1: Selecting Slave Drive).
  • Bit 5: Obsolete and isn't used, but should be set.
  • Bit 6: LBA (0: CHS, 1: LBA).
  • Bit 7: Obsolete and isn't used, but should be set.

Lets write all these information to the register, while the obsolete bits are set (0xA0):

   // (IV) Select Drive from the controller;
   if (lba_mode == 0)
      ide_write(channel, ATA_REG_HDDEVSEL, 0xA0 | (slavebit << 4) | head); // Drive & CHS.
   else
      ide_write(channel, ATA_REG_HDDEVSEL, 0xE0 | (slavebit << 4) | head); // Drive & LBA

Let's write the parameters to registers:

   // (V) Write Parameters;
   if (lba_mode == 2) {
      ide_write(channel, ATA_REG_SECCOUNT1,   0);
      ide_write(channel, ATA_REG_LBA3,   lba_io[3]);
      ide_write(channel, ATA_REG_LBA4,   lba_io[4]);
      ide_write(channel, ATA_REG_LBA5,   lba_io[5]);
   }
   ide_write(channel, ATA_REG_SECCOUNT0,   numsects);
   ide_write(channel, ATA_REG_LBA0,   lba_io[0]);
   ide_write(channel, ATA_REG_LBA1,   lba_io[1]);
   ide_write(channel, ATA_REG_LBA2,   lba_io[2]);

If you are using LBA48 and want to write to the LBA0 and LBA3 registers, you should write LBA3 to Register 3, then write LBA0 to Register 3. ide_write function makes it quite simple, refer to the function and you will fully understand the code.

Now, we have a great set of commands described in ATA/ATAPI-8 Specification, we should choose the suitable command to execute:

   // (VI) Select the command and send it;
   // Routine that is followed:
   // If ( DMA & LBA48)   DO_DMA_EXT;
   // If ( DMA & LBA28)   DO_DMA_LBA;
   // If ( DMA & LBA28)   DO_DMA_CHS;
   // If (!DMA & LBA48)   DO_PIO_EXT;
   // If (!DMA & LBA28)   DO_PIO_LBA;
   // If (!DMA & !LBA#)   DO_PIO_CHS;

There isn't a command for doing CHS with DMA.

   if (lba_mode == 0 && dma == 0 && direction == 0) cmd = ATA_CMD_READ_PIO;
   if (lba_mode == 1 && dma == 0 && direction == 0) cmd = ATA_CMD_READ_PIO;   
   if (lba_mode == 2 && dma == 0 && direction == 0) cmd = ATA_CMD_READ_PIO_EXT;   
   if (lba_mode == 0 && dma == 1 && direction == 0) cmd = ATA_CMD_READ_DMA;
   if (lba_mode == 1 && dma == 1 && direction == 0) cmd = ATA_CMD_READ_DMA;
   if (lba_mode == 2 && dma == 1 && direction == 0) cmd = ATA_CMD_READ_DMA_EXT;
   if (lba_mode == 0 && dma == 0 && direction == 1) cmd = ATA_CMD_WRITE_PIO;
   if (lba_mode == 1 && dma == 0 && direction == 1) cmd = ATA_CMD_WRITE_PIO;
   if (lba_mode == 2 && dma == 0 && direction == 1) cmd = ATA_CMD_WRITE_PIO_EXT;
   if (lba_mode == 0 && dma == 1 && direction == 1) cmd = ATA_CMD_WRITE_DMA;
   if (lba_mode == 1 && dma == 1 && direction == 1) cmd = ATA_CMD_WRITE_DMA;
   if (lba_mode == 2 && dma == 1 && direction == 1) cmd = ATA_CMD_WRITE_DMA_EXT;
   ide_write(channel, ATA_REG_COMMAND, cmd);               // Send the Command.

This ATA_CMD_READ_PIO command is used for reading in LBA28 or CHS, and the IDE controller refers to bit 6 of the HDDEVSEL register to find out the mode of reading (LBA or CHS).

After sending the command, we should poll, then we read/write a sector, then we should poll, then we read/write a sector, until we read/write all sectors needed, if an error has happened, the function will return a specific error code.

Notice that after writing, we should execute the CACHE FLUSH command, and we should poll after it, but without checking for errors.

   if (dma)
      if (direction == 0);
         // DMA Read.
      else;
         // DMA Write.
   else
      if (direction == 0)
         // PIO Read.
      for (i = 0; i < numsects; i++) {
         if (err = ide_polling(channel, 1))
            return err; // Polling, set error and exit if there is.
         asm("pushw %es");
         asm("mov %%ax, %%es" : : "a"(selector));
         asm("rep insw" : : "c"(words), "d"(bus), "D"(edi)); // Receive Data.
         asm("popw %es");
         edi += (words*2);
      } else {
      // PIO Write.
         for (i = 0; i < numsects; i++) {
            ide_polling(channel, 0); // Polling.
            asm("pushw %ds");
            asm("mov %%ax, %%ds"::"a"(selector));
            asm("rep outsw"::"c"(words), "d"(bus), "S"(edi)); // Send Data
            asm("popw %ds");
            edi += (words*2);
         }
         ide_write(channel, ATA_REG_COMMAND, (char []) {   ATA_CMD_CACHE_FLUSH,
                        ATA_CMD_CACHE_FLUSH,
                        ATA_CMD_CACHE_FLUSH_EXT}[lba_mode]);
         ide_polling(channel, 0); // Polling.
      }

   return 0; // Easy, isn't it?
}

Reading from an ATAPI Drive

Let's move to an easier part - reading from an ATAPI drive. I will not make the function that writes to an ATAPI drive, because writing to it is very complex and is outside of the scope of this tutorial.

An ATAPI drive is different from an ATA drive, as it uses the SCSI command set instead of the ATA command set. Parameters are sent as packets, therefore it's called the ATA Packet Interface [ATAPI].

Notice also that ATAPI drives always use IRQs and you can't disable them. We should create a function that waits for an IRQ:

void ide_wait_irq() {
   while (!ide_irq_invoked)
      ;
   ide_irq_invoked = 0;
}

When an IRQ happens, the following function should be executed by ISR:

void ide_irq() {
   ide_irq_invoked = 1;
}

ide_wait_irq will go into a while loop, which waits for the variable ide_irq_invoked to be set, then clears it.

unsigned char ide_atapi_read(unsigned char drive, unsigned int lba, unsigned char numsects,
          unsigned short selector, unsigned int edi) {
  • drive is the drive number, which is from 0 to 3.
  • lba is the LBA address.
  • numsects is the number of sectors. It should always be 1, and if you want to read more than one sector, re-execute this function with th updated LBA address.
  • selector is the Segment Selector.
  • edi is the offset in the selector.

Let's read the parameters of the drive:

   unsigned int   channel  = ide_devices[drive].Channel;
   unsigned int   slavebit = ide_devices[drive].Drive;
   unsigned int   bus      = channels[channel].Base;
   unsigned int   words    = 1024; // Sector Size. ATAPI drives have a sector size of 2048 bytes.
   unsigned char  err;
   int i;

We need IRQs:

   // Enable IRQs:
   ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);

Let's setup the SCSI Packet, which is 6 words (12 bytes) long:

   // (I): Setup SCSI Packet:
   // ------------------------------------------------------------------
   atapi_packet[ 0] = ATAPI_CMD_READ;
   atapi_packet[ 1] = 0x0;
   atapi_packet[ 2] = (lba >> 24) & 0xFF;
   atapi_packet[ 3] = (lba >> 16) & 0xFF;
   atapi_packet[ 4] = (lba >> 8) & 0xFF;
   atapi_packet[ 5] = (lba >> 0) & 0xFF;
   atapi_packet[ 6] = 0x0;
   atapi_packet[ 7] = 0x0;
   atapi_packet[ 8] = 0x0;
   atapi_packet[ 9] = numsects;
   atapi_packet[10] = 0x0;
   atapi_packet[11] = 0x0;

Now we should select the drive:

   // (II): Select the drive:
   // ------------------------------------------------------------------
   ide_write(channel, ATA_REG_HDDEVSEL, slavebit << 4);

400 nanoseconds delay after this select is a good idea:

   // (III): Delay 400 nanoseconds for select to complete:
   // ------------------------------------------------------------------
   for(int i = 0; i < 4; i++)
       ide_read(channel, ATA_REG_ALTSTATUS); // Reading the Alternate Status port wastes 100ns.
   // (IV): Inform the Controller that we use PIO mode:
   // ------------------------------------------------------------------
   ide_write(channel, ATA_REG_FEATURES, 0);         // PIO mode.

Tell the controller the size of the buffer

   // (V): Tell the Controller the size of buffer:
   // ------------------------------------------------------------------
   ide_write(channel, ATA_REG_LBA1, (words * 2) & 0xFF);   // Lower Byte of Sector Size.
   ide_write(channel, ATA_REG_LBA2, (words * 2) >> 8);   // Upper Byte of Sector Size.

Now that we want to send the packet, we should first send the command "Packet":

   // (VI): Send the Packet Command:
   // ------------------------------------------------------------------
   ide_write(channel, ATA_REG_COMMAND, ATA_CMD_PACKET);      // Send the Command.

   // (VII): Waiting for the driver to finish or return an error code:
   // ------------------------------------------------------------------
   if (err = ide_polling(channel, 1)) return err;         // Polling and return if error.

   // (VIII): Sending the packet data:
   // ------------------------------------------------------------------
   asm("rep   outsw" : : "c"(6), "d"(bus), "S"(atapi_packet));   // Send Packet Data

Here we cannot poll. We should wait for an IRQ, then read the sectors. These two operations should be repeated for each sector.

   // (IX): Receiving Data:
   // ------------------------------------------------------------------
   for (i = 0; i < numsects; i++) {
      ide_wait_irq();                  // Wait for an IRQ.
      if (err = ide_polling(channel, 1))
         return err;      // Polling and return if error.
      asm("pushw %es");
      asm("mov %%ax, %%es"::"a"(selector));
      asm("rep insw"::"c"(words), "d"(bus), "D"(edi));// Receive Data.
      asm("popw %es");
      edi += (words * 2);
   }

Now we should wait for an IRQ and poll until the Busy and DRQ bits are clear:

   // (X): Waiting for an IRQ:
   // ------------------------------------------------------------------
   ide_wait_irq();

   // (XI): Waiting for BSY & DRQ to clear:
   // ------------------------------------------------------------------
   while (ide_read(channel, ATA_REG_STATUS) & (ATA_SR_BSY | ATA_SR_DRQ))
      ;

   return 0; // Easy, ... Isn't it?
}

Reading from an ATA/ATAPI Drive

void ide_read_sectors(unsigned char drive, unsigned char numsects, unsigned int lba,
                      unsigned short es, unsigned int edi) {

   // 1: Check if the drive presents:
   // ==================================
   if (drive > 3 || ide_devices[drive].Reserved == 0) package[0] = 0x1;      // Drive Not Found!

   // 2: Check if inputs are valid:
   // ==================================
   else if (((lba + numsects) > ide_devices[drive].Size) && (ide_devices[drive].Type == IDE_ATA))
      package[0] = 0x2;                     // Seeking to invalid position.

   // 3: Read in PIO Mode through Polling & IRQs:
   // ============================================
   else {
      unsigned char err;
      if (ide_devices[drive].Type == IDE_ATA)
         err = ide_ata_access(ATA_READ, drive, lba, numsects, es, edi);
      else if (ide_devices[drive].Type == IDE_ATAPI)
         for (i = 0; i < numsects; i++)
            err = ide_atapi_read(drive, lba + i, 1, es, edi + (i*2048));
      package[0] = ide_print_error(drive, err);
   }
}
// package[0] is an entry of an array. It contains the Error Code.

Writing to an ATA drive

void ide_write_sectors(unsigned char drive, unsigned char numsects, unsigned int lba,
                       unsigned short es, unsigned int edi) {

   // 1: Check if the drive presents:
   // ==================================
   if (drive > 3 || ide_devices[drive].Reserved == 0)
      package[0] = 0x1;      // Drive Not Found!
   // 2: Check if inputs are valid:
   // ==================================
   else if (((lba + numsects) > ide_devices[drive].Size) && (ide_devices[drive].Type == IDE_ATA))
      package[0] = 0x2;                     // Seeking to invalid position.
   // 3: Read in PIO Mode through Polling & IRQs:
   // ============================================
   else {
      unsigned char err;
      if (ide_devices[drive].Type == IDE_ATA)
         err = ide_ata_access(ATA_WRITE, drive, lba, numsects, es, edi);
      else if (ide_devices[drive].Type == IDE_ATAPI)
         err = 4; // Write-Protected.
      package[0] = ide_print_error(drive, err);
   }
}

Ejecting an ATAPI Drive

void ide_atapi_eject(unsigned char drive) {
   unsigned int   channel      = ide_devices[drive].Channel;
   unsigned int   slavebit      = ide_devices[drive].Drive;
   unsigned int   bus      = channels[channel].Base;
   unsigned int   words      = 2048 / 2;               // Sector Size in Words.
   unsigned char  err = 0;
   ide_irq_invoked = 0;

   // 1: Check if the drive presents:
   // ==================================
   if (drive > 3 || ide_devices[drive].Reserved == 0)
      package[0] = 0x1;      // Drive Not Found!
   // 2: Check if drive isn't ATAPI:
   // ==================================
   else if (ide_devices[drive].Type == IDE_ATA)
      package[0] = 20;         // Command Aborted.
   // 3: Eject ATAPI Driver:
   // ============================================
   else {
      // Enable IRQs:
      ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);

      // (I): Setup SCSI Packet:
      // ------------------------------------------------------------------
      atapi_packet[ 0] = ATAPI_CMD_EJECT;
      atapi_packet[ 1] = 0x00;
      atapi_packet[ 2] = 0x00;
      atapi_packet[ 3] = 0x00;
      atapi_packet[ 4] = 0x02;
      atapi_packet[ 5] = 0x00;
      atapi_packet[ 6] = 0x00;
      atapi_packet[ 7] = 0x00;
      atapi_packet[ 8] = 0x00;
      atapi_packet[ 9] = 0x00;
      atapi_packet[10] = 0x00;
      atapi_packet[11] = 0x00;

      // (II): Select the Drive:
      // ------------------------------------------------------------------
      ide_write(channel, ATA_REG_HDDEVSEL, slavebit << 4);

      // (III): Delay 400 nanosecond for select to complete:
      // ------------------------------------------------------------------
      for(int i = 0; i < 4; i++)
         ide_read(channel, ATA_REG_ALTSTATUS); // Reading Alternate Status Port wastes 100ns.

      // (IV): Send the Packet Command:
      // ------------------------------------------------------------------
      ide_write(channel, ATA_REG_COMMAND, ATA_CMD_PACKET);      // Send the Command.

      // (V): Waiting for the driver to finish or invoke an error:
      // ------------------------------------------------------------------
      err = ide_polling(channel, 1);            // Polling and stop if error.

      // (VI): Sending the packet data:
      // ------------------------------------------------------------------
      else {
         asm("rep   outsw"::"c"(6), "d"(bus), "S"(atapi_packet));// Send Packet Data
         ide_wait_irq();                  // Wait for an IRQ.
         err = ide_polling(channel, 1);            // Polling and get error code.
         if (err == 3) err = 0; // DRQ is not needed here.
      }
      package[0] = ide_print_error(drive, err); // Return;
   }
}

When this method is invoked, the optical device on the given channel is ejected.

See Also

Wiki Pages

Threads

External Links