Pci設備驅動0:設備枚舉


 

有了設備模型基礎及usb設備驅動的基礎知識,來了解PCI設備驅動,就相對簡單了,因為PCI設備驅動仍然套用了設備驅動模型的方式,用到的仍然是設備模型的相應函數,只是把相應的pci設備掛載到PCI總線的device隊列,而憑此驅動則掛載到pci總線的driver隊列,對應的匹配函數,探測函數,都是pci總線提供的函數。

 

因為pci設備驅動的安裝跟usb設備驅動的安裝模式相似,因此,其繁雜的地方則是如何發現設備並把設備添加到pci設備隊列中去這個步驟了。

 

 

網上有一篇文章很好的介紹了PCI設備的枚舉過程:

(原文:http://www.diybl.com/course/6_system/linux/Linuxjs/2008827/137983.html)

 

------------------------------------------

本文系本站原創,歡迎轉載!

轉載請注明出處:http://ericxiao.cublog.cn/

------------------------------------------

 一:前言

Pci,是Peripheral Component Interconnect的縮寫,翻譯成中文即為外部設備互聯.與傳統的總線相比.它的傳輸速率較高.能為用戶提供動態查詢pci deivce.和局部總線信息的方法,此外,它還能自動為總線提供仲裁.在近幾年的發展過程中,被廣泛應用於多種平台.

pci協議比較復雜,關於它的詳細說明,請查閱有關pci規范的資料,本文不會重復這些部份.

對於驅動工程師來說,Pci設備的枚舉是pci設備驅動編寫最復雜的操作.分析和理解這部份,是進行深入分析pci設備驅動架構的基礎.

我們也順便來研究一下,linux是怎么對這個龐然大物進行封裝的.

二:pci架構概貌

上圖展現了pci驅動架構中,pci_bus.pci_dev之間的關系.

如上圖所示:所有的根總線都鏈接在pci_root_buses鏈表中. Pci_bus ->device鏈表鏈接着該總線下的所有設備.而pci_bus->children鏈表鏈接着它的下層總線.

對於pci_dev來說,pci_dev->bus指向它所屬的pci_bus. Pci_dev->bus_list鏈接在它所屬bus的device鏈表上.此外,所有pci設備都鏈接在pci_device鏈表中.

三:pci設備的配置空間

每個pci設備都有最多256個連續的配置空間.配置空間中包含了設備的廠商ID,設備ID,IRQ,設備存儲區信息等.摘下LDD3中的一副說明圖,如下:

要注意了,上圖是以字節為單位的,而不是以位為單位.

那怎么去讀取每個設備的配置空間呢?我們在開篇的時候提到過,pci總線為用戶提供了動態查詢pci設備信息的方法.

在x86上,保留了0xCF8~0xCFF的8個寄存器.實際上就是對應地址為0xCF8的32位寄存器和地址為0xCFC的32位寄存器.

在0xCF8寄存中寫入要訪問設備對應的總線號, 設備號、功能號和寄存器號組成的一個32位數寫入0xCF8.然后從0xCFC上就可以取出對應pci設備的信息.

寫入到0xCF8寄存器的格式如下:

低八位(0~7):            (寄存器地址)&0xFC.低二位為零

8~10:功能位.            有時候,一個pci設備對應多個功能.將每個功能單元分離出來,對應一個獨立的pci device

11~15位:設備號        對應該pci總線上的設備序號

16~23位:總線號        根總線的總線號為0.每遍歷到下層總線,總線號+1

31:有效位                    如果該位為1.則說明寫入的數據有效,否則無效

例如:要讀取n總線號m設備號f功能號對應設備的vendor id和Device id.過程如下:

要寫入到0xCF8中的數為: l = 0x80<<23 | n<<16 | m<<11 | f<<8 | 0x00

即:outl(l,0xCF8)

從0xCFC中讀相關信息:

L = Inw(0xCFC)  (從上圖中看到,vendor id和device id總共占四個字節.因此用inw)

所以:device id = L&0xFF

         Vendor id = (L>>8)&0xFF

 

四:總線枚舉入口分析

Pci的代碼分為兩個部份.一個部份是與平台相關的部份.存放在linux-2.6.25\arch\XXX\pci.在x86,對應為linux-2.6.25\arch\x86\pci\  另一個部份是平台無關的代碼,存放在linux-2.6.25\driver\pci\下面.

大致瀏覽一下這兩個地方的init函數.發現可能枚舉pci設備是由函數pcibios_scan_root()完成的.不過搜索源代碼后,發現有兩個地方會調用這個調數.一個是在linux-2.6.25\arch\x86\pci\numa.c 另一個是linux-2.6.25\arch\x86\pci\Legacy.c

這兩個地方都是封裝在一個subsys_initcall()所引用的初始化函數呢? 到底哪一個文件才是我們要分析的呢?

分析一下linux-2.6.25\arch\x86\pci\下的Makefile_32.內容如下:

obj-y                             := i386.o init.o

 

obj-$(CONFIG_PCI_BIOS)                 += pcbios.o

obj-$(CONFIG_PCI_MMCONFIG)     += mmconfig_32.o direct.o mmconfig-shared.o

obj-$(CONFIG_PCI_DIRECT)  += direct.o

 

pci-y                             := fixup.o

pci-$(CONFIG_ACPI)                  += acpi.o

pci-y                             += legacy.o irq.o

 

pci-$(CONFIG_X86_VISWS)              := visws.o fixup.o

pci-$(CONFIG_X86_NUMAQ)            := numa.o irq.o

 

obj-y                             += $(pci-y) common.o early.o

從這個makefile中可以看出:legacy.c是一定會編譯到了.而numa.c只有在編譯選擇了CONFIG_X86_NUMAQ的時候才起效.所以,我們可以毫不猶豫的將眼光放到了legacy.c中.

該文件中的初始化函數如下:

static int __init pci_legacy_init(void)

{

         if (!raw_pci_ops) {

                   printk("PCI: System does not support PCI\n");

                   return 0;

         }

 

         if (pcibios_scanned++)

                   return 0;

 

         printk("PCI: Probing PCI hardware\n");

         pci_root_bus = pcibios_scan_root(0);

         if (pci_root_bus)

                   pci_bus_add_devices(pci_root_bus);

 

         pcibios_fixup_peer_bridges();

 

         return 0;

}

 

subsys_initcall(pci_legacy_init);

由subsys_initcall()引用的函數都會放在init區域,這里面的函數是kernel啟動的時候會自己執行的函數.首先我們碰到的問題是raw_pci_ops是在什么地方被賦值的.搜索整個代碼樹,發現是在pci_access_init()中初始化的.如下:

static __init int pci_access_init(void)

{

         int type __maybe_unused = 0;

 

#ifdef CONFIG_PCI_DIRECT

         type = pci_direct_probe();

#endif

#ifdef CONFIG_PCI_MMCONFIG

         pci_mmcfg_init(type);

#endif

         if (raw_pci_ops)

                   return 0;

#ifdef CONFIG_PCI_BIOS

         pci_pcbios_init();

#endif

         /*

          * don't check for raw_pci_ops here because we want pcbios as last

          * fallback, yet it's needed to run first to set pcibios_last_bus

          * in case legacy PCI probing is used. otherwise detecting peer busses

          * fails.

          */

#ifdef CONFIG_PCI_DIRECT

         pci_direct_init(type);

#endif

         if (!raw_pci_ops)

                   printk(KERN_ERR

                   "PCI: Fatal: No config space access function found\n");

 

         return 0;

}

arch_initcall(pci_access_init);

由於arch_initcall()的優先級比subsys_initcall要高.因此,會先運行完pci_access_init之后,才會執行pci_legacy_init.

 

上面的代碼看起來很復雜,沒關系,去掉幾個我們沒有用到的編譯代碼就簡單了. 在x86中,bios其實提供了pci設備的枚舉功能.這也是CONFIG_PCI_BIOS的作用,如果對它進行了定義,那么就用bios的pci枚舉功能.如果沒有定義,說明不采用bios的功能,而是自己手動去枚舉,這就是CONFIG_PCI_DIRECT的作用.為了一般性,我們分析CONFIG_PCI_DIRECT的過程.把其它不相關的代碼略掉.剩余的就簡單了.

 

在pci規范中,定義了兩種操作配置空間的方法,即type1 和type2.在新的設計中,type2的配置機制不會被采用,通常會使用type1.因此,在代碼中pci_direct_probe()一般會返回1,即使用type1.

pci_direct_init()的代碼如下:

void __init pci_direct_init(int type)

{

         if (type == 0)

                   return;

         printk(KERN_INFO "PCI: Using configuration type %d\n", type);

         if (type == 1)

                   raw_pci_ops = &pci_direct_conf1;

         else

                   raw_pci_ops = &pci_direct_conf2;

}

在這里看到,ram_pci_ops最終會指向pci_direct_conf1.順便看下這個結構:

struct pci_raw_ops pci_direct_conf1 = {

         .read =               pci_conf1_read,

         .write =      pci_conf1_write,

};

這個結構其實就是pci設備配置空間操作的接口.

 

五:pci設備的枚舉過程

返回到pci_legacy_init()中

static int __init pci_legacy_init(void)

{

         ……

         printk("PCI: Probing PCI hardware\n");

         pci_root_bus = pcibios_scan_root(0);

         if (pci_root_bus)

                   pci_bus_add_devices(pci_root_bus);

         ……

}

Pci設備的枚舉過程是由pcibios_scan_root()完成的.在這里調用是以0為參數.說明是從根總線起開始枚舉.

 

pcibios_scan_root()代碼如下:

struct pci_bus * __devinit pcibios_scan_root(int busnum)

{

         struct pci_bus *bus = NULL;

         struct pci_sysdata *sd;

 

         dmi_check_system(pciprobe_dmi_table);

         while ((bus = pci_find_next_bus(bus)) != NULL) {

                   if (bus->number == busnum) {

                            /* Already scanned */

                            return bus;

                   }

         }

 

         /* Allocate per-root-bus (not per bus) arch-specific data.

          * TODO: leak; this memory is never freed.

          * It's arguable whether it's worth the trouble to care.

          */

         sd = kzalloc(sizeof(*sd), GFP_KERNEL);

         if (!sd) {

                   printk(KERN_ERR "PCI: OOM, not probing PCI bus %02x\n", busnum);

                   return NULL;

         }

 

         printk(KERN_DEBUG "PCI: Probing PCI hardware (bus %02x)\n", busnum);

 

         return pci_scan_bus_parented(NULL, busnum, &pci_root_ops, sd);

}

 

 

先在pci_root_buses中判斷是否存在這個根總線對應的總線號.如果存在,說明這條總線已經遍歷過了,直接退出.

Pci_root_ops這是定義的pci設備配置空間的操作.在沒有選擇CONFIG_PCI_MMCONFIG的情況下,它的操作都會轉入我們在上面的分析的,ram_pci_ops中.這個過程非常簡單,可以自行分析.

然后,流程轉入pci_scan_bus_parented().代碼如下:

 

struct pci_bus * __devinit pci_scan_bus_parented(struct device *parent,

                   int bus, struct pci_ops *ops, void *sysdata)

{

         struct pci_bus *b;

 

         b = pci_create_bus(parent, bus, ops, sysdata);

         if (b)

                   b->subordinate = pci_scan_child_bus(b);

         return b;

}

在pci_create_bus()中,為對應總線號構建pci_bus,然后將其掛入到pci_root_buses鏈表.該函數代碼比較簡單,請自行分析.然后,調用然后pci_scan_child_bus枚舉該總線下的所有設備.pci_bus->subordinate表示下流總線的最大總線號.pci_sacn_child_bus()代碼如下:

 

 

unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)

