Selaa lähdekoodia

中断初始化功能

simon 1 vuosi sitten
vanhempi
commit
1141891db5
14 muutettua tiedostoa jossa 473 lisäystä ja 18 poistoa
  1. 2 0
      .gitignore
  2. 12 5
      build.sh
  3. 1 1
      clean.sh
  4. 43 0
      doc/gate descriptor.md
  5. 161 0
      doc/pic.md
  6. 30 0
      kernel/global.h
  7. 9 0
      kernel/init.c
  8. 6 0
      kernel/init.h
  9. 60 0
      kernel/interrupt.c
  10. 36 0
      kernel/interrupt.h
  11. 68 0
      kernel/kernel.S
  12. 3 9
      kernel/main.c
  13. 39 0
      lib/kernel/io.h
  14. 3 3
      lib/kernel/print.S

+ 2 - 0
.gitignore

@@ -72,3 +72,5 @@ bochs_log.txt
 *.img
 *.bin
 .vscode/
+.dist/
+build/

+ 12 - 5
build.sh

@@ -1,11 +1,18 @@
 #!/bin/sh
 
-echo "Compiling..."
+echo "Compiling mbr..."
 nasm -I boot/include/ -o boot/mbr.bin boot/mbr.S
+echo "Compiling loader..."
 nasm -I boot/include/ -o boot/loader.bin boot/loader.S
-nasm -f elf -o lib/kernel/print.o lib/kernel/print.S
-i386-elf-gcc -I ./lib/ -I lib/kernel/ -c -o kernel/main.o kernel/main.c
-i386-elf-ld -Ttext 0xc0001500 -e main -o kernel/kernel.bin kernel/main.o lib/kernel/print.o
+echo "Compiling kernel..."
+i386-elf-gcc -I ./lib/ -I lib/kernel/ -I kernel/ -c -fno-builtin -o build/main.o kernel/main.c
+nasm -f elf -o build/print.o lib/kernel/print.S
+nasm -f elf -o build/kernel.o kernel/kernel.S
+
+i386-elf-gcc -I lib/kernel/ -I ./lib/ -I kernel/ -c -fno-builtin -o build/interrupt.o kernel/interrupt.c
+i386-elf-gcc -I lib/kernel/ -I ./lib/ -I kernel/ -c -fno-builtin -o build/init.o kernel/init.c
+
+i386-elf-ld -Ttext 0xc0001500 -e main -o build/kernel.bin build/main.o build/init.o build/interrupt.o build/print.o build/kernel.o
 
 echo "Creating disk image..."
 bximage -q -func=create -hd=30M hd30M.img
@@ -16,7 +23,7 @@ dd if=boot/mbr.bin of=hd30M.img bs=512 count=1 conv=notrunc
 echo " 1. Writing loader to disk..."
 dd if=boot/loader.bin of=hd30M.img bs=512 count=4 seek=2 conv=notrunc
 echo " 2. Writing kernel to disk..."
-dd if=kernel/kernel.bin of=hd30M.img bs=512 count=200 seek=9 conv=notrunc
+dd if=build/kernel.bin of=hd30M.img bs=512 count=200 seek=9 conv=notrunc
 echo "Disk image created successfully."
 
 echo "Now starting bochs ..."

+ 1 - 1
clean.sh

@@ -1,3 +1,3 @@
 #!/bin/sh
 
