<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 9.4.?使用 I/O 內存 盡管 I/O 端口在 x86 世界中流行, 用來和設備通訊的主要機制是通過內存映射的寄存器和設備內存. 2 者都稱為 I/O 內存, 因為寄存器和內存之間的區別對軟件是透明的. I/O 內存是簡單的一個象 RAM 的區域, 它被處理器用來跨過總線存取設備. 這個內存可用作幾個目的, 例如持有視頻數據或者以太網報文, 同時實現設備寄存器就象 I/O 端口一樣的行為(即, 它們有讀和寫它們相關聯的邊際效果). 存取 I/O 內存的方式依賴計算機體系, 總線, 和使用的設備, 盡管外設到處都一樣. 本章的討論主要觸及 ISA 和 PCI 內存, 而也試圖傳遞通用的信息. 盡管存取 PCI 內存在這里介紹, 一個 PCI 的通透介紹安排在第 12 章. 依賴計算機平臺和使用的總線, I/O 內存可以或者不可以通過頁表來存取. 當通過頁表存取, 內核必須首先安排從你的驅動可見的物理地址, 并且這常常意味著你必須調用 ioremap 在做任何 I/O 之前. 如果不需要頁表, I/O 內存位置看來很象 I/O 端口, 并且你只可以使用正確的包裝函數讀和寫它們. 不管是否需要 ioremap 來存取 I/O 內存, 不鼓勵直接使用 I/O 內存的指針. 盡管(如同在 "I/O 端口和 I/O 內存" 一節中介紹的 )I/O 內存如同在硬件級別的正常 RAM 一樣尋址, 在"I/O 寄存器和傳統內存"一節中概述的額外的小心建議避免正常的指針. 用來存取 I/O 內存的包裝函數在所有平臺上是安全的并且在任何時候直接的指針解引用能夠進行操作時, 會被優化掉. 因此, 盡管在 x86 上解引用一個指針能工作(在現在), 不使用正確的宏定義阻礙了驅動的移植性和可讀性. ### 9.4.1.?I/O 內存分配和映射 I/O 內存區必須在使用前分配. 分配內存區的接口是( 在 <linux/ioport.h> 定義): ~~~ struct resource *request_mem_region(unsigned long start, unsigned long len, char *name); ~~~ 這個函數分配一個 len 字節的內存區, 從 start 開始. 如果一切順利, 一個 非NULL 指針返回; 否則返回值是 NULL. 所有的 I/O 內存分配來 /proc/iomem 中列出. 內存區在不再需要時應當釋放: ~~~ void release_mem_region(unsigned long start, unsigned long len); ~~~ 還有一個舊的檢查 I/O 內存區可用性的函數: ~~~ int check_mem_region(unsigned long start, unsigned long len); ~~~ 但是, 對于 check_region, 這個函數是不安全和應當避免的. 在存取內存之前, 分配 I/O 內嵌不是唯一的要求的步驟. 你必須也保證這個 I/O 內存已經對內核是可存取的. 使用 I/O 內存不只是解引用一個指針的事情; 在許多系統, I/O 內存根本不是可以這種方式直接存取的. 因此必須首先設置一個映射. 這是 ioremap 函數的功能, 在第 1 章的 "vmalloc 及其友"一節中介紹的. 這個函數設計來特別的安排虛擬地址給 I/O 內存區. 一旦裝備了 ioremap (和iounmap), 一個設備驅動可以存取任何 I/O 內存地址, 不管是否它是直接映射到虛擬地址空間. 記住, 但是, 從 ioremap 返回的地址不應當直接解引用; 相反, 應當使用內核提供的存取函數. 在我們進入這些函數之前, 我們最好回顧一下 ioremap 原型和介紹幾個我們在前一章略過的細節. 這些函數根據下列定義調用: ~~~ #include <asm/io.h> void *ioremap(unsigned long phys_addr, unsigned long size); void *ioremap_nocache(unsigned long phys_addr, unsigned long size); void iounmap(void * addr); ~~~ 首先, 你注意新函數 ioremap_nacache. 我們在第 8 章沒有涉及它, 因為它的意思是明確地硬件相關的. 引用自一個內核頭文件:"It’s useful if some control registers are in such an area, and write combining or read caching is not desirable.". 實際上, 函數實現在大部分計算機平臺上與 ioremap 一致: 在所有 I/O 內存已經通過非緩沖地址可見的地方, 沒有理由使用一個分開的, 非緩沖 ioremap 版本. ### 9.4.2.?存取 I/O 內存 在一些平臺上, 你可能逃過作為一個指針使用 ioremap 的返回值的懲罰. 這樣的使用不是可移植的, 并且, 更加地, 內核開發者已經努力來消除任何這樣的使用. 使用 I/O 內存的正確方式是通過一系列為此而提供的函數(通過 <asm/io.h> 定義的). 從 I/O 內存讀, 使用下列之一: ~~~ unsigned int ioread8(void *addr); unsigned int ioread16(void *addr); unsigned int ioread32(void *addr); ~~~ 這里, addr 應當是從 ioremap 獲得的地址(也許與一個整型偏移); 返回值是從給定 I/O 內存讀取的. 有類似的一系列函數來寫 I/O 內存: ~~~ void iowrite8(u8 value, void *addr); void iowrite16(u16 value, void *addr); void iowrite32(u32 value, void *addr); ~~~ 如果你必須讀和寫一系列值到一個給定的 I/O 內存地址, 你可以使用這些函數的重復版本: ~~~ 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); ~~~ 這些函數讀或寫 count 值從給定的 buf 到 給定的 addr. 注意 count 表達為在被寫入的數據大小; ioread32_rep 讀取 count 32-位值從 buf 開始. 上面描述的函數進行所有的 I/O 到給定的 addr. 如果, 相反, 你需要操作一塊 I/O 地址, 你可使用下列之一: ~~~ 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); ~~~ 這些函數行為如同它們的 C 庫類似物. 如果你通覽內核源碼, 你可看到許多調用舊的一套函數, 當使用 I/O 內存時. 這些函數仍然可以工作, 但是它們在新代碼中的使用不鼓勵. 除了別的外, 它們較少安全因為它們不進行同樣的類型檢查. 但是, 我們在這里描述它們: ~~~ unsigned readb(address); unsigned readw(address); unsigned readl(address); ~~~ 這些宏定義用來從 I/O 內存獲取 8-位, 16-位, 和 32-位 數據值. ~~~ void writeb(unsigned value, address); void writew(unsigned value, address); void writel(unsigned value, address); ~~~ 如同前面的函數, 這些函數(宏)用來寫 8-位, 16-位, 和 32-位數據項. 一些 64-位平臺也提供 readq 和 writeq, 為 PCI 總線上的 4-字(8-字節)內存操作. 這個 4-字 的命名是一個從所有的真實處理器有 16-位 字的時候的歷史遺留. 實際上, 用作 32-位 值的 L 命名也已變得不正確, 但是命名任何東西可能使事情更混淆. ### 9.4.3.?作為 I/O 內存的端口 一些硬件有一個有趣的特性: 一些版本使用 I/O 端口, 而其他的使用 I/O 內存. 輸出給處理器的寄存器在任一種情況中相同, 但是存取方法是不同的. 作為一個使驅動處理這類硬件的生活容易些的方式, 并且作為一個使 I/O 端口和內存存取的區別最小化的方法, 2.6 內核提供了一個函數, 稱為 ioport_map: ~~~ void *ioport_map(unsigned long port, unsigned int count); ~~~ 這個函數重映射 count I/O 端口和使它們出現為 I/O 內存. 從這點以后, 驅動可以在返回的地址上使用 ioread8 和其友并且根本忘記它在使用 I/O 端口. 這個映射應當在它不再被使用時恢復: ~~~ void ioport_unmap(void *addr); ~~~ 這些函數使 I/O 端口看來象內存. 但是, 注意 I/O 端口必須仍然使用 request_region 在它們以這種方式被重映射前分配. ### 9.4.4.?重用 short 為 I/O 內存 short 例子模塊, 在存取 I/O 端口前介紹的, 也能用來存取 I/O 內存. 為此, 你必須告訴它使用 I/O 內存在加載時; 還有, 你需要改變基地址來使它指向你的 I/O 區. 例如, 這是我們如何使用 short 來點亮調試 LED, 在一個 MIPS 開發板上: ~~~ mips.root# ./short_load use_mem=1 base=0xb7ffffc0 mips.root# echo -n 7 > /dev/short0 ~~~ 使用 short 給 I/O 內存是與它用在 I/O 端口上同樣的. 下列片段顯示了 short 在寫入一個內存位置時用的循環: ~~~ while (count--) { iowrite8(*ptr++, address); wmb(); } ~~~ 注意, 這里使用一個寫內存屏障. 因為在很多體系上 iowrites8 可能轉變為一個直接賦值, 需要內存屏障來保證以希望的順序來發生. short 使用 inb 和 outb 來顯示它如何完成. 對于讀者它可能是一個直接的練習, 但是, 改變 short 來使用 ioport_map 重映射 I/O 端口, 并且相當地簡化剩下的代碼. ### 9.4.5.?在 1 MB 之下的 ISA 內存 一個最著名的 I/O 內存區是在個人計算機上的 ISA 范圍. 這是在 640 KB(0xA0000)和 1 MB(0x100000)之間的內存范圍. 因此, 它正好出現于常規內存 RAM 中間. 這個位置可能看起來有點奇怪; 它是一個在 1980 年代早期所作的決定的產物, 當時 640 KB 內存看來多于任何人可能用到的大小. 這個內存方法屬于非直接映射的內存類別. [[36](#)]你可以讀/寫幾個字節在這個內存范圍, 如同前面解釋的使用 short 模塊, 就是, 通過在加載時設置 use_mem. 盡管 ISA I/O 內存只在 x86-類 計算機中存在, 我們認為值得用幾句話和一個例子驅動. 我們不會談論 PCI 在本章, 因為它是最干凈的一類 I/O 內存: 一旦你知道內存地址, 你可簡單地重映射和存取它. PCI I/O 內存的"問題"是它不能為本章提供一個能工作的例子, 因為我們不能事先知道你的 PCI 內存映射到的物理地址, 或者是否它是安全的來存取任一這些范圍. 我們選擇來描述 ISA 內存范圍, 因為它不但少干凈并且更適合運行例子代碼. 為演示存取 ISA 內存, 我們還使用另一個 silly 小模塊( 例子源碼的一部分). 實際上, 這個稱為 silly, 作為 Simple Tool for Unloading and Printing ISA Data 的縮寫, 或者如此的東東. 模塊補充了 short 的功能, 通過存取整個 384-KB 內存空間和通過顯示所有的不同 I/O 功能. 它特有 4 個設備節點來進行同樣的任務, 使用不同的數據傳輸函數. silly 設備作為一個 I/O 內存上的窗口, 以類似 /dev/mem 的方式. 你可以讀和寫數據, 并且lseek 到一個任意 I/O 內存地址. 因為 silly 提供了對 ISA 內存的存取, 它必須開始于從映射物理 ISA 地址到內核虛擬地址. 在 Linux 內核的早期, 一個人可以簡單地安排一個指針給一個感興趣的 ISA 地址, 接著直接對它解引用. 在現代世界, 但是, 我們必須首先使用虛擬內存系統和重映射內存范圍. 這個映射使用 ioremap 完成, 如同前面為 short 解釋的: ~~~ #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); ~~~ ioremap 返回一個指針值, 它能被用來使用 ioread8 和其他函數, 在"存取 I/O 內存"一節中解釋. 讓我們回顧我們的例子模塊來看看這些函數如何被使用. /dev/sillyb, 特有次編號 0, 存取 I/O 內存使用 ioread8 和 iowrite8. 下列代碼顯示了讀的實現, 它使地址范圍 0xA0000-0xFFFF 作為一個虛擬文件在范圍 0-0x5FFF. 讀函數構造為一個 switch 語句在不同存取模式上; 這是 sillyb 例子: ~~~ case M_8: while (count) { *ptr = ioread8(add); add++; count--; ptr++; } break; ~~~ 實際上, 這不是完全正確. 內存范圍是很小和很頻繁的使用, 以至于內核在啟動時建立頁表來存取這些地址. 但是, 這個用來存取它們的虛擬地址不是同一個物理地址, 并且因此無論如何需要 ioremap. 下 2 個設備是 /dev/sillyw (次編號 1) 和 /dev/silly1 (次編號 2). 它們表現象 /dev/sillyb, 除了它們使用 16-位 和 32-位 函數. 這是 sillyl 的寫實現, 又一次部分 switch: ~~~ case M_32: while (count >= 4) { iowrite8(*(u32 *)ptr, add); add += 4; count -= 4; ptr += 4; } break; ~~~ 最后的設備是 /dev/sillycp (次編號 3), 它使用 memcpy_*io 函數來進行同樣的任務. 這是它的讀實現的核心: ~~~ case M_memcpy: memcpy_fromio(ptr, add, count); break; ~~~ 因為 ioremap 用來提供對 ISA 內存區的存取, silly 必須調用 iounmap 當模塊卸載時: ~~~ iounmap(io_base); ~~~ ### 9.4.6.?isa_readb 和其友 看一下內核源碼會展現另一套函數, 有如 isa_readb 的名子. 實際上, 每個剛才描述的函數都有一個 isa_ 對等體. 這些函數提供對 ISA 內存的存取不需要一個單獨的 ioremap 步驟. 但是, 來自內核開發者的話, 是這些函數打算用來作為暫時的驅動移植輔助, 并且它可能將來消失. 因此, 你應當避免使用它們.
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看