當前位置:首頁 > 嵌入式 > 嵌入式軟件
[導(dǎo)讀]Linux下PCI設(shè)備驅(qū)動程序開發(fā)

一、PCI總線系統(tǒng)體系結(jié)構(gòu)

PCI是外圍設(shè)備互連(Peripheral Component Interconnect)的簡稱,作為一種通用的總線接口標準,它在目前的計算機系統(tǒng)中得到了非常廣泛的應(yīng)用。PCI提供了一組完整的總線接口規(guī)范,其目的是描述如何將計算機系統(tǒng)中的外圍設(shè)備以一種結(jié)構(gòu)化和可控化的方式連接在一起,同時它還刻畫了外圍設(shè)備在連接時的電氣特性和行為規(guī)約,并且詳細定義了計算機系統(tǒng)中的各個不同部件之間應(yīng)該如何正確地進行交互。

無論是在基于Intel芯片的PC機中,或是在基于Alpha芯片的工作站上,PCI毫無疑問都是目前使用最廣泛的一種總線接口標準。同舊式的ISA總線不同,PCI將計算機系統(tǒng)中的總線子系統(tǒng)與存儲子系統(tǒng)完全地分開,CPU通過一塊稱為PCI橋(PCI-Bridge)的設(shè)備來完成同總線子系統(tǒng)的交互,如圖1所示。


圖1 PCI子系統(tǒng)的體系結(jié)構(gòu)

由于使用了更高的時鐘頻率,因此PCI總線能夠獲得比ISA總線更好的整體性能。PCI總線的時鐘頻率一般在25MHz到33MHz范圍內(nèi),有些甚至能夠達到66MHz或者133MHz,而在64位系統(tǒng)中則最高能達到266MHz。盡管目前PCI設(shè)備大多采用32位數(shù)據(jù)總線,但PCI規(guī)范中已經(jīng)給出了64位的擴展實現(xiàn),從而使PCI總線能夠更好地實現(xiàn)平臺無關(guān)性,現(xiàn)在PCI總線已經(jīng)能夠用于IA-32、Alpha、PowerPC、SPARC64和IA-64等體系結(jié)構(gòu)中。

PCI總線具有三個非常顯著的優(yōu)點,使得它能夠完成最終取代ISA總線這一歷史使命:

  • 在計算機和外設(shè)間傳輸數(shù)據(jù)時具有更好的性能;
  • 能夠盡量獨立于具體的平臺;
  • 可以很方便地實現(xiàn)即插即用。

圖2是一個典型的基于PCI總線的計算機系統(tǒng)邏輯示意圖,系統(tǒng)的各個部分通過PCI總線和PCI-PCI橋連接在一起。從圖中不難看出,CPU和RAM需要通過PCI橋連接到PCI總線0(即主PCI總線),而具有PCI接口的顯卡則可以直接連接到主PCI總線上。PCI-PCI橋是一個特殊的PCI設(shè)備,它負責(zé)將PCI總線0和PCI總線1(即從PCI主線)連接在一起,通常PCI總線1稱為PCI-PCI橋的下游(downstream),而PCI總線0則稱為PCI-PCI橋的上游(upstream)。圖中連接到從PCI總線上的是SCSI卡和以太網(wǎng)卡。為了兼容舊的ISA總線標準,PCI總線還可以通過PCI-ISA橋來連接ISA總線,從而能夠支持以前的ISA設(shè)備。圖中ISA總線上連接著一個多功能I/O控制器,用于控制鍵盤、鼠標和軟驅(qū)。


圖2 PCI系統(tǒng)示意圖

在此我只對PCI總線系統(tǒng)體系結(jié)構(gòu)作了概括性介紹,如果讀者想進一步了解,David A Rusling在The Linux Kernel中對Linux的PCI子系統(tǒng)有比較詳細的介紹。

二、Linux驅(qū)動程序框架

Linux將所有外部設(shè)備看成是一類特殊文件,稱之為“設(shè)備文件”,如果說系統(tǒng)調(diào)用是Linux內(nèi)核和應(yīng)用程序之間的接口,那么設(shè)備驅(qū)動程序則可以看成是Linux內(nèi)核與外部設(shè)備之間的接口。設(shè)備驅(qū)動程序向應(yīng)用程序屏蔽了硬件在實現(xiàn)上的細節(jié),使得應(yīng)用程序可以像操作普通文件一樣來操作外部設(shè)備。 

