Linux2.6內核PCI驅動程序開發

一,PCI相關數據結構說明

1.1struct pci_driver

這個數據結構在文件/linux/pci.h裏,這是Linux內核版本2.4之後爲新型的PCI設備驅動程序所添加的,其中最主要的是用於識別設備的id_table結構,以及用於檢測設備的函數probe( )和卸載設備的函數remove( )

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

};

爲創建一個正確的struct pci_driver 結構, 只有4個字段需要被初始化:name,id_table,proberemove

其中id_table初始化可以用到宏PCI_DEVICE(VENDOR_ID,DEVICE_ID)VENDOR_IDDEVICE_ID分別爲設備和廠商編號,由板卡生產廠家指定。

Static const struct pci_device_id mypci[] =

      {

             {

                    PCI_DEVICE(VENDOR_ID,DEVICE_ID)

},

{}

};

1.2pci_dev

這個數據結構也在文件include/linux/pci.h裏,它詳細描述了一個PCI設備幾乎所有的硬件信息,包括廠商ID、設備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);

};

二,PCI驅動基本框架

在用模塊方式實現PCI設備驅動程序時,通常至少要實現以下幾個部分:初始化設備模塊、設備打開模塊、數據讀寫和控制模塊、中斷處理模塊、設備釋放模塊、設備卸載模塊。下面給出一個典型的PCI設備驅動程序的基本框架。

/* 指明該驅動程序適用於哪一些PCI設備 */

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設備進行描述的數據結構 */

struct demo_card {

  unsigned int magic;


  /* 使用鏈表保存所有同類的PCI設備 */

  struct demo_card *next;


  /* ... */

}


/* 中斷處理模塊 */

static void demo_interrupt(int irq, void*dev_id, struct pt_regs *regs)

{

  /* ... */

}



/* 設備模塊信息 */

static struct pci_driver demo_pci_driver ={

  name:       demo_MODULE_NAME,    /* 設備模塊名稱 */

  id_table:   demo_pci_tbl,    /* 能夠驅動的設備列表 */

  probe:      demo_probe,    /* 查找並初始化設備 */

  remove:     demo_remove    /* 卸載設備模塊 */

  /* ... */

};


static int __init demo_init_module (void)

{

  pci_register_driver(&demo_pci_driver);

}


static void __exit demo_cleanup_module(void)

{

  pci_unregister_driver(&demo_pci_driver);

}


/* 加載驅動程序模塊入口 */

module_init(demo_init_module);


/* 卸載驅動程序模塊入口 */

module_exit(demo_cleanup_module);

三,PCI設備操作實現

3.1設備初始化

demo_init_module 中,用pci_register_driver( )函數來註冊PCI設備的驅動程序,此時需要提供一個pci_driver結構,在該結構中給出的probe探測例程將負責完成對硬件的檢測工作。

probe函數中,需要實現以下幾個功能:

1)使能PCI

PCI 驅動的探測函數中, 在驅動可存取 PCI 設備的任何設備資源(I/O 區或者中斷)之前, 驅動必須調用pci_enable_device 函數:

int pci_enable_device(struct pci_dev *dev);

這個函數實際上使能設備. 它喚醒設備以及在某些情況下也分配它的中斷線和 I/O . 例如, 這發生在 CardBus 設備上(它在驅動層次上已經完全和PCI 等同了).

(2)請求PCI資源

在初始化中很重要的一個操作就是讓系統爲PCI分配資源,如I/O端口等。

      pci_resource_regions(struct pci_dev  *dev, char * name);

(3)存取配置空間

因爲微處理器無法直接存取配置空間, 計算機供應商不得不提供一個方法來完成它. 爲存取配置空間, CPU 必須寫和讀 PCI 控制器中的寄存器, 但是確切的實現是依賴於供應商的, 並且和這個討論無關, 因爲 Linux提供了一個標準接口來存取配置空間.

對於驅動, 配置空間可通過8-, 16-, 或者 32-位數據傳輸來存取. 相關的函數原型定義於 <linux/pci.h>:

int pci_read_config_byte(struct pci_dev*dev, int where, u8 *val);

int pci_read_config_word(struct pci_dev*dev, int where, u16 *val);

int pci_read_config_dword(struct pci_dev*dev, int where, u32 *val);

從由 dev 所標識出的設備的配置空間讀1 , 2 個或者 4 個字節. where 參數是從配置空間開始的字節偏移. 從配置空間取得的值通過val 指針返回, 並且這個函數的返回值是一個錯誤碼. word dword 函數轉換剛剛讀的值從小端到處理器的本地字節序, 因此你不必處理字節序.

int pci_write_config_byte(struct pci_dev*dev, int where, u8 val);

int pci_write_config_word(struct pci_dev*dev, int where, u16 val);

int pci_write_config_dword(struct pci_dev*dev, int where, u32 val);

4)映射 I/O 和內存空間

每個PCI可以有1-6I/O或者內存空間,並且每塊空間都有一個BAR寄存器與其空間首地址想對應。BAR0-6寄存器的最後一位爲只讀,爲1則說明該空間爲I/O,否則爲MEM

下面以BAR0爲例介紹如何映射空間到虛擬內存,以便用戶訪問。

LocalAddr0 =pci_resource_start(dev,0);

//得到BAR0區域的開始地址

Map0 = (unsignedchar *)ioremap(LocalAddr0, pci_resource_len(dev,0));

      //BAR0區域影射到內存虛擬地址

如果ioremap函數出現問題,可以嘗試ioport_mappci_iomap。這樣以後就可以針對Map0進行讀寫,而忽略具體硬件是I/O或是MEM的細節。

5)註冊中斷

int request_irq(      unsigned int irq,

               irqreturn_t (*handler)(int,void *, struct pt_regs *),

               unsigned long flags,

               const char *dev_name,

               void *dev_id);

request_irq 返回給請求函數的返回值或者是 0 指示成功, 或者是一個負的錯誤碼, 如同平常. 函數返回 -EBUSY 來指示另一個驅動已經使用請求的中斷線是不尋常的.

3.2數據讀寫

使用在初始化過程中影射後的虛擬內存地址(Map0)進行讀寫,並轉換到用戶空間,最後傳給應用程序。可以使用以下讀寫函數:

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


你可以使用這些函數的重複版本,

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

這些函數讀或寫從給定的buf 給定的 addrcount .


需要操作一塊 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);


3.3中斷處理

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

      irqreturn_tshort_interrupt(int irq, void *dev, struct pt_regs *regs);

在該函數中實現中斷服務,每次硬件檢測到中斷,都會調用該函數。


3.4釋放設備

釋放設備模塊主要負責釋放對設備的控制權,釋放佔用的內存和中斷等,所做的事情正好與設備初始化相反。

demo_cleanup_module中卸載PCI驅動:

pci_unregister_driver()

pci_driverremove中釋放所請求的資源:

iounmap(Map0);

free_irq(int irq,pci_dev *dev)

pci_release_regions(pci_dev *dev)


3.5Makefile文件的編寫

      PWD= $(shell pwd)

      KERNEL_SRC=/usr/src/linux

      obj-m:=mypci.o

module_objs :=mypci.o

all:

      $(MAKE) –C $( KERNEL_SRC) M=$(PWD)modules

clean:

      rm *.ro

      rm *.o


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章