匯流排無關裝置訪問

作者:

Matthew Wilcox

作者:

Alan Cox

簡介

Linux 提供了一個 API,用於抽象在所有匯流排和裝置上執行 I/O 操作,從而允許裝置驅動程式獨立於匯流排型別編寫。

記憶體對映 I/O

獲取裝置訪問許可權

最廣泛支援的 I/O 形式是記憶體對映 I/O。也就是說,CPU 地址空間的一部分不是被解釋為對記憶體的訪問,而是被解釋為對裝置的訪問。有些架構將裝置定義在固定地址,但大多數架構都有發現裝置的方法。PCI 匯流排遍歷就是一個很好的例子。本文件不涵蓋如何獲取此類地址,而是假設您已經擁有一個。物理地址的型別是 unsigned long。

此地址不應直接使用。相反,要獲取適合傳遞給下面描述的訪問器函式的地址,您應該呼叫 ioremap()。一個適合訪問裝置的地址將返回給您。

使用完裝置後(例如,在您的模組退出例程中),呼叫 iounmap() 以將地址空間返回給核心。大多數架構在每次呼叫 ioremap() 時都會分配新的地址空間,如果您不呼叫 iounmap(),它們可能會耗盡。

訪問裝置

驅動程式最常用的介面部分是讀寫裝置上的記憶體對映暫存器。Linux 提供了讀寫 8 位、16 位、32 位和 64 位數量的介面。由於歷史原因,它們被命名為位元組、字、長字和四字訪問。讀和寫訪問都支援;目前沒有預取支援。

這些函式被命名為 readb()、readw()、readl()、readq()、readb_relaxed()、readw_relaxed()、readl_relaxed()、readq_relaxed()、writeb()、writew()、writel() 和 writeq()。

某些裝置(例如幀緩衝器)希望一次傳輸超過 8 位元組的資料。對於這些裝置,提供了 memcpy_toio()、memcpy_fromio() 和 memset_io() 函式。不要在 I/O 地址上使用 memset 或 memcpy;它們不保證按順序複製資料。

讀寫函式被定義為有序的。也就是說,編譯器不允許對 I/O 序列進行重新排序。當排序可以由編譯器最佳化時,您可以使用 __readb() 和相關函式來指示寬鬆的排序。請謹慎使用。

雖然基本函式被定義為彼此同步且彼此有序,但裝置所處的匯流排本身可能具有非同步性。特別是,許多作者因 PCI 匯流排寫操作是非同步提交的這一事實而困擾。驅動程式作者必須從同一裝置發出讀操作,以確保在作者關心的特定情況下寫操作已經發生。這種特性不能在 API 中對驅動程式編寫者隱藏。在某些情況下,用於重新整理裝置的讀操作可能預期會失敗(例如,如果卡正在復位)。在這種情況下,讀操作應從配置空間進行,如果卡沒有響應,配置空間保證軟失敗。

以下是當驅動程式希望確保寫操作的效果在繼續執行之前可見時,重新整理對裝置寫操作的示例

static inline void
qla1280_disable_intrs(struct scsi_qla_host *ha)
{
    struct device_reg *reg;

    reg = ha->iobase;
    /* disable risc and host interrupts */
    WRT_REG_WORD(&reg->ictrl, 0);
    /*
     * The following read will ensure that the above write
     * has been received by the device before we return from this
     * function.
     */
    RD_REG_WORD(&reg->ictrl);
    ha->flags.ints_enabled = 0;
}

PCI 排序規則還保證 PIO 讀響應在來自該匯流排的任何未完成 DMA 寫操作之後到達,因為對於某些裝置,readb() 呼叫的結果可能向驅動程式指示 DMA 事務已完成。然而,在許多情況下,驅動程式可能希望指示下一個 readb() 呼叫與裝置執行的任何先前的 DMA 寫操作無關。驅動程式可以使用 readb_relaxed() 處理這些情況,儘管只有一些平臺會遵守寬鬆的語義。使用寬鬆的讀函式將在支援它的平臺上提供顯著的效能優勢。qla2xxx 驅動程式提供瞭如何使用 readX_relaxed() 的示例。在許多情況下,驅動程式的大多數 readX() 呼叫可以安全地轉換為 readX_relaxed() 呼叫,因為只有少數會指示或依賴 DMA 完成。

埠空間訪問

埠空間解釋

另一種常用的 I/O 形式是埠空間。這是一段與正常記憶體地址空間分離的地址範圍。訪問這些地址通常不如訪問記憶體對映地址快,並且其地址空間也可能更小。

