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