-rm -rf bochs_log.txt hd30M.img *.bin boot/*.bin kernel/kernel.bin kernel/main.o lib/kernel/*.o
+rm -rf bochs_log.txt hd30M.img *.bin boot/*.bin build/*.o build/kernel.bin

+ 43 - 0
doc/gate descriptor.md

@@ -0,0 +1,43 @@
+# 门描述符(32 位)
+
+| 63            48 | 47  | 46  45 | 44  | 43   40   | 39   32  |
+| ---------------- | --- | ------ | --- | --------- | -------- |
+| Offset           | P   | DPL    | 0   | Gate Type | Reserved |
+
+| 31            16 | 15                             0 |
+| ---------------- | -------------------------------- |
+| Segment Selector | offset                           |
+
+- **Offset**: A 32-bit value, split in two parts. It represents the address of the entry point of the Interrupt Service Routine.
+- **Selector**: A Segment Selector with multiple fields which must point to a valid code segment in your GDT.
+- **Gate Type**: A 4-bit value which defines the type of gate this Interrupt Descriptor represents. There are five valid type values:
+  - 0b0101 or 0x5: Task Gate, note that in this case, the Offset value is unused and should be set to zero.
+  - 0b0110 or 0x6: 16-bit Interrupt Gate
+  - 0b0111 or 0x7: 16-bit Trap Gate
+  - 0b1110 or 0xE: 32-bit Interrupt Gate
+  - 0b1111 or 0xF: 32-bit Trap Gate
+- **DPL**: A 2-bit value which defines the CPU Privilege Levels which are allowed to access this interrupt via the INT instruction. Hardware interrupts ignore this mechanism.
+- **P**: Present bit. Must be set (1) for the descriptor to be valid.
+
+For more information, see **Section 6.11: IDT Descriptors** and **Figure 6-2: IDT Gate Descriptors** of the [Intel Software Developer Manual, Volume 3-A][0].
+
+**Example Code**
+C Struct:
+
+```c
+struct InterruptDescriptor32 {
+   uint16_t offset_1;        // offset bits 0..15
+   uint16_t selector;        // a code segment selector in GDT or LDT
+   uint8_t  zero;            // unused, set to 0
+   uint8_t  type_attributes; // gate type, dpl, and p fields
+   uint16_t offset_2;        // offset bits 16..31
+};
+```
+
+Example type_attributes values that people are likely to use (assuming DPL is 0):
+
+- 32-bit Interrupt Gate: 0x8E (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110=0x8E)
+- 32-bit Trap Gate: 0x8F (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b=0x8F)
+- Task Gate: 0x85 (p=1, dpl=0b00, type=0b0101 => type_attributes=0b1000_0101=0x85)
+
+[0]: https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html

+ 161 - 0
doc/pic.md

@@ -0,0 +1,161 @@
+# 8259 Programmable Interrupt Controller (PIC)
+
+ Initialization Command Word 1 at Port 20h and A0h
+
+```sh
+  │7│6│5│4│3│2│1│0│  ICW1
+  │ │ │ │ │ │ │ └──── 1=ICW4 is needed, 0=no ICW4 needed
+  │ │ │ │ │ │ └───── 1=single 8259, 0=cascading 8259's
+  │ │ │ │ │ └────── 1=4 byte interrupt vectors, 0=8 byte int vectors
+  │ │ │ │ └─────── 1=level triggered mode, 0=edge triggered mode
+  │ │ │ └──────── must be 1 for ICW1 (port must also be 20h or A0h)
+  └─┴─┴───────── must be zero for PC systems
+
+
+ Initialization Command Word 2 at Port 21h and A1h
+
+ │7│6│5│4│3│2│1│0│  ICW2
+  │ │ │ │ │ └─┴─┴──── 000= on 80x86 systems
+  └─┴─┴─┴─┴───────── A7-A3 of 80x86 interrupt vector
+
+ Initialization Command Word 3 at Port 21h and A1h
+
+ │7│6│5│4│3│2│1│0│  ICW3 for Master Device
+  │ │ │ │ │ │ │ └──── 1=interrupt request 0 has slave, 0=no slave
+  │ │ │ │ │ │ └───── 1=interrupt request 1 has slave, 0=no slave
+  │ │ │ │ │ └────── 1=interrupt request 2 has slave, 0=no slave
+  │ │ │ │ └─────── 1=interrupt request 3 has slave, 0=no slave
+  │ │ │ └──────── 1=interrupt request 4 has slave, 0=no slave
+  │ │ └───────── 1=interrupt request 5 has slave, 0=no slave
+  │ └────────── 1=interrupt request 6 has slave, 0=no slave
+  └─────────── 1=interrupt request 7 has slave, 0=no slave
+
+ │7│6│5│4│3│2│1│0│  ICW3 for Slave Device
+  │ │ │ │ │ └─┴─┴──── master interrupt request slave is attached to
+  └─┴─┴─┴─┴───────── must be zero
+
+
+ Initialization Command Word 4 at Port 21h and A1h
+
+ │7│6│5│4│3│2│1│0│  ICW4
+  │ │ │ │ │ │ │ └──── 1 for 80x86 mode, 0 = MCS 80/85 mode
+  │ │ │ │ │ │ └───── 1 = auto EOI, 0=normal EOI
+  │ │ │ │ └─┴────── slave/master buffered mode (see below)
+  │ │ │ └───────── 1 = special fully nested mode (SFNM), 0=sequential
+  └─┴─┴────────── unused (set to zero)
+
+ Bits
+  32 Buffering Mode
+  00 not buffered
+  01 not buffered
+  10 buffered mode slave (PC mode)
+  11 buffered mode master (PC mode)
+
+ Operation Control Word 1 / Interrupt Mask Reg. (Ports 21h & A1h)
+
+ │7│6│5│4│3│2│1│0│  OCW1 - IMR Interrupt Mask Register
+  │ │ │ │ │ │ │ └──── 0 = service IRQ0, 1 = mask off
+  │ │ │ │ │ │ └───── 0 = service IRQ1, 1 = mask off
+  │ │ │ │ │ └────── 0 = service IRQ2, 1 = mask off
+  │ │ │ │ └─────── 0 = service IRQ3, 1 = mask off
+  │ │ │ └──────── 0 = service IRQ4, 1 = mask off
+  │ │ └───────── 0 = service IRQ5, 1 = mask off
+  │ └────────── 0 = service IRQ6, 1 = mask off
+  └─────────── 0 = service IRQ7, 1 = mask off
+
+ Operation Control Word 2 / Interrupt Command Reg. (Ports 20h & A0h)
+
+ │7│6│5│4│3│2│1│0│  OCW2 - ICR Interrupt Command Register
+  │ │ │ │ │ └─┴─┴──── interrupt request level to act upon
+  │ │ │ │ └───────── must be 0 for OCW2
+  │ │ │ └────────── must be 0 for OCW2
+  └─┴─┴─────────── EOI type (see table)
+
+ Bits
+ 765  EOI - End Of Interrupt code (PC specific)
+ 001  non-specific EOI command
+ 010  NOP
+ 011  specific EOI command
+ 100  rotate in automatic EOI mode
+ 101  rotate on non-specific EOI command
+ 110  set priority command  (uses bits 2-0)
+ 111  rotate on specific EOI command
+
+ Operation Control Word 3   (Ports 20h & A0h)
+
+ │7│6│5│4│3│2│1│0│  OCW3
+  │ │ │ │ │ │ │ └─── 1=read IRR on next read, 0=read ISR on next read
+  │ │ │ │ │ │ └──── 1=act on value of bit 0, 0=no action if bit 0 set
+  │ │ │ │ │ └───── 1=poll command issued, 0=no poll command issued
+  │ │ │ │ └────── must be 1 for OCW3
+  │ │ │ └─────── must be 0 for OCW3
+  │ │ └──────── 1=set special mask, 0=reset special mask
+  │ └───────── 1=act on value of bit 5, 0=no action if bit 5 set
+  └────────── not used (zero)
+```
+
+ Other Registers
+
+ IRR - Interrupt Request Register, maintains a bit vector indicating
+       which IRQ hardware events are awaiting service. Highest
+       level interrupt is reset when the CPU acknowledges interrupt.
+ ISR - In Service Register, tracks IRQ line currently being serviced.
+       Updated by EOI command.
+
+Hardware Interrupt Sequence of Events:
+
+ 1. 8259 IRQ signal is raised high by hardware setting the
+    corresponding IRR bits true.
+
+ 2. PIC evaluates the interrupt requests and signals the CPU
+    where appropriate.
+
+ 3. CPU acknowledges the INT by pulsing INTA (inverted)
+
+ 4. INTA signal from CPU is received by the PIC, which then sets the
+    highest priority ISR bit, and clears the corresponding IRR bit
+
+ 5. CPU sends a second INTA pulse which causes the PIC to send the
+    interrupt ID byte onto the bus.  CPU begins interrupt processing.
+
+ 6. Interrupts of lower and equal priority are blocked until a
+    Non-Specific EOI (20h) is sent to the command port.
+
+Initialization Procedure
+
+Initialization
+
+  1. write ICW1 to port 20h
+  2. write ICW2 to port 21h
+  3. if ICW1 bit D1=1  do nothing
+     if ICW1 bit D1=0  write ICW3 to port 20h
+  4. write ICW4 to port 21h
+  5. OCW's can follow in any order
+
+8259 Programmable Interrupt Controller Notes
+
+- Operation Command Word (OCW), commands that set the 8259 in
+   various interrupt modes.  These can be written to the 8259
+   anytime after initialization.
+- The 8259 differentiates between the OCW1, OCW2 and OCW3 by the
+   port address and the value of the data bits D4 and D3.  ICW2
+   through ICW4 are order related and must follow ICW1. ICW1 is
+   identified by an even port number and data bit D4 set to 1.
+- PCs operate in fully nested mode, where a Non-Specific EOI resets
+   the interrupt identified by the highest bit value in the ISR
+- 8259s can be chained together where the INT pin (output) of a
+   slave 8259 can be used as the input to an IRQ line allowing up
+   to 64 priority vectored interrupts.  AT level machines use two
+   8259's for a total of 16 hardware interrupt levels
+- the first 8259 ports are located at 20h and 21h
+- the second 8259 ports are located at A0h and A1h
+- PC and AT interrupts are Edge Triggered while PS/2's are Level
+   Triggered
+- some ASIC chips designed for Tandy 1000 Systems malfunction if
+   specific and non-specific EOIs are mixed
+- for a more in-depth discussion of the 8259, see Intel's "Micro-
+   processor and Peripherals Handbook, Volume I"
+- see  [PORTS](https://helppc.netcore2k.net/hardware/ports)  and  [INT TABLE](https://helppc.netcore2k.net/interrupt/int-table)
+
+
+ [来源](https://helppc.netcore2k.net/hardware/8259)

+ 30 - 0
kernel/global.h

@@ -0,0 +1,30 @@
+#ifndef __KERNEL_GLOBAL_H
+#define __KERNEL_GLOBAL_H
+#include "stdint.h"
+
+#define RPL0 0
+#define RPL1 1
+#define RPL2 2
+#define RPL3 3
+
+#define TI_GDT 0x00
+#define TI_LDT 0x01
+
+#define PG_P 1
+
+#define SELECTOR_K_CODE ((1 << 3) + (TI_GDT << 2) + RPL0)
+#define SELECTOR_K_DATA ((2 << 3) + (TI_GDT << 2) + RPL0)
+#define SELECTOR_K_STACK SELECTOR_K_DATA
+#define SELECTOR_K_GS ((3 << 3) + (TI_GDT << 2) + RPL0)
+
+/*---------------------- IDT 描述符属性 -----------------------------------------*/
+#define IDT_DESC_P 1
+#define IDT_DESC_DPL0 0
+#define IDT_DESC_DPL3 3
+#define IDT_DESC_32_TYPE 0xE // 32 位的门
+#define IDT_DESC_16_TYPE 0x6 // 16 位的门,不用,定义它只为和 32 位门区分
+
+#define IDT_DESC_ATTR_DPL0 ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE)
+#define IDT_DESC_ATTR_DPL3 ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE)
+
+#endif // __KERNEL_GLOBAL_H