與記憶體對映 I/O 不同,訪問埠空間無需準備。

訪問埠空間

對該空間的訪問透過一組函式提供,這些函式允許 8 位、16 位和 32 位訪問;也稱為位元組、字和長字。這些函式是 inb()、inw()、inl()、outb()、outw() 和 outl()。

為這些函式提供了一些變體。有些裝置要求對其埠的訪問進行減速。此功能透過在函式末尾附加 _p 來提供。還有 memcpy 的等效函式。ins() 和 outs() 函式將位元組、字或長字複製到給定埠。

__iomem 指標標記

MMIO 地址的資料型別是 __iomem 限定的指標,例如 void __iomem *reg。在大多數架構上,它是一個指向虛擬記憶體地址的常規指標,可以進行偏移或解引用,但在可移植程式碼中,它只能從顯式操作 __iomem 標記的函式傳入和傳出,特別是 ioremap() 和 readl()/writel() 函式。可以使用 ‘sparse’ 語義程式碼檢查器來驗證此操作是否正確。

雖然在大多數架構上,ioremap() 為指向物理 MMIO 地址的未快取虛擬地址建立頁表項,但某些架構需要特殊的 MMIO 指令,並且 __iomem 指標只是編碼了物理地址或由 readl()/writel() 解釋的可偏移的 cookie。

I/O 訪問函式之間的差異

readq(), readl(), readw(), readb(), writeq(), writel(), writew(), writeb()

這些是最通用的訪問器,提供針對其他 MMIO 訪問和 DMA 訪問的序列化,以及用於訪問小端 PCI 裝置和片上外設的固定位元組序。可移植的裝置驅動程式通常應使用它們來訪問 __iomem 指標。

請注意,提交式寫操作與自旋鎖之間並非嚴格有序,請參閱 記憶體對映地址的 I/O 寫操作排序

readq_relaxed(), readl_relaxed(), readw_relaxed(), readb_relaxed(), writeq_relaxed(), writel_relaxed(), writew_relaxed(), writeb_relaxed()

在需要昂貴的屏障來與 DMA 序列化的架構上,這些 MMIO 訪問器的“寬鬆”版本僅相互序列化,但包含一個成本較低的屏障操作。裝置驅動程式可以在對效能特別敏感的快速路徑中使用它們,並附帶註釋解釋為什麼在特定位置使用它們是安全的,而無需額外的屏障。

有關非寬鬆和寬鬆版本的精確排序保證的更詳細討論,請參閱 memory-barriers.txt。

ioread64(), ioread32(), ioread16(), ioread8(), iowrite64(), iowrite32(), iowrite16(), iowrite8()

這些是普通 readl()/writel() 函式的替代品,行為幾乎相同,但它們也可以操作透過 pci_iomap() 或 ioport_map() 對映 PCI I/O 空間返回的 __iomem 標記。在需要特殊指令進行 I/O 埠訪問的架構上,這會增加 lib/iomap.c 中實現的間接函式呼叫的小開銷,而在其他架構上,它們只是別名。

ioread64be(), ioread32be(), ioread16be() iowrite64be(), iowrite32be(), iowrite16be()

它們的行為與 ioread32()/iowrite32() 系列相同,但位元組順序相反,用於訪問具有大端 MMIO 暫存器的裝置。可以在大端或小端暫存器上操作的裝置驅動程式可能需要實現一個自定義包裝函式,根據找到的裝置選擇其中之一。

注意:在某些架構上,通常的 readl()/writel() 函式傳統上假定裝置與 CPU 具有相同的位元組序,而在執行大端核心時,會在 PCI 總線上使用硬體位元組反轉。以這種方式使用 readl()/writel() 的驅動程式通常不可移植,但往往僅限於特定的 SoC。

hi_lo_readq(), lo_hi_readq(), hi_lo_readq_relaxed(), lo_hi_readq_relaxed(), ioread64_lo_hi(), ioread64_hi_lo(), ioread64be_lo_hi(), ioread64be_hi_lo(), hi_lo_writeq(), lo_hi_writeq(), hi_lo_writeq_relaxed(), lo_hi_writeq_relaxed(), iowrite64_lo_hi(), iowrite64_hi_lo(), iowrite64be_lo_hi(), iowrite64be_hi_lo()

