Linux

如何為 PCIe UIO 設備創建基於序列號的符號連結?

  • May 25, 2021

我正在一個基於 Linux 伺服器的系統工作,該伺服器託管多個 PCIe 數據採集板。每個闆卡在擴展的 PCIe 配置空間中都有其單獨的設備序列號。這些卡作為 UIO 設備處理。不幸的是,似乎這些板有時是按隨機順序列舉的。系統的正確操作要求創建符號連結,其名稱與各個板永久關聯。

自然的解決方案是使用 udev(例如,基於來自http://reactivated.net/writing_udev_rules.html的資訊)。但是,udev 不提供設備屬性之間的設備序列號功能。可以使用如下命令進行測試:

#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"

找到關聯的udevadm父 PCIe 設備,但不顯示其設備序列號。

可以使用 lspci 讀取 DSN:

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

基於此,我創建了一個可行的解決方案。第一部分是儲存在文件中的 udev 規則/etc/udev/rules.d/30-daq.rules

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

PCIe板的DSN是通過腳本找到的,/opt/bin/uio_namer這是解決方案的第二部分:

#!/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##*" "}

該腳本還過濾掉為其他 PCIe 板創建的 UIO。對於“我們的”板,設備文件的符號連結在 /dev/daq 目錄中創建為板的序列號。例如:/dev/daq/12-34-56-78-90-ab-cd-ef

該解決方案已在 QEMU 中進行了測試,並添加了 PCIe DAQ 板的型號。這是工作。但是,我認為這不是最佳選擇,以下是問題:

  1. 有沒有辦法在 udev 規則中訪問父設備的屬性?
  2. 我的解決方案依賴於lspci -vvv. 如果更改了該格式,則該解決方案將不再有效。對“機器可讀輸出”使用“-m”選項沒有幫助,因為它不列印 DSN。是否有任何實用程序可以可靠地以穩定格式輸出 PCIe 設備的 DSN?

我也找到了第二個問題的答案。我創建了一個顯示 PCIe 設備序列號的簡單應用程序。它可用於替換lspci輔助腳本中的呼叫。也消除了輸出格式的改變lspci導致之前的解決方案無效的風險。來源:

/*
 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;
}

可以編譯

gcc -o pcidsn pcidsn.c -lpci

生成的應用程序應該可以通過系統 PATH 獲得。/opt/bin/uio_namer然後應將幫助腳本修改為:

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

udev 規則/etc/udev/rules.d/30-daq.rules可能和以前一樣:

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

我找到了第一個問題的答案。在http://reactivated.net/writing_udev_rules.html中有一個聲明:

ATTRS - 匹配設備的 sysfs 屬性,或任何父設備的 sysfs 屬性

因此,PCI 供應商和設備屬性的規則也可能與父設備匹配。

udev 規則/etc/udev/rules.d/30-daq.rules可以修改為:

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

並且幫助腳本/opt/bin/uio_namer可以簡化為:

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

引用自:https://unix.stackexchange.com/questions/650370