1. 字符設(shè)備和塊設(shè)備

Linux抽象了對硬件的處理,所有的硬件設(shè)備都可以像普通文件一樣來看待:它們可以使用和操作文件相同的、標準的系統(tǒng)調(diào)用接口來完成打開、關(guān)閉、讀寫和I/O控制操作,而驅(qū)動程序的主要任務(wù)也就是要實現(xiàn)這些系統(tǒng)調(diào)用函數(shù)。Linux系統(tǒng)中的所有硬件設(shè)備都使用一個特殊的設(shè)備文件來表示,例如,系統(tǒng)中的第一個IDE硬盤使用/dev/hda表示。每個設(shè)備文件對應(yīng)有兩個設(shè)備號:一個是主設(shè)備號,標識該設(shè)備的種類,也標識了該設(shè)備所使用的驅(qū)動程序;另一個是次設(shè)備號,標識使用同一設(shè)備驅(qū)動程序的不同硬件設(shè)備。設(shè)備文件的主設(shè)備號必須與設(shè)備驅(qū)動程序在登錄該設(shè)備時申請的主設(shè)備號一致,否則用戶進程將無法訪問到設(shè)備驅(qū)動程序。 

在Linux操作系統(tǒng)下有兩類主要的設(shè)備文件:一類是字符設(shè)備,另一類則是塊設(shè)備。字符設(shè)備是以字節(jié)為單位逐個進行I/O操作的設(shè)備,在對字符設(shè)備發(fā)出讀寫請求時,實際的硬件I/O緊接著就發(fā)生了,一般來說字符設(shè)備中的緩存是可有可無的,而且也不支持隨機訪問。塊設(shè)備則是利用一塊系統(tǒng)內(nèi)存作為緩沖區(qū),當用戶進程對設(shè)備進行讀寫請求時,驅(qū)動程序先查看緩沖區(qū)中的內(nèi)容,如果緩沖區(qū)中的數(shù)據(jù)能滿足用戶的要求就返回相應(yīng)的數(shù)據(jù),否則就調(diào)用相應(yīng)的請求函數(shù)來進行實際的I/O操作。塊設(shè)備主要是針對磁盤等慢速設(shè)備設(shè)計的,其目的是避免耗費過多的CPU時間來等待操作的完成。一般說來,PCI卡通常都屬于字符設(shè)備。 

所有已經(jīng)注冊(即已經(jīng)加載了驅(qū)動程序)的硬件設(shè)備的主設(shè)備號可以從/proc/devices文件中得到。使用mknod命令可以創(chuàng)建指定類型的設(shè)備文件,同時為其分配相應(yīng)的主設(shè)備號和次設(shè)備號。例如,下面的命令: 

[root@gary root]# mknod  /dev/lp0  c  6  0
 



將建立一個主設(shè)備號為6,次設(shè)備號為0的字符設(shè)備文件/dev/lp0。當應(yīng)用程序?qū)δ硞€設(shè)備文件進行系統(tǒng)調(diào)用時,Linux內(nèi)核會根據(jù)該設(shè)備文件的設(shè)備類型和主設(shè)備號調(diào)用相應(yīng)的驅(qū)動程序,并從用戶態(tài)進入到核心態(tài),再由驅(qū)動程序判斷該設(shè)備的次設(shè)備號,最終完成對相應(yīng)硬件的操作。 

2. 設(shè)備驅(qū)動程序接口

Linux中的I/O子系統(tǒng)向內(nèi)核中的其他部分提供了一個統(tǒng)一的標準設(shè)備接口,這是通過include/linux/fs.h中的數(shù)據(jù)結(jié)構(gòu)file_operations來完成的: 

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *); 
 ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); 
 ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); 
 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
 



當應(yīng)用程序?qū)υO(shè)備文件進行諸如open、close、read、write等操作時,Linux內(nèi)核將通過file_operations結(jié)構(gòu)訪問驅(qū)動程序提供的函數(shù)。例如,當應(yīng)用程序?qū)υO(shè)備文件執(zhí)行讀操作時,內(nèi)核將調(diào)用file_operations結(jié)構(gòu)中的read函數(shù)。 

