board_inspector/pcieparser: add PCIe config space parser

This patch adds a parser of PCI-compatible configuration space read from
sysfs. The headers and capability lists are fully parsed, but only a couple
of capabilities are parsed completely. Parsing of additional capabilities
will be added on an on-demand basis.

v1 -> v2:
 - Fix a typo that causes incorrect parsing of BAR types
 - Parse capability structures using from_buffer_copy instead of
   from_address

Tracked-On: #5922
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
This commit is contained in:
Junjie Mao 2021-05-03 10:23:24 +08:00 committed by wenlingz
parent 0215603812
commit 3a395bb342
3 changed files with 394 additions and 0 deletions

View File

@ -0,0 +1,42 @@
# Copyright (C) 2021 Intel Corporation. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
import os
import ctypes
from collections import namedtuple
from pcieparser.header import header
from pcieparser.caps import capabilities
from pcieparser.extcaps import extended_capabilities
class PCIConfigSpace(namedtuple("PCIConfigSpace", ["header", "caps", "extcaps"])):
def __repr__(self):
acc = str(self.header)
for cap in self.caps:
acc += "\n"
acc += str(cap)
for extcap in self.extcaps:
acc += "\n"
acc += str(extcap)
return acc
def has_cap(self, cap_name):
for cap in self.caps:
if cap_name == cap.name:
return True
for cap in self.extcaps:
if cap_name == cap.name:
return True
return False
def parse_config_space(path):
data = open(os.path.join(path, "config"), mode='rb').read()
hdr = header(data)
caps = capabilities(data, hdr.capability_pointer)
config_space = PCIConfigSpace(hdr, caps, [])
if config_space.has_cap("PCI Express"):
extcaps = extended_capabilities(data)
config_space = PCIConfigSpace(hdr, caps, extcaps)
return config_space

View File

@ -0,0 +1,163 @@
# Copyright (C) 2021 Intel Corporation. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
import ctypes
import copy
import lib.cdata as cdata
class Capability:
# Capability names from PCI Local Bus Specification and PCI Express Base Specification
_cap_names_ = {
0x01: "Power Management",
0x02: "AGP",
0x03: "VPD",
0x04: "Slot Identification",
0x05: "MSI",
0x06: "CompactPCI Hot Swap",
0x07: "PCI-X",
0x08: "Hyper Transport",
0x09: "Vendor-Specific",
0x0a: "Debug port",
0x0b: "CompactPCI Central Resource Control",
0x0c: "Hot Plug",
0x0d: "Subsystem ID and Subsystem Vendor ID",
0x0e: "AGP 8x",
0x0f: "Secure Device",
0x10: "PCI Express",
0x11: "MSI-X",
0x13: "Conventional PCI Advanced Features",
0x14: "Enhanced Allocation",
0x15: "FPB",
}
@property
def name(self):
if self.id in self._cap_names_.keys():
return self._cap_names_[self.id]
else:
return f"Reserved ({hex(self.id)})"
class CapabilityListRegister(cdata.Struct, Capability):
_pack_ = 1
_fields_ = [
('id', ctypes.c_uint8),
('next_cap_ptr', ctypes.c_uint8),
]
# Power Management (0x01)
class PowerManagement(cdata.Struct, Capability):
_pack_ = 1
_fields_ = copy.copy(CapabilityListRegister._fields_) + [
('version', ctypes.c_uint16, 3),
('pme_clock', ctypes.c_uint16, 1),
('immediate_readiness_on_return_to_d0', ctypes.c_uint16, 1),
('device_specific_initialization', ctypes.c_uint16, 1),
('aux_current', ctypes.c_uint16, 3),
('d1_support', ctypes.c_uint16, 1),
('d2_support', ctypes.c_uint16, 1),
('pme_support', ctypes.c_uint16, 5),
('power_state', ctypes.c_uint16, 2),
('reserved1', ctypes.c_uint16, 1),
('no_soft_reset', ctypes.c_uint16, 1),
('reserved2', ctypes.c_uint16, 4),
('pme_en', ctypes.c_uint16, 1),
('data_select', ctypes.c_uint16, 4),
('data_scale', ctypes.c_uint16, 2),
('pme_status', ctypes.c_uint16, 1),
('reserved3', ctypes.c_uint16, 6),
('undefined', ctypes.c_uint16, 2),
('data', ctypes.c_uint8),
]
def parse_power_management(buf, cap_ptr):
return PowerManagement.from_buffer_copy(buf, cap_ptr)
# MSI (0x05)
def MSI_factory(field_list):
class MSI(cdata.Struct, Capability):
_pack_ = 1
_fields_ = copy.copy(CapabilityListRegister._fields_) + [
('msi_enable', ctypes.c_uint16, 1),
('multiple_message_capable', ctypes.c_uint16, 3),
('multiple_message_enable', ctypes.c_uint16, 3),
('address_64bit', ctypes.c_uint16, 1),
('per_vector_masking_capable', ctypes.c_uint16, 1),
('reserved', ctypes.c_uint16, 7),
] + field_list
return MSI
def msi_field_list(addr):
field_list = list()
msgctrl = MSI_factory([]).from_address(addr)
if msgctrl.address_64bit == 1:
field_list.append(('message_address', ctypes.c_uint64))
else:
field_list.append(('message_address', ctypes.c_uint32))
field_list.append(('message_data', ctypes.c_uint16))
if msgctrl.per_vector_masking_capable:
field_list.append(('reserved', ctypes.c_uint16))
field_list.append(('mask_bits', ctypes.c_uint32))
field_list.append(('pending_bits', ctypes.c_uint32))
return field_list
def parse_msi(buf, cap_ptr):
addr = ctypes.addressof(buf) + cap_ptr
field_list = msi_field_list(addr)
return MSI_factory(field_list).from_buffer_copy(buf, cap_ptr)
# MSI-X
class MSIX(cdata.Struct, Capability):
_pack_ = 1
_fields_ = copy.copy(CapabilityListRegister._fields_) + [
('table_size', ctypes.c_uint16, 10),
('reserved', ctypes.c_uint16, 3),
('function_mask', ctypes.c_uint16, 1),
('msix_enable', ctypes.c_uint16, 1),
('table_bir', ctypes.c_uint32, 3),
('table_offset_z', ctypes.c_uint32, 29),
('pba_bir', ctypes.c_uint32, 3),
('pba_offset_z', ctypes.c_uint32, 29),
]
@property
def table_offset(self):
return self.table_offset_z << 3
@property
def pba_offset(self):
return self.pba_offset_z << 3
def parse_msix(buf, cap_ptr):
return MSIX.from_buffer_copy(buf, cap_ptr)
# Module API
capability_parsers = {
0x1: parse_power_management,
0x5: parse_msi,
0x11: parse_msix,
}
def capabilities(data, cap_ptr):
buf = ctypes.create_string_buffer(data, len(data))
acc = list()
while cap_ptr != 0:
caplist = CapabilityListRegister.from_buffer_copy(buf, cap_ptr)
if caplist.id in capability_parsers.keys():
acc.append(capability_parsers[caplist.id](buf, cap_ptr))
else:
acc.append(caplist)
cap_ptr = caplist.next_cap_ptr
return acc