{

         unsigned int devfn, pass, max = bus->secondary;

         struct pci_dev *dev;

 

         pr_debug("PCI: Scanning bus %04x:%02x\n", pci_domain_nr(bus), bus->number);

 

         /* Go find them, Rover! */

         //按功能號掃描設備號對應的pci 設備

         for (devfn = 0; devfn < 0x100; devfn += 8)

                   pci_scan_slot(bus, devfn);

 

         /*

          * After performing arch-dependent fixup of the bus, look behind

          * all PCI-to-PCI bridges on this bus.

          */

         pr_debug("PCI: Fixups for bus %04x:%02x\n", pci_domain_nr(bus), bus->number);

         pcibios_fixup_bus(bus);

         for (pass=0; pass < 2; pass++)

                   list_for_each_entry(dev, &bus->devices, bus_list) {

                            if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||

                                dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)

                                     max = pci_scan_bridge(bus, dev, max, pass);

                   }

 

         /*

          * We've scanned the bus and so we know all about what's on

          * the other side of any bridges that may be on this bus plus

          * any devices.

          *

          * Return how far we've got finding sub-buses.

          */

         pr_debug("PCI: Bus scan for %04x:%02x returning with max=%02x\n",

                   pci_domain_nr(bus), bus->number, max);

         return max;

}

