linux设备驱动之pci设备的驱动架构
生活随笔
收集整理的這篇文章主要介紹了
linux设备驱动之pci设备的驱动架构
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
驅(qū)動(dòng)工程師最關(guān)心就是如何編寫PCI設(shè)備驅(qū)動(dòng)了.
經(jīng)過(guò)前面的處理,所有設(shè)備及其信息都已經(jīng)遍歷出來(lái)了.在深入分析PCI驅(qū)動(dòng)架構(gòu)之前,我們來(lái)回顧一下前面遍歷PCI設(shè)備時(shí),對(duì)pci_dev->dev的一些重要成員的賦值.以及各結(jié)構(gòu)在sysfs中的視圖
8.1:pci架構(gòu)在sysfs中視圖
1:對(duì)于pci_dev
pci_dev->dev的所屬bus,parent和name的賦值:
在pci_scan_child_bus()?--> ?pci_scan_slot()--> pci_scan_single_device()-->pci_scan_device():
……
dev->dev.parent = bus->bridge;
dev->dev.bus = &pci_bus_type;
……
pci_scan_child_bus()-->pci_scan_slot()-->pci_scan_single_device()-->pci_scan_device()-->pci_setup_device()
……
sprintf(pci_name(dev), "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
……
由此可見.對(duì)于所有的pci_dev.它的所屬bus全為pci_bus_type.它的name部份由四個(gè)部份組成:所屬的總線域(根據(jù)代碼 看,好像總線域一直都是0.不知道是x86平臺(tái)是樣子,還是我忽略掉了很多東西?),總線號(hào),設(shè)備號(hào)和功能號(hào).它的parent為 bus->bridge.
既然看一下bus->bridge的賦值:
對(duì)于根總線:
pci_scan_bus_parented()-->pci_create_bus():
……
memset(dev, 0, sizeof(*dev));
dev->parent = parent;
dev->release = pci_release_bus_bridge_dev;
sprintf(dev->bus_id, "pci%04x:%02x", pci_domain_nr(b), bus);
error = device_register(dev);
if (error)
goto dev_reg_err;
b->bridge = get_device(dev);
……
從上面的代碼片段可以看出.根總線的bridge的賦值情況.對(duì)于第一根根總線.對(duì)于根總線的name:由pci+兩部份組成.分別是總線域和根總線號(hào).
對(duì)于下層總線:
pci_scan_bridge()-->pci_add_new_bus()àpci_alloc_child_bus():
……
child->self = bridge;
child->parent = parent;
child->ops = parent->ops;
child->sysdata = parent->sysdata;
child->bus_flags = parent->bus_flags;
child->bridge = get_device(&bridge->dev);
……
從上面的代碼看到.下層總線的bridge對(duì)應(yīng)于它的pci-pci bridge.
我們到/sys下面看看,來(lái)論證我們的分析:
[root@localhost /]# cd /sys/device
[root@localhost devices]# ls
isa? LNXSYSTM:00? pci0000:00? platform? pnp0? pnp1? system
可以看到,在/sys/device下有一個(gè)名為pci0000:00的結(jié)點(diǎn).根據(jù)上面的分析,這就是第一條根總線.另外,也看可以看到,我的機(jī)器上只有一條根總線.
[root@localhost devices]# cd pci0000\:00/
[root@localhost pci0000:00]# ls
0000:00:00.0? 0000:00:07.0? 0000:00:07.2? 0000:00:0f.0? 0000:00:11.0? pci_bus:0000:00? uevent
0000:00:01.0? 0000:00:07.1? 0000:00:07.3? 0000:00:10.0? 0000:00:12.0? power
下面以0000開頭的,對(duì)應(yīng)了根總線下的所有設(shè)備.其它的文件是在pci_bus_add_device()生成的.我們暫且不要管它們.
再來(lái)看一看,下面有沒有pci-pci bridge.
[root@localhost pci0000:00]# tree
.
|-- 0000:00:00.0
|?? |-- broken_parity_status
|?? |-- bus -> ../../../bus/pci
|?? |-- class
|?? |-- config
|?? |-- device
|?? |-- driver -> ../../../bus/pci/drivers/agpgart-intel
|?? |-- enable
|?? |-- irq
|?? |-- local_cpus
|?? |-- modalias
|?? |-- msi_bus
|?? |-- power
|?? |?? `-- wakeup
|?? |-- resource
|?? |-- resource0
|?? |-- subsystem -> ../../../bus/pci
|?? |-- subsystem_device
|?? |-- subsystem_vendor
|?? |-- uevent
|?? `-- vendor
|-- 0000:00:01.0
|?? |-- broken_parity_status
|?? |-- bus -> ../../../bus/pci
|?? |-- class
|?? |-- config
|?? |-- device
|?? |-- enable
|?? |-- irq
|?? |-- local_cpus
|?? |-- modalias
|?? |-- msi_bus
|?? |-- pci_bus:0000:01 -> ../../../class/pci_bus/0000:01
|?? |-- power
|?? |?? `-- wakeup
|?? |-- resource
|?? |-- subsystem -> ../../../bus/pci
|?? |-- subsystem_device
|?? |-- subsystem_vendor
|?? |-- uevent
|?? `-- vendor
……
……
由于輸出較長(zhǎng),我把后面的省略掉了.
用tree命令看到,在下層目錄中,并沒有對(duì)應(yīng)0000開頭的文件.(為什么是0000開頭呢?因?yàn)橥桓偩€下的所有設(shè)備.根總線域是相同的 ^_^).那說(shuō)明,下層總線沒有任何的設(shè)備.我們?cè)谛┻€不能夠確有沒有下層總線.因?yàn)榭赡苡羞@樣的情況:有下層總線,但下層總線上沒有掛上任何設(shè)備.
2:pci_bus.
pci_bus在sysfs的存放和pci_dev是不相同的.我們跟蹤代碼看一下:
根總線的pci_bus初始化如下所示:
pci_create_bus():
……
b->dev.class = &pcibus_class;
b->dev.parent = b->bridge;
sprintf(b->dev.bus_id, "%04x:%02x", pci_domain_nr(b), bus);
……
我們可以看到,pci_bus是屬于pcibus_class的.它的父結(jié)點(diǎn)為b->bridge. b->bridge我們?cè)谏厦嬉呀?jīng)分析過(guò)了.它的名稱為總線域+總線號(hào).
對(duì)于下級(jí)總線,初始化如下所示:
……
child->parent = parent;
……
child->dev.class = &pcibus_class;
sprintf(child->dev.bus_id, "%04x:%02x", pci_domain_nr(child), busnr);
…….
我們可以看到,它的parent為它的上層總線,屬于pcibus_class類.另外,名稱為總線域+總線號(hào)
在sysfs中論證一下我們剛才的分析:
[root@localhost linux-2.6.25]# cd /sys/class/pci_bus/
[root@localhost pci_bus]# ll
total 0
drwxr-xr-x 3 root root 0 08-21 05:41 0000:00
drwxr-xr-x 3 root root 0 08-21 05:41 0000:01
可以看出.有兩個(gè)pci 總線.用tree命令看下:
|-- 0000:00
|?? |-- cpuaffinity
|?? |-- device -> ../../../devices/pci0000:00
|?? |-- power
|?? |?? `-- wakeup
|?? |-- subsystem -> ../../pci_bus
|?? `-- uevent
`-- 0000:01
|-- cpuaffinity
|-- device -> ../../../devices/pci0000:00/0000:00:01.0
|-- power
|?? `-- wakeup
|-- subsystem -> ../../pci_bus
`-- uevent
從device的指向可以看出. 0000:00的parent為/sys/device/pci0000:00.即為第一根根總線
0000:01的parent為/sys/devices/pci0000:00/0000:00:01.0
綜合上面的分析,我們看到得出這樣的結(jié)論:
測(cè)試的PC上只有一根根總線,根總線下面有一個(gè)次總線.次總線下面沒有設(shè)備.
8.2:pci驅(qū)動(dòng)架構(gòu)初始化:
Pci驅(qū)動(dòng)架構(gòu)的初始化如下所示:
static int __init pci_driver_init(void)
{
return bus_register(&pci_bus_type);
}
postcore_initcall(pci_driver_init);
即初始化了一個(gè)名為pci_bus的總線.其初始化如下示:
struct bus_type pci_bus_type = {
.name??????????????? = "pci",
.match??????????????? = pci_bus_match,
.uevent?????????????? = pci_uevent,
.probe??????????????? = pci_device_probe,
.remove???????????? = pci_device_remove,
.suspend = pci_device_suspend,
.suspend_late? = pci_device_suspend_late,
.resume_early? = pci_device_resume_early,
.resume???????????? = pci_device_resume,
.shutdown???????? = pci_device_shutdown,
.dev_attrs = pci_dev_attrs,
};
上面的幾個(gè)接口,我們等用到的時(shí)候再進(jìn)行分析.
注冊(cè)一個(gè)pci driver的接口為:
pci_register_driver():代碼如下所示:
pci_register_driver()à__pci_register_driver():
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)
return error;
error = pci_create_newid_file(drv);
if (error)
driver_unregister(&drv->driver);
return error;
}
Pci_driver的結(jié)構(gòu)如下所示:
struct module;
struct pci_driver {
struct list_head node;
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);
struct pci_error_handlers *err_handler;
struct device_driver? driver;
struct pci_dynids dynids;
};
由此可見:pci_driver中封裝了一個(gè)device_driver結(jié)構(gòu).pci_driver的注冊(cè)過(guò)程,就是初始化 pci_driver封裝的device-driver.然后將其注冊(cè)的過(guò)程. Struct pci_driver這個(gè)結(jié)構(gòu)成員的含義,在下面的分析中遇到的時(shí)候再進(jìn)行分析.
我們注意到,在注冊(cè)pci_driver的過(guò)程,將其封裝的device_driver->bus賦值為pci_bus_type. 回憶一下,在遍歷pci 設(shè)備的時(shí)候,也是將其bus設(shè)置為pci_bus-type.根據(jù)我們之前對(duì)設(shè)備模型的分析.注冊(cè)pci_driver會(huì)對(duì)所有掛在 pci_bus_type的pci_dev產(chǎn)生一次probe事件.首先,它會(huì)調(diào)用 pci_bus_type->match()來(lái)匹配驅(qū)動(dòng)對(duì)齊的設(shè)備.對(duì)應(yīng)的接口如下所示:
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *pci_drv = to_pci_driver(drv);
const struct pci_device_id *found_id;
found_id = pci_match_device(pci_drv, pci_dev);
if (found_id)
return 1;
return 0;
}
Pci_dev中封裝了struct device. Pci_driver中封裝了struct device_driver.根據(jù)它們的位置差,就可以進(jìn)行struct device. Struct device-driver到pci_dev,pci_driver的轉(zhuǎn)換.
然后調(diào)用pci_match_device().代碼如下:
static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
struct pci_dev *dev)
{
struct pci_dynid *dynid;
/* Look at the dynamic ids first, before the static ones */
spin_lock(&drv->dynids.lock);
list_for_each_entry(dynid, &drv->dynids.list, node) {
if (pci_match_one_device(&dynid->id, dev)) {
spin_unlock(&drv->dynids.lock);
return &dynid->id;
}
}
spin_unlock(&drv->dynids.lock);
return pci_match_id(drv->id_table, dev);
}
根據(jù)代碼中的注釋,可以得到,在匹配的過(guò)程中,會(huì)優(yōu)先匹配drv-dynids的數(shù)據(jù).如果匹配不同功,再去匹配drv->id-table.
struct pci_dynid定義如下所示:
struct pci_dynid {
struct list_head node;
struct pci_device_id id;
};
其中,node用來(lái)形成鏈表.struct 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 */
};
看到這個(gè)結(jié)構(gòu)里面的成員是不是覺得很熟悉?沒錯(cuò),pci驅(qū)動(dòng)就是根據(jù)這些信息來(lái)匹配設(shè)備的.
具體的匹配過(guò)程是由pci_match_one_device()完成的.代碼如下示:
static inline const struct pci_device_id *
pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
{
if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
(id->device == PCI_ANY_ID || id->device == dev->device) &&
(id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
(id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
!((id->class ^ dev->class) & id->class_mask))
return id;
return NULL;
}
由此可以看出,必須vendor, device, subvendor, subdevice, class全部匹配才能認(rèn)為該驅(qū)動(dòng)和設(shè)備是匹配的.特別的,對(duì)于PCI_ANY_ID,表示任何該類型的設(shè)備.例如,如果id->vendor == PCI_ANY_ID,表示匹配任何廠商的設(shè)備.
如果匹配成功,就會(huì)調(diào)用所屬bus的probe函數(shù).相應(yīng)接口如下所示:
static int pci_device_probe(struct device * dev)
{
int error = 0;
struct pci_driver *drv;
struct pci_dev *pci_dev;
drv = to_pci_driver(dev->driver);
pci_dev = to_pci_dev(dev);
pci_dev_get(pci_dev);
error = __pci_device_probe(drv, pci_dev);
if (error)
pci_dev_put(pci_dev);
return error;
}
同理,還是先轉(zhuǎn)換到封裝的結(jié)構(gòu).然后調(diào)用__pci_device_probe().代碼如下所示:
static int
__pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)
{
const struct pci_device_id *id;
int error = 0;
if (!pci_dev->driver && drv->probe) {
error = -ENODEV;
id = pci_match_device(drv, pci_dev);
if (id)
error = pci_call_probe(drv, pci_dev, id);
if (error >= 0) {
pci_dev->driver = drv;
error = 0;
}
}
return error;
}
如果pci_dev被成功的驅(qū)動(dòng),就會(huì)就pci_dev->driver指向它所屬的驅(qū)動(dòng).在這里,第一次匹配設(shè)備的時(shí)候,設(shè)備是沒有被驅(qū)動(dòng)的.然后,再次確認(rèn)pci_dev和pci_driver匹配之后,轉(zhuǎn)入pci_call_probe ().代碼如下:
static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
const struct pci_device_id *id)
{
int error;
#ifdef CONFIG_NUMA
/* Execute driver initialization on node where the
device's bus is attached to.? This way the driver likely
allocates its local memory on the right node without
any need to change it. */
struct mempolicy *oldpol;
cpumask_t oldmask = current->cpus_allowed;
int node = pcibus_to_node(dev->bus);
if (node >= 0 && node_online(node))
set_cpus_allowed(current, node_to_cpumask(node));
/* And set default memory allocation policy */
oldpol = current->mempolicy;
current->mempolicy = NULL;??? /* fall back to system default policy */
#endif
error = drv->probe(dev, id);
#ifdef CONFIG_NUMA
set_cpus_allowed(current, oldmask);
current->mempolicy = oldpol;
#endif
return error;
}
上面代碼中,我們忽略一些選擇編譯的東東,然后就非常清楚了.它就是將probe操作回溯到pci_driver的probe函數(shù)中.
另外,我們可以看到,pci_bus_type的很多函數(shù)都是這樣處理的.
再來(lái)關(guān)心一下hotplug事件.每注冊(cè)pci_dev 的時(shí)候都會(huì)調(diào)用pci_bus_type的uevent函數(shù).代碼如下:
int pci_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct pci_dev *pdev;
if (!dev)
return -ENODEV;
pdev = to_pci_dev(dev);
if (!pdev)
return -ENODEV;
if (add_uevent_var(env, "PCI_CLASS=%04X", pdev->class))
return -ENOMEM;
if (add_uevent_var(env, "PCI_ID=%04X:%04X", pdev->vendor, pdev->device))
return -ENOMEM;
if (add_uevent_var(env, "PCI_SUBSYS_ID=%04X:%04X", pdev->subsystem_vendor,
pdev->subsystem_device))
return -ENOMEM;
if (add_uevent_var(env, "PCI_SLOT_NAME=%s", pci_name(pdev)))
return -ENOMEM;
if (add_uevent_var(env, "MODALIAS=pci:v%08Xd%08Xsv%08Xsd%08Xbc%02Xsc%02Xi%02x",
pdev->vendor, pdev->device,
pdev->subsystem_vendor, pdev->subsystem_device,
(u8)(pdev->class >> 16), (u8)(pdev->class >> 8),
(u8)(pdev->class)))
return -ENOMEM;
return 0;
}
從此可以看出.它會(huì)將class, vendor, device放到hotplug的環(huán)境變量中.我們從sysfs中驗(yàn)證一下.
根據(jù)我們前面的分析.bus/device/event這個(gè)文件會(huì)將bus添加的環(huán)境變量列出來(lái).進(jìn)一個(gè)pci devcie的目錄:
Cd /sys/bus/pci/?devices
上面顯示了注冊(cè)到pci_bus_type上的設(shè)備.
然后:
[root@localhost devices]# cat 0000\:00\:00.0/uevent
DRIVER=agpgart-intel
PHYSDEVBUS=pci
PHYSDEVDRIVER=agpgart-intel
PCI_CLASS=60000
PCI_ID=8086:7190
PCI_SUBSYS_ID=15AD:1976
PCI_SLOT_NAME=0000:00:00.0
MODALIAS=pci:v00008086d00007190sv000015ADsd00001976bc06sc00i00
[root@localhost devices]#
這樣就驗(yàn)證了我們剛才所說(shuō)的.
到此為至.對(duì)pci架構(gòu)分析已經(jīng)清楚了.再來(lái)看一下,在pci驅(qū)動(dòng)中經(jīng)常所用到的接口函數(shù).
8.3:pci驅(qū)動(dòng)程序常用接口分析:
Linux內(nèi)核開發(fā)人員建議PCI驅(qū)動(dòng)程序,在使用設(shè)備的時(shí)候pci_enable_device().在關(guān)閉的時(shí)候pci_disable_device ().
pci_enable_device()這個(gè)函數(shù)其實(shí)我們?cè)诜治鰌ci設(shè)備資源分配的時(shí)候就已經(jīng)討論過(guò),只是那時(shí)候沒有給出詳細(xì)的分析.
代碼如下:
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;
//如果enable_cnt 已經(jīng)大于1.則說(shuō)明它已經(jīng)被啟用了
if (atomic_add_return(1, &dev->enable_cnt) > 1)
return 0;???????????? /* already enabled */
//對(duì)需要設(shè)置的資源項(xiàng)都在bars中置位
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++)
if (dev->resource[i].flags & flags)
bars |= (1 << i);
//啟用該設(shè)備
err = do_pci_enable_device(dev, bars);
//如果分配失敗.遞減enable_cnt
if (err < 0)
atomic_dec(&dev->enable_cnt);
return err;
}
對(duì)照添加在代碼中的注釋.應(yīng)該不難理解.跟進(jìn)do_pci_enable_device():
static int do_pci_enable_device(struct pci_dev *dev, int bars)
{
int err;
//更改設(shè)備的電源狀態(tài).可能設(shè)備之前處于suspended狀態(tài)
err = pci_set_power_state(dev, PCI_D0);
if (err < 0 && err != -EIO)
return err;
//啟用設(shè)備
err = pcibios_enable_device(dev, bars);
if (err < 0)
return err;
//一些平臺(tái)的修正函數(shù)
pci_fixup_device(pci_fixup_enable, dev);
return 0;
}
pcibios_enable_device()函數(shù)代碼如下所示:
int pcibios_enable_device(struct pci_dev *dev, int mask)
{
int err;
if ((err = pcibios_enable_resources(dev, mask)) < 0)
return err;
if (!dev->msi_enabled)
return pcibios_enable_irq(dev);
return 0;
}
這個(gè)函數(shù)會(huì)啟用設(shè)備的相關(guān)存儲(chǔ)區(qū).然后調(diào)用pcibios_enable_irq()啟用設(shè)備的中斷.這個(gè)過(guò)程包括到PIR中取得中斷號(hào),并開啟中斷功能.
PCI學(xué)習(xí)部份小結(jié):
Pci是一個(gè)龐大的工程,由于PCI總線結(jié)構(gòu)的特性,代碼中大量采用了深度優(yōu)先遍歷算法.完全理解這部份代碼首先需要對(duì)PCI的體系結(jié)構(gòu)要一個(gè)較為深刻的認(rèn)識(shí).其實(shí)這部份的重點(diǎn)和難點(diǎn)是在對(duì)PCI設(shè)備的遍歷上.其它后續(xù)的步驟都是在遍時(shí)生成樹的基礎(chǔ)上完成的.
這個(gè)復(fù)雜的一個(gè)結(jié)構(gòu),展再在驅(qū)動(dòng)工程師面前卻非常簡(jiǎn)單.只需要按部就班的就pci_driver中的接口完成就可以了.
總結(jié)
以上是生活随笔為你收集整理的linux设备驱动之pci设备的驱动架构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

- 上一篇: 在GridView中针对鼠标单击的某一独
- 下一篇: linux驱动模型开发——linux p