Table of Contents

PCI Configuration Space

Jedes PCI-Gerät hat seinen eigenen PCI Configuration Space im Speicher. Mit pciconf -l können darüber Informationen zu PCI-Geräten ausgegeben werden. Hier die Ausgabe einer FreeBSD 16.0-CURRENT VM, die über einen VirtualBox Hypervisor auf macOS mit Apple Silicon läuft.

% pciconf -l
vgapci0@pci0:0:0:0:	class=0x030000 rev=0x00 hdr=0x00 vendor=0x15ad device=0x0406 subvendor=0x15ad subdevice=0x0406
none0@pci0:0:1:0:	class=0x088000 rev=0x00 hdr=0x00 vendor=0x80ee device=0xcafe subvendor=0x0000 subdevice=0x0000
none1@pci0:0:2:0:	class=0x040300 rev=0x01 hdr=0x00 vendor=0x8086 device=0x2668 subvendor=0x8384 subdevice=0x7680
pcib1@pci0:0:3:0:	class=0x060401 rev=0xf2 hdr=0x01 vendor=0x8086 device=0x2448 subvendor=0x0000 subdevice=0x0000
xhci0@pci0:0:6:0:	class=0x0c0330 rev=0x00 hdr=0x00 vendor=0x8086 device=0x1e31 subvendor=0x0000 subdevice=0x0000
em0@pci0:0:8:0:	class=0x020000 rev=0x02 hdr=0x00 vendor=0x8086 device=0x100e subvendor=0x8086 subdevice=0x001e
em1@pci0:0:9:0:	class=0x020000 rev=0x02 hdr=0x00 vendor=0x8086 device=0x100e subvendor=0x8086 subdevice=0x001e
virtio_pci0@pci0:0:10:0:	class=0x020000 rev=0x00 hdr=0x00 vendor=0x1af4 device=0x1000 subvendor=0x1af4 subdevice=0x0001
ahci0@pci0:0:16:0:	class=0x010601 rev=0x02 hdr=0x00 vendor=0x8086 device=0x2829 subvendor=0x0000 subdevice=0x0000

Hier betrachte ich den VirtIO Netzwerkadapter genauer.

Mit dem Parameter v gibt pciconf die Namen zu den IDs der class, device, usw. aus.

% pciconf -lv virtio_pci0@pci0:0:10:0
virtio_pci0@pci0:0:10:0:	class=0x020000 rev=0x00 hdr=0x00 vendor=0x1af4 device=0x1000 subvendor=0x1af4 subdevice=0x0001
    vendor     = 'Red Hat, Inc.'
    device     = 'Virtio network device'
    class      = network
    subclass   = ethernet

Standardisierter Bereich

In der einfachen Variante zeigt pciconf nur einen kleinen Teil der Informationen an, die im Configuration Space stehen. Pro PCI-Gerät ist der Configuration Space 256 Byte groß. Die ersten 64 Byte sind standardisiert über das folgende Format.

   Bit 0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 Byte +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |           Vendor ID           |            Device ID          |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |            Command            |             Status            |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |  Revision ID  |                  Class Code                   |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 | Cache Line S. |  Lat. Timer   |  Header Type  |     BIST      |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   16 |                             BAR 1                             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   20 |                             BAR 2                             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   24 |                             BAR 3                             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   28 |                             BAR 4                             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   32 |                             BAR 5                             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   36 |                             BAR 6                             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   40 |                       Cardbus CIS Pointer                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   44 |       Subsystem Vendor ID      |        Subsystem ID          |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   48 |                 Expansion ROM Base Address                    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   52 | Cap. Pointer  |                   Reserved                    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   56 |.                           Reserved                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   60 |Interrupt Line | Interrupt Pin |    Min Gnt.   |    Max Lat.   |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Mit dem Parameter r kann pciconf den PCI Configuration Space direkt ausgeben. Der Parameter b dahinter gibt an, dass der Configuration Space Byteweise ausgelesen werden soll und 0x0:0x3f gibt den Bereich an, der ausgelesen werden soll (also die kompletten ersten 64 Byte).