2. 設(shè)備驅(qū)動程序模塊

Linux下的設(shè)備驅(qū)動程序可以按照兩種方式進行編譯,一種是直接靜態(tài)編譯成內(nèi)核的一部分,另一種則是編譯成可以動態(tài)加載的模塊。如果編譯進內(nèi)核的話,會增加內(nèi)核的大小,還要改動內(nèi)核的源文件,而且不能動態(tài)地卸載,不利于調(diào)試,所有推薦使用模塊方式。 

從本質(zhì)上來講,模塊也是內(nèi)核的一部分,它不同于普通的應(yīng)用程序,不能調(diào)用位于用戶態(tài)下的C或者C++庫函數(shù),而只能調(diào)用Linux內(nèi)核提供的函數(shù),在/proc/ksyms中可以查看到內(nèi)核提供的所有函數(shù)。 

在以模塊方式編寫驅(qū)動程序時,要實現(xiàn)兩個必不可少的函數(shù)init_module( )和cleanup_module( ),而且至少要包含<linux/krernel.h>和<linux/module.h>兩個頭文件。在用gcc編譯內(nèi)核模塊時,需要加上-DMODULE -D__KERNEL__ -DLINUX這幾個參數(shù),編譯生成的模塊(一般為.o文件)可以使用命令insmod載入Linux內(nèi)核,從而成為內(nèi)核的一個組成部分,此時內(nèi)核會調(diào)用模塊中的函數(shù)init_module( )。當不需要該模塊時,可以使用rmmod命令進行卸載,此進內(nèi)核會調(diào)用模塊中的函數(shù)cleanup_module( )。任何時候都可以使用命令來lsmod查看目前已經(jīng)加載的模塊以及正在使用該模塊的用戶數(shù)。 

3. 設(shè)備驅(qū)動程序結(jié)構(gòu)

了解設(shè)備驅(qū)動程序的基本結(jié)構(gòu)(或者稱為框架),對開發(fā)人員而言是非常重要的,Linux的設(shè)備驅(qū)動程序大致可以分為如下幾個部分:驅(qū)動程序的注冊與注銷、設(shè)備的打開與釋放、設(shè)備的讀寫操作、設(shè)備的控制操作、設(shè)備的中斷和輪詢處理。 

驅(qū)動程序的注冊與注銷 
向系統(tǒng)增加一個驅(qū)動程序意味著要賦予它一個主設(shè)備號,這可以通過在驅(qū)動程序的初始化過程中調(diào)用register_chrdev( )或者register_blkdev( )來完成。而在關(guān)閉字符設(shè)備或者塊設(shè)備時,則需要通過調(diào)用unregister_chrdev( )或unregister_blkdev( )從內(nèi)核中注銷設(shè)備,同時釋放占用的主設(shè)備號。 

設(shè)備的打開與釋放 
打開設(shè)備是通過調(diào)用file_operations結(jié)構(gòu)中的函數(shù)open( )來完成的,它是驅(qū)動程序用來為今后的操作完成初始化準備工作的。在大部分驅(qū)動程序中,open( )通常需要完成下列工作: 

檢查設(shè)備相關(guān)錯誤,如設(shè)備尚未準備好等。 
如果是第一次打開,則初始化硬件設(shè)備。 
識別次設(shè)備號,如果有必要則更新讀寫操作的當前位置指針f_ops。 
分配和填寫要放在file->private_data里的數(shù)據(jù)結(jié)構(gòu)。 
使用計數(shù)增1。 
釋放設(shè)備是通過調(diào)用file_operations結(jié)構(gòu)中的函數(shù)release( )來完成的,這個設(shè)備方法有時也被稱為close( ),它的作用正好與open( )相反,通常要完成下列工作: 

