iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0

這幾天我們處理了文字模式畫面與硬碟(INT 13h)讀寫。若要讓 loader 能把資料搬進記憶體、再順利切換到保護模式甚至長模式(IA-32e),我們必須提供 2 個記憶體服務: 1.A20 gate 與 E820,A20 gate 可以讓實模式的地址線不會在 1MB 處回繞。而 E820 則可提供真實的記憶體布局給 bootloader。

A20 gate

A20 gate 是為了相容性的歷史遺留。在 8088 時代,CPU 只有 20 條地址線,尋址範圍僅 2 ^ 20 = 1 MB,一旦位址超過 1 MB,就會在第 21 位 (A20) 被截斷而回繞道 0。到了 80286 開始,地址線擴充,CPU 具備了超過 1 MB 的尋址能力,但為了維持既有軟體行為,IBM 在鍵盤控制器中加入了 AND 邏輯閘,來控制是否允許訪問超過 1MB 這就是 A20 gate。這個開關後來演變成更快的 0x92 (Fast A20)。
開啟 A20 gate 只能保證記憶體尋址不回繞,若想讓實模式下訪問高地址,常見做法是一種叫 big real mode 的技巧,做法是先短暫切到保護模式,準備一份含有 base=0、limit = 4 GB 的段描述符,把它載入段暫存器如 fs,接著把 CR0.PE 清除回到實模式,由於段暫存器的隱藏部分還保留著 4 GB 的界限。此時我們可以用 32 位暫存器在高位址做資料搬運,但注意如果重載了這些暫存器將失去這個尋址能力,回到 1MB 的限制。

由於我們在 KVM 上實作,不需要真的還原這個細節,可以是為 A20 gate 永遠開啟,因始只要攔截到 0x92 或是 8042 鍵盤控制器操作 0x60 0x64 寫入都直接默認開啟(成功)。

int a20_register(struct a20_device *dev, struct io_bus *bus)
{
    if (!dev || !bus)
        return -1;

    struct io_device controller = {
        .port_start = 0x60u,
        .port_end = 0x64u,
        .in = a20_io_in,
        .out = a20_io_out,
        .opaque = dev,
    };

    int ret = io_bus_register(bus, &controller);
    if (ret < 0)
        return ret;

    struct io_device fast = {
        .port_start = 0x92u,
        .port_end = 0x92u,
        .in = a20_io_in,
        .out = a20_io_out,
        .opaque = dev,
    };

    ret = io_bus_register(bus, &fast);
    if (ret < 0)
        return ret;

    return 0;
}

在這裡我們分別註冊兩個塊路徑與慢路徑,其中 0x60 與 0x64 的寫入是透過鍵盤控制器處理,而 0x92 則是快路徑。
開啟 A20 gate 的程式碼如下:

慢路徑

call    wait_keyboard          ; 等 8042 可寫(等 IBF=0)
mov     al,0xd1                ; 告訴 8042 : 下一個寫到 0x60 的值
out     0x64,al

call    wait_keyboard          ; 再等一次 IBF=0
mov     AL,0xdf                ; 要寫入的輸出埠值 : bit1=1 -> A20 = ON
out     0x60,al

call    wait_keyboard          ; 寫完再等 IBF 清掉

0xd1 是 write output port 指令,他會告訴 8042 接下來會把直寫入 0x60。而在 0x60 中寫入 0xdf 則相當於輸入 Enable A20 address line 命令。而 wait_keyboard 可以是任何的延遲程式碼,目的只是為了讓鍵盤控制器清空。
可以參考這裡

快路徑

    in      al,0X92           ; 
    or      al,10b            ; 0x92的 bit1 控制A20 gate
    out     92h,    al        ; 開啟A20 gate

快路徑則簡單很多。

為了模擬這個行為我們將針對各自 60h 64h 92h, 3 個端口各自準備函數,92h 默認直接寫入值。

static enum exit_handle_status a20_io_out(void *opaque, const struct io_port_access *access) {
    struct a20_device *dev = opaque;
    if (!dev || !access || !access->data || access->size == 0)
        return EXIT_HANDLE_ERROR;
    switch (access->port) {
    case 0x60u:
        a20_handle_write_60(dev, access);
        break;
    case 0x64u:
        a20_handle_write_64(dev, access);
        break;
    case 0x92u:
        a20_handle_write_92(dev, access);
        break;
    default:
        break;
    }
    return EXIT_HANDLE_CONTINUE;
}

以下說明 0x60 與 0x64 兩個 port。

static void a20_handle_write_64(struct a20_device *dev, const struct io_port_access *access)
{
    size_t stride = access->size;

    for (uint32_t i = 0; i < access->count; ++i) {
        const unsigned char *src = access->data + (size_t)i * stride;
        uint8_t value = src[0];

        switch (value) {
        case 0xd0u:
            dev->pending_output_read = 1;
            dev->status |= KBC_STATUS_OBF;
            dev->status &= (uint8_t)~KBC_STATUS_IBF;
            break;
        case 0xd1u:
            dev->expecting_output_write = 1;
            dev->status |= KBC_STATUS_IBF;
            break;
        default:
            dev->status &= (uint8_t)~KBC_STATUS_IBF;
            break;
        }
    }
}

在 io port 0x64 中我們只模擬兩個命令: 0xd0(read output port)與 0xd1(write output oort)。
OBF = 1,則告訴 CPU 0x60 有資料可讀,而 IBF = 1 則表示鍵盤控制器正在等待 CPU 將資料送入 0x60。

static void a20_handle_write_60(struct a20_device *dev, const struct io_port_access *access)
{
    if (!dev->expecting_output_write)
        return;

    const unsigned char *src = access->data;
    if (!src)
        return;

    a20_apply_output_value(dev, src[0]);
    dev->expecting_output_write = 0;
    dev->status &= (uint8_t)~KBC_STATUS_IBF;
}

在 0x60 port 中,我們會檢查 expecting_output_write 在 0x64 寫入 0xd1 時這個值才是 1,此時才允許寫入 0x60,a20_apply_output_value 會設定 A20 bit1 = 1。


上一篇
Day 22 實現螢幕設備(2)
系列文
30 天 hypervisor 入門23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言