diff options
Diffstat (limited to 'scripts')
276 files changed, 22546 insertions, 10222 deletions
diff --git a/scripts/.gitignore b/scripts/.gitignore index 3dbb8bb2457b..c2ef68848da5 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only /asn1_compiler +/gen_packed_field_checks /generate_rust_target /insert-sys-cert /kallsyms diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include index 2f331879816b..8c311b997e24 100644 --- a/scripts/Kbuild.include +++ b/scripts/Kbuild.include @@ -62,14 +62,13 @@ stringify = $(squote)$(quote)$1$(quote)$(squote) ### # The path to Kbuild or Makefile. Kbuild has precedence over Makefile. -kbuild-dir = $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) -kbuild-file = $(or $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Makefile) +kbuild-file = $(or $(wildcard $(src)/Kbuild),$(src)/Makefile) ### # Read a file, replacing newlines with spaces # # Make 4.2 or later can read a file by using its builtin function. -ifneq ($(filter-out 3.% 4.0 4.1, $(MAKE_VERSION)),) +ifneq ($(filter-out 4.0 4.1, $(MAKE_VERSION)),) read-file = $(subst $(newline),$(space),$(file < $1)) else read-file = $(shell cat $1 2>/dev/null) @@ -206,7 +205,7 @@ if_changed_dep = $(if $(if-changed-cond),$(cmd_and_fixdep),@:) cmd_and_fixdep = \ $(cmd); \ - scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\ + $(objtree)/scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\ rm -f $(depfile) # Usage: $(call if_changed_rule,foo) diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index 3ee8ecfb8c04..33193ca6e803 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -33,7 +33,8 @@ ld-option = $(success,$(LD) -v $(1)) # $(as-instr,<instr>) # Return y if the assembler supports <instr>, n otherwise -as-instr = $(success,printf "%b\n" "$(1)" | $(CC) $(CLANG_FLAGS) -Wa$(comma)--fatal-warnings -c -x assembler-with-cpp -o /dev/null -) +as-instr = $(success,printf "%b\n" "$(1)" | $(CC) $(CLANG_FLAGS) $(2) -Wa$(comma)--fatal-warnings -c -x assembler-with-cpp -o /dev/null -) +as-instr64 = $(as-instr,$(1),$(m64-flag)) # check if $(CC) and $(LD) exist $(error-if,$(failure,command -v $(CC)),C compiler '$(CC)' not found) @@ -63,3 +64,14 @@ ld-version := $(shell,set -- $(ld-info) && echo $2) cc-option-bit = $(if-success,$(CC) -Werror $(1) -E -x c /dev/null -o /dev/null,$(1)) m32-flag := $(cc-option-bit,-m32) m64-flag := $(cc-option-bit,-m64) + +rustc-version := $(shell,$(srctree)/scripts/rustc-version.sh $(RUSTC)) +rustc-llvm-version := $(shell,$(srctree)/scripts/rustc-llvm-version.sh $(RUSTC)) + +# $(rustc-option,<flag>) +# Return y if the Rust compiler supports <flag>, n otherwise +# Calls to this should be guarded so that they are not evaluated if +# CONFIG_RUST_IS_AVAILABLE is not set. +# If you are testing for unstable features, consider testing RUSTC_VERSION +# instead, as features may have different completeness while available. +rustc-option = $(success,trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(RUSTC) $(1) --crate-type=rlib /dev/null --out-dir=.tmp_$$ -o .tmp_$$/tmp.rlib) diff --git a/scripts/Makefile b/scripts/Makefile index bc90520a5426..46f860529df5 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -12,7 +12,7 @@ hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE) += insert-sys-cert hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_builder hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_gen -ifneq ($(or $(CONFIG_X86_64),$(CONFIG_LOONGARCH)),) +ifneq ($(or $(CONFIG_X86_64),$(CONFIG_X86_32)),) always-$(CONFIG_RUST) += target.json filechk_rust_target = $< < include/config/auto.conf @@ -47,14 +47,16 @@ HOSTCFLAGS_sorttable.o += -DMCOUNT_SORT_ENABLED endif # The following programs are only built on demand -hostprogs += unifdef +hostprogs += unifdef gen_packed_field_checks # The module linker script is preprocessed on demand targets += module.lds subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins -subdir-$(CONFIG_MODVERSIONS) += genksyms +subdir-$(CONFIG_GENKSYMS) += genksyms +subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms subdir-$(CONFIG_SECURITY_SELINUX) += selinux +subdir-$(CONFIG_SECURITY_IPE) += ipe # Let clean descend into subdirs subdir- += basic dtc gdb kconfig mod diff --git a/scripts/Makefile.asm-generic b/scripts/Makefile.asm-generic deleted file mode 100644 index 8d01b37b7677..000000000000 --- a/scripts/Makefile.asm-generic +++ /dev/null @@ -1,58 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# include/asm-generic contains a lot of files that are used -# verbatim by several architectures. -# -# This Makefile reads the file arch/$(SRCARCH)/include/(uapi/)/asm/Kbuild -# and for each file listed in this file with generic-y creates -# a small wrapper file in arch/$(SRCARCH)/include/generated/(uapi/)/asm. - -PHONY := all -all: - -src := $(subst /generated,,$(obj)) - -include $(srctree)/scripts/Kbuild.include --include $(kbuild-file) - -# $(generic)/Kbuild lists mandatory-y. Exclude um since it is a special case. -ifneq ($(SRCARCH),um) -include $(srctree)/$(generic)/Kbuild -endif - -redundant := $(filter $(mandatory-y) $(generated-y), $(generic-y)) -redundant += $(foreach f, $(generic-y), $(if $(wildcard $(srctree)/$(src)/$(f)),$(f))) -redundant := $(sort $(redundant)) -$(if $(redundant),\ - $(warning redundant generic-y found in $(src)/Kbuild: $(redundant))) - -# If arch does not implement mandatory headers, fallback to asm-generic ones. -mandatory-y := $(filter-out $(generated-y), $(mandatory-y)) -generic-y += $(foreach f, $(mandatory-y), $(if $(wildcard $(srctree)/$(src)/$(f)),,$(f))) - -generic-y := $(addprefix $(obj)/, $(generic-y)) -generated-y := $(addprefix $(obj)/, $(generated-y)) - -# Remove stale wrappers when the corresponding files are removed from generic-y -old-headers := $(wildcard $(obj)/*.h) -unwanted := $(filter-out $(generic-y) $(generated-y),$(old-headers)) - -quiet_cmd_wrap = WRAP $@ - cmd_wrap = echo "\#include <asm-generic/$*.h>" > $@ - -quiet_cmd_remove = REMOVE $(unwanted) - cmd_remove = rm -f $(unwanted) - -all: $(generic-y) - $(if $(unwanted),$(call cmd,remove)) - @: - -$(obj)/%.h: - $(call cmd,wrap) - -# Create output directory. Skip it if at least one old header exists -# since we know the output directory already exists. -ifeq ($(old-headers),) -$(shell mkdir -p $(obj)) -endif - -.PHONY: $(PHONY) diff --git a/scripts/Makefile.asm-headers b/scripts/Makefile.asm-headers new file mode 100644 index 000000000000..8a4856e74180 --- /dev/null +++ b/scripts/Makefile.asm-headers @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: GPL-2.0 +# include/asm-generic contains a lot of files that are used +# verbatim by several architectures. +# +# This Makefile generates arch/$(SRCARCH)/include/generated/(uapi/)/asm +# headers from multiple sources: +# - a small wrapper to include the corresponding asm-generic/*.h +# is generated for each file listed as generic-y +# - uapi/asm/unistd_*.h files listed as syscalls-y are generated from +# syscall.tbl with the __NR_* macros +# - Corresponding asm/syscall_table_*.h are generated from the same input + +PHONY := all +all: + +src := $(srctree)/$(subst /generated,,$(obj)) + +syscall_abis_32 += common,32 +syscall_abis_64 += common,64 +syscalltbl := $(srctree)/scripts/syscall.tbl +syshdr-args := --emit-nr + +# let architectures override $(syscall_abis_%) and $(syscalltbl) +-include $(srctree)/arch/$(SRCARCH)/kernel/Makefile.syscalls +include $(srctree)/scripts/Kbuild.include +-include $(kbuild-file) + +syshdr := $(srctree)/scripts/syscallhdr.sh +systbl := $(srctree)/scripts/syscalltbl.sh + +# $(generic)/Kbuild lists mandatory-y. Exclude um since it is a special case. +ifneq ($(SRCARCH),um) +include $(srctree)/$(generic)/Kbuild +endif + +redundant := $(filter $(mandatory-y) $(generated-y), $(generic-y)) +redundant += $(foreach f, $(generic-y), $(if $(wildcard $(src)/$(f)),$(f))) +redundant := $(sort $(redundant)) +$(if $(redundant),\ + $(warning redundant generic-y found in $(src)/Kbuild: $(redundant))) + +# If arch does not implement mandatory headers, fallback to asm-generic ones. +mandatory-y := $(filter-out $(generated-y), $(mandatory-y)) +generic-y += $(foreach f, $(mandatory-y), $(if $(wildcard $(src)/$(f)),,$(f))) + +generic-y := $(addprefix $(obj)/, $(generic-y)) +syscall-y := $(addprefix $(obj)/, $(syscall-y)) +generated-y := $(addprefix $(obj)/, $(generated-y)) + +# Remove stale wrappers when the corresponding files are removed from generic-y +old-headers := $(wildcard $(obj)/*.h) +unwanted := $(filter-out $(generic-y) $(generated-y) $(syscall-y),$(old-headers)) + +quiet_cmd_wrap = WRAP $@ + cmd_wrap = echo "\#include <asm-generic/$*.h>" > $@ + +quiet_cmd_remove = REMOVE $(unwanted) + cmd_remove = rm -f $(unwanted) + +quiet_cmd_syshdr = SYSHDR $@ + cmd_syshdr = $(CONFIG_SHELL) $(syshdr) \ + $(if $(syshdr-args-$*),$(syshdr-args-$*),$(syshdr-args)) \ + $(if $(syscall_compat),--prefix "compat$*_") \ + --abis $(subst $(space),$(comma),$(strip $(syscall_abis_$*))) \ + $< $@ + +quiet_cmd_systbl = SYSTBL $@ + cmd_systbl = $(CONFIG_SHELL) $(systbl) \ + $(if $(systbl-args-$*),$(systbl-args-$*),$(systbl-args)) \ + --abis $(subst $(space),$(comma),$(strip $(syscall_abis_$*))) \ + $< $@ + +all: $(generic-y) $(syscall-y) + $(if $(unwanted),$(call cmd,remove)) + @: + +$(obj)/%.h: $(srctree)/$(generic)/%.h + $(call cmd,wrap) + +$(obj)/unistd_%.h: $(syscalltbl) $(syshdr) FORCE + $(call if_changed,syshdr) + +$(obj)/unistd_compat_%.h: syscall_compat:=1 +$(obj)/unistd_compat_%.h: $(syscalltbl) $(syshdr) FORCE + $(call if_changed,syshdr) + +$(obj)/syscall_table_%.h: $(syscalltbl) $(systbl) FORCE + $(call if_changed,systbl) + +targets := $(syscall-y) + +# Create output directory. Skip it if at least one old header exists +# since we know the output directory already exists. +ifeq ($(old-headers),) +$(shell mkdir -p $(obj)) +endif + +PHONY += FORCE + +FORCE: + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.autofdo b/scripts/Makefile.autofdo new file mode 100644 index 000000000000..1caf2457e585 --- /dev/null +++ b/scripts/Makefile.autofdo @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Enable available and selected Clang AutoFDO features. + +CFLAGS_AUTOFDO_CLANG := -fdebug-info-for-profiling -mllvm -enable-fs-discriminator=true -mllvm -improved-fs-discriminator=true + +ifndef CONFIG_DEBUG_INFO + CFLAGS_AUTOFDO_CLANG += -gmlt +endif + +ifdef CLANG_AUTOFDO_PROFILE + CFLAGS_AUTOFDO_CLANG += -fprofile-sample-use=$(CLANG_AUTOFDO_PROFILE) -ffunction-sections + CFLAGS_AUTOFDO_CLANG += -fsplit-machine-functions +endif + +ifdef CONFIG_LTO_CLANG_THIN + ifdef CLANG_AUTOFDO_PROFILE + KBUILD_LDFLAGS += --lto-sample-profile=$(CLANG_AUTOFDO_PROFILE) + endif + KBUILD_LDFLAGS += --mllvm=-enable-fs-discriminator=true --mllvm=-improved-fs-discriminator=true -plugin-opt=thinlto + KBUILD_LDFLAGS += -plugin-opt=-split-machine-functions +endif + +export CFLAGS_AUTOFDO_CLANG diff --git a/scripts/Makefile.btf b/scripts/Makefile.btf index 82377e470aed..db76335dd917 100644 --- a/scripts/Makefile.btf +++ b/scripts/Makefile.btf @@ -3,6 +3,10 @@ pahole-ver := $(CONFIG_PAHOLE_VERSION) pahole-flags-y := +JOBS := $(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS))) + +ifeq ($(call test-le, $(pahole-ver), 125),y) + # pahole 1.18 through 1.21 can't handle zero-sized per-CPU vars ifeq ($(call test-le, $(pahole-ver), 121),y) pahole-flags-$(call test-ge, $(pahole-ver), 118) += --skip_encoding_btf_vars @@ -10,10 +14,24 @@ endif pahole-flags-$(call test-ge, $(pahole-ver), 121) += --btf_gen_floats -pahole-flags-$(call test-ge, $(pahole-ver), 122) += -j - -pahole-flags-$(CONFIG_PAHOLE_HAS_LANG_EXCLUDE) += --lang_exclude=rust +pahole-flags-$(call test-ge, $(pahole-ver), 122) += -j$(JOBS) pahole-flags-$(call test-ge, $(pahole-ver), 125) += --skip_encoding_btf_inconsistent_proto --btf_gen_optimized +else + +# Switch to using --btf_features for v1.26 and later. +pahole-flags-$(call test-ge, $(pahole-ver), 126) = -j$(JOBS) --btf_features=encode_force,var,float,enum64,decl_tag,type_tag,optimized_func,consistent_func,decl_tag_kfuncs + +pahole-flags-$(call test-ge, $(pahole-ver), 130) += --btf_features=attributes + +ifneq ($(KBUILD_EXTMOD),) +module-pahole-flags-$(call test-ge, $(pahole-ver), 128) += --btf_features=distilled_base +endif + +endif + +pahole-flags-$(CONFIG_PAHOLE_HAS_LANG_EXCLUDE) += --lang_exclude=rust + export PAHOLE_FLAGS := $(pahole-flags-y) +export MODULE_PAHOLE_FLAGS := $(module-pahole-flags-y) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index baf86c0880b6..ba71b27aa363 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -3,7 +3,7 @@ # Building # ========================================================================== -src := $(obj) +src := $(srcroot)/$(obj) PHONY := $(obj)/ $(obj)/: @@ -20,10 +20,6 @@ always-m := targets := subdir-y := subdir-m := -EXTRA_AFLAGS := -EXTRA_CFLAGS := -EXTRA_CPPFLAGS := -EXTRA_LDFLAGS := asflags-y := ccflags-y := rustflags-y := @@ -34,25 +30,95 @@ subdir-asflags-y := subdir-ccflags-y := # Read auto.conf if it exists, otherwise ignore --include include/config/auto.conf +-include $(objtree)/include/config/auto.conf include $(srctree)/scripts/Kbuild.include include $(srctree)/scripts/Makefile.compiler include $(kbuild-file) include $(srctree)/scripts/Makefile.lib -# Do not include hostprogs rules unless needed. -# $(sort ...) is used here to remove duplicated words and excessive spaces. -hostprogs := $(sort $(hostprogs)) -ifneq ($(hostprogs),) -include $(srctree)/scripts/Makefile.host +# flags that take effect in current and sub directories +KBUILD_AFLAGS += $(subdir-asflags-y) +KBUILD_CFLAGS += $(subdir-ccflags-y) +KBUILD_RUSTFLAGS += $(subdir-rustflags-y) + +# Figure out what we need to build from the various variables +# =========================================================================== + +# When an object is listed to be built compiled-in and modular, +# only build the compiled-in version +obj-m := $(filter-out $(obj-y),$(obj-m)) + +# Libraries are always collected in one lib file. +# Filter out objects already built-in +lib-y := $(filter-out $(obj-y), $(sort $(lib-y) $(lib-m))) + +# Subdirectories we need to descend into +subdir-ym := $(sort $(subdir-y) $(subdir-m) \ + $(patsubst %/,%, $(filter %/, $(obj-y) $(obj-m)))) + +# Handle objects in subdirs: +# - If we encounter foo/ in $(obj-y), replace it by foo/built-in.a and +# foo/modules.order +# - If we encounter foo/ in $(obj-m), replace it by foo/modules.order +# +# Generate modules.order to determine modorder. Unfortunately, we don't have +# information about ordering between -y and -m subdirs. Just put -y's first. + +ifdef need-modorder +obj-m := $(patsubst %/,%/modules.order, $(filter %/, $(obj-y)) $(obj-m)) +else +obj-m := $(filter-out %/, $(obj-m)) endif -# Do not include userprogs rules unless needed. -# $(sort ...) is used here to remove duplicated words and excessive spaces. -userprogs := $(sort $(userprogs)) -ifneq ($(userprogs),) -include $(srctree)/scripts/Makefile.userprogs +ifdef need-builtin +obj-y := $(patsubst %/, %/built-in.a, $(obj-y)) +else +obj-y := $(filter-out %/, $(obj-y)) +endif + +# Expand $(foo-objs) $(foo-y) etc. by replacing their individuals +suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s)))) +# List composite targets that are constructed by combining other targets +multi-search = $(sort $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $m))) +# List primitive targets that are compiled from source files +real-search = $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $(call suffix-search, $m, $2, $3), $m)) + +# If $(foo-objs), $(foo-y), $(foo-m), or $(foo-) exists, foo.o is a composite object +multi-obj-y := $(call multi-search, $(obj-y), .o, -objs -y) +multi-obj-m := $(call multi-search, $(obj-m), .o, -objs -y -m) +multi-obj-ym := $(multi-obj-y) $(multi-obj-m) + +# Replace multi-part objects by their individual parts, +# including built-in.a from subdirectories +real-obj-y := $(call real-search, $(obj-y), .o, -objs -y) +real-obj-m := $(call real-search, $(obj-m), .o, -objs -y -m) + +always-y += $(always-m) + +# hostprogs-always-y += foo +# ... is a shorthand for +# hostprogs += foo +# always-y += foo +hostprogs += $(hostprogs-always-y) $(hostprogs-always-m) +always-y += $(hostprogs-always-y) $(hostprogs-always-m) + +# userprogs-always-y is likewise. +userprogs += $(userprogs-always-y) $(userprogs-always-m) +always-y += $(userprogs-always-y) $(userprogs-always-m) + +# Add subdir path + +ifneq ($(obj),.) +extra-y := $(addprefix $(obj)/, $(extra-y)) +always-y := $(addprefix $(obj)/, $(always-y)) +targets := $(addprefix $(obj)/, $(targets)) +obj-m := $(addprefix $(obj)/, $(obj-m)) +lib-y := $(addprefix $(obj)/, $(lib-y)) +real-obj-y := $(addprefix $(obj)/, $(real-obj-y)) +real-obj-m := $(addprefix $(obj)/, $(real-obj-m)) +multi-obj-m := $(addprefix $(obj)/, $(multi-obj-m)) +subdir-ym := $(addprefix $(obj)/, $(subdir-ym)) endif ifndef obj @@ -71,7 +137,6 @@ endif # subdir-builtin and subdir-modorder may contain duplications. Use $(sort ...) subdir-builtin := $(sort $(filter %/built-in.a, $(real-obj-y))) subdir-modorder := $(sort $(filter %/modules.order, $(obj-m))) -subdir-dtbslist := $(sort $(filter %/dtbs-list, $(dtb-y))) targets-for-builtin := $(extra-y) @@ -102,7 +167,7 @@ else ifeq ($(KBUILD_CHECKSRC),2) endif ifneq ($(KBUILD_EXTRA_WARN),) - cmd_checkdoc = $(srctree)/scripts/kernel-doc -none $(KDOCFLAGS) \ + cmd_checkdoc = PYTHONDONTWRITEBYTECODE=1 $(KERNELDOC) -none $(KDOCFLAGS) \ $(if $(findstring 2, $(KBUILD_EXTRA_WARN)), -Wall) \ $< endif @@ -113,35 +178,40 @@ endif quiet_cmd_cc_s_c = CC $(quiet_modtag) $@ cmd_cc_s_c = $(CC) $(filter-out $(DEBUG_CFLAGS) $(CC_FLAGS_LTO), $(c_flags)) -fverbose-asm -S -o $@ $< -$(obj)/%.s: $(src)/%.c FORCE +$(obj)/%.s: $(obj)/%.c FORCE $(call if_changed_dep,cc_s_c) quiet_cmd_cpp_i_c = CPP $(quiet_modtag) $@ cmd_cpp_i_c = $(CPP) $(c_flags) -o $@ $< -$(obj)/%.i: $(src)/%.c FORCE +$(obj)/%.i: $(obj)/%.c FORCE $(call if_changed_dep,cpp_i_c) -genksyms = scripts/genksyms/genksyms \ - $(if $(1), -T $(2)) \ - $(if $(KBUILD_PRESERVE), -p) \ - -r $(or $(wildcard $(2:.symtypes=.symref)), /dev/null) +getexportsymbols = $(NM) $@ | sed -n 's/.* __export_symbol_\(.*\)/$(1)/p' -# These mirror gensymtypes_S and co below, keep them in synch. -cmd_gensymtypes_c = $(CPP) -D__GENKSYMS__ $(c_flags) $< | $(genksyms) +gendwarfksyms = $(objtree)/scripts/gendwarfksyms/gendwarfksyms \ + $(if $(KBUILD_SYMTYPES), --symtypes $(@:.o=.symtypes)) \ + $(if $(KBUILD_GENDWARFKSYMS_STABLE), --stable) -quiet_cmd_cc_symtypes_c = SYM $(quiet_modtag) $@ - cmd_cc_symtypes_c = $(call cmd_gensymtypes_c,true,$@) >/dev/null +genksyms = $(objtree)/scripts/genksyms/genksyms \ + $(if $(KBUILD_SYMTYPES), -T $(@:.o=.symtypes)) \ + $(if $(KBUILD_PRESERVE), -p) \ + $(addprefix -r , $(wildcard $(@:.o=.symref))) -$(obj)/%.symtypes : $(src)/%.c FORCE - $(call cmd,cc_symtypes_c) +# These mirror gensymtypes_S and co below, keep them in synch. +ifdef CONFIG_GENDWARFKSYMS +cmd_gensymtypes_c = $(if $(skip_gendwarfksyms),, \ + $(call getexportsymbols,\1) | $(gendwarfksyms) $@) +else +cmd_gensymtypes_c = $(CPP) -D__GENKSYMS__ $(c_flags) $< | $(genksyms) +endif # CONFIG_GENDWARFKSYMS # LLVM assembly # Generate .ll files from .c quiet_cmd_cc_ll_c = CC $(quiet_modtag) $@ cmd_cc_ll_c = $(CC) $(c_flags) -emit-llvm -S -fno-discard-value-names -o $@ $< -$(obj)/%.ll: $(src)/%.c FORCE +$(obj)/%.ll: $(obj)/%.c FORCE $(call if_changed_dep,cc_ll_c) # C (.c) files @@ -150,17 +220,6 @@ $(obj)/%.ll: $(src)/%.c FORCE is-single-obj-m = $(and $(part-of-module),$(filter $@, $(obj-m)),y) -# When a module consists of a single object, there is no reason to keep LLVM IR. -# Make $(LD) covert LLVM IR to ELF here. -ifdef CONFIG_LTO_CLANG -cmd_ld_single_m = $(if $(is-single-obj-m), ; $(LD) $(ld_flags) -r -o $(tmp-target) $@; mv $(tmp-target) $@) -endif - -quiet_cmd_cc_o_c = CC $(quiet_modtag) $@ - cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \ - $(cmd_ld_single_m) \ - $(cmd_objtool) - ifdef CONFIG_MODVERSIONS # When module versioning is enabled the following steps are executed: # o compile a <file>.o from <file>.c @@ -173,8 +232,7 @@ ifdef CONFIG_MODVERSIONS gen_symversions = \ if $(NM) $@ 2>/dev/null | grep -q ' __export_symbol_'; then \ - $(call cmd_gensymtypes_$(1),$(KBUILD_SYMTYPES),$(@:.o=.symtypes)) \ - >> $(dot-target).cmd; \ + $(cmd_gensymtypes_$1) >> $(dot-target).cmd; \ fi cmd_gen_symversions_c = $(call gen_symversions,c) @@ -214,33 +272,18 @@ endif # CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT # 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file # 'OBJECT_FILES_NON_STANDARD_foo.o := 'n': override directory skip for a file -is-standard-object = $(if $(filter-out y%, $(OBJECT_FILES_NON_STANDARD_$(target-stem).o)$(OBJECT_FILES_NON_STANDARD)n),y) +is-standard-object = $(if $(filter-out y%, $(OBJECT_FILES_NON_STANDARD_$(target-stem).o)$(OBJECT_FILES_NON_STANDARD)n),$(is-kernel-object)) -$(obj)/%.o: objtool-enabled = $(if $(is-standard-object),$(if $(delay-objtool),$(is-single-obj-m),y)) +ifdef CONFIG_OBJTOOL +$(obj)/%.o: private objtool-enabled = $(if $(is-standard-object),$(if $(delay-objtool),$(is-single-obj-m),y)) +endif ifneq ($(findstring 1, $(KBUILD_EXTRA_WARN)),) cmd_warn_shared_object = $(if $(word 2, $(modname-multi)),$(warning $(kbuild-file): $*.o is added to multiple modules: $(modname-multi))) endif -define rule_cc_o_c - $(call cmd_and_fixdep,cc_o_c) - $(call cmd,checksrc) - $(call cmd,checkdoc) - $(call cmd,gen_objtooldep) - $(call cmd,gen_symversions_c) - $(call cmd,record_mcount) - $(call cmd,warn_shared_object) -endef - -define rule_as_o_S - $(call cmd_and_fixdep,as_o_S) - $(call cmd,gen_objtooldep) - $(call cmd,gen_symversions_S) - $(call cmd,warn_shared_object) -endef - # Built-in and composite module parts -$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE +$(obj)/%.o: $(obj)/%.c $(recordmcount_source) FORCE $(call if_changed_rule,cc_o_c) $(call cmd,force_checksrc) @@ -257,23 +300,34 @@ quiet_cmd_cc_lst_c = MKLST $@ $(CONFIG_SHELL) $(srctree)/scripts/makelst $*.o \ System.map $(OBJDUMP) > $@ -$(obj)/%.lst: $(src)/%.c FORCE +$(obj)/%.lst: $(obj)/%.c FORCE $(call if_changed_dep,cc_lst_c) # Compile Rust sources (.rs) # --------------------------------------------------------------------------- -rust_allowed_features := new_uninit,offset_of +# The features in this list are the ones allowed for non-`rust/` code. +# +# - Stable since Rust 1.81.0: `feature(lint_reasons)`. +# - Stable since Rust 1.82.0: `feature(asm_const)`, `feature(raw_ref_op)`. +# - Stable since Rust 1.87.0: `feature(asm_goto)`. +# - Expected to become stable: `feature(arbitrary_self_types)`. +# - To be determined: `feature(used_with_arg)`. +# +# Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on +# the unstable features in use. +rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,raw_ref_op,used_with_arg # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree # modules case. rust_common_cmd = \ + OBJTREE=$(abspath $(objtree)) \ RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \ -Zallow-features=$(rust_allowed_features) \ -Zcrate-attr=no_std \ -Zcrate-attr='feature($(rust_allowed_features))' \ - --extern alloc --extern kernel \ + -Zunstable-options --extern pin_init --extern kernel \ --crate-type rlib -L $(objtree)/rust/ \ --crate-name $(basename $(notdir $@)) \ --sysroot=/dev/null \ @@ -288,31 +342,42 @@ rust_common_cmd = \ # would not match each other. quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ - cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $< + cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $< $(cmd_objtool) -$(obj)/%.o: $(src)/%.rs FORCE - +$(call if_changed_dep,rustc_o_rs) +define rule_rustc_o_rs + $(call cmd_and_fixdep,rustc_o_rs) + $(call cmd,gen_objtooldep) +endef + +$(obj)/%.o: $(obj)/%.rs FORCE + +$(call if_changed_rule,rustc_o_rs) quiet_cmd_rustc_rsi_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_rsi_rs = \ $(rust_common_cmd) -Zunpretty=expanded $< >$@; \ command -v $(RUSTFMT) >/dev/null && $(RUSTFMT) $@ -$(obj)/%.rsi: $(src)/%.rs FORCE +$(obj)/%.rsi: $(obj)/%.rs FORCE +$(call if_changed_dep,rustc_rsi_rs) quiet_cmd_rustc_s_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_s_rs = $(rust_common_cmd) --emit=asm=$@ $< -$(obj)/%.s: $(src)/%.rs FORCE +$(obj)/%.s: $(obj)/%.rs FORCE +$(call if_changed_dep,rustc_s_rs) quiet_cmd_rustc_ll_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_ll_rs = $(rust_common_cmd) --emit=llvm-ir=$@ $< -$(obj)/%.ll: $(src)/%.rs FORCE +$(obj)/%.ll: $(obj)/%.rs FORCE +$(call if_changed_dep,rustc_ll_rs) +quiet_cmd_rustc_rs_rs_S = RSCPP $(quiet_modtag) $@ + cmd_rustc_rs_rs_S = $(CPP) $(c_flags) -xc -C -P $< | sed '1,/^\/\/ Cut here.$$/d' >$@ + +$(obj)/%.rs: $(obj)/%.rs.S FORCE + +$(call if_changed_dep,rustc_rs_rs_S) + # Compile assembler sources (.S) # --------------------------------------------------------------------------- @@ -324,31 +389,33 @@ $(obj)/%.ll: $(src)/%.rs FORCE # This is convoluted. The .S file must first be preprocessed to run guards and # expand names, then the resulting exports must be constructed into plain # EXPORT_SYMBOL(symbol); to build our dummy C file, and that gets preprocessed -# to make the genksyms input. +# to make the genksyms input or compiled into an object for gendwarfksyms. # # These mirror gensymtypes_c and co above, keep them in synch. -cmd_gensymtypes_S = \ - { echo "\#include <linux/kernel.h>" ; \ - echo "\#include <asm/asm-prototypes.h>" ; \ - $(NM) $@ | sed -n 's/.* __export_symbol_\(.*\)/EXPORT_SYMBOL(\1);/p' ; } | \ - $(CPP) -D__GENKSYMS__ $(c_flags) -xc - | $(genksyms) - -quiet_cmd_cc_symtypes_S = SYM $(quiet_modtag) $@ - cmd_cc_symtypes_S = $(call cmd_gensymtypes_S,true,$@) >/dev/null - -$(obj)/%.symtypes : $(src)/%.S FORCE - $(call cmd,cc_symtypes_S) - +getasmexports = \ + { echo "\#include <linux/kernel.h>" ; \ + echo "\#include <linux/string.h>" ; \ + echo "\#include <asm/asm-prototypes.h>" ; \ + $(call getexportsymbols,EXPORT_SYMBOL(\1);) ; } + +ifdef CONFIG_GENDWARFKSYMS +cmd_gensymtypes_S = \ + $(getasmexports) | \ + $(CC) $(c_flags) -c -o $(@:.o=.gendwarfksyms.o) -xc -; \ + $(call getexportsymbols,\1) | \ + $(gendwarfksyms) $(@:.o=.gendwarfksyms.o) +else +cmd_gensymtypes_S = \ + $(getasmexports) | \ + $(CPP) -D__GENKSYMS__ $(c_flags) -xc - | $(genksyms) +endif # CONFIG_GENDWARFKSYMS quiet_cmd_cpp_s_S = CPP $(quiet_modtag) $@ cmd_cpp_s_S = $(CPP) $(a_flags) -o $@ $< -$(obj)/%.s: $(src)/%.S FORCE +$(obj)/%.s: $(obj)/%.S FORCE $(call if_changed_dep,cpp_s_S) -quiet_cmd_as_o_S = AS $(quiet_modtag) $@ - cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $< $(cmd_objtool) - ifdef CONFIG_ASM_MODVERSIONS # versioning matches the C process described above, with difference that @@ -358,12 +425,12 @@ cmd_gen_symversions_S = $(call gen_symversions,S) endif -$(obj)/%.o: $(src)/%.S FORCE +$(obj)/%.o: $(obj)/%.S FORCE $(call if_changed_rule,as_o_S) targets += $(filter-out $(subdir-builtin), $(real-obj-y)) targets += $(filter-out $(subdir-modorder), $(real-obj-m)) -targets += $(real-dtb-y) $(lib-y) $(always-y) +targets += $(lib-y) $(always-y) # Linker scripts preprocessor (.lds.S -> .lds) # --------------------------------------------------------------------------- @@ -389,7 +456,6 @@ $(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler # To build objects in subdirs, we need to descend into the directories $(subdir-builtin): $(obj)/%/built-in.a: $(obj)/% ; $(subdir-modorder): $(obj)/%/modules.order: $(obj)/% ; -$(subdir-dtbslist): $(obj)/%/dtbs-list: $(obj)/% ; # # Rule to compile a set of .o files into one .a file (without symbol table) @@ -405,12 +471,8 @@ quiet_cmd_ar_builtin = AR $@ $(obj)/built-in.a: $(real-obj-y) FORCE $(call if_changed,ar_builtin) -# -# Rule to create modules.order and dtbs-list -# -# This is a list of build artifacts (module or dtb) from the current Makefile -# and its sub-directories. The timestamp should be updated when any of the -# member files. +# This is a list of build artifacts from the current Makefile and its +# sub-directories. The timestamp should be updated when any of the member files. cmd_gen_order = { $(foreach m, $(real-prereqs), \ $(if $(filter %/$(notdir $@), $m), cat $m, echo $m);) :; } \ @@ -419,9 +481,6 @@ cmd_gen_order = { $(foreach m, $(real-prereqs), \ $(obj)/modules.order: $(obj-m) FORCE $(call if_changed,gen_order) -$(obj)/dtbs-list: $(dtb-y) FORCE - $(call if_changed,gen_order) - # # Rule to compile a set of .o files into one .a file (with symbol table) # @@ -437,8 +496,8 @@ define rule_ld_multi_m $(call cmd,gen_objtooldep) endef -$(multi-obj-m): objtool-enabled := $(delay-objtool) -$(multi-obj-m): part-of-module := y +$(multi-obj-m): private objtool-enabled := $(delay-objtool) +$(multi-obj-m): private part-of-module := y $(multi-obj-m): %.o: %.mod FORCE $(call if_changed_rule,ld_multi_m) $(call multi_depend, $(multi-obj-m), .o, -objs -y -m) @@ -450,15 +509,26 @@ intermediate_targets = $(foreach sfx, $(2), \ $(patsubst %$(strip $(1)),%$(sfx), \ $(filter %$(strip $(1)), $(targets)))) # %.asn1.o <- %.asn1.[ch] <- %.asn1 -# %.dtb.o <- %.dtb.S <- %.dtb <- %.dts -# %.dtbo.o <- %.dtbo.S <- %.dtbo <- %.dtso -# %.lex.o <- %.lex.c <- %.l -# %.tab.o <- %.tab.[ch] <- %.y -targets += $(call intermediate_targets, .asn1.o, .asn1.c .asn1.h) \ - $(call intermediate_targets, .dtb.o, .dtb.S .dtb) \ - $(call intermediate_targets, .dtbo.o, .dtbo.S .dtbo) \ - $(call intermediate_targets, .lex.o, .lex.c) \ - $(call intermediate_targets, .tab.o, .tab.c .tab.h) +targets += $(call intermediate_targets, .asn1.o, .asn1.c .asn1.h) + +# Include additional build rules when necessary +# --------------------------------------------------------------------------- + +# $(sort ...) is used here to remove duplicated words and excessive spaces. +hostprogs := $(sort $(hostprogs)) +ifneq ($(hostprogs),) +include $(srctree)/scripts/Makefile.host +endif + +# $(sort ...) is used here to remove duplicated words and excessive spaces. +userprogs := $(sort $(userprogs)) +ifneq ($(userprogs),) +include $(srctree)/scripts/Makefile.userprogs +endif + +ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets)),) +include $(srctree)/scripts/Makefile.dtbs +endif # Build # --------------------------------------------------------------------------- @@ -487,7 +557,7 @@ $(subdir-ym): need-modorder=$(if $(filter $@/modules.order, $(subdir-modorder)),1) \ $(filter $@/%, $(single-subdir-goals)) -# Add FORCE to the prequisites of a target to force it to be always rebuilt. +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. # --------------------------------------------------------------------------- PHONY += FORCE diff --git a/scripts/Makefile.clang b/scripts/Makefile.clang index 6c23c6af797f..b67636b28c35 100644 --- a/scripts/Makefile.clang +++ b/scripts/Makefile.clang @@ -10,7 +10,10 @@ CLANG_TARGET_FLAGS_mips := mipsel-linux-gnu CLANG_TARGET_FLAGS_powerpc := powerpc64le-linux-gnu CLANG_TARGET_FLAGS_riscv := riscv64-linux-gnu CLANG_TARGET_FLAGS_s390 := s390x-linux-gnu +CLANG_TARGET_FLAGS_sparc := sparc64-linux-gnu CLANG_TARGET_FLAGS_x86 := x86_64-linux-gnu +# This is only for i386 UM builds, which need the 32-bit target not -m32 +CLANG_TARGET_FLAGS_i386 := i386-linux-gnu CLANG_TARGET_FLAGS_um := $(CLANG_TARGET_FLAGS_$(SUBARCH)) CLANG_TARGET_FLAGS := $(CLANG_TARGET_FLAGS_$(SRCARCH)) diff --git a/scripts/Makefile.clean b/scripts/Makefile.clean index f2cb4d7ffd96..6ead00ec7313 100644 --- a/scripts/Makefile.clean +++ b/scripts/Makefile.clean @@ -3,7 +3,7 @@ # Cleaning up # ========================================================================== -src := $(obj) +src := $(srcroot)/$(obj) PHONY := __clean __clean: diff --git a/scripts/Makefile.compiler b/scripts/Makefile.compiler index 92be0c9a13ee..ef91910de265 100644 --- a/scripts/Makefile.compiler +++ b/scripts/Makefile.compiler @@ -13,7 +13,7 @@ cc-cross-prefix = $(firstword $(foreach c, $(1), \ $(if $(shell command -v -- $(c)gcc 2>/dev/null), $(c)))) # output directory for tests below -TMPOUT = $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_$$$$ +TMPOUT = .tmp_$$$$ # try-run # Usage: option = $(call try-run, $(CC)...-o "$$TMP",option-ok,otherwise) @@ -43,7 +43,7 @@ as-instr = $(call try-run,\ # __cc-option # Usage: MY_CFLAGS += $(call __cc-option,$(CC),$(MY_CFLAGS),-march=winchip-c6,-march=i586) __cc-option = $(call try-run,\ - $(1) -Werror $(2) $(3) -c -x c /dev/null -o "$$TMP",$(3),$(4)) + $(1) -Werror $(2) $(3:-Wno-%=-W%) -c -x c /dev/null -o "$$TMP",$(3),$(4)) # cc-option # Usage: cflags-y += $(call cc-option,-march=winchip-c6,-march=i586) @@ -53,22 +53,41 @@ cc-option = $(call __cc-option, $(CC),\ # cc-option-yn # Usage: flag := $(call cc-option-yn,-march=winchip-c6) -cc-option-yn = $(call try-run,\ - $(CC) -Werror $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(1) -c -x c /dev/null -o "$$TMP",y,n) +cc-option-yn = $(if $(call cc-option,$1),y,n) # cc-disable-warning # Usage: cflags-y += $(call cc-disable-warning,unused-but-set-variable) -cc-disable-warning = $(call try-run,\ - $(CC) -Werror $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -W$(strip $(1)) -c -x c /dev/null -o "$$TMP",-Wno-$(strip $(1))) +cc-disable-warning = $(call cc-option,-Wno-$(strip $1)) # gcc-min-version -# Usage: cflags-$(call gcc-min-version, 70100) += -foo +# Usage: cflags-$(call gcc-min-version, 110100) += -foo gcc-min-version = $(call test-ge, $(CONFIG_GCC_VERSION), $1) # clang-min-version # Usage: cflags-$(call clang-min-version, 110000) += -foo clang-min-version = $(call test-ge, $(CONFIG_CLANG_VERSION), $1) +# rustc-min-version +# Usage: rustc-$(call rustc-min-version, 108500) += -Cfoo +rustc-min-version = $(call test-ge, $(CONFIG_RUSTC_VERSION), $1) + # ld-option # Usage: KBUILD_LDFLAGS += $(call ld-option, -X, -Y) ld-option = $(call try-run, $(LD) $(KBUILD_LDFLAGS) $(1) -v,$(1),$(2),$(3)) + +# __rustc-option +# Usage: MY_RUSTFLAGS += $(call __rustc-option,$(RUSTC),$(MY_RUSTFLAGS),-Cinstrument-coverage,-Zinstrument-coverage) +# TODO: remove RUSTC_BOOTSTRAP=1 when we raise the minimum GNU Make version to 4.4 +__rustc-option = $(call try-run,\ + echo '$(pound)![allow(missing_docs)]$(pound)![feature(no_core)]$(pound)![no_core]' | RUSTC_BOOTSTRAP=1\ + $(1) --sysroot=/dev/null $(filter-out --sysroot=/dev/null --target=%,$(2)) $(3)\ + --crate-type=rlib --out-dir=$(TMPOUT) --emit=obj=- - >/dev/null,$(3),$(4)) + +# rustc-option +# Usage: rustflags-y += $(call rustc-option,-Cinstrument-coverage,-Zinstrument-coverage) +rustc-option = $(call __rustc-option, $(RUSTC),\ + $(KBUILD_RUSTFLAGS),$(1),$(2)) + +# rustc-option-yn +# Usage: flag := $(call rustc-option-yn,-Cinstrument-coverage) +rustc-option-yn = $(if $(call rustc-option,$1),y,n) diff --git a/scripts/Makefile.debug b/scripts/Makefile.debug index 059ff38fe0cb..107db997ce38 100644 --- a/scripts/Makefile.debug +++ b/scripts/Makefile.debug @@ -17,6 +17,12 @@ endif DEBUG_CFLAGS += $(debug-flags-y) KBUILD_AFLAGS += $(debug-flags-y) +ifdef CONFIG_DEBUG_INFO_DWARF4 +DEBUG_RUSTFLAGS += -Zdwarf-version=4 +else ifdef CONFIG_DEBUG_INFO_DWARF5 +DEBUG_RUSTFLAGS += -Zdwarf-version=5 +endif + ifdef CONFIG_DEBUG_INFO_REDUCED DEBUG_CFLAGS += -fno-var-tracking DEBUG_RUSTFLAGS += -Cdebuginfo=1 @@ -29,10 +35,12 @@ endif ifdef CONFIG_DEBUG_INFO_COMPRESSED_ZLIB DEBUG_CFLAGS += -gz=zlib +DEBUG_RUSTFLAGS += -Zdebuginfo-compression=zlib KBUILD_AFLAGS += -gz=zlib KBUILD_LDFLAGS += --compress-debug-sections=zlib else ifdef CONFIG_DEBUG_INFO_COMPRESSED_ZSTD DEBUG_CFLAGS += -gz=zstd +DEBUG_RUSTFLAGS += -Zdebuginfo-compression=zstd KBUILD_AFLAGS += -gz=zstd KBUILD_LDFLAGS += --compress-debug-sections=zstd endif diff --git a/scripts/Makefile.defconf b/scripts/Makefile.defconf index 226ea3df3b4b..a44307f08e9d 100644 --- a/scripts/Makefile.defconf +++ b/scripts/Makefile.defconf @@ -1,6 +1,11 @@ # SPDX-License-Identifier: GPL-2.0 # Configuration heplers +cmd_merge_fragments = \ + $(srctree)/scripts/kconfig/merge_config.sh \ + $4 -m -O $(objtree) $(srctree)/arch/$(SRCARCH)/configs/$2 \ + $(foreach config,$3,$(srctree)/arch/$(SRCARCH)/configs/$(config).config) + # Creates 'merged defconfigs' # --------------------------------------------------------------------------- # Usage: @@ -8,9 +13,7 @@ # # Input config fragments without '.config' suffix define merge_into_defconfig - $(Q)$(CONFIG_SHELL) $(srctree)/scripts/kconfig/merge_config.sh \ - -m -O $(objtree) $(srctree)/arch/$(SRCARCH)/configs/$(1) \ - $(foreach config,$(2),$(srctree)/arch/$(SRCARCH)/configs/$(config).config) + $(call cmd,merge_fragments,$1,$2) +$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig endef @@ -22,8 +25,6 @@ endef # # Input config fragments without '.config' suffix define merge_into_defconfig_override - $(Q)$(CONFIG_SHELL) $(srctree)/scripts/kconfig/merge_config.sh \ - -Q -m -O $(objtree) $(srctree)/arch/$(SRCARCH)/configs/$(1) \ - $(foreach config,$(2),$(srctree)/arch/$(SRCARCH)/configs/$(config).config) + $(call cmd,merge_fragments,$1,$2,-Q) +$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig endef diff --git a/scripts/Makefile.dtbinst b/scripts/Makefile.dtbinst index 67956f6496a5..9d920419a62c 100644 --- a/scripts/Makefile.dtbinst +++ b/scripts/Makefile.dtbinst @@ -17,7 +17,7 @@ include $(srctree)/scripts/Kbuild.include dst := $(INSTALL_DTBS_PATH) quiet_cmd_dtb_install = INSTALL $@ - cmd_dtb_install = install -D $< $@ + cmd_dtb_install = install -D -m 0644 $< $@ $(dst)/%: $(obj)/% $(call cmd,dtb_install) diff --git a/scripts/Makefile.dtbs b/scripts/Makefile.dtbs new file mode 100644 index 000000000000..8d56c0815f33 --- /dev/null +++ b/scripts/Makefile.dtbs @@ -0,0 +1,144 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# If CONFIG_OF_ALL_DTBS is enabled, all DT blobs are built +dtb-$(CONFIG_OF_ALL_DTBS) += $(dtb-) + +# Composite DTB (i.e. DTB constructed by overlay) +multi-dtb-y := $(call multi-search, $(dtb-y), .dtb, -dtbs) +# Primitive DTB compiled from *.dts +real-dtb-y := $(call real-search, $(dtb-y), .dtb, -dtbs) +# Base DTB that overlay is applied onto +base-dtb-y := $(filter %.dtb, $(call real-search, $(multi-dtb-y), .dtb, -dtbs)) + +dtb-y := $(addprefix $(obj)/, $(dtb-y)) +multi-dtb-y := $(addprefix $(obj)/, $(multi-dtb-y)) +real-dtb-y := $(addprefix $(obj)/, $(real-dtb-y)) + +always-y += $(dtb-y) +targets += $(real-dtb-y) + +# dtbs-list +# --------------------------------------------------------------------------- + +ifdef need-dtbslist +subdir-dtbslist := $(addsuffix /dtbs-list, $(subdir-ym)) +dtb-y += $(subdir-dtbslist) +always-y += $(obj)/dtbs-list +endif + +$(subdir-dtbslist): $(obj)/%/dtbs-list: $(obj)/% ; + +$(obj)/dtbs-list: $(dtb-y) FORCE + $(call if_changed,gen_order) + +# Assembly file to wrap dtb(o) +# --------------------------------------------------------------------------- + +builtin-dtb-section = $(if $(filter arch/$(SRCARCH)/boot/dts%, $(obj)),.dtb.init.rodata,.rodata) + +# Generate an assembly file to wrap the output of the device tree compiler +quiet_cmd_wrap_S_dtb = WRAP $@ + cmd_wrap_S_dtb = { \ + symbase=__$(patsubst .%,%,$(suffix $<))_$(subst -,_,$(notdir $*)); \ + echo '\#include <asm-generic/vmlinux.lds.h>'; \ + echo '.section $(builtin-dtb-section),"a"'; \ + echo '.balign STRUCT_ALIGNMENT'; \ + echo ".global $${symbase}_begin"; \ + echo "$${symbase}_begin:"; \ + echo '.incbin "$<" '; \ + echo ".global $${symbase}_end"; \ + echo "$${symbase}_end:"; \ + echo '.balign STRUCT_ALIGNMENT'; \ + } > $@ + +$(obj)/%.dtb.S: $(obj)/%.dtb FORCE + $(call if_changed,wrap_S_dtb) + +$(obj)/%.dtbo.S: $(obj)/%.dtbo FORCE + $(call if_changed,wrap_S_dtb) + +# Schema check +# --------------------------------------------------------------------------- + +ifneq ($(CHECK_DTBS),) +DT_CHECKER ?= dt-validate +DT_CHECKER_FLAGS ?= $(if $(DT_SCHEMA_FILES),-l $(DT_SCHEMA_FILES),-m) +DT_BINDING_DIR := Documentation/devicetree/bindings +DT_TMP_SCHEMA := $(objtree)/$(DT_BINDING_DIR)/processed-schema.json +dtb-check-enabled = $(if $(filter %.dtb, $@),y) +endif + +quiet_dtb_check_tag = $(if $(dtb-check-enabled),[C], ) +cmd_dtb_check = $(if $(dtb-check-enabled),; $(DT_CHECKER) $(DT_CHECKER_FLAGS) -u $(srctree)/$(DT_BINDING_DIR) -p $(DT_TMP_SCHEMA) $@ || true) + +# Overlay +# --------------------------------------------------------------------------- + +# NOTE: +# Do not replace $(filter %.dtb %.dtbo, $^) with $(real-prereqs). When a single +# DTB is turned into a multi-blob DTB, $^ will contain header file dependencies +# recorded in the .*.cmd file. +quiet_cmd_fdtoverlay = OVL $(quiet_dtb_check_tag) $@ + cmd_fdtoverlay = $(objtree)/scripts/dtc/fdtoverlay -o $@ -i $(filter %.dtb %.dtbo, $^) $(cmd_dtb_check) + +$(multi-dtb-y): $(DT_TMP_SCHEMA) FORCE + $(call if_changed,fdtoverlay) +$(call multi_depend, $(multi-dtb-y), .dtb, -dtbs) + +# DTC +# --------------------------------------------------------------------------- + +DTC ?= $(objtree)/scripts/dtc/dtc +DTC_FLAGS += -Wno-unique_unit_address + +# Disable noisy checks by default +ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),) +DTC_FLAGS += -Wno-unit_address_vs_reg \ + -Wno-avoid_unnecessary_addr_size \ + -Wno-alias_paths \ + -Wno-graph_child_address \ + -Wno-simple_bus_reg +else +DTC_FLAGS += -Wunique_unit_address_if_enabled +endif + +ifneq ($(findstring 2,$(KBUILD_EXTRA_WARN)),) +DTC_FLAGS += -Wnode_name_chars_strict \ + -Wproperty_name_chars_strict \ + -Wunique_unit_address +endif + +DTC_FLAGS += $(DTC_FLAGS_$(target-stem)) + +# Set -@ if the target is a base DTB that overlay is applied onto +DTC_FLAGS += $(if $(filter $(patsubst $(obj)/%,%,$@), $(base-dtb-y)), -@) + +DTC_INCLUDE := $(srctree)/scripts/dtc/include-prefixes + +dtc_cpp_flags = -Wp,-MMD,$(depfile).pre.tmp -nostdinc -I $(DTC_INCLUDE) -undef -D__DTS__ + +dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp) + +quiet_cmd_dtc = DTC $(quiet_dtb_check_tag) $@ + cmd_dtc = \ + $(HOSTCC) -E $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \ + $(DTC) -o $@ -b 0 $(addprefix -i,$(dir $<) $(DTC_INCLUDE)) \ + $(DTC_FLAGS) -d $(depfile).dtc.tmp $(dtc-tmp) ; \ + cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile) \ + $(cmd_dtb_check) + +$(obj)/%.dtb: $(obj)/%.dts $(DTC) $(DT_TMP_SCHEMA) FORCE + $(call if_changed_dep,dtc) + +$(obj)/%.dtbo: $(src)/%.dtso $(DTC) FORCE + $(call if_changed_dep,dtc) + +# targets +# --------------------------------------------------------------------------- + +targets += $(always-y) + +# %.dtb.o <- %.dtb.S <- %.dtb <- %.dts +# %.dtbo.o <- %.dtbo.S <- %.dtbo <- %.dtso +targets += $(call intermediate_targets, .dtb.o, .dtb.S .dtb) \ + $(call intermediate_targets, .dtbo.o, .dtbo.S .dtbo) diff --git a/scripts/Makefile.extrawarn b/scripts/Makefile.extrawarn index 3ce5d503a6da..dca175fffcab 100644 --- a/scripts/Makefile.extrawarn +++ b/scripts/Makefile.extrawarn @@ -8,6 +8,7 @@ # Default set of warnings, always enabled KBUILD_CFLAGS += -Wall +KBUILD_CFLAGS += -Wextra KBUILD_CFLAGS += -Wundef KBUILD_CFLAGS += -Werror=implicit-function-declaration KBUILD_CFLAGS += -Werror=implicit-int @@ -15,8 +16,8 @@ KBUILD_CFLAGS += -Werror=return-type KBUILD_CFLAGS += -Werror=strict-prototypes KBUILD_CFLAGS += -Wno-format-security KBUILD_CFLAGS += -Wno-trigraphs -KBUILD_CFLAGS += $(call cc-disable-warning,frame-address,) -KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member) +KBUILD_CFLAGS += $(call cc-option, -Wno-frame-address) +KBUILD_CFLAGS += $(call cc-option, -Wno-address-of-packed-member) KBUILD_CFLAGS += -Wmissing-declarations KBUILD_CFLAGS += -Wmissing-prototypes @@ -31,22 +32,39 @@ KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-bounds ifdef CONFIG_CC_IS_CLANG # The kernel builds with '-std=gnu11' so use of GNU extensions is acceptable. KBUILD_CFLAGS += -Wno-gnu + +# Clang checks for overflow/truncation with '%p', while GCC does not: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111219 +KBUILD_CFLAGS += $(call cc-option, -Wno-format-overflow-non-kprintf) +KBUILD_CFLAGS += $(call cc-option, -Wno-format-truncation-non-kprintf) + +# Clang may emit a warning when a const variable, such as the dummy variables +# in typecheck(), or const member of an aggregate type are not initialized, +# which can result in unexpected behavior. However, in many audited cases of +# the "field" variant of the warning, this is intentional because the field is +# never used within a particular call path, the field is within a union with +# other non-const members, or the containing object is not const so the field +# can be modified via memcpy() / memset(). While the variable warning also gets +# disabled with this same switch, there should not be too much coverage lost +# because -Wuninitialized will still flag when an uninitialized const variable +# is used. +KBUILD_CFLAGS += $(call cc-option, -Wno-default-const-init-unsafe) else # gcc inanely warns about local variables called 'main' KBUILD_CFLAGS += -Wno-main endif -# These warnings generated too much noise in a regular build. -# Use make W=1 to enable them (see scripts/Makefile.extrawarn) -KBUILD_CFLAGS += $(call cc-disable-warning, unused-but-set-variable) -KBUILD_CFLAGS += $(call cc-disable-warning, unused-const-variable) - # These result in bogus false positives -KBUILD_CFLAGS += $(call cc-disable-warning, dangling-pointer) +KBUILD_CFLAGS += $(call cc-option, -Wno-dangling-pointer) -# Variable Length Arrays (VLAs) should not be used anywhere in the kernel -KBUILD_CFLAGS += -Wvla +# Stack Variable Length Arrays (VLAs) must not be used in the kernel. +# Function array parameters should, however, be usable, but -Wvla will +# warn for those. Clang has no way yet to distinguish between the VLA +# types, so depend on GCC for now to keep stack VLAs out of the tree. +# https://github.com/llvm/llvm-project/issues/57098 +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98217 +KBUILD_CFLAGS += $(call cc-option,-Wvla-larger-than=1) # disable pointer signed / unsigned warnings in gcc 4.0 KBUILD_CFLAGS += -Wno-pointer-sign @@ -56,6 +74,13 @@ KBUILD_CFLAGS += -Wno-pointer-sign # globally built with -Wcast-function-type. KBUILD_CFLAGS += $(call cc-option, -Wcast-function-type) +# Currently, disable -Wstringop-overflow for GCC 11, globally. +KBUILD_CFLAGS-$(CONFIG_CC_NO_STRINGOP_OVERFLOW) += $(call cc-option, -Wno-stringop-overflow) +KBUILD_CFLAGS-$(CONFIG_CC_STRINGOP_OVERFLOW) += $(call cc-option, -Wstringop-overflow) + +# Currently, disable -Wunterminated-string-initialization as broken +KBUILD_CFLAGS += $(call cc-option, -Wno-unterminated-string-initialization) + # The allocators already balk at large sizes, so silence the compiler # warnings for bounds checks involving those possible values. While # -Wno-alloc-size-larger-than would normally be used here, earlier versions @@ -82,22 +107,16 @@ KBUILD_CFLAGS += $(call cc-option,-Werror=designated-init) # Warn if there is an enum types mismatch KBUILD_CFLAGS += $(call cc-option,-Wenum-conversion) +KBUILD_CFLAGS += -Wunused + # # W=1 - warnings which may be relevant and do not occur too often # ifneq ($(findstring 1, $(KBUILD_EXTRA_WARN)),) -KBUILD_CFLAGS += -Wextra -Wunused -Wno-unused-parameter -KBUILD_CFLAGS += $(call cc-option, -Wrestrict) KBUILD_CFLAGS += -Wmissing-format-attribute -KBUILD_CFLAGS += -Wold-style-definition KBUILD_CFLAGS += -Wmissing-include-dirs -KBUILD_CFLAGS += $(call cc-option, -Wunused-but-set-variable) KBUILD_CFLAGS += $(call cc-option, -Wunused-const-variable) -KBUILD_CFLAGS += $(call cc-option, -Wpacked-not-aligned) -KBUILD_CFLAGS += $(call cc-option, -Wformat-overflow) -KBUILD_CFLAGS += $(call cc-option, -Wformat-truncation) -KBUILD_CFLAGS += $(call cc-option, -Wstringop-truncation) KBUILD_CPPFLAGS += -Wundef KBUILD_CPPFLAGS += -DKBUILD_EXTRA_WARN1 @@ -106,13 +125,16 @@ else # Some diagnostics enabled by default are noisy. # Suppress them by using -Wno... except for W=1. -KBUILD_CFLAGS += $(call cc-disable-warning, unused-but-set-variable) -KBUILD_CFLAGS += $(call cc-disable-warning, unused-const-variable) -KBUILD_CFLAGS += $(call cc-disable-warning, restrict) -KBUILD_CFLAGS += $(call cc-disable-warning, packed-not-aligned) -KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow) -KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation) -KBUILD_CFLAGS += $(call cc-disable-warning, stringop-truncation) +KBUILD_CFLAGS += $(call cc-option, -Wno-unused-but-set-variable) +KBUILD_CFLAGS += $(call cc-option, -Wno-unused-const-variable) +KBUILD_CFLAGS += $(call cc-option, -Wno-packed-not-aligned) +KBUILD_CFLAGS += $(call cc-option, -Wno-format-overflow) +ifdef CONFIG_CC_IS_GCC +KBUILD_CFLAGS += $(call cc-option, -Wno-format-truncation) +endif +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. @@ -128,12 +150,10 @@ ifeq ($(call clang-min-version, 120000),y) KBUILD_CFLAGS += -Wformat-insufficient-args endif endif -KBUILD_CFLAGS += $(call cc-disable-warning, pointer-to-enum-cast) +KBUILD_CFLAGS += $(call cc-option, -Wno-pointer-to-enum-cast) KBUILD_CFLAGS += -Wno-tautological-constant-out-of-range-compare -KBUILD_CFLAGS += $(call cc-disable-warning, unaligned-access) -KBUILD_CFLAGS += $(call cc-disable-warning, cast-function-type-strict) +KBUILD_CFLAGS += $(call cc-option, -Wno-unaligned-access) KBUILD_CFLAGS += -Wno-enum-compare-conditional -KBUILD_CFLAGS += -Wno-enum-enum-conversion endif endif @@ -146,15 +166,8 @@ ifneq ($(findstring 2, $(KBUILD_EXTRA_WARN)),) KBUILD_CFLAGS += -Wdisabled-optimization KBUILD_CFLAGS += -Wshadow KBUILD_CFLAGS += $(call cc-option, -Wlogical-op) -KBUILD_CFLAGS += -Wmissing-field-initializers -KBUILD_CFLAGS += -Wtype-limits -KBUILD_CFLAGS += $(call cc-option, -Wmaybe-uninitialized) KBUILD_CFLAGS += $(call cc-option, -Wunused-macros) -ifdef CONFIG_CC_IS_CLANG -KBUILD_CFLAGS += -Winitializer-overrides -endif - KBUILD_CPPFLAGS += -DKBUILD_EXTRA_WARN2 else @@ -165,8 +178,10 @@ KBUILD_CFLAGS += -Wno-type-limits KBUILD_CFLAGS += -Wno-shift-negative-value ifdef CONFIG_CC_IS_CLANG -KBUILD_CFLAGS += -Wno-initializer-overrides -else +KBUILD_CFLAGS += -Wno-enum-enum-conversion +endif + +ifdef CONFIG_CC_IS_GCC KBUILD_CFLAGS += -Wno-maybe-uninitialized endif @@ -194,6 +209,7 @@ else # The following turn off the warnings enabled by -Wextra KBUILD_CFLAGS += -Wno-sign-compare +KBUILD_CFLAGS += -Wno-unused-parameter endif diff --git a/scripts/Makefile.gcc-plugins b/scripts/Makefile.gcc-plugins index e4deaf5fa571..435ab3f0ec44 100644 --- a/scripts/Makefile.gcc-plugins +++ b/scripts/Makefile.gcc-plugins @@ -8,20 +8,6 @@ ifdef CONFIG_GCC_PLUGIN_LATENT_ENTROPY endif export DISABLE_LATENT_ENTROPY_PLUGIN -gcc-plugin-$(CONFIG_GCC_PLUGIN_STRUCTLEAK) += structleak_plugin.so -gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK_VERBOSE) \ - += -fplugin-arg-structleak_plugin-verbose -gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF) \ - += -fplugin-arg-structleak_plugin-byref -gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL) \ - += -fplugin-arg-structleak_plugin-byref-all -ifdef CONFIG_GCC_PLUGIN_STRUCTLEAK - DISABLE_STRUCTLEAK_PLUGIN += -fplugin-arg-structleak_plugin-disable -endif -export DISABLE_STRUCTLEAK_PLUGIN -gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK) \ - += -DSTRUCTLEAK_PLUGIN - gcc-plugin-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak_plugin.so gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STACKLEAK) \ += -DSTACKLEAK_PLUGIN @@ -36,15 +22,9 @@ ifdef CONFIG_GCC_PLUGIN_STACKLEAK endif export DISABLE_STACKLEAK_PLUGIN -gcc-plugin-$(CONFIG_GCC_PLUGIN_ARM_SSP_PER_TASK) += arm_ssp_per_task_plugin.so -ifdef CONFIG_GCC_PLUGIN_ARM_SSP_PER_TASK - DISABLE_ARM_SSP_PER_TASK_PLUGIN += -fplugin-arg-arm_ssp_per_task_plugin-disable -endif -export DISABLE_ARM_SSP_PER_TASK_PLUGIN - # All the plugin CFLAGS are collected here in case a build target needs to # filter them out of the KBUILD_CFLAGS. -GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) +GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) -DGCC_PLUGINS export GCC_PLUGINS_CFLAGS # Add the flags to the build! @@ -52,8 +32,6 @@ KBUILD_CFLAGS += $(GCC_PLUGINS_CFLAGS) # Some plugins are enabled outside of this Makefile, but they still need to # be included in GCC_PLUGIN so they can get built. -gcc-plugin-external-$(CONFIG_GCC_PLUGIN_SANCOV) \ - += sancov_plugin.so gcc-plugin-external-$(CONFIG_GCC_PLUGIN_RANDSTRUCT) \ += randomize_layout_plugin.so diff --git a/scripts/Makefile.host b/scripts/Makefile.host index 3c17e6ba421c..c1dedf646a39 100644 --- a/scripts/Makefile.host +++ b/scripts/Makefile.host @@ -96,12 +96,10 @@ hostrust_flags = --out-dir $(dir $@) --emit=dep-info=$(depfile) \ $(KBUILD_HOSTRUSTFLAGS) $(HOST_EXTRARUSTFLAGS) \ $(HOSTRUSTFLAGS_$(target-stem)) -# $(objtree)/$(obj) for including generated headers from checkin source files -ifeq ($(KBUILD_EXTMOD),) +# $(obj) for including generated headers from checkin source files ifdef building_out_of_srctree -hostc_flags += -I $(objtree)/$(obj) -hostcxx_flags += -I $(objtree)/$(obj) -endif +hostc_flags += -I $(obj) +hostcxx_flags += -I $(obj) endif ##### @@ -112,7 +110,7 @@ endif quiet_cmd_host-csingle = HOSTCC $@ cmd_host-csingle = $(HOSTCC) $(hostc_flags) $(KBUILD_HOSTLDFLAGS) -o $@ $< \ $(KBUILD_HOSTLDLIBS) $(HOSTLDLIBS_$(target-stem)) -$(host-csingle): $(obj)/%: $(src)/%.c FORCE +$(host-csingle): $(obj)/%: $(obj)/%.c FORCE $(call if_changed_dep,host-csingle) # Link an executable based on list of .o files, all plain c @@ -129,7 +127,7 @@ $(call multi_depend, $(host-cmulti), , -objs) # host-cobjs -> .o quiet_cmd_host-cobjs = HOSTCC $@ cmd_host-cobjs = $(HOSTCC) $(hostc_flags) -c -o $@ $< -$(host-cobjs): $(obj)/%.o: $(src)/%.c FORCE +$(host-cobjs): $(obj)/%.o: $(obj)/%.c FORCE $(call if_changed_dep,host-cobjs) # Link an executable based on list of .o files, a mixture of .c and .cc @@ -146,7 +144,7 @@ $(call multi_depend, $(host-cxxmulti), , -objs -cxxobjs) # Create .o file from a single .cc (C++) file quiet_cmd_host-cxxobjs = HOSTCXX $@ cmd_host-cxxobjs = $(HOSTCXX) $(hostcxx_flags) -c -o $@ $< -$(host-cxxobjs): $(obj)/%.o: $(src)/%.cc FORCE +$(host-cxxobjs): $(obj)/%.o: $(obj)/%.cc FORCE $(call if_changed_dep,host-cxxobjs) # Create executable from a single Rust crate (which may consist of @@ -160,3 +158,8 @@ $(host-rust): $(obj)/%: $(src)/%.rs FORCE targets += $(host-csingle) $(host-cmulti) $(host-cobjs) \ $(host-cxxmulti) $(host-cxxobjs) $(host-rust) + +# %.lex.o <- %.lex.c <- %.l +# %.tab.o <- %.tab.[ch] <- %.y +targets += $(call intermediate_targets, .lex.o, .lex.c) \ + $(call intermediate_targets, .tab.o, .tab.c .tab.h) diff --git a/scripts/Makefile.kasan b/scripts/Makefile.kasan index 390658a2d5b7..693dbbebebba 100644 --- a/scripts/Makefile.kasan +++ b/scripts/Makefile.kasan @@ -12,6 +12,11 @@ endif KASAN_SHADOW_OFFSET ?= $(CONFIG_KASAN_SHADOW_OFFSET) cc-param = $(call cc-option, -mllvm -$(1), $(call cc-option, --param $(1))) +rustc-param = $(call rustc-option, -Cllvm-args=-$(1),) + +check-args = $(foreach arg,$(2),$(call $(1),$(arg))) + +kasan_params := ifdef CONFIG_KASAN_STACK stack_enable := 1 @@ -22,57 +27,78 @@ endif ifdef CONFIG_KASAN_GENERIC ifdef CONFIG_KASAN_INLINE + # When the number of memory accesses in a function is less than this + # call threshold number, the compiler will use inline instrumentation. + # 10000 is chosen offhand as a sufficiently large number to make all + # kernel functions to be instrumented inline. call_threshold := 10000 else call_threshold := 0 endif -CFLAGS_KASAN_MINIMAL := -fsanitize=kernel-address - -# -fasan-shadow-offset fails without -fsanitize -CFLAGS_KASAN_SHADOW := $(call cc-option, -fsanitize=kernel-address \ - -fasan-shadow-offset=$(KASAN_SHADOW_OFFSET), \ - $(call cc-option, -fsanitize=kernel-address \ - -mllvm -asan-mapping-offset=$(KASAN_SHADOW_OFFSET))) - -ifeq ($(strip $(CFLAGS_KASAN_SHADOW)),) - CFLAGS_KASAN := $(CFLAGS_KASAN_MINIMAL) -else - # Now add all the compiler specific options that are valid standalone - CFLAGS_KASAN := $(CFLAGS_KASAN_SHADOW) \ - $(call cc-param,asan-globals=1) \ - $(call cc-param,asan-instrumentation-with-call-threshold=$(call_threshold)) \ - $(call cc-param,asan-instrument-allocas=1) -endif - -CFLAGS_KASAN += $(call cc-param,asan-stack=$(stack_enable)) +# First, enable -fsanitize=kernel-address together with providing the shadow +# mapping offset, as for GCC, -fasan-shadow-offset fails without -fsanitize +# (GCC accepts the shadow mapping offset via -fasan-shadow-offset instead of +# a --param like the other KASAN parameters). +# Instead of ifdef-checking the compiler, rely on cc-option. +CFLAGS_KASAN := $(call cc-option, -fsanitize=kernel-address \ + -fasan-shadow-offset=$(KASAN_SHADOW_OFFSET), \ + $(call cc-option, -fsanitize=kernel-address \ + -mllvm -asan-mapping-offset=$(KASAN_SHADOW_OFFSET))) + +# The minimum supported `rustc` version has a minimum supported LLVM +# version late enough that we can assume support for -asan-mapping-offset. +RUSTFLAGS_KASAN := -Zsanitizer=kernel-address \ + -Zsanitizer-recover=kernel-address \ + -Cllvm-args=-asan-mapping-offset=$(KASAN_SHADOW_OFFSET) + +# Now, add other parameters enabled similarly in GCC, Clang, and rustc. +# As some of them are not supported by older compilers, these will be filtered +# through `cc-param` or `rust-param` as applicable. +kasan_params += asan-instrumentation-with-call-threshold=$(call_threshold) \ + asan-stack=$(stack_enable) \ + asan-instrument-allocas=1 \ + asan-globals=1 # Instrument memcpy/memset/memmove calls by using instrumented __asan_mem*() # instead. With compilers that don't support this option, compiler-inserted # memintrinsics won't be checked by KASAN on GENERIC_ENTRY architectures. -CFLAGS_KASAN += $(call cc-param,asan-kernel-mem-intrinsic-prefix=1) +kasan_params += asan-kernel-mem-intrinsic-prefix=1 endif # CONFIG_KASAN_GENERIC 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 + ifdef CONFIG_KASAN_INLINE - instrumentation_flags := $(call cc-param,hwasan-mapping-offset=$(KASAN_SHADOW_OFFSET)) + kasan_params += hwasan-mapping-offset=$(KASAN_SHADOW_OFFSET) else - instrumentation_flags := $(call cc-param,hwasan-instrument-with-calls=1) + kasan_params += hwasan-instrument-with-calls=1 endif -CFLAGS_KASAN := -fsanitize=kernel-hwaddress \ - $(call cc-param,hwasan-instrument-stack=$(stack_enable)) \ - $(call cc-param,hwasan-use-short-granules=0) \ - $(call cc-param,hwasan-inline-all-checks=0) \ - $(instrumentation_flags) +kasan_params += hwasan-instrument-stack=$(stack_enable) \ + hwasan-use-short-granules=0 \ + hwasan-inline-all-checks=0 # Instrument memcpy/memset/memmove calls by using instrumented __hwasan_mem*(). ifeq ($(call clang-min-version, 150000)$(call gcc-min-version, 130000),y) -CFLAGS_KASAN += $(call cc-param,hwasan-kernel-mem-intrinsic-prefix=1) + kasan_params += hwasan-kernel-mem-intrinsic-prefix=1 endif endif # CONFIG_KASAN_SW_TAGS -export CFLAGS_KASAN CFLAGS_KASAN_NOSANITIZE +# Add all as-supported KASAN LLVM parameters requested by the configuration. +CFLAGS_KASAN += $(call check-args, cc-param, $(kasan_params)) + +ifdef CONFIG_RUST + # Avoid calling `rustc-param` unless Rust is enabled. + RUSTFLAGS_KASAN += $(call check-args, rustc-param, $(kasan_params)) +endif # CONFIG_RUST + +export CFLAGS_KASAN CFLAGS_KASAN_NOSANITIZE RUSTFLAGS_KASAN diff --git a/scripts/Makefile.kcov b/scripts/Makefile.kcov index 67e8cfe3474b..78305a84ba9d 100644 --- a/scripts/Makefile.kcov +++ b/scripts/Makefile.kcov @@ -1,6 +1,11 @@ # SPDX-License-Identifier: GPL-2.0-only -kcov-flags-$(CONFIG_CC_HAS_SANCOV_TRACE_PC) += -fsanitize-coverage=trace-pc +kcov-flags-y += -fsanitize-coverage=trace-pc kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -fsanitize-coverage=trace-cmp -kcov-flags-$(CONFIG_GCC_PLUGIN_SANCOV) += -fplugin=$(objtree)/scripts/gcc-plugins/sancov_plugin.so + +kcov-rflags-y += -Cpasses=sancov-module +kcov-rflags-y += -Cllvm-args=-sanitizer-coverage-level=3 +kcov-rflags-y += -Cllvm-args=-sanitizer-coverage-trace-pc +kcov-rflags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -Cllvm-args=-sanitizer-coverage-trace-compares export CFLAGS_KCOV := $(kcov-flags-y) +export RUSTFLAGS_KCOV := $(kcov-rflags-y) diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 3179747cbd2c..1d581ba5df66 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -1,114 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -# Backward compatibility -asflags-y += $(EXTRA_AFLAGS) -ccflags-y += $(EXTRA_CFLAGS) -cppflags-y += $(EXTRA_CPPFLAGS) -ldflags-y += $(EXTRA_LDFLAGS) - -# flags that take effect in current and sub directories -KBUILD_AFLAGS += $(subdir-asflags-y) -KBUILD_CFLAGS += $(subdir-ccflags-y) -KBUILD_RUSTFLAGS += $(subdir-rustflags-y) - -# Figure out what we need to build from the various variables -# =========================================================================== - -# When an object is listed to be built compiled-in and modular, -# only build the compiled-in version -obj-m := $(filter-out $(obj-y),$(obj-m)) - -# Libraries are always collected in one lib file. -# Filter out objects already built-in -lib-y := $(filter-out $(obj-y), $(sort $(lib-y) $(lib-m))) - -# Subdirectories we need to descend into -subdir-ym := $(sort $(subdir-y) $(subdir-m) \ - $(patsubst %/,%, $(filter %/, $(obj-y) $(obj-m)))) - -# Handle objects in subdirs: -# - If we encounter foo/ in $(obj-y), replace it by foo/built-in.a and -# foo/modules.order -# - If we encounter foo/ in $(obj-m), replace it by foo/modules.order -# -# Generate modules.order to determine modorder. Unfortunately, we don't have -# information about ordering between -y and -m subdirs. Just put -y's first. - -ifdef need-modorder -obj-m := $(patsubst %/,%/modules.order, $(filter %/, $(obj-y)) $(obj-m)) -else -obj-m := $(filter-out %/, $(obj-m)) -endif - -ifdef need-builtin -obj-y := $(patsubst %/, %/built-in.a, $(obj-y)) -else -obj-y := $(filter-out %/, $(obj-y)) -endif - -ifdef need-dtbslist -dtb-y += $(addsuffix /dtbs-list, $(subdir-ym)) -always-y += dtbs-list -endif - -# Expand $(foo-objs) $(foo-y) etc. by replacing their individuals -suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s)))) -# List composite targets that are constructed by combining other targets -multi-search = $(sort $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $m))) -# List primitive targets that are compiled from source files -real-search = $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $(call suffix-search, $m, $2, $3), $m)) - -# If $(foo-objs), $(foo-y), $(foo-m), or $(foo-) exists, foo.o is a composite object -multi-obj-y := $(call multi-search, $(obj-y), .o, -objs -y) -multi-obj-m := $(call multi-search, $(obj-m), .o, -objs -y -m) -multi-obj-ym := $(multi-obj-y) $(multi-obj-m) - -# Replace multi-part objects by their individual parts, -# including built-in.a from subdirectories -real-obj-y := $(call real-search, $(obj-y), .o, -objs -y) -real-obj-m := $(call real-search, $(obj-m), .o, -objs -y -m) - -always-y += $(always-m) - -# hostprogs-always-y += foo -# ... is a shorthand for -# hostprogs += foo -# always-y += foo -hostprogs += $(hostprogs-always-y) $(hostprogs-always-m) -always-y += $(hostprogs-always-y) $(hostprogs-always-m) - -# userprogs-always-y is likewise. -userprogs += $(userprogs-always-y) $(userprogs-always-m) -always-y += $(userprogs-always-y) $(userprogs-always-m) - -# DTB -# If CONFIG_OF_ALL_DTBS is enabled, all DT blobs are built -dtb-$(CONFIG_OF_ALL_DTBS) += $(dtb-) - -# Composite DTB (i.e. DTB constructed by overlay) -multi-dtb-y := $(call multi-search, $(dtb-y), .dtb, -dtbs) -# Primitive DTB compiled from *.dts -real-dtb-y := $(call real-search, $(dtb-y), .dtb, -dtbs) -# Base DTB that overlay is applied onto -base-dtb-y := $(filter %.dtb, $(call real-search, $(multi-dtb-y), .dtb, -dtbs)) - -always-y += $(dtb-y) - -# Add subdir path - -ifneq ($(obj),.) -extra-y := $(addprefix $(obj)/,$(extra-y)) -always-y := $(addprefix $(obj)/,$(always-y)) -targets := $(addprefix $(obj)/,$(targets)) -obj-m := $(addprefix $(obj)/,$(obj-m)) -lib-y := $(addprefix $(obj)/,$(lib-y)) -real-obj-y := $(addprefix $(obj)/,$(real-obj-y)) -real-obj-m := $(addprefix $(obj)/,$(real-obj-m)) -multi-obj-m := $(addprefix $(obj)/, $(multi-obj-m)) -dtb-y := $(addprefix $(obj)/, $(dtb-y)) -multi-dtb-y := $(addprefix $(obj)/, $(multi-dtb-y)) -real-dtb-y := $(addprefix $(obj)/, $(real-dtb-y)) -subdir-ym := $(addprefix $(obj)/,$(subdir-ym)) -endif # Finds the multi-part object the current object will be linked into. # If the object belongs to two or more multi-part objects, list them all. @@ -154,7 +44,7 @@ _cpp_flags = $(KBUILD_CPPFLAGS) $(cppflags-y) $(CPPFLAGS_$(target-stem).lds) # ifeq ($(CONFIG_GCOV_KERNEL),y) _c_flags += $(if $(patsubst n%,, \ - $(GCOV_PROFILE_$(target-stem).o)$(GCOV_PROFILE)$(CONFIG_GCOV_PROFILE_ALL)), \ + $(GCOV_PROFILE_$(target-stem).o)$(GCOV_PROFILE)$(if $(is-kernel-object),$(CONFIG_GCOV_PROFILE_ALL))), \ $(CFLAGS_GCOV)) endif @@ -165,33 +55,39 @@ endif ifeq ($(CONFIG_KASAN),y) ifneq ($(CONFIG_KASAN_HW_TAGS),y) _c_flags += $(if $(patsubst n%,, \ - $(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)y), \ + $(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)$(is-kernel-object)), \ $(CFLAGS_KASAN), $(CFLAGS_KASAN_NOSANITIZE)) +_rust_flags += $(if $(patsubst n%,, \ + $(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)$(is-kernel-object)), \ + $(RUSTFLAGS_KASAN)) endif endif ifeq ($(CONFIG_KMSAN),y) _c_flags += $(if $(patsubst n%,, \ - $(KMSAN_SANITIZE_$(target-stem).o)$(KMSAN_SANITIZE)y), \ + $(KMSAN_SANITIZE_$(target-stem).o)$(KMSAN_SANITIZE)$(is-kernel-object)), \ $(CFLAGS_KMSAN)) _c_flags += $(if $(patsubst n%,, \ - $(KMSAN_ENABLE_CHECKS_$(target-stem).o)$(KMSAN_ENABLE_CHECKS)y), \ + $(KMSAN_ENABLE_CHECKS_$(target-stem).o)$(KMSAN_ENABLE_CHECKS)$(is-kernel-object)), \ , -mllvm -msan-disable-checks=1) endif ifeq ($(CONFIG_UBSAN),y) _c_flags += $(if $(patsubst n%,, \ - $(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SANITIZE)y), \ + $(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SANITIZE)$(is-kernel-object)), \ $(CFLAGS_UBSAN)) _c_flags += $(if $(patsubst n%,, \ - $(UBSAN_SIGNED_WRAP_$(target-stem).o)$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SIGNED_WRAP)$(UBSAN_SANITIZE)y), \ - $(CFLAGS_UBSAN_SIGNED_WRAP)) + $(UBSAN_INTEGER_WRAP_$(target-stem).o)$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_INTEGER_WRAP)$(UBSAN_SANITIZE)$(is-kernel-object)), \ + $(CFLAGS_UBSAN_INTEGER_WRAP)) endif ifeq ($(CONFIG_KCOV),y) _c_flags += $(if $(patsubst n%,, \ - $(KCOV_INSTRUMENT_$(target-stem).o)$(KCOV_INSTRUMENT)$(CONFIG_KCOV_INSTRUMENT_ALL)), \ + $(KCOV_INSTRUMENT_$(target-stem).o)$(KCOV_INSTRUMENT)$(if $(is-kernel-object),$(CONFIG_KCOV_INSTRUMENT_ALL))), \ $(CFLAGS_KCOV)) +_rust_flags += $(if $(patsubst n%,, \ + $(KCOV_INSTRUMENT_$(target-stem).o)$(KCOV_INSTRUMENT)$(if $(is-kernel-object),$(CONFIG_KCOV_INSTRUMENT_ALL))), \ + $(RUSTFLAGS_KCOV)) endif # @@ -200,7 +96,7 @@ endif # ifeq ($(CONFIG_KCSAN),y) _c_flags += $(if $(patsubst n%,, \ - $(KCSAN_SANITIZE_$(target-stem).o)$(KCSAN_SANITIZE)y), \ + $(KCSAN_SANITIZE_$(target-stem).o)$(KCSAN_SANITIZE)$(is-kernel-object)), \ $(CFLAGS_KCSAN)) # Some uninstrumented files provide implied barriers required to avoid false # positives: set KCSAN_INSTRUMENT_BARRIERS for barrier instrumentation only. @@ -209,16 +105,38 @@ _c_flags += $(if $(patsubst n%,, \ -D__KCSAN_INSTRUMENT_BARRIERS__) endif -# $(srctree)/$(src) for including checkin headers from generated source files -# $(objtree)/$(obj) for including generated headers from checkin source files -ifeq ($(KBUILD_EXTMOD),) -ifdef building_out_of_srctree -_c_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj) -_a_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj) -_cpp_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj) +# +# Enable AutoFDO build flags except some files or directories we don't want to +# enable (depends on variables AUTOFDO_PROFILE_obj.o and AUTOFDO_PROFILE). +# +ifeq ($(CONFIG_AUTOFDO_CLANG),y) +_c_flags += $(if $(patsubst n%,, \ + $(AUTOFDO_PROFILE_$(target-stem).o)$(AUTOFDO_PROFILE)$(is-kernel-object)), \ + $(CFLAGS_AUTOFDO_CLANG)) endif + +# +# Enable Propeller build flags except some files or directories we don't want to +# enable (depends on variables AUTOFDO_PROPELLER_obj.o and PROPELLER_PROFILE). +# +ifdef CONFIG_PROPELLER_CLANG +_c_flags += $(if $(patsubst n%,, \ + $(AUTOFDO_PROFILE_$(target-stem).o)$(AUTOFDO_PROFILE)$(PROPELLER_PROFILE))$(is-kernel-object), \ + $(CFLAGS_PROPELLER_CLANG)) endif +# $(src) for including checkin headers from generated source files +# $(obj) for including generated headers from checkin source files +ifdef building_out_of_srctree +_c_flags += $(addprefix -I, $(src) $(obj)) +_a_flags += $(addprefix -I, $(src) $(obj)) +_cpp_flags += $(addprefix -I, $(src) $(obj)) +endif + +# If $(is-kernel-object) is 'y', this object will be linked to vmlinux or modules +is-kernel-object = $(or $(part-of-builtin),$(part-of-module)) + +part-of-builtin = $(if $(filter $(basename $@).o, $(real-obj-y) $(lib-y)),y) part-of-module = $(if $(filter $(basename $@).o, $(real-obj-m)),y) quiet_modtag = $(if $(part-of-module),[M], ) @@ -234,7 +152,7 @@ modkern_rustflags = \ modkern_aflags = $(if $(part-of-module), \ $(KBUILD_AFLAGS_MODULE) $(AFLAGS_MODULE), \ - $(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL)) + $(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL) $(modfile_flags)) c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ -include $(srctree)/include/linux/compiler_types.h \ @@ -244,19 +162,13 @@ c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ rust_flags = $(_rust_flags) $(modkern_rustflags) @$(objtree)/include/generated/rustc_cfg a_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ - $(_a_flags) $(modkern_aflags) + $(_a_flags) $(modkern_aflags) $(modname_flags) cpp_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ $(_cpp_flags) ld_flags = $(KBUILD_LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F)) -DTC_INCLUDE := $(srctree)/scripts/dtc/include-prefixes - -dtc_cpp_flags = -Wp,-MMD,$(depfile).pre.tmp -nostdinc \ - $(addprefix -I,$(DTC_INCLUDE)) \ - -undef -D__DTS__ - ifdef CONFIG_OBJTOOL objtool := $(objtree)/tools/objtool/objtool @@ -277,8 +189,9 @@ objtool-args-$(CONFIG_MITIGATION_SLS) += --sls objtool-args-$(CONFIG_STACK_VALIDATION) += --stackval objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess -objtool-args-$(CONFIG_GCOV_KERNEL) += --no-unreachable +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) \ $(if $(delay-objtool), --link) \ @@ -289,6 +202,8 @@ delay-objtool := $(or $(CONFIG_LTO_CLANG),$(CONFIG_X86_KERNEL_IBT)) cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool-args) $@) cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$@: $$(wildcard $(objtool))' ; } >> $(dot-target).cmd) +objtool-enabled := y + endif # CONFIG_OBJTOOL # Useful for describing the dependency of composite objects @@ -300,6 +215,55 @@ $(foreach m, $1, \ $(addprefix $(obj)/, $(call suffix-search, $(patsubst $(obj)/%,%,$m), $2, $3)))) endef +# Remove ".." and "." from a path, without using "realpath" +# Usage: +# $(call normalize_path,path/to/../file) +define normalize_path +$(strip $(eval elements :=) \ +$(foreach elem,$(subst /, ,$1), \ + $(if $(filter-out .,$(elem)), \ + $(if $(filter ..,$(elem)), \ + $(eval elements := $(wordlist 2,$(words $(elements)),x $(elements))), \ + $(eval elements := $(elements) $(elem))))) \ +$(subst $(space),/,$(elements))) +endef + +# Build commands +# =========================================================================== +# These are shared by some Makefile.* files. + +ifdef CONFIG_LTO_CLANG +# Run $(LD) here to convert LLVM IR to ELF in the following cases: +# - when this object needs objtool processing, as objtool cannot process LLVM IR +# - when this is a single-object module, as modpost cannot process LLVM IR +cmd_ld_single = $(if $(objtool-enabled)$(is-single-obj-m), ; $(LD) $(ld_flags) -r -o $(tmp-target) $@; mv $(tmp-target) $@) +endif + +quiet_cmd_cc_o_c = CC $(quiet_modtag) $@ + cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \ + $(cmd_ld_single) \ + $(cmd_objtool) + +define rule_cc_o_c + $(call cmd_and_fixdep,cc_o_c) + $(call cmd,checksrc) + $(call cmd,checkdoc) + $(call cmd,gen_objtooldep) + $(call cmd,gen_symversions_c) + $(call cmd,record_mcount) + $(call cmd,warn_shared_object) +endef + +quiet_cmd_as_o_S = AS $(quiet_modtag) $@ + cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $< $(cmd_objtool) + +define rule_as_o_S + $(call cmd_and_fixdep,as_o_S) + $(call cmd,gen_objtooldep) + $(call cmd,gen_symversions_S) + $(call cmd,warn_shared_object) +endef + # Copy a file # =========================================================================== # 'cp' preserves permissions. If you use it to copy a file in read-only srctree, @@ -311,6 +275,11 @@ quiet_cmd_copy = COPY $@ $(obj)/%: $(src)/%_shipped $(call cmd,copy) +# Touch a file +# =========================================================================== +quiet_cmd_touch = TOUCH $(call normalize_path,$@) + cmd_touch = touch $@ + # Commands useful for building a boot image # =========================================================================== # @@ -346,91 +315,6 @@ cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ quiet_cmd_gzip = GZIP $@ cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@ -# DTC -# --------------------------------------------------------------------------- -DTC ?= $(objtree)/scripts/dtc/dtc -DTC_FLAGS += \ - -Wno-unique_unit_address - -# Disable noisy checks by default -ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),) -DTC_FLAGS += -Wno-unit_address_vs_reg \ - -Wno-avoid_unnecessary_addr_size \ - -Wno-alias_paths \ - -Wno-graph_child_address \ - -Wno-simple_bus_reg -else -DTC_FLAGS += \ - -Wunique_unit_address_if_enabled -endif - -ifneq ($(findstring 2,$(KBUILD_EXTRA_WARN)),) -DTC_FLAGS += -Wnode_name_chars_strict \ - -Wproperty_name_chars_strict \ - -Wunique_unit_address -endif - -DTC_FLAGS += $(DTC_FLAGS_$(target-stem)) - -# Set -@ if the target is a base DTB that overlay is applied onto -DTC_FLAGS += $(if $(filter $(patsubst $(obj)/%,%,$@), $(base-dtb-y)), -@) - -# Generate an assembly file to wrap the output of the device tree compiler -quiet_cmd_wrap_S_dtb = WRAP $@ - cmd_wrap_S_dtb = { \ - symbase=__$(patsubst .%,%,$(suffix $<))_$(subst -,_,$(notdir $*)); \ - echo '\#include <asm-generic/vmlinux.lds.h>'; \ - echo '.section .dtb.init.rodata,"a"'; \ - echo '.balign STRUCT_ALIGNMENT'; \ - echo ".global $${symbase}_begin"; \ - echo "$${symbase}_begin:"; \ - echo '.incbin "$<" '; \ - echo ".global $${symbase}_end"; \ - echo "$${symbase}_end:"; \ - echo '.balign STRUCT_ALIGNMENT'; \ - } > $@ - -$(obj)/%.dtb.S: $(obj)/%.dtb FORCE - $(call if_changed,wrap_S_dtb) - -$(obj)/%.dtbo.S: $(obj)/%.dtbo FORCE - $(call if_changed,wrap_S_dtb) - -quiet_cmd_dtc = DTC $@ -cmd_dtc = $(HOSTCC) -E $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \ - $(DTC) -o $@ -b 0 \ - $(addprefix -i,$(dir $<) $(DTC_INCLUDE)) $(DTC_FLAGS) \ - -d $(depfile).dtc.tmp $(dtc-tmp) ; \ - cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile) - -quiet_cmd_fdtoverlay = DTOVL $@ - cmd_fdtoverlay = $(objtree)/scripts/dtc/fdtoverlay -o $@ -i $(real-prereqs) - -$(multi-dtb-y): FORCE - $(call if_changed,fdtoverlay) -$(call multi_depend, $(multi-dtb-y), .dtb, -dtbs) - -ifneq ($(CHECK_DTBS)$(CHECK_DT_BINDING),) -DT_CHECKER ?= dt-validate -DT_CHECKER_FLAGS ?= $(if $(DT_SCHEMA_FILES),-l $(DT_SCHEMA_FILES),-m) -DT_BINDING_DIR := Documentation/devicetree/bindings -DT_TMP_SCHEMA := $(objtree)/$(DT_BINDING_DIR)/processed-schema.json - -quiet_cmd_dtb = DTC_CHK $@ - cmd_dtb = $(cmd_dtc) ; $(DT_CHECKER) $(DT_CHECKER_FLAGS) -u $(srctree)/$(DT_BINDING_DIR) -p $(DT_TMP_SCHEMA) $@ || true -else -quiet_cmd_dtb = $(quiet_cmd_dtc) - cmd_dtb = $(cmd_dtc) -endif - -$(obj)/%.dtb: $(src)/%.dts $(DTC) $(DT_TMP_SCHEMA) FORCE - $(call if_changed_dep,dtb) - -$(obj)/%.dtbo: $(src)/%.dtso $(DTC) FORCE - $(call if_changed_dep,dtc) - -dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp) - # Bzip2 # --------------------------------------------------------------------------- @@ -476,10 +360,10 @@ quiet_cmd_lzo_with_size = LZO $@ cmd_lzo_with_size = { cat $(real-prereqs) | $(KLZOP) -9; $(size_append); } > $@ quiet_cmd_lz4 = LZ4 $@ - cmd_lz4 = cat $(real-prereqs) | $(LZ4) -l -c1 stdin stdout > $@ + cmd_lz4 = cat $(real-prereqs) | $(LZ4) -l -9 - - > $@ quiet_cmd_lz4_with_size = LZ4 $@ - cmd_lz4_with_size = { cat $(real-prereqs) | $(LZ4) -l -c1 stdin stdout; \ + cmd_lz4_with_size = { cat $(real-prereqs) | $(LZ4) -l -9 - -; \ $(size_append); } > $@ # U-Boot mkimage @@ -504,16 +388,36 @@ quiet_cmd_uimage = UIMAGE $@ -a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \ -n '$(UIMAGE_NAME)' -d $< $@ +# Flat Image Tree (FIT) +# This allows for packaging of a kernel and all devicetrees files, using +# compression. +# --------------------------------------------------------------------------- + +MAKE_FIT := $(srctree)/scripts/make_fit.py + +# Use this to override the compression algorithm +FIT_COMPRESSION ?= gzip + +quiet_cmd_fit = FIT $@ + cmd_fit = $(MAKE_FIT) -o $@ --arch $(UIMAGE_ARCH) --os linux \ + --name '$(UIMAGE_NAME)' \ + $(if $(findstring 1,$(KBUILD_VERBOSE)),-v) \ + $(if $(FIT_DECOMPOSE_DTBS),--decompose-dtbs) \ + --compress $(FIT_COMPRESSION) -k $< @$(word 2,$^) + # XZ # --------------------------------------------------------------------------- -# Use xzkern to compress the kernel image and xzmisc to compress other things. +# Use xzkern or xzkern_with_size to compress the kernel image and xzmisc to +# compress other things. # # xzkern uses a big LZMA2 dictionary since it doesn't increase memory usage # of the kernel decompressor. A BCJ filter is used if it is available for -# the target architecture. xzkern also appends uncompressed size of the data -# using size_append. The .xz format has the size information available at -# the end of the file too, but it's in more complex format and it's good to -# avoid changing the part of the boot code that reads the uncompressed size. +# the target architecture. +# +# xzkern_with_size also appends uncompressed size of the data using +# size_append. The .xz format has the size information available at the end +# of the file too, but it's in more complex format and it's good to avoid +# changing the part of the boot code that reads the uncompressed size. # Note that the bytes added by size_append will make the xz tool think that # the file is corrupt. This is expected. # diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal index 8568d256d6fb..542ba462ed3e 100644 --- a/scripts/Makefile.modfinal +++ b/scripts/Makefile.modfinal @@ -6,14 +6,12 @@ PHONY := __modfinal __modfinal: -include include/config/auto.conf +include $(objtree)/include/config/auto.conf include $(srctree)/scripts/Kbuild.include - -# for c_flags include $(srctree)/scripts/Makefile.lib # find all modules listed in modules.order -modules := $(call read-file, $(MODORDER)) +modules := $(call read-file, modules.order) __modfinal: $(modules:%.o=%.ko) @: @@ -21,26 +19,28 @@ __modfinal: $(modules:%.o=%.ko) # modname and part-of-module are set to make c_flags define proper module flags modname = $(notdir $(@:.mod.o=)) part-of-module = y - -quiet_cmd_cc_o_c = CC [M] $@ - cmd_cc_o_c = $(CC) $(filter-out $(CC_FLAGS_CFI) $(CFLAGS_GCOV), $(c_flags)) -c -o $@ $< +GCOV_PROFILE := n +ccflags-remove-y := $(CC_FLAGS_CFI) %.mod.o: %.mod.c FORCE - $(call if_changed_dep,cc_o_c) + $(call if_changed_rule,cc_o_c) + +.module-common.o: $(srctree)/scripts/module-common.c FORCE + $(call if_changed_rule,cc_o_c) quiet_cmd_ld_ko_o = LD [M] $@ - cmd_ld_ko_o += \ + cmd_ld_ko_o = \ $(LD) -r $(KBUILD_LDFLAGS) \ $(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \ - -T scripts/module.lds -o $@ $(filter %.o, $^) + -T $(objtree)/scripts/module.lds -o $@ $(filter %.o, $^) quiet_cmd_btf_ko = BTF [M] $@ cmd_btf_ko = \ - if [ ! -f vmlinux ]; then \ + if [ ! -f $(objtree)/vmlinux ]; then \ printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \ else \ - LLVM_OBJCOPY="$(OBJCOPY)" $(PAHOLE) -J $(PAHOLE_FLAGS) --btf_base vmlinux $@; \ - $(RESOLVE_BTFIDS) -b vmlinux $@; \ + LLVM_OBJCOPY="$(OBJCOPY)" $(PAHOLE) -J $(PAHOLE_FLAGS) $(MODULE_PAHOLE_FLAGS) --btf_base $(objtree)/vmlinux $@; \ + $(RESOLVE_BTFIDS) -b $(objtree)/vmlinux $@; \ fi; # Same as newer-prereqs, but allows to exclude specified extra dependencies @@ -52,15 +52,15 @@ if_changed_except = $(if $(call newer_prereqs_except,$(2))$(cmd-check), \ 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 scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),vmlinux) FORCE - +$(call if_changed_except,ld_ko_o,vmlinux) +%.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) ifdef CONFIG_DEBUG_INFO_BTF_MODULES +$(if $(newer-prereqs),$(call cmd,btf_ko)) endif -targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o) +targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o) .module-common.o -# Add FORCE to the prequisites of a target to force it to be always rebuilt. +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. # --------------------------------------------------------------------------- PHONY += FORCE diff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst index 0afd75472679..1628198f3e83 100644 --- a/scripts/Makefile.modinst +++ b/scripts/Makefile.modinst @@ -6,7 +6,7 @@ PHONY := __modinst __modinst: -include include/config/auto.conf +include $(objtree)/include/config/auto.conf include $(srctree)/scripts/Kbuild.include install-y := @@ -30,15 +30,17 @@ $(MODLIB)/modules.order: modules.order FORCE quiet_cmd_install_modorder = INSTALL $@ cmd_install_modorder = sed 's:^\(.*\)\.o$$:kernel/\1.ko:' $< > $@ -# Install modules.builtin(.modinfo) even when CONFIG_MODULES is disabled. +# Install modules.builtin(.modinfo,.ranges) even when CONFIG_MODULES is disabled. install-y += $(addprefix $(MODLIB)/, modules.builtin modules.builtin.modinfo) -$(addprefix $(MODLIB)/, modules.builtin modules.builtin.modinfo): $(MODLIB)/%: % FORCE +install-$(CONFIG_BUILTIN_MODULE_RANGES) += $(MODLIB)/modules.builtin.ranges + +$(addprefix $(MODLIB)/, modules.builtin modules.builtin.modinfo modules.builtin.ranges): $(MODLIB)/%: % FORCE $(call cmd,install) endif -modules := $(call read-file, $(MODORDER)) +modules := $(call read-file, modules.order) ifeq ($(KBUILD_EXTMOD),) dst := $(MODLIB)/kernel @@ -51,11 +53,13 @@ $(foreach x, % :, $(if $(findstring $x, $(dst)), \ $(error module installation path cannot contain '$x'))) suffix-y := +ifdef CONFIG_MODULE_COMPRESS_ALL suffix-$(CONFIG_MODULE_COMPRESS_GZIP) := .gz suffix-$(CONFIG_MODULE_COMPRESS_XZ) := .xz suffix-$(CONFIG_MODULE_COMPRESS_ZSTD) := .zst +endif -modules := $(patsubst $(extmod_prefix)%.o, $(dst)/%.ko$(suffix-y), $(modules)) +modules := $(patsubst %.o, $(dst)/%.ko$(suffix-y), $(modules)) install-$(CONFIG_MODULES) += $(modules) __modinst: $(install-y) @@ -101,7 +105,7 @@ else sig-key := $(CONFIG_MODULE_SIG_KEY) endif quiet_cmd_sign = SIGN $@ - cmd_sign = scripts/sign-file $(CONFIG_MODULE_SIG_HASH) "$(sig-key)" certs/signing_key.x509 $@ \ + cmd_sign = $(objtree)/scripts/sign-file $(CONFIG_MODULE_SIG_HASH) "$(sig-key)" $(objtree)/certs/signing_key.x509 $@ \ $(if $(KBUILD_EXTMOD),|| true) ifeq ($(sign-only),) @@ -115,7 +119,7 @@ endif # Create necessary directories $(foreach dir, $(sort $(dir $(install-y))), $(shell mkdir -p $(dir))) -$(dst)/%.ko: $(extmod_prefix)%.ko FORCE +$(dst)/%.ko: %.ko FORCE $(call cmd,install) $(call cmd,strip) $(call cmd,sign) @@ -146,7 +150,7 @@ quiet_cmd_gzip = GZIP $@ quiet_cmd_xz = XZ $@ cmd_xz = $(XZ) --check=crc32 --lzma2=dict=1MiB -f $< quiet_cmd_zstd = ZSTD $@ - cmd_zstd = $(ZSTD) -T0 --rm -f -q $< + cmd_zstd = $(ZSTD) --rm -f -q $< $(dst)/%.ko.gz: $(dst)/%.ko FORCE $(call cmd,gzip) diff --git a/scripts/Makefile.modpost b/scripts/Makefile.modpost index 739402f45509..d7d45067d08b 100644 --- a/scripts/Makefile.modpost +++ b/scripts/Makefile.modpost @@ -35,18 +35,20 @@ PHONY := __modpost __modpost: -include include/config/auto.conf +include $(objtree)/include/config/auto.conf include $(srctree)/scripts/Kbuild.include -MODPOST = scripts/mod/modpost +MODPOST = $(objtree)/scripts/mod/modpost modpost-args = \ $(if $(CONFIG_MODULES),-M) \ $(if $(CONFIG_MODVERSIONS),-m) \ + $(if $(CONFIG_BASIC_MODVERSIONS),-b) \ + $(if $(CONFIG_EXTENDED_MODVERSIONS),-x) \ $(if $(CONFIG_MODULE_SRCVERSION_ALL),-a) \ $(if $(CONFIG_SECTION_MISMATCH_WARN_ONLY),,-E) \ $(if $(KBUILD_MODPOST_WARN),-w) \ - $(if $(KBUILD_NSDEPS),-d $(MODULES_NSDEPS)) \ + $(if $(KBUILD_NSDEPS),-d modules.nsdeps) \ $(if $(CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS)$(KBUILD_NSDEPS),-N) \ $(if $(findstring 1, $(KBUILD_EXTRA_WARN)),-W) \ -o $@ @@ -61,8 +63,8 @@ endif # Read out modules.order to pass in modpost. # Otherwise, allmodconfig would fail with "Argument list too long". ifdef KBUILD_MODULES -modpost-args += -T $(MODORDER) -modpost-deps += $(MODORDER) +modpost-args += -T modules.order +modpost-deps += modules.order endif ifeq ($(KBUILD_EXTMOD),) @@ -94,7 +96,7 @@ targets += .vmlinux.objs ifdef CONFIG_TRIM_UNUSED_KSYMS ksym-wl := $(CONFIG_UNUSED_KSYMS_WHITELIST) -ksym-wl := $(if $(filter-out /%, $(ksym-wl)),$(srctree)/)$(ksym-wl) +ksym-wl := $(if $(filter-out /%, $(ksym-wl)),$(if $(wildcard $(ksym-wl)),,$(srctree)/))$(ksym-wl) modpost-args += -t $(addprefix -u , $(ksym-wl)) modpost-deps += $(ksym-wl) endif @@ -111,19 +113,19 @@ endif else # set src + obj - they may be used in the modules's Makefile -obj := $(KBUILD_EXTMOD) -src := $(obj) +obj := . +src := $(srcroot) # Include the module's Makefile to find KBUILD_EXTRA_SYMBOLS include $(kbuild-file) -output-symdump := $(KBUILD_EXTMOD)/Module.symvers +output-symdump := Module.symvers -ifeq ($(wildcard Module.symvers),) -missing-input := Module.symvers +ifeq ($(wildcard $(objtree)/Module.symvers),) +missing-input := $(objtree)/Module.symvers else -modpost-args += -i Module.symvers -modpost-deps += Module.symvers +modpost-args += -i $(objtree)/Module.symvers +modpost-deps += $(objtree)/Module.symvers endif modpost-args += -e $(addprefix -i , $(KBUILD_EXTRA_SYMBOLS)) diff --git a/scripts/Makefile.package b/scripts/Makefile.package index 38653f3e8108..74bcb9e7f7a4 100644 --- a/scripts/Makefile.package +++ b/scripts/Makefile.package @@ -62,6 +62,10 @@ rpm-sources: linux.tar.gz PHONY += rpm-pkg srcrpm-pkg binrpm-pkg +ifneq ($(CC),$(HOSTCC)) +rpm-no-devel = --without=devel +endif + rpm-pkg: private build-type := a srcrpm-pkg: private build-type := s binrpm-pkg: private build-type := b @@ -72,7 +76,8 @@ rpm-pkg srcrpm-pkg binrpm-pkg: rpmbuild/SPECS/kernel.spec --define='_topdir $(abspath rpmbuild)' \ $(if $(filter a b, $(build-type)), \ --target $(UTS_MACHINE)-linux --build-in-place --noprep --define='_smp_mflags %{nil}' \ - $$(rpm -q rpm >/dev/null 2>&1 || echo --nodeps)) \ + $$(rpm -q rpm >/dev/null 2>&1 || echo --nodeps) \ + $(rpm-no-devel)) \ $(RPMOPTS)) # deb-pkg srcdeb-pkg bindeb-pkg @@ -103,7 +108,7 @@ debian-orig: private version = $(shell dpkg-parsechangelog -S Version | sed 's/- debian-orig: private orig-name = $(source)_$(version).orig.tar$(debian-orig-suffix) debian-orig: mkdebian-opts = --need-source debian-orig: linux.tar$(debian-orig-suffix) debian - $(Q)if [ "$(df --output=target .. 2>/dev/null)" = "$(df --output=target $< 2>/dev/null)" ]; then \ + $(Q)if [ "$$(df --output=target .. 2>/dev/null)" = "$$(df --output=target $< 2>/dev/null)" ]; then \ ln -f $< ../$(orig-name); \ else \ cp $< ../$(orig-name); \ @@ -141,6 +146,18 @@ snap-pkg: cd $(objtree)/snap && \ snapcraft --target-arch=$(UTS_MACHINE) +# pacman-pkg +# --------------------------------------------------------------------------- + +PHONY += pacman-pkg +pacman-pkg: + @ln -srf $(srctree)/scripts/package/PKGBUILD $(objtree)/PKGBUILD + +BUILDDIR="$(realpath $(objtree))/pacman" \ + CARCH="$(UTS_MACHINE)" \ + KBUILD_MAKEFLAGS="$(MAKEFLAGS)" \ + KBUILD_REVISION="$(shell $(srctree)/scripts/build-version)" \ + makepkg $(MAKEPKGOPTS) + # dir-pkg tar*-pkg - tarball targets # --------------------------------------------------------------------------- @@ -221,6 +238,7 @@ help: @echo ' bindeb-pkg - Build only the binary kernel deb package' @echo ' snap-pkg - Build only the binary kernel snap package' @echo ' (will connect to external hosts)' + @echo ' pacman-pkg - Build only the binary kernel pacman package' @echo ' dir-pkg - Build the kernel as a plain directory structure' @echo ' tar-pkg - Build the kernel as an uncompressed tarball' @echo ' targz-pkg - Build the kernel as a gzip compressed tarball' diff --git a/scripts/Makefile.propeller b/scripts/Makefile.propeller new file mode 100644 index 000000000000..48a660128e25 --- /dev/null +++ b/scripts/Makefile.propeller @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Enable available and selected Clang Propeller features. +ifdef CLANG_PROPELLER_PROFILE_PREFIX + CFLAGS_PROPELLER_CLANG := -fbasic-block-sections=list=$(CLANG_PROPELLER_PROFILE_PREFIX)_cc_profile.txt -ffunction-sections + KBUILD_LDFLAGS += --symbol-ordering-file=$(CLANG_PROPELLER_PROFILE_PREFIX)_ld_profile.txt --no-warn-symbol-ordering +else + # Starting with Clang v20, the '-fbasic-block-sections=labels' option is + # deprecated. Use the recommended '-fbasic-block-address-map' option. + # Link: https://github.com/llvm/llvm-project/pull/110039 + ifeq ($(call clang-min-version, 200000),y) + CFLAGS_PROPELLER_CLANG := -fbasic-block-address-map + else + CFLAGS_PROPELLER_CLANG := -fbasic-block-sections=labels + endif +endif + +# Propeller requires debug information to embed module names in the profiles. +# If CONFIG_DEBUG_INFO is not enabled, set -gmlt option. Skip this for AutoFDO, +# as the option should already be set. +ifndef CONFIG_DEBUG_INFO + ifndef CONFIG_AUTOFDO_CLANG + CFLAGS_PROPELLER_CLANG += -gmlt + endif +endif + +ifdef CONFIG_LTO_CLANG_THIN + ifdef CLANG_PROPELLER_PROFILE_PREFIX + KBUILD_LDFLAGS += --lto-basic-block-sections=$(CLANG_PROPELLER_PROFILE_PREFIX)_cc_profile.txt + else + ifeq ($(call test-ge, $(CONFIG_LLD_VERSION), 200000),y) + KBUILD_LDFLAGS += --lto-basic-block-address-map + else + KBUILD_LDFLAGS += --lto-basic-block-sections=labels + endif + endif +endif + +export CFLAGS_PROPELLER_CLANG diff --git a/scripts/Makefile.ubsan b/scripts/Makefile.ubsan index b2d3b273b802..734a102e6b56 100644 --- a/scripts/Makefile.ubsan +++ b/scripts/Makefile.ubsan @@ -1,5 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 +# Shared with KVM/arm64. +export CFLAGS_UBSAN_TRAP := $(call cc-option,-fsanitize-trap=undefined,-fsanitize-undefined-trap-on-error) + # Enable available and selected UBSAN features. ubsan-cflags-$(CONFIG_UBSAN_ALIGNMENT) += -fsanitize=alignment ubsan-cflags-$(CONFIG_UBSAN_BOUNDS_STRICT) += -fsanitize=bounds-strict @@ -10,9 +13,16 @@ ubsan-cflags-$(CONFIG_UBSAN_DIV_ZERO) += -fsanitize=integer-divide-by-zero ubsan-cflags-$(CONFIG_UBSAN_UNREACHABLE) += -fsanitize=unreachable ubsan-cflags-$(CONFIG_UBSAN_BOOL) += -fsanitize=bool ubsan-cflags-$(CONFIG_UBSAN_ENUM) += -fsanitize=enum -ubsan-cflags-$(CONFIG_UBSAN_TRAP) += $(call cc-option,-fsanitize-trap=undefined,-fsanitize-undefined-trap-on-error) +ubsan-cflags-$(CONFIG_UBSAN_TRAP) += $(CFLAGS_UBSAN_TRAP) export CFLAGS_UBSAN := $(ubsan-cflags-y) -ubsan-signed-wrap-cflags-$(CONFIG_UBSAN_SIGNED_WRAP) += -fsanitize=signed-integer-overflow -export CFLAGS_UBSAN_SIGNED_WRAP := $(ubsan-signed-wrap-cflags-y) +ubsan-integer-wrap-cflags-$(CONFIG_UBSAN_INTEGER_WRAP) += \ + -DINTEGER_WRAP \ + -fsanitize-undefined-ignore-overflow-pattern=all \ + -fsanitize=signed-integer-overflow \ + -fsanitize=unsigned-integer-overflow \ + -fsanitize=implicit-signed-integer-truncation \ + -fsanitize=implicit-unsigned-integer-truncation \ + -fsanitize-ignorelist=$(srctree)/scripts/integer-wrap-ignore.scl +export CFLAGS_UBSAN_INTEGER_WRAP := $(ubsan-integer-wrap-cflags-y) diff --git a/scripts/Makefile.vdsoinst b/scripts/Makefile.vdsoinst index c477d17b0aa5..ac85f9a4a569 100644 --- a/scripts/Makefile.vdsoinst +++ b/scripts/Makefile.vdsoinst @@ -13,16 +13,15 @@ install-dir := $(MODLIB)/vdso define gen_install_rules -src := $$(firstword $$(subst :,$(space),$(1))) -dest := $(install-dir)/$$(or $$(word 2,$$(subst :,$(space),$(1))),$$(patsubst %.dbg,%,$$(notdir $(1)))) +dest := $(install-dir)/$$(patsubst %.dbg,%,$$(notdir $(1))) __default: $$(dest) -$$(dest): $$(src) FORCE +$$(dest): $(1) FORCE $$(call cmd,install) # Some architectures create .build-id symlinks -ifneq ($(filter arm sparc x86, $(SRCARCH)),) -link := $(install-dir)/.build-id/$$(shell $(READELF) -n $$(src) | sed -n 's@^.*Build ID: \(..\)\(.*\)@\1/\2@p').debug +ifneq ($(filter arm s390 sparc x86, $(SRCARCH)),) +link := $(install-dir)/.build-id/$$(shell $(READELF) -n $(1) | sed -n 's@^.*Build ID: \(..\)\(.*\)@\1/\2@p').debug __default: $$(link) $$(link): $$(dest) FORCE diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux index c9f3e03124d7..b64862dc6f08 100644 --- a/scripts/Makefile.vmlinux +++ b/scripts/Makefile.vmlinux @@ -5,38 +5,117 @@ __default: vmlinux include include/config/auto.conf include $(srctree)/scripts/Kbuild.include - -# for c_flags include $(srctree)/scripts/Makefile.lib targets := -quiet_cmd_cc_o_c = CC $@ - cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< +ifdef CONFIG_ARCH_VMLINUX_NEEDS_RELOCS +vmlinux-final := vmlinux.unstripped + +quiet_cmd_strip_relocs = RSTRIP $@ + cmd_strip_relocs = $(OBJCOPY) --remove-section='.rel*' --remove-section=!'.rel*.dyn' $< $@ + +vmlinux: $(vmlinux-final) FORCE + $(call if_changed,strip_relocs) + +targets += vmlinux +else +vmlinux-final := vmlinux +endif %.o: %.c FORCE - $(call if_changed_dep,cc_o_c) + $(call if_changed_rule,cc_o_c) + +%.o: %.S FORCE + $(call if_changed_rule,as_o_S) + +# Built-in dtb +# --------------------------------------------------------------------------- + +quiet_cmd_wrap_dtbs = WRAP $@ + cmd_wrap_dtbs = { \ + echo '\#include <asm-generic/vmlinux.lds.h>'; \ + echo '.section .dtb.init.rodata,"a"'; \ + while read dtb; do \ + symbase=__dtb_$$(basename -s .dtb "$${dtb}" | tr - _); \ + echo '.balign STRUCT_ALIGNMENT'; \ + echo ".global $${symbase}_begin"; \ + echo "$${symbase}_begin:"; \ + echo '.incbin "'$$dtb'" '; \ + echo ".global $${symbase}_end"; \ + echo "$${symbase}_end:"; \ + done < $<; \ + } > $@ + +.builtin-dtbs.S: .builtin-dtbs-list FORCE + $(call if_changed,wrap_dtbs) + +quiet_cmd_gen_dtbs_list = GEN $@ + cmd_gen_dtbs_list = \ + $(if $(CONFIG_BUILTIN_DTB_NAME), echo "arch/$(SRCARCH)/boot/dts/$(CONFIG_BUILTIN_DTB_NAME).dtb",:) > $@ + +.builtin-dtbs-list: arch/$(SRCARCH)/boot/dts/dtbs-list FORCE + $(call if_changed,$(if $(CONFIG_BUILTIN_DTB_ALL),copy,gen_dtbs_list)) + +targets += .builtin-dtbs-list + +ifdef CONFIG_GENERIC_BUILTIN_DTB +targets += .builtin-dtbs.S .builtin-dtbs.o +$(vmlinux-final): .builtin-dtbs.o +endif + +# vmlinux +# --------------------------------------------------------------------------- ifdef CONFIG_MODULES -KASAN_SANITIZE_.vmlinux.export.o := n -KCSAN_SANITIZE_.vmlinux.export.o := n -GCOV_PROFILE_.vmlinux.export.o := n targets += .vmlinux.export.o -vmlinux: .vmlinux.export.o +$(vmlinux-final): .vmlinux.export.o +endif + +ifdef CONFIG_ARCH_WANTS_PRE_LINK_VMLINUX +$(vmlinux-final): arch/$(SRCARCH)/tools/vmlinux.arch.o + +arch/$(SRCARCH)/tools/vmlinux.arch.o: vmlinux.o FORCE + $(Q)$(MAKE) $(build)=arch/$(SRCARCH)/tools $@ endif ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink) # Final link of vmlinux with optional arch pass after final link cmd_link_vmlinux = \ - $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; \ + $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)" "$@"; \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) -targets += vmlinux -vmlinux: scripts/link-vmlinux.sh vmlinux.o $(KBUILD_LDS) FORCE +targets += $(vmlinux-final) +$(vmlinux-final): scripts/link-vmlinux.sh vmlinux.o $(KBUILD_LDS) FORCE +$(call if_changed_dep,link_vmlinux) +ifdef CONFIG_DEBUG_INFO_BTF +$(vmlinux-final): $(RESOLVE_BTFIDS) +endif + +ifdef CONFIG_BUILDTIME_TABLE_SORT +$(vmlinux-final): scripts/sorttable +endif + +# modules.builtin.ranges +# --------------------------------------------------------------------------- +ifdef CONFIG_BUILTIN_MODULE_RANGES +__default: modules.builtin.ranges + +quiet_cmd_modules_builtin_ranges = GEN $@ + cmd_modules_builtin_ranges = gawk -f $(real-prereqs) > $@ + +targets += modules.builtin.ranges +modules.builtin.ranges: $(srctree)/scripts/generate_builtin_ranges.awk \ + modules.builtin vmlinux.map vmlinux.o.map FORCE + $(call if_changed,modules_builtin_ranges) + +vmlinux.map: $(vmlinux-final) + @: + +endif -# Add FORCE to the prequisites of a target to force it to be always rebuilt. +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. # --------------------------------------------------------------------------- PHONY += FORCE diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o index 6de297916ce6..b024ffb3e201 100644 --- a/scripts/Makefile.vmlinux_o +++ b/scripts/Makefile.vmlinux_o @@ -30,13 +30,20 @@ endif # objtool for vmlinux.o # --------------------------------------------------------------------------- # -# For LTO and IBT, objtool doesn't run on individual translation units. -# Run everything on vmlinux instead. +# For delay-objtool (IBT or LTO), objtool doesn't run on individual translation +# units. Instead it runs on vmlinux.o. +# +# For !delay-objtool + CONFIG_NOINSTR_VALIDATION, it runs on both translation +# units and vmlinux.o, with the latter only used for noinstr/unret validation. objtool-enabled := $(or $(delay-objtool),$(CONFIG_NOINSTR_VALIDATION)) -vmlinux-objtool-args-$(delay-objtool) += $(objtool-args-y) -vmlinux-objtool-args-$(CONFIG_GCOV_KERNEL) += --no-unreachable +ifeq ($(delay-objtool),y) +vmlinux-objtool-args-y += $(objtool-args-y) +else +vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --Werror +endif + vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \ $(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret) @@ -45,9 +52,12 @@ objtool-args = $(vmlinux-objtool-args-y) --link # Link of vmlinux.o used for section mismatch analysis # --------------------------------------------------------------------------- +vmlinux-o-ld-args-$(CONFIG_BUILTIN_MODULE_RANGES) += -Map=$@.map + quiet_cmd_ld_vmlinux.o = LD $@ cmd_ld_vmlinux.o = \ $(LD) ${KBUILD_LDFLAGS} -r -o $@ \ + $(vmlinux-o-ld-args-y) \ $(addprefix -T , $(initcalls-lds)) \ --whole-archive vmlinux.a --no-whole-archive \ --start-group $(KBUILD_VMLINUX_LIBS) --end-group \ @@ -63,7 +73,7 @@ vmlinux.o: $(initcalls-lds) vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE targets += vmlinux.o -# module.builtin.modinfo +# modules.builtin.modinfo # --------------------------------------------------------------------------- OBJCOPYFLAGS_modules.builtin.modinfo := -j .modinfo -O binary @@ -72,7 +82,7 @@ targets += modules.builtin.modinfo modules.builtin.modinfo: vmlinux.o FORCE $(call if_changed,objcopy) -# module.builtin +# modules.builtin # --------------------------------------------------------------------------- # The second line aids cases where multiple modules share the same object. @@ -87,7 +97,7 @@ targets += modules.builtin modules.builtin: modules.builtin.modinfo FORCE $(call if_changed,modules_builtin) -# Add FORCE to the prequisites of a target to force it to be always rebuilt. +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. # --------------------------------------------------------------------------- PHONY += FORCE diff --git a/scripts/atomic/kerneldoc/sub_and_test b/scripts/atomic/kerneldoc/sub_and_test index d3760f7749d4..96615e50836b 100644 --- a/scripts/atomic/kerneldoc/sub_and_test +++ b/scripts/atomic/kerneldoc/sub_and_test @@ -1,7 +1,7 @@ cat <<EOF /** * ${class}${atomicname}() - atomic subtract and test if zero with ${desc_order} ordering - * @i: ${int} value to add + * @i: ${int} value to subtract * @v: pointer to ${atomic}_t * * Atomically updates @v to (@v - @i) with ${desc_order} ordering. diff --git a/scripts/bash-completion/make b/scripts/bash-completion/make new file mode 100644 index 000000000000..42e8dcead25a --- /dev/null +++ b/scripts/bash-completion/make @@ -0,0 +1,451 @@ +# SPDX-License-Identifier: GPL-2.0-only +# bash completion for GNU make with kbuild extension -*- shell-script -*- + +# Load the default completion script for make. It is typically located at +# /usr/share/bash-completion/completions/make, but we do not rely on it. +__kbuild_load_default_make_completion() +{ + local -a dirs=("${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions") + local ifs=$IFS IFS=: dir compfile this_dir + + for dir in ${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do + dirs+=("$dir"/bash-completion/completions) + done + IFS=$ifs + + this_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + + for dir in "${dirs[@]}"; do + if [[ ! -d ${dir} || ${dir} = "${this_dir}" ]]; then + continue + fi + + for compfile in make make.bash _make; do + compfile=$dir/$compfile + # Avoid trying to source dirs; https://bugzilla.redhat.com/903540 + if [[ -f ${compfile} ]] && . "${compfile}" &>/dev/null; then + + __kbuild_default_make_completion=$( + # shellcheck disable=SC2046 # word splitting is the point here + set -- $(complete -p make) + + while [[ $# -gt 1 && "$1" != -F ]]; do + shift + done + + if [[ "$1" = -F ]]; then + echo "$2" + fi + ) + + return + fi + done + done +} + +__kbuild_load_default_make_completion + +__kbuild_handle_variable() +{ + local var=${1%%=*} + local cur=${cur#"${var}"=} + local srctree=$2 + local keywords=() + + case $var in + ARCH) + # sub-directories under arch/ + keywords+=($(find "${srctree}/arch" -mindepth 1 -maxdepth 1 -type d -printf '%P\n')) + # architectures hard-coded in the top Makefile + keywords+=(i386 x86_64 sparc32 sparc64 parisc64) + ;; + CROSS_COMPILE) + # toolchains with a full path + local cross_compile=() + local c c2 + _filedir + + for c in "${COMPREPLY[@]}"; do + # eval for tilde expansion + # suppress error, as this fails when it contains a space + eval "c2=${c}" 2>/dev/null || continue + if [[ ${c} == *-elfedit && ! -d ${c2} && -x ${c2} ]]; then + cross_compile+=("${c%elfedit}") + fi + done + + # toolchains in the PATH environment + while read -r c; do + if [[ ${c} == *-elfedit ]]; then + keywords+=("${c%elfedit}") + fi + done < <(compgen -c) + + COMPREPLY=() + _filedir -d + + # Add cross_compile directly without passing it to compgen. + # Otherwise, toolchain paths with a tilde do not work. + # e.g.) + # CROSS_COMPILE=~/0day/gcc-14.2.0-nolibc/aarch64-linux/bin/aarch64-linux- + COMPREPLY+=("${cross_compile[@]}") + ;; + LLVM) + # LLVM=1 uses the default 'clang' etc. + keywords+=(1) + + # suffix for a particular version. LLVM=-18 uses 'clang-18' etc. + while read -r c; do + if [[ ${c} == clang-[0-9]* ]]; then + keywords+=("${c#clang}") + fi + done < <(compgen -c) + + # directory path to LLVM toolchains + _filedir -d + ;; + KCONFIG_ALLCONFIG) + # KCONFIG_ALLCONFIG=1 selects the default fragment + keywords+=(1) + # or the path to a fragment file + _filedir + ;; + C | KBUILD_CHECKSRC) + keywords+=(1 2) + ;; + V | KBUILD_VERBOSE) + keywords+=({,1}{,2}) + ;; + W | KBUILD_EXTRA_WARN) + keywords+=({,1}{,2}{,3}{,c}{,e}) + ;; + KBUILD_ABS_SRCTREE | KBUILD_MODPOST_NOFINAL | KBUILD_MODPOST_WARN | \ + CLIPPY | KBUILD_CLIPPY | KCONFIG_NOSILENTUPDATE | \ + KCONFIG_OVERWRITECONFIG | KCONFIG_WARN_UNKNOWN_SYMBOL | \ + KCONFIG_WERROR ) + keywords+=(1) + ;; + INSTALL_MOD_STRIP) + keywords+=(1 --strip-debug --strip-unneeded) + ;; + O | KBUILD_OUTPUT | M | KBUILD_EXTMOD | MO | KBUILD_EXTMOD_OUTPUT | *_PATH) + # variables that take a directory. + _filedir -d + return + ;; + KBUILD_EXTRA_SYMBOL | KBUILD_KCONFIG | KCONFIG_CONFIG) + # variables that take a file. + _filedir + return + esac + + COMPREPLY+=($(compgen -W "${keywords[*]}" -- "${cur}")) +} + +# Check the -C, -f options and 'source' symlink. Return the source tree we are +# working in. +__kbuild_get_srctree() +{ + local words=("$@") + local cwd makef_dir + + # see if a path was specified with -C/--directory + for ((i = 1; i < ${#words[@]}; i++)); do + if [[ ${words[i]} == -@(C|-directory) ]]; then + # eval for tilde expansion. + # suppress error, as this fails when it contains a space + eval "cwd=${words[i + 1]}" 2>/dev/null + break + fi + done + + if [[ -z ${cwd} ]]; then + cwd=. + fi + + # see if a Makefile was specified with -f/--file/--makefile + for ((i = 1; i < ${#words[@]}; i++)); do + if [[ ${words[i]} == -@(f|-?(make)file) ]]; then + # eval for tilde expansion + # suppress error, as this fails when it contains a space + eval "makef_dir=${words[i + 1]%/*}" 2>/dev/null + break + fi + done + + if [ -z "${makef_dir}" ]; then + makef_dir=${cwd} + elif [[ ${makef_dir} != /* ]]; then + makef_dir=${cwd}/${makef_dir} + fi + + # If ${makef_dir} is a build directory created by the O= option, there + # is a symbolic link 'source', which points to the kernel source tree. + if [[ -L ${makef_dir}/source ]]; then + makef_dir=$(readlink "${makef_dir}/source") + fi + + echo "${makef_dir}" +} + +# Get SRCARCH to do a little more clever things +__kbuild_get_srcarch() +{ + local words=("$@") + local arch srcarch uname_m + + # see if ARCH= is explicitly specified + for ((i = 1; i < ${#words[@]}; i++)); do + if [[ ${words[i]} == ARCH=* ]]; then + arch=${words[i]#ARCH=} + break + fi + done + + # If ARCH= is not specified, check the build marchine's architecture + if [[ -z ${arch} ]]; then + uname_m=$(uname -m) + + # shellcheck disable=SC2209 # 'sh' is SuperH, not a shell command + case ${uname_m} in + arm64 | aarch64*) arch=arm64 ;; + arm* | sa110) arch=arm ;; + i?86 | x86_64) arch=x86 ;; + loongarch*) arch=loongarch ;; + mips*) arch=mips ;; + ppc*) arch=powerpc ;; + riscv*) arch=riscv ;; + s390x) arch=s390 ;; + sh[234]*) arch=sh ;; + sun4u) arch=sparc64 ;; + *) arch=${uname_m} ;; + esac + fi + + case ${arch} in + parisc64) srcarch=parisc ;; + sparc32 | sparc64) srcarch=sparc ;; + i386 | x86_64) srcarch=x86 ;; + *) srcarch=${arch} ;; + esac + + echo "$srcarch" +} + +# small Makefile to parse obj-* syntax +__kbuild_tmp_makefile() +{ +cat <<'EOF' +.PHONY: __default +__default: + $(foreach m,$(obj-y) $(obj-m) $(obj-),$(foreach s, -objs -y -m -,$($(m:%.o=%$s))) $(m)) +EOF +echo "include ${1}" +} + +_make_for_kbuild () +{ + # shellcheck disable=SC2034 # these are set by _init_completion + local cur prev words cword split + _init_completion -s || return + + local srctree + srctree=$(__kbuild_get_srctree "${words[@]}") + + # If 'kernel' and 'Documentation' directories are found, we assume this + # is a kernel tree. Otherwise, we fall back to the generic rule provided + # by the bash-completion project. + if [[ ! -d ${srctree}/kernel || ! -d ${srctree}/Documentation ]]; then + if [ -n "${__kbuild_default_make_completion}" ]; then + "${__kbuild_default_make_completion}" "$@" + fi + return + fi + + # make options with a parameter (copied from the bash-completion project) + case ${prev} in + --file | --makefile | --old-file | --assume-old | --what-if | --new-file | \ + --assume-new | -!(-*)[foW]) + _filedir + return + ;; + --include-dir | --directory | -!(-*)[ICm]) + _filedir -d + return + ;; + -!(-*)E) + COMPREPLY=($(compgen -v -- "$cur")) + return + ;; + --eval | -!(-*)[DVx]) + return + ;; + --jobs | -!(-*)j) + COMPREPLY=($(compgen -W "{1..$(($(_ncpus) * 2))}" -- "$cur")) + return + ;; + esac + + local keywords=() + + case ${cur} in + -*) + # make options (copied from the bash-completion project) + local opts + opts="$(_parse_help "$1")" + COMPREPLY=($(compgen -W "${opts:-$(_parse_usage "$1")}" -- "$cur")) + if [[ ${COMPREPLY-} == *= ]]; then + compopt -o nospace + fi + return + ;; + *=*) + __kbuild_handle_variable "${cur}" "${srctree}" + return + ;; + KBUILD_*) + # There are many variables prefixed with 'KBUILD_'. + # Display them only when 'KBUILD_' is entered. + # shellcheck disable=SC2191 # '=' is appended for variables + keywords+=( + KBUILD_{CHECKSRC,EXTMOD,EXTMOD_OUTPUT,OUTPUT,VERBOSE,EXTRA_WARN,CLIPPY}= + KBUILD_BUILD_{USER,HOST,TIMESTAMP}= + KBUILD_MODPOST_{NOFINAL,WARN}= + KBUILD_{ABS_SRCTREE,EXTRA_SYMBOLS,KCONFIG}= + ) + ;; + KCONFIG_*) + # There are many variables prefixed with 'KCONFIG_'. + # Display them only when 'KCONFIG_' is entered. + # shellcheck disable=SC2191 # '=' is appended for variables + keywords+=( + KCONFIG_{CONFIG,ALLCONFIG,NOSILENTUPDATE,OVERWRITECONFIG}= + KCONFIG_{SEED,PROBABILITY}= + KCONFIG_WARN_UNKNOWN_SYMBOL= + KCONFIG_WERROR= + ) + ;; + *) + # By default, hide KBUILD_* and KCONFIG_* variables. + # Instead, display only the prefix parts. + keywords+=(KBUILD_ KCONFIG_) + ;; + esac + + if [[ ${cur} != /* && ${cur} != *//* ]]; then + local dir srcarch kbuild_file tmp + srcarch=$(__kbuild_get_srcarch "${words[@]}") + + # single build + dir=${cur} + while true; do + if [[ ${dir} == */* ]]; then + dir=${dir%/*} + else + dir=. + fi + + # Search for 'Kbuild' or 'Makefile' in the parent + # directories (may not be a direct parent) + if [[ -f ${srctree}/${dir}/Kbuild ]]; then + kbuild_file=${srctree}/${dir}/Kbuild + break + fi + if [[ -f ${srctree}/${dir}/Makefile ]]; then + kbuild_file=${srctree}/${dir}/Makefile + break + fi + + if [[ ${dir} == . ]]; then + break + fi + done + + if [[ -n ${kbuild_file} ]]; then + tmp=($(__kbuild_tmp_makefile "${kbuild_file}" | + SRCARCH=${srcarch} obj=${dir} src=${srctree}/${dir} \ + "${1}" -n -f - 2>/dev/null)) + + # Add $(obj)/ prefix + if [[ ${dir} != . ]]; then + tmp=("${tmp[@]/#/${dir}\/}") + fi + + keywords+=("${tmp[@]}") + fi + + # *_defconfig and *.config files. These might be grouped into + # subdirectories, e.g., arch/powerpc/configs/*/*_defconfig. + if [[ ${cur} == */* ]]; then + dir=${cur%/*} + else + dir=. + fi + + tmp=($(find "${srctree}/arch/${srcarch}/configs/${dir}" \ + "${srctree}/kernel/configs/${dir}" \ + -mindepth 1 -maxdepth 1 -type d -printf '%P/\n' \ + -o -printf '%P\n' 2>/dev/null)) + + if [[ ${dir} != . ]]; then + tmp=("${tmp[@]/#/${dir}\/}") + fi + + keywords+=("${tmp[@]}") + fi + + # shellcheck disable=SC2191 # '=' is appended for variables + keywords+=( + # + # variables (append =) + # + ARCH= + CROSS_COMPILE= + LLVM= + C= M= MO= O= V= W= + INSTALL{,_MOD,_HDR,_DTBS}_PATH= + KERNELRELEASE= + + # + # targets + # + all help + clean mrproper distclean + clang-{tidy,analyzer} compile_commands.json + coccicheck + dtbs{,_check,_install} dt_binding_{check,schemas} + headers{,_install} + vmlinux install + modules{,_prepare,_install,_sign} + vdso_install + tags TAGS cscope gtags + rust{available,fmt,fmtcheck} + kernel{version,release} image_name + kselftest{,-all,-install,-clean,-merge} + + # configuration + {,old,olddef,sync,def,savedef,rand,listnew,helpnew,test,tiny}config + {,build_}{menu,n,g,x}config + local{mod,yes}config + all{no,yes,mod,def}config + {yes2mod,mod2yes,mod2no}config + + # docs + {html,textinfo,info,latex,pdf,epub,xml,linkcheck,refcheck,clean}docs + + # package + {,bin,src}{rpm,deb}-pkg + {pacman,dir,tar}-pkg + tar{,gz,bz2,xz,zst}-pkg + perf-tar{,gz,bz2,xz,zst}-src-pkg + ) + + COMPREPLY=($(compgen -W "${keywords[*]}" -- "${cur}")) + + # Do not append a space for variables, subdirs, "KBUILD_", "KCONFIG_". + if [[ ${COMPREPLY-} == *[=/] || ${COMPREPLY-} =~ ^(KBUILD|KCONFIG)_$ ]]; then + compopt -o nospace + fi + +} && complete -F _make_for_kbuild make diff --git a/scripts/basic/Makefile b/scripts/basic/Makefile index dd289a6725ac..fb8e2c38fbc7 100644 --- a/scripts/basic/Makefile +++ b/scripts/basic/Makefile @@ -14,3 +14,8 @@ cmd_create_randstruct_seed = \ $(obj)/randstruct.seed: $(gen-randstruct-seed) FORCE $(call if_changed,create_randstruct_seed) always-$(CONFIG_RANDSTRUCT) += randstruct.seed + +# integer-wrap: if the .scl file changes, we need to do a full rebuild. +$(obj)/../../include/generated/integer-wrap.h: $(srctree)/scripts/integer-wrap-ignore.scl FORCE + $(call if_changed,touch) +always-$(CONFIG_UBSAN_INTEGER_WRAP) += ../../include/generated/integer-wrap.h diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c index 84b6efa849f4..cdd5da7e009b 100644 --- a/scripts/basic/fixdep.c +++ b/scripts/basic/fixdep.c @@ -99,6 +99,8 @@ #include <stdio.h> #include <ctype.h> +#include <xalloc.h> + static void usage(void) { fprintf(stderr, "Usage: fixdep <depfile> <target> <cmdline>\n"); @@ -131,12 +133,9 @@ static unsigned int strhash(const char *str, unsigned int sz) static void add_to_hashtable(const char *name, int len, unsigned int hash, struct item *hashtab[]) { - struct item *aux = malloc(sizeof(*aux) + len); + struct item *aux; - if (!aux) { - perror("fixdep:malloc"); - exit(1); - } + aux = xmalloc(sizeof(*aux) + len); memcpy(aux->name, name, len); aux->len = len; aux->hash = hash; @@ -228,11 +227,7 @@ static void *read_file(const char *filename) perror(filename); exit(2); } - buf = malloc(st.st_size + 1); - if (!buf) { - perror("fixdep: malloc"); - exit(2); - } + buf = xmalloc(st.st_size + 1); if (read(fd, buf, st.st_size) != st.st_size) { perror("fixdep: read"); exit(2); diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py index 4606944984ee..c77dc40f7689 100755 --- a/scripts/bpf_doc.py +++ b/scripts/bpf_doc.py @@ -8,6 +8,7 @@ from __future__ import print_function import argparse +import json import re import sys, os import subprocess @@ -42,6 +43,13 @@ class APIElement(object): self.desc = desc self.ret = ret + def to_dict(self): + return { + 'proto': self.proto, + 'desc': self.desc, + 'ret': self.ret + } + class Helper(APIElement): """ @@ -50,8 +58,9 @@ class Helper(APIElement): @desc: textual description of the helper function @ret: description of the return value of the helper function """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, proto='', desc='', ret='', attrs=[]): + super().__init__(proto, desc, ret) + self.attrs = attrs self.enum_val = None def proto_break_down(self): @@ -80,6 +89,17 @@ class Helper(APIElement): return res + def to_dict(self): + d = super().to_dict() + d["attrs"] = self.attrs + d.update(self.proto_break_down()) + return d + + +ATTRS = { + '__bpf_fastcall': 'bpf_fastcall' +} + class HeaderParser(object): """ @@ -111,7 +131,8 @@ class HeaderParser(object): proto = self.parse_proto() desc = self.parse_desc(proto) ret = self.parse_ret(proto) - return Helper(proto=proto, desc=desc, ret=ret) + attrs = self.parse_attrs(proto) + return Helper(proto=proto, desc=desc, ret=ret, attrs=attrs) def parse_symbol(self): p = re.compile(r' \* ?(BPF\w+)$') @@ -192,6 +213,28 @@ class HeaderParser(object): raise Exception("No return found for " + proto) return ret + def parse_attrs(self, proto): + p = re.compile(r' \* ?(?:\t| {5,8})Attributes$') + capture = p.match(self.line) + if not capture: + return [] + # Expect a single line with mnemonics for attributes separated by spaces + self.line = self.reader.readline() + p = re.compile(r' \* ?(?:\t| {5,8})(?:\t| {8})(.*)') + capture = p.match(self.line) + if not capture: + raise Exception("Incomplete 'Attributes' section for " + proto) + attrs = capture.group(1).split(' ') + for attr in attrs: + if attr not in ATTRS: + raise Exception("Unexpected attribute '" + attr + "' specified for " + proto) + self.line = self.reader.readline() + if self.line != ' *\n': + raise Exception("Expecting empty line after 'Attributes' section for " + proto) + # Prepare a line for next self.parse_* to consume + self.line = self.reader.readline() + return attrs + def seek_to(self, target, help_message, discard_lines = 1): self.reader.seek(0) offset = self.reader.read().find(target) @@ -414,8 +457,8 @@ class PrinterRST(Printer): version = version.stdout.decode().rstrip() except: try: - version = subprocess.run(['make', 'kernelversion'], cwd=linuxRoot, - capture_output=True, check=True) + version = subprocess.run(['make', '-s', '--no-print-directory', 'kernelversion'], + cwd=linuxRoot, capture_output=True, check=True) version = version.stdout.decode().rstrip() except: return 'Linux' @@ -646,7 +689,7 @@ COMMANDS self.print_elem(command) -class PrinterHelpers(Printer): +class PrinterHelpersHeader(Printer): """ A printer for dumping collected information about helpers as C header to be included from BPF program. @@ -789,6 +832,21 @@ class PrinterHelpers(Printer): print('%s;' % fwd) print('') + used_attrs = set() + for helper in self.elements: + for attr in helper.attrs: + used_attrs.add(attr) + for attr in sorted(used_attrs): + print('#ifndef %s' % attr) + print('#if __has_attribute(%s)' % ATTRS[attr]) + print('#define %s __attribute__((%s))' % (attr, ATTRS[attr])) + print('#else') + print('#define %s' % attr) + print('#endif') + print('#endif') + if used_attrs: + print('') + def print_footer(self): footer = '' print(footer) @@ -827,7 +885,10 @@ class PrinterHelpers(Printer): print(' *{}{}'.format(' \t' if line else '', line)) print(' */') - print('static %s %s(* const %s)(' % (self.map_type(proto['ret_type']), + print('static ', end='') + if helper.attrs: + print('%s ' % (" ".join(helper.attrs)), end='') + print('%s %s(* const %s)(' % (self.map_type(proto['ret_type']), proto['ret_star'], proto['name']), end='') comma = '' for i, a in enumerate(proto['args']): @@ -849,6 +910,43 @@ class PrinterHelpers(Printer): print(') = (void *) %d;' % helper.enum_val) print('') + +class PrinterHelpersJSON(Printer): + """ + A printer for dumping collected information about helpers as a JSON file. + @parser: A HeaderParser with Helper objects + """ + + def __init__(self, parser): + self.elements = parser.helpers + self.elem_number_check( + parser.desc_unique_helpers, + parser.define_unique_helpers, + "helper", + "___BPF_FUNC_MAPPER", + ) + + def print_all(self): + helper_dicts = [helper.to_dict() for helper in self.elements] + out_dict = {'helpers': helper_dicts} + print(json.dumps(out_dict, indent=4)) + + +class PrinterSyscallJSON(Printer): + """ + A printer for dumping collected syscall information as a JSON file. + @parser: A HeaderParser with APIElement objects + """ + + def __init__(self, parser): + self.elements = parser.commands + self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd') + + def print_all(self): + syscall_dicts = [syscall.to_dict() for syscall in self.elements] + out_dict = {'syscall': syscall_dicts} + print(json.dumps(out_dict, indent=4)) + ############################################################################### # If script is launched from scripts/ from kernel tree and can access @@ -858,9 +956,17 @@ script = os.path.abspath(sys.argv[0]) linuxRoot = os.path.dirname(os.path.dirname(script)) bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h') +# target -> output format -> printer printers = { - 'helpers': PrinterHelpersRST, - 'syscall': PrinterSyscallRST, + 'helpers': { + 'rst': PrinterHelpersRST, + 'json': PrinterHelpersJSON, + 'header': PrinterHelpersHeader, + }, + 'syscall': { + 'rst': PrinterSyscallRST, + 'json': PrinterSyscallJSON + }, } argParser = argparse.ArgumentParser(description=""" @@ -870,6 +976,8 @@ rst2man utility. """) argParser.add_argument('--header', action='store_true', help='generate C header file') +argParser.add_argument('--json', action='store_true', + help='generate a JSON') if (os.path.isfile(bpfh)): argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h', default=bpfh) @@ -877,17 +985,35 @@ else: argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h') argParser.add_argument('target', nargs='?', default='helpers', choices=printers.keys(), help='eBPF API target') -args = argParser.parse_args() -# Parse file. -headerParser = HeaderParser(args.filename) -headerParser.run() - -# Print formatted output to standard output. -if args.header: - if args.target != 'helpers': - raise NotImplementedError('Only helpers header generation is supported') - printer = PrinterHelpers(headerParser) -else: - printer = printers[args.target](headerParser) -printer.print_all() +def error_die(message: str): + argParser.print_usage(file=sys.stderr) + print('Error: {}'.format(message), file=sys.stderr) + exit(1) + +def parse_and_dump(): + args = argParser.parse_args() + + # Parse file. + headerParser = HeaderParser(args.filename) + headerParser.run() + + if args.header and args.json: + error_die('Use either --header or --json, not both') + + output_format = 'rst' + if args.header: + output_format = 'header' + elif args.json: + output_format = 'json' + + try: + printer = printers[args.target][output_format](headerParser) + # Print formatted output to standard output. + printer.print_all() + except KeyError: + error_die('Unsupported target/format combination: "{}", "{}"' + .format(args.target, output_format)) + +if __name__ == "__main__": + parse_and_dump() diff --git a/scripts/build-version b/scripts/build-version new file mode 100755 index 000000000000..537d45815083 --- /dev/null +++ b/scripts/build-version @@ -0,0 +1,10 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +prev_ver=$(cat .version 2>/dev/null) && +ver=$(expr ${prev_ver} + 1 2>/dev/null) || +ver=1 + +echo ${ver} > .version + +echo ${ver} diff --git a/scripts/check-variable-fonts.sh b/scripts/check-variable-fonts.sh new file mode 100755 index 000000000000..ce63f0acea5f --- /dev/null +++ b/scripts/check-variable-fonts.sh @@ -0,0 +1,115 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) Akira Yokosawa, 2024 +# +# For "make pdfdocs", reports of build errors of translations.pdf started +# arriving early 2024 [1, 2]. It turned out that Fedora and openSUSE +# tumbleweed have started deploying variable-font [3] format of "Noto CJK" +# fonts [4, 5]. For PDF, a LaTeX package named xeCJK is used for CJK +# (Chinese, Japanese, Korean) pages. xeCJK requires XeLaTeX/XeTeX, which +# does not (and likely never will) understand variable fonts for historical +# reasons. +# +# The build error happens even when both of variable- and non-variable-format +# fonts are found on the build system. To make matters worse, Fedora enlists +# variable "Noto CJK" fonts in the requirements of langpacks-ja, -ko, -zh_CN, +# -zh_TW, etc. Hence developers who have interest in CJK pages are more +# likely to encounter the build errors. +# +# This script is invoked from the error path of "make pdfdocs" and emits +# suggestions if variable-font files of "Noto CJK" fonts are in the list of +# fonts accessible from XeTeX. +# +# References: +# [1]: https://lore.kernel.org/r/8734tqsrt7.fsf@meer.lwn.net/ +# [2]: https://lore.kernel.org/r/1708585803.600323099@f111.i.mail.ru/ +# [3]: https://en.wikipedia.org/wiki/Variable_font +# [4]: https://fedoraproject.org/wiki/Changes/Noto_CJK_Variable_Fonts +# [5]: https://build.opensuse.org/request/show/1157217 +# +#=========================================================================== +# Workarounds for building translations.pdf +#=========================================================================== +# +# * Denylist "variable font" Noto CJK fonts. +# - Create $HOME/deny-vf/fontconfig/fonts.conf from template below, with +# tweaks if necessary. Remove leading "# ". +# - Path of fontconfig/fonts.conf can be overridden by setting an env +# variable FONTS_CONF_DENY_VF. +# +# * Template: +# ----------------------------------------------------------------- +# <?xml version="1.0"?> +# <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"> +# <fontconfig> +# <!-- +# Ignore variable-font glob (not to break xetex) +# --> +# <selectfont> +# <rejectfont> +# <!-- +# for Fedora +# --> +# <glob>/usr/share/fonts/google-noto-*-cjk-vf-fonts</glob> +# <!-- +# for openSUSE tumbleweed +# --> +# <glob>/usr/share/fonts/truetype/Noto*CJK*-VF.otf</glob> +# </rejectfont> +# </selectfont> +# </fontconfig> +# ----------------------------------------------------------------- +# +# The denylisting is activated for "make pdfdocs". +# +# * For skipping CJK pages in PDF +# - Uninstall texlive-xecjk. +# Denylisting is not needed in this case. +# +# * For printing CJK pages in PDF +# - Need non-variable "Noto CJK" fonts. +# * Fedora +# - google-noto-sans-cjk-fonts +# - google-noto-serif-cjk-fonts +# * openSUSE tumbleweed +# - Non-variable "Noto CJK" fonts are not available as distro packages +# as of April, 2024. Fetch a set of font files from upstream Noto +# CJK Font released at: +# https://github.com/notofonts/noto-cjk/tree/main/Sans#super-otc +# and at: +# https://github.com/notofonts/noto-cjk/tree/main/Serif#super-otc +# , then uncompress and deploy them. +# - Remember to update fontconfig cache by running fc-cache. +# +# !!! Caution !!! +# Uninstalling "variable font" packages can be dangerous. +# They might be depended upon by other packages important for your work. +# Denylisting should be less invasive, as it is effective only while +# XeLaTeX runs in "make pdfdocs". + +# Default per-user fontconfig path (overridden by env variable) +: ${FONTS_CONF_DENY_VF:=$HOME/deny-vf} + +export XDG_CONFIG_HOME=${FONTS_CONF_DENY_VF} + +notocjkvffonts=`fc-list : file family variable | \ + grep 'variable=True' | \ + grep -E -e 'Noto (Sans|Sans Mono|Serif) CJK' | \ + sed -e 's/^/ /' -e 's/: Noto S.*$//' | sort | uniq` + +if [ "x$notocjkvffonts" != "x" ] ; then + echo '=============================================================================' + echo 'XeTeX is confused by "variable font" files listed below:' + echo "$notocjkvffonts" + echo + echo 'For CJK pages in PDF, they need to be hidden from XeTeX by denylisting.' + echo 'Or, CJK pages can be skipped by uninstalling texlive-xecjk.' + echo + echo 'For more info on denylisting, other options, and variable font, see header' + echo 'comments of scripts/check-variable-fonts.sh.' + echo '=============================================================================' +fi + +# As this script is invoked from Makefile's error path, always error exit +# regardless of whether any variable font is discovered or not. +exit 1 diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 9c4c4a61bc83..664f7b7a622c 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -28,6 +28,7 @@ my %verbose_messages = (); my %verbose_emitted = (); my $tree = 1; my $chk_signoff = 1; +my $chk_fixes_tag = 1; my $chk_patch = 1; my $tst_only; my $emacs = 0; @@ -88,6 +89,7 @@ Options: -v, --verbose verbose mode --no-tree run without a kernel tree --no-signoff do not check for 'Signed-off-by' line + --no-fixes-tag do not check for 'Fixes:' tag --patch treat FILE as patchfile (default) --emacs emacs compile window format --terse one line per report @@ -111,7 +113,8 @@ Options: --max-line-length=n set the maximum line length, (default $max_line_length) if exceeded, warn on patches requires --strict for use with --file - --min-conf-desc-length=n set the min description length, if shorter, warn + --min-conf-desc-length=n set the minimum description length for config symbols + in lines, if shorter, warn (default $min_conf_desc_length) --tab-size=n set the number of spaces for tab (default $tabsize) --root=PATH PATH to the kernel tree root --no-summary suppress the per-file summary @@ -148,6 +151,24 @@ EOM exit($exitcode); } +my $DO_WHILE_0_ADVICE = q{ + do {} while (0) advice is over-stated in a few situations: + + The more obvious case is macros, like MODULE_PARM_DESC, invoked at + file-scope, where C disallows code (it must be in functions). See + $exceptions if you have one to add by name. + + More troublesome is declarative macros used at top of new scope, + like DECLARE_PER_CPU. These might just compile with a do-while-0 + wrapper, but would be incorrect. Most of these are handled by + detecting struct,union,etc declaration primitives in $exceptions. + + Theres also macros called inside an if (block), which "return" an + expression. These cannot do-while, and need a ({}) wrapper. + + Enjoy this qualification while we work to improve our heuristics. +}; + sub uniq { my %seen; return grep { !$seen{$_}++ } @_; @@ -295,6 +316,7 @@ GetOptions( 'v|verbose!' => \$verbose, 'tree!' => \$tree, 'signoff!' => \$chk_signoff, + 'fixes-tag!' => \$chk_fixes_tag, 'patch!' => \$chk_patch, 'emacs!' => \$emacs, 'terse!' => \$terse, @@ -831,20 +853,12 @@ foreach my $entry (@mode_permission_funcs) { $mode_perms_search = "(?:${mode_perms_search})"; our %deprecated_apis = ( - "synchronize_rcu_bh" => "synchronize_rcu", - "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", - "call_rcu_bh" => "call_rcu", - "rcu_barrier_bh" => "rcu_barrier", - "synchronize_sched" => "synchronize_rcu", - "synchronize_sched_expedited" => "synchronize_rcu_expedited", - "call_rcu_sched" => "call_rcu", - "rcu_barrier_sched" => "rcu_barrier", - "get_state_synchronize_sched" => "get_state_synchronize_rcu", - "cond_synchronize_sched" => "cond_synchronize_rcu", "kmap" => "kmap_local_page", "kunmap" => "kunmap_local", "kmap_atomic" => "kmap_local_page", "kunmap_atomic" => "kunmap_local", + "srcu_read_lock_lite" => "srcu_read_lock_fast", + "srcu_read_unlock_lite" => "srcu_read_unlock_fast", ); #Create a search pattern for all these strings to speed up a loop below @@ -1257,6 +1271,7 @@ sub git_commit_info { } $chk_signoff = 0 if ($file); +$chk_fixes_tag = 0 if ($file); my @rawlines = (); my @lines = (); @@ -2636,6 +2651,9 @@ sub process { our $clean = 1; my $signoff = 0; + my $fixes_tag = 0; + my $is_revert = 0; + my $needs_fixes_tag = ""; my $author = ''; my $authorsignoff = 0; my $author_sob = ''; @@ -2868,7 +2886,7 @@ sub process { if ($realfile =~ m@^include/asm/@) { ERROR("MODIFIED_INCLUDE_ASM", - "do not modify files in include/asm, change architecture specific files in include/asm-<architecture>\n" . "$here$rawline\n"); + "do not modify files in include/asm, change architecture specific files in arch/<architecture>/include/asm\n" . "$here$rawline\n"); } $found_file = 1; } @@ -3189,47 +3207,53 @@ sub process { } } +# These indicate a bug fix + if (!$in_header_lines && !$is_patch && + $line =~ /^This reverts commit/) { + $is_revert = 1; + } + + if (!$in_header_lines && !$is_patch && + $line =~ /((?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller))/) { + $needs_fixes_tag = $1; + } # Check Fixes: styles is correct if (!$in_header_lines && - $line =~ /^\s*fixes:?\s*(?:commit\s*)?[0-9a-f]{5,}\b/i) { - my $orig_commit = ""; - my $id = "0123456789ab"; - my $title = "commit title"; - my $tag_case = 1; - my $tag_space = 1; - my $id_length = 1; - my $id_case = 1; + $line =~ /^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})(?:\s*($balanced_parens))?/i) { + my $tag = $1; + my $orig_commit = $2; + my $title; my $title_has_quotes = 0; - - if ($line =~ /(\s*fixes:?)\s+([0-9a-f]{5,})\s+($balanced_parens)/i) { - my $tag = $1; - $orig_commit = $2; - $title = $3; - - $tag_case = 0 if $tag eq "Fixes:"; - $tag_space = 0 if ($line =~ /^fixes:? [0-9a-f]{5,} ($balanced_parens)/i); - - $id_length = 0 if ($orig_commit =~ /^[0-9a-f]{12}$/i); - $id_case = 0 if ($orig_commit !~ /[A-F]/); - + $fixes_tag = 1; + if (defined $3) { # Always strip leading/trailing parens then double quotes if existing - $title = substr($title, 1, -1); + $title = substr($3, 1, -1); if ($title =~ /^".*"$/) { $title = substr($title, 1, -1); $title_has_quotes = 1; } + } else { + $title = "commit title" } + + my $tag_case = not ($tag eq "Fixes:"); + my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i); + + my $id_length = not ($orig_commit =~ /^[0-9a-f]{12,40}$/i); + my $id_case = not ($orig_commit !~ /[A-F]/); + + my $id = "0123456789ab"; my ($cid, $ctitle) = git_commit_info($orig_commit, $id, $title); - if ($ctitle ne $title || $tag_case || $tag_space || - $id_length || $id_case || !$title_has_quotes) { + if (defined($cid) && ($ctitle ne $title || $tag_case || $tag_space || $id_length || $id_case || !$title_has_quotes)) { + my $fixed = "Fixes: $cid (\"$ctitle\")"; if (WARN("BAD_FIXES_TAG", - "Please use correct Fixes: style 'Fixes: <12 chars of sha1> (\"<title line>\")' - ie: 'Fixes: $cid (\"$ctitle\")'\n" . $herecurr) && + "Please use correct Fixes: style 'Fixes: <12+ chars of sha1> (\"<title line>\")' - ie: '$fixed'\n" . $herecurr) && $fix) { - $fixed[$fixlinenr] = "Fixes: $cid (\"$ctitle\")"; + $fixed[$fixlinenr] = $fixed; } } } @@ -3642,7 +3666,7 @@ sub process { $help_length < $min_conf_desc_length) { my $stat_real = get_stat_real($linenr, $ln - 1); WARN("CONFIG_DESCRIPTION", - "please write a help paragraph that fully describes the config symbol\n" . "$here\n$stat_real\n"); + "please write a help paragraph that fully describes the config symbol with at least $min_conf_desc_length lines\n" . "$here\n$stat_real\n"); } } @@ -3686,20 +3710,6 @@ sub process { } } - if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && - ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { - my $flag = $1; - my $replacement = { - 'EXTRA_AFLAGS' => 'asflags-y', - 'EXTRA_CFLAGS' => 'ccflags-y', - 'EXTRA_CPPFLAGS' => 'cppflags-y', - 'EXTRA_LDFLAGS' => 'ldflags-y', - }; - - WARN("DEPRECATED_VARIABLE", - "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); - } - # check for DT compatible documentation if (defined $root && (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || @@ -3858,7 +3868,7 @@ sub process { } if ($msg_type ne "" && - (show_type("LONG_LINE") || show_type($msg_type))) { + show_type("LONG_LINE") && show_type($msg_type)) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}($msg_type, @@ -3997,16 +4007,6 @@ sub process { } } -# Block comment styles -# Networking with an initial /* - if ($realfile =~ m@^(drivers/net/|net/)@ && - $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ && - $rawline =~ /^\+[ \t]*\*/ && - $realline > 3) { # Do not warn about the initial copyright comment block after SPDX-License-Identifier - WARN("NETWORKING_BLOCK_COMMENT_STYLE", - "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev); - } - # Block comments use * on subsequent lines if ($prevline =~ /$;[ \t]*$/ && #ends in comment $prevrawline =~ /^\+.*?\/\*/ && #starting /* @@ -4824,7 +4824,7 @@ sub process { } # do not use BUG() or variants - if ($line =~ /\b(?!AA_|BUILD_|DCCP_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) { + if ($line =~ /\b(?!AA_|BUILD_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}("AVOID_BUG", @@ -5510,9 +5510,9 @@ sub process { } } -# check for unnecessary parentheses around comparisons in if uses -# when !drivers/staging or command-line uses --strict - if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && +# check for unnecessary parentheses around comparisons +# except in drivers/staging + if (($realfile !~ m@^(?:drivers/staging/)@) && $perl_version_ok && defined($stat) && $stat =~ /(^.\s*if\s*($balanced_parens))/) { my $if_stat = $1; @@ -5840,6 +5840,8 @@ sub process { #CamelCase if ($var !~ /^$Constant$/ && $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore C keywords + $var !~ /^_Generic$/ && #Ignore some autogenerated defines and enum values $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && #Ignore Page<foo> variants @@ -5901,9 +5903,9 @@ sub process { } } -# multi-statement macros should be enclosed in a do while loop, grab the -# first statement and ensure its the whole macro if its not enclosed -# in a known good container +# Usually multi-statement macros should be enclosed in a do {} while +# (0) loop. Grab the first statement and ensure its the whole macro +# if its not enclosed in a known good container if ($realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { my $ln = $linenr; @@ -5956,10 +5958,13 @@ sub process { my $exceptions = qr{ $Declare| + # named exceptions module_param_named| MODULE_PARM_DESC| DECLARE_PER_CPU| DEFINE_PER_CPU| + static_assert| + # declaration primitives __typeof__\(| union| struct| @@ -5994,11 +5999,11 @@ sub process { ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); } elsif ($dstat =~ /;/) { - ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", - "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); + WARN("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Non-declarative macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx\nBUT SEE:\n$DO_WHILE_0_ADVICE"); } else { ERROR("COMPLEX_MACRO", - "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); + "Macros with complex values should be enclosed in parentheses\n" . "$herectx\nBUT SEE:\n$DO_WHILE_0_ADVICE"); } } @@ -6040,6 +6045,12 @@ sub process { CHK("MACRO_ARG_PRECEDENCE", "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); } + +# check if this is an unused argument + if ($define_stmt !~ /\b$arg\b/ && $define_stmt) { + WARN("MACRO_ARG_UNUSED", + "Argument '$arg' is not used in function-like macro\n" . "$herectx"); + } } # check for macros with flow control, but without ## concatenation @@ -6583,11 +6594,11 @@ sub process { # ignore udelay's < 10, however if (! ($delay < 10) ) { CHK("USLEEP_RANGE", - "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.rst\n" . $herecurr); + "usleep_range is preferred over udelay; see function description of usleep_range() and udelay().\n" . $herecurr); } if ($delay > 2000) { WARN("LONG_UDELAY", - "long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\n" . $herecurr); + "long udelay - prefer mdelay; see function description of mdelay().\n" . $herecurr); } } @@ -6595,7 +6606,7 @@ sub process { if ($line =~ /\bmsleep\s*\((\d+)\);/) { if ($1 < 20) { WARN("MSLEEP", - "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.rst\n" . $herecurr); + "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); } } @@ -6903,7 +6914,7 @@ sub process { ($extension eq "f" && defined $qualifier && $qualifier !~ /^w/) || ($extension eq "4" && - defined $qualifier && $qualifier !~ /^cc/)) { + defined $qualifier && $qualifier !~ /^c(?:[hlbc]|hR)$/)) { $bad_specifier = $specifier; last; } @@ -7063,11 +7074,11 @@ sub process { my $max = $7; if ($min eq $max) { WARN("USLEEP_RANGE", - "usleep_range should not use min == max args; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); + "usleep_range should not use min == max args; see function description of usleep_range().\n" . "$here\n$stat\n"); } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && $min > $max) { WARN("USLEEP_RANGE", - "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); + "usleep_range args reversed, use min then max; see function description of usleep_range().\n" . "$here\n$stat\n"); } } @@ -7691,6 +7702,12 @@ sub process { ERROR("NOT_UNIFIED_DIFF", "Does not appear to be a unified-diff format patch\n"); } + if ($is_patch && $has_commit_log && $chk_fixes_tag) { + if ($needs_fixes_tag ne "" && !$is_revert && !$fixes_tag) { + WARN("MISSING_FIXES_TAG", + "The commit message has '$needs_fixes_tag', perhaps it also needs a 'Fixes:' tag?\n"); + } + } if ($is_patch && $has_commit_log && $chk_signoff) { if ($signoff == 0) { ERROR("MISSING_SIGN_OFF", diff --git a/scripts/checktransupdate.py b/scripts/checktransupdate.py new file mode 100755 index 000000000000..578c3fecfdfd --- /dev/null +++ b/scripts/checktransupdate.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +This script helps track the translation status of the documentation +in different locales, e.g., zh_CN. More specially, it uses `git log` +commit to find the latest english commit from the translation commit +(order by author date) and the latest english commits from HEAD. If +differences occur, report the file and commits that need to be updated. + +The usage is as follows: +- ./scripts/checktransupdate.py -l zh_CN +This will print all the files that need to be updated or translated in the zh_CN locale. +- ./scripts/checktransupdate.py Documentation/translations/zh_CN/dev-tools/testing-overview.rst +This will only print the status of the specified file. + +The output is something like: +Documentation/dev-tools/kfence.rst +No translation in the locale of zh_CN + +Documentation/translations/zh_CN/dev-tools/testing-overview.rst +commit 42fb9cfd5b18 ("Documentation: dev-tools: Add link to RV docs") +1 commits needs resolving in total +""" + +import os +import time +import logging +from argparse import ArgumentParser, ArgumentTypeError, BooleanOptionalAction +from datetime import datetime + + +def get_origin_path(file_path): + """Get the origin path from the translation path""" + paths = file_path.split("/") + tidx = paths.index("translations") + opaths = paths[:tidx] + opaths += paths[tidx + 2 :] + return "/".join(opaths) + + +def get_latest_commit_from(file_path, commit): + """Get the latest commit from the specified commit for the specified file""" + command = f"git log --pretty=format:%H%n%aD%n%cD%n%n%B {commit} -1 -- {file_path}" + logging.debug(command) + pipe = os.popen(command) + result = pipe.read() + result = result.split("\n") + if len(result) <= 1: + return None + + logging.debug("Result: %s", result[0]) + + return { + "hash": result[0], + "author_date": datetime.strptime(result[1], "%a, %d %b %Y %H:%M:%S %z"), + "commit_date": datetime.strptime(result[2], "%a, %d %b %Y %H:%M:%S %z"), + "message": result[4:], + } + + +def get_origin_from_trans(origin_path, t_from_head): + """Get the latest origin commit from the translation commit""" + o_from_t = get_latest_commit_from(origin_path, t_from_head["hash"]) + while o_from_t is not None and o_from_t["author_date"] > t_from_head["author_date"]: + o_from_t = get_latest_commit_from(origin_path, o_from_t["hash"] + "^") + if o_from_t is not None: + logging.debug("tracked origin commit id: %s", o_from_t["hash"]) + return o_from_t + + +def get_commits_count_between(opath, commit1, commit2): + """Get the commits count between two commits for the specified file""" + command = f"git log --pretty=format:%H {commit1}...{commit2} -- {opath}" + logging.debug(command) + pipe = os.popen(command) + result = pipe.read().split("\n") + # filter out empty lines + result = list(filter(lambda x: x != "", result)) + return result + + +def pretty_output(commit): + """Pretty print the commit message""" + command = f"git log --pretty='format:%h (\"%s\")' -1 {commit}" + logging.debug(command) + pipe = os.popen(command) + return pipe.read() + + +def valid_commit(commit): + """Check if the commit is valid or not""" + msg = pretty_output(commit) + return "Merge tag" not in msg + +def check_per_file(file_path): + """Check the translation status for the specified file""" + opath = get_origin_path(file_path) + + if not os.path.isfile(opath): + logging.error("Cannot find the origin path for {file_path}") + return + + o_from_head = get_latest_commit_from(opath, "HEAD") + t_from_head = get_latest_commit_from(file_path, "HEAD") + + if o_from_head is None or t_from_head is None: + logging.error("Cannot find the latest commit for %s", file_path) + return + + o_from_t = get_origin_from_trans(opath, t_from_head) + + if o_from_t is None: + logging.error("Error: Cannot find the latest origin commit for %s", file_path) + return + + if o_from_head["hash"] == o_from_t["hash"]: + logging.debug("No update needed for %s", file_path) + else: + logging.info(file_path) + commits = get_commits_count_between( + opath, o_from_t["hash"], o_from_head["hash"] + ) + count = 0 + for commit in commits: + if valid_commit(commit): + logging.info("commit %s", pretty_output(commit)) + count += 1 + logging.info("%d commits needs resolving in total\n", count) + + +def valid_locales(locale): + """Check if the locale is valid or not""" + script_path = os.path.dirname(os.path.abspath(__file__)) + linux_path = os.path.join(script_path, "..") + if not os.path.isdir(f"{linux_path}/Documentation/translations/{locale}"): + raise ArgumentTypeError("Invalid locale: {locale}") + return locale + + +def list_files_with_excluding_folders(folder, exclude_folders, include_suffix): + """List all files with the specified suffix in the folder and its subfolders""" + files = [] + stack = [folder] + + while stack: + pwd = stack.pop() + # filter out the exclude folders + if os.path.basename(pwd) in exclude_folders: + continue + # list all files and folders + for item in os.listdir(pwd): + ab_item = os.path.join(pwd, item) + if os.path.isdir(ab_item): + stack.append(ab_item) + else: + if ab_item.endswith(include_suffix): + files.append(ab_item) + + return files + + +class DmesgFormatter(logging.Formatter): + """Custom dmesg logging formatter""" + def format(self, record): + timestamp = time.time() + formatted_time = f"[{timestamp:>10.6f}]" + log_message = f"{formatted_time} {record.getMessage()}" + return log_message + + +def config_logging(log_level, log_file="checktransupdate.log"): + """configure logging based on the log level""" + # set up the root logger + logger = logging.getLogger() + logger.setLevel(log_level) + + # Create console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(log_level) + + # Create file handler + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(log_level) + + # Create formatter and add it to the handlers + formatter = DmesgFormatter() + console_handler.setFormatter(formatter) + file_handler.setFormatter(formatter) + + # Add the handler to the logger + logger.addHandler(console_handler) + logger.addHandler(file_handler) + + +def main(): + """Main function of the script""" + script_path = os.path.dirname(os.path.abspath(__file__)) + linux_path = os.path.join(script_path, "..") + + parser = ArgumentParser(description="Check the translation update") + parser.add_argument( + "-l", + "--locale", + default="zh_CN", + type=valid_locales, + help="Locale to check when files are not specified", + ) + + parser.add_argument( + "--print-missing-translations", + action=BooleanOptionalAction, + default=True, + help="Print files that do not have translations", + ) + + parser.add_argument( + '--log', + default='INFO', + choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], + help='Set the logging level') + + parser.add_argument( + '--logfile', + default='checktransupdate.log', + help='Set the logging file (default: checktransupdate.log)') + + parser.add_argument( + "files", nargs="*", help="Files to check, if not specified, check all files" + ) + args = parser.parse_args() + + # Configure logging based on the --log argument + log_level = getattr(logging, args.log.upper(), logging.INFO) + config_logging(log_level) + + # Get files related to linux path + files = args.files + if len(files) == 0: + offical_files = list_files_with_excluding_folders( + os.path.join(linux_path, "Documentation"), ["translations", "output"], "rst" + ) + + for file in offical_files: + # split the path into parts + path_parts = file.split(os.sep) + # find the index of the "Documentation" directory + kindex = path_parts.index("Documentation") + # insert the translations and locale after the Documentation directory + new_path_parts = path_parts[:kindex + 1] + ["translations", args.locale] \ + + path_parts[kindex + 1 :] + # join the path parts back together + new_file = os.sep.join(new_path_parts) + if os.path.isfile(new_file): + files.append(new_file) + else: + if args.print_missing_translations: + logging.info(os.path.relpath(os.path.abspath(file), linux_path)) + logging.info("No translation in the locale of %s\n", args.locale) + + files = list(map(lambda x: os.path.relpath(os.path.abspath(x), linux_path), files)) + + # cd to linux root directory + os.chdir(linux_path) + + for file in files: + check_per_file(file) + + +if __name__ == "__main__": + main() diff --git a/scripts/clang-tools/gen_compile_commands.py b/scripts/clang-tools/gen_compile_commands.py index e4fb686dfaa9..96e6e46ad1a7 100755 --- a/scripts/clang-tools/gen_compile_commands.py +++ b/scripts/clang-tools/gen_compile_commands.py @@ -167,10 +167,10 @@ def process_line(root_directory, command_prefix, file_path): root_directory or file_directory. """ # The .cmd files are intended to be included directly by Make, so they - # escape the pound sign '#', either as '\#' or '$(pound)' (depending on the - # kernel version). The compile_commands.json file is not interepreted - # by Make, so this code replaces the escaped version with '#'. - prefix = command_prefix.replace(r'\#', '#').replace('$(pound)', '#') + # escape the pound sign '#' as '$(pound)'. The compile_commands.json file + # is not interepreted by Make, so this code replaces the escaped version + # with '#'. + prefix = command_prefix.replace('$(pound)', '#') # Return the canonical path, eliminating any symbolic links encountered in the path. abs_path = os.path.realpath(os.path.join(root_directory, file_path)) diff --git a/scripts/coccicheck b/scripts/coccicheck index e52cb43fede6..0e6bc5a10320 100755 --- a/scripts/coccicheck +++ b/scripts/coccicheck @@ -80,11 +80,7 @@ command results in a shift count error.' NPROC=1 else ONLINE=0 - if [ "$KBUILD_EXTMOD" = "" ] ; then - OPTIONS="--dir $srctree $COCCIINCLUDE" - else - OPTIONS="--dir $KBUILD_EXTMOD $COCCIINCLUDE" - fi + OPTIONS="--dir $srcroot $COCCIINCLUDE" # Use only one thread per core by default if hyperthreading is enabled THREADS_PER_CORE=$(LANG=C lscpu | grep "Thread(s) per core: " | tr -cd "[:digit:]") diff --git a/scripts/coccinelle/api/pm_runtime.cocci b/scripts/coccinelle/api/pm_runtime.cocci index 4b9778874453..2c931e748dda 100644 --- a/scripts/coccinelle/api/pm_runtime.cocci +++ b/scripts/coccinelle/api/pm_runtime.cocci @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/// Make sure pm_runtime_* calls does not use unnecessary IS_ERR_VALUE +/// Make sure pm_runtime_* calls do not unnecessarily use IS_ERR_VALUE /// // Keywords: pm_runtime // Confidence: Medium diff --git a/scripts/coccinelle/api/stream_open.cocci b/scripts/coccinelle/api/stream_open.cocci index df00d6619b06..50ab60c81f13 100644 --- a/scripts/coccinelle/api/stream_open.cocci +++ b/scripts/coccinelle/api/stream_open.cocci @@ -131,7 +131,6 @@ identifier llseek_f; identifier fops0.fops; @@ struct file_operations fops = { - .llseek = no_llseek, }; @ has_noop_llseek @ diff --git a/scripts/coccinelle/api/string_choices.cocci b/scripts/coccinelle/api/string_choices.cocci index a71966c0494e..375045086912 100644 --- a/scripts/coccinelle/api/string_choices.cocci +++ b/scripts/coccinelle/api/string_choices.cocci @@ -14,23 +14,18 @@ expression E; - ((E == 1) ? "" : "s") + str_plural(E) | -- ((E != 1) ? "s" : "") -+ str_plural(E) -| - ((E > 1) ? "s" : "") + str_plural(E) ) -@str_plural_r depends on !patch exists@ +@str_plural_r depends on !patch@ expression E; position P; @@ ( -* ((E@P == 1) ? "" : "s") -| -* ((E@P != 1) ? "s" : "") +* (E@P == 1) ? "" : "s" | -* ((E@P > 1) ? "s" : "") +* (E@P > 1) ? "s" : "" ) @script:python depends on report@ @@ -39,3 +34,269 @@ e << str_plural_r.E; @@ coccilib.report.print_report(p[0], "opportunity for str_plural(%s)" % e) + +@str_up_down depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "up" : "down") ++ str_up_down(E) + +@str_up_down_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "up" : "down" + +@script:python depends on report@ +p << str_up_down_r.P; +e << str_up_down_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_up_down(%s)" % e) + +@str_down_up depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "down" : "up") ++ str_down_up(E) + +@str_down_up_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "down" : "up" + +@script:python depends on report@ +p << str_down_up_r.P; +e << str_down_up_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_down_up(%s)" % e) + +@str_true_false depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "true" : "false") ++ str_true_false(E) + +@str_true_false_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "true" : "false" + +@script:python depends on report@ +p << str_true_false_r.P; +e << str_true_false_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_true_false(%s)" % e) + +@str_false_true depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "false" : "true") ++ str_false_true(E) + +@str_false_true_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "false" : "true" + +@script:python depends on report@ +p << str_false_true_r.P; +e << str_false_true_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_false_true(%s)" % e) + +@str_hi_lo depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "hi" : "lo") ++ str_hi_lo(E) + +@str_hi_lo_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "hi" : "lo" + +@script:python depends on report@ +p << str_hi_lo_r.P; +e << str_hi_lo_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_hi_lo(%s)" % e) + +@str_high_low depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "high" : "low") ++ str_high_low(E) + +@str_high_low_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "high" : "low" + +@script:python depends on report@ +p << str_high_low_r.P; +e << str_high_low_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_high_low(%s)" % e) + +@str_lo_hi depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "lo" : "hi") ++ str_lo_hi(E) + +@str_lo_hi_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "lo" : "hi" + +@script:python depends on report@ +p << str_lo_hi_r.P; +e << str_lo_hi_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_lo_hi(%s)" % e) + +@str_low_high depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "low" : "high") ++ str_low_high(E) + +@str_low_high_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "low" : "high" + +@script:python depends on report@ +p << str_low_high_r.P; +e << str_low_high_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_low_high(%s)" % e) + +@str_enable_disable depends on patch@ +expression E; +@@ +- ((E) ? "enable" : "disable") ++ str_enable_disable(E) + +@str_enable_disable_r depends on !patch@ +expression E; +position P; +@@ +* E@P ? "enable" : "disable" + +@script:python depends on report@ +p << str_enable_disable_r.P; +e << str_enable_disable_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_enable_disable(%s)" % e) + +@str_enabled_disabled depends on patch@ +expression E; +@@ +- ((E) ? "enabled" : "disabled") ++ str_enabled_disabled(E) + +@str_enabled_disabled_r depends on !patch@ +expression E; +position P; +@@ +* E@P ? "enabled" : "disabled" + +@script:python depends on report@ +p << str_enabled_disabled_r.P; +e << str_enabled_disabled_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_enabled_disabled(%s)" % e) + +@str_read_write depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "read" : "write") ++ str_read_write(E) + +@str_read_write_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "read" : "write" + +@script:python depends on report@ +p << str_read_write_r.P; +e << str_read_write_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_read_write(%s)" % e) + +@str_write_read depends on patch disable neg_if_exp@ +expression E; +@@ +- ((E) ? "write" : "read") ++ str_write_read(E) + +@str_write_read_r depends on !patch disable neg_if_exp@ +expression E; +position P; +@@ +* E@P ? "write" : "read" + +@script:python depends on report@ +p << str_write_read_r.P; +e << str_write_read_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_write_read(%s)" % e) + +@str_on_off depends on patch@ +expression E; +@@ +- ((E) ? "on" : "off") ++ str_on_off(E) + +@str_on_off_r depends on !patch@ +expression E; +position P; +@@ +* E@P ? "on" : "off" + +@script:python depends on report@ +p << str_on_off_r.P; +e << str_on_off_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_on_off(%s)" % e) + +@str_yes_no depends on patch@ +expression E; +@@ +- ((E) ? "yes" : "no") ++ str_yes_no(E) + +@str_yes_no_r depends on !patch@ +expression E; +position P; +@@ +* E@P ? "yes" : "no" + +@script:python depends on report@ +p << str_yes_no_r.P; +e << str_yes_no_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_yes_no(%s)" % e) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index fcf908b34f27..ca4830ae3042 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -50,11 +50,26 @@ func(...) ...> } +// Ignore errcode returns. +@errcode@ +position p; +identifier func; +expression x; +binary operator cmp = {<, <=}; +@@ + +func(...) +{ + <... + return ((x) cmp@p 0 ? (x) : 0); + ...> +} + @rmin depends on !patch@ identifier func; expression x, y; binary operator cmp = {<, <=}; -position p; +position p != errcode.p; @@ func(...) @@ -116,21 +131,6 @@ func(...) ...> } -// Don't generate patches for errcode returns. -@errcode depends on patch@ -position p; -identifier func; -expression x; -binary operator cmp = {<, <=}; -@@ - -func(...) -{ - <... - return ((x) cmp@p 0 ? (x) : 0); - ...> -} - @pmin depends on patch@ identifier func; expression x, y; diff --git a/scripts/coccinelle/misc/newline_in_nl_msg.cocci b/scripts/coccinelle/misc/newline_in_nl_msg.cocci index 9baffe55d917..2814f6b205b9 100644 --- a/scripts/coccinelle/misc/newline_in_nl_msg.cocci +++ b/scripts/coccinelle/misc/newline_in_nl_msg.cocci @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /// -/// Catch strings ending in newline with GENL_SET_ERR_MSG, NL_SET_ERR_MSG, -/// NL_SET_ERR_MSG_MOD. +/// Catch strings ending in newline with (GE)NL_SET_ERR_MSG*. /// // Confidence: Very High // Copyright: (C) 2020 Intel Corporation @@ -17,7 +16,11 @@ expression e; constant m; position p; @@ - \(GENL_SET_ERR_MSG\|NL_SET_ERR_MSG\|NL_SET_ERR_MSG_MOD\)(e,m@p) + \(GENL_SET_ERR_MSG\|GENL_SET_ERR_MSG_FMT\|NL_SET_ERR_MSG\|NL_SET_ERR_MSG_MOD\| + NL_SET_ERR_MSG_FMT\|NL_SET_ERR_MSG_FMT_MOD\|NL_SET_ERR_MSG_WEAK\| + NL_SET_ERR_MSG_WEAK_MOD\|NL_SET_ERR_MSG_ATTR_POL\| + NL_SET_ERR_MSG_ATTR_POL_FMT\|NL_SET_ERR_MSG_ATTR\| + NL_SET_ERR_MSG_ATTR_FMT\)(e,m@p,...) @script:python@ m << r.m; @@ -32,7 +35,7 @@ expression r.e; constant r.m; position r.p; @@ - fname(e,m@p) + fname(e,m@p,...) //---------------------------------------------------------- // For context mode @@ -43,7 +46,7 @@ identifier r1.fname; expression r.e; constant r.m; @@ -* fname(e,m) +* fname(e,m,...) //---------------------------------------------------------- // For org mode diff --git a/scripts/coccinelle/misc/secs_to_jiffies.cocci b/scripts/coccinelle/misc/secs_to_jiffies.cocci new file mode 100644 index 000000000000..416f348174ca --- /dev/null +++ b/scripts/coccinelle/misc/secs_to_jiffies.cocci @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Find usages of: +/// - msecs_to_jiffies(value*1000) +/// - msecs_to_jiffies(value*MSEC_PER_SEC) +/// +// Confidence: High +// Copyright: (C) 2024 Easwar Hariharan, Microsoft +// Keywords: secs, seconds, jiffies +// + +virtual patch + +@depends on patch@ constant C; @@ + +- msecs_to_jiffies(C * 1000) ++ secs_to_jiffies(C) + +@depends on patch@ constant C; @@ + +- msecs_to_jiffies(C * MSEC_PER_SEC) ++ secs_to_jiffies(C) + +@depends on patch@ expression E; @@ + +- msecs_to_jiffies(E * 1000) ++ secs_to_jiffies(E) + +@depends on patch@ expression E; @@ + +- msecs_to_jiffies(E * MSEC_PER_SEC) ++ secs_to_jiffies(E) diff --git a/scripts/config b/scripts/config index ff88e2faefd3..ea475c07de28 100755 --- a/scripts/config +++ b/scripts/config @@ -32,6 +32,7 @@ commands: Disable option directly after other option --module-after|-M beforeopt option Turn option into module directly after other option + --refresh Refresh the config using old settings commands can be repeated multiple times @@ -124,16 +125,22 @@ undef_var() { txt_delete "^# $name is not set" "$FN" } -if [ "$1" = "--file" ]; then - FN="$2" - if [ "$FN" = "" ] ; then - usage +FN=.config +CMDS=() +while [[ $# -gt 0 ]]; do + if [ "$1" = "--file" ]; then + if [ "$2" = "" ]; then + usage + fi + FN="$2" + shift 2 + else + CMDS+=("$1") + shift fi - shift 2 -else - FN=.config -fi +done +set -- "${CMDS[@]}" if [ "$1" = "" ] ; then usage fi @@ -217,9 +224,8 @@ while [ "$1" != "" ] ; do set_var "${CONFIG_}$B" "${CONFIG_}$B=m" "${CONFIG_}$A" ;; - # undocumented because it ignores --file (fixme) --refresh) - yes "" | make oldconfig + yes "" | make oldconfig KCONFIG_CONFIG=$FN ;; *) diff --git a/scripts/const_structs.checkpatch b/scripts/const_structs.checkpatch index fa96cfd16e99..e8609a03c3d8 100644 --- a/scripts/const_structs.checkpatch +++ b/scripts/const_structs.checkpatch @@ -6,6 +6,7 @@ bus_type clk_ops comedi_lrange component_ops +ctl_table dentry_operations dev_pm_ops device_type @@ -39,6 +40,7 @@ kgdb_arch kgdb_io kobj_type kset_uevent_ops +lcd_ops lock_manager_operations machine_desc microcode_ops @@ -63,7 +65,17 @@ platform_suspend_ops proc_ops proto_ops pwm_ops +reg_default +reg_field +reg_sequence regmap_access_table +regmap_bus +regmap_config +regmap_irq +regmap_irq_chip +regmap_irq_sub_irq_map +regmap_range +regmap_range_cfg regulator_ops reset_control_ops rpc_pipe_ops @@ -78,6 +90,7 @@ snd_rawmidi_ops snd_soc_component_driver snd_soc_dai_ops snd_soc_ops +snd_soc_tplg_ops soc_pcmcia_socket_ops stacktrace_ops sysfs_ops diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh index fa5be6f57b00..17abc4e7a985 100755 --- a/scripts/decode_stacktrace.sh +++ b/scripts/decode_stacktrace.sh @@ -1,11 +1,13 @@ -#!/bin/bash +#!/usr/bin/env bash # SPDX-License-Identifier: GPL-2.0 # (c) 2014, Sasha Levin <sasha.levin@oracle.com> #set -x usage() { echo "Usage:" - echo " $0 -r <release> | <vmlinux> [<base path>|auto] [<modules path>]" + echo " $0 -r <release>" + echo " $0 [<vmlinux> [<base_path>|auto [<modules_path>]]]" + echo " $0 -h" } # Try to find a Rust demangler @@ -30,8 +32,12 @@ fi READELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX} ADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX} +NM=${UTIL_PREFIX}nm${UTIL_SUFFIX} -if [[ $1 == "-r" ]] ; then +if [[ $1 == "-h" ]] ; then + usage + exit 0 +elif [[ $1 == "-r" ]] ; then vmlinux="" basepath="auto" modpath="" @@ -88,31 +94,32 @@ find_module() { fi fi - if [[ "$modpath" != "" ]] ; then - for fn in $(find "$modpath" -name "${module//_/[-_]}.ko*") ; do - if ${READELF} -WS "$fn" | grep -qwF .debug_line ; then - echo $fn - return - fi - done - return 1 - fi - - modpath=$(dirname "$vmlinux") - find_module && return - - if [[ $release == "" ]] ; then + if [ -z $release ] ; then release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p') fi + if [ -n "${release}" ] ; then + release_dirs="/usr/lib/debug/lib/modules/$release /lib/modules/$release" + fi - for dn in {/usr/lib/debug,}/lib/modules/$release ; do - if [ -e "$dn" ] ; then - modpath="$dn" - find_module && return + found_without_debug_info=false + for dir in "$modpath" "$(dirname "$vmlinux")" ${release_dirs}; do + if [ -n "${dir}" ] && [ -e "${dir}" ]; then + for fn in $(find "$dir" -name "${module//_/[-_]}.ko*") ; do + if ${READELF} -WS "$fn" | grep -qwF .debug_line ; then + echo $fn + return + fi + found_without_debug_info=true + done fi done - modpath="" + if [[ ${found_without_debug_info} == true ]]; then + echo "WARNING! No debugging info in module ${module}, rebuild with DEBUG_KERNEL and DEBUG_INFO" >&2 + else + echo "WARNING! Cannot find .ko for module ${module}, please pass a valid module path" >&2 + fi + return 1 } @@ -130,7 +137,6 @@ parse_symbol() { else local objfile=$(find_module) if [[ $objfile == "" ]] ; then - echo "WARNING! Modules path isn't set, but is needed to parse this symbol" >&2 return fi if [[ $aarray_support == true ]]; then @@ -158,7 +164,7 @@ parse_symbol() { if [[ $aarray_support == true && "${cache[$module,$name]+isset}" == "isset" ]]; then local base_addr=${cache[$module,$name]} else - local base_addr=$(nm "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') + local base_addr=$(${NM} "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') if [[ $base_addr == "" ]] ; then # address not found return @@ -280,8 +286,23 @@ handle_line() { last=$(( $last - 1 )) fi + # Extract info after the symbol if present. E.g.: + # func_name+0x54/0x80 (P) + # ^^^ + # The regex assumes only uppercase letters will be used. To be + # extended if needed. + local info_str="" + if [[ ${words[$last]} =~ \([A-Z]*\) ]]; then + info_str=${words[$last]} + unset words[$last] + last=$(( $last - 1 )) + fi + if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then module=${words[$last]} + # some traces format is "(%pS)", which like "(foo+0x0/0x1 [bar])" + # so $module may like "[bar])". Strip the right parenthesis firstly + module=${module%\)} module=${module#\[} module=${module%\]} modbuildid=${module#* } @@ -302,7 +323,12 @@ handle_line() { parse_symbol # modifies $symbol # Add up the line number to the symbol - echo "${words[@]}" "$symbol $module" + if [[ -z ${module} ]] + then + echo "${words[@]}" "$symbol ${info_str}" + else + echo "${words[@]}" "$symbol $module ${info_str}" + fi } while read line; do diff --git a/scripts/depmod.sh b/scripts/depmod.sh index e22da27fe13e..3c34fecacbc8 100755 --- a/scripts/depmod.sh +++ b/scripts/depmod.sh @@ -12,7 +12,7 @@ KERNELRELEASE=$1 : ${DEPMOD:=depmod} -if ! test -r System.map ; then +if ! test -r "${objtree}/System.map" ; then echo "Warning: modules_install: missing 'System.map' file. Skipping depmod." >&2 exit 0 fi @@ -25,7 +25,7 @@ if [ -z $(command -v $DEPMOD) ]; then exit 0 fi -set -- -ae -F System.map +set -- -ae -F "${objtree}/System.map" if test -n "$INSTALL_MOD_PATH"; then set -- "$@" -b "$INSTALL_MOD_PATH" fi diff --git a/scripts/documentation-file-ref-check b/scripts/documentation-file-ref-check index 68083f2f1122..408b1dbe7884 100755 --- a/scripts/documentation-file-ref-check +++ b/scripts/documentation-file-ref-check @@ -92,7 +92,7 @@ while (<IN>) { next if ($f =~ m,^Next/,); # Makefiles and scripts contain nasty expressions to parse docs - next if ($f =~ m/Makefile/ || $f =~ m/\.sh$/); + next if ($f =~ m/Makefile/ || $f =~ m/\.(sh|py|pl|~|rej|org|orig)$/); # It doesn't make sense to parse hidden files next if ($f =~ m#/\.#); diff --git a/scripts/dtc/Makefile b/scripts/dtc/Makefile index 4d32b9497da9..b47f4daa4515 100644 --- a/scripts/dtc/Makefile +++ b/scripts/dtc/Makefile @@ -3,7 +3,7 @@ # *** Also keep .gitignore in sync when changing *** hostprogs-always-$(CONFIG_DTC) += dtc fdtoverlay -hostprogs-always-$(CHECK_DT_BINDING) += dtc +hostprogs-always-$(CHECK_DTBS) += dtc dtc-objs := dtc.o flattree.o fstree.o data.o livetree.o treesource.o \ srcpos.o checks.o util.o @@ -16,12 +16,12 @@ libfdt = $(addprefix libfdt/,$(libfdt-objs)) fdtoverlay-objs := $(libfdt) fdtoverlay.o util.o # Source files need to get at the userspace version of libfdt_env.h to compile -HOST_EXTRACFLAGS += -I $(srctree)/$(src)/libfdt +HOST_EXTRACFLAGS += -I $(src)/libfdt HOST_EXTRACFLAGS += -DNO_YAML # Generated files need one more search path to include headers in source tree -HOSTCFLAGS_dtc-lexer.lex.o := -I $(srctree)/$(src) -HOSTCFLAGS_dtc-parser.tab.o := -I $(srctree)/$(src) +HOSTCFLAGS_dtc-lexer.lex.o := -I $(src) +HOSTCFLAGS_dtc-parser.tab.o := -I $(src) # dependencies on generated files need to be listed explicitly $(obj)/dtc-lexer.lex.o: $(obj)/dtc-parser.tab.h diff --git a/scripts/dtc/checks.c b/scripts/dtc/checks.c index 9f31d2607182..6e06aeab5503 100644 --- a/scripts/dtc/checks.c +++ b/scripts/dtc/checks.c @@ -31,7 +31,7 @@ typedef void (*check_fn)(struct check *c, struct dt_info *dti, struct node *node struct check { const char *name; check_fn fn; - void *data; + const void *data; bool warn, error; enum checkstatus status; bool inprogress; @@ -114,6 +114,7 @@ static inline void PRINTF(5, 6) check_msg(struct check *c, struct dt_info *dti, } fputs(str, stderr); + free(str); } #define FAIL(c, dti, node, ...) \ @@ -207,7 +208,7 @@ static void check_is_string(struct check *c, struct dt_info *dti, struct node *node) { struct property *prop; - char *propname = c->data; + const char *propname = c->data; prop = get_property(node, propname); if (!prop) @@ -226,7 +227,7 @@ static void check_is_string_list(struct check *c, struct dt_info *dti, { int rem, l; struct property *prop; - char *propname = c->data; + const char *propname = c->data; char *str; prop = get_property(node, propname); @@ -254,7 +255,7 @@ static void check_is_cell(struct check *c, struct dt_info *dti, struct node *node) { struct property *prop; - char *propname = c->data; + const char *propname = c->data; prop = get_property(node, propname); if (!prop) @@ -1078,10 +1079,11 @@ static void check_i2c_bus_reg(struct check *c, struct dt_info *dti, struct node /* Ignore I2C_OWN_SLAVE_ADDRESS */ reg &= ~I2C_OWN_SLAVE_ADDRESS; - if ((reg & I2C_TEN_BIT_ADDRESS) && ((reg & ~I2C_TEN_BIT_ADDRESS) > 0x3ff)) - FAIL_PROP(c, dti, node, prop, "I2C address must be less than 10-bits, got \"0x%x\"", + if (reg & I2C_TEN_BIT_ADDRESS) { + if ((reg & ~I2C_TEN_BIT_ADDRESS) > 0x3ff) + FAIL_PROP(c, dti, node, prop, "I2C address must be less than 10-bits, got \"0x%x\"", reg); - else if (reg > 0x7f) + } else if (reg > 0x7f) FAIL_PROP(c, dti, node, prop, "I2C address must be less than 7-bits, got \"0x%x\". Set I2C_TEN_BIT_ADDRESS for 10 bit addresses or fix the property", reg); } @@ -1108,7 +1110,7 @@ static void check_spi_bus_bridge(struct check *c, struct dt_info *dti, struct no for_each_child(node, child) { struct property *prop; for_each_property(child, prop) { - if (strprefixeq(prop->name, 4, "spi-")) { + if (strstarts(prop->name, "spi-")) { node->bus = &spi_bus; break; } @@ -1180,7 +1182,7 @@ static void check_unit_address_format(struct check *c, struct dt_info *dti, /* skip over 0x for next test */ unitname += 2; } - if (unitname[0] == '0' && isxdigit(unitname[1])) + if (unitname[0] == '0' && isxdigit((unsigned char)unitname[1])) FAIL(c, dti, node, "unit name should not have leading 0s"); } WARNING(unit_address_format, check_unit_address_format, NULL, @@ -1222,7 +1224,7 @@ static void check_avoid_unnecessary_addr_size(struct check *c, struct dt_info *d if (!node->parent || node->addr_cells < 0 || node->size_cells < 0) return; - if (get_property(node, "ranges") || !node->children) + if (get_property(node, "ranges") || get_property(node, "dma-ranges") || !node->children) return; for_each_child(node, child) { @@ -1232,7 +1234,7 @@ static void check_avoid_unnecessary_addr_size(struct check *c, struct dt_info *d } if (!has_reg) - FAIL(c, dti, node, "unnecessary #address-cells/#size-cells without \"ranges\" or child \"reg\" property"); + FAIL(c, dti, node, "unnecessary #address-cells/#size-cells without \"ranges\", \"dma-ranges\" or child \"reg\" property"); } WARNING(avoid_unnecessary_addr_size, check_avoid_unnecessary_addr_size, NULL, &avoid_default_addr_size); @@ -1465,7 +1467,7 @@ static void check_provider_cells_property(struct check *c, struct dt_info *dti, struct node *node) { - struct provider *provider = c->data; + const struct provider *provider = c->data; struct property *prop; prop = get_property(node, provider->prop_name); @@ -1673,6 +1675,10 @@ static void check_interrupt_map(struct check *c, parent_cellsize += propval_cell(cellprop); cell += 1 + parent_cellsize; + if (cell > map_cells) + FAIL_PROP(c, dti, node, irq_map_prop, + "property size (%d) mismatch, expected %zu", + irq_map_prop->val.len, cell * sizeof(cell_t)); } } WARNING(interrupt_map, check_interrupt_map, NULL, &phandle_references, &addr_size_cells, &interrupt_provider); @@ -1765,6 +1771,11 @@ static void check_graph_nodes(struct check *c, struct dt_info *dti, get_property(child, "remote-endpoint"))) continue; + /* The root node cannot be a port */ + if (!node->parent) { + FAIL(c, dti, node, "root node contains endpoint node '%s', potentially misplaced remote-endpoint property", child->name); + continue; + } node->bus = &graph_port_bus; /* The parent of 'port' nodes can be either 'ports' or a device */ @@ -1778,31 +1789,6 @@ static void check_graph_nodes(struct check *c, struct dt_info *dti, } WARNING(graph_nodes, check_graph_nodes, NULL); -static void check_graph_child_address(struct check *c, struct dt_info *dti, - struct node *node) -{ - int cnt = 0; - struct node *child; - - if (node->bus != &graph_ports_bus && node->bus != &graph_port_bus) - return; - - for_each_child(node, child) { - struct property *prop = get_property(child, "reg"); - - /* No error if we have any non-zero unit address */ - if (prop && propval_cell(prop) != 0) - return; - - cnt++; - } - - if (cnt == 1 && node->addr_cells != -1) - FAIL(c, dti, node, "graph node has single child node '%s', #address-cells/#size-cells are not necessary", - node->children->name); -} -WARNING(graph_child_address, check_graph_child_address, NULL, &graph_nodes); - static void check_graph_reg(struct check *c, struct dt_info *dti, struct node *node) { @@ -1840,10 +1826,14 @@ static void check_graph_port(struct check *c, struct dt_info *dti, if (node->bus != &graph_port_bus) return; + check_graph_reg(c, dti, node); + + /* skip checks below for overlays */ + if (dti->dtsflags & DTSF_PLUGIN) + return; + if (!strprefixeq(node->name, node->basenamelen, "port")) FAIL(c, dti, node, "graph port node name should be 'port'"); - - check_graph_reg(c, dti, node); } WARNING(graph_port, check_graph_port, NULL, &graph_nodes); @@ -1878,11 +1868,15 @@ static void check_graph_endpoint(struct check *c, struct dt_info *dti, if (!node->parent || node->parent->bus != &graph_port_bus) return; + check_graph_reg(c, dti, node); + + /* skip checks below for overlays */ + if (dti->dtsflags & DTSF_PLUGIN) + return; + if (!strprefixeq(node->name, node->basenamelen, "endpoint")) FAIL(c, dti, node, "graph endpoint node name should be 'endpoint'"); - check_graph_reg(c, dti, node); - remote_node = get_remote_endpoint(c, dti, node); if (!remote_node) return; @@ -1893,6 +1887,31 @@ static void check_graph_endpoint(struct check *c, struct dt_info *dti, } WARNING(graph_endpoint, check_graph_endpoint, NULL, &graph_nodes); +static void check_graph_child_address(struct check *c, struct dt_info *dti, + struct node *node) +{ + int cnt = 0; + struct node *child; + + if (node->bus != &graph_ports_bus && node->bus != &graph_port_bus) + return; + + for_each_child(node, child) { + struct property *prop = get_property(child, "reg"); + + /* No error if we have any non-zero unit address */ + if (prop && propval_cell(prop) != 0 ) + return; + + cnt++; + } + + if (cnt == 1 && node->addr_cells != -1) + FAIL(c, dti, node, "graph node has single child node '%s', #address-cells/#size-cells are not necessary", + node->children->name); +} +WARNING(graph_child_address, check_graph_child_address, NULL, &graph_nodes, &graph_port, &graph_endpoint); + static struct check *check_table[] = { &duplicate_node_names, &duplicate_property_names, &node_name_chars, &node_name_format, &property_name_chars, diff --git a/scripts/dtc/dt-extract-compatibles b/scripts/dtc/dt-extract-compatibles index 5ffb2364409b..6570efabaa64 100755 --- a/scripts/dtc/dt-extract-compatibles +++ b/scripts/dtc/dt-extract-compatibles @@ -46,6 +46,15 @@ def parse_of_match_table(data): return match_table_list +def parse_of_functions(data, func_name): + """ Find all compatibles in the last argument of a given function """ + compat_list = [] + for m in re.finditer(rf'{func_name}\(([a-zA-Z0-9_>\(\)"\-]+,\s)*"([a-zA-Z0-9_,-]+)"\)', data): + compat_list.append(m[2]) + + return compat_list + + def parse_compatibles(file, compat_ignore_list): with open(file, 'r', encoding='utf-8') as f: data = f.read().replace('\n', '') @@ -60,6 +69,10 @@ def parse_compatibles(file, compat_ignore_list): else: compat_list = parse_of_declare_macros(data) compat_list += parse_of_device_id(data) + compat_list += parse_of_functions(data, "_is_compatible") + compat_list += parse_of_functions(data, "of_find_compatible_node") + compat_list += parse_of_functions(data, "for_each_compatible_node") + compat_list += parse_of_functions(data, "of_get_compatible_child") return compat_list diff --git a/scripts/dtc/dtc-parser.y b/scripts/dtc/dtc-parser.y index bff1337ec266..4d5eece52624 100644 --- a/scripts/dtc/dtc-parser.y +++ b/scripts/dtc/dtc-parser.y @@ -284,14 +284,17 @@ propdef: DT_PROPNODENAME '=' propdata ';' { $$ = build_property($1, $3, &@$); + free($1); } | DT_PROPNODENAME ';' { $$ = build_property($1, empty_data, &@$); + free($1); } | DT_DEL_PROP DT_PROPNODENAME ';' { $$ = build_property_delete($2); + free($2); } | DT_LABEL propdef { @@ -570,10 +573,12 @@ subnode: DT_PROPNODENAME nodedef { $$ = name_node($2, $1); + free($1); } | DT_DEL_NODE DT_PROPNODENAME ';' { $$ = name_node(build_node_delete(&@$), $2); + free($2); } | DT_OMIT_NO_REF subnode { diff --git a/scripts/dtc/dtc.c b/scripts/dtc/dtc.c index bc786c543b7e..0655c2e2c362 100644 --- a/scripts/dtc/dtc.c +++ b/scripts/dtc/dtc.c @@ -47,7 +47,7 @@ static void fill_fullpaths(struct node *tree, const char *prefix) /* Usage related data. */ static const char usage_synopsis[] = "dtc [options] <input file>"; -static const char usage_short_opts[] = "qI:O:o:V:d:R:S:p:a:fb:i:H:sW:E:@AThv"; +static const char usage_short_opts[] = "qI:O:o:V:d:R:S:p:a:fb:i:H:sW:E:@LAThv"; static struct option const usage_long_opts[] = { {"quiet", no_argument, NULL, 'q'}, {"in-format", a_argument, NULL, 'I'}, @@ -67,6 +67,7 @@ static struct option const usage_long_opts[] = { {"warning", a_argument, NULL, 'W'}, {"error", a_argument, NULL, 'E'}, {"symbols", no_argument, NULL, '@'}, + {"local-fixups", no_argument, NULL, 'L'}, {"auto-alias", no_argument, NULL, 'A'}, {"annotate", no_argument, NULL, 'T'}, {"help", no_argument, NULL, 'h'}, @@ -104,6 +105,7 @@ static const char * const usage_opts_help[] = { "\n\tEnable/disable warnings (prefix with \"no-\")", "\n\tEnable/disable errors (prefix with \"no-\")", "\n\tEnable generation of symbols", + "\n\tPossibly generates a __local_fixups__ and a __fixups__ node at the root node", "\n\tEnable auto-alias of labels", "\n\tAnnotate output .dts with input source file and line (-T -T for more details)", "\n\tPrint this help and exit", @@ -252,6 +254,11 @@ int main(int argc, char *argv[]) case '@': generate_symbols = 1; break; + + case 'L': + generate_fixups = 1; + break; + case 'A': auto_label_aliases = 1; break; diff --git a/scripts/dtc/dtc.h b/scripts/dtc/dtc.h index 0a1f54991026..4c4aaca1fc41 100644 --- a/scripts/dtc/dtc.h +++ b/scripts/dtc/dtc.h @@ -260,16 +260,16 @@ struct node { void add_label(struct label **labels, char *label); void delete_labels(struct label **labels); -struct property *build_property(char *name, struct data val, +struct property *build_property(const char *name, struct data val, struct srcpos *srcpos); -struct property *build_property_delete(char *name); +struct property *build_property_delete(const char *name); struct property *chain_property(struct property *first, struct property *list); struct property *reverse_properties(struct property *first); struct node *build_node(struct property *proplist, struct node *children, struct srcpos *srcpos); struct node *build_node_delete(struct srcpos *srcpos); -struct node *name_node(struct node *node, char *name); +struct node *name_node(struct node *node, const char *name); struct node *omit_node_if_unused(struct node *node); struct node *reference_node(struct node *node); struct node *chain_node(struct node *first, struct node *list); @@ -336,9 +336,9 @@ struct dt_info *build_dt_info(unsigned int dtsflags, struct reserve_info *reservelist, struct node *tree, uint32_t boot_cpuid_phys); void sort_tree(struct dt_info *dti); -void generate_label_tree(struct dt_info *dti, char *name, bool allocph); -void generate_fixups_tree(struct dt_info *dti, char *name); -void generate_local_fixups_tree(struct dt_info *dti, char *name); +void generate_label_tree(struct dt_info *dti, const char *name, bool allocph); +void generate_fixups_tree(struct dt_info *dti, const char *name); +void generate_local_fixups_tree(struct dt_info *dti, const char *name); /* Checks */ diff --git a/scripts/dtc/fdtoverlay.c b/scripts/dtc/fdtoverlay.c index 5350af65679f..699b4f616502 100644 --- a/scripts/dtc/fdtoverlay.c +++ b/scripts/dtc/fdtoverlay.c @@ -23,9 +23,7 @@ /* Usage related data. */ static const char usage_synopsis[] = "apply a number of overlays to a base blob\n" - " fdtoverlay <options> [<overlay.dtbo> [<overlay.dtbo>]]\n" - "\n" - USAGE_TYPE_MSG; + " fdtoverlay <options> [<overlay.dtbo> [<overlay.dtbo>]]"; static const char usage_short_opts[] = "i:o:v" USAGE_COMMON_SHORT_OPTS; static struct option const usage_long_opts[] = { {"input", required_argument, NULL, 'i'}, @@ -50,7 +48,7 @@ static void *apply_one(char *base, const char *overlay, size_t *buf_len, int ret; /* - * We take a copies first, because a a failed apply can trash + * We take copies first, because a failed apply can trash * both the base blob and the overlay */ tmpo = xmalloc(fdt_totalsize(overlay)); diff --git a/scripts/dtc/flattree.c b/scripts/dtc/flattree.c index 95e43d32c3e6..1bcd8089c5b9 100644 --- a/scripts/dtc/flattree.c +++ b/scripts/dtc/flattree.c @@ -604,11 +604,11 @@ static void flat_realign(struct inbuf *inb, int align) die("Premature end of data parsing flat device tree\n"); } -static char *flat_read_string(struct inbuf *inb) +static const char *flat_read_string(struct inbuf *inb) { int len = 0; const char *p = inb->ptr; - char *str; + const char *str; do { if (p >= inb->limit) @@ -616,7 +616,7 @@ static char *flat_read_string(struct inbuf *inb) len++; } while ((*p++) != '\0'); - str = xstrdup(inb->ptr); + str = inb->ptr; inb->ptr += len; @@ -711,7 +711,7 @@ static struct reserve_info *flat_read_mem_reserve(struct inbuf *inb) } -static char *nodename_from_path(const char *ppath, const char *cpath) +static const char *nodename_from_path(const char *ppath, const char *cpath) { int plen; @@ -725,7 +725,7 @@ static char *nodename_from_path(const char *ppath, const char *cpath) if (!streq(ppath, "/")) plen++; - return xstrdup(cpath + plen); + return cpath + plen; } static struct node *unflatten_tree(struct inbuf *dtbuf, @@ -733,7 +733,7 @@ static struct node *unflatten_tree(struct inbuf *dtbuf, const char *parent_flatname, int flags) { struct node *node; - char *flatname; + const char *flatname; uint32_t val; node = build_node(NULL, NULL, NULL); @@ -741,9 +741,10 @@ static struct node *unflatten_tree(struct inbuf *dtbuf, flatname = flat_read_string(dtbuf); if (flags & FTF_FULLPATH) - node->name = nodename_from_path(parent_flatname, flatname); + node->name = xstrdup(nodename_from_path(parent_flatname, + flatname)); else - node->name = flatname; + node->name = xstrdup(flatname); do { struct property *prop; @@ -785,10 +786,6 @@ static struct node *unflatten_tree(struct inbuf *dtbuf, } } while (val != FDT_END_NODE); - if (node->name != flatname) { - free(flatname); - } - return node; } diff --git a/scripts/dtc/fstree.c b/scripts/dtc/fstree.c index 5e59594ab301..0f9a534bacdb 100644 --- a/scripts/dtc/fstree.c +++ b/scripts/dtc/fstree.c @@ -43,7 +43,7 @@ static struct node *read_fstree(const char *dirname) "WARNING: Cannot open %s: %s\n", tmpname, strerror(errno)); } else { - prop = build_property(xstrdup(de->d_name), + prop = build_property(de->d_name, data_copy_file(pfile, st.st_size), NULL); diff --git a/scripts/dtc/libfdt/fdt_overlay.c b/scripts/dtc/libfdt/fdt_overlay.c index 5c0c3981b89d..28b667ffc490 100644 --- a/scripts/dtc/libfdt/fdt_overlay.c +++ b/scripts/dtc/libfdt/fdt_overlay.c @@ -101,26 +101,22 @@ int fdt_overlay_target_offset(const void *fdt, const void *fdto, static int overlay_phandle_add_offset(void *fdt, int node, const char *name, uint32_t delta) { - const fdt32_t *val; - uint32_t adj_val; + fdt32_t *valp, val; int len; - val = fdt_getprop(fdt, node, name, &len); - if (!val) + valp = fdt_getprop_w(fdt, node, name, &len); + if (!valp) return len; - if (len != sizeof(*val)) + if (len != sizeof(val)) return -FDT_ERR_BADPHANDLE; - adj_val = fdt32_to_cpu(*val); - if ((adj_val + delta) < adj_val) - return -FDT_ERR_NOPHANDLES; - - adj_val += delta; - if (adj_val == (uint32_t)-1) + val = fdt32_ld(valp); + if (val + delta < val || val + delta == (uint32_t)-1) return -FDT_ERR_NOPHANDLES; - return fdt_setprop_inplace_u32(fdt, node, name, adj_val); + fdt32_st(valp, val + delta); + return 0; } /** @@ -213,8 +209,8 @@ static int overlay_update_local_node_references(void *fdto, fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { const fdt32_t *fixup_val; - const char *tree_val; const char *name; + char *tree_val; int fixup_len; int tree_len; int i; @@ -228,7 +224,7 @@ static int overlay_update_local_node_references(void *fdto, return -FDT_ERR_BADOVERLAY; fixup_len /= sizeof(uint32_t); - tree_val = fdt_getprop(fdto, tree_node, name, &tree_len); + tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); if (!tree_val) { if (tree_len == -FDT_ERR_NOTFOUND) return -FDT_ERR_BADOVERLAY; @@ -237,33 +233,15 @@ static int overlay_update_local_node_references(void *fdto, } for (i = 0; i < fixup_len; i++) { - fdt32_t adj_val; - uint32_t poffset; + fdt32_t *refp; - poffset = fdt32_to_cpu(fixup_val[i]); + refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); /* - * phandles to fixup can be unaligned. - * - * Use a memcpy for the architectures that do - * not support unaligned accesses. + * phandles to fixup can be unaligned, so use + * fdt32_{ld,st}() to read/write them. */ - memcpy(&adj_val, tree_val + poffset, sizeof(adj_val)); - - adj_val = cpu_to_fdt32(fdt32_to_cpu(adj_val) + delta); - - ret = fdt_setprop_inplace_namelen_partial(fdto, - tree_node, - name, - strlen(name), - poffset, - &adj_val, - sizeof(adj_val)); - if (ret == -FDT_ERR_NOSPACE) - return -FDT_ERR_BADOVERLAY; - - if (ret) - return ret; + fdt32_st(refp, fdt32_ld(refp) + delta); } } @@ -337,7 +315,7 @@ static int overlay_update_local_references(void *fdto, uint32_t delta) * @name: Name of the property holding the phandle reference in the overlay * @name_len: number of name characters to consider * @poffset: Offset within the overlay property where the phandle is stored - * @label: Label of the node referenced by the phandle + * @phandle: Phandle referencing the node * * overlay_fixup_one_phandle() resolves an overlay phandle pointing to * a node in the base device tree. @@ -354,30 +332,14 @@ static int overlay_fixup_one_phandle(void *fdt, void *fdto, int symbols_off, const char *path, uint32_t path_len, const char *name, uint32_t name_len, - int poffset, const char *label) + int poffset, uint32_t phandle) { - const char *symbol_path; - uint32_t phandle; fdt32_t phandle_prop; - int symbol_off, fixup_off; - int prop_len; + int fixup_off; if (symbols_off < 0) return symbols_off; - symbol_path = fdt_getprop(fdt, symbols_off, label, - &prop_len); - if (!symbol_path) - return prop_len; - - symbol_off = fdt_path_offset(fdt, symbol_path); - if (symbol_off < 0) - return symbol_off; - - phandle = fdt_get_phandle(fdt, symbol_off); - if (!phandle) - return -FDT_ERR_NOTFOUND; - fixup_off = fdt_path_offset_namelen(fdto, path, path_len); if (fixup_off == -FDT_ERR_NOTFOUND) return -FDT_ERR_BADOVERLAY; @@ -416,6 +378,10 @@ static int overlay_fixup_phandle(void *fdt, void *fdto, int symbols_off, const char *value; const char *label; int len; + const char *symbol_path; + int prop_len; + int symbol_off; + uint32_t phandle; value = fdt_getprop_by_offset(fdto, property, &label, &len); @@ -426,6 +392,18 @@ static int overlay_fixup_phandle(void *fdt, void *fdto, int symbols_off, return len; } + symbol_path = fdt_getprop(fdt, symbols_off, label, &prop_len); + if (!symbol_path) + return prop_len; + + symbol_off = fdt_path_offset(fdt, symbol_path); + if (symbol_off < 0) + return symbol_off; + + phandle = fdt_get_phandle(fdt, symbol_off); + if (!phandle) + return -FDT_ERR_NOTFOUND; + do { const char *path, *name, *fixup_end; const char *fixup_str = value; @@ -467,7 +445,7 @@ static int overlay_fixup_phandle(void *fdt, void *fdto, int symbols_off, ret = overlay_fixup_one_phandle(fdt, fdto, symbols_off, path, path_len, name, name_len, - poffset, label); + poffset, phandle); if (ret) return ret; } while (len > 0); @@ -521,6 +499,255 @@ static int overlay_fixup_phandles(void *fdt, void *fdto) } /** + * overlay_adjust_local_conflicting_phandle: Changes a phandle value + * @fdto: Device tree overlay + * @node: The node the phandle is set for + * @fdt_phandle: The new value for the phandle + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_local_conflicting_phandle(void *fdto, int node, + uint32_t fdt_phandle) +{ + const fdt32_t *php; + int len, ret; + + php = fdt_getprop(fdto, node, "phandle", &len); + if (php && len == sizeof(*php)) { + ret = fdt_setprop_inplace_u32(fdto, node, "phandle", fdt_phandle); + if (ret) + return ret; + } + + php = fdt_getprop(fdto, node, "linux,phandle", &len); + if (php && len == sizeof(*php)) { + ret = fdt_setprop_inplace_u32(fdto, node, "linux,phandle", fdt_phandle); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_node_conflicting_references - Recursively replace phandle values + * @fdto: Device tree overlay blob + * @tree_node: Node to recurse into + * @fixup_node: Node offset of the matching local fixups node + * @fdt_phandle: Value to replace phandles with + * @fdto_phandle: Value to be replaced + * + * Replaces all phandles with value @fdto_phandle by @fdt_phandle. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_node_conflicting_references(void *fdto, int tree_node, + int fixup_node, + uint32_t fdt_phandle, + uint32_t fdto_phandle) +{ + int fixup_prop; + int fixup_child; + int ret; + + fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { + const fdt32_t *fixup_val; + const char *name; + char *tree_val; + int fixup_len; + int tree_len; + int i; + + fixup_val = fdt_getprop_by_offset(fdto, fixup_prop, + &name, &fixup_len); + if (!fixup_val) + return fixup_len; + + if (fixup_len % sizeof(uint32_t)) + return -FDT_ERR_BADOVERLAY; + fixup_len /= sizeof(uint32_t); + + tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); + if (!tree_val) { + if (tree_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + + return tree_len; + } + + for (i = 0; i < fixup_len; i++) { + fdt32_t *refp; + uint32_t valp; + + refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); + valp = fdt32_ld(refp); + + if (valp == fdto_phandle) + fdt32_st(refp, fdt_phandle); + } + } + + fdt_for_each_subnode(fixup_child, fdto, fixup_node) { + const char *fixup_child_name = fdt_get_name(fdto, fixup_child, NULL); + int tree_child; + + tree_child = fdt_subnode_offset(fdto, tree_node, fixup_child_name); + + if (tree_child == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (tree_child < 0) + return tree_child; + + ret = overlay_update_node_conflicting_references(fdto, tree_child, + fixup_child, + fdt_phandle, + fdto_phandle); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_local_conflicting_references - Recursively replace phandle values + * @fdto: Device tree overlay blob + * @fdt_phandle: Value to replace phandles with + * @fdto_phandle: Value to be replaced + * + * Replaces all phandles with value @fdto_phandle by @fdt_phandle. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_conflicting_references(void *fdto, + uint32_t fdt_phandle, + uint32_t fdto_phandle) +{ + int fixups; + + fixups = fdt_path_offset(fdto, "/__local_fixups__"); + if (fixups == -FDT_ERR_NOTFOUND) + return 0; + if (fixups < 0) + return fixups; + + return overlay_update_node_conflicting_references(fdto, 0, fixups, + fdt_phandle, + fdto_phandle); +} + +/** + * overlay_prevent_phandle_overwrite_node - Helper function for overlay_prevent_phandle_overwrite + * @fdt: Base Device tree blob + * @fdtnode: Node in fdt that is checked for an overwrite + * @fdto: Device tree overlay blob + * @fdtonode: Node in fdto matching @fdtnode + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_prevent_phandle_overwrite_node(void *fdt, int fdtnode, + void *fdto, int fdtonode) +{ + uint32_t fdt_phandle, fdto_phandle; + int fdtochild; + + fdt_phandle = fdt_get_phandle(fdt, fdtnode); + fdto_phandle = fdt_get_phandle(fdto, fdtonode); + + if (fdt_phandle && fdto_phandle) { + int ret; + + ret = overlay_adjust_local_conflicting_phandle(fdto, fdtonode, + fdt_phandle); + if (ret) + return ret; + + ret = overlay_update_local_conflicting_references(fdto, + fdt_phandle, + fdto_phandle); + if (ret) + return ret; + } + + fdt_for_each_subnode(fdtochild, fdto, fdtonode) { + const char *name = fdt_get_name(fdto, fdtochild, NULL); + int fdtchild; + int ret; + + fdtchild = fdt_subnode_offset(fdt, fdtnode, name); + if (fdtchild == -FDT_ERR_NOTFOUND) + /* + * no further overwrites possible here as this node is + * new + */ + continue; + + ret = overlay_prevent_phandle_overwrite_node(fdt, fdtchild, + fdto, fdtochild); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_prevent_phandle_overwrite - Fixes overlay phandles to not overwrite base phandles + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * Checks recursively if applying fdto overwrites phandle values in the base + * dtb. When such a phandle is found, the fdto is changed to use the fdt's + * phandle value to not break references in the base. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_prevent_phandle_overwrite(void *fdt, void *fdto) +{ + int fragment; + + fdt_for_each_subnode(fragment, fdto, 0) { + int overlay; + int target; + int ret; + + overlay = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (overlay == -FDT_ERR_NOTFOUND) + continue; + + if (overlay < 0) + return overlay; + + target = fdt_overlay_target_offset(fdt, fdto, fragment, NULL); + if (target == -FDT_ERR_NOTFOUND) + /* + * The subtree doesn't exist in the base, so nothing + * will be overwritten. + */ + continue; + else if (target < 0) + return target; + + ret = overlay_prevent_phandle_overwrite_node(fdt, target, + fdto, overlay); + if (ret) + return ret; + } + + return 0; +} + +/** * overlay_apply_node - Merges a node into the base device tree * @fdt: Base Device Tree blob * @target: Node offset in the base device tree to apply the fragment to @@ -824,18 +1051,26 @@ int fdt_overlay_apply(void *fdt, void *fdto) if (ret) goto err; + /* Increase all phandles in the fdto by delta */ ret = overlay_adjust_local_phandles(fdto, delta); if (ret) goto err; + /* Adapt the phandle values in fdto to the above increase */ ret = overlay_update_local_references(fdto, delta); if (ret) goto err; + /* Update fdto's phandles using symbols from fdt */ ret = overlay_fixup_phandles(fdt, fdto); if (ret) goto err; + /* Don't overwrite phandles in fdt */ + ret = overlay_prevent_phandle_overwrite(fdt, fdto); + if (ret) + goto err; + ret = overlay_merge(fdt, fdto); if (ret) goto err; diff --git a/scripts/dtc/libfdt/fdt_ro.c b/scripts/dtc/libfdt/fdt_ro.c index 9f6c551a22c2..b78c4e48f1cb 100644 --- a/scripts/dtc/libfdt/fdt_ro.c +++ b/scripts/dtc/libfdt/fdt_ro.c @@ -255,6 +255,9 @@ int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) FDT_RO_PROBE(fdt); + if (!can_assume(VALID_INPUT) && namelen <= 0) + return -FDT_ERR_BADPATH; + /* see if we have an alias */ if (*path != '/') { const char *q = memchr(path, '/', end - p); @@ -522,16 +525,31 @@ uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) return fdt32_ld_(php); } +static const void *fdt_path_getprop_namelen(const void *fdt, const char *path, + const char *propname, int propnamelen, + int *lenp) +{ + int offset = fdt_path_offset(fdt, path); + + if (offset < 0) + return NULL; + + return fdt_getprop_namelen(fdt, offset, propname, propnamelen, lenp); +} + const char *fdt_get_alias_namelen(const void *fdt, const char *name, int namelen) { - int aliasoffset; + int len; + const char *alias; - aliasoffset = fdt_path_offset(fdt, "/aliases"); - if (aliasoffset < 0) + alias = fdt_path_getprop_namelen(fdt, "/aliases", name, namelen, &len); + + if (!can_assume(VALID_DTB) && + !(alias && len > 0 && alias[len - 1] == '\0' && *alias == '/')) return NULL; - return fdt_getprop_namelen(fdt, aliasoffset, name, namelen, NULL); + return alias; } const char *fdt_get_alias(const void *fdt, const char *name) @@ -539,6 +557,17 @@ const char *fdt_get_alias(const void *fdt, const char *name) return fdt_get_alias_namelen(fdt, name, strlen(name)); } +const char *fdt_get_symbol_namelen(const void *fdt, + const char *name, int namelen) +{ + return fdt_path_getprop_namelen(fdt, "/__symbols__", name, namelen, NULL); +} + +const char *fdt_get_symbol(const void *fdt, const char *name) +{ + return fdt_get_symbol_namelen(fdt, name, strlen(name)); +} + int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) { int pdepth = 0, p = 0; diff --git a/scripts/dtc/libfdt/libfdt.h b/scripts/dtc/libfdt/libfdt.h index 77ccff19911e..2d409d8e829b 100644 --- a/scripts/dtc/libfdt/libfdt.h +++ b/scripts/dtc/libfdt/libfdt.h @@ -524,10 +524,35 @@ int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen); * level matching the given component, differentiated only by unit * address). * + * If the path is not absolute (i.e. does not begin with '/'), the + * first component is treated as an alias. That is, the property by + * that name is looked up in the /aliases node, and the value of that + * property used in place of that first component. + * + * For example, for this small fragment + * + * / { + * aliases { + * i2c2 = &foo; // RHS compiles to "/soc@0/i2c@30a40000/eeprom@52" + * }; + * soc@0 { + * foo: i2c@30a40000 { + * bar: eeprom@52 { + * }; + * }; + * }; + * }; + * + * these would be equivalent: + * + * /soc@0/i2c@30a40000/eeprom@52 + * i2c2/eeprom@52 + * * returns: * structure block offset of the node with the requested path (>=0), on * success - * -FDT_ERR_BADPATH, given path does not begin with '/' or is invalid + * -FDT_ERR_BADPATH, given path does not begin with '/' and the first + * component is not a valid alias * -FDT_ERR_NOTFOUND, if the requested node does not exist * -FDT_ERR_BADMAGIC, * -FDT_ERR_BADVERSION, @@ -870,6 +895,42 @@ const char *fdt_get_alias_namelen(const void *fdt, const char *fdt_get_alias(const void *fdt, const char *name); /** + * fdt_get_symbol_namelen - get symbol based on substring + * @fdt: pointer to the device tree blob + * @name: name of the symbol to look up + * @namelen: number of characters of name to consider + * + * Identical to fdt_get_symbol(), but only examine the first @namelen + * characters of @name for matching the symbol name. + * + * Return: a pointer to the expansion of the symbol named @name, if it exists, + * NULL otherwise + */ +#ifndef SWIG /* Not available in Python */ +const char *fdt_get_symbol_namelen(const void *fdt, + const char *name, int namelen); +#endif + +/** + * fdt_get_symbol - retrieve the path referenced by a given symbol + * @fdt: pointer to the device tree blob + * @name: name of the symbol to look up + * + * fdt_get_symbol() retrieves the value of a given symbol. That is, + * the value of the property named @name in the node + * /__symbols__. Such a node exists only for a device tree blob that + * has been compiled with the -@ dtc option. Each property corresponds + * to a label appearing in the device tree source, with the name of + * the property being the label and the value being the full path of + * the node it is attached to. + * + * returns: + * a pointer to the expansion of the symbol named 'name', if it exists + * NULL, if the given symbol or the /__symbols__ node does not exist + */ +const char *fdt_get_symbol(const void *fdt, const char *name); + +/** * fdt_get_path - determine the full path of a node * @fdt: pointer to the device tree blob * @nodeoffset: offset of the node whose path to find @@ -1450,7 +1511,7 @@ int fdt_nop_node(void *fdt, int nodeoffset); * fdt_create_with_flags() begins the process of creating a new fdt with * the sequential write interface. * - * fdt creation process must end with fdt_finished() to produce a valid fdt. + * fdt creation process must end with fdt_finish() to produce a valid fdt. * * returns: * 0, on success @@ -1968,7 +2029,7 @@ static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, * address and size) to the value of the named property in the given * node, or creates a new property with that value if it does not * already exist. - * If "name" is not specified, a default "reg" is used. + * * Cell sizes are determined by parent's #address-cells and #size-cells. * * This function may insert data into the blob, and will therefore diff --git a/scripts/dtc/livetree.c b/scripts/dtc/livetree.c index f46a098d5ada..49f723002f85 100644 --- a/scripts/dtc/livetree.c +++ b/scripts/dtc/livetree.c @@ -36,27 +36,27 @@ void delete_labels(struct label **labels) label->deleted = 1; } -struct property *build_property(char *name, struct data val, +struct property *build_property(const char *name, struct data val, struct srcpos *srcpos) { struct property *new = xmalloc(sizeof(*new)); memset(new, 0, sizeof(*new)); - new->name = name; + new->name = xstrdup(name); new->val = val; new->srcpos = srcpos_copy(srcpos); return new; } -struct property *build_property_delete(char *name) +struct property *build_property_delete(const char *name) { struct property *new = xmalloc(sizeof(*new)); memset(new, 0, sizeof(*new)); - new->name = name; + new->name = xstrdup(name); new->deleted = 1; return new; @@ -116,11 +116,11 @@ struct node *build_node_delete(struct srcpos *srcpos) return new; } -struct node *name_node(struct node *node, char *name) +struct node *name_node(struct node *node, const char *name) { assert(node->name == NULL); - node->name = name; + node->name = xstrdup(name); return node; } @@ -250,6 +250,7 @@ struct node * add_orphan_node(struct node *dt, struct node *new_node, char *ref) name_node(new_node, "__overlay__"); node = build_node(p, new_node, NULL); name_node(node, name); + free(name); add_child(dt, node); return dt; @@ -440,7 +441,7 @@ cell_t propval_cell(struct property *prop) cell_t propval_cell_n(struct property *prop, unsigned int n) { - assert(prop->val.len / sizeof(cell_t) >= n); + assert(prop->val.len / sizeof(cell_t) > n); return fdt32_to_cpu(*((fdt32_t *)prop->val.val + n)); } @@ -616,10 +617,25 @@ struct node *get_node_by_ref(struct node *tree, const char *ref) return target; } +static void add_phandle_property(struct node *node, + const char *name, int format) +{ + struct data d; + + if (!(phandle_format & format)) + return; + if (get_property(node, name)) + return; + + d = data_add_marker(empty_data, TYPE_UINT32, NULL); + d = data_append_cell(d, node->phandle); + + add_property(node, build_property(name, d, NULL)); +} + cell_t get_node_phandle(struct node *root, struct node *node) { static cell_t phandle = 1; /* FIXME: ick, static local */ - struct data d = empty_data; if (phandle_is_valid(node->phandle)) return node->phandle; @@ -629,16 +645,8 @@ cell_t get_node_phandle(struct node *root, struct node *node) node->phandle = phandle; - d = data_add_marker(d, TYPE_UINT32, NULL); - d = data_append_cell(d, phandle); - - if (!get_property(node, "linux,phandle") - && (phandle_format & PHANDLE_LEGACY)) - add_property(node, build_property("linux,phandle", d, NULL)); - - if (!get_property(node, "phandle") - && (phandle_format & PHANDLE_EPAPR)) - add_property(node, build_property("phandle", d, NULL)); + add_phandle_property(node, "linux,phandle", PHANDLE_LEGACY); + add_phandle_property(node, "phandle", PHANDLE_EPAPR); /* If the node *does* have a phandle property, we must * be dealing with a self-referencing phandle, which will be @@ -808,18 +816,18 @@ void sort_tree(struct dt_info *dti) } /* utility helper to avoid code duplication */ -static struct node *build_and_name_child_node(struct node *parent, char *name) +static struct node *build_and_name_child_node(struct node *parent, const char *name) { struct node *node; node = build_node(NULL, NULL, NULL); - name_node(node, xstrdup(name)); + name_node(node, name); add_child(parent, node); return node; } -static struct node *build_root_node(struct node *dt, char *name) +static struct node *build_root_node(struct node *dt, const char *name) { struct node *an; @@ -1040,7 +1048,7 @@ static void generate_local_fixups_tree_internal(struct dt_info *dti, generate_local_fixups_tree_internal(dti, lfn, c); } -void generate_label_tree(struct dt_info *dti, char *name, bool allocph) +void generate_label_tree(struct dt_info *dti, const char *name, bool allocph) { if (!any_label_tree(dti, dti->dt)) return; @@ -1048,7 +1056,7 @@ void generate_label_tree(struct dt_info *dti, char *name, bool allocph) dti->dt, allocph); } -void generate_fixups_tree(struct dt_info *dti, char *name) +void generate_fixups_tree(struct dt_info *dti, const char *name) { if (!any_fixup_tree(dti, dti->dt)) return; @@ -1056,7 +1064,7 @@ void generate_fixups_tree(struct dt_info *dti, char *name) dti->dt); } -void generate_local_fixups_tree(struct dt_info *dti, char *name) +void generate_local_fixups_tree(struct dt_info *dti, const char *name) { if (!any_local_fixup_tree(dti, dti->dt)) return; diff --git a/scripts/dtc/srcpos.c b/scripts/dtc/srcpos.c index 4fdb22a019bd..8e4d18a90b47 100644 --- a/scripts/dtc/srcpos.c +++ b/scripts/dtc/srcpos.c @@ -3,7 +3,9 @@ * Copyright 2007 Jon Loeliger, Freescale Semiconductor, Inc. */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include <stdio.h> @@ -311,8 +313,8 @@ srcpos_string(struct srcpos *pos) static char * srcpos_string_comment(struct srcpos *pos, bool first_line, int level) { - char *pos_str, *fname, *first, *rest; - bool fresh_fname = false; + char *pos_str, *fresh_fname = NULL, *first, *rest; + const char *fname; if (!pos) { if (level > 1) { @@ -330,9 +332,9 @@ srcpos_string_comment(struct srcpos *pos, bool first_line, int level) else if (level > 1) fname = pos->file->name; else { - fname = shorten_to_initial_path(pos->file->name); - if (fname) - fresh_fname = true; + fresh_fname = shorten_to_initial_path(pos->file->name); + if (fresh_fname) + fname = fresh_fname; else fname = pos->file->name; } @@ -346,7 +348,7 @@ srcpos_string_comment(struct srcpos *pos, bool first_line, int level) first_line ? pos->first_line : pos->last_line); if (fresh_fname) - free(fname); + free(fresh_fname); if (pos->next != NULL) { rest = srcpos_string_comment(pos->next, first_line, level); diff --git a/scripts/dtc/treesource.c b/scripts/dtc/treesource.c index 33fedee82d58..ae15839ba6a5 100644 --- a/scripts/dtc/treesource.c +++ b/scripts/dtc/treesource.c @@ -139,6 +139,28 @@ static const char *delim_end[] = { [TYPE_STRING] = "", }; +static void add_string_markers(struct property *prop) +{ + int l, len = prop->val.len; + const char *p = prop->val.val; + + for (l = strlen(p) + 1; l < len; l += strlen(p + l) + 1) { + struct marker *m, **nextp; + + m = xmalloc(sizeof(*m)); + m->offset = l; + m->type = TYPE_STRING; + m->ref = NULL; + m->next = NULL; + + /* Find the end of the markerlist */ + nextp = &prop->val.markers; + while (*nextp) + nextp = &((*nextp)->next); + *nextp = m; + } +} + static enum markertype guess_value_type(struct property *prop) { int len = prop->val.len; @@ -164,6 +186,8 @@ static enum markertype guess_value_type(struct property *prop) if ((p[len-1] == '\0') && (nnotstring == 0) && (nnul <= (len-nnul)) && (nnotstringlbl == 0)) { + if (nnul > 1) + add_string_markers(prop); return TYPE_STRING; } else if (((len % sizeof(cell_t)) == 0) && (nnotcelllbl == 0)) { return TYPE_UINT32; @@ -241,6 +265,8 @@ static void write_propval(FILE *f, struct property *prop) } else { write_propval_int(f, p, chunk_len, 4); } + if (data_len > chunk_len) + fputc(' ', f); break; case TYPE_UINT64: write_propval_int(f, p, chunk_len, 8); diff --git a/scripts/dtc/util.h b/scripts/dtc/util.h index 9d38edee9736..b448cd79efd3 100644 --- a/scripts/dtc/util.h +++ b/scripts/dtc/util.h @@ -13,7 +13,9 @@ */ #ifdef __GNUC__ -#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) +#ifdef __MINGW_PRINTF_FORMAT +#define PRINTF(i, j) __attribute__((format (__MINGW_PRINTF_FORMAT, i, j))) +#elif __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) #define PRINTF(i, j) __attribute__((format (gnu_printf, i, j))) #else #define PRINTF(i, j) __attribute__((format (printf, i, j))) @@ -65,7 +67,7 @@ extern char *xstrndup(const char *s, size_t len); extern int PRINTF(2, 3) xasprintf(char **strp, const char *fmt, ...); extern int PRINTF(2, 3) xasprintf_append(char **strp, const char *fmt, ...); -extern int xavsprintf_append(char **strp, const char *fmt, va_list ap); +extern int PRINTF(2, 0) xavsprintf_append(char **strp, const char *fmt, va_list ap); extern char *join_path(const char *path, const char *name); /** diff --git a/scripts/dtc/version_gen.h b/scripts/dtc/version_gen.h index 99614ec1a289..bf81ce593685 100644 --- a/scripts/dtc/version_gen.h +++ b/scripts/dtc/version_gen.h @@ -1 +1 @@ -#define DTC_VERSION "DTC 1.6.1-gabbd523b" +#define DTC_VERSION "DTC 1.7.0-gbcd02b52" diff --git a/scripts/export_report.pl b/scripts/export_report.pl deleted file mode 100755 index feb3d5542a62..000000000000 --- a/scripts/export_report.pl +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env perl -# SPDX-License-Identifier: GPL-2.0-only -# -# (C) Copyright IBM Corporation 2006. -# Author : Ram Pai (linuxram@us.ibm.com) -# -# Usage: export_report.pl -k Module.symvers [-o report_file ] -f *.mod.c -# - -use warnings; -use Getopt::Std; -use strict; - -sub numerically { - my $no1 = (split /\s+/, $a)[1]; - my $no2 = (split /\s+/, $b)[1]; - return $no1 <=> $no2; -} - -sub alphabetically { - my ($module1, $value1) = @{$a}; - my ($module2, $value2) = @{$b}; - return $value1 <=> $value2 || $module2 cmp $module1; -} - -sub print_depends_on { - my ($href) = @_; - print "\n"; - for my $mod (sort keys %$href) { - my $list = $href->{$mod}; - print "\t$mod:\n"; - foreach my $sym (sort numerically @{$list}) { - my ($symbol, $no) = split /\s+/, $sym; - printf("\t\t%-25s\n", $symbol); - } - print "\n"; - } - print "\n"; - print "~"x80 , "\n"; -} - -sub usage { - print "Usage: @_ -h -k Module.symvers [ -o outputfile ] \n", - "\t-f: treat all the non-option argument as .mod.c files. ", - "Recommend using this as the last option\n", - "\t-h: print detailed help\n", - "\t-k: the path to Module.symvers file. By default uses ", - "the file from the current directory\n", - "\t-o outputfile: output the report to outputfile\n"; - exit 0; -} - -sub collectcfiles { - my @file; - open my $fh, '< modules.order' or die "cannot open modules.order: $!\n"; - while (<$fh>) { - s/\.ko$/.mod.c/; - push (@file, $_) - } - close($fh); - chomp @file; - return @file; -} - -my (%SYMBOL, %MODULE, %opt, @allcfiles); - -if (not getopts('hk:o:f',\%opt) or defined $opt{'h'}) { - usage($0); -} - -if (defined $opt{'f'}) { - @allcfiles = @ARGV; -} else { - @allcfiles = collectcfiles(); -} - -if (not defined $opt{'k'}) { - $opt{'k'} = "Module.symvers"; -} - -open (my $module_symvers, '<', $opt{'k'}) - or die "Sorry, cannot open $opt{'k'}: $!\n"; - -if (defined $opt{'o'}) { - open (my $out, '>', $opt{'o'}) - or die "Sorry, cannot open $opt{'o'} $!\n"; - - select $out; -} - -# -# collect all the symbols and their attributes from the -# Module.symvers file -# -while ( <$module_symvers> ) { - chomp; - my (undef, $symbol, $module, $gpl, $namespace) = split('\t'); - $SYMBOL { $symbol } = [ $module , "0" , $symbol, $gpl]; -} -close($module_symvers); - -# -# collect the usage count of each symbol. -# -my $modversion_warnings = 0; - -foreach my $thismod (@allcfiles) { - my $module; - - unless (open ($module, '<', $thismod)) { - warn "Sorry, cannot open $thismod: $!\n"; - next; - } - - my $state=0; - while ( <$module> ) { - chomp; - if ($state == 0) { - $state = 1 if ($_ =~ /static const struct modversion_info/); - next; - } - if ($state == 1) { - $state = 2 if ($_ =~ /__attribute__\(\(section\("__versions"\)\)\)/); - next; - } - if ($state == 2) { - if ( $_ !~ /0x[0-9a-f]+,/ ) { - next; - } - my $sym = (split /([,"])/,)[4]; - my ($module, $value, $symbol, $gpl) = @{$SYMBOL{$sym}}; - $SYMBOL{ $sym } = [ $module, $value+1, $symbol, $gpl]; - push(@{$MODULE{$thismod}} , $sym); - } - } - if ($state != 2) { - warn "WARNING:$thismod is not built with CONFIG_MODVERSIONS enabled\n"; - $modversion_warnings++; - } - close($module); -} - -print "\tThis file reports the exported symbols usage patterns by in-tree\n", - "\t\t\t\tmodules\n"; -printf("%s\n\n\n","x"x80); -printf("\t\t\t\tINDEX\n\n\n"); -printf("SECTION 1: Usage counts of all exported symbols\n"); -printf("SECTION 2: List of modules and the exported symbols they use\n"); -printf("%s\n\n\n","x"x80); -printf("SECTION 1:\tThe exported symbols and their usage count\n\n"); -printf("%-25s\t%-25s\t%-5s\t%-25s\n", "Symbol", "Module", "Usage count", - "export type"); - -# -# print the list of unused exported symbols -# -foreach my $list (sort alphabetically values(%SYMBOL)) { - my ($module, $value, $symbol, $gpl) = @{$list}; - printf("%-25s\t%-25s\t%-10s\t", $symbol, $module, $value); - if (defined $gpl) { - printf("%-25s\n",$gpl); - } else { - printf("\n"); - } -} -printf("%s\n\n\n","x"x80); - -printf("SECTION 2:\n\tThis section reports export-symbol-usage of in-kernel -modules. Each module lists the modules, and the symbols from that module that -it uses. Each listed symbol reports the number of modules using it\n"); - -print "\nNOTE: Got $modversion_warnings CONFIG_MODVERSIONS warnings\n\n" - if $modversion_warnings; - -print "~"x80 , "\n"; -for my $thismod (sort keys %MODULE) { - my $list = $MODULE{$thismod}; - my %depends; - $thismod =~ s/\.mod\.c/.ko/; - print "\t\t\t$thismod\n"; - foreach my $symbol (@{$list}) { - my ($module, $value, undef, $gpl) = @{$SYMBOL{$symbol}}; - push (@{$depends{"$module"}}, "$symbol $value"); - } - print_depends_on(\%depends); -} diff --git a/scripts/extract-fwblobs b/scripts/extract-fwblobs new file mode 100755 index 000000000000..53729124e5a0 --- /dev/null +++ b/scripts/extract-fwblobs @@ -0,0 +1,30 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# ----------------------------------------------------------------------------- +# Extracts the vmlinux built-in firmware blobs - requires a non-stripped image +# ----------------------------------------------------------------------------- + +if [ -z "$1" ]; then + echo "Must provide a non-stripped vmlinux as argument" + exit 1 +fi + +read -r RD_ADDR_HEX RD_OFF_HEX <<< "$( readelf -SW "$1" |\ +grep -w rodata | awk '{print "0x"$5" 0x"$6}' )" + +FW_SYMS="$(readelf -sW "$1" |\ +awk -n '/fw_end/ { end=$2 ; print name " 0x" start " 0x" end; } { start=$2; name=$8; }')" + +while IFS= read -r entry; do + read -r FW_NAME FW_ADDR_ST_HEX FW_ADDR_END_HEX <<< "$entry" + + # Notice kernel prepends _fw_ and appends _bin to the FW name + # in rodata; hence we hereby filter that out. + FW_NAME=${FW_NAME:4:-4} + + FW_OFFSET="$(printf "%d" $((FW_ADDR_ST_HEX - RD_ADDR_HEX + RD_OFF_HEX)))" + FW_SIZE="$(printf "%d" $((FW_ADDR_END_HEX - FW_ADDR_ST_HEX)))" + + dd if="$1" of="./${FW_NAME}" bs="${FW_SIZE}" count=1 iflag=skip_bytes skip="${FW_OFFSET}" +done <<< "${FW_SYMS}" diff --git a/scripts/faddr2line b/scripts/faddr2line index 587415a52b6f..1fa6beef9f97 100755 --- a/scripts/faddr2line +++ b/scripts/faddr2line @@ -85,15 +85,17 @@ command -v ${ADDR2LINE} >/dev/null 2>&1 || die "${ADDR2LINE} isn't installed" # init/main.c! This only works for vmlinux. Otherwise it falls back to # printing the absolute path. find_dir_prefix() { - local objfile=$1 - - local start_kernel_addr=$(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' | + local start_kernel_addr=$(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | ${AWK} '$8 == "start_kernel" {printf "0x%s", $2}') [[ -z $start_kernel_addr ]] && return - local file_line=$(${ADDR2LINE} -e $objfile $start_kernel_addr) - [[ -z $file_line ]] && return + run_addr2line ${start_kernel_addr} "" + [[ -z $ADDR2LINE_OUT ]] && return + local file_line=${ADDR2LINE_OUT#* at } + if [[ -z $file_line ]] || [[ $file_line = $ADDR2LINE_OUT ]]; then + return + fi local prefix=${file_line%init/main.c:*} if [[ -z $prefix ]] || [[ $prefix = $file_line ]]; then return @@ -103,6 +105,71 @@ find_dir_prefix() { return 0 } +run_readelf() { + local objfile=$1 + local out=$(${READELF} --file-header --section-headers --symbols --wide $objfile) + + # This assumes that readelf first prints the file header, then the section headers, then the symbols. + # Note: It seems that GNU readelf does not prefix section headers with the "There are X section headers" + # line when multiple options are given, so let's also match with the "Section Headers:" line. + ELF_FILEHEADER=$(echo "${out}" | sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/q;p') + ELF_SECHEADERS=$(echo "${out}" | sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/,$p' | sed -n '/Symbol table .* contains [0-9]* entries:/q;p') + ELF_SYMS=$(echo "${out}" | sed -n '/Symbol table .* contains [0-9]* entries:/,$p') +} + +check_vmlinux() { + # vmlinux uses absolute addresses in the section table rather than + # section offsets. + IS_VMLINUX=0 + local file_type=$(echo "${ELF_FILEHEADER}" | + ${AWK} '$1 == "Type:" { print $2; exit }') + if [[ $file_type = "EXEC" ]] || [[ $file_type == "DYN" ]]; then + IS_VMLINUX=1 + fi +} + +init_addr2line() { + local objfile=$1 + + check_vmlinux + + ADDR2LINE_ARGS="--functions --pretty-print --inlines --addresses --exe=$objfile" + if [[ $IS_VMLINUX = 1 ]]; then + # If the executable file is vmlinux, we don't pass section names to + # addr2line, so we can launch it now as a single long-running process. + coproc ADDR2LINE_PROC (${ADDR2LINE} ${ADDR2LINE_ARGS}) + fi +} + +run_addr2line() { + local addr=$1 + local sec_name=$2 + + if [[ $IS_VMLINUX = 1 ]]; then + # We send to the addr2line process: (1) the address, then (2) a sentinel + # value, i.e., something that can't be interpreted as a valid address + # (i.e., ","). This causes addr2line to write out: (1) the answer for + # our address, then (2) either "?? ??:0" or "0x0...0: ..." (if + # using binutils' addr2line), or "," (if using LLVM's addr2line). + echo ${addr} >& "${ADDR2LINE_PROC[1]}" + echo "," >& "${ADDR2LINE_PROC[1]}" + local first_line + read -r first_line <& "${ADDR2LINE_PROC[0]}" + ADDR2LINE_OUT=$(echo "${first_line}" | sed 's/^0x[0-9a-fA-F]*: //') + while read -r line <& "${ADDR2LINE_PROC[0]}"; do + if [[ "$line" == "?? ??:0" ]] || [[ "$line" == "," ]] || [[ $(echo "$line" | ${GREP} "^0x00*: ") ]]; then + break + fi + ADDR2LINE_OUT+=$'\n'$(echo "$line" | sed 's/^0x[0-9a-fA-F]*: //') + done + else + # Run addr2line as a single invocation. + local sec_arg + [[ -z $sec_name ]] && sec_arg="" || sec_arg="--section=${sec_name}" + ADDR2LINE_OUT=$(${ADDR2LINE} ${ADDR2LINE_ARGS} ${sec_arg} ${addr} | sed 's/^0x[0-9a-fA-F]*: //') + fi +} + __faddr2line() { local objfile=$1 local func_addr=$2 @@ -113,8 +180,6 @@ __faddr2line() { local func_offset=${func_addr#*+} func_offset=${func_offset%/*} local user_size= - local file_type - local is_vmlinux=0 [[ $func_addr =~ "/" ]] && user_size=${func_addr#*/} if [[ -z $sym_name ]] || [[ -z $func_offset ]] || [[ $sym_name = $func_addr ]]; then @@ -123,14 +188,6 @@ __faddr2line() { return fi - # vmlinux uses absolute addresses in the section table rather than - # section offsets. - local file_type=$(${READELF} --file-header $objfile | - ${AWK} '$1 == "Type:" { print $2; exit }') - if [[ $file_type = "EXEC" ]] || [[ $file_type == "DYN" ]]; then - is_vmlinux=1 - fi - # Go through each of the object's symbols which match the func name. # In rare cases there might be duplicates, in which case we print all # matches. @@ -143,8 +200,7 @@ __faddr2line() { local sec_name # Get the section size: - sec_size=$(${READELF} --section-headers --wide $objfile | - sed 's/\[ /\[/' | + sec_size=$(echo "${ELF_SECHEADERS}" | sed 's/\[ /\[/' | ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print "0x" $6; exit }') if [[ -z $sec_size ]]; then @@ -154,8 +210,7 @@ __faddr2line() { fi # Get the section name: - sec_name=$(${READELF} --section-headers --wide $objfile | - sed 's/\[ /\[/' | + sec_name=$(echo "${ELF_SECHEADERS}" | sed 's/\[ /\[/' | ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print $2; exit }') if [[ -z $sec_name ]]; then @@ -197,7 +252,7 @@ __faddr2line() { found=2 break fi - done < <(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' | ${AWK} -v sec=$sym_sec '$7 == sec' | sort --key=2) + done < <(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | ${AWK} -v sec=$sym_sec '$7 == sec' | sort --key=2) if [[ $found = 0 ]]; then warn "can't find symbol: sym_name: $sym_name sym_sec: $sym_sec sym_addr: $sym_addr sym_elf_size: $sym_elf_size" @@ -249,9 +304,8 @@ __faddr2line() { # Pass section address to addr2line and strip absolute paths # from the output: - local args="--functions --pretty-print --inlines --exe=$objfile" - [[ $is_vmlinux = 0 ]] && args="$args --section=$sec_name" - local output=$(${ADDR2LINE} $args $addr | sed "s; $dir_prefix\(\./\)*; ;") + run_addr2line $addr $sec_name + local output=$(echo "${ADDR2LINE_OUT}" | sed "s; $dir_prefix\(\./\)*; ;") [[ -z $output ]] && continue # Default output (non --list): @@ -278,7 +332,7 @@ __faddr2line() { DONE=1 - done < <(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' | ${AWK} -v fn=$sym_name '$8 == fn') + done < <(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | ${AWK} -v fn=$sym_name '$8 == fn') } [[ $# -lt 2 ]] && usage @@ -291,10 +345,14 @@ LIST=0 [[ ! -f $objfile ]] && die "can't find objfile $objfile" shift -${READELF} --section-headers --wide $objfile | ${GREP} -q '\.debug_info' || die "CONFIG_DEBUG_INFO not enabled" +run_readelf $objfile + +echo "${ELF_SECHEADERS}" | ${GREP} -q '\.debug_info' || die "CONFIG_DEBUG_INFO not enabled" + +init_addr2line $objfile DIR_PREFIX=supercalifragilisticexpialidocious -find_dir_prefix $objfile +find_dir_prefix FIRST=1 while [[ $# -gt 0 ]]; do diff --git a/scripts/find-unused-docs.sh b/scripts/find-unused-docs.sh index ee6a50e33aba..d6d397fbf917 100755 --- a/scripts/find-unused-docs.sh +++ b/scripts/find-unused-docs.sh @@ -54,7 +54,7 @@ for file in `find $1 -name '*.c'`; do if [[ ${FILES_INCLUDED[$file]+_} ]]; then continue; fi - str=$(scripts/kernel-doc -export "$file" 2>/dev/null) + str=$(PYTHONDONTWRITEBYTECODE=1 scripts/kernel-doc -export "$file" 2>/dev/null) if [[ -n "$str" ]]; then echo "$file" fi diff --git a/scripts/gcc-plugins/Kconfig b/scripts/gcc-plugins/Kconfig index e383cda05367..6b34ba19358d 100644 --- a/scripts/gcc-plugins/Kconfig +++ b/scripts/gcc-plugins/Kconfig @@ -19,16 +19,6 @@ menuconfig GCC_PLUGINS if GCC_PLUGINS -config GCC_PLUGIN_SANCOV - bool - # Plugin can be removed once the kernel only supports GCC 6+ - depends on !CC_HAS_SANCOV_TRACE_PC - help - This plugin inserts a __sanitizer_cov_trace_pc() call at the start of - basic blocks. It supports all gcc versions with plugin support (from - gcc-4.5 on). It is based on the commit "Add fuzzing coverage support" - by Dmitry Vyukov <dvyukov@google.com>. - config GCC_PLUGIN_LATENT_ENTROPY bool "Generate some entropy during boot and runtime" help @@ -46,8 +36,4 @@ config GCC_PLUGIN_LATENT_ENTROPY * https://grsecurity.net/ * https://pax.grsecurity.net/ -config GCC_PLUGIN_ARM_SSP_PER_TASK - bool - depends on GCC_PLUGINS && ARM - endif diff --git a/scripts/gcc-plugins/Makefile b/scripts/gcc-plugins/Makefile index 320afd3cf8e8..05b14aba41ef 100644 --- a/scripts/gcc-plugins/Makefile +++ b/scripts/gcc-plugins/Makefile @@ -66,3 +66,7 @@ quiet_cmd_plugin_cxx_o_c = HOSTCXX $@ $(plugin-objs): $(obj)/%.o: $(src)/%.c FORCE $(call if_changed_dep,plugin_cxx_o_c) + +$(obj)/../../include/generated/gcc-plugins.h: $(plugin-single) $(plugin-multi) FORCE + $(call if_changed,touch) +always-y += ../../include/generated/gcc-plugins.h diff --git a/scripts/gcc-plugins/arm_ssp_per_task_plugin.c b/scripts/gcc-plugins/arm_ssp_per_task_plugin.c deleted file mode 100644 index 7328d037f975..000000000000 --- a/scripts/gcc-plugins/arm_ssp_per_task_plugin.c +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include "gcc-common.h" - -__visible int plugin_is_GPL_compatible; - -static unsigned int canary_offset; - -static unsigned int arm_pertask_ssp_rtl_execute(void) -{ - rtx_insn *insn; - - for (insn = get_insns(); insn; insn = NEXT_INSN(insn)) { - const char *sym; - rtx body; - rtx current; - - /* - * Find a SET insn involving a SYMBOL_REF to __stack_chk_guard - */ - if (!INSN_P(insn)) - continue; - body = PATTERN(insn); - if (GET_CODE(body) != SET || - GET_CODE(SET_SRC(body)) != SYMBOL_REF) - continue; - sym = XSTR(SET_SRC(body), 0); - if (strcmp(sym, "__stack_chk_guard")) - continue; - - /* - * Replace the source of the SET insn with an expression that - * produces the address of the current task's stack canary value - */ - current = gen_reg_rtx(Pmode); - - emit_insn_before(gen_load_tp_hard(current), insn); - - SET_SRC(body) = gen_rtx_PLUS(Pmode, current, - GEN_INT(canary_offset)); - } - return 0; -} - -#define PASS_NAME arm_pertask_ssp_rtl - -#define NO_GATE -#include "gcc-generate-rtl-pass.h" - -#if BUILDING_GCC_VERSION >= 9000 -static bool no(void) -{ - return false; -} - -static void arm_pertask_ssp_start_unit(void *gcc_data, void *user_data) -{ - targetm.have_stack_protect_combined_set = no; - targetm.have_stack_protect_combined_test = no; -} -#endif - -__visible int plugin_init(struct plugin_name_args *plugin_info, - struct plugin_gcc_version *version) -{ - const char * const plugin_name = plugin_info->base_name; - const int argc = plugin_info->argc; - const struct plugin_argument *argv = plugin_info->argv; - int i; - - if (!plugin_default_version_check(version, &gcc_version)) { - error(G_("incompatible gcc/plugin versions")); - return 1; - } - - for (i = 0; i < argc; ++i) { - if (!strcmp(argv[i].key, "disable")) - return 0; - - /* all remaining options require a value */ - if (!argv[i].value) { - error(G_("no value supplied for option '-fplugin-arg-%s-%s'"), - plugin_name, argv[i].key); - return 1; - } - - if (!strcmp(argv[i].key, "offset")) { - canary_offset = atoi(argv[i].value); - continue; - } - error(G_("unknown option '-fplugin-arg-%s-%s'"), - plugin_name, argv[i].key); - return 1; - } - - PASS_INFO(arm_pertask_ssp_rtl, "expand", 1, PASS_POS_INSERT_AFTER); - - register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, - NULL, &arm_pertask_ssp_rtl_pass_info); - -#if BUILDING_GCC_VERSION >= 9000 - register_callback(plugin_info->base_name, PLUGIN_START_UNIT, - arm_pertask_ssp_start_unit, NULL); -#endif - - return 0; -} diff --git a/scripts/gcc-plugins/gcc-common.h b/scripts/gcc-plugins/gcc-common.h index 1ae39b9f4a95..6cb6d1051815 100644 --- a/scripts/gcc-plugins/gcc-common.h +++ b/scripts/gcc-plugins/gcc-common.h @@ -3,11 +3,7 @@ #define GCC_COMMON_H_INCLUDED #include "bversion.h" -#if BUILDING_GCC_VERSION >= 6000 #include "gcc-plugin.h" -#else -#include "plugin.h" -#endif #include "plugin-version.h" #include "config.h" #include "system.h" @@ -39,9 +35,7 @@ #include "hash-map.h" -#if BUILDING_GCC_VERSION >= 7000 #include "memmodel.h" -#endif #include "emit-rtl.h" #include "debug.h" #include "target.h" @@ -62,11 +56,7 @@ #include "pass_manager.h" #include "predict.h" #include "ipa-utils.h" - -#if BUILDING_GCC_VERSION >= 8000 #include "stringpool.h" -#endif - #include "attribs.h" #include "varasm.h" #include "stor-layout.h" @@ -78,10 +68,7 @@ #include "context.h" #include "tree-ssa-alias.h" #include "tree-ssa.h" -#include "stringpool.h" -#if BUILDING_GCC_VERSION >= 7000 #include "tree-vrp.h" -#endif #include "tree-ssanames.h" #include "print-tree.h" #include "tree-eh.h" @@ -128,6 +115,38 @@ static inline tree build_const_char_string(int len, const char *str) return cstr; } +static inline void __add_type_attr(tree type, const char *attr, tree args) +{ + tree oldattr; + + if (type == NULL_TREE) + return; + oldattr = lookup_attribute(attr, TYPE_ATTRIBUTES(type)); + if (oldattr != NULL_TREE) { + gcc_assert(TREE_VALUE(oldattr) == args || TREE_VALUE(TREE_VALUE(oldattr)) == TREE_VALUE(args)); + return; + } + + TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type)); + TYPE_ATTRIBUTES(type) = tree_cons(get_identifier(attr), args, TYPE_ATTRIBUTES(type)); +} + +static inline void add_type_attr(tree type, const char *attr, tree args) +{ + tree main_variant = TYPE_MAIN_VARIANT(type); + + __add_type_attr(TYPE_CANONICAL(type), attr, args); + __add_type_attr(TYPE_CANONICAL(main_variant), attr, args); + __add_type_attr(main_variant, attr, args); + + for (type = TYPE_NEXT_VARIANT(main_variant); type; type = TYPE_NEXT_VARIANT(type)) { + if (!lookup_attribute(attr, TYPE_ATTRIBUTES(type))) + TYPE_ATTRIBUTES(type) = TYPE_ATTRIBUTES(main_variant); + + __add_type_attr(TYPE_CANONICAL(type), attr, args); + } +} + #define PASS_INFO(NAME, REF, ID, POS) \ struct register_pass_info NAME##_pass_info = { \ .pass = make_##NAME##_pass(), \ @@ -154,16 +173,6 @@ static inline opt_pass *get_pass_for_id(int id) return g->get_passes()->get_pass_for_id(id); } -#if BUILDING_GCC_VERSION < 6000 -/* gimple related */ -template <> -template <> -inline bool is_a_helper<const gassign *>::test(const_gimple gs) -{ - return gs->code == GIMPLE_ASSIGN; -} -#endif - #define TODO_verify_ssa TODO_verify_il #define TODO_verify_flow TODO_verify_il #define TODO_verify_stmts TODO_verify_il @@ -186,7 +195,6 @@ static inline const char *get_decl_section_name(const_tree decl) #define varpool_get_node(decl) varpool_node::get(decl) #define dump_varpool_node(file, node) (node)->dump(file) -#if BUILDING_GCC_VERSION >= 8000 #define cgraph_create_edge(caller, callee, call_stmt, count, freq) \ (caller)->create_edge((callee), (call_stmt), (count)) @@ -194,15 +202,6 @@ static inline const char *get_decl_section_name(const_tree decl) old_call_stmt, call_stmt, count, freq, reason) \ (caller)->create_edge_including_clones((callee), \ (old_call_stmt), (call_stmt), (count), (reason)) -#else -#define cgraph_create_edge(caller, callee, call_stmt, count, freq) \ - (caller)->create_edge((callee), (call_stmt), (count), (freq)) - -#define cgraph_create_edge_including_clones(caller, callee, \ - old_call_stmt, call_stmt, count, freq, reason) \ - (caller)->create_edge_including_clones((callee), \ - (old_call_stmt), (call_stmt), (count), (freq), (reason)) -#endif typedef struct cgraph_node *cgraph_node_ptr; typedef struct cgraph_edge *cgraph_edge_p; @@ -298,14 +297,12 @@ static inline void cgraph_call_edge_duplication_hooks(cgraph_edge *cs1, cgraph_e symtab->call_edge_duplication_hooks(cs1, cs2); } -#if BUILDING_GCC_VERSION >= 6000 typedef gimple *gimple_ptr; typedef const gimple *const_gimple_ptr; #define gimple gimple_ptr #define const_gimple const_gimple_ptr #undef CONST_CAST_GIMPLE #define CONST_CAST_GIMPLE(X) CONST_CAST(gimple, (X)) -#endif /* gimple related */ static inline gimple gimple_build_assign_with_ops(enum tree_code subcode, tree lhs, tree op1, tree op2 MEM_STAT_DECL) @@ -405,15 +402,7 @@ static inline void ipa_remove_stmt_references(symtab_node *referring_node, gimpl referring_node->remove_stmt_references(stmt); } -#if BUILDING_GCC_VERSION < 6000 -#define get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep, keep_aligning) \ - get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, pvolatilep, keep_aligning) -#define gen_rtx_set(ARG0, ARG1) gen_rtx_SET(VOIDmode, (ARG0), (ARG1)) -#endif - -#if BUILDING_GCC_VERSION >= 6000 #define gen_rtx_set(ARG0, ARG1) gen_rtx_SET((ARG0), (ARG1)) -#endif #ifdef __cplusplus static inline void debug_tree(const_tree t) @@ -430,15 +419,8 @@ static inline void debug_gimple_stmt(const_gimple s) #define debug_gimple_stmt(s) debug_gimple_stmt(CONST_CAST_GIMPLE(s)) #endif -#if BUILDING_GCC_VERSION >= 7000 #define get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep, keep_aligning) \ get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep) -#endif - -#if BUILDING_GCC_VERSION < 7000 -#define SET_DECL_ALIGN(decl, align) DECL_ALIGN(decl) = (align) -#define SET_DECL_MODE(decl, mode) DECL_MODE(decl) = (mode) -#endif #if BUILDING_GCC_VERSION >= 14000 #define last_stmt(x) last_nondebug_stmt(x) diff --git a/scripts/gcc-plugins/randomize_layout_plugin.c b/scripts/gcc-plugins/randomize_layout_plugin.c index 746ff2d272f2..ff65a4f87f24 100644 --- a/scripts/gcc-plugins/randomize_layout_plugin.c +++ b/scripts/gcc-plugins/randomize_layout_plugin.c @@ -19,10 +19,6 @@ #include "gcc-common.h" #include "randomize_layout_seed.h" -#if BUILDING_GCC_MAJOR < 4 || (BUILDING_GCC_MAJOR == 4 && BUILDING_GCC_MINOR < 7) -#error "The RANDSTRUCT plugin requires GCC 4.7 or newer." -#endif - #define ORIG_TYPE_NAME(node) \ (TYPE_NAME(TYPE_MAIN_VARIANT(node)) != NULL_TREE ? ((const unsigned char *)IDENTIFIER_POINTER(TYPE_NAME(TYPE_MAIN_VARIANT(node)))) : (const unsigned char *)"anonymous") @@ -77,6 +73,9 @@ static tree handle_randomize_layout_attr(tree *node, tree name, tree args, int f if (TYPE_P(*node)) { type = *node; + } else if (TREE_CODE(*node) == FIELD_DECL) { + *no_add_attrs = false; + return NULL_TREE; } else { gcc_assert(TREE_CODE(*node) == TYPE_DECL); type = TREE_TYPE(*node); @@ -348,35 +347,18 @@ static int relayout_struct(tree type) shuffle(type, (tree *)newtree, shuffle_length); - /* - * set up a bogus anonymous struct field designed to error out on unnamed struct initializers - * as gcc provides no other way to detect such code - */ - list = make_node(FIELD_DECL); - TREE_CHAIN(list) = newtree[0]; - TREE_TYPE(list) = void_type_node; - DECL_SIZE(list) = bitsize_zero_node; - DECL_NONADDRESSABLE_P(list) = 1; - DECL_FIELD_BIT_OFFSET(list) = bitsize_zero_node; - DECL_SIZE_UNIT(list) = size_zero_node; - DECL_FIELD_OFFSET(list) = size_zero_node; - DECL_CONTEXT(list) = type; - // to satisfy the constify plugin - TREE_READONLY(list) = 1; - for (i = 0; i < num_fields - 1; i++) TREE_CHAIN(newtree[i]) = newtree[i+1]; TREE_CHAIN(newtree[num_fields - 1]) = NULL_TREE; + add_type_attr(type, "randomize_performed", NULL_TREE); + add_type_attr(type, "designated_init", NULL_TREE); + if (has_flexarray) + add_type_attr(type, "has_flexarray", NULL_TREE); + main_variant = TYPE_MAIN_VARIANT(type); - for (variant = main_variant; variant; variant = TYPE_NEXT_VARIANT(variant)) { - TYPE_FIELDS(variant) = list; - TYPE_ATTRIBUTES(variant) = copy_list(TYPE_ATTRIBUTES(variant)); - TYPE_ATTRIBUTES(variant) = tree_cons(get_identifier("randomize_performed"), NULL_TREE, TYPE_ATTRIBUTES(variant)); - TYPE_ATTRIBUTES(variant) = tree_cons(get_identifier("designated_init"), NULL_TREE, TYPE_ATTRIBUTES(variant)); - if (has_flexarray) - TYPE_ATTRIBUTES(type) = tree_cons(get_identifier("has_flexarray"), NULL_TREE, TYPE_ATTRIBUTES(type)); - } + for (variant = main_variant; variant; variant = TYPE_NEXT_VARIANT(variant)) + TYPE_FIELDS(variant) = newtree[0]; /* * force a re-layout of the main variant @@ -444,10 +426,8 @@ static void randomize_type(tree type) if (lookup_attribute("randomize_layout", TYPE_ATTRIBUTES(TYPE_MAIN_VARIANT(type))) || is_pure_ops_struct(type)) relayout_struct(type); - for (variant = TYPE_MAIN_VARIANT(type); variant; variant = TYPE_NEXT_VARIANT(variant)) { - TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type)); - TYPE_ATTRIBUTES(type) = tree_cons(get_identifier("randomize_considered"), NULL_TREE, TYPE_ATTRIBUTES(type)); - } + add_type_attr(type, "randomize_considered", NULL_TREE); + #ifdef __DEBUG_PLUGIN fprintf(stderr, "Marking randomize_considered on struct %s\n", ORIG_TYPE_NAME(type)); #ifdef __DEBUG_VERBOSE diff --git a/scripts/gcc-plugins/sancov_plugin.c b/scripts/gcc-plugins/sancov_plugin.c deleted file mode 100644 index b76cb9c42cec..000000000000 --- a/scripts/gcc-plugins/sancov_plugin.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2011-2016 by Emese Revfy <re.emese@gmail.com> - * Licensed under the GPL v2, or (at your option) v3 - * - * Homepage: - * https://github.com/ephox-gcc-plugins/sancov - * - * This plugin inserts a __sanitizer_cov_trace_pc() call at the start of basic blocks. - * It supports all gcc versions with plugin support (from gcc-4.5 on). - * It is based on the commit "Add fuzzing coverage support" by Dmitry Vyukov <dvyukov@google.com>. - * - * You can read about it more here: - * https://gcc.gnu.org/viewcvs/gcc?limit_changes=0&view=revision&revision=231296 - * https://lwn.net/Articles/674854/ - * https://github.com/google/syzkaller - * https://lwn.net/Articles/677764/ - * - * Usage: - * make run - */ - -#include "gcc-common.h" - -__visible int plugin_is_GPL_compatible; - -tree sancov_fndecl; - -static struct plugin_info sancov_plugin_info = { - .version = PLUGIN_VERSION, - .help = "sancov plugin\n", -}; - -static unsigned int sancov_execute(void) -{ - basic_block bb; - - /* Remove this line when this plugin and kcov will be in the kernel. - if (!strcmp(DECL_NAME_POINTER(current_function_decl), DECL_NAME_POINTER(sancov_fndecl))) - return 0; - */ - - FOR_EACH_BB_FN(bb, cfun) { - const_gimple stmt; - gcall *gcall; - gimple_stmt_iterator gsi = gsi_after_labels(bb); - - if (gsi_end_p(gsi)) - continue; - - stmt = gsi_stmt(gsi); - gcall = as_a_gcall(gimple_build_call(sancov_fndecl, 0)); - gimple_set_location(gcall, gimple_location(stmt)); - gsi_insert_before(&gsi, gcall, GSI_SAME_STMT); - } - return 0; -} - -#define PASS_NAME sancov - -#define NO_GATE -#define TODO_FLAGS_FINISH TODO_dump_func | TODO_verify_stmts | TODO_update_ssa_no_phi | TODO_verify_flow - -#include "gcc-generate-gimple-pass.h" - -static void sancov_start_unit(void __unused *gcc_data, void __unused *user_data) -{ - tree leaf_attr, nothrow_attr; - tree BT_FN_VOID = build_function_type_list(void_type_node, NULL_TREE); - - sancov_fndecl = build_fn_decl("__sanitizer_cov_trace_pc", BT_FN_VOID); - - DECL_ASSEMBLER_NAME(sancov_fndecl); - TREE_PUBLIC(sancov_fndecl) = 1; - DECL_EXTERNAL(sancov_fndecl) = 1; - DECL_ARTIFICIAL(sancov_fndecl) = 1; - DECL_PRESERVE_P(sancov_fndecl) = 1; - DECL_UNINLINABLE(sancov_fndecl) = 1; - TREE_USED(sancov_fndecl) = 1; - - nothrow_attr = tree_cons(get_identifier("nothrow"), NULL, NULL); - decl_attributes(&sancov_fndecl, nothrow_attr, 0); - gcc_assert(TREE_NOTHROW(sancov_fndecl)); - leaf_attr = tree_cons(get_identifier("leaf"), NULL, NULL); - decl_attributes(&sancov_fndecl, leaf_attr, 0); -} - -__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) -{ - int i; - const char * const plugin_name = plugin_info->base_name; - const int argc = plugin_info->argc; - const struct plugin_argument * const argv = plugin_info->argv; - bool enable = true; - - static const struct ggc_root_tab gt_ggc_r_gt_sancov[] = { - { - .base = &sancov_fndecl, - .nelt = 1, - .stride = sizeof(sancov_fndecl), - .cb = >_ggc_mx_tree_node, - .pchw = >_pch_nx_tree_node - }, - LAST_GGC_ROOT_TAB - }; - - /* BBs can be split afterwards?? */ - PASS_INFO(sancov, "asan", 0, PASS_POS_INSERT_BEFORE); - - if (!plugin_default_version_check(version, &gcc_version)) { - error(G_("incompatible gcc/plugin versions")); - return 1; - } - - for (i = 0; i < argc; ++i) { - if (!strcmp(argv[i].key, "no-sancov")) { - enable = false; - continue; - } - error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key); - } - - register_callback(plugin_name, PLUGIN_INFO, NULL, &sancov_plugin_info); - - if (!enable) - return 0; - -#if BUILDING_GCC_VERSION < 6000 - register_callback(plugin_name, PLUGIN_START_UNIT, &sancov_start_unit, NULL); - register_callback(plugin_name, PLUGIN_REGISTER_GGC_ROOTS, NULL, (void *)>_ggc_r_gt_sancov); - register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &sancov_pass_info); -#endif - - return 0; -} diff --git a/scripts/gcc-plugins/stackleak_plugin.c b/scripts/gcc-plugins/stackleak_plugin.c index c5c2ce113c92..d20c47d21ad8 100644 --- a/scripts/gcc-plugins/stackleak_plugin.c +++ b/scripts/gcc-plugins/stackleak_plugin.c @@ -467,6 +467,8 @@ static bool stackleak_gate(void) return false; if (STRING_EQUAL(section, ".entry.text")) return false; + if (STRING_EQUAL(section, ".head.text")) + return false; } return track_frame_size >= 0; diff --git a/scripts/gcc-plugins/structleak_plugin.c b/scripts/gcc-plugins/structleak_plugin.c deleted file mode 100644 index d8c744233832..000000000000 --- a/scripts/gcc-plugins/structleak_plugin.c +++ /dev/null @@ -1,257 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright 2013-2017 by PaX Team <pageexec@freemail.hu> - * - * Note: the choice of the license means that the compilation process is - * NOT 'eligible' as defined by gcc's library exception to the GPL v3, - * but for the kernel it doesn't matter since it doesn't link against - * any of the gcc libraries - * - * gcc plugin to forcibly initialize certain local variables that could - * otherwise leak kernel stack to userland if they aren't properly initialized - * by later code - * - * Homepage: https://pax.grsecurity.net/ - * - * Options: - * -fplugin-arg-structleak_plugin-disable - * -fplugin-arg-structleak_plugin-verbose - * -fplugin-arg-structleak_plugin-byref - * -fplugin-arg-structleak_plugin-byref-all - * - * Usage: - * $ # for 4.5/4.6/C based 4.7 - * $ gcc -I`gcc -print-file-name=plugin`/include -I`gcc -print-file-name=plugin`/include/c-family -fPIC -shared -O2 -o structleak_plugin.so structleak_plugin.c - * $ # for C++ based 4.7/4.8+ - * $ g++ -I`g++ -print-file-name=plugin`/include -I`g++ -print-file-name=plugin`/include/c-family -fPIC -shared -O2 -o structleak_plugin.so structleak_plugin.c - * $ gcc -fplugin=./structleak_plugin.so test.c -O2 - * - * TODO: eliminate redundant initializers - */ - -#include "gcc-common.h" - -/* unused C type flag in all versions 4.5-6 */ -#define TYPE_USERSPACE(TYPE) TYPE_LANG_FLAG_5(TYPE) - -__visible int plugin_is_GPL_compatible; - -static struct plugin_info structleak_plugin_info = { - .version = PLUGIN_VERSION, - .help = "disable\tdo not activate plugin\n" - "byref\tinit structs passed by reference\n" - "byref-all\tinit anything passed by reference\n" - "verbose\tprint all initialized variables\n", -}; - -#define BYREF_STRUCT 1 -#define BYREF_ALL 2 - -static bool verbose; -static int byref; - -static tree handle_user_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs) -{ - *no_add_attrs = true; - - /* check for types? for now accept everything linux has to offer */ - if (TREE_CODE(*node) != FIELD_DECL) - return NULL_TREE; - - *no_add_attrs = false; - return NULL_TREE; -} - -static struct attribute_spec user_attr = { }; - -static void register_attributes(void *event_data, void *data) -{ - user_attr.name = "user"; - user_attr.handler = handle_user_attribute; - user_attr.affects_type_identity = true; - - register_attribute(&user_attr); -} - -static tree get_field_type(tree field) -{ - return strip_array_types(TREE_TYPE(field)); -} - -static bool is_userspace_type(tree type) -{ - tree field; - - for (field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) { - tree fieldtype = get_field_type(field); - enum tree_code code = TREE_CODE(fieldtype); - - if (code == RECORD_TYPE || code == UNION_TYPE) - if (is_userspace_type(fieldtype)) - return true; - - if (lookup_attribute("user", DECL_ATTRIBUTES(field))) - return true; - } - return false; -} - -static void finish_type(void *event_data, void *data) -{ - tree type = (tree)event_data; - - if (type == NULL_TREE || type == error_mark_node) - return; - - if (TREE_CODE(type) == ENUMERAL_TYPE) - return; - - if (TYPE_USERSPACE(type)) - return; - - if (is_userspace_type(type)) - TYPE_USERSPACE(type) = 1; -} - -static void initialize(tree var) -{ - basic_block bb; - gimple_stmt_iterator gsi; - tree initializer; - gimple init_stmt; - tree type; - - /* this is the original entry bb before the forced split */ - bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun)); - - /* first check if variable is already initialized, warn otherwise */ - for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) { - gimple stmt = gsi_stmt(gsi); - tree rhs1; - - /* we're looking for an assignment of a single rhs... */ - if (!gimple_assign_single_p(stmt)) - continue; - rhs1 = gimple_assign_rhs1(stmt); - /* ... of a non-clobbering expression... */ - if (TREE_CLOBBER_P(rhs1)) - continue; - /* ... to our variable... */ - if (gimple_get_lhs(stmt) != var) - continue; - /* if it's an initializer then we're good */ - if (TREE_CODE(rhs1) == CONSTRUCTOR) - return; - } - - /* these aren't the 0days you're looking for */ - if (verbose) - inform(DECL_SOURCE_LOCATION(var), - "%s variable will be forcibly initialized", - (byref && TREE_ADDRESSABLE(var)) ? "byref" - : "userspace"); - - /* build the initializer expression */ - type = TREE_TYPE(var); - if (AGGREGATE_TYPE_P(type)) - initializer = build_constructor(type, NULL); - else - initializer = fold_convert(type, integer_zero_node); - - /* build the initializer stmt */ - init_stmt = gimple_build_assign(var, initializer); - gsi = gsi_after_labels(single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun))); - gsi_insert_before(&gsi, init_stmt, GSI_NEW_STMT); - update_stmt(init_stmt); -} - -static unsigned int structleak_execute(void) -{ - basic_block bb; - tree var; - unsigned int i; - - /* split the first bb where we can put the forced initializers */ - gcc_assert(single_succ_p(ENTRY_BLOCK_PTR_FOR_FN(cfun))); - bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun)); - if (!single_pred_p(bb)) { - split_edge(single_succ_edge(ENTRY_BLOCK_PTR_FOR_FN(cfun))); - gcc_assert(single_succ_p(ENTRY_BLOCK_PTR_FOR_FN(cfun))); - } - - /* enumerate all local variables and forcibly initialize our targets */ - FOR_EACH_LOCAL_DECL(cfun, i, var) { - tree type = TREE_TYPE(var); - - gcc_assert(DECL_P(var)); - if (!auto_var_in_fn_p(var, current_function_decl)) - continue; - - /* only care about structure types unless byref-all */ - if (byref != BYREF_ALL && TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) - continue; - - /* if the type is of interest, examine the variable */ - if (TYPE_USERSPACE(type) || - (byref && TREE_ADDRESSABLE(var))) - initialize(var); - } - - return 0; -} - -#define PASS_NAME structleak -#define NO_GATE -#define PROPERTIES_REQUIRED PROP_cfg -#define TODO_FLAGS_FINISH TODO_verify_il | TODO_verify_ssa | TODO_verify_stmts | TODO_dump_func | TODO_remove_unused_locals | TODO_update_ssa | TODO_ggc_collect | TODO_verify_flow -#include "gcc-generate-gimple-pass.h" - -__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) -{ - int i; - const char * const plugin_name = plugin_info->base_name; - const int argc = plugin_info->argc; - const struct plugin_argument * const argv = plugin_info->argv; - bool enable = true; - - PASS_INFO(structleak, "early_optimizations", 1, PASS_POS_INSERT_BEFORE); - - if (!plugin_default_version_check(version, &gcc_version)) { - error(G_("incompatible gcc/plugin versions")); - return 1; - } - - if (strncmp(lang_hooks.name, "GNU C", 5) && !strncmp(lang_hooks.name, "GNU C+", 6)) { - inform(UNKNOWN_LOCATION, G_("%s supports C only, not %s"), plugin_name, lang_hooks.name); - enable = false; - } - - for (i = 0; i < argc; ++i) { - if (!strcmp(argv[i].key, "disable")) { - enable = false; - continue; - } - if (!strcmp(argv[i].key, "verbose")) { - verbose = true; - continue; - } - if (!strcmp(argv[i].key, "byref")) { - byref = BYREF_STRUCT; - continue; - } - if (!strcmp(argv[i].key, "byref-all")) { - byref = BYREF_ALL; - continue; - } - error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key); - } - - register_callback(plugin_name, PLUGIN_INFO, NULL, &structleak_plugin_info); - if (enable) { - register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &structleak_pass_info); - register_callback(plugin_name, PLUGIN_FINISH_TYPE, finish_type, NULL); - } - register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL); - - return 0; -} diff --git a/scripts/gcc-x86_32-has-stack-protector.sh b/scripts/gcc-x86_32-has-stack-protector.sh deleted file mode 100755 index 825c75c5b715..000000000000 --- a/scripts/gcc-x86_32-has-stack-protector.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0 - -# This requires GCC 8.1 or better. Specifically, we require -# -mstack-protector-guard-reg, added by -# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81708 - -echo "int foo(void) { char X[200]; return 3; }" | $* -S -x c -c -m32 -O0 -fstack-protector -mstack-protector-guard-reg=fs -mstack-protector-guard-symbol=__stack_chk_guard - -o - 2> /dev/null | grep -q "%fs" diff --git a/scripts/gcc-x86_64-has-stack-protector.sh b/scripts/gcc-x86_64-has-stack-protector.sh deleted file mode 100755 index 75e4e22b986a..000000000000 --- a/scripts/gcc-x86_64-has-stack-protector.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0 - -echo "int foo(void) { char X[200]; return 3; }" | $* -S -x c -c -m64 -O0 -mcmodel=kernel -fno-PIE -fstack-protector - -o - 2> /dev/null | grep -q "%gs" diff --git a/scripts/gdb/linux/Makefile b/scripts/gdb/linux/Makefile index 48941faa6ea6..fcd32fcf3ae0 100644 --- a/scripts/gdb/linux/Makefile +++ b/scripts/gdb/linux/Makefile @@ -2,10 +2,10 @@ ifdef building_out_of_srctree -symlinks := $(patsubst $(srctree)/$(src)/%,%,$(wildcard $(srctree)/$(src)/*.py)) +symlinks := $(patsubst $(src)/%,%,$(wildcard $(src)/*.py)) quiet_cmd_symlink = SYMLINK $@ - cmd_symlink = ln -fsn $(patsubst $(obj)/%,$(abspath $(srctree))/$(src)/%,$@) $@ + cmd_symlink = ln -fsn $(patsubst $(obj)/%,$(abspath $(src))/%,$@) $@ always-y += $(symlinks) $(addprefix $(obj)/, $(symlinks)): FORCE diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in index fd6bd69c5096..f795302ddfa8 100644 --- a/scripts/gdb/linux/constants.py.in +++ b/scripts/gdb/linux/constants.py.in @@ -20,6 +20,7 @@ #include <linux/of_fdt.h> #include <linux/page_ext.h> #include <linux/radix-tree.h> +#include <linux/maple_tree.h> #include <linux/slab.h> #include <linux/threads.h> #include <linux/vmalloc.h> @@ -93,6 +94,12 @@ LX_GDBPARSED(RADIX_TREE_MAP_SIZE) LX_GDBPARSED(RADIX_TREE_MAP_SHIFT) LX_GDBPARSED(RADIX_TREE_MAP_MASK) +/* linux/maple_tree.h */ +LX_VALUE(MAPLE_NODE_SLOTS) +LX_VALUE(MAPLE_RANGE64_SLOTS) +LX_VALUE(MAPLE_ARANGE64_SLOTS) +LX_GDBPARSED(MAPLE_NODE_MASK) + /* linux/vmalloc.h */ LX_VALUE(VM_IOREMAP) LX_VALUE(VM_ALLOC) diff --git a/scripts/gdb/linux/cpus.py b/scripts/gdb/linux/cpus.py index cba589e5b57d..6edf4ef61636 100644 --- a/scripts/gdb/linux/cpus.py +++ b/scripts/gdb/linux/cpus.py @@ -26,11 +26,7 @@ def get_current_cpu(): if utils.get_gdbserver_type() == utils.GDBSERVER_QEMU: return gdb.selected_thread().num - 1 elif utils.get_gdbserver_type() == utils.GDBSERVER_KGDB: - tid = gdb.selected_thread().ptid[2] - if tid > (0x100000000 - MAX_CPUS - 2): - return 0x100000000 - tid - 2 - else: - return tasks.get_thread_info(tasks.get_task_by_pid(tid))['cpu'] + return gdb.parse_and_eval("kgdb_active.counter") else: raise gdb.GdbError("Sorry, obtaining the current CPU is not yet " "supported with this gdb server.") @@ -50,7 +46,7 @@ def per_cpu(var_ptr, cpu): # !CONFIG_SMP case offset = 0 pointer = var_ptr.cast(utils.get_long_type()) + offset - return pointer.cast(var_ptr.type).dereference() + return pointer.cast(var_ptr.type) cpu_mask = {} @@ -145,20 +141,37 @@ LxCpus() class PerCpu(gdb.Function): """Return per-cpu variable. -$lx_per_cpu("VAR"[, CPU]): Return the per-cpu variable called VAR for the +$lx_per_cpu(VAR[, CPU]): Return the per-cpu variable called VAR for the given CPU number. If CPU is omitted, the CPU of the current context is used. Note that VAR has to be quoted as string.""" def __init__(self): super(PerCpu, self).__init__("lx_per_cpu") - def invoke(self, var_name, cpu=-1): - var_ptr = gdb.parse_and_eval("&" + var_name.string()) - return per_cpu(var_ptr, cpu) + def invoke(self, var, cpu=-1): + return per_cpu(var.address, cpu).dereference() PerCpu() + +class PerCpuPtr(gdb.Function): + """Return per-cpu pointer. + +$lx_per_cpu_ptr(VAR[, CPU]): Return the per-cpu pointer called VAR for the +given CPU number. If CPU is omitted, the CPU of the current context is used. +Note that VAR has to be quoted as string.""" + + def __init__(self): + super(PerCpuPtr, self).__init__("lx_per_cpu_ptr") + + def invoke(self, var, cpu=-1): + return per_cpu(var, cpu) + + +PerCpuPtr() + + def get_current_task(cpu): task_ptr_type = task_type.get_type().pointer() @@ -169,10 +182,10 @@ def get_current_task(cpu): var_ptr = gdb.parse_and_eval("(struct task_struct *)cpu_tasks[0].task") return var_ptr.dereference() else: - var_ptr = gdb.parse_and_eval("&pcpu_hot.current_task") + var_ptr = gdb.parse_and_eval("¤t_task") return per_cpu(var_ptr, cpu).dereference() elif utils.is_target_arch("aarch64"): - current_task_addr = gdb.parse_and_eval("$SP_EL0") + current_task_addr = gdb.parse_and_eval("(unsigned long)$SP_EL0") if (current_task_addr >> 63) != 0: current_task = current_task_addr.cast(task_ptr_type) return current_task.dereference() diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py index 66ae5c7690cf..f4f715a8f0e3 100644 --- a/scripts/gdb/linux/interrupts.py +++ b/scripts/gdb/linux/interrupts.py @@ -7,7 +7,7 @@ import gdb from linux import constants from linux import cpus from linux import utils -from linux import radixtree +from linux import mapletree irq_desc_type = utils.CachedType("struct irq_desc") @@ -23,12 +23,12 @@ def irqd_is_level(desc): def show_irq_desc(prec, irq): text = "" - desc = radixtree.lookup(gdb.parse_and_eval("&irq_desc_tree"), irq) + desc = mapletree.mtree_load(gdb.parse_and_eval("&sparse_irqs"), irq) if desc is None: return text - desc = desc.cast(irq_desc_type.get_type()) - if desc is None: + desc = desc.cast(irq_desc_type.get_type().pointer()) + if desc == 0: return text if irq_settings_is_hidden(desc): @@ -37,7 +37,7 @@ def show_irq_desc(prec, irq): any_count = 0 if desc['kstat_irqs']: for cpu in cpus.each_online_cpu(): - any_count += cpus.per_cpu(desc['kstat_irqs'], cpu) + any_count += cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt'] if (desc['action'] == 0 or irq_desc_is_chained(desc)) and any_count == 0: return text; @@ -45,7 +45,7 @@ def show_irq_desc(prec, irq): text += "%*d: " % (prec, irq) for cpu in cpus.each_online_cpu(): if desc['kstat_irqs']: - count = cpus.per_cpu(desc['kstat_irqs'], cpu) + count = cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt'] else: count = 0 text += "%10u" % (count) @@ -110,7 +110,7 @@ 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)) + text += "%10u " % (cpus.per_cpu(pvar, cpu).dereference()) text += " %s\n" % (desc) return text @@ -142,7 +142,7 @@ def x86_show_interupts(prec): 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 += x86_show_mce(prec, "&mce_poll_count", "MCP", "Machine check polls") text += show_irq_err_count(prec) @@ -177,7 +177,7 @@ def arm_common_show_interrupts(prec): if desc == 0: continue for cpu in cpus.each_online_cpu(): - text += "%10u" % (cpus.per_cpu(desc['kstat_irqs'], cpu)) + text += "%10u" % (cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt']) text += " %s" % (ipi_types[ipi].string()) text += "\n" return text @@ -221,8 +221,8 @@ class LxInterruptList(gdb.Command): gdb.write("CPU%-8d" % cpu) gdb.write("\n") - if utils.gdb_eval_or_none("&irq_desc_tree") is None: - return + if utils.gdb_eval_or_none("&sparse_irqs") is None: + 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)) diff --git a/scripts/gdb/linux/kasan.py b/scripts/gdb/linux/kasan.py new file mode 100644 index 000000000000..56730b3fde0b --- /dev/null +++ b/scripts/gdb/linux/kasan.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright 2024 Canonical Ltd. +# +# Authors: +# Kuan-Ying Lee <kuan-ying.lee@canonical.com> +# + +import gdb +from linux import constants, mm + +def help(): + t = """Usage: lx-kasan_mem_to_shadow [Hex memory addr] + Example: + lx-kasan_mem_to_shadow 0xffff000008eca008\n""" + gdb.write("Unrecognized command\n") + raise gdb.GdbError(t) + +class KasanMemToShadow(gdb.Command): + """Translate memory address to kasan shadow address""" + + p_ops = None + + def __init__(self): + if constants.LX_CONFIG_KASAN_GENERIC or constants.LX_CONFIG_KASAN_SW_TAGS: + super(KasanMemToShadow, self).__init__("lx-kasan_mem_to_shadow", gdb.COMMAND_SUPPORT) + + def invoke(self, args, from_tty): + if not constants.LX_CONFIG_KASAN_GENERIC or constants.LX_CONFIG_KASAN_SW_TAGS: + raise gdb.GdbError('CONFIG_KASAN_GENERIC or CONFIG_KASAN_SW_TAGS is not set') + + argv = gdb.string_to_argv(args) + if len(argv) == 1: + if self.p_ops is None: + self.p_ops = mm.page_ops().ops + addr = int(argv[0], 16) + shadow_addr = self.kasan_mem_to_shadow(addr) + gdb.write('shadow addr: 0x%x\n' % shadow_addr) + else: + help() + def kasan_mem_to_shadow(self, addr): + return (addr >> self.p_ops.KASAN_SHADOW_SCALE_SHIFT) + self.p_ops.KASAN_SHADOW_OFFSET + +KasanMemToShadow() diff --git a/scripts/gdb/linux/mapletree.py b/scripts/gdb/linux/mapletree.py new file mode 100644 index 000000000000..d52d51c0a03f --- /dev/null +++ b/scripts/gdb/linux/mapletree.py @@ -0,0 +1,252 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Maple tree helpers +# +# Copyright (c) 2025 Broadcom +# +# Authors: +# Florian Fainelli <florian.fainelli@broadcom.com> + +import gdb + +from linux import utils +from linux import constants +from linux import xarray + +maple_tree_root_type = utils.CachedType("struct maple_tree") +maple_node_type = utils.CachedType("struct maple_node") +maple_enode_type = utils.CachedType("void") + +maple_dense = 0 +maple_leaf_64 = 1 +maple_range_64 = 2 +maple_arange_64 = 3 + +class Mas(object): + ma_active = 0 + ma_start = 1 + ma_root = 2 + ma_none = 3 + ma_pause = 4 + ma_overflow = 5 + ma_underflow = 6 + ma_error = 7 + + def __init__(self, mt, first, end): + if mt.type == maple_tree_root_type.get_type().pointer(): + self.tree = mt.dereference() + elif mt.type != maple_tree_root_type.get_type(): + raise gdb.GdbError("must be {} not {}" + .format(maple_tree_root_type.get_type().pointer(), mt.type)) + self.tree = mt + self.index = first + self.last = end + self.node = None + self.status = self.ma_start + self.min = 0 + self.max = -1 + + def is_start(self): + # mas_is_start() + return self.status == self.ma_start + + def is_ptr(self): + # mas_is_ptr() + return self.status == self.ma_root + + def is_none(self): + # mas_is_none() + return self.status == self.ma_none + + def root(self): + # mas_root() + return self.tree['ma_root'].cast(maple_enode_type.get_type().pointer()) + + def start(self): + # mas_start() + if self.is_start() is False: + return None + + self.min = 0 + self.max = ~0 + + while True: + self.depth = 0 + root = self.root() + if xarray.xa_is_node(root): + self.depth = 0 + self.status = self.ma_active + self.node = mte_safe_root(root) + self.offset = 0 + if mte_dead_node(self.node) is True: + continue + + return None + + self.node = None + # Empty tree + if root is None: + self.status = self.ma_none + self.offset = constants.LX_MAPLE_NODE_SLOTS + return None + + # Single entry tree + self.status = self.ma_root + self.offset = constants.LX_MAPLE_NODE_SLOTS + + if self.index != 0: + return None + + return root + + return None + + def reset(self): + # mas_reset() + self.status = self.ma_start + self.node = None + +def mte_safe_root(node): + if node.type != maple_enode_type.get_type().pointer(): + raise gdb.GdbError("{} must be {} not {}" + .format(mte_safe_root.__name__, maple_enode_type.get_type().pointer(), node.type)) + ulong_type = utils.get_ulong_type() + indirect_ptr = node.cast(ulong_type) & ~0x2 + val = indirect_ptr.cast(maple_enode_type.get_type().pointer()) + return val + +def mte_node_type(entry): + ulong_type = utils.get_ulong_type() + val = None + if entry.type == maple_enode_type.get_type().pointer(): + val = entry.cast(ulong_type) + elif entry.type == ulong_type: + val = entry + else: + raise gdb.GdbError("{} must be {} not {}" + .format(mte_node_type.__name__, maple_enode_type.get_type().pointer(), entry.type)) + return (val >> 0x3) & 0xf + +def ma_dead_node(node): + if node.type != maple_node_type.get_type().pointer(): + raise gdb.GdbError("{} must be {} not {}" + .format(ma_dead_node.__name__, maple_node_type.get_type().pointer(), node.type)) + ulong_type = utils.get_ulong_type() + parent = node['parent'] + indirect_ptr = node['parent'].cast(ulong_type) & ~constants.LX_MAPLE_NODE_MASK + return indirect_ptr == node + +def mte_to_node(enode): + ulong_type = utils.get_ulong_type() + if enode.type == maple_enode_type.get_type().pointer(): + indirect_ptr = enode.cast(ulong_type) + elif enode.type == ulong_type: + indirect_ptr = enode + else: + raise gdb.GdbError("{} must be {} not {}" + .format(mte_to_node.__name__, maple_enode_type.get_type().pointer(), enode.type)) + indirect_ptr = indirect_ptr & ~constants.LX_MAPLE_NODE_MASK + return indirect_ptr.cast(maple_node_type.get_type().pointer()) + +def mte_dead_node(enode): + if enode.type != maple_enode_type.get_type().pointer(): + raise gdb.GdbError("{} must be {} not {}" + .format(mte_dead_node.__name__, maple_enode_type.get_type().pointer(), enode.type)) + node = mte_to_node(enode) + return ma_dead_node(node) + +def ma_is_leaf(tp): + result = tp < maple_range_64 + return tp < maple_range_64 + +def mt_pivots(t): + if t == maple_dense: + return 0 + elif t == maple_leaf_64 or t == maple_range_64: + return constants.LX_MAPLE_RANGE64_SLOTS - 1 + elif t == maple_arange_64: + return constants.LX_MAPLE_ARANGE64_SLOTS - 1 + +def ma_pivots(node, t): + if node.type != maple_node_type.get_type().pointer(): + raise gdb.GdbError("{}: must be {} not {}" + .format(ma_pivots.__name__, maple_node_type.get_type().pointer(), node.type)) + if t == maple_arange_64: + return node['ma64']['pivot'] + elif t == maple_leaf_64 or t == maple_range_64: + return node['mr64']['pivot'] + else: + return None + +def ma_slots(node, tp): + if node.type != maple_node_type.get_type().pointer(): + raise gdb.GdbError("{}: must be {} not {}" + .format(ma_slots.__name__, maple_node_type.get_type().pointer(), node.type)) + if tp == maple_arange_64: + return node['ma64']['slot'] + elif tp == maple_range_64 or tp == maple_leaf_64: + return node['mr64']['slot'] + elif tp == maple_dense: + return node['slot'] + else: + return None + +def mt_slot(mt, slots, offset): + ulong_type = utils.get_ulong_type() + return slots[offset].cast(ulong_type) + +def mtree_lookup_walk(mas): + ulong_type = utils.get_ulong_type() + n = mas.node + + while True: + node = mte_to_node(n) + tp = mte_node_type(n) + pivots = ma_pivots(node, tp) + end = mt_pivots(tp) + offset = 0 + while True: + if pivots[offset] >= mas.index: + break + if offset >= end: + break + offset += 1 + + slots = ma_slots(node, tp) + n = mt_slot(mas.tree, slots, offset) + if ma_dead_node(node) is True: + mas.reset() + return None + break + + if ma_is_leaf(tp) is True: + break + + return n + +def mtree_load(mt, index): + ulong_type = utils.get_ulong_type() + # MT_STATE(...) + mas = Mas(mt, index, index) + entry = None + + while True: + entry = mas.start() + if mas.is_none(): + return None + + if mas.is_ptr(): + if index != 0: + entry = None + return entry + + entry = mtree_lookup_walk(mas) + if entry is None and mas.is_start(): + continue + else: + break + + if xarray.xa_is_zero(entry): + return None + + return entry diff --git a/scripts/gdb/linux/mm.py b/scripts/gdb/linux/mm.py index 515730fd4c9d..7571aebbe650 100644 --- a/scripts/gdb/linux/mm.py +++ b/scripts/gdb/linux/mm.py @@ -33,7 +33,7 @@ class aarch64_page_ops(): def __init__(self): self.SUBSECTION_SHIFT = 21 self.SEBSECTION_SIZE = 1 << self.SUBSECTION_SHIFT - self.MODULES_VSIZE = 128 * 1024 * 1024 + self.MODULES_VSIZE = 2 * 1024 * 1024 * 1024 if constants.LX_CONFIG_ARM64_64K_PAGES: self.SECTION_SIZE_BITS = 29 @@ -47,8 +47,13 @@ class aarch64_page_ops(): self.VA_BITS = constants.LX_CONFIG_ARM64_VA_BITS if self.VA_BITS > 48: - self.VA_BITS_MIN = 48 - self.vabits_actual = gdb.parse_and_eval('vabits_actual') + if constants.LX_CONFIG_ARM64_16K_PAGES: + self.VA_BITS_MIN = 47 + else: + self.VA_BITS_MIN = 48 + tcr_el1 = gdb.execute("info registers $TCR_EL1", to_string=True) + tcr_el1 = int(tcr_el1.split()[1], 16) + self.vabits_actual = 64 - ((tcr_el1 >> 16) & 63) else: self.VA_BITS_MIN = self.VA_BITS self.vabits_actual = self.VA_BITS @@ -59,9 +64,9 @@ class aarch64_page_ops(): if str(constants.LX_CONFIG_ARCH_FORCE_MAX_ORDER).isdigit(): self.MAX_ORDER = constants.LX_CONFIG_ARCH_FORCE_MAX_ORDER else: - self.MAX_ORDER = 11 + self.MAX_ORDER = 10 - self.MAX_ORDER_NR_PAGES = 1 << (self.MAX_ORDER - 1) + self.MAX_ORDER_NR_PAGES = 1 << (self.MAX_ORDER) self.PFN_SECTION_SHIFT = self.SECTION_SIZE_BITS - self.PAGE_SHIFT self.NR_MEM_SECTIONS = 1 << self.SECTIONS_SHIFT self.PAGES_PER_SECTION = 1 << self.PFN_SECTION_SHIFT @@ -89,10 +94,10 @@ class aarch64_page_ops(): self.MODULES_VADDR = self._PAGE_END(self.VA_BITS_MIN) self.MODULES_END = self.MODULES_VADDR + self.MODULES_VSIZE - self.VMEMMAP_SHIFT = (self.PAGE_SHIFT - self.STRUCT_PAGE_MAX_SHIFT) - self.VMEMMAP_SIZE = ((self._PAGE_END(self.VA_BITS_MIN) - self.PAGE_OFFSET) >> self.VMEMMAP_SHIFT) - self.VMEMMAP_START = (-(1 << (self.VA_BITS - self.VMEMMAP_SHIFT))) & 0xffffffffffffffff - self.VMEMMAP_END = self.VMEMMAP_START + self.VMEMMAP_SIZE + self.VMEMMAP_RANGE = self._PAGE_END(self.VA_BITS_MIN) - self.PAGE_OFFSET + self.VMEMMAP_SIZE = (self.VMEMMAP_RANGE >> self.PAGE_SHIFT) * self.struct_page_size + self.VMEMMAP_END = (-(1 * 1024 * 1024 * 1024)) & 0xffffffffffffffff + self.VMEMMAP_START = self.VMEMMAP_END - self.VMEMMAP_SIZE self.VMALLOC_START = self.MODULES_END self.VMALLOC_END = self.VMEMMAP_START - 256 * 1024 * 1024 diff --git a/scripts/gdb/linux/modules.py b/scripts/gdb/linux/modules.py index 298dfcc25eae..fa15f872ddbe 100644 --- a/scripts/gdb/linux/modules.py +++ b/scripts/gdb/linux/modules.py @@ -19,6 +19,9 @@ from linux import cpus, utils, lists, constants module_type = utils.CachedType("struct module") +def has_modules(): + return utils.gdb_eval_or_none("modules") is not None + def module_list(): global module_type modules = utils.gdb_eval_or_none("modules") diff --git a/scripts/gdb/linux/pgtable.py b/scripts/gdb/linux/pgtable.py index 30d837f3dfae..09aac2421fb8 100644 --- a/scripts/gdb/linux/pgtable.py +++ b/scripts/gdb/linux/pgtable.py @@ -29,11 +29,9 @@ def page_mask(level=1): raise Exception(f'Unknown page level: {level}') -#page_offset_base in case CONFIG_DYNAMIC_MEMORY_LAYOUT is disabled -POB_NO_DYNAMIC_MEM_LAYOUT = '0xffff888000000000' def _page_offset_base(): pob_symbol = gdb.lookup_global_symbol('page_offset_base') - pob = pob_symbol.name if pob_symbol else POB_NO_DYNAMIC_MEM_LAYOUT + pob = pob_symbol.name return gdb.parse_and_eval(pob) diff --git a/scripts/gdb/linux/proc.py b/scripts/gdb/linux/proc.py index 43c687e7a69d..65dd1bd12964 100644 --- a/scripts/gdb/linux/proc.py +++ b/scripts/gdb/linux/proc.py @@ -18,6 +18,7 @@ from linux import utils from linux import tasks from linux import lists from linux import vfs +from linux import rbtree from struct import * @@ -172,8 +173,7 @@ values of that process namespace""" gdb.write("{:^18} {:^15} {:>9} {} {} options\n".format( "mount", "super_block", "devname", "pathname", "fstype")) - for mnt in lists.list_for_each_entry(namespace['list'], - mount_ptr_type, "mnt_list"): + for mnt in rbtree.rb_inorder_for_each_entry(namespace['mounts'], mount_ptr_type, "mnt_node"): devname = mnt['mnt_devname'].string() devname = devname if devname else "none" diff --git a/scripts/gdb/linux/rbtree.py b/scripts/gdb/linux/rbtree.py index fe462855eefd..fcbcc5f4153c 100644 --- a/scripts/gdb/linux/rbtree.py +++ b/scripts/gdb/linux/rbtree.py @@ -9,6 +9,18 @@ from linux import utils rb_root_type = utils.CachedType("struct rb_root") rb_node_type = utils.CachedType("struct rb_node") +def rb_inorder_for_each(root): + def inorder(node): + if node: + yield from inorder(node['rb_left']) + yield node + yield from inorder(node['rb_right']) + + yield from inorder(root['rb_node']) + +def rb_inorder_for_each_entry(root, gdbtype, member): + for node in rb_inorder_for_each(root): + yield utils.container_of(node, gdbtype, member) def rb_first(root): if root.type == rb_root_type.get_type(): diff --git a/scripts/gdb/linux/stackdepot.py b/scripts/gdb/linux/stackdepot.py index 0281d9de4b7c..37313a5a51a0 100644 --- a/scripts/gdb/linux/stackdepot.py +++ b/scripts/gdb/linux/stackdepot.py @@ -13,6 +13,13 @@ if constants.LX_CONFIG_STACKDEPOT: stack_record_type = utils.CachedType('struct stack_record') DEPOT_STACK_ALIGN = 4 +def help(): + t = """Usage: lx-stack_depot_lookup [Hex handle value] + Example: + lx-stack_depot_lookup 0x00c80300\n""" + gdb.write("Unrecognized command\n") + raise gdb.GdbError(t) + def stack_depot_fetch(handle): global DEPOT_STACK_ALIGN global stack_record_type @@ -27,14 +34,18 @@ def stack_depot_fetch(handle): offset = parts['offset'] << DEPOT_STACK_ALIGN pools_num = gdb.parse_and_eval('pools_num') - if parts['pool_index'] > pools_num: + if handle == 0: + raise gdb.GdbError("handle is 0\n") + + pool_index = parts['pool_index_plus_1'] - 1 + if pool_index >= pools_num: gdb.write("pool index %d out of bounds (%d) for stack id 0x%08x\n" % (parts['pool_index'], pools_num, handle)) return gdb.Value(0), 0 stack_pools = gdb.parse_and_eval('stack_pools') try: - pool = stack_pools[parts['pool_index']] + pool = stack_pools[pool_index] stack = (pool + gdb.Value(offset).cast(utils.get_size_t_type())).cast(stack_record_type.get_type().pointer()) size = int(stack['size'].cast(utils.get_ulong_type())) return stack['entries'], size @@ -53,3 +64,23 @@ def stack_depot_print(handle): gdb.execute("x /i 0x%x" % (int(entries[i]))) except Exception as e: gdb.write("%s\n" % e) + +class StackDepotLookup(gdb.Command): + """Search backtrace by handle""" + + def __init__(self): + if constants.LX_CONFIG_STACKDEPOT: + super(StackDepotLookup, self).__init__("lx-stack_depot_lookup", gdb.COMMAND_SUPPORT) + + def invoke(self, args, from_tty): + if not constants.LX_CONFIG_STACKDEPOT: + raise gdb.GdbError('CONFIG_STACKDEPOT is not set') + + argv = gdb.string_to_argv(args) + if len(argv) == 1: + handle = int(argv[0], 16) + stack_depot_print(gdb.Value(handle).cast(utils.get_uint_type())) + else: + help() + +StackDepotLookup() diff --git a/scripts/gdb/linux/symbols.py b/scripts/gdb/linux/symbols.py index e8316beb17a7..2332bd8eddf1 100644 --- a/scripts/gdb/linux/symbols.py +++ b/scripts/gdb/linux/symbols.py @@ -14,7 +14,9 @@ import gdb import os import re +import struct +from itertools import count from linux import modules, utils, constants @@ -36,23 +38,52 @@ if hasattr(gdb, 'Breakpoint'): # Disable pagination while reporting symbol (re-)loading. # The console input is blocked in this context so that we would # get stuck waiting for the user to acknowledge paged output. - show_pagination = gdb.execute("show pagination", to_string=True) - pagination = show_pagination.endswith("on.\n") - gdb.execute("set pagination off") - - if module_name in cmd.loaded_modules: - gdb.write("refreshing all symbols to reload module " - "'{0}'\n".format(module_name)) - cmd.load_all_symbols() - else: - cmd.load_module_symbols(module) - - # restore pagination state - gdb.execute("set pagination %s" % ("on" if pagination else "off")) + with utils.pagination_off(): + if module_name in cmd.loaded_modules: + gdb.write("refreshing all symbols to reload module " + "'{0}'\n".format(module_name)) + cmd.load_all_symbols() + else: + cmd.load_module_symbols(module) return False +def get_vmcore_s390(): + with utils.qemu_phy_mem_mode(): + vmcore_info = 0x0e0c + paddr_vmcoreinfo_note = gdb.parse_and_eval("*(unsigned long long *)" + + hex(vmcore_info)) + if paddr_vmcoreinfo_note == 0 or paddr_vmcoreinfo_note & 1: + # In the early boot case, extract vm_layout.kaslr_offset from the + # vmlinux image in physical memory. + if paddr_vmcoreinfo_note == 0: + kaslr_offset_phys = 0 + else: + kaslr_offset_phys = paddr_vmcoreinfo_note - 1 + with utils.pagination_off(): + gdb.execute("symbol-file {0} -o {1}".format( + utils.get_vmlinux(), hex(kaslr_offset_phys))) + kaslr_offset = gdb.parse_and_eval("vm_layout.kaslr_offset") + return "KERNELOFFSET=" + hex(kaslr_offset)[2:] + inferior = gdb.selected_inferior() + elf_note = inferior.read_memory(paddr_vmcoreinfo_note, 12) + n_namesz, n_descsz, n_type = struct.unpack(">III", elf_note) + desc_paddr = paddr_vmcoreinfo_note + len(elf_note) + n_namesz + 1 + return gdb.parse_and_eval("(char *)" + hex(desc_paddr)).string() + + +def get_kerneloffset(): + if utils.is_target_arch('s390'): + try: + vmcore_str = get_vmcore_s390() + except gdb.error as e: + gdb.write("{}\n".format(e)) + return None + return utils.parse_vmcore(vmcore_str).kerneloffset + return None + + class LxSymbols(gdb.Command): """(Re-)load symbols of Linux kernel and currently loaded modules. @@ -95,10 +126,14 @@ lx-symbols command.""" except gdb.error: return str(module_addr) - attrs = sect_attrs['attrs'] - section_name_to_address = { - attrs[n]['battr']['attr']['name'].string(): attrs[n]['address'] - for n in range(int(sect_attrs['nsections']))} + section_name_to_address = {} + for i in count(): + # this is a NULL terminated array + if sect_attrs['grp']['bin_attrs'][i] == 0x0: + break + + attr = sect_attrs['grp']['bin_attrs'][i].dereference() + section_name_to_address[attr['attr']['name'].string()] = attr['private'] textaddr = section_name_to_address.get(".text", module_addr) args = [] @@ -149,13 +184,14 @@ lx-symbols command.""" saved_states.append({'breakpoint': bp, 'enabled': bp.enabled}) # drop all current symbols and reload vmlinux - orig_vmlinux = 'vmlinux' - for obj in gdb.objfiles(): - if (obj.filename.endswith('vmlinux') or - obj.filename.endswith('vmlinux.debug')): - orig_vmlinux = obj.filename + orig_vmlinux = utils.get_vmlinux() gdb.execute("symbol-file", to_string=True) - gdb.execute("symbol-file {0}".format(orig_vmlinux)) + kerneloffset = get_kerneloffset() + if kerneloffset is None: + offset_arg = "" + else: + offset_arg = " -o " + hex(kerneloffset) + gdb.execute("symbol-file {0}{1}".format(orig_vmlinux, offset_arg)) self.loaded_modules = [] module_list = modules.module_list() @@ -178,6 +214,9 @@ lx-symbols command.""" self.load_all_symbols() + if not modules.has_modules(): + return + if hasattr(gdb, 'Breakpoint'): if self.breakpoint is not None: self.breakpoint.delete() diff --git a/scripts/gdb/linux/tasks.py b/scripts/gdb/linux/tasks.py index 6793d6e86e77..62348397c1f5 100644 --- a/scripts/gdb/linux/tasks.py +++ b/scripts/gdb/linux/tasks.py @@ -85,7 +85,7 @@ thread_info_type = utils.CachedType("struct thread_info") def get_thread_info(task): thread_info_ptr_type = thread_info_type.get_type().pointer() - if task.type.fields()[0].type == thread_info_type.get_type(): + if task_type.get_type().fields()[0].type == thread_info_type.get_type(): return task['thread_info'] thread_info = task['stack'].cast(thread_info_ptr_type) return thread_info.dereference() diff --git a/scripts/gdb/linux/timerlist.py b/scripts/gdb/linux/timerlist.py index 64bc87191003..98445671fe83 100644 --- a/scripts/gdb/linux/timerlist.py +++ b/scripts/gdb/linux/timerlist.py @@ -87,21 +87,22 @@ def print_cpu(hrtimer_bases, cpu, max_clock_bases): text += "\n" if constants.LX_CONFIG_TICK_ONESHOT: - fmts = [(" .{} : {}", 'nohz_mode'), - (" .{} : {} nsecs", 'last_tick'), - (" .{} : {}", 'tick_stopped'), - (" .{} : {}", 'idle_jiffies'), - (" .{} : {}", 'idle_calls'), - (" .{} : {}", 'idle_sleeps'), - (" .{} : {} nsecs", 'idle_entrytime'), - (" .{} : {} nsecs", 'idle_waketime'), - (" .{} : {} nsecs", 'idle_exittime'), - (" .{} : {} nsecs", 'idle_sleeptime'), - (" .{}: {} nsecs", 'iowait_sleeptime'), - (" .{} : {}", 'last_jiffies'), - (" .{} : {}", 'next_timer'), - (" .{} : {} nsecs", 'idle_expires')] - text += "\n".join([s.format(f, ts[f]) for s, f in fmts]) + TS_FLAG_STOPPED = 1 << 1 + TS_FLAG_NOHZ = 1 << 4 + 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" text += "\njiffies: {}\n".format(jiffies) text += "\n" diff --git a/scripts/gdb/linux/utils.py b/scripts/gdb/linux/utils.py index 7d5278d815fa..e11f6f67961a 100644 --- a/scripts/gdb/linux/utils.py +++ b/scripts/gdb/linux/utils.py @@ -11,6 +11,11 @@ # This work is licensed under the terms of the GNU GPL version 2. # +import contextlib +import dataclasses +import re +import typing + import gdb @@ -195,8 +200,8 @@ def get_gdbserver_type(): def probe_kgdb(): try: - thread_info = gdb.execute("info thread 2", to_string=True) - return "shadowCPU0" in thread_info + thread_info = gdb.execute("info thread 1", to_string=True) + return "shadowCPU" in thread_info except gdb.error: return False @@ -216,3 +221,53 @@ def gdb_eval_or_none(expresssion): return gdb.parse_and_eval(expresssion) except gdb.error: return None + + +@contextlib.contextmanager +def qemu_phy_mem_mode(): + connection = gdb.selected_inferior().connection + orig = connection.send_packet("qqemu.PhyMemMode") + if orig not in b"01": + raise gdb.error("Unexpected qemu.PhyMemMode") + orig = orig.decode() + if connection.send_packet("Qqemu.PhyMemMode:1") != b"OK": + raise gdb.error("Failed to set qemu.PhyMemMode") + try: + yield + finally: + if connection.send_packet("Qqemu.PhyMemMode:" + orig) != b"OK": + raise gdb.error("Failed to restore qemu.PhyMemMode") + + +@dataclasses.dataclass +class VmCore: + kerneloffset: typing.Optional[int] + + +def parse_vmcore(s): + match = re.search(r"KERNELOFFSET=([0-9a-f]+)", s) + if match is None: + kerneloffset = None + else: + kerneloffset = int(match.group(1), 16) + return VmCore(kerneloffset=kerneloffset) + + +def get_vmlinux(): + vmlinux = 'vmlinux' + for obj in gdb.objfiles(): + if (obj.filename.endswith('vmlinux') or + obj.filename.endswith('vmlinux.debug')): + vmlinux = obj.filename + return vmlinux + + +@contextlib.contextmanager +def pagination_off(): + show_pagination = gdb.execute("show pagination", to_string=True) + pagination = show_pagination.endswith("on.\n") + gdb.execute("set pagination off") + try: + yield + finally: + gdb.execute("set pagination %s" % ("on" if pagination else "off")) diff --git a/scripts/gdb/linux/vfs.py b/scripts/gdb/linux/vfs.py index c77b9ce75f6d..9e921b645a68 100644 --- a/scripts/gdb/linux/vfs.py +++ b/scripts/gdb/linux/vfs.py @@ -22,7 +22,7 @@ def dentry_name(d): if parent == d or parent == 0: return "" p = dentry_name(d['d_parent']) + "/" - return p + d['d_iname'].string() + return p + d['d_name']['name'].string() class DentryName(gdb.Function): """Return string of the full path of a dentry. diff --git a/scripts/gdb/linux/xarray.py b/scripts/gdb/linux/xarray.py new file mode 100644 index 000000000000..f4477b5def75 --- /dev/null +++ b/scripts/gdb/linux/xarray.py @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Xarray helpers +# +# Copyright (c) 2025 Broadcom +# +# Authors: +# Florian Fainelli <florian.fainelli@broadcom.com> + +import gdb + +from linux import utils +from linux import constants + +def xa_is_internal(entry): + ulong_type = utils.get_ulong_type() + return ((entry.cast(ulong_type) & 3) == 2) + +def xa_mk_internal(v): + return ((v << 2) | 2) + +def xa_is_zero(entry): + ulong_type = utils.get_ulong_type() + return entry.cast(ulong_type) == xa_mk_internal(257) + +def xa_is_node(entry): + ulong_type = utils.get_ulong_type() + return xa_is_internal(entry) and (entry.cast(ulong_type) > 4096) diff --git a/scripts/gdb/vmlinux-gdb.py b/scripts/gdb/vmlinux-gdb.py index fc53cdf286f1..d4eeed4506fd 100644 --- a/scripts/gdb/vmlinux-gdb.py +++ b/scripts/gdb/vmlinux-gdb.py @@ -49,3 +49,4 @@ else: import linux.page_owner import linux.slab import linux.vmalloc + import linux.kasan diff --git a/scripts/gen-crc-consts.py b/scripts/gen-crc-consts.py new file mode 100755 index 000000000000..f9b44fc3a03f --- /dev/null +++ b/scripts/gen-crc-consts.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script that generates constants for computing the given CRC variant(s). +# +# Copyright 2025 Google LLC +# +# Author: Eric Biggers <ebiggers@google.com> + +import sys + +# XOR (add) an iterable of polynomials. +def xor(iterable): + res = 0 + for val in iterable: + res ^= val + return res + +# Multiply two polynomials. +def clmul(a, b): + return xor(a << i for i in range(b.bit_length()) if (b & (1 << i)) != 0) + +# Polynomial division floor(a / b). +def div(a, b): + q = 0 + while a.bit_length() >= b.bit_length(): + q ^= 1 << (a.bit_length() - b.bit_length()) + a ^= b << (a.bit_length() - b.bit_length()) + return q + +# Reduce the polynomial 'a' modulo the polynomial 'b'. +def reduce(a, b): + return a ^ clmul(div(a, b), b) + +# Reflect the bits of a polynomial. +def bitreflect(poly, num_bits): + assert poly.bit_length() <= num_bits + return xor(((poly >> i) & 1) << (num_bits - 1 - i) for i in range(num_bits)) + +# Format a polynomial as hex. Bit-reflect it if the CRC is lsb-first. +def fmt_poly(variant, poly, num_bits): + if variant.lsb: + poly = bitreflect(poly, num_bits) + return f'0x{poly:0{2*num_bits//8}x}' + +# Print a pair of 64-bit polynomial multipliers. They are always passed in the +# order [HI64_TERMS, LO64_TERMS] but will be printed in the appropriate order. +def print_mult_pair(variant, mults): + mults = list(mults if variant.lsb else reversed(mults)) + terms = ['HI64_TERMS', 'LO64_TERMS'] if variant.lsb else ['LO64_TERMS', 'HI64_TERMS'] + for i in range(2): + print(f'\t\t{fmt_poly(variant, mults[i]["val"], 64)},\t/* {terms[i]}: {mults[i]["desc"]} */') + +# Pretty-print a polynomial. +def pprint_poly(prefix, poly): + terms = [f'x^{i}' for i in reversed(range(poly.bit_length())) + if (poly & (1 << i)) != 0] + j = 0 + while j < len(terms): + s = prefix + terms[j] + (' +' if j < len(terms) - 1 else '') + j += 1 + while j < len(terms) and len(s) < 73: + s += ' ' + terms[j] + (' +' if j < len(terms) - 1 else '') + j += 1 + print(s) + prefix = ' * ' + (' ' * (len(prefix) - 3)) + +# Print a comment describing constants generated for the given CRC variant. +def print_header(variant, what): + print('/*') + s = f'{"least" if variant.lsb else "most"}-significant-bit-first CRC-{variant.bits}' + print(f' * {what} generated for {s} using') + pprint_poly(' * G(x) = ', variant.G) + print(' */') + +class CrcVariant: + def __init__(self, bits, generator_poly, bit_order): + self.bits = bits + if bit_order not in ['lsb', 'msb']: + raise ValueError('Invalid value for bit_order') + self.lsb = bit_order == 'lsb' + self.name = f'crc{bits}_{bit_order}_0x{generator_poly:0{(2*bits+7)//8}x}' + if self.lsb: + generator_poly = bitreflect(generator_poly, bits) + self.G = generator_poly ^ (1 << bits) + +# Generate tables for CRC computation using the "slice-by-N" method. +# N=1 corresponds to the traditional byte-at-a-time table. +def gen_slicebyN_tables(variants, n): + for v in variants: + print('') + print_header(v, f'Slice-by-{n} CRC table') + print(f'static const u{v.bits} __maybe_unused {v.name}_table[{256*n}] = {{') + s = '' + for i in range(256 * n): + # The i'th table entry is the CRC of the message consisting of byte + # i % 256 followed by i // 256 zero bytes. + poly = (bitreflect(i % 256, 8) if v.lsb else i % 256) << (v.bits + 8*(i//256)) + next_entry = fmt_poly(v, reduce(poly, v.G), v.bits) + ',' + if len(s + next_entry) > 71: + print(f'\t{s}') + s = '' + s += (' ' if s else '') + next_entry + if s: + print(f'\t{s}') + print('};') + +def print_riscv_const(v, bits_per_long, name, val, desc): + print(f'\t.{name} = {fmt_poly(v, val, bits_per_long)}, /* {desc} */') + +def do_gen_riscv_clmul_consts(v, bits_per_long): + (G, n, lsb) = (v.G, v.bits, v.lsb) + + pow_of_x = 3 * bits_per_long - (1 if lsb else 0) + print_riscv_const(v, bits_per_long, 'fold_across_2_longs_const_hi', + reduce(1 << pow_of_x, G), f'x^{pow_of_x} mod G') + pow_of_x = 2 * bits_per_long - (1 if lsb else 0) + print_riscv_const(v, bits_per_long, 'fold_across_2_longs_const_lo', + reduce(1 << pow_of_x, G), f'x^{pow_of_x} mod G') + + pow_of_x = bits_per_long - 1 + n + print_riscv_const(v, bits_per_long, 'barrett_reduction_const_1', + div(1 << pow_of_x, G), f'floor(x^{pow_of_x} / G)') + + val = G - (1 << n) + desc = f'G - x^{n}' + if lsb: + val <<= bits_per_long - n + desc = f'({desc}) * x^{bits_per_long - n}' + print_riscv_const(v, bits_per_long, 'barrett_reduction_const_2', val, desc) + +def gen_riscv_clmul_consts(variants): + print('') + print('struct crc_clmul_consts {'); + print('\tunsigned long fold_across_2_longs_const_hi;'); + print('\tunsigned long fold_across_2_longs_const_lo;'); + print('\tunsigned long barrett_reduction_const_1;'); + print('\tunsigned long barrett_reduction_const_2;'); + print('};'); + for v in variants: + print(''); + if v.bits > 32: + print_header(v, 'Constants') + print('#ifdef CONFIG_64BIT') + print(f'static const struct crc_clmul_consts {v.name}_consts __maybe_unused = {{') + do_gen_riscv_clmul_consts(v, 64) + print('};') + print('#endif') + else: + print_header(v, 'Constants') + print(f'static const struct crc_clmul_consts {v.name}_consts __maybe_unused = {{') + print('#ifdef CONFIG_64BIT') + do_gen_riscv_clmul_consts(v, 64) + print('#else') + do_gen_riscv_clmul_consts(v, 32) + print('#endif') + print('};') + +# Generate constants for carryless multiplication based CRC computation. +def gen_x86_pclmul_consts(variants): + # These are the distances, in bits, to generate folding constants for. + FOLD_DISTANCES = [2048, 1024, 512, 256, 128] + + for v in variants: + (G, n, lsb) = (v.G, v.bits, v.lsb) + print('') + print_header(v, 'CRC folding constants') + print('static const struct {') + if not lsb: + print('\tu8 bswap_mask[16];') + for i in FOLD_DISTANCES: + print(f'\tu64 fold_across_{i}_bits_consts[2];') + print('\tu8 shuf_table[48];') + print('\tu64 barrett_reduction_consts[2];') + print(f'}} {v.name}_consts ____cacheline_aligned __maybe_unused = {{') + + # Byte-reflection mask, needed for msb-first CRCs + if not lsb: + print('\t.bswap_mask = {' + ', '.join(str(i) for i in reversed(range(16))) + '},') + + # Fold constants for all distances down to 128 bits + for i in FOLD_DISTANCES: + print(f'\t.fold_across_{i}_bits_consts = {{') + # Given 64x64 => 128 bit carryless multiplication instructions, two + # 64-bit fold constants are needed per "fold distance" i: one for + # HI64_TERMS that is basically x^(i+64) mod G and one for LO64_TERMS + # that is basically x^i mod G. The exact values however undergo a + # couple adjustments, described below. + mults = [] + for j in [64, 0]: + pow_of_x = i + j + if lsb: + # Each 64x64 => 128 bit carryless multiplication instruction + # actually generates a 127-bit product in physical bits 0 + # through 126, which in the lsb-first case represent the + # coefficients of x^1 through x^127, not x^0 through x^126. + # Thus in the lsb-first case, each such instruction + # implicitly adds an extra factor of x. The below removes a + # factor of x from each constant to compensate for this. + # For n < 64 the x could be removed from either the reduced + # part or unreduced part, but for n == 64 the reduced part + # is the only option. Just always use the reduced part. + pow_of_x -= 1 + # Make a factor of x^(64-n) be applied unreduced rather than + # reduced, to cause the product to use only the x^(64-n) and + # higher terms and always be zero in the lower terms. Usually + # this makes no difference as it does not affect the product's + # congruence class mod G and the constant remains 64-bit, but + # part of the final reduction from 128 bits does rely on this + # property when it reuses one of the constants. + pow_of_x -= 64 - n + mults.append({ 'val': reduce(1 << pow_of_x, G) << (64 - n), + 'desc': f'(x^{pow_of_x} mod G) * x^{64-n}' }) + print_mult_pair(v, mults) + print('\t},') + + # Shuffle table for handling 1..15 bytes at end + print('\t.shuf_table = {') + print('\t\t' + (16*'-1, ').rstrip()) + print('\t\t' + ''.join(f'{i:2}, ' for i in range(16)).rstrip()) + print('\t\t' + (16*'-1, ').rstrip()) + print('\t},') + + # Barrett reduction constants for reducing 128 bits to the final CRC + print('\t.barrett_reduction_consts = {') + mults = [] + + val = div(1 << (63+n), G) + desc = f'floor(x^{63+n} / G)' + if not lsb: + val = (val << 1) - (1 << 64) + desc = f'({desc} * x) - x^64' + mults.append({ 'val': val, 'desc': desc }) + + val = G - (1 << n) + desc = f'G - x^{n}' + if lsb and n == 64: + assert (val & 1) != 0 # The x^0 term should always be nonzero. + val >>= 1 + desc = f'({desc} - x^0) / x' + else: + pow_of_x = 64 - n - (1 if lsb else 0) + val <<= pow_of_x + desc = f'({desc}) * x^{pow_of_x}' + mults.append({ 'val': val, 'desc': desc }) + + print_mult_pair(v, mults) + print('\t},') + + print('};') + +def parse_crc_variants(vars_string): + variants = [] + for var_string in vars_string.split(','): + bits, bit_order, generator_poly = var_string.split('_') + assert bits.startswith('crc') + bits = int(bits.removeprefix('crc')) + assert generator_poly.startswith('0x') + generator_poly = generator_poly.removeprefix('0x') + assert len(generator_poly) % 2 == 0 + generator_poly = int(generator_poly, 16) + variants.append(CrcVariant(bits, generator_poly, bit_order)) + return variants + +if len(sys.argv) != 3: + sys.stderr.write(f'Usage: {sys.argv[0]} CONSTS_TYPE[,CONSTS_TYPE]... CRC_VARIANT[,CRC_VARIANT]...\n') + sys.stderr.write(' CONSTS_TYPE can be sliceby[1-8], riscv_clmul, or x86_pclmul\n') + sys.stderr.write(' CRC_VARIANT is crc${num_bits}_${bit_order}_${generator_poly_as_hex}\n') + sys.stderr.write(' E.g. crc16_msb_0x8bb7 or crc32_lsb_0xedb88320\n') + sys.stderr.write(' Polynomial must use the given bit_order and exclude x^{num_bits}\n') + sys.exit(1) + +print('/* SPDX-License-Identifier: GPL-2.0-or-later */') +print('/*') +print(' * CRC constants generated by:') +print(' *') +print(f' *\t{sys.argv[0]} {" ".join(sys.argv[1:])}') +print(' *') +print(' * Do not edit manually.') +print(' */') +consts_types = sys.argv[1].split(',') +variants = parse_crc_variants(sys.argv[2]) +for consts_type in consts_types: + if consts_type.startswith('sliceby'): + gen_slicebyN_tables(variants, int(consts_type.removeprefix('sliceby'))) + elif consts_type == 'riscv_clmul': + gen_riscv_clmul_consts(variants) + elif consts_type == 'x86_pclmul': + gen_x86_pclmul_consts(variants) + else: + raise ValueError(f'Unknown consts_type: {consts_type}') diff --git a/scripts/gen_packed_field_checks.c b/scripts/gen_packed_field_checks.c new file mode 100644 index 000000000000..60042b7616ee --- /dev/null +++ b/scripts/gen_packed_field_checks.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2024, Intel Corporation +#include <stdbool.h> +#include <stdio.h> + +#define MAX_PACKED_FIELD_SIZE 50 + +int main(int argc, char **argv) +{ + /* The first macro doesn't need a 'do {} while(0)' loop */ + printf("#define CHECK_PACKED_FIELDS_1(fields) \\\n"); + printf("\tCHECK_PACKED_FIELD(fields, 0)\n\n"); + + /* Remaining macros require a do/while loop, and are implemented + * recursively by calling the previous iteration's macro. + */ + for (int i = 2; i <= MAX_PACKED_FIELD_SIZE; i++) { + printf("#define CHECK_PACKED_FIELDS_%d(fields) do { \\\n", i); + printf("\tCHECK_PACKED_FIELDS_%d(fields); \\\n", i - 1); + printf("\tCHECK_PACKED_FIELD(fields, %d); \\\n", i - 1); + printf("} while (0)\n\n"); + } + + printf("#define CHECK_PACKED_FIELDS(fields) \\\n"); + + for (int i = 1; i <= MAX_PACKED_FIELD_SIZE; i++) + printf("\t__builtin_choose_expr(ARRAY_SIZE(fields) == %d, ({ CHECK_PACKED_FIELDS_%d(fields); }), \\\n", + i, i); + + printf("\t({ BUILD_BUG_ON_MSG(1, \"CHECK_PACKED_FIELDS() must be regenerated to support array sizes larger than %d.\"); }) \\\n", + MAX_PACKED_FIELD_SIZE); + + for (int i = 1; i <= MAX_PACKED_FIELD_SIZE; i++) + printf(")"); + + printf("\n"); +} diff --git a/scripts/gendwarfksyms/.gitignore b/scripts/gendwarfksyms/.gitignore new file mode 100644 index 000000000000..0927f8d3cd96 --- /dev/null +++ b/scripts/gendwarfksyms/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +/gendwarfksyms diff --git a/scripts/gendwarfksyms/Makefile b/scripts/gendwarfksyms/Makefile new file mode 100644 index 000000000000..6334c7d3c4d5 --- /dev/null +++ b/scripts/gendwarfksyms/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +hostprogs-always-y += gendwarfksyms + +gendwarfksyms-objs += gendwarfksyms.o +gendwarfksyms-objs += cache.o +gendwarfksyms-objs += die.o +gendwarfksyms-objs += dwarf.o +gendwarfksyms-objs += kabi.o +gendwarfksyms-objs += symbols.o +gendwarfksyms-objs += types.o + +HOSTLDLIBS_gendwarfksyms := -ldw -lelf -lz diff --git a/scripts/gendwarfksyms/cache.c b/scripts/gendwarfksyms/cache.c new file mode 100644 index 000000000000..c9c19b86a686 --- /dev/null +++ b/scripts/gendwarfksyms/cache.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#include "gendwarfksyms.h" + +struct cache_item { + unsigned long key; + int value; + struct hlist_node hash; +}; + +void cache_set(struct cache *cache, unsigned long key, int value) +{ + struct cache_item *ci; + + ci = xmalloc(sizeof(struct cache_item)); + ci->key = key; + ci->value = value; + hash_add(cache->cache, &ci->hash, hash_32(key)); +} + +int cache_get(struct cache *cache, unsigned long key) +{ + struct cache_item *ci; + + hash_for_each_possible(cache->cache, ci, hash, hash_32(key)) { + if (ci->key == key) + return ci->value; + } + + return -1; +} + +void cache_init(struct cache *cache) +{ + hash_init(cache->cache); +} + +void cache_free(struct cache *cache) +{ + struct hlist_node *tmp; + struct cache_item *ci; + + hash_for_each_safe(cache->cache, ci, tmp, hash) { + free(ci); + } + + hash_init(cache->cache); +} diff --git a/scripts/gendwarfksyms/die.c b/scripts/gendwarfksyms/die.c new file mode 100644 index 000000000000..6183bbbe7b54 --- /dev/null +++ b/scripts/gendwarfksyms/die.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#include <string.h> +#include "gendwarfksyms.h" + +#define DIE_HASH_BITS 16 + +/* {die->addr, state} -> struct die * */ +static HASHTABLE_DEFINE(die_map, 1 << DIE_HASH_BITS); + +static unsigned int map_hits; +static unsigned int map_misses; + +static inline unsigned int die_hash(uintptr_t addr, enum die_state state) +{ + return hash_32(addr_hash(addr) ^ (unsigned int)state); +} + +static void init_die(struct die *cd) +{ + cd->state = DIE_INCOMPLETE; + cd->mapped = false; + cd->fqn = NULL; + cd->tag = -1; + cd->addr = 0; + INIT_LIST_HEAD(&cd->fragments); +} + +static struct die *create_die(Dwarf_Die *die, enum die_state state) +{ + struct die *cd; + + cd = xmalloc(sizeof(struct die)); + init_die(cd); + cd->addr = (uintptr_t)die->addr; + + hash_add(die_map, &cd->hash, die_hash(cd->addr, state)); + return cd; +} + +int __die_map_get(uintptr_t addr, enum die_state state, struct die **res) +{ + struct die *cd; + + hash_for_each_possible(die_map, cd, hash, die_hash(addr, state)) { + if (cd->addr == addr && cd->state == state) { + *res = cd; + return 0; + } + } + + return -1; +} + +struct die *die_map_get(Dwarf_Die *die, enum die_state state) +{ + struct die *cd; + + if (__die_map_get((uintptr_t)die->addr, state, &cd) == 0) { + map_hits++; + return cd; + } + + map_misses++; + return create_die(die, state); +} + +static void reset_die(struct die *cd) +{ + struct die_fragment *tmp; + struct die_fragment *df; + + list_for_each_entry_safe(df, tmp, &cd->fragments, list) { + if (df->type == FRAGMENT_STRING) + free(df->data.str); + free(df); + } + + if (cd->fqn && *cd->fqn) + free(cd->fqn); + init_die(cd); +} + +void die_map_for_each(die_map_callback_t func, void *arg) +{ + struct hlist_node *tmp; + struct die *cd; + + hash_for_each_safe(die_map, cd, tmp, hash) { + func(cd, arg); + } +} + +void die_map_free(void) +{ + struct hlist_node *tmp; + unsigned int stats[DIE_LAST + 1]; + struct die *cd; + int i; + + memset(stats, 0, sizeof(stats)); + + hash_for_each_safe(die_map, cd, tmp, hash) { + stats[cd->state]++; + reset_die(cd); + free(cd); + } + hash_init(die_map); + + if (map_hits + map_misses > 0) + debug("hits %u, misses %u (hit rate %.02f%%)", map_hits, + map_misses, + (100.0f * map_hits) / (map_hits + map_misses)); + + for (i = 0; i <= DIE_LAST; i++) + debug("%s: %u entries", die_state_name(i), stats[i]); +} + +static struct die_fragment *append_item(struct die *cd) +{ + struct die_fragment *df; + + df = xmalloc(sizeof(struct die_fragment)); + df->type = FRAGMENT_EMPTY; + list_add_tail(&df->list, &cd->fragments); + return df; +} + +void die_map_add_string(struct die *cd, const char *str) +{ + struct die_fragment *df; + + if (!cd) + return; + + df = append_item(cd); + df->data.str = xstrdup(str); + df->type = FRAGMENT_STRING; +} + +void die_map_add_linebreak(struct die *cd, int linebreak) +{ + struct die_fragment *df; + + if (!cd) + return; + + df = append_item(cd); + df->data.linebreak = linebreak; + df->type = FRAGMENT_LINEBREAK; +} + +void die_map_add_die(struct die *cd, struct die *child) +{ + struct die_fragment *df; + + if (!cd) + return; + + df = append_item(cd); + df->data.addr = child->addr; + df->type = FRAGMENT_DIE; +} diff --git a/scripts/gendwarfksyms/dwarf.c b/scripts/gendwarfksyms/dwarf.c new file mode 100644 index 000000000000..13ea7bf1ae7d --- /dev/null +++ b/scripts/gendwarfksyms/dwarf.c @@ -0,0 +1,1183 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#define _GNU_SOURCE +#include <assert.h> +#include <inttypes.h> +#include <stdarg.h> +#include "gendwarfksyms.h" + +/* See get_union_kabi_status */ +#define KABI_PREFIX "__kabi_" +#define KABI_PREFIX_LEN (sizeof(KABI_PREFIX) - 1) +#define KABI_RESERVED_PREFIX "reserved" +#define KABI_RESERVED_PREFIX_LEN (sizeof(KABI_RESERVED_PREFIX) - 1) +#define KABI_RENAMED_PREFIX "renamed" +#define KABI_RENAMED_PREFIX_LEN (sizeof(KABI_RENAMED_PREFIX) - 1) +#define KABI_IGNORED_PREFIX "ignored" +#define KABI_IGNORED_PREFIX_LEN (sizeof(KABI_IGNORED_PREFIX) - 1) + +static inline bool is_kabi_prefix(const char *name) +{ + return name && !strncmp(name, KABI_PREFIX, KABI_PREFIX_LEN); +} + +enum kabi_status { + /* >0 to stop DIE processing */ + KABI_NORMAL = 1, + KABI_RESERVED, + KABI_IGNORED, +}; + +static bool do_linebreak; +static int indentation_level; + +/* Line breaks and indentation for pretty-printing */ +static void process_linebreak(struct die *cache, int n) +{ + indentation_level += n; + do_linebreak = true; + die_map_add_linebreak(cache, n); +} + +#define DEFINE_GET_ATTR(attr, type) \ + static bool get_##attr##_attr(Dwarf_Die *die, unsigned int id, \ + type *value) \ + { \ + Dwarf_Attribute da; \ + return dwarf_attr(die, id, &da) && \ + !dwarf_form##attr(&da, value); \ + } + +DEFINE_GET_ATTR(flag, bool) +DEFINE_GET_ATTR(udata, Dwarf_Word) + +static bool get_ref_die_attr(Dwarf_Die *die, unsigned int id, Dwarf_Die *value) +{ + Dwarf_Attribute da; + + /* dwarf_formref_die returns a pointer instead of an error value. */ + return dwarf_attr(die, id, &da) && dwarf_formref_die(&da, value); +} + +#define DEFINE_GET_STRING_ATTR(attr) \ + static const char *get_##attr##_attr(Dwarf_Die *die) \ + { \ + Dwarf_Attribute da; \ + if (dwarf_attr(die, DW_AT_##attr, &da)) \ + return dwarf_formstring(&da); \ + return NULL; \ + } + +DEFINE_GET_STRING_ATTR(name) +DEFINE_GET_STRING_ATTR(linkage_name) + +static const char *get_symbol_name(Dwarf_Die *die) +{ + const char *name; + + /* rustc uses DW_AT_linkage_name for exported symbols */ + name = get_linkage_name_attr(die); + if (!name) + name = get_name_attr(die); + + return name; +} + +static bool match_export_symbol(struct state *state, Dwarf_Die *die) +{ + Dwarf_Die *source = die; + Dwarf_Die origin; + + /* If the DIE has an abstract origin, use it for type information. */ + if (get_ref_die_attr(die, DW_AT_abstract_origin, &origin)) + source = &origin; + + state->sym = symbol_get(get_symbol_name(die)); + + /* Look up using the origin name if there are no matches. */ + if (!state->sym && source != die) + state->sym = symbol_get(get_symbol_name(source)); + + state->die = *source; + return !!state->sym; +} + +/* DW_AT_decl_file -> struct srcfile */ +static struct cache srcfile_cache; + +static bool is_definition_private(Dwarf_Die *die) +{ + Dwarf_Word filenum; + Dwarf_Files *files; + Dwarf_Die cudie; + const char *s; + int res; + + /* + * Definitions in .c files cannot change the public ABI, + * so consider them private. + */ + if (!get_udata_attr(die, DW_AT_decl_file, &filenum)) + return false; + + res = cache_get(&srcfile_cache, filenum); + if (res >= 0) + return !!res; + + if (!dwarf_cu_die(die->cu, &cudie, NULL, NULL, NULL, NULL, NULL, NULL)) + error("dwarf_cu_die failed: '%s'", dwarf_errmsg(-1)); + + if (dwarf_getsrcfiles(&cudie, &files, NULL)) + error("dwarf_getsrcfiles failed: '%s'", dwarf_errmsg(-1)); + + s = dwarf_filesrc(files, filenum, NULL, NULL); + if (!s) + error("dwarf_filesrc failed: '%s'", dwarf_errmsg(-1)); + + s = strrchr(s, '.'); + res = s && !strcmp(s, ".c"); + cache_set(&srcfile_cache, filenum, res); + + return !!res; +} + +static bool is_kabi_definition(struct die *cache, Dwarf_Die *die) +{ + bool value; + + if (get_flag_attr(die, DW_AT_declaration, &value) && value) + return false; + + if (kabi_is_declonly(cache->fqn)) + return false; + + return !is_definition_private(die); +} + +/* + * Type string processing + */ +static void process(struct die *cache, const char *s) +{ + s = s ?: "<null>"; + + if (dump_dies && do_linebreak) { + fputs("\n", stderr); + for (int i = 0; i < indentation_level; i++) + fputs(" ", stderr); + do_linebreak = false; + } + if (dump_dies) + fputs(s, stderr); + + if (cache) + die_debug_r("cache %p string '%s'", cache, s); + die_map_add_string(cache, s); +} + +#define MAX_FMT_BUFFER_SIZE 128 + +static void process_fmt(struct die *cache, const char *fmt, ...) +{ + char buf[MAX_FMT_BUFFER_SIZE]; + va_list args; + + va_start(args, fmt); + + if (checkp(vsnprintf(buf, sizeof(buf), fmt, args)) >= sizeof(buf)) + error("vsnprintf overflow: increase MAX_FMT_BUFFER_SIZE"); + + process(cache, buf); + va_end(args); +} + +static void update_fqn(struct die *cache, Dwarf_Die *die) +{ + struct die *fqn; + + if (!cache->fqn) { + if (!__die_map_get((uintptr_t)die->addr, DIE_FQN, &fqn) && + *fqn->fqn) + cache->fqn = xstrdup(fqn->fqn); + else + cache->fqn = ""; + } +} + +static void process_fqn(struct die *cache, Dwarf_Die *die) +{ + update_fqn(cache, die); + if (*cache->fqn) + process(cache, " "); + process(cache, cache->fqn); +} + +#define DEFINE_PROCESS_UDATA_ATTRIBUTE(attribute) \ + static void process_##attribute##_attr(struct die *cache, \ + Dwarf_Die *die) \ + { \ + Dwarf_Word value; \ + if (get_udata_attr(die, DW_AT_##attribute, &value)) \ + process_fmt(cache, " " #attribute "(%" PRIu64 ")", \ + value); \ + } + +DEFINE_PROCESS_UDATA_ATTRIBUTE(accessibility) +DEFINE_PROCESS_UDATA_ATTRIBUTE(alignment) +DEFINE_PROCESS_UDATA_ATTRIBUTE(bit_size) +DEFINE_PROCESS_UDATA_ATTRIBUTE(encoding) +DEFINE_PROCESS_UDATA_ATTRIBUTE(data_bit_offset) +DEFINE_PROCESS_UDATA_ATTRIBUTE(data_member_location) +DEFINE_PROCESS_UDATA_ATTRIBUTE(discr_value) + +static void process_byte_size_attr(struct die *cache, Dwarf_Die *die) +{ + Dwarf_Word value; + unsigned long override; + + if (get_udata_attr(die, DW_AT_byte_size, &value)) { + if (stable && kabi_get_byte_size(cache->fqn, &override)) + value = override; + + process_fmt(cache, " byte_size(%" PRIu64 ")", value); + } +} + +/* Match functions -- die_match_callback_t */ +#define DEFINE_MATCH(type) \ + static bool match_##type##_type(Dwarf_Die *die) \ + { \ + return dwarf_tag(die) == DW_TAG_##type##_type; \ + } + +DEFINE_MATCH(enumerator) +DEFINE_MATCH(formal_parameter) +DEFINE_MATCH(member) +DEFINE_MATCH(subrange) + +bool match_all(Dwarf_Die *die) +{ + return true; +} + +int process_die_container(struct state *state, struct die *cache, + Dwarf_Die *die, die_callback_t func, + die_match_callback_t match) +{ + Dwarf_Die current; + int res; + + /* Track the first item in lists. */ + if (state) + state->first_list_item = true; + + res = checkp(dwarf_child(die, ¤t)); + while (!res) { + if (match(¤t)) { + /* <0 = error, 0 = continue, >0 = stop */ + res = checkp(func(state, cache, ¤t)); + if (res) + goto out; + } + + res = checkp(dwarf_siblingof(¤t, ¤t)); + } + + res = 0; +out: + if (state) + state->first_list_item = false; + + return res; +} + +static int process_type(struct state *state, struct die *parent, + Dwarf_Die *die); + +static void process_type_attr(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + Dwarf_Die type; + + if (get_ref_die_attr(die, DW_AT_type, &type)) { + check(process_type(state, cache, &type)); + return; + } + + /* Compilers can omit DW_AT_type -- print out 'void' to clarify */ + process(cache, "base_type void"); +} + +static void process_list_comma(struct state *state, struct die *cache) +{ + if (state->first_list_item) { + state->first_list_item = false; + } else { + process(cache, " ,"); + process_linebreak(cache, 0); + } +} + +/* Comma-separated with DW_AT_type */ +static void __process_list_type(struct state *state, struct die *cache, + Dwarf_Die *die, const char *type) +{ + const char *name = get_name_attr(die); + + if (stable) { + if (is_kabi_prefix(name)) + name = NULL; + state->kabi.orig_name = NULL; + } + + process_list_comma(state, cache); + process(cache, type); + process_type_attr(state, cache, die); + + if (stable && state->kabi.orig_name) + name = state->kabi.orig_name; + if (name) { + process(cache, " "); + process(cache, name); + } + + process_accessibility_attr(cache, die); + process_bit_size_attr(cache, die); + process_data_bit_offset_attr(cache, die); + process_data_member_location_attr(cache, die); +} + +#define DEFINE_PROCESS_LIST_TYPE(type) \ + static void process_##type##_type(struct state *state, \ + struct die *cache, Dwarf_Die *die) \ + { \ + __process_list_type(state, cache, die, #type " "); \ + } + +DEFINE_PROCESS_LIST_TYPE(formal_parameter) +DEFINE_PROCESS_LIST_TYPE(member) + +/* Container types with DW_AT_type */ +static void __process_type(struct state *state, struct die *cache, + Dwarf_Die *die, const char *type) +{ + process(cache, type); + process_fqn(cache, die); + process(cache, " {"); + process_linebreak(cache, 1); + process_type_attr(state, cache, die); + process_linebreak(cache, -1); + process(cache, "}"); + process_byte_size_attr(cache, die); + process_alignment_attr(cache, die); +} + +#define DEFINE_PROCESS_TYPE(type) \ + static void process_##type##_type(struct state *state, \ + struct die *cache, Dwarf_Die *die) \ + { \ + __process_type(state, cache, die, #type "_type"); \ + } + +DEFINE_PROCESS_TYPE(atomic) +DEFINE_PROCESS_TYPE(const) +DEFINE_PROCESS_TYPE(immutable) +DEFINE_PROCESS_TYPE(packed) +DEFINE_PROCESS_TYPE(pointer) +DEFINE_PROCESS_TYPE(reference) +DEFINE_PROCESS_TYPE(restrict) +DEFINE_PROCESS_TYPE(rvalue_reference) +DEFINE_PROCESS_TYPE(shared) +DEFINE_PROCESS_TYPE(template_type_parameter) +DEFINE_PROCESS_TYPE(volatile) +DEFINE_PROCESS_TYPE(typedef) + +static void process_subrange_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + Dwarf_Word count = 0; + + if (get_udata_attr(die, DW_AT_count, &count)) + process_fmt(cache, "[%" PRIu64 "]", count); + else if (get_udata_attr(die, DW_AT_upper_bound, &count)) + process_fmt(cache, "[%" PRIu64 "]", count + 1); + else + process(cache, "[]"); +} + +static void process_array_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + process(cache, "array_type"); + /* Array size */ + check(process_die_container(state, cache, die, process_type, + match_subrange_type)); + process(cache, " {"); + process_linebreak(cache, 1); + process_type_attr(state, cache, die); + process_linebreak(cache, -1); + process(cache, "}"); +} + +static void __process_subroutine_type(struct state *state, struct die *cache, + Dwarf_Die *die, const char *type) +{ + process(cache, type); + process(cache, " ("); + process_linebreak(cache, 1); + /* Parameters */ + check(process_die_container(state, cache, die, process_type, + match_formal_parameter_type)); + process_linebreak(cache, -1); + process(cache, ")"); + process_linebreak(cache, 0); + /* Return type */ + process(cache, "-> "); + process_type_attr(state, cache, die); +} + +static void process_subroutine_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + __process_subroutine_type(state, cache, die, "subroutine_type"); +} + +static void process_variant_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + process_list_comma(state, cache); + process(cache, "variant {"); + process_linebreak(cache, 1); + check(process_die_container(state, cache, die, process_type, + match_member_type)); + process_linebreak(cache, -1); + process(cache, "}"); + process_discr_value_attr(cache, die); +} + +static void process_variant_part_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + process_list_comma(state, cache); + process(cache, "variant_part {"); + process_linebreak(cache, 1); + check(process_die_container(state, cache, die, process_type, + match_all)); + process_linebreak(cache, -1); + process(cache, "}"); +} + +static int get_kabi_status(Dwarf_Die *die, const char **suffix) +{ + const char *name = get_name_attr(die); + + if (suffix) + *suffix = NULL; + + if (is_kabi_prefix(name)) { + name += KABI_PREFIX_LEN; + + if (!strncmp(name, KABI_RESERVED_PREFIX, + KABI_RESERVED_PREFIX_LEN)) + return KABI_RESERVED; + if (!strncmp(name, KABI_IGNORED_PREFIX, + KABI_IGNORED_PREFIX_LEN)) + return KABI_IGNORED; + + if (!strncmp(name, KABI_RENAMED_PREFIX, + KABI_RENAMED_PREFIX_LEN)) { + if (suffix) { + name += KABI_RENAMED_PREFIX_LEN; + *suffix = name; + } + return KABI_RESERVED; + } + } + + return KABI_NORMAL; +} + +static int check_struct_member_kabi_status(struct state *state, + struct die *__unused, Dwarf_Die *die) +{ + int res; + + assert(dwarf_tag(die) == DW_TAG_member_type); + + /* + * If the union member is a struct, expect the __kabi field to + * be the first member of the structure, i.e..: + * + * union { + * type new_member; + * struct { + * type __kabi_field; + * } + * }; + */ + res = get_kabi_status(die, &state->kabi.orig_name); + + if (res == KABI_RESERVED && + !get_ref_die_attr(die, DW_AT_type, &state->kabi.placeholder)) + error("structure member missing a type?"); + + return res; +} + +static int check_union_member_kabi_status(struct state *state, + struct die *__unused, Dwarf_Die *die) +{ + Dwarf_Die type; + int res; + + assert(dwarf_tag(die) == DW_TAG_member_type); + + if (!get_ref_die_attr(die, DW_AT_type, &type)) + error("union member missing a type?"); + + /* + * We expect a union with two members. Check if either of them + * has a __kabi name prefix, i.e.: + * + * union { + * ... + * type memberN; // <- type, N = {0,1} + * ... + * }; + * + * The member can also be a structure type, in which case we'll + * check the first structure member. + * + * In any case, stop processing after we've seen two members. + */ + res = get_kabi_status(die, &state->kabi.orig_name); + + if (res == KABI_RESERVED) + state->kabi.placeholder = type; + if (res != KABI_NORMAL) + return res; + + if (dwarf_tag(&type) == DW_TAG_structure_type) + res = checkp(process_die_container( + state, NULL, &type, check_struct_member_kabi_status, + match_member_type)); + + if (res <= KABI_NORMAL && ++state->kabi.members < 2) + return 0; /* Continue */ + + return res; +} + +static int get_union_kabi_status(Dwarf_Die *die, Dwarf_Die *placeholder, + const char **orig_name) +{ + struct state state; + int res; + + if (!stable) + return KABI_NORMAL; + + /* + * To maintain a stable kABI, distributions may choose to reserve + * space in structs for later use by adding placeholder members, + * for example: + * + * struct s { + * u32 a; + * // an 8-byte placeholder for future use + * u64 __kabi_reserved_0; + * }; + * + * When the reserved member is taken into use, the type change + * would normally cause the symbol version to change as well, but + * if the replacement uses the following convention, gendwarfksyms + * continues to use the placeholder type for versioning instead, + * thus maintaining the same symbol version: + * + * struct s { + * u32 a; + * union { + * // placeholder replaced with a new member `b` + * struct t b; + * struct { + * // the placeholder type that is still + * // used for versioning + * u64 __kabi_reserved_0; + * }; + * }; + * }; + * + * I.e., as long as the replaced member is in a union, and the + * placeholder has a __kabi_reserved name prefix, we'll continue + * to use the placeholder type (here u64) for version calculation + * instead of the union type. + * + * It's also possible to ignore new members from versioning if + * they've been added to alignment holes, for example, by + * including them in a union with another member that uses the + * __kabi_ignored name prefix: + * + * struct s { + * u32 a; + * // an alignment hole is used to add `n` + * union { + * u32 n; + * // hide the entire union member from versioning + * u8 __kabi_ignored_0; + * }; + * u64 b; + * }; + * + * Note that the user of this feature is responsible for ensuring + * that the structure actually remains ABI compatible. + */ + memset(&state.kabi, 0, sizeof(struct kabi_state)); + + res = checkp(process_die_container(&state, NULL, die, + check_union_member_kabi_status, + match_member_type)); + + if (res == KABI_RESERVED) { + if (placeholder) + *placeholder = state.kabi.placeholder; + if (orig_name) + *orig_name = state.kabi.orig_name; + } + + return res; +} + +static bool is_kabi_ignored(Dwarf_Die *die) +{ + Dwarf_Die type; + + if (!stable) + return false; + + if (!get_ref_die_attr(die, DW_AT_type, &type)) + error("member missing a type?"); + + return dwarf_tag(&type) == DW_TAG_union_type && + checkp(get_union_kabi_status(&type, NULL, NULL)) == KABI_IGNORED; +} + +static int ___process_structure_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + switch (dwarf_tag(die)) { + case DW_TAG_member: + if (is_kabi_ignored(die)) + return 0; + return check(process_type(state, cache, die)); + case DW_TAG_variant_part: + return check(process_type(state, cache, die)); + case DW_TAG_class_type: + case DW_TAG_enumeration_type: + case DW_TAG_structure_type: + case DW_TAG_template_type_parameter: + case DW_TAG_union_type: + case DW_TAG_subprogram: + /* Skip non-member types, including member functions */ + return 0; + default: + error("unexpected structure_type child: %x", dwarf_tag(die)); + } +} + +static void __process_structure_type(struct state *state, struct die *cache, + Dwarf_Die *die, const char *type, + die_callback_t process_func, + die_match_callback_t match_func) +{ + bool expand; + + process(cache, type); + process_fqn(cache, die); + process(cache, " {"); + process_linebreak(cache, 1); + + expand = state->expand.expand && is_kabi_definition(cache, die); + + if (expand) { + state->expand.current_fqn = cache->fqn; + check(process_die_container(state, cache, die, process_func, + match_func)); + } + + process_linebreak(cache, -1); + process(cache, "}"); + + if (expand) { + process_byte_size_attr(cache, die); + process_alignment_attr(cache, die); + } +} + +#define DEFINE_PROCESS_STRUCTURE_TYPE(structure) \ + static void process_##structure##_type( \ + struct state *state, struct die *cache, Dwarf_Die *die) \ + { \ + __process_structure_type(state, cache, die, \ + #structure "_type", \ + ___process_structure_type, \ + match_all); \ + } + +DEFINE_PROCESS_STRUCTURE_TYPE(class) +DEFINE_PROCESS_STRUCTURE_TYPE(structure) + +static void process_union_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + Dwarf_Die placeholder; + + int res = checkp(get_union_kabi_status(die, &placeholder, + &state->kabi.orig_name)); + + if (res == KABI_RESERVED) + check(process_type(state, cache, &placeholder)); + if (res > KABI_NORMAL) + return; + + __process_structure_type(state, cache, die, "union_type", + ___process_structure_type, match_all); +} + +static void process_enumerator_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + bool overridden = false; + Dwarf_Word value; + + if (stable) { + /* Get the fqn before we process anything */ + update_fqn(cache, die); + + if (kabi_is_enumerator_ignored(state->expand.current_fqn, + cache->fqn)) + return; + + overridden = kabi_get_enumerator_value( + state->expand.current_fqn, cache->fqn, &value); + } + + process_list_comma(state, cache); + process(cache, "enumerator"); + process_fqn(cache, die); + + if (overridden || get_udata_attr(die, DW_AT_const_value, &value)) { + process(cache, " = "); + process_fmt(cache, "%" PRIu64, value); + } +} + +static void process_enumeration_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + __process_structure_type(state, cache, die, "enumeration_type", + process_type, match_enumerator_type); +} + +static void process_base_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + process(cache, "base_type"); + process_fqn(cache, die); + process_byte_size_attr(cache, die); + process_encoding_attr(cache, die); + process_alignment_attr(cache, die); +} + +static void process_unspecified_type(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + /* + * These can be emitted for stand-alone assembly code, which means we + * might run into them in vmlinux.o. + */ + process(cache, "unspecified_type"); +} + +static void process_cached(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + struct die_fragment *df; + Dwarf_Die child; + + list_for_each_entry(df, &cache->fragments, list) { + switch (df->type) { + case FRAGMENT_STRING: + die_debug_b("cache %p STRING '%s'", cache, + df->data.str); + process(NULL, df->data.str); + break; + case FRAGMENT_LINEBREAK: + process_linebreak(NULL, df->data.linebreak); + break; + case FRAGMENT_DIE: + if (!dwarf_die_addr_die(dwarf_cu_getdwarf(die->cu), + (void *)df->data.addr, &child)) + error("dwarf_die_addr_die failed"); + die_debug_b("cache %p DIE addr %" PRIxPTR " tag %x", + cache, df->data.addr, dwarf_tag(&child)); + check(process_type(state, NULL, &child)); + break; + default: + error("empty die_fragment"); + } + } +} + +static void state_init(struct state *state) +{ + state->expand.expand = true; + state->expand.current_fqn = NULL; + cache_init(&state->expansion_cache); +} + +static void expansion_state_restore(struct expansion_state *state, + struct expansion_state *saved) +{ + state->expand = saved->expand; + state->current_fqn = saved->current_fqn; +} + +static void expansion_state_save(struct expansion_state *state, + struct expansion_state *saved) +{ + expansion_state_restore(saved, state); +} + +static bool is_expanded_type(int tag) +{ + return tag == DW_TAG_class_type || tag == DW_TAG_structure_type || + tag == DW_TAG_union_type || tag == DW_TAG_enumeration_type; +} + +#define PROCESS_TYPE(type) \ + case DW_TAG_##type##_type: \ + process_##type##_type(state, cache, die); \ + break; + +static int process_type(struct state *state, struct die *parent, Dwarf_Die *die) +{ + enum die_state want_state = DIE_COMPLETE; + struct die *cache; + struct expansion_state saved; + int tag = dwarf_tag(die); + + expansion_state_save(&state->expand, &saved); + + /* + * Structures and enumeration types are expanded only once per + * exported symbol. This is sufficient for detecting ABI changes + * within the structure. + */ + if (is_expanded_type(tag)) { + if (cache_was_expanded(&state->expansion_cache, die->addr)) + state->expand.expand = false; + + if (state->expand.expand) + cache_mark_expanded(&state->expansion_cache, die->addr); + else + want_state = DIE_UNEXPANDED; + } + + /* + * If we have want_state already cached, use it instead of walking + * through DWARF. + */ + cache = die_map_get(die, want_state); + + if (cache->state == want_state) { + die_debug_g("cached addr %p tag %x -- %s", die->addr, tag, + die_state_name(cache->state)); + + process_cached(state, cache, die); + die_map_add_die(parent, cache); + + expansion_state_restore(&state->expand, &saved); + return 0; + } + + die_debug_g("addr %p tag %x -- %s -> %s", die->addr, tag, + die_state_name(cache->state), die_state_name(want_state)); + + switch (tag) { + /* Type modifiers */ + PROCESS_TYPE(atomic) + PROCESS_TYPE(const) + PROCESS_TYPE(immutable) + PROCESS_TYPE(packed) + PROCESS_TYPE(pointer) + PROCESS_TYPE(reference) + PROCESS_TYPE(restrict) + PROCESS_TYPE(rvalue_reference) + PROCESS_TYPE(shared) + PROCESS_TYPE(volatile) + /* Container types */ + PROCESS_TYPE(class) + PROCESS_TYPE(structure) + PROCESS_TYPE(union) + PROCESS_TYPE(enumeration) + /* Subtypes */ + PROCESS_TYPE(enumerator) + PROCESS_TYPE(formal_parameter) + PROCESS_TYPE(member) + PROCESS_TYPE(subrange) + PROCESS_TYPE(template_type_parameter) + PROCESS_TYPE(variant) + PROCESS_TYPE(variant_part) + /* Other types */ + PROCESS_TYPE(array) + PROCESS_TYPE(base) + PROCESS_TYPE(subroutine) + PROCESS_TYPE(typedef) + PROCESS_TYPE(unspecified) + default: + error("unexpected type: %x", tag); + } + + die_debug_r("parent %p cache %p die addr %p tag %x", parent, cache, + die->addr, tag); + + /* Update cache state and append to the parent (if any) */ + cache->tag = tag; + cache->state = want_state; + die_map_add_die(parent, cache); + + expansion_state_restore(&state->expand, &saved); + return 0; +} + +/* + * Exported symbol processing + */ +static struct die *get_symbol_cache(struct state *state, Dwarf_Die *die) +{ + struct die *cache; + + cache = die_map_get(die, DIE_SYMBOL); + + if (cache->state != DIE_INCOMPLETE) + return NULL; /* We already processed a symbol for this DIE */ + + cache->tag = dwarf_tag(die); + return cache; +} + +static void process_symbol(struct state *state, Dwarf_Die *die, + die_callback_t process_func) +{ + struct die *cache; + + symbol_set_die(state->sym, die); + + cache = get_symbol_cache(state, die); + if (!cache) + return; + + debug("%s", state->sym->name); + check(process_func(state, cache, die)); + cache->state = DIE_SYMBOL; + if (dump_dies) + fputs("\n", stderr); +} + +static int __process_subprogram(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + __process_subroutine_type(state, cache, die, "subprogram"); + return 0; +} + +static void process_subprogram(struct state *state, Dwarf_Die *die) +{ + process_symbol(state, die, __process_subprogram); +} + +static int __process_variable(struct state *state, struct die *cache, + Dwarf_Die *die) +{ + process(cache, "variable "); + process_type_attr(state, cache, die); + return 0; +} + +static void process_variable(struct state *state, Dwarf_Die *die) +{ + process_symbol(state, die, __process_variable); +} + +static void save_symbol_ptr(struct state *state) +{ + Dwarf_Die ptr_type; + Dwarf_Die type; + + if (!get_ref_die_attr(&state->die, DW_AT_type, &ptr_type) || + dwarf_tag(&ptr_type) != DW_TAG_pointer_type) + error("%s must be a pointer type!", + get_symbol_name(&state->die)); + + if (!get_ref_die_attr(&ptr_type, DW_AT_type, &type)) + error("%s pointer missing a type attribute?", + get_symbol_name(&state->die)); + + /* + * Save the symbol pointer DIE in case the actual symbol is + * missing from the DWARF. Clang, for example, intentionally + * omits external symbols from the debugging information. + */ + if (dwarf_tag(&type) == DW_TAG_subroutine_type) + symbol_set_ptr(state->sym, &type); + else + symbol_set_ptr(state->sym, &ptr_type); +} + +static int process_exported_symbols(struct state *unused, struct die *cache, + Dwarf_Die *die) +{ + int tag = dwarf_tag(die); + + switch (tag) { + /* Possible containers of exported symbols */ + case DW_TAG_namespace: + case DW_TAG_class_type: + case DW_TAG_structure_type: + return check(process_die_container( + NULL, cache, die, process_exported_symbols, match_all)); + + /* Possible exported symbols */ + case DW_TAG_subprogram: + case DW_TAG_variable: { + struct state state; + + if (!match_export_symbol(&state, die)) + return 0; + + state_init(&state); + + if (is_symbol_ptr(get_symbol_name(&state.die))) + save_symbol_ptr(&state); + else if (tag == DW_TAG_subprogram) + process_subprogram(&state, &state.die); + else + process_variable(&state, &state.die); + + cache_free(&state.expansion_cache); + return 0; + } + default: + return 0; + } +} + +static void process_symbol_ptr(struct symbol *sym, void *arg) +{ + struct state state; + Dwarf *dwarf = arg; + + if (sym->state != SYMBOL_UNPROCESSED || !sym->ptr_die_addr) + return; + + debug("%s", sym->name); + state_init(&state); + state.sym = sym; + + if (!dwarf_die_addr_die(dwarf, (void *)sym->ptr_die_addr, &state.die)) + error("dwarf_die_addr_die failed for symbol ptr: '%s'", + sym->name); + + if (dwarf_tag(&state.die) == DW_TAG_subroutine_type) + process_subprogram(&state, &state.die); + else + process_variable(&state, &state.die); + + cache_free(&state.expansion_cache); +} + +static int resolve_fqns(struct state *parent, struct die *unused, + Dwarf_Die *die) +{ + struct state state; + struct die *cache; + const char *name; + bool use_prefix; + char *prefix = NULL; + char *fqn = ""; + int tag; + + if (!__die_map_get((uintptr_t)die->addr, DIE_FQN, &cache)) + return 0; + + tag = dwarf_tag(die); + + /* + * Only namespaces and structures need to pass a prefix to the next + * scope. + */ + use_prefix = tag == DW_TAG_namespace || tag == DW_TAG_class_type || + tag == DW_TAG_structure_type; + + state.expand.current_fqn = NULL; + name = get_name_attr(die); + + if (parent && parent->expand.current_fqn && (use_prefix || name)) { + /* + * The fqn for the current DIE, and if needed, a prefix for the + * next scope. + */ + if (asprintf(&prefix, "%s::%s", parent->expand.current_fqn, + name ? name : "<anonymous>") < 0) + error("asprintf failed"); + + if (use_prefix) + state.expand.current_fqn = prefix; + + /* + * Use fqn only if the DIE has a name. Otherwise fqn will + * remain empty. + */ + if (name) { + fqn = prefix; + /* prefix will be freed by die_map. */ + prefix = NULL; + } + } else if (name) { + /* No prefix from the previous scope. Use only the name. */ + fqn = xstrdup(name); + + if (use_prefix) + state.expand.current_fqn = fqn; + } + + /* If the DIE has a non-empty name, cache it. */ + if (*fqn) { + cache = die_map_get(die, DIE_FQN); + /* Move ownership of fqn to die_map. */ + cache->fqn = fqn; + cache->state = DIE_FQN; + } + + check(process_die_container(&state, NULL, die, resolve_fqns, + match_all)); + + free(prefix); + return 0; +} + +void process_cu(Dwarf_Die *cudie) +{ + check(process_die_container(NULL, NULL, cudie, resolve_fqns, + match_all)); + + check(process_die_container(NULL, NULL, cudie, process_exported_symbols, + match_all)); + + symbol_for_each(process_symbol_ptr, dwarf_cu_getdwarf(cudie->cu)); + + cache_free(&srcfile_cache); +} diff --git a/scripts/gendwarfksyms/examples/kabi.h b/scripts/gendwarfksyms/examples/kabi.h new file mode 100644 index 000000000000..170733a3fba4 --- /dev/null +++ b/scripts/gendwarfksyms/examples/kabi.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Google LLC + * + * Example macros for maintaining kABI stability. + * + * This file is based on android_kabi.h, which has the following notice: + * + * Heavily influenced by rh_kabi.h which came from the RHEL/CENTOS kernel + * and was: + * Copyright (c) 2014 Don Zickus + * Copyright (c) 2015-2018 Jiri Benc + * Copyright (c) 2015 Sabrina Dubroca, Hannes Frederic Sowa + * Copyright (c) 2016-2018 Prarit Bhargava + * Copyright (c) 2017 Paolo Abeni, Larry Woodman + */ + +#ifndef __KABI_H__ +#define __KABI_H__ + +/* Kernel macros for userspace testing. */ +#ifndef __aligned +#define __aligned(x) __attribute__((__aligned__(x))) +#endif +#ifndef __used +#define __used __attribute__((__used__)) +#endif +#ifndef __section +#define __section(section) __attribute__((__section__(section))) +#endif +#ifndef __PASTE +#define ___PASTE(a, b) a##b +#define __PASTE(a, b) ___PASTE(a, b) +#endif +#ifndef __stringify +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) +#endif + +#define ___KABI_RULE(hint, target, value) \ + static const char __PASTE(__gendwarfksyms_rule_, \ + __COUNTER__)[] __used __aligned(1) \ + __section(".discard.gendwarfksyms.kabi_rules") = \ + "1\0" #hint "\0" target "\0" value + +#define __KABI_RULE(hint, target, value) \ + ___KABI_RULE(hint, #target, #value) + +#define __KABI_NORMAL_SIZE_ALIGN(_orig, _new) \ + union { \ + _Static_assert( \ + sizeof(struct { _new; }) <= sizeof(struct { _orig; }), \ + __FILE__ ":" __stringify(__LINE__) ": " __stringify( \ + _new) " is larger than " __stringify(_orig)); \ + _Static_assert( \ + __alignof__(struct { _new; }) <= \ + __alignof__(struct { _orig; }), \ + __FILE__ ":" __stringify(__LINE__) ": " __stringify( \ + _orig) " is not aligned the same as " __stringify(_new)); \ + } + +#define __KABI_REPLACE(_orig, _new) \ + union { \ + _new; \ + struct { \ + _orig; \ + }; \ + __KABI_NORMAL_SIZE_ALIGN(_orig, _new); \ + } + +/* + * KABI_DECLONLY(fqn) + * Treat the struct/union/enum fqn as a declaration, i.e. even if + * a definition is available, don't expand the contents. + */ +#define KABI_DECLONLY(fqn) __KABI_RULE(declonly, fqn, ) + +/* + * KABI_ENUMERATOR_IGNORE(fqn, field) + * When expanding enum fqn, skip the provided field. This makes it + * possible to hide added enum fields from versioning. + */ +#define KABI_ENUMERATOR_IGNORE(fqn, field) \ + __KABI_RULE(enumerator_ignore, fqn field, ) + +/* + * KABI_ENUMERATOR_VALUE(fqn, field, value) + * When expanding enum fqn, use the provided value for the + * specified field. This makes it possible to override enumerator + * values when calculating versions. + */ +#define KABI_ENUMERATOR_VALUE(fqn, field, value) \ + __KABI_RULE(enumerator_value, fqn field, value) + +/* + * KABI_BYTE_SIZE(fqn, value) + * Set the byte_size attribute for the struct/union/enum fqn to + * value bytes. + */ +#define KABI_BYTE_SIZE(fqn, value) __KABI_RULE(byte_size, fqn, value) + +/* + * KABI_TYPE_STRING(type, str) + * For the given type, override the type string used in symtypes + * output and version calculation with str. + */ +#define KABI_TYPE_STRING(type, str) ___KABI_RULE(type_string, type, str) + +/* + * KABI_RESERVE + * Reserve some "padding" in a structure for use by LTS backports. + * This is normally placed at the end of a structure. + * number: the "number" of the padding variable in the structure. Start with + * 1 and go up. + */ +#define KABI_RESERVE(n) unsigned long __kabi_reserved##n + +/* + * KABI_RESERVE_ARRAY + * Same as _BACKPORT_RESERVE but allocates an array with the specified + * size in bytes. + */ +#define KABI_RESERVE_ARRAY(n, s) \ + unsigned char __aligned(8) __kabi_reserved##n[s] + +/* + * KABI_IGNORE + * Add a new field that's ignored in versioning. + */ +#define KABI_IGNORE(n, _new) \ + union { \ + _new; \ + unsigned char __kabi_ignored##n; \ + } + +/* + * KABI_REPLACE + * Replace a field with a compatible new field. + */ +#define KABI_REPLACE(_oldtype, _oldname, _new) \ + __KABI_REPLACE(_oldtype __kabi_renamed##_oldname, struct { _new; }) + +/* + * KABI_USE(number, _new) + * Use a previous padding entry that was defined with KABI_RESERVE + * number: the previous "number" of the padding variable + * _new: the variable to use now instead of the padding variable + */ +#define KABI_USE(number, _new) __KABI_REPLACE(KABI_RESERVE(number), _new) + +/* + * KABI_USE2(number, _new1, _new2) + * Use a previous padding entry that was defined with KABI_RESERVE for + * two new variables that fit into 64 bits. This is good for when you do not + * want to "burn" a 64bit padding variable for a smaller variable size if not + * needed. + */ +#define KABI_USE2(number, _new1, _new2) \ + __KABI_REPLACE( \ + KABI_RESERVE(number), struct { \ + _new1; \ + _new2; \ + }) +/* + * KABI_USE_ARRAY(number, bytes, _new) + * Use a previous padding entry that was defined with KABI_RESERVE_ARRAY + * number: the previous "number" of the padding variable + * bytes: the size in bytes reserved for the array + * _new: the variable to use now instead of the padding variable + */ +#define KABI_USE_ARRAY(number, bytes, _new) \ + __KABI_REPLACE(KABI_RESERVE_ARRAY(number, bytes), _new) + +#endif /* __KABI_H__ */ diff --git a/scripts/gendwarfksyms/examples/kabi_ex.c b/scripts/gendwarfksyms/examples/kabi_ex.c new file mode 100644 index 000000000000..1f799eb7c756 --- /dev/null +++ b/scripts/gendwarfksyms/examples/kabi_ex.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * kabi_ex.c + * + * Copyright (C) 2024 Google LLC + * + * Examples for kABI stability features with --stable. See kabi_ex.h + * for details. + */ + +#include "kabi_ex.h" + +struct s e0; +enum e e1; + +struct ex0a ex0a; +struct ex0b ex0b; +struct ex0c ex0c; + +struct ex1a ex1a; +struct ex1b ex1b; +struct ex1c ex1c; + +struct ex2a ex2a; +struct ex2b ex2b; +struct ex2c ex2c; + +struct ex3a ex3a; +struct ex3b ex3b; +struct ex3c ex3c; + +struct ex4a ex4a; + +struct ex5a ex5a; +struct ex5b ex5b; + +int ex6a; diff --git a/scripts/gendwarfksyms/examples/kabi_ex.h b/scripts/gendwarfksyms/examples/kabi_ex.h new file mode 100644 index 000000000000..785b211d9c58 --- /dev/null +++ b/scripts/gendwarfksyms/examples/kabi_ex.h @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * kabi_ex.h + * + * Copyright (C) 2024 Google LLC + * + * Examples for kABI stability features with --stable. + */ + +/* + * The comments below each example contain the expected gendwarfksyms + * output, which can be verified using LLVM's FileCheck tool: + * + * https://llvm.org/docs/CommandGuide/FileCheck.html + * + * Usage: + * + * $ gcc -g -c examples/kabi_ex.c -o examples/kabi_ex.o + * + * $ nm examples/kabi_ex.o | awk '{ print $NF }' | \ + * ./gendwarfksyms --stable --dump-dies \ + * examples/kabi_ex.o 2>&1 >/dev/null | \ + * FileCheck examples/kabi_ex.h --check-prefix=STABLE + + * $ nm examples/kabi_ex.o | awk '{ print $NF }' | \ + * ./gendwarfksyms --stable --dump-versions \ + * examples/kabi_ex.o 2>&1 >/dev/null | \ + * sort | \ + * FileCheck examples/kabi_ex.h --check-prefix=VERSIONS + */ + +#ifndef __KABI_EX_H__ +#define __KABI_EX_H__ + +#include "kabi.h" + +/* + * Example: kABI rules + */ + +struct s { + int a; +}; + +KABI_DECLONLY(s); + +/* + * STABLE: variable structure_type s { + * STABLE-NEXT: } + */ + +enum e { + A, + B, + C, + D, +}; + +KABI_ENUMERATOR_IGNORE(e, B); +KABI_ENUMERATOR_IGNORE(e, C); +KABI_ENUMERATOR_VALUE(e, D, 123456789); + +/* + * STABLE: variable enumeration_type e { + * STABLE-NEXT: enumerator A = 0 , + * STABLE-NEXT: enumerator D = 123456789 + * STABLE-NEXT: } byte_size(4) +*/ + +/* + * Example: Reserved fields + */ +struct ex0a { + int a; + KABI_RESERVE(0); + KABI_RESERVE(1); +}; + +/* + * STABLE: variable structure_type ex0a { + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) , + * STABLE-NEXT: member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) data_member_location(8) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16) + * STABLE-NEXT: } byte_size(24) + */ + +struct ex0b { + int a; + KABI_RESERVE(0); + KABI_USE2(1, int b, int c); +}; + +/* + * STABLE: variable structure_type ex0b { + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(8) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16) + * STABLE-NEXT: } byte_size(24) + */ + +struct ex0c { + int a; + KABI_USE(0, void *p); + KABI_USE2(1, int b, int c); +}; + +/* + * STABLE: variable structure_type ex0c { + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(8) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) data_member_location(16) + * STABLE-NEXT: } byte_size(24) + */ + +/* + * Example: A reserved array + */ + +struct ex1a { + unsigned int a; + KABI_RESERVE_ARRAY(0, 64); +}; + +/* + * STABLE: variable structure_type ex1a { + * STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) , + * STABLE-NEXT: member array_type[64] { + * STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8) + * STABLE-NEXT: } data_member_location(8) + * STABLE-NEXT: } byte_size(72) + */ + +struct ex1b { + unsigned int a; + KABI_USE_ARRAY( + 0, 64, struct { + void *p; + KABI_RESERVE_ARRAY(1, 56); + }); +}; + +/* + * STABLE: variable structure_type ex1b { + * STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) , + * STABLE-NEXT: member array_type[64] { + * STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8) + * STABLE-NEXT: } data_member_location(8) + * STABLE-NEXT: } byte_size(72) + */ + +struct ex1c { + unsigned int a; + KABI_USE_ARRAY(0, 64, void *p[8]); +}; + +/* + * STABLE: variable structure_type ex1c { + * STABLE-NEXT: member base_type unsigned int byte_size(4) encoding(7) a data_member_location(0) , + * STABLE-NEXT: member array_type[64] { + * STABLE-NEXT: base_type unsigned char byte_size(1) encoding(8) + * STABLE-NEXT: } data_member_location(8) + * STABLE-NEXT: } byte_size(72) + */ + +/* + * Example: An ignored field added to an alignment hole + */ + +struct ex2a { + int a; + unsigned long b; + int c; + unsigned long d; +}; + +/* + * STABLE: variable structure_type ex2a { + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8) + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24) + * STABLE-NEXT: } byte_size(32) + */ + +struct ex2b { + int a; + KABI_IGNORE(0, unsigned int n); + unsigned long b; + int c; + unsigned long d; +}; + +_Static_assert(sizeof(struct ex2a) == sizeof(struct ex2b), "ex2a size doesn't match ex2b"); + +/* + * STABLE: variable structure_type ex2b { + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8) + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24) + * STABLE-NEXT: } byte_size(32) + */ + +struct ex2c { + int a; + KABI_IGNORE(0, unsigned int n); + unsigned long b; + int c; + KABI_IGNORE(1, unsigned int m); + unsigned long d; +}; + +_Static_assert(sizeof(struct ex2a) == sizeof(struct ex2c), "ex2a size doesn't match ex2c"); + +/* + * STABLE: variable structure_type ex2c { + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) a data_member_location(0) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8) + * STABLE-NEXT: member base_type int byte_size(4) encoding(5) c data_member_location(16) , + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24) + * STABLE-NEXT: } byte_size(32) + */ + + +/* + * Example: A replaced field + */ + +struct ex3a { + unsigned long a; + unsigned long unused; +}; + +/* + * STABLE: variable structure_type ex3a { + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0) + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8) + * STABLE-NEXT: } byte_size(16) + */ + +struct ex3b { + unsigned long a; + KABI_REPLACE(unsigned long, unused, unsigned long renamed); +}; + +_Static_assert(sizeof(struct ex3a) == sizeof(struct ex3b), "ex3a size doesn't match ex3b"); + +/* + * STABLE: variable structure_type ex3b { + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0) + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8) + * STABLE-NEXT: } byte_size(16) + */ + +struct ex3c { + unsigned long a; + KABI_REPLACE(unsigned long, unused, long replaced); +}; + +_Static_assert(sizeof(struct ex3a) == sizeof(struct ex3c), "ex3a size doesn't match ex3c"); + +/* + * STABLE: variable structure_type ex3c { + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0) + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8) + * STABLE-NEXT: } byte_size(16) + */ + +/* + * Example: An ignored field added to an end of a partially opaque struct, + * while keeping the byte_size attribute unchanged. + */ + +struct ex4a { + unsigned long a; + KABI_IGNORE(0, unsigned long b); +}; + +/* + * This may be safe if the structure allocation is managed by the core kernel + * and the layout remains unchanged except for appended new members. + */ +KABI_BYTE_SIZE(ex4a, 8); + +/* + * STABLE: variable structure_type ex4a { + * STABLE-NEXT: member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0) + * STABLE-NEXT: } byte_size(8) + */ + +/* + * Example: A type string override. + */ + +struct ex5a { + unsigned long a; +}; + +/* + * This may be safe if the structure is fully opaque to modules, even though + * its definition has inadvertently become part of the ABI. + */ +KABI_TYPE_STRING( + "s#ex5a", + "structure_type ex5a { member pointer_type { s#ex4a } byte_size(8) p data_member_location(0) } byte_size(8)"); + +/* + * Make sure the fully expanded type string includes ex4a. + * + * VERSIONS: ex5a variable structure_type ex5a { + * VERSIONS-SAME: member pointer_type { + * VERSIONS-SAME: structure_type ex4a { + * VERSIONS-SAME: member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) a data_member_location(0) + * VERSIONS-SAME: } byte_size(8) + * VERSIONS-SAME: } byte_size(8) p data_member_location(0) + * VERSIONS-SAME: } byte_size(8) + */ + +/* + * Example: A type string definition for a non-existent type. + */ + +struct ex5b { + unsigned long a; +}; + +/* Replace the type string for struct ex5b */ +KABI_TYPE_STRING( + "s#ex5b", + "structure_type ex5b { member pointer_type { s#ex5c } byte_size(8) p data_member_location(0) } byte_size(8)"); + +/* Define a type string for a non-existent struct ex5c */ +KABI_TYPE_STRING( + "s#ex5c", + "structure_type ex5c { member base_type int byte_size(4) encoding(5) n data_member_location(0) } byte_size(8)"); + +/* + * Make sure the fully expanded type string includes the definition for ex5c. + * + * VERSIONS: ex5b variable structure_type ex5b { + * VERSIONS-SAME: member pointer_type { + * VERSIONS-SAME: structure_type ex5c { + * VERSIONS-SAME: member base_type int byte_size(4) encoding(5) n data_member_location(0) + * VERSIONS-SAME: } byte_size(8) + * VERSIONS-SAME: } byte_size(8) p data_member_location(0) + * VERSIONS-SAME: } byte_size(8) + */ + +/* + * Example: A type string override for a symbol. + */ + +KABI_TYPE_STRING("ex6a", "variable s#ex5c"); + +/* + * VERSIONS: ex6a variable structure_type ex5c { + * VERSIONS-SAME: member base_type int byte_size(4) encoding(5) n data_member_location(0) + * VERSIONS-SAME: } byte_size(8) + */ +#endif /* __KABI_EX_H__ */ diff --git a/scripts/gendwarfksyms/examples/symbolptr.c b/scripts/gendwarfksyms/examples/symbolptr.c new file mode 100644 index 000000000000..88bc1bd60da8 --- /dev/null +++ b/scripts/gendwarfksyms/examples/symbolptr.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + * + * Example for symbol pointers. When compiled with Clang, gendwarfkyms + * uses a symbol pointer for `f`. + * + * $ clang -g -c examples/symbolptr.c -o examples/symbolptr.o + * $ echo -e "f\ng\np" | ./gendwarfksyms -d examples/symbolptr.o + */ + +/* Kernel macros for userspace testing. */ +#ifndef __used +#define __used __attribute__((__used__)) +#endif +#ifndef __section +#define __section(section) __attribute__((__section__(section))) +#endif + +#define __GENDWARFKSYMS_EXPORT(sym) \ + static typeof(sym) *__gendwarfksyms_ptr_##sym __used \ + __section(".discard.gendwarfksyms") = &sym; + +extern void f(unsigned int arg); +void g(int *arg); +void g(int *arg) {} + +struct s; +extern struct s *p; + +__GENDWARFKSYMS_EXPORT(f); +__GENDWARFKSYMS_EXPORT(g); +__GENDWARFKSYMS_EXPORT(p); diff --git a/scripts/gendwarfksyms/gendwarfksyms.c b/scripts/gendwarfksyms/gendwarfksyms.c new file mode 100644 index 000000000000..08ae61eb327e --- /dev/null +++ b/scripts/gendwarfksyms/gendwarfksyms.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#include <fcntl.h> +#include <getopt.h> +#include <errno.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include "gendwarfksyms.h" + +/* + * Options + */ + +/* Print debugging information to stderr */ +int debug; +/* Dump DIE contents */ +int dump_dies; +/* Print debugging information about die_map changes */ +int dump_die_map; +/* Print out type strings (i.e. type_map) */ +int dump_types; +/* Print out expanded type strings used for symbol versions */ +int dump_versions; +/* Support kABI stability features */ +int stable; +/* Write a symtypes file */ +int symtypes; +static const char *symtypes_file; + +static void usage(void) +{ + fputs("Usage: gendwarfksyms [options] elf-object-file ... < symbol-list\n\n" + "Options:\n" + " -d, --debug Print debugging information\n" + " --dump-dies Dump DWARF DIE contents\n" + " --dump-die-map Print debugging information about die_map changes\n" + " --dump-types Dump type strings\n" + " --dump-versions Dump expanded type strings used for symbol versions\n" + " -s, --stable Support kABI stability features\n" + " -T, --symtypes file Write a symtypes file\n" + " -h, --help Print this message\n" + "\n", + stderr); +} + +static int process_module(Dwfl_Module *mod, void **userdata, const char *name, + Dwarf_Addr base, void *arg) +{ + Dwarf_Addr dwbias; + Dwarf_Die cudie; + Dwarf_CU *cu = NULL; + Dwarf *dbg; + FILE *symfile = arg; + int res; + + debug("%s", name); + dbg = dwfl_module_getdwarf(mod, &dwbias); + + /* + * Look for exported symbols in each CU, follow the DIE tree, and add + * the entries to die_map. + */ + do { + res = dwarf_get_units(dbg, cu, &cu, NULL, NULL, &cudie, NULL); + if (res < 0) + error("dwarf_get_units failed: no debugging information?"); + if (res == 1) + break; /* No more units */ + + process_cu(&cudie); + } while (cu); + + /* + * Use die_map to expand type strings, write them to `symfile`, and + * calculate symbol versions. + */ + generate_symtypes_and_versions(symfile); + die_map_free(); + + return DWARF_CB_OK; +} + +static const Dwfl_Callbacks callbacks = { + .section_address = dwfl_offline_section_address, + .find_debuginfo = dwfl_standard_find_debuginfo, +}; + +int main(int argc, char **argv) +{ + FILE *symfile = NULL; + unsigned int n; + int opt; + + static const struct option opts[] = { + { "debug", 0, NULL, 'd' }, + { "dump-dies", 0, &dump_dies, 1 }, + { "dump-die-map", 0, &dump_die_map, 1 }, + { "dump-types", 0, &dump_types, 1 }, + { "dump-versions", 0, &dump_versions, 1 }, + { "stable", 0, NULL, 's' }, + { "symtypes", 1, NULL, 'T' }, + { "help", 0, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + while ((opt = getopt_long(argc, argv, "dsT:h", opts, NULL)) != EOF) { + switch (opt) { + case 0: + break; + case 'd': + debug = 1; + break; + case 's': + stable = 1; + break; + case 'T': + symtypes = 1; + symtypes_file = optarg; + break; + case 'h': + usage(); + return 0; + default: + usage(); + return 1; + } + } + + if (dump_die_map) + dump_dies = 1; + + if (optind >= argc) { + usage(); + error("no input files?"); + } + + symbol_read_exports(stdin); + + if (symtypes_file) { + symfile = fopen(symtypes_file, "w"); + if (!symfile) + error("fopen failed for '%s': %s", symtypes_file, + strerror(errno)); + } + + for (n = optind; n < argc; n++) { + Dwfl *dwfl; + int fd; + + fd = open(argv[n], O_RDONLY); + if (fd == -1) + error("open failed for '%s': %s", argv[n], + strerror(errno)); + + symbol_read_symtab(fd); + kabi_read_rules(fd); + + dwfl = dwfl_begin(&callbacks); + if (!dwfl) + error("dwfl_begin failed for '%s': %s", argv[n], + dwarf_errmsg(-1)); + + if (!dwfl_report_offline(dwfl, argv[n], argv[n], fd)) + error("dwfl_report_offline failed for '%s': %s", + argv[n], dwarf_errmsg(-1)); + + dwfl_report_end(dwfl, NULL, NULL); + + if (dwfl_getmodules(dwfl, &process_module, symfile, 0)) + error("dwfl_getmodules failed for '%s'", argv[n]); + + dwfl_end(dwfl); + kabi_free(); + } + + if (symfile) + check(fclose(symfile)); + + symbol_print_versions(); + symbol_free(); + + return 0; +} diff --git a/scripts/gendwarfksyms/gendwarfksyms.h b/scripts/gendwarfksyms/gendwarfksyms.h new file mode 100644 index 000000000000..d9c06d2cb1df --- /dev/null +++ b/scripts/gendwarfksyms/gendwarfksyms.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Google LLC + */ + +#include <dwarf.h> +#include <elfutils/libdw.h> +#include <elfutils/libdwfl.h> +#include <stdlib.h> +#include <stdio.h> + +#include <hash.h> +#include <hashtable.h> +#include <xalloc.h> + +#ifndef __GENDWARFKSYMS_H +#define __GENDWARFKSYMS_H + +/* + * Options -- in gendwarfksyms.c + */ +extern int debug; +extern int dump_dies; +extern int dump_die_map; +extern int dump_types; +extern int dump_versions; +extern int stable; +extern int symtypes; + +/* + * Output helpers + */ +#define __PREFIX "gendwarfksyms: " +#define __println(prefix, format, ...) \ + fprintf(stderr, prefix __PREFIX "%s: " format "\n", __func__, \ + ##__VA_ARGS__) + +#define debug(format, ...) \ + do { \ + if (debug) \ + __println("", format, ##__VA_ARGS__); \ + } while (0) + +#define warn(format, ...) __println("warning: ", format, ##__VA_ARGS__) +#define error(format, ...) \ + do { \ + __println("error: ", format, ##__VA_ARGS__); \ + exit(1); \ + } while (0) + +#define __die_debug(color, format, ...) \ + do { \ + if (dump_dies && dump_die_map) \ + fprintf(stderr, \ + "\033[" #color "m<" format ">\033[39m", \ + __VA_ARGS__); \ + } while (0) + +#define die_debug_r(format, ...) __die_debug(91, format, __VA_ARGS__) +#define die_debug_g(format, ...) __die_debug(92, format, __VA_ARGS__) +#define die_debug_b(format, ...) __die_debug(94, format, __VA_ARGS__) + +/* + * Error handling helpers + */ +#define __check(expr, test) \ + ({ \ + int __res = expr; \ + if (test) \ + error("`%s` failed: %d", #expr, __res); \ + __res; \ + }) + +/* Error == non-zero values */ +#define check(expr) __check(expr, __res) +/* Error == negative values */ +#define checkp(expr) __check(expr, __res < 0) + +/* Consistent aliases (DW_TAG_<type>_type) for DWARF tags */ +#define DW_TAG_enumerator_type DW_TAG_enumerator +#define DW_TAG_formal_parameter_type DW_TAG_formal_parameter +#define DW_TAG_member_type DW_TAG_member +#define DW_TAG_template_type_parameter_type DW_TAG_template_type_parameter +#define DW_TAG_typedef_type DW_TAG_typedef +#define DW_TAG_variant_part_type DW_TAG_variant_part +#define DW_TAG_variant_type DW_TAG_variant + +/* + * symbols.c + */ + +/* See symbols.c:is_symbol_ptr */ +#define SYMBOL_PTR_PREFIX "__gendwarfksyms_ptr_" +#define SYMBOL_PTR_PREFIX_LEN (sizeof(SYMBOL_PTR_PREFIX) - 1) + +static inline unsigned int addr_hash(uintptr_t addr) +{ + return hash_ptr((const void *)addr); +} + +enum symbol_state { + SYMBOL_UNPROCESSED, + SYMBOL_MAPPED, + SYMBOL_PROCESSED +}; + +struct symbol_addr { + uint32_t section; + Elf64_Addr address; +}; + +struct symbol { + const char *name; + struct symbol_addr addr; + struct hlist_node addr_hash; + struct hlist_node name_hash; + enum symbol_state state; + uintptr_t die_addr; + uintptr_t ptr_die_addr; + unsigned long crc; +}; + +typedef void (*symbol_callback_t)(struct symbol *, void *arg); + +bool is_symbol_ptr(const char *name); +void symbol_read_exports(FILE *file); +void symbol_read_symtab(int fd); +struct symbol *symbol_get(const char *name); +void symbol_set_ptr(struct symbol *sym, Dwarf_Die *ptr); +void symbol_set_die(struct symbol *sym, Dwarf_Die *die); +void symbol_set_crc(struct symbol *sym, unsigned long crc); +void symbol_for_each(symbol_callback_t func, void *arg); +void symbol_print_versions(void); +void symbol_free(void); + +/* + * die.c + */ + +enum die_state { + DIE_INCOMPLETE, + DIE_FQN, + DIE_UNEXPANDED, + DIE_COMPLETE, + DIE_SYMBOL, + DIE_LAST = DIE_SYMBOL +}; + +enum die_fragment_type { + FRAGMENT_EMPTY, + FRAGMENT_STRING, + FRAGMENT_LINEBREAK, + FRAGMENT_DIE +}; + +struct die_fragment { + enum die_fragment_type type; + union { + char *str; + int linebreak; + uintptr_t addr; + } data; + struct list_head list; +}; + +#define CASE_CONST_TO_STR(name) \ + case name: \ + return #name; + +static inline const char *die_state_name(enum die_state state) +{ + switch (state) { + CASE_CONST_TO_STR(DIE_INCOMPLETE) + CASE_CONST_TO_STR(DIE_FQN) + CASE_CONST_TO_STR(DIE_UNEXPANDED) + CASE_CONST_TO_STR(DIE_COMPLETE) + CASE_CONST_TO_STR(DIE_SYMBOL) + } + + error("unexpected die_state: %d", state); +} + +struct die { + enum die_state state; + bool mapped; + char *fqn; + int tag; + uintptr_t addr; + struct list_head fragments; + struct hlist_node hash; +}; + +typedef void (*die_map_callback_t)(struct die *, void *arg); + +int __die_map_get(uintptr_t addr, enum die_state state, struct die **res); +struct die *die_map_get(Dwarf_Die *die, enum die_state state); +void die_map_add_string(struct die *pd, const char *str); +void die_map_add_linebreak(struct die *pd, int linebreak); +void die_map_for_each(die_map_callback_t func, void *arg); +void die_map_add_die(struct die *pd, struct die *child); +void die_map_free(void); + +/* + * cache.c + */ + +#define CACHE_HASH_BITS 10 + +/* A cache for addresses we've already seen. */ +struct cache { + HASHTABLE_DECLARE(cache, 1 << CACHE_HASH_BITS); +}; + +void cache_set(struct cache *cache, unsigned long key, int value); +int cache_get(struct cache *cache, unsigned long key); +void cache_init(struct cache *cache); +void cache_free(struct cache *cache); + +static inline void cache_mark_expanded(struct cache *cache, void *addr) +{ + cache_set(cache, (unsigned long)addr, 1); +} + +static inline bool cache_was_expanded(struct cache *cache, void *addr) +{ + return cache_get(cache, (unsigned long)addr) == 1; +} + +/* + * dwarf.c + */ + +struct expansion_state { + bool expand; + const char *current_fqn; +}; + +struct kabi_state { + int members; + Dwarf_Die placeholder; + const char *orig_name; +}; + +struct state { + struct symbol *sym; + Dwarf_Die die; + + /* List expansion */ + bool first_list_item; + + /* Structure expansion */ + struct expansion_state expand; + struct cache expansion_cache; + + /* Reserved or ignored members */ + struct kabi_state kabi; +}; + +typedef int (*die_callback_t)(struct state *state, struct die *cache, + Dwarf_Die *die); +typedef bool (*die_match_callback_t)(Dwarf_Die *die); +bool match_all(Dwarf_Die *die); + +int process_die_container(struct state *state, struct die *cache, + Dwarf_Die *die, die_callback_t func, + die_match_callback_t match); + +void process_cu(Dwarf_Die *cudie); + +/* + * types.c + */ + +void generate_symtypes_and_versions(FILE *file); + +/* + * kabi.c + */ + +bool kabi_get_byte_size(const char *fqn, unsigned long *value); +bool kabi_is_enumerator_ignored(const char *fqn, const char *field); +bool kabi_get_enumerator_value(const char *fqn, const char *field, + unsigned long *value); +bool kabi_is_declonly(const char *fqn); +bool kabi_get_type_string(const char *type, const char **str); + +void kabi_read_rules(int fd); +void kabi_free(void); + +#endif /* __GENDWARFKSYMS_H */ diff --git a/scripts/gendwarfksyms/kabi.c b/scripts/gendwarfksyms/kabi.c new file mode 100644 index 000000000000..b3ade713778f --- /dev/null +++ b/scripts/gendwarfksyms/kabi.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#define _GNU_SOURCE +#include <errno.h> +#include <stdio.h> + +#include "gendwarfksyms.h" + +#define KABI_RULE_SECTION ".discard.gendwarfksyms.kabi_rules" +#define KABI_RULE_VERSION "1" + +/* + * The rule section consists of four null-terminated strings per + * entry: + * + * 1. version + * Entry format version. Must match KABI_RULE_VERSION. + * + * 2. type + * Type of the kABI rule. Must be one of the tags defined below. + * + * 3. target + * Rule-dependent target, typically the fully qualified name of + * the target DIE. + * + * 4. value + * Rule-dependent value. + */ +#define KABI_RULE_MIN_ENTRY_SIZE \ + (/* version\0 */ 2 + /* type\0 */ 2 + /* target\0" */ 1 + \ + /* value\0 */ 1) +#define KABI_RULE_EMPTY_VALUE "" + +/* + * Rule: declonly + * - For the struct/enum/union in the target field, treat it as a + * declaration only even if a definition is available. + */ +#define KABI_RULE_TAG_DECLONLY "declonly" + +/* + * Rule: enumerator_ignore + * - For the enum_field in the target field, ignore the enumerator. + */ +#define KABI_RULE_TAG_ENUMERATOR_IGNORE "enumerator_ignore" + +/* + * Rule: enumerator_value + * - For the fqn_field in the target field, set the value to the + * unsigned integer in the value field. + */ +#define KABI_RULE_TAG_ENUMERATOR_VALUE "enumerator_value" + +/* + * Rule: byte_size + * - For the fqn_field in the target field, set the byte_size + * attribute to the value in the value field. + */ +#define KABI_RULE_TAG_BYTE_SIZE "byte_size" + +/* + * Rule: type_string + * - For the type reference in the fqn field, use the type string + * in the value field. + */ +#define KABI_RULE_TAG_TYPE_STRING "type_string" + +enum kabi_rule_type { + KABI_RULE_TYPE_UNKNOWN, + KABI_RULE_TYPE_DECLONLY, + KABI_RULE_TYPE_ENUMERATOR_IGNORE, + KABI_RULE_TYPE_ENUMERATOR_VALUE, + KABI_RULE_TYPE_BYTE_SIZE, + KABI_RULE_TYPE_TYPE_STRING, +}; + +#define RULE_HASH_BITS 7 + +struct rule { + enum kabi_rule_type type; + const char *target; + const char *value; + struct hlist_node hash; +}; + +/* { type, target } -> struct rule */ +static HASHTABLE_DEFINE(rules, 1 << RULE_HASH_BITS); + +static inline unsigned int rule_values_hash(enum kabi_rule_type type, + const char *target) +{ + return hash_32(type) ^ hash_str(target); +} + +static inline unsigned int rule_hash(const struct rule *rule) +{ + return rule_values_hash(rule->type, rule->target); +} + +static inline const char *get_rule_field(const char **pos, ssize_t *left) +{ + const char *start = *pos; + size_t len; + + if (*left <= 0) + error("unexpected end of kABI rules"); + + len = strnlen(start, *left) + 1; + *pos += len; + *left -= len; + + return start; +} + +void kabi_read_rules(int fd) +{ + GElf_Shdr shdr_mem; + GElf_Shdr *shdr; + Elf_Data *rule_data = NULL; + Elf_Scn *scn; + Elf *elf; + size_t shstrndx; + const char *rule_str; + ssize_t left; + int i; + + const struct { + enum kabi_rule_type type; + const char *tag; + } rule_types[] = { + { + .type = KABI_RULE_TYPE_DECLONLY, + .tag = KABI_RULE_TAG_DECLONLY, + }, + { + .type = KABI_RULE_TYPE_ENUMERATOR_IGNORE, + .tag = KABI_RULE_TAG_ENUMERATOR_IGNORE, + }, + { + .type = KABI_RULE_TYPE_ENUMERATOR_VALUE, + .tag = KABI_RULE_TAG_ENUMERATOR_VALUE, + }, + { + .type = KABI_RULE_TYPE_BYTE_SIZE, + .tag = KABI_RULE_TAG_BYTE_SIZE, + }, + { + .type = KABI_RULE_TYPE_TYPE_STRING, + .tag = KABI_RULE_TAG_TYPE_STRING, + }, + }; + + if (!stable) + return; + + if (elf_version(EV_CURRENT) != EV_CURRENT) + error("elf_version failed: %s", elf_errmsg(-1)); + + elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!elf) + error("elf_begin failed: %s", elf_errmsg(-1)); + + if (elf_getshdrstrndx(elf, &shstrndx) < 0) + error("elf_getshdrstrndx failed: %s", elf_errmsg(-1)); + + scn = elf_nextscn(elf, NULL); + + while (scn) { + const char *sname; + + shdr = gelf_getshdr(scn, &shdr_mem); + if (!shdr) + error("gelf_getshdr failed: %s", elf_errmsg(-1)); + + sname = elf_strptr(elf, shstrndx, shdr->sh_name); + if (!sname) + error("elf_strptr failed: %s", elf_errmsg(-1)); + + if (!strcmp(sname, KABI_RULE_SECTION)) { + rule_data = elf_getdata(scn, NULL); + if (!rule_data) + error("elf_getdata failed: %s", elf_errmsg(-1)); + break; + } + + scn = elf_nextscn(elf, scn); + } + + if (!rule_data) { + debug("kABI rules not found"); + check(elf_end(elf)); + return; + } + + rule_str = rule_data->d_buf; + left = shdr->sh_size; + + if (left < KABI_RULE_MIN_ENTRY_SIZE) + error("kABI rule section too small: %zd bytes", left); + + if (rule_str[left - 1] != '\0') + error("kABI rules are not null-terminated"); + + while (left > KABI_RULE_MIN_ENTRY_SIZE) { + enum kabi_rule_type type = KABI_RULE_TYPE_UNKNOWN; + const char *field; + struct rule *rule; + + /* version */ + field = get_rule_field(&rule_str, &left); + + if (strcmp(field, KABI_RULE_VERSION)) + error("unsupported kABI rule version: '%s'", field); + + /* type */ + field = get_rule_field(&rule_str, &left); + + for (i = 0; i < ARRAY_SIZE(rule_types); i++) { + if (!strcmp(field, rule_types[i].tag)) { + type = rule_types[i].type; + break; + } + } + + if (type == KABI_RULE_TYPE_UNKNOWN) + error("unsupported kABI rule type: '%s'", field); + + rule = xmalloc(sizeof(struct rule)); + + rule->type = type; + rule->target = xstrdup(get_rule_field(&rule_str, &left)); + rule->value = xstrdup(get_rule_field(&rule_str, &left)); + + hash_add(rules, &rule->hash, rule_hash(rule)); + + debug("kABI rule: type: '%s', target: '%s', value: '%s'", field, + rule->target, rule->value); + } + + if (left > 0) + warn("unexpected data at the end of the kABI rules section"); + + check(elf_end(elf)); +} + +static char *get_enumerator_target(const char *fqn, const char *field) +{ + char *target = NULL; + + if (asprintf(&target, "%s %s", fqn, field) < 0) + error("asprintf failed for '%s %s'", fqn, field); + + return target; +} + +static struct rule *find_rule(enum kabi_rule_type type, const char *target) +{ + struct rule *rule; + + if (!stable) + return NULL; + if (!target || !*target) + return NULL; + + hash_for_each_possible(rules, rule, hash, + rule_values_hash(type, target)) { + if (rule->type == type && !strcmp(target, rule->target)) + return rule; + } + + return NULL; +} + +static struct rule *find_enumerator_rule(enum kabi_rule_type type, + const char *fqn, const char *field) +{ + struct rule *rule; + char *target; + + if (!stable) + return NULL; + if (!fqn || !*fqn || !field || !*field) + return NULL; + + target = get_enumerator_target(fqn, field); + rule = find_rule(type, target); + + free(target); + return rule; +} + +bool kabi_is_declonly(const char *fqn) +{ + return !!find_rule(KABI_RULE_TYPE_DECLONLY, fqn); +} + +static unsigned long get_ulong_value(const char *value) +{ + unsigned long result = 0; + char *endptr = NULL; + + errno = 0; + result = strtoul(value, &endptr, 10); + + if (errno || *endptr) + error("invalid unsigned value '%s'", value); + + return result; +} + +bool kabi_is_enumerator_ignored(const char *fqn, const char *field) +{ + return !!find_enumerator_rule(KABI_RULE_TYPE_ENUMERATOR_IGNORE, fqn, + field); +} + +bool kabi_get_enumerator_value(const char *fqn, const char *field, + unsigned long *value) +{ + struct rule *rule; + + rule = find_enumerator_rule(KABI_RULE_TYPE_ENUMERATOR_VALUE, fqn, + field); + if (rule) { + *value = get_ulong_value(rule->value); + return true; + } + + return false; +} + +bool kabi_get_byte_size(const char *fqn, unsigned long *value) +{ + struct rule *rule; + + rule = find_rule(KABI_RULE_TYPE_BYTE_SIZE, fqn); + if (rule) { + *value = get_ulong_value(rule->value); + return true; + } + + return false; +} + +bool kabi_get_type_string(const char *type, const char **str) +{ + struct rule *rule; + + rule = find_rule(KABI_RULE_TYPE_TYPE_STRING, type); + if (rule) { + *str = rule->value; + return true; + } + + return false; +} + +void kabi_free(void) +{ + struct hlist_node *tmp; + struct rule *rule; + + hash_for_each_safe(rules, rule, tmp, hash) { + free((void *)rule->target); + free((void *)rule->value); + free(rule); + } + + hash_init(rules); +} diff --git a/scripts/gendwarfksyms/symbols.c b/scripts/gendwarfksyms/symbols.c new file mode 100644 index 000000000000..327f87389c34 --- /dev/null +++ b/scripts/gendwarfksyms/symbols.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#include "gendwarfksyms.h" + +#define SYMBOL_HASH_BITS 12 + +/* struct symbol_addr -> struct symbol */ +static HASHTABLE_DEFINE(symbol_addrs, 1 << SYMBOL_HASH_BITS); +/* name -> struct symbol */ +static HASHTABLE_DEFINE(symbol_names, 1 << SYMBOL_HASH_BITS); + +static inline unsigned int symbol_addr_hash(const struct symbol_addr *addr) +{ + return hash_32(addr->section ^ addr_hash(addr->address)); +} + +static unsigned int __for_each_addr(struct symbol *sym, symbol_callback_t func, + void *data) +{ + struct hlist_node *tmp; + struct symbol *match = NULL; + unsigned int processed = 0; + + hash_for_each_possible_safe(symbol_addrs, match, tmp, addr_hash, + symbol_addr_hash(&sym->addr)) { + if (match == sym) + continue; /* Already processed */ + + if (match->addr.section == sym->addr.section && + match->addr.address == sym->addr.address) { + func(match, data); + ++processed; + } + } + + return processed; +} + +/* + * For symbols without debugging information (e.g. symbols defined in other + * TUs), we also match __gendwarfksyms_ptr_<symbol_name> symbols, which the + * kernel uses to ensure type information is present in the TU that exports + * the symbol. A __gendwarfksyms_ptr pointer must have the same type as the + * exported symbol, e.g.: + * + * typeof(symname) *__gendwarf_ptr_symname = &symname; + */ +bool is_symbol_ptr(const char *name) +{ + return name && !strncmp(name, SYMBOL_PTR_PREFIX, SYMBOL_PTR_PREFIX_LEN); +} + +static unsigned int for_each(const char *name, symbol_callback_t func, + void *data) +{ + struct hlist_node *tmp; + struct symbol *match; + + if (!name || !*name) + return 0; + if (is_symbol_ptr(name)) + name += SYMBOL_PTR_PREFIX_LEN; + + hash_for_each_possible_safe(symbol_names, match, tmp, name_hash, + hash_str(name)) { + if (strcmp(match->name, name)) + continue; + + /* Call func for the match, and all address matches */ + if (func) + func(match, data); + + if (match->addr.section != SHN_UNDEF) + return __for_each_addr(match, func, data) + 1; + + return 1; + } + + return 0; +} + +static void set_crc(struct symbol *sym, void *data) +{ + unsigned long *crc = data; + + if (sym->state == SYMBOL_PROCESSED && sym->crc != *crc) + warn("overriding version for symbol %s (crc %lx vs. %lx)", + sym->name, sym->crc, *crc); + + sym->state = SYMBOL_PROCESSED; + sym->crc = *crc; +} + +void symbol_set_crc(struct symbol *sym, unsigned long crc) +{ + if (for_each(sym->name, set_crc, &crc) == 0) + error("no matching symbols: '%s'", sym->name); +} + +static void set_ptr(struct symbol *sym, void *data) +{ + sym->ptr_die_addr = (uintptr_t)((Dwarf_Die *)data)->addr; +} + +void symbol_set_ptr(struct symbol *sym, Dwarf_Die *ptr) +{ + if (for_each(sym->name, set_ptr, ptr) == 0) + error("no matching symbols: '%s'", sym->name); +} + +static void set_die(struct symbol *sym, void *data) +{ + sym->die_addr = (uintptr_t)((Dwarf_Die *)data)->addr; + sym->state = SYMBOL_MAPPED; +} + +void symbol_set_die(struct symbol *sym, Dwarf_Die *die) +{ + if (for_each(sym->name, set_die, die) == 0) + error("no matching symbols: '%s'", sym->name); +} + +static bool is_exported(const char *name) +{ + return for_each(name, NULL, NULL) > 0; +} + +void symbol_read_exports(FILE *file) +{ + struct symbol *sym; + char *line = NULL; + char *name = NULL; + size_t size = 0; + int nsym = 0; + + while (getline(&line, &size, file) > 0) { + if (sscanf(line, "%ms\n", &name) != 1) + error("malformed input line: %s", line); + + if (is_exported(name)) { + /* Ignore duplicates */ + free(name); + continue; + } + + sym = xcalloc(1, sizeof(struct symbol)); + sym->name = name; + sym->addr.section = SHN_UNDEF; + sym->state = SYMBOL_UNPROCESSED; + + hash_add(symbol_names, &sym->name_hash, hash_str(sym->name)); + ++nsym; + + debug("%s", sym->name); + } + + free(line); + debug("%d exported symbols", nsym); +} + +static void get_symbol(struct symbol *sym, void *arg) +{ + struct symbol **res = arg; + + if (sym->state == SYMBOL_UNPROCESSED) + *res = sym; +} + +struct symbol *symbol_get(const char *name) +{ + struct symbol *sym = NULL; + + for_each(name, get_symbol, &sym); + return sym; +} + +void symbol_for_each(symbol_callback_t func, void *arg) +{ + struct hlist_node *tmp; + struct symbol *sym; + + hash_for_each_safe(symbol_names, sym, tmp, name_hash) { + func(sym, arg); + } +} + +typedef void (*elf_symbol_callback_t)(const char *name, GElf_Sym *sym, + Elf32_Word xndx, void *arg); + +static void elf_for_each_global(int fd, elf_symbol_callback_t func, void *arg) +{ + size_t sym_size; + GElf_Shdr shdr_mem; + GElf_Shdr *shdr; + Elf_Data *xndx_data = NULL; + Elf_Scn *scn; + Elf *elf; + + if (elf_version(EV_CURRENT) != EV_CURRENT) + error("elf_version failed: %s", elf_errmsg(-1)); + + elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!elf) + error("elf_begin failed: %s", elf_errmsg(-1)); + + scn = elf_nextscn(elf, NULL); + + while (scn) { + shdr = gelf_getshdr(scn, &shdr_mem); + if (!shdr) + error("gelf_getshdr failed: %s", elf_errmsg(-1)); + + if (shdr->sh_type == SHT_SYMTAB_SHNDX) { + xndx_data = elf_getdata(scn, NULL); + if (!xndx_data) + error("elf_getdata failed: %s", elf_errmsg(-1)); + break; + } + + scn = elf_nextscn(elf, scn); + } + + sym_size = gelf_fsize(elf, ELF_T_SYM, 1, EV_CURRENT); + scn = elf_nextscn(elf, NULL); + + while (scn) { + shdr = gelf_getshdr(scn, &shdr_mem); + if (!shdr) + error("gelf_getshdr failed: %s", elf_errmsg(-1)); + + if (shdr->sh_type == SHT_SYMTAB) { + unsigned int nsyms; + unsigned int n; + Elf_Data *data = elf_getdata(scn, NULL); + + if (!data) + error("elf_getdata failed: %s", elf_errmsg(-1)); + + if (shdr->sh_entsize != sym_size) + error("expected sh_entsize (%lu) to be %zu", + shdr->sh_entsize, sym_size); + + nsyms = shdr->sh_size / shdr->sh_entsize; + + for (n = 1; n < nsyms; ++n) { + const char *name = NULL; + Elf32_Word xndx = 0; + GElf_Sym sym_mem; + GElf_Sym *sym; + + sym = gelf_getsymshndx(data, xndx_data, n, + &sym_mem, &xndx); + if (!sym) + error("gelf_getsymshndx failed: %s", + elf_errmsg(-1)); + + if (GELF_ST_BIND(sym->st_info) == STB_LOCAL) + continue; + + if (sym->st_shndx != SHN_XINDEX) + xndx = sym->st_shndx; + + name = elf_strptr(elf, shdr->sh_link, + sym->st_name); + if (!name) + error("elf_strptr failed: %s", + elf_errmsg(-1)); + + /* Skip empty symbol names */ + if (*name) + func(name, sym, xndx, arg); + } + } + + scn = elf_nextscn(elf, scn); + } + + check(elf_end(elf)); +} + +static void set_symbol_addr(struct symbol *sym, void *arg) +{ + struct symbol_addr *addr = arg; + + if (sym->addr.section == SHN_UNDEF) { + sym->addr = *addr; + hash_add(symbol_addrs, &sym->addr_hash, + symbol_addr_hash(&sym->addr)); + + debug("%s -> { %u, %lx }", sym->name, sym->addr.section, + sym->addr.address); + } else if (sym->addr.section != addr->section || + sym->addr.address != addr->address) { + warn("multiple addresses for symbol %s?", sym->name); + } +} + +static void elf_set_symbol_addr(const char *name, GElf_Sym *sym, + Elf32_Word xndx, void *arg) +{ + struct symbol_addr addr = { .section = xndx, .address = sym->st_value }; + + /* Set addresses for exported symbols */ + if (addr.section != SHN_UNDEF) + for_each(name, set_symbol_addr, &addr); +} + +void symbol_read_symtab(int fd) +{ + elf_for_each_global(fd, elf_set_symbol_addr, NULL); +} + +void symbol_print_versions(void) +{ + struct hlist_node *tmp; + struct symbol *sym; + + hash_for_each_safe(symbol_names, sym, tmp, name_hash) { + if (sym->state != SYMBOL_PROCESSED) + warn("no information for symbol %s", sym->name); + + printf("#SYMVER %s 0x%08lx\n", sym->name, sym->crc); + } +} + +void symbol_free(void) +{ + struct hlist_node *tmp; + struct symbol *sym; + + hash_for_each_safe(symbol_names, sym, tmp, name_hash) { + free((void *)sym->name); + free(sym); + } + + hash_init(symbol_addrs); + hash_init(symbol_names); +} diff --git a/scripts/gendwarfksyms/types.c b/scripts/gendwarfksyms/types.c new file mode 100644 index 000000000000..7bd459ea6c59 --- /dev/null +++ b/scripts/gendwarfksyms/types.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#define _GNU_SOURCE +#include <inttypes.h> +#include <stdio.h> +#include <zlib.h> + +#include "gendwarfksyms.h" + +static struct cache expansion_cache; + +/* + * A simple linked list of shared or owned strings to avoid copying strings + * around when not necessary. + */ +struct type_list_entry { + const char *str; + void *owned; + struct list_head list; +}; + +static void type_list_free(struct list_head *list) +{ + struct type_list_entry *entry; + struct type_list_entry *tmp; + + list_for_each_entry_safe(entry, tmp, list, list) { + if (entry->owned) + free(entry->owned); + free(entry); + } + + INIT_LIST_HEAD(list); +} + +static int type_list_append(struct list_head *list, const char *s, void *owned) +{ + struct type_list_entry *entry; + + if (!s) + return 0; + + entry = xmalloc(sizeof(struct type_list_entry)); + entry->str = s; + entry->owned = owned; + list_add_tail(&entry->list, list); + + return strlen(entry->str); +} + +static void type_list_write(struct list_head *list, FILE *file) +{ + struct type_list_entry *entry; + + list_for_each_entry(entry, list, list) { + if (entry->str) + checkp(fputs(entry->str, file)); + } +} + +/* + * An expanded type string in symtypes format. + */ +struct type_expansion { + char *name; + size_t len; + struct list_head expanded; + struct hlist_node hash; +}; + +static void type_expansion_init(struct type_expansion *type) +{ + type->name = NULL; + type->len = 0; + INIT_LIST_HEAD(&type->expanded); +} + +static inline void type_expansion_free(struct type_expansion *type) +{ + free(type->name); + type->name = NULL; + type->len = 0; + type_list_free(&type->expanded); +} + +static void type_expansion_append(struct type_expansion *type, const char *s, + void *owned) +{ + type->len += type_list_append(&type->expanded, s, owned); +} + +/* + * type_map -- the longest expansions for each type. + * + * const char *name -> struct type_expansion * + */ +#define TYPE_HASH_BITS 12 +static HASHTABLE_DEFINE(type_map, 1 << TYPE_HASH_BITS); + +static int __type_map_get(const char *name, struct type_expansion **res) +{ + struct type_expansion *e; + + hash_for_each_possible(type_map, e, hash, hash_str(name)) { + if (!strcmp(name, e->name)) { + *res = e; + return 0; + } + } + + return -1; +} + +static struct type_expansion *type_map_add(const char *name, + struct type_expansion *type) +{ + struct type_expansion *e; + + if (__type_map_get(name, &e)) { + e = xmalloc(sizeof(struct type_expansion)); + type_expansion_init(e); + e->name = xstrdup(name); + + hash_add(type_map, &e->hash, hash_str(e->name)); + + if (dump_types) + debug("adding %s", e->name); + } else { + /* Use the longest available expansion */ + if (type->len <= e->len) + return e; + + type_list_free(&e->expanded); + + if (dump_types) + debug("replacing %s", e->name); + } + + /* Take ownership of type->expanded */ + list_replace_init(&type->expanded, &e->expanded); + e->len = type->len; + + if (dump_types) { + checkp(fputs(e->name, stderr)); + checkp(fputs(" ", stderr)); + type_list_write(&e->expanded, stderr); + checkp(fputs("\n", stderr)); + } + + return e; +} + +static void type_parse(const char *name, const char *str, + struct type_expansion *type); + +static int type_map_get(const char *name, struct type_expansion **res) +{ + struct type_expansion type; + const char *override; + + if (!__type_map_get(name, res)) + return 0; + + /* + * If die_map didn't contain a type, we might still have + * a type_string kABI rule that defines it. + */ + if (stable && kabi_get_type_string(name, &override)) { + type_expansion_init(&type); + type_parse(name, override, &type); + *res = type_map_add(name, &type); + type_expansion_free(&type); + return 0; + } + + return -1; +} + +static void type_map_write(FILE *file) +{ + struct type_expansion *e; + struct hlist_node *tmp; + + if (!file) + return; + + hash_for_each_safe(type_map, e, tmp, hash) { + checkp(fputs(e->name, file)); + checkp(fputs(" ", file)); + type_list_write(&e->expanded, file); + checkp(fputs("\n", file)); + } +} + +static void type_map_free(void) +{ + struct type_expansion *e; + struct hlist_node *tmp; + + hash_for_each_safe(type_map, e, tmp, hash) { + type_expansion_free(e); + free(e); + } + + hash_init(type_map); +} + +/* + * CRC for a type, with an optional fully expanded type string for + * debugging. + */ +struct version { + struct type_expansion type; + unsigned long crc; +}; + +static void version_init(struct version *version) +{ + version->crc = crc32(0, NULL, 0); + type_expansion_init(&version->type); +} + +static void version_free(struct version *version) +{ + type_expansion_free(&version->type); +} + +static void version_add(struct version *version, const char *s) +{ + version->crc = crc32(version->crc, (void *)s, strlen(s)); + if (dump_versions) + type_expansion_append(&version->type, s, NULL); +} + +/* + * Type reference format: <prefix>#<name>, where prefix: + * s -> structure + * u -> union + * e -> enum + * t -> typedef + * + * Names with spaces are additionally wrapped in single quotes. + */ +static inline bool is_type_prefix(const char *s) +{ + return (s[0] == 's' || s[0] == 'u' || s[0] == 'e' || s[0] == 't') && + s[1] == '#'; +} + +static char get_type_prefix(int tag) +{ + switch (tag) { + case DW_TAG_class_type: + case DW_TAG_structure_type: + return 's'; + case DW_TAG_union_type: + return 'u'; + case DW_TAG_enumeration_type: + return 'e'; + case DW_TAG_typedef_type: + return 't'; + default: + return 0; + } +} + +static char *get_type_name(struct die *cache) +{ + const char *quote; + char prefix; + char *name; + + if (cache->state == DIE_INCOMPLETE) { + warn("found incomplete cache entry: %p", cache); + return NULL; + } + if (cache->state == DIE_SYMBOL || cache->state == DIE_FQN) + return NULL; + if (!cache->fqn || !*cache->fqn) + return NULL; + + prefix = get_type_prefix(cache->tag); + if (!prefix) + return NULL; + + /* Wrap names with spaces in single quotes */ + quote = strstr(cache->fqn, " ") ? "'" : ""; + + /* <prefix>#<type_name>\0 */ + if (asprintf(&name, "%c#%s%s%s", prefix, quote, cache->fqn, quote) < 0) + error("asprintf failed for '%s'", cache->fqn); + + return name; +} + +static void __calculate_version(struct version *version, + struct type_expansion *type) +{ + struct type_list_entry *entry; + struct type_expansion *e; + + /* Calculate a CRC over an expanded type string */ + list_for_each_entry(entry, &type->expanded, list) { + if (is_type_prefix(entry->str)) { + if (type_map_get(entry->str, &e)) + error("unknown type reference to '%s' when expanding '%s'", + entry->str, type->name); + + /* + * It's sufficient to expand each type reference just + * once to detect changes. + */ + if (cache_was_expanded(&expansion_cache, e)) { + version_add(version, entry->str); + } else { + cache_mark_expanded(&expansion_cache, e); + __calculate_version(version, e); + } + } else { + version_add(version, entry->str); + } + } +} + +static void calculate_version(struct version *version, + struct type_expansion *type) +{ + version_init(version); + __calculate_version(version, type); + cache_free(&expansion_cache); +} + +static void __type_expand(struct die *cache, struct type_expansion *type) +{ + struct die_fragment *df; + struct die *child; + char *name; + + list_for_each_entry(df, &cache->fragments, list) { + switch (df->type) { + case FRAGMENT_STRING: + type_expansion_append(type, df->data.str, NULL); + break; + case FRAGMENT_DIE: + /* Use a complete die_map expansion if available */ + if (__die_map_get(df->data.addr, DIE_COMPLETE, + &child) && + __die_map_get(df->data.addr, DIE_UNEXPANDED, + &child)) + error("unknown child: %" PRIxPTR, + df->data.addr); + + name = get_type_name(child); + if (name) + type_expansion_append(type, name, name); + else + __type_expand(child, type); + + break; + case FRAGMENT_LINEBREAK: + /* + * Keep whitespace in the symtypes format, but avoid + * repeated spaces. + */ + if (list_is_last(&df->list, &cache->fragments) || + list_next_entry(df, list)->type != + FRAGMENT_LINEBREAK) + type_expansion_append(type, " ", NULL); + break; + default: + error("empty die_fragment in %p", cache); + } + } +} + +static void type_expand(const char *name, struct die *cache, + struct type_expansion *type) +{ + const char *override; + + type_expansion_init(type); + + if (stable && kabi_get_type_string(name, &override)) + type_parse(name, override, type); + else + __type_expand(cache, type); +} + +static void type_parse(const char *name, const char *str, + struct type_expansion *type) +{ + char *fragment; + size_t start = 0; + size_t end; + size_t pos; + + if (!*str) + error("empty type string override for '%s'", name); + + for (pos = 0; str[pos]; ++pos) { + bool empty; + char marker = ' '; + + if (!is_type_prefix(&str[pos])) + continue; + + end = pos + 2; + + /* + * Find the end of the type reference. If the type name contains + * spaces, it must be in single quotes. + */ + if (str[end] == '\'') { + marker = '\''; + ++end; + } + while (str[end] && str[end] != marker) + ++end; + + /* Check that we have a non-empty type name */ + if (marker == '\'') { + if (str[end] != marker) + error("incomplete %c# type reference for '%s' (string : '%s')", + str[pos], name, str); + empty = end == pos + 3; + ++end; + } else { + empty = end == pos + 2; + } + if (empty) + error("empty %c# type name for '%s' (string: '%s')", + str[pos], name, str); + + /* Append the part of the string before the type reference */ + if (pos > start) { + fragment = xstrndup(&str[start], pos - start); + type_expansion_append(type, fragment, fragment); + } + + /* + * Append the type reference -- note that if the reference + * is invalid, i.e. points to a non-existent type, we will + * print out an error when calculating versions. + */ + fragment = xstrndup(&str[pos], end - pos); + type_expansion_append(type, fragment, fragment); + + start = end; + pos = end - 1; + } + + /* Append the rest of the type string, if there's any left */ + if (str[start]) + type_expansion_append(type, &str[start], NULL); +} + +static void expand_type(struct die *cache, void *arg) +{ + struct type_expansion type; + char *name; + + if (cache->mapped) + return; + + cache->mapped = true; + + /* + * Skip unexpanded die_map entries if there's a complete + * expansion available for this DIE. + */ + if (cache->state == DIE_UNEXPANDED && + !__die_map_get(cache->addr, DIE_COMPLETE, &cache)) { + if (cache->mapped) + return; + + cache->mapped = true; + } + + name = get_type_name(cache); + if (!name) + return; + + debug("%s", name); + + type_expand(name, cache, &type); + type_map_add(name, &type); + type_expansion_free(&type); + free(name); +} + +static void expand_symbol(struct symbol *sym, void *arg) +{ + struct type_expansion type; + struct version version; + struct die *cache; + + /* + * No need to expand again unless we want a symtypes file entry + * for the symbol. Note that this means `sym` has the same address + * as another symbol that was already processed. + */ + if (!symtypes && sym->state == SYMBOL_PROCESSED) + return; + + if (__die_map_get(sym->die_addr, DIE_SYMBOL, &cache)) + return; /* We'll warn about missing CRCs later. */ + + type_expand(sym->name, cache, &type); + + /* If the symbol already has a version, don't calculate it again. */ + if (sym->state != SYMBOL_PROCESSED) { + calculate_version(&version, &type); + symbol_set_crc(sym, version.crc); + debug("%s = %lx", sym->name, version.crc); + + if (dump_versions) { + checkp(fputs(sym->name, stderr)); + checkp(fputs(" ", stderr)); + type_list_write(&version.type.expanded, stderr); + checkp(fputs("\n", stderr)); + } + + version_free(&version); + } + + /* These aren't needed in type_map unless we want a symtypes file. */ + if (symtypes) + type_map_add(sym->name, &type); + + type_expansion_free(&type); +} + +void generate_symtypes_and_versions(FILE *file) +{ + cache_init(&expansion_cache); + + /* + * die_map processing: + * + * 1. die_map contains all types referenced in exported symbol + * signatures, but can contain duplicates just like the original + * DWARF, and some references may not be fully expanded depending + * on how far we processed the DIE tree for that specific symbol. + * + * For each die_map entry, find the longest available expansion, + * and add it to type_map. + */ + die_map_for_each(expand_type, NULL); + + /* + * 2. For each exported symbol, expand the die_map type, and use + * type_map expansions to calculate a symbol version from the + * fully expanded type string. + */ + symbol_for_each(expand_symbol, NULL); + + /* + * 3. If a symtypes file is requested, write type_map contents to + * the file. + */ + type_map_write(file); + type_map_free(); +} diff --git a/scripts/generate_builtin_ranges.awk b/scripts/generate_builtin_ranges.awk new file mode 100755 index 000000000000..d4bd5c2b998c --- /dev/null +++ b/scripts/generate_builtin_ranges.awk @@ -0,0 +1,513 @@ +#!/usr/bin/gawk -f +# SPDX-License-Identifier: GPL-2.0 +# generate_builtin_ranges.awk: Generate address range data for builtin modules +# Written by Kris Van Hees <kris.van.hees@oracle.com> +# +# Usage: generate_builtin_ranges.awk modules.builtin vmlinux.map \ +# vmlinux.o.map > modules.builtin.ranges +# + +# Return the module name(s) (if any) associated with the given object. +# +# If we have seen this object before, return information from the cache. +# Otherwise, retrieve it from the corresponding .cmd file. +# +function get_module_info(fn, mod, obj, s) { + if (fn in omod) + return omod[fn]; + + if (match(fn, /\/[^/]+$/) == 0) + return ""; + + obj = fn; + mod = ""; + fn = substr(fn, 1, RSTART) "." substr(fn, RSTART + 1) ".cmd"; + if (getline s <fn == 1) { + if (match(s, /DKBUILD_MODFILE=['"]+[^'"]+/) > 0) { + mod = substr(s, RSTART + 16, RLENGTH - 16); + gsub(/['"]/, "", mod); + } else if (match(s, /RUST_MODFILE=[^ ]+/) > 0) + mod = substr(s, RSTART + 13, RLENGTH - 13); + } + close(fn); + + # A single module (common case) also reflects objects that are not part + # of a module. Some of those objects have names that are also a module + # name (e.g. core). We check the associated module file name, and if + # they do not match, the object is not part of a module. + if (mod !~ / /) { + if (!(mod in mods)) + mod = ""; + } + + gsub(/([^/ ]*\/)+/, "", mod); + gsub(/-/, "_", mod); + + # At this point, mod is a single (valid) module name, or a list of + # module names (that do not need validation). + omod[obj] = mod; + + return mod; +} + +# Update the ranges entry for the given module 'mod' in section 'osect'. +# +# We use a modified absolute start address (soff + base) as index because we +# may need to insert an anchor record later that must be at the start of the +# section data, and the first module may very well start at the same address. +# So, we use (addr << 1) + 1 to allow a possible anchor record to be placed at +# (addr << 1). This is safe because the index is only used to sort the entries +# before writing them out. +# +function update_entry(osect, mod, soff, eoff, sect, idx) { + sect = sect_in[osect]; + idx = sprintf("%016x", (soff + sect_base[osect]) * 2 + 1); + entries[idx] = sprintf("%s %08x-%08x %s", sect, soff, eoff, mod); + count[sect]++; +} + +# (1) Build a lookup map of built-in module names. +# +# The first file argument is used as input (modules.builtin). +# +# Lines will be like: +# kernel/crypto/lzo-rle.ko +# and we record the object name "crypto/lzo-rle". +# +ARGIND == 1 { + sub(/kernel\//, ""); # strip off "kernel/" prefix + sub(/\.ko$/, ""); # strip off .ko suffix + + mods[$1] = 1; + next; +} + +# (2) Collect address information for each section. +# +# The second file argument is used as input (vmlinux.map). +# +# We collect the base address of the section in order to convert all addresses +# in the section into offset values. +# +# We collect the address of the anchor (or first symbol in the section if there +# is no explicit anchor) to allow users of the range data to calculate address +# ranges based on the actual load address of the section in the running kernel. +# +# We collect the start address of any sub-section (section included in the top +# level section being processed). This is needed when the final linking was +# done using vmlinux.a because then the list of objects contained in each +# section is to be obtained from vmlinux.o.map. The offset of the sub-section +# is recorded here, to be used as an addend when processing vmlinux.o.map +# later. +# + +# Both GNU ld and LLVM lld linker map format are supported by converting LLVM +# lld linker map records into equivalent GNU ld linker map records. +# +# The first record of the vmlinux.map file provides enough information to know +# which format we are dealing with. +# +ARGIND == 2 && FNR == 1 && NF == 7 && $1 == "VMA" && $7 == "Symbol" { + map_is_lld = 1; + if (dbg) + printf "NOTE: %s uses LLVM lld linker map format\n", FILENAME >"/dev/stderr"; + next; +} + +# (LLD) Convert a section record fronm lld format to ld format. +# +# lld: ffffffff82c00000 2c00000 2493c0 8192 .data +# -> +# ld: .data 0xffffffff82c00000 0x2493c0 load address 0x0000000002c00000 +# +ARGIND == 2 && map_is_lld && NF == 5 && /[0-9] [^ ]+$/ { + $0 = $5 " 0x"$1 " 0x"$3 " load address 0x"$2; +} + +# (LLD) Convert an anchor record from lld format to ld format. +# +# lld: ffffffff81000000 1000000 0 1 _text = . +# -> +# ld: 0xffffffff81000000 _text = . +# +ARGIND == 2 && map_is_lld && !anchor && NF == 7 && raw_addr == "0x"$1 && $6 == "=" && $7 == "." { + $0 = " 0x"$1 " " $5 " = ."; +} + +# (LLD) Convert an object record from lld format to ld format. +# +# lld: 11480 11480 1f07 16 vmlinux.a(arch/x86/events/amd/uncore.o):(.text) +# -> +# ld: .text 0x0000000000011480 0x1f07 arch/x86/events/amd/uncore.o +# +ARGIND == 2 && map_is_lld && NF == 5 && $5 ~ /:\(/ { + gsub(/\)/, ""); + sub(/ vmlinux\.a\(/, " "); + sub(/:\(/, " "); + $0 = " "$6 " 0x"$1 " 0x"$3 " " $5; +} + +# (LLD) Convert a symbol record from lld format to ld format. +# +# We only care about these while processing a section for which no anchor has +# been determined yet. +# +# lld: ffffffff82a859a4 2a859a4 0 1 btf_ksym_iter_id +# -> +# ld: 0xffffffff82a859a4 btf_ksym_iter_id +# +ARGIND == 2 && map_is_lld && sect && !anchor && NF == 5 && $5 ~ /^[_A-Za-z][_A-Za-z0-9]*$/ { + $0 = " 0x"$1 " " $5; +} + +# (LLD) We do not need any other ldd linker map records. +# +ARGIND == 2 && map_is_lld && /^[0-9a-f]{16} / { + next; +} + +# (LD) Section records with just the section name at the start of the line +# need to have the next line pulled in to determine whether it is a +# loadable section. If it is, the next line will contains a hex value +# as first and second items. +# +ARGIND == 2 && !map_is_lld && NF == 1 && /^[^ ]/ { + s = $0; + getline; + if ($1 !~ /^0x/ || $2 !~ /^0x/) + next; + + $0 = s " " $0; +} + +# (LD) Object records with just the section name denote records with a long +# section name for which the remainder of the record can be found on the +# next line. +# +# (This is also needed for vmlinux.o.map, when used.) +# +ARGIND >= 2 && !map_is_lld && NF == 1 && /^ [^ \*]/ { + s = $0; + getline; + $0 = s " " $0; +} + +# Beginning a new section - done with the previous one (if any). +# +ARGIND == 2 && /^[^ ]/ { + sect = 0; +} + +# Process a loadable section (we only care about .-sections). +# +# Record the section name and its base address. +# We also record the raw (non-stripped) address of the section because it can +# be used to identify an anchor record. +# +# Note: +# Since some AWK implementations cannot handle large integers, we strip off the +# first 4 hex digits from the address. This is safe because the kernel space +# is not large enough for addresses to extend into those digits. The portion +# to strip off is stored in addr_prefix as a regexp, so further clauses can +# perform a simple substitution to do the address stripping. +# +ARGIND == 2 && /^\./ { + # Explicitly ignore a few sections that are not relevant here. + if ($1 ~ /^\.orc_/ || $1 ~ /_sites$/ || $1 ~ /\.percpu/) + next; + + # Sections with a 0-address can be ignored as well. + if ($2 ~ /^0x0+$/) + next; + + raw_addr = $2; + addr_prefix = "^" substr($2, 1, 6); + base = $2; + sub(addr_prefix, "0x", base); + base = strtonum(base); + sect = $1; + anchor = 0; + sect_base[sect] = base; + sect_size[sect] = strtonum($3); + + if (dbg) + printf "[%s] BASE %016x\n", sect, base >"/dev/stderr"; + + next; +} + +# If we are not in a section we care about, we ignore the record. +# +ARGIND == 2 && !sect { + next; +} + +# Record the first anchor symbol for the current section. +# +# An anchor record for the section bears the same raw address as the section +# record. +# +ARGIND == 2 && !anchor && NF == 4 && raw_addr == $1 && $3 == "=" && $4 == "." { + anchor = sprintf("%s %08x-%08x = %s", sect, 0, 0, $2); + sect_anchor[sect] = anchor; + + if (dbg) + printf "[%s] ANCHOR %016x = %s (.)\n", sect, 0, $2 >"/dev/stderr"; + + next; +} + +# If no anchor record was found for the current section, use the first symbol +# in the section as anchor. +# +ARGIND == 2 && !anchor && NF == 2 && $1 ~ /^0x/ && $2 !~ /^0x/ { + addr = $1; + sub(addr_prefix, "0x", addr); + addr = strtonum(addr) - base; + anchor = sprintf("%s %08x-%08x = %s", sect, addr, addr, $2); + sect_anchor[sect] = anchor; + + if (dbg) + printf "[%s] ANCHOR %016x = %s\n", sect, addr, $2 >"/dev/stderr"; + + next; +} + +# The first occurrence of a section name in an object record establishes the +# addend (often 0) for that section. This information is needed to handle +# sections that get combined in the final linking of vmlinux (e.g. .head.text +# getting included at the start of .text). +# +# If the section does not have a base yet, use the base of the encapsulating +# section. +# +ARGIND == 2 && sect && NF == 4 && /^ [^ \*]/ && !($1 in sect_addend) { + # There are a few sections with constant data (without symbols) that + # can get resized during linking, so it is best to ignore them. + if ($1 ~ /^\.rodata\.(cst|str)[0-9]/) + next; + + if (!($1 in sect_base)) { + sect_base[$1] = base; + + if (dbg) + printf "[%s] BASE %016x\n", $1, base >"/dev/stderr"; + } + + addr = $2; + sub(addr_prefix, "0x", addr); + addr = strtonum(addr); + sect_addend[$1] = addr - sect_base[$1]; + sect_in[$1] = sect; + + if (dbg) + printf "[%s] ADDEND %016x - %016x = %016x\n", $1, addr, base, sect_addend[$1] >"/dev/stderr"; + + # If the object is vmlinux.o then we will need vmlinux.o.map to get the + # actual offsets of objects. + if ($4 == "vmlinux.o") + need_o_map = 1; +} + +# (3) Collect offset ranges (relative to the section base address) for built-in +# modules. +# +# If the final link was done using the actual objects, vmlinux.map contains all +# the information we need (see section (3a)). +# If linking was done using vmlinux.a as intermediary, we will need to process +# vmlinux.o.map (see section (3b)). + +# (3a) Determine offset range info using vmlinux.map. +# +# Since we are already processing vmlinux.map, the top level section that is +# being processed is already known. If we do not have a base address for it, +# we do not need to process records for it. +# +# Given the object name, we determine the module(s) (if any) that the current +# object is associated with. +# +# If we were already processing objects for a (list of) module(s): +# - If the current object belongs to the same module(s), update the range data +# to include the current object. +# - Otherwise, ensure that the end offset of the range is valid. +# +# If the current object does not belong to a built-in module, ignore it. +# +# If it does, we add a new built-in module offset range record. +# +ARGIND == 2 && !need_o_map && /^ [^ ]/ && NF == 4 && $3 != "0x0" { + if (!(sect in sect_base)) + next; + + # Turn the address into an offset from the section base. + soff = $2; + sub(addr_prefix, "0x", soff); + soff = strtonum(soff) - sect_base[sect]; + eoff = soff + strtonum($3); + + # Determine which (if any) built-in modules the object belongs to. + mod = get_module_info($4); + + # If we are processing a built-in module: + # - If the current object is within the same module, we update its + # entry by extending the range and move on + # - Otherwise: + # + If we are still processing within the same main section, we + # validate the end offset against the start offset of the + # current object (e.g. .rodata.str1.[18] objects are often + # listed with an incorrect size in the linker map) + # + Otherwise, we validate the end offset against the section + # size + if (mod_name) { + if (mod == mod_name) { + mod_eoff = eoff; + update_entry(mod_sect, mod_name, mod_soff, eoff); + + next; + } else if (sect == sect_in[mod_sect]) { + if (mod_eoff > soff) + update_entry(mod_sect, mod_name, mod_soff, soff); + } else { + v = sect_size[sect_in[mod_sect]]; + if (mod_eoff > v) + update_entry(mod_sect, mod_name, mod_soff, v); + } + } + + mod_name = mod; + + # If we encountered an object that is not part of a built-in module, we + # do not need to record any data. + if (!mod) + next; + + # At this point, we encountered the start of a new built-in module. + mod_name = mod; + mod_soff = soff; + mod_eoff = eoff; + mod_sect = $1; + update_entry($1, mod, soff, mod_eoff); + + next; +} + +# If we do not need to parse the vmlinux.o.map file, we are done. +# +ARGIND == 3 && !need_o_map { + if (dbg) + printf "Note: %s is not needed.\n", FILENAME >"/dev/stderr"; + exit; +} + +# (3) Collect offset ranges (relative to the section base address) for built-in +# modules. +# + +# (LLD) Convert an object record from lld format to ld format. +# +ARGIND == 3 && map_is_lld && NF == 5 && $5 ~ /:\(/ { + gsub(/\)/, ""); + sub(/:\(/, " "); + + sect = $6; + if (!(sect in sect_addend)) + next; + + sub(/ vmlinux\.a\(/, " "); + $0 = " "sect " 0x"$1 " 0x"$3 " " $5; +} + +# (3b) Determine offset range info using vmlinux.o.map. +# +# If we do not know an addend for the object's section, we are interested in +# anything within that section. +# +# Determine the top-level section that the object's section was included in +# during the final link. This is the section name offset range data will be +# associated with for this object. +# +# The remainder of the processing of the current object record follows the +# procedure outlined in (3a). +# +ARGIND == 3 && /^ [^ ]/ && NF == 4 && $3 != "0x0" { + osect = $1; + if (!(osect in sect_addend)) + next; + + # We need to work with the main section. + sect = sect_in[osect]; + + # Turn the address into an offset from the section base. + soff = $2; + sub(addr_prefix, "0x", soff); + soff = strtonum(soff) + sect_addend[osect]; + eoff = soff + strtonum($3); + + # Determine which (if any) built-in modules the object belongs to. + mod = get_module_info($4); + + # If we are processing a built-in module: + # - If the current object is within the same module, we update its + # entry by extending the range and move on + # - Otherwise: + # + If we are still processing within the same main section, we + # validate the end offset against the start offset of the + # current object (e.g. .rodata.str1.[18] objects are often + # listed with an incorrect size in the linker map) + # + Otherwise, we validate the end offset against the section + # size + if (mod_name) { + if (mod == mod_name) { + mod_eoff = eoff; + update_entry(mod_sect, mod_name, mod_soff, eoff); + + next; + } else if (sect == sect_in[mod_sect]) { + if (mod_eoff > soff) + update_entry(mod_sect, mod_name, mod_soff, soff); + } else { + v = sect_size[sect_in[mod_sect]]; + if (mod_eoff > v) + update_entry(mod_sect, mod_name, mod_soff, v); + } + } + + mod_name = mod; + + # If we encountered an object that is not part of a built-in module, we + # do not need to record any data. + if (!mod) + next; + + # At this point, we encountered the start of a new built-in module. + mod_name = mod; + mod_soff = soff; + mod_eoff = eoff; + mod_sect = osect; + update_entry(osect, mod, soff, mod_eoff); + + next; +} + +# (4) Generate the output. +# +# Anchor records are added for each section that contains offset range data +# records. They are added at an adjusted section base address (base << 1) to +# ensure they come first in the second records (see update_entry() above for +# more information). +# +# All entries are sorted by (adjusted) address to ensure that the output can be +# parsed in strict ascending address order. +# +END { + for (sect in count) { + if (sect in sect_anchor) { + idx = sprintf("%016x", sect_base[sect] * 2); + entries[idx] = sect_anchor[sect]; + } + } + + n = asorti(entries, indices); + for (i = 1; i <= n; i++) + print entries[indices[i]]; +} diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index fc52bc41d3e7..7c3ea2b55041 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -8,6 +8,7 @@ import json import logging import os import pathlib +import subprocess import sys def args_crates_cfgs(cfgs): @@ -18,7 +19,7 @@ def args_crates_cfgs(cfgs): return crates_cfgs -def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): +def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs, core_edition): # Generate the configuration list. cfg = [] with open(objtree / "include" / "generated" / "rustc_cfg") as fd: @@ -34,29 +35,50 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): crates_indexes = {} crates_cfgs = args_crates_cfgs(cfgs) - def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False): - crates_indexes[display_name] = len(crates) - crates.append({ + def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False, edition="2021"): + crate = { "display_name": display_name, "root_module": str(root_module), "is_workspace_member": is_workspace_member, "is_proc_macro": is_proc_macro, "deps": [{"crate": crates_indexes[dep], "name": dep} for dep in deps], "cfg": cfg, - "edition": "2021", + "edition": edition, "env": { "RUST_MODFILE": "This is only for rust-analyzer" } - }) - - # First, the ones in `rust/` since they are a bit special. - append_crate( - "core", - sysroot_src / "core" / "src" / "lib.rs", - [], - cfg=crates_cfgs.get("core", []), - is_workspace_member=False, - ) + } + if is_proc_macro: + proc_macro_dylib_name = subprocess.check_output( + [os.environ["RUSTC"], "--print", "file-names", "--crate-name", display_name, "--crate-type", "proc-macro", "-"], + stdin=subprocess.DEVNULL, + ).decode('utf-8').strip() + crate["proc_macro_dylib_path"] = f"{objtree}/rust/{proc_macro_dylib_name}" + crates_indexes[display_name] = len(crates) + crates.append(crate) + + def append_sysroot_crate( + display_name, + deps, + cfg=[], + edition="2021", + ): + append_crate( + display_name, + sysroot_src / display_name / "src" / "lib.rs", + deps, + cfg, + is_workspace_member=False, + edition=edition, + ) + + # NB: sysroot crates reexport items from one another so setting up our transitive dependencies + # here is important for ensuring that rust-analyzer can resolve symbols. The sources of truth + # for this dependency graph are `(sysroot_src / crate / "Cargo.toml" for crate in crates)`. + append_sysroot_crate("core", [], cfg=crates_cfgs.get("core", []), edition=core_edition) + append_sysroot_crate("alloc", ["core"]) + append_sysroot_crate("std", ["alloc", "core"]) + append_sysroot_crate("proc_macro", ["core", "std"]) append_crate( "compiler_builtins", @@ -65,19 +87,11 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): ) append_crate( - "alloc", - srctree / "rust" / "alloc" / "lib.rs", - ["core", "compiler_builtins"], - cfg=crates_cfgs.get("alloc", []), - ) - - append_crate( "macros", srctree / "rust" / "macros" / "lib.rs", - [], + ["std", "proc_macro"], is_proc_macro=True, ) - crates[-1]["proc_macro_dylib_path"] = f"{objtree}/rust/libmacros.so" append_crate( "build_error", @@ -86,26 +100,48 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): ) append_crate( - "bindings", - srctree / "rust"/ "bindings" / "lib.rs", - ["core"], - cfg=cfg, + "pin_init_internal", + srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs", + [], + cfg=["kernel"], + is_proc_macro=True, ) - crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True)) append_crate( - "kernel", - srctree / "rust" / "kernel" / "lib.rs", - ["core", "alloc", "macros", "build_error", "bindings"], - cfg=cfg, + "pin_init", + srctree / "rust" / "pin-init" / "src" / "lib.rs", + ["core", "pin_init_internal", "macros"], + cfg=["kernel"], + ) + + append_crate( + "ffi", + srctree / "rust" / "ffi.rs", + ["core", "compiler_builtins"], ) - crates[-1]["source"] = { - "include_dirs": [ - str(srctree / "rust" / "kernel"), - str(objtree / "rust") - ], - "exclude_dirs": [], - } + + def append_crate_with_generated( + display_name, + deps, + ): + append_crate( + display_name, + srctree / "rust"/ display_name / "lib.rs", + deps, + cfg=cfg, + ) + crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True)) + crates[-1]["source"] = { + "include_dirs": [ + str(srctree / "rust" / display_name), + str(objtree / "rust") + ], + "exclude_dirs": [], + } + + append_crate_with_generated("bindings", ["core", "ffi"]) + append_crate_with_generated("uapi", ["core", "ffi"]) + append_crate_with_generated("kernel", ["core", "macros", "build_error", "pin_init", "ffi", "bindings", "uapi"]) def is_root_crate(build_file, target): try: @@ -133,7 +169,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( name, path, - ["core", "alloc", "kernel"], + ["core", "kernel"], cfg=cfg, ) @@ -143,8 +179,10 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('--verbose', '-v', action='store_true') parser.add_argument('--cfgs', action='append', default=[]) + parser.add_argument("core_edition") parser.add_argument("srctree", type=pathlib.Path) parser.add_argument("objtree", type=pathlib.Path) + parser.add_argument("sysroot", type=pathlib.Path) parser.add_argument("sysroot_src", type=pathlib.Path) parser.add_argument("exttree", type=pathlib.Path, nargs="?") args = parser.parse_args() @@ -154,9 +192,12 @@ def main(): level=logging.INFO if args.verbose else logging.WARNING ) + # Making sure that the `sysroot` and `sysroot_src` belong to the same toolchain. + assert args.sysroot in args.sysroot_src.parents + rust_project = { - "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs), - "sysroot_src": str(args.sysroot_src), + "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs, args.core_edition), + "sysroot": str(args.sysroot), } json.dump(rust_project, sys.stdout, sort_keys=True, indent=4) diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs index 54919cf48621..39c82908ff3a 100644 --- a/scripts/generate_rust_target.rs +++ b/scripts/generate_rust_target.rs @@ -20,12 +20,28 @@ enum Value { Boolean(bool), Number(i32), String(String), + Array(Vec<Value>), Object(Object), } type Object = Vec<(String, Value)>; -/// Minimal "almost JSON" generator (e.g. no `null`s, no arrays, no escaping), +fn comma_sep<T>( + seq: &[T], + formatter: &mut Formatter<'_>, + f: impl Fn(&mut Formatter<'_>, &T) -> Result, +) -> Result { + if let [ref rest @ .., ref last] = seq[..] { + for v in rest { + f(formatter, v)?; + formatter.write_str(",")?; + } + f(formatter, last)?; + } + Ok(()) +} + +/// Minimal "almost JSON" generator (e.g. no `null`s, no escaping), /// enough for this purpose. impl Display for Value { fn fmt(&self, formatter: &mut Formatter<'_>) -> Result { @@ -33,59 +49,67 @@ impl Display for Value { Value::Boolean(boolean) => write!(formatter, "{}", boolean), Value::Number(number) => write!(formatter, "{}", number), Value::String(string) => write!(formatter, "\"{}\"", string), + Value::Array(values) => { + formatter.write_str("[")?; + comma_sep(&values[..], formatter, |formatter, v| v.fmt(formatter))?; + formatter.write_str("]") + } Value::Object(object) => { formatter.write_str("{")?; - if let [ref rest @ .., ref last] = object[..] { - for (key, value) in rest { - write!(formatter, "\"{}\": {},", key, value)?; - } - write!(formatter, "\"{}\": {}", last.0, last.1)?; - } + comma_sep(&object[..], formatter, |formatter, v| { + write!(formatter, "\"{}\": {}", v.0, v.1) + })?; formatter.write_str("}") } } } } -struct TargetSpec(Object); - -impl TargetSpec { - fn new() -> TargetSpec { - TargetSpec(Vec::new()) +impl From<bool> for Value { + fn from(value: bool) -> Self { + Self::Boolean(value) } } -trait Push<T> { - fn push(&mut self, key: &str, value: T); +impl From<i32> for Value { + fn from(value: i32) -> Self { + Self::Number(value) + } } -impl Push<bool> for TargetSpec { - fn push(&mut self, key: &str, value: bool) { - self.0.push((key.to_string(), Value::Boolean(value))); +impl From<String> for Value { + fn from(value: String) -> Self { + Self::String(value) } } -impl Push<i32> for TargetSpec { - fn push(&mut self, key: &str, value: i32) { - self.0.push((key.to_string(), Value::Number(value))); +impl From<&str> for Value { + fn from(value: &str) -> Self { + Self::String(value.to_string()) } } -impl Push<String> for TargetSpec { - fn push(&mut self, key: &str, value: String) { - self.0.push((key.to_string(), Value::String(value))); +impl From<Object> for Value { + fn from(object: Object) -> Self { + Self::Object(object) } } -impl Push<&str> for TargetSpec { - fn push(&mut self, key: &str, value: &str) { - self.push(key, value.to_string()); +impl<T: Into<Value>, const N: usize> From<[T; N]> for Value { + fn from(i: [T; N]) -> Self { + Self::Array(i.into_iter().map(|v| v.into()).collect()) } } -impl Push<Object> for TargetSpec { - fn push(&mut self, key: &str, value: Object) { - self.0.push((key.to_string(), Value::Object(value))); +struct TargetSpec(Object); + +impl TargetSpec { + fn new() -> TargetSpec { + TargetSpec(Vec::new()) + } + + fn push(&mut self, key: &str, value: impl Into<Value>) { + self.0.push((key.to_string(), value.into())); } } @@ -141,6 +165,18 @@ impl KernelConfig { let option = "CONFIG_".to_owned() + option; self.0.contains_key(&option) } + + /// Is the rustc version at least `major.minor.patch`? + fn rustc_version_atleast(&self, major: u32, minor: u32, patch: u32) -> bool { + let check_version = 100000 * major + 100 * minor + patch; + let actual_version = self + .0 + .get("CONFIG_RUSTC_VERSION") + .unwrap() + .parse::<u32>() + .unwrap(); + check_version <= actual_version + } } fn main() { @@ -148,28 +184,70 @@ fn main() { let mut ts = TargetSpec::new(); // `llvm-target`s are taken from `scripts/Makefile.clang`. - if cfg.has("ARM64") { + if cfg.has("ARM") { + panic!("arm uses the builtin rustc target"); + } else if cfg.has("ARM64") { panic!("arm64 uses the builtin rustc aarch64-unknown-none target"); + } else if cfg.has("RISCV") { + if cfg.has("64BIT") { + panic!("64-bit RISC-V uses the builtin rustc riscv64-unknown-none-elf target"); + } else { + panic!("32-bit RISC-V is an unsupported architecture"); + } } else if cfg.has("X86_64") { ts.push("arch", "x86_64"); + if cfg.rustc_version_atleast(1, 86, 0) { + ts.push("rustc-abi", "x86-softfloat"); + } ts.push( "data-layout", - "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", + "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", ); - let mut features = "-3dnow,-3dnowa,-mmx,+soft-float".to_string(); + let mut features = "-mmx,+soft-float".to_string(); if cfg.has("MITIGATION_RETPOLINE") { + // The kernel uses `-mretpoline-external-thunk` (for Clang), which Clang maps to the + // target feature of the same name plus the other two target features in + // `clang/lib/Driver/ToolChains/Arch/X86.cpp`. These should be eventually enabled via + // `-Ctarget-feature` when `rustc` starts recognizing them (or via a new dedicated + // flag); see <https://github.com/rust-lang/rust/issues/116852>. features += ",+retpoline-external-thunk"; + features += ",+retpoline-indirect-branches"; + features += ",+retpoline-indirect-calls"; + } + if cfg.has("MITIGATION_SLS") { + // The kernel uses `-mharden-sls=all`, which Clang maps to both these target features in + // `clang/lib/Driver/ToolChains/Arch/X86.cpp`. These should be eventually enabled via + // `-Ctarget-feature` when `rustc` starts recognizing them (or via a new dedicated + // flag); see <https://github.com/rust-lang/rust/issues/116851>. + features += ",+harden-sls-ijmp"; + features += ",+harden-sls-ret"; } ts.push("features", features); ts.push("llvm-target", "x86_64-linux-gnu"); + ts.push("supported-sanitizers", ["kcfi", "kernel-address"]); ts.push("target-pointer-width", "64"); + } else if cfg.has("X86_32") { + // This only works on UML, as i386 otherwise needs regparm support in rustc + if !cfg.has("UML") { + panic!("32-bit x86 only works under UML"); + } + ts.push("arch", "x86"); + if cfg.rustc_version_atleast(1, 86, 0) { + ts.push("rustc-abi", "x86-softfloat"); + } + ts.push( + "data-layout", + "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", + ); + let mut features = "-mmx,+soft-float".to_string(); + if cfg.has("MITIGATION_RETPOLINE") { + features += ",+retpoline-external-thunk"; + } + ts.push("features", features); + ts.push("llvm-target", "i386-unknown-linux-gnu"); + ts.push("target-pointer-width", "32"); } else if cfg.has("LOONGARCH") { - ts.push("arch", "loongarch64"); - ts.push("data-layout", "e-m:e-p:64:64-i64:64-i128:128-n64-S128"); - ts.push("features", "-f,-d"); - ts.push("llvm-target", "loongarch64-linux-gnusf"); - ts.push("llvm-abiname", "lp64s"); - ts.push("target-pointer-width", "64"); + panic!("loongarch uses the builtin rustc loongarch64-unknown-none-softfloat target"); } else { panic!("Unsupported architecture"); } diff --git a/scripts/genksyms/Makefile b/scripts/genksyms/Makefile index d6a422a63b6a..4350311fb7b3 100644 --- a/scripts/genksyms/Makefile +++ b/scripts/genksyms/Makefile @@ -4,27 +4,9 @@ hostprogs-always-y += genksyms genksyms-objs := genksyms.o parse.tab.o lex.lex.o -# FIXME: fix the ambiguous grammar in parse.y and delete this hack -# -# Suppress shift/reduce, reduce/reduce conflicts warnings -# unless W=1 is specified. -# -# Just in case, run "$(YACC) --version" without suppressing stderr -# so that 'bison: not found' will be displayed if it is missing. -ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),) - -quiet_cmd_bison_no_warn = $(quiet_cmd_bison) - cmd_bison_no_warn = $(YACC) --version >/dev/null; \ - $(cmd_bison) 2>/dev/null - -$(obj)/pars%.tab.c $(obj)/pars%.tab.h: $(src)/pars%.y FORCE - $(call if_changed,bison_no_warn) - -endif - # -I needed for generated C source to include headers in source tree -HOSTCFLAGS_parse.tab.o := -I $(srctree)/$(src) -HOSTCFLAGS_lex.lex.o := -I $(srctree)/$(src) +HOSTCFLAGS_parse.tab.o := -I $(src) +HOSTCFLAGS_lex.lex.o := -I $(src) # dependencies on generated files need to be listed explicitly $(obj)/lex.lex.o: $(obj)/parse.tab.h diff --git a/scripts/genksyms/genksyms.c b/scripts/genksyms/genksyms.c index f3901c55df23..83e48670c2fc 100644 --- a/scripts/genksyms/genksyms.c +++ b/scripts/genksyms/genksyms.c @@ -12,18 +12,19 @@ #include <stdio.h> #include <string.h> +#include <stdint.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <stdarg.h> #include <getopt.h> +#include <hashtable.h> + #include "genksyms.h" /*----------------------------------------------------------------------*/ -#define HASH_BUCKETS 4096 - -static struct symbol *symtab[HASH_BUCKETS]; +static HASHTABLE_DEFINE(symbol_hashtable, 1U << 12); static FILE *debugfile; int cur_line = 1; @@ -60,7 +61,7 @@ static void print_type_name(enum symbol_type type, const char *name); /*----------------------------------------------------------------------*/ -static const unsigned int crctab32[] = { +static const uint32_t crctab32[] = { 0x00000000U, 0x77073096U, 0xee0e612cU, 0x990951baU, 0x076dc419U, 0x706af48fU, 0xe963a535U, 0x9e6495a3U, 0x0edb8832U, 0x79dcb8a4U, 0xe0d5e91eU, 0x97d2d988U, 0x09b64c2bU, 0x7eb17cbdU, 0xe7b82d07U, @@ -115,19 +116,19 @@ static const unsigned int crctab32[] = { 0x2d02ef8dU }; -static unsigned long partial_crc32_one(unsigned char c, unsigned long crc) +static uint32_t partial_crc32_one(uint8_t c, uint32_t crc) { return crctab32[(crc ^ c) & 0xff] ^ (crc >> 8); } -static unsigned long partial_crc32(const char *s, unsigned long crc) +static uint32_t partial_crc32(const char *s, uint32_t crc) { while (*s) crc = partial_crc32_one(*s++, crc); return crc; } -static unsigned long crc32(const char *s) +static uint32_t crc32(const char *s) { return partial_crc32(s, 0xffffffff) ^ 0xffffffff; } @@ -151,14 +152,14 @@ static enum symbol_type map_to_ns(enum symbol_type t) struct symbol *find_symbol(const char *name, enum symbol_type ns, int exact) { - unsigned long h = crc32(name) % HASH_BUCKETS; struct symbol *sym; - for (sym = symtab[h]; sym; sym = sym->hash_next) + hash_for_each_possible(symbol_hashtable, sym, hnode, crc32(name)) { if (map_to_ns(sym->type) == map_to_ns(ns) && strcmp(name, sym->name) == 0 && sym->is_declared) break; + } if (exact && sym && sym->type != ns) return NULL; @@ -180,13 +181,9 @@ static int is_unknown_symbol(struct symbol *sym) strcmp(defn->string, "{") == 0); } -static struct symbol *__add_symbol(const char *name, enum symbol_type type, - struct string_list *defn, int is_extern, - int is_reference) +static struct string_list *process_enum(const char *name, enum symbol_type type, + struct string_list *defn) { - unsigned long h; - struct symbol *sym; - enum symbol_status status = STATUS_UNCHANGED; /* The parser adds symbols in the order their declaration completes, * so it is safe to store the value of the previous enum constant in * a static variable. @@ -215,7 +212,7 @@ static struct symbol *__add_symbol(const char *name, enum symbol_type type, defn = mk_node(buf); } } - } else if (type == SYM_ENUM) { + } else { free_list(last_enum_expr, NULL); last_enum_expr = NULL; enum_counter = 0; @@ -224,64 +221,73 @@ static struct symbol *__add_symbol(const char *name, enum symbol_type type, return NULL; } - h = crc32(name) % HASH_BUCKETS; - for (sym = symtab[h]; sym; sym = sym->hash_next) { - if (map_to_ns(sym->type) == map_to_ns(type) && - strcmp(name, sym->name) == 0) { - if (is_reference) - /* fall through */ ; - else if (sym->type == type && - equal_list(sym->defn, defn)) { - if (!sym->is_declared && sym->is_override) { - print_location(); - print_type_name(type, name); - fprintf(stderr, " modversion is " - "unchanged\n"); - } - sym->is_declared = 1; - return sym; - } else if (!sym->is_declared) { - if (sym->is_override && flag_preserve) { - print_location(); - fprintf(stderr, "ignoring "); - print_type_name(type, name); - fprintf(stderr, " modversion change\n"); - sym->is_declared = 1; - return sym; - } else { - status = is_unknown_symbol(sym) ? - STATUS_DEFINED : STATUS_MODIFIED; - } - } else { - error_with_pos("redefinition of %s", name); - return sym; + return defn; +} + +static struct symbol *__add_symbol(const char *name, enum symbol_type type, + struct string_list *defn, int is_extern, + int is_reference) +{ + unsigned long h; + struct symbol *sym; + enum symbol_status status = STATUS_UNCHANGED; + + if ((type == SYM_ENUM_CONST || type == SYM_ENUM) && !is_reference) { + defn = process_enum(name, type, defn); + if (defn == NULL) + return NULL; + } + + h = crc32(name); + hash_for_each_possible(symbol_hashtable, sym, hnode, h) { + if (map_to_ns(sym->type) != map_to_ns(type) || + strcmp(name, sym->name)) + continue; + + if (is_reference) { + break; + } else if (sym->type == type && equal_list(sym->defn, defn)) { + if (!sym->is_declared && sym->is_override) { + print_location(); + print_type_name(type, name); + fprintf(stderr, " modversion is unchanged\n"); } + sym->is_declared = 1; + } else if (sym->is_declared) { + error_with_pos("redefinition of %s", name); + } else if (sym->is_override && flag_preserve) { + print_location(); + fprintf(stderr, "ignoring "); + print_type_name(type, name); + fprintf(stderr, " modversion change\n"); + sym->is_declared = 1; + } else { + status = is_unknown_symbol(sym) ? + STATUS_DEFINED : STATUS_MODIFIED; break; } + free_list(defn, NULL); + return sym; } if (sym) { - struct symbol **psym; + hash_del(&sym->hnode); - for (psym = &symtab[h]; *psym; psym = &(*psym)->hash_next) { - if (*psym == sym) { - *psym = sym->hash_next; - break; - } - } + free_list(sym->defn, NULL); + free(sym->name); + free(sym); --nsyms; } sym = xmalloc(sizeof(*sym)); - sym->name = name; + sym->name = xstrdup(name); sym->type = type; sym->defn = defn; sym->expansion_trail = NULL; sym->visited = NULL; sym->is_extern = is_extern; - sym->hash_next = symtab[h]; - symtab[h] = sym; + hash_add(symbol_hashtable, &sym->hnode, h); sym->is_declared = !is_reference; sym->status = status; @@ -480,7 +486,7 @@ static void read_reference(FILE *f) defn = def; def = read_node(f); } - subsym = add_reference_symbol(xstrdup(sym->string), sym->tag, + subsym = add_reference_symbol(sym->string, sym->tag, defn, is_extern); subsym->is_override = is_override; free_node(sym); @@ -525,7 +531,7 @@ static void print_list(FILE * f, struct string_list *list) } } -static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc) +static uint32_t expand_and_crc_sym(struct symbol *sym, uint32_t crc) { struct string_list *list = sym->defn; struct string_list **e, **b; @@ -632,54 +638,55 @@ static unsigned long expand_and_crc_sym(struct symbol *sym, unsigned long crc) void export_symbol(const char *name) { struct symbol *sym; + uint32_t crc; + int has_changed = 0; sym = find_symbol(name, SYM_NORMAL, 0); - if (!sym) + if (!sym) { error_with_pos("export undefined symbol %s", name); - else { - unsigned long crc; - int has_changed = 0; - - if (flag_dump_defs) - fprintf(debugfile, "Export %s == <", name); - - expansion_trail = (struct symbol *)-1L; - - sym->expansion_trail = expansion_trail; - expansion_trail = sym; - crc = expand_and_crc_sym(sym, 0xffffffff) ^ 0xffffffff; - - sym = expansion_trail; - while (sym != (struct symbol *)-1L) { - struct symbol *n = sym->expansion_trail; - - if (sym->status != STATUS_UNCHANGED) { - if (!has_changed) { - print_location(); - fprintf(stderr, "%s: %s: modversion " - "changed because of changes " - "in ", flag_preserve ? "error" : - "warning", name); - } else - fprintf(stderr, ", "); - print_type_name(sym->type, sym->name); - if (sym->status == STATUS_DEFINED) - fprintf(stderr, " (became defined)"); - has_changed = 1; - if (flag_preserve) - errors++; + return; + } + + if (flag_dump_defs) + fprintf(debugfile, "Export %s == <", name); + + expansion_trail = (struct symbol *)-1L; + + sym->expansion_trail = expansion_trail; + expansion_trail = sym; + crc = expand_and_crc_sym(sym, 0xffffffff) ^ 0xffffffff; + + sym = expansion_trail; + while (sym != (struct symbol *)-1L) { + struct symbol *n = sym->expansion_trail; + + if (sym->status != STATUS_UNCHANGED) { + if (!has_changed) { + print_location(); + fprintf(stderr, + "%s: %s: modversion changed because of changes in ", + flag_preserve ? "error" : "warning", + name); + } else { + fprintf(stderr, ", "); } - sym->expansion_trail = 0; - sym = n; + print_type_name(sym->type, sym->name); + if (sym->status == STATUS_DEFINED) + fprintf(stderr, " (became defined)"); + has_changed = 1; + if (flag_preserve) + errors++; } - if (has_changed) - fprintf(stderr, "\n"); + sym->expansion_trail = 0; + sym = n; + } + if (has_changed) + fprintf(stderr, "\n"); - if (flag_dump_defs) - fputs(">\n", debugfile); + if (flag_dump_defs) + fputs(">\n", debugfile); - printf("#SYMVER %s 0x%08lx\n", name, crc); - } + printf("#SYMVER %s 0x%08lx\n", name, (unsigned long)crc); } /*----------------------------------------------------------------------*/ @@ -831,9 +838,9 @@ int main(int argc, char **argv) } if (flag_debug) { - fprintf(debugfile, "Hash table occupancy %d/%d = %g\n", - nsyms, HASH_BUCKETS, - (double)nsyms / (double)HASH_BUCKETS); + fprintf(debugfile, "Hash table occupancy %d/%zd = %g\n", + nsyms, HASH_SIZE(symbol_hashtable), + (double)nsyms / HASH_SIZE(symbol_hashtable)); } if (dumpfile) diff --git a/scripts/genksyms/genksyms.h b/scripts/genksyms/genksyms.h index 21ed2ec2d98c..0c355075f0e6 100644 --- a/scripts/genksyms/genksyms.h +++ b/scripts/genksyms/genksyms.h @@ -12,8 +12,11 @@ #ifndef MODUTILS_GENKSYMS_H #define MODUTILS_GENKSYMS_H 1 +#include <stdbool.h> #include <stdio.h> +#include <list_types.h> + enum symbol_type { SYM_NORMAL, SYM_TYPEDEF, SYM_ENUM, SYM_STRUCT, SYM_UNION, SYM_ENUM_CONST @@ -31,8 +34,8 @@ struct string_list { }; struct symbol { - struct symbol *hash_next; - const char *name; + struct hlist_node hnode; + char *name; enum symbol_type type; struct string_list *defn; struct symbol *expansion_trail; @@ -64,6 +67,8 @@ struct string_list *copy_list_range(struct string_list *start, int yylex(void); int yyparse(void); +extern bool dont_want_type_specifier; + void error_with_pos(const char *, ...) __attribute__ ((format(printf, 1, 2))); /*----------------------------------------------------------------------*/ diff --git a/scripts/genksyms/keywords.c b/scripts/genksyms/keywords.c index b85e0979a00c..ee1499d27061 100644 --- a/scripts/genksyms/keywords.c +++ b/scripts/genksyms/keywords.c @@ -17,6 +17,8 @@ static struct resword { { "__signed__", SIGNED_KEYW }, { "__typeof", TYPEOF_KEYW }, { "__typeof__", TYPEOF_KEYW }, + { "__typeof_unqual", TYPEOF_KEYW }, + { "__typeof_unqual__", TYPEOF_KEYW }, { "__volatile", VOLATILE_KEYW }, { "__volatile__", VOLATILE_KEYW }, { "__builtin_va_list", VA_LIST_KEYW }, @@ -40,6 +42,10 @@ static struct resword { // KAO. }, // { "attribute", ATTRIBUTE_KEYW }, + // X86 named address space qualifiers + { "__seg_gs", X86_SEG_KEYW }, + { "__seg_fs", X86_SEG_KEYW }, + { "auto", AUTO_KEYW }, { "char", CHAR_KEYW }, { "const", CONST_KEYW }, @@ -57,6 +63,7 @@ static struct resword { { "struct", STRUCT_KEYW }, { "typedef", TYPEDEF_KEYW }, { "typeof", TYPEOF_KEYW }, + { "typeof_unqual", TYPEOF_KEYW }, { "union", UNION_KEYW }, { "unsigned", UNSIGNED_KEYW }, { "void", VOID_KEYW }, diff --git a/scripts/genksyms/lex.l b/scripts/genksyms/lex.l index a4d7495eaf75..f81033af1528 100644 --- a/scripts/genksyms/lex.l +++ b/scripts/genksyms/lex.l @@ -12,6 +12,7 @@ %{ #include <limits.h> +#include <stdbool.h> #include <stdlib.h> #include <string.h> #include <ctype.h> @@ -50,6 +51,7 @@ MC_TOKEN ([~%^&*+=|<>/-]=)|(&&)|("||")|(->)|(<<)|(>>) %% +u?int(8|16|32|64)x(1|2|4|8|16)_t return BUILTIN_INT_KEYW; /* Keep track of our location in the original source files. */ ^#[ \t]+{INT}[ \t]+\"[^\"\n]+\".*\n return FILENAME; @@ -113,6 +115,12 @@ MC_TOKEN ([~%^&*+=|<>/-]=)|(&&)|("||")|(->)|(<<)|(>>) /* The second stage lexer. Here we incorporate knowledge of the state of the parser to tailor the tokens that are returned. */ +/* + * The lexer cannot distinguish whether a typedef'ed string is a TYPE or an + * IDENT. We need a hint from the parser to handle this accurately. + */ +bool dont_want_type_specifier; + int yylex(void) { @@ -168,10 +176,10 @@ repeat: switch (lexstate) { case ST_NORMAL: + APP; switch (token) { case IDENT: - APP; { int r = is_reserved_word(yytext, yyleng); if (r >= 0) @@ -207,7 +215,7 @@ repeat: goto repeat; } } - if (!suppress_type_lookup) + if (!suppress_type_lookup && !dont_want_type_specifier) { if (find_symbol(yytext, SYM_TYPEDEF, 1)) token = TYPE; @@ -216,13 +224,11 @@ repeat: break; case '[': - APP; lexstate = ST_BRACKET; count = 1; goto repeat; case '{': - APP; if (dont_want_brace_phrase) break; lexstate = ST_BRACE; @@ -230,12 +236,10 @@ repeat: goto repeat; case '=': case ':': - APP; lexstate = ST_EXPRESSION; break; default: - APP; break; } break; @@ -431,7 +435,12 @@ fini: if (suppress_type_lookup > 0) --suppress_type_lookup; - if (dont_want_brace_phrase > 0) + + /* + * __attribute__() can be placed immediately after the 'struct' keyword. + * e.g.) struct __attribute__((__packed__)) foo { ... }; + */ + if (token != ATTRIBUTE_PHRASE && dont_want_brace_phrase > 0) --dont_want_brace_phrase; yylval = &next_node->next; diff --git a/scripts/genksyms/parse.y b/scripts/genksyms/parse.y index 8e9b5e69e8f0..efdcf07c4eb6 100644 --- a/scripts/genksyms/parse.y +++ b/scripts/genksyms/parse.y @@ -12,6 +12,7 @@ %{ #include <assert.h> +#include <stdbool.h> #include <stdlib.h> #include <string.h> #include "genksyms.h" @@ -90,6 +91,8 @@ static void record_compound(struct string_list **keyw, %token TYPEOF_KEYW %token VA_LIST_KEYW +%token X86_SEG_KEYW + %token EXPORT_SYMBOL_KEYW %token ASM_PHRASE @@ -148,32 +151,45 @@ simple_declaration: current_name = NULL; } $$ = $3; + dont_want_type_specifier = false; } ; init_declarator_list_opt: - /* empty */ { $$ = NULL; } - | init_declarator_list + /* empty */ { $$ = NULL; } + | init_declarator_list { free_list(decl_spec, NULL); $$ = $1; } ; init_declarator_list: init_declarator { struct string_list *decl = *$1; *$1 = NULL; + + /* avoid sharing among multiple init_declarators */ + if (decl_spec) + decl_spec = copy_list_range(decl_spec, NULL); + add_symbol(current_name, is_typedef ? SYM_TYPEDEF : SYM_NORMAL, decl, is_extern); current_name = NULL; $$ = $1; + dont_want_type_specifier = true; } - | init_declarator_list ',' init_declarator - { struct string_list *decl = *$3; - *$3 = NULL; + | init_declarator_list ',' attribute_opt init_declarator + { struct string_list *decl = *$4; + *$4 = NULL; free_list(*$2, NULL); *$2 = decl_spec; + + /* avoid sharing among multiple init_declarators */ + if (decl_spec) + decl_spec = copy_list_range(decl_spec, NULL); + add_symbol(current_name, is_typedef ? SYM_TYPEDEF : SYM_NORMAL, decl, is_extern); current_name = NULL; - $$ = $3; + $$ = $4; + dont_want_type_specifier = true; } ; @@ -189,8 +205,9 @@ decl_specifier_seq_opt: ; decl_specifier_seq: - decl_specifier { decl_spec = *$1; } + attribute_opt decl_specifier { decl_spec = *$2; } | decl_specifier_seq decl_specifier { decl_spec = *$2; } + | decl_specifier_seq ATTRIBUTE_PHRASE { decl_spec = *$2; } ; decl_specifier: @@ -200,7 +217,8 @@ decl_specifier: remove_node($1); $$ = $1; } - | type_specifier + | type_specifier { dont_want_type_specifier = true; $$ = $1; } + | type_qualifier ; storage_class_specifier: @@ -213,24 +231,23 @@ storage_class_specifier: type_specifier: simple_type_specifier - | cvar_qualifier | TYPEOF_KEYW '(' parameter_declaration ')' | TYPEOF_PHRASE /* References to s/u/e's defined elsewhere. Rearrange things so that it is easier to expand the definition fully later. */ - | STRUCT_KEYW IDENT - { remove_node($1); (*$2)->tag = SYM_STRUCT; $$ = $2; } - | UNION_KEYW IDENT - { remove_node($1); (*$2)->tag = SYM_UNION; $$ = $2; } + | STRUCT_KEYW attribute_opt IDENT + { remove_node($1); (*$3)->tag = SYM_STRUCT; $$ = $3; } + | UNION_KEYW attribute_opt IDENT + { remove_node($1); (*$3)->tag = SYM_UNION; $$ = $3; } | ENUM_KEYW IDENT { remove_node($1); (*$2)->tag = SYM_ENUM; $$ = $2; } /* Full definitions of an s/u/e. Record it. */ - | STRUCT_KEYW IDENT class_body - { record_compound($1, $2, $3, SYM_STRUCT); $$ = $3; } - | UNION_KEYW IDENT class_body - { record_compound($1, $2, $3, SYM_UNION); $$ = $3; } + | STRUCT_KEYW attribute_opt IDENT class_body + { record_compound($1, $3, $4, SYM_STRUCT); $$ = $4; } + | UNION_KEYW attribute_opt IDENT class_body + { record_compound($1, $3, $4, SYM_UNION); $$ = $4; } | ENUM_KEYW IDENT enum_body { record_compound($1, $2, $3, SYM_ENUM); $$ = $3; } /* @@ -239,8 +256,8 @@ type_specifier: | ENUM_KEYW enum_body { add_symbol(NULL, SYM_ENUM, NULL, 0); $$ = $2; } /* Anonymous s/u definitions. Nothing needs doing. */ - | STRUCT_KEYW class_body { $$ = $2; } - | UNION_KEYW class_body { $$ = $2; } + | STRUCT_KEYW attribute_opt class_body { $$ = $3; } + | UNION_KEYW attribute_opt class_body { $$ = $3; } ; simple_type_specifier: @@ -260,22 +277,25 @@ simple_type_specifier: ; ptr_operator: - '*' cvar_qualifier_seq_opt + '*' type_qualifier_seq_opt { $$ = $2 ? $2 : $1; } ; -cvar_qualifier_seq_opt: +type_qualifier_seq_opt: /* empty */ { $$ = NULL; } - | cvar_qualifier_seq + | type_qualifier_seq ; -cvar_qualifier_seq: - cvar_qualifier - | cvar_qualifier_seq cvar_qualifier { $$ = $2; } +type_qualifier_seq: + type_qualifier + | ATTRIBUTE_PHRASE + | type_qualifier_seq type_qualifier { $$ = $2; } + | type_qualifier_seq ATTRIBUTE_PHRASE { $$ = $2; } ; -cvar_qualifier: - CONST_KEYW | VOLATILE_KEYW | ATTRIBUTE_PHRASE +type_qualifier: + X86_SEG_KEYW + | CONST_KEYW | VOLATILE_KEYW | RESTRICT_KEYW { /* restrict has no effect in prototypes so ignore it */ remove_node($1); @@ -297,15 +317,7 @@ direct_declarator: current_name = (*$1)->string; $$ = $1; } - } - | TYPE - { if (current_name != NULL) { - error_with_pos("unexpected second declaration name"); - YYERROR; - } else { - current_name = (*$1)->string; - $$ = $1; - } + dont_want_type_specifier = false; } | direct_declarator '(' parameter_declaration_clause ')' { $$ = $4; } @@ -325,16 +337,19 @@ nested_declarator: ; direct_nested_declarator: - IDENT - | TYPE - | direct_nested_declarator '(' parameter_declaration_clause ')' + direct_nested_declarator1 + | direct_nested_declarator1 '(' parameter_declaration_clause ')' { $$ = $4; } - | direct_nested_declarator '(' error ')' + ; + +direct_nested_declarator1: + IDENT { $$ = $1; dont_want_type_specifier = false; } + | direct_nested_declarator1 '(' error ')' { $$ = $4; } - | direct_nested_declarator BRACKET_PHRASE + | direct_nested_declarator1 BRACKET_PHRASE { $$ = $2; } - | '(' nested_declarator ')' - { $$ = $3; } + | '(' attribute_opt nested_declarator ')' + { $$ = $4; } | '(' error ')' { $$ = $3; } ; @@ -352,45 +367,57 @@ parameter_declaration_list_opt: parameter_declaration_list: parameter_declaration + { $$ = $1; dont_want_type_specifier = false; } | parameter_declaration_list ',' parameter_declaration - { $$ = $3; } + { $$ = $3; dont_want_type_specifier = false; } ; parameter_declaration: - decl_specifier_seq m_abstract_declarator + decl_specifier_seq abstract_declarator_opt { $$ = $2 ? $2 : $1; } ; -m_abstract_declarator: - ptr_operator m_abstract_declarator +abstract_declarator_opt: + /* empty */ { $$ = NULL; } + | abstract_declarator + ; + +abstract_declarator: + ptr_operator + | ptr_operator abstract_declarator { $$ = $2 ? $2 : $1; } - | direct_m_abstract_declarator + | direct_abstract_declarator attribute_opt + { $$ = $2; dont_want_type_specifier = false; } ; -direct_m_abstract_declarator: - /* empty */ { $$ = NULL; } - | IDENT +direct_abstract_declarator: + direct_abstract_declarator1 + | direct_abstract_declarator1 open_paren parameter_declaration_clause ')' + { $$ = $4; } + | open_paren parameter_declaration_clause ')' + { $$ = $3; } + ; + +direct_abstract_declarator1: + IDENT { /* For version 2 checksums, we don't want to remember private parameter names. */ remove_node($1); $$ = $1; } - /* This wasn't really a typedef name but an identifier that - shadows one. */ - | TYPE - { remove_node($1); - $$ = $1; - } - | direct_m_abstract_declarator '(' parameter_declaration_clause ')' - { $$ = $4; } - | direct_m_abstract_declarator '(' error ')' + | direct_abstract_declarator1 open_paren error ')' { $$ = $4; } - | direct_m_abstract_declarator BRACKET_PHRASE + | direct_abstract_declarator1 BRACKET_PHRASE { $$ = $2; } - | '(' m_abstract_declarator ')' - { $$ = $3; } - | '(' error ')' + | open_paren attribute_opt abstract_declarator ')' + { $$ = $4; } + | open_paren error ')' { $$ = $3; } + | BRACKET_PHRASE + ; + +open_paren: + '(' { $$ = $1; dont_want_type_specifier = false; } ; function_definition: @@ -430,9 +457,9 @@ member_specification: member_declaration: decl_specifier_seq_opt member_declarator_list_opt ';' - { $$ = $3; } + { $$ = $3; dont_want_type_specifier = false; } | error ';' - { $$ = $2; } + { $$ = $2; dont_want_type_specifier = false; } ; member_declarator_list_opt: @@ -442,7 +469,9 @@ member_declarator_list_opt: member_declarator_list: member_declarator - | member_declarator_list ',' member_declarator { $$ = $3; } + { $$ = $1; dont_want_type_specifier = true; } + | member_declarator_list ',' member_declarator + { $$ = $3; dont_want_type_specifier = true; } ; member_declarator: @@ -457,7 +486,7 @@ member_bitfield_declarator: attribute_opt: /* empty */ { $$ = NULL; } - | attribute_opt ATTRIBUTE_PHRASE + | attribute_opt ATTRIBUTE_PHRASE { $$ = $2; } ; enum_body: @@ -472,12 +501,12 @@ enumerator_list: enumerator: IDENT { - const char *name = strdup((*$1)->string); + const char *name = (*$1)->string; add_symbol(name, SYM_ENUM_CONST, NULL, 0); } | IDENT '=' EXPRESSION_PHRASE { - const char *name = strdup((*$1)->string); + const char *name = (*$1)->string; struct string_list *expr = copy_list_range(*$3, *$2); add_symbol(name, SYM_ENUM_CONST, expr, 0); } diff --git a/scripts/get_abi.pl b/scripts/get_abi.pl deleted file mode 100755 index de1c0354b50c..000000000000 --- a/scripts/get_abi.pl +++ /dev/null @@ -1,1103 +0,0 @@ -#!/usr/bin/env perl -# SPDX-License-Identifier: GPL-2.0 - -BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Termcap'; } - -use strict; -use warnings; -use utf8; -use Pod::Usage qw(pod2usage); -use Getopt::Long; -use File::Find; -use IO::Handle; -use Fcntl ':mode'; -use Cwd 'abs_path'; -use Data::Dumper; - -my $help = 0; -my $hint = 0; -my $man = 0; -my $debug = 0; -my $enable_lineno = 0; -my $show_warnings = 1; -my $prefix="Documentation/ABI"; -my $sysfs_prefix="/sys"; -my $search_string; - -# Debug options -my $dbg_what_parsing = 1; -my $dbg_what_open = 2; -my $dbg_dump_abi_structs = 4; -my $dbg_undefined = 8; - -$Data::Dumper::Indent = 1; -$Data::Dumper::Terse = 1; - -# -# If true, assumes that the description is formatted with ReST -# -my $description_is_rst = 1; - -GetOptions( - "debug=i" => \$debug, - "enable-lineno" => \$enable_lineno, - "rst-source!" => \$description_is_rst, - "dir=s" => \$prefix, - 'help|?' => \$help, - "show-hints" => \$hint, - "search-string=s" => \$search_string, - man => \$man -) or pod2usage(2); - -pod2usage(1) if $help; -pod2usage(-exitstatus => 0, -noperldoc, -verbose => 2) if $man; - -pod2usage(2) if (scalar @ARGV < 1 || @ARGV > 2); - -my ($cmd, $arg) = @ARGV; - -pod2usage(2) if ($cmd ne "search" && $cmd ne "rest" && $cmd ne "validate" && $cmd ne "undefined"); -pod2usage(2) if ($cmd eq "search" && !$arg); - -require Data::Dumper if ($debug & $dbg_dump_abi_structs); - -my %data; -my %symbols; - -# -# Displays an error message, printing file name and line -# -sub parse_error($$$$) { - my ($file, $ln, $msg, $data) = @_; - - return if (!$show_warnings); - - $data =~ s/\s+$/\n/; - - print STDERR "Warning: file $file#$ln:\n\t$msg"; - - if ($data ne "") { - print STDERR ". Line\n\t\t$data"; - } else { - print STDERR "\n"; - } -} - -# -# Parse an ABI file, storing its contents at %data -# -sub parse_abi { - my $file = $File::Find::name; - - my $mode = (stat($file))[2]; - return if ($mode & S_IFDIR); - return if ($file =~ m,/README,); - return if ($file =~ m,/\.,); - return if ($file =~ m,\.(rej|org|orig|bak)$,); - - my $name = $file; - $name =~ s,.*/,,; - - my $fn = $file; - $fn =~ s,.*Documentation/ABI/,,; - - my $nametag = "File $fn"; - $data{$nametag}->{what} = "File $name"; - $data{$nametag}->{type} = "File"; - $data{$nametag}->{file} = $name; - $data{$nametag}->{filepath} = $file; - $data{$nametag}->{is_file} = 1; - $data{$nametag}->{line_no} = 1; - - my $type = $file; - $type =~ s,.*/(.*)/.*,$1,; - - my $what; - my $new_what; - my $tag = ""; - my $ln; - my $xrefs; - my $space; - my @labels; - my $label = ""; - - print STDERR "Opening $file\n" if ($debug & $dbg_what_open); - open IN, $file; - while(<IN>) { - $ln++; - if (m/^(\S+)(:\s*)(.*)/i) { - my $new_tag = lc($1); - my $sep = $2; - my $content = $3; - - if (!($new_tag =~ m/(what|where|date|kernelversion|contact|description|users)/)) { - if ($tag eq "description") { - # New "tag" is actually part of - # description. Don't consider it a tag - $new_tag = ""; - } elsif ($tag ne "") { - parse_error($file, $ln, "tag '$tag' is invalid", $_); - } - } - - # Invalid, but it is a common mistake - if ($new_tag eq "where") { - parse_error($file, $ln, "tag 'Where' is invalid. Should be 'What:' instead", ""); - $new_tag = "what"; - } - - if ($new_tag =~ m/what/) { - $space = ""; - $content =~ s/[,.;]$//; - - push @{$symbols{$content}->{file}}, " $file:" . ($ln - 1); - - if ($tag =~ m/what/) { - $what .= "\xac" . $content; - } else { - if ($what) { - parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description}); - - foreach my $w(split /\xac/, $what) { - $symbols{$w}->{xref} = $what; - }; - } - - $what = $content; - $label = $content; - $new_what = 1; - } - push @labels, [($content, $label)]; - $tag = $new_tag; - - push @{$data{$nametag}->{symbols}}, $content if ($data{$nametag}->{what}); - next; - } - - if ($tag ne "" && $new_tag) { - $tag = $new_tag; - - if ($new_what) { - @{$data{$what}->{label_list}} = @labels if ($data{$nametag}->{what}); - @labels = (); - $label = ""; - $new_what = 0; - - $data{$what}->{type} = $type; - if (!defined($data{$what}->{file})) { - $data{$what}->{file} = $name; - $data{$what}->{filepath} = $file; - } else { - $data{$what}->{description} .= "\n\n" if (defined($data{$what}->{description})); - if ($name ne $data{$what}->{file}) { - $data{$what}->{file} .= " " . $name; - $data{$what}->{filepath} .= " " . $file; - } - } - print STDERR "\twhat: $what\n" if ($debug & $dbg_what_parsing); - $data{$what}->{line_no} = $ln; - } else { - $data{$what}->{line_no} = $ln if (!defined($data{$what}->{line_no})); - } - - if (!$what) { - parse_error($file, $ln, "'What:' should come first:", $_); - next; - } - if ($new_tag eq "description") { - $sep =~ s,:, ,; - $content = ' ' x length($new_tag) . $sep . $content; - while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} - if ($content =~ m/^(\s*)(\S.*)$/) { - # Preserve initial spaces for the first line - $space = $1; - $content = "$2\n"; - $data{$what}->{$tag} .= $content; - } else { - undef($space); - } - - } else { - $data{$what}->{$tag} = $content; - } - next; - } - } - - # Store any contents before tags at the database - if (!$tag && $data{$nametag}->{what}) { - $data{$nametag}->{description} .= $_; - next; - } - - if ($tag eq "description") { - my $content = $_; - while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} - if (m/^\s*\n/) { - $data{$what}->{$tag} .= "\n"; - next; - } - - if (!defined($space)) { - # Preserve initial spaces for the first line - if ($content =~ m/^(\s*)(\S.*)$/) { - $space = $1; - $content = "$2\n"; - } - } else { - $space = "" if (!($content =~ s/^($space)//)); - } - $data{$what}->{$tag} .= $content; - - next; - } - if (m/^\s*(.*)/) { - $data{$what}->{$tag} .= "\n$1"; - $data{$what}->{$tag} =~ s/\n+$//; - next; - } - - # Everything else is error - parse_error($file, $ln, "Unexpected content", $_); - } - $data{$nametag}->{description} =~ s/^\n+// if ($data{$nametag}->{description}); - if ($what) { - parse_error($file, $ln, "What '$what' doesn't have a description", "") if (!$data{$what}->{description}); - - foreach my $w(split /\xac/,$what) { - $symbols{$w}->{xref} = $what; - }; - } - close IN; -} - -sub create_labels { - my %labels; - - foreach my $what (keys %data) { - next if ($data{$what}->{file} eq "File"); - - foreach my $p (@{$data{$what}->{label_list}}) { - my ($content, $label) = @{$p}; - $label = "abi_" . $label . " "; - $label =~ tr/A-Z/a-z/; - - # Convert special chars to "_" - $label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g; - $label =~ s,_+,_,g; - $label =~ s,_$,,; - - # Avoid duplicated labels - while (defined($labels{$label})) { - my @chars = ("A".."Z", "a".."z"); - $label .= $chars[rand @chars]; - } - $labels{$label} = 1; - - $data{$what}->{label} = $label; - - # only one label is enough - last; - } - } -} - -# -# Outputs the book on ReST format -# - -# \b doesn't work well with paths. So, we need to define something else: -# Boundaries are punct characters, spaces and end-of-line -my $start = qr {(^|\s|\() }x; -my $bondary = qr { ([,.:;\)\s]|\z) }x; -my $xref_match = qr { $start(\/(sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)$bondary }x; -my $symbols = qr { ([\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff]) }x; - -sub output_rest { - create_labels(); - - my $part = ""; - - foreach my $what (sort { - ($data{$a}->{type} eq "File") cmp ($data{$b}->{type} eq "File") || - $a cmp $b - } keys %data) { - my $type = $data{$what}->{type}; - - my @file = split / /, $data{$what}->{file}; - my @filepath = split / /, $data{$what}->{filepath}; - - if ($enable_lineno) { - printf ".. LINENO %s%s#%s\n\n", - $prefix, $file[0], - $data{$what}->{line_no}; - } - - my $w = $what; - - if ($type ne "File") { - my $cur_part = $what; - if ($what =~ '/') { - if ($what =~ m#^(\/?(?:[\w\-]+\/?){1,2})#) { - $cur_part = "Symbols under $1"; - $cur_part =~ s,/$,,; - } - } - - if ($cur_part ne "" && $part ne $cur_part) { - $part = $cur_part; - my $bar = $part; - $bar =~ s/./-/g; - print "$part\n$bar\n\n"; - } - - printf ".. _%s:\n\n", $data{$what}->{label}; - - my @names = split /\xac/,$w; - my $len = 0; - - foreach my $name (@names) { - $name =~ s/$symbols/\\$1/g; - $name = "**$name**"; - $len = length($name) if (length($name) > $len); - } - - print "+-" . "-" x $len . "-+\n"; - foreach my $name (@names) { - printf "| %s", $name . " " x ($len - length($name)) . " |\n"; - print "+-" . "-" x $len . "-+\n"; - } - - print "\n"; - } - - for (my $i = 0; $i < scalar(@filepath); $i++) { - my $path = $filepath[$i]; - my $f = $file[$i]; - - $path =~ s,.*/(.*/.*),$1,;; - $path =~ s,[/\-],_,g;; - my $fileref = "abi_file_".$path; - - if ($type eq "File") { - print ".. _$fileref:\n\n"; - } else { - print "Defined on file :ref:`$f <$fileref>`\n\n"; - } - } - - if ($type eq "File") { - my $bar = $w; - $bar =~ s/./-/g; - print "$w\n$bar\n\n"; - } - - my $desc = ""; - $desc = $data{$what}->{description} if (defined($data{$what}->{description})); - $desc =~ s/\s+$/\n/; - - if (!($desc =~ /^\s*$/)) { - if ($description_is_rst) { - # Remove title markups from the description - # Having titles inside ABI files will only work if extra - # care would be taken in order to strictly follow the same - # level order for each markup. - $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; - - # Enrich text by creating cross-references - - my $new_desc = ""; - my $init_indent = -1; - my $literal_indent = -1; - - open(my $fh, "+<", \$desc); - while (my $d = <$fh>) { - my $indent = $d =~ m/^(\s+)/; - my $spaces = length($indent); - $init_indent = $indent if ($init_indent < 0); - if ($literal_indent >= 0) { - if ($spaces > $literal_indent) { - $new_desc .= $d; - next; - } else { - $literal_indent = -1; - } - } else { - if ($d =~ /()::$/ && !($d =~ /^\s*\.\./)) { - $literal_indent = $spaces; - } - } - - $d =~ s,Documentation/(?!devicetree)(\S+)\.rst,:doc:`/$1`,g; - - my @matches = $d =~ m,Documentation/ABI/([\w\/\-]+),g; - foreach my $f (@matches) { - my $xref = $f; - my $path = $f; - $path =~ s,.*/(.*/.*),$1,;; - $path =~ s,[/\-],_,g;; - $xref .= " <abi_file_" . $path . ">"; - $d =~ s,\bDocumentation/ABI/$f\b,:ref:`$xref`,g; - } - - # Seek for cross reference symbols like /sys/... - @matches = $d =~ m/$xref_match/g; - - foreach my $s (@matches) { - next if (!($s =~ m,/,)); - if (defined($data{$s}) && defined($data{$s}->{label})) { - my $xref = $s; - - $xref =~ s/$symbols/\\$1/g; - $xref = ":ref:`$xref <" . $data{$s}->{label} . ">`"; - - $d =~ s,$start$s$bondary,$1$xref$2,g; - } - } - $new_desc .= $d; - } - close $fh; - - - print "$new_desc\n\n"; - } else { - $desc =~ s/^\s+//; - - # Remove title markups from the description, as they won't work - $desc =~ s/\n[\-\*\=\^\~]+\n/\n\n/g; - - if ($desc =~ m/\:\n/ || $desc =~ m/\n[\t ]+/ || $desc =~ m/[\x00-\x08\x0b-\x1f\x7b-\xff]/) { - # put everything inside a code block - $desc =~ s/\n/\n /g; - - print "::\n\n"; - print " $desc\n\n"; - } else { - # Escape any special chars from description - $desc =~s/([\x00-\x08\x0b-\x1f\x21-\x2a\x2d\x2f\x3c-\x40\x5c\x5e-\x60\x7b-\xff])/\\$1/g; - print "$desc\n\n"; - } - } - } else { - print "DESCRIPTION MISSING for $what\n\n" if (!$data{$what}->{is_file}); - } - - if ($data{$what}->{symbols}) { - printf "Has the following ABI:\n\n"; - - foreach my $content(@{$data{$what}->{symbols}}) { - my $label = $data{$symbols{$content}->{xref}}->{label}; - - # Escape special chars from content - $content =~s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g; - - print "- :ref:`$content <$label>`\n\n"; - } - } - - if (defined($data{$what}->{users})) { - my $users = $data{$what}->{users}; - - $users =~ s/\n/\n\t/g; - printf "Users:\n\t%s\n\n", $users if ($users ne ""); - } - - } -} - -# -# Searches for ABI symbols -# -sub search_symbols { - foreach my $what (sort keys %data) { - next if (!($what =~ m/($arg)/)); - - my $type = $data{$what}->{type}; - next if ($type eq "File"); - - my $file = $data{$what}->{filepath}; - - $what =~ s/\xac/, /g; - my $bar = $what; - $bar =~ s/./-/g; - - print "\n$what\n$bar\n\n"; - - my $kernelversion = $data{$what}->{kernelversion} if (defined($data{$what}->{kernelversion})); - my $contact = $data{$what}->{contact} if (defined($data{$what}->{contact})); - my $users = $data{$what}->{users} if (defined($data{$what}->{users})); - my $date = $data{$what}->{date} if (defined($data{$what}->{date})); - my $desc = $data{$what}->{description} if (defined($data{$what}->{description})); - - $kernelversion =~ s/^\s+// if ($kernelversion); - $contact =~ s/^\s+// if ($contact); - if ($users) { - $users =~ s/^\s+//; - $users =~ s/\n//g; - } - $date =~ s/^\s+// if ($date); - $desc =~ s/^\s+// if ($desc); - - printf "Kernel version:\t\t%s\n", $kernelversion if ($kernelversion); - printf "Date:\t\t\t%s\n", $date if ($date); - printf "Contact:\t\t%s\n", $contact if ($contact); - printf "Users:\t\t\t%s\n", $users if ($users); - print "Defined on file(s):\t$file\n\n"; - print "Description:\n\n$desc"; - } -} - -# Exclude /sys/kernel/debug and /sys/kernel/tracing from the search path -sub dont_parse_special_attributes { - if (($File::Find::dir =~ m,^/sys/kernel,)) { - return grep {!/(debug|tracing)/ } @_; - } - - if (($File::Find::dir =~ m,^/sys/fs,)) { - return grep {!/(pstore|bpf|fuse)/ } @_; - } - - return @_ -} - -my %leaf; -my %aliases; -my @files; -my %root; - -sub graph_add_file { - my $file = shift; - my $type = shift; - - my $dir = $file; - $dir =~ s,^(.*/).*,$1,; - $file =~ s,.*/,,; - - my $name; - my $file_ref = \%root; - foreach my $edge(split "/", $dir) { - $name .= "$edge/"; - if (!defined ${$file_ref}{$edge}) { - ${$file_ref}{$edge} = { }; - } - $file_ref = \%{$$file_ref{$edge}}; - ${$file_ref}{"__name"} = [ $name ]; - } - $name .= "$file"; - ${$file_ref}{$file} = { - "__name" => [ $name ] - }; - - return \%{$$file_ref{$file}}; -} - -sub graph_add_link { - my $file = shift; - my $link = shift; - - # Traverse graph to find the reference - my $file_ref = \%root; - foreach my $edge(split "/", $file) { - $file_ref = \%{$$file_ref{$edge}} || die "Missing node!"; - } - - # do a BFS - - my @queue; - my %seen; - my $st; - - push @queue, $file_ref; - $seen{$start}++; - - while (@queue) { - my $v = shift @queue; - my @child = keys(%{$v}); - - foreach my $c(@child) { - next if $seen{$$v{$c}}; - next if ($c eq "__name"); - - if (!defined($$v{$c}{"__name"})) { - printf STDERR "Error: Couldn't find a non-empty name on a children of $file/.*: "; - print STDERR Dumper(%{$v}); - exit; - } - - # Add new name - my $name = @{$$v{$c}{"__name"}}[0]; - if ($name =~ s#^$file/#$link/#) { - push @{$$v{$c}{"__name"}}, $name; - } - # Add child to the queue and mark as seen - push @queue, $$v{$c}; - $seen{$c}++; - } - } -} - -my $escape_symbols = qr { ([\x01-\x08\x0e-\x1f\x21-\x29\x2b-\x2d\x3a-\x40\x7b-\xfe]) }x; -sub parse_existing_sysfs { - my $file = $File::Find::name; - - my $mode = (lstat($file))[2]; - my $abs_file = abs_path($file); - - my @tmp; - push @tmp, $file; - push @tmp, $abs_file if ($abs_file ne $file); - - foreach my $f(@tmp) { - # Ignore cgroup, as this is big and has zero docs under ABI - return if ($f =~ m#^/sys/fs/cgroup/#); - - # Ignore firmware as it is documented elsewhere - # Either ACPI or under Documentation/devicetree/bindings/ - return if ($f =~ m#^/sys/firmware/#); - - # Ignore some sysfs nodes that aren't actually part of ABI - return if ($f =~ m#/sections|notes/#); - - # Would need to check at - # Documentation/admin-guide/kernel-parameters.txt, but this - # is not easily parseable. - return if ($f =~ m#/parameters/#); - } - - if (S_ISLNK($mode)) { - $aliases{$file} = $abs_file; - return; - } - - return if (S_ISDIR($mode)); - - # Trivial: file is defined exactly the same way at ABI What: - return if (defined($data{$file})); - return if (defined($data{$abs_file})); - - push @files, graph_add_file($abs_file, "file"); -} - -sub get_leave($) -{ - my $what = shift; - my $leave; - - my $l = $what; - my $stop = 1; - - $leave = $l; - $leave =~ s,/$,,; - $leave =~ s,.*/,,; - $leave =~ s/[\(\)]//g; - - # $leave is used to improve search performance at - # check_undefined_symbols, as the algorithm there can seek - # for a small number of "what". It also allows giving a - # hint about a leave with the same name somewhere else. - # However, there are a few occurences where the leave is - # either a wildcard or a number. Just group such cases - # altogether. - if ($leave =~ m/\.\*/ || $leave eq "" || $leave =~ /\\d/) { - $leave = "others"; - } - - return $leave; -} - -my @not_found; - -sub check_file($$) -{ - my $file_ref = shift; - my $names_ref = shift; - my @names = @{$names_ref}; - my $file = $names[0]; - - my $found_string; - - my $leave = get_leave($file); - if (!defined($leaf{$leave})) { - $leave = "others"; - } - my @expr = @{$leaf{$leave}->{expr}}; - die ("\rmissing rules for $leave") if (!defined($leaf{$leave})); - - my $path = $file; - $path =~ s,(.*/).*,$1,; - - if ($search_string) { - return if (!($file =~ m#$search_string#)); - $found_string = 1; - } - - for (my $i = 0; $i < @names; $i++) { - if ($found_string && $hint) { - if (!$i) { - print STDERR "--> $names[$i]\n"; - } else { - print STDERR " $names[$i]\n"; - } - } - foreach my $re (@expr) { - print STDERR "$names[$i] =~ /^$re\$/\n" if ($debug && $dbg_undefined); - if ($names[$i] =~ $re) { - return; - } - } - } - - if ($leave ne "others") { - my @expr = @{$leaf{"others"}->{expr}}; - for (my $i = 0; $i < @names; $i++) { - foreach my $re (@expr) { - print STDERR "$names[$i] =~ /^$re\$/\n" if ($debug && $dbg_undefined); - if ($names[$i] =~ $re) { - return; - } - } - } - } - - push @not_found, $file if (!$search_string || $found_string); - - if ($hint && (!$search_string || $found_string)) { - my $what = $leaf{$leave}->{what}; - $what =~ s/\xac/\n\t/g; - if ($leave ne "others") { - print STDERR "\r more likely regexes:\n\t$what\n"; - } else { - print STDERR "\r tested regexes:\n\t$what\n"; - } - } -} - -sub check_undefined_symbols { - my $num_files = scalar @files; - my $next_i = 0; - my $start_time = times; - - @files = sort @files; - - my $last_time = $start_time; - - # When either debug or hint is enabled, there's no sense showing - # progress, as the progress will be overriden. - if ($hint || ($debug && $dbg_undefined)) { - $next_i = $num_files; - } - - my $is_console; - $is_console = 1 if (-t STDERR); - - for (my $i = 0; $i < $num_files; $i++) { - my $file_ref = $files[$i]; - my @names = @{$$file_ref{"__name"}}; - - check_file($file_ref, \@names); - - my $cur_time = times; - - if ($i == $next_i || $cur_time > $last_time + 1) { - my $percent = $i * 100 / $num_files; - - my $tm = $cur_time - $start_time; - my $time = sprintf "%d:%02d", int($tm), 60 * ($tm - int($tm)); - - printf STDERR "\33[2K\r", if ($is_console); - printf STDERR "%s: processing sysfs files... %i%%: $names[0]", $time, $percent; - printf STDERR "\n", if (!$is_console); - STDERR->flush(); - - $next_i = int (($percent + 1) * $num_files / 100); - $last_time = $cur_time; - } - } - - my $cur_time = times; - my $tm = $cur_time - $start_time; - my $time = sprintf "%d:%02d", int($tm), 60 * ($tm - int($tm)); - - printf STDERR "\33[2K\r", if ($is_console); - printf STDERR "%s: processing sysfs files... done\n", $time; - - foreach my $file (@not_found) { - print "$file not found.\n"; - } -} - -sub undefined_symbols { - print STDERR "Reading $sysfs_prefix directory contents..."; - find({ - wanted =>\&parse_existing_sysfs, - preprocess =>\&dont_parse_special_attributes, - no_chdir => 1 - }, $sysfs_prefix); - print STDERR "done.\n"; - - $leaf{"others"}->{what} = ""; - - print STDERR "Converting ABI What fields into regexes..."; - foreach my $w (sort keys %data) { - foreach my $what (split /\xac/,$w) { - next if (!($what =~ m/^$sysfs_prefix/)); - - # Convert what into regular expressions - - # Escape dot characters - $what =~ s/\./\xf6/g; - - # Temporarily change [0-9]+ type of patterns - $what =~ s/\[0\-9\]\+/\xff/g; - - # Temporarily change [\d+-\d+] type of patterns - $what =~ s/\[0\-\d+\]/\xff/g; - $what =~ s/\[(\d+)\]/\xf4$1\xf5/g; - - # Temporarily change [0-9] type of patterns - $what =~ s/\[(\d)\-(\d)\]/\xf4$1-$2\xf5/g; - - # Handle multiple option patterns - $what =~ s/[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]/($1|$2)/g; - - # Handle wildcards - $what =~ s,\*,.*,g; - $what =~ s,/\xf6..,/.*,g; - $what =~ s/\<[^\>]+\>/.*/g; - $what =~ s/\{[^\}]+\}/.*/g; - $what =~ s/\[[^\]]+\]/.*/g; - - $what =~ s/[XYZ]/.*/g; - - # Recover [0-9] type of patterns - $what =~ s/\xf4/[/g; - $what =~ s/\xf5/]/g; - - # Remove duplicated spaces - $what =~ s/\s+/ /g; - - # Special case: this ABI has a parenthesis on it - $what =~ s/sqrt\(x^2\+y^2\+z^2\)/sqrt\(x^2\+y^2\+z^2\)/; - - # Special case: drop comparition as in: - # What: foo = <something> - # (this happens on a few IIO definitions) - $what =~ s,\s*\=.*$,,; - - # Escape all other symbols - $what =~ s/$escape_symbols/\\$1/g; - $what =~ s/\\\\/\\/g; - $what =~ s/\\([\[\]\(\)\|])/$1/g; - $what =~ s/(\d+)\\(-\d+)/$1$2/g; - - $what =~ s/\xff/\\d+/g; - - # Special case: IIO ABI which a parenthesis. - $what =~ s/sqrt(.*)/sqrt\(.*\)/; - - # Simplify regexes with multiple .* - $what =~ s#(?:\.\*){2,}##g; -# $what =~ s#\.\*/\.\*#.*#g; - - # Recover dot characters - $what =~ s/\xf6/\./g; - - my $leave = get_leave($what); - - my $added = 0; - foreach my $l (split /\|/, $leave) { - if (defined($leaf{$l})) { - next if ($leaf{$l}->{what} =~ m/\b$what\b/); - $leaf{$l}->{what} .= "\xac" . $what; - $added = 1; - } else { - $leaf{$l}->{what} = $what; - $added = 1; - } - } - if ($search_string && $added) { - print STDERR "What: $what\n" if ($what =~ m#$search_string#); - } - - } - } - # Compile regexes - foreach my $l (sort keys %leaf) { - my @expr; - foreach my $w(sort split /\xac/, $leaf{$l}->{what}) { - push @expr, qr /^$w$/; - } - $leaf{$l}->{expr} = \@expr; - } - - # Take links into account - foreach my $link (sort keys %aliases) { - my $abs_file = $aliases{$link}; - graph_add_link($abs_file, $link); - } - print STDERR "done.\n"; - - check_undefined_symbols; -} - -# Ensure that the prefix will always end with a slash -# While this is not needed for find, it makes the patch nicer -# with --enable-lineno -$prefix =~ s,/?$,/,; - -if ($cmd eq "undefined" || $cmd eq "search") { - $show_warnings = 0; -} -# -# Parses all ABI files located at $prefix dir -# -find({wanted =>\&parse_abi, no_chdir => 1}, $prefix); - -print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug & $dbg_dump_abi_structs); - -# -# Handles the command -# -if ($cmd eq "undefined") { - undefined_symbols; -} elsif ($cmd eq "search") { - search_symbols; -} else { - if ($cmd eq "rest") { - output_rest; - } - - # Warn about duplicated ABI entries - foreach my $what(sort keys %symbols) { - my @files = @{$symbols{$what}->{file}}; - - next if (scalar(@files) == 1); - - printf STDERR "Warning: $what is defined %d times: @files\n", - scalar(@files); - } -} - -__END__ - -=head1 NAME - -get_abi.pl - parse the Linux ABI files and produce a ReST book. - -=head1 SYNOPSIS - -B<get_abi.pl> [--debug <level>] [--enable-lineno] [--man] [--help] - [--(no-)rst-source] [--dir=<dir>] [--show-hints] - [--search-string <regex>] - <COMMAND> [<ARGUMENT>] - -Where B<COMMAND> can be: - -=over 8 - -B<search> I<SEARCH_REGEX> - search for I<SEARCH_REGEX> inside ABI - -B<rest> - output the ABI in ReST markup language - -B<validate> - validate the ABI contents - -B<undefined> - existing symbols at the system that aren't - defined at Documentation/ABI - -=back - -=head1 OPTIONS - -=over 8 - -=item B<--dir> - -Changes the location of the ABI search. By default, it uses -the Documentation/ABI directory. - -=item B<--rst-source> and B<--no-rst-source> - -The input file may be using ReST syntax or not. Those two options allow -selecting between a rst-compliant source ABI (B<--rst-source>), or a -plain text that may be violating ReST spec, so it requres some escaping -logic (B<--no-rst-source>). - -=item B<--enable-lineno> - -Enable output of .. LINENO lines. - -=item B<--debug> I<debug level> - -Print debug information according with the level, which is given by the -following bitmask: - - - 1: Debug parsing What entries from ABI files; - - 2: Shows what files are opened from ABI files; - - 4: Dump the structs used to store the contents of the ABI files. - -=item B<--show-hints> - -Show hints about possible definitions for the missing ABI symbols. -Used only when B<undefined>. - -=item B<--search-string> I<regex string> - -Show only occurences that match a search string. -Used only when B<undefined>. - -=item B<--help> - -Prints a brief help message and exits. - -=item B<--man> - -Prints the manual page and exits. - -=back - -=head1 DESCRIPTION - -Parse the Linux ABI files from ABI DIR (usually located at Documentation/ABI), -allowing to search for ABI symbols or to produce a ReST book containing -the Linux ABI documentation. - -=head1 EXAMPLES - -Search for all stable symbols with the word "usb": - -=over 8 - -$ scripts/get_abi.pl search usb --dir Documentation/ABI/stable - -=back - -Search for all symbols that match the regex expression "usb.*cap": - -=over 8 - -$ scripts/get_abi.pl search usb.*cap - -=back - -Output all obsoleted symbols in ReST format - -=over 8 - -$ scripts/get_abi.pl rest --dir Documentation/ABI/obsolete - -=back - -=head1 BUGS - -Report bugs to Mauro Carvalho Chehab <mchehab+huawei@kernel.org> - -=head1 COPYRIGHT - -Copyright (c) 2016-2021 by Mauro Carvalho Chehab <mchehab+huawei@kernel.org>. - -License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. - -This is free software: you are free to change and redistribute it. -There is NO WARRANTY, to the extent permitted by law. - -=cut diff --git a/scripts/get_abi.py b/scripts/get_abi.py new file mode 100755 index 000000000000..7ce4748a46d2 --- /dev/null +++ b/scripts/get_abi.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +# pylint: disable=R0903 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# SPDX-License-Identifier: GPL-2.0 + +""" +Parse ABI documentation and produce results from it. +""" + +import argparse +import logging +import os +import sys + +# Import Python modules + +LIB_DIR = "lib/abi" +SRC_DIR = os.path.dirname(os.path.realpath(__file__)) + +sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) + +from abi_parser import AbiParser # pylint: disable=C0413 +from abi_regex import AbiRegex # pylint: disable=C0413 +from helpers import ABI_DIR, DEBUG_HELP # pylint: disable=C0413 +from system_symbols import SystemSymbols # pylint: disable=C0413 + +# Command line classes + + +REST_DESC = """ +Produce output in ReST format. + +The output is done on two sections: + +- Symbols: show all parsed symbols in alphabetic order; +- Files: cross reference the content of each file with the symbols on it. +""" + +class AbiRest: + """Initialize an argparse subparser for rest output""" + + def __init__(self, subparsers): + """Initialize argparse subparsers""" + + parser = subparsers.add_parser("rest", + formatter_class=argparse.RawTextHelpFormatter, + description=REST_DESC) + + parser.add_argument("--enable-lineno", action="store_true", + help="enable lineno") + parser.add_argument("--raw", action="store_true", + help="output text as contained in the ABI files. " + "It not used, output will contain dynamically" + " generated cross references when possible.") + parser.add_argument("--no-file", action="store_true", + help="Don't the files section") + parser.add_argument("--show-hints", help="Show-hints") + + parser.set_defaults(func=self.run) + + def run(self, args): + """Run subparser""" + + parser = AbiParser(args.dir, debug=args.debug) + parser.parse_abi() + parser.check_issues() + + for t in parser.doc(args.raw, not args.no_file): + if args.enable_lineno: + print (f".. LINENO {t[1]}#{t[2]}\n\n") + + print(t[0]) + +class AbiValidate: + """Initialize an argparse subparser for ABI validation""" + + def __init__(self, subparsers): + """Initialize argparse subparsers""" + + parser = subparsers.add_parser("validate", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="list events") + + parser.set_defaults(func=self.run) + + def run(self, args): + """Run subparser""" + + parser = AbiParser(args.dir, debug=args.debug) + parser.parse_abi() + parser.check_issues() + + +class AbiSearch: + """Initialize an argparse subparser for ABI search""" + + def __init__(self, subparsers): + """Initialize argparse subparsers""" + + parser = subparsers.add_parser("search", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Search ABI using a regular expression") + + parser.add_argument("expression", + help="Case-insensitive search pattern for the ABI symbol") + + parser.set_defaults(func=self.run) + + def run(self, args): + """Run subparser""" + + parser = AbiParser(args.dir, debug=args.debug) + parser.parse_abi() + parser.search_symbols(args.expression) + +UNDEFINED_DESC=""" +Check undefined ABIs on local machine. + +Read sysfs devnodes and check if the devnodes there are defined inside +ABI documentation. + +The search logic tries to minimize the number of regular expressions to +search per each symbol. + +By default, it runs on a single CPU, as Python support for CPU threads +is still experimental, and multi-process runs on Python is very slow. + +On experimental tests, if the number of ABI symbols to search per devnode +is contained on a limit of ~150 regular expressions, using a single CPU +is a lot faster than using multiple processes. However, if the number of +regular expressions to check is at the order of ~30000, using multiple +CPUs speeds up the check. +""" + +class AbiUndefined: + """ + Initialize an argparse subparser for logic to check undefined ABI at + the current machine's sysfs + """ + + def __init__(self, subparsers): + """Initialize argparse subparsers""" + + parser = subparsers.add_parser("undefined", + formatter_class=argparse.RawTextHelpFormatter, + description=UNDEFINED_DESC) + + parser.add_argument("-S", "--sysfs-dir", default="/sys", + help="directory where sysfs is mounted") + parser.add_argument("-s", "--search-string", + help="search string regular expression to limit symbol search") + parser.add_argument("-H", "--show-hints", action="store_true", + help="Hints about definitions for missing ABI symbols.") + parser.add_argument("-j", "--jobs", "--max-workers", type=int, default=1, + help="If bigger than one, enables multiprocessing.") + parser.add_argument("-c", "--max-chunk-size", type=int, default=50, + help="Maximum number of chunk size") + parser.add_argument("-f", "--found", action="store_true", + help="Also show found items. " + "Helpful to debug the parser."), + parser.add_argument("-d", "--dry-run", action="store_true", + help="Don't actually search for undefined. " + "Helpful to debug the parser."), + + parser.set_defaults(func=self.run) + + def run(self, args): + """Run subparser""" + + abi = AbiRegex(args.dir, debug=args.debug, + search_string=args.search_string) + + abi_symbols = SystemSymbols(abi=abi, hints=args.show_hints, + sysfs=args.sysfs_dir) + + abi_symbols.check_undefined_symbols(dry_run=args.dry_run, + found=args.found, + max_workers=args.jobs, + chunk_size=args.max_chunk_size) + + +def main(): + """Main program""" + + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument("-d", "--debug", type=int, default=0, help="debug level") + parser.add_argument("-D", "--dir", default=ABI_DIR, help=DEBUG_HELP) + + subparsers = parser.add_subparsers() + + AbiRest(subparsers) + AbiValidate(subparsers) + AbiSearch(subparsers) + AbiUndefined(subparsers) + + args = parser.parse_args() + + if args.debug: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") + + if "func" in args: + args.func(args) + else: + sys.exit(f"Please specify a valid command for {sys.argv[0]}") + + +# Call main method +if __name__ == "__main__": + main() diff --git a/scripts/get_feat.pl b/scripts/get_feat.pl index 5c5397eeb237..40fb28c8424e 100755 --- a/scripts/get_feat.pl +++ b/scripts/get_feat.pl @@ -512,13 +512,13 @@ print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug); # Handles the command # if ($cmd eq "current") { - $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/'); + $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/'); $arch =~s/\s+$//; } if ($cmd eq "ls" or $cmd eq "list") { if (!$arch) { - $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/'); + $arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/'); $arch =~s/\s+$//; } diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl index ee1aed7e090c..4414194bedcf 100755 --- a/scripts/get_maintainer.pl +++ b/scripts/get_maintainer.pl @@ -50,10 +50,12 @@ my $output_multiline = 1; my $output_separator = ", "; my $output_roles = 0; my $output_rolestats = 1; +my $output_substatus = undef; my $output_section_maxlen = 50; my $scm = 0; my $tree = 1; my $web = 0; +my $bug = 0; my $subsystem = 0; my $status = 0; my $letters = ""; @@ -268,9 +270,11 @@ if (!GetOptions( 'separator=s' => \$output_separator, 'subsystem!' => \$subsystem, 'status!' => \$status, + 'substatus!' => \$output_substatus, 'scm!' => \$scm, 'tree!' => \$tree, 'web!' => \$web, + 'bug!' => \$bug, 'letters=s' => \$letters, 'pattern-depth=i' => \$pattern_depth, 'k|keywords!' => \$keywords, @@ -312,6 +316,10 @@ $output_multiline = 0 if ($output_separator ne ", "); $output_rolestats = 1 if ($interactive); $output_roles = 1 if ($output_rolestats); +if (!defined $output_substatus) { + $output_substatus = $email && $output_roles && -t STDOUT; +} + if ($sections || $letters ne "") { $sections = 1; $email = 0; @@ -320,13 +328,14 @@ if ($sections || $letters ne "") { $status = 0; $subsystem = 0; $web = 0; + $bug = 0; $keywords = 0; $keywords_in_file = 0; $interactive = 0; } else { - my $selections = $email + $scm + $status + $subsystem + $web; + my $selections = $email + $scm + $status + $subsystem + $web + $bug; if ($selections == 0) { - die "$P: Missing required option: email, scm, status, subsystem or web\n"; + die "$P: Missing required option: email, scm, status, subsystem, web or bug\n"; } } @@ -631,8 +640,10 @@ my %hash_list_to; my @list_to = (); my @scm = (); my @web = (); +my @bug = (); my @subsystem = (); my @status = (); +my @substatus = (); my %deduplicate_name_hash = (); my %deduplicate_address_hash = (); @@ -647,6 +658,11 @@ if ($scm) { output(@scm); } +if ($output_substatus) { + @substatus = uniq(@substatus); + output(@substatus); +} + if ($status) { @status = uniq(@status); output(@status); @@ -662,6 +678,11 @@ if ($web) { output(@web); } +if ($bug) { + @bug = uniq(@bug); + output(@bug); +} + exit($exit); sub self_test { @@ -847,8 +868,10 @@ sub get_maintainers { @list_to = (); @scm = (); @web = (); + @bug = (); @subsystem = (); @status = (); + @substatus = (); %deduplicate_name_hash = (); %deduplicate_address_hash = (); if ($email_git_all_signature_types) { @@ -1061,14 +1084,16 @@ MAINTAINER field selection options: --moderated => include moderated lists(s) if any (default: true) --s => include subscriber only list(s) if any (default: false) --remove-duplicates => minimize duplicate email names/addresses - --roles => show roles (status:subsystem, git-signer, list, etc...) + --roles => show roles (role:subsystem, git-signer, list, etc...) --rolestats => show roles and statistics (commits/total_commits, %) + --substatus => show subsystem status if not Maintained (default: match --roles when output is tty)" --file-emails => add email addresses found in -f file (default: 0 (off)) --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on)) --scm => print SCM tree(s) if any --status => print status if any --subsystem => print subsystem name if any --web => print website(s) if any + --bug => print bug reporting info if any Output type options: --separator [, ] => separator for multiple entries on 1 line @@ -1273,8 +1298,9 @@ sub get_maintainer_role { my $start = find_starting_index($index); my $end = find_ending_index($index); - my $role = "unknown"; + my $role = "maintainer"; my $subsystem = get_subsystem_name($index); + my $status = "unknown"; for ($i = $start + 1; $i < $end; $i++) { my $tv = $typevalue[$i]; @@ -1282,23 +1308,13 @@ sub get_maintainer_role { my $ptype = $1; my $pvalue = $2; if ($ptype eq "S") { - $role = $pvalue; + $status = $pvalue; } } } - $role = lc($role); - if ($role eq "supported") { - $role = "supporter"; - } elsif ($role eq "maintained") { - $role = "maintainer"; - } elsif ($role eq "odd fixes") { - $role = "odd fixer"; - } elsif ($role eq "orphan") { - $role = "orphan minder"; - } elsif ($role eq "obsolete") { - $role = "obsolete minder"; - } elsif ($role eq "buried alive in reporters") { + $status = lc($status); + if ($status eq "buried alive in reporters") { $role = "chief penguin"; } @@ -1324,7 +1340,9 @@ sub add_categories { my $start = find_starting_index($index); my $end = find_ending_index($index); - push(@subsystem, $typevalue[$start]); + my $subsystem = $typevalue[$start]; + push(@subsystem, $subsystem); + my $status = "Unknown"; for ($i = $start + 1; $i < $end; $i++) { my $tv = $typevalue[$i]; @@ -1375,18 +1393,25 @@ sub add_categories { } } elsif ($ptype eq "R") { if ($email_reviewer) { - my $subsystem = get_subsystem_name($i); - push_email_addresses($pvalue, "reviewer:$subsystem" . $suffix); + my $subs = get_subsystem_name($i); + push_email_addresses($pvalue, "reviewer:$subs" . $suffix); } } elsif ($ptype eq "T") { push(@scm, $pvalue . $suffix); } elsif ($ptype eq "W") { push(@web, $pvalue . $suffix); + } elsif ($ptype eq "B") { + push(@bug, $pvalue . $suffix); } elsif ($ptype eq "S") { push(@status, $pvalue . $suffix); + $status = $pvalue; } } } + + if ($subsystem ne "THE REST" and $status ne "Maintained") { + push(@substatus, $subsystem . " status: " . $status . $suffix) + } } sub email_inuse { @@ -1890,6 +1915,7 @@ EOT $done = 1; $output_rolestats = 0; $output_roles = 0; + $output_substatus = 0; last; } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) { $selected{$nr - 1} = !$selected{$nr - 1}; diff --git a/scripts/gfp-translate b/scripts/gfp-translate index 6c9aed17cf56..8385ae0d5af9 100755 --- a/scripts/gfp-translate +++ b/scripts/gfp-translate @@ -62,25 +62,57 @@ if [ "$GFPMASK" = "none" ]; then fi # Extract GFP flags from the kernel source -TMPFILE=`mktemp -t gfptranslate-XXXXXX` || exit 1 -grep -q ___GFP $SOURCE/include/linux/gfp_types.h -if [ $? -eq 0 ]; then - grep "^#define ___GFP" $SOURCE/include/linux/gfp_types.h | sed -e 's/u$//' | grep -v GFP_BITS > $TMPFILE -else - grep "^#define __GFP" $SOURCE/include/linux/gfp_types.h | sed -e 's/(__force gfp_t)//' | sed -e 's/u)/)/' | grep -v GFP_BITS | sed -e 's/)\//) \//' > $TMPFILE -fi +TMPFILE=`mktemp -t gfptranslate-XXXXXX.c` || exit 1 -# Parse the flags -IFS=" -" echo Source: $SOURCE echo Parsing: $GFPMASK -for LINE in `cat $TMPFILE`; do - MASK=`echo $LINE | awk '{print $3}'` - if [ $(($GFPMASK&$MASK)) -ne 0 ]; then - echo $LINE - fi -done -rm -f $TMPFILE +( + cat <<EOF +#include <stdint.h> +#include <stdio.h> + +// Try to fool compiler.h into not including extra stuff +#define __ASSEMBLY__ 1 + +#include <generated/autoconf.h> +#include <linux/gfp_types.h> + +static const char *masks[] = { +EOF + + sed -nEe 's/^[[:space:]]+(___GFP_.*)_BIT,.*$/\1/p' $SOURCE/include/linux/gfp_types.h | + while read b; do + cat <<EOF +#if defined($b) && ($b > 0) + [${b}_BIT] = "$b", +#endif +EOF + done + + cat <<EOF +}; + +int main(int argc, char *argv[]) +{ + unsigned long long mask = $GFPMASK; + + for (int i = 0; i < sizeof(mask) * 8; i++) { + unsigned long long bit = 1ULL << i; + if (mask & bit) + printf("\t%-25s0x%llx\n", + (i < ___GFP_LAST_BIT && masks[i]) ? + masks[i] : "*** INVALID ***", + bit); + } + + return 0; +} +EOF +) > $TMPFILE + +${CC:-gcc} -Wall -o ${TMPFILE}.bin -I $SOURCE/include $TMPFILE && ${TMPFILE}.bin + +rm -f $TMPFILE ${TMPFILE}.bin + exit 0 diff --git a/scripts/git-resolve.sh b/scripts/git-resolve.sh new file mode 100755 index 000000000000..e9b5940c0f28 --- /dev/null +++ b/scripts/git-resolve.sh @@ -0,0 +1,201 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# (c) 2025, Sasha Levin <sashal@kernel.org> + +usage() { + echo "Usage: $(basename "$0") [--selftest] [--force] <commit-id> [commit-subject]" + echo "Resolves a short git commit ID to its full SHA-1 hash, particularly useful for fixing references in commit messages." + echo "" + echo "Arguments:" + echo " --selftest Run self-tests" + echo " --force Try to find commit by subject if ID lookup fails" + echo " commit-id Short git commit ID to resolve" + echo " commit-subject Optional commit subject to help resolve between multiple matches" + exit 1 +} + +# Convert subject with ellipsis to grep pattern +convert_to_grep_pattern() { + local subject="$1" + # First escape ALL regex special characters + local escaped_subject + escaped_subject=$(printf '%s\n' "$subject" | sed 's/[[\.*^$()+?{}|]/\\&/g') + # Also escape colons, parentheses, and hyphens as they are special in our context + escaped_subject=$(echo "$escaped_subject" | sed 's/[:-]/\\&/g') + # Then convert escaped ... sequence to .*? + escaped_subject=$(echo "$escaped_subject" | sed 's/\\\.\\\.\\\./.*?/g') + echo "^${escaped_subject}$" +} + +git_resolve_commit() { + local force=0 + if [ "$1" = "--force" ]; then + force=1 + shift + fi + + # Split input into commit ID and subject + local input="$*" + local commit_id="${input%% *}" + local subject="" + + # Extract subject if present (everything after the first space) + if [[ "$input" == *" "* ]]; then + subject="${input#* }" + # Strip the ("...") quotes if present + subject="${subject#*(\"}" + subject="${subject%\")*}" + fi + + # Get all possible matching commit IDs + local matches + readarray -t matches < <(git rev-parse --disambiguate="$commit_id" 2>/dev/null) + + # Return immediately if we have exactly one match + if [ ${#matches[@]} -eq 1 ]; then + echo "${matches[0]}" + return 0 + fi + + # If no matches and not in force mode, return failure + if [ ${#matches[@]} -eq 0 ] && [ $force -eq 0 ]; then + return 1 + fi + + # If we have a subject, try to find a match with that subject + if [ -n "$subject" ]; then + # Convert subject with possible ellipsis to grep pattern + local grep_pattern + grep_pattern=$(convert_to_grep_pattern "$subject") + + # In force mode with no ID matches, use git log --grep directly + if [ ${#matches[@]} -eq 0 ] && [ $force -eq 1 ]; then + # Use git log to search, but filter to ensure subject matches exactly + local match + match=$(git log --format="%H %s" --grep="$grep_pattern" --perl-regexp -10 | \ + while read -r hash subject; do + if echo "$subject" | grep -qP "$grep_pattern"; then + echo "$hash" + break + fi + done) + if [ -n "$match" ]; then + echo "$match" + return 0 + fi + else + # Normal subject matching for existing matches + for match in "${matches[@]}"; do + if git log -1 --format="%s" "$match" | grep -qP "$grep_pattern"; then + echo "$match" + return 0 + fi + done + fi + fi + + # No match found + return 1 +} + +run_selftest() { + local test_cases=( + '00250b5 ("MAINTAINERS: add new Rockchip SoC list")' + '0037727 ("KVM: selftests: Convert xen_shinfo_test away from VCPU_ID")' + 'ffef737 ("net/tls: Fix skb memory leak when running kTLS traffic")' + 'd3d7 ("cifs: Improve guard for excluding $LXDEV xattr")' + 'dbef ("Rename .data.once to .data..once to fix resetting WARN*_ONCE")' + '12345678' # Non-existent commit + '12345 ("I'\''m a dummy commit")' # Valid prefix but wrong subject + '--force 99999999 ("net/tls: Fix skb memory leak when running kTLS traffic")' # Force mode with non-existent ID but valid subject + '83be ("firmware: ... auto-update: fix poll_complete() ... errors")' # Wildcard test + '--force 999999999999 ("firmware: ... auto-update: fix poll_complete() ... errors")' # Force mode wildcard test + ) + + local expected=( + "00250b529313d6262bb0ebbd6bdf0a88c809f6f0" + "0037727b3989c3fe1929c89a9a1dfe289ad86f58" + "ffef737fd0372ca462b5be3e7a592a8929a82752" + "d3d797e326533794c3f707ce1761da7a8895458c" + "dbefa1f31a91670c9e7dac9b559625336206466f" + "" # Expect empty output for non-existent commit + "" # Expect empty output for wrong subject + "ffef737fd0372ca462b5be3e7a592a8929a82752" # Should find commit by subject in force mode + "83beece5aff75879bdfc6df8ba84ea88fd93050e" # Wildcard test + "83beece5aff75879bdfc6df8ba84ea88fd93050e" # Force mode wildcard test + ) + + local expected_exit_codes=( + 0 + 0 + 0 + 0 + 0 + 1 # Expect failure for non-existent commit + 1 # Expect failure for wrong subject + 0 # Should succeed in force mode + 0 # Should succeed with wildcard + 0 # Should succeed with force mode and wildcard + ) + + local failed=0 + + echo "Running self-tests..." + for i in "${!test_cases[@]}"; do + # Capture both output and exit code + local result + result=$(git_resolve_commit ${test_cases[$i]}) # Removed quotes to allow --force to be parsed + local exit_code=$? + + # Check both output and exit code + if [ "$result" != "${expected[$i]}" ] || [ $exit_code != ${expected_exit_codes[$i]} ]; then + echo "Test case $((i+1)) FAILED" + echo "Input: ${test_cases[$i]}" + echo "Expected output: '${expected[$i]}'" + echo "Got output: '$result'" + echo "Expected exit code: ${expected_exit_codes[$i]}" + echo "Got exit code: $exit_code" + failed=1 + else + echo "Test case $((i+1)) PASSED" + fi + done + + if [ $failed -eq 0 ]; then + echo "All tests passed!" + exit 0 + else + echo "Some tests failed!" + exit 1 + fi +} + +# Check for selftest +if [ "$1" = "--selftest" ]; then + run_selftest + exit $? +fi + +# Handle --force flag +force="" +if [ "$1" = "--force" ]; then + force="--force" + shift +fi + +# Verify arguments +if [ $# -eq 0 ]; then + usage +fi + +# Skip validation in force mode +if [ -z "$force" ]; then + # Validate that the first argument matches at least one git commit + if [ "$(git rev-parse --disambiguate="$1" 2>/dev/null | wc -l)" -eq 0 ]; then + echo "Error: '$1' does not match any git commit" + exit 1 + fi +fi + +git_resolve_commit $force "$@" +exit $? diff --git a/scripts/head-object-list.txt b/scripts/head-object-list.txt index 890f69005bab..7274dfc65af6 100644 --- a/scripts/head-object-list.txt +++ b/scripts/head-object-list.txt @@ -23,11 +23,8 @@ arch/m68k/coldfire/head.o arch/m68k/kernel/head.o arch/m68k/kernel/sun3-head.o arch/microblaze/kernel/head.o -arch/mips/kernel/head.o arch/nios2/kernel/head.o -arch/openrisc/kernel/head.o arch/parisc/kernel/head.o -arch/powerpc/kernel/head_40x.o arch/powerpc/kernel/head_44x.o arch/powerpc/kernel/head_64.o arch/powerpc/kernel/head_8xx.o diff --git a/scripts/headers_install.sh b/scripts/headers_install.sh index f7d9b114de8f..6bbccb43f7e7 100755 --- a/scripts/headers_install.sh +++ b/scripts/headers_install.sh @@ -74,7 +74,6 @@ arch/arc/include/uapi/asm/page.h:CONFIG_ARC_PAGE_SIZE_16K arch/arc/include/uapi/asm/page.h:CONFIG_ARC_PAGE_SIZE_4K arch/arc/include/uapi/asm/swab.h:CONFIG_ARC_HAS_SWAPE arch/arm/include/uapi/asm/ptrace.h:CONFIG_CPU_ENDIAN_BE8 -arch/m68k/include/uapi/asm/ptrace.h:CONFIG_COLDFIRE arch/nios2/include/uapi/asm/swab.h:CONFIG_NIOS2_CI_SWAB_NO arch/nios2/include/uapi/asm/swab.h:CONFIG_NIOS2_CI_SWAB_SUPPORT arch/x86/include/uapi/asm/auxvec.h:CONFIG_IA32_EMULATION diff --git a/scripts/kconfig/array_size.h b/scripts/include/array_size.h index 26ba78d867d1..26ba78d867d1 100644 --- a/scripts/kconfig/array_size.h +++ b/scripts/include/array_size.h diff --git a/scripts/include/hash.h b/scripts/include/hash.h new file mode 100644 index 000000000000..efa904368a62 --- /dev/null +++ b/scripts/include/hash.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef HASH_H +#define HASH_H + +static inline unsigned int hash_str(const char *s) +{ + /* fnv32 hash */ + unsigned int hash = 2166136261U; + + for (; *s; s++) + hash = (hash ^ *s) * 0x01000193; + return hash; +} + +/* simplified version of functions from include/linux/hash.h */ +#define GOLDEN_RATIO_32 0x61C88647 + +static inline unsigned int hash_32(unsigned int val) +{ + return 0x61C88647 * val; +} + +static inline unsigned int hash_ptr(const void *ptr) +{ + return hash_32((unsigned int)(unsigned long)ptr); +} + +#endif /* HASH_H */ diff --git a/scripts/include/hashtable.h b/scripts/include/hashtable.h new file mode 100644 index 000000000000..45abcb12bfce --- /dev/null +++ b/scripts/include/hashtable.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef HASHTABLE_H +#define HASHTABLE_H + +#include "array_size.h" +#include "list.h" + +#define HASH_SIZE(name) (ARRAY_SIZE(name)) + +#define HASHTABLE_DECLARE(name, size) struct hlist_head name[size] + +#define HASHTABLE_DEFINE(name, size) \ + HASHTABLE_DECLARE(name, size) = \ + { [0 ... ((size) - 1)] = HLIST_HEAD_INIT } + +#define hash_head(table, key) (&(table)[(key) % HASH_SIZE(table)]) + +static inline void __hash_init(struct hlist_head *ht, unsigned int sz) +{ + unsigned int i; + + for (i = 0; i < sz; i++) + INIT_HLIST_HEAD(&ht[i]); +} + +/** + * hash_init - initialize a hash table + * @table: hashtable to be initialized + * + * This has to be a macro since HASH_SIZE() will not work on pointers since + * it calculates the size during preprocessing. + */ +#define hash_init(table) __hash_init(table, HASH_SIZE(table)) + +/** + * hash_add - add an object to a hashtable + * @table: hashtable to add to + * @node: the &struct hlist_node of the object to be added + * @key: the key of the object to be added + */ +#define hash_add(table, node, key) \ + hlist_add_head(node, hash_head(table, key)) + +/** + * hash_del - remove an object from a hashtable + * @node: &struct hlist_node of the object to remove + */ +static inline void hash_del(struct hlist_node *node) +{ + hlist_del_init(node); +} + +/** + * hash_for_each - iterate over a hashtable + * @table: hashtable to iterate + * @obj: the type * to use as a loop cursor for each entry + * @member: the name of the hlist_node within the struct + */ +#define hash_for_each(table, obj, member) \ + for (int _bkt = 0; _bkt < HASH_SIZE(table); _bkt++) \ + hlist_for_each_entry(obj, &table[_bkt], member) + +/** + * hash_for_each_safe - iterate over a hashtable safe against removal of + * hash entry + * @table: hashtable to iterate + * @obj: the type * to use as a loop cursor for each entry + * @tmp: a &struct hlist_node used for temporary storage + * @member: the name of the hlist_node within the struct + */ +#define hash_for_each_safe(table, obj, tmp, member) \ + for (int _bkt = 0; _bkt < HASH_SIZE(table); _bkt++) \ + hlist_for_each_entry_safe(obj, tmp, &table[_bkt], member) + +/** + * hash_for_each_possible - iterate over all possible objects hashing to the + * same bucket + * @table: hashtable to iterate + * @obj: the type * to use as a loop cursor for each entry + * @member: the name of the hlist_node within the struct + * @key: the key of the objects to iterate over + */ +#define hash_for_each_possible(table, obj, member, key) \ + hlist_for_each_entry(obj, hash_head(table, key), member) + +/** + * hash_for_each_possible_safe - iterate over all possible objects hashing to the + * same bucket safe against removals + * @table: hashtable to iterate + * @obj: the type * to use as a loop cursor for each entry + * @tmp: a &struct hlist_node used for temporary storage + * @member: the name of the hlist_node within the struct + * @key: the key of the objects to iterate over + */ +#define hash_for_each_possible_safe(table, obj, tmp, member, key) \ + hlist_for_each_entry_safe(obj, tmp, hash_head(table, key), member) + +#endif /* HASHTABLE_H */ diff --git a/scripts/kconfig/list.h b/scripts/include/list.h index 882859ddf9f4..8bdcaadca709 100644 --- a/scripts/kconfig/list.h +++ b/scripts/include/list.h @@ -128,6 +128,79 @@ static inline void list_del(struct list_head *entry) } /** + * list_replace - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace(struct list_head *old, + struct list_head *new) +{ + new->next = old->next; + new->next->prev = new; + new->prev = old->prev; + new->prev->next = new; +} + +/** + * list_replace_init - replace old entry by new one and initialize the old one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace_init(struct list_head *old, + struct list_head *new) +{ + list_replace(old, new); + INIT_LIST_HEAD(old); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del_entry(list); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del_entry(list); + list_add_tail(list, head); +} + +/** + * list_is_first -- tests whether @list is the first entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_first(const struct list_head *list, const struct list_head *head) +{ + return list->prev == head; +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, const struct list_head *head) +{ + return list->next == head; +} + +/** * list_is_head - tests whether @list is the list @head * @list: the entry to test * @head: the head of the list @@ -167,6 +240,17 @@ static inline int list_empty(const struct list_head *head) list_entry((ptr)->next, type, member) /** + * list_last_entry - get the last element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_head within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_last_entry(ptr, type, member) \ + list_entry((ptr)->prev, type, member) + +/** * list_next_entry - get the next element in list * @pos: the type * to cursor * @member: the name of the list_head within the struct. @@ -175,6 +259,14 @@ static inline int list_empty(const struct list_head *head) list_entry((pos)->member.next, typeof(*(pos)), member) /** + * list_prev_entry - get the prev element in list + * @pos: the type * to cursor + * @member: the name of the list_head within the struct. + */ +#define list_prev_entry(pos, member) \ + list_entry((pos)->member.prev, typeof(*(pos)), member) + +/** * list_entry_is_head - test if the entry points to the head of the list * @pos: the type * to cursor * @head: the head for your list. @@ -195,6 +287,17 @@ static inline int list_empty(const struct list_head *head) pos = list_next_entry(pos, member)) /** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_head within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_last_entry(head, typeof(*pos), member); \ + !list_entry_is_head(pos, head, member); \ + pos = list_prev_entry(pos, member)) + +/** * list_for_each_entry_safe - iterate over list of given type. Safe against removal of list entry * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage @@ -215,6 +318,63 @@ static inline int list_empty(const struct list_head *head) */ #define HLIST_HEAD_INIT { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +static inline void INIT_HLIST_NODE(struct hlist_node *h) +{ + h->next = NULL; + h->pprev = NULL; +} + +/** + * hlist_unhashed - Has node been removed from list and reinitialized? + * @h: Node to be checked + * + * Not that not all removal functions will leave a node in unhashed + * state. For example, hlist_nulls_del_init_rcu() does leave the + * node in unhashed state, but hlist_nulls_del() does not. + */ +static inline int hlist_unhashed(const struct hlist_node *h) +{ + return !h->pprev; +} + +static inline void __hlist_del(struct hlist_node *n) +{ + struct hlist_node *next = n->next; + struct hlist_node **pprev = n->pprev; + + *pprev = next; + if (next) + next->pprev = pprev; +} + +/** + * hlist_del - Delete the specified hlist_node from its list + * @n: Node to delete. + * + * Note that this function leaves the node in hashed state. Use + * hlist_del_init() or similar instead to unhash @n. + */ +static inline void hlist_del(struct hlist_node *n) +{ + __hlist_del(n); + n->next = LIST_POISON1; + n->pprev = LIST_POISON2; +} + +/** + * hlist_del_init - Delete the specified hlist_node from its list and initialize + * @n: Node to delete. + * + * Note that this function leaves the node in unhashed state. + */ +static inline void hlist_del_init(struct hlist_node *n) +{ + if (!hlist_unhashed(n)) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} /** * hlist_add_head - add a new entry at the beginning of the hlist @@ -253,4 +413,16 @@ static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) pos; \ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member)) +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: a &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(pos, n, head, member) \ + for (pos = hlist_entry_safe((head)->first, typeof(*pos), member);\ + pos && ({ n = pos->member.next; 1; }); \ + pos = hlist_entry_safe(n, typeof(*pos), member)) + #endif /* LIST_H */ diff --git a/scripts/kconfig/list_types.h b/scripts/include/list_types.h index d935b7c5aa81..d935b7c5aa81 100644 --- a/scripts/kconfig/list_types.h +++ b/scripts/include/list_types.h diff --git a/scripts/include/xalloc.h b/scripts/include/xalloc.h new file mode 100644 index 000000000000..cdadb07d0592 --- /dev/null +++ b/scripts/include/xalloc.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef XALLOC_H +#define XALLOC_H + +#include <stdlib.h> +#include <string.h> + +static inline void *xmalloc(size_t size) +{ + void *p = malloc(size); + + if (!p) + exit(1); + return p; +} + +static inline void *xcalloc(size_t nmemb, size_t size) +{ + void *p = calloc(nmemb, size); + + if (!p) + exit(1); + return p; +} + +static inline void *xrealloc(void *p, size_t size) +{ + p = realloc(p, size); + if (!p) + exit(1); + return p; +} + +static inline char *xstrdup(const char *s) +{ + char *p = strdup(s); + + if (!p) + exit(1); + return p; +} + +static inline char *xstrndup(const char *s, size_t n) +{ + char *p = strndup(s, n); + + if (!p) + exit(1); + return p; +} + +#endif /* XALLOC_H */ diff --git a/scripts/install.sh b/scripts/install.sh index 9bb0fb44f04a..05d62ac513ee 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -20,6 +20,10 @@ do fi done +if [ -n "${INSTALL_PATH}" ] && ! [ -e "${INSTALL_PATH}" ]; then + mkdir -p "${INSTALL_PATH}" +fi + # User/arch may have a custom install script for file in "${HOME}/bin/${INSTALLKERNEL}" \ "/sbin/${INSTALLKERNEL}" \ diff --git a/scripts/integer-wrap-ignore.scl b/scripts/integer-wrap-ignore.scl new file mode 100644 index 000000000000..431c3053a4a2 --- /dev/null +++ b/scripts/integer-wrap-ignore.scl @@ -0,0 +1,3 @@ +[{unsigned-integer-overflow,signed-integer-overflow,implicit-signed-integer-truncation,implicit-unsigned-integer-truncation}] +type:* +type:size_t=sanitize diff --git a/scripts/ipe/Makefile b/scripts/ipe/Makefile new file mode 100644 index 000000000000..e87553fbb8d6 --- /dev/null +++ b/scripts/ipe/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +subdir-y := polgen diff --git a/scripts/selinux/genheaders/.gitignore b/scripts/ipe/polgen/.gitignore index 5fcadd307908..b6f05cf3dc0e 100644 --- a/scripts/selinux/genheaders/.gitignore +++ b/scripts/ipe/polgen/.gitignore @@ -1,2 +1,2 @@ # SPDX-License-Identifier: GPL-2.0-only -genheaders +polgen diff --git a/scripts/ipe/polgen/Makefile b/scripts/ipe/polgen/Makefile new file mode 100644 index 000000000000..c20456a2f2e9 --- /dev/null +++ b/scripts/ipe/polgen/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +hostprogs-always-y := polgen +HOST_EXTRACFLAGS += \ + -I$(srctree)/include \ + -I$(srctree)/include/uapi \ diff --git a/scripts/ipe/polgen/polgen.c b/scripts/ipe/polgen/polgen.c new file mode 100644 index 000000000000..01134cf895d0 --- /dev/null +++ b/scripts/ipe/polgen/polgen.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. + */ + +#include <stdlib.h> +#include <stddef.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> + +static void usage(const char *const name) +{ + printf("Usage: %s OutputFile (PolicyFile)\n", name); + exit(EINVAL); +} + +static int policy_to_buffer(const char *pathname, char **buffer, size_t *size) +{ + size_t fsize; + size_t read; + char *lbuf; + int rc = 0; + FILE *fd; + + fd = fopen(pathname, "r"); + if (!fd) { + rc = errno; + goto out; + } + + fseek(fd, 0, SEEK_END); + fsize = ftell(fd); + rewind(fd); + + lbuf = malloc(fsize); + if (!lbuf) { + rc = ENOMEM; + goto out_close; + } + + read = fread((void *)lbuf, sizeof(*lbuf), fsize, fd); + if (read != fsize) { + rc = -1; + goto out_free; + } + + *buffer = lbuf; + *size = fsize; + fclose(fd); + + return rc; + +out_free: + free(lbuf); +out_close: + fclose(fd); +out: + return rc; +} + +static int write_boot_policy(const char *pathname, const char *buf, size_t size) +{ + FILE *fd; + size_t i; + + fd = fopen(pathname, "w"); + if (!fd) + return errno; + + fprintf(fd, "/* This file is automatically generated."); + fprintf(fd, " Do not edit. */\n"); + fprintf(fd, "#include <linux/stddef.h>\n"); + fprintf(fd, "\nextern const char *const ipe_boot_policy;\n\n"); + fprintf(fd, "const char *const ipe_boot_policy =\n"); + + if (!buf || size == 0) { + fprintf(fd, "\tNULL;\n"); + fclose(fd); + return 0; + } + + fprintf(fd, "\t\""); + + for (i = 0; i < size; ++i) { + switch (buf[i]) { + case '"': + fprintf(fd, "\\\""); + break; + case '\'': + fprintf(fd, "'"); + break; + case '\n': + fprintf(fd, "\\n\"\n\t\""); + break; + case '\\': + fprintf(fd, "\\\\"); + break; + case '\t': + fprintf(fd, "\\t"); + break; + case '\?': + fprintf(fd, "\\?"); + break; + default: + fprintf(fd, "%c", buf[i]); + } + } + fprintf(fd, "\";\n"); + fclose(fd); + + return 0; +} + +int main(int argc, const char *const argv[]) +{ + char *policy = NULL; + size_t len = 0; + int rc = 0; + + if (argc < 2) + usage(argv[0]); + + if (argc > 2) { + rc = policy_to_buffer(argv[2], &policy, &len); + if (rc != 0) + goto cleanup; + } + + rc = write_boot_policy(argv[1], policy, len); +cleanup: + if (policy) + free(policy); + if (rc != 0) + perror("An error occurred during policy conversion: "); + return rc; +} diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index 653b92f6d4c8..4b0234e4b12f 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -5,8 +5,7 @@ * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * - * Usage: kallsyms [--all-symbols] [--absolute-percpu] - * [--base-relative] [--lto-clang] in.map > out.S + * Usage: kallsyms [--all-symbols] in.map > out.S * * Table compression uses all the unused char codes on the symbols and * maps these to the most used substrings (tokens). For instance, it might @@ -28,6 +27,8 @@ #include <ctype.h> #include <limits.h> +#include <xalloc.h> + #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) #define KSYM_NAME_LEN 512 @@ -36,8 +37,6 @@ struct sym_entry { unsigned long long addr; unsigned int len; unsigned int seq; - unsigned int start_pos; - unsigned int percpu_absolute; unsigned char sym[]; }; @@ -55,16 +54,9 @@ static struct addr_range text_ranges[] = { #define text_range_text (&text_ranges[0]) #define text_range_inittext (&text_ranges[1]) -static struct addr_range percpu_range = { - "__per_cpu_start", "__per_cpu_end", -1ULL, 0 -}; - static struct sym_entry **table; static unsigned int table_size, table_cnt; static int all_symbols; -static int absolute_percpu; -static int base_relative; -static int lto_clang; static int token_profit[0x10000]; @@ -75,8 +67,7 @@ static unsigned char best_table_len[256]; static void usage(void) { - fprintf(stderr, "Usage: kallsyms [--all-symbols] [--absolute-percpu] " - "[--base-relative] [--lto-clang] in.map > out.S\n"); + fprintf(stderr, "Usage: kallsyms [--all-symbols] in.map > out.S\n"); exit(1); } @@ -167,23 +158,16 @@ static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len) return NULL; check_symbol_range(name, addr, text_ranges, ARRAY_SIZE(text_ranges)); - check_symbol_range(name, addr, &percpu_range, 1); /* include the type field in the symbol name, so that it gets * compressed together */ len++; - sym = malloc(sizeof(*sym) + len + 1); - if (!sym) { - fprintf(stderr, "kallsyms failure: " - "unable to allocate required amount of memory\n"); - exit(EXIT_FAILURE); - } + sym = xmalloc(sizeof(*sym) + len + 1); sym->addr = addr; sym->len = len; sym->sym[0] = type; strcpy(sym_name(sym), name); - sym->percpu_absolute = 0; return sym; } @@ -204,6 +188,11 @@ static int symbol_in_range(const struct sym_entry *s, return 0; } +static bool string_starts_with(const char *s, const char *prefix) +{ + return strncmp(s, prefix, strlen(prefix)) == 0; +} + static int symbol_valid(const struct sym_entry *s) { const char *name = sym_name(s); @@ -211,6 +200,14 @@ static int symbol_valid(const struct sym_entry *s) /* if --all-symbols is not specified, then symbols outside the text * and inittext sections are discarded */ if (!all_symbols) { + /* + * Symbols starting with __start and __stop are used to denote + * section boundaries, and should always be included: + */ + if (string_starts_with(name, "__start_") || + string_starts_with(name, "__stop_")) + return 1; + if (symbol_in_range(s, text_ranges, ARRAY_SIZE(text_ranges)) == 0) return 0; @@ -246,12 +243,6 @@ static void shrink_table(void) } } table_cnt = pos; - - /* When valid symbol is not registered, exit to error */ - if (!table_cnt) { - fprintf(stderr, "No valid symbol.\n"); - exit(1); - } } static void read_map(const char *in) @@ -272,16 +263,11 @@ static void read_map(const char *in) if (!sym) continue; - sym->start_pos = table_cnt; + sym->seq = table_cnt; if (table_cnt >= table_size) { table_size += 10000; - table = realloc(table, sizeof(*table) * table_size); - if (!table) { - fprintf(stderr, "out of memory\n"); - fclose(fp); - exit (1); - } + table = xrealloc(table, sizeof(*table) * table_size); } table[table_cnt++] = sym; @@ -298,15 +284,6 @@ static void output_label(const char *label) printf("%s:\n", label); } -/* Provide proper symbols relocatability by their '_text' relativeness. */ -static void output_address(unsigned long long addr) -{ - if (_text <= addr) - printf("\tPTR\t_text + %#llx\n", addr - _text); - else - printf("\tPTR\t_text - %#llx\n", _text - addr); -} - /* uncompress a compressed symbol. When this function is called, the best table * might still be compressed itself, so the function needs to be recursive */ static int expand_symbol(const unsigned char *data, int len, char *result) @@ -334,30 +311,6 @@ static int expand_symbol(const unsigned char *data, int len, char *result) return total; } -static int symbol_absolute(const struct sym_entry *s) -{ - return s->percpu_absolute; -} - -static void cleanup_symbol_name(char *s) -{ - char *p; - - /* - * ASCII[.] = 2e - * ASCII[0-9] = 30,39 - * ASCII[A-Z] = 41,5a - * ASCII[_] = 5f - * ASCII[a-z] = 61,7a - * - * As above, replacing the first '.' in ".llvm." with '\0' does not - * affect the main sorting, but it helps us with subsorting. - */ - p = strstr(s, ".llvm."); - if (p) - *p = '\0'; -} - static int compare_names(const void *a, const void *b) { int ret; @@ -387,7 +340,7 @@ static void write_src(void) { unsigned int i, k, off; unsigned int best_idx[256]; - unsigned int *markers; + unsigned int *markers, markers_cnt; char buf[KSYM_NAME_LEN]; printf("#include <asm/bitsperlong.h>\n"); @@ -407,12 +360,8 @@ static void write_src(void) /* table of offset markers, that give the offset in the compressed stream * every 256 symbols */ - markers = malloc(sizeof(unsigned int) * ((table_cnt + 255) / 256)); - if (!markers) { - fprintf(stderr, "kallsyms failure: " - "unable to allocate required memory\n"); - exit(EXIT_FAILURE); - } + markers_cnt = (table_cnt + 255) / 256; + markers = xmalloc(sizeof(*markers) * markers_cnt); output_label("kallsyms_names"); off = 0; @@ -449,21 +398,19 @@ static void write_src(void) } for (k = 0; k < table[i]->len; k++) printf(", 0x%02x", table[i]->sym[k]); - printf("\n"); - } - printf("\n"); - /* - * Now that we wrote out the compressed symbol names, restore the - * original names, which are needed in some of the later steps. - */ - for (i = 0; i < table_cnt; i++) { + /* + * Now that we wrote out the compressed symbol name, restore the + * original name and print it in the comment. + */ expand_symbol(table[i]->sym, table[i]->len, buf); strcpy((char *)table[i]->sym, buf); + printf("\t/* %s */\n", table[i]->sym); } + printf("\n"); output_label("kallsyms_markers"); - for (i = 0; i < ((table_cnt + 255) >> 8); i++) + for (i = 0; i < markers_cnt; i++) printf("\t.long\t%u\n", markers[i]); printf("\n"); @@ -484,66 +431,45 @@ static void write_src(void) printf("\t.short\t%d\n", best_idx[i]); printf("\n"); - if (!base_relative) - output_label("kallsyms_addresses"); - else - output_label("kallsyms_offsets"); + output_label("kallsyms_offsets"); for (i = 0; i < table_cnt; i++) { - if (base_relative) { - /* - * Use the offset relative to the lowest value - * encountered of all relative symbols, and emit - * non-relocatable fixed offsets that will be fixed - * up at runtime. - */ - - long long offset; - int overflow; - - if (!absolute_percpu) { - offset = table[i]->addr - relative_base; - overflow = (offset < 0 || offset > UINT_MAX); - } else if (symbol_absolute(table[i])) { - offset = table[i]->addr; - overflow = (offset < 0 || offset > INT_MAX); - } else { - offset = relative_base - table[i]->addr - 1; - overflow = (offset < INT_MIN || offset >= 0); - } - if (overflow) { - fprintf(stderr, "kallsyms failure: " - "%s symbol value %#llx out of range in relative mode\n", - symbol_absolute(table[i]) ? "absolute" : "relative", - table[i]->addr); - exit(EXIT_FAILURE); - } - printf("\t.long\t%#x /* %s */\n", (int)offset, table[i]->sym); - } else if (!symbol_absolute(table[i])) { - output_address(table[i]->addr); - } else { - printf("\tPTR\t%#llx\n", table[i]->addr); + /* + * Use the offset relative to the lowest value + * encountered of all relative symbols, and emit + * non-relocatable fixed offsets that will be fixed + * up at runtime. + */ + + long long offset; + + offset = table[i]->addr - relative_base; + if (offset < 0 || offset > UINT_MAX) { + fprintf(stderr, "kallsyms failure: " + "relative symbol value %#llx out of range\n", + table[i]->addr); + exit(EXIT_FAILURE); } + printf("\t.long\t%#x\t/* %s */\n", (int)offset, table[i]->sym); } printf("\n"); - if (base_relative) { - output_label("kallsyms_relative_base"); - output_address(relative_base); - printf("\n"); - } - - if (lto_clang) - for (i = 0; i < table_cnt; i++) - cleanup_symbol_name((char *)table[i]->sym); + output_label("kallsyms_relative_base"); + /* Provide proper symbols relocatability by their '_text' relativeness. */ + if (_text <= relative_base) + printf("\tPTR\t_text + %#llx\n", relative_base - _text); + else + printf("\tPTR\t_text - %#llx\n", _text - relative_base); + printf("\n"); sort_symbols_by_name(); output_label("kallsyms_seqs_of_names"); for (i = 0; i < table_cnt; i++) - printf("\t.byte 0x%02x, 0x%02x, 0x%02x\n", + printf("\t.byte 0x%02x, 0x%02x, 0x%02x\t/* %s */\n", (unsigned char)(table[i]->seq >> 16), (unsigned char)(table[i]->seq >> 8), - (unsigned char)(table[i]->seq >> 0)); + (unsigned char)(table[i]->seq >> 0), + table[i]->sym); printf("\n"); } @@ -767,7 +693,7 @@ static int compare_symbols(const void *a, const void *b) return wa - wb; /* sort by initial order, so that other symbols are left undisturbed */ - return sa->start_pos - sb->start_pos; + return sa->seq - sb->seq; } static void sort_symbols(void) @@ -775,36 +701,15 @@ static void sort_symbols(void) qsort(table, table_cnt, sizeof(table[0]), compare_symbols); } -static void make_percpus_absolute(void) -{ - unsigned int i; - - for (i = 0; i < table_cnt; i++) - if (symbol_in_range(table[i], &percpu_range, 1)) { - /* - * Keep the 'A' override for percpu symbols to - * ensure consistent behavior compared to older - * versions of this tool. - */ - table[i]->sym[0] = 'A'; - table[i]->percpu_absolute = 1; - } -} - /* find the minimum non-absolute symbol address */ static void record_relative_base(void) { - unsigned int i; - - for (i = 0; i < table_cnt; i++) - if (!symbol_absolute(table[i])) { - /* - * The table is sorted by address. - * Take the first non-absolute symbol value. - */ - relative_base = table[i]->addr; - return; - } + /* + * The table is sorted by address. + * Take the first symbol value. + */ + if (table_cnt) + relative_base = table[0]->addr; } int main(int argc, char **argv) @@ -812,9 +717,6 @@ int main(int argc, char **argv) while (1) { static const struct option long_options[] = { {"all-symbols", no_argument, &all_symbols, 1}, - {"absolute-percpu", no_argument, &absolute_percpu, 1}, - {"base-relative", no_argument, &base_relative, 1}, - {"lto-clang", no_argument, <o_clang, 1}, {}, }; @@ -831,11 +733,8 @@ int main(int argc, char **argv) read_map(argv[optind]); shrink_table(); - if (absolute_percpu) - make_percpus_absolute(); sort_symbols(); - if (base_relative) - record_relative_base(); + record_relative_base(); optimize_token_table(); write_src(); diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile index ea1bf3b3dbde..fb50bd4f4103 100644 --- a/scripts/kconfig/Makefile +++ b/scripts/kconfig/Makefile @@ -57,7 +57,7 @@ $(foreach c, config menuconfig nconfig gconfig xconfig, $(eval $(call config_rul PHONY += localmodconfig localyesconfig localyesconfig localmodconfig: $(obj)/conf - $(Q)$(PERL) $(srctree)/$(src)/streamline_config.pl --$@ $(srctree) $(Kconfig) > .tmp.config + $(Q)$(PERL) $(src)/streamline_config.pl --$@ $(srctree) $(Kconfig) > .tmp.config $(Q)if [ -f .config ]; then \ cmp -s .tmp.config .config || \ (mv -f .config .config.old.1; \ @@ -105,9 +105,11 @@ configfiles = $(wildcard $(srctree)/kernel/configs/$(1) $(srctree)/arch/$(SRCARC all-config-fragments = $(call configfiles,*.config) config-fragments = $(call configfiles,$@) +cmd_merge_fragments = $(srctree)/scripts/kconfig/merge_config.sh -m $(KCONFIG_CONFIG) $(config-fragments) + %.config: $(obj)/conf $(if $(config-fragments),, $(error $@ fragment does not exists on this architecture)) - $(Q)$(CONFIG_SHELL) $(srctree)/scripts/kconfig/merge_config.sh -m $(KCONFIG_CONFIG) $(config-fragments) + $(call cmd,merge_fragments) $(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig PHONY += tinyconfig @@ -118,7 +120,7 @@ tinyconfig: # CHECK: -o cache_dir=<path> working? PHONY += testconfig testconfig: $(obj)/conf - $(Q)$(PYTHON3) -B -m pytest $(srctree)/$(src)/tests \ + $(Q)$(PYTHON3) -B -m pytest $(src)/tests \ -o cache_dir=$(abspath $(obj)/tests/.cache) \ $(if $(findstring 1,$(KBUILD_VERBOSE)),--capture=no) clean-files += tests/.cache @@ -165,8 +167,8 @@ common-objs := confdata.o expr.o lexer.lex.o menu.o parser.tab.o \ preprocess.o symbol.o util.o $(obj)/lexer.lex.o: $(obj)/parser.tab.h -HOSTCFLAGS_lexer.lex.o := -I $(srctree)/$(src) -HOSTCFLAGS_parser.tab.o := -I $(srctree)/$(src) +HOSTCFLAGS_lexer.lex.o := -I $(src) +HOSTCFLAGS_parser.tab.o := -I $(src) # conf: Used for defconfig, oldconfig and related targets hostprogs += conf diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c index b5730061872b..8abe57041955 100644 --- a/scripts/kconfig/conf.c +++ b/scripts/kconfig/conf.c @@ -114,51 +114,54 @@ static void set_randconfig_seed(void) srand(seed); } -static bool randomize_choice_values(struct symbol *csym) +/** + * randomize_choice_values - randomize choice block + * + * @choice: menu entry for the choice + */ +static void randomize_choice_values(struct menu *choice) { - struct property *prop; - struct symbol *sym; - struct expr *e; - int cnt, def; + struct menu *menu; + int x; + int cnt = 0; /* - * If choice is mod then we may have more items selected - * and if no then no-one. - * In both cases stop. + * First, count the number of symbols to randomize. If sym_has_value() + * is true, it was specified by KCONFIG_ALLCONFIG. It needs to be + * respected. */ - if (csym->curr.tri != yes) - return false; + menu_for_each_sub_entry(menu, choice) { + struct symbol *sym = menu->sym; + + if (sym && !sym_has_value(sym)) + cnt++; + } - prop = sym_get_choice_prop(csym); + while (cnt > 0) { + x = rand() % cnt; - /* count entries in choice block */ - cnt = 0; - expr_list_for_each_sym(prop->expr, e, sym) - cnt++; + menu_for_each_sub_entry(menu, choice) { + struct symbol *sym = menu->sym; - /* - * find a random value and set it to yes, - * set the rest to no so we have only one set - */ - def = rand() % cnt; - - cnt = 0; - expr_list_for_each_sym(prop->expr, e, sym) { - if (def == cnt++) { - sym->def[S_DEF_USER].tri = yes; - csym->def[S_DEF_USER].val = sym; - } else { - sym->def[S_DEF_USER].tri = no; + if (sym && !sym_has_value(sym)) + x--; + + if (x < 0) { + sym->def[S_DEF_USER].tri = yes; + sym->flags |= SYMBOL_DEF_USER; + /* + * Move the selected item to the _tail_ because + * this needs to have a lower priority than the + * user input from KCONFIG_ALLCONFIG. + */ + list_move_tail(&sym->choice_link, + &choice->choice_members); + + break; + } } - sym->flags |= SYMBOL_DEF_USER; - /* clear VALID to get value calculated */ - sym->flags &= ~SYMBOL_VALID; + cnt--; } - csym->flags |= SYMBOL_DEF_USER; - /* clear VALID to get value calculated */ - csym->flags &= ~SYMBOL_VALID; - - return true; } enum conf_def_mode { @@ -169,9 +172,9 @@ enum conf_def_mode { def_random }; -static bool conf_set_all_new_symbols(enum conf_def_mode mode) +static void conf_set_all_new_symbols(enum conf_def_mode mode) { - struct symbol *sym, *csym; + struct menu *menu; int cnt; /* * can't go as the default in switch-case below, otherwise gcc whines @@ -180,7 +183,6 @@ static bool conf_set_all_new_symbols(enum conf_def_mode mode) int pby = 50; /* probability of bool = y */ int pty = 33; /* probability of tristate = y */ int ptm = 33; /* probability of tristate = m */ - bool has_changed = false; if (mode == def_random) { int n, p[3]; @@ -227,79 +229,51 @@ static bool conf_set_all_new_symbols(enum conf_def_mode mode) } } - for_all_symbols(sym) { - if (sym_has_value(sym) || sym->flags & SYMBOL_VALID) - continue; - switch (sym_get_type(sym)) { - case S_BOOLEAN: - case S_TRISTATE: - has_changed = true; - switch (mode) { - case def_yes: - sym->def[S_DEF_USER].tri = yes; - break; - case def_mod: - sym->def[S_DEF_USER].tri = mod; - break; - case def_no: - sym->def[S_DEF_USER].tri = no; - break; - case def_random: - sym->def[S_DEF_USER].tri = no; - cnt = rand() % 100; - if (sym->type == S_TRISTATE) { - if (cnt < pty) - sym->def[S_DEF_USER].tri = yes; - else if (cnt < pty + ptm) - sym->def[S_DEF_USER].tri = mod; - } else if (cnt < pby) - sym->def[S_DEF_USER].tri = yes; - break; - default: - continue; - } - if (!(sym_is_choice(sym) && mode == def_random)) - sym->flags |= SYMBOL_DEF_USER; - break; - default: - break; - } - - } + menu_for_each_entry(menu) { + struct symbol *sym = menu->sym; + tristate val; - sym_clear_all_valid(); + if (!sym || !menu->prompt || sym_has_value(sym) || + (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) || + sym_is_choice_value(sym)) + continue; - /* - * We have different type of choice blocks. - * If curr.tri equals to mod then we can select several - * choice symbols in one block. - * In this case we do nothing. - * If curr.tri equals yes then only one symbol can be - * selected in a choice block and we set it to yes, - * and the rest to no. - */ - if (mode != def_random) { - for_all_symbols(csym) { - if ((sym_is_choice(csym) && !sym_has_value(csym)) || - sym_is_choice_value(csym)) - csym->flags |= SYMBOL_NEED_SET_CHOICE_VALUES; + if (sym_is_choice(sym)) { + if (mode == def_random) + randomize_choice_values(menu); + continue; } - } - for_all_symbols(csym) { - if (sym_has_value(csym) || !sym_is_choice(csym)) + switch (mode) { + case def_yes: + val = yes; + break; + case def_mod: + val = mod; + break; + case def_no: + val = no; + break; + case def_random: + val = no; + cnt = rand() % 100; + if (sym->type == S_TRISTATE) { + if (cnt < pty) + val = yes; + else if (cnt < pty + ptm) + val = mod; + } else if (cnt < pby) { + val = yes; + } + break; + default: continue; - - sym_calc_value(csym); - if (mode == def_random) - has_changed |= randomize_choice_values(csym); - else { - set_all_choice_values(csym); - has_changed = true; } + sym->def[S_DEF_USER].tri = val; + sym->flags |= SYMBOL_DEF_USER; } - return has_changed; + sym_clear_all_valid(); } static void conf_rewrite_tristates(tristate old_val, tristate new_val) @@ -446,42 +420,17 @@ help: } } -static int conf_choice(struct menu *menu) +static void conf_choice(struct menu *menu) { - struct symbol *sym, *def_sym; + struct symbol *def_sym; struct menu *child; - bool is_new; - - sym = menu->sym; - is_new = !sym_has_value(sym); - if (sym_is_changeable(sym)) { - conf_sym(menu); - sym_calc_value(sym); - switch (sym_get_tristate_value(sym)) { - case no: - return 1; - case mod: - return 0; - case yes: - break; - } - } else { - switch (sym_get_tristate_value(sym)) { - case no: - return 1; - case mod: - printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); - return 0; - case yes: - break; - } - } + bool is_new = false; while (1) { int cnt, def; printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); - def_sym = sym_get_choice_value(sym); + def_sym = sym_calc_choice(menu); cnt = def = 0; line[0] = 0; for (child = menu->list; child; child = child->next) { @@ -497,11 +446,12 @@ static int conf_choice(struct menu *menu) printf("%*c", indent, '>'); } else printf("%*c", indent, ' '); - printf(" %d. %s", cnt, menu_get_prompt(child)); - if (child->sym->name) - printf(" (%s)", child->sym->name); - if (!sym_has_value(child->sym)) + printf(" %d. %s (%s)", cnt, menu_get_prompt(child), + child->sym->name); + if (!sym_has_value(child->sym)) { + is_new = true; printf(" (NEW)"); + } printf("\n"); } printf("%*schoice", indent - 1, ""); @@ -551,13 +501,8 @@ static int conf_choice(struct menu *menu) print_help(child); continue; } - sym_set_tristate_value(child->sym, yes); - for (child = child->list; child; child = child->next) { - indent += 2; - conf(child); - indent -= 2; - } - return 1; + choice_set_value(menu, child->sym); + return; } } @@ -603,9 +548,7 @@ static void conf(struct menu *menu) if (sym_is_choice(sym)) { conf_choice(menu); - if (sym->curr.tri != mod) - return; - goto conf_childs; + return; } switch (sym->type) { @@ -637,10 +580,7 @@ static void check_conf(struct menu *menu) return; sym = menu->sym; - if (sym && !sym_has_value(sym) && - (sym_is_changeable(sym) || - (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes))) { - + if (sym && !sym_has_value(sym) && sym_is_changeable(sym)) { switch (input_mode) { case listnewconfig: if (sym->name) @@ -688,7 +628,7 @@ static const struct option long_opts[] = { static void conf_usage(const char *progname) { - printf("Usage: %s [options] <kconfig-file>\n", progname); + printf("Usage: %s [options] kconfig_file\n", progname); printf("\n"); printf("Generic options:\n"); printf(" -h, --help Print this message and exit.\n"); @@ -713,6 +653,9 @@ static void conf_usage(const char *progname) printf(" --mod2yesconfig Change answers from mod to yes if possible\n"); printf(" --mod2noconfig Change answers from mod to no if possible\n"); printf(" (If none of the above is given, --oldaskconfig is the default)\n"); + printf("\n"); + printf("Arguments:\n"); + printf(" kconfig_file Top-level Kconfig file.\n"); } int main(int ac, char **av) @@ -856,8 +799,7 @@ int main(int ac, char **av) conf_set_all_new_symbols(def_default); break; case randconfig: - /* Really nothing to do in this loop */ - while (conf_set_all_new_symbols(def_random)) ; + conf_set_all_new_symbols(def_random); break; case defconfig: conf_set_all_new_symbols(def_default); diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c index 0e35c4819cf1..ac95661a1c9d 100644 --- a/scripts/kconfig/confdata.c +++ b/scripts/kconfig/confdata.c @@ -18,6 +18,7 @@ #include <time.h> #include <unistd.h> +#include <xalloc.h> #include "internal.h" #include "lkc.h" @@ -359,10 +360,12 @@ int conf_read_simple(const char *name, int def) *p = '\0'; - in = zconf_fopen(env); + name = env; + + in = zconf_fopen(name); if (in) { conf_message("using defaults found in %s", - env); + name); goto load; } @@ -382,10 +385,7 @@ load: def_flags = SYMBOL_DEF << def; for_all_symbols(sym) { - sym->flags |= SYMBOL_CHANGED; - sym->flags &= ~(def_flags|SYMBOL_VALID); - if (sym_is_choice(sym)) - sym->flags |= def_flags; + sym->flags &= ~def_flags; switch (sym->type) { case S_INT: case S_HEX: @@ -398,7 +398,15 @@ load: } } + if (def == S_DEF_USER) { + for_all_symbols(sym) + sym->flags &= ~SYMBOL_VALID; + expr_invalidate_all(); + } + while (getline_stripped(&line, &line_asize, in) != -1) { + struct menu *choice; + conf_lineno++; if (!line[0]) /* blank line */ @@ -460,25 +468,17 @@ load: if (conf_set_sym_val(sym, def, def_flags, val)) continue; - if (sym && sym_is_choice_value(sym)) { - struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); - switch (sym->def[def].tri) { - case no: - break; - case mod: - if (cs->def[def].tri == yes) { - conf_warning("%s creates inconsistent choice state", sym->name); - cs->flags &= ~def_flags; - } - break; - case yes: - if (cs->def[def].tri != no) - conf_warning("override: %s changes choice state", sym->name); - cs->def[def].val = sym; - break; - } - cs->def[def].tri = EXPR_OR(cs->def[def].tri, sym->def[def].tri); - } + if (def != S_DEF_USER) + continue; + + /* + * If this is a choice member, give it the highest priority. + * If conflicting CONFIG options are given from an input file, + * the last one wins. + */ + choice = sym_get_choice_menu(sym); + if (choice) + list_move(&sym->choice_link, &choice->choice_members); } free(line); fclose(in); @@ -489,7 +489,6 @@ load: int conf_read(const char *name) { struct symbol *sym; - int conf_unsaved = 0; conf_set_changed(false); @@ -502,7 +501,7 @@ int conf_read(const char *name) for_all_symbols(sym) { sym_calc_value(sym); - if (sym_is_choice(sym) || (sym->flags & SYMBOL_NO_WRITE)) + if (sym_is_choice(sym)) continue; if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) { /* check that calculated value agrees with saved value */ @@ -520,36 +519,11 @@ int conf_read(const char *name) } else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE)) /* no previous value and not saved */ continue; - conf_unsaved++; + conf_set_changed(true); /* maybe print value in verbose mode... */ } - for_all_symbols(sym) { - if (sym_has_value(sym) && !sym_is_choice_value(sym)) { - /* Reset values of generates values, so they'll appear - * as new, if they should become visible, but that - * doesn't quite work if the Kconfig and the saved - * configuration disagree. - */ - if (sym->visible == no && !conf_unsaved) - sym->flags &= ~SYMBOL_DEF_USER; - switch (sym->type) { - case S_STRING: - case S_INT: - case S_HEX: - /* Reset a string value if it's out of range */ - if (sym_string_within_range(sym, sym->def[S_DEF_USER].val)) - break; - sym->flags &= ~SYMBOL_VALID; - conf_unsaved++; - break; - default: - break; - } - } - } - - if (conf_warnings || conf_unsaved) + if (conf_warnings) conf_set_changed(true); return 0; @@ -793,59 +767,35 @@ int conf_write_defconfig(const char *filename) sym_clear_all_valid(); - /* Traverse all menus to find all relevant symbols */ - menu = rootmenu.list; + menu_for_each_entry(menu) { + struct menu *choice; - while (menu != NULL) - { sym = menu->sym; - if (sym && !sym_is_choice(sym)) { - sym_calc_value(sym); - if (!(sym->flags & SYMBOL_WRITE)) - goto next_menu; - sym->flags &= ~SYMBOL_WRITE; - /* If we cannot change the symbol - skip */ - if (!sym_is_changeable(sym)) - goto next_menu; - /* If symbol equals to default value - skip */ - if (strcmp(sym_get_string_value(sym), sym_get_string_default(sym)) == 0) - goto next_menu; - - /* - * If symbol is a choice value and equals to the - * default for a choice - skip. - * But only if value is bool and equal to "y" and - * choice is not "optional". - * (If choice is "optional" then all values can be "n") - */ - if (sym_is_choice_value(sym)) { - struct symbol *cs; - struct symbol *ds; - - cs = prop_get_symbol(sym_get_choice_prop(sym)); - ds = sym_choice_default(cs); - if (!sym_is_optional(cs) && sym == ds) { - if ((sym->type == S_BOOLEAN) && - sym_get_tristate_value(sym) == yes) - goto next_menu; - } - } - print_symbol_for_dotconfig(out, sym); - } -next_menu: - if (menu->list != NULL) { - menu = menu->list; - } - else if (menu->next != NULL) { - menu = menu->next; - } else { - while ((menu = menu->parent)) { - if (menu->next != NULL) { - menu = menu->next; - break; - } - } + + if (!sym || sym_is_choice(sym)) + continue; + + sym_calc_value(sym); + if (!(sym->flags & SYMBOL_WRITE)) + continue; + sym->flags &= ~SYMBOL_WRITE; + /* Skip unchangeable symbols */ + if (!sym_is_changeable(sym)) + continue; + /* Skip symbols that are equal to the default */ + if (!strcmp(sym_get_string_value(sym), sym_get_string_default(sym))) + continue; + + /* Skip choice values that are equal to the default */ + choice = sym_get_choice_menu(sym); + if (choice) { + struct symbol *ds; + + ds = sym_choice_default(choice); + if (sym == ds && sym_get_tristate_value(sym) == yes) + continue; } + print_symbol_for_dotconfig(out, sym); } fclose(out); return 0; @@ -906,7 +856,7 @@ int conf_write(const char *name) "# %s\n" "#\n", str); need_newline = false; - } else if (!(sym->flags & SYMBOL_CHOICE) && + } else if (!sym_is_choice(sym) && !(sym->flags & SYMBOL_WRITTEN)) { sym_calc_value(sym); if (!(sym->flags & SYMBOL_WRITE)) @@ -1024,11 +974,9 @@ static int conf_touch_deps(void) depfile_path[depfile_prefix_len] = 0; conf_read_simple(name, S_DEF_AUTO); - sym_calc_value(modules_sym); for_all_symbols(sym) { - sym_calc_value(sym); - if ((sym->flags & SYMBOL_NO_WRITE) || !sym->name) + if (sym_is_choice(sym)) continue; if (sym->flags & SYMBOL_WRITE) { if (sym->flags & SYMBOL_DEF_AUTO) { @@ -1141,12 +1089,12 @@ int conf_write_autoconf(int overwrite) if (ret) return -1; - if (conf_touch_deps()) - return 1; - for_all_symbols(sym) sym_calc_value(sym); + if (conf_touch_deps()) + return 1; + ret = __conf_write_autoconf(conf_get_autoheader_name(), print_symbol_for_c, &comment_style_c); @@ -1174,16 +1122,14 @@ int conf_write_autoconf(int overwrite) } static bool conf_changed; -static void (*conf_changed_callback)(void); +static void (*conf_changed_callback)(bool); void conf_set_changed(bool val) { - bool changed = conf_changed != val; + if (conf_changed_callback && conf_changed != val) + conf_changed_callback(val); conf_changed = val; - - if (conf_changed_callback && changed) - conf_changed_callback(); } bool conf_get_changed(void) @@ -1191,27 +1137,7 @@ bool conf_get_changed(void) return conf_changed; } -void conf_set_changed_callback(void (*fn)(void)) +void conf_set_changed_callback(void (*fn)(bool)) { conf_changed_callback = fn; } - -void set_all_choice_values(struct symbol *csym) -{ - struct property *prop; - struct symbol *sym; - struct expr *e; - - prop = sym_get_choice_prop(csym); - - /* - * Set all non-assinged choice values to no - */ - expr_list_for_each_sym(prop->expr, e, sym) { - if (!sym_has_value(sym)) - sym->def[S_DEF_USER].tri = no; - } - csym->flags |= SYMBOL_DEF_USER; - /* clear VALID to get value calculated */ - csym->flags &= ~(SYMBOL_VALID | SYMBOL_NEED_SET_CHOICE_VALUES); -} diff --git a/scripts/kconfig/expr.c b/scripts/kconfig/expr.c index a290de36307b..16f92c4a775a 100644 --- a/scripts/kconfig/expr.c +++ b/scripts/kconfig/expr.c @@ -9,44 +9,69 @@ #include <stdlib.h> #include <string.h> +#include <hash.h> +#include <xalloc.h> +#include "internal.h" #include "lkc.h" #define DEBUG_EXPR 0 +HASHTABLE_DEFINE(expr_hashtable, EXPR_HASHSIZE); + static struct expr *expr_eliminate_yn(struct expr *e); -struct expr *expr_alloc_symbol(struct symbol *sym) +/** + * expr_lookup - return the expression with the given type and sub-nodes + * This looks up an expression with the specified type and sub-nodes. If such + * an expression is found in the hash table, it is returned. Otherwise, a new + * expression node is allocated and added to the hash table. + * @type: expression type + * @l: left node + * @r: right node + * return: expression + */ +static struct expr *expr_lookup(enum expr_type type, void *l, void *r) { - struct expr *e = xcalloc(1, sizeof(*e)); - e->type = E_SYMBOL; - e->left.sym = sym; + struct expr *e; + int hash; + + hash = hash_32((unsigned int)type ^ hash_ptr(l) ^ hash_ptr(r)); + + hash_for_each_possible(expr_hashtable, e, node, hash) { + if (e->type == type && e->left._initdata == l && + e->right._initdata == r) + return e; + } + + e = xmalloc(sizeof(*e)); + e->type = type; + e->left._initdata = l; + e->right._initdata = r; + e->val_is_valid = false; + + hash_add(expr_hashtable, &e->node, hash); + return e; } +struct expr *expr_alloc_symbol(struct symbol *sym) +{ + return expr_lookup(E_SYMBOL, sym, NULL); +} + struct expr *expr_alloc_one(enum expr_type type, struct expr *ce) { - struct expr *e = xcalloc(1, sizeof(*e)); - e->type = type; - e->left.expr = ce; - return e; + return expr_lookup(type, ce, NULL); } struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2) { - struct expr *e = xcalloc(1, sizeof(*e)); - e->type = type; - e->left.expr = e1; - e->right.expr = e2; - return e; + return expr_lookup(type, e1, e2); } struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2) { - struct expr *e = xcalloc(1, sizeof(*e)); - e->type = type; - e->left.sym = s1; - e->right.sym = s2; - return e; + return expr_lookup(type, s1, s2); } struct expr *expr_alloc_and(struct expr *e1, struct expr *e2) @@ -63,82 +88,8 @@ struct expr *expr_alloc_or(struct expr *e1, struct expr *e2) return e2 ? expr_alloc_two(E_OR, e1, e2) : e1; } -struct expr *expr_copy(const struct expr *org) -{ - struct expr *e; - - if (!org) - return NULL; - - e = xmalloc(sizeof(*org)); - memcpy(e, org, sizeof(*org)); - switch (org->type) { - case E_SYMBOL: - e->left = org->left; - break; - case E_NOT: - e->left.expr = expr_copy(org->left.expr); - break; - case E_EQUAL: - case E_GEQ: - case E_GTH: - case E_LEQ: - case E_LTH: - case E_UNEQUAL: - e->left.sym = org->left.sym; - e->right.sym = org->right.sym; - break; - case E_AND: - case E_OR: - case E_LIST: - e->left.expr = expr_copy(org->left.expr); - e->right.expr = expr_copy(org->right.expr); - break; - default: - fprintf(stderr, "can't copy type %d\n", e->type); - free(e); - e = NULL; - break; - } - - return e; -} - -void expr_free(struct expr *e) -{ - if (!e) - return; - - switch (e->type) { - case E_SYMBOL: - break; - case E_NOT: - expr_free(e->left.expr); - break; - case E_EQUAL: - case E_GEQ: - case E_GTH: - case E_LEQ: - case E_LTH: - case E_UNEQUAL: - break; - case E_OR: - case E_AND: - expr_free(e->left.expr); - expr_free(e->right.expr); - break; - default: - fprintf(stderr, "how to free type %d?\n", e->type); - break; - } - free(e); -} - static int trans_count; -#define e1 (*ep1) -#define e2 (*ep2) - /* * expr_eliminate_eq() helper. * @@ -149,40 +100,47 @@ static int trans_count; */ static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2) { + struct expr *l, *r; + /* Recurse down to leaves */ - if (e1->type == type) { - __expr_eliminate_eq(type, &e1->left.expr, &e2); - __expr_eliminate_eq(type, &e1->right.expr, &e2); + if ((*ep1)->type == type) { + l = (*ep1)->left.expr; + r = (*ep1)->right.expr; + __expr_eliminate_eq(type, &l, ep2); + __expr_eliminate_eq(type, &r, ep2); + *ep1 = expr_alloc_two(type, l, r); return; } - if (e2->type == type) { - __expr_eliminate_eq(type, &e1, &e2->left.expr); - __expr_eliminate_eq(type, &e1, &e2->right.expr); + if ((*ep2)->type == type) { + l = (*ep2)->left.expr; + r = (*ep2)->right.expr; + __expr_eliminate_eq(type, ep1, &l); + __expr_eliminate_eq(type, ep1, &r); + *ep2 = expr_alloc_two(type, l, r); return; } - /* e1 and e2 are leaves. Compare them. */ + /* *ep1 and *ep2 are leaves. Compare them. */ - if (e1->type == E_SYMBOL && e2->type == E_SYMBOL && - e1->left.sym == e2->left.sym && - (e1->left.sym == &symbol_yes || e1->left.sym == &symbol_no)) + if ((*ep1)->type == E_SYMBOL && (*ep2)->type == E_SYMBOL && + (*ep1)->left.sym == (*ep2)->left.sym && + ((*ep1)->left.sym == &symbol_yes || (*ep1)->left.sym == &symbol_no)) return; - if (!expr_eq(e1, e2)) + if (!expr_eq(*ep1, *ep2)) return; - /* e1 and e2 are equal leaves. Prepare them for elimination. */ + /* *ep1 and *ep2 are equal leaves. Prepare them for elimination. */ trans_count++; - expr_free(e1); expr_free(e2); switch (type) { case E_OR: - e1 = expr_alloc_symbol(&symbol_no); - e2 = expr_alloc_symbol(&symbol_no); + *ep1 = expr_alloc_symbol(&symbol_no); + *ep2 = expr_alloc_symbol(&symbol_no); break; case E_AND: - e1 = expr_alloc_symbol(&symbol_yes); - e2 = expr_alloc_symbol(&symbol_yes); + *ep1 = expr_alloc_symbol(&symbol_yes); + *ep2 = expr_alloc_symbol(&symbol_yes); break; default: ; @@ -220,38 +178,36 @@ static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct e */ void expr_eliminate_eq(struct expr **ep1, struct expr **ep2) { - if (!e1 || !e2) + if (!*ep1 || !*ep2) return; - switch (e1->type) { + switch ((*ep1)->type) { case E_OR: case E_AND: - __expr_eliminate_eq(e1->type, ep1, ep2); + __expr_eliminate_eq((*ep1)->type, ep1, ep2); default: ; } - if (e1->type != e2->type) switch (e2->type) { + if ((*ep1)->type != (*ep2)->type) switch ((*ep2)->type) { case E_OR: case E_AND: - __expr_eliminate_eq(e2->type, ep1, ep2); + __expr_eliminate_eq((*ep2)->type, ep1, ep2); default: ; } - e1 = expr_eliminate_yn(e1); - e2 = expr_eliminate_yn(e2); + *ep1 = expr_eliminate_yn(*ep1); + *ep2 = expr_eliminate_yn(*ep2); } -#undef e1 -#undef e2 - /* * Returns true if 'e1' and 'e2' are equal, after minor simplification. Two * &&/|| expressions are considered equal if every operand in one expression * equals some operand in the other (operands do not need to appear in the same * order), recursively. */ -int expr_eq(struct expr *e1, struct expr *e2) +bool expr_eq(struct expr *e1, struct expr *e2) { - int res, old_count; + int old_count; + bool res; /* * A NULL expr is taken to be yes, but there's also a different way to @@ -261,7 +217,7 @@ int expr_eq(struct expr *e1, struct expr *e2) return expr_is_yes(e1) && expr_is_yes(e2); if (e1->type != e2->type) - return 0; + return false; switch (e1->type) { case E_EQUAL: case E_GEQ: @@ -276,17 +232,12 @@ int expr_eq(struct expr *e1, struct expr *e2) return expr_eq(e1->left.expr, e2->left.expr); case E_AND: case E_OR: - e1 = expr_copy(e1); - e2 = expr_copy(e2); old_count = trans_count; expr_eliminate_eq(&e1, &e2); res = (e1->type == E_SYMBOL && e2->type == E_SYMBOL && e1->left.sym == e2->left.sym); - expr_free(e1); - expr_free(e2); trans_count = old_count; return res; - case E_LIST: case E_RANGE: case E_NONE: /* panic */; @@ -299,11 +250,11 @@ int expr_eq(struct expr *e1, struct expr *e2) printf(" ?\n"); } - return 0; + return false; } /* - * Recursively performs the following simplifications in-place (as well as the + * Recursively performs the following simplifications (as well as the * corresponding simplifications with swapped operands): * * expr && n -> n @@ -315,108 +266,39 @@ int expr_eq(struct expr *e1, struct expr *e2) */ static struct expr *expr_eliminate_yn(struct expr *e) { - struct expr *tmp; + struct expr *l, *r; if (e) switch (e->type) { case E_AND: - e->left.expr = expr_eliminate_yn(e->left.expr); - e->right.expr = expr_eliminate_yn(e->right.expr); - if (e->left.expr->type == E_SYMBOL) { - if (e->left.expr->left.sym == &symbol_no) { - expr_free(e->left.expr); - expr_free(e->right.expr); - e->type = E_SYMBOL; - e->left.sym = &symbol_no; - e->right.expr = NULL; - return e; - } else if (e->left.expr->left.sym == &symbol_yes) { - free(e->left.expr); - tmp = e->right.expr; - *e = *(e->right.expr); - free(tmp); - return e; - } + l = expr_eliminate_yn(e->left.expr); + r = expr_eliminate_yn(e->right.expr); + if (l->type == E_SYMBOL) { + if (l->left.sym == &symbol_no) + return l; + else if (l->left.sym == &symbol_yes) + return r; } - if (e->right.expr->type == E_SYMBOL) { - if (e->right.expr->left.sym == &symbol_no) { - expr_free(e->left.expr); - expr_free(e->right.expr); - e->type = E_SYMBOL; - e->left.sym = &symbol_no; - e->right.expr = NULL; - return e; - } else if (e->right.expr->left.sym == &symbol_yes) { - free(e->right.expr); - tmp = e->left.expr; - *e = *(e->left.expr); - free(tmp); - return e; - } + if (r->type == E_SYMBOL) { + if (r->left.sym == &symbol_no) + return r; + else if (r->left.sym == &symbol_yes) + return l; } break; case E_OR: - e->left.expr = expr_eliminate_yn(e->left.expr); - e->right.expr = expr_eliminate_yn(e->right.expr); - if (e->left.expr->type == E_SYMBOL) { - if (e->left.expr->left.sym == &symbol_no) { - free(e->left.expr); - tmp = e->right.expr; - *e = *(e->right.expr); - free(tmp); - return e; - } else if (e->left.expr->left.sym == &symbol_yes) { - expr_free(e->left.expr); - expr_free(e->right.expr); - e->type = E_SYMBOL; - e->left.sym = &symbol_yes; - e->right.expr = NULL; - return e; - } + l = expr_eliminate_yn(e->left.expr); + r = expr_eliminate_yn(e->right.expr); + if (l->type == E_SYMBOL) { + if (l->left.sym == &symbol_no) + return r; + else if (l->left.sym == &symbol_yes) + return l; } - if (e->right.expr->type == E_SYMBOL) { - if (e->right.expr->left.sym == &symbol_no) { - free(e->right.expr); - tmp = e->left.expr; - *e = *(e->left.expr); - free(tmp); - return e; - } else if (e->right.expr->left.sym == &symbol_yes) { - expr_free(e->left.expr); - expr_free(e->right.expr); - e->type = E_SYMBOL; - e->left.sym = &symbol_yes; - e->right.expr = NULL; - return e; - } - } - break; - default: - ; - } - return e; -} - -/* - * bool FOO!=n => FOO - */ -struct expr *expr_trans_bool(struct expr *e) -{ - if (!e) - return NULL; - switch (e->type) { - case E_AND: - case E_OR: - case E_NOT: - e->left.expr = expr_trans_bool(e->left.expr); - e->right.expr = expr_trans_bool(e->right.expr); - break; - case E_UNEQUAL: - // FOO!=n -> FOO - if (e->left.sym->type == S_TRISTATE) { - if (e->right.sym == &symbol_no) { - e->type = E_SYMBOL; - e->right.sym = NULL; - } + if (r->type == E_SYMBOL) { + if (r->left.sym == &symbol_no) + return l; + else if (r->left.sym == &symbol_yes) + return r; } break; default: @@ -434,7 +316,7 @@ static struct expr *expr_join_or(struct expr *e1, struct expr *e2) struct symbol *sym1, *sym2; if (expr_eq(e1, e2)) - return expr_copy(e1); + return e1; if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) return NULL; if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) @@ -476,7 +358,8 @@ static struct expr *expr_join_or(struct expr *e1, struct expr *e2) return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_yes); } } - if (sym1->type == S_BOOLEAN && sym1 == sym2) { + if (sym1->type == S_BOOLEAN) { + // a || !a -> y if ((e1->type == E_NOT && e1->left.expr->type == E_SYMBOL && e2->type == E_SYMBOL) || (e2->type == E_NOT && e2->left.expr->type == E_SYMBOL && e1->type == E_SYMBOL)) return expr_alloc_symbol(&symbol_yes); @@ -498,7 +381,7 @@ static struct expr *expr_join_and(struct expr *e1, struct expr *e2) struct symbol *sym1, *sym2; if (expr_eq(e1, e2)) - return expr_copy(e1); + return e1; if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) return NULL; if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) @@ -595,59 +478,49 @@ static struct expr *expr_join_and(struct expr *e1, struct expr *e2) */ static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2) { -#define e1 (*ep1) -#define e2 (*ep2) - struct expr *tmp; + struct expr *tmp, *l, *r; /* Recurse down to leaves */ - if (e1->type == type) { - expr_eliminate_dups1(type, &e1->left.expr, &e2); - expr_eliminate_dups1(type, &e1->right.expr, &e2); + if ((*ep1)->type == type) { + l = (*ep1)->left.expr; + r = (*ep1)->right.expr; + expr_eliminate_dups1(type, &l, ep2); + expr_eliminate_dups1(type, &r, ep2); + *ep1 = expr_alloc_two(type, l, r); return; } - if (e2->type == type) { - expr_eliminate_dups1(type, &e1, &e2->left.expr); - expr_eliminate_dups1(type, &e1, &e2->right.expr); + if ((*ep2)->type == type) { + l = (*ep2)->left.expr; + r = (*ep2)->right.expr; + expr_eliminate_dups1(type, ep1, &l); + expr_eliminate_dups1(type, ep1, &r); + *ep2 = expr_alloc_two(type, l, r); return; } - /* e1 and e2 are leaves. Compare and process them. */ - - if (e1 == e2) - return; - - switch (e1->type) { - case E_OR: case E_AND: - expr_eliminate_dups1(e1->type, &e1, &e1); - default: - ; - } + /* *ep1 and *ep2 are leaves. Compare and process them. */ switch (type) { case E_OR: - tmp = expr_join_or(e1, e2); + tmp = expr_join_or(*ep1, *ep2); if (tmp) { - expr_free(e1); expr_free(e2); - e1 = expr_alloc_symbol(&symbol_no); - e2 = tmp; + *ep1 = expr_alloc_symbol(&symbol_no); + *ep2 = tmp; trans_count++; } break; case E_AND: - tmp = expr_join_and(e1, e2); + tmp = expr_join_and(*ep1, *ep2); if (tmp) { - expr_free(e1); expr_free(e2); - e1 = expr_alloc_symbol(&symbol_yes); - e2 = tmp; + *ep1 = expr_alloc_symbol(&symbol_yes); + *ep2 = tmp; trans_count++; } break; default: ; } -#undef e1 -#undef e2 } /* @@ -668,19 +541,21 @@ struct expr *expr_eliminate_dups(struct expr *e) return e; oldcount = trans_count; - while (1) { + do { + struct expr *l, *r; + trans_count = 0; switch (e->type) { case E_OR: case E_AND: - expr_eliminate_dups1(e->type, &e, &e); + l = expr_eliminate_dups(e->left.expr); + r = expr_eliminate_dups(e->right.expr); + expr_eliminate_dups1(e->type, &l, &r); + e = expr_alloc_two(e->type, l, r); default: ; } - if (!trans_count) - /* No simplifications done in this pass. We're done */ - break; e = expr_eliminate_yn(e); - } + } while (trans_count); /* repeat until we get no more simplifications */ trans_count = oldcount; return e; } @@ -689,12 +564,34 @@ struct expr *expr_eliminate_dups(struct expr *e) * Performs various simplifications involving logical operators and * comparisons. * + * For bool type: + * A=n -> !A + * A=m -> n + * A=y -> A + * A!=n -> A + * A!=m -> y + * A!=y -> !A + * + * For any type: + * !!A -> A + * !(A=B) -> A!=B + * !(A!=B) -> A=B + * !(A<=B) -> A>B + * !(A>=B) -> A<B + * !(A<B) -> A>=B + * !(A>B) -> A<=B + * !(A || B) -> !A && !B + * !(A && B) -> !A || !B + * + * For constant: + * !y -> n + * !m -> m + * !n -> y + * * Allocates and returns a new expression. */ struct expr *expr_transform(struct expr *e) { - struct expr *tmp; - if (!e) return NULL; switch (e->type) { @@ -705,11 +602,11 @@ struct expr *expr_transform(struct expr *e) case E_LTH: case E_UNEQUAL: case E_SYMBOL: - case E_LIST: break; default: - e->left.expr = expr_transform(e->left.expr); - e->right.expr = expr_transform(e->right.expr); + e = expr_alloc_two(e->type, + expr_transform(e->left.expr), + expr_transform(e->right.expr)); } switch (e->type) { @@ -717,21 +614,19 @@ struct expr *expr_transform(struct expr *e) if (e->left.sym->type != S_BOOLEAN) break; if (e->right.sym == &symbol_no) { - e->type = E_NOT; - e->left.expr = expr_alloc_symbol(e->left.sym); - e->right.sym = NULL; + // A=n -> !A + e = expr_alloc_one(E_NOT, expr_alloc_symbol(e->left.sym)); break; } if (e->right.sym == &symbol_mod) { + // A=m -> n printf("boolean symbol %s tested for 'm'? test forced to 'n'\n", e->left.sym->name); - e->type = E_SYMBOL; - e->left.sym = &symbol_no; - e->right.sym = NULL; + e = expr_alloc_symbol(&symbol_no); break; } if (e->right.sym == &symbol_yes) { - e->type = E_SYMBOL; - e->right.sym = NULL; + // A=y -> A + e = expr_alloc_symbol(e->left.sym); break; } break; @@ -739,104 +634,71 @@ struct expr *expr_transform(struct expr *e) if (e->left.sym->type != S_BOOLEAN) break; if (e->right.sym == &symbol_no) { - e->type = E_SYMBOL; - e->right.sym = NULL; + // A!=n -> A + e = expr_alloc_symbol(e->left.sym); break; } if (e->right.sym == &symbol_mod) { + // A!=m -> y printf("boolean symbol %s tested for 'm'? test forced to 'y'\n", e->left.sym->name); - e->type = E_SYMBOL; - e->left.sym = &symbol_yes; - e->right.sym = NULL; + e = expr_alloc_symbol(&symbol_yes); break; } if (e->right.sym == &symbol_yes) { - e->type = E_NOT; - e->left.expr = expr_alloc_symbol(e->left.sym); - e->right.sym = NULL; + // A!=y -> !A + e = expr_alloc_one(E_NOT, e->left.expr); break; } break; case E_NOT: switch (e->left.expr->type) { case E_NOT: - // !!a -> a - tmp = e->left.expr->left.expr; - free(e->left.expr); - free(e); - e = tmp; - e = expr_transform(e); + // !!A -> A + e = e->left.expr->left.expr; break; case E_EQUAL: case E_UNEQUAL: - // !a='x' -> a!='x' - tmp = e->left.expr; - free(e); - e = tmp; - e->type = e->type == E_EQUAL ? E_UNEQUAL : E_EQUAL; + // !(A=B) -> A!=B + e = expr_alloc_comp(e->left.expr->type == E_EQUAL ? E_UNEQUAL : E_EQUAL, + e->left.expr->left.sym, + e->left.expr->right.sym); break; case E_LEQ: case E_GEQ: - // !a<='x' -> a>'x' - tmp = e->left.expr; - free(e); - e = tmp; - e->type = e->type == E_LEQ ? E_GTH : E_LTH; + // !(A<=B) -> A>B + e = expr_alloc_comp(e->left.expr->type == E_LEQ ? E_GTH : E_LTH, + e->left.expr->left.sym, + e->left.expr->right.sym); break; case E_LTH: case E_GTH: - // !a<'x' -> a>='x' - tmp = e->left.expr; - free(e); - e = tmp; - e->type = e->type == E_LTH ? E_GEQ : E_LEQ; + // !(A<B) -> A>=B + e = expr_alloc_comp(e->left.expr->type == E_LTH ? E_GEQ : E_LEQ, + e->left.expr->left.sym, + e->left.expr->right.sym); break; case E_OR: - // !(a || b) -> !a && !b - tmp = e->left.expr; - e->type = E_AND; - e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); - tmp->type = E_NOT; - tmp->right.expr = NULL; + // !(A || B) -> !A && !B + e = expr_alloc_and(expr_alloc_one(E_NOT, e->left.expr->left.expr), + expr_alloc_one(E_NOT, e->left.expr->right.expr)); e = expr_transform(e); break; case E_AND: - // !(a && b) -> !a || !b - tmp = e->left.expr; - e->type = E_OR; - e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); - tmp->type = E_NOT; - tmp->right.expr = NULL; + // !(A && B) -> !A || !B + e = expr_alloc_or(expr_alloc_one(E_NOT, e->left.expr->left.expr), + expr_alloc_one(E_NOT, e->left.expr->right.expr)); e = expr_transform(e); break; case E_SYMBOL: - if (e->left.expr->left.sym == &symbol_yes) { + if (e->left.expr->left.sym == &symbol_yes) // !'y' -> 'n' - tmp = e->left.expr; - free(e); - e = tmp; - e->type = E_SYMBOL; - e->left.sym = &symbol_no; - break; - } - if (e->left.expr->left.sym == &symbol_mod) { + e = expr_alloc_symbol(&symbol_no); + else if (e->left.expr->left.sym == &symbol_mod) // !'m' -> 'm' - tmp = e->left.expr; - free(e); - e = tmp; - e->type = E_SYMBOL; - e->left.sym = &symbol_mod; - break; - } - if (e->left.expr->left.sym == &symbol_no) { + e = expr_alloc_symbol(&symbol_mod); + else if (e->left.expr->left.sym == &symbol_no) // !'n' -> 'y' - tmp = e->left.expr; - free(e); - e = tmp; - e->type = E_SYMBOL; - e->left.sym = &symbol_yes; - break; - } + e = expr_alloc_symbol(&symbol_yes); break; default: ; @@ -848,10 +710,10 @@ struct expr *expr_transform(struct expr *e) return e; } -int expr_contains_symbol(struct expr *dep, struct symbol *sym) +bool expr_contains_symbol(struct expr *dep, struct symbol *sym) { if (!dep) - return 0; + return false; switch (dep->type) { case E_AND: @@ -873,7 +735,7 @@ int expr_contains_symbol(struct expr *dep, struct symbol *sym) default: ; } - return 0; + return false; } bool expr_depends_symbol(struct expr *dep, struct symbol *sym) @@ -960,23 +822,22 @@ struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symb case E_EQUAL: if (type == E_EQUAL) { if (sym == &symbol_yes) - return expr_copy(e); + return e; if (sym == &symbol_mod) return expr_alloc_symbol(&symbol_no); if (sym == &symbol_no) - return expr_alloc_one(E_NOT, expr_copy(e)); + return expr_alloc_one(E_NOT, e); } else { if (sym == &symbol_yes) - return expr_alloc_one(E_NOT, expr_copy(e)); + return expr_alloc_one(E_NOT, e); if (sym == &symbol_mod) return expr_alloc_symbol(&symbol_yes); if (sym == &symbol_no) - return expr_copy(e); + return e; } break; case E_SYMBOL: return expr_alloc_comp(type, e->left.sym, sym); - case E_LIST: case E_RANGE: case E_NONE: /* panic */; @@ -1027,7 +888,7 @@ static enum string_value_kind expr_parse_string(const char *str, ? kind : k_string; } -tristate expr_calc_value(struct expr *e) +static tristate __expr_calc_value(struct expr *e) { tristate val1, val2; const char *str1, *str2; @@ -1035,9 +896,6 @@ tristate expr_calc_value(struct expr *e) union string_value lval = {}, rval = {}; int res; - if (!e) - return yes; - switch (e->type) { case E_SYMBOL: sym_calc_value(e->left.sym); @@ -1101,6 +959,35 @@ tristate expr_calc_value(struct expr *e) } } +/** + * expr_calc_value - return the tristate value of the given expression + * @e: expression + * return: tristate value of the expression + */ +tristate expr_calc_value(struct expr *e) +{ + if (!e) + return yes; + + if (!e->val_is_valid) { + e->val = __expr_calc_value(e); + e->val_is_valid = true; + } + + return e->val; +} + +/** + * expr_invalidate_all - invalidate all cached expression values + */ +void expr_invalidate_all(void) +{ + struct expr *e; + + hash_for_each(expr_hashtable, e, node) + e->val_is_valid = false; +} + static int expr_compare_type(enum expr_type t1, enum expr_type t2) { if (t1 == t2) @@ -1112,29 +999,27 @@ static int expr_compare_type(enum expr_type t1, enum expr_type t2) case E_GTH: if (t2 == E_EQUAL || t2 == E_UNEQUAL) return 1; + /* fallthrough */ case E_EQUAL: case E_UNEQUAL: if (t2 == E_NOT) return 1; + /* fallthrough */ case E_NOT: if (t2 == E_AND) return 1; + /* fallthrough */ case E_AND: if (t2 == E_OR) return 1; - case E_OR: - if (t2 == E_LIST) - return 1; - case E_LIST: - if (t2 == 0) - return 1; + /* fallthrough */ default: - return -1; + break; } return 0; } -void expr_print(struct expr *e, +void expr_print(const struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken) { @@ -1200,13 +1085,6 @@ void expr_print(struct expr *e, fn(data, NULL, " && "); expr_print(e->right.expr, fn, data, E_AND); break; - case E_LIST: - fn(data, e->right.sym, e->right.sym->name); - if (e->left.expr) { - fn(data, NULL, " ^ "); - expr_print(e->left.expr, fn, data, E_LIST); - } - break; case E_RANGE: fn(data, NULL, "["); fn(data, e->left.sym, e->left.sym->name); @@ -1266,7 +1144,7 @@ static void expr_print_gstr_helper(void *data, struct symbol *sym, const char *s str_printf(gs, " [=%s]", sym_str); } -void expr_gstr_print(struct expr *e, struct gstr *gs) +void expr_gstr_print(const struct expr *e, struct gstr *gs) { expr_print(e, expr_print_gstr_helper, gs, E_NONE); } diff --git a/scripts/kconfig/expr.h b/scripts/kconfig/expr.h index 0158f5eac454..fe2231e0e6a4 100644 --- a/scripts/kconfig/expr.h +++ b/scripts/kconfig/expr.h @@ -12,12 +12,11 @@ extern "C" { #include <assert.h> #include <stdio.h> -#include "list_types.h" #ifndef __cplusplus #include <stdbool.h> #endif -#include "list_types.h" +#include <list_types.h> typedef enum tristate { no, mod, yes @@ -26,16 +25,30 @@ typedef enum tristate { enum expr_type { E_NONE, E_OR, E_AND, E_NOT, E_EQUAL, E_UNEQUAL, E_LTH, E_LEQ, E_GTH, E_GEQ, - E_LIST, E_SYMBOL, E_RANGE + E_SYMBOL, E_RANGE }; union expr_data { - struct expr *expr; - struct symbol *sym; + struct expr * const expr; + struct symbol * const sym; + void *_initdata; }; +/** + * struct expr - expression + * + * @node: link node for the hash table + * @type: expressoin type + * @val: calculated tristate value + * @val_is_valid: indicate whether the value is valid + * @left: left node + * @right: right node + */ struct expr { + struct hlist_node node; enum expr_type type; + tristate val; + bool val_is_valid; union expr_data left, right; }; @@ -43,9 +56,6 @@ struct expr { #define EXPR_AND(dep1, dep2) (((dep1)<(dep2))?(dep1):(dep2)) #define EXPR_NOT(dep) (2-(dep)) -#define expr_list_for_each_sym(l, e, s) \ - for (e = (l); e && (s = e->right.sym); e = e->left.expr) - struct expr_value { struct expr *expr; tristate tri; @@ -72,8 +82,9 @@ enum { /* * Represents a configuration symbol. * - * Choices are represented as a special kind of symbol and have the - * SYMBOL_CHOICE bit set in 'flags'. + * Choices are represented as a special kind of symbol with null name. + * + * @choice_link: linked to menu::choice_members */ struct symbol { /* link node for the hash table */ @@ -111,6 +122,8 @@ struct symbol { /* config entries associated with this symbol */ struct list_head menus; + struct list_head choice_link; + /* SYMBOL_* flags */ int flags; @@ -131,14 +144,9 @@ struct symbol { #define SYMBOL_CONST 0x0001 /* symbol is const */ #define SYMBOL_CHECK 0x0008 /* used during dependency checking */ -#define SYMBOL_CHOICE 0x0010 /* start of a choice block (null name) */ -#define SYMBOL_CHOICEVAL 0x0020 /* used as a value in a choice block */ #define SYMBOL_VALID 0x0080 /* set when symbol.curr is calculated */ -#define SYMBOL_OPTIONAL 0x0100 /* choice is optional - values can be 'n' */ #define SYMBOL_WRITE 0x0200 /* write symbol to file (KCONFIG_CONFIG) */ -#define SYMBOL_CHANGED 0x0400 /* ? */ #define SYMBOL_WRITTEN 0x0800 /* track info to avoid double-write to .config */ -#define SYMBOL_NO_WRITE 0x1000 /* Symbol for internal use only; it will not be written */ #define SYMBOL_CHECKED 0x2000 /* used during dependency checking */ #define SYMBOL_WARNED 0x8000 /* warning has been issued */ @@ -149,9 +157,6 @@ struct symbol { #define SYMBOL_DEF3 0x40000 /* symbol.def[S_DEF_3] is valid */ #define SYMBOL_DEF4 0x80000 /* symbol.def[S_DEF_4] is valid */ -/* choice values need to be set before calculating this symbol value */ -#define SYMBOL_NEED_SET_CHOICE_VALUES 0x100000 - #define SYMBOL_MAXLENGTH 256 /* A property represent the config options that can be associated @@ -174,11 +179,9 @@ enum prop_type { P_COMMENT, /* text associated with a comment */ P_MENU, /* prompt associated with a menu or menuconfig symbol */ P_DEFAULT, /* default y */ - P_CHOICE, /* choice value */ P_SELECT, /* select BAR */ P_IMPLY, /* imply BAR */ P_RANGE, /* range 7..100 (for a symbol) */ - P_SYMBOL, /* where a symbol is defined */ }; struct property { @@ -188,7 +191,7 @@ struct property { struct expr_value visible; struct expr *expr; /* the optional conditional part of the property */ struct menu *menu; /* the menu the property are associated with - * valid for: P_SELECT, P_RANGE, P_CHOICE, + * valid for: P_SELECT, P_RANGE, * P_PROMPT, P_DEFAULT, P_MENU, P_COMMENT */ const char *filename; /* what file was this property defined */ int lineno; /* what lineno was this property defined */ @@ -198,18 +201,30 @@ struct property { for (st = sym->prop; st; st = st->next) \ if (st->type == (tok)) #define for_all_defaults(sym, st) for_all_properties(sym, st, P_DEFAULT) -#define for_all_choices(sym, st) for_all_properties(sym, st, P_CHOICE) #define for_all_prompts(sym, st) \ for (st = sym->prop; st; st = st->next) \ if (st->text) +enum menu_type { + M_CHOICE, // "choice" + M_COMMENT, // "comment" + M_IF, // "if" + M_MENU, // "mainmenu", "menu", "menuconfig" + M_NORMAL, // others, i.e., "config" +}; + /* * Represents a node in the menu tree, as seen in e.g. menuconfig (though used * for all front ends). Each symbol, menu, etc. defined in the Kconfig files * gets a node. A symbol defined in multiple locations gets one node at each * location. + * + * @type: type of the menu entry + * @choice_members: list of choice members with priority. */ struct menu { + enum menu_type type; + /* The next menu node at the same level */ struct menu *next; @@ -227,6 +242,8 @@ struct menu { struct list_head link; /* link to symbol::menus */ + struct list_head choice_members; + /* * The prompt associated with the node. This holds the prompt for a * symbol as well as the text for a menu or comment, along with the @@ -283,34 +300,26 @@ struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2); struct expr *expr_alloc_and(struct expr *e1, struct expr *e2); struct expr *expr_alloc_or(struct expr *e1, struct expr *e2); -struct expr *expr_copy(const struct expr *org); -void expr_free(struct expr *e); void expr_eliminate_eq(struct expr **ep1, struct expr **ep2); -int expr_eq(struct expr *e1, struct expr *e2); +bool expr_eq(struct expr *e1, struct expr *e2); tristate expr_calc_value(struct expr *e); -struct expr *expr_trans_bool(struct expr *e); struct expr *expr_eliminate_dups(struct expr *e); struct expr *expr_transform(struct expr *e); -int expr_contains_symbol(struct expr *dep, struct symbol *sym); +bool expr_contains_symbol(struct expr *dep, struct symbol *sym); bool expr_depends_symbol(struct expr *dep, struct symbol *sym); struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym); void expr_fprint(struct expr *e, FILE *out); struct gstr; /* forward */ -void expr_gstr_print(struct expr *e, struct gstr *gs); +void expr_gstr_print(const struct expr *e, struct gstr *gs); void expr_gstr_print_revdep(struct expr *e, struct gstr *gs, tristate pr_type, const char *title); -static inline int expr_is_yes(struct expr *e) +static inline bool expr_is_yes(const struct expr *e) { return !e || (e->type == E_SYMBOL && e->left.sym == &symbol_yes); } -static inline int expr_is_no(struct expr *e) -{ - return e && (e->type == E_SYMBOL && e->left.sym == &symbol_no); -} - #ifdef __cplusplus } #endif diff --git a/scripts/kconfig/gconf-cfg.sh b/scripts/kconfig/gconf-cfg.sh index 040d8f338820..fc954c0538fa 100755 --- a/scripts/kconfig/gconf-cfg.sh +++ b/scripts/kconfig/gconf-cfg.sh @@ -1,6 +1,8 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 +set -eu + cflags=$1 libs=$2 diff --git a/scripts/kconfig/gconf.c b/scripts/kconfig/gconf.c index 9709aca3a30f..c0f46f189060 100644 --- a/scripts/kconfig/gconf.c +++ b/scripts/kconfig/gconf.c @@ -18,8 +18,6 @@ #include <unistd.h> #include <time.h> -//#define DEBUG - enum { SINGLE_VIEW, SPLIT_VIEW, FULL_VIEW }; @@ -67,42 +65,6 @@ static void display_list(void); static void display_tree(struct menu *menu); static void display_tree_part(void); static void update_tree(struct menu *src, GtkTreeIter * dst); -static void set_node(GtkTreeIter * node, struct menu *menu, gchar ** row); -static gchar **fill_row(struct menu *menu); -static void conf_changed(void); - -/* Helping/Debugging Functions */ -#ifdef DEBUG -static const char *dbg_sym_flags(int val) -{ - static char buf[256]; - - bzero(buf, 256); - - if (val & SYMBOL_CONST) - strcat(buf, "const/"); - if (val & SYMBOL_CHECK) - strcat(buf, "check/"); - if (val & SYMBOL_CHOICE) - strcat(buf, "choice/"); - if (val & SYMBOL_CHOICEVAL) - strcat(buf, "choiceval/"); - if (val & SYMBOL_VALID) - strcat(buf, "valid/"); - if (val & SYMBOL_OPTIONAL) - strcat(buf, "optional/"); - if (val & SYMBOL_WRITE) - strcat(buf, "write/"); - if (val & SYMBOL_CHANGED) - strcat(buf, "changed/"); - if (val & SYMBOL_NO_WRITE) - strcat(buf, "no_write/"); - - buf[strlen(buf) - 1] = '\0'; - - return buf; -} -#endif static void replace_button_icon(GladeXML *xml, GdkDrawable *window, GtkStyle *style, gchar *btn_name, gchar **xpm) @@ -122,6 +84,12 @@ static void replace_button_icon(GladeXML *xml, GdkDrawable *window, gtk_tool_button_set_icon_widget(button, image); } +static void conf_changed(bool dirty) +{ + gtk_widget_set_sensitive(save_btn, dirty); + gtk_widget_set_sensitive(save_menu_item, dirty); +} + /* Main Window Initialization */ static void init_main_window(const gchar *glade_file) { @@ -493,7 +461,7 @@ load_filename(GtkFileSelection * file_selector, gpointer user_data) if (conf_read(fn)) text_insert_msg("Error", "Unable to load configuration !"); else - display_tree(&rootmenu); + display_tree_part(); } void on_load1_activate(GtkMenuItem * menuitem, gpointer user_data) @@ -1082,15 +1050,13 @@ static gchar **fill_row(struct menu *menu) row[COL_NAME] = g_strdup(sym->name); sym_calc_value(sym); - sym->flags &= ~SYMBOL_CHANGED; + menu->flags &= ~MENU_CHANGED; if (sym_is_choice(sym)) { // parse childs for getting final value struct menu *child; - struct symbol *def_sym = sym_get_choice_value(sym); + struct symbol *def_sym = sym_calc_choice(menu); struct menu *def_menu = NULL; - row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); - for (child = menu->list; child; child = child->next) { if (menu_is_visible(child) && child->sym == def_sym) @@ -1100,18 +1066,16 @@ static gchar **fill_row(struct menu *menu) if (def_menu) row[COL_VALUE] = g_strdup(menu_get_prompt(def_menu)); + + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + return row; } - if (sym->flags & SYMBOL_CHOICEVAL) + if (sym_is_choice_value(sym)) row[COL_BTNRAD] = GINT_TO_POINTER(TRUE); stype = sym_get_type(sym); switch (stype) { case S_BOOLEAN: - if (GPOINTER_TO_INT(row[COL_PIXVIS]) == FALSE) - row[COL_BTNVIS] = GINT_TO_POINTER(TRUE); - if (sym_is_choice(sym)) - break; - /* fall through */ case S_TRISTATE: val = sym_get_tristate_value(sym); switch (val) { @@ -1268,12 +1232,6 @@ static void update_tree(struct menu *src, GtkTreeIter * dst) else menu2 = NULL; // force adding of a first child -#ifdef DEBUG - printf("%*c%s | %s\n", indent, ' ', - menu1 ? menu_get_prompt(menu1) : "nil", - menu2 ? menu_get_prompt(menu2) : "nil"); -#endif - if ((opt_mode == OPT_NORMAL && !menu_is_visible(child1)) || (opt_mode == OPT_PROMPT && !menu_has_prompt(child1)) || (opt_mode == OPT_ALL && !menu_get_prompt(child1))) { @@ -1314,7 +1272,7 @@ static void update_tree(struct menu *src, GtkTreeIter * dst) else goto reparse; // next child } - } else if (sym && (sym->flags & SYMBOL_CHANGED)) { + } else if (sym && (child1->flags & MENU_CHANGED)) { set_node(child2, menu1, fill_row(menu1)); } @@ -1330,7 +1288,6 @@ static void update_tree(struct menu *src, GtkTreeIter * dst) /* Display the whole tree (single/split/full view) */ static void display_tree(struct menu *menu) { - struct symbol *sym; struct property *prop; struct menu *child; enum prop_type ptype; @@ -1342,11 +1299,9 @@ static void display_tree(struct menu *menu) for (child = menu->list; child; child = child->next) { prop = child->prompt; - sym = child->sym; ptype = prop ? prop->type : P_UNKNOWN; - if (sym) - sym->flags &= ~SYMBOL_CHANGED; + menu->flags &= ~MENU_CHANGED; if ((view_mode == SPLIT_VIEW) && !(child->flags & MENU_ROOT) && (tree == tree1)) @@ -1360,19 +1315,7 @@ static void display_tree(struct menu *menu) (opt_mode == OPT_PROMPT && menu_has_prompt(child)) || (opt_mode == OPT_ALL && menu_get_prompt(child))) place_node(child, fill_row(child)); -#ifdef DEBUG - printf("%*c%s: ", indent, ' ', menu_get_prompt(child)); - printf("%s", child->flags & MENU_ROOT ? "rootmenu | " : ""); - printf("%s", prop_get_type_name(ptype)); - printf(" | "); - if (sym) { - printf("%s", sym_type_name(sym->type)); - printf(" | "); - printf("%s", dbg_sym_flags(sym->flags)); - printf("\n"); - } else - printf("\n"); -#endif + if ((view_mode != FULL_VIEW) && (ptype == P_MENU) && (tree == tree2)) continue; @@ -1405,6 +1348,8 @@ static void display_tree_part(void) display_tree(current); else if (view_mode == SPLIT_VIEW) display_tree(browsed); + else if (view_mode == FULL_VIEW) + display_tree(&rootmenu); gtk_tree_view_expand_all(GTK_TREE_VIEW(tree2_w)); } @@ -1478,7 +1423,6 @@ int main(int ac, char *av[]) conf_parse(name); fixup_rootmenu(&rootmenu); - conf_read(NULL); /* Load the interface and connect signals */ init_main_window(glade_file); @@ -1486,6 +1430,8 @@ int main(int ac, char *av[]) init_left_tree(); init_right_tree(); + conf_read(NULL); + switch (view_mode) { case SINGLE_VIEW: display_tree_part(); @@ -1502,10 +1448,3 @@ int main(int ac, char *av[]) return 0; } - -static void conf_changed(void) -{ - bool changed = conf_get_changed(); - gtk_widget_set_sensitive(save_btn, changed); - gtk_widget_set_sensitive(save_menu_item, changed); -} diff --git a/scripts/kconfig/hashtable.h b/scripts/kconfig/hashtable.h deleted file mode 100644 index a0a2c8f5f639..000000000000 --- a/scripts/kconfig/hashtable.h +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef HASHTABLE_H -#define HASHTABLE_H - -#include "array_size.h" -#include "list.h" - -#define HASH_SIZE(name) (ARRAY_SIZE(name)) - -#define HASHTABLE_DECLARE(name, size) struct hlist_head name[size] - -#define HASHTABLE_DEFINE(name, size) \ - HASHTABLE_DECLARE(name, size) = \ - { [0 ... ((size) - 1)] = HLIST_HEAD_INIT } - -#define hash_head(table, key) (&(table)[(key) % HASH_SIZE(table)]) - -/** - * hash_add - add an object to a hashtable - * @table: hashtable to add to - * @node: the &struct hlist_node of the object to be added - * @key: the key of the object to be added - */ -#define hash_add(table, node, key) \ - hlist_add_head(node, hash_head(table, key)) - -/** - * hash_for_each - iterate over a hashtable - * @table: hashtable to iterate - * @obj: the type * to use as a loop cursor for each entry - * @member: the name of the hlist_node within the struct - */ -#define hash_for_each(table, obj, member) \ - for (int _bkt = 0; _bkt < HASH_SIZE(table); _bkt++) \ - hlist_for_each_entry(obj, &table[_bkt], member) - -/** - * hash_for_each_possible - iterate over all possible objects hashing to the - * same bucket - * @table: hashtable to iterate - * @obj: the type * to use as a loop cursor for each entry - * @member: the name of the hlist_node within the struct - * @key: the key of the objects to iterate over - */ -#define hash_for_each_possible(table, obj, member, key) \ - hlist_for_each_entry(obj, hash_head(table, key), member) - -#endif /* HASHTABLE_H */ diff --git a/scripts/kconfig/internal.h b/scripts/kconfig/internal.h index 6c721c4cfd72..d0ffce2dfbba 100644 --- a/scripts/kconfig/internal.h +++ b/scripts/kconfig/internal.h @@ -2,7 +2,7 @@ #ifndef INTERNAL_H #define INTERNAL_H -#include "hashtable.h" +#include <hashtable.h> #define SYMBOL_HASHSIZE (1U << 14) @@ -11,6 +11,12 @@ extern HASHTABLE_DECLARE(sym_hashtable, SYMBOL_HASHSIZE); #define for_all_symbols(sym) \ hash_for_each(sym_hashtable, sym, node) +#define EXPR_HASHSIZE (1U << 14) + +extern HASHTABLE_DECLARE(expr_hashtable, EXPR_HASHSIZE); + +void expr_invalidate_all(void); + struct menu; extern struct menu *current_menu, *current_entry; diff --git a/scripts/kconfig/lexer.l b/scripts/kconfig/lexer.l index 89544c3a1a29..9c2cdfc33c6f 100644 --- a/scripts/kconfig/lexer.l +++ b/scripts/kconfig/lexer.l @@ -13,6 +13,7 @@ #include <stdlib.h> #include <string.h> +#include <xalloc.h> #include "lkc.h" #include "preprocess.h" @@ -120,7 +121,6 @@ n [A-Za-z0-9_-] "menuconfig" return T_MENUCONFIG; "modules" return T_MODULES; "on" return T_ON; -"optional" return T_OPTIONAL; "prompt" return T_PROMPT; "range" return T_RANGE; "select" return T_SELECT; diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h index e69d7c59d930..fbc907f75eac 100644 --- a/scripts/kconfig/lkc.h +++ b/scripts/kconfig/lkc.h @@ -40,7 +40,6 @@ void zconf_nextfile(const char *name); /* confdata.c */ extern struct gstr autoconf_cmd; const char *conf_get_configname(void); -void set_all_choice_values(struct symbol *csym); /* confdata.c and expr.c */ static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out) @@ -52,13 +51,7 @@ static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out) } /* util.c */ -unsigned int strhash(const char *s); const char *file_lookup(const char *name); -void *xmalloc(size_t size); -void *xcalloc(size_t nmemb, size_t size); -void *xrealloc(void *p, size_t size); -char *xstrdup(const char *s); -char *xstrndup(const char *s, size_t n); /* lexer.l */ int yylex(void); @@ -76,28 +69,34 @@ struct gstr str_new(void); void str_free(struct gstr *gs); void str_append(struct gstr *gs, const char *s); void str_printf(struct gstr *gs, const char *fmt, ...); -char *str_get(struct gstr *gs); +char *str_get(const struct gstr *gs); /* menu.c */ +struct menu *menu_next(struct menu *menu, struct menu *root); +#define menu_for_each_sub_entry(menu, root) \ + for (menu = menu_next(root, root); menu; menu = menu_next(menu, root)) +#define menu_for_each_entry(menu) \ + menu_for_each_sub_entry(menu, &rootmenu) void _menu_init(void); -void menu_warn(struct menu *menu, const char *fmt, ...); +void menu_warn(const struct menu *menu, const char *fmt, ...); struct menu *menu_add_menu(void); void menu_end_menu(void); -void menu_add_entry(struct symbol *sym); +void menu_add_entry(struct symbol *sym, enum menu_type type); void menu_add_dep(struct expr *dep); void menu_add_visibility(struct expr *dep); -struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep); +struct property *menu_add_prompt(enum prop_type type, const char *prompt, + struct expr *dep); void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep); void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep); -void menu_finalize(struct menu *parent); +void menu_finalize(void); void menu_set_type(int type); extern struct menu rootmenu; bool menu_is_empty(struct menu *menu); bool menu_is_visible(struct menu *menu); -bool menu_has_prompt(struct menu *menu); -const char *menu_get_prompt(struct menu *menu); +bool menu_has_prompt(const struct menu *menu); +const char *menu_get_prompt(const struct menu *menu); struct menu *menu_get_parent_menu(struct menu *menu); int get_jump_key_char(void); struct gstr get_relations_str(struct symbol **sym_arr, struct list_head *head); @@ -105,39 +104,27 @@ void menu_get_ext_help(struct menu *menu, struct gstr *help); /* symbol.c */ void sym_clear_all_valid(void); -struct symbol *sym_choice_default(struct symbol *sym); +struct symbol *sym_choice_default(struct menu *choice); +struct symbol *sym_calc_choice(struct menu *choice); struct property *sym_get_range_prop(struct symbol *sym); const char *sym_get_string_default(struct symbol *sym); struct symbol *sym_check_deps(struct symbol *sym); -struct symbol *prop_get_symbol(struct property *prop); +struct symbol *prop_get_symbol(const struct property *prop); -static inline tristate sym_get_tristate_value(struct symbol *sym) +static inline tristate sym_get_tristate_value(const struct symbol *sym) { return sym->curr.tri; } - -static inline struct symbol *sym_get_choice_value(struct symbol *sym) +static inline bool sym_is_choice(const struct symbol *sym) { - return (struct symbol *)sym->curr.val; + /* A choice is a symbol with no name */ + return sym->name == NULL; } -static inline bool sym_is_choice(struct symbol *sym) -{ - return sym->flags & SYMBOL_CHOICE ? true : false; -} - -static inline bool sym_is_choice_value(struct symbol *sym) -{ - return sym->flags & SYMBOL_CHOICEVAL ? true : false; -} - -static inline bool sym_is_optional(struct symbol *sym) -{ - return sym->flags & SYMBOL_OPTIONAL ? true : false; -} +bool sym_is_choice_value(const struct symbol *sym); -static inline bool sym_has_value(struct symbol *sym) +static inline bool sym_has_value(const struct symbol *sym) { return sym->flags & SYMBOL_DEF_USER ? true : false; } diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h index 2807fa584c2b..8914b4e8f2a8 100644 --- a/scripts/kconfig/lkc_proto.h +++ b/scripts/kconfig/lkc_proto.h @@ -13,7 +13,7 @@ int conf_write(const char *name); int conf_write_autoconf(int overwrite); void conf_set_changed(bool val); bool conf_get_changed(void); -void conf_set_changed_callback(void (*fn)(void)); +void conf_set_changed_callback(void (*fn)(bool)); void conf_set_message_callback(void (*fn)(const char *s)); bool conf_errors(void); @@ -25,20 +25,24 @@ struct symbol ** sym_re_search(const char *pattern); const char * sym_type_name(enum symbol_type type); void sym_calc_value(struct symbol *sym); bool sym_dep_errors(void); -enum symbol_type sym_get_type(struct symbol *sym); -bool sym_tristate_within_range(struct symbol *sym,tristate tri); +enum symbol_type sym_get_type(const struct symbol *sym); +bool sym_tristate_within_range(const struct symbol *sym, tristate tri); bool sym_set_tristate_value(struct symbol *sym,tristate tri); +void choice_set_value(struct menu *choice, struct symbol *sym); tristate sym_toggle_tristate_value(struct symbol *sym); bool sym_string_valid(struct symbol *sym, const char *newval); bool sym_string_within_range(struct symbol *sym, const char *str); bool sym_set_string_value(struct symbol *sym, const char *newval); -bool sym_is_changeable(struct symbol *sym); -struct property * sym_get_choice_prop(struct symbol *sym); +bool sym_is_changeable(const struct symbol *sym); +struct menu *sym_get_prompt_menu(const struct symbol *sym); +struct menu *sym_get_choice_menu(const struct symbol *sym); const char * sym_get_string_value(struct symbol *sym); const char * prop_get_type_name(enum prop_type type); /* expr.c */ -void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken); +void expr_print(const struct expr *e, + void (*fn)(void *, struct symbol *, const char *), + void *data, int prevtoken); #endif /* LKC_PROTO_H */ diff --git a/scripts/kconfig/lxdialog/checklist.c b/scripts/kconfig/lxdialog/checklist.c index 31d0a89fbeb7..75493302fb85 100644 --- a/scripts/kconfig/lxdialog/checklist.c +++ b/scripts/kconfig/lxdialog/checklist.c @@ -119,7 +119,7 @@ int dialog_checklist(const char *title, const char *prompt, int height, } do_resize: - if (getmaxy(stdscr) < (height + CHECKLIST_HEIGTH_MIN)) + if (getmaxy(stdscr) < (height + CHECKLIST_HEIGHT_MIN)) return -ERRDISPLAYTOOSMALL; if (getmaxx(stdscr) < (width + CHECKLIST_WIDTH_MIN)) return -ERRDISPLAYTOOSMALL; diff --git a/scripts/kconfig/lxdialog/dialog.h b/scripts/kconfig/lxdialog/dialog.h index 2d15ba893fbf..f6c2ebe6d1f9 100644 --- a/scripts/kconfig/lxdialog/dialog.h +++ b/scripts/kconfig/lxdialog/dialog.h @@ -162,17 +162,17 @@ int on_key_esc(WINDOW *win); int on_key_resize(void); /* minimum (re)size values */ -#define CHECKLIST_HEIGTH_MIN 6 /* For dialog_checklist() */ +#define CHECKLIST_HEIGHT_MIN 6 /* For dialog_checklist() */ #define CHECKLIST_WIDTH_MIN 6 -#define INPUTBOX_HEIGTH_MIN 2 /* For dialog_inputbox() */ +#define INPUTBOX_HEIGHT_MIN 2 /* For dialog_inputbox() */ #define INPUTBOX_WIDTH_MIN 2 -#define MENUBOX_HEIGTH_MIN 15 /* For dialog_menu() */ +#define MENUBOX_HEIGHT_MIN 15 /* For dialog_menu() */ #define MENUBOX_WIDTH_MIN 65 -#define TEXTBOX_HEIGTH_MIN 8 /* For dialog_textbox() */ +#define TEXTBOX_HEIGHT_MIN 8 /* For dialog_textbox() */ #define TEXTBOX_WIDTH_MIN 8 -#define YESNO_HEIGTH_MIN 4 /* For dialog_yesno() */ +#define YESNO_HEIGHT_MIN 4 /* For dialog_yesno() */ #define YESNO_WIDTH_MIN 4 -#define WINDOW_HEIGTH_MIN 19 /* For init_dialog() */ +#define WINDOW_HEIGHT_MIN 19 /* For init_dialog() */ #define WINDOW_WIDTH_MIN 80 int init_dialog(const char *backtitle); diff --git a/scripts/kconfig/lxdialog/inputbox.c b/scripts/kconfig/lxdialog/inputbox.c index 1dcfb288ee63..3c6e24b20f5b 100644 --- a/scripts/kconfig/lxdialog/inputbox.c +++ b/scripts/kconfig/lxdialog/inputbox.c @@ -43,7 +43,7 @@ int dialog_inputbox(const char *title, const char *prompt, int height, int width strcpy(instr, init); do_resize: - if (getmaxy(stdscr) <= (height - INPUTBOX_HEIGTH_MIN)) + if (getmaxy(stdscr) <= (height - INPUTBOX_HEIGHT_MIN)) return -ERRDISPLAYTOOSMALL; if (getmaxx(stdscr) <= (width - INPUTBOX_WIDTH_MIN)) return -ERRDISPLAYTOOSMALL; diff --git a/scripts/kconfig/lxdialog/menubox.c b/scripts/kconfig/lxdialog/menubox.c index 0e333284e947..6e6244df0c56 100644 --- a/scripts/kconfig/lxdialog/menubox.c +++ b/scripts/kconfig/lxdialog/menubox.c @@ -172,7 +172,7 @@ int dialog_menu(const char *title, const char *prompt, do_resize: height = getmaxy(stdscr); width = getmaxx(stdscr); - if (height < MENUBOX_HEIGTH_MIN || width < MENUBOX_WIDTH_MIN) + if (height < MENUBOX_HEIGHT_MIN || width < MENUBOX_WIDTH_MIN) return -ERRDISPLAYTOOSMALL; height -= 4; diff --git a/scripts/kconfig/lxdialog/textbox.c b/scripts/kconfig/lxdialog/textbox.c index 058ed0e5bbd5..0abaf635978f 100644 --- a/scripts/kconfig/lxdialog/textbox.c +++ b/scripts/kconfig/lxdialog/textbox.c @@ -175,7 +175,7 @@ int dialog_textbox(const char *title, const char *tbuf, int initial_height, do_resize: getmaxyx(stdscr, height, width); - if (height < TEXTBOX_HEIGTH_MIN || width < TEXTBOX_WIDTH_MIN) + if (height < TEXTBOX_HEIGHT_MIN || width < TEXTBOX_WIDTH_MIN) return -ERRDISPLAYTOOSMALL; if (initial_height != 0) height = initial_height; diff --git a/scripts/kconfig/lxdialog/util.c b/scripts/kconfig/lxdialog/util.c index 3fb7508b68a2..964139c87fcb 100644 --- a/scripts/kconfig/lxdialog/util.c +++ b/scripts/kconfig/lxdialog/util.c @@ -17,22 +17,13 @@ struct dialog_info dlg; static void set_mono_theme(void) { - dlg.screen.atr = A_NORMAL; - dlg.shadow.atr = A_NORMAL; - dlg.dialog.atr = A_NORMAL; dlg.title.atr = A_BOLD; - dlg.border.atr = A_NORMAL; dlg.button_active.atr = A_REVERSE; dlg.button_inactive.atr = A_DIM; dlg.button_key_active.atr = A_REVERSE; dlg.button_key_inactive.atr = A_BOLD; dlg.button_label_active.atr = A_REVERSE; - dlg.button_label_inactive.atr = A_NORMAL; - dlg.inputbox.atr = A_NORMAL; dlg.position_indicator.atr = A_BOLD; - dlg.menubox.atr = A_NORMAL; - dlg.menubox_border.atr = A_NORMAL; - dlg.item.atr = A_NORMAL; dlg.item_selected.atr = A_REVERSE; dlg.tag.atr = A_BOLD; dlg.tag_selected.atr = A_REVERSE; @@ -291,7 +282,7 @@ int init_dialog(const char *backtitle) getyx(stdscr, saved_y, saved_x); getmaxyx(stdscr, height, width); - if (height < WINDOW_HEIGTH_MIN || width < WINDOW_WIDTH_MIN) { + if (height < WINDOW_HEIGHT_MIN || width < WINDOW_WIDTH_MIN) { endwin(); return -ERRDISPLAYTOOSMALL; } diff --git a/scripts/kconfig/lxdialog/yesno.c b/scripts/kconfig/lxdialog/yesno.c index bcaac9b7bab2..b57d25e1549f 100644 --- a/scripts/kconfig/lxdialog/yesno.c +++ b/scripts/kconfig/lxdialog/yesno.c @@ -32,7 +32,7 @@ int dialog_yesno(const char *title, const char *prompt, int height, int width) WINDOW *dialog; do_resize: - if (getmaxy(stdscr) < (height + YESNO_HEIGTH_MIN)) + if (getmaxy(stdscr) < (height + YESNO_HEIGHT_MIN)) return -ERRDISPLAYTOOSMALL; if (getmaxx(stdscr) < (width + YESNO_WIDTH_MIN)) return -ERRDISPLAYTOOSMALL; diff --git a/scripts/kconfig/mconf-cfg.sh b/scripts/kconfig/mconf-cfg.sh index 1e61f50a5905..1bc304dc2f7d 100755 --- a/scripts/kconfig/mconf-cfg.sh +++ b/scripts/kconfig/mconf-cfg.sh @@ -1,6 +1,8 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 +set -eu + cflags=$1 libs=$2 diff --git a/scripts/kconfig/mconf.c b/scripts/kconfig/mconf.c index f4bb391d50cf..84ea9215c0a7 100644 --- a/scripts/kconfig/mconf.c +++ b/scripts/kconfig/mconf.c @@ -19,7 +19,8 @@ #include <signal.h> #include <unistd.h> -#include "list.h" +#include <list.h> +#include <xalloc.h> #include "lkc.h" #include "lxdialog/dialog.h" #include "mnconf-common.h" @@ -514,7 +515,7 @@ static void build_conf(struct menu *menu) type = sym_get_type(sym); if (sym_is_choice(sym)) { - struct symbol *def_sym = sym_get_choice_value(sym); + struct symbol *def_sym = sym_calc_choice(menu); struct menu *def_menu = NULL; child_count++; @@ -523,42 +524,14 @@ static void build_conf(struct menu *menu) def_menu = child; } - val = sym_get_tristate_value(sym); - if (sym_is_changeable(sym)) { - switch (type) { - case S_BOOLEAN: - item_make("[%c]", val == no ? ' ' : '*'); - break; - case S_TRISTATE: - switch (val) { - case yes: ch = '*'; break; - case mod: ch = 'M'; break; - default: ch = ' '; break; - } - item_make("<%c>", ch); - break; - } - item_set_tag('t'); - item_set_data(menu); - } else { - item_make(" "); - item_set_tag(def_menu ? 't' : ':'); - item_set_data(menu); - } + item_make(" "); + item_set_tag(def_menu ? 't' : ':'); + item_set_data(menu); item_add_str("%*c%s", indent + 1, ' ', menu_get_prompt(menu)); - if (val == yes) { - if (def_menu) { - item_add_str(" (%s)", menu_get_prompt(def_menu)); - item_add_str(" --->"); - if (def_menu->list) { - indent += 2; - build_conf(def_menu); - indent -= 2; - } - } - return; - } + if (def_menu) + item_add_str(" (%s) --->", menu_get_prompt(def_menu)); + return; } else { if (menu == current_menu) { item_make("---%*c%s", indent + 1, ' ', menu_get_prompt(menu)); @@ -568,49 +541,43 @@ static void build_conf(struct menu *menu) } child_count++; val = sym_get_tristate_value(sym); - if (sym_is_choice_value(sym) && val == yes) { - item_make(" "); - item_set_tag(':'); + switch (type) { + case S_BOOLEAN: + if (sym_is_changeable(sym)) + item_make("[%c]", val == no ? ' ' : '*'); + else + item_make("-%c-", val == no ? ' ' : '*'); + item_set_tag('t'); item_set_data(menu); - } else { - switch (type) { - case S_BOOLEAN: - if (sym_is_changeable(sym)) - item_make("[%c]", val == no ? ' ' : '*'); - else - item_make("-%c-", val == no ? ' ' : '*'); - item_set_tag('t'); - item_set_data(menu); - break; - case S_TRISTATE: - switch (val) { - case yes: ch = '*'; break; - case mod: ch = 'M'; break; - default: ch = ' '; break; - } - if (sym_is_changeable(sym)) { - if (sym->rev_dep.tri == mod) - item_make("{%c}", ch); - else - item_make("<%c>", ch); - } else - item_make("-%c-", ch); - item_set_tag('t'); - item_set_data(menu); - break; - default: - tmp = 2 + strlen(sym_get_string_value(sym)); /* () = 2 */ - item_make("(%s)", sym_get_string_value(sym)); - tmp = indent - tmp + 4; - if (tmp < 0) - tmp = 0; - item_add_str("%*c%s%s", tmp, ' ', menu_get_prompt(menu), - (sym_has_value(sym) || !sym_is_changeable(sym)) ? - "" : " (NEW)"); - item_set_tag('s'); - item_set_data(menu); - goto conf_childs; + break; + case S_TRISTATE: + switch (val) { + case yes: ch = '*'; break; + case mod: ch = 'M'; break; + default: ch = ' '; break; } + if (sym_is_changeable(sym)) { + if (sym->rev_dep.tri == mod) + item_make("{%c}", ch); + else + item_make("<%c>", ch); + } else + item_make("-%c-", ch); + item_set_tag('t'); + item_set_data(menu); + break; + default: + tmp = 2 + strlen(sym_get_string_value(sym)); /* () = 2 */ + item_make("(%s)", sym_get_string_value(sym)); + tmp = indent - tmp + 4; + if (tmp < 0) + tmp = 0; + item_add_str("%*c%s%s", tmp, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changeable(sym)) ? + "" : " (NEW)"); + item_set_tag('s'); + item_set_data(menu); + goto conf_childs; } item_add_str("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu), (sym_has_value(sym) || !sym_is_changeable(sym)) ? @@ -634,7 +601,7 @@ static void conf_choice(struct menu *menu) struct menu *child; struct symbol *active; - active = sym_get_choice_value(menu->sym); + active = sym_calc_choice(menu); while (1) { int res; int selected; @@ -653,15 +620,15 @@ static void conf_choice(struct menu *menu) item_set_data(child); if (child->sym == active) item_set_selected(1); - if (child->sym == sym_get_choice_value(menu->sym)) + if (child->sym == sym_calc_choice(menu)) item_set_tag('X'); } dialog_clear(); res = dialog_checklist(prompt ? prompt : "Main Menu", radiolist_instructions, - MENUBOX_HEIGTH_MIN, + MENUBOX_HEIGHT_MIN, MENUBOX_WIDTH_MIN, - CHECKLIST_HEIGTH_MIN); + CHECKLIST_HEIGHT_MIN); selected = item_activate_selected(); switch (res) { case 0: @@ -670,7 +637,7 @@ static void conf_choice(struct menu *menu) if (!child->sym) break; - sym_set_tristate_value(child->sym, yes); + choice_set_value(menu, child->sym); } return; case 1: @@ -834,7 +801,7 @@ static void conf(struct menu *menu, struct menu *active_menu) conf(submenu, NULL); break; case 't': - if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes) + if (sym_is_choice(sym)) conf_choice(submenu); else if (submenu->prompt->type == P_MENU) conf(submenu, NULL); diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 8498481e6afe..7d48a692bd27 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -8,16 +8,38 @@ #include <stdlib.h> #include <string.h> +#include <list.h> +#include <xalloc.h> #include "lkc.h" #include "internal.h" -#include "list.h" static const char nohelp_text[] = "There is no help available for this option."; -struct menu rootmenu; +struct menu rootmenu = { .type = M_MENU }; static struct menu **last_entry_ptr; -void menu_warn(struct menu *menu, const char *fmt, ...) +/** + * menu_next - return the next menu entry with depth-first traversal + * @menu: pointer to the current menu + * @root: root of the sub-tree to traverse. If NULL is given, the traveral + * continues until it reaches the end of the entire menu tree. + * return: the menu to visit next, or NULL when it reaches the end. + */ +struct menu *menu_next(struct menu *menu, struct menu *root) +{ + if (menu->list) + return menu->list; + + while (menu != root && !menu->next) + menu = menu->parent; + + if (menu == root) + return NULL; + + return menu->next; +} + +void menu_warn(const struct menu *menu, const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -27,7 +49,7 @@ void menu_warn(struct menu *menu, const char *fmt, ...) va_end(ap); } -static void prop_warn(struct property *prop, const char *fmt, ...) +static void prop_warn(const struct property *prop, const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -43,12 +65,13 @@ void _menu_init(void) last_entry_ptr = &rootmenu.list; } -void menu_add_entry(struct symbol *sym) +void menu_add_entry(struct symbol *sym, enum menu_type type) { struct menu *menu; menu = xmalloc(sizeof(*menu)); memset(menu, 0, sizeof(*menu)); + menu->type = type; menu->sym = sym; menu->parent = current_menu; menu->filename = cur_filename; @@ -57,10 +80,8 @@ void menu_add_entry(struct symbol *sym) *last_entry_ptr = menu; last_entry_ptr = &menu->next; current_entry = menu; - if (sym) { - menu_add_symbol(P_SYMBOL, sym, NULL); + if (sym) list_add_tail(&menu->link, &sym->menus); - } } struct menu *menu_add_menu(void) @@ -87,12 +108,13 @@ static struct expr *rewrite_m(struct expr *e) switch (e->type) { case E_NOT: - e->left.expr = rewrite_m(e->left.expr); + e = expr_alloc_one(E_NOT, rewrite_m(e->left.expr)); break; case E_OR: case E_AND: - e->left.expr = rewrite_m(e->left.expr); - e->right.expr = rewrite_m(e->right.expr); + e = expr_alloc_two(e->type, + rewrite_m(e->left.expr), + rewrite_m(e->right.expr)); break; case E_SYMBOL: /* change 'm' into 'm' && MODULES */ @@ -154,7 +176,7 @@ static struct property *menu_add_prop(enum prop_type type, struct expr *expr, return prop; } -struct property *menu_add_prompt(enum prop_type type, char *prompt, +struct property *menu_add_prompt(enum prop_type type, const char *prompt, struct expr *dep) { struct property *prop = menu_add_prop(type, NULL, dep); @@ -172,21 +194,11 @@ struct property *menu_add_prompt(enum prop_type type, char *prompt, struct menu *menu = current_entry; while ((menu = menu->parent) != NULL) { - struct expr *dup_expr; if (!menu->visibility) continue; - /* - * Do not add a reference to the menu's visibility - * expression but use a copy of it. Otherwise the - * expression reduction functions will modify - * expressions that have multiple references which - * can cause unwanted side effects. - */ - dup_expr = expr_copy(menu->visibility); - prop->visible.expr = expr_alloc_and(prop->visible.expr, - dup_expr); + menu->visibility); } } @@ -242,11 +254,9 @@ static void sym_check_prop(struct symbol *sym) sym->name); } if (sym_is_choice(sym)) { - struct property *choice_prop = - sym_get_choice_prop(sym2); + struct menu *choice = sym_get_choice_menu(sym2); - if (!choice_prop || - prop_get_symbol(choice_prop) != sym) + if (!choice || choice->sym != sym) prop_warn(prop, "choice default symbol '%s' is not contained in the choice", sym2->name); @@ -282,12 +292,12 @@ static void sym_check_prop(struct symbol *sym) } } -void menu_finalize(struct menu *parent) +static void _menu_finalize(struct menu *parent, bool inside_choice) { struct menu *menu, *last_menu; struct symbol *sym; struct property *prop; - struct expr *parentdep, *basedep, *dep, *dep2, **ep; + struct expr *basedep, *dep, *dep2; sym = parent->sym; if (parent->list) { @@ -296,30 +306,6 @@ void menu_finalize(struct menu *parent) * and propagate parent dependencies before moving on. */ - if (sym && sym_is_choice(sym)) { - if (sym->type == S_UNKNOWN) { - /* find the first choice value to find out choice type */ - current_entry = parent; - for (menu = parent->list; menu; menu = menu->next) { - if (menu->sym && menu->sym->type != S_UNKNOWN) { - menu_set_type(menu->sym->type); - break; - } - } - } - - /* - * Use the choice itself as the parent dependency of - * the contained items. This turns the mode of the - * choice into an upper bound on the visibility of the - * choice value symbols. - */ - parentdep = expr_alloc_symbol(sym); - } else { - /* Menu node for 'menu', 'if' */ - parentdep = parent->dep; - } - /* For each child menu node... */ for (menu = parent->list; menu; menu = menu->next) { /* @@ -328,7 +314,7 @@ void menu_finalize(struct menu *parent) */ basedep = rewrite_m(menu->dep); basedep = expr_transform(basedep); - basedep = expr_alloc_and(expr_copy(parentdep), basedep); + basedep = expr_alloc_and(parent->dep, basedep); basedep = expr_eliminate_dups(basedep); menu->dep = basedep; @@ -372,10 +358,8 @@ void menu_finalize(struct menu *parent) */ dep = rewrite_m(prop->visible.expr); dep = expr_transform(dep); - dep = expr_alloc_and(expr_copy(basedep), dep); + dep = expr_alloc_and(basedep, dep); dep = expr_eliminate_dups(dep); - if (menu->sym && menu->sym->type != S_TRISTATE) - dep = expr_trans_bool(dep); prop->visible.expr = dep; /* @@ -385,25 +369,22 @@ void menu_finalize(struct menu *parent) if (prop->type == P_SELECT) { struct symbol *es = prop_get_symbol(prop); es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr, - expr_alloc_and(expr_alloc_symbol(menu->sym), expr_copy(dep))); + expr_alloc_and(expr_alloc_symbol(menu->sym), dep)); } else if (prop->type == P_IMPLY) { struct symbol *es = prop_get_symbol(prop); es->implied.expr = expr_alloc_or(es->implied.expr, - expr_alloc_and(expr_alloc_symbol(menu->sym), expr_copy(dep))); + expr_alloc_and(expr_alloc_symbol(menu->sym), dep)); } } } - if (sym && sym_is_choice(sym)) - expr_free(parentdep); - /* * Recursively process children in the same fashion before * moving on */ for (menu = parent->list; menu; menu = menu->next) - menu_finalize(menu); - } else if (sym) { + _menu_finalize(menu, sym && sym_is_choice(sym)); + } else if (!inside_choice && sym) { /* * Automatic submenu creation. If sym is a symbol and A, B, C, * ... are consecutive items (symbols, menus, ifs, etc.) that @@ -452,22 +433,18 @@ void menu_finalize(struct menu *parent) */ dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no); dep = expr_eliminate_dups(expr_transform(dep)); - dep2 = expr_copy(basedep); + dep2 = basedep; expr_eliminate_eq(&dep, &dep2); - expr_free(dep); if (!expr_is_yes(dep2)) { /* Not superset, quit */ - expr_free(dep2); break; } /* Superset, put in submenu */ - expr_free(dep2); next: - menu_finalize(menu); + _menu_finalize(menu, false); menu->parent = parent; last_menu = menu; } - expr_free(basedep); if (last_menu) { parent->list = parent->next; parent->next = last_menu->next; @@ -477,46 +454,6 @@ void menu_finalize(struct menu *parent) sym->dir_dep.expr = expr_alloc_or(sym->dir_dep.expr, parent->dep); } for (menu = parent->list; menu; menu = menu->next) { - if (sym && sym_is_choice(sym) && - menu->sym && !sym_is_choice_value(menu->sym)) { - current_entry = menu; - menu->sym->flags |= SYMBOL_CHOICEVAL; - if (!menu->prompt) - menu_warn(menu, "choice value must have a prompt"); - for (prop = menu->sym->prop; prop; prop = prop->next) { - if (prop->type == P_DEFAULT) - prop_warn(prop, "defaults for choice " - "values not supported"); - if (prop->menu == menu) - continue; - if (prop->type == P_PROMPT && - prop->menu->parent->sym != sym) - prop_warn(prop, "choice value used outside its choice group"); - } - /* Non-tristate choice values of tristate choices must - * depend on the choice being set to Y. The choice - * values' dependencies were propagated to their - * properties above, so the change here must be re- - * propagated. - */ - if (sym->type == S_TRISTATE && menu->sym->type != S_TRISTATE) { - basedep = expr_alloc_comp(E_EQUAL, sym, &symbol_yes); - menu->dep = expr_alloc_and(basedep, menu->dep); - for (prop = menu->sym->prop; prop; prop = prop->next) { - if (prop->menu != menu) - continue; - prop->visible.expr = expr_alloc_and(expr_copy(basedep), - prop->visible.expr); - } - } - menu_add_symbol(P_CHOICE, sym, NULL); - prop = sym_get_choice_prop(sym); - for (ep = &prop->expr; *ep; ep = &(*ep)->left.expr) - ; - *ep = expr_alloc_one(E_LIST, NULL); - (*ep)->right.sym = menu->sym; - } - /* * This code serves two purposes: * @@ -565,24 +502,14 @@ void menu_finalize(struct menu *parent) sym_check_prop(sym); sym->flags |= SYMBOL_WARNED; } +} - /* - * For non-optional choices, add a reverse dependency (corresponding to - * a select) of '<visibility> && m'. This prevents the user from - * setting the choice mode to 'n' when the choice is visible. - * - * This would also work for non-choice symbols, but only non-optional - * choices clear SYMBOL_OPTIONAL as of writing. Choices are implemented - * as a type of symbol. - */ - if (sym && !sym_is_optional(sym) && parent->prompt) { - sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr, - expr_alloc_and(parent->prompt->visible.expr, - expr_alloc_symbol(&symbol_mod))); - } +void menu_finalize(void) +{ + _menu_finalize(&rootmenu, false); } -bool menu_has_prompt(struct menu *menu) +bool menu_has_prompt(const struct menu *menu) { if (!menu->prompt) return false; @@ -632,18 +559,14 @@ bool menu_is_visible(struct menu *menu) if (!sym || sym_get_tristate_value(menu->sym) == no) return false; - for (child = menu->list; child; child = child->next) { - if (menu_is_visible(child)) { - if (sym) - sym->flags |= SYMBOL_DEF_USER; + for (child = menu->list; child; child = child->next) + if (menu_is_visible(child)) return true; - } - } return false; } -const char *menu_get_prompt(struct menu *menu) +const char *menu_get_prompt(const struct menu *menu) { if (menu->prompt) return menu->prompt->text; @@ -664,13 +587,14 @@ struct menu *menu_get_parent_menu(struct menu *menu) return menu; } -static void get_def_str(struct gstr *r, struct menu *menu) +static void get_def_str(struct gstr *r, const struct menu *menu) { str_printf(r, "Defined at %s:%d\n", menu->filename, menu->lineno); } -static void get_dep_str(struct gstr *r, struct expr *expr, const char *prefix) +static void get_dep_str(struct gstr *r, const struct expr *expr, + const char *prefix) { if (!expr_is_yes(expr)) { str_append(r, prefix); diff --git a/scripts/kconfig/merge_config.sh b/scripts/kconfig/merge_config.sh index 902eb429b9db..79c09b378be8 100755 --- a/scripts/kconfig/merge_config.sh +++ b/scripts/kconfig/merge_config.sh @@ -112,8 +112,8 @@ INITFILE=$1 shift; if [ ! -r "$INITFILE" ]; then - echo "The base file '$INITFILE' does not exist. Exit." >&2 - exit 1 + echo "The base file '$INITFILE' does not exist. Creating one..." >&2 + touch "$INITFILE" fi MERGE_LIST=$* @@ -167,6 +167,8 @@ for ORIG_MERGE_FILE in $MERGE_LIST ; do sed -i "/$CFG[ =]/d" $MERGE_FILE fi done + # In case the previous file lacks a new line at the end + echo >> $TMP_FILE cat $MERGE_FILE >> $TMP_FILE done diff --git a/scripts/kconfig/mnconf-common.c b/scripts/kconfig/mnconf-common.c index 18cb9a6c5aaa..8e24b07121df 100644 --- a/scripts/kconfig/mnconf-common.c +++ b/scripts/kconfig/mnconf-common.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only +#include <list.h> #include "expr.h" -#include "list.h" #include "mnconf-common.h" int jump_key_char; diff --git a/scripts/kconfig/mnconf-common.h b/scripts/kconfig/mnconf-common.h index ab6292cc4bf2..53bd7292e931 100644 --- a/scripts/kconfig/mnconf-common.h +++ b/scripts/kconfig/mnconf-common.h @@ -4,6 +4,8 @@ #include <stddef.h> +#include <list_types.h> + struct search_data { struct list_head *head; struct menu *target; diff --git a/scripts/kconfig/nconf-cfg.sh b/scripts/kconfig/nconf-cfg.sh index f871a2160e36..a20290b1a37d 100755 --- a/scripts/kconfig/nconf-cfg.sh +++ b/scripts/kconfig/nconf-cfg.sh @@ -1,6 +1,8 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 +set -eu + cflags=$1 libs=$2 diff --git a/scripts/kconfig/nconf.c b/scripts/kconfig/nconf.c index 9d22b0f3197b..c0b2dabf6c89 100644 --- a/scripts/kconfig/nconf.c +++ b/scripts/kconfig/nconf.c @@ -11,7 +11,8 @@ #include <strings.h> #include <stdlib.h> -#include "list.h" +#include <list.h> +#include <xalloc.h> #include "lkc.h" #include "mnconf-common.h" #include "nconf.h" @@ -466,7 +467,7 @@ static void handle_f9(int *key, struct menu *current_item) return; } -/* return != 0 to indicate the key was handles */ +/* return != 0 to indicate the key was handled */ static int process_special_keys(int *key, struct menu *menu) { int i; @@ -815,7 +816,7 @@ static void build_conf(struct menu *menu) type = sym_get_type(sym); if (sym_is_choice(sym)) { - struct symbol *def_sym = sym_get_choice_value(sym); + struct symbol *def_sym = sym_calc_choice(menu); struct menu *def_menu = NULL; child_count++; @@ -825,46 +826,13 @@ static void build_conf(struct menu *menu) } val = sym_get_tristate_value(sym); - if (sym_is_changeable(sym)) { - switch (type) { - case S_BOOLEAN: - item_make(menu, 't', "[%c]", - val == no ? ' ' : '*'); - break; - case S_TRISTATE: - switch (val) { - case yes: - ch = '*'; - break; - case mod: - ch = 'M'; - break; - default: - ch = ' '; - break; - } - item_make(menu, 't', "<%c>", ch); - break; - } - } else { - item_make(menu, def_menu ? 't' : ':', " "); - } + item_make(menu, def_menu ? 't' : ':', " "); item_add_str("%*c%s", indent + 1, ' ', menu_get_prompt(menu)); - if (val == yes) { - if (def_menu) { - item_add_str(" (%s)", - menu_get_prompt(def_menu)); - item_add_str(" --->"); - if (def_menu->list) { - indent += 2; - build_conf(def_menu); - indent -= 2; - } - } - return; - } + if (def_menu) + item_add_str(" (%s) --->", menu_get_prompt(def_menu)); + return; } else { if (menu == current_menu) { item_make(menu, ':', @@ -874,54 +842,46 @@ static void build_conf(struct menu *menu) } child_count++; val = sym_get_tristate_value(sym); - if (sym_is_choice_value(sym) && val == yes) { - item_make(menu, ':', " "); - } else { - switch (type) { - case S_BOOLEAN: - if (sym_is_changeable(sym)) - item_make(menu, 't', "[%c]", - val == no ? ' ' : '*'); - else - item_make(menu, 't', "-%c-", - val == no ? ' ' : '*'); + switch (type) { + case S_BOOLEAN: + if (sym_is_changeable(sym)) + item_make(menu, 't', "[%c]", + val == no ? ' ' : '*'); + else + item_make(menu, 't', "-%c-", + val == no ? ' ' : '*'); + break; + case S_TRISTATE: + switch (val) { + case yes: + ch = '*'; break; - case S_TRISTATE: - switch (val) { - case yes: - ch = '*'; - break; - case mod: - ch = 'M'; - break; - default: - ch = ' '; - break; - } - if (sym_is_changeable(sym)) { - if (sym->rev_dep.tri == mod) - item_make(menu, - 't', "{%c}", ch); - else - item_make(menu, - 't', "<%c>", ch); - } else - item_make(menu, 't', "-%c-", ch); + case mod: + ch = 'M'; break; default: - tmp = 2 + strlen(sym_get_string_value(sym)); - item_make(menu, 's', " (%s)", - sym_get_string_value(sym)); - tmp = indent - tmp + 4; - if (tmp < 0) - tmp = 0; - item_add_str("%*c%s%s", tmp, ' ', - menu_get_prompt(menu), - (sym_has_value(sym) || - !sym_is_changeable(sym)) ? "" : - " (NEW)"); - goto conf_childs; + ch = ' '; + break; } + if (sym_is_changeable(sym)) { + if (sym->rev_dep.tri == mod) + item_make(menu, 't', "{%c}", ch); + else + item_make(menu, 't', "<%c>", ch); + } else + item_make(menu, 't', "-%c-", ch); + break; + default: + tmp = 2 + strlen(sym_get_string_value(sym)); + item_make(menu, 's', " (%s)", + sym_get_string_value(sym)); + tmp = indent - tmp + 4; + if (tmp < 0) + tmp = 0; + item_add_str("%*c%s%s", tmp, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || + !sym_is_changeable(sym)) ? "" : " (NEW)"); + goto conf_childs; } item_add_str("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu), @@ -1215,8 +1175,7 @@ static void selected_conf(struct menu *menu, struct menu *active_menu) conf(submenu); break; case 't': - if (sym_is_choice(sym) && - sym_get_tristate_value(sym) == yes) + if (sym_is_choice(sym)) conf_choice(submenu); else if (submenu->prompt && submenu->prompt->type == P_MENU) @@ -1281,7 +1240,7 @@ static void conf_choice(struct menu *menu) .pattern = "", }; - active = sym_get_choice_value(menu->sym); + active = sym_calc_choice(menu); /* this is mostly duplicated from the conf() function. */ while (!global_exit) { reset_menu(); @@ -1290,7 +1249,7 @@ static void conf_choice(struct menu *menu) if (!show_all_items && !menu_is_visible(child)) continue; - if (child->sym == sym_get_choice_value(menu->sym)) + if (child->sym == sym_calc_choice(menu)) item_make(child, ':', "<X> %s", menu_get_prompt(child)); else if (child->sym) @@ -1373,7 +1332,7 @@ static void conf_choice(struct menu *menu) case ' ': case 10: case KEY_RIGHT: - sym_set_tristate_value(child->sym, yes); + choice_set_value(menu, child->sym); return; case 'h': case '?': diff --git a/scripts/kconfig/nconf.gui.c b/scripts/kconfig/nconf.gui.c index 25a7263ef3c8..4bfdf8ac2a9a 100644 --- a/scripts/kconfig/nconf.gui.c +++ b/scripts/kconfig/nconf.gui.c @@ -4,6 +4,7 @@ * * Derived from menuconfig. */ +#include <xalloc.h> #include "nconf.h" #include "lkc.h" @@ -276,6 +277,15 @@ int btn_dialog(WINDOW *main_window, const char *msg, int btn_num, ...) case KEY_RIGHT: menu_driver(menu, REQ_RIGHT_ITEM); break; + case 9: /* TAB */ + if (btn_num > 1) { + /* cycle through buttons */ + if (item_index(current_item(menu)) == btn_num - 1) + menu_driver(menu, REQ_FIRST_ITEM); + else + menu_driver(menu, REQ_NEXT_ITEM); + } + break; case 10: /* ENTER */ case 27: /* ESCAPE */ case ' ': diff --git a/scripts/kconfig/parser.y b/scripts/kconfig/parser.y index b45bfaf0a02b..e9c3c664e925 100644 --- a/scripts/kconfig/parser.y +++ b/scripts/kconfig/parser.y @@ -11,6 +11,7 @@ #include <string.h> #include <stdbool.h> +#include <xalloc.h> #include "lkc.h" #include "internal.h" #include "preprocess.h" @@ -23,12 +24,11 @@ int cdebug = PRINTD; static void yyerror(const char *err); -static void zconfprint(const char *err, ...); static void zconf_error(const char *err, ...); static bool zconf_endtoken(const char *tokenname, const char *expected_tokenname); -struct menu *current_menu, *current_entry; +struct menu *current_menu, *current_entry, *current_choice; %} @@ -69,7 +69,6 @@ struct menu *current_menu, *current_entry; %token T_MODULES %token T_ON %token T_OPEN_PAREN -%token T_OPTIONAL %token T_PLUS_EQUAL %token T_PROMPT %token T_RANGE @@ -89,7 +88,7 @@ struct menu *current_menu, *current_entry; %type <symbol> nonconst_symbol %type <symbol> symbol -%type <type> type logic_type default +%type <type> type default %type <expr> expr %type <expr> if_expr %type <string> end @@ -140,20 +139,41 @@ stmt_list_in_choice: config_entry_start: T_CONFIG nonconst_symbol T_EOL { - $2->flags |= SYMBOL_OPTIONAL; - menu_add_entry($2); + menu_add_entry($2, M_NORMAL); printd(DEBUG_PARSE, "%s:%d:config %s\n", cur_filename, cur_lineno, $2->name); }; config_stmt: config_entry_start config_option_list { + if (current_choice) { + if (!current_entry->prompt) { + fprintf(stderr, "%s:%d: error: choice member must have a prompt\n", + current_entry->filename, current_entry->lineno); + yynerrs++; + } + + if (current_entry->sym->type != S_BOOLEAN) { + fprintf(stderr, "%s:%d: error: choice member must be bool\n", + current_entry->filename, current_entry->lineno); + yynerrs++; + } + + /* + * If the same symbol appears twice in a choice block, the list + * node would be added twice, leading to a broken linked list. + * list_empty() ensures that this symbol has not yet added. + */ + if (list_empty(¤t_entry->sym->choice_link)) + list_add_tail(¤t_entry->sym->choice_link, + ¤t_choice->choice_members); + } + printd(DEBUG_PARSE, "%s:%d:endconfig\n", cur_filename, cur_lineno); }; menuconfig_entry_start: T_MENUCONFIG nonconst_symbol T_EOL { - $2->flags |= SYMBOL_OPTIONAL; - menu_add_entry($2); + menu_add_entry($2, M_MENU); printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", cur_filename, cur_lineno, $2->name); }; @@ -162,7 +182,7 @@ menuconfig_stmt: menuconfig_entry_start config_option_list if (current_entry->prompt) current_entry->prompt->type = P_MENU; else - zconfprint("warning: menuconfig statement without prompt"); + zconf_error("menuconfig statement without prompt"); printd(DEBUG_PARSE, "%s:%d:endconfig\n", cur_filename, cur_lineno); }; @@ -224,10 +244,12 @@ config_option: T_MODULES T_EOL choice: T_CHOICE T_EOL { - struct symbol *sym = sym_lookup(NULL, SYMBOL_CHOICE); - sym->flags |= SYMBOL_NO_WRITE; - menu_add_entry(sym); - menu_add_expr(P_CHOICE, NULL, NULL); + struct symbol *sym = sym_lookup(NULL, 0); + + menu_add_entry(sym, M_CHOICE); + menu_set_type(S_BOOLEAN); + INIT_LIST_HEAD(¤t_entry->choice_members); + printd(DEBUG_PARSE, "%s:%d:choice\n", cur_filename, cur_lineno); }; @@ -240,10 +262,14 @@ choice_entry: choice choice_option_list } $$ = menu_add_menu(); + + current_choice = current_entry; }; choice_end: end { + current_choice = NULL; + if (zconf_endtoken($1, "choice")) { menu_end_menu(); printd(DEBUG_PARSE, "%s:%d:endchoice\n", cur_filename, cur_lineno); @@ -266,18 +292,6 @@ choice_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno); }; -choice_option: logic_type prompt_stmt_opt T_EOL -{ - menu_set_type($1); - printd(DEBUG_PARSE, "%s:%d:type(%u)\n", cur_filename, cur_lineno, $1); -}; - -choice_option: T_OPTIONAL T_EOL -{ - current_entry->sym->flags |= SYMBOL_OPTIONAL; - printd(DEBUG_PARSE, "%s:%d:optional\n", cur_filename, cur_lineno); -}; - choice_option: T_DEFAULT nonconst_symbol if_expr T_EOL { menu_add_symbol(P_DEFAULT, $2, $3); @@ -285,15 +299,12 @@ choice_option: T_DEFAULT nonconst_symbol if_expr T_EOL }; type: - logic_type + T_BOOL { $$ = S_BOOLEAN; } + | T_TRISTATE { $$ = S_TRISTATE; } | T_INT { $$ = S_INT; } | T_HEX { $$ = S_HEX; } | T_STRING { $$ = S_STRING; } -logic_type: - T_BOOL { $$ = S_BOOLEAN; } - | T_TRISTATE { $$ = S_TRISTATE; } - default: T_DEFAULT { $$ = S_UNKNOWN; } | T_DEF_BOOL { $$ = S_BOOLEAN; } @@ -304,7 +315,7 @@ default: if_entry: T_IF expr T_EOL { printd(DEBUG_PARSE, "%s:%d:if\n", cur_filename, cur_lineno); - menu_add_entry(NULL); + menu_add_entry(NULL, M_IF); menu_add_dep($2); $$ = menu_add_menu(); }; @@ -327,7 +338,7 @@ if_stmt_in_choice: if_entry stmt_list_in_choice if_end menu: T_MENU T_WORD_QUOTE T_EOL { - menu_add_entry(NULL); + menu_add_entry(NULL, M_MENU); menu_add_prompt(P_MENU, $2, NULL); printd(DEBUG_PARSE, "%s:%d:menu\n", cur_filename, cur_lineno); }; @@ -365,7 +376,7 @@ source_stmt: T_SOURCE T_WORD_QUOTE T_EOL comment: T_COMMENT T_WORD_QUOTE T_EOL { - menu_add_entry(NULL); + menu_add_entry(NULL, M_COMMENT); menu_add_prompt(P_COMMENT, $2, NULL); printd(DEBUG_PARSE, "%s:%d:comment\n", cur_filename, cur_lineno); }; @@ -390,14 +401,14 @@ help: help_start T_HELPTEXT { if (current_entry->help) { free(current_entry->help); - zconfprint("warning: '%s' defined with more than one help text -- only the last one will be used", - current_entry->sym->name ?: "<choice>"); + zconf_error("'%s' defined with more than one help text", + current_entry->sym->name ?: "<choice>"); } /* Is the help text empty or all whitespace? */ if ($2[strspn($2, " \f\n\r\t\v")] == '\0') - zconfprint("warning: '%s' defined with blank help text", - current_entry->sym->name ?: "<choice>"); + zconf_error("'%s' defined with blank help text", + current_entry->sym->name ?: "<choice>"); current_entry->help = $2; }; @@ -471,6 +482,38 @@ assign_val: %% +/** + * choice_check_sanity - check sanity of a choice member + * + * @menu: menu of the choice member + * + * Return: -1 if an error is found, 0 otherwise. + */ +static int choice_check_sanity(const struct menu *menu) +{ + struct property *prop; + int ret = 0; + + for (prop = menu->sym->prop; prop; prop = prop->next) { + if (prop->type == P_DEFAULT) { + fprintf(stderr, "%s:%d: error: %s", + prop->filename, prop->lineno, + "defaults for choice values not supported\n"); + ret = -1; + } + + if (prop->menu != menu && prop->type == P_PROMPT && + prop->menu->parent != menu->parent) { + fprintf(stderr, "%s:%d: error: %s", + prop->filename, prop->lineno, + "choice value has a prompt outside its choice group\n"); + ret = -1; + } + } + + return ret; +} + void conf_parse(const char *name) { struct menu *menu; @@ -487,14 +530,6 @@ void conf_parse(const char *name) yydebug = 1; yyparse(); - /* - * FIXME: - * cur_filename and cur_lineno are used even after yyparse(); - * menu_finalize() calls menu_add_symbol(). This should be fixed. - */ - cur_filename = "<none>"; - cur_lineno = 0; - str_printf(&autoconf_cmd, "\n" "$(autoconfig): $(deps_config)\n" @@ -515,22 +550,19 @@ void conf_parse(const char *name) menu_add_prompt(P_MENU, "Main menu", NULL); } - menu_finalize(&rootmenu); + menu_finalize(); + + menu_for_each_entry(menu) { + struct menu *child; - menu = &rootmenu; - while (menu) { if (menu->sym && sym_check_deps(menu->sym)) yynerrs++; - if (menu->list) { - menu = menu->list; - continue; + if (menu->sym && sym_is_choice(menu->sym)) { + menu_for_each_sub_entry(child, menu) + if (child->sym && choice_check_sanity(child)) + yynerrs++; } - - while (!menu->next && menu->parent) - menu = menu->parent; - - menu = menu->next; } if (yynerrs) @@ -559,17 +591,6 @@ static bool zconf_endtoken(const char *tokenname, return true; } -static void zconfprint(const char *err, ...) -{ - va_list ap; - - fprintf(stderr, "%s:%d: ", cur_filename, cur_lineno); - va_start(ap, err); - vfprintf(stderr, err, ap); - va_end(ap); - fprintf(stderr, "\n"); -} - static void zconf_error(const char *err, ...) { va_list ap; @@ -604,7 +625,7 @@ static void print_quoted_string(FILE *out, const char *str) putc('"', out); } -static void print_symbol(FILE *out, struct menu *menu) +static void print_symbol(FILE *out, const struct menu *menu) { struct symbol *sym = menu->sym; struct property *prop; @@ -655,9 +676,6 @@ static void print_symbol(FILE *out, struct menu *menu) } fputc('\n', out); break; - case P_CHOICE: - fputs(" #choice value\n", out); - break; case P_SELECT: fputs( " select ", out); expr_fprint(prop->expr, out); @@ -678,10 +696,6 @@ static void print_symbol(FILE *out, struct menu *menu) print_quoted_string(out, prop->text); fputc('\n', out); break; - case P_SYMBOL: - fputs( " symbol ", out); - fprintf(out, "%s\n", prop->menu->sym->name); - break; default: fprintf(out, " unknown prop %d!\n", prop->type); break; diff --git a/scripts/kconfig/preprocess.c b/scripts/kconfig/preprocess.c index f0a4a218c4a5..783abcaa5cc5 100644 --- a/scripts/kconfig/preprocess.c +++ b/scripts/kconfig/preprocess.c @@ -9,9 +9,10 @@ #include <stdlib.h> #include <string.h> -#include "array_size.h" +#include <array_size.h> +#include <list.h> +#include <xalloc.h> #include "internal.h" -#include "list.h" #include "lkc.h" #include "preprocess.h" diff --git a/scripts/kconfig/qconf-cfg.sh b/scripts/kconfig/qconf-cfg.sh index 0e113b0f2455..bb2df66363a8 100755 --- a/scripts/kconfig/qconf-cfg.sh +++ b/scripts/kconfig/qconf-cfg.sh @@ -1,6 +1,8 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 +set -eu + cflags=$1 libs=$2 bin=$3 diff --git a/scripts/kconfig/qconf.cc b/scripts/kconfig/qconf.cc index c6c42c0f4e5d..eaa465b0ccf9 100644 --- a/scripts/kconfig/qconf.cc +++ b/scripts/kconfig/qconf.cc @@ -22,6 +22,7 @@ #include <stdlib.h> +#include <xalloc.h> #include "lkc.h" #include "qconf.h" @@ -109,7 +110,7 @@ void ConfigItem::updateMenu(void) if (prop) switch (prop->type) { case P_MENU: - if (list->mode == singleMode || list->mode == symbolMode) { + if (list->mode == singleMode) { /* a menuconfig entry is displayed differently * depending whether it's at the view root or a child. */ @@ -147,7 +148,7 @@ void ConfigItem::updateMenu(void) expr = sym_get_tristate_value(sym); switch (expr) { case yes: - if (sym_is_choice_value(sym) && type == S_BOOLEAN) + if (sym_is_choice_value(sym)) setIcon(promptColIdx, choiceYesIcon); else setIcon(promptColIdx, symbolYesIcon); @@ -158,7 +159,7 @@ void ConfigItem::updateMenu(void) ch = 'M'; break; default: - if (sym_is_choice_value(sym) && type == S_BOOLEAN) + if (sym_is_choice_value(sym)) setIcon(promptColIdx, choiceNoIcon); else setIcon(promptColIdx, symbolNoIcon); @@ -174,17 +175,16 @@ void ConfigItem::updateMenu(void) setText(dataColIdx, sym_get_string_value(sym)); break; } - if (!sym_has_value(sym) && visible) + if (!sym_has_value(sym)) prompt += " (NEW)"; set_prompt: setText(promptColIdx, prompt); } -void ConfigItem::testUpdateMenu(bool v) +void ConfigItem::testUpdateMenu(void) { ConfigItem* i; - visible = v; if (!menu) return; @@ -306,7 +306,6 @@ ConfigList::ConfigList(QWidget *parent, const char *name) { setObjectName(name); setSortingEnabled(false); - setRootIsDecorated(true); setVerticalScrollMode(ScrollPerPixel); setHorizontalScrollMode(ScrollPerPixel); @@ -429,27 +428,26 @@ void ConfigList::updateList() item = (ConfigItem*)(*it); if (!item->menu) continue; - item->testUpdateMenu(menu_is_visible(item->menu)); + item->testUpdateMenu(); ++it; } return; } - if (rootEntry != &rootmenu && (mode == singleMode || - (mode == symbolMode && rootEntry->parent != &rootmenu))) { + if (rootEntry != &rootmenu && mode == singleMode) { item = (ConfigItem *)topLevelItem(0); if (!item) - item = new ConfigItem(this, 0, true); + item = new ConfigItem(this, 0); last = item; } if ((mode == singleMode || (mode == symbolMode && !(rootEntry->flags & MENU_ROOT))) && rootEntry->sym && rootEntry->prompt) { item = last ? last->nextSibling() : nullptr; if (!item) - item = new ConfigItem(this, last, rootEntry, true); + item = new ConfigItem(this, last, rootEntry); else - item->testUpdateMenu(true); + item->testUpdateMenu(); updateMenuList(item, rootEntry); update(); @@ -598,7 +596,6 @@ void ConfigList::updateMenuList(ConfigItem *parent, struct menu* menu) struct menu* child; ConfigItem* item; ConfigItem* last; - bool visible; enum prop_type type; if (!menu) { @@ -630,14 +627,13 @@ void ConfigList::updateMenuList(ConfigItem *parent, struct menu* menu) break; } - visible = menu_is_visible(child); if (!menuSkip(child)) { if (!child->sym && !child->list && !child->prompt) continue; if (!item || item->menu != child) - item = new ConfigItem(parent, last, child, visible); + item = new ConfigItem(parent, last, child); else - item->testUpdateMenu(visible); + item->testUpdateMenu(); if (mode == fullMode || mode == menuMode || type != P_MENU) updateMenuList(item, child); @@ -663,7 +659,6 @@ void ConfigList::updateMenuList(struct menu *menu) struct menu* child; ConfigItem* item; ConfigItem* last; - bool visible; enum prop_type type; if (!menu) { @@ -695,14 +690,13 @@ void ConfigList::updateMenuList(struct menu *menu) break; } - visible = menu_is_visible(child); if (!menuSkip(child)) { if (!child->sym && !child->list && !child->prompt) continue; if (!item || item->menu != child) - item = new ConfigItem(this, last, child, visible); + item = new ConfigItem(this, last, child); else - item->testUpdateMenu(visible); + item->testUpdateMenu(); if (mode == fullMode || mode == menuMode || type != P_MENU) updateMenuList(item, child); @@ -730,7 +724,7 @@ void ConfigList::keyPressEvent(QKeyEvent* ev) struct menu *menu; enum prop_type type; - if (ev->key() == Qt::Key_Escape && mode != fullMode && mode != listMode) { + if (ev->key() == Qt::Key_Escape && mode == singleMode) { emit parentSelected(); ev->accept(); return; @@ -780,13 +774,6 @@ void ConfigList::keyPressEvent(QKeyEvent* ev) ev->accept(); } -void ConfigList::mousePressEvent(QMouseEvent* e) -{ - //QPoint p(contentsToViewport(e->pos())); - //printf("contentsMousePressEvent: %d,%d\n", p.x(), p.y()); - Parent::mousePressEvent(e); -} - void ConfigList::mouseReleaseEvent(QMouseEvent* e) { QPoint p = e->pos(); @@ -833,13 +820,6 @@ skip: Parent::mouseReleaseEvent(e); } -void ConfigList::mouseMoveEvent(QMouseEvent* e) -{ - //QPoint p(contentsToViewport(e->pos())); - //printf("contentsMouseMoveEvent: %d,%d\n", p.x(), p.y()); - Parent::mouseMoveEvent(e); -} - void ConfigList::mouseDoubleClickEvent(QMouseEvent* e) { QPoint p = e->pos(); @@ -1021,7 +1001,7 @@ void ConfigInfoView::menuInfo(void) if (sym->name) { stream << " ("; if (showDebug()) - stream << "<a href=\"s" << sym->name << "\">"; + stream << "<a href=\"" << sym->name << "\">"; stream << print_filter(sym->name); if (showDebug()) stream << "</a>"; @@ -1030,7 +1010,7 @@ void ConfigInfoView::menuInfo(void) } else if (sym->name) { stream << "<big><b>"; if (showDebug()) - stream << "<a href=\"s" << sym->name << "\">"; + stream << "<a href=\"" << sym->name << "\">"; stream << print_filter(sym->name); if (showDebug()) stream << "</a>"; @@ -1085,30 +1065,21 @@ QString ConfigInfoView::debug_info(struct symbol *sym) switch (prop->type) { case P_PROMPT: case P_MENU: - stream << "prompt: <a href=\"m" << sym->name << "\">"; + stream << "prompt: "; stream << print_filter(prop->text); - stream << "</a><br>"; + stream << "<br>"; break; case P_DEFAULT: case P_SELECT: case P_RANGE: case P_COMMENT: case P_IMPLY: - case P_SYMBOL: stream << prop_get_type_name(prop->type); stream << ": "; expr_print(prop->expr, expr_print_help, &stream, E_NONE); stream << "<br>"; break; - case P_CHOICE: - if (sym_is_choice(sym)) { - stream << "choice: "; - expr_print(prop->expr, expr_print_help, - &stream, E_NONE); - stream << "<br>"; - } - break; default: stream << "unknown property: "; stream << prop_get_type_name(prop->type); @@ -1130,28 +1101,19 @@ QString ConfigInfoView::print_filter(const QString &str) { QRegularExpression re("[<>&\"\\n]"); QString res = str; + + QHash<QChar, QString> patterns; + patterns['<'] = "<"; + patterns['>'] = ">"; + patterns['&'] = "&"; + patterns['"'] = """; + patterns['\n'] = "<br>"; + for (int i = 0; (i = res.indexOf(re, i)) >= 0;) { - switch (res[i].toLatin1()) { - case '<': - res.replace(i, 1, "<"); - i += 4; - break; - case '>': - res.replace(i, 1, ">"); - i += 4; - break; - case '&': - res.replace(i, 1, "&"); - i += 5; - break; - case '"': - res.replace(i, 1, """); - i += 6; - break; - case '\n': - res.replace(i, 1, "<br>"); - i += 4; - break; + const QString n = patterns.value(res[i], QString()); + if (!n.isEmpty()) { + res.replace(i, 1, n); + i += n.length(); } } return res; @@ -1162,7 +1124,7 @@ void ConfigInfoView::expr_print_help(void *data, struct symbol *sym, const char QTextStream *stream = reinterpret_cast<QTextStream *>(data); if (sym && sym->name && !(sym->flags & SYMBOL_CONST)) { - *stream << "<a href=\"s" << sym->name << "\">"; + *stream << "<a href=\"" << sym->name << "\">"; *stream << print_filter(str); *stream << "</a>"; } else { @@ -1172,39 +1134,11 @@ void ConfigInfoView::expr_print_help(void *data, struct symbol *sym, const char void ConfigInfoView::clicked(const QUrl &url) { - QByteArray str = url.toEncoded(); - const std::size_t count = str.size(); - char *data = new char[count + 1]; - struct symbol **result; - struct menu *m = NULL; - - if (count < 1) { - delete[] data; - return; - } + struct menu *m; - memcpy(data, str.constData(), count); - data[count] = '\0'; - - /* Seek for exact match */ - data[0] = '^'; - strcat(data, "$"); - result = sym_re_search(data); - if (!result) { - delete[] data; - return; - } - - sym = *result; - - /* Seek for the menu which holds the symbol */ - for (struct property *prop = sym->prop; prop; prop = prop->next) { - if (prop->type != P_PROMPT && prop->type != P_MENU) - continue; - m = prop->menu; - break; - } + sym = sym_find(url.toEncoded().constData()); + m = sym_get_prompt_menu(sym); if (!m) { /* Symbol is not visible as a menu */ symbolInfo(); @@ -1212,9 +1146,6 @@ void ConfigInfoView::clicked(const QUrl &url) } else { emit menuSelected(m); } - - free(result); - delete[] data; } void ConfigInfoView::contextMenuEvent(QContextMenuEvent *event) @@ -1248,8 +1179,7 @@ ConfigSearchWindow::ConfigSearchWindow(ConfigMainWindow *parent) layout2->addWidget(searchButton); layout1->addLayout(layout2); - split = new QSplitter(this); - split->setOrientation(Qt::Vertical); + split = new QSplitter(Qt::Vertical, this); list = new ConfigList(split, "search"); list->mode = listMode; info = new ConfigInfoView(split, "search"); @@ -1308,8 +1238,7 @@ void ConfigSearchWindow::search(void) return; for (p = result; *p; p++) { for_all_prompts((*p), prop) - lastItem = new ConfigItem(list, lastItem, prop->menu, - menu_is_visible(prop->menu)); + lastItem = new ConfigItem(list, lastItem, prop->menu); } } @@ -1349,63 +1278,56 @@ ConfigMainWindow::ConfigMainWindow(void) ConfigItem::menubackIcon = QIcon(QPixmap(xpm_menuback)); QWidget *widget = new QWidget(this); - QVBoxLayout *layout = new QVBoxLayout(widget); setCentralWidget(widget); - split1 = new QSplitter(widget); - split1->setOrientation(Qt::Horizontal); - split1->setChildrenCollapsible(false); - - menuList = new ConfigList(widget, "menu"); + QVBoxLayout *layout = new QVBoxLayout(widget); - split2 = new QSplitter(widget); + split2 = new QSplitter(Qt::Vertical, widget); + layout->addWidget(split2); split2->setChildrenCollapsible(false); - split2->setOrientation(Qt::Vertical); - // create config tree - configList = new ConfigList(widget, "config"); + split1 = new QSplitter(Qt::Horizontal, split2); + split1->setChildrenCollapsible(false); - helpText = new ConfigInfoView(widget, "help"); + configList = new ConfigList(split1, "config"); - layout->addWidget(split2); - split2->addWidget(split1); - split1->addWidget(configList); - split1->addWidget(menuList); - split2->addWidget(helpText); + menuList = new ConfigList(split1, "menu"); + helpText = new ConfigInfoView(split2, "help"); setTabOrder(configList, helpText); + configList->setFocus(); backAction = new QAction(QPixmap(xpm_back), "Back", this); + backAction->setShortcut(QKeySequence::Back); connect(backAction, &QAction::triggered, this, &ConfigMainWindow::goBack); QAction *quitAction = new QAction("&Quit", this); - quitAction->setShortcut(Qt::CTRL | Qt::Key_Q); + quitAction->setShortcut(QKeySequence::Quit); connect(quitAction, &QAction::triggered, this, &ConfigMainWindow::close); - QAction *loadAction = new QAction(QPixmap(xpm_load), "&Load", this); - loadAction->setShortcut(Qt::CTRL | Qt::Key_L); + QAction *loadAction = new QAction(QPixmap(xpm_load), "&Open", this); + loadAction->setShortcut(QKeySequence::Open); connect(loadAction, &QAction::triggered, this, &ConfigMainWindow::loadConfig); saveAction = new QAction(QPixmap(xpm_save), "&Save", this); - saveAction->setShortcut(Qt::CTRL | Qt::Key_S); + saveAction->setShortcut(QKeySequence::Save); connect(saveAction, &QAction::triggered, this, &ConfigMainWindow::saveConfig); conf_set_changed_callback(conf_changed); - // Set saveAction's initial state - conf_changed(); - configname = xstrdup(conf_get_configname()); + configname = conf_get_configname(); QAction *saveAsAction = new QAction("Save &As...", this); + saveAsAction->setShortcut(QKeySequence::SaveAs); connect(saveAsAction, &QAction::triggered, this, &ConfigMainWindow::saveConfigAs); QAction *searchAction = new QAction("&Find", this); - searchAction->setShortcut(Qt::CTRL | Qt::Key_F); + searchAction->setShortcut(QKeySequence::Find); connect(searchAction, &QAction::triggered, this, &ConfigMainWindow::searchConfig); singleViewAction = new QAction(QPixmap(xpm_single_view), "Single View", this); @@ -1515,6 +1437,11 @@ ConfigMainWindow::ConfigMainWindow(void) connect(helpText, &ConfigInfoView::menuSelected, this, &ConfigMainWindow::setMenuLink); + connect(configApp, &QApplication::aboutToQuit, + this, &ConfigMainWindow::saveSettings); + + conf_read(NULL); + QString listMode = configSettings->value("/listMode", "symbol").toString(); if (listMode == "single") showSingleView(); @@ -1536,28 +1463,22 @@ ConfigMainWindow::ConfigMainWindow(void) void ConfigMainWindow::loadConfig(void) { QString str; - QByteArray ba; - const char *name; - str = QFileDialog::getOpenFileName(this, "", configname); - if (str.isNull()) + str = QFileDialog::getOpenFileName(this, QString(), configname); + if (str.isEmpty()) return; - ba = str.toLocal8Bit(); - name = ba.data(); - - if (conf_read(name)) + if (conf_read(str.toLocal8Bit().constData())) QMessageBox::information(this, "qconf", "Unable to load configuration!"); - free(configname); - configname = xstrdup(name); + configname = str; ConfigList::updateListAllForAll(); } bool ConfigMainWindow::saveConfig(void) { - if (conf_write(configname)) { + if (conf_write(configname.toLocal8Bit().constData())) { QMessageBox::information(this, "qconf", "Unable to save configuration!"); return false; } @@ -1569,23 +1490,17 @@ bool ConfigMainWindow::saveConfig(void) void ConfigMainWindow::saveConfigAs(void) { QString str; - QByteArray ba; - const char *name; - str = QFileDialog::getSaveFileName(this, "", configname); - if (str.isNull()) + str = QFileDialog::getSaveFileName(this, QString(), configname); + if (str.isEmpty()) return; - ba = str.toLocal8Bit(); - name = ba.data(); - - if (conf_write(name)) { + if (conf_write(str.toLocal8Bit().constData())) { QMessageBox::information(this, "qconf", "Unable to save configuration!"); } conf_write_autoconf(0); - free(configname); - configname = xstrdup(name); + configname = str; } void ConfigMainWindow::searchConfig(void) @@ -1670,9 +1585,6 @@ void ConfigMainWindow::listFocusChanged(void) void ConfigMainWindow::goBack(void) { - if (configList->rootEntry == &rootmenu) - return; - configList->setParentMenu(); } @@ -1851,10 +1763,10 @@ void ConfigMainWindow::saveSettings(void) configSettings->writeSizes("/split2", split2->sizes()); } -void ConfigMainWindow::conf_changed(void) +void ConfigMainWindow::conf_changed(bool dirty) { if (saveAction) - saveAction->setEnabled(conf_get_changed()); + saveAction->setEnabled(dirty); } void fixup_rootmenu(struct menu *menu) @@ -1904,7 +1816,6 @@ int main(int ac, char** av) conf_parse(name); fixup_rootmenu(&rootmenu); - conf_read(NULL); //zconfdump(stdout); configApp = new QApplication(ac, av); @@ -1914,8 +1825,7 @@ int main(int ac, char** av) v = new ConfigMainWindow(); //zconfdump(stdout); - configApp->connect(configApp, SIGNAL(lastWindowClosed()), SLOT(quit())); - configApp->connect(configApp, SIGNAL(aboutToQuit()), v, SLOT(saveSettings())); + v->show(); configApp->exec(); diff --git a/scripts/kconfig/qconf.h b/scripts/kconfig/qconf.h index 78b0a1dfcd53..62ab3286d04f 100644 --- a/scripts/kconfig/qconf.h +++ b/scripts/kconfig/qconf.h @@ -55,9 +55,7 @@ public: protected: void keyPressEvent(QKeyEvent *e); - void mousePressEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); void mouseDoubleClickEvent(QMouseEvent *e); void focusInEvent(QFocusEvent *e); void contextMenuEvent(QContextMenuEvent *e); @@ -116,25 +114,25 @@ public: class ConfigItem : public QTreeWidgetItem { typedef class QTreeWidgetItem Parent; public: - ConfigItem(ConfigList *parent, ConfigItem *after, struct menu *m, bool v) - : Parent(parent, after), nextItem(0), menu(m), visible(v), goParent(false) + ConfigItem(ConfigList *parent, ConfigItem *after, struct menu *m) + : Parent(parent, after), nextItem(0), menu(m), goParent(false) { init(); } - ConfigItem(ConfigItem *parent, ConfigItem *after, struct menu *m, bool v) - : Parent(parent, after), nextItem(0), menu(m), visible(v), goParent(false) + ConfigItem(ConfigItem *parent, ConfigItem *after, struct menu *m) + : Parent(parent, after), nextItem(0), menu(m), goParent(false) { init(); } - ConfigItem(ConfigList *parent, ConfigItem *after, bool v) - : Parent(parent, after), nextItem(0), menu(0), visible(v), goParent(true) + ConfigItem(ConfigList *parent, ConfigItem *after) + : Parent(parent, after), nextItem(0), menu(0), goParent(true) { init(); } ~ConfigItem(void); void init(void); void updateMenu(void); - void testUpdateMenu(bool v); + void testUpdateMenu(void); ConfigList* listView() const { return (ConfigList*)Parent::treeWidget(); @@ -161,7 +159,6 @@ public: ConfigItem* nextItem; struct menu *menu; - bool visible; bool goParent; static QIcon symbolYesIcon, symbolModIcon, symbolNoIcon; @@ -237,9 +234,9 @@ protected: class ConfigMainWindow : public QMainWindow { Q_OBJECT - char *configname; + QString configname; static QAction *saveAction; - static void conf_changed(void); + static void conf_changed(bool); public: ConfigMainWindow(void); public slots: diff --git a/scripts/kconfig/streamline_config.pl b/scripts/kconfig/streamline_config.pl index d51cd7ac15d2..8e23faab5d22 100755 --- a/scripts/kconfig/streamline_config.pl +++ b/scripts/kconfig/streamline_config.pl @@ -144,6 +144,7 @@ my %selects; my %prompts; my %objects; my %config2kfile; +my %defaults; my $var; my $iflevel = 0; my @ifdeps; @@ -220,8 +221,9 @@ sub read_kconfig { $depends{$config} = $1; } elsif ($state eq "DEP" && /^\s*depends\s+on\s+(.*)$/) { $depends{$config} .= " " . $1; - } elsif ($state eq "DEP" && /^\s*def(_(bool|tristate)|ault)\s+(\S.*)$/) { + } elsif ($state ne "NONE" && /^\s*def(_(bool|tristate)|ault)\s+(\S.*)$/) { my $dep = $3; + $defaults{$config} = 1; if ($dep !~ /^\s*(y|m|n)\s*$/) { $dep =~ s/.*\sif\s+//; $depends{$config} .= " " . $dep; @@ -503,7 +505,7 @@ sub parse_config_selects # Check if something other than a module selects this config if (defined($orig_configs{$conf}) && $orig_configs{$conf} ne "m") { - dprint "$conf (non module) selects config, we are good\n"; + dprint "$conf (non module) selects $config, we are good\n"; # we are good with this return; } @@ -523,8 +525,16 @@ sub parse_config_selects # If no possible config selected this, then something happened. if (!defined($next_config)) { - print STDERR "WARNING: $config is required, but nothing in the\n"; - print STDERR " current config selects it.\n"; + + # Some config options have no prompt, and nothing selects them, but + # they stay turned on once the final checks for the configs + # are done. These configs have a default option, so turn off the + # warnings for configs with default options. + if (!defined($defaults{$config})) { + print STDERR "WARNING: $config is required, but nothing in the\n"; + print STDERR " current config selects it.\n"; + } + return; } diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index 81fe1884ef8a..d57f8cbba291 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -9,11 +9,14 @@ #include <string.h> #include <regex.h> +#include <hash.h> +#include <xalloc.h> #include "internal.h" #include "lkc.h" struct symbol symbol_yes = { .name = "y", + .type = S_TRISTATE, .curr = { "y", yes }, .menus = LIST_HEAD_INIT(symbol_yes.menus), .flags = SYMBOL_CONST|SYMBOL_VALID, @@ -21,6 +24,7 @@ struct symbol symbol_yes = { struct symbol symbol_mod = { .name = "m", + .type = S_TRISTATE, .curr = { "m", mod }, .menus = LIST_HEAD_INIT(symbol_mod.menus), .flags = SYMBOL_CONST|SYMBOL_VALID, @@ -28,6 +32,7 @@ struct symbol symbol_mod = { struct symbol symbol_no = { .name = "n", + .type = S_TRISTATE, .curr = { "n", no }, .menus = LIST_HEAD_INIT(symbol_no.menus), .flags = SYMBOL_CONST|SYMBOL_VALID, @@ -37,16 +42,12 @@ struct symbol *modules_sym; static tristate modules_val; static int sym_warnings; -enum symbol_type sym_get_type(struct symbol *sym) +enum symbol_type sym_get_type(const struct symbol *sym) { enum symbol_type type = sym->type; - if (type == S_TRISTATE) { - if (sym_is_choice_value(sym) && sym->visible == yes) - type = S_BOOLEAN; - else if (modules_val == no) - type = S_BOOLEAN; - } + if (type == S_TRISTATE && modules_val == no) + type = S_BOOLEAN; return type; } @@ -69,12 +70,50 @@ const char *sym_type_name(enum symbol_type type) return "???"; } -struct property *sym_get_choice_prop(struct symbol *sym) +/** + * sym_get_prompt_menu - get the menu entry with a prompt + * + * @sym: a symbol pointer + * + * Return: the menu entry with a prompt. + */ +struct menu *sym_get_prompt_menu(const struct symbol *sym) { - struct property *prop; + struct menu *m; + + list_for_each_entry(m, &sym->menus, link) + if (m->prompt) + return m; + + return NULL; +} + +/** + * sym_get_choice_menu - get the parent choice menu if present + * + * @sym: a symbol pointer + * + * Return: a choice menu if this function is called against a choice member. + */ +struct menu *sym_get_choice_menu(const struct symbol *sym) +{ + struct menu *menu = NULL; + + /* + * Choice members must have a prompt. Find a menu entry with a prompt, + * and assume it resides inside a choice block. + */ + menu = sym_get_prompt_menu(sym); + if (!menu) + return NULL; + + do { + menu = menu->parent; + } while (menu && !menu->sym); + + if (menu && menu->sym && sym_is_choice(menu->sym)) + return menu; - for_all_choices(sym, prop) - return prop; return NULL; } @@ -152,13 +191,10 @@ static void sym_validate_range(struct symbol *sym) static void sym_set_changed(struct symbol *sym) { - struct property *prop; + struct menu *menu; - sym->flags |= SYMBOL_CHANGED; - for (prop = sym->prop; prop; prop = prop->next) { - if (prop->menu) - prop->menu->flags |= MENU_CHANGED; - } + list_for_each_entry(menu, &sym->menus, link) + menu->flags |= MENU_CHANGED; } static void sym_set_all_changed(void) @@ -172,26 +208,12 @@ static void sym_set_all_changed(void) static void sym_calc_visibility(struct symbol *sym) { struct property *prop; - struct symbol *choice_sym = NULL; tristate tri; /* any prompt visible? */ tri = no; - - if (sym_is_choice_value(sym)) - choice_sym = prop_get_symbol(sym_get_choice_prop(sym)); - for_all_prompts(sym, prop) { prop->visible.tri = expr_calc_value(prop->visible.expr); - /* - * Tristate choice_values with visibility 'mod' are - * not visible if the corresponding choice's value is - * 'yes'. - */ - if (choice_sym && sym->type == S_TRISTATE && - prop->visible.tri == mod && choice_sym->curr.tri == yes) - prop->visible.tri = no; - tri = EXPR_OR(tri, prop->visible.tri); } if (tri == mod && (sym->type != S_TRISTATE || modules_val == no)) @@ -238,14 +260,14 @@ static void sym_calc_visibility(struct symbol *sym) * Next locate the first visible choice value * Return NULL if none was found */ -struct symbol *sym_choice_default(struct symbol *sym) +struct symbol *sym_choice_default(struct menu *choice) { + struct menu *menu; struct symbol *def_sym; struct property *prop; - struct expr *e; /* any of the defaults visible? */ - for_all_defaults(sym, prop) { + for_all_defaults(choice->sym, prop) { prop->visible.tri = expr_calc_value(prop->visible.expr); if (prop->visible.tri == no) continue; @@ -255,48 +277,99 @@ struct symbol *sym_choice_default(struct symbol *sym) } /* just get the first visible value */ - prop = sym_get_choice_prop(sym); - expr_list_for_each_sym(prop->expr, e, def_sym) - if (def_sym->visible != no) - return def_sym; + menu_for_each_sub_entry(menu, choice) + if (menu->sym && menu->sym->visible != no) + return menu->sym; /* failed to locate any defaults */ return NULL; } -static struct symbol *sym_calc_choice(struct symbol *sym) +/* + * sym_calc_choice - calculate symbol values in a choice + * + * @choice: a menu of the choice + * + * Return: a chosen symbol + */ +struct symbol *sym_calc_choice(struct menu *choice) { - struct symbol *def_sym; - struct property *prop; - struct expr *e; - int flags; - - /* first calculate all choice values' visibilities */ - flags = sym->flags; - prop = sym_get_choice_prop(sym); - expr_list_for_each_sym(prop->expr, e, def_sym) { - sym_calc_visibility(def_sym); - if (def_sym->visible != no) - flags &= def_sym->flags; + struct symbol *res = NULL; + struct symbol *sym; + struct menu *menu; + + /* Traverse the list of choice members in the priority order. */ + list_for_each_entry(sym, &choice->choice_members, choice_link) { + sym_calc_visibility(sym); + if (sym->visible == no) + continue; + + /* The first visible symble with the user value 'y'. */ + if (sym_has_value(sym) && sym->def[S_DEF_USER].tri == yes) { + res = sym; + break; + } } - sym->flags &= flags | ~SYMBOL_DEF_USER; + /* + * If 'y' is not found in the user input, use the default, unless it is + * explicitly set to 'n'. + */ + if (!res) { + res = sym_choice_default(choice); + if (res && sym_has_value(res) && res->def[S_DEF_USER].tri == no) + res = NULL; + } - /* is the user choice visible? */ - def_sym = sym->def[S_DEF_USER].val; - if (def_sym && def_sym->visible != no) - return def_sym; + /* Still not found. Pick up the first visible, user-unspecified symbol. */ + if (!res) { + menu_for_each_sub_entry(menu, choice) { + sym = menu->sym; - def_sym = sym_choice_default(sym); + if (!sym || sym->visible == no || sym_has_value(sym)) + continue; - if (def_sym == NULL) - /* no choice? reset tristate value */ - sym->curr.tri = no; + res = sym; + break; + } + } + + /* + * Still not found. Traverse the linked list in the _reverse_ order to + * pick up the least prioritized 'n'. + */ + if (!res) { + list_for_each_entry_reverse(sym, &choice->choice_members, + choice_link) { + if (sym->visible == no) + continue; + + res = sym; + break; + } + } + + menu_for_each_sub_entry(menu, choice) { + tristate val; + + sym = menu->sym; + + if (!sym || sym->visible == no) + continue; + + val = sym == res ? yes : no; - return def_sym; + if (sym->curr.tri != val) + sym_set_changed(sym); + + sym->curr.tri = val; + sym->flags |= SYMBOL_VALID | SYMBOL_WRITE; + } + + return res; } -static void sym_warn_unmet_dep(struct symbol *sym) +static void sym_warn_unmet_dep(const struct symbol *sym) { struct gstr gs = str_new(); @@ -315,6 +388,7 @@ static void sym_warn_unmet_dep(struct symbol *sym) " Selected by [m]:\n"); fputs(str_get(&gs), stderr); + str_free(&gs); sym_warnings++; } @@ -329,7 +403,7 @@ void sym_calc_value(struct symbol *sym) { struct symbol_value newval, oldval; struct property *prop; - struct expr *e; + struct menu *choice_menu; if (!sym) return; @@ -337,13 +411,6 @@ void sym_calc_value(struct symbol *sym) if (sym->flags & SYMBOL_VALID) return; - if (sym_is_choice_value(sym) && - sym->flags & SYMBOL_NEED_SET_CHOICE_VALUES) { - sym->flags &= ~SYMBOL_NEED_SET_CHOICE_VALUES; - prop = sym_get_choice_prop(sym); - sym_calc_value(prop_get_symbol(prop)); - } - sym->flags |= SYMBOL_VALID; oldval = sym->curr; @@ -382,9 +449,11 @@ void sym_calc_value(struct symbol *sym) switch (sym_get_type(sym)) { case S_BOOLEAN: case S_TRISTATE: - if (sym_is_choice_value(sym) && sym->visible == yes) { - prop = sym_get_choice_prop(sym); - newval.tri = (prop_get_symbol(prop)->curr.val == sym) ? yes : no; + choice_menu = sym_get_choice_menu(sym); + + if (choice_menu) { + sym_calc_choice(choice_menu); + newval.tri = sym->curr.tri; } else { if (sym->visible != no) { /* if the symbol is visible use the user value @@ -443,8 +512,6 @@ void sym_calc_value(struct symbol *sym) } sym->curr = newval; - if (sym_is_choice(sym) && newval.tri == yes) - sym->curr.val = sym_calc_choice(sym); sym_validate_range(sym); if (memcmp(&oldval, &sym->curr, sizeof(oldval))) { @@ -455,24 +522,8 @@ void sym_calc_value(struct symbol *sym) } } - if (sym_is_choice(sym)) { - struct symbol *choice_sym; - - prop = sym_get_choice_prop(sym); - expr_list_for_each_sym(prop->expr, e, choice_sym) { - if ((sym->flags & SYMBOL_WRITE) && - choice_sym->visible != no) - choice_sym->flags |= SYMBOL_WRITE; - if (sym->flags & SYMBOL_CHANGED) - sym_set_changed(choice_sym); - } - } - - if (sym->flags & SYMBOL_NO_WRITE) + if (sym_is_choice(sym)) sym->flags &= ~SYMBOL_WRITE; - - if (sym->flags & SYMBOL_NEED_SET_CHOICE_VALUES) - set_all_choice_values(sym); } void sym_clear_all_valid(void) @@ -481,11 +532,12 @@ void sym_clear_all_valid(void) for_all_symbols(sym) sym->flags &= ~SYMBOL_VALID; + expr_invalidate_all(); conf_set_changed(true); sym_calc_value(modules_sym); } -bool sym_tristate_within_range(struct symbol *sym, tristate val) +bool sym_tristate_within_range(const struct symbol *sym, tristate val) { int type = sym_get_type(sym); @@ -499,8 +551,6 @@ bool sym_tristate_within_range(struct symbol *sym, tristate val) return false; if (sym->visible <= sym->rev_dep.tri) return false; - if (sym_is_choice_value(sym) && sym->visible == yes) - return val == yes; return val >= sym->rev_dep.tri && val <= sym->visible; } @@ -508,42 +558,75 @@ bool sym_set_tristate_value(struct symbol *sym, tristate val) { tristate oldval = sym_get_tristate_value(sym); - if (oldval != val && !sym_tristate_within_range(sym, val)) + if (!sym_tristate_within_range(sym, val)) return false; - if (!(sym->flags & SYMBOL_DEF_USER)) { + if (!(sym->flags & SYMBOL_DEF_USER) || sym->def[S_DEF_USER].tri != val) { + sym->def[S_DEF_USER].tri = val; sym->flags |= SYMBOL_DEF_USER; sym_set_changed(sym); } - /* - * setting a choice value also resets the new flag of the choice - * symbol and all other choice values. - */ - if (sym_is_choice_value(sym) && val == yes) { - struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); - struct property *prop; - struct expr *e; - - cs->def[S_DEF_USER].val = sym; - cs->flags |= SYMBOL_DEF_USER; - prop = sym_get_choice_prop(cs); - for (e = prop->expr; e; e = e->left.expr) { - if (e->right.sym->visible != no) - e->right.sym->flags |= SYMBOL_DEF_USER; - } - } - sym->def[S_DEF_USER].tri = val; if (oldval != val) sym_clear_all_valid(); return true; } +/** + * choice_set_value - set the user input to a choice + * + * @choice: menu entry for the choice + * @sym: selected symbol + */ +void choice_set_value(struct menu *choice, struct symbol *sym) +{ + struct menu *menu; + bool changed = false; + + menu_for_each_sub_entry(menu, choice) { + tristate val; + + if (!menu->sym) + continue; + + if (menu->sym->visible == no) + continue; + + val = menu->sym == sym ? yes : no; + + if (menu->sym->curr.tri != val) + changed = true; + + menu->sym->def[S_DEF_USER].tri = val; + menu->sym->flags |= SYMBOL_DEF_USER; + + /* + * Now, the user has explicitly enabled or disabled this symbol, + * it should be given the highest priority. We are possibly + * setting multiple symbols to 'n', where the first symbol is + * given the least prioritized 'n'. This works well when the + * choice block ends up with selecting 'n' symbol. + * (see sym_calc_choice()) + */ + list_move(&menu->sym->choice_link, &choice->choice_members); + } + + if (changed) + sym_clear_all_valid(); +} + tristate sym_toggle_tristate_value(struct symbol *sym) { + struct menu *choice; tristate oldval, newval; + choice = sym_get_choice_menu(sym); + if (choice) { + choice_set_value(choice, sym); + return yes; + } + oldval = newval = sym_get_tristate_value(sym); do { switch (newval) { @@ -788,8 +871,7 @@ const char *sym_get_string_value(struct symbol *sym) case no: return "n"; case mod: - sym_calc_value(modules_sym); - return (modules_sym->curr.tri == no) ? "n" : "m"; + return "m"; case yes: return "y"; } @@ -797,12 +879,17 @@ const char *sym_get_string_value(struct symbol *sym) default: ; } - return (const char *)sym->curr.val; + return sym->curr.val; } -bool sym_is_changeable(struct symbol *sym) +bool sym_is_changeable(const struct symbol *sym) { - return sym->visible > sym->rev_dep.tri; + return !sym_is_choice(sym) && sym->visible > sym->rev_dep.tri; +} + +bool sym_is_choice_value(const struct symbol *sym) +{ + return !list_empty(&sym->choice_link); } HASHTABLE_DEFINE(sym_hashtable, SYMBOL_HASHSIZE); @@ -821,13 +908,13 @@ struct symbol *sym_lookup(const char *name, int flags) case 'n': return &symbol_no; } } - hash = strhash(name); + hash = hash_str(name); hash_for_each_possible(sym_hashtable, symbol, node, hash) { if (symbol->name && !strcmp(symbol->name, name) && (flags ? symbol->flags & flags - : !(symbol->flags & (SYMBOL_CONST|SYMBOL_CHOICE)))) + : !(symbol->flags & SYMBOL_CONST))) return symbol; } new_name = xstrdup(name); @@ -842,6 +929,7 @@ struct symbol *sym_lookup(const char *name, int flags) symbol->type = S_UNKNOWN; symbol->flags = flags; INIT_LIST_HEAD(&symbol->menus); + INIT_LIST_HEAD(&symbol->choice_link); hash_add(sym_hashtable, &symbol->node, hash); @@ -863,7 +951,7 @@ struct symbol *sym_find(const char *name) case 'n': return &symbol_no; } } - hash = strhash(name); + hash = hash_str(name); hash_for_each_possible(sym_hashtable, symbol, node, hash) { if (symbol->name && @@ -1002,13 +1090,14 @@ static void sym_check_print_recursive(struct symbol *last_sym) { struct dep_stack *stack; struct symbol *sym, *next_sym; - struct menu *menu = NULL; - struct property *prop; + struct menu *choice; struct dep_stack cv_stack; + enum prop_type type; - if (sym_is_choice_value(last_sym)) { + choice = sym_get_choice_menu(last_sym); + if (choice) { dep_stack_insert(&cv_stack, last_sym); - last_sym = prop_get_symbol(sym_get_choice_prop(last_sym)); + last_sym = choice->sym; } for (stack = check_top; stack != NULL; stack = stack->prev) @@ -1022,59 +1111,37 @@ static void sym_check_print_recursive(struct symbol *last_sym) for (; stack; stack = stack->next) { sym = stack->sym; next_sym = stack->next ? stack->next->sym : last_sym; - prop = stack->prop; - if (prop == NULL) - prop = stack->sym->prop; - - /* for choice values find the menu entry (used below) */ - if (sym_is_choice(sym) || sym_is_choice_value(sym)) { - for (prop = sym->prop; prop; prop = prop->next) { - menu = prop->menu; - if (prop->menu) - break; - } - } + type = stack->prop ? stack->prop->type : P_UNKNOWN; + if (stack->sym == last_sym) - fprintf(stderr, "%s:%d:error: recursive dependency detected!\n", - prop->filename, prop->lineno); + fprintf(stderr, "error: recursive dependency detected!\n"); - if (sym_is_choice(sym)) { - fprintf(stderr, "%s:%d:\tchoice %s contains symbol %s\n", - menu->filename, menu->lineno, - sym->name ? sym->name : "<choice>", - next_sym->name ? next_sym->name : "<choice>"); - } else if (sym_is_choice_value(sym)) { - fprintf(stderr, "%s:%d:\tsymbol %s is part of choice %s\n", - menu->filename, menu->lineno, + if (sym_is_choice(next_sym)) { + choice = list_first_entry(&next_sym->menus, struct menu, link); + + fprintf(stderr, "\tsymbol %s is part of choice block at %s:%d\n", sym->name ? sym->name : "<choice>", - next_sym->name ? next_sym->name : "<choice>"); + choice->filename, choice->lineno); } else if (stack->expr == &sym->dir_dep.expr) { - fprintf(stderr, "%s:%d:\tsymbol %s depends on %s\n", - prop->filename, prop->lineno, + fprintf(stderr, "\tsymbol %s depends on %s\n", sym->name ? sym->name : "<choice>", - next_sym->name ? next_sym->name : "<choice>"); + next_sym->name); } else if (stack->expr == &sym->rev_dep.expr) { - fprintf(stderr, "%s:%d:\tsymbol %s is selected by %s\n", - prop->filename, prop->lineno, - sym->name ? sym->name : "<choice>", - next_sym->name ? next_sym->name : "<choice>"); + fprintf(stderr, "\tsymbol %s is selected by %s\n", + sym->name, next_sym->name); } else if (stack->expr == &sym->implied.expr) { - fprintf(stderr, "%s:%d:\tsymbol %s is implied by %s\n", - prop->filename, prop->lineno, - sym->name ? sym->name : "<choice>", - next_sym->name ? next_sym->name : "<choice>"); + fprintf(stderr, "\tsymbol %s is implied by %s\n", + sym->name, next_sym->name); } else if (stack->expr) { - fprintf(stderr, "%s:%d:\tsymbol %s %s value contains %s\n", - prop->filename, prop->lineno, + fprintf(stderr, "\tsymbol %s %s value contains %s\n", sym->name ? sym->name : "<choice>", - prop_get_type_name(prop->type), - next_sym->name ? next_sym->name : "<choice>"); + prop_get_type_name(type), + next_sym->name); } else { - fprintf(stderr, "%s:%d:\tsymbol %s %s is visible depending on %s\n", - prop->filename, prop->lineno, + fprintf(stderr, "\tsymbol %s %s is visible depending on %s\n", sym->name ? sym->name : "<choice>", - prop_get_type_name(prop->type), - next_sym->name ? next_sym->name : "<choice>"); + prop_get_type_name(type), + next_sym->name); } } @@ -1087,7 +1154,7 @@ static void sym_check_print_recursive(struct symbol *last_sym) dep_stack_remove(); } -static struct symbol *sym_check_expr_deps(struct expr *e) +static struct symbol *sym_check_expr_deps(const struct expr *e) { struct symbol *sym; @@ -1148,8 +1215,7 @@ static struct symbol *sym_check_sym_deps(struct symbol *sym) stack.expr = NULL; for (prop = sym->prop; prop; prop = prop->next) { - if (prop->type == P_CHOICE || prop->type == P_SELECT || - prop->type == P_IMPLY) + if (prop->type == P_SELECT || prop->type == P_IMPLY) continue; stack.prop = prop; sym2 = sym_check_expr_deps(prop->visible.expr); @@ -1172,16 +1238,18 @@ out: static struct symbol *sym_check_choice_deps(struct symbol *choice) { - struct symbol *sym, *sym2; - struct property *prop; - struct expr *e; + struct menu *choice_menu, *menu; + struct symbol *sym2; struct dep_stack stack; dep_stack_insert(&stack, choice); - prop = sym_get_choice_prop(choice); - expr_list_for_each_sym(prop->expr, e, sym) - sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); + choice_menu = list_first_entry(&choice->menus, struct menu, link); + + menu_for_each_sub_entry(menu, choice_menu) { + if (menu->sym) + menu->sym->flags |= SYMBOL_CHECK | SYMBOL_CHECKED; + } choice->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); sym2 = sym_check_sym_deps(choice); @@ -1189,18 +1257,25 @@ static struct symbol *sym_check_choice_deps(struct symbol *choice) if (sym2) goto out; - expr_list_for_each_sym(prop->expr, e, sym) { - sym2 = sym_check_sym_deps(sym); + menu_for_each_sub_entry(menu, choice_menu) { + if (!menu->sym) + continue; + sym2 = sym_check_sym_deps(menu->sym); if (sym2) break; } out: - expr_list_for_each_sym(prop->expr, e, sym) - sym->flags &= ~SYMBOL_CHECK; + menu_for_each_sub_entry(menu, choice_menu) + if (menu->sym) + menu->sym->flags &= ~SYMBOL_CHECK; - if (sym2 && sym_is_choice_value(sym2) && - prop_get_symbol(sym_get_choice_prop(sym2)) == choice) - sym2 = choice; + if (sym2) { + struct menu *choice_menu2; + + choice_menu2 = sym_get_choice_menu(sym2); + if (choice_menu2 == choice_menu) + sym2 = choice; + } dep_stack_remove(); @@ -1209,8 +1284,8 @@ out: struct symbol *sym_check_deps(struct symbol *sym) { + struct menu *choice; struct symbol *sym2; - struct property *prop; if (sym->flags & SYMBOL_CHECK) { sym_check_print_recursive(sym); @@ -1219,13 +1294,13 @@ struct symbol *sym_check_deps(struct symbol *sym) if (sym->flags & SYMBOL_CHECKED) return NULL; - if (sym_is_choice_value(sym)) { + choice = sym_get_choice_menu(sym); + if (choice) { struct dep_stack stack; /* for choice groups start the check with main choice symbol */ dep_stack_insert(&stack, sym); - prop = sym_get_choice_prop(sym); - sym2 = sym_check_deps(prop_get_symbol(prop)); + sym2 = sym_check_deps(choice->sym); dep_stack_remove(); } else if (sym_is_choice(sym)) { sym2 = sym_check_choice_deps(sym); @@ -1238,10 +1313,9 @@ struct symbol *sym_check_deps(struct symbol *sym) return sym2; } -struct symbol *prop_get_symbol(struct property *prop) +struct symbol *prop_get_symbol(const struct property *prop) { - if (prop->expr && (prop->expr->type == E_SYMBOL || - prop->expr->type == E_LIST)) + if (prop->expr && prop->expr->type == E_SYMBOL) return prop->expr->left.sym; return NULL; } @@ -1257,16 +1331,12 @@ const char *prop_get_type_name(enum prop_type type) return "menu"; case P_DEFAULT: return "default"; - case P_CHOICE: - return "choice"; case P_SELECT: return "select"; case P_IMPLY: return "imply"; case P_RANGE: return "range"; - case P_SYMBOL: - return "symbol"; case P_UNKNOWN: break; } diff --git a/scripts/kconfig/tests/choice/Kconfig b/scripts/kconfig/tests/choice/Kconfig index 0930eb65e932..cd252579a623 100644 --- a/scripts/kconfig/tests/choice/Kconfig +++ b/scripts/kconfig/tests/choice/Kconfig @@ -1,10 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -config MODULES - bool "Enable loadable module support" - modules - default y - choice prompt "boolean choice" default BOOL_CHOICE1 @@ -16,41 +11,3 @@ config BOOL_CHOICE1 bool "choice 1" endchoice - -choice - prompt "optional boolean choice" - optional - default OPT_BOOL_CHOICE1 - -config OPT_BOOL_CHOICE0 - bool "choice 0" - -config OPT_BOOL_CHOICE1 - bool "choice 1" - -endchoice - -choice - prompt "tristate choice" - default TRI_CHOICE1 - -config TRI_CHOICE0 - tristate "choice 0" - -config TRI_CHOICE1 - tristate "choice 1" - -endchoice - -choice - prompt "optional tristate choice" - optional - default OPT_TRI_CHOICE1 - -config OPT_TRI_CHOICE0 - tristate "choice 0" - -config OPT_TRI_CHOICE1 - tristate "choice 1" - -endchoice diff --git a/scripts/kconfig/tests/choice/__init__.py b/scripts/kconfig/tests/choice/__init__.py index 4318fce05912..0fc7bf9b5c78 100644 --- a/scripts/kconfig/tests/choice/__init__.py +++ b/scripts/kconfig/tests/choice/__init__.py @@ -1,13 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 """ Basic choice tests. - -The handling of 'choice' is a bit complicated part in Kconfig. - -The behavior of 'y' choice is intuitive. If choice values are tristate, -the choice can be 'm' where each value can be enabled independently. -Also, if a choice is marked as 'optional', the whole choice can be -invisible. """ @@ -16,11 +9,6 @@ def test_oldask0(conf): assert conf.stdout_contains('oldask0_expected_stdout') -def test_oldask1(conf): - assert conf.oldaskconfig('oldask1_config') == 0 - assert conf.stdout_contains('oldask1_expected_stdout') - - def test_allyes(conf): assert conf.allyesconfig() == 0 assert conf.config_contains('allyes_expected_config') diff --git a/scripts/kconfig/tests/choice/alldef_expected_config b/scripts/kconfig/tests/choice/alldef_expected_config index 7a754bf4be94..b359a2e6493e 100644 --- a/scripts/kconfig/tests/choice/alldef_expected_config +++ b/scripts/kconfig/tests/choice/alldef_expected_config @@ -1,5 +1,2 @@ -CONFIG_MODULES=y # CONFIG_BOOL_CHOICE0 is not set CONFIG_BOOL_CHOICE1=y -# CONFIG_TRI_CHOICE0 is not set -# CONFIG_TRI_CHOICE1 is not set diff --git a/scripts/kconfig/tests/choice/allmod_expected_config b/scripts/kconfig/tests/choice/allmod_expected_config index f1f5dcdb7923..b359a2e6493e 100644 --- a/scripts/kconfig/tests/choice/allmod_expected_config +++ b/scripts/kconfig/tests/choice/allmod_expected_config @@ -1,9 +1,2 @@ -CONFIG_MODULES=y # CONFIG_BOOL_CHOICE0 is not set CONFIG_BOOL_CHOICE1=y -# CONFIG_OPT_BOOL_CHOICE0 is not set -CONFIG_OPT_BOOL_CHOICE1=y -CONFIG_TRI_CHOICE0=m -CONFIG_TRI_CHOICE1=m -CONFIG_OPT_TRI_CHOICE0=m -CONFIG_OPT_TRI_CHOICE1=m diff --git a/scripts/kconfig/tests/choice/allno_expected_config b/scripts/kconfig/tests/choice/allno_expected_config index b88ee7a43136..b359a2e6493e 100644 --- a/scripts/kconfig/tests/choice/allno_expected_config +++ b/scripts/kconfig/tests/choice/allno_expected_config @@ -1,5 +1,2 @@ -# CONFIG_MODULES is not set # CONFIG_BOOL_CHOICE0 is not set CONFIG_BOOL_CHOICE1=y -# CONFIG_TRI_CHOICE0 is not set -CONFIG_TRI_CHOICE1=y diff --git a/scripts/kconfig/tests/choice/allyes_expected_config b/scripts/kconfig/tests/choice/allyes_expected_config index e5a062a1157c..b359a2e6493e 100644 --- a/scripts/kconfig/tests/choice/allyes_expected_config +++ b/scripts/kconfig/tests/choice/allyes_expected_config @@ -1,9 +1,2 @@ -CONFIG_MODULES=y # CONFIG_BOOL_CHOICE0 is not set CONFIG_BOOL_CHOICE1=y -# CONFIG_OPT_BOOL_CHOICE0 is not set -CONFIG_OPT_BOOL_CHOICE1=y -# CONFIG_TRI_CHOICE0 is not set -CONFIG_TRI_CHOICE1=y -# CONFIG_OPT_TRI_CHOICE0 is not set -CONFIG_OPT_TRI_CHOICE1=y diff --git a/scripts/kconfig/tests/choice/oldask0_expected_stdout b/scripts/kconfig/tests/choice/oldask0_expected_stdout index b251bba9698b..80ec34c61ebc 100644 --- a/scripts/kconfig/tests/choice/oldask0_expected_stdout +++ b/scripts/kconfig/tests/choice/oldask0_expected_stdout @@ -1,10 +1,4 @@ -Enable loadable module support (MODULES) [Y/n/?] (NEW) boolean choice 1. choice 0 (BOOL_CHOICE0) (NEW) > 2. choice 1 (BOOL_CHOICE1) (NEW) choice[1-2?]: -optional boolean choice [N/y/?] (NEW) -tristate choice [M/y/?] (NEW) - choice 0 (TRI_CHOICE0) [N/m/?] (NEW) - choice 1 (TRI_CHOICE1) [N/m/?] (NEW) -optional tristate choice [N/m/y/?] (NEW) diff --git a/scripts/kconfig/tests/choice/oldask1_config b/scripts/kconfig/tests/choice/oldask1_config deleted file mode 100644 index b67bfe3c641f..000000000000 --- a/scripts/kconfig/tests/choice/oldask1_config +++ /dev/null @@ -1,2 +0,0 @@ -# CONFIG_MODULES is not set -CONFIG_OPT_BOOL_CHOICE0=y diff --git a/scripts/kconfig/tests/choice/oldask1_expected_stdout b/scripts/kconfig/tests/choice/oldask1_expected_stdout deleted file mode 100644 index c2125e9bf96a..000000000000 --- a/scripts/kconfig/tests/choice/oldask1_expected_stdout +++ /dev/null @@ -1,15 +0,0 @@ -Enable loadable module support (MODULES) [N/y/?] -boolean choice - 1. choice 0 (BOOL_CHOICE0) (NEW) -> 2. choice 1 (BOOL_CHOICE1) (NEW) -choice[1-2?]: -optional boolean choice [Y/n/?] (NEW) -optional boolean choice -> 1. choice 0 (OPT_BOOL_CHOICE0) - 2. choice 1 (OPT_BOOL_CHOICE1) (NEW) -choice[1-2?]: -tristate choice - 1. choice 0 (TRI_CHOICE0) (NEW) -> 2. choice 1 (TRI_CHOICE1) (NEW) -choice[1-2?]: -optional tristate choice [N/y/?] diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/Kconfig b/scripts/kconfig/tests/choice_value_with_m_dep/Kconfig deleted file mode 100644 index bd970cec07d6..000000000000 --- a/scripts/kconfig/tests/choice_value_with_m_dep/Kconfig +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 - -config MODULES - def_bool y - modules - -config DEP - tristate - default m - -choice - prompt "Tristate Choice" - -config CHOICE0 - tristate "Choice 0" - -config CHOICE1 - tristate "Choice 1" - depends on DEP - -endchoice diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/__init__.py b/scripts/kconfig/tests/choice_value_with_m_dep/__init__.py deleted file mode 100644 index 075b4e08696e..000000000000 --- a/scripts/kconfig/tests/choice_value_with_m_dep/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -""" -Hide tristate choice values with mod dependency in y choice. - -If tristate choice values depend on symbols set to 'm', they should be -hidden when the choice containing them is changed from 'm' to 'y' -(i.e. exclusive choice). - -Related Linux commit: fa64e5f6a35efd5e77d639125d973077ca506074 -""" - - -def test(conf): - assert conf.oldaskconfig('config', 'y') == 0 - assert conf.config_contains('expected_config') - assert conf.stdout_contains('expected_stdout') diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/config b/scripts/kconfig/tests/choice_value_with_m_dep/config deleted file mode 100644 index 3a126b7a2546..000000000000 --- a/scripts/kconfig/tests/choice_value_with_m_dep/config +++ /dev/null @@ -1,2 +0,0 @@ -CONFIG_CHOICE0=m -CONFIG_CHOICE1=m diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/expected_config b/scripts/kconfig/tests/choice_value_with_m_dep/expected_config deleted file mode 100644 index 4d07b449540e..000000000000 --- a/scripts/kconfig/tests/choice_value_with_m_dep/expected_config +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_MODULES=y -CONFIG_DEP=m -CONFIG_CHOICE0=y diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout b/scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout deleted file mode 100644 index 2b50ab65c86a..000000000000 --- a/scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout +++ /dev/null @@ -1,4 +0,0 @@ -Tristate Choice [M/y/?] y -Tristate Choice -> 1. Choice 0 (CHOICE0) -choice[1]: 1 diff --git a/scripts/kconfig/tests/err_recursive_dep/expected_stderr b/scripts/kconfig/tests/err_recursive_dep/expected_stderr index 05d4ced70320..fc2e860af082 100644 --- a/scripts/kconfig/tests/err_recursive_dep/expected_stderr +++ b/scripts/kconfig/tests/err_recursive_dep/expected_stderr @@ -1,38 +1,38 @@ -Kconfig:5:error: recursive dependency detected! -Kconfig:5: symbol A depends on A +error: recursive dependency detected! + symbol A depends on A For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" -Kconfig:11:error: recursive dependency detected! -Kconfig:11: symbol B is selected by B +error: recursive dependency detected! + symbol B is selected by B For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" -Kconfig:17:error: recursive dependency detected! -Kconfig:17: symbol C1 depends on C2 -Kconfig:21: symbol C2 depends on C1 +error: recursive dependency detected! + symbol C1 depends on C2 + symbol C2 depends on C1 For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" -Kconfig:27:error: recursive dependency detected! -Kconfig:27: symbol D1 depends on D2 -Kconfig:32: symbol D2 is selected by D1 +error: recursive dependency detected! + symbol D1 depends on D2 + symbol D2 is selected by D1 For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" -Kconfig:37:error: recursive dependency detected! -Kconfig:37: symbol E1 depends on E2 -Kconfig:42: symbol E2 is implied by E1 +error: recursive dependency detected! + symbol E1 depends on E2 + symbol E2 is implied by E1 For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" -Kconfig:49:error: recursive dependency detected! -Kconfig:49: symbol F1 default value contains F2 -Kconfig:51: symbol F2 depends on F1 +error: recursive dependency detected! + symbol F1 default value contains F2 + symbol F2 depends on F1 For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" -Kconfig:60:error: recursive dependency detected! -Kconfig:60: symbol G depends on G +error: recursive dependency detected! + symbol G depends on G For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" diff --git a/scripts/kconfig/tests/inter_choice/Kconfig b/scripts/kconfig/tests/inter_choice/Kconfig deleted file mode 100644 index 26c25f68695b..000000000000 --- a/scripts/kconfig/tests/inter_choice/Kconfig +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 - -config MODULES - def_bool y - modules - -choice - prompt "Choice" - -config CHOICE_VAL0 - tristate "Choice 0" - -config CHOIVE_VAL1 - tristate "Choice 1" - -endchoice - -choice - prompt "Another choice" - depends on CHOICE_VAL0 - -config DUMMY - bool "dummy" - -endchoice diff --git a/scripts/kconfig/tests/inter_choice/__init__.py b/scripts/kconfig/tests/inter_choice/__init__.py deleted file mode 100644 index ffea6b1148a6..000000000000 --- a/scripts/kconfig/tests/inter_choice/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -""" -Do not affect user-assigned choice value by another choice. - -Handling of state flags for choices is complecated. In old days, -the defconfig result of a choice could be affected by another choice -if those choices interact by 'depends on', 'select', etc. - -Related Linux commit: fbe98bb9ed3dae23e320c6b113e35f129538d14a -""" - - -def test(conf): - assert conf.defconfig('defconfig') == 0 - assert conf.config_contains('expected_config') diff --git a/scripts/kconfig/tests/inter_choice/defconfig b/scripts/kconfig/tests/inter_choice/defconfig deleted file mode 100644 index 162c4148e2a5..000000000000 --- a/scripts/kconfig/tests/inter_choice/defconfig +++ /dev/null @@ -1 +0,0 @@ -CONFIG_CHOICE_VAL0=y diff --git a/scripts/kconfig/tests/inter_choice/expected_config b/scripts/kconfig/tests/inter_choice/expected_config deleted file mode 100644 index 5dceefb054e3..000000000000 --- a/scripts/kconfig/tests/inter_choice/expected_config +++ /dev/null @@ -1,4 +0,0 @@ -CONFIG_MODULES=y -CONFIG_CHOICE_VAL0=y -# CONFIG_CHOIVE_VAL1 is not set -CONFIG_DUMMY=y diff --git a/scripts/kconfig/util.c b/scripts/kconfig/util.c index 439c131b424e..5cdcee144b58 100644 --- a/scripts/kconfig/util.c +++ b/scripts/kconfig/util.c @@ -8,19 +8,11 @@ #include <stdlib.h> #include <string.h> -#include "hashtable.h" +#include <hash.h> +#include <hashtable.h> +#include <xalloc.h> #include "lkc.h" -unsigned int strhash(const char *s) -{ - /* fnv32 hash */ - unsigned int hash = 2166136261U; - - for (; *s; s++) - hash = (hash ^ *s) * 0x01000193; - return hash; -} - /* hash table of all parsed Kconfig files */ static HASHTABLE_DEFINE(file_hashtable, 1U << 11); @@ -34,7 +26,7 @@ const char *file_lookup(const char *name) { struct file *file; size_t len; - int hash = strhash(name); + int hash = hash_str(name); hash_for_each_possible(file_hashtable, file, node, hash) if (!strcmp(name, file->name)) @@ -98,56 +90,7 @@ void str_printf(struct gstr *gs, const char *fmt, ...) } /* Retrieve value of growable string */ -char *str_get(struct gstr *gs) +char *str_get(const struct gstr *gs) { return gs->s; } - -void *xmalloc(size_t size) -{ - void *p = malloc(size); - if (p) - return p; - fprintf(stderr, "Out of memory.\n"); - exit(1); -} - -void *xcalloc(size_t nmemb, size_t size) -{ - void *p = calloc(nmemb, size); - if (p) - return p; - fprintf(stderr, "Out of memory.\n"); - exit(1); -} - -void *xrealloc(void *p, size_t size) -{ - p = realloc(p, size); - if (p) - return p; - fprintf(stderr, "Out of memory.\n"); - exit(1); -} - -char *xstrdup(const char *s) -{ - char *p; - - p = strdup(s); - if (p) - return p; - fprintf(stderr, "Out of memory.\n"); - exit(1); -} - -char *xstrndup(const char *s, size_t n) -{ - char *p; - - p = strndup(s, n); - if (p) - return p; - fprintf(stderr, "Out of memory.\n"); - exit(1); -} diff --git a/scripts/kernel-doc b/scripts/kernel-doc index 967f1abb0edb..3b6ef807791a 100755..120000 --- a/scripts/kernel-doc +++ b/scripts/kernel-doc @@ -1,2541 +1 @@ -#!/usr/bin/env perl -# SPDX-License-Identifier: GPL-2.0 -# vim: softtabstop=4 - -use warnings; -use strict; - -## Copyright (c) 1998 Michael Zucchi, All Rights Reserved ## -## Copyright (C) 2000, 1 Tim Waugh <twaugh@redhat.com> ## -## Copyright (C) 2001 Simon Huggins ## -## Copyright (C) 2005-2012 Randy Dunlap ## -## Copyright (C) 2012 Dan Luedtke ## -## ## -## #define enhancements by Armin Kuster <akuster@mvista.com> ## -## Copyright (c) 2000 MontaVista Software, Inc. ## -# -# Copyright (C) 2022 Tomasz Warniełło (POD) - -use Pod::Usage qw/pod2usage/; - -=head1 NAME - -kernel-doc - Print formatted kernel documentation to stdout - -=head1 SYNOPSIS - - kernel-doc [-h] [-v] [-Werror] [-Wall] [-Wreturn] [-Wshort-desc[ription]] [-Wcontents-before-sections] - [ -man | - -rst [-sphinx-version VERSION] [-enable-lineno] | - -none - ] - [ - -export | - -internal | - [-function NAME] ... | - [-nosymbol NAME] ... - ] - [-no-doc-sections] - [-export-file FILE] ... - FILE ... - -Run `kernel-doc -h` for details. - -=head1 DESCRIPTION - -Read C language source or header FILEs, extract embedded documentation comments, -and print formatted documentation to standard output. - -The documentation comments are identified by the "/**" opening comment mark. - -See Documentation/doc-guide/kernel-doc.rst for the documentation comment syntax. - -=cut - -# more perldoc at the end of the file - -## init lots of data - -my $errors = 0; -my $warnings = 0; -my $anon_struct_union = 0; - -# match expressions used to find embedded type information -my $type_constant = '\b``([^\`]+)``\b'; -my $type_constant2 = '\%([-_\w]+)'; -my $type_func = '(\w+)\(\)'; -my $type_param = '\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)'; -my $type_param_ref = '([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)'; -my $type_fp_param = '\@(\w+)\(\)'; # Special RST handling for func ptr params -my $type_fp_param2 = '\@(\w+->\S+)\(\)'; # Special RST handling for structs with func ptr params -my $type_env = '(\$\w+)'; -my $type_enum = '\&(enum\s*([_\w]+))'; -my $type_struct = '\&(struct\s*([_\w]+))'; -my $type_typedef = '\&(typedef\s*([_\w]+))'; -my $type_union = '\&(union\s*([_\w]+))'; -my $type_member = '\&([_\w]+)(\.|->)([_\w]+)'; -my $type_fallback = '\&([_\w]+)'; -my $type_member_func = $type_member . '\(\)'; - -# Output conversion substitutions. -# One for each output format - -# these are pretty rough -my @highlights_man = ( - [$type_constant, "\$1"], - [$type_constant2, "\$1"], - [$type_func, "\\\\fB\$1\\\\fP"], - [$type_enum, "\\\\fI\$1\\\\fP"], - [$type_struct, "\\\\fI\$1\\\\fP"], - [$type_typedef, "\\\\fI\$1\\\\fP"], - [$type_union, "\\\\fI\$1\\\\fP"], - [$type_param, "\\\\fI\$1\\\\fP"], - [$type_param_ref, "\\\\fI\$1\$2\\\\fP"], - [$type_member, "\\\\fI\$1\$2\$3\\\\fP"], - [$type_fallback, "\\\\fI\$1\\\\fP"] - ); -my $blankline_man = ""; - -# rst-mode -my @highlights_rst = ( - [$type_constant, "``\$1``"], - [$type_constant2, "``\$1``"], - - # Note: need to escape () to avoid func matching later - [$type_member_func, "\\:c\\:type\\:`\$1\$2\$3\\\\(\\\\) <\$1>`"], - [$type_member, "\\:c\\:type\\:`\$1\$2\$3 <\$1>`"], - [$type_fp_param, "**\$1\\\\(\\\\)**"], - [$type_fp_param2, "**\$1\\\\(\\\\)**"], - [$type_func, "\$1()"], - [$type_enum, "\\:c\\:type\\:`\$1 <\$2>`"], - [$type_struct, "\\:c\\:type\\:`\$1 <\$2>`"], - [$type_typedef, "\\:c\\:type\\:`\$1 <\$2>`"], - [$type_union, "\\:c\\:type\\:`\$1 <\$2>`"], - - # in rst this can refer to any type - [$type_fallback, "\\:c\\:type\\:`\$1`"], - [$type_param_ref, "**\$1\$2**"] - ); -my $blankline_rst = "\n"; - -# read arguments -if ($#ARGV == -1) { - pod2usage( - -message => "No arguments!\n", - -exitval => 1, - -verbose => 99, - -sections => 'SYNOPSIS', - -output => \*STDERR, - ); -} - -my $kernelversion; -my ($sphinx_major, $sphinx_minor, $sphinx_patch); - -my $dohighlight = ""; - -my $verbose = 0; -my $Werror = 0; -my $Wreturn = 0; -my $Wshort_desc = 0; -my $Wcontents_before_sections = 0; -my $output_mode = "rst"; -my $output_preformatted = 0; -my $no_doc_sections = 0; -my $enable_lineno = 0; -my @highlights = @highlights_rst; -my $blankline = $blankline_rst; -my $modulename = "Kernel API"; - -use constant { - OUTPUT_ALL => 0, # output all symbols and doc sections - OUTPUT_INCLUDE => 1, # output only specified symbols - OUTPUT_EXPORTED => 2, # output exported symbols - OUTPUT_INTERNAL => 3, # output non-exported symbols -}; -my $output_selection = OUTPUT_ALL; -my $show_not_found = 0; # No longer used - -my @export_file_list; - -my @build_time; -if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) && - (my $seconds = `date -d"${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') { - @build_time = gmtime($seconds); -} else { - @build_time = localtime; -} - -my $man_date = ('January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', - 'November', 'December')[$build_time[4]] . - " " . ($build_time[5]+1900); - -# Essentially these are globals. -# They probably want to be tidied up, made more localised or something. -# CAVEAT EMPTOR! Some of the others I localised may not want to be, which -# could cause "use of undefined value" or other bugs. -my ($function, %function_table, %parametertypes, $declaration_purpose); -my %nosymbol_table = (); -my $declaration_start_line; -my ($type, $declaration_name, $return_type); -my ($newsection, $newcontents, $prototype, $brcount, %source_map); - -if (defined($ENV{'KBUILD_VERBOSE'}) && $ENV{'KBUILD_VERBOSE'} =~ '1') { - $verbose = 1; -} - -if (defined($ENV{'KCFLAGS'})) { - my $kcflags = "$ENV{'KCFLAGS'}"; - - if ($kcflags =~ /(\s|^)-Werror(\s|$)/) { - $Werror = 1; - } -} - -# reading this variable is for backwards compat just in case -# someone was calling it with the variable from outside the -# kernel's build system -if (defined($ENV{'KDOC_WERROR'})) { - $Werror = "$ENV{'KDOC_WERROR'}"; -} -# other environment variables are converted to command-line -# arguments in cmd_checkdoc in the build system - -# Generated docbook code is inserted in a template at a point where -# docbook v3.1 requires a non-zero sequence of RefEntry's; see: -# https://www.oasis-open.org/docbook/documentation/reference/html/refentry.html -# We keep track of number of generated entries and generate a dummy -# if needs be to ensure the expanded template can be postprocessed -# into html. -my $section_counter = 0; - -my $lineprefix=""; - -# Parser states -use constant { - STATE_NORMAL => 0, # normal code - STATE_NAME => 1, # looking for function name - STATE_BODY_MAYBE => 2, # body - or maybe more description - STATE_BODY => 3, # the body of the comment - STATE_BODY_WITH_BLANK_LINE => 4, # the body, which has a blank line - STATE_PROTO => 5, # scanning prototype - STATE_DOCBLOCK => 6, # documentation block - STATE_INLINE => 7, # gathering doc outside main block -}; -my $state; -my $in_doc_sect; -my $leading_space; - -# Inline documentation state -use constant { - STATE_INLINE_NA => 0, # not applicable ($state != STATE_INLINE) - STATE_INLINE_NAME => 1, # looking for member name (@foo:) - STATE_INLINE_TEXT => 2, # looking for member documentation - STATE_INLINE_END => 3, # done - STATE_INLINE_ERROR => 4, # error - Comment without header was found. - # Spit a warning as it's not - # proper kernel-doc and ignore the rest. -}; -my $inline_doc_state; - -#declaration types: can be -# 'function', 'struct', 'union', 'enum', 'typedef' -my $decl_type; - -# Name of the kernel-doc identifier for non-DOC markups -my $identifier; - -my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start. -my $doc_end = '\*/'; -my $doc_com = '\s*\*\s*'; -my $doc_com_body = '\s*\* ?'; -my $doc_decl = $doc_com . '(\w+)'; -# @params and a strictly limited set of supported section names -# Specifically: -# Match @word: -# @...: -# @{section-name}: -# while trying to not match literal block starts like "example::" -# -my $doc_sect = $doc_com . - '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:([^:].*)?$'; -my $doc_content = $doc_com_body . '(.*)'; -my $doc_block = $doc_com . 'DOC:\s*(.*)?'; -my $doc_inline_start = '^\s*/\*\*\s*$'; -my $doc_inline_sect = '\s*\*\s*(@\s*[\w][\w\.]*\s*):(.*)'; -my $doc_inline_end = '^\s*\*/\s*$'; -my $doc_inline_oneline = '^\s*/\*\*\s*(@[\w\s]+):\s*(.*)\s*\*/\s*$'; -my $export_symbol = '^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*;'; -my $export_symbol_ns = '^\s*EXPORT_SYMBOL_NS(_GPL)?\s*\(\s*(\w+)\s*,\s*\w+\)\s*;'; -my $function_pointer = qr{([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)}; -my $attribute = qr{__attribute__\s*\(\([a-z0-9,_\*\s\(\)]*\)\)}i; - -my %parameterdescs; -my %parameterdesc_start_lines; -my @parameterlist; -my %sections; -my @sectionlist; -my %section_start_lines; -my $sectcheck; -my $struct_actual; - -my $contents = ""; -my $new_start_line = 0; - -# the canonical section names. see also $doc_sect above. -my $section_default = "Description"; # default section -my $section_intro = "Introduction"; -my $section = $section_default; -my $section_context = "Context"; -my $section_return = "Return"; - -my $undescribed = "-- undescribed --"; - -reset_state(); - -while ($ARGV[0] =~ m/^--?(.*)/) { - my $cmd = $1; - shift @ARGV; - if ($cmd eq "man") { - $output_mode = "man"; - @highlights = @highlights_man; - $blankline = $blankline_man; - } elsif ($cmd eq "rst") { - $output_mode = "rst"; - @highlights = @highlights_rst; - $blankline = $blankline_rst; - } elsif ($cmd eq "none") { - $output_mode = "none"; - } elsif ($cmd eq "module") { # not needed for XML, inherits from calling document - $modulename = shift @ARGV; - } elsif ($cmd eq "function") { # to only output specific functions - $output_selection = OUTPUT_INCLUDE; - $function = shift @ARGV; - $function_table{$function} = 1; - } elsif ($cmd eq "nosymbol") { # Exclude specific symbols - my $symbol = shift @ARGV; - $nosymbol_table{$symbol} = 1; - } elsif ($cmd eq "export") { # only exported symbols - $output_selection = OUTPUT_EXPORTED; - %function_table = (); - } elsif ($cmd eq "internal") { # only non-exported symbols - $output_selection = OUTPUT_INTERNAL; - %function_table = (); - } elsif ($cmd eq "export-file") { - my $file = shift @ARGV; - push(@export_file_list, $file); - } elsif ($cmd eq "v") { - $verbose = 1; - } elsif ($cmd eq "Werror") { - $Werror = 1; - } elsif ($cmd eq "Wreturn") { - $Wreturn = 1; - } elsif ($cmd eq "Wshort-desc" or $cmd eq "Wshort-description") { - $Wshort_desc = 1; - } elsif ($cmd eq "Wcontents-before-sections") { - $Wcontents_before_sections = 1; - } elsif ($cmd eq "Wall") { - $Wreturn = 1; - $Wshort_desc = 1; - $Wcontents_before_sections = 1; - } elsif (($cmd eq "h") || ($cmd eq "help")) { - pod2usage(-exitval => 0, -verbose => 2); - } elsif ($cmd eq 'no-doc-sections') { - $no_doc_sections = 1; - } elsif ($cmd eq 'enable-lineno') { - $enable_lineno = 1; - } elsif ($cmd eq 'show-not-found') { - $show_not_found = 1; # A no-op but don't fail - } elsif ($cmd eq "sphinx-version") { - my $ver_string = shift @ARGV; - if ($ver_string =~ m/^(\d+)(\.\d+)?(\.\d+)?/) { - $sphinx_major = $1; - if (defined($2)) { - $sphinx_minor = substr($2,1); - } else { - $sphinx_minor = 0; - } - if (defined($3)) { - $sphinx_patch = substr($3,1) - } else { - $sphinx_patch = 0; - } - } else { - die "Sphinx version should either major.minor or major.minor.patch format\n"; - } - } else { - # Unknown argument - pod2usage( - -message => "Argument unknown!\n", - -exitval => 1, - -verbose => 99, - -sections => 'SYNOPSIS', - -output => \*STDERR, - ); - } - if ($#ARGV < 0){ - pod2usage( - -message => "FILE argument missing\n", - -exitval => 1, - -verbose => 99, - -sections => 'SYNOPSIS', - -output => \*STDERR, - ); - } -} - -# continue execution near EOF; - -# The C domain dialect changed on Sphinx 3. So, we need to check the -# version in order to produce the right tags. -sub findprog($) -{ - foreach(split(/:/, $ENV{PATH})) { - return "$_/$_[0]" if(-x "$_/$_[0]"); - } -} - -sub get_sphinx_version() -{ - my $ver; - - my $cmd = "sphinx-build"; - if (!findprog($cmd)) { - my $cmd = "sphinx-build3"; - if (!findprog($cmd)) { - $sphinx_major = 1; - $sphinx_minor = 2; - $sphinx_patch = 0; - printf STDERR "Warning: Sphinx version not found. Using default (Sphinx version %d.%d.%d)\n", - $sphinx_major, $sphinx_minor, $sphinx_patch; - return; - } - } - - open IN, "$cmd --version 2>&1 |"; - while (<IN>) { - if (m/^\s*sphinx-build\s+([\d]+)\.([\d\.]+)(\+\/[\da-f]+)?$/) { - $sphinx_major = $1; - $sphinx_minor = $2; - $sphinx_patch = $3; - last; - } - # Sphinx 1.2.x uses a different format - if (m/^\s*Sphinx.*\s+([\d]+)\.([\d\.]+)$/) { - $sphinx_major = $1; - $sphinx_minor = $2; - $sphinx_patch = $3; - last; - } - } - close IN; -} - -# get kernel version from env -sub get_kernel_version() { - my $version = 'unknown kernel version'; - - if (defined($ENV{'KERNELVERSION'})) { - $version = $ENV{'KERNELVERSION'}; - } - return $version; -} - -# -sub print_lineno { - my $lineno = shift; - if ($enable_lineno && defined($lineno)) { - print ".. LINENO " . $lineno . "\n"; - } -} - -sub emit_warning { - my $location = shift; - my $msg = shift; - print STDERR "$location: warning: $msg"; - ++$warnings; -} -## -# dumps section contents to arrays/hashes intended for that purpose. -# -sub dump_section { - my $file = shift; - my $name = shift; - my $contents = join "\n", @_; - - if ($name =~ m/$type_param/) { - $name = $1; - $parameterdescs{$name} = $contents; - $sectcheck = $sectcheck . $name . " "; - $parameterdesc_start_lines{$name} = $new_start_line; - $new_start_line = 0; - } elsif ($name eq "@\.\.\.") { - $name = "..."; - $parameterdescs{$name} = $contents; - $sectcheck = $sectcheck . $name . " "; - $parameterdesc_start_lines{$name} = $new_start_line; - $new_start_line = 0; - } else { - if (defined($sections{$name}) && ($sections{$name} ne "")) { - # Only warn on user specified duplicate section names. - if ($name ne $section_default) { - emit_warning("${file}:$.", "duplicate section name '$name'\n"); - } - $sections{$name} .= $contents; - } else { - $sections{$name} = $contents; - push @sectionlist, $name; - $section_start_lines{$name} = $new_start_line; - $new_start_line = 0; - } - } -} - -## -# dump DOC: section after checking that it should go out -# -sub dump_doc_section { - my $file = shift; - my $name = shift; - my $contents = join "\n", @_; - - if ($no_doc_sections) { - return; - } - - return if (defined($nosymbol_table{$name})); - - if (($output_selection == OUTPUT_ALL) || - (($output_selection == OUTPUT_INCLUDE) && - defined($function_table{$name}))) - { - dump_section($file, $name, $contents); - output_blockhead({'sectionlist' => \@sectionlist, - 'sections' => \%sections, - 'module' => $modulename, - 'content-only' => ($output_selection != OUTPUT_ALL), }); - } -} - -## -# output function -# -# parameterdescs, a hash. -# function => "function name" -# parameterlist => @list of parameters -# parameterdescs => %parameter descriptions -# sectionlist => @list of sections -# sections => %section descriptions -# - -sub output_highlight { - my $contents = join "\n",@_; - my $line; - -# DEBUG -# if (!defined $contents) { -# use Carp; -# confess "output_highlight got called with no args?\n"; -# } - -# print STDERR "contents b4:$contents\n"; - eval $dohighlight; - die $@ if $@; -# print STDERR "contents af:$contents\n"; - - foreach $line (split "\n", $contents) { - if (! $output_preformatted) { - $line =~ s/^\s*//; - } - if ($line eq ""){ - if (! $output_preformatted) { - print $lineprefix, $blankline; - } - } else { - if ($output_mode eq "man" && substr($line, 0, 1) eq ".") { - print "\\&$line"; - } else { - print $lineprefix, $line; - } - } - print "\n"; - } -} - -## -# output function in man -sub output_function_man(%) { - my %args = %{$_[0]}; - my ($parameter, $section); - my $count; - - print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n"; - - print ".SH NAME\n"; - print $args{'function'} . " \\- " . $args{'purpose'} . "\n"; - - print ".SH SYNOPSIS\n"; - if ($args{'functiontype'} ne "") { - print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n"; - } else { - print ".B \"" . $args{'function'} . "\n"; - } - $count = 0; - my $parenth = "("; - my $post = ","; - foreach my $parameter (@{$args{'parameterlist'}}) { - if ($count == $#{$args{'parameterlist'}}) { - $post = ");"; - } - $type = $args{'parametertypes'}{$parameter}; - if ($type =~ m/$function_pointer/) { - # pointer-to-function - print ".BI \"" . $parenth . $1 . "\" " . " \") (" . $2 . ")" . $post . "\"\n"; - } else { - $type =~ s/([^\*])$/$1 /; - print ".BI \"" . $parenth . $type . "\" " . " \"" . $post . "\"\n"; - } - $count++; - $parenth = ""; - } - - print ".SH ARGUMENTS\n"; - foreach $parameter (@{$args{'parameterlist'}}) { - my $parameter_name = $parameter; - $parameter_name =~ s/\[.*//; - - print ".IP \"" . $parameter . "\" 12\n"; - output_highlight($args{'parameterdescs'}{$parameter_name}); - } - foreach $section (@{$args{'sectionlist'}}) { - print ".SH \"", uc $section, "\"\n"; - output_highlight($args{'sections'}{$section}); - } -} - -## -# output enum in man -sub output_enum_man(%) { - my %args = %{$_[0]}; - my ($parameter, $section); - my $count; - - print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n"; - - print ".SH NAME\n"; - print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n"; - - print ".SH SYNOPSIS\n"; - print "enum " . $args{'enum'} . " {\n"; - $count = 0; - foreach my $parameter (@{$args{'parameterlist'}}) { - print ".br\n.BI \" $parameter\"\n"; - if ($count == $#{$args{'parameterlist'}}) { - print "\n};\n"; - last; - } else { - print ", \n.br\n"; - } - $count++; - } - - print ".SH Constants\n"; - foreach $parameter (@{$args{'parameterlist'}}) { - my $parameter_name = $parameter; - $parameter_name =~ s/\[.*//; - - print ".IP \"" . $parameter . "\" 12\n"; - output_highlight($args{'parameterdescs'}{$parameter_name}); - } - foreach $section (@{$args{'sectionlist'}}) { - print ".SH \"$section\"\n"; - output_highlight($args{'sections'}{$section}); - } -} - -## -# output struct in man -sub output_struct_man(%) { - my %args = %{$_[0]}; - my ($parameter, $section); - - print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n"; - - print ".SH NAME\n"; - print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n"; - - my $declaration = $args{'definition'}; - $declaration =~ s/\t/ /g; - $declaration =~ s/\n/"\n.br\n.BI \"/g; - print ".SH SYNOPSIS\n"; - print $args{'type'} . " " . $args{'struct'} . " {\n.br\n"; - print ".BI \"$declaration\n};\n.br\n\n"; - - print ".SH Members\n"; - foreach $parameter (@{$args{'parameterlist'}}) { - ($parameter =~ /^#/) && next; - - my $parameter_name = $parameter; - $parameter_name =~ s/\[.*//; - - ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; - print ".IP \"" . $parameter . "\" 12\n"; - output_highlight($args{'parameterdescs'}{$parameter_name}); - } - foreach $section (@{$args{'sectionlist'}}) { - print ".SH \"$section\"\n"; - output_highlight($args{'sections'}{$section}); - } -} - -## -# output typedef in man -sub output_typedef_man(%) { - my %args = %{$_[0]}; - my ($parameter, $section); - - print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n"; - - print ".SH NAME\n"; - print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n"; - - foreach $section (@{$args{'sectionlist'}}) { - print ".SH \"$section\"\n"; - output_highlight($args{'sections'}{$section}); - } -} - -sub output_blockhead_man(%) { - my %args = %{$_[0]}; - my ($parameter, $section); - my $count; - - print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n"; - - foreach $section (@{$args{'sectionlist'}}) { - print ".SH \"$section\"\n"; - output_highlight($args{'sections'}{$section}); - } -} - -## -# output in restructured text -# - -# -# This could use some work; it's used to output the DOC: sections, and -# starts by putting out the name of the doc section itself, but that tends -# to duplicate a header already in the template file. -# -sub output_blockhead_rst(%) { - my %args = %{$_[0]}; - my ($parameter, $section); - - foreach $section (@{$args{'sectionlist'}}) { - next if (defined($nosymbol_table{$section})); - - if ($output_selection != OUTPUT_INCLUDE) { - print ".. _$section:\n\n"; - print "**$section**\n\n"; - } - print_lineno($section_start_lines{$section}); - output_highlight_rst($args{'sections'}{$section}); - print "\n"; - } -} - -# -# Apply the RST highlights to a sub-block of text. -# -sub highlight_block($) { - # The dohighlight kludge requires the text be called $contents - my $contents = shift; - eval $dohighlight; - die $@ if $@; - return $contents; -} - -# -# Regexes used only here. -# -my $sphinx_literal = '^[^.].*::$'; -my $sphinx_cblock = '^\.\.\ +code-block::'; - -sub output_highlight_rst { - my $input = join "\n",@_; - my $output = ""; - my $line; - my $in_literal = 0; - my $litprefix; - my $block = ""; - - foreach $line (split "\n",$input) { - # - # If we're in a literal block, see if we should drop out - # of it. Otherwise pass the line straight through unmunged. - # - if ($in_literal) { - if (! ($line =~ /^\s*$/)) { - # - # If this is the first non-blank line in a literal - # block we need to figure out what the proper indent is. - # - if ($litprefix eq "") { - $line =~ /^(\s*)/; - $litprefix = '^' . $1; - $output .= $line . "\n"; - } elsif (! ($line =~ /$litprefix/)) { - $in_literal = 0; - } else { - $output .= $line . "\n"; - } - } else { - $output .= $line . "\n"; - } - } - # - # Not in a literal block (or just dropped out) - # - if (! $in_literal) { - $block .= $line . "\n"; - if (($line =~ /$sphinx_literal/) || ($line =~ /$sphinx_cblock/)) { - $in_literal = 1; - $litprefix = ""; - $output .= highlight_block($block); - $block = "" - } - } - } - - if ($block) { - $output .= highlight_block($block); - } - foreach $line (split "\n", $output) { - print $lineprefix . $line . "\n"; - } -} - -sub output_function_rst(%) { - my %args = %{$_[0]}; - my ($parameter, $section); - my $oldprefix = $lineprefix; - - my $signature = ""; - if ($args{'functiontype'} ne "") { - $signature = $args{'functiontype'} . " " . $args{'function'} . " ("; - } else { - $signature = $args{'function'} . " ("; - } - - my $count = 0; - foreach my $parameter (@{$args{'parameterlist'}}) { - if ($count ne 0) { - $signature .= ", "; - } - $count++; - $type = $args{'parametertypes'}{$parameter}; - - if ($type =~ m/$function_pointer/) { - # pointer-to-function - $signature .= $1 . $parameter . ") (" . $2 . ")"; - } else { - $signature .= $type; - } - } - - $signature .= ")"; - - if ($sphinx_major < 3) { - if ($args{'typedef'}) { - print ".. c:type:: ". $args{'function'} . "\n\n"; - print_lineno($declaration_start_line); - print " **Typedef**: "; - $lineprefix = ""; - output_highlight_rst($args{'purpose'}); - print "\n\n**Syntax**\n\n"; - print " ``$signature``\n\n"; - } else { - print ".. c:function:: $signature\n\n"; - } - } else { - if ($args{'typedef'} || $args{'functiontype'} eq "") { - print ".. c:macro:: ". $args{'function'} . "\n\n"; - - if ($args{'typedef'}) { - print_lineno($declaration_start_line); - print " **Typedef**: "; - $lineprefix = ""; - output_highlight_rst($args{'purpose'}); - print "\n\n**Syntax**\n\n"; - print " ``$signature``\n\n"; - } else { - print "``$signature``\n\n"; - } - } else { - print ".. c:function:: $signature\n\n"; - } - } - - if (!$args{'typedef'}) { - print_lineno($declaration_start_line); - $lineprefix = " "; - output_highlight_rst($args{'purpose'}); - print "\n"; - } - - # - # Put our descriptive text into a container (thus an HTML <div>) to help - # set the function prototypes apart. - # - print ".. container:: kernelindent\n\n"; - $lineprefix = " "; - print $lineprefix . "**Parameters**\n\n"; - foreach $parameter (@{$args{'parameterlist'}}) { - my $parameter_name = $parameter; - $parameter_name =~ s/\[.*//; - $type = $args{'parametertypes'}{$parameter}; - - if ($type ne "") { - print $lineprefix . "``$type``\n"; - } else { - print $lineprefix . "``$parameter``\n"; - } - - print_lineno($parameterdesc_start_lines{$parameter_name}); - - $lineprefix = " "; - if (defined($args{'parameterdescs'}{$parameter_name}) && - $args{'parameterdescs'}{$parameter_name} ne $undescribed) { - output_highlight_rst($args{'parameterdescs'}{$parameter_name}); - } else { - print $lineprefix . "*undescribed*\n"; - } - $lineprefix = " "; - print "\n"; - } - - output_section_rst(@_); - $lineprefix = $oldprefix; -} - -sub output_section_rst(%) { - my %args = %{$_[0]}; - my $section; - my $oldprefix = $lineprefix; - - foreach $section (@{$args{'sectionlist'}}) { - print $lineprefix . "**$section**\n\n"; - print_lineno($section_start_lines{$section}); - output_highlight_rst($args{'sections'}{$section}); - print "\n"; - } - print "\n"; -} - -sub output_enum_rst(%) { - my %args = %{$_[0]}; - my ($parameter); - my $oldprefix = $lineprefix; - my $count; - my $outer; - - if ($sphinx_major < 3) { - my $name = "enum " . $args{'enum'}; - print "\n\n.. c:type:: " . $name . "\n\n"; - } else { - my $name = $args{'enum'}; - print "\n\n.. c:enum:: " . $name . "\n\n"; - } - print_lineno($declaration_start_line); - $lineprefix = " "; - output_highlight_rst($args{'purpose'}); - print "\n"; - - print ".. container:: kernelindent\n\n"; - $outer = $lineprefix . " "; - $lineprefix = $outer . " "; - print $outer . "**Constants**\n\n"; - foreach $parameter (@{$args{'parameterlist'}}) { - print $outer . "``$parameter``\n"; - - if ($args{'parameterdescs'}{$parameter} ne $undescribed) { - output_highlight_rst($args{'parameterdescs'}{$parameter}); - } else { - print $lineprefix . "*undescribed*\n"; - } - print "\n"; - } - print "\n"; - $lineprefix = $oldprefix; - output_section_rst(@_); -} - -sub output_typedef_rst(%) { - my %args = %{$_[0]}; - my ($parameter); - my $oldprefix = $lineprefix; - my $name; - - if ($sphinx_major < 3) { - $name = "typedef " . $args{'typedef'}; - } else { - $name = $args{'typedef'}; - } - print "\n\n.. c:type:: " . $name . "\n\n"; - print_lineno($declaration_start_line); - $lineprefix = " "; - output_highlight_rst($args{'purpose'}); - print "\n"; - - $lineprefix = $oldprefix; - output_section_rst(@_); -} - -sub output_struct_rst(%) { - my %args = %{$_[0]}; - my ($parameter); - my $oldprefix = $lineprefix; - - if ($sphinx_major < 3) { - my $name = $args{'type'} . " " . $args{'struct'}; - print "\n\n.. c:type:: " . $name . "\n\n"; - } else { - my $name = $args{'struct'}; - if ($args{'type'} eq 'union') { - print "\n\n.. c:union:: " . $name . "\n\n"; - } else { - print "\n\n.. c:struct:: " . $name . "\n\n"; - } - } - print_lineno($declaration_start_line); - $lineprefix = " "; - output_highlight_rst($args{'purpose'}); - print "\n"; - - print ".. container:: kernelindent\n\n"; - print $lineprefix . "**Definition**::\n\n"; - my $declaration = $args{'definition'}; - $lineprefix = $lineprefix . " "; - $declaration =~ s/\t/$lineprefix/g; - print $lineprefix . $args{'type'} . " " . $args{'struct'} . " {\n$declaration" . $lineprefix . "};\n\n"; - - $lineprefix = " "; - print $lineprefix . "**Members**\n\n"; - foreach $parameter (@{$args{'parameterlist'}}) { - ($parameter =~ /^#/) && next; - - my $parameter_name = $parameter; - $parameter_name =~ s/\[.*//; - - ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; - $type = $args{'parametertypes'}{$parameter}; - print_lineno($parameterdesc_start_lines{$parameter_name}); - print $lineprefix . "``" . $parameter . "``\n"; - $lineprefix = " "; - output_highlight_rst($args{'parameterdescs'}{$parameter_name}); - $lineprefix = " "; - print "\n"; - } - print "\n"; - - $lineprefix = $oldprefix; - output_section_rst(@_); -} - -## none mode output functions - -sub output_function_none(%) { -} - -sub output_enum_none(%) { -} - -sub output_typedef_none(%) { -} - -sub output_struct_none(%) { -} - -sub output_blockhead_none(%) { -} - -## -# generic output function for all types (function, struct/union, typedef, enum); -# calls the generated, variable output_ function name based on -# functype and output_mode -sub output_declaration { - no strict 'refs'; - my $name = shift; - my $functype = shift; - my $func = "output_${functype}_$output_mode"; - - return if (defined($nosymbol_table{$name})); - - if (($output_selection == OUTPUT_ALL) || - (($output_selection == OUTPUT_INCLUDE || - $output_selection == OUTPUT_EXPORTED) && - defined($function_table{$name})) || - ($output_selection == OUTPUT_INTERNAL && - !($functype eq "function" && defined($function_table{$name})))) - { - &$func(@_); - $section_counter++; - } -} - -## -# generic output function - calls the right one based on current output mode. -sub output_blockhead { - no strict 'refs'; - my $func = "output_blockhead_" . $output_mode; - &$func(@_); - $section_counter++; -} - -## -# takes a declaration (struct, union, enum, typedef) and -# invokes the right handler. NOT called for functions. -sub dump_declaration($$) { - no strict 'refs'; - my ($prototype, $file) = @_; - my $func = "dump_" . $decl_type; - &$func(@_); -} - -sub dump_union($$) { - dump_struct(@_); -} - -sub dump_struct($$) { - my $x = shift; - my $file = shift; - my $decl_type; - my $members; - my $type = qr{struct|union}; - # For capturing struct/union definition body, i.e. "{members*}qualifiers*" - my $qualifiers = qr{$attribute|__packed|__aligned|____cacheline_aligned_in_smp|____cacheline_aligned}; - my $definition_body = qr{\{(.*)\}\s*$qualifiers*}; - my $struct_members = qr{($type)([^\{\};]+)\{([^\{\}]*)\}([^\{\}\;]*)\;}; - - if ($x =~ /($type)\s+(\w+)\s*$definition_body/) { - $decl_type = $1; - $declaration_name = $2; - $members = $3; - } elsif ($x =~ /typedef\s+($type)\s*$definition_body\s*(\w+)\s*;/) { - $decl_type = $1; - $declaration_name = $3; - $members = $2; - } - - if ($members) { - if ($identifier ne $declaration_name) { - emit_warning("${file}:$.", "expecting prototype for $decl_type $identifier. Prototype was for $decl_type $declaration_name instead\n"); - return; - } - - # ignore members marked private: - $members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi; - $members =~ s/\/\*\s*private:.*//gosi; - # strip comments: - $members =~ s/\/\*.*?\*\///gos; - # strip attributes - $members =~ s/\s*$attribute/ /gi; - $members =~ s/\s*__aligned\s*\([^;]*\)/ /gos; - $members =~ s/\s*__counted_by\s*\([^;]*\)/ /gos; - $members =~ s/\s*__packed\s*/ /gos; - $members =~ s/\s*CRYPTO_MINALIGN_ATTR/ /gos; - $members =~ s/\s*____cacheline_aligned_in_smp/ /gos; - $members =~ s/\s*____cacheline_aligned/ /gos; - # unwrap struct_group(): - # - first eat non-declaration parameters and rewrite for final match - # - then remove macro, outer parens, and trailing semicolon - $members =~ s/\bstruct_group\s*\(([^,]*,)/STRUCT_GROUP(/gos; - $members =~ s/\bstruct_group_(attr|tagged)\s*\(([^,]*,){2}/STRUCT_GROUP(/gos; - $members =~ s/\b__struct_group\s*\(([^,]*,){3}/STRUCT_GROUP(/gos; - $members =~ s/\bSTRUCT_GROUP(\(((?:(?>[^)(]+)|(?1))*)\))[^;]*;/$2/gos; - - my $args = qr{([^,)]+)}; - # replace DECLARE_BITMAP - $members =~ s/__ETHTOOL_DECLARE_LINK_MODE_MASK\s*\(([^\)]+)\)/DECLARE_BITMAP($1, __ETHTOOL_LINK_MODE_MASK_NBITS)/gos; - $members =~ s/DECLARE_PHY_INTERFACE_MASK\s*\(([^\)]+)\)/DECLARE_BITMAP($1, PHY_INTERFACE_MODE_MAX)/gos; - $members =~ s/DECLARE_BITMAP\s*\($args,\s*$args\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos; - # replace DECLARE_HASHTABLE - $members =~ s/DECLARE_HASHTABLE\s*\($args,\s*$args\)/unsigned long $1\[1 << (($2) - 1)\]/gos; - # replace DECLARE_KFIFO - $members =~ s/DECLARE_KFIFO\s*\($args,\s*$args,\s*$args\)/$2 \*$1/gos; - # replace DECLARE_KFIFO_PTR - $members =~ s/DECLARE_KFIFO_PTR\s*\($args,\s*$args\)/$2 \*$1/gos; - # replace DECLARE_FLEX_ARRAY - $members =~ s/(?:__)?DECLARE_FLEX_ARRAY\s*\($args,\s*$args\)/$1 $2\[\]/gos; - #replace DEFINE_DMA_UNMAP_ADDR - $members =~ s/DEFINE_DMA_UNMAP_ADDR\s*\($args\)/dma_addr_t $1/gos; - #replace DEFINE_DMA_UNMAP_LEN - $members =~ s/DEFINE_DMA_UNMAP_LEN\s*\($args\)/__u32 $1/gos; - my $declaration = $members; - - # Split nested struct/union elements as newer ones - while ($members =~ m/$struct_members/) { - my $newmember; - my $maintype = $1; - my $ids = $4; - my $content = $3; - foreach my $id(split /,/, $ids) { - $newmember .= "$maintype $id; "; - - $id =~ s/[:\[].*//; - $id =~ s/^\s*\**(\S+)\s*/$1/; - foreach my $arg (split /;/, $content) { - next if ($arg =~ m/^\s*$/); - if ($arg =~ m/^([^\(]+\(\*?\s*)([\w\.]*)(\s*\).*)/) { - # pointer-to-function - my $type = $1; - my $name = $2; - my $extra = $3; - next if (!$name); - if ($id =~ m/^\s*$/) { - # anonymous struct/union - $newmember .= "$type$name$extra; "; - } else { - $newmember .= "$type$id.$name$extra; "; - } - } else { - my $type; - my $names; - $arg =~ s/^\s+//; - $arg =~ s/\s+$//; - # Handle bitmaps - $arg =~ s/:\s*\d+\s*//g; - # Handle arrays - $arg =~ s/\[.*\]//g; - # The type may have multiple words, - # and multiple IDs can be defined, like: - # const struct foo, *bar, foobar - # So, we remove spaces when parsing the - # names, in order to match just names - # and commas for the names - $arg =~ s/\s*,\s*/,/g; - if ($arg =~ m/(.*)\s+([\S+,]+)/) { - $type = $1; - $names = $2; - } else { - $newmember .= "$arg; "; - next; - } - foreach my $name (split /,/, $names) { - $name =~ s/^\s*\**(\S+)\s*/$1/; - next if (($name =~ m/^\s*$/)); - if ($id =~ m/^\s*$/) { - # anonymous struct/union - $newmember .= "$type $name; "; - } else { - $newmember .= "$type $id.$name; "; - } - } - } - } - } - $members =~ s/$struct_members/$newmember/; - } - - # Ignore other nested elements, like enums - $members =~ s/(\{[^\{\}]*\})//g; - - create_parameterlist($members, ';', $file, $declaration_name); - check_sections($file, $declaration_name, $decl_type, $sectcheck, $struct_actual); - - # Adjust declaration for better display - $declaration =~ s/([\{;])/$1\n/g; - $declaration =~ s/\}\s+;/};/g; - # Better handle inlined enums - do {} while ($declaration =~ s/(enum\s+\{[^\}]+),([^\n])/$1,\n$2/); - - my @def_args = split /\n/, $declaration; - my $level = 1; - $declaration = ""; - foreach my $clause (@def_args) { - $clause =~ s/^\s+//; - $clause =~ s/\s+$//; - $clause =~ s/\s+/ /; - next if (!$clause); - $level-- if ($clause =~ m/(\})/ && $level > 1); - if (!($clause =~ m/^\s*#/)) { - $declaration .= "\t" x $level; - } - $declaration .= "\t" . $clause . "\n"; - $level++ if ($clause =~ m/(\{)/ && !($clause =~m/\}/)); - } - output_declaration($declaration_name, - 'struct', - {'struct' => $declaration_name, - 'module' => $modulename, - 'definition' => $declaration, - 'parameterlist' => \@parameterlist, - 'parameterdescs' => \%parameterdescs, - 'parametertypes' => \%parametertypes, - 'sectionlist' => \@sectionlist, - 'sections' => \%sections, - 'purpose' => $declaration_purpose, - 'type' => $decl_type - }); - } else { - print STDERR "${file}:$.: error: Cannot parse struct or union!\n"; - ++$errors; - } -} - - -sub show_warnings($$) { - my $functype = shift; - my $name = shift; - - return 0 if (defined($nosymbol_table{$name})); - - return 1 if ($output_selection == OUTPUT_ALL); - - if ($output_selection == OUTPUT_EXPORTED) { - if (defined($function_table{$name})) { - return 1; - } else { - return 0; - } - } - if ($output_selection == OUTPUT_INTERNAL) { - if (!($functype eq "function" && defined($function_table{$name}))) { - return 1; - } else { - return 0; - } - } - if ($output_selection == OUTPUT_INCLUDE) { - if (defined($function_table{$name})) { - return 1; - } else { - return 0; - } - } - die("Please add the new output type at show_warnings()"); -} - -sub dump_enum($$) { - my $x = shift; - my $file = shift; - my $members; - - # ignore members marked private: - $x =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi; - $x =~ s/\/\*\s*private:.*}/}/gosi; - - $x =~ s@/\*.*?\*/@@gos; # strip comments. - # strip #define macros inside enums - $x =~ s@#\s*((define|ifdef|if)\s+|endif)[^;]*;@@gos; - - if ($x =~ /typedef\s+enum\s*\{(.*)\}\s*(\w*)\s*;/) { - $declaration_name = $2; - $members = $1; - } elsif ($x =~ /enum\s+(\w*)\s*\{(.*)\}/) { - $declaration_name = $1; - $members = $2; - } - - if ($members) { - if ($identifier ne $declaration_name) { - if ($identifier eq "") { - emit_warning("${file}:$.", "wrong kernel-doc identifier on line:\n"); - } else { - emit_warning("${file}:$.", "expecting prototype for enum $identifier. Prototype was for enum $declaration_name instead\n"); - } - return; - } - $declaration_name = "(anonymous)" if ($declaration_name eq ""); - - my %_members; - - $members =~ s/\s+$//; - $members =~ s/\([^;]*?[\)]//g; - - foreach my $arg (split ',', $members) { - $arg =~ s/^\s*(\w+).*/$1/; - push @parameterlist, $arg; - if (!$parameterdescs{$arg}) { - $parameterdescs{$arg} = $undescribed; - if (show_warnings("enum", $declaration_name)) { - emit_warning("${file}:$.", "Enum value '$arg' not described in enum '$declaration_name'\n"); - } - } - $_members{$arg} = 1; - } - - while (my ($k, $v) = each %parameterdescs) { - if (!exists($_members{$k})) { - if (show_warnings("enum", $declaration_name)) { - emit_warning("${file}:$.", "Excess enum value '$k' description in '$declaration_name'\n"); - } - } - } - - output_declaration($declaration_name, - 'enum', - {'enum' => $declaration_name, - 'module' => $modulename, - 'parameterlist' => \@parameterlist, - 'parameterdescs' => \%parameterdescs, - 'sectionlist' => \@sectionlist, - 'sections' => \%sections, - 'purpose' => $declaration_purpose - }); - } else { - print STDERR "${file}:$.: error: Cannot parse enum!\n"; - ++$errors; - } -} - -my $typedef_type = qr { ((?:\s+[\w\*]+\b){1,8})\s* }x; -my $typedef_ident = qr { \*?\s*(\w\S+)\s* }x; -my $typedef_args = qr { \s*\((.*)\); }x; - -my $typedef1 = qr { typedef$typedef_type\($typedef_ident\)$typedef_args }x; -my $typedef2 = qr { typedef$typedef_type$typedef_ident$typedef_args }x; - -sub dump_typedef($$) { - my $x = shift; - my $file = shift; - - $x =~ s@/\*.*?\*/@@gos; # strip comments. - - # Parse function typedef prototypes - if ($x =~ $typedef1 || $x =~ $typedef2) { - $return_type = $1; - $declaration_name = $2; - my $args = $3; - $return_type =~ s/^\s+//; - - if ($identifier ne $declaration_name) { - emit_warning("${file}:$.", "expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n"); - return; - } - - create_parameterlist($args, ',', $file, $declaration_name); - - output_declaration($declaration_name, - 'function', - {'function' => $declaration_name, - 'typedef' => 1, - 'module' => $modulename, - 'functiontype' => $return_type, - 'parameterlist' => \@parameterlist, - 'parameterdescs' => \%parameterdescs, - 'parametertypes' => \%parametertypes, - 'sectionlist' => \@sectionlist, - 'sections' => \%sections, - 'purpose' => $declaration_purpose - }); - return; - } - - while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) { - $x =~ s/\(*.\)\s*;$/;/; - $x =~ s/\[*.\]\s*;$/;/; - } - - if ($x =~ /typedef.*\s+(\w+)\s*;/) { - $declaration_name = $1; - - if ($identifier ne $declaration_name) { - emit_warning("${file}:$.", "expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n"); - return; - } - - output_declaration($declaration_name, - 'typedef', - {'typedef' => $declaration_name, - 'module' => $modulename, - 'sectionlist' => \@sectionlist, - 'sections' => \%sections, - 'purpose' => $declaration_purpose - }); - } else { - print STDERR "${file}:$.: error: Cannot parse typedef!\n"; - ++$errors; - } -} - -sub save_struct_actual($) { - my $actual = shift; - - # strip all spaces from the actual param so that it looks like one string item - $actual =~ s/\s*//g; - $struct_actual = $struct_actual . $actual . " "; -} - -sub create_parameterlist($$$$) { - my $args = shift; - my $splitter = shift; - my $file = shift; - my $declaration_name = shift; - my $type; - my $param; - - # temporarily replace commas inside function pointer definition - my $arg_expr = qr{\([^\),]+}; - while ($args =~ /$arg_expr,/) { - $args =~ s/($arg_expr),/$1#/g; - } - - foreach my $arg (split($splitter, $args)) { - # strip comments - $arg =~ s/\/\*.*\*\///; - # ignore argument attributes - $arg =~ s/\sPOS0?\s/ /; - # strip leading/trailing spaces - $arg =~ s/^\s*//; - $arg =~ s/\s*$//; - $arg =~ s/\s+/ /; - - if ($arg =~ /^#/) { - # Treat preprocessor directive as a typeless variable just to fill - # corresponding data structures "correctly". Catch it later in - # output_* subs. - push_parameter($arg, "", "", $file); - } elsif ($arg =~ m/\(.+\)\s*\(/) { - # pointer-to-function - $arg =~ tr/#/,/; - $arg =~ m/[^\(]+\(\*?\s*([\w\[\]\.]*)\s*\)/; - $param = $1; - $type = $arg; - $type =~ s/([^\(]+\(\*?)\s*$param/$1/; - save_struct_actual($param); - push_parameter($param, $type, $arg, $file, $declaration_name); - } elsif ($arg =~ m/\(.+\)\s*\[/) { - # array-of-pointers - $arg =~ tr/#/,/; - $arg =~ m/[^\(]+\(\s*\*\s*([\w\[\]\.]*?)\s*(\s*\[\s*[\w]+\s*\]\s*)*\)/; - $param = $1; - $type = $arg; - $type =~ s/([^\(]+\(\*?)\s*$param/$1/; - save_struct_actual($param); - push_parameter($param, $type, $arg, $file, $declaration_name); - } elsif ($arg) { - $arg =~ s/\s*:\s*/:/g; - $arg =~ s/\s*\[/\[/g; - - my @args = split('\s*,\s*', $arg); - if ($args[0] =~ m/\*/) { - $args[0] =~ s/(\*+)\s*/ $1/; - } - - my @first_arg; - if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) { - shift @args; - push(@first_arg, split('\s+', $1)); - push(@first_arg, $2); - } else { - @first_arg = split('\s+', shift @args); - } - - unshift(@args, pop @first_arg); - $type = join " ", @first_arg; - - foreach $param (@args) { - if ($param =~ m/^(\*+)\s*(.*)/) { - save_struct_actual($2); - - push_parameter($2, "$type $1", $arg, $file, $declaration_name); - } elsif ($param =~ m/(.*?):(\d+)/) { - if ($type ne "") { # skip unnamed bit-fields - save_struct_actual($1); - push_parameter($1, "$type:$2", $arg, $file, $declaration_name) - } - } else { - save_struct_actual($param); - push_parameter($param, $type, $arg, $file, $declaration_name); - } - } - } - } -} - -sub push_parameter($$$$$) { - my $param = shift; - my $type = shift; - my $org_arg = shift; - my $file = shift; - my $declaration_name = shift; - - if (($anon_struct_union == 1) && ($type eq "") && - ($param eq "}")) { - return; # ignore the ending }; from anon. struct/union - } - - $anon_struct_union = 0; - $param =~ s/[\[\)].*//; - - if ($type eq "" && $param =~ /\.\.\.$/) - { - if (!$param =~ /\w\.\.\.$/) { - # handles unnamed variable parameters - $param = "..."; - } elsif ($param =~ /\w\.\.\.$/) { - # for named variable parameters of the form `x...`, remove the dots - $param =~ s/\.\.\.$//; - } - if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") { - $parameterdescs{$param} = "variable arguments"; - } - } - elsif ($type eq "" && ($param eq "" or $param eq "void")) - { - $param="void"; - $parameterdescs{void} = "no arguments"; - } - elsif ($type eq "" && ($param eq "struct" or $param eq "union")) - # handle unnamed (anonymous) union or struct: - { - $type = $param; - $param = "{unnamed_" . $param . "}"; - $parameterdescs{$param} = "anonymous\n"; - $anon_struct_union = 1; - } - elsif ($param =~ "__cacheline_group" ) - # handle cache group enforcing variables: they do not need be described in header files - { - return; # ignore __cacheline_group_begin and __cacheline_group_end - } - - # warn if parameter has no description - # (but ignore ones starting with # as these are not parameters - # but inline preprocessor statements); - # Note: It will also ignore void params and unnamed structs/unions - if (!defined $parameterdescs{$param} && $param !~ /^#/) { - $parameterdescs{$param} = $undescribed; - - if (show_warnings($type, $declaration_name) && $param !~ /\./) { - emit_warning("${file}:$.", "Function parameter or struct member '$param' not described in '$declaration_name'\n"); - } - } - - # strip spaces from $param so that it is one continuous string - # on @parameterlist; - # this fixes a problem where check_sections() cannot find - # a parameter like "addr[6 + 2]" because it actually appears - # as "addr[6", "+", "2]" on the parameter list; - # but it's better to maintain the param string unchanged for output, - # so just weaken the string compare in check_sections() to ignore - # "[blah" in a parameter string; - ###$param =~ s/\s*//g; - push @parameterlist, $param; - $org_arg =~ s/\s\s+/ /g; - $parametertypes{$param} = $org_arg; -} - -sub check_sections($$$$$) { - my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck) = @_; - my @sects = split ' ', $sectcheck; - my @prms = split ' ', $prmscheck; - my $err; - my ($px, $sx); - my $prm_clean; # strip trailing "[array size]" and/or beginning "*" - - foreach $sx (0 .. $#sects) { - $err = 1; - foreach $px (0 .. $#prms) { - $prm_clean = $prms[$px]; - $prm_clean =~ s/\[.*\]//; - $prm_clean =~ s/$attribute//i; - # ignore array size in a parameter string; - # however, the original param string may contain - # spaces, e.g.: addr[6 + 2] - # and this appears in @prms as "addr[6" since the - # parameter list is split at spaces; - # hence just ignore "[..." for the sections check; - $prm_clean =~ s/\[.*//; - - ##$prm_clean =~ s/^\**//; - if ($prm_clean eq $sects[$sx]) { - $err = 0; - last; - } - } - if ($err) { - if ($decl_type eq "function") { - emit_warning("${file}:$.", - "Excess function parameter " . - "'$sects[$sx]' " . - "description in '$decl_name'\n"); - } elsif (($decl_type eq "struct") or - ($decl_type eq "union")) { - emit_warning("${file}:$.", - "Excess $decl_type member " . - "'$sects[$sx]' " . - "description in '$decl_name'\n"); - } - } - } -} - -## -# Checks the section describing the return value of a function. -sub check_return_section { - my $file = shift; - my $declaration_name = shift; - my $return_type = shift; - - # Ignore an empty return type (It's a macro) - # Ignore functions with a "void" return type. (But don't ignore "void *") - if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) { - return; - } - - if (!defined($sections{$section_return}) || - $sections{$section_return} eq "") - { - emit_warning("${file}:$.", - "No description found for return value of " . - "'$declaration_name'\n"); - } -} - -## -# takes a function prototype and the name of the current file being -# processed and spits out all the details stored in the global -# arrays/hashes. -sub dump_function($$) { - my $prototype = shift; - my $file = shift; - my $noret = 0; - - print_lineno($new_start_line); - - $prototype =~ s/^static +//; - $prototype =~ s/^extern +//; - $prototype =~ s/^asmlinkage +//; - $prototype =~ s/^inline +//; - $prototype =~ s/^__inline__ +//; - $prototype =~ s/^__inline +//; - $prototype =~ s/^__always_inline +//; - $prototype =~ s/^noinline +//; - $prototype =~ s/^__FORTIFY_INLINE +//; - $prototype =~ s/__init +//; - $prototype =~ s/__init_or_module +//; - $prototype =~ s/__deprecated +//; - $prototype =~ s/__flatten +//; - $prototype =~ s/__meminit +//; - $prototype =~ s/__must_check +//; - $prototype =~ s/__weak +//; - $prototype =~ s/__sched +//; - $prototype =~ s/__printf\s*\(\s*\d*\s*,\s*\d*\s*\) +//; - $prototype =~ s/__(?:re)?alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) +//; - $prototype =~ s/__diagnose_as\s*\(\s*\S+\s*(?:,\s*\d+\s*)*\) +//; - my $define = $prototype =~ s/^#\s*define\s+//; #ak added - $prototype =~ s/__attribute_const__ +//; - $prototype =~ s/__attribute__\s*\(\( - (?: - [\w\s]++ # attribute name - (?:\([^)]*+\))? # attribute arguments - \s*+,? # optional comma at the end - )+ - \)\)\s+//x; - - # Yes, this truly is vile. We are looking for: - # 1. Return type (may be nothing if we're looking at a macro) - # 2. Function name - # 3. Function parameters. - # - # All the while we have to watch out for function pointer parameters - # (which IIRC is what the two sections are for), C types (these - # regexps don't even start to express all the possibilities), and - # so on. - # - # If you mess with these regexps, it's a good idea to check that - # the following functions' documentation still comes out right: - # - parport_register_device (function pointer parameters) - # - atomic_set (macro) - # - pci_match_device, __copy_to_user (long return type) - my $name = qr{[a-zA-Z0-9_~:]+}; - my $prototype_end1 = qr{[^\(]*}; - my $prototype_end2 = qr{[^\{]*}; - my $prototype_end = qr{\(($prototype_end1|$prototype_end2)\)}; - my $type1 = qr{[\w\s]+}; - my $type2 = qr{$type1\*+}; - - if ($define && $prototype =~ m/^()($name)\s+/) { - # This is an object-like macro, it has no return type and no parameter - # list. - # Function-like macros are not allowed to have spaces between - # declaration_name and opening parenthesis (notice the \s+). - $return_type = $1; - $declaration_name = $2; - $noret = 1; - } elsif ($prototype =~ m/^()($name)\s*$prototype_end/ || - $prototype =~ m/^($type1)\s+($name)\s*$prototype_end/ || - $prototype =~ m/^($type2+)\s*($name)\s*$prototype_end/) { - $return_type = $1; - $declaration_name = $2; - my $args = $3; - - create_parameterlist($args, ',', $file, $declaration_name); - } else { - emit_warning("${file}:$.", "cannot understand function prototype: '$prototype'\n"); - return; - } - - if ($identifier ne $declaration_name) { - emit_warning("${file}:$.", "expecting prototype for $identifier(). Prototype was for $declaration_name() instead\n"); - return; - } - - my $prms = join " ", @parameterlist; - check_sections($file, $declaration_name, "function", $sectcheck, $prms); - - # This check emits a lot of warnings at the moment, because many - # functions don't have a 'Return' doc section. So until the number - # of warnings goes sufficiently down, the check is only performed in - # -Wreturn mode. - # TODO: always perform the check. - if ($Wreturn && !$noret) { - check_return_section($file, $declaration_name, $return_type); - } - - # The function parser can be called with a typedef parameter. - # Handle it. - if ($return_type =~ /typedef/) { - output_declaration($declaration_name, - 'function', - {'function' => $declaration_name, - 'typedef' => 1, - 'module' => $modulename, - 'functiontype' => $return_type, - 'parameterlist' => \@parameterlist, - 'parameterdescs' => \%parameterdescs, - 'parametertypes' => \%parametertypes, - 'sectionlist' => \@sectionlist, - 'sections' => \%sections, - 'purpose' => $declaration_purpose - }); - } else { - output_declaration($declaration_name, - 'function', - {'function' => $declaration_name, - 'module' => $modulename, - 'functiontype' => $return_type, - 'parameterlist' => \@parameterlist, - 'parameterdescs' => \%parameterdescs, - 'parametertypes' => \%parametertypes, - 'sectionlist' => \@sectionlist, - 'sections' => \%sections, - 'purpose' => $declaration_purpose - }); - } -} - -sub reset_state { - $function = ""; - %parameterdescs = (); - %parametertypes = (); - @parameterlist = (); - %sections = (); - @sectionlist = (); - $sectcheck = ""; - $struct_actual = ""; - $prototype = ""; - - $state = STATE_NORMAL; - $inline_doc_state = STATE_INLINE_NA; -} - -sub tracepoint_munge($) { - my $file = shift; - my $tracepointname = 0; - my $tracepointargs = 0; - - if ($prototype =~ m/TRACE_EVENT\((.*?),/) { - $tracepointname = $1; - } - if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) { - $tracepointname = $1; - } - if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) { - $tracepointname = $2; - } - $tracepointname =~ s/^\s+//; #strip leading whitespace - if ($prototype =~ m/TP_PROTO\((.*?)\)/) { - $tracepointargs = $1; - } - if (($tracepointname eq 0) || ($tracepointargs eq 0)) { - emit_warning("${file}:$.", "Unrecognized tracepoint format: \n". - "$prototype\n"); - } else { - $prototype = "static inline void trace_$tracepointname($tracepointargs)"; - $identifier = "trace_$identifier"; - } -} - -sub syscall_munge() { - my $void = 0; - - $prototype =~ s@[\r\n]+@ @gos; # strip newlines/CR's -## if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) { - if ($prototype =~ m/SYSCALL_DEFINE0/) { - $void = 1; -## $prototype = "long sys_$1(void)"; - } - - $prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name - if ($prototype =~ m/long (sys_.*?),/) { - $prototype =~ s/,/\(/; - } elsif ($void) { - $prototype =~ s/\)/\(void\)/; - } - - # now delete all of the odd-number commas in $prototype - # so that arg types & arg names don't have a comma between them - my $count = 0; - my $len = length($prototype); - if ($void) { - $len = 0; # skip the for-loop - } - for (my $ix = 0; $ix < $len; $ix++) { - if (substr($prototype, $ix, 1) eq ',') { - $count++; - if ($count % 2 == 1) { - substr($prototype, $ix, 1) = ' '; - } - } - } -} - -sub process_proto_function($$) { - my $x = shift; - my $file = shift; - - $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line - - if ($x =~ /^#/ && $x !~ /^#\s*define/) { - # do nothing - } elsif ($x =~ /([^\{]*)/) { - $prototype .= $1; - } - - if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) { - $prototype =~ s@/\*.*?\*/@@gos; # strip comments. - $prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's. - $prototype =~ s@^\s+@@gos; # strip leading spaces - - # Handle prototypes for function pointers like: - # int (*pcs_config)(struct foo) - $prototype =~ s@^(\S+\s+)\(\s*\*(\S+)\)@$1$2@gos; - - if ($prototype =~ /SYSCALL_DEFINE/) { - syscall_munge(); - } - if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ || - $prototype =~ /DEFINE_SINGLE_EVENT/) - { - tracepoint_munge($file); - } - dump_function($prototype, $file); - reset_state(); - } -} - -sub process_proto_type($$) { - my $x = shift; - my $file = shift; - - $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's. - $x =~ s@^\s+@@gos; # strip leading spaces - $x =~ s@\s+$@@gos; # strip trailing spaces - $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line - - if ($x =~ /^#/) { - # To distinguish preprocessor directive from regular declaration later. - $x .= ";"; - } - - while (1) { - if ( $x =~ /([^\{\};]*)([\{\};])(.*)/ ) { - if( length $prototype ) { - $prototype .= " " - } - $prototype .= $1 . $2; - ($2 eq '{') && $brcount++; - ($2 eq '}') && $brcount--; - if (($2 eq ';') && ($brcount == 0)) { - dump_declaration($prototype, $file); - reset_state(); - last; - } - $x = $3; - } else { - $prototype .= $x; - last; - } - } -} - - -sub map_filename($) { - my $file; - my ($orig_file) = @_; - - if (defined($ENV{'SRCTREE'})) { - $file = "$ENV{'SRCTREE'}" . "/" . $orig_file; - } else { - $file = $orig_file; - } - - if (defined($source_map{$file})) { - $file = $source_map{$file}; - } - - return $file; -} - -sub process_export_file($) { - my ($orig_file) = @_; - my $file = map_filename($orig_file); - - if (!open(IN,"<$file")) { - print STDERR "Error: Cannot open file $file\n"; - ++$errors; - return; - } - - while (<IN>) { - if (/$export_symbol/) { - next if (defined($nosymbol_table{$2})); - $function_table{$2} = 1; - } - if (/$export_symbol_ns/) { - next if (defined($nosymbol_table{$2})); - $function_table{$2} = 1; - } - } - - close(IN); -} - -# -# Parsers for the various processing states. -# -# STATE_NORMAL: looking for the /** to begin everything. -# -sub process_normal() { - if (/$doc_start/o) { - $state = STATE_NAME; # next line is always the function name - $in_doc_sect = 0; - $declaration_start_line = $. + 1; - } -} - -# -# STATE_NAME: Looking for the "name - description" line -# -sub process_name($$) { - my $file = shift; - my $descr; - - if (/$doc_block/o) { - $state = STATE_DOCBLOCK; - $contents = ""; - $new_start_line = $.; - - if ( $1 eq "" ) { - $section = $section_intro; - } else { - $section = $1; - } - } elsif (/$doc_decl/o) { - $identifier = $1; - my $is_kernel_comment = 0; - my $decl_start = qr{$doc_com}; - # test for pointer declaration type, foo * bar() - desc - my $fn_type = qr{\w+\s*\*\s*}; - my $parenthesis = qr{\(\w*\)}; - my $decl_end = qr{[-:].*}; - if (/^$decl_start([\w\s]+?)$parenthesis?\s*$decl_end?$/) { - $identifier = $1; - } - if ($identifier =~ m/^(struct|union|enum|typedef)\b\s*(\S*)/) { - $decl_type = $1; - $identifier = $2; - $is_kernel_comment = 1; - } - # Look for foo() or static void foo() - description; or misspelt - # identifier - elsif (/^$decl_start$fn_type?(\w+)\s*$parenthesis?\s*$decl_end?$/ || - /^$decl_start$fn_type?(\w+.*)$parenthesis?\s*$decl_end$/) { - $identifier = $1; - $decl_type = 'function'; - $identifier =~ s/^define\s+//; - $is_kernel_comment = 1; - } - $identifier =~ s/\s+$//; - - $state = STATE_BODY; - # if there's no @param blocks need to set up default section - # here - $contents = ""; - $section = $section_default; - $new_start_line = $. + 1; - if (/[-:](.*)/) { - # strip leading/trailing/multiple spaces - $descr= $1; - $descr =~ s/^\s*//; - $descr =~ s/\s*$//; - $descr =~ s/\s+/ /g; - $declaration_purpose = $descr; - $state = STATE_BODY_MAYBE; - } else { - $declaration_purpose = ""; - } - - if (!$is_kernel_comment) { - emit_warning("${file}:$.", "This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n$_"); - $state = STATE_NORMAL; - } - - if (($declaration_purpose eq "") && $Wshort_desc) { - emit_warning("${file}:$.", "missing initial short description on line:\n$_"); - } - - if ($identifier eq "" && $decl_type ne "enum") { - emit_warning("${file}:$.", "wrong kernel-doc identifier on line:\n$_"); - $state = STATE_NORMAL; - } - - if ($verbose) { - print STDERR "${file}:$.: info: Scanning doc for $decl_type $identifier\n"; - } - } else { - emit_warning("${file}:$.", "Cannot understand $_ on line $. - I thought it was a doc line\n"); - $state = STATE_NORMAL; - } -} - - -# -# STATE_BODY and STATE_BODY_MAYBE: the bulk of a kerneldoc comment. -# -sub process_body($$) { - my $file = shift; - - if ($state == STATE_BODY_WITH_BLANK_LINE && /^\s*\*\s?\S/) { - dump_section($file, $section, $contents); - $section = $section_default; - $new_start_line = $.; - $contents = ""; - } - - if (/$doc_sect/i) { # case insensitive for supported section names - $in_doc_sect = 1; - $newsection = $1; - $newcontents = $2; - - # map the supported section names to the canonical names - if ($newsection =~ m/^description$/i) { - $newsection = $section_default; - } elsif ($newsection =~ m/^context$/i) { - $newsection = $section_context; - } elsif ($newsection =~ m/^returns?$/i) { - $newsection = $section_return; - } elsif ($newsection =~ m/^\@return$/) { - # special: @return is a section, not a param description - $newsection = $section_return; - } - - if (($contents ne "") && ($contents ne "\n")) { - if (!$in_doc_sect && $Wcontents_before_sections) { - emit_warning("${file}:$.", "contents before sections\n"); - } - dump_section($file, $section, $contents); - $section = $section_default; - } - - $in_doc_sect = 1; - $state = STATE_BODY; - $contents = $newcontents; - $new_start_line = $.; - while (substr($contents, 0, 1) eq " ") { - $contents = substr($contents, 1); - } - if ($contents ne "") { - $contents .= "\n"; - } - $section = $newsection; - $leading_space = undef; - } elsif (/$doc_end/) { - if (($contents ne "") && ($contents ne "\n")) { - dump_section($file, $section, $contents); - $section = $section_default; - $contents = ""; - } - # look for doc_com + <text> + doc_end: - if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') { - emit_warning("${file}:$.", "suspicious ending line: $_"); - } - - $prototype = ""; - $state = STATE_PROTO; - $brcount = 0; - $new_start_line = $. + 1; - } elsif (/$doc_content/) { - if ($1 eq "") { - if ($section eq $section_context) { - dump_section($file, $section, $contents); - $section = $section_default; - $contents = ""; - $new_start_line = $.; - $state = STATE_BODY; - } else { - if ($section ne $section_default) { - $state = STATE_BODY_WITH_BLANK_LINE; - } else { - $state = STATE_BODY; - } - $contents .= "\n"; - } - } elsif ($state == STATE_BODY_MAYBE) { - # Continued declaration purpose - chomp($declaration_purpose); - $declaration_purpose .= " " . $1; - $declaration_purpose =~ s/\s+/ /g; - } else { - my $cont = $1; - if ($section =~ m/^@/ || $section eq $section_context) { - if (!defined $leading_space) { - if ($cont =~ m/^(\s+)/) { - $leading_space = $1; - } else { - $leading_space = ""; - } - } - $cont =~ s/^$leading_space//; - } - $contents .= $cont . "\n"; - } - } else { - # i dont know - bad line? ignore. - emit_warning("${file}:$.", "bad line: $_"); - } -} - - -# -# STATE_PROTO: reading a function/whatever prototype. -# -sub process_proto($$) { - my $file = shift; - - if (/$doc_inline_oneline/) { - $section = $1; - $contents = $2; - if ($contents ne "") { - $contents .= "\n"; - dump_section($file, $section, $contents); - $section = $section_default; - $contents = ""; - } - } elsif (/$doc_inline_start/) { - $state = STATE_INLINE; - $inline_doc_state = STATE_INLINE_NAME; - } elsif ($decl_type eq 'function') { - process_proto_function($_, $file); - } else { - process_proto_type($_, $file); - } -} - -# -# STATE_DOCBLOCK: within a DOC: block. -# -sub process_docblock($$) { - my $file = shift; - - if (/$doc_end/) { - dump_doc_section($file, $section, $contents); - $section = $section_default; - $contents = ""; - $function = ""; - %parameterdescs = (); - %parametertypes = (); - @parameterlist = (); - %sections = (); - @sectionlist = (); - $prototype = ""; - $state = STATE_NORMAL; - } elsif (/$doc_content/) { - if ( $1 eq "" ) { - $contents .= $blankline; - } else { - $contents .= $1 . "\n"; - } - } -} - -# -# STATE_INLINE: docbook comments within a prototype. -# -sub process_inline($$) { - my $file = shift; - - # First line (state 1) needs to be a @parameter - if ($inline_doc_state == STATE_INLINE_NAME && /$doc_inline_sect/o) { - $section = $1; - $contents = $2; - $new_start_line = $.; - if ($contents ne "") { - while (substr($contents, 0, 1) eq " ") { - $contents = substr($contents, 1); - } - $contents .= "\n"; - } - $inline_doc_state = STATE_INLINE_TEXT; - # Documentation block end */ - } elsif (/$doc_inline_end/) { - if (($contents ne "") && ($contents ne "\n")) { - dump_section($file, $section, $contents); - $section = $section_default; - $contents = ""; - } - $state = STATE_PROTO; - $inline_doc_state = STATE_INLINE_NA; - # Regular text - } elsif (/$doc_content/) { - if ($inline_doc_state == STATE_INLINE_TEXT) { - $contents .= $1 . "\n"; - # nuke leading blank lines - if ($contents =~ /^\s*$/) { - $contents = ""; - } - } elsif ($inline_doc_state == STATE_INLINE_NAME) { - $inline_doc_state = STATE_INLINE_ERROR; - emit_warning("${file}:$.", "Incorrect use of kernel-doc format: $_"); - } - } -} - - -sub process_file($) { - my $file; - my $initial_section_counter = $section_counter; - my ($orig_file) = @_; - - $file = map_filename($orig_file); - - if (!open(IN_FILE,"<$file")) { - print STDERR "Error: Cannot open file $file\n"; - ++$errors; - return; - } - - $. = 1; - - $section_counter = 0; - while (<IN_FILE>) { - while (!/^ \*/ && s/\\\s*$//) { - $_ .= <IN_FILE>; - } - # Replace tabs by spaces - while ($_ =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}; - # Hand this line to the appropriate state handler - if ($state == STATE_NORMAL) { - process_normal(); - } elsif ($state == STATE_NAME) { - process_name($file, $_); - } elsif ($state == STATE_BODY || $state == STATE_BODY_MAYBE || - $state == STATE_BODY_WITH_BLANK_LINE) { - process_body($file, $_); - } elsif ($state == STATE_INLINE) { # scanning for inline parameters - process_inline($file, $_); - } elsif ($state == STATE_PROTO) { - process_proto($file, $_); - } elsif ($state == STATE_DOCBLOCK) { - process_docblock($file, $_); - } - } - - # Make sure we got something interesting. - if ($initial_section_counter == $section_counter && $ - output_mode ne "none") { - if ($output_selection == OUTPUT_INCLUDE) { - emit_warning("${file}:1", "'$_' not found\n") - for keys %function_table; - } else { - emit_warning("${file}:1", "no structured comments found\n"); - } - } - close IN_FILE; -} - - -if ($output_mode eq "rst") { - get_sphinx_version() if (!$sphinx_major); -} - -$kernelversion = get_kernel_version(); - -# generate a sequence of code that will splice in highlighting information -# using the s// operator. -for (my $k = 0; $k < @highlights; $k++) { - my $pattern = $highlights[$k][0]; - my $result = $highlights[$k][1]; -# print STDERR "scanning pattern:$pattern, highlight:($result)\n"; - $dohighlight .= "\$contents =~ s:$pattern:$result:gs;\n"; -} - -# Read the file that maps relative names to absolute names for -# separate source and object directories and for shadow trees. -if (open(SOURCE_MAP, "<.tmp_filelist.txt")) { - my ($relname, $absname); - while(<SOURCE_MAP>) { - chop(); - ($relname, $absname) = (split())[0..1]; - $relname =~ s:^/+::; - $source_map{$relname} = $absname; - } - close(SOURCE_MAP); -} - -if ($output_selection == OUTPUT_EXPORTED || - $output_selection == OUTPUT_INTERNAL) { - - push(@export_file_list, @ARGV); - - foreach (@export_file_list) { - chomp; - process_export_file($_); - } -} - -foreach (@ARGV) { - chomp; - process_file($_); -} -if ($verbose && $errors) { - print STDERR "$errors errors\n"; -} -if ($verbose && $warnings) { - print STDERR "$warnings warnings\n"; -} - -if ($Werror && $warnings) { - print STDERR "$warnings warnings as Errors\n"; - exit($warnings); -} else { - exit($output_mode eq "none" ? 0 : $errors) -} - -__END__ - -=head1 OPTIONS - -=head2 Output format selection (mutually exclusive): - -=over 8 - -=item -man - -Output troff manual page format. - -=item -rst - -Output reStructuredText format. This is the default. - -=item -none - -Do not output documentation, only warnings. - -=back - -=head2 Output format modifiers - -=head3 reStructuredText only - -=over 8 - -=item -sphinx-version VERSION - -Use the ReST C domain dialect compatible with a specific Sphinx Version. - -If not specified, kernel-doc will auto-detect using the sphinx-build version -found on PATH. - -=back - -=head2 Output selection (mutually exclusive): - -=over 8 - -=item -export - -Only output documentation for the symbols that have been exported using -EXPORT_SYMBOL() and related macros in any input FILE or -export-file FILE. - -=item -internal - -Only output documentation for the symbols that have NOT been exported using -EXPORT_SYMBOL() and related macros in any input FILE or -export-file FILE. - -=item -function NAME - -Only output documentation for the given function or DOC: section title. -All other functions and DOC: sections are ignored. - -May be specified multiple times. - -=item -nosymbol NAME - -Exclude the specified symbol from the output documentation. - -May be specified multiple times. - -=back - -=head2 Output selection modifiers: - -=over 8 - -=item -no-doc-sections - -Do not output DOC: sections. - -=item -export-file FILE - -Specify an additional FILE in which to look for EXPORT_SYMBOL information. - -To be used with -export or -internal. - -May be specified multiple times. - -=back - -=head3 reStructuredText only - -=over 8 - -=item -enable-lineno - -Enable output of .. LINENO lines. - -=back - -=head2 Other parameters: - -=over 8 - -=item -h, -help - -Print this help. - -=item -v - -Verbose output, more warnings and other information. - -=item -Werror - -Treat warnings as errors. - -=back - -=cut +kernel-doc.py
\ No newline at end of file diff --git a/scripts/kernel-doc.pl b/scripts/kernel-doc.pl new file mode 100755 index 000000000000..5db23cbf4eb2 --- /dev/null +++ b/scripts/kernel-doc.pl @@ -0,0 +1,2439 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# vim: softtabstop=4 + +use warnings; +use strict; + +## Copyright (c) 1998 Michael Zucchi, All Rights Reserved ## +## Copyright (C) 2000, 1 Tim Waugh <twaugh@redhat.com> ## +## Copyright (C) 2001 Simon Huggins ## +## Copyright (C) 2005-2012 Randy Dunlap ## +## Copyright (C) 2012 Dan Luedtke ## +## ## +## #define enhancements by Armin Kuster <akuster@mvista.com> ## +## Copyright (c) 2000 MontaVista Software, Inc. ## +# +# Copyright (C) 2022 Tomasz Warniełło (POD) + +use Pod::Usage qw/pod2usage/; + +=head1 NAME + +kernel-doc - Print formatted kernel documentation to stdout + +=head1 SYNOPSIS + + kernel-doc [-h] [-v] [-Werror] [-Wall] [-Wreturn] [-Wshort-desc[ription]] [-Wcontents-before-sections] + [ -man | + -rst [-enable-lineno] | + -none + ] + [ + -export | + -internal | + [-function NAME] ... | + [-nosymbol NAME] ... + ] + [-no-doc-sections] + [-export-file FILE] ... + FILE ... + +Run `kernel-doc -h` for details. + +=head1 DESCRIPTION + +Read C language source or header FILEs, extract embedded documentation comments, +and print formatted documentation to standard output. + +The documentation comments are identified by the "/**" opening comment mark. + +See Documentation/doc-guide/kernel-doc.rst for the documentation comment syntax. + +=cut + +# more perldoc at the end of the file + +## init lots of data + +my $errors = 0; +my $warnings = 0; +my $anon_struct_union = 0; + +# match expressions used to find embedded type information +my $type_constant = '\b``([^\`]+)``\b'; +my $type_constant2 = '\%([-_*\w]+)'; +my $type_func = '(\w+)\(\)'; +my $type_param = '\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)'; +my $type_param_ref = '([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)'; +my $type_fp_param = '\@(\w+)\(\)'; # Special RST handling for func ptr params +my $type_fp_param2 = '\@(\w+->\S+)\(\)'; # Special RST handling for structs with func ptr params +my $type_env = '(\$\w+)'; +my $type_enum = '\&(enum\s*([_\w]+))'; +my $type_struct = '\&(struct\s*([_\w]+))'; +my $type_typedef = '\&(typedef\s*([_\w]+))'; +my $type_union = '\&(union\s*([_\w]+))'; +my $type_member = '\&([_\w]+)(\.|->)([_\w]+)'; +my $type_fallback = '\&([_\w]+)'; +my $type_member_func = $type_member . '\(\)'; + +# Output conversion substitutions. +# One for each output format + +# these are pretty rough +my @highlights_man = ( + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_func, "\\\\fB\$1\\\\fP"], + [$type_enum, "\\\\fI\$1\\\\fP"], + [$type_struct, "\\\\fI\$1\\\\fP"], + [$type_typedef, "\\\\fI\$1\\\\fP"], + [$type_union, "\\\\fI\$1\\\\fP"], + [$type_param, "\\\\fI\$1\\\\fP"], + [$type_param_ref, "\\\\fI\$1\$2\\\\fP"], + [$type_member, "\\\\fI\$1\$2\$3\\\\fP"], + [$type_fallback, "\\\\fI\$1\\\\fP"] + ); +my $blankline_man = ""; + +# rst-mode +my @highlights_rst = ( + [$type_constant, "``\$1``"], + [$type_constant2, "``\$1``"], + + # Note: need to escape () to avoid func matching later + [$type_member_func, "\\:c\\:type\\:`\$1\$2\$3\\\\(\\\\) <\$1>`"], + [$type_member, "\\:c\\:type\\:`\$1\$2\$3 <\$1>`"], + [$type_fp_param, "**\$1\\\\(\\\\)**"], + [$type_fp_param2, "**\$1\\\\(\\\\)**"], + [$type_func, "\$1()"], + [$type_enum, "\\:c\\:type\\:`\$1 <\$2>`"], + [$type_struct, "\\:c\\:type\\:`\$1 <\$2>`"], + [$type_typedef, "\\:c\\:type\\:`\$1 <\$2>`"], + [$type_union, "\\:c\\:type\\:`\$1 <\$2>`"], + + # in rst this can refer to any type + [$type_fallback, "\\:c\\:type\\:`\$1`"], + [$type_param_ref, "**\$1\$2**"] + ); +my $blankline_rst = "\n"; + +# read arguments +if ($#ARGV == -1) { + pod2usage( + -message => "No arguments!\n", + -exitval => 1, + -verbose => 99, + -sections => 'SYNOPSIS', + -output => \*STDERR, + ); +} + +my $kernelversion; + +my $dohighlight = ""; + +my $verbose = 0; +my $Werror = 0; +my $Wreturn = 0; +my $Wshort_desc = 0; +my $output_mode = "rst"; +my $output_preformatted = 0; +my $no_doc_sections = 0; +my $enable_lineno = 0; +my @highlights = @highlights_rst; +my $blankline = $blankline_rst; +my $modulename = "Kernel API"; + +use constant { + OUTPUT_ALL => 0, # output all symbols and doc sections + OUTPUT_INCLUDE => 1, # output only specified symbols + OUTPUT_EXPORTED => 2, # output exported symbols + OUTPUT_INTERNAL => 3, # output non-exported symbols +}; +my $output_selection = OUTPUT_ALL; +my $show_not_found = 0; # No longer used + +my @export_file_list; + +my @build_time; +if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) && + (my $seconds = `date -d "${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') { + @build_time = gmtime($seconds); +} else { + @build_time = localtime; +} + +my $man_date = ('January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', + 'November', 'December')[$build_time[4]] . + " " . ($build_time[5]+1900); + +# Essentially these are globals. +# They probably want to be tidied up, made more localised or something. +# CAVEAT EMPTOR! Some of the others I localised may not want to be, which +# could cause "use of undefined value" or other bugs. +my ($function, %function_table, %parametertypes, $declaration_purpose); +my %nosymbol_table = (); +my $declaration_start_line; +my ($type, $declaration_name, $return_type); +my ($newsection, $newcontents, $prototype, $brcount); + +if (defined($ENV{'KBUILD_VERBOSE'}) && $ENV{'KBUILD_VERBOSE'} =~ '1') { + $verbose = 1; +} + +if (defined($ENV{'KCFLAGS'})) { + my $kcflags = "$ENV{'KCFLAGS'}"; + + if ($kcflags =~ /(\s|^)-Werror(\s|$)/) { + $Werror = 1; + } +} + +# reading this variable is for backwards compat just in case +# someone was calling it with the variable from outside the +# kernel's build system +if (defined($ENV{'KDOC_WERROR'})) { + $Werror = "$ENV{'KDOC_WERROR'}"; +} +# other environment variables are converted to command-line +# arguments in cmd_checkdoc in the build system + +# Generated docbook code is inserted in a template at a point where +# docbook v3.1 requires a non-zero sequence of RefEntry's; see: +# https://www.oasis-open.org/docbook/documentation/reference/html/refentry.html +# We keep track of number of generated entries and generate a dummy +# if needs be to ensure the expanded template can be postprocessed +# into html. +my $section_counter = 0; + +my $lineprefix=""; + +# Parser states +use constant { + STATE_NORMAL => 0, # normal code + STATE_NAME => 1, # looking for function name + STATE_BODY_MAYBE => 2, # body - or maybe more description + STATE_BODY => 3, # the body of the comment + STATE_BODY_WITH_BLANK_LINE => 4, # the body, which has a blank line + STATE_PROTO => 5, # scanning prototype + STATE_DOCBLOCK => 6, # documentation block + STATE_INLINE => 7, # gathering doc outside main block +}; +my $state; +my $leading_space; + +# Inline documentation state +use constant { + STATE_INLINE_NA => 0, # not applicable ($state != STATE_INLINE) + STATE_INLINE_NAME => 1, # looking for member name (@foo:) + STATE_INLINE_TEXT => 2, # looking for member documentation + STATE_INLINE_END => 3, # done + STATE_INLINE_ERROR => 4, # error - Comment without header was found. + # Spit a warning as it's not + # proper kernel-doc and ignore the rest. +}; +my $inline_doc_state; + +#declaration types: can be +# 'function', 'struct', 'union', 'enum', 'typedef' +my $decl_type; + +# Name of the kernel-doc identifier for non-DOC markups +my $identifier; + +my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start. +my $doc_end = '\*/'; +my $doc_com = '\s*\*\s*'; +my $doc_com_body = '\s*\* ?'; +my $doc_decl = $doc_com . '(\w+)'; +# @params and a strictly limited set of supported section names +# Specifically: +# Match @word: +# @...: +# @{section-name}: +# while trying to not match literal block starts like "example::" +# +my $doc_sect = $doc_com . + '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:([^:].*)?$'; +my $doc_content = $doc_com_body . '(.*)'; +my $doc_block = $doc_com . 'DOC:\s*(.*)?'; +my $doc_inline_start = '^\s*/\*\*\s*$'; +my $doc_inline_sect = '\s*\*\s*(@\s*[\w][\w\.]*\s*):(.*)'; +my $doc_inline_end = '^\s*\*/\s*$'; +my $doc_inline_oneline = '^\s*/\*\*\s*(@[\w\s]+):\s*(.*)\s*\*/\s*$'; +my $export_symbol = '^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*;'; +my $export_symbol_ns = '^\s*EXPORT_SYMBOL_NS(_GPL)?\s*\(\s*(\w+)\s*,\s*"\S+"\)\s*;'; +my $function_pointer = qr{([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)}; +my $attribute = qr{__attribute__\s*\(\([a-z0-9,_\*\s\(\)]*\)\)}i; + +my %parameterdescs; +my %parameterdesc_start_lines; +my @parameterlist; +my %sections; +my @sectionlist; +my %section_start_lines; +my $sectcheck; +my $struct_actual; + +my $contents = ""; +my $new_start_line = 0; + +# the canonical section names. see also $doc_sect above. +my $section_default = "Description"; # default section +my $section_intro = "Introduction"; +my $section = $section_default; +my $section_context = "Context"; +my $section_return = "Return"; + +my $undescribed = "-- undescribed --"; + +reset_state(); + +while ($ARGV[0] =~ m/^--?(.*)/) { + my $cmd = $1; + shift @ARGV; + if ($cmd eq "man") { + $output_mode = "man"; + @highlights = @highlights_man; + $blankline = $blankline_man; + } elsif ($cmd eq "rst") { + $output_mode = "rst"; + @highlights = @highlights_rst; + $blankline = $blankline_rst; + } elsif ($cmd eq "none") { + $output_mode = "none"; + } elsif ($cmd eq "module") { # not needed for XML, inherits from calling document + $modulename = shift @ARGV; + } elsif ($cmd eq "function") { # to only output specific functions + $output_selection = OUTPUT_INCLUDE; + $function = shift @ARGV; + $function_table{$function} = 1; + } elsif ($cmd eq "nosymbol") { # Exclude specific symbols + my $symbol = shift @ARGV; + $nosymbol_table{$symbol} = 1; + } elsif ($cmd eq "export") { # only exported symbols + $output_selection = OUTPUT_EXPORTED; + %function_table = (); + } elsif ($cmd eq "internal") { # only non-exported symbols + $output_selection = OUTPUT_INTERNAL; + %function_table = (); + } elsif ($cmd eq "export-file") { + my $file = shift @ARGV; + push(@export_file_list, $file); + } elsif ($cmd eq "v") { + $verbose = 1; + } elsif ($cmd eq "Werror") { + $Werror = 1; + } elsif ($cmd eq "Wreturn") { + $Wreturn = 1; + } elsif ($cmd eq "Wshort-desc" or $cmd eq "Wshort-description") { + $Wshort_desc = 1; + } elsif ($cmd eq "Wall") { + $Wreturn = 1; + $Wshort_desc = 1; + } elsif (($cmd eq "h") || ($cmd eq "help")) { + pod2usage(-exitval => 0, -verbose => 2); + } elsif ($cmd eq 'no-doc-sections') { + $no_doc_sections = 1; + } elsif ($cmd eq 'enable-lineno') { + $enable_lineno = 1; + } elsif ($cmd eq 'show-not-found') { + $show_not_found = 1; # A no-op but don't fail + } else { + # Unknown argument + pod2usage( + -message => "Argument unknown!\n", + -exitval => 1, + -verbose => 99, + -sections => 'SYNOPSIS', + -output => \*STDERR, + ); + } + if ($#ARGV < 0){ + pod2usage( + -message => "FILE argument missing\n", + -exitval => 1, + -verbose => 99, + -sections => 'SYNOPSIS', + -output => \*STDERR, + ); + } +} + +# continue execution near EOF; + +sub findprog($) +{ + foreach(split(/:/, $ENV{PATH})) { + return "$_/$_[0]" if(-x "$_/$_[0]"); + } +} + +# get kernel version from env +sub get_kernel_version() { + my $version = 'unknown kernel version'; + + if (defined($ENV{'KERNELVERSION'})) { + $version = $ENV{'KERNELVERSION'}; + } + return $version; +} + +# +sub print_lineno { + my $lineno = shift; + if ($enable_lineno && defined($lineno)) { + print ".. LINENO " . $lineno . "\n"; + } +} + +sub emit_warning { + my $location = shift; + my $msg = shift; + print STDERR "$location: warning: $msg"; + ++$warnings; +} +## +# dumps section contents to arrays/hashes intended for that purpose. +# +sub dump_section { + my $file = shift; + my $name = shift; + my $contents = join "\n", @_; + + if ($name =~ m/$type_param/) { + $name = $1; + $parameterdescs{$name} = $contents; + $sectcheck = $sectcheck . $name . " "; + $parameterdesc_start_lines{$name} = $new_start_line; + $new_start_line = 0; + } elsif ($name eq "@\.\.\.") { + $name = "..."; + $parameterdescs{$name} = $contents; + $sectcheck = $sectcheck . $name . " "; + $parameterdesc_start_lines{$name} = $new_start_line; + $new_start_line = 0; + } else { + if (defined($sections{$name}) && ($sections{$name} ne "")) { + # Only warn on user specified duplicate section names. + if ($name ne $section_default) { + emit_warning("${file}:$.", "duplicate section name '$name'\n"); + } + $sections{$name} .= $contents; + } else { + $sections{$name} = $contents; + push @sectionlist, $name; + $section_start_lines{$name} = $new_start_line; + $new_start_line = 0; + } + } +} + +## +# dump DOC: section after checking that it should go out +# +sub dump_doc_section { + my $file = shift; + my $name = shift; + my $contents = join "\n", @_; + + if ($no_doc_sections) { + return; + } + + return if (defined($nosymbol_table{$name})); + + if (($output_selection == OUTPUT_ALL) || + (($output_selection == OUTPUT_INCLUDE) && + defined($function_table{$name}))) + { + dump_section($file, $name, $contents); + output_blockhead({'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'module' => $modulename, + 'content-only' => ($output_selection != OUTPUT_ALL), }); + } +} + +## +# output function +# +# parameterdescs, a hash. +# function => "function name" +# parameterlist => @list of parameters +# parameterdescs => %parameter descriptions +# sectionlist => @list of sections +# sections => %section descriptions +# + +sub output_highlight { + my $contents = join "\n",@_; + my $line; + +# DEBUG +# if (!defined $contents) { +# use Carp; +# confess "output_highlight got called with no args?\n"; +# } + +# print STDERR "contents b4:$contents\n"; + eval $dohighlight; + die $@ if $@; +# print STDERR "contents af:$contents\n"; + + foreach $line (split "\n", $contents) { + if (! $output_preformatted) { + $line =~ s/^\s*//; + } + if ($line eq ""){ + if (! $output_preformatted) { + print $lineprefix, $blankline; + } + } else { + if ($output_mode eq "man" && substr($line, 0, 1) eq ".") { + print "\\&$line"; + } else { + print $lineprefix, $line; + } + } + print "\n"; + } +} + +## +# output function in man +sub output_function_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $func_macro = $args{'func_macro'}; + my $paramcount = $#{$args{'parameterlist'}}; # -1 is empty + + print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n"; + + print ".SH NAME\n"; + print $args{'function'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + if ($args{'functiontype'} ne "") { + print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n"; + } else { + print ".B \"" . $args{'function'} . "\n"; + } + $count = 0; + my $parenth = "("; + my $post = ","; + foreach my $parameter (@{$args{'parameterlist'}}) { + if ($count == $#{$args{'parameterlist'}}) { + $post = ");"; + } + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/$function_pointer/) { + # pointer-to-function + print ".BI \"" . $parenth . $1 . "\" " . " \") (" . $2 . ")" . $post . "\"\n"; + } else { + $type =~ s/([^\*])$/$1 /; + print ".BI \"" . $parenth . $type . "\" " . " \"" . $post . "\"\n"; + } + $count++; + $parenth = ""; + } + + $paramcount = $#{$args{'parameterlist'}}; # -1 is empty + if ($paramcount >= 0) { + print ".SH ARGUMENTS\n"; + } + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"", uc $section, "\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output enum in man +sub output_enum_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + print "enum " . $args{'enum'} . " {\n"; + $count = 0; + foreach my $parameter (@{$args{'parameterlist'}}) { + print ".br\n.BI \" $parameter\"\n"; + if ($count == $#{$args{'parameterlist'}}) { + print "\n};\n"; + last; + } else { + print ", \n.br\n"; + } + $count++; + } + + print ".SH Constants\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output struct in man +sub output_struct_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n"; + + my $declaration = $args{'definition'}; + $declaration =~ s/\t/ /g; + $declaration =~ s/\n/"\n.br\n.BI \"/g; + print ".SH SYNOPSIS\n"; + print $args{'type'} . " " . $args{'struct'} . " {\n.br\n"; + print ".BI \"$declaration\n};\n.br\n\n"; + + print ".SH Members\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output typedef in man +sub output_typedef_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n"; + + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +sub output_blockhead_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output in restructured text +# + +# +# This could use some work; it's used to output the DOC: sections, and +# starts by putting out the name of the doc section itself, but that tends +# to duplicate a header already in the template file. +# +sub output_blockhead_rst(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + foreach $section (@{$args{'sectionlist'}}) { + next if (defined($nosymbol_table{$section})); + + if ($output_selection != OUTPUT_INCLUDE) { + print ".. _$section:\n\n"; + print "**$section**\n\n"; + } + print_lineno($section_start_lines{$section}); + output_highlight_rst($args{'sections'}{$section}); + print "\n"; + } +} + +# +# Apply the RST highlights to a sub-block of text. +# +sub highlight_block($) { + # The dohighlight kludge requires the text be called $contents + my $contents = shift; + eval $dohighlight; + die $@ if $@; + return $contents; +} + +# +# Regexes used only here. +# +my $sphinx_literal = '^[^.].*::$'; +my $sphinx_cblock = '^\.\.\ +code-block::'; + +sub output_highlight_rst { + my $input = join "\n",@_; + my $output = ""; + my $line; + my $in_literal = 0; + my $litprefix; + my $block = ""; + + foreach $line (split "\n",$input) { + # + # If we're in a literal block, see if we should drop out + # of it. Otherwise pass the line straight through unmunged. + # + if ($in_literal) { + if (! ($line =~ /^\s*$/)) { + # + # If this is the first non-blank line in a literal + # block we need to figure out what the proper indent is. + # + if ($litprefix eq "") { + $line =~ /^(\s*)/; + $litprefix = '^' . $1; + $output .= $line . "\n"; + } elsif (! ($line =~ /$litprefix/)) { + $in_literal = 0; + } else { + $output .= $line . "\n"; + } + } else { + $output .= $line . "\n"; + } + } + # + # Not in a literal block (or just dropped out) + # + if (! $in_literal) { + $block .= $line . "\n"; + if (($line =~ /$sphinx_literal/) || ($line =~ /$sphinx_cblock/)) { + $in_literal = 1; + $litprefix = ""; + $output .= highlight_block($block); + $block = "" + } + } + } + + if ($block) { + $output .= highlight_block($block); + } + + $output =~ s/^\n+//g; + $output =~ s/\n+$//g; + + foreach $line (split "\n", $output) { + print $lineprefix . $line . "\n"; + } +} + +sub output_function_rst(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $oldprefix = $lineprefix; + + my $signature = ""; + my $func_macro = $args{'func_macro'}; + my $paramcount = $#{$args{'parameterlist'}}; # -1 is empty + + if ($func_macro) { + $signature = $args{'function'}; + } else { + if ($args{'functiontype'}) { + $signature = $args{'functiontype'} . " "; + } + $signature .= $args{'function'} . " ("; + } + + my $count = 0; + foreach my $parameter (@{$args{'parameterlist'}}) { + if ($count ne 0) { + $signature .= ", "; + } + $count++; + $type = $args{'parametertypes'}{$parameter}; + + if ($type =~ m/$function_pointer/) { + # pointer-to-function + $signature .= $1 . $parameter . ") (" . $2 . ")"; + } else { + $signature .= $type; + } + } + + if (!$func_macro) { + $signature .= ")"; + } + + if ($args{'typedef'} || $args{'functiontype'} eq "") { + print ".. c:macro:: ". $args{'function'} . "\n\n"; + + if ($args{'typedef'}) { + print_lineno($declaration_start_line); + print " **Typedef**: "; + $lineprefix = ""; + output_highlight_rst($args{'purpose'}); + print "\n\n**Syntax**\n\n"; + print " ``$signature``\n\n"; + } else { + print "``$signature``\n\n"; + } + } else { + print ".. c:function:: $signature\n\n"; + } + + if (!$args{'typedef'}) { + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + } + + # + # Put our descriptive text into a container (thus an HTML <div>) to help + # set the function prototypes apart. + # + $lineprefix = " "; + if ($paramcount >= 0) { + print ".. container:: kernelindent\n\n"; + print $lineprefix . "**Parameters**\n\n"; + } + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + $type = $args{'parametertypes'}{$parameter}; + + if ($type ne "") { + print $lineprefix . "``$type``\n"; + } else { + print $lineprefix . "``$parameter``\n"; + } + + print_lineno($parameterdesc_start_lines{$parameter_name}); + + $lineprefix = " "; + if (defined($args{'parameterdescs'}{$parameter_name}) && + $args{'parameterdescs'}{$parameter_name} ne $undescribed) { + output_highlight_rst($args{'parameterdescs'}{$parameter_name}); + } else { + print $lineprefix . "*undescribed*\n"; + } + $lineprefix = " "; + print "\n"; + } + + output_section_rst(@_); + $lineprefix = $oldprefix; +} + +sub output_section_rst(%) { + my %args = %{$_[0]}; + my $section; + my $oldprefix = $lineprefix; + + foreach $section (@{$args{'sectionlist'}}) { + print $lineprefix . "**$section**\n\n"; + print_lineno($section_start_lines{$section}); + output_highlight_rst($args{'sections'}{$section}); + print "\n"; + } + print "\n"; +} + +sub output_enum_rst(%) { + my %args = %{$_[0]}; + my ($parameter); + my $oldprefix = $lineprefix; + my $count; + my $outer; + + my $name = $args{'enum'}; + print "\n\n.. c:enum:: " . $name . "\n\n"; + + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + + print ".. container:: kernelindent\n\n"; + $outer = $lineprefix . " "; + $lineprefix = $outer . " "; + print $outer . "**Constants**\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print $outer . "``$parameter``\n"; + + if ($args{'parameterdescs'}{$parameter} ne $undescribed) { + output_highlight_rst($args{'parameterdescs'}{$parameter}); + } else { + print $lineprefix . "*undescribed*\n"; + } + print "\n"; + } + print "\n"; + $lineprefix = $oldprefix; + output_section_rst(@_); +} + +sub output_typedef_rst(%) { + my %args = %{$_[0]}; + my ($parameter); + my $oldprefix = $lineprefix; + my $name; + + $name = $args{'typedef'}; + + print "\n\n.. c:type:: " . $name . "\n\n"; + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + + $lineprefix = $oldprefix; + output_section_rst(@_); +} + +sub output_struct_rst(%) { + my %args = %{$_[0]}; + my ($parameter); + my $oldprefix = $lineprefix; + + my $name = $args{'struct'}; + if ($args{'type'} eq 'union') { + print "\n\n.. c:union:: " . $name . "\n\n"; + } else { + print "\n\n.. c:struct:: " . $name . "\n\n"; + } + + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + + print ".. container:: kernelindent\n\n"; + print $lineprefix . "**Definition**::\n\n"; + my $declaration = $args{'definition'}; + $lineprefix = $lineprefix . " "; + $declaration =~ s/\t/$lineprefix/g; + print $lineprefix . $args{'type'} . " " . $args{'struct'} . " {\n$declaration" . $lineprefix . "};\n\n"; + + $lineprefix = " "; + print $lineprefix . "**Members**\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + print_lineno($parameterdesc_start_lines{$parameter_name}); + print $lineprefix . "``" . $parameter . "``\n"; + $lineprefix = " "; + output_highlight_rst($args{'parameterdescs'}{$parameter_name}); + $lineprefix = " "; + print "\n"; + } + print "\n"; + + $lineprefix = $oldprefix; + output_section_rst(@_); +} + +## none mode output functions + +sub output_function_none(%) { +} + +sub output_enum_none(%) { +} + +sub output_typedef_none(%) { +} + +sub output_struct_none(%) { +} + +sub output_blockhead_none(%) { +} + +## +# generic output function for all types (function, struct/union, typedef, enum); +# calls the generated, variable output_ function name based on +# functype and output_mode +sub output_declaration { + no strict 'refs'; + my $name = shift; + my $functype = shift; + my $func = "output_${functype}_$output_mode"; + + return if (defined($nosymbol_table{$name})); + + if (($output_selection == OUTPUT_ALL) || + (($output_selection == OUTPUT_INCLUDE || + $output_selection == OUTPUT_EXPORTED) && + defined($function_table{$name})) || + ($output_selection == OUTPUT_INTERNAL && + !($functype eq "function" && defined($function_table{$name})))) + { + &$func(@_); + $section_counter++; + } +} + +## +# generic output function - calls the right one based on current output mode. +sub output_blockhead { + no strict 'refs'; + my $func = "output_blockhead_" . $output_mode; + &$func(@_); + $section_counter++; +} + +## +# takes a declaration (struct, union, enum, typedef) and +# invokes the right handler. NOT called for functions. +sub dump_declaration($$) { + no strict 'refs'; + my ($prototype, $file) = @_; + my $func = "dump_" . $decl_type; + &$func(@_); +} + +sub dump_union($$) { + dump_struct(@_); +} + +sub dump_struct($$) { + my $x = shift; + my $file = shift; + my $decl_type; + my $members; + my $type = qr{struct|union}; + # For capturing struct/union definition body, i.e. "{members*}qualifiers*" + my $qualifiers = qr{$attribute|__packed|__aligned|____cacheline_aligned_in_smp|____cacheline_aligned}; + my $definition_body = qr{\{(.*)\}\s*$qualifiers*}; + my $struct_members = qr{($type)([^\{\};]+)\{([^\{\}]*)\}([^\{\}\;]*)\;}; + + if ($x =~ /($type)\s+(\w+)\s*$definition_body/) { + $decl_type = $1; + $declaration_name = $2; + $members = $3; + } elsif ($x =~ /typedef\s+($type)\s*$definition_body\s*(\w+)\s*;/) { + $decl_type = $1; + $declaration_name = $3; + $members = $2; + } + + if ($members) { + if ($identifier ne $declaration_name) { + emit_warning("${file}:$.", "expecting prototype for $decl_type $identifier. Prototype was for $decl_type $declaration_name instead\n"); + return; + } + + # ignore members marked private: + $members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi; + $members =~ s/\/\*\s*private:.*//gosi; + # strip comments: + $members =~ s/\/\*.*?\*\///gos; + # strip attributes + $members =~ s/\s*$attribute/ /gi; + $members =~ s/\s*__aligned\s*\([^;]*\)/ /gos; + $members =~ s/\s*__counted_by\s*\([^;]*\)/ /gos; + $members =~ s/\s*__counted_by_(le|be)\s*\([^;]*\)/ /gos; + $members =~ s/\s*__packed\s*/ /gos; + $members =~ s/\s*CRYPTO_MINALIGN_ATTR/ /gos; + $members =~ s/\s*____cacheline_aligned_in_smp/ /gos; + $members =~ s/\s*____cacheline_aligned/ /gos; + # unwrap struct_group(): + # - first eat non-declaration parameters and rewrite for final match + # - then remove macro, outer parens, and trailing semicolon + $members =~ s/\bstruct_group\s*\(([^,]*,)/STRUCT_GROUP(/gos; + $members =~ s/\bstruct_group_attr\s*\(([^,]*,){2}/STRUCT_GROUP(/gos; + $members =~ s/\bstruct_group_tagged\s*\(([^,]*),([^,]*),/struct $1 $2; STRUCT_GROUP(/gos; + $members =~ s/\b__struct_group\s*\(([^,]*,){3}/STRUCT_GROUP(/gos; + $members =~ s/\bSTRUCT_GROUP(\(((?:(?>[^)(]+)|(?1))*)\))[^;]*;/$2/gos; + + my $args = qr{([^,)]+)}; + # replace DECLARE_BITMAP + $members =~ s/__ETHTOOL_DECLARE_LINK_MODE_MASK\s*\(([^\)]+)\)/DECLARE_BITMAP($1, __ETHTOOL_LINK_MODE_MASK_NBITS)/gos; + $members =~ s/DECLARE_PHY_INTERFACE_MASK\s*\(([^\)]+)\)/DECLARE_BITMAP($1, PHY_INTERFACE_MODE_MAX)/gos; + $members =~ s/DECLARE_BITMAP\s*\($args,\s*$args\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos; + # replace DECLARE_HASHTABLE + $members =~ s/DECLARE_HASHTABLE\s*\($args,\s*$args\)/unsigned long $1\[1 << (($2) - 1)\]/gos; + # replace DECLARE_KFIFO + $members =~ s/DECLARE_KFIFO\s*\($args,\s*$args,\s*$args\)/$2 \*$1/gos; + # replace DECLARE_KFIFO_PTR + $members =~ s/DECLARE_KFIFO_PTR\s*\($args,\s*$args\)/$2 \*$1/gos; + # replace DECLARE_FLEX_ARRAY + $members =~ s/(?:__)?DECLARE_FLEX_ARRAY\s*\($args,\s*$args\)/$1 $2\[\]/gos; + #replace DEFINE_DMA_UNMAP_ADDR + $members =~ s/DEFINE_DMA_UNMAP_ADDR\s*\($args\)/dma_addr_t $1/gos; + #replace DEFINE_DMA_UNMAP_LEN + $members =~ s/DEFINE_DMA_UNMAP_LEN\s*\($args\)/__u32 $1/gos; + my $declaration = $members; + + # Split nested struct/union elements as newer ones + while ($members =~ m/$struct_members/) { + my $newmember; + my $maintype = $1; + my $ids = $4; + my $content = $3; + foreach my $id(split /,/, $ids) { + $newmember .= "$maintype $id; "; + + $id =~ s/[:\[].*//; + $id =~ s/^\s*\**(\S+)\s*/$1/; + foreach my $arg (split /;/, $content) { + next if ($arg =~ m/^\s*$/); + if ($arg =~ m/^([^\(]+\(\*?\s*)([\w\.]*)(\s*\).*)/) { + # pointer-to-function + my $type = $1; + my $name = $2; + my $extra = $3; + next if (!$name); + if ($id =~ m/^\s*$/) { + # anonymous struct/union + $newmember .= "$type$name$extra; "; + } else { + $newmember .= "$type$id.$name$extra; "; + } + } else { + my $type; + my $names; + $arg =~ s/^\s+//; + $arg =~ s/\s+$//; + # Handle bitmaps + $arg =~ s/:\s*\d+\s*//g; + # Handle arrays + $arg =~ s/\[.*\]//g; + # The type may have multiple words, + # and multiple IDs can be defined, like: + # const struct foo, *bar, foobar + # So, we remove spaces when parsing the + # names, in order to match just names + # and commas for the names + $arg =~ s/\s*,\s*/,/g; + if ($arg =~ m/(.*)\s+([\S+,]+)/) { + $type = $1; + $names = $2; + } else { + $newmember .= "$arg; "; + next; + } + foreach my $name (split /,/, $names) { + $name =~ s/^\s*\**(\S+)\s*/$1/; + next if (($name =~ m/^\s*$/)); + if ($id =~ m/^\s*$/) { + # anonymous struct/union + $newmember .= "$type $name; "; + } else { + $newmember .= "$type $id.$name; "; + } + } + } + } + } + $members =~ s/$struct_members/$newmember/; + } + + # Ignore other nested elements, like enums + $members =~ s/(\{[^\{\}]*\})//g; + + create_parameterlist($members, ';', $file, $declaration_name); + check_sections($file, $declaration_name, $decl_type, $sectcheck, $struct_actual); + + # Adjust declaration for better display + $declaration =~ s/([\{;])/$1\n/g; + $declaration =~ s/\}\s+;/};/g; + # Better handle inlined enums + do {} while ($declaration =~ s/(enum\s+\{[^\}]+),([^\n])/$1,\n$2/); + + my @def_args = split /\n/, $declaration; + my $level = 1; + $declaration = ""; + foreach my $clause (@def_args) { + $clause =~ s/^\s+//; + $clause =~ s/\s+$//; + $clause =~ s/\s+/ /; + next if (!$clause); + $level-- if ($clause =~ m/(\})/ && $level > 1); + if (!($clause =~ m/^\s*#/)) { + $declaration .= "\t" x $level; + } + $declaration .= "\t" . $clause . "\n"; + $level++ if ($clause =~ m/(\{)/ && !($clause =~m/\}/)); + } + output_declaration($declaration_name, + 'struct', + {'struct' => $declaration_name, + 'module' => $modulename, + 'definition' => $declaration, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose, + 'type' => $decl_type + }); + } else { + print STDERR "${file}:$.: error: Cannot parse struct or union!\n"; + ++$errors; + } +} + + +sub show_warnings($$) { + my $functype = shift; + my $name = shift; + + return 0 if (defined($nosymbol_table{$name})); + + return 1 if ($output_selection == OUTPUT_ALL); + + if ($output_selection == OUTPUT_EXPORTED) { + if (defined($function_table{$name})) { + return 1; + } else { + return 0; + } + } + if ($output_selection == OUTPUT_INTERNAL) { + if (!($functype eq "function" && defined($function_table{$name}))) { + return 1; + } else { + return 0; + } + } + if ($output_selection == OUTPUT_INCLUDE) { + if (defined($function_table{$name})) { + return 1; + } else { + return 0; + } + } + die("Please add the new output type at show_warnings()"); +} + +sub dump_enum($$) { + my $x = shift; + my $file = shift; + my $members; + + # ignore members marked private: + $x =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi; + $x =~ s/\/\*\s*private:.*}/}/gosi; + + $x =~ s@/\*.*?\*/@@gos; # strip comments. + # strip #define macros inside enums + $x =~ s@#\s*((define|ifdef|if)\s+|endif)[^;]*;@@gos; + + if ($x =~ /typedef\s+enum\s*\{(.*)\}\s*(\w*)\s*;/) { + $declaration_name = $2; + $members = $1; + } elsif ($x =~ /enum\s+(\w*)\s*\{(.*)\}/) { + $declaration_name = $1; + $members = $2; + } + + if ($members) { + if ($identifier ne $declaration_name) { + if ($identifier eq "") { + emit_warning("${file}:$.", "wrong kernel-doc identifier on line:\n"); + } else { + emit_warning("${file}:$.", "expecting prototype for enum $identifier. Prototype was for enum $declaration_name instead\n"); + } + return; + } + $declaration_name = "(anonymous)" if ($declaration_name eq ""); + + my %_members; + + $members =~ s/\s+$//; + $members =~ s/\([^;]*?[\)]//g; + + foreach my $arg (split ',', $members) { + $arg =~ s/^\s*(\w+).*/$1/; + push @parameterlist, $arg; + if (!$parameterdescs{$arg}) { + $parameterdescs{$arg} = $undescribed; + if (show_warnings("enum", $declaration_name)) { + emit_warning("${file}:$.", "Enum value '$arg' not described in enum '$declaration_name'\n"); + } + } + $_members{$arg} = 1; + } + + while (my ($k, $v) = each %parameterdescs) { + if (!exists($_members{$k})) { + if (show_warnings("enum", $declaration_name)) { + emit_warning("${file}:$.", "Excess enum value '$k' description in '$declaration_name'\n"); + } + } + } + + output_declaration($declaration_name, + 'enum', + {'enum' => $declaration_name, + 'module' => $modulename, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + } else { + print STDERR "${file}:$.: error: Cannot parse enum!\n"; + ++$errors; + } +} + +my $typedef_type = qr { ((?:\s+[\w\*]+\b){0,7}\s+(?:\w+\b|\*+))\s* }x; +my $typedef_ident = qr { \*?\s*(\w\S+)\s* }x; +my $typedef_args = qr { \s*\((.*)\); }x; + +my $typedef1 = qr { typedef$typedef_type\($typedef_ident\)$typedef_args }x; +my $typedef2 = qr { typedef$typedef_type$typedef_ident$typedef_args }x; + +sub dump_typedef($$) { + my $x = shift; + my $file = shift; + + $x =~ s@/\*.*?\*/@@gos; # strip comments. + + # Parse function typedef prototypes + if ($x =~ $typedef1 || $x =~ $typedef2) { + $return_type = $1; + $declaration_name = $2; + my $args = $3; + $return_type =~ s/^\s+//; + + if ($identifier ne $declaration_name) { + emit_warning("${file}:$.", "expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n"); + return; + } + + create_parameterlist($args, ',', $file, $declaration_name); + + output_declaration($declaration_name, + 'function', + {'function' => $declaration_name, + 'typedef' => 1, + 'module' => $modulename, + 'functiontype' => $return_type, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + return; + } + + while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) { + $x =~ s/\(*.\)\s*;$/;/; + $x =~ s/\[*.\]\s*;$/;/; + } + + if ($x =~ /typedef.*\s+(\w+)\s*;/) { + $declaration_name = $1; + + if ($identifier ne $declaration_name) { + emit_warning("${file}:$.", "expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n"); + return; + } + + output_declaration($declaration_name, + 'typedef', + {'typedef' => $declaration_name, + 'module' => $modulename, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + } else { + print STDERR "${file}:$.: error: Cannot parse typedef!\n"; + ++$errors; + } +} + +sub save_struct_actual($) { + my $actual = shift; + + # strip all spaces from the actual param so that it looks like one string item + $actual =~ s/\s*//g; + $struct_actual = $struct_actual . $actual . " "; +} + +sub create_parameterlist($$$$) { + my $args = shift; + my $splitter = shift; + my $file = shift; + my $declaration_name = shift; + my $type; + my $param; + + # temporarily replace commas inside function pointer definition + my $arg_expr = qr{\([^\),]+}; + while ($args =~ /$arg_expr,/) { + $args =~ s/($arg_expr),/$1#/g; + } + + foreach my $arg (split($splitter, $args)) { + # strip comments + $arg =~ s/\/\*.*\*\///; + # ignore argument attributes + $arg =~ s/\sPOS0?\s/ /; + # strip leading/trailing spaces + $arg =~ s/^\s*//; + $arg =~ s/\s*$//; + $arg =~ s/\s+/ /; + + if ($arg =~ /^#/) { + # Treat preprocessor directive as a typeless variable just to fill + # corresponding data structures "correctly". Catch it later in + # output_* subs. + push_parameter($arg, "", "", $file); + } elsif ($arg =~ m/\(.+\)\s*\(/) { + # pointer-to-function + $arg =~ tr/#/,/; + $arg =~ m/[^\(]+\(\*?\s*([\w\[\]\.]*)\s*\)/; + $param = $1; + $type = $arg; + $type =~ s/([^\(]+\(\*?)\s*$param/$1/; + save_struct_actual($param); + push_parameter($param, $type, $arg, $file, $declaration_name); + } elsif ($arg =~ m/\(.+\)\s*\[/) { + # array-of-pointers + $arg =~ tr/#/,/; + $arg =~ m/[^\(]+\(\s*\*\s*([\w\[\]\.]*?)\s*(\s*\[\s*[\w]+\s*\]\s*)*\)/; + $param = $1; + $type = $arg; + $type =~ s/([^\(]+\(\*?)\s*$param/$1/; + save_struct_actual($param); + push_parameter($param, $type, $arg, $file, $declaration_name); + } elsif ($arg) { + $arg =~ s/\s*:\s*/:/g; + $arg =~ s/\s*\[/\[/g; + + my @args = split('\s*,\s*', $arg); + if ($args[0] =~ m/\*/) { + $args[0] =~ s/(\*+)\s*/ $1/; + } + + my @first_arg; + if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) { + shift @args; + push(@first_arg, split('\s+', $1)); + push(@first_arg, $2); + } else { + @first_arg = split('\s+', shift @args); + } + + unshift(@args, pop @first_arg); + $type = join " ", @first_arg; + + foreach $param (@args) { + if ($param =~ m/^(\*+)\s*(.*)/) { + save_struct_actual($2); + + push_parameter($2, "$type $1", $arg, $file, $declaration_name); + } elsif ($param =~ m/(.*?):(\w+)/) { + if ($type ne "") { # skip unnamed bit-fields + save_struct_actual($1); + push_parameter($1, "$type:$2", $arg, $file, $declaration_name) + } + } else { + save_struct_actual($param); + push_parameter($param, $type, $arg, $file, $declaration_name); + } + } + } + } +} + +sub push_parameter($$$$$) { + my $param = shift; + my $type = shift; + my $org_arg = shift; + my $file = shift; + my $declaration_name = shift; + + if (($anon_struct_union == 1) && ($type eq "") && + ($param eq "}")) { + return; # ignore the ending }; from anon. struct/union + } + + $anon_struct_union = 0; + $param =~ s/[\[\)].*//; + + if ($type eq "" && $param =~ /\.\.\.$/) + { + if (!$param =~ /\w\.\.\.$/) { + # handles unnamed variable parameters + $param = "..."; + } elsif ($param =~ /\w\.\.\.$/) { + # for named variable parameters of the form `x...`, remove the dots + $param =~ s/\.\.\.$//; + } + if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") { + $parameterdescs{$param} = "variable arguments"; + } + } + elsif ($type eq "" && ($param eq "" or $param eq "void")) + { + $param="void"; + $parameterdescs{void} = "no arguments"; + } + elsif ($type eq "" && ($param eq "struct" or $param eq "union")) + # handle unnamed (anonymous) union or struct: + { + $type = $param; + $param = "{unnamed_" . $param . "}"; + $parameterdescs{$param} = "anonymous\n"; + $anon_struct_union = 1; + } + elsif ($param =~ "__cacheline_group" ) + # handle cache group enforcing variables: they do not need be described in header files + { + return; # ignore __cacheline_group_begin and __cacheline_group_end + } + + # warn if parameter has no description + # (but ignore ones starting with # as these are not parameters + # but inline preprocessor statements); + # Note: It will also ignore void params and unnamed structs/unions + if (!defined $parameterdescs{$param} && $param !~ /^#/) { + $parameterdescs{$param} = $undescribed; + + if (show_warnings($type, $declaration_name) && $param !~ /\./) { + emit_warning("${file}:$.", "Function parameter or struct member '$param' not described in '$declaration_name'\n"); + } + } + + # strip spaces from $param so that it is one continuous string + # on @parameterlist; + # this fixes a problem where check_sections() cannot find + # a parameter like "addr[6 + 2]" because it actually appears + # as "addr[6", "+", "2]" on the parameter list; + # but it's better to maintain the param string unchanged for output, + # so just weaken the string compare in check_sections() to ignore + # "[blah" in a parameter string; + ###$param =~ s/\s*//g; + push @parameterlist, $param; + $org_arg =~ s/\s\s+/ /g; + $parametertypes{$param} = $org_arg; +} + +sub check_sections($$$$$) { + my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck) = @_; + my @sects = split ' ', $sectcheck; + my @prms = split ' ', $prmscheck; + my $err; + my ($px, $sx); + my $prm_clean; # strip trailing "[array size]" and/or beginning "*" + + foreach $sx (0 .. $#sects) { + $err = 1; + foreach $px (0 .. $#prms) { + $prm_clean = $prms[$px]; + $prm_clean =~ s/\[.*\]//; + $prm_clean =~ s/$attribute//i; + # ignore array size in a parameter string; + # however, the original param string may contain + # spaces, e.g.: addr[6 + 2] + # and this appears in @prms as "addr[6" since the + # parameter list is split at spaces; + # hence just ignore "[..." for the sections check; + $prm_clean =~ s/\[.*//; + + ##$prm_clean =~ s/^\**//; + if ($prm_clean eq $sects[$sx]) { + $err = 0; + last; + } + } + if ($err) { + if ($decl_type eq "function") { + emit_warning("${file}:$.", + "Excess function parameter " . + "'$sects[$sx]' " . + "description in '$decl_name'\n"); + } elsif (($decl_type eq "struct") or + ($decl_type eq "union")) { + emit_warning("${file}:$.", + "Excess $decl_type member " . + "'$sects[$sx]' " . + "description in '$decl_name'\n"); + } + } + } +} + +## +# Checks the section describing the return value of a function. +sub check_return_section { + my $file = shift; + my $declaration_name = shift; + my $return_type = shift; + + # Ignore an empty return type (It's a macro) + # Ignore functions with a "void" return type. (But don't ignore "void *") + if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) { + return; + } + + if (!defined($sections{$section_return}) || + $sections{$section_return} eq "") + { + emit_warning("${file}:$.", + "No description found for return value of " . + "'$declaration_name'\n"); + } +} + +## +# takes a function prototype and the name of the current file being +# processed and spits out all the details stored in the global +# arrays/hashes. +sub dump_function($$) { + my $prototype = shift; + my $file = shift; + my $func_macro = 0; + + print_lineno($new_start_line); + + $prototype =~ s/^static +//; + $prototype =~ s/^extern +//; + $prototype =~ s/^asmlinkage +//; + $prototype =~ s/^inline +//; + $prototype =~ s/^__inline__ +//; + $prototype =~ s/^__inline +//; + $prototype =~ s/^__always_inline +//; + $prototype =~ s/^noinline +//; + $prototype =~ s/^__FORTIFY_INLINE +//; + $prototype =~ s/__init +//; + $prototype =~ s/__init_or_module +//; + $prototype =~ s/__deprecated +//; + $prototype =~ s/__flatten +//; + $prototype =~ s/__meminit +//; + $prototype =~ s/__must_check +//; + $prototype =~ s/__weak +//; + $prototype =~ s/__sched +//; + $prototype =~ s/_noprof//; + $prototype =~ s/__printf\s*\(\s*\d*\s*,\s*\d*\s*\) +//; + $prototype =~ s/__(?:re)?alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) +//; + $prototype =~ s/__diagnose_as\s*\(\s*\S+\s*(?:,\s*\d+\s*)*\) +//; + $prototype =~ s/DECL_BUCKET_PARAMS\s*\(\s*(\S+)\s*,\s*(\S+)\s*\)/$1, $2/; + my $define = $prototype =~ s/^#\s*define\s+//; #ak added + $prototype =~ s/__attribute_const__ +//; + $prototype =~ s/__attribute__\s*\(\( + (?: + [\w\s]++ # attribute name + (?:\([^)]*+\))? # attribute arguments + \s*+,? # optional comma at the end + )+ + \)\)\s+//x; + + # Yes, this truly is vile. We are looking for: + # 1. Return type (may be nothing if we're looking at a macro) + # 2. Function name + # 3. Function parameters. + # + # All the while we have to watch out for function pointer parameters + # (which IIRC is what the two sections are for), C types (these + # regexps don't even start to express all the possibilities), and + # so on. + # + # If you mess with these regexps, it's a good idea to check that + # the following functions' documentation still comes out right: + # - parport_register_device (function pointer parameters) + # - atomic_set (macro) + # - pci_match_device, __copy_to_user (long return type) + my $name = qr{[a-zA-Z0-9_~:]+}; + my $prototype_end1 = qr{[^\(]*}; + my $prototype_end2 = qr{[^\{]*}; + my $prototype_end = qr{\(($prototype_end1|$prototype_end2)\)}; + my $type1 = qr{[\w\s]+}; + my $type2 = qr{$type1\*+}; + + if ($define && $prototype =~ m/^()($name)\s+/) { + # This is an object-like macro, it has no return type and no parameter + # list. + # Function-like macros are not allowed to have spaces between + # declaration_name and opening parenthesis (notice the \s+). + $return_type = $1; + $declaration_name = $2; + $func_macro = 1; + } elsif ($prototype =~ m/^()($name)\s*$prototype_end/ || + $prototype =~ m/^($type1)\s+($name)\s*$prototype_end/ || + $prototype =~ m/^($type2+)\s*($name)\s*$prototype_end/) { + $return_type = $1; + $declaration_name = $2; + my $args = $3; + + create_parameterlist($args, ',', $file, $declaration_name); + } else { + emit_warning("${file}:$.", "cannot understand function prototype: '$prototype'\n"); + return; + } + + if ($identifier ne $declaration_name) { + emit_warning("${file}:$.", "expecting prototype for $identifier(). Prototype was for $declaration_name() instead\n"); + return; + } + + my $prms = join " ", @parameterlist; + check_sections($file, $declaration_name, "function", $sectcheck, $prms); + + # This check emits a lot of warnings at the moment, because many + # functions don't have a 'Return' doc section. So until the number + # of warnings goes sufficiently down, the check is only performed in + # -Wreturn mode. + # TODO: always perform the check. + if ($Wreturn && !$func_macro) { + check_return_section($file, $declaration_name, $return_type); + } + + # The function parser can be called with a typedef parameter. + # Handle it. + if ($return_type =~ /typedef/) { + output_declaration($declaration_name, + 'function', + {'function' => $declaration_name, + 'typedef' => 1, + 'module' => $modulename, + 'functiontype' => $return_type, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose, + 'func_macro' => $func_macro + }); + } else { + output_declaration($declaration_name, + 'function', + {'function' => $declaration_name, + 'module' => $modulename, + 'functiontype' => $return_type, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose, + 'func_macro' => $func_macro + }); + } +} + +sub reset_state { + $function = ""; + %parameterdescs = (); + %parametertypes = (); + @parameterlist = (); + %sections = (); + @sectionlist = (); + $sectcheck = ""; + $struct_actual = ""; + $prototype = ""; + + $state = STATE_NORMAL; + $inline_doc_state = STATE_INLINE_NA; +} + +sub tracepoint_munge($) { + my $file = shift; + my $tracepointname = 0; + my $tracepointargs = 0; + + if ($prototype =~ m/TRACE_EVENT\((.*?),/) { + $tracepointname = $1; + } + if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) { + $tracepointname = $1; + } + if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) { + $tracepointname = $2; + } + $tracepointname =~ s/^\s+//; #strip leading whitespace + if ($prototype =~ m/TP_PROTO\((.*?)\)/) { + $tracepointargs = $1; + } + if (($tracepointname eq 0) || ($tracepointargs eq 0)) { + emit_warning("${file}:$.", "Unrecognized tracepoint format: \n". + "$prototype\n"); + } else { + $prototype = "static inline void trace_$tracepointname($tracepointargs)"; + $identifier = "trace_$identifier"; + } +} + +sub syscall_munge() { + my $void = 0; + + $prototype =~ s@[\r\n]+@ @gos; # strip newlines/CR's +## if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) { + if ($prototype =~ m/SYSCALL_DEFINE0/) { + $void = 1; +## $prototype = "long sys_$1(void)"; + } + + $prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name + if ($prototype =~ m/long (sys_.*?),/) { + $prototype =~ s/,/\(/; + } elsif ($void) { + $prototype =~ s/\)/\(void\)/; + } + + # now delete all of the odd-number commas in $prototype + # so that arg types & arg names don't have a comma between them + my $count = 0; + my $len = length($prototype); + if ($void) { + $len = 0; # skip the for-loop + } + for (my $ix = 0; $ix < $len; $ix++) { + if (substr($prototype, $ix, 1) eq ',') { + $count++; + if ($count % 2 == 1) { + substr($prototype, $ix, 1) = ' '; + } + } + } +} + +sub process_proto_function($$) { + my $x = shift; + my $file = shift; + + $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line + + if ($x =~ /^#/ && $x !~ /^#\s*define/) { + # do nothing + } elsif ($x =~ /([^\{]*)/) { + $prototype .= $1; + } + + if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) { + $prototype =~ s@/\*.*?\*/@@gos; # strip comments. + $prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's. + $prototype =~ s@^\s+@@gos; # strip leading spaces + + # Handle prototypes for function pointers like: + # int (*pcs_config)(struct foo) + $prototype =~ s@^(\S+\s+)\(\s*\*(\S+)\)@$1$2@gos; + + if ($prototype =~ /SYSCALL_DEFINE/) { + syscall_munge(); + } + if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ || + $prototype =~ /DEFINE_SINGLE_EVENT/) + { + tracepoint_munge($file); + } + dump_function($prototype, $file); + reset_state(); + } +} + +sub process_proto_type($$) { + my $x = shift; + my $file = shift; + + $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's. + $x =~ s@^\s+@@gos; # strip leading spaces + $x =~ s@\s+$@@gos; # strip trailing spaces + $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line + + if ($x =~ /^#/) { + # To distinguish preprocessor directive from regular declaration later. + $x .= ";"; + } + + while (1) { + if ( $x =~ /([^\{\};]*)([\{\};])(.*)/ ) { + if( length $prototype ) { + $prototype .= " " + } + $prototype .= $1 . $2; + ($2 eq '{') && $brcount++; + ($2 eq '}') && $brcount--; + if (($2 eq ';') && ($brcount == 0)) { + dump_declaration($prototype, $file); + reset_state(); + last; + } + $x = $3; + } else { + $prototype .= $x; + last; + } + } +} + + +sub map_filename($) { + my $file; + my ($orig_file) = @_; + + if (defined($ENV{'SRCTREE'})) { + $file = "$ENV{'SRCTREE'}" . "/" . $orig_file; + } else { + $file = $orig_file; + } + + return $file; +} + +sub process_export_file($) { + my ($orig_file) = @_; + my $file = map_filename($orig_file); + + if (!open(IN,"<$file")) { + print STDERR "Error: Cannot open file $file\n"; + ++$errors; + return; + } + + while (<IN>) { + if (/$export_symbol/) { + next if (defined($nosymbol_table{$2})); + $function_table{$2} = 1; + } + if (/$export_symbol_ns/) { + next if (defined($nosymbol_table{$2})); + $function_table{$2} = 1; + } + } + + close(IN); +} + +# +# Parsers for the various processing states. +# +# STATE_NORMAL: looking for the /** to begin everything. +# +sub process_normal() { + if (/$doc_start/o) { + $state = STATE_NAME; # next line is always the function name + $declaration_start_line = $. + 1; + } +} + +# +# STATE_NAME: Looking for the "name - description" line +# +sub process_name($$) { + my $file = shift; + my $descr; + + if (/$doc_block/o) { + $state = STATE_DOCBLOCK; + $contents = ""; + $new_start_line = $.; + + if ( $1 eq "" ) { + $section = $section_intro; + } else { + $section = $1; + } + } elsif (/$doc_decl/o) { + $identifier = $1; + my $is_kernel_comment = 0; + my $decl_start = qr{$doc_com}; + # test for pointer declaration type, foo * bar() - desc + my $fn_type = qr{\w+\s*\*\s*}; + my $parenthesis = qr{\(\w*\)}; + my $decl_end = qr{[-:].*}; + if (/^$decl_start([\w\s]+?)$parenthesis?\s*$decl_end?$/) { + $identifier = $1; + } + if ($identifier =~ m/^(struct|union|enum|typedef)\b\s*(\S*)/) { + $decl_type = $1; + $identifier = $2; + $is_kernel_comment = 1; + } + # Look for foo() or static void foo() - description; or misspelt + # identifier + elsif (/^$decl_start$fn_type?(\w+)\s*$parenthesis?\s*$decl_end?$/ || + /^$decl_start$fn_type?(\w+[^-:]*)$parenthesis?\s*$decl_end$/) { + $identifier = $1; + $decl_type = 'function'; + $identifier =~ s/^define\s+//; + $is_kernel_comment = 1; + } + $identifier =~ s/\s+$//; + + $state = STATE_BODY; + # if there's no @param blocks need to set up default section + # here + $contents = ""; + $section = $section_default; + $new_start_line = $. + 1; + if (/[-:](.*)/) { + # strip leading/trailing/multiple spaces + $descr= $1; + $descr =~ s/^\s*//; + $descr =~ s/\s*$//; + $descr =~ s/\s+/ /g; + $declaration_purpose = $descr; + $state = STATE_BODY_MAYBE; + } else { + $declaration_purpose = ""; + } + + if (!$is_kernel_comment) { + emit_warning("${file}:$.", "This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n$_"); + $state = STATE_NORMAL; + } + + if (($declaration_purpose eq "") && $Wshort_desc) { + emit_warning("${file}:$.", "missing initial short description on line:\n$_"); + } + + if ($identifier eq "" && $decl_type ne "enum") { + emit_warning("${file}:$.", "wrong kernel-doc identifier on line:\n$_"); + $state = STATE_NORMAL; + } + + if ($verbose) { + print STDERR "${file}:$.: info: Scanning doc for $decl_type $identifier\n"; + } + } else { + emit_warning("${file}:$.", "Cannot understand $_ on line $. - I thought it was a doc line\n"); + $state = STATE_NORMAL; + } +} + + +# +# STATE_BODY and STATE_BODY_MAYBE: the bulk of a kerneldoc comment. +# +sub process_body($$) { + my $file = shift; + + if ($state == STATE_BODY_WITH_BLANK_LINE && /^\s*\*\s?\S/) { + dump_section($file, $section, $contents); + $section = $section_default; + $new_start_line = $.; + $contents = ""; + } + + if (/$doc_sect/i) { # case insensitive for supported section names + $newsection = $1; + $newcontents = $2; + + # map the supported section names to the canonical names + if ($newsection =~ m/^description$/i) { + $newsection = $section_default; + } elsif ($newsection =~ m/^context$/i) { + $newsection = $section_context; + } elsif ($newsection =~ m/^returns?$/i) { + $newsection = $section_return; + } elsif ($newsection =~ m/^\@return$/) { + # special: @return is a section, not a param description + $newsection = $section_return; + } + + if (($contents ne "") && ($contents ne "\n")) { + dump_section($file, $section, $contents); + $section = $section_default; + } + + $state = STATE_BODY; + $contents = $newcontents; + $new_start_line = $.; + while (substr($contents, 0, 1) eq " ") { + $contents = substr($contents, 1); + } + if ($contents ne "") { + $contents .= "\n"; + } + $section = $newsection; + $leading_space = undef; + } elsif (/$doc_end/) { + if (($contents ne "") && ($contents ne "\n")) { + dump_section($file, $section, $contents); + $section = $section_default; + $contents = ""; + } + # look for doc_com + <text> + doc_end: + if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') { + emit_warning("${file}:$.", "suspicious ending line: $_"); + } + + $prototype = ""; + $state = STATE_PROTO; + $brcount = 0; + $new_start_line = $. + 1; + } elsif (/$doc_content/) { + if ($1 eq "") { + if ($section eq $section_context) { + dump_section($file, $section, $contents); + $section = $section_default; + $contents = ""; + $new_start_line = $.; + $state = STATE_BODY; + } else { + if ($section ne $section_default) { + $state = STATE_BODY_WITH_BLANK_LINE; + } else { + $state = STATE_BODY; + } + $contents .= "\n"; + } + } elsif ($state == STATE_BODY_MAYBE) { + # Continued declaration purpose + chomp($declaration_purpose); + $declaration_purpose .= " " . $1; + $declaration_purpose =~ s/\s+/ /g; + } else { + my $cont = $1; + if ($section =~ m/^@/ || $section eq $section_context) { + if (!defined $leading_space) { + if ($cont =~ m/^(\s+)/) { + $leading_space = $1; + } else { + $leading_space = ""; + } + } + $cont =~ s/^$leading_space//; + } + $contents .= $cont . "\n"; + } + } else { + # i dont know - bad line? ignore. + emit_warning("${file}:$.", "bad line: $_"); + } +} + + +# +# STATE_PROTO: reading a function/whatever prototype. +# +sub process_proto($$) { + my $file = shift; + + if (/$doc_inline_oneline/) { + $section = $1; + $contents = $2; + if ($contents ne "") { + $contents .= "\n"; + dump_section($file, $section, $contents); + $section = $section_default; + $contents = ""; + } + } elsif (/$doc_inline_start/) { + $state = STATE_INLINE; + $inline_doc_state = STATE_INLINE_NAME; + } elsif ($decl_type eq 'function') { + process_proto_function($_, $file); + } else { + process_proto_type($_, $file); + } +} + +# +# STATE_DOCBLOCK: within a DOC: block. +# +sub process_docblock($$) { + my $file = shift; + + if (/$doc_end/) { + dump_doc_section($file, $section, $contents); + $section = $section_default; + $contents = ""; + $function = ""; + %parameterdescs = (); + %parametertypes = (); + @parameterlist = (); + %sections = (); + @sectionlist = (); + $prototype = ""; + $state = STATE_NORMAL; + } elsif (/$doc_content/) { + if ( $1 eq "" ) { + $contents .= $blankline; + } else { + $contents .= $1 . "\n"; + } + } +} + +# +# STATE_INLINE: docbook comments within a prototype. +# +sub process_inline($$) { + my $file = shift; + + # First line (state 1) needs to be a @parameter + if ($inline_doc_state == STATE_INLINE_NAME && /$doc_inline_sect/o) { + $section = $1; + $contents = $2; + $new_start_line = $.; + if ($contents ne "") { + while (substr($contents, 0, 1) eq " ") { + $contents = substr($contents, 1); + } + $contents .= "\n"; + } + $inline_doc_state = STATE_INLINE_TEXT; + # Documentation block end */ + } elsif (/$doc_inline_end/) { + if (($contents ne "") && ($contents ne "\n")) { + dump_section($file, $section, $contents); + $section = $section_default; + $contents = ""; + } + $state = STATE_PROTO; + $inline_doc_state = STATE_INLINE_NA; + # Regular text + } elsif (/$doc_content/) { + if ($inline_doc_state == STATE_INLINE_TEXT) { + $contents .= $1 . "\n"; + # nuke leading blank lines + if ($contents =~ /^\s*$/) { + $contents = ""; + } + } elsif ($inline_doc_state == STATE_INLINE_NAME) { + $inline_doc_state = STATE_INLINE_ERROR; + emit_warning("${file}:$.", "Incorrect use of kernel-doc format: $_"); + } + } +} + + +sub process_file($) { + my $file; + my ($orig_file) = @_; + + $file = map_filename($orig_file); + + if (!open(IN_FILE,"<$file")) { + print STDERR "Error: Cannot open file $file\n"; + ++$errors; + return; + } + + $. = 1; + + $section_counter = 0; + while (<IN_FILE>) { + while (!/^ \*/ && s/\\\s*$//) { + $_ .= <IN_FILE>; + } + # Replace tabs by spaces + while ($_ =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}; + # Hand this line to the appropriate state handler + if ($state == STATE_NORMAL) { + process_normal(); + } elsif ($state == STATE_NAME) { + process_name($file, $_); + } elsif ($state == STATE_BODY || $state == STATE_BODY_MAYBE || + $state == STATE_BODY_WITH_BLANK_LINE) { + process_body($file, $_); + } elsif ($state == STATE_INLINE) { # scanning for inline parameters + process_inline($file, $_); + } elsif ($state == STATE_PROTO) { + process_proto($file, $_); + } elsif ($state == STATE_DOCBLOCK) { + process_docblock($file, $_); + } + } + + # Make sure we got something interesting. + if (!$section_counter && $output_mode ne "none") { + if ($output_selection == OUTPUT_INCLUDE) { + emit_warning("${file}:1", "'$_' not found\n") + for keys %function_table; + } else { + emit_warning("${file}:1", "no structured comments found\n"); + } + } + close IN_FILE; +} + +$kernelversion = get_kernel_version(); + +# generate a sequence of code that will splice in highlighting information +# using the s// operator. +for (my $k = 0; $k < @highlights; $k++) { + my $pattern = $highlights[$k][0]; + my $result = $highlights[$k][1]; +# print STDERR "scanning pattern:$pattern, highlight:($result)\n"; + $dohighlight .= "\$contents =~ s:$pattern:$result:gs;\n"; +} + +if ($output_selection == OUTPUT_EXPORTED || + $output_selection == OUTPUT_INTERNAL) { + + push(@export_file_list, @ARGV); + + foreach (@export_file_list) { + chomp; + process_export_file($_); + } +} + +foreach (@ARGV) { + chomp; + process_file($_); +} +if ($verbose && $errors) { + print STDERR "$errors errors\n"; +} +if ($verbose && $warnings) { + print STDERR "$warnings warnings\n"; +} + +if ($Werror && $warnings) { + print STDERR "$warnings warnings as Errors\n"; + exit($warnings); +} else { + exit($output_mode eq "none" ? 0 : $errors) +} + +__END__ + +=head1 OPTIONS + +=head2 Output format selection (mutually exclusive): + +=over 8 + +=item -man + +Output troff manual page format. + +=item -rst + +Output reStructuredText format. This is the default. + +=item -none + +Do not output documentation, only warnings. + +=back + +=head2 Output format modifiers + +=head3 reStructuredText only + +=head2 Output selection (mutually exclusive): + +=over 8 + +=item -export + +Only output documentation for the symbols that have been exported using +EXPORT_SYMBOL() and related macros in any input FILE or -export-file FILE. + +=item -internal + +Only output documentation for the symbols that have NOT been exported using +EXPORT_SYMBOL() and related macros in any input FILE or -export-file FILE. + +=item -function NAME + +Only output documentation for the given function or DOC: section title. +All other functions and DOC: sections are ignored. + +May be specified multiple times. + +=item -nosymbol NAME + +Exclude the specified symbol from the output documentation. + +May be specified multiple times. + +=back + +=head2 Output selection modifiers: + +=over 8 + +=item -no-doc-sections + +Do not output DOC: sections. + +=item -export-file FILE + +Specify an additional FILE in which to look for EXPORT_SYMBOL information. + +To be used with -export or -internal. + +May be specified multiple times. + +=back + +=head3 reStructuredText only + +=over 8 + +=item -enable-lineno + +Enable output of .. LINENO lines. + +=back + +=head2 Other parameters: + +=over 8 + +=item -h, -help + +Print this help. + +=item -v + +Verbose output, more warnings and other information. + +=item -Werror + +Treat warnings as errors. + +=back + +=cut diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py new file mode 100755 index 000000000000..12ae66f40bd7 --- /dev/null +++ b/scripts/kernel-doc.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# +# pylint: disable=C0103,R0915 +# +# Converted from the kernel-doc script originally written in Perl +# under GPLv2, copyrighted since 1998 by the following authors: +# +# Aditya Srivastava <yashsri421@gmail.com> +# Akira Yokosawa <akiyks@gmail.com> +# Alexander A. Klimov <grandmaster@al2klimov.de> +# Alexander Lobakin <aleksander.lobakin@intel.com> +# André Almeida <andrealmeid@igalia.com> +# Andy Shevchenko <andriy.shevchenko@linux.intel.com> +# Anna-Maria Behnsen <anna-maria@linutronix.de> +# Armin Kuster <akuster@mvista.com> +# Bart Van Assche <bart.vanassche@sandisk.com> +# Ben Hutchings <ben@decadent.org.uk> +# Borislav Petkov <bbpetkov@yahoo.de> +# Chen-Yu Tsai <wenst@chromium.org> +# Coco Li <lixiaoyan@google.com> +# Conchúr Navid <conchur@web.de> +# Daniel Santos <daniel.santos@pobox.com> +# Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk> +# Dan Luedtke <mail@danrl.de> +# Donald Hunter <donald.hunter@gmail.com> +# Gabriel Krisman Bertazi <krisman@collabora.co.uk> +# Greg Kroah-Hartman <gregkh@linuxfoundation.org> +# Harvey Harrison <harvey.harrison@gmail.com> +# Horia Geanta <horia.geanta@freescale.com> +# Ilya Dryomov <idryomov@gmail.com> +# Jakub Kicinski <kuba@kernel.org> +# Jani Nikula <jani.nikula@intel.com> +# Jason Baron <jbaron@redhat.com> +# Jason Gunthorpe <jgg@nvidia.com> +# Jérémy Bobbio <lunar@debian.org> +# Johannes Berg <johannes.berg@intel.com> +# Johannes Weiner <hannes@cmpxchg.org> +# Jonathan Cameron <Jonathan.Cameron@huawei.com> +# Jonathan Corbet <corbet@lwn.net> +# Jonathan Neuschäfer <j.neuschaefer@gmx.net> +# Kamil Rytarowski <n54@gmx.com> +# Kees Cook <kees@kernel.org> +# Laurent Pinchart <laurent.pinchart@ideasonboard.com> +# Levin, Alexander (Sasha Levin) <alexander.levin@verizon.com> +# Linus Torvalds <torvalds@linux-foundation.org> +# Lucas De Marchi <lucas.demarchi@profusion.mobi> +# Mark Rutland <mark.rutland@arm.com> +# Markus Heiser <markus.heiser@darmarit.de> +# Martin Waitz <tali@admingilde.org> +# Masahiro Yamada <masahiroy@kernel.org> +# Matthew Wilcox <willy@infradead.org> +# Mauro Carvalho Chehab <mchehab+huawei@kernel.org> +# Michal Wajdeczko <michal.wajdeczko@intel.com> +# Michael Zucchi +# Mike Rapoport <rppt@linux.ibm.com> +# Niklas Söderlund <niklas.soderlund@corigine.com> +# Nishanth Menon <nm@ti.com> +# Paolo Bonzini <pbonzini@redhat.com> +# Pavan Kumar Linga <pavan.kumar.linga@intel.com> +# Pavel Pisa <pisa@cmp.felk.cvut.cz> +# Peter Maydell <peter.maydell@linaro.org> +# Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> +# Randy Dunlap <rdunlap@infradead.org> +# Richard Kennedy <richard@rsk.demon.co.uk> +# Rich Walker <rw@shadow.org.uk> +# Rolf Eike Beer <eike-kernel@sf-tec.de> +# Sakari Ailus <sakari.ailus@linux.intel.com> +# Silvio Fricke <silvio.fricke@gmail.com> +# Simon Huggins +# Tim Waugh <twaugh@redhat.com> +# Tomasz Warniełło <tomasz.warniello@gmail.com> +# Utkarsh Tripathi <utripathi2002@gmail.com> +# valdis.kletnieks@vt.edu <valdis.kletnieks@vt.edu> +# Vegard Nossum <vegard.nossum@oracle.com> +# Will Deacon <will.deacon@arm.com> +# Yacine Belkadi <yacine.belkadi.1@gmail.com> +# Yujie Liu <yujie.liu@intel.com> + +""" +kernel_doc +========== + +Print formatted kernel documentation to stdout + +Read C language source or header FILEs, extract embedded +documentation comments, and print formatted documentation +to standard output. + +The documentation comments are identified by the "/**" +opening comment mark. + +See Documentation/doc-guide/kernel-doc.rst for the +documentation comment syntax. +""" + +import argparse +import logging +import os +import sys + +# Import Python modules + +LIB_DIR = "lib/kdoc" +SRC_DIR = os.path.dirname(os.path.realpath(__file__)) + +sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) + +from kdoc_files import KernelFiles # pylint: disable=C0413 +from kdoc_output import RestFormat, ManFormat # pylint: disable=C0413 + +DESC = """ +Read C language source or header FILEs, extract embedded documentation comments, +and print formatted documentation to standard output. + +The documentation comments are identified by the "/**" opening comment mark. + +See Documentation/doc-guide/kernel-doc.rst for the documentation comment syntax. +""" + +EXPORT_FILE_DESC = """ +Specify an additional FILE in which to look for EXPORT_SYMBOL information. + +May be used multiple times. +""" + +EXPORT_DESC = """ +Only output documentation for the symbols that have been +exported using EXPORT_SYMBOL() and related macros in any input +FILE or -export-file FILE. +""" + +INTERNAL_DESC = """ +Only output documentation for the symbols that have NOT been +exported using EXPORT_SYMBOL() and related macros in any input +FILE or -export-file FILE. +""" + +FUNCTION_DESC = """ +Only output documentation for the given function or DOC: section +title. All other functions and DOC: sections are ignored. + +May be used multiple times. +""" + +NOSYMBOL_DESC = """ +Exclude the specified symbol from the output documentation. + +May be used multiple times. +""" + +FILES_DESC = """ +Header and C source files to be parsed. +""" + +WARN_CONTENTS_BEFORE_SECTIONS_DESC = """ +Warns if there are contents before sections (deprecated). + +This option is kept just for backward-compatibility, but it does nothing, +neither here nor at the original Perl script. +""" + + +class MsgFormatter(logging.Formatter): + """Helper class to format warnings on a similar way to kernel-doc.pl""" + + def format(self, record): + record.levelname = record.levelname.capitalize() + return logging.Formatter.format(self, record) + +def main(): + """Main program""" + + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, + description=DESC) + + # Normal arguments + + parser.add_argument("-v", "-verbose", "--verbose", action="store_true", + help="Verbose output, more warnings and other information.") + + parser.add_argument("-d", "-debug", "--debug", action="store_true", + help="Enable debug messages") + + parser.add_argument("-M", "-modulename", "--modulename", + default="Kernel API", + help="Allow setting a module name at the output.") + + parser.add_argument("-l", "-enable-lineno", "--enable_lineno", + action="store_true", + help="Enable line number output (only in ReST mode)") + + # Arguments to control the warning behavior + + parser.add_argument("-Wreturn", "--wreturn", action="store_true", + help="Warns about the lack of a return markup on functions.") + + parser.add_argument("-Wshort-desc", "-Wshort-description", "--wshort-desc", + action="store_true", + help="Warns if initial short description is missing") + + parser.add_argument("-Wcontents-before-sections", + "--wcontents-before-sections", action="store_true", + help=WARN_CONTENTS_BEFORE_SECTIONS_DESC) + + parser.add_argument("-Wall", "--wall", action="store_true", + help="Enable all types of warnings") + + parser.add_argument("-Werror", "--werror", action="store_true", + help="Treat warnings as errors.") + + parser.add_argument("-export-file", "--export-file", action='append', + help=EXPORT_FILE_DESC) + + # Output format mutually-exclusive group + + out_group = parser.add_argument_group("Output format selection (mutually exclusive)") + + out_fmt = out_group.add_mutually_exclusive_group() + + out_fmt.add_argument("-m", "-man", "--man", action="store_true", + help="Output troff manual page format.") + out_fmt.add_argument("-r", "-rst", "--rst", action="store_true", + help="Output reStructuredText format (default).") + out_fmt.add_argument("-N", "-none", "--none", action="store_true", + help="Do not output documentation, only warnings.") + + # Output selection mutually-exclusive group + + sel_group = parser.add_argument_group("Output selection (mutually exclusive)") + sel_mut = sel_group.add_mutually_exclusive_group() + + sel_mut.add_argument("-e", "-export", "--export", action='store_true', + help=EXPORT_DESC) + + sel_mut.add_argument("-i", "-internal", "--internal", action='store_true', + help=INTERNAL_DESC) + + sel_mut.add_argument("-s", "-function", "--symbol", action='append', + help=FUNCTION_DESC) + + # Those are valid for all 3 types of filter + parser.add_argument("-n", "-nosymbol", "--nosymbol", action='append', + help=NOSYMBOL_DESC) + + parser.add_argument("-D", "-no-doc-sections", "--no-doc-sections", + action='store_true', help="Don't outputt DOC sections") + + parser.add_argument("files", metavar="FILE", + nargs="+", help=FILES_DESC) + + args = parser.parse_args() + + if args.wall: + args.wreturn = True + args.wshort_desc = True + args.wcontents_before_sections = True + + logger = logging.getLogger() + + if not args.debug: + logger.setLevel(logging.INFO) + else: + logger.setLevel(logging.DEBUG) + + formatter = MsgFormatter('%(levelname)s: %(message)s') + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + logger.addHandler(handler) + + if args.man: + out_style = ManFormat(modulename=args.modulename) + elif args.none: + out_style = None + else: + out_style = RestFormat() + + kfiles = KernelFiles(verbose=args.verbose, + out_style=out_style, werror=args.werror, + wreturn=args.wreturn, wshort_desc=args.wshort_desc, + wcontents_before_sections=args.wcontents_before_sections) + + kfiles.parse(args.files, export_file=args.export_file) + + for t in kfiles.msg(enable_lineno=args.enable_lineno, export=args.export, + internal=args.internal, symbol=args.symbol, + nosymbol=args.nosymbol, export_file=args.export_file, + no_doc_sections=args.no_doc_sections): + msg = t[1] + if msg: + print(msg) + + error_count = kfiles.errors + if not error_count: + sys.exit(0) + + if args.werror: + print(f"{error_count} warnings as errors") + sys.exit(error_count) + + if args.verbose: + print(f"{error_count} errors") + + if args.none: + sys.exit(0) + + sys.exit(error_count) + + +# Call main method +if __name__ == "__main__": + main() diff --git a/scripts/ld-version.sh b/scripts/ld-version.sh index a78b804b680c..b9513d224476 100755 --- a/scripts/ld-version.sh +++ b/scripts/ld-version.sh @@ -57,9 +57,11 @@ else fi fi -# Some distributions append a package release number, as in 2.34-4.fc32 -# Trim the hyphen and any characters that follow. -version=${version%-*} +# There may be something after the version, such as a distribution's package +# release number (like Fedora's "2.34-4.fc32") or punctuation (like LLD briefly +# added before the "compatible with GNU linkers" string), so remove everything +# after just numbers and periods. +version=${version%%[!0-9.]*} cversion=$(get_canonical_version $version) min_cversion=$(get_canonical_version $min_version) diff --git a/scripts/lib/abi/abi_parser.py b/scripts/lib/abi/abi_parser.py new file mode 100644 index 000000000000..66a738013ce1 --- /dev/null +++ b/scripts/lib/abi/abi_parser.py @@ -0,0 +1,628 @@ +#!/usr/bin/env python3 +# pylint: disable=R0902,R0903,R0911,R0912,R0913,R0914,R0915,R0917,C0302 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# SPDX-License-Identifier: GPL-2.0 + +""" +Parse ABI documentation and produce results from it. +""" + +from argparse import Namespace +import logging +import os +import re + +from pprint import pformat +from random import randrange, seed + +# Import Python modules + +from helpers import AbiDebug, ABI_DIR + + +class AbiParser: + """Main class to parse ABI files""" + + TAGS = r"(what|where|date|kernelversion|contact|description|users)" + XREF = r"(?:^|\s|\()(\/(?:sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)(?:[,.:;\)\s]|\Z)" + + def __init__(self, directory, logger=None, + enable_lineno=False, show_warnings=True, debug=0): + """Stores arguments for the class and initialize class vars""" + + self.directory = directory + self.enable_lineno = enable_lineno + self.show_warnings = show_warnings + self.debug = debug + + if not logger: + self.log = logging.getLogger("get_abi") + else: + self.log = logger + + self.data = {} + self.what_symbols = {} + self.file_refs = {} + self.what_refs = {} + + # Ignore files that contain such suffixes + self.ignore_suffixes = (".rej", ".org", ".orig", ".bak", "~") + + # Regular expressions used on parser + self.re_abi_dir = re.compile(r"(.*)" + ABI_DIR) + self.re_tag = re.compile(r"(\S+)(:\s*)(.*)", re.I) + self.re_valid = re.compile(self.TAGS) + self.re_start_spc = re.compile(r"(\s*)(\S.*)") + self.re_whitespace = re.compile(r"^\s+") + + # Regular used on print + self.re_what = re.compile(r"(\/?(?:[\w\-]+\/?){1,2})") + self.re_escape = re.compile(r"([\.\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])") + self.re_unprintable = re.compile(r"([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff]+)") + self.re_title_mark = re.compile(r"\n[\-\*\=\^\~]+\n") + self.re_doc = re.compile(r"Documentation/(?!devicetree)(\S+)\.rst") + self.re_abi = re.compile(r"(Documentation/ABI/)([\w\/\-]+)") + self.re_xref_node = re.compile(self.XREF) + + def warn(self, fdata, msg, extra=None): + """Displays a parse error if warning is enabled""" + + if not self.show_warnings: + return + + msg = f"{fdata.fname}:{fdata.ln}: {msg}" + if extra: + msg += "\n\t\t" + extra + + self.log.warning(msg) + + def add_symbol(self, what, fname, ln=None, xref=None): + """Create a reference table describing where each 'what' is located""" + + if what not in self.what_symbols: + self.what_symbols[what] = {"file": {}} + + if fname not in self.what_symbols[what]["file"]: + self.what_symbols[what]["file"][fname] = [] + + if ln and ln not in self.what_symbols[what]["file"][fname]: + self.what_symbols[what]["file"][fname].append(ln) + + if xref: + self.what_symbols[what]["xref"] = xref + + def _parse_line(self, fdata, line): + """Parse a single line of an ABI file""" + + new_what = False + new_tag = False + content = None + + match = self.re_tag.match(line) + if match: + new = match.group(1).lower() + sep = match.group(2) + content = match.group(3) + + match = self.re_valid.search(new) + if match: + new_tag = match.group(1) + else: + if fdata.tag == "description": + # New "tag" is actually part of description. + # Don't consider it a tag + new_tag = False + elif fdata.tag != "": + self.warn(fdata, f"tag '{fdata.tag}' is invalid", line) + + if new_tag: + # "where" is Invalid, but was a common mistake. Warn if found + if new_tag == "where": + self.warn(fdata, "tag 'Where' is invalid. Should be 'What:' instead") + new_tag = "what" + + if new_tag == "what": + fdata.space = None + + if content not in self.what_symbols: + self.add_symbol(what=content, fname=fdata.fname, ln=fdata.ln) + + if fdata.tag == "what": + fdata.what.append(content.strip("\n")) + else: + if fdata.key: + if "description" not in self.data.get(fdata.key, {}): + self.warn(fdata, f"{fdata.key} doesn't have a description") + + for w in fdata.what: + self.add_symbol(what=w, fname=fdata.fname, + ln=fdata.what_ln, xref=fdata.key) + + fdata.label = content + new_what = True + + key = "abi_" + content.lower() + fdata.key = self.re_unprintable.sub("_", key).strip("_") + + # Avoid duplicated keys but using a defined seed, to make + # the namespace identical if there aren't changes at the + # ABI symbols + seed(42) + + while fdata.key in self.data: + char = randrange(0, 51) + ord("A") + if char > ord("Z"): + char += ord("a") - ord("Z") - 1 + + fdata.key += chr(char) + + if fdata.key and fdata.key not in self.data: + self.data[fdata.key] = { + "what": [content], + "file": [fdata.file_ref], + "path": fdata.ftype, + "line_no": fdata.ln, + } + + fdata.what = self.data[fdata.key]["what"] + + self.what_refs[content] = fdata.key + fdata.tag = new_tag + fdata.what_ln = fdata.ln + + if fdata.nametag["what"]: + t = (content, fdata.key) + if t not in fdata.nametag["symbols"]: + fdata.nametag["symbols"].append(t) + + return + + if fdata.tag and new_tag: + fdata.tag = new_tag + + if new_what: + fdata.label = "" + + if "description" in self.data[fdata.key]: + self.data[fdata.key]["description"] += "\n\n" + + if fdata.file_ref not in self.data[fdata.key]["file"]: + self.data[fdata.key]["file"].append(fdata.file_ref) + + if self.debug == AbiDebug.WHAT_PARSING: + self.log.debug("what: %s", fdata.what) + + if not fdata.what: + self.warn(fdata, "'What:' should come first:", line) + return + + if new_tag == "description": + fdata.space = None + + if content: + sep = sep.replace(":", " ") + + c = " " * len(new_tag) + sep + content + c = c.expandtabs() + + match = self.re_start_spc.match(c) + if match: + # Preserve initial spaces for the first line + fdata.space = match.group(1) + content = match.group(2) + "\n" + + self.data[fdata.key][fdata.tag] = content + + return + + # Store any contents before tags at the database + if not fdata.tag and "what" in fdata.nametag: + fdata.nametag["description"] += line + return + + if fdata.tag == "description": + content = line.expandtabs() + + if self.re_whitespace.sub("", content) == "": + self.data[fdata.key][fdata.tag] += "\n" + return + + if fdata.space is None: + match = self.re_start_spc.match(content) + if match: + # Preserve initial spaces for the first line + fdata.space = match.group(1) + + content = match.group(2) + "\n" + else: + if content.startswith(fdata.space): + content = content[len(fdata.space):] + + else: + fdata.space = "" + + if fdata.tag == "what": + w = content.strip("\n") + if w: + self.data[fdata.key][fdata.tag].append(w) + else: + self.data[fdata.key][fdata.tag] += content + return + + content = line.strip() + if fdata.tag: + if fdata.tag == "what": + w = content.strip("\n") + if w: + self.data[fdata.key][fdata.tag].append(w) + else: + self.data[fdata.key][fdata.tag] += "\n" + content.rstrip("\n") + return + + # Everything else is error + if content: + self.warn(fdata, "Unexpected content", line) + + def parse_readme(self, nametag, fname): + """Parse ABI README file""" + + nametag["what"] = ["Introduction"] + nametag["path"] = "README" + with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp: + for line in fp: + match = self.re_tag.match(line) + if match: + new = match.group(1).lower() + + match = self.re_valid.search(new) + if match: + nametag["description"] += "\n:" + line + continue + + nametag["description"] += line + + def parse_file(self, fname, path, basename): + """Parse a single file""" + + ref = f"abi_file_{path}_{basename}" + ref = self.re_unprintable.sub("_", ref).strip("_") + + # Store per-file state into a namespace variable. This will be used + # by the per-line parser state machine and by the warning function. + fdata = Namespace + + fdata.fname = fname + fdata.name = basename + + pos = fname.find(ABI_DIR) + if pos > 0: + f = fname[pos:] + else: + f = fname + + fdata.file_ref = (f, ref) + self.file_refs[f] = ref + + fdata.ln = 0 + fdata.what_ln = 0 + fdata.tag = "" + fdata.label = "" + fdata.what = [] + fdata.key = None + fdata.xrefs = None + fdata.space = None + fdata.ftype = path.split("/")[0] + + fdata.nametag = {} + fdata.nametag["what"] = [f"ABI file {path}/{basename}"] + fdata.nametag["type"] = "File" + fdata.nametag["path"] = fdata.ftype + fdata.nametag["file"] = [fdata.file_ref] + fdata.nametag["line_no"] = 1 + fdata.nametag["description"] = "" + fdata.nametag["symbols"] = [] + + self.data[ref] = fdata.nametag + + if self.debug & AbiDebug.WHAT_OPEN: + self.log.debug("Opening file %s", fname) + + if basename == "README": + self.parse_readme(fdata.nametag, fname) + return + + with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp: + for line in fp: + fdata.ln += 1 + + self._parse_line(fdata, line) + + if "description" in fdata.nametag: + fdata.nametag["description"] = fdata.nametag["description"].lstrip("\n") + + if fdata.key: + if "description" not in self.data.get(fdata.key, {}): + self.warn(fdata, f"{fdata.key} doesn't have a description") + + for w in fdata.what: + self.add_symbol(what=w, fname=fname, xref=fdata.key) + + def _parse_abi(self, root=None): + """Internal function to parse documentation ABI recursively""" + + if not root: + root = self.directory + + with os.scandir(root) as obj: + for entry in obj: + name = os.path.join(root, entry.name) + + if entry.is_dir(): + self._parse_abi(name) + continue + + if not entry.is_file(): + continue + + basename = os.path.basename(name) + + if basename.startswith("."): + continue + + if basename.endswith(self.ignore_suffixes): + continue + + path = self.re_abi_dir.sub("", os.path.dirname(name)) + + self.parse_file(name, path, basename) + + def parse_abi(self, root=None): + """Parse documentation ABI""" + + self._parse_abi(root) + + if self.debug & AbiDebug.DUMP_ABI_STRUCTS: + self.log.debug(pformat(self.data)) + + def desc_txt(self, desc): + """Print description as found inside ABI files""" + + desc = desc.strip(" \t\n") + + return desc + "\n\n" + + def xref(self, fname): + """ + Converts a Documentation/ABI + basename into a ReST cross-reference + """ + + xref = self.file_refs.get(fname) + if not xref: + return None + else: + return xref + + def desc_rst(self, desc): + """Enrich ReST output by creating cross-references""" + + # Remove title markups from the description + # Having titles inside ABI files will only work if extra + # care would be taken in order to strictly follow the same + # level order for each markup. + desc = self.re_title_mark.sub("\n\n", "\n" + desc) + desc = desc.rstrip(" \t\n").lstrip("\n") + + # Python's regex performance for non-compiled expressions is a lot + # than Perl, as Perl automatically caches them at their + # first usage. Here, we'll need to do the same, as otherwise the + # performance penalty is be high + + new_desc = "" + for d in desc.split("\n"): + if d == "": + new_desc += "\n" + continue + + # Use cross-references for doc files where needed + d = self.re_doc.sub(r":doc:`/\1`", d) + + # Use cross-references for ABI generated docs where needed + matches = self.re_abi.findall(d) + for m in matches: + abi = m[0] + m[1] + + xref = self.file_refs.get(abi) + if not xref: + # This may happen if ABI is on a separate directory, + # like parsing ABI testing and symbol is at stable. + # The proper solution is to move this part of the code + # for it to be inside sphinx/kernel_abi.py + self.log.info("Didn't find ABI reference for '%s'", abi) + else: + new = self.re_escape.sub(r"\\\1", m[1]) + d = re.sub(fr"\b{abi}\b", f":ref:`{new} <{xref}>`", d) + + # Seek for cross reference symbols like /sys/... + # Need to be careful to avoid doing it on a code block + if d[0] not in [" ", "\t"]: + matches = self.re_xref_node.findall(d) + for m in matches: + # Finding ABI here is more complex due to wildcards + xref = self.what_refs.get(m) + if xref: + new = self.re_escape.sub(r"\\\1", m) + d = re.sub(fr"\b{m}\b", f":ref:`{new} <{xref}>`", d) + + new_desc += d + "\n" + + return new_desc + "\n\n" + + def doc(self, output_in_txt=False, show_symbols=True, show_file=True, + filter_path=None): + """Print ABI at stdout""" + + part = None + for key, v in sorted(self.data.items(), + key=lambda x: (x[1].get("type", ""), + x[1].get("what"))): + + wtype = v.get("type", "Symbol") + file_ref = v.get("file") + names = v.get("what", [""]) + + if wtype == "File": + if not show_file: + continue + else: + if not show_symbols: + continue + + if filter_path: + if v.get("path") != filter_path: + continue + + msg = "" + + if wtype != "File": + cur_part = names[0] + if cur_part.find("/") >= 0: + match = self.re_what.match(cur_part) + if match: + symbol = match.group(1).rstrip("/") + cur_part = "Symbols under " + symbol + + if cur_part and cur_part != part: + part = cur_part + msg += part + "\n"+ "-" * len(part) +"\n\n" + + msg += f".. _{key}:\n\n" + + max_len = 0 + for i in range(0, len(names)): # pylint: disable=C0200 + names[i] = "**" + self.re_escape.sub(r"\\\1", names[i]) + "**" + + max_len = max(max_len, len(names[i])) + + msg += "+-" + "-" * max_len + "-+\n" + for name in names: + msg += f"| {name}" + " " * (max_len - len(name)) + " |\n" + msg += "+-" + "-" * max_len + "-+\n" + msg += "\n" + + for ref in file_ref: + if wtype == "File": + msg += f".. _{ref[1]}:\n\n" + else: + base = os.path.basename(ref[0]) + msg += f"Defined on file :ref:`{base} <{ref[1]}>`\n\n" + + if wtype == "File": + msg += names[0] +"\n" + "-" * len(names[0]) +"\n\n" + + desc = v.get("description") + if not desc and wtype != "File": + msg += f"DESCRIPTION MISSING for {names[0]}\n\n" + + if desc: + if output_in_txt: + msg += self.desc_txt(desc) + else: + msg += self.desc_rst(desc) + + symbols = v.get("symbols") + if symbols: + msg += "Has the following ABI:\n\n" + + for w, label in symbols: + # Escape special chars from content + content = self.re_escape.sub(r"\\\1", w) + + msg += f"- :ref:`{content} <{label}>`\n\n" + + users = v.get("users") + if users and users.strip(" \t\n"): + users = users.strip("\n").replace('\n', '\n\t') + msg += f"Users:\n\t{users}\n\n" + + ln = v.get("line_no", 1) + + yield (msg, file_ref[0][0], ln) + + def check_issues(self): + """Warn about duplicated ABI entries""" + + for what, v in self.what_symbols.items(): + files = v.get("file") + if not files: + # Should never happen if the parser works properly + self.log.warning("%s doesn't have a file associated", what) + continue + + if len(files) == 1: + continue + + f = [] + for fname, lines in sorted(files.items()): + if not lines: + f.append(f"{fname}") + elif len(lines) == 1: + f.append(f"{fname}:{lines[0]}") + else: + m = fname + "lines " + m += ", ".join(str(x) for x in lines) + f.append(m) + + self.log.warning("%s is defined %d times: %s", what, len(f), "; ".join(f)) + + def search_symbols(self, expr): + """ Searches for ABI symbols """ + + regex = re.compile(expr, re.I) + + found_keys = 0 + for t in sorted(self.data.items(), key=lambda x: [0]): + v = t[1] + + wtype = v.get("type", "") + if wtype == "File": + continue + + for what in v.get("what", [""]): + if regex.search(what): + found_keys += 1 + + kernelversion = v.get("kernelversion", "").strip(" \t\n") + date = v.get("date", "").strip(" \t\n") + contact = v.get("contact", "").strip(" \t\n") + users = v.get("users", "").strip(" \t\n") + desc = v.get("description", "").strip(" \t\n") + + files = [] + for f in v.get("file", ()): + files.append(f[0]) + + what = str(found_keys) + ". " + what + title_tag = "-" * len(what) + + print(f"\n{what}\n{title_tag}\n") + + if kernelversion: + print(f"Kernel version:\t\t{kernelversion}") + + if date: + print(f"Date:\t\t\t{date}") + + if contact: + print(f"Contact:\t\t{contact}") + + if users: + print(f"Users:\t\t\t{users}") + + print("Defined on file(s):\t" + ", ".join(files)) + + if desc: + desc = desc.strip("\n") + print(f"\n{desc}\n") + + if not found_keys: + print(f"Regular expression /{expr}/ not found.") diff --git a/scripts/lib/abi/abi_regex.py b/scripts/lib/abi/abi_regex.py new file mode 100644 index 000000000000..8a57846cbc69 --- /dev/null +++ b/scripts/lib/abi/abi_regex.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# xxpylint: disable=R0903 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# SPDX-License-Identifier: GPL-2.0 + +""" +Convert ABI what into regular expressions +""" + +import re +import sys + +from pprint import pformat + +from abi_parser import AbiParser +from helpers import AbiDebug + +class AbiRegex(AbiParser): + """Extends AbiParser to search ABI nodes with regular expressions""" + + # Escape only ASCII visible characters + escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])" + leave_others = "others" + + # Tuples with regular expressions to be compiled and replacement data + re_whats = [ + # Drop escape characters that might exist + (re.compile("\\\\"), ""), + + # Temporarily escape dot characters + (re.compile(r"\."), "\xf6"), + + # Temporarily change [0-9]+ type of patterns + (re.compile(r"\[0\-9\]\+"), "\xff"), + + # Temporarily change [\d+-\d+] type of patterns + (re.compile(r"\[0\-\d+\]"), "\xff"), + (re.compile(r"\[0:\d+\]"), "\xff"), + (re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"), + + # Temporarily change [0-9] type of patterns + (re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"), + + # Handle multiple option patterns + (re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"), + + # Handle wildcards + (re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"), + (re.compile(r"/\*/"), "/.*/"), + (re.compile(r"/\xf6\xf6\xf6"), "/.*"), + (re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"), + (re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"), + (re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"), + + (re.compile(r"XX+"), "\\\\w\xf7"), + (re.compile(r"([^A-Z])[XYZ]([^A-Z])"), "\\1\\\\w\xf7\\2"), + (re.compile(r"([^A-Z])[XYZ]$"), "\\1\\\\w\xf7"), + (re.compile(r"_[AB]_"), "_\\\\w\xf7_"), + + # Recover [0-9] type of patterns + (re.compile(r"\xf4"), "["), + (re.compile(r"\xf5"), "]"), + + # Remove duplicated spaces + (re.compile(r"\s+"), r" "), + + # Special case: drop comparison as in: + # What: foo = <something> + # (this happens on a few IIO definitions) + (re.compile(r"\s*\=.*$"), ""), + + # Escape all other symbols + (re.compile(escape_symbols), r"\\\1"), + (re.compile(r"\\\\"), r"\\"), + (re.compile(r"\\([\[\]\(\)\|])"), r"\1"), + (re.compile(r"(\d+)\\(-\d+)"), r"\1\2"), + + (re.compile(r"\xff"), r"\\d+"), + + # Special case: IIO ABI which a parenthesis. + (re.compile(r"sqrt(.*)"), r"sqrt(.*)"), + + # Simplify regexes with multiple .* + (re.compile(r"(?:\.\*){2,}"), ""), + + # Recover dot characters + (re.compile(r"\xf6"), "\\."), + # Recover plus characters + (re.compile(r"\xf7"), "+"), + ] + re_has_num = re.compile(r"\\d") + + # Symbol name after escape_chars that are considered a devnode basename + re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$") + + # List of popular group names to be skipped to minimize regex group size + # Use AbiDebug.SUBGROUP_SIZE to detect those + skip_names = set(["devices", "hwmon"]) + + def regex_append(self, what, new): + """ + Get a search group for a subset of regular expressions. + + As ABI may have thousands of symbols, using a for to search all + regular expressions is at least O(n^2). When there are wildcards, + the complexity increases substantially, eventually becoming exponential. + + To avoid spending too much time on them, use a logic to split + them into groups. The smaller the group, the better, as it would + mean that searches will be confined to a small number of regular + expressions. + + The conversion to a regex subset is tricky, as we need something + that can be easily obtained from the sysfs symbol and from the + regular expression. So, we need to discard nodes that have + wildcards. + + If it can't obtain a subgroup, place the regular expression inside + a special group (self.leave_others). + """ + + search_group = None + + for search_group in reversed(new.split("/")): + if not search_group or search_group in self.skip_names: + continue + if self.re_symbol_name.match(search_group): + break + + if not search_group: + search_group = self.leave_others + + if self.debug & AbiDebug.SUBGROUP_MAP: + self.log.debug("%s: mapped as %s", what, search_group) + + try: + if search_group not in self.regex_group: + self.regex_group[search_group] = [] + + self.regex_group[search_group].append(re.compile(new)) + if self.search_string: + if what.find(self.search_string) >= 0: + print(f"What: {what}") + except re.PatternError: + self.log.warning("Ignoring '%s' as it produced an invalid regex:\n" + " '%s'", what, new) + + def get_regexes(self, what): + """ + Given an ABI devnode, return a list of all regular expressions that + may match it, based on the sub-groups created by regex_append() + """ + + re_list = [] + + patches = what.split("/") + patches.reverse() + patches.append(self.leave_others) + + for search_group in patches: + if search_group in self.regex_group: + re_list += self.regex_group[search_group] + + return re_list + + def __init__(self, *args, **kwargs): + """ + Override init method to get verbose argument + """ + + self.regex_group = None + self.search_string = None + self.re_string = None + + if "search_string" in kwargs: + self.search_string = kwargs.get("search_string") + del kwargs["search_string"] + + if self.search_string: + + try: + self.re_string = re.compile(self.search_string) + except re.PatternError as e: + msg = f"{self.search_string} is not a valid regular expression" + raise ValueError(msg) from e + + super().__init__(*args, **kwargs) + + def parse_abi(self, *args, **kwargs): + + super().parse_abi(*args, **kwargs) + + self.regex_group = {} + + print("Converting ABI What fields into regexes...", file=sys.stderr) + + for t in sorted(self.data.items(), key=lambda x: x[0]): + v = t[1] + if v.get("type") == "File": + continue + + v["regex"] = [] + + for what in v.get("what", []): + if not what.startswith("/sys"): + continue + + new = what + for r, s in self.re_whats: + try: + new = r.sub(s, new) + except re.PatternError as e: + # Help debugging troubles with new regexes + raise re.PatternError(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e + + v["regex"].append(new) + + if self.debug & AbiDebug.REGEX: + self.log.debug("%-90s <== %s", new, what) + + # Store regex into a subgroup to speedup searches + self.regex_append(what, new) + + if self.debug & AbiDebug.SUBGROUP_DICT: + self.log.debug("%s", pformat(self.regex_group)) + + if self.debug & AbiDebug.SUBGROUP_SIZE: + biggestd_keys = sorted(self.regex_group.keys(), + key= lambda k: len(self.regex_group[k]), + reverse=True) + + print("Top regex subgroups:", file=sys.stderr) + for k in biggestd_keys[:10]: + print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr) diff --git a/scripts/lib/abi/helpers.py b/scripts/lib/abi/helpers.py new file mode 100644 index 000000000000..639b23e4ca33 --- /dev/null +++ b/scripts/lib/abi/helpers.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# pylint: disable=R0903 +# SPDX-License-Identifier: GPL-2.0 + +""" +Helper classes for ABI parser +""" + +ABI_DIR = "Documentation/ABI/" + + +class AbiDebug: + """Debug levels""" + + WHAT_PARSING = 1 + WHAT_OPEN = 2 + DUMP_ABI_STRUCTS = 4 + UNDEFINED = 8 + REGEX = 16 + SUBGROUP_MAP = 32 + SUBGROUP_DICT = 64 + SUBGROUP_SIZE = 128 + GRAPH = 256 + + +DEBUG_HELP = """ +1 - enable debug parsing logic +2 - enable debug messages on file open +4 - enable debug for ABI parse data +8 - enable extra debug information to identify troubles + with ABI symbols found at the local machine that + weren't found on ABI documentation (used only for + undefined subcommand) +16 - enable debug for what to regex conversion +32 - enable debug for symbol regex subgroups +64 - enable debug for sysfs graph tree variable +""" diff --git a/scripts/lib/abi/system_symbols.py b/scripts/lib/abi/system_symbols.py new file mode 100644 index 000000000000..f15c94a6e33c --- /dev/null +++ b/scripts/lib/abi/system_symbols.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 +# pylint: disable=R0902,R0912,R0914,R0915,R1702 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# SPDX-License-Identifier: GPL-2.0 + +""" +Parse ABI documentation and produce results from it. +""" + +import os +import re +import sys + +from concurrent import futures +from datetime import datetime +from random import shuffle + +from helpers import AbiDebug + +class SystemSymbols: + """Stores arguments for the class and initialize class vars""" + + def graph_add_file(self, path, link=None): + """ + add a file path to the sysfs graph stored at self.root + """ + + if path in self.files: + return + + name = "" + ref = self.root + for edge in path.split("/"): + name += edge + "/" + if edge not in ref: + ref[edge] = {"__name": [name.rstrip("/")]} + + ref = ref[edge] + + if link and link not in ref["__name"]: + ref["__name"].append(link.rstrip("/")) + + self.files.add(path) + + def print_graph(self, root_prefix="", root=None, level=0): + """Prints a reference tree graph using UTF-8 characters""" + + if not root: + root = self.root + level = 0 + + # Prevent endless traverse + if level > 5: + return + + if level > 0: + prefix = "├──" + last_prefix = "└──" + else: + prefix = "" + last_prefix = "" + + items = list(root.items()) + + names = root.get("__name", []) + for k, edge in items: + if k == "__name": + continue + + if not k: + k = "/" + + if len(names) > 1: + k += " links: " + ",".join(names[1:]) + + if edge == items[-1][1]: + print(root_prefix + last_prefix + k) + p = root_prefix + if level > 0: + p += " " + self.print_graph(p, edge, level + 1) + else: + print(root_prefix + prefix + k) + p = root_prefix + "│ " + self.print_graph(p, edge, level + 1) + + def _walk(self, root): + """ + Walk through sysfs to get all devnodes that aren't ignored. + + By default, uses /sys as sysfs mounting point. If another + directory is used, it replaces them to /sys at the patches. + """ + + with os.scandir(root) as obj: + for entry in obj: + path = os.path.join(root, entry.name) + if self.sysfs: + p = path.replace(self.sysfs, "/sys", count=1) + else: + p = path + + if self.re_ignore.search(p): + return + + # Handle link first to avoid directory recursion + if entry.is_symlink(): + real = os.path.realpath(path) + if not self.sysfs: + self.aliases[path] = real + else: + real = real.replace(self.sysfs, "/sys", count=1) + + # Add absfile location to graph if it doesn't exist + if not self.re_ignore.search(real): + # Add link to the graph + self.graph_add_file(real, p) + + elif entry.is_file(): + self.graph_add_file(p) + + elif entry.is_dir(): + self._walk(path) + + def __init__(self, abi, sysfs="/sys", hints=False): + """ + Initialize internal variables and get a list of all files inside + sysfs that can currently be parsed. + + Please notice that there are several entries on sysfs that aren't + documented as ABI. Ignore those. + + The real paths will be stored under self.files. Aliases will be + stored in separate, as self.aliases. + """ + + self.abi = abi + self.log = abi.log + + if sysfs != "/sys": + self.sysfs = sysfs.rstrip("/") + else: + self.sysfs = None + + self.hints = hints + + self.root = {} + self.aliases = {} + self.files = set() + + dont_walk = [ + # Those require root access and aren't documented at ABI + f"^{sysfs}/kernel/debug", + f"^{sysfs}/kernel/tracing", + f"^{sysfs}/fs/pstore", + f"^{sysfs}/fs/bpf", + f"^{sysfs}/fs/fuse", + + # This is not documented at ABI + f"^{sysfs}/module", + + f"^{sysfs}/fs/cgroup", # this is big and has zero docs under ABI + f"^{sysfs}/firmware", # documented elsewhere: ACPI, DT bindings + "sections|notes", # aren't actually part of ABI + + # kernel-parameters.txt - not easy to parse + "parameters", + ] + + self.re_ignore = re.compile("|".join(dont_walk)) + + print(f"Reading {sysfs} directory contents...", file=sys.stderr) + self._walk(sysfs) + + def check_file(self, refs, found): + """Check missing ABI symbols for a given sysfs file""" + + res_list = [] + + try: + for names in refs: + fname = names[0] + + res = { + "found": False, + "fname": fname, + "msg": "", + } + res_list.append(res) + + re_what = self.abi.get_regexes(fname) + if not re_what: + self.abi.log.warning(f"missing rules for {fname}") + continue + + for name in names: + for r in re_what: + if self.abi.debug & AbiDebug.UNDEFINED: + self.log.debug("check if %s matches '%s'", name, r.pattern) + if r.match(name): + res["found"] = True + if found: + res["msg"] += f" {fname}: regex:\n\t" + continue + + if self.hints and not res["found"]: + res["msg"] += f" {fname} not found. Tested regexes:\n" + for r in re_what: + res["msg"] += " " + r.pattern + "\n" + + except KeyboardInterrupt: + pass + + return res_list + + def _ref_interactor(self, root): + """Recursive function to interact over the sysfs tree""" + + for k, v in root.items(): + if isinstance(v, dict): + yield from self._ref_interactor(v) + + if root == self.root or k == "__name": + continue + + if self.abi.re_string: + fname = v["__name"][0] + if self.abi.re_string.search(fname): + yield v + else: + yield v + + + def get_fileref(self, all_refs, chunk_size): + """Interactor to group refs into chunks""" + + n = 0 + refs = [] + + for ref in all_refs: + refs.append(ref) + + n += 1 + if n >= chunk_size: + yield refs + n = 0 + refs = [] + + yield refs + + def check_undefined_symbols(self, max_workers=None, chunk_size=50, + found=None, dry_run=None): + """Seach ABI for sysfs symbols missing documentation""" + + self.abi.parse_abi() + + if self.abi.debug & AbiDebug.GRAPH: + self.print_graph() + + all_refs = [] + for ref in self._ref_interactor(self.root): + all_refs.append(ref["__name"]) + + if dry_run: + print("Would check", file=sys.stderr) + for ref in all_refs: + print(", ".join(ref)) + + return + + print("Starting to search symbols (it may take several minutes):", + file=sys.stderr) + start = datetime.now() + old_elapsed = None + + # Python doesn't support multithreading due to limitations on its + # global lock (GIL). While Python 3.13 finally made GIL optional, + # there are still issues related to it. Also, we want to have + # backward compatibility with older versions of Python. + # + # So, use instead multiprocess. However, Python is very slow passing + # data from/to multiple processes. Also, it may consume lots of memory + # if the data to be shared is not small. So, we need to group workload + # in chunks that are big enough to generate performance gains while + # not being so big that would cause out-of-memory. + + num_refs = len(all_refs) + print(f"Number of references to parse: {num_refs}", file=sys.stderr) + + if not max_workers: + max_workers = os.cpu_count() + elif max_workers > os.cpu_count(): + max_workers = os.cpu_count() + + max_workers = max(max_workers, 1) + + max_chunk_size = int((num_refs + max_workers - 1) / max_workers) + chunk_size = min(chunk_size, max_chunk_size) + chunk_size = max(1, chunk_size) + + if max_workers > 1: + executor = futures.ProcessPoolExecutor + + # Place references in a random order. This may help improving + # performance, by mixing complex/simple expressions when creating + # chunks + shuffle(all_refs) + else: + # Python has a high overhead with processes. When there's just + # one worker, it is faster to not create a new process. + # Yet, User still deserves to have a progress print. So, use + # python's "thread", which is actually a single process, using + # an internal schedule to switch between tasks. No performance + # gains for non-IO tasks, but still it can be quickly interrupted + # from time to time to display progress. + executor = futures.ThreadPoolExecutor + + not_found = [] + f_list = [] + with executor(max_workers=max_workers) as exe: + for refs in self.get_fileref(all_refs, chunk_size): + if refs: + try: + f_list.append(exe.submit(self.check_file, refs, found)) + + except KeyboardInterrupt: + return + + total = len(f_list) + + if not total: + if self.abi.re_string: + print(f"No ABI symbol matches {self.abi.search_string}") + else: + self.abi.log.warning("No ABI symbols found") + return + + print(f"{len(f_list):6d} jobs queued on {max_workers} workers", + file=sys.stderr) + + while f_list: + try: + t = futures.wait(f_list, timeout=1, + return_when=futures.FIRST_COMPLETED) + + done = t[0] + + for fut in done: + res_list = fut.result() + + for res in res_list: + if not res["found"]: + not_found.append(res["fname"]) + if res["msg"]: + print(res["msg"]) + + f_list.remove(fut) + except KeyboardInterrupt: + return + + except RuntimeError as e: + self.abi.log.warning(f"Future: {e}") + break + + if sys.stderr.isatty(): + elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0] + if len(f_list) < total: + elapsed += f" ({total - len(f_list)}/{total} jobs completed). " + if elapsed != old_elapsed: + print(elapsed + "\r", end="", flush=True, + file=sys.stderr) + old_elapsed = elapsed + + elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0] + print(elapsed, file=sys.stderr) + + for f in sorted(not_found): + print(f"{f} not found.") diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py new file mode 100644 index 000000000000..9be4a64df71d --- /dev/null +++ b/scripts/lib/kdoc/kdoc_files.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# +# pylint: disable=R0903,R0913,R0914,R0917 + +""" +Parse lernel-doc tags on multiple kernel source files. +""" + +import argparse +import logging +import os +import re + +from kdoc_parser import KernelDoc +from kdoc_output import OutputFormat + + +class GlobSourceFiles: + """ + Parse C source code file names and directories via an Interactor. + """ + + def __init__(self, srctree=None, valid_extensions=None): + """ + Initialize valid extensions with a tuple. + + If not defined, assume default C extensions (.c and .h) + + It would be possible to use python's glob function, but it is + very slow, and it is not interactive. So, it would wait to read all + directories before actually do something. + + So, let's use our own implementation. + """ + + if not valid_extensions: + self.extensions = (".c", ".h") + else: + self.extensions = valid_extensions + + self.srctree = srctree + + def _parse_dir(self, dirname): + """Internal function to parse files recursively""" + + with os.scandir(dirname) as obj: + for entry in obj: + name = os.path.join(dirname, entry.name) + + if entry.is_dir(): + yield from self._parse_dir(name) + + if not entry.is_file(): + continue + + basename = os.path.basename(name) + + if not basename.endswith(self.extensions): + continue + + yield name + + def parse_files(self, file_list, file_not_found_cb): + """ + Define an interator to parse all source files from file_list, + handling directories if any + """ + + if not file_list: + return + + for fname in file_list: + if self.srctree: + f = os.path.join(self.srctree, fname) + else: + f = fname + + if os.path.isdir(f): + yield from self._parse_dir(f) + elif os.path.isfile(f): + yield f + elif file_not_found_cb: + file_not_found_cb(fname) + + +class KernelFiles(): + """ + Parse kernel-doc tags on multiple kernel source files. + + There are two type of parsers defined here: + - self.parse_file(): parses both kernel-doc markups and + EXPORT_SYMBOL* macros; + - self.process_export_file(): parses only EXPORT_SYMBOL* macros. + """ + + def warning(self, msg): + """Ancillary routine to output a warning and increment error count""" + + self.config.log.warning(msg) + self.errors += 1 + + def error(self, msg): + """Ancillary routine to output an error and increment error count""" + + self.config.log.error(msg) + self.errors += 1 + + def parse_file(self, fname): + """ + Parse a single Kernel source. + """ + + # Prevent parsing the same file twice if results are cached + if fname in self.files: + return + + doc = KernelDoc(self.config, fname) + export_table, entries = doc.parse_kdoc() + + self.export_table[fname] = export_table + + self.files.add(fname) + self.export_files.add(fname) # parse_kdoc() already check exports + + self.results[fname] = entries + + def process_export_file(self, fname): + """ + Parses EXPORT_SYMBOL* macros from a single Kernel source file. + """ + + # Prevent parsing the same file twice if results are cached + if fname in self.export_files: + return + + doc = KernelDoc(self.config, fname) + export_table = doc.parse_export() + + if not export_table: + self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}") + export_table = set() + + self.export_table[fname] = export_table + self.export_files.add(fname) + + def file_not_found_cb(self, fname): + """ + Callback to warn if a file was not found. + """ + + self.error(f"Cannot find file {fname}") + + def __init__(self, verbose=False, out_style=None, + werror=False, wreturn=False, wshort_desc=False, + wcontents_before_sections=False, + logger=None): + """ + Initialize startup variables and parse all files + """ + + if not verbose: + verbose = bool(os.environ.get("KBUILD_VERBOSE", 0)) + + if out_style is None: + out_style = OutputFormat() + + if not werror: + kcflags = os.environ.get("KCFLAGS", None) + if kcflags: + match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags) + if match: + werror = True + + # reading this variable is for backwards compat just in case + # someone was calling it with the variable from outside the + # kernel's build system + kdoc_werror = os.environ.get("KDOC_WERROR", None) + if kdoc_werror: + werror = kdoc_werror + + # Some variables are global to the parser logic as a whole as they are + # used to send control configuration to KernelDoc class. As such, + # those variables are read-only inside the KernelDoc. + self.config = argparse.Namespace + + self.config.verbose = verbose + self.config.werror = werror + self.config.wreturn = wreturn + self.config.wshort_desc = wshort_desc + self.config.wcontents_before_sections = wcontents_before_sections + + if not logger: + self.config.log = logging.getLogger("kernel-doc") + else: + self.config.log = logger + + self.config.warning = self.warning + + self.config.src_tree = os.environ.get("SRCTREE", None) + + # Initialize variables that are internal to KernelFiles + + self.out_style = out_style + + self.errors = 0 + self.results = {} + + self.files = set() + self.export_files = set() + self.export_table = {} + + def parse(self, file_list, export_file=None): + """ + Parse all files + """ + + glob = GlobSourceFiles(srctree=self.config.src_tree) + + for fname in glob.parse_files(file_list, self.file_not_found_cb): + self.parse_file(fname) + + for fname in glob.parse_files(export_file, self.file_not_found_cb): + self.process_export_file(fname) + + def out_msg(self, fname, name, arg): + """ + Return output messages from a file name using the output style + filtering. + + If output type was not handled by the syler, return None. + """ + + # NOTE: we can add rules here to filter out unwanted parts, + # although OutputFormat.msg already does that. + + return self.out_style.msg(fname, name, arg) + + def msg(self, enable_lineno=False, export=False, internal=False, + symbol=None, nosymbol=None, no_doc_sections=False, + filenames=None, export_file=None): + """ + Interacts over the kernel-doc results and output messages, + returning kernel-doc markups on each interaction + """ + + self.out_style.set_config(self.config) + + if not filenames: + filenames = sorted(self.results.keys()) + + glob = GlobSourceFiles(srctree=self.config.src_tree) + + for fname in filenames: + function_table = set() + + if internal or export: + if not export_file: + export_file = [fname] + + for f in glob.parse_files(export_file, self.file_not_found_cb): + function_table |= self.export_table[f] + + if symbol: + for s in symbol: + function_table.add(s) + + self.out_style.set_filter(export, internal, symbol, nosymbol, + function_table, enable_lineno, + no_doc_sections) + + msg = "" + if fname not in self.results: + self.config.log.warning("No kernel-doc for file %s", fname) + continue + + for name, arg in self.results[fname]: + m = self.out_msg(fname, name, arg) + + if m is None: + ln = arg.get("ln", 0) + dtype = arg.get('type', "") + + self.config.log.warning("%s:%d Can't handle %s", + fname, ln, dtype) + else: + msg += m + + if msg: + yield fname, msg diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py new file mode 100644 index 000000000000..86102e628d91 --- /dev/null +++ b/scripts/lib/kdoc/kdoc_output.py @@ -0,0 +1,793 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# +# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917 + +""" +Implement output filters to print kernel-doc documentation. + +The implementation uses a virtual base class (OutputFormat) which +contains a dispatches to virtual methods, and some code to filter +out output messages. + +The actual implementation is done on one separate class per each type +of output. Currently, there are output classes for ReST and man/troff. +""" + +import os +import re +from datetime import datetime + +from kdoc_parser import KernelDoc, type_param +from kdoc_re import KernRe + + +function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False) + +# match expressions used to find embedded type information +type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False) +type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False) +type_func = KernRe(r"(\w+)\(\)", cache=False) +type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) + +# Special RST handling for func ptr params +type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False) + +# Special RST handling for structs with func ptr params +type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False) + +type_env = KernRe(r"(\$\w+)", cache=False) +type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False) +type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False) +type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False) +type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False) +type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False) +type_fallback = KernRe(r"\&([_\w]+)", cache=False) +type_member_func = type_member + KernRe(r"\(\)", cache=False) + + +class OutputFormat: + """ + Base class for OutputFormat. If used as-is, it means that only + warnings will be displayed. + """ + + # output mode. + OUTPUT_ALL = 0 # output all symbols and doc sections + OUTPUT_INCLUDE = 1 # output only specified symbols + OUTPUT_EXPORTED = 2 # output exported symbols + OUTPUT_INTERNAL = 3 # output non-exported symbols + + # Virtual member to be overriden at the inherited classes + highlights = [] + + def __init__(self): + """Declare internal vars and set mode to OUTPUT_ALL""" + + self.out_mode = self.OUTPUT_ALL + self.enable_lineno = None + self.nosymbol = {} + self.symbol = None + self.function_table = None + self.config = None + self.no_doc_sections = False + + self.data = "" + + def set_config(self, config): + """ + Setup global config variables used by both parser and output. + """ + + self.config = config + + def set_filter(self, export, internal, symbol, nosymbol, function_table, + enable_lineno, no_doc_sections): + """ + Initialize filter variables according with the requested mode. + + Only one choice is valid between export, internal and symbol. + + The nosymbol filter can be used on all modes. + """ + + self.enable_lineno = enable_lineno + self.no_doc_sections = no_doc_sections + self.function_table = function_table + + if symbol: + self.out_mode = self.OUTPUT_INCLUDE + elif export: + self.out_mode = self.OUTPUT_EXPORTED + elif internal: + self.out_mode = self.OUTPUT_INTERNAL + else: + self.out_mode = self.OUTPUT_ALL + + if nosymbol: + self.nosymbol = set(nosymbol) + + + def highlight_block(self, block): + """ + Apply the RST highlights to a sub-block of text. + """ + + for r, sub in self.highlights: + block = r.sub(sub, block) + + return block + + def out_warnings(self, args): + """ + Output warnings for identifiers that will be displayed. + """ + + warnings = args.get('warnings', []) + + for log_msg in warnings: + self.config.warning(log_msg) + + def check_doc(self, name, args): + """Check if DOC should be output""" + + if self.no_doc_sections: + return False + + if name in self.nosymbol: + return False + + if self.out_mode == self.OUTPUT_ALL: + self.out_warnings(args) + return True + + if self.out_mode == self.OUTPUT_INCLUDE: + if name in self.function_table: + self.out_warnings(args) + return True + + return False + + def check_declaration(self, dtype, name, args): + """ + Checks if a declaration should be output or not based on the + filtering criteria. + """ + + if name in self.nosymbol: + return False + + if self.out_mode == self.OUTPUT_ALL: + self.out_warnings(args) + return True + + if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]: + if name in self.function_table: + return True + + if self.out_mode == self.OUTPUT_INTERNAL: + if dtype != "function": + self.out_warnings(args) + return True + + if name not in self.function_table: + self.out_warnings(args) + return True + + return False + + def msg(self, fname, name, args): + """ + Handles a single entry from kernel-doc parser + """ + + self.data = "" + + dtype = args.get('type', "") + + if dtype == "doc": + self.out_doc(fname, name, args) + return self.data + + if not self.check_declaration(dtype, name, args): + return self.data + + if dtype == "function": + self.out_function(fname, name, args) + return self.data + + if dtype == "enum": + self.out_enum(fname, name, args) + return self.data + + if dtype == "typedef": + self.out_typedef(fname, name, args) + return self.data + + if dtype in ["struct", "union"]: + self.out_struct(fname, name, args) + return self.data + + # Warn if some type requires an output logic + self.config.log.warning("doesn't now how to output '%s' block", + dtype) + + return None + + # Virtual methods to be overridden by inherited classes + # At the base class, those do nothing. + def out_doc(self, fname, name, args): + """Outputs a DOC block""" + + def out_function(self, fname, name, args): + """Outputs a function""" + + def out_enum(self, fname, name, args): + """Outputs an enum""" + + def out_typedef(self, fname, name, args): + """Outputs a typedef""" + + def out_struct(self, fname, name, args): + """Outputs a struct""" + + +class RestFormat(OutputFormat): + """Consts and functions used by ReST output""" + + highlights = [ + (type_constant, r"``\1``"), + (type_constant2, r"``\1``"), + + # Note: need to escape () to avoid func matching later + (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"), + (type_member, r":c:type:`\1\2\3 <\1>`"), + (type_fp_param, r"**\1\\(\\)**"), + (type_fp_param2, r"**\1\\(\\)**"), + (type_func, r"\1()"), + (type_enum, r":c:type:`\1 <\2>`"), + (type_struct, r":c:type:`\1 <\2>`"), + (type_typedef, r":c:type:`\1 <\2>`"), + (type_union, r":c:type:`\1 <\2>`"), + + # in rst this can refer to any type + (type_fallback, r":c:type:`\1`"), + (type_param_ref, r"**\1\2**") + ] + blankline = "\n" + + sphinx_literal = KernRe(r'^[^.].*::$', cache=False) + sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False) + + def __init__(self): + """ + Creates class variables. + + Not really mandatory, but it is a good coding style and makes + pylint happy. + """ + + super().__init__() + self.lineprefix = "" + + def print_lineno(self, ln): + """Outputs a line number""" + + if self.enable_lineno and ln is not None: + ln += 1 + self.data += f".. LINENO {ln}\n" + + def output_highlight(self, args): + """ + Outputs a C symbol that may require being converted to ReST using + the self.highlights variable + """ + + input_text = args + output = "" + in_literal = False + litprefix = "" + block = "" + + for line in input_text.strip("\n").split("\n"): + + # If we're in a literal block, see if we should drop out of it. + # Otherwise, pass the line straight through unmunged. + if in_literal: + if line.strip(): # If the line is not blank + # If this is the first non-blank line in a literal block, + # figure out the proper indent. + if not litprefix: + r = KernRe(r'^(\s*)') + if r.match(line): + litprefix = '^' + r.group(1) + else: + litprefix = "" + + output += line + "\n" + elif not KernRe(litprefix).match(line): + in_literal = False + else: + output += line + "\n" + else: + output += line + "\n" + + # Not in a literal block (or just dropped out) + if not in_literal: + block += line + "\n" + if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line): + in_literal = True + litprefix = "" + output += self.highlight_block(block) + block = "" + + # Handle any remaining block + if block: + output += self.highlight_block(block) + + # Print the output with the line prefix + for line in output.strip("\n").split("\n"): + self.data += self.lineprefix + line + "\n" + + def out_section(self, args, out_docblock=False): + """ + Outputs a block section. + + This could use some work; it's used to output the DOC: sections, and + starts by putting out the name of the doc section itself, but that + tends to duplicate a header already in the template file. + """ + + sectionlist = args.get('sectionlist', []) + sections = args.get('sections', {}) + section_start_lines = args.get('section_start_lines', {}) + + for section in sectionlist: + # Skip sections that are in the nosymbol_table + if section in self.nosymbol: + continue + + if out_docblock: + if not self.out_mode == self.OUTPUT_INCLUDE: + self.data += f".. _{section}:\n\n" + self.data += f'{self.lineprefix}**{section}**\n\n' + else: + self.data += f'{self.lineprefix}**{section}**\n\n' + + self.print_lineno(section_start_lines.get(section, 0)) + self.output_highlight(sections[section]) + self.data += "\n" + self.data += "\n" + + def out_doc(self, fname, name, args): + if not self.check_doc(name, args): + return + self.out_section(args, out_docblock=True) + + def out_function(self, fname, name, args): + + oldprefix = self.lineprefix + signature = "" + + func_macro = args.get('func_macro', False) + if func_macro: + signature = args['function'] + else: + if args.get('functiontype'): + signature = args['functiontype'] + " " + signature += args['function'] + " (" + + parameterlist = args.get('parameterlist', []) + parameterdescs = args.get('parameterdescs', {}) + parameterdesc_start_lines = args.get('parameterdesc_start_lines', {}) + + ln = args.get('declaration_start_line', 0) + + count = 0 + for parameter in parameterlist: + if count != 0: + signature += ", " + count += 1 + dtype = args['parametertypes'].get(parameter, "") + + if function_pointer.search(dtype): + signature += function_pointer.group(1) + parameter + function_pointer.group(3) + else: + signature += dtype + + if not func_macro: + signature += ")" + + self.print_lineno(ln) + if args.get('typedef') or not args.get('functiontype'): + self.data += f".. c:macro:: {args['function']}\n\n" + + if args.get('typedef'): + self.data += " **Typedef**: " + self.lineprefix = "" + self.output_highlight(args.get('purpose', "")) + self.data += "\n\n**Syntax**\n\n" + self.data += f" ``{signature}``\n\n" + else: + self.data += f"``{signature}``\n\n" + else: + self.data += f".. c:function:: {signature}\n\n" + + if not args.get('typedef'): + self.print_lineno(ln) + self.lineprefix = " " + self.output_highlight(args.get('purpose', "")) + self.data += "\n" + + # Put descriptive text into a container (HTML <div>) to help set + # function prototypes apart + self.lineprefix = " " + + if parameterlist: + self.data += ".. container:: kernelindent\n\n" + self.data += f"{self.lineprefix}**Parameters**\n\n" + + for parameter in parameterlist: + parameter_name = KernRe(r'\[.*').sub('', parameter) + dtype = args['parametertypes'].get(parameter, "") + + if dtype: + self.data += f"{self.lineprefix}``{dtype}``\n" + else: + self.data += f"{self.lineprefix}``{parameter}``\n" + + self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) + + self.lineprefix = " " + if parameter_name in parameterdescs and \ + parameterdescs[parameter_name] != KernelDoc.undescribed: + + self.output_highlight(parameterdescs[parameter_name]) + self.data += "\n" + else: + self.data += f"{self.lineprefix}*undescribed*\n\n" + self.lineprefix = " " + + self.out_section(args) + self.lineprefix = oldprefix + + def out_enum(self, fname, name, args): + + oldprefix = self.lineprefix + name = args.get('enum', '') + parameterlist = args.get('parameterlist', []) + parameterdescs = args.get('parameterdescs', {}) + ln = args.get('declaration_start_line', 0) + + self.data += f"\n\n.. c:enum:: {name}\n\n" + + self.print_lineno(ln) + self.lineprefix = " " + self.output_highlight(args.get('purpose', '')) + self.data += "\n" + + self.data += ".. container:: kernelindent\n\n" + outer = self.lineprefix + " " + self.lineprefix = outer + " " + self.data += f"{outer}**Constants**\n\n" + + for parameter in parameterlist: + self.data += f"{outer}``{parameter}``\n" + + if parameterdescs.get(parameter, '') != KernelDoc.undescribed: + self.output_highlight(parameterdescs[parameter]) + else: + self.data += f"{self.lineprefix}*undescribed*\n\n" + self.data += "\n" + + self.lineprefix = oldprefix + self.out_section(args) + + def out_typedef(self, fname, name, args): + + oldprefix = self.lineprefix + name = args.get('typedef', '') + ln = args.get('declaration_start_line', 0) + + self.data += f"\n\n.. c:type:: {name}\n\n" + + self.print_lineno(ln) + self.lineprefix = " " + + self.output_highlight(args.get('purpose', '')) + + self.data += "\n" + + self.lineprefix = oldprefix + self.out_section(args) + + def out_struct(self, fname, name, args): + + name = args.get('struct', "") + purpose = args.get('purpose', "") + declaration = args.get('definition', "") + dtype = args.get('type', "struct") + ln = args.get('declaration_start_line', 0) + + parameterlist = args.get('parameterlist', []) + parameterdescs = args.get('parameterdescs', {}) + parameterdesc_start_lines = args.get('parameterdesc_start_lines', {}) + + self.data += f"\n\n.. c:{dtype}:: {name}\n\n" + + self.print_lineno(ln) + + oldprefix = self.lineprefix + self.lineprefix += " " + + self.output_highlight(purpose) + self.data += "\n" + + self.data += ".. container:: kernelindent\n\n" + self.data += f"{self.lineprefix}**Definition**::\n\n" + + self.lineprefix = self.lineprefix + " " + + declaration = declaration.replace("\t", self.lineprefix) + + self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" + self.data += f"{declaration}{self.lineprefix}" + "};\n\n" + + self.lineprefix = " " + self.data += f"{self.lineprefix}**Members**\n\n" + for parameter in parameterlist: + if not parameter or parameter.startswith("#"): + continue + + parameter_name = parameter.split("[", maxsplit=1)[0] + + if parameterdescs.get(parameter_name) == KernelDoc.undescribed: + continue + + self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) + + self.data += f"{self.lineprefix}``{parameter}``\n" + + self.lineprefix = " " + self.output_highlight(parameterdescs[parameter_name]) + self.lineprefix = " " + + self.data += "\n" + + self.data += "\n" + + self.lineprefix = oldprefix + self.out_section(args) + + +class ManFormat(OutputFormat): + """Consts and functions used by man pages output""" + + highlights = ( + (type_constant, r"\1"), + (type_constant2, r"\1"), + (type_func, r"\\fB\1\\fP"), + (type_enum, r"\\fI\1\\fP"), + (type_struct, r"\\fI\1\\fP"), + (type_typedef, r"\\fI\1\\fP"), + (type_union, r"\\fI\1\\fP"), + (type_param, r"\\fI\1\\fP"), + (type_param_ref, r"\\fI\1\2\\fP"), + (type_member, r"\\fI\1\2\3\\fP"), + (type_fallback, r"\\fI\1\\fP") + ) + blankline = "" + + date_formats = [ + "%a %b %d %H:%M:%S %Z %Y", + "%a %b %d %H:%M:%S %Y", + "%Y-%m-%d", + "%b %d %Y", + "%B %d %Y", + "%m %d %Y", + ] + + def __init__(self, modulename): + """ + Creates class variables. + + Not really mandatory, but it is a good coding style and makes + pylint happy. + """ + + super().__init__() + self.modulename = modulename + + dt = None + tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") + if tstamp: + for fmt in self.date_formats: + try: + dt = datetime.strptime(tstamp, fmt) + break + except ValueError: + pass + + if not dt: + dt = datetime.now() + + self.man_date = dt.strftime("%B %Y") + + def output_highlight(self, block): + """ + Outputs a C symbol that may require being highlighted with + self.highlights variable using troff syntax + """ + + contents = self.highlight_block(block) + + if isinstance(contents, list): + contents = "\n".join(contents) + + for line in contents.strip("\n").split("\n"): + line = KernRe(r"^\s*").sub("", line) + if not line: + continue + + if line[0] == ".": + self.data += "\\&" + line + "\n" + else: + self.data += line + "\n" + + def out_doc(self, fname, name, args): + sectionlist = args.get('sectionlist', []) + sections = args.get('sections', {}) + + if not self.check_doc(name, args): + return + + self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n" + + for section in sectionlist: + self.data += f'.SH "{section}"' + "\n" + self.output_highlight(sections.get(section)) + + def out_function(self, fname, name, args): + """output function in man""" + + parameterlist = args.get('parameterlist', []) + parameterdescs = args.get('parameterdescs', {}) + sectionlist = args.get('sectionlist', []) + sections = args.get('sections', {}) + + self.data += f'.TH "{args["function"]}" 9 "{args["function"]}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" + + self.data += ".SH NAME\n" + self.data += f"{args['function']} \\- {args['purpose']}\n" + + self.data += ".SH SYNOPSIS\n" + if args.get('functiontype', ''): + self.data += f'.B "{args["functiontype"]}" {args["function"]}' + "\n" + else: + self.data += f'.B "{args["function"]}' + "\n" + + count = 0 + parenth = "(" + post = "," + + for parameter in parameterlist: + if count == len(parameterlist) - 1: + post = ");" + + dtype = args['parametertypes'].get(parameter, "") + if function_pointer.match(dtype): + # Pointer-to-function + self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" + else: + dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype) + + self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" + count += 1 + parenth = "" + + if parameterlist: + self.data += ".SH ARGUMENTS\n" + + for parameter in parameterlist: + parameter_name = re.sub(r'\[.*', '', parameter) + + self.data += f'.IP "{parameter}" 12' + "\n" + self.output_highlight(parameterdescs.get(parameter_name, "")) + + for section in sectionlist: + self.data += f'.SH "{section.upper()}"' + "\n" + self.output_highlight(sections[section]) + + def out_enum(self, fname, name, args): + + name = args.get('enum', '') + parameterlist = args.get('parameterlist', []) + sectionlist = args.get('sectionlist', []) + sections = args.get('sections', {}) + + self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" + + self.data += ".SH NAME\n" + self.data += f"enum {args['enum']} \\- {args['purpose']}\n" + + self.data += ".SH SYNOPSIS\n" + self.data += f"enum {args['enum']}" + " {\n" + + count = 0 + for parameter in parameterlist: + self.data += f'.br\n.BI " {parameter}"' + "\n" + if count == len(parameterlist) - 1: + self.data += "\n};\n" + else: + self.data += ", \n.br\n" + + count += 1 + + self.data += ".SH Constants\n" + + for parameter in parameterlist: + parameter_name = KernRe(r'\[.*').sub('', parameter) + self.data += f'.IP "{parameter}" 12' + "\n" + self.output_highlight(args['parameterdescs'].get(parameter_name, "")) + + for section in sectionlist: + self.data += f'.SH "{section}"' + "\n" + self.output_highlight(sections[section]) + + def out_typedef(self, fname, name, args): + module = self.modulename + typedef = args.get('typedef') + purpose = args.get('purpose') + sectionlist = args.get('sectionlist', []) + sections = args.get('sections', {}) + + self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n" + + self.data += ".SH NAME\n" + self.data += f"typedef {typedef} \\- {purpose}\n" + + for section in sectionlist: + self.data += f'.SH "{section}"' + "\n" + self.output_highlight(sections.get(section)) + + def out_struct(self, fname, name, args): + module = self.modulename + struct_type = args.get('type') + struct_name = args.get('struct') + purpose = args.get('purpose') + definition = args.get('definition') + sectionlist = args.get('sectionlist', []) + parameterlist = args.get('parameterlist', []) + sections = args.get('sections', {}) + parameterdescs = args.get('parameterdescs', {}) + + self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n" + + self.data += ".SH NAME\n" + self.data += f"{struct_type} {struct_name} \\- {purpose}\n" + + # Replace tabs with two spaces and handle newlines + declaration = definition.replace("\t", " ") + declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration) + + self.data += ".SH SYNOPSIS\n" + self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n" + self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" + + self.data += ".SH Members\n" + for parameter in parameterlist: + if parameter.startswith("#"): + continue + + parameter_name = re.sub(r"\[.*", "", parameter) + + if parameterdescs.get(parameter_name) == KernelDoc.undescribed: + continue + + self.data += f'.IP "{parameter}" 12' + "\n" + self.output_highlight(parameterdescs.get(parameter_name)) + + for section in sectionlist: + self.data += f'.SH "{section}"' + "\n" + self.output_highlight(sections.get(section)) diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py new file mode 100644 index 000000000000..062453eefc7a --- /dev/null +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -0,0 +1,1745 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. +# +# pylint: disable=C0301,C0302,R0904,R0912,R0913,R0914,R0915,R0917,R1702 + +""" +kdoc_parser +=========== + +Read a C language source or header FILE and extract embedded +documentation comments +""" + +import re +from pprint import pformat + +from kdoc_re import NestedMatch, KernRe + + +# +# Regular expressions used to parse kernel-doc markups at KernelDoc class. +# +# Let's declare them in lowercase outside any class to make easier to +# convert from the python script. +# +# As those are evaluated at the beginning, no need to cache them +# + +# Allow whitespace at end of comment start. +doc_start = KernRe(r'^/\*\*\s*$', cache=False) + +doc_end = KernRe(r'\*/', cache=False) +doc_com = KernRe(r'\s*\*\s*', cache=False) +doc_com_body = KernRe(r'\s*\* ?', cache=False) +doc_decl = doc_com + KernRe(r'(\w+)', cache=False) + +# @params and a strictly limited set of supported section names +# Specifically: +# Match @word: +# @...: +# @{section-name}: +# while trying to not match literal block starts like "example::" +# +doc_sect = doc_com + \ + KernRe(r'\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:([^:].*)?$', + flags=re.I, cache=False) + +doc_content = doc_com_body + KernRe(r'(.*)', cache=False) +doc_block = doc_com + KernRe(r'DOC:\s*(.*)?', cache=False) +doc_inline_start = KernRe(r'^\s*/\*\*\s*$', cache=False) +doc_inline_sect = KernRe(r'\s*\*\s*(@\s*[\w][\w\.]*\s*):(.*)', cache=False) +doc_inline_end = KernRe(r'^\s*\*/\s*$', cache=False) +doc_inline_oneline = KernRe(r'^\s*/\*\*\s*(@[\w\s]+):\s*(.*)\s*\*/\s*$', cache=False) +attribute = KernRe(r"__attribute__\s*\(\([a-z0-9,_\*\s\(\)]*\)\)", + flags=re.I | re.S, cache=False) + +export_symbol = KernRe(r'^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*', cache=False) +export_symbol_ns = KernRe(r'^\s*EXPORT_SYMBOL_NS(_GPL)?\s*\(\s*(\w+)\s*,\s*"\S+"\)\s*', cache=False) + +type_param = KernRe(r"\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) + +class state: + """ + State machine enums + """ + + # Parser states + NORMAL = 0 # normal code + NAME = 1 # looking for function name + BODY_MAYBE = 2 # body - or maybe more description + BODY = 3 # the body of the comment + BODY_WITH_BLANK_LINE = 4 # the body which has a blank line + PROTO = 5 # scanning prototype + DOCBLOCK = 6 # documentation block + INLINE = 7 # gathering doc outside main block + + name = [ + "NORMAL", + "NAME", + "BODY_MAYBE", + "BODY", + "BODY_WITH_BLANK_LINE", + "PROTO", + "DOCBLOCK", + "INLINE", + ] + + # Inline documentation state + INLINE_NA = 0 # not applicable ($state != INLINE) + INLINE_NAME = 1 # looking for member name (@foo:) + INLINE_TEXT = 2 # looking for member documentation + INLINE_END = 3 # done + INLINE_ERROR = 4 # error - Comment without header was found. + # Spit a warning as it's not + # proper kernel-doc and ignore the rest. + + inline_name = [ + "", + "_NAME", + "_TEXT", + "_END", + "_ERROR", + ] + +SECTION_DEFAULT = "Description" # default section + +class KernelEntry: + + def __init__(self, config, ln): + self.config = config + + self.contents = "" + self.function = "" + self.sectcheck = "" + self.struct_actual = "" + self.prototype = "" + + self.warnings = [] + + self.parameterlist = [] + self.parameterdescs = {} + self.parametertypes = {} + self.parameterdesc_start_lines = {} + + self.section_start_lines = {} + self.sectionlist = [] + self.sections = {} + + self.anon_struct_union = False + + self.leading_space = None + + # State flags + self.brcount = 0 + + self.in_doc_sect = False + self.declaration_start_line = ln + 1 + + # TODO: rename to emit_message after removal of kernel-doc.pl + def emit_msg(self, log_msg, warning=True): + """Emit a message""" + + if not warning: + self.config.log.info(log_msg) + return + + # Delegate warning output to output logic, as this way it + # will report warnings/info only for symbols that are output + + self.warnings.append(log_msg) + return + + def dump_section(self, start_new=True): + """ + Dumps section contents to arrays/hashes intended for that purpose. + """ + + name = self.section + contents = self.contents + + if type_param.match(name): + name = type_param.group(1) + + self.parameterdescs[name] = contents + self.parameterdesc_start_lines[name] = self.new_start_line + + self.sectcheck += name + " " + self.new_start_line = 0 + + elif name == "@...": + name = "..." + self.parameterdescs[name] = contents + self.sectcheck += name + " " + self.parameterdesc_start_lines[name] = self.new_start_line + self.new_start_line = 0 + + else: + if name in self.sections and self.sections[name] != "": + # Only warn on user-specified duplicate section names + if name != SECTION_DEFAULT: + self.emit_msg(self.new_start_line, + f"duplicate section name '{name}'\n") + self.sections[name] += contents + else: + self.sections[name] = contents + self.sectionlist.append(name) + self.section_start_lines[name] = self.new_start_line + self.new_start_line = 0 + +# self.config.log.debug("Section: %s : %s", name, pformat(vars(self))) + + if start_new: + self.section = SECTION_DEFAULT + self.contents = "" + + +class KernelDoc: + """ + Read a C language source or header FILE and extract embedded + documentation comments. + """ + + # Section names + + section_intro = "Introduction" + section_context = "Context" + section_return = "Return" + + undescribed = "-- undescribed --" + + def __init__(self, config, fname): + """Initialize internal variables""" + + self.fname = fname + self.config = config + + # Initial state for the state machines + self.state = state.NORMAL + self.inline_doc_state = state.INLINE_NA + + # Store entry currently being processed + self.entry = None + + # Place all potential outputs into an array + self.entries = [] + + def emit_msg(self, ln, msg, warning=True): + """Emit a message""" + + log_msg = f"{self.fname}:{ln} {msg}" + + if self.entry: + self.entry.emit_msg(log_msg, warning) + return + + if warning: + self.config.log.warning(log_msg) + else: + self.config.log.info(log_msg) + + def dump_section(self, start_new=True): + """ + Dumps section contents to arrays/hashes intended for that purpose. + """ + + if self.entry: + self.entry.dump_section(start_new) + + # TODO: rename it to store_declaration after removal of kernel-doc.pl + def output_declaration(self, dtype, name, **args): + """ + Stores the entry into an entry array. + + The actual output and output filters will be handled elsewhere + """ + + # The implementation here is different than the original kernel-doc: + # instead of checking for output filters or actually output anything, + # it just stores the declaration content at self.entries, as the + # output will happen on a separate class. + # + # For now, we're keeping the same name of the function just to make + # easier to compare the source code of both scripts + + args["declaration_start_line"] = self.entry.declaration_start_line + args["type"] = dtype + args["warnings"] = self.entry.warnings + + # TODO: use colletions.OrderedDict to remove sectionlist + + sections = args.get('sections', {}) + sectionlist = args.get('sectionlist', []) + + # Drop empty sections + # TODO: improve empty sections logic to emit warnings + for section in ["Description", "Return"]: + if section in sectionlist: + if not sections[section].rstrip(): + del sections[section] + sectionlist.remove(section) + + self.entries.append((name, args)) + + self.config.log.debug("Output: %s:%s = %s", dtype, name, pformat(args)) + + def reset_state(self, ln): + """ + Ancillary routine to create a new entry. It initializes all + variables used by the state machine. + """ + + self.entry = KernelEntry(self.config, ln) + + # State flags + self.state = state.NORMAL + self.inline_doc_state = state.INLINE_NA + + def push_parameter(self, ln, decl_type, param, dtype, + org_arg, declaration_name): + """ + Store parameters and their descriptions at self.entry. + """ + + if self.entry.anon_struct_union and dtype == "" and param == "}": + return # Ignore the ending }; from anonymous struct/union + + self.entry.anon_struct_union = False + + param = KernRe(r'[\[\)].*').sub('', param, count=1) + + if dtype == "" and param.endswith("..."): + if KernRe(r'\w\.\.\.$').search(param): + # For named variable parameters of the form `x...`, + # remove the dots + param = param[:-3] + else: + # Handles unnamed variable parameters + param = "..." + + if param not in self.entry.parameterdescs or \ + not self.entry.parameterdescs[param]: + + self.entry.parameterdescs[param] = "variable arguments" + + elif dtype == "" and (not param or param == "void"): + param = "void" + self.entry.parameterdescs[param] = "no arguments" + + elif dtype == "" and param in ["struct", "union"]: + # Handle unnamed (anonymous) union or struct + dtype = param + param = "{unnamed_" + param + "}" + self.entry.parameterdescs[param] = "anonymous\n" + self.entry.anon_struct_union = True + + # Handle cache group enforcing variables: they do not need + # to be described in header files + elif "__cacheline_group" in param: + # Ignore __cacheline_group_begin and __cacheline_group_end + return + + # Warn if parameter has no description + # (but ignore ones starting with # as these are not parameters + # but inline preprocessor statements) + if param not in self.entry.parameterdescs and not param.startswith("#"): + self.entry.parameterdescs[param] = self.undescribed + + if "." not in param: + if decl_type == 'function': + dname = f"{decl_type} parameter" + else: + dname = f"{decl_type} member" + + self.emit_msg(ln, + f"{dname} '{param}' not described in '{declaration_name}'") + + # Strip spaces from param so that it is one continuous string on + # parameterlist. This fixes a problem where check_sections() + # cannot find a parameter like "addr[6 + 2]" because it actually + # appears as "addr[6", "+", "2]" on the parameter list. + # However, it's better to maintain the param string unchanged for + # output, so just weaken the string compare in check_sections() + # to ignore "[blah" in a parameter string. + + self.entry.parameterlist.append(param) + org_arg = KernRe(r'\s\s+').sub(' ', org_arg) + self.entry.parametertypes[param] = org_arg + + def save_struct_actual(self, actual): + """ + Strip all spaces from the actual param so that it looks like + one string item. + """ + + actual = KernRe(r'\s*').sub("", actual, count=1) + + self.entry.struct_actual += actual + " " + + def create_parameter_list(self, ln, decl_type, args, + splitter, declaration_name): + """ + Creates a list of parameters, storing them at self.entry. + """ + + # temporarily replace all commas inside function pointer definition + arg_expr = KernRe(r'(\([^\),]+),') + while arg_expr.search(args): + args = arg_expr.sub(r"\1#", args) + + for arg in args.split(splitter): + # Strip comments + arg = KernRe(r'\/\*.*\*\/').sub('', arg) + + # Ignore argument attributes + arg = KernRe(r'\sPOS0?\s').sub(' ', arg) + + # Strip leading/trailing spaces + arg = arg.strip() + arg = KernRe(r'\s+').sub(' ', arg, count=1) + + if arg.startswith('#'): + # Treat preprocessor directive as a typeless variable just to fill + # corresponding data structures "correctly". Catch it later in + # output_* subs. + + # Treat preprocessor directive as a typeless variable + self.push_parameter(ln, decl_type, arg, "", + "", declaration_name) + + elif KernRe(r'\(.+\)\s*\(').search(arg): + # Pointer-to-function + + arg = arg.replace('#', ',') + + r = KernRe(r'[^\(]+\(\*?\s*([\w\[\]\.]*)\s*\)') + if r.match(arg): + param = r.group(1) + else: + self.emit_msg(ln, f"Invalid param: {arg}") + param = arg + + dtype = KernRe(r'([^\(]+\(\*?)\s*' + re.escape(param)).sub(r'\1', arg) + self.save_struct_actual(param) + self.push_parameter(ln, decl_type, param, dtype, + arg, declaration_name) + + elif KernRe(r'\(.+\)\s*\[').search(arg): + # Array-of-pointers + + arg = arg.replace('#', ',') + r = KernRe(r'[^\(]+\(\s*\*\s*([\w\[\]\.]*?)\s*(\s*\[\s*[\w]+\s*\]\s*)*\)') + if r.match(arg): + param = r.group(1) + else: + self.emit_msg(ln, f"Invalid param: {arg}") + param = arg + + dtype = KernRe(r'([^\(]+\(\*?)\s*' + re.escape(param)).sub(r'\1', arg) + + self.save_struct_actual(param) + self.push_parameter(ln, decl_type, param, dtype, + arg, declaration_name) + + elif arg: + arg = KernRe(r'\s*:\s*').sub(":", arg) + arg = KernRe(r'\s*\[').sub('[', arg) + + args = KernRe(r'\s*,\s*').split(arg) + if args[0] and '*' in args[0]: + args[0] = re.sub(r'(\*+)\s*', r' \1', args[0]) + + first_arg = [] + r = KernRe(r'^(.*\s+)(.*?\[.*\].*)$') + if args[0] and r.match(args[0]): + args.pop(0) + first_arg.extend(r.group(1)) + first_arg.append(r.group(2)) + else: + first_arg = KernRe(r'\s+').split(args.pop(0)) + + args.insert(0, first_arg.pop()) + dtype = ' '.join(first_arg) + + for param in args: + if KernRe(r'^(\*+)\s*(.*)').match(param): + r = KernRe(r'^(\*+)\s*(.*)') + if not r.match(param): + self.emit_msg(ln, f"Invalid param: {param}") + continue + + param = r.group(1) + + self.save_struct_actual(r.group(2)) + self.push_parameter(ln, decl_type, r.group(2), + f"{dtype} {r.group(1)}", + arg, declaration_name) + + elif KernRe(r'(.*?):(\w+)').search(param): + r = KernRe(r'(.*?):(\w+)') + if not r.match(param): + self.emit_msg(ln, f"Invalid param: {param}") + continue + + if dtype != "": # Skip unnamed bit-fields + self.save_struct_actual(r.group(1)) + self.push_parameter(ln, decl_type, r.group(1), + f"{dtype}:{r.group(2)}", + arg, declaration_name) + else: + self.save_struct_actual(param) + self.push_parameter(ln, decl_type, param, dtype, + arg, declaration_name) + + def check_sections(self, ln, decl_name, decl_type, sectcheck, prmscheck): + """ + Check for errors inside sections, emitting warnings if not found + parameters are described. + """ + + sects = sectcheck.split() + prms = prmscheck.split() + err = False + + for sx in range(len(sects)): # pylint: disable=C0200 + err = True + for px in range(len(prms)): # pylint: disable=C0200 + prm_clean = prms[px] + prm_clean = KernRe(r'\[.*\]').sub('', prm_clean) + prm_clean = attribute.sub('', prm_clean) + + # ignore array size in a parameter string; + # however, the original param string may contain + # spaces, e.g.: addr[6 + 2] + # and this appears in @prms as "addr[6" since the + # parameter list is split at spaces; + # hence just ignore "[..." for the sections check; + prm_clean = KernRe(r'\[.*').sub('', prm_clean) + + if prm_clean == sects[sx]: + err = False + break + + if err: + if decl_type == 'function': + dname = f"{decl_type} parameter" + else: + dname = f"{decl_type} member" + + self.emit_msg(ln, + f"Excess {dname} '{sects[sx]}' description in '{decl_name}'") + + def check_return_section(self, ln, declaration_name, return_type): + """ + If the function doesn't return void, warns about the lack of a + return description. + """ + + if not self.config.wreturn: + return + + # Ignore an empty return type (It's a macro) + # Ignore functions with a "void" return type (but not "void *") + if not return_type or KernRe(r'void\s*\w*\s*$').search(return_type): + return + + if not self.entry.sections.get("Return", None): + self.emit_msg(ln, + f"No description found for return value of '{declaration_name}'") + + def dump_struct(self, ln, proto): + """ + Store an entry for an struct or union + """ + + type_pattern = r'(struct|union)' + + qualifiers = [ + "__attribute__", + "__packed", + "__aligned", + "____cacheline_aligned_in_smp", + "____cacheline_aligned", + ] + + definition_body = r'\{(.*)\}\s*' + "(?:" + '|'.join(qualifiers) + ")?" + struct_members = KernRe(type_pattern + r'([^\{\};]+)(\{)([^\{\}]*)(\})([^\{\}\;]*)(\;)') + + # Extract struct/union definition + members = None + declaration_name = None + decl_type = None + + r = KernRe(type_pattern + r'\s+(\w+)\s*' + definition_body) + if r.search(proto): + decl_type = r.group(1) + declaration_name = r.group(2) + members = r.group(3) + else: + r = KernRe(r'typedef\s+' + type_pattern + r'\s*' + definition_body + r'\s*(\w+)\s*;') + + if r.search(proto): + decl_type = r.group(1) + declaration_name = r.group(3) + members = r.group(2) + + if not members: + self.emit_msg(ln, f"{proto} error: Cannot parse struct or union!") + return + + if self.entry.identifier != declaration_name: + self.emit_msg(ln, + f"expecting prototype for {decl_type} {self.entry.identifier}. Prototype was for {decl_type} {declaration_name} instead\n") + return + + args_pattern = r'([^,)]+)' + + sub_prefixes = [ + (KernRe(r'\/\*\s*private:.*?\/\*\s*public:.*?\*\/', re.S | re.I), ''), + (KernRe(r'\/\*\s*private:.*', re.S | re.I), ''), + + # Strip comments + (KernRe(r'\/\*.*?\*\/', re.S), ''), + + # Strip attributes + (attribute, ' '), + (KernRe(r'\s*__aligned\s*\([^;]*\)', re.S), ' '), + (KernRe(r'\s*__counted_by\s*\([^;]*\)', re.S), ' '), + (KernRe(r'\s*__counted_by_(le|be)\s*\([^;]*\)', re.S), ' '), + (KernRe(r'\s*__packed\s*', re.S), ' '), + (KernRe(r'\s*CRYPTO_MINALIGN_ATTR', re.S), ' '), + (KernRe(r'\s*____cacheline_aligned_in_smp', re.S), ' '), + (KernRe(r'\s*____cacheline_aligned', re.S), ' '), + + # Unwrap struct_group macros based on this definition: + # __struct_group(TAG, NAME, ATTRS, MEMBERS...) + # which has variants like: struct_group(NAME, MEMBERS...) + # Only MEMBERS arguments require documentation. + # + # Parsing them happens on two steps: + # + # 1. drop struct group arguments that aren't at MEMBERS, + # storing them as STRUCT_GROUP(MEMBERS) + # + # 2. remove STRUCT_GROUP() ancillary macro. + # + # The original logic used to remove STRUCT_GROUP() using an + # advanced regex: + # + # \bSTRUCT_GROUP(\(((?:(?>[^)(]+)|(?1))*)\))[^;]*; + # + # with two patterns that are incompatible with + # Python re module, as it has: + # + # - a recursive pattern: (?1) + # - an atomic grouping: (?>...) + # + # I tried a simpler version: but it didn't work either: + # \bSTRUCT_GROUP\(([^\)]+)\)[^;]*; + # + # As it doesn't properly match the end parenthesis on some cases. + # + # So, a better solution was crafted: there's now a NestedMatch + # class that ensures that delimiters after a search are properly + # matched. So, the implementation to drop STRUCT_GROUP() will be + # handled in separate. + + (KernRe(r'\bstruct_group\s*\(([^,]*,)', re.S), r'STRUCT_GROUP('), + (KernRe(r'\bstruct_group_attr\s*\(([^,]*,){2}', re.S), r'STRUCT_GROUP('), + (KernRe(r'\bstruct_group_tagged\s*\(([^,]*),([^,]*),', re.S), r'struct \1 \2; STRUCT_GROUP('), + (KernRe(r'\b__struct_group\s*\(([^,]*,){3}', re.S), r'STRUCT_GROUP('), + + # Replace macros + # + # TODO: use NestedMatch for FOO($1, $2, ...) matches + # + # it is better to also move those to the NestedMatch logic, + # to ensure that parenthesis will be properly matched. + + (KernRe(r'__ETHTOOL_DECLARE_LINK_MODE_MASK\s*\(([^\)]+)\)', re.S), r'DECLARE_BITMAP(\1, __ETHTOOL_LINK_MODE_MASK_NBITS)'), + (KernRe(r'DECLARE_PHY_INTERFACE_MASK\s*\(([^\)]+)\)', re.S), r'DECLARE_BITMAP(\1, PHY_INTERFACE_MODE_MAX)'), + (KernRe(r'DECLARE_BITMAP\s*\(' + args_pattern + r',\s*' + args_pattern + r'\)', re.S), r'unsigned long \1[BITS_TO_LONGS(\2)]'), + (KernRe(r'DECLARE_HASHTABLE\s*\(' + args_pattern + r',\s*' + args_pattern + r'\)', re.S), r'unsigned long \1[1 << ((\2) - 1)]'), + (KernRe(r'DECLARE_KFIFO\s*\(' + args_pattern + r',\s*' + args_pattern + r',\s*' + args_pattern + r'\)', re.S), r'\2 *\1'), + (KernRe(r'DECLARE_KFIFO_PTR\s*\(' + args_pattern + r',\s*' + args_pattern + r'\)', re.S), r'\2 *\1'), + (KernRe(r'(?:__)?DECLARE_FLEX_ARRAY\s*\(' + args_pattern + r',\s*' + args_pattern + r'\)', re.S), r'\1 \2[]'), + (KernRe(r'DEFINE_DMA_UNMAP_ADDR\s*\(' + args_pattern + r'\)', re.S), r'dma_addr_t \1'), + (KernRe(r'DEFINE_DMA_UNMAP_LEN\s*\(' + args_pattern + r'\)', re.S), r'__u32 \1'), + ] + + # Regexes here are guaranteed to have the end limiter matching + # the start delimiter. Yet, right now, only one replace group + # is allowed. + + sub_nested_prefixes = [ + (re.compile(r'\bSTRUCT_GROUP\('), r'\1'), + ] + + for search, sub in sub_prefixes: + members = search.sub(sub, members) + + nested = NestedMatch() + + for search, sub in sub_nested_prefixes: + members = nested.sub(search, sub, members) + + # Keeps the original declaration as-is + declaration = members + + # Split nested struct/union elements + # + # This loop was simpler at the original kernel-doc perl version, as + # while ($members =~ m/$struct_members/) { ... } + # reads 'members' string on each interaction. + # + # Python behavior is different: it parses 'members' only once, + # creating a list of tuples from the first interaction. + # + # On other words, this won't get nested structs. + # + # So, we need to have an extra loop on Python to override such + # re limitation. + + while True: + tuples = struct_members.findall(members) + if not tuples: + break + + for t in tuples: + newmember = "" + maintype = t[0] + s_ids = t[5] + content = t[3] + + oldmember = "".join(t) + + for s_id in s_ids.split(','): + s_id = s_id.strip() + + newmember += f"{maintype} {s_id}; " + s_id = KernRe(r'[:\[].*').sub('', s_id) + s_id = KernRe(r'^\s*\**(\S+)\s*').sub(r'\1', s_id) + + for arg in content.split(';'): + arg = arg.strip() + + if not arg: + continue + + r = KernRe(r'^([^\(]+\(\*?\s*)([\w\.]*)(\s*\).*)') + if r.match(arg): + # Pointer-to-function + dtype = r.group(1) + name = r.group(2) + extra = r.group(3) + + if not name: + continue + + if not s_id: + # Anonymous struct/union + newmember += f"{dtype}{name}{extra}; " + else: + newmember += f"{dtype}{s_id}.{name}{extra}; " + + else: + arg = arg.strip() + # Handle bitmaps + arg = KernRe(r':\s*\d+\s*').sub('', arg) + + # Handle arrays + arg = KernRe(r'\[.*\]').sub('', arg) + + # Handle multiple IDs + arg = KernRe(r'\s*,\s*').sub(',', arg) + + r = KernRe(r'(.*)\s+([\S+,]+)') + + if r.search(arg): + dtype = r.group(1) + names = r.group(2) + else: + newmember += f"{arg}; " + continue + + for name in names.split(','): + name = KernRe(r'^\s*\**(\S+)\s*').sub(r'\1', name).strip() + + if not name: + continue + + if not s_id: + # Anonymous struct/union + newmember += f"{dtype} {name}; " + else: + newmember += f"{dtype} {s_id}.{name}; " + + members = members.replace(oldmember, newmember) + + # Ignore other nested elements, like enums + members = re.sub(r'(\{[^\{\}]*\})', '', members) + + self.create_parameter_list(ln, decl_type, members, ';', + declaration_name) + self.check_sections(ln, declaration_name, decl_type, + self.entry.sectcheck, self.entry.struct_actual) + + # Adjust declaration for better display + declaration = KernRe(r'([\{;])').sub(r'\1\n', declaration) + declaration = KernRe(r'\}\s+;').sub('};', declaration) + + # Better handle inlined enums + while True: + r = KernRe(r'(enum\s+\{[^\}]+),([^\n])') + if not r.search(declaration): + break + + declaration = r.sub(r'\1,\n\2', declaration) + + def_args = declaration.split('\n') + level = 1 + declaration = "" + for clause in def_args: + + clause = clause.strip() + clause = KernRe(r'\s+').sub(' ', clause, count=1) + + if not clause: + continue + + if '}' in clause and level > 1: + level -= 1 + + if not KernRe(r'^\s*#').match(clause): + declaration += "\t" * level + + declaration += "\t" + clause + "\n" + if "{" in clause and "}" not in clause: + level += 1 + + self.output_declaration(decl_type, declaration_name, + struct=declaration_name, + definition=declaration, + parameterlist=self.entry.parameterlist, + parameterdescs=self.entry.parameterdescs, + parametertypes=self.entry.parametertypes, + parameterdesc_start_lines=self.entry.parameterdesc_start_lines, + sectionlist=self.entry.sectionlist, + sections=self.entry.sections, + section_start_lines=self.entry.section_start_lines, + purpose=self.entry.declaration_purpose) + + def dump_enum(self, ln, proto): + """ + Stores an enum inside self.entries array. + """ + + # Ignore members marked private + proto = KernRe(r'\/\*\s*private:.*?\/\*\s*public:.*?\*\/', flags=re.S).sub('', proto) + proto = KernRe(r'\/\*\s*private:.*}', flags=re.S).sub('}', proto) + + # Strip comments + proto = KernRe(r'\/\*.*?\*\/', flags=re.S).sub('', proto) + + # Strip #define macros inside enums + proto = KernRe(r'#\s*((define|ifdef|if)\s+|endif)[^;]*;', flags=re.S).sub('', proto) + + members = None + declaration_name = None + + r = KernRe(r'typedef\s+enum\s*\{(.*)\}\s*(\w*)\s*;') + if r.search(proto): + declaration_name = r.group(2) + members = r.group(1).rstrip() + else: + r = KernRe(r'enum\s+(\w*)\s*\{(.*)\}') + if r.match(proto): + declaration_name = r.group(1) + members = r.group(2).rstrip() + + if not members: + self.emit_msg(ln, f"{proto}: error: Cannot parse enum!") + return + + if self.entry.identifier != declaration_name: + if self.entry.identifier == "": + self.emit_msg(ln, + f"{proto}: wrong kernel-doc identifier on prototype") + else: + self.emit_msg(ln, + f"expecting prototype for enum {self.entry.identifier}. Prototype was for enum {declaration_name} instead") + return + + if not declaration_name: + declaration_name = "(anonymous)" + + member_set = set() + + members = KernRe(r'\([^;]*?[\)]').sub('', members) + + for arg in members.split(','): + if not arg: + continue + arg = KernRe(r'^\s*(\w+).*').sub(r'\1', arg) + self.entry.parameterlist.append(arg) + if arg not in self.entry.parameterdescs: + self.entry.parameterdescs[arg] = self.undescribed + self.emit_msg(ln, + f"Enum value '{arg}' not described in enum '{declaration_name}'") + member_set.add(arg) + + for k in self.entry.parameterdescs: + if k not in member_set: + self.emit_msg(ln, + f"Excess enum value '%{k}' description in '{declaration_name}'") + + self.output_declaration('enum', declaration_name, + enum=declaration_name, + parameterlist=self.entry.parameterlist, + parameterdescs=self.entry.parameterdescs, + parameterdesc_start_lines=self.entry.parameterdesc_start_lines, + sectionlist=self.entry.sectionlist, + sections=self.entry.sections, + section_start_lines=self.entry.section_start_lines, + purpose=self.entry.declaration_purpose) + + def dump_declaration(self, ln, prototype): + """ + Stores a data declaration inside self.entries array. + """ + + if self.entry.decl_type == "enum": + self.dump_enum(ln, prototype) + return + + if self.entry.decl_type == "typedef": + self.dump_typedef(ln, prototype) + return + + if self.entry.decl_type in ["union", "struct"]: + self.dump_struct(ln, prototype) + return + + self.output_declaration(self.entry.decl_type, prototype, + entry=self.entry) + + def dump_function(self, ln, prototype): + """ + Stores a function of function macro inside self.entries array. + """ + + func_macro = False + return_type = '' + decl_type = 'function' + + # Prefixes that would be removed + sub_prefixes = [ + (r"^static +", "", 0), + (r"^extern +", "", 0), + (r"^asmlinkage +", "", 0), + (r"^inline +", "", 0), + (r"^__inline__ +", "", 0), + (r"^__inline +", "", 0), + (r"^__always_inline +", "", 0), + (r"^noinline +", "", 0), + (r"^__FORTIFY_INLINE +", "", 0), + (r"__init +", "", 0), + (r"__init_or_module +", "", 0), + (r"__deprecated +", "", 0), + (r"__flatten +", "", 0), + (r"__meminit +", "", 0), + (r"__must_check +", "", 0), + (r"__weak +", "", 0), + (r"__sched +", "", 0), + (r"_noprof", "", 0), + (r"__printf\s*\(\s*\d*\s*,\s*\d*\s*\) +", "", 0), + (r"__(?:re)?alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) +", "", 0), + (r"__diagnose_as\s*\(\s*\S+\s*(?:,\s*\d+\s*)*\) +", "", 0), + (r"DECL_BUCKET_PARAMS\s*\(\s*(\S+)\s*,\s*(\S+)\s*\)", r"\1, \2", 0), + (r"__attribute_const__ +", "", 0), + + # It seems that Python support for re.X is broken: + # At least for me (Python 3.13), this didn't work +# (r""" +# __attribute__\s*\(\( +# (?: +# [\w\s]+ # attribute name +# (?:\([^)]*\))? # attribute arguments +# \s*,? # optional comma at the end +# )+ +# \)\)\s+ +# """, "", re.X), + + # So, remove whitespaces and comments from it + (r"__attribute__\s*\(\((?:[\w\s]+(?:\([^)]*\))?\s*,?)+\)\)\s+", "", 0), + ] + + for search, sub, flags in sub_prefixes: + prototype = KernRe(search, flags).sub(sub, prototype) + + # Macros are a special case, as they change the prototype format + new_proto = KernRe(r"^#\s*define\s+").sub("", prototype) + if new_proto != prototype: + is_define_proto = True + prototype = new_proto + else: + is_define_proto = False + + # Yes, this truly is vile. We are looking for: + # 1. Return type (may be nothing if we're looking at a macro) + # 2. Function name + # 3. Function parameters. + # + # All the while we have to watch out for function pointer parameters + # (which IIRC is what the two sections are for), C types (these + # regexps don't even start to express all the possibilities), and + # so on. + # + # If you mess with these regexps, it's a good idea to check that + # the following functions' documentation still comes out right: + # - parport_register_device (function pointer parameters) + # - atomic_set (macro) + # - pci_match_device, __copy_to_user (long return type) + + name = r'[a-zA-Z0-9_~:]+' + prototype_end1 = r'[^\(]*' + prototype_end2 = r'[^\{]*' + prototype_end = fr'\(({prototype_end1}|{prototype_end2})\)' + + # Besides compiling, Perl qr{[\w\s]+} works as a non-capturing group. + # So, this needs to be mapped in Python with (?:...)? or (?:...)+ + + type1 = r'(?:[\w\s]+)?' + type2 = r'(?:[\w\s]+\*+)+' + + found = False + + if is_define_proto: + r = KernRe(r'^()(' + name + r')\s+') + + if r.search(prototype): + return_type = '' + declaration_name = r.group(2) + func_macro = True + + found = True + + if not found: + patterns = [ + rf'^()({name})\s*{prototype_end}', + rf'^({type1})\s+({name})\s*{prototype_end}', + rf'^({type2})\s*({name})\s*{prototype_end}', + ] + + for p in patterns: + r = KernRe(p) + + if r.match(prototype): + + return_type = r.group(1) + declaration_name = r.group(2) + args = r.group(3) + + self.create_parameter_list(ln, decl_type, args, ',', + declaration_name) + + found = True + break + if not found: + self.emit_msg(ln, + f"cannot understand function prototype: '{prototype}'") + return + + if self.entry.identifier != declaration_name: + self.emit_msg(ln, + f"expecting prototype for {self.entry.identifier}(). Prototype was for {declaration_name}() instead") + return + + prms = " ".join(self.entry.parameterlist) + self.check_sections(ln, declaration_name, "function", + self.entry.sectcheck, prms) + + self.check_return_section(ln, declaration_name, return_type) + + if 'typedef' in return_type: + self.output_declaration(decl_type, declaration_name, + function=declaration_name, + typedef=True, + functiontype=return_type, + parameterlist=self.entry.parameterlist, + parameterdescs=self.entry.parameterdescs, + parametertypes=self.entry.parametertypes, + parameterdesc_start_lines=self.entry.parameterdesc_start_lines, + sectionlist=self.entry.sectionlist, + sections=self.entry.sections, + section_start_lines=self.entry.section_start_lines, + purpose=self.entry.declaration_purpose, + func_macro=func_macro) + else: + self.output_declaration(decl_type, declaration_name, + function=declaration_name, + typedef=False, + functiontype=return_type, + parameterlist=self.entry.parameterlist, + parameterdescs=self.entry.parameterdescs, + parametertypes=self.entry.parametertypes, + parameterdesc_start_lines=self.entry.parameterdesc_start_lines, + sectionlist=self.entry.sectionlist, + sections=self.entry.sections, + section_start_lines=self.entry.section_start_lines, + purpose=self.entry.declaration_purpose, + func_macro=func_macro) + + def dump_typedef(self, ln, proto): + """ + Stores a typedef inside self.entries array. + """ + + typedef_type = r'((?:\s+[\w\*]+\b){0,7}\s+(?:\w+\b|\*+))\s*' + typedef_ident = r'\*?\s*(\w\S+)\s*' + typedef_args = r'\s*\((.*)\);' + + typedef1 = KernRe(r'typedef' + typedef_type + r'\(' + typedef_ident + r'\)' + typedef_args) + typedef2 = KernRe(r'typedef' + typedef_type + typedef_ident + typedef_args) + + # Strip comments + proto = KernRe(r'/\*.*?\*/', flags=re.S).sub('', proto) + + # Parse function typedef prototypes + for r in [typedef1, typedef2]: + if not r.match(proto): + continue + + return_type = r.group(1).strip() + declaration_name = r.group(2) + args = r.group(3) + + if self.entry.identifier != declaration_name: + self.emit_msg(ln, + f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n") + return + + decl_type = 'function' + self.create_parameter_list(ln, decl_type, args, ',', declaration_name) + + self.output_declaration(decl_type, declaration_name, + function=declaration_name, + typedef=True, + functiontype=return_type, + parameterlist=self.entry.parameterlist, + parameterdescs=self.entry.parameterdescs, + parametertypes=self.entry.parametertypes, + parameterdesc_start_lines=self.entry.parameterdesc_start_lines, + sectionlist=self.entry.sectionlist, + sections=self.entry.sections, + section_start_lines=self.entry.section_start_lines, + purpose=self.entry.declaration_purpose) + return + + # Handle nested parentheses or brackets + r = KernRe(r'(\(*.\)\s*|\[*.\]\s*);$') + while r.search(proto): + proto = r.sub('', proto) + + # Parse simple typedefs + r = KernRe(r'typedef.*\s+(\w+)\s*;') + if r.match(proto): + declaration_name = r.group(1) + + if self.entry.identifier != declaration_name: + self.emit_msg(ln, + f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n") + return + + self.output_declaration('typedef', declaration_name, + typedef=declaration_name, + sectionlist=self.entry.sectionlist, + sections=self.entry.sections, + section_start_lines=self.entry.section_start_lines, + purpose=self.entry.declaration_purpose) + return + + self.emit_msg(ln, "error: Cannot parse typedef!") + + @staticmethod + def process_export(function_set, line): + """ + process EXPORT_SYMBOL* tags + + This method doesn't use any variable from the class, so declare it + with a staticmethod decorator. + """ + + # Note: it accepts only one EXPORT_SYMBOL* per line, as having + # multiple export lines would violate Kernel coding style. + + if export_symbol.search(line): + symbol = export_symbol.group(2) + function_set.add(symbol) + return + + if export_symbol_ns.search(line): + symbol = export_symbol_ns.group(2) + function_set.add(symbol) + + def process_normal(self, ln, line): + """ + STATE_NORMAL: looking for the /** to begin everything. + """ + + if not doc_start.match(line): + return + + # start a new entry + self.reset_state(ln) + self.entry.in_doc_sect = False + + # next line is always the function name + self.state = state.NAME + + def process_name(self, ln, line): + """ + STATE_NAME: Looking for the "name - description" line + """ + + if doc_block.search(line): + self.entry.new_start_line = ln + + if not doc_block.group(1): + self.entry.section = self.section_intro + else: + self.entry.section = doc_block.group(1) + + self.entry.identifier = self.entry.section + self.state = state.DOCBLOCK + return + + if doc_decl.search(line): + self.entry.identifier = doc_decl.group(1) + self.entry.is_kernel_comment = False + + decl_start = str(doc_com) # comment block asterisk + fn_type = r"(?:\w+\s*\*\s*)?" # type (for non-functions) + parenthesis = r"(?:\(\w*\))?" # optional parenthesis on function + decl_end = r"(?:[-:].*)" # end of the name part + + # test for pointer declaration type, foo * bar() - desc + r = KernRe(fr"^{decl_start}([\w\s]+?){parenthesis}?\s*{decl_end}?$") + if r.search(line): + self.entry.identifier = r.group(1) + + # Test for data declaration + r = KernRe(r"^\s*\*?\s*(struct|union|enum|typedef)\b\s*(\w*)") + if r.search(line): + self.entry.decl_type = r.group(1) + self.entry.identifier = r.group(2) + self.entry.is_kernel_comment = True + else: + # Look for foo() or static void foo() - description; + # or misspelt identifier + + r1 = KernRe(fr"^{decl_start}{fn_type}(\w+)\s*{parenthesis}\s*{decl_end}?$") + r2 = KernRe(fr"^{decl_start}{fn_type}(\w+[^-:]*){parenthesis}\s*{decl_end}$") + + for r in [r1, r2]: + if r.search(line): + self.entry.identifier = r.group(1) + self.entry.decl_type = "function" + + r = KernRe(r"define\s+") + self.entry.identifier = r.sub("", self.entry.identifier) + self.entry.is_kernel_comment = True + break + + self.entry.identifier = self.entry.identifier.strip(" ") + + self.state = state.BODY + + # if there's no @param blocks need to set up default section here + self.entry.section = SECTION_DEFAULT + self.entry.new_start_line = ln + 1 + + r = KernRe("[-:](.*)") + if r.search(line): + # strip leading/trailing/multiple spaces + self.entry.descr = r.group(1).strip(" ") + + r = KernRe(r"\s+") + self.entry.descr = r.sub(" ", self.entry.descr) + self.entry.declaration_purpose = self.entry.descr + self.state = state.BODY_MAYBE + else: + self.entry.declaration_purpose = "" + + if not self.entry.is_kernel_comment: + self.emit_msg(ln, + f"This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n{line}") + self.state = state.NORMAL + + if not self.entry.declaration_purpose and self.config.wshort_desc: + self.emit_msg(ln, + f"missing initial short description on line:\n{line}") + + if not self.entry.identifier and self.entry.decl_type != "enum": + self.emit_msg(ln, + f"wrong kernel-doc identifier on line:\n{line}") + self.state = state.NORMAL + + if self.config.verbose: + self.emit_msg(ln, + f"Scanning doc for {self.entry.decl_type} {self.entry.identifier}", + warning=False) + + return + + # Failed to find an identifier. Emit a warning + self.emit_msg(ln, f"Cannot find identifier on line:\n{line}") + + def process_body(self, ln, line): + """ + STATE_BODY and STATE_BODY_MAYBE: the bulk of a kerneldoc comment. + """ + + if self.state == state.BODY_WITH_BLANK_LINE: + r = KernRe(r"\s*\*\s?\S") + if r.match(line): + self.dump_section() + self.entry.section = SECTION_DEFAULT + self.entry.new_start_line = ln + self.entry.contents = "" + + if doc_sect.search(line): + self.entry.in_doc_sect = True + newsection = doc_sect.group(1) + + if newsection.lower() in ["description", "context"]: + newsection = newsection.title() + + # Special case: @return is a section, not a param description + if newsection.lower() in ["@return", "@returns", + "return", "returns"]: + newsection = "Return" + + # Perl kernel-doc has a check here for contents before sections. + # the logic there is always false, as in_doc_sect variable is + # always true. So, just don't implement Wcontents_before_sections + + # .title() + newcontents = doc_sect.group(2) + if not newcontents: + newcontents = "" + + if self.entry.contents.strip("\n"): + self.dump_section() + + self.entry.new_start_line = ln + self.entry.section = newsection + self.entry.leading_space = None + + self.entry.contents = newcontents.lstrip() + if self.entry.contents: + self.entry.contents += "\n" + + self.state = state.BODY + return + + if doc_end.search(line): + self.dump_section() + + # Look for doc_com + <text> + doc_end: + r = KernRe(r'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') + if r.match(line): + self.emit_msg(ln, f"suspicious ending line: {line}") + + self.entry.prototype = "" + self.entry.new_start_line = ln + 1 + + self.state = state.PROTO + return + + if doc_content.search(line): + cont = doc_content.group(1) + + if cont == "": + if self.entry.section == self.section_context: + self.dump_section() + + self.entry.new_start_line = ln + self.state = state.BODY + else: + if self.entry.section != SECTION_DEFAULT: + self.state = state.BODY_WITH_BLANK_LINE + else: + self.state = state.BODY + + self.entry.contents += "\n" + + elif self.state == state.BODY_MAYBE: + + # Continued declaration purpose + self.entry.declaration_purpose = self.entry.declaration_purpose.rstrip() + self.entry.declaration_purpose += " " + cont + + r = KernRe(r"\s+") + self.entry.declaration_purpose = r.sub(' ', + self.entry.declaration_purpose) + + else: + if self.entry.section.startswith('@') or \ + self.entry.section == self.section_context: + if self.entry.leading_space is None: + r = KernRe(r'^(\s+)') + if r.match(cont): + self.entry.leading_space = len(r.group(1)) + else: + self.entry.leading_space = 0 + + # Double-check if leading space are realy spaces + pos = 0 + for i in range(0, self.entry.leading_space): + if cont[i] != " ": + break + pos += 1 + + cont = cont[pos:] + + # NEW LOGIC: + # In case it is different, update it + if self.entry.leading_space != pos: + self.entry.leading_space = pos + + self.entry.contents += cont + "\n" + return + + # Unknown line, ignore + self.emit_msg(ln, f"bad line: {line}") + + def process_inline(self, ln, line): + """STATE_INLINE: docbook comments within a prototype.""" + + if self.inline_doc_state == state.INLINE_NAME and \ + doc_inline_sect.search(line): + self.entry.section = doc_inline_sect.group(1) + self.entry.new_start_line = ln + + self.entry.contents = doc_inline_sect.group(2).lstrip() + if self.entry.contents != "": + self.entry.contents += "\n" + + self.inline_doc_state = state.INLINE_TEXT + # Documentation block end */ + return + + if doc_inline_end.search(line): + if self.entry.contents not in ["", "\n"]: + self.dump_section() + + self.state = state.PROTO + self.inline_doc_state = state.INLINE_NA + return + + if doc_content.search(line): + if self.inline_doc_state == state.INLINE_TEXT: + self.entry.contents += doc_content.group(1) + "\n" + if not self.entry.contents.strip(" ").rstrip("\n"): + self.entry.contents = "" + + elif self.inline_doc_state == state.INLINE_NAME: + self.emit_msg(ln, + f"Incorrect use of kernel-doc format: {line}") + + self.inline_doc_state = state.INLINE_ERROR + + def syscall_munge(self, ln, proto): # pylint: disable=W0613 + """ + Handle syscall definitions + """ + + is_void = False + + # Strip newlines/CR's + proto = re.sub(r'[\r\n]+', ' ', proto) + + # Check if it's a SYSCALL_DEFINE0 + if 'SYSCALL_DEFINE0' in proto: + is_void = True + + # Replace SYSCALL_DEFINE with correct return type & function name + proto = KernRe(r'SYSCALL_DEFINE.*\(').sub('long sys_', proto) + + r = KernRe(r'long\s+(sys_.*?),') + if r.search(proto): + proto = KernRe(',').sub('(', proto, count=1) + elif is_void: + proto = KernRe(r'\)').sub('(void)', proto, count=1) + + # Now delete all of the odd-numbered commas in the proto + # so that argument types & names don't have a comma between them + count = 0 + length = len(proto) + + if is_void: + length = 0 # skip the loop if is_void + + for ix in range(length): + if proto[ix] == ',': + count += 1 + if count % 2 == 1: + proto = proto[:ix] + ' ' + proto[ix + 1:] + + return proto + + def tracepoint_munge(self, ln, proto): + """ + Handle tracepoint definitions + """ + + tracepointname = None + tracepointargs = None + + # Match tracepoint name based on different patterns + r = KernRe(r'TRACE_EVENT\((.*?),') + if r.search(proto): + tracepointname = r.group(1) + + r = KernRe(r'DEFINE_SINGLE_EVENT\((.*?),') + if r.search(proto): + tracepointname = r.group(1) + + r = KernRe(r'DEFINE_EVENT\((.*?),(.*?),') + if r.search(proto): + tracepointname = r.group(2) + + if tracepointname: + tracepointname = tracepointname.lstrip() + + r = KernRe(r'TP_PROTO\((.*?)\)') + if r.search(proto): + tracepointargs = r.group(1) + + if not tracepointname or not tracepointargs: + self.emit_msg(ln, + f"Unrecognized tracepoint format:\n{proto}\n") + else: + proto = f"static inline void trace_{tracepointname}({tracepointargs})" + self.entry.identifier = f"trace_{self.entry.identifier}" + + return proto + + def process_proto_function(self, ln, line): + """Ancillary routine to process a function prototype""" + + # strip C99-style comments to end of line + r = KernRe(r"\/\/.*$", re.S) + line = r.sub('', line) + + if KernRe(r'\s*#\s*define').match(line): + self.entry.prototype = line + elif line.startswith('#'): + # Strip other macros like #ifdef/#ifndef/#endif/... + pass + else: + r = KernRe(r'([^\{]*)') + if r.match(line): + self.entry.prototype += r.group(1) + " " + + if '{' in line or ';' in line or KernRe(r'\s*#\s*define').match(line): + # strip comments + r = KernRe(r'/\*.*?\*/') + self.entry.prototype = r.sub('', self.entry.prototype) + + # strip newlines/cr's + r = KernRe(r'[\r\n]+') + self.entry.prototype = r.sub(' ', self.entry.prototype) + + # strip leading spaces + r = KernRe(r'^\s+') + self.entry.prototype = r.sub('', self.entry.prototype) + + # Handle self.entry.prototypes for function pointers like: + # int (*pcs_config)(struct foo) + + r = KernRe(r'^(\S+\s+)\(\s*\*(\S+)\)') + self.entry.prototype = r.sub(r'\1\2', self.entry.prototype) + + if 'SYSCALL_DEFINE' in self.entry.prototype: + self.entry.prototype = self.syscall_munge(ln, + self.entry.prototype) + + r = KernRe(r'TRACE_EVENT|DEFINE_EVENT|DEFINE_SINGLE_EVENT') + if r.search(self.entry.prototype): + self.entry.prototype = self.tracepoint_munge(ln, + self.entry.prototype) + + self.dump_function(ln, self.entry.prototype) + self.reset_state(ln) + + def process_proto_type(self, ln, line): + """Ancillary routine to process a type""" + + # Strip newlines/cr's. + line = KernRe(r'[\r\n]+', re.S).sub(' ', line) + + # Strip leading spaces + line = KernRe(r'^\s+', re.S).sub('', line) + + # Strip trailing spaces + line = KernRe(r'\s+$', re.S).sub('', line) + + # Strip C99-style comments to the end of the line + line = KernRe(r"\/\/.*$", re.S).sub('', line) + + # To distinguish preprocessor directive from regular declaration later. + if line.startswith('#'): + line += ";" + + r = KernRe(r'([^\{\};]*)([\{\};])(.*)') + while True: + if r.search(line): + if self.entry.prototype: + self.entry.prototype += " " + self.entry.prototype += r.group(1) + r.group(2) + + self.entry.brcount += r.group(2).count('{') + self.entry.brcount -= r.group(2).count('}') + + self.entry.brcount = max(self.entry.brcount, 0) + + if r.group(2) == ';' and self.entry.brcount == 0: + self.dump_declaration(ln, self.entry.prototype) + self.reset_state(ln) + break + + line = r.group(3) + else: + self.entry.prototype += line + break + + def process_proto(self, ln, line): + """STATE_PROTO: reading a function/whatever prototype.""" + + if doc_inline_oneline.search(line): + self.entry.section = doc_inline_oneline.group(1) + self.entry.contents = doc_inline_oneline.group(2) + + if self.entry.contents != "": + self.entry.contents += "\n" + self.dump_section(start_new=False) + + elif doc_inline_start.search(line): + self.state = state.INLINE + self.inline_doc_state = state.INLINE_NAME + + elif self.entry.decl_type == 'function': + self.process_proto_function(ln, line) + + else: + self.process_proto_type(ln, line) + + def process_docblock(self, ln, line): + """STATE_DOCBLOCK: within a DOC: block.""" + + if doc_end.search(line): + self.dump_section() + self.output_declaration("doc", self.entry.identifier, + sectionlist=self.entry.sectionlist, + sections=self.entry.sections, + section_start_lines=self.entry.section_start_lines) + self.reset_state(ln) + + elif doc_content.search(line): + self.entry.contents += doc_content.group(1) + "\n" + + def parse_export(self): + """ + Parses EXPORT_SYMBOL* macros from a single Kernel source file. + """ + + export_table = set() + + try: + with open(self.fname, "r", encoding="utf8", + errors="backslashreplace") as fp: + + for line in fp: + self.process_export(export_table, line) + + except IOError: + return None + + return export_table + + def parse_kdoc(self): + """ + Open and process each line of a C source file. + The parsing is controlled via a state machine, and the line is passed + to a different process function depending on the state. The process + function may update the state as needed. + + Besides parsing kernel-doc tags, it also parses export symbols. + """ + + cont = False + prev = "" + prev_ln = None + export_table = set() + + try: + with open(self.fname, "r", encoding="utf8", + errors="backslashreplace") as fp: + for ln, line in enumerate(fp): + + line = line.expandtabs().strip("\n") + + # Group continuation lines on prototypes + if self.state == state.PROTO: + if line.endswith("\\"): + prev += line.rstrip("\\") + cont = True + + if not prev_ln: + prev_ln = ln + + continue + + if cont: + ln = prev_ln + line = prev + line + prev = "" + cont = False + prev_ln = None + + self.config.log.debug("%d %s%s: %s", + ln, state.name[self.state], + state.inline_name[self.inline_doc_state], + line) + + # This is an optimization over the original script. + # There, when export_file was used for the same file, + # it was read twice. Here, we use the already-existing + # loop to parse exported symbols as well. + # + # TODO: It should be noticed that not all states are + # needed here. On a future cleanup, process export only + # at the states that aren't handling comment markups. + self.process_export(export_table, line) + + # Hand this line to the appropriate state handler + if self.state == state.NORMAL: + self.process_normal(ln, line) + elif self.state == state.NAME: + self.process_name(ln, line) + elif self.state in [state.BODY, state.BODY_MAYBE, + state.BODY_WITH_BLANK_LINE]: + self.process_body(ln, line) + elif self.state == state.INLINE: # scanning for inline parameters + self.process_inline(ln, line) + elif self.state == state.PROTO: + self.process_proto(ln, line) + elif self.state == state.DOCBLOCK: + self.process_docblock(ln, line) + except OSError: + self.config.log.error(f"Error: Cannot open file {self.fname}") + + return export_table, self.entries diff --git a/scripts/lib/kdoc/kdoc_re.py b/scripts/lib/kdoc/kdoc_re.py new file mode 100644 index 000000000000..e81695b273bf --- /dev/null +++ b/scripts/lib/kdoc/kdoc_re.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. + +""" +Regular expression ancillary classes. + +Those help caching regular expressions and do matching for kernel-doc. +""" + +import re + +# Local cache for regular expressions +re_cache = {} + + +class KernRe: + """ + Helper class to simplify regex declaration and usage, + + It calls re.compile for a given pattern. It also allows adding + regular expressions and define sub at class init time. + + Regular expressions can be cached via an argument, helping to speedup + searches. + """ + + def _add_regex(self, string, flags): + """ + Adds a new regex or re-use it from the cache. + """ + + if string in re_cache: + self.regex = re_cache[string] + else: + self.regex = re.compile(string, flags=flags) + + if self.cache: + re_cache[string] = self.regex + + def __init__(self, string, cache=True, flags=0): + """ + Compile a regular expression and initialize internal vars. + """ + + self.cache = cache + self.last_match = None + + self._add_regex(string, flags) + + def __str__(self): + """ + Return the regular expression pattern. + """ + return self.regex.pattern + + def __add__(self, other): + """ + Allows adding two regular expressions into one. + """ + + return KernRe(str(self) + str(other), cache=self.cache or other.cache, + flags=self.regex.flags | other.regex.flags) + + def match(self, string): + """ + Handles a re.match storing its results + """ + + self.last_match = self.regex.match(string) + return self.last_match + + def search(self, string): + """ + Handles a re.search storing its results + """ + + self.last_match = self.regex.search(string) + return self.last_match + + def findall(self, string): + """ + Alias to re.findall + """ + + return self.regex.findall(string) + + def split(self, string): + """ + Alias to re.split + """ + + return self.regex.split(string) + + def sub(self, sub, string, count=0): + """ + Alias to re.sub + """ + + return self.regex.sub(sub, string, count=count) + + def group(self, num): + """ + Returns the group results of the last match + """ + + return self.last_match.group(num) + + +class NestedMatch: + """ + Finding nested delimiters is hard with regular expressions. It is + even harder on Python with its normal re module, as there are several + advanced regular expressions that are missing. + + This is the case of this pattern: + + '\\bSTRUCT_GROUP(\\(((?:(?>[^)(]+)|(?1))*)\\))[^;]*;' + + which is used to properly match open/close parenthesis of the + string search STRUCT_GROUP(), + + Add a class that counts pairs of delimiters, using it to match and + replace nested expressions. + + The original approach was suggested by: + https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex + + Although I re-implemented it to make it more generic and match 3 types + of delimiters. The logic checks if delimiters are paired. If not, it + will ignore the search string. + """ + + # TODO: make NestedMatch handle multiple match groups + # + # Right now, regular expressions to match it are defined only up to + # the start delimiter, e.g.: + # + # \bSTRUCT_GROUP\( + # + # is similar to: STRUCT_GROUP\((.*)\) + # except that the content inside the match group is delimiter's aligned. + # + # The content inside parenthesis are converted into a single replace + # group (e.g. r`\1'). + # + # It would be nice to change such definition to support multiple + # match groups, allowing a regex equivalent to. + # + # FOO\((.*), (.*), (.*)\) + # + # it is probably easier to define it not as a regular expression, but + # with some lexical definition like: + # + # FOO(arg1, arg2, arg3) + + DELIMITER_PAIRS = { + '{': '}', + '(': ')', + '[': ']', + } + + RE_DELIM = re.compile(r'[\{\}\[\]\(\)]') + + def _search(self, regex, line): + """ + Finds paired blocks for a regex that ends with a delimiter. + + The suggestion of using finditer to match pairs came from: + https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex + but I ended using a different implementation to align all three types + of delimiters and seek for an initial regular expression. + + The algorithm seeks for open/close paired delimiters and place them + into a stack, yielding a start/stop position of each match when the + stack is zeroed. + + The algorithm shoud work fine for properly paired lines, but will + silently ignore end delimiters that preceeds an start delimiter. + This should be OK for kernel-doc parser, as unaligned delimiters + would cause compilation errors. So, we don't need to rise exceptions + to cover such issues. + """ + + stack = [] + + for match_re in regex.finditer(line): + start = match_re.start() + offset = match_re.end() + + d = line[offset - 1] + if d not in self.DELIMITER_PAIRS: + continue + + end = self.DELIMITER_PAIRS[d] + stack.append(end) + + for match in self.RE_DELIM.finditer(line[offset:]): + pos = match.start() + offset + + d = line[pos] + + if d in self.DELIMITER_PAIRS: + end = self.DELIMITER_PAIRS[d] + + stack.append(end) + continue + + # Does the end delimiter match what it is expected? + if stack and d == stack[-1]: + stack.pop() + + if not stack: + yield start, offset, pos + 1 + break + + def search(self, regex, line): + """ + This is similar to re.search: + + It matches a regex that it is followed by a delimiter, + returning occurrences only if all delimiters are paired. + """ + + for t in self._search(regex, line): + + yield line[t[0]:t[2]] + + def sub(self, regex, sub, line, count=0): + """ + This is similar to re.sub: + + It matches a regex that it is followed by a delimiter, + replacing occurrences only if all delimiters are paired. + + if r'\1' is used, it works just like re: it places there the + matched paired data with the delimiter stripped. + + If count is different than zero, it will replace at most count + items. + """ + out = "" + + cur_pos = 0 + n = 0 + + for start, end, pos in self._search(regex, line): + out += line[cur_pos:start] + + # Value, ignoring start/end delimiters + value = line[end:pos - 1] + + # replaces \1 at the sub string, if \1 is used there + new_sub = sub + new_sub = new_sub.replace(r'\1', value) + + out += new_sub + + # Drop end ';' if any + if line[pos] == ';': + pos += 1 + + cur_pos = pos + n += 1 + + if count and count >= n: + break + + # Append the remaining string + l = len(line) + out += line[cur_pos:l] + + return out diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 7862a8101747..51367c2bfc21 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -31,6 +31,7 @@ set -e LD="$1" KBUILD_LDFLAGS="$2" LDFLAGS_vmlinux="$3" +VMLINUX="$4" is_enabled() { grep -q "^$1=y" include/config/auto.conf @@ -45,7 +46,6 @@ info() # Link of vmlinux # ${1} - output file -# ${2}, ${3}, ... - optional extra .o files vmlinux_link() { local output=${1} @@ -69,6 +69,10 @@ vmlinux_link() libs="${KBUILD_VMLINUX_LIBS}" fi + if is_enabled CONFIG_GENERIC_BUILTIN_DTB; then + objs="${objs} .builtin-dtbs.o" + fi + if is_enabled CONFIG_MODULES; then objs="${objs} .vmlinux.export.o" fi @@ -90,50 +94,36 @@ vmlinux_link() ldflags="${ldflags} ${wl}--script=${objtree}/${KBUILD_LDS}" # The kallsyms linking does not need debug symbols included. - if [ "$output" != "${output#.tmp_vmlinux.kallsyms}" ] ; then + if [ -n "${strip_debug}" ] ; then ldflags="${ldflags} ${wl}--strip-debug" fi - if is_enabled CONFIG_VMLINUX_MAP; then - ldflags="${ldflags} ${wl}-Map=${output}.map" + if [ -n "${generate_map}" ]; then + ldflags="${ldflags} ${wl}-Map=vmlinux.map" fi ${ld} ${ldflags} -o ${output} \ ${wl}--whole-archive ${objs} ${wl}--no-whole-archive \ ${wl}--start-group ${libs} ${wl}--end-group \ - $@ ${ldlibs} + ${kallsymso} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs} } # generate .BTF typeinfo from DWARF debuginfo # ${1} - vmlinux image -# ${2} - file to dump raw BTF data into gen_btf() { - local pahole_ver + local btf_data=${1}.btf.o - if ! [ -x "$(command -v ${PAHOLE})" ]; then - echo >&2 "BTF: ${1}: pahole (${PAHOLE}) is not available" - return 1 - fi - - pahole_ver=$(${PAHOLE} --version | sed -E 's/v([0-9]+)\.([0-9]+)/\1\2/') - if [ "${pahole_ver}" -lt "116" ]; then - echo >&2 "BTF: ${1}: pahole version $(${PAHOLE} --version) is too old, need at least v1.16" - return 1 - fi - - vmlinux_link ${1} - - info "BTF" ${2} + info BTF "${btf_data}" LLVM_OBJCOPY="${OBJCOPY}" ${PAHOLE} -J ${PAHOLE_FLAGS} ${1} - # Create ${2} which contains just .BTF section but no symbols. Add + # Create ${btf_data} which contains just .BTF section but no symbols. Add # SHF_ALLOC because .BTF will be part of the vmlinux image. --strip-all # deletes all symbols including __start_BTF and __stop_BTF, which will # be redefined in the linker script. Add 2>/dev/null to suppress GNU # objcopy warnings: "empty loadable segment detected at ..." ${OBJCOPY} --only-section=.BTF --set-section-flags .BTF=alloc,readonly \ - --strip-all ${1} ${2} 2>/dev/null + --strip-all ${1} "${btf_data}" 2>/dev/null # Change e_type to ET_REL so that it can be used to link final vmlinux. # GNU ld 2.35+ and lld do not allow an ET_EXEC input. if is_enabled CONFIG_CPU_BIG_ENDIAN; then @@ -141,10 +131,12 @@ gen_btf() else et_rel='\1\0' fi - printf "${et_rel}" | dd of=${2} conv=notrunc bs=1 seek=16 status=none + printf "${et_rel}" | dd of="${btf_data}" conv=notrunc bs=1 seek=16 status=none + + btf_vmlinux_bin_o=${btf_data} } -# Create ${2} .S file with all symbols from the ${1} object file +# Create ${2}.o file with all symbols from the ${1} object file kallsyms() { local kallsymopt; @@ -153,39 +145,23 @@ kallsyms() kallsymopt="${kallsymopt} --all-symbols" fi - if is_enabled CONFIG_KALLSYMS_ABSOLUTE_PERCPU; then - kallsymopt="${kallsymopt} --absolute-percpu" - fi + info KSYMS "${2}.S" + scripts/kallsyms ${kallsymopt} "${1}" > "${2}.S" - if is_enabled CONFIG_KALLSYMS_BASE_RELATIVE; then - kallsymopt="${kallsymopt} --base-relative" - fi - - if is_enabled CONFIG_LTO_CLANG; then - kallsymopt="${kallsymopt} --lto-clang" - fi + info AS "${2}.o" + ${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \ + ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} -c -o "${2}.o" "${2}.S" - info KSYMS ${2} - scripts/kallsyms ${kallsymopt} ${1} > ${2} + kallsymso=${2}.o } -# Perform one step in kallsyms generation, including temporary linking of -# vmlinux. -kallsyms_step() +# Perform kallsyms for the given temporary vmlinux. +sysmap_and_kallsyms() { - kallsymso_prev=${kallsymso} - kallsyms_vmlinux=.tmp_vmlinux.kallsyms${1} - kallsymso=${kallsyms_vmlinux}.o - kallsyms_S=${kallsyms_vmlinux}.S + mksysmap "${1}" "${1}.syms" + kallsyms "${1}.syms" "${1}.kallsyms" - vmlinux_link ${kallsyms_vmlinux} "${kallsymso_prev}" ${btf_vmlinux_bin_o} - mksysmap ${kallsyms_vmlinux} ${kallsyms_vmlinux}.syms ${kallsymso_prev} - kallsyms ${kallsyms_vmlinux}.syms ${kallsyms_S} - - info AS ${kallsyms_S} - ${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \ - ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} \ - -c -o ${kallsymso} ${kallsyms_S} + kallsyms_sysmap=${1}.syms } # Create map file with all symbols from ${1} @@ -193,18 +169,19 @@ kallsyms_step() mksysmap() { info NM ${2} - ${CONFIG_SHELL} "${srctree}/scripts/mksysmap" ${1} ${2} ${3} + ${NM} -n "${1}" | sed -f "${srctree}/scripts/mksysmap" > "${2}" } sorttable() { - ${objtree}/scripts/sorttable ${1} + ${NM} -S ${1} > .tmp_vmlinux.nm-sort + ${objtree}/scripts/sorttable -s .tmp_vmlinux.nm-sort ${1} } -# Delete output files in case of error cleanup() { rm -f .btf.* + rm -f .tmp_vmlinux.nm-sort rm -f System.map rm -f vmlinux rm -f vmlinux.map @@ -224,32 +201,53 @@ fi ${MAKE} -f "${srctree}/scripts/Makefile.build" obj=init init/version-timestamp.o -btf_vmlinux_bin_o="" +arch_vmlinux_o= +if is_enabled CONFIG_ARCH_WANTS_PRE_LINK_VMLINUX; then + arch_vmlinux_o=arch/${SRCARCH}/tools/vmlinux.arch.o +fi + +btf_vmlinux_bin_o= +kallsymso= +strip_debug= +generate_map= + +if is_enabled CONFIG_KALLSYMS; then + true > .tmp_vmlinux0.syms + kallsyms .tmp_vmlinux0.syms .tmp_vmlinux0.kallsyms +fi + +if is_enabled CONFIG_KALLSYMS || is_enabled CONFIG_DEBUG_INFO_BTF; then + + # The kallsyms linking does not need debug symbols, but the BTF does. + if ! is_enabled CONFIG_DEBUG_INFO_BTF; then + strip_debug=1 + fi + + vmlinux_link .tmp_vmlinux1 +fi + if is_enabled CONFIG_DEBUG_INFO_BTF; then - btf_vmlinux_bin_o=.btf.vmlinux.bin.o - if ! gen_btf .tmp_vmlinux.btf $btf_vmlinux_bin_o ; then + if ! gen_btf .tmp_vmlinux1; then echo >&2 "Failed to generate BTF for vmlinux" echo >&2 "Try to disable CONFIG_DEBUG_INFO_BTF" exit 1 fi fi -kallsymso="" -kallsymso_prev="" -kallsyms_vmlinux="" if is_enabled CONFIG_KALLSYMS; then # kallsyms support # Generate section listing all symbols and add it into vmlinux - # It's a three step process: - # 1) Link .tmp_vmlinux.kallsyms1 so it has all symbols and sections, - # but __kallsyms is empty. - # Running kallsyms on that gives us .tmp_kallsyms1.o with + # It's a four step process: + # 0) Generate a dummy __kallsyms with empty symbol list. + # 1) Link .tmp_vmlinux1.kallsyms so it has all symbols and sections, + # with a dummy __kallsyms. + # Running kallsyms on that gives us .tmp_vmlinux1.kallsyms.o with # the right size - # 2) Link .tmp_vmlinux.kallsyms2 so it now has a __kallsyms section of + # 2) Link .tmp_vmlinux2.kallsyms so it now has a __kallsyms section of # the right size, but due to the added section, some # addresses have shifted. - # From here, we generate a correct .tmp_vmlinux.kallsyms2.o + # From here, we generate a correct .tmp_vmlinux2.kallsyms.o # 3) That link may have expanded the kernel image enough that # more linker branch stubs / trampolines had to be added, which # introduces new names, which further expands kallsyms. Do another @@ -262,31 +260,45 @@ if is_enabled CONFIG_KALLSYMS; then # a) Verify that the System.map from vmlinux matches the map from # ${kallsymso}. - kallsyms_step 1 - kallsyms_step 2 + # The kallsyms linking does not need debug symbols included. + strip_debug=1 + + sysmap_and_kallsyms .tmp_vmlinux1 + size1=$(${CONFIG_SHELL} "${srctree}/scripts/file-size.sh" ${kallsymso}) - # step 3 - size1=$(${CONFIG_SHELL} "${srctree}/scripts/file-size.sh" ${kallsymso_prev}) + vmlinux_link .tmp_vmlinux2 + sysmap_and_kallsyms .tmp_vmlinux2 size2=$(${CONFIG_SHELL} "${srctree}/scripts/file-size.sh" ${kallsymso}) if [ $size1 -ne $size2 ] || [ -n "${KALLSYMS_EXTRA_PASS}" ]; then - kallsyms_step 3 + vmlinux_link .tmp_vmlinux3 + sysmap_and_kallsyms .tmp_vmlinux3 fi fi -vmlinux_link vmlinux "${kallsymso}" ${btf_vmlinux_bin_o} +strip_debug= + +if is_enabled CONFIG_VMLINUX_MAP; then + generate_map=1 +fi + +vmlinux_link "${VMLINUX}" # fill in BTF IDs -if is_enabled CONFIG_DEBUG_INFO_BTF && is_enabled CONFIG_BPF; then - info BTFIDS vmlinux - ${RESOLVE_BTFIDS} vmlinux +if is_enabled CONFIG_DEBUG_INFO_BTF; then + info BTFIDS "${VMLINUX}" + RESOLVE_BTFIDS_ARGS="" + if is_enabled CONFIG_WERROR; then + RESOLVE_BTFIDS_ARGS=" --fatal_warnings " + fi + ${RESOLVE_BTFIDS} ${RESOLVE_BTFIDS_ARGS} "${VMLINUX}" fi -mksysmap vmlinux System.map ${kallsymso} +mksysmap "${VMLINUX}" System.map if is_enabled CONFIG_BUILDTIME_TABLE_SORT; then - info SORTTAB vmlinux - if ! sorttable vmlinux; then + info SORTTAB "${VMLINUX}" + if ! sorttable "${VMLINUX}"; then echo >&2 Failed to sort kernel tables exit 1 fi @@ -294,7 +306,7 @@ fi # step a (see comment above) if is_enabled CONFIG_KALLSYMS; then - if ! cmp -s System.map ${kallsyms_vmlinux}.syms; then + if ! cmp -s System.map "${kallsyms_sysmap}"; then echo >&2 Inconsistent kallsyms data echo >&2 'Try "make KALLSYMS_EXTRA_PASS=1" as a workaround' exit 1 @@ -302,4 +314,4 @@ if is_enabled CONFIG_KALLSYMS; then fi # For fixdep -echo "vmlinux: $0" > .vmlinux.d +echo "${VMLINUX}: $0" > ".${VMLINUX}.d" diff --git a/scripts/macro_checker.py b/scripts/macro_checker.py new file mode 100755 index 000000000000..ba550982e98f --- /dev/null +++ b/scripts/macro_checker.py @@ -0,0 +1,131 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 +# Author: Julian Sun <sunjunchao2870@gmail.com> + +""" Find macro definitions with unused parameters. """ + +import argparse +import os +import re + +parser = argparse.ArgumentParser() + +parser.add_argument("path", type=str, help="The file or dir path that needs check") +parser.add_argument("-v", "--verbose", action="store_true", + help="Check conditional macros, but may lead to more false positives") +args = parser.parse_args() + +macro_pattern = r"#define\s+(\w+)\(([^)]*)\)" +# below vars were used to reduce false positives +fp_patterns = [r"\s*do\s*\{\s*\}\s*while\s*\(\s*0\s*\)", + r"\(?0\)?", r"\(?1\)?"] +correct_macros = [] +cond_compile_mark = "#if" +cond_compile_end = "#endif" + +def check_macro(macro_line, report): + match = re.match(macro_pattern, macro_line) + if match: + macro_def = re.sub(macro_pattern, '', macro_line) + identifier = match.group(1) + content = match.group(2) + arguments = [item.strip() for item in content.split(',') if item.strip()] + + macro_def = macro_def.strip() + if not macro_def: + return + # used to reduce false positives, like #define endfor_nexthops(rt) } + if len(macro_def) == 1: + return + + for fp_pattern in fp_patterns: + if (re.match(fp_pattern, macro_def)): + return + + for arg in arguments: + # used to reduce false positives + if "..." in arg: + return + for arg in arguments: + if not arg in macro_def and report == False: + return + # if there is a correct macro with the same name, do not report it. + if not arg in macro_def and identifier not in correct_macros: + print(f"Argument {arg} is not used in function-line macro {identifier}") + return + + correct_macros.append(identifier) + + +# remove comment and whitespace +def macro_strip(macro): + comment_pattern1 = r"\/\/*" + comment_pattern2 = r"\/\**\*\/" + + macro = macro.strip() + macro = re.sub(comment_pattern1, '', macro) + macro = re.sub(comment_pattern2, '', macro) + + return macro + +def file_check_macro(file_path, report): + # number of conditional compiling + cond_compile = 0 + # only check .c and .h file + if not file_path.endswith(".c") and not file_path.endswith(".h"): + return + + with open(file_path, "r") as f: + while True: + line = f.readline() + if not line: + break + line = line.strip() + if line.startswith(cond_compile_mark): + cond_compile += 1 + continue + if line.startswith(cond_compile_end): + cond_compile -= 1 + continue + + macro = re.match(macro_pattern, line) + if macro: + macro = macro_strip(macro.string) + while macro[-1] == '\\': + macro = macro[0:-1] + macro = macro.strip() + macro += f.readline() + macro = macro_strip(macro) + if not args.verbose: + if file_path.endswith(".c") and cond_compile != 0: + continue + # 1 is for #ifdef xxx at the beginning of the header file + if file_path.endswith(".h") and cond_compile != 1: + continue + check_macro(macro, report) + +def get_correct_macros(path): + file_check_macro(path, False) + +def dir_check_macro(dir_path): + + for dentry in os.listdir(dir_path): + path = os.path.join(dir_path, dentry) + if os.path.isdir(path): + dir_check_macro(path) + elif os.path.isfile(path): + get_correct_macros(path) + file_check_macro(path, True) + + +def main(): + if os.path.isfile(args.path): + get_correct_macros(args.path) + file_check_macro(args.path, True) + elif os.path.isdir(args.path): + dir_check_macro(args.path) + else: + print(f"{args.path} doesn't exit or is neither a file nor a dir") + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/scripts/make_fit.py b/scripts/make_fit.py new file mode 100755 index 000000000000..1683e5ec6e67 --- /dev/null +++ b/scripts/make_fit.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2024 Google LLC +# Written by Simon Glass <sjg@chromium.org> +# + +"""Build a FIT containing a lot of devicetree files + +Usage: + make_fit.py -A arm64 -n 'Linux-6.6' -O linux + -o arch/arm64/boot/image.fit -k /tmp/kern/arch/arm64/boot/image.itk + @arch/arm64/boot/dts/dtbs-list -E -c gzip + +Creates a FIT containing the supplied kernel and a set of devicetree files, +either specified individually or listed in a file (with an '@' prefix). + +Use -E to generate an external FIT (where the data is placed after the +FIT data structure). This allows parsing of the data without loading +the entire FIT. + +Use -c to compress the data, using bzip2, gzip, lz4, lzma, lzo and +zstd algorithms. + +Use -D to decompose "composite" DTBs into their base components and +deduplicate the resulting base DTBs and DTB overlays. This requires the +DTBs to be sourced from the kernel build directory, as the implementation +looks at the .cmd files produced by the kernel build. + +The resulting FIT can be booted by bootloaders which support FIT, such +as U-Boot, Linuxboot, Tianocore, etc. + +Note that this tool does not yet support adding a ramdisk / initrd. +""" + +import argparse +import collections +import os +import subprocess +import sys +import tempfile +import time + +import libfdt + + +# Tool extension and the name of the command-line tools +CompTool = collections.namedtuple('CompTool', 'ext,tools') + +COMP_TOOLS = { + 'bzip2': CompTool('.bz2', 'bzip2'), + 'gzip': CompTool('.gz', 'pigz,gzip'), + 'lz4': CompTool('.lz4', 'lz4'), + 'lzma': CompTool('.lzma', 'lzma'), + 'lzo': CompTool('.lzo', 'lzop'), + 'zstd': CompTool('.zstd', 'zstd'), +} + + +def parse_args(): + """Parse the program ArgumentParser + + Returns: + Namespace object containing the arguments + """ + epilog = 'Build a FIT from a directory tree containing .dtb files' + parser = argparse.ArgumentParser(epilog=epilog, fromfile_prefix_chars='@') + parser.add_argument('-A', '--arch', type=str, required=True, + help='Specifies the architecture') + parser.add_argument('-c', '--compress', type=str, default='none', + help='Specifies the compression') + parser.add_argument('-D', '--decompose-dtbs', action='store_true', + help='Decompose composite DTBs into base DTB and overlays') + parser.add_argument('-E', '--external', action='store_true', + help='Convert the FIT to use external data') + parser.add_argument('-n', '--name', type=str, required=True, + help='Specifies the name') + parser.add_argument('-o', '--output', type=str, required=True, + help='Specifies the output file (.fit)') + parser.add_argument('-O', '--os', type=str, required=True, + help='Specifies the operating system') + parser.add_argument('-k', '--kernel', type=str, required=True, + help='Specifies the (uncompressed) kernel input file (.itk)') + parser.add_argument('-v', '--verbose', action='store_true', + help='Enable verbose output') + parser.add_argument('dtbs', type=str, nargs='*', + help='Specifies the devicetree files to process') + + return parser.parse_args() + + +def setup_fit(fsw, name): + """Make a start on writing the FIT + + Outputs the root properties and the 'images' node + + Args: + fsw (libfdt.FdtSw): Object to use for writing + name (str): Name of kernel image + """ + fsw.INC_SIZE = 65536 + fsw.finish_reservemap() + fsw.begin_node('') + fsw.property_string('description', f'{name} with devicetree set') + fsw.property_u32('#address-cells', 1) + + fsw.property_u32('timestamp', int(time.time())) + fsw.begin_node('images') + + +def write_kernel(fsw, data, args): + """Write out the kernel image + + Writes a kernel node along with the required properties + + Args: + fsw (libfdt.FdtSw): Object to use for writing + data (bytes): Data to write (possibly compressed) + args (Namespace): Contains necessary strings: + arch: FIT architecture, e.g. 'arm64' + fit_os: Operating Systems, e.g. 'linux' + name: Name of OS, e.g. 'Linux-6.6.0-rc7' + compress: Compression algorithm to use, e.g. 'gzip' + """ + with fsw.add_node('kernel'): + fsw.property_string('description', args.name) + fsw.property_string('type', 'kernel_noload') + fsw.property_string('arch', args.arch) + fsw.property_string('os', args.os) + fsw.property_string('compression', args.compress) + fsw.property('data', data) + fsw.property_u32('load', 0) + fsw.property_u32('entry', 0) + + +def finish_fit(fsw, entries): + """Finish the FIT ready for use + + Writes the /configurations node and subnodes + + Args: + fsw (libfdt.FdtSw): Object to use for writing + entries (list of tuple): List of configurations: + str: Description of model + str: Compatible stringlist + """ + fsw.end_node() + seq = 0 + with fsw.add_node('configurations'): + for model, compat, files in entries: + seq += 1 + with fsw.add_node(f'conf-{seq}'): + fsw.property('compatible', bytes(compat)) + fsw.property_string('description', model) + fsw.property('fdt', bytes(''.join(f'fdt-{x}\x00' for x in files), "ascii")) + fsw.property_string('kernel', 'kernel') + fsw.end_node() + + +def compress_data(inf, compress): + """Compress data using a selected algorithm + + Args: + inf (IOBase): Filename containing the data to compress + compress (str): Compression algorithm, e.g. 'gzip' + + Return: + bytes: Compressed data + """ + if compress == 'none': + return inf.read() + + comp = COMP_TOOLS.get(compress) + if not comp: + raise ValueError(f"Unknown compression algorithm '{compress}'") + + with tempfile.NamedTemporaryFile() as comp_fname: + with open(comp_fname.name, 'wb') as outf: + done = False + for tool in comp.tools.split(','): + try: + subprocess.call([tool, '-c'], stdin=inf, stdout=outf) + done = True + break + except FileNotFoundError: + pass + if not done: + raise ValueError(f'Missing tool(s): {comp.tools}\n') + with open(comp_fname.name, 'rb') as compf: + comp_data = compf.read() + return comp_data + + +def output_dtb(fsw, seq, fname, arch, compress): + """Write out a single devicetree to the FIT + + Args: + fsw (libfdt.FdtSw): Object to use for writing + seq (int): Sequence number (1 for first) + fname (str): Filename containing the DTB + arch: FIT architecture, e.g. 'arm64' + compress (str): Compressed algorithm, e.g. 'gzip' + """ + with fsw.add_node(f'fdt-{seq}'): + fsw.property_string('description', os.path.basename(fname)) + fsw.property_string('type', 'flat_dt') + fsw.property_string('arch', arch) + fsw.property_string('compression', compress) + + with open(fname, 'rb') as inf: + compressed = compress_data(inf, compress) + fsw.property('data', compressed) + + +def process_dtb(fname, args): + """Process an input DTB, decomposing it if requested and is possible + + Args: + fname (str): Filename containing the DTB + args (Namespace): Program arguments + Returns: + tuple: + str: Model name string + str: Root compatible string + files: list of filenames corresponding to the DTB + """ + # Get the compatible / model information + with open(fname, 'rb') as inf: + data = inf.read() + fdt = libfdt.FdtRo(data) + model = fdt.getprop(0, 'model').as_str() + compat = fdt.getprop(0, 'compatible') + + if args.decompose_dtbs: + # Check if the DTB needs to be decomposed + path, basename = os.path.split(fname) + cmd_fname = os.path.join(path, f'.{basename}.cmd') + with open(cmd_fname, 'r', encoding='ascii') as inf: + cmd = inf.read() + + if 'scripts/dtc/fdtoverlay' in cmd: + # This depends on the structure of the composite DTB command + files = cmd.split() + files = files[files.index('-i') + 1:] + else: + files = [fname] + else: + files = [fname] + + return (model, compat, files) + +def build_fit(args): + """Build the FIT from the provided files and arguments + + Args: + args (Namespace): Program arguments + + Returns: + tuple: + bytes: FIT data + int: Number of configurations generated + size: Total uncompressed size of data + """ + seq = 0 + size = 0 + fsw = libfdt.FdtSw() + setup_fit(fsw, args.name) + entries = [] + fdts = {} + + # Handle the kernel + with open(args.kernel, 'rb') as inf: + comp_data = compress_data(inf, args.compress) + size += os.path.getsize(args.kernel) + write_kernel(fsw, comp_data, args) + + for fname in args.dtbs: + # Ignore non-DTB (*.dtb) files + if os.path.splitext(fname)[1] != '.dtb': + continue + + try: + (model, compat, files) = process_dtb(fname, args) + except Exception as e: + sys.stderr.write(f"Error processing {fname}:\n") + raise e + + for fn in files: + if fn not in fdts: + seq += 1 + size += os.path.getsize(fn) + output_dtb(fsw, seq, fn, args.arch, args.compress) + fdts[fn] = seq + + files_seq = [fdts[fn] for fn in files] + + entries.append([model, compat, files_seq]) + + finish_fit(fsw, entries) + + # Include the kernel itself in the returned file count + return fsw.as_fdt().as_bytearray(), seq + 1, size + + +def run_make_fit(): + """Run the tool's main logic""" + args = parse_args() + + out_data, count, size = build_fit(args) + with open(args.output, 'wb') as outf: + outf.write(out_data) + + ext_fit_size = None + if args.external: + mkimage = os.environ.get('MKIMAGE', 'mkimage') + subprocess.check_call([mkimage, '-E', '-F', args.output], + stdout=subprocess.DEVNULL) + + with open(args.output, 'rb') as inf: + data = inf.read() + ext_fit = libfdt.FdtRo(data) + ext_fit_size = ext_fit.totalsize() + + if args.verbose: + comp_size = len(out_data) + print(f'FIT size {comp_size:#x}/{comp_size / 1024 / 1024:.1f} MB', + end='') + if ext_fit_size: + print(f', header {ext_fit_size:#x}/{ext_fit_size / 1024:.1f} KB', + end='') + print(f', {count} files, uncompressed {size / 1024 / 1024:.1f} MB') + + +if __name__ == "__main__": + sys.exit(run_make_fit()) diff --git a/scripts/min-tool-version.sh b/scripts/min-tool-version.sh index 5927cc6b7de3..0d223b4a9445 100755 --- a/scripts/min-tool-version.sh +++ b/scripts/min-tool-version.sh @@ -14,17 +14,17 @@ fi case "$1" in binutils) - echo 2.25.0 + echo 2.30.0 ;; gcc) if [ "$ARCH" = parisc64 ]; then echo 12.0.0 else - echo 5.1.0 + echo 8.1.0 fi ;; llvm) - if [ "$SRCARCH" = s390 ]; then + if [ "$SRCARCH" = s390 -o "$SRCARCH" = x86 ]; then echo 15.0.0 elif [ "$SRCARCH" = loongarch ]; then echo 18.0.0 @@ -33,7 +33,7 @@ llvm) fi ;; rustc) - echo 1.76.0 + echo 1.78.0 ;; bindgen) echo 0.65.1 diff --git a/scripts/misc-check b/scripts/misc-check index d40d5484e0c5..84f08da17b2c 100755 --- a/scripts/misc-check +++ b/scripts/misc-check @@ -3,17 +3,74 @@ set -e -# Detect files that are tracked but ignored by git. This is checked only when -# ${KBUILD_EXTRA_WARN} contains 1, git is installed, and the source tree is -# tracked by git. +# Detect files that are tracked but ignored by git. check_tracked_ignored_files () { - case "${KBUILD_EXTRA_WARN}" in - *1*) ;; - *) return;; - esac - - git -C ${srctree:-.} ls-files -i -c --exclude-per-directory=.gitignore 2>/dev/null | + git -C "${srctree:-.}" ls-files -i -c --exclude-per-directory=.gitignore 2>/dev/null | sed 's/$/: warning: ignored by one of the .gitignore files/' >&2 } -check_tracked_ignored_files +# Check for missing #include <linux/export.h> +# +# The rule for including <linux/export.h> is very simple: +# Include <linux/export.h> only when you use EXPORT_SYMBOL(). That's it. +# +# However, some headers include <linux/export.h> even though they are completely +# unrelated to EXPORT_SYMBOL(). +# +# One example is include/linux/module.h. Please note <linux/module.h> and +# <linux/export.h> are orthogonal. <linux/module.h> should be included by files +# that can be compiled as modules. In other words, <linux/module.h> should be +# included by EXPORT_SYMBOL consumers. In contrast, <linux/export.h> should be +# included from EXPORT_SYMBOL providers, which may or may not be modular. +# Hence, include/linux/module.h should *not* include <linux/export.h>. +# +# Another example is include/linux/linkage.h, which is completely unrelated to +# EXPORT_SYMBOL(). Worse, it is included by most C files, which means, most C +# files end up including <linux/export.h>, even though only some of them +# actually export symbols. Hence, include/linux/linkage.h should *not* include +# <linux/export.h>. +# +# Before fixing such headers, we must ensure that C files using EXPORT_SYMBOL() +# include <linux/export.h> directly, since many C files currently rely on +# <linux/export.h> being included indirectly (likely, via <linux/linkage> etc.). +# +# Therefore, this check. +# +# The problem is simple - the warned files use EXPORT_SYMBOL(), but do not +# include <linux/export.h>. Please add #include <linux/export.h> to them. +# +# If the included headers are sorted alphabetically, please insert +# <linux/export.h> in the appropriate position to maintain the sort order. +# For this reason, this script only checks missing <linux/export.h>, but +# does not automatically fix it. +check_missing_include_linux_export_h () { + + git -C "${srctree:-.}" grep --files-with-matches -E 'EXPORT_SYMBOL((_NS)?(_GPL)?|_GPL_FOR_MODULES)\(.*\)' \ + -- '*.[ch]' :^tools/ :^include/linux/export.h | + xargs -r git -C "${srctree:-.}" grep --files-without-match '#include[[:space:]]*<linux/export\.h>' | + xargs -r printf "%s: warning: EXPORT_SYMBOL() is used, but #include <linux/export.h> is missing\n" >&2 +} + +# If you do not use EXPORT_SYMBOL(), please do not include <linux/export.h>. +# Currently, this is checked for *.c files, but not for *.h files, because some +# *.c files rely on <linux/export.h> being included indirectly. +check_unnecessary_include_linux_export_h () { + + git -C "${srctree:-.}" grep --files-with-matches '#include[[:space:]]*<linux/export\.h>' \ + -- '*.[c]' :^tools/ | + xargs -r git -C "${srctree:-.}" grep --files-without-match -E 'EXPORT_SYMBOL((_NS)?(_GPL)?|_GPL_FOR_MODULES)\(.*\)' | + xargs -r printf "%s: warning: EXPORT_SYMBOL() is not used, but #include <linux/export.h> is present\n" >&2 +} + +case "${KBUILD_EXTRA_WARN}" in +*1*) + check_tracked_ignored_files + ;; +esac + +case "${KBUILD_EXTRA_WARN}" in +*2*) + check_missing_include_linux_export_h + check_unnecessary_include_linux_export_h + ;; +esac diff --git a/scripts/mksysmap b/scripts/mksysmap index 57ff5656d566..3accbdb269ac 100755 --- a/scripts/mksysmap +++ b/scripts/mksysmap @@ -1,22 +1,16 @@ -#!/bin/sh -x -# Based on the vmlinux file create the System.map file +#!/bin/sed -f +# SPDX-License-Identifier: GPL-2.0-only +# +# sed script to filter out symbols that are not needed for System.map, +# or not suitable for kallsyms. The input should be 'nm -n <file>'. +# # System.map is used by module-init tools and some debugging # tools to retrieve the actual addresses of symbols in the kernel. # -# Usage -# mksysmap vmlinux System.map [exclude] - - -##### -# Generate System.map (actual filename passed as second argument) -# The following refers to the symbol type as per nm(1). - # readprofile starts reading symbols when _stext is found, and # continue until it finds a symbol which is not either of 'T', 't', # 'W' or 'w'. # - -${NM} -n ${1} | sed >${2} -e " # --------------------------------------------------------------------------- # Ignored symbol types # @@ -32,7 +26,7 @@ ${NM} -n ${1} | sed >${2} -e " # (do not forget a space before each pattern) # local symbols for ARM, MIPS, etc. -/ \\$/d +/ \$/d # local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc. / \.L/d @@ -45,7 +39,7 @@ ${NM} -n ${1} | sed >${2} -e " / __pi_\.L/d # arm64 local symbols in non-VHE KVM namespace -/ __kvm_nvhe_\\$/d +/ __kvm_nvhe_\$/d / __kvm_nvhe_\.L/d # lld arm/aarch64/mips thunks @@ -92,13 +86,3 @@ ${NM} -n ${1} | sed >${2} -e " # ppc stub /\.long_branch\./d /\.plt_branch\./d - -# --------------------------------------------------------------------------- -# Ignored kallsyms symbols -# -# If the 3rd parameter exists, symbols from it will be omitted from the output. -# This makes kallsyms have the identical symbol lists in the step 1 and 2. -# Without this, the step2 would get new symbols generated by scripts/kallsyms.c -# when CONFIG_KALLSYMS_ALL is enabled. That might require one more pass. -$(if [ $# -ge 3 ]; then ${NM} ${3} | sed -n '/ U /!s:.* \([^ ]*\)$:/ \1$/d:p'; fi) -" diff --git a/scripts/mod/Makefile b/scripts/mod/Makefile index 3c54125eb373..c729bc936bae 100644 --- a/scripts/mod/Makefile +++ b/scripts/mod/Makefile @@ -1,5 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -OBJECT_FILES_NON_STANDARD := y CFLAGS_REMOVE_empty.o += $(CC_FLAGS_LTO) hostprogs-always-y += modpost mk_elfconfig diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index 518200813d4e..d3d00e85edf7 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -153,6 +153,10 @@ int main(void) DEVID_FIELD(i3c_device_id, part_id); DEVID_FIELD(i3c_device_id, extra_info); + DEVID(slim_device_id); + DEVID_FIELD(slim_device_id, manf_id); + DEVID_FIELD(slim_device_id, prod_code); + DEVID(spi_device_id); DEVID_FIELD(spi_device_id, name); @@ -233,7 +237,6 @@ int main(void) DEVID(typec_device_id); DEVID_FIELD(typec_device_id, svid); - DEVID_FIELD(typec_device_id, mode); DEVID(tee_client_device_id); DEVID_FIELD(tee_client_device_id, uuid); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 5d1c61fa5a55..00586119a25b 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -10,6 +10,12 @@ * of the GNU General Public License, incorporated herein by reference. */ +#include <stdarg.h> +#include <stdio.h> + +#include "list.h" +#include "xalloc.h" + #include "modpost.h" #include "devicetable-offsets.h" @@ -31,6 +37,66 @@ typedef Elf64_Addr kernel_ulong_t; #include <ctype.h> #include <stdbool.h> +/** + * module_alias_printf - add auto-generated MODULE_ALIAS() + * + * @mod: module + * @append_wildcard: append '*' for future extension if not exist yet + * @fmt: printf(3)-like format + */ +static void __attribute__((format (printf, 3, 4))) +module_alias_printf(struct module *mod, bool append_wildcard, + const char *fmt, ...) +{ + struct module_alias *new, *als; + size_t len; + int n; + va_list ap; + + /* Determine required size. */ + va_start(ap, fmt); + n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + if (n < 0) { + error("vsnprintf failed\n"); + return; + } + + len = n + 1; /* extra byte for '\0' */ + + if (append_wildcard) + len++; /* extra byte for '*' */ + + new = xmalloc(sizeof(*new) + len); + + /* Now, really print it to the allocated buffer */ + va_start(ap, fmt); + n = vsnprintf(new->str, len, fmt, ap); + va_end(ap); + + if (n < 0) { + error("vsnprintf failed\n"); + free(new); + return; + } + + if (append_wildcard && (n == 0 || new->str[n - 1] != '*')) { + new->str[n] = '*'; + new->str[n + 1] = '\0'; + } + + /* avoid duplication */ + list_for_each_entry(als, &mod->aliases, node) { + if (!strcmp(als->str, new->str)) { + free(new); + return; + } + } + + list_add_tail(&new->node, &mod->aliases); +} + typedef uint32_t __u32; typedef uint16_t __u16; typedef unsigned char __u8; @@ -56,35 +122,25 @@ typedef struct { * we handle those differences explicitly below */ #include "../../include/linux/mod_devicetable.h" -/* This array collects all instances that use the generic do_table */ struct devtable { - const char *device_id; /* name of table, __mod_<name>__*_device_table. */ + const char *device_id; unsigned long id_size; - int (*do_entry)(const char *filename, void *symval, char *alias); + void (*do_entry)(struct module *mod, void *symval); }; -/* Size of alias provided to do_entry functions */ -#define ALIAS_SIZE 500 - /* Define a variable f that holds the value of field f of struct devid * based at address m. */ #define DEF_FIELD(m, devid, f) \ - typeof(((struct devid *)0)->f) f = TO_NATIVE(*(typeof(f) *)((m) + OFF_##devid##_##f)) - -/* Define a variable v that holds the address of field f of struct devid - * based at address m. Due to the way typeof works, for a field of type - * T[N] the variable has type T(*)[N], _not_ T*. - */ -#define DEF_FIELD_ADDR_VAR(m, devid, f, v) \ - typeof(((struct devid *)0)->f) *v = ((m) + OFF_##devid##_##f) + typeof(((struct devid *)0)->f) f = \ + get_unaligned_native((typeof(f) *)((m) + OFF_##devid##_##f)) /* Define a variable f that holds the address of field f of struct devid * based at address m. Due to the way typeof works, for a field of type * T[N] the variable has type T(*)[N], _not_ T*. */ #define DEF_FIELD_ADDR(m, devid, f) \ - DEF_FIELD_ADDR_VAR(m, devid, f, f) + typeof(((struct devid *)0)->f) *f = ((m) + OFF_##devid##_##f) #define ADD(str, sep, cond, field) \ do { \ @@ -99,15 +155,6 @@ do { \ sprintf(str + strlen(str), "*"); \ } while(0) -/* End in a wildcard, for future extension */ -static inline void add_wildcard(char *str) -{ - int len = strlen(str); - - if (str[len - 1] != '*') - strcat(str + len, "*"); -} - static inline void add_uuid(char *str, uuid_le uuid) { int len = strlen(str); @@ -130,40 +177,6 @@ static inline void add_guid(char *str, guid_t guid) guid.b[12], guid.b[13], guid.b[14], guid.b[15]); } -/** - * Check that sizeof(device_id type) are consistent with size of section - * in .o file. If in-consistent then userspace and kernel does not agree - * on actual size which is a bug. - * Also verify that the final entry in the table is all zeros. - * Ignore both checks if build host differ from target host and size differs. - **/ -static void device_id_check(const char *modname, const char *device_id, - unsigned long size, unsigned long id_size, - void *symval) -{ - int i; - - if (size % id_size || size < id_size) { - fatal("%s: sizeof(struct %s_device_id)=%lu is not a modulo of the size of section __mod_%s__<identifier>_device_table=%lu.\n" - "Fix definition of struct %s_device_id in mod_devicetable.h\n", - modname, device_id, id_size, device_id, size, device_id); - } - /* Verify last one is a terminator */ - for (i = 0; i < id_size; i++ ) { - if (*(uint8_t*)(symval+size-id_size+i)) { - fprintf(stderr, - "%s: struct %s_device_id is %lu bytes. The last of %lu is:\n", - modname, device_id, id_size, size / id_size); - for (i = 0; i < id_size; i++ ) - fprintf(stderr,"0x%02x ", - *(uint8_t*)(symval+size-id_size+i) ); - fprintf(stderr,"\n"); - fatal("%s: struct %s_device_id is not terminated with a NULL entry!\n", - modname, device_id); - } - } -} - /* USB is special because the bcdDevice can be matched against a numeric range */ /* Looks like "usb:vNpNdNdcNdscNdpNicNiscNipNinN" */ static void do_usb_entry(void *symval, @@ -229,9 +242,7 @@ static void do_usb_entry(void *symval, ADD(alias, "in", match_flags&USB_DEVICE_ID_MATCH_INT_NUMBER, bInterfaceNumber); - add_wildcard(alias); - buf_printf(&mod->dev_table_buf, - "MODULE_ALIAS(\"%s\");\n", alias); + module_alias_printf(mod, true, "%s", alias); } /* Handles increment/decrement of BCD formatted integers */ @@ -273,7 +284,7 @@ static unsigned int incbcd(unsigned int *bcd, return init; } -static void do_usb_entry_multi(void *symval, struct module *mod) +static void do_usb_entry_multi(struct module *mod, void *symval) { unsigned int devlo, devhi; unsigned char chi, clo, max; @@ -338,22 +349,7 @@ static void do_usb_entry_multi(void *symval, struct module *mod) } } -static void do_usb_table(void *symval, unsigned long size, - struct module *mod) -{ - unsigned int i; - const unsigned long id_size = SIZE_usb_device_id; - - device_id_check(mod->name, "usb", size, id_size, symval); - - /* Leave last one: it's the terminator. */ - size -= id_size; - - for (i = 0; i < size; i += id_size) - do_usb_entry_multi(symval + i, mod); -} - -static void do_of_entry_multi(void *symval, struct module *mod) +static void do_of_entry(struct module *mod, void *symval) { char alias[500]; int len; @@ -375,56 +371,39 @@ static void do_of_entry_multi(void *symval, struct module *mod) if (isspace(*tmp)) *tmp = '_'; - buf_printf(&mod->dev_table_buf, "MODULE_ALIAS(\"%s\");\n", alias); - strcat(alias, "C"); - add_wildcard(alias); - buf_printf(&mod->dev_table_buf, "MODULE_ALIAS(\"%s\");\n", alias); -} - -static void do_of_table(void *symval, unsigned long size, - struct module *mod) -{ - unsigned int i; - const unsigned long id_size = SIZE_of_device_id; - - device_id_check(mod->name, "of", size, id_size, symval); - - /* Leave last one: it's the terminator. */ - size -= id_size; - - for (i = 0; i < size; i += id_size) - do_of_entry_multi(symval + i, mod); + module_alias_printf(mod, false, "%s", alias); + module_alias_printf(mod, false, "%sC*", alias); } /* Looks like: hid:bNvNpN */ -static int do_hid_entry(const char *filename, - void *symval, char *alias) +static void do_hid_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, hid_device_id, bus); DEF_FIELD(symval, hid_device_id, group); DEF_FIELD(symval, hid_device_id, vendor); DEF_FIELD(symval, hid_device_id, product); - sprintf(alias, "hid:"); ADD(alias, "b", bus != HID_BUS_ANY, bus); ADD(alias, "g", group != HID_GROUP_ANY, group); ADD(alias, "v", vendor != HID_ANY_ID, vendor); ADD(alias, "p", product != HID_ANY_ID, product); - return 1; + module_alias_printf(mod, false, "hid:%s", alias); } /* Looks like: ieee1394:venNmoNspNverN */ -static int do_ieee1394_entry(const char *filename, - void *symval, char *alias) +static void do_ieee1394_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, ieee1394_device_id, match_flags); DEF_FIELD(symval, ieee1394_device_id, vendor_id); DEF_FIELD(symval, ieee1394_device_id, model_id); DEF_FIELD(symval, ieee1394_device_id, specifier_id); DEF_FIELD(symval, ieee1394_device_id, version); - strcpy(alias, "ieee1394:"); ADD(alias, "ven", match_flags & IEEE1394_MATCH_VENDOR_ID, vendor_id); ADD(alias, "mo", match_flags & IEEE1394_MATCH_MODEL_ID, @@ -434,14 +413,13 @@ static int do_ieee1394_entry(const char *filename, ADD(alias, "ver", match_flags & IEEE1394_MATCH_VERSION, version); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "ieee1394:%s", alias); } /* Looks like: pci:vNdNsvNsdNbcNscNiN or <prefix>_pci:vNdNsvNsdNbcNscNiN. */ -static int do_pci_entry(const char *filename, - void *symval, char *alias) +static void do_pci_entry(struct module *mod, void *symval) { + char alias[256]; /* Class field can be divided into these three. */ unsigned char baseclass, subclass, interface, baseclass_mask, subclass_mask, interface_mask; @@ -464,7 +442,6 @@ static int do_pci_entry(const char *filename, default: warn("Unknown PCI driver_override alias %08X\n", override_only); - return 0; } ADD(alias, "v", vendor != PCI_ANY_ID, vendor); @@ -483,28 +460,28 @@ static int do_pci_entry(const char *filename, || (subclass_mask != 0 && subclass_mask != 0xFF) || (interface_mask != 0 && interface_mask != 0xFF)) { warn("Can't handle masks in %s:%04X\n", - filename, class_mask); - return 0; + mod->name, class_mask); + return; } ADD(alias, "bc", baseclass_mask == 0xFF, baseclass); ADD(alias, "sc", subclass_mask == 0xFF, subclass); ADD(alias, "i", interface_mask == 0xFF, interface); - add_wildcard(alias); - return 1; + + module_alias_printf(mod, true, "%s", alias); } /* looks like: "ccw:tNmNdtNdmN" */ -static int do_ccw_entry(const char *filename, - void *symval, char *alias) +static void do_ccw_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, ccw_device_id, match_flags); DEF_FIELD(symval, ccw_device_id, cu_type); DEF_FIELD(symval, ccw_device_id, cu_model); DEF_FIELD(symval, ccw_device_id, dev_type); DEF_FIELD(symval, ccw_device_id, dev_model); - strcpy(alias, "ccw:"); ADD(alias, "t", match_flags&CCW_DEVICE_ID_MATCH_CU_TYPE, cu_type); ADD(alias, "m", match_flags&CCW_DEVICE_ID_MATCH_CU_MODEL, @@ -513,47 +490,42 @@ static int do_ccw_entry(const char *filename, dev_type); ADD(alias, "dm", match_flags&CCW_DEVICE_ID_MATCH_DEVICE_MODEL, dev_model); - add_wildcard(alias); - return 1; + + module_alias_printf(mod, true, "ccw:%s", alias); } /* looks like: "ap:tN" */ -static int do_ap_entry(const char *filename, - void *symval, char *alias) +static void do_ap_entry(struct module *mod, void *symval) { DEF_FIELD(symval, ap_device_id, dev_type); - sprintf(alias, "ap:t%02X*", dev_type); - return 1; + module_alias_printf(mod, false, "ap:t%02X*", dev_type); } /* looks like: "css:tN" */ -static int do_css_entry(const char *filename, - void *symval, char *alias) +static void do_css_entry(struct module *mod, void *symval) { DEF_FIELD(symval, css_device_id, type); - sprintf(alias, "css:t%01X", type); - return 1; + module_alias_printf(mod, false, "css:t%01X", type); } /* Looks like: "serio:tyNprNidNexN" */ -static int do_serio_entry(const char *filename, - void *symval, char *alias) +static void do_serio_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, serio_device_id, type); DEF_FIELD(symval, serio_device_id, proto); DEF_FIELD(symval, serio_device_id, id); DEF_FIELD(symval, serio_device_id, extra); - strcpy(alias, "serio:"); ADD(alias, "ty", type != SERIO_ANY, type); ADD(alias, "pr", proto != SERIO_ANY, proto); ADD(alias, "id", id != SERIO_ANY, id); ADD(alias, "ex", extra != SERIO_ANY, extra); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "serio:%s", alias); } /* looks like: "acpi:ACPI0003" or "acpi:PNP0C0B" or "acpi:LNXVIDEO" or @@ -563,127 +535,73 @@ static int do_serio_entry(const char *filename, * or _CLS. Also, bb, ss, and pp can be substituted with ?? * as don't care byte. */ -static int do_acpi_entry(const char *filename, - void *symval, char *alias) +static void do_acpi_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, acpi_device_id, id); - DEF_FIELD_ADDR(symval, acpi_device_id, cls); - DEF_FIELD_ADDR(symval, acpi_device_id, cls_msk); + DEF_FIELD(symval, acpi_device_id, cls); + DEF_FIELD(symval, acpi_device_id, cls_msk); - if (id && strlen((const char *)*id)) - sprintf(alias, "acpi*:%s:*", *id); - else if (cls) { + if ((*id)[0]) + module_alias_printf(mod, false, "acpi*:%s:*", *id); + else { + char alias[256]; int i, byte_shift, cnt = 0; unsigned int msk; - sprintf(&alias[cnt], "acpi*:"); - cnt = 6; for (i = 1; i <= 3; i++) { byte_shift = 8 * (3-i); - msk = (*cls_msk >> byte_shift) & 0xFF; + msk = (cls_msk >> byte_shift) & 0xFF; if (msk) sprintf(&alias[cnt], "%02x", - (*cls >> byte_shift) & 0xFF); + (cls >> byte_shift) & 0xFF); else sprintf(&alias[cnt], "??"); cnt += 2; } - sprintf(&alias[cnt], ":*"); + module_alias_printf(mod, false, "acpi*:%s:*", alias); } - return 1; } /* looks like: "pnp:dD" */ -static void do_pnp_device_entry(void *symval, unsigned long size, - struct module *mod) +static void do_pnp_device_entry(struct module *mod, void *symval) { - const unsigned long id_size = SIZE_pnp_device_id; - const unsigned int count = (size / id_size)-1; - unsigned int i; - - device_id_check(mod->name, "pnp", size, id_size, symval); - - for (i = 0; i < count; i++) { - DEF_FIELD_ADDR(symval + i*id_size, pnp_device_id, id); - char acpi_id[sizeof(*id)]; - int j; + DEF_FIELD_ADDR(symval, pnp_device_id, id); + char acpi_id[sizeof(*id)]; - buf_printf(&mod->dev_table_buf, - "MODULE_ALIAS(\"pnp:d%s*\");\n", *id); - - /* fix broken pnp bus lowercasing */ - for (j = 0; j < sizeof(acpi_id); j++) - acpi_id[j] = toupper((*id)[j]); - buf_printf(&mod->dev_table_buf, - "MODULE_ALIAS(\"acpi*:%s:*\");\n", acpi_id); - } + /* fix broken pnp bus lowercasing */ + for (unsigned int i = 0; i < sizeof(acpi_id); i++) + acpi_id[i] = toupper((*id)[i]); + module_alias_printf(mod, false, "pnp:d%s*", *id); + module_alias_printf(mod, false, "acpi*:%s:*", acpi_id); } /* looks like: "pnp:dD" for every device of the card */ -static void do_pnp_card_entries(void *symval, unsigned long size, - struct module *mod) +static void do_pnp_card_entry(struct module *mod, void *symval) { - const unsigned long id_size = SIZE_pnp_card_device_id; - const unsigned int count = (size / id_size)-1; - unsigned int i; - - device_id_check(mod->name, "pnp", size, id_size, symval); - - for (i = 0; i < count; i++) { - unsigned int j; - DEF_FIELD_ADDR(symval + i * id_size, pnp_card_device_id, devs); - - for (j = 0; j < PNP_MAX_DEVICES; j++) { - const char *id = (char *)(*devs)[j].id; - int i2, j2; - int dup = 0; + DEF_FIELD_ADDR(symval, pnp_card_device_id, devs); - if (!id[0]) - break; + for (unsigned int i = 0; i < PNP_MAX_DEVICES; i++) { + const char *id = (char *)(*devs)[i].id; + char acpi_id[PNP_ID_LEN]; - /* find duplicate, already added value */ - for (i2 = 0; i2 < i && !dup; i2++) { - DEF_FIELD_ADDR_VAR(symval + i2 * id_size, - pnp_card_device_id, - devs, devs_dup); - - for (j2 = 0; j2 < PNP_MAX_DEVICES; j2++) { - const char *id2 = - (char *)(*devs_dup)[j2].id; - - if (!id2[0]) - break; - - if (!strcmp(id, id2)) { - dup = 1; - break; - } - } - } - - /* add an individual alias for every device entry */ - if (!dup) { - char acpi_id[PNP_ID_LEN]; - int k; + if (!id[0]) + break; - buf_printf(&mod->dev_table_buf, - "MODULE_ALIAS(\"pnp:d%s*\");\n", id); + /* fix broken pnp bus lowercasing */ + for (unsigned int j = 0; j < sizeof(acpi_id); j++) + acpi_id[j] = toupper(id[j]); - /* fix broken pnp bus lowercasing */ - for (k = 0; k < sizeof(acpi_id); k++) - acpi_id[k] = toupper(id[k]); - buf_printf(&mod->dev_table_buf, - "MODULE_ALIAS(\"acpi*:%s:*\");\n", acpi_id); - } - } + /* add an individual alias for every device entry */ + module_alias_printf(mod, false, "pnp:d%s*", id); + module_alias_printf(mod, false, "acpi*:%s:*", acpi_id); } } /* Looks like: pcmcia:mNcNfNfnNpfnNvaNvbNvcNvdN. */ -static int do_pcmcia_entry(const char *filename, - void *symval, char *alias) +static void do_pcmcia_entry(struct module *mod, void *symval) { - unsigned int i; + char alias[256] = {}; + DEF_FIELD(symval, pcmcia_device_id, match_flags); DEF_FIELD(symval, pcmcia_device_id, manf_id); DEF_FIELD(symval, pcmcia_device_id, card_id); @@ -692,11 +610,6 @@ static int do_pcmcia_entry(const char *filename, DEF_FIELD(symval, pcmcia_device_id, device_no); DEF_FIELD_ADDR(symval, pcmcia_device_id, prod_id_hash); - for (i=0; i<4; i++) { - (*prod_id_hash)[i] = TO_NATIVE((*prod_id_hash)[i]); - } - - strcpy(alias, "pcmcia:"); ADD(alias, "m", match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID, manf_id); ADD(alias, "c", match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID, @@ -707,18 +620,21 @@ static int do_pcmcia_entry(const char *filename, function); ADD(alias, "pfn", match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO, device_no); - ADD(alias, "pa", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1, (*prod_id_hash)[0]); - ADD(alias, "pb", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2, (*prod_id_hash)[1]); - ADD(alias, "pc", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3, (*prod_id_hash)[2]); - ADD(alias, "pd", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4, (*prod_id_hash)[3]); + ADD(alias, "pa", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1, + get_unaligned_native(*prod_id_hash + 0)); + ADD(alias, "pb", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2, + get_unaligned_native(*prod_id_hash + 1)); + ADD(alias, "pc", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3, + get_unaligned_native(*prod_id_hash + 2)); + ADD(alias, "pd", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4, + get_unaligned_native(*prod_id_hash + 3)); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "pcmcia:%s", alias); } -static int do_vio_entry(const char *filename, void *symval, - char *alias) +static void do_vio_entry(struct module *mod, void *symval) { + char alias[256]; char *tmp; DEF_FIELD_ADDR(symval, vio_device_id, type); DEF_FIELD_ADDR(symval, vio_device_id, compat); @@ -731,8 +647,7 @@ static int do_vio_entry(const char *filename, void *symval, if (isspace (*tmp)) *tmp = '_'; - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "%s", alias); } static void do_input(char *alias, @@ -740,17 +655,17 @@ static void do_input(char *alias, { unsigned int i; - for (i = min / BITS_PER_LONG; i < max / BITS_PER_LONG + 1; i++) - arr[i] = TO_NATIVE(arr[i]); - for (i = min; i < max; i++) - if (arr[i / BITS_PER_LONG] & (1L << (i%BITS_PER_LONG))) + for (i = min; i <= max; i++) + if (get_unaligned_native(arr + i / BITS_PER_LONG) & + (1ULL << (i % BITS_PER_LONG))) sprintf(alias + strlen(alias), "%X,*", i); } /* input:b0v0p0e0-eXkXrXaXmXlXsXfXwX where X is comma-separated %02X. */ -static int do_input_entry(const char *filename, void *symval, - char *alias) +static void do_input_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, input_device_id, flags); DEF_FIELD(symval, input_device_id, bustype); DEF_FIELD(symval, input_device_id, vendor); @@ -766,8 +681,6 @@ static int do_input_entry(const char *filename, void *symval, DEF_FIELD_ADDR(symval, input_device_id, ffbit); DEF_FIELD_ADDR(symval, input_device_id, swbit); - sprintf(alias, "input:"); - ADD(alias, "b", flags & INPUT_DEVICE_ID_MATCH_BUS, bustype); ADD(alias, "v", flags & INPUT_DEVICE_ID_MATCH_VENDOR, vendor); ADD(alias, "p", flags & INPUT_DEVICE_ID_MATCH_PRODUCT, product); @@ -802,102 +715,96 @@ static int do_input_entry(const char *filename, void *symval, sprintf(alias + strlen(alias), "w*"); if (flags & INPUT_DEVICE_ID_MATCH_SWBIT) do_input(alias, *swbit, 0, INPUT_DEVICE_ID_SW_MAX); - return 1; + + module_alias_printf(mod, false, "input:%s", alias); } -static int do_eisa_entry(const char *filename, void *symval, - char *alias) +static void do_eisa_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, eisa_device_id, sig); - if (sig[0]) - sprintf(alias, EISA_DEVICE_MODALIAS_FMT "*", *sig); - else - strcat(alias, "*"); - return 1; + module_alias_printf(mod, false, EISA_DEVICE_MODALIAS_FMT "*", *sig); } /* Looks like: parisc:tNhvNrevNsvN */ -static int do_parisc_entry(const char *filename, void *symval, - char *alias) +static void do_parisc_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, parisc_device_id, hw_type); DEF_FIELD(symval, parisc_device_id, hversion); DEF_FIELD(symval, parisc_device_id, hversion_rev); DEF_FIELD(symval, parisc_device_id, sversion); - strcpy(alias, "parisc:"); ADD(alias, "t", hw_type != PA_HWTYPE_ANY_ID, hw_type); ADD(alias, "hv", hversion != PA_HVERSION_ANY_ID, hversion); ADD(alias, "rev", hversion_rev != PA_HVERSION_REV_ANY_ID, hversion_rev); ADD(alias, "sv", sversion != PA_SVERSION_ANY_ID, sversion); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "parisc:%s", alias); } /* Looks like: sdio:cNvNdN. */ -static int do_sdio_entry(const char *filename, - void *symval, char *alias) +static void do_sdio_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, sdio_device_id, class); DEF_FIELD(symval, sdio_device_id, vendor); DEF_FIELD(symval, sdio_device_id, device); - strcpy(alias, "sdio:"); ADD(alias, "c", class != (__u8)SDIO_ANY_ID, class); ADD(alias, "v", vendor != (__u16)SDIO_ANY_ID, vendor); ADD(alias, "d", device != (__u16)SDIO_ANY_ID, device); - add_wildcard(alias); - return 1; + + module_alias_printf(mod, true, "sdio:%s", alias); } /* Looks like: ssb:vNidNrevN. */ -static int do_ssb_entry(const char *filename, - void *symval, char *alias) +static void do_ssb_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, ssb_device_id, vendor); DEF_FIELD(symval, ssb_device_id, coreid); DEF_FIELD(symval, ssb_device_id, revision); - strcpy(alias, "ssb:"); ADD(alias, "v", vendor != SSB_ANY_VENDOR, vendor); ADD(alias, "id", coreid != SSB_ANY_ID, coreid); ADD(alias, "rev", revision != SSB_ANY_REV, revision); - add_wildcard(alias); - return 1; + + module_alias_printf(mod, true, "ssb:%s", alias); } /* Looks like: bcma:mNidNrevNclN. */ -static int do_bcma_entry(const char *filename, - void *symval, char *alias) +static void do_bcma_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, bcma_device_id, manuf); DEF_FIELD(symval, bcma_device_id, id); DEF_FIELD(symval, bcma_device_id, rev); DEF_FIELD(symval, bcma_device_id, class); - strcpy(alias, "bcma:"); ADD(alias, "m", manuf != BCMA_ANY_MANUF, manuf); ADD(alias, "id", id != BCMA_ANY_ID, id); ADD(alias, "rev", rev != BCMA_ANY_REV, rev); ADD(alias, "cl", class != BCMA_ANY_CLASS, class); - add_wildcard(alias); - return 1; + + module_alias_printf(mod, true, "bcma:%s", alias); } /* Looks like: virtio:dNvN */ -static int do_virtio_entry(const char *filename, void *symval, - char *alias) +static void do_virtio_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, virtio_device_id, device); DEF_FIELD(symval, virtio_device_id, vendor); - strcpy(alias, "virtio:"); ADD(alias, "d", device != VIRTIO_DEV_ANY_ID, device); ADD(alias, "v", vendor != VIRTIO_DEV_ANY_ID, vendor); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "virtio:%s", alias); } /* @@ -905,69 +812,65 @@ static int do_virtio_entry(const char *filename, void *symval, * Each byte of the guid will be represented by two hex characters * in the name. */ - -static int do_vmbus_entry(const char *filename, void *symval, - char *alias) +static void do_vmbus_entry(struct module *mod, void *symval) { - int i; DEF_FIELD_ADDR(symval, hv_vmbus_device_id, guid); - char guid_name[(sizeof(*guid) + 1) * 2]; + char guid_name[sizeof(*guid) * 2 + 1]; - for (i = 0; i < (sizeof(*guid) * 2); i += 2) - sprintf(&guid_name[i], "%02x", TO_NATIVE((guid->b)[i/2])); + for (int i = 0; i < sizeof(*guid); i++) + sprintf(&guid_name[i * 2], "%02x", guid->b[i]); - strcpy(alias, "vmbus:"); - strcat(alias, guid_name); - - return 1; + module_alias_printf(mod, false, "vmbus:%s", guid_name); } /* Looks like: rpmsg:S */ -static int do_rpmsg_entry(const char *filename, void *symval, - char *alias) +static void do_rpmsg_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, rpmsg_device_id, name); - sprintf(alias, RPMSG_DEVICE_MODALIAS_FMT, *name); - return 1; + module_alias_printf(mod, false, RPMSG_DEVICE_MODALIAS_FMT, *name); } /* Looks like: i2c:S */ -static int do_i2c_entry(const char *filename, void *symval, - char *alias) +static void do_i2c_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, i2c_device_id, name); - sprintf(alias, I2C_MODULE_PREFIX "%s", *name); - return 1; + module_alias_printf(mod, false, I2C_MODULE_PREFIX "%s", *name); } -static int do_i3c_entry(const char *filename, void *symval, - char *alias) +static void do_i3c_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, i3c_device_id, match_flags); DEF_FIELD(symval, i3c_device_id, dcr); DEF_FIELD(symval, i3c_device_id, manuf_id); DEF_FIELD(symval, i3c_device_id, part_id); DEF_FIELD(symval, i3c_device_id, extra_info); - strcpy(alias, "i3c:"); ADD(alias, "dcr", match_flags & I3C_MATCH_DCR, dcr); ADD(alias, "manuf", match_flags & I3C_MATCH_MANUF, manuf_id); ADD(alias, "part", match_flags & I3C_MATCH_PART, part_id); ADD(alias, "ext", match_flags & I3C_MATCH_EXTRA_INFO, extra_info); - return 1; + module_alias_printf(mod, false, "i3c:%s", alias); +} + +static void do_slim_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, slim_device_id, manf_id); + DEF_FIELD(symval, slim_device_id, prod_code); + + module_alias_printf(mod, false, "slim:%x:%x:*", manf_id, prod_code); } /* Looks like: spi:S */ -static int do_spi_entry(const char *filename, void *symval, - char *alias) +static void do_spi_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, spi_device_id, name); - sprintf(alias, SPI_MODULE_PREFIX "%s", *name); - return 1; + module_alias_printf(mod, false, SPI_MODULE_PREFIX "%s", *name); } static const struct dmifield { @@ -1002,12 +905,11 @@ static void dmi_ascii_filter(char *d, const char *s) } -static int do_dmi_entry(const char *filename, void *symval, - char *alias) +static void do_dmi_entry(struct module *mod, void *symval) { + char alias[256] = {}; int i, j; DEF_FIELD_ADDR(symval, dmi_system_id, matches); - sprintf(alias, "dmi*"); for (i = 0; i < ARRAY_SIZE(dmi_fields); i++) { for (j = 0; j < 4; j++) { @@ -1022,80 +924,75 @@ static int do_dmi_entry(const char *filename, void *symval, } } - strcat(alias, ":"); - return 1; + module_alias_printf(mod, false, "dmi*%s:", alias); } -static int do_platform_entry(const char *filename, - void *symval, char *alias) +static void do_platform_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, platform_device_id, name); - sprintf(alias, PLATFORM_MODULE_PREFIX "%s", *name); - return 1; + + module_alias_printf(mod, false, PLATFORM_MODULE_PREFIX "%s", *name); } -static int do_mdio_entry(const char *filename, - void *symval, char *alias) +static void do_mdio_entry(struct module *mod, void *symval) { + char id[33]; int i; DEF_FIELD(symval, mdio_device_id, phy_id); DEF_FIELD(symval, mdio_device_id, phy_id_mask); - alias += sprintf(alias, MDIO_MODULE_PREFIX); - for (i = 0; i < 32; i++) { if (!((phy_id_mask >> (31-i)) & 1)) - *(alias++) = '?'; + id[i] = '?'; else if ((phy_id >> (31-i)) & 1) - *(alias++) = '1'; + id[i] = '1'; else - *(alias++) = '0'; + id[i] = '0'; } /* Terminate the string */ - *alias = 0; + id[32] = '\0'; - return 1; + module_alias_printf(mod, false, MDIO_MODULE_PREFIX "%s", id); } /* Looks like: zorro:iN. */ -static int do_zorro_entry(const char *filename, void *symval, - char *alias) +static void do_zorro_entry(struct module *mod, void *symval) { + char alias[256] = {}; DEF_FIELD(symval, zorro_device_id, id); - strcpy(alias, "zorro:"); + ADD(alias, "i", id != ZORRO_WILDCARD, id); - return 1; + + module_alias_printf(mod, false, "zorro:%s", alias); } /* looks like: "pnp:dD" */ -static int do_isapnp_entry(const char *filename, - void *symval, char *alias) +static void do_isapnp_entry(struct module *mod, void *symval) { DEF_FIELD(symval, isapnp_device_id, vendor); DEF_FIELD(symval, isapnp_device_id, function); - sprintf(alias, "pnp:d%c%c%c%x%x%x%x*", + module_alias_printf(mod, false, "pnp:d%c%c%c%x%x%x%x*", 'A' + ((vendor >> 2) & 0x3f) - 1, 'A' + (((vendor & 3) << 3) | ((vendor >> 13) & 7)) - 1, 'A' + ((vendor >> 8) & 0x1f) - 1, (function >> 4) & 0x0f, function & 0x0f, (function >> 12) & 0x0f, (function >> 8) & 0x0f); - return 1; } /* Looks like: "ipack:fNvNdN". */ -static int do_ipack_entry(const char *filename, - void *symval, char *alias) +static void do_ipack_entry(struct module *mod, void *symval) { + char alias[256] = {}; DEF_FIELD(symval, ipack_device_id, format); DEF_FIELD(symval, ipack_device_id, vendor); DEF_FIELD(symval, ipack_device_id, device); - strcpy(alias, "ipack:"); + ADD(alias, "f", format != IPACK_ANY_FORMAT, format); ADD(alias, "v", vendor != IPACK_ANY_ID, vendor); ADD(alias, "d", device != IPACK_ANY_ID, device); - add_wildcard(alias); - return 1; + + module_alias_printf(mod, true, "ipack:%s", alias); } /* @@ -1146,9 +1043,9 @@ static void append_nibble_mask(char **outp, * N is exactly 8 digits, where each is an upper-case hex digit, or * a ? or [] pattern matching exactly one digit. */ -static int do_amba_entry(const char *filename, - void *symval, char *alias) +static void do_amba_entry(struct module *mod, void *symval) { + char alias[256]; unsigned int digit; char *p = alias; DEF_FIELD(symval, amba_id, id); @@ -1156,15 +1053,14 @@ static int do_amba_entry(const char *filename, if ((id & mask) != id) fatal("%s: Masked-off bit(s) of AMBA device ID are non-zero: id=0x%08X, mask=0x%08X. Please fix this driver.\n", - filename, id, mask); + mod->name, id, mask); - p += sprintf(alias, "amba:d"); for (digit = 0; digit < 8; digit++) append_nibble_mask(&p, (id >> (4 * (7 - digit))) & 0xf, (mask >> (4 * (7 - digit))) & 0xf); - return 1; + module_alias_printf(mod, false, "amba:d%s", alias); } /* @@ -1173,13 +1069,11 @@ static int do_amba_entry(const char *filename, * N is exactly 2 digits, where each is an upper-case hex digit, or * a ? or [] pattern matching exactly one digit. */ -static int do_mips_cdmm_entry(const char *filename, - void *symval, char *alias) +static void do_mips_cdmm_entry(struct module *mod, void *symval) { DEF_FIELD(symval, mips_cdmm_device_id, type); - sprintf(alias, "mipscdmm:t%02X*", type); - return 1; + module_alias_printf(mod, false, "mipscdmm:t%02X*", type); } /* LOOKS like cpu:type:x86,venVVVVfamFFFFmodMMMM:feature:*,FEAT,* @@ -1188,137 +1082,130 @@ static int do_mips_cdmm_entry(const char *filename, * complicated. */ -static int do_x86cpu_entry(const char *filename, void *symval, - char *alias) +static void do_x86cpu_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, x86_cpu_id, feature); DEF_FIELD(symval, x86_cpu_id, family); DEF_FIELD(symval, x86_cpu_id, model); DEF_FIELD(symval, x86_cpu_id, vendor); - strcpy(alias, "cpu:type:x86,"); ADD(alias, "ven", vendor != X86_VENDOR_ANY, vendor); ADD(alias, "fam", family != X86_FAMILY_ANY, family); ADD(alias, "mod", model != X86_MODEL_ANY, model); strcat(alias, ":feature:*"); if (feature != X86_FEATURE_ANY) sprintf(alias + strlen(alias), "%04X*", feature); - return 1; + + module_alias_printf(mod, false, "cpu:type:x86,%s", alias); } /* LOOKS like cpu:type:*:feature:*FEAT* */ -static int do_cpu_entry(const char *filename, void *symval, char *alias) +static void do_cpu_entry(struct module *mod, void *symval) { DEF_FIELD(symval, cpu_feature, feature); - sprintf(alias, "cpu:type:*:feature:*%04X*", feature); - return 1; + module_alias_printf(mod, false, "cpu:type:*:feature:*%04X*", feature); } /* Looks like: mei:S:uuid:N:* */ -static int do_mei_entry(const char *filename, void *symval, - char *alias) +static void do_mei_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD_ADDR(symval, mei_cl_device_id, name); DEF_FIELD_ADDR(symval, mei_cl_device_id, uuid); DEF_FIELD(symval, mei_cl_device_id, version); - sprintf(alias, MEI_CL_MODULE_PREFIX); - sprintf(alias + strlen(alias), "%s:", (*name)[0] ? *name : "*"); add_uuid(alias, *uuid); ADD(alias, ":", version != MEI_CL_VERSION_ANY, version); - strcat(alias, ":*"); - - return 1; + module_alias_printf(mod, false, MEI_CL_MODULE_PREFIX "%s:%s:*", + (*name)[0] ? *name : "*", alias); } /* Looks like: rapidio:vNdNavNadN */ -static int do_rio_entry(const char *filename, - void *symval, char *alias) +static void do_rio_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, rio_device_id, did); DEF_FIELD(symval, rio_device_id, vid); DEF_FIELD(symval, rio_device_id, asm_did); DEF_FIELD(symval, rio_device_id, asm_vid); - strcpy(alias, "rapidio:"); ADD(alias, "v", vid != RIO_ANY_ID, vid); ADD(alias, "d", did != RIO_ANY_ID, did); ADD(alias, "av", asm_vid != RIO_ANY_ID, asm_vid); ADD(alias, "ad", asm_did != RIO_ANY_ID, asm_did); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "rapidio:%s", alias); } /* Looks like: ulpi:vNpN */ -static int do_ulpi_entry(const char *filename, void *symval, - char *alias) +static void do_ulpi_entry(struct module *mod, void *symval) { DEF_FIELD(symval, ulpi_device_id, vendor); DEF_FIELD(symval, ulpi_device_id, product); - sprintf(alias, "ulpi:v%04xp%04x", vendor, product); - - return 1; + module_alias_printf(mod, false, "ulpi:v%04xp%04x", vendor, product); } /* Looks like: hdaudio:vNrNaN */ -static int do_hda_entry(const char *filename, void *symval, char *alias) +static void do_hda_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, hda_device_id, vendor_id); DEF_FIELD(symval, hda_device_id, rev_id); DEF_FIELD(symval, hda_device_id, api_version); - strcpy(alias, "hdaudio:"); ADD(alias, "v", vendor_id != 0, vendor_id); ADD(alias, "r", rev_id != 0, rev_id); ADD(alias, "a", api_version != 0, api_version); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "hdaudio:%s", alias); } /* Looks like: sdw:mNpNvNcN */ -static int do_sdw_entry(const char *filename, void *symval, char *alias) +static void do_sdw_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, sdw_device_id, mfg_id); DEF_FIELD(symval, sdw_device_id, part_id); DEF_FIELD(symval, sdw_device_id, sdw_version); DEF_FIELD(symval, sdw_device_id, class_id); - strcpy(alias, "sdw:"); ADD(alias, "m", mfg_id != 0, mfg_id); ADD(alias, "p", part_id != 0, part_id); ADD(alias, "v", sdw_version != 0, sdw_version); ADD(alias, "c", class_id != 0, class_id); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "sdw:%s", alias); } /* Looks like: fsl-mc:vNdN */ -static int do_fsl_mc_entry(const char *filename, void *symval, - char *alias) +static void do_fsl_mc_entry(struct module *mod, void *symval) { DEF_FIELD(symval, fsl_mc_device_id, vendor); DEF_FIELD_ADDR(symval, fsl_mc_device_id, obj_type); - sprintf(alias, "fsl-mc:v%08Xd%s", vendor, *obj_type); - return 1; + module_alias_printf(mod, false, "fsl-mc:v%08Xd%s", vendor, *obj_type); } /* Looks like: tbsvc:kSpNvNrN */ -static int do_tbsvc_entry(const char *filename, void *symval, char *alias) +static void do_tbsvc_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, tb_service_id, match_flags); DEF_FIELD_ADDR(symval, tb_service_id, protocol_key); DEF_FIELD(symval, tb_service_id, protocol_id); DEF_FIELD(symval, tb_service_id, protocol_version); DEF_FIELD(symval, tb_service_id, protocol_revision); - strcpy(alias, "tbsvc:"); if (match_flags & TBSVC_MATCH_PROTOCOL_KEY) sprintf(alias + strlen(alias), "k%s", *protocol_key); else @@ -1329,93 +1216,75 @@ static int do_tbsvc_entry(const char *filename, void *symval, char *alias) ADD(alias, "r", match_flags & TBSVC_MATCH_PROTOCOL_REVISION, protocol_revision); - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "tbsvc:%s", alias); } -/* Looks like: typec:idNmN */ -static int do_typec_entry(const char *filename, void *symval, char *alias) +/* Looks like: typec:idN */ +static void do_typec_entry(struct module *mod, void *symval) { DEF_FIELD(symval, typec_device_id, svid); - DEF_FIELD(symval, typec_device_id, mode); - - sprintf(alias, "typec:id%04X", svid); - ADD(alias, "m", mode != TYPEC_ANY_MODE, mode); - return 1; + module_alias_printf(mod, false, "typec:id%04X", svid); } /* Looks like: tee:uuid */ -static int do_tee_entry(const char *filename, void *symval, char *alias) +static void do_tee_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, tee_client_device_id, uuid); - sprintf(alias, "tee:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + module_alias_printf(mod, true, + "tee:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid->b[0], uuid->b[1], uuid->b[2], uuid->b[3], uuid->b[4], uuid->b[5], uuid->b[6], uuid->b[7], uuid->b[8], uuid->b[9], uuid->b[10], uuid->b[11], uuid->b[12], uuid->b[13], uuid->b[14], uuid->b[15]); - - add_wildcard(alias); - return 1; } /* Looks like: wmi:guid */ -static int do_wmi_entry(const char *filename, void *symval, char *alias) +static void do_wmi_entry(struct module *mod, void *symval) { - int len; DEF_FIELD_ADDR(symval, wmi_device_id, guid_string); if (strlen(*guid_string) != UUID_STRING_LEN) { warn("Invalid WMI device id 'wmi:%s' in '%s'\n", - *guid_string, filename); - return 0; + *guid_string, mod->name); + return; } - len = snprintf(alias, ALIAS_SIZE, WMI_MODULE_PREFIX "%s", *guid_string); - if (len < 0 || len >= ALIAS_SIZE) { - warn("Could not generate all MODULE_ALIAS's in '%s'\n", - filename); - return 0; - } - return 1; + module_alias_printf(mod, false, WMI_MODULE_PREFIX "%s", *guid_string); } /* Looks like: mhi:S */ -static int do_mhi_entry(const char *filename, void *symval, char *alias) +static void do_mhi_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, mhi_device_id, chan); - sprintf(alias, MHI_DEVICE_MODALIAS_FMT, *chan); - return 1; + module_alias_printf(mod, false, MHI_DEVICE_MODALIAS_FMT, *chan); } /* Looks like: mhi_ep:S */ -static int do_mhi_ep_entry(const char *filename, void *symval, char *alias) +static void do_mhi_ep_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, mhi_device_id, chan); - sprintf(alias, MHI_EP_DEVICE_MODALIAS_FMT, *chan); - return 1; + module_alias_printf(mod, false, MHI_EP_DEVICE_MODALIAS_FMT, *chan); } /* Looks like: ishtp:{guid} */ -static int do_ishtp_entry(const char *filename, void *symval, char *alias) +static void do_ishtp_entry(struct module *mod, void *symval) { + char alias[256] = {}; DEF_FIELD_ADDR(symval, ishtp_device_id, guid); - strcpy(alias, ISHTP_MODULE_PREFIX "{"); add_guid(alias, *guid); - strcat(alias, "}"); - return 1; + module_alias_printf(mod, false, ISHTP_MODULE_PREFIX "{%s}", alias); } -static int do_auxiliary_entry(const char *filename, void *symval, char *alias) +static void do_auxiliary_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, auxiliary_device_id, name); - sprintf(alias, AUXILIARY_MODULE_PREFIX "%s", *name); - return 1; + module_alias_printf(mod, false, AUXILIARY_MODULE_PREFIX "%s", *name); } /* @@ -1423,8 +1292,10 @@ static int do_auxiliary_entry(const char *filename, void *symval, char *alias) * * N is exactly 2 digits, where each is an upper-case hex digit. */ -static int do_ssam_entry(const char *filename, void *symval, char *alias) +static void do_ssam_entry(struct module *mod, void *symval) { + char alias[256] = {}; + DEF_FIELD(symval, ssam_device_id, match_flags); DEF_FIELD(symval, ssam_device_id, domain); DEF_FIELD(symval, ssam_device_id, category); @@ -1432,30 +1303,28 @@ static int do_ssam_entry(const char *filename, void *symval, char *alias) DEF_FIELD(symval, ssam_device_id, instance); DEF_FIELD(symval, ssam_device_id, function); - sprintf(alias, "ssam:d%02Xc%02X", domain, category); ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); - return 1; + module_alias_printf(mod, false, "ssam:d%02Xc%02X%s", + domain, category, alias); } /* Looks like: dfl:tNfN */ -static int do_dfl_entry(const char *filename, void *symval, char *alias) +static void do_dfl_entry(struct module *mod, void *symval) { DEF_FIELD(symval, dfl_device_id, type); DEF_FIELD(symval, dfl_device_id, feature_id); - sprintf(alias, "dfl:t%04Xf%04X", type, feature_id); - - add_wildcard(alias); - return 1; + module_alias_printf(mod, true, "dfl:t%04Xf%04X", type, feature_id); } /* Looks like: cdx:vNdN */ -static int do_cdx_entry(const char *filename, void *symval, - char *alias) +static void do_cdx_entry(struct module *mod, void *symval) { + char alias[256]; + DEF_FIELD(symval, cdx_device_id, vendor); DEF_FIELD(symval, cdx_device_id, device); DEF_FIELD(symval, cdx_device_id, subvendor); @@ -1474,7 +1343,7 @@ static int do_cdx_entry(const char *filename, void *symval, default: warn("Unknown CDX driver_override alias %08X\n", override_only); - return 0; + return; } ADD(alias, "v", vendor != CDX_ANY_ID, vendor); @@ -1483,24 +1352,22 @@ static int do_cdx_entry(const char *filename, void *symval, ADD(alias, "sd", subdevice != CDX_ANY_ID, subdevice); ADD(alias, "c", class_mask == 0xFFFFFF, class); - return 1; + module_alias_printf(mod, false, "%s", alias); } -static int do_vchiq_entry(const char *filename, void *symval, char *alias) +static void do_vchiq_entry(struct module *mod, void *symval) { DEF_FIELD_ADDR(symval, vchiq_device_id, name); - sprintf(alias, "vchiq:%s", *name); - return 1; + module_alias_printf(mod, false, "vchiq:%s", *name); } /* Looks like: coreboot:tN */ -static int do_coreboot_entry(const char *filename, void *symval, char *alias) +static void do_coreboot_entry(struct module *mod, void *symval) { DEF_FIELD(symval, coreboot_device_id, tag); - sprintf(alias, "coreboot:t%08X", tag); - return 1; + module_alias_printf(mod, false, "coreboot:t%08X", tag); } /* Does namelen bytes of name exactly match the symbol? */ @@ -1512,25 +1379,34 @@ static bool sym_is(const char *name, unsigned namelen, const char *symbol) return memcmp(name, symbol, namelen) == 0; } -static void do_table(void *symval, unsigned long size, +static void do_table(const char *name, void *symval, unsigned long size, unsigned long id_size, const char *device_id, - int (*do_entry)(const char *filename, void *symval, char *alias), + void (*do_entry)(struct module *mod, void *symval), struct module *mod) { unsigned int i; - char alias[ALIAS_SIZE]; - device_id_check(mod->name, device_id, size, id_size, symval); - /* Leave last one: it's the terminator. */ - size -= id_size; + if (size % id_size || size < id_size) { + error("%s: type mismatch between %s[] and MODULE_DEVICE_TABLE(%s, ...)\n", + mod->name, name, device_id); + return; + } - for (i = 0; i < size; i += id_size) { - if (do_entry(mod->name, symval+i, alias)) { - buf_printf(&mod->dev_table_buf, - "MODULE_ALIAS(\"%s\");\n", alias); + /* Verify the last entry is a terminator */ + for (i = size - id_size; i < size; i++) { + if (*(uint8_t *)(symval + i)) { + error("%s: %s[] is not terminated with a NULL entry\n", + mod->name, name); + return; } } + + /* Leave last one: it's the terminator. */ + size -= id_size; + + for (i = 0; i < size; i += id_size) + do_entry(mod, symval + i); } static const struct devtable devtable[] = { @@ -1555,6 +1431,7 @@ static const struct devtable devtable[] = { {"rpmsg", SIZE_rpmsg_device_id, do_rpmsg_entry}, {"i2c", SIZE_i2c_device_id, do_i2c_entry}, {"i3c", SIZE_i3c_device_id, do_i3c_entry}, + {"slim", SIZE_slim_device_id, do_slim_entry}, {"spi", SIZE_spi_device_id, do_spi_entry}, {"dmi", SIZE_dmi_system_id, do_dmi_entry}, {"platform", SIZE_platform_device_id, do_platform_entry}, @@ -1585,6 +1462,10 @@ static const struct devtable devtable[] = { {"cdx", SIZE_cdx_device_id, do_cdx_entry}, {"vchiq", SIZE_vchiq_device_id, do_vchiq_entry}, {"coreboot", SIZE_coreboot_device_id, do_coreboot_entry}, + {"of", SIZE_of_device_id, do_of_entry}, + {"usb", SIZE_usb_device_id, do_usb_entry_multi}, + {"pnp", SIZE_pnp_device_id, do_pnp_device_entry}, + {"pnp_card", SIZE_pnp_card_device_id, do_pnp_card_entry}, }; /* Create MODULE_ALIAS() statements. @@ -1595,8 +1476,9 @@ void handle_moddevtable(struct module *mod, struct elf_info *info, { void *symval; char *zeros = NULL; - const char *name, *identifier; - unsigned int namelen; + const char *type, *name; + size_t typelen; + static const char *prefix = "__mod_device_table__"; /* We're looking for a section relative symbol */ if (!sym->st_shndx || get_secindex(info, sym) >= info->num_sections) @@ -1606,19 +1488,16 @@ void handle_moddevtable(struct module *mod, struct elf_info *info, if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT) return; - /* All our symbols are of form __mod_<name>__<identifier>_device_table. */ - if (strncmp(symname, "__mod_", strlen("__mod_"))) - return; - name = symname + strlen("__mod_"); - namelen = strlen(name); - if (namelen < strlen("_device_table")) - return; - if (strcmp(name + namelen - strlen("_device_table"), "_device_table")) + /* All our symbols are of form __mod_device_table__<type>__<name>. */ + if (!strstarts(symname, prefix)) return; - identifier = strstr(name, "__"); - if (!identifier) + type = symname + strlen(prefix); + + name = strstr(type, "__"); + if (!name) return; - namelen = identifier - name; + typelen = name - type; + name += strlen("__"); /* Handle all-NULL symbols allocated into .bss */ if (info->sechdrs[get_secindex(info, sym)].sh_type & SHT_NOBITS) { @@ -1628,35 +1507,15 @@ void handle_moddevtable(struct module *mod, struct elf_info *info, symval = sym_get_data(info, sym); } - /* First handle the "special" cases */ - if (sym_is(name, namelen, "usb")) - do_usb_table(symval, sym->st_size, mod); - else if (sym_is(name, namelen, "of")) - do_of_table(symval, sym->st_size, mod); - else if (sym_is(name, namelen, "pnp")) - do_pnp_device_entry(symval, sym->st_size, mod); - else if (sym_is(name, namelen, "pnp_card")) - do_pnp_card_entries(symval, sym->st_size, mod); - else { - int i; - - for (i = 0; i < ARRAY_SIZE(devtable); i++) { - const struct devtable *p = &devtable[i]; + for (int i = 0; i < ARRAY_SIZE(devtable); i++) { + const struct devtable *p = &devtable[i]; - if (sym_is(name, namelen, p->device_id)) { - do_table(symval, sym->st_size, p->id_size, - p->device_id, p->do_entry, mod); - break; - } + if (sym_is(type, typelen, p->device_id)) { + do_table(name, symval, sym->st_size, p->id_size, + p->device_id, p->do_entry, mod); + break; } } - free(zeros); -} -/* Now add out buffered information to the generated C source */ -void add_moddevtable(struct buffer *buf, struct module *mod) -{ - buf_printf(buf, "\n"); - buf_write(buf, mod->dev_table_buf.p, mod->dev_table_buf.pos); - free(mod->dev_table_buf.p); + free(zeros); } diff --git a/scripts/mod/list.h b/scripts/mod/list.h deleted file mode 100644 index a924a6c4aa4d..000000000000 --- a/scripts/mod/list.h +++ /dev/null @@ -1,213 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef LIST_H -#define LIST_H - -#include <stdbool.h> -#include <stddef.h> - -/* Are two types/vars the same type (ignoring qualifiers)? */ -#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) - -/** - * container_of - cast a member of a structure out to the containing structure - * @ptr: the pointer to the member. - * @type: the type of the container struct this is embedded in. - * @member: the name of the member within the struct. - * - */ -#define container_of(ptr, type, member) ({ \ - void *__mptr = (void *)(ptr); \ - _Static_assert(__same_type(*(ptr), ((type *)0)->member) || \ - __same_type(*(ptr), void), \ - "pointer type mismatch in container_of()"); \ - ((type *)(__mptr - offsetof(type, member))); }) - -#define LIST_POISON1 ((void *) 0x100) -#define LIST_POISON2 ((void *) 0x122) - -/* - * Circular doubly linked list implementation. - * - * Some of the internal functions ("__xxx") are useful when - * manipulating whole lists rather than single entries, as - * sometimes we already know the next/prev entries and we can - * generate better code by using them directly rather than - * using the generic single-entry routines. - */ - -struct list_head { - struct list_head *next, *prev; -}; - -#define LIST_HEAD_INIT(name) { &(name), &(name) } - -#define LIST_HEAD(name) \ - struct list_head name = LIST_HEAD_INIT(name) - -/** - * INIT_LIST_HEAD - Initialize a list_head structure - * @list: list_head structure to be initialized. - * - * Initializes the list_head to point to itself. If it is a list header, - * the result is an empty list. - */ -static inline void INIT_LIST_HEAD(struct list_head *list) -{ - list->next = list; - list->prev = list; -} - -/* - * Insert a new entry between two known consecutive entries. - * - * This is only for internal list manipulation where we know - * the prev/next entries already! - */ -static inline void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next) -{ - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; -} - -/** - * list_add - add a new entry - * @new: new entry to be added - * @head: list head to add it after - * - * Insert a new entry after the specified head. - * This is good for implementing stacks. - */ -static inline void list_add(struct list_head *new, struct list_head *head) -{ - __list_add(new, head, head->next); -} - -/** - * list_add_tail - add a new entry - * @new: new entry to be added - * @head: list head to add it before - * - * Insert a new entry before the specified head. - * This is useful for implementing queues. - */ -static inline void list_add_tail(struct list_head *new, struct list_head *head) -{ - __list_add(new, head->prev, head); -} - -/* - * Delete a list entry by making the prev/next entries - * point to each other. - * - * This is only for internal list manipulation where we know - * the prev/next entries already! - */ -static inline void __list_del(struct list_head *prev, struct list_head *next) -{ - next->prev = prev; - prev->next = next; -} - -static inline void __list_del_entry(struct list_head *entry) -{ - __list_del(entry->prev, entry->next); -} - -/** - * list_del - deletes entry from list. - * @entry: the element to delete from the list. - * Note: list_empty() on entry does not return true after this, the entry is - * in an undefined state. - */ -static inline void list_del(struct list_head *entry) -{ - __list_del_entry(entry); - entry->next = LIST_POISON1; - entry->prev = LIST_POISON2; -} - -/** - * list_is_head - tests whether @list is the list @head - * @list: the entry to test - * @head: the head of the list - */ -static inline int list_is_head(const struct list_head *list, const struct list_head *head) -{ - return list == head; -} - -/** - * list_empty - tests whether a list is empty - * @head: the list to test. - */ -static inline int list_empty(const struct list_head *head) -{ - return head->next == head; -} - -/** - * list_entry - get the struct for this entry - * @ptr: the &struct list_head pointer. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_head within the struct. - */ -#define list_entry(ptr, type, member) \ - container_of(ptr, type, member) - -/** - * list_first_entry - get the first element from a list - * @ptr: the list head to take the element from. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_head within the struct. - * - * Note, that list is expected to be not empty. - */ -#define list_first_entry(ptr, type, member) \ - list_entry((ptr)->next, type, member) - -/** - * list_next_entry - get the next element in list - * @pos: the type * to cursor - * @member: the name of the list_head within the struct. - */ -#define list_next_entry(pos, member) \ - list_entry((pos)->member.next, typeof(*(pos)), member) - -/** - * list_entry_is_head - test if the entry points to the head of the list - * @pos: the type * to cursor - * @head: the head for your list. - * @member: the name of the list_head within the struct. - */ -#define list_entry_is_head(pos, head, member) \ - (&pos->member == (head)) - -/** - * list_for_each_entry - iterate over list of given type - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - */ -#define list_for_each_entry(pos, head, member) \ - for (pos = list_first_entry(head, typeof(*pos), member); \ - !list_entry_is_head(pos, head, member); \ - pos = list_next_entry(pos, member)) - -/** - * list_for_each_entry_safe - iterate over list of given type. Safe against removal of list entry - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_head within the struct. - */ -#define list_for_each_entry_safe(pos, n, head, member) \ - for (pos = list_first_entry(head, typeof(*pos), member), \ - n = list_next_entry(pos, member); \ - !list_entry_is_head(pos, head, member); \ - pos = n, n = list_next_entry(n, member)) - -#endif /* LIST_H */ diff --git a/scripts/mod/mk_elfconfig.c b/scripts/mod/mk_elfconfig.c index 680eade89be1..e8cee4e4bc73 100644 --- a/scripts/mod/mk_elfconfig.c +++ b/scripts/mod/mk_elfconfig.c @@ -8,7 +8,6 @@ int main(int argc, char **argv) { unsigned char ei[EI_NIDENT]; - union { short s; char c[2]; } endian_test; if (fread(ei, 1, EI_NIDENT, stdin) != EI_NIDENT) { fprintf(stderr, "Error: input truncated\n"); @@ -28,30 +27,6 @@ main(int argc, char **argv) default: exit(1); } - switch (ei[EI_DATA]) { - case ELFDATA2LSB: - printf("#define KERNEL_ELFDATA ELFDATA2LSB\n"); - break; - case ELFDATA2MSB: - printf("#define KERNEL_ELFDATA ELFDATA2MSB\n"); - break; - default: - exit(1); - } - - if (sizeof(unsigned long) == 4) { - printf("#define HOST_ELFCLASS ELFCLASS32\n"); - } else if (sizeof(unsigned long) == 8) { - printf("#define HOST_ELFCLASS ELFCLASS64\n"); - } - - endian_test.s = 0x0102; - if (memcmp(endian_test.c, "\x01\x02", 2) == 0) - printf("#define HOST_ELFDATA ELFDATA2MSB\n"); - else if (memcmp(endian_test.c, "\x02\x01", 2) == 0) - printf("#define HOST_ELFDATA ELFDATA2LSB\n"); - else - exit(1); return 0; } diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c index 6b37039c9e92..5ca7c268294e 100644 --- a/scripts/mod/modpost.c +++ b/scripts/mod/modpost.c @@ -20,14 +20,25 @@ #include <limits.h> #include <stdbool.h> #include <errno.h> + +#include <hash.h> +#include <hashtable.h> +#include <list.h> +#include <xalloc.h> #include "modpost.h" #include "../../include/linux/license.h" +#define MODULE_NS_PREFIX "module:" + static bool module_enabled; /* Are we using CONFIG_MODVERSIONS? */ static bool modversions; /* Is CONFIG_MODULE_SRCVERSION_ALL set? */ static bool all_versions; +/* Is CONFIG_BASIC_MODVERSIONS set? */ +static bool basic_modversions; +/* Is CONFIG_EXTENDED_MODVERSIONS set? */ +static bool extended_modversions; /* If we are modposting external module set to 1 */ static bool external_module; /* Only warn about unresolved symbols */ @@ -47,6 +58,9 @@ static bool error_occurred; static bool extra_warn; +bool target_is_big_endian; +bool host_is_big_endian; + /* * Cut off the warnings when there are too many. This typically occurs when * vmlinux is missing. ('make modules' without building vmlinux.) @@ -60,20 +74,15 @@ static unsigned int nr_unresolved; #define MODULE_NAME_LEN (64 - sizeof(Elf_Addr)) -void modpost_log(enum loglevel loglevel, const char *fmt, ...) +void modpost_log(bool is_error, const char *fmt, ...) { va_list arglist; - switch (loglevel) { - case LOG_WARN: - fprintf(stderr, "WARNING: "); - break; - case LOG_ERROR: + if (is_error) { fprintf(stderr, "ERROR: "); error_occurred = true; - break; - default: /* invalid loglevel, ignore */ - break; + } else { + fprintf(stderr, "WARNING: "); } fprintf(stderr, "modpost: "); @@ -91,12 +100,16 @@ static inline bool strends(const char *str, const char *postfix) return strcmp(str + strlen(str) - strlen(postfix), postfix) == 0; } -void *do_nofail(void *ptr, const char *expr) +/** + * get_basename - return the last part of a pathname. + * + * @path: path to extract the filename from. + */ +const char *get_basename(const char *path) { - if (!ptr) - fatal("Memory allocation failure: %s.\n", expr); + const char *tail = strrchr(path, '/'); - return ptr; + return tail ? tail + 1 : path; } char *read_text_file(const char *filename) @@ -117,7 +130,7 @@ char *read_text_file(const char *filename) exit(1); } - buf = NOFAIL(malloc(st.st_size + 1)); + buf = xmalloc(st.st_size + 1); nbytes = st.st_size; @@ -160,12 +173,13 @@ char *get_line(char **stringp) /* A list of all modules we processed */ LIST_HEAD(modules); -static struct module *find_module(const char *modname) +static struct module *find_module(const char *filename, const char *modname) { struct module *mod; list_for_each_entry(mod, &modules, list) { - if (strcmp(mod->name, modname) == 0) + if (!strcmp(mod->dump_file, filename) && + !strcmp(mod->name, modname)) return mod; } return NULL; @@ -175,13 +189,14 @@ static struct module *new_module(const char *name, size_t namelen) { struct module *mod; - mod = NOFAIL(malloc(sizeof(*mod) + namelen + 1)); + mod = xmalloc(sizeof(*mod) + namelen + 1); memset(mod, 0, sizeof(*mod)); INIT_LIST_HEAD(&mod->exported_symbols); INIT_LIST_HEAD(&mod->unresolved_symbols); INIT_LIST_HEAD(&mod->missing_namespaces); INIT_LIST_HEAD(&mod->imported_namespaces); + INIT_LIST_HEAD(&mod->aliases); memcpy(mod->name, name, namelen); mod->name[namelen] = '\0'; @@ -189,8 +204,8 @@ static struct module *new_module(const char *name, size_t namelen) /* * Set mod->is_gpl_compatible to true by default. If MODULE_LICENSE() - * is missing, do not check the use for EXPORT_SYMBOL_GPL() becasue - * modpost will exit wiht error anyway. + * is missing, do not check the use for EXPORT_SYMBOL_GPL() because + * modpost will exit with an error anyway. */ mod->is_gpl_compatible = true; @@ -199,13 +214,8 @@ static struct module *new_module(const char *name, size_t namelen) return mod; } -/* A hash of all exported symbols, - * struct symbol is also used for lists of unresolved symbols */ - -#define SYMBOL_HASH_SIZE 1024 - struct symbol { - struct symbol *next; + struct hlist_node hnode;/* link to hash table */ struct list_head list; /* link to module::exported_symbols or module::unresolved_symbols */ struct module *module; char *namespace; @@ -218,20 +228,7 @@ struct symbol { char name[]; }; -static struct symbol *symbolhash[SYMBOL_HASH_SIZE]; - -/* This is based on the hash algorithm from gdbm, via tdb */ -static inline unsigned int tdb_hash(const char *name) -{ - unsigned value; /* Used to compute the hash value. */ - unsigned i; /* Used to cycle through random values. */ - - /* Set the initial value from the key size. */ - for (value = 0x238F13AF * strlen(name), i = 0; name[i]; i++) - value = (value + (((unsigned char *)name)[i] << (i*5 % 24))); - - return (1103515243 * value + 12345); -} +static HASHTABLE_DEFINE(symbol_hashtable, 1U << 10); /** * Allocate a new symbols for use in the hash of exported symbols or @@ -239,7 +236,7 @@ static inline unsigned int tdb_hash(const char *name) **/ static struct symbol *alloc_symbol(const char *name) { - struct symbol *s = NOFAIL(malloc(sizeof(*s) + strlen(name) + 1)); + struct symbol *s = xmalloc(sizeof(*s) + strlen(name) + 1); memset(s, 0, sizeof(*s)); strcpy(s->name, name); @@ -250,11 +247,7 @@ static struct symbol *alloc_symbol(const char *name) /* For the hash of exported symbols */ static void hash_add_symbol(struct symbol *sym) { - unsigned int hash; - - hash = tdb_hash(sym->name) % SYMBOL_HASH_SIZE; - sym->next = symbolhash[hash]; - symbolhash[hash] = sym; + hash_add(symbol_hashtable, &sym->hnode, hash_str(sym->name)); } static void sym_add_unresolved(const char *name, struct module *mod, bool weak) @@ -275,7 +268,7 @@ static struct symbol *sym_find_with_module(const char *name, struct module *mod) if (name[0] == '.') name++; - for (s = symbolhash[tdb_hash(name) % SYMBOL_HASH_SIZE]; s; s = s->next) { + hash_for_each_possible(symbol_hashtable, s, hnode, hash_str(name)) { if (strcmp(s->name, name) == 0 && (!mod || s->module == mod)) return s; } @@ -316,8 +309,7 @@ static void add_namespace(struct list_head *head, const char *namespace) struct namespace_list *ns_entry; if (!contains_namespace(head, namespace)) { - ns_entry = NOFAIL(malloc(sizeof(*ns_entry) + - strlen(namespace) + 1)); + ns_entry = xmalloc(sizeof(*ns_entry) + strlen(namespace) + 1); strcpy(ns_entry->namespace, namespace); list_add_tail(&ns_entry->list, head); } @@ -356,8 +348,6 @@ static const char *sec_name(const struct elf_info *info, unsigned int secindex) return sech_name(info, &info->sechdrs[secindex]); } -#define strstarts(str, prefix) (strncmp(str, prefix, strlen(prefix)) == 0) - static struct symbol *sym_add_exported(const char *name, struct module *mod, bool gpl_only, const char *namespace) { @@ -372,7 +362,7 @@ static struct symbol *sym_add_exported(const char *name, struct module *mod, s = alloc_symbol(name); s->module = mod; s->is_gpl_only = gpl_only; - s->namespace = NOFAIL(strdup(namespace)); + s->namespace = xstrdup(namespace); list_add_tail(&s->list, &mod->exported_symbols); hash_add_symbol(s); @@ -444,6 +434,18 @@ static int parse_elf(struct elf_info *info, const char *filename) /* Not an ELF file - silently ignore it */ return 0; } + + switch (hdr->e_ident[EI_DATA]) { + case ELFDATA2LSB: + target_is_big_endian = false; + break; + case ELFDATA2MSB: + target_is_big_endian = true; + break; + default: + fatal("target endian is unknown\n"); + } + /* Fix endianness in ELF header */ hdr->e_type = TO_NATIVE(hdr->e_type); hdr->e_machine = TO_NATIVE(hdr->e_machine); @@ -519,6 +521,9 @@ static int parse_elf(struct elf_info *info, const char *filename) info->modinfo_len = sechdrs[i].sh_size; } else if (!strcmp(secname, ".export_symbol")) { info->export_symbol_secndx = i; + } else if (!strcmp(secname, ".no_trim_symbol")) { + info->no_trim_symbol = (void *)hdr + sechdrs[i].sh_offset; + info->no_trim_symbol_len = sechdrs[i].sh_size; } if (sechdrs[i].sh_type == SHT_SYMTAB) { @@ -601,11 +606,6 @@ static int ignore_undef_symbol(struct elf_info *info, const char *symname) strstarts(symname, "_savevr_") || strcmp(symname, ".TOC.") == 0) return 1; - - if (info->hdr->e_machine == EM_S390) - /* Expoline thunks are linked on all kernel modules during final link of .ko */ - if (strstarts(symname, "__s390_indirect_jump_r")) - return 1; /* Do not ignore this symbol */ return 0; } @@ -633,7 +633,7 @@ static void handle_symbol(struct module *mod, struct elf_info *info, if (ELF_ST_TYPE(sym->st_info) == STT_SPARC_REGISTER) break; if (symname[0] == '.') { - char *munged = NOFAIL(strdup(symname)); + char *munged = xstrdup(symname); munged[0] = '_'; munged[1] = toupper(munged[1]); symname = munged; @@ -701,10 +701,7 @@ static char *get_modinfo(struct elf_info *info, const char *tag) static const char *sym_name(struct elf_info *elf, Elf_Sym *sym) { - if (sym) - return elf->strtab + sym->st_name; - else - return "(unknown)"; + return sym ? elf->strtab + sym->st_name : ""; } /* @@ -781,17 +778,14 @@ static void check_section(const char *modname, struct elf_info *elf, #define ALL_INIT_DATA_SECTIONS \ - ".init.setup", ".init.rodata", ".meminit.rodata", \ - ".init.data", ".meminit.data" + ".init.setup", ".init.rodata", ".init.data" #define ALL_PCI_INIT_SECTIONS \ ".pci_fixup_early", ".pci_fixup_header", ".pci_fixup_final", \ ".pci_fixup_enable", ".pci_fixup_resume", \ ".pci_fixup_resume_early", ".pci_fixup_suspend" -#define ALL_XXXINIT_SECTIONS ".meminit.*" - -#define ALL_INIT_SECTIONS INIT_SECTIONS, ALL_XXXINIT_SECTIONS +#define ALL_INIT_SECTIONS ".init.*" #define ALL_EXIT_SECTIONS ".exit.*" #define DATA_SECTIONS ".data", ".data.rel" @@ -800,11 +794,9 @@ static void check_section(const char *modname, struct elf_info *elf, ".ltext", ".ltext.*" #define OTHER_TEXT_SECTIONS ".ref.text", ".head.text", ".spinlock.text", \ ".fixup", ".entry.text", ".exception.text", \ - ".coldtext", ".softirqentry.text" - -#define INIT_SECTIONS ".init.*" + ".coldtext", ".softirqentry.text", ".irqentry.text" -#define ALL_TEXT_SECTIONS ".init.text", ".meminit.text", ".exit.text", \ +#define ALL_TEXT_SECTIONS ".init.text", ".exit.text", \ TEXT_SECTIONS, OTHER_TEXT_SECTIONS enum mismatch { @@ -844,12 +836,6 @@ static const struct sectioncheck sectioncheck[] = { .bad_tosec = { ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS, NULL }, .mismatch = TEXTDATA_TO_ANY_INIT_EXIT, }, -/* Do not reference init code/data from meminit code/data */ -{ - .fromsec = { ALL_XXXINIT_SECTIONS, NULL }, - .bad_tosec = { INIT_SECTIONS, NULL }, - .mismatch = XXXINIT_TO_SOME_INIT, -}, /* Do not use exit code/data from init code */ { .fromsec = { ALL_INIT_SECTIONS, NULL }, @@ -864,7 +850,7 @@ static const struct sectioncheck sectioncheck[] = { }, { .fromsec = { ALL_PCI_INIT_SECTIONS, NULL }, - .bad_tosec = { INIT_SECTIONS, NULL }, + .bad_tosec = { ALL_INIT_SECTIONS, NULL }, .mismatch = ANY_INIT_TO_ANY_EXIT, }, { @@ -970,17 +956,6 @@ static int secref_whitelist(const char *fromsec, const char *fromsym, match(fromsym, PATTERNS("*_ops", "*_probe", "*_console"))) return 0; - /* - * symbols in data sections must not refer to .exit.*, but there are - * quite a few offenders, so hide these unless for W=1 builds until - * these are fixed. - */ - if (!extra_warn && - match(fromsec, PATTERNS(DATA_SECTIONS)) && - match(tosec, PATTERNS(ALL_EXIT_SECTIONS)) && - match(fromsym, PATTERNS("*driver"))) - return 0; - /* Check for pattern 3 */ if (strstarts(fromsec, ".head.text") && match(tosec, PATTERNS(ALL_INIT_SECTIONS))) @@ -1007,6 +982,8 @@ static Elf_Sym *find_fromsym(struct elf_info *elf, Elf_Addr addr, static Elf_Sym *find_tosym(struct elf_info *elf, Elf_Addr addr, Elf_Sym *sym) { + Elf_Sym *new_sym; + /* If the supplied symbol has a valid name, return it */ if (is_valid_name(elf, sym)) return sym; @@ -1015,8 +992,9 @@ static Elf_Sym *find_tosym(struct elf_info *elf, Elf_Addr addr, Elf_Sym *sym) * Strive to find a better symbol name, but the resulting name may not * match the symbol referenced in the original code. */ - return symsearch_find_nearest(elf, addr, get_secindex(elf, sym), - true, 20); + new_sym = symsearch_find_nearest(elf, addr, get_secindex(elf, sym), + true, 20); + return new_sym ? new_sym : sym; } static bool is_executable_section(struct elf_info *elf, unsigned int secndx) @@ -1036,6 +1014,7 @@ static void default_mismatch_handler(const char *modname, struct elf_info *elf, Elf_Sym *from; const char *tosym; const char *fromsym; + char taddr_str[16]; from = find_fromsym(elf, faddr, fsecndx); fromsym = sym_name(elf, from); @@ -1049,10 +1028,17 @@ static void default_mismatch_handler(const char *modname, struct elf_info *elf, sec_mismatch_count++; - warn("%s: section mismatch in reference: %s+0x%x (section: %s) -> %s (section: %s)\n", - modname, fromsym, - (unsigned int)(faddr - (from ? from->st_value : 0)), - fromsec, tosym, tosec); + if (!tosym[0]) + snprintf(taddr_str, sizeof(taddr_str), "0x%x", (unsigned int)taddr); + + /* + * The format for the reference source: <symbol_name>+<offset> or <address> + * The format for the reference destination: <symbol_name> or <address> + */ + warn("%s: section mismatch in reference: %s%s0x%x (section: %s) -> %s (section: %s)\n", + modname, fromsym, fromsym[0] ? "+" : "", + (unsigned int)(faddr - (fromsym[0] ? from->st_value : 0)), + fromsec, tosym[0] ? tosym : taddr_str, tosec); if (mismatch->mismatch == EXTABLE_TO_NON_TEXT) { if (match(tosec, mismatch->bad_tosec)) @@ -1173,48 +1159,14 @@ static Elf_Addr addend_386_rel(uint32_t *location, unsigned int r_type) { switch (r_type) { case R_386_32: - return TO_NATIVE(*location); + return get_unaligned_native(location); case R_386_PC32: - return TO_NATIVE(*location) + 4; + return get_unaligned_native(location) + 4; } return (Elf_Addr)(-1); } -#ifndef R_ARM_CALL -#define R_ARM_CALL 28 -#endif -#ifndef R_ARM_JUMP24 -#define R_ARM_JUMP24 29 -#endif - -#ifndef R_ARM_THM_CALL -#define R_ARM_THM_CALL 10 -#endif -#ifndef R_ARM_THM_JUMP24 -#define R_ARM_THM_JUMP24 30 -#endif - -#ifndef R_ARM_MOVW_ABS_NC -#define R_ARM_MOVW_ABS_NC 43 -#endif - -#ifndef R_ARM_MOVT_ABS -#define R_ARM_MOVT_ABS 44 -#endif - -#ifndef R_ARM_THM_MOVW_ABS_NC -#define R_ARM_THM_MOVW_ABS_NC 47 -#endif - -#ifndef R_ARM_THM_MOVT_ABS -#define R_ARM_THM_MOVT_ABS 48 -#endif - -#ifndef R_ARM_THM_JUMP19 -#define R_ARM_THM_JUMP19 51 -#endif - static int32_t sign_extend32(int32_t value, int index) { uint8_t shift = 31 - index; @@ -1230,24 +1182,24 @@ static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type) switch (r_type) { case R_ARM_ABS32: case R_ARM_REL32: - inst = TO_NATIVE(*(uint32_t *)loc); + inst = get_unaligned_native((uint32_t *)loc); return inst + sym->st_value; case R_ARM_MOVW_ABS_NC: case R_ARM_MOVT_ABS: - inst = TO_NATIVE(*(uint32_t *)loc); + inst = get_unaligned_native((uint32_t *)loc); offset = sign_extend32(((inst & 0xf0000) >> 4) | (inst & 0xfff), 15); return offset + sym->st_value; case R_ARM_PC24: case R_ARM_CALL: case R_ARM_JUMP24: - inst = TO_NATIVE(*(uint32_t *)loc); + inst = get_unaligned_native((uint32_t *)loc); offset = sign_extend32((inst & 0x00ffffff) << 2, 25); return offset + sym->st_value + 8; case R_ARM_THM_MOVW_ABS_NC: case R_ARM_THM_MOVT_ABS: - upper = TO_NATIVE(*(uint16_t *)loc); - lower = TO_NATIVE(*((uint16_t *)loc + 1)); + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); offset = sign_extend32(((upper & 0x000f) << 12) | ((upper & 0x0400) << 1) | ((lower & 0x7000) >> 4) | @@ -1264,8 +1216,8 @@ static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type) * imm11 = lower[10:0] * imm32 = SignExtend(S:J2:J1:imm6:imm11:'0') */ - upper = TO_NATIVE(*(uint16_t *)loc); - lower = TO_NATIVE(*((uint16_t *)loc + 1)); + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); sign = (upper >> 10) & 1; j1 = (lower >> 13) & 1; @@ -1275,7 +1227,7 @@ static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type) ((lower & 0x07ff) << 1), 20); return offset + sym->st_value + 4; - case R_ARM_THM_CALL: + case R_ARM_THM_PC22: case R_ARM_THM_JUMP24: /* * Encoding T4: @@ -1288,8 +1240,8 @@ static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type) * I2 = NOT(J2 XOR S) * imm32 = SignExtend(S:I1:I2:imm10:imm11:'0') */ - upper = TO_NATIVE(*(uint16_t *)loc); - lower = TO_NATIVE(*((uint16_t *)loc + 1)); + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); sign = (upper >> 10) & 1; j1 = (lower >> 13) & 1; @@ -1310,7 +1262,7 @@ static Elf_Addr addend_mips_rel(uint32_t *location, unsigned int r_type) { uint32_t inst; - inst = TO_NATIVE(*location); + inst = get_unaligned_native(location); switch (r_type) { case R_MIPS_LO16: return inst & 0xffff; @@ -1523,14 +1475,8 @@ static void extract_crcs_for_object(const char *object, struct module *mod) const char *base; int dirlen, ret; - base = strrchr(object, '/'); - if (base) { - base++; - dirlen = base - object; - } else { - dirlen = 0; - base = object; - } + base = get_basename(object); + dirlen = base - object; ret = snprintf(cmd_file, sizeof(cmd_file), "%.*s.%s.cmd", dirlen, object, base); @@ -1631,6 +1577,14 @@ static void read_symbols(const char *modname) /* strip trailing .o */ mod = new_module(modname, strlen(modname) - strlen(".o")); + /* save .no_trim_symbol section for later use */ + if (info.no_trim_symbol_len) { + mod->no_trim_symbol = xmalloc(info.no_trim_symbol_len); + memcpy(mod->no_trim_symbol, info.no_trim_symbol, + info.no_trim_symbol_len); + mod->no_trim_symbol_len = info.no_trim_symbol_len; + } + if (!mod->is_vmlinux) { license = get_modinfo(&info, "license"); if (!license) @@ -1643,16 +1597,20 @@ static void read_symbols(const char *modname) license = get_next_modinfo(&info, "license", license); } - namespace = get_modinfo(&info, "import_ns"); - while (namespace) { + for (namespace = get_modinfo(&info, "import_ns"); + namespace; + namespace = get_next_modinfo(&info, "import_ns", namespace)) { + if (strstarts(namespace, MODULE_NS_PREFIX)) + error("%s: explicitly importing namespace \"%s\" is not allowed.\n", + mod->name, namespace); + add_namespace(&mod->imported_namespaces, namespace); - namespace = get_next_modinfo(&info, "import_ns", - namespace); } + + if (!get_modinfo(&info, "description")) + warn("missing MODULE_DESCRIPTION() in %s\n", modname); } - if (extra_warn && !get_modinfo(&info, "description")) - warn("missing MODULE_DESCRIPTION() in %s\n", modname); for (sym = info.symtab_start; sym < info.symtab_stop; sym++) { symname = remove_dot(info.strtab + sym->st_name); @@ -1725,12 +1683,52 @@ void buf_write(struct buffer *buf, const char *s, int len) { if (buf->size - buf->pos < len) { buf->size += len + SZ; - buf->p = NOFAIL(realloc(buf->p, buf->size)); + buf->p = xrealloc(buf->p, buf->size); } strncpy(buf->p + buf->pos, s, len); buf->pos += len; } +/** + * verify_module_namespace() - does @modname have access to this symbol's @namespace + * @namespace: export symbol namespace + * @modname: module name + * + * If @namespace is prefixed with "module:" to indicate it is a module namespace + * then test if @modname matches any of the comma separated patterns. + * + * The patterns only support tail-glob. + */ +static bool verify_module_namespace(const char *namespace, const char *modname) +{ + size_t len, modlen = strlen(modname); + const char *prefix = "module:"; + const char *sep; + bool glob; + + if (!strstarts(namespace, prefix)) + return false; + + for (namespace += strlen(prefix); *namespace; namespace = sep) { + sep = strchrnul(namespace, ','); + len = sep - namespace; + + glob = false; + if (sep[-1] == '*') { + len--; + glob = true; + } + + if (*sep) + sep++; + + if (strncmp(namespace, modname, len) == 0 && (glob || len == modlen)) + return true; + } + + return false; +} + static void check_exports(struct module *mod) { struct symbol *s, *exp; @@ -1740,7 +1738,7 @@ static void check_exports(struct module *mod) exp = find_symbol(s->name); if (!exp) { if (!s->weak && nr_unresolved++ < MAX_UNRESOLVED_REPORTS) - modpost_log(warn_unresolved ? LOG_WARN : LOG_ERROR, + modpost_log(!warn_unresolved, "\"%s\" [%s.ko] undefined!\n", s->name, mod->name); continue; @@ -1756,14 +1754,11 @@ static void check_exports(struct module *mod) s->crc_valid = exp->crc_valid; s->crc = exp->crc; - basename = strrchr(mod->name, '/'); - if (basename) - basename++; - else - basename = mod->name; + basename = get_basename(mod->name); - if (!contains_namespace(&mod->imported_namespaces, exp->namespace)) { - modpost_log(allow_missing_ns_imports ? LOG_WARN : LOG_ERROR, + if (!verify_module_namespace(exp->namespace, basename) && + !contains_namespace(&mod->imported_namespaces, exp->namespace)) { + modpost_log(!allow_missing_ns_imports, "module %s uses symbol %s from namespace %s, but does not import it.\n", basename, exp->name, exp->namespace); add_namespace(&mod->missing_namespaces, exp->namespace); @@ -1792,15 +1787,34 @@ static void handle_white_list_exports(const char *white_list) free(buf); } +/* + * Keep symbols recorded in the .no_trim_symbol section. This is necessary to + * prevent CONFIG_TRIM_UNUSED_KSYMS from dropping EXPORT_SYMBOL because + * symbol_get() relies on the symbol being present in the ksymtab for lookups. + */ +static void keep_no_trim_symbols(struct module *mod) +{ + unsigned long size = mod->no_trim_symbol_len; + + for (char *s = mod->no_trim_symbol; s; s = next_string(s , &size)) { + struct symbol *sym; + + /* + * If find_symbol() returns NULL, this symbol is not provided + * by any module, and symbol_get() will fail. + */ + sym = find_symbol(s); + if (sym) + sym->used = true; + } +} + static void check_modname_len(struct module *mod) { const char *mod_name; - mod_name = strrchr(mod->name, '/'); - if (mod_name == NULL) - mod_name = mod->name; - else - mod_name++; + mod_name = get_basename(mod->name); + if (strlen(mod_name) >= MODULE_NAME_LEN) error("module name is too long [%s.ko]\n", mod->name); } @@ -1811,26 +1825,9 @@ static void check_modname_len(struct module *mod) static void add_header(struct buffer *b, struct module *mod) { buf_printf(b, "#include <linux/module.h>\n"); - /* - * Include build-salt.h after module.h in order to - * inherit the definitions. - */ - buf_printf(b, "#define INCLUDE_VERMAGIC\n"); - buf_printf(b, "#include <linux/build-salt.h>\n"); - buf_printf(b, "#include <linux/elfnote-lto.h>\n"); buf_printf(b, "#include <linux/export-internal.h>\n"); - buf_printf(b, "#include <linux/vermagic.h>\n"); buf_printf(b, "#include <linux/compiler.h>\n"); buf_printf(b, "\n"); - buf_printf(b, "#ifdef CONFIG_UNWINDER_ORC\n"); - buf_printf(b, "#include <asm/orc_header.h>\n"); - buf_printf(b, "ORC_HEADER;\n"); - buf_printf(b, "#endif\n"); - buf_printf(b, "\n"); - buf_printf(b, "BUILD_SALT;\n"); - buf_printf(b, "BUILD_LTO_INFO;\n"); - buf_printf(b, "\n"); - buf_printf(b, "MODULE_INFO(vermagic, VERMAGIC_STRING);\n"); buf_printf(b, "MODULE_INFO(name, KBUILD_MODNAME);\n"); buf_printf(b, "\n"); buf_printf(b, "__visible struct module __this_module\n"); @@ -1848,12 +1845,6 @@ static void add_header(struct buffer *b, struct module *mod) if (!external_module) buf_printf(b, "\nMODULE_INFO(intree, \"Y\");\n"); - buf_printf(b, - "\n" - "#ifdef CONFIG_MITIGATION_RETPOLINE\n" - "MODULE_INFO(retpoline, \"Y\");\n" - "#endif\n"); - if (strstarts(mod->name, "drivers/staging")) buf_printf(b, "\nMODULE_INFO(staging, \"Y\");\n"); @@ -1897,13 +1888,56 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod) } /** + * Record CRCs for unresolved symbols, supporting long names + */ +static void add_extended_versions(struct buffer *b, struct module *mod) +{ + struct symbol *s; + + if (!extended_modversions) + return; + + buf_printf(b, "\n"); + buf_printf(b, "static const u32 ____version_ext_crcs[]\n"); + buf_printf(b, "__used __section(\"__version_ext_crcs\") = {\n"); + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (!s->module) + continue; + if (!s->crc_valid) { + warn("\"%s\" [%s.ko] has no CRC!\n", + s->name, mod->name); + continue; + } + buf_printf(b, "\t0x%08x,\n", s->crc); + } + buf_printf(b, "};\n"); + + buf_printf(b, "static const char ____version_ext_names[]\n"); + buf_printf(b, "__used __section(\"__version_ext_names\") =\n"); + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (!s->module) + continue; + if (!s->crc_valid) + /* + * We already warned on this when producing the crc + * table. + * We need to skip its name too, as the indexes in + * both tables need to align. + */ + continue; + buf_printf(b, "\t\"%s\\0\"\n", s->name); + } + buf_printf(b, ";\n"); +} + +/** * Record CRCs for unresolved symbols **/ static void add_versions(struct buffer *b, struct module *mod) { struct symbol *s; - if (!modversions) + if (!basic_modversions) return; buf_printf(b, "\n"); @@ -1919,11 +1953,16 @@ static void add_versions(struct buffer *b, struct module *mod) continue; } if (strlen(s->name) >= MODULE_NAME_LEN) { - error("too long symbol \"%s\" [%s.ko]\n", - s->name, mod->name); - break; + if (extended_modversions) { + /* this symbol will only be in the extended info */ + continue; + } else { + error("too long symbol \"%s\" [%s.ko]\n", + s->name, mod->name); + break; + } } - buf_printf(b, "\t{ %#8x, \"%s\" },\n", + buf_printf(b, "\t{ 0x%08x, \"%s\" },\n", s->crc, s->name); } @@ -1952,11 +1991,7 @@ static void add_depends(struct buffer *b, struct module *mod) continue; s->module->seen = true; - p = strrchr(s->module->name, '/'); - if (p) - p++; - else - p = s->module->name; + p = get_basename(s->module->name); buf_printf(b, "%s%s", first ? "" : ",", p); first = 0; } @@ -2010,7 +2045,7 @@ static void write_if_changed(struct buffer *b, const char *fname) if (st.st_size != b->pos) goto close_write; - tmp = NOFAIL(malloc(b->pos)); + tmp = xmalloc(b->pos); if (fread(tmp, 1, b->pos, file) != b->pos) goto free_write; @@ -2045,14 +2080,23 @@ static void write_vmlinux_export_c_file(struct module *mod) static void write_mod_c_file(struct module *mod) { struct buffer buf = { }; + struct module_alias *alias, *next; char fname[PATH_MAX]; int ret; add_header(&buf, mod); add_exported_symbols(&buf, mod); add_versions(&buf, mod); + add_extended_versions(&buf, mod); add_depends(&buf, mod); - add_moddevtable(&buf, mod); + + buf_printf(&buf, "\n"); + list_for_each_entry_safe(alias, next, &mod->aliases, node) { + buf_printf(&buf, "MODULE_ALIAS(\"%s\");\n", alias->str); + list_del(&alias->node); + free(alias); + } + add_srcversion(&buf, mod); ret = snprintf(fname, sizeof(fname), "%s.mod.c", mod->name); @@ -2114,10 +2158,10 @@ static void read_dump(const char *fname) continue; } - mod = find_module(modname); + mod = find_module(fname, modname); if (!mod) { mod = new_module(modname, strlen(modname)); - mod->from_dump = true; + mod->dump_file = fname; } s = sym_add_exported(symname, mod, gpl_only, namespace); sym_set_crc(s, crc); @@ -2136,7 +2180,7 @@ static void write_dump(const char *fname) struct symbol *sym; list_for_each_entry(mod, &modules, list) { - if (mod->from_dump) + if (mod->dump_file) continue; list_for_each_entry(sym, &mod->exported_symbols, list) { if (trim_unused_exports && !sym->used) @@ -2160,7 +2204,7 @@ static void write_namespace_deps_files(const char *fname) list_for_each_entry(mod, &modules, list) { - if (mod->from_dump || list_empty(&mod->missing_namespaces)) + if (mod->dump_file || list_empty(&mod->missing_namespaces)) continue; buf_printf(&ns_deps_buf, "%s.ko:", mod->name); @@ -2180,6 +2224,25 @@ struct dump_list { const char *file; }; +static void check_host_endian(void) +{ + static const union { + short s; + char c[2]; + } endian_test = { .c = {0x01, 0x02} }; + + switch (endian_test.s) { + case 0x0102: + host_is_big_endian = true; + break; + case 0x0201: + host_is_big_endian = false; + break; + default: + fatal("Unknown host endian\n"); + } +} + int main(int argc, char **argv) { struct module *mod; @@ -2190,13 +2253,13 @@ int main(int argc, char **argv) LIST_HEAD(dump_lists); struct dump_list *dl, *dl2; - while ((opt = getopt(argc, argv, "ei:MmnT:to:au:WwENd:")) != -1) { + while ((opt = getopt(argc, argv, "ei:MmnT:to:au:WwENd:xb")) != -1) { switch (opt) { case 'e': external_module = true; break; case 'i': - dl = NOFAIL(malloc(sizeof(*dl))); + dl = xmalloc(sizeof(*dl)); dl->file = optarg; list_add_tail(&dl->list, &dump_lists); break; @@ -2239,11 +2302,19 @@ int main(int argc, char **argv) case 'd': missing_namespace_deps = optarg; break; + case 'b': + basic_modversions = true; + break; + case 'x': + extended_modversions = true; + break; default: exit(1); } } + check_host_endian(); + list_for_each_entry_safe(dl, dl2, &dump_lists, list) { read_dump(dl->file); list_del(&dl->list); @@ -2257,7 +2328,9 @@ int main(int argc, char **argv) read_symbols_from_files(files_source); list_for_each_entry(mod, &modules, list) { - if (mod->from_dump || mod->is_vmlinux) + keep_no_trim_symbols(mod); + + if (mod->dump_file || mod->is_vmlinux) continue; check_modname_len(mod); @@ -2268,7 +2341,7 @@ int main(int argc, char **argv) handle_white_list_exports(unused_exports_white_list); list_for_each_entry(mod, &modules, list) { - if (mod->from_dump) + if (mod->dump_file) continue; if (mod->is_vmlinux) diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h index ee43c7950636..9133e4c3803f 100644 --- a/scripts/mod/modpost.h +++ b/scripts/mod/modpost.h @@ -13,7 +13,7 @@ #include <elf.h> #include "../../include/linux/module_symbol.h" -#include "list.h" +#include <list_types.h> #include "elfconfig.h" /* On BSD-alike OSes elf.h defines these according to host's word size */ @@ -62,21 +62,26 @@ x); \ }) -#if KERNEL_ELFDATA != HOST_ELFDATA +#define TO_NATIVE(x) \ + (target_is_big_endian == host_is_big_endian ? x : bswap(x)) -#define TO_NATIVE(x) (bswap(x)) - -#else /* endianness matches */ - -#define TO_NATIVE(x) (x) +#define __get_unaligned_t(type, ptr) ({ \ + const struct { type x; } __attribute__((__packed__)) *__pptr = \ + (typeof(__pptr))(ptr); \ + __pptr->x; \ +}) -#endif +#define get_unaligned(ptr) __get_unaligned_t(typeof(*(ptr)), (ptr)) -#define NOFAIL(ptr) do_nofail((ptr), #ptr) +#define get_unaligned_native(ptr) \ +({ \ + typeof(*(ptr)) _val = get_unaligned(ptr); \ + TO_NATIVE(_val); \ +}) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) -void *do_nofail(void *ptr, const char *expr); +#define strstarts(str, prefix) (strncmp(str, prefix, strlen(prefix)) == 0) struct buffer { char *p; @@ -90,22 +95,43 @@ buf_printf(struct buffer *buf, const char *fmt, ...); void buf_write(struct buffer *buf, const char *s, int len); +/** + * struct module_alias - auto-generated MODULE_ALIAS() + * + * @node: linked to module::aliases + * @str: a string for MODULE_ALIAS() + */ +struct module_alias { + struct list_head node; + char str[]; +}; + +/** + * struct module - represent a module (vmlinux or *.ko) + * + * @dump_file: path to the .symvers file if loaded from a file + * @aliases: list head for module_aliases + * @no_trim_symbol: .no_trim_symbol section data + * @no_trim_symbol_len: length of the .no_trim_symbol section + */ struct module { struct list_head list; struct list_head exported_symbols; struct list_head unresolved_symbols; + const char *dump_file; bool is_gpl_compatible; - bool from_dump; /* true if module was loaded from *.symvers */ bool is_vmlinux; bool seen; bool has_init; bool has_cleanup; - struct buffer dev_table_buf; char srcversion[25]; // Missing namespace dependencies struct list_head missing_namespaces; // Actual imported namespaces struct list_head imported_namespaces; + struct list_head aliases; + char *no_trim_symbol; + unsigned int no_trim_symbol_len; char name[]; }; @@ -119,6 +145,8 @@ struct elf_info { char *strtab; char *modinfo; unsigned int modinfo_len; + char *no_trim_symbol; + unsigned int no_trim_symbol_len; /* support for 32bit section numbers */ @@ -181,23 +209,20 @@ Elf_Sym *symsearch_find_nearest(struct elf_info *elf, Elf_Addr addr, /* file2alias.c */ void handle_moddevtable(struct module *mod, struct elf_info *info, Elf_Sym *sym, const char *symname); -void add_moddevtable(struct buffer *buf, struct module *mod); /* sumversion.c */ void get_src_version(const char *modname, char sum[], unsigned sumlen); /* from modpost.c */ +extern bool target_is_big_endian; +extern bool host_is_big_endian; +const char *get_basename(const char *path); char *read_text_file(const char *filename); char *get_line(char **stringp); void *sym_get_data(const struct elf_info *info, const Elf_Sym *sym); -enum loglevel { - LOG_WARN, - LOG_ERROR, -}; - void __attribute__((format(printf, 2, 3))) -modpost_log(enum loglevel loglevel, const char *fmt, ...); +modpost_log(bool is_error, const char *fmt, ...); /* * warn - show the given message, then let modpost continue running, still @@ -212,6 +237,6 @@ modpost_log(enum loglevel loglevel, const char *fmt, ...); * fatal - show the given message, and bail out immediately. This should be * used when there is no point to continue running modpost. */ -#define warn(fmt, args...) modpost_log(LOG_WARN, fmt, ##args) -#define error(fmt, args...) modpost_log(LOG_ERROR, fmt, ##args) +#define warn(fmt, args...) modpost_log(false, fmt, ##args) +#define error(fmt, args...) modpost_log(true, fmt, ##args) #define fatal(fmt, args...) do { error(fmt, ##args); exit(1); } while (1) diff --git a/scripts/mod/sumversion.c b/scripts/mod/sumversion.c index dc4878502276..3dd28b4d0099 100644 --- a/scripts/mod/sumversion.c +++ b/scripts/mod/sumversion.c @@ -8,6 +8,8 @@ #include <errno.h> #include <string.h> #include <limits.h> + +#include <xalloc.h> #include "modpost.h" /* @@ -305,18 +307,13 @@ static int parse_source_files(const char *objfile, struct md4_ctx *md) const char *base; int dirlen, ret = 0, check_files = 0; - cmd = NOFAIL(malloc(strlen(objfile) + sizeof("..cmd"))); + cmd = xmalloc(strlen(objfile) + sizeof("..cmd")); - base = strrchr(objfile, '/'); - if (base) { - base++; - dirlen = base - objfile; - sprintf(cmd, "%.*s.%s.cmd", dirlen, objfile, base); - } else { - dirlen = 0; - sprintf(cmd, ".%s.cmd", objfile); - } - dir = NOFAIL(malloc(dirlen + 1)); + base = get_basename(objfile); + dirlen = base - objfile; + sprintf(cmd, "%.*s.%s.cmd", dirlen, objfile, base); + + dir = xmalloc(dirlen + 1); strncpy(dir, objfile, dirlen); dir[dirlen] = '\0'; @@ -333,7 +330,7 @@ static int parse_source_files(const char *objfile, struct md4_ctx *md) line++; p = line; - if (strncmp(line, "source_", sizeof("source_")-1) == 0) { + if (strstarts(line, "source_")) { p = strrchr(line, ' '); if (!p) { warn("malformed line: %s\n", line); @@ -347,7 +344,7 @@ static int parse_source_files(const char *objfile, struct md4_ctx *md) } continue; } - if (strncmp(line, "deps_", sizeof("deps_")-1) == 0) { + if (strstarts(line, "deps_")) { check_files = 1; continue; } @@ -390,7 +387,7 @@ out_file: /* Calc and record src checksum. */ void get_src_version(const char *modname, char sum[], unsigned sumlen) { - char *buf; + char *buf, *pos; struct md4_ctx md; char *fname; char filelist[PATH_MAX + 1]; @@ -399,9 +396,10 @@ void get_src_version(const char *modname, char sum[], unsigned sumlen) snprintf(filelist, sizeof(filelist), "%s.mod", modname); buf = read_text_file(filelist); + pos = buf; md4_init(&md); - while ((fname = strsep(&buf, "\n"))) { + while ((fname = strsep(&pos, "\n"))) { if (!*fname) continue; if (!(is_static_library(fname)) && diff --git a/scripts/mod/symsearch.c b/scripts/mod/symsearch.c index aa4ed51f9960..b9737b92f7f8 100644 --- a/scripts/mod/symsearch.c +++ b/scripts/mod/symsearch.c @@ -4,7 +4,7 @@ * Helper functions for finding the symbol in an ELF which is "nearest" * to a given address. */ - +#include <xalloc.h> #include "modpost.h" struct syminfo { @@ -125,8 +125,8 @@ void symsearch_init(struct elf_info *elf) { unsigned int table_size = symbol_count(elf); - elf->symsearch = NOFAIL(malloc(sizeof(struct symsearch) + - sizeof(struct syminfo) * table_size)); + elf->symsearch = xmalloc(sizeof(struct symsearch) + + sizeof(struct syminfo) * table_size); elf->symsearch->table_size = table_size; symsearch_populate(elf, elf->symsearch->table, table_size); diff --git a/scripts/module-common.c b/scripts/module-common.c new file mode 100644 index 000000000000..12fbc6d3aae8 --- /dev/null +++ b/scripts/module-common.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/module.h> +/* + * Include build-salt.h after module.h in order to + * inherit the definitions. + */ +#define INCLUDE_VERMAGIC +#include <linux/build-salt.h> +#include <linux/elfnote-lto.h> +#include <linux/vermagic.h> + +#ifdef CONFIG_UNWINDER_ORC +#include <asm/orc_header.h> +ORC_HEADER; +#endif + +BUILD_SALT; +BUILD_LTO_INFO; + +MODULE_INFO(vermagic, VERMAGIC_STRING); + +#ifdef CONFIG_MITIGATION_RETPOLINE +MODULE_INFO(retpoline, "Y"); +#endif diff --git a/scripts/module.lds.S b/scripts/module.lds.S index bf5bcf2836d8..450f1088d5fd 100644 --- a/scripts/module.lds.S +++ b/scripts/module.lds.S @@ -9,16 +9,20 @@ #define DISCARD_EH_FRAME *(.eh_frame) #endif +#include <asm-generic/codetag.lds.h> + SECTIONS { /DISCARD/ : { *(.discard) *(.discard.*) + *(.export_symbol) + *(.no_trim_symbol) } - __ksymtab 0 : { *(SORT(___ksymtab+*)) } - __ksymtab_gpl 0 : { *(SORT(___ksymtab_gpl+*)) } - __kcrctab 0 : { *(SORT(___kcrctab+*)) } - __kcrctab_gpl 0 : { *(SORT(___kcrctab_gpl+*)) } + __ksymtab 0 : ALIGN(8) { *(SORT(___ksymtab+*)) } + __ksymtab_gpl 0 : ALIGN(8) { *(SORT(___ksymtab_gpl+*)) } + __kcrctab 0 : ALIGN(4) { *(SORT(___kcrctab+*)) } + __kcrctab_gpl 0 : ALIGN(4) { *(SORT(___kcrctab_gpl+*)) } .ctors 0 : ALIGN(8) { *(SORT(.ctors.*)) *(.ctors) } .init_array 0 : ALIGN(8) { *(SORT(.init_array.*)) *(.init_array) } @@ -26,6 +30,7 @@ SECTIONS { .altinstructions 0 : ALIGN(8) { KEEP(*(.altinstructions)) } __bug_table 0 : ALIGN(8) { KEEP(*(__bug_table)) } __jump_table 0 : ALIGN(8) { KEEP(*(__jump_table)) } + __ex_table 0 : ALIGN(4) { KEEP(*(__ex_table)) } __patchable_function_entries : { *(__patchable_function_entries) } @@ -47,13 +52,19 @@ SECTIONS { .data : { *(.data .data.[0-9a-zA-Z_]*) *(.data..L*) + MOD_CODETAG_SECTIONS() } .rodata : { *(.rodata .rodata.[0-9a-zA-Z_]*) *(.rodata..L*) } +#else + .data : { + MOD_CODETAG_SECTIONS() + } #endif + MOD_SEPARATE_CODETAG_SECTIONS() } /* bring in arch-specific sections */ diff --git a/scripts/nsdeps b/scripts/nsdeps index f1718cc0d700..a3372166ac01 100644 --- a/scripts/nsdeps +++ b/scripts/nsdeps @@ -19,22 +19,16 @@ if ! { echo "$SPATCH_REQ_VERSION"; echo "$SPATCH_VERSION"; } | sort -CV ; then exit 1 fi -if [ "$KBUILD_EXTMOD" ]; then - src_prefix= -else - src_prefix=$srctree/ -fi - generate_deps_for_ns() { $SPATCH --very-quiet --in-place --sp-file \ - $srctree/scripts/coccinelle/misc/add_namespace.cocci -D nsdeps -D ns=$1 $2 + $srctree/scripts/coccinelle/misc/add_namespace.cocci -D nsdeps -D ns=\"$1\" $2 } generate_deps() { local mod=${1%.ko:} shift local namespaces="$*" - local mod_source_files=$(sed "s|^\(.*\)\.o$|${src_prefix}\1.c|" $mod.mod) + local mod_source_files=$(sed "s|^\(.*\)\.o$|${srcroot}/\1.c|" $mod.mod) for ns in $namespaces; do echo "Adding namespace $ns to module $mod.ko." @@ -57,4 +51,4 @@ generate_deps() { while read line do generate_deps $line -done < $MODULES_NSDEPS +done < modules.nsdeps diff --git a/scripts/package/PKGBUILD b/scripts/package/PKGBUILD new file mode 100644 index 000000000000..452374d63c24 --- /dev/null +++ b/scripts/package/PKGBUILD @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Maintainer: Thomas Weißschuh <linux@weissschuh.net> +# Contributor: Jan Alexander Steffens (heftig) <heftig@archlinux.org> + +pkgbase=${PACMAN_PKGBASE:-linux-upstream} +pkgname=("${pkgbase}") + +_extrapackages=${PACMAN_EXTRAPACKAGES-headers api-headers debug} +for pkg in $_extrapackages; do + pkgname+=("${pkgbase}-${pkg}") +done + +pkgver="${KERNELRELEASE//-/_}" +# The PKGBUILD is evaluated multiple times. +# Running scripts/build-version from here would introduce inconsistencies. +pkgrel="${KBUILD_REVISION}" +pkgdesc='Upstream Linux' +url='https://www.kernel.org/' +# Enable flexible cross-compilation +arch=(${CARCH}) +license=(GPL-2.0-only) +makedepends=( + bc + bison + flex + gettext + kmod + libelf + openssl + pahole + perl + python + rsync + tar +) +options=(!debug !strip !buildflags !makeflags) + +_prologue() { + # MAKEFLAGS from makepkg.conf override the ones inherited from kbuild. + # Bypass this override with a custom variable. + export MAKEFLAGS="${KBUILD_MAKEFLAGS}" + + # Kbuild works in the output directory, where this PKGBUILD is located. + cd "$(dirname "${BASH_SOURCE[0]}")" +} + +build() { + _prologue + + ${MAKE} KERNELRELEASE="${KERNELRELEASE}" KBUILD_BUILD_VERSION="${pkgrel}" +} + +_package() { + pkgdesc="The ${pkgdesc} kernel and modules" + + local modulesdir="${pkgdir}/usr/lib/modules/${KERNELRELEASE}" + + _prologue + + echo "Installing boot image..." + # systemd expects to find the kernel here to allow hibernation + # https://github.com/systemd/systemd/commit/edda44605f06a41fb86b7ab8128dcf99161d2344 + install -Dm644 "$(${MAKE} -s image_name)" "${modulesdir}/vmlinuz" + + # Used by mkinitcpio to name the kernel + echo "${pkgbase}" > "${modulesdir}/pkgbase" + + echo "Installing modules..." + ${MAKE} INSTALL_MOD_PATH="${pkgdir}/usr" INSTALL_MOD_STRIP=1 \ + DEPMOD=true modules_install + + if [ -d "${srctree}/arch/${SRCARCH}/boot/dts" ]; then + echo "Installing dtbs..." + ${MAKE} INSTALL_DTBS_PATH="${modulesdir}/dtb" dtbs_install + fi + + # remove build link, will be part of -headers package + rm -f "${modulesdir}/build" +} + +_package-headers() { + pkgdesc="Headers and scripts for building modules for the ${pkgdesc} kernel" + + local builddir="${pkgdir}/usr/lib/modules/${KERNELRELEASE}/build" + + _prologue + + if grep -q CONFIG_MODULES=y include/config/auto.conf; then + echo "Installing build files..." + "${srctree}/scripts/package/install-extmod-build" "${builddir}" + fi + + echo "Installing System.map and config..." + mkdir -p "${builddir}" + cp System.map "${builddir}/System.map" + cp .config "${builddir}/.config" + + echo "Adding symlink..." + mkdir -p "${pkgdir}/usr/src" + ln -sr "${builddir}" "${pkgdir}/usr/src/${pkgbase}" +} + +_package-api-headers() { + pkgdesc="Kernel headers sanitized for use in userspace" + provides=(linux-api-headers="${pkgver}") + conflicts=(linux-api-headers) + + _prologue + + ${MAKE} headers_install INSTALL_HDR_PATH="${pkgdir}/usr" +} + +_package-debug(){ + pkgdesc="Non-stripped vmlinux file for the ${pkgdesc} kernel" + + local debugdir="${pkgdir}/usr/src/debug/${pkgbase}" + local builddir="${pkgdir}/usr/lib/modules/${KERNELRELEASE}/build" + + _prologue + + install -Dt "${debugdir}" -m644 vmlinux + mkdir -p "${builddir}" + ln -sr "${debugdir}/vmlinux" "${builddir}/vmlinux" +} + +for _p in "${pkgname[@]}"; do + eval "package_$_p() { + $(declare -f "_package${_p#$pkgbase}") + _package${_p#$pkgbase} + }" +done diff --git a/scripts/package/builddeb b/scripts/package/builddeb index e797ad360f7a..3627ca227e5a 100755 --- a/scripts/package/builddeb +++ b/scripts/package/builddeb @@ -5,12 +5,14 @@ # # Simple script to generate a deb package for a Linux kernel. All the # complexity of what to do with a kernel after it is installed or removed -# is left to other scripts and packages: they can install scripts in the -# /etc/kernel/{pre,post}{inst,rm}.d/ directories (or an alternative location -# specified in KDEB_HOOKDIR) that will be called on package install and -# removal. +# is left to other scripts and packages. Scripts can be placed into the +# preinst, postinst, prerm and postrm directories in /etc/kernel or +# /usr/share/kernel. A different list of search directories can be given +# via KDEB_HOOKDIR. Scripts in directories earlier in the list will +# override scripts of the same name in later directories. The script will +# be called on package installation and removal. -set -e +set -eu is_enabled() { grep -q "^$1=y" include/config/auto.conf @@ -63,18 +65,21 @@ install_linux_image () { esac cp "$(${MAKE} -s -f ${srctree}/Makefile image_name)" "${pdir}/${installed_image_path}" + if [ "${ARCH}" != um ]; then + install_maint_scripts "${pdir}" + fi +} + +install_maint_scripts () { # Install the maintainer scripts # Note: hook scripts under /etc/kernel are also executed by official Debian # kernel packages, as well as kernel packages built using make-kpkg. # make-kpkg sets $INITRD to indicate whether an initramfs is wanted, and # so do we; recent versions of dracut and initramfs-tools will obey this. - debhookdir=${KDEB_HOOKDIR:-/etc/kernel} + debhookdir=${KDEB_HOOKDIR:-/etc/kernel /usr/share/kernel} for script in postinst postrm preinst prerm; do - mkdir -p "${pdir}${debhookdir}/${script}.d" - mkdir -p "${pdir}/DEBIAN" cat <<-EOF > "${pdir}/DEBIAN/${script}" - #!/bin/sh set -e @@ -85,7 +90,15 @@ install_linux_image () { # Tell initramfs builder whether it's wanted export INITRD=$(if_enabled_echo CONFIG_BLK_DEV_INITRD Yes No) - test -d ${debhookdir}/${script}.d && run-parts --arg="${KERNELRELEASE}" --arg="/${installed_image_path}" ${debhookdir}/${script}.d + # run-parts will error out if one of its directory arguments does not + # exist, so filter the list of hook directories accordingly. + hookdirs= + for dir in ${debhookdir}; do + test -d "\$dir/${script}.d" || continue + hookdirs="\$hookdirs \$dir/${script}.d" + done + hookdirs="\${hookdirs# }" + test -n "\$hookdirs" && run-parts --arg="${KERNELRELEASE}" --arg="/${installed_image_path}" \$hookdirs exit 0 EOF chmod 755 "${pdir}/DEBIAN/${script}" @@ -97,16 +110,18 @@ install_linux_image_dbg () { # Parse modules.order directly because 'make modules_install' may sign, # compress modules, and then run unneeded depmod. - while read -r mod; do - mod="${mod%.o}.ko" - dbg="${pdir}/usr/lib/debug/lib/modules/${KERNELRELEASE}/kernel/${mod}" - buildid=$("${READELF}" -n "${mod}" | sed -n 's@^.*Build ID: \(..\)\(.*\)@\1/\2@p') - link="${pdir}/usr/lib/debug/.build-id/${buildid}.debug" - - mkdir -p "${dbg%/*}" "${link%/*}" - "${OBJCOPY}" --only-keep-debug "${mod}" "${dbg}" - ln -sf --relative "${dbg}" "${link}" - done < modules.order + if is_enabled CONFIG_MODULES; then + while read -r mod; do + mod="${mod%.o}.ko" + dbg="${pdir}/usr/lib/debug/lib/modules/${KERNELRELEASE}/kernel/${mod}" + buildid=$("${READELF}" -n "${mod}" | sed -n 's@^.*Build ID: \(..\)\(.*\)@\1/\2@p') + link="${pdir}/usr/lib/debug/.build-id/${buildid}.debug" + + mkdir -p "${dbg%/*}" "${link%/*}" + "${OBJCOPY}" --only-keep-debug "${mod}" "${dbg}" + ln -sf --relative "${dbg}" "${link}" + done < modules.order + fi # Build debug package # Different tools want the image in different locations @@ -124,7 +139,7 @@ install_kernel_headers () { pdir=debian/$1 version=${1#linux-headers-} - "${srctree}/scripts/package/install-extmod-build" "${pdir}/usr/src/linux-headers-${version}" + CC="${DEB_HOST_GNU_TYPE}-gcc" "${srctree}/scripts/package/install-extmod-build" "${pdir}/usr/src/linux-headers-${version}" mkdir -p $pdir/lib/modules/$version/ ln -s /usr/src/linux-headers-$version $pdir/lib/modules/$version/build diff --git a/scripts/package/buildtar b/scripts/package/buildtar index 72c91a1b832f..cc87a473c01f 100755 --- a/scripts/package/buildtar +++ b/scripts/package/buildtar @@ -11,7 +11,7 @@ # Wichert Akkerman <wichert@wiggy.net>. # -set -e +set -eu # # Some variables and settings used throughout the script @@ -53,18 +53,20 @@ cp -v -- "${objtree}/vmlinux" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" # # Install arch-specific kernel image(s) # +# Note: +# mips and arm64 copy the first image found. This may not produce the desired +# outcome because it may pick up a stale file remaining in the build tree. +# case "${ARCH}" in - x86|i386|x86_64) - [ -f "${objtree}/arch/x86/boot/bzImage" ] && cp -v -- "${objtree}/arch/x86/boot/bzImage" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" - ;; alpha) - [ -f "${objtree}/arch/alpha/boot/vmlinux.gz" ] && cp -v -- "${objtree}/arch/alpha/boot/vmlinux.gz" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" + cp -v -- "${objtree}/arch/alpha/boot/vmlinux.gz" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" ;; parisc*) - [ -f "${KBUILD_IMAGE}" ] && cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" + cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" [ -f "${objtree}/lifimage" ] && cp -v -- "${objtree}/lifimage" "${tmpdir}/boot/lifimage-${KERNELRELEASE}" ;; mips) + # Please note the following code may copy a stale file. if [ -f "${objtree}/arch/mips/boot/compressed/vmlinux.bin" ]; then cp -v -- "${objtree}/arch/mips/boot/compressed/vmlinux.bin" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" elif [ -f "${objtree}/arch/mips/boot/compressed/vmlinux.ecoff" ]; then @@ -86,6 +88,7 @@ case "${ARCH}" in fi ;; arm64) + # Please note the following code may copy a stale file. for i in Image.bz2 Image.gz Image.lz4 Image.lzma Image.lzo vmlinuz.efi ; do if [ -f "${objtree}/arch/arm64/boot/${i}" ] ; then cp -v -- "${objtree}/arch/arm64/boot/${i}" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" @@ -94,21 +97,14 @@ case "${ARCH}" in done ;; riscv) - for i in Image.bz2 Image.gz Image; do - if [ -f "${objtree}/arch/riscv/boot/${i}" ] ; then - cp -v -- "${objtree}/arch/riscv/boot/${i}" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" - break - fi - done + case "${KBUILD_IMAGE##*/}" in + Image.*|vmlinuz.efi) + cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}";; + *) + cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}";; + esac ;; *) - [ -f "${KBUILD_IMAGE}" ] && cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinux-kbuild-${KERNELRELEASE}" - echo "" >&2 - echo '** ** ** WARNING ** ** **' >&2 - echo "" >&2 - echo "Your architecture did not define any architecture-dependent files" >&2 - echo "to be placed into the tarball. Please add those to ${0} ..." >&2 - echo "" >&2 - sleep 5 + cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" ;; esac diff --git a/scripts/package/debian/rules b/scripts/package/debian/rules index ca07243bd5cd..a417a7f8bbc1 100755 --- a/scripts/package/debian/rules +++ b/scripts/package/debian/rules @@ -21,9 +21,11 @@ ifeq ($(origin KBUILD_VERBOSE),undefined) endif endif -revision = $(lastword $(subst -, ,$(shell dpkg-parsechangelog -S Version))) +revision = $(shell dpkg-parsechangelog -S Version | sed -n 's/.*-//p') CROSS_COMPILE ?= $(filter-out $(DEB_BUILD_GNU_TYPE)-, $(DEB_HOST_GNU_TYPE)-) -make-opts = ARCH=$(ARCH) KERNELRELEASE=$(KERNELRELEASE) KBUILD_BUILD_VERSION=$(revision) $(addprefix CROSS_COMPILE=,$(CROSS_COMPILE)) +make-opts = ARCH=$(ARCH) KERNELRELEASE=$(KERNELRELEASE) \ + $(addprefix KBUILD_BUILD_VERSION=,$(revision)) \ + $(addprefix CROSS_COMPILE=,$(CROSS_COMPILE)) binary-targets := $(addprefix binary-, image image-dbg headers libc-dev) @@ -41,6 +43,10 @@ package = $($(@:binary-%=%-package)) # which package is being processed in the build log. DH_OPTIONS = -p$(package) +# Note: future removal of KDEB_COMPRESS +# dpkg-deb >= 1.21.10 supports the DPKG_DEB_COMPRESSOR_TYPE environment +# variable, which provides the same functionality as KDEB_COMPRESS. The +# KDEB_COMPRESS variable will be removed in the future. define binary $(Q)dh_testdir $(DH_OPTIONS) $(Q)dh_testroot $(DH_OPTIONS) diff --git a/scripts/package/gen-diff-patch b/scripts/package/gen-diff-patch index 8a98b7bb78a0..f272f7770ea3 100755 --- a/scripts/package/gen-diff-patch +++ b/scripts/package/gen-diff-patch @@ -1,6 +1,8 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0-only +set -eu + diff_patch=$1 mkdir -p "$(dirname "${diff_patch}")" diff --git a/scripts/package/install-extmod-build b/scripts/package/install-extmod-build index 76e0765dfcd6..b96538787f3d 100755 --- a/scripts/package/install-extmod-build +++ b/scripts/package/install-extmod-build @@ -1,26 +1,30 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0-only -set -e +set -eu destdir=${1} -test -n "${srctree}" -test -n "${SRCARCH}" - is_enabled() { grep -q "^$1=y" include/config/auto.conf } +find_in_scripts() { + find scripts \ + \( -name atomic -o -name dtc -o -name kconfig -o -name package \) -prune -o \ + ! -name unifdef -a ! -name mk_elfconfig -a \( -type f -o -type l \) -print +} + mkdir -p "${destdir}" ( cd "${srctree}" echo Makefile find "arch/${SRCARCH}" -maxdepth 1 -name 'Makefile*' - find include scripts -type f -o -type l + find "arch/${SRCARCH}" -name generated -prune -o -name include -type d -print find "arch/${SRCARCH}" -name Kbuild.platforms -o -name Platform - find "arch/${SRCARCH}" -name include -type d + find include \( -name config -o -name generated \) -prune -o \( -type f -o -type l \) -print + find_in_scripts ) | tar -c -f - -C "${srctree}" -T - | tar -xf - -C "${destdir}" { @@ -28,12 +32,40 @@ mkdir -p "${destdir}" echo tools/objtool/objtool fi - find "arch/${SRCARCH}/include" Module.symvers include scripts -type f + echo Module.symvers + echo "arch/${SRCARCH}/include/generated" + echo include/config/auto.conf + echo include/config/kernel.release + echo include/generated + find_in_scripts if is_enabled CONFIG_GCC_PLUGINS; then find scripts/gcc-plugins -name '*.so' fi } | tar -c -f - -T - | tar -xf - -C "${destdir}" -# copy .config manually to be where it's expected to be -cp "${KCONFIG_CONFIG}" "${destdir}/.config" +# When ${CC} and ${HOSTCC} differ, rebuild host programs using ${CC}. +# +# This caters to host programs that participate in Kbuild. objtool and +# resolve_btfids are out of scope. +if [ "${CC}" != "${HOSTCC}" ]; then + cat "${destdir}/scripts/Makefile" - <<-'EOF' > "${destdir}/scripts/Kbuild" + subdir-y += basic + hostprogs-always-y += mod/modpost + mod/modpost-objs := $(addprefix mod/, modpost.o file2alias.o sumversion.o symsearch.o) + EOF + + # HOSTCXX is not overridden. The C++ compiler is used to build: + # - scripts/kconfig/qconf, which is unneeded for external module builds + # - GCC plugins, which will not work on the installed system even after + # being rebuilt. + # + # Clear VPATH and srcroot because the source files reside in the output + # directory. + # shellcheck disable=SC2016 # $(MAKE) and $(build) will be expanded by Make + "${MAKE}" run-command KBUILD_RUN_COMMAND='+$(MAKE) HOSTCC='"${CC}"' VPATH= srcroot=. $(build)='"$(realpath --relative-base=. "${destdir}")"/scripts + + rm -f "${destdir}/scripts/Kbuild" +fi + +find "${destdir}" \( -name '.*.cmd' -o -name '*.o' \) -delete diff --git a/scripts/package/kernel.spec b/scripts/package/kernel.spec index e095eb1e290e..98f206cb7c60 100644 --- a/scripts/package/kernel.spec +++ b/scripts/package/kernel.spec @@ -2,8 +2,6 @@ %{!?_arch: %define _arch dummy} %{!?make: %define make make} %define makeflags %{?_smp_mflags} ARCH=%{ARCH} -%define __spec_install_post /usr/lib/rpm/brp-compress || : -%define debug_package %{nil} Name: kernel Summary: The Linux Kernel @@ -18,6 +16,7 @@ Source1: config Source2: diff.patch Provides: kernel-%{KERNELRELEASE} BuildRequires: bc binutils bison dwarves +BuildRequires: (elfutils-devel or libdw-devel) BuildRequires: (elfutils-libelf-devel or libelf-devel) flex BuildRequires: gcc make openssl openssl-devel perl python3 rsync @@ -27,7 +26,7 @@ The Linux Kernel, the operating system core itself %package headers Summary: Header files for the Linux kernel for use by glibc Group: Development/System -Obsoletes: kernel-headers +Obsoletes: kernel-headers < %{version} Provides: kernel-headers = %{version} %description headers Kernel-headers includes the C header files that specify the interface @@ -46,6 +45,36 @@ This package provides kernel headers and makefiles sufficient to build modules against the %{version} kernel package. %endif +%if %{with_debuginfo} +# list of debuginfo-related options taken from distribution kernel.spec +# files +%undefine _include_minidebuginfo +%undefine _find_debuginfo_dwz_opts +%undefine _unique_build_ids +%undefine _unique_debug_names +%undefine _unique_debug_srcs +%undefine _debugsource_packages +%undefine _debuginfo_subpackages +%global _find_debuginfo_opts -r +%global _missing_build_ids_terminate_build 1 +%global _no_recompute_build_ids 1 +%{debug_package} +%endif +# some (but not all) versions of rpmbuild emit %%debug_package with +# %%install. since we've already emitted it manually, that would cause +# a package redefinition error. ensure that doesn't happen +%define debug_package %{nil} + +# later, we make all modules executable so that find-debuginfo.sh strips +# them up. but they don't actually need to be executable, so remove the +# executable bit, taking care to do it _after_ find-debuginfo.sh has run +%define __spec_install_post \ + %{?__debug_package:%{__debug_install_post}} \ + %{__arch_install_post} \ + %{__os_install_post} \ + find %{buildroot}/lib/modules/%{KERNELRELEASE} -name "*.ko" -type f \\\ + | xargs --no-run-if-empty chmod u-x + %prep %setup -q -n linux cp %{SOURCE1} .config @@ -57,7 +86,8 @@ patch -p1 < %{SOURCE2} %install mkdir -p %{buildroot}/lib/modules/%{KERNELRELEASE} cp $(%{make} %{makeflags} -s image_name) %{buildroot}/lib/modules/%{KERNELRELEASE}/vmlinuz -%{make} %{makeflags} INSTALL_MOD_PATH=%{buildroot} modules_install +# DEPMOD=true makes depmod no-op. We do not package depmod-generated files. +%{make} %{makeflags} INSTALL_MOD_PATH=%{buildroot} DEPMOD=true modules_install %{make} %{makeflags} INSTALL_HDR_PATH=%{buildroot}/usr headers_install cp System.map %{buildroot}/lib/modules/%{KERNELRELEASE} cp .config %{buildroot}/lib/modules/%{KERNELRELEASE}/config @@ -70,13 +100,10 @@ ln -fns /usr/src/kernels/%{KERNELRELEASE} %{buildroot}/lib/modules/%{KERNELRELEA %endif { - for x in System.map config kernel modules.builtin \ - modules.builtin.modinfo modules.order vmlinuz; do - echo "/lib/modules/%{KERNELRELEASE}/${x}" - done + echo "/lib/modules/%{KERNELRELEASE}" for x in alias alias.bin builtin.alias.bin builtin.bin dep dep.bin \ - devname softdep symbols symbols.bin; do + devname softdep symbols symbols.bin weakdep; do echo "%ghost /lib/modules/%{KERNELRELEASE}/modules.${x}" done @@ -85,15 +112,28 @@ ln -fns /usr/src/kernels/%{KERNELRELEASE} %{buildroot}/lib/modules/%{KERNELRELEA done if [ -d "%{buildroot}/lib/modules/%{KERNELRELEASE}/dtb" ];then - echo "/lib/modules/%{KERNELRELEASE}/dtb" find "%{buildroot}/lib/modules/%{KERNELRELEASE}/dtb" -printf "%%%ghost /boot/dtb-%{KERNELRELEASE}/%%P\n" fi echo "%exclude /lib/modules/%{KERNELRELEASE}/build" } > %{buildroot}/kernel.list +# make modules executable so that find-debuginfo.sh strips them. this +# will be undone later in %%__spec_install_post +find %{buildroot}/lib/modules/%{KERNELRELEASE} -name "*.ko" -type f \ + | xargs --no-run-if-empty chmod u+x + +%if %{with_debuginfo} +# copying vmlinux directly to the debug directory means it will not get +# stripped (but its source paths will still be collected + fixed up) +mkdir -p %{buildroot}/usr/lib/debug/lib/modules/%{KERNELRELEASE} +cp vmlinux %{buildroot}/usr/lib/debug/lib/modules/%{KERNELRELEASE} +%endif + %clean rm -rf %{buildroot} +rm -f debugfiles.list debuglinks.list debugsourcefiles.list debugsources.list \ + elfbins.list %post if [ -x /usr/bin/kernel-install ]; then diff --git a/scripts/package/mkdebian b/scripts/package/mkdebian index 070149c985fe..d4b007b38a47 100755 --- a/scripts/package/mkdebian +++ b/scripts/package/mkdebian @@ -4,7 +4,7 @@ # # Simple script to generate a debian/ directory for a Linux kernel. -set -e +set -eu is_enabled() { grep -q "^$1=y" include/config/auto.conf @@ -19,7 +19,7 @@ if_enabled_echo() { } set_debarch() { - if [ -n "$KBUILD_DEBARCH" ] ; then + if [ "${KBUILD_DEBARCH:+set}" ]; then debarch="$KBUILD_DEBARCH" return fi @@ -70,6 +70,15 @@ set_debarch() { debarch=sh4$(if_enabled_echo CONFIG_CPU_BIG_ENDIAN eb) fi ;; + um) + if is_enabled CONFIG_64BIT; then + debarch=amd64 + else + debarch=i386 + fi + ;; + loongarch64) + debarch=loong64 ;; esac if [ -z "$debarch" ]; then debarch=$(dpkg-architecture -qDEB_HOST_ARCH) @@ -125,32 +134,35 @@ gen_source () rm -rf debian mkdir debian -email=${DEBEMAIL-$EMAIL} - -# use email string directly if it contains <email> -if echo "${email}" | grep -q '<.*>'; then - maintainer=${email} +user=${KBUILD_BUILD_USER:-$(id -nu)} +name=${DEBFULLNAME:-${user}} +if [ "${DEBEMAIL:+set}" ]; then + email=${DEBEMAIL} else - # or construct the maintainer string - user=${KBUILD_BUILD_USER-$(id -nu)} - name=${DEBFULLNAME-${user}} - if [ -z "${email}" ]; then - buildhost=${KBUILD_BUILD_HOST-$(hostname -f 2>/dev/null || hostname)} - email="${user}@${buildhost}" - fi - maintainer="${name} <${email}>" + buildhost=${KBUILD_BUILD_HOST:-$(hostname -f 2>/dev/null || hostname)} + email="${user}@${buildhost}" fi +maintainer="${name} <${email}>" -if [ "$1" = --need-source ]; then - gen_source -fi +while [ $# -gt 0 ]; do + case "$1" in + --need-source) + gen_source + shift + ;; + *) + break + ;; + esac +done # Some variables and settings used throughout the script -version=$KERNELRELEASE -if [ -n "$KDEB_PKGVERSION" ]; then +if [ "${KDEB_PKGVERSION:+set}" ]; then packageversion=$KDEB_PKGVERSION else - packageversion=$(${srctree}/scripts/setlocalversion --no-local ${srctree})-$($srctree/init/build-version) + upstream_version=$("${srctree}/scripts/setlocalversion" --no-local "${srctree}" | sed 's/-\(rc[1-9]\)/~\1/') + debian_revision=$("${srctree}/scripts/build-version") + packageversion=${upstream_version}-${debian_revision} fi sourcename=${KDEB_SOURCENAME:-linux-upstream} @@ -164,7 +176,7 @@ debarch= set_debarch # Try to determine distribution -if [ -n "$KDEB_CHANGELOG_DIST" ]; then +if [ "${KDEB_CHANGELOG_DIST:+set}" ]; then distribution=$KDEB_CHANGELOG_DIST # In some cases lsb_release returns the codename as n/a, which breaks dpkg-parsechangelog elif distribution=$(lsb_release -cs 2>/dev/null) && [ -n "$distribution" ] && [ "$distribution" != "n/a" ]; then @@ -177,6 +189,8 @@ fi echo $debarch > debian/arch +host_gnu=$(dpkg-architecture -a "${debarch}" -q DEB_HOST_GNU_TYPE | sed 's/_/-/g') + # Generate a simple changelog template cat <<EOF > debian/changelog $sourcename ($packageversion) $distribution; urgency=low @@ -194,14 +208,18 @@ Priority: optional Maintainer: $maintainer Rules-Requires-Root: no Build-Depends: debhelper-compat (= 12) -Build-Depends-Arch: bc, bison, cpio, flex, kmod, libelf-dev:native, libssl-dev:native, rsync +Build-Depends-Arch: bc, bison, flex, + gcc-${host_gnu} <!pkg.${sourcename}.nokernelheaders>, + kmod, libdw-dev:native, libelf-dev:native, + libssl-dev:native, libssl-dev <!pkg.${sourcename}.nokernelheaders>, + python3:native, rsync Homepage: https://www.kernel.org/ -Package: $packagename-$version +Package: $packagename-${KERNELRELEASE} Architecture: $debarch -Description: Linux kernel, version $version +Description: Linux kernel, version ${KERNELRELEASE} This package contains the Linux kernel, modules and corresponding other - files, version: $version. + files, version: ${KERNELRELEASE}. EOF if [ "${SRCARCH}" != um ]; then @@ -220,10 +238,11 @@ EOF if is_enabled CONFIG_MODULES; then cat <<EOF >> debian/control -Package: linux-headers-$version +Package: linux-headers-${KERNELRELEASE} Architecture: $debarch -Description: Linux kernel headers for $version on $debarch - This package provides kernel header files for $version on $debarch +Build-Profiles: <!pkg.${sourcename}.nokernelheaders> +Description: Linux kernel headers for ${KERNELRELEASE} on $debarch + This package provides kernel header files for ${KERNELRELEASE} on $debarch . This is useful for people who need to build external modules EOF @@ -233,10 +252,11 @@ fi if is_enabled CONFIG_DEBUG_INFO; then cat <<EOF >> debian/control -Package: linux-image-$version-dbg +Package: linux-image-${KERNELRELEASE}-dbg Section: debug Architecture: $debarch -Description: Linux kernel debugging symbols for $version +Build-Profiles: <!pkg.${sourcename}.nokerneldbg> +Description: Linux kernel debugging symbols for ${KERNELRELEASE} This package will come in handy if you need to debug the kernel. It provides all the necessary debug symbols for the kernel and its modules. EOF diff --git a/scripts/package/mkspec b/scripts/package/mkspec index ce201bfa8377..c7375bfc25a9 100755 --- a/scripts/package/mkspec +++ b/scripts/package/mkspec @@ -9,6 +9,8 @@ # Patched for non-x86 by Opencon (L) 2002 <opencon@rio.skydome.net> # +set -eu + output=$1 mkdir -p "$(dirname "${output}")" @@ -21,10 +23,43 @@ else echo '%define with_devel 0' fi +# debuginfo package generation uses find-debuginfo.sh under the hood, +# which only works on uncompressed modules that contain debuginfo +if grep -q CONFIG_DEBUG_INFO=y include/config/auto.conf && + (! grep -q CONFIG_MODULE_COMPRESS=y include/config/auto.conf) && + (! grep -q CONFIG_DEBUG_INFO_SPLIT=y include/config/auto.conf); then +echo '%define with_debuginfo %{?_without_debuginfo: 0} %{?!_without_debuginfo: 1}' +else +echo '%define with_debuginfo 0' +fi + cat<<EOF %define ARCH ${ARCH} %define KERNELRELEASE ${KERNELRELEASE} -%define pkg_release $("${srctree}/init/build-version") +%define pkg_release $("${srctree}/scripts/build-version") EOF cat "${srctree}/scripts/package/kernel.spec" + +# collect the user's name and email address for the changelog entry +if [ "$(command -v git)" ]; then + name=$(git config user.name) || true + email=$(git config user.email) || true +fi + +if [ ! "${name:+set}" ]; then + name=${KBUILD_BUILD_USER:-$(id -nu)} +fi + +if [ ! "${email:+set}" ]; then + buildhost=${KBUILD_BUILD_HOST:-$(hostname -f 2>/dev/null || hostname)} + builduser=${KBUILD_BUILD_USER:-$(id -nu)} + email="${builduser}@${buildhost}" +fi + +cat << EOF + +%changelog +* $(LC_ALL=C date +'%a %b %d %Y') ${name} <${email}> +- Custom built Linux kernel. +EOF diff --git a/scripts/remove-stale-files b/scripts/remove-stale-files index 385610fe3936..6e39fa8540df 100755 --- a/scripts/remove-stale-files +++ b/scripts/remove-stale-files @@ -20,22 +20,9 @@ set -e # yard. Stale files stay in this file for a while (for some release cycles?), # then will be really dead and removed from the code base entirely. -rm -f arch/powerpc/purgatory/kexec-purgatory.c -rm -f arch/riscv/purgatory/kexec-purgatory.c -rm -f arch/x86/purgatory/kexec-purgatory.c - -rm -f scripts/extract-cert - -rm -f scripts/kconfig/[gmnq]conf-cfg - -rm -f rust/target.json - -rm -f scripts/bin2c - -rm -f .scmversion - -rm -rf include/ksym - -find . -name '*.usyms' | xargs rm -f +# moved to security/selinux/genheaders +rm -f scripts/selinux/genheaders/genheaders rm -f *.spec + +rm -f lib/test_fortify.log diff --git a/scripts/rust_is_available.sh b/scripts/rust_is_available.sh index 117018946b57..d2323de0692c 100755 --- a/scripts/rust_is_available.sh +++ b/scripts/rust_is_available.sh @@ -117,20 +117,18 @@ if [ "$rust_compiler_cversion" -lt "$rust_compiler_min_cversion" ]; then echo >&2 "***" exit 1 fi -if [ "$rust_compiler_cversion" -gt "$rust_compiler_min_cversion" ]; then - echo >&2 "***" - echo >&2 "*** Rust compiler '$RUSTC' is too new. This may or may not work." - echo >&2 "*** Your version: $rust_compiler_version" - echo >&2 "*** Expected version: $rust_compiler_min_version" - echo >&2 "***" - warning=1 -fi # Check that the Rust bindings generator is suitable. # # Non-stable and distributions' versions may have a version suffix, e.g. `-dev`. +# +# The dummy parameter `workaround-for-0.69.0` is required to support 0.69.0 +# (https://github.com/rust-lang/rust-bindgen/pull/2678) and 0.71.0 +# (https://github.com/rust-lang/rust-bindgen/pull/3040). It can be removed when +# the minimum version is upgraded past the latter (0.69.1 and 0.71.1 both fixed +# the issue). rust_bindings_generator_output=$( \ - LC_ALL=C "$BINDGEN" --version 2>/dev/null + LC_ALL=C "$BINDGEN" --version workaround-for-0.69.0 2>/dev/null ) || rust_bindings_generator_code=$? if [ -n "$rust_bindings_generator_code" ]; then echo >&2 "***" @@ -165,13 +163,18 @@ if [ "$rust_bindings_generator_cversion" -lt "$rust_bindings_generator_min_cvers echo >&2 "***" exit 1 fi -if [ "$rust_bindings_generator_cversion" -gt "$rust_bindings_generator_min_cversion" ]; then - echo >&2 "***" - echo >&2 "*** Rust bindings generator '$BINDGEN' is too new. This may or may not work." - echo >&2 "*** Your version: $rust_bindings_generator_version" - echo >&2 "*** Expected version: $rust_bindings_generator_min_version" - echo >&2 "***" - warning=1 +if [ "$rust_bindings_generator_cversion" -eq 6600 ] || + [ "$rust_bindings_generator_cversion" -eq 6601 ]; then + # Distributions may have patched the issue (e.g. Debian did). + if ! "$BINDGEN" $(dirname $0)/rust_is_available_bindgen_0_66.h >/dev/null; then + echo >&2 "***" + echo >&2 "*** Rust bindings generator '$BINDGEN' versions 0.66.0 and 0.66.1 may not" + echo >&2 "*** work due to a bug (https://github.com/rust-lang/rust-bindgen/pull/2567)," + echo >&2 "*** unless patched (like Debian's)." + echo >&2 "*** Your version: $rust_bindings_generator_version" + echo >&2 "***" + warning=1 + fi fi # Check that the `libclang` used by the Rust bindings generator is suitable. @@ -224,6 +227,21 @@ if [ "$bindgen_libclang_cversion" -lt "$bindgen_libclang_min_cversion" ]; then exit 1 fi +if [ "$bindgen_libclang_cversion" -ge 1900100 ] && + [ "$rust_bindings_generator_cversion" -lt 6905 ]; then + # Distributions may have patched the issue (e.g. Debian did). + if ! "$BINDGEN" $(dirname $0)/rust_is_available_bindgen_libclang_concat.h | grep -q foofoo; then + echo >&2 "***" + echo >&2 "*** Rust bindings generator '$BINDGEN' < 0.69.5 together with libclang >= 19.1" + echo >&2 "*** may not work due to a bug (https://github.com/rust-lang/rust-bindgen/pull/2824)," + echo >&2 "*** unless patched (like Debian's)." + echo >&2 "*** Your bindgen version: $rust_bindings_generator_version" + echo >&2 "*** Your libclang version: $bindgen_libclang_version" + echo >&2 "***" + warning=1 + fi +fi + # If the C compiler is Clang, then we can also check whether its version # matches the `libclang` version used by the Rust bindings generator. # diff --git a/scripts/rust_is_available_bindgen_0_66.h b/scripts/rust_is_available_bindgen_0_66.h new file mode 100644 index 000000000000..c0431293421c --- /dev/null +++ b/scripts/rust_is_available_bindgen_0_66.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define A "\0" diff --git a/scripts/rust_is_available_bindgen_libclang_concat.h b/scripts/rust_is_available_bindgen_libclang_concat.h new file mode 100644 index 000000000000..efc6e98d0f1d --- /dev/null +++ b/scripts/rust_is_available_bindgen_libclang_concat.h @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define F(x) int x##x +F(foo); diff --git a/scripts/rust_is_available_test.py b/scripts/rust_is_available_test.py index 57613fe5ed75..4fcc319dea84 100755 --- a/scripts/rust_is_available_test.py +++ b/scripts/rust_is_available_test.py @@ -54,18 +54,41 @@ else: """) @classmethod - def generate_bindgen(cls, version_stdout, libclang_stderr): + def generate_bindgen(cls, version_stdout, libclang_stderr, version_0_66_patched=False, libclang_concat_patched=False): + if libclang_stderr is None: + libclang_case = f"raise SystemExit({cls.bindgen_default_bindgen_libclang_failure_exit_code})" + else: + libclang_case = f"print({repr(libclang_stderr)}, file=sys.stderr)" + + if version_0_66_patched: + version_0_66_case = "pass" + else: + version_0_66_case = "raise SystemExit(1)" + + if libclang_concat_patched: + libclang_concat_case = "print('pub static mut foofoo: ::std::os::raw::c_int;')" + else: + libclang_concat_case = "pass" + return cls.generate_executable(f"""#!/usr/bin/env python3 import sys if "rust_is_available_bindgen_libclang.h" in " ".join(sys.argv): - print({repr(libclang_stderr)}, file=sys.stderr) + {libclang_case} +elif "rust_is_available_bindgen_0_66.h" in " ".join(sys.argv): + {version_0_66_case} +elif "rust_is_available_bindgen_libclang_concat.h" in " ".join(sys.argv): + {libclang_concat_case} else: print({repr(version_stdout)}) """) @classmethod - def generate_bindgen_version(cls, stdout): - return cls.generate_bindgen(stdout, cls.bindgen_default_bindgen_libclang_stderr) + def generate_bindgen_version(cls, stdout, version_0_66_patched=False): + return cls.generate_bindgen(stdout, cls.bindgen_default_bindgen_libclang_stderr, version_0_66_patched) + + @classmethod + def generate_bindgen_libclang_failure(cls): + return cls.generate_bindgen(cls.bindgen_default_bindgen_version_stdout, None) @classmethod def generate_bindgen_libclang(cls, stderr): @@ -89,6 +112,7 @@ else: cls.rust_default_sysroot = subprocess.check_output(("rustc", "--print", "sysroot")).decode().strip() cls.bindgen_default_bindgen_version_stdout = f"bindgen {cls.bindgen_default_version}" + cls.bindgen_default_bindgen_libclang_failure_exit_code = 42 cls.bindgen_default_bindgen_libclang_stderr = f"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {cls.llvm_default_version} [-W#pragma-messages], err: false" cls.default_rustc = cls.generate_rustc(f"rustc {cls.rustc_default_version}") @@ -193,11 +217,6 @@ else: result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc }) self.assertIn(f"Rust compiler '{rustc}' is too old.", result.stderr) - def test_rustc_new_version(self): - rustc = self.generate_rustc("rustc 1.999.0 (a8314ef7d 2099-06-27)") - result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "RUSTC": rustc }) - self.assertIn(f"Rust compiler '{rustc}' is too new. This may or may not work.", result.stderr) - def test_bindgen_nonexecutable(self): result = self.run_script(self.Expected.FAILURE, { "BINDGEN": self.nonexecutable }) self.assertIn(f"Running '{self.nonexecutable}' to check the Rust bindings generator version failed with", result.stderr) @@ -226,21 +245,24 @@ else: result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) self.assertIn(f"Rust bindings generator '{bindgen}' is too old.", result.stderr) - def test_bindgen_new_version(self): - bindgen = self.generate_bindgen_version("bindgen 0.999.0") - result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "BINDGEN": bindgen }) - self.assertIn(f"Rust bindings generator '{bindgen}' is too new. This may or may not work.", result.stderr) + def test_bindgen_bad_version_0_66_0_and_0_66_1(self): + for version in ("0.66.0", "0.66.1"): + with self.subTest(version=version): + bindgen = self.generate_bindgen_version(f"bindgen {version}") + result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "BINDGEN": bindgen }) + self.assertIn(f"Rust bindings generator '{bindgen}' versions 0.66.0 and 0.66.1 may not", result.stderr) + + def test_bindgen_bad_version_0_66_0_and_0_66_1_patched(self): + for version in ("0.66.0", "0.66.1"): + with self.subTest(version=version): + bindgen = self.generate_bindgen_version(f"bindgen {version}", True) + result = self.run_script(self.Expected.SUCCESS, { "BINDGEN": bindgen }) def test_bindgen_libclang_failure(self): - for env in ( - { "LLVM_CONFIG_PATH": self.missing }, - { "LIBCLANG_PATH": self.missing }, - { "CLANG_PATH": self.missing }, - ): - with self.subTest(env=env): - result = self.run_script(self.Expected.FAILURE, env | { "PATH": os.environ["PATH"], "BINDGEN": "bindgen" }) - self.assertIn("Running 'bindgen' to check the libclang version (used by the Rust", result.stderr) - self.assertIn("bindings generator) failed with code ", result.stderr) + bindgen = self.generate_bindgen_libclang_failure() + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) + self.assertIn(f"Running '{bindgen}' to check the libclang version (used by the Rust", result.stderr) + self.assertIn(f"bindings generator) failed with code {self.bindgen_default_bindgen_libclang_failure_exit_code}. This may be caused by", result.stderr) def test_bindgen_libclang_unexpected_version(self): bindgen = self.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version unexpected [-W#pragma-messages], err: false") @@ -253,6 +275,31 @@ else: result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) self.assertIn(f"libclang (used by the Rust bindings generator '{bindgen}') is too old.", result.stderr) + def test_bindgen_bad_libclang_concat(self): + for (bindgen_version, libclang_version, expected_not_patched) in ( + ("0.69.4", "18.0.0", self.Expected.SUCCESS), + ("0.69.4", "19.1.0", self.Expected.SUCCESS_WITH_WARNINGS), + ("0.69.4", "19.2.0", self.Expected.SUCCESS_WITH_WARNINGS), + + ("0.69.5", "18.0.0", self.Expected.SUCCESS), + ("0.69.5", "19.1.0", self.Expected.SUCCESS), + ("0.69.5", "19.2.0", self.Expected.SUCCESS), + + ("0.70.0", "18.0.0", self.Expected.SUCCESS), + ("0.70.0", "19.1.0", self.Expected.SUCCESS), + ("0.70.0", "19.2.0", self.Expected.SUCCESS), + ): + with self.subTest(bindgen_version=bindgen_version, libclang_version=libclang_version): + cc = self.generate_clang(f"clang version {libclang_version}") + libclang_stderr = f"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {libclang_version} [-W#pragma-messages], err: false" + bindgen = self.generate_bindgen(f"bindgen {bindgen_version}", libclang_stderr) + result = self.run_script(expected_not_patched, { "BINDGEN": bindgen, "CC": cc }) + if expected_not_patched == self.Expected.SUCCESS_WITH_WARNINGS: + self.assertIn(f"Rust bindings generator '{bindgen}' < 0.69.5 together with libclang >= 19.1", result.stderr) + + bindgen = self.generate_bindgen(f"bindgen {bindgen_version}", libclang_stderr, libclang_concat_patched=True) + result = self.run_script(self.Expected.SUCCESS, { "BINDGEN": bindgen, "CC": cc }) + def test_clang_matches_bindgen_libclang_different_bindgen(self): bindgen = self.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version 999.0.0 [-W#pragma-messages], err: false") result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "BINDGEN": bindgen }) diff --git a/scripts/rustc-llvm-version.sh b/scripts/rustc-llvm-version.sh new file mode 100755 index 000000000000..a500d1ae3101 --- /dev/null +++ b/scripts/rustc-llvm-version.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Usage: $ ./rustc-llvm-version.sh rustc +# +# Print the LLVM version that the Rust compiler uses in a 6 digit form. + +# Convert the version string x.y.z to a canonical up-to-6-digits form. +get_canonical_version() +{ + IFS=. + set -- $1 + echo $((10000 * $1 + 100 * $2 + $3)) +} + +if output=$("$@" --version --verbose 2>/dev/null | grep -E 'LLVM.*[0-9]+\.[0-9]+\.[0-9]+'); then + set -- $output + get_canonical_version $3 +else + echo 0 + exit 1 +fi diff --git a/scripts/rustc-version.sh b/scripts/rustc-version.sh new file mode 100755 index 000000000000..4e22593e2eab --- /dev/null +++ b/scripts/rustc-version.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Usage: $ ./rustc-version.sh rustc +# +# Print the Rust compiler version in a 6 or 7-digit form. + +# Convert the version string x.y.z to a canonical up-to-7-digits form. +# +# Note that this function uses one more digit (compared to other +# instances in other version scripts) to give a bit more space to +# `rustc` since it will reach 1.100.0 in late 2026. +get_canonical_version() +{ + IFS=. + set -- $1 + echo $((100000 * $1 + 100 * $2 + $3)) +} + +if output=$("$@" --version 2>/dev/null); then + set -- $output + get_canonical_version $2 +else + echo 0 + exit 1 +fi diff --git a/scripts/rustdoc_test_builder.rs b/scripts/rustdoc_test_builder.rs index e5894652f12c..f7540bcf595a 100644 --- a/scripts/rustdoc_test_builder.rs +++ b/scripts/rustdoc_test_builder.rs @@ -28,7 +28,7 @@ fn main() { // // ``` // fn main() { #[allow(non_snake_case)] fn _doctest_main_rust_kernel_file_rs_28_0() { - // fn main() { #[allow(non_snake_case)] fn _doctest_main_rust_kernel_file_rs_37_0() -> Result<(), impl core::fmt::Debug> { + // fn main() { #[allow(non_snake_case)] fn _doctest_main_rust_kernel_file_rs_37_0() -> Result<(), impl ::core::fmt::Debug> { // ``` // // It should be unlikely that doctest code matches such lines (when code is formatted properly). @@ -49,8 +49,10 @@ fn main() { // Qualify `Result` to avoid the collision with our own `Result` coming from the prelude. let body = body.replace( - &format!("{rustdoc_function_name}() -> Result<(), impl core::fmt::Debug> {{"), - &format!("{rustdoc_function_name}() -> core::result::Result<(), impl core::fmt::Debug> {{"), + &format!("{rustdoc_function_name}() -> Result<(), impl ::core::fmt::Debug> {{"), + &format!( + "{rustdoc_function_name}() -> ::core::result::Result<(), impl ::core::fmt::Debug> {{" + ), ); // For tests that get generated with `Result`, like above, `rustdoc` generates an `unwrap()` on diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs index 5ebd42ae4a3f..1ca253594d38 100644 --- a/scripts/rustdoc_test_gen.rs +++ b/scripts/rustdoc_test_gen.rs @@ -15,8 +15,8 @@ //! - Test code should be able to define functions and call them, without having to carry //! the context. //! -//! - Later on, we may want to be able to test non-kernel code (e.g. `core`, `alloc` or -//! third-party crates) which likely use the standard library `assert*!` macros. +//! - Later on, we may want to be able to test non-kernel code (e.g. `core` or third-party +//! crates) which likely use the standard library `assert*!` macros. //! //! For this reason, instead of the passed context, `kunit_get_current_test()` is used instead //! (i.e. `current->kunit_test`). @@ -87,8 +87,8 @@ fn find_real_path<'a>(srctree: &Path, valid_paths: &'a mut Vec<PathBuf>, file: & assert!( valid_paths.len() > 0, - "No path candidates found. This is likely a bug in the build system, or some files went \ - away while compiling." + "No path candidates found for `{file}`. This is likely a bug in the build system, or some \ + files went away while compiling." ); if valid_paths.len() > 1 { @@ -97,8 +97,8 @@ fn find_real_path<'a>(srctree: &Path, valid_paths: &'a mut Vec<PathBuf>, file: & eprintln!(" {path:?}"); } panic!( - "Several path candidates found, please resolve the ambiguity by renaming a file or \ - folder." + "Several path candidates found for `{file}`, please resolve the ambiguity by renaming \ + a file or folder." ); } @@ -167,12 +167,14 @@ fn main() { rust_tests, r#"/// Generated `{name}` KUnit test case from a Rust documentation test. #[no_mangle] -pub extern "C" fn {kunit_name}(__kunit_test: *mut kernel::bindings::kunit) {{ +pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. #[allow(unused)] macro_rules! assert {{ ($cond:expr $(,)?) => {{{{ - kernel::kunit_assert!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $cond); + ::kernel::kunit_assert!( + "{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $cond + ); }}}} }} @@ -180,13 +182,15 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut kernel::bindings::kunit) {{ #[allow(unused)] macro_rules! assert_eq {{ ($left:expr, $right:expr $(,)?) => {{{{ - kernel::kunit_assert_eq!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right); + ::kernel::kunit_assert_eq!( + "{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right + ); }}}} }} // Many tests need the prelude, so provide it by default. #[allow(unused)] - use kernel::prelude::*; + use ::kernel::prelude::*; // Unconditionally print the location of the original doctest (i.e. rather than the location in // the generated file) so that developers can easily map the test back to the source code. @@ -197,11 +201,11 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut kernel::bindings::kunit) {{ // This follows the syntax for declaring test metadata in the proposed KTAP v2 spec, which may // be used for the proposed KUnit test attributes API. Thus hopefully this will make migration // easier later on. - kernel::kunit::info(format_args!(" # {kunit_name}.location: {real_path}:{line}\n")); + ::kernel::kunit::info(format_args!(" # {kunit_name}.location: {real_path}:{line}\n")); /// The anchor where the test code body starts. #[allow(unused)] - static __DOCTEST_ANCHOR: i32 = core::line!() as i32 + {body_offset} + 1; + static __DOCTEST_ANCHOR: i32 = ::core::line!() as i32 + {body_offset} + 1; {{ {body} main(); diff --git a/scripts/selinux/Makefile b/scripts/selinux/Makefile index 59494e14989b..4b1308fa5732 100644 --- a/scripts/selinux/Makefile +++ b/scripts/selinux/Makefile @@ -1,2 +1,2 @@ # SPDX-License-Identifier: GPL-2.0-only -subdir-y := mdp genheaders +subdir-y := mdp diff --git a/scripts/selinux/genheaders/Makefile b/scripts/selinux/genheaders/Makefile deleted file mode 100644 index 1faf7f07e8db..000000000000 --- a/scripts/selinux/genheaders/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -hostprogs-always-y += genheaders -HOST_EXTRACFLAGS += \ - -I$(srctree)/include/uapi -I$(srctree)/include \ - -I$(srctree)/security/selinux/include diff --git a/scripts/selinux/genheaders/genheaders.c b/scripts/selinux/genheaders/genheaders.c deleted file mode 100644 index 15520806889e..000000000000 --- a/scripts/selinux/genheaders/genheaders.c +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/* NOTE: we really do want to use the kernel headers here */ -#define __EXPORTED_HEADERS__ - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <ctype.h> - -struct security_class_mapping { - const char *name; - const char *perms[sizeof(unsigned) * 8 + 1]; -}; - -#include "classmap.h" -#include "initial_sid_to_string.h" - -const char *progname; - -static void usage(void) -{ - printf("usage: %s flask.h av_permissions.h\n", progname); - exit(1); -} - -static char *stoupperx(const char *s) -{ - char *s2 = strdup(s); - char *p; - - if (!s2) { - fprintf(stderr, "%s: out of memory\n", progname); - exit(3); - } - - for (p = s2; *p; p++) - *p = toupper(*p); - return s2; -} - -int main(int argc, char *argv[]) -{ - int i, j; - int isids_len; - FILE *fout; - - progname = argv[0]; - - if (argc < 3) - usage(); - - fout = fopen(argv[1], "w"); - if (!fout) { - fprintf(stderr, "Could not open %s for writing: %s\n", - argv[1], strerror(errno)); - exit(2); - } - - fprintf(fout, "/* This file is automatically generated. Do not edit. */\n"); - fprintf(fout, "#ifndef _SELINUX_FLASK_H_\n#define _SELINUX_FLASK_H_\n\n"); - - for (i = 0; secclass_map[i].name; i++) { - char *name = stoupperx(secclass_map[i].name); - - fprintf(fout, "#define SECCLASS_%-39s %2d\n", name, i+1); - free(name); - } - - fprintf(fout, "\n"); - - isids_len = sizeof(initial_sid_to_string) / sizeof(char *); - for (i = 1; i < isids_len; i++) { - const char *s = initial_sid_to_string[i]; - if (s) { - char *sidname = stoupperx(s); - - fprintf(fout, "#define SECINITSID_%-39s %2d\n", sidname, i); - free(sidname); - } - } - fprintf(fout, "\n#define SECINITSID_NUM %d\n", i-1); - fprintf(fout, "\nstatic inline bool security_is_socket_class(u16 kern_tclass)\n"); - fprintf(fout, "{\n"); - fprintf(fout, "\tbool sock = false;\n\n"); - fprintf(fout, "\tswitch (kern_tclass) {\n"); - for (i = 0; secclass_map[i].name; i++) { - static char s[] = "SOCKET"; - int len, l; - char *name = stoupperx(secclass_map[i].name); - - len = strlen(name); - l = sizeof(s) - 1; - if (len >= l && memcmp(name + len - l, s, l) == 0) - fprintf(fout, "\tcase SECCLASS_%s:\n", name); - free(name); - } - fprintf(fout, "\t\tsock = true;\n"); - fprintf(fout, "\t\tbreak;\n"); - fprintf(fout, "\tdefault:\n"); - fprintf(fout, "\t\tbreak;\n"); - fprintf(fout, "\t}\n\n"); - fprintf(fout, "\treturn sock;\n"); - fprintf(fout, "}\n"); - - fprintf(fout, "\n#endif\n"); - - if (fclose(fout) != 0) { - fprintf(stderr, "Could not successfully close %s: %s\n", - argv[1], strerror(errno)); - exit(4); - } - - fout = fopen(argv[2], "w"); - if (!fout) { - fprintf(stderr, "Could not open %s for writing: %s\n", - argv[2], strerror(errno)); - exit(5); - } - - fprintf(fout, "/* This file is automatically generated. Do not edit. */\n"); - fprintf(fout, "#ifndef _SELINUX_AV_PERMISSIONS_H_\n#define _SELINUX_AV_PERMISSIONS_H_\n\n"); - - for (i = 0; secclass_map[i].name; i++) { - const struct security_class_mapping *map = &secclass_map[i]; - int len; - char *name = stoupperx(map->name); - - len = strlen(name); - for (j = 0; map->perms[j]; j++) { - char *permname; - - if (j >= 32) { - fprintf(stderr, "Too many permissions to fit into an access vector at (%s, %s).\n", - map->name, map->perms[j]); - exit(5); - } - permname = stoupperx(map->perms[j]); - fprintf(fout, "#define %s__%-*s 0x%08xU\n", name, - 39-len, permname, 1U<<j); - free(permname); - } - free(name); - } - - fprintf(fout, "\n#endif\n"); - - if (fclose(fout) != 0) { - fprintf(stderr, "Could not successfully close %s: %s\n", - argv[2], strerror(errno)); - exit(6); - } - - exit(0); -} diff --git a/scripts/selinux/install_policy.sh b/scripts/selinux/install_policy.sh index 24086793b0d8..db40237e60ce 100755 --- a/scripts/selinux/install_policy.sh +++ b/scripts/selinux/install_policy.sh @@ -6,27 +6,24 @@ if [ `id -u` -ne 0 ]; then exit 1 fi -SF=`which setfiles` -if [ $? -eq 1 ]; then +SF=`which setfiles` || { echo "Could not find setfiles" echo "Do you have policycoreutils installed?" exit 1 -fi +} -CP=`which checkpolicy` -if [ $? -eq 1 ]; then +CP=`which checkpolicy` || { echo "Could not find checkpolicy" echo "Do you have checkpolicy installed?" exit 1 -fi +} VERS=`$CP -V | awk '{print $1}'` -ENABLED=`which selinuxenabled` -if [ $? -eq 1 ]; then +ENABLED=`which selinuxenabled` || { echo "Could not find selinuxenabled" echo "Do you have libselinux-utils installed?" exit 1 -fi +} if selinuxenabled; then echo "SELinux is already enabled" diff --git a/scripts/selinux/mdp/Makefile b/scripts/selinux/mdp/Makefile index d61058ddd15c..673782e3212f 100644 --- a/scripts/selinux/mdp/Makefile +++ b/scripts/selinux/mdp/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 hostprogs-always-y += mdp HOST_EXTRACFLAGS += \ - -I$(srctree)/include/uapi -I$(srctree)/include \ + -I$(srctree)/include \ -I$(srctree)/security/selinux/include -I$(objtree)/include clean-files := policy.* file_contexts diff --git a/scripts/selinux/mdp/mdp.c b/scripts/selinux/mdp/mdp.c index 1415604c3d24..ea7fbe595971 100644 --- a/scripts/selinux/mdp/mdp.c +++ b/scripts/selinux/mdp/mdp.c @@ -11,10 +11,6 @@ * Authors: Serge E. Hallyn <serue@us.ibm.com> */ - -/* NOTE: we really do want to use the kernel headers here */ -#define __EXPORTED_HEADERS__ - #include <stdio.h> #include <stdlib.h> #include <unistd.h> @@ -171,9 +167,6 @@ int main(int argc, char *argv[]) #ifdef CONFIG_JFS_SECURITY FS_USE("xattr", "jfs"); #endif -#ifdef CONFIG_REISERFS_FS_SECURITY - FS_USE("xattr", "reiserfs"); -#endif #ifdef CONFIG_JFFS2_FS_SECURITY FS_USE("xattr", "jffs2"); #endif diff --git a/scripts/setlocalversion b/scripts/setlocalversion index 38b96c6797f4..28169d7e143b 100755 --- a/scripts/setlocalversion +++ b/scripts/setlocalversion @@ -10,6 +10,8 @@ # # +set -e + usage() { echo "Usage: $0 [--no-local] [srctree]" >&2 exit 1 @@ -30,6 +32,29 @@ if test $# -gt 0 -o ! -d "$srctree"; then usage fi +try_tag() { + tag="$1" + + # Is $tag an annotated tag? + if [ "$(git cat-file -t "$tag" 2> /dev/null)" != tag ]; then + return + fi + + # Is it an ancestor of HEAD, and if so, how many commits are in $tag..HEAD? + # shellcheck disable=SC2046 # word splitting is the point here + set -- $(git rev-list --count --left-right "$tag"...HEAD 2> /dev/null) + + # $1 is 0 if and only if $tag is an ancestor of HEAD. Use + # string comparison, because $1 is empty if the 'git rev-list' + # command somehow failed. + if [ "$1" != 0 ]; then + return + fi + + # $2 is the number of commits in the range $tag..HEAD, possibly 0. + count="$2" +} + scm_version() { local short=false @@ -61,33 +86,33 @@ scm_version() # stable kernel: 6.1.7 -> v6.1.7 version_tag=v$(echo "${KERNELVERSION}" | sed -E 's/^([0-9]+\.[0-9]+)\.0(.*)$/\1\2/') + # try_tag initializes count if the tag is usable. + count= + # If a localversion* file exists, and the corresponding # annotated tag exists and is an ancestor of HEAD, use # it. This is the case in linux-next. - tag=${file_localversion#-} - desc= - if [ -n "${tag}" ]; then - desc=$(git describe --match=$tag 2>/dev/null) + if [ -n "${file_localversion#-}" ] ; then + try_tag "${file_localversion#-}" fi # Otherwise, if a localversion* file exists, and the tag # obtained by appending it to the tag derived from # KERNELVERSION exists and is an ancestor of HEAD, use # it. This is e.g. the case in linux-rt. - if [ -z "${desc}" ] && [ -n "${file_localversion}" ]; then - tag="${version_tag}${file_localversion}" - desc=$(git describe --match=$tag 2>/dev/null) + if [ -z "${count}" ] && [ -n "${file_localversion}" ]; then + try_tag "${version_tag}${file_localversion}" fi # Otherwise, default to the annotated tag derived from KERNELVERSION. - if [ -z "${desc}" ]; then - tag="${version_tag}" - desc=$(git describe --match=$tag 2>/dev/null) + if [ -z "${count}" ]; then + try_tag "${version_tag}" fi - # If we are at the tagged commit, we ignore it because the version is - # well-defined. - if [ "${tag}" != "${desc}" ]; then + # If we are at the tagged commit, we ignore it because the + # version is well-defined. If none of the attempted tags exist + # or were usable, $count is still empty. + if [ -z "${count}" ] || [ "${count}" -gt 0 ]; then # If only the short version is requested, don't bother # running further git commands @@ -95,14 +120,15 @@ scm_version() echo "+" return fi + # If we are past the tagged commit, we pretty print it. # (like 6.1.0-14595-g292a089d78d3) - if [ -n "${desc}" ]; then - echo "${desc}" | awk -F- '{printf("-%05d", $(NF-1))}' + if [ -n "${count}" ]; then + printf "%s%05d" "-" "${count}" fi # Add -g and exactly 12 hex chars. - printf '%s%s' -g "$(echo $head | cut -c1-12)" + printf '%s%.12s' -g "$head" fi if ${no_dirty}; then diff --git a/scripts/show_delta b/scripts/show_delta index 291ad65e3089..3755b6c6e557 100755 --- a/scripts/show_delta +++ b/scripts/show_delta @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only # # show_deltas: Read list of printk messages instrumented with diff --git a/scripts/sign-file.c b/scripts/sign-file.c index 3edb156ae52c..7070245edfc1 100644 --- a/scripts/sign-file.c +++ b/scripts/sign-file.c @@ -27,14 +27,17 @@ #include <openssl/evp.h> #include <openssl/pem.h> #include <openssl/err.h> -#include <openssl/engine.h> - -/* - * OpenSSL 3.0 deprecates the OpenSSL's ENGINE API. - * - * Remove this if/when that API is no longer used - */ -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#if OPENSSL_VERSION_MAJOR >= 3 +# define USE_PKCS11_PROVIDER +# include <openssl/provider.h> +# include <openssl/store.h> +#else +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# define USE_PKCS11_ENGINE +# include <openssl/engine.h> +# endif +#endif +#include "ssl-common.h" /* * Use CMS if we have openssl-1.0.0 or newer available - otherwise we have to @@ -83,41 +86,6 @@ void format(void) exit(2); } -static void display_openssl_errors(int l) -{ - const char *file; - char buf[120]; - int e, line; - - if (ERR_peek_error() == 0) - return; - fprintf(stderr, "At main.c:%d:\n", l); - - while ((e = ERR_get_error_line(&file, &line))) { - ERR_error_string(e, buf); - fprintf(stderr, "- SSL %s: %s:%d\n", buf, file, line); - } -} - -static void drain_openssl_errors(void) -{ - const char *file; - int line; - - if (ERR_peek_error() == 0) - return; - while (ERR_get_error_line(&file, &line)) {} -} - -#define ERR(cond, fmt, ...) \ - do { \ - bool __cond = (cond); \ - display_openssl_errors(__LINE__); \ - if (__cond) { \ - errx(1, fmt, ## __VA_ARGS__); \ - } \ - } while(0) - static const char *key_pass; static int pem_pw_cb(char *buf, int len, int w, void *v) @@ -139,28 +107,64 @@ static int pem_pw_cb(char *buf, int len, int w, void *v) return pwlen; } -static EVP_PKEY *read_private_key(const char *private_key_name) +static EVP_PKEY *read_private_key_pkcs11(const char *private_key_name) { - EVP_PKEY *private_key; + EVP_PKEY *private_key = NULL; +#ifdef USE_PKCS11_PROVIDER + OSSL_STORE_CTX *store; + if (!OSSL_PROVIDER_try_load(NULL, "pkcs11", true)) + ERR(1, "OSSL_PROVIDER_try_load(pkcs11)"); + if (!OSSL_PROVIDER_try_load(NULL, "default", true)) + ERR(1, "OSSL_PROVIDER_try_load(default)"); + + store = OSSL_STORE_open(private_key_name, NULL, NULL, NULL, NULL); + ERR(!store, "OSSL_STORE_open"); + + while (!OSSL_STORE_eof(store)) { + OSSL_STORE_INFO *info = OSSL_STORE_load(store); + + if (!info) { + drain_openssl_errors(__LINE__, 0); + continue; + } + if (OSSL_STORE_INFO_get_type(info) == OSSL_STORE_INFO_PKEY) { + private_key = OSSL_STORE_INFO_get1_PKEY(info); + ERR(!private_key, "OSSL_STORE_INFO_get1_PKEY"); + } + OSSL_STORE_INFO_free(info); + if (private_key) + break; + } + OSSL_STORE_close(store); +#elif defined(USE_PKCS11_ENGINE) + ENGINE *e; + + ENGINE_load_builtin_engines(); + drain_openssl_errors(__LINE__, 1); + e = ENGINE_by_id("pkcs11"); + ERR(!e, "Load PKCS#11 ENGINE"); + if (ENGINE_init(e)) + drain_openssl_errors(__LINE__, 1); + else + ERR(1, "ENGINE_init"); + if (key_pass) + ERR(!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0), "Set PKCS#11 PIN"); + private_key = ENGINE_load_private_key(e, private_key_name, NULL, NULL); + ERR(!private_key, "%s", private_key_name); +#else + fprintf(stderr, "no pkcs11 engine/provider available\n"); + exit(1); +#endif + return private_key; +} + +static EVP_PKEY *read_private_key(const char *private_key_name) +{ if (!strncmp(private_key_name, "pkcs11:", 7)) { - ENGINE *e; - - ENGINE_load_builtin_engines(); - drain_openssl_errors(); - e = ENGINE_by_id("pkcs11"); - ERR(!e, "Load PKCS#11 ENGINE"); - if (ENGINE_init(e)) - drain_openssl_errors(); - else - ERR(1, "ENGINE_init"); - if (key_pass) - ERR(!ENGINE_ctrl_cmd_string(e, "PIN", key_pass, 0), - "Set PKCS#11 PIN"); - private_key = ENGINE_load_private_key(e, private_key_name, - NULL, NULL); - ERR(!private_key, "%s", private_key_name); + return read_private_key_pkcs11(private_key_name); } else { + EVP_PKEY *private_key; BIO *b; b = BIO_new_file(private_key_name, "rb"); @@ -169,9 +173,9 @@ static EVP_PKEY *read_private_key(const char *private_key_name) NULL); ERR(!private_key, "%s", private_key_name); BIO_free(b); - } - return private_key; + return private_key; + } } static X509 *read_x509(const char *x509_name) @@ -306,7 +310,7 @@ int main(int argc, char **argv) /* Digest the module data. */ OpenSSL_add_all_digests(); - display_openssl_errors(__LINE__); + drain_openssl_errors(__LINE__, 0); digest_algo = EVP_get_digestbyname(hash_algo); ERR(!digest_algo, "EVP_get_digestbyname"); diff --git a/scripts/sorttable.c b/scripts/sorttable.c index 83cdb843d92f..deed676bfe38 100644 --- a/scripts/sorttable.c +++ b/scripts/sorttable.c @@ -28,6 +28,7 @@ #include <fcntl.h> #include <stdio.h> #include <stdlib.h> +#include <stdbool.h> #include <string.h> #include <unistd.h> #include <errno.h> @@ -64,14 +65,246 @@ #define EM_LOONGARCH 258 #endif +typedef union { + Elf32_Ehdr e32; + Elf64_Ehdr e64; +} Elf_Ehdr; + +typedef union { + Elf32_Shdr e32; + Elf64_Shdr e64; +} Elf_Shdr; + +typedef union { + Elf32_Sym e32; + Elf64_Sym e64; +} Elf_Sym; + +typedef union { + Elf32_Rela e32; + Elf64_Rela e64; +} Elf_Rela; + static uint32_t (*r)(const uint32_t *); static uint16_t (*r2)(const uint16_t *); static uint64_t (*r8)(const uint64_t *); static void (*w)(uint32_t, uint32_t *); -static void (*w2)(uint16_t, uint16_t *); static void (*w8)(uint64_t, uint64_t *); typedef void (*table_sort_t)(char *, int); +static struct elf_funcs { + int (*compare_extable)(const void *a, const void *b); + uint64_t (*ehdr_shoff)(Elf_Ehdr *ehdr); + uint16_t (*ehdr_shstrndx)(Elf_Ehdr *ehdr); + uint16_t (*ehdr_shentsize)(Elf_Ehdr *ehdr); + uint16_t (*ehdr_shnum)(Elf_Ehdr *ehdr); + uint64_t (*shdr_addr)(Elf_Shdr *shdr); + uint64_t (*shdr_offset)(Elf_Shdr *shdr); + uint64_t (*shdr_size)(Elf_Shdr *shdr); + uint64_t (*shdr_entsize)(Elf_Shdr *shdr); + uint32_t (*shdr_link)(Elf_Shdr *shdr); + uint32_t (*shdr_name)(Elf_Shdr *shdr); + uint32_t (*shdr_type)(Elf_Shdr *shdr); + uint8_t (*sym_type)(Elf_Sym *sym); + uint32_t (*sym_name)(Elf_Sym *sym); + uint64_t (*sym_value)(Elf_Sym *sym); + uint16_t (*sym_shndx)(Elf_Sym *sym); + uint64_t (*rela_offset)(Elf_Rela *rela); + uint64_t (*rela_info)(Elf_Rela *rela); + uint64_t (*rela_addend)(Elf_Rela *rela); + void (*rela_write_addend)(Elf_Rela *rela, uint64_t val); +} e; + +static uint64_t ehdr64_shoff(Elf_Ehdr *ehdr) +{ + return r8(&ehdr->e64.e_shoff); +} + +static uint64_t ehdr32_shoff(Elf_Ehdr *ehdr) +{ + return r(&ehdr->e32.e_shoff); +} + +static uint64_t ehdr_shoff(Elf_Ehdr *ehdr) +{ + return e.ehdr_shoff(ehdr); +} + +#define EHDR_HALF(fn_name) \ +static uint16_t ehdr64_##fn_name(Elf_Ehdr *ehdr) \ +{ \ + return r2(&ehdr->e64.e_##fn_name); \ +} \ + \ +static uint16_t ehdr32_##fn_name(Elf_Ehdr *ehdr) \ +{ \ + return r2(&ehdr->e32.e_##fn_name); \ +} \ + \ +static uint16_t ehdr_##fn_name(Elf_Ehdr *ehdr) \ +{ \ + return e.ehdr_##fn_name(ehdr); \ +} + +EHDR_HALF(shentsize) +EHDR_HALF(shstrndx) +EHDR_HALF(shnum) + +#define SHDR_WORD(fn_name) \ +static uint32_t shdr64_##fn_name(Elf_Shdr *shdr) \ +{ \ + return r(&shdr->e64.sh_##fn_name); \ +} \ + \ +static uint32_t shdr32_##fn_name(Elf_Shdr *shdr) \ +{ \ + return r(&shdr->e32.sh_##fn_name); \ +} \ + \ +static uint32_t shdr_##fn_name(Elf_Shdr *shdr) \ +{ \ + return e.shdr_##fn_name(shdr); \ +} + +#define SHDR_ADDR(fn_name) \ +static uint64_t shdr64_##fn_name(Elf_Shdr *shdr) \ +{ \ + return r8(&shdr->e64.sh_##fn_name); \ +} \ + \ +static uint64_t shdr32_##fn_name(Elf_Shdr *shdr) \ +{ \ + return r(&shdr->e32.sh_##fn_name); \ +} \ + \ +static uint64_t shdr_##fn_name(Elf_Shdr *shdr) \ +{ \ + return e.shdr_##fn_name(shdr); \ +} + +#define SHDR_WORD(fn_name) \ +static uint32_t shdr64_##fn_name(Elf_Shdr *shdr) \ +{ \ + return r(&shdr->e64.sh_##fn_name); \ +} \ + \ +static uint32_t shdr32_##fn_name(Elf_Shdr *shdr) \ +{ \ + return r(&shdr->e32.sh_##fn_name); \ +} \ +static uint32_t shdr_##fn_name(Elf_Shdr *shdr) \ +{ \ + return e.shdr_##fn_name(shdr); \ +} + +SHDR_ADDR(addr) +SHDR_ADDR(offset) +SHDR_ADDR(size) +SHDR_ADDR(entsize) + +SHDR_WORD(link) +SHDR_WORD(name) +SHDR_WORD(type) + +#define SYM_ADDR(fn_name) \ +static uint64_t sym64_##fn_name(Elf_Sym *sym) \ +{ \ + return r8(&sym->e64.st_##fn_name); \ +} \ + \ +static uint64_t sym32_##fn_name(Elf_Sym *sym) \ +{ \ + return r(&sym->e32.st_##fn_name); \ +} \ + \ +static uint64_t sym_##fn_name(Elf_Sym *sym) \ +{ \ + return e.sym_##fn_name(sym); \ +} + +#define SYM_WORD(fn_name) \ +static uint32_t sym64_##fn_name(Elf_Sym *sym) \ +{ \ + return r(&sym->e64.st_##fn_name); \ +} \ + \ +static uint32_t sym32_##fn_name(Elf_Sym *sym) \ +{ \ + return r(&sym->e32.st_##fn_name); \ +} \ + \ +static uint32_t sym_##fn_name(Elf_Sym *sym) \ +{ \ + return e.sym_##fn_name(sym); \ +} + +#define SYM_HALF(fn_name) \ +static uint16_t sym64_##fn_name(Elf_Sym *sym) \ +{ \ + return r2(&sym->e64.st_##fn_name); \ +} \ + \ +static uint16_t sym32_##fn_name(Elf_Sym *sym) \ +{ \ + return r2(&sym->e32.st_##fn_name); \ +} \ + \ +static uint16_t sym_##fn_name(Elf_Sym *sym) \ +{ \ + return e.sym_##fn_name(sym); \ +} + +static uint8_t sym64_type(Elf_Sym *sym) +{ + return ELF64_ST_TYPE(sym->e64.st_info); +} + +static uint8_t sym32_type(Elf_Sym *sym) +{ + return ELF32_ST_TYPE(sym->e32.st_info); +} + +static uint8_t sym_type(Elf_Sym *sym) +{ + return e.sym_type(sym); +} + +SYM_ADDR(value) +SYM_WORD(name) +SYM_HALF(shndx) + +#define __maybe_unused __attribute__((__unused__)) + +#define RELA_ADDR(fn_name) \ +static uint64_t rela64_##fn_name(Elf_Rela *rela) \ +{ \ + return r8((uint64_t *)&rela->e64.r_##fn_name); \ +} \ + \ +static uint64_t rela32_##fn_name(Elf_Rela *rela) \ +{ \ + return r((uint32_t *)&rela->e32.r_##fn_name); \ +} \ + \ +static uint64_t __maybe_unused rela_##fn_name(Elf_Rela *rela) \ +{ \ + return e.rela_##fn_name(rela); \ +} + +RELA_ADDR(offset) +RELA_ADDR(info) +RELA_ADDR(addend) + +static void rela64_write_addend(Elf_Rela *rela, uint64_t val) +{ + w8(val, (uint64_t *)&rela->e64.r_addend); +} + +static void rela32_write_addend(Elf_Rela *rela, uint64_t val) +{ + w(val, (uint32_t *)&rela->e32.r_addend); +} + /* * Get the whole file as a programming convenience in order to avoid * malloc+lseek+read+free of many pieces. If successful, then mmap @@ -146,24 +379,14 @@ static void wbe(uint32_t val, uint32_t *x) put_unaligned_be32(val, x); } -static void w2be(uint16_t val, uint16_t *x) -{ - put_unaligned_be16(val, x); -} - -static void w8be(uint64_t val, uint64_t *x) -{ - put_unaligned_be64(val, x); -} - static void wle(uint32_t val, uint32_t *x) { put_unaligned_le32(val, x); } -static void w2le(uint16_t val, uint16_t *x) +static void w8be(uint64_t val, uint64_t *x) { - put_unaligned_le16(val, x); + put_unaligned_be64(val, x); } static void w8le(uint64_t val, uint64_t *x) @@ -195,10 +418,740 @@ static inline unsigned int get_secindex(unsigned int shndx, return r(&symtab_shndx_start[sym_offs]); } -/* 32 bit and 64 bit are very similar */ -#include "sorttable.h" -#define SORTTABLE_64 -#include "sorttable.h" +static int compare_extable_32(const void *a, const void *b) +{ + Elf32_Addr av = r(a); + Elf32_Addr bv = r(b); + + if (av < bv) + return -1; + return av > bv; +} + +static int compare_extable_64(const void *a, const void *b) +{ + Elf64_Addr av = r8(a); + Elf64_Addr bv = r8(b); + + if (av < bv) + return -1; + return av > bv; +} + +static int compare_extable(const void *a, const void *b) +{ + return e.compare_extable(a, b); +} + +static inline void *get_index(void *start, int entsize, int index) +{ + return start + (entsize * index); +} + +static int extable_ent_size; +static int long_size; + +#define ERRSTR_MAXSZ 256 + +#ifdef UNWINDER_ORC_ENABLED +/* ORC unwinder only support X86_64 */ +#include <asm/orc_types.h> + +static char g_err[ERRSTR_MAXSZ]; +static int *g_orc_ip_table; +static struct orc_entry *g_orc_table; + +static pthread_t orc_sort_thread; + +static inline unsigned long orc_ip(const int *ip) +{ + return (unsigned long)ip + *ip; +} + +static int orc_sort_cmp(const void *_a, const void *_b) +{ + struct orc_entry *orc_a, *orc_b; + const int *a = g_orc_ip_table + *(int *)_a; + const int *b = g_orc_ip_table + *(int *)_b; + unsigned long a_val = orc_ip(a); + unsigned long b_val = orc_ip(b); + + if (a_val > b_val) + return 1; + if (a_val < b_val) + return -1; + + /* + * The "weak" section terminator entries need to always be on the left + * to ensure the lookup code skips them in favor of real entries. + * These terminator entries exist to handle any gaps created by + * whitelisted .o files which didn't get objtool generation. + */ + orc_a = g_orc_table + (a - g_orc_ip_table); + orc_b = g_orc_table + (b - g_orc_ip_table); + if (orc_a->type == ORC_TYPE_UNDEFINED && orc_b->type == ORC_TYPE_UNDEFINED) + return 0; + return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1; +} + +static void *sort_orctable(void *arg) +{ + int i; + int *idxs = NULL; + int *tmp_orc_ip_table = NULL; + struct orc_entry *tmp_orc_table = NULL; + unsigned int *orc_ip_size = (unsigned int *)arg; + unsigned int num_entries = *orc_ip_size / sizeof(int); + unsigned int orc_size = num_entries * sizeof(struct orc_entry); + + idxs = (int *)malloc(*orc_ip_size); + if (!idxs) { + snprintf(g_err, ERRSTR_MAXSZ, "malloc idxs: %s", + strerror(errno)); + pthread_exit(g_err); + } + + tmp_orc_ip_table = (int *)malloc(*orc_ip_size); + if (!tmp_orc_ip_table) { + snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_ip_table: %s", + strerror(errno)); + pthread_exit(g_err); + } + + tmp_orc_table = (struct orc_entry *)malloc(orc_size); + if (!tmp_orc_table) { + snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_table: %s", + strerror(errno)); + pthread_exit(g_err); + } + + /* initialize indices array, convert ip_table to absolute address */ + for (i = 0; i < num_entries; i++) { + idxs[i] = i; + tmp_orc_ip_table[i] = g_orc_ip_table[i] + i * sizeof(int); + } + memcpy(tmp_orc_table, g_orc_table, orc_size); + + qsort(idxs, num_entries, sizeof(int), orc_sort_cmp); + + for (i = 0; i < num_entries; i++) { + if (idxs[i] == i) + continue; + + /* convert back to relative address */ + g_orc_ip_table[i] = tmp_orc_ip_table[idxs[i]] - i * sizeof(int); + g_orc_table[i] = tmp_orc_table[idxs[i]]; + } + + free(idxs); + free(tmp_orc_ip_table); + free(tmp_orc_table); + pthread_exit(NULL); +} +#endif + +#ifdef MCOUNT_SORT_ENABLED + +static int compare_values_64(const void *a, const void *b) +{ + uint64_t av = *(uint64_t *)a; + uint64_t bv = *(uint64_t *)b; + + if (av < bv) + return -1; + return av > bv; +} + +static int compare_values_32(const void *a, const void *b) +{ + uint32_t av = *(uint32_t *)a; + uint32_t bv = *(uint32_t *)b; + + if (av < bv) + return -1; + return av > bv; +} + +static int (*compare_values)(const void *a, const void *b); + +/* Only used for sorting mcount table */ +static void rela_write_addend(Elf_Rela *rela, uint64_t val) +{ + e.rela_write_addend(rela, val); +} + +struct func_info { + uint64_t addr; + uint64_t size; +}; + +/* List of functions created by: nm -S vmlinux */ +static struct func_info *function_list; +static int function_list_size; + +/* Allocate functions in 1k blocks */ +#define FUNC_BLK_SIZE 1024 +#define FUNC_BLK_MASK (FUNC_BLK_SIZE - 1) + +static int add_field(uint64_t addr, uint64_t size) +{ + struct func_info *fi; + int fsize = function_list_size; + + if (!(fsize & FUNC_BLK_MASK)) { + fsize += FUNC_BLK_SIZE; + fi = realloc(function_list, fsize * sizeof(struct func_info)); + if (!fi) + return -1; + function_list = fi; + } + fi = &function_list[function_list_size++]; + fi->addr = addr; + fi->size = size; + return 0; +} + +/* Used for when mcount/fentry is before the function entry */ +static int before_func; + +/* Only return match if the address lies inside the function size */ +static int cmp_func_addr(const void *K, const void *A) +{ + uint64_t key = *(const uint64_t *)K; + const struct func_info *a = A; + + if (key + before_func < a->addr) + return -1; + return key >= a->addr + a->size; +} + +/* Find the function in function list that is bounded by the function size */ +static int find_func(uint64_t key) +{ + return bsearch(&key, function_list, function_list_size, + sizeof(struct func_info), cmp_func_addr) != NULL; +} + +static int cmp_funcs(const void *A, const void *B) +{ + const struct func_info *a = A; + const struct func_info *b = B; + + if (a->addr < b->addr) + return -1; + return a->addr > b->addr; +} + +static int parse_symbols(const char *fname) +{ + FILE *fp; + char addr_str[20]; /* Only need 17, but round up to next int size */ + char size_str[20]; + char type; + + fp = fopen(fname, "r"); + if (!fp) { + perror(fname); + return -1; + } + + while (fscanf(fp, "%16s %16s %c %*s\n", addr_str, size_str, &type) == 3) { + uint64_t addr; + uint64_t size; + + /* Only care about functions */ + if (type != 't' && type != 'T' && type != 'W') + continue; + + addr = strtoull(addr_str, NULL, 16); + size = strtoull(size_str, NULL, 16); + if (add_field(addr, size) < 0) + return -1; + } + fclose(fp); + + qsort(function_list, function_list_size, sizeof(struct func_info), cmp_funcs); + + return 0; +} + +static pthread_t mcount_sort_thread; +static bool sort_reloc; + +static long rela_type; + +static char m_err[ERRSTR_MAXSZ]; + +struct elf_mcount_loc { + Elf_Ehdr *ehdr; + Elf_Shdr *init_data_sec; + uint64_t start_mcount_loc; + uint64_t stop_mcount_loc; +}; + +/* Fill the array with the content of the relocs */ +static int fill_relocs(void *ptr, uint64_t size, Elf_Ehdr *ehdr, uint64_t start_loc) +{ + Elf_Shdr *shdr_start; + Elf_Rela *rel; + unsigned int shnum; + unsigned int count = 0; + int shentsize; + void *array_end = ptr + size; + + shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr)); + shentsize = ehdr_shentsize(ehdr); + + shnum = ehdr_shnum(ehdr); + if (shnum == SHN_UNDEF) + shnum = shdr_size(shdr_start); + + for (int i = 0; i < shnum; i++) { + Elf_Shdr *shdr = get_index(shdr_start, shentsize, i); + void *end; + + if (shdr_type(shdr) != SHT_RELA) + continue; + + rel = (void *)ehdr + shdr_offset(shdr); + end = (void *)rel + shdr_size(shdr); + + for (; (void *)rel < end; rel = (void *)rel + shdr_entsize(shdr)) { + uint64_t offset = rela_offset(rel); + + if (offset >= start_loc && offset < start_loc + size) { + if (ptr + long_size > array_end) { + snprintf(m_err, ERRSTR_MAXSZ, + "Too many relocations"); + return -1; + } + + /* Make sure this has the correct type */ + if (rela_info(rel) != rela_type) { + snprintf(m_err, ERRSTR_MAXSZ, + "rela has type %lx but expected %lx\n", + (long)rela_info(rel), rela_type); + return -1; + } + + if (long_size == 4) + *(uint32_t *)ptr = rela_addend(rel); + else + *(uint64_t *)ptr = rela_addend(rel); + ptr += long_size; + count++; + } + } + } + return count; +} + +/* Put the sorted vals back into the relocation elements */ +static void replace_relocs(void *ptr, uint64_t size, Elf_Ehdr *ehdr, uint64_t start_loc) +{ + Elf_Shdr *shdr_start; + Elf_Rela *rel; + unsigned int shnum; + int shentsize; + + shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr)); + shentsize = ehdr_shentsize(ehdr); + + shnum = ehdr_shnum(ehdr); + if (shnum == SHN_UNDEF) + shnum = shdr_size(shdr_start); + + for (int i = 0; i < shnum; i++) { + Elf_Shdr *shdr = get_index(shdr_start, shentsize, i); + void *end; + + if (shdr_type(shdr) != SHT_RELA) + continue; + + rel = (void *)ehdr + shdr_offset(shdr); + end = (void *)rel + shdr_size(shdr); + + for (; (void *)rel < end; rel = (void *)rel + shdr_entsize(shdr)) { + uint64_t offset = rela_offset(rel); + + if (offset >= start_loc && offset < start_loc + size) { + if (long_size == 4) + rela_write_addend(rel, *(uint32_t *)ptr); + else + rela_write_addend(rel, *(uint64_t *)ptr); + ptr += long_size; + } + } + } +} + +static int fill_addrs(void *ptr, uint64_t size, void *addrs) +{ + void *end = ptr + size; + int count = 0; + + for (; ptr < end; ptr += long_size, addrs += long_size, count++) { + if (long_size == 4) + *(uint32_t *)ptr = r(addrs); + else + *(uint64_t *)ptr = r8(addrs); + } + return count; +} + +static void replace_addrs(void *ptr, uint64_t size, void *addrs) +{ + void *end = ptr + size; + + for (; ptr < end; ptr += long_size, addrs += long_size) { + if (long_size == 4) + w(*(uint32_t *)ptr, addrs); + else + w8(*(uint64_t *)ptr, addrs); + } +} + +/* Sort the addresses stored between __start_mcount_loc to __stop_mcount_loc in vmlinux */ +static void *sort_mcount_loc(void *arg) +{ + struct elf_mcount_loc *emloc = (struct elf_mcount_loc *)arg; + uint64_t offset = emloc->start_mcount_loc - shdr_addr(emloc->init_data_sec) + + shdr_offset(emloc->init_data_sec); + uint64_t size = emloc->stop_mcount_loc - emloc->start_mcount_loc; + unsigned char *start_loc = (void *)emloc->ehdr + offset; + Elf_Ehdr *ehdr = emloc->ehdr; + void *e_msg = NULL; + void *vals; + int count; + + vals = malloc(long_size * size); + if (!vals) { + snprintf(m_err, ERRSTR_MAXSZ, "Failed to allocate sort array"); + pthread_exit(m_err); + } + + if (sort_reloc) { + count = fill_relocs(vals, size, ehdr, emloc->start_mcount_loc); + /* gcc may use relocs to save the addresses, but clang does not. */ + if (!count) { + count = fill_addrs(vals, size, start_loc); + sort_reloc = 0; + } + } else + count = fill_addrs(vals, size, start_loc); + + if (count < 0) { + e_msg = m_err; + goto out; + } + + if (count != size / long_size) { + snprintf(m_err, ERRSTR_MAXSZ, "Expected %u mcount elements but found %u\n", + (int)(size / long_size), count); + e_msg = m_err; + goto out; + } + + /* zero out any locations not found by function list */ + if (function_list_size) { + for (void *ptr = vals; ptr < vals + size; ptr += long_size) { + uint64_t key; + + key = long_size == 4 ? *(uint32_t *)ptr : *(uint64_t *)ptr; + if (!find_func(key)) { + if (long_size == 4) + *(uint32_t *)ptr = 0; + else + *(uint64_t *)ptr = 0; + } + } + } + + compare_values = long_size == 4 ? compare_values_32 : compare_values_64; + + qsort(vals, count, long_size, compare_values); + + if (sort_reloc) + replace_relocs(vals, size, ehdr, emloc->start_mcount_loc); + else + replace_addrs(vals, size, start_loc); + +out: + free(vals); + + pthread_exit(e_msg); +} + +/* Get the address of __start_mcount_loc and __stop_mcount_loc in System.map */ +static void get_mcount_loc(struct elf_mcount_loc *emloc, Elf_Shdr *symtab_sec, + const char *strtab) +{ + Elf_Sym *sym, *end_sym; + int symentsize = shdr_entsize(symtab_sec); + int found = 0; + + sym = (void *)emloc->ehdr + shdr_offset(symtab_sec); + end_sym = (void *)sym + shdr_size(symtab_sec); + + while (sym < end_sym) { + if (!strcmp(strtab + sym_name(sym), "__start_mcount_loc")) { + emloc->start_mcount_loc = sym_value(sym); + if (++found == 2) + break; + } else if (!strcmp(strtab + sym_name(sym), "__stop_mcount_loc")) { + emloc->stop_mcount_loc = sym_value(sym); + if (++found == 2) + break; + } + sym = (void *)sym + symentsize; + } + + if (!emloc->start_mcount_loc) { + fprintf(stderr, "get start_mcount_loc error!"); + return; + } + + if (!emloc->stop_mcount_loc) { + fprintf(stderr, "get stop_mcount_loc error!"); + return; + } +} +#else /* MCOUNT_SORT_ENABLED */ +static inline int parse_symbols(const char *fname) { return 0; } +#endif + +static int do_sort(Elf_Ehdr *ehdr, + char const *const fname, + table_sort_t custom_sort) +{ + int rc = -1; + Elf_Shdr *shdr_start; + Elf_Shdr *strtab_sec = NULL; + Elf_Shdr *symtab_sec = NULL; + Elf_Shdr *extab_sec = NULL; + Elf_Shdr *string_sec; + Elf_Sym *sym; + const Elf_Sym *symtab; + Elf32_Word *symtab_shndx = NULL; + Elf_Sym *sort_needed_sym = NULL; + Elf_Shdr *sort_needed_sec; + uint32_t *sort_needed_loc; + void *sym_start; + void *sym_end; + const char *secstrings; + const char *strtab; + char *extab_image; + int sort_need_index; + int symentsize; + int shentsize; + int idx; + int i; + unsigned int shnum; + unsigned int shstrndx; +#ifdef MCOUNT_SORT_ENABLED + struct elf_mcount_loc mstruct = {0}; +#endif +#ifdef UNWINDER_ORC_ENABLED + unsigned int orc_ip_size = 0; + unsigned int orc_size = 0; + unsigned int orc_num_entries = 0; +#endif + + shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr)); + shentsize = ehdr_shentsize(ehdr); + + shstrndx = ehdr_shstrndx(ehdr); + if (shstrndx == SHN_XINDEX) + shstrndx = shdr_link(shdr_start); + string_sec = get_index(shdr_start, shentsize, shstrndx); + secstrings = (const char *)ehdr + shdr_offset(string_sec); + + shnum = ehdr_shnum(ehdr); + if (shnum == SHN_UNDEF) + shnum = shdr_size(shdr_start); + + for (i = 0; i < shnum; i++) { + Elf_Shdr *shdr = get_index(shdr_start, shentsize, i); + + idx = shdr_name(shdr); + if (!strcmp(secstrings + idx, "__ex_table")) + extab_sec = shdr; + if (!strcmp(secstrings + idx, ".symtab")) + symtab_sec = shdr; + if (!strcmp(secstrings + idx, ".strtab")) + strtab_sec = shdr; + + if (shdr_type(shdr) == SHT_SYMTAB_SHNDX) + symtab_shndx = (Elf32_Word *)((const char *)ehdr + + shdr_offset(shdr)); + +#ifdef MCOUNT_SORT_ENABLED + /* locate the .init.data section in vmlinux */ + if (!strcmp(secstrings + idx, ".init.data")) + mstruct.init_data_sec = shdr; +#endif + +#ifdef UNWINDER_ORC_ENABLED + /* locate the ORC unwind tables */ + if (!strcmp(secstrings + idx, ".orc_unwind_ip")) { + orc_ip_size = shdr_size(shdr); + g_orc_ip_table = (int *)((void *)ehdr + + shdr_offset(shdr)); + } + if (!strcmp(secstrings + idx, ".orc_unwind")) { + orc_size = shdr_size(shdr); + g_orc_table = (struct orc_entry *)((void *)ehdr + + shdr_offset(shdr)); + } +#endif + } /* for loop */ + +#ifdef UNWINDER_ORC_ENABLED + if (!g_orc_ip_table || !g_orc_table) { + fprintf(stderr, + "incomplete ORC unwind tables in file: %s\n", fname); + goto out; + } + + orc_num_entries = orc_ip_size / sizeof(int); + if (orc_ip_size % sizeof(int) != 0 || + orc_size % sizeof(struct orc_entry) != 0 || + orc_num_entries != orc_size / sizeof(struct orc_entry)) { + fprintf(stderr, + "inconsistent ORC unwind table entries in file: %s\n", + fname); + goto out; + } + + /* create thread to sort ORC unwind tables concurrently */ + if (pthread_create(&orc_sort_thread, NULL, + sort_orctable, &orc_ip_size)) { + fprintf(stderr, + "pthread_create orc_sort_thread failed '%s': %s\n", + strerror(errno), fname); + goto out; + } +#endif + if (!extab_sec) { + fprintf(stderr, "no __ex_table in file: %s\n", fname); + goto out; + } + + if (!symtab_sec) { + fprintf(stderr, "no .symtab in file: %s\n", fname); + goto out; + } + + if (!strtab_sec) { + fprintf(stderr, "no .strtab in file: %s\n", fname); + goto out; + } + + extab_image = (void *)ehdr + shdr_offset(extab_sec); + strtab = (const char *)ehdr + shdr_offset(strtab_sec); + symtab = (const Elf_Sym *)((const char *)ehdr + shdr_offset(symtab_sec)); + +#ifdef MCOUNT_SORT_ENABLED + mstruct.ehdr = ehdr; + get_mcount_loc(&mstruct, symtab_sec, strtab); + + if (!mstruct.init_data_sec || !mstruct.start_mcount_loc || !mstruct.stop_mcount_loc) { + fprintf(stderr, + "incomplete mcount's sort in file: %s\n", + fname); + goto out; + } + + /* create thread to sort mcount_loc concurrently */ + if (pthread_create(&mcount_sort_thread, NULL, &sort_mcount_loc, &mstruct)) { + fprintf(stderr, + "pthread_create mcount_sort_thread failed '%s': %s\n", + strerror(errno), fname); + goto out; + } +#endif + + if (custom_sort) { + custom_sort(extab_image, shdr_size(extab_sec)); + } else { + int num_entries = shdr_size(extab_sec) / extable_ent_size; + qsort(extab_image, num_entries, + extable_ent_size, compare_extable); + } + + /* find the flag main_extable_sort_needed */ + sym_start = (void *)ehdr + shdr_offset(symtab_sec); + sym_end = sym_start + shdr_size(symtab_sec); + symentsize = shdr_entsize(symtab_sec); + + for (sym = sym_start; (void *)sym + symentsize < sym_end; + sym = (void *)sym + symentsize) { + if (sym_type(sym) != STT_OBJECT) + continue; + if (!strcmp(strtab + sym_name(sym), + "main_extable_sort_needed")) { + sort_needed_sym = sym; + break; + } + } + + if (!sort_needed_sym) { + fprintf(stderr, + "no main_extable_sort_needed symbol in file: %s\n", + fname); + goto out; + } + + sort_need_index = get_secindex(sym_shndx(sym), + ((void *)sort_needed_sym - (void *)symtab) / symentsize, + symtab_shndx); + sort_needed_sec = get_index(shdr_start, shentsize, sort_need_index); + sort_needed_loc = (void *)ehdr + + shdr_offset(sort_needed_sec) + + sym_value(sort_needed_sym) - shdr_addr(sort_needed_sec); + + /* extable has been sorted, clear the flag */ + w(0, sort_needed_loc); + rc = 0; + +out: +#ifdef UNWINDER_ORC_ENABLED + if (orc_sort_thread) { + void *retval = NULL; + /* wait for ORC tables sort done */ + rc = pthread_join(orc_sort_thread, &retval); + if (rc) { + fprintf(stderr, + "pthread_join failed '%s': %s\n", + strerror(errno), fname); + } else if (retval) { + rc = -1; + fprintf(stderr, + "failed to sort ORC tables '%s': %s\n", + (char *)retval, fname); + } + } +#endif + +#ifdef MCOUNT_SORT_ENABLED + if (mcount_sort_thread) { + void *retval = NULL; + /* wait for mcount sort done */ + rc = pthread_join(mcount_sort_thread, &retval); + if (rc) { + fprintf(stderr, + "pthread_join failed '%s': %s\n", + strerror(errno), fname); + } else if (retval) { + rc = -1; + fprintf(stderr, + "failed to sort mcount '%s': %s\n", + (char *)retval, fname); + } + } +#endif + return rc; +} static int compare_relative_table(const void *a, const void *b) { @@ -267,17 +1220,15 @@ static void sort_relative_table_with_data(char *extab_image, int image_size) static int do_file(char const *const fname, void *addr) { - int rc = -1; - Elf32_Ehdr *ehdr = addr; + Elf_Ehdr *ehdr = addr; table_sort_t custom_sort = NULL; - switch (ehdr->e_ident[EI_DATA]) { + switch (ehdr->e32.e_ident[EI_DATA]) { case ELFDATA2LSB: r = rle; r2 = r2le; r8 = r8le; w = wle; - w2 = w2le; w8 = w8le; break; case ELFDATA2MSB: @@ -285,25 +1236,31 @@ static int do_file(char const *const fname, void *addr) r2 = r2be; r8 = r8be; w = wbe; - w2 = w2be; w8 = w8be; break; default: fprintf(stderr, "unrecognized ELF data encoding %d: %s\n", - ehdr->e_ident[EI_DATA], fname); + ehdr->e32.e_ident[EI_DATA], fname); return -1; } - if (memcmp(ELFMAG, ehdr->e_ident, SELFMAG) != 0 || - (r2(&ehdr->e_type) != ET_EXEC && r2(&ehdr->e_type) != ET_DYN) || - ehdr->e_ident[EI_VERSION] != EV_CURRENT) { + if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 || + (r2(&ehdr->e32.e_type) != ET_EXEC && r2(&ehdr->e32.e_type) != ET_DYN) || + ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) { fprintf(stderr, "unrecognized ET_EXEC/ET_DYN file %s\n", fname); return -1; } - switch (r2(&ehdr->e_machine)) { - case EM_386: + switch (r2(&ehdr->e32.e_machine)) { case EM_AARCH64: +#ifdef MCOUNT_SORT_ENABLED + sort_reloc = true; + rela_type = 0x403; + /* arm64 uses patchable function entry placing before function */ + before_func = 8; +#endif + /* fallthrough */ + case EM_386: case EM_LOONGARCH: case EM_RISCV: case EM_S390: @@ -324,40 +1281,93 @@ static int do_file(char const *const fname, void *addr) break; default: fprintf(stderr, "unrecognized e_machine %d %s\n", - r2(&ehdr->e_machine), fname); + r2(&ehdr->e32.e_machine), fname); return -1; } - switch (ehdr->e_ident[EI_CLASS]) { - case ELFCLASS32: - if (r2(&ehdr->e_ehsize) != sizeof(Elf32_Ehdr) || - r2(&ehdr->e_shentsize) != sizeof(Elf32_Shdr)) { + switch (ehdr->e32.e_ident[EI_CLASS]) { + case ELFCLASS32: { + struct elf_funcs efuncs = { + .compare_extable = compare_extable_32, + .ehdr_shoff = ehdr32_shoff, + .ehdr_shentsize = ehdr32_shentsize, + .ehdr_shstrndx = ehdr32_shstrndx, + .ehdr_shnum = ehdr32_shnum, + .shdr_addr = shdr32_addr, + .shdr_offset = shdr32_offset, + .shdr_link = shdr32_link, + .shdr_size = shdr32_size, + .shdr_name = shdr32_name, + .shdr_type = shdr32_type, + .shdr_entsize = shdr32_entsize, + .sym_type = sym32_type, + .sym_name = sym32_name, + .sym_value = sym32_value, + .sym_shndx = sym32_shndx, + .rela_offset = rela32_offset, + .rela_info = rela32_info, + .rela_addend = rela32_addend, + .rela_write_addend = rela32_write_addend, + }; + + e = efuncs; + long_size = 4; + extable_ent_size = 8; + + if (r2(&ehdr->e32.e_ehsize) != sizeof(Elf32_Ehdr) || + r2(&ehdr->e32.e_shentsize) != sizeof(Elf32_Shdr)) { fprintf(stderr, "unrecognized ET_EXEC/ET_DYN file: %s\n", fname); - break; + return -1; + } + } - rc = do_sort_32(ehdr, fname, custom_sort); break; - case ELFCLASS64: - { - Elf64_Ehdr *const ghdr = (Elf64_Ehdr *)ehdr; - if (r2(&ghdr->e_ehsize) != sizeof(Elf64_Ehdr) || - r2(&ghdr->e_shentsize) != sizeof(Elf64_Shdr)) { + case ELFCLASS64: { + struct elf_funcs efuncs = { + .compare_extable = compare_extable_64, + .ehdr_shoff = ehdr64_shoff, + .ehdr_shentsize = ehdr64_shentsize, + .ehdr_shstrndx = ehdr64_shstrndx, + .ehdr_shnum = ehdr64_shnum, + .shdr_addr = shdr64_addr, + .shdr_offset = shdr64_offset, + .shdr_link = shdr64_link, + .shdr_size = shdr64_size, + .shdr_name = shdr64_name, + .shdr_type = shdr64_type, + .shdr_entsize = shdr64_entsize, + .sym_type = sym64_type, + .sym_name = sym64_name, + .sym_value = sym64_value, + .sym_shndx = sym64_shndx, + .rela_offset = rela64_offset, + .rela_info = rela64_info, + .rela_addend = rela64_addend, + .rela_write_addend = rela64_write_addend, + }; + + e = efuncs; + long_size = 8; + extable_ent_size = 16; + + if (r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) || + r2(&ehdr->e64.e_shentsize) != sizeof(Elf64_Shdr)) { fprintf(stderr, "unrecognized ET_EXEC/ET_DYN file: %s\n", fname); - break; + return -1; } - rc = do_sort_64(ghdr, fname, custom_sort); + } break; default: fprintf(stderr, "unrecognized ELF class %d %s\n", - ehdr->e_ident[EI_CLASS], fname); - break; + ehdr->e32.e_ident[EI_CLASS], fname); + return -1; } - return rc; + return do_sort(ehdr, fname, custom_sort); } int main(int argc, char *argv[]) @@ -365,14 +1375,29 @@ int main(int argc, char *argv[]) int i, n_error = 0; /* gcc-4.3.0 false positive complaint */ size_t size = 0; void *addr = NULL; + int c; + + while ((c = getopt(argc, argv, "s:")) >= 0) { + switch (c) { + case 's': + if (parse_symbols(optarg) < 0) { + fprintf(stderr, "Could not parse %s\n", optarg); + return -1; + } + break; + default: + fprintf(stderr, "usage: sorttable [-s nm-file] vmlinux...\n"); + return 0; + } + } - if (argc < 2) { + if ((argc - optind) < 1) { fprintf(stderr, "usage: sorttable vmlinux...\n"); return 0; } /* Process each file in turn, allowing deep failure. */ - for (i = 1; i < argc; i++) { + for (i = optind; i < argc; i++) { addr = mmap_file(argv[i], &size); if (!addr) { ++n_error; diff --git a/scripts/sorttable.h b/scripts/sorttable.h deleted file mode 100644 index 7bd0184380d3..000000000000 --- a/scripts/sorttable.h +++ /dev/null @@ -1,497 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * sorttable.h - * - * Added ORC unwind tables sort support and other updates: - * Copyright (C) 1999-2019 Alibaba Group Holding Limited. by: - * Shile Zhang <shile.zhang@linux.alibaba.com> - * - * Copyright 2011 - 2012 Cavium, Inc. - * - * Some of code was taken out of arch/x86/kernel/unwind_orc.c, written by: - * Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> - * - * Some of this code was taken out of recordmcount.h written by: - * - * Copyright 2009 John F. Reiser <jreiser@BitWagon.com>. All rights reserved. - * Copyright 2010 Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. - */ - -#undef extable_ent_size -#undef compare_extable -#undef get_mcount_loc -#undef sort_mcount_loc -#undef elf_mcount_loc -#undef do_sort -#undef Elf_Addr -#undef Elf_Ehdr -#undef Elf_Shdr -#undef Elf_Rel -#undef Elf_Rela -#undef Elf_Sym -#undef ELF_R_SYM -#undef Elf_r_sym -#undef ELF_R_INFO -#undef Elf_r_info -#undef ELF_ST_BIND -#undef ELF_ST_TYPE -#undef fn_ELF_R_SYM -#undef fn_ELF_R_INFO -#undef uint_t -#undef _r -#undef _w - -#ifdef SORTTABLE_64 -# define extable_ent_size 16 -# define compare_extable compare_extable_64 -# define get_mcount_loc get_mcount_loc_64 -# define sort_mcount_loc sort_mcount_loc_64 -# define elf_mcount_loc elf_mcount_loc_64 -# define do_sort do_sort_64 -# define Elf_Addr Elf64_Addr -# define Elf_Ehdr Elf64_Ehdr -# define Elf_Shdr Elf64_Shdr -# define Elf_Rel Elf64_Rel -# define Elf_Rela Elf64_Rela -# define Elf_Sym Elf64_Sym -# define ELF_R_SYM ELF64_R_SYM -# define Elf_r_sym Elf64_r_sym -# define ELF_R_INFO ELF64_R_INFO -# define Elf_r_info Elf64_r_info -# define ELF_ST_BIND ELF64_ST_BIND -# define ELF_ST_TYPE ELF64_ST_TYPE -# define fn_ELF_R_SYM fn_ELF64_R_SYM -# define fn_ELF_R_INFO fn_ELF64_R_INFO -# define uint_t uint64_t -# define _r r8 -# define _w w8 -#else -# define extable_ent_size 8 -# define compare_extable compare_extable_32 -# define get_mcount_loc get_mcount_loc_32 -# define sort_mcount_loc sort_mcount_loc_32 -# define elf_mcount_loc elf_mcount_loc_32 -# define do_sort do_sort_32 -# define Elf_Addr Elf32_Addr -# define Elf_Ehdr Elf32_Ehdr -# define Elf_Shdr Elf32_Shdr -# define Elf_Rel Elf32_Rel -# define Elf_Rela Elf32_Rela -# define Elf_Sym Elf32_Sym -# define ELF_R_SYM ELF32_R_SYM -# define Elf_r_sym Elf32_r_sym -# define ELF_R_INFO ELF32_R_INFO -# define Elf_r_info Elf32_r_info -# define ELF_ST_BIND ELF32_ST_BIND -# define ELF_ST_TYPE ELF32_ST_TYPE -# define fn_ELF_R_SYM fn_ELF32_R_SYM -# define fn_ELF_R_INFO fn_ELF32_R_INFO -# define uint_t uint32_t -# define _r r -# define _w w -#endif - -#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED) -/* ORC unwinder only support X86_64 */ -#include <asm/orc_types.h> - -#define ERRSTR_MAXSZ 256 - -char g_err[ERRSTR_MAXSZ]; -int *g_orc_ip_table; -struct orc_entry *g_orc_table; - -pthread_t orc_sort_thread; - -static inline unsigned long orc_ip(const int *ip) -{ - return (unsigned long)ip + *ip; -} - -static int orc_sort_cmp(const void *_a, const void *_b) -{ - struct orc_entry *orc_a; - const int *a = g_orc_ip_table + *(int *)_a; - const int *b = g_orc_ip_table + *(int *)_b; - unsigned long a_val = orc_ip(a); - unsigned long b_val = orc_ip(b); - - if (a_val > b_val) - return 1; - if (a_val < b_val) - return -1; - - /* - * The "weak" section terminator entries need to always be on the left - * to ensure the lookup code skips them in favor of real entries. - * These terminator entries exist to handle any gaps created by - * whitelisted .o files which didn't get objtool generation. - */ - orc_a = g_orc_table + (a - g_orc_ip_table); - return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1; -} - -static void *sort_orctable(void *arg) -{ - int i; - int *idxs = NULL; - int *tmp_orc_ip_table = NULL; - struct orc_entry *tmp_orc_table = NULL; - unsigned int *orc_ip_size = (unsigned int *)arg; - unsigned int num_entries = *orc_ip_size / sizeof(int); - unsigned int orc_size = num_entries * sizeof(struct orc_entry); - - idxs = (int *)malloc(*orc_ip_size); - if (!idxs) { - snprintf(g_err, ERRSTR_MAXSZ, "malloc idxs: %s", - strerror(errno)); - pthread_exit(g_err); - } - - tmp_orc_ip_table = (int *)malloc(*orc_ip_size); - if (!tmp_orc_ip_table) { - snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_ip_table: %s", - strerror(errno)); - pthread_exit(g_err); - } - - tmp_orc_table = (struct orc_entry *)malloc(orc_size); - if (!tmp_orc_table) { - snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_table: %s", - strerror(errno)); - pthread_exit(g_err); - } - - /* initialize indices array, convert ip_table to absolute address */ - for (i = 0; i < num_entries; i++) { - idxs[i] = i; - tmp_orc_ip_table[i] = g_orc_ip_table[i] + i * sizeof(int); - } - memcpy(tmp_orc_table, g_orc_table, orc_size); - - qsort(idxs, num_entries, sizeof(int), orc_sort_cmp); - - for (i = 0; i < num_entries; i++) { - if (idxs[i] == i) - continue; - - /* convert back to relative address */ - g_orc_ip_table[i] = tmp_orc_ip_table[idxs[i]] - i * sizeof(int); - g_orc_table[i] = tmp_orc_table[idxs[i]]; - } - - free(idxs); - free(tmp_orc_ip_table); - free(tmp_orc_table); - pthread_exit(NULL); -} -#endif - -static int compare_extable(const void *a, const void *b) -{ - Elf_Addr av = _r(a); - Elf_Addr bv = _r(b); - - if (av < bv) - return -1; - if (av > bv) - return 1; - return 0; -} -#ifdef MCOUNT_SORT_ENABLED -pthread_t mcount_sort_thread; - -struct elf_mcount_loc { - Elf_Ehdr *ehdr; - Elf_Shdr *init_data_sec; - uint_t start_mcount_loc; - uint_t stop_mcount_loc; -}; - -/* Sort the addresses stored between __start_mcount_loc to __stop_mcount_loc in vmlinux */ -static void *sort_mcount_loc(void *arg) -{ - struct elf_mcount_loc *emloc = (struct elf_mcount_loc *)arg; - uint_t offset = emloc->start_mcount_loc - _r(&(emloc->init_data_sec)->sh_addr) - + _r(&(emloc->init_data_sec)->sh_offset); - uint_t count = emloc->stop_mcount_loc - emloc->start_mcount_loc; - unsigned char *start_loc = (void *)emloc->ehdr + offset; - - qsort(start_loc, count/sizeof(uint_t), sizeof(uint_t), compare_extable); - return NULL; -} - -/* Get the address of __start_mcount_loc and __stop_mcount_loc in System.map */ -static void get_mcount_loc(uint_t *_start, uint_t *_stop) -{ - FILE *file_start, *file_stop; - char start_buff[20]; - char stop_buff[20]; - int len = 0; - - file_start = popen(" grep start_mcount System.map | awk '{print $1}' ", "r"); - if (!file_start) { - fprintf(stderr, "get start_mcount_loc error!"); - return; - } - - file_stop = popen(" grep stop_mcount System.map | awk '{print $1}' ", "r"); - if (!file_stop) { - fprintf(stderr, "get stop_mcount_loc error!"); - pclose(file_start); - return; - } - - while (fgets(start_buff, sizeof(start_buff), file_start) != NULL) { - len = strlen(start_buff); - start_buff[len - 1] = '\0'; - } - *_start = strtoul(start_buff, NULL, 16); - - while (fgets(stop_buff, sizeof(stop_buff), file_stop) != NULL) { - len = strlen(stop_buff); - stop_buff[len - 1] = '\0'; - } - *_stop = strtoul(stop_buff, NULL, 16); - - pclose(file_start); - pclose(file_stop); -} -#endif -static int do_sort(Elf_Ehdr *ehdr, - char const *const fname, - table_sort_t custom_sort) -{ - int rc = -1; - Elf_Shdr *s, *shdr = (Elf_Shdr *)((char *)ehdr + _r(&ehdr->e_shoff)); - Elf_Shdr *strtab_sec = NULL; - Elf_Shdr *symtab_sec = NULL; - Elf_Shdr *extab_sec = NULL; - Elf_Sym *sym; - const Elf_Sym *symtab; - Elf32_Word *symtab_shndx = NULL; - Elf_Sym *sort_needed_sym = NULL; - Elf_Shdr *sort_needed_sec; - Elf_Rel *relocs = NULL; - int relocs_size = 0; - uint32_t *sort_needed_loc; - const char *secstrings; - const char *strtab; - char *extab_image; - int extab_index = 0; - int i; - int idx; - unsigned int shnum; - unsigned int shstrndx; -#ifdef MCOUNT_SORT_ENABLED - struct elf_mcount_loc mstruct = {0}; - uint_t _start_mcount_loc = 0; - uint_t _stop_mcount_loc = 0; -#endif -#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED) - unsigned int orc_ip_size = 0; - unsigned int orc_size = 0; - unsigned int orc_num_entries = 0; -#endif - - shstrndx = r2(&ehdr->e_shstrndx); - if (shstrndx == SHN_XINDEX) - shstrndx = r(&shdr[0].sh_link); - secstrings = (const char *)ehdr + _r(&shdr[shstrndx].sh_offset); - - shnum = r2(&ehdr->e_shnum); - if (shnum == SHN_UNDEF) - shnum = _r(&shdr[0].sh_size); - - for (i = 0, s = shdr; s < shdr + shnum; i++, s++) { - idx = r(&s->sh_name); - if (!strcmp(secstrings + idx, "__ex_table")) { - extab_sec = s; - extab_index = i; - } - if (!strcmp(secstrings + idx, ".symtab")) - symtab_sec = s; - if (!strcmp(secstrings + idx, ".strtab")) - strtab_sec = s; - - if ((r(&s->sh_type) == SHT_REL || - r(&s->sh_type) == SHT_RELA) && - r(&s->sh_info) == extab_index) { - relocs = (void *)ehdr + _r(&s->sh_offset); - relocs_size = _r(&s->sh_size); - } - if (r(&s->sh_type) == SHT_SYMTAB_SHNDX) - symtab_shndx = (Elf32_Word *)((const char *)ehdr + - _r(&s->sh_offset)); - -#ifdef MCOUNT_SORT_ENABLED - /* locate the .init.data section in vmlinux */ - if (!strcmp(secstrings + idx, ".init.data")) { - get_mcount_loc(&_start_mcount_loc, &_stop_mcount_loc); - mstruct.ehdr = ehdr; - mstruct.init_data_sec = s; - mstruct.start_mcount_loc = _start_mcount_loc; - mstruct.stop_mcount_loc = _stop_mcount_loc; - } -#endif - -#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED) - /* locate the ORC unwind tables */ - if (!strcmp(secstrings + idx, ".orc_unwind_ip")) { - orc_ip_size = s->sh_size; - g_orc_ip_table = (int *)((void *)ehdr + - s->sh_offset); - } - if (!strcmp(secstrings + idx, ".orc_unwind")) { - orc_size = s->sh_size; - g_orc_table = (struct orc_entry *)((void *)ehdr + - s->sh_offset); - } -#endif - } /* for loop */ - -#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED) - if (!g_orc_ip_table || !g_orc_table) { - fprintf(stderr, - "incomplete ORC unwind tables in file: %s\n", fname); - goto out; - } - - orc_num_entries = orc_ip_size / sizeof(int); - if (orc_ip_size % sizeof(int) != 0 || - orc_size % sizeof(struct orc_entry) != 0 || - orc_num_entries != orc_size / sizeof(struct orc_entry)) { - fprintf(stderr, - "inconsistent ORC unwind table entries in file: %s\n", - fname); - goto out; - } - - /* create thread to sort ORC unwind tables concurrently */ - if (pthread_create(&orc_sort_thread, NULL, - sort_orctable, &orc_ip_size)) { - fprintf(stderr, - "pthread_create orc_sort_thread failed '%s': %s\n", - strerror(errno), fname); - goto out; - } -#endif - -#ifdef MCOUNT_SORT_ENABLED - if (!mstruct.init_data_sec || !_start_mcount_loc || !_stop_mcount_loc) { - fprintf(stderr, - "incomplete mcount's sort in file: %s\n", - fname); - goto out; - } - - /* create thread to sort mcount_loc concurrently */ - if (pthread_create(&mcount_sort_thread, NULL, &sort_mcount_loc, &mstruct)) { - fprintf(stderr, - "pthread_create mcount_sort_thread failed '%s': %s\n", - strerror(errno), fname); - goto out; - } -#endif - if (!extab_sec) { - fprintf(stderr, "no __ex_table in file: %s\n", fname); - goto out; - } - - if (!symtab_sec) { - fprintf(stderr, "no .symtab in file: %s\n", fname); - goto out; - } - - if (!strtab_sec) { - fprintf(stderr, "no .strtab in file: %s\n", fname); - goto out; - } - - extab_image = (void *)ehdr + _r(&extab_sec->sh_offset); - strtab = (const char *)ehdr + _r(&strtab_sec->sh_offset); - symtab = (const Elf_Sym *)((const char *)ehdr + - _r(&symtab_sec->sh_offset)); - - if (custom_sort) { - custom_sort(extab_image, _r(&extab_sec->sh_size)); - } else { - int num_entries = _r(&extab_sec->sh_size) / extable_ent_size; - qsort(extab_image, num_entries, - extable_ent_size, compare_extable); - } - - /* If there were relocations, we no longer need them. */ - if (relocs) - memset(relocs, 0, relocs_size); - - /* find the flag main_extable_sort_needed */ - for (sym = (void *)ehdr + _r(&symtab_sec->sh_offset); - sym < sym + _r(&symtab_sec->sh_size) / sizeof(Elf_Sym); - sym++) { - if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT) - continue; - if (!strcmp(strtab + r(&sym->st_name), - "main_extable_sort_needed")) { - sort_needed_sym = sym; - break; - } - } - - if (!sort_needed_sym) { - fprintf(stderr, - "no main_extable_sort_needed symbol in file: %s\n", - fname); - goto out; - } - - sort_needed_sec = &shdr[get_secindex(r2(&sym->st_shndx), - sort_needed_sym - symtab, - symtab_shndx)]; - sort_needed_loc = (void *)ehdr + - _r(&sort_needed_sec->sh_offset) + - _r(&sort_needed_sym->st_value) - - _r(&sort_needed_sec->sh_addr); - - /* extable has been sorted, clear the flag */ - w(0, sort_needed_loc); - rc = 0; - -out: -#if defined(SORTTABLE_64) && defined(UNWINDER_ORC_ENABLED) - if (orc_sort_thread) { - void *retval = NULL; - /* wait for ORC tables sort done */ - rc = pthread_join(orc_sort_thread, &retval); - if (rc) { - fprintf(stderr, - "pthread_join failed '%s': %s\n", - strerror(errno), fname); - } else if (retval) { - rc = -1; - fprintf(stderr, - "failed to sort ORC tables '%s': %s\n", - (char *)retval, fname); - } - } -#endif - -#ifdef MCOUNT_SORT_ENABLED - if (mcount_sort_thread) { - void *retval = NULL; - /* wait for mcount sort done */ - rc = pthread_join(mcount_sort_thread, &retval); - if (rc) { - fprintf(stderr, - "pthread_join failed '%s': %s\n", - strerror(errno), fname); - } else if (retval) { - rc = -1; - fprintf(stderr, - "failed to sort mcount '%s': %s\n", - (char *)retval, fname); - } - } -#endif - return rc; -} diff --git a/scripts/spdxcheck.py b/scripts/spdxcheck.py index 18cb9f5b3d3d..8d608f61bf37 100755 --- a/scripts/spdxcheck.py +++ b/scripts/spdxcheck.py @@ -214,9 +214,15 @@ class id_parser(object): # Remove trailing xml comment closure if line.strip().endswith('-->'): expr = expr.rstrip('-->').strip() + # Remove trailing Jinja2 comment closure + if line.strip().endswith('#}'): + expr = expr.rstrip('#}').strip() # Special case for SH magic boot code files if line.startswith('LIST \"'): expr = expr.rstrip('\"').strip() + # Remove j2 comment closure + if line.startswith('{#'): + expr = expr.rstrip('#}').strip() self.parse(expr) self.spdx_valid += 1 # @@ -412,6 +418,9 @@ if __name__ == '__main__': if parser.checked: pc = int(100 * parser.spdx_valid / parser.checked) sys.stderr.write('Files with SPDX: %12d %3d%%\n' %(parser.spdx_valid, pc)) + missing = parser.checked - parser.spdx_valid + mpc = int(100 * missing / parser.checked) + sys.stderr.write('Files without SPDX:%12d %3d%%\n' %(missing, mpc)) sys.stderr.write('Files with errors: %12d\n' %parser.spdx_errors) if ndirs: sys.stderr.write('\n') diff --git a/scripts/spelling.txt b/scripts/spelling.txt index edec60d39bbf..ac94fa1c2415 100644 --- a/scripts/spelling.txt +++ b/scripts/spelling.txt @@ -141,6 +141,7 @@ anomoly||anomaly anonynous||anonymous anway||anyway aplication||application +apeared||appeared appearence||appearance applicaion||application appliction||application @@ -155,6 +156,7 @@ apropriate||appropriate aquainted||acquainted aquired||acquired aquisition||acquisition +aquires||acquires arbitary||arbitrary architechture||architecture archtecture||architecture @@ -176,17 +178,21 @@ assigment||assignment assigments||assignments assistent||assistant assocaited||associated +assocated||associated assocating||associating assocation||association +assocative||associative associcated||associated assotiated||associated asssert||assert assum||assume assumtpion||assumption +asume||assume asuming||assuming asycronous||asynchronous asychronous||asynchronous asynchnous||asynchronous +asynchrnous||asynchronous asynchronus||asynchronous asynchromous||asynchronous asymetric||asymmetric @@ -216,6 +222,7 @@ autonymous||autonomous auxillary||auxiliary auxilliary||auxiliary avaiable||available +avaialable||available avaible||available availabe||available availabled||available @@ -261,12 +268,14 @@ broadcase||broadcast broadcat||broadcast bufer||buffer bufferred||buffered +bufferur||buffer bufufer||buffer cacluated||calculated caculate||calculate caculation||calculation cadidate||candidate cahces||caches +calcluate||calculate calender||calendar calescing||coalescing calibraiton||calibration @@ -329,6 +338,7 @@ chouse||chose circumvernt||circumvent claread||cleared clared||cleared +clearify||clarify closeing||closing clustred||clustered cnfiguration||configuration @@ -377,12 +387,14 @@ comsumed||consumed comunicate||communicate comunication||communication conbination||combination +concurent||concurrent conditionaly||conditionally conditon||condition condtion||condition condtional||conditional conected||connected conector||connector +configed||configured configration||configuration configred||configured configuartion||configuration @@ -392,8 +404,10 @@ configuratoin||configuration configuraton||configuration configuretion||configuration configutation||configuration +congiuration||configuration conider||consider conjuction||conjunction +connction||connection connecetd||connected connectinos||connections connetor||connector @@ -401,6 +415,8 @@ connnection||connection connnections||connections consistancy||consistency consistant||consistent +consits||consists +constructred||constructed containes||contains containts||contains contaisn||contains @@ -438,6 +454,7 @@ creationg||creating cryptocraphic||cryptographic cummulative||cumulative cunter||counter +curent||current curently||currently cylic||cyclic dafault||default @@ -449,7 +466,9 @@ decendant||descendant decendants||descendants decompres||decompress decsribed||described +decrese||decrease decription||description +detault||default dectected||detected defailt||default deferal||deferral @@ -472,6 +491,7 @@ delare||declare delares||declares delaring||declaring delemiter||delimiter +deley||delay delibrately||deliberately delievered||delivered demodualtor||demodulator @@ -485,6 +505,7 @@ depreacte||deprecate desactivate||deactivate desciptor||descriptor desciptors||descriptors +descritpor||descriptor descripto||descriptor descripton||description descrition||description @@ -537,12 +558,14 @@ disgest||digest disired||desired dispalying||displaying dissable||disable +dissapeared||disappeared diplay||display directon||direction direcly||directly direectly||directly diregard||disregard disassocation||disassociation +disassocative||disassociative disapear||disappear disapeared||disappeared disappared||disappeared @@ -591,6 +614,7 @@ eigth||eight elementry||elementary eletronic||electronic embeded||embedded +emtpy||empty enabledi||enabled enbale||enable enble||enable @@ -598,6 +622,7 @@ enchanced||enhanced encorporating||incorporating encrupted||encrypted encrypiton||encryption +encryped||encrypted encryptio||encryption endianess||endianness enpoint||endpoint @@ -627,6 +652,7 @@ etsbalishment||establishment evalute||evaluate evalutes||evaluates evalution||evaluation +evaulated||evaluated excecutable||executable excceed||exceed exceded||exceeded @@ -647,15 +673,18 @@ exlcude||exclude exlcuding||excluding exlcusive||exclusive exlusive||exclusive +exlicitly||explicitly exmaple||example expecially||especially experies||expires explicite||explicit +explicity||explicitly explicitely||explicitly explict||explicit explictely||explicitly explictly||explicitly expresion||expression +exprienced||experienced exprimental||experimental extened||extended exteneded||extended @@ -704,10 +733,12 @@ followign||following followings||following follwing||following fonud||found +forcebly||forcibly forseeable||foreseeable forse||force fortan||fortran forwardig||forwarding +forwared||forwarded frambuffer||framebuffer framming||framing framwork||framework @@ -748,6 +779,7 @@ grahpical||graphical granularty||granularity grapic||graphic grranted||granted +grups||groups guage||gauge guarenteed||guaranteed guarentee||guarantee @@ -761,6 +793,7 @@ hardare||hardware harware||hardware hardward||hardware havind||having +heigth||height heirarchically||hierarchically heirarchy||hierarchy heirachy||hierarchy @@ -769,9 +802,11 @@ hearbeat||heartbeat heterogenous||heterogeneous hexdecimal||hexadecimal hybernate||hibernate +hiearchy||hierarchy hierachy||hierarchy hierarchie||hierarchy homogenous||homogeneous +horizental||horizontal howver||however hsould||should hypervior||hypervisor @@ -823,6 +858,7 @@ independed||independent indiate||indicate indicat||indicate inexpect||inexpected +infalte||inflate inferface||interface infinit||infinite infomation||information @@ -831,6 +867,7 @@ informations||information informtion||information infromation||information ingore||ignore +inheritence||inheritance inital||initial initalized||initialized initalised||initialized @@ -841,6 +878,7 @@ initators||initiators initialiazation||initialization initializationg||initialization initializiation||initialization +initializtion||initialization initialze||initialize initialzed||initialized initialzing||initializing @@ -857,6 +895,7 @@ instanciate||instantiate instanciated||instantiated instuments||instruments insufficent||insufficient +intead||instead inteface||interface integreated||integrated integrety||integrity @@ -875,6 +914,7 @@ interoprability||interoperability interuupt||interrupt interupt||interrupt interupts||interrupts +interurpt||interrupt interrface||interface interrrupt||interrupt interrup||interrupt @@ -922,6 +962,7 @@ jumpimng||jumping juse||just jus||just kown||known +lable||label langage||language langauage||language langauge||language @@ -992,6 +1033,7 @@ metdata||metadata micropone||microphone microprocesspr||microprocessor migrateable||migratable +miliseconds||milliseconds millenium||millennium milliseonds||milliseconds minimim||minimum @@ -1058,6 +1100,7 @@ notications||notifications notifcations||notifications notifed||notified notity||notify +notfify||notify nubmer||number numebr||number numer||number @@ -1099,6 +1142,7 @@ orientatied||orientated orientied||oriented orignal||original originial||original +orphanded||orphaned otherise||otherwise ouput||output oustanding||outstanding @@ -1129,6 +1173,7 @@ palne||plane paramameters||parameters paramaters||parameters paramater||parameter +paramenters||parameters parametes||parameters parametised||parametrised paramter||parameter @@ -1160,9 +1205,11 @@ peroid||period persistance||persistence persistant||persistent phoneticly||phonetically +pipline||pipeline plaform||platform plalform||platform platfoem||platform +platfomr||platform platfrom||platform plattform||platform pleaes||please @@ -1174,9 +1221,11 @@ poiter||pointer posible||possible positon||position possibilites||possibilities +postion||position potocol||protocol powerfull||powerful pramater||parameter +preambule||preamble preamle||preamble preample||preamble preapre||prepare @@ -1185,11 +1234,14 @@ preceeding||preceding preceed||precede precendence||precedence precission||precision +predicition||prediction preemptable||preemptible prefered||preferred prefferably||preferably prefitler||prefilter preform||perform +previleged||privileged +previlege||privilege premption||preemption prepaired||prepared prepate||prepare @@ -1263,9 +1315,11 @@ querrying||querying queus||queues randomally||randomly raoming||roaming +readyness||readiness reasearcher||researcher reasearchers||researchers reasearch||research +recalcualte||recalculate receieve||receive recepient||recipient recevied||received @@ -1278,8 +1332,10 @@ recieves||receives recieving||receiving recogniced||recognised recognizeable||recognizable +recompte||recompute recommanded||recommended recyle||recycle +redect||reject redircet||redirect redirectrion||redirection redundacy||redundancy @@ -1287,7 +1343,9 @@ reename||rename refcounf||refcount refence||reference refered||referred +referencce||reference referenace||reference +refererence||reference refering||referring refernces||references refernnce||reference @@ -1312,17 +1370,21 @@ reloade||reload remoote||remote remore||remote removeable||removable +repective||respective repectively||respectively replacable||replaceable replacments||replacements replys||replies reponse||response representaion||representation +repsonse||response +reqested||requested reqeust||request reqister||register requed||requeued requestied||requested requiere||require +requieres||requires requirment||requirement requred||required requried||required @@ -1359,6 +1421,7 @@ reuest||request reuqest||request reutnred||returned revsion||revision +rewritting||rewriting rmeoved||removed rmeove||remove rmeoves||removes @@ -1409,6 +1472,7 @@ sequencial||sequential serivce||service serveral||several servive||service +sesion||session setts||sets settting||setting shapshot||snapshot @@ -1441,6 +1505,7 @@ soluation||solution souce||source speach||speech specfic||specific +specfication||specification specfield||specified speciefied||specified specifc||specific @@ -1541,6 +1606,7 @@ syncronus||synchronous syste||system sytem||system sythesis||synthesis +tagert||target taht||that tained||tainted tarffic||traffic @@ -1569,10 +1635,13 @@ trys||tries thses||these tiggers||triggers tiggered||triggered +tiggerring||triggering tipically||typically timeing||timing +timming||timing timout||timeout tmis||this +tolarance||tolerance toogle||toggle torerable||tolerable torlence||tolerance @@ -1594,9 +1663,12 @@ transision||transition transistioned||transitioned transmittd||transmitted transormed||transformed +trasaction||transaction trasfer||transfer trasmission||transmission +trasmitter||transmitter treshold||threshold +trigged||triggered triggerd||triggered trigerred||triggered trigerring||triggering @@ -1612,6 +1684,7 @@ uknown||unknown usccess||success uncommited||uncommitted uncompatible||incompatible +uncomressed||uncompressed unconditionaly||unconditionally undeflow||underflow undelying||underlying @@ -1679,6 +1752,7 @@ utitity||utility utitlty||utility vaid||valid vaild||valid +validationg||validating valide||valid variantions||variations varible||variable @@ -1688,6 +1762,7 @@ verbse||verbose veify||verify verfication||verification veriosn||version +versoin||version verisons||versions verison||version veritical||vertical diff --git a/scripts/sphinx-pre-install b/scripts/sphinx-pre-install index 4c781617ffe6..ad9945ccb0cf 100755 --- a/scripts/sphinx-pre-install +++ b/scripts/sphinx-pre-install @@ -300,8 +300,6 @@ sub check_sphinx() } $cur_version = get_sphinx_version($sphinx); - die ("$sphinx returned an error") if (!$cur_version); - die "$sphinx didn't return its version" if (!$cur_version); if ($cur_version lt $min_version) { @@ -514,6 +512,7 @@ sub give_mageia_hints() { my %map = ( "python-sphinx" => "python3-sphinx", + "yaml" => "python3-yaml", "virtualenv" => "python3-virtualenv", "dot" => "graphviz", "convert" => "ImageMagick", @@ -557,10 +556,11 @@ sub give_mageia_hints() sub give_arch_linux_hints() { my %map = ( + "yaml" => "python-yaml", "virtualenv" => "python-virtualenv", "dot" => "graphviz", "convert" => "imagemagick", - "xelatex" => "texlive-bin", + "xelatex" => "texlive-xetex", "latexmk" => "texlive-core", "rsvg-convert" => "extra/librsvg", ); @@ -587,6 +587,7 @@ sub give_arch_linux_hints() sub give_gentoo_hints() { my %map = ( + "yaml" => "dev-python/pyyaml", "virtualenv" => "dev-python/virtualenv", "dot" => "media-gfx/graphviz", "convert" => "media-gfx/imagemagick", diff --git a/scripts/ssl-common.h b/scripts/ssl-common.h new file mode 100644 index 000000000000..2db0e181143c --- /dev/null +++ b/scripts/ssl-common.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* + * SSL helper functions shared by sign-file and extract-cert. + */ + +static void drain_openssl_errors(int l, int silent) +{ + const char *file; + char buf[120]; + int e, line; + + if (ERR_peek_error() == 0) + return; + if (!silent) + fprintf(stderr, "At main.c:%d:\n", l); + + while ((e = ERR_peek_error_line(&file, &line))) { + ERR_error_string(e, buf); + if (!silent) + fprintf(stderr, "- SSL %s: %s:%d\n", buf, file, line); + ERR_get_error(); + } +} + +#define ERR(cond, fmt, ...) \ + do { \ + bool __cond = (cond); \ + drain_openssl_errors(__LINE__, 0); \ + if (__cond) { \ + errx(1, fmt, ## __VA_ARGS__); \ + } \ + } while (0) diff --git a/scripts/subarch.include b/scripts/subarch.include index 4bd327d0ae42..c4592d59d69b 100644 --- a/scripts/subarch.include +++ b/scripts/subarch.include @@ -6,7 +6,7 @@ SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \ -e s/sun4u/sparc64/ \ - -e s/arm.*/arm/ -e s/sa110/arm/ \ + -e /^arm64$$/!s/arm.*/arm/ -e s/sa110/arm/ \ -e s/s390x/s390/ \ -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \ -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ \ diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl new file mode 100644 index 000000000000..580b4e246aec --- /dev/null +++ b/scripts/syscall.tbl @@ -0,0 +1,410 @@ +# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note +# +# This file contains the system call numbers for all of the +# more recently added architectures. +# +# As a basic principle, no duplication of functionality +# should be added, e.g. we don't use lseek when llseek +# is present. New architectures should use this file +# and implement the less feature-full calls in user space. +# +0 common io_setup sys_io_setup compat_sys_io_setup +1 common io_destroy sys_io_destroy +2 common io_submit sys_io_submit compat_sys_io_submit +3 common io_cancel sys_io_cancel +4 time32 io_getevents sys_io_getevents_time32 +4 64 io_getevents sys_io_getevents +5 common setxattr sys_setxattr +6 common lsetxattr sys_lsetxattr +7 common fsetxattr sys_fsetxattr +8 common getxattr sys_getxattr +9 common lgetxattr sys_lgetxattr +10 common fgetxattr sys_fgetxattr +11 common listxattr sys_listxattr +12 common llistxattr sys_llistxattr +13 common flistxattr sys_flistxattr +14 common removexattr sys_removexattr +15 common lremovexattr sys_lremovexattr +16 common fremovexattr sys_fremovexattr +17 common getcwd sys_getcwd +18 common lookup_dcookie sys_ni_syscall +19 common eventfd2 sys_eventfd2 +20 common epoll_create1 sys_epoll_create1 +21 common epoll_ctl sys_epoll_ctl +22 common epoll_pwait sys_epoll_pwait compat_sys_epoll_pwait +23 common dup sys_dup +24 common dup3 sys_dup3 +25 32 fcntl64 sys_fcntl64 compat_sys_fcntl64 +25 64 fcntl sys_fcntl +26 common inotify_init1 sys_inotify_init1 +27 common inotify_add_watch sys_inotify_add_watch +28 common inotify_rm_watch sys_inotify_rm_watch +29 common ioctl sys_ioctl compat_sys_ioctl +30 common ioprio_set sys_ioprio_set +31 common ioprio_get sys_ioprio_get +32 common flock sys_flock +33 common mknodat sys_mknodat +34 common mkdirat sys_mkdirat +35 common unlinkat sys_unlinkat +36 common symlinkat sys_symlinkat +37 common linkat sys_linkat +# renameat is superseded with flags by renameat2 +38 renameat renameat sys_renameat +39 common umount2 sys_umount +40 common mount sys_mount +41 common pivot_root sys_pivot_root +42 common nfsservctl sys_ni_syscall +43 32 statfs64 sys_statfs64 compat_sys_statfs64 +43 64 statfs sys_statfs +44 32 fstatfs64 sys_fstatfs64 compat_sys_fstatfs64 +44 64 fstatfs sys_fstatfs +45 32 truncate64 sys_truncate64 compat_sys_truncate64 +45 64 truncate sys_truncate +46 32 ftruncate64 sys_ftruncate64 compat_sys_ftruncate64 +46 64 ftruncate sys_ftruncate +47 common fallocate sys_fallocate compat_sys_fallocate +48 common faccessat sys_faccessat +49 common chdir sys_chdir +50 common fchdir sys_fchdir +51 common chroot sys_chroot +52 common fchmod sys_fchmod +53 common fchmodat sys_fchmodat +54 common fchownat sys_fchownat +55 common fchown sys_fchown +56 common openat sys_openat +57 common close sys_close +58 common vhangup sys_vhangup +59 common pipe2 sys_pipe2 +60 common quotactl sys_quotactl +61 common getdents64 sys_getdents64 +62 32 llseek sys_llseek +62 64 lseek sys_lseek +63 common read sys_read +64 common write sys_write +65 common readv sys_readv sys_readv +66 common writev sys_writev sys_writev +67 common pread64 sys_pread64 compat_sys_pread64 +68 common pwrite64 sys_pwrite64 compat_sys_pwrite64 +69 common preadv sys_preadv compat_sys_preadv +70 common pwritev sys_pwritev compat_sys_pwritev +71 32 sendfile64 sys_sendfile64 +71 64 sendfile sys_sendfile64 +72 time32 pselect6 sys_pselect6_time32 compat_sys_pselect6_time32 +72 64 pselect6 sys_pselect6 +73 time32 ppoll sys_ppoll_time32 compat_sys_ppoll_time32 +73 64 ppoll sys_ppoll +74 common signalfd4 sys_signalfd4 compat_sys_signalfd4 +75 common vmsplice sys_vmsplice +76 common splice sys_splice +77 common tee sys_tee +78 common readlinkat sys_readlinkat +79 stat64 fstatat64 sys_fstatat64 +79 64 newfstatat sys_newfstatat +80 stat64 fstat64 sys_fstat64 +80 64 fstat sys_newfstat +81 common sync sys_sync +82 common fsync sys_fsync +83 common fdatasync sys_fdatasync +84 common sync_file_range sys_sync_file_range compat_sys_sync_file_range +85 common timerfd_create sys_timerfd_create +86 time32 timerfd_settime sys_timerfd_settime32 +86 64 timerfd_settime sys_timerfd_settime +87 time32 timerfd_gettime sys_timerfd_gettime32 +87 64 timerfd_gettime sys_timerfd_gettime +88 time32 utimensat sys_utimensat_time32 +88 64 utimensat sys_utimensat +89 common acct sys_acct +90 common capget sys_capget +91 common capset sys_capset +92 common personality sys_personality +93 common exit sys_exit +94 common exit_group sys_exit_group +95 common waitid sys_waitid compat_sys_waitid +96 common set_tid_address sys_set_tid_address +97 common unshare sys_unshare +98 time32 futex sys_futex_time32 +98 64 futex sys_futex +99 common set_robust_list sys_set_robust_list compat_sys_set_robust_list +100 common get_robust_list sys_get_robust_list compat_sys_get_robust_list +101 time32 nanosleep sys_nanosleep_time32 +101 64 nanosleep sys_nanosleep +102 common getitimer sys_getitimer compat_sys_getitimer +103 common setitimer sys_setitimer compat_sys_setitimer +104 common kexec_load sys_kexec_load compat_sys_kexec_load +105 common init_module sys_init_module +106 common delete_module sys_delete_module +107 common timer_create sys_timer_create compat_sys_timer_create +108 time32 timer_gettime sys_timer_gettime32 +108 64 timer_gettime sys_timer_gettime +109 common timer_getoverrun sys_timer_getoverrun +110 time32 timer_settime sys_timer_settime32 +110 64 timer_settime sys_timer_settime +111 common timer_delete sys_timer_delete +112 time32 clock_settime sys_clock_settime32 +112 64 clock_settime sys_clock_settime +113 time32 clock_gettime sys_clock_gettime32 +113 64 clock_gettime sys_clock_gettime +114 time32 clock_getres sys_clock_getres_time32 +114 64 clock_getres sys_clock_getres +115 time32 clock_nanosleep sys_clock_nanosleep_time32 +115 64 clock_nanosleep sys_clock_nanosleep +116 common syslog sys_syslog +117 common ptrace sys_ptrace compat_sys_ptrace +118 common sched_setparam sys_sched_setparam +119 common sched_setscheduler sys_sched_setscheduler +120 common sched_getscheduler sys_sched_getscheduler +121 common sched_getparam sys_sched_getparam +122 common sched_setaffinity sys_sched_setaffinity compat_sys_sched_setaffinity +123 common sched_getaffinity sys_sched_getaffinity compat_sys_sched_getaffinity +124 common sched_yield sys_sched_yield +125 common sched_get_priority_max sys_sched_get_priority_max +126 common sched_get_priority_min sys_sched_get_priority_min +127 time32 sched_rr_get_interval sys_sched_rr_get_interval_time32 +127 64 sched_rr_get_interval sys_sched_rr_get_interval +128 common restart_syscall sys_restart_syscall +129 common kill sys_kill +130 common tkill sys_tkill +131 common tgkill sys_tgkill +132 common sigaltstack sys_sigaltstack compat_sys_sigaltstack +133 common rt_sigsuspend sys_rt_sigsuspend compat_sys_rt_sigsuspend +134 common rt_sigaction sys_rt_sigaction compat_sys_rt_sigaction +135 common rt_sigprocmask sys_rt_sigprocmask compat_sys_rt_sigprocmask +136 common rt_sigpending sys_rt_sigpending compat_sys_rt_sigpending +137 time32 rt_sigtimedwait sys_rt_sigtimedwait_time32 compat_sys_rt_sigtimedwait_time32 +137 64 rt_sigtimedwait sys_rt_sigtimedwait +138 common rt_sigqueueinfo sys_rt_sigqueueinfo compat_sys_rt_sigqueueinfo +139 common rt_sigreturn sys_rt_sigreturn compat_sys_rt_sigreturn +140 common setpriority sys_setpriority +141 common getpriority sys_getpriority +142 common reboot sys_reboot +143 common setregid sys_setregid +144 common setgid sys_setgid +145 common setreuid sys_setreuid +146 common setuid sys_setuid +147 common setresuid sys_setresuid +148 common getresuid sys_getresuid +149 common setresgid sys_setresgid +150 common getresgid sys_getresgid +151 common setfsuid sys_setfsuid +152 common setfsgid sys_setfsgid +153 common times sys_times compat_sys_times +154 common setpgid sys_setpgid +155 common getpgid sys_getpgid +156 common getsid sys_getsid +157 common setsid sys_setsid +158 common getgroups sys_getgroups +159 common setgroups sys_setgroups +160 common uname sys_newuname +161 common sethostname sys_sethostname +162 common setdomainname sys_setdomainname +# getrlimit and setrlimit are superseded with prlimit64 +163 rlimit getrlimit sys_getrlimit compat_sys_getrlimit +164 rlimit setrlimit sys_setrlimit compat_sys_setrlimit +165 common getrusage sys_getrusage compat_sys_getrusage +166 common umask sys_umask +167 common prctl sys_prctl +168 common getcpu sys_getcpu +169 time32 gettimeofday sys_gettimeofday compat_sys_gettimeofday +169 64 gettimeofday sys_gettimeofday +170 time32 settimeofday sys_settimeofday compat_sys_settimeofday +170 64 settimeofday sys_settimeofday +171 time32 adjtimex sys_adjtimex_time32 +171 64 adjtimex sys_adjtimex +172 common getpid sys_getpid +173 common getppid sys_getppid +174 common getuid sys_getuid +175 common geteuid sys_geteuid +176 common getgid sys_getgid +177 common getegid sys_getegid +178 common gettid sys_gettid +179 common sysinfo sys_sysinfo compat_sys_sysinfo +180 common mq_open sys_mq_open compat_sys_mq_open +181 common mq_unlink sys_mq_unlink +182 time32 mq_timedsend sys_mq_timedsend_time32 +182 64 mq_timedsend sys_mq_timedsend +183 time32 mq_timedreceive sys_mq_timedreceive_time32 +183 64 mq_timedreceive sys_mq_timedreceive +184 common mq_notify sys_mq_notify compat_sys_mq_notify +185 common mq_getsetattr sys_mq_getsetattr compat_sys_mq_getsetattr +186 common msgget sys_msgget +187 common msgctl sys_msgctl compat_sys_msgctl +188 common msgrcv sys_msgrcv compat_sys_msgrcv +189 common msgsnd sys_msgsnd compat_sys_msgsnd +190 common semget sys_semget +191 common semctl sys_semctl compat_sys_semctl +192 time32 semtimedop sys_semtimedop_time32 +192 64 semtimedop sys_semtimedop +193 common semop sys_semop +194 common shmget sys_shmget +195 common shmctl sys_shmctl compat_sys_shmctl +196 common shmat sys_shmat compat_sys_shmat +197 common shmdt sys_shmdt +198 common socket sys_socket +199 common socketpair sys_socketpair +200 common bind sys_bind +201 common listen sys_listen +202 common accept sys_accept +203 common connect sys_connect +204 common getsockname sys_getsockname +205 common getpeername sys_getpeername +206 common sendto sys_sendto +207 common recvfrom sys_recvfrom compat_sys_recvfrom +208 common setsockopt sys_setsockopt sys_setsockopt +209 common getsockopt sys_getsockopt sys_getsockopt +210 common shutdown sys_shutdown +211 common sendmsg sys_sendmsg compat_sys_sendmsg +212 common recvmsg sys_recvmsg compat_sys_recvmsg +213 common readahead sys_readahead compat_sys_readahead +214 common brk sys_brk +215 common munmap sys_munmap +216 common mremap sys_mremap +217 common add_key sys_add_key +218 common request_key sys_request_key +219 common keyctl sys_keyctl compat_sys_keyctl +220 common clone sys_clone +221 common execve sys_execve compat_sys_execve +222 32 mmap2 sys_mmap2 +222 64 mmap sys_mmap +223 32 fadvise64_64 sys_fadvise64_64 compat_sys_fadvise64_64 +223 64 fadvise64 sys_fadvise64_64 +224 common swapon sys_swapon +225 common swapoff sys_swapoff +226 common mprotect sys_mprotect +227 common msync sys_msync +228 common mlock sys_mlock +229 common munlock sys_munlock +230 common mlockall sys_mlockall +231 common munlockall sys_munlockall +232 common mincore sys_mincore +233 common madvise sys_madvise +234 common remap_file_pages sys_remap_file_pages +235 common mbind sys_mbind +236 common get_mempolicy sys_get_mempolicy +237 common set_mempolicy sys_set_mempolicy +238 common migrate_pages sys_migrate_pages +239 common move_pages sys_move_pages +240 common rt_tgsigqueueinfo sys_rt_tgsigqueueinfo compat_sys_rt_tgsigqueueinfo +241 common perf_event_open sys_perf_event_open +242 common accept4 sys_accept4 +243 time32 recvmmsg sys_recvmmsg_time32 compat_sys_recvmmsg_time32 +243 64 recvmmsg sys_recvmmsg +# Architectures may provide up to 16 syscalls of their own between 244 and 259 +244 arc cacheflush sys_cacheflush +245 arc arc_settls sys_arc_settls +246 arc arc_gettls sys_arc_gettls +247 arc sysfs sys_sysfs +248 arc arc_usr_cmpxchg sys_arc_usr_cmpxchg + +244 csky set_thread_area sys_set_thread_area +245 csky cacheflush sys_cacheflush + +244 nios2 cacheflush sys_cacheflush + +244 or1k or1k_atomic sys_or1k_atomic + +258 riscv riscv_hwprobe sys_riscv_hwprobe +259 riscv riscv_flush_icache sys_riscv_flush_icache + +260 time32 wait4 sys_wait4 compat_sys_wait4 +260 64 wait4 sys_wait4 +261 common prlimit64 sys_prlimit64 +262 common fanotify_init sys_fanotify_init +263 common fanotify_mark sys_fanotify_mark +264 common name_to_handle_at sys_name_to_handle_at +265 common open_by_handle_at sys_open_by_handle_at +266 time32 clock_adjtime sys_clock_adjtime32 +266 64 clock_adjtime sys_clock_adjtime +267 common syncfs sys_syncfs +268 common setns sys_setns +269 common sendmmsg sys_sendmmsg compat_sys_sendmmsg +270 common process_vm_readv sys_process_vm_readv +271 common process_vm_writev sys_process_vm_writev +272 common kcmp sys_kcmp +273 common finit_module sys_finit_module +274 common sched_setattr sys_sched_setattr +275 common sched_getattr sys_sched_getattr +276 common renameat2 sys_renameat2 +277 common seccomp sys_seccomp +278 common getrandom sys_getrandom +279 common memfd_create sys_memfd_create +280 common bpf sys_bpf +281 common execveat sys_execveat compat_sys_execveat +282 common userfaultfd sys_userfaultfd +283 common membarrier sys_membarrier +284 common mlock2 sys_mlock2 +285 common copy_file_range sys_copy_file_range +286 common preadv2 sys_preadv2 compat_sys_preadv2 +287 common pwritev2 sys_pwritev2 compat_sys_pwritev2 +288 common pkey_mprotect sys_pkey_mprotect +289 common pkey_alloc sys_pkey_alloc +290 common pkey_free sys_pkey_free +291 common statx sys_statx +292 time32 io_pgetevents sys_io_pgetevents_time32 compat_sys_io_pgetevents +292 64 io_pgetevents sys_io_pgetevents +293 common rseq sys_rseq +294 common kexec_file_load sys_kexec_file_load +# 295 through 402 are unassigned to sync up with generic numbers don't use +403 32 clock_gettime64 sys_clock_gettime +404 32 clock_settime64 sys_clock_settime +405 32 clock_adjtime64 sys_clock_adjtime +406 32 clock_getres_time64 sys_clock_getres +407 32 clock_nanosleep_time64 sys_clock_nanosleep +408 32 timer_gettime64 sys_timer_gettime +409 32 timer_settime64 sys_timer_settime +410 32 timerfd_gettime64 sys_timerfd_gettime +411 32 timerfd_settime64 sys_timerfd_settime +412 32 utimensat_time64 sys_utimensat +413 32 pselect6_time64 sys_pselect6 compat_sys_pselect6_time64 +414 32 ppoll_time64 sys_ppoll compat_sys_ppoll_time64 +416 32 io_pgetevents_time64 sys_io_pgetevents compat_sys_io_pgetevents_time64 +417 32 recvmmsg_time64 sys_recvmmsg compat_sys_recvmmsg_time64 +418 32 mq_timedsend_time64 sys_mq_timedsend +419 32 mq_timedreceive_time64 sys_mq_timedreceive +420 32 semtimedop_time64 sys_semtimedop +421 32 rt_sigtimedwait_time64 sys_rt_sigtimedwait compat_sys_rt_sigtimedwait_time64 +422 32 futex_time64 sys_futex +423 32 sched_rr_get_interval_time64 sys_sched_rr_get_interval +424 common pidfd_send_signal sys_pidfd_send_signal +425 common io_uring_setup sys_io_uring_setup +426 common io_uring_enter sys_io_uring_enter +427 common io_uring_register sys_io_uring_register +428 common open_tree sys_open_tree +429 common move_mount sys_move_mount +430 common fsopen sys_fsopen +431 common fsconfig sys_fsconfig +432 common fsmount sys_fsmount +433 common fspick sys_fspick +434 common pidfd_open sys_pidfd_open +435 common clone3 sys_clone3 +436 common close_range sys_close_range +437 common openat2 sys_openat2 +438 common pidfd_getfd sys_pidfd_getfd +439 common faccessat2 sys_faccessat2 +440 common process_madvise sys_process_madvise +441 common epoll_pwait2 sys_epoll_pwait2 compat_sys_epoll_pwait2 +442 common mount_setattr sys_mount_setattr +443 common quotactl_fd sys_quotactl_fd +444 common landlock_create_ruleset sys_landlock_create_ruleset +445 common landlock_add_rule sys_landlock_add_rule +446 common landlock_restrict_self sys_landlock_restrict_self +447 memfd_secret memfd_secret sys_memfd_secret +448 common process_mrelease sys_process_mrelease +449 common futex_waitv sys_futex_waitv +450 common set_mempolicy_home_node sys_set_mempolicy_home_node +451 common cachestat sys_cachestat +452 common fchmodat2 sys_fchmodat2 +453 common map_shadow_stack sys_map_shadow_stack +454 common futex_wake sys_futex_wake +455 common futex_wait sys_futex_wait +456 common futex_requeue sys_futex_requeue +457 common statmount sys_statmount +458 common listmount sys_listmount +459 common lsm_get_self_attr sys_lsm_get_self_attr +460 common lsm_set_self_attr sys_lsm_set_self_attr +461 common lsm_list_modules sys_lsm_list_modules +462 common mseal sys_mseal +463 common setxattrat sys_setxattrat +464 common getxattrat sys_getxattrat +465 common listxattrat sys_listxattrat +466 common removexattrat sys_removexattrat +467 common open_tree_attr sys_open_tree_attr diff --git a/scripts/syscalltbl.sh b/scripts/syscalltbl.sh index 6abe143889ef..6a903b87a7c2 100755 --- a/scripts/syscalltbl.sh +++ b/scripts/syscalltbl.sh @@ -54,7 +54,7 @@ nxt=0 grep -E "^[0-9]+[[:space:]]+$abis" "$infile" | { - while read nr abi name native compat ; do + while read nr abi name native compat noreturn; do if [ $nxt -gt $nr ]; then echo "error: $infile: syscall table is not sorted or duplicates the same syscall number" >&2 @@ -66,7 +66,21 @@ grep -E "^[0-9]+[[:space:]]+$abis" "$infile" | { nxt=$((nxt + 1)) done - if [ -n "$compat" ]; then + if [ "$compat" = "-" ]; then + unset compat + fi + + if [ -n "$noreturn" ]; then + if [ "$noreturn" != "noreturn" ]; then + echo "error: $infile: invalid string \"$noreturn\" in 'noreturn' column" + exit 1 + fi + if [ -n "$compat" ]; then + echo "__SYSCALL_COMPAT_NORETURN($nr, $native, $compat)" + else + echo "__SYSCALL_NORETURN($nr, $native)" + fi + elif [ -n "$compat" ]; then echo "__SYSCALL_WITH_COMPAT($nr, $native, $compat)" elif [ -n "$native" ]; then echo "__SYSCALL($nr, $native)" diff --git a/scripts/tags.sh b/scripts/tags.sh index 191e0461d6d5..99ce427d9a69 100755 --- a/scripts/tags.sh +++ b/scripts/tags.sh @@ -146,15 +146,14 @@ dogtags() # a ^[^#] is prepended by setup_regex unless an anchor is already present regex_asm=( '/^\(ENTRY\|_GLOBAL\)([[:space:]]*\([[:alnum:]_\\]*\)).*/\2/' + '/^SYM_[[:alnum:]_]*START[[:alnum:]_]*([[:space:]]*\([[:alnum:]_\\]*\)).*/\1/' ) regex_c=( '/^SYSCALL_DEFINE[0-9]([[:space:]]*\([[:alnum:]_]*\).*/sys_\1/' '/^BPF_CALL_[0-9]([[:space:]]*\([[:alnum:]_]*\).*/\1/' '/^COMPAT_SYSCALL_DEFINE[0-9]([[:space:]]*\([[:alnum:]_]*\).*/compat_sys_\1/' '/^TRACE_EVENT([[:space:]]*\([[:alnum:]_]*\).*/trace_\1/' - '/^TRACE_EVENT([[:space:]]*\([[:alnum:]_]*\).*/trace_\1_rcuidle/' '/^DEFINE_EVENT([^,)]*,[[:space:]]*\([[:alnum:]_]*\).*/trace_\1/' - '/^DEFINE_EVENT([^,)]*,[[:space:]]*\([[:alnum:]_]*\).*/trace_\1_rcuidle/' '/^DEFINE_INSN_CACHE_OPS([[:space:]]*\([[:alnum:]_]*\).*/get_\1_slot/' '/^DEFINE_INSN_CACHE_OPS([[:space:]]*\([[:alnum:]_]*\).*/free_\1_slot/' '/^PAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/Page\1/' @@ -190,8 +189,9 @@ regex_c=( '/^PCI_OP_WRITE([[:space:]]*\(\w*\).*[1-4])/pci_bus_write_config_\1/' '/\<DEFINE_\(RT_MUTEX\|MUTEX\|SEMAPHORE\|SPINLOCK\)([[:space:]]*\([[:alnum:]_]*\)/\2/v/' '/\<DEFINE_\(RAW_SPINLOCK\|RWLOCK\|SEQLOCK\)([[:space:]]*\([[:alnum:]_]*\)/\2/v/' + '/\<DEFINE_TIMER(\([^,)]*\),/\1/' '/\<DECLARE_\(RWSEM\|COMPLETION\)([[:space:]]*\([[:alnum:]_]\+\)/\2/v/' - '/\<DECLARE_BITMAP([[:space:]]*\([[:alnum:]_]*\)/\1/v/' + '/\<DECLARE_BITMAP([[:space:]]*\([[:alnum:]_]\+\)/\1/v/' '/\(^\|\s\)\(\|L\|H\)LIST_HEAD([[:space:]]*\([[:alnum:]_]*\)/\3/v/' '/\(^\|\s\)RADIX_TREE([[:space:]]*\([[:alnum:]_]*\)/\2/v/' '/\<DEFINE_PER_CPU([^,]*,[[:space:]]*\([[:alnum:]_]*\)/\1/v/' @@ -212,6 +212,15 @@ regex_c=( '/\<\(DEFINE\|DECLARE\)_STATIC_KEY_\(TRUE\|FALSE\)\(\|_RO\)([[:space:]]*\([[:alnum:]_]\+\)/\4/' '/^SEQCOUNT_LOCKTYPE(\([^,]*\),[[:space:]]*\([^,]*\),[^)]*)/seqcount_\2_t/' '/^SEQCOUNT_LOCKTYPE(\([^,]*\),[[:space:]]*\([^,]*\),[^)]*)/seqcount_\2_init/' + '/^\<DECLARE_IDTENTRY[[:alnum:]_]*([^,)]*,[[:space:]]*\([[:alnum:]_]\+\)/\1/' + '/^\<DEFINE_IDTENTRY[[:alnum:]_]*([[:space:]]*\([[:alnum:]_]\+\)/\1/' + '/^\<DEFINE_FREE(\([[:alnum:]_]\+\)/cleanup_\1/' + '/^\<DEFINE_CLASS(\([[:alnum:]_]\+\)/class_\1/' + '/^\<EXTEND_CLASS(\([[:alnum:]_]\+\),[[:space:]]*\([[:alnum:]_]\+\)/class_\1\2/' + '/^\<DEFINE_GUARD(\([[:alnum:]_]\+\)/class_\1/' + '/^\<DEFINE_GUARD_COND(\([[:alnum:]_]\+\),[[:space:]]*\([[:alnum:]_]\+\)/class_\1\2/' + '/^\<DEFINE_LOCK_GUARD_[[:digit:]](\([[:alnum:]_]\+\)/class_\1/' + '/^\<DEFINE_LOCK_GUARD_[[:digit:]]_COND(\([[:alnum:]_]\+\),[[:space:]]*\([[:alnum:]_]\+\)/class_\1\2/' ) regex_kconfig=( '/^[[:blank:]]*\(menu\|\)config[[:blank:]]\+\([[:alnum:]_]\+\)/\2/' @@ -257,19 +266,30 @@ exuberant() CTAGS_EXTRA="extras" fi setup_regex exuberant asm c - all_target_sources | xargs $1 -a \ - -I __initdata,__exitdata,__initconst,__ro_after_init \ - -I __initdata_memblock \ - -I __refdata,__attribute,__maybe_unused,__always_unused \ - -I __acquires,__releases,__deprecated,__always_inline \ - -I __read_mostly,__aligned,____cacheline_aligned \ - -I ____cacheline_aligned_in_smp \ - -I __cacheline_aligned,__cacheline_aligned_in_smp \ - -I ____cacheline_internodealigned_in_smp \ - -I __used,__packed,__packed2__,__must_check,__must_hold \ - -I EXPORT_SYMBOL,EXPORT_SYMBOL_GPL,ACPI_EXPORT_SYMBOL \ - -I DEFINE_TRACE,EXPORT_TRACEPOINT_SYMBOL,EXPORT_TRACEPOINT_SYMBOL_GPL \ - -I static,const \ + # identifiers to ignore by ctags + local ign=( + ACPI_EXPORT_SYMBOL + DECLARE_BITMAP + DEFINE_{TRACE,MUTEX,TIMER} + EXPORT_SYMBOL EXPORT_SYMBOL_GPL + EXPORT_TRACEPOINT_SYMBOL EXPORT_TRACEPOINT_SYMBOL_GPL + ____cacheline_aligned ____cacheline_aligned_in_smp + ____cacheline_internodealigned_in_smp + __acquires __aligned __always_inline __always_unused + __attribute + __cacheline_aligned __cacheline_aligned_in_smp + __deprecated + __exitdata + __initconst __initdata __initdata_memblock + __maybe_unused __must_check __must_hold + __packed __packed2__ + __read_mostly __refdata __releases __ro_after_init + __used + const + static + ) + all_target_sources | \ + xargs $1 -a -I "$(IFS=','; echo "${ign[*]}")" \ --$CTAGS_EXTRA=+fq --c-kinds=+px --fields=+iaS --langmap=c:+.h \ "${regex[@]}" @@ -324,7 +344,7 @@ case "$1" in "tags") rm -f tags - xtags ctags + xtags ${CTAGS:-ctags} remove_structs=y ;; diff --git a/scripts/test_fortify.sh b/scripts/test_fortify.sh deleted file mode 100644 index c2688ab8281d..000000000000 --- a/scripts/test_fortify.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0-only -set -e - -# Argument 1: Source file to build. -IN="$1" -shift -# Extract just the filename for error messages below. -FILE="${IN##*/}" -# Extract the function name for error messages below. -FUNC="${FILE#*-}" -FUNC="${FUNC%%-*}" -FUNC="${FUNC%%.*}" -# Extract the symbol to test for in build/symbol test below. -WANT="__${FILE%%-*}" - -# Argument 2: Where to write the build log. -OUT="$1" -shift -TMP="${OUT}.tmp" - -# Argument 3: Path to "nm" tool. -NM="$1" -shift - -# Remaining arguments are: $(CC) $(c_flags) - -# Clean up temporary file at exit. -__cleanup() { - rm -f "$TMP" -} -trap __cleanup EXIT - -# Function names in warnings are wrapped in backticks under UTF-8 locales. -# Run the commands with LANG=C so that grep output will not change. -export LANG=C - -status= -# Attempt to build a source that is expected to fail with a specific warning. -if "$@" -Werror -c "$IN" -o "$OUT".o 2> "$TMP" ; then - # If the build succeeds, either the test has failed or the - # warning may only happen at link time (Clang). In that case, - # make sure the expected symbol is unresolved in the symbol list. - # If so, FORTIFY is working for this case. - if ! $NM -A "$OUT".o | grep -m1 "\bU ${WANT}$" >>"$TMP" ; then - status="warning: unsafe ${FUNC}() usage lacked '$WANT' symbol in $IN" - fi -else - # If the build failed, check for the warning in the stderr. - # GCC: - # ./include/linux/fortify-string.h:316:25: error: call to '__write_overflow_field' declared with attribute warning: detected write beyond size of field (1st parameter); maybe use struct_group()? [-Werror=attribute-warning] - # Clang 14: - # ./include/linux/fortify-string.h:316:4: error: call to __write_overflow_field declared with 'warning' attribute: detected write beyond size of field (1st parameter); maybe use struct_group()? [-Werror,-Wattribute-warning] - if ! grep -Eq -m1 "error: call to .?\b${WANT}\b.?" "$TMP" ; then - status="warning: unsafe ${FUNC}() usage lacked '$WANT' warning in $IN" - fi -fi - -if [ -n "$status" ]; then - # Report on failure results, including compilation warnings. - echo "$status" | tee "$OUT" >&2 -else - # Report on good results, and save any compilation output to log. - echo "ok: unsafe ${FUNC}() usage correctly detected with '$WANT' in $IN" >"$OUT" -fi -cat "$TMP" >>"$OUT" diff --git a/scripts/tracing/draw_functrace.py b/scripts/tracing/draw_functrace.py deleted file mode 100755 index 42fa87300941..000000000000 --- a/scripts/tracing/draw_functrace.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python -# SPDX-License-Identifier: GPL-2.0-only - -""" -Copyright 2008 (c) Frederic Weisbecker <fweisbec@gmail.com> - -This script parses a trace provided by the function tracer in -kernel/trace/trace_functions.c -The resulted trace is processed into a tree to produce a more human -view of the call stack by drawing textual but hierarchical tree of -calls. Only the functions's names and the call time are provided. - -Usage: - Be sure that you have CONFIG_FUNCTION_TRACER - # mount -t tracefs nodev /sys/kernel/tracing - # echo function > /sys/kernel/tracing/current_tracer - $ cat /sys/kernel/tracing/trace_pipe > ~/raw_trace_func - Wait some times but not too much, the script is a bit slow. - Break the pipe (Ctrl + Z) - $ scripts/tracing/draw_functrace.py < ~/raw_trace_func > draw_functrace - Then you have your drawn trace in draw_functrace -""" - - -import sys, re - -class CallTree: - """ This class provides a tree representation of the functions - call stack. If a function has no parent in the kernel (interrupt, - syscall, kernel thread...) then it is attached to a virtual parent - called ROOT. - """ - ROOT = None - - def __init__(self, func, time = None, parent = None): - self._func = func - self._time = time - if parent is None: - self._parent = CallTree.ROOT - else: - self._parent = parent - self._children = [] - - def calls(self, func, calltime): - """ If a function calls another one, call this method to insert it - into the tree at the appropriate place. - @return: A reference to the newly created child node. - """ - child = CallTree(func, calltime, self) - self._children.append(child) - return child - - def getParent(self, func): - """ Retrieve the last parent of the current node that - has the name given by func. If this function is not - on a parent, then create it as new child of root - @return: A reference to the parent. - """ - tree = self - while tree != CallTree.ROOT and tree._func != func: - tree = tree._parent - if tree == CallTree.ROOT: - child = CallTree.ROOT.calls(func, None) - return child - return tree - - def __repr__(self): - return self.__toString("", True) - - def __toString(self, branch, lastChild): - if self._time is not None: - s = "%s----%s (%s)\n" % (branch, self._func, self._time) - else: - s = "%s----%s\n" % (branch, self._func) - - i = 0 - if lastChild: - branch = branch[:-1] + " " - while i < len(self._children): - if i != len(self._children) - 1: - s += "%s" % self._children[i].__toString(branch +\ - " |", False) - else: - s += "%s" % self._children[i].__toString(branch +\ - " |", True) - i += 1 - return s - -class BrokenLineException(Exception): - """If the last line is not complete because of the pipe breakage, - we want to stop the processing and ignore this line. - """ - pass - -class CommentLineException(Exception): - """ If the line is a comment (as in the beginning of the trace file), - just ignore it. - """ - pass - - -def parseLine(line): - line = line.strip() - if line.startswith("#"): - raise CommentLineException - m = re.match("[^]]+?\\] +([a-z.]+) +([0-9.]+): (\\w+) <-(\\w+)", line) - if m is None: - raise BrokenLineException - return (m.group(2), m.group(3), m.group(4)) - - -def main(): - CallTree.ROOT = CallTree("Root (Nowhere)", None, None) - tree = CallTree.ROOT - - for line in sys.stdin: - try: - calltime, callee, caller = parseLine(line) - except BrokenLineException: - break - except CommentLineException: - continue - tree = tree.getParent(caller) - tree = tree.calls(callee, calltime) - - print(CallTree.ROOT) - -if __name__ == "__main__": - main() diff --git a/scripts/unifdef.c b/scripts/unifdef.c index db00e3e30a59..ff15efd6e7d7 100644 --- a/scripts/unifdef.c +++ b/scripts/unifdef.c @@ -203,7 +203,7 @@ static int depth; /* current #if nesting */ static int delcount; /* count of deleted lines */ static unsigned blankcount; /* count of blank lines */ static unsigned blankmax; /* maximum recent blankcount */ -static bool constexpr; /* constant #if expression */ +static bool constexpression; /* constant #if expression */ static bool zerosyms = true; /* to format symdepth output */ static bool firstsym; /* ditto */ @@ -819,7 +819,7 @@ static const struct ops { /* * Function for evaluating the innermost parts of expressions, * viz. !expr (expr) number defined(symbol) symbol - * We reset the constexpr flag in the last two cases. + * We reset the constexpression flag in the last two cases. */ static Linetype eval_unary(const struct ops *ops, int *valp, const char **cpp) @@ -877,7 +877,7 @@ eval_unary(const struct ops *ops, int *valp, const char **cpp) cp = skipcomment(cp); if (defparen && *cp++ != ')') return (LT_ERROR); - constexpr = false; + constexpression = false; } else if (!endsym(*cp)) { debug("eval%d symbol", ops - eval_ops); sym = findsym(cp); @@ -895,7 +895,7 @@ eval_unary(const struct ops *ops, int *valp, const char **cpp) lt = *valp ? LT_TRUE : LT_FALSE; cp = skipargs(cp); } - constexpr = false; + constexpression = false; } else { debug("eval%d bad expr", ops - eval_ops); return (LT_ERROR); @@ -955,10 +955,10 @@ ifeval(const char **cpp) int val = 0; debug("eval %s", *cpp); - constexpr = killconsts ? false : true; + constexpression = killconsts ? false : true; ret = eval_table(eval_ops, &val, cpp); debug("eval = %d", val); - return (constexpr ? LT_IF : ret == LT_ERROR ? LT_IF : ret); + return (constexpression ? LT_IF : ret == LT_ERROR ? LT_IF : ret); } /* diff --git a/scripts/verify_builtin_ranges.awk b/scripts/verify_builtin_ranges.awk new file mode 100755 index 000000000000..0de7ed521601 --- /dev/null +++ b/scripts/verify_builtin_ranges.awk @@ -0,0 +1,370 @@ +#!/usr/bin/gawk -f +# SPDX-License-Identifier: GPL-2.0 +# verify_builtin_ranges.awk: Verify address range data for builtin modules +# Written by Kris Van Hees <kris.van.hees@oracle.com> +# +# Usage: verify_builtin_ranges.awk modules.builtin.ranges System.map \ +# modules.builtin vmlinux.map vmlinux.o.map +# + +# Return the module name(s) (if any) associated with the given object. +# +# If we have seen this object before, return information from the cache. +# Otherwise, retrieve it from the corresponding .cmd file. +# +function get_module_info(fn, mod, obj, s) { + if (fn in omod) + return omod[fn]; + + if (match(fn, /\/[^/]+$/) == 0) + return ""; + + obj = fn; + mod = ""; + fn = substr(fn, 1, RSTART) "." substr(fn, RSTART + 1) ".cmd"; + if (getline s <fn == 1) { + if (match(s, /DKBUILD_MODFILE=['"]+[^'"]+/) > 0) { + mod = substr(s, RSTART + 16, RLENGTH - 16); + gsub(/['"]/, "", mod); + } else if (match(s, /RUST_MODFILE=[^ ]+/) > 0) + mod = substr(s, RSTART + 13, RLENGTH - 13); + } else { + print "ERROR: Failed to read: " fn "\n\n" \ + " For kernels built with O=<objdir>, cd to <objdir>\n" \ + " and execute this script as ./source/scripts/..." \ + >"/dev/stderr"; + close(fn); + total = 0; + exit(1); + } + close(fn); + + # A single module (common case) also reflects objects that are not part + # of a module. Some of those objects have names that are also a module + # name (e.g. core). We check the associated module file name, and if + # they do not match, the object is not part of a module. + if (mod !~ / /) { + if (!(mod in mods)) + mod = ""; + } + + gsub(/([^/ ]*\/)+/, "", mod); + gsub(/-/, "_", mod); + + # At this point, mod is a single (valid) module name, or a list of + # module names (that do not need validation). + omod[obj] = mod; + + return mod; +} + +# Return a representative integer value for a given hexadecimal address. +# +# Since all kernel addresses fall within the same memory region, we can safely +# strip off the first 6 hex digits before performing the hex-to-dec conversion, +# thereby avoiding integer overflows. +# +function addr2val(val) { + sub(/^0x/, "", val); + if (length(val) == 16) + val = substr(val, 5); + return strtonum("0x" val); +} + +# Determine the kernel build directory to use (default is .). +# +BEGIN { + if (ARGC < 6) { + print "Syntax: verify_builtin_ranges.awk <ranges-file> <system-map>\n" \ + " <builtin-file> <vmlinux-map> <vmlinux-o-map>\n" \ + >"/dev/stderr"; + total = 0; + exit(1); + } +} + +# (1) Load the built-in module address range data. +# +ARGIND == 1 { + ranges[FNR] = $0; + rcnt++; + next; +} + +# (2) Annotate System.map symbols with module names. +# +ARGIND == 2 { + addr = addr2val($1); + name = $3; + + while (addr >= mod_eaddr) { + if (sect_symb) { + if (sect_symb != name) + next; + + sect_base = addr - sect_off; + if (dbg) + printf "[%s] BASE (%s) %016x - %016x = %016x\n", sect_name, sect_symb, addr, sect_off, sect_base >"/dev/stderr"; + sect_symb = 0; + } + + if (++ridx > rcnt) + break; + + $0 = ranges[ridx]; + sub(/-/, " "); + if ($4 != "=") { + sub(/-/, " "); + mod_saddr = strtonum("0x" $2) + sect_base; + mod_eaddr = strtonum("0x" $3) + sect_base; + $1 = $2 = $3 = ""; + sub(/^ +/, ""); + mod_name = $0; + + if (dbg) + printf "[%s] %s from %016x to %016x\n", sect_name, mod_name, mod_saddr, mod_eaddr >"/dev/stderr"; + } else { + sect_name = $1; + sect_off = strtonum("0x" $2); + sect_symb = $5; + } + } + + idx = addr"-"name; + if (addr >= mod_saddr && addr < mod_eaddr) + sym2mod[idx] = mod_name; + + next; +} + +# Once we are done annotating the System.map, we no longer need the ranges data. +# +FNR == 1 && ARGIND == 3 { + delete ranges; +} + +# (3) Build a lookup map of built-in module names. +# +# Lines from modules.builtin will be like: +# kernel/crypto/lzo-rle.ko +# and we record the object name "crypto/lzo-rle". +# +ARGIND == 3 { + sub(/kernel\//, ""); # strip off "kernel/" prefix + sub(/\.ko$/, ""); # strip off .ko suffix + + mods[$1] = 1; + next; +} + +# (4) Get a list of symbols (per object). +# +# Symbols by object are read from vmlinux.map, with fallback to vmlinux.o.map +# if vmlinux is found to have inked in vmlinux.o. +# + +# If we were able to get the data we need from vmlinux.map, there is no need to +# process vmlinux.o.map. +# +FNR == 1 && ARGIND == 5 && total > 0 { + if (dbg) + printf "Note: %s is not needed.\n", FILENAME >"/dev/stderr"; + exit; +} + +# First determine whether we are dealing with a GNU ld or LLVM lld linker map. +# +ARGIND >= 4 && FNR == 1 && NF == 7 && $1 == "VMA" && $7 == "Symbol" { + map_is_lld = 1; + next; +} + +# (LLD) Convert a section record fronm lld format to ld format. +# +ARGIND >= 4 && map_is_lld && NF == 5 && /[0-9] [^ ]+$/ { + $0 = $5 " 0x"$1 " 0x"$3 " load address 0x"$2; +} + +# (LLD) Convert an object record from lld format to ld format. +# +ARGIND >= 4 && map_is_lld && NF == 5 && $5 ~ /:\(/ { + if (/\.a\(/ && !/ vmlinux\.a\(/) + next; + + gsub(/\)/, ""); + sub(/:\(/, " "); + sub(/ vmlinux\.a\(/, " "); + $0 = " "$6 " 0x"$1 " 0x"$3 " " $5; +} + +# (LLD) Convert a symbol record from lld format to ld format. +# +ARGIND >= 4 && map_is_lld && NF == 5 && $5 ~ /^[A-Za-z_][A-Za-z0-9_]*$/ { + $0 = " 0x" $1 " " $5; +} + +# (LLD) We do not need any other ldd linker map records. +# +ARGIND >= 4 && map_is_lld && /^[0-9a-f]{16} / { + next; +} + +# Handle section records with long section names (spilling onto a 2nd line). +# +ARGIND >= 4 && !map_is_lld && NF == 1 && /^[^ ]/ { + s = $0; + getline; + $0 = s " " $0; +} + +# Next section - previous one is done. +# +ARGIND >= 4 && /^[^ ]/ { + sect = 0; +} + +# Get the (top level) section name. +# +ARGIND >= 4 && /^\./ { + # Explicitly ignore a few sections that are not relevant here. + if ($1 ~ /^\.orc_/ || $1 ~ /_sites$/ || $1 ~ /\.percpu/) + next; + + # Sections with a 0-address can be ignored as well (in vmlinux.map). + if (ARGIND == 4 && $2 ~ /^0x0+$/) + next; + + sect = $1; + + next; +} + +# If we are not currently in a section we care about, ignore records. +# +!sect { + next; +} + +# Handle object records with long section names (spilling onto a 2nd line). +# +ARGIND >= 4 && /^ [^ \*]/ && NF == 1 { + # If the section name is long, the remainder of the entry is found on + # the next line. + s = $0; + getline; + $0 = s " " $0; +} + +# Objects linked in from static libraries are ignored. +# If the object is vmlinux.o, we need to consult vmlinux.o.map for per-object +# symbol information +# +ARGIND == 4 && /^ [^ ]/ && NF == 4 { + if ($4 ~ /\.a\(/) + next; + + idx = sect":"$1; + if (!(idx in sect_addend)) { + sect_addend[idx] = addr2val($2); + if (dbg) + printf "ADDEND %s = %016x\n", idx, sect_addend[idx] >"/dev/stderr"; + } + if ($4 == "vmlinux.o") { + need_o_map = 1; + next; + } +} + +# If data from vmlinux.o.map is needed, we only process section and object +# records from vmlinux.map to determine which section we need to pay attention +# to in vmlinux.o.map. So skip everything else from vmlinux.map. +# +ARGIND == 4 && need_o_map { + next; +} + +# Get module information for the current object. +# +ARGIND >= 4 && /^ [^ ]/ && NF == 4 { + msect = $1; + mod_name = get_module_info($4); + mod_eaddr = addr2val($2) + addr2val($3); + + next; +} + +# Process a symbol record. +# +# Evaluate the module information obtained from vmlinux.map (or vmlinux.o.map) +# as follows: +# - For all symbols in a given object: +# - If the symbol is annotated with the same module name(s) that the object +# belongs to, count it as a match. +# - Otherwise: +# - If the symbol is known to have duplicates of which at least one is +# in a built-in module, disregard it. +# - If the symbol us not annotated with any module name(s) AND the +# object belongs to built-in modules, count it as missing. +# - Otherwise, count it as a mismatch. +# +ARGIND >= 4 && /^ / && NF == 2 && $1 ~ /^0x/ { + idx = sect":"msect; + if (!(idx in sect_addend)) + next; + + addr = addr2val($1); + + # Handle the rare but annoying case where a 0-size symbol is placed at + # the byte *after* the module range. Based on vmlinux.map it will be + # considered part of the current object, but it falls just beyond the + # module address range. Unfortunately, its address could be at the + # start of another built-in module, so the only safe thing to do is to + # ignore it. + if (mod_name && addr == mod_eaddr) + next; + + # If we are processing vmlinux.o.map, we need to apply the base address + # of the section to the relative address on the record. + # + if (ARGIND == 5) + addr += sect_addend[idx]; + + idx = addr"-"$2; + mod = ""; + if (idx in sym2mod) { + mod = sym2mod[idx]; + if (sym2mod[idx] == mod_name) { + mod_matches++; + matches++; + } else if (mod_name == "") { + print $2 " in " mod " (should NOT be)"; + mismatches++; + } else { + print $2 " in " mod " (should be " mod_name ")"; + mismatches++; + } + } else if (mod_name != "") { + print $2 " should be in " mod_name; + missing++; + } else + matches++; + + total++; + + next; +} + +# Issue the comparison report. +# +END { + if (total) { + printf "Verification of %s:\n", ARGV[1]; + printf " Correct matches: %6d (%d%% of total)\n", matches, 100 * matches / total; + printf " Module matches: %6d (%d%% of matches)\n", mod_matches, 100 * mod_matches / matches; + printf " Mismatches: %6d (%d%% of total)\n", mismatches, 100 * mismatches / total; + printf " Missing: %6d (%d%% of total)\n", missing, 100 * missing / total; + + if (mismatches || missing) + exit(1); + } +} diff --git a/scripts/xz_wrap.sh b/scripts/xz_wrap.sh index d06baf626abe..f19369687030 100755 --- a/scripts/xz_wrap.sh +++ b/scripts/xz_wrap.sh @@ -1,22 +1,162 @@ #!/bin/sh +# SPDX-License-Identifier: 0BSD # # This is a wrapper for xz to compress the kernel image using appropriate # compression options depending on the architecture. # # Author: Lasse Collin <lasse.collin@tukaani.org> + +# This has specialized settings for the following archs. However, +# XZ-compressed kernel isn't currently supported on every listed arch. # -# This file has been put into the public domain. -# You can do whatever you want with this file. -# +# Arch Align Notes +# arm 2/4 ARM and ARM-Thumb2 +# arm64 4 +# csky 2 +# loongarch 4 +# mips 2/4 MicroMIPS is 2-byte aligned +# parisc 4 +# powerpc 4 Uses its own wrapper for compressors instead of this. +# riscv 2/4 +# s390 2 +# sh 2 +# sparc 4 +# x86 1 + +# A few archs use 2-byte or 4-byte aligned instructions depending on +# the kernel config. This function is used to check if the relevant +# config option is set to "y". +is_enabled() +{ + grep -q "^$1=y$" include/config/auto.conf +} + +# XZ_VERSION is needed to disable features that aren't available in +# old XZ Utils versions. +XZ_VERSION=$($XZ --robot --version) || exit +XZ_VERSION=$(printf '%s\n' "$XZ_VERSION" | sed -n 's/^XZ_VERSION=//p') +# Assume that no BCJ filter is available. BCJ= -LZMA2OPTS= +# Set the instruction alignment to 1, 2, or 4 bytes. +# +# Set the BCJ filter if one is available. +# It must match the #ifdef usage in lib/decompress_unxz.c. case $SRCARCH in - x86) BCJ=--x86 ;; - powerpc) BCJ=--powerpc ;; - arm) BCJ=--arm ;; - sparc) BCJ=--sparc ;; + arm) + if is_enabled CONFIG_THUMB2_KERNEL; then + ALIGN=2 + BCJ=--armthumb + else + ALIGN=4 + BCJ=--arm + fi + ;; + + arm64) + ALIGN=4 + + # ARM64 filter was added in XZ Utils 5.4.0. + if [ "$XZ_VERSION" -ge 50040002 ]; then + BCJ=--arm64 + else + echo "$0: Upgrading to xz >= 5.4.0" \ + "would enable the ARM64 filter" \ + "for better compression" >&2 + fi + ;; + + csky) + ALIGN=2 + ;; + + loongarch) + ALIGN=4 + ;; + + mips) + if is_enabled CONFIG_CPU_MICROMIPS; then + ALIGN=2 + else + ALIGN=4 + fi + ;; + + parisc) + ALIGN=4 + ;; + + powerpc) + ALIGN=4 + + # The filter is only for big endian instruction encoding. + if is_enabled CONFIG_CPU_BIG_ENDIAN; then + BCJ=--powerpc + fi + ;; + + riscv) + if is_enabled CONFIG_RISCV_ISA_C; then + ALIGN=2 + else + ALIGN=4 + fi + + # RISC-V filter was added in XZ Utils 5.6.0. + if [ "$XZ_VERSION" -ge 50060002 ]; then + BCJ=--riscv + else + echo "$0: Upgrading to xz >= 5.6.0" \ + "would enable the RISC-V filter" \ + "for better compression" >&2 + fi + ;; + + s390) + ALIGN=2 + ;; + + sh) + ALIGN=2 + ;; + + sparc) + ALIGN=4 + BCJ=--sparc + ;; + + x86) + ALIGN=1 + BCJ=--x86 + ;; + + *) + echo "$0: Arch-specific tuning is missing for '$SRCARCH'" >&2 + + # Guess 2-byte-aligned instructions. Guessing too low + # should hurt less than guessing too high. + ALIGN=2 + ;; +esac + +# Select the LZMA2 options matching the instruction alignment. +case $ALIGN in + 1) LZMA2OPTS= ;; + 2) LZMA2OPTS=lp=1 ;; + 4) LZMA2OPTS=lp=2,lc=2 ;; + *) echo "$0: ALIGN wrong or missing" >&2; exit 1 ;; esac -exec $XZ --check=crc32 $BCJ --lzma2=$LZMA2OPTS,dict=32MiB +# Use single-threaded mode because it compresses a little better +# (and uses less RAM) than multithreaded mode. +# +# For the best compression, the dictionary size shouldn't be +# smaller than the uncompressed kernel. 128 MiB dictionary +# needs less than 1400 MiB of RAM in single-threaded mode. +# +# On the archs that use this script to compress the kernel, +# decompression in the preboot code is done in single-call mode. +# Thus the dictionary size doesn't affect the memory requirements +# of the preboot decompressor at all. +exec $XZ --check=crc32 --threads=1 $BCJ --lzma2=$LZMA2OPTS,dict=128MiB |