操作系统-IDE控制器

Posted on Thu, Nov 4, 2021 Operating System

最新在研究虚拟机逃逸,对于大部分虚拟机程序来说,一个很大攻击面就是PCI设备实现。不同的PCI设备,它们的交互方法、通信协议千差万别,需要逐个分析。又是到了考验操作系统基本功的时候了,大学时没好好上操作系统,落下的课果然是要还的。在这里开个坑,介绍各种设备的交互方法,以及一些简单的代码实现。

IDE控制器(Integrated Drive Electronics)是一个用来读写硬盘的设备,准确来讲应该是一个连接器,是操作系统和磁盘驱动器通信的桥梁。

一个IDE外设有两个Channel,分别为Primary和Secondary,每个Channel又分别可以连接Master和Slave两个drive。所以一个IDE外设可以最多连接四块硬盘,标准的名称分别是

PCI设备探测

如果一个PCI设备的class code为0x01(Mass Storage Controller),subclass code 为 0x01 (IDE),就说明是IDE控制器。我们通过IDE进行磁盘读写操作需要使用到5个基址,分别存放在PCI的BAR0,BAR1,BAR2,BAR3,BAR4。

IDE控制器的每个channel有16个寄存器,通过上面的BAR进行寻址。比如Primary Channel的寄存器信息如下:

读写寄存器代码封装

#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 // for read
#define ATA_REG_STATUS     0x07 // for write
#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

void ide_write(unsigned char channel, unsigned char reg, unsigned char data) {
	if (reg > 0x07 && reg < 0x0C)
		//Set this to read back the High Order Byte of the last LBA48 value sent to an IO port.
		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);
}

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;
}

我们要读写磁盘一般有ATA和ATAPI两种接口,不同的接口规格在交互上有些许差别。

下面是如何区分ATA、ATAPI驱动器的初始化代码

static void ide_initialize(uint16_t bar0, uint16_t bar1,
                           uint16_t bar2, uint16_t bar3, uint16_t bar4) {
	uint8_t 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
	// 2- Disable IRQs:
	ide_write(ATA_PRIMARY  , ATA_REG_CONTROL, 2);
	ide_write(ATA_SECONDARY, ATA_REG_CONTROL, 2);
	// 3- Detect ATA-ATAPI Devices:
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			uint8_t err = 0;
			uint8_t type = IDE_ATA;
			ide_devices[count].reserved = 0;
			// (I) Select Drive:
			ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j << 4)); // Select Drive.
			ide_delay(i);
			// (II) Send ATA Identify Command:
			ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY);
			ide_delay(i); // This function should be implemented in your OS. which waits for 1 ms.
			// it is based on System Timer Device Driver.
			if (ide_read(i, ATA_REG_STATUS) == 0) continue;
			while (1) {
				uint8_t 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.
			}
			if (err) {
				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);
				babysleep(50);
			}
			memset(ide_buf, 0, sizeof(ide_buf));

			// (V) Read Identification Space of the Device:
			ide_read_buffer(i, ATA_REG_DATA, 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 (int 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.

			ide_devices[count].udma = -1;
			ide_devices[count].mdma = -1;
			//check udma support
			for (int bit = 6; bit >= 0; bit--) {
				if (ide_buf[ATA_IDENT_UDMASUP] & (1 << bit)) {
					ide_devices[count].udma = bit;
					break;
				}
			}
			for (int bit = 2; bit >= 0; bit--) {
				if (ide_buf[ATA_IDENT_MDMASUP] & (1 << bit)) {
					ide_devices[count].mdma = bit;
					break;
				}
			}

			count++;
		}
	}
	// 4- Print Summary:
	for (int i = 0; i < 4; i++)
		if (ide_devices[i].reserved == 1) {
			debug("IDE[%d:%d] %s Drive %dMB - %s\n",
			      ide_devices[i].channel, ide_devices[i].drive,
			(const char *[]) {"ATA", "ATAPI"}[ide_devices[i].type],        /* Type */
			ide_devices[i].size / 1024 / 2,               /* Size */
			ide_devices[i].model);
		}
}

IDE磁盘读写

在实现磁盘读写操作之前,有必要弄清楚IDE控制器的几种交互方法。

PIO模式

磁盘的数据使用io port进行传输,所有的IDE外设均支持此模式。

DMA模式

DMA即Direct Memory Access,IDE外设通过memory bus直接访问内存进行数据传输,速度比PIO快。当PCI设备的Prog If寄存器的第七个bit为0时,表示该控制器不支持DMA模式。

上文提到了ATA、ATAPI两种驱动器,因此我们需要我们一共需要实现以下几个函数

ata_pio_read、ata_pio_write、ata_dma_read、ata_dma_write、

atapi_pio_read、atapi_pio_write、atapi_dma_read、atapi_dma_write

我们知道,老式磁盘由扇区构成,现代磁盘可以直接通过地址访问。在实现上,一共有三种定位磁盘读写位置的方法:

在实际选择时,可以通过当前读写的位置选择合适的方法,代码如下

uint8_t ide_access_drive(uint8_t channel, uint8_t drive, uint32_t lba, uint32_t numsects) {
	uint8_t lba_mode; /* 0: CHS, 1:LBA28, 2: LBA48 */
	uint8_t head, sect;
	uint16_t cyl;
	uint8_t lba_io[6];
	if (lba > 0x10000000) { // Sure Drive should support LBA in this case, or you are giving a wrong LBA.
		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; // We said that we lba is integer, so 32-bit are enough to access 2TB.
		lba_io[5] = 0; // We said that we lba is integer, so 32-bit are enough to access 2TB.
		head      = 0; // Lower 4-bits of HDDEVSEL are not used here.
	} else if (ide_devices[drive].capabilities & 0x200)  {
		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 {
		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.
	}
	while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY); // Wait if Busy.

	// (IV) Select Drive from the controller;
	if (lba_mode == 0)
		ide_write(channel, ATA_REG_HDDEVSEL, 0xA0 | (drive << 4) | head); // Select Drive CHS.
	else
		ide_write(channel, ATA_REG_HDDEVSEL, 0xE0 | (drive << 4) | head); // Select Drive LBA.
	// (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]);
	return lba_mode;
}

接下来我们先实现基于PIO读写磁盘的函数

#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

unsigned char ide_ata_pio_read(unsigned char idx, unsigned int lba, 
	unsigned char numsects, void* addr){
	unsigned int drive = ide_devices[idx].drive;
	unsigned int channel = ide_devices[idx].channel;
	uint16_t words = 256;
	uint16_t bus = channels[channel].base;
	uint16_t lba_mode = ide_access_drive(channel,drive,lba,numsects);
	ide_delay(channel);
	ide_write(channel, ATA_REG_COMMAND, lba_mode ==2 ? ATA_CMD_READ_PIO_EXT:ATA_CMD_READ_PIO);
	for (int i = 0; i < numsects; i++) {
	 	uint8_t err = ide_polling(channel, 1);
		if (err){
			return err;
		}
		insw(bus,addr,words);
        addr += words*2;
	}
	return 0;
}
unsigned char ide_ata_pio_write(unsigned char idx, unsigned int lba, 
	unsigned char numsects, void* addr){
	unsigned int drive = ide_devices[idx].drive;
	unsigned int channel = ide_devices[idx].channel;
	uint16_t words = 256;
	uint16_t bus = channels[channel].base;
	uint16_t lba_mode = ide_access_drive(channel,drive,lba,numsects);
	ide_delay(channel);
	ide_write(channel, ATA_REG_COMMAND, lba_mode ==2 ? ATA_CMD_WRITE_PIO_EXT:ATA_CMD_WRITE_PIO);
	for (int i = 0; i < numsects; i++) {
	 	uint8_t err = ide_polling(channel, 1);
		if (err){
			return err;
		}
		outsw(bus,addr,words);
        addr += words*2;
	}
	ide_write(channel, ATA_REG_COMMAND,lba_mode == 2?ATA_CMD_CACHE_FLUSH_EXT:ATA_CMD_CACHE_FLUSH);
	ide_polling(channel,1);
	return 0;
}
unsigned char ide_atapi_pio_read(unsigned char idx, unsigned int lba,
                                 unsigned char numsects, void* addr) {
	unsigned int channel = ide_devices[idx].channel;
	unsigned int drive = ide_devices[idx].drive;
	unsigned int   words      = 2048 / 2; // Sector Size in Words, Almost All ATAPI Drives has a sector size of 2048 bytes.
	uint8_t err;
	unsigned int   bus      = channels[channel].base;
	ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);
	ide_write(channel, ATA_REG_HDDEVSEL, drive << 4);
	ide_write(channel, ATA_REG_FEATURES, 0); // PIO mode
	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_COMMAND, ATA_CMD_PACKET);      // Send the Command.
	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;
	err = ide_polling(channel, 1);
	if (err) return err;
	outsw(bus, atapi_packet, 6);
	for (int i = 0; i < numsects; i++) {
		ide_wait_irq();                  // Wait for an IRQ.
		err = ide_polling(channel, 1);
		if (err) return err;      // Polling and return if error.
		insw(bus, addr, words);
		addr += (words * 2);
	}
	ide_wait_irq();
	return 0;
}
unsigned char ide_atapi_pio_write(unsigned char idx, unsigned int lba,
                                  unsigned char numsects, void* addr) {
	unsigned int channel = ide_devices[idx].channel;
	unsigned int drive = ide_devices[idx].drive;
	unsigned int   words      = 2048 / 2; // Sector Size in Words, Almost All ATAPI Drives has a sector size of 2048 bytes.
	uint8_t err;
	unsigned int   bus      = channels[channel].base;
	ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);
	ide_write(channel, ATA_REG_HDDEVSEL, drive << 4);
	ide_write(channel, ATA_REG_FEATURES, 0); // PIO mode
	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_COMMAND, ATA_CMD_PACKET);      // Send the Command.
	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;
	err = ide_polling(channel, 1);
	if (err) return err;
	outsw(bus, atapi_packet, 6);
	for (int i = 0; i < numsects; i++) {
		ide_wait_irq();                  // Wait for an IRQ.
		err = ide_polling(channel, 1);
		if (err) return err;      // Polling and return if error.
		outsw(bus, addr, words);
		addr += (words * 2);
	}
	ide_wait_irq();
	return 0;
}

对于DMA模式,需要使用到Bus master port,即PCI驱动里的BAR4。该基址空间为16字节,每个channel使用8个字节。具体每个字节的意义可以在一本名为《Programming Interface for Bus Master IDE Controller》的小册子里找到,大概意思就是需要我们向这里写入一个内存地址,告诉IDE控制器,在我们发送命令以后不需要再等待io传输数据,直接从约定的物理地址里读写数据即可。

具体实现代码如下

