1 / 29

Linux Device Driver

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 記憶體

terri
Download Presentation

Linux Device Driver

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Linux Device Driver Ch9 Communicating with Hardware Nickle @ CCU CSIE

  2. Content • I/O 埠 與 I/O 記憶體 • I/O 暫存器與傳統記憶體 • 使用 I/O 埠 • 字串操作 • 暫停 I/O • 平台相依性 • 使用數位 I/O 埠 • 並列埠的基本概念 • short驅動程式

  3. Content -cont. • 使用 I/O 記憶體 • 直接映射記憶體 • 使用short 測試 I/O 記憶體 • 軟體映射的 I/O 記憶體 • 位於1MB以下的 ISA記憶體 • isa_readb()與相關函式

  4. I/O 埠 與 I/O 記憶體 • 幾乎每一種周邊裝置的控制,都是藉由 register 來達成的,這些暫存器,可能在記憶體空間,也可能在 I/O 空間。 • 目前流行的周邊匯流排是以PC架構為模型;因此,即使沒將 I/O 位址空間獨立出來的處理器,在存取這些週邊裝置時,也必須”假裝”讀寫 I/O 埠。因此,Linux 虛構一組 I/O 埠存取架構。 • 一般認為,I/O 記憶區是比較適當的形式,他有下列優點: • 不需要用到特殊用途的 CPU 指令。 • CPU core 對記憶體的存取效率相對於專用 I/O 指令而言較高。 • 在暫存器的分配及定址模式上,由編輯器產生存取記憶體的程式碼有較自由的選擇。

  5. I/O 埠 與 I/O 記憶體 • 直接映射記憶體 • I/O 暫存器和RAM最大的差異,在於 I/O 操作有”副作用”: • 存取記憶體只是改變目標的儲存值。 • 存取 I/O,目的不在於儲存值,許多控制是藉由”讀取”和“寫入”的動作來改變裝置的狀態。 • 將原本施加於記憶體最佳化技術施(cache)加於 I/O 暫存器身上,則無法產生 I/O 暫存器操作想要的“副作用”。 • 解決方法:在必須以原貌出現在硬體上的程式之間,加上記憶屏障(memory barrier)。

  6. 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() 保證 讀、寫動作都會落實執行。

  7. 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-> …….)的動作。

  8. 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

  9. 使用 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埠,因此不能像平常存許系統記憶體那樣混用。

  10. 使用 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 並不存在。

  11. 使用 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

  12. 使用 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為單位。

  13. 使用 I/O 埠 • 暫停 I/O:當處理器與匯流排之間的傳輸率過快,導致裝置漏失一些資料。 • 改採一些會暫停的函式來代替正常函式,其作用和用法與之前那些正常I/O相同,只在名稱末端多了 _p字樣(e.g. inb_p() 、outb_p())

  14. 使用 I/O 埠 • 平台相依性 • I/O 指令天生和處理器之間有著高度的相依性,因為他們控制著資料如何進出處理器的細節;因此,程式中涉及I/O埠的部份,幾乎都必須針對特定平台來設計,而沒有一體性的寫法。 • 各平台差異請詳見課本 p.243 ~ p.244

  15. 使用數位 I/O 埠 • 數位I/O埠最平常的具體形式,是一個單位元組寬的I/O位置,該位址可能映射到記憶空間,但也可能有專屬的I/O空間。 • 並列埠的基本概念 • 依照PC標準規格每台可以配置兩個並列埠介面,第一個介面起始位置是0x378,第二個是0x278。並列埠的基本模式是由三個8-bit埠構成;第一個埠是雙向的資料暫存器,直接連到實體街頭的pin2 ~ pin9;第二埠是一個唯讀的暫存器;第三埠是控制暫存器(能寫不能讀)。

  16. 使用數位 I/O 埠 並列埠各個位元的規格

  17. 使用數位 I/O 埠 • short(Simple Hardware Operations and Raw Tests)驅動程式 • short只能讀、寫在載入期指定的少數幾個 8-bits埠。 • short驅動程式不會做任何有用的事情,只讓我們將I/O指令作用到指定的埠。 • 不能用short存取已被其他驅動程式佔用的裝置。 • short宗旨:在任何Linux平台測試任何可透過outb()和inb()來存取數位I/O介面。

  18. 使用數位 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 確保動作不會被最佳化處理掉

  19. 使用數位 I/O 埠 • short三種變形 • /dev/short0 用前述的緊密迴圈 • /dev/short0p使用outb_p() 和 inb_p() • /dev/short0s使用字串函式

  20. 使用 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);

  21. 使用 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);

  22. 使用 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()一樣。

  23. 使用 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); 使得作法更為簡便。

  24. 使用 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()沒有作用。

  25. 使用 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);

  26. 使用 I/O 記憶體 • Silly的作業方法: • 存取/dev/sillyb (8-bits存取模式) case M_8: while (count) { *ptr = ioread8(add); add++; count--; ptr++; } break;

  27. 使用 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)恢復原狀。

  28. 使用 I/O 記憶體 • isa_readb()與相關函式 • 先前介紹的每個函式,都有各自對等的isa_*()函式,讓我們可以存取ISA記憶體而不必是先呼叫ioremap()。 • 不過這些函式將來可能會消失,盡量避免不用。

  29. THE END~ Any question?

More Related