這節的難點就是在這個地方了,從我們之前分析的pci設備配置空間的讀寫方式可得知.對特定總線.下面最多個32個設備號.每個設備號又對應8 個功能號.我們可以將設備號和功能號放到一起,即占8~15位.在這面的代碼中.對每個設備號調用pci_scan_slot()去掃描它下面的8個功能號對應的設備.總而言之,把該總線下面的所有設備都要枚舉完.

 

pci_scan_slot()代碼如下:

nt pci_scan_slot(struct pci_bus *bus, int devfn)

{

         int func, nr = 0;

         int scan_all_fns;

 

         scan_all_fns = pcibios_scan_all_fns(bus, devfn);

 

         for (func = 0; func < 8; func++, devfn++) {

                   struct pci_dev *dev;

 

                   dev = pci_scan_single_device(bus, devfn);

                   if (dev) {

                            nr++;

 

                            /*

                            * If this is a single function device,

                            * don't scan past the first function.

                            */

                            if (!dev->multifunction) {

                                     if (func > 0) {

                                               dev->multifunction = 1;

                                     } else {

                                              break;

                                     }

                            }

                   } else {

                            if (func == 0 && !scan_all_fns)

                                     break;

                   }

         }

         return nr;

}

對其它的每個設備都會調用pci_scan_single_device().如果是單功能設備(dev->multifunction == 0).則只要判斷它的第一個功能號可以了,不需要判斷之后功能號對應的設備.

Pci_scan_single_device()代碼如下:

