count web page visits
Panasonic Viera 3D

Linux驱动学习--初识PCI驱动(一)

写在前面的话:

感觉好久没有来写了,今天就来看看Linux下PCI驱动的一个基本编写规范。


一,我们来看看PCI是什么。

PCI---Peripheral Component Interconnect,外围设备互联总线。是一种广泛采用的总线标准,它提供了许多优于其它总线标准(如EISA)的新特性,目前已经成为计算机系统中应用最为广泛,并且最为通用的总线标准。

不知道大家对总线的概念是什么,按我的通俗理解,总线就是一条国道,将许多的设备连接起来。当然,这是一种十分宽泛的理解。好,今天我们的重点并不是总线,而是在Linux驱动程序中的编写。

对于驱动的编写来讲,现今的内核已经为我们实现了很多,而我们所需要的做的,就是将一个实际的设备和内核进行连接,就好像是一座桥。

不管如何,我们还是要来看PCI的一点基本知识。

1.与CPU等的交互:

 jiaohu.jpg

从上图,我们可以看出PCI将计算机系统中的总线子系统 与存储子系统完全地分开,CPU通过一块称为PCI桥(PCI-Bridge)的设备来完成同总线子系统的交互。

2.自此,我们应该可以大概有了点印象,PCI总线大概是处于何种位置,当然,上面的图只是一个很简略的图,但是我们只有有一种感性的认知即可。那么,在逻辑上,PCI总线又会在哪里呢?我想这对于我们变成更是重要的吧,请看下图:

 jiben.jpg

上 图是一个典型的基于PCI总线的计算机系统逻辑示意图,系统的各个部分通过PCI总线和PCI-PCI桥连接在一起。从图中不难看出,CPU和RAM需要 通过PCI连接到PCI总线0(即主PCI总线),而具有PCI接口的显卡则可以直接连接到PCI总线上。PCI-PCI桥是一个特殊的PCI设备,他负 责将PCi总线0和PCI总线1连接在一起,通常PCI总线1称为PCI-PCI总线的下游(downstream),而PCI总线0则称为PCI- PCI桥的上游(upstream)。上图中,连接到从PCI总线上的是SCSI卡和以太网卡。为了兼容ISA总线标准,PCI总线还可以通过PCI- ISA桥来连接ISA总线,从而能够支持以前的ISA设备。图中的ISA总线上连接着一个多功能I/O控制器,用于控制键盘,鼠标和软驱。

