Chinaunix首页 | 论坛 | 博客
  • 博客访问: 598708
  • 博文数量: 50
  • 博客积分: 4764
  • 博客等级: 上校
  • 技术积分: 597
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-18 09:00
个人简介

资深IT码农,擅长Linux、C/C++、bash

文章分类

全部博文(50)

文章存档

2015年(17)

2014年(2)

2011年(7)

2010年(4)

2009年(20)

分类: C/C++

2009-11-16 15:32:30

枚举所有PCI设备

冷胜魁(Seaquester)
lengshengkui@gmail.com
2009-11-16

在Linux下,lspci可以枚举所有PCI设备。它是通过读取PCI配置空间(PCI Configuration Space)信息来实现PCI设备的枚举的。这里,我通过两种方式来简单的模拟一下lspci的功能。一种是通过PCI总线的CF8和CFC端口来枚举(参考PCI总线规范);另一种是利用proc filesystem。

方法一:这种方法需要对端口进行操作,在Linux下,普通应用程序没有权限读写I/O 端口,需要通过iopl或ioperm来提升权限,我的代码里面使用iopl。

/*
 * Enum all pci device via the PCI config register(CF8 and CFC).
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/io.h>

#define PCI_MAX_BUS 255 /* 8 bits (0 ~ 255) */
#define PCI_MAX_DEV 31 /* 5 bits (0 ~ 31) */
#define PCI_MAX_FUN 7 /* 3 bits (0 ~ 7) */

#define CONFIG_ADDRESS 0xCF8
#define CONFIG_DATA 0xCFC

#define PCICFG_REG_VID 0x00 /* Vendor id, 2 bytes */
#define PCICFG_REG_DID 0x02 /* Device id, 2 bytes */
#define PCICFG_REG_CMD 0x04 /* Command register, 2 bytes */
#define PCICFG_REG_STAT 0x06 /* Status register, 2 bytes */
#define PCICFG_REG_RID 0x08 /* Revision id, 1 byte */


void list_pci_devices()
{
    unsigned int bus, dev, fun;
    unsigned int addr, data;

    //printf("BB:DD:FF VID:DID\n");


    for (bus = 0; bus <= PCI_MAX_BUS; bus++) {
        for (dev = 0; dev <= PCI_MAX_DEV; dev++) {
            for (fun = 0; fun <= PCI_MAX_FUN; fun++) {
                addr = 0x80000000L | (bus<<16) | (dev<<11) | (fun<<8);
                outl(addr, CONFIG_ADDRESS);
                data = inl(CONFIG_DATA);

                /* Identify vendor ID */
                if ((data != 0xFFFFFFFF) && (data != 0)) {
                    printf("%02X:%02X:%02X ", bus, dev, fun);
                    printf("%04X:%04X", data&0xFFFF, data>>16);
                    addr = 0x80000000L | (bus<<16) | (dev<<11) | (fun<<8) | PCICFG_REG_RID;
                    outl(addr, CONFIG_ADDRESS);
                    data = inl(CONFIG_DATA);
                    if (data&0xFF) {
                        printf(" (rev %02X)\n", data&0xFF);
                    } else {
                        printf("\n");
                    }
                }
            }
        }
    }
}


int main()
{
    int ret;

    /* Enable r/w permission of all 65536 ports */
    ret = iopl(3);
    if (ret < 0) {
        perror("iopl set error");
        return 1;
    }

    list_pci_devices();

    /* Disable r/w permission of all 65536 ports */
    ret = iopl(0);
    if (ret < 0) {
        perror("iopl set error");
        return 1;
    }

    return 0;
}


方法二:这种方法需不需要对端口进行操作,而是利用Linux procfs来实现对PCI 配置空间的访问。

/*
 * Enum all pci device via /proc/bus/pci/.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#define PCI_MAX_BUS 255 /* 8 bits (0 ~ 255) */
#define PCI_MAX_DEV 31 /* 5 bits (0 ~ 31) */
#define PCI_MAX_FUN 7 /* 3 bits (0 ~ 7) */

/*
 * PCI Configuration Header offsets
 */

