日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Linux下PIC總線驅(qū)動詳解(linuxpic總線驅(qū)動)

隨著嵌入式系統(tǒng)的發(fā)展,許多嵌入式系統(tǒng)都需要使用外設(shè)來擴(kuò)展其功能。在這些外設(shè)中,PIC(Peripheral Interface Controller)的使用越來越廣泛。PIC是一種基于總線結(jié)構(gòu)的外設(shè),可用于控制數(shù)據(jù)交換和傳輸。本文將重點(diǎn)介紹如何在Linux下實(shí)現(xiàn)PIC總線驅(qū)動。

一、PIC總線簡介

PIC總線是一種快速總線,用于連接外設(shè)到微處理器。它具有以下特點(diǎn):

1. 高速傳輸:PIC總線的更高頻率可達(dá)40MHz,具備高速傳輸?shù)奶攸c(diǎn)。

2. 多設(shè)備連接:多個(gè)外設(shè)可通過PIC總線連接到處理器,并可對每個(gè)設(shè)備進(jìn)行獨(dú)立管理。

3. 靈活性:根據(jù)不同需求,PIC總線采用各種協(xié)議和傳輸方式,具有極高的靈活性。

4. 硬件實(shí)現(xiàn):PIC總線通常實(shí)現(xiàn)在處理器或者外設(shè)芯片內(nèi),使用硬件方式進(jìn)行控制。

二、PIC總線驅(qū)動的實(shí)現(xiàn)

1. PIC總線驅(qū)動框架

在Linux中,PIC總線驅(qū)動可采用platform或spi框架實(shí)現(xiàn)。在形成驅(qū)動框架之前,應(yīng)首先了解板級和外設(shè)級的概念。

板級:板級表示系統(tǒng)中的所有硬件平臺,包括處理器、外設(shè)和總線等。

外設(shè)級:外設(shè)級是指各種外設(shè)設(shè)備,例如網(wǎng)絡(luò)接口、SCSI控制器、硬盤等。

對于PIC總線驅(qū)動來說,板級部分包括處理器和PIC總線,外設(shè)部分則是通過PIC總線連接的各種設(shè)備。

驅(qū)動框架應(yīng)該包含以下幾個(gè)部分:

1.1 設(shè)備樹

設(shè)備樹是在Linux系統(tǒng)中描述硬件的一種可移植描述方法。設(shè)備樹節(jié)點(diǎn)應(yīng)包含設(shè)備信息和有關(guān)PIC總線的信息。設(shè)備樹文件應(yīng)包含必要的設(shè)備信息和總線信息。設(shè)備樹應(yīng)提供必要的信息以允許PIC總線驅(qū)動正確識別和配置設(shè)備。

1.2 連接與資源管理

在PIC總線驅(qū)動中,連接和資源管理是關(guān)鍵部分。驅(qū)動應(yīng)該檢查設(shè)備樹和總線配置,以確定PIC總線中連接的設(shè)備。驅(qū)動還應(yīng)允許設(shè)備讀取和寫入數(shù)據(jù),同時(shí)管理總線中的硬件資源。

1.3 初始化與清除

PIC總線驅(qū)動應(yīng)提供必要的初始化和清除功能,以確保設(shè)備正常工作。初始化應(yīng)設(shè)置必要的寄存器值,并為設(shè)備分配內(nèi)存空間。清除應(yīng)該關(guān)閉設(shè)備并釋放內(nèi)存。

1.4 傳輸

傳輸是PIC總線驅(qū)動實(shí)現(xiàn)的主要任務(wù)之一。PIC總線驅(qū)動應(yīng)提供可靠的數(shù)據(jù)傳輸、收集和處理方式,在正確配置寄存器的情況下發(fā)送和接收數(shù)據(jù)。驅(qū)動還應(yīng)該處理數(shù)據(jù)差錯(cuò),并發(fā)出適當(dāng)?shù)腻e(cuò)誤響應(yīng)。

2. 驅(qū)動實(shí)現(xiàn)步驟

2.1 設(shè)計(jì)并實(shí)現(xiàn)設(shè)備樹

設(shè)備樹是為了把外設(shè)和處理器聯(lián)系起來提供的一種技術(shù)。設(shè)備樹文件應(yīng)包含設(shè)備信息和有關(guān)PIC總線的信息。設(shè)備樹節(jié)點(diǎn)應(yīng)包含有關(guān)連接和資源的信息。我們需要編寫一個(gè)包含設(shè)備信息的設(shè)備樹文件,并將其與總線連接在一起。

2.2 創(chuàng)建設(shè)備驅(qū)動

創(chuàng)建設(shè)備驅(qū)動可使用platform或者spi框架。platform框架可以通過linux/platform_data.h文件獲得相關(guān)信息,而使用spi框架需要設(shè)置gpio氣泡,電氣參數(shù)和傳輸參數(shù)。我們需要進(jìn)行板級和外設(shè)級配置,以識別驅(qū)動程序所需的設(shè)備信息。驅(qū)動程序還需包含總線連接,資源管理、初始化、傳輸?shù)群诵墓δ堋?/p>

2.3 編譯驅(qū)動程序