对于PCI的一些知识,我就先这样的做一个总的概括性介绍。更加具体的,请看The Linux Kernel(http://tldp.org/LDP/tlk/dd/pci.html)中的详细介绍。

二,和PCI驱动程序相关的几个数据结构

驱动程序总是离不开数据结构,在Linux中,用数据结构来表示各色各样的设备或者其他的东西。因此,我们掌握设备驱动程序的关键之一,就是对各种数据结构的理解和运用。

1.pci_device_id

在介绍该结构之前,让我们来看看PCI的地址空间:I/O空间,存储空间,配置空间。

CPU 可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用,内核在 启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以 及这些我设备的参数和属性。

下图是PCI配置寄存器。

 peizhi.png

我们并不需要去了解配置寄存器的所有位代表了什么,有什么含义。我们只要用三个或者五个PCI寄存器去标识一个设备即可。通常,我们会选择下面三个寄存器:

vendorID:标识硬件制造商,是一个16位的寄存器。

deviceID:设备ID,由制造商选择,也是一个16位的寄存器。一般与厂商ID配对生成一个唯一的32位硬件设备标识符。

class:每个外部设备属于某个类(class),也是一个16位的寄存器。当某个驱动程序可支持多个相似的设备,每个具有不同的签名,但都属于同一个类,这时,就可以用class类对它们的外设进行识别。

讲了这么多,那我们应该怎么去设置这些值呢?不用怕,内核已经为我们都想好了,它已经将这些都归纳到一个数据结构进去了,我们要做的就是对这个数据结构进行填充,是不是很方便啊!

这个数据结构就是--pci_device_id。

    struct pci_device_id {
    __u32 vendor, device;/* Vendor and device ID or PCI_ANY_ID*/
    __u32 subvendor, subdevice;/* Subsystem ID's or PCI_ANY_ID */
    __u32 class, class_mask;/* (class,subclass,prog-if) triplet */
    kernel_ulong_t driver_data;/* Data private to the driver */
    };

那现在问题又来了,我们前面说过,一个驱动程序可以匹配一个甚至多个设备。那么,此时我们又该如何呢?可以想到数组,对吧。是的,不过这里有点地方需要注意

    staticstruct pci_device_id example_pci_tbl [] __initdata ={
    {PCI_VENDOR_ID_EXAMPLE, PCI_DEVICE_ID_EXAMPLE, PCI_ANY_ID, PCI_ANY_ID,0,0, EXAMPLE},
    {0,}
    };

注意到了吧,是的,不管你这里匹配了多少设备,记得最后一个都是{0,}。

这里还有两个关于初始化该结构体的宏,可以用来简化相关的操作。

PCI_DEVICE(vendor, device)

        创建一个仅和特定厂商及设备ID相匹配的struct pci_device_id。它把结构体的subvendor和subdevice设为PCI_ANY_ID。PCI_ANY_ID定义如下:

    #define PCI_ANY_ID (~0)

PCI_DEVICE_CLASS(device_class, device_class_mask)

        创建一个和特定PCI类相匹配的struct pci_device_id。

这里不再多说。

2.pci_driver

按照上面说的,你已经将你要匹配的设备说明了,但这仅仅只是说明,内核如何去识别它们呢?那就要用到下面的数据结构了--pci_driver。

struct pci_driver {
    struct list_head node;
    char*name;
    conststruct pci_device_id *id_table;/* must be non-NULL for probe to be called */
    int(*probe)(struct pci_dev *dev,conststruct 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);
    struct pci_error_handlers *err_handler;
    struct device_driver driver;
    struct pci_dynids dynids;
};

从上面的结构体定义可以看出,它的作用并不仅仅是识别设备的id_table结构,还包括了检测设备的 函数probe( )和卸载设备的函数remove( ):这种结构体,我们之前就已经接触过很多了,不再多说。

3.pci_dev

让我们来最后最后一个相关的数据结构--pci_dev。

/*
* The pci_dev structure is used to describe PCI devices. */ struct pci_dev { struct list_head bus_list;/* node in per-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 */ unsignedint devfn;/* encoded device & function index */ unsignedshort vendor; unsignedshort device; unsignedshort subsystem_vendor; unsignedshort subsystem_device; unsignedintclass;/* 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_cap;/* PCI-E capability offset */ 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 */ unsignedint pme_support:5;/* Bitmask of states from which PME# can be generated */ unsignedint pme_interrupt:1; unsignedint d1_support:1;/* Low power state D1 is supported */ unsignedint d2_support:1;/* Low power state D2 is supported */ unsignedint no_d1d2:1;/* Only allow D0 and D3 */ unsignedint mmio_always_on:1;/* disallow turning off io/mem decoding during bar sizing */ unsignedint wakeup_prepared:1; unsignedint d3_delay;/* D3->D0 transition time in ms */ #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! */ unsignedint irq; struct resource resource[DEVICE_COUNT_RESOURCE];/* I/O and memory regions + expansion ROMs */ resource_size_t fw_addr[DEVICE_COUNT_RESOURCE];/* FW-assigned addr */ /* These fields are used by common fixups */ unsignedint transparent:1;/* Transparent PCI bridge */ unsignedint multifunction:1;/* Part of multi-function device */ /* keep track of device state */ unsignedint is_added:1; unsignedint is_busmaster:1;/* device is busmaster */ unsignedint no_msi:1;/* device may not use msi */ unsignedint block_ucfg_access:1;/* userspace config space access is blocked */ unsignedint broken_parity_status:1;/* Device generates false positive parity */ unsignedint irq_reroute_variant:2;/* device needs IRQ rerouting variant */ unsignedint msi_enabled:1; unsignedint msix_enabled:1; unsignedint ari_enabled:1;/* ARI forwarding */ unsignedint is_managed:1; unsignedint is_pcie:1;/* Obsolete. Will be removed. Use pci_is_pcie() instead */ unsignedint needs_freset:1;/* Dev requires fundamental reset */ unsignedint state_saved:1; unsignedint is_physfn:1; unsignedint is_virtfn:1; unsignedint reset_fn:1; unsignedint is_hotplug_bridge:1; unsignedint __aer_firmware_first_valid:1; unsignedint __aer_firmware_first: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 };

由上面的定义可以知道,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等:

三,基本框架

上面将我们要用到的一些基本信息都做了一些简单的介绍。下面,我们就来看看PCI驱动程序的一个基本的框架,如何将这些东西进行整理成一个程序。

 1         staticstruct pci_device_id example_pci_tbl [] __initdata ={
 2          {PCI_VENDOR_ID_EXAMPLE, PCI_DEVICE_ID_EXAMPLE, PCI_ANY_ID, PCI_ANY_ID,0,0, EXAMPLE},
 3          {0,}
 4         };
 5         /* 对特定PCI设备进行描述的数据结构 */
 6         struct example_pci {
 7          unsignedint magic;
 8          /* 使用链表保存所有同类的PCI设备 */
 9          struct example_pci *next;
10         
11          /* ... */
12         }
13         /* 中断处理模块 */
14         staticvoid example_interrupt(int irq,void*dev_id,struct pt_regs *regs)
15         {
16          /* ... */
17         }
18         /* 设备文件操作接口 */
19         staticstruct file_operations example_fops ={
20          owner: THIS_MODULE,/* demo_fops所属的设备模块 */
21          read: example_read,/* 读设备操作*/
22          write: example_write,/* 写设备操作*/
23          ioctl: example_ioctl,/* 控制设备操作*/
24          open: example_open,/* 打开设备操作*/
25          release: example_release /* 释放设备操作*/
26          /* ... */
27         };
28         /* 设备模块信息 */
29         staticstruct pci_driver example_pci_driver ={
30          name: example_MODULE_NAME,/* 设备模块名称 */
31          id_table: example_pci_tbl,/* 能够驱动的设备列表 */
32          probe: example_probe,/* 查找并初始化设备 */
33          remove: example_remove /* 卸载设备模块 */
34          /* ... */
35         };
36         staticint __init example_init_module (void)
37         {
38          /* ... */
39         }
40         staticvoid __exit example_cleanup_module (void)
41         {
42          pci_unregister_driver(&demo_pci_driver);
43         }
44         /* 加载驱动程序模块入口 */
45         module_init( example_init_module);
46         /* 卸载驱动程序模块入口 */
47         module_exit( example_cleanup_module);

上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。

好了,今天就先介绍到这,下次,在对里面的一些函数流程进行分析。

posted @ 2012-12-11 09:30  夜半私时  阅读(19332)  评论(0编辑  收藏  举报