#define PCICFG_REG_VID 0x00 /* Vendor id, 2 bytes */
#define PCICFG_REG_DID 0x02 /* Device id, 2 bytes */
#define PCICFG_REG_CMD 0x04 /* Command register, 2 bytes */
#define PCICFG_REG_STAT 0x06 /* Status register, 2 bytes */
#define PCICFG_REG_RID 0x08 /* Revision id, 1 byte */
#define PCICFG_REG_PROG_INTF 0x09 /* Programming interface code, 1 byte */
#define PCICFG_REG_SUBCLASS 0x0A /* Sub-class code, 1 byte */
#define PCICFG_REG_BASCLASS 0x0B /* Base class code, 1 byte */
#define PCICFG_REG_CACHE_LINESZ 0x0C /* Cache line size, 1 byte */
#define PCICFG_REG_LATENCY_TIMER 0x0D /* Latency timer, 1 byte */
#define PCICFG_REG_HEADER_TYPE 0x0E /* Header type, 1 byte */
#define PCICFG_REG_BIST 0x0F /* Builtin self test, 1 byte */
#define PCICFG_REG_BAR0 0x10 /* Base addr register 0, 4 bytes */
#define PCICFG_REG_BAR1 0x14 /* Base addr register 1, 4 bytes */
#define PCICFG_REG_BAR2 0x18 /* Base addr register 2, 4 bytes */
#define PCICFG_REG_BAR3 0x1C /* Base addr register 3, 4 bytes */
#define PCICFG_REG_BAR4 0x20 /* Base addr register 4, 4 bytes */
#define PCICFG_REG_BAR5 0x24 /* Base addr register 5, 4 bytes */
#define PCICFG_REG_CIS 0x28 /* Cardbus CIS Pointer */
#define PCICFG_REG_SVID 0x2C /* Subsystem Vendor ID, 2 bytes */
#define PCICFG_REG_SDID 0x2E /* Subsystem ID, 2 bytes */
#define PCICFG_REG_ROMBAR 0x30 /* ROM base register, 4 bytes */
#define PCICFG_REG_CAPPTR 0x34 /* Capabilities pointer, 1 byte */
#define PCICFG_REG_INT_LINE 0x3C /* Interrupt line, 1 byte */
#define PCICFG_REG_INT_PIN 0x3D /* Interrupt pin, 1 byte */
#define PCICFG_REG_MIN_GNT 0x3E /* Minimum grant, 1 byte */
#define PCICFG_REG_MAX_LAT 0x3F /* Maximum lat, 1 byte */


void list_pci_devices()
{
    unsigned int bus, dev, fun;

    //printf("BB:DD:FF VID:DID(RID)\n");


    for (bus = 0; bus <= PCI_MAX_BUS; bus++) {
        for (dev = 0; dev <= PCI_MAX_DEV; dev++) {
            for (fun = 0; fun <= PCI_MAX_FUN; fun++) {
                char proc_name[64];
                int cfg_handle;
                uint32_t data;
                uint16_t vid, did;
                uint8_t rid;

                snprintf(proc_name, sizeof(proc_name),
                    "/proc/bus/pci/%02x/%02x.%x", bus, dev, fun);
                cfg_handle = open(proc_name, O_RDWR);
                if (cfg_handle <= 0) {
                    continue;
                }

                lseek(cfg_handle, PCICFG_REG_VID, SEEK_SET);
                read(cfg_handle, &data, sizeof(data));

                /* Identify vendor ID */
                if ((data != 0xFFFFFFFF) && (data != 0)) {
                    lseek(cfg_handle, PCICFG_REG_RID, SEEK_SET);
                    read(cfg_handle, &rid, sizeof(rid));
                    vid = data&0xFFFF;
                    did = data>>16;
                   
                    printf("%02X:%02X:%02X", bus, dev, fun);
                    if (rid > 0) {
                        printf(" %04X:%04X (rev %02X)\n", vid, did, rid);
                    } else {
                        printf(" %04X:%04X\n", vid, did);
                    }
                }
            }
        }
    }
}


int main(int argc, char **argv)
{
    list_pci_devices();

    return 0;
}


这两种方法各有优缺点,第一种方法方便移植到其他OS,第二种就只适用于Linux。但是,第一种方法需要对I/O port进行直接操作。第二种就不需要。

注意:执行这两段代码时,需要超级用户(root) 权限。

补充:今天在枚举 Westmere-EP Processor(Intel Xeon Processor 5500 Series(Nehalem-EP))的 IMC(Integrated Memory Controller)时发现一个问题。lspci无法枚举到IMC设备。Westmere-EP 是 Intel 新的处理器架构。和以往的CPU不一样,它把Memory Controller集成到了CPU里面。IMC控制器被映射到了PCI总线上,Bus Number 是0xFE~0xFF,procfs(/proc/bus/pci/)下没有这几个设备。但是,通过 CF8/CFC 端口可以枚举到这些设备。

阅读(5755) | 评论(0) | 转发(2) |
0

上一篇:没有makefile也可以make

下一篇:读取RTC时间

给主人留下些什么吧!~~