iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0

昨天介紹完 Memory-Mapped I/O (MMIO) 之後,今天我們要繼續探討另一種周邊設備與作業系統的溝通模式——Port-Mapped I/O (PMIO)。

PMIO 基本概念

當作業系統使用 MMIO 操作周邊設備時,是透過一般的讀寫記憶體指令,並提供物理記憶體位址 (Physical Address Space) 來進行操作。然而,PMIO 則是採用一種獨立的周邊設備操作方式。每個周邊設備會提供若干個 "Port"(類似暫存器),讓作業系統和周邊設備進行讀寫,資料交換。

// drivers/net/ethernet/8390/ne.c
#define NE_CMD	 	0x00
#define NE_DATAPORT	0x10	/* NatSemi-defined port window offset. */
#define NE_RESET	0x1f	/* Issue a read to reset, a write to clear. */
#define NE_IO_EXTENT	0x20

#define NE1SM_START_PG	0x20	/* First page of TX buffer */
#define NE1SM_STOP_PG 	0x40	/* Last page +1 of RX ring */
#define NESM_START_PG	0x40	/* First page of TX buffer */
#define NESM_STOP_PG	0x80	/* Last page +1 of RX ring */

例如,在 Intel 8390 網卡的驅動程式中,定義了 8 個 Port,例如用來傳輸指令 (如 NE_CMD) 和資料 (如 NE_DATAPORT)。

由於周邊設備的數量是動態變化的,為了有效管理這些 Port,處理器會將所有 Port 整合,形成一個獨立於實體記憶體空間的地址空間,這個空間稱為 I/O space。

I/O Space 的管理

在 I/O space 中,每個設備的 Port 都會佔據特定的連續範圍。透過 /proc/ioports,我們可以查看所有周邊設備的 Port 在 I/O space 裡的分佈情況:

> cat /proc/ioports
000-0cf7 : PCI Bus 0000:00
  0000-001f : dma1
  0020-0021 : pic1
  0040-0043 : timer0
  0050-0053 : timer1
  0060-0060 : keyboard
  0062-0062 : PNP0C09:01
    0062-0062 : EC data
  0064-0064 : keyboard
  0066-0066 : PNP0C09:01
    0066-0066 : EC cmd
  0070-0071 : rtc_cmos
    0070-0071 : rtc0
  0080-008f : dma page reg
  00a0-00a1 : pic2
  00c0-00df : dma2
  00f0-00ff : fpu
  0290-029f : pnp 00:00
  03f8-03ff : serial
  0400-041f : iTCO_wdt
  0680-069f : pnp 00:01

這段內容顯示了 PCI Bus 以及其他設備在 I/O space 中的位址範圍。由於 I/O port 是獨立於記憶體空間的,因此讀寫操作需要使用特定的指令。

I/O Space 操作指令

在 Intel 架構中,作業系統透過 inout 指令來讀寫 I/O port,這些指令專門用於與設備的 Port 進行資料交換。Linux Kernel 對該指令進行了封裝。

// arch/x86/include/asm/shared/io.h

static __always_inline void __out##bwl(type value, u16 port)        \
{                                                                   \
	asm volatile("out" #bwl " %" #bw "0, %w1"	                    \
		     : : "a"(value), "Nd"(port));		                    \
}                                                                   \
                                                                    \
static __always_inline type __in##bwl(u16 port)	                    \
{                                                                   \
	type value;	                                                    \
	asm volatile("in" #bwl " %w1, %" #bw "0"                        \
		     : "=a"(value) : "Nd"(port));                           \
	return value;                                                   \
}

BUILDIO(b, b, u8)
BUILDIO(w, w, u16)
BUILDIO(l,  , u32)
#undef BUILDIO

#define inb __inb
#define inw __inw
#define inl __inl
#define outb __outb
#define outw __outw
#define outl __outl

上述程式碼中,inb, inw, inl, outb, outw, outl 這幾個函數分別對應 inout 指令的封裝,並提供 8-bit、16-bit、32-bit 等不同大小的版本,方便驅動程式操作 I/O port。

例如,以下程式碼展示了如何向網卡的 EN0_RCNTLO Port 寫入資料:

// source/drivers/net/ethernet/8390/ne.c
outb_p(count & 0xff, nic_base + EN0_RCNTLO);

static inline void outb_p(u8 value, unsigned long addr)
{
	outb(value, addr);
}

與 MMIO 相似,我們同樣會遇到一個問題:設備的 I/O port 在 I/O Address Space 中的位置是如何決定與分配的呢?這個問題需要進一步探討。在明天的文章中,我們會深入討論這個問題,並解釋設備在 I/O space 中的地址分配邏輯。


上一篇
Memory-Mapped IO
下一篇
PCI Configuration Space
系列文
Linux Kernel 網路巡禮30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言