diff options
Diffstat (limited to 'scripts')
80 files changed, 3005 insertions, 212 deletions
diff --git a/scripts/Makefile.autofdo b/scripts/Makefile.autofdo index 1caf2457e585..1442043da139 100644 --- a/scripts/Makefile.autofdo +++ b/scripts/Makefile.autofdo @@ -3,14 +3,18 @@ # Enable available and selected Clang AutoFDO features. CFLAGS_AUTOFDO_CLANG := -fdebug-info-for-profiling -mllvm -enable-fs-discriminator=true -mllvm -improved-fs-discriminator=true +RUSTFLAGS_AUTOFDO_CLANG := $(if $(call rustc-min-version,109800),-Zdebuginfo-for-profiling,-Zdebug-info-for-profiling) -Cllvm-args=-enable-fs-discriminator=true -Cllvm-args=-improved-fs-discriminator=true ifndef CONFIG_DEBUG_INFO CFLAGS_AUTOFDO_CLANG += -gmlt + RUSTFLAGS_AUTOFDO_CLANG += -Cdebuginfo=line-tables-only endif ifdef CLANG_AUTOFDO_PROFILE CFLAGS_AUTOFDO_CLANG += -fprofile-sample-use=$(CLANG_AUTOFDO_PROFILE) -ffunction-sections CFLAGS_AUTOFDO_CLANG += -fsplit-machine-functions + RUSTFLAGS_AUTOFDO_CLANG += -Zprofile-sample-use=$(CLANG_AUTOFDO_PROFILE) -Zfunction-sections=y + RUSTFLAGS_AUTOFDO_CLANG += -Cllvm-args=-split-machine-functions endif ifdef CONFIG_LTO_CLANG_THIN @@ -21,4 +25,4 @@ ifdef CONFIG_LTO_CLANG_THIN KBUILD_LDFLAGS += -plugin-opt=-split-machine-functions endif -export CFLAGS_AUTOFDO_CLANG +export CFLAGS_AUTOFDO_CLANG RUSTFLAGS_AUTOFDO_CLANG diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 3498d25b15e8..911745743246 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -329,6 +329,7 @@ rust_common_cmd = \ -Zcrate-attr=no_std \ -Zcrate-attr='feature($(rust_allowed_features))' \ -Zunstable-options --extern pin_init --extern kernel \ + --extern zerocopy --extern zerocopy_derive \ --crate-type rlib -L $(objtree)/rust/ \ --sysroot=/dev/null \ --out-dir $(dir $@) --emit=dep-info=$(depfile) diff --git a/scripts/Makefile.kasan b/scripts/Makefile.kasan index 0ba2aac3b8dc..91504e81247a 100644 --- a/scripts/Makefile.kasan +++ b/scripts/Makefile.kasan @@ -71,8 +71,6 @@ ifdef CONFIG_KASAN_SW_TAGS CFLAGS_KASAN := -fsanitize=kernel-hwaddress -# This sets flags that will enable SW_TAGS KASAN once enabled in Rust. These -# will not work today, and is guarded against in dependencies for CONFIG_RUST. RUSTFLAGS_KASAN := -Zsanitizer=kernel-hwaddress \ -Zsanitizer-recover=kernel-hwaddress diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 0718e39cedda..0a4fdd8bd975 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -123,6 +123,9 @@ ifeq ($(CONFIG_AUTOFDO_CLANG),y) _c_flags += $(if $(patsubst n%,, \ $(AUTOFDO_PROFILE_$(target-stem).o)$(AUTOFDO_PROFILE)$(is-kernel-object)), \ $(CFLAGS_AUTOFDO_CLANG)) +_rust_flags += $(if $(patsubst n%,, \ + $(AUTOFDO_PROFILE_$(target-stem).o)$(AUTOFDO_PROFILE)$(is-kernel-object)), \ + $(RUSTFLAGS_AUTOFDO_CLANG)) endif # @@ -187,7 +190,11 @@ objtool-args-$(CONFIG_HAVE_JUMP_LABEL_HACK) += --hacks=jump_label objtool-args-$(CONFIG_HAVE_NOINSTR_HACK) += --hacks=noinstr objtool-args-$(CONFIG_MITIGATION_CALL_DEPTH_TRACKING) += --hacks=skylake objtool-args-$(CONFIG_X86_KERNEL_IBT) += --ibt -objtool-args-$(CONFIG_FINEIBT) += --cfi +objtool-args-$(CONFIG_CALL_PADDING) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES) +ifdef CONFIG_CALL_PADDING +objtool-args-$(CONFIG_CFI) += --cfi +objtool-args-$(CONFIG_FINEIBT) += --fineibt +endif objtool-args-$(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL) += --mcount ifdef CONFIG_FTRACE_MCOUNT_USE_OBJTOOL objtool-args-$(CONFIG_HAVE_OBJTOOL_NOP_MCOUNT) += --mnop @@ -200,7 +207,6 @@ objtool-args-$(CONFIG_STACK_VALIDATION) += --stackval objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess objtool-args-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable -objtool-args-$(CONFIG_PREFIX_SYMBOLS) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES) objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror objtool-args = $(objtool-args-y) \ @@ -249,6 +255,13 @@ ifdef CONFIG_LTO_CLANG cmd_ld_single = $(if $(objtool-enabled)$(is-single-obj-m), ; $(LD) $(ld_flags) -r -o $(tmp-target) $@; mv $(tmp-target) $@) endif +ifdef CONFIG_LTO_CLANG_THIN_DIST +# Save the _c_flags, sliently. +quiet_cmd_save_c_flags = + saved_c_flags = $(_c_flags) $(modkern_cflags) + cmd_save_c_flags = printf '\n%s\n' 'saved_c_flags_$@ := $(call escsq,$(saved_c_flags))' >> $(dot-target).cmd +endif + quiet_cmd_cc_o_c = CC $(quiet_modtag) $@ cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \ $(cmd_ld_single) \ @@ -256,6 +269,7 @@ quiet_cmd_cc_o_c = CC $(quiet_modtag) $@ define rule_cc_o_c $(call cmd_and_fixdep,cc_o_c) + $(call cmd,save_c_flags) $(call cmd,checksrc) $(call cmd,checkdoc) $(call cmd,gen_objtooldep) diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal index adcbcde16a07..01a37ec872b9 100644 --- a/scripts/Makefile.modfinal +++ b/scripts/Makefile.modfinal @@ -46,17 +46,9 @@ quiet_cmd_btf_ko = BTF [M] $@ $(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/vmlinux $@; \ fi; -# Same as newer-prereqs, but allows to exclude specified extra dependencies -newer_prereqs_except = $(filter-out $(PHONY) $(1),$?) - -# Same as if_changed, but allows to exclude specified extra dependencies -if_changed_except = $(if $(call newer_prereqs_except,$(2))$(cmd-check), \ - $(cmd); \ - printf '%s\n' 'savedcmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:) - # Re-generate module BTFs if either module's .ko or vmlinux changed %.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE - +$(call if_changed_except,ld_ko_o,$(objtree)/vmlinux) + +$(call if_changed,ld_ko_o) ifdef CONFIG_DEBUG_INFO_BTF_MODULES +$(if $(newer-prereqs),$(call cmd,btf_ko)) endif diff --git a/scripts/Makefile.thinlto b/scripts/Makefile.thinlto new file mode 100644 index 000000000000..bb83f13f3cd6 --- /dev/null +++ b/scripts/Makefile.thinlto @@ -0,0 +1,40 @@ +PHONY := __default +__default: + +include include/config/auto.conf +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.lib + +native-objs := $(patsubst %.o,%.thinlto-native.o,$(call read-file, vmlinux.thinlto-index)) + +__default: $(native-objs) + +# Generate .thinlto-native.o (obj) from .o (bitcode) and .thinlto.bc (summary) files +# --------------------------------------------------------------------------- +quiet_cmd_cc_o_bc = CC $(quiet_modtag) $@ + be_flags = $(shell sed -n '/saved_c_flags_/s/.*:= //p' \ + $(dir $(<)).$(notdir $(<)).cmd) + cmd_cc_o_bc = \ + $(CC) $(be_flags) -x ir -fno-lto -Wno-unused-command-line-argument \ + -fthinlto-index=$(word 2, $^) -c -o $@ $< + +targets += $(native-objs) +$(native-objs): %.thinlto-native.o: %.o %.o.thinlto.bc FORCE + $(call if_changed,cc_o_bc) + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f, $(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.vmlinux_a b/scripts/Makefile.vmlinux_a new file mode 100644 index 000000000000..395e29998d7d --- /dev/null +++ b/scripts/Makefile.vmlinux_a @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: GPL-2.0-only + +PHONY := __default +__default: vmlinux.a + +include include/config/auto.conf +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.lib + +# Link of built-in-fixup.a +# --------------------------------------------------------------------------- + +quiet_cmd_ar_builtin_fixup = AR $@ + cmd_ar_builtin_fixup = \ + rm -f $@; \ + $(AR) cDPrST $@ $(KBUILD_VMLINUX_OBJS); \ + $(AR) mPi $$($(AR) t $@ | sed -n 1p) $@ $$($(AR) t $@ | grep -F -f $(srctree)/scripts/head-object-list.txt) + +targets += built-in-fixup.a +built-in-fixup.a: $(KBUILD_VMLINUX_OBJS) scripts/head-object-list.txt FORCE + $(call if_changed,ar_builtin_fixup) + +ifdef CONFIG_LTO_CLANG_THIN_DIST + +quiet_cmd_builtin.order = GEN $@ + cmd_builtin.order = $(AR) t $< > $@ + +targets += builtin.order +builtin.order: built-in-fixup.a FORCE + $(call if_changed,builtin.order) + +quiet_cmd_ld_thinlto_index = LD $@ + cmd_ld_thinlto_index = \ + $(LD) $(KBUILD_LDFLAGS) -r --thinlto-index-only=$@ @$< + +targets += vmlinux.thinlto-index +vmlinux.thinlto-index: builtin.order FORCE + $(call if_changed,ld_thinlto_index) + +quiet_cmd_ar_vmlinux.a = GEN $@ + cmd_ar_vmlinux.a = \ + rm -f $@; \ + while read -r obj; do \ + if grep -Fqx $${obj} $(word 2, $^); then \ + echo $${obj%.o}.thinlto-native.o; \ + else \ + echo $${obj}; \ + fi; \ + done < $< | xargs $(AR) cDPrS --thin $@ + +targets += vmlinux.a +vmlinux.a: builtin.order vmlinux.thinlto-index FORCE + $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.thinlto + $(call if_changed,ar_vmlinux.a) + +else + +# vmlinux.a +# --------------------------------------------------------------------------- + +targets += vmlinux.a +vmlinux.a: built-in-fixup.a FORCE + $(call if_changed,copy) + +endif + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.warn b/scripts/Makefile.warn index e77ca875aea4..35af7d6c6d18 100644 --- a/scripts/Makefile.warn +++ b/scripts/Makefile.warn @@ -135,16 +135,6 @@ KBUILD_CFLAGS += $(call cc-option, -Wno-stringop-truncation) KBUILD_CFLAGS += -Wno-override-init # alias for -Wno-initializer-overrides in clang ifdef CONFIG_CC_IS_CLANG -# Clang before clang-16 would warn on default argument promotions. -ifneq ($(call clang-min-version, 160000),y) -# Disable -Wformat -KBUILD_CFLAGS += -Wno-format -# Then re-enable flags that were part of the -Wformat group that aren't -# problematic. -KBUILD_CFLAGS += -Wformat-extra-args -Wformat-invalid-specifier -KBUILD_CFLAGS += -Wformat-zero-length -Wnonnull -KBUILD_CFLAGS += -Wformat-insufficient-args -endif KBUILD_CFLAGS += -Wno-pointer-to-enum-cast KBUILD_CFLAGS += -Wno-tautological-constant-out-of-range-compare KBUILD_CFLAGS += -Wno-unaligned-access diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 0492d6afc9a1..cc5bbd70cb84 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -865,8 +865,6 @@ our %deprecated_apis = ( "DEFINE_IDR" => "DEFINE_XARRAY", "idr_init" => "xa_init", "idr_init_base" => "xa_init_flags", - "rcu_read_lock_trace" => "rcu_read_lock_tasks_trace", - "rcu_read_unlock_trace" => "rcu_read_unlock_tasks_trace", ); #Create a search pattern for all these strings to speed up a loop below @@ -7596,12 +7594,15 @@ sub process { # Complain about RCU Tasks Trace used outside of BPF (and of course, RCU). our $rcu_trace_funcs = qr{(?x: + rcu_read_lock_tasks_trace | rcu_read_lock_trace | rcu_read_lock_trace_held | rcu_read_unlock_trace | + rcu_read_unlock_tasks_trace | call_rcu_tasks_trace | synchronize_rcu_tasks_trace | rcu_barrier_tasks_trace | + rcu_tasks_trace_expedite_current | rcu_request_urgent_qs_task )}; our $rcu_trace_paths = qr{(?x: diff --git a/scripts/clang-tools/gen_compile_commands.py b/scripts/clang-tools/gen_compile_commands.py index 96e6e46ad1a7..8d14b81efd73 100755 --- a/scripts/clang-tools/gen_compile_commands.py +++ b/scripts/clang-tools/gen_compile_commands.py @@ -201,6 +201,8 @@ def main(): # Modules are listed in modules.order. if os.path.isdir(path): cmdfiles = cmdfiles_in_dir(path) + elif os.path.basename(path) == 'libgcc.a': + cmdfiles = [] elif path.endswith('.a'): cmdfiles = cmdfiles_for_a(path, ar) elif path.endswith('modules.order'): diff --git a/scripts/clang-tools/run-clang-tools.py b/scripts/clang-tools/run-clang-tools.py index f31ffd09e1ea..e78be82aa693 100755 --- a/scripts/clang-tools/run-clang-tools.py +++ b/scripts/clang-tools/run-clang-tools.py @@ -79,14 +79,15 @@ def run_analysis(entry): def main(): - try: - args = parse_arguments() + args = parse_arguments() + + # Read JSON data into the datastore variable + with open(args.path) as f: + datastore = json.load(f) - lock = multiprocessing.Lock() - pool = multiprocessing.Pool(initializer=init, initargs=(lock, args)) - # Read JSON data into the datastore variable - with open(args.path, "r") as f: - datastore = json.load(f) + lock = multiprocessing.Lock() + try: + with multiprocessing.Pool(initializer=init, initargs=(lock, args)) as pool: pool.map(run_analysis, datastore) except BrokenPipeError: # Python flushes standard streams on exit; redirect remaining output diff --git a/scripts/dtc/dt-check-style b/scripts/dtc/dt-check-style new file mode 100755 index 000000000000..2d5723d41ea3 --- /dev/null +++ b/scripts/dtc/dt-check-style @@ -0,0 +1,1192 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# +# Check DTS coding style on YAML binding examples and on +# .dts/.dtsi/.dtso source files. Enforces rules from +# Documentation/devicetree/bindings/dts-coding-style.rst. +# +# Two modes: +# --mode=relaxed (default) +# Only rules that produce zero warnings on the current tree. +# Suitable for dt_binding_check. +# --mode=strict +# All rules. Required for new submissions. +# +# Two input types (auto-detected by file extension): +# *.yaml -- DT binding; check each example block +# *.dts/*.dtsi/*.dtso -- DTS source; whole file is one block +# +# Rules are declared in a registry (see RULES below); each rule is +# tagged with the lowest mode that runs it. Promoting a rule from +# 'strict' to 'relaxed' is a one-line change. + +import argparse +import re +import sys +from enum import Enum, auto + +import ruamel.yaml + + +# --------------------------------------------------------------------------- +# Line classification +# --------------------------------------------------------------------------- + +class LineType(Enum): + BLANK = auto() + COMMENT = auto() # // ... or /* ... */ on one line + COMMENT_START = auto() # /* without closing */ + COMMENT_BODY = auto() # inside a multi-line comment + COMMENT_END = auto() # closing */ + PREPROCESSOR = auto() # #include / #define / #ifdef / ... + NODE_OPEN = auto() # something { (with optional label/name/addr) + NODE_CLOSE = auto() # }; + PROPERTY = auto() # name = value; or name; + CONTINUATION = auto() # continuation of a multi-line property + + +re_cpp_directive = re.compile( + r'^#\s*(include|define|undef|ifdef|ifndef|if|else|elif|endif|' + r'pragma|error|warning)\b') + +# label: name@addr { -- label and addr optional; name can be "/" +# Per the DT spec a node name may start with a digit (e.g. 1wire@...). +# The address part is captured loosely (any non-space, non-brace run) so +# malformed addresses (e.g. memory@0x1000) still reach +# check_unit_address_format() instead of silently bypassing the check. +re_node_header = re.compile( + r'^(?:([a-zA-Z_][a-zA-Z0-9_]*):\s*)?' + r'([a-zA-Z0-9][a-zA-Z0-9,._+-]*|/)' + r'(?:@([^\s{]+))?' + r'\s*\{$') + +re_ref_node = re.compile( + r'^&([a-zA-Z_][a-zA-Z0-9_]*)\s*\{$') + + +def is_preprocessor(stripped): + """Tell C preprocessor directives apart from DTS '#'-prefixed props.""" + return re_cpp_directive.match(stripped) is not None + + +class DtsLine: + __slots__ = ('lineno', 'raw', 'linetype', 'indent_str', 'stripped', + 'prop_name', 'continuations', + 'node_name', 'node_addr', 'label', 'ref_name', 'depth', + 'closures') + + def __init__(self, lineno, raw, linetype, indent_str, stripped): + self.lineno = lineno # 1-based within the block + self.raw = raw + self.linetype = linetype + self.indent_str = indent_str # leading whitespace as-is + self.stripped = stripped + self.prop_name = None + self.continuations = [] + self.node_name = None + self.node_addr = None + self.label = None + self.ref_name = None + self.depth = 0 # filled in by classify_lines + self.closures = 1 # count of '}' on a NODE_CLOSE line + + +def _split_code(text): + """Return (code, opens_block) for a leading-stripped line: the + code portion with // and /* */ comments removed (string literals + kept verbatim), and whether a /* */ block comment is left open. + The code portion is right-stripped so the endswith() checks in + classify_lines see code only, not a trailing comment or blanks.""" + out = [] + i = 0 + n = len(text) + while i < n: + c = text[i] + if c == '"': + j = i + 1 + while j < n: + if text[j] == '\\': + j += 2 + continue + if text[j] == '"': + j += 1 + break + j += 1 + out.append(text[i:j]) + i = j + continue + if c == '/' and i + 1 < n and text[i + 1] == '/': + break + if c == '/' and i + 1 < n and text[i + 1] == '*': + end = text.find('*/', i + 2) + if end < 0: + return (''.join(out).rstrip(), True) + i = end + 2 + continue + out.append(c) + i += 1 + return (''.join(out).rstrip(), False) + + +re_only_closures = re.compile(r'(?:\}\s*;?\s*)+$') + + +def classify_lines(text): + """Return a list of DtsLine. Tracks { } depth and groups + continuation lines onto their leading PROPERTY line.""" + out = [] + in_block_comment = False + in_cpp_macro = False + prev_complete = True + depth = 0 + + # Split preserving the indent string verbatim + re_lead = re.compile(r'^([ \t]*)(.*)$') + + for i, raw in enumerate(text.split('\n'), start=1): + m = re_lead.match(raw) + indent_str = m.group(1) + stripped = m.group(2) + + # Continuation of a multi-line C preprocessor directive: the + # previous PREPROCESSOR line ended with a '\\' line splice, so + # this line is part of the same macro. Treat it as + # PREPROCESSOR until the splice chain ends (no trailing '\\' + # or a blank line). + if in_cpp_macro: + dl = DtsLine(i, raw, LineType.PREPROCESSOR, + indent_str, stripped) + dl.depth = depth + out.append(dl) + in_cpp_macro = (bool(stripped) and + stripped.rstrip().endswith('\\')) + continue + + if not stripped: + dl = DtsLine(i, raw, LineType.BLANK, '', '') + dl.depth = depth + out.append(dl) + continue + + if in_block_comment: + ltype = (LineType.COMMENT_END if '*/' in stripped + else LineType.COMMENT_BODY) + if ltype == LineType.COMMENT_END: + in_block_comment = False + dl = DtsLine(i, raw, ltype, indent_str, stripped) + dl.depth = depth + out.append(dl) + continue + + if stripped.startswith('#') and is_preprocessor(stripped): + dl = DtsLine(i, raw, LineType.PREPROCESSOR, + indent_str, stripped) + dl.depth = depth + out.append(dl) + prev_complete = True + in_cpp_macro = stripped.rstrip().endswith('\\') + continue + + # Strip comments first so all later structural checks see code + # only. An unclosed /* sets in_block_comment for the next line. + code, opens_block = _split_code(stripped) + if opens_block: + in_block_comment = True + + # Pure-comment line: nothing left after stripping. Classify as + # COMMENT_START (carries to next line) or COMMENT, and skip the + # structural classification entirely. + if not code: + ltype = LineType.COMMENT_START if opens_block else LineType.COMMENT + dl = DtsLine(i, raw, ltype, indent_str, stripped) + dl.depth = depth + out.append(dl) + continue + + if not prev_complete: + dl = DtsLine(i, raw, LineType.CONTINUATION, indent_str, code) + dl.depth = depth + out.append(dl) + prev_complete = (code.endswith(';') or + code.endswith('{') or + code.endswith('};')) + continue + + # NODE_CLOSE: the canonical form is "}" or "};" alone. A line + # that is nothing but closures (e.g. "}; };") is still treated + # as NODE_CLOSE for depth tracking, but the multi-closure case + # is flagged separately by check_node_close_alone via + # dl.closures. + if re_only_closures.match(code): + closures = code.count('}') + depth = max(depth - closures, 0) + dl = DtsLine(i, raw, LineType.NODE_CLOSE, indent_str, code) + dl.depth = depth + dl.closures = closures + out.append(dl) + prev_complete = True + continue + + if code.endswith('{'): + dl = DtsLine(i, raw, LineType.NODE_OPEN, indent_str, code) + parse_node_header(dl) + dl.depth = depth + out.append(dl) + depth += 1 + prev_complete = True + continue + + # Property (or first line of a multi-line property). + dl = DtsLine(i, raw, LineType.PROPERTY, indent_str, code) + parse_property_name(dl) + dl.depth = depth + out.append(dl) + prev_complete = code.endswith(';') + + # Group continuation lines onto their leading PROPERTY. + last_prop = None + grouped = [] + for dl in out: + if dl.linetype == LineType.CONTINUATION and last_prop is not None: + last_prop.continuations.append(dl) + continue + if dl.linetype == LineType.PROPERTY: + last_prop = dl + elif dl.linetype != LineType.BLANK and \ + dl.linetype not in (LineType.COMMENT, LineType.COMMENT_BODY, + LineType.COMMENT_END, + LineType.COMMENT_START): + last_prop = None + grouped.append(dl) + return grouped + + +def parse_node_header(dl): + m = re_node_header.match(dl.stripped) + if m: + dl.label = m.group(1) + dl.node_name = m.group(2) + dl.node_addr = m.group(3) + return + m = re_ref_node.match(dl.stripped) + if m: + dl.ref_name = m.group(1) + + +def parse_property_name(dl): + m = re.match(r'^([a-zA-Z0-9#][a-zA-Z0-9,._+#-]*)\s*[=;]', dl.stripped) + if m: + dl.prop_name = m.group(1) + + +def collect_labels_and_refs(text): + """Return (defined_labels, referenced_labels) found anywhere outside + /* */ comments and string literals. Labels named fake_intc* (injected + by dt-extract-example) are skipped.""" + # Strip block comments first so labels inside them don't count + stripped = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) + # Strip line comments + stripped = re.sub(r'//[^\n]*', '', stripped) + # Strip string literals so words inside quotes (e.g. "Error: foo") + # are not picked up as label definitions or &-references. + stripped = re.sub(r'"(?:[^"\\]|\\.)*"', '""', stripped) + defined = set() + referenced = set() + # A label precedes a node header; the next non-space token may start + # with a letter (foo, &ref), a digit (1wire), or '/' (root node). + for m in re.finditer( + r'(?:^|[\s{])([a-zA-Z_][a-zA-Z0-9_]*):\s*[a-zA-Z0-9/&]', + stripped): + name = m.group(1) + if not name.startswith('fake_intc'): + defined.add(name) + for m in re.finditer(r'&([a-zA-Z_][a-zA-Z0-9_]*)', stripped): + referenced.add(m.group(1)) + return defined, referenced + + +# --------------------------------------------------------------------------- +# Rule registry +# --------------------------------------------------------------------------- + +class Ctx: + """Context passed to each rule check. Carries the parsed lines, + raw text, mode, and indent kind.""" + + def __init__(self, lines, text, mode, indent_kind): + self.lines = lines + self.text = text + self.mode = mode # 'relaxed' or 'strict' + self.indent_kind = indent_kind # 'spaces' or 'tab' + + +class Rule: + __slots__ = ('name', 'mode', 'description', 'check', 'applies_to') + + def __init__(self, name, mode, description, check, + applies_to=('yaml', 'dts', 'dtsi', 'dtso')): + self.name = name + self.mode = mode # 'relaxed' or 'strict' + self.description = description + self.check = check + self.applies_to = applies_to # input types this rule covers + + +# --- individual rule check functions -------------------------------------- + +def check_trailing_whitespace(ctx): + for dl in ctx.lines: + if dl.raw != dl.raw.rstrip(): + yield (dl.lineno, 'trailing whitespace') + + +def check_tab_in_dts(ctx): + """Reject literal tabs in DTS lines when input is YAML. + + For YAML examples, indent and content must use spaces. Tabs inside + a #define value are tolerated (those are CPP macros, not DTS). + For .dts files, this rule does not apply -- tabs are required. + """ + if ctx.indent_kind != 'spaces': + return + for dl in ctx.lines: + if dl.linetype == LineType.PREPROCESSOR: + continue + if dl.linetype == LineType.BLANK: + continue + if '\t' in dl.raw: + yield (dl.lineno, 'tab character not allowed in DTS example') + + +def check_mixed_indent_chars(ctx): + """Indent must be all-spaces or all-tabs, never mixed on one line.""" + for dl in ctx.lines: + if not dl.indent_str: + continue + if dl.linetype == LineType.PREPROCESSOR: + continue + if ' ' in dl.indent_str and '\t' in dl.indent_str: + yield (dl.lineno, 'mixed tabs and spaces in indent') + + +def detect_indent_unit(ctx): + """Find the indent unit used at depth 1 in this block. + + Returns one of: ' ' (2 spaces), ' ' (4 spaces), '\\t' (tab), + or None if depth-1 is empty or ambiguous.""" + for dl in ctx.lines: + if dl.depth != 1: + continue + if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): + continue + if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): + continue + if not dl.indent_str: + continue + if dl.indent_str == '\t': + return '\t' + if dl.indent_str == ' ': + return ' ' + if dl.indent_str == ' ': + return ' ' + # Anything else at depth 1 is non-canonical; flag elsewhere. + return dl.indent_str + return None + + +def check_indent_unit_relaxed(ctx): + """YAML examples: 2 or 4 spaces. Never tabs or other widths.""" + unit = detect_indent_unit(ctx) + if unit is None: + return + if unit not in (' ', ' '): + yield (1, 'indent unit must be 2 or 4 spaces, got %r' % unit) + + +def check_indent_unit_dts(ctx): + """DTS files: 1 tab per level. Always required.""" + unit = detect_indent_unit(ctx) + if unit is None: + return + if unit != '\t': + yield (1, 'indent unit must be 1 tab in DTS, got %r' % unit) + + +def check_indent_unit_strict(ctx): + """YAML: must be exactly 4 spaces. DTS: 1 tab (same as relaxed).""" + unit = detect_indent_unit(ctx) + if unit is None: + return + if ctx.indent_kind == 'spaces': + if unit != ' ': + yield (1, 'indent unit must be 4 spaces in strict mode, ' + 'got %r' % unit) + + +def check_indent_consistent(ctx): + """All indented lines must be a multiple of the detected unit.""" + unit = detect_indent_unit(ctx) + if unit is None: + return + if ctx.indent_kind == 'spaces': + if unit not in (' ', ' '): + return # let check_indent_unit_* report this + else: + if unit != '\t': + return + + for dl in ctx.lines: + if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): + continue + if dl.linetype == LineType.CONTINUATION: + continue # continuations align to <, not to indent unit + if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): + continue + if not dl.indent_str: + continue + # The indent must be 'unit' repeated dl.depth times, exactly. + # NODE_CLOSE lines have depth equal to the post-decrement value, + # which matches the indent expected. + expected = unit * dl.depth + if dl.indent_str != expected: + yield (dl.lineno, + 'indent mismatch (expected depth %d * %r)' % + (dl.depth, unit)) + + +def check_blank_lines(ctx): + """No two consecutive blank lines, no leading/trailing blank lines + in any node body.""" + lines = ctx.lines + # Consecutive blanks + for i in range(1, len(lines)): + if lines[i].linetype == LineType.BLANK and \ + lines[i - 1].linetype == LineType.BLANK: + yield (lines[i].lineno, 'consecutive blank lines') + # Blank right after { or right before } + for i, dl in enumerate(lines): + if dl.linetype != LineType.BLANK: + continue + prev = lines[i - 1] if i > 0 else None + nxt = lines[i + 1] if i + 1 < len(lines) else None + if prev is not None and prev.linetype == LineType.NODE_OPEN: + yield (dl.lineno, 'blank line at start of node body') + if nxt is not None and nxt.linetype == LineType.NODE_CLOSE: + yield (dl.lineno, 'blank line at end of node body') + + +def _walk_bodies(lines): + """Yield lists of immediate-child NODE_OPEN lines for each node body + in the input. Skips ref-nodes (&label) since those don't have an + intrinsic ordering.""" + body_stack = [[]] + for dl in lines: + if dl.linetype == LineType.NODE_OPEN: + body_stack[-1].append(dl) + body_stack.append([]) + continue + if dl.linetype == LineType.NODE_CLOSE: + if len(body_stack) <= 1: + # Unbalanced; ignore to avoid crashing on malformed input + continue + yield body_stack.pop() + continue + while body_stack: + yield body_stack.pop() + + +def _natural_sort_key(s): + """Split a string into a tuple of (kind, value) pairs that compares + numeric runs as ints, so 'foo10' sorts after 'foo2'.""" + parts = [] + for part in re.split(r'(\d+)', s): + if part.isdigit(): + parts.append((0, int(part))) + else: + parts.append((1, part)) + return tuple(parts) + + +def check_child_address_order(ctx): + """Addressed siblings (foo@N) must appear in ascending address + order within their parent node body.""" + for children in _walk_bodies(ctx.lines): + addressed = [] + for c in children: + if c.node_addr is None: + continue + try: + parts = tuple(int(p, 16) for p in c.node_addr.split(',')) + except ValueError: + continue + addressed.append((parts, c)) + for i in range(1, len(addressed)): + if addressed[i][0] < addressed[i - 1][0]: + dl = addressed[i][1] + yield (dl.lineno, + 'child node @%s out of address order' % + dl.node_addr) + + +def check_child_name_order(ctx): + """Unaddressed siblings must appear in natural-sort order by node + name within their parent node body. Addressed children are scoped + by check_child_address_order; reference nodes (&label { ... }) and + the root node are skipped.""" + for children in _walk_bodies(ctx.lines): + unaddressed = [] + for c in children: + if c.node_addr is not None: + continue + if c.node_name in (None, '/'): + continue + if c.ref_name is not None: + continue + unaddressed.append((_natural_sort_key(c.node_name), c)) + for i in range(1, len(unaddressed)): + if unaddressed[i][0] < unaddressed[i - 1][0]: + dl = unaddressed[i][1] + yield (dl.lineno, + 'child node %r out of name order' % dl.node_name) + + +def _property_bucket(name): + """Return the canonical bucket index for a property: + 0 compatible + 1 reg / reg-names + 2 ranges + 3 standard properties (no vendor comma in #-stripped name) + 4 vendor-specific properties + 5 status + Plus a sub-key inside the bucket for fixed slots (compatible, reg, + reg-names, ranges, status). 'standard' and 'vendor' return None for + the sub-key, signalling that the within-bucket key is computed by + the pairing rules.""" + stripped = name.lstrip('#') + if name == 'compatible': + return (0, 0) + if name == 'reg': + return (1, 0) + if name == 'reg-names': + return (1, 1) + if name == 'ranges': + return (2, 0) + if name == 'status': + return (5, 0) + return (4 if ',' in stripped else 3, None) + + +# Declarative pairing rules: each is a callable +# (name, all_names) -> anchor_name_or_None +# If a rule returns an anchor, the property sorts immediately after the +# anchor. Rules are tried in order; the first match wins. If none +# matches, the within-bucket key falls back to natural sort by the +# #-stripped name. + +def _pair_pinctrl_names(name, all_names): + """pinctrl-names follows the highest pinctrl-N in the same node.""" + if name != 'pinctrl-names': + return None + cands = [n for n in all_names if re.match(r'^pinctrl-\d+$', n)] + if not cands: + return None + return max(cands, key=_natural_sort_key) + + +def _pair_x_names(name, all_names): + """Generic <x>-names follows its owning property. The owner is + usually plural (clocks/clock-names, dmas/dma-names, + resets/reset-names) but occasionally singular (reg/reg-names is + handled by the fixed slot above; this rule catches anything else).""" + if not name.endswith('-names'): + return None + base = name[:-len('-names')] + # Try plural and singular forms. + if (base + 's') in all_names: + return base + 's' + if base in all_names: + return base + return None + + +PAIRING_RULES = (_pair_pinctrl_names, _pair_x_names) + + +def _property_sort_key(name, all_names): + """Sort key for a property among its node-body siblings. + + Format: (bucket, within_key, tiebreak). 'within_key' for + standard/vendor buckets follows pairing rules: a property paired + with anchor X sorts as if it were X with a higher tiebreak.""" + bucket, fixed_sub = _property_bucket(name) + if fixed_sub is not None: + return (bucket, (), fixed_sub) + + for rule in PAIRING_RULES: + anchor = rule(name, all_names) + if anchor is not None: + return (bucket, _natural_sort_key(anchor.lstrip('#')), 1) + + return (bucket, _natural_sort_key(name.lstrip('#')), 0) + + +def check_property_order(ctx): + """Properties within a node body must appear in canonical order: + compatible, reg(/reg-names), ranges, then the standard group, then + the vendor-specific group, then status. Inside the standard and + vendor groups, pairing rules apply (e.g. <x>-names follows <x>); + everything else falls back to natural sort by the #-stripped name.""" + lines = ctx.lines + for i, dl in enumerate(lines): + if dl.linetype != LineType.NODE_OPEN: + continue + body_depth = dl.depth + 1 + props = [] + for j in range(i + 1, len(lines)): + d = lines[j] + if d.linetype == LineType.NODE_CLOSE and \ + d.depth == body_depth - 1: + break + if d.linetype == LineType.PROPERTY and d.depth == body_depth \ + and d.prop_name is not None: + props.append(d) + if len(props) < 2: + continue + all_names = [p.prop_name for p in props] + keyed = [(p, _property_sort_key(p.prop_name, all_names)) + for p in props] + for k in range(1, len(keyed)): + if keyed[k][1] < keyed[k - 1][1]: + p = keyed[k][0] + prev = keyed[k - 1][0] + yield (p.lineno, + 'property %r out of canonical order ' + '(should sort before %r)' % + (p.prop_name, prev.prop_name)) + + +def _strip_strings_and_comments(text): + """Remove string literals and /* */ + // comments from a single + line, replacing them with empty strings. Used so syntactic checks + (whitespace, hex case, etc.) don't false-positive on contents of + quoted strings or comments. An unclosed /* on the line is treated + as a comment running to end of line.""" + text = re.sub(r'"(?:[^"\\]|\\.)*"', '""', text) + text = re.sub(r'/\*.*?\*/', '', text) + text = re.sub(r'/\*.*$', '', text) + text = re.sub(r'//.*$', '', text) + return text + + +def check_required_blank_lines(ctx): + """A blank line must precede each child node and the 'status' + property within a node body, except when these are the first + substantive item in the body.""" + lines = ctx.lines + for i, open_dl in enumerate(lines): + if open_dl.linetype != LineType.NODE_OPEN: + continue + body_depth = open_dl.depth + 1 + prev_substantive = None + between_blanks = 0 + depth_inside = 0 + for j in range(i + 1, len(lines)): + d = lines[j] + if d.linetype == LineType.NODE_CLOSE and \ + d.depth == body_depth - 1 and depth_inside == 0: + break + # Track depth inside nested children so we only look at + # immediate-body items. + if d.linetype == LineType.NODE_OPEN and \ + d.depth >= body_depth and depth_inside > 0: + depth_inside += 1 + continue + if d.linetype == LineType.NODE_CLOSE and depth_inside > 0: + depth_inside -= 1 + continue + if depth_inside > 0: + continue + if d.linetype == LineType.BLANK: + if prev_substantive is not None: + between_blanks += 1 + continue + if d.linetype in (LineType.COMMENT, LineType.COMMENT_START, + LineType.COMMENT_BODY, LineType.COMMENT_END, + LineType.PREPROCESSOR): + continue + if d.linetype == LineType.CONTINUATION: + continue + + needs_blank = False + if d.linetype == LineType.NODE_OPEN: + needs_blank = True + depth_inside = 1 # entered the child body + elif d.linetype == LineType.PROPERTY and d.prop_name == 'status': + needs_blank = True + + if needs_blank and prev_substantive is not None and \ + between_blanks == 0: + if d.linetype == LineType.NODE_OPEN: + yield (d.lineno, + 'child node must be preceded by a blank line') + else: + yield (d.lineno, + '"status" must be preceded by a blank line') + + prev_substantive = d + between_blanks = 0 + + +def check_hex_case(ctx): + """Hex literals (0xN) must use lowercase digits and prefix.""" + for dl in ctx.lines: + if dl.linetype in (LineType.BLANK, LineType.COMMENT, + LineType.COMMENT_START, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.PREPROCESSOR): + continue + text = _strip_strings_and_comments(dl.raw) + for m in re.finditer(r'\b0[xX][0-9a-fA-F]+\b', text): + lit = m.group(0) + if any(c.isupper() for c in lit[2:]) or lit[1] == 'X': + yield (dl.lineno, + 'hex literal %r must be lowercase' % lit) + + +def check_unit_address_format(ctx): + """Unit addresses must be lowercase hex without leading zeros and + without a '0x' prefix. For multi-cell addresses (comma-separated), + each part is checked independently. A single '0' is permitted + (canonical zero).""" + for dl in ctx.lines: + if dl.linetype != LineType.NODE_OPEN: + continue + if dl.node_addr is None: + continue + addr = dl.node_addr + for part in addr.split(','): + if part[:2] in ('0x', '0X'): + yield (dl.lineno, + 'unit address %r must not have a "0x" prefix' % + addr) + break + if not re.match(r'^[0-9a-fA-F]+$', part): + yield (dl.lineno, + 'unit address %r is not valid hex' % addr) + break + if any(c in 'ABCDEF' for c in part): + yield (dl.lineno, + 'unit address %r must be lowercase hex' % addr) + break + if len(part) > 1 and part.startswith('0'): + yield (dl.lineno, + 'unit address %r has leading zeros' % addr) + break + + +def check_value_whitespace(ctx): + """A <...> cell list must have no whitespace directly after '<' + or directly before '>'. Continuation lines are joined onto the + property so a <...> split across lines is checked too; a '<' or + '>' at a line break is glued straight to the neighbouring value, + so the break itself is not counted as padding. Outside strings + and comments only.""" + for dl in ctx.lines: + if dl.linetype != LineType.PROPERTY: + continue + segs = [_strip_strings_and_comments(dl.raw).strip()] + for cont in dl.continuations: + segs.append(_strip_strings_and_comments(cont.stripped).strip()) + text = '' + for s in segs: + if not s: + continue + if not text or text.endswith('<') or s.startswith('>'): + text += s + else: + text += ' ' + s + for m in re.finditer(r'<([^<>]*)>', text): + content = m.group(1) + if content and content != content.strip(): + yield (dl.lineno, 'extra whitespace inside <...>') + break + + +def check_node_close_alone(ctx): + """The closing '};' of a node must be on its own line. The + classifier accepts a canonical "}" or "};" as NODE_CLOSE; a line + that is all closures (e.g. "}; };") is still NODE_CLOSE for depth + tracking but is flagged here via dl.closures. Any other line that + still contains '};' (in code, not in strings or comments) is + mixing a node close with something else.""" + for dl in ctx.lines: + if dl.linetype == LineType.NODE_CLOSE: + if dl.closures > 1: + yield (dl.lineno, + 'closing brace must be on its own line') + continue + if dl.linetype in (LineType.BLANK, LineType.COMMENT, + LineType.COMMENT_START, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.PREPROCESSOR): + continue + text = _strip_strings_and_comments(dl.raw) + if '};' in text: + yield (dl.lineno, + 'closing brace must be on its own line') + + +def _display_col(text): + """Visual column width of text, with tabs expanded to the next + 8-column stop, matching how printf and most editors render a + line and the kernel-wide line length convention.""" + col = 0 + for ch in text: + if ch == '\t': + col = (col // 8 + 1) * 8 + else: + col += 1 + return col + + +def check_line_length(ctx): + """Lines must not exceed 80 columns; tabs count as 8 (see + _display_col).""" + for dl in ctx.lines: + if dl.linetype == LineType.BLANK: + continue + cols = _display_col(dl.raw) + if cols > 80: + yield (dl.lineno, + 'line exceeds 80 columns (%d)' % cols) + + +def check_continuation_alignment(ctx): + """A multi-line property's continuation lines must align their + first non-whitespace character to the display column of the first + '<' or '"' after the '=' in the leading line. Display columns are + used so tab-indented .dts files (where a continuation aligns with + tabs plus spaces) are compared correctly.""" + for dl in ctx.lines: + if dl.linetype != LineType.PROPERTY: + continue + if not dl.continuations: + continue + eq = dl.raw.find('=') + if eq < 0: + continue + # First '<' or '"' after '=' + rest = dl.raw[eq + 1:] + m = re.search(r'[<"]', rest) + if not m: + continue + target_col = _display_col(dl.raw[:eq + 1 + m.start()]) + for cont in dl.continuations: + if _display_col(cont.indent_str) != target_col: + yield (cont.lineno, + 'continuation should align to column %d ' + '(under "<" or \\")' % (target_col + 1)) + + +def check_unclosed_block_comment(ctx): + """Every /* must have a matching */ in the same block. Catches both + a comment opened on its own line (COMMENT_START) and a tail comment + opened on a PROPERTY or other code line (where in_block_comment is + set by _split_code so the next line becomes COMMENT_BODY without a + preceding COMMENT_START).""" + open_lineno = None + for dl in ctx.lines: + if dl.linetype == LineType.COMMENT_START: + open_lineno = dl.lineno + elif dl.linetype == LineType.COMMENT_END: + open_lineno = None + elif dl.linetype == LineType.COMMENT_BODY and open_lineno is None: + # Block was opened by a /* tail on a code line; report at + # the first orphan body line since the originating line is + # already classified as something else. + open_lineno = dl.lineno + if open_lineno is not None: + yield (open_lineno, 'unclosed /* block comment') + + +def check_unused_labels(ctx): + """Labels defined but never referenced are clutter.""" + defined, referenced = collect_labels_and_refs(ctx.text) + for label in sorted(defined - referenced): + # Find the line where this label is defined for line-number + # reporting. + m = re.search(r'(?m)^.*\b' + re.escape(label) + r'\s*:', ctx.text) + lineno = ctx.text[:m.start()].count('\n') + 1 if m else 1 + yield (lineno, 'label %r defined but never &-referenced' % label) + + +# --- registry -------------------------------------------------------------- + +RULES = [ + # 'relaxed' is the default; rules in this group must produce zero + # output on a clean kernel tree (post the small prep-cleanup + # commit at the head of this series). + Rule('trailing-whitespace', 'relaxed', + 'no trailing whitespace on any line', + check_trailing_whitespace), + Rule('tab-in-dts', 'relaxed', + 'YAML examples may not contain tab characters', + check_tab_in_dts, applies_to=('yaml',)), + Rule('mixed-indent-chars', 'relaxed', + 'indent must not mix tabs and spaces', + check_mixed_indent_chars), + Rule('unclosed-block-comment', 'relaxed', + 'every /* block comment must close with */', + check_unclosed_block_comment), + + # DTS files always use tabs; this is not negotiable per kernel + # coding style (.dts files are real source). Relaxed mode. + Rule('indent-unit-dts', 'relaxed', + 'DTS files: 1 tab per nesting level', + check_indent_unit_dts, + applies_to=('dts', 'dtsi', 'dtso')), + + # 'strict' rules are opt-in (e.g. for new submissions via + # checkpatch.pl in a follow-up series). They flag many existing + # files and can be promoted to relaxed once those are cleaned up. + Rule('indent-unit', 'strict', + 'YAML: 2 or 4 spaces per level', + check_indent_unit_relaxed, applies_to=('yaml',)), + Rule('indent-unit-strict', 'strict', + 'YAML: must be 4 spaces per level', + check_indent_unit_strict, applies_to=('yaml',)), + Rule('indent-consistent', 'strict', + 'every line indented at depth * unit', + check_indent_consistent), + Rule('blank-lines', 'strict', + 'no consecutive blanks; no blanks at node body edges', + check_blank_lines), + Rule('child-address-order', 'strict', + 'addressed siblings must be in ascending address order', + check_child_address_order), + Rule('child-name-order', 'strict', + 'unaddressed siblings must be in natural-sort name order', + check_child_name_order), + Rule('property-order', 'strict', + 'canonical bucket + pairing + natural-sort order of properties', + check_property_order), + Rule('required-blank-lines', 'strict', + 'blank line before child nodes and before "status"', + check_required_blank_lines), + Rule('hex-case', 'strict', + 'hex literals must be lowercase', + check_hex_case), + Rule('unit-address-format', 'strict', + 'unit addresses must be lowercase hex without leading zeros', + check_unit_address_format), + Rule('value-whitespace', 'strict', + 'no whitespace directly inside <...> brackets', + check_value_whitespace), + Rule('node-close-alone', 'strict', + 'closing brace must be on its own line', + check_node_close_alone), + Rule('line-length', 'strict', + 'lines must not exceed 80 columns', + check_line_length), + Rule('continuation-alignment', 'strict', + 'multi-line property continuations align under "<" or "\\""', + check_continuation_alignment), + Rule('unused-labels', 'strict', + 'every label must be &-referenced in the same example/file ' + '(skipped for .dtsi/.dtso since labels there are exported)', + check_unused_labels, applies_to=('yaml', 'dts')), +] + + +def select_rules(mode, input_kind): + """Return rules that apply to the given mode and input type.""" + rank = {'relaxed': 0, 'strict': 1} + out = [] + for r in RULES: + if rank[r.mode] > rank[mode]: + continue + if input_kind not in r.applies_to: + continue + out.append(r) + return out + + +# --------------------------------------------------------------------------- +# Block runner +# --------------------------------------------------------------------------- + +def check_block(text, mode, indent_kind, input_type): + """Run all selected rules on a single block of DTS text. Returns a + list of (lineno, rule_name, message) tuples.""" + lines = classify_lines(text) + ctx = Ctx(lines, text, mode, indent_kind) + rules = select_rules(mode, input_type) + findings = [] + for r in rules: + for lineno, msg in r.check(ctx): + findings.append((lineno, r.name, msg)) + findings.sort(key=lambda t: (t[0], t[1])) + return findings + + +# --------------------------------------------------------------------------- +# Input drivers (YAML examples vs raw DTS) +# --------------------------------------------------------------------------- + +def _yaml_loader(): + return ruamel.yaml.YAML() + + +def iter_yaml_examples(filepath): + """Yield (example_text, base_lineno_in_file, example_index) tuples.""" + yaml = _yaml_loader() + try: + with open(filepath, encoding='utf-8') as f: + data = yaml.load(f) + except Exception as e: + print('%s: error loading YAML: %s' % (filepath, e), + file=sys.stderr) + return + if not isinstance(data, dict) or 'examples' not in data: + return + examples = data['examples'] + if not hasattr(examples, '__iter__'): + return + for i, ex in enumerate(examples): + if not isinstance(ex, str): + continue + try: + base = examples.lc.item(i)[0] + 2 + except Exception: + base = 1 + yield (str(ex), base, i) + + +def iter_dts_file(filepath): + """Treat the whole file as a single block.""" + try: + with open(filepath, encoding='utf-8') as f: + text = f.read() + except Exception as e: + print('%s: error reading: %s' % (filepath, e), file=sys.stderr) + return + yield (text, 1, None) + + +# --------------------------------------------------------------------------- +# Top-level processing +# --------------------------------------------------------------------------- + +def input_kind(filepath): + p = filepath.lower() + if p.endswith('.yaml') or p.endswith('.yml'): + return 'yaml' + if p.endswith('.dts'): + return 'dts' + if p.endswith('.dtsi'): + return 'dtsi' + if p.endswith('.dtso'): + return 'dtso' + return None + + +# All input types that use tab indentation and follow DTS coding style. +DTS_FAMILY = ('dts', 'dtsi', 'dtso') + + +def collect_findings(filepath, mode): + """Return a (lines, count) pair for filepath. lines is a list of + formatted output strings; count is the number of findings.""" + kind = input_kind(filepath) + if kind == 'yaml': + indent_kind = 'spaces' + iterator = iter_yaml_examples(filepath) + elif kind in DTS_FAMILY: + indent_kind = 'tab' + iterator = iter_dts_file(filepath) + else: + return (['%s: unknown file type, skipping' % filepath], 0) + + out = [] + for text, base, idx in iterator: + for lineno, rule, msg in check_block(text, mode, indent_kind, kind): + abs_line = base + lineno - 1 + ex_tag = '' if idx is None else ' example %d' % idx + out.append('%s:%d:%s [%s] %s' % + (filepath, abs_line, ex_tag, rule, msg)) + return (out, len(out)) + + +# Worker entry point for ProcessPoolExecutor.map(). Top-level so it is +# picklable on every platform. +def _worker(args): + filepath, mode = args + return collect_findings(filepath, mode) + + +def main(): + import os + ap = argparse.ArgumentParser( + description='Check DTS coding style on YAML examples and ' + '.dts/.dtsi/.dtso files.', + fromfile_prefix_chars='@') + ap.add_argument('--mode', choices=('relaxed', 'strict'), + default='relaxed', + help='which rule set to apply (default: relaxed)') + ap.add_argument('-j', '--jobs', type=int, default=0, + metavar='N', + help='run N workers in parallel (default: respect ' + 'the make jobserver via $PARALLELISM, otherwise ' + 'os.cpu_count(); use 1 to disable multiprocessing)') + ap.add_argument('--list-rules', action='store_true', + help='print all rules with their mode and exit') + ap.add_argument('files', nargs='*', metavar='file', + help='YAML binding files or .dts/.dtsi/.dtso files; ' + 'use @argfile to read paths from a file') + args = ap.parse_args() + + if args.list_rules: + for r in RULES: + applies = ','.join(r.applies_to) + print('%-22s %-7s [%s] %s' % + (r.name, r.mode, applies, r.description)) + return 0 + + if not args.files: + ap.error('no input files') + + if args.jobs > 0: + jobs = args.jobs + else: + # When invoked under scripts/jobserver-exec, $PARALLELISM + # holds the slot count make has reserved for us; this lets + # `make -j N dt_binding_check` constrain our worker pool to N. + try: + jobs = int(os.environ['PARALLELISM']) + except (KeyError, ValueError): + jobs = os.cpu_count() or 1 + # Single-process path: keep import surface small for tests and + # easy debugging. + if jobs == 1 or len(args.files) == 1: + total = 0 + for f in args.files: + lines, n = collect_findings(f, args.mode) + for line in lines: + print(line, file=sys.stderr) + total += n + return 1 if total else 0 + + # Multi-process path. ex.map preserves input order so output is + # deterministic across runs. + from concurrent.futures import ProcessPoolExecutor + total = 0 + work = [(f, args.mode) for f in args.files] + chunk = max(1, len(work) // (jobs * 8)) if work else 1 + with ProcessPoolExecutor(max_workers=jobs) as ex: + for lines, n in ex.map(_worker, work, chunksize=chunk): + for line in lines: + print(line, file=sys.stderr) + total += n + return 1 if total else 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/dtc/dt-style-selftest/bad/dts-spaces.dts b/scripts/dtc/dt-style-selftest/bad/dts-spaces.dts new file mode 100644 index 000000000000..9dad22adce51 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/dts-spaces.dts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: a .dts using space indent (must use tabs). + */ + +/dts-v1/; + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; +}; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-child-addr-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-child-addr-order.yaml new file mode 100644 index 000000000000..3df56e69a1ff --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-child-addr-order.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-child-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with addressed children out of order + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-child-order + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@200 { + compatible = "example,test-child-order"; + reg = <0x200 0x10>; + }; + + device@100 { + compatible = "example,test-child-order"; + reg = <0x100 0x10>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-child-name-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-child-name-order.yaml new file mode 100644 index 000000000000..35d85e5573c2 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-child-name-order.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-child-name-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with unaddressed children out of name order + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-child-name-order + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + + foo { + label = "foo"; + }; + + bar { + label = "bar"; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-cont-align.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-cont-align.yaml new file mode 100644 index 000000000000..92778540b056 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-cont-align.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-cont-align.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with mis-aligned multi-line property + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-cont-align + reg: + maxItems: 2 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-cont-align"; + reg = <0x1000 0x100>, + <0x2000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-digit-node-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-digit-node-order.yaml new file mode 100644 index 000000000000..44a9d25e5ba0 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-digit-node-order.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-digit-node-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with digit-leading nodes out of address order + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-digit-node-order + +required: + - compatible + +additionalProperties: false + +examples: + - | + bus@0 { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + + 3d-engine@20 { + compatible = "example,3d-engine"; + reg = <0x20 0x4>; + }; + + 1wire@10 { + compatible = "example,1wire"; + reg = <0x10 0x4>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-hex-case.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-hex-case.yaml new file mode 100644 index 000000000000..b26d1bf58de9 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-hex-case.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-hex-case.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with uppercase hex literals + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-hex-case + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-hex-case"; + reg = <0xABCD 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-indent-strict.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-indent-strict.yaml new file mode 100644 index 000000000000..bee4cf118d73 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-indent-strict.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-indent-strict.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture using 2-space indent (rejected by strict mode) + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-indent-strict + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-indent-strict"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-label-in-string.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-label-in-string.yaml new file mode 100644 index 000000000000..ba512869b702 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-label-in-string.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-label-in-string.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture where a label is only "referenced" inside a string + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-label-in-string + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo: device@1000 { + compatible = "example,test-label-in-string"; + reg = <0x1000 0x100>; + info = "see &foo for details"; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-line-length.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-line-length.yaml new file mode 100644 index 000000000000..64427bf1c385 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-line-length.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-line-length.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture exceeding 80 columns + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-line-length + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-line-length-this-is-a-very-long-name-indeed-yeah"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-mixed-indent.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-mixed-indent.yaml new file mode 100644 index 000000000000..5401d1a423a1 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-mixed-indent.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-mixed-indent.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture mixing tabs and spaces in indent + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-mixed + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-mixed"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-multi-close.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-multi-close.yaml new file mode 100644 index 000000000000..4d9fa27b50a2 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-multi-close.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-multi-close.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with two closing braces on one line + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-multi-close + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@100 { + compatible = "example,test-multi-close"; + reg = <0x100 0x10>; + }; }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-node-close.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-node-close.yaml new file mode 100644 index 000000000000..e107659fd9e8 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-node-close.yaml @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-node-close.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with closing brace not on its own line + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-node-close + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + + empty {}; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-prop-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-prop-order.yaml new file mode 100644 index 000000000000..75582a3d2f6e --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-prop-order.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-prop-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with reg before compatible + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-prop-order + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + reg = <0x1000 0x100>; + compatible = "example,test-prop-order"; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-prop-pairing.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-prop-pairing.yaml new file mode 100644 index 000000000000..767ab21c39f3 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-prop-pairing.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-prop-pairing.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture exercising <x>-names and pinctrl-names pairing + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-prop-pairing + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-prop-pairing"; + reg = <0x1000 0x100>; + clock-names = "bus"; + clocks = <&clk 0>; + pinctrl-names = "default"; + pinctrl-0 = <&p0>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-required-blank.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-required-blank.yaml new file mode 100644 index 000000000000..8bb53240cffa --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-required-blank.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-required-blank.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture missing required blank lines + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-required-blank + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + status = "okay"; + child@100 { + reg = <0x100>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml new file mode 100644 index 000000000000..487d07ff8cb6 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-tab.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with a tab in a DTS line + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-tab + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-tab"; + reg = <0x1000 0x100>; /* registers */ + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-trailing-comment.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-comment.yaml new file mode 100644 index 000000000000..2368ada8106f --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-comment.yaml @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-trailing-comment.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with properties out of order behind trailing comments + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-trailing-comment + +required: + - compatible + +additionalProperties: false + +examples: + - | + foo@0 { /* the device node */ + reg = <0x0 0x4>; /* registers */ + compatible = "example,test-trailing-comment"; // misplaced + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-trailing-ws.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-ws.yaml new file mode 100644 index 000000000000..5c4b4bd833c5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-ws.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-trailing.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with trailing whitespace + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-trailing + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-trailing"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unclosed-comment.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unclosed-comment.yaml new file mode 100644 index 000000000000..63c1c08712a5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unclosed-comment.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unclosed-comment.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with an unclosed /* block comment + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unclosed-comment + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + /* this comment never closes + device@1000 { + compatible = "example,test-unclosed-comment"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr-prefix.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr-prefix.yaml new file mode 100644 index 000000000000..9b3fe508c5fd --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr-prefix.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unit-addr-prefix.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with 0x-prefixed unit address + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unit-addr-prefix + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + memory@0x1000 { + compatible = "example,test-unit-addr-prefix"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr.yaml new file mode 100644 index 000000000000..93705cd45410 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unit-addr.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with malformed unit address + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unit-addr + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@01000 { + compatible = "example,test-unit-addr"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unused-label.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unused-label.yaml new file mode 100644 index 000000000000..28d7176cbf08 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unused-label.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unused-label.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with an unused label + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unused-label + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + dev: device@1000 { + compatible = "example,test-unused-label"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-value-ws-multiline.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws-multiline.yaml new file mode 100644 index 000000000000..504bf0931c27 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws-multiline.yaml @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-value-ws-multiline.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with extra whitespace in a multi-line cell array + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-value-ws-multiline + +required: + - compatible + +additionalProperties: false + +examples: + - | + foo@0 { + compatible = "example,test-value-ws-multiline"; + reg = < 0x0 0x4 + 0x8 0xc>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-value-ws.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws.yaml new file mode 100644 index 000000000000..342ab9f399f1 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-value-ws.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with extra whitespace inside <...> + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-value-ws + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-value-ws"; + reg = < 0x1000 0x100 >; + }; diff --git a/scripts/dtc/dt-style-selftest/expected/dts-spaces.dts.txt b/scripts/dtc/dt-style-selftest/expected/dts-spaces.dts.txt new file mode 100644 index 000000000000..070025c4568c --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/dts-spaces.dts.txt @@ -0,0 +1,2 @@ +# mode=relaxed +bad/dts-spaces.dts:1: [indent-unit-dts] indent unit must be 1 tab in DTS, got ' ' diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-child-addr-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-child-addr-order.yaml.txt new file mode 100644 index 000000000000..f0db79a0018b --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-child-addr-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-child-addr-order.yaml:37: example 0 [child-address-order] child node @100 out of address order diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-child-name-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-child-name-order.yaml.txt new file mode 100644 index 000000000000..bb434b126191 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-child-name-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-child-name-order.yaml:34: example 0 [child-name-order] child node 'bar' out of name order diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-cont-align.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-cont-align.yaml.txt new file mode 100644 index 000000000000..b5576dd0f6b1 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-cont-align.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-cont-align.yaml:29: example 0 [continuation-alignment] continuation should align to column 11 (under "<" or \") diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-digit-node-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-digit-node-order.yaml.txt new file mode 100644 index 000000000000..6de275e2dcb5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-digit-node-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-digit-node-order.yaml:33: example 0 [child-address-order] child node @10 out of address order diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-hex-case.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-hex-case.yaml.txt new file mode 100644 index 000000000000..6600f7cd1ba5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-hex-case.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-hex-case.yaml:28: example 0 [hex-case] hex literal '0xABCD' must be lowercase diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-indent-strict.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-indent-strict.yaml.txt new file mode 100644 index 000000000000..5ef290d3a847 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-indent-strict.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-indent-strict.yaml:26: example 0 [indent-unit-strict] indent unit must be 4 spaces in strict mode, got ' ' diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-label-in-string.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-label-in-string.yaml.txt new file mode 100644 index 000000000000..05da06f81364 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-label-in-string.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-label-in-string.yaml:26: example 0 [unused-labels] label 'foo' defined but never &-referenced diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-line-length.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-line-length.yaml.txt new file mode 100644 index 000000000000..89b36360caa4 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-line-length.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-line-length.yaml:27: example 0 [line-length] line exceeds 80 columns (81) diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-mixed-indent.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-mixed-indent.yaml.txt new file mode 100644 index 000000000000..c989f8f19853 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-mixed-indent.yaml.txt @@ -0,0 +1,3 @@ +# mode=relaxed +bad/yaml-mixed-indent.yaml:27: example 0 [mixed-indent-chars] mixed tabs and spaces in indent +bad/yaml-mixed-indent.yaml:27: example 0 [tab-in-dts] tab character not allowed in DTS example diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-multi-close.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-multi-close.yaml.txt new file mode 100644 index 000000000000..637d0f8ea103 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-multi-close.yaml.txt @@ -0,0 +1,3 @@ +# mode=strict +bad/yaml-multi-close.yaml:35: example 0 [indent-consistent] indent mismatch (expected depth 0 * ' ') +bad/yaml-multi-close.yaml:35: example 0 [node-close-alone] closing brace must be on its own line diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-node-close.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-node-close.yaml.txt new file mode 100644 index 000000000000..ee894747b5b9 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-node-close.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-node-close.yaml:30: example 0 [node-close-alone] closing brace must be on its own line diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-prop-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-prop-order.yaml.txt new file mode 100644 index 000000000000..578df7209170 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-prop-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-prop-order.yaml:28: example 0 [property-order] property 'compatible' out of canonical order (should sort before 'reg') diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-prop-pairing.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-prop-pairing.yaml.txt new file mode 100644 index 000000000000..e6e21349a939 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-prop-pairing.yaml.txt @@ -0,0 +1,3 @@ +# mode=strict +bad/yaml-prop-pairing.yaml:30: example 0 [property-order] property 'clocks' out of canonical order (should sort before 'clock-names') +bad/yaml-prop-pairing.yaml:32: example 0 [property-order] property 'pinctrl-0' out of canonical order (should sort before 'pinctrl-names') diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-required-blank.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-required-blank.yaml.txt new file mode 100644 index 000000000000..04ea0bacdcb9 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-required-blank.yaml.txt @@ -0,0 +1,3 @@ +# mode=strict +bad/yaml-required-blank.yaml:29: example 0 [required-blank-lines] "status" must be preceded by a blank line +bad/yaml-required-blank.yaml:30: example 0 [required-blank-lines] child node must be preceded by a blank line diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-tab.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-tab.yaml.txt new file mode 100644 index 000000000000..9e83246fbaa1 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-tab.yaml.txt @@ -0,0 +1,2 @@ +# mode=relaxed +bad/yaml-tab.yaml:28: example 0 [tab-in-dts] tab character not allowed in DTS example diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-trailing-comment.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-comment.yaml.txt new file mode 100644 index 000000000000..69dbb1d03239 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-comment.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-trailing-comment.yaml:25: example 0 [property-order] property 'compatible' out of canonical order (should sort before 'reg') diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-trailing-ws.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-ws.yaml.txt new file mode 100644 index 000000000000..cfdbc8476c73 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-ws.yaml.txt @@ -0,0 +1,2 @@ +# mode=relaxed +bad/yaml-trailing-ws.yaml:27: example 0 [trailing-whitespace] trailing whitespace diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unclosed-comment.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unclosed-comment.yaml.txt new file mode 100644 index 000000000000..9a30ee7145e6 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unclosed-comment.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unclosed-comment.yaml:26: example 0 [unclosed-block-comment] unclosed /* block comment diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr-prefix.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr-prefix.yaml.txt new file mode 100644 index 000000000000..8dec6c1176b5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr-prefix.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unit-addr-prefix.yaml:26: example 0 [unit-address-format] unit address '0x1000' must not have a "0x" prefix diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr.yaml.txt new file mode 100644 index 000000000000..b52f0ef20bee --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unit-addr.yaml:26: example 0 [unit-address-format] unit address '01000' has leading zeros diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unused-label.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unused-label.yaml.txt new file mode 100644 index 000000000000..4f00202f0902 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unused-label.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unused-label.yaml:26: example 0 [unused-labels] label 'dev' defined but never &-referenced diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-value-ws-multiline.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws-multiline.yaml.txt new file mode 100644 index 000000000000..3df55b1762d0 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws-multiline.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-value-ws-multiline.yaml:25: example 0 [value-whitespace] extra whitespace inside <...> diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-value-ws.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws.yaml.txt new file mode 100644 index 000000000000..cbb5f88fe85f --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-value-ws.yaml:28: example 0 [value-whitespace] extra whitespace inside <...> diff --git a/scripts/dtc/dt-style-selftest/good/dts-cont-align.dts b/scripts/dtc/dt-style-selftest/good/dts-cont-align.dts new file mode 100644 index 000000000000..36fb4eefcd83 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-cont-align.dts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: tab-indented .dts with a tab-and-space aligned + * multi-line property. Continuation lines mix tabs for indent and + * spaces for alignment by design; that must not be flagged. + */ + +/dts-v1/; + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller@10000 { + compatible = "example,intc"; + reg = <0x10000 0x1000>; + interrupts = <1 2 3>, + <4 5 6>, + <7 8 9>; + pinmux = < + 0x01 + 0x02 + >; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-tab.dts b/scripts/dtc/dt-style-selftest/good/dts-tab.dts new file mode 100644 index 000000000000..ab7b5d1242ba --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-tab.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: a properly formatted .dts using one-tab indent. + */ + +/dts-v1/; + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; + + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@100 { + compatible = "example,test"; + reg = <0x100 0x10>; + }; + + device@200 { + compatible = "example,test"; + reg = <0x200 0x10>; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/yaml-4space.yaml b/scripts/dtc/dt-style-selftest/good/yaml-4space.yaml new file mode 100644 index 000000000000..1502f803c24c --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/yaml-4space.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-good-4space.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture for dt-check-style + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-4space + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@100 { + compatible = "example,test-4space"; + reg = <0x100 0x10>; + }; + + device@200 { + compatible = "example,test-4space"; + reg = <0x200 0x10>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/good/yaml-tricky-parsing.yaml b/scripts/dtc/dt-style-selftest/good/yaml-tricky-parsing.yaml new file mode 100644 index 000000000000..a836d5f36b93 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/yaml-tricky-parsing.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-good-tricky-parsing.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture exercising parser corner cases + +description: | + Covers patterns that previously broke the classifier: + - "/* ... */ code;" with code following the closing of a one-line + block comment must still parse the code. + - a label on a node whose name starts with a digit (1wire@10). + - a multi-line C preprocessor macro using backslash continuations + must not be parsed as DTS lines. + - a /* comment that opens and closes on the same code line must + not leave the parser in block-comment state. + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-tricky-parsing + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #define MY_REG(a, b) \ + ((a) << 16 | \ + (b) << 0) + + one_wire: 1wire@10 { + compatible = "example,test-tricky-parsing"; + reg = <MY_REG(0x10, 0)>; + /* inline-closed */ status = "okay"; + }; + + other: device@20 { + compatible = "example,test-tricky-parsing"; + reg = <0x20 0x10>; + }; + + &one_wire { + status = "okay"; + }; + + &other { + status = "okay"; + }; diff --git a/scripts/dtc/dt-style-selftest/run.sh b/scripts/dtc/dt-style-selftest/run.sh new file mode 100755 index 000000000000..8117dd9be90a --- /dev/null +++ b/scripts/dtc/dt-style-selftest/run.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Run dt-check-style against fixtures under good/ and bad/. +# good/ files must produce no output and exit 0 in both modes. +# bad/ files must produce the expected output (in expected/<name>.txt) +# and exit 1. +# +# The mode used for a bad fixture is whichever produces a violation: +# trailing-whitespace and tab fixtures use the default (relaxed), +# the rest use --mode=strict. The expected output files name the +# mode in their first line. + +set -u + +here=$(cd "$(dirname "$0")" && pwd) +tool="$here/../dt-check-style" +fail=0 + +run() { + file=$1 + mode=$2 + "$tool" --mode="$mode" "$file" 2>&1 +} + +# good/ -- must exit 0 and produce no output in both modes +for f in "$here"/good/*; do + [ -e "$f" ] || continue + for mode in relaxed strict; do + out=$(run "$f" "$mode") + rc=$? + if [ -n "$out" ] || [ "$rc" -ne 0 ]; then + echo "FAIL good/$mode: $(basename "$f") (exit $rc, want 0):" + echo "$out" | sed 's/^/ /' + fail=$((fail + 1)) + fi + done +done + +# bad/ -- must match expected/<name>.txt +for f in "$here"/bad/*; do + [ -e "$f" ] || continue + name=$(basename "$f") + expected="$here/expected/$name.txt" + if [ ! -f "$expected" ]; then + echo "FAIL bad: missing $expected" + fail=$((fail + 1)) + continue + fi + mode=$(head -1 "$expected" | sed 's/^# mode=//') + body=$(tail -n +2 "$expected") + out=$(run "$f" "$mode") + rc=$? + # Strip the directory prefix so expected files are portable. + out=$(printf '%s\n' "$out" | sed "s|$here/bad/|bad/|g") + if [ "$out" != "$body" ] || [ "$rc" -ne 1 ]; then + echo "FAIL bad/$mode: $name (exit $rc, want 1):" + bf=$(mktemp) + printf '%s\n' "$body" > "$bf" + printf '%s\n' "$out" | diff -u "$bf" - | sed 's/^/ /' + rm -f "$bf" + fail=$((fail + 1)) + fi +done + +if [ "$fail" -eq 0 ]; then + echo "PASS" + exit 0 +fi +echo "FAILED ($fail)" +exit 1 diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py index f4f715a8f0e3..a68ae91b4531 100644 --- a/scripts/gdb/linux/interrupts.py +++ b/scripts/gdb/linux/interrupts.py @@ -20,7 +20,7 @@ def irq_desc_is_chained(desc): def irqd_is_level(desc): return desc['irq_data']['common']['state_use_accessors'] & constants.LX_IRQD_LEVEL -def show_irq_desc(prec, irq): +def show_irq_desc(prec, chip_width, irq): text = "" desc = mapletree.mtree_load(gdb.parse_and_eval("&sparse_irqs"), irq) @@ -48,7 +48,7 @@ def show_irq_desc(prec, irq): count = cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt'] else: count = 0 - text += "%10u" % (count) + text += "%10u " % (count) name = "None" if desc['irq_data']['chip']: @@ -58,7 +58,7 @@ def show_irq_desc(prec, irq): else: name = "-" - text += " %8s" % (name) + text += " %-*s" % (chip_width, name) if desc['irq_data']['domain']: text += " %*lu" % (prec, desc['irq_data']['hwirq']) @@ -97,64 +97,29 @@ def show_irq_err_count(prec): text += "%*s: %10u\n" % (prec, "ERR", cnt['counter']) return text -def x86_show_irqstat(prec, pfx, field, desc): - irq_stat = gdb.parse_and_eval("&irq_stat") +def x86_show_irqstat(prec, pfx, idx, desc): + irq_stat = gdb.parse_and_eval("&irq_stat.counts[%d]" %idx) text = "%*s: " % (prec, pfx) for cpu in cpus.each_online_cpu(): stat = cpus.per_cpu(irq_stat, cpu) - text += "%10u " % (stat[field]) - text += " %s\n" % (desc) - return text - -def x86_show_mce(prec, var, pfx, desc): - pvar = gdb.parse_and_eval(var) - text = "%*s: " % (prec, pfx) - for cpu in cpus.each_online_cpu(): - text += "%10u " % (cpus.per_cpu(pvar, cpu).dereference()) - text += " %s\n" % (desc) + text += "%10u " % (stat.dereference()) + text += desc return text def x86_show_interupts(prec): - text = x86_show_irqstat(prec, "NMI", '__nmi_count', 'Non-maskable interrupts') - - if constants.LX_CONFIG_X86_LOCAL_APIC: - text += x86_show_irqstat(prec, "LOC", 'apic_timer_irqs', "Local timer interrupts") - text += x86_show_irqstat(prec, "SPU", 'irq_spurious_count', "Spurious interrupts") - text += x86_show_irqstat(prec, "PMI", 'apic_perf_irqs', "Performance monitoring interrupts") - text += x86_show_irqstat(prec, "IWI", 'apic_irq_work_irqs', "IRQ work interrupts") - text += x86_show_irqstat(prec, "RTR", 'icr_read_retry_count', "APIC ICR read retries") - if utils.gdb_eval_or_none("x86_platform_ipi_callback") is not None: - text += x86_show_irqstat(prec, "PLT", 'x86_platform_ipis', "Platform interrupts") - - if constants.LX_CONFIG_SMP: - text += x86_show_irqstat(prec, "RES", 'irq_resched_count', "Rescheduling interrupts") - text += x86_show_irqstat(prec, "CAL", 'irq_call_count', "Function call interrupts") - text += x86_show_irqstat(prec, "TLB", 'irq_tlb_count', "TLB shootdowns") - - if constants.LX_CONFIG_X86_THERMAL_VECTOR: - text += x86_show_irqstat(prec, "TRM", 'irq_thermal_count', "Thermal events interrupts") - - if constants.LX_CONFIG_X86_MCE_THRESHOLD: - text += x86_show_irqstat(prec, "THR", 'irq_threshold_count', "Threshold APIC interrupts") - - if constants.LX_CONFIG_X86_MCE_AMD: - text += x86_show_irqstat(prec, "DFR", 'irq_deferred_error_count', "Deferred Error APIC interrupts") + info_type = gdb.lookup_type('struct irq_stat_info') + info = gdb.parse_and_eval('irq_stat_info') + bitmap = gdb.parse_and_eval('irq_stat_count_show') + bitsperlong = 8 * int(bitmap.type.target().sizeof) - if constants.LX_CONFIG_X86_MCE: - text += x86_show_mce(prec, "&mce_exception_count", "MCE", "Machine check exceptions") - text += x86_show_mce(prec, "&mce_poll_count", "MCP", "Machine check polls") - - text += show_irq_err_count(prec) - - if constants.LX_CONFIG_X86_IO_APIC: - cnt = utils.gdb_eval_or_none("irq_mis_count") - if cnt is not None: - text += "%*s: %10u\n" % (prec, "MIS", cnt['counter']) - - if constants.LX_CONFIG_KVM: - text += x86_show_irqstat(prec, "PIN", 'kvm_posted_intr_ipis', 'Posted-interrupt notification event') - text += x86_show_irqstat(prec, "NPI", 'kvm_posted_intr_nested_ipis', 'Nested posted-interrupt event') - text += x86_show_irqstat(prec, "PIW", 'kvm_posted_intr_wakeup_ipis', 'Posted-interrupt wakeup event') + text = "" + for idx in range(int(info.type.sizeof / info_type.sizeof)): + show = bitmap[int(idx / bitsperlong)] + if not show & 1 << int(idx % bitsperlong): + continue + pfx = info[idx]['symbol'].string() + desc = info[idx]['text'].string() + text += x86_show_irqstat(prec, pfx, idx, desc) return text @@ -166,23 +131,19 @@ def arm_common_show_interrupts(prec): if nr_ipi is None or ipi_desc is None or ipi_types is None: return text - if prec >= 4: - sep = " " - else: - sep = "" - for ipi in range(nr_ipi): - text += "%*s%u:%s" % (prec - 1, "IPI", ipi, sep) + text += "%*s%u: " % (prec - 1, "IPI", ipi) desc = ipi_desc[ipi].cast(irq_desc_type.get_type().pointer()) if desc == 0: continue for cpu in cpus.each_online_cpu(): - text += "%10u" % (cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt']) - text += " %s" % (ipi_types[ipi].string()) + text += "%10u " % (cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt']) + text += "%s" % (ipi_types[ipi].string()) text += "\n" return text def aarch64_show_interrupts(prec): + # Does not work for ARM64 as "ipi_desc" is not available there text = arm_common_show_interrupts(prec) text += "%*s: %10lu\n" % (prec, "ERR", gdb.parse_and_eval("irq_err_count")) return text @@ -209,12 +170,19 @@ class LxInterruptList(gdb.Command): super(LxInterruptList, self).__init__("lx-interruptlist", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): - nr_irqs = gdb.parse_and_eval("nr_irqs") - prec = 3 - j = 1000 - while prec < 10 and j <= nr_irqs: - prec += 1 - j *= 10 + nr_irqs = gdb.parse_and_eval("total_nr_irqs") + constr = utils.gdb_eval_or_none('irq_proc_constraints') + + if constr: + prec = int(constr['num_prec']) + chip_width = int(constr['chip_width']) + else: + prec = 4 + j = 10000 + while prec < 10 and j <= nr_irqs: + prec += 1 + j *= 10 + chip_width = 8 gdb.write("%*s" % (prec + 8, "")) for cpu in cpus.each_online_cpu(): @@ -225,7 +193,7 @@ class LxInterruptList(gdb.Command): raise gdb.GdbError("Unable to find the sparse IRQ tree, is CONFIG_SPARSE_IRQ enabled?") for irq in range(nr_irqs): - gdb.write(show_irq_desc(prec, irq)) + gdb.write(show_irq_desc(prec, chip_width, irq)) gdb.write(arch_show_interrupts(prec)) diff --git a/scripts/gdb/linux/timerlist.py b/scripts/gdb/linux/timerlist.py index 9fb3436a217c..744b032e4d38 100644 --- a/scripts/gdb/linux/timerlist.py +++ b/scripts/gdb/linux/timerlist.py @@ -90,14 +90,10 @@ def print_cpu(hrtimer_bases, cpu, max_clock_bases): text += f" .{'nohz':15s}: {int(bool(ts['flags'] & TS_FLAG_NOHZ))}\n" text += f" .{'last_tick':15s}: {ts['last_tick']}\n" text += f" .{'tick_stopped':15s}: {int(bool(ts['flags'] & TS_FLAG_STOPPED))}\n" - text += f" .{'idle_jiffies':15s}: {ts['idle_jiffies']}\n" text += f" .{'idle_calls':15s}: {ts['idle_calls']}\n" text += f" .{'idle_sleeps':15s}: {ts['idle_sleeps']}\n" text += f" .{'idle_entrytime':15s}: {ts['idle_entrytime']} nsecs\n" text += f" .{'idle_waketime':15s}: {ts['idle_waketime']} nsecs\n" - text += f" .{'idle_exittime':15s}: {ts['idle_exittime']} nsecs\n" - text += f" .{'idle_sleeptime':15s}: {ts['idle_sleeptime']} nsecs\n" - text += f" .{'iowait_sleeptime':15s}: {ts['iowait_sleeptime']} nsecs\n" text += f" .{'last_jiffies':15s}: {ts['last_jiffies']}\n" text += f" .{'next_timer':15s}: {ts['next_timer']}\n" text += f" .{'idle_expires':15s}: {ts['idle_expires']} nsecs\n" diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index d5f9a0ca742c..dc1219736f77 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -26,6 +26,14 @@ def args_crates_cfgs(cfgs: List[str]) -> Dict[str, List[str]]: return crates_cfgs +def args_crates_envs(envs: List[str]) -> Dict[str, Dict[str, str]]: + crates_envs = {} + for env in envs: + crate, vals = env.split("=", 1) + crates_envs[crate] = dict(v.split("=", 1) for v in vals.split()) + + return crates_envs + class Dependency(TypedDict): crate: int name: str @@ -61,6 +69,7 @@ def generate_crates( sysroot_src: pathlib.Path, external_src: Optional[pathlib.Path], cfgs: List[str], + envs: List[str], core_edition: str, ) -> List[Crate]: # Generate the configuration list. @@ -74,6 +83,7 @@ def generate_crates( # Now fill the crates list. crates: List[Crate] = [] crates_cfgs = args_crates_cfgs(cfgs) + crates_envs = args_crates_envs(envs) def get_crate_name(path: pathlib.Path) -> str: return invoke_rustc(["--print", "crate-name", str(path)]) @@ -92,6 +102,10 @@ def generate_crates( is_workspace_member if is_workspace_member is not None else True ) edition = edition if edition is not None else "2021" + crate_env = { + "RUST_MODFILE": "This is only for rust-analyzer", + **crates_envs.get(display_name, {}), + } return { "display_name": display_name, "root_module": str(root_module), @@ -99,9 +113,7 @@ def generate_crates( "deps": deps, "cfg": cfg, "edition": edition, - "env": { - "RUST_MODFILE": "This is only for rust-analyzer" - } + "env": crate_env, } def append_proc_macro_crate( @@ -240,6 +252,12 @@ def generate_crates( [std, proc_macro, proc_macro2, quote, syn], ) + zerocopy_derive = append_proc_macro_crate( + "zerocopy_derive", + srctree / "rust" / "zerocopy-derive" / "lib.rs", + [std, proc_macro, proc_macro2, quote, syn], + ) + build_error = append_crate( "build_error", srctree / "rust" / "build_error.rs", @@ -264,6 +282,12 @@ def generate_crates( [core, compiler_builtins], ) + zerocopy = append_crate( + "zerocopy", + srctree / "rust" / "zerocopy" / "src" / "lib.rs", + [core, compiler_builtins], + ) + def append_crate_with_generated( display_name: str, deps: List[Dependency], @@ -292,7 +316,7 @@ def generate_crates( bindings = append_crate_with_generated("bindings", [core, ffi, pin_init]) uapi = append_crate_with_generated("uapi", [core, ffi, pin_init]) kernel = append_crate_with_generated( - "kernel", [core, macros, build_error, pin_init, ffi, bindings, uapi] + "kernel", [core, macros, build_error, pin_init, ffi, bindings, uapi, zerocopy, zerocopy_derive] ) scripts = srctree / "scripts" @@ -337,7 +361,7 @@ def generate_crates( append_crate( crate_name, path, - [core, kernel, pin_init], + [core, kernel, pin_init, zerocopy, zerocopy_derive], cfg=generated_cfg, ) @@ -347,6 +371,7 @@ def main() -> None: parser = argparse.ArgumentParser() parser.add_argument('--verbose', '-v', action='store_true') parser.add_argument('--cfgs', action='append', default=[]) + parser.add_argument('--envs', action='append', default=[]) parser.add_argument("core_edition") parser.add_argument("srctree", type=pathlib.Path) parser.add_argument("objtree", type=pathlib.Path) @@ -357,6 +382,7 @@ def main() -> None: class Args(argparse.Namespace): verbose: bool cfgs: List[str] + envs: List[str] srctree: pathlib.Path objtree: pathlib.Path sysroot: pathlib.Path @@ -372,7 +398,7 @@ def main() -> None: ) rust_project = { - "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs, args.core_edition), + "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs, args.envs, args.core_edition), "sysroot": str(args.sysroot), } diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs index 16f7e855e012..3bf296581a88 100644 --- a/scripts/generate_rust_target.rs +++ b/scripts/generate_rust_target.rs @@ -260,6 +260,8 @@ fn main() { } } else if cfg.has("LOONGARCH") { panic!("loongarch uses the builtin rustc loongarch64-unknown-none-softfloat target"); + } else if cfg.has("S390") { + panic!("s390 uses the builtin rustc s390x-unknown-none-softfloat target"); } else { panic!("Unsupported architecture"); } diff --git a/scripts/jobserver-exec b/scripts/jobserver-exec index 758e947a6fb9..21b319e6c9a5 100755 --- a/scripts/jobserver-exec +++ b/scripts/jobserver-exec @@ -28,8 +28,8 @@ def main(): sys.exit("usage: " + name +" command [args ...]\n" + __doc__) with JobserverExec() as jobserver: - jobserver.run(sys.argv[1:]) + return jobserver.run(sys.argv[1:]) if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c index a7b44cd8ae14..c368bec5ab60 100644 --- a/scripts/kconfig/conf.c +++ b/scripts/kconfig/conf.c @@ -297,9 +297,7 @@ static int conf_askvalue(struct symbol *sym, const char *def) line[1] = 0; if (!sym_is_changeable(sym)) { - printf("%s\n", def); - line[0] = '\n'; - line[1] = 0; + printf("%s\n", def ?: ""); return 0; } @@ -307,7 +305,7 @@ static int conf_askvalue(struct symbol *sym, const char *def) case oldconfig: case syncconfig: if (sym_has_value(sym)) { - printf("%s\n", def); + printf("%s\n", def ?: ""); return 0; } /* fall through */ diff --git a/scripts/kconfig/kconfig-sym-check.pl b/scripts/kconfig/kconfig-sym-check.pl new file mode 100755 index 000000000000..daa5285fdefc --- /dev/null +++ b/scripts/kconfig/kconfig-sym-check.pl @@ -0,0 +1,132 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 + +use warnings; +use strict; + +my $srctree = shift @ARGV; +unless (defined $srctree) { + $srctree = `git rev-parse --show-toplevel 2>/dev/null`; + chomp $srctree; + my $msg = "Usage: $0 <srctree> [excludes file]\n"; + $msg .= "Please provide <srctree>."; + $msg .= " Is it '$srctree'?" if $srctree; + $msg .= "\n"; + die $msg; +} +my $kconfig_sym_check_excludes = defined $ARGV[0] ? $ARGV[0] : undef; + +sub indent_depth { + my ($ws) = @_; + my $col = 0; + for my $c (split //, $ws) { + $col = $c eq "\t" ? int($col / 8) * 8 + 8 : $col + 1; + } + return $col; +} + +my @files = `git -C \Q$srctree\E ls-files '*Kconfig*' 2>/dev/null`; +if (@files) { + chomp @files; + @files = map { "$srctree/$_" } @files; +} else { + @files = `find \Q$srctree\E -name '*Kconfig*'`; + chomp @files; +} + +@files = grep { !m{/scripts/kconfig/tests/} } @files; + +my %configs = (); +my %refs = (); + +foreach my $file (@files) { + open F, $file or die "Cannot open $file: $!"; + + my $help = 0; + my $help_level; + my $level; + + while (<F>) { + chomp; + + while (/\\\s*$/) { + s/\\\s*$/ /; + my $cont = <F> // last; + chomp $cont; + $_ .= $cont; + } + + next if /^\s*$/; + next if /^\s*#/; + + /^(\s*)/; + $level = indent_depth($1); + + if ($help && $level < $help_level) { + $help = 0; + } + + next if ($help); + + if (/^\s*(help|\-\-\-help\-\-\-)$/) { + $help = 1; + my $next; + while (defined($next = <F>)) { + last unless $next =~ /^\s*(?:#.*)?$/; + } + last unless defined $next; + $next =~ /^(\s*)/; + if (indent_depth($1) >= $level) { + $help_level = indent_depth($1); + } else { + $help = 0; + } + $_ = $next; + redo; + } + + if (/^\s*(config|menuconfig)\s+([a-zA-Z0-9_]+)\s*(#.*)?$/) { + $configs{$2}++; + next; + } + + if (/^\s*(default|def_bool|def_tristate|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) { + my $s = $2; + $s =~ s/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'//g; + $s =~ s/#.*//; + $s =~ s/\$\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\)//g; + $s =~ s/%%[^%]*%%//g; + my @syms = split /[^a-zA-Z0-9_]+/, $s; + map { + $refs{$_}++ if (/[a-zA-Z]/ && $_ ne "if" && $_ ne "y" && $_ ne "n" && $_ ne "m" && !/^0[xX][0-9a-fA-F]+$/); + } @syms + } + } + + close F; +} + +my %known_syms = (); +if (defined $kconfig_sym_check_excludes) { + my $file = $kconfig_sym_check_excludes; + open(F, "<", $file) or die "Cannot open $file: $!"; + while (<F>) { + chomp; + next if /^\s*$/; + next if /^\s*#/; + $known_syms{$1}++ if (/^\s*([a-zA-Z0-9_]+)\s*(#.*)?$/); + } +} + +my $ret = 0; +foreach my $k (sort keys %refs) { + next if (exists $configs{$k} || exists $known_syms{$k}); + + print "$k"; + print " - warning: '$k' is probably not what you want; Kconfig tristate literals are always lowercase ('n', 'y', 'm')" if ($k eq "N" || $k eq "Y" || $k eq "M"); + print "\n"; + + $ret = 1; +} + +exit $ret; diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py index ffd469d1f226..791ed659c76b 100644 --- a/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py +++ b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py @@ -8,7 +8,7 @@ for symbols with unmet dependency. This was not working correctly for choice values because choice needs a bit different symbol computation. -This checks that no unneeded "# COFIG_... is not set" is contained in +This checks that no unneeded "# CONFIG_... is not set" is contained in the .config file. Related Linux commit: cb67ab2cd2b8abd9650292c986c79901e3073a59 diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 0ad7e6631314..c4a7acf8edc3 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -3,7 +3,7 @@ # # Build a livepatch module -# shellcheck disable=SC1090,SC2155 +# shellcheck disable=SC1090,SC2155,SC2164 if (( BASH_VERSINFO[0] < 4 || \ (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then @@ -11,21 +11,19 @@ if (( BASH_VERSINFO[0] < 4 || \ exit 1 fi -set -o errexit set -o errtrace set -o pipefail set -o nounset # Allow doing 'cmd | mapfile -t array' instead of 'mapfile -t array < <(cmd)'. -# This helps keep execution in pipes so pipefail+errexit can catch errors. +# This helps keep execution in pipes so pipefail+ERR trap can catch errors. shopt -s lastpipe -unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE +unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE REPLACE=1 SHORT_CIRCUIT=0 JOBS="$(getconf _NPROCESSORS_ONLN)" -VERBOSE="-s" shopt -o xtrace | grep -q 'on' && XTRACE=1 # Avoid removing the previous $TMP_DIR until args have been fully processed. @@ -35,16 +33,16 @@ SCRIPT="$(basename "$0")" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines" -SRC="$(pwd)" -OBJ="$(pwd)" +OBJTOOL="$PWD/tools/objtool/objtool" +CONFIG="$PWD/.config" +TMP_DIR="$PWD/klp-tmp" -CONFIG="$OBJ/.config" -TMP_DIR="$OBJ/klp-tmp" - -ORIG_DIR="$TMP_DIR/orig" -PATCHED_DIR="$TMP_DIR/patched" -DIFF_DIR="$TMP_DIR/diff" -KMOD_DIR="$TMP_DIR/kmod" +ORIG_DIR="$TMP_DIR/1-orig" +PATCHED_DIR="$TMP_DIR/2-patched" +ORIG_CSUM_DIR="$TMP_DIR/3-checksum-orig" +PATCHED_CSUM_DIR="$TMP_DIR/3-checksum-patched" +DIFF_DIR="$TMP_DIR/4-diff" +KMOD_DIR="$TMP_DIR/5-kmod" STASH_DIR="$TMP_DIR/stash" TIMESTAMP="$TMP_DIR/timestamp" @@ -90,7 +88,7 @@ declare -a STASHED_FILES stash_file() { local file="$1" - local rel_file="${file#"$SRC"/}" + local rel_file="${file#"$PWD"/}" [[ ! -e "$file" ]] && die "no file to stash: $file" @@ -104,7 +102,7 @@ restore_files() { local file for file in "${STASHED_FILES[@]}"; do - mv -f "$STASH_DIR/$file" "$SRC/$file" || warn "can't restore file: $file" + mv -f "$STASH_DIR/$file" "$PWD/$file" || warn "can't restore file: $file" done STASHED_FILES=() @@ -140,10 +138,11 @@ Options: Advanced Options: -d, --debug Show symbol/reloc cloning decisions -S, --short-circuit=STEP Start at build step (requires prior --keep-tmp) - 1|orig Build original kernel (default) - 2|patched Build patched kernel - 3|diff Diff objects - 4|kmod Build patch module + 1|orig Build original kernel (default) + 2|patched Build patched kernel + 3|checksum Generate checksums + 4|diff Diff objects + 5|kmod Build patch module -T, --keep-tmp Preserve tmp dir on exit EOF @@ -158,6 +157,7 @@ process_args() { local short local long local args + local patch short="hfj:o:vdS:T" long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp" @@ -194,7 +194,7 @@ process_args() { shift ;; -v | --verbose) - VERBOSE="V=1" + VERBOSE=1 shift ;; -d | --debug) @@ -206,10 +206,11 @@ process_args() { [[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir" keep_tmp=1 case "$2" in - 1 | orig) SHORT_CIRCUIT=1; ;; - 2 | patched) SHORT_CIRCUIT=2; ;; - 3 | diff) SHORT_CIRCUIT=3; ;; - 4 | mod) SHORT_CIRCUIT=4; ;; + 1 | orig) SHORT_CIRCUIT=1; ;; + 2 | patched) SHORT_CIRCUIT=2; ;; + 3 | checksum) SHORT_CIRCUIT=3; ;; + 4 | diff) SHORT_CIRCUIT=4; ;; + 5 | kmod) SHORT_CIRCUIT=5; ;; *) die "invalid short-circuit step '$2'" ;; esac shift 2 @@ -236,6 +237,10 @@ process_args() { KEEP_TMP="$keep_tmp" PATCHES=("$@") + + for patch in "${PATCHES[@]}"; do + [[ -f "$patch" ]] || die "$patch doesn't exist" + done } # temporarily disable xtrace for especially verbose code @@ -270,6 +275,9 @@ validate_config() { [[ "$CONFIG_AS_VERSION" -lt 200000 ]] && \ die "Clang assembler version < 20 not supported" + [[ -x "$OBJTOOL" ]] && "$OBJTOOL" klp 2>&1 | command grep -q "not implemented" && \ + die "objtool not built with KLP support; install xxhash-devel/libxxhash-dev (version >= 0.8) and recompile" + return 0 } @@ -301,12 +309,17 @@ set_module_name() { # Hardcode the value printed by the localversion script to prevent patch # application from appending it with '+' due to a dirty working tree. set_kernelversion() { - local file="$SRC/scripts/setlocalversion" + local file="$PWD/scripts/setlocalversion" local kernelrelease stash_file "$file" - kernelrelease="$(cd "$SRC" && make syncconfig &>/dev/null && make -s kernelrelease)" + if [[ -n "$(make -s listnewconfig 2>/dev/null)" ]]; then + die ".config mismatch, check your .config or run 'make olddefconfig'" + fi + make syncconfig &>/dev/null || die "make syncconfig failed" + + kernelrelease="$(make -s kernelrelease)" [[ -z "$kernelrelease" ]] && die "failed to get kernel version" sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion @@ -349,7 +362,7 @@ check_unsupported_patches() { for file in "${files[@]}"; do case "$file" in - lib/*|*.S) + lib/*|*/vdso/*|*/realmode/rm/*|*.S) die "${patch}: unsupported patch to $file" ;; esac @@ -367,24 +380,24 @@ apply_patch() { [[ ! -f "$patch" ]] && die "$patch doesn't exist" status=0 - output=$(patch -d "$SRC" -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? + output=$(patch -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? if [[ "$status" -ne 0 ]]; then echo "$output" >&2 die "$patch did not apply" elif [[ "$output" =~ $drift_regex ]]; then - echo "$output" >&2 + [[ -v VERBOSE ]] && echo "$output" >&2 warn "${patch} applied with fuzz" fi - patch -d "$SRC" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" APPLIED_PATCHES+=("$patch") + patch -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" } revert_patch() { local patch="$1" local tmp=() - patch -d "$SRC" -p1 -R --silent --no-backup-if-mismatch -r /dev/null < "$patch" + patch -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true for p in "${APPLIED_PATCHES[@]}"; do [[ "$p" == "$patch" ]] && continue @@ -422,8 +435,21 @@ validate_patches() { do_init() { # We're not yet smart enough to handle anything other than in-tree # builds in pwd. - [[ ! "$SRC" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" - [[ ! "$OBJ" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" + [[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" + + if (( SHORT_CIRCUIT >= 2 )); then + [[ -f "$ORIG_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_DIR" + fi + if (( SHORT_CIRCUIT >= 3 )); then + [[ -f "$PATCHED_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_DIR" + fi + if (( SHORT_CIRCUIT >= 4 )); then + [[ -f "$ORIG_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_CSUM_DIR" + [[ -f "$PATCHED_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_CSUM_DIR" + fi + if (( SHORT_CIRCUIT >= 5 )); then + [[ -f "$DIFF_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $DIFF_DIR" + fi (( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR" mkdir -p "$TMP_DIR" @@ -454,11 +480,11 @@ refresh_patch() { get_patch_output_files "$patch" | mapfile -t output_files # Copy orig source files to 'a' - ( cd "$SRC" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) + echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" # Copy patched source files to 'b' apply_patch "$patch" "--silent" - ( cd "$SRC" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" ) + echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" revert_patch "$patch" # Diff 'a' and 'b' to make a clean patch @@ -502,20 +528,14 @@ clean_kernel() { cmd+=("-j$JOBS") cmd+=("clean") - ( - cd "$SRC" - "${cmd[@]}" - ) + "${cmd[@]}" } build_kernel() { local build="$1" local log="$TMP_DIR/build.log" - local objtool_args=() local cmd=() - objtool_args=("--checksum") - cmd=("make") # When a patch to a kernel module references a newly created unexported @@ -535,19 +555,20 @@ build_kernel() { # cmd+=("KBUILD_MODPOST_WARN=1") - cmd+=("$VERBOSE") + if [[ -v VERBOSE ]]; then + cmd+=("V=1") + else + cmd+=("-s") + fi cmd+=("-j$JOBS") cmd+=("KCFLAGS=-ffunction-sections -fdata-sections") - cmd+=("OBJTOOL_ARGS=${objtool_args[*]}") cmd+=("vmlinux") cmd+=("modules") - ( - cd "$SRC" - "${cmd[@]}" \ - 1> >(tee -a "$log") \ - 2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2) - ) || die "$build kernel build failed" + "${cmd[@]}" \ + 1> >(tee -a "$log") \ + 2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2) \ + || die "$build kernel build failed" } find_objects() { @@ -555,9 +576,9 @@ find_objects() { # Find root-level vmlinux.o and non-root-level .ko files, # excluding klp-tmp/ and .git/ - find "$OBJ" \( -path "$TMP_DIR" -o -path "$OBJ/.git" -o -regex "$OBJ/[^/][^/]*\.ko" \) -prune -o \ + find "$PWD" \( -path "$TMP_DIR" -o -path "$PWD/.git" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ -type f "${opts[@]}" \ - \( -name "*.ko" -o -path "$OBJ/vmlinux.o" \) \ + \( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \ -printf '%P\n' } @@ -570,10 +591,10 @@ copy_orig_objects() { find_objects | mapfile -t files - xtrace_save "copying orig objects" + xtrace_save "copying original objects" for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" - local file="$OBJ/$rel_file" + local file="$PWD/$rel_file" local orig_file="$ORIG_DIR/$rel_file" local orig_dir="$(dirname "$orig_file")" @@ -586,6 +607,7 @@ copy_orig_objects() { mv -f "$TMP_DIR/build.log" "$ORIG_DIR" touch "$TIMESTAMP" + touch "$ORIG_DIR/.complete" } # Copy all changed objects to $PATCHED_DIR @@ -606,7 +628,7 @@ copy_patched_objects() { xtrace_save "copying changed objects" for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" - local file="$OBJ/$rel_file" + local file="$PWD/$rel_file" local orig_file="$ORIG_DIR/$rel_file" local patched_file="$PATCHED_DIR/$rel_file" local patched_dir="$(dirname "$patched_file")" @@ -624,6 +646,36 @@ copy_patched_objects() { (( found == 0 )) && die "no changes detected" mv -f "$TMP_DIR/build.log" "$PATCHED_DIR" + touch "$PATCHED_DIR/.complete" +} + +# Copy .o files to a separate directory and run "objtool klp checksum" on each +# copy. The checksums are written to a .discard.sym_checksum section. +# +# If match_dir is given, only process files which also exist there. +generate_checksums() { + local src_dir="$1" + local dest_dir="$2" + local match_dir="${3:-}" + local files=() + local file + + rm -rf "$dest_dir" + mkdir -p "$dest_dir" + + find "$src_dir" -type f -name "*.o" | mapfile -t files + for file in "${files[@]}"; do + local rel="${file#"$src_dir"/}" + local dest="$dest_dir/$rel" + + [[ -n "$match_dir" && ! -f "$match_dir/$rel" ]] && continue + + mkdir -p "$(dirname "$dest")" + cp -f "$file" "$dest" + "$OBJTOOL" klp checksum "$dest" + done + + touch "$dest_dir/.complete" } # Diff changed objects, writing output object to $DIFF_DIR @@ -635,23 +687,23 @@ diff_objects() { rm -rf "$DIFF_DIR" mkdir -p "$DIFF_DIR" - find "$PATCHED_DIR" -type f -name "*.o" | mapfile -t files + find "$PATCHED_CSUM_DIR" -type f -name "*.o" | mapfile -t files [[ ${#files[@]} -eq 0 ]] && die "no changes detected" [[ -v DEBUG_CLONE ]] && opts=("--debug") # Diff all changed objects for file in "${files[@]}"; do - local rel_file="${file#"$PATCHED_DIR"/}" + local rel_file="${file#"$PATCHED_CSUM_DIR"/}" local orig_file="$rel_file" - local patched_file="$PATCHED_DIR/$rel_file" + local patched_file="$PATCHED_CSUM_DIR/$rel_file" local out_file="$DIFF_DIR/$rel_file" local filter=() local cmd=() mkdir -p "$(dirname "$out_file")" - cmd=("$SRC/tools/objtool/objtool") + cmd=("$OBJTOOL") cmd+=("klp") cmd+=("diff") (( ${#opts[@]} > 0 )) && cmd+=("${opts[@]}") @@ -668,18 +720,21 @@ diff_objects() { fi ( - cd "$ORIG_DIR" + cd "$ORIG_CSUM_DIR" + [[ -v VERBOSE ]] && echo "cd $ORIG_CSUM_DIR && ${cmd[*]}" "${cmd[@]}" \ 1> >(tee -a "$log") \ 2> >(tee -a "$log" | "${filter[@]}" >&2) || \ die "objtool klp diff failed" ) done + + touch "$DIFF_DIR/.complete" } -# For each changed object, run objtool with --debug-checksum to get the -# per-instruction checksums, and then diff those to find the first changed -# instruction for each function. +# For each changed object, run "objtool klp checksum" with --debug-checksum to +# get the per-instruction checksums, and then diff those to find the first +# changed instruction for each function. diff_checksums() { local orig_log="$ORIG_DIR/checksum.log" local patched_log="$PATCHED_DIR/checksum.log" @@ -703,9 +758,8 @@ diff_checksums() { fi done - cmd=("$SRC/tools/objtool/objtool") - cmd+=("--checksum") - cmd+=("--link") + cmd=("$OBJTOOL") + cmd+=("klp" "checksum") cmd+=("--dry-run") for file in "${!funcs[@]}"; do @@ -714,21 +768,37 @@ diff_checksums() { ( cd "$ORIG_DIR" "${cmd[@]}" "$opt" "$file" &> "$orig_log" || \ - ( cat "$orig_log" >&2; die "objtool --debug-checksum failed" ) + ( cat "$orig_log" >&2; die "objtool klp checksum failed" ) cd "$PATCHED_DIR" "${cmd[@]}" "$opt" "$file" &> "$patched_log" || \ - ( cat "$patched_log" >&2; die "objtool --debug-checksum failed" ) + ( cat "$patched_log" >&2; die "objtool klp checksum failed" ) ) for func in ${funcs[$file]}; do - diff <( grep0 -E "^DEBUG: .*checksum: $func " "$orig_log" | sed "s|$ORIG_DIR/||") \ - <( grep0 -E "^DEBUG: .*checksum: $func " "$patched_log" | sed "s|$PATCHED_DIR/||") \ - | gawk '/^< DEBUG: / { - gsub(/:/, "") - printf "%s: %s: %s\n", $3, $5, $6 - exit - }' || true + local -a orig patched + paste <(grep0 -E "^DEBUG: .*checksum: $func " "$orig_log") \ + <(grep0 -E "^DEBUG: .*checksum: $func " "$patched_log") | + while IFS= read -r line; do + read -ra orig <<< "${line%%$'\t'*}" + read -ra patched <<< "${line#*$'\t'}" + + if [[ ${#patched[@]} -eq 0 ]]; then + printf "%s: %s: %s (removed)\n" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}" + break + elif [[ ${#orig[@]} -eq 0 ]]; then + printf "%s: %s: %s (added)\n" "${patched[1]%:}" "${patched[3]}" "${patched[-2]}" + break + fi + + [[ "${orig[-1]}" == "${patched[-1]}" ]] && continue + + printf "%s: %s: %s" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}" + [[ "${orig[-2]}" != "${patched[-2]}" ]] && \ + printf " (patched: %s)" "${patched[-2]}" + printf "\n" + break + done || true done done } @@ -745,7 +815,7 @@ build_patch_module() { rm -rf "$KMOD_DIR" mkdir -p "$KMOD_DIR" - cp -f "$SRC/scripts/livepatch/init.c" "$KMOD_DIR" + cp -f "$SCRIPT_DIR/init.c" "$KMOD_DIR" echo "obj-m := $NAME.o" > "$makefile" echo -n "$NAME-y := init.o" >> "$makefile" @@ -780,19 +850,20 @@ build_patch_module() { [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE") cmd=("make") - cmd+=("$VERBOSE") + if [[ -v VERBOSE ]]; then + cmd+=("V=1") + else + cmd+=("-s") + fi cmd+=("-j$JOBS") cmd+=("--directory=.") cmd+=("M=$KMOD_DIR") cmd+=("KCFLAGS=${cflags[*]}") # Build a "normal" kernel module with init.c and the diffed objects - ( - cd "$SRC" - "${cmd[@]}" \ - 1> >(tee -a "$log") \ - 2> >(tee -a "$log" >&2) - ) + "${cmd[@]}" \ + 1> >(tee -a "$log") \ + 2> >(tee -a "$log" >&2) kmod_file="$KMOD_DIR/$NAME.ko" @@ -803,7 +874,7 @@ build_patch_module() { objcopy --remove-section=.BTF "$kmod_file" # Fix (and work around) linker wreckage for klp syms / relocs - "$SRC/tools/objtool/objtool" klp post-link "$kmod_file" || die "objtool klp post-link failed" + "$OBJTOOL" klp post-link "$kmod_file" || die "objtool klp post-link failed" cp -f "$kmod_file" "$OUTFILE" } @@ -839,6 +910,13 @@ if (( SHORT_CIRCUIT <= 2 )); then fi if (( SHORT_CIRCUIT <= 3 )); then + status "Generating original checksums" + generate_checksums "$ORIG_DIR" "$ORIG_CSUM_DIR" "$PATCHED_DIR" + status "Generating patched checksums" + generate_checksums "$PATCHED_DIR" "$PATCHED_CSUM_DIR" +fi + +if (( SHORT_CIRCUIT <= 4 )); then status "Diffing objects" diff_objects if [[ -v DIFF_CHECKSUM ]]; then @@ -847,7 +925,7 @@ if (( SHORT_CIRCUIT <= 3 )); then fi fi -if (( SHORT_CIRCUIT <= 4 )); then +if (( SHORT_CIRCUIT <= 5 )); then status "Building patch module: $OUTFILE" build_patch_module fi diff --git a/scripts/min-tool-version.sh b/scripts/min-tool-version.sh index b96ec2d379b6..031f2192b390 100755 --- a/scripts/min-tool-version.sh +++ b/scripts/min-tool-version.sh @@ -27,11 +27,15 @@ llvm) if [ "$SRCARCH" = loongarch ]; then echo 18.0.0 else - echo 15.0.0 + echo 17.0.1 fi ;; rustc) - echo 1.85.0 + if [ "$SRCARCH" = "s390" ]; then + echo 1.96.0 + else + echo 1.85.0 + fi ;; bindgen) echo 0.71.1 diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c index abbcd3fc1394..d592548cbd60 100644 --- a/scripts/mod/modpost.c +++ b/scripts/mod/modpost.c @@ -765,6 +765,8 @@ static const char *const section_white_list[] = ".gnu.lto*", ".discard.*", ".llvm.call-graph-profile", /* call graph */ + "__llvm_covfun", + "__llvm_covmap", NULL }; @@ -1487,13 +1489,22 @@ static void extract_crcs_for_object(const char *object, struct module *mod) char cmd_file[PATH_MAX]; char *buf, *p; const char *base; - int dirlen, ret; + int dirlen, baselen_without_suffix, ret; base = get_basename(object); dirlen = base - object; - ret = snprintf(cmd_file, sizeof(cmd_file), "%.*s.%s.cmd", - dirlen, object, base); + baselen_without_suffix = strlen(object) - dirlen - strlen(".o"); + + /* + * When CONFIG_LTO_CLANG_THIN_DIST=y, the ELF is *.thinlto-native.o + * but the symbol CRCs are recorded in *.o.cmd file. + */ + if (strends(object, ".thinlto-native.o")) + baselen_without_suffix -= strlen(".thinlto-native"); + + ret = snprintf(cmd_file, sizeof(cmd_file), "%.*s.%.*s.o.cmd", + dirlen, object, baselen_without_suffix, base); if (ret >= sizeof(cmd_file)) { error("%s: too long path was truncated\n", cmd_file); return; @@ -1689,8 +1700,17 @@ void __attribute__((format(printf, 2, 3))) buf_printf(struct buffer *buf, va_start(ap, fmt); len = vsnprintf(tmp, SZ, fmt, ap); - buf_write(buf, tmp, len); va_end(ap); + + if (len < 0) { + perror("vsnprintf failed"); + exit(1); + } + if (len >= SZ) + fatal("buf_printf output truncated for string %s: %d bytes needed, %d available\n", + tmp, len + 1, SZ); + + buf_write(buf, tmp, len); } void buf_write(struct buffer *buf, const char *s, int len) diff --git a/scripts/package/PKGBUILD b/scripts/package/PKGBUILD index 1213c8e04671..66e4b6a37783 100644 --- a/scripts/package/PKGBUILD +++ b/scripts/package/PKGBUILD @@ -121,6 +121,9 @@ _package-debug(){ install -Dt "${debugdir}" -m644 vmlinux mkdir -p "${builddir}" ln -sr "${debugdir}/vmlinux" "${builddir}/vmlinux" + + echo "Installing unstripped vDSO(s)..." + ${MAKE} INSTALL_MOD_PATH="${pkgdir}/usr" vdso_install } for _p in "${pkgname[@]}"; do diff --git a/scripts/package/kernel.spec b/scripts/package/kernel.spec index b3c956205af0..c732415662ef 100644 --- a/scripts/package/kernel.spec +++ b/scripts/package/kernel.spec @@ -6,7 +6,7 @@ Name: kernel Summary: The Linux Kernel Version: %(echo %{KERNELRELEASE} | sed -e 's/-/_/g') -Release: %{pkg_release} +Release: %{pkg_release}%{?dist} License: GPL Group: System Environment/Kernel Vendor: The Linux Community diff --git a/scripts/timer_migration_tree.py b/scripts/timer_migration_tree.py new file mode 100755 index 000000000000..faac9de854bd --- /dev/null +++ b/scripts/timer_migration_tree.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +Draw the timer migration tree. + +1) Boot with trace_event==tmigr_connect_cpu_parent,tmigr_connect_child_parent +2) ./timer_migration_tree.py < /sys/kernel/tracing/trace +""" + +import re, sys +from ete3 import Tree + +class Node: + def __init__(self, group): + self.group = group + self.children = [] + self.parent = None + self.num_children = 0 + self.groupmask = 0 + self.lvl = -1 + + def set_groupmask(self, groupmask): + self.groupmask = groupmask + + def set_parent(self, parent): + self.parent = parent + + def add_child(self, child): + self.children.append(child) + + def set_lvl(self, lvl): + self.lvl = lvl + + def set_numa(self, numa): + self.numa = numa + + def set_num_children(self, num_children): + self.num_children = num_children + + def __repr__(self): + if self.parent: + parent_grp = self.parent.group + else: + parent_grp = "-" + return "Group: %s mask: %s parent: %s lvl: %d numa: %d num_children: %d" % (self.group, self.groupmask, parent_grp, self.lvl, self.numa, self.num_children) + +hierarchies = { } + +def get_hierarchy(capacity): + if capacity not in hierarchies: + hierarchies[capacity] = {} + return hierarchies[capacity] + +def get_node(capacity, group): + hier = get_hierarchy(capacity) + if group in hier: + return hier[group] + else: + n = Node(group) + hier[group] = n + return n + +def tmigr_connect_cpu_parent(ts, line): + s = re.search("tmigr_connect_cpu_parent: cpu=([0-9]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) numa=([-]?[0-9]+) capacity=([-]?[0-9]+) num_children=([0-9]+)", line) + if s is None: + return False + (cpu, groupmask, parent, lvl, numa, capacity, num_children) = (int(s.group(1)), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6)), int(s.group(7))) + n = get_node(capacity, cpu) + p = get_node(capacity, parent) + n.set_parent(p) + n.set_groupmask(groupmask) + n.set_lvl(-1) + p.set_lvl(lvl) + p.set_numa(numa) + n.set_numa(numa) + p.set_num_children(num_children) + p.add_child(n) + +def tmigr_connect_child_parent(ts, line): + s = re.search("tmigr_connect_child_parent: group=([0-9a-zA-Z]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) numa=([-]?[0-9]+) capacity=([-]?[0-9]+) num_children=([0-9]+)", line) + if s is None: + return False + (group, groupmask, parent, lvl, numa, capacity, num_children) = (s.group(1), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6)), int(s.group(7))) + n = get_node(capacity, group) + p = get_node(capacity, parent) + n.set_parent(p) + n.set_groupmask(groupmask) + p.set_lvl(lvl) + p.set_numa(numa) + p.set_num_children(num_children) + p.add_child(n) + +def populate(enode, node): + enode = enode.add_child(name = node.group) + enode.add_feature("groupmask", "m:%s" % node.groupmask) + enode.add_feature("lvl", "lvl:%d" % node.lvl) + enode.add_feature("numa", "node %d" % node.numa) + enode.add_feature("num_children", "c=%d" % node.num_children) + for child in node.children: + populate(enode, child) + +if __name__ == "__main__": + for line in sys.stdin: + s = re.search("([0-9]+[.][0-9]{6}): (.+?)$", line, re.S) + if s is not None: + if tmigr_connect_cpu_parent(float(s.group(1)), s.group(2)): + continue + if tmigr_connect_child_parent(float(s.group(1)), s.group(2)): + continue + + for cap in hierarchies: + h = hierarchies[cap] + print("Tree for capacity %d" % cap) + for k in h: + n = h[k] + while n.parent != None: + n = n.parent + root = Tree() + populate(root, n) + print(root.get_ascii(show_internal=True, attributes=["name", "numa", "lvl"])) + break diff --git a/scripts/update-intel-ucode-defs.py b/scripts/update-intel-ucode-defs.py new file mode 100755 index 000000000000..9d6cc2c6075f --- /dev/null +++ b/scripts/update-intel-ucode-defs.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +import argparse +import re +import shutil +import subprocess +import sys +import os + +script = os.path.relpath(__file__) + +DESCRIPTION = f""" +For Intel CPUs, update the microcode revisions that determine +X86_BUG_OLD_MICROCODE. + +This script is intended to be run in response to releases of the +official Intel microcode GitHub repository: +https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files.git + +It takes the Intel microcode files as input and uses iucode-tool to +extract the revision information. It prints the output in the format +expected by intel-ucode-defs.h. + +Usage: + ./{script} /path/to/microcode/files > /path/to/intel-ucode-defs.h + +Typically, someone at Intel would see a new public release, wait for at +least three months to ensure the update is stable, run this script to +refresh the intel-ucode-defs.h file, and send a patch upstream to update +the mainline and stable versions. + +Any exception to this process should be supported with an appropriate +justification. +""" + +SIG_RE = re.compile(r'sig (0x[0-9a-fA-F]+)') +PFM_RE = re.compile(r'pf_mask (0x[0-9a-fA-F]+)') +REV_RE = re.compile(r'rev (0x[0-9a-fA-F]+)') + +# Functions to extract family, model, and stepping +def bits(val, bottom, top): + mask = (1 << (top + 1 - bottom)) - 1 + return (val >> bottom) & mask + +def family(sig): + if bits(sig, 8, 11) == 0xf: + return bits(sig, 8, 11) + bits(sig, 20, 27) + return bits(sig, 8, 11) + +def model(sig): + return bits(sig, 4, 7) | (bits(sig, 16, 19) << 4) + +def step(sig): + return bits(sig, 0, 3) + +class Ucode: + def __init__(self, sig, pfm, rev): + self.family = family(sig) + self.model = model(sig) + self.steppings = 1 << step(sig) + self.platforms = pfm + self.rev = rev + + self.key = (self.family, self.model, self.steppings, self.platforms) + + def __eq__(self, other): + return self.key == other.key + + def __hash__(self): + return hash(self.key) + + def __str__(self): + return "{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x%x, .model = 0x%02x, .steppings = 0x%04x, .platform_mask = 0x%02x, .driver_data = 0x%x }," % \ + (self.family, self.model, self.steppings, self.platforms, self.rev) + +def main(): + parser = argparse.ArgumentParser(description=DESCRIPTION, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('ucode_files', nargs='+', help='Path(s) to the microcode files') + + args = parser.parse_args() + + # Process the microcode files using iucode-tool + iucode_tool = shutil.which("iucode-tool") or shutil.which("iucode_tool") + if iucode_tool is None: + print("Error: iucode-tool not found, please install it", file=sys.stderr) + sys.exit(1) + + cmd = [iucode_tool, '--list-all'] + args.ucode_files + + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print("Error: iucode-tool ran into an error, exiting", file=sys.stderr) + if result.stderr: + print(result.stderr, file=sys.stderr, end='') + sys.exit(1) + + ucodes = set() + + # Parse the output of iucode-tool + for line in result.stdout.splitlines(): + sig_match = SIG_RE.search(line) + pfm_match = PFM_RE.search(line) + rev_match = REV_RE.search(line) + + if not (sig_match and pfm_match and rev_match): + continue + + sig = int(sig_match.group(1), 16) + pfm = int(pfm_match.group(1), 16) + rev = int(rev_match.group(1), 16) + debug_rev = bits(rev, 31, 31) + if debug_rev != 0: + print("Error: Debug ucode file found, exiting", file=sys.stderr) + sys.exit(1) + + ucodes.add(Ucode(sig, pfm, rev)) + + if not ucodes: + print("Error: No valid microcode files found, exiting", file=sys.stderr) + sys.exit(1) + + # Sort and print the microcode entries + print("/* SPDX-License-Identifier: GPL-2.0 */") + print("/* Auto-generated by scripts/update-intel-ucode-defs.py */") + for u in sorted(ucodes, key=lambda x: x.key): + print(u) + +if __name__ == "__main__": + main() |
