Linux设备驱动程序学习(十)——PCI驱动程序
??前面介紹的是最底層的硬件控制,這部分將介紹高級總線架構的一些綜述,總線由電氣接口和編程接口夠成。下面將重點介紹PCI總線的編程接口以及對應的內核函數。
PCI(外圍設備互聯)接口
??PCI總線是當今普遍使用在桌面以及更大型計算機上的外設總線,而且該總線是內核中得到最好支持的總線。盡管許多計算機用戶將PCI看成是一種布置電子線路的方式,但實際上它是一組完整的規范,定義了計算機的各個不同部分之間該如何交互。
??PCI規范涵蓋了與計算機接口相關的大部分問題。這里詳細介紹PCI驅動程序如何尋找其硬件和獲得對它的訪問。
??PCI架構的三個主要目標:
- 獲得在計算機和外設之間傳輸數據時更好的性能;
- 盡可能的平臺無關;
- 簡化往系統中添加和刪除外設的工作。
PCI總線與配置空間
??PCI 總線體系結構是一種層次式的體系結構。在這種層次式體系結構中,PCI 橋設備占據著重要的地位,它將父總線與子總線連接在一起,從而使整個系統看起來像一顆倒置的樹型結構。樹的頂端是系統的 CPU,它通過一個較為特殊的 PCI 橋設備——Host/PCI 橋設備與根 PCI 總線連接起來。
??作為一種特殊的 PCI 設備,PCI 橋包括以下幾種:
- Host/PCI 橋:用于連接 CPU 與 PCI 根總線,第 1 個根總線的編號為0,內存控制器也通常被集成到 Host/PCI 橋設備芯片中,橋通常也被稱為“北橋芯片組(North Bridge Chipset)”。
- PCI/ISA 橋:用于連接舊的 ISA 總線。通常,PCI 中的類似i8359A 中斷控制器這樣的設備也會被集成到 PCI/ISA 橋設備中,因此,PCI/I稱為“南橋芯片組(South Bridge Chipset)”。
- PCI-to-PCI 橋:用于連接 PCI 主總線(primary bus)與次總線(sPCI 橋所處的 PCI 總線稱為“主總線”(即次總線的父總線),橋設備所線稱為“次總線”(即主總線的子總線)。
??在 Linux 系統中,PCI 總線用 pci_bus 來描述,這個結構體記錄了本 PCI 總線的信息以及本 PCI 總線的父總線、子總線、橋設備信息,這個結構體的定義:
struct pci_bus{struct list_head node; /* 鏈表元素 node */struct pci_bus * parent; /*指向該 PCI 總線的父總線,即 PCI 橋所在的總線 */struct list_head children; /* 描述了這條 PCI 總線的子總線鏈表的表頭 */struct list_head devices; /* 描述了這條 PCI 總線的邏輯設備鏈表的表頭 */struct pci_dev * self; /* 指向引出這條 PCI 總線的橋設備的 pci_dev 結構 */struct resource * resource[PCI_BUS_NUM_RESOURCES];/* 指向應路由到這條 PCI 總線的地址空間資源 */struct pci_ops * ops; /* 這條 PCI 總線所使用的配置空間訪問函數 */void *sysdata; /* 指向系統特定的擴展數據 */struct proc_dir_entry * procdir; /*該 PCI 總線在/proc/bus/pci 中對應目錄項*/unsigned char number; /* 這條 PCI 總線的總線編號 */16 unsigned char primary; /* 橋設備的主總線 */unsigned char secondary; /* PCI 總線的橋設備的次總線號 */18 unsigned char subordinate; /*PCI 總線的下屬 PCI 總線的總線編號最大值*/19 char name[48];unsigned short bridge_ctl;unsigned short pad2;struct device * bridge;struct class_device class_dev;struct bin_attribute * legacy_io;struct bin_attribute * legacy_mem;};??系統中當前存在的所有根總線都通過其 pci_bus 結構體中的 node 成員鏈接成一條全局的根總線鏈表,其表頭由 list 類型的全局變量 pci_root_buses 來描述。而根總線下面的所有下級總線則都通過其 pci_bus 結構體中的 node 成員鏈接到其父總線的children 鏈表中。這樣,通過這兩種 PCI 總線鏈表,Linux 內核就將所有的 pci_bus 結構體以一種倒置樹的方式組織起來。
PCI設備
??在 Linux 系統中,所有種類的 PCI 設備都可以用 pci_dev 結構體來描述,由于一個 PCI 接口卡上可能包含多個功能模塊,每個功能被當作一個獨立的邏輯設備,因此,每一個 PCI 功能,即 PCI 邏輯設備都唯一地對應一個 pci_dev 設備描述符。該結構體為:
struct pci_dev{struct list_head global_list; /* 全局鏈表元素 */struct list_head bus_list; /* 總線設備鏈表元素 */struct pci_bus * bus; /* 這個 PCI 設備所在的 PCI 總線的 pci_bus 結構 */struct pci_bus * subordinate; /* 指向這個 PCI 設備所橋接的下級總線 */void *sysdata; /* 指向一片特定于系統的擴展數據 */struct proc_dir_entry * procent; /* 該 PCI 設備在/proc/bus/pci 中對應的目錄項 */unsigned int devfn; /* 這個 PCI 設備的設備功能號 */unsigned short vendor; /* PCI 設備的廠商 ID*/unsigned short device; /* PCI 設備的設備 ID */unsigned short subsystem_vendor; /* PCI 設備的子系統廠商 ID */unsigned short subsystem_device; /* PCI 設備的子系統設備 ID */unsigned int class; /* 32 位的無符號整數,表示該 PCI 設備的類別, bit[7∶0]為編程接口,bit[15∶8]為子類別代碼,bit[23∶16]為基類別代碼,bit[31∶24]無意義 */u8 hdr_type; /* PCI 配置空間頭部的類型 */u8 rom_base_reg; /* 表示 PCI 配置空間中的 ROM 基地址寄存器在 PCI 配置空間中的位置 */struct pci_driver * driver; /* 指向這個 PCI 設備所對應的驅動 pci_driver結構 */u64 dma_mask; /* 該設備支持的總線地址位掩碼,通常是 0xffffffff */pci_power_t current_state; /* 當前的操作狀態 */struct device dev; /* 通用的設備接口 */ /* 定義這個 PCI 設備與哪些設備相兼容 */unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];int cfg_size; /* 配置空間大小 */unsigned int irq;struct resource resource[DEVICE_COUNT_RESOURCE];/*表示該設備可能用到的資源,包括:I/O 端口區域、設備內存地址區域以及擴展 ROM 地址區域 */unsigned int transparent : 1; /* 透明 PCI 橋 */unsigned int multifunction : 1; /* 多功能設備 *//* keep track of device state */unsigned int is_enabled : 1; /* pci_enable_device 已經被調用? */unsigned int is_busmaster : 1; /* 設備是主設備? */unsigned int no_msi : 1; /* 設備可不使用 msi? */u32 saved_config_space[16]; /* 掛起事保存的配置空間 */struct bin_attribute * rom_attr; /* sysfs ROM 入口的屬性描述 */int rom_attr_enabled;struct bin_attribute * res_attr[DEVICE_COUNT_RESOURCE]; /*資源的sysfs 文件*/};??在 Linux 系統中,所有的 PCI 設備都通過其 pci_dev 結構體中的 global_list 成員鏈接一條全局 PCI 設備鏈表pci_devices。另外,同屬一條 PCI 總線上的所有 PCI 設備也通過其 pci_dev 結構體中的 bus_list 成員鏈接成一個屬于這條 PCI 總線的總線設備鏈表,表頭則由該 PCI 總線的 pci_bus 結構中的 devices 成員所定義。
PCI配置空間訪問
??PCI設備上有三種地址空間:PCI的I/O空間、PCI的存儲空間和PCI的配置空間。CPU可以訪問PCI設備上的所有地址空間,其中I/O空間和存儲空間提供給設備驅動程序使用,而配置空間則由Linux內核中的PCI初始化代碼使用,這些代碼用于配置 PCI 設備,比如中斷號以及 I/O 或內存基地址
PCI 規范定義了 3 種類型的 PCI 配置空間頭部,其中 type 0 用于標準的 PCI 設備,type 1 用于 PCI 橋,type 2 用于 PCI CardBus 橋:
??pci_bus 結構體中的 pci_ops 類型成員指針 ops 指向該 PCI 總線所使用的配置空間訪問操作的具體實現,pci_ops 結構體的定義:
struct pci_ops{int(*read) (struct pci_bus * bus, unsigned int devfn, int where, int size, u32 * val); //讀配置空間int(*write) (struct pci_bus * bus, unsigned int devfn, int where, int size, u32 val); //寫配置空間};??read()和 write()成員函數中的 size 表示訪問的是字節、2字節還是4字節,對于write()而言,val 是要寫入的值;對于 read()而言,val 是要返回的讀取到的值的指針。通過 bus 參數的成員以及 devfn 可以定位相應 PCI 總線上相應 PCI 邏輯設備的配置空間。在 Linux 設備驅動中,可用如下一組函數來訪問配置空間:
int pci_bus_read_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 *val); //讀字節 int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 *val); //讀字 int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 *val); //讀雙字 int pci_bus_write_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 val); //寫字節 int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 val); //寫字 int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 val); //寫雙字PCI設備驅動結構
??從本質上講 PCI 只是一種總線,具體的 PCI 設備可以是字符設備、網絡設備、USB主機控制器等,因此,一個通過 PCI 總線與系統連接的設備的驅動至少包含以下兩部分:
- PCI 設備驅動
- 設備本身的驅動
??PCI 驅動只是為了輔助設備本身的驅動,它不是目的,只是手段,PCI 設備本身含有雙重以上的身份。
pci_driver結構體
??在 Linux 內核中,用 pci_driver 結構體來定義 PCI 驅動,該結構體中包含了 PCI設備的探測/移除、掛起/恢復等函數,其定義如下:
struct pci_driver{struct list_head node;char *name;struct module * owner;const struct pci_device_id * id_table; /*不能為 NULL,以便 probe 函數調用*//* 新設備添加 */int(*probe) (struct pci_dev * dev, const struct pci_device_id * id);void(*remove) (struct pci_dev * dev); /* 設備移出 */int(*suspend) (struct pci_dev * dev, pm_message_t state); /* 設備掛起 */int(*resume) (struct pci_dev * dev); /* 設備喚醒 *//* 使能喚醒事件 */int(*enable_wake) (struct pci_dev * dev, pci_power_t state, int enable);void(*shutdown) (struct pci_dev * dev);struct device_driver driver;struct pci_dynids dynids;};??對 pci_driver 的注冊和注銷通過如下函數來實現:
int pci_register_driver(struct pci_driver *driver); //注冊 void pci_unregister_driver(struct pci_driver *driver); //銷毀??pci_driver 的 probe()函數要完成 PCI 設備的初始化及其設備本身身份(字符、TTY、網絡等)驅動的注冊。當 Linux 內核啟動并完成對所有 PCI 設備進行掃描、登錄和分配資源等初始化操作的同時,會建立起系統中所有 PCI 設備的拓撲結構,probe()函數將負責硬件的探測工作并保存配置信息。
pci_driver_id結構體
??在 PCI 設備驅動中,也需要定義一個 pci_device_id 結構體數組并導出到用戶空間,使熱插拔和模塊裝載系統知道驅動模塊所針對的硬件設備。pci_device_id結構體的定義:
struct pci_device_id { __u32 vendor, device; /* 廠商和設備 ID或 PCI_ANY_ID*/ __u32 subvendor, subdevice; /* 子系統 ID 或 PCI_ANY_ID */ __u32 class, class_mask; /* (類、子類、prog-if) 三元組 */ kernel_ulong_t driver_data; /* 驅動私有數據 */ };??pci_device_id 結構體數組使用宏 MODULE_DEVICE_TABLE 導出到用戶空間:
static struct pci_device_id netdrv_pci_tbl[] = { {0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, NETDRV_CB }, {0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SMC1211TX },{0,} } MODULE_DEVICE_TABLE (pci, netdrv_pci_tbl);PCI驅動設備程序的實現
??在用模塊方式實現PCI設備驅動程序時,通常至少要實現以下幾個部分:初始化設備模塊、設備打開模塊、數據讀寫和控制模塊、中斷處理模塊、設備釋放模塊、設備卸載模塊,下面是一個典型的PCI設備驅動程序的基本框架:
/* 指明該驅動程序適用于哪一些 PCI 設備 */ static struct pci_device_id xxx_pci_tbl [] __initdata = {{PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},{0,} }; MODULE_DEVICE_TABLE(pci, xxx_pci_tbl); module_init(xxx_init_module); module_exit(xxx_cleanup_module); /* 中斷處理函數 */static void xxx_interrupt(int irq, void * dev_id, struct pt_regs * regs){ /*PC的中斷資源比較有限,只有0~15的中斷號,因此大部分外部設備都是以共享的形式申請中斷號的。當中斷發生的時候,中斷處理程序首先負責對中斷進行識別,然后再做進一步的處理。*/}/* 字符設備 file_operations open 成員函數 */static int xxx_open(struct inode * inode, struct file * file){/* 在這個模塊里主要實現申請中斷、檢查讀寫模式以及申請對設備的控制權等。在申請控制權的時候,非阻塞方式遇忙返回,否則進程主動接受調度,進入睡眠狀態,等待其它進程釋放對設備的控制權。*/request_irq(xxx_irq, &xxx_interrupt, ...));...} /* 字符設備 file_operations ioctl 成員函數 */static int xxx_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg){...}/* 字符設備 file_operations read、write、mmap 等成員函數 *//* 設備文件操作接口 ,PCI設備驅動程序可以通過xxx_fops 結構中的函數xxx_ioctl( ),向應用程序提供對硬件進行控制的接口。*/static struct file_operations xxx_fops = {owner:THIS_MODULE, /* xxx_fops 所屬的設備模塊 */read:xxx_read, /* 讀設備操作*/write:xxx_write, /* 寫設備操作*/ioctl:xxx_ioctl, /* 控制設備操作*/mmap:xxx_mmap, /* 內存重映射操作*/open:xxx_open, /* 打開設備操作*/release:xxx_release /* 釋放設備操作*/};/* pci_driver 的 probe 成員函數probe探測例程將負責完成對硬件的檢測工作*/static int _ _init xxx_probe(struct pci_dev * pci_dev, const struct pci_device_id * pci_id){pci_enable_device(pci_dev); //啟動 PCI 設備 /* 讀取 PCI 配置信息 */Iobase = pci_resource_start(pci_dev, 1);... pci_set_master(pci_dev); //設置成總線主 DMA 模式 pci_request_regions(pci_dev); //申請 I/O 資源 /* 注冊字符設備 */cdev_init(xxx_cdev, &xxx_fops);register_chrdev_region(xxx_dev_no, 1, ...);cdev_add(xxx_cdev);return 0;}/* pci_driver 的 remove 成員函數 */static int _ _init xxx_release(struct pci_dev * pdev){pci_release_regions(pdev); //釋放 I/O 資源pci_disable_device(pdev); //禁止 PCI 設備unregister_chrdev_region(xxx_dev_no, 1); //釋放占用的設備號cdev_del(&xxx_dev.cdev); //注銷字符設備... return 0;}/* 設備模塊信息 */static struct pci_driver xxx_pci_driver ={name:xxx_MODULE_NAME, /* 設備模塊名稱 */id_table:xxx_pci_tbl, /* 能夠驅動的設備列表 */probe:xxx_probe, /* 查找并初始化設備 */remove:xxx_remove /* 卸載設備模塊 */};??在Linux系統下,想要完成對一個PCI設備的初始化,需要完成以下工作:
- 檢查PCI總線是否被Linux內核支持;
- 檢查設備是否插在總線插槽上,如果在的話則保存它所占用的插槽的位置等信息。
- 讀出配置頭中的信息提供給驅動程序使用。
??當Linux內核啟動并完成對所有PCI設備進行掃描、登錄和分配資源等初始化操作的同時,會建立起系統中所有PCI設備的拓撲結構:
??假設用樹來表示PCI總線,那么樹根就是主機/PCI橋,樹葉就是具體的PCI設備,樹葉與樹枝通過pci_driver連接,而樹葉本身的驅動,讀寫、控制樹葉則需要通過其樹葉設備本身所屬類設備驅動來完成。
ISA
??ISA總線在設計上相當陳舊而且其差勁的性能臭名昭著,但是在支持老主板而速度不是很重要的時候,ISA比PCI要更有優勢。
??一個ISA設備可配備有I/O端口,內存區域以及中斷線:
- 盡管 x86 處理器支持 64 KB I/O 端口內存(即處理器有 16 條地址線), 一些老 PC 硬件僅解碼最低的 10 位地址線,這限制可用的地址空間為 1024 個端口。
- 如果 I/O 端口的可用性被限制, 內存存取更加麻煩. 一個 ISA 設備可只使用 640KB 到 1 MB 之間的內存范圍和 15 MB 和 16MB 之間的范圍給 I/O 寄存器和設備控制。
- 對 ISA 設備板第 3 個可用資源是中斷線. 一個有限數目的中斷線被連接到 ISA 總線, 并且它們由所有接口板共享. 結果是, 如果設備不被正確配置, 它們可能發現它們自己在使用同一個中斷線。
ISA編程
??對于編程, 內核中沒有特別的幫助來易于存取 ISA 設備(像對 PCI 那樣有). 你可使用的唯一工具是 I/O 端口和 IRQ 線的注冊, 只能通過中斷處理來實現, 驅動可探測 I/O 端口, 并且中斷線必須被自動探測, 這要通過"自動探測 IRQ 號"技術來實現。
其它PC總線
?? PCI 和 ISA 是在 PC 世界中最常用的外設接口, 但是它們不是唯一的. 這里簡單介紹一下 PC 市場上的其他總線的特性:
MCA總線
??微通道結構(MCA)是,用在 PS/2 計算機和一些筆記本電腦的IBM 標準. 在硬件層次上, 微通道比 ISA 有更多特性. 它支持多主 DMA, 32-位地址和數據線, 共享中斷線, 和地理式尋址來存取每塊板的配置寄存器. 這樣的寄存器被稱為可編程選項選擇(POS), 但是它們沒有 PCI 寄存器的全部特點. Linux 對 微通道的支持包括輸出給模塊的函數。
EISA總線
??擴展 ISA (EISA) 總線是一個對 ISA 的 32-位 擴展, 帶有一個兼容的接口連接器; ISA 設備板可被插入一個 EISA 連接器. 增加的線在 ISA 接觸之下被連接.
VLB總線
??另一個對 ISA 的擴展是 VESA Local Bus(VLB) 接口總線, 它擴展了 ISA 連接器, 通過添加第 3 個知道長度的槽位。一個設備可只插入這個額外的連接器(不用插入 2 個關聯的 ISA 連接器), 因為 VLB 槽位從 ISA 連接器復制了所有的重要信號. 這樣"獨立"的 VLB 外設不使用 ISA 槽位是少見的, 因為大部分設備需要伸到后面板, 使它們的外部連接器是可用的。
??VESA 總線比 EISA , MCA, 和 PCI 總線在它的能力方面更加限制, 并且正在從市場上消失. 沒有特殊的內核支持位 VLB 而存在。
總結
以上是生活随笔為你收集整理的Linux设备驱动程序学习(十)——PCI驱动程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 卫星传输链路需求分析和参数调整
- 下一篇: linux 其他常用命令