View File

@ -0,0 +1,189 @@
# Copyright (C) 2021 Intel Corporation. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
import ctypes
import copy
import lib.cdata as cdata
class Common(cdata.Struct):
_pack_ = 1
_fields_ = [
('vendor_id', ctypes.c_uint16),
('device_id', ctypes.c_uint16),
('command', ctypes.c_uint16),
('status', ctypes.c_uint16),
('revision_id', ctypes.c_uint32, 8),
('class_code', ctypes.c_uint32, 24),
('cacheline_size', ctypes.c_uint8),
('latency_timer', ctypes.c_uint8),
('header_type', ctypes.c_uint8, 7),
('multi_function', ctypes.c_uint8, 1),
('bist', ctypes.c_uint8),
]
class MemoryBar32(cdata.Struct):
_pack_ = 1
_fields_ = [
('indicator', ctypes.c_uint8, 1),
('type', ctypes.c_uint8, 2),
('prefetchable', ctypes.c_uint8, 1),
('base_z', ctypes.c_uint32, 28),
]
resource_type = "memory"
@property
def base(self):
return self.base_z << 4
class MemoryBar64(cdata.Struct):
_pack_ = 1
_fields_ = [
('indicator', ctypes.c_uint8, 1),
('type', ctypes.c_uint8, 2),
('prefetchable', ctypes.c_uint8, 1),
('base_z', ctypes.c_uint64, 60),
]
resource_type = "memory"
@property
def base(self):
return self.base_z << 4
class IOBar(cdata.Struct):
_pack_ = 1
_fields_ = [
('indicator', ctypes.c_uint8, 1),
('reserved', ctypes.c_uint8, 1),
('base_z', ctypes.c_uint32, 30),
]
resource_type = "io_port"
@property
def base(self):
return self.base_z << 2
PCIE_BAR_SPACE_MASK = 0x1
PCIE_BAR_MEMORY_SPACE = 0x0
PCIE_BAR_IO_SPACE = 0x1
PCIE_BAR_TYPE_MASK = 0x6
PCIE_BAR_TYPE_32_BIT = 0x0
PCIE_BAR_TYPE_64_BIT = 0x4
def header_type_0_field_list(addr):
bar_list = list()
bar_addr = addr
bar_end = addr + 0x18
while bar_addr < bar_end:
bar = ctypes.c_uint32.from_address(bar_addr).value
idx = int((bar_addr - addr) / 4)
if (bar & PCIE_BAR_SPACE_MASK) == PCIE_BAR_MEMORY_SPACE:
if (bar & PCIE_BAR_TYPE_MASK) == PCIE_BAR_TYPE_64_BIT:
bar_list.append((f"bar{idx}", MemoryBar64))
bar_addr += 0x8
else:
bar_list.append((f"bar{idx}", MemoryBar32))
bar_addr += 0x4
else:
bar_list.append((f"bar{idx}", IOBar))
bar_addr += 0x4
class Bars(cdata.Struct):
_pack_ = 1
_fields_ = bar_list
def __iter__(self):
for f in self._fields_:
yield getattr(self, f[0])
return [
('bars', Bars),
('cardbus_cis_pointer', ctypes.c_uint32),
('subsystem_vendor_id', ctypes.c_uint16),
('subsystem_device_id', ctypes.c_uint16),
('expansion_rom_base_address', ctypes.c_uint32),
('capability_pointer', ctypes.c_uint8),
('reserved', ctypes.c_uint8 * 7),
('interrupt_line', ctypes.c_uint8),
('interrupt_pin', ctypes.c_uint8),
('min_gnt', ctypes.c_uint8),
('max_lat', ctypes.c_uint8),
]
def header_type_1_field_list(addr):
bar_list = list()
bar_addr = addr
bar_end = addr + 0x08
while bar_addr < bar_end:
bar = ctypes.c_uint32.from_address(addr).value
idx = int((bar_addr - addr) / 4)
if (bar & PCIE_BAR_SPACE_MASK) == PCIE_BAR_MEMORY_SPACE:
if (bar & PCIE_BAR_TYPE_MASK) == PCIE_BAR_TYPE_64_BIT:
bar_list.append((f"bar{idx}", MemoryBar64))
bar_addr += 0x8
else:
bar_list.append((f"bar{idx}", MemoryBar32))
bar_addr += 0x4
else:
bar_list.append((f"bar{idx}", IOBar))
bar_addr += 0x4
class Bars(cdata.Struct):
_pack_ = 1
_fields_ = bar_list
def __iter__(self):
for f in self._fields_:
yield getattr(self, f[0])
return [
('bars', Bars),
('primary_bus_number', ctypes.c_uint8),
('secondary_bus_number', ctypes.c_uint8),
('subordinate_bus_number', ctypes.c_uint8),
('secondary_latency_timer', ctypes.c_uint8),
('io_base', ctypes.c_uint8),
('io_limit', ctypes.c_uint8),
('secondary_status', ctypes.c_uint16),
('memory_base', ctypes.c_uint16),
('memory_limit', ctypes.c_uint16),
('prefetchable_memory_base', ctypes.c_uint16),
('prefetchable_memory_limit', ctypes.c_uint16),
('prefetchable_base_upper_32_bits', ctypes.c_uint32),
('prefetchable_limit_upper_32_bits', ctypes.c_uint32),
('io_base_upper_16_bits', ctypes.c_uint16),
('io_limit_upper_16_bits', ctypes.c_uint16),
('capability_pointer', ctypes.c_uint8),
('reserved', ctypes.c_uint8 * 3),
('expansion_rom_base_address', ctypes.c_uint32),
('interrupt_line', ctypes.c_uint8),
('interrupt_pin', ctypes.c_uint8),
('bridge_control', ctypes.c_uint16),
]
def header_field_list(addr):
common_header = Common.from_address(addr)
if common_header.header_type == 0x00:
return header_type_0_field_list(addr + ctypes.sizeof(Common))
elif common_header.header_type == 0x01:
return header_type_1_field_list(addr + ctypes.sizeof(Common))
else:
return [('data', ctypes.c_uint8 * 0x30)]
def header_factory(field_list):
class Header(cdata.Struct):
_pack_ = 1
_fields_ = copy.copy(Common._fields_) + field_list
return Header
def header(data):
"""Create class based on decode of a PCI configuration space header from raw data."""
buf = ctypes.create_string_buffer(data, len(data))
addr = ctypes.addressof(buf)
field_list = header_field_list(addr)
return header_factory(field_list).from_buffer_copy(data)