PCI IDE Controller: Difference between revisions
[unchecked revision] | [unchecked revision] |
Content deleted Content added
m Large cleanup, needs grammar and technical checks. Part one |
m Cleanup, part 2 |
||
Line 64:
// 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
}
}
Line 84:
We can assume that BAR4 is 0x0 because we are not going to use it yet.
We will return to ide_initialize
<source lang="c">
#define
#define
#define
#define
#define
#define
#define
#define
</source>
<source lang="c">
#define
#define
#define
#define
#define
#define
#define
#define
</source>
<source lang="c">
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
</source>
When you write to the Command/Status port, you are executing one of the commands above.
Line 131:
#define ATAPI_CMD_EJECT 0x1B
</source>
The commands above are for ATAPI devices, which will be understood soon.
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">
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
</source>
When you select a drive, you should specify if it is the master drive or the slave one:
<source lang="c">
#define
#define
</source>
<source lang="c">
#define
#define
</source>
<source lang="c">
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
</source>
Task File is a range of
* BAR0 + 0 is first port.
* BAR0 + 1 is second port.
* BAR0 +
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 + 2; // 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.
<source lang="c">
Line 228 ⟶ 218:
</source>
We have
* BAR0 is the
* BAR1 is the
* BAR2 is the
* BAR3 is the
* BAR4 is the
* BAR4 + 8 is the Base of 8 I/O
So we can make this global structure:
<source lang="c">
struct
unsigned short base; // I/O Base.
unsigned short ctrl; // Control Base
Line 247 ⟶ 236:
</source>
We also need a buffer to read the identification space
<source lang="c">
Line 259 ⟶ 248:
<source lang="c">
struct ide_device {
unsigned char
unsigned char
unsigned char
unsigned short
unsigned short
unsigned short
unsigned int
unsigned int
unsigned char
} ide_devices[4];
</source>
Line 273 ⟶ 262:
When we read a register in a channel, like STATUS Register, it is easy to execute:
<source lang="c">
ide_read(channel, ATA_REG_STATUS);
unsigned char ide_read(unsigned char channel, unsigned char reg) {
unsigned char result;
if
if (reg < 0x08)
else if
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;
}
</source>
<source lang="c">
void ide_write(unsigned char channel, unsigned char reg, unsigned char data) {
if
if (reg < 0x08)
else if
outb(data, channels[channel].base + reg - 0x06);
else if (reg < 0x0E)
outb(data, channels[channel].ctrl + reg - 0x0A);
else if (reg < 0x16)
outb(data, channels[channel].bmide + reg - 0x0E);
if (reg > 0x07 && reg < 0x0C)
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
}
</source>
<source lang="c">
void ide_read_buffer(unsigned char channel, unsigned char reg, unsigned int buffer,
unsigned int quads) {
if
ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN);
asm("pushw %es; movw %ds, %ax; movw %ax, %es");
if
else if
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
ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN);
}
</source>
When we send a command, we should wait for 400 nanosecond, then
After
<source lang="c">
Line 327 ⟶ 332:
// (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
// (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 (
return 3; // DRQ should be set
}
Line 360 ⟶ 366:
</source>
<source lang="c">
unsigned char ide_print_error(unsigned int drive, unsigned char err) {
if (err == 0)
printk("
if (err == 1) {printk("- Device Fault\n "); err = 19;}
else if (err == 2) {
Line 382 ⟶ 387:
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);
Line 390 ⟶ 395:
</source>
Now
<source lang="c">
void ide_initialize(unsigned int BAR0, unsigned int BAR1, unsigned int BAR2, unsigned int BAR3,
Line 399 ⟶ 403:
// 1- Detect I/O Ports which interface IDE Controller:
channels[ATA_PRIMARY ].base = (BAR0 &
channels[ATA_PRIMARY ].ctrl = (BAR1 &
channels[ATA_SECONDARY].base = (BAR2 &
channels[ATA_SECONDARY].ctrl = (BAR3 &
channels[ATA_PRIMARY ].bmide = (BAR4 &
channels[ATA_SECONDARY].bmide = (BAR4 &
</source>
Then we should disable IRQs in both channels by setting bit 1 [nIEN] in Control Port:
<source lang="c">
// 2- Disable IRQs:
ide_write(ATA_PRIMARY , ATA_REG_CONTROL, 2);
Line 416 ⟶ 418:
</source>
Now we need to check for drives which could be connected to each channel
Notice that
<source lang="c">
Line 426 ⟶ 428:
unsigned char err = 0, type = IDE_ATA, status;
ide_devices[count].
// (I) Select Drive:
ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j << 4)); // Select Drive.
sleep(1); // Wait 1ms for drive select to work.
Line 438 ⟶ 440:
// (III) Polling:
if
while(1) {
status = ide_read(i, ATA_REG_STATUS);
if (
if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break; // Everything is right.
}
Line 448 ⟶ 450:
// (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
else
type = IDE_ATAPI;
else
continue; // Unknown Type (may not be a device).
ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);
Line 464 ⟶ 469:
// (VI) Read Device Parameters:
ide_devices[count].
ide_devices[count].
ide_devices[count].
ide_devices[count].
ide_devices[count].
ide_devices[count].
ide_devices[count].
// (VII) Get Size:
if (ide_devices[count].
// Device uses 48-Bit Addressing:
ide_devices[count].
else
// Device uses CHS or 28-bit Addressing:
ide_devices[count].
// (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...):
for(k =
ide_devices[count].
ide_devices[count].
ide_devices[count].
count++;
Line 493 ⟶ 496:
// 4- Print Summary:
for (i = 0; i < 4; i++)
if (ide_devices[i].
printk(" Found %s Drive %dGB - %s\n",
(const char *[]){"ATA", "ATAPI"}[ide_devices[i].
ide_devices[i].
ide_devices[i].
}
}
Line 503 ⟶ 506:
===Read/Write From ATA Drive===
Now we
There is 3 ways of addressing a sector:
* CHS (Cylinder-Head-Sector): an old way of addressing sectors in ATA drives,
* LBA28: Accessing a sector by its
* LBA48: Accessing a sector by its
So We can conclude an algorithm to determine which type of Addressing we are going to use:
Line 515 ⟶ 518:
// Use CHS.
else (if the LBA Sector Address > 0x0FFFFFFF)
//
else
// Use LBA28.
Line 521 ⟶ 524:
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].
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 complex
We can conclude also this table:
Line 533 ⟶ 536:
* Addressing Modes:
* ================
* - LBA28 Mode.
* - LBA48 Mode.
* - CHS. (+)
* Reading Modes:
Line 545 ⟶ 548:
* ================
* - IRQs
* - Polling Status
*/
</source>
There is something needed to be expressed here, I have told before that Task-File is like that:
* Register 0: [Word] Data Register.
* Register 1: [Byte] Error Register.
* Register 1: [Byte] Features Register.
* Register 2: [Byte] SECCOUNT0 Register.
* Register 3: [Byte] LBA0 Register.
* Register 4: [Byte] LBA1 Register.
* Register 5: [Byte] LBA2 Register.
* Register 6: [Byte] HDDEVSEL Register.
* Register 7: [Byte] Command Register.
* Register 7: [Byte] Status Register.
So each
* Register 2: [Bits 0-7] SECCOUNT0, [Bits 8-15] SECOUNT1
Line 568 ⟶ 571:
* 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
* LBA1
* LBA2
* LBA3
* LBA4
* LBA5
Notice that
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:
Line 591 ⟶ 592:
</source>
This
* drive
* lba
* numsects
* selector
* edi
<source lang="c">
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].
unsigned int slavebit = ide_devices[drive].
unsigned int bus = channels[channel].
unsigned int words = 256; //
unsigned short cyl, i
unsigned char head, sect, err;
</source>
We don't need IRQs, so we should disable it to
<source lang="c">
Line 622 ⟶ 624:
// 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; //
lba_io[5] = 0; //
head = 0; // Lower 4-bits of HDDEVSEL are not used here.
} else if (ide_devices[drive].
// 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.
}
</source>
Line 657 ⟶ 659:
<source lang="c">
// (II) See if
dma = 0; //
</source>
Lets
<source lang="c">
// (III) Wait if the drive is busy;
while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY)
; // Wait if Busy.
</source>
The HDDDEVSEL
* Bits 0
* Bit 4: Slave Bit. (0: Selecting Master Drive, 1: Selecting Slave Drive).
* Bit 5: Obsolete and isn't used, but should be set.
Line 680 ⟶ 683:
<source lang="c">
// (IV) Select Drive from the controller;
if (lba_mode == 0)
else
ide_write(channel, ATA_REG_HDDEVSEL, 0xE0 | (slavebit << 4) | head); // Drive & LBA
</source>
Line 700 ⟶ 705:
</source>
Now, we have a great set of commands described in ATA/ATAPI-8 Specification, we should choose the suitable command to execute:
Line 715 ⟶ 720:
</source>
There isn't a command for
<source lang="c">
Line 733 ⟶ 738:
</source>
This
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
Notice that after writing, we should execute the CACHE FLUSH
<source lang="c">
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,
}
</source>
===Read From ATAPI Drive===
Let's move to
An ATAPI
Notice also that ATAPI drives
<source lang="c">
void ide_wait_irq() {
while (!ide_irq_invoked)
;
ide_irq_invoked = 0;
}
Line 796 ⟶ 804:
</source>
<source lang="c">
Line 803 ⟶ 811:
</source>
* drive
* lba
* numsects
* selector
* edi
Let's read the parameters of the drive:
<source lang="c">
unsigned int channel = ide_devices[drive].
unsigned int slavebit = ide_devices[drive].
unsigned int bus = channels[channel].
unsigned int words = 1024; // Sector Size. ATAPI Drives has a sector size of 2048 bytes.
unsigned char err
int i;
</source>
Line 826 ⟶ 835:
</source>
Let's setup the SCSI Packet,
<source lang="c">
Line 833 ⟶ 842:
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;
Line 850 ⟶ 859:
// (II): Select the Drive:
// ------------------------------------------------------------------
ide_write(channel, ATA_REG_HDDEVSEL, slavebit << 4);
</source>
Line 858 ⟶ 867:
// (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
</source>
Line 870 ⟶ 877:
</source>
Tell the controller the size of the buffer
<source lang="c">
Line 876 ⟶ 883:
// ------------------------------------------------------------------
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.
</source>
Line 896 ⟶ 903:
// (VIII): Sending the packet data:
// ------------------------------------------------------------------
asm("rep outsw" : : "c"(6), "d"(bus), "S"(atapi_packet)); // Send Packet Data
</source>
|