驅動工程師最關心就是如何編寫PCI設備驅動了.
經過前面的處理,所有設備及其信息都已經遍歷出來了.在深入分析PCI驅動架構之前,我們來回顧一下前面遍歷PCI設備時,對pci_dev->dev的一些重要成員的賦值.以及各結構在sysfs中的視圖
8.1:pci架構在sysfs中視圖
1:對於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));
……
由此可見.對於所有的pci_dev.它的所屬bus全為pci_bus_type.它的name部份由四個部份組成:所屬的總線域(根據代碼看,好像總線域一直都是0.不知道是x86平臺是樣子,還是我忽略掉了很多東西?),總線號,設備號和功能號.它的parent為bus->bridge.
既然看一下bus->bridge的賦值:
對於根總線:
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的賦值情況.對於第一根根總線.對於根總線的name:由pci+兩部份組成.分別是總線域和根總線號.
對於下層總線:
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對應於它的pci-pci bridge.
我們到/sys下面看看,來論證我們的分析:
[root@localhost /]# cd /sys/device
[root@localhost devices]# ls
isa LNXSYSTM:00 pci0000:00 platform pnp0 pnp1 system
可以看到,在/sys/device下有一個名為pci0000:00的結點.根據上面的分析,這就是第一條根總線.另外,也看可以看到,我的機器上只有一條根總線.
[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開頭的,對應了根總線下的所有設備.其它的文件是在pci_bus_add_device()生成的.我們暫且不要管它們.
再來看一看,下面有沒有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
……
……
由於輸出較長,我把後面的省略掉了.
用tree命令看到,在下層目錄中,並沒有對應0000開頭的文件.(為什麽是0000開頭呢?因為同一根總線下的所有設備.根總線域是相同的^_^).那說明,下層總線沒有任何的設備.我們在些還不能夠確有沒有下層總線.因為可能有這樣的情況:有下層總線,但下層總線上沒有掛上任何設備.
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的.它的父結點為b->bridge. b->bridge我們在上面已經分析過了.它的名稱為總線域+總線號.
對於下級總線,初始化如下所示:
……
child->parent = parent;
……
child->dev.class = &pcibus_class;
sprintf(child->dev.bus_id, "%04x:%02x", pci_domain_nr(child), busnr);
…….
我們可以看到,它的parent為它的上層總線,屬於pcibus_class類.另外,名稱為總線域+總線號
在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
可以看出.有兩個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
綜合上面的分析,我們看到得出這樣的結論:
測試的PC上只有一根根總線,根總線下面有一個次總線.次總線下面沒有設備.
8.2:pci驅動架構初始化:
Pci驅動架構的初始化如下所示:
static int __init pci_driver_init(void)
{
return bus_register(&pci_bus_type);
}
postcore_initcall(pci_driver_init);
即初始化了一個名為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,
};
上面的幾個接口,我們等用到的時候再進行分析.
註冊一個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的結構如下所示:
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中封裝了一個device_driver結構.pci_driver的註冊過程,就是初始化pci_driver封裝的device-driver.然後將其註冊的過程. Struct pci_driver這個結構成員的含義,在下面的分析中遇到的時候再進行分析.
我們註意到,在註冊pci_driver的過程,將其封裝的device_driver->bus賦值為pci_bus_type. 回憶一下,在遍歷pci 設備的時候,也是將其bus設置為pci_bus-type.根據我們之前對設備模型的分析.註冊pci_driver會對所有掛在pci_bus_type的pci_dev產生一次probe事件.首先,它會調用 pci_bus_type->match()來匹配驅動對齊的設備.對應的接口如下所示:
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.根據它們的位置差,就可以進行struct device. Struct device-driver到pci_dev,pci_driver的轉換.
然後調用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);
}
根據代碼中的註釋,可以得到,在匹配的過程中,會優先匹配drv-dynids的數據.如果匹配不同功,再去匹配drv->id-table.
struct pci_dynid定義如下所示:
struct pci_dynid {
struct list_head node;
struct pci_device_id id;
};
其中,node用來形成鏈表.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 */
};
看到這個結構裏面的成員是不是覺得很熟悉?沒錯,pci驅動就是根據這些信息來匹配設備的.
具體的匹配過程是由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全部匹配才能認為該驅動和設備是匹配的.特別的,對於PCI_ANY_ID,表示任何該類型的設備.例如,如果id->vendor == PCI_ANY_ID,表示匹配任何廠商的設備.
如果匹配成功,就會調用所屬bus的probe函數.相應接口如下所示:
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;
}
同理,還是先轉換到封裝的結構.然後調用__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被成功的驅動,就會就pci_dev->driver指向它所屬的驅動.在這裏,第一次匹配設備的時候,設備是沒有被驅動的.然後,再次確認pci_dev和pci_driver匹配之後,轉入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函數中.
另外,我們可以看到,pci_bus_type的很多函數都是這樣處理的.
再來關心一下hotplug事件.每註冊pci_dev 的時候都會調用pci_bus_type的uevent函數.代碼如下:
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;
}
從此可以看出.它會將class, vendor, device放到hotplug的環境變量中.我們從sysfs中驗證一下.
根據我們前面的分析.bus/device/event這個文件會將bus添加的環境變量列出來.進一個pci devcie的目錄:
Cd /sys/bus/pci/ devices
上面顯示了註冊到pci_bus_type上的設備.
然後:
[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]#
這樣就驗證了我們剛才所說的.
到此為至.對pci架構分析已經清楚了.再來看一下,在pci驅動中經常所用到的接口函數.
8.3:pci驅動程序常用接口分析:
Linux內核開發人員建議PCI驅動程序,在使用設備的時候pci_enable_device().在關閉的時候pci_disable_device ().
pci_enable_device()這個函數其實我們在分析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;
//如果enable_cnt 已經大於1.則說明它已經被啟用了
if (atomic_add_return(1, &dev->enable_cnt) > 1)
return 0; /* already enabled */
//對需要設置的資源項都在bars中置位
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++)
if (dev->resource[i].flags & flags)
bars |= (1 << i);
//啟用該設備
err = do_pci_enable_device(dev, bars);
//如果分配失敗.遞減enable_cnt
if (err < 0)
atomic_dec(&dev->enable_cnt);
return err;
}
對照添加在代碼中的註釋.應該不難理解.跟進do_pci_enable_device():
static int do_pci_enable_device(struct pci_dev *dev, int bars)
{
int err;
//更改設備的電源狀態.可能設備之前處於suspended狀態
err = pci_set_power_state(dev, PCI_D0);
if (err < 0 && err != -EIO)
return err;
//啟用設備
err = pcibios_enable_device(dev, bars);
if (err < 0)
return err;
//一些平臺的修正函數
pci_fixup_device(pci_fixup_enable, dev);
return 0;
}
pcibios_enable_device()函數代碼如下所示:
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;
}
這個函數會啟用設備的相關存儲區.然後調用pcibios_enable_irq()啟用設備的中斷.這個過程包括到PIR中取得中斷號,並開啟中斷功能.
PCI學習部份小結:
Pci是一個龐大的工程,由於PCI總線結構的特性,代碼中大量采用了深度優先遍歷算法.完全理解這部份代碼首先需要對PCI的體系結構要一個較為深刻的認識.其實這部份的重點和難點是在對PCI設備的遍歷上.其它後續的步驟都是在遍時生成樹的基礎上完成的.
這個復雜的一個結構,展再在驅動工程師面前卻非常簡單.只需要按部就班的就pci_driver中的接口完成就可以了.
全站熱搜
留言列表