Revision 595d153dd1022392083ac93a1550382cbee127e0 authored by Michael Ellerman on 26 May 2020, 06:18:08 UTC, committed by Michael Ellerman on 26 May 2020, 07:32:37 UTC
Commit 702f09805222 ("powerpc/64s/exception: Remove lite interrupt return") changed the interrupt return path to not restore non-volatile registers by default, and explicitly restore them in paths where it is required. But it missed that the facility unavailable exception can sometimes modify user registers, ie. when it does emulation of move from DSCR. This is seen as a failure of the dscr_sysfs_thread_test: test: dscr_sysfs_thread_test [cpu 0] User DSCR should be 1 but is 0 failure: dscr_sysfs_thread_test So restore non-volatile GPRs after facility unavailable exceptions. Currently the hypervisor facility unavailable exception is also wired up to call facility_unavailable_exception(). In practice we should never take a hypervisor facility unavailable exception for the DSCR. On older bare metal systems we set HFSCR_DSCR unconditionally in __init_HFSCR, or on newer systems it should be enabled via the "data-stream-control-register" device tree CPU feature. Even if it's not, since commit f3c99f97a3cd ("KVM: PPC: Book3S HV: Don't access HFSCR, LPIDR or LPCR when running nested"), the KVM code has unconditionally set HFSCR_DSCR when running guests. So we should only get a hypervisor facility unavailable for the DSCR if skiboot has disabled the "data-stream-control-register" feature, and we are somehow in guest context but not via KVM. Given all that, it should be unnecessary to add a restore of non-volatile GPRs after the hypervisor facility exception, because we never expect to hit that path. But equally we may as well add the restore, because we never expect to hit that path, and if we ever did, at least we would correctly restore the registers to their post emulation state. In future we can split the non-HV and HV facility unavailable handling so that there is no emulation in the HV handler, and then remove the restore for the HV case. Fixes: 702f09805222 ("powerpc/64s/exception: Remove lite interrupt return") Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/20200526061808.2472279-1-mpe@ellerman.id.au
1 parent 8659a0e
checkkconfigsymbols.py
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
"""Find Kconfig symbols that are referenced but not defined."""
# (c) 2014-2017 Valentin Rothberg <valentinrothberg@gmail.com>
# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de>
#
import argparse
import difflib
import os
import re
import signal
import subprocess
import sys
from multiprocessing import Pool, cpu_count
# regex expressions
OPERATORS = r"&|\(|\)|\||\!"
SYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}"
DEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + r")\s*"
EXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + r")+"
DEFAULT = r"default\s+.*?(?:if\s.+){,1}"
STMT = r"^\s*(?:if|select|imply|depends\s+on|(?:" + DEFAULT + r"))\s+" + EXPR
SOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + SYMBOL + r")"
# regex objects
REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$")
REGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + r'(?!\B)')
REGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL)
REGEX_KCONFIG_DEF = re.compile(DEF)
REGEX_KCONFIG_EXPR = re.compile(EXPR)
REGEX_KCONFIG_STMT = re.compile(STMT)
REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$")
REGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$")
REGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+")
REGEX_QUOTES = re.compile("(\"(.*?)\")")
def parse_options():
"""The user interface of this module."""
usage = "Run this tool to detect Kconfig symbols that are referenced but " \
"not defined in Kconfig. If no option is specified, " \
"checkkconfigsymbols defaults to check your current tree. " \
"Please note that specifying commits will 'git reset --hard\' " \
"your current tree! You may save uncommitted changes to avoid " \
"losing data."
parser = argparse.ArgumentParser(description=usage)
parser.add_argument('-c', '--commit', dest='commit', action='store',
default="",
help="check if the specified commit (hash) introduces "
"undefined Kconfig symbols")
parser.add_argument('-d', '--diff', dest='diff', action='store',
default="",
help="diff undefined symbols between two commits "
"(e.g., -d commmit1..commit2)")
parser.add_argument('-f', '--find', dest='find', action='store_true',
default=False,
help="find and show commits that may cause symbols to be "
"missing (required to run with --diff)")
parser.add_argument('-i', '--ignore', dest='ignore', action='store',
default="",
help="ignore files matching this Python regex "
"(e.g., -i '.*defconfig')")
parser.add_argument('-s', '--sim', dest='sim', action='store', default="",
help="print a list of max. 10 string-similar symbols")
parser.add_argument('--force', dest='force', action='store_true',
default=False,
help="reset current Git tree even when it's dirty")
parser.add_argument('--no-color', dest='color', action='store_false',
default=True,
help="don't print colored output (default when not "
"outputting to a terminal)")
args = parser.parse_args()
if args.commit and args.diff:
sys.exit("Please specify only one option at once.")
if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff):
sys.exit("Please specify valid input in the following format: "
"\'commit1..commit2\'")
if args.commit or args.diff:
if not args.force and tree_is_dirty():
sys.exit("The current Git tree is dirty (see 'git status'). "
"Running this script may\ndelete important data since it "
"calls 'git reset --hard' for some performance\nreasons. "
" Please run this script in a clean Git tree or pass "
"'--force' if you\nwant to ignore this warning and "
"continue.")
if args.commit:
args.find = False
if args.ignore:
try:
re.match(args.ignore, "this/is/just/a/test.c")
except:
sys.exit("Please specify a valid Python regex.")
return args
def main():
"""Main function of this module."""
args = parse_options()
global COLOR
COLOR = args.color and sys.stdout.isatty()
if args.sim and not args.commit and not args.diff:
sims = find_sims(args.sim, args.ignore)
if sims:
print("%s: %s" % (yel("Similar symbols"), ', '.join(sims)))
else:
print("%s: no similar symbols found" % yel("Similar symbols"))
sys.exit(0)
# dictionary of (un)defined symbols
defined = {}
undefined = {}
if args.commit or args.diff:
head = get_head()
# get commit range
commit_a = None
commit_b = None
if args.commit:
commit_a = args.commit + "~"
commit_b = args.commit
elif args.diff:
split = args.diff.split("..")
commit_a = split[0]
commit_b = split[1]
undefined_a = {}
undefined_b = {}
# get undefined items before the commit
reset(commit_a)
undefined_a, _ = check_symbols(args.ignore)
# get undefined items for the commit
reset(commit_b)
undefined_b, defined = check_symbols(args.ignore)
# report cases that are present for the commit but not before
for symbol in sorted(undefined_b):
# symbol has not been undefined before
if symbol not in undefined_a:
files = sorted(undefined_b.get(symbol))
undefined[symbol] = files
# check if there are new files that reference the undefined symbol
else:
files = sorted(undefined_b.get(symbol) -
undefined_a.get(symbol))
if files:
undefined[symbol] = files
# reset to head
reset(head)
# default to check the entire tree
else:
undefined, defined = check_symbols(args.ignore)
# now print the output
for symbol in sorted(undefined):
print(red(symbol))
files = sorted(undefined.get(symbol))
print("%s: %s" % (yel("Referencing files"), ", ".join(files)))
sims = find_sims(symbol, args.ignore, defined)
sims_out = yel("Similar symbols")
if sims:
print("%s: %s" % (sims_out, ', '.join(sims)))
else:
print("%s: %s" % (sims_out, "no similar symbols found"))
if args.find:
print("%s:" % yel("Commits changing symbol"))
commits = find_commits(symbol, args.diff)
if commits:
for commit in commits:
commit = commit.split(" ", 1)
print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1]))
else:
print("\t- no commit found")
print() # new line
def reset(commit):
"""Reset current git tree to %commit."""
execute(["git", "reset", "--hard", commit])
def yel(string):
"""
Color %string yellow.
"""
return "\033[33m%s\033[0m" % string if COLOR else string
def red(string):
"""
Color %string red.
"""
return "\033[31m%s\033[0m" % string if COLOR else string
def execute(cmd):
"""Execute %cmd and return stdout. Exit in case of error."""
try:
stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False)
stdout = stdout.decode(errors='replace')
except subprocess.CalledProcessError as fail:
exit(fail)
return stdout
def find_commits(symbol, diff):
"""Find commits changing %symbol in the given range of %diff."""
commits = execute(["git", "log", "--pretty=oneline",
"--abbrev-commit", "-G",
symbol, diff])
return [x for x in commits.split("\n") if x]
def tree_is_dirty():
"""Return true if the current working tree is dirty (i.e., if any file has
been added, deleted, modified, renamed or copied but not committed)."""
stdout = execute(["git", "status", "--porcelain"])
for line in stdout:
if re.findall(r"[URMADC]{1}", line[:2]):
return True
return False
def get_head():
"""Return commit hash of current HEAD."""
stdout = execute(["git", "rev-parse", "HEAD"])
return stdout.strip('\n')
def partition(lst, size):
"""Partition list @lst into eveni-sized lists of size @size."""
return [lst[i::size] for i in range(size)]
def init_worker():
"""Set signal handler to ignore SIGINT."""
signal.signal(signal.SIGINT, signal.SIG_IGN)
def find_sims(symbol, ignore, defined=[]):
"""Return a list of max. ten Kconfig symbols that are string-similar to
@symbol."""
if defined:
return difflib.get_close_matches(symbol, set(defined), 10)
pool = Pool(cpu_count(), init_worker)
kfiles = []
for gitfile in get_files():
if REGEX_FILE_KCONFIG.match(gitfile):
kfiles.append(gitfile)
arglist = []
for part in partition(kfiles, cpu_count()):
arglist.append((part, ignore))
for res in pool.map(parse_kconfig_files, arglist):
defined.extend(res[0])
return difflib.get_close_matches(symbol, set(defined), 10)
def get_files():
"""Return a list of all files in the current git directory."""
# use 'git ls-files' to get the worklist
stdout = execute(["git", "ls-files"])
if len(stdout) > 0 and stdout[-1] == "\n":
stdout = stdout[:-1]
files = []
for gitfile in stdout.rsplit("\n"):
if ".git" in gitfile or "ChangeLog" in gitfile or \
".log" in gitfile or os.path.isdir(gitfile) or \
gitfile.startswith("tools/"):
continue
files.append(gitfile)
return files
def check_symbols(ignore):
"""Find undefined Kconfig symbols and return a dict with the symbol as key
and a list of referencing files as value. Files matching %ignore are not
checked for undefined symbols."""
pool = Pool(cpu_count(), init_worker)
try:
return check_symbols_helper(pool, ignore)
except KeyboardInterrupt:
pool.terminate()
pool.join()
sys.exit(1)
def check_symbols_helper(pool, ignore):
"""Helper method for check_symbols(). Used to catch keyboard interrupts in
check_symbols() in order to properly terminate running worker processes."""
source_files = []
kconfig_files = []
defined_symbols = []
referenced_symbols = dict() # {file: [symbols]}
for gitfile in get_files():
if REGEX_FILE_KCONFIG.match(gitfile):
kconfig_files.append(gitfile)
else:
if ignore and not re.match(ignore, gitfile):
continue
# add source files that do not match the ignore pattern
source_files.append(gitfile)
# parse source files
arglist = partition(source_files, cpu_count())
for res in pool.map(parse_source_files, arglist):
referenced_symbols.update(res)
# parse kconfig files
arglist = []
for part in partition(kconfig_files, cpu_count()):
arglist.append((part, ignore))
for res in pool.map(parse_kconfig_files, arglist):
defined_symbols.extend(res[0])
referenced_symbols.update(res[1])
defined_symbols = set(defined_symbols)
# inverse mapping of referenced_symbols to dict(symbol: [files])
inv_map = dict()
for _file, symbols in referenced_symbols.items():
for symbol in symbols:
inv_map[symbol] = inv_map.get(symbol, set())
inv_map[symbol].add(_file)
referenced_symbols = inv_map
undefined = {} # {symbol: [files]}
for symbol in sorted(referenced_symbols):
# filter some false positives
if symbol == "FOO" or symbol == "BAR" or \
symbol == "FOO_BAR" or symbol == "XXX":
continue
if symbol not in defined_symbols:
if symbol.endswith("_MODULE"):
# avoid false positives for kernel modules
if symbol[:-len("_MODULE")] in defined_symbols:
continue
undefined[symbol] = referenced_symbols.get(symbol)
return undefined, defined_symbols
def parse_source_files(source_files):
"""Parse each source file in @source_files and return dictionary with source
files as keys and lists of references Kconfig symbols as values."""
referenced_symbols = dict()
for sfile in source_files:
referenced_symbols[sfile] = parse_source_file(sfile)
return referenced_symbols
def parse_source_file(sfile):
"""Parse @sfile and return a list of referenced Kconfig symbols."""
lines = []
references = []
if not os.path.exists(sfile):
return references
with open(sfile, "r", encoding='utf-8', errors='replace') as stream:
lines = stream.readlines()
for line in lines:
if "CONFIG_" not in line:
continue
symbols = REGEX_SOURCE_SYMBOL.findall(line)
for symbol in symbols:
if not REGEX_FILTER_SYMBOLS.search(symbol):
continue
references.append(symbol)
return references
def get_symbols_in_line(line):
"""Return mentioned Kconfig symbols in @line."""
return REGEX_SYMBOL.findall(line)
def parse_kconfig_files(args):
"""Parse kconfig files and return tuple of defined and references Kconfig
symbols. Note, @args is a tuple of a list of files and the @ignore
pattern."""
kconfig_files = args[0]
ignore = args[1]
defined_symbols = []
referenced_symbols = dict()
for kfile in kconfig_files:
defined, references = parse_kconfig_file(kfile)
defined_symbols.extend(defined)
if ignore and re.match(ignore, kfile):
# do not collect references for files that match the ignore pattern
continue
referenced_symbols[kfile] = references
return (defined_symbols, referenced_symbols)
def parse_kconfig_file(kfile):
"""Parse @kfile and update symbol definitions and references."""
lines = []
defined = []
references = []
skip = False
if not os.path.exists(kfile):
return defined, references
with open(kfile, "r", encoding='utf-8', errors='replace') as stream:
lines = stream.readlines()
for i in range(len(lines)):
line = lines[i]
line = line.strip('\n')
line = line.split("#")[0] # ignore comments
if REGEX_KCONFIG_DEF.match(line):
symbol_def = REGEX_KCONFIG_DEF.findall(line)
defined.append(symbol_def[0])
skip = False
elif REGEX_KCONFIG_HELP.match(line):
skip = True
elif skip:
# ignore content of help messages
pass
elif REGEX_KCONFIG_STMT.match(line):
line = REGEX_QUOTES.sub("", line)
symbols = get_symbols_in_line(line)
# multi-line statements
while line.endswith("\\"):
i += 1
line = lines[i]
line = line.strip('\n')
symbols.extend(get_symbols_in_line(line))
for symbol in set(symbols):
if REGEX_NUMERIC.match(symbol):
# ignore numeric values
continue
references.append(symbol)
return defined, references
if __name__ == "__main__":
main()
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...