Ch9 Communicating with Hardware Linux Device Driver Ch9 Communicating with Hardware
Content I/O 埠 與 I/O 記憶體 使用 I/O 埠 使用數位 I/O 埠 I/O 暫存器與傳統記憶體 字串操作 暫停 I/O 平台相依性 使用數位 I/O 埠 並列埠的基本概念 short驅動程式
Content -cont. 使用 I/O 記憶體 直接映射記憶體 使用short 測試 I/O 記憶體 軟體映射的 I/O 記憶體 位於1MB以下的 ISA記憶體 isa_readb()與相關函式
I/O 埠 與 I/O 記憶體 幾乎每一種周邊裝置的控制,都是藉由 register 來達 成的,這些暫存器,可能在記憶體空間,也可能在 I/O 空間。 目前流行的周邊匯流排是以PC架構為模型;因此,即 使沒將 I/O 位址空間獨立出來的處理器,在存取這些 週邊裝置時,也必須”假裝”讀寫 I/O 埠。因此, Linux 虛構一組 I/O 埠存取架構。 一般認為,I/O 記憶區是比較適當的形式,他有下列 優點: 不需要用到特殊用途的 CPU 指令。 CPU core 對記憶體的存取效率相對於專用 I/O 指令而言較高。 在暫存器的分配及定址模式上,由編輯器產生存取記憶體的 程式碼有較自由的選擇。
I/O 埠 與 I/O 記憶體 直接映射記憶體 I/O 暫存器和RAM最大的差異,在於 I/O 操作有”副作用”: 存取記憶體只是改變目標的儲存值。 存取 I/O,目的不在於儲存值,許多控制是藉由”讀取”和“寫入”的 動作來改變裝置的狀態。 將原本施加於記憶體最佳化技術施(cache)加於 I/O 暫存器身上, 則無法產生 I/O 暫存器操作想要的“副作用”。 解決方法:在必須以原貌出現在硬體上的程式之間,加上記憶屏 障(memory barrier)。
I/O 埠 與 I/O 記憶體 memory barrier #include <linux/kernel.h> void barrier(void) 要求編譯器編譯出來的程式碼,被修改存在CPU暫存器的值,會確 實寫回記憶體: #include <asm/system.h> void rmb(void); void read_barrier_depends(void); void wmb(void); void mb(void); rmb()保證出現在barrier之前的讀取動作,都會在後續的任何讀取 動作之前如實完成。 wmb()保證寫出動作會被依序徹底完成。 mb() 保證 讀、寫動作都會落實執行。
I/O 埠 與 I/O 記憶體 void smp_rmb(void); void smp_read_barrier_depends(void); void smp_wmb(void); void smp_mb(void); 同前述函式,但只在支援SMP的system有作用 使用barrier的例子: writel(dev->registers.addr, io_destination_address); writel(dev->registers.size, io_size); writel(dev->registers.operation, DEV_READ); wmb( ); writel(dev->registers.control, DEV_GO); 前面三個writel都完成後,才會進行writel(dev-> …….)的動作。
I/O 埠 與 I/O 記憶體 某些平台容許「一次設值(atomic_t)」和「一個記憶體屏障」組成比較有 效率的執行單位,提供以下巨集: #define set_mb(var, value) do {var = value; mb( );} while 0 #define set_wmb(var, value) do {var = value; wmb( );} while 0 #define set_rmb(var, value) do {var = value; rmb( );} while 0
使用 I/O 埠 #include <linux/ioport.h> int check_region(unsigned long first, unsigned long n); struct resource *request_region(unsigned long first, unsigned long n,const char *name); void release_region(unsigned long start, unsigned long n); 大部分的硬體會區分 8-bit 16-bit 32-bit埠,因此不能像平常存許系統記憶 體那樣混用。
使用 I/O 埠 Linux核心中的<asm/io.h>定義以下用來存取I/O埠的內插函式: unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); 讀寫1-byte埠,某些平台上port定義成unsigned long,回傳值也因平台 而易。 unsigned inw(unsigned port); void outw(unsigned short word, unsigned port); 存取16-bits埠,在只支援byte I/O的平台不存在。 unsigned inl(unsigned port); void outl(unsigned longword, unsigned port); 存取32-bits埠,longword有可能被宣告成unsigned long或是unsigned int 64-bits port I/O 並不存在。
使用 I/O 埠 前述 I/O 函式也可以在user space使用,GUN定義在<sys/io.h>,但 必須遵守以下條件: 必須先使用ioperm() 或iopl()取得目標I/O埠的存取權,這兩個函式只能 用在Intel系統上。 程式本身要以root的身分呼叫ioerm()或iopl(),或是他的父行程之一必 須已經用root取得I/O埠的存取權。 範例程式:misc-progs/inp.c、misc-progs/outp.c
使用 I/O 埠 字串操作(string intructions):某些處理器提供特殊指令能 夠將依連串同等大小的bytes、word、longs讀入、寫出到一個 I/O埠。 字串操作的巨集原型: void insb(unsigned port, void *addr, unsigned long count); void outsb(unsigned port, void *addr, unsigned long count); 前者從port讀取count個位元組存入addr位址上的記憶體,後 者將addr位址上的count個位元組寫入port void insw(unsigned port, void *addr, unsigned long count); void outsw(unsigned port, void *addr, unsigned long count); 同上面的函式,以16-bits word為單位。 void insl(unsigned port, void *addr, unsigned long count); void outsl(unsigned port, void *addr, unsigned long count); 同上面的函式,以32-bits long word為單位。
使用 I/O 埠 暫停 I/O:當處理器與匯流排之間的傳輸率過快,導致裝置漏失一 些資料。 改採一些會暫停的函式來代替正常函式,其作用和用法與之前那些 正常I/O相同,只在名稱末端多了 _p字樣(e.g. inb_p() 、outb_p())
使用 I/O 埠 平台相依性 I/O 指令天生和處理器之間有著高度的相依性,因為他們控制著資 料如何進出處理器的細節;因此,程式中涉及I/O埠的部份,幾乎 都必須針對特定平台來設計,而沒有一體性的寫法。 各平台差異請詳見課本 p.243 ~ p.244
使用數位 I/O 埠 數位I/O埠最平常的具體形式,是一個單位元組寬的I/O位置,該位址 可能映射到記憶空間,但也可能有專屬的I/O空間。 並列埠的基本概念 依照PC標準規格每台可以配置兩個並列埠介面,第一個介面起始位置是 0x378,第二個是0x278。並列埠的基本模式是由三個8-bit埠構成;第一個 埠是雙向的資料暫存器,直接連到實體街頭的pin2 ~ pin9;第二埠是一個 唯讀的暫存器;第三埠是控制暫存器(能寫不能讀)。
使用數位 I/O 埠 並列埠各個位元的規格
使用數位 I/O 埠 short(Simple Hardware Operations and Raw Tests)驅動程式 short只能讀、寫在載入期指定的少數幾個 8-bits埠。 short驅動程式不會做任何有用的事情,只讓我們將I/O指令作用到指 定的埠。 不能用short存取已被其他驅動程式佔用的裝置。 short宗旨:在任何Linux平台測試任何可透過outb()和inb()來存取數 位I/O介面。
使用數位 I/O 埠 short本身操作- /dev/short0 是 I/O的基底位址8-bits管道,能 夠一次寫出或讀入8-bits資料。/dev/short1對應到base + 1, 以此類推到short7。 輸出動作 while (count--) { outb(*(ptr++), port); wmb( ); } #echo –n “any string” > /dev/short0 點亮LED燈,只有最後一個字元能夠被看到,因此加上-n選項,在 字串末端加上‘\n’。 讀入動作 outb()換成inb()。 讀出port 0x378的值: #dd if=/dev/short0 bs=1 count=1 | od –t x1 Memory barrier 確保動作不會被最佳化處理掉
使用數位 I/O 埠 short三種變形 /dev/short0 用前述的緊密迴圈 /dev/short0p使用outb_p() 和 inb_p() /dev/short0s使用字串函式
使用 I/O 記憶體 I/O 記憶體是性質類似於RAM的特殊區域,處理器可直接從其 匯流排存取特定的硬體裝置,意即,讀寫這些區域會產生“副 作用”。 本章只討論如何存取PCI和ISA記憶體。 基於8.1.1強調的額外顧慮,應避免使用直接指向I/O記憶體的指 標 裝置記憶體需要先配置才能使用(linux/ioport.h): struct resource *request_mem_region(unsigned long start, unsigned long len,char *name); 配置的記憶體列表於 /proc/iomem. void release_mem_region(unsigned long start, unsigned long len); int check_mem_region(unsigned long start, unsigned long len);
使用 I/O 記憶體 直接映射記憶體 保留部位記憶位址空間給I/O專用,這些I/O專區不受記憶體管理系統的 管制,也沒有任何虛擬位址會落在I/O專區的範圍內。 存取直接映射的I/O記憶區:用指標存取 unsigned int ioread8(void *addr); unsigned int ioread16(void *addr); unsigned int ioread32(void *addr); void iowrite8(u8 value, void *addr); void iowrite16(u16 value, void *addr); void iowrite32(u32 value, void *addr); 如果要存取一序列的資料(repeat): void ioread8_rep(void *addr, void *buf, unsigned long count); void ioread16_rep(void *addr, void *buf, unsigned long count); void ioread32_rep(void *addr, void *buf, unsigned long count); void iowrite8_rep(void *addr, const void *buf, unsigned long count); void iowrite16_rep(void *addr, const void *buf, unsigned long count); void iowrite32_rep(void *addr, const void *buf, unsigned long count);
使用 I/O 記憶體 讀寫一個block,用: unsigned readb(address); unsigned readw(address); unsigned readl(address); 這些巨集分別從I/O記憶體擷取8-bits、16-bits、32-bits資料值。 void writeb(unsigned value, address); void writew(unsigned value, address); void writel(unsigned value, address); 用於寫出8-bits、16-bits、32-bits資料值。 void memset_io(void *addr, u8 value, unsigned int count); void memcpy_fromio(void *dest, void *source, unsigned int count); void memcpy_toio(void *dest, void *source, unsigned int count); 這些函式將資料塊搬出、搬入I/O記憶體,類似C函式庫中的memcpy() 一樣。
使用 I/O 記憶體 使用short 測試 I/O 記憶體 Ports as I/O Memory 於載入期告訴它使用I/O記憶體,並將I/O region的起始位置告 訴它。 對short,存取I/O埠和I/O記憶體一樣,不過I/O記憶體沒有字 串操作;因此/dev/short0p /dev/short0s和/dev/short0操作是 一樣的。 Ports as I/O Memory 有些裝置使用I/O port,有些使用I/O Memory;兩者access的 方法不同,2.6版本提供: void *ioport_map(unsigned long port, unsigned int count); void ioport_unmap(void *addr); 使得作法更為簡便。
使用 I/O 記憶體 軟體映射的 I/O 記憶體 對於要存取I/O記憶體的軟體,必須要有一種辦法將虛 擬位址指向裝置:ioremap() #include <asm/io.h> void *ioremap(unsigned long phys_addr, unsigned long size); void *ioremap_nocache(unsigned long phys_addr, unsigned long size); 大多數平台上,其實做和ioremap()完全一樣。 void iounmap(void * addr); 如果使用的是完全映射的I/O位址,則ioremap()沒有作 用。
使用 I/O 記憶體 位於1MB以下的 ISA記憶體 指位於640KB ~ 1024KB範圍的的記憶體位址。 Silly模組(Simple Tool for Unloading and Printing ISA Data)。 Silly的任務是存取ISA記憶體,他必須把ISA的實體位址映射到核心 的虛擬位址(使用ioremap() )。 #define ISA_BASE 0xA0000 #define ISA_MAX 0x100000 /* for general memory access */ /* this line appears in silly_init */ io_base = ioremap(ISA_BASE, ISA_MAX - ISA_BASE);
使用 I/O 記憶體 Silly的作業方法: 存取/dev/sillyb (8-bits存取模式) case M_8: while (count) { *ptr = ioread8(add); add++; count--; ptr++; } break;
使用 I/O 記憶體 最後使用iounmap(io_base)恢復原狀。 存取/dev/sillyw和/dev/sillyl case M_32: while (count >= 4) { iowrite8(*(u32 *)ptr, add); add += 4; count -= 4; ptr += 4; } break; 存取/dev/sillycp,使用memcpy_*io() case M_memcpy: memcpy_fromio(ptr, add, count); 最後使用iounmap(io_base)恢復原狀。
使用 I/O 記憶體 isa_readb()與相關函式 先前介紹的每個函式,都有各自對等的isa_*()函式,讓我們可以存取ISA 記憶體而不必是先呼叫ioremap()。 不過這些函式將來可能會消失,盡量避免不用。
THE END~ Any question?