struct pci_dev *__ref pci_scan_single_device(struct pci_bus *bus, int devfn)

{

         struct pci_dev *dev;

 

         dev = pci_scan_device(bus, devfn);

         if (!dev)

                   return NULL;

 

         //將pci_dev加至pci_bus->devices

         pci_device_add(dev, bus);

 

         return dev;

}

對每個設備,都會調用pci_scan_device()執行掃描的過程,如果該設備存在,就會將該設備加入到所屬總線的devices鏈表上.這是在pci_device_add()函數中完成的,這個函數比較簡單.這里不做詳細分析.我們把注意力集中到pci_scan_device(),這函數有點長,分段分析如下:

 

static struct pci_dev * __devinit

pci_scan_device(struct pci_bus *bus, int devfn)

{

         struct pci_dev *dev;

         u32 l;

         u8 hdr_type;

         int delay = 1;

 

         if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))

                   return NULL;

 

         /* some broken boards return 0 or ~0 if a slot is empty: */

         if (l == 0xffffffff || l == 0x00000000 ||

             l == 0x0000ffff || l == 0xffff0000)

                   return NULL;

 

         /* Configuration request Retry Status */

         while (l == 0xffff0001) {

                   msleep(delay);

                   delay *= 2;

                   if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))

                            return NULL;

                   /* Card hasn't responded in 60 seconds?  Must be stuck. */

                   if (delay > 60 * 1000) {

                            printk(KERN_WARNING "Device %04x:%02x:%02x.%d not "

                                               "responding\n", pci_domain_nr(bus),

                                               bus->number, PCI_SLOT(devfn),

                                               PCI_FUNC(devfn));

                            return NULL;

                   }

         }

從配置空間中讀取該設備對應的vendor id和device id.如果讀出來的值,有一個是空的,則說明該功能號對應的設備不存在,或者是配置非法.

如果讀出來的是0xffff0001.則需要重新讀一次,如果重讀次數過多,也會退出

 

         if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))

                   return NULL;

 

         dev = alloc_pci_dev();

         if (!dev)

                   return NULL;

 

         dev->bus = bus;

         dev->sysdata = bus->sysdata;

         dev->dev.parent = bus->bridge;

         dev->dev.bus = &pci_bus_type;

         dev->devfn = devfn;

         dev->hdr_type = hdr_type & 0x7f;

         dev->multifunction = !!(hdr_type & 0x80);

         dev->vendor = l & 0xffff;

         dev->device = (l >> 16) & 0xffff;

         dev->cfg_size = pci_cfg_space_size(dev);

         dev->error_state = pci_channel_io_normal;

         set_pcie_port_type(dev);

 

         /* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)

            set this higher, assuming the system even supports it.  */

         dev->dma_mask = 0xffffffff;

接着,將不同類型設備的共同頭部配置讀出來,然后賦值給pci_dev的相應成員.這里有個特別要值得注意的地方: dev->dev.bus = &pci_bus_type.即將pci_dev里面封裝的device結構的bus設置為了pci_bus_type.這個是很核心的一個步驟.我們先將它放到這里,之后的再來詳細分析

特別的, HEADER_TYPE的最高位為0,表示該設備是一個單功能設備

 

         if (pci_setup_device(dev) < 0) {

                   kfree(dev);

                   return NULL;

         }

 

         return dev;

}

最后,流程就會轉入到pci_setup_deivce()對特定類型的設備配置都行讀取操作了.代碼如下:

static int pci_setup_device(struct pci_dev * dev)

