1. 程式人生 > >再識PCI:一個PCI驅動例項

再識PCI:一個PCI驅動例項

之前寫了第一篇關於PCI的文章,當時只是作為入門的接觸筆記,後來對PCI又研究了一下,主要包括PCI裝置的掃描過程及PCI驅動註冊過程。

本文主要給出一個PCI例項,並在核心中做很多的列印以便跟蹤其過程。程式碼參考i2c-i801.c檔案,在intel平臺上試驗。

對於學習,我想像力一向不好,只能靠笨方法來學習。

程式碼如下:

/**
 * @file   pci_driver_test.c
 * @author Late Lee <[email protected]>
 * @date   Fri Apr 24 20:21:19 2015
 * 
 * @brief  PCI驅動學習示例
 * 
 * @note  
 
 log PCI驅動註冊過程:
[63909.555877] [++LL debug: 269 @ foo_init]: MARK...
[63909.555897] [++LL debug: 1268 @ __pci_register_driver]: MARK...
[63909.555901] [++LL debug: 153 @ driver_register]: start MARK...
[63909.555906] [++LL debug: 168 @ driver_register]: MARK add driver to bus...
[63909.555909] bus: 'pci': add driver foo_bus
[63909.555913] [++LL debug: 694 @ bus_add_driver]: MARK init and add driver: foo_bus...
[63909.555920] [++LL debug: 700 @ bus_add_driver]: MARK add tail to klis_drivers...
[63909.555924] [++LL debug: 703 @ bus_add_driver]: MARK...
[63909.555932] [++LL debug: 404 @ driver_probe_device]: bus: 'pci': driver_probe_device: matched device 0000:00:1f.0 with driver foo_bus
[63909.555936] [++LL debug: 287 @ really_probe]: bus: 'pci': really_probe: probing driver foo_bus with device 0000:00:1f.0
[63909.555944] [++LL debug: 305 @ really_probe]: ready probe bus: 0000:00:1f.0
[63909.555949] [++LL debug: 404 @ pci_device_probe]: 0000:00:1f.0 ready to call pci probe.(0x8086: 0xf1c)
[63909.555952] [++LL debug: 163 @ foo_probe]: Got LPC.
[63909.555973] [++LL debug: 212 @ foo_probe]: SPI flash base addr: 0xfed01000 map mem: 0xf8adc000
[63909.555977] [++LL debug: 216 @ foo_probe]: temp: 0x0 WPEN: 0 WPST: 0
[63909.555987] [++LL debug: 319 @ really_probe]: bus: 'pci': really_probe: bound device 0000:00:1f.0 to driver foo_bus
[63909.555992] [++LL debug: 404 @ driver_probe_device]: bus: 'pci': driver_probe_device: matched device 0000:00:1f.3 with driver foo_bus
[63909.555996] [++LL debug: 287 @ really_probe]: bus: 'pci': really_probe: probing driver foo_bus with device 0000:00:1f.3
[63909.556004] [++LL debug: 305 @ really_probe]: ready probe bus: 0000:00:1f.3
[63909.556007] [++LL debug: 404 @ pci_device_probe]: 0000:00:1f.3 ready to call pci probe.(0x8086: 0xf12)
[63909.556010] [++LL debug: 158 @ foo_probe]: Got SMBUS.
[63909.556151] [++LL debug: 200 @ foo_probe]: got SMBUS smba: 0x2000
[63909.556161] [++LL debug: 203 @ foo_probe]: read SMBC: 0x7
[63909.556169] [++LL debug: 319 @ really_probe]: bus: 'pci': really_probe: bound device 0000:00:1f.3 to driver foo_bus
[63909.556182] [++LL debug: 710 @ bus_add_driver]: create file uevent...
[63909.556204] [++LL debug: 178 @ driver_register]: end MARK...

SMBus IO埠和記憶體

[++LL debug: 248 @ foo_probe]: got SMBUS smba: 0x2000
[++LL debug: 251 @ foo_probe]: read SMBC: 0x7
[++LL debug: 255 @ foo_probe]: got SMBUS bar 0(0x10): 0x90705000

# lspci -s 00:1f.3 -xxx
00:1f.3 Class 0c05: Device 8086:0f12 (rev 11)
00: 86 80 12 0f 03 00 90 02 11 00 05 0c 00 00 00 00
10: 00 50 70 90 00 00 00 00 00 00 00 00 00 00 00 00
20: 01 20 00 00 00 00 00 00 00 00 00 00 86 80 70 72
30: 00 00 00 00 50 00 00 00 00 00 00 00 0a 02 00 00
40: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
50: 01 00 03 00 08 00 00 00 00 00 00 00 00 00 00 00
60: 03 04 04 00 00 00 08 08 00 00 00 00 00 00 00 00
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
80: 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
f0: 00 00 00 00 00 00 00 00 1a 0f 11 01 03 01 00 00


bar0為記憶體地址:
cat /proc/iomem 
90705000-9070501f : 0000:00:1f.3


bar4為IO埠地址:
cat /proc/ioports  
2000-201f : 0000:00:1f.3
 */
 
