xref: /dpdk/buildtools/pmdinfogen.py (revision daa02b5cddbb8e11b31d41e2bf7bb1ae64dcae2f)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-3-Clause
3# Copyright (c) 2016 Neil Horman <nhorman@tuxdriver.com>
4# Copyright (c) 2020 Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>
5
6import argparse
7import ctypes
8import json
9import sys
10import tempfile
11
12try:
13    import elftools
14    from elftools.elf.elffile import ELFFile
15    from elftools.elf.sections import SymbolTableSection
16except ImportError:
17    pass
18
19import coff
20
21
22class ELFSymbol:
23    def __init__(self, image, symbol):
24        self._image = image
25        self._symbol = symbol
26
27    @property
28    def string_value(self):
29        size = self._symbol["st_size"]
30        value = self.get_value(0, size)
31        return coff.decode_asciiz(value)  # not COFF-specific
32
33    def get_value(self, offset, size):
34        section = self._symbol["st_shndx"]
35        data = self._image.get_section(section).data()
36        base = self._symbol["st_value"] + offset
37        return data[base : base + size]
38
39
40class ELFImage:
41    def __init__(self, data):
42        version = tuple(int(c) for c in elftools.__version__.split("."))
43        self._legacy_elftools = version < (0, 24)
44
45        self._image = ELFFile(data)
46
47        section = b".symtab" if self._legacy_elftools else ".symtab"
48        self._symtab = self._image.get_section_by_name(section)
49        if not isinstance(self._symtab, SymbolTableSection):
50            raise Exception(".symtab section is not a symbol table")
51
52    @property
53    def is_big_endian(self):
54        return not self._image.little_endian
55
56    def find_by_name(self, name):
57        symbol = self._get_symbol_by_name(name)
58        return ELFSymbol(self._image, symbol[0]) if symbol else None
59
60    def _get_symbol_by_name(self, name):
61        if not self._legacy_elftools:
62            return self._symtab.get_symbol_by_name(name)
63        name = name.encode("utf-8")
64        for symbol in self._symtab.iter_symbols():
65            if symbol.name == name:
66                return [symbol]
67        return None
68
69    def find_by_prefix(self, prefix):
70        prefix = prefix.encode("utf-8") if self._legacy_elftools else prefix
71        for i in range(self._symtab.num_symbols()):
72            symbol = self._symtab.get_symbol(i)
73            if symbol.name.startswith(prefix):
74                yield ELFSymbol(self._image, symbol)
75
76
77class COFFSymbol:
78    def __init__(self, image, symbol):
79        self._image = image
80        self._symbol = symbol
81
82    def get_value(self, offset, size):
83        value = self._symbol.get_value(offset)
84        return value[:size] if value else value
85
86    @property
87    def string_value(self):
88        value = self._symbol.get_value(0)
89        return coff.decode_asciiz(value) if value else ''
90
91
92class COFFImage:
93    def __init__(self, data):
94        self._image = coff.Image(data)
95
96    @property
97    def is_big_endian(self):
98        return False
99
100    def find_by_prefix(self, prefix):
101        for symbol in self._image.symbols:
102            if symbol.name.startswith(prefix):
103                yield COFFSymbol(self._image, symbol)
104
105    def find_by_name(self, name):
106        for symbol in self._image.symbols:
107            if symbol.name == name:
108                return COFFSymbol(self._image, symbol)
109        return None
110
111
112def define_rte_pci_id(is_big_endian):
113    base_type = ctypes.LittleEndianStructure
114    if is_big_endian:
115        base_type = ctypes.BigEndianStructure
116
117    class rte_pci_id(base_type):
118        _pack_ = True
119        _fields_ = [
120            ("class_id", ctypes.c_uint32),
121            ("vendor_id", ctypes.c_uint16),
122            ("device_id", ctypes.c_uint16),
123            ("subsystem_vendor_id", ctypes.c_uint16),
124            ("subsystem_device_id", ctypes.c_uint16),
125        ]
126
127    return rte_pci_id
128
129
130class Driver:
131    OPTIONS = [
132        ("params", "_param_string_export"),
133        ("kmod", "_kmod_dep_export"),
134    ]
135
136    def __init__(self, name, options):
137        self.name = name
138        for key, value in options.items():
139            setattr(self, key, value)
140        self.pci_ids = []
141
142    @classmethod
143    def load(cls, image, symbol):
144        name = symbol.string_value
145
146        options = {}
147        for key, suffix in cls.OPTIONS:
148            option_symbol = image.find_by_name("__%s%s" % (name, suffix))
149            if option_symbol:
150                value = option_symbol.string_value
151                options[key] = value
152
153        driver = cls(name, options)
154
155        pci_table_name_symbol = image.find_by_name("__%s_pci_tbl_export" % name)
156        if pci_table_name_symbol:
157            driver.pci_ids = cls._load_pci_ids(image, pci_table_name_symbol)
158
159        return driver
160
161    @staticmethod
162    def _load_pci_ids(image, table_name_symbol):
163        table_name = table_name_symbol.string_value
164        table_symbol = image.find_by_name(table_name)
165        if not table_symbol:
166            raise Exception("PCI table declared but not defined: %d" % table_name)
167
168        rte_pci_id = define_rte_pci_id(image.is_big_endian)
169
170        result = []
171        while True:
172            size = ctypes.sizeof(rte_pci_id)
173            offset = size * len(result)
174            data = table_symbol.get_value(offset, size)
175            if not data:
176                break
177            pci_id = rte_pci_id.from_buffer_copy(data)
178            if not pci_id.device_id:
179                break
180            result.append(
181                [
182                    pci_id.vendor_id,
183                    pci_id.device_id,
184                    pci_id.subsystem_vendor_id,
185                    pci_id.subsystem_device_id,
186                ]
187            )
188        return result
189
190    def dump(self, file):
191        dumped = json.dumps(self.__dict__)
192        escaped = dumped.replace('"', '\\"')
193        print(
194            'const char %s_pmd_info[] __attribute__((used)) = "PMD_INFO_STRING= %s";'
195            % (self.name, escaped),
196            file=file,
197        )
198
199
200def load_drivers(image):
201    drivers = []
202    for symbol in image.find_by_prefix("this_pmd_name"):
203        drivers.append(Driver.load(image, symbol))
204    return drivers
205
206
207def dump_drivers(drivers, file):
208    # Keep legacy order of definitions.
209    for driver in reversed(drivers):
210        driver.dump(file)
211
212
213def parse_args():
214    parser = argparse.ArgumentParser()
215    parser.add_argument("format", help="object file format, 'elf' or 'coff'")
216    parser.add_argument(
217        "input", nargs='+', help="input object file path or '-' for stdin"
218    )
219    parser.add_argument("output", help="output C file path or '-' for stdout")
220    return parser.parse_args()
221
222
223def open_input(path):
224    if path == "-":
225        temp = tempfile.TemporaryFile()
226        temp.write(sys.stdin.buffer.read())
227        return temp
228    return open(path, "rb")
229
230
231def read_input(path):
232    if path == "-":
233        return sys.stdin.buffer.read()
234    with open(path, "rb") as file:
235        return file.read()
236
237
238def load_image(fmt, path):
239    if fmt == "elf":
240        return ELFImage(open_input(path))
241    if fmt == "coff":
242        return COFFImage(read_input(path))
243    raise Exception("unsupported object file format")
244
245
246def open_output(path):
247    if path == "-":
248        return sys.stdout
249    return open(path, "w")
250
251
252def write_header(output):
253    output.write(
254        "static __attribute__((unused)) const char *generator = \"%s\";\n" % sys.argv[0]
255    )
256
257
258def main():
259    args = parse_args()
260    if args.input.count('-') > 1:
261        raise Exception("'-' input cannot be used multiple times")
262    if args.format == "elf" and "ELFFile" not in globals():
263        raise Exception("elftools module not found")
264
265    output = open_output(args.output)
266    write_header(output)
267    for path in args.input:
268        image = load_image(args.format, path)
269        drivers = load_drivers(image)
270        dump_drivers(drivers, output)
271
272
273if __name__ == "__main__":
274    main()
275