跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第一节 读写IDE设备
 
 

一、硬盘操作

        在实时模式下读写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));

 

    返回首页    返回顶部
#1楼  leifeng  于 2019年05月24日19:21:52 发表
 
bit 6=1 表示驱动未就绪吧,还是我理解错了?

代码的意思应该是:驱动器不忙并且就绪状态才能继续操作吧?
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号