#include <linux/module.h>
#include <linux/kernel.h>       /**< printk() */
#include <linux/init.h>

#include <linux/cdev.h>         /**< cdev_* */
#include <linux/fs.h>
#include <asm/uaccess.h>        /**< copy_*_user */

#include <linux/types.h>        /**< size_t */
#include <linux/errno.h>        /**< error codes */
#include <linux/string.h>

#include <linux/slab.h>
#include <linux/pci.h>          /**< pci... */

#ifndef LL_DEBUG
#define LL_DEBUG
#endif

#ifdef LL_DEBUG
/* KERN_INFO */
#define ll_debug(fmt, ...) printk(KERN_INFO "[++LL debug: %d @ %s]: " fmt, __LINE__, __func__, ##__VA_ARGS__)
#else
#define ll_debug(fmt, ...)
#endif

// #define TEST_CHAR
#ifdef TEST_CHAR
extern int foodrv_probe(void);
extern int foodrv_remove(void);
#endif

// SMBus Port
#define PCI_DEVICE_ID_INTEL_BAYTRAIL_SMBUS  0x0F12
// LPC: Bridge to Intel Legacy Block
#define PCI_DEVICE_ID_INTEL_BAYTRAIL_LPC    0x0F1C
// I211
#define E1000_DEV_ID_I211_COPPER  0x1539

// PCI標準暫存器,共64位元組
#define PCI_STD_HEADER_SIZEOF 64
#define PCI_VENDOR_ID  0x00 /* 16 bits */
#define PCI_DEVICE_ID  0x02 /* 16 bits */
#define PCI_COMMAND  0x04 /* 16 bits */
#define PCI_STATUS  0x06 /* 16 bits */
#define PCI_CLASS_REVISION 0x08 /* High 24 bits are class, low 8 revision */
#define PCI_CACHE_LINE_SIZE 0x0c /* 8 bits */
#define PCI_LATENCY_TIMER 0x0d /* 8 bits */
#define PCI_HEADER_TYPE  0x0e /* 8 bits */
#define PCI_BIST  0x0f /* 8 bits */
/*
 * Base addresses specify locations in memory or I/O space.
 * Decoded size can be determined by writing a value of
 * 0xffffffff to the register, and reading it back.  Only
 * 1 bits are decoded.
 */
