天天看點

pci驅動架構分析

一、字元裝置和塊裝置

Linux抽象了對硬體的處理,所有的硬體裝置都可以像普通檔案一樣來看待:它們可以使用和操作檔案相同的、标準的系統調用接口來完成打開、關閉、讀寫和I/O控制操作,而驅動程式的主要任務也就是要實作這些系統調用函數。Linux系統中的所有硬體裝置都使用一個特殊的裝置檔案來表示,例如,系統中的第一個IDE硬碟使用/dev/hda表示。每個裝置檔案對應有兩個裝置号:一個是主裝置号,辨別該裝置的種類,也辨別了該裝置所使用的驅動程式;另一個是次裝置号,辨別使用同一裝置驅動程式的不同硬體裝置。裝置檔案的主裝置号必須與裝置驅動程式在登入該裝置時申請的主裝置号一緻,否則使用者程序将無法通路到裝置驅動程式。

在Linux作業系統下有兩類主要的裝置檔案:一類是字元裝置,另一類則是塊裝置。字元裝置是以位元組為機關逐個進行I/O操作的裝置,在對字元裝置發出讀寫請求時,實際的硬體I/O緊接着就發生了,一般來說字元裝置中的緩存是可有可無的,而且也不支援随機通路。塊裝置則是利用一塊系統記憶體作為緩沖區,當使用者程序對裝置進行讀寫請求時,驅動程式先檢視緩沖區中的内容,如果緩沖區中的資料能滿足使用者的要求就傳回相應的資料,否則就調用相應的請求函數來進行實際的I/O操作。塊裝置主要是針對磁盤等慢速裝置設計的,其目的是避免耗費過多的CPU時間來等待操作的完成。一般說來,PCI卡通常都屬于字元裝置。

所有已經注冊(即已經加載了驅動程式)的硬體裝置的主裝置号可以從/proc/devices檔案中得到。使用mknod指令可以建立指定類型的裝置檔案,同時為其配置設定相應的主裝置号和次裝置号。例如,下面的指令:

[[email protected] root]# mknod /dev/lp0 c 6 0

将建立一個主裝置号為6,次裝置号為0的字元裝置檔案/dev/lp0。當應用程式對某個裝置檔案進行系統調用時,Linux核心會根據該裝置檔案的裝置類型和主裝置号調用相應的驅動程式,并從使用者态進入到核心态,再由驅動程式判斷該裝置的次裝置号,最終完成對相應硬體的操作。

二、裝置驅動程式接口

Linux中的I/O子系統向核心中的其他部分提供了一個統一的标準裝置接口,這是通過include/linux/fs.h中的資料結構file_operations來完成的:

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, loff_t, loff_t, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

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

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **);

long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

當應用程式對裝置檔案進行諸如open、close、read、write等操作時,Linux核心将通過file_operations結構通路驅動程 序提供的函數。例如,當應用程式對裝置檔案執行讀操作時,核心将調用file_operations結構中的read函數。

三、裝置驅動程式子產品

Linux下的裝置驅動程式可以按照兩種方式進行編譯,一種是直接靜态編譯成核心的一部分,另一種則是編譯成可以動态加載的子產品。如果編譯進核心的話,會增加核心的大小,還要改動核心的源檔案,而且不能動态地解除安裝,不利于調試,所有推薦使用子產品方式。

從本質上來講,子產品也是核心的一部分,它不同于普通的應用程式,不能調用位于使用者态下的C或者C++庫函數,而隻能調用Linux核心提供的函數,在/proc/ksyms中可以檢視到核心提供的所有函數。

在以子產品方式編寫驅動程式時,要實作兩個必不可少的函數init_module( )和cleanup_module( ),而且至少要包含和兩 個頭檔案。一般使用LDD3 例程中使用的makefile 作為基本的版本,稍作改變之後用來編譯驅動,編譯生成的子產品(一般為.ko檔案)可以使用指令insmod載入Linux核心,進而成為核心的一個組成部分,此時核心會調用 子產品中的函數init_module( )。當不需要該子產品時,可以使用rmmod指令進行解除安裝,此進核心會調用子產品中的函數cleanup_module( )。任何時候都可以使用指令來lsmod檢視目前已經加載的子產品以及正在使用該子產品的使用者數。

四、裝置驅動程式結構