使用計數(shù)減1。 
釋放在file->private_data中分配的內(nèi)存。 
如果使用計算為0,則關(guān)閉設(shè)備。 
設(shè)備的讀寫操作 
字符設(shè)備的讀寫操作相對比較簡單,直接使用函數(shù)read( )和write( )就可以了。但如果是塊設(shè)備的話,則需要調(diào)用函數(shù)block_read( )和block_write( )來進行數(shù)據(jù)讀寫,這兩個函數(shù)將向設(shè)備請求表中增加讀寫請求,以便Linux內(nèi)核可以對請求順序進行優(yōu)化。由于是對內(nèi)存緩沖區(qū)而不是直接對設(shè)備進行操作的,因此能很大程度上加快讀寫速度。如果內(nèi)存緩沖區(qū)中沒有所要讀入的數(shù)據(jù),或者需要執(zhí)行寫操作將數(shù)據(jù)寫入設(shè)備,那么就要執(zhí)行真正的數(shù)據(jù)傳輸,這是通過調(diào)用數(shù)據(jù)結(jié)構(gòu)blk_dev_struct中的函數(shù)request_fn( )來完成的。 

設(shè)備的控制操作 
除了讀寫操作外,應(yīng)用程序有時還需要對設(shè)備進行控制,這可以通過設(shè)備驅(qū)動程序中的函數(shù)ioctl( )來完成。ioctl( )的用法與具體設(shè)備密切關(guān)聯(lián),因此需要根據(jù)設(shè)備的實際情況進行具體分析。 

設(shè)備的中斷和輪詢處理 

對于不支持中斷的硬件設(shè)備,讀寫時需要輪流查詢設(shè)備狀態(tài),以便決定是否繼續(xù)進行數(shù)據(jù)傳輸。如果設(shè)備支持中斷,則可以按中斷方式進行操作。  

三、PCI驅(qū)動程序?qū)崿F(xiàn)

1. 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)

PCI設(shè)備上有三種地址空間:PCI的I/O空間、PCI的存儲空間和PCI的配置空間。CPU可以訪問PCI設(shè)備上的所有地址空間,其中I/O空間和存儲空間提供給設(shè)備驅(qū)動程序使用,而配置空間則由Linux內(nèi)核中的PCI初始化代碼使用。內(nèi)核在啟動時負責(zé)對所有PCI設(shè)備進行初始化,配置好所有的PCI設(shè)備,包括中斷號以及I/O基址,并在文件/proc/pci中列出所有找到的PCI設(shè)備,以及這些設(shè)備的參數(shù)和屬性。 

Linux驅(qū)動程序通常使用結(jié)構(gòu)(struct)來表示一種設(shè)備,而結(jié)構(gòu)體中的變量則代表某一具體設(shè)備,該變量存放了與該設(shè)備相關(guān)的所有信息。好的驅(qū)動程序都應(yīng)該能驅(qū)動多個同種設(shè)備,每個設(shè)備之間用次設(shè)備號進行區(qū)分,如果采用結(jié)構(gòu)數(shù)據(jù)來代表所有能由該驅(qū)動程序驅(qū)動的設(shè)備,那么就可以簡單地使用數(shù)組下標來表示次設(shè)備號。 

在PCI驅(qū)動程序中,下面幾個關(guān)鍵數(shù)據(jù)結(jié)構(gòu)起著非常核心的作用: 

pci_driver 
這個數(shù)據(jù)結(jié)構(gòu)在文件include/linux/pci.h里,這是Linux內(nèi)核版本2.4之后為新型的PCI設(shè)備驅(qū)動程序所添加的,其中最主要的是用于識別設(shè)備的id_table結(jié)構(gòu),以及用于檢測設(shè)備的函數(shù)probe( )和卸載設(shè)備的函數(shù)remove( ): 

struct pci_driver {
    struct list_head node;
    char *name;
    const struct pci_device_id *id_table;
    int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);
    void (*remove) (struct pci_dev *dev);
    int  (*save_state) (struct pci_dev *dev, u32 state);
    int  (*suspend)(struct pci_dev *dev, u32 state);
    int  (*resume) (struct pci_dev *dev);
    int  (*enable_wake) (struct pci_dev *dev, u32 state, int enable);
};
 



pci_dev 
這個數(shù)據(jù)結(jié)構(gòu)也在文件include/linux/pci.h里,它詳細描述了一個PCI設(shè)備幾乎所有的硬件信息,包括廠商ID、設(shè)備ID、各種資源等: 