% sudo pciconf -rb virtio_pci0@pci0:0:10:0 0x0:0x3f
f4 1a 00 10 17 00 10 00  00 00 00 02 00 00 00 00 
01 00 00 00 00 00 00 00  00 60 85 e1 00 00 00 00 
00 00 00 00 00 00 00 00  00 00 00 00 f4 1a 01 00 
00 00 00 00 40 00 00 00  00 00 00 00 0c 01 00 00 

Die Bytes sind in Little Endian. Die ersten zwei Bytes sind also 0x1af4, die die Vendor ID angeben.

Base Address Register (BAR)

In einer Base Address Register (BAR) Region, kann das PCI-Gerät weitere Informationen schreiben und auch der Treiber kann darin schreiben, um z. B. Informationen zurück zu geben. Das BAR in dem standardisierten Bereich ist hauptsächlich die Base Address, das ist die Adresse unter der sich die BAR Region im Speicher befindet. Darin kodiert ist z. B. der Typ des BAR. Es gibt Memory (0) und I/O (1).

Bei dem PCI-Gerät gibt es zwei BARs. Hier der Abschnitt vom Configuration Space zu den BARs.

| BAR 1     |            | BAR 2     |
 01 00 00 00 00 00 00 00  00 60 85 e1 00 00 00 00 
 00 00 00 00 00 00 00 00

BAR 1 hat Typ 1 (I/O) und Base Address 0. BAR 2 hat Typ 0 (Memory) und Base Address 0xe1856000. Die BARs lassen sich mit pciconf über den Parameter b ausgeben.

% pciconf -lb virtio_pci0@pci0:0:10:0
virtio_pci0@pci0:0:10:0:	class=0x020000 rev=0x00 hdr=0x00 vendor=0x1af4 device=0x1000 subvendor=0x1af4 subdevice=0x0001
    bar   [10] = type I/O Port, range 32, base 0, size 32, enabled
    bar   [18] = type Memory, range 32, base 0xe1856000, size 8192, enabled

Die Zahl in eckigen Klammern gibt über ein Byte-Offset hexadezimal an, wo im Configuration Space der BAR angegeben ist.

VirtIO nutzt im Legacy Modus den BAR vom Typ I/O und im Modern Modus den BAR vom Typ Memory. Das PCI-Gerät, was VirtualBox bereistellt ist ein Transitional Device. Das heißt, es unterstützt beide Modi. FreeBSD hat einen Treiber pro Modus (src/sys/dev/virtio/pci/virtio_pci_[legacy|modern].c). Es ruft jeweils die entsprechende device_probe Methode auf und trifft die Entscheidung anhand des Rückgabewertes. Hier betrachte ich den Modern Modus.

Mit pciconf kann über den Parameter D eine BAR Region ausgelesen werden.

% sudo pciconf -D pci0:0:10:0 0x18 | hexdump -C
00000000  01 00 00 00 01 00 00 00  01 00 00 00 01 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 90 fa 03 01 00 00 00  00 d0 fa 03 01 00 00 00  |................|
00000030  00 e0 fa 03 01 00 00 00  ff ff ff ff ff ff ff ff  |................|
00000040  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000060  ff ff ff ff ff ff ff ff  ff ff ff ff 00 00 00 00  |................|
00000070  08 00 27 54 00 00 00 00  01 00 00 00 ff ff ff ff  |..'T............|
00000080  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00002000

Im Modern Mode zeigen die PCI Capabilities auf, wo sich in der BAR Region welche Informationen finden.

Capabilities

Zu einem PCI-Gerät können eine Reihe von Capabilities angegeben werden, die z. B. angeben können, in welchen BAR Regionen und wo darin weitere Informationen zu finden sind. Der Capabilities Pointer ist in dem PCI Configuration Space von oben auf 0x40 gesetzt. Das ist ein Pointer auf des erste Element in einer linked list von Capabilities. Die Capabilities lassen sich mit pciconf über den Parameter c ausgeben.

