Thread: Help with pciutils-dev

  1. #1
    Registered User
    Join Date
    May 2013
    Posts
    228

    Help with pciutils-dev

    I am trying to familiarize myself with pciutils-dev C library, and I came across this example file.

    For simplicity, let me write (a mildly changed version of) example.c here:

    Code:
        int main(int argc, char* argv[]) {
        
            struct pci_access*    pacc;
            struct pci_dev*        dev;
        
            pacc = pci_alloc();
            pci_init(pacc);
            pci_scan_bus(pacc);
        
            for (dev = pacc->devices; dev; dev = dev->next) {
                pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_BASES | PCI_FILL_CLASS);
                printf("%02x:%02x.%d vendor=%04x device=%04x class=%04x irq=%d base0=%lx\n",
                    dev->bus, dev->dev, dev->func, dev->vendor_id, dev->device_id,
                    dev->device_class, dev->irq, (long) dev->base_addr[0]);
            }
        
            pci_cleanup(pacc);
            return 0;
        }
    I omitted things I'm not interested in right now.
    So, I'm am trying to find out what does pci_fill_info() do.
    Now, you're right, its name and arguments kinda give it away, and plus this helps revealing more (from here):

    Code:
    struct pci_dev {
        struct pci_dev *next;         /* Next device in the chain */
        u16 domain;               /* PCI domain (host bridge) */
        u8 bus, dev, func;            /* Bus inside domain, device and function */
    
        /* These fields are set by pci_fill_info() */
        int known_fields;         /* Set of info fields already known */
        u16 vendor_id, device_id;     /* Identity of the device */
        u16 device_class;         /* PCI device class */
        int irq;              /* IRQ number */
        pciaddr_t base_addr[6];       /* Base addresses including flags in lower bits */
        pciaddr_t size[6];            /* Region sizes */
        pciaddr_t rom_base_addr;      /* Expansion ROM base address */
        pciaddr_t rom_size;           /* Expansion ROM size */
        struct pci_cap *first_cap;        /* List of capabilities */
        char *phy_slot;           /* Physical slot */
        char *module_alias;           /* Linux kernel module alias */
        char *label;              /* Device name as exported by BIOS */
        int numa_node;            /* NUMA node */
    
        /* Fields used internally: */
        struct pci_access *access;
        struct pci_methods *methods;
        u8 *cache;                /* Cached config registers */
        int cache_len;
        int hdrtype;              /* Cached low 7 bits of header type, -1 if unknown */
        void *aux;                /* Auxillary data */
    };

    Nonetheless, I wanted to be sure that it does what I think it does.
    So I looked everywhere for its definition and couldn't find it!
    Please, if anyone knows where I can find its implementation. that would be great!

    Another weird thing that happens; is that when I tried to confirm the function's mode of operation, something unexpected happened.

    Consider this:

    Code:
    int main(int argc, char* argv[]) {
    
        struct pci_access*    pacc;
        struct pci_dev*        dev;
    
        pacc = pci_alloc();
        pci_init(pacc);
        pci_scan_bus(pacc);
    
        for (dev = pacc->devices; dev; dev = dev->next) {
            struct pci_dev* temp = dev->next;
            memset(dev, 0, sizeof(dev));
            dev->next = temp;
            pci_fill_info(dev, PCI_FILL_IDENT);
            printf("%02x:%02x.%d vendor=%04x device=%04x class=%04x irq=%d base0=%lx\n",
                dev->bus, dev->dev, dev->func, dev->vendor_id, dev->device_id,
                dev->device_class, dev->irq, (long) dev->base_addr[0]);
        }
    
        pci_cleanup(pacc);
        return 0;
    }
    Notice that the only flag I give to pci_fill_info() is PCI_FILL_IDENT, but the above still prints valid values for all the fields!
    It is as if pci_fill_info() completely ignores the flags, and just populate the entire struct!

    Is anyone familiar with this function and can shed some light?

    Thanks.

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    > dev->next = temp;
    You should only be FOLLOWING the chain, not changing it.

    IMO, try just deleting these lines
    Code:
            struct pci_dev* temp = dev->next;
            memset(dev, 0, sizeof(dev));
            dev->next = temp;
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    May 2013
    Posts
    228
    Sorry, I guess I should have been more clear about why I stuck these three lines there.

    You see, I wanted to see if omitting the flags PCI_FILL_BASES | PCI_FILL_CLASS will cause pci_fill_info() not to fill these fields (BARs and Class).
    After omitting these flags and running the program for the second time, I noticed that these fields still holds valid values.
    But then I thought to myself... "Wait, that still doesn't mean that pci_fill_info() filled these fields with values. Maybe these values were there from a previous run, and what I see is garbage..."
    So to be absolutely sure, I filled these fields with zeros before calling pci_fill_info(). Obviously I couldn't overwrite dev->next, so that's why I added these three lines.
    Last edited by Absurd; 12-31-2015 at 01:19 PM.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    Why do you care if there is extra information?

    So long as the implementation conforms to it's specification, I don't think there is much to worry about.

    Maybe for convenience, the library implementor has logically split up PCI_FILL_ into several sub-sets for later development, but for the moment, PCI_FILL_IDENT fills in everything.

    That it fills in everything is an internal implementation detail, but you should not rely on that behaviour. If you want PCI_FILL_BASES, then you need to explicitly say so.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    The line
    Quote Originally Posted by Absurd View Post
    Code:
            memset(dev, 0, sizeof(dev));
    clears the first (size of a pointer) bytes of the structure pointed to by dev; in Linux, that is just the 'next' member.

    Your code basically just ends up saving the value of the next member in the dev structure, clearing it, and copying it back. Not what you indented, I'd wager.

    Modifying these structures is not a sane approach. Your program should not do that.

    However, I can see why doing so is interesting for learning purposes. With the caveat that this is not even supposed to work -- because the structure is supposed to be informational, not messed with willy-nilly --, here is the approach I recommend. In the loop body:
    Code:
            struct pci_dev  custom; /* Not a pointer! */
    
            memset(&custom, 0, sizeof custom);
    
            /* Copy the PCI device identifiers. */
            custom.domain = dev->domain;
            custom.bus = dev->bus;
            custom.dev = dev->dev;
            custom.func = dev->func;
    
            /* Copy the internal fields the library expects to be there */
            custom.access = dev->access;
            custom.methods = dev->methods;
            custom.cache = dev->cache;
            custom.cache_len = dev->cache_len;
            custom.hdrtype = dev->hdrtype;
            custom.aux = dev->aux;
    
            /* Fill in the device information */
            pci_fill_info(&custom, ...);
    That will leak some memory, as the capabilities list is lost as custom goes out of scope, but it's all recovered by the kernel when the process exits, so it's okay for exploration.

    The source for pci_fill_info() is in lib/access.c in the source tree. It is actually a versioned alias; at the HEAD the current function is actually pci_fill_info_v34(). You can find the name by using readelf -s or objdump -t on your pciutils library file (to list all symbol names). The actual function is the highest versioned one.

    Before you get excited, that function is trivial. Because the library supports all kinds of systems, it stores the pointers to the correct access functions (like fill_info) in a structure hanging off the methods member in the dev structure. That function -- and there are several implementations; which one gets called depends on the system and bus -- is responsible for filling in the fields. Look at the source tree, at the various type_fill_info() functions.

    For Linux, I guess the lib/sysfs.c:sysfs_get_info() and lib/generic.c:pci_generic_fill_info() are most interesting.

  6. #6
    Registered User
    Join Date
    May 2013
    Posts
    228
    Quote Originally Posted by Salem View Post
    So long as the implementation conforms to it's specification, I don't think there is much to worry about.
    That is actually what I was looking for. Its specification...

  7. #7
    Registered User
    Join Date
    May 2013
    Posts
    228
    Quote Originally Posted by Nominal Animal View Post
    The line

    clears the first (size of a pointer) bytes of the structure pointed to by dev; in Linux, that is just the 'next' member.

    Your code basically just ends up saving the value of the next member in the dev structure, clearing it, and copying it back. Not what you indented, I'd wager.
    You're right. That's not what I was aiming for... This should have been sizeof(struct pci_dev)...

    Quote Originally Posted by Nominal Animal View Post
    Modifying these structures is not a sane approach. Your program should not do that.

    However, I can see why doing so is interesting for learning purposes. With the caveat that this is not even supposed to work -- because the structure is supposed to be informational, not messed with willy-nilly --, here is the approach I recommend. In the loop body:
    Code:
            struct pci_dev  custom; /* Not a pointer! */
    
            memset(&custom, 0, sizeof custom);
    
            /* Copy the PCI device identifiers. */
            custom.domain = dev->domain;
            custom.bus = dev->bus;
            custom.dev = dev->dev;
            custom.func = dev->func;
    
            /* Copy the internal fields the library expects to be there */
            custom.access = dev->access;
            custom.methods = dev->methods;
            custom.cache = dev->cache;
            custom.cache_len = dev->cache_len;
            custom.hdrtype = dev->hdrtype;
            custom.aux = dev->aux;
    
            /* Fill in the device information */
            pci_fill_info(&custom, ...);
    That will leak some memory, as the capabilities list is lost as custom goes out of scope, but it's all recovered by the kernel when the process exits, so it's okay for exploration.

    The source for pci_fill_info() is in lib/access.c in the source tree. It is actually a versioned alias; at the HEAD the current function is actually pci_fill_info_v34(). You can find the name by using readelf -s or objdump -t on your pciutils library file (to list all symbol names). The actual function is the highest versioned one.

    Before you get excited, that function is trivial. Because the library supports all kinds of systems, it stores the pointers to the correct access functions (like fill_info) in a structure hanging off the methods member in the dev structure. That function -- and there are several implementations; which one gets called depends on the system and bus -- is responsible for filling in the fields. Look at the source tree, at the various type_fill_info() functions.

    For Linux, I guess the lib/sysfs.c:sysfs_get_info() and lib/generic.cci_generic_fill_info() are most interesting.
    Yes. The only reason for doing so was to see how it works.

    Thanks!

  8. #8
    Registered User
    Join Date
    May 2013
    Posts
    228
    OK, I cleared the fields one at a time:

    Code:
    int main(int argc, char* argv[]) {
    
        struct pci_access*    pacc;
        struct pci_dev*        dev;
    
        pacc = pci_alloc();
        pci_init(pacc);
        pci_scan_bus(pacc);
    
        for (dev = pacc->devices; dev; dev = dev->next) {
            dev->bus = 0;
            dev->dev = 0;
            dev->func = 0;
            dev->vendor_id = 0;
            dev->device_id = 0;
            dev->device_class = 0;
            dev->irq = 0;
            dev->base_addr[0] = 0;
            
            pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_BASES | PCI_FILL_CLASS);
            printf("%02x:%02x.%d vendor=%04x device=%04x class=%04x irq=%d base0=%lx\n",
                dev->bus, dev->dev, dev->func, dev->vendor_id, dev->device_id,
                dev->device_class, dev->irq, (long) dev->base_addr[0]);
        }
    
        pci_cleanup(pacc);
        return 0;
    }
    But this prints nothing but zeros everywhere...

    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0
    00:00.0 vendor=0000 device=0000 class=0000 irq=0 base0=0

    What's wrong with it? Why is pci_fill_info() suddenly does not work when you clear these fields before calling it?

  9. #9
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Absurd View Post
    OK, I cleared the fields one at a time:
    You might have missed my point: my code clears a device structure, then copies to it the bare minimum fields needed for the library to work at all.

    You must not zero:
    • domain, because it identifies the domain (PCI bridge) used for this particular device
    • bus, dev, or func, because they identify the PCI device in this domain
    • access, methods, cache, cache_len, hdrtype, or aux, because they are internal fields used by the library
    • next, because it is used to iterate through the list

    If you clear any of those, you cannot expect the library to work right. If you clear any or all of the four fields listed first above, the library no longer knows which device the structure is supposed to refer to. If you clear any of the internal fields, the library will (likely) malfunction. If you clear the next field, you won't see any but the very first device in the chain.

  10. #10
    Registered User
    Join Date
    May 2013
    Posts
    228
    Ohh... OK. Now it all makes sense.
    Thanks again!

Popular pages Recent additions subscribe to a feed