某些裝置驅動程式具有 64 位暫存器,這些暫存器在 32 位架構上無法原子訪問,但允許兩次連續的 32 位訪問。由於它取決於特定裝置,即兩個半部分中哪個必須首先訪問,因此為 64 位訪問器的每個組合提供了助手,可以是低/高或高/低字序。裝置驅動程式必須包含 <linux/io-64-nonatomic-lo-hi.h> 或 <linux/io-64-nonatomic-hi-lo.h> 以獲取函式定義以及在不原生提供 64 位訪問的架構上將普通 readq()/writeq() 重定向到它們的助手。

__raw_readq(), __raw_readl(), __raw_readw(), __raw_readb(), __raw_writeq(), __raw_writel(), __raw_writew(), __raw_writeb()

這些是低階 MMIO 訪問器,沒有屏障或位元組序更改以及架構特定行為。訪問通常是原子的,即四位元組的 __raw_readl() 不會被拆分為單個位元組載入,但多個連續訪問可以在總線上組合。在可移植程式碼中,僅在訪問裝置匯流排後面的記憶體而非 MMIO 暫存器時使用它們是安全的,因為對於其他 MMIO 訪問甚至自旋鎖,都沒有排序保證。位元組順序通常與普通記憶體相同,因此與其他函式不同,這些函式可用於在核心記憶體和裝置記憶體之間複製資料。

inl(), inw(), inb(), outl(), outw(), outb()

PCI I/O 埠資源傳統上需要單獨的輔助函式,因為它們是使用 x86 架構上的特殊指令實現的。在大多數其他架構上,這些函式在內部對映到 readl()/writel() 風格的訪問器,通常指向虛擬記憶體中的固定區域。與 __iomem 指標不同,地址是一個 32 位整數標記,用於標識埠號。PCI 要求 I/O 埠訪問是非提交的,這意味著 outb() 必須在後續程式碼執行之前完成,而普通的 writeb() 可能仍在進行中。在正確實現這一點的架構上,I/O 埠訪問因此與自旋鎖有序。然而,許多非 x86 PCI 主橋實現和 CPU 架構未能實現 PCI 上的非提交 I/O 空間,因此它們在這種硬體上最終可能是提交的。

在某些架構中,I/O 埠號空間與 __iomem 指標具有 1:1 的對映,但不建議這樣做,裝置驅動程式不應依賴此來實現可移植性。類似地,PCI 基址暫存器中描述的 I/O 埠號可能與裝置驅動程式看到的埠號不對應。可移植驅動程式需要讀取核心提供的資源的埠號。

沒有直接的 64 位 I/O 埠訪問器,但可以改用 pci_iomap() 結合 ioread64/iowrite64。

inl_p(), inw_p(), inb_p(), outl_p(), outw_p(), outb_p()

在需要特定時序的 ISA 裝置上,I/O 訪問器的 _p 版本會增加少量延遲。在沒有 ISA 匯流排的架構上,它們是普通 inb/outb 輔助函式的別名。

readsq, readsl, readsw, readsb writesq, writesl, writesw, writesb ioread64_rep, ioread32_rep, ioread16_rep, ioread8_rep iowrite64_rep, iowrite32_rep, iowrite16_rep, iowrite8_rep insl, insw, insb, outsl, outsw, outsb

這些輔助函式多次訪問同一地址,通常用於在核心記憶體位元組流和 FIFO 緩衝區之間複製資料。與普通的 MMIO 訪問器不同,這些函式在大端核心上不執行位元組交換,因此無論架構如何,FIFO 暫存器中的第一個位元組都與記憶體緩衝區中的第一個位元組對應。

裝置記憶體對映模式

某些架構支援多種裝置記憶體對映模式。ioremap_*() 變體圍繞這些架構特定模式提供了通用抽象,並具有共享的語義集。

ioremap() 是最常見的對映型別,適用於典型的裝置記憶體(例如 I/O 暫存器)。如果架構支援,其他模式可以提供更弱或更強的保證。從最常見到最不常見,它們如下所示

ioremap()

預設模式,適用於大多數記憶體對映裝置,例如控制暫存器。使用 ioremap() 對映的記憶體具有以下特性

  • 未快取 - CPU 端快取被繞過,所有讀寫操作都由裝置直接處理

  • 無推測操作 - CPU 不得對該記憶體發出讀或寫操作,除非執行該操作的指令已在已提交的程式流程中達到。

  • 無亂序 - CPU 不得對該記憶體對映的訪問進行相互重新排序。在某些架構上,這依賴於 readl_relaxed()/writel_relaxed() 中的屏障。

  • 無重複 - CPU 不得為單個程式指令發出多個讀或寫操作。

  • 無寫合併 - 每個 I/O 操作都會向裝置發出一個離散的讀或寫操作,並且多個寫操作不會合併為更大的寫操作。在使用 __raw I/O 訪問器或指標解引用時,這可能強制執行也可能不強制執行。

  • 不可執行 - CPU 不允許從該記憶體推測指令執行(這可能不言而喻,但您也不允許跳轉到裝置記憶體中)。