#define PCI_BASE_ADDRESS_0 0x10 /* 32 bits */
#define PCI_BASE_ADDRESS_1 0x14 /* 32 bits [htype 0,1 only] */
#define PCI_BASE_ADDRESS_2 0x18 /* 32 bits [htype 0 only] */
#define PCI_BASE_ADDRESS_3 0x1c /* 32 bits */
#define PCI_BASE_ADDRESS_4 0x20 /* 32 bits */
#define PCI_BASE_ADDRESS_5 0x24 /* 32 bits */
#define PCI_CARDBUS_CIS  0x28
#define PCI_SUBSYSTEM_VENDOR_ID 0x2c
#define PCI_SUBSYSTEM_ID 0x2e
#define PCI_ROM_ADDRESS  0x30 /* Bits 31..11 are address, 10..1 reserved */
/* 0x35-0x3b are reserved */
#define PCI_INTERRUPT_LINE 0x3c /* 8 bits */
#define PCI_INTERRUPT_PIN 0x3d /* 8 bits */
#define PCI_MIN_GNT  0x3e /* 8 bits */
#define PCI_MAX_LAT  0x3f /* 8 bits */
// PCI標準暫存器 結束
///////////////////////////////////////////////////

/* SMBus 基地址在bar 4 */
#define SMBBAR        4

/* PCI Address Constants */
#define SMBCOM  0x004
#define SMBBA  0x014
#define SMBATPC  0x05B /* used to unlock xxxBA registers */
#define SMBHSTCFG 0x0E0
#define SMBSLVC  0x0E1
#define SMBCLK  0x0E2
#define SMBREV  0x008
/////////////////////////////////////////////////////////////////////////

// 這裡是SMBUS io訪問的地址
/* SMBus address offsets */
#define SMBHSTSTS(p)    (0 + (p)->smba)
#define SMBHSTCNT(p)    (2 + (p)->smba)
#define SMBHSTCMD(p)    (3 + (p)->smba)
#define SMBHSTADD(p)    (4 + (p)->smba)
#define SMBHSTDAT0(p)   (5 + (p)->smba)
#define SMBHSTDAT1(p)   (6 + (p)->smba)
#define SMBBLKDAT(p)    (7 + (p)->smba)
#define SMBPEC(p)       (8 + (p)->smba)        /* ICH3 and later */
#define SMBAUXSTS(p)    (12 + (p)->smba)    /* ICH4 and later */
#define SMBAUXCTL(p)    (13 + (p)->smba)    /* ICH4 and later */

#define SMBSMBC(p)      (0xF + (p)->smba)

//////////////////////////////////////////////////////////////////////////
// LPC
/* PCI config registers in LPC bridge. LPC橋(這是一個PCI裝置)上的外設偏移地址*/
#define REVID  0x08
#define ABASE  0x40
#define PBASE  0x44
#define GBASE  0x48
#define IOBASE  0x4c
#define IBASE  0x50
#define SBASE  0x54 // SPI的
#define MPBASE  0x58
#define PUBASE  0x5c
#define UART_CONT 0x80
#define RCBA  0xf0

//////////////////////////////////////////////////////////////////////////

struct foo_priv {
    u8 *name;
    int type;
    u32 smba;
    u32 mem;
    struct pci_dev *pci_dev;
    
};

static const struct pci_device_id foo_ids[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_BAYTRAIL_SMBUS) },
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_BAYTRAIL_LPC) },
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_I211_COPPER) }, // 不懂為何,這個認不出
    { 0, }
};