struct pci_dev {
    struct list_head global_list;
    struct list_head bus_list;
    struct pci_bus  *bus;
    struct pci_bus  *subordinate;
    void        *sysdata;
    struct proc_dir_entry *procent;
    unsigned int    devfn;
    unsigned short  vendor;
    unsigned short  device;
    unsigned short  subsystem_vendor;
    unsigned short  subsystem_device;
    unsigned int    class;
    u8      hdr_type;
    u8      rom_base_reg;
    struct pci_driver *driver;
    void        *driver_data;
    u64     dma_mask;
    u32             current_state;
    unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
    unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
    unsigned int    irq;
    struct resource resource[DEVICE_COUNT_RESOURCE];
    struct resource dma_resource[DEVICE_COUNT_DMA];
    struct resource irq_resource[DEVICE_COUNT_IRQ];
    char        name[80];
    char        slot_name[8];
    int     active;
    int     ro;
    unsigned short  regs;
    int (*prepare)(struct pci_dev *dev);
    int (*activate)(struct pci_dev *dev);
    int (*deactivate)(struct pci_dev *dev);
};
 



2. 基本框架

在用模塊方式實現(xiàn)PCI設(shè)備驅(qū)動程序時,通常至少要實現(xiàn)以下幾個部分:初始化設(shè)備模塊、設(shè)備打開模塊、數(shù)據(jù)讀寫和控制模塊、中斷處理模塊、設(shè)備釋放模塊、設(shè)備卸載模塊。下面給出一個典型的PCI設(shè)備驅(qū)動程序的基本框架,從中不難體會到這幾個關(guān)鍵模塊是如何組織起來的。 

/* 指明該驅(qū)動程序適用于哪一些PCI設(shè)備 */
static struct pci_device_id demo_pci_tbl [] __initdata = {
    {PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,
     PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},
    {0,}
};
/* 對特定PCI設(shè)備進行描述的數(shù)據(jù)結(jié)構(gòu) */
struct demo_card {
    unsigned int magic;
    /* 使用鏈表保存所有同類的PCI設(shè)備 */
    struct demo_card *next;
    
    /* ... */
}
/* 中斷處理模塊 */
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    /* ... */
}
/* 設(shè)備文件操作接口 */
static struct file_operations demo_fops = {
    owner:      THIS_MODULE,   /* demo_fops所屬的設(shè)備模塊 */
    read:       demo_read,    /* 讀設(shè)備操作*/
    write:      demo_write,    /* 寫設(shè)備操作*/
    ioctl:      demo_ioctl,    /* 控制設(shè)備操作*/
    mmap:       demo_mmap,    /* 內(nèi)存重映射操作*/
    open:       demo_open,    /* 打開設(shè)備操作*/
    release:    demo_release    /* 釋放設(shè)備操作*/
    /* ... */
};
/* 設(shè)備模塊信息 */
static struct pci_driver demo_pci_driver = {
    name:       demo_MODULE_NAME,    /* 設(shè)備模塊名稱 */
    id_table:   demo_pci_tbl,    /* 能夠驅(qū)動的設(shè)備列表 */
    probe:      demo_probe,    /* 查找并初始化設(shè)備 */
    remove:     demo_remove    /* 卸載設(shè)備模塊 */
    /* ... */
};
static int __init demo_init_module (void)
{
    /* ... */
}
static void __exit demo_cleanup_module (void)
{
    pci_unregister_driver(&demo_pci_driver);
}
/* 加載驅(qū)動程序模塊入口 */
module_init(demo_init_module);
/* 卸載驅(qū)動程序模塊入口 */
module_exit(demo_cleanup_module);
 



上面這段代碼給出了一個典型的PCI設(shè)備驅(qū)動程序的框架,是一種相對固定的模式。需要注意的是,同加載和卸載模塊相關(guān)的函數(shù)或數(shù)據(jù)結(jié)構(gòu)都要在前面加上__init、__exit等標志符,以使同普通函數(shù)區(qū)分開來。構(gòu)造出這樣一個框架之后,接下去的工作就是如何完成框架內(nèi)的各個功能模塊了。 

3. 初始化設(shè)備模塊

在Linux系統(tǒng)下,想要完成對一個PCI設(shè)備的初始化,需要完成以下工作:

檢查PCI總線是否被Linux內(nèi)核支持; 
檢查設(shè)備是否插在總線插槽上,如果在的話則保存它所占用的插槽的位置等信息。 
讀出配置頭中的信息提供給驅(qū)動程序使用。 
當Linux內(nèi)核啟動并完成對所有PCI設(shè)備進行掃描、登錄和分配資源等初始化操作的同時,會建立起系統(tǒng)中所有PCI設(shè)備的拓撲結(jié)構(gòu),此后當PCI驅(qū)動程序需要對設(shè)備進行初始化時,一般都會調(diào)用如下的代碼: 

static int __init demo_init_module (void)
{
    /* 檢查系統(tǒng)是否支持PCI總線 */
    if (!pci_present())
        return -ENODEV;
    /* 注冊硬件驅(qū)動程序 */
    if (!pci_register_driver(&demo_pci_driver)) {
        pci_unregister_driver(&demo_pci_driver);
                return -ENODEV;
    }
    /* ... */
   
    return 0;
}
 



驅(qū)動程序首先調(diào)用函數(shù)pci_present( )檢查PCI總線是否已經(jīng)被Linux內(nèi)核支持,如果系統(tǒng)支持PCI總線結(jié)構(gòu),這個函數(shù)的返回值為0,如果驅(qū)動程序在調(diào)用這個函數(shù)時得到了一個非0的返回值,那么驅(qū)動程序就必須得中止自己的任務(wù)了。在2.4以前的內(nèi)核中,需要手工調(diào)用pci_find_device( )函數(shù)來查找PCI設(shè)備,但在2.4以后更好的辦法是調(diào)用pci_register_driver( )函數(shù)來注冊PCI設(shè)備的驅(qū)動程序,此時需要提供一個pci_driver結(jié)構(gòu),在該結(jié)構(gòu)中給出的probe探測例程將負責(zé)完成對硬件的檢測工作。 

 
static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
    struct demo_card *card;
    /* 啟動PCI設(shè)備 */
    if (pci_enable_device(pci_dev))
        return -EIO;
    /* 設(shè)備DMA標識 */
    if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) {
        return -ENODEV;
    }
    /* 在內(nèi)核空間中動態(tài)申請內(nèi)存 */
    if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) {
        printk(KERN_ERR "pci_demo: out of memory ");
        return -ENOMEM;
    }
    memset(card, 0, sizeof(*card));
    /* 讀取PCI配置信息 */
    card->iobase = pci_resource_start (pci_dev, 1);
    card->pci_dev = pci_dev;
    card->pci_id = pci_id->device;
    card->irq = pci_dev->irq;
    card->next = devs;
    card->magic = DEMO_CARD_MAGIC;
    /* 設(shè)置成總線主DMA模式 */    
    pci_set_master(pci_dev);
    /* 申請I/O資源 */
    request_region(card->iobase, 64, card_names[pci_id->driver_data]);
    return 0;
}
 



4. 打開設(shè)備模塊

在這個模塊里主要實現(xiàn)申請中斷、檢查讀寫模式以及申請對設(shè)備的控制權(quán)等。在申請控制權(quán)的時候,非阻塞方式遇忙返回,否則進程主動接受調(diào)度,進入睡眠狀態(tài),等待其它進程釋放對設(shè)備的控制權(quán)。 

static int demo_open(struct inode *inode, struct file *file)
{
    /* 申請中斷,注冊中斷處理程序 */
    request_irq(card->irq, &demo_interrupt, SA_SHIRQ,
        card_names[pci_id->driver_data], card)) {
    /* 檢查讀寫模式 */
    if(file->f_mode & FMODE_READ) {
        /* ... */
    }
    if(file->f_mode & FMODE_WRITE) {
       /* ... */
    }
    
    /* 申請對設(shè)備的控制權(quán) */
    down(&card->open_sem);
    while(card->open_mode & file->f_mode) {
        if (file->f_flags & O_NONBLOCK) {
            /* NONBLOCK模式,返回-EBUSY */
            up(&card->open_sem);
            return -EBUSY;
        } else {
            /* 等待調(diào)度,獲得控制權(quán) */
            card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE);
            up(&card->open_sem);
            /* 設(shè)備打開計數(shù)增1 */
            MOD_INC_USE_COUNT;
            /* ... */
        }
    }
}
 