% sudo pciconf -lc virtio_pci0@pci0:0:10:0
virtio_pci0@pci0:0:10:0:	class=0x020000 rev=0x00 hdr=0x00 vendor=0x1af4 device=0x1000 subvendor=0x1af4 subdevice=0x0001
    cap 09[40] = vendor (length 16)
    cap 09[50] = vendor (length 20)
    cap 09[64] = vendor (length 16)
    cap 09[74] = vendor (length 20)
    cap 09[88] = vendor (length 16)

Der erste Wert nach cap gibt die Capability ID an. Der Wert in eckigen Klammern gibt über ein Byte-Offset hexadezimal an, wo im Configuration Space die Capability beschrieben steht. Die erste Capability ist also 0x40 = 64 Byte hinter dem Start des Configuration Space zu finden, also direkt hinter dem standardisierten Bereich. Die Bytes lassen sich ebenfalls mit pciconf -rb auslesen.

% sudo pciconf -rb virtio_pci0@pci0:0:10:0 0x40:0x97
09 50 10 01 02 00 00 00  00 00 00 00 38 00 00 00 
09 64 14 02 02 00 00 00  38 00 00 00 32 00 00 00 
02 00 00 00 09 74 10 03  02 00 00 00 6c 00 00 00 
01 00 00 00 09 88 14 05  02 00 00 00 00 00 00 00 
04 00 00 00 00 00 00 00  09 00 10 04 02 00 00 00 
70 00 00 00 0a 00 00 00

Das jeweils erste Byte gibt die Capability ID an und das zweite Byte den Offset zur nächsten Capability. Eine bessere Ausgabe zu den Capabilities bietet lspci, was sich über

% sudo pkg install pciutils

installieren lässt. Hier die Ausgabe zu dem PCI-Gerät von lspci.

% sudo lspci -vv
...
00:0a.0 Ethernet controller: Red Hat, Inc. Virtio network device
	Subsystem: Red Hat, Inc. Device 0001
	Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV+ VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
	Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
	Latency: 0
	Interrupt: pin A routed to IRQ 12
	Region 0: I/O ports at 0000
	Region 2: Memory at e1856000 (32-bit, non-prefetchable)
	Capabilities: [40] Vendor Specific Information: VirtIO: CommonCfg
		BAR=2 offset=00000000 size=00000038
	Capabilities: [50] Vendor Specific Information: VirtIO: Notify
		BAR=2 offset=00000038 size=00000032 multiplier=00000002
	Capabilities: [64] Vendor Specific Information: VirtIO: ISR
		BAR=2 offset=0000006c size=00000001
	Capabilities: [74] Vendor Specific Information: VirtIO: <unknown>
		BAR=2 offset=00000000 size=00000004
	Capabilities: [88] Vendor Specific Information: VirtIO: DeviceCfg
		BAR=2 offset=00000070 size=0000000a
...

Alle Capabilities sind also VirtIO spezifische Capabilities und die Informationen befinden sich in der Region vom BAR 2, also in der vom Typ memory (die im VirtIO Modern Modus benutzt wird). Das Offset gibt an ab welcher Byte Position die Informationen in der BAR Region sind und size wie viele Bytes die Informationen umfassen.

VirtIO: CommonCfg

Die erste Capability vom Typ VirtIO: CommonCfg enthält VirtIO-Gerätunabhängige Einstellungen. Sie startet in der BAR Region bei Byte 0 und ist 0x38 Byte groß. Es handelt sich um den Bereich vor den ganzen 0xffff, also

00000000  01 00 00 00 01 00 00 00  01 00 00 00 01 00 00 00
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00000020  00 90 fa 03 01 00 00 00  00 d0 fa 03 01 00 00 00
00000030  00 e0 fa 03 01 00 00 00

Beschrieben ist das Format in der VirtIO Spezifikation im Abschnitt 4.1.4.3 Common configuration structure layout mit

