290 likes | 460 Views
Linux Device Driver. Ch9 Communicating with Hardware. Nickle @ CCU CSIE. Content. I/O 埠 與 I/O 記憶體 I/O 暫存器與傳統記憶體 使用 I/O 埠 字串操作 暫停 I/O 平台相依性 使用數位 I/O 埠 並列埠的基本概念 short 驅動程式. Content -cont. 使用 I/O 記憶體 直接映射記憶體 使用 short 測試 I/O 記憶體 軟體映射的 I/O 記憶體 位於 1MB 以下的 ISA 記憶體
E N D
Linux Device Driver Ch9 Communicating with Hardware Nickle @ CCU CSIE
Content • 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 埠 • 在驅動程式真正使用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>,但必須遵守以下條件: • 使用-O選項,強迫展開內插函式。 • 必須先使用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 記憶體 • 於載入期告訴它使用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範圍的的記憶體位址。 • ISA記憶體位址範圍屬於非直接映射式。 • 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 記憶體 • 存取/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); break; • 最後使用iounmap(io_base)恢復原狀。
使用 I/O 記憶體 • isa_readb()與相關函式 • 先前介紹的每個函式,都有各自對等的isa_*()函式,讓我們可以存取ISA記憶體而不必是先呼叫ioremap()。 • 不過這些函式將來可能會消失,盡量避免不用。
THE END~ Any question?