{

         u32 class;

 

         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_read_config_dword(dev, PCI_CLASS_REVISION, &class);

         dev->revision = class & 0xff;

         class >>= 8;                                      /* upper 3 bytes */

         dev->class = class;

         class >>= 8;

 

         pr_debug("PCI: Found %s [%04x/%04x] %06x %02x\n", pci_name(dev),

                    dev->vendor, dev->device, class, dev->hdr_type);

 

         /* "Unknown power state" */

         dev->current_state = PCI_UNKNOWN;

 

         /* Early fixups, before probing the BARs */

         pci_fixup_device(pci_fixup_early, dev);

         class = dev->class >> 8;

 

         switch (dev->hdr_type) {                 /* header type */

         case PCI_HEADER_TYPE_NORMAL:                    /* standard header */

                   if (class == PCI_CLASS_BRIDGE_PCI)

                            goto bad;

                   pci_read_irq(dev);

                   pci_read_bases(dev, 6, PCI_ROM_ADDRESS);

                   pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);

                   pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);

 

                   /*

                    *      Do the ugly legacy mode stuff here rather than broken chip

                    *      quirk code. Legacy mode ATA controllers have fixed

                    *      addresses. These are not always echoed in BAR0-3, and

                    *      BAR0-3 in a few cases contain junk!

                    */

                   if (class == PCI_CLASS_STORAGE_IDE) {

                            u8 progif;

                            pci_read_config_byte(dev, PCI_CLASS_PROG, &progif);

                            if ((progif & 1) == 0) {

                                     dev->resource[0].start = 0x1F0;

                                     dev->resource[0].end = 0x1F7;

                                     dev->resource[0].flags = LEGACY_IO_RESOURCE;

                                     dev->resource[1].start = 0x3F6;

                                     dev->resource[1].end = 0x3F6;

                                     dev->resource[1].flags = LEGACY_IO_RESOURCE;

                            }

                            if ((progif & 4) == 0) {

                                     dev->resource[2].start = 0x170;

                                     dev->resource[2].end = 0x177;

                                     dev->resource[2].flags = LEGACY_IO_RESOURCE;

                                     dev->resource[3].start = 0x376;

                                     dev->resource[3].end = 0x376;

                                     dev->resource[3].flags = LEGACY_IO_RESOURCE;

                            }

                   }

                   break;

 

         case PCI_HEADER_TYPE_BRIDGE:                     /* bridge header */

                   if (class != PCI_CLASS_BRIDGE_PCI)

                            goto bad;

                   /* The PCI-to-PCI bridge spec requires that subtractive

                      decoding (i.e. transparent) bridge must have programming

                      interface code of 0x01. */

                   pci_read_irq(dev);

                   dev->transparent = ((dev->class & 0xff) == 1);

                   pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);

                   break;

 

         case PCI_HEADER_TYPE_CARDBUS:                 /* CardBus bridge header */

                   if (class != PCI_CLASS_BRIDGE_CARDBUS)

                            goto bad;

                   pci_read_irq(dev);

                   pci_read_bases(dev, 1, 0);

                   pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);

                   pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);

                   break;

 

         default:                                     /* unknown header */

                   printk(KERN_ERR "PCI: device %s has unknown header type %02x, ignoring.\n",

                            pci_name(dev), dev->hdr_type);

                   return -1;

 

         bad:

                   printk(KERN_ERR "PCI: %s: class %x doesn't match header type %02x. Ignoring class.\n",

                          pci_name(dev), class, dev->hdr_type);

                   dev->class = PCI_CLASS_NOT_DEFINED;

         }

 

         /* We found a fine healthy device, go go go... */

         return 0;

}

總共有三種類型的設備,分別為常規設備(PCI_HEADER_TYPE_NORMAL) ,pci-pci橋設備(PCI_HEADER_TYPE_BRIDGE),筆記本電腦上使用的cardbus(PCI_HEADER_TYPE_CARDBUS).這里的操作不外乎是IRQ的確定,設備存儲區間映射等.先將這幾個操作分析如下:

1: IRQ號的確定

該操作接口為pci_read_irq():

static void pci_read_irq(struct pci_dev *dev)

{

         unsigned char irq;

 

         pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq);

         dev->pin = irq;

         if (irq)

                   pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq);

         dev->irq = irq;

}

在PCI_INTERRUPT_PIN中存放的是將INTA~INTD的哪一個引腳連接到了中斷控制器,如果該值為零.說明並末將引腳連接至中斷控制器.自然也就不能產生中斷信號.

其實,在PCI_INTERRUPT_LINE存放的是該設備的中斷線連接在中斷控制器的哪一個IRQ線上.也就是對應設備的IRQ.

注意這里的寄存器只讀有意義,並不是更改寄存器的值就更改該設備的IRQ

 

2:內部存儲區間的確定

從之前的pci設備配置寄存器圖中可以看到.有從0x10~0x27的6個base address寄存器.里面存放的就是內部存儲器的地起地址和長度,及其類型.

首先將對應寄存器的值取出.如果最低位為1.則說明該區域是I/O端口,高29位是端口地址的高29位,低3位為零.否則是存儲映射區間.前28位是存儲區的高28位,低四位為零.

然后,將該寄存器全部置1.再讀,取得的是長度信息. 如果是I/O端口,屏弊其低三位,如果是存儲區間,屏弊其低四位.最后取第1個位為1對應的大小,即為相應區間的長度.

例如,取出來的值是0xC107.假設是I/O端口

屏蔽掉低三位,為0xC100.第一個為1的值對應的值為0x0100.即0x100

另上,ROM的操作也跟此類似.

在上面的代碼中,內部存儲區間的確定是由pci_read_bases()完成的.這個函數代碼比較長.涉及到的東西又不多,因此不做詳細分析.結構上面的分析,應該很容易看懂代碼了.

 

從上面的代碼可以看出,對於常規設備,有6個存儲區間和一個ROM。Pci briage只有2個存儲區間和一個ROM。Cardbus只有一個存儲區間沒有ROM。

好了,再這里,每一類設備的信息都已經完全讀取出來了,並存放在pci_dev的相關字段。此后在驅動中就可以直接找到pci_dev.取得相應的信息,而不需要再次去枚舉了.

 