編譯必須在目標(biāo)系統(tǒng)上進(jìn)行。驅(qū)動程序可編譯成內(nèi)核模塊或編譯到內(nèi)核中。我們需要使用交叉編譯工具鏈將驅(qū)動和內(nèi)核靜態(tài)鏈接。同時(shí),還要將目標(biāo)驅(qū)動程序添加到initrd中,確保在啟動時(shí)完全加載。

2.4 測試PIC總線驅(qū)動

測試是驅(qū)動程序的一部分,我們應(yīng)該測試驅(qū)動的功能,以確保它們按照預(yù)期進(jìn)行。在驅(qū)動測試過程中,應(yīng)該把注意力放在數(shù)據(jù)傳輸、狀態(tài)機(jī)和錯(cuò)誤處理方面,確保驅(qū)動程序在緊急情況下能夠正確響應(yīng)。

三、結(jié)論

本文詳細(xì)介紹了在Linux下實(shí)現(xiàn)PIC總線驅(qū)動的方法。采用platform或spi框架實(shí)現(xiàn)驅(qū)動程序,需要進(jìn)行設(shè)備樹設(shè)計(jì)和設(shè)備驅(qū)動編寫,并編譯驅(qū)動程序。在測試過程中應(yīng)對數(shù)據(jù)傳輸、狀態(tài)機(jī)和錯(cuò)誤處理進(jìn)行充分測試,以確保驅(qū)動程序可以按預(yù)期工作。如果能夠正確配置和實(shí)現(xiàn)PIC總線驅(qū)動,就可以將其應(yīng)用于多個(gè)嵌入式系統(tǒng)并為它們提供擴(kuò)展功能。

成都網(wǎng)站建設(shè)公司-創(chuàng)新互聯(lián)為您提供網(wǎng)站建設(shè)、網(wǎng)站制作、網(wǎng)頁設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù)!

LINUX設(shè)備驅(qū)動程序如何與硬件通信

