1. 程式人生 > >PCI驅動基礎 >> Linux裝置驅動程式

PCI驅動基礎 >> Linux裝置驅動程式

俗話說的好,免費是最貴,閒暇是最累的,但是我自己選的路就要負責走完;
壓力一天比一天重,當學習了理論卻不知道該如何輸出的時候,會有一種油然而生的挫敗感;
看來必須得調整自己的心態還是要調整學習方法,如何才能用最好狀態去接受新的知識;

文章目錄

[0x100]概念與特徵

[0x110] PCI介面特徵

概述 描述
更高傳輸效能 由於擁有 66MHz~133MHz 的時鐘頻率傳輸速率、32/64位記憶體地址空間
替代ISA裝置 用於計算機與外設通訊的介面
平臺無關性 介面板自動探測、無需初始化配置,多功能中斷線明確不衝突
多裝置支援 基於PCI域,最多支援256域 每個域支援32 個裝置
存在配置空間 通過普通I/O 定址訪問配置空間, 每個PCI裝置擁有256K大小配置空間

黑了一張書上的圖圖,描述了 PCI到底處於匯流排架構什麼位置這裡黑了一張書上的圖圖,描述 PCI到底處於匯流排架構什麼位置,以及怎麼連線到CPU的

[0x120] PCI介面定址

  1. 8位匯流排編號 + 8 位裝置/功能編號
  2. 8位匯流排編號 + 8 位裝置編號 + 4位功能編號 [圖示]
  3. 16位PCI域號 + 8 位匯流排編號 + 5 位裝置編號 + 3位功能編號
  4. 匯流排共享空間 :PCI記憶體對映、PCI I/O埠
  5. 匯流排私有空間 :配置暫存器
    在這裡插入圖片描述

[0x200] PCI裝置相關資訊

[0x210]配置空間

  • PCI 裝置引導階段只響應配置事務,因為配置空間沒有初始化成功時,不知道存在哪些裝置;
  • 所有裝置中斷為禁用狀態;

在這裡插入圖片描述前64位元組的配置空間

  • PCI 配置暫存器的位元組序是小端位元組序,如果使用大端的系統時,要注意訪問問題;
  • PCI 共有256k的配置空間,前64位標準的暫存器空間,後面空間為外設卡修改
  • 16 位廠商ID [0x1 0x2]+ 16位裝置ID[0x3 0x4]==裝置識別符號;
  • 24 位類代號 高8位為所屬主類;
  • 16 位子系統廠商ID + 16位子系統裝置ID == 子系統識別符號,用於裝置PCI裝置具體型號區別;
#include <linux/mod_devicetable.h>
/*驅動程式支援裝置標識列表,通常是以結構體陣列的形式出現,結束需要顯式指定"{}"*/
struct pci_device_id {
        __u32 vendor, device;           /* PCI裝置識別符號 Vendor and device ID or PCI_ANY_ID*/
        __u32 subvendor, subdevice;     /* PCI裝置型號識別符號 Subsystem ID's or PCI_ANY_ID */
        __u32 class, class_mask;        /* PCI裝置所屬裝置組類 (class,subclass,prog-if) triplet */
        kernel_ulong_t driver_data;     /* PCI驅動私有資料*/
};
/*初始化 僅配置vendor和device,子系統型號被設定成了 PCI_ANY_ID*/
#define PCI_DEVICE(vend,dev) \
        .vendor = (vend), .device = (dev), \
        .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID

/*設定PCI 裝置主類標識,以及類掩碼*/
#define PCI_DEVICE_CLASS(dev_class,dev_class_mask) \
        .class = (dev_class), .class_mask = (dev_class_mask), \
        .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \
        .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID

/*struct pci_device_id 資訊匯出到使用者空間,type=pci name= 【struct pci_device_id的陣列名稱】*/
#define MODULE_DEVICE_TABLE(type,name)  MODULE_GENERIC_TABLE(type##_device,name)
#if defined(MODULE) || !defined(CONFIG_SYSFS)
#define MODULE_VERSION(_version) MODULE_INFO(version, _version)
#else
#define MODULE_VERSION(_version)                                        \
        static struct module_version_attribute ___modver_attr = {       \
                .mattr  = {                                             \
                        .attr   = {                                     \
                                .name   = "version",                    \
                                .mode   = S_IRUGO,                      \
                        },                                              \
                        .show   = __modver_version_show,                \
                },                                                      \
                .module_name    = KBUILD_MODNAME,                       \
                .version        = _version,                             \
        };                                                              \
        static const struct module_version_attribute                    \
        __used __attribute__ ((__section__ ("__modver")))               \
        * __moduleparam_const __modver_attr = &___modver_attr
#endif       

[0x211]訪問配置空間介面

  • 配置空間用於查詢裝置對映位置與IO空間的對映位置;
#include <linux/pci.h> 
struct pci_bus {
        struct list_head node;          /* 匯流排節點連結串列*/
        struct pci_bus  *parent;        /* parent bus this bridge is on */
        struct list_head children;      /* 子匯流排列表*/
        struct list_head devices;       /* 匯流排裝置列表*/
        struct pci_dev  *self;          /* bridge device as seen by parent */
        struct list_head slots;         /* 匯流排槽列表 */
        struct resource *resource[PCI_BRIDGE_RESOURCE_NUM];  /*獲取配置空間 BASE_ADDR bar*/
        struct list_head resources;    

        struct pci_ops  *ops;           /* 配置訪問控制函式集*/
        void            *sysdata;       /* hook for sys-specific extension */
        struct proc_dir_entry *procdir; /* 程序目錄pci匯流排目錄 */

        unsigned char   number;         /* 匯流排編號 */
        unsigned char   primary;        /* 主橋編號 */
        unsigned char   secondary;      /* 副橋編號*/
        unsigned char   subordinate;    /* 次級匯流排最大數量 */
        unsigned char   max_bus_speed;  /* 最大匯流排速率 */
        unsigned char   cur_bus_speed;  /* 當前匯流排速率 */

        char            name[48];       /*匯流排名稱*/

        unsigned short  bridge_ctl;     /* manage NO_ISA/FBB/et al behaviors */
        pci_bus_flags_t bus_flags;      /* 子匯流排繼承 */
        struct device           *bridge;
        struct device           dev;
        struct bin_attribute    *legacy_io; /* IO埠屬性*/
        struct bin_attribute    *legacy_mem; /* IO記憶體屬性*/
        unsigned int            is_added:1;
};
/*如果不能使用 struct pci_dev 任何時候都可以直接呼叫裡面的函式
 *實際使用的 struct pci_bus * 和 unsigned int devfn,來自於pci_dev 中,
 */
static inline int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val)
{
        return pci_bus_read_config_byte(dev->bus, dev->devfn, where, val);
}
static inline int pci_read_config_word(const struct pci_dev *dev, int where, u16 *val)
{
        return pci_bus_read_config_word(dev->bus, dev->devfn, where, val);
}
static inline int pci_read_config_dword(const struct pci_dev *dev, int where,u32 *val)
{
        return pci_bus_read_config_dword(dev->bus, dev->devfn, where, val);
}

