diff --git "a/docs/09 \350\256\276\345\244\207\351\251\261\345\212\250/075 \347\241\254\347\233\230\345\274\202\346\255\245 PIO.md" "b/docs/09 \350\256\276\345\244\207\351\251\261\345\212\250/075 \347\241\254\347\233\230\345\274\202\346\255\245 PIO.md" new file mode 100644 index 00000000..f8a2187f --- /dev/null +++ "b/docs/09 \350\256\276\345\244\207\351\251\261\345\212\250/075 \347\241\254\347\233\230\345\274\202\346\255\245 PIO.md" @@ -0,0 +1,73 @@ +# 硬盘异步 PIO + +由于同步状态检测,消耗了大量的 CPU 资源,所以可以使用异步的方式来等待硬盘驱动器。给驱动器发送完读写命令后,进程可以进入阻塞态,当驱动器完成一个扇区的操作 (读/写) 时,会发送中断,可以在中断中恢复进程到就绪态,继续执行。 + +--- + +## 中断 + +注意:当命令以错误结束时,它不会生成 IRQ。每秒检查几次备用状态寄存器是明智的,以查看是否设置了 ERR 位。否则,直到命令超时您才会知道。 + +--- + +## 处理中断 + +在早期,IRQ 的唯一目的是通知 IRQ 处理程序驱动器已经准备好发送或接受数据。我们的期望是 IRQ 处理程序本身将立即对下一个数据块执行基于 PIO 的数据传输。现在事情没那么简单了。总线上的一个或两个驱动器可能处于 DMA 模式,或者数据块大小不是 256 个 16 位值。此外,现在更强调以尽可能快的速度从 IRQ 处理程序例程中返回。所以问题是:IRQ处理程序需要做的最小操作集是什么? + +如果您正在使用 IRQ 共享,您将需要检查 PCI 总线主状态字节,以验证 IRQ 来自磁盘。如果是这样,就需要读取常规状态寄存器一次,以使磁盘清除其中断标志。如果状态寄存器中的 ERR 位被设置(位 0,值 = 1),您可能想从错误 IO 端口(主总线上 0x1F1)读取并保存 **错误详细信息** 值。 + +如果传输是一个 READ DMA 操作,则必须从总线主状态寄存器读取值。因为 IRQ 处理程序可能不知道操作是否是 DMA 操作,所以在所有 IRQ 之后 (如果总线是由 PCI 控制器控制的,几乎肯定是这样),您可能会检查 Busmaster Status 字节。如果该字节设置了 ERR 位(位 1,值 = 2),您可能希望将当前值保存在磁盘的 LBA IO 端口中,它们可以告诉您驱动器上哪个扇区生成了错误。您还需要清除错误位,方法是向其写入一个 2。 + +您还需要向两个 PIC 发送 EOI (0x20),以清除它们的中断标志。然后,您需要设置一个标志来“解除”驱动程序,并让它知道发生了另一个 IRQ ——这样驱动程序就可以进行任何必要的数据传输。 + +注意:如果你仍然处于单请求模式,并且只在 PIO 模式下轮询常规状态寄存器,那么 IRQ 处理器需要做的唯一一件事就是向 PCI 发送 EOI。您甚至可能想要设置控制寄存器的 nIEN 位,以尝试完全关闭磁盘 IRQ。 + +--- + +## 轮询状态 VS 中断 + +当驱动发出 PIO 读写命令时,需要等待驱动准备好后才能传输数据。有两种方法可以知道驱动器何时准备好接收数据。当它就绪时,驱动器将发送一个 IRQ。或者,驱动程序可以轮询其中一个状态端口(常规或备用状态)。 + +轮询有两个优点: + +- 轮询的响应速度比 IRQ 快 +- 轮询的逻辑比等待 IRQ 简单得多 + +还有一个巨大的缺点: + +- 在多任务环境中,轮询会耗尽所有CPU时间 +- 但是,在单请求模式下,这不是问题(CPU 没有更好的事情可做),所以轮询是一件好事 + +如何轮询(等待驱动器准备传输数据): + +- 读取常规状态端口,直到第 7 位(BSY,值= 0x80)清除,和第 3 位(DRQ,值= 8)设置 +- 或直到第 0 位(ERR,值= 1) 或 第 5 位(DF,值= 0x20)设置。 +- 如果两个错误位都没有设置,那么设备就已经准备好了 + +--- + +## 抢占/防止中断触发 + +如果驱动程序在向驱动器发送命令后读取了常规状态端口,则 **响应** IRQ 可能永远不会发生 + +如果您想要接收 IRQs,那么总是读取 **备用状态端口**,而不是常规状态端口。但有时 IRQ 只是浪费资源,让它们消失可能是一个好主意。 + +防止 ATA IRQs 发生的更完整的方法是在特定选定驱动器的控制寄存器中设置 nIEN 位。这应该会阻止总线上的驱动器发送任何 IRQs,直到你再次清除该位。 + +然而,它可能并不总是有效,一些程序员报告了 nIEN 运行时的问题。当 nIEN 是总线上的选定驱动器时,驱动器只响应新写入的 nIEN 值。 + +也就是说,如果选择了一个驱动器,并且您设置了 nIEN,然后选择带有驱动器选择寄存器的另一个驱动器,然后清除 nIEN ——然后第一个驱动器应该永远“记住”它被告知不要发送 irq ——直到您再次选择它,并在控制寄存器中写入一个 0 到 nIEN 位。 + +### 读写多块 + +在多任务 PIO 模式下,尝试减少 IRQ 数量的一种方法是使用 READ MULTIPLE (0xC4) 和 WRITE MULTIPLE (0xC5) 命令。这 + +些命令使驱动器缓冲区的扇区“块”,并且每个块只发送一个 IRQ,而不是每个扇区发送一个 IRQ。参考 IDENTIFY 命令的 uint16_t 47 和 59 来确定一个块中扇区的数量。您还可以尝试使用 SET MULTIPLE MODE (0xC6) 命令来更改每个块的扇区。 + +注意:总的来说,PIO 模式是一种慢速传输方式。在实际工作条件下,几乎任何驱动器都应该由 DMA 驱动器控制,而不应该使用 PIO。试图通过抢占 IRQ(或任何其他方法)来加快 PIO 模式的速度基本上是在浪费时间和精力。但是,400MB 或更小的 ATA 驱动器可能不支持多字 DMA 模式0。如果您希望支持这种大小的驱动器,那么在 PIO 模式驱动程序上花点功夫可能是值得的。 + +## 参考文献 + +- +- Information Technology - AT Attachment - 8 ATA/ATAPI Command Set (ATA8-ACS) +- diff --git a/src/include/onix/ide.h b/src/include/onix/ide.h index 7f10a553..686b91cf 100644 --- a/src/include/onix/ide.h +++ b/src/include/onix/ide.h @@ -26,6 +26,7 @@ typedef struct ide_ctrl_t u16 iobase; // IO 寄存器基址 ide_disk_t disks[IDE_DISK_NR]; // 磁盘 ide_disk_t *active; // 当前选择的磁盘 + struct task_t *waiter; // 等待控制器的进程 } ide_ctrl_t; int ide_pio_read(ide_disk_t *disk, void *buf, u8 count, idx_t lba); diff --git a/src/kernel/gate.c b/src/kernel/gate.c index baa174b4..7265546b 100644 --- a/src/kernel/gate.c +++ b/src/kernel/gate.c @@ -25,9 +25,24 @@ static void sys_default() panic("syscall not implemented!!!"); } +#include +#include + +extern ide_ctrl_t controllers[2]; + static u32 sys_test() { - // LOGK("syscall test...\n"); + u16 *buf = (u16 *)alloc_kpage(1); + LOGK("pio read buffer 0x%p\n", buf); + ide_disk_t *disk = &controllers[0].disks[0]; + ide_pio_read(disk, buf, 4, 0); + BMB; + + memset(buf, 0x5a, 512); + + ide_pio_write(disk, buf, 1, 1); + + free_kpage((u32)buf, 1); return 255; } diff --git a/src/kernel/handler.asm b/src/kernel/handler.asm index f6e043a5..f27aa4eb 100644 --- a/src/kernel/handler.asm +++ b/src/kernel/handler.asm @@ -105,8 +105,8 @@ INTERRUPT_HANDLER 0x2a, 0 INTERRUPT_HANDLER 0x2b, 0 INTERRUPT_HANDLER 0x2c, 0 INTERRUPT_HANDLER 0x2d, 0 -INTERRUPT_HANDLER 0x2e, 0 -INTERRUPT_HANDLER 0x2f, 0 +INTERRUPT_HANDLER 0x2e, 0; harddisk1 硬盘主通道 +INTERRUPT_HANDLER 0x2f, 0; harddisk2 硬盘从通道 ; 下面的数组记录了每个中断入口函数的指针 section .data diff --git a/src/kernel/ide.c b/src/kernel/ide.c index b7f97301..f71839db 100644 --- a/src/kernel/ide.c +++ b/src/kernel/ide.c @@ -67,6 +67,24 @@ ide_ctrl_t controllers[IDE_CTRL_NR]; +void ide_handler(int vector) +{ + send_eoi(vector); // 向中断控制器发送中断处理结束信号 + + // 得到中断向量对应的控制器 + ide_ctrl_t *ctrl = &controllers[vector - IRQ_HARDDISK - 0x20]; + + // 读取常规状态寄存器,表示中断处理结束 + u8 state = inb(ctrl->iobase + IDE_STATUS); + LOGK("harddisk interrupt vector %d state 0x%x\n", vector, state); + if (ctrl->waiter) + { + // 如果有进程阻塞,则取消阻塞 + task_unblock(ctrl->waiter); + ctrl->waiter = NULL; + } +} + static u32 ide_error(ide_ctrl_t *ctrl) { u8 error = inb(ctrl->iobase + IDE_ERR); @@ -157,6 +175,7 @@ static void ide_pio_write_sector(ide_disk_t *disk, u16 *buf) int ide_pio_read(ide_disk_t *disk, void *buf, u8 count, idx_t lba) { assert(count > 0); + assert(!get_interrupt_state()); // 异步方式,调用该函数时不许中断 ide_ctrl_t *ctrl = disk->ctrl; @@ -176,7 +195,16 @@ int ide_pio_read(ide_disk_t *disk, void *buf, u8 count, idx_t lba) for (size_t i = 0; i < count; i++) { + task_t *task = running_task(); + if (task->state == TASK_RUNNING) // 系统初始化时,不能使用异步方式 + { + // 阻塞自己等待中断的到来,等待磁盘准备数据 + ctrl->waiter = task; + task_block(task, NULL, TASK_BLOCKED); + } + ide_busy_wait(ctrl, IDE_SR_DRQ); + u32 offset = ((u32)buf + i * SECTOR_SIZE); ide_pio_read_sector(disk, (u16 *)offset); } @@ -189,6 +217,7 @@ int ide_pio_read(ide_disk_t *disk, void *buf, u8 count, idx_t lba) int ide_pio_write(ide_disk_t *disk, void *buf, u8 count, idx_t lba) { assert(count > 0); + assert(!get_interrupt_state()); // 异步方式,调用该函数时不许中断 ide_ctrl_t *ctrl = disk->ctrl; @@ -213,6 +242,14 @@ int ide_pio_write(ide_disk_t *disk, void *buf, u8 count, idx_t lba) u32 offset = ((u32)buf + i * SECTOR_SIZE); ide_pio_write_sector(disk, (u16 *)offset); + task_t *task = running_task(); + if (task->state == TASK_RUNNING) // 系统初始化时,不能使用异步方式 + { + // 阻塞自己等待磁盘写数据完成 + ctrl->waiter = task; + task_block(task, NULL, TASK_BLOCKED); + } + ide_busy_wait(ctrl, IDE_SR_NULL); } @@ -262,14 +299,10 @@ void ide_init() LOGK("ide init...\n"); ide_ctrl_init(); - void *buf = (void *)alloc_kpage(1); - BMB; - LOGK("read buffer %x\n", buf); - ide_pio_read(&controllers[0].disks[0], buf, 1, 0); - BMB; - memset(buf, 0x5a, SECTOR_SIZE); - BMB; - ide_pio_write(&controllers[0].disks[0], buf, 1, 1); - - free_kpage((u32)buf, 1); + // 注册硬盘中断,并打开屏蔽字 + set_interrupt_handler(IRQ_HARDDISK, ide_handler); + set_interrupt_handler(IRQ_HARDDISK2, ide_handler); + set_interrupt_mask(IRQ_HARDDISK, true); + set_interrupt_mask(IRQ_HARDDISK2, true); + set_interrupt_mask(IRQ_CASCADE, true); } diff --git a/src/kernel/thread.c b/src/kernel/thread.c index adfb2e3d..9df6adfb 100644 --- a/src/kernel/thread.c +++ b/src/kernel/thread.c @@ -51,8 +51,9 @@ static void user_init_thread() void init_thread() { - // set_interrupt_state(true); char temp[100]; // 为栈顶有足够的空间 + set_interrupt_state(true); + test(); task_to_user_mode(user_init_thread); }