LINUX設(shè)備驅(qū)動程序是怎么樣和硬件通信的?下面將由我?guī)Т蠹襾斫獯疬@個(gè)啟信祥疑問吧,希望對大家有所收獲!

 

 LINUX設(shè)備驅(qū)動程序與硬件設(shè)備之間的通信

  設(shè)備驅(qū)動程序是軟件概念和硬件電路之間的一個(gè)抽象層,因此兩方面都要討論。到目前為止,我們已經(jīng)討論詳細(xì)討論了軟件概念上的一些細(xì)節(jié),現(xiàn)在討論另一方面,介紹驅(qū)動程序在Linux上如何在保持可移植性的前提下訪問I/O端口和I/O內(nèi)存。

  我們在需要示例的場合會使用簡單的數(shù)字I/O端口來講解I/O指令,并使用普通的幀緩沖區(qū)顯存來講解內(nèi)存映射I/O。

  I/O端口和I/O內(nèi)存

  計(jì)算機(jī)對每種外設(shè)都是通過讀寫它的寄存悄搏器進(jìn)行控制的。大部分外設(shè)都有幾個(gè)寄存器,不管是在內(nèi)存地址空間還是在I/O地址空間,這些寄存器的訪問地址都是連續(xù)的。

  I/O端口就是I/O端口,設(shè)備會把寄存器映射到I/O端口,不管處理器是否具有獨(dú)立的I/O端口地址空間。即使沒有在訪問外設(shè)時(shí)也要模擬成讀寫I/O端口。

  I/O內(nèi)存是設(shè)備把寄存器映射到某個(gè)內(nèi)存地址區(qū)段(如PCI設(shè)備)。這種I/O內(nèi)存通常是首先方案,它不需要特殊的處理器指令,而且CPU核心訪問內(nèi)存更有效率。

  I/O寄存器和常規(guī)內(nèi)存

  盡管硬件寄存器和內(nèi)存非常相似,但程序員在訪問I/O寄存器的時(shí)候必須注意避免由于CPU或編譯器不恰當(dāng)?shù)膬?yōu)化而改變預(yù)期的I/O動作。

  I/O寄存器和RAM最主要的區(qū)別就是I/O操作具有邊際效應(yīng),而內(nèi)存操作則沒有:由坦橘于內(nèi)存沒有邊際效應(yīng),所以可以用多種 方法 進(jìn)行優(yōu)化,如使用高速緩存保存數(shù)值、重新排序讀/寫指令等。

  編譯器能夠?qū)?shù)值緩存在CPU寄存器中而不寫入內(nèi)存,即使儲存數(shù)據(jù),讀寫操作也都能在高速緩存中進(jìn)行而不用訪問物理RAM。無論是在編譯器一級或是硬件一級,指令的重新排序都有可能發(fā)生:一個(gè)指令序列如果以不同于程序文本中的次序運(yùn)行常常能執(zhí)行得更快。

  在對常規(guī)內(nèi)存進(jìn)行這些優(yōu)化的時(shí)候,優(yōu)化過程是透明的,而且效果良好,但是對I/O操作來說這些優(yōu)化很可能造成致命的錯(cuò)誤,這是因?yàn)槭艿竭呺H效應(yīng)的干擾,而這卻是驅(qū)動程序訪問I/O寄存器的主要目的。處理器無法預(yù)料某些 其它 進(jìn)程(在另一個(gè)處理器上運(yùn)行,或在在某個(gè)I/O控制器中發(fā)生的操作)是否會依賴于內(nèi)存訪問的順序。編譯器或CPU可能會自作聰明地重新排序所要求的操作,結(jié)果會發(fā)生奇怪的錯(cuò)誤,并且很難調(diào)度。因此,驅(qū)動程序必須確保不使用高速緩沖,并且在訪問寄存器時(shí)不發(fā)生讀或?qū)懼噶畹闹匦屡判颉?/p>

  由硬件自身引起的問題很解決:只要把底層硬件配置成(可以是自動的或是由Linux初始化代碼完成)在訪問I/O區(qū)域(不管是內(nèi)存還是端口)時(shí)禁止硬件緩存即可。

  由編譯器優(yōu)化和硬件重新排序引起的問題的解決辦法是:對硬件(或其他處理器)必須以特定順序的操作之間設(shè)置內(nèi)存屏障(memory barrier)。Linux提供了4個(gè)宏來解決所有可能的排序問題:

  #include

  void barrier(void)

  這個(gè)函數(shù)通知編譯器插入一個(gè)內(nèi)存屏障,但對硬件沒有影響。編譯后的代碼會把當(dāng)前CPU寄存器中的所有修改過的數(shù)值保存到內(nèi)存中,需要這些數(shù)據(jù)的時(shí)候再重新讀出來。對barrier的調(diào)用可避免在屏障前后的編譯器優(yōu)化,但硬件完成自己的重新排序。

  #include

  void rmb(void);

  void read_barrier_depends(void);

  void wmb(void);

  void mb(void);

  這些函數(shù)在已編譯的指令流中插入硬件內(nèi)存屏障;具體實(shí)現(xiàn)方法是平臺相關(guān)的。rmb(讀內(nèi)存屏障)保證了屏障之前的讀操作一定會在后來的讀操作之前完成。wmb保證寫操作不會亂序,mb指令保證了兩者都不會。這些函數(shù)都是barrier的超集。

  void p_rmb(void);

  void p_read_barrier_depends(void);

  void p_wmb(void);

  void p_mb(void);

  上述屏障宏版本也插入硬件屏障,但僅僅在內(nèi)核針對P系統(tǒng)編譯時(shí)有效;在單處理器系統(tǒng)上,它們均會被擴(kuò)展為上面那些簡單的屏障調(diào)用。

  設(shè)備驅(qū)動程序中使用內(nèi)存屏障的典型形式如下:

  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);

  在這個(gè)例子中,最重要的是要確??刂颇撤N特定操作的所有設(shè)備寄存器一定要在操作開始之前已被正確設(shè)置。其中的內(nèi)存屏障會強(qiáng)制寫操作以要求的順序完成。

  因?yàn)閮?nèi)存屏障會影響系統(tǒng)性能,所以應(yīng)該只用于真正需要的地方。不同類型的內(nèi)存屏障對性能的影響也不盡相同,所以更好盡可能使用更符合需要的特定類型。

  值得注意的是,大多數(shù)處理同步的內(nèi)核原語,如自旋鎖和atomic_t操作,也能作為內(nèi)存屏障使用。同時(shí)還需要注意,某些外設(shè)總線(比如PCI總線)存在自身的高速緩存問題,我們將在后面的章節(jié)中討論相關(guān)問題。

  在某些體系架構(gòu)上,允許把賦值語句和內(nèi)存屏障進(jìn)行合并以提高效率。內(nèi)核提供了幾個(gè)執(zhí)行這種合并的宏,在默認(rèn)情況下,這些宏的定義如下:

  #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

  在適當(dāng)?shù)牡胤剑卸x的這些宏可以利用體系架構(gòu)特有的指令更快的完成任務(wù)。注意只有小部分體系架構(gòu)定義了set_rmb宏。

  使用I/O端口

  I/O端口是驅(qū)動程序與許多設(shè)備之間的通信方式——至少在部分時(shí)間是這樣。本節(jié)講解了使用I/O端口的不同函數(shù),另外也涉及到一些可移植性問題。

  I/O端口分配

  下面我們提供了一個(gè)注冊的接口,它允允許驅(qū)動程序聲明自己需要操作的端口:

  #include

  struct resource *request_region(unsigned long first, unsigned long n, const char *name);

  它告訴內(nèi)核,我們要使用起始于first的n個(gè)端口。name是設(shè)備的名稱。如果分配成功返回非NULL,如果失敗返回NULL。

  所有分配的端口可從/proc/ioports中找到。如果我們無法分配到我們要的端口,則可以查看這個(gè)文件哪個(gè)驅(qū)動程序已經(jīng)分配了這些端口。

  如果不再使用這些端口,則用下面函數(shù)返回這些端口給系統(tǒng):

  void release_region(unsigned long start, unsigned long n);

  下面函數(shù)允許驅(qū)動程序檢查給定的I/O端口是否可用:

  int check_region(unsigned long first, unsigned long n);//不可用返回負(fù)的錯(cuò)誤代碼

  我們不贊成用這個(gè)函數(shù),因?yàn)樗祷爻晒Σ⒉荒艽_保分配能夠成功,因?yàn)闄z查和其后的分配并不是原子操作。我們應(yīng)該始終使用request_region,因?yàn)檫@個(gè)函數(shù)執(zhí)行了必要的鎖定,以確保分配過程以安全原子的方式完成。

  操作I/O端口

  當(dāng)驅(qū)動程序請求了需要使用的I/O端口范圍后,必須讀取和/或?qū)懭脒@些端口。為此,大多數(shù)硬件都會把8位、16位、32位區(qū)分開來。它們不能像訪問系統(tǒng)內(nèi)存那樣混淆使用。

  因此,C語言程序必須調(diào)用不同的函數(shù)訪問大小不同的端口。那些只支持映射的I/O寄存器的計(jì)算機(jī)體系架構(gòu)通過把I/O端口地址重新映射到內(nèi)存地址來偽裝端口I/O,并且為了易于移植,內(nèi)核對驅(qū)動程序隱藏了這些細(xì)節(jié)。Linux內(nèi)核頭文件中(在與體系架構(gòu)相關(guān)的頭文件中)定義了如下一些訪問I/O端口的內(nèi)聯(lián)函數(shù):

  unsigned inb(unsigned port);

  void outb(unsigned char byte, unsigned port);

  字節(jié)讀寫端口。

  unsigned inw(unsigned port);

  void outw(unsigned short word, unsigned port);

  訪問16位端口

  unsigned inl(unsigned port);

  void outl(unsigned longword, unsigned port);

  訪問32位端口

  在用戶空間訪問I/O端口

  上面這些函數(shù)主要是提供給設(shè)備驅(qū)動程序使用的,但它們也可以用戶空間使用,至少在PC類計(jì)算機(jī)上可以使用。GNU的C庫在中定義了這些函數(shù)。如果要要用戶空間使用inb及相關(guān)函數(shù),則必須滿足正下面這些條件:

  編譯程序時(shí)必須帶有-O選項(xiàng)來強(qiáng)制內(nèi)聯(lián)函數(shù)的展開。

  必須用ioperm(獲取單個(gè)端口的權(quán)限)或iopl(獲取整個(gè)I/O空間)系統(tǒng)調(diào)用來獲取對端口進(jìn)行I/O操作的權(quán)限。這兩個(gè)函數(shù)都是x86平臺特有的。

  必須以root身份運(yùn)行該程序才能調(diào)用ioperm或iopl?;蛘哌M(jìn)程的祖先進(jìn)程之一已經(jīng)以root身份獲取對端口的訪問。

  如果宿主平臺沒有以上兩個(gè)系統(tǒng)調(diào)用,則用戶空間程序仍然可以使用/dev/port設(shè)備文件訪問I/O端口。不過要注意,該設(shè)備文件的含義與平臺密切相關(guān),并且除PC平臺以處,它幾乎沒有什么用處。

  串操作

  以上的I/O操作都是一次傳輸一個(gè)數(shù)據(jù),作為補(bǔ)充,有些處理器實(shí)現(xiàn)了一次傳輸一個(gè)數(shù)據(jù)序列的特殊指令,序列中的數(shù)據(jù)單位可以是字節(jié)、字、雙字。這些指令稱為串操作指令,它們執(zhí)行這些任務(wù)時(shí)比一個(gè)C語言編寫的循環(huán)語句快得多。下面列出的宏實(shí)現(xiàn)了串I/O:

  void in(unsigned port, void *addr, unsigned long count);

  void out(unsigned port, void *addr, unsigned long count);從內(nèi)存addr開始連續(xù)讀/寫count數(shù)目的字節(jié)。只對單一端口port讀取或?qū)懭霐?shù)據(jù)

  void insw(unsigned port, void *addr, unsigned long count);

  void outsw(unsigned port, void *addr, unsigned long count);對一個(gè)16位端口讀寫16位數(shù)據(jù)

  void insl(unsigned port, void *addr, unsigned long count);

  void outsl(unsigned port, void *addr, unsigned long count);對一個(gè)32位端口讀寫32位數(shù)據(jù)

  在使用串I/O操作函數(shù)時(shí),需要銘記的是:它們直接將字節(jié)流從端口中讀取或?qū)懭?。因此,?dāng)端口和主機(jī)系統(tǒng)具有不同的字節(jié)序時(shí),將導(dǎo)致不可預(yù)期的結(jié)果。使用inw讀取端口將在必要時(shí)交換字節(jié),以便確保讀入的值匹配于主機(jī)的字節(jié)序。然而,串函數(shù)不會完成這種交換。

  暫停式I/O

  在處理器試圖從總線上快速傳輸數(shù)據(jù)時(shí),某些平臺(特別是i386)就會出現(xiàn)問題。當(dāng)處理器時(shí)鐘比外設(shè)時(shí)鐘(如ISA)快時(shí)就會出現(xiàn)問題,并且在設(shè)備板上特別慢時(shí)表現(xiàn)出來。為了防止出現(xiàn)丟失數(shù)據(jù)的情況,可以使用暫停式的I/O函數(shù)來取代通常的I/O函數(shù),這些暫停式的I/O函數(shù)很像前面介紹的那些I/O函數(shù),不同之處是它們的名字用_p結(jié)尾,如inb_p、outb_p等等。在linux支持的大多數(shù)平臺上都定義了這些函數(shù),不過它們常常擴(kuò)展為非暫停式I/O同樣的代碼,因?yàn)槿绻皇褂眠^時(shí)的外設(shè)總線就不需要額外的暫停。

  平臺相關(guān)性

  I/O指令是與處理器密切相關(guān)的。因?yàn)樗鼈兊墓ぷ魃婕暗教幚砥饕迫胍瞥鰯?shù)據(jù)的細(xì)節(jié),所以隱藏平臺間的差異非常困難。因此,大部分與I/O端口相關(guān)的源代碼都與平臺相關(guān)。

  回顧前面函數(shù)列表可以看到有一處不兼容的地方,即數(shù)據(jù)類型。函數(shù)的參數(shù)根據(jù)各平臺體系架構(gòu)上的不同要相應(yīng)地使用不同的數(shù)據(jù)類型。例如,port參數(shù)在x86平臺上(處理器只支持64KB的I/O空間)上定義為unsigned short,但在其他平臺上定義為unsigned long,在這些平臺上,端口是與內(nèi)存在同一地址空間內(nèi)的一些特定區(qū)域。

  感興趣的讀者可以從io.h文件獲得更多信息,除了本章介紹的函數(shù),一些與體系架構(gòu)相關(guān)的函數(shù)有時(shí)也由該文件定義。

  值得注意的是,x86家族之外的處理器都不為端口提供獨(dú)立的地址空間。

  I/O操作在各個(gè)平臺上執(zhí)行的細(xì)節(jié)在對應(yīng)平臺的編程手冊中有詳細(xì)的敘述;也可以從web上下載這些手冊的PDF文件。

  I/O端口示例

  演示設(shè)備驅(qū)動程序的端口I/O的示例代碼運(yùn)行于通用的數(shù)字I/O端口上,這種端口在大多數(shù)計(jì)算機(jī)平臺上都能找到。

  數(shù)字I/O端口最常見的一種形式是一個(gè)字節(jié)寬度的I/O區(qū)域,它或者映射到內(nèi)存,或者映射到端口。當(dāng)把數(shù)字寫入到輸出區(qū)域時(shí),輸出引腳上的電平信號隨著寫入的各位而發(fā)生相應(yīng)變化。從輸入?yún)^(qū)域讀取到的數(shù)據(jù)則是輸入引腳各位當(dāng)前的邏輯電平值。

  這類I/O端口的具體實(shí)現(xiàn)和軟件接口是因系統(tǒng)而異的。大多數(shù)情況下,I/O引腳由兩個(gè)I/O區(qū)域控制的:一個(gè)區(qū)域中可以選擇用于輸入和輸出的引腳,另一個(gè)區(qū)域中可以讀寫實(shí)際的邏輯電平。不過有時(shí)情況簡單些,每個(gè)位不是輸入就是輸出(不過這種情況下就不能稱為“通用I/O”了);在所有個(gè)人計(jì)算機(jī)上都能找到的并口就是這樣的非通用的I/O端口。

  并口簡介

  并口的最小配置由3個(gè)8位端口組成。之一個(gè)端口是一個(gè)雙向的數(shù)據(jù)寄存器,它直接連接到物理連接器的2~9號引腳上。第二個(gè)端口是一個(gè)只讀的狀態(tài)寄存器;當(dāng)并口連接打印機(jī)時(shí),該寄存器 報(bào)告 打印機(jī)狀態(tài),如是否是線、缺紙、正忙等等。第三個(gè)端口是一個(gè)只用于輸出的控制寄存器,它的作用之一是控制是否啟用中斷。

  如下所示:并口的引腳

  示例驅(qū)動程序

  while(count–) {

  outb(*(ptr++), port);

  wmb();

  }

  使用I/O內(nèi)存

  除了x86上普遍使的I/O端口之外,和設(shè)備通信的另一種主要機(jī)制是通過使用映射到內(nèi)存的寄存器或設(shè)備內(nèi)存,這兩種都稱為I/O內(nèi)存,因?yàn)榧拇嫫骱蛢?nèi)存的差別對軟件是透明的。

  I/O內(nèi)存僅僅是類似RAM的一個(gè)區(qū)域,在那里處理器可以通過總線訪問設(shè)備。這種內(nèi)存有很多用途,比如存放視頻數(shù)據(jù)或以太網(wǎng)數(shù)據(jù)包,也可以用來實(shí)現(xiàn)類似I/O端口的設(shè)備寄存器(也就是說,對它們的讀寫也存在邊際效應(yīng))。

  根據(jù)計(jì)算機(jī)平臺和所使用總線的不同,i/o內(nèi)存可能是,也可能不是通過頁表訪問的。如果訪問是經(jīng)由頁表進(jìn)行的,內(nèi)核必須首先安排物理地址使其對設(shè)備驅(qū)動程序可見(這通常意味著在進(jìn)行任何I/O之前必須先調(diào)用ioremap)。如果訪問無需頁表,那么I/O內(nèi)存區(qū)域就非常類似于I/O端口,可以使用適當(dāng)形式的函數(shù)讀取它們。

  不管訪問I/O內(nèi)存是否需要調(diào)用ioremap,都不鼓勵(lì)直接使用指向I/O內(nèi)存的指針。相反使用包裝函數(shù)訪問I/O內(nèi)存,這一方面在所有平臺上都是安全的,另一方面,在可以直接對指針指向的內(nèi)存區(qū)域執(zhí)行操作的時(shí)候,這些函數(shù)是經(jīng)過優(yōu)化的。并且直接使用指針會影響程序的可移植性。

  I/O內(nèi)存分配和映射

  在使用之前,必須首先分配I/O區(qū)域。分配內(nèi)存區(qū)域的接口如下(在中定義):

  struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

  該函數(shù)從start開始分配len字節(jié)長的內(nèi)存區(qū)域。如果成功返回非NULL,否則返回NULL值。所有的I/O內(nèi)存分配情況可從/proc/iomem得到。

  不再使用已分配的內(nèi)存區(qū)域時(shí),使用如下接口釋放:

  void release_mem_region(unsigned long start, unsigned long len);

  下面函數(shù)用來檢查給定的I/O內(nèi)存區(qū)域是否可用的老函數(shù):

  int check_mem_region(unsigned long start, unsigned long len);//這個(gè)函數(shù)和check_region一樣不安全,應(yīng)避免使用

  分配內(nèi)存之后我們還必須確保該I/O內(nèi)存對內(nèi)存而言是可訪問的。獲取I/O內(nèi)存并不意味著可引用對應(yīng)的指針;在許多系統(tǒng)上,I/O內(nèi)存根本不能通過這種方式直接訪問。因此,我們必須由ioremap函數(shù)建立映射,ioremap專用于為I/O內(nèi)存區(qū)域分配虛擬地址。

  我們根據(jù)以下定義來調(diào)用ioremap函數(shù):

  #include

  void *ioremap(unsigned long phys_addr, unsigned long size);

  void *ioremap_nocache(unsigned long phys_addr, unsigned long size);在大多數(shù)計(jì)算機(jī)平臺上,該函數(shù)和ioremap相同:當(dāng)所有I/O內(nèi)存已屬于非緩存地址時(shí),就沒有必要實(shí)現(xiàn)ioremap的獨(dú)立的,非緩沖版本。

  void iounmap(void *addr);

  記住,由ioremap返回的地址不應(yīng)該直接引用,而應(yīng)該使用內(nèi)核提供的accessor函數(shù)。

  訪問I/O內(nèi)存

  在某些平臺上我們可以將ioremap的返回值直接當(dāng)作指針使用。但是,這種使用不具有可移植性,訪問I/O內(nèi)存的正確方法是通過一組專用于些目的的函數(shù)(在中定義)。

  從I/O內(nèi)存中讀取,可使用以下函數(shù)之一:

  unsigned int ioread8(void *addr);

  unsigned int ioread16(void *addr);

  unsigned int ioread32(void *addr);

  其中,addr是從ioremap獲得的地址(可能包含一個(gè)整數(shù)偏移量);返回值是從給定I/O內(nèi)存讀取到的值。

  寫入I/O內(nèi)存的函數(shù)如下:

  void iowrite8(u8 value, void *addr);

  void iowrite16(u16 value, void *addr);

  void iowrite32(u32 value, void *addr);

  如果必須在給定的I/O內(nèi)存地址處讀/寫一系列值,則可使用上述函數(shù)的重復(fù)版本:

  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);

  上述函數(shù)從給定的buf向給定的addr讀取或?qū)懭隿ount個(gè)值。count以被寫入數(shù)據(jù)的大小為單位。

  上面函數(shù)均在給定的addr處執(zhí)行所有的I/O操作,如果我們要在一塊I/O內(nèi)存上執(zhí)行操作,則可以使用下面的函數(shù):

  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);

  上述函數(shù)和C函數(shù)庫的對應(yīng)函數(shù)功能一致。

  像I/O內(nèi)存一樣使用I/O端口

  某些硬件具有一種有趣的特性:某些版本使用I/O端口,而其他版本則使用I/O內(nèi)存。導(dǎo)出給處理器的寄存器在兩種情況下都是一樣的,但訪問方法卻不同。為了讓處理這類硬件的驅(qū)動程序更加易于編寫,也為了最小化I/O端口和I/O內(nèi)存訪問這間的表面區(qū)別,2.6內(nèi)核引入了ioport_map函數(shù):

  void *ioport_map(unsigned long port, unsigned int count);

  該函數(shù)重新映射count個(gè)I/O端口,使其看起來像I/O內(nèi)存。此后,驅(qū)動程序可在該函數(shù)返回的地址上使用ioread8及其相關(guān)函數(shù),這樣就不必理會I/O端口和I/O內(nèi)存之間的區(qū)別了。

  當(dāng)不需要這種映射時(shí)使用下面函數(shù)一撤消:

  void ioport_unmap(void *addr);

  這些函數(shù)使得I/O端口看起來像內(nèi)存。但需要注意的是,在重新映射之前,我們必須通過request_region來分配這些I/O端口。

  為I/O內(nèi)存重用short

  前面介紹的short示例模塊訪問的是I/O端口,它也可以訪問I/O內(nèi)存。為此必須在加載時(shí)通知它使用I/O內(nèi)存,另外還要修改base地址以使其指向I/O區(qū)域。

  下例是在MIPS開發(fā)板上點(diǎn)亮調(diào)試用的LED:

  mips.root# ./short_load use_mem=1 base = 0xb7ffffc0

  mips.root# echo -n 7 > /dev/short0

  下面代碼是short寫入內(nèi)存區(qū)域時(shí)使用的循環(huán):

  while(count–) {

  iowrite8(*ptr++, address);

  wmb();

  }