/*
會呼叫2次,由PCI匯流排根據本驅動的id來決定
*/
static int foo_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
    unsigned char temp;
    int err;
    struct foo_priv *priv;

    ll_debug("MARK Start of probe....\n");
    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->pci_dev = dev;
    switch (dev->device) {
    case PCI_DEVICE_ID_INTEL_BAYTRAIL_SMBUS: // 0000:00:1f.3
        ll_debug("Got SMBUS.\n");
        priv->name = "SMBUS";
        priv->type = 0;
        break;
    case PCI_DEVICE_ID_INTEL_BAYTRAIL_LPC: // 0000:00:1f.0
        ll_debug("Got LPC.\n");
        priv->name = "LPC";
        priv->type = 1;
        break;
    case E1000_DEV_ID_I211_COPPER: // 0000:01:00.0
        ll_debug("Got I211.\n");
        priv->name = "I211";
        priv->type = 2;
        break;
    default:
        priv->name = "Unknown";
        break;
    }

    err = pci_enable_device(dev); // 使能PCI裝置
    if (err) {
        dev_err(&dev->dev, "Failed to enable SMBus PCI device (%d)\n",
            err);
        goto exit;
    }

    /* 從SMBus手冊知,偏移量0x20為bar 4,5~15位元為IO基地址
    最低位為只讀,值為1,表示SMB邏輯為IO對映(如手工讀則要將基地址移位)
    但pci_resource_start返回的是正確的地址,可直接使用
    */
    if (priv->type == 0) {
        priv->smba = (u32)pci_resource_start(dev, SMBBAR);  // io bar
        if (!priv->smba) {
            dev_err(&dev->dev, "SMBus base address uninitialized, "
                "upgrade BIOS\n");
            err = -ENODEV;
            goto exit;
        }
#if 0
        err = pci_request_region(dev, SMBBAR, priv->name);
        if (err) {
            dev_err(&dev->dev, "Failed to request SMBus region "
                "0x%x-0x%Lx\n", priv->smba,
                (unsigned long long)pci_resource_end(dev, SMBBAR));
            goto exit;
        }
#endif
        ll_debug("got %s smba: 0x%x\n", priv->name, priv->smba);
        // 這是測試0xf暫存器的值,預設為7
        temp = inb_p(SMBSMBC(priv));
        ll_debug("read SMBC: 0x%x\n", temp);
        
        // 讀bar0
        priv->smba = (u32)pci_resource_start(dev, 0); // memory bar
        ll_debug("got %s bar 0(0x10): 0x%x\n", priv->name, priv->smba);
    }

    // LPC橋的BAR沒有賦值,讀的話全為0
    // 但其上有許多外設的基地址,要根據偏移量來計算,見上面的巨集定義
    if (priv->type == 1)
    {
        pci_read_config_dword(priv->pci_dev, SBASE, &priv->smba);
        priv->smba &= ~0x1FF; // 為何要對齊?
        priv->mem = (u32)ioremap(priv->smba, 4);
        ll_debug("SPI flash base addr: 0x%x map mem: 0x%x\n", priv->smba, priv->mem);
        
        // 讀個暫存器試試
        temp = readl((void*)(priv->mem+0xF8));
        ll_debug("temp: 0x%x WPEN: %d WPST: %d\n", temp, (temp>>7)&0x1, (temp>>6)&0x1);

        #if 0
        int i;
        for (i = 0x10; i < 0x90; i+=4) {
            temp = readl((void*)(priv->mem+i));
            ll_debug("reg[%x] value: 0x%x\n", i, temp);
        }
        #endif
    }
    if (priv->type == 2)
    {
        priv->smba = (u32)pci_iomap(priv->pci_dev, 0, 0);
        ll_debug("I211 base addr: 0x%x\n", priv->smba);
    }

    pci_set_drvdata(dev, priv);

#ifdef TEST_CHAR
    // 這裡可以註冊cdev裝置
    if (priv->type == 0)
        foodrv_probe();
#endif

    return 0;

exit:
    kfree(priv);
    return err;
}

static void foo_remove(struct pci_dev *dev)
{
    struct foo_priv *priv = pci_get_drvdata(dev);

    ll_debug("remove %s\n", priv->name);

    kfree(priv);

#ifdef TEST_CHAR
    if (priv->type == 0)
        foodrv_remove();
#endif
}

#define foo_suspend NULL
#define foo_resume NULL

static struct pci_driver foo_driver = {
    .name        = "foo_bus",
    .id_table    = foo_ids,
    .probe        = foo_probe,
    .remove        = foo_remove,
    //.suspend    = foo_suspend,
    //.resume        = foo_resume,
};

static int __init foo_init(void)
{
    ll_debug("+++++++++++++++++++++++++++Start of Test...\n");
    return pci_register_driver(&foo_driver);
}