+ 9 - 0
kernel/init.c

@@ -0,0 +1,9 @@
+#include "init.h"
+#include "../lib/kernel/print.h"
+#include "interrupt.h"
+
+void init_all()
+{
+    put_str("init_all\n");
+    itd_init(); // 初始化中断
+}

+ 6 - 0
kernel/init.h

@@ -0,0 +1,6 @@
+#ifndef __KERNEL_INIT_H
+#define __KERNEL_INIT_H
+
+void init_all();
+
+#endif // __KERNEL_INIT_H

+ 60 - 0
kernel/interrupt.c

@@ -0,0 +1,60 @@
+#include "interrupt.h"
+#include "stdint.h"
+#include "global.h"
+#include "../lib/kernel/print.h"
+#include "../lib/kernel/io.h"
+
+/*创建中断门描述符*/
+static void make_idt_desc(gate_desc *p_gdesc, uint8_t attr, intr_handler function)
+{
+    p_gdesc->offset_low = (uint32_t)function & 0x0000FFFF;
+    p_gdesc->selector = SELECTOR_K_CODE;
+    p_gdesc->dcount = 0;
+    p_gdesc->attribute = attr;
+    p_gdesc->offset_high = ((uint32_t)function & 0xFFFF0000) >> 16;
+}
+/* 初始化中断描述符表*/
+static void itd_desc_init(void)
+{
+    int i;
+    for (i = 0; i < IDT_DESC_CNT; i++)
+    {
+        make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
+    }
+    put_str("   idt_desc_init done\n");
+}
+
+/* 初始化可编程中断控制器 8259A */
+static void pic_init(void)
+{
+    /* 初始化主片*/
+    outb(PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要 ICW4
+    outb(PIC_M_DATA, 0x20); // ICW2: 起始中断向量号 0x20, 也就是 IRQ[0~7] 为 0x20~0x27
+    outb(PIC_M_DATA, 0x04); // ICW3: IR2 接从片
+    outb(PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI
+
+    /* 初始化从片 */
+    outb(PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要 ICW4
+    outb(PIC_S_DATA, 0x28); // ICW2: 起始中断向量号 0x28, 也就是 IRQ[8~15] 以 0x28~0x2F
+    outb(PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的 IR2 引脚
+    outb(PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI
+
+    /* 打开主片上 IR0, 也就是目前只接受时钟产生的中断*/
+    outb(PIC_M_DATA, 0xfe);
+    outb(PIC_S_DATA, 0xff);
+
+    put_str("   pic_init done\n");
+}
+
+/* 中断初始化主函数 */
+void itd_init(void)
+{
+    put_str("init_idt start\n");
+    itd_desc_init();
+    pic_init(); // 初始化可编程中断控制器8259A
+
+    /* 加载 idt */
+    uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16)));
+    asm volatile("lidt %0" ::"m"(idt_operand));
+    put_str("itd_init done\n");
+}

+ 36 - 0
kernel/interrupt.h

@@ -0,0 +1,36 @@
+#ifndef __KERNEL_INTERRUPT_H
+#define __KERNEL_INTERRUPT_H
+
+#include "stdint.h"
+#include "global.h"
+
+#define IDT_DESC_CNT 0x21 // 目前支持的中断数
+#define PIC_M_CTRL 0x20   // 主片的控制端口是 0x20
+#define PIC_M_DATA 0x21   // 主片的数据端口是 0x21
+#define PIC_S_CTRL 0xA0   // 从片的控制端口是 0xA0
+#define PIC_S_DATA 0xA1   // 从片的数据端口是 0xA1
+
+typedef void *intr_handler;
+
+/* 中断门描述符结构体 */
+// Example type_attributes values that people are likely to use (assuming DPL is 0):
+// 32-bit Interrupt Gate: 0x8E (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110=0x8E)
+// 32-bit Trap Gate: 0x8F (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b=0x8F)
+// Task Gate: 0x85 (p=1, dpl=0b00, type=0b0101 => type_attributes=0b1000_0101=0x85)
+typedef struct
+{
+    uint16_t offset_low;  // 16位 偏移量的低 16 位 (0..15)
+    uint16_t selector;    // 选择器 acode segment selector in GDT or LDT
+    uint8_t dcount;       // 置 0
+    uint8_t attribute;    // 述符类型 (bit 7:0)  gate type, dpl, and p fields
+    uint16_t offset_high; // 16 位 偏移量的高 16 位 (16..31)
+} __attribute__((packed)) gate_desc;
+
+static void make_idt_desc(gate_desc *p_gdesc, uint8_t attr, intr_handler function);
+static void itd_desc_init(void);
+void itd_init(void);
+
+static gate_desc idt[IDT_DESC_CNT];                 // 中断描述符表,本质上就是个中断门描述符数组
+extern intr_handler intr_entry_table[IDT_DESC_CNT]; // 声明引用定义在 kernel.S 中的中断处理函数入口数组
+
+#endif // __KERNEL_INTERRUPT_H

+ 68 - 0
kernel/kernel.S

@@ -0,0 +1,68 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; kernel.S - 中断处理程序
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[bits 32]
+%define ERROR_CODE nop 
+%define ZERO push 0
+
+extern put_str       ; 声明外部函数
+
+section .data
+intr_str db "interrup occur!", 0xa, 0
+global intr_entry_table
+intr_entry_table:
+
+%macro VECTOR 2
+section .text
+intr%1entry:         ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序
+    %2
+    push intr_str
+    call put_str
+    add esp, 4       ; 清除参数
+
+    ; 如果从片上进入的中断,除了从片上发送 EOI 外,还要往主片上发送 EOI
+    mov al, 0x20    ; EOI 中断结束命令
+    out 0xa0, al    ; 发送给从片
+    out 0x20, al    ; 发送给主片
+
+    add esp, 4      ; 清除错误码, error_code
+    iret            ; 中断返回, 会自动从栈中弹出 eip, cs, eflags, esp, ss
+
+section .data
+    dd intr%1entry  ; 存储各个中断入口程序的地址,形成 intr_entry_table 数组
+
+%endmacro
+
+VECTOR 0x00, ZERO
+VECTOR 0x01, ZERO
+VECTOR 0x02, ZERO
+VECTOR 0x03, ZERO
+VECTOR 0x04, ZERO
+VECTOR 0x05, ZERO
+VECTOR 0x06, ZERO
+VECTOR 0x07, ZERO
+VECTOR 0x08, ZERO
+VECTOR 0x09, ZERO
+VECTOR 0x0a, ZERO
+VECTOR 0x0b, ZERO
+VECTOR 0x0c, ZERO
+VECTOR 0x0d, ZERO
+VECTOR 0x0e, ZERO
+VECTOR 0x0f, ZERO
+VECTOR 0x10, ZERO
+VECTOR 0x11, ZERO
+VECTOR 0x12, ZERO
+VECTOR 0x13, ZERO
+VECTOR 0x14, ZERO
+VECTOR 0x15, ZERO
+VECTOR 0x16, ZERO
+VECTOR 0x17, ZERO
+VECTOR 0x18, ZERO
+VECTOR 0x19, ZERO
+VECTOR 0x1a, ZERO
+VECTOR 0x1b, ZERO
+VECTOR 0x1c, ZERO
+VECTOR 0x1d, ZERO
+VECTOR 0x1e, ERROR_CODE
+VECTOR 0x1f, ZERO
+VECTOR 0x20, ZERO

+ 3 - 9
kernel/main.c

@@ -1,17 +1,11 @@
 #include "print.h"
+#include "init.h"
 
 int main(void)
 {
     put_str("I am kernel\n");
-    put_int(0);
-    put_char('\n');
-    put_int(9);
-    put_char('\n');
-    put_int(0x00021a3f);
-    put_char('\n');
-    put_int(0x12345678);
-    put_char('\n');
-    put_int(0x00000);
+    init_all();
+    asm volatile("sti"); // 使能中断
     while (1)
         ;
     return 0;

+ 39 - 0
lib/kernel/io.h

@@ -0,0 +1,39 @@
+#ifndef __LIB_KERNEL_IO_H
+#define __LIB_KERNEL_IO_H
+#include "stdint.h"
+
+/// @brief 向端口写入一个字节
+/// @param port 0~255
+/// @param data
+static inline void outb(uint16_t port, uint8_t data)
+{
+    // 对端口指定 N 表示 0~255,d 表示用 dx 存储端口号, %b0 表示对应 al,%w1 表示对应 dx
+    asm volatile("outb %b0, %w1" : : "a"(data), "Nd"(port));
+}
+
+/// @brief 将 addr 处起始的 word_cnt 个字写入端口 port
+/// @param port 端口号
+/// @param addr 地址
+/// @param word_cnt 个数
+static inline void outsw(uint16_t port, const void *addr, uint32_t word_cnt)
+{
+    // + 表示此限制即做输入又做输出 outsw 是把 ds:esi 处的 16 位的内容写入 port 端口,共写入 word_cnt 个
+    asm volatile("cld; rep outsw" : "+S"(addr), "+c"(word_cnt) : "d"(port));
+}
+
+/// @brief 读取端口 port 的一个字节
+/// @param port 端口号
+static inline uint8_t inb(uint16_t port)
+{
+    uint8_t data;
+    asm volatile("inb %w1, %b0" : "=a"(data) : "Nd"(port));
+    return data;
+}
+
+static inline void insw(uint16_t port, void *addr, uint32_t word_cnt)
+{
+    // insw 是把从端口 port 处读入的 16 位内容写入 es:edi 指向的内存,共 word_cnt 个
+    asm volatile("cld; rep insw" : "+D"(addr), "+c"(word_cnt) : "d"(port) : "memory");
+}
+
+#endif // __LIB_KERNEL_IO_H

+ 3 - 3
lib/kernel/print.S

@@ -106,9 +106,9 @@ put_char:
 .roll_screen:       ; 若超出屏幕大小,开始滚屏
     cld                 ; DF = 0
     mov ecx, 960        ; 2000-80=1920 个字符要搬运,共 1920*2=3840 字节,一次搬运 4 字节,共 960 次
-    mov edi, 0xc00b8000 ; 第 0 行行首
-    mov esi, 0xc00b80a0 ; 第 1 行行首 0xc00b8000 + (80 * 2)
-    rep movsb
+    mov edi, 0xb8000 ; 第 0 行行首
+    mov esi, 0xb80a0 ; 第 1 行行首 0xc00b8000 + (80 * 2)
+    rep movsd
 
     ; 将最后一行填充为空白
     mov ebx, 3840    ; 最后一行首字符的第一个字节偏移=1920*2