了解裝置驅動程式的基本結構(或者稱為架構),對開發人員而言是非常重要的,Linux的裝置驅動程式大緻可以分為如下幾個部分:驅動程式的注冊與登出、裝置的打開與釋放、裝置的讀寫操作、裝置的控制操作、裝置的中斷和輪詢處理。

1.驅動程式的注冊與登出

向系統增加一個驅動程式意味着要賦予它一個主裝置号,這可以通過在驅動程式的初始化過程中調用alloc_chrdev_region( )或者register_chrdev_region( )來完成。而在關閉字元裝置時,則需要通過調用unregister_chrdev_region( )從核心中登出裝置,同時釋放占用的主裝置号。

2.裝置的打開與釋放

打開裝置是通過調用file_operations結構中的函數open( )來完成的,它是驅動程式用來為今後的操作完成初始化準備工作的。在大部分驅動程式中,open( )通常需要完成下列工作:

a. 檢查裝置相關錯誤,如裝置尚未準備好等。

b. 如果是第一次打開,則初始化硬體裝置。

c. 識别次裝置号,如果有必要則更新讀寫操作的目前位置指針f_ops。

d. 配置設定和填寫要放在file->private_data裡的資料結構。

e. 使用計數增1。

釋放裝置是通過調用file_operations結構中的函數release( )來完成的,這個裝置方法有時也被稱為close( ),它的作用正好與open( )相反,通常要完成下列工作:

a. 使用計數減1。

b. 釋放在file->private_data中配置設定的記憶體。

c. 如果使用計算為0,則關閉裝置。

3.裝置的讀寫操作

字元裝置的讀寫操作相對比較簡單,直接使用函數read( )和write( )就可以了。但如果是塊裝置的話,則需要調用函數block_read( )和block_write( )來進行資料讀寫,這兩個函數将向裝置請求表中增加讀寫請求,以便Linux核心可以對請求順序進行優化。由于是對記憶體緩沖區而不是直接對裝置進行操作的,是以能很大程度上加快讀寫速度。如果記憶體緩沖區中沒有所要讀入的資料,或者需要執行寫操作将資料寫入裝置,那麼就要執行真正的資料傳輸,這是通過調用資料結構blk_dev_struct中的函數request_fn( )來完成的。

4.裝置的控制操作

除了讀寫操作外,應用程式有時還需要對裝置進行控制,這可以通過裝置驅動程式中的函數ioctl( )來完成,ioctl 系統調用有下面的原型: int ioctl(int fd, unsigned long cmd, …),第一個參數是檔案描述符,第二個參數是具體的指令,一般使用宏定義來确定,第三個參數一般是傳遞給驅動中處理裝置控制操作函數的參數。ioctl( )的用法與具體裝置密切關聯,是以需要根據裝置的實際情況進行具體分析。

5.裝置的中斷和輪詢處理

對于不支援中斷的硬體裝置,讀寫時需要輪流查詢裝置狀态,以便決定是否繼續進行資料傳輸。如果裝置支援中斷,則可以按中斷方式進行操作。

五、PCI驅動程式架構

1.關鍵資料結構

PCI裝置上有三種位址空間:PCI的I/O空間、PCI的存儲空間和PCI的配置空間。CPU可以通路PCI裝置上的所有位址空間,其中I/O空間和存儲空間提供給裝置驅動程式使用,而配置空間則由Linux核心中的PCI初始化代碼使用。核心在啟動時負責對所有PCI裝置進行初始化,配置好所有的PCI裝置,包括中斷号以及I/O基址,并在檔案/proc/pci中列出所有找到的PCI裝置,以及這些裝置的參數和屬性。

Linux驅動程式通常使用結構(struct)來表示一種裝置,而結構體中的變量則代表某一具體裝置,該變量存放了與該裝置相關的所有資訊。好的驅動程式都應該能驅動多個同種裝置,每個裝置之間用次裝置号進行區分,如果采用結構資料來代表所有能由該驅動程式驅動的裝置,那麼就可以簡單地使用數組下标來表示次裝置号。

在PCI驅動程式中,下面幾個關鍵資料結構起着非常核心的作用:

a. pci_driver

這個資料結構在檔案include/linux/pci.h裡,其中最主要的是用于識别裝置的id_table結構,以及用于檢測裝置的函數probe( )和解除安裝裝置的函數remove( ):

struct pci_driver {

struct list_head node;

const char *name;

const struct pci_device_id id_table; / must be non-NULL for probe to be called */

int (*probe) (struct pci_dev *dev, const struct pci_device_id id); / New device inserted */

void (*remove) (struct pci_dev dev); / Device removed (NULL if not a hot-plug capable driver) */

int (*suspend) (struct pci_dev dev, pm_message_t state); / Device suspended */

int (*suspend_late) (struct pci_dev *dev, pm_message_t state);

int (*resume_early) (struct pci_dev *dev);

int (*resume) (struct pci_dev dev); / Device woken up */

void (*shutdown) (struct pci_dev *dev);

int (*sriov_configure) (struct pci_dev dev, int num_vfs); / PF pdev */

const struct pci_error_handlers *err_handler;

struct device_driver driver;

struct pci_dynids dynids;

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

b. pci_dev

這個資料結構也在檔案include/linux/pci.h裡,它較長的描述了一個PCI裝置幾乎所有的硬體資訊,包括廠商ID、裝置ID、各種資源等

struct pci_dev {

struct list_head bus_list;

struct pci_bus bus; / bus this device is on */

struct pci_bus subordinate; / bus this device bridges to */

void sysdata; / hook for sys-specific extension */

struct proc_dir_entry procent; / device entry in /proc/bus/pci */

struct pci_slot slot; / Physical slot this device is in /

unsigned int devfn; / encoded device & function index /

unsigned short vendor;

unsigned short device;

unsigned short subsystem_vendor;

unsigned short subsystem_device;

unsigned int class; / 3 bytes: (base,sub,prog-if) /

u8 revision; / PCI revision, low byte of class word /

u8 hdr_type; / PCI header type (`multi’ flag masked out) /

u8 pcie_type; / PCI-E device/port type /

u8 rom_base_reg; / which config register controls the ROM /

u8 pin; / which interrupt pin this device uses */

struct pci_driver driver; / which driver has allocated this device /

u64 dma_mask; / Mask of the bits of bus address this device implements. Normally this is 0xffffffff. You only need to change this if your device has broken DMA or supports 64-bit transfers. /

struct device_dma_parameters dma_parms;

pci_power_t current_state; / Current operating state. In ACPI-speak, this is D0-D3, D0 being fully functional, and D3 being off. /

int pm_cap; / PM capability offset in the configuration space /

unsigned int pme_support:5; / Bitmask of states from which PME# can be generated /

unsigned int d1_support:1; / Low power state D1 is supported /

unsigned int d2_support:1; / Low power state D2 is supported /

unsigned int no_d1d2:1; / Only allow D0 and D3 */

unsigned int wakeup_prepared:1;

#ifdef CONFIG_PCIEASPM

struct pcie_link_state link_state; / ASPM link state. /

#endif

pci_channel_state_t error_state; / current connectivity state /

struct device dev; / Generic device interface /

int cfg_size; / Size of configuration space /

/

* Instead of touching interrupt line and base address registers

* directly, use the values stored here. They might be different!

/

unsigned int irq;

struct resource resource[DEVICE_COUNT_RESOURCE]; / I/O and memory regions + expansion ROMs /

/ These fields are used by common fixups /

unsigned int transparent:1; / Transparent PCI bridge /

unsigned int multifunction:1;/ Part of multi-function device /

/ keep track of device state /

unsigned int is_added:1;

unsigned int is_busmaster:1; / device is busmaster /

unsigned int no_msi:1; / device may not use msi /

unsigned int block_ucfg_access:1; / userspace config space access is blocked /

unsigned int broken_parity_status:1; / Device generates false positive parity /

unsigned int irq_reroute_variant:2; / device needs IRQ rerouting variant /

unsigned int msi_enabled:1;

unsigned int msix_enabled:1;

unsigned int ari_enabled:1; / ARI forwarding /

unsigned int is_managed:1;

unsigned int is_pcie:1;

unsigned int needs_freset:1; / Dev requires fundamental reset /

unsigned int state_saved:1;

unsigned int is_physfn:1;

unsigned int is_virtfn:1;

unsigned int reset_fn:1;

unsigned int is_hotplug_bridge:1;

pci_dev_flags_t dev_flags;

atomic_t enable_cnt; / pci_enable_device has been called /

u32 saved_config_space[16]; / config space saved at suspend time */

struct hlist_head saved_cap_space;

struct bin_attribute rom_attr; / attribute descriptor for sysfs ROM entry /

int rom_attr_enabled; / has display of the rom attribute been enabled? */

struct bin_attribute res_attr[DEVICE_COUNT_RESOURCE]; / sysfs file for resources */

struct bin_attribute res_attr_wc[DEVICE_COUNT_RESOURCE]; / sysfs file for WC mapping of resources */

#ifdef CONFIG_PCI_MSI

struct list_head msi_list;

#endif

struct pci_vpd *vpd;

#ifdef CONFIG_PCI_IOV

union {

struct pci_sriov sriov; / SR-IOV capability related */

struct pci_dev physfn; / the PF this VF is associated with */

};

struct pci_ats ats; / Address Translation Service */

#endif

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

2.基本架構

在用子產品方式實作PCI裝置驅動程式時,通常至少要實作以下幾個部分:初始化裝置子產品、裝置打開子產品、資料讀寫和控制子產品、中斷處理子產品、裝置釋放子產品、裝置解除安裝子產品。下面給出一個典型的PCI裝置驅動程式的基本架構,從中不難體會到這幾個關鍵子產品是如何組織起來的。

static struct pci_device_id demo_pci_tbl[] ={

{PCI_DEVICE(PCI_VENDOR_ID_DEMO,PCI_DEVICE_ID_DEMO),},

{0,},

};

};

static irqreturn_t device_interrupt(int irq, void dev_id)

{

/ … */}

static int demo_open(struct inode *inode,struct file file)

{/ … */

try_module_get(THIS_MODULE);

return 0;

}

static int demo_read(struct file *file,char __user *buffer,size_t count,loff_t offp)

{

/ … */}

static int demo_write(struct file *file,const char __user *buffer,size_t count,loff_t offp)

{

/ … */}

static int demo_mmap(struct file *file, struct vm_area_struct vma)

{

/ … */}

static int demo_ioctl(struct inode *inode,struct file file, unsigned int cmd,unsigned long arg)

{

switch(cmd){

case CMD1:

device_func(arg);

break;

default:

}

/ … */}

static int demo_release(struct inode *inode,struct file file)

{/ … */

module_put(THIS_MODULE);

return 0;

}

.write = demo_write,

.open = demo_open,

.ioctl = demo_ioctl,

.mmap = demo_mmap,

.release = demo_release,

};

static int __init demo_probe(struct pci_dev *pci_dev,const struct pci_device_id pci_id)

{

/ … */};

static void __devexit demo_remove(struct pci_dev pci_dev)

{

/ … */};

};

static int __init demo_init_module (void)

{

pci_register_driver(&demo_pci_driver); //注冊裝置驅動

}

static void __exit demo_exit_module(void)

{

pci_unregister_driver(&demo_pci_driver);

}

module_init(demo_init_module);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

上面這段代碼給出了一個典型的PCI裝置驅動程式的架構,是一種相對固定的模式。需要注意的是,同加載和解除安裝子產品相關的函數或資料結構都要在前面加上 __init、__exit等标志符,以使同普通函數區分開來。構造出這樣一個架構之後,接下去的工作就是如何完成架構内的各個功能子產品了。

六、架構的具體實作之子產品操作

1.struct pci_device_id

PCI驅動程式向PCI子系統注冊其支援的廠家ID,裝置ID和裝置類編碼。使用這個資料庫,插入的卡通過配置空間被識别後,PCI子系統把插入的卡和對應的驅動程式綁定。

PCI裝置清單

struct pci_device_id {

__u32 vendor, device;

__u32 subvendor, subdevice;

};

1

2

3

4

5

6

pci_device_id被用在struct pci_device 中。在示例中,建立了一個結構體數組,每一個結構表明使用該結構體數組的驅動支援的裝置,數組的最後一個值是全部設定為0的空結構體,也就是{0,}。這個結構體需要被導出到使用者空間,使熱插拔和子產品裝載系統知道什麼子產品對應什麼硬體裝置,宏MODULE_DEVICE_TABLE完成這個工作。例如:

MODULE_DEVICE_TABLE(pci, demo_pci_tbl);

1

2.初始化裝置子產品

在Linux系統下,想要完成對一個PCI裝置的初始化,需要完成以下工作:

a. 檢查PCI總線是否被Linux核心支援;

b. 檢查裝置是否插在總線插槽上,如果在的話則儲存它所占用的插槽的位置等資訊。

c. 讀出配置頭中的資訊提供給驅動程式使用。

當Linux核心啟動并完成對所有PCI裝置進行掃描、登入和配置設定資源等初始化操作的同時,會建立起系統中所有PCI裝置的拓撲結構。系統加載子產品是調用pci_init_module函數,在這個函數中我們通過pci_register_driver 把new_pci_driver注冊到系統中。在調用pci_register_driver時,需要提供一個pci_driver結構。這個函數首先檢測id_table中定義的PCI資訊是否和系統中的PCI資訊有比對,如果有則傳回0,比對成功後調用probe函數對PCI裝置進行進一步的操作。

static int __init demo_init_module (void)

{

ret = alloc_chrdev_region(&devno, 0, MAX_DEVICE, “buffer”);

ret= pci_register_driver(&demo_pci_driver);

}

1

2

3

4

5

6

probe函數的作用就是啟動pci裝置,讀取配置空間資訊,進行相應的初始化。

static int __init demo_probe(struct pci_dev *pci_dev,const struct pci_device_id *pci_id)

{

int result;

printk(“probe function is running\n”);

struct device_privdata *privdata;

privdata->pci_dev = pci_dev;

//把裝置指針位址放入PCI裝置中的裝置指針中,便于後面調用pci_get_drvdata
pci_set_drvdata(pci_dev, privdata); 

/* 啟動PCI裝置 */
if(pci_enable_device(pci_dev))
{
    printk(KERN_ERR "%s:cannot enable device\n", pci_name(pci_dev)); 
    return -ENODEV;
} 

/*動态申請裝置号,把fops傳進去*/
privdata->cdev = cdev_alloc();
privdata->cdev->ops=&jlas_fops;
privdata->cdev->owner = THIS_MODULE;
cdev_add(privdata->cdev,devno,1);

/*動态建立裝置節點*/
privdata->cdev_class = class_create(THIS_MODULE,DEV_NAME);
device_create(privdata->cdev_class,NULL, devno, pci_dev, DEV_NAME);
privdata->irq=pci_dev->irq;
privdata->iobase=pci_resource_start(privdata->pci_dev, BAR_IO);

/*判斷IO資源是否可用*/
if((pci_resource_flags(pci_dev, BAR_IO) & IORESOURCE_IO) != IORESOURCE_IO)
    goto err_out;

/* 對PCI區進行标記 ,标記該區域已經配置設定出去*/ 
ret= pci_request_regions(pci_dev, DEVICE_NAME);
if(ret) 
     goto err_out;

/*初始化tasklet*/
tasklet_init(&(privdata->my_tasklet),jlas_1780_do_tasklet,(unsigned long )&jlas_pci_cdev); 

/* 初始化自旋鎖 */     
spin_lock_init(&private->lock); 

/*初始化等待隊列*/
init_waitqueue_head(&(privdata->read_queue)); 

/* 設定成總線主DMA模式 */ 
pci_set_master(pci_dev); 

/*申請記憶體*/
privdata->mem = (u32 *) __get_free_pages(GFP_KERNEL|__GFP_DMA | __GFP_ZERO, memorder);
if (!privdata->mem) {   
    goto err_out;
} 

/*DMA映射*/
privdata->dma_addrp = pci_map_single(pdev, privdata->mem,PAGE_SIZE * (1 << memorder), PCI_DMA_FROMDEVICE);
if (pci_dma_mapping_error(pdev, privdata->dma_addrp)) {
    goto err_out;
}  

/*對硬體進行初始化設定,往寄存器中寫一些值,複位硬體等*/
device_init(xx_device);
return 0;
           

err_out:

printk(“error process\n”);

resource_cleanup_dev(FCswitch); //如果出現任何問題,釋放已經配置設定了的資源

return ret;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

3.解除安裝裝置子產品

解除安裝裝置子產品與初始化裝置子產品是相對應的,實作起來相對比較簡單,主要是調用函數pci_unregister_driver( )從Linux核心中登出裝置驅動程式:

static void __exit demo_cleanup_module (void)

{

pci_unregister_driver(&demo_pci_driver);

}

1

2

3

4

在解除安裝子產品時調用pci_cleanup_module,這個函數中通過pci_unregister_driver對new_pci_driver進行登出,這個會調用到remove函數。remove函數的職責就是釋放一切配置設定過的資源,根據自己代碼的需要進行具體的操作。

static void __devexit my_pci_remove(struct pci_dev *pci_dev)

{

struct device_private private;

private= (struct device_private)pci_get_drvdata(pci_dev);

/*對硬體進行操作,如硬體複位*/
Device_close(xx_device);
pci_unmap_single(pdev, privdata->dma_mem,PAGE_SIZE * (1 << memorder), PCI_DMA_FROMDEVICE); 

// 釋放配置設定的記憶體空間
free_pages ((unsigned long) privdata->mem, memorder);
pci_clear_master(pdev); /* Nobody seems to do this */
tasklet_kill(&(privdata->my_tasklet));
pci_release_regions(pci_dev); 

// 移除動态建立的裝置号和裝置
device_destroy(device_class, device->my_dev);
class_destroy(device_class);
if(privdata->pci_dev!=NULL)
    cdev_del(privdata->cdev); 
privdata->pci_dev=NULL;
pci_disable_device(pci_dev);
pci_set_drvdata(pci_dev,NULL);
           

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

4.中斷處理

中斷處理,主要就是讀取中斷寄存器,然後調用中斷處理函數來進行中斷的下半部分,一般通過tasklet或者workqueue來實作。

注意:由于使用request_irq 獲得的中斷是共享中斷,是以在中斷處理函數的上半部需要區分是不是該裝置發出的中斷,這就需要讀取中斷狀态寄存器的值來判斷,如果不是該裝置發起的中斷則 傳回 IRQ_NONE

void jlas_do_tasklet(unsigned long data)

{

spin_lock(&(privdata->my_spin_lock));

//具體操作

spin_unlock(&(privdata->my_spin_lock));

wake_up_interruptible(&(privdata->read_queue));

}

static irqreturn_t device_interrupt(int irq, void *dev_id)

{

struct device_privdata privdata = dev_id;

tasklet_schedule(&(privdata->my_tasklet));

return IRQ_HANDLED;

/ … */}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

七、架構的具體實作之裝置檔案操作

1.裝置檔案操作接口

當應用程式對裝置檔案進行諸如open、close、read、write等操作時,Linux核心将通過file_operations結構通路驅動程式提供的函數。例如,當應用程式對裝置檔案執行讀操作時,核心将調用file_operations結構中的read函數。

.write = demo_write,

.open = demo_open, /

.ioctl = demo_ioctl,

.mmap = demo_mmap,

.release = demo_release,

};

1

2

3

4

5

6

7

8

9

10

2.打開裝置

open 方法提供給驅動來做任何的初始化來準備後續的操作.在這個子產品裡主要實作申請中斷、檢查讀寫模式以及申請對裝置的控制權等。在申請控制權的時候,非阻塞方式遇忙傳回,否則程序主動接受排程,進入睡眠狀态,等待其它程序釋放對裝置的控制權。 open 方法的原型是:

int (*open)(struct inode *inode, struct file *filp);

1

inode 參數有我們需要的資訊,以它的 i_cdev 成員的形式, 裡面包含我們之前建立的cdev 結構. 唯一的問題是通常我們不想要 cdev 結構本身, 我們需要的是包含 cdev 結構的 device_private 結構.

static int demo_open(struct inode *inode, struct file *filp)

{

struct device_private *private;

private= container_of(inode->i_cdev, struct device_private, my_cdev);

filp->private_data = private;

private->open_flag++;

/*申請中斷*/
ret = request_irq(privdata->irq,interrupt_handler, IRQF_SHARED, DEV_NAME,privdata);
if(ret)
    return -EINVAL;   
...
try_module_get(THIS_MODULE);
return 0;
           

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

3.釋放裝置

release 方法的角色是 open 的反面,裝置方法應當進行下面的任務:

a. 釋放 open 配置設定在 filp->private_data 中的任何東西

b. 在最後的 close 關閉裝置

static int demo_release(struct inode *inode,struct file *filp)

{

struct device_private *private= filp->private_data;

private->open_flag–;

free_irq(pdev->irq, privdata);

module_put(THIS_MODULE);

printk(“pci device close success\n”);

return 0;

}

1

2

3

4

5

6

7

8

9

4.裝置資料讀寫和ioctl

PCI裝置驅動程式可以通過device_fops 結構中的函數device_ioctl( ),向應用程式提供對硬體進行控制的接口。例如,通過它可以從I/O寄存器裡讀取一個資料,并傳送到使用者空間裡。

static int device_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)

{

int retval = 0;

struct device_private *privdata= filp->private_data;

switch (cmd)

{

case CMD1:

device_func(arg);

break;

default:

retval = -EINVAL;

}

return retval;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

5.記憶體映射

static int device_mmap(struct file *filp, struct vm_area_struct *vma)

{

int ret;

struct device_private *private = filp->private_data;

vma->vm_page_prot = PAGE_SHARED;//通路權限

vma->vm_pgoff = virt_to_phys(FCswitch->rx_buf_virts) >> PAGE_SHIFT;//偏移(頁幀号)

ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, (unsigned long)(vma->vm_end-vma->vm_start), vma->vm_page_prot);

if(ret!=0)

return -EAGAIN;

return 0;

}

1

2

3

4

5

6

7

8

9

10

11

對 remap_pfn_range()函數的說明:

remap_pfn_range()函數的原型

int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

1

該函數的功能是建立頁表。其中參數vma是核心根據使用者的請求自己填寫的,而參數addr表示記憶體映射開始處的虛拟位址,是以,該函數為addr~addr+size之間的虛拟位址構造頁表。

另外,pfn(Page Fram Number)是虛拟位址應該映射到的實體位址的頁面号,實際上就是實體位址右移PAGE_SHIFT位。如果PAGE_SHIFT為4kb,則 PAGE_SHIFT為12,因為PAGE_SHIFT等于1 << PAGE_SHIFT 。

最後一個參數prot是新頁所要求的保護屬性。

在驅動程式中,一般能使用remap_pfn_range()映射記憶體中的保留頁(如X86系統中的640KB~1MB區域)和裝置I/O記憶體。是以,如 果想把kmalloc()申請的記憶體映射到使用者空間,則可以通過SetPageReserved把相應的記憶體設定為保留後就可以。

八、附錄

1.PCI裝置私有資料結構

struct device_private

{

/次裝置号/

unsigned int minor;

/注冊字元驅動和發現PCI裝置的時候使用/

struct pci_dev *pci_dev;

struct cdev *cdev;

struct class cdev_class;

/中斷号/

unsigned int irq;

/ 用于擷取PCI裝置配置空間的基本資訊 */

unsigned long iobase;

/用于儲存配置設定給PCI裝置的記憶體空間的資訊/

dma_addr_t dma_addrp;

char virts_addr;

/基本的同步手段/

spinlock_t lock;

/等待隊列/

wait_queue_head_t read_queue;

/tasklet/

struct tasklet_struct my_tasklet;

/異步/

struct fasync_struct async_queue;

/裝置打開标記/

int open_flag /

/ …/

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

2.PCI配置寄存器

所有的PCI裝置都有至少256位元組的位址空間,前64位元組是标準化的,而其餘的是裝置相關的。圖1顯示了裝置無關的配置空間的布局。

在Linux系統上,可以通過cat /proc/pci 等指令檢視系統中所有PCI裝置的類别、型号以及廠商等等資訊,那就是從這些寄存器來的。下面是用lspci -x指令截取的部分資訊(lspci指令也是使用/proc檔案作為其資訊來源)(PCI寄存器是小端位元組序格式的):

00:00.0 Host bridge: Intel Corp. 440BX/ZX/DX - 82443BX/ZX/DX Host bridge (rev 01)

00: 86 80 90 71 06 00 00 02 01 00 00 06 00 00 00 00

10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

20: 00 00 00 00 00 00 00 00 00 00 00 00 ad 15 76 19

30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

1

2

3

4

5

那麼根據下面的PCI配置寄存器組的結構,這個Host bridge的Vendor ID就是0x8086。

圖1.标準化的PCI配置寄存器

參考資料:

  1. LINUX裝置驅動程式(第三版)
  2. Linux下PCI裝置驅動程式開發

    http://www.ibm.com/developerworks/cn/linux/l-pci/index.html

  3. Linux PCI 裝置驅動基本架構(一)

    http://www.cnblogs.com/zhuyp1015/archive/2012/06/30/2571400.html

  4. Linux PCI 裝置驅動基本架構(二)

    http://www.cnblogs.com/zhuyp1015/archive/2012/06/30/2571408.html

  5. 淺談Linux PCI裝置驅動(一)

    http://blog.csdn.net/linuxdrivers/article/details/5849698

    ————————————————

    版權聲明:本文為CSDN部落客「迷路麋鹿」的原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結及本聲明。

    原文連結:https://blog.csdn.net/cjecho/article/details/54934264

繼續閱讀