在許多平臺和匯流排(例如 PCI)上,透過 ioremap() 對映發出的寫操作是提交式的,這意味著 CPU 在寫指令退出之前不會等待寫操作實際到達目標裝置。

在許多平臺上,I/O 訪問必須與訪問大小對齊;否則將導致異常或不可預測的結果。

ioremap_wc()

將 I/O 記憶體對映為具有寫合併的普通記憶體。與 ioremap() 不同的是,

  • CPU 可能會從裝置推測性地發出程式實際未執行的讀操作,並且可能選擇基本上讀取它想要的任何內容。

  • 只要結果從程式的角度來看是一致的,CPU 就可以重新排序操作。

  • CPU 可以多次寫入同一位置,即使程式只發出了一次寫操作。

  • CPU 可以將多個寫操作合併為一個更大的寫操作。

此模式通常用於影片幀緩衝器,其中它可以提高寫操作的效能。它也可以用於裝置中的其他記憶體塊(例如緩衝區或共享記憶體),但必須小心,因為訪問不保證在沒有顯式屏障的情況下與正常的 ioremap() MMIO 暫存器訪問有序。

在 PCI 總線上,通常可以安全地在標記為 IORESOURCE_PREFETCH 的 MMIO 區域上使用 ioremap_wc(),但不能在沒有該標誌的區域上使用。對於片上裝置,沒有對應的標誌,但驅動程式可以在已知安全的裝置上使用 ioremap_wc()。

ioremap_wt()

將 I/O 記憶體對映為具有寫直通快取的普通記憶體。與 ioremap_wc() 類似,但同時,

  • CPU 可以快取對裝置發出的寫操作和從裝置讀取的資料,並從該快取提供讀取服務。

此模式有時用於影片幀緩衝器,其中驅動程式仍然期望寫操作及時到達裝置(並且不會停留在 CPU 快取中),但為了效率,讀操作可以從快取提供。然而,如今它很少有用,因為幀緩衝器驅動程式通常只執行寫操作,而 ioremap_wc() 對此更有效率(因為它不會不必要地汙染快取)。大多數驅動程式不應使用此模式。

ioremap_np()

ioremap() 類似,但明確請求非提交式寫語義。在某些架構和總線上,ioremap() 對映具有提交式寫語義,這意味著從 CPU 的角度來看,寫操作可能在寫入資料實際到達目標裝置之前就已經“完成”。寫操作仍然與同一裝置的其他寫操作和讀操作有序,但由於提交式寫語義,對於其他裝置則並非如此。ioremap_np() 明確請求非提交式語義,這意味著寫指令只有在裝置收到(並在某種程度上得到平臺特定確認)寫入資料後才會顯示完成。

這種對映模式主要為了適應那些需要這種特定對映模式才能正常工作的匯流排結構平臺而存在。這些平臺為需要 ioremap_np() 語義的資源設定 IORESOURCE_MEM_NONPOSTED 標誌,可移植驅動程式應使用在適當情況下自動選擇它的抽象(參見下面的 高階 ioremap 抽象 部分)。

裸露的 ioremap_np() 僅在某些架構上可用;在其他架構上,它總是返回 NULL。驅動程式通常不應使用它,除非它們是平臺特定的,或者它們從支援的非提交寫操作中獲得好處,並且在其他情況下可以回退到 ioremap()。確保提交寫操作完成的通常方法是在寫操作之後進行一次虛擬讀操作,如 訪問裝置 中所解釋的,這在所有平臺上都適用於 ioremap()

ioremap_np() 絕不應用於 PCI 驅動程式。PCI 記憶體空間寫操作總是提交式的,即使在其他方面實現了 ioremap_np() 的架構上也是如此。將 ioremap_np() 用於 PCI BARs 最好會產生提交寫語義,最壞會導致完全損壞。

請注意,非提交寫語義與 CPU 端的排序保證是正交的。CPU 仍然可以選擇在非提交寫指令退出之前發出其他讀寫操作。有關 CPU 方面的詳細資訊,請參閱上一節中的 MMIO 訪問函式。