static void __exit foo_exit(void)
{
    ll_debug("MARK...\n");
    pci_unregister_driver(&foo_driver);
    
    ll_debug("+++++++++++++++++++++++++++End of Exit...\n");
}

module_init(foo_init);
module_exit(foo_exit);

MODULE_AUTHOR("Jim Kent");
MODULE_DESCRIPTION("SMBus driver");
MODULE_LICENSE("GPL");

李遲 5.6 中午

相關推薦

PCI一個PCI驅動例項

之前寫了第一篇關於PCI的文章,當時只是作為入門的接觸筆記,後來對PCI又研究了一下,主要包括PCI裝置的掃描過程及PCI驅動註冊過程。 本文主要給出一個PCI例項,並在核心中做很多的列印以便跟蹤其過程。程式碼參考i2c-i801.c檔案,在intel平臺上試驗。 對於學習

Linux裝置驅動程式架構分析之一個I2C驅動例項

作者:劉昊昱  核心版本:3.10.1 編寫一個I2C裝置驅動程式的工作可分為兩部分,一是定義和註冊I2C裝置,即i2c_client;二是定義和註冊I2C裝置驅動,即i2c_driver。下面我們就以mini2440的I2C裝置at24c08 EEPROM為例,介紹如

開源純C#工控網關+組態軟件(三)加入一個驅動西門子S7

space 流量 php cls clsid hub pro 第一個 問題 一、 引子 首先感謝博客園:第一篇文章、第一個開源項目,算是旗開得勝。可以看到,項目大部分流量來自於博客園,碼農樂園,名不虛傳^^。 園友給了我很多支持,並提出了很好的改進意見。現加入屏幕分辨率自適

補碼(為什麽按位取反加一)告訴你一個其實很簡單的問題

滿足 所有 我們 進位 數字 樂意 如果 二進制 關系   首先,閱讀這篇文章的你,肯定是一個在網上已經糾結了很久的讀者,因為你查閱了所有你能查到的資料,然後他們都會很耐心的告訴你,補碼:就是按位取反,然後加一。準確無誤,毫無破綻。但是,你搜遍了所有俯拾即是而且準確無誤的答

Python經典練習題1一個整數,它加上100後是一個完全平方數,加上168又是一個完全平方數,請問該數是多少?

span range pytho 能夠 break clas 完全平方數 imp 經典 Python經典練習題 網上能夠搜得到的答案為: for i in range(1,85): if 168 % i == 0: j = 168 / i;

R in Action學習筆記一個簡單的資料處理例項

這是來自《R in Action》中的一個數據處理例項。 資料:一組學生的名字和其對應的數學、科學、英語的成績; 資料分析需求: 1、為所有學生確定一個單一的成績衡量指標; 2、將前20%的學生評定為A,接下來20%的學生評定為B,依次類推; 3、按照學生姓氏的字母順序對學生排序。

socket例項C語言一個簡單的聊天程式

我們老師讓寫一個簡單的聊天軟體,並且實現不同機子之間的通訊,我用的是SOCKET程式設計。不廢話多說了,先附上程式碼: 伺服器端server.c #include <stdio.h> #include <stdlib.h> #include

安卓APP實戰(一)需求介紹及安卓工程結構