unsigned char ide_ata_dma_read(unsigned char idx, unsigned int lba, 
	unsigned char numsects, void* addr){
	unsigned int  drive      = ide_devices[idx].drive;
	unsigned int channel = ide_devices[idx].channel;
	uint16_t words = 256;
	uint16_t port = channels[channel].bmide;
	channels[channel].nIEN = 0;
	ide_write(channel, ATA_REG_CONTROL, 0x00);
	region_desc* prd_table = channels[channel].prd_table;
	memset(channels[channel].dma_buffer,'\xcc',numsects*words*2);
	// prepare dma
	for(int i=0;i<numsects;i++){
		prd_table[i].address = (uint32_t)&channels[channel].dma_buffer[i*2*words];
		prd_table[i].count = words*2;
		prd_table[i].end = 0;
	}
	prd_table[numsects-1].end = 0x8000;
	outl(port + 4,(uint32_t)prd_table);
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel,ATA_REG_DMA_CMD)|1<<3);
	ide_write(channel, ATA_REG_DMA_STATUS, ide_read(channel,ATA_REG_DMA_STATUS)|4|2);//clear err intr
	// access driver
	uint16_t lba_mode = ide_access_drive(channel,drive,lba,numsects);
	ide_delay(channel);
	ide_write(channel, ATA_REG_COMMAND, lba_mode ==2 ? ATA_CMD_READ_DMA_EXT:ATA_CMD_READ_DMA);
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel,ATA_REG_DMA_CMD)|1);
	ide_wait_irq();
	ide_write(channel,ATA_REG_DMA_CMD,ide_read(channel,ATA_REG_DMA_CMD)&(~1));
	memcpy(addr,channels[channel].dma_buffer,numsects*words*2);
	return 0;
}
unsigned char ide_ata_dma_write(unsigned char idx, unsigned int lba, 
	unsigned char numsects, void* addr){
	unsigned int  drive      = ide_devices[idx].drive;
	unsigned int channel = ide_devices[idx].channel;
	uint16_t words = 256;
	// uint16_t port = channels[channel].bmide;
	region_desc* prd_table = channels[channel].prd_table;
	channels[channel].nIEN = 0;
	ide_write(channel, ATA_REG_CONTROL, 0x00);
	ide_irq_invoked = 0;
	memcpy(channels[channel].dma_buffer,addr,numsects*words*2);
	// prepare dma
	for(int i=0;i<numsects;i++){
		prd_table[i].address = (uint32_t)&channels[channel].dma_buffer[i*2*words];
		prd_table[i].count = words*2;
		prd_table[i].end = 0;
	}
	prd_table[numsects-1].end = 0x8000;
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel,ATA_REG_DMA_CMD)&~(1<<3));//write
	ide_write(channel, ATA_REG_DMA_STATUS, ide_read(channel,ATA_REG_DMA_STATUS)|4|2);//clear err intr
	// access driver
	uint16_t lba_mode = ide_access_drive(channel,drive,lba,numsects);
	ide_delay(channel);
	ide_write(channel, ATA_REG_COMMAND, lba_mode == 2 ? ATA_CMD_WRITE_DMA_EXT:ATA_CMD_WRITE_DMA);
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel,ATA_REG_DMA_CMD)|1);
	ide_wait_irq();
	ide_write(channel,ATA_REG_DMA_CMD,ide_read(channel,ATA_REG_DMA_CMD)&(~1));
	return 0;
}
unsigned char ide_atapi_dma_read(unsigned char idx, unsigned int lba,
                                 unsigned char numsects, void* addr) {
	unsigned int channel = ide_devices[idx].channel;
	unsigned int drive = ide_devices[idx].drive;
	unsigned int words = 2048 / 2; // Sector Size in Words, Almost All ATAPI Drives has a sector size of 2048 bytes.
	uint8_t err;
	unsigned int bus = channels[channel].base;
	ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);
	ide_write(channel, ATA_REG_HDDEVSEL, drive << 4);
	ide_write(channel, ATA_REG_FEATURES, 1); // PIO mode
	ide_write(channel, ATA_REG_LBA1, 0);   // Lower Byte of Sector Size.
	ide_write(channel, ATA_REG_LBA2, 0);   // Upper Byte of Sector Size.
	ide_write(channel, ATA_REG_COMMAND, ATA_CMD_PACKET);      // Send the Command.
	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;
	err = ide_polling(channel, 1);
	if (err) return err;
	region_desc* prd_table = channels[channel].prd_table;
	for (int i = 0; i < numsects; i++) {
		prd_table[i].address = (uint32_t)&channels[channel].dma_buffer[i*words*2];
		prd_table[i].count = words * 2;
		prd_table[i].end = 0;
	}
	prd_table[numsects - 1].end = 0x8000;
	outl(channels[channel].bmide + IDE_DMA_PRD,(uint32_t)prd_table);
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel, ATA_REG_DMA_CMD)|(1<<3));
	ide_write(channel, ATA_REG_DMA_STATUS, ide_read(channel, ATA_REG_DMA_STATUS) | 4 | 2);
	outsw(bus, atapi_packet, 6);
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel, ATA_REG_DMA_CMD) | 1);
	ide_wait_irq();
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel, ATA_REG_DMA_CMD) & (~1));
	memcpy(addr,channels[channel].dma_buffer,numsects*words*2);
	return 0;
}

unsigned char ide_atapi_dma_write(unsigned char idx, unsigned int lba,
                                  unsigned char numsects, void* addr) {
	unsigned int channel = ide_devices[idx].channel;
	unsigned int drive = ide_devices[idx].drive;
	unsigned int words = 2048 / 2; // Sector Size in Words, Almost All ATAPI Drives has a sector size of 2048 bytes.
	uint8_t err;
	unsigned int bus = channels[channel].base;
	ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN = ide_irq_invoked = 0x0);
	ide_write(channel, ATA_REG_HDDEVSEL, drive << 4);
	ide_write(channel, ATA_REG_FEATURES, 1); // PIO mode
	ide_write(channel, ATA_REG_LBA1, 0);   // Lower Byte of Sector Size.
	ide_write(channel, ATA_REG_LBA2, 0);   // Upper Byte of Sector Size.
	ide_write(channel, ATA_REG_COMMAND, ATA_CMD_PACKET);      // Send the Command.
	atapi_packet[ 0] = ATAPI_CMD_WRITE;
	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;
	err = ide_polling(channel, 1);
	if (err) return err;
	region_desc* prd_table = channels[channel].prd_table;
	memcpy(channels[channel].dma_buffer,addr,numsects*words*2);

	for (int i = 0; i < numsects; i++) {
		prd_table[i].address = (uint32_t)&channels[channel].dma_buffer[i*words*2];
		prd_table[i].count = words * 2;
		prd_table[i].end = 0;
	}
	prd_table[numsects - 1].end = 0x8000;
	outl(channels[channel].bmide + IDE_DMA_PRD,(uint32_t)prd_table);
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel, ATA_REG_DMA_CMD) &~(1 << 3));
	ide_write(channel, ATA_REG_DMA_STATUS, ide_read(channel, ATA_REG_DMA_STATUS) | 4 | 2);
	outsw(bus, atapi_packet, 6);
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel, ATA_REG_DMA_CMD) | 1);
	ide_wait_irq();
	ide_write(channel, ATA_REG_DMA_CMD, ide_read(channel, ATA_REG_DMA_CMD) & (~1));
	return 0;
}

至此我么已经完成了所有的读写方法,最后将上面的几个函数封装一下

void ide_read_sectors(unsigned char drive, unsigned char numsects, unsigned int lba, void* addr) {
	// 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) {
			if (ide_devices[drive].dma) {
				debug("read from ata using dma\n");
				err = ide_ata_dma_read(drive, lba, numsects, addr);
			} else {
				debug("read from ata using pio\n");
				err = ide_ata_pio_read(drive, lba, numsects, addr);
			}
		}
		if (ide_devices[drive].type == IDE_ATAPI) {
			for (int i = 0; i < numsects; i++) {
				if (ide_devices[drive].dma) {
					debug("read from atapi using dma\n");
					err = ide_atapi_dma_read(drive, lba + i, 1, addr + i * 2048);
				} else {
					debug("read from atapi using pio\n");
					err = ide_atapi_pio_read(drive, lba + i, 1, addr + i * 2048);
				}
			}
		}
		package[0] = ide_print_error(drive, err);
	}
}

void ide_write_sectors(unsigned char drive, unsigned char numsects, unsigned int lba, void* addr) {

	// 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) {
			if (ide_devices[drive].dma) {
				debug("write from ata using dma\n");
				err = ide_ata_dma_write(drive, lba, numsects, addr);
			} else {
				debug("write from ata using pio\n");
				err = ide_ata_pio_write(drive, lba, numsects, addr);
			}
		}
		else if (ide_devices[drive].type == IDE_ATAPI) {
			if (ide_devices[drive].dma) {
				err = ide_atapi_dma_write(drive, lba, numsects, addr);
			} else {
				err = ide_atapi_pio_write(drive, lba, numsects, addr);
			}
		}
		package[0] = ide_print_error(drive, err);
	}
}

参考链接

  1. https://wiki.osdev.org/PCI_IDE_Controller
  2. https://www.opennet.ru/docs/FAQ/hardware/ide-part2.html
  3. https://github.com/alberinfo/Slidoor-OS/tree/8a9280b624f397a339de27943b93e1c41d6244bc/kernel/src/drivers/ata