再這里,萬里長征只是邁出了一小步。我們知道,pci總線可以通過pci bridge再連一層pci總線。這個問題顯然是一個遞歸過程。我們接下來看pci橋的處理。

返回到pci_scan_child_bus()中。我們將下面要分析的代碼列出來:

unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)

{

         ……

         ……

         for (devfn = 0; devfn < 0x100; devfn += 8)

                   pci_scan_slot(bus, devfn);

         pcibios_fixup_bus(bus);

         for (pass=0; pass < 2; pass++)

                   list_for_each_entry(dev, &bus->devices, bus_list) {

                            if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||

                                dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)

                                     max = pci_scan_bridge(bus, dev, max, pass);

                   }

         ……

}

Pcibios_fixup_bus()這個函數看名字是用來修正總線的。芯片廠商在發布產品后,又檢測到上次發布的產品有問題。回廠升級是不可能的了。只能提供軟件修改的手段,發布一些修正包。Linux將很多廠商的修改正集合在一起。這也就是pcibios_fixup_bus()要進行的操作。具體設備的修正功能,我們就不再研究了。這個函數里還有一個重要的操作。列出代碼如下:

void __devinit  pcibios_fixup_bus(struct pci_bus *b)

{

         struct pci_dev *dev;

 

         pcibios_fixup_ghosts(b);

         pci_read_bridge_bases(b);

         list_for_each_entry(dev, &b->devices, bus_list)

                   pcibios_fixup_device_resources(dev);

}

我們所在討論的重要的操作就是在pci_read_bridge_bases()中完成的。除了之上分析的配置字段外,其實pci橋還有一個很重要的配置項。即:過濾窗口。

過濾窗口決定了訪問的方向。例如:如果cpu一側要經過pci bridge訪問pci總線,則它的地址必須要落在這個pci橋的過濾窗口內才可以通過。另外,pci bridge下游的pci bus要訪問cpu側。則地址必須要落在過濾窗口外才可以。

此外,pci  bridge還提供了一個命令寄存器來控制“memory access enable“和“I/O access enable”兩個位來控制兩個功能。如果全為0.則兩個方向都會關閉。在pci初始化前,為了防止對cpu側造成干擾, 這兩個功能都關閉的,

Pci bridge有三個這樣的窗口,分別如下:

1:起始地址在PCI_IO_BASE中,長度在PCI_IO_LIMIT中。如果是32位,還要通過PCI_IO_BASE_UPPER16和PCI_IO_LIMIT_UPPER16提供高16位。

2:起始地址在PCI_MEMORY_BASE,長度在PCI_MEMORY_LIMIT中。這個是一個16位的窗口。

3:起始地址在PCI_PREF_MEMORY_BASE,長度在PCI_PREF_MEMORY_LIMIT.默認是32位。如果是64,則需要PCI_PREF_BASE_UPPER32和PCI_PREF_LIMIT_UPPER32提供高32位.

存儲區間在這里看起來有點繁雜。以圖的形式總結如下:

結合上面說的,理解pci_read_bridge_bases()的代碼就不難了。這里不再做詳細分析。

 

現在終於把應該讀的配置讀完了,可以進行下層pci總線的遍歷了。

列出這段代碼:

unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)

{

……

         ……

for (pass=0; pass < 2; pass++)

                   list_for_each_entry(dev, &bus->devices, bus_list) {

                            if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||

                                dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)

                                     max = pci_scan_bridge(bus, dev, max, pass);

                   }

         ……

         ……

}

上面的操作基本上就是遍歷掛在pci_bus->devices上面的設備(是否還記得上面在分析的時候,每枚舉到一個設備都會加入到pci_bus->device呢*^_^*)。如果是pci橋或者是cardbus。就會調用pci_scan_bridge()來遍歷橋下面的設備.

這里讓人疑惑的是,為什么要遍歷二次呢?

這是因為,在x86上,系統啟動的時候,bios會枚舉一次pci設備。所以有些pci bridge是經過bios處理過的。而有些是bios可能沒有枚舉到的。這就需要分兩次處理。一次來處理那里已經由bios處理過的pci bridge.一次是處理全新的pci bridge.這樣做,這樣做是因為,每次枚舉總線后,要為其分配一個總線號,而bios處理后的pci bridge的總線號全部都由bios分配好了,要為新的pci bridge分配總線號。而必須要處理完舊的pci bridge才會知道可用的總線號是多少。

 

跟進pci_sacn_bridge()的代碼,這段代碼較長,分段分析如下:

 

int __devinit pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, int pass)