在搭建好開發環境,大致瞭解了Android的相關基礎知識,新建好第一個專案併成功執行後。便開始做第一個專案!需求如下: APP應用名稱叫歡樂寫數字 (Android專案開發實戰入門 明日科技出版 中第一個專案,本書紙質內容只有八個專案,並且屬於傻瓜式教程,似乎有基礎知識介紹在光盤裡,我沒有找

linux驅動由淺入深系列輸入子系統之二(編寫一個gpio_key驅動)

本系列導航: 在上一篇文章中我們大致瞭解了linux input subsystem的功能及應用層的使用,本文我們一起來看一看驅動程式碼的編寫。接下來一篇,計劃寫一下應用層如何模擬按鍵訊息,產生與按下實際按鍵相同的效果。 在“linux驅動由淺入深系列:驅動程式的基

菜鳥WDF驅動開發系列(2)除錯第一個KMDF驅動程式

根據系列上一篇的內容,已經基本作好了驅動除錯環境的配置,現在著手開始試一下怎麼用WinDBG除錯。每一位新手在開始學習驅動開發的時候相信總會看大量的資料,如我第一篇提到的幾本書的確是不錯的,但名著總有一個問題就是,開篇一開始總是會鋪陳太多的基本理論,這會讓我們覺得手足無措,

web專案Log4j日誌輸出路徑配置問題 問題描述一個web專案想在一個tomcat下執行多個例項(通過修改war包名稱的實現),然後每個例項都將日誌輸出到tomcat的logs目錄下例項名命名的文

問題描述:一個web專案想在一個tomcat下執行多個例項(通過修改war包名稱的實現),然後每個例項都將日誌輸出到tomcat的logs目錄下例項名命名的資料夾下進行區分檢視每個例項日誌,要求通過儘可能少的改動配置檔案,最好修改例項名後可以不修改log4j的配置檔案。 實現分析:一般實現上面需求,需要在修

教你寫Linux裝置驅動程式一個簡短的教程

摘自:http://blog.chinaunix.net/uid-20799298-id-99675.html 原文為 Writing device driver in Linux:A brief tutorial. 該文重點給出了三個例項來講解Linux驅

題目一個整數,它加上100後是一個完全平方數,加上168又是一個完全平方數,請問該數是多少?

題目是copy的csdn論壇裡面的。當時,樓主給出了演算法: public class HisTime { public static void main(String[] args) { long startTime = System.currentTimeMil

libevent一個在網路伺服器中事件驅動開發庫

Libevent 是一個基於事件觸發的網路庫。它提供一種機制,即當某個具體事件發生在一個檔案描述符上或已經達到超時時,來執行某一個回撥函式。此外,它也支援訊號或定時器事件的回撥。 Libevent 是為了取代在事件驅動的網路服務中的事件迴圈。應用程式只需要呼叫event_dispatch() 函式,然後動

PHP面向物件之旅一個介面例項,很好的例子

我們設計一個線上銷售系統,使用者部分設計如下: 將使用者分為,NormalUser, VipUser, InnerUser三種。 要求根據使用者的不同折扣計算使用者購買產品的價格。 並要求為以後擴充套件和維護預留空間。 使用者部分先聲明瞭一個介面User,使用者都是User

SpringMVC實戰教程 | 第一篇一個在JSP頁面輸出“HelloWorld”的Spring MVC例項

配置web.xml 作用:裝載DispatcherServlet類,讀取Spring配置檔案,設定一些初始化引數,加入標記庫,設定比如 * .do 、* .form的對映等。 <servlet> <servlet-nam

第三題一個整數,它加上100後是一個完全平方數,加上168又是一個完全平方數,請問該數是多少?

第三題:一個整數,它加上100後是一個完全平方數,再加上168又是一個完全平方數,請問該數是多少? 設這個整數是X,根據題目:x+100=n*n,x+100+168=n*n+168=m*m 方法一:簡

自定義控制元件四一個簡單的自定義控制元件例項

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入門程式猿來說對於Android自定義View,可能都是比較恐懼的,但是這又是高手進階的必經之路,所有準備在

spring一個簡單例項之對DAO的支援

spring 對 DAO 的支援~~ 1、在 MySQL 中建立 db_spring 資料庫,新建 t_student 表 DROP TABLE IF EXISTS `t_student`; C

利用C#進行Socket通訊程式設計之二一個例項

    利用晚上這點閒暇時間,寫了一個Socket通訊的小例項,該例項包含伺服器端和客戶端。其基本工作流程是:當伺服器啟動服務以後,客戶端進行連線,如果連線成功,則使用者可以在傳送訊息框中輸入待發送的訊