ioremap_uc()

ioremap_uc() 僅在具有 PAT 擴充套件的舊 x86-32 系統以及具有稍微非常規 ioremap() 行為的 ia64 上有意義,其他所有地方 ioremap_uc() 預設返回 NULL。

可移植驅動程式應避免使用 ioremap_uc(),而應使用 ioremap()

ioremap_cache()

ioremap_cache() 實際上將 I/O 記憶體對映為普通 RAM。可以使用 CPU 回寫快取,並且 CPU 可以自由地將裝置視為一塊 RAM。這絕不應用於任何具有副作用的裝置記憶體,或者在讀取時不會返回先前寫入資料的裝置記憶體。

它也不應用於實際的 RAM,因為返回的指標是 __iomem 標記。memremap() 可用於將線性核心記憶體區域之外的普通 RAM 對映到常規指標。

可移植驅動程式應避免使用 ioremap_cache()。

架構示例

以下是上述模式如何對映到 ARM64 架構上的記憶體屬性設定

API

記憶體區域型別和可快取性

ioremap_np()

Device-nGnRnE

ioremap()

Device-nGnRE

ioremap_uc()

(未實現)

ioremap_wc()

普通-不可快取

ioremap_wt()

(未實現;回退到 ioremap)

ioremap_cache()

普通-回寫快取

高階 ioremap 抽象

驅動程式應鼓勵使用更高級別的 API,而不是使用上述原始的 ioremap() 模式。這些 API 可以實現平臺特定的邏輯,以在任何給定總線上自動選擇適當的 ioremap 模式,從而允許平臺無關的驅動程式在這些平臺上工作而無需任何特殊情況。在撰寫本文時,以下 ioremap() 包裝器具有此類邏輯

devm_ioremap_resource()

如果結構體 resource 上設定了 IORESOURCE_MEM_NONPOSTED 標誌,則可以根據平臺要求自動選擇 ioremap_np() 而非 ioremap()。當驅動程式的 probe() 函式失敗或裝置與驅動程式解綁時,使用 devres 自動解除對映資源。

記錄在 Devres - 管理裝置資源 中。

of_address_to_resource()

自動為需要特定匯流排非提交寫操作的平臺設定 IORESOURCE_MEM_NONPOSTED 標誌(請參閱 nonposted-mmio 和 posted-mmio 裝置樹屬性)。

of_iomap()

對映裝置樹中 reg 屬性中描述的資源,執行所有必要的轉換。根據平臺要求自動選擇 ioremap_np(),如上所述。

pci_ioremap_bar(), pci_ioremap_wc_bar()

對映 PCI 基址中描述的資源,無需首先提取物理地址。

pci_iomap(), pci_iomap_wc()

類似於 pci_ioremap_bar()/pci_ioremap_bar(),但與 ioread32()/iowrite32() 及類似訪問器一起使用時,也可在 I/O 空間上工作

pcim_iomap()

類似於 pci_iomap(),但當驅動程式的 probe() 函式失敗或裝置與驅動程式解綁時,使用 devres 自動解除對映資源

記錄在 Devres - 管理裝置資源 中。

不使用這些包裝器可能會導致驅動程式在某些對對映 I/O 記憶體有更嚴格規則的平臺上無法使用。

概括系統和 I/O 記憶體訪問

訪問記憶體區域時,根據其位置,使用者可能需要使用 I/O 操作或記憶體載入/儲存操作來訪問它。例如,複製到系統記憶體可以使用 memcpy() 完成,複製到 I/O 記憶體將使用 memcpy_toio() 完成。

void *vaddr = ...; // pointer to system memory
memcpy(vaddr, src, len);

void *vaddr_iomem = ...; // pointer to I/O memory
memcpy_toio(vaddr_iomem, src, len);

此類指標的使用者可能沒有關於該區域對映的資訊,或者可能希望有一個單一的程式碼路徑來處理該緩衝區上的操作,無論它位於系統記憶體還是 I/O 記憶體中。struct iosys_map 型別及其輔助函式抽象了這一點,以便緩衝區可以傳遞給其他驅動程式,或者在同一驅動程式內部用於分配、讀寫操作時具有不同的職責。

直接編寫訪問 struct iosys_map 的程式碼被認為是不良風格。與其直接訪問其欄位,不如使用提供的輔助函式之一,或者實現您自己的函式。例如,struct iosys_map 的例項可以使用 IOSYS_MAP_INIT_VADDR() 靜態初始化,或者在執行時使用 iosys_map_set_vaddr() 初始化。這些輔助函式將設定系統記憶體中的地址。

