1

I'm working at a system based on Linux servers hosting multiple PCIe data acquisition boards. Each board has its individual Device Serial Number available in the extended PCIe configuration space. The cards are handled as UIO devices. Unfortunately, it appears that the boards are sometimes enumerated in the random order. Proper operation of the system requires that there are symlinks created with names permanently associated with the individual boards.

The natural solution is to use udev (e.g., based on information from http://reactivated.net/writing_udev_rules.html ). However, udev does not provide the Device Serial Number capability between the device attributes. It may be tested with command like this:

#udevadm info --attribute-walk /dev/uio0
[...]
  looking at device '/devices/pci0000:00/0000:00:03.0/uio/uio0':
    KERNEL=="uio0"
    SUBSYSTEM=="uio"
    DRIVER==""
    ATTR{event}=="0"
    ATTR{name}=="uio_pci_generic"
    ATTR{power/async}=="disabled"
    ATTR{power/control}=="auto"
    ATTR{power/runtime_active_kids}=="0"
    ATTR{power/runtime_active_time}=="0"
    ATTR{power/runtime_enabled}=="disabled"
    ATTR{power/runtime_status}=="unsupported"
    ATTR{power/runtime_suspended_time}=="0"
    ATTR{power/runtime_usage}=="0"
    ATTR{version}=="0.01.0"

  looking at parent device '/devices/pci0000:00/0000:00:03.0':
    KERNELS=="0000:00:03.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="uio_pci_generic"
    ATTRS{ari_enabled}=="0"
    ATTRS{broken_parity_status}=="0"
    ATTRS{class}=="0x00ff00"
    ATTRS{consistent_dma_mask_bits}=="32"
    ATTRS{current_link_speed}=="Unknown"
    ATTRS{current_link_width}=="0"
    ATTRS{d3cold_allowed}=="0"
    ATTRS{device}=="0x3342"
    ATTRS{dma_mask_bits}=="32"
    ATTRS{driver_override}=="(null)"
    ATTRS{enable}=="1"
    ATTRS{irq}=="23"
    ATTRS{local_cpulist}=="0"
    ATTRS{local_cpus}=="1"
    ATTRS{max_link_speed}=="Unknown"
    ATTRS{max_link_width}=="255"
    ATTRS{msi_bus}=="1"
    ATTRS{numa_node}=="-1"
    ATTRS{power/async}=="enabled"
    ATTRS{power/control}=="on"
    ATTRS{power/runtime_active_kids}=="0"
    ATTRS{power/runtime_active_time}=="696133"
    ATTRS{power/runtime_enabled}=="forbidden"
    ATTRS{power/runtime_status}=="active"
    ATTRS{power/runtime_suspended_time}=="0"
    ATTRS{power/runtime_usage}=="2"
    ATTRS{revision}=="0x00"
    ATTRS{subsystem_device}=="0x1100"
    ATTRS{subsystem_vendor}=="0x1af4"
    ATTRS{vendor}=="0xabba"

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""
    ATTRS{power/async}=="enabled"
    ATTRS{power/control}=="auto"
    ATTRS{power/runtime_active_kids}=="7"
    ATTRS{power/runtime_active_time}=="0"
    ATTRS{power/runtime_enabled}=="disabled"
    ATTRS{power/runtime_status}=="unsupported"
    ATTRS{power/runtime_suspended_time}=="0"
    ATTRS{power/runtime_usage}=="0"

The udevadm finds the associated parent PCIe device, but does not show its Device Serial Number.

The DSN may be read with lspci:

# lspci -vvv -s 0000:00:03.0 
[...]
Capabilities: [100 v1] Device Serial Number 12-34-56-78-90-ab-cd-ef
[...]

Based on that I have created a working solution. The first part is the udev rule stored in the file /etc/udev/rules.d/30-daq.rules:

SUBSYSTEM=="uio" PROGRAM="/opt/bin/uio_namer" SYMLINK="%c"

The DSN of the PCIe board is found by the script /opt/bin/uio_namer which is the second part of the solution:

#!/bin/sh
DEVID=abba:3342
SLOTNAME=`basename \`readlink /sys/${DEVPATH}/device\``
DLSPCI=`lspci -vvv -s ${SLOTNAME}`
#Check if it is our device
if [ -z "`echo ${DLSPCI} | grep Device | grep ${DEVID}`" ] ; then
  # It is UIO device associated with other PCIe device
  # don't create the symlink for it
  exit 1
fi
DSNCAP=`lspci -vvv -s ${SLOTNAME} | grep "Device Serial Number" `
echo daq/${DSNCAP##*" "}

The script also filters out UIO created for other PCIe boards. For "our" boards the symlinks to the device files are created in the /dev/daq directory as the board's serial numbers. For example: /dev/daq/12-34-56-78-90-ab-cd-ef

The solution has been tested in QEMU with added model of the PCIe DAQ board. It is working. However, I don't think it is optimal and here are the questions:

  1. Is there any way to access the attributes of the parent device in a udev rule?
  2. My solution relies on the particular format of the text output of lspci -vvv. If that format is changed, the solution won't work any more. Using the "-m" option for "machine readable output" does not help, as it does not print DSN. Is there any utility that may reliably output DSN of a PCIe device in a stable format?
0

I have found the answer for the first question. In http://reactivated.net/writing_udev_rules.html there is a statement:

ATTRS - match a sysfs attribute of the device, or a sysfs attribute of any of the parent devices

So the rule for the PCI vendor and device attributes may match also the parent device.

The udev rule /etc/udev/rules.d/30-daq.rules may be modified to:

SUBSYSTEM=="uio"  ACTION=="add" ATTRS{vendor}=="0xabba" ATTRS{device}=="0x3342" PROGRAM="/opt/bin/uio_namer" SYMLINK="%c"

and the helper script /opt/bin/uio_namer may be simplified to:

#!/bin/sh
SLOTNAME=`basename \`readlink /sys/${DEVPATH}/device\``
DSNCAP=`lspci -vvv -s ${SLOTNAME} | grep "Device Serial Number" `
echo daq/${DSNCAP##*" "}
0

I have also found the answer for the second question. I created a simple application that displays the PCIe Device Serial Number. It may be used to replace the lspci call in the helper script. It also eliminates the risk that the change of the output format of lspci renders the previous solution invalid. The source:

/*
  Simple application displaying the PCIe Device Serial Number
  Written by Wojciech M. Zabolotny (wzab01@gmail.com or wzab@ise.pw.edu.pl)
  2021.05.23
  This code is heavily based on the examples provided in the pciutils package
  https://github.com/pciutils/pciutils therefore I release it under the same
  license: GNU GENERAL PUBLIC LICENSE Version 2, June 1991
  
  You may compile it with:
    gcc -o pcidsn pcidsn.c -lpci
*/

#include <stdio.h>
#include <pci/pci.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>

void __attribute__((noreturn))
die(char *msg, ...)
{
    va_list args;

    va_start(args, msg);
    fprintf(stderr, "pcisdn: ");
    vfprintf(stderr, msg, args);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char * argv[])
{
    struct pci_access *pacc;
    struct pci_filter filter;
    struct pci_dev *dev;
    struct pci_cap *pdsn;
    unsigned int c;
    char namebuf[1024], *name;
    char * msg = NULL;
    pacc = pci_alloc();     /* Get the pci_access structure */
    pacc->error = die;
    pci_filter_init(pacc,&filter);
    if (msg=pci_filter_parse_slot(&filter,argv[1])) {
        die("Wrong slot definition: %s",msg);
    };
    pci_init(pacc);     /* Initialize the PCI library */

    pci_scan_bus(pacc);     /* We want to get the list of devices */
    for (dev=pacc->devices; dev; dev=dev->next) /* Iterate over all devices */
    {
        if(pci_filter_match(&filter,dev)) { /* Select only our slot */
            pdsn = pci_find_cap(dev, PCI_EXT_CAP_ID_DSN, PCI_CAP_EXTENDED);
            if(pdsn) {
                uint32_t lw = pci_read_long(dev, pdsn->addr+4);
                uint32_t hw = pci_read_long(dev, pdsn->addr+8);
                printf("%8.8lx%8.8lx\n",hw,lw);
            }
        }
    }
    pci_cleanup(pacc);      /* Close everything */
    return 0;
}

may be compiled with

gcc -o pcidsn pcidsn.c -lpci

The resulting application should be available via the system PATH. The helper script /opt/bin/uio_namer should be then modified to:

#!/bin/sh
set -e
SLOTNAME=`basename \`readlink /sys/${DEVPATH}/device\``
DSNCAP=`pcidsn ${SLOTNAME}`
echo daq/${DSNCAP##*" "}

The udev rule /etc/udev/rules.d/30-daq.rules may be as previously:

SUBSYSTEM=="uio"  ACTION=="add" ATTRS{vendor}=="0xabba" ATTRS{device}=="0x3342" PROGRAM="/opt/bin/uio_namer" SYMLINK="%c"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.