最新在研究虚拟机逃逸,对于大部分虚拟机程序来说,一个很大攻击面就是PCI设备实现。不同的PCI设备,它们的交互方法、通信协议千差万别,需要逐个分析。又是到了考验操作系统基本功的时候了,大学时没好好上操作系统,落下的课果然是要还的。在这里开个坑,介绍各种设备的交互方法,以及一些简单的代码实现。
IDE控制器(Integrated Drive Electronics)是一个用来读写硬盘的设备,准确来讲应该是一个连接器,是操作系统和磁盘驱动器通信的桥梁。
一个IDE外设有两个Channel,分别为Primary和Secondary,每个Channel又分别可以连接Master和Slave两个drive。所以一个IDE外设可以最多连接四块硬盘,标准的名称分别是
- Primary Master Drive.
- Primary Slave Drive.
- Secondary Master Drive.
- Secondary Slave Drive.
PCI设备探测
如果一个PCI设备的class code为0x01(Mass Storage Controller),subclass code 为 0x01 (IDE),就说明是IDE控制器。我们通过IDE进行磁盘读写操作需要使用到5个基址,分别存放在PCI的BAR0,BAR1,BAR2,BAR3,BAR4。
- BAR0 Primary Master Drive的数据起始地址,默认为0x1F0
- BAR1 Primary Master Drive的控制地址,默认为0x3F6
- BAR2 Secondary Master Drive的数据起始地址,默认为0x170
- BAR3 Secondary Master Drive的控制地址,默认为0x376
- BAR4 Bus master IDE起始地址,控制驱动器使用DMA方式进行数据交互
IDE控制器的每个channel有16个寄存器,通过上面的BAR进行寻址。比如Primary Channel的寄存器信息如下:
- 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; // 将Control Register的最高位置0再进行读写
- LBA1: BAR0 + 4; // 将Control Register的最高位置0再进行读写
- LBA2: BAR0 + 5; // 将Control Register的最高位置0再进行读写
- LBA3: BAR0 + 3; // 将Control Register的最高位置1再进行读写
- LBA4: BAR0 + 4; // 将Control Register的最高位置1再进行读写
- LBA5: BAR0 + 5; // 将Control Register的最高位置1再进行读写
- HDDEVSEL: BAR0 + 6; // Read-Write, used to select a drive in the channel.
- Command Register: BAR0 + 7; // Write Only.
- Status Register: BAR1 + 7; // Read Only.
- Alternate Status Register: BAR1 + 2; // Read Only.
- Control Register: BAR1 + 2; // Write Only.
- DEVADDRESS: BAR1 + 3;
读写寄存器代码封装
#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
我们知道,老式磁盘由扇区构成,现代磁盘可以直接通过地址访问。在实现上,一共有三种定位磁盘读写位置的方法:
- CHS 通过扇区定位读写位置,所有驱动器均支持此方式
- LBA24 通过28bit的地址访问磁盘位置,最多访问128G磁盘
- LBA48 通过48bit的地址访问磁盘
在实际选择时,可以通过当前读写的位置选择合适的方法,代码如下
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);
}
}