struct iosys_map map = IOSYS_MAP_INIT_VADDR(0xdeadbeaf);

iosys_map_set_vaddr(&map, 0xdeadbeaf);

要設定 I/O 記憶體中的地址,請使用 IOSYS_MAP_INIT_VADDR_IOMEM()iosys_map_set_vaddr_iomem()

struct iosys_map map = IOSYS_MAP_INIT_VADDR_IOMEM(0xdeadbeaf);

iosys_map_set_vaddr_iomem(&map, 0xdeadbeaf);

struct iosys_map 的例項不需要清理,但可以使用 iosys_map_clear() 清除為 NULL。清除的對映始終指向系統記憶體。

iosys_map_clear(&map);

使用 iosys_map_is_set()iosys_map_is_null() 測試對映是否有效。

if (iosys_map_is_set(&map) != iosys_map_is_null(&map))
        // always true

struct iosys_map 的例項可以使用 iosys_map_is_equal() 進行相等性比較。指向不同記憶體空間(系統或 I/O)的對映永不相等。即使兩個空間位於同一地址空間中,兩個對映包含相同的地址值,或者兩個對映都指向 NULL,情況也是如此。

struct iosys_map sys_map; // refers to system memory
struct iosys_map io_map; // refers to I/O memory

if (iosys_map_is_equal(&sys_map, &io_map))
        // always false

一個已設定的 struct iosys_map 例項可用於訪問或操作緩衝區記憶體。根據記憶體位置,提供的輔助函式將選擇正確的操作。資料可以使用 iosys_map_memcpy_to() 複製到記憶體中。地址可以使用 iosys_map_incr() 進行操作。

const void *src = ...; // source buffer
size_t len = ...; // length of src

iosys_map_memcpy_to(&map, src, len);
iosys_map_incr(&map, len); // go to first byte after the memcpy
struct iosys_map

指向 I/O/系統記憶體的指標

定義:

struct iosys_map {
    union {
        void __iomem *vaddr_iomem;
        void *vaddr;
    };
    bool is_iomem;
};

成員

{unnamed_union}

匿名

vaddr_iomem

如果緩衝區在 I/O 記憶體中,則為緩衝區的地址

vaddr

如果緩衝區在系統記憶體中,則為緩衝區的地址

is_iomem

如果緩衝區位於 I/O 記憶體中,則為 True,否則為 False。

IOSYS_MAP_INIT_VADDR

IOSYS_MAP_INIT_VADDR (vaddr_)

struct iosys_map 初始化為系統記憶體中的地址

引數

vaddr_

一個系統記憶體地址

IOSYS_MAP_INIT_VADDR_IOMEM

IOSYS_MAP_INIT_VADDR_IOMEM (vaddr_iomem_)

struct iosys_map 初始化為 I/O 記憶體中的地址

引數

vaddr_iomem_

一個 I/O 記憶體地址

IOSYS_MAP_INIT_OFFSET

IOSYS_MAP_INIT_OFFSET (map_, offset_)

從另一個 iosys_map 初始化 struct iosys_map

引數

map_

要從中複製的 dma-buf 對映結構體

offset_

要新增到其他對映的偏移量

描述

根據作為引數傳入的另一個 iosys_map 結構體初始化一個新的 iosys_map 結構體。它對結構體進行淺複製,因此可以在不改變原始對映指向位置的情況下更新後端儲存。這等同於執行

iosys_map map = other_map;
iosys_map_incr(&map, &offset);

示例用法

void foo(struct device *dev, struct iosys_map *base_map)
{
        ...
        struct iosys_map map = IOSYS_MAP_INIT_OFFSET(base_map, FIELD_OFFSET);
        ...
}

與僅僅使用 iosys_map_incr() 增加偏移量相比,使用初始化器的好處是,新對映在其作用域內將始終指向緩衝區的正確位置。這減少了更新緩衝區錯誤部分的風險,並且不會收到編譯器關於此的警告。如果忘記對 IOSYS_MAP_INIT_OFFSET() 進行賦值,編譯器可以警告使用未初始化變數。

void iosys_map_set_vaddr(struct iosys_map *map, void *vaddr)

將 iosys 對映結構體設定為系統記憶體中的地址

引數

struct iosys_map *map

iosys_map 結構體

void *vaddr

一個系統記憶體地址

描述