MB地址空間之下的ISA內(nèi)存

  最廣為人知的I/O內(nèi)存區(qū)之一就是個(gè)人計(jì)算機(jī)上的ISA內(nèi)存段。它的內(nèi)存范圍在64KB(0xA0000)到1MB(0x100000)之間,因此它正好出現(xiàn)在常規(guī)系統(tǒng)RAM的中間。這種地址看上去有點(diǎn)奇怪,因?yàn)檫@個(gè)設(shè)計(jì)決策是20世紀(jì)80年代早期作出的,在當(dāng)時(shí)看來沒有人會用到640KB以上的內(nèi)存。

如何讓linux重新枚舉pci設(shè)備

在Linux下,lspci可以枚舉所有PCI設(shè)鎮(zhèn)梁備。它是通過讀取PCI配置空間(PCI Configuration Space)信息來實(shí)現(xiàn)PCI設(shè)備的枚舉的。這里,我通過兩種方式來簡單的模擬一下lspci的功能。一種是通過PCI總線的CF8和CFC端口來枚舉(參考PCI總線規(guī)范);另一種是利用proc filesystem。

  方法一:這種方法需要對端口進(jìn)行操作,在Linux下,普通應(yīng)用程序沒有權(quán)限讀寫I/O 端口,需圓仿要通過iopl或ioperm來提升權(quán)限,我的代碼里面使用iopl。

  

   view plaincopyprint?

  /*

  * Enum all pci device via the PCI config register(CF8 and CFC).

  */

  #include

  #include

  #include

  #include

  

  #define PCI_MAX_BUS 255 /* 8 bits (0 ~ 255) */

  #define PCI_MAX_DEV 31 /* 5 bits (0 ~ 31) */

  #define PCI_MAX_FUN 7 /* 3 bits (0 ~ 7) */

  

  #define CONFIG_ADDRESS 0xCF8

  #define CONFIG_DATA 0xCFC

  

  #define PCICFG_REG_VID 0x00 /* Vendor id, 2 bytes */

  #define PCICFG_REG_DID 0x02 /* Device id, 2 bytes */

  #define PCICFG_REG_CMD 0x04 /* Command register, 2 bytes */

  #define PCICFG_REG_STAT 0x06 /* Status register, 2 bytes */

  #define PCICFG_REG_RID 0x08 /* Revision id, 1 byte */

  

  

  void list_pci_devices()

  {

  unsigned int bus, dev, fun;

  unsigned int addr, data;

  

  //printf(“BB:DD:FF VID:DID\n”);

  

  

  for (bus = 0; bus >16);

  addr = 0xL | (bus

  #include

  #include

  #include

  #include

  #include

  

  #define PCI_MAX_BUS 255 /* 8 bits (0 ~ 255) */

  #define PCI_MAX_DEV 31 /* 5 bits (0 ~ 31) */

  #define PCI_MAX_FUN 7 /* 3 bits (0 ~ 7) */

  

  /*

  * PCI Configuration Header offsets

  */

  #define PCICFG_REG_VID 0x00 /* Vendor id, 2 bytes */

  #define PCICFG_REG_DID 0x02 /* Device id, 2 bytes */

  #define PCICFG_REG_CMD 0x04 /* Command register, 2 bytes */

  #define PCICFG_REG_STAT 0x06 /* Status register, 2 bytes */

  #define PCICFG_REG_RID 0x08 /* Revision id, 1 byte */

  #define PCICFG_REG_PROG_INTF 0x09 /* Programming interface code, 1 byte */

  #define PCICFG_REG_SUBCLASS 0x0A /* Sub-class code, 1 byte */

  #define PCICFG_REG_BASCLASS 0x0B /* Base class code, 1 byte */

  #define PCICFG_REG_CACHE_LINESZ 0x0C /* Cache line size, 1 byte */

  #define PCICFG_REG_LATENCY_TIMER 0x0D /* Latency timer, 1 byte */

  #define PCICFG_REG_HEADER_TYPE 0x0E /* Header type, 1 byte */

  #define PCICFG_REG_BIST 0x0F /* Builtin self test, 1 byte */

  #define PCICFG_REG_BAR0 0x10 /* Base addr register 0, 4 bytes */

  #define PCICFG_REG_BAR1 0x14 /* Base addr register 1, 4 bytes */

  #define PCICFG_REG_BAR2 0x18 /* Base addr register 2, 4 bytes */

  #define PCICFG_REG_BAR3 0x1C /* Base addr register 3, 4 bytes */

  #define PCICFG_REG_BAR4 0x20 /* Base addr register 4, 4 bytes */

  #define PCICFG_REG_BAR5 0x24 /* Base addr register 5, 4 bytes */

  #define PCICFG_REG_CIS 0x28 /* Cardbus CIS Pointer */

  #define PCICFG_REG_SVID 0x2C /* Subsystem Vendor ID, 2 bytes */

  #define PCICFG_REG_SDID 0x2E /* Subsystem ID, 2 bytes */

  #define PCICFG_REG_ROMBAR 0x30 /* ROM base register, 4 bytes */

  #define PCICFG_REG_CAPPTR 0x34 /* Capabilities pointer, 1 byte */

  #define PCICFG_REG_INT_LINE 0x3C /* Interrupt line, 1 byte */

  #define PCICFG_REG_INT_PIN 0x3D /* Interrupt pin, 1 byte */

  #define PCICFG_REG_MIN_GNT 0x3E /* Minimum grant, 1 byte */

  #define PCICFG_REG_MAX_LAT 0x3F /* Maximum lat, 1 byte */

  

  

  void list_pci_devices()

  {

  unsigned int bus, dev, fun;

  

  //printf(“BB:DD:FF VID:DID(RID)\n”);

  

  

  for (bus = 0; bus >16;

  

  printf(“%02X:%02X:%02X”, bus, dev, fun);

  if (rid > 0) {

  printf(” %04X:%04X (rev %02X)\n”, vid, did, rid);

  } else {

  printf(” %04X:%04X\n”, vid, did);

  }

  }

  } // end func

  } // end device

  } // end bus

  }

  

  int main(int argc, char **argv)

  {

  list_pci_devices();

  

  return 0;

  }

  

  這兩種方法各有優(yōu)缺點(diǎn),之一種方法方便移植到其他OS,第二種就只適用于Linux。但是,之一種方法需要對I/O port進(jìn)行直接操作。第二種就不需要。

  注意:執(zhí)行這兩段代碼時(shí),需要超級用戶(root) 權(quán)限。

  補(bǔ)充:今天在枚舉 Westmere-EP Processor(Intel Xeon Processor 5500 Series(Nehalem-EP))的 IMC(Integrated Memory Controller)時(shí)發(fā)現(xiàn)一個(gè)問題。lspci無法枚舉到IMC設(shè)備。Westmere-EP 是 Intel 新的處理器架構(gòu)。和以往的CPU不一樣,它把Memory Controller集成到了CPU里面。IMC控制器被映射到了PCI總線上,Bus Number 是0xFE~0xFF,procfs(/proc/bus/pci/)下沒有這幾個(gè)設(shè)備。但是,通過 CF8/CFC 端口可以枚舉到這些設(shè)備。

  3. 這段代碼是在驅(qū)動中可以用來查找特定的pci device,并且返回一個(gè)pci_dev的結(jié)構(gòu)體變量。通過這樣一個(gè)struct變量,內(nèi)核提供的接口函數(shù)可以直接套用,如pci_read_config_word(),pci_write_config_word()等。

   view plaincopyprint?

  void list_pci_device()

  {

  struct pci_dev *dev;

  struct pci_bus *bus,*childbus;

  

  list_for_each_entry(bus, &pci_root_buses, node) { //globle pci_root_buses in pci.h

  list_for_each_entry(dev, &bus->devices, bus_list) { // for bus 0

  printk(“%02X:%02X:%02X %04X:%04X\n”,dev->bus->number,dev->devfn >> 3, dev->devfn & 0x07,dev->vendor,dev->device);

  }

  list_for_each_entry(childbus, &bus->children,node) { // for bus 1,2,3,…

  list_for_each_entry(dev, &childbus->devices, bus_list) {

  printk(“%02X:%02X:%02X %04X:%04X\n”,dev->bus->number,dev->devfn >> 3, dev->devfn & 0x07,dev->vendor,dev->device);

  }

  }

關(guān)于linux pic總線驅(qū)動的介紹到此就結(jié)束了,不知道你從中找到你需要的信息了嗎 ?如果你還想了解更多這方面的信息,記得收藏關(guān)注本站。

創(chuàng)新互聯(lián)-老牌IDC、云計(jì)算及IT信息化服務(wù)領(lǐng)域的服務(wù)供應(yīng)商,業(yè)務(wù)涵蓋IDC(互聯(lián)網(wǎng)數(shù)據(jù)中心)服務(wù)、云計(jì)算服務(wù)、IT信息化、AI算力租賃平臺(智算云),軟件開發(fā),網(wǎng)站建設(shè),咨詢熱線:028-86922220


文章標(biāo)題:Linux下PIC總線驅(qū)動詳解(linuxpic總線驅(qū)動)
當(dāng)前URL:http://www.dlmjj.cn/article/cccicod.html