summaryrefslogtreecommitdiff
path: root/packages/linux-kernel/list-required-firmware.py
blob: 1cbf7b8e89f15cc2c8993302e7c776a315e3f806 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env python3
#
# Copyright (C) 2020 Daniil Baturin
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import re
import os
import sys
import glob
import argparse
import subprocess

# Loads the kernel config -- only options set to y or m
def load_config(path):
    with open(path, 'r') as f:
        config = f.read()
    targets = re.findall(r'(.*)=(?:y|m)', config)
    return targets

# Finds subdir targets from the Makefile
# that are enabled by the kernel build config
def find_enabled_subdirs(config, makefile_path):
    try:
        with open(makefile_path, 'r') as f:
            makefile = f.read()
    except OSError:
        # Shouldn't happen due to the way collect_source_files()
        # calls this function.
        return []

    dir_stmts = re.findall(r'obj-\$\((.*)\)\s+\+=\s+(.*)/(?:\n|$)', makefile)
    subdirs = []

    for ds in dir_stmts:
        config_key, src_dir = ds

        if args.debug:
            print("Processing make targets from {0} ({1})".format(ds[1], ds[0]), file=sys.stderr)
        if config_key in config:
            subdirs.append(src_dir)
        elif args.debug:
            print("{0} is disabled in the config, ignoring {1}".format(ds[0], ds[1]), file=sys.stderr)

    return subdirs

# For filtering
def file_loads_firmware(file):
    with open(file, 'r') as f:
        source = f.read()
    if re.search(r'MODULE_FIRMWARE\((.*)\)', source):
        return True

# Find all source files that reference firmware
def collect_source_files(config, path):
    files = []

    makefile = os.path.join(path, "Makefile")

    # Find and process all C files in this directory
    # This is a compromise: sometimes there are single-file modules,
    # that in fact may be disabled in the config,
    # so this approach can create occasional false positives.
    c_files = glob.glob("{0}/*.c".format(path))
    files = list(filter(file_loads_firmware, c_files))

    # Now walk the subdirectories
    enabled_subdirs = find_enabled_subdirs(config, makefile)
    subdirs = glob.glob("{0}/*/".format(path))
    for d in subdirs:
        dir_name = d.rstrip("/")

        if os.path.exists(os.path.join(d, "Makefile")):
            # If there's a makefile, it's an independent module
            # or a high level dir
            if os.path.basename(dir_name) in enabled_subdirs:
                files = files + collect_source_files(config, d)
        else:
            # It's simply a subdirectory of the current module
            # Some modules, like iwlwifi, keep their firmware-loading files
            # in subdirs, so we have to handle this case
            c_files = glob.iglob("{0}/**/*.c".format(d), recursive=True)
            files += list(filter(file_loads_firmware, c_files))

    return files

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-s", "--source-dir", action="append", help="Kernel source directory to process", required=True)
    parser.add_argument("-c", "--kernel-config", action="store", help="Kernel configuration")
    parser.add_argument("-d", "--debug", action="store_true", help="Enable Debug output")
    parser.add_argument("-f", "--list-source-files", action="store_true", help="List source files that reference firmware and exit")
    args = parser.parse_args()

    if not args.kernel_config:
        args.kernel_config = ".config"

    config = load_config(args.kernel_config)

    # Collect source files that reference firmware
    for directory in args.source_dir:
        source_files = collect_source_files(config, directory)

    if args.list_source_files:
        for sf in source_files:
            print(sf)
    else:
        fw_files = []
        for sf in source_files:
            i_file = re.sub(r'\.c', r'.i', sf)
            res = subprocess.run(["make {0} 2>&1".format(i_file)], shell=True, capture_output=True)
            if res.returncode != 0:
                print("Failed to preprocess file {0}".format(sf), file=sys.stderr)
                print(res.stdout.decode(), file=sys.stderr)
            else:
                with open(i_file, 'r') as f:
                    source = f.read()
                    fw_statements = re.findall(r'__UNIQUE_ID_firmware.*"firmware"\s+"="\s+(.*);', source)
                    fw_files += list(map(lambda s: re.sub(r'(\s|")', r'', s), fw_statements))

        for fw in fw_files:
            print(fw)