{

         struct pci_bus *child;

         int is_cardbus = (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS);

         u32 buses, i, j = 0;

         u16 bctl;

 

         pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses);

 

         pr_debug("PCI: Scanning behind PCI bridge %s, config %06x, pass %d\n",

                    pci_name(dev), buses & 0xffffff, pass);

 

         /* Disable MasterAbortMode during probing to avoid reporting

            of bus errors (in some architectures) */

         pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &bctl);

         pci_write_config_word(dev, PCI_BRIDGE_CONTROL,

                                  bctl & ~PCI_BRIDGE_CTL_MASTER_ABORT);

         if ((buses & 0xffff00) && !pcibios_assign_all_busses() && !is_cardbus) {

                   unsigned int cmax, busnr;

                   /*

                    * Bus already configured by firmware, process it in the first

                    * pass and just note the configuration.

                    */

                   if (pass)

                            goto out;

                   //次總線號,也就是下游pci的總線號

                   busnr = (buses >> 8) & 0xFF;

 

                   /*

                    * If we already got to this bus through a different bridge,

                    * ignore it.  This can happen with the i450NX chipset.

                    */

                    //如果該總線號已經在我們的遍歷樹中了,說明這條總線已經是處理過的

                   if (pci_find_bus(pci_domain_nr(bus), busnr)) {

                            printk(KERN_INFO "PCI: Bus %04x:%02x already known\n",

                                               pci_domain_nr(bus), busnr);

                            goto out;

                   }

 

                   //構造一個pci_bus.並將其鏈入到父總線的children鏈表上

                   child = pci_add_new_bus(bus, dev, busnr);

                   if (!child)

                            goto out;

                   child->primary = buses & 0xFF;

                   child->subordinate = (buses >> 16) & 0xFF;

                   child->bridge_ctl = bctl;

 

                   //遞歸遍歷子總線.返回的是下層最大的總線號

                   cmax = pci_scan_child_bus(child);

                   if (cmax > max)

                            max = cmax;

                   if (child->subordinate > max)

                            max = child->subordinate;

         } else {

                   /*

                    * We need to assign a number to this bus which we always

                    * do in the second pass.

                    */

                    //在第一次遍歷的時候。是不會處理新的pci bus的

                   if (!pass) {

                            if (pcibios_assign_all_busses())

                                     /* Temporarily disable forwarding of the

                                        configuration cycles on all bridges in

                                        this bus segment to avoid possible

                                        conflicts in the second pass between two

                                        bridges programmed with overlapping

                                        bus ranges. */

                                     pci_write_config_dword(dev, PCI_PRIMARY_BUS,

                                                               buses & ~0xffffff);

                            goto out;

                   }

 

                   /* Clear errors */

                  //往狀態寄存器全寫1

                   pci_write_config_word(dev, PCI_STATUS, 0xffff);

 

                   /* Prevent assigning a bus number that already exists.

                    * This can happen when a bridge is hot-plugged */

                    //要處理的總線編號是否已經存在了

                   if (pci_find_bus(pci_domain_nr(bus), max+1))

                            goto out;

                   child = pci_add_new_bus(bus, dev, ++max);

                   buses = (buses & 0xff000000)

                         | ((unsigned int)(child->primary)     <<  0)

                         | ((unsigned int)(child->secondary)   <<  8)

                         | ((unsigned int)(child->subordinate) << 16);

 

                   /*

                    * yenta.c forces a secondary latency timer of 176.

                    * Copy that behaviour here.

                    */

                   if (is_cardbus) {

                            buses &= ~0xff000000;

                            buses |= CARDBUS_LATENCY_TIMER << 24;

                   }

                           

                  

                  pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses);

 

                   if (!is_cardbus) {

                            child->bridge_ctl = bctl;

                           

                            pci_fixup_parent_subordinate_busnr(child, max);

                            /* Now we can scan all subordinate buses... */

                            max = pci_scan_child_bus(child);

                            pci_fixup_parent_subordinate_busnr(child, max);

                   } else {

                                     for (i=0; i<CARDBUS_RESERVE_BUSNR; i++) {

                                     struct pci_bus *parent = bus;

                                     if (pci_find_bus(pci_domain_nr(bus),

                                                                 max+i+1))

                                               break;

                                     while (parent->parent) {

                                               if ((!pcibios_assign_all_busses()) &&

                                                   (parent->subordinate > max) &&

                                                   (parent->subordinate <= max+i)) {

                                                        j = 1;

                                               }

                                               parent = parent->parent;

                                     }

                                     if (j) {

                                               i /= 2;

                                               break;

                                     }

                            }

                            max += i;

                            pci_fixup_parent_subordinate_busnr(child, max);

                   }

                   child->subordinate = max;

                   pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, max);

         }

 

         sprintf(child->name, (is_cardbus ? "PCI CardBus #%02x" : "PCI Bus #%02x"), child->number);

 

         /* Has only triggered on CardBus, fixup is in yenta_socket */

         while (bus->parent) {

                   if ((child->subordinate > bus->subordinate) ||

                       (child->number > bus->subordinate) ||

                       (child->number < bus->number) ||

                       (child->subordinate < bus->number)) {

                            pr_debug("PCI: Bus #%02x (-#%02x) is %s "

                                     "hidden behind%s bridge #%02x (-#%02x)\n",

                                     child->number, child->subordinate,

                                     (bus->number > child->subordinate &&

                                      bus->subordinate < child->number) ?

                                               "wholly" : "partially",

                                     bus->self->transparent ? " transparent" : "",

                                     bus->number, bus->subordinate);

                   }

                   bus = bus->parent;

         }

 