static inline int pci_write_config_byte(const struct pci_dev *dev, int where, u8 val)
{
        return pci_bus_write_config_byte(dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_word(const struct pci_dev *dev, int where, u16 val)
{
        return pci_bus_write_config_word(dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_dword(const struct pci_dev *dev, int where,u32 val)
{
        return pci_bus_write_config_dword(dev->bus, dev->devfn, where, val);
}

[0x212]獲取PCI裝置IO空間位置

#include <linux/pci.h>
/*pci_dev 獲取配置空間中 BASE_ADDR bar 的第一個可用地址,返回地址unsigned long*/
#define pci_resource_start(dev, bar)    ((dev)->resource[(bar)].start)
/*pci_dev 獲取配置空間中 BASE_ADDR bar 的最後一個可用地址,返回地址unsigned long*/
#define pci_resource_end(dev, bar)      ((dev)->resource[(bar)].end)

[0x213]中斷偵測

  1. PCI_INTERRUPT_LINE : 中斷線 , 位於配置空間 0x3c 的位置;
  2. PCI_INTERRUPT_PIN : 中斷偵測位,位於配置空間 0x3e的位置;
  3. 只需要通過IO空間讀取函式從中斷線中讀取中斷號,然後註冊IRQ就好了;

[0x220]註冊PCI驅動程式流程

[0x221] 填充結構pci_driver

#include <linux/pci.h>
/*1.定義並填充PCI裝置資訊結構,*/
struct pci_driver {
        /*核心實現連結串列*/
        struct list_head node;
        /*[必選引數]裝置名稱 顯示在 /proc/bus/pci/device 目錄下*/
        const char *name; 
        /*[必選引數]硬體PCI裝置接入功能列表 陣列首地址指定*/                          
        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); 
        /*[可選函式]當指向的pci_dev裝置,被掛起時執行 */
        int  (*suspend) (struct pci_dev *dev, pm_message_t state);
        int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
         /*恢復掛起的PCI裝置 */
        int  (*resume) (struct pci_dev *dev);
        int  (*resume_early) (struct pci_dev *dev);
        /*關機時安全的解除安裝裝置*/
        void (*shutdown) (struct pci_dev *dev);
        struct pci_error_handlers *err_handler;
        struct device_driver    driver;
        struct pci_dynids dynids;
};

[0x221] 註冊pci驅動程式

/*註冊PCI裝置到核心中*/
#define pci_register_driver(driver)             \
        __pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
/*核心函式 */
int __pci_register_driver(struct pci_driver *drv, struct module *owner,const char *mod_name)
{
        int error;

        /* initialize common driver fields */
        drv->driver.name = drv->name;
        drv->driver.bus = &pci_bus_type;
        drv->driver.owner = owner;
        drv->driver.mod_name = mod_name;
        
        spin_lock_init(&drv->dynids.lock);
        INIT_LIST_HEAD(&drv->dynids.list);

        /* register with core */
        error = driver_register(&drv->driver);
        if (error)
                goto out;
        /*動態分配PCI ID,寫入檔案為 new_id*/
        error = pci_create_newid_files(drv);
        if (error)
                goto out_newid;
out:              
        return error;

out_newid:
        driver_unregister(&drv->driver);
        goto out;
}        

Func : 將註冊PCI裝置到核心中
args1: 驅動程式資訊,於第一步填充完成;
args2: 模組所有者,常見的THIS_MODULE;
args3: 模組名稱,只能用這個“KBUILD_MODNAME”;
retval: 成功 0 失敗錯誤碼

[0x222] 登出pci驅動程式

void
pci_unregister_driver(struct pci_driver *drv)
{
        pci_remove_newid_files(drv);
        driver_unregister(&drv->driver);
        pci_free_dynids(drv);
}

[0x222] 啟用pci驅動程式

int pci_enable_device(struct pci_dev *dev)
{
        return __pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}
/*核心函式*/
static int __pci_enable_device_flags(struct pci_dev *dev,resource_size_t flags)
{
        int err;
        int i, bars = 0;
        if (dev->pm_cap) {
                u16 pmcsr;
                pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
                dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
        }

        if (atomic_add_return(1, &dev->enable_cnt) > 1)
                return 0;               /* already enabled */

        /* only skip sriov related */
        for (i = 0; i <= PCI_ROM_RESOURCE; i++)
                if (dev->resource[i].flags & flags)
                        bars |= (1 << i);
        for (i = PCI_BRIDGE_RESOURCES; i < DEVICE_COUNT_RESOURCE; i++)
                if (dev->resource[i].flags & flags)
                        bars |= (1 << i);

        err = do_pci_enable_device(dev, bars);
        if (err < 0)
                atomic_dec(&dev->enable_cnt);
        return err;
}