設定地址並清除 I/O 記憶體標誌。

void __iomem *iosys_map_set_vaddr_iomem(struct iosys_map *map, void __iomem *vaddr_iomem)

將 iosys 對映結構體設定為 I/O 記憶體中的地址

引數

struct iosys_map *map

iosys_map 結構體

void __iomem *vaddr_iomem

一個 I/O 記憶體地址

描述

設定地址和 I/O 記憶體標誌。

bool iosys_map_is_equal(const struct iosys_map *lhs, const struct iosys_map *rhs)

比較兩個 iosys 對映結構體是否相等

引數

const struct iosys_map *lhs

iosys_map 結構體

const struct iosys_map *rhs

要比較的 iosys_map 結構體

描述

如果兩個 iosys 對映結構體都指向相同型別的記憶體並且指向該記憶體中的相同地址,則它們相等。

返回

如果兩個結構體相等,則為 True,否則為 False。

bool iosys_map_is_null(const struct iosys_map *map)

測試 iosys 對映是否為 NULL

引數

const struct iosys_map *map

iosys_map 結構體

描述

根據 struct iosys_map.is_iomem 的狀態,測試對映是否為 NULL。

返回

如果對映為 NULL,則為 True,否則為 False。

bool iosys_map_is_set(const struct iosys_map *map)

測試 iosys 對映是否已設定

引數

const struct iosys_map *map

iosys_map 結構體

描述

根據 struct iosys_map.is_iomem 的狀態,測試對映是否已設定。

返回

如果對映已設定,則為 True,否則為 False。

void iosys_map_clear(struct iosys_map *map)

清除 iosys 對映結構體

引數

struct iosys_map *map

iosys_map 結構體

描述

清除所有欄位為零,包括 struct iosys_map.is_iomem,因此指向 I/O 記憶體的對映結構體將重置為系統記憶體。指標被清除為 NULL。這是預設值。

void iosys_map_memcpy_to(struct iosys_map *dst, size_t dst_offset, const void *src, size_t len)

將資料複製到 iosys_map 的偏移量處

引數

struct iosys_map *dst

iosys_map 結構體

size_t dst_offset

開始複製的偏移量

const void *src

源緩衝區

size_t len

src 中的位元組數

描述

將資料複製到帶有偏移量的 iosys_map 中。源緩衝區位於系統記憶體中。根據緩衝區的位置,輔助函式會選擇正確的記憶體訪問方法。

void iosys_map_memcpy_from(void *dst, const struct iosys_map *src, size_t src_offset, size_t len)

將資料從 iosys_map 複製到系統記憶體

引數

void *dst

系統記憶體中的目標地址

const struct iosys_map *src

iosys_map 結構體

size_t src_offset

開始複製的偏移量

size_t len

src 中的位元組數

描述

從帶有偏移量的 iosys_map 複製資料。目標緩衝區位於系統記憶體中。根據對映位置,輔助函式會選擇正確的記憶體訪問方法。

void iosys_map_incr(struct iosys_map *map, size_t incr)

增加 iosys 對映中儲存的地址

引數

struct iosys_map *map

iosys_map 結構體

size_t incr

要增加的位元組數

描述

增加 iosys 對映中儲存的地址。根據緩衝區的位置,將更新正確的值。

void iosys_map_memset(struct iosys_map *dst, size_t offset, int value, size_t len)

設定 iosys_map 記憶體

引數

struct iosys_map *dst

iosys_map 結構體

size_t offset

從 dst 開始設定值的偏移量

int value

要設定的值

size_t len

在 dst 中要設定的位元組數

描述

在 iosys_map 中設定值。根據緩衝區的位置,輔助函式會選擇正確的記憶體訪問方法。

iosys_map_rd

iosys_map_rd (map__, offset__, type__)

從 iosys_map 讀取一個 C 型別值

引數

map__

iosys_map 結構體

offset__

開始讀取的偏移量

type__

正在讀取的值的型別

描述

從 iosys_map 讀取一個 C 型別值(u8、u16、u32 和 u64)。對於其他型別或如果指標可能未對齊(並且對所支援的架構有問題),請使用 iosys_map_memcpy_from()

返回

從對映中讀取的值。

iosys_map_wr

iosys_map_wr (map__, offset__, type__, val__)

向 iosys_map 寫入一個 C 型別值

引數

map__

iosys_map 結構體

offset__

寫入對映的偏移量

type__

正在寫入的值的型別

val__

要寫入的值

描述

