diff --git a/misc/config_tools/board_inspector/pcieparser/__init__.py b/misc/config_tools/board_inspector/pcieparser/__init__.py new file mode 100644 index 000000000..1e4c30c3b --- /dev/null +++ b/misc/config_tools/board_inspector/pcieparser/__init__.py @@ -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 diff --git a/misc/config_tools/board_inspector/pcieparser/caps.py b/misc/config_tools/board_inspector/pcieparser/caps.py new file mode 100644 index 000000000..5326f63a9 --- /dev/null +++ b/misc/config_tools/board_inspector/pcieparser/caps.py @@ -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 diff --git a/misc/config_tools/board_inspector/pcieparser/header.py b/misc/config_tools/board_inspector/pcieparser/header.py new file mode 100644 index 000000000..34f7e45a4 --- /dev/null +++ b/misc/config_tools/board_inspector/pcieparser/header.py @@ -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)