5. 數(shù)據(jù)讀寫和控制信息模塊

PCI設(shè)備驅(qū)動程序可以通過demo_fops 結(jié)構(gòu)中的函數(shù)demo_ioctl( ),向應(yīng)用程序提供對硬件進行控制的接口。例如,通過它可以從I/O寄存器里讀取一個數(shù)據(jù),并傳送到用戶空間里: 

static int demo_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    /* ... */
    
    switch(cmd) {
        case DEMO_RDATA:
            /* 從I/O端口讀取4字節(jié)的數(shù)據(jù) */
            val = inl(card->iobae + 0x10);
            
/* 將讀取的數(shù)據(jù)傳輸?shù)接脩艨臻g */
            return 0;
    }
    
    /* ... */
}
 



事實上,在demo_fops里還可以實現(xiàn)諸如demo_read( )、demo_mmap( )等操作,Linux內(nèi)核源碼中的driver目錄里提供了許多設(shè)備驅(qū)動程序的源代碼,找那里可以找到類似的例子。在對資源的訪問方式上,除了有I/O指令以外,還有對外設(shè)I/O內(nèi)存的訪問。對這些內(nèi)存的操作一方面可以通過把I/O內(nèi)存重新映射后作為普通內(nèi)存進行操作,另一方面也可以通過總線主DMA(Bus Master DMA)的方式讓設(shè)備把數(shù)據(jù)通過DMA傳送到系統(tǒng)內(nèi)存中。 

6. 中斷處理模塊

PC的中斷資源比較有限,只有0~15的中斷號,因此大部分外部設(shè)備都是以共享的形式申請中斷號的。當中斷發(fā)生的時候,中斷處理程序首先負責(zé)對中斷進行識別,然后再做進一步的處理。 

static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    struct demo_card *card = (struct demo_card *)dev_id;
    u32 status;
    spin_lock(&card->lock);
    /* 識別中斷 */
    status = inl(card->iobase + GLOB_STA);
    if(!(status & INT_MASK)) 
    {
        spin_unlock(&card->lock);
        return;  /* not for us */
    }
    /* 告訴設(shè)備已經(jīng)收到中斷 */
    outl(status & INT_MASK, card->iobase + GLOB_STA);
    spin_unlock(&card->lock);
    
    /* 其它進一步的處理,如更新DMA緩沖區(qū)指針等 */
}
 



7. 釋放設(shè)備模塊

釋放設(shè)備模塊主要負責(zé)釋放對設(shè)備的控制權(quán),釋放占用的內(nèi)存和中斷等,所做的事情正好與打開設(shè)備模塊相反:

static int demo_release(struct inode *inode, struct file *file)
{
    /* ... */
    
    /* 釋放對設(shè)備的控制權(quán) */
    card->open_mode &= (FMODE_READ | FMODE_WRITE);
    
    /* 喚醒其它等待獲取控制權(quán)的進程 */
    wake_up(&card->open_wait);
    up(&card->open_sem);
    
    /* 釋放中斷 */
    free_irq(card->irq, card);
    
    /* 設(shè)備打開計數(shù)增1 */
    MOD_DEC_USE_COUNT;
    
    /* ... */  
}
 



8. 卸載設(shè)備模塊

卸載設(shè)備模塊與初始化設(shè)備模塊是相對應(yīng)的,實現(xiàn)起來相對比較簡單,主要是調(diào)用函數(shù)pci_unregister_driver( )從Linux內(nèi)核中注銷設(shè)備驅(qū)動程序: 

static void __exit demo_cleanup_module (void)
{
    pci_unregister_driver(&demo_pci_driver);

 
四、小結(jié)

PCI總線不僅是目前應(yīng)用廣泛的計算機總線標準,而且是一種兼容性最強、功能最全的計算機總線。而Linux作為一種新的操作系統(tǒng),其發(fā)展前景是無法估量的,同時也為PCI總線與各種新型設(shè)備互連成為可能。由于Linux源碼開放,因此給連接到PCI總線上的任何設(shè)備編寫驅(qū)動程序變得相對容易。本文介紹如何編譯Linux下的PCI驅(qū)動程序,針對的內(nèi)核版本是2.4。 

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