向 iosys_map 寫入一個 C 型別值(u8、u16、u32 和 u64)。對於其他型別或如果指標可能未對齊(並且對所支援的架構有問題),請使用 iosys_map_memcpy_to()

iosys_map_rd_field

iosys_map_rd_field (map__, struct_offset__, struct_type__, field__)

從 iosys_map 中的結構體讀取成員

引數

map__

iosys_map 結構體

struct_offset__

從對映開始處到結構體所在位置的偏移量

struct_type__

描述對映佈局的結構體

field__

要讀取的結構體成員

描述

從 iosys_map 讀取一個值,假設其佈局由從 **struct_offset__** 開始的 C 結構體描述。計算欄位的偏移量和大小並讀取其值。如果欄位訪問會導致未對齊訪問,則需要使用 iosys_map_memcpy_from() 或架構必須支援。例如:假設有一個如下定義的 **struct** foo,並且需要從 iosys_map 中讀取值 foo.field2.inner2

struct foo {
        int field1;
        struct {
                int inner1;
                int inner2;
        } field2;
        int field3;
} __packed;

這是使用 iosys_map_rd_field() 的緩衝區的預期記憶體佈局

地址

內容

buffer + 0000

由 iosys_map 指向的記憶體對映緩衝區的起始

...

...

buffer + struct_offset__

struct foo 的起始

...

...

buffer + wwww

foo.field2.inner2

...

...

buffer + yyyy

struct foo 的結束

...

...

buffer + zzzz

記憶體對映緩衝區的結束

此宏自動計算或不需要的值由 wwww、yyyy 和 zzzz 表示。這是讀取該值的程式碼

x = iosys_map_rd_field(&map, offset, struct foo, field2.inner2);

返回

從對映中讀取的值。

iosys_map_wr_field

iosys_map_wr_field (map__, struct_offset__, struct_type__, field__, val__)

向 iosys_map 中結構體的成員寫入資料

引數

map__

iosys_map 結構體

struct_offset__

從對映開始處到結構體所在位置的偏移量

struct_type__

描述對映佈局的結構體

field__

要讀取的結構體成員

val__

要寫入的值

描述

向 iosys_map 寫入一個值,假設其佈局由從 **struct_offset__** 開始的 C 結構體描述。計算欄位的偏移量和大小,並寫入 **val__**。如果欄位訪問會導致未對齊訪問,則需要使用 iosys_map_memcpy_to() 或架構必須支援。有關預期用法和記憶體佈局,請參閱 iosys_map_rd_field()

提供的公共函式

phys_addr_t virt_to_phys(volatile void *address)

將虛擬地址對映到物理地址

引數

volatile void *address

要重新對映的地址

返回的物理地址是給定記憶體地址的物理(CPU)對映。此函式僅對直接對映或透過 kmalloc 分配的地址有效。

此函式不提供 DMA 傳輸的匯流排對映。在幾乎所有可想象的情況下,裝置驅動程式都不應使用此函式

void *phys_to_virt(phys_addr_t address)

將物理地址對映到虛擬地址

引數

phys_addr_t address

要重新對映的地址

返回的虛擬地址是給定記憶體地址的當前 CPU 對映。此函式僅對具有核心對映的地址有效

此函式不處理 DMA 傳輸的匯流排對映。在幾乎所有可想象的情況下,裝置驅動程式都不應使用此函式

void __iomem *ioremap(resource_size_t offset, unsigned long size)

將匯流排記憶體對映到 CPU 空間

引數

resource_size_t offset

記憶體的匯流排地址

unsigned long size

要對映的資源大小

描述

ioremap 執行平臺特定的操作序列,透過 readb/readw/readl/writeb/writew/writel 函式和其他 mmio 輔助函式使匯流排記憶體可由 CPU 訪問。返回的地址不保證可以直接用作虛擬地址。

如果您嘗試對映的區域是 PCI BAR,您應該檢視 pci_iomap()

void __iomem *iosubmit_cmds512(void __iomem *dst, const void *src, size_t count)

以 512 位為單位,將資料複製到單個 MMIO 位置

引數

void __iomem *dst

目標,在 MMIO 空間中(必須是 512 位對齊)

const void *src

size_t count

要提交的 512 位數量

描述

將資料從核心空間提交到 MMIO 空間,每次以 512 位為單位。不保證訪問順序,之後也不執行記憶體屏障。

警告:除非您的驅動程式已檢查 CPU 指令在平臺上受支援,否則請勿使用此輔助函式。