struct virtio_pci_common_cfg { 
        /* About the whole device. */ 
        le32 device_feature_select;     /* read-write */ 
        le32 device_feature;            /* read-only for driver */ 
        le32 driver_feature_select;     /* read-write */ 
        le32 driver_feature;            /* read-write */ 
        le16 config_msix_vector;        /* read-write */ 
        le16 num_queues;                /* read-only for driver */ 
        u8 device_status;               /* read-write */ 
        u8 config_generation;           /* read-only for driver */ 
 
        /* About a specific virtqueue. */ 
        le16 queue_select;              /* read-write */ 
        le16 queue_size;                /* read-write */ 
        le16 queue_msix_vector;         /* read-write */ 
        le16 queue_enable;              /* read-write */ 
        le16 queue_notify_off;          /* read-only for driver */ 
        le64 queue_desc;                /* read-write */ 
        le64 queue_driver;              /* read-write */ 
        le64 queue_device;              /* read-write */ 
        le16 queue_notif_config_data;   /* read-only for driver */ 
        le16 queue_reset;               /* read-write */ 
 
        /* About the administration virtqueue. */ 
        le16 admin_queue_index;         /* read-only for driver */ 
        le16 admin_queue_num;         /* read-only for driver */ 
};

Das VirtualBox PCI-Gerät gibt darüber also z. B. an, welche Features es unterstützt. Für die Features gibt es insgesamt 64 Bit. In device_feature_select wird geschrieben, ob device_feature die unteren 32 Bit (0) oder die oberen 32 Bit (1) anzeigt. In der Ausgabe oben ist in Little Endian gelesen device_feature_select=1, somit gibt das device_feature mit 1 die oberen oberen 32 Bit an. Auf die gleiche Weise schreibt der FreeBSD Treiber die Features, die es davon unterstützt und nutzen möchte in driver_feature_select und driver_feature. Auch hier ist in der Ausgabe oben in Little Endian gelesen driver_feature_select=1, somit gibt das driver_feature mit 1 die oberen oberen 32 Bit an.

Mit device_feature_select=0 und driver_feature_select=0 sieht die erste Zeile der Ausgabe so aus.

00000000  00 00 00 00 dd a3 00 2f  00 00 00 00 23 98 0f 00

In Little Endian gelesen sind device_feature=0x1002fdda3, d. h. das PCI-Gerät unterstützt die folgenden Features

Version1,GuestAnnounce,CtrlVLANFilter,CtrlRxMode,CtrlVq,Status,MrgRxBuf,TxUFO,TxTSOv6,TxTSOv4,RxUFO,RxLROv6,RxLROv4,MAC,RxChecksum,TxChecksum

und driver_feature=0x1000f9823, d. h. ausgehandelt wurden die folgenden Features

Version1,CtrlVLANFilter,CtrlRxMode,CtrlVq,Status,MrgRxBuf,TxTSOv6,TxTSOv4,MAC,RxChecksum,TxChecksum

VirtIO: DeviceCfg

Die Capability VirtIO: DeviceCfg gibt VirtIO-Gerätspezifische Einstellungen an. In der BAR Region startet sie bei Byte-Offset 0x70 und ist 0xa = 10 Byte lang, was diesem Abschnitt entspricht:

00000070  08 00 27 54 00 00 00 00  01 00

Beschrieben ist das Format in der VirtIO Spezifikation im Abschnitt 5.1.4 Device configuration layout mit:

struct virtio_net_config { 
        u8 mac[6]; 
        le16 status; 
        le16 max_virtqueue_pairs; 
        le16 mtu; 
        le32 speed; 
        u8 duplex; 
        u8 rss_max_key_size; 
        le16 rss_max_indirection_table_length; 
        le32 supported_hash_types; 
        le32 supported_tunnel_types; 
};

Es sind jedoch nicht immer alle Werte angegeben. Welche Werte angegeben sind, hängt von den ausgehandelten Features ab. Hier sind lediglich die MAC-Adresse=08:00:27:54:00:00, der Status=0 und max_virtqueue_pairs=1 angegeben. Die MTU fehlt z. B., weil das Feature VIRTIO_NET_F_MTU nicht ausgehandelt wurde.