在实时模式下读写IDE硬盘可以使用BIOS中断程序,但在保护模式下就不可以使用BIOS中断程序了,只能通过读写端口来实现硬盘的访问。IDE标准定义了8个寄存器来操作硬盘。PC体系结构将第一个硬盘控制器映射到端口0x1F0 ~ 0x1F7处,而第二个硬盘控制器则被映射到端口0x170 ~ 0x177处。这几个寄存器的描述如下:
0x1F0:数据寄存器。读写数据都必须通过这个寄存器
0x1F1:错误寄存器,每一位代表一类错误。全零表示操作成功。
0x1F2:扇区计数。这里面存放你要操作的扇区数量
0x1F3:扇区LBA地址的0-7位
0x1F4:扇区LBA地址的8-15位
0x1F5:扇区LBA地址的16-23位
0x1F6(低4位):扇区LBA地址的24-27位
0x1F6(第4位):0表示选择主盘,1表示选择从盘
0x1F6(5-7位):必须为1
0x1F7(写):命令寄存器
0x1F7(读):状态寄存器
bit 7 = 1 控制器忙
bit 6 = 1 驱动器就绪
bit 5 = 1 设备错误
bit 4 N/A
bit 3 = 1 扇区缓冲区错误
bit 2 = 1 磁盘已被读校验
bit 1 N/A
bit 0 = 1 上一次命令执行失败
首先定义一些必要的宏,用于操作的端口号和读写命令(读0x20;写0x30):
#define HD_PORT_DATA 0x1f0 #define HD_PORT_ERROR 0x1f1 #define HD_PORT_SECT_COUNT 0x1f2 #define HD_PORT_LBA0 0x1f3 #define HD_PORT_LBA1 0x1f4 #define HD_PORT_LBA2 0x1f5 #define HD_PORT_LBA3 0x1f6 #define HD_PORT_STATUS 0x1f7 #define HD_PORT_COMMAND 0x1f7 #define HD_READ 0x20 #define HD_WRITE 0x30
设置操作的扇区号:
u8 lba0 = (u8) (lba & 0xff); u8 lba1 = (u8) (lba >> 8 & 0xff); u8 lba2 = (u8) (lba >> 16 & 0xff); u8 lba3 = (u8) (lba >> 24 & 0xf); lba3 |= 0xe0; // 1110 0000
等待驱动器空闲时对其下达操作命令:
while (inb_p(HD_PORT_STATUS) < 0) { } while ((inb_p(HD_PORT_STATUS) & 0xc0) != 0x40) { } outb_p(sects_to_access, HD_PORT_SECT_COUNT); outb_p(lba0, HD_PORT_LBA0); outb_p(lba1, HD_PORT_LBA1); outb_p(lba2, HD_PORT_LBA2); outb_p(lba3, HD_PORT_LBA3); outb_p(com, HD_PORT_COMMAND);
当IDE设备就绪时对其发送读写命令:
while (inb_p(HD_PORT_STATUS) < 0) { } while ((inb_p(HD_PORT_STATUS) & 0xc0) != 0x40) { } if (com == HD_READ) { insl(buf2, HD_PORT_DATA, sects_to_access << 7); } else if (com == HD_WRITE) { outsl(buf2, HD_PORT_DATA, sects_to_access << 7); }
实际上当IDE设备就绪时,会触发一个IDE外部中断。下面来实现如何通过中断方式来读写IDE设备。首先需要对8259A编程,打开IDE设备中断:
//PIC1的第3个bit是连接到PIC2的开关,所以要先打开11111011=0xfb outb_p(inb_p(0x21) & 0xfb, 0x21); outb_p(inb_p(0xa1) & 0xbf, 0xa1);
实现方式为:每个IDE设备操作都是流水线性,为每一个读写操作加入一个读写队列,如果队列为空则停止操作,否则执行队列头的操作,等待IDE设备中断,操作完成时写入队列头中的操作结果,并将其移出队列(先进先出)。队列的数据结构如下:
typedef struct s_hda_rw { //扇区号 u32 lba; //读写命令 u8 com; //数据缓冲区 u8* buff; //操作状态 int* status; //队列的下一个节点 struct s_hda_rw *next; } s_hda_rw;
为每一个IDE读写操作加入队列:
void hd_rw_insert_queue(s_hda_info *hda_info, u32 lba, u8 com, u8 *buff, int *status) { s_hda_rw *p = alloc_mm(sizeof(s_hda_rw)); p->lba = lba; p->com = com; p->buff = buff; p->status = status; p->next = NULL; //空队列 if (hda_info->hda_rw_header == NULL) { hda_info->hda_rw_header = p; hda_info->hda_rw_footer = p; } //加入队列尾 else { hda_info->hda_rw_footer->next = p; hda_info->hda_rw_footer = p; } }
循环执行队列头的任务:
void hd_rw_execute(s_hda_info *hda_info) { s_hda_rw *header = hda_info->hda_rw_header; if (header != NULL) { //发送读写命令 if (*(header->status) == 0) { *(header->status) = 1; hd_rw_cmd(header->lba, header->com, header->buff); } } }
执行读写命令,对于写命令需要直接写入数据,并等待设备执行定操作完成。而对于读命令来说必须要等待设备操作完成(为用户准备数据的过程)之后才能读取数据:
void hd_rw_cmd(u32 lba, u8 com, u8* buff) { u8 lba0 = (u8) (lba & 0xff); u8 lba1 = (u8) (lba >> 8 & 0xff); u8 lba2 = (u8) (lba >> 16 & 0xff); u8 lba3 = (u8) (lba >> 24 & 0xf); //IDE0主设备 lba3 |= 0xe0; // 1110 0000 u16 sects_to_access = 1; outb_p(sects_to_access, HD_PORT_SECT_COUNT); outb_p(lba0, HD_PORT_LBA0); outb_p(lba1, HD_PORT_LBA1); outb_p(lba2, HD_PORT_LBA2); outb_p(lba3, HD_PORT_LBA3); outb_p(com, HD_PORT_COMMAND); //如果是写命令可以直接写入数据 if (com == HD_WRITE) { outsl(buff, HD_PORT_DATA, sects_to_access << 7); } }
当IDE设备执行读写操作结束时会触发IDE中断,中断响应函数会调用操作后的数据读写函数。对于写操作来说,执行完毕时只时提示操作已完成,而读操作必须在IDE设备操作完成之后再读入相关的数据:
void hd_rw_data(u8 com, u8* buff) { u16 sects_to_access = 1; //如果是读命令,读数据 if (com == HD_READ) { insl(buff, HD_PORT_DATA, sects_to_access << 7); } inb_p(HD_PORT_STATUS); }
光盘操作相对简单一些,因为光盘设备是一个只读设备。我们只需要完成读取数据的功能即可:
//0xA8为读取扇区数据的命令 u8 read_cmd[12] = { 0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //设置设备号为IDE0从设备 outb_p(drive & (1 << 4), ATA_DRIVE_SELECT(bus)); outb_p(ATAPI_SECTOR_SIZE & 0xFF, ATA_ADDRESS2(bus)); outb_p(ATAPI_SECTOR_SIZE >> 8, ATA_ADDRESS3(bus)); outb_p(0xa0, ATA_COMMAND(bus)); //等待设备空闲 while ((read_status = inb_p(ATA_STATUS(bus))) & 0x80) { } while (!((read_status = inb_p(ATA_STATUS(bus))) & 0x8) && !(read_status & 0x1)) { } //设备异常 if (read_status & 0x1) { return; } //读取1个扇区 read_cmd[9] = 1; //设置扇区号 read_cmd[2] = (lba >> 0x18) & 0xFF; read_cmd[3] = (lba >> 0x10) & 0xFF; read_cmd[4] = (lba >> 0x08) & 0xFF; read_cmd[5] = (lba >> 0x00) & 0xFF; //发送命令 send_atapi_command((u16 *) read_cmd, ATA_DATA(bus)); //等待设备就绪 while (inb_p(ATA_STATUS(bus)) < 0) { } while ((inb_p(ATA_STATUS(bus)) & 0xc0) != 0x40) { } //读取数据 insl_p((u32)buff, ATA_DATA(bus), 1); //读取操作状态 read_status = inb_p(ATA_STATUS(bus));
Copyright © 2015-2023 问渠网 辽ICP备15013245号