out:

         pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);

 

         return max;

}

忽略有關cardbus總線相關的部份。PCI_PRIMARY_BUS寄存器中的值的含義為:從低位到高位分別為:主總線號,次總線號,子層最大線號。各占兩位。如果從PCI_PRIMARY_BUS取出來的值,次總線號和子層最大線號有意義。說明該pci-bridge是被bios處理過的。 這里為什么沒有判斷主總線號的值呢?這是因為總主線號可能為零,如根總線。而次總線是每枚舉到一個pci-bridge就會增1。

如果該pci bridge是被bios處理過的,那直接構造一個pci_bus(pci_add_new_bus()).再遞歸枚舉這個pci_bus下的設備就可以了。

相反,如果該pci-bridge沒有被bios處理過,那就需要我們手動去處理了。這時,為它分配一個可能的總線號。然后將總線號寫入PCI_PRIMARY_BUS寄存器。再構造一個pci_bus.遞歸枚舉其下的設備。

最后的一個while()循環是打印出一些DEBUG信息。不需要理會

 

TODO:在上述代碼紅色標識部份。如果是沒有由bios處理過的pci-bridge.有效設置只是次總線號和下層最大總線號。難道不需要設置主總線號么?

特別注意.在遍其次級總線下層pci時.此時還不知道下層最大總號是多少.所以將pci_bus->subordinate賦值為了0xFF.即其下的所有設備都可以透過這個pci-bridge(參考pci_alloc_child_bus()中的處理).然后等下層的子總線遍歷完了之后,再來確定子總線的最大總線號,將其更新至pci_bus->subordinate.

對於上次主總線號的疑問:其實這個總主線號在訪問下層總線是不會被使用到的,因為在配置的時候,只會比較pci_bridge的次總線號和下層最大總線號.如果總線號落在這個區間中間.將其透傳到下一層總線.否則,忽略這個請求.

遞歸完成之后,pci總線上的所有信息都被找到了。所有pci_bus被存放在pci_root_buses為根的倒立樹中.而總線上對應的pci 設備存放在pci_bus->device鏈表中.

返回到我們開篇時的初始化函數pci_legacy_init().這個函數還剩下一部份.代碼如下:

static int __init pci_legacy_init(void)

{

         if (!raw_pci_ops) {

                   printk("PCI: System does not support PCI\n");

                   return 0;

         }

 

         if (pcibios_scanned++)

                   return 0;

 

         printk("PCI: Probing PCI hardware\n");

         pci_root_bus = pcibios_scan_root(0);

         if (pci_root_bus)

                   pci_bus_add_devices(pci_root_bus);

 

         pcibios_fixup_peer_bridges();

 

         return 0;

}

如果總線遍歷成功,就會轉入pci_bus_add_devices().代碼片段如下:

void pci_bus_add_devices(struct pci_bus *bus)

{

         ……

         ……

pci_bus_add_device(dev)

……

……

}

該函數會遍歷總線上的device鏈表.然后對每個設備調用pci_bus_add_device().代碼如下:

int pci_bus_add_device(struct pci_dev *dev)

{

         int retval;

         retval = device_add(&dev->dev);

         if (retval)

                   return retval;

 

         down_write(&pci_bus_sem);

         list_add_tail(&dev->global_list, &pci_devices);

         up_write(&pci_bus_sem);

 

         pci_proc_attach_device(dev);

         pci_create_sysfs_dev_files(dev);

         return 0;

}

從上面的代碼可以看出.它先將設備添加,然后再將設備掛載到全局的pci_devices鏈表上.

這樣順着pci_devices就可以找到所有的設備信息了.

另外,對於pci_dev的初始化,我們之前曾強調過.初始化代碼片段如下:

static struct pci_dev * __devinit

pci_scan_device(struct pci_bus *bus, int devfn)

{

         ……

         ……

         dev->dev.parent = bus->bridge;

         dev->dev.bus = &pci_bus_type;

         ……

         ……

}

即該設備是屬於pci_bus_type總線的.事實上,我們編寫的pci驅動程序,也是基於pci_bus_type.這樣,就可以在添加驅動的時候,就可以匹配想要的設備了.

在這里,特別注意一下,經過這里的枚舉,只是枚舉完了第一條根總線.那其它的根總線是在什么地方被枚舉的呢? 在下一節里,再來分析這個問題.

 

六:小結

在linux的pci架構中,大量運用了深度優先遍歷算法.這是由pci總線結構所決定的.經過這一章的分析過后,我們應該對pci架構有了一定的了解.同時在這一章還留下了一個問題.到下一節再來進行分析.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM