PCI IDE Controller: Difference between revisions

Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
m bugged me
m Bot: Replace deprecated source tag with syntaxhighlight
Line 60: Line 60:


To initialise the IDE driver, we call ide_initialise:
To initialise the IDE driver, we call ide_initialise:
<source lang="c">
<syntaxhighlight 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:
If you only want to support the parallel IDE, you can use these parameters:
<source lang="c">
<syntaxhighlight lang="c">
ide_initialize(0x1F0, 0x3F6, 0x170, 0x376, 0x000);
ide_initialize(0x1F0, 0x3F6, 0x170, 0x376, 0x000);
</syntaxhighlight>
</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.
Line 77: Line 77:
The Command/Status Port returns a bit mask referring to the status of a channel when read.
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 // Busy
#define ATA_SR_BSY 0x80 // Busy
#define ATA_SR_DRDY 0x40 // Drive ready
#define ATA_SR_DRDY 0x40 // Drive ready
Line 86: Line 86:
#define ATA_SR_IDX 0x02 // Index
#define ATA_SR_IDX 0x02 // Index
#define ATA_SR_ERR 0x01 // Error
#define ATA_SR_ERR 0x01 // Error
</syntaxhighlight>
</source>


=== Errors ===
=== Errors ===
Line 92: Line 92:
The Features/Error Port, which returns the most recent error upon read, has these possible bit masks
The Features/Error Port, which returns the most recent error upon read, has these possible bit masks


<source lang="c">
<syntaxhighlight lang="c">
#define ATA_ER_BBK 0x80 // Bad block
#define ATA_ER_BBK 0x80 // Bad block
#define ATA_ER_UNC 0x40 // Uncorrectable data
#define ATA_ER_UNC 0x40 // Uncorrectable data
Line 101: Line 101:
#define ATA_ER_TK0NF 0x02 // Track 0 not found
#define ATA_ER_TK0NF 0x02 // Track 0 not found
#define ATA_ER_AMNF 0x01 // No address mark
#define ATA_ER_AMNF 0x01 // No address mark
</syntaxhighlight>
</source>


=== Commands ===
=== Commands ===
Line 107: Line 107:
When you write to the Command/Status port, you are executing one of the commands below.
When you write to the Command/Status port, you are executing one of the commands below.


<source lang="c">
<syntaxhighlight lang="c">
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_READ_PIO_EXT 0x24
#define ATA_CMD_READ_PIO_EXT 0x24
Line 121: Line 121:
#define ATA_CMD_IDENTIFY_PACKET 0xA1
#define ATA_CMD_IDENTIFY_PACKET 0xA1
#define ATA_CMD_IDENTIFY 0xEC
#define ATA_CMD_IDENTIFY 0xEC
</syntaxhighlight>
</source>


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


<source lang="c">
<syntaxhighlight 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.
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.
<source lang="c">
<syntaxhighlight lang="c">
#define ATA_IDENT_DEVICETYPE 0
#define ATA_IDENT_DEVICETYPE 0
#define ATA_IDENT_CYLINDERS 2
#define ATA_IDENT_CYLINDERS 2
Line 143: Line 143:
#define ATA_IDENT_COMMANDSETS 164
#define ATA_IDENT_COMMANDSETS 164
#define ATA_IDENT_MAX_LBA_EXT 200
#define ATA_IDENT_MAX_LBA_EXT 200
</syntaxhighlight>
</source>


When you select a drive, you should specify the interface type and whether it is the master or slave:
When you select a drive, you should specify the interface type and whether it is the master or slave:
<source lang="c">
<syntaxhighlight lang="c">
#define IDE_ATA 0x00
#define IDE_ATA 0x00
#define IDE_ATAPI 0x01
#define IDE_ATAPI 0x01
Line 152: Line 152:
#define ATA_MASTER 0x00
#define ATA_MASTER 0x00
#define ATA_SLAVE 0x01
#define ATA_SLAVE 0x01
</syntaxhighlight>
</source>


Task File is a range of 8 ports which are offsets from BAR0 (primary channel) and/or BAR2 (secondary channel). To exemplify:
Task File is a range of 8 ports which are offsets from BAR0 (primary channel) and/or BAR2 (secondary channel). To exemplify:
Line 158: Line 158:
* BAR0 + 1 is second port.
* BAR0 + 1 is second port.
* BAR0 + 2 is the third
* BAR0 + 2 is the third
<source lang="c">
<syntaxhighlight lang="c">
#define ATA_REG_DATA 0x00
#define ATA_REG_DATA 0x00
#define ATA_REG_ERROR 0x01
#define ATA_REG_ERROR 0x01
Line 176: Line 176:
#define ATA_REG_ALTSTATUS 0x0C
#define ATA_REG_ALTSTATUS 0x0C
#define ATA_REG_DEVADDRESS 0x0D
#define ATA_REG_DEVADDRESS 0x0D
</syntaxhighlight>
</source>


The ALTSTATUS/CONTROL port returns the alternate status when read and controls a channel when written to.
The ALTSTATUS/CONTROL port returns the alternate status when read and controls a channel when written to.
Line 199: Line 199:
The map above is the same with the secondary channel, but it uses BAR2 and BAR3 instead of BAR0 and BAR1.
The map above is the same with the secondary channel, but it uses BAR2 and BAR3 instead of BAR0 and BAR1.


<source lang="c">
<syntaxhighlight lang="c">
// Channels:
// Channels:
#define ATA_PRIMARY 0x00
#define ATA_PRIMARY 0x00
Line 207: Line 207:
#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
We have defined everything needed by the driver, now lets move to an important part. We said that
Line 218: Line 218:


So we can make this global structure:
So we can make this global structure:
<source lang="c">
<syntaxhighlight lang="c">
struct IDEChannelRegisters {
struct IDEChannelRegisters {
unsigned short base; // I/O Base.
unsigned short base; // I/O Base.
Line 225: Line 225:
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:
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:


<source lang="c">
<syntaxhighlight lang="c">
unsigned char ide_buf[2048] = {0};
unsigned char ide_buf[2048] = {0};
volatile 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:


<source lang="c">
<syntaxhighlight 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).
Line 249: Line 249:
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:
Line 362: Line 362:


If there is an error, we have a function which prints errors on screen:
If there is an error, we have a function which prints errors on screen:
<source lang="c">
<syntaxhighlight 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)
Line 388: Line 388:
return err;
return err;
}
}
</syntaxhighlight>
</source>


Now let's return to the initialization function:
Now let's return to the initialization function:
<source lang="c">
<syntaxhighlight 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 404: Line 404:
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>
</source>
Then we should disable IRQs in both channels by setting bit 1 (nIEN) in the Control port:
Then we should disable IRQs in both channels by setting bit 1 (nIEN) in the Control port:


<source lang="c">
<syntaxhighlight lang="c">
// 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 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.
Line 526: Line 526:
We can conclude also this table:
We can conclude also this table:


<source lang="c">
<syntaxhighlight lang="c">
/* ATA/ATAPI Read/Write Modes:
/* ATA/ATAPI Read/Write Modes:
* ++++++++++++++++++++++++++++++++
* ++++++++++++++++++++++++++++++++
Line 545: Line 545:
* - 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:
Line 582: Line 582:
Lets go into the code:
Lets go into the code:


<source lang="c">
<syntaxhighlight 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.
This function reads/writes sectors from ATA-Drive. If direction is 0 we are reading, else we are writing.
Line 594: Line 594:
* edi is the offset in that segment. (the memory address for the data buffer)
* edi is the offset in that segment. (the memory address for the data buffer)


<source lang="c">
<syntaxhighlight 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];
Line 603: Line 603:
unsigned short cyl, i;
unsigned short cyl, i;
unsigned char head, sect, err;
unsigned char head, sect, err;
</syntaxhighlight>
</source>


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.
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.


<source lang="c">
<syntaxhighlight 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:


<source lang="c">
<syntaxhighlight 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 649: Line 649:
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]:


<source lang="c">
<syntaxhighlight lang="c">
// (II) See if drive supports DMA or not;
// (II) See if drive supports DMA or not;
dma = 0; // We don't support DMA
dma = 0; // We don't support DMA
</syntaxhighlight>
</source>


Lets poll the Status port while the channel is busy:
Lets poll the Status port while the channel is busy:


<source lang="c">
<syntaxhighlight 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){
while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY){
} // Wait if busy.
} // Wait if busy.
</syntaxhighlight>
</source>


The HDDDEVSEL register now looks like this:
The HDDDEVSEL register now looks like this:
Line 687: Line 687:
Let's write the parameters to registers:
Let's write the parameters to registers:


<source lang="c">
<syntaxhighlight lang="c">
// (V) Write Parameters;
// (V) Write Parameters;
if (lba_mode == 2) {
if (lba_mode == 2) {
Line 699: Line 699:
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.
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.
Line 705: Line 705:
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:


<source lang="c">
<syntaxhighlight 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 714: Line 714:
// 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.
There isn't a command for doing CHS with DMA.


<source lang="c">
<syntaxhighlight 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 732: Line 732:
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).
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).
Line 784: Line 784:
Notice also that ATAPI drives always use IRQs and you can't disable them. We should create a function that waits for an IRQ:
Notice also that ATAPI drives always use IRQs and you can't disable them. We should create a function that waits for an IRQ:


<source lang="c">
<syntaxhighlight lang="c">
void ide_wait_irq() {
void ide_wait_irq() {
while (!ide_irq_invoked)
while (!ide_irq_invoked)
Line 790: Line 790:
ide_irq_invoked = 0;
ide_irq_invoked = 0;
}
}
</syntaxhighlight>
</source>


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


<source lang="c">
<syntaxhighlight 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.
ide_wait_irq will go into a while loop, which waits for the variable ide_irq_invoked to be set, then clears it.


<source lang="c">
<syntaxhighlight 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.
Line 815: Line 815:
Let's read the parameters of the drive:
Let's read the parameters of the drive:


<source lang="c">
<syntaxhighlight lang="c">
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;
Line 822: Line 822:
unsigned char err;
unsigned char err;
int i;
int i;
</syntaxhighlight>
</source>


We need IRQs:
We need IRQs:


<source lang="c">
<syntaxhighlight 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:
Let's setup the SCSI Packet, which is 6 words (12 bytes) long:


<source lang="c">
<syntaxhighlight lang="c">
// (I): Setup SCSI Packet:
// (I): Setup SCSI Packet:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
Line 848: Line 848:
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:
Line 867: Line 867:
</source>
</source>


<source lang="c">
<syntaxhighlight 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
Tell the controller the size of the buffer


<source lang="c">
<syntaxhighlight 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":
Now that we want to send the packet, we should first send the command "Packet":


<source lang="c">
<syntaxhighlight lang="c">
// (VI): Send the Packet Command:
// (VI): Send the Packet Command:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
Line 896: Line 896:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
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.
Here we cannot poll. We should wait for an IRQ, then read the sectors. These two operations should be repeated for each sector.
Line 917: Line 917:
Now we should wait for an IRQ and poll until the Busy and DRQ bits are clear:
Now we should wait for an IRQ and poll until the Busy and DRQ bits are clear:


<source lang="c">
<syntaxhighlight lang="c">
// (X): Waiting for an IRQ:
// (X): Waiting for an IRQ:
// ------------------------------------------------------------------
// ------------------------------------------------------------------
Line 929: Line 929:
return 0; // Easy, ... Isn't it?
return 0; // Easy, ... Isn't it?
}
}
</syntaxhighlight>
</source>


== Reading from an ATA/ATAPI Drive ==
== Reading from an ATA/ATAPI Drive ==
Line 961: Line 961:


== Writing to an ATA drive ==
== Writing to an ATA drive ==
<source lang="c">
<syntaxhighlight 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 984: Line 984:
}
}
}
}
</syntaxhighlight>
</source>


== Ejecting an ATAPI Drive ==
== Ejecting an ATAPI Drive ==