From 3f1fe48a36b0b6722dc3fd421d93512bac138e9a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 2 Oct 2026 12:17:24 -0700 Subject: Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux Pull io_uring fixes from Jens Axboe: - Fix a task_work add use-after-free with SQPOLL. The sqpoll thread could pop and complete the last request while io_req_normal_work_add() was still looking at them after the mpscq push. Use the same approach as DEFER_TASKRUN to protect from that, holding an RCU read lock across the add, and have exit wait for an RCU grace period for SQPOLL rings as well. - CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED rings - Mark the source filter table as COW when cloning bpf filters, so registering another filter on the source doesn't modify the shared table in place - Initialize the task context before running the BPF loop - Requeue zcrx multishot receives stopped by a local resource - End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit) * tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: io_uring: fix task_work add use-after-free with SQPOLL io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full io_uring/zcrx: requeue multishot receives stopped by a local resource io_uring: initialize task context before running the BPF loop io_uring: zero big_cqe for aux CQEs on CQE32 rings io_uring: fix free entry check for 32b CQEs on CQE32 rings io_uring: only post the dummy skip CQE on CQE_MIXED rings io_uring/bpf_filter: mark source as COW when cloning filters --- scripts/.gitignore | 15 + scripts/Kbuild.include | 272 + scripts/Kconfig.include | 78 + scripts/Lindent | 26 + scripts/Makefile | 69 + scripts/Makefile.asm-headers | 105 + scripts/Makefile.autofdo | 28 + scripts/Makefile.btf | 32 + scripts/Makefile.build | 598 ++ scripts/Makefile.clang | 42 + scripts/Makefile.clean | 63 + scripts/Makefile.compiler | 93 + scripts/Makefile.context-analysis | 11 + scripts/Makefile.debug | 52 + scripts/Makefile.defconf | 30 + scripts/Makefile.dtbinst | 43 + scripts/Makefile.dtbs | 153 + scripts/Makefile.gcc-plugins | 29 + scripts/Makefile.headersinst | 84 + scripts/Makefile.host | 165 + scripts/Makefile.kasan | 106 + scripts/Makefile.kcov | 11 + scripts/Makefile.kcsan | 26 + scripts/Makefile.kmsan | 8 + scripts/Makefile.kstack_erase | 21 + scripts/Makefile.lib | 515 ++ scripts/Makefile.modfinal | 74 + scripts/Makefile.modinst | 167 + scripts/Makefile.modpost | 157 + scripts/Makefile.package | 286 + scripts/Makefile.propeller | 39 + scripts/Makefile.randstruct | 17 + scripts/Makefile.thinlto | 40 + scripts/Makefile.ubsan | 28 + scripts/Makefile.userprogs | 45 + scripts/Makefile.vdsoinst | 45 + scripts/Makefile.vmlinux | 175 + scripts/Makefile.vmlinux_a | 82 + scripts/Makefile.vmlinux_o | 98 + scripts/Makefile.warn | 219 + scripts/as-version.sh | 82 + scripts/asn1_compiler.c | 1611 ++++ scripts/atomic/atomic-tbl.sh | 290 + scripts/atomic/atomics.tbl | 41 + scripts/atomic/fallbacks/acquire | 5 + scripts/atomic/fallbacks/add_negative | 3 + scripts/atomic/fallbacks/add_unless | 3 + scripts/atomic/fallbacks/andnot | 3 + scripts/atomic/fallbacks/cmpxchg | 3 + scripts/atomic/fallbacks/dec | 3 + scripts/atomic/fallbacks/dec_and_test | 3 + scripts/atomic/fallbacks/dec_if_positive | 11 + scripts/atomic/fallbacks/dec_unless_positive | 10 + scripts/atomic/fallbacks/fence | 7 + scripts/atomic/fallbacks/fetch_add_unless | 10 + scripts/atomic/fallbacks/inc | 3 + scripts/atomic/fallbacks/inc_and_test | 3 + scripts/atomic/fallbacks/inc_not_zero | 3 + scripts/atomic/fallbacks/inc_unless_negative | 10 + scripts/atomic/fallbacks/read_acquire | 12 + scripts/atomic/fallbacks/release | 4 + scripts/atomic/fallbacks/set_release | 8 + scripts/atomic/fallbacks/sub_and_test | 3 + scripts/atomic/fallbacks/try_cmpxchg | 7 + scripts/atomic/fallbacks/xchg | 3 + scripts/atomic/gen-atomic-fallback.sh | 336 + scripts/atomic/gen-atomic-instrumented.sh | 184 + scripts/atomic/gen-atomic-long.sh | 100 + scripts/atomic/gen-atomics.sh | 21 + scripts/atomic/gen-rust-atomic-helpers.sh | 62 + scripts/atomic/kerneldoc/add | 13 + scripts/atomic/kerneldoc/add_negative | 13 + scripts/atomic/kerneldoc/add_unless | 19 + scripts/atomic/kerneldoc/and | 13 + scripts/atomic/kerneldoc/andnot | 13 + scripts/atomic/kerneldoc/cmpxchg | 15 + scripts/atomic/kerneldoc/dec | 12 + scripts/atomic/kerneldoc/dec_and_test | 12 + scripts/atomic/kerneldoc/dec_if_positive | 13 + scripts/atomic/kerneldoc/dec_unless_positive | 13 + scripts/atomic/kerneldoc/inc | 12 + scripts/atomic/kerneldoc/inc_and_test | 12 + scripts/atomic/kerneldoc/inc_not_zero | 13 + scripts/atomic/kerneldoc/inc_unless_negative | 13 + scripts/atomic/kerneldoc/or | 13 + scripts/atomic/kerneldoc/read | 12 + scripts/atomic/kerneldoc/set | 13 + scripts/atomic/kerneldoc/sub | 13 + scripts/atomic/kerneldoc/sub_and_test | 13 + scripts/atomic/kerneldoc/try_cmpxchg | 16 + scripts/atomic/kerneldoc/xchg | 13 + scripts/atomic/kerneldoc/xor | 13 + scripts/bash-completion/make | 451 ++ scripts/basic/.gitignore | 3 + scripts/basic/Makefile | 21 + scripts/basic/fixdep.c | 440 ++ scripts/bloat-o-meter | 113 + scripts/bootgraph.pl | 224 + scripts/bpf_doc.py | 1020 +++ scripts/build-version | 10 + scripts/cc-can-link.sh | 11 + scripts/cc-version.sh | 66 + scripts/check-function-names.sh | 25 + scripts/check-git | 14 + scripts/check-sysctl-docs | 174 + scripts/check-uapi.sh | 578 ++ scripts/check_extable.sh | 147 + scripts/checkdeclares.pl | 53 + scripts/checker-valid.sh | 19 + scripts/checkincludes.pl | 98 + scripts/checkkconfigsymbols.py | 482 ++ scripts/checkpatch.pl | 7984 ++++++++++++++++++++ scripts/checkstack.pl | 208 + scripts/checksyscalls.sh | 282 + scripts/checkversion.pl | 76 + scripts/clang-tools/gen_compile_commands.py | 230 + scripts/clang-tools/run-clang-tools.py | 101 + scripts/cleanfile | 178 + scripts/cleanpatch | 260 + scripts/coccicheck | 286 + scripts/coccinelle/api/alloc/alloc_cast.cocci | 138 + .../coccinelle/api/alloc/pool_zalloc-simple.cocci | 74 + scripts/coccinelle/api/alloc/zalloc-simple.cocci | 372 + scripts/coccinelle/api/atomic_as_refcounter.cocci | 126 + scripts/coccinelle/api/check_bq27xxx_data.cocci | 162 + scripts/coccinelle/api/d_find_alias.cocci | 81 + scripts/coccinelle/api/device_attr_show.cocci | 54 + scripts/coccinelle/api/err_cast.cocci | 57 + scripts/coccinelle/api/kfree_mismatch.cocci | 228 + scripts/coccinelle/api/kfree_sensitive.cocci | 100 + scripts/coccinelle/api/kmalloc_objs.cocci | 135 + scripts/coccinelle/api/kstrdup.cocci | 105 + scripts/coccinelle/api/kvmalloc.cocci | 256 + scripts/coccinelle/api/memdup.cocci | 66 + scripts/coccinelle/api/memdup_user.cocci | 119 + scripts/coccinelle/api/platform_get_irq.cocci | 102 + scripts/coccinelle/api/platform_no_drv_owner.cocci | 189 + scripts/coccinelle/api/pm_runtime.cocci | 113 + scripts/coccinelle/api/resource_size.cocci | 94 + scripts/coccinelle/api/simple_open.cocci | 71 + scripts/coccinelle/api/stream_open.cocci | 369 + scripts/coccinelle/api/string_choices.cocci | 302 + scripts/coccinelle/api/vma_pages.cocci | 61 + scripts/coccinelle/free/clk_put.cocci | 68 + scripts/coccinelle/free/devm_free.cocci | 152 + scripts/coccinelle/free/ifnulldev_put.cocci | 55 + scripts/coccinelle/free/ifnullfree.cocci | 67 + scripts/coccinelle/free/iounmap.cocci | 68 + scripts/coccinelle/free/kfree.cocci | 134 + scripts/coccinelle/free/kfreeaddr.cocci | 37 + scripts/coccinelle/free/put_device.cocci | 55 + scripts/coccinelle/hid/ff_race.cocci | 35 + .../iterators/device_node_continue.cocci | 104 + scripts/coccinelle/iterators/for_each_child.cocci | 358 + scripts/coccinelle/iterators/itnull.cocci | 95 + .../coccinelle/iterators/list_entry_update.cocci | 63 + scripts/coccinelle/iterators/use_after_iter.cocci | 153 + scripts/coccinelle/locks/call_kern.cocci | 106 + scripts/coccinelle/locks/double_lock.cocci | 106 + scripts/coccinelle/locks/flags.cocci | 81 + scripts/coccinelle/locks/mini_lock.cocci | 119 + scripts/coccinelle/misc/add_namespace.cocci | 31 + scripts/coccinelle/misc/array_size.cocci | 88 + scripts/coccinelle/misc/array_size_dup.cocci | 209 + scripts/coccinelle/misc/badty.cocci | 77 + scripts/coccinelle/misc/boolconv.cocci | 91 + scripts/coccinelle/misc/cond_no_effect.cocci | 65 + scripts/coccinelle/misc/cstptr.cocci | 42 + scripts/coccinelle/misc/do_div.cocci | 155 + scripts/coccinelle/misc/doubleinit.cocci | 54 + scripts/coccinelle/misc/excluded_middle.cocci | 39 + scripts/coccinelle/misc/flexible_array.cocci | 107 + scripts/coccinelle/misc/ifcol.cocci | 49 + scripts/coccinelle/misc/irqf_oneshot.cocci | 113 + scripts/coccinelle/misc/minmax.cocci | 260 + scripts/coccinelle/misc/newline_in_nl_msg.cocci | 78 + scripts/coccinelle/misc/noderef.cocci | 82 + scripts/coccinelle/misc/of_table.cocci | 86 + scripts/coccinelle/misc/orplus.cocci | 71 + scripts/coccinelle/misc/ptr_err_to_pe.cocci | 34 + scripts/coccinelle/misc/returnvar.cocci | 67 + scripts/coccinelle/misc/secs_to_jiffies.cocci | 71 + scripts/coccinelle/misc/semicolon.cocci | 84 + scripts/coccinelle/misc/struct_size.cocci | 70 + scripts/coccinelle/misc/swap.cocci | 122 + scripts/coccinelle/misc/test_addr.cocci | 33 + scripts/coccinelle/misc/uninitialized_var.cocci | 56 + scripts/coccinelle/misc/warn.cocci | 110 + scripts/coccinelle/null/badzero.cocci | 239 + scripts/coccinelle/null/deref_null.cocci | 283 + scripts/coccinelle/null/eno.cocci | 49 + scripts/coccinelle/null/kmerr.cocci | 73 + scripts/coccinelle/tests/doublebitand.cocci | 55 + scripts/coccinelle/tests/doubletest.cocci | 59 + scripts/coccinelle/tests/odd_ptr_err.cocci | 118 + .../tests/unsigned_lesser_than_zero.cocci | 76 + scripts/config | 240 + scripts/const_structs.checkpatch | 113 + scripts/container | 199 + scripts/context-analysis-suppression.txt | 34 + scripts/crypto/gen-aead-testvecs.py | 69 + scripts/crypto/gen-fips-testvecs.py | 148 + scripts/crypto/gen-hash-testvecs.py | 364 + scripts/decode_stacktrace.sh | 368 + scripts/decodecode | 251 + scripts/depmod.sh | 32 + scripts/dev-needs.sh | 315 + scripts/diffconfig | 144 + scripts/dtc/.gitignore | 3 + scripts/dtc/Makefile | 27 + scripts/dtc/checks.c | 2082 +++++ scripts/dtc/data.c | 293 + scripts/dtc/dt-check-style | 1205 +++ scripts/dtc/dt-extract-compatibles | 135 + .../dt-style-selftest/bad/dts-child-name-order.dts | 32 + .../bad/dts-extend-node-child-name-order.dts | 25 + .../bad/dts-extend-node-digit-node-order.dts | 33 + .../dtc/dt-style-selftest/bad/dts-mixed-indent.dts | 21 + .../dt-style-selftest/bad/dts-property-order.dts | 43 + scripts/dtc/dt-style-selftest/bad/dts-spaces.dts | 12 + .../bad/yaml-child-addr-order.yaml | 41 + .../bad/yaml-child-name-order.yaml | 37 + .../dtc/dt-style-selftest/bad/yaml-cont-align.yaml | 30 + .../bad/yaml-digit-node-order.yaml | 37 + .../dtc/dt-style-selftest/bad/yaml-hex-case.yaml | 29 + .../dt-style-selftest/bad/yaml-indent-strict.yaml | 29 + .../bad/yaml-label-in-string.yaml | 30 + .../dt-style-selftest/bad/yaml-line-length.yaml | 29 + .../dt-style-selftest/bad/yaml-mixed-indent.yaml | 29 + .../dt-style-selftest/bad/yaml-multi-close.yaml | 35 + .../dtc/dt-style-selftest/bad/yaml-node-close.yaml | 31 + .../bad/yaml-prop-order-device-type.yaml | 31 + .../dtc/dt-style-selftest/bad/yaml-prop-order.yaml | 29 + .../dt-style-selftest/bad/yaml-prop-pairing.yaml | 33 + .../dt-style-selftest/bad/yaml-required-blank.yaml | 33 + scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml | 29 + .../bad/yaml-trailing-comment.yaml | 26 + .../dt-style-selftest/bad/yaml-trailing-ws.yaml | 29 + .../bad/yaml-unclosed-comment.yaml | 30 + .../bad/yaml-unit-addr-prefix.yaml | 29 + .../dtc/dt-style-selftest/bad/yaml-unit-addr.yaml | 29 + .../dt-style-selftest/bad/yaml-unused-label.yaml | 29 + .../bad/yaml-value-ws-multiline.yaml | 27 + .../dtc/dt-style-selftest/bad/yaml-value-ws.yaml | 29 + .../expected/dts-child-name-order.dts.txt | 2 + .../dts-extend-node-child-name-order.dts.txt | 2 + .../dts-extend-node-digit-node-order.dts.txt | 2 + .../expected/dts-mixed-indent.dts.txt | 9 + .../expected/dts-property-order.dts.txt | 6 + .../dt-style-selftest/expected/dts-spaces.dts.txt | 2 + .../expected/yaml-child-addr-order.yaml.txt | 2 + .../expected/yaml-child-name-order.yaml.txt | 2 + .../expected/yaml-cont-align.yaml.txt | 2 + .../expected/yaml-digit-node-order.yaml.txt | 2 + .../expected/yaml-hex-case.yaml.txt | 2 + .../expected/yaml-indent-strict.yaml.txt | 2 + .../expected/yaml-label-in-string.yaml.txt | 2 + .../expected/yaml-line-length.yaml.txt | 2 + .../expected/yaml-mixed-indent.yaml.txt | 2 + .../expected/yaml-multi-close.yaml.txt | 3 + .../expected/yaml-node-close.yaml.txt | 2 + .../expected/yaml-prop-order-device-type.yaml.txt | 2 + .../expected/yaml-prop-order.yaml.txt | 2 + .../expected/yaml-prop-pairing.yaml.txt | 3 + .../expected/yaml-required-blank.yaml.txt | 3 + .../dt-style-selftest/expected/yaml-tab.yaml.txt | 2 + .../expected/yaml-trailing-comment.yaml.txt | 2 + .../expected/yaml-trailing-ws.yaml.txt | 2 + .../expected/yaml-unclosed-comment.yaml.txt | 2 + .../expected/yaml-unit-addr-prefix.yaml.txt | 2 + .../expected/yaml-unit-addr.yaml.txt | 2 + .../expected/yaml-unused-label.yaml.txt | 2 + .../expected/yaml-value-ws-multiline.yaml.txt | 2 + .../expected/yaml-value-ws.yaml.txt | 2 + .../good/dts-child-name-order.dts | 32 + .../dtc/dt-style-selftest/good/dts-cont-align.dts | 26 + .../good/dts-digit-node-order.dts | 43 + scripts/dtc/dt-style-selftest/good/dts-dtc.dts | 21 + .../good/dts-extend-node-child-name-order.dts | 25 + .../good/dts-extend-node-digit-node-order.dts | 33 + .../dt-style-selftest/good/dts-preprocessor.dts | 21 + .../dt-style-selftest/good/dts-property-order.dts | 41 + scripts/dtc/dt-style-selftest/good/dts-tab.dts | 29 + scripts/dtc/dt-style-selftest/good/soc.dtsi | 49 + .../dtc/dt-style-selftest/good/yaml-4space.yaml | 41 + .../good/yaml-tricky-parsing.yaml | 57 + scripts/dtc/dt-style-selftest/run.sh | 71 + scripts/dtc/dt_to_config | 1212 +++ scripts/dtc/dtc-lexer.l | 309 + scripts/dtc/dtc-parser.y | 599 ++ scripts/dtc/dtc.c | 385 + scripts/dtc/dtc.h | 377 + scripts/dtc/dtx_diff | 361 + scripts/dtc/fdtget.c | 352 + scripts/dtc/fdtoverlay.c | 214 + scripts/dtc/fdtput.c | 348 + scripts/dtc/flattree.c | 927 +++ scripts/dtc/fstree.c | 76 + scripts/dtc/include-prefixes/arc | 1 + scripts/dtc/include-prefixes/arm | 1 + scripts/dtc/include-prefixes/arm64 | 1 + scripts/dtc/include-prefixes/dt-bindings | 1 + scripts/dtc/include-prefixes/microblaze | 1 + scripts/dtc/include-prefixes/mips | 1 + scripts/dtc/include-prefixes/nios2 | 1 + scripts/dtc/include-prefixes/openrisc | 1 + scripts/dtc/include-prefixes/powerpc | 1 + scripts/dtc/include-prefixes/riscv | 1 + scripts/dtc/include-prefixes/sh | 1 + scripts/dtc/include-prefixes/xtensa | 1 + scripts/dtc/libfdt/fdt.c | 347 + scripts/dtc/libfdt/fdt.h | 66 + scripts/dtc/libfdt/fdt_addresses.c | 101 + scripts/dtc/libfdt/fdt_empty_tree.c | 38 + scripts/dtc/libfdt/fdt_overlay.c | 1101 +++ scripts/dtc/libfdt/fdt_ro.c | 888 +++ scripts/dtc/libfdt/fdt_rw.c | 512 ++ scripts/dtc/libfdt/fdt_strerror.c | 60 + scripts/dtc/libfdt/fdt_sw.c | 384 + scripts/dtc/libfdt/fdt_wip.c | 94 + scripts/dtc/libfdt/libfdt.h | 2540 +++++++ scripts/dtc/libfdt/libfdt_env.h | 69 + scripts/dtc/libfdt/libfdt_internal.h | 202 + scripts/dtc/livetree.c | 1315 ++++ scripts/dtc/of_unittest_expect | 541 ++ scripts/dtc/srcpos.c | 441 ++ scripts/dtc/srcpos.h | 104 + scripts/dtc/treesource.c | 437 ++ scripts/dtc/update-dtc-source.sh | 80 + scripts/dtc/util.c | 495 ++ scripts/dtc/util.h | 256 + scripts/dtc/version_gen.h | 1 + scripts/dtc/yamltree.c | 235 + .../dummy-plugin-dir/include/plugin-version.h | 0 scripts/dummy-tools/gcc | 116 + scripts/dummy-tools/ld | 30 + scripts/dummy-tools/nm | 1 + scripts/dummy-tools/objcopy | 1 + scripts/dummy-tools/pahole | 4 + scripts/dummy-tools/python3 | 4 + scripts/elf-parse.c | 198 + scripts/elf-parse.h | 305 + scripts/extract-fwblobs | 30 + scripts/extract-ikconfig | 69 + scripts/extract-module-sig.pl | 138 + scripts/extract-sys-certs.pl | 159 + scripts/extract-vmlinux | 65 + scripts/extract_xc3028.pl | 1717 +++++ scripts/faddr2line | 380 + scripts/file-size.sh | 4 + scripts/gcc-plugins/.gitignore | 2 + scripts/gcc-plugins/Kconfig | 39 + scripts/gcc-plugins/Makefile | 72 + scripts/gcc-plugins/gcc-common.h | 438 ++ scripts/gcc-plugins/gcc-generate-gimple-pass.h | 145 + scripts/gcc-plugins/gcc-generate-ipa-pass.h | 248 + scripts/gcc-plugins/gcc-generate-rtl-pass.h | 140 + scripts/gcc-plugins/gcc-generate-simple_ipa-pass.h | 140 + scripts/gcc-plugins/latent_entropy_plugin.c | 626 ++ scripts/gcc-plugins/randomize_layout_plugin.c | 881 +++ scripts/gcc-plugins/stackleak_plugin.c | 635 ++ scripts/gdb/Makefile | 2 + scripts/gdb/linux/.gitignore | 4 + scripts/gdb/linux/Makefile | 25 + scripts/gdb/linux/__init__.py | 1 + scripts/gdb/linux/bpf.py | 253 + scripts/gdb/linux/clk.py | 78 + scripts/gdb/linux/config.py | 44 + scripts/gdb/linux/constants.py.in | 175 + scripts/gdb/linux/cpus.py | 227 + scripts/gdb/linux/device.py | 182 + scripts/gdb/linux/dmesg.py | 152 + scripts/gdb/linux/genpd.py | 85 + scripts/gdb/linux/interrupts.py | 200 + scripts/gdb/linux/kasan.py | 44 + scripts/gdb/linux/lists.py | 136 + scripts/gdb/linux/mapletree.py | 252 + scripts/gdb/linux/mm.py | 574 ++ scripts/gdb/linux/modules.py | 134 + scripts/gdb/linux/page_owner.py | 184 + scripts/gdb/linux/pgtable.py | 220 + scripts/gdb/linux/proc.py | 277 + scripts/gdb/linux/radixtree.py | 215 + scripts/gdb/linux/rbtree.py | 189 + scripts/gdb/linux/slab.py | 325 + scripts/gdb/linux/stackdepot.py | 86 + scripts/gdb/linux/symbols.py | 338 + scripts/gdb/linux/tasks.py | 127 + scripts/gdb/linux/timerlist.py | 216 + scripts/gdb/linux/utils.py | 273 + scripts/gdb/linux/vfs.py | 59 + scripts/gdb/linux/vmalloc.py | 62 + scripts/gdb/linux/xarray.py | 28 + scripts/gdb/vmlinux-gdb.py | 52 + scripts/gen-btf.sh | 147 + scripts/gen-crc-consts.py | 291 + scripts/gen-randstruct-seed.sh | 7 + scripts/gen_packed_field_checks.c | 37 + scripts/gendwarfksyms/.gitignore | 2 + scripts/gendwarfksyms/Makefile | 12 + scripts/gendwarfksyms/cache.c | 51 + scripts/gendwarfksyms/die.c | 166 + scripts/gendwarfksyms/dwarf.c | 1185 +++ scripts/gendwarfksyms/examples/kabi.h | 174 + scripts/gendwarfksyms/examples/kabi_ex.c | 37 + scripts/gendwarfksyms/examples/kabi_ex.h | 360 + scripts/gendwarfksyms/examples/symbolptr.c | 33 + scripts/gendwarfksyms/gendwarfksyms.c | 188 + scripts/gendwarfksyms/gendwarfksyms.h | 290 + scripts/gendwarfksyms/kabi.c | 373 + scripts/gendwarfksyms/symbols.c | 344 + scripts/gendwarfksyms/types.c | 589 ++ scripts/generate_builtin_ranges.awk | 513 ++ scripts/generate_initcall_order.pl | 270 + scripts/generate_rust_analyzer.py | 408 + scripts/generate_rust_target.rs | 288 + scripts/genksyms/.gitignore | 2 + scripts/genksyms/Makefile | 12 + scripts/genksyms/genksyms.c | 850 +++ scripts/genksyms/genksyms.h | 88 + scripts/genksyms/keywords.c | 85 + scripts/genksyms/lex.l | 449 ++ scripts/genksyms/parse.y | 539 ++ scripts/get_dvb_firmware | 929 +++ scripts/get_maintainer.pl | 2673 +++++++ scripts/gfp-translate | 118 + scripts/git-resolve.sh | 201 + scripts/git.orderFile | 42 + scripts/head-object-list.txt | 41 + scripts/headerdep.pl | 193 + scripts/headers_install.sh | 75 + scripts/include/array_size.h | 11 + scripts/include/hash.h | 28 + scripts/include/hashtable.h | 98 + scripts/include/list.h | 428 ++ scripts/include/list_types.h | 17 + scripts/include/xalloc.h | 53 + scripts/insert-sys-cert.c | 410 + scripts/install.sh | 44 + scripts/integer-wrap-ignore.scl | 3 + scripts/ipe/Makefile | 2 + scripts/ipe/polgen/.gitignore | 2 + scripts/ipe/polgen/Makefile | 5 + scripts/ipe/polgen/polgen.c | 137 + scripts/jobserver-exec | 35 + scripts/kallsyms.c | 716 ++ scripts/kconfig/.gitignore | 7 + scripts/kconfig/Makefile | 236 + scripts/kconfig/conf.c | 884 +++ scripts/kconfig/confdata.c | 1241 +++ scripts/kconfig/expr.c | 1213 +++ scripts/kconfig/expr.h | 329 + scripts/kconfig/gconf-cfg.sh | 28 + scripts/kconfig/gconf.c | 1356 ++++ scripts/kconfig/gconf.ui | 536 ++ scripts/kconfig/icons/back.xpm | 29 + scripts/kconfig/icons/choice_no.xpm | 18 + scripts/kconfig/icons/choice_yes.xpm | 18 + scripts/kconfig/icons/load.xpm | 31 + scripts/kconfig/icons/menu.xpm | 18 + scripts/kconfig/icons/menuback.xpm | 18 + scripts/kconfig/icons/save.xpm | 31 + scripts/kconfig/icons/single_view.xpm | 28 + scripts/kconfig/icons/split_view.xpm | 28 + scripts/kconfig/icons/symbol_mod.xpm | 18 + scripts/kconfig/icons/symbol_no.xpm | 18 + scripts/kconfig/icons/symbol_yes.xpm | 18 + scripts/kconfig/icons/tree_view.xpm | 28 + scripts/kconfig/internal.h | 27 + scripts/kconfig/kconfig-sym-check.pl | 132 + scripts/kconfig/lexer.l | 461 ++ scripts/kconfig/lkc.h | 139 + scripts/kconfig/lkc_proto.h | 48 + scripts/kconfig/lxdialog/BIG.FAT.WARNING | 4 + scripts/kconfig/lxdialog/checklist.c | 318 + scripts/kconfig/lxdialog/dialog.h | 203 + scripts/kconfig/lxdialog/inputbox.c | 291 + scripts/kconfig/lxdialog/menubox.c | 416 + scripts/kconfig/lxdialog/textbox.c | 358 + scripts/kconfig/lxdialog/util.c | 672 ++ scripts/kconfig/lxdialog/yesno.c | 101 + scripts/kconfig/mconf-cfg.sh | 57 + scripts/kconfig/mconf.c | 966 +++ scripts/kconfig/menu.c | 884 +++ scripts/kconfig/merge_config.sh | 383 + scripts/kconfig/mnconf-common.c | 53 + scripts/kconfig/mnconf-common.h | 20 + scripts/kconfig/nconf-cfg.sh | 56 + scripts/kconfig/nconf.c | 1560 ++++ scripts/kconfig/nconf.gui.c | 650 ++ scripts/kconfig/nconf.h | 88 + scripts/kconfig/parser.y | 797 ++ scripts/kconfig/preprocess.c | 581 ++ scripts/kconfig/preprocess.h | 19 + scripts/kconfig/qconf-cfg.sh | 42 + scripts/kconfig/qconf.cc | 1862 +++++ scripts/kconfig/qconf.h | 273 + scripts/kconfig/streamline_config.pl | 716 ++ scripts/kconfig/symbol.c | 1366 ++++ scripts/kconfig/tests/auto_submenu/Kconfig | 52 + scripts/kconfig/tests/auto_submenu/__init__.py | 13 + scripts/kconfig/tests/auto_submenu/expected_stdout | 10 + scripts/kconfig/tests/choice/Kconfig | 13 + scripts/kconfig/tests/choice/__init__.py | 29 + .../kconfig/tests/choice/alldef_expected_config | 2 + .../kconfig/tests/choice/allmod_expected_config | 2 + scripts/kconfig/tests/choice/allno_expected_config | 2 + .../kconfig/tests/choice/allyes_expected_config | 2 + .../kconfig/tests/choice/oldask0_expected_stdout | 4 + scripts/kconfig/tests/choice_randomize/Kconfig | 22 + scripts/kconfig/tests/choice_randomize/__init__.py | 34 + .../tests/choice_randomize/expected_config0 | 6 + .../tests/choice_randomize/expected_config1 | 8 + .../tests/choice_randomize/expected_config2 | 8 + scripts/kconfig/tests/choice_randomize2/Kconfig | 32 + .../kconfig/tests/choice_randomize2/__init__.py | 18 + .../tests/choice_randomize2/expected_config0 | 8 + .../tests/choice_randomize2/expected_config1 | 7 + .../tests/choice_randomize2/expected_config2 | 6 + scripts/kconfig/tests/conditional_dep/Kconfig | 32 + scripts/kconfig/tests/conditional_dep/__init__.py | 14 + .../kconfig/tests/conditional_dep/expected_config1 | 11 + .../kconfig/tests/conditional_dep/expected_config2 | 9 + .../kconfig/tests/conditional_dep/expected_config3 | 11 + scripts/kconfig/tests/conditional_dep/test_config1 | 6 + scripts/kconfig/tests/conditional_dep/test_config2 | 7 + scripts/kconfig/tests/conditional_dep/test_config3 | 6 + scripts/kconfig/tests/conftest.py | 318 + scripts/kconfig/tests/err_recursive_dep/Kconfig | 63 + .../kconfig/tests/err_recursive_dep/__init__.py | 10 + .../tests/err_recursive_dep/expected_stderr | 38 + scripts/kconfig/tests/err_recursive_inc/Kconfig | 2 + .../kconfig/tests/err_recursive_inc/Kconfig.inc1 | 5 + .../kconfig/tests/err_recursive_inc/Kconfig.inc2 | 4 + .../kconfig/tests/err_recursive_inc/Kconfig.inc3 | 2 + .../kconfig/tests/err_recursive_inc/__init__.py | 11 + .../tests/err_recursive_inc/expected_stderr | 6 + scripts/kconfig/tests/err_repeated_inc/Kconfig | 3 + .../kconfig/tests/err_repeated_inc/Kconfig.inc1 | 4 + .../kconfig/tests/err_repeated_inc/Kconfig.inc2 | 3 + .../kconfig/tests/err_repeated_inc/Kconfig.inc3 | 1 + scripts/kconfig/tests/err_repeated_inc/__init__.py | 10 + .../kconfig/tests/err_repeated_inc/expected_stderr | 2 + scripts/kconfig/tests/err_transitional/Kconfig | 52 + scripts/kconfig/tests/err_transitional/__init__.py | 14 + .../kconfig/tests/err_transitional/expected_stderr | 7 + scripts/kconfig/tests/new_choice_with_dep/Kconfig | 39 + .../kconfig/tests/new_choice_with_dep/__init__.py | 15 + scripts/kconfig/tests/new_choice_with_dep/config | 3 + .../tests/new_choice_with_dep/expected_stdout | 10 + .../kconfig/tests/no_write_if_dep_unmet/Kconfig | 16 + .../tests/no_write_if_dep_unmet/__init__.py | 20 + scripts/kconfig/tests/no_write_if_dep_unmet/config | 1 + .../tests/no_write_if_dep_unmet/expected_config | 5 + .../kconfig/tests/preprocess/builtin_func/Kconfig | 27 + .../tests/preprocess/builtin_func/__init__.py | 9 + .../tests/preprocess/builtin_func/expected_stderr | 5 + .../tests/preprocess/builtin_func/expected_stdout | 1 + .../tests/preprocess/circular_expansion/Kconfig | 5 + .../preprocess/circular_expansion/__init__.py | 11 + .../preprocess/circular_expansion/expected_stderr | 1 + scripts/kconfig/tests/preprocess/escape/Kconfig | 44 + .../kconfig/tests/preprocess/escape/__init__.py | 8 + .../tests/preprocess/escape/expected_stderr | 10 + scripts/kconfig/tests/preprocess/variable/Kconfig | 53 + .../kconfig/tests/preprocess/variable/__init__.py | 8 + .../tests/preprocess/variable/expected_stderr | 9 + scripts/kconfig/tests/pytest.ini | 7 + scripts/kconfig/tests/transitional/Kconfig | 132 + scripts/kconfig/tests/transitional/__init__.py | 25 + scripts/kconfig/tests/transitional/expected_config | 15 + scripts/kconfig/tests/transitional/expected_stdout | 1 + scripts/kconfig/tests/transitional/initial_config | 20 + scripts/kconfig/tests/warn_changed_input/Kconfig | 40 + .../kconfig/tests/warn_changed_input/__init__.py | 33 + scripts/kconfig/tests/warn_changed_input/config | 3 + .../tests/warn_changed_input/expected_config | 6 + .../tests/warn_changed_input/expected_defconfig | 1 + .../tests/warn_changed_input/expected_stderr | 4 + scripts/kconfig/util.c | 121 + scripts/kernel-doc | 1 + scripts/ksymoops/README | 7 + scripts/ld-version.sh | 78 + scripts/leaking_addresses.pl | 706 ++ scripts/link-vmlinux.sh | 315 + scripts/livepatch/Makefile | 20 + scripts/livepatch/fix-patch-lines | 79 + scripts/livepatch/init.c | 106 + scripts/livepatch/klp-build | 943 +++ scripts/macro_checker.py | 131 + scripts/make_fit.py | 444 ++ scripts/makelst | 32 + scripts/markup_oops.pl | 366 + scripts/min-tool-version.sh | 49 + scripts/misc-check | 76 + scripts/mkcompile_h | 27 + scripts/mksysmap | 94 + scripts/mkuboot.sh | 20 + scripts/mod/.gitignore | 5 + scripts/mod/Makefile | 27 + scripts/mod/devicetable-offsets.c | 288 + scripts/mod/empty.c | 1 + scripts/mod/file2alias.c | 1610 ++++ scripts/mod/mk_elfconfig.c | 32 + scripts/mod/modpost.c | 2420 ++++++ scripts/mod/modpost.h | 244 + scripts/mod/sumversion.c | 413 + scripts/mod/symsearch.c | 199 + scripts/module-common.c | 25 + scripts/module.lds.S | 63 + scripts/modules-check.sh | 26 + scripts/nsdeps | 54 + scripts/objdiff | 162 + scripts/objdump-func | 45 + scripts/orc_hash.sh | 16 + scripts/package/PKGBUILD | 134 + scripts/package/builddeb | 176 + scripts/package/buildtar | 110 + scripts/package/debian/copyright | 16 + scripts/package/debian/rules | 97 + scripts/package/gen-diff-patch | 38 + scripts/package/install-extmod-build | 75 + scripts/package/kernel.spec | 226 + scripts/package/mkdebian | 273 + scripts/package/mkspec | 97 + scripts/package/snapcraft.template | 14 + scripts/pahole-version.sh | 13 + scripts/parse-maintainers.pl | 194 + scripts/patch-kernel | 332 + scripts/profile2linkerlist.pl | 20 + scripts/prune-kernel | 25 + scripts/recordmcount.c | 713 ++ scripts/recordmcount.h | 697 ++ scripts/recordmcount.pl | 596 ++ scripts/relocs_check.sh | 20 + scripts/remove-stale-files | 28 + scripts/rust_is_available.sh | 259 + scripts/rust_is_available_bindgen_libclang.h | 2 + scripts/rust_is_available_bindgen_libclang_22.h | 5 + scripts/rust_is_available_test.py | 381 + scripts/rustc-llvm-version.sh | 22 + scripts/rustc-version.sh | 26 + scripts/rustdoc_test_builder.rs | 78 + scripts/rustdoc_test_gen.rs | 291 + scripts/sbom/sbom.py | 135 + scripts/sbom/sbom/__init__.py | 0 scripts/sbom/sbom/cmd_graph/__init__.py | 7 + scripts/sbom/sbom/cmd_graph/cmd_file.py | 162 + scripts/sbom/sbom/cmd_graph/cmd_graph.py | 46 + scripts/sbom/sbom/cmd_graph/cmd_graph_node.py | 142 + scripts/sbom/sbom/cmd_graph/deps_parser.py | 52 + .../sbom/sbom/cmd_graph/hardcoded_dependencies.py | 87 + scripts/sbom/sbom/cmd_graph/incbin_parser.py | 42 + .../sbom/cmd_graph/savedcmd_parser/__init__.py | 6 + .../savedcmd_parser/command_parser_registry.py | 516 ++ .../cmd_graph/savedcmd_parser/command_splitter.py | 128 + .../cmd_graph/savedcmd_parser/savedcmd_parser.py | 67 + .../sbom/cmd_graph/savedcmd_parser/tokenizer.py | 92 + scripts/sbom/sbom/config.py | 320 + scripts/sbom/sbom/environment.py | 192 + scripts/sbom/sbom/path_utils.py | 22 + scripts/sbom/sbom/sbom_logging.py | 94 + scripts/sbom/sbom/spdx/__init__.py | 7 + scripts/sbom/sbom/spdx/build.py | 17 + scripts/sbom/sbom/spdx/core.py | 170 + scripts/sbom/sbom/spdx/serialization.py | 62 + scripts/sbom/sbom/spdx/simplelicensing.py | 20 + scripts/sbom/sbom/spdx/software.py | 69 + scripts/sbom/sbom/spdx/spdxId.py | 36 + scripts/sbom/sbom/spdx_graph/__init__.py | 7 + scripts/sbom/sbom/spdx_graph/build_spdx_graphs.py | 83 + scripts/sbom/sbom/spdx_graph/kernel_file.py | 315 + .../sbom/sbom/spdx_graph/shared_spdx_elements.py | 32 + scripts/sbom/sbom/spdx_graph/spdx_build_graph.py | 318 + scripts/sbom/sbom/spdx_graph/spdx_graph_model.py | 36 + scripts/sbom/sbom/spdx_graph/spdx_output_graph.py | 187 + scripts/sbom/sbom/spdx_graph/spdx_source_graph.py | 130 + scripts/sbom/tests/__init__.py | 0 scripts/sbom/tests/cmd_graph/__init__.py | 0 .../sbom/tests/cmd_graph/test_savedcmd_parser.py | 468 ++ scripts/sbom/tests/spdx_graph/__init__.py | 0 scripts/sbom/tests/spdx_graph/test_kernel_file.py | 35 + scripts/selinux/Makefile | 2 + scripts/selinux/README | 2 + scripts/selinux/install_policy.sh | 81 + scripts/selinux/mdp/.gitignore | 2 + scripts/selinux/mdp/Makefile | 7 + scripts/selinux/mdp/dbus_contexts | 6 + scripts/selinux/mdp/mdp.c | 266 + scripts/setlocalversion | 212 + scripts/show_delta | 129 + scripts/sign-file.c | 358 + scripts/sorttable.c | 1010 +++ scripts/spdxcheck-test.sh | 10 + scripts/spdxcheck.py | 456 ++ scripts/spdxexclude | 18 + scripts/spelling.txt | 1804 +++++ scripts/ssl-common.h | 32 + scripts/stackdelta | 60 + scripts/stackusage | 34 + scripts/subarch.include | 13 + scripts/syscall.tbl | 415 + scripts/syscallhdr.sh | 98 + scripts/syscallnr.sh | 74 + scripts/syscalltbl.sh | 92 + scripts/tags.sh | 384 + scripts/timer_migration_tree.py | 122 + scripts/tools-support-relr.sh | 21 + scripts/tracepoint-update.c | 266 + scripts/tracing/ftrace-bisect.sh | 134 + scripts/unifdef.c | 1225 +++ scripts/update-intel-ucode-defs.py | 130 + scripts/ver_linux | 97 + scripts/verify_builtin_ranges.awk | 370 + scripts/xen-hypercalls.sh | 13 + scripts/xz_wrap.sh | 162 + 716 files changed, 125790 insertions(+) create mode 100644 scripts/.gitignore create mode 100644 scripts/Kbuild.include create mode 100644 scripts/Kconfig.include create mode 100755 scripts/Lindent create mode 100644 scripts/Makefile create mode 100644 scripts/Makefile.asm-headers create mode 100644 scripts/Makefile.autofdo create mode 100644 scripts/Makefile.btf create mode 100644 scripts/Makefile.build create mode 100644 scripts/Makefile.clang create mode 100644 scripts/Makefile.clean create mode 100644 scripts/Makefile.compiler create mode 100644 scripts/Makefile.context-analysis create mode 100644 scripts/Makefile.debug create mode 100644 scripts/Makefile.defconf create mode 100644 scripts/Makefile.dtbinst create mode 100644 scripts/Makefile.dtbs create mode 100644 scripts/Makefile.gcc-plugins create mode 100644 scripts/Makefile.headersinst create mode 100644 scripts/Makefile.host create mode 100644 scripts/Makefile.kasan create mode 100644 scripts/Makefile.kcov create mode 100644 scripts/Makefile.kcsan create mode 100644 scripts/Makefile.kmsan create mode 100644 scripts/Makefile.kstack_erase create mode 100644 scripts/Makefile.lib create mode 100644 scripts/Makefile.modfinal create mode 100644 scripts/Makefile.modinst create mode 100644 scripts/Makefile.modpost create mode 100644 scripts/Makefile.package create mode 100644 scripts/Makefile.propeller create mode 100644 scripts/Makefile.randstruct create mode 100644 scripts/Makefile.thinlto create mode 100644 scripts/Makefile.ubsan create mode 100644 scripts/Makefile.userprogs create mode 100644 scripts/Makefile.vdsoinst create mode 100644 scripts/Makefile.vmlinux create mode 100644 scripts/Makefile.vmlinux_a create mode 100644 scripts/Makefile.vmlinux_o create mode 100644 scripts/Makefile.warn create mode 100755 scripts/as-version.sh create mode 100644 scripts/asn1_compiler.c create mode 100755 scripts/atomic/atomic-tbl.sh create mode 100644 scripts/atomic/atomics.tbl create mode 100755 scripts/atomic/fallbacks/acquire create mode 100755 scripts/atomic/fallbacks/add_negative create mode 100755 scripts/atomic/fallbacks/add_unless create mode 100755 scripts/atomic/fallbacks/andnot create mode 100644 scripts/atomic/fallbacks/cmpxchg create mode 100755 scripts/atomic/fallbacks/dec create mode 100755 scripts/atomic/fallbacks/dec_and_test create mode 100755 scripts/atomic/fallbacks/dec_if_positive create mode 100755 scripts/atomic/fallbacks/dec_unless_positive create mode 100755 scripts/atomic/fallbacks/fence create mode 100755 scripts/atomic/fallbacks/fetch_add_unless create mode 100755 scripts/atomic/fallbacks/inc create mode 100755 scripts/atomic/fallbacks/inc_and_test create mode 100755 scripts/atomic/fallbacks/inc_not_zero create mode 100755 scripts/atomic/fallbacks/inc_unless_negative create mode 100755 scripts/atomic/fallbacks/read_acquire create mode 100755 scripts/atomic/fallbacks/release create mode 100755 scripts/atomic/fallbacks/set_release create mode 100755 scripts/atomic/fallbacks/sub_and_test create mode 100755 scripts/atomic/fallbacks/try_cmpxchg create mode 100644 scripts/atomic/fallbacks/xchg create mode 100755 scripts/atomic/gen-atomic-fallback.sh create mode 100755 scripts/atomic/gen-atomic-instrumented.sh create mode 100755 scripts/atomic/gen-atomic-long.sh create mode 100755 scripts/atomic/gen-atomics.sh create mode 100755 scripts/atomic/gen-rust-atomic-helpers.sh create mode 100644 scripts/atomic/kerneldoc/add create mode 100644 scripts/atomic/kerneldoc/add_negative create mode 100644 scripts/atomic/kerneldoc/add_unless create mode 100644 scripts/atomic/kerneldoc/and create mode 100644 scripts/atomic/kerneldoc/andnot create mode 100644 scripts/atomic/kerneldoc/cmpxchg create mode 100644 scripts/atomic/kerneldoc/dec create mode 100644 scripts/atomic/kerneldoc/dec_and_test create mode 100644 scripts/atomic/kerneldoc/dec_if_positive create mode 100644 scripts/atomic/kerneldoc/dec_unless_positive create mode 100644 scripts/atomic/kerneldoc/inc create mode 100644 scripts/atomic/kerneldoc/inc_and_test create mode 100644 scripts/atomic/kerneldoc/inc_not_zero create mode 100644 scripts/atomic/kerneldoc/inc_unless_negative create mode 100644 scripts/atomic/kerneldoc/or create mode 100644 scripts/atomic/kerneldoc/read create mode 100644 scripts/atomic/kerneldoc/set create mode 100644 scripts/atomic/kerneldoc/sub create mode 100644 scripts/atomic/kerneldoc/sub_and_test create mode 100644 scripts/atomic/kerneldoc/try_cmpxchg create mode 100644 scripts/atomic/kerneldoc/xchg create mode 100644 scripts/atomic/kerneldoc/xor create mode 100644 scripts/bash-completion/make create mode 100644 scripts/basic/.gitignore create mode 100644 scripts/basic/Makefile create mode 100644 scripts/basic/fixdep.c create mode 100755 scripts/bloat-o-meter create mode 100755 scripts/bootgraph.pl create mode 100755 scripts/bpf_doc.py create mode 100755 scripts/build-version create mode 100755 scripts/cc-can-link.sh create mode 100755 scripts/cc-version.sh create mode 100755 scripts/check-function-names.sh create mode 100755 scripts/check-git create mode 100755 scripts/check-sysctl-docs create mode 100755 scripts/check-uapi.sh create mode 100755 scripts/check_extable.sh create mode 100755 scripts/checkdeclares.pl create mode 100755 scripts/checker-valid.sh create mode 100755 scripts/checkincludes.pl create mode 100755 scripts/checkkconfigsymbols.py create mode 100755 scripts/checkpatch.pl create mode 100755 scripts/checkstack.pl create mode 100755 scripts/checksyscalls.sh create mode 100755 scripts/checkversion.pl create mode 100755 scripts/clang-tools/gen_compile_commands.py create mode 100755 scripts/clang-tools/run-clang-tools.py create mode 100755 scripts/cleanfile create mode 100755 scripts/cleanpatch create mode 100755 scripts/coccicheck create mode 100644 scripts/coccinelle/api/alloc/alloc_cast.cocci create mode 100644 scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci create mode 100644 scripts/coccinelle/api/alloc/zalloc-simple.cocci create mode 100644 scripts/coccinelle/api/atomic_as_refcounter.cocci create mode 100644 scripts/coccinelle/api/check_bq27xxx_data.cocci create mode 100644 scripts/coccinelle/api/d_find_alias.cocci create mode 100644 scripts/coccinelle/api/device_attr_show.cocci create mode 100644 scripts/coccinelle/api/err_cast.cocci create mode 100644 scripts/coccinelle/api/kfree_mismatch.cocci create mode 100644 scripts/coccinelle/api/kfree_sensitive.cocci create mode 100644 scripts/coccinelle/api/kmalloc_objs.cocci create mode 100644 scripts/coccinelle/api/kstrdup.cocci create mode 100644 scripts/coccinelle/api/kvmalloc.cocci create mode 100644 scripts/coccinelle/api/memdup.cocci create mode 100644 scripts/coccinelle/api/memdup_user.cocci create mode 100644 scripts/coccinelle/api/platform_get_irq.cocci create mode 100644 scripts/coccinelle/api/platform_no_drv_owner.cocci create mode 100644 scripts/coccinelle/api/pm_runtime.cocci create mode 100644 scripts/coccinelle/api/resource_size.cocci create mode 100644 scripts/coccinelle/api/simple_open.cocci create mode 100644 scripts/coccinelle/api/stream_open.cocci create mode 100644 scripts/coccinelle/api/string_choices.cocci create mode 100644 scripts/coccinelle/api/vma_pages.cocci create mode 100644 scripts/coccinelle/free/clk_put.cocci create mode 100644 scripts/coccinelle/free/devm_free.cocci create mode 100644 scripts/coccinelle/free/ifnulldev_put.cocci create mode 100644 scripts/coccinelle/free/ifnullfree.cocci create mode 100644 scripts/coccinelle/free/iounmap.cocci create mode 100644 scripts/coccinelle/free/kfree.cocci create mode 100644 scripts/coccinelle/free/kfreeaddr.cocci create mode 100644 scripts/coccinelle/free/put_device.cocci create mode 100644 scripts/coccinelle/hid/ff_race.cocci create mode 100644 scripts/coccinelle/iterators/device_node_continue.cocci create mode 100644 scripts/coccinelle/iterators/for_each_child.cocci create mode 100644 scripts/coccinelle/iterators/itnull.cocci create mode 100644 scripts/coccinelle/iterators/list_entry_update.cocci create mode 100644 scripts/coccinelle/iterators/use_after_iter.cocci create mode 100644 scripts/coccinelle/locks/call_kern.cocci create mode 100644 scripts/coccinelle/locks/double_lock.cocci create mode 100644 scripts/coccinelle/locks/flags.cocci create mode 100644 scripts/coccinelle/locks/mini_lock.cocci create mode 100644 scripts/coccinelle/misc/add_namespace.cocci create mode 100644 scripts/coccinelle/misc/array_size.cocci create mode 100644 scripts/coccinelle/misc/array_size_dup.cocci create mode 100644 scripts/coccinelle/misc/badty.cocci create mode 100644 scripts/coccinelle/misc/boolconv.cocci create mode 100644 scripts/coccinelle/misc/cond_no_effect.cocci create mode 100644 scripts/coccinelle/misc/cstptr.cocci create mode 100644 scripts/coccinelle/misc/do_div.cocci create mode 100644 scripts/coccinelle/misc/doubleinit.cocci create mode 100644 scripts/coccinelle/misc/excluded_middle.cocci create mode 100644 scripts/coccinelle/misc/flexible_array.cocci create mode 100644 scripts/coccinelle/misc/ifcol.cocci create mode 100644 scripts/coccinelle/misc/irqf_oneshot.cocci create mode 100644 scripts/coccinelle/misc/minmax.cocci create mode 100644 scripts/coccinelle/misc/newline_in_nl_msg.cocci create mode 100644 scripts/coccinelle/misc/noderef.cocci create mode 100644 scripts/coccinelle/misc/of_table.cocci create mode 100644 scripts/coccinelle/misc/orplus.cocci create mode 100644 scripts/coccinelle/misc/ptr_err_to_pe.cocci create mode 100644 scripts/coccinelle/misc/returnvar.cocci create mode 100644 scripts/coccinelle/misc/secs_to_jiffies.cocci create mode 100644 scripts/coccinelle/misc/semicolon.cocci create mode 100644 scripts/coccinelle/misc/struct_size.cocci create mode 100644 scripts/coccinelle/misc/swap.cocci create mode 100644 scripts/coccinelle/misc/test_addr.cocci create mode 100644 scripts/coccinelle/misc/uninitialized_var.cocci create mode 100644 scripts/coccinelle/misc/warn.cocci create mode 100644 scripts/coccinelle/null/badzero.cocci create mode 100644 scripts/coccinelle/null/deref_null.cocci create mode 100644 scripts/coccinelle/null/eno.cocci create mode 100644 scripts/coccinelle/null/kmerr.cocci create mode 100644 scripts/coccinelle/tests/doublebitand.cocci create mode 100644 scripts/coccinelle/tests/doubletest.cocci create mode 100644 scripts/coccinelle/tests/odd_ptr_err.cocci create mode 100644 scripts/coccinelle/tests/unsigned_lesser_than_zero.cocci create mode 100755 scripts/config create mode 100644 scripts/const_structs.checkpatch create mode 100755 scripts/container create mode 100644 scripts/context-analysis-suppression.txt create mode 100755 scripts/crypto/gen-aead-testvecs.py create mode 100755 scripts/crypto/gen-fips-testvecs.py create mode 100755 scripts/crypto/gen-hash-testvecs.py create mode 100755 scripts/decode_stacktrace.sh create mode 100755 scripts/decodecode create mode 100755 scripts/depmod.sh create mode 100755 scripts/dev-needs.sh create mode 100755 scripts/diffconfig create mode 100644 scripts/dtc/.gitignore create mode 100644 scripts/dtc/Makefile create mode 100644 scripts/dtc/checks.c create mode 100644 scripts/dtc/data.c create mode 100755 scripts/dtc/dt-check-style create mode 100755 scripts/dtc/dt-extract-compatibles create mode 100644 scripts/dtc/dt-style-selftest/bad/dts-child-name-order.dts create mode 100644 scripts/dtc/dt-style-selftest/bad/dts-extend-node-child-name-order.dts create mode 100644 scripts/dtc/dt-style-selftest/bad/dts-extend-node-digit-node-order.dts create mode 100644 scripts/dtc/dt-style-selftest/bad/dts-mixed-indent.dts create mode 100644 scripts/dtc/dt-style-selftest/bad/dts-property-order.dts create mode 100644 scripts/dtc/dt-style-selftest/bad/dts-spaces.dts create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-child-addr-order.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-child-name-order.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-cont-align.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-digit-node-order.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-hex-case.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-indent-strict.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-label-in-string.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-line-length.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-mixed-indent.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-multi-close.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-node-close.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-prop-order-device-type.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-prop-order.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-prop-pairing.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-required-blank.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-trailing-comment.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-trailing-ws.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-unclosed-comment.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-unit-addr-prefix.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-unit-addr.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-unused-label.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-value-ws-multiline.yaml create mode 100644 scripts/dtc/dt-style-selftest/bad/yaml-value-ws.yaml create mode 100644 scripts/dtc/dt-style-selftest/expected/dts-child-name-order.dts.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/dts-extend-node-child-name-order.dts.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/dts-extend-node-digit-node-order.dts.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/dts-mixed-indent.dts.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/dts-property-order.dts.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/dts-spaces.dts.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-child-addr-order.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-child-name-order.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-cont-align.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-digit-node-order.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-hex-case.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-indent-strict.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-label-in-string.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-line-length.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-mixed-indent.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-multi-close.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-node-close.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-prop-order-device-type.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-prop-order.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-prop-pairing.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-required-blank.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-tab.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-trailing-comment.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-trailing-ws.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-unclosed-comment.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-unit-addr-prefix.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-unit-addr.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-unused-label.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-value-ws-multiline.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/expected/yaml-value-ws.yaml.txt create mode 100644 scripts/dtc/dt-style-selftest/good/dts-child-name-order.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-cont-align.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-digit-node-order.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-dtc.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-extend-node-child-name-order.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-extend-node-digit-node-order.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-preprocessor.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-property-order.dts create mode 100644 scripts/dtc/dt-style-selftest/good/dts-tab.dts create mode 100644 scripts/dtc/dt-style-selftest/good/soc.dtsi create mode 100644 scripts/dtc/dt-style-selftest/good/yaml-4space.yaml create mode 100644 scripts/dtc/dt-style-selftest/good/yaml-tricky-parsing.yaml create mode 100755 scripts/dtc/dt-style-selftest/run.sh create mode 100755 scripts/dtc/dt_to_config create mode 100644 scripts/dtc/dtc-lexer.l create mode 100644 scripts/dtc/dtc-parser.y create mode 100644 scripts/dtc/dtc.c create mode 100644 scripts/dtc/dtc.h create mode 100755 scripts/dtc/dtx_diff create mode 100644 scripts/dtc/fdtget.c create mode 100644 scripts/dtc/fdtoverlay.c create mode 100644 scripts/dtc/fdtput.c create mode 100644 scripts/dtc/flattree.c create mode 100644 scripts/dtc/fstree.c create mode 120000 scripts/dtc/include-prefixes/arc create mode 120000 scripts/dtc/include-prefixes/arm create mode 120000 scripts/dtc/include-prefixes/arm64 create mode 120000 scripts/dtc/include-prefixes/dt-bindings create mode 120000 scripts/dtc/include-prefixes/microblaze create mode 120000 scripts/dtc/include-prefixes/mips create mode 120000 scripts/dtc/include-prefixes/nios2 create mode 120000 scripts/dtc/include-prefixes/openrisc create mode 120000 scripts/dtc/include-prefixes/powerpc create mode 120000 scripts/dtc/include-prefixes/riscv create mode 120000 scripts/dtc/include-prefixes/sh create mode 120000 scripts/dtc/include-prefixes/xtensa create mode 100644 scripts/dtc/libfdt/fdt.c create mode 100644 scripts/dtc/libfdt/fdt.h create mode 100644 scripts/dtc/libfdt/fdt_addresses.c create mode 100644 scripts/dtc/libfdt/fdt_empty_tree.c create mode 100644 scripts/dtc/libfdt/fdt_overlay.c create mode 100644 scripts/dtc/libfdt/fdt_ro.c create mode 100644 scripts/dtc/libfdt/fdt_rw.c create mode 100644 scripts/dtc/libfdt/fdt_strerror.c create mode 100644 scripts/dtc/libfdt/fdt_sw.c create mode 100644 scripts/dtc/libfdt/fdt_wip.c create mode 100644 scripts/dtc/libfdt/libfdt.h create mode 100644 scripts/dtc/libfdt/libfdt_env.h create mode 100644 scripts/dtc/libfdt/libfdt_internal.h create mode 100644 scripts/dtc/livetree.c create mode 100755 scripts/dtc/of_unittest_expect create mode 100644 scripts/dtc/srcpos.c create mode 100644 scripts/dtc/srcpos.h create mode 100644 scripts/dtc/treesource.c create mode 100755 scripts/dtc/update-dtc-source.sh create mode 100644 scripts/dtc/util.c create mode 100644 scripts/dtc/util.h create mode 100644 scripts/dtc/version_gen.h create mode 100644 scripts/dtc/yamltree.c create mode 100644 scripts/dummy-tools/dummy-plugin-dir/include/plugin-version.h create mode 100755 scripts/dummy-tools/gcc create mode 100755 scripts/dummy-tools/ld create mode 120000 scripts/dummy-tools/nm create mode 120000 scripts/dummy-tools/objcopy create mode 100755 scripts/dummy-tools/pahole create mode 100755 scripts/dummy-tools/python3 create mode 100644 scripts/elf-parse.c create mode 100644 scripts/elf-parse.h create mode 100755 scripts/extract-fwblobs create mode 100755 scripts/extract-ikconfig create mode 100755 scripts/extract-module-sig.pl create mode 100755 scripts/extract-sys-certs.pl create mode 100755 scripts/extract-vmlinux create mode 100755 scripts/extract_xc3028.pl create mode 100755 scripts/faddr2line create mode 100755 scripts/file-size.sh create mode 100644 scripts/gcc-plugins/.gitignore create mode 100644 scripts/gcc-plugins/Kconfig create mode 100644 scripts/gcc-plugins/Makefile create mode 100644 scripts/gcc-plugins/gcc-common.h create mode 100644 scripts/gcc-plugins/gcc-generate-gimple-pass.h create mode 100644 scripts/gcc-plugins/gcc-generate-ipa-pass.h create mode 100644 scripts/gcc-plugins/gcc-generate-rtl-pass.h create mode 100644 scripts/gcc-plugins/gcc-generate-simple_ipa-pass.h create mode 100644 scripts/gcc-plugins/latent_entropy_plugin.c create mode 100644 scripts/gcc-plugins/randomize_layout_plugin.c create mode 100644 scripts/gcc-plugins/stackleak_plugin.c create mode 100644 scripts/gdb/Makefile create mode 100644 scripts/gdb/linux/.gitignore create mode 100644 scripts/gdb/linux/Makefile create mode 100644 scripts/gdb/linux/__init__.py create mode 100644 scripts/gdb/linux/bpf.py create mode 100644 scripts/gdb/linux/clk.py create mode 100644 scripts/gdb/linux/config.py create mode 100644 scripts/gdb/linux/constants.py.in create mode 100644 scripts/gdb/linux/cpus.py create mode 100644 scripts/gdb/linux/device.py create mode 100644 scripts/gdb/linux/dmesg.py create mode 100644 scripts/gdb/linux/genpd.py create mode 100644 scripts/gdb/linux/interrupts.py create mode 100644 scripts/gdb/linux/kasan.py create mode 100644 scripts/gdb/linux/lists.py create mode 100644 scripts/gdb/linux/mapletree.py create mode 100644 scripts/gdb/linux/mm.py create mode 100644 scripts/gdb/linux/modules.py create mode 100644 scripts/gdb/linux/page_owner.py create mode 100644 scripts/gdb/linux/pgtable.py create mode 100644 scripts/gdb/linux/proc.py create mode 100644 scripts/gdb/linux/radixtree.py create mode 100644 scripts/gdb/linux/rbtree.py create mode 100644 scripts/gdb/linux/slab.py create mode 100644 scripts/gdb/linux/stackdepot.py create mode 100644 scripts/gdb/linux/symbols.py create mode 100644 scripts/gdb/linux/tasks.py create mode 100644 scripts/gdb/linux/timerlist.py create mode 100644 scripts/gdb/linux/utils.py create mode 100644 scripts/gdb/linux/vfs.py create mode 100644 scripts/gdb/linux/vmalloc.py create mode 100644 scripts/gdb/linux/xarray.py create mode 100644 scripts/gdb/vmlinux-gdb.py create mode 100755 scripts/gen-btf.sh create mode 100755 scripts/gen-crc-consts.py create mode 100755 scripts/gen-randstruct-seed.sh create mode 100644 scripts/gen_packed_field_checks.c create mode 100644 scripts/gendwarfksyms/.gitignore create mode 100644 scripts/gendwarfksyms/Makefile create mode 100644 scripts/gendwarfksyms/cache.c create mode 100644 scripts/gendwarfksyms/die.c create mode 100644 scripts/gendwarfksyms/dwarf.c create mode 100644 scripts/gendwarfksyms/examples/kabi.h create mode 100644 scripts/gendwarfksyms/examples/kabi_ex.c create mode 100644 scripts/gendwarfksyms/examples/kabi_ex.h create mode 100644 scripts/gendwarfksyms/examples/symbolptr.c create mode 100644 scripts/gendwarfksyms/gendwarfksyms.c create mode 100644 scripts/gendwarfksyms/gendwarfksyms.h create mode 100644 scripts/gendwarfksyms/kabi.c create mode 100644 scripts/gendwarfksyms/symbols.c create mode 100644 scripts/gendwarfksyms/types.c create mode 100755 scripts/generate_builtin_ranges.awk create mode 100755 scripts/generate_initcall_order.pl create mode 100755 scripts/generate_rust_analyzer.py create mode 100644 scripts/generate_rust_target.rs create mode 100644 scripts/genksyms/.gitignore create mode 100644 scripts/genksyms/Makefile create mode 100644 scripts/genksyms/genksyms.c create mode 100644 scripts/genksyms/genksyms.h create mode 100644 scripts/genksyms/keywords.c create mode 100644 scripts/genksyms/lex.l create mode 100644 scripts/genksyms/parse.y create mode 100755 scripts/get_dvb_firmware create mode 100755 scripts/get_maintainer.pl create mode 100755 scripts/gfp-translate create mode 100755 scripts/git-resolve.sh create mode 100644 scripts/git.orderFile create mode 100644 scripts/head-object-list.txt create mode 100755 scripts/headerdep.pl create mode 100755 scripts/headers_install.sh create mode 100644 scripts/include/array_size.h create mode 100644 scripts/include/hash.h create mode 100644 scripts/include/hashtable.h create mode 100644 scripts/include/list.h create mode 100644 scripts/include/list_types.h create mode 100644 scripts/include/xalloc.h create mode 100644 scripts/insert-sys-cert.c create mode 100755 scripts/install.sh create mode 100644 scripts/integer-wrap-ignore.scl create mode 100644 scripts/ipe/Makefile create mode 100644 scripts/ipe/polgen/.gitignore create mode 100644 scripts/ipe/polgen/Makefile create mode 100644 scripts/ipe/polgen/polgen.c create mode 100755 scripts/jobserver-exec create mode 100644 scripts/kallsyms.c create mode 100644 scripts/kconfig/.gitignore create mode 100644 scripts/kconfig/Makefile create mode 100644 scripts/kconfig/conf.c create mode 100644 scripts/kconfig/confdata.c create mode 100644 scripts/kconfig/expr.c create mode 100644 scripts/kconfig/expr.h create mode 100755 scripts/kconfig/gconf-cfg.sh create mode 100644 scripts/kconfig/gconf.c create mode 100644 scripts/kconfig/gconf.ui create mode 100644 scripts/kconfig/icons/back.xpm create mode 100644 scripts/kconfig/icons/choice_no.xpm create mode 100644 scripts/kconfig/icons/choice_yes.xpm create mode 100644 scripts/kconfig/icons/load.xpm create mode 100644 scripts/kconfig/icons/menu.xpm create mode 100644 scripts/kconfig/icons/menuback.xpm create mode 100644 scripts/kconfig/icons/save.xpm create mode 100644 scripts/kconfig/icons/single_view.xpm create mode 100644 scripts/kconfig/icons/split_view.xpm create mode 100644 scripts/kconfig/icons/symbol_mod.xpm create mode 100644 scripts/kconfig/icons/symbol_no.xpm create mode 100644 scripts/kconfig/icons/symbol_yes.xpm create mode 100644 scripts/kconfig/icons/tree_view.xpm create mode 100644 scripts/kconfig/internal.h create mode 100755 scripts/kconfig/kconfig-sym-check.pl create mode 100644 scripts/kconfig/lexer.l create mode 100644 scripts/kconfig/lkc.h create mode 100644 scripts/kconfig/lkc_proto.h create mode 100644 scripts/kconfig/lxdialog/BIG.FAT.WARNING create mode 100644 scripts/kconfig/lxdialog/checklist.c create mode 100644 scripts/kconfig/lxdialog/dialog.h create mode 100644 scripts/kconfig/lxdialog/inputbox.c create mode 100644 scripts/kconfig/lxdialog/menubox.c create mode 100644 scripts/kconfig/lxdialog/textbox.c create mode 100644 scripts/kconfig/lxdialog/util.c create mode 100644 scripts/kconfig/lxdialog/yesno.c create mode 100755 scripts/kconfig/mconf-cfg.sh create mode 100644 scripts/kconfig/mconf.c create mode 100644 scripts/kconfig/menu.c create mode 100755 scripts/kconfig/merge_config.sh create mode 100644 scripts/kconfig/mnconf-common.c create mode 100644 scripts/kconfig/mnconf-common.h create mode 100755 scripts/kconfig/nconf-cfg.sh create mode 100644 scripts/kconfig/nconf.c create mode 100644 scripts/kconfig/nconf.gui.c create mode 100644 scripts/kconfig/nconf.h create mode 100644 scripts/kconfig/parser.y create mode 100644 scripts/kconfig/preprocess.c create mode 100644 scripts/kconfig/preprocess.h create mode 100755 scripts/kconfig/qconf-cfg.sh create mode 100644 scripts/kconfig/qconf.cc create mode 100644 scripts/kconfig/qconf.h create mode 100755 scripts/kconfig/streamline_config.pl create mode 100644 scripts/kconfig/symbol.c create mode 100644 scripts/kconfig/tests/auto_submenu/Kconfig create mode 100644 scripts/kconfig/tests/auto_submenu/__init__.py create mode 100644 scripts/kconfig/tests/auto_submenu/expected_stdout create mode 100644 scripts/kconfig/tests/choice/Kconfig create mode 100644 scripts/kconfig/tests/choice/__init__.py create mode 100644 scripts/kconfig/tests/choice/alldef_expected_config create mode 100644 scripts/kconfig/tests/choice/allmod_expected_config create mode 100644 scripts/kconfig/tests/choice/allno_expected_config create mode 100644 scripts/kconfig/tests/choice/allyes_expected_config create mode 100644 scripts/kconfig/tests/choice/oldask0_expected_stdout create mode 100644 scripts/kconfig/tests/choice_randomize/Kconfig create mode 100644 scripts/kconfig/tests/choice_randomize/__init__.py create mode 100644 scripts/kconfig/tests/choice_randomize/expected_config0 create mode 100644 scripts/kconfig/tests/choice_randomize/expected_config1 create mode 100644 scripts/kconfig/tests/choice_randomize/expected_config2 create mode 100644 scripts/kconfig/tests/choice_randomize2/Kconfig create mode 100644 scripts/kconfig/tests/choice_randomize2/__init__.py create mode 100644 scripts/kconfig/tests/choice_randomize2/expected_config0 create mode 100644 scripts/kconfig/tests/choice_randomize2/expected_config1 create mode 100644 scripts/kconfig/tests/choice_randomize2/expected_config2 create mode 100644 scripts/kconfig/tests/conditional_dep/Kconfig create mode 100644 scripts/kconfig/tests/conditional_dep/__init__.py create mode 100644 scripts/kconfig/tests/conditional_dep/expected_config1 create mode 100644 scripts/kconfig/tests/conditional_dep/expected_config2 create mode 100644 scripts/kconfig/tests/conditional_dep/expected_config3 create mode 100644 scripts/kconfig/tests/conditional_dep/test_config1 create mode 100644 scripts/kconfig/tests/conditional_dep/test_config2 create mode 100644 scripts/kconfig/tests/conditional_dep/test_config3 create mode 100644 scripts/kconfig/tests/conftest.py create mode 100644 scripts/kconfig/tests/err_recursive_dep/Kconfig create mode 100644 scripts/kconfig/tests/err_recursive_dep/__init__.py create mode 100644 scripts/kconfig/tests/err_recursive_dep/expected_stderr create mode 100644 scripts/kconfig/tests/err_recursive_inc/Kconfig create mode 100644 scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1 create mode 100644 scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2 create mode 100644 scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3 create mode 100644 scripts/kconfig/tests/err_recursive_inc/__init__.py create mode 100644 scripts/kconfig/tests/err_recursive_inc/expected_stderr create mode 100644 scripts/kconfig/tests/err_repeated_inc/Kconfig create mode 100644 scripts/kconfig/tests/err_repeated_inc/Kconfig.inc1 create mode 100644 scripts/kconfig/tests/err_repeated_inc/Kconfig.inc2 create mode 100644 scripts/kconfig/tests/err_repeated_inc/Kconfig.inc3 create mode 100644 scripts/kconfig/tests/err_repeated_inc/__init__.py create mode 100644 scripts/kconfig/tests/err_repeated_inc/expected_stderr create mode 100644 scripts/kconfig/tests/err_transitional/Kconfig create mode 100644 scripts/kconfig/tests/err_transitional/__init__.py create mode 100644 scripts/kconfig/tests/err_transitional/expected_stderr create mode 100644 scripts/kconfig/tests/new_choice_with_dep/Kconfig create mode 100644 scripts/kconfig/tests/new_choice_with_dep/__init__.py create mode 100644 scripts/kconfig/tests/new_choice_with_dep/config create mode 100644 scripts/kconfig/tests/new_choice_with_dep/expected_stdout create mode 100644 scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig create mode 100644 scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py create mode 100644 scripts/kconfig/tests/no_write_if_dep_unmet/config create mode 100644 scripts/kconfig/tests/no_write_if_dep_unmet/expected_config create mode 100644 scripts/kconfig/tests/preprocess/builtin_func/Kconfig create mode 100644 scripts/kconfig/tests/preprocess/builtin_func/__init__.py create mode 100644 scripts/kconfig/tests/preprocess/builtin_func/expected_stderr create mode 100644 scripts/kconfig/tests/preprocess/builtin_func/expected_stdout create mode 100644 scripts/kconfig/tests/preprocess/circular_expansion/Kconfig create mode 100644 scripts/kconfig/tests/preprocess/circular_expansion/__init__.py create mode 100644 scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr create mode 100644 scripts/kconfig/tests/preprocess/escape/Kconfig create mode 100644 scripts/kconfig/tests/preprocess/escape/__init__.py create mode 100644 scripts/kconfig/tests/preprocess/escape/expected_stderr create mode 100644 scripts/kconfig/tests/preprocess/variable/Kconfig create mode 100644 scripts/kconfig/tests/preprocess/variable/__init__.py create mode 100644 scripts/kconfig/tests/preprocess/variable/expected_stderr create mode 100644 scripts/kconfig/tests/pytest.ini create mode 100644 scripts/kconfig/tests/transitional/Kconfig create mode 100644 scripts/kconfig/tests/transitional/__init__.py create mode 100644 scripts/kconfig/tests/transitional/expected_config create mode 100644 scripts/kconfig/tests/transitional/expected_stdout create mode 100644 scripts/kconfig/tests/transitional/initial_config create mode 100644 scripts/kconfig/tests/warn_changed_input/Kconfig create mode 100644 scripts/kconfig/tests/warn_changed_input/__init__.py create mode 100644 scripts/kconfig/tests/warn_changed_input/config create mode 100644 scripts/kconfig/tests/warn_changed_input/expected_config create mode 100644 scripts/kconfig/tests/warn_changed_input/expected_defconfig create mode 100644 scripts/kconfig/tests/warn_changed_input/expected_stderr create mode 100644 scripts/kconfig/util.c create mode 120000 scripts/kernel-doc create mode 100644 scripts/ksymoops/README create mode 100755 scripts/ld-version.sh create mode 100755 scripts/leaking_addresses.pl create mode 100755 scripts/link-vmlinux.sh create mode 100644 scripts/livepatch/Makefile create mode 100755 scripts/livepatch/fix-patch-lines create mode 100644 scripts/livepatch/init.c create mode 100755 scripts/livepatch/klp-build create mode 100755 scripts/macro_checker.py create mode 100755 scripts/make_fit.py create mode 100755 scripts/makelst create mode 100755 scripts/markup_oops.pl create mode 100755 scripts/min-tool-version.sh create mode 100755 scripts/misc-check create mode 100755 scripts/mkcompile_h create mode 100755 scripts/mksysmap create mode 100755 scripts/mkuboot.sh create mode 100644 scripts/mod/.gitignore create mode 100644 scripts/mod/Makefile create mode 100644 scripts/mod/devicetable-offsets.c create mode 100644 scripts/mod/empty.c create mode 100644 scripts/mod/file2alias.c create mode 100644 scripts/mod/mk_elfconfig.c create mode 100644 scripts/mod/modpost.c create mode 100644 scripts/mod/modpost.h create mode 100644 scripts/mod/sumversion.c create mode 100644 scripts/mod/symsearch.c create mode 100644 scripts/module-common.c create mode 100644 scripts/module.lds.S create mode 100755 scripts/modules-check.sh create mode 100644 scripts/nsdeps create mode 100755 scripts/objdiff create mode 100755 scripts/objdump-func create mode 100644 scripts/orc_hash.sh create mode 100644 scripts/package/PKGBUILD create mode 100755 scripts/package/builddeb create mode 100755 scripts/package/buildtar create mode 100644 scripts/package/debian/copyright create mode 100755 scripts/package/debian/rules create mode 100755 scripts/package/gen-diff-patch create mode 100755 scripts/package/install-extmod-build create mode 100644 scripts/package/kernel.spec create mode 100755 scripts/package/mkdebian create mode 100755 scripts/package/mkspec create mode 100644 scripts/package/snapcraft.template create mode 100755 scripts/pahole-version.sh create mode 100755 scripts/parse-maintainers.pl create mode 100755 scripts/patch-kernel create mode 100755 scripts/profile2linkerlist.pl create mode 100755 scripts/prune-kernel create mode 100644 scripts/recordmcount.c create mode 100644 scripts/recordmcount.h create mode 100755 scripts/recordmcount.pl create mode 100755 scripts/relocs_check.sh create mode 100755 scripts/remove-stale-files create mode 100755 scripts/rust_is_available.sh create mode 100644 scripts/rust_is_available_bindgen_libclang.h create mode 100644 scripts/rust_is_available_bindgen_libclang_22.h create mode 100755 scripts/rust_is_available_test.py create mode 100755 scripts/rustc-llvm-version.sh create mode 100755 scripts/rustc-version.sh create mode 100644 scripts/rustdoc_test_builder.rs create mode 100644 scripts/rustdoc_test_gen.rs create mode 100644 scripts/sbom/sbom.py create mode 100644 scripts/sbom/sbom/__init__.py create mode 100644 scripts/sbom/sbom/cmd_graph/__init__.py create mode 100644 scripts/sbom/sbom/cmd_graph/cmd_file.py create mode 100644 scripts/sbom/sbom/cmd_graph/cmd_graph.py create mode 100644 scripts/sbom/sbom/cmd_graph/cmd_graph_node.py create mode 100644 scripts/sbom/sbom/cmd_graph/deps_parser.py create mode 100644 scripts/sbom/sbom/cmd_graph/hardcoded_dependencies.py create mode 100644 scripts/sbom/sbom/cmd_graph/incbin_parser.py create mode 100644 scripts/sbom/sbom/cmd_graph/savedcmd_parser/__init__.py create mode 100644 scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_parser_registry.py create mode 100644 scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_splitter.py create mode 100644 scripts/sbom/sbom/cmd_graph/savedcmd_parser/savedcmd_parser.py create mode 100644 scripts/sbom/sbom/cmd_graph/savedcmd_parser/tokenizer.py create mode 100644 scripts/sbom/sbom/config.py create mode 100644 scripts/sbom/sbom/environment.py create mode 100644 scripts/sbom/sbom/path_utils.py create mode 100644 scripts/sbom/sbom/sbom_logging.py create mode 100644 scripts/sbom/sbom/spdx/__init__.py create mode 100644 scripts/sbom/sbom/spdx/build.py create mode 100644 scripts/sbom/sbom/spdx/core.py create mode 100644 scripts/sbom/sbom/spdx/serialization.py create mode 100644 scripts/sbom/sbom/spdx/simplelicensing.py create mode 100644 scripts/sbom/sbom/spdx/software.py create mode 100644 scripts/sbom/sbom/spdx/spdxId.py create mode 100644 scripts/sbom/sbom/spdx_graph/__init__.py create mode 100644 scripts/sbom/sbom/spdx_graph/build_spdx_graphs.py create mode 100644 scripts/sbom/sbom/spdx_graph/kernel_file.py create mode 100644 scripts/sbom/sbom/spdx_graph/shared_spdx_elements.py create mode 100644 scripts/sbom/sbom/spdx_graph/spdx_build_graph.py create mode 100644 scripts/sbom/sbom/spdx_graph/spdx_graph_model.py create mode 100644 scripts/sbom/sbom/spdx_graph/spdx_output_graph.py create mode 100644 scripts/sbom/sbom/spdx_graph/spdx_source_graph.py create mode 100644 scripts/sbom/tests/__init__.py create mode 100644 scripts/sbom/tests/cmd_graph/__init__.py create mode 100644 scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py create mode 100644 scripts/sbom/tests/spdx_graph/__init__.py create mode 100644 scripts/sbom/tests/spdx_graph/test_kernel_file.py create mode 100644 scripts/selinux/Makefile create mode 100644 scripts/selinux/README create mode 100755 scripts/selinux/install_policy.sh create mode 100644 scripts/selinux/mdp/.gitignore create mode 100644 scripts/selinux/mdp/Makefile create mode 100644 scripts/selinux/mdp/dbus_contexts create mode 100644 scripts/selinux/mdp/mdp.c create mode 100755 scripts/setlocalversion create mode 100755 scripts/show_delta create mode 100644 scripts/sign-file.c create mode 100644 scripts/sorttable.c create mode 100644 scripts/spdxcheck-test.sh create mode 100755 scripts/spdxcheck.py create mode 100644 scripts/spdxexclude create mode 100644 scripts/spelling.txt create mode 100644 scripts/ssl-common.h create mode 100755 scripts/stackdelta create mode 100755 scripts/stackusage create mode 100644 scripts/subarch.include create mode 100644 scripts/syscall.tbl create mode 100755 scripts/syscallhdr.sh create mode 100755 scripts/syscallnr.sh create mode 100755 scripts/syscalltbl.sh create mode 100755 scripts/tags.sh create mode 100755 scripts/timer_migration_tree.py create mode 100755 scripts/tools-support-relr.sh create mode 100644 scripts/tracepoint-update.c create mode 100755 scripts/tracing/ftrace-bisect.sh create mode 100644 scripts/unifdef.c create mode 100755 scripts/update-intel-ucode-defs.py create mode 100755 scripts/ver_linux create mode 100755 scripts/verify_builtin_ranges.awk create mode 100755 scripts/xen-hypercalls.sh create mode 100755 scripts/xz_wrap.sh (limited to 'scripts') diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 000000000..4215c2208 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +/asn1_compiler +/gen_packed_field_checks +/generate_rust_target +/insert-sys-cert +/kallsyms +/module.lds +/recordmcount +/rustdoc_test_builder +/rustdoc_test_gen +/sign-file +/sorttable +/target.json +/tracepoint-update +/unifdef diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include new file mode 100644 index 000000000..8c311b997 --- /dev/null +++ b/scripts/Kbuild.include @@ -0,0 +1,272 @@ +# SPDX-License-Identifier: GPL-2.0 +#### +# kbuild: Generic definitions + +# Convenient variables +comma := , +quote := " +squote := ' +empty := +space := $(empty) $(empty) +space_escape := _-_SPACE_-_ +pound := \# +define newline + + +endef + +### +# Comparison macros. +# Usage: $(call test-lt, $(CONFIG_LLD_VERSION), 150000) +# +# Use $(intcmp ...) if supported. (Make >= 4.4) +# Otherwise, fall back to the 'test' shell command. +ifeq ($(intcmp 1,0,,,y),y) +test-ge = $(intcmp $(strip $1)0, $(strip $2)0,,y,y) +test-gt = $(intcmp $(strip $1)0, $(strip $2)0,,,y) +else +test-ge = $(shell test $(strip $1)0 -ge $(strip $2)0 && echo y) +test-gt = $(shell test $(strip $1)0 -gt $(strip $2)0 && echo y) +endif +test-le = $(call test-ge, $2, $1) +test-lt = $(call test-gt, $2, $1) + +### +# Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o +dot-target = $(dir $@).$(notdir $@) + +### +# Name of target with a '.tmp_' as filename prefix. foo/bar.o => foo/.tmp_bar.o +tmp-target = $(dir $@).tmp_$(notdir $@) + +### +# The temporary file to save gcc -MMD generated dependencies must not +# contain a comma +depfile = $(subst $(comma),_,$(dot-target).d) + +### +# filename of target with directory and extension stripped +basetarget = $(basename $(notdir $@)) + +### +# real prerequisites without phony targets +real-prereqs = $(filter-out $(PHONY), $^) + +### +# Escape single quote for use in echo statements +escsq = $(subst $(squote),'\$(squote)',$1) + +### +# Quote a string to pass it to C files. foo => '"foo"' +stringify = $(squote)$(quote)$1$(quote)$(squote) + +### +# The path to Kbuild or Makefile. Kbuild has precedence over 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 4.0 4.1, $(MAKE_VERSION)),) +read-file = $(subst $(newline),$(space),$(file < $1)) +else +read-file = $(shell cat $1 2>/dev/null) +endif + +### +# Easy method for doing a status message + kecho := : + quiet_kecho := echo +silent_kecho := : +kecho := $($(quiet)kecho) + +### +# filechk is used to check if the content of a generated file is updated. +# Sample usage: +# +# filechk_sample = echo $(KERNELRELEASE) +# version.h: FORCE +# $(call filechk,sample) +# +# The rule defined shall write to stdout the content of the new file. +# The existing file will be compared with the new one. +# - If no file exist it is created +# - If the content differ the new file is used +# - If they are equal no change, and no timestamp update +define filechk + $(check-FORCE) + $(Q)set -e; \ + mkdir -p $(dir $@); \ + trap "rm -f $(tmp-target)" EXIT; \ + { $(filechk_$(1)); } > $(tmp-target); \ + if [ ! -r $@ ] || ! cmp -s $@ $(tmp-target); then \ + $(kecho) ' UPD $@'; \ + mv -f $(tmp-target) $@; \ + fi +endef + +### +# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= +# Usage: +# $(Q)$(MAKE) $(build)=dir +build := -f $(srctree)/scripts/Makefile.build obj + +### +# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.clean obj= +# Usage: +# $(Q)$(MAKE) $(clean)=dir +clean := -f $(srctree)/scripts/Makefile.clean obj + +# pring log +# +# If quiet is "silent_", print nothing and sink stdout +# If quiet is "quiet_", print short log +# If quiet is empty, print short log and whole command +silent_log_print = exec >/dev/null; + quiet_log_print = $(if $(quiet_cmd_$1), echo ' $(call escsq,$(quiet_cmd_$1)$(why))';) + log_print = echo '$(pound) $(call escsq,$(or $(quiet_cmd_$1),cmd_$1 $@)$(why))'; \ + echo ' $(call escsq,$(cmd_$1))'; + +# Delete the target on interruption +# +# GNU Make automatically deletes the target if it has already been changed by +# the interrupted recipe. So, you can safely stop the build by Ctrl-C (Make +# will delete incomplete targets), and resume it later. +# +# However, this does not work when the stderr is piped to another program, like +# $ make >&2 | tee log +# Make dies with SIGPIPE before cleaning the targets. +# +# To address it, we clean the target in signal traps. +# +# Make deletes the target when it catches SIGHUP, SIGINT, SIGQUIT, SIGTERM. +# So, we cover them, and also SIGPIPE just in case. +# +# Of course, this is unneeded for phony targets. +delete-on-interrupt = \ + $(if $(filter-out $(PHONY), $@), \ + $(foreach sig, HUP INT QUIT TERM PIPE, \ + trap 'rm -f $@; trap - $(sig); kill -s $(sig) $$$$' $(sig);)) + +# print and execute commands +cmd = @$(if $(cmd_$(1)),set -e; $($(quiet)log_print) $(delete-on-interrupt) $(cmd_$(1)),:) + +### +# if_changed - execute command if any prerequisite is newer than +# target, or command line has changed +# if_changed_dep - as if_changed, but uses fixdep to reveal dependencies +# including used config symbols +# if_changed_rule - as if_changed but execute rule instead +# See Documentation/kbuild/makefiles.rst for more info + +ifneq ($(KBUILD_NOCMDDEP),1) +# Check if both commands are the same including their order. Result is empty +# string if equal. User may override this check using make KBUILD_NOCMDDEP=1 +# If the target does not exist, the *.cmd file should not be included so +# $(savedcmd_$@) gets empty. Then, target will be built even if $(newer-prereqs) +# happens to become empty. +cmd-check = $(filter-out $(subst $(space),$(space_escape),$(strip $(savedcmd_$@))), \ + $(subst $(space),$(space_escape),$(strip $(cmd_$1)))) +else +# We still need to detect missing targets. +cmd-check = $(if $(strip $(savedcmd_$@)),,1) +endif + +# Replace >$< with >$$< to preserve $ when reloading the .cmd file +# (needed for make) +# Replace >#< with >$(pound)< to avoid starting a comment in the .cmd file +# (needed for make) +# Replace >'< with >'\''< to be able to enclose the whole string in '...' +# (needed for the shell) +make-cmd = $(call escsq,$(subst $(pound),$$(pound),$(subst $$,$$$$,$(cmd_$(1))))) + +# Find any prerequisites that are newer than target or that do not exist. +# PHONY targets skipped in both cases. +# If there is no prerequisite other than phony targets, $(newer-prereqs) becomes +# empty even if the target does not exist. cmd-check saves this corner case. +newer-prereqs = $(filter-out $(PHONY),$?) + +# It is a typical mistake to forget the FORCE prerequisite. Check it here so +# no more breakage will slip in. +check-FORCE = $(if $(filter FORCE, $^),,$(warning FORCE prerequisite is missing)) + +if-changed-cond = $(newer-prereqs)$(cmd-check)$(check-FORCE) + +# Execute command if command has changed or prerequisite(s) are updated. +if_changed = $(if $(if-changed-cond),$(cmd_and_savecmd),@:) + +cmd_and_savecmd = \ + $(cmd); \ + printf '%s\n' 'savedcmd_$@ := $(make-cmd)' > $(dot-target).cmd + +# Execute the command and also postprocess generated .d dependencies file. +if_changed_dep = $(if $(if-changed-cond),$(cmd_and_fixdep),@:) + +cmd_and_fixdep = \ + $(cmd); \ + $(objtree)/scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\ + rm -f $(depfile) + +# Usage: $(call if_changed_rule,foo) +# Will check if $(cmd_foo) or any of the prerequisites changed, +# and if so will execute $(rule_foo). +if_changed_rule = $(if $(if-changed-cond),$(rule_$(1)),@:) + +### +# why - tell why a target got built +# enabled by make V=2 +# Output (listed in the order they are checked): +# (1) - due to target is PHONY +# (2) - due to target missing +# (3) - due to: file1.h file2.h +# (4) - due to command line change +# (5) - due to missing .cmd file +# (6) - due to target not in $(targets) +# (1) PHONY targets are always build +# (2) No target, so we better build it +# (3) Prerequisite is newer than target +# (4) The command line stored in the file named dir/.target.cmd +# differed from actual command line. This happens when compiler +# options changes +# (5) No dir/.target.cmd file (used to store command line) +# (6) No dir/.target.cmd file and target not listed in $(targets) +# This is a good hint that there is a bug in the kbuild file +ifneq ($(findstring 2, $(KBUILD_VERBOSE)),) +_why = \ + $(if $(filter $@, $(PHONY)),- due to target is PHONY, \ + $(if $(wildcard $@), \ + $(if $(newer-prereqs),- due to: $(newer-prereqs), \ + $(if $(cmd-check), \ + $(if $(savedcmd_$@),- due to command line change, \ + $(if $(filter $@, $(targets)), \ + - due to missing .cmd file, \ + - due to $(notdir $@) not in $$(targets) \ + ) \ + ) \ + ) \ + ), \ + - due to target missing \ + ) \ + ) + +why = $(space)$(strip $(_why)) +endif + +############################################################################### + +# delete partially updated (i.e. corrupted) files on error +.DELETE_ON_ERROR: + +# do not delete intermediate files automatically +# +# .NOTINTERMEDIATE is more correct, but only available on newer Make versions. +# Make 4.4 introduced .NOTINTERMEDIATE, and it appears in .FEATURES, but the +# global .NOTINTERMEDIATE does not work. We can use it on Make > 4.4. +# Use .SECONDARY for older Make versions, but "newer-prereq" cannot detect +# deleted files. +ifneq ($(and $(filter notintermediate, $(.FEATURES)),$(filter-out 4.4,$(MAKE_VERSION))),) +.NOTINTERMEDIATE: +else +.SECONDARY: +endif diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include new file mode 100644 index 000000000..fc10671c2 --- /dev/null +++ b/scripts/Kconfig.include @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Kconfig helper macros + +# Convenient variables +comma := , +quote := " +squote := ' +empty := +space := $(empty) $(empty) +dollar := $ +right_paren := ) +left_paren := ( + +# $(if-success,,,) +# Return if exits with 0, otherwise. +if-success = $(shell,{ $(1); } >/dev/null 2>&1 && echo "$(2)" || echo "$(3)") + +# $(success,) +# Return y if exits with 0, n otherwise +success = $(if-success,$(1),y,n) + +# $(failure,) +# Return n if exits with 0, y otherwise +failure = $(if-success,$(1),n,y) + +# $(cc-option,) +# Return y if the compiler supports , n otherwise +cc-option = $(success,trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(1) -c -x c /dev/null -o .tmp_$$/tmp.o) + +# $(ld-option,) +# Return y if the linker supports , n otherwise +ld-option = $(success,$(LD) -v $(1)) + +# $(as-instr,) +# Return y if the assembler supports , n otherwise +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) +$(error-if,$(failure,command -v $(LD)),linker '$(LD)' not found) + +# Get the C compiler name, version, and error out if it is not supported. +cc-info := $(shell,$(srctree)/scripts/cc-version.sh $(CC)) +$(error-if,$(success,test -z "$(cc-info)"),Sorry$(comma) this C compiler is not supported.) +cc-name := $(shell,set -- $(cc-info) && echo $1) +cc-version := $(shell,set -- $(cc-info) && echo $2) + +# Get the assembler name, version, and error out if it is not supported. +as-info := $(shell,$(srctree)/scripts/as-version.sh $(CC) $(CLANG_FLAGS)) +$(error-if,$(success,test -z "$(as-info)"),Sorry$(comma) this assembler is not supported.) +as-name := $(shell,set -- $(as-info) && echo $1) +as-version := $(shell,set -- $(as-info) && echo $2) + +# Get the linker name, version, and error out if it is not supported. +ld-info := $(shell,$(srctree)/scripts/ld-version.sh $(LD)) +$(error-if,$(success,test -z "$(ld-info)"),Sorry$(comma) this linker is not supported.) +ld-name := $(shell,set -- $(ld-info) && echo $1) +ld-version := $(shell,set -- $(ld-info) && echo $2) + +# machine bit flags +# $(m32-flag): -m32 if the compiler supports it, or an empty string otherwise. +# $(m64-flag): -m64 if the compiler supports it, or an empty string otherwise. +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) + +# Test whether the compiler can link userspace applications +cc_can_link_user = $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(1)) + +rustc-version := $(shell,$(srctree)/scripts/rustc-version.sh $(RUSTC)) +rustc-llvm-version := $(shell,$(srctree)/scripts/rustc-llvm-version.sh $(RUSTC)) + +# $(rustc-option,) +# Return y if the Rust compiler supports , n otherwise +# 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/Lindent b/scripts/Lindent new file mode 100755 index 000000000..1688c44c2 --- /dev/null +++ b/scripts/Lindent @@ -0,0 +1,26 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +PARAM="-npro -kr -i8 -ts8 -sob -l80 -ss -ncs -cp1" + +RES=`indent --version | cut -d' ' -f3` +if [ "$RES" = "" ]; then + exit 1 +fi +V1=`echo $RES | cut -d'.' -f1` +V2=`echo $RES | cut -d'.' -f2` +V3=`echo $RES | cut -d'.' -f3` + +if [ $V1 -gt 2 ]; then + PARAM="$PARAM -il0" +elif [ $V1 -eq 2 ]; then + if [ $V2 -gt 2 ]; then + PARAM="$PARAM -il0" + elif [ $V2 -eq 2 ]; then + if [ $V3 -ge 10 ]; then + PARAM="$PARAM -il0" + fi + fi +fi + +indent $PARAM "$@" diff --git a/scripts/Makefile b/scripts/Makefile new file mode 100644 index 000000000..3434a82a1 --- /dev/null +++ b/scripts/Makefile @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0 +### +# scripts contains sources for various helper programs used throughout +# the kernel for the build process. + +hostprogs-always-$(CONFIG_KALLSYMS) += kallsyms +hostprogs-always-$(BUILD_C_RECORDMCOUNT) += recordmcount +hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable +hostprogs-always-$(CONFIG_ASN1) += asn1_compiler +hostprogs-always-$(CONFIG_MODULE_SIG_FORMAT) += sign-file +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 +hostprogs-always-$(CONFIG_TRACEPOINTS) += tracepoint-update + +sorttable-objs := sorttable.o elf-parse.o +tracepoint-update-objs := tracepoint-update.o elf-parse.o + +ifneq ($(or $(CONFIG_X86_64),$(CONFIG_X86_32)),) +always-$(CONFIG_RUST) += target.json +filechk_rust_target = $< < include/config/auto.conf + +$(obj)/target.json: scripts/generate_rust_target include/config/auto.conf FORCE + $(call filechk,rust_target) +endif + +hostprogs += generate_rust_target +generate_rust_target-rust := y +rustdoc_test_builder-rust := y +rustdoc_test_gen-rust := y + +HOSTCFLAGS_tracepoint-update.o = -I$(srctree)/tools/include +HOSTCFLAGS_elf-parse.o = -I$(srctree)/tools/include +HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include +HOSTLDLIBS_sorttable = -lpthread +HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include +HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) +HOSTCFLAGS_sign-file.o += -I$(srctree)/tools/include/uapi/ +HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) + +ifdef CONFIG_UNWINDER_ORC +ifeq ($(ARCH),x86_64) +SRCARCH := x86 +endif +ifeq ($(ARCH),loongarch) +SRCARCH := loongarch +endif +HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/$(SRCARCH)/include +HOSTCFLAGS_sorttable.o += -DUNWINDER_ORC_ENABLED +endif + +ifdef CONFIG_BUILDTIME_MCOUNT_SORT +HOSTCFLAGS_sorttable.o += -DMCOUNT_SORT_ENABLED +endif + +# The following programs are only built on demand +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_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-headers b/scripts/Makefile.asm-headers new file mode 100644 index 000000000..f1c3d287c --- /dev/null +++ b/scripts/Makefile.asm-headers @@ -0,0 +1,105 @@ +# 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 := $(shell test -d $(obj) && find $(obj) -name '*.h' ! -name '.*') +unwanted := $(filter-out $(generic-y) $(generated-y) $(syscall-y),$(old-headers)) + +filechk_wrap = echo "\#include " + +quiet_cmd_remove = REMOVE $(unwanted) + cmd_remove = rm -f $(unwanted); find $(obj) -type d -empty -delete + +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 FORCE + $(call filechk,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 000000000..1442043da --- /dev/null +++ b/scripts/Makefile.autofdo @@ -0,0 +1,28 @@ +# 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 +RUSTFLAGS_AUTOFDO_CLANG := $(if $(call rustc-min-version,109800),-Zdebuginfo-for-profiling,-Zdebug-info-for-profiling) -Cllvm-args=-enable-fs-discriminator=true -Cllvm-args=-improved-fs-discriminator=true + +ifndef CONFIG_DEBUG_INFO + CFLAGS_AUTOFDO_CLANG += -gmlt + RUSTFLAGS_AUTOFDO_CLANG += -Cdebuginfo=line-tables-only +endif + +ifdef CLANG_AUTOFDO_PROFILE + CFLAGS_AUTOFDO_CLANG += -fprofile-sample-use=$(CLANG_AUTOFDO_PROFILE) -ffunction-sections + CFLAGS_AUTOFDO_CLANG += -fsplit-machine-functions + RUSTFLAGS_AUTOFDO_CLANG += -Zprofile-sample-use=$(CLANG_AUTOFDO_PROFILE) -Zfunction-sections=y + RUSTFLAGS_AUTOFDO_CLANG += -Cllvm-args=-split-machine-functions +endif + +ifdef CONFIG_LTO_CLANG_THIN + 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 RUSTFLAGS_AUTOFDO_CLANG diff --git a/scripts/Makefile.btf b/scripts/Makefile.btf new file mode 100644 index 000000000..a1812985a --- /dev/null +++ b/scripts/Makefile.btf @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 + +pahole-ver := $(CONFIG_PAHOLE_VERSION) +pahole-flags-y := + +JOBS := $(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS))) + +ifeq ($(call test-le, $(pahole-ver), 125),y) + +pahole-flags-y += --btf_gen_floats -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 + +pahole-flags-$(call test-ge, $(pahole-ver), 131) += --btf_features=layout + +endif + +pahole-flags-$(CONFIG_PAHOLE_HAS_LANG_EXCLUDE) += --lang_exclude=rust + +export PAHOLE_FLAGS := $(pahole-flags-y) + +resolve-btfids-flags-y := +resolve-btfids-flags-$(CONFIG_WERROR) += --fatal_warnings +resolve-btfids-flags-$(if $(KBUILD_EXTMOD),y) += --distill_base +resolve-btfids-flags-$(if $(KBUILD_VERBOSE),y) += --verbose + +export RESOLVE_BTFIDS_FLAGS := $(resolve-btfids-flags-y) diff --git a/scripts/Makefile.build b/scripts/Makefile.build new file mode 100644 index 000000000..4349108e7 --- /dev/null +++ b/scripts/Makefile.build @@ -0,0 +1,598 @@ +# SPDX-License-Identifier: GPL-2.0 +# ========================================================================== +# Building +# ========================================================================== + +src := $(srcroot)/$(obj) + +PHONY := $(obj)/ +$(obj)/: + +# Init all relevant variables used in kbuild files so +# 1) they have correct type +# 2) they do not inherit any value from the environment +obj-y := +obj-m := +lib-y := +lib-m := +always-y := +always-m := +targets := +subdir-y := +subdir-m := +asflags-y := +ccflags-y := +rustflags-y := +cppflags-y := +ldflags-y := + +subdir-asflags-y := +subdir-ccflags-y := +subdir-rustflags-y := + +# Read auto.conf if it exists, otherwise ignore +-include $(objtree)/include/config/auto.conf + +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.compiler +include $(kbuild-file) +include $(srctree)/scripts/Makefile.lib + +# 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 + +# 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 +$(warning kbuild: Makefile.build is included improperly) +endif + +ifeq ($(need-modorder),) +ifneq ($(obj-m),) +$(warning $(patsubst %.o,'%.ko',$(obj-m)) will not be built even though obj-m is specified.) +$(warning You cannot use subdir-y/m to visit a module Makefile. Use obj-y/m instead.) +endif +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))) + +targets-for-builtin := $(extra-y) + +ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),) +targets-for-builtin += $(obj)/lib.a +endif + +ifdef need-builtin +targets-for-builtin += $(obj)/built-in.a +endif + +targets-for-modules := $(foreach x, o mod, \ + $(patsubst %.o, %.$x, $(filter %.o, $(obj-m)))) + +ifdef need-modorder +targets-for-modules += $(obj)/modules.order +endif + +targets += $(targets-for-builtin) $(targets-for-modules) + +# Linus' kernel sanity checking tool +ifeq ($(KBUILD_CHECKSRC),1) + quiet_cmd_checksrc = CHECK $(patsubst $(srctree)/%,%,$<) + cmd_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< +else ifeq ($(KBUILD_CHECKSRC),2) + quiet_cmd_force_checksrc = CHECK $(patsubst $(srctree)/%,%,$<) + cmd_force_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< +endif + +ifeq ($(KBUILD_EXTMOD),) +ifneq ($(KBUILD_EXTRA_WARN),) + cmd_checkdoc = PYTHONDONTWRITEBYTECODE=1 $(PYTHON3) $(KERNELDOC) -none $(KDOCFLAGS) \ + $(if $(findstring 2, $(KBUILD_EXTRA_WARN)), -Wall) \ + $< +endif +endif + +# Compile C sources (.c) +# --------------------------------------------------------------------------- + +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: $(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: $(obj)/%.c FORCE + $(call if_changed_dep,cpp_i_c) + +getexportsymbols = $(NM) $@ | sed -n 's/.* __export_symbol_\(.*\)/$(1)/p' + +gendwarfksyms = $(objtree)/scripts/gendwarfksyms/gendwarfksyms \ + $(if $(KBUILD_SYMTYPES), --symtypes $(@:.o=.symtypes)) \ + $(if $(KBUILD_GENDWARFKSYMS_STABLE), --stable) + +genksyms = $(objtree)/scripts/genksyms/genksyms \ + $(if $(KBUILD_SYMTYPES), -T $(@:.o=.symtypes)) \ + $(if $(KBUILD_PRESERVE), -p) \ + $(addprefix -r , $(wildcard $(@:.o=.symref))) + +# 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: $(obj)/%.c FORCE + $(call if_changed_dep,cc_ll_c) + +# C (.c) files +# The C file is compiled and updated dependency information is generated. +# (See cmd_cc_o_c + relevant part of rule_cc_o_c) + +is-single-obj-m = $(and $(part-of-module),$(filter $@, $(obj-m)),y) + +ifdef CONFIG_MODVERSIONS +# When module versioning is enabled the following steps are executed: +# o compile a .o from .c +# o if .o doesn't contain a __export_symbol_*, i.e. does +# not export symbols, it's done. +# o otherwise, we calculate symbol versions using the good old +# genksyms on the preprocessed source and dump them into the .cmd file. +# o modpost will extract versions from that file and create *.c files that will +# be compiled and linked to the kernel and/or modules. + +gen_symversions = \ + if $(NM) $@ 2>/dev/null | grep -q ' __export_symbol_'; then \ + $(cmd_gensymtypes_$1) >> $(dot-target).cmd; \ + fi + +cmd_gen_symversions_c = $(call gen_symversions,c) + +endif + +ifdef CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT +# compiler will not generate __mcount_loc use recordmcount or recordmcount.pl +ifdef BUILD_C_RECORDMCOUNT +ifeq ("$(origin RECORDMCOUNT_WARN)", "command line") + RECORDMCOUNT_FLAGS = -w +endif +# Due to recursion, we must skip empty.o. +# The empty.o file is created in the make process in order to determine +# the target endianness and word size. It is made before all other C +# files, including recordmcount. +sub_cmd_record_mcount = \ + if [ $(@) != "scripts/mod/empty.o" ]; then \ + $(objtree)/scripts/recordmcount $(RECORDMCOUNT_FLAGS) "$(@)"; \ + fi; +recordmcount_source := $(srctree)/scripts/recordmcount.c \ + $(srctree)/scripts/recordmcount.h +else +sub_cmd_record_mcount = perl $(srctree)/scripts/recordmcount.pl "$(ARCH)" \ + "$(if $(CONFIG_CPU_BIG_ENDIAN),big,little)" \ + "$(if $(CONFIG_64BIT),64,32)" \ + "$(OBJDUMP)" "$(OBJCOPY)" "$(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)" \ + "$(LD) $(KBUILD_LDFLAGS)" "$(NM)" "$(RM)" "$(MV)" \ + "$(if $(part-of-module),1,0)" "$(@)"; +recordmcount_source := $(srctree)/scripts/recordmcount.pl +endif # BUILD_C_RECORDMCOUNT +cmd_record_mcount = $(if $(findstring $(strip $(CC_FLAGS_FTRACE)),$(_c_flags)), \ + $(sub_cmd_record_mcount)) +endif # CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT + +# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory +# '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),$(is-kernel-object)) + +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 + +# Built-in and composite module parts +$(obj)/%.o: $(obj)/%.c $(recordmcount_source) FORCE + $(call if_changed_rule,cc_o_c) + $(call cmd,force_checksrc) + +# To make this rule robust against "Argument list too long" error, +# ensure to add $(obj)/ prefix by a shell command. +cmd_mod = printf '%s\n' $(call real-search, $*.o, .o, -objs -y -m) | \ + $(AWK) '!x[$$0]++ { print("$(obj)/"$$0) }' > $@ + +$(obj)/%.mod: FORCE + $(call if_changed,mod) + +quiet_cmd_cc_lst_c = MKLST $@ + cmd_cc_lst_c = $(CC) $(c_flags) -g -c -o $*.o $< && \ + $(CONFIG_SHELL) $(srctree)/scripts/makelst $*.o \ + System.map $(OBJDUMP) > $@ + +$(obj)/%.lst: $(obj)/%.c FORCE + $(call if_changed_dep,cc_lst_c) + +# Compile Rust sources (.rs) +# --------------------------------------------------------------------------- + +# The features in this list are the ones allowed for non-`rust/` code. +# +# - Stable since Rust 1.87.0: `feature(asm_goto)`. +# - Stable since Rust 1.89.0: `feature(generic_arg_infer)`. +# - 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 := arbitrary_self_types,asm_goto,generic_arg_infer,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))' \ + -Zunstable-options --extern pin_init --extern kernel \ + --extern zerocopy --extern zerocopy_derive \ + --crate-type rlib -L $(objtree)/rust/ \ + --sysroot=/dev/null \ + --out-dir $(dir $@) --emit=dep-info=$(depfile) + +# `--emit=obj`, `--emit=asm` and `--emit=llvm-ir` imply a single codegen unit +# will be used. We explicitly request `-Ccodegen-units=1` in any case, and +# the compiler shows a warning if it is not 1. However, if we ever stop +# requesting it explicitly and we start using some other `--emit` that does not +# imply it (and for which codegen is performed), then we would be out of sync, +# i.e. the outputs we would get for the different single targets (e.g. `.ll`) +# would not match each other. + +quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ + cmd_rustc_o_rs = $(rust_common_cmd) --emit=$(if $(CONFIG_RUST_INLINE_HELPERS),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) $< \ + $(if $(CONFIG_RUST_INLINE_HELPERS),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ + $(objtree)/rust/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ + $(CC) $(CLANG_FLAGS) $(filter-out $(CC_FLAGS_LTO),$(KBUILD_CFLAGS)) \ + $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ + $(cmd_ld_single)) \ + $(cmd_objtool) + +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) --config-path $(srctree)/.rustfmt.toml $@ + +$(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: $(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: $(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) +# --------------------------------------------------------------------------- + +# .S file exports must have their C prototypes defined in asm/asm-prototypes.h +# or a file that it includes, in order to get versioned symbols. We build a +# dummy C file that includes asm-prototypes and the EXPORT_SYMBOL lines from +# the .S file (with trailing ';'), and run genksyms on that, to extract vers. +# +# 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 or compiled into an object for gendwarfksyms. +# +# These mirror gensymtypes_c and co above, keep them in synch. +getasmexports = \ + { echo "\#include " ; \ + echo "\#include " ; \ + echo "\#include " ; \ + $(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: $(obj)/%.S FORCE + $(call if_changed_dep,cpp_s_S) + +ifdef CONFIG_ASM_MODVERSIONS + +# versioning matches the C process described above, with difference that +# we parse asm-prototypes.h C header to get function definitions. + +cmd_gen_symversions_S = $(call gen_symversions,S) + +endif + +$(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 += $(lib-y) $(always-y) + +# Linker scripts preprocessor (.lds.S -> .lds) +# --------------------------------------------------------------------------- +quiet_cmd_cpp_lds_S = LDS $@ + cmd_cpp_lds_S = $(CPP) $(cpp_flags) -P -U$(ARCH) \ + -D__ASSEMBLY__ -DLINKER_SCRIPT -o $@ $< + +$(obj)/%.lds: $(src)/%.lds.S FORCE + $(call if_changed_dep,cpp_lds_S) + +# ASN.1 grammar +# --------------------------------------------------------------------------- +quiet_cmd_asn1_compiler = ASN.1 $(basename $@).[ch] + cmd_asn1_compiler = $(objtree)/scripts/asn1_compiler $< \ + $(basename $@).c $(basename $@).h + +$(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler + $(call cmd,asn1_compiler) + +# Build the compiled-in targets +# --------------------------------------------------------------------------- + +# 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)/% ; + +# +# Rule to compile a set of .o files into one .a file (without symbol table) +# +# To make this rule robust against "Argument list too long" error, +# remove $(obj)/ prefix, and restore it by a shell command. + +quiet_cmd_ar_builtin = AR $@ + cmd_ar_builtin = rm -f $@; \ + $(if $(real-prereqs), printf "$(obj)/%s " $(patsubst $(obj)/%,%,$(real-prereqs)) | xargs) \ + $(AR) cDPrST $@ + +$(obj)/built-in.a: $(real-obj-y) FORCE + $(call if_changed,ar_builtin) + +# 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);) :; } \ + > $@ + +$(obj)/modules.order: $(obj-m) FORCE + $(call if_changed,gen_order) + +# +# Rule to compile a set of .o files into one .a file (with symbol table) +# + +$(obj)/lib.a: $(lib-y) FORCE + $(call if_changed,ar) + +quiet_cmd_ld_multi_m = LD [M] $@ + cmd_ld_multi_m = $(LD) $(ld_flags) -r -o $@ @$< $(cmd_objtool) + +define rule_ld_multi_m + $(call cmd_and_savecmd,ld_multi_m) + $(call cmd,gen_objtooldep) +endef + +$(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) + +# Add intermediate targets: +# When building objects with specific suffix patterns, add intermediate +# targets that the final targets are derived from. +intermediate_targets = $(foreach sfx, $(2), \ + $(patsubst %$(strip $(1)),%$(sfx), \ + $(filter %$(strip $(1)), $(targets)))) +# %.asn1.o <- %.asn1.[ch] <- %.asn1 +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 + +# Single targets +# --------------------------------------------------------------------------- + +single-subdirs := $(foreach d, $(subdir-ym), $(if $(filter $d/%, $(MAKECMDGOALS)), $d)) +single-subdir-goals := $(filter $(addsuffix /%, $(single-subdirs)), $(MAKECMDGOALS)) + +$(single-subdir-goals): $(single-subdirs) + @: + +# Descending +# --------------------------------------------------------------------------- + +PHONY += $(subdir-ym) +$(subdir-ym): + $(Q)$(MAKE) $(build)=$@ \ + need-builtin=$(if $(filter $@/built-in.a, $(subdir-builtin)),1) \ + need-modorder=$(if $(filter $@/modules.order, $(subdir-modorder)),1) \ + $(filter $@/%, $(single-subdir-goals)) + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE + +FORCE: + +targets += $(filter-out $(single-subdir-goals), $(MAKECMDGOALS)) +targets := $(filter-out $(PHONY), $(targets)) + +# Now that targets is fully known, include dtb rules if needed +ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets)),) +include $(srctree)/scripts/Makefile.dtbs +endif + +# Build +# Needs to be after the include of Makefile.dtbs, which updates always-y +# --------------------------------------------------------------------------- + +$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \ + $(if $(KBUILD_MODULES), $(targets-for-modules)) \ + $(subdir-ym) $(always-y) + @: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +# Create directories for object files if they do not exist +obj-dirs := $(sort $(patsubst %/,%, $(dir $(targets)))) +# If targets exist, their directories apparently exist. Skip mkdir. +existing-dirs := $(sort $(patsubst %/,%, $(dir $(existing-targets)))) +obj-dirs := $(strip $(filter-out $(existing-dirs), $(obj-dirs))) +ifneq ($(obj-dirs),) +$(shell mkdir -p $(obj-dirs)) +endif + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.clang b/scripts/Makefile.clang new file mode 100644 index 000000000..b67636b28 --- /dev/null +++ b/scripts/Makefile.clang @@ -0,0 +1,42 @@ +# Individual arch/{arch}/Makefiles should use -EL/-EB to set intended +# endianness and -m32/-m64 to set word size based on Kconfigs instead of +# relying on the target triple. +CLANG_TARGET_FLAGS_arm := arm-linux-gnueabi +CLANG_TARGET_FLAGS_arm64 := aarch64-linux-gnu +CLANG_TARGET_FLAGS_hexagon := hexagon-linux-musl +CLANG_TARGET_FLAGS_loongarch := loongarch64-linux-gnusf +CLANG_TARGET_FLAGS_m68k := m68k-linux-gnu +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)) + +ifeq ($(CLANG_TARGET_FLAGS),) +$(error add '--target=' option to scripts/Makefile.clang) +else +CLANG_FLAGS += --target=$(CLANG_TARGET_FLAGS) +endif + +ifeq ($(LLVM_IAS),0) +CLANG_FLAGS += -fno-integrated-as +GCC_TOOLCHAIN_DIR := $(dir $(shell which $(CROSS_COMPILE)elfedit)) +CLANG_FLAGS += --prefix=$(GCC_TOOLCHAIN_DIR)$(notdir $(CROSS_COMPILE)) +else +CLANG_FLAGS += -fintegrated-as +endif +# By default, clang only warns when it encounters an unknown warning flag or +# certain optimization flags it knows it has not implemented. +# Make it behave more like gcc by erroring when these flags are encountered +# so they can be implemented or wrapped in cc-option. +CLANG_FLAGS += -Werror=unknown-warning-option +CLANG_FLAGS += -Werror=ignored-optimization-argument +CLANG_FLAGS += -Werror=option-ignored +CLANG_FLAGS += -Werror=unused-command-line-argument +KBUILD_CPPFLAGS += $(CLANG_FLAGS) +export CLANG_FLAGS diff --git a/scripts/Makefile.clean b/scripts/Makefile.clean new file mode 100644 index 000000000..6ead00ec7 --- /dev/null +++ b/scripts/Makefile.clean @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +# ========================================================================== +# Cleaning up +# ========================================================================== + +src := $(srcroot)/$(obj) + +PHONY := __clean +__clean: + +include $(srctree)/scripts/Kbuild.include +include $(kbuild-file) + +# Figure out what we need to build from the various variables +# ========================================================================== + +subdir-ymn := $(sort $(subdir-y) $(subdir-m) $(subdir-) \ + $(patsubst %/,%, $(filter %/, $(obj-y) $(obj-m) $(obj-)))) + +# Add subdir path + +subdir-ymn := $(addprefix $(obj)/,$(subdir-ymn)) + +# build a list of files to remove, usually relative to the current +# directory + +__clean-files := \ + $(clean-files) $(targets) $(hostprogs) $(userprogs) \ + $(extra-y) $(extra-m) $(extra-) \ + $(always-y) $(always-m) $(always-) \ + $(hostprogs-always-y) $(hostprogs-always-m) $(hostprogs-always-) \ + $(userprogs-always-y) $(userprogs-always-m) $(userprogs-always-) + +__clean-files := $(filter-out $(no-clean-files), $(__clean-files)) + +__clean-files := $(wildcard $(addprefix $(obj)/, $(__clean-files))) + +# ========================================================================== + +# To make this rule robust against "Argument list too long" error, +# remove $(obj)/ prefix, and restore it by a shell command. +quiet_cmd_clean = CLEAN $(obj) + cmd_clean = printf '$(obj)/%s ' $(patsubst $(obj)/%,%,$(__clean-files)) | xargs rm -rf + +__clean: $(subdir-ymn) +ifneq ($(strip $(__clean-files)),) + $(call cmd,clean) +endif + @: + + +# =========================================================================== +# Generic stuff +# =========================================================================== + +# Descending +# --------------------------------------------------------------------------- + +PHONY += $(subdir-ymn) +$(subdir-ymn): + $(Q)$(MAKE) $(clean)=$@ + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.compiler b/scripts/Makefile.compiler new file mode 100644 index 000000000..06bbe29c8 --- /dev/null +++ b/scripts/Makefile.compiler @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# cc-cross-prefix +# Usage: CROSS_COMPILE := $(call cc-cross-prefix, m68k-linux-gnu- m68k-linux-) +# Return first where a gcc is found in PATH. +# If no gcc found in PATH with listed prefixes return nothing +# +# Note: '2>/dev/null' is here to force Make to invoke a shell. Otherwise, it +# would try to directly execute the shell builtin 'command'. This workaround +# should be kept for a long time since this issue was fixed only after the +# GNU Make 4.2.1 release. +cc-cross-prefix = $(firstword $(foreach c, $(1), \ + $(if $(shell command -v -- $(c)gcc 2>/dev/null), $(c)))) + +# output directory for tests below +TMPOUT = .tmp_$$$$ + +# try-run +# Usage: option = $(call try-run, $(CC)...-o "$$TMP",option-ok,otherwise) +# Exit code chooses option. "$$TMP" serves as a temporary file and is +# automatically cleaned up. +try-run = $(shell set -e; \ + TMP=$(TMPOUT)/tmp; \ + trap "rm -rf $(TMPOUT)" EXIT; \ + mkdir -p $(TMPOUT); \ + if ($(1)) >/dev/null 2>&1; \ + then echo "$(2)"; \ + else echo "$(3)"; \ + fi) + +# as-option +# Usage: aflags-y += $(call as-option,-Wa$(comma)-isa=foo,) + +as-option = $(call try-run,\ + $(CC) -Werror $(KBUILD_CPPFLAGS) $(KBUILD_AFLAGS) $(1) -c -x assembler-with-cpp /dev/null -o "$$TMP",$(1),$(2)) + +# as-instr +# Usage: aflags-y += $(call as-instr,instr,option1,option2) + +as-instr = $(call try-run,\ + printf "%b\n" "$(1)" | $(CC) -Werror $(CLANG_FLAGS) $(KBUILD_AFLAGS) -Wa$(comma)--fatal-warnings -c -x assembler-with-cpp -o "$$TMP" -,$(2),$(3)) + +# __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:-Wno-%=-W%) -c -x c /dev/null -o "$$TMP",$(3),$(4)) + +# cc-option +# Usage: cflags-y += $(call cc-option,-march=winchip-c6,-march=i586) + +cc-option = $(call __cc-option, $(CC),\ + $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS),$(1),$(2)) + +# cc-option-yn +# Usage: flag := $(call cc-option-yn,-march=winchip-c6) +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 cc-option,-Wno-$(strip $1)) + +# gcc-min-version +# 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 $(KBUILD_RUSTFLAGS_OPTION_CHKS) $(filter-out --sysroot=/dev/null --target=%target.json,$(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.context-analysis b/scripts/Makefile.context-analysis new file mode 100644 index 000000000..cd3bb49d3 --- /dev/null +++ b/scripts/Makefile.context-analysis @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 + +context-analysis-cflags := -DWARN_CONTEXT_ANALYSIS \ + -fexperimental-late-parse-attributes -Wthread-safety \ + -Wthread-safety-pointer -Wthread-safety-beta + +ifndef CONFIG_WARN_CONTEXT_ANALYSIS_ALL +context-analysis-cflags += --warning-suppression-mappings=$(srctree)/scripts/context-analysis-suppression.txt +endif + +export CFLAGS_CONTEXT_ANALYSIS := $(context-analysis-cflags) diff --git a/scripts/Makefile.debug b/scripts/Makefile.debug new file mode 100644 index 000000000..107db997c --- /dev/null +++ b/scripts/Makefile.debug @@ -0,0 +1,52 @@ +DEBUG_CFLAGS := +DEBUG_RUSTFLAGS := + +debug-flags-y := -g + +ifdef CONFIG_DEBUG_INFO_SPLIT +DEBUG_CFLAGS += -gsplit-dwarf +endif + +debug-flags-$(CONFIG_DEBUG_INFO_DWARF4) += -gdwarf-4 +debug-flags-$(CONFIG_DEBUG_INFO_DWARF5) += -gdwarf-5 +ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_AS_IS_GNU),yy) +# Clang does not pass -g or -gdwarf-* option down to GAS. +# Add -Wa, prefix to explicitly specify the flags. +KBUILD_AFLAGS += $(addprefix -Wa$(comma), $(debug-flags-y)) +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 +ifdef CONFIG_CC_IS_GCC +DEBUG_CFLAGS += -femit-struct-debug-baseonly +endif +else +DEBUG_RUSTFLAGS += -Cdebuginfo=2 +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 + +KBUILD_CFLAGS += $(DEBUG_CFLAGS) +export DEBUG_CFLAGS + +KBUILD_RUSTFLAGS += $(DEBUG_RUSTFLAGS) +export DEBUG_RUSTFLAGS diff --git a/scripts/Makefile.defconf b/scripts/Makefile.defconf new file mode 100644 index 000000000..a44307f08 --- /dev/null +++ b/scripts/Makefile.defconf @@ -0,0 +1,30 @@ +# 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: +# $(call merge_into_defconfig,base_config,config_fragment1 config_fragment2 ...) +# +# Input config fragments without '.config' suffix +define merge_into_defconfig + $(call cmd,merge_fragments,$1,$2) + +$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig +endef + + +# Creates 'merged defconfigs without warning about overrides' +# --------------------------------------------------------------------------- +# Usage: +# $(call merge_into_defconfig_override,base_config,config_fragment1 config_fragment2 ...) +# +# Input config fragments without '.config' suffix +define merge_into_defconfig_override + $(call cmd,merge_fragments,$1,$2,-Q) + +$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig +endef diff --git a/scripts/Makefile.dtbinst b/scripts/Makefile.dtbinst new file mode 100644 index 000000000..9d920419a --- /dev/null +++ b/scripts/Makefile.dtbinst @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0 +# ========================================================================== +# Installing dtb files +# +# Installs all dtb files listed in $(dtb-y) either in the +# INSTALL_DTBS_PATH directory or the default location: +# +# $INSTALL_PATH/dtbs/$KERNELRELEASE +# ========================================================================== + +PHONY := __dtbs_install +__dtbs_install: + +include include/config/auto.conf +include $(srctree)/scripts/Kbuild.include + +dst := $(INSTALL_DTBS_PATH) + +quiet_cmd_dtb_install = INSTALL $@ + cmd_dtb_install = install -D -m 0644 $< $@ + +$(dst)/%: $(obj)/% + $(call cmd,dtb_install) + +dtbs := $(patsubst $(obj)/%,%,$(call read-file, $(obj)/dtbs-list)) + +ifdef CONFIG_ARCH_WANT_FLAT_DTB_INSTALL + +define gen_install_rules +$(dst)/%: $(obj)/$(1)% + $$(call cmd,dtb_install) +endef + +$(foreach d, $(sort $(dir $(dtbs))), $(eval $(call gen_install_rules,$(d)))) + +dtbs := $(notdir $(dtbs)) + +endif # CONFIG_ARCH_WANT_FLAT_DTB_INSTALL + +__dtbs_install: $(addprefix $(dst)/, $(dtbs)) + @: + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.dtbs b/scripts/Makefile.dtbs new file mode 100644 index 000000000..c4e466390 --- /dev/null +++ b/scripts/Makefile.dtbs @@ -0,0 +1,153 @@ +# SPDX-License-Identifier: GPL-2.0-only + +all-dtb := $(dtb-y) $(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)) + +# Ensure that any .dtbo is applied to at least one base .dtb. Otherwise, it +# does not get validated. +applied-dtbo := $(filter %.dtbo, \ + $(call real-search, $(call multi-search, $(all-dtb), .dtb, -dtbs), .dtb, -dtbs)) +unapplied-dtbo := $(filter-out $(applied-dtbo),$(filter %.dtbo, $(dtb-y))) +$(if $(unapplied-dtbo), $(warning .dtbo is not applied to any base: $(unapplied-dtbo))) + +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 '; \ + 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-interrupt_map \ + -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.gcc-plugins b/scripts/Makefile.gcc-plugins new file mode 100644 index 000000000..b0e1423b0 --- /dev/null +++ b/scripts/Makefile.gcc-plugins @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 + +gcc-plugin-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) += latent_entropy_plugin.so +gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) \ + += -DLATENT_ENTROPY_PLUGIN +ifdef CONFIG_GCC_PLUGIN_LATENT_ENTROPY + DISABLE_LATENT_ENTROPY_PLUGIN += -fplugin-arg-latent_entropy_plugin-disable -ULATENT_ENTROPY_PLUGIN +endif +export DISABLE_LATENT_ENTROPY_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)) -DGCC_PLUGINS +export GCC_PLUGINS_CFLAGS + +# Add the flags to the build! +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_RANDSTRUCT) \ + += randomize_layout_plugin.so +gcc-plugin-external-$(CONFIG_GCC_PLUGIN_STACKLEAK) \ + += stackleak_plugin.so + +# All enabled GCC plugins are collected here for building in +# scripts/gcc-scripts/Makefile. +GCC_PLUGIN := $(gcc-plugin-y) $(gcc-plugin-external-y) +export GCC_PLUGIN diff --git a/scripts/Makefile.headersinst b/scripts/Makefile.headersinst new file mode 100644 index 000000000..029d85bb0 --- /dev/null +++ b/scripts/Makefile.headersinst @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: GPL-2.0 +# ========================================================================== +# Installing headers +# +# All headers under include/uapi, include/generated/uapi, +# arch//include/uapi and arch//include/generated/uapi are +# exported. +# They are preprocessed to remove __KERNEL__ section of the file. +# +# ========================================================================== + +PHONY := __headers +__headers: + +include $(srctree)/scripts/Kbuild.include + +src := $(srctree)/$(obj) +gen := $(objtree)/$(subst include/,include/generated/,$(obj)) +dst := usr/include + +-include $(src)/Kbuild + +# $(filter %/, ...) is a workaround for GNU Make <= 4.2.1, where +# $(wildcard $(src)/*/) contains not only directories but also regular files. +src-subdirs := $(patsubst $(src)/%/,%,$(filter %/, $(wildcard $(src)/*/))) +gen-subdirs := $(patsubst $(gen)/%/,%,$(filter %/, $(wildcard $(gen)/*/))) +all-subdirs := $(sort $(src-subdirs) $(gen-subdirs)) + +src-headers := $(if $(src-subdirs), $(shell cd $(src) && find $(src-subdirs) -name '*.h')) +src-headers := $(filter-out $(no-export-headers), $(src-headers)) +gen-headers := $(if $(gen-subdirs), $(shell cd $(gen) && find $(gen-subdirs) -name '*.h')) +gen-headers := $(filter-out $(no-export-headers), $(gen-headers)) + +# If the same header is exported from source and generated directories, +# the former takes precedence, but this should be warned. +duplicated := $(filter $(gen-headers), $(src-headers)) +$(if $(duplicated), $(warning duplicated header export: $(duplicated))) + +gen-headers := $(filter-out $(duplicated), $(gen-headers)) + +# Add dst path prefix +all-subdirs := $(addprefix $(dst)/, $(all-subdirs)) +src-headers := $(addprefix $(dst)/, $(src-headers)) +gen-headers := $(addprefix $(dst)/, $(gen-headers)) +all-headers := $(src-headers) $(gen-headers) + +# Work out what needs to be removed +old-subdirs := $(wildcard $(all-subdirs)) +old-headers := $(if $(old-subdirs),$(shell find $(old-subdirs) -name '*.h')) +unwanted := $(filter-out $(all-headers), $(old-headers)) + +# Create directories +existing-dirs := $(sort $(dir $(old-headers))) +wanted-dirs := $(sort $(dir $(all-headers))) +new-dirs := $(filter-out $(existing-dirs), $(wanted-dirs)) +$(if $(new-dirs), $(shell mkdir -p $(new-dirs))) + +# Rules +quiet_cmd_install = HDRINST $@ + cmd_install = $(CONFIG_SHELL) $(srctree)/scripts/headers_install.sh $< $@ + +$(src-headers): $(dst)/%.h: $(src)/%.h $(srctree)/scripts/headers_install.sh FORCE + $(call if_changed,install) + +$(gen-headers): $(dst)/%.h: $(gen)/%.h $(srctree)/scripts/headers_install.sh FORCE + $(call if_changed,install) + +quiet_cmd_remove = REMOVE $(unwanted) + cmd_remove = rm -f $(unwanted) + +__headers: $(all-headers) +ifneq ($(unwanted),) + $(call cmd,remove) +endif + @: + +existing-headers := $(filter $(old-headers), $(all-headers)) + +-include $(foreach f,$(existing-headers),$(dir $(f)).$(notdir $(f)).cmd) + +PHONY += FORCE +FORCE: + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.host b/scripts/Makefile.host new file mode 100644 index 000000000..c1dedf646 --- /dev/null +++ b/scripts/Makefile.host @@ -0,0 +1,165 @@ +# SPDX-License-Identifier: GPL-2.0 + +# LEX +# --------------------------------------------------------------------------- +quiet_cmd_flex = LEX $@ + cmd_flex = $(LEX) -o$@ -L $< + +$(obj)/%.lex.c: $(src)/%.l FORCE + $(call if_changed,flex) + +# YACC +# --------------------------------------------------------------------------- +quiet_cmd_bison = YACC $(basename $@).[ch] + cmd_bison = $(YACC) -o $(basename $@).c --defines=$(basename $@).h -t -l $< + +$(obj)/%.tab.c $(obj)/%.tab.h: $(src)/%.y FORCE + $(call if_changed,bison) + +# ========================================================================== +# Building binaries on the host system +# Binaries are used during the compilation of the kernel, for example +# to preprocess a data file. +# +# Both C and C++ are supported, but preferred language is C for such utilities. +# Rust is also supported, but it may only be used in scenarios where a Rust +# toolchain is required to be available (e.g. when `CONFIG_RUST` is enabled). +# +# Sample syntax (see Documentation/kbuild/makefiles.rst for reference) +# hostprogs := bin2hex +# Will compile bin2hex.c and create an executable named bin2hex +# +# hostprogs := lxdialog +# lxdialog-objs := checklist.o lxdialog.o +# Will compile lxdialog.c and checklist.c, and then link the executable +# lxdialog, based on checklist.o and lxdialog.o +# +# hostprogs := qconf +# qconf-cxxobjs := qconf.o +# qconf-objs := menu.o +# Will compile qconf as a C++ program, and menu as a C program. +# They are linked as C++ code to the executable qconf +# +# hostprogs := target +# target-rust := y +# Will compile `target` as a Rust program, using `target.rs` as the crate root. +# The crate may consist of several source files. + +# C code +# Executables compiled from a single .c file +host-csingle := $(foreach m,$(hostprogs), \ + $(if $($(m)-objs)$($(m)-cxxobjs)$($(m)-rust),,$(m))) + +# C executables linked based on several .o files +host-cmulti := $(foreach m,$(hostprogs),\ + $(if $($(m)-cxxobjs)$($(m)-rust),,$(if $($(m)-objs),$(m)))) + +# Object (.o) files compiled from .c files +host-cobjs := $(sort $(foreach m,$(hostprogs),$($(m)-objs))) + +# C++ code +# C++ executables compiled from at least one .cc file +# and zero or more .c files +host-cxxmulti := $(foreach m,$(hostprogs),$(if $($(m)-cxxobjs),$(m))) + +# C++ Object (.o) files compiled from .cc files +host-cxxobjs := $(sort $(foreach m,$(host-cxxmulti),$($(m)-cxxobjs))) + +# Rust code +# Executables compiled from a single Rust crate (which may consist of +# one or more .rs files) +host-rust := $(foreach m,$(hostprogs),$(if $($(m)-rust),$(m))) + +host-csingle := $(addprefix $(obj)/,$(host-csingle)) +host-cmulti := $(addprefix $(obj)/,$(host-cmulti)) +host-cobjs := $(addprefix $(obj)/,$(host-cobjs)) +host-cxxmulti := $(addprefix $(obj)/,$(host-cxxmulti)) +host-cxxobjs := $(addprefix $(obj)/,$(host-cxxobjs)) +host-rust := $(addprefix $(obj)/,$(host-rust)) + +##### +# Handle options to gcc. Support building with separate output directory + +hostc_flags = -Wp,-MMD,$(depfile) \ + $(KBUILD_HOSTCFLAGS) $(HOST_EXTRACFLAGS) \ + $(HOSTCFLAGS_$(target-stem).o) +hostcxx_flags = -Wp,-MMD,$(depfile) \ + $(KBUILD_HOSTCXXFLAGS) $(HOST_EXTRACXXFLAGS) \ + $(HOSTCXXFLAGS_$(target-stem).o) + +# `--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. +hostrust_flags = --out-dir $(dir $@) --emit=dep-info=$(depfile) \ + -Clinker-flavor=gcc -Clinker=$(HOSTCC) \ + -Clink-args='$(call escsq,$(KBUILD_HOSTLDFLAGS))' \ + $(KBUILD_HOSTRUSTFLAGS) $(HOST_EXTRARUSTFLAGS) \ + $(HOSTRUSTFLAGS_$(target-stem)) + +# $(obj) for including generated headers from checkin source files +ifdef building_out_of_srctree +hostc_flags += -I $(obj) +hostcxx_flags += -I $(obj) +endif + +##### +# Compile programs on the host + +# Create executable from a single .c file +# host-csingle -> Executable +quiet_cmd_host-csingle = HOSTCC $@ + cmd_host-csingle = $(HOSTCC) $(hostc_flags) $(KBUILD_HOSTLDFLAGS) -o $@ $< \ + $(KBUILD_HOSTLDLIBS) $(HOSTLDLIBS_$(target-stem)) +$(host-csingle): $(obj)/%: $(obj)/%.c FORCE + $(call if_changed_dep,host-csingle) + +# Link an executable based on list of .o files, all plain c +# host-cmulti -> executable +quiet_cmd_host-cmulti = HOSTLD $@ + cmd_host-cmulti = $(HOSTCC) $(KBUILD_HOSTLDFLAGS) -o $@ \ + $(addprefix $(obj)/, $($(target-stem)-objs)) \ + $(KBUILD_HOSTLDLIBS) $(HOSTLDLIBS_$(target-stem)) +$(host-cmulti): FORCE + $(call if_changed,host-cmulti) +$(call multi_depend, $(host-cmulti), , -objs) + +# Create .o file from a single .c file +# host-cobjs -> .o +quiet_cmd_host-cobjs = HOSTCC $@ + cmd_host-cobjs = $(HOSTCC) $(hostc_flags) -c -o $@ $< +$(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 +# host-cxxmulti -> executable +quiet_cmd_host-cxxmulti = HOSTLD $@ + cmd_host-cxxmulti = $(HOSTCXX) $(KBUILD_HOSTLDFLAGS) -o $@ \ + $(foreach o,objs cxxobjs,\ + $(addprefix $(obj)/, $($(target-stem)-$(o)))) \ + $(KBUILD_HOSTLDLIBS) $(HOSTLDLIBS_$(target-stem)) +$(host-cxxmulti): FORCE + $(call if_changed,host-cxxmulti) +$(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: $(obj)/%.cc FORCE + $(call if_changed_dep,host-cxxobjs) + +# Create executable from a single Rust crate (which may consist of +# one or more `.rs` files) +# host-rust -> Executable +quiet_cmd_host-rust = HOSTRUSTC $@ + cmd_host-rust = \ + $(HOSTRUSTC) $(hostrust_flags) --emit=link=$@ $< +$(host-rust): $(obj)/%: $(src)/%.rs FORCE + +$(call if_changed_dep,host-rust) + +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 new file mode 100644 index 000000000..91504e812 --- /dev/null +++ b/scripts/Makefile.kasan @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: GPL-2.0 + +ifdef CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX +# Safe for compiler to generate meminstrinsic calls in uninstrumented files. +CFLAGS_KASAN_NOSANITIZE := +else +# Don't let compiler generate memintrinsic calls in uninstrumented files +# because they are instrumented. +CFLAGS_KASAN_NOSANITIZE := -fno-builtin +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 +else + stack_enable := 0 +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 + +# 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. +kasan_params += asan-kernel-mem-intrinsic-prefix=1 + +endif # CONFIG_KASAN_GENERIC + +ifdef CONFIG_KASAN_SW_TAGS + +CFLAGS_KASAN := -fsanitize=kernel-hwaddress + +RUSTFLAGS_KASAN := -Zsanitizer=kernel-hwaddress \ + -Zsanitizer-recover=kernel-hwaddress + +ifdef CONFIG_KASAN_INLINE + kasan_params += hwasan-mapping-offset=$(KASAN_SHADOW_OFFSET) +else + kasan_params += hwasan-instrument-with-calls=1 +endif + +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 __(hw)asan_mem*(). +ifdef CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX + ifdef CONFIG_CC_IS_GCC + kasan_params += asan-kernel-mem-intrinsic-prefix=1 + else + kasan_params += hwasan-kernel-mem-intrinsic-prefix=1 + endif +endif # CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX + +endif # CONFIG_KASAN_SW_TAGS + +# 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 new file mode 100644 index 000000000..78305a84b --- /dev/null +++ b/scripts/Makefile.kcov @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +kcov-flags-y += -fsanitize-coverage=trace-pc +kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -fsanitize-coverage=trace-cmp + +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.kcsan b/scripts/Makefile.kcsan new file mode 100644 index 000000000..19f693b68 --- /dev/null +++ b/scripts/Makefile.kcsan @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# GCC and Clang accept backend options differently. Do not wrap in cc-option, +# because Clang accepts "--param" even if it is unused. +ifdef CONFIG_CC_IS_CLANG +cc-param = -mllvm -$(1) +else +cc-param = --param $(1) +endif + +# Keep most options here optional, to allow enabling more compilers if absence +# of some options does not break KCSAN nor causes false positive reports. +kcsan-cflags := -fsanitize=thread -fno-optimize-sibling-calls \ + $(call cc-option,$(call cc-param,tsan-compound-read-before-write=1),$(call cc-option,$(call cc-param,tsan-instrument-read-before-write=1))) \ + $(call cc-param,tsan-distinguish-volatile=1) + +ifdef CONFIG_CC_IS_GCC +# GCC started warning about operations unsupported by the TSan runtime. But +# KCSAN != TSan, so just ignore these warnings. +kcsan-cflags += -Wno-tsan +endif + +ifndef CONFIG_KCSAN_WEAK_MEMORY +kcsan-cflags += $(call cc-option,$(call cc-param,tsan-instrument-func-entry-exit=0)) +endif + +export CFLAGS_KCSAN := $(kcsan-cflags) diff --git a/scripts/Makefile.kmsan b/scripts/Makefile.kmsan new file mode 100644 index 000000000..b5b0aa613 --- /dev/null +++ b/scripts/Makefile.kmsan @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +kmsan-cflags := -fsanitize=kernel-memory + +ifdef CONFIG_KMSAN_CHECK_PARAM_RETVAL +kmsan-cflags += -fsanitize-memory-param-retval +endif + +export CFLAGS_KMSAN := $(kmsan-cflags) diff --git a/scripts/Makefile.kstack_erase b/scripts/Makefile.kstack_erase new file mode 100644 index 000000000..ee7e4ef7b --- /dev/null +++ b/scripts/Makefile.kstack_erase @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 + +ifdef CONFIG_GCC_PLUGIN_STACKLEAK +kstack-erase-cflags-y += -fplugin=$(objtree)/scripts/gcc-plugins/stackleak_plugin.so +kstack-erase-cflags-y += -fplugin-arg-stackleak_plugin-track-min-size=$(CONFIG_KSTACK_ERASE_TRACK_MIN_SIZE) +kstack-erase-cflags-y += -fplugin-arg-stackleak_plugin-arch=$(SRCARCH) +kstack-erase-cflags-$(CONFIG_GCC_PLUGIN_STACKLEAK_VERBOSE) += -fplugin-arg-stackleak_plugin-verbose +DISABLE_KSTACK_ERASE := -fplugin-arg-stackleak_plugin-disable +endif + +ifdef CONFIG_CC_IS_CLANG +kstack-erase-cflags-y += -fsanitize-coverage=stack-depth +kstack-erase-cflags-y += -fsanitize-coverage-stack-depth-callback-min=$(CONFIG_KSTACK_ERASE_TRACK_MIN_SIZE) +DISABLE_KSTACK_ERASE := -fno-sanitize-coverage=stack-depth +endif + +KSTACK_ERASE_CFLAGS := $(kstack-erase-cflags-y) + +export KSTACK_ERASE_CFLAGS DISABLE_KSTACK_ERASE + +KBUILD_CFLAGS += $(KSTACK_ERASE_CFLAGS) diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib new file mode 100644 index 000000000..0a4fdd8bd --- /dev/null +++ b/scripts/Makefile.lib @@ -0,0 +1,515 @@ +# SPDX-License-Identifier: GPL-2.0 + +# 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. +modname-multi = $(sort $(foreach m,$(multi-obj-ym),\ + $(if $(filter $*.o, $(call suffix-search, $m, .o, -objs -y -m)),$(m:.o=)))) + +__modname = $(or $(modname-multi),$(basetarget)) + +modname = $(subst $(space),:,$(__modname)) +modfile = $(addprefix $(obj)/,$(__modname)) + +# target with $(obj)/ and its suffix stripped +target-stem = $(basename $(patsubst $(obj)/%,%,$@)) + +# These flags are needed for modversions and compiling, so we define them here +# $(modname_flags) defines KBUILD_MODNAME as the name of the module it will +# end up in (or would, if it gets compiled in) +name-fix-token = $(subst $(comma),_,$(subst -,_,$1)) +name-fix = $(call stringify,$(call name-fix-token,$1)) +basename_flags = -DKBUILD_BASENAME=$(call name-fix,$(basetarget)) +modname_flags = -DKBUILD_MODNAME=$(call name-fix,$(modname)) \ + -D__KBUILD_MODNAME=$(call name-fix-token,$(modname)) +modfile_flags = -DKBUILD_MODFILE=$(call stringify,$(modfile)) + +_c_flags = $(filter-out $(CFLAGS_REMOVE_$(target-stem).o), \ + $(filter-out $(ccflags-remove-y), \ + $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(ccflags-y)) \ + $(CFLAGS_$(target-stem).o)) +_rust_flags = $(filter-out $(RUSTFLAGS_REMOVE_$(target-stem).o), \ + $(filter-out $(rustflags-remove-y), \ + $(KBUILD_RUSTFLAGS) $(rustflags-y)) \ + $(RUSTFLAGS_$(target-stem).o)) +_a_flags = $(filter-out $(AFLAGS_REMOVE_$(target-stem).o), \ + $(filter-out $(asflags-remove-y), \ + $(KBUILD_CPPFLAGS) $(KBUILD_AFLAGS) $(asflags-y)) \ + $(AFLAGS_$(target-stem).o)) +_cpp_flags = $(KBUILD_CPPFLAGS) $(cppflags-y) $(CPPFLAGS_$(target-stem).lds) + +# +# Enable gcov profiling flags for a file, directory or for all files depending +# on variables GCOV_PROFILE_obj.o, GCOV_PROFILE and CONFIG_GCOV_PROFILE_ALL +# (in this order) +# +ifeq ($(CONFIG_GCOV_KERNEL),y) +_c_flags += $(if $(patsubst n%,, \ + $(GCOV_PROFILE_$(target-stem).o)$(GCOV_PROFILE)$(if $(is-kernel-object),$(CONFIG_GCOV_PROFILE_ALL))), \ + $(CFLAGS_GCOV)) +endif + +# +# Enable address sanitizer flags for kernel except some files or directories +# we don't want to check (depends on variables KASAN_SANITIZE_obj.o, KASAN_SANITIZE) +# +ifeq ($(CONFIG_KASAN),y) +ifneq ($(CONFIG_KASAN_HW_TAGS),y) +_c_flags += $(if $(patsubst n%,, \ + $(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)$(is-kernel-object)), \ + $(CFLAGS_KMSAN)) +_c_flags += $(if $(patsubst n%,, \ + $(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)$(is-kernel-object)), \ + $(CFLAGS_UBSAN)) +_c_flags += $(if $(patsubst n%,, \ + $(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)$(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 + +# +# Enable KCSAN flags except some files or directories we don't want to check +# (depends on variables KCSAN_SANITIZE_obj.o, KCSAN_SANITIZE) +# +ifeq ($(CONFIG_KCSAN),y) +_c_flags += $(if $(patsubst n%,, \ + $(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. +_c_flags += $(if $(patsubst n%,, \ + $(KCSAN_INSTRUMENT_BARRIERS_$(target-stem).o)$(KCSAN_INSTRUMENT_BARRIERS)n), \ + -D__KCSAN_INSTRUMENT_BARRIERS__) +endif + +# +# Enable context analysis flags only where explicitly opted in. +# (depends on variables CONTEXT_ANALYSIS_obj.o, CONTEXT_ANALYSIS) +# +ifeq ($(CONFIG_WARN_CONTEXT_ANALYSIS),y) +_c_flags += $(if $(patsubst n%,, \ + $(CONTEXT_ANALYSIS_$(target-stem).o)$(CONTEXT_ANALYSIS)$(if $(is-kernel-object),$(CONFIG_WARN_CONTEXT_ANALYSIS_ALL))), \ + $(CFLAGS_CONTEXT_ANALYSIS)) +endif + +# +# 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)) +_rust_flags += $(if $(patsubst n%,, \ + $(AUTOFDO_PROFILE_$(target-stem).o)$(AUTOFDO_PROFILE)$(is-kernel-object)), \ + $(RUSTFLAGS_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], ) + +modkern_cflags = \ + $(if $(part-of-module), \ + $(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE), \ + $(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags)) + +modkern_rustflags = \ + $(if $(part-of-module), \ + $(KBUILD_RUSTFLAGS_MODULE) $(RUSTFLAGS_MODULE), \ + $(KBUILD_RUSTFLAGS_KERNEL) $(RUSTFLAGS_KERNEL)) + +modkern_aflags = $(if $(part-of-module), \ + $(KBUILD_AFLAGS_MODULE) $(AFLAGS_MODULE), \ + $(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL) $(modfile_flags)) + +c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ + -include $(srctree)/include/linux/compiler_types.h \ + $(_c_flags) $(modkern_cflags) \ + $(basename_flags) $(modname_flags) + +rust_flags = $(_rust_flags) $(modkern_rustflags) @$(objtree)/include/generated/rustc_cfg + +a_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ + $(_a_flags) $(modkern_aflags) $(modname_flags) + +cpp_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ + $(_cpp_flags) + +ld_flags = $(KBUILD_LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F)) + +ifdef CONFIG_OBJTOOL + +objtool := $(objtree)/tools/objtool/objtool + +objtool-args-$(CONFIG_HAVE_JUMP_LABEL_HACK) += --hacks=jump_label +objtool-args-$(CONFIG_HAVE_NOINSTR_HACK) += --hacks=noinstr +objtool-args-$(CONFIG_MITIGATION_CALL_DEPTH_TRACKING) += --hacks=skylake +objtool-args-$(CONFIG_X86_KERNEL_IBT) += --ibt +objtool-args-$(CONFIG_CALL_PADDING) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES) +ifdef CONFIG_CALL_PADDING +objtool-args-$(CONFIG_CFI) += --cfi +objtool-args-$(CONFIG_FINEIBT) += --fineibt +endif +objtool-args-$(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL) += --mcount +ifdef CONFIG_FTRACE_MCOUNT_USE_OBJTOOL +objtool-args-$(CONFIG_HAVE_OBJTOOL_NOP_MCOUNT) += --mnop +endif +objtool-args-$(CONFIG_UNWINDER_ORC) += --orc +objtool-args-$(CONFIG_MITIGATION_RETPOLINE) += --retpoline +objtool-args-$(CONFIG_MITIGATION_RETHUNK) += --rethunk +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-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable +objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror + +objtool-args = $(objtool-args-y) \ + $(if $(delay-objtool), --link) \ + $(if $(part-of-module), --module) + +delay-objtool := $(or $(CONFIG_LTO_CLANG),$(CONFIG_X86_KERNEL_IBT),$(CONFIG_KLP_BUILD)) + +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 +# Usage: +# $(call multi_depend, multi_used_targets, suffix_to_remove, suffix_to_add) +define multi_depend +$(foreach m, $1, \ + $(eval $m: \ + $(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 + +ifdef CONFIG_LTO_CLANG_THIN_DIST +# Save the _c_flags, sliently. +quiet_cmd_save_c_flags = + saved_c_flags = $(_c_flags) $(modkern_cflags) + cmd_save_c_flags = printf '\n%s\n' 'saved_c_flags_$@ := $(call escsq,$(saved_c_flags))' >> $(dot-target).cmd +endif + +quiet_cmd_cc_o_c = CC $(quiet_modtag) $@ + cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \ + $(cmd_ld_single) \ + $(cmd_objtool) + +define rule_cc_o_c + $(call cmd_and_fixdep,cc_o_c) + $(call cmd,save_c_flags) + $(call cmd,checksrc) + $(call cmd,checkdoc) + $(call cmd,gen_objtooldep) + $(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, +# the copy would be read-only as well, leading to an error when executing the +# rule next time. Use 'cat' instead in order to generate a writable file. +quiet_cmd_copy = COPY $@ + cmd_copy = cat $< > $@ + +$(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 +# =========================================================================== +# +# Use as following: +# +# target: source(s) FORCE +# $(if_changed,ld/objcopy/gzip) +# +# and add target to 'targets' so that we know we have to +# read in the saved command line + +# Linking +# --------------------------------------------------------------------------- + +quiet_cmd_ld = LD $@ + cmd_ld = $(LD) $(ld_flags) $(real-prereqs) -o $@ + +# Archive +# --------------------------------------------------------------------------- + +quiet_cmd_ar = AR $@ + cmd_ar = rm -f $@; $(AR) cDPrsT $@ $(real-prereqs) + +# Objcopy +# --------------------------------------------------------------------------- + +quiet_cmd_objcopy = OBJCOPY $@ +cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ + +# Gzip +# --------------------------------------------------------------------------- + +quiet_cmd_gzip = GZIP $@ + cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@ + +# Bzip2 +# --------------------------------------------------------------------------- + +# Bzip2 and LZMA do not include size in file... so we have to fake that; +# append the size as a 32-bit littleendian number as gzip does. +size_append = printf $(shell \ +dec_size=0; \ +for F in $(real-prereqs); do \ + fsize=$$($(CONFIG_SHELL) $(srctree)/scripts/file-size.sh $$F); \ + dec_size=$$(expr $$dec_size + $$fsize); \ +done; \ +printf "%08x\n" $$dec_size | \ + sed 's/\(..\)/\1 /g' | { \ + read ch0 ch1 ch2 ch3; \ + for ch in $$ch3 $$ch2 $$ch1 $$ch0; do \ + printf '%s%03o' '\\' $$((0x$$ch)); \ + done; \ + } \ +) + +quiet_cmd_file_size = GEN $@ + cmd_file_size = $(size_append) > $@ + +quiet_cmd_bzip2 = BZIP2 $@ + cmd_bzip2 = cat $(real-prereqs) | $(KBZIP2) -9 > $@ + +quiet_cmd_bzip2_with_size = BZIP2 $@ + cmd_bzip2_with_size = { cat $(real-prereqs) | $(KBZIP2) -9; $(size_append); } > $@ + +# Lzma +# --------------------------------------------------------------------------- + +quiet_cmd_lzma = LZMA $@ + cmd_lzma = cat $(real-prereqs) | $(LZMA) -9 > $@ + +quiet_cmd_lzma_with_size = LZMA $@ + cmd_lzma_with_size = { cat $(real-prereqs) | $(LZMA) -9; $(size_append); } > $@ + +quiet_cmd_lzo = LZO $@ + cmd_lzo = cat $(real-prereqs) | $(KLZOP) -9 > $@ + +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 -9 - - > $@ + +quiet_cmd_lz4_with_size = LZ4 $@ + cmd_lz4_with_size = { cat $(real-prereqs) | $(LZ4) -l -9 - -; \ + $(size_append); } > $@ + +# U-Boot mkimage +# --------------------------------------------------------------------------- + +MKIMAGE := $(srctree)/scripts/mkuboot.sh + +# SRCARCH just happens to match slightly more than ARCH (on sparc), so reduces +# the number of overrides in arch makefiles +UIMAGE_ARCH ?= $(SRCARCH) +UIMAGE_COMPRESSION ?= $(or $(2),none) +UIMAGE_OPTS-y ?= +UIMAGE_TYPE ?= kernel +UIMAGE_LOADADDR ?= arch_must_set_this +UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR) +UIMAGE_NAME ?= Linux-$(KERNELRELEASE) + +quiet_cmd_uimage = UIMAGE $@ + cmd_uimage = $(BASH) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \ + -C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \ + -T $(UIMAGE_TYPE) \ + -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)' $(FIT_EXTRA_ARGS) \ + $(if $(findstring 1,$(KBUILD_VERBOSE)),-v) \ + $(if $(FIT_DECOMPOSE_DTBS),--decompose-dtbs) \ + --compress $(FIT_COMPRESSION) -k $< @$(word 2,$^) + +# XZ +# --------------------------------------------------------------------------- +# 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_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. +# +# xzmisc doesn't use size_append, so it can be used to create normal .xz +# files. xzmisc uses smaller LZMA2 dictionary than xzkern, because a very +# big dictionary would increase the memory usage too much in the multi-call +# decompression mode. A BCJ filter isn't used either. +quiet_cmd_xzkern = XZKERN $@ + cmd_xzkern = cat $(real-prereqs) | sh $(srctree)/scripts/xz_wrap.sh > $@ + +quiet_cmd_xzkern_with_size = XZKERN $@ + cmd_xzkern_with_size = { cat $(real-prereqs) | sh $(srctree)/scripts/xz_wrap.sh; \ + $(size_append); } > $@ + +quiet_cmd_xzmisc = XZMISC $@ + cmd_xzmisc = cat $(real-prereqs) | $(XZ) --check=crc32 --lzma2=dict=1MiB > $@ + +# ZSTD +# --------------------------------------------------------------------------- +# Appends the uncompressed size of the data using size_append. The .zst +# format has the size information available at the beginning of the file too, +# but it's in a 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 zstd tool think that +# the file is corrupt. This is expected. +# +# zstd uses a maximum window size of 8 MB. zstd22 uses a maximum window size of +# 128 MB. zstd22 is used for kernel compression because it is decompressed in a +# single pass, so zstd doesn't need to allocate a window buffer. When streaming +# decompression is used, like initramfs decompression, zstd22 should likely not +# be used because it would require zstd to allocate a 128 MB buffer. + +quiet_cmd_zstd = ZSTD $@ + cmd_zstd = cat $(real-prereqs) | $(ZSTD) -19 > $@ + +quiet_cmd_zstd22 = ZSTD22 $@ + cmd_zstd22 = cat $(real-prereqs) | $(ZSTD) -22 --ultra > $@ + +quiet_cmd_zstd22_with_size = ZSTD22 $@ + cmd_zstd22_with_size = { cat $(real-prereqs) | $(ZSTD) -22 --ultra; $(size_append); } > $@ + +# ASM offsets +# --------------------------------------------------------------------------- + +# Default sed regexp - multiline due to syntax constraints +# +# Use [:space:] because LLVM's integrated assembler inserts around +# the .ascii directive whereas GCC keeps the as-is. +define sed-offsets + 's:^[[:space:]]*\.ascii[[:space:]]*"\(.*\)".*:\1:; \ + /^->/{s:->#\(.*\):/* \1 */:; \ + s:^->\([^ ]*\) [\$$#]*\([^ ]*\) \(.*\):#define \1 \2 /* \3 */:; \ + s:->::; p;}' +endef + +# Use filechk to avoid rebuilds when a header changes, but the resulting file +# does not +define filechk_offsets + echo "#ifndef $2"; \ + echo "#define $2"; \ + echo "/*"; \ + echo " * DO NOT MODIFY."; \ + echo " *"; \ + echo " * This file was generated by Kbuild"; \ + echo " */"; \ + echo ""; \ + sed -ne $(sed-offsets) < $<; \ + echo ""; \ + echo "#endif" +endef diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal new file mode 100644 index 000000000..01a37ec87 --- /dev/null +++ b/scripts/Makefile.modfinal @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0-only +# =========================================================================== +# Module final link +# =========================================================================== + +PHONY := __modfinal +__modfinal: + +include $(objtree)/include/config/auto.conf +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.lib + +# find all modules listed in modules.order +modules := $(call read-file, modules.order) + +__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 +GCOV_PROFILE := n +ccflags-remove-y := $(CC_FLAGS_CFI) + +%.mod.o: %.mod.c FORCE + $(call if_changed_rule,cc_o_c) + +.module-common.o: $(srctree)/scripts/module-common.c FORCE + $(call if_changed_rule,cc_o_c) + +ifneq ($(WARN_ON_UNUSED_TRACEPOINTS),) +cmd_check_tracepoint = $(objtree)/scripts/tracepoint-update --module $<; +endif + +quiet_cmd_ld_ko_o = LD [M] $@ + cmd_ld_ko_o = \ + $(LD) -r $(KBUILD_LDFLAGS) \ + $(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \ + -T $(objtree)/scripts/module.lds -o $@ $(filter %.o, $^) + +quiet_cmd_btf_ko = BTF [M] $@ + cmd_btf_ko = \ + if [ ! -f $(objtree)/vmlinux ]; then \ + printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \ + else \ + $(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/vmlinux $@; \ + fi; + +# Re-generate module BTFs if either module's .ko or vmlinux changed +%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE + +$(call if_changed,ld_ko_o) +ifdef CONFIG_DEBUG_INFO_BTF_MODULES + +$(if $(newer-prereqs),$(call cmd,btf_ko)) +endif + +$(call cmd,check_tracepoint) + +targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o) .module-common.o + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst new file mode 100644 index 000000000..9ba45e5b3 --- /dev/null +++ b/scripts/Makefile.modinst @@ -0,0 +1,167 @@ +# SPDX-License-Identifier: GPL-2.0 +# ========================================================================== +# Installing modules +# ========================================================================== + +PHONY := __modinst +__modinst: + +include $(objtree)/include/config/auto.conf +include $(srctree)/scripts/Kbuild.include + +install-y := + +ifeq ($(KBUILD_EXTMOD)$(sign-only),) + +# remove the old directory and symlink +$(shell rm -fr $(MODLIB)/kernel $(MODLIB)/build) + +install-$(CONFIG_MODULES) += $(addprefix $(MODLIB)/, build modules.order) + +$(MODLIB)/build: FORCE + $(call cmd,symlink) + +quiet_cmd_symlink = SYMLINK $@ + cmd_symlink = ln -s $(CURDIR) $@ + +$(MODLIB)/modules.order: modules.order FORCE + $(call cmd,install_modorder) + +quiet_cmd_install_modorder = INSTALL $@ + cmd_install_modorder = sed 's:^\(.*\)\.o$$:kernel/\1.ko:' $< > $@ + +# Install modules.builtin(.modinfo,.ranges) even when CONFIG_MODULES is disabled. +install-y += $(addprefix $(MODLIB)/, modules.builtin modules.builtin.modinfo) + +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, modules.order) + +ifeq ($(KBUILD_EXTMOD),) +dst := $(MODLIB)/kernel +else +INSTALL_MOD_DIR ?= updates +dst := $(MODLIB)/$(INSTALL_MOD_DIR) +endif + +$(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 %.o, $(dst)/%.ko$(suffix-y), $(modules)) +install-$(CONFIG_MODULES) += $(modules) + +__modinst: $(install-y) + @: + +# +# Installation +# +quiet_cmd_install = INSTALL $@ + cmd_install = cp $< $@ + +# Strip +# +# INSTALL_MOD_STRIP, if defined, will cause modules to be stripped after they +# are installed. If INSTALL_MOD_STRIP is '1', then the default option +# --strip-debug will be used. Otherwise, INSTALL_MOD_STRIP value will be used +# as the options to the strip command. +ifdef INSTALL_MOD_STRIP + +ifeq ($(INSTALL_MOD_STRIP),1) +strip-option := --strip-debug +else +strip-option := $(INSTALL_MOD_STRIP) +endif + +quiet_cmd_strip = STRIP $@ + cmd_strip = $(STRIP) $(strip-option) $@ + +else + +quiet_cmd_strip = + cmd_strip = : + +endif + +# +# Signing +# Don't stop modules_install even if we can't sign external modules. +# +ifeq ($(filter pkcs11:%, $(CONFIG_MODULE_SIG_KEY)),) +sig-key := $(if $(wildcard $(CONFIG_MODULE_SIG_KEY)),,$(objtree)/)$(CONFIG_MODULE_SIG_KEY) +else +sig-key := $(CONFIG_MODULE_SIG_KEY) +endif +quiet_cmd_sign = SIGN $@ + cmd_sign = $(objtree)/scripts/sign-file $(CONFIG_MODULE_SIG_HASH) "$(sig-key)" $(objtree)/certs/signing_key.x509 $@ \ + $(if $(KBUILD_EXTMOD),|| true) + +ifeq ($(sign-only),) + +# During modules_install, modules are signed only when CONFIG_MODULE_SIG_ALL=y. +ifndef CONFIG_MODULE_SIG_ALL +quiet_cmd_sign := + cmd_sign := : +endif + +# Create necessary directories +$(foreach dir, $(sort $(dir $(install-y))), $(shell mkdir -p $(dir))) + +$(dst)/%.ko: %.ko FORCE + $(call cmd,install) + $(call cmd,strip) + $(call cmd,sign) + +ifdef CONFIG_MODULES +__modinst: depmod + +PHONY += depmod +depmod: $(install-y) + $(call cmd,depmod) + +quiet_cmd_depmod = DEPMOD $(MODLIB) + cmd_depmod = $(srctree)/scripts/depmod.sh $(KERNELRELEASE) +endif + +else + +$(dst)/%.ko: FORCE + $(call cmd,sign) + +endif + +# +# Compression +# +quiet_cmd_gzip = GZIP $@ + cmd_gzip = $(KGZIP) -n -f $< +quiet_cmd_xz = XZ $@ + cmd_xz = $(XZ) --check=crc32 --lzma2=dict=1MiB -f $< +quiet_cmd_zstd = ZSTD $@ + cmd_zstd = $(ZSTD) --rm -f -q $< + +$(dst)/%.ko.gz: $(dst)/%.ko FORCE + $(call cmd,gzip) + +$(dst)/%.ko.xz: $(dst)/%.ko FORCE + $(call cmd,xz) + +$(dst)/%.ko.zst: $(dst)/%.ko FORCE + $(call cmd,zstd) + +PHONY += FORCE +FORCE: + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.modpost b/scripts/Makefile.modpost new file mode 100644 index 000000000..d7d45067d --- /dev/null +++ b/scripts/Makefile.modpost @@ -0,0 +1,157 @@ +# SPDX-License-Identifier: GPL-2.0 +# =========================================================================== +# Module versions +# =========================================================================== +# +# Stage one of module building created the following: +# a) The individual .o files used for the module +# b) A .o file which is the .o files above linked together +# c) A .mod file, listing the name of the preliminary .o file, +# plus all .o files +# d) modules.order, which lists all the modules + +# Stage 2 is handled by this file and does the following +# 1) Find all modules listed in modules.order +# 2) modpost is then used to +# 3) create one .mod.c file per module +# 4) create one Module.symvers file with CRC for all exported symbols + +# Step 3 is used to place certain information in the module's ELF +# section, including information such as: +# Version magic (see include/linux/vermagic.h for full details) +# - Kernel release +# - SMP is CONFIG_SMP +# - PREEMPT is CONFIG_PREEMPT[_RT] +# - GCC Version +# Module info +# - Module version (MODULE_VERSION) +# - Module alias'es (MODULE_ALIAS) +# - Module license (MODULE_LICENSE) +# - See include/linux/module.h for more details + +# Step 4 is solely used to allow module versioning in external modules, +# where the CRC of each module is retrieved from the Module.symvers file. + +PHONY := __modpost +__modpost: + +include $(objtree)/include/config/auto.conf +include $(srctree)/scripts/Kbuild.include + +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 $(CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS)$(KBUILD_NSDEPS),-N) \ + $(if $(findstring 1, $(KBUILD_EXTRA_WARN)),-W) \ + -o $@ + +modpost-deps := $(MODPOST) + +# 'make -i -k' ignores compile errors, and builds as many modules as possible. +ifneq ($(findstring i,$(filter-out --%,$(MAKEFLAGS))),) +modpost-args += -n +endif + +# Read out modules.order to pass in modpost. +# Otherwise, allmodconfig would fail with "Argument list too long". +ifdef KBUILD_MODULES +modpost-args += -T modules.order +modpost-deps += modules.order +endif + +ifeq ($(KBUILD_EXTMOD),) + +# Generate the list of in-tree objects in vmlinux +# --------------------------------------------------------------------------- + +# This is used to retrieve symbol versions generated by genksyms. +ifdef CONFIG_MODVERSIONS +vmlinux.symvers Module.symvers: .vmlinux.objs +endif + +# Ignore libgcc.a +# Some architectures do '$(CC) --print-libgcc-file-name' to borrow libgcc.a +# from the toolchain, but there is no EXPORT_SYMBOL in it. + +quiet_cmd_vmlinux_objs = GEN $@ + cmd_vmlinux_objs = \ + for f in $(real-prereqs); do \ + case $${f} in \ + *libgcc.a) ;; \ + *) $(AR) t $${f} ;; \ + esac \ + done > $@ + +targets += .vmlinux.objs +.vmlinux.objs: vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE + $(call if_changed,vmlinux_objs) + +ifdef CONFIG_TRIM_UNUSED_KSYMS +ksym-wl := $(CONFIG_UNUSED_KSYMS_WHITELIST) +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 + +ifeq ($(wildcard vmlinux.o),) +missing-input := vmlinux.o +output-symdump := modules-only.symvers +else +modpost-args += vmlinux.o +modpost-deps += vmlinux.o +output-symdump := $(if $(KBUILD_MODULES), Module.symvers, vmlinux.symvers) +endif + +else + +# set src + obj - they may be used in the modules's Makefile +obj := . +src := $(srcroot) + +# Include the module's Makefile to find KBUILD_EXTRA_SYMBOLS +include $(kbuild-file) + +output-symdump := Module.symvers + +ifeq ($(wildcard $(objtree)/Module.symvers),) +missing-input := $(objtree)/Module.symvers +else +modpost-args += -i $(objtree)/Module.symvers +modpost-deps += $(objtree)/Module.symvers +endif + +modpost-args += -e $(addprefix -i , $(KBUILD_EXTRA_SYMBOLS)) + +endif # ($(KBUILD_EXTMOD),) + +quiet_cmd_modpost = MODPOST $@ + cmd_modpost = \ + $(if $(missing-input), \ + echo >&2 "WARNING: $(missing-input) is missing."; \ + echo >&2 " Modules may not have dependencies or modversions."; \ + echo >&2 " You may get many unresolved symbol errors."; \ + echo >&2 " You can set KBUILD_MODPOST_WARN=1 to turn errors into warning"; \ + echo >&2 " if you want to proceed at your own risk.";) \ + $(MODPOST) $(modpost-args) + +targets += $(output-symdump) +$(output-symdump): $(modpost-deps) FORCE + $(call if_changed,modpost) + +__modpost: $(output-symdump) +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.package b/scripts/Makefile.package new file mode 100644 index 000000000..6d36786ba --- /dev/null +++ b/scripts/Makefile.package @@ -0,0 +1,286 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for the different targets used to generate full packages of a kernel + +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.lib + +# Git +# --------------------------------------------------------------------------- + +filechk_HEAD = git -C $(srctree) rev-parse --verify HEAD 2>/dev/null + +.tmp_HEAD: check-git FORCE + $(call filechk,HEAD) + +PHONY += check-git +check-git: + @if ! $(srctree)/scripts/check-git; then \ + echo >&2 "error: creating source package requires git repository"; \ + false; \ + fi + +git-config-tar.gz = -c tar.tar.gz.command="$(KGZIP)" +git-config-tar.bz2 = -c tar.tar.bz2.command="$(KBZIP2)" +git-config-tar.lzma = -c tar.tar.lzma.command="$(LZMA)" +git-config-tar.xz = -c tar.tar.xz.command="$(XZ)" +git-config-tar.zst = -c tar.tar.zst.command="$(ZSTD)" + +quiet_cmd_archive = ARCHIVE $@ + cmd_archive = git -C $(srctree) $(git-config-tar$(suffix $@)) archive \ + --output=$$(realpath $@) $(archive-args) + +suffix-gzip := .gz +suffix-bzip2 := .bz2 +suffix-lzma := .lzma +suffix-xz := .xz + +# Linux source tarball +# --------------------------------------------------------------------------- + +linux-tarballs := $(addprefix linux, .tar.gz .tar.bz2 .tar.lzma .tar.xz) + +targets += $(linux-tarballs) +$(linux-tarballs): archive-args = --prefix=linux/ $$(cat $<) +$(linux-tarballs): .tmp_HEAD FORCE + $(call if_changed,archive) + +# rpm-pkg srcrpm-pkg binrpm-pkg +# --------------------------------------------------------------------------- + +quiet_cmd_mkspec = GEN $@ + cmd_mkspec = $(srctree)/scripts/package/mkspec $@ + +rpmbuild/SPECS/kernel.spec: FORCE + $(call cmd,mkspec) + +PHONY += rpm-sources +rpm-sources: linux.tar.gz + $(Q)mkdir -p rpmbuild/SOURCES + $(Q)ln -f linux.tar.gz rpmbuild/SOURCES/linux.tar.gz + $(Q)cp $(KCONFIG_CONFIG) rpmbuild/SOURCES/config + $(Q)$(srctree)/scripts/package/gen-diff-patch rpmbuild/SOURCES/diff.patch + +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 + +rpm-pkg srcrpm-pkg: rpm-sources +rpm-pkg srcrpm-pkg binrpm-pkg: rpmbuild/SPECS/kernel.spec + +$(strip rpmbuild -b$(build-type) 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-no-devel)) \ + $(RPMOPTS)) + +# deb-pkg srcdeb-pkg bindeb-pkg +# --------------------------------------------------------------------------- + +KDEB_SOURCE_COMPRESS ?= gzip + +supported-deb-source-compress := gzip bzip2 lzma xz + +PHONY += linux.tar.unsupported-deb-src-compress +linux.tar.unsupported-deb-src-compress: + @echo "error: KDEB_SOURCE_COMPRESS=$(KDEB_SOURCE_COMPRESS) is not supported. The supported values are: $(supported-deb-source-compress)" >&2 + @false + +debian-orig-suffix := \ + $(strip $(if $(filter $(supported-deb-source-compress), $(KDEB_SOURCE_COMPRESS)), \ + $(suffix-$(KDEB_SOURCE_COMPRESS)),.unsupported-deb-src-compress)) + +quiet_cmd_debianize = GEN $@ + cmd_debianize = $(srctree)/scripts/package/mkdebian $(mkdebian-opts) + +debian: FORCE + $(call cmd,debianize) + +PHONY += debian-orig +debian-orig: private source = $(shell dpkg-parsechangelog -S Source) +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 \ + ln -f $< ../$(orig-name); \ + else \ + cp $< ../$(orig-name); \ + fi + +PHONY += deb-pkg srcdeb-pkg bindeb-pkg + +deb-pkg: private build-type := source,binary +srcdeb-pkg: private build-type := source +bindeb-pkg: private build-type := binary + +deb-pkg srcdeb-pkg: debian-orig +bindeb-pkg: debian +deb-pkg srcdeb-pkg bindeb-pkg: + +$(strip dpkg-buildpackage \ + --build=$(build-type) --no-pre-clean --unsigned-changes \ + $(if $(findstring source, $(build-type)), \ + --unsigned-source --compression=$(KDEB_SOURCE_COMPRESS)) \ + $(if $(findstring binary, $(build-type)), \ + -R'$(MAKE) -f debian/rules' -j1 -a$$(cat debian/arch), \ + --no-check-builddeps) \ + $(DPKG_FLAGS)) + +# snap-pkg +# --------------------------------------------------------------------------- +PHONY += snap-pkg +snap-pkg: + rm -rf $(objtree)/snap + mkdir $(objtree)/snap + $(MAKE) clean + sed "s@KERNELRELEASE@$(KERNELRELEASE)@; \ + s@SRCTREE@$(realpath $(srctree))@" \ + $(srctree)/scripts/package/snapcraft.template > \ + $(objtree)/snap/snapcraft.yaml + 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 +# --------------------------------------------------------------------------- + +tar-install: FORCE + $(Q)$(MAKE) -f $(srctree)/Makefile + +$(Q)$(srctree)/scripts/package/buildtar $@ + +compress-tar.gz = -I "$(KGZIP)" +compress-tar.bz2 = -I "$(KBZIP2)" +compress-tar.xz = -I "$(XZ)" +compress-tar.zst = -I "$(ZSTD)" + +quiet_cmd_tar = TAR $@ + cmd_tar = cd $<; tar cf ../$@ $(compress-tar$(suffix $@)) --owner=root --group=root --sort=name * + +dir-tarballs := $(addprefix linux-$(KERNELRELEASE)-$(ARCH), .tar .tar.gz .tar.bz2 .tar.xz .tar.zst) + +$(dir-tarballs): tar-install + $(call cmd,tar) + +PHONY += dir-pkg +dir-pkg: tar-install + @echo "Kernel tree successfully created in $<" + +PHONY += tar-pkg +tar-pkg: linux-$(KERNELRELEASE)-$(ARCH).tar + @: + +tar%-pkg: linux-$(KERNELRELEASE)-$(ARCH).tar.% FORCE + @: + +# modules-cpio-pkg - generate an initramfs with the modules +# --------------------------------------------------------------------------- + +.tmp_modules_cpio: FORCE + $(Q)$(MAKE) -f $(srctree)/Makefile + $(Q)rm -rf $@ + $(Q)$(MAKE) -f $(srctree)/Makefile INSTALL_MOD_PATH=$@/$(INSTALL_MOD_PATH) modules_install + +quiet_cmd_cpio = CPIO $@ + cmd_cpio = $(CONFIG_SHELL) $(srctree)/usr/gen_initramfs.sh -o $@ $< + +modules-$(KERNELRELEASE)-$(ARCH).cpio: .tmp_modules_cpio + $(call cmd,cpio) + +PHONY += modules-cpio-pkg +modules-cpio-pkg: modules-$(KERNELRELEASE)-$(ARCH).cpio + @: + +# perf-tar*-src-pkg - generate a source tarball with perf source +# --------------------------------------------------------------------------- + +.tmp_perf: + $(Q)mkdir .tmp_perf + +.tmp_perf/HEAD: .tmp_HEAD | .tmp_perf + $(call cmd,copy) + +quiet_cmd_perf_version_file = GEN $@ + cmd_perf_version_file = cd $(srctree)/tools/perf; util/PERF-VERSION-GEN $(dir $(abspath $@)) + +# PERF-VERSION-FILE and .tmp_HEAD are independent, but this avoids updating the +# timestamp of PERF-VERSION-FILE. +# The best is to fix tools/perf/util/PERF-VERSION-GEN. +.tmp_perf/PERF-VERSION-FILE: .tmp_HEAD $(srctree)/tools/perf/util/PERF-VERSION-GEN | .tmp_perf + $(call cmd,perf_version_file) + +perf-archive-args = --add-file=$$(realpath $(word 2, $^)) \ + --add-file=$$(realpath $(word 3, $^)) \ + $$(cat $(word 2, $^))^{tree} $$(cat $<) + + +perf-tarballs := $(addprefix perf-$(KERNELVERSION), .tar .tar.gz .tar.bz2 .tar.xz .tar.zst) + +targets += $(perf-tarballs) +$(perf-tarballs): archive-args = --prefix=perf-$(KERNELVERSION)/ $(perf-archive-args) +$(perf-tarballs): tools/perf/MANIFEST .tmp_perf/HEAD .tmp_perf/PERF-VERSION-FILE FORCE + $(call if_changed,archive) + +PHONY += perf-tar-src-pkg +perf-tar-src-pkg: perf-$(KERNELVERSION).tar + @: + +perf-tar%-src-pkg: perf-$(KERNELVERSION).tar.% FORCE + @: + +# Help text displayed when executing 'make help' +# --------------------------------------------------------------------------- +PHONY += help +help: + @echo ' rpm-pkg - Build both source and binary RPM kernel packages' + @echo ' srcrpm-pkg - Build only the source kernel RPM package' + @echo ' binrpm-pkg - Build only the binary kernel RPM package' + @echo ' deb-pkg - Build both source and binary deb kernel packages' + @echo ' srcdeb-pkg - Build only the source kernel deb package' + @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' + @echo ' tarbz2-pkg - Build the kernel as a bzip2 compressed tarball' + @echo ' tarxz-pkg - Build the kernel as a xz compressed tarball' + @echo ' tarzst-pkg - Build the kernel as a zstd compressed tarball' + @echo ' modules-cpio-pkg - Build the kernel modules as cpio archive' + @echo ' (uses INSTALL_MOD_PATH inside the archive)' + @echo ' perf-tar-src-pkg - Build the perf source tarball with no compression' + @echo ' perf-targz-src-pkg - Build the perf source tarball with gzip compression' + @echo ' perf-tarbz2-src-pkg - Build the perf source tarball with bz2 compression' + @echo ' perf-tarxz-src-pkg - Build the perf source tarball with xz compression' + @echo ' perf-tarzst-src-pkg - Build the perf source tarball with zst compression' + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.propeller b/scripts/Makefile.propeller new file mode 100644 index 000000000..48a660128 --- /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.randstruct b/scripts/Makefile.randstruct new file mode 100644 index 000000000..24e283e89 --- /dev/null +++ b/scripts/Makefile.randstruct @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +randstruct-cflags-y += -DRANDSTRUCT + +ifdef CONFIG_GCC_PLUGIN_RANDSTRUCT +randstruct-cflags-y \ + += -fplugin=$(objtree)/scripts/gcc-plugins/randomize_layout_plugin.so +randstruct-cflags-$(CONFIG_RANDSTRUCT_PERFORMANCE) \ + += -fplugin-arg-randomize_layout_plugin-performance-mode +else +randstruct-cflags-y \ + += -frandomize-layout-seed-file=$(objtree)/scripts/basic/randstruct.seed +endif + +export RANDSTRUCT_CFLAGS := $(randstruct-cflags-y) + +KBUILD_CFLAGS += $(RANDSTRUCT_CFLAGS) diff --git a/scripts/Makefile.thinlto b/scripts/Makefile.thinlto new file mode 100644 index 000000000..bb83f13f3 --- /dev/null +++ b/scripts/Makefile.thinlto @@ -0,0 +1,40 @@ +PHONY := __default +__default: + +include include/config/auto.conf +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.lib + +native-objs := $(patsubst %.o,%.thinlto-native.o,$(call read-file, vmlinux.thinlto-index)) + +__default: $(native-objs) + +# Generate .thinlto-native.o (obj) from .o (bitcode) and .thinlto.bc (summary) files +# --------------------------------------------------------------------------- +quiet_cmd_cc_o_bc = CC $(quiet_modtag) $@ + be_flags = $(shell sed -n '/saved_c_flags_/s/.*:= //p' \ + $(dir $(<)).$(notdir $(<)).cmd) + cmd_cc_o_bc = \ + $(CC) $(be_flags) -x ir -fno-lto -Wno-unused-command-line-argument \ + -fthinlto-index=$(word 2, $^) -c -o $@ $< + +targets += $(native-objs) +$(native-objs): %.thinlto-native.o: %.o %.o.thinlto.bc FORCE + $(call if_changed,cc_o_bc) + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f, $(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.ubsan b/scripts/Makefile.ubsan new file mode 100644 index 000000000..734a102e6 --- /dev/null +++ b/scripts/Makefile.ubsan @@ -0,0 +1,28 @@ +# 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 +ubsan-cflags-$(CONFIG_UBSAN_ARRAY_BOUNDS) += -fsanitize=array-bounds +ubsan-cflags-$(CONFIG_UBSAN_LOCAL_BOUNDS) += -fsanitize=local-bounds +ubsan-cflags-$(CONFIG_UBSAN_SHIFT) += -fsanitize=shift +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) += $(CFLAGS_UBSAN_TRAP) + +export CFLAGS_UBSAN := $(ubsan-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.userprogs b/scripts/Makefile.userprogs new file mode 100644 index 000000000..f3a7e1ef3 --- /dev/null +++ b/scripts/Makefile.userprogs @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Build userspace programs for the target system +# + +# Executables compiled from a single .c file +user-csingle := $(foreach m, $(userprogs), $(if $($(m)-objs),,$(m))) + +# Executables linked based on several .o files +user-cmulti := $(foreach m, $(userprogs), $(if $($(m)-objs),$(m))) + +# Objects compiled from .c files +user-cobjs := $(sort $(foreach m, $(userprogs), $($(m)-objs))) + +user-csingle := $(addprefix $(obj)/, $(user-csingle)) +user-cmulti := $(addprefix $(obj)/, $(user-cmulti)) +user-cobjs := $(addprefix $(obj)/, $(user-cobjs)) + +user_ccflags = -Wp,-MMD,$(depfile) $(KBUILD_USERCFLAGS) $(userccflags) \ + $($(target-stem)-userccflags) +user_ldflags = $(KBUILD_USERLDFLAGS) $(userldflags) $($(target-stem)-userldflags) +user_ldlibs = $(userldlibs) $($(target-stem)-userldlibs) + +# Create an executable from a single .c file +quiet_cmd_user_cc_c = CC [U] $@ + cmd_user_cc_c = $(CC) $(user_ccflags) $(user_ldflags) -o $@ $< \ + $(user_ldlibs) +$(user-csingle): $(obj)/%: $(src)/%.c FORCE + $(call if_changed_dep,user_cc_c) + +# Link an executable based on list of .o files +quiet_cmd_user_ld = LD [U] $@ + cmd_user_ld = $(CC) $(user_ldflags) -o $@ \ + $(addprefix $(obj)/, $($(target-stem)-objs)) $(user_ldlibs) +$(user-cmulti): FORCE + $(call if_changed,user_ld) +$(call multi_depend, $(user-cmulti), , -objs) + +# Create .o file from a .c file +quiet_cmd_user_cc_o_c = CC [U] $@ + cmd_user_cc_o_c = $(CC) $(user_ccflags) -c -o $@ $< +$(user-cobjs): $(obj)/%.o: $(src)/%.c FORCE + $(call if_changed_dep,user_cc_o_c) + +targets += $(user-csingle) $(user-cmulti) $(user-cobjs) diff --git a/scripts/Makefile.vdsoinst b/scripts/Makefile.vdsoinst new file mode 100644 index 000000000..d9f724321 --- /dev/null +++ b/scripts/Makefile.vdsoinst @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only +# ========================================================================== +# Install unstripped copies of vDSO +# ========================================================================== + +PHONY := __default +__default: + @: + +include $(srctree)/scripts/Kbuild.include + +install-dir := $(MODLIB)/vdso + +define gen_install_rules + +dest := $(install-dir)/$$(patsubst %.dbg,%,$$(notdir $(1))) + +__default: $$(dest) +$$(dest): $(1) FORCE + $$(call cmd,install) + +build-id-file := $$(shell $(READELF) -n $(1) 2>/dev/null | sed -n 's@^.*Build ID: \(..\)\(.*\)@\1/\2@p') + +ifneq ($$(build-id-file),) +link := $(install-dir)/.build-id/$$(build-id-file).debug + +__default: $$(link) +$$(link): $$(dest) FORCE + $$(call cmd,symlink) +endif + +endef + +$(foreach x, $(sort $(INSTALL_FILES)), $(eval $(call gen_install_rules,$(x)))) + +quiet_cmd_install = INSTALL $@ + cmd_install = mkdir -p $(dir $@); cp $< $@ + +quiet_cmd_symlink = SYMLINK $@ + cmd_symlink = mkdir -p $(dir $@); ln -sf --relative $< $@ + +PHONY += FORCE +FORCE: + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux new file mode 100644 index 000000000..fcae1e432 --- /dev/null +++ b/scripts/Makefile.vmlinux @@ -0,0 +1,175 @@ +# SPDX-License-Identifier: GPL-2.0-only + +PHONY := __default +__default: vmlinux + +include include/config/auto.conf +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.lib + +targets := + +%.o: %.c FORCE + $(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 '; \ + 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.unstripped: .builtin-dtbs.o +endif + +# vmlinux.unstripped +# --------------------------------------------------------------------------- + +ifdef CONFIG_ARCH_WANTS_PRE_LINK_VMLINUX +vmlinux.unstripped: 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)" "$@"; \ + $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) + +targets += vmlinux.unstripped .vmlinux.export.o +vmlinux.unstripped: scripts/link-vmlinux.sh vmlinux.o .vmlinux.export.o $(KBUILD_LDS) FORCE + +$(call if_changed_dep,link_vmlinux) +ifdef CONFIG_DEBUG_INFO_BTF +vmlinux.unstripped: $(RESOLVE_BTFIDS) $(srctree)/scripts/gen-btf.sh +endif + +ifdef CONFIG_BUILDTIME_TABLE_SORT +vmlinux.unstripped: scripts/sorttable +endif + +# vmlinux +# --------------------------------------------------------------------------- + +remove-section-y := .modinfo +remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS) += '.rel*' '!.rel*.dyn' +# for compatibility with binutils < 2.32 +# https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=c12d9fa2afe7abcbe407a00e15719e1a1350c2a7 +remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS) += '.rel.*' + +remove-symbols := -w --strip-unneeded-symbol='__mod_device_table__*' + +# To avoid warnings: "empty loadable segment detected at ..." from GNU objcopy, +# it is necessary to remove the PT_LOAD flag from the segment. +quiet_cmd_strip_relocs = OBJCOPY $@ + cmd_strip_relocs = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \ + $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@ + +targets += vmlinux +vmlinux: vmlinux.unstripped FORCE + $(call if_changed,strip_relocs) + +# modules.builtin.modinfo +# --------------------------------------------------------------------------- + +# .modinfo in vmlinux.unstripped is aligned to 8 bytes for compatibility with +# tools that expect vmlinux to have sufficiently aligned sections but the +# additional bytes used for padding .modinfo to satisfy this requirement break +# certain versions of kmod with +# +# depmod: ERROR: kmod_builtin_iter_next: unexpected string without modname prefix +# +# Strip the trailing padding bytes after extracting .modinfo to comply with +# what kmod expects to parse. +quiet_cmd_modules_builtin_modinfo = GEN $@ + cmd_modules_builtin_modinfo = $(cmd_objcopy); \ + sed -i 's/\x00\+$$/\x00/g' $@; \ + chmod -x $@ + +OBJCOPYFLAGS_modules.builtin.modinfo := -j .modinfo -O binary + +targets += modules.builtin.modinfo +modules.builtin.modinfo: vmlinux.unstripped FORCE + $(call if_changed,modules_builtin_modinfo) + +# modules.builtin +# --------------------------------------------------------------------------- + +__default: modules.builtin + +# The second line aids cases where multiple modules share the same object. + +quiet_cmd_modules_builtin = GEN $@ + cmd_modules_builtin = \ + tr '\0' '\n' < $< | \ + sed -n 's/^[[:alnum:]:_]*\.file=//p' | \ + tr ' ' '\n' | uniq | sed -e 's:^:kernel/:' -e 's/$$/.ko/' > $@ + +targets += modules.builtin +modules.builtin: modules.builtin.modinfo FORCE + $(call if_changed,modules_builtin) + +# 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.unstripped + @: + +endif + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.vmlinux_a b/scripts/Makefile.vmlinux_a new file mode 100644 index 000000000..395e29998 --- /dev/null +++ b/scripts/Makefile.vmlinux_a @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: GPL-2.0-only + +PHONY := __default +__default: vmlinux.a + +include include/config/auto.conf +include $(srctree)/scripts/Kbuild.include +include $(srctree)/scripts/Makefile.lib + +# Link of built-in-fixup.a +# --------------------------------------------------------------------------- + +quiet_cmd_ar_builtin_fixup = AR $@ + cmd_ar_builtin_fixup = \ + rm -f $@; \ + $(AR) cDPrST $@ $(KBUILD_VMLINUX_OBJS); \ + $(AR) mPi $$($(AR) t $@ | sed -n 1p) $@ $$($(AR) t $@ | grep -F -f $(srctree)/scripts/head-object-list.txt) + +targets += built-in-fixup.a +built-in-fixup.a: $(KBUILD_VMLINUX_OBJS) scripts/head-object-list.txt FORCE + $(call if_changed,ar_builtin_fixup) + +ifdef CONFIG_LTO_CLANG_THIN_DIST + +quiet_cmd_builtin.order = GEN $@ + cmd_builtin.order = $(AR) t $< > $@ + +targets += builtin.order +builtin.order: built-in-fixup.a FORCE + $(call if_changed,builtin.order) + +quiet_cmd_ld_thinlto_index = LD $@ + cmd_ld_thinlto_index = \ + $(LD) $(KBUILD_LDFLAGS) -r --thinlto-index-only=$@ @$< + +targets += vmlinux.thinlto-index +vmlinux.thinlto-index: builtin.order FORCE + $(call if_changed,ld_thinlto_index) + +quiet_cmd_ar_vmlinux.a = GEN $@ + cmd_ar_vmlinux.a = \ + rm -f $@; \ + while read -r obj; do \ + if grep -Fqx $${obj} $(word 2, $^); then \ + echo $${obj%.o}.thinlto-native.o; \ + else \ + echo $${obj}; \ + fi; \ + done < $< | xargs $(AR) cDPrS --thin $@ + +targets += vmlinux.a +vmlinux.a: builtin.order vmlinux.thinlto-index FORCE + $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.thinlto + $(call if_changed,ar_vmlinux.a) + +else + +# vmlinux.a +# --------------------------------------------------------------------------- + +targets += vmlinux.a +vmlinux.a: built-in-fixup.a FORCE + $(call if_changed,copy) + +endif + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o new file mode 100644 index 000000000..24a3a4fd2 --- /dev/null +++ b/scripts/Makefile.vmlinux_o @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: GPL-2.0-only + +PHONY := __default +__default: vmlinux.o + +include include/config/auto.conf +include $(srctree)/scripts/Kbuild.include + +# for objtool +include $(srctree)/scripts/Makefile.lib + +# Generate a linker script to ensure correct ordering of initcalls for Clang LTO +# --------------------------------------------------------------------------- + +quiet_cmd_gen_initcalls_lds = GEN $@ + cmd_gen_initcalls_lds = \ + $(PYTHON3) $(srctree)/scripts/jobserver-exec \ + $(PERL) $(real-prereqs) > $@ + +.tmp_initcalls.lds: $(srctree)/scripts/generate_initcall_order.pl \ + vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE + $(call if_changed,gen_initcalls_lds) + +targets := .tmp_initcalls.lds + +ifdef CONFIG_LTO_CLANG +initcalls-lds := .tmp_initcalls.lds +endif + +# objtool for vmlinux.o +# --------------------------------------------------------------------------- +# +# 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)) + +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) + +# Only used for builds initiated by klp-build +vmlinux-objtool-args-$(if $(KLP_SYMIDS),y) += --klp-symids + +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 \ + $(cmd_objtool) + +cmd_check_function_names = $(srctree)/scripts/check-function-names.sh $@ + +define rule_ld_vmlinux.o + $(call cmd_and_savecmd,ld_vmlinux.o) + $(call cmd,gen_objtooldep) + $(call cmd,check_function_names) +endef + + +vmlinux.o: $(initcalls-lds) vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE + $(call if_changed_rule,ld_vmlinux.o) + +targets += vmlinux.o + +# Add FORCE to the prerequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +existing-targets := $(wildcard $(sort $(targets))) + +-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd) + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.warn b/scripts/Makefile.warn new file mode 100644 index 000000000..35af7d6c6 --- /dev/null +++ b/scripts/Makefile.warn @@ -0,0 +1,219 @@ +# SPDX-License-Identifier: GPL-2.0 +# ========================================================================== +# make W=... settings +# +# There are four warning groups enabled by W=1, W=2, W=3, and W=e +# They are independent, and can be combined like W=12 or W=123e. +# ========================================================================== + +# 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 +KBUILD_CFLAGS += -Werror=return-type +KBUILD_CFLAGS += -Werror=strict-prototypes +KBUILD_CFLAGS += -Wno-format-security +KBUILD_CFLAGS += -Wno-trigraphs +KBUILD_CFLAGS += -Wno-frame-address +KBUILD_CFLAGS += $(call cc-option, -Wno-address-of-packed-member) +KBUILD_CFLAGS += -Wmissing-declarations +KBUILD_CFLAGS += -Wmissing-prototypes + +ifneq ($(CONFIG_FRAME_WARN),0) +KBUILD_CFLAGS += -Wframe-larger-than=$(CONFIG_FRAME_WARN) +endif + +KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-bounds + +ifdef CONFIG_CC_IS_CLANG +# 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 + +# Too noisy on range checks and in macros handling both signed and unsigned. +KBUILD_CFLAGS += -Wno-type-limits + +# These result in bogus false positives +KBUILD_CFLAGS += $(call cc-option, -Wno-dangling-pointer) + +# 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 + +# In order to make sure new function cast mismatches are not introduced +# in the kernel (to avoid tripping CFI checking), the kernel should be +# globally built with -Wcast-function-type. +KBUILD_CFLAGS += -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 +# of gcc (<9.1) weirdly don't handle the option correctly when _other_ +# warnings are produced (?!). Using -Walloc-size-larger-than=SIZE_MAX +# doesn't work (as it is documented to), silently resolving to "0" prior to +# version 9.1 (and producing an error more recently). Numeric values larger +# than PTRDIFF_MAX also don't work prior to version 9.1, which are silently +# ignored, continuing to default to PTRDIFF_MAX. So, left with no other +# choice, we must perform a versioned check to disable this warning. +# https://lore.kernel.org/lkml/20210824115859.187f272f@canb.auug.org.au +KBUILD_CFLAGS-$(call gcc-min-version, 90100) += -Wno-alloc-size-larger-than +KBUILD_CFLAGS += $(KBUILD_CFLAGS-y) $(CONFIG_CC_IMPLICIT_FALLTHROUGH) + +# Prohibit date/time macros, which would make the build non-deterministic +KBUILD_CFLAGS += -Werror=date-time + +# enforce correct pointer usage +KBUILD_CFLAGS += -Werror=incompatible-pointer-types + +# Require designated initializers for all marked structures +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 += -Wmissing-format-attribute +KBUILD_CFLAGS += -Wmissing-include-dirs +KBUILD_CFLAGS += -Wunused-const-variable + +KBUILD_CPPFLAGS += -Wundef +KBUILD_CPPFLAGS += -DKBUILD_EXTRA_WARN1 + +else + +# Some diagnostics enabled by default are noisy. +# Suppress them by using -Wno... except for W=1. +KBUILD_CFLAGS += -Wno-unused-but-set-variable +KBUILD_CFLAGS += -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 += -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 +KBUILD_CFLAGS += -Wno-pointer-to-enum-cast +KBUILD_CFLAGS += -Wno-tautological-constant-out-of-range-compare +KBUILD_CFLAGS += -Wno-unaligned-access +KBUILD_CFLAGS += -Wno-enum-compare-conditional +endif + +endif + +# +# W=2 - warnings which occur quite often but may still be relevant +# +ifneq ($(findstring 2, $(KBUILD_EXTRA_WARN)),) + +KBUILD_CFLAGS += -Wdisabled-optimization +KBUILD_CFLAGS += -Wshadow +KBUILD_CFLAGS += $(call cc-option, -Wlogical-op) +KBUILD_CFLAGS += -Wunused-macros + +KBUILD_CPPFLAGS += -DKBUILD_EXTRA_WARN2 + +else + +# The following turn off the warnings enabled by -Wextra +KBUILD_CFLAGS += -Wno-missing-field-initializers +KBUILD_CFLAGS += -Wno-shift-negative-value + +ifdef CONFIG_CC_IS_CLANG +KBUILD_CFLAGS += -Wno-enum-enum-conversion +endif + +ifdef CONFIG_CC_IS_GCC +KBUILD_CFLAGS += -Wno-maybe-uninitialized +endif + +endif + +# +# W=3 - more obscure warnings, can most likely be ignored +# +ifneq ($(findstring 3, $(KBUILD_EXTRA_WARN)),) + +KBUILD_CFLAGS += -Wbad-function-cast +KBUILD_CFLAGS += -Wcast-align +KBUILD_CFLAGS += -Wcast-qual +KBUILD_CFLAGS += -Wconversion +KBUILD_CFLAGS += -Wpacked +KBUILD_CFLAGS += -Wpadded +KBUILD_CFLAGS += -Wpointer-arith +KBUILD_CFLAGS += -Wredundant-decls +KBUILD_CFLAGS += -Wsign-compare +KBUILD_CFLAGS += -Wswitch-default + +KBUILD_CPPFLAGS += -DKBUILD_EXTRA_WARN3 + +else + +# The following turn off the warnings enabled by -Wextra +KBUILD_CFLAGS += -Wno-sign-compare +KBUILD_CFLAGS += -Wno-unused-parameter + +endif + +# +# W=e and CONFIG_WERROR - error out on warnings +# +ifneq ($(findstring e, $(KBUILD_EXTRA_WARN))$(CONFIG_WERROR),) + +KBUILD_CPPFLAGS += -Werror +KBUILD_AFLAGS += -Wa,--fatal-warnings +KBUILD_LDFLAGS += --fatal-warnings +KBUILD_USERCFLAGS += -Werror +KBUILD_USERLDFLAGS += -Wl,--fatal-warnings +KBUILD_RUSTFLAGS += -Dwarnings + +# While hostprog flags are used during build bootstrapping (thus should not +# depend on CONFIG_ symbols), -Werror is disruptive and should be opted into. +# Only apply -Werror to hostprogs built after the initial Kconfig stage. +KBUILD_HOSTCFLAGS += -Werror +KBUILD_HOSTLDFLAGS += -Wl,--fatal-warnings +KBUILD_HOSTRUSTFLAGS += -Dwarnings + +endif diff --git a/scripts/as-version.sh b/scripts/as-version.sh new file mode 100755 index 000000000..af7174761 --- /dev/null +++ b/scripts/as-version.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Print the assembler name and its version in a 5 or 6-digit form. +# Also, perform the minimum version check. +# (If it is the integrated assembler, return 0 as the version, and +# skip the version check.) + +set -e + +# Convert the version string x.y.z to a canonical 5 or 6-digit form. +get_canonical_version() +{ + IFS=. + set -- $1 + + # If the 2nd or 3rd field is missing, fill it with a zero. + # + # The 4th field, if present, is ignored. + # This occurs in development snapshots as in 2.35.1.20201116 + echo $((10000 * $1 + 100 * ${2:-0} + ${3:-0})) +} + +# Clang fails to handle -Wa,--version unless -fno-integrated-as is given. +# We check -fintegrated-as, expecting it is explicitly passed in for the +# integrated assembler case. +check_integrated_as() +{ + while [ $# -gt 0 ]; do + if [ "$1" = -fintegrated-as ]; then + # For the integrated assembler, we do not check the + # version here. It is the same as the clang version, and + # it has been already checked by scripts/cc-version.sh. + echo LLVM 0 + exit 0 + fi + shift + done +} + +check_integrated_as "$@" + +orig_args="$@" + +# Get the first line of the --version output. +IFS=' +' +set -- $(LC_ALL=C "$@" -Wa,--version -c -x assembler-with-cpp /dev/null -o /dev/null 2>/dev/null) + +# Split the line on spaces. +IFS=' ' +set -- $1 + +min_tool_version=$(dirname $0)/min-tool-version.sh + +if [ "$1" = GNU -a "$2" = assembler ]; then + shift $(($# - 1)) + version=$1 + min_version=$($min_tool_version binutils) + name=GNU +else + echo "$orig_args: unknown assembler invoked" >&2 + exit 1 +fi + +# Some distributions append a package release number, as in 2.34-4.fc32 +# Trim the hyphen and any characters that follow. +version=${version%-*} + +cversion=$(get_canonical_version $version) +min_cversion=$(get_canonical_version $min_version) + +if [ "$cversion" -lt "$min_cversion" ]; then + echo >&2 "***" + echo >&2 "*** Assembler is too old." + echo >&2 "*** Your $name assembler version: $version" + echo >&2 "*** Minimum $name assembler version: $min_version" + echo >&2 "***" + exit 1 +fi + +echo $name $cversion diff --git a/scripts/asn1_compiler.c b/scripts/asn1_compiler.c new file mode 100644 index 000000000..4c3f64506 --- /dev/null +++ b/scripts/asn1_compiler.c @@ -0,0 +1,1611 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Simplified ASN.1 notation parser + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum token_type { + DIRECTIVE_ABSENT, + DIRECTIVE_ALL, + DIRECTIVE_ANY, + DIRECTIVE_APPLICATION, + DIRECTIVE_AUTOMATIC, + DIRECTIVE_BEGIN, + DIRECTIVE_BIT, + DIRECTIVE_BMPString, + DIRECTIVE_BOOLEAN, + DIRECTIVE_BY, + DIRECTIVE_CHARACTER, + DIRECTIVE_CHOICE, + DIRECTIVE_CLASS, + DIRECTIVE_COMPONENT, + DIRECTIVE_COMPONENTS, + DIRECTIVE_CONSTRAINED, + DIRECTIVE_CONTAINING, + DIRECTIVE_DEFAULT, + DIRECTIVE_DEFINED, + DIRECTIVE_DEFINITIONS, + DIRECTIVE_EMBEDDED, + DIRECTIVE_ENCODED, + DIRECTIVE_ENCODING_CONTROL, + DIRECTIVE_END, + DIRECTIVE_ENUMERATED, + DIRECTIVE_EXCEPT, + DIRECTIVE_EXPLICIT, + DIRECTIVE_EXPORTS, + DIRECTIVE_EXTENSIBILITY, + DIRECTIVE_EXTERNAL, + DIRECTIVE_FALSE, + DIRECTIVE_FROM, + DIRECTIVE_GeneralString, + DIRECTIVE_GeneralizedTime, + DIRECTIVE_GraphicString, + DIRECTIVE_IA5String, + DIRECTIVE_IDENTIFIER, + DIRECTIVE_IMPLICIT, + DIRECTIVE_IMPLIED, + DIRECTIVE_IMPORTS, + DIRECTIVE_INCLUDES, + DIRECTIVE_INSTANCE, + DIRECTIVE_INSTRUCTIONS, + DIRECTIVE_INTEGER, + DIRECTIVE_INTERSECTION, + DIRECTIVE_ISO646String, + DIRECTIVE_MAX, + DIRECTIVE_MIN, + DIRECTIVE_MINUS_INFINITY, + DIRECTIVE_NULL, + DIRECTIVE_NumericString, + DIRECTIVE_OBJECT, + DIRECTIVE_OCTET, + DIRECTIVE_OF, + DIRECTIVE_OPTIONAL, + DIRECTIVE_ObjectDescriptor, + DIRECTIVE_PATTERN, + DIRECTIVE_PDV, + DIRECTIVE_PLUS_INFINITY, + DIRECTIVE_PRESENT, + DIRECTIVE_PRIVATE, + DIRECTIVE_PrintableString, + DIRECTIVE_REAL, + DIRECTIVE_RELATIVE_OID, + DIRECTIVE_SEQUENCE, + DIRECTIVE_SET, + DIRECTIVE_SIZE, + DIRECTIVE_STRING, + DIRECTIVE_SYNTAX, + DIRECTIVE_T61String, + DIRECTIVE_TAGS, + DIRECTIVE_TRUE, + DIRECTIVE_TeletexString, + DIRECTIVE_UNION, + DIRECTIVE_UNIQUE, + DIRECTIVE_UNIVERSAL, + DIRECTIVE_UTCTime, + DIRECTIVE_UTF8String, + DIRECTIVE_UniversalString, + DIRECTIVE_VideotexString, + DIRECTIVE_VisibleString, + DIRECTIVE_WITH, + NR__DIRECTIVES, + TOKEN_ASSIGNMENT = NR__DIRECTIVES, + TOKEN_OPEN_CURLY, + TOKEN_CLOSE_CURLY, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE, + TOKEN_OPEN_ACTION, + TOKEN_CLOSE_ACTION, + TOKEN_COMMA, + TOKEN_NUMBER, + TOKEN_TYPE_NAME, + TOKEN_ELEMENT_NAME, + NR__TOKENS +}; + +static const unsigned char token_to_tag[NR__TOKENS] = { + /* EOC goes first */ + [DIRECTIVE_BOOLEAN] = ASN1_BOOL, + [DIRECTIVE_INTEGER] = ASN1_INT, + [DIRECTIVE_BIT] = ASN1_BTS, + [DIRECTIVE_OCTET] = ASN1_OTS, + [DIRECTIVE_NULL] = ASN1_NULL, + [DIRECTIVE_OBJECT] = ASN1_OID, + [DIRECTIVE_ObjectDescriptor] = ASN1_ODE, + [DIRECTIVE_EXTERNAL] = ASN1_EXT, + [DIRECTIVE_REAL] = ASN1_REAL, + [DIRECTIVE_ENUMERATED] = ASN1_ENUM, + [DIRECTIVE_EMBEDDED] = 0, + [DIRECTIVE_UTF8String] = ASN1_UTF8STR, + [DIRECTIVE_RELATIVE_OID] = ASN1_RELOID, + /* 14 */ + /* 15 */ + [DIRECTIVE_SEQUENCE] = ASN1_SEQ, + [DIRECTIVE_SET] = ASN1_SET, + [DIRECTIVE_NumericString] = ASN1_NUMSTR, + [DIRECTIVE_PrintableString] = ASN1_PRNSTR, + [DIRECTIVE_T61String] = ASN1_TEXSTR, + [DIRECTIVE_TeletexString] = ASN1_TEXSTR, + [DIRECTIVE_VideotexString] = ASN1_VIDSTR, + [DIRECTIVE_IA5String] = ASN1_IA5STR, + [DIRECTIVE_UTCTime] = ASN1_UNITIM, + [DIRECTIVE_GeneralizedTime] = ASN1_GENTIM, + [DIRECTIVE_GraphicString] = ASN1_GRASTR, + [DIRECTIVE_VisibleString] = ASN1_VISSTR, + [DIRECTIVE_GeneralString] = ASN1_GENSTR, + [DIRECTIVE_UniversalString] = ASN1_UNITIM, + [DIRECTIVE_CHARACTER] = ASN1_CHRSTR, + [DIRECTIVE_BMPString] = ASN1_BMPSTR, +}; + +static const char asn1_classes[4][5] = { + [ASN1_UNIV] = "UNIV", + [ASN1_APPL] = "APPL", + [ASN1_CONT] = "CONT", + [ASN1_PRIV] = "PRIV" +}; + +static const char asn1_methods[2][5] = { + [ASN1_UNIV] = "PRIM", + [ASN1_APPL] = "CONS" +}; + +static const char *const asn1_universal_tags[32] = { + "EOC", + "BOOL", + "INT", + "BTS", + "OTS", + "NULL", + "OID", + "ODE", + "EXT", + "REAL", + "ENUM", + "EPDV", + "UTF8STR", + "RELOID", + NULL, /* 14 */ + NULL, /* 15 */ + "SEQ", + "SET", + "NUMSTR", + "PRNSTR", + "TEXSTR", + "VIDSTR", + "IA5STR", + "UNITIM", + "GENTIM", + "GRASTR", + "VISSTR", + "GENSTR", + "UNISTR", + "CHRSTR", + "BMPSTR", + NULL /* 31 */ +}; + +static const char *filename; +static const char *grammar_name; +static const char *outputname; +static const char *headername; + +static const char *const directives[NR__DIRECTIVES] = { +#define _(X) [DIRECTIVE_##X] = #X + _(ABSENT), + _(ALL), + _(ANY), + _(APPLICATION), + _(AUTOMATIC), + _(BEGIN), + _(BIT), + _(BMPString), + _(BOOLEAN), + _(BY), + _(CHARACTER), + _(CHOICE), + _(CLASS), + _(COMPONENT), + _(COMPONENTS), + _(CONSTRAINED), + _(CONTAINING), + _(DEFAULT), + _(DEFINED), + _(DEFINITIONS), + _(EMBEDDED), + _(ENCODED), + [DIRECTIVE_ENCODING_CONTROL] = "ENCODING-CONTROL", + _(END), + _(ENUMERATED), + _(EXCEPT), + _(EXPLICIT), + _(EXPORTS), + _(EXTENSIBILITY), + _(EXTERNAL), + _(FALSE), + _(FROM), + _(GeneralString), + _(GeneralizedTime), + _(GraphicString), + _(IA5String), + _(IDENTIFIER), + _(IMPLICIT), + _(IMPLIED), + _(IMPORTS), + _(INCLUDES), + _(INSTANCE), + _(INSTRUCTIONS), + _(INTEGER), + _(INTERSECTION), + _(ISO646String), + _(MAX), + _(MIN), + [DIRECTIVE_MINUS_INFINITY] = "MINUS-INFINITY", + [DIRECTIVE_NULL] = "NULL", + _(NumericString), + _(OBJECT), + _(OCTET), + _(OF), + _(OPTIONAL), + _(ObjectDescriptor), + _(PATTERN), + _(PDV), + [DIRECTIVE_PLUS_INFINITY] = "PLUS-INFINITY", + _(PRESENT), + _(PRIVATE), + _(PrintableString), + _(REAL), + [DIRECTIVE_RELATIVE_OID] = "RELATIVE-OID", + _(SEQUENCE), + _(SET), + _(SIZE), + _(STRING), + _(SYNTAX), + _(T61String), + _(TAGS), + _(TRUE), + _(TeletexString), + _(UNION), + _(UNIQUE), + _(UNIVERSAL), + _(UTCTime), + _(UTF8String), + _(UniversalString), + _(VideotexString), + _(VisibleString), + _(WITH) +}; + +struct action { + struct action *next; + char *name; + unsigned char index; +}; + +static struct action *action_list; +static unsigned nr_actions; + +struct token { + unsigned short line; + enum token_type token_type : 8; + unsigned char size; + struct action *action; + char *content; + struct type *type; +}; + +static struct token *token_list; +static unsigned nr_tokens; +static bool verbose_opt; +static bool debug_opt; + +#define verbose(fmt, ...) do { if (verbose_opt) printf(fmt, ## __VA_ARGS__); } while (0) +#define debug(fmt, ...) do { if (debug_opt) printf(fmt, ## __VA_ARGS__); } while (0) + +static int directive_compare(const void *_key, const void *_pdir) +{ + const struct token *token = _key; + const char *const *pdir = _pdir, *dir = *pdir; + size_t dlen, clen; + int val; + + dlen = strlen(dir); + clen = (dlen < token->size) ? dlen : token->size; + + //debug("cmp(%s,%s) = ", token->content, dir); + + val = memcmp(token->content, dir, clen); + if (val != 0) { + //debug("%d [cmp]\n", val); + return val; + } + + if (dlen == token->size) { + //debug("0\n"); + return 0; + } + //debug("%d\n", (int)dlen - (int)token->size); + return dlen - token->size; /* shorter -> negative */ +} + +/* + * Tokenise an ASN.1 grammar + */ +static void tokenise(char *buffer, char *end) +{ + struct token *tokens; + char *line, *nl, *start, *p, *q; + unsigned tix, lineno; + + /* Assume we're going to have half as many tokens as we have + * characters + */ + token_list = tokens = calloc((end - buffer) / 2, sizeof(struct token)); + if (!tokens) { + perror(NULL); + exit(1); + } + tix = 0; + + lineno = 0; + while (buffer < end) { + /* First of all, break out a line */ + lineno++; + line = buffer; + nl = memchr(line, '\n', end - buffer); + if (!nl) { + buffer = nl = end; + } else { + buffer = nl + 1; + *nl = '\0'; + } + + /* Remove "--" comments */ + p = line; + next_comment: + while ((p = memchr(p, '-', nl - p))) { + if (p[1] == '-') { + /* Found a comment; see if there's a terminator */ + q = p + 2; + while ((q = memchr(q, '-', nl - q))) { + if (q[1] == '-') { + /* There is - excise the comment */ + q += 2; + memmove(p, q, nl - q); + goto next_comment; + } + q++; + } + *p = '\0'; + nl = p; + break; + } else { + p++; + } + } + + p = line; + while (p < nl) { + /* Skip white space */ + while (p < nl && isspace(*p)) + *(p++) = 0; + if (p >= nl) + break; + + tokens[tix].line = lineno; + start = p; + + /* Handle string tokens */ + if (isalpha(*p)) { + const char **dir; + + /* Can be a directive, type name or element + * name. Find the end of the name. + */ + q = p + 1; + while (q < nl && (isalnum(*q) || *q == '-' || *q == '_')) + q++; + tokens[tix].size = q - p; + p = q; + + tokens[tix].content = malloc(tokens[tix].size + 1); + if (!tokens[tix].content) { + perror(NULL); + exit(1); + } + memcpy(tokens[tix].content, start, tokens[tix].size); + tokens[tix].content[tokens[tix].size] = 0; + + /* If it begins with a lowercase letter then + * it's an element name + */ + if (islower(tokens[tix].content[0])) { + tokens[tix++].token_type = TOKEN_ELEMENT_NAME; + continue; + } + + /* Otherwise we need to search the directive + * table + */ + dir = bsearch(&tokens[tix], directives, + sizeof(directives) / sizeof(directives[1]), + sizeof(directives[1]), + directive_compare); + if (dir) { + tokens[tix++].token_type = dir - directives; + continue; + } + + tokens[tix++].token_type = TOKEN_TYPE_NAME; + continue; + } + + /* Handle numbers */ + if (isdigit(*p)) { + /* Find the end of the number */ + q = p + 1; + while (q < nl && (isdigit(*q))) + q++; + tokens[tix].size = q - p; + p = q; + tokens[tix].content = malloc(tokens[tix].size + 1); + if (!tokens[tix].content) { + perror(NULL); + exit(1); + } + memcpy(tokens[tix].content, start, tokens[tix].size); + tokens[tix].content[tokens[tix].size] = 0; + tokens[tix++].token_type = TOKEN_NUMBER; + continue; + } + + if (nl - p >= 3) { + if (memcmp(p, "::=", 3) == 0) { + p += 3; + tokens[tix].size = 3; + tokens[tix].content = "::="; + tokens[tix++].token_type = TOKEN_ASSIGNMENT; + continue; + } + } + + if (nl - p >= 2) { + if (memcmp(p, "({", 2) == 0) { + p += 2; + tokens[tix].size = 2; + tokens[tix].content = "({"; + tokens[tix++].token_type = TOKEN_OPEN_ACTION; + continue; + } + if (memcmp(p, "})", 2) == 0) { + p += 2; + tokens[tix].size = 2; + tokens[tix].content = "})"; + tokens[tix++].token_type = TOKEN_CLOSE_ACTION; + continue; + } + } + + if (nl - p >= 1) { + tokens[tix].size = 1; + switch (*p) { + case '{': + p += 1; + tokens[tix].content = "{"; + tokens[tix++].token_type = TOKEN_OPEN_CURLY; + continue; + case '}': + p += 1; + tokens[tix].content = "}"; + tokens[tix++].token_type = TOKEN_CLOSE_CURLY; + continue; + case '[': + p += 1; + tokens[tix].content = "["; + tokens[tix++].token_type = TOKEN_OPEN_SQUARE; + continue; + case ']': + p += 1; + tokens[tix].content = "]"; + tokens[tix++].token_type = TOKEN_CLOSE_SQUARE; + continue; + case ',': + p += 1; + tokens[tix].content = ","; + tokens[tix++].token_type = TOKEN_COMMA; + continue; + default: + break; + } + } + + fprintf(stderr, "%s:%u: Unknown character in grammar: '%c'\n", + filename, lineno, *p); + exit(1); + } + } + + nr_tokens = tix; + verbose("Extracted %u tokens\n", nr_tokens); + +#if 0 + { + int n; + for (n = 0; n < nr_tokens; n++) + debug("Token %3u: '%s'\n", n, token_list[n].content); + } +#endif +} + +static void build_type_list(void); +static void parse(void); +static void dump_elements(void); +static void render(FILE *out, FILE *hdr); + +/* + * + */ +int main(int argc, char **argv) +{ + struct stat st; + ssize_t readlen; + FILE *out, *hdr; + char *buffer, *p; + char *kbuild_verbose; + int fd; + + kbuild_verbose = getenv("KBUILD_VERBOSE"); + if (kbuild_verbose && strchr(kbuild_verbose, '1')) + verbose_opt = true; + + while (argc > 4) { + if (strcmp(argv[1], "-v") == 0) + verbose_opt = true; + else if (strcmp(argv[1], "-d") == 0) + debug_opt = true; + else + break; + memmove(&argv[1], &argv[2], (argc - 2) * sizeof(char *)); + argc--; + } + + if (argc != 4) { + fprintf(stderr, "Format: %s [-v] [-d] \n", + argv[0]); + exit(2); + } + + filename = argv[1]; + outputname = argv[2]; + headername = argv[3]; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + perror(filename); + exit(1); + } + + if (fstat(fd, &st) < 0) { + perror(filename); + exit(1); + } + + if (!(buffer = malloc(st.st_size + 1))) { + perror(NULL); + exit(1); + } + + if ((readlen = read(fd, buffer, st.st_size)) < 0) { + perror(filename); + exit(1); + } + + if (close(fd) < 0) { + perror(filename); + exit(1); + } + + if (readlen != st.st_size) { + fprintf(stderr, "%s: Short read\n", filename); + exit(1); + } + + p = strrchr(argv[1], '/'); + p = p ? p + 1 : argv[1]; + grammar_name = strdup(p); + if (!grammar_name) { + perror(NULL); + exit(1); + } + p = strchr(grammar_name, '.'); + if (p) + *p = '\0'; + + buffer[readlen] = 0; + tokenise(buffer, buffer + readlen); + build_type_list(); + parse(); + dump_elements(); + + out = fopen(outputname, "w"); + if (!out) { + perror(outputname); + exit(1); + } + + hdr = fopen(headername, "w"); + if (!hdr) { + perror(headername); + exit(1); + } + + render(out, hdr); + + if (fclose(out) < 0) { + perror(outputname); + exit(1); + } + + if (fclose(hdr) < 0) { + perror(headername); + exit(1); + } + + return 0; +} + +enum compound { + NOT_COMPOUND, + SET, + SET_OF, + SEQUENCE, + SEQUENCE_OF, + CHOICE, + ANY, + TYPE_REF, + TAG_OVERRIDE +}; + +struct element { + struct type *type_def; + struct token *name; + struct token *type; + struct action *action; + struct element *children; + struct element *next; + struct element *render_next; + struct element *list_next; + uint8_t n_elements; + enum compound compound : 8; + enum asn1_class class : 8; + enum asn1_method method : 8; + uint8_t tag; + unsigned entry_index; + unsigned flags; +#define ELEMENT_IMPLICIT 0x0001 +#define ELEMENT_EXPLICIT 0x0002 +#define ELEMENT_TAG_SPECIFIED 0x0004 +#define ELEMENT_RENDERED 0x0008 +#define ELEMENT_SKIPPABLE 0x0010 +#define ELEMENT_CONDITIONAL 0x0020 +}; + +struct type { + struct token *name; + struct token *def; + struct element *element; + unsigned ref_count; + unsigned flags; +#define TYPE_STOP_MARKER 0x0001 +#define TYPE_BEGIN 0x0002 +}; + +static struct type *type_list; +static struct type **type_index; +static unsigned nr_types; + +static int type_index_compare(const void *_a, const void *_b) +{ + const struct type *const *a = _a, *const *b = _b; + + if ((*a)->name->size != (*b)->name->size) + return (*a)->name->size - (*b)->name->size; + else + return memcmp((*a)->name->content, (*b)->name->content, + (*a)->name->size); +} + +static int type_finder(const void *_key, const void *_ti) +{ + const struct token *token = _key; + const struct type *const *ti = _ti; + const struct type *type = *ti; + + if (token->size != type->name->size) + return token->size - type->name->size; + else + return memcmp(token->content, type->name->content, + token->size); +} + +/* + * Build up a list of types and a sorted index to that list. + */ +static void build_type_list(void) +{ + struct type *types; + unsigned nr, t, n; + + nr = 0; + for (n = 0; n < nr_tokens - 1; n++) + if (token_list[n + 0].token_type == TOKEN_TYPE_NAME && + token_list[n + 1].token_type == TOKEN_ASSIGNMENT) + nr++; + + if (nr == 0) { + fprintf(stderr, "%s: No defined types\n", filename); + exit(1); + } + + nr_types = nr; + types = type_list = calloc(nr + 1, sizeof(type_list[0])); + if (!type_list) { + perror(NULL); + exit(1); + } + type_index = calloc(nr, sizeof(type_index[0])); + if (!type_index) { + perror(NULL); + exit(1); + } + + t = 0; + types[t].flags |= TYPE_BEGIN; + for (n = 0; n < nr_tokens - 1; n++) { + if (token_list[n + 0].token_type == TOKEN_TYPE_NAME && + token_list[n + 1].token_type == TOKEN_ASSIGNMENT) { + types[t].name = &token_list[n]; + type_index[t] = &types[t]; + t++; + } + } + types[t].name = &token_list[n + 1]; + types[t].flags |= TYPE_STOP_MARKER; + + qsort(type_index, nr, sizeof(type_index[0]), type_index_compare); + + verbose("Extracted %u types\n", nr_types); +#if 0 + for (n = 0; n < nr_types; n++) { + struct type *type = type_index[n]; + debug("- %*.*s\n", type->name->content); + } +#endif +} + +static struct element *parse_type(struct token **_cursor, struct token *stop, + struct token *name); + +/* + * Parse the token stream + */ +static void parse(void) +{ + struct token *cursor; + struct type *type; + + /* Parse one type definition statement at a time */ + type = type_list; + do { + cursor = type->name; + + if (cursor[0].token_type != TOKEN_TYPE_NAME || + cursor[1].token_type != TOKEN_ASSIGNMENT) + abort(); + cursor += 2; + + type->element = parse_type(&cursor, type[1].name, NULL); + type->element->type_def = type; + + if (cursor != type[1].name) { + fprintf(stderr, "%s:%d: Parse error at token '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + + } while (type++, !(type->flags & TYPE_STOP_MARKER)); + + verbose("Extracted %u actions\n", nr_actions); +} + +static struct element *element_list; + +static struct element *alloc_elem(void) +{ + struct element *e = calloc(1, sizeof(*e)); + if (!e) { + perror(NULL); + exit(1); + } + e->list_next = element_list; + element_list = e; + return e; +} + +static struct element *parse_compound(struct token **_cursor, struct token *end, + int alternates); + +/* + * Parse one type definition statement + */ +static struct element *parse_type(struct token **_cursor, struct token *end, + struct token *name) +{ + struct element *top, *element; + struct action *action, **ppaction; + struct token *cursor = *_cursor; + struct type **ref; + char *p; + int labelled = 0, implicit = 0; + + top = element = alloc_elem(); + element->class = ASN1_UNIV; + element->method = ASN1_PRIM; + element->tag = token_to_tag[cursor->token_type]; + element->name = name; + + /* Extract the tag value if one given */ + if (cursor->token_type == TOKEN_OPEN_SQUARE) { + cursor++; + if (cursor >= end) + goto overrun_error; + switch (cursor->token_type) { + case DIRECTIVE_UNIVERSAL: + element->class = ASN1_UNIV; + cursor++; + break; + case DIRECTIVE_APPLICATION: + element->class = ASN1_APPL; + cursor++; + break; + case TOKEN_NUMBER: + element->class = ASN1_CONT; + break; + case DIRECTIVE_PRIVATE: + element->class = ASN1_PRIV; + cursor++; + break; + default: + fprintf(stderr, "%s:%d: Unrecognised tag class token '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_NUMBER) { + fprintf(stderr, "%s:%d: Missing tag number '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + + element->tag &= ~0x1f; + element->tag |= strtoul(cursor->content, &p, 10); + element->flags |= ELEMENT_TAG_SPECIFIED; + if (p - cursor->content != cursor->size) + abort(); + cursor++; + + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_CLOSE_SQUARE) { + fprintf(stderr, "%s:%d: Missing closing square bracket '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + if (cursor >= end) + goto overrun_error; + labelled = 1; + } + + /* Handle implicit and explicit markers */ + if (cursor->token_type == DIRECTIVE_IMPLICIT) { + element->flags |= ELEMENT_IMPLICIT; + implicit = 1; + cursor++; + if (cursor >= end) + goto overrun_error; + } else if (cursor->token_type == DIRECTIVE_EXPLICIT) { + element->flags |= ELEMENT_EXPLICIT; + cursor++; + if (cursor >= end) + goto overrun_error; + } + + if (labelled) { + if (!implicit) + element->method |= ASN1_CONS; + element->compound = implicit ? TAG_OVERRIDE : SEQUENCE; + element->children = alloc_elem(); + element = element->children; + element->class = ASN1_UNIV; + element->method = ASN1_PRIM; + element->tag = token_to_tag[cursor->token_type]; + element->name = name; + } + + /* Extract the type we're expecting here */ + element->type = cursor; + switch (cursor->token_type) { + case DIRECTIVE_ANY: + element->compound = ANY; + cursor++; + break; + + case DIRECTIVE_NULL: + case DIRECTIVE_BOOLEAN: + case DIRECTIVE_ENUMERATED: + case DIRECTIVE_INTEGER: + element->compound = NOT_COMPOUND; + cursor++; + break; + + case DIRECTIVE_EXTERNAL: + element->method = ASN1_CONS; + + case DIRECTIVE_BMPString: + case DIRECTIVE_GeneralString: + case DIRECTIVE_GraphicString: + case DIRECTIVE_IA5String: + case DIRECTIVE_ISO646String: + case DIRECTIVE_NumericString: + case DIRECTIVE_PrintableString: + case DIRECTIVE_T61String: + case DIRECTIVE_TeletexString: + case DIRECTIVE_UniversalString: + case DIRECTIVE_UTF8String: + case DIRECTIVE_VideotexString: + case DIRECTIVE_VisibleString: + case DIRECTIVE_ObjectDescriptor: + case DIRECTIVE_GeneralizedTime: + case DIRECTIVE_UTCTime: + element->compound = NOT_COMPOUND; + cursor++; + break; + + case DIRECTIVE_BIT: + case DIRECTIVE_OCTET: + element->compound = NOT_COMPOUND; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != DIRECTIVE_STRING) + goto parse_error; + cursor++; + break; + + case DIRECTIVE_OBJECT: + element->compound = NOT_COMPOUND; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != DIRECTIVE_IDENTIFIER) + goto parse_error; + cursor++; + break; + + case TOKEN_TYPE_NAME: + element->compound = TYPE_REF; + ref = bsearch(cursor, type_index, nr_types, sizeof(type_index[0]), + type_finder); + if (!ref) { + fprintf(stderr, "%s:%d: Type '%s' undefined\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor->type = *ref; + (*ref)->ref_count++; + cursor++; + break; + + case DIRECTIVE_CHOICE: + element->compound = CHOICE; + cursor++; + element->children = parse_compound(&cursor, end, 1); + break; + + case DIRECTIVE_SEQUENCE: + element->compound = SEQUENCE; + element->method = ASN1_CONS; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type == DIRECTIVE_OF) { + element->compound = SEQUENCE_OF; + cursor++; + if (cursor >= end) + goto overrun_error; + element->children = parse_type(&cursor, end, NULL); + } else { + element->children = parse_compound(&cursor, end, 0); + } + break; + + case DIRECTIVE_SET: + element->compound = SET; + element->method = ASN1_CONS; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type == DIRECTIVE_OF) { + element->compound = SET_OF; + cursor++; + if (cursor >= end) + goto parse_error; + element->children = parse_type(&cursor, end, NULL); + } else { + element->children = parse_compound(&cursor, end, 1); + } + break; + + default: + fprintf(stderr, "%s:%d: Token '%s' does not introduce a type\n", + filename, cursor->line, cursor->content); + exit(1); + } + + /* Handle elements that are optional */ + if (cursor < end && (cursor->token_type == DIRECTIVE_OPTIONAL || + cursor->token_type == DIRECTIVE_DEFAULT) + ) { + cursor++; + top->flags |= ELEMENT_SKIPPABLE; + } + + if (cursor < end && cursor->token_type == TOKEN_OPEN_ACTION) { + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_ELEMENT_NAME) { + fprintf(stderr, "%s:%d: Token '%s' is not an action function name\n", + filename, cursor->line, cursor->content); + exit(1); + } + + action = malloc(sizeof(struct action)); + if (!action) { + perror(NULL); + exit(1); + } + action->index = 0; + action->name = cursor->content; + + for (ppaction = &action_list; + *ppaction; + ppaction = &(*ppaction)->next + ) { + int cmp = strcmp(action->name, (*ppaction)->name); + if (cmp == 0) { + free(action); + action = *ppaction; + goto found; + } + if (cmp < 0) { + action->next = *ppaction; + *ppaction = action; + nr_actions++; + goto found; + } + } + action->next = NULL; + *ppaction = action; + nr_actions++; + found: + + element->action = action; + cursor->action = action; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_CLOSE_ACTION) { + fprintf(stderr, "%s:%d: Missing close action, got '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + } + + *_cursor = cursor; + return top; + +parse_error: + fprintf(stderr, "%s:%d: Unexpected token '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + +overrun_error: + fprintf(stderr, "%s: Unexpectedly hit EOF\n", filename); + exit(1); +} + +/* + * Parse a compound type list + */ +static struct element *parse_compound(struct token **_cursor, struct token *end, + int alternates) +{ + struct element *children, **child_p = &children, *element; + struct token *cursor = *_cursor, *name; + + if (cursor->token_type != TOKEN_OPEN_CURLY) { + fprintf(stderr, "%s:%d: Expected compound to start with brace not '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + if (cursor >= end) + goto overrun_error; + + if (cursor->token_type == TOKEN_OPEN_CURLY) { + fprintf(stderr, "%s:%d: Empty compound\n", + filename, cursor->line); + exit(1); + } + + for (;;) { + name = NULL; + if (cursor->token_type == TOKEN_ELEMENT_NAME) { + name = cursor; + cursor++; + if (cursor >= end) + goto overrun_error; + } + + element = parse_type(&cursor, end, name); + if (alternates) + element->flags |= ELEMENT_SKIPPABLE | ELEMENT_CONDITIONAL; + + *child_p = element; + child_p = &element->next; + + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_COMMA) + break; + cursor++; + if (cursor >= end) + goto overrun_error; + } + + children->flags &= ~ELEMENT_CONDITIONAL; + + if (cursor->token_type != TOKEN_CLOSE_CURLY) { + fprintf(stderr, "%s:%d: Expected compound closure, got '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + + *_cursor = cursor; + return children; + +overrun_error: + fprintf(stderr, "%s: Unexpectedly hit EOF\n", filename); + exit(1); +} + +static void dump_element(const struct element *e, int level) +{ + const struct element *c; + const struct type *t = e->type_def; + const char *name = e->name ? e->name->content : "."; + const char *tname = t && t->name ? t->name->content : "."; + char tag[32]; + + if (e->class == 0 && e->method == 0 && e->tag == 0) + strcpy(tag, "<...>"); + else if (e->class == ASN1_UNIV) + sprintf(tag, "%s %s %s", + asn1_classes[e->class], + asn1_methods[e->method], + asn1_universal_tags[e->tag]); + else + sprintf(tag, "%s %s %u", + asn1_classes[e->class], + asn1_methods[e->method], + e->tag); + + printf("%c%c%c%c%c %c %*s[*] \e[33m%s\e[m %s %s \e[35m%s\e[m\n", + e->flags & ELEMENT_IMPLICIT ? 'I' : '-', + e->flags & ELEMENT_EXPLICIT ? 'E' : '-', + e->flags & ELEMENT_TAG_SPECIFIED ? 'T' : '-', + e->flags & ELEMENT_SKIPPABLE ? 'S' : '-', + e->flags & ELEMENT_CONDITIONAL ? 'C' : '-', + "-tTqQcaro"[e->compound], + level, "", + tag, + tname, + name, + e->action ? e->action->name : ""); + if (e->compound == TYPE_REF) + dump_element(e->type->type->element, level + 3); + else + for (c = e->children; c; c = c->next) + dump_element(c, level + 3); +} + +static void dump_elements(void) +{ + if (debug_opt) + dump_element(type_list[0].element, 0); +} + +static void render_element(FILE *out, struct element *e, struct element *tag); +static void render_out_of_line_list(FILE *out); + +static int nr_entries; +static int render_depth = 1; +static struct element *render_list, **render_list_p = &render_list; + +__attribute__((format(printf, 2, 3))) +static void render_opcode(FILE *out, const char *fmt, ...) +{ + va_list va; + + if (out) { + fprintf(out, "\t[%4d] =%*s", nr_entries, render_depth, ""); + va_start(va, fmt); + vfprintf(out, fmt, va); + va_end(va); + } + nr_entries++; +} + +__attribute__((format(printf, 2, 3))) +static void render_more(FILE *out, const char *fmt, ...) +{ + va_list va; + + if (out) { + va_start(va, fmt); + vfprintf(out, fmt, va); + va_end(va); + } +} + +/* + * Render the grammar into a state machine definition. + */ +static void render(FILE *out, FILE *hdr) +{ + struct element *e; + struct action *action; + struct type *root; + int index; + + fprintf(hdr, "/*\n"); + fprintf(hdr, " * Automatically generated by asn1_compiler. Do not edit\n"); + fprintf(hdr, " *\n"); + fprintf(hdr, " * ASN.1 parser for %s\n", grammar_name); + fprintf(hdr, " */\n"); + fprintf(hdr, "#include \n"); + fprintf(hdr, "\n"); + fprintf(hdr, "extern const struct asn1_decoder %s_decoder;\n", grammar_name); + if (ferror(hdr)) { + perror(headername); + exit(1); + } + + fprintf(out, "/*\n"); + fprintf(out, " * Automatically generated by asn1_compiler. Do not edit\n"); + fprintf(out, " *\n"); + fprintf(out, " * ASN.1 parser for %s\n", grammar_name); + fprintf(out, " */\n"); + fprintf(out, "#include \n"); + fprintf(out, "#include \"%s.asn1.h\"\n", grammar_name); + fprintf(out, "\n"); + if (ferror(out)) { + perror(outputname); + exit(1); + } + + /* Tabulate the action functions we might have to call */ + fprintf(hdr, "\n"); + index = 0; + for (action = action_list; action; action = action->next) { + action->index = index++; + fprintf(hdr, + "extern int %s(void *, size_t, unsigned char," + " const void *, size_t);\n", + action->name); + } + fprintf(hdr, "\n"); + + fprintf(out, "enum %s_actions {\n", grammar_name); + for (action = action_list; action; action = action->next) + fprintf(out, "\tACT_%s = %u,\n", + action->name, action->index); + fprintf(out, "\tNR__%s_actions = %u\n", grammar_name, nr_actions); + fprintf(out, "};\n"); + + fprintf(out, "\n"); + fprintf(out, "static const asn1_action_t %s_action_table[NR__%s_actions] = {\n", + grammar_name, grammar_name); + for (action = action_list; action; action = action->next) + fprintf(out, "\t[%4u] = %s,\n", action->index, action->name); + fprintf(out, "};\n"); + + if (ferror(out)) { + perror(outputname); + exit(1); + } + + /* We do two passes - the first one calculates all the offsets */ + verbose("Pass 1\n"); + nr_entries = 0; + root = &type_list[0]; + render_element(NULL, root->element, NULL); + render_opcode(NULL, "ASN1_OP_COMPLETE,\n"); + render_out_of_line_list(NULL); + + for (e = element_list; e; e = e->list_next) + e->flags &= ~ELEMENT_RENDERED; + + /* And then we actually render */ + verbose("Pass 2\n"); + fprintf(out, "\n"); + fprintf(out, "static const unsigned char %s_machine[] = {\n", + grammar_name); + + nr_entries = 0; + root = &type_list[0]; + render_element(out, root->element, NULL); + render_opcode(out, "ASN1_OP_COMPLETE,\n"); + render_out_of_line_list(out); + + fprintf(out, "};\n"); + + fprintf(out, "\n"); + fprintf(out, "const struct asn1_decoder %s_decoder = {\n", grammar_name); + fprintf(out, "\t.machine = %s_machine,\n", grammar_name); + fprintf(out, "\t.machlen = sizeof(%s_machine),\n", grammar_name); + fprintf(out, "\t.actions = %s_action_table,\n", grammar_name); + fprintf(out, "};\n"); +} + +/* + * Render the out-of-line elements + */ +static void render_out_of_line_list(FILE *out) +{ + struct element *e, *ce; + const char *act; + int entry; + + while ((e = render_list)) { + render_list = e->render_next; + if (!render_list) + render_list_p = &render_list; + + render_more(out, "\n"); + e->entry_index = entry = nr_entries; + render_depth++; + for (ce = e->children; ce; ce = ce->next) + render_element(out, ce, NULL); + render_depth--; + + act = e->action ? "_ACT" : ""; + switch (e->compound) { + case SEQUENCE: + render_opcode(out, "ASN1_OP_END_SEQ%s,\n", act); + break; + case SEQUENCE_OF: + render_opcode(out, "ASN1_OP_END_SEQ_OF%s,\n", act); + render_opcode(out, "_jump_target(%u),\n", entry); + break; + case SET: + render_opcode(out, "ASN1_OP_END_SET%s,\n", act); + break; + case SET_OF: + render_opcode(out, "ASN1_OP_END_SET_OF%s,\n", act); + render_opcode(out, "_jump_target(%u),\n", entry); + break; + default: + break; + } + if (e->action) + render_opcode(out, "_action(ACT_%s),\n", + e->action->name); + render_opcode(out, "ASN1_OP_RETURN,\n"); + } +} + +/* + * Render an element. + */ +static void render_element(FILE *out, struct element *e, struct element *tag) +{ + struct element *ec, *x; + const char *cond, *act; + int entry, skippable = 0, outofline = 0; + + if (e->flags & ELEMENT_SKIPPABLE || + (tag && tag->flags & ELEMENT_SKIPPABLE)) + skippable = 1; + + if ((e->type_def && e->type_def->ref_count > 1) || + skippable) + outofline = 1; + + if (e->type_def && out) { + render_more(out, "\t// %s\n", e->type_def->name->content); + } + + /* Render the operation */ + cond = (e->flags & ELEMENT_CONDITIONAL || + (tag && tag->flags & ELEMENT_CONDITIONAL)) ? "COND_" : ""; + act = e->action ? "_ACT" : ""; + switch (e->compound) { + case ANY: + render_opcode(out, "ASN1_OP_%sMATCH_ANY%s%s,", + cond, act, skippable ? "_OR_SKIP" : ""); + if (e->name) + render_more(out, "\t\t// %s", e->name->content); + render_more(out, "\n"); + goto dont_render_tag; + + case TAG_OVERRIDE: + render_element(out, e->children, e); + return; + + case SEQUENCE: + case SEQUENCE_OF: + case SET: + case SET_OF: + render_opcode(out, "ASN1_OP_%sMATCH%s%s,", + cond, + outofline ? "_JUMP" : "", + skippable ? "_OR_SKIP" : ""); + break; + + case CHOICE: + goto dont_render_tag; + + case TYPE_REF: + if (e->class == ASN1_UNIV && e->method == ASN1_PRIM && e->tag == 0) + goto dont_render_tag; + default: + render_opcode(out, "ASN1_OP_%sMATCH%s%s,", + cond, act, + skippable ? "_OR_SKIP" : ""); + break; + } + + x = tag ?: e; + if (x->name) + render_more(out, "\t\t// %s", x->name->content); + render_more(out, "\n"); + + /* Render the tag */ + if (!tag || !(tag->flags & ELEMENT_TAG_SPECIFIED)) + tag = e; + + if (tag->class == ASN1_UNIV && + tag->tag != 14 && + tag->tag != 15 && + tag->tag != 31) + render_opcode(out, "_tag(%s, %s, %s),\n", + asn1_classes[tag->class], + asn1_methods[tag->method | e->method], + asn1_universal_tags[tag->tag]); + else + render_opcode(out, "_tagn(%s, %s, %2u),\n", + asn1_classes[tag->class], + asn1_methods[tag->method | e->method], + tag->tag); + tag = NULL; +dont_render_tag: + + /* Deal with compound types */ + switch (e->compound) { + case TYPE_REF: + render_element(out, e->type->type->element, tag); + if (e->action) + render_opcode(out, "ASN1_OP_%sACT,\n", + skippable ? "MAYBE_" : ""); + break; + + case SEQUENCE: + if (outofline) { + /* Render out-of-line for multiple use or + * skipability */ + render_opcode(out, "_jump_target(%u),", e->entry_index); + if (e->type_def && e->type_def->name) + render_more(out, "\t\t// --> %s", + e->type_def->name->content); + render_more(out, "\n"); + if (!(e->flags & ELEMENT_RENDERED)) { + e->flags |= ELEMENT_RENDERED; + *render_list_p = e; + render_list_p = &e->render_next; + } + return; + } else { + /* Render inline for single use */ + render_depth++; + for (ec = e->children; ec; ec = ec->next) + render_element(out, ec, NULL); + render_depth--; + render_opcode(out, "ASN1_OP_END_SEQ%s,\n", act); + } + break; + + case SEQUENCE_OF: + case SET_OF: + if (outofline) { + /* Render out-of-line for multiple use or + * skipability */ + render_opcode(out, "_jump_target(%u),", e->entry_index); + if (e->type_def && e->type_def->name) + render_more(out, "\t\t// --> %s", + e->type_def->name->content); + render_more(out, "\n"); + if (!(e->flags & ELEMENT_RENDERED)) { + e->flags |= ELEMENT_RENDERED; + *render_list_p = e; + render_list_p = &e->render_next; + } + return; + } else { + /* Render inline for single use */ + entry = nr_entries; + render_depth++; + render_element(out, e->children, NULL); + render_depth--; + if (e->compound == SEQUENCE_OF) + render_opcode(out, "ASN1_OP_END_SEQ_OF%s,\n", act); + else + render_opcode(out, "ASN1_OP_END_SET_OF%s,\n", act); + render_opcode(out, "_jump_target(%u),\n", entry); + } + break; + + case SET: + /* I can't think of a nice way to do SET support without having + * a stack of bitmasks to make sure no element is repeated. + * The bitmask has also to be checked that no non-optional + * elements are left out whilst not preventing optional + * elements from being left out. + */ + fprintf(stderr, "The ASN.1 SET type is not currently supported.\n"); + exit(1); + + case CHOICE: + for (ec = e->children; ec; ec = ec->next) + render_element(out, ec, ec); + if (!skippable) + render_opcode(out, "ASN1_OP_COND_FAIL,\n"); + if (e->action) + render_opcode(out, "ASN1_OP_ACT,\n"); + break; + + default: + break; + } + + if (e->action) + render_opcode(out, "_action(ACT_%s),\n", e->action->name); +} diff --git a/scripts/atomic/atomic-tbl.sh b/scripts/atomic/atomic-tbl.sh new file mode 100755 index 000000000..608ff39eb --- /dev/null +++ b/scripts/atomic/atomic-tbl.sh @@ -0,0 +1,290 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# helpers for dealing with atomics.tbl + +#meta_in(meta, match) +meta_in() +{ + case "$1" in + [$2]) return 0;; + esac + + return 1 +} + +#meta_has_ret(meta) +meta_has_ret() +{ + meta_in "$1" "bBiIfFlR" +} + +#meta_has_acquire(meta) +meta_has_acquire() +{ + meta_in "$1" "BFIlR" +} + +#meta_has_release(meta) +meta_has_release() +{ + meta_in "$1" "BFIRs" +} + +#meta_has_relaxed(meta) +meta_has_relaxed() +{ + meta_in "$1" "BFIR" +} + +#meta_is_implicitly_relaxed(meta) +meta_is_implicitly_relaxed() +{ + meta_in "$1" "vls" +} + +#find_template(tmpltype, pfx, name, sfx, order) +find_template() +{ + local tmpltype="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + + local base="" + local file="" + + # We may have fallbacks for a specific case (e.g. read_acquire()), or + # an entire class, e.g. *inc*(). + # + # Start at the most specific, and fall back to the most general. Once + # we find a specific fallback, don't bother looking for more. + for base in "${pfx}${name}${sfx}${order}" "${pfx}${name}${sfx}" "${name}"; do + file="${ATOMICDIR}/${tmpltype}/${base}" + + if [ -f "${file}" ]; then + printf "${file}" + break + fi + done +} + +#find_fallback_template(pfx, name, sfx, order) +find_fallback_template() +{ + find_template "fallbacks" "$@" +} + +#find_kerneldoc_template(pfx, name, sfx, order) +find_kerneldoc_template() +{ + find_template "kerneldoc" "$@" +} + +#gen_ret_type(meta, int) +gen_ret_type() { + local meta="$1"; shift + local int="$1"; shift + + case "${meta}" in + [sv]) printf "void";; + [bB]) printf "bool";; + [aiIfFlR]) printf "${int}";; + esac +} + +#gen_ret_stmt(meta) +gen_ret_stmt() +{ + if meta_has_ret "${meta}"; then + printf "return "; + fi +} + +# gen_param_name(arg) +gen_param_name() +{ + # strip off the leading 'c' for 'cv' + local name="${1#c}" + printf "${name#*:}" +} + +# gen_param_type(arg, int, atomic) +gen_param_type() +{ + local type="${1%%:*}"; shift + local int="$1"; shift + local atomic="$1"; shift + + case "${type}" in + i) type="${int} ";; + p) type="${int} *";; + v) type="${atomic}_t *";; + cv) type="const ${atomic}_t *";; + esac + + printf "${type}" +} + +#gen_param(arg, int, atomic) +gen_param() +{ + local arg="$1"; shift + local int="$1"; shift + local atomic="$1"; shift + local name="$(gen_param_name "${arg}")" + local type="$(gen_param_type "${arg}" "${int}" "${atomic}")" + + printf "${type}${name}" +} + +#gen_params(int, atomic, arg...) +gen_params() +{ + local int="$1"; shift + local atomic="$1"; shift + + while [ "$#" -gt 0 ]; do + gen_param "$1" "${int}" "${atomic}" + [ "$#" -gt 1 ] && printf ", " + shift; + done +} + +#gen_args(arg...) +gen_args() +{ + while [ "$#" -gt 0 ]; do + printf "$(gen_param_name "$1")" + [ "$#" -gt 1 ] && printf ", " + shift; + done +} + +#gen_desc_return(meta) +gen_desc_return() +{ + local meta="$1"; shift + + case "${meta}" in + [v]) + printf "Return: Nothing." + ;; + [Ff]) + printf "Return: The original value of @v." + ;; + [R]) + printf "Return: The updated value of @v." + ;; + [l]) + printf "Return: The value of @v." + ;; + esac +} + +#gen_template_kerneldoc(template, class, meta, pfx, name, sfx, order, atomic, int, args...) +gen_template_kerneldoc() +{ + local template="$1"; shift + local class="$1"; shift + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + local atomic="$1"; shift + local int="$1"; shift + + local atomicname="${atomic}_${pfx}${name}${sfx}${order}" + + local ret="$(gen_ret_type "${meta}" "${int}")" + local retstmt="$(gen_ret_stmt "${meta}")" + local params="$(gen_params "${int}" "${atomic}" "$@")" + local args="$(gen_args "$@")" + local desc_order="" + local desc_instrumentation="" + local desc_return="" + + if [ ! -z "${order}" ]; then + desc_order="${order##_}" + elif meta_is_implicitly_relaxed "${meta}"; then + desc_order="relaxed" + else + desc_order="full" + fi + + if [ -z "${class}" ]; then + desc_noinstr="Unsafe to use in noinstr code; use raw_${atomicname}() there." + else + desc_noinstr="Safe to use in noinstr code; prefer ${atomicname}() elsewhere." + fi + + desc_return="$(gen_desc_return "${meta}")" + + . ${template} +} + +#gen_kerneldoc(class, meta, pfx, name, sfx, order, atomic, int, args...) +gen_kerneldoc() +{ + local class="$1"; shift + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + + local atomicname="${atomic}_${pfx}${name}${sfx}${order}" + + local tmpl="$(find_kerneldoc_template "${pfx}" "${name}" "${sfx}" "${order}")" + if [ -z "${tmpl}" ]; then + printf "/*\n" + printf " * No kerneldoc available for ${class}${atomicname}\n" + printf " */\n" + else + gen_template_kerneldoc "${tmpl}" "${class}" "${meta}" "${pfx}" "${name}" "${sfx}" "${order}" "$@" + fi +} + +#gen_proto_order_variants(meta, pfx, name, sfx, ...) +gen_proto_order_variants() +{ + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "" "$@" + + if meta_has_acquire "${meta}"; then + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "_acquire" "$@" + fi + if meta_has_release "${meta}"; then + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "_release" "$@" + fi + if meta_has_relaxed "${meta}"; then + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "_relaxed" "$@" + fi +} + +#gen_proto_variants(meta, name, ...) +gen_proto_variants() +{ + local meta="$1"; shift + local name="$1"; shift + local pfx="" + local sfx="" + + meta_in "${meta}" "fF" && pfx="fetch_" + meta_in "${meta}" "R" && sfx="_return" + + gen_proto_order_variants "${meta}" "${pfx}" "${name}" "${sfx}" "$@" +} + +#gen_proto(meta, ...) +gen_proto() { + local meta="$1"; shift + for m in $(echo "${meta}" | grep -o .); do + gen_proto_variants "${m}" "$@" + done +} diff --git a/scripts/atomic/atomics.tbl b/scripts/atomic/atomics.tbl new file mode 100644 index 000000000..903946cbf --- /dev/null +++ b/scripts/atomic/atomics.tbl @@ -0,0 +1,41 @@ +# name meta args... +# +# Where meta contains a string of variants to generate. +# Upper-case implies _{acquire,release,relaxed} variants. +# Valid meta values are: +# * B/b - bool: returns bool +# * v - void: returns void +# * I/i - int: returns base type +# * R - return: returns base type (has _return variants) +# * F/f - fetch: returns base type (has fetch_ variants) +# * l - load: returns base type (has _acquire order variant) +# * s - store: returns void (has _release order variant) +# +# Where args contains list of type[:name], where type is: +# * cv - const pointer to atomic base type (atomic_t/atomic64_t/atomic_long_t) +# * v - pointer to atomic base type (atomic_t/atomic64_t/atomic_long_t) +# * i - base type (int/s64/long) +# * p - pointer to base type (int/s64/long) +# +read l cv +set s v i +add vRF i v +sub vRF i v +inc vRF v +dec vRF v +and vF i v +andnot vF i v +or vF i v +xor vF i v +xchg I v i:new +cmpxchg I v i:old i:new +try_cmpxchg B v p:old i:new +sub_and_test b i v +dec_and_test b v +inc_and_test b v +add_negative B i v +add_unless fb v i:a i:u +inc_not_zero b v +inc_unless_negative b v +dec_unless_positive b v +dec_if_positive i v diff --git a/scripts/atomic/fallbacks/acquire b/scripts/atomic/fallbacks/acquire new file mode 100755 index 000000000..4da0cab36 --- /dev/null +++ b/scripts/atomic/fallbacks/acquire @@ -0,0 +1,5 @@ +cat <counter, old, new); +EOF diff --git a/scripts/atomic/fallbacks/dec b/scripts/atomic/fallbacks/dec new file mode 100755 index 000000000..60d286d40 --- /dev/null +++ b/scripts/atomic/fallbacks/dec @@ -0,0 +1,3 @@ +cat < 0)) + return false; + } while (!raw_${atomic}_try_cmpxchg(v, &c, c - 1)); + + return true; +EOF diff --git a/scripts/atomic/fallbacks/fence b/scripts/atomic/fallbacks/fence new file mode 100755 index 000000000..40d5b3976 --- /dev/null +++ b/scripts/atomic/fallbacks/fence @@ -0,0 +1,7 @@ +cat <counter); + } else { + ret = raw_${atomic}_read(v); + __atomic_acquire_fence(); + } + + return ret; +EOF diff --git a/scripts/atomic/fallbacks/release b/scripts/atomic/fallbacks/release new file mode 100755 index 000000000..1e6daf57b --- /dev/null +++ b/scripts/atomic/fallbacks/release @@ -0,0 +1,4 @@ +cat <counter, i); + } else { + __atomic_release_fence(); + raw_${atomic}_set(v, i); + } +EOF diff --git a/scripts/atomic/fallbacks/sub_and_test b/scripts/atomic/fallbacks/sub_and_test new file mode 100755 index 000000000..d1f746fe0 --- /dev/null +++ b/scripts/atomic/fallbacks/sub_and_test @@ -0,0 +1,3 @@ +cat <counter, new); +EOF diff --git a/scripts/atomic/gen-atomic-fallback.sh b/scripts/atomic/gen-atomic-fallback.sh new file mode 100755 index 000000000..f80d69cfe --- /dev/null +++ b/scripts/atomic/gen-atomic-fallback.sh @@ -0,0 +1,336 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +ATOMICDIR=$(dirname $0) + +. ${ATOMICDIR}/atomic-tbl.sh + +#gen_template_fallback(template, meta, pfx, name, sfx, order, atomic, int, args...) +gen_template_fallback() +{ + local template="$1"; shift + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + local atomic="$1"; shift + local int="$1"; shift + + local ret="$(gen_ret_type "${meta}" "${int}")" + local retstmt="$(gen_ret_stmt "${meta}")" + local params="$(gen_params "${int}" "${atomic}" "$@")" + local args="$(gen_args "$@")" + + . ${template} +} + +#gen_order_fallback(meta, pfx, name, sfx, order, atomic, int, args...) +gen_order_fallback() +{ + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + + local tmpl_order=${order#_} + local tmpl="${ATOMICDIR}/fallbacks/${tmpl_order:-fence}" + gen_template_fallback "${tmpl}" "${meta}" "${pfx}" "${name}" "${sfx}" "${order}" "$@" +} + +#gen_proto_fallback(meta, pfx, name, sfx, order, atomic, int, args...) +gen_proto_fallback() +{ + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + + local tmpl="$(find_fallback_template "${pfx}" "${name}" "${sfx}" "${order}")" + gen_template_fallback "${tmpl}" "${meta}" "${pfx}" "${name}" "${sfx}" "${order}" "$@" +} + +#gen_proto_order_variant(meta, pfx, name, sfx, order, atomic, int, args...) +gen_proto_order_variant() +{ + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + local atomic="$1"; shift + local int="$1"; shift + + local atomicname="${atomic}_${pfx}${name}${sfx}${order}" + local basename="${atomic}_${pfx}${name}${sfx}" + + local template="$(find_fallback_template "${pfx}" "${name}" "${sfx}" "${order}")" + + local ret="$(gen_ret_type "${meta}" "${int}")" + local retstmt="$(gen_ret_stmt "${meta}")" + local params="$(gen_params "${int}" "${atomic}" "$@")" + local args="$(gen_args "$@")" + + gen_kerneldoc "raw_" "${meta}" "${pfx}" "${name}" "${sfx}" "${order}" "${atomic}" "${int}" "$@" + + printf "static __always_inline ${ret}\n" + printf "raw_${atomicname}(${params})\n" + printf "{\n" + + # Where there is no possible fallback, this order variant is mandatory + # and must be provided by arch code. Add a comment to the header to + # make this obvious. + # + # Ideally we'd error on a missing definition, but arch code might + # define this order variant as a C function without a preprocessor + # symbol. + if [ -z ${template} ] && [ -z "${order}" ] && ! meta_has_relaxed "${meta}"; then + printf "\t${retstmt}arch_${atomicname}(${args});\n" + printf "}\n\n" + return + fi + + printf "#if defined(arch_${atomicname})\n" + printf "\t${retstmt}arch_${atomicname}(${args});\n" + + # Allow FULL/ACQUIRE/RELEASE ops to be defined in terms of RELAXED ops + if [ "${order}" != "_relaxed" ] && meta_has_relaxed "${meta}"; then + printf "#elif defined(arch_${basename}_relaxed)\n" + gen_order_fallback "${meta}" "${pfx}" "${name}" "${sfx}" "${order}" "${atomic}" "${int}" "$@" + fi + + # Allow ACQUIRE/RELEASE/RELAXED ops to be defined in terms of FULL ops + if [ ! -z "${order}" ] && ! meta_is_implicitly_relaxed "${meta}"; then + printf "#elif defined(arch_${basename})\n" + printf "\t${retstmt}arch_${basename}(${args});\n" + fi + + printf "#else\n" + if [ ! -z "${template}" ]; then + gen_proto_fallback "${meta}" "${pfx}" "${name}" "${sfx}" "${order}" "${atomic}" "${int}" "$@" + else + printf "#error \"Unable to define raw_${atomicname}\"\n" + fi + + printf "#endif\n" + printf "}\n\n" +} + + +#gen_proto_order_variants(meta, pfx, name, sfx, atomic, int, args...) +gen_proto_order_variants() +{ + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local atomic="$1" + + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "" "$@" + + if meta_has_acquire "${meta}"; then + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "_acquire" "$@" + fi + + if meta_has_release "${meta}"; then + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "_release" "$@" + fi + + if meta_has_relaxed "${meta}"; then + gen_proto_order_variant "${meta}" "${pfx}" "${name}" "${sfx}" "_relaxed" "$@" + fi +} + +#gen_basic_fallbacks(basename) +gen_basic_fallbacks() +{ + local basename="$1"; shift +cat << EOF +#define raw_${basename}_acquire arch_${basename} +#define raw_${basename}_release arch_${basename} +#define raw_${basename}_relaxed arch_${basename} +EOF +} + +gen_order_fallbacks() +{ + local xchg="$1"; shift + +cat < + +EOF + +for xchg in "xchg" "cmpxchg" "cmpxchg64" "cmpxchg128"; do + gen_xchg_fallbacks "${xchg}" +done + +for cmpxchg in "cmpxchg" "cmpxchg64" "cmpxchg128"; do + gen_try_cmpxchg_order_fallbacks "${cmpxchg}" +done + +for cmpxchg in "cmpxchg" "cmpxchg64" "cmpxchg128"; do + gen_def_and_try_cmpxchg_fallback "" "${cmpxchg}" "_local" +done + +for cmpxchg in "cmpxchg"; do + gen_def_and_try_cmpxchg_fallback "sync_" "${cmpxchg}" "" +done + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" "atomic" "int" ${args} +done + +cat < +#endif + +EOF + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" "atomic64" "s64" ${args} +done + +cat < +#include +#include + +EOF + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" "atomic" "int" ${args} +done + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" "atomic64" "s64" ${args} +done + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" "atomic_long" "long" ${args} +done + + +for xchg in "xchg" "cmpxchg" "cmpxchg64" "cmpxchg128" "try_cmpxchg" "try_cmpxchg64" "try_cmpxchg128"; do + for order in "" "_acquire" "_release" "_relaxed"; do + gen_xchg "${xchg}" "${order}" + printf "\n" + done +done + +for xchg in "cmpxchg_local" "cmpxchg64_local" "cmpxchg128_local" "sync_cmpxchg" \ + "try_cmpxchg_local" "try_cmpxchg64_local" "try_cmpxchg128_local" "sync_try_cmpxchg"; do + gen_xchg "${xchg}" "" + printf "\n" +done + +cat < +#include + +#ifdef CONFIG_64BIT +typedef atomic64_t atomic_long_t; +#define ATOMIC_LONG_INIT(i) ATOMIC64_INIT(i) +#define atomic_long_cond_read_acquire atomic64_cond_read_acquire +#define atomic_long_cond_read_relaxed atomic64_cond_read_relaxed +#else +typedef atomic_t atomic_long_t; +#define ATOMIC_LONG_INIT(i) ATOMIC_INIT(i) +#define atomic_long_cond_read_acquire atomic_cond_read_acquire +#define atomic_long_cond_read_relaxed atomic_cond_read_relaxed +#endif + +EOF + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" ${args} +done + +cat < ${LINUXDIR}/include/${header} + HASH="$(sha1sum ${LINUXDIR}/include/${header})" + HASH="${HASH%% *}" + printf "// %s\n" "${HASH}" >> ${LINUXDIR}/include/${header} +done diff --git a/scripts/atomic/gen-rust-atomic-helpers.sh b/scripts/atomic/gen-rust-atomic-helpers.sh new file mode 100755 index 000000000..a3732153a --- /dev/null +++ b/scripts/atomic/gen-rust-atomic-helpers.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +ATOMICDIR=$(dirname $0) + +. ${ATOMICDIR}/atomic-tbl.sh + +#gen_proto_order_variant(meta, pfx, name, sfx, order, atomic, int, arg...) +gen_proto_order_variant() +{ + local meta="$1"; shift + local pfx="$1"; shift + local name="$1"; shift + local sfx="$1"; shift + local order="$1"; shift + local atomic="$1"; shift + local int="$1"; shift + + local atomicname="${atomic}_${pfx}${name}${sfx}${order}" + + local ret="$(gen_ret_type "${meta}" "${int}")" + local params="$(gen_params "${int}" "${atomic}" "$@")" + local args="$(gen_args "$@")" + local retstmt="$(gen_ret_stmt "${meta}")" + +cat < + +EOF + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" "atomic" "int" ${args} +done + +grep '^[a-z]' "$1" | while read name meta args; do + gen_proto "${meta}" "${name}" "atomic64" "s64" ${args} +done + +cat < 0), atomically updates @v to (@v - 1) with ${desc_order} ordering. + * Otherwise, @v is not modified and relaxed ordering is provided. + * + * ${desc_noinstr} + * + * Return: The old value of (@v - 1), regardless of whether @v was updated. + */ +EOF diff --git a/scripts/atomic/kerneldoc/dec_unless_positive b/scripts/atomic/kerneldoc/dec_unless_positive new file mode 100644 index 000000000..06a678678 --- /dev/null +++ b/scripts/atomic/kerneldoc/dec_unless_positive @@ -0,0 +1,13 @@ +cat <= 0), atomically updates @v to (@v + 1) with ${desc_order} ordering. + * Otherwise, @v is not modified and relaxed ordering is provided. + * + * ${desc_noinstr} + * + * Return: @true if @v was updated, @false otherwise. + */ +EOF diff --git a/scripts/atomic/kerneldoc/or b/scripts/atomic/kerneldoc/or new file mode 100644 index 000000000..55b33de50 --- /dev/null +++ b/scripts/atomic/kerneldoc/or @@ -0,0 +1,13 @@ +cat </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/.gitignore b/scripts/basic/.gitignore new file mode 100644 index 000000000..07c195f60 --- /dev/null +++ b/scripts/basic/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +/fixdep +/randstruct.seed diff --git a/scripts/basic/Makefile b/scripts/basic/Makefile new file mode 100644 index 000000000..fb8e2c38f --- /dev/null +++ b/scripts/basic/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# fixdep: used to generate dependency information during build process + +hostprogs-always-y += fixdep + +# randstruct: the seed is needed before building the gcc-plugin or +# before running a Clang kernel build. +gen-randstruct-seed := $(srctree)/scripts/gen-randstruct-seed.sh +quiet_cmd_create_randstruct_seed = GENSEED $@ +cmd_create_randstruct_seed = \ + $(CONFIG_SHELL) $(gen-randstruct-seed) \ + $@ $(objtree)/include/generated/randstruct_hash.h +$(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 new file mode 100644 index 000000000..54063d980 --- /dev/null +++ b/scripts/basic/fixdep.c @@ -0,0 +1,440 @@ +/* + * "Optimize" a list of dependencies as spit out by gcc -MD + * for the kernel build + * =========================================================================== + * + * Author Kai Germaschewski + * Copyright 2002 by Kai Germaschewski + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * + * Introduction: + * + * gcc produces a very nice and correct list of dependencies which + * tells make when to remake a file. + * + * However, to use this list as-is has the drawback that virtually + * every file in the kernel includes autoconf.h. + * + * If the user re-runs make *config, autoconf.h will be + * regenerated. make notices that and will rebuild every file which + * includes autoconf.h, i.e. basically all files. This is extremely + * annoying if the user just changed CONFIG_USER_DRIVER from n to m. + * + * So we play the same trick that "mkdep" played before. We replace + * the dependency on autoconf.h by a dependency on every config + * option which is mentioned in any of the listed prerequisites. + * + * kconfig populates a tree in include/config/ with an empty file + * for each config symbol and when the configuration is updated + * the files representing changed config options are touched + * which then let make pick up the changes and the files that use + * the config symbols are rebuilt. + * + * So if the user changes their CONFIG_USER_DRIVER option, only the objects + * which depend on "include/config/USER_DRIVER" will be rebuilt, + * so most likely only the user's driver ;-) + * + * The idea above dates, by the way, back to Michael E Chastain, AFAIK. + * + * So to get dependencies right, there are two issues: + * o if any of the files the compiler read changed, we need to rebuild + * o if the command line given to the compile the file changed, we + * better rebuild as well. + * + * The former is handled by using the -MD output, the later by saving + * the command line used to compile the old object and comparing it + * to the one we would now use. + * + * Again, also this idea is pretty old and has been discussed on + * kbuild-devel a long time ago. I don't have a sensibly working + * internet connection right now, so I rather don't mention names + * without double checking. + * + * This code here has been based partially based on mkdep.c, which + * says the following about its history: + * + * Copyright abandoned, Michael Chastain, . + * This is a C version of syncdep.pl by Werner Almesberger. + * + * + * It is invoked as + * + * fixdep + * + * and will read the dependency file + * + * The transformed dependency snipped is written to stdout. + * + * It first generates a line + * + * savedcmd_ = + * + * and then basically copies the ..d file to stdout, in the + * process filtering out the dependency on autoconf.h and adding + * dependencies on include/config/MY_OPTION for every + * CONFIG_MY_OPTION encountered in any of the prerequisites. + * + * We don't even try to really parse the header files, but + * merely grep, i.e. if CONFIG_FOO is mentioned in a comment, it will + * be picked up as well. It's not a problem with respect to + * correctness, since that can only give too many dependencies, thus + * we cannot miss a rebuild. Since people tend to not mention totally + * unrelated CONFIG_ options all over the place, it's not an + * efficiency problem either. + * + * (Note: it'd be easy to port over the complete mkdep state machine, + * but I don't think the added complexity is worth it) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static void usage(void) +{ + fprintf(stderr, "Usage: fixdep \n"); + exit(1); +} + +struct item { + struct item *next; + unsigned int len; + unsigned int hash; + char name[]; +}; + +#define HASHSZ 256 +static struct item *config_hashtab[HASHSZ], *file_hashtab[HASHSZ]; + +static unsigned int strhash(const char *str, unsigned int sz) +{ + /* fnv32 hash */ + unsigned int i, hash = 2166136261U; + + for (i = 0; i < sz; i++) + hash = (hash ^ str[i]) * 0x01000193; + return hash; +} + +/* + * Add a new value to the configuration string. + */ +static void add_to_hashtable(const char *name, int len, unsigned int hash, + struct item *hashtab[]) +{ + struct item *aux; + + aux = xmalloc(sizeof(*aux) + len); + memcpy(aux->name, name, len); + aux->len = len; + aux->hash = hash; + aux->next = hashtab[hash % HASHSZ]; + hashtab[hash % HASHSZ] = aux; +} + +/* + * Lookup a string in the hash table. If found, just return true. + * If not, add it to the hashtable and return false. + */ +static bool in_hashtable(const char *name, int len, struct item *hashtab[]) +{ + struct item *aux; + unsigned int hash = strhash(name, len); + + for (aux = hashtab[hash % HASHSZ]; aux; aux = aux->next) { + if (aux->hash == hash && aux->len == len && + memcmp(aux->name, name, len) == 0) + return true; + } + + add_to_hashtable(name, len, hash, hashtab); + + return false; +} + +/* + * Record the use of a CONFIG_* word. + */ +static void use_config(const char *m, int slen) +{ + if (in_hashtable(m, slen, config_hashtab)) + return; + + /* Print out a dependency path from a symbol name. */ + printf(" $(wildcard include/config/%.*s) \\\n", slen, m); +} + +/* test if s ends in sub */ +static int str_ends_with(const char *s, int slen, const char *sub) +{ + int sublen = strlen(sub); + + if (sublen > slen) + return 0; + + return !memcmp(s + slen - sublen, sub, sublen); +} + +static void parse_config_file(const char *p) +{ + const char *q, *r; + const char *start = p; + + while ((p = strstr(p, "CONFIG_"))) { + if (p > start && (isalnum(p[-1]) || p[-1] == '_')) { + p += 7; + continue; + } + p += 7; + q = p; + while (isalnum(*q) || *q == '_') + q++; + if (str_ends_with(p, q - p, "_MODULE")) + r = q - 7; + else + r = q; + if (r > p) + use_config(p, r - p); + p = q; + } +} + +static void *read_file(const char *filename) +{ + struct stat st; + int fd; + char *buf; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "fixdep: error opening file: "); + perror(filename); + exit(2); + } + if (fstat(fd, &st) < 0) { + fprintf(stderr, "fixdep: error fstat'ing file: "); + perror(filename); + exit(2); + } + buf = xmalloc(st.st_size + 1); + if (read(fd, buf, st.st_size) != st.st_size) { + perror("fixdep: read"); + exit(2); + } + buf[st.st_size] = '\0'; + close(fd); + + return buf; +} + +/* Ignore certain dependencies */ +static int is_ignored_file(const char *s, int len) +{ + return str_ends_with(s, len, "include/generated/autoconf.h"); +} + +/* Do not parse these files */ +static int is_no_parse_file(const char *s, int len) +{ + /* rustc may list binary files in dep-info */ + return str_ends_with(s, len, ".rlib") || + str_ends_with(s, len, ".rmeta") || + str_ends_with(s, len, ".so"); +} + +/* + * Important: The below generated source_foo.o and deps_foo.o variable + * assignments are parsed not only by make, but also by the rather simple + * parser in scripts/mod/sumversion.c. + */ +static void parse_dep_file(char *p, const char *target) +{ + bool saw_any_target = false; + bool is_target = true; + bool is_source = false; + bool need_parse; + char *q, saved_c; + + while (*p) { + /* handle some special characters first. */ + switch (*p) { + case '#': + /* + * skip comments. + * rustc may emit comments to dep-info. + */ + p++; + while (*p != '\0' && *p != '\n') { + /* + * escaped newlines continue the comment across + * multiple lines. + */ + if (*p == '\\') + p++; + p++; + } + continue; + case ' ': + case '\t': + /* skip whitespaces */ + p++; + continue; + case '\\': + /* + * backslash/newline combinations continue the + * statement. Skip it just like a whitespace. + */ + if (*(p + 1) == '\n') { + p += 2; + continue; + } + break; + case '\n': + /* + * Makefiles use a line-based syntax, where the newline + * is the end of a statement. After seeing a newline, + * we expect the next token is a target. + */ + p++; + is_target = true; + continue; + case ':': + /* + * assume the first dependency after a colon as the + * source file. + */ + p++; + is_target = false; + is_source = true; + continue; + } + + /* find the end of the token */ + q = p; + while (*q != ' ' && *q != '\t' && *q != '\n' && *q != '#' && *q != ':') { + if (*q == '\\') { + /* + * backslash/newline combinations work like as + * a whitespace, so this is the end of token. + */ + if (*(q + 1) == '\n') + break; + + /* escaped special characters */ + if (*(q + 1) == '#' || *(q + 1) == ':') { + memmove(p + 1, p, q - p); + p++; + } + + q++; + } + + if (*q == '\0') + break; + q++; + } + + /* Just discard the target */ + if (is_target) { + p = q; + continue; + } + + saved_c = *q; + *q = '\0'; + need_parse = false; + + /* + * Do not list the source file as dependency, so that kbuild is + * not confused if a .c file is rewritten into .S or vice versa. + * Storing it in source_* is needed for modpost to compute + * srcversions. + */ + if (is_source) { + /* + * The DT build rule concatenates multiple dep files. + * When processing them, only process the first source + * name, which will be the original one, and ignore any + * other source names, which will be intermediate + * temporary files. + * + * rustc emits the same dependency list for each + * emission type. It is enough to list the source name + * just once. + */ + if (!saw_any_target) { + saw_any_target = true; + printf("source_%s := %s\n\n", target, p); + printf("deps_%s := \\\n", target); + need_parse = true; + } + } else if (!is_ignored_file(p, q - p) && + !in_hashtable(p, q - p, file_hashtab)) { + printf(" %s \\\n", p); + need_parse = true; + } + + if (need_parse && !is_no_parse_file(p, q - p)) { + void *buf; + + buf = read_file(p); + parse_config_file(buf); + free(buf); + } + + is_source = false; + *q = saved_c; + p = q; + } + + if (!saw_any_target) { + fprintf(stderr, "fixdep: parse error; no targets found\n"); + exit(1); + } + + printf("\n%s: $(deps_%s)\n\n", target, target); + printf("$(deps_%s):\n", target); +} + +int main(int argc, char *argv[]) +{ + const char *depfile, *target, *cmdline; + void *buf; + + if (argc != 4) + usage(); + + depfile = argv[1]; + target = argv[2]; + cmdline = argv[3]; + + printf("savedcmd_%s := %s\n\n", target, cmdline); + + buf = read_file(depfile); + parse_dep_file(buf, target); + free(buf); + + fflush(stdout); + + /* + * In the intended usage, the stdout is redirected to .*.cmd files. + * Call ferror() to catch errors such as "No space left on device". + */ + if (ferror(stdout)) { + fprintf(stderr, "fixdep: not all data was written to the output\n"); + exit(1); + } + + return 0; +} diff --git a/scripts/bloat-o-meter b/scripts/bloat-o-meter new file mode 100755 index 000000000..5868a8b11 --- /dev/null +++ b/scripts/bloat-o-meter @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# Copyright 2004 Matt Mackall +# +# inspired by perl Bloat-O-Meter (c) 1997 by Andi Kleen +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import sys, os, re, argparse +from signal import signal, SIGPIPE, SIG_DFL + +signal(SIGPIPE, SIG_DFL) + +parser = argparse.ArgumentParser(description="Simple script used to compare the symbol sizes of 2 object files") +group = parser.add_mutually_exclusive_group() +group.add_argument('-c', help='categorize output based on symbol type', action='store_true') +group.add_argument('-d', help='Show delta of Data Section', action='store_true') +group.add_argument('-t', help='Show delta of text Section', action='store_true') +parser.add_argument('-p', dest='prefix', help='Arch prefix for the tool being used. Useful in cross build scenarios') +parser.add_argument('file_old', help='First file to compare') +parser.add_argument('file_new', help='Second file to compare') + +args = parser.parse_args() + +re_NUMBER = re.compile(r'\.[0-9]+') + +def getsizes(file, format): + sym = {} + nm = "nm" + if args.prefix: + nm = "{}nm".format(args.prefix) + + with os.popen("{} --size-sort {}".format(nm, file)) as f: + for line in f: + if line.startswith("\n") or ":" in line: + continue + size, type, name = line.split() + if type in format: + # strip generated symbols + if name.startswith("__mod_"): continue + if name.startswith("__se_sys"): continue + if name.startswith("__se_compat_sys"): continue + if name.startswith("__addressable_"): continue + if name.startswith("__noinstr_text_start"): continue + if name.startswith("_sdata"): continue + if name == "linux_banner": continue + if name == "vermagic": continue + # statics and some other optimizations adds random .NUMBER + name = re_NUMBER.sub('', name) + sym[name] = sym.get(name, 0) + int(size, 16) + return sym + +def calc(oldfile, newfile, format): + old = getsizes(oldfile, format) + new = getsizes(newfile, format) + grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0 + delta, common = [], {} + otot, ntot = 0, 0 + + for a in old: + if a in new: + common[a] = 1 + + for name in old: + otot += old[name] + if name not in common: + remove += 1 + down += old[name] + delta.append((-old[name], name)) + + for name in new: + ntot += new[name] + if name not in common: + add += 1 + up += new[name] + delta.append((new[name], name)) + + for name in common: + d = new.get(name, 0) - old.get(name, 0) + if d>0: grow, up = grow+1, up+d + if d<0: shrink, down = shrink+1, down-d + delta.append((d, name)) + + delta.sort(reverse=True) + return grow, shrink, add, remove, up, down, delta, old, new, otot, ntot + +def print_result(symboltype, symbolformat): + grow, shrink, add, remove, up, down, delta, old, new, otot, ntot = \ + calc(args.file_old, args.file_new, symbolformat) + + print("add/remove: %s/%s grow/shrink: %s/%s up/down: %s/%s (%s)" % \ + (add, remove, grow, shrink, up, -down, up-down)) + print("%-40s %7s %7s %+7s" % (symboltype, "old", "new", "delta")) + for d, n in delta: + if d: print("%-40s %7s %7s %+7d" % (n, old.get(n,"-"), new.get(n,"-"), d)) + + if otot: + percent = (ntot - otot) * 100.0 / otot + else: + percent = 0 + print("Total: Before=%d, After=%d, chg %+.2f%%" % (otot, ntot, percent)) + +if args.c: + print_result("Function", "tTwW") + print_result("Data", "dDbBvV") + print_result("RO Data", "rR") +elif args.d: + print_result("Data", "dDbBrRvV") +elif args.t: + print_result("Function", "tTwW") +else: + print_result("Function", "tTdDbBrRvVwW") diff --git a/scripts/bootgraph.pl b/scripts/bootgraph.pl new file mode 100755 index 000000000..79c903292 --- /dev/null +++ b/scripts/bootgraph.pl @@ -0,0 +1,224 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-only + +# Copyright 2008, Intel Corporation +# +# This file is part of the Linux kernel +# +# Authors: +# Arjan van de Ven + + +# +# This script turns a dmesg output into a SVG graphic that shows which +# functions take how much time. You can view SVG graphics with various +# programs, including Inkscape, The Gimp and Firefox. +# +# +# For this script to work, the kernel needs to be compiled with the +# CONFIG_PRINTK_TIME configuration option enabled, and with +# "initcall_debug" passed on the kernel command line. +# +# usage: +# dmesg | perl scripts/bootgraph.pl > output.svg +# + +use strict; +use Getopt::Long; +my $header = 0; + +sub help { + my $text = << "EOM"; +Usage: +1) dmesg | perl scripts/bootgraph.pl [OPTION] > output.svg +2) perl scripts/bootgraph.pl -h + +Options: + -header Insert kernel version and date +EOM + my $std=shift; + if ($std == 1) { + print STDERR $text; + } else { + print $text; + } + exit; +} + +GetOptions( + 'h|help' =>\&help, + 'header' =>\$header +); + +my %start; +my %end; +my %type; +my $done = 0; +my $maxtime = 0; +my $firsttime = 99999; +my $count = 0; +my %pids; +my %pidctr; + +my $headerstep = 20; +my $xheader = 15; +my $yheader = 25; +my $cyheader = 0; + +while (<>) { + my $line = $_; + if ($line =~ /([0-9\.]+)\] calling ([a-zA-Z0-9\_\.]+)\+/) { + my $func = $2; + if ($done == 0) { + $start{$func} = $1; + $type{$func} = 0; + if ($1 < $firsttime) { + $firsttime = $1; + } + } + if ($line =~ /\@ ([0-9]+)/) { + $pids{$func} = $1; + } + $count = $count + 1; + } + + if ($line =~ /([0-9\.]+)\] async_waiting @ ([0-9]+)/) { + my $pid = $2; + my $func; + if (!defined($pidctr{$pid})) { + $func = "wait_" . $pid . "_1"; + $pidctr{$pid} = 1; + } else { + $pidctr{$pid} = $pidctr{$pid} + 1; + $func = "wait_" . $pid . "_" . $pidctr{$pid}; + } + if ($done == 0) { + $start{$func} = $1; + $type{$func} = 1; + if ($1 < $firsttime) { + $firsttime = $1; + } + } + $pids{$func} = $pid; + $count = $count + 1; + } + + if ($line =~ /([0-9\.]+)\] initcall ([a-zA-Z0-9\_\.]+)\+.*returned/) { + if ($done == 0) { + $end{$2} = $1; + $maxtime = $1; + } + } + + if ($line =~ /([0-9\.]+)\] async_continuing @ ([0-9]+)/) { + my $pid = $2; + my $func = "wait_" . $pid . "_" . $pidctr{$pid}; + $end{$func} = $1; + $maxtime = $1; + } + if ($line =~ /Write protecting the/) { + $done = 1; + } + if ($line =~ /Freeing unused kernel memory/) { + $done = 1; + } +} + +if ($count == 0) { + print STDERR < \n"; +print "\n"; + + +if ($header) { + my $version = `uname -a`; + my $date = `date`; + print "Kernel version: $version\n"; + $cyheader = $yheader+$headerstep; + print "Date: $date\n"; +} + +my @styles; + +$styles[0] = "fill:rgb(0,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[1] = "fill:rgb(0,255,0);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[2] = "fill:rgb(255,0,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[3] = "fill:rgb(255,255,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[4] = "fill:rgb(255,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[5] = "fill:rgb(0,255,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[6] = "fill:rgb(0,128,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[7] = "fill:rgb(0,255,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[8] = "fill:rgb(255,0,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[9] = "fill:rgb(255,255,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[10] = "fill:rgb(255,128,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; +$styles[11] = "fill:rgb(128,255,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)"; + +my $style_wait = "fill:rgb(128,128,128);fill-opacity:0.5;stroke-width:0;stroke:rgb(0,0,0)"; + +my $mult = 1950.0 / ($maxtime - $firsttime); +my $threshold2 = ($maxtime - $firsttime) / 120.0; +my $threshold = $threshold2/10; +my $stylecounter = 0; +my %rows; +my $rowscount = 1; +my @initcalls = sort { $start{$a} <=> $start{$b} } keys(%start); + +foreach my $key (@initcalls) { + my $duration = $end{$key} - $start{$key}; + + if ($duration >= $threshold) { + my ($s, $s2, $s3, $e, $w, $y, $y2, $style); + my $pid = $pids{$key}; + + if (!defined($rows{$pid})) { + $rows{$pid} = $rowscount; + $rowscount = $rowscount + 1; + } + $s = ($start{$key} - $firsttime) * $mult; + $s2 = $s + 6; + $s3 = $s + 1; + $e = ($end{$key} - $firsttime) * $mult; + $w = $e - $s; + + $y = $rows{$pid} * 150; + $y2 = $y + 4; + + $style = $styles[$stylecounter]; + $stylecounter = $stylecounter + 1; + if ($stylecounter > 11) { + $stylecounter = 0; + }; + + if ($type{$key} == 1) { + $y = $y + 15; + print "\n"; + } else { + print "\n"; + if ($duration >= $threshold2) { + print "$key\n"; + } else { + print "$key\n"; + } + } + } +} + + +# print the time line on top +my $time = $firsttime; +my $step = ($maxtime - $firsttime) / 15; +while ($time < $maxtime) { + my $s3 = ($time - $firsttime) * $mult; + my $tm = int($time * 100) / 100.0; + print "$tm\n"; + $time = $time + $step; +} + +print "\n"; diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py new file mode 100755 index 000000000..15d113a1b --- /dev/null +++ b/scripts/bpf_doc.py @@ -0,0 +1,1020 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2018-2019 Netronome Systems, Inc. +# Copyright (C) 2021 Isovalent, Inc. + +# In case user attempts to run with Python 2. +from __future__ import print_function + +import argparse +import json +import re +import sys, os +import subprocess + +helpersDocStart = 'Start of BPF helper function descriptions:' + +class NoHelperFound(BaseException): + pass + +class NoSyscallCommandFound(BaseException): + pass + +class ParsingError(BaseException): + def __init__(self, line='', reader=None): + if reader: + BaseException.__init__(self, + 'Error at file offset %d, parsing line: %s' % + (reader.tell(), line)) + else: + BaseException.__init__(self, 'Error parsing line: %s' % line) + + +class APIElement(object): + """ + An object representing the description of an aspect of the eBPF API. + @proto: prototype of the API symbol + @desc: textual description of the symbol + @ret: (optional) description of any associated return value + """ + def __init__(self, proto='', desc='', ret=''): + self.proto = proto + self.desc = desc + self.ret = ret + + def to_dict(self): + return { + 'proto': self.proto, + 'desc': self.desc, + 'ret': self.ret + } + + +class Helper(APIElement): + """ + An object representing the description of an eBPF helper function. + @proto: function prototype of the helper function + @desc: textual description of the helper function + @ret: description of the return value of the helper function + """ + def __init__(self, proto='', desc='', ret='', attrs=[]): + super().__init__(proto, desc, ret) + self.attrs = attrs + self.enum_val = None + + def proto_break_down(self): + """ + Break down helper function protocol into smaller chunks: return type, + name, distincts arguments. + """ + arg_re = re.compile(r'((\w+ )*?(\w+|...))( (\**)(\w+))?$') + res = {} + proto_re = re.compile(r'(.+) (\**)(\w+)\(((([^,]+)(, )?){1,5})\)$') + + capture = proto_re.match(self.proto) + res['ret_type'] = capture.group(1) + res['ret_star'] = capture.group(2) + res['name'] = capture.group(3) + res['args'] = [] + + args = capture.group(4).split(', ') + for a in args: + capture = arg_re.match(a) + res['args'].append({ + 'type' : capture.group(1), + 'star' : capture.group(5), + 'name' : capture.group(6) + }) + + 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): + """ + An object used to parse a file in order to extract the documentation of a + list of eBPF helper functions. All the helpers that can be retrieved are + stored as Helper object, in the self.helpers() array. + @filename: name of file to parse, usually include/uapi/linux/bpf.h in the + kernel tree + """ + def __init__(self, filename): + self.reader = open(filename, 'r') + self.line = '' + self.helpers = [] + self.commands = [] + self.desc_unique_helpers = set() + self.define_unique_helpers = [] + self.helper_enum_vals = {} + self.helper_enum_pos = {} + self.desc_syscalls = [] + self.enum_syscalls = [] + + def parse_element(self): + proto = self.parse_symbol() + desc = self.parse_desc(proto) + ret = self.parse_ret(proto) + return APIElement(proto=proto, desc=desc, ret=ret) + + def parse_helper(self): + proto = self.parse_proto() + desc = self.parse_desc(proto) + ret = self.parse_ret(proto) + 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+)$') + capture = p.match(self.line) + if not capture: + raise NoSyscallCommandFound + end_re = re.compile(r' \* ?NOTES$') + end = end_re.match(self.line) + if end: + raise NoSyscallCommandFound + self.line = self.reader.readline() + return capture.group(1) + + def parse_proto(self): + # Argument can be of shape: + # - "void" + # - "type name" + # - "type *name" + # - Same as above, with "const" and/or "struct" in front of type + # - "..." (undefined number of arguments, for bpf_trace_printk()) + # There is at least one term ("void"), and at most five arguments. + p = re.compile(r' \* ?((.+) \**\w+\((((const )?(struct )?(\w+|\.\.\.)( \**\w+)?)(, )?){1,5}\))$') + capture = p.match(self.line) + if not capture: + raise NoHelperFound + self.line = self.reader.readline() + return capture.group(1) + + def parse_desc(self, proto): + p = re.compile(r' \* ?(?:\t| {5,8})Description$') + capture = p.match(self.line) + if not capture: + raise Exception("No description section found for " + proto) + # Description can be several lines, some of them possibly empty, and it + # stops when another subsection title is met. + desc = '' + desc_present = False + while True: + self.line = self.reader.readline() + if self.line == ' *\n': + desc += '\n' + else: + p = re.compile(r' \* ?(?:\t| {5,8})(?:\t| {8})(.*)') + capture = p.match(self.line) + if capture: + desc_present = True + desc += capture.group(1) + '\n' + else: + break + + if not desc_present: + raise Exception("No description found for " + proto) + return desc + + def parse_ret(self, proto): + p = re.compile(r' \* ?(?:\t| {5,8})Return$') + capture = p.match(self.line) + if not capture: + raise Exception("No return section found for " + proto) + # Return value description can be several lines, some of them possibly + # empty, and it stops when another subsection title is met. + ret = '' + ret_present = False + while True: + self.line = self.reader.readline() + if self.line == ' *\n': + ret += '\n' + else: + p = re.compile(r' \* ?(?:\t| {5,8})(?:\t| {8})(.*)') + capture = p.match(self.line) + if capture: + ret_present = True + ret += capture.group(1) + '\n' + else: + break + + if not ret_present: + 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) + if offset == -1: + raise Exception(help_message) + self.reader.seek(offset) + self.reader.readline() + for _ in range(discard_lines): + self.reader.readline() + self.line = self.reader.readline() + + def parse_desc_syscall(self): + self.seek_to('* DOC: eBPF Syscall Commands', + 'Could not find start of eBPF syscall descriptions list') + while True: + try: + command = self.parse_element() + self.commands.append(command) + self.desc_syscalls.append(command.proto) + + except NoSyscallCommandFound: + break + + def parse_enum_syscall(self): + self.seek_to('enum bpf_cmd {', + 'Could not find start of bpf_cmd enum', 0) + # Searches for either one or more BPF\w+ enums + bpf_p = re.compile(r'\s*(BPF\w+)+') + # Searches for an enum entry assigned to another entry, + # for e.g. BPF_PROG_RUN = BPF_PROG_TEST_RUN, which is + # not documented hence should be skipped in check to + # determine if the right number of syscalls are documented + assign_p = re.compile(r'\s*(BPF\w+)\s*=\s*(BPF\w+)') + bpf_cmd_str = '' + while True: + capture = assign_p.match(self.line) + if capture: + # Skip line if an enum entry is assigned to another entry + self.line = self.reader.readline() + continue + capture = bpf_p.match(self.line) + if capture: + bpf_cmd_str += self.line + else: + break + self.line = self.reader.readline() + # Find the number of occurences of BPF\w+ + self.enum_syscalls = re.findall(r'(BPF\w+)+', bpf_cmd_str) + + def parse_desc_helpers(self): + self.seek_to(helpersDocStart, + 'Could not find start of eBPF helper descriptions list') + while True: + try: + helper = self.parse_helper() + self.helpers.append(helper) + proto = helper.proto_break_down() + self.desc_unique_helpers.add(proto['name']) + except NoHelperFound: + break + + def parse_define_helpers(self): + # Parse FN(...) in #define ___BPF_FUNC_MAPPER to compare later with the + # number of unique function names present in description and use the + # correct enumeration value. + # Note: seek_to(..) discards the first line below the target search text, + # resulting in FN(unspec, 0, ##ctx) being skipped and not added to + # self.define_unique_helpers. + self.seek_to('#define ___BPF_FUNC_MAPPER(FN, ctx...)', + 'Could not find start of eBPF helper definition list') + # Searches for one FN(\w+) define or a backslash for newline + p = re.compile(r'\s*FN\((\w+), (\d+), ##ctx\)|\\\\') + fn_defines_str = '' + i = 0 + while True: + capture = p.match(self.line) + if capture: + fn_defines_str += self.line + helper_name = capture.expand(r'bpf_\1') + self.helper_enum_vals[helper_name] = int(capture.group(2)) + self.helper_enum_pos[helper_name] = i + i += 1 + else: + break + self.line = self.reader.readline() + # Find the number of occurences of FN(\w+) + self.define_unique_helpers = re.findall(r'FN\(\w+, \d+, ##ctx\)', fn_defines_str) + + def validate_helpers(self): + last_helper = '' + seen_helpers = set() + seen_enum_vals = set() + i = 0 + for helper in self.helpers: + proto = helper.proto_break_down() + name = proto['name'] + try: + enum_val = self.helper_enum_vals[name] + enum_pos = self.helper_enum_pos[name] + except KeyError: + raise Exception("Helper %s is missing from enum bpf_func_id" % name) + + if name in seen_helpers: + if last_helper != name: + raise Exception("Helper %s has multiple descriptions which are not grouped together" % name) + continue + + # Enforce current practice of having the descriptions ordered + # by enum value. + if enum_pos != i: + raise Exception("Helper %s (ID %d) comment order (#%d) must be aligned with its position (#%d) in enum bpf_func_id" % (name, enum_val, i + 1, enum_pos + 1)) + if enum_val in seen_enum_vals: + raise Exception("Helper %s has duplicated value %d" % (name, enum_val)) + + seen_helpers.add(name) + last_helper = name + seen_enum_vals.add(enum_val) + + helper.enum_val = enum_val + i += 1 + + def run(self): + self.parse_desc_syscall() + self.parse_enum_syscall() + self.parse_desc_helpers() + self.parse_define_helpers() + self.validate_helpers() + self.reader.close() + +############################################################################### + +class Printer(object): + """ + A generic class for printers. Printers should be created with an array of + Helper objects, and implement a way to print them in the desired fashion. + @parser: A HeaderParser with objects to print to standard output + """ + def __init__(self, parser): + self.parser = parser + self.elements = [] + + def print_header(self): + pass + + def print_footer(self): + pass + + def print_one(self, helper): + pass + + def print_all(self): + self.print_header() + for elem in self.elements: + self.print_one(elem) + self.print_footer() + + def elem_number_check(self, desc_unique_elem, define_unique_elem, type, instance): + """ + Checks the number of helpers/syscalls documented within the header file + description with those defined as part of enum/macro and raise an + Exception if they don't match. + """ + nr_desc_unique_elem = len(desc_unique_elem) + nr_define_unique_elem = len(define_unique_elem) + if nr_desc_unique_elem != nr_define_unique_elem: + exception_msg = ''' +The number of unique %s in description (%d) doesn\'t match the number of unique %s defined in %s (%d) +''' % (type, nr_desc_unique_elem, type, instance, nr_define_unique_elem) + if nr_desc_unique_elem < nr_define_unique_elem: + # Function description is parsed until no helper is found (which can be due to + # misformatting). Hence, only print the first missing/misformatted helper/enum. + exception_msg += ''' +The description for %s is not present or formatted correctly. +''' % (define_unique_elem[nr_desc_unique_elem]) + raise Exception(exception_msg) + +class PrinterRST(Printer): + """ + A generic class for printers that print ReStructured Text. Printers should + be created with a HeaderParser object, and implement a way to print API + elements in the desired fashion. + @parser: A HeaderParser with objects to print to standard output + """ + def __init__(self, parser): + self.parser = parser + + def print_license(self): + license = '''\ +.. Copyright (C) All BPF authors and contributors from 2014 to present. +.. See git log include/uapi/linux/bpf.h in kernel tree for details. +.. +.. SPDX-License-Identifier: Linux-man-pages-copyleft +.. +.. Please do not edit this file. It was generated from the documentation +.. located in file include/uapi/linux/bpf.h of the Linux kernel sources +.. (helpers description), and from scripts/bpf_doc.py in the same +.. repository (header and footer). +''' + print(license) + + def print_elem(self, elem): + if (elem.desc): + print('\tDescription') + # Do not strip all newline characters: formatted code at the end of + # a section must be followed by a blank line. + for line in re.sub('\n$', '', elem.desc, count=1).split('\n'): + print('{}{}'.format('\t\t' if line else '', line)) + + if (elem.ret): + print('\tReturn') + for line in elem.ret.rstrip().split('\n'): + print('{}{}'.format('\t\t' if line else '', line)) + + print('') + + def get_kernel_version(self): + try: + version = subprocess.run(['git', 'describe'], cwd=linuxRoot, + capture_output=True, check=True) + version = version.stdout.decode().rstrip() + except: + try: + version = subprocess.run(['make', '-s', '--no-print-directory', 'kernelversion'], + cwd=linuxRoot, capture_output=True, check=True) + version = version.stdout.decode().rstrip() + except: + return 'Linux' + return 'Linux {version}'.format(version=version) + + def get_last_doc_update(self, delimiter): + try: + cmd = ['git', 'log', '-1', '--pretty=format:%cs', '--no-patch', + '-L', + '/{}/,/\\*\\//:include/uapi/linux/bpf.h'.format(delimiter)] + date = subprocess.run(cmd, cwd=linuxRoot, + capture_output=True, check=True) + return date.stdout.decode().rstrip() + except: + return '' + +class PrinterHelpersRST(PrinterRST): + """ + A printer for dumping collected information about helpers as a ReStructured + Text page compatible with the rst2man program, which can be used to + generate a manual page for the helpers. + @parser: A HeaderParser with Helper objects to print to standard output + """ + 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_header(self): + header = '''\ +=========== +BPF-HELPERS +=========== +------------------------------------------------------------------------------- +list of eBPF helper functions +------------------------------------------------------------------------------- + +:Manual section: 7 +:Version: {version} +{date_field}{date} + +DESCRIPTION +=========== + +The extended Berkeley Packet Filter (eBPF) subsystem consists in programs +written in a pseudo-assembly language, then attached to one of the several +kernel hooks and run in reaction of specific events. This framework differs +from the older, "classic" BPF (or "cBPF") in several aspects, one of them being +the ability to call special functions (or "helpers") from within a program. +These functions are restricted to a white-list of helpers defined in the +kernel. + +These helpers are used by eBPF programs to interact with the system, or with +the context in which they work. For instance, they can be used to print +debugging messages, to get the time since the system was booted, to interact +with eBPF maps, or to manipulate network packets. Since there are several eBPF +program types, and that they do not run in the same context, each program type +can only call a subset of those helpers. + +Due to eBPF conventions, a helper can not have more than five arguments. + +Internally, eBPF programs call directly into the compiled helper functions +without requiring any foreign-function interface. As a result, calling helpers +introduces no overhead, thus offering excellent performance. + +This document is an attempt to list and document the helpers available to eBPF +developers. They are sorted by chronological order (the oldest helpers in the +kernel at the top). + +HELPERS +======= +''' + kernelVersion = self.get_kernel_version() + lastUpdate = self.get_last_doc_update(helpersDocStart) + + PrinterRST.print_license(self) + print(header.format(version=kernelVersion, + date_field = ':Date: ' if lastUpdate else '', + date=lastUpdate)) + + def print_footer(self): + footer = ''' +EXAMPLES +======== + +Example usage for most of the eBPF helpers listed in this manual page are +available within the Linux kernel sources, at the following locations: + +* *samples/bpf/* +* *tools/testing/selftests/bpf/* + +LICENSE +======= + +eBPF programs can have an associated license, passed along with the bytecode +instructions to the kernel when the programs are loaded. The format for that +string is identical to the one in use for kernel modules (Dual licenses, such +as "Dual BSD/GPL", may be used). Some helper functions are only accessible to +programs that are compatible with the GNU General Public License (GNU GPL). + +In order to use such helpers, the eBPF program must be loaded with the correct +license string passed (via **attr**) to the **bpf**\\ () system call, and this +generally translates into the C source code of the program containing a line +similar to the following: + +:: + + char ____license[] __attribute__((section("license"), used)) = "GPL"; + +IMPLEMENTATION +============== + +This manual page is an effort to document the existing eBPF helper functions. +But as of this writing, the BPF sub-system is under heavy development. New eBPF +program or map types are added, along with new helper functions. Some helpers +are occasionally made available for additional program types. So in spite of +the efforts of the community, this page might not be up-to-date. If you want to +check by yourself what helper functions exist in your kernel, or what types of +programs they can support, here are some files among the kernel tree that you +may be interested in: + +* *include/uapi/linux/bpf.h* is the main BPF header. It contains the full list + of all helper functions, as well as many other BPF definitions including most + of the flags, structs or constants used by the helpers. +* *net/core/filter.c* contains the definition of most network-related helper + functions, and the list of program types from which they can be used. +* *kernel/trace/bpf_trace.c* is the equivalent for most tracing program-related + helpers. +* *kernel/bpf/verifier.c* contains the functions used to check that valid types + of eBPF maps are used with a given helper function. +* *kernel/bpf/* directory contains other files in which additional helpers are + defined (for cgroups, sockmaps, etc.). +* The bpftool utility can be used to probe the availability of helper functions + on the system (as well as supported program and map types, and a number of + other parameters). To do so, run **bpftool feature probe** (see + **bpftool-feature**\\ (8) for details). Add the **unprivileged** keyword to + list features available to unprivileged users. + +Compatibility between helper functions and program types can generally be found +in the files where helper functions are defined. Look for the **struct +bpf_func_proto** objects and for functions returning them: these functions +contain a list of helpers that a given program type can call. Note that the +**default:** label of the **switch ... case** used to filter helpers can call +other functions, themselves allowing access to additional helpers. The +requirement for GPL license is also in those **struct bpf_func_proto**. + +Compatibility between helper functions and map types can be found in the +**check_map_func_compatibility**\\ () function in file *kernel/bpf/verifier.c*. + +Helper functions that invalidate the checks on **data** and **data_end** +pointers for network processing are listed in function +**bpf_helper_changes_pkt_data**\\ () in file *net/core/filter.c*. + +SEE ALSO +======== + +**bpf**\\ (2), +**bpftool**\\ (8), +**cgroups**\\ (7), +**ip**\\ (8), +**perf_event_open**\\ (2), +**sendmsg**\\ (2), +**socket**\\ (7), +**tc-bpf**\\ (8)''' + print(footer) + + def print_proto(self, helper): + """ + Format function protocol with bold and italics markers. This makes RST + file less readable, but gives nice results in the manual page. + """ + proto = helper.proto_break_down() + + print('**%s %s%s(' % (proto['ret_type'], + proto['ret_star'].replace('*', '\\*'), + proto['name']), + end='') + + comma = '' + for a in proto['args']: + one_arg = '{}{}'.format(comma, a['type']) + if a['name']: + if a['star']: + one_arg += ' {}**\\ '.format(a['star'].replace('*', '\\*')) + else: + one_arg += '** ' + one_arg += '*{}*\\ **'.format(a['name']) + comma = ', ' + print(one_arg, end='') + + print(')**') + + def print_one(self, helper): + self.print_proto(helper) + self.print_elem(helper) + + +class PrinterSyscallRST(PrinterRST): + """ + A printer for dumping collected information about the syscall API as a + ReStructured Text page compatible with the rst2man program, which can be + used to generate a manual page for the syscall. + @parser: A HeaderParser with APIElement objects to print to standard + output + """ + def __init__(self, parser): + self.elements = parser.commands + self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd') + + def print_header(self): + header = '''\ +=== +bpf +=== +------------------------------------------------------------------------------- +Perform a command on an extended BPF object +------------------------------------------------------------------------------- + +:Manual section: 2 + +COMMANDS +======== +''' + PrinterRST.print_license(self) + print(header) + + def print_one(self, command): + print('**%s**' % (command.proto)) + self.print_elem(command) + + +class PrinterHelpersHeader(Printer): + """ + A printer for dumping collected information about helpers as C header to + be included from BPF program. + @parser: A HeaderParser with Helper objects to print to standard output + """ + def __init__(self, parser): + self.elements = parser.helpers + self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '___BPF_FUNC_MAPPER') + + type_fwds = [ + 'struct bpf_fib_lookup', + 'struct bpf_sk_lookup', + 'struct bpf_perf_event_data', + 'struct bpf_perf_event_value', + 'struct bpf_pidns_info', + 'struct bpf_redir_neigh', + 'struct bpf_sock', + 'struct bpf_sock_addr', + 'struct bpf_sock_ops', + 'struct bpf_sock_tuple', + 'struct bpf_spin_lock', + 'struct bpf_sysctl', + 'struct bpf_tcp_sock', + 'struct bpf_tunnel_key', + 'struct bpf_xfrm_state', + 'struct linux_binprm', + 'struct pt_regs', + 'struct sk_reuseport_md', + 'struct sockaddr', + 'struct tcphdr', + 'struct seq_file', + 'struct tcp6_sock', + 'struct tcp_sock', + 'struct tcp_timewait_sock', + 'struct tcp_request_sock', + 'struct udp6_sock', + 'struct unix_sock', + 'struct task_struct', + 'struct cgroup', + + 'struct __sk_buff', + 'struct sk_msg_md', + 'struct xdp_md', + 'struct path', + 'struct btf_ptr', + 'struct inode', + 'struct socket', + 'struct file', + 'struct bpf_timer', + 'struct mptcp_sock', + 'struct bpf_dynptr', + 'struct iphdr', + 'struct ipv6hdr', + ] + known_types = { + '...', + 'void', + 'const void', + 'char', + 'const char', + 'int', + 'long', + 'unsigned long', + + '__be16', + '__be32', + '__wsum', + + 'struct bpf_fib_lookup', + 'struct bpf_perf_event_data', + 'struct bpf_perf_event_value', + 'struct bpf_pidns_info', + 'struct bpf_redir_neigh', + 'struct bpf_sk_lookup', + 'struct bpf_sock', + 'struct bpf_sock_addr', + 'struct bpf_sock_ops', + 'struct bpf_sock_tuple', + 'struct bpf_spin_lock', + 'struct bpf_sysctl', + 'struct bpf_tcp_sock', + 'struct bpf_tunnel_key', + 'struct bpf_xfrm_state', + 'struct linux_binprm', + 'struct pt_regs', + 'struct sk_reuseport_md', + 'struct sockaddr', + 'struct tcphdr', + 'struct seq_file', + 'struct tcp6_sock', + 'struct tcp_sock', + 'struct tcp_timewait_sock', + 'struct tcp_request_sock', + 'struct udp6_sock', + 'struct unix_sock', + 'struct task_struct', + 'struct cgroup', + 'struct path', + 'const struct path', + 'struct btf_ptr', + 'struct inode', + 'struct socket', + 'struct file', + 'struct bpf_timer', + 'struct mptcp_sock', + 'struct bpf_dynptr', + 'const struct bpf_dynptr', + 'struct iphdr', + 'struct ipv6hdr', + } + mapped_types = { + 'u8': '__u8', + 'u16': '__u16', + 'u32': '__u32', + 'u64': '__u64', + 's8': '__s8', + 's16': '__s16', + 's32': '__s32', + 's64': '__s64', + 'size_t': 'unsigned long', + 'struct bpf_map': 'void', + 'struct sk_buff': 'struct __sk_buff', + 'const struct sk_buff': 'const struct __sk_buff', + 'struct sk_msg_buff': 'struct sk_msg_md', + 'struct xdp_buff': 'struct xdp_md', + } + # Helpers overloaded for different context types. + overloaded_helpers = [ + 'bpf_get_socket_cookie', + 'bpf_sk_assign', + ] + + def print_header(self): + header = '''\ +/* This is auto-generated file. See bpf_doc.py for details. */ + +/* Forward declarations of BPF structs */''' + + print(header) + for fwd in self.type_fwds: + 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) + + def map_type(self, t): + if t in self.known_types: + return t + if t in self.mapped_types: + return self.mapped_types[t] + print("Unrecognized type '%s', please add it to known types!" % t, + file=sys.stderr) + sys.exit(1) + + seen_helpers = set() + + def print_one(self, helper): + proto = helper.proto_break_down() + + if proto['name'] in self.seen_helpers: + return + self.seen_helpers.add(proto['name']) + + print('/*') + print(" * %s" % proto['name']) + print(" *") + if (helper.desc): + # Do not strip all newline characters: formatted code at the end of + # a section must be followed by a blank line. + for line in re.sub('\n$', '', helper.desc, count=1).split('\n'): + print(' *{}{}'.format(' \t' if line else '', line)) + + if (helper.ret): + print(' *') + print(' * Returns') + for line in helper.ret.rstrip().split('\n'): + print(' *{}{}'.format(' \t' if line else '', line)) + + print(' */') + 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']): + t = a['type'] + n = a['name'] + if proto['name'] in self.overloaded_helpers and i == 0: + t = 'void' + n = 'ctx' + one_arg = '{}{}'.format(comma, self.map_type(t)) + if n: + if a['star']: + one_arg += ' {}'.format(a['star']) + else: + one_arg += ' ' + one_arg += '{}'.format(n) + comma = ', ' + print(one_arg, end='') + + 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 +# ../include/uapi/linux/bpf.h, use it as a default name for the file to parse, +# otherwise the --filename argument will be required from the command line. +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': { + 'rst': PrinterHelpersRST, + 'json': PrinterHelpersJSON, + 'header': PrinterHelpersHeader, + }, + 'syscall': { + 'rst': PrinterSyscallRST, + 'json': PrinterSyscallJSON + }, +} + +argParser = argparse.ArgumentParser(description=""" +Parse eBPF header file and generate documentation for the eBPF API. +The RST-formatted output produced can be turned into a manual page with the +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) +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') + +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 000000000..537d45815 --- /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/cc-can-link.sh b/scripts/cc-can-link.sh new file mode 100755 index 000000000..58dc7dd6d --- /dev/null +++ b/scripts/cc-can-link.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +cat << "END" | $@ -Werror -Wl,--fatal-warnings -x c - -o /dev/null >/dev/null 2>&1 +#include +int main(void) +{ + printf("\n"); + return 0; +} +END diff --git a/scripts/cc-version.sh b/scripts/cc-version.sh new file mode 100755 index 000000000..a7e28b6a5 --- /dev/null +++ b/scripts/cc-version.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Print the C compiler name and its version in a 5 or 6-digit form. +# Also, perform the minimum version check. + +set -e + +# Print the C compiler name and some version components. +get_c_compiler_info() +{ + cat <<- EOF | "$@" -E -P -x c - 2>/dev/null + #if defined(__clang__) + Clang __clang_major__ __clang_minor__ __clang_patchlevel__ + #elif defined(__GNUC__) + GCC __GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__ + #else + unknown + #endif + EOF +} + +# Convert the version string x.y.z to a canonical 5 or 6-digit form. +get_canonical_version() +{ + IFS=. + set -- $1 + echo $((10000 * $1 + 100 * $2 + $3)) +} + +# $@ instead of $1 because multiple words might be given, e.g. CC="ccache gcc". +orig_args="$@" +set -- $(get_c_compiler_info "$@") + +name=$1 + +min_tool_version=$(dirname $0)/min-tool-version.sh + +case "$name" in +GCC) + version=$2.$3.$4 + min_version=$($min_tool_version gcc) + ;; +Clang) + version=$2.$3.$4 + min_version=$($min_tool_version llvm) + ;; +*) + echo "$orig_args: unknown C compiler" >&2 + exit 1 + ;; +esac + +cversion=$(get_canonical_version $version) +min_cversion=$(get_canonical_version $min_version) + +if [ "$cversion" -lt "$min_cversion" ]; then + echo >&2 "***" + echo >&2 "*** C compiler is too old." + echo >&2 "*** Your $name version: $version" + echo >&2 "*** Minimum $name version: $min_version" + echo >&2 "***" + exit 1 +fi + +echo $name $cversion diff --git a/scripts/check-function-names.sh b/scripts/check-function-names.sh new file mode 100755 index 000000000..08071133e --- /dev/null +++ b/scripts/check-function-names.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Certain function names are disallowed due to section name ambiguities +# introduced by -ffunction-sections. +# +# See the comment above TEXT_MAIN in include/asm-generic/vmlinux.lds.h. + +objfile="$1" + +if [ ! -f "$objfile" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +bad_symbols=$(${NM:-nm} "$objfile" | awk '$2 ~ /^[TtWw]$/ {print $3}' | grep -E '^(startup|exit|split|unlikely|hot|unknown)(\.|$)') + +if [ -n "$bad_symbols" ]; then + echo "$bad_symbols" | while read -r sym; do + echo "$objfile: error: $sym() function name creates ambiguity with -ffunction-sections" >&2 + done + exit 1 +fi + +exit 0 diff --git a/scripts/check-git b/scripts/check-git new file mode 100755 index 000000000..2ca6c5df1 --- /dev/null +++ b/scripts/check-git @@ -0,0 +1,14 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# succeed if we are in a git repository + +srctree="$(dirname $0)/.." + +if ! git -C "${srctree}" rev-parse --verify HEAD >/dev/null 2>/dev/null; then + exit 1 +fi + +if ! test -z $(git -C "${srctree}" rev-parse --show-cdup 2>/dev/null); then + exit 1 +fi diff --git a/scripts/check-sysctl-docs b/scripts/check-sysctl-docs new file mode 100755 index 000000000..910fd8a9a --- /dev/null +++ b/scripts/check-sysctl-docs @@ -0,0 +1,174 @@ +#!/usr/bin/env -S gawk -f +# SPDX-License-Identifier: GPL-2.0 + +# Script to check sysctl documentation against source files +# +# Copyright (c) 2020 Stephen Kitt + +# Example invocation: +# scripts/check-sysctl-docs -vtable="kernel" \ +# Documentation/admin-guide/sysctl/kernel.rst \ +# $(git grep -l register_sysctl) +# +# Specify -vdebug=1 to see debugging information + +BEGIN { + if (!table) { + print "Please specify the table to look for using the table variable" > "/dev/stderr" + exit 1 + } + + # Documentation title skiplist + skiplist[0] = "^Documentation for" + skiplist[1] = "Network core options$" + skiplist[2] = "POSIX message queues filesystem$" + skiplist[3] = "Configuration options" + skiplist[4] = ". /proc/sys/fs" + skiplist[5] = "^Introduction$" + skiplist[6] = "^seccomp$" + skiplist[7] = "^pty$" + skiplist[8] = "^firmware_config$" + skiplist[9] = "^random$" +} + +# The following globals are used: +# documented: maps documented entries (each key is an entry) +# entries: maps ctl_table names and procnames to counts (so +# enumerating the subkeys for a given ctl_table lists its +# procnames) +# curtable: the name of the current ctl_table struct +# curentry: the name of the current proc entry (procname when parsing +# a ctl_table, constructed path when parsing a ctl_path) + + +# Remove punctuation from the given value +function trimpunct(value) { + while (value ~ /^["&]/) { + value = substr(value, 2) + } + while (value ~ /[]["&,}]$/) { + value = substr(value, 1, length(value) - 1) + } + return value +} + +# Print the information for the given entry +function printentry(entry) { + seen[entry]++ + printf "* %s from %s", entry, file[entry] + if (documented[entry]) { + printf " (documented)" + } + print "" +} + + +# Stage 1: build the list of documented entries +FNR == NR && /^=+$/ { + for (i in skiplist) { + if (prevline ~ skiplist[i]) { + next + } + } + + # The previous line is a section title, parse it + $0 = prevline + if (debug) print "Parsing " $0 + inbrackets = 0 + for (i = 1; i <= NF; i++) { + if (length($i) == 0) { + continue + } + if (!inbrackets && substr($i, 1, 1) == "(") { + inbrackets = 1 + } + if (!inbrackets) { + token = trimpunct($i) + if (length(token) > 0 && token != "and") { + if (debug) print trimpunct($i) + documented[trimpunct($i)]++ + } + } + if (inbrackets && substr($i, length($i), 1) == ")") { + inbrackets = 0 + } + } +} + +FNR == NR { + prevline = $0 + next +} + + +# Stage 2: process each file and find all sysctl tables +BEGINFILE { + delete entries + curtable = "" + curentry = "" + delete vars + if (debug) print "Processing file " FILENAME +} + +/^static( const)? struct ctl_table/ { + match($0, /static( const)? struct ctl_table ([^][]+)/, tables) + curtable = tables[2] + if (debug) print "Processing table " curtable +} + +/^};$/ { + curtable = "" + curentry = "" + delete vars +} + +curtable && /\.procname[\t ]*=[\t ]*".+"/ { + match($0, /.procname[\t ]*=[\t ]*"([^"]+)"/, names) + curentry = names[1] + if (debug) print "Adding entry " curentry " to table " curtable + entries[curtable][curentry]++ + file[curentry] = FILENAME +} + +curtable && /UCOUNT_ENTRY.*/ { + match($0, /UCOUNT_ENTRY\("([^"]+)"\)/, names) + curentry = names[1] + if (debug) print "Adding entry " curentry " to table " curtable + entries[curtable][curentry]++ + file[curentry] = FILENAME +} + +/register_sysctl.*/ { + match($0, /register_sysctl(|_init|_sz)\("([^"]+)" *, *([^,)]+)/, tables) + if (debug) print "Registering table " tables[3] " at " tables[2] + if (tables[2] == table) { + for (entry in entries[tables[3]]) { + printentry(entry) + } + } +} + +/kmemdup.*/ { + match($0, /([^ \t]+) *= *kmemdup\(([^,]+) *,/, names) + if (debug) print "Found variable " names[1] " for table " names[2] + if (names[2] in entries) { + vars[names[1]] = names[2] + } +} + +/__register_sysctl_table.*/ { + match($0, /__register_sysctl_table\([^,]+, *"([^"]+)" *, *([^,]+)/, tables) + if (debug) print "Registering variable table " tables[2] " at " tables[1] + if (tables[1] == table && tables[2] in vars) { + for (entry in entries[vars[tables[2]]]) { + printentry(entry) + } + } +} + +END { + for (entry in documented) { + if (!seen[entry]) + print "No implementation for " entry + } +} diff --git a/scripts/check-uapi.sh b/scripts/check-uapi.sh new file mode 100755 index 000000000..c8beec588 --- /dev/null +++ b/scripts/check-uapi.sh @@ -0,0 +1,578 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +# Script to check commits for UAPI backwards compatibility + +set -o errexit +set -o pipefail + +print_usage() { + name=$(basename "$0") + cat << EOF +$name - check for UAPI header stability across Git commits + +By default, the script will check to make sure the latest commit (or current +dirty changes) did not introduce ABI changes when compared to HEAD^1. You can +check against additional commit ranges with the -b and -p options. + +The script will not check UAPI headers for architectures other than the one +defined in ARCH. + +Usage: $name [-b BASE_REF] [-p PAST_REF] [-j N] [-l ERROR_LOG] [-i] [-q] [-v] + +Options: + -b BASE_REF Base git reference to use for comparison. If unspecified or empty, + will use any dirty changes in tree to UAPI files. If there are no + dirty changes, HEAD will be used. + -p PAST_REF Compare BASE_REF to PAST_REF (e.g. -p v6.1). If unspecified or empty, + will use BASE_REF^1. Must be an ancestor of BASE_REF. Only headers + that exist on PAST_REF will be checked for compatibility. + -j JOBS Number of checks to run in parallel (default: number of CPU cores). + -l ERROR_LOG Write error log to file (default: no error log is generated). + -i Ignore ambiguous changes that may or may not break UAPI compatibility. + -q Quiet operation. + -v Verbose operation (print more information about each header being checked). + +Environmental args: + ABIDIFF Custom path to abidiff binary + CROSS_COMPILE Toolchain prefix for compiler + CC C compiler (default is "\${CROSS_COMPILE}gcc") + ARCH Target architecture for the UAPI check (default is host arch) + +Exit codes: + $SUCCESS) Success + $FAIL_ABI) ABI difference detected + $FAIL_PREREQ) Prerequisite not met +EOF +} + +readonly SUCCESS=0 +readonly FAIL_ABI=1 +readonly FAIL_PREREQ=2 + +# Print to stderr +eprintf() { + # shellcheck disable=SC2059 + printf "$@" >&2 +} + +# Expand an array with a specific character (similar to Python string.join()) +join() { + local IFS="$1" + shift + printf "%s" "$*" +} + +# Create abidiff suppressions +gen_suppressions() { + # Common enum variant names which we don't want to worry about + # being shifted when new variants are added. + local -a enum_regex=( + ".*_AFTER_LAST$" + ".*_CNT$" + ".*_COUNT$" + ".*_END$" + ".*_LAST$" + ".*_MASK$" + ".*_MAX$" + ".*_MAX_BIT$" + ".*_MAX_BPF_ATTACH_TYPE$" + ".*_MAX_ID$" + ".*_MAX_SHIFT$" + ".*_NBITS$" + ".*_NETDEV_NUMHOOKS$" + ".*_NFT_META_IIFTYPE$" + ".*_NL80211_ATTR$" + ".*_NLDEV_NUM_OPS$" + ".*_NUM$" + ".*_NUM_ELEMS$" + ".*_NUM_IRQS$" + ".*_SIZE$" + ".*_TLSMAX$" + "^MAX_.*" + "^NUM_.*" + ) + + # Common padding field names which can be expanded into + # without worrying about users. + local -a padding_regex=( + ".*end$" + ".*pad$" + ".*pad[0-9]?$" + ".*pad_[0-9]?$" + ".*padding$" + ".*padding[0-9]?$" + ".*padding_[0-9]?$" + ".*res$" + ".*resv$" + ".*resv[0-9]?$" + ".*resv_[0-9]?$" + ".*reserved$" + ".*reserved[0-9]?$" + ".*reserved_[0-9]?$" + ".*rsvd[0-9]?$" + ".*unused$" + ) + + cat << EOF +[suppress_type] + type_kind = enum + changed_enumerators_regexp = $(join , "${enum_regex[@]}") +EOF + + for p in "${padding_regex[@]}"; do + cat << EOF +[suppress_type] + type_kind = struct + has_data_member_inserted_at = offset_of_first_data_member_regexp(${p}) +EOF + done + +if [ "$IGNORE_AMBIGUOUS_CHANGES" = "true" ]; then + cat << EOF +[suppress_type] + type_kind = struct + has_data_member_inserted_at = end + has_size_change = yes +EOF +fi +} + +# Check if git tree is dirty +tree_is_dirty() { + ! git diff --quiet +} + +# Get list of files installed in $ref +get_file_list() { + local -r ref="$1" + local -r tree="$(get_header_tree "$ref")" + + # Print all installed headers, filtering out ones that can't be compiled + find "$tree" -type f -name '*.h' -printf '%P\n' | grep -v -f "$INCOMPAT_LIST" +} + +# Add to the list of incompatible headers +add_to_incompat_list() { + local -r ref="$1" + + # Start with the usr/include/Makefile to get a list of the headers + # that don't compile using this method. + if [ ! -f usr/include/Makefile ]; then + eprintf "error - no usr/include/Makefile present at %s\n" "$ref" + eprintf "Note: usr/include/Makefile was added in the v5.3 kernel release\n" + exit "$FAIL_PREREQ" + fi + { + # shellcheck disable=SC2016 + printf 'all: ; @echo $(no-header-test)\n' + cat usr/include/Makefile + } | SRCARCH="$ARCH" make --always-make -f - | tr " " "\n" \ + | grep -v "asm-generic" >> "$INCOMPAT_LIST" + + # The makefile also skips all asm-generic files, but prints "asm-generic/%" + # which won't work for our grep match. Instead, print something grep will match. + printf "asm-generic/.*\.h\n" >> "$INCOMPAT_LIST" +} + +# Compile the simple test app +do_compile() { + local -r inc_dir="$1" + local -r header="$2" + local -r out="$3" + printf "int f(void) { return 0; }\n" | \ + "$CC" \ + -shared \ + -nostdlib \ + -fPIC \ + -o "$out" \ + -x c \ + -O0 \ + -std=c90 \ + -fno-eliminate-unused-debug-types \ + -g \ + "-I${inc_dir}" \ + "-Iusr/dummy-include" \ + -include "$header" \ + - +} + +# Run make headers_install +run_make_headers_install() { + local -r ref="$1" + local -r install_dir="$(get_header_tree "$ref")" + make -j "$MAX_THREADS" CROSS_COMPILE="${CROSS_COMPILE}" ARCH="$ARCH" INSTALL_HDR_PATH="$install_dir" \ + headers_install > /dev/null +} + +# Install headers for both git refs +install_headers() { + local -r base_ref="$1" + local -r past_ref="$2" + + for ref in "$base_ref" "$past_ref"; do + printf "Installing user-facing UAPI headers from %s... " "${ref:-dirty tree}" + if [ -n "$ref" ]; then + git archive --format=tar --prefix="${ref}-archive/" "$ref" \ + | (cd "$TMP_DIR" && tar xf -) + ( + cd "${TMP_DIR}/${ref}-archive" + run_make_headers_install "$ref" + add_to_incompat_list "$ref" "$INCOMPAT_LIST" + ) + else + run_make_headers_install "$ref" + add_to_incompat_list "$ref" "$INCOMPAT_LIST" + fi + printf "OK\n" + done + sort -u -o "$INCOMPAT_LIST" "$INCOMPAT_LIST" + sed -i -e '/^$/d' "$INCOMPAT_LIST" +} + +# Print the path to the headers_install tree for a given ref +get_header_tree() { + local -r ref="$1" + printf "%s" "${TMP_DIR}/${ref}/usr" +} + +# Check file list for UAPI compatibility +check_uapi_files() { + local -r base_ref="$1" + local -r past_ref="$2" + local -r abi_error_log="$3" + + local passed=0; + local failed=0; + local -a threads=() + set -o errexit + + printf "Checking changes to UAPI headers between %s and %s...\n" "$past_ref" "${base_ref:-dirty tree}" + # Loop over all UAPI headers that were installed by $past_ref (if they only exist on $base_ref, + # there's no way they're broken and no way to compare anyway) + while read -r file; do + if [ "${#threads[@]}" -ge "$MAX_THREADS" ]; then + if wait "${threads[0]}"; then + passed=$((passed + 1)) + else + failed=$((failed + 1)) + fi + threads=("${threads[@]:1}") + fi + + check_individual_file "$base_ref" "$past_ref" "$file" & + threads+=("$!") + done < <(get_file_list "$past_ref") + + for t in "${threads[@]}"; do + if wait "$t"; then + passed=$((passed + 1)) + else + failed=$((failed + 1)) + fi + done + + if [ -n "$abi_error_log" ]; then + printf 'Generated by "%s %s" from git ref %s\n\n' \ + "$0" "$*" "$(git rev-parse HEAD)" > "$abi_error_log" + fi + + while read -r error_file; do + { + cat "$error_file" + printf "\n\n" + } | tee -a "${abi_error_log:-/dev/null}" >&2 + done < <(find "$TMP_DIR" -type f -name '*.error' | sort) + + total="$((passed + failed))" + if [ "$failed" -gt 0 ]; then + eprintf "error - %d/%d UAPI headers compatible with %s appear _not_ to be backwards compatible\n" \ + "$failed" "$total" "$ARCH" + if [ -n "$abi_error_log" ]; then + eprintf "Failure summary saved to %s\n" "$abi_error_log" + fi + else + printf "All %d UAPI headers compatible with %s appear to be backwards compatible\n" \ + "$total" "$ARCH" + fi + + return "$failed" +} + +# Check an individual file for UAPI compatibility +check_individual_file() { + local -r base_ref="$1" + local -r past_ref="$2" + local -r file="$3" + + local -r base_header="$(get_header_tree "$base_ref")/${file}" + local -r past_header="$(get_header_tree "$past_ref")/${file}" + + if [ ! -f "$base_header" ]; then + mkdir -p "$(dirname "$base_header")" + printf "==== UAPI header %s was removed between %s and %s ====" \ + "$file" "$past_ref" "$base_ref" \ + > "${base_header}.error" + return 1 + fi + + compare_abi "$file" "$base_header" "$past_header" "$base_ref" "$past_ref" +} + +# Perform the A/B compilation and compare output ABI +compare_abi() { + local -r file="$1" + local -r base_header="$2" + local -r past_header="$3" + local -r base_ref="$4" + local -r past_ref="$5" + local -r log="${TMP_DIR}/log/${file}.log" + local -r error_log="${TMP_DIR}/log/${file}.error" + + mkdir -p "$(dirname "$log")" + + if ! do_compile "$(get_header_tree "$base_ref")/include" "$base_header" "${base_header}.bin" 2> "$log"; then + { + warn_str=$(printf "==== Could not compile version of UAPI header %s at %s ====\n" \ + "$file" "$base_ref") + printf "%s\n" "$warn_str" + cat "$log" + printf -- "=%.0s" $(seq 0 ${#warn_str}) + } > "$error_log" + return 1 + fi + + if ! do_compile "$(get_header_tree "$past_ref")/include" "$past_header" "${past_header}.bin" 2> "$log"; then + { + warn_str=$(printf "==== Could not compile version of UAPI header %s at %s ====\n" \ + "$file" "$past_ref") + printf "%s\n" "$warn_str" + cat "$log" + printf -- "=%.0s" $(seq 0 ${#warn_str}) + } > "$error_log" + return 1 + fi + + local ret=0 + "$ABIDIFF" --non-reachable-types \ + --suppressions "$SUPPRESSIONS" \ + "${past_header}.bin" "${base_header}.bin" > "$log" || ret="$?" + if [ "$ret" -eq 0 ]; then + if [ "$VERBOSE" = "true" ]; then + printf "No ABI differences detected in %s from %s -> %s\n" \ + "$file" "$past_ref" "$base_ref" + fi + else + # Bits in abidiff's return code can be used to determine the type of error + if [ $((ret & 0x2)) -gt 0 ]; then + eprintf "error - abidiff did not run properly\n" + exit 1 + fi + + if [ "$IGNORE_AMBIGUOUS_CHANGES" = "true" ] && [ "$ret" -eq 4 ]; then + return 0 + fi + + # If the only changes were additions (not modifications to existing APIs), then + # there's no problem. Ignore these diffs. + if grep "Unreachable types summary" "$log" | grep -q "0 removed" && + grep "Unreachable types summary" "$log" | grep -q "0 changed"; then + return 0 + fi + + { + warn_str=$(printf "==== ABI differences detected in %s from %s -> %s ====" \ + "$file" "$past_ref" "$base_ref") + printf "%s\n" "$warn_str" + sed -e '/summary:/d' -e '/changed type/d' -e '/^$/d' -e 's/^/ /g' "$log" + printf -- "=%.0s" $(seq 0 ${#warn_str}) + if cmp "$past_header" "$base_header" > /dev/null 2>&1; then + printf "\n%s did not change between %s and %s...\n" "$file" "$past_ref" "${base_ref:-dirty tree}" + printf "It's possible a change to one of the headers it includes caused this error:\n" + grep '^#include' "$base_header" + printf "\n" + fi + } > "$error_log" + + return 1 + fi +} + +# Check that a minimum software version number is satisfied +min_version_is_satisfied() { + local -r min_version="$1" + local -r version_installed="$2" + + printf "%s\n%s\n" "$min_version" "$version_installed" \ + | sort -Vc > /dev/null 2>&1 +} + +# Make sure we have the tools we need and the arguments make sense +check_deps() { + ABIDIFF="${ABIDIFF:-abidiff}" + CC="${CC:-${CROSS_COMPILE}gcc}" + ARCH="${ARCH:-$(uname -m)}" + if [ "$ARCH" = "x86_64" ]; then + ARCH="x86" + fi + + local -r abidiff_min_version="2.4" + local -r libdw_min_version_if_clang="0.171" + + if ! command -v "$ABIDIFF" > /dev/null 2>&1; then + eprintf "error - abidiff not found!\n" + eprintf "Please install abigail-tools version %s or greater\n" "$abidiff_min_version" + eprintf "See: https://sourceware.org/libabigail/manual/libabigail-overview.html\n" + return 1 + fi + + local -r abidiff_version="$("$ABIDIFF" --version | cut -d ' ' -f 2)" + if ! min_version_is_satisfied "$abidiff_min_version" "$abidiff_version"; then + eprintf "error - abidiff version too old: %s\n" "$abidiff_version" + eprintf "Please install abigail-tools version %s or greater\n" "$abidiff_min_version" + eprintf "See: https://sourceware.org/libabigail/manual/libabigail-overview.html\n" + return 1 + fi + + if ! command -v "$CC" > /dev/null 2>&1; then + eprintf 'error - %s not found\n' "$CC" + return 1 + fi + + if "$CC" --version | grep -q clang; then + local -r libdw_version="$(ldconfig -v 2>/dev/null | grep -v SKIPPED | grep -m 1 -o 'libdw-[0-9]\+.[0-9]\+' | cut -c 7-)" + if ! min_version_is_satisfied "$libdw_min_version_if_clang" "$libdw_version"; then + eprintf "error - libdw version too old for use with clang: %s\n" "$libdw_version" + eprintf "Please install libdw from elfutils version %s or greater\n" "$libdw_min_version_if_clang" + eprintf "See: https://sourceware.org/elfutils/\n" + return 1 + fi + fi + + if [ ! -d "arch/${ARCH}" ]; then + eprintf 'error - ARCH "%s" is not a subdirectory under arch/\n' "$ARCH" + eprintf "Please set ARCH to one of:\n%s\n" "$(find arch -maxdepth 1 -mindepth 1 -type d -printf '%f ' | fmt)" + return 1 + fi + + if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then + eprintf "error - this script requires the kernel tree to be initialized with Git\n" + return 1 + fi + + if ! git rev-parse --verify "$past_ref" > /dev/null 2>&1; then + printf 'error - invalid git reference "%s"\n' "$past_ref" + return 1 + fi + + if [ -n "$base_ref" ]; then + if ! git merge-base --is-ancestor "$past_ref" "$base_ref" > /dev/null 2>&1; then + printf 'error - "%s" is not an ancestor of base ref "%s"\n' "$past_ref" "$base_ref" + return 1 + fi + if [ "$(git rev-parse "$base_ref")" = "$(git rev-parse "$past_ref")" ]; then + printf 'error - "%s" and "%s" are the same reference\n' "$past_ref" "$base_ref" + return 1 + fi + fi +} + +run() { + local base_ref="$1" + local past_ref="$2" + local abi_error_log="$3" + shift 3 + + if [ -z "$KERNEL_SRC" ]; then + KERNEL_SRC="$(realpath "$(dirname "$0")"/..)" + fi + + cd "$KERNEL_SRC" + + if [ -z "$base_ref" ] && ! tree_is_dirty; then + base_ref=HEAD + fi + + if [ -z "$past_ref" ]; then + if [ -n "$base_ref" ]; then + past_ref="${base_ref}^1" + else + past_ref=HEAD + fi + fi + + if ! check_deps; then + exit "$FAIL_PREREQ" + fi + + TMP_DIR=$(mktemp -d) + readonly TMP_DIR + trap 'rm -rf "$TMP_DIR"' EXIT + + readonly INCOMPAT_LIST="${TMP_DIR}/incompat_list.txt" + touch "$INCOMPAT_LIST" + + readonly SUPPRESSIONS="${TMP_DIR}/suppressions.txt" + gen_suppressions > "$SUPPRESSIONS" + + # Run make install_headers for both refs + install_headers "$base_ref" "$past_ref" + + # Check for any differences in the installed header trees + if diff -r -q "$(get_header_tree "$base_ref")" "$(get_header_tree "$past_ref")" > /dev/null 2>&1; then + printf "No changes to UAPI headers were applied between %s and %s\n" "$past_ref" "${base_ref:-dirty tree}" + exit "$SUCCESS" + fi + + if ! check_uapi_files "$base_ref" "$past_ref" "$abi_error_log"; then + exit "$FAIL_ABI" + fi +} + +main() { + MAX_THREADS=$(nproc) + VERBOSE="false" + IGNORE_AMBIGUOUS_CHANGES="false" + quiet="false" + local base_ref="" + while getopts "hb:p:j:l:iqv" opt; do + case $opt in + h) + print_usage + exit "$SUCCESS" + ;; + b) + base_ref="$OPTARG" + ;; + p) + past_ref="$OPTARG" + ;; + j) + MAX_THREADS="$OPTARG" + ;; + l) + abi_error_log="$OPTARG" + ;; + i) + IGNORE_AMBIGUOUS_CHANGES="true" + ;; + q) + quiet="true" + VERBOSE="false" + ;; + v) + VERBOSE="true" + quiet="false" + ;; + *) + exit "$FAIL_PREREQ" + esac + done + + if [ "$quiet" = "true" ]; then + exec > /dev/null 2>&1 + fi + + run "$base_ref" "$past_ref" "$abi_error_log" "$@" +} + +main "$@" diff --git a/scripts/check_extable.sh b/scripts/check_extable.sh new file mode 100755 index 000000000..4b380564c --- /dev/null +++ b/scripts/check_extable.sh @@ -0,0 +1,147 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# (c) 2015, Quentin Casasnovas + +obj=$1 + +file ${obj} | grep -q ELF || (echo "${obj} is not an ELF file." 1>&2 ; exit 0) + +# Bail out early if there isn't an __ex_table section in this object file. +objdump -hj __ex_table ${obj} 2> /dev/null > /dev/null +[ $? -ne 0 ] && exit 0 + +white_list=.text,.fixup + +suspicious_relocs=$(objdump -rj __ex_table ${obj} | tail -n +6 | + grep -v $(eval echo -e{${white_list}}) | awk '{print $3}') + +# No suspicious relocs in __ex_table, jobs a good'un +[ -z "${suspicious_relocs}" ] && exit 0 + + +# After this point, something is seriously wrong since we just found out we +# have some relocations in __ex_table which point to sections which aren't +# white listed. If you're adding a new section in the Linux kernel, and +# you're expecting this section to contain code which can fault (i.e. the +# __ex_table relocation to your new section is expected), simply add your +# new section to the white_list variable above. If not, you're probably +# doing something wrong and the rest of this code is just trying to print +# you more information about it. + +function find_section_offset_from_symbol() +{ + eval $(objdump -t ${obj} | grep ${1} | sed 's/\([0-9a-f]\+\) .\{7\} \([^ \t]\+\).*/section="\2"; section_offset="0x\1" /') + + # addr2line takes addresses in hexadecimal... + section_offset=$(printf "0x%016x" $(( ${section_offset} + $2 )) ) +} + +function find_symbol_and_offset_from_reloc() +{ + # Extract symbol and offset from the objdump output + eval $(echo $reloc | sed 's/\([^+]\+\)+\?\(0x[0-9a-f]\+\)\?/symbol="\1"; symbol_offset="\2"/') + + # When the relocation points to the begining of a symbol or section, it + # won't print the offset since it is zero. + if [ -z "${symbol_offset}" ]; then + symbol_offset=0x0 + fi +} + +function find_alt_replacement_target() +{ + # The target of the .altinstr_replacement is the relocation just before + # the .altinstr_replacement one. + eval $(objdump -rj .altinstructions ${obj} | grep -B1 "${section}+${section_offset}" | head -n1 | awk '{print $3}' | + sed 's/\([^+]\+\)+\(0x[0-9a-f]\+\)/alt_target_section="\1"; alt_target_offset="\2"/') +} + +function handle_alt_replacement_reloc() +{ + # This will define alt_target_section and alt_target_section_offset + find_alt_replacement_target ${section} ${section_offset} + + echo "Error: found a reference to .altinstr_replacement in __ex_table:" + addr2line -fip -j ${alt_target_section} -e ${obj} ${alt_target_offset} | awk '{print "\t" $0}' + + error=true +} + +function is_executable_section() +{ + objdump -hwj ${section} ${obj} | grep -q CODE + return $? +} + +function handle_suspicious_generic_reloc() +{ + if is_executable_section ${section}; then + # We've got a relocation to a non white listed _executable_ + # section, print a warning so the developper adds the section to + # the white list or fix his code. We try to pretty-print the file + # and line number where that relocation was added. + echo "Warning: found a reference to section \"${section}\" in __ex_table:" + addr2line -fip -j ${section} -e ${obj} ${section_offset} | awk '{print "\t" $0}' + else + # Something is definitively wrong here since we've got a relocation + # to a non-executable section, there's no way this would ever be + # running in the kernel. + echo "Error: found a reference to non-executable section \"${section}\" in __ex_table at offset ${section_offset}" + error=true + fi +} + +function handle_suspicious_reloc() +{ + case "${section}" in + ".altinstr_replacement") + handle_alt_replacement_reloc ${section} ${section_offset} + ;; + *) + handle_suspicious_generic_reloc ${section} ${section_offset} + ;; + esac +} + +function diagnose() +{ + + for reloc in ${suspicious_relocs}; do + # Let's find out where the target of the relocation in __ex_table + # is, this will define ${symbol} and ${symbol_offset} + find_symbol_and_offset_from_reloc ${reloc} + + # When there's a global symbol at the place of the relocation, + # objdump will use it instead of giving us a section+offset, so + # let's find out which section is this symbol in and the total + # offset withing that section. + find_section_offset_from_symbol ${symbol} ${symbol_offset} + + # In this case objdump was presenting us with a reloc to a symbol + # rather than a section. Now that we've got the actual section, + # we can skip it if it's in the white_list. + if [ -z "$( echo $section | grep -v $(eval echo -e{${white_list}}))" ]; then + continue; + fi + + # Will either print a warning if the relocation happens to be in a + # section we do not know but has executable bit set, or error out. + handle_suspicious_reloc + done +} + +function check_debug_info() { + objdump -hj .debug_info ${obj} 2> /dev/null > /dev/null || + echo -e "${obj} does not contain debug information, the addr2line output will be limited.\n" \ + "Recompile ${obj} with CONFIG_DEBUG_INFO to get a more useful output." +} + +check_debug_info + +diagnose + +if [ "${error}" ]; then + exit 1 +fi + +exit 0 diff --git a/scripts/checkdeclares.pl b/scripts/checkdeclares.pl new file mode 100755 index 000000000..f6d551c84 --- /dev/null +++ b/scripts/checkdeclares.pl @@ -0,0 +1,53 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# checkdeclares: find struct declared more than once +# +# Copyright 2021 Wan Jiabing +# Inspired by checkincludes.pl +# +# This script checks for duplicate struct declares. +# Note that this will not take into consideration macros so +# you should run this only if you know you do have real dups +# and do not have them under #ifdef's. +# You could also just review the results. + +use strict; + +sub usage { + print "Usage: checkdeclares.pl file1.h ...\n"; + print "Warns of struct declaration duplicates\n"; + exit 1; +} + +if ($#ARGV < 0) { + usage(); +} + +my $dup_counter = 0; + +foreach my $file (@ARGV) { + open(my $f, '<', $file) + or die "Cannot open $file: $!.\n"; + + my %declaredstructs = (); + + while (<$f>) { + if (m/^\s*struct\s*(\w*);$/o) { + ++$declaredstructs{$1}; + } + } + + close($f); + + foreach my $structname (keys %declaredstructs) { + if ($declaredstructs{$structname} > 1) { + print "$file: struct $structname is declared more than once.\n"; + ++$dup_counter; + } + } +} + +if ($dup_counter == 0) { + print "No duplicate struct declares found.\n"; +} diff --git a/scripts/checker-valid.sh b/scripts/checker-valid.sh new file mode 100755 index 000000000..625a789ed --- /dev/null +++ b/scripts/checker-valid.sh @@ -0,0 +1,19 @@ +#!/bin/sh -eu +# SPDX-License-Identifier: GPL-2.0 + +[ ! -x "$(command -v "$1")" ] && exit 1 + +tmp_file=$(mktemp) +trap "rm -f $tmp_file" EXIT + +cat << EOF >$tmp_file +static inline int u(const int *q) +{ + __typeof_unqual__(*q) v = *q; + return v; +} +EOF + +# sparse happily exits with 0 on error so validate +# there is none on stderr. Use awk as grep is a pain with sh -e +$@ $tmp_file 2>&1 | awk -v c=1 '/error/{c=0}END{print c}' diff --git a/scripts/checkincludes.pl b/scripts/checkincludes.pl new file mode 100755 index 000000000..b514a956d --- /dev/null +++ b/scripts/checkincludes.pl @@ -0,0 +1,98 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# checkincludes: find/remove files included more than once +# +# Copyright abandoned, 2000, Niels Kristian Bech Jensen . +# Copyright 2009 Luis R. Rodriguez +# +# This script checks for duplicate includes. It also has support +# to remove them in place. Note that this will not take into +# consideration macros so you should run this only if you know +# you do have real dups and do not have them under #ifdef's. You +# could also just review the results. + +use strict; + +sub usage { + print "Usage: checkincludes.pl [-r]\n"; + print "By default we just warn of duplicates\n"; + print "To remove duplicated includes in place use -r\n"; + exit 1; +} + +my $remove = 0; + +if ($#ARGV < 0) { + usage(); +} + +if ($#ARGV >= 1) { + if ($ARGV[0] =~ /^-/) { + if ($ARGV[0] eq "-r") { + $remove = 1; + shift; + } else { + usage(); + } + } +} + +my $dup_counter = 0; + +foreach my $file (@ARGV) { + open(my $f, '<', $file) + or die "Cannot open $file: $!.\n"; + + my %includedfiles = (); + my @file_lines = (); + + while (<$f>) { + if (m/^\s*#\s*include\s*[<"](\S*)[>"]/o) { + ++$includedfiles{$1}; + } + push(@file_lines, $_); + } + + close($f); + + if (!$remove) { + foreach my $filename (keys %includedfiles) { + if ($includedfiles{$filename} > 1) { + print "$file: $filename is included more than once.\n"; + ++$dup_counter; + } + } + next; + } + + open($f, '>', $file) + or die("Cannot write to $file: $!"); + + my $dups = 0; + foreach (@file_lines) { + if (m/^\s*#\s*include\s*[<"](\S*)[>"]/o) { + foreach my $filename (keys %includedfiles) { + if ($1 eq $filename) { + if ($includedfiles{$filename} > 1) { + $includedfiles{$filename}--; + $dups++; + ++$dup_counter; + } else { + print {$f} $_; + } + } + } + } else { + print {$f} $_; + } + } + if ($dups > 0) { + print "$file: removed $dups duplicate includes\n"; + } + close($f); +} + +if ($dup_counter == 0) { + print "No duplicate includes found.\n"; +} diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py new file mode 100755 index 000000000..36c920e71 --- /dev/null +++ b/scripts/checkkconfigsymbols.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only + +"""Find Kconfig symbols that are referenced but not defined.""" + +# (c) 2014-2017 Valentin Rothberg +# (c) 2014 Stefan Hengelein +# + + +import argparse +import difflib +import os +import re +import signal +import subprocess +import sys +from multiprocessing import Pool, cpu_count + + +# regex expressions +OPERATORS = r"&|\(|\)|\||\!" +SYMBOL = r"(?:\w*[A-Z0-9]\w*){2,}" +DEF = r"^\s*(?:menu){,1}config\s+(" + SYMBOL + r")\s*" +EXPR = r"(?:" + OPERATORS + r"|\s|" + SYMBOL + r")+" +DEFAULT = r"default\s+.*?(?:if\s.+){,1}" +STMT = r"^\s*(?:if|select|imply|depends\s+on|(?:" + DEFAULT + r"))\s+" + EXPR +SOURCE_SYMBOL = r"(?:\W|\b)+[D]{,1}CONFIG_(" + SYMBOL + r")" + +# regex objects +REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") +REGEX_SYMBOL = re.compile(r'(?!\B)' + SYMBOL + r'(?!\B)') +REGEX_SOURCE_SYMBOL = re.compile(SOURCE_SYMBOL) +REGEX_KCONFIG_DEF = re.compile(DEF) +REGEX_KCONFIG_EXPR = re.compile(EXPR) +REGEX_KCONFIG_STMT = re.compile(STMT) +REGEX_FILTER_SYMBOLS = re.compile(r"[A-Za-z0-9]$") +REGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") +REGEX_QUOTES = re.compile("(\"(.*?)\")") + + +def parse_options(): + """The user interface of this module.""" + usage = "Run this tool to detect Kconfig symbols that are referenced but " \ + "not defined in Kconfig. If no option is specified, " \ + "checkkconfigsymbols defaults to check your current tree. " \ + "Please note that specifying commits will 'git reset --hard\' " \ + "your current tree! You may save uncommitted changes to avoid " \ + "losing data." + + parser = argparse.ArgumentParser(description=usage) + + parser.add_argument('-c', '--commit', dest='commit', action='store', + default="", + help="check if the specified commit (hash) introduces " + "undefined Kconfig symbols") + + parser.add_argument('-d', '--diff', dest='diff', action='store', + default="", + help="diff undefined symbols between two commits " + "(e.g., -d commmit1..commit2)") + + parser.add_argument('-f', '--find', dest='find', action='store_true', + default=False, + help="find and show commits that may cause symbols to be " + "missing (required to run with --diff)") + + parser.add_argument('-i', '--ignore', dest='ignore', action='store', + default="", + help="ignore files matching this Python regex " + "(e.g., -i '.*defconfig')") + + parser.add_argument('-s', '--sim', dest='sim', action='store', default="", + help="print a list of max. 10 string-similar symbols") + + parser.add_argument('--force', dest='force', action='store_true', + default=False, + help="reset current Git tree even when it's dirty") + + parser.add_argument('--no-color', dest='color', action='store_false', + default=True, + help="don't print colored output (default when not " + "outputting to a terminal)") + + args = parser.parse_args() + + if args.commit and args.diff: + sys.exit("Please specify only one option at once.") + + if args.diff and not re.match(r"^[\w\-\.\^]+\.\.[\w\-\.\^]+$", args.diff): + sys.exit("Please specify valid input in the following format: " + "\'commit1..commit2\'") + + if args.commit or args.diff: + if not args.force and tree_is_dirty(): + sys.exit("The current Git tree is dirty (see 'git status'). " + "Running this script may\ndelete important data since it " + "calls 'git reset --hard' for some performance\nreasons. " + " Please run this script in a clean Git tree or pass " + "'--force' if you\nwant to ignore this warning and " + "continue.") + + if args.commit: + if args.commit.startswith('HEAD'): + sys.exit("The --commit option can't use the HEAD ref") + + args.find = False + + if args.ignore: + try: + re.match(args.ignore, "this/is/just/a/test.c") + except: + sys.exit("Please specify a valid Python regex.") + + return args + + +def print_undefined_symbols(): + """Main function of this module.""" + args = parse_options() + + global COLOR + COLOR = args.color and sys.stdout.isatty() + + if args.sim and not args.commit and not args.diff: + sims = find_sims(args.sim, args.ignore) + if sims: + print("%s: %s" % (yel("Similar symbols"), ', '.join(sims))) + else: + print("%s: no similar symbols found" % yel("Similar symbols")) + sys.exit(0) + + # dictionary of (un)defined symbols + defined = {} + undefined = {} + + if args.commit or args.diff: + head = get_head() + + # get commit range + commit_a = None + commit_b = None + if args.commit: + commit_a = args.commit + "~" + commit_b = args.commit + elif args.diff: + split = args.diff.split("..") + commit_a = split[0] + commit_b = split[1] + undefined_a = {} + undefined_b = {} + + # get undefined items before the commit + reset(commit_a) + undefined_a, _ = check_symbols(args.ignore) + + # get undefined items for the commit + reset(commit_b) + undefined_b, defined = check_symbols(args.ignore) + + # report cases that are present for the commit but not before + for symbol in sorted(undefined_b): + # symbol has not been undefined before + if symbol not in undefined_a: + files = sorted(undefined_b.get(symbol)) + undefined[symbol] = files + # check if there are new files that reference the undefined symbol + else: + files = sorted(undefined_b.get(symbol) - + undefined_a.get(symbol)) + if files: + undefined[symbol] = files + + # reset to head + reset(head) + + # default to check the entire tree + else: + undefined, defined = check_symbols(args.ignore) + + # now print the output + for symbol in sorted(undefined): + print(red(symbol)) + + files = sorted(undefined.get(symbol)) + print("%s: %s" % (yel("Referencing files"), ", ".join(files))) + + sims = find_sims(symbol, args.ignore, defined) + sims_out = yel("Similar symbols") + if sims: + print("%s: %s" % (sims_out, ', '.join(sims))) + else: + print("%s: %s" % (sims_out, "no similar symbols found")) + + if args.find: + print("%s:" % yel("Commits changing symbol")) + commits = find_commits(symbol, args.diff) + if commits: + for commit in commits: + commit = commit.split(" ", 1) + print("\t- %s (\"%s\")" % (yel(commit[0]), commit[1])) + else: + print("\t- no commit found") + print() # new line + + +def reset(commit): + """Reset current git tree to %commit.""" + execute(["git", "reset", "--hard", commit]) + + +def yel(string): + """ + Color %string yellow. + """ + return "\033[33m%s\033[0m" % string if COLOR else string + + +def red(string): + """ + Color %string red. + """ + return "\033[31m%s\033[0m" % string if COLOR else string + + +def execute(cmd): + """Execute %cmd and return stdout. Exit in case of error.""" + try: + stdout = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False) + stdout = stdout.decode(errors='replace') + except subprocess.CalledProcessError as fail: + exit(fail) + return stdout + + +def find_commits(symbol, diff): + """Find commits changing %symbol in the given range of %diff.""" + commits = execute(["git", "log", "--pretty=oneline", + "--abbrev-commit", "-G", + symbol, diff]) + return [x for x in commits.split("\n") if x] + + +def tree_is_dirty(): + """Return true if the current working tree is dirty (i.e., if any file has + been added, deleted, modified, renamed or copied but not committed).""" + stdout = execute(["git", "status", "--porcelain"]) + for line in stdout: + if re.findall(r"[URMADC]{1}", line[:2]): + return True + return False + + +def get_head(): + """Return commit hash of current HEAD.""" + stdout = execute(["git", "rev-parse", "HEAD"]) + return stdout.strip('\n') + + +def partition(lst, size): + """Partition list @lst into eveni-sized lists of size @size.""" + return [lst[i::size] for i in range(size)] + + +def init_worker(): + """Set signal handler to ignore SIGINT.""" + signal.signal(signal.SIGINT, signal.SIG_IGN) + + +def find_sims(symbol, ignore, defined=[]): + """Return a list of max. ten Kconfig symbols that are string-similar to + @symbol.""" + if defined: + return difflib.get_close_matches(symbol, set(defined), 10) + + pool = Pool(cpu_count(), init_worker) + kfiles = [] + for gitfile in get_files(): + if REGEX_FILE_KCONFIG.match(gitfile): + kfiles.append(gitfile) + + arglist = [] + for part in partition(kfiles, cpu_count()): + arglist.append((part, ignore)) + + for res in pool.map(parse_kconfig_files, arglist): + defined.extend(res[0]) + + return difflib.get_close_matches(symbol, set(defined), 10) + + +def get_files(): + """Return a list of all files in the current git directory.""" + # use 'git ls-files' to get the worklist + stdout = execute(["git", "ls-files"]) + if len(stdout) > 0 and stdout[-1] == "\n": + stdout = stdout[:-1] + + files = [] + for gitfile in stdout.rsplit("\n"): + if ".git" in gitfile or "ChangeLog" in gitfile or \ + ".log" in gitfile or os.path.isdir(gitfile) or \ + gitfile.startswith("tools/"): + continue + files.append(gitfile) + return files + + +def check_symbols(ignore): + """Find undefined Kconfig symbols and return a dict with the symbol as key + and a list of referencing files as value. Files matching %ignore are not + checked for undefined symbols.""" + pool = Pool(cpu_count(), init_worker) + try: + return check_symbols_helper(pool, ignore) + except KeyboardInterrupt: + pool.terminate() + pool.join() + sys.exit(1) + + +def check_symbols_helper(pool, ignore): + """Helper method for check_symbols(). Used to catch keyboard interrupts in + check_symbols() in order to properly terminate running worker processes.""" + source_files = [] + kconfig_files = [] + defined_symbols = [] + referenced_symbols = dict() # {file: [symbols]} + + for gitfile in get_files(): + if REGEX_FILE_KCONFIG.match(gitfile): + kconfig_files.append(gitfile) + else: + if ignore and re.match(ignore, gitfile): + continue + # add source files that do not match the ignore pattern + source_files.append(gitfile) + + # parse source files + arglist = partition(source_files, cpu_count()) + for res in pool.map(parse_source_files, arglist): + referenced_symbols.update(res) + + # parse kconfig files + arglist = [] + for part in partition(kconfig_files, cpu_count()): + arglist.append((part, ignore)) + for res in pool.map(parse_kconfig_files, arglist): + defined_symbols.extend(res[0]) + referenced_symbols.update(res[1]) + defined_symbols = set(defined_symbols) + + # inverse mapping of referenced_symbols to dict(symbol: [files]) + inv_map = dict() + for _file, symbols in referenced_symbols.items(): + for symbol in symbols: + inv_map[symbol] = inv_map.get(symbol, set()) + inv_map[symbol].add(_file) + referenced_symbols = inv_map + + undefined = {} # {symbol: [files]} + for symbol in sorted(referenced_symbols): + # filter some false positives + if symbol == "FOO" or symbol == "BAR" or \ + symbol == "FOO_BAR" or symbol == "XXX": + continue + if symbol not in defined_symbols: + if symbol.endswith("_MODULE"): + # avoid false positives for kernel modules + if symbol[:-len("_MODULE")] in defined_symbols: + continue + undefined[symbol] = referenced_symbols.get(symbol) + return undefined, defined_symbols + + +def parse_source_files(source_files): + """Parse each source file in @source_files and return dictionary with source + files as keys and lists of references Kconfig symbols as values.""" + referenced_symbols = dict() + for sfile in source_files: + referenced_symbols[sfile] = parse_source_file(sfile) + return referenced_symbols + + +def parse_source_file(sfile): + """Parse @sfile and return a list of referenced Kconfig symbols.""" + lines = [] + references = [] + + if not os.path.exists(sfile): + return references + + with open(sfile, "r", encoding='utf-8', errors='replace') as stream: + lines = stream.readlines() + + for line in lines: + if "CONFIG_" not in line: + continue + symbols = REGEX_SOURCE_SYMBOL.findall(line) + for symbol in symbols: + if not REGEX_FILTER_SYMBOLS.search(symbol): + continue + references.append(symbol) + + return references + + +def get_symbols_in_line(line): + """Return mentioned Kconfig symbols in @line.""" + return REGEX_SYMBOL.findall(line) + + +def parse_kconfig_files(args): + """Parse kconfig files and return tuple of defined and references Kconfig + symbols. Note, @args is a tuple of a list of files and the @ignore + pattern.""" + kconfig_files = args[0] + ignore = args[1] + defined_symbols = [] + referenced_symbols = dict() + + for kfile in kconfig_files: + defined, references = parse_kconfig_file(kfile) + defined_symbols.extend(defined) + if ignore and re.match(ignore, kfile): + # do not collect references for files that match the ignore pattern + continue + referenced_symbols[kfile] = references + return (defined_symbols, referenced_symbols) + + +def parse_kconfig_file(kfile): + """Parse @kfile and update symbol definitions and references.""" + lines = [] + defined = [] + references = [] + + if not os.path.exists(kfile): + return defined, references + + with open(kfile, "r", encoding='utf-8', errors='replace') as stream: + lines = stream.readlines() + + for i in range(len(lines)): + line = lines[i] + line = line.strip('\n') + line = line.split("#")[0] # ignore comments + + if REGEX_KCONFIG_DEF.match(line): + symbol_def = REGEX_KCONFIG_DEF.findall(line) + defined.append(symbol_def[0]) + elif REGEX_KCONFIG_STMT.match(line): + line = REGEX_QUOTES.sub("", line) + symbols = get_symbols_in_line(line) + # multi-line statements + while line.endswith("\\"): + i += 1 + line = lines[i] + line = line.strip('\n') + symbols.extend(get_symbols_in_line(line)) + for symbol in set(symbols): + if REGEX_NUMERIC.match(symbol): + # ignore numeric values + continue + references.append(symbol) + + return defined, references + + +def main(): + try: + print_undefined_symbols() + except BrokenPipeError: + # Python flushes standard streams on exit; redirect remaining output + # to devnull to avoid another BrokenPipeError at shutdown + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + sys.exit(1) # Python exits with error code 1 on EPIPE + + +if __name__ == "__main__": + main() diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl new file mode 100755 index 000000000..8a7787d22 --- /dev/null +++ b/scripts/checkpatch.pl @@ -0,0 +1,7984 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp (the ugly bit) +# (c) 2007,2008, Andy Whitcroft (new conditions, test suite) +# (c) 2008-2010 Andy Whitcroft +# (c) 2010-2018 Joe Perches + +use strict; +use warnings; +use POSIX; +use File::Basename; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); +use Encode qw(decode encode); + +my $P = $0; +my $D = dirname(abs_path($P)); + +my $V = '0.32'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $quiet = 0; +my $verbose = 0; +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; +my $terse = 0; +my $showfile = 0; +my $file = 0; +my $git = 0; +my %git_commits = (); +my $check = 0; +my $check_orig = 0; +my $summary = 1; +my $mailback = 0; +my $summary_file = 0; +my $show_types = 0; +my $list_types = 0; +my $fix = 0; +my $fix_inplace = 0; +my $root; +my $gitroot = $ENV{'GIT_DIR'}; +$gitroot = ".git" if !defined($gitroot); +my %debug; +my %camelcase = (); +my %use_type = (); +my @use = (); +my %ignore_type = (); +my @ignore = (); +my $help = 0; +my $configuration_file = ".checkpatch.conf"; +my $def_configuration_dirs_help = '.:$HOME:.scripts'; +(my $def_configuration_dirs = $def_configuration_dirs_help) =~ s/\$(\w+)/$ENV{$1}/g; +my $env_config_dir = 'CHECKPATCH_CONFIG_DIR'; +my $max_line_length = 100; +my $ignore_perl_version = 0; +my $spdx_cxx_comments = 0; +my $minimum_perl_version = 5.10.0; +my $min_conf_desc_length = 4; +my $spelling_file = "$D/spelling.txt"; +my $codespell = 0; +my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $user_codespellfile = ""; +my $conststructsfile = "$D/const_structs.checkpatch"; +my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; +my $typedefsfile; +my $color = "auto"; +my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE +# git output parsing needs US English output, so first set backtick child process LANGUAGE +my $git_command ='export LANGUAGE=en_US.UTF-8; git'; +my $tabsize = 8; +my ${CONFIG_} = "CONFIG_"; + +my %maybe_linker_symbol; # for externs in c exceptions, when seen in *vmlinux.lds.h + +sub help { + my ($exitcode) = @_; + + print << "EOM"; +Usage: $P [OPTION]... [FILE]... +Version: $V + +Options: + -q, --quiet quiet + -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 + --showfile emit diffed file position, not input file position + -g, --git treat FILE as a single commit or git revision range + single git commit with: + + ^ + ~n + multiple git commits with: + .. + ... + - + git merges are ignored + -f, --file treat FILE as regular source file + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --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 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 + --mailback only produce a report in case of warnings/errors + --summary-file include the filename in summary + --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of + 'values', 'possible', 'type', and 'attr' (default + is all off) + --test-only=WORD report only warnings/errors containing WORD + literally + --fix EXPERIMENTAL - may create horrible results + If correctable single-line errors exist, create + ".EXPERIMENTAL-checkpatch-fixes" + with potential errors corrected to the preferred + checkpatch style + --fix-inplace EXPERIMENTAL - may create horrible results + Is the same as --fix, but overwrites the input + file. It's your fault if there's no backup or git + --ignore-perl-version override checking of perl version. expect + runtime errors. + --spdx-cxx-comments don't force C comments (/* */) for SPDX license + (required by old toolchains), allow also C++ + comments (//). + NOTE: it should *not* be used for Linux mainline. + --codespell Use the codespell dictionary for spelling/typos + (default:$codespellfile) + --codespellfile Use this codespell dictionary + --typedefsfile Read additional types from this file + --color[=WHEN] Use colors 'always', 'never', or only when output + is a terminal ('auto'). Default is 'auto'. + --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default + ${CONFIG_}) + -h, --help, --version display this help and exit + +When FILE is - read standard input. + +CONFIGURATION FILE +Default configuration options can be stored in $configuration_file, +search path: '$def_configuration_dirs_help' or in a directory specified by +\$$env_config_dir environment variable (fallback to the default search path). +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{$_}++ } @_; +} + +sub list_types { + my ($exitcode) = @_; + + my $count = 0; + + local $/ = undef; + + open(my $script, '<', abs_path($P)) or + die "$P: Can't read '$P' $!\n"; + + my $text = <$script>; + close($script); + + my %types = (); + # Also catch when type or level is passed through a variable + while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + if (defined($1)) { + if (exists($types{$2})) { + $types{$2} .= ",$1" if ($types{$2} ne $1); + } else { + $types{$2} = $1; + } + } else { + $types{$2} = "UNDETERMINED"; + } + } + + print("#\tMessage type\n\n"); + if ($color) { + print(" ( Color coding: "); + print(RED . "ERROR" . RESET); + print(" | "); + print(YELLOW . "WARNING" . RESET); + print(" | "); + print(GREEN . "CHECK" . RESET); + print(" | "); + print("Multiple levels / Undetermined"); + print(" )\n\n"); + } + + foreach my $type (sort keys %types) { + my $orig_type = $type; + if ($color) { + my $level = $types{$type}; + if ($level eq "ERROR") { + $type = RED . $type . RESET; + } elsif ($level eq "WARN") { + $type = YELLOW . $type . RESET; + } elsif ($level eq "CHK") { + $type = GREEN . $type . RESET; + } + } + print(++$count . "\t" . $type . "\n"); + if ($verbose && exists($verbose_messages{$orig_type})) { + my $message = $verbose_messages{$orig_type}; + $message =~ s/\n/\n\t/g; + print("\t" . $message . "\n\n"); + } + } + + exit($exitcode); +} + +my $conf = which_conf($configuration_file, $env_config_dir, $def_configuration_dirs); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable $configuration_file file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +sub load_docs { + open(my $docs, '<', "$docsfile") + or warn "$P: Can't read the documentation file $docsfile $!\n"; + + my $type = ''; + my $desc = ''; + my $in_desc = 0; + + while (<$docs>) { + chomp; + my $line = $_; + $line =~ s/\s+$//; + + if ($line =~ /^\s*\*\*(.+)\*\*$/) { + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + $type = $1; + $desc = ''; + $in_desc = 1; + } elsif ($in_desc) { + if ($line =~ /^(?:\s{4,}|$)/) { + $line =~ s/^\s{4}//; + $desc .= $line; + $desc .= "\n"; + } else { + $verbose_messages{$type} = trim($desc); + $type = ''; + $desc = ''; + $in_desc = 0; + } + } + } + + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + close($docs); +} + +# Perl's Getopt::Long allows options to take optional arguments after a space. +# Prevent --color by itself from consuming other arguments +foreach (@ARGV) { + if ($_ eq "--color" || $_ eq "-color") { + $_ = "--color=$color"; + } +} + +GetOptions( + 'q|quiet+' => \$quiet, + 'v|verbose!' => \$verbose, + 'tree!' => \$tree, + 'signoff!' => \$chk_signoff, + 'fixes-tag!' => \$chk_fixes_tag, + 'patch!' => \$chk_patch, + 'emacs!' => \$emacs, + 'terse!' => \$terse, + 'showfile!' => \$showfile, + 'f|file!' => \$file, + 'g|git!' => \$git, + 'subjective!' => \$check, + 'strict!' => \$check, + 'ignore=s' => \@ignore, + 'types=s' => \@use, + 'show-types!' => \$show_types, + 'list-types!' => \$list_types, + 'max-line-length=i' => \$max_line_length, + 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'tab-size=i' => \$tabsize, + 'root=s' => \$root, + 'summary!' => \$summary, + 'mailback!' => \$mailback, + 'summary-file!' => \$summary_file, + 'fix!' => \$fix, + 'fix-inplace!' => \$fix_inplace, + 'ignore-perl-version!' => \$ignore_perl_version, + 'spdx-cxx-comments!' => \$spdx_cxx_comments, + 'debug=s' => \%debug, + 'test-only=s' => \$tst_only, + 'codespell!' => \$codespell, + 'codespellfile=s' => \$user_codespellfile, + 'typedefsfile=s' => \$typedefsfile, + 'color=s' => \$color, + 'no-color' => \$color, #keep old behaviors of -nocolor + 'nocolor' => \$color, #keep old behaviors of -nocolor + 'kconfig-prefix=s' => \${CONFIG_}, + 'h|help' => \$help, + 'version' => \$help +) or $help = 2; + +if ($user_codespellfile) { + # Use the user provided codespell file unconditionally + $codespellfile = $user_codespellfile; +} elsif (!(-f $codespellfile)) { + # If /usr/share/codespell/dictionary.txt is not present, try to find it + # under codespell's install directory: /data/dictionary.txt + if (($codespell || $help) && which("python3") ne "") { + my $python_codespell_dict = << "EOF"; + +import os.path as op +import codespell_lib +codespell_dir = op.dirname(codespell_lib.__file__) +codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt') +print(codespell_file, end='') +EOF + + my $codespell_dict = `python3 -c "$python_codespell_dict" 2> /dev/null`; + $codespellfile = $codespell_dict if (-f $codespell_dict); + } +} + +# $help is 1 if either -h, --help or --version is passed as option - exitcode: 0 +# $help is 2 if invalid option is passed - exitcode: 1 +help($help - 1) if ($help); + +die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); +die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "$P: Invalid color mode: $color\n"; +} + +load_docs() if ($verbose); +list_types(0) if ($list_types); + +$fix = 1 if ($fix_inplace); +$check_orig = $check; + +my $exit = 0; + +my $perl_version_ok = 1; +if ($^V && $^V lt $minimum_perl_version) { + $perl_version_ok = 0; + printf "$P: requires at least perl version %vd\n", $minimum_perl_version; + exit(1) if (!$ignore_perl_version); +} + +#if no filenames are given, push '-' to read patch from stdin +if ($#ARGV < 0) { + push(@ARGV, '-'); +} + +# skip TAB size 1 to avoid additional checks on $tabsize - 1 +die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); + +sub hash_save_array_words { + my ($hashRef, $arrayRef) = @_; + + my @array = split(/,/, join(',', @$arrayRef)); + foreach my $word (@array) { + $word =~ s/\s*\n?$//g; + $word =~ s/^\s*//g; + $word =~ s/\s+/ /g; + $word =~ tr/[a-z]/[A-Z]/; + + next if ($word =~ m/^\s*#/); + next if ($word =~ m/^\s*$/); + + $hashRef->{$word}++; + } +} + +sub hash_show_words { + my ($hashRef, $prefix) = @_; + + if (keys %$hashRef) { + print "\nNOTE: $prefix message types:"; + foreach my $word (sort keys %$hashRef) { + print " $word"; + } + print "\n"; + } +} + +hash_save_array_words(\%ignore_type, \@ignore); +hash_save_array_words(\%use_type, \@use); + +my $dbg_values = 0; +my $dbg_possible = 0; +my $dbg_type = 0; +my $dbg_attr = 0; +for my $key (keys %debug) { + ## no critic + eval "\${dbg_$key} = '$debug{$key}';"; + die "$@" if ($@); +} + +my $rpt_cleaners = 0; + +if ($terse) { + $emacs = 1; + $quiet++; +} + +if ($tree) { + if (defined $root) { + if (!top_of_kernel_tree($root)) { + die "$P: $root: --root does not point at a valid tree\n"; + } + } else { + if (top_of_kernel_tree('.')) { + $root = '.'; + } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && + top_of_kernel_tree($1)) { + $root = $1; + } + } + + if (!defined $root) { + print "Must be run from the top-level dir. of a kernel tree\n"; + exit(2); + } +} + +my $emitted_corrupt = 0; + +our $Ident = qr{ + [A-Za-z_][A-Za-z\d_]* + (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* + }x; +our $Storage = qr{extern|static|asmlinkage}; +our $Sparse = qr{ + __user| + __kernel| + __force| + __iomem| + __must_check| + __kprobes| + __ref| + __refconst| + __refdata| + __rcu| + __private + }x; +our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; +our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; +our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; +our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; +our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; + +# Notes to $Attribute: +# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check +our $Attribute = qr{ + const| + volatile| + __percpu| + __nocast| + __safe| + __bitwise| + __packed__| + __packed2__| + __naked| + __maybe_unused| + __always_unused| + __noreturn| + __used| + __cold| + __pure| + __noclone| + __deprecated| + __read_mostly| + __ro_after_init| + __kprobes| + $InitAttribute| + __aligned\s*\(.*\)| + ____cacheline_aligned| + ____cacheline_aligned_in_smp| + ____cacheline_internodealigned_in_smp| + __weak| + __alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) + }x; +our $Modifier; +our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; +our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; +our $Lval = qr{$Ident(?:$Member)*}; + +our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; +our $Binary = qr{(?i)0b[01]+$Int_type?}; +our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; +our $Int = qr{[0-9]+$Int_type?}; +our $Octal = qr{0[0-7]+$Int_type?}; +our $String = qr{(?:\b[Lu])?"[X\t]*"}; +our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; +our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; +our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; +our $Float = qr{$Float_hex|$Float_dec|$Float_int}; +our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; +our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; +our $Compare = qr{<=|>=|==|!=|<|(?}; +our $Arithmetic = qr{\+|-|\*|\/|%}; +our $Operators = qr{ + <=|>=|==|!=| + =>|->|<<|>>|<|>|!|~| + &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic + }x; + +our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; + +our $BasicType; +our $NonptrType; +our $NonptrTypeMisordered; +our $NonptrTypeWithAttr; +our $Type; +our $TypeMisordered; +our $Declare; +our $DeclareMisordered; + +our $NON_ASCII_UTF8 = qr{ + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +}x; + +our $UTF8 = qr{ + [\x09\x0A\x0D\x20-\x7E] # ASCII + | $NON_ASCII_UTF8 +}x; + +our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; +our $typeOtherOSTypedefs = qr{(?x: + u_(?:char|short|int|long) | # bsd + u(?:nchar|short|int|long) # sysv +)}; +our $typeKernelTypedefs = qr{(?x: + (?:__)?(?:u|s|be|le)(?:8|16|32|64)| + atomic_t +)}; +our $typeStdioTypedefs = qr{(?x: + FILE +)}; +our $typeTypedefs = qr{(?x: + $typeC99Typedefs\b| + $typeOtherOSTypedefs\b| + $typeKernelTypedefs\b| + $typeStdioTypedefs\b +)}; + +our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; + +our $logFunctions = qr{(?x: + printk(?:_ratelimited|_once|_deferred_once|_deferred|)| + (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| + TP_printk| + WARN(?:_RATELIMIT|_ONCE|)| + panic| + MODULE_[A-Z_]+| + seq_vprintf|seq_printf|seq_puts +)}; + +our $allocFunctions = qr{(?x: + (?:(?:devm_)? + (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | + kstrdup(?:_const)? | + kmemdup(?:_nul)?) | + (?:\w+)?alloc_skb(?:_ip_align)? | + # dev_alloc_skb/netdev_alloc_skb, et al + dma_alloc_coherent +)}; + +our $signature_tags = qr{(?xi: + Signed-off-by:| + Co-developed-by:| + Acked-by:| + Tested-by:| + Reviewed-by:| + Reported-by:| + Suggested-by:| + Assisted-by:| + To:| + Cc: +)}; + +our @link_tags = qw(Link Closes); + +#Create a search and print patterns for all these strings to be used directly below +our $link_tags_search = ""; +our $link_tags_print = ""; +foreach my $entry (@link_tags) { + if ($link_tags_search ne "") { + $link_tags_search .= '|'; + $link_tags_print .= ' or '; + } + $entry .= ':'; + $link_tags_search .= $entry; + $link_tags_print .= "'$entry'"; +} +$link_tags_search = "(?:${link_tags_search})"; + +our $tracing_logging_tags = qr{(?xi: + [=-]*> | + <[=-]* | + \[ | + \] | + start | + called | + entered | + entry | + enter | + in | + inside | + here | + begin | + exit | + end | + done | + leave | + completed | + out | + return | + [\.\!:\s]* +)}; + +# Device ID types like found in include/linux/mod_devicetable.h. +our $dev_id_types = qr{\b[a-z]\w*_device_id\b}; + +sub edit_distance_min { + my (@arr) = @_; + my $len = scalar @arr; + if ((scalar @arr) < 1) { + # if underflow, return + return; + } + my $min = $arr[0]; + for my $i (0 .. ($len-1)) { + if ($arr[$i] < $min) { + $min = $arr[$i]; + } + } + return $min; +} + +sub get_edit_distance { + my ($str1, $str2) = @_; + $str1 = lc($str1); + $str2 = lc($str2); + $str1 =~ s/-//g; + $str2 =~ s/-//g; + my $len1 = length($str1); + my $len2 = length($str2); + # two dimensional array storing minimum edit distance + my @distance; + for my $i (0 .. $len1) { + for my $j (0 .. $len2) { + if ($i == 0) { + $distance[$i][$j] = $j; + } elsif ($j == 0) { + $distance[$i][$j] = $i; + } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { + $distance[$i][$j] = $distance[$i - 1][$j - 1]; + } else { + my $dist1 = $distance[$i][$j - 1]; #insert distance + my $dist2 = $distance[$i - 1][$j]; # remove + my $dist3 = $distance[$i - 1][$j - 1]; #replace + $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); + } + } + } + return $distance[$len1][$len2]; +} + +sub find_standard_signature { + my ($sign_off) = @_; + my @standard_signature_tags = ( + 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', + 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' + ); + foreach my $signature (@standard_signature_tags) { + return $signature if (get_edit_distance($sign_off, $signature) <= 2); + } + + return ""; +} + +our $obsolete_archives = qr{(?xi: + \Qfreedesktop.org/archives/dri-devel\E | + \Qlists.infradead.org\E | + \Qlkml.org\E | + \Qmail-archive.com\E | + \Qmailman.alsa-project.org/pipermail\E | + \Qmarc.info\E | + \Qozlabs.org/pipermail\E | + \Qspinics.net\E +)}; + +our @typeListMisordered = ( + qr{char\s+(?:un)?signed}, + qr{int\s+(?:(?:un)?signed\s+)?short\s}, + qr{int\s+short(?:\s+(?:un)?signed)}, + qr{short\s+int(?:\s+(?:un)?signed)}, + qr{(?:un)?signed\s+int\s+short}, + qr{short\s+(?:un)?signed}, + qr{long\s+int\s+(?:un)?signed}, + qr{int\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed\s+int}, + qr{int\s+(?:un)?signed\s+long}, + qr{int\s+(?:un)?signed}, + qr{int\s+long\s+long\s+(?:un)?signed}, + qr{long\s+long\s+int\s+(?:un)?signed}, + qr{long\s+long\s+(?:un)?signed\s+int}, + qr{long\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed}, +); + +our @typeList = ( + qr{void}, + qr{(?:(?:un)?signed\s+)?char}, + qr{(?:(?:un)?signed\s+)?short\s+int}, + qr{(?:(?:un)?signed\s+)?short}, + qr{(?:(?:un)?signed\s+)?int}, + qr{(?:(?:un)?signed\s+)?long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long}, + qr{(?:(?:un)?signed\s+)?long}, + qr{(?:un)?signed}, + qr{float}, + qr{double}, + qr{bool}, + qr{struct\s+$Ident}, + qr{union\s+$Ident}, + qr{enum\s+$Ident}, + qr{${Ident}_t}, + qr{${Ident}_handler}, + qr{${Ident}_handler_fn}, + @typeListMisordered, +); + +our $C90_int_types = qr{(?x: + long\s+long\s+int\s+(?:un)?signed| + long\s+long\s+(?:un)?signed\s+int| + long\s+long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+long\s+int| + (?:(?:un)?signed\s+)?long\s+long| + int\s+long\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long\s+long| + + long\s+int\s+(?:un)?signed| + long\s+(?:un)?signed\s+int| + long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+int| + (?:(?:un)?signed\s+)?long| + int\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long| + + int\s+(?:un)?signed| + (?:(?:un)?signed\s+)?int +)}; + +our @typeListFile = (); +our @typeListWithAttr = ( + @typeList, + qr{struct\s+$InitAttribute\s+$Ident}, + qr{union\s+$InitAttribute\s+$Ident}, +); + +our @modifierList = ( + qr{fastcall}, +); +our @modifierListFile = (); + +our @mode_permission_funcs = ( + ["module_param", 3], + ["module_param_(?:array|named|string)", 4], + ["module_param_array_named", 5], + ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], + ["proc_create(?:_data|)", 2], + ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], + ["IIO_DEV_ATTR_[A-Z_]+", 1], + ["SENSOR_(?:DEVICE_|)ATTR_2", 2], + ["SENSOR_TEMPLATE(?:_2|)", 3], + ["__ATTR", 2], +); + +my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; + +#Create a search pattern for all these functions to speed up a loop below +our $mode_perms_search = ""; +foreach my $entry (@mode_permission_funcs) { + $mode_perms_search .= '|' if ($mode_perms_search ne ""); + $mode_perms_search .= $entry->[0]; +} +$mode_perms_search = "(?:${mode_perms_search})"; + +our %deprecated_apis = ( + "kmap" => "kmap_local_page", + "kunmap" => "kunmap_local", + "kmap_atomic" => "kmap_local_page", + "kunmap_atomic" => "kunmap_local", + #These should be enough to drive away new IDR users + "DEFINE_IDR" => "DEFINE_XARRAY", + "idr_init" => "xa_init", + "idr_init_base" => "xa_init_flags", +); + +#Create a search pattern for all these strings to speed up a loop below +our $deprecated_apis_search = ""; +foreach my $entry (keys %deprecated_apis) { + $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); + $deprecated_apis_search .= $entry; +} +$deprecated_apis_search = "(?:${deprecated_apis_search})"; + +our $mode_perms_world_writable = qr{ + S_IWUGO | + S_IWOTH | + S_IRWXUGO | + S_IALLUGO | + 0[0-7][0-7][2367] +}x; + +our %mode_permission_string_types = ( + "S_IRWXU" => 0700, + "S_IRUSR" => 0400, + "S_IWUSR" => 0200, + "S_IXUSR" => 0100, + "S_IRWXG" => 0070, + "S_IRGRP" => 0040, + "S_IWGRP" => 0020, + "S_IXGRP" => 0010, + "S_IRWXO" => 0007, + "S_IROTH" => 0004, + "S_IWOTH" => 0002, + "S_IXOTH" => 0001, + "S_IRWXUGO" => 0777, + "S_IRUGO" => 0444, + "S_IWUGO" => 0222, + "S_IXUGO" => 0111, +); + +#Create a search pattern for all these strings to speed up a loop below +our $mode_perms_string_search = ""; +foreach my $entry (keys %mode_permission_string_types) { + $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); + $mode_perms_string_search .= $entry; +} +our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; +our $multi_mode_perms_string_search = qr{ + ${single_mode_perms_string_search} + (?:\s*\|\s*${single_mode_perms_string_search})* +}x; + +sub perms_to_octal { + my ($string) = @_; + + return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); + + my $val = ""; + my $oval = ""; + my $to = 0; + my $curpos = 0; + my $lastpos = 0; + while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { + $curpos = pos($string); + my $match = $2; + my $omatch = $1; + last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); + $lastpos = $curpos; + $to |= $mode_permission_string_types{$match}; + $val .= '\s*\|\s*' if ($val ne ""); + $val .= $match; + $oval .= $omatch; + } + $oval =~ s/^\s*\|\s*//; + $oval =~ s/\s*\|\s*$//; + return sprintf("%04o", $to); +} + +our $allowed_asm_includes = qr{(?x: + irq| + memory| + time| + reboot +)}; +# memory.h: ARM has a custom one + +# Load common spelling mistakes and build regular expression list. +my $misspellings; +my %spelling_fix; + +if (open(my $spelling, '<', $spelling_file)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my ($suspect, $fix) = split(/\|\|/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); +} else { + warn "No typos will be found - file '$spelling_file': $!\n"; +} + +if ($codespell) { + if (open(my $spelling, '<', $codespellfile)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + next if ($line =~ m/, disabled/i); + + $line =~ s/,.*$//; + + my ($suspect, $fix) = split(/->/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); + } else { + warn "No codespell typos will be found - file '$codespellfile': $!\n"; + } +} + +$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; + +sub read_words { + my ($wordsRef, $file) = @_; + + if (open(my $words, '<', $file)) { + while (<$words>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + if ($line =~ /\s/) { + print("$file: '$line' invalid - ignored\n"); + next; + } + + $$wordsRef .= '|' if (defined $$wordsRef); + $$wordsRef .= $line; + } + close($file); + return 1; + } + + return 0; +} + +my $const_structs; +if (show_type("CONST_STRUCT")) { + read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; +} + +if (defined($typedefsfile)) { + my $typeOtherTypedefs; + read_words(\$typeOtherTypedefs, $typedefsfile) + or warn "No additional types will be considered - file '$typedefsfile': $!\n"; + $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); +} + +sub build_types { + my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; + my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; + my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; + my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; + $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; + $BasicType = qr{ + (?:$typeTypedefs\b)| + (?:${all}\b) + }x; + $NonptrType = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${all}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeMisordered = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:${Misordered}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeWithAttr = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${allWithAttr}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $Type = qr{ + $NonptrType + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $TypeMisordered = qr{ + $NonptrTypeMisordered + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; + $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; +} +build_types(); + +our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; + +# Using $balanced_parens, $LvalOrFunc, or $FuncArg +# requires at least perl version v5.10.0 +# Any use must be runtime checked with $^V + +our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; +our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; +our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; + +our $declaration_macros = qr{(?x: + (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| + (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| + (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\(| + (?:$Storage\s+)?(?:XA_STATE|XA_STATE_ORDER)\s*\(| + __cacheline_group_(?:begin|end)(?:_aligned)?\s*\(| + __dma_from_device_group_(?:begin|end)\s*\( +)}; + +our %allow_repeated_words = ( + add => '', + added => '', + bad => '', + be => '', +); + +sub deparenthesize { + my ($string) = @_; + return "" if (!defined($string)); + + while ($string =~ /^\s*\(.*\)\s*$/) { + $string =~ s@^\s*\(\s*@@; + $string =~ s@\s*\)\s*$@@; + } + + $string =~ s@\s+@ @g; + + return $string; +} + +sub seed_camelcase_file { + my ($file) = @_; + + return if (!(-f $file)); + + local $/; + + open(my $include_file, '<', "$file") + or warn "$P: Can't read '$file' $!\n"; + my $text = <$include_file>; + close($include_file); + + my @lines = split('\n', $text); + + foreach my $line (@lines) { + next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); + if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { + $camelcase{$1} = 1; + } + } +} + +our %maintained_status = (); + +sub is_maintained_obsolete { + my ($filename) = @_; + + return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); + + if (!exists($maintained_status{$filename})) { + $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + } + + return $maintained_status{$filename} =~ /obsolete/i; +} + +sub is_SPDX_License_valid { + my ($license) = @_; + + return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); + + my $root_path = abs_path($root); + my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; + return 0 if ($status ne ""); + return 1; +} + +my $camelcase_seeded = 0; +sub seed_camelcase_includes { + return if ($camelcase_seeded); + + my $files; + my $camelcase_cache = ""; + my @include_files = (); + + $camelcase_seeded = 1; + + if (-e "$gitroot") { + my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; + chomp $git_last_include_commit; + $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } else { + my $last_mod_date = 0; + $files = `find $root/include -name "*.h"`; + @include_files = split('\n', $files); + foreach my $file (@include_files) { + my $date = POSIX::strftime("%Y%m%d%H%M", + localtime((stat $file)[9])); + $last_mod_date = $date if ($last_mod_date < $date); + } + $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; + } + + if ($camelcase_cache ne "" && -f $camelcase_cache) { + open(my $camelcase_file, '<', "$camelcase_cache") + or warn "$P: Can't read '$camelcase_cache' $!\n"; + while (<$camelcase_file>) { + chomp; + $camelcase{$_} = 1; + } + close($camelcase_file); + + return; + } + + if (-e "$gitroot") { + $files = `${git_command} ls-files "include/*.h"`; + @include_files = split('\n', $files); + } + + foreach my $file (@include_files) { + seed_camelcase_file($file); + } + + if ($camelcase_cache ne "") { + unlink glob ".checkpatch-camelcase.*"; + open(my $camelcase_file, '>', "$camelcase_cache") + or warn "$P: Can't write '$camelcase_cache' $!\n"; + foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { + print $camelcase_file ("$_\n"); + } + close($camelcase_file); + } +} + +sub git_is_single_file { + my ($filename) = @_; + + return 0 if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} ls-files -- $filename 2>/dev/null`; + my $count = $output =~ tr/\n//; + return $count eq 1 && $output =~ m{^${filename}$}; +} + +sub git_commit_info { + my ($commit, $id, $desc) = @_; + + return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; + $output =~ s/^\s*//gm; + my @lines = split("\n", $output); + + return ($id, $desc) if ($#lines < 0); + + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { +# Maybe one day convert this block of bash into something that returns +# all matching commit ids, but it's very slow... +# +# echo "checking commits $1..." +# git rev-list --remotes | grep -i "^$1" | +# while read line ; do +# git log --format='%H %s' -1 $line | +# echo "commit $(cut -c 1-12,41-)" +# done + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || + $lines[0] =~ /^fatal: bad object $commit/) { + $id = undef; + } else { + $id = substr($lines[0], 0, 12); + $desc = substr($lines[0], 41); + } + + return ($id, $desc); +} + +$chk_signoff = 0 if ($file); +$chk_fixes_tag = 0 if ($file); + +my @rawlines = (); +my @lines = (); +my @fixed = (); +my @fixed_inserted = (); +my @fixed_deleted = (); +my $fixlinenr = -1; + +# If input is git commits, extract all commits from the commit expressions. +# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. +die "$P: No git repository found\n" if ($git && !-e "$gitroot"); + +if ($git) { + my @commits = (); + foreach my $commit_expr (@ARGV) { + my $git_range; + if ($commit_expr =~ m/^(.*)-(\d+)$/) { + $git_range = "-$2 $1"; + } elsif ($commit_expr =~ m/\.\./) { + $git_range = "$commit_expr"; + } else { + $git_range = "-1 $commit_expr"; + } + my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + foreach my $line (split(/\n/, $lines)) { + $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; + next if (!defined($1) || !defined($2)); + my $sha1 = $1; + my $subject = $2; + unshift(@commits, $sha1); + $git_commits{$sha1} = $subject; + } + } + die "$P: no git commits after extraction!\n" if (@commits == 0); + @ARGV = @commits; +} + +my $vname; +$allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; +for my $filename (@ARGV) { + my $FILE; + my $is_git_file = git_is_single_file($filename); + my $oldfile = $file; + $file = 1 if ($is_git_file); + if ($git) { + open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || + die "$P: $filename: git format-patch failed - $!\n"; + } elsif ($file) { + open($FILE, '-|', "diff -u /dev/null $filename") || + die "$P: $filename: diff failed - $!\n"; + } elsif ($filename eq '-') { + open($FILE, '<&STDIN'); + } else { + open($FILE, '<', "$filename") || + die "$P: $filename: open failed - $!\n"; + } + if ($filename eq '-') { + $vname = 'Your patch'; + } elsif ($git) { + $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; + } else { + $vname = $filename; + } + while (<$FILE>) { + chomp; + push(@rawlines, $_); + $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); + } + close($FILE); + + if ($#ARGV > 0 && $quiet == 0) { + print '-' x length($vname) . "\n"; + print "$vname\n"; + print '-' x length($vname) . "\n"; + } + + if (!process($filename)) { + $exit = 1; + } + @rawlines = (); + @lines = (); + @fixed = (); + @fixed_inserted = (); + @fixed_deleted = (); + $fixlinenr = -1; + @modifierListFile = (); + @typeListFile = (); + build_types(); + $file = $oldfile if ($is_git_file); +} + +if (!$quiet) { + hash_show_words(\%use_type, "Used"); + hash_show_words(\%ignore_type, "Ignored"); + + if (!$perl_version_ok) { + print << "EOM" + +NOTE: perl $^V is not modern enough to detect all possible issues. + An upgrade to at least perl $minimum_perl_version is suggested. +EOM + } + if ($exit) { + print << "EOM" + +NOTE: If any of the errors are false positives, please report + them to the maintainer, see CHECKPATCH in MAINTAINERS. +EOM + } +} + +exit($exit); + +sub top_of_kernel_tree { + my ($root) = @_; + + my @tree_check = ( + "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts", + ); + + foreach my $check (@tree_check) { + if (! -e $root . '/' . $check) { + return 0; + } + } + return 1; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $quoted = ""; + my $name_comment = ""; + my $address = ""; + my $comment = ""; + + if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { + $name = $1; + $address = $2; + $comment = $3 if defined $3; + } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + $formatted_email =~ s/\Q$address\E.*$//; + $name = $formatted_email; + $name = trim($name); + $name =~ s/^\"|\"$//g; + # If there's a name left after stripping spaces and + # leading quotes, and the address doesn't have both + # leading and trailing angle brackets, the address + # is invalid. ie: + # "joe smith joe@smith.com" bad + # "joe smith ]+>$/) { + $name = ""; + $address = ""; + $comment = ""; + } + } + + # Extract comments from names excluding quoted parts + # "John D. (Doe)" - Do not extract + if ($name =~ s/\"(.+)\"//) { + $quoted = $1; + } + while ($name =~ s/\s*($balanced_parens)\s*/ /) { + $name_comment .= trim($1); + } + $name =~ s/^[ \"]+|[ \"]+$//g; + $name = trim("$quoted $name"); + + $address = trim($address); + $address =~ s/^\<|\>$//g; + $comment = trim($comment); + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?"; + } + $formatted_email .= "$comment"; + return $formatted_email; +} + +sub reformat_email { + my ($email) = @_; + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + return format_email($email_name, $name_comment, $email_address, $comment); +} + +sub same_email_addresses { + my ($email1, $email2) = @_; + + my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); + my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); + + return $email1_name eq $email2_name && + $email1_address eq $email2_address && + $name1_comment eq $name2_comment && + $comment1 eq $comment2; +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf, $env_key, $paths) = @_; + my $env_dir = $ENV{$env_key}; + + if (defined($env_dir) && $env_dir ne "") { + return "$env_dir/$conf" if (-e "$env_dir/$conf"); + warn "$P: Can't find a readable $conf in '$env_dir', falling back to default search paths\n"; + } + + foreach my $path (split(/:/, $paths)) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub expand_tabs { + my ($str) = @_; + + my $res = ''; + my $n = 0; + for my $c (split(//, $str)) { + if ($c eq "\t") { + $res .= ' '; + $n++; + for (; ($n % $tabsize) != 0; $n++) { + $res .= ' '; + } + next; + } + $res .= $c; + $n++; + } + + return $res; +} +sub copy_spacing { + (my $res = shift) =~ tr/\t/ /c; + return $res; +} + +sub line_stats { + my ($line) = @_; + + # Drop the diff line leader and expand tabs + $line =~ s/^.//; + $line = expand_tabs($line); + + # Pick the indent from the front of the line. + my ($white) = ($line =~ /^(\s*)/); + + return (length($line), length($white)); +} + +my $sanitise_quote = ''; + +sub sanitise_line_reset { + my ($in_comment) = @_; + + if ($in_comment) { + $sanitise_quote = '*/'; + } else { + $sanitise_quote = ''; + } +} +sub sanitise_line { + my ($line) = @_; + + my $res = ''; + my $l = ''; + + my $qlen = 0; + my $off = 0; + my $c; + + # Always copy over the diff marker. + $res = substr($line, 0, 1); + + for ($off = 1; $off < length($line); $off++) { + $c = substr($line, $off, 1); + + # Comments we are whacking completely including the begin + # and end, all to $;. + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { + $sanitise_quote = '*/'; + + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { + $sanitise_quote = ''; + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { + $sanitise_quote = '//'; + + substr($res, $off, 2, $sanitise_quote); + $off++; + next; + } + + # A \ in a string means ignore the next character. + if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && + $c eq "\\") { + substr($res, $off, 2, 'XX'); + $off++; + next; + } + # Regular quotes. + if ($c eq "'" || $c eq '"') { + if ($sanitise_quote eq '') { + $sanitise_quote = $c; + + substr($res, $off, 1, $c); + next; + } elsif ($sanitise_quote eq $c) { + $sanitise_quote = ''; + } + } + + #print "c<$c> SQ<$sanitise_quote>\n"; + if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { + substr($res, $off, 1, 'X'); + } else { + substr($res, $off, 1, $c); + } + } + + if ($sanitise_quote eq '//') { + $sanitise_quote = ''; + } + + # The pathname on a #include may be surrounded by '<' and '>'. + if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { + my $clean = 'X' x length($1); + $res =~ s@\<.*\>@<$clean>@; + + # The whole of a #error is a string. + } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { + my $clean = 'X' x length($1); + $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; + } + + if ($allow_c99_comments && $res =~ m@(//.*$)@) { + my $match = $1; + $res =~ s/\Q$match\E/"$;" x length($match)/e; + } + + return $res; +} + +sub get_quoted_string { + my ($line, $rawline) = @_; + + return "" if (!defined($line) || !defined($rawline)); + return "" if ($line !~ m/($String)/g); + return substr($rawline, $-[0], $+[0] - $-[0]); +} + +sub ctx_statement_block { + my ($linenr, $remain, $off) = @_; + my $line = $linenr - 1; + my $blk = ''; + my $soff = $off; + my $coff = $off - 1; + my $coff_set = 0; + + my $loff = 0; + + my $type = ''; + my $level = 0; + my @stack = (); + my $p; + my $c; + my $len = 0; + + my $remainder; + while (1) { + @stack = (['', 0]) if ($#stack == -1); + + #warn "CSB: blk<$blk> remain<$remain>\n"; + # If we are about to drop off the end, pull in more + # context. + if ($off >= $len) { + for (; $remain > 0; $line++) { + last if (!defined $lines[$line]); + next if ($lines[$line] =~ /^-/); + $remain--; + $loff = $len; + $blk .= $lines[$line] . "\n"; + $len = length($blk); + $line++; + last; + } + # Bail if there is no further context. + #warn "CSB: blk<$blk> off<$off> len<$len>\n"; + if ($off >= $len) { + last; + } + if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { + $level++; + $type = '#'; + } + } + $p = $c; + $c = substr($blk, $off, 1); + $remainder = substr($blk, $off); + + #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; + + # Handle nested #if/#else. + if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, [ $type, $level ]); + } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { + ($type, $level) = @{$stack[$#stack - 1]}; + } elsif ($remainder =~ /^#\s*endif\b/) { + ($type, $level) = @{pop(@stack)}; + } + + # Statement ends at the ';' or a close '}' at the + # outermost level. + if ($level == 0 && $c eq ';') { + last; + } + + # An else is really a conditional as long as its not else if + if ($level == 0 && $coff_set == 0 && + (!defined($p) || $p =~ /(?:\s|\}|\+)/) && + $remainder =~ /^(else)(?:\s|{)/ && + $remainder !~ /^else\s+if\b/) { + $coff = $off + length($1) - 1; + $coff_set = 1; + #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; + #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; + } + + if (($type eq '' || $type eq '(') && $c eq '(') { + $level++; + $type = '('; + } + if ($type eq '(' && $c eq ')') { + $level--; + $type = ($level != 0)? '(' : ''; + + if ($level == 0 && $coff < $soff) { + $coff = $off; + $coff_set = 1; + #warn "CSB: mark coff<$coff>\n"; + } + } + if (($type eq '' || $type eq '{') && $c eq '{') { + $level++; + $type = '{'; + } + if ($type eq '{' && $c eq '}') { + $level--; + $type = ($level != 0)? '{' : ''; + + if ($level == 0) { + if (substr($blk, $off + 1, 1) eq ';') { + $off++; + } + last; + } + } + # Preprocessor commands end at the newline unless escaped. + if ($type eq '#' && $c eq "\n" && $p ne "\\") { + $level--; + $type = ''; + $off++; + last; + } + $off++; + } + # We are truly at the end, so shuffle to the next line. + if ($off == $len) { + $loff = $len + 1; + $line++; + $remain--; + } + + my $statement = substr($blk, $soff, $off - $soff + 1); + my $condition = substr($blk, $soff, $coff - $soff + 1); + + #warn "STATEMENT<$statement>\n"; + #warn "CONDITION<$condition>\n"; + + #print "coff<$coff> soff<$off> loff<$loff>\n"; + + return ($statement, $condition, + $line, $remain + 1, $off - $loff + 1, $level); +} + +sub statement_lines { + my ($stmt) = @_; + + # Strip the diff line prefixes and rip blank lines at start and end. + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_rawlines { + my ($stmt) = @_; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_block_size { + my ($stmt) = @_; + + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*{//; + $stmt =~ s/}\s*$//; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + my @stmt_statements = ($stmt =~ /;/g); + + my $stmt_lines = $#stmt_lines + 2; + my $stmt_statements = $#stmt_statements + 1; + + if ($stmt_lines > $stmt_statements) { + return $stmt_lines; + } else { + return $stmt_statements; + } +} + +sub ctx_statement_full { + my ($linenr, $remain, $off) = @_; + my ($statement, $condition, $level); + + my (@chunks); + + # Grab the first conditional/block pair. + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "F: c<$condition> s<$statement> remain<$remain>\n"; + push(@chunks, [ $condition, $statement ]); + if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { + return ($level, $linenr, @chunks); + } + + # Pull in the following conditional/block pairs and see if they + # could continue the statement. + for (;;) { + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "C: c<$condition> s<$statement> remain<$remain>\n"; + last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); + #print "C: push\n"; + push(@chunks, [ $condition, $statement ]); + } + + return ($level, $linenr, @chunks); +} + +sub ctx_block_get { + my ($linenr, $remain, $outer, $open, $close, $off) = @_; + my $line; + my $start = $linenr - 1; + my $blk = ''; + my @o; + my @c; + my @res = (); + + my $level = 0; + my @stack = ($level); + for ($line = $start; $remain > 0; $line++) { + next if ($rawlines[$line] =~ /^-/); + $remain--; + + $blk .= $rawlines[$line]; + + # Handle nested #if/#else. + if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, $level); + } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { + $level = $stack[$#stack - 1]; + } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { + $level = pop(@stack); + } + + foreach my $c (split(//, $lines[$line])) { + ##print "C<$c>L<$level><$open$close>O<$off>\n"; + if ($off > 0) { + $off--; + next; + } + + if ($c eq $close && $level > 0) { + $level--; + last if ($level == 0); + } elsif ($c eq $open) { + $level++; + } + } + + if (!$outer || $level <= 1) { + push(@res, $rawlines[$line]); + } + + last if ($level == 0); + } + + return ($level, @res); +} +sub ctx_block_outer { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); + return @r; +} +sub ctx_block { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); + return @r; +} +sub ctx_statement { + my ($linenr, $remain, $off) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); + return @r; +} +sub ctx_block_level { + my ($linenr, $remain) = @_; + + return ctx_block_get($linenr, $remain, 0, '{', '}', 0); +} +sub ctx_statement_level { + my ($linenr, $remain, $off) = @_; + + return ctx_block_get($linenr, $remain, 0, '(', ')', $off); +} + +sub ctx_locate_comment { + my ($first_line, $end_line) = @_; + + # If c99 comment on the current line, or the line before or after + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + + # Catch a comment on the end of the line itself. + ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + return $current_comment if (defined $current_comment); + + # Look through the context and try and figure out if there is a + # comment. + my $in_comment = 0; + $current_comment = ''; + for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { + my $line = $rawlines[$linenr - 1]; + #warn " $line\n"; + if ($linenr == $first_line and $line =~ m@^.\s*\*@) { + $in_comment = 1; + } + if ($line =~ m@/\*@) { + $in_comment = 1; + } + if (!$in_comment && $current_comment ne '') { + $current_comment = ''; + } + $current_comment .= $line . "\n" if ($in_comment); + if ($line =~ m@\*/@) { + $in_comment = 0; + } + } + + chomp($current_comment); + return($current_comment); +} +sub ctx_has_comment { + my ($first_line, $end_line) = @_; + my $cmt = ctx_locate_comment($first_line, $end_line); + + ##print "LINE: $rawlines[$end_line - 1 ]\n"; + ##print "CMMT: $cmt\n"; + + return ($cmt ne ''); +} + +sub raw_line { + my ($linenr, $cnt) = @_; + + my $offset = $linenr - 1; + $cnt++; + + my $line; + while ($cnt) { + $line = $rawlines[$offset++]; + next if (defined($line) && $line =~ /^-/); + $cnt--; + } + + return $line; +} + +sub get_stat_real { + my ($linenr, $lc) = @_; + + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + + return $stat_real; +} + +sub get_stat_here { + my ($linenr, $cnt, $here) = @_; + + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + return $herectx; +} + +sub cat_vet { + my ($vet) = @_; + my ($res, $coded); + + $res = ''; + while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { + $res .= $1; + if ($2 ne '') { + $coded = sprintf("^%c", unpack('C', $2) + 64); + $res .= $coded; + } + } + $res =~ s/$/\$/; + + return $res; +} + +my $av_preprocessor = 0; +my $av_pending; +my @av_paren_type; +my $av_pend_colon; + +sub annotate_reset { + $av_preprocessor = 0; + $av_pending = '_'; + @av_paren_type = ('E'); + $av_pend_colon = 'O'; +} + +sub annotate_values { + my ($stream, $type) = @_; + + my $res; + my $var = '_' x length($stream); + my $cur = $stream; + + print "$stream\n" if ($dbg_values > 1); + + while (length($cur)) { + @av_paren_type = ('E') if ($#av_paren_type < 0); + print " <" . join('', @av_paren_type) . + "> <$type> <$av_pending>" if ($dbg_values > 1); + if ($cur =~ /^(\s+)/o) { + print "WS($1)\n" if ($dbg_values > 1); + if ($1 =~ /\n/ && $av_preprocessor) { + $type = pop(@av_paren_type); + $av_preprocessor = 0; + } + + } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { + print "CAST($1)\n" if ($dbg_values > 1); + push(@av_paren_type, $type); + $type = 'c'; + + } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { + print "DECLARE($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^($Modifier)\s*/) { + print "MODIFIER($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { + print "DEFINE($1,$2)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + if ($2 ne '') { + $av_pending = 'N'; + } + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { + print "UNDEF($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + + } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { + print "PRE_START($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { + print "PRE_RESTART($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $av_paren_type[$#av_paren_type]); + + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:endif))/o) { + print "PRE_END($1)\n" if ($dbg_values > 1); + + $av_preprocessor = 1; + + # Assume all arms of the conditional end as this + # one does, and continue as if the #endif was not here. + pop(@av_paren_type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\\\n)/o) { + print "PRECONT($1)\n" if ($dbg_values > 1); + + } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { + print "ATTR($1)\n" if ($dbg_values > 1); + $av_pending = $type; + $type = 'N'; + + } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { + print "SIZEOF($1)\n" if ($dbg_values > 1); + if (defined $2) { + $av_pending = 'V'; + } + $type = 'N'; + + } elsif ($cur =~ /^(if|while|for)\b/o) { + print "COND($1)\n" if ($dbg_values > 1); + $av_pending = 'E'; + $type = 'N'; + + } elsif ($cur =~/^(case)/o) { + print "CASE($1)\n" if ($dbg_values > 1); + $av_pend_colon = 'C'; + $type = 'N'; + + } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { + print "KEYWORD($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(\()/o) { + print "PAREN('$1')\n" if ($dbg_values > 1); + push(@av_paren_type, $av_pending); + $av_pending = '_'; + $type = 'N'; + + } elsif ($cur =~ /^(\))/o) { + my $new_type = pop(@av_paren_type); + if ($new_type ne '_') { + $type = $new_type; + print "PAREN('$1') -> $type\n" + if ($dbg_values > 1); + } else { + print "PAREN('$1')\n" if ($dbg_values > 1); + } + + } elsif ($cur =~ /^($Ident)\s*\(/o) { + print "FUNC($1)\n" if ($dbg_values > 1); + $type = 'V'; + $av_pending = 'V'; + + } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { + if (defined $2 && $type eq 'C' || $type eq 'T') { + $av_pend_colon = 'B'; + } elsif ($type eq 'E') { + $av_pend_colon = 'L'; + } + print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Ident|$Constant)/o) { + print "IDENT($1)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Assignment)/o) { + print "ASSIGN($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~/^(;|{|})/) { + print "END($1)\n" if ($dbg_values > 1); + $type = 'E'; + $av_pend_colon = 'O'; + + } elsif ($cur =~/^(,)/) { + print "COMMA($1)\n" if ($dbg_values > 1); + $type = 'C'; + + } elsif ($cur =~ /^(\?)/o) { + print "QUESTION($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(:)/o) { + print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); + + substr($var, length($res), 1, $av_pend_colon); + if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { + $type = 'E'; + } else { + $type = 'N'; + } + $av_pend_colon = 'O'; + + } elsif ($cur =~ /^(\[)/o) { + print "CLOSE($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { + my $variant; + + print "OPV($1)\n" if ($dbg_values > 1); + if ($type eq 'V') { + $variant = 'B'; + } else { + $variant = 'U'; + } + + substr($var, length($res), 1, $variant); + $type = 'N'; + + } elsif ($cur =~ /^($Operators)/o) { + print "OP($1)\n" if ($dbg_values > 1); + if ($1 ne '++' && $1 ne '--') { + $type = 'N'; + } + + } elsif ($cur =~ /(^.)/o) { + print "C($1)\n" if ($dbg_values > 1); + } + if (defined $1) { + $cur = substr($cur, length($1)); + $res .= $type x length($1); + } + } + + return ($res, $var); +} + +sub possible { + my ($possible, $line) = @_; + my $notPermitted = qr{(?: + ^(?: + $Modifier| + $Storage| + $Type| + DEFINE_\S+ + )$| + ^(?: + goto| + return| + case| + else| + asm|__asm__| + do| + \#| + \#\#| + )(?:\s|$)| + ^(?:typedef|struct|enum)\b + )}x; + warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); + if ($possible !~ $notPermitted) { + # Check for modifiers. + $possible =~ s/\s*$Storage\s*//g; + $possible =~ s/\s*$Sparse\s*//g; + if ($possible =~ /^\s*$/) { + + } elsif ($possible =~ /\s/) { + $possible =~ s/\s*$Type\s*//g; + for my $modifier (split(' ', $possible)) { + if ($modifier !~ $notPermitted) { + warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); + push(@modifierListFile, $modifier); + } + } + + } else { + warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); + push(@typeListFile, $possible); + } + build_types(); + } else { + warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); + } +} + +my $prefix = ''; + +sub show_type { + my ($type) = @_; + + $type =~ tr/[a-z]/[A-Z]/; + + return defined $use_type{$type} if (scalar keys %use_type > 0); + + return !defined $ignore_type{$type}; +} + +sub report { + my ($level, $type, $msg) = @_; + + if (!show_type($type) || + (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { + return 0; + } + my $output = ''; + if ($color) { + if ($level eq 'ERROR') { + $output .= RED; + } elsif ($level eq 'WARNING') { + $output .= YELLOW; + } else { + $output .= GREEN; + } + } + $output .= $prefix . $level . ':'; + if ($show_types) { + $output .= BLUE if ($color); + $output .= "$type:"; + } + $output .= RESET if ($color); + $output .= ' ' . $msg . "\n"; + + if ($showfile) { + my @lines = split("\n", $output, -1); + splice(@lines, 1, 1); + $output = join("\n", @lines); + } + + if ($terse) { + $output = (split('\n', $output))[0] . "\n"; + } + + if ($verbose && exists($verbose_messages{$type}) && + !exists($verbose_emitted{$type})) { + $output .= $verbose_messages{$type} . "\n\n"; + $verbose_emitted{$type} = 1; + } + + push(our @report, $output); + + return 1; +} + +sub report_dump { + our @report; +} + +sub fixup_current_range { + my ($lineRef, $offset, $length) = @_; + + if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { + my $o = $1; + my $l = $2; + my $no = $o + $offset; + my $nl = $l + $length; + $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; + } +} + +sub fix_inserted_deleted_lines { + my ($linesRef, $insertedRef, $deletedRef) = @_; + + my $range_last_linenr = 0; + my $delta_offset = 0; + + my $old_linenr = 0; + my $new_linenr = 0; + + my $next_insert = 0; + my $next_delete = 0; + + my @lines = (); + + my $inserted = @{$insertedRef}[$next_insert++]; + my $deleted = @{$deletedRef}[$next_delete++]; + + foreach my $old_line (@{$linesRef}) { + my $save_line = 1; + my $line = $old_line; #don't modify the array + if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename + $delta_offset = 0; + } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk + $range_last_linenr = $new_linenr; + fixup_current_range(\$line, $delta_offset, 0); + } + + while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { + $deleted = @{$deletedRef}[$next_delete++]; + $save_line = 0; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); + } + + while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { + push(@lines, ${$inserted}{'LINE'}); + $inserted = @{$insertedRef}[$next_insert++]; + $new_linenr++; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); + } + + if ($save_line) { + push(@lines, $line); + $new_linenr++; + } + + $old_linenr++; + } + + return @lines; +} + +sub fix_insert_line { + my ($linenr, $line) = @_; + + my $inserted = { + LINENR => $linenr, + LINE => $line, + }; + push(@fixed_inserted, $inserted); +} + +sub fix_delete_line { + my ($linenr, $line) = @_; + + my $deleted = { + LINENR => $linenr, + LINE => $line, + }; + + push(@fixed_deleted, $deleted); +} + +sub ERROR { + my ($type, $msg) = @_; + + if (report("ERROR", $type, $msg)) { + our $clean = 0; + our $cnt_error++; + return 1; + } + return 0; +} +sub WARN { + my ($type, $msg) = @_; + + if (report("WARNING", $type, $msg)) { + our $clean = 0; + our $cnt_warn++; + return 1; + } + return 0; +} +sub CHK { + my ($type, $msg) = @_; + + if ($check && report("CHECK", $type, $msg)) { + our $clean = 0; + our $cnt_chk++; + return 1; + } + return 0; +} + +sub check_absolute_file { + my ($absolute, $herecurr) = @_; + my $file = $absolute; + + ##print "absolute<$absolute>\n"; + + # See if any suffix of this path is a path within the tree. + while ($file =~ s@^[^/]*/@@) { + if (-f "$root/$file") { + ##print "file<$file>\n"; + last; + } + } + if (! -f _) { + return 0; + } + + # It is, so see if the prefix is acceptable. + my $prefix = $absolute; + substr($prefix, -length($file)) = ''; + + ##print "prefix<$prefix>\n"; + if ($prefix ne ".../") { + WARN("USE_RELATIVE_PATH", + "use relative pathname instead of absolute in changelog text\n" . $herecurr); + } +} + +sub trim { + my ($string) = @_; + + $string =~ s/^\s+|\s+$//g; + + return $string; +} + +sub ltrim { + my ($string) = @_; + + $string =~ s/^\s+//; + + return $string; +} + +sub rtrim { + my ($string) = @_; + + $string =~ s/\s+$//; + + return $string; +} + +sub string_find_replace { + my ($string, $find, $replace) = @_; + + $string =~ s/$find/$replace/g; + + return $string; +} + +sub tabify { + my ($leading) = @_; + + my $source_indent = $tabsize; + my $max_spaces_before_tab = $source_indent - 1; + my $spaces_to_tab = " " x $source_indent; + + #convert leading spaces to tabs + 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; + #Remove spaces before a tab + 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; + + return "$leading"; +} + +sub pos_last_openparen { + my ($line) = @_; + + my $pos = 0; + + my $opens = $line =~ tr/\(/\(/; + my $closes = $line =~ tr/\)/\)/; + + my $last_openparen = 0; + + if (($opens == 0) || ($closes >= $opens)) { + return -1; + } + + my $len = length($line); + + for ($pos = 0; $pos < $len; $pos++) { + my $string = substr($line, $pos); + if ($string =~ /^($FuncArg|$balanced_parens)/) { + $pos += length($1) - 1; + } elsif (substr($line, $pos, 1) eq '(') { + $last_openparen = $pos; + } elsif (index($string, '(') == -1) { + last; + } + } + + return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; +} + +sub get_raw_comment { + my ($line, $rawline) = @_; + my $comment = ''; + + for my $i (0 .. (length($line) - 1)) { + if (substr($line, $i, 1) eq "$;") { + $comment .= substr($rawline, $i, 1); + } + } + + return $comment; +} + +sub exclude_global_initialisers { + my ($realfile) = @_; + + # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). + return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || + $realfile =~ m@^samples/bpf/.*_kern\.c$@ || + $realfile =~ m@/bpf/.*\.bpf\.c$@; +} + +sub is_userspace { + my ($realfile) = @_; + return ($realfile =~ m@^tools/@ || $realfile =~ m@^scripts/@); +} + +sub process { + my $filename = shift; + + my $linenr=0; + my $prevline=""; + my $prevrawline=""; + my $stashline=""; + my $stashrawline=""; + + my $length; + my $indent; + my $previndent=0; + my $stashindent=0; + + 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 = ''; + my $is_patch = 0; + my $is_binding_patch = -1; + my $in_header_lines = $file ? 0 : 1; + my $in_commit_log = 0; #Scanning lines before patch + my $has_patch_separator = 0; #Found a --- line + my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_lines = 0; #Number of commit log lines + my $commit_log_possible_stack_dump = 0; + my $commit_log_long_line = 0; + my $commit_log_has_diff = 0; + my $reported_maintainer_file = 0; + my $non_utf8_charset = 0; + + my $last_git_commit_id_linenr = -1; + + my $last_blank_line = 0; + my $last_coalesced_string_linenr = -1; + + our @report = (); + our $cnt_lines = 0; + our $cnt_error = 0; + our $cnt_warn = 0; + our $cnt_chk = 0; + + # Trace the real file/line as we go. + my $realfile = ''; + my $realline = 0; + my $realcnt = 0; + my $here = ''; + my $context_function; #undef'd unless there's a known function + my $in_comment = 0; + my $comment_edge = 0; + my $first_line = 0; + my $p1_prefix = ''; + + my $prev_values = 'E'; + + # suppression flags + my %suppress_ifbraces; + my %suppress_whiletrailers; + my %suppress_export; + my $suppress_statement = 0; + + my %signatures = (); + + # Pre-scan the patch sanitizing the lines. + # Pre-scan the patch looking for any __setup documentation. + # + my @setup_docs = (); + my $setup_docs = 0; + + my $camelcase_file_seeded = 0; + + my $checklicenseline = 1; + + sanitise_line_reset(); + my $line; + foreach my $rawline (@rawlines) { + $linenr++; + $line = $rawline; + + push(@fixed, $rawline) if ($fix); + + if ($rawline=~/^\+\+\+\s+(\S+)/) { + $setup_docs = 0; + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { + $setup_docs = 1; + } + #next; + } + if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + $in_comment = 0; + + # Guestimate if this is a continuing comment. Run + # the context looking for a comment "edge". If this + # edge is a close comment then we must be in a comment + # at context start. + my $edge; + my $cnt = $realcnt; + for (my $ln = $linenr + 1; $cnt > 0; $ln++) { + next if (defined $rawlines[$ln - 1] && + $rawlines[$ln - 1] =~ /^-/); + $cnt--; + #print "RAW<$rawlines[$ln - 1]>\n"; + last if (!defined $rawlines[$ln - 1]); + if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && + $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { + ($edge) = $1; + last; + } + } + if (defined $edge && $edge eq '*/') { + $in_comment = 1; + } + + # Guestimate if this is a continuing comment. If this + # is the start of a diff block and this line starts + # ' *' then it is very likely a comment. + if (!defined $edge && + $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) + { + $in_comment = 1; + } + + ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; + sanitise_line_reset($in_comment); + + } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { + # Standardise the strings and chars within the input to + # simplify matching -- only bother with positive lines. + $line = sanitise_line($rawline); + } + push(@lines, $line); + + if ($realcnt > 1) { + $realcnt-- if ($line =~ /^(?:\+| |$)/); + } else { + $realcnt = 0; + } + + #print "==>$rawline\n"; + #print "-->$line\n"; + + if ($setup_docs && $line =~ /^\+/) { + push(@setup_docs, $line); + } + } + + $prefix = ''; + + $realcnt = 0; + $linenr = 0; + $fixlinenr = -1; + foreach my $line (@lines) { + $linenr++; + $fixlinenr++; + my $sline = $line; #copy of $line + $sline =~ s/$;/ /g; #with comments as spaces + + my $rawline = $rawlines[$linenr - 1]; + my $raw_comment = get_raw_comment($line, $rawline); + +# check if it's a mode change, rename or start of a patch + if (!$in_commit_log && + ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || + ($line =~ /^rename (?:from|to) \S+\s*$/ || + $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { + $is_patch = 1; + } + +#extract the line range in the file after the patch is applied + if (!$in_commit_log && + $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { + my $context = $4; + $is_patch = 1; + $first_line = $linenr + 1; + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + annotate_reset(); + $prev_values = 'E'; + + %suppress_ifbraces = (); + %suppress_whiletrailers = (); + %suppress_export = (); + $suppress_statement = 0; + if ($context =~ /\b(\w+)\s*\(/) { + $context_function = $1; + } else { + undef $context_function; + } + next; + +# track the line number as we move through the hunk, note that +# new versions of GNU diff omit the leading space on completely +# blank context lines so we need to count that too. + } elsif ($line =~ /^( |\+|$)/) { + $realline++; + $realcnt-- if ($realcnt != 0); + + # Measure the line length and indent. + ($length, $indent) = line_stats($rawline); + + # Track the previous line. + ($prevline, $stashline) = ($stashline, $line); + ($previndent, $stashindent) = ($stashindent, $indent); + ($prevrawline, $stashrawline) = ($stashrawline, $rawline); + + #warn "line<$line>\n"; + + } elsif ($realcnt == 1) { + $realcnt--; + } + + my $hunk_line = ($realcnt != 0); + + $here = "#$linenr: " if (!$file); + $here = "#$realline: " if ($file); + + my $found_file = 0; + # extract the filename as it passes + if ($line =~ /^diff --git.*?(\S+)$/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + $found_file = 1; + } elsif ($line =~ /^\+\+\+\s+(\S+)/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + + $p1_prefix = $1; + if (!$file && $tree && $p1_prefix ne '' && + -e "$root/$p1_prefix") { + WARN("PATCH_PREFIX", + "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); + } + + if ($realfile =~ m@^include/asm/@) { + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change architecture specific files in arch//include/asm\n" . "$here$rawline\n"); + } + $found_file = 1; + } + +#make up the handle for any error we report on this line + if ($showfile) { + $prefix = "$realfile:$realline: " + } elsif ($emacs) { + if ($file) { + $prefix = "$filename:$realline: "; + } else { + $prefix = "$filename:$linenr: "; + } + } + + if ($found_file) { + if (is_maintained_obsolete($realfile)) { + WARN("OBSOLETE", + "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); + } + if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { + $check = 1; + } else { + $check = $check_orig; + } + $checklicenseline = 1; + + if ($realfile !~ /^(MAINTAINERS|dev\/null)/) { + my $last_binding_patch = $is_binding_patch; + + $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; + + if (($last_binding_patch != -1) && + ($last_binding_patch ^ $is_binding_patch)) { + WARN("DT_SPLIT_BINDING_PATCH", + "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); + } + } + + next; + } + + $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); + + my $hereline = "$here\n$rawline\n"; + my $herecurr = "$here\n$rawline\n"; + my $hereprev = "$here\n$prevrawline\n$rawline\n"; + + $cnt_lines++ if ($realcnt != 0); + +# Verify the existence of a commit log if appropriate +# 2 is used because a $signature is counted in $commit_log_lines + if ($in_commit_log) { + if ($line !~ /^\s*$/) { + $commit_log_lines++; #could be a $signature + } + } elsif ($has_commit_log && $commit_log_lines < 2) { + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n"); + $commit_log_lines = 2; #warn only once + } + +# Check if the commit log has what seems like a diff which can confuse patch + if ($in_commit_log && !$commit_log_has_diff && + (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && + $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || + $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || + $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); + $commit_log_has_diff = 1; + } + +# Check for incorrect file permissions + if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { + my $permhere = $here . "FILE: $realfile\n"; + if ($realfile !~ m@scripts/@ && + $realfile !~ /\.(py|pl|awk|sh)$/) { + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" . $permhere); + } + } + +# Check the patch for a From: + if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { + $author = $1; + my $curline = $linenr; + while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { + $author .= $1; + } + $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); + $author =~ s/"//g; + $author = reformat_email($author); + } + +# Check the patch for a signoff: + if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { + $signoff++; + $in_commit_log = 0; + if ($author ne '' && $authorsignoff != 1) { + if (same_email_addresses($1, $author)) { + $authorsignoff = 1; + } else { + my $ctx = $1; + my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); + my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); + + if (lc $email_address eq lc $author_address && $email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 2; + } elsif (lc $email_address eq lc $author_address) { + $author_sob = $ctx; + $authorsignoff = 3; + } elsif ($email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 4; + + my $address1 = $email_address; + my $address2 = $author_address; + + if ($address1 =~ /(\S+)\+\S+(\@.*)/) { + $address1 = "$1$2"; + } + if ($address2 =~ /(\S+)\+\S+(\@.*)/) { + $address2 = "$1$2"; + } + if ($address1 eq $address2) { + $authorsignoff = 5; + } + } + } + } + } + +# Check for invalid patch separator + if ($in_commit_log && + $line =~ /^---.+/) { + if (ERROR("BAD_COMMIT_SEPARATOR", + "Invalid commit separator - some tools may have problems applying this\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/-/=/g; + } + } + +# Check for patch separator + if ($line =~ /^---$/) { + $has_patch_separator = 1; + $in_commit_log = 0; + } + +# Check if MAINTAINERS is being updated. If so, there's probably no need to +# emit the "does MAINTAINERS need updating?" message on file add/move/delete + if ($line =~ /^\s*MAINTAINERS\s*\|/) { + $reported_maintainer_file = 1; + } + +# Check signature styles + if (!$in_header_lines && + $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { + my $space_before = $1; + my $sign_off = $2; + my $space_after = $3; + my $email = $4; + my $ucfirst_sign_off = ucfirst(lc($sign_off)); + + if ($sign_off !~ /$signature_tags/) { + my $suggested_signature = find_standard_signature($sign_off); + if ($suggested_signature eq "") { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } else { + if (WARN("BAD_SIGN_OFF", + "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; + } + } + } + if (defined $space_before && $space_before ne "") { + if (WARN("BAD_SIGN_OFF", + "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { + if (WARN("BAD_SIGN_OFF", + "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + + } + if (!defined $space_after || $space_after ne " ") { + if (WARN("BAD_SIGN_OFF", + "Use a single space after $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + + # Assisted-by uses a free-form value (e.g. "LLM"), not an + # email address, so skip the email format checks below. + if ($sign_off =~ /^Assisted-by:/i) { + if ($email =~ /^\s*$/) { + WARN("BAD_SIGN_OFF", + "Assisted-by requires a value\n" . $herecurr); + } + next; + } + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); + if ($suggested_email eq "") { + ERROR("BAD_SIGN_OFF", + "Unrecognized email address: '$email'\n" . $herecurr); + } else { + my $dequoted = $suggested_email; + $dequoted =~ s/^"//; + $dequoted =~ s/" 1) { + WARN("BAD_SIGN_OFF", + "Use a single name comment in email: '$email'\n" . $herecurr); + } + + + # stable@vger.kernel.org or stable@kernel.org shouldn't + # have an email name. In addition comments should strictly + # begin with a # + if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { + if (($comment ne "" && $comment !~ /^#.+/) || + ($email_name ne "")) { + my $cur_name = $email_name; + my $new_comment = $comment; + $cur_name =~ s/[a-zA-Z\s\-\"]+//g; + + # Remove brackets enclosing comment text + # and # from start of comments to get comment text + $new_comment =~ s/^\((.*)\)$/$1/; + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^[\s\#]+|\s+$//g; + + $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); + $new_comment = " # $new_comment" if ($new_comment ne ""); + my $new_email = "$email_address$new_comment"; + + if (WARN("BAD_STABLE_ADDRESS_STYLE", + "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { + my $new_comment = $comment; + + # Extract comment text from within brackets or + # c89 style /*...*/ comments + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^\/\*(.*)\*\/$/$1/; + + $new_comment = trim($new_comment); + $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo + $new_comment = "($new_comment)" if ($new_comment ne ""); + my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); + + if (WARN("BAD_SIGN_OFF", + "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } + +# Check for duplicate signatures + my $sig_nospace = $line; + $sig_nospace =~ s/\s//g; + $sig_nospace = lc($sig_nospace); + if (defined $signatures{$sig_nospace}) { + WARN("BAD_SIGN_OFF", + "Duplicate signature\n" . $herecurr); + } else { + $signatures{$sig_nospace} = 1; + } + +# Check Co-developed-by: immediately followed by Signed-off-by: with same name and email + if ($sign_off =~ /^co-developed-by:$/i) { + if ($email eq $author) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . $herecurr); + } + if (!defined $lines[$linenr]) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr); + } elsif ($rawlines[$linenr] !~ /^signed-off-by:\s*(.*)/i) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr . $rawlines[$linenr] . "\n"); + } elsif ($1 ne $email) { + WARN("BAD_SIGN_OFF", + "Co-developed-by and Signed-off-by: name/email do not match\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + +# check if Reported-by: is followed by a Closes: tag + if ($sign_off =~ /^reported(?:|-and-tested)-by:$/i) { + if (!defined $lines[$linenr]) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: or Link: with a URL to the report\n" . $herecurr . "\n"); + } elsif ($rawlines[$linenr] !~ /^(closes|link):\s*/i) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: or Link: with a URL to the report\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + } + +# 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,40})(?:\s*($balanced_parens))?/i) { + my $tag = $1; + my $orig_commit = $2; + my $title; + my $title_has_quotes = 0; + $fixes_tag = 1; + if (defined $3) { + # Always strip leading/trailing parens then double quotes if existing + $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 (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> (\"\")' - ie: '$fixed'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = $fixed; + } + } + } + +# Check email subject for common tools that don't need to be mentioned + if ($in_header_lines && + $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { + WARN("EMAIL_SUBJECT", + "A patch subject line should describe the change not the tool that found it\n" . $herecurr); + } + +# Check for Gerrit Change-Ids not in any patch context + if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { + if (ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# Check if the commit log is in a possible stack dump + if ($in_commit_log && !$commit_log_possible_stack_dump && + ($line =~ /^\s*(?:WARNING:|BUG:)/ || + $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || + # timestamp + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || + $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || + $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { + # stack dump address styles + $commit_log_possible_stack_dump = 1; + } + +# Check for line lengths > 75 in commit log, warn once + if ($in_commit_log && !$commit_log_long_line && + length($line) > 75 && + !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || + # file delta changes + $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ || + # filename then : + $line =~ /^\s*(?:Fixes:|https?:|$link_tags_search|$signature_tags)/i || + # A Fixes:, link or signature tag line + $commit_log_possible_stack_dump)) { + WARN("COMMIT_LOG_LONG_LINE", + "Prefer a maximum 75 chars per line (possible unwrapped commit description?)\n" . $herecurr); + $commit_log_long_line = 1; + } + +# Reset possible stack dump if a blank line is found + if ($in_commit_log && $commit_log_possible_stack_dump && + $line =~ /^\s*$/) { + $commit_log_possible_stack_dump = 0; + } + +# Check for odd tags before a URI/URL + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*http/ && $1 !~ /^$link_tags_search$/) { + if ($1 =~ /^v(?:ersion)?\d+/i) { + WARN("COMMIT_LOG_VERSIONING", + "Patch version information should be after the --- line\n" . $herecurr); + } else { + WARN("COMMIT_LOG_USE_LINK", + "Unknown link reference '$1', use $link_tags_print instead\n" . $herecurr); + } + } + +# Check for misuse of the link tags + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*(\S+)/) { + my $tag = $1; + my $value = $2; + if ($tag =~ /^$link_tags_search$/ && $value !~ m{^https?://}) { + WARN("COMMIT_LOG_WRONG_LINK", + "'$tag' should be followed by a public http(s) link\n" . $herecurr); + } + } + +# Check for lines starting with a # + if ($in_commit_log && $line =~ /^#/) { + if (WARN("COMMIT_COMMENT_SYMBOL", + "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^/ /; + } + } + +# Check for auto-generated unhandled placeholder text (mostly for cover letters) + if (($in_commit_log || $in_header_lines) && + $rawline =~ /(?:SUBJECT|BLURB) HERE/) { + ERROR("PLACEHOLDER_USE", + "Placeholder text detected\n" . $herecurr); + } + +# Check for git id commit length and improperly formed commit descriptions +# A correctly formed commit description is: +# commit <SHA-1 hash length 12+ chars> ("Complete commit subject") +# with the commit subject '("' prefix and '")' suffix +# This is a fairly compilicated block as it tests for what appears to be +# bare SHA-1 hash with minimum length of 5. It also avoids several types of +# possible SHA-1 matches. +# A commit match can span multiple lines so this block attempts to find a +# complete typical commit on a maximum of 3 lines + if ($perl_version_ok && + $in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && + $line !~ /^This reverts commit [0-9a-f]{7,40}/ && + (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || + ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && + $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && + $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { + my $init_char = "c"; + my $orig_commit = ""; + my $short = 1; + my $long = 0; + my $case = 1; + my $space = 1; + my $id = '0123456789ab'; + my $orig_desc = "commit description"; + my $description = ""; + my $herectx = $herecurr; + my $has_parens = 0; + my $has_quotes = 0; + + my $input = $line; + if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { + for (my $n = 0; $n < 2; $n++) { + if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { + $orig_desc = $1; + $has_parens = 1; + # Always strip leading/trailing parens then double quotes if existing + $orig_desc = substr($orig_desc, 1, -1); + if ($orig_desc =~ /^".*"$/) { + $orig_desc = substr($orig_desc, 1, -1); + $has_quotes = 1; + } + last; + } + last if ($#lines < $linenr + $n); + $input .= " " . trim($rawlines[$linenr + $n]); + $herectx .= "$rawlines[$linenr + $n]\n"; + } + $herectx = $herecurr if (!$has_parens); + } + + if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + $init_char = $1; + $orig_commit = lc($2); + $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { + $orig_commit = lc($1); + } + + ($id, $description) = git_commit_info($orig_commit, + $id, $orig_desc); + + if (defined($id) && + ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && + $last_git_commit_id_linenr != $linenr - 1) { + ERROR("GIT_COMMIT_ID", + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); + } + #don't report the next line if this line ends in commit and the sha1 hash is the next line + $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); + } + +# Check for mailing list archives other than lore.kernel.org + if ($rawline =~ m{http.*\b$obsolete_archives}) { + WARN("PREFER_LORE_ARCHIVE", + "Use lore.kernel.org archive links when possible - see https://lore.kernel.org/lists.html\n" . $herecurr); + } + +# Check for added, moved or deleted files + if (!$reported_maintainer_file && !$in_commit_log && + ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || + $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || + ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && + (defined($1) || defined($2))))) { + $is_patch = 1; + $reported_maintainer_file = 1; + WARN("FILE_PATH_CHANGES", + "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); + } + +# Check for adding new DT bindings not in schema format + if (!$in_commit_log && + ($line =~ /^new file mode\s*\d+\s*$/) && + ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { + WARN("DT_SCHEMA_BINDING_PATCH", + "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); + } + +# Check for wrappage within a valid hunk of the file + if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { + ERROR("CORRUPTED_PATCH", + "patch seems to be corrupt (line wrapped?)\n" . + $herecurr) if (!$emitted_corrupt++); + } + +# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php + if (($realfile =~ /^$/ || $line =~ /^\+/) && + $rawline !~ m/^$UTF8*$/) { + my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); + + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; + my $hereptr = "$hereline$ptr\n"; + + CHK("INVALID_UTF8", + "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); + } + +# Check if it's the start of a commit log +# (not a header line and we haven't seen the patch filename) + if ($in_header_lines && $realfile =~ /^$/ && + !($rawline =~ /^\s+(?:\S|$)/ || + $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { + $in_header_lines = 0; + $in_commit_log = 1; + $has_commit_log = 1; + } + +# Check if there is UTF-8 in a commit log when a mail header has explicitly +# declined it, i.e defined some charset where it is missing. + if ($in_header_lines && + $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && + $1 !~ /utf-8/i) { + $non_utf8_charset = 1; + } + + if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && + $rawline =~ /$NON_ASCII_UTF8/) { + WARN("UTF8_BEFORE_PATCH", + "8-bit UTF-8 used in possible commit log\n" . $herecurr); + } + +# Check for absolute kernel paths in commit message + if ($tree && $in_commit_log) { + while ($line =~ m{(?:^|\s)(/\S*)}g) { + my $file = $1; + + if ($file =~ m{^(.*?)(?::\d+)+:?$} && + check_absolute_file($1, $herecurr)) { + # + } else { + check_absolute_file($file, $herecurr); + } + } + } + +# Check for various typo / spelling mistakes + if (defined($misspellings) && + ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { + my $rawline_utf8 = decode("utf8", $rawline); + while ($rawline_utf8 =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { + my $typo = $1; + my $blank = copy_spacing($rawline_utf8); + my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); + my $hereptr = "$hereline$ptr\n"; + my $typo_fix = $spelling_fix{lc($typo)}; + $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); + $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("TYPO_SPELLING", + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; + } + } + } + +# check for invalid commit id + if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { + my $id; + my $description; + ($id, $description) = git_commit_info($2, undef, undef); + if (!defined($id)) { + WARN("UNKNOWN_COMMIT_ID", + "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); + } + } + +# check for repeated words separated by a single space +# avoid false positive from list command eg, '-rw-r--r-- 1 root root' + if (($rawline =~ /^\+/ || $in_commit_log) && + $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { + pos($rawline) = 1 if (!$in_commit_log); + while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { + + my $first = $1; + my $second = $2; + my $start_pos = $-[1]; + my $end_pos = $+[2]; + if ($first =~ /(?:struct|union|enum)/) { + pos($rawline) += length($first) + length($second) + 1; + next; + } + + next if (lc($first) ne lc($second)); + next if ($first eq 'long'); + + # check for character before and after the word matches + my $start_char = ''; + my $end_char = ''; + $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); + $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); + + next if ($start_char =~ /^\S$/); + next if (index(" \t.,;?!", $end_char) == -1); + + # avoid repeating hex occurrences like 'ff ff fe 09 ...' + if ($first =~ /\b[0-9a-f]{2,}\b/i) { + next if (!exists($allow_repeated_words{lc($first)})); + } + + if (WARN("REPEATED_WORD", + "Possible repeated word: '$first'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; + } + } + + # if it's a repeated word on consecutive lines in a comment block + if ($prevline =~ /$;+\s*$/ && + $prevrawline =~ /($word_pattern)\s*$/) { + my $last_word = $1; + if ($rawline =~ /^\+\s*\*\s*$last_word /) { + if (WARN("REPEATED_WORD", + "Possible repeated word: '$last_word'\n" . $hereprev) && + $fix) { + $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; + } + } + } + } + +# ignore non-hunk lines and lines being removed + next if (!$hunk_line || $line =~ /^-/); + +#trailing whitespace + if ($line =~ /^\+.*\015/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("DOS_LINE_ENDINGS", + "DOS line endings\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/[\s\015]+$//; + } + } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("TRAILING_WHITESPACE", + "trailing whitespace\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + + $rpt_cleaners = 1; + } + +# Check for FSF mailing addresses. + if ($rawline =~ /\bwrite to the Free/i || + $rawline =~ /\b675\s+Mass\s+Ave/i || + $rawline =~ /\b59\s+Temple\s+Pl/i || + $rawline =~ /\b51\s+Franklin\s+St/i) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + my $msg_level = \&ERROR; + $msg_level = \&CHK if ($file); + &{$msg_level}("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) + } + +# check for Kconfig help text having a real description +# Only applies when adding the entry originally, after that we do not have +# sufficient context to determine whether it is indeed long enough. + if ($realfile =~ /Kconfig/ && + # 'choice' is usually the last thing on the line (though + # Kconfig supports named choices), so use a word boundary + # (\b) rather than a whitespace character (\s) + $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { + my $ln = $linenr; + my $needs_help = 0; + my $has_help = 0; + my $help_length = 0; + while (defined $lines[$ln]) { + my $f = $lines[$ln++]; + + next if ($f =~ /^-/); + last if ($f !~ /^[\+ ]/); # !patch context + + if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { + $needs_help = 1; + next; + } + if ($f =~ /^\+\s*help\s*$/) { + $has_help = 1; + next; + } + + $f =~ s/^.//; # strip patch context [+ ] + $f =~ s/#.*//; # strip # directives + $f =~ s/^\s+//; # strip leading blanks + next if ($f =~ /^$/); # skip blank lines + + # At the end of this Kconfig block: + # This only checks context lines in the patch + # and so hopefully shouldn't trigger false + # positives, even though some of these are + # common words in help texts + if ($f =~ /^(?:config|menuconfig|choice|endchoice| + if|endif|menu|endmenu|source)\b/x) { + last; + } + $help_length++ if ($has_help); + } + if ($needs_help && + $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 with at least $min_conf_desc_length lines\n" . "$here\n$stat_real\n"); + } + } + +# check MAINTAINERS entries + if ($realfile =~ /^MAINTAINERS$/) { +# check MAINTAINERS entries for the right form + if ($rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } +# check MAINTAINERS entries for the right ordering too + my $preferred_order = 'MRLSWQBCPTFXNK'; + if ($rawline =~ /^\+[A-Z]:/ && + $prevrawline =~ /^[\+ ][A-Z]:/) { + $rawline =~ /^\+([A-Z]):\s*(.*)/; + my $cur = $1; + my $curval = $2; + $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; + my $prev = $1; + my $prevval = $2; + my $curindex = index($preferred_order, $cur); + my $previndex = index($preferred_order, $prev); + if ($curindex < 0) { + WARN("MAINTAINERS_STYLE", + "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); + } else { + if ($previndex >= 0 && $curindex < $previndex) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); + } elsif ((($prev eq 'F' && $cur eq 'F') || + ($prev eq 'X' && $cur eq 'X')) && + ($prevval cmp $curval) > 0) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); + } + } + } + } + +# check for DT compatible documentation + if (defined $root && + (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || + ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { + + my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; + + my $dt_path = $root . "/Documentation/devicetree/bindings/"; + my $vp_file = $dt_path . "vendor-prefixes.yaml"; + + foreach my $compat (@compats) { + # Skip ID-based PCI and USB compatible patterns. + # DT validation will check them properly. + next if $compat =~ /^pciclass,/; + next if $compat =~ /^pci[a-f0-9]{2,4},/; + next if $compat =~ /^usb(if)?[a-f0-9]{1,4},/; + + my $compat2 = $compat; + $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; + my $compat3 = $compat; + $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; + `grep -Erq "$compat|$compat2|$compat3" $dt_path`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); + } + + next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; + my $vendor = $1; + `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); + } + } + } + +# Check for RGMII phy-mode with delay on PCB + if ($realfile =~ /\.(dts|dtsi|dtso)$/ && + $line =~ /^\+\s*(phy-mode|phy-connection-type)\s*=\s*"/ && + !ctx_has_comment($first_line, $linenr)) { + my $prop = $1; + my $mode = get_quoted_string($line, $rawline); + if ($mode =~ /^"rgmii(?:|-rxid|-txid)"$/) { + WARN("UNCOMMENTED_RGMII_MODE", + "$prop $mode without comment -- delays on the PCB should be described, otherwise use \"rgmii-id\"\n" . $herecurr); + } + } + +# check for using SPDX license tag at beginning of files + if ($realline == $checklicenseline) { + if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { + $checklicenseline = 2; + } elsif ($rawline =~ /^\+/) { + my $comment = ""; + if ($realfile =~ /\.(c|rs|dts|dtsi)$/) { + $comment = '//'; + } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { + $comment = '#'; + } elsif ($realfile =~ /\.rst$/) { + $comment = '..'; + } + my $pattern = qr{\Q$comment\E}; + if ($realfile =~ /\.(h|s|S)$/) { + $comment = '/*'; + $pattern = qr{/\*}; + if ($spdx_cxx_comments) { + $comment = '// or /*'; + $pattern = qr{//|/\*}; + } + } + +# check SPDX comment style for .[chsS] files + if ($realfile =~ /\.[chsS]$/ && + $rawline =~ /SPDX-License-Identifier:/ && + $rawline !~ m@^\+\s*$pattern\s*@) { + WARN("SPDX_LICENSE_TAG", + "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); + } + + if ($comment !~ /^$/ && + $rawline !~ m@^\+$pattern SPDX-License-Identifier: @) { + WARN("SPDX_LICENSE_TAG", + "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); + } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { + my $spdx_license = $1; + if (!is_SPDX_License_valid($spdx_license)) { + WARN("SPDX_LICENSE_TAG", + "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); + } + if ($realfile =~ m@^Documentation/devicetree/bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR BSD-2-Clause/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("SPDX_LICENSE_TAG", + + "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; + } + } + if ($realfile =~ m@^include/dt-bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR \S+/) { + WARN("SPDX_LICENSE_TAG", + "DT binding headers should be licensed (GPL-2.0-only OR .*)\n" . $herecurr); + } + } + } + } + +# check for embedded filenames + if ($rawline =~ /^\+.*\b\Q$realfile\E\b/) { + WARN("EMBEDDED_FILENAME", + "It's generally not useful to have the filename in the file\n" . $herecurr); + } + +# check we are in a valid source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts)$/); + +# check for using SPDX-License-Identifier on the wrong line number + if ($realline != $checklicenseline && + $rawline =~ /\bSPDX-License-Identifier:/ && + substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { + WARN("SPDX_LICENSE_TAG", + "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); + } + +# check for disallowed SPDX file tags + if ($rawline =~ /\bSPDX-.*:/ && + $rawline !~ /\bSPDX-License-Identifier:/ && + $rawline !~ /\bSPDX-FileCopyrightText:/) { + WARN("SPDX_LICENSE_TAG", + "Disallowed SPDX tag\n" . $herecurr); + } + +# line length limit (with some exclusions) +# +# There are a few types of lines that may extend beyond $max_line_length: +# logging functions like pr_info that end in a string +# lines with a single string +# #defines that are a single string +# lines with an RFC3986 like URL +# +# There are 3 different line length message types: +# LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length +# LONG_LINE_STRING a string starts before but extends beyond $max_line_length +# LONG_LINE all other lines longer than $max_line_length +# +# if LONG_LINE is ignored, the other 2 types are also ignored +# + + if ($line =~ /^\+/ && $length > $max_line_length) { + my $msg_type = "LONG_LINE"; + + # Check the allowed long line types first + + # logging functions that end in a string that starts + # before $max_line_length + if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = ""; + + # lines with only strings (w/ possible termination) + # #defines with only strings + } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || + $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { + $msg_type = ""; + + # More special cases + } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || + $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { + $msg_type = ""; + + # URL ($rawline is used in case the URL is in a comment) + } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { + $msg_type = ""; + + # Otherwise set the alternate message types + + # a comment starts before $max_line_length + } elsif ($line =~ /($;[\s$;]*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_COMMENT" + + # a quoted string starts before $max_line_length + } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_STRING" + } + + if ($msg_type ne "" && + show_type("LONG_LINE") && show_type($msg_type)) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}($msg_type, + "line length of $length exceeds $max_line_length columns\n" . $herecurr); + } + } + +# check for adding lines without a newline. + if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { + if (WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr+1, "No newline at end of file"); + } + } + +# check for .L prefix local symbols in .S files + if ($realfile =~ /\.S$/ && + $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { + WARN("AVOID_L_PREFIX", + "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/core-api/asm-annotations.rst\n" . $herecurr); + } + +# check we are in a valid source file C or perl if not then ignore this hunk + next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); + +# at the beginning of a line any tabs must come first and anything +# more than $tabsize must use tabs. + if ($rawline =~ /^\+\s* \t\s*\S/ || + $rawline =~ /^\+\s* \s*/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + $rpt_cleaners = 1; + if (ERROR("CODE_INDENT", + "code indent should use tabs where possible\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check for space before tabs. + if ($rawline =~ /^\+/ && $rawline =~ / \t/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("SPACE_BEFORE_TAB", + "please, no space before tabs\n" . $herevet) && + $fix) { + while ($fixed[$fixlinenr] =~ + s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} + while ($fixed[$fixlinenr] =~ + s/(^\+.*) +\t/$1\t/) {} + } + } + +# check for assignments on the start of a line + if ($sline =~ /^\+\s+($Assignment)[^=]/) { + my $operator = $1; + if (CHK("ASSIGNMENT_CONTINUATIONS", + "Assignment operator '$1' should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # add assignment operator to the previous line, remove from current line + $fixed[$fixlinenr - 1] .= " $operator"; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check for && or || at the start of a line + if ($rawline =~ /^\+\s*(&&|\|\|)/) { + my $operator = $1; + if (CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # insert logical operator at last non-comment, non-whitepsace char on previous line + $prevline =~ /[\s$;]*$/; + my $line_end = substr($prevrawline, $-[0]); + $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check indentation starts on a tab stop + if ($perl_version_ok && + $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { + my $indent = length($1); + if ($indent % $tabsize) { + if (WARN("TABSTOP", + "Statements should start on a tabstop\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; + } + } + } + +# check multi-line statement indentation matches previous line + if ($perl_version_ok && + $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { + $prevline =~ /^\+(\t*)(.*)$/; + my $oldindent = $1; + my $rest = $2; + + my $pos = pos_last_openparen($rest); + if ($pos >= 0) { + $line =~ /^(\+| )([ \t]*)/; + my $newindent = $2; + + my $goodtabindent = $oldindent . + "\t" x ($pos / $tabsize) . + " " x ($pos % $tabsize); + my $goodspaceindent = $oldindent . " " x $pos; + + if ($newindent ne $goodtabindent && + $newindent ne $goodspaceindent) { + + if (CHK("PARENTHESIS_ALIGNMENT", + "Alignment should match open parenthesis\n" . $hereprev) && + $fix && $line =~ /^\+/) { + $fixed[$fixlinenr] =~ + s/^\+[ \t]*/\+$goodtabindent/; + } + } + } + } + +# check for space after cast like "(int) foo" or "(struct foo) bar" +# avoid checking a few false positives: +# "sizeof(<type>)" or "__alignof__(<type>)" +# function pointer declarations like "(*foo)(int) = bar;" +# structure definitions like "(struct foo) { 0 };" +# multiline macros that define functions +# known attributes or the __attribute__ keyword + if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && + (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { + if (CHK("SPACING", + "No space is necessary after a cast\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/(\(\s*$Type\s*\))[ \t]+/$1/; + } + } + +# Block comments use * on subsequent lines + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $prevrawline =~ /^\+.*?\/\*/ && #starting /* + $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ + $rawline =~ /^\+/ && #line is new + $rawline !~ /^\+[ \t]*\*/) { #no leading * + WARN("BLOCK_COMMENT_STYLE", + "Block comments use * on subsequent lines\n" . $hereprev); + } + +# Block comments use */ on trailing lines + if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ + $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ + $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ + $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ + WARN("BLOCK_COMMENT_STYLE", + "Block comments use a trailing */ on a separate line\n" . $herecurr); + } + +# Block comment * alignment + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $line =~ /^\+[ \t]*$;/ && #leading comment + $rawline =~ /^\+[ \t]*\*/ && #leading * + (($prevrawline =~ /^\+.*?\/\*/ && #leading /* + $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ + $prevrawline =~ /^\+[ \t]*\*/)) { #leading * + my $oldindent; + $prevrawline =~ m@^\+([ \t]*/?)\*@; + if (defined($1)) { + $oldindent = expand_tabs($1); + } else { + $prevrawline =~ m@^\+(.*/?)\*@; + $oldindent = expand_tabs($1); + } + $rawline =~ m@^\+([ \t]*)\*@; + my $newindent = $1; + $newindent = expand_tabs($newindent); + if (length($oldindent) ne length($newindent)) { + WARN("BLOCK_COMMENT_STYLE", + "Block comments should align the * on each line\n" . $hereprev); + } + } + +# check for missing blank lines after struct/union declarations +# with exceptions for various attributes and macros + if ($prevline =~ /^[\+ ]};?\s*$/ && + $line =~ /^\+/ && + !($line =~ /^\+\s*$/ || + $line =~ /^\+\s*(?:EXPORT_SYMBOL|early_param|ALLOW_ERROR_INJECTION)/ || + $line =~ /^\+\s*MODULE_/i || + $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || + $line =~ /^\+[a-z_]*init/ || + $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || + $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*NOKPROBE_SYMBOL/ || + $line =~ /^\+\s*builtin_[\w_]*driver/ || + $line =~ /^\+\s*__setup/)) { + if (CHK("LINE_SPACING", + "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for multiple consecutive blank lines + if ($prevline =~ /^[\+ ]\s*$/ && + $line =~ /^\+\s*$/ && + $last_blank_line != ($linenr - 1)) { + if (CHK("LINE_SPACING", + "Please don't use multiple blank lines\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + + $last_blank_line = $linenr; + } + +# check for missing blank lines after declarations +# (declarations must have the same indentation and not be at the start of line) + if (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/) { + # use temporaries + my $sl = $sline; + my $pl = $prevline; + # remove $Attribute/$Sparse uses to simplify comparisons + $sl =~ s/\b(?:$Attribute|$Sparse)\b//g; + $pl =~ s/\b(?:$Attribute|$Sparse)\b//g; + if (($pl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $pl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident(?:\s*\[\s*(?:$Ident|$Constant)?\s*\])?\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $pl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $pl =~ /^\+\s+$declaration_macros/) && + # for "else if" which can look like "$Ident $Ident" + !($pl =~ /^\+\s+$c90_Keywords\b/ || + # other possible extensions of declaration lines + $pl =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + # not starting a section or a macro "\" extended line + $pl =~ /(?:\{\s*|\\)$/) && + # looks like a declaration + !($sl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $sl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident(?:\s*\[\s*(?:$Ident|$Constant)?\s*\])?\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $sl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $sl =~ /^\+\s+$declaration_macros/ || + # start of struct or union or enum + $sl =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || + # start or end of block or continuation of declaration + $sl =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + # bitfield continuation + $sl =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + # other possible extensions of declaration lines + $sl =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + } + +# check for spaces at the beginning of a line. +# Exceptions: +# 1) within comments +# 2) indented preprocessor commands +# 3) hanging labels + if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("LEADING_SPACE", + "please, no spaces at the start of a line\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check we are in a valid C source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c)$/); + +# check for unusual line ending [ or ( + if ($line =~ /^\+.*([\[\(])\s*$/) { + CHK("OPEN_ENDED_LINE", + "Lines should not end with a '$1'\n" . $herecurr); + } + +# check if this appears to be the start function declaration, save the name + if ($sline =~ /^\+\{\s*$/ && + $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { + $context_function = $1; + } + +# check if this appears to be the end of function declaration + if ($sline =~ /^\+\}\s*$/) { + undef $context_function; + } + +# check indentation of any line with a bare else +# (but not if it is a multiple line "if (foo) return bar; else return baz;") +# if the previous line is a break or return and is indented 1 tab more... + if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { + my $tabs = length($1) + 1; + if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || + ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && + defined $lines[$linenr] && + $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { + WARN("UNNECESSARY_ELSE", + "else is not generally useful after a break or return\n" . $hereprev); + } + } + +# check indentation of a line with a break; +# if the previous line is a goto, return or break +# and is indented the same # of tabs + if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { + my $tabs = $1; + if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { + if (WARN("UNNECESSARY_BREAK", + "break is not useful after a $1\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + } + +# check for RCS/CVS revision markers + if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { + WARN("CVS_KEYWORD", + "CVS style keyword markers, these will _not_ be updated\n". $herecurr); + } + +# check for old HOTPLUG __dev<foo> section markings + if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { + WARN("HOTPLUG_SECTION", + "Using $1 is unnecessary\n" . $herecurr); + } + +# Check for potential 'bare' types + my ($stat, $cond, $line_nr_next, $remain_next, $off_next, + $realline_next); +#print "LINE<$line>\n"; + if ($linenr > $suppress_statement && + $realcnt && $sline =~ /.\s*\S/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0); + $stat =~ s/\n./\n /g; + $cond =~ s/\n./\n /g; + +#print "linenr<$linenr> <$stat>\n"; + # If this statement has no statement boundaries within + # it there is no point in retrying a statement scan + # until we hit end of it. + my $frag = $stat; $frag =~ s/;+\s*$//; + if ($frag !~ /(?:{|;)/) { +#print "skip<$line_nr_next>\n"; + $suppress_statement = $line_nr_next; + } + + # Find the real next line. + $realline_next = $line_nr_next; + if (defined $realline_next && + (!defined $lines[$realline_next - 1] || + substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { + $realline_next++; + } + + my $s = $stat; + $s =~ s/{.*$//s; + + # Ignore goto labels. + if ($s =~ /$Ident:\*$/s) { + + # Ignore functions being called + } elsif ($s =~ /^.\s*$Ident\s*\(/s) { + + } elsif ($s =~ /^.\s*else\b/s) { + + # declarations always start with types + } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { + my $type = $1; + $type =~ s/\s+/ /g; + possible($type, "A:" . $s); + + # definitions in global scope can only start with types + } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { + possible($1, "B:" . $s); + } + + # any (foo ... *) is a pointer cast, and foo is a type + while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { + possible($1, "C:" . $s); + } + + # Check for any sort of function declaration. + # int foo(something bar, other baz); + # void (*store_gdt)(x86_descr_ptr *); + if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { + my ($name_len) = length($1); + + my $ctx = $s; + substr($ctx, 0, $name_len + 1, ''); + $ctx =~ s/\)[^\)]*$//; + + for my $arg (split(/\s*,\s*/, $ctx)) { + if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { + + possible($1, "D:" . $s); + } + } + } + + } + +# +# Checks which may be anchored in the context. +# + +# Check for switch () and associated case and default +# statements should be at the same indent. + if ($line=~/\bswitch\s*\(.*\)/) { + my $err = ''; + my $sep = ''; + my @ctx = ctx_block_outer($linenr, $realcnt); + shift(@ctx); + for my $ctx (@ctx) { + my ($clen, $cindent) = line_stats($ctx); + if ($ctx =~ /^\+\s*(case\s+|default:)/ && + $indent != $cindent) { + $err .= "$sep$ctx\n"; + $sep = ''; + } else { + $sep = "[...]\n"; + } + } + if ($err ne '') { + ERROR("SWITCH_CASE_INDENT_LEVEL", + "switch and case should be at the same indent\n$hereline$err"); + } + } + +# if/while/etc brace do not go on next line, unless defining a do while loop, +# or if that brace on the next line is for something else + if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { + my $pre_ctx = "$1$2"; + + my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); + + if ($line =~ /^\+\t{6,}/) { + WARN("DEEP_INDENTATION", + "Too many leading tabs - consider code refactoring\n" . $herecurr); + } + + my $ctx_cnt = $realcnt - $#ctx - 1; + my $ctx = join("\n", @ctx); + + my $ctx_ln = $linenr; + my $ctx_skip = $realcnt; + + while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && + defined $lines[$ctx_ln - 1] && + $lines[$ctx_ln - 1] =~ /^-/)) { + ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; + $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); + $ctx_ln++; + } + + #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; + #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; + + if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { + ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && + $ctx =~ /\)\s*\;\s*$/ && + defined $lines[$ctx_ln - 1]) + { + my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); + if ($nindent > $indent) { + WARN("TRAILING_SEMICOLON", + "trailing semicolon indicates no statements, indent implies otherwise\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + } + } + +# Check relative indent for conditionals and blocks. + if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($s, $c) = ($stat, $cond); + + substr($s, 0, length($c), ''); + + # remove inline comments + $s =~ s/$;/ /g; + $c =~ s/$;/ /g; + + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + + # Make sure we remove the line prefixes as we have + # none on the first line, and are going to readd them + # where necessary. + $s =~ s/\n./\n/gs; + while ($s =~ /\n\s+\\\n/) { + $cond_lines += $s =~ s/\n\s+\\\n/\n/g; + } + + # We want to check the first line inside the block + # starting at the end of the conditional, so remove: + # 1) any blank line termination + # 2) any opening brace { on end of the line + # 3) any do (...) { + my $continuation = 0; + my $check = 0; + $s =~ s/^.*\bdo\b//; + $s =~ s/^\s*{//; + if ($s =~ s/^\s*\\//) { + $continuation = 1; + } + if ($s =~ s/^\s*?\n//) { + $check = 1; + $cond_lines++; + } + + # Also ignore a loop construct at the end of a + # preprocessor statement. + if (($prevline =~ /^.\s*#\s*define\s/ || + $prevline =~ /\\\s*$/) && $continuation == 0) { + $check = 0; + } + + my $cond_ptr = -1; + $continuation = 0; + while ($cond_ptr != $cond_lines) { + $cond_ptr = $cond_lines; + + # If we see an #else/#elif then the code + # is not linear. + if ($s =~ /^\s*\#\s*(?:else|elif)/) { + $check = 0; + } + + # Ignore: + # 1) blank lines, they should be at 0, + # 2) preprocessor lines, and + # 3) labels. + if ($continuation || + $s =~ /^\s*?\n/ || + $s =~ /^\s*#\s*?/ || + $s =~ /^\s*$Ident\s*:/) { + $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; + if ($s =~ s/^.*?\n//) { + $cond_lines++; + } + } + } + + my (undef, $sindent) = line_stats("+" . $s); + my $stat_real = raw_line($linenr, $cond_lines); + + # Check if either of these lines are modified, else + # this is not this patch's fault. + if (!defined($stat_real) || + $stat !~ /^\+/ && $stat_real !~ /^\+/) { + $check = 0; + } + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; + + if ($check && $s ne '' && + (($sindent % $tabsize) != 0 || + ($sindent < $indent) || + ($sindent == $indent && + ($s !~ /^\s*(?:\}|\{|else\b)/)) || + ($sindent > $indent + $tabsize))) { + WARN("SUSPECT_CODE_INDENT", + "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); + } + } + + # Track the 'values' across context and added lines. + my $opline = $line; $opline =~ s/^./ /; + my ($curr_values, $curr_vars) = + annotate_values($opline . "\n", $prev_values); + $curr_values = $prev_values . $curr_values; + if ($dbg_values) { + my $outline = $opline; $outline =~ s/\t/ /g; + print "$linenr > .$outline\n"; + print "$linenr > $curr_values\n"; + print "$linenr > $curr_vars\n"; + } + $prev_values = substr($curr_values, -1); + +#ignore lines not being added + next if ($line =~ /^[^\+]/); + +# check for self assignments used to avoid compiler warnings +# e.g.: int foo = foo, *bar = NULL; +# struct foo bar = *(&(bar)); + if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { + my $var = $1; + if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { + WARN("SELF_ASSIGNMENT", + "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); + } + } + +# check for dereferences that span multiple lines + if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && + $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { + $prevline =~ /($Lval\s*(?:\.|->))\s*$/; + my $ref = $1; + $line =~ /^.\s*($Lval)/; + $ref .= $1; + $ref =~ s/\s//g; + WARN("MULTILINE_DEREFERENCE", + "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); + } + +# check for declarations of signed or unsigned without int + while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { + my $type = $1; + my $var = $2; + $var = "" if (!defined $var); + if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { + my $sign = $1; + my $pointer = $2; + + $pointer = "" if (!defined $pointer); + + if (WARN("UNSPECIFIED_INT", + "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && + $fix) { + my $decl = trim($sign) . " int "; + my $comp_pointer = $pointer; + $comp_pointer =~ s/\s//g; + $decl .= $comp_pointer; + $decl = rtrim($decl) if ($var eq ""); + $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; + } + } + } + +# TEST: allow direct testing of the type matcher. + if ($dbg_type) { + if ($line =~ /^.\s*$Declare\s*$/) { + ERROR("TEST_TYPE", + "TEST: is type\n" . $herecurr); + } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { + ERROR("TEST_NOT_TYPE", + "TEST: is not type ($1 is)\n". $herecurr); + } + next; + } +# TEST: allow direct testing of the attribute matcher. + if ($dbg_attr) { + if ($line =~ /^.\s*$Modifier\s*$/) { + ERROR("TEST_ATTR", + "TEST: is attr\n" . $herecurr); + } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { + ERROR("TEST_NOT_ATTR", + "TEST: is not attr ($1 is)\n". $herecurr); + } + next; + } + +# check for initialisation to aggregates open brace on the next line + if ($line =~ /^.\s*{/ && + $prevline =~ /(?:^|[^=])=\s*$/) { + if (ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/\s*=\s*$/ = {/; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $line; + $fixedline =~ s/^(.\s*)\{\s*/$1/; + fix_insert_line($fixlinenr, $fixedline); + } + } + +# +# Checks which are anchored on the added line. +# + +# check for malformed paths in #include statements (uses RAW line) + if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { + my $path = $1; + if ($path =~ m{//}) { + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" . $herecurr); + } + if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { + ERROR("UAPI_INCLUDE", + "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); + } + } + +# no C99 // comments + if ($line =~ m{//}) { + if (ERROR("C99_COMMENTS", + "do not use C99 // comments\n" . $herecurr) && + $fix) { + my $line = $fixed[$fixlinenr]; + if ($line =~ /\/\/(.*)$/) { + my $comment = trim($1); + $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; + } + } + } + # Remove C99 comments. + $line =~ s@//.*@@; + $opline =~ s@//.*@@; + +# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider +# the whole statement. +#print "APW <$lines[$realline_next - 1]>\n"; + if (defined $realline_next && + exists $lines[$realline_next - 1] && + !defined $suppress_export{$realline_next} && + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { + # Handle definitions which produce identifiers with + # a prefix: + # XXX(foo); + # EXPORT_SYMBOL(something_foo); + my $name = $1; + $name =~ s/^\s*($Ident).*/$1/; + if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && + $name =~ /^${Ident}_$2/) { +#print "FOO C name<$name>\n"; + $suppress_export{$realline_next} = 1; + + } elsif ($stat !~ /(?: + \n.}\s*$| + ^.DEFINE_$Ident\(\Q$name\E\)| + ^.DECLARE_$Ident\(\Q$name\E\)| + ^.LIST_HEAD\(\Q$name\E\)| + ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| + \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() + )/x) { +#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; + $suppress_export{$realline_next} = 2; + } else { + $suppress_export{$realline_next} = 1; + } + } + if (!defined $suppress_export{$linenr} && + $prevline =~ /^.\s*$/ && + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { +#print "FOO B <$lines[$linenr - 1]>\n"; + $suppress_export{$linenr} = 2; + } + if (defined $suppress_export{$linenr} && + $suppress_export{$linenr} == 2) { + WARN("EXPORT_SYMBOL", + "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); + } + +# check for global initialisers. + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && + !exclude_global_initialisers($realfile)) { + if (ERROR("GLOBAL_INITIALISERS", + "do not initialise globals to $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; + } + } +# check for static initialisers. + if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { + if (ERROR("INITIALISED_STATIC", + "do not initialise statics to $1\n" . + $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; + } + } + +# check for misordered declarations of char/short/int/long with signed/unsigned + while ($sline =~ m{(\b$TypeMisordered\b)}g) { + my $tmp = trim($1); + WARN("MISORDERED_TYPE", + "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); + } + +# check for unnecessary <signed> int declarations of short/long/long long + while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { + my $type = trim($1); + next if ($type !~ /\bint\b/); + next if ($type !~ /\b(?:short|long\s+long|long)\b/); + my $new_type = $type; + $new_type =~ s/\b\s*int\s*\b/ /; + $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; + $new_type =~ s/^const\s+//; + $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); + $new_type = "const $new_type" if ($type =~ /^const\b/); + $new_type =~ s/\s+/ /g; + $new_type = trim($new_type); + if (WARN("UNNECESSARY_INT", + "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; + } + } + +# check for static const char * arrays. + if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static const char * array should probably be static const char * const\n" . + $herecurr); + } + +# check for initialized const char arrays that should be static const + if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { + if (WARN("STATIC_CONST_CHAR_ARRAY", + "const array should probably be static const\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; + } + } + +# check for static char foo[] = "bar" declarations. + if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static char array declaration should probably be static const char\n" . + $herecurr); + } + +# check for const <foo> const where <foo> is not a pointer or array type + if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { + my $found = $1; + if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { + WARN("CONST_CONST", + "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); + } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { + WARN("CONST_CONST", + "'const $found const' should probably be 'const $found'\n" . $herecurr); + } + } + +# check for const static or static <non ptr type> const declarations +# prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' + if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || + $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { + if (WARN("STATIC_CONST", + "Move const after static - use 'static const $1'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; + $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; + } + } + +# check for non-global char *foo[] = {"bar", ...} declarations. + if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "char * array declaration might be better as static const\n" . + $herecurr); + } + +# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) + if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { + my $array = $1; + if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { + my $array_div = $1; + if (WARN("ARRAY_SIZE", + "Prefer ARRAY_SIZE($array)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; + } + } + } + +# check for function declarations without arguments like "int foo()" + if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { + if (ERROR("FUNCTION_WITHOUT_ARGS", + "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; + } + } + +# check for new typedefs, only function parameters and sparse annotations +# make sense. + if ($line =~ /\btypedef\s/ && + $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && + $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && + $line !~ /\b$typeTypedefs\b/ && + $line !~ /\b__bitwise\b/) { + WARN("NEW_TYPEDEFS", + "do not add new typedefs\n" . $herecurr); + } + +# * goes on variable not on type + # (char*[ const]) + while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { + #print "AA<$1>\n"; + my ($ident, $from, $to) = ($1, $2, $2); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + +## print "1: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to) { + if (ERROR("POINTER_LOCATION", + "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && + $fix) { + my $sub_from = $ident; + my $sub_to = $ident; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { + #print "BB<$1>\n"; + my ($match, $from, $to, $ident) = ($1, $2, $2, $3); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + # Modifiers should have spaces. + $to =~ s/(\b$Modifier$)/$1 /; + +## print "2: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to && $ident !~ /^$Modifier$/) { + if (ERROR("POINTER_LOCATION", + "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && + $fix) { + + my $sub_from = $match; + my $sub_to = $match; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + +# do not use BUG() or variants + 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", + "Do not crash the kernel unless it is absolutely unavoidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BUG() or variants\n" . $herecurr); + } + +# avoid LINUX_VERSION_CODE + if ($line =~ /\bLINUX_VERSION_CODE\b/) { + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); + } + +# check for uses of printk_ratelimit + if ($line =~ /\bprintk_ratelimit\s*\(/) { + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); + } + +# printk should use KERN_* levels + if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); + } + +# prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> + if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { + my $printk = $1; + my $modifier = $2; + my $orig = $3; + $modifier = "" if (!defined($modifier)); + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + my $level2 = $level; + $level2 = "dbg" if ($level eq "debug"); + $level .= $modifier; + $level2 .= $modifier; + WARN("PREFER_PR_LEVEL", + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); + } + +# prefer dev_<level> to dev_printk(KERN_<LEVEL> + if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + $level = "dbg" if ($level eq "debug"); + WARN("PREFER_DEV_LEVEL", + "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); + } + +# trace_printk should not be used in production code. + if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { + WARN("TRACE_PRINTK", + "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); + } + +# ENOSYS means "bad syscall nr" and nothing else. This will have a small +# number of false positives, but assembly files are not checked, so at +# least the arch entry code will not trigger this warning. + if ($line =~ /\bENOSYS\b/) { + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); + } + +# ENOTSUPP is not a standard error code and should be avoided in new patches. +# Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. +# Similarly to ENOSYS warning a small number of false positives is expected. + if (!$file && $line =~ /\bENOTSUPP\b/) { + if (WARN("ENOTSUPP", + "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; + } + } + +# function brace can't be on same line, except for #defines of do while, +# or if closed on same line + if ($perl_version_ok && + $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && + $sline !~ /\#\s*define\b.*do\s*\{/ && + $sline !~ /}/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + my $fixed_line = $rawline; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; + my $line1 = $1; + my $line2 = $2; + fix_insert_line($fixlinenr, ltrim($line1)); + fix_insert_line($fixlinenr, "\+{"); + if ($line2 !~ /^\s*$/) { + fix_insert_line($fixlinenr, "\+\t" . trim($line2)); + } + } + } + +# open braces for enum, union and struct go on the same line. + if ($line =~ /^.\s*{/ && + $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following $1 go on the same line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = rtrim($prevrawline) . " {"; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)\{\s*/$1\t/; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +# missing space after union, struct or enum definition + if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { + if (WARN("SPACING", + "missing space after $1 definition\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; + } + } + +# Function pointer declarations +# check spacing between type, funcptr, and args +# canonical declaration is "type (*funcptr)(args...)" + if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { + my $declare = $1; + my $pre_pointer_space = $2; + my $post_pointer_space = $3; + my $funcname = $4; + my $post_funcname_space = $5; + my $pre_args_space = $6; + +# the $Declare variable will capture all spaces after the type +# so check it for a missing trailing missing space but pointer return types +# don't need a space so don't warn for those. + my $post_declare_space = ""; + if ($declare =~ /(\s+)$/) { + $post_declare_space = $1; + $declare = rtrim($declare); + } + if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { + WARN("SPACING", + "missing space after return type\n" . $herecurr); + $post_declare_space = " "; + } + +# unnecessary space "type (*funcptr)(args...)" +# This test is not currently implemented because these declarations are +# equivalent to +# int foo(int bar, ...) +# and this is form shouldn't/doesn't generate a checkpatch warning. +# +# elsif ($declare =~ /\s{2,}$/) { +# WARN("SPACING", +# "Multiple spaces after return type\n" . $herecurr); +# } + +# unnecessary space "type ( *funcptr)(args...)" + if (defined $pre_pointer_space && + $pre_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer open parenthesis\n" . $herecurr); + } + +# unnecessary space "type (* funcptr)(args...)" + if (defined $post_pointer_space && + $post_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr )(args...)" + if (defined $post_funcname_space && + $post_funcname_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr) (args...)" + if (defined $pre_args_space && + $pre_args_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer arguments\n" . $herecurr); + } + + if (show_type("SPACING") && $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; + } + } + +# check for spacing round square brackets; allowed: +# 1. with a type on the left -- int [] a; +# 2. at the beginning of a line for slice initialisers -- [0...10] = 5, +# 3. inside a curly brace -- = { [0...10] = 5 } + while ($line =~ /(.*?\s)\[/g) { + my ($where, $prefix) = ($-[1], $1); + if ($prefix !~ /$Type\s+$/ && + ($where != 0 || $prefix !~ /^.\s+$/) && + $prefix !~ /[{,:]\s+$/) { + if (ERROR("BRACKET_SPACE", + "space prohibited before open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(\+.*?)\s+\[/$1\[/; + } + } + } + +# check for spaces between functions and their parentheses. + while ($line =~ /($Ident)\s+\(/g) { + my $name = $1; + my $ctx_before = substr($line, 0, $-[1]); + my $ctx = "$ctx_before$name"; + + # Ignore those directives where spaces _are_ permitted. + if ($name =~ /^(?: + if|for|while|switch|return|case| + volatile|__volatile__| + __attribute__|format|__extension__| + asm|__asm__|scoped_guard)$/x) + { + # cpp #define statements have non-optional spaces, ie + # if there is a space between the name and the open + # parenthesis it is simply not a parameter group. + } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { + + # cpp #elif statement condition may start with a ( + } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { + + # If this whole things ends with a type its most + # likely a typedef for a function. + } elsif ($ctx =~ /$Type$/) { + + } else { + if (WARN("SPACING", + "space prohibited between function name and open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b$name\s+\(/$name\(/; + } + } + } + +# Check operator spacing. + if (!($line=~/\#\s*include/)) { + my $fixed_line = ""; + my $line_fixed = 0; + + my $ops = qr{ + <<=|>>=|<=|>=|==|!=| + \+=|-=|\*=|\/=|%=|\^=|\|=|&=| + =>|->|<<|>>|<|>|=|!|~| + &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| + \?:|\?|: + }x; + my @elements = split(/($ops|;)/, $opline); + +## print("element count: <" . $#elements . ">\n"); +## foreach my $el (@elements) { +## print("el: <$el>\n"); +## } + + my @fix_elements = (); + my $off = 0; + + foreach my $el (@elements) { + push(@fix_elements, substr($rawline, $off, length($el))); + $off += length($el); + } + + $off = 0; + + my $blank = copy_spacing($opline); + my $last_after = -1; + + for (my $n = 0; $n < $#elements; $n += 2) { + + my $good = $fix_elements[$n] . $fix_elements[$n + 1]; + +## print("n: <$n> good: <$good>\n"); + + $off += length($elements[$n]); + + # Pick up the preceding and succeeding characters. + my $ca = substr($opline, 0, $off); + my $cc = ''; + if (length($opline) >= ($off + length($elements[$n + 1]))) { + $cc = substr($opline, $off + length($elements[$n + 1])); + } + my $cb = "$ca$;$cc"; + + my $a = ''; + $a = 'V' if ($elements[$n] ne ''); + $a = 'W' if ($elements[$n] =~ /\s$/); + $a = 'C' if ($elements[$n] =~ /$;$/); + $a = 'B' if ($elements[$n] =~ /(\[|\()$/); + $a = 'O' if ($elements[$n] eq ''); + $a = 'E' if ($ca =~ /^\s*$/); + + my $op = $elements[$n + 1]; + + my $c = ''; + if (defined $elements[$n + 2]) { + $c = 'V' if ($elements[$n + 2] ne ''); + $c = 'W' if ($elements[$n + 2] =~ /^\s/); + $c = 'C' if ($elements[$n + 2] =~ /^$;/); + $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); + $c = 'O' if ($elements[$n + 2] eq ''); + $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); + } else { + $c = 'E'; + } + + my $ctx = "${a}x${c}"; + + my $at = "(ctx:$ctx)"; + + my $ptr = substr($blank, 0, $off) . "^"; + my $hereptr = "$hereline$ptr\n"; + + # Pull out the value of this operator. + my $op_type = substr($curr_values, $off + 1, 1); + + # Get the full operator variant. + my $opv = $op . substr($curr_vars, $off, 1); + + # Ignore operators passed as parameters. + if ($op_type ne 'V' && + $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { + +# # Ignore comments +# } elsif ($op =~ /^$;+$/) { + + # ; should have either the end of line or a space or \ after it + } elsif ($op eq ';') { + if ($ctx !~ /.x[WEBC]/ && + $cc !~ /^\\/ && $cc !~ /^;/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + + # // is a comment + } elsif ($op eq '//') { + + # : when part of a bitfield + } elsif ($opv eq ':B') { + # skip the bitfield test for now + + # No spaces for: + # -> + } elsif ($op eq '->') { + if ($ctx =~ /Wx.|.xW/) { + if (ERROR("SPACING", + "spaces prohibited around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # , must not have a space before and must have a space on the right. + } elsif ($op eq ',') { + my $rtrim_before = 0; + my $space_after = 0; + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $rtrim_before = 1; + } + } + if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $last_after = $n; + $space_after = 1; + } + } + if ($rtrim_before || $space_after) { + if ($rtrim_before) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + } else { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + } + if ($space_after) { + $good .= " "; + } + } + + # '*' as part of a type definition -- reported already. + } elsif ($opv eq '*_') { + #warn "'*' is part of type\n"; + + # unary operators should have a space before and + # none after. May be left adjacent to another + # unary operator, or a cast + } elsif ($op eq '!' || $op eq '~' || + $opv eq '*U' || $opv eq '-U' || + $opv eq '&U' || $opv eq '&&U') { + if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { + if (ERROR("SPACING", + "space required before that '$op' $at\n" . $hereptr)) { + if ($n != $last_after + 2) { + $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } + if ($op eq '*' && $cc =~/\s*$Modifier\b/) { + # A unary '*' may be const + + } elsif ($ctx =~ /.xW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # unary ++ and unary -- are allowed no space on one side. + } elsif ($op eq '++' or $op eq '--') { + if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { + if (ERROR("SPACING", + "space required one side of that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + if ($ctx =~ /Wx[BE]/ || + ($ctx =~ /Wx./ && $cc =~ /^;/)) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + if ($ctx =~ /ExW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # << and >> may either have or not have spaces both sides + } elsif ($op eq '<<' or $op eq '>>' or + $op eq '&' or $op eq '^' or $op eq '|' or + $op eq '+' or $op eq '-' or + $op eq '*' or $op eq '/' or + $op eq '%') + { + if ($check) { + if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { + if (CHK("SPACING", + "spaces preferred around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + $fix_elements[$n + 2] =~ s/^\s+//; + $line_fixed = 1; + } + } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { + if (CHK("SPACING", + "space preferred before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { + if (ERROR("SPACING", + "need consistent spacing around '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # A colon needs no spaces before when it is + # terminating a case value or a label. + } elsif ($opv eq ':C' || $opv eq ':L') { + if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + + # All the others need spaces both sides. + } elsif ($ctx !~ /[EWC]x[CWE]/) { + my $ok = 0; + + # Ignore email addresses <foo@bar> + if (($op eq '<' && + $cc =~ /^\S+\@\S+>/) || + ($op eq '>' && + $ca =~ /<\S+\@\S+$/)) + { + $ok = 1; + } + + # for asm volatile statements + # ignore a colon with another + # colon immediately before or after + if (($op eq ':') && + ($ca =~ /:$/ || $cc =~ /^:/)) { + $ok = 1; + } + + # messages are ERROR, but ?: are CHK + if ($ok == 0) { + my $msg_level = \&ERROR; + $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); + + if (&{$msg_level}("SPACING", + "spaces required around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + } + $off += length($elements[$n + 1]); + +## print("n: <$n> GOOD: <$good>\n"); + + $fixed_line = $fixed_line . $good; + } + + if (($#elements % 2) == 0) { + $fixed_line = $fixed_line . $fix_elements[$#elements]; + } + + if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { + $fixed[$fixlinenr] = $fixed_line; + } + + + } + +# check for whitespace before a non-naked semicolon + if ($line =~ /^\+.*\S\s+;\s*$/) { + if (WARN("SPACING", + "space prohibited before semicolon\n" . $herecurr) && + $fix) { + 1 while $fixed[$fixlinenr] =~ + s/^(\+.*\S)\s+;/$1;/; + } + } + +# check for multiple assignments + if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { + CHK("MULTIPLE_ASSIGNMENTS", + "multiple assignments should be avoided\n" . $herecurr); + } + +## # check for multiple declarations, allowing for a function declaration +## # continuation. +## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && +## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { +## +## # Remove any bracketed sections to ensure we do not +## # falsely report the parameters of functions. +## my $ln = $line; +## while ($ln =~ s/\([^\(\)]*\)//g) { +## } +## if ($ln =~ /,/) { +## WARN("MULTIPLE_DECLARATION", +## "declaring multiple variables together should be avoided\n" . $herecurr); +## } +## } + +#need space before brace following if, while, etc + if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || + $line =~ /\b(?:else|do)\{/) { + if (ERROR("SPACING", + "space required before the open brace '{'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; + } + } + +## # check for blank lines before declarations +## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && +## $prevrawline =~ /^.\s*$/) { +## WARN("SPACING", +## "No blank lines before declarations\n" . $hereprev); +## } +## + +# closing brace should have a space following it when it has anything +# on the line + if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { + if (ERROR("SPACING", + "space required after that close brace '}'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/}((?!(?:,|;|\)))\S)/} $1/; + } + } + +# check spacing on square brackets + if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { + if (ERROR("SPACING", + "space prohibited after that open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\[\s+/\[/; + } + } + if ($line =~ /\s\]/) { + if (ERROR("SPACING", + "space prohibited before that close square bracket ']'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\]/\]/; + } + } + +# check spacing on parentheses + if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && + $line !~ /for\s*\(\s+;/) { + if (ERROR("SPACING", + "space prohibited after that open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\(\s+/\(/; + } + } + if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && + $line !~ /for\s*\(.*;\s+\)/ && + $line !~ /:\s+\)/) { + if (ERROR("SPACING", + "space prohibited before that close parenthesis ')'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\)/\)/; + } + } + +# check unnecessary parentheses around addressof/dereference single $Lvals +# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar + + while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { + my $var = $1; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around $var\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; + } + } + +# check for unnecessary parentheses around function pointer uses +# ie: (foo->bar)(); should be foo->bar(); +# but not "if (foo->bar) (" to avoid some false positives + if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { + my $var = $2; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around function pointer $var\n" . $herecurr) && + $fix) { + my $var2 = deparenthesize($var); + $var2 =~ s/\s//g; + $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; + } + } + +# 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; + my $test = substr($2, 1, -1); + my $herectx; + while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { + my $match = $1; + # avoid parentheses around potential macro args + next if ($match =~ /^\s*\w+\s*$/); + if (!defined($herectx)) { + $herectx = $here . "\n"; + my $cnt = statement_rawlines($if_stat); + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + last if $rl =~ /^[ \+].*\{/; + } + } + CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around '$match'\n" . $herectx); + } + } + +# check that goto labels aren't indented (allow a single space indentation) +# and ignore bitfield definitions like foo:1 +# Strictly, labels can have whitespace after the identifier and before the : +# but this is not allowed here as many ?: uses would appear to be labels + if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && + $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && + $sline !~ /^.\s+default:/) { + if (WARN("INDENTED_LABEL", + "labels should not be indented\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.)\s+/$1/; + } + } + +# check if a statement with a comma should be two statements like: +# foo = bar(), /* comma should be semicolon */ +# bar = baz(); + if (defined($stat) && + $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + WARN("SUSPECT_COMMA_SEMICOLON", + "Possible comma where semicolon could be used\n" . $herectx); + } + +# return is not a function + if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { + my $spacing = $1; + if ($perl_version_ok && + $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { + my $value = $1; + $value = deparenthesize($value); + if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { + ERROR("RETURN_PARENTHESES", + "return is not a function, parentheses are not required\n" . $herecurr); + } + } elsif ($spacing !~ /\s+/) { + ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr); + } + } + +# unnecessary return in a void function +# at end-of-function, with the previous line a single leading tab, then return; +# and the line before that not a goto label target like "out:" + if ($sline =~ /^[ \+]}\s*$/ && + $prevline =~ /^\+\treturn\s*;\s*$/ && + $linenr >= 3 && + $lines[$linenr - 3] =~ /^[ +]/ && + $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { + WARN("RETURN_VOID", + "void function return statements are not generally useful\n" . $hereprev); + } + +# if statements using unnecessary parentheses - ie: if ((foo == bar)) + if ($perl_version_ok && + $line =~ /\bif\s*((?:\(\s*){2,})/) { + my $openparens = $1; + my $count = $openparens =~ tr@\(@\(@; + my $msg = ""; + if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { + my $comp = $4; #Not $1 because of $LvalOrFunc + $msg = " - maybe == should be = ?" if ($comp eq "=="); + WARN("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses$msg\n" . $herecurr); + } + } + +# comparisons with a constant or upper case identifier on the left +# avoid cases like "foo + BAR < baz" +# only fix matches surrounded by parentheses to avoid incorrect +# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" + if ($perl_version_ok && + $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { + my $lead = $1; + my $const = $2; + my $comp = $3; + my $to = $4; + my $newcomp = $comp; + if ($lead !~ /(?:$Operators|\.)\s*$/ && + $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && + WARN("CONSTANT_COMPARISON", + "Comparisons should place the constant on the right side of the test\n" . $herecurr) && + $fix) { + if ($comp eq "<") { + $newcomp = ">"; + } elsif ($comp eq "<=") { + $newcomp = ">="; + } elsif ($comp eq ">") { + $newcomp = "<"; + } elsif ($comp eq ">=") { + $newcomp = "<="; + } + $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; + } + } + +# Return of what appears to be an errno should normally be negative + if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { + my $name = $1; + if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { + WARN("USE_NEGATIVE_ERRNO", + "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); + } + } + +# Need a space before open parenthesis after if, while etc + if ($line =~ /\b(if|while|for|switch)\(/) { + if (ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b(if|while|for|switch)\(/$1 \(/; + } + } + +# Check for illegal assignment in if conditional -- and check for trailing +# statements after the conditional. + if ($line =~ /do\s*(?!{)/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($stat_next) = ctx_statement_block($line_nr_next, + $remain_next, $off_next); + $stat_next =~ s/\n./\n /g; + ##print "stat<$stat> stat_next<$stat_next>\n"; + + if ($stat_next =~ /^\s*while\b/) { + # If the statement carries leading newlines, + # then count those as offsets. + my ($whitespace) = + ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = + statement_rawlines($whitespace) - 1; + + $suppress_whiletrailers{$line_nr_next + + $offset} = 1; + } + } + if (!defined $suppress_whiletrailers{$linenr} && + defined($stat) && defined($cond) && + $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { + my ($s, $c) = ($stat, $cond); + my $fixed_assign_in_if = 0; + + if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + if (ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr) && + $fix && $perl_version_ok) { + if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { + my $space = $1; + my $not = $2; + my $statement = $3; + my $assigned = $4; + my $test = $8; + my $against = $9; + my $brace = $15; + fix_delete_line($fixlinenr, $rawline); + fix_insert_line($fixlinenr, "$space$statement;"); + my $newline = "${space}if ("; + $newline .= '!' if defined($not); + $newline .= '(' if (defined $not && defined($test) && defined($against)); + $newline .= "$assigned"; + $newline .= " $test $against" if (defined($test) && defined($against)); + $newline .= ')' if (defined $not && defined($test) && defined($against)); + $newline .= ')'; + $newline .= " {" if (defined($brace)); + fix_insert_line($fixlinenr + 1, $newline); + $fixed_assign_in_if = 1; + } + } + } + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + $s =~ s/$;//g; # Remove any comments + if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && + $c !~ /}\s*while\s*/) + { + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + my $stat_real = ''; + + $stat_real = raw_line($linenr, $cond_lines) + . "\n" if ($cond_lines); + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + if (ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr . $stat_real) && + !$fixed_assign_in_if && + $cond_lines == 0 && + $fix && $perl_version_ok && + $fixed[$fixlinenr] =~ /^\+(\s*)((?:if|while|for)\s*$balanced_parens)\s*(.*)$/) { + my $indent = $1; + my $test = $2; + my $rest = rtrim($4); + if ($rest =~ /;$/) { + $fixed[$fixlinenr] = "\+$indent$test"; + fix_insert_line($fixlinenr + 1, "$indent\t$rest"); + } + } + } + } + +# Check for bitwise tests written as boolean + if ($line =~ / + (?: + (?:\[|\(|\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\|) + | + (?:\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\||\)|\]) + )/x) + { + WARN("HEXADECIMAL_BOOLEAN_TEST", + "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); + } + +# if and else should not have general statements after it + if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { + my $s = $1; + $s =~ s/$;//g; # Remove any comments + if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + } +# if should not continue a brace + if ($line =~ /}\s*if\b/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line (or did you mean 'else if'?)\n" . + $herecurr); + } +# case and default should not have general statements after them + if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && + $line !~ /\G(?: + (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| + \s*return\s+ + )/xg) + { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + + # Check for }<nl>else {, these must be at the same + # indent level to be relevant to each other. + if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && + $previndent == $indent) { + if (ERROR("ELSE_AFTER_BRACE", + "else should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/}\s*$//; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)else/$1} else/; + fix_insert_line($fixlinenr, $fixedline); + } + } + + if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && + $previndent == $indent) { + my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + + if ($s =~ /^\s*;/) { + if (ERROR("WHILE_AFTER_BRACE", + "while should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + my $trailing = $rawline; + $trailing =~ s/^\+//; + $trailing = trim($trailing); + $fixedline =~ s/}\s*$/} $trailing/; + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +#Specific variable tests + while ($line =~ m{($Constant|$Lval)}g) { + my $var = $1; + +#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 + $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && +#Ignore ETHTOOL_LINK_MODE_<foo> variants + $var !~ /^ETHTOOL_LINK_MODE_/ && +#Ignore SI style variants like nS, mV and dB +#(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) + $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && +#Ignore some three character SI units explicitly, like MiB and KHz + $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { + while ($var =~ m{\b($Ident)}g) { + my $word = $1; + next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); + if ($check) { + seed_camelcase_includes(); + if (!$file && !$camelcase_file_seeded) { + seed_camelcase_file($realfile); + $camelcase_file_seeded = 1; + } + } + if (!defined $camelcase{$word}) { + $camelcase{$word} = 1; + CHK("CAMELCASE", + "Avoid CamelCase: <$word>\n" . $herecurr); + } + } + } + } + +#no spaces allowed after \ in define + if ($line =~ /\#\s*define.*\\\s+$/) { + if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", + "Whitespace after \\ makes next lines useless\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + } + +# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes +# itself <asm/foo.h> (uses RAW line) + if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { + my $file = "$1.h"; + my $checkfile = "include/linux/$file"; + if (-f "$root/$checkfile" && + $realfile ne $checkfile && + $1 !~ /$allowed_asm_includes/) + { + my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; + if ($asminclude > 0) { + if ($realfile =~ m{^arch/}) { + CHK("ARCH_INCLUDE_LINUX", + "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } else { + WARN("INCLUDE_LINUX", + "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } + } + } + } + +# 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; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + my $has_flow_statement = 0; + my $has_arg_concat = 0; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; + #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; + + $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); + $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); + + $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; + my $define_args = $1; + my $define_stmt = $dstat; + my @def_args = (); + + if (defined $define_args && $define_args ne "") { + $define_args = substr($define_args, 1, length($define_args) - 2); + $define_args =~ s/\s*//g; + $define_args =~ s/\\\+?//g; + @def_args = split(",", $define_args); + } + + $dstat =~ s/$;//g; + $dstat =~ s/\\\n.//g; + $dstat =~ s/^\s*//s; + $dstat =~ s/\s*$//s; + + # Flatten any parentheses and braces + while ($dstat =~ s/\([^\(\)]*\)/1u/ || + $dstat =~ s/\{[^\{\}]*\}/1u/ || + $dstat =~ s/.\[[^\[\]]*\]/1u/) + { + } + + # Flatten any obvious string concatenation. + while ($dstat =~ s/($String)\s*$Ident/$1/ || + $dstat =~ s/$Ident\s*($String)/$1/) + { + } + + # Make asm volatile uses seem like a generic function + $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; + + 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| + \.$Ident\s*=\s*| + ^\"|\"$| + ^\[ + }x; + #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; + + $ctx =~ s/\n*$//; + my $stmt_cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $stmt_cnt, $here); + + if ($dstat ne '' && + $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), + $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); + $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz + $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants + $dstat !~ /$exceptions/ && + $dstat !~ /^\.$Ident\s*=/ && # .foo = + $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo + $dstat !~ /^case\b/ && # case ... + $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} + $dstat !~ /^for\s*$Constant$/ && # for (...) + $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() + $dstat !~ /^do\s*{/ && # do {... + $dstat !~ /^\(\{/ && # ({... + $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) + { + if ($dstat =~ /^\s*if\b/) { + 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 =~ /;/) { + 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\nBUT SEE:\n$DO_WHILE_0_ADVICE"); + } + + } + + # Make $define_stmt single line, comment-free, etc + my @stmt_array = split('\n', $define_stmt); + my $first = 1; + $define_stmt = ""; + foreach my $l (@stmt_array) { + $l =~ s/\\$//; + if ($first) { + $define_stmt = $l; + $first = 0; + } elsif ($l =~ /^[\+ ]/) { + $define_stmt .= substr($l, 1); + } + } + $define_stmt =~ s/$;//g; + $define_stmt =~ s/\s+/ /g; + $define_stmt = trim($define_stmt); + +# check if any macro arguments are reused (ignore '...' and 'type') + foreach my $arg (@def_args) { + next if ($arg =~ /\.\.\./); + next if ($arg =~ /^type$/i); + my $tmp_stmt = $define_stmt; + $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\#+\s*$arg\b//g; + $tmp_stmt =~ s/\b$arg\s*\#\#//g; + my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; + if ($use_cnt > 1) { + CHK("MACRO_ARG_REUSE", + "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); + } +# check if any macro arguments may have other precedence issues + if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && + ((defined($1) && $1 ne ',') || + (defined($2) && $2 ne ','))) { + 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 +# ## concatenation is commonly a macro that defines a function so ignore those + if ($has_flow_statement && !$has_arg_concat) { + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("MACRO_WITH_FLOW_CONTROL", + "Macros with flow control statements should be avoided\n" . "$herectx"); + } + +# check for line continuations outside of #defines, preprocessor #, and asm + + } elsif ($realfile =~ m@/vmlinux.lds.h$@) { + $line =~ s/(\w+)/$maybe_linker_symbol{$1}++/ge; + #print "REAL: $realfile\nln: $line\nkeys:", sort keys %maybe_linker_symbol; + } else { + if ($prevline !~ /^..*\\$/ && + $line !~ /^\+\s*\#.*\\$/ && # preprocessor + $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm + $line =~ /^\+.*\\$/) { + WARN("LINE_CONTINUATIONS", + "Avoid unnecessary line continuations\n" . $herecurr); + } + } + +# do {} while (0) macro tests: +# single-statement macros do not need to be enclosed in do while (0) loop, +# macro should not end with a semicolon + if ($perl_version_ok && + $realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + + $dstat =~ s/\\\n.//g; + $dstat =~ s/$;/ /g; + + if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { + my $stmts = $2; + my $semis = $3; + + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (($stmts =~ tr/;/;/) == 1 && + $stmts !~ /^\s*(if|while|for|switch)\b/) { + WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", + "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); + } + if (defined $semis && $semis ne "") { + WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", + "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); + } + } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("TRAILING_SEMICOLON", + "macros should not use a trailing semicolon\n" . "$herectx"); + } + } + +# check for redundant bracing round if etc + if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, 1); + #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; + #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; + if ($#chunks > 0 && $level == 0) { + my @allowed = (); + my $allow = 0; + my $seen = 0; + my $herectx = $here . "\n"; + my $ln = $linenr - 1; + for my $chunk (@chunks) { + my ($cond, $block) = @{$chunk}; + + # If the condition carries leading newlines, then count those as offsets. + my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = statement_rawlines($whitespace) - 1; + + $allowed[$allow] = 0; + #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; + + # We have looked at and allowed this specific line. + $suppress_ifbraces{$ln + $offset} = 1; + + $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; + $ln += statement_rawlines($block) - 1; + + substr($block, 0, length($cond), ''); + + $seen++ if ($block =~ /^\s*{/); + + #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed[$allow] = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed[$allow] = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed[$allow] = 1; + } + $allow++; + } + if ($seen) { + my $sum_allowed = 0; + foreach (@allowed) { + $sum_allowed += $_; + } + if ($sum_allowed == 0) { + WARN("BRACES", + "braces {} are not necessary for any arm of this statement\n" . $herectx); + } elsif ($sum_allowed != $allow && + $seen != $allow) { + CHK("BRACES", + "braces {} should be used on all arms of this statement\n" . $herectx); + } + } + } + } + if (!defined $suppress_ifbraces{$linenr - 1} && + $line =~ /\b(if|while|for|else)\b/) { + my $allowed = 0; + + # Check the pre-context. + if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { + #print "APW: ALLOWED: pre<$1>\n"; + $allowed = 1; + } + + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, $-[0]); + + # Check the condition. + my ($cond, $block) = @{$chunks[0]}; + #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed = 1; + } + # Check the post-context. + if (defined $chunks[1]) { + my ($cond, $block) = @{$chunks[1]}; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if ($block =~ /^\s*\{/) { + #print "APW: ALLOWED: chunk-1 block<$block>\n"; + $allowed = 1; + } + } + if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { + my $cnt = statement_rawlines($block); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("BRACES", + "braces {} are not necessary for single statement blocks\n" . $herectx); + } + } + +# check for single line unbalanced braces + if ($sline =~ /^.\s*\}\s*else\s*$/ || + $sline =~ /^.\s*else\s*\{\s*$/) { + CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); + } + +# check for unnecessary blank lines around braces + if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + } + } + if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# no volatiles please + my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; + if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); + } + +# Check for user-visible strings broken across lines, which breaks the ability +# to grep for the string. Make exceptions when the previous string ends in a +# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' +# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value + if ($line =~ /^\+\s*$String/ && + $prevline =~ /"\s*$/ && + $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { + if (WARN("SPLIT_STRING", + "quoted string split across lines\n" . $hereprev) && + $fix && + $prevrawline =~ /^\+.*"\s*$/ && + $last_coalesced_string_linenr != $linenr - 1) { + my $extracted_string = get_quoted_string($line, $rawline); + my $comma_close = ""; + if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { + $comma_close = $1; + } + + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/"\s*$//; + $fixedline .= substr($extracted_string, 1) . trim($comma_close); + fix_insert_line($fixlinenr - 1, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; + if ($fixedline !~ /\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $last_coalesced_string_linenr = $linenr; + } + } + +# check for missing a space in a string concatenation + if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { + WARN('MISSING_SPACE', + "break quoted strings at a space character\n" . $hereprev); + } + +# check for an embedded function name in a string when the function is known +# This does not work very well for -f --file checking as it depends on patch +# context providing the function name or a single line form for in-file +# function declarations + if ($line =~ /^\+.*$String/ && + defined($context_function) && + get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && + length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { + WARN("EMBEDDED_FUNCTION_NAME", + "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); + } + +# check for unnecessary function tracing like uses +# This does not use $logFunctions because there are many instances like +# 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions + if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { + if (WARN("TRACING_LOGGING", + "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# check for spaces before a quoted newline + if ($rawline =~ /^.*\".*\s\\n/) { + if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", + "unnecessary whitespace before a quoted newline\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; + } + + } + +# concatenated string without spaces between elements + if ($line =~ /$String[A-Z_]/ || + ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { + if (CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr) && + $fix) { + while ($line =~ /($String)/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; + $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; + } + } + } + +# uncoalesced string fragments + if ($line =~ /$String\s*[Lu]?"/) { + if (WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr) && + $fix) { + while ($line =~ /($String)(?=\s*")/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; + } + } + } + +# check for non-standard and hex prefixed decimal printf formats + my $show_L = 1; #don't show the same defect twice + my $show_Z = 1; + while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { + my $string = substr($rawline, $-[1], $+[1] - $-[1]); + $string =~ s/%%/__/g; + # check for %L + if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { + WARN("PRINTF_L", + "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); + $show_L = 0; + } + # check for %Z + if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { + WARN("PRINTF_Z", + "%Z$1 is non-standard C, use %z$1\n" . $herecurr); + $show_Z = 0; + } + # check for 0x<decimal> + if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { + ERROR("PRINTF_0XDECIMAL", + "Prefixing 0x with decimal output is defective\n" . $herecurr); + } + } + +# check for line continuations in quoted strings with odd counts of " + if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { + WARN("LINE_CONTINUATIONS", + "Avoid line continuations in quoted strings\n" . $herecurr); + } + +# warn about #if 0 + if ($line =~ /^.\s*\#\s*if\s+0\b/) { + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); + } + +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" . $herecurr); + } + +# check for needless "if (<foo>) fn(<foo>)" uses + if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { + my $tested = quotemeta($1); + my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; + if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { + my $func = $1; + if (WARN('NEEDLESS_IF', + "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && + $fix) { + my $do_fix = 1; + my $leading_tabs = ""; + my $new_leading_tabs = ""; + if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { + $leading_tabs = $1; + } else { + $do_fix = 0; + } + if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { + $new_leading_tabs = $1; + if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { + $do_fix = 0; + } + } else { + $do_fix = 0; + } + if ($do_fix) { + fix_delete_line($fixlinenr - 1, $prevrawline); + $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; + } + } + } + } + +# check for unnecessary "Out of Memory" messages + if ($line =~ /^\+.*\b$logFunctions\s*\(/ && + $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && + (defined $1 || defined $3) && + $linenr > 3) { + my $testval = $2; + my $testline = $lines[$linenr - 3]; + + my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); +# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); + + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && + $s !~ /\b__GFP_NOWARN\b/ ) { + WARN("OOM_MESSAGE", + "Possible unnecessary 'out of memory' message\n" . $hereprev); + } + } + +# check for logging functions with KERN_<LEVEL> + if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && + $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { + my $level = $1; + if (WARN("UNNECESSARY_KERN_LEVEL", + "Possible unnecessary $level\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s*$level\s*//; + } + } + +# check for logging continuations + if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { + WARN("LOGGING_CONTINUATION", + "Avoid logging continuation uses where feasible\n" . $herecurr); + } + +# check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions + if (defined $stat && + $line =~ /\b$logFunctions\s*\(/ && + index($stat, '"') >= 0) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + pos($stat_real) = index($stat_real, '"'); + while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { + my $pspec = $1; + my $h = $2; + my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; + if (WARN("UNNECESSARY_MODIFIER", + "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && + $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { + my $nspec = $pspec; + $nspec =~ s/h//g; + $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; + } + } + } + +# check for mask then right shift without a parentheses + if ($perl_version_ok && + $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && + $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so + WARN("MASK_THEN_SHIFT", + "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); + } + +# check for pointer comparisons to NULL + if ($perl_version_ok) { + while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { + my $val = $1; + my $equal = "!"; + $equal = "" if ($4 eq "!="); + if (CHK("COMPARISON_TO_NULL", + "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; + } + } + } + +# check for bad placement of section $InitAttribute (e.g.: __initdata) + if ($line =~ /(\b$InitAttribute\b)/) { + my $attr = $1; + if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { + my $ptr = $1; + my $var = $2; + if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && + ERROR("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr)) || + ($ptr !~ /\b(union|struct)\s+$attr\b/ && + WARN("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr))) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; + } + } + } + +# check for $InitAttributeData (ie: __initdata) with const + if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { + my $attr = $1; + $attr =~ /($InitAttributePrefix)(.*)/; + my $attr_prefix = $1; + my $attr_type = $2; + if (ERROR("INIT_ATTRIBUTE", + "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/$InitAttributeData/${attr_prefix}initconst/; + } + } + +# check for $InitAttributeConst (ie: __initconst) without const + if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { + my $attr = $1; + if (ERROR("INIT_ATTRIBUTE", + "Use of $attr requires a separate use of const\n" . $herecurr) && + $fix) { + my $lead = $fixed[$fixlinenr] =~ + /(^\+\s*(?:static\s+))/; + $lead = rtrim($1); + $lead = "$lead " if ($lead !~ /^\+$/); + $lead = "${lead}const "; + $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; + } + } + +# check for __read_mostly with const non-pointer (should just be const) + if ($line =~ /\b__read_mostly\b/ && + $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { + if (ERROR("CONST_READ_MOSTLY", + "Invalid use of __read_mostly with const type\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; + } + } + +# don't use __constant_<foo> functions outside of include/uapi/ + if ($realfile !~ m@^include/uapi/@ && + $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { + my $constant_func = $1; + my $func = $constant_func; + $func =~ s/^__constant_//; + if (WARN("CONSTANT_CONVERSION", + "$constant_func should be $func\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; + } + } + +# prefer usleep_range over udelay + if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + my $delay = $1; + # ignore udelay's < 10, however + if (! ($delay < 10) ) { + CHK("USLEEP_RANGE", + "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 function description of mdelay().\n" . $herecurr); + } + } + +# warn about unexpectedly long msleep's + if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if ($1 < 20) { + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); + } + } + +# check for comparisons of jiffies + if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); + } + +# check for comparisons of get_jiffies_64() + if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { + WARN("JIFFIES_COMPARISON", + "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); + } + +# warn about #ifdefs in C files +# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { +# print "#ifdef in C files should be avoided\n"; +# print "$herecurr"; +# $clean = 0; +# } + +# warn about spacing in #ifdefs + if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { + if (ERROR("SPACING", + "exactly one space required after that #$1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; + } + + } + +# check for spinlock_t definitions without a comment. + if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || + $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { + my $which = $1; + if (!ctx_has_comment($first_line, $linenr)) { + CHK("UNCOMMENTED_DEFINITION", + "$1 definition without comment\n" . $herecurr); + } + } +# check for memory barriers without a comment. + + my $barriers = qr{ + mb| + rmb| + wmb + }x; + my $barrier_stems = qr{ + mb__before_atomic| + mb__after_atomic| + store_release| + load_acquire| + store_mb| + (?:$barriers) + }x; + my $all_barriers = qr{ + (?:$barriers)| + smp_(?:$barrier_stems)| + virt_(?:$barrier_stems) + }x; + + if ($line =~ /\b(?:$all_barriers)\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("MEMORY_BARRIER", + "memory barrier without comment\n" . $herecurr); + } + } + + my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; + + if ($realfile !~ m@^include/asm-generic/@ && + $realfile !~ m@/barrier\.h$@ && + $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && + $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { + WARN("MEMORY_BARRIER", + "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); + } + +# check for waitqueue_active without a comment. + if ($line =~ /\bwaitqueue_active\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("WAITQUEUE_ACTIVE", + "waitqueue_active without comment\n" . $herecurr); + } + } + +# check for data_race without a comment. + if ($line =~ /\bdata_race\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("DATA_RACE", + "data_race without comment\n" . $herecurr); + } + } + +# check for context_unsafe without a comment. + if ($line =~ /\bcontext_unsafe\b/ && + !ctx_has_comment($first_line, $linenr)) { + WARN("CONTEXT_UNSAFE", + "context_unsafe without comment\n" . $herecurr); + } + +# check of hardware specific defines + if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { + CHK("ARCH_DEFINES", + "architecture specific defines should be avoided\n" . $herecurr); + } + +# check that the storage class is not after a type + if ($line =~ /\b($Type)\s+($Storage)\b/) { + WARN("STORAGE_CLASS", + "storage class '$2' should be located before type '$1'\n" . $herecurr); + } +# Check that the storage class is at the beginning of a declaration + if ($line =~ /\b$Storage\b/ && + $line !~ /^.\s*$Storage/ && + $line =~ /^.\s*(.+?)\$Storage\s/ && + $1 !~ /[\,\)]\s*$/) { + WARN("STORAGE_CLASS", + "storage class should be at the beginning of the declaration\n" . $herecurr); + } + +# check the location of the inline attribute, that it is between +# storage class and type. + if ($line =~ /\b$Type\s+$Inline\b/ || + $line =~ /\b$Inline\s+$Storage\b/) { + ERROR("INLINE_LOCATION", + "inline keyword should sit between storage class and type\n" . $herecurr); + } + +# Check for __inline__ and __inline, prefer inline + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b(__inline__|__inline)\b/) { + if (WARN("INLINE", + "plain inline is preferred over $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; + + } + } + +# Check for compiler attributes + if ($realfile !~ m@\binclude/uapi/@ && + $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { + my $attr = $1; + $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; + + my %attr_list = ( + "alias" => "__alias", + "aligned" => "__aligned", + "always_inline" => "__always_inline", + "assume_aligned" => "__assume_aligned", + "cold" => "__cold", + "const" => "__attribute_const__", + "copy" => "__copy", + "designated_init" => "__designated_init", + "externally_visible" => "__visible", + "format" => "printf|scanf", + "gnu_inline" => "__gnu_inline", + "malloc" => "__malloc", + "mode" => "__mode", + "no_caller_saved_registers" => "__no_caller_saved_registers", + "noclone" => "__noclone", + "noinline" => "noinline", + "nonstring" => "__nonstring", + "noreturn" => "__noreturn", + "packed" => "__packed", + "pure" => "__pure", + "section" => "__section", + "used" => "__used", + "weak" => "__weak" + ); + + while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { + my $orig_attr = $1; + my $params = ''; + $params = $2 if defined($2); + my $curr_attr = $orig_attr; + $curr_attr =~ s/^[\s_]+|[\s_]+$//g; + if (exists($attr_list{$curr_attr})) { + my $new = $attr_list{$curr_attr}; + if ($curr_attr eq "format" && $params) { + $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; + $new = "__$1\($2"; + } else { + $new = "$new$params"; + } + if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && + $fix) { + my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; + $fixed[$fixlinenr] =~ s/$remove//; + $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; + $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; + $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; + } + } + } + + # Check for __attribute__ unused, prefer __always_unused or __maybe_unused + if ($attr =~ /^_*unused/) { + WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); + } + } + +# Check for __attribute__ weak, or __weak declarations (may have link issues) + if ($perl_version_ok && + $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && + ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || + $line =~ /\b__weak\b/)) { + ERROR("WEAK_DECLARATION", + "Using weak declarations can have unintended link defects\n" . $herecurr); + } + +# check for c99 types like uint8_t used outside of uapi/ and tools/ + if ($realfile !~ m@\binclude/uapi/@ && + $realfile !~ m@\btools/@ && + $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { + my $type = $1; + if ($type =~ /\b($typeC99Typedefs)\b/) { + $type = $1; + my $kernel_type = 'u'; + $kernel_type = 's' if ($type =~ /^_*[si]/); + $type =~ /(\d+)/; + $kernel_type .= $1; + if (CHK("PREFER_KERNEL_TYPES", + "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; + } + } + } + +# check for cast of C90 native int or longer types constants + if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { + my $cast = $1; + my $const = $2; + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } + if (WARN("TYPECAST_INT_CONSTANT", + "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; + } + } + +# check for sizeof(&) + if ($line =~ /\bsizeof\s*\(\s*\&/) { + WARN("SIZEOF_ADDRESS", + "sizeof(& should be avoided\n" . $herecurr); + } + +# check for sizeof without parenthesis + if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { + if (WARN("SIZEOF_PARENTHESIS", + "sizeof $1 should be sizeof($1)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; + } + } + +# check for struct spinlock declarations + if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" . $herecurr); + } + +# check for seq_printf uses that could be seq_puts + if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { + my $fmt = get_quoted_string($line, $rawline); + $fmt =~ s/%%//g; + if ($fmt !~ /%/) { + if (WARN("PREFER_SEQ_PUTS", + "Prefer seq_puts to seq_printf\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; + } + } + } + +# check for vsprintf extension %p<foo> misuses + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && + $1 !~ /^_*volatile_*$/) { + my $stat_real; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + for (my $count = $linenr; $count <= $lc; $count++) { + my $specifier; + my $extension; + my $qualifier; + my $bad_specifier = ""; + my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); + $fmt =~ s/%%//g; + + while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { + $specifier = $1; + $extension = $2; + $qualifier = $3; + if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || + ($extension eq "f" && + defined $qualifier && $qualifier !~ /^w/) || + ($extension eq "4" && + defined $qualifier && $qualifier !~ /^c(?:[hlbc]|hR)$/)) { + $bad_specifier = $specifier; + last; + } + if ($extension eq "x" && !defined($stat_real)) { + if (!defined($stat_real)) { + $stat_real = get_stat_real($linenr, $lc); + } + WARN("VSPRINTF_SPECIFIER_PX", + "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); + } + } + if ($bad_specifier ne "") { + my $stat_real = get_stat_real($linenr, $lc); + my $msg_level = \&WARN; + my $ext_type = "Invalid"; + my $use = ""; + if ($bad_specifier =~ /p[Ff]/) { + $use = " - use %pS instead"; + $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); + } elsif ($bad_specifier =~ /pA/) { + $use = " - '%pA' is only intended to be used from Rust code"; + $msg_level = \&ERROR; + } + + &{$msg_level}("VSPRINTF_POINTER_EXTENSION", + "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); + } + } + } + +# Check for misused memsets + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { + + my $ms_addr = $2; + my $ms_val = $7; + my $ms_size = $12; + + if ($ms_size =~ /^(0x|)0$/i) { + ERROR("MEMSET", + "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); + } elsif ($ms_size =~ /^(0x|)1$/i) { + WARN("MEMSET", + "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); + } + } + +# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# if (WARN("PREFER_ETHER_ADDR_COPY", +# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; +# } +# } + +# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# WARN("PREFER_ETHER_ADDR_EQUAL", +# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") +# } + +# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr +# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# +# my $ms_val = $7; +# +# if ($ms_val =~ /^(?:0x|)0+$/i) { +# if (WARN("PREFER_ETH_ZERO_ADDR", +# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; +# } +# } elsif ($ms_val =~ /^(?:0xff|255)$/i) { +# if (WARN("PREFER_ETH_BROADCAST_ADDR", +# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; +# } +# } +# } +# strcpy uses that should likely be strscpy + if ($line =~ /\bstrcpy\s*\(/ && !is_userspace($realfile)) { + WARN("STRCPY", + "Prefer strscpy over strcpy - see: https://github.com/KSPP/linux/issues/88\n" . $herecurr); + } + +# strlcpy uses that should likely be strscpy + if ($line =~ /\bstrlcpy\s*\(/ && !is_userspace($realfile)) { + WARN("STRLCPY", + "Prefer strscpy over strlcpy - see: https://github.com/KSPP/linux/issues/89\n" . $herecurr); + } + +# strncpy uses that should likely be strscpy or strscpy_pad + if ($line =~ /\bstrncpy\s*\(/ && !is_userspace($realfile)) { + WARN("STRNCPY", + "Prefer strscpy, strscpy_pad, or __nonstring over strncpy - see: https://github.com/KSPP/linux/issues/90\n" . $herecurr); + } + +# ethtool_sprintf uses that should likely be ethtool_puts + if ($line =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with only two arguments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + # use $rawline because $line loses %s via sanitization and thus we can't match against it. + if ($rawline =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*\"\%s\"\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with standalone \"%s\" specifier\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*"\%s"\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + +# typecasts on min/max could be min_t/max_t + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (defined $2 || defined $7) { + my $call = $1; + my $cast1 = deparenthesize($2); + my $arg1 = $3; + my $cast2 = deparenthesize($7); + my $arg2 = $8; + my $cast; + + if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { + $cast = "$cast1 or $cast2"; + } elsif ($cast1 ne "") { + $cast = $cast1; + } else { + $cast = $cast2; + } + WARN("MINMAX", + "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); + } + } + +# check usleep_range arguments + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { + my $min = $1; + my $max = $7; + if ($min eq $max) { + WARN("USLEEP_RANGE", + "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 function description of usleep_range().\n" . "$here\n$stat\n"); + } + } + +# check for naked sscanf + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/ && + ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && + $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && + $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + WARN("NAKED_SSCANF", + "unchecked sscanf return value\n" . "$here\n$stat_real\n"); + } + +# check for simple sscanf that should be kstrto<foo> + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { + my $format = $6; + my $count = $format =~ tr@%@%@; + if ($count == 1 && + $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { + WARN("SSCANF_TO_KSTRTO", + "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); + } + } + } + +# check for new externs in .h files. + if ($realfile =~ /\.h$/ && + $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { + if (CHK("AVOID_EXTERNS", + "extern prototypes should be avoided in .h files\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; + } + } + +# check for new externs in .c files. + if ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) + { + my $function_name = $1; + my $paren_space = $2; + + my $s = $stat; + if (defined $cond) { + substr($s, 0, length($cond), ''); + } + if ($s =~ /^\s*;/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + + if ($paren_space =~ /\n/) { + WARN("FUNCTION_ARGUMENTS", + "arguments for function declarations should follow identifier\n" . $herecurr); + } + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^\+extern struct\s+(\w+)\s+(\w+)\[\];/) + { + my ($st_type, $st_name) = ($1, $2); + + for my $s (keys %maybe_linker_symbol) { + #print "Linker symbol? $st_name : $s\n"; + goto LIKELY_LINKER_SYMBOL + if $st_name =~ /$s/; + } + WARN("AVOID_EXTERNS", + "found a file-scoped extern type:$st_type name:$st_name in .c file\n" + . "is this a linker symbol ?\n" . $herecurr); + LIKELY_LINKER_SYMBOL: + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*extern\s+/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + +# check for function declarations that have arguments without identifier names + if (defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && + $1 ne "void") { + my $args = trim($1); + while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { + my $arg = trim($1); + if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { + WARN("FUNCTION_ARGUMENTS", + "function definition argument '$arg' should also have an identifier name\n" . $herecurr); + } + } + } + +# check for function definitions + if ($perl_version_ok && + defined $stat && + $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { + $context_function = $1; + +# check for multiline function definition with misplaced open brace + my $ok = 0; + my $cnt = statement_rawlines($stat); + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + $ok = 1 if ($rl =~ /^[ \+]\{/); + $ok = 1 if ($rl =~ /\{/ && $n == 0); + last if $rl =~ /^[ \+].*\{/; + } + if (!$ok) { + ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herectx); + } + } + +# checks for new __setup's + if ($rawline =~ /\b__setup\("([^"]*)"/) { + my $name = $1; + + if (!grep(/$name/, @setup_docs)) { + CHK("UNDOCUMENTED_SETUP", + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); + } + } + +# check for pointless casting of alloc functions + if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { + WARN("UNNECESSARY_CASTS", + "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); + } + +# alloc style +# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + CHK("ALLOC_SIZEOF_STRUCT", + "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); + } + +# check for (kv|k)[mz]alloc that could be kmalloc_obj/kvmalloc_obj/kzalloc_obj/kvzalloc_obj + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $newfunc = "kmalloc_obj"; + $newfunc = "kvmalloc_obj" if ($oldfunc eq "kvmalloc"); + $newfunc = "kvzalloc_obj" if ($oldfunc eq "kvzalloc"); + $newfunc = "kzalloc_obj" if ($oldfunc eq "kzalloc"); + + if ($a1 =~ s/^sizeof\s*\S\(?([^\)]*)\)?$/$1/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_SIZEOF", + "Prefer $newfunc over $oldfunc with sizeof\n" . $herectx) && + $cnt == 1 && + $fix) { + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*,/$1 = $newfunc($a1,/; + } + } + } + + +# check for (kv|k)[mz]alloc with multiplies that could be kmalloc_objs/kvmalloc_objs/kzalloc_objs/kvzalloc_objs + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $a2 = $10; + my $newfunc = "kmalloc_objs"; + $newfunc = "kvmalloc_objs" if ($oldfunc eq "kvmalloc"); + $newfunc = "kvzalloc_objs" if ($oldfunc eq "kvzalloc"); + $newfunc = "kzalloc_objs" if ($oldfunc eq "kzalloc"); + my $r1 = $a1; + my $r2 = $a2; + if ($a1 =~ /^sizeof\s*\S/) { + $r1 = $a2; + $r2 = $a1; + } + if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && + !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_MULTIPLY", + "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && + $cnt == 1 && + $fix) { + my $sized = trim($r2); + $sized =~ s/^sizeof\s*\S\(?([^\)]*)\)?$/$1/; + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . $sized . ', ' . trim($r1)/e; + } + } + } + +# check for krealloc arg reuse + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && + $1 eq $3) { + WARN("KREALLOC_ARG_REUSE", + "Reusing the krealloc arg is almost always a bug\n" . $herecurr); + } + +# check for alloc argument mismatch + if ($line =~ /\b((?:devm_)?((?:k|kv)?(calloc|malloc_array)(?:_node)?))\s*\(\s*sizeof\b/) { + WARN("ALLOC_ARRAY_ARGS", + "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); + } + +# check for multiple semicolons + if ($line =~ /;\s*;\s*$/) { + if (WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; + } + } + +# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi + if ($realfile !~ m@^include/uapi/@ && + $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { + my $ull = ""; + $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); + if (CHK("BIT_MACRO", + "Prefer using the BIT$ull macro\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; + } + } + +# check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) + if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { + WARN("IS_ENABLED_CONFIG", + "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); + } + +# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + my $config = $1; + if (WARN("PREFER_IS_ENABLED", + "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; + } + } + +# check for /* fallthrough */ like comment, prefer fallthrough; + my @fallthroughs = ( + 'fallthrough', + '@fallthrough@', + 'lint -fallthrough[ \t]*', + 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', + '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', + 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + ); + if ($raw_comment ne '') { + foreach my $ft (@fallthroughs) { + if ($raw_comment =~ /$ft/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("PREFER_FALLTHROUGH", + "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); + last; + } + } + } + +# check for switch/default statements without a break; + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("DEFAULT_NO_BREAK", + "switch default: should use break\n" . $herectx); + } + +# check for gcc specific __FUNCTION__ + if ($line =~ /\b__FUNCTION__\b/) { + if (WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; + } + } + +# check for uses of __DATE__, __TIME__, __TIMESTAMP__ + while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { + ERROR("DATE_TIME", + "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); + } + +# check for use of yield() + if ($line =~ /\byield\s*\(\s*\)/) { + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); + } + +# check for comparisons against true and false + if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { + my $lead = $1; + my $arg = $2; + my $test = $3; + my $otype = $4; + my $trail = $5; + my $op = "!"; + + ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); + + my $type = lc($otype); + if ($type =~ /^(?:true|false)$/) { + if (("$test" eq "==" && "$type" eq "true") || + ("$test" eq "!=" && "$type" eq "false")) { + $op = ""; + } + + CHK("BOOL_COMPARISON", + "Using comparison to $otype is error prone\n" . $herecurr); + +## maybe suggesting a correct construct would better +## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); + + } + } + +# check for semaphores initialized locked + if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { + WARN("CONSIDER_COMPLETION", + "consider using a completion\n" . $herecurr); + } + +# recommend kstrto* over simple_strto* and strict_strto* + if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { + WARN("CONSIDER_KSTRTO", + "$1 is obsolete, use k$3 instead\n" . $herecurr); + } + +# check for __initcall(), use device_initcall() explicitly or more appropriate function please + if ($line =~ /^.\s*__initcall\s*\(/) { + WARN("USE_DEVICE_INITCALL", + "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); + } + +# check for spin_is_locked(), suggest lockdep instead + if ($line =~ /\bspin_is_locked\(/) { + WARN("USE_LOCKDEP", + "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); + } + +# check for deprecated apis + if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { + my $deprecated_api = $1; + my $new_api = $deprecated_apis{$deprecated_api}; + WARN("DEPRECATED_API", + "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); + } + +# check for various structs that are normally const (ops, kgdb, device_tree) +# and avoid what seem like struct definitions 'struct foo {' or forward declarations 'struct foo;' + if (defined($const_structs) && + $line !~ /\bconst\b/ && + $line =~ /\bstruct\s+($const_structs)\b(?!\s*[\{;])/) { + WARN("CONST_STRUCT", + "struct $1 should normally be const\n" . $herecurr); + } + +# use of NR_CPUS is usually wrong +# ignore definitions of NR_CPUS and usage to define arrays as likely right +# ignore designated initializers using NR_CPUS + if ($line =~ /\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && + $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) + { + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); + } + +# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. + if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { + ERROR("DEFINE_ARCH_HAS", + "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); + } + +# likely/unlikely comparisons similar to "(likely(foo) > 0)" + if ($perl_version_ok && + $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { + WARN("LIKELY_MISUSE", + "Using $1 should generally have parentheses around the comparison\n" . $herecurr); + } + +# return sysfs_emit(foo, fmt, ...) fmt without newline + if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && + substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { + my $offset = $+[6] - 1; + if (WARN("SYSFS_EMIT", + "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && + $fix) { + substr($fixed[$fixlinenr], $offset, 0) = '\\n'; + } + } + +# check for array definition/declarations that should use flexible arrays instead + if ($sline =~ /^[\+ ]\s*\}(?:\s*__packed)?\s*;\s*$/ && + $prevline =~ /^\+\s*(?:\}(?:\s*__packed\s*)?|$Type)\s*$Ident\s*\[\s*(0|1)\s*\]\s*;\s*$/) { + if (ERROR("FLEXIBLE_ARRAY", + "Use C99 flexible arrays - see https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays\n" . $hereprev) && + $1 == '0' && $fix) { + $fixed[$fixlinenr - 1] =~ s/\[\s*0\s*\]/[]/; + } + } + +# nested likely/unlikely calls + if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { + WARN("LIKELY_MISUSE", + "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); + } + +# whine mightly about in_atomic + if ($line =~ /\bin_atomic\s*\(/) { + if ($realfile =~ m@^drivers/@) { + ERROR("IN_ATOMIC", + "do not use in_atomic in drivers\n" . $herecurr); + } elsif ($realfile !~ m@^kernel/@) { + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); + } + } + +# Complain about RCU Tasks Trace used outside of BPF (and of course, RCU). + our $rcu_trace_funcs = qr{(?x: + rcu_read_lock_tasks_trace | + rcu_read_lock_trace | + rcu_read_lock_trace_held | + rcu_read_unlock_trace | + rcu_read_unlock_tasks_trace | + call_rcu_tasks_trace | + synchronize_rcu_tasks_trace | + rcu_barrier_tasks_trace | + rcu_tasks_trace_expedite_current | + rcu_request_urgent_qs_task + )}; + our $rcu_trace_paths = qr{(?x: + kernel/bpf/ | + include/linux/bpf | + net/bpf/ | + kernel/rcu/ | + include/linux/rcu + )}; + if ($line =~ /\b($rcu_trace_funcs)\s*\(/) { + if ($realfile !~ m{^$rcu_trace_paths}) { + WARN("RCU_TASKS_TRACE", + "use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr); + } + } + +# check for lockdep_set_novalidate_class + if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || + $line =~ /__lockdep_no_validate__\s*\)/ ) { + if ($realfile !~ m@^kernel/lockdep@ && + $realfile !~ m@^include/linux/lockdep@ && + $realfile !~ m@^drivers/base/core@) { + ERROR("LOCKDEP", + "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); + } + } + + if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || + $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { + WARN("EXPORTED_WORLD_WRITABLE", + "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); + } + +# check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> +# and whether or not function naming is typical and if +# DEVICE_ATTR permissions uses are unusual too + if ($perl_version_ok && + defined $stat && + $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { + my $var = $1; + my $perms = $2; + my $show = $3; + my $store = $4; + my $octal_perms = perms_to_octal($perms); + if ($show =~ /^${var}_show$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0644") { + if (WARN("DEVICE_ATTR_RW", + "Use DEVICE_ATTR_RW\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; + } + } elsif ($show =~ /^${var}_show$/ && + $store =~ /^NULL$/ && + $octal_perms eq "0444") { + if (WARN("DEVICE_ATTR_RO", + "Use DEVICE_ATTR_RO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; + } + } elsif ($show =~ /^NULL$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0200") { + if (WARN("DEVICE_ATTR_WO", + "Use DEVICE_ATTR_WO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; + } + } elsif ($octal_perms eq "0644" || + $octal_perms eq "0444" || + $octal_perms eq "0200") { + my $newshow = "$show"; + $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); + my $newstore = $store; + $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); + my $rename = ""; + if ($show ne $newshow) { + $rename .= " '$show' to '$newshow'"; + } + if ($store ne $newstore) { + $rename .= " '$store' to '$newstore'"; + } + WARN("DEVICE_ATTR_FUNCTIONS", + "Consider renaming function(s)$rename\n" . $herecurr); + } else { + WARN("DEVICE_ATTR_PERMS", + "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); + } + } + +# Mode permission misuses where it seems decimal should be octal +# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop +# o Ignore module_param*(...) uses with a decimal 0 permission as that has a +# specific definition of not visible in sysfs. +# o Ignore proc_create*(...) uses with a decimal 0 permission as that means +# use the default permissions + if ($perl_version_ok && + defined $stat && + $line =~ /$mode_perms_search/) { + foreach my $entry (@mode_permission_funcs) { + my $func = $entry->[0]; + my $arg_pos = $entry->[1]; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + + my $skip_args = ""; + if ($arg_pos > 1) { + $arg_pos--; + $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; + } + my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; + if ($stat =~ /$test/) { + my $val = $1; + $val = $6 if ($skip_args ne ""); + if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && + (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || + ($val =~ /^$Octal$/ && length($val) ne 4))) { + ERROR("NON_OCTAL_PERMISSIONS", + "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); + } + if ($val =~ /^$Octal$/ && (oct($val) & 02)) { + ERROR("EXPORTED_WORLD_WRITABLE", + "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); + } + } + } + } + +# check for uses of S_<PERMS> that could be octal for readability + while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { + my $oval = $1; + my $octal = perms_to_octal($oval); + if (WARN("SYMBOLIC_PERMS", + "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; + } + } + +# validate content of MODULE_LICENSE against list from include/linux/module.h + if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { + my $extracted_string = get_quoted_string($line, $rawline); + my $valid_licenses = qr{ + GPL| + GPL\ v2| + GPL\ and\ additional\ rights| + Dual\ BSD/GPL| + Dual\ MIT/GPL| + Dual\ MPL/GPL| + Proprietary + }x; + if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { + WARN("MODULE_LICENSE", + "unknown module license " . $extracted_string . "\n" . $herecurr); + } + if (!$file && $extracted_string eq '"GPL v2"') { + if (WARN("MODULE_LICENSE", + "Prefer \"GPL\" over \"GPL v2\" - see commit bf7fbeeae6db (\"module: Cure the MODULE_LICENSE \"GPL\" vs. \"GPL v2\" bogosity\")\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bMODULE_LICENSE\s*\(\s*"GPL v2"\s*\)/MODULE_LICENSE("GPL")/; + } + } + } + +# check for sysctl duplicate constants + if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { + WARN("DUPLICATED_SYSCTL_CONST", + "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); + } + +# Check that *_device_id tables have sentinel entries. + if (defined $stat && $line =~ /struct\s+$dev_id_types\s+\w+\s*\[\s*\]\s*=\s*\{/) { + my $stripped = $stat; + + # Strip diff line prefixes. + $stripped =~ s/(^|\n)./$1/g; + # Line continuations. + $stripped =~ s/\\\n/\n/g; + # Strip whitespace, empty strings, zeroes, and commas. + $stripped =~ s/""//g; + $stripped =~ s/0x0//g; + $stripped =~ s/[\s$;,0]//g; + # Strip field assignments. + $stripped =~ s/\.$Ident=//g; + + if (!(substr($stripped, -4) eq "{}};" || + substr($stripped, -6) eq "{{}}};" || + $stripped =~ /ISAPNP_DEVICE_SINGLE_END}};$/ || + $stripped =~ /ISAPNP_CARD_END}};$/ || + $stripped =~ /NULL};$/ || + $stripped =~ /PCMCIA_DEVICE_NULL};$/)) { + ERROR("MISSING_SENTINEL", "missing sentinel in ID array\n" . "$here\n$stat\n"); + } + } + +# check for uninitialized pointers with __free attribute + while ($line =~ /\*\s*($Ident)\s+__free\s*\(\s*$Ident\s*\)\s*[,;]/g) { + ERROR("UNINITIALIZED_PTR_WITH_FREE", + "pointer '$1' with __free attribute should be initialized\n" . $herecurr); + } + } + + # If we have no input at all, then there is nothing to report on + # so just keep quiet. + if ($#rawlines == -1) { + exit(0); + } + + # In mailback mode only produce a report in the negative, for + # things that appear to be patches. + if ($mailback && ($clean == 1 || !$is_patch)) { + exit(0); + } + + # This is not a patch, and we are in 'no-patch' mode so + # just keep quiet. + if (!$chk_patch && !$is_patch) { + exit(0); + } + + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { + 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", + "Missing Signed-off-by: line(s)\n"); + } elsif ($authorsignoff != 1) { + # authorsignoff values: + # 0 -> missing sign off + # 1 -> sign off identical + # 2 -> names and addresses match, comments mismatch + # 3 -> addresses match, names different + # 4 -> names match, addresses different + # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match + + my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; + + if ($authorsignoff == 0) { + ERROR("NO_AUTHOR_SIGN_OFF", + "Missing Signed-off-by: line by nominal patch author '$author'\n"); + } elsif ($authorsignoff == 2) { + CHK("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 3) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email name mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 4) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email address mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 5) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); + } + } + } + + print report_dump(); + if ($summary && !($clean == 1 && $quiet == 1)) { + print "$filename " if ($summary_file); + print "total: $cnt_error errors, $cnt_warn warnings, " . + (($check)? "$cnt_chk checks, " : "") . + "$cnt_lines lines checked\n"; + } + + if ($quiet == 0) { + # If there were any defects found and not already fixing them + if (!$clean and !$fix) { + print << "EOM" + +NOTE: For some of the reported defects, checkpatch may be able to + mechanically convert to the typical style using --fix or --fix-inplace. +EOM + } + # If there were whitespace errors which cleanpatch can fix + # then suggest that. + if ($rpt_cleaners) { + $rpt_cleaners = 0; + print << "EOM" + +NOTE: Whitespace errors detected. + You may wish to use scripts/cleanpatch or scripts/cleanfile +EOM + } + } + + if ($clean == 0 && $fix && + ("@rawlines" ne "@fixed" || + $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { + my $newfile = $filename; + $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); + my $linecount = 0; + my $f; + + @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); + + open($f, '>', $newfile) + or die "$P: Can't open $newfile for write\n"; + foreach my $fixed_line (@fixed) { + $linecount++; + if ($file) { + if ($linecount > 3) { + $fixed_line =~ s/^\+//; + print $f $fixed_line . "\n"; + } + } else { + print $f $fixed_line . "\n"; + } + } + close($f); + + if (!$quiet) { + print << "EOM"; + +Wrote EXPERIMENTAL --fix correction(s) to '$newfile' + +Do _NOT_ trust the results written to this file. +Do _NOT_ submit these changes without inspecting them for correctness. + +This EXPERIMENTAL file is simply a convenience to help rewrite patches. +No warranties, expressed or implied... +EOM + } + } + + if ($quiet == 0) { + print "\n"; + if ($clean == 1) { + print "$vname has no obvious style problems and is ready for submission.\n"; + } else { + print "$vname has style problems, please review.\n"; + } + } + return $clean; +} diff --git a/scripts/checkstack.pl b/scripts/checkstack.pl new file mode 100755 index 000000000..14ce31f73 --- /dev/null +++ b/scripts/checkstack.pl @@ -0,0 +1,208 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 + +# Check the stack usage of functions +# +# Copyright Joern Engel <joern@lazybastard.org> +# Inspired by Linus Torvalds +# Original idea maybe from Keith Owens +# s390 port and big speedup by Arnd Bergmann <arnd@bergmann-dalldorf.de> +# Mips port by Juan Quintela <quintela@mandrakesoft.com> +# Arm port by Holger Schurig +# Random bits by Matt Mackall <mpm@selenic.com> +# M68k port by Geert Uytterhoeven and Andreas Schwab +# AArch64, PARISC ports by Kyle McMartin +# sparc port by Martin Habets <errandir_news@mph.eclipse.co.uk> +# ppc64le port by Breno Leitao <leitao@debian.org> +# riscv port by Wadim Mueller <wafgo01@gmail.com> +# loongarch port by Youling Tang <tangyouling@kylinos.cn> +# +# Usage: +# objdump -d vmlinux | scripts/checkstack.pl [arch] [min_stack] +# +# TODO : Port to all architectures (one regex per arch) + +use strict; + +# check for arch +# +# $re is used for two matches: +# $& (whole re) matches the complete objdump line with the stack growth +# $1 (first bracket) matches the size of the stack growth +# +# $dre is similar, but for dynamic stack redutions: +# $& (whole re) matches the complete objdump line with the stack growth +# $1 (first bracket) matches the dynamic amount of the stack growth +# +# $sub: subroutine for special handling to check stack usage. +# +# use anything else and feel the pain ;) +my (@stack, $re, $dre, $sub, $x, $xs, $funcre, $min_stack); +{ + my $arch = shift; + if ($arch eq "") { + $arch = `uname -m`; + chomp($arch); + } + + $min_stack = shift; + if ($min_stack eq "" || $min_stack !~ /^\d+$/) { + $min_stack = 512; + } + + $x = "[0-9a-f]"; # hex character + $xs = "[0-9a-f ]"; # hex character or space + $funcre = qr/^$x* <(.*)>:$/; + if ($arch =~ '^(aarch|arm)64$') { + #ffffffc0006325cc: a9bb7bfd stp x29, x30, [sp, #-80]! + #a110: d11643ff sub sp, sp, #0x590 + $re = qr/^.*stp.*sp, ?\#-([0-9]{1,8})\]\!/o; + $dre = qr/^.*sub.*sp, sp, #(0x$x{1,8})/o; + } elsif ($arch eq 'arm') { + #c0008ffc: e24dd064 sub sp, sp, #100 ; 0x64 + $re = qr/.*sub.*sp, sp, #([0-9]{1,4})/o; + $sub = \&arm_push_handling; + } elsif ($arch =~ /^x86(_64)?$/ || $arch =~ /^i[3456]86$/) { + #c0105234: 81 ec ac 05 00 00 sub $0x5ac,%esp + # or + # 2f60: 48 81 ec e8 05 00 00 sub $0x5e8,%rsp + $re = qr/^.*[as][du][db] \$(0x$x{1,8}),\%(e|r)sp$/o; + $dre = qr/^.*[as][du][db] (%.*),\%(e|r)sp$/o; + } elsif ($arch eq 'm68k') { + # 2b6c: 4e56 fb70 linkw %fp,#-1168 + # 1df770: defc ffe4 addaw #-28,%sp + $re = qr/.*(?:linkw %fp,|addaw )#-([0-9]{1,4})(?:,%sp)?$/o; + } elsif ($arch eq 'mips64') { + #8800402c: 67bdfff0 daddiu sp,sp,-16 + $re = qr/.*daddiu.*sp,sp,-([0-9]{1,8})/o; + } elsif ($arch eq 'mips') { + #88003254: 27bdffe0 addiu sp,sp,-32 + $re = qr/.*addiu.*sp,sp,-([0-9]{1,8})/o; + } elsif ($arch eq 'nios2') { + #25a8: defffb04 addi sp,sp,-20 + $re = qr/.*addi.*sp,sp,-([0-9]{1,8})/o; + } elsif ($arch eq 'openrisc') { + # c000043c: 9c 21 fe f0 l.addi r1,r1,-272 + $re = qr/.*l\.addi.*r1,r1,-([0-9]{1,8})/o; + } elsif ($arch eq 'parisc' || $arch eq 'parisc64') { + $re = qr/.*ldo ($x{1,8})\(sp\),sp/o; + } elsif ($arch eq 'powerpc' || $arch =~ /^ppc(64)?(le)?$/ ) { + # powerpc : 94 21 ff 30 stwu r1,-208(r1) + # ppc64(le) : 81 ff 21 f8 stdu r1,-128(r1) + $re = qr/.*st[dw]u.*r1,-($x{1,8})\(r1\)/o; + } elsif ($arch =~ /^s390x?$/) { + # 11160: a7 fb ff 60 aghi %r15,-160 + # or + # 100092: e3 f0 ff c8 ff 71 lay %r15,-56(%r15) + $re = qr/.*(?:lay|ag?hi).*\%r15,-([0-9]+)(?:\(\%r15\))?$/o; + } elsif ($arch eq 'sparc' || $arch eq 'sparc64') { + # f0019d10: 9d e3 bf 90 save %sp, -112, %sp + $re = qr/.*save.*%sp, -([0-9]{1,8}), %sp/o; + } elsif ($arch =~ /^riscv(64)?$/) { + #ffffffff8036e868: c2010113 addi sp,sp,-992 + $re = qr/.*addi.*sp,sp,-([0-9]{1,8})/o; + } elsif ($arch =~ /^loongarch(32|64)?$/) { + #9000000000224708: 02ff4063 addi.d $sp, $sp, -48(0xfd0) + $re = qr/.*addi\..*sp, .*sp, -([0-9]{1,8}).*/o; + } else { + print("wrong or unknown architecture \"$arch\"\n"); + exit + } +} + +# +# To count stack usage of push {*, fp, ip, lr, pc} instruction in ARM, +# if FRAME POINTER is enabled. +# e.g. c01f0d48: e92ddff0 push {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc} +# +sub arm_push_handling { + my $regex = qr/.*push.*fp, ip, lr, pc}/o; + my $size = 0; + my $line_arg = shift; + + if ($line_arg =~ m/$regex/) { + $size = $line_arg =~ tr/,//; + $size = ($size + 1) * 4; + } + + return $size; +} + +# +# main() +# +my ($func, $file, $lastslash, $total_size, $addr, $intro); + +$total_size = 0; + +while (my $line = <STDIN>) { + if ($line =~ m/$funcre/) { + $func = $1; + next if $line !~ m/^($x*)/; + if ($total_size > $min_stack) { + push @stack, "$intro$total_size\n"; + } + $addr = "0x$1"; + $intro = "$addr $func [$file]:"; + my $padlen = 56 - length($intro); + while ($padlen > 0) { + $intro .= ' '; + $padlen -= 8; + } + + $total_size = 0; + } + elsif ($line =~ m/(.*):\s*file format/) { + $file = $1; + $file =~ s/\.ko//; + $lastslash = rindex($file, "/"); + if ($lastslash != -1) { + $file = substr($file, $lastslash + 1); + } + } + elsif ($line =~ m/$re/) { + my $size = $1; + $size = hex($size) if ($size =~ /^0x/); + + if ($size > 0xf0000000) { + $size = - $size; + $size += 0x80000000; + $size += 0x80000000; + } + next if ($size > 0x10000000); + + $total_size += $size; + } + elsif (defined $dre && $line =~ m/$dre/) { + my $size = $1; + + $size = hex($size) if ($size =~ /^0x/); + $total_size += $size; + } + elsif (defined $sub) { + my $size = &$sub($line); + + $total_size += $size; + } +} +if ($total_size > $min_stack) { + push @stack, "$intro$total_size\n"; +} + +# Sort output by size (last field) and function name if size is the same +sub sort_lines { + my ($a, $b) = @_; + + my $num_a = $1 if $a =~ /:\t*(\d+)$/; + my $num_b = $1 if $b =~ /:\t*(\d+)$/; + my $func_a = $1 if $a =~ / (.*):/; + my $func_b = $1 if $b =~ / (.*):/; + + if ($num_a != $num_b) { + return $num_b <=> $num_a; + } else { + return $func_a cmp $func_b; + } +} + +print sort { sort_lines($a, $b) } @stack; diff --git a/scripts/checksyscalls.sh b/scripts/checksyscalls.sh new file mode 100755 index 000000000..e2970421c --- /dev/null +++ b/scripts/checksyscalls.sh @@ -0,0 +1,282 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Check if current architecture are missing any function calls compared +# to i386. +# i386 define a number of legacy system calls that are i386 specific +# and listed below so they are ignored. +# +# Usage: +# checksyscalls.sh gcc gcc-options +# + +set -e + +reference_table="$(dirname $0)/../arch/x86/entry/syscalls/syscall_32.tbl" + +ignore_list() { +cat << EOF +#include <asm/types.h> +#include <asm/unistd.h> + +/* *at */ +#define __IGNORE_open /* openat */ +#define __IGNORE_link /* linkat */ +#define __IGNORE_unlink /* unlinkat */ +#define __IGNORE_mknod /* mknodat */ +#define __IGNORE_chmod /* fchmodat */ +#define __IGNORE_chown /* fchownat */ +#define __IGNORE_mkdir /* mkdirat */ +#define __IGNORE_rmdir /* unlinkat */ +#define __IGNORE_lchown /* fchownat */ +#define __IGNORE_access /* faccessat */ +#define __IGNORE_rename /* renameat2 */ +#define __IGNORE_readlink /* readlinkat */ +#define __IGNORE_symlink /* symlinkat */ +#define __IGNORE_utimes /* futimesat */ +#define __IGNORE_stat /* fstatat */ +#define __IGNORE_lstat /* fstatat */ +#define __IGNORE_stat64 /* fstatat64 */ +#define __IGNORE_lstat64 /* fstatat64 */ + +#ifndef __ARCH_WANT_SET_GET_RLIMIT +#define __IGNORE_getrlimit /* getrlimit */ +#define __IGNORE_setrlimit /* setrlimit */ +#endif + +#ifndef __ARCH_WANT_MEMFD_SECRET +#define __IGNORE_memfd_secret +#endif + +/* Missing flags argument */ +#define __IGNORE_renameat /* renameat2 */ + +/* CLOEXEC flag */ +#define __IGNORE_pipe /* pipe2 */ +#define __IGNORE_dup2 /* dup3 */ +#define __IGNORE_epoll_create /* epoll_create1 */ +#define __IGNORE_inotify_init /* inotify_init1 */ +#define __IGNORE_eventfd /* eventfd2 */ +#define __IGNORE_signalfd /* signalfd4 */ + +/* MMU */ +#ifndef CONFIG_MMU +#define __IGNORE_madvise +#define __IGNORE_mbind +#define __IGNORE_mincore +#define __IGNORE_mlock +#define __IGNORE_mlockall +#define __IGNORE_munlock +#define __IGNORE_munlockall +#define __IGNORE_mprotect +#define __IGNORE_msync +#define __IGNORE_migrate_pages +#define __IGNORE_move_pages +#define __IGNORE_remap_file_pages +#define __IGNORE_get_mempolicy +#define __IGNORE_set_mempolicy +#define __IGNORE_swapoff +#define __IGNORE_swapon +#endif + +/* System calls for 32-bit kernels only */ +#if BITS_PER_LONG == 64 +#define __IGNORE_sendfile64 +#define __IGNORE_ftruncate64 +#define __IGNORE_truncate64 +#define __IGNORE_stat64 +#define __IGNORE_lstat64 +#define __IGNORE_fcntl64 +#define __IGNORE_fadvise64_64 +#define __IGNORE_fstatfs64 +#define __IGNORE_statfs64 +#define __IGNORE_llseek +#define __IGNORE_mmap2 +#define __IGNORE_clock_gettime64 +#define __IGNORE_clock_settime64 +#define __IGNORE_clock_adjtime64 +#define __IGNORE_clock_getres_time64 +#define __IGNORE_clock_nanosleep_time64 +#define __IGNORE_timer_gettime64 +#define __IGNORE_timer_settime64 +#define __IGNORE_timerfd_gettime64 +#define __IGNORE_timerfd_settime64 +#define __IGNORE_utimensat_time64 +#define __IGNORE_pselect6_time64 +#define __IGNORE_ppoll_time64 +#define __IGNORE_io_pgetevents_time64 +#define __IGNORE_recvmmsg_time64 +#define __IGNORE_mq_timedsend_time64 +#define __IGNORE_mq_timedreceive_time64 +#define __IGNORE_semtimedop_time64 +#define __IGNORE_rt_sigtimedwait_time64 +#define __IGNORE_futex_time64 +#define __IGNORE_sched_rr_get_interval_time64 +#else +#define __IGNORE_sendfile +#define __IGNORE_ftruncate +#define __IGNORE_truncate +#define __IGNORE_stat +#define __IGNORE_lstat +#define __IGNORE_fcntl +#define __IGNORE_fadvise64 +#define __IGNORE_newfstatat +#define __IGNORE_fstatfs +#define __IGNORE_statfs +#define __IGNORE_lseek +#define __IGNORE_mmap +#define __IGNORE_clock_gettime +#define __IGNORE_clock_settime +#define __IGNORE_clock_adjtime +#define __IGNORE_clock_getres +#define __IGNORE_clock_nanosleep +#define __IGNORE_timer_gettime +#define __IGNORE_timer_settime +#define __IGNORE_timerfd_gettime +#define __IGNORE_timerfd_settime +#define __IGNORE_utimensat +#define __IGNORE_pselect6 +#define __IGNORE_ppoll +#define __IGNORE_io_pgetevents +#define __IGNORE_recvmmsg +#define __IGNORE_mq_timedsend +#define __IGNORE_mq_timedreceive +#define __IGNORE_semtimedop +#define __IGNORE_rt_sigtimedwait +#define __IGNORE_futex +#define __IGNORE_sched_rr_get_interval +#define __IGNORE_gettimeofday +#define __IGNORE_settimeofday +#define __IGNORE_wait4 +#define __IGNORE_adjtimex +#define __IGNORE_nanosleep +#define __IGNORE_io_getevents +#define __IGNORE_recvmmsg +#endif + +/* i386-specific or historical system calls */ +#define __IGNORE_break +#define __IGNORE_stty +#define __IGNORE_gtty +#define __IGNORE_ftime +#define __IGNORE_prof +#define __IGNORE_lock +#define __IGNORE_mpx +#define __IGNORE_ulimit +#define __IGNORE_profil +#define __IGNORE_ioperm +#define __IGNORE_iopl +#define __IGNORE_idle +#define __IGNORE_modify_ldt +#define __IGNORE_ugetrlimit +#define __IGNORE_vm86 +#define __IGNORE_vm86old +#define __IGNORE_set_thread_area +#define __IGNORE_get_thread_area +#define __IGNORE_madvise1 +#define __IGNORE_oldstat +#define __IGNORE_oldfstat +#define __IGNORE_oldlstat +#define __IGNORE_oldolduname +#define __IGNORE_olduname +#define __IGNORE_umount +#define __IGNORE_waitpid +#define __IGNORE_stime +#define __IGNORE_nice +#define __IGNORE_signal +#define __IGNORE_sigaction +#define __IGNORE_sgetmask +#define __IGNORE_sigsuspend +#define __IGNORE_sigpending +#define __IGNORE_ssetmask +#define __IGNORE_readdir +#define __IGNORE_socketcall +#define __IGNORE_ipc +#define __IGNORE_sigreturn +#define __IGNORE_sigprocmask +#define __IGNORE_bdflush +#define __IGNORE__llseek +#define __IGNORE__newselect +#define __IGNORE_create_module +#define __IGNORE_query_module +#define __IGNORE_get_kernel_syms +#define __IGNORE_sysfs +#define __IGNORE_uselib +#define __IGNORE__sysctl +#define __IGNORE_arch_prctl +#define __IGNORE_nfsservctl + +/* ... including the "new" 32-bit uid syscalls */ +#define __IGNORE_lchown32 +#define __IGNORE_getuid32 +#define __IGNORE_getgid32 +#define __IGNORE_geteuid32 +#define __IGNORE_getegid32 +#define __IGNORE_setreuid32 +#define __IGNORE_setregid32 +#define __IGNORE_getgroups32 +#define __IGNORE_setgroups32 +#define __IGNORE_fchown32 +#define __IGNORE_setresuid32 +#define __IGNORE_getresuid32 +#define __IGNORE_setresgid32 +#define __IGNORE_getresgid32 +#define __IGNORE_chown32 +#define __IGNORE_setuid32 +#define __IGNORE_setgid32 +#define __IGNORE_setfsuid32 +#define __IGNORE_setfsgid32 + +/* these can be expressed using other calls */ +#define __IGNORE_alarm /* setitimer */ +#define __IGNORE_creat /* open */ +#define __IGNORE_fork /* clone */ +#define __IGNORE_futimesat /* utimensat */ +#define __IGNORE_getpgrp /* getpgid */ +#define __IGNORE_getdents /* getdents64 */ +#define __IGNORE_pause /* sigsuspend */ +#define __IGNORE_poll /* ppoll */ +#define __IGNORE_select /* pselect6 */ +#define __IGNORE_epoll_wait /* epoll_pwait */ +#define __IGNORE_time /* gettimeofday */ +#define __IGNORE_uname /* newuname */ +#define __IGNORE_ustat /* statfs */ +#define __IGNORE_utime /* utimes */ +#define __IGNORE_vfork /* clone */ + +/* sync_file_range had a stupid ABI. Allow sync_file_range2 instead */ +#ifdef __NR_sync_file_range2 +#define __IGNORE_sync_file_range +#endif + +/* Unmerged syscalls for AFS, STREAMS, etc. */ +#define __IGNORE_afs_syscall +#define __IGNORE_getpmsg +#define __IGNORE_putpmsg +#define __IGNORE_vserver + +/* 64-bit ports never needed these, and new 32-bit ports can use statx */ +#define __IGNORE_fstat64 +#define __IGNORE_fstatat64 + +/* Newer ports are not required to provide fstat in favor of statx */ +#define __IGNORE_fstat +EOF +} + +syscall_list() { + grep '^[0-9]' "$1" | sort -n | + while read nr abi name entry ; do + echo "#if !defined(__NR_${name}) && !defined(__IGNORE_${name})" + echo "#warning syscall ${name} not implemented" + echo "#endif" + done +} + +(ignore_list && syscall_list ${reference_table}) | \ +$* -Wno-error -Wno-unused-macros -E -x c - > /dev/null + +# For fixdep +if [ -n "${DEPFILE}" ]; then + echo "${0}: ${0} ${reference_table}" >> "${DEPFILE}" +fi diff --git a/scripts/checkversion.pl b/scripts/checkversion.pl new file mode 100755 index 000000000..94cd49eff --- /dev/null +++ b/scripts/checkversion.pl @@ -0,0 +1,76 @@ +#! /usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# checkversion finds uses of all macros in <linux/version.h> +# where the source files do not #include <linux/version.h>; or cases +# of including <linux/version.h> where it is not needed. +# Copyright (C) 2003, Randy Dunlap <rdunlap@infradead.org> + +use strict; + +$| = 1; + +my $debugging; + +foreach my $file (@ARGV) { + next if $file =~ "include/generated/uapi/linux/version\.h"; + next if $file =~ "usr/include/linux/version\.h"; + # Open this file. + open( my $f, '<', $file ) + or die "Can't open $file: $!\n"; + + # Initialize variables. + my ($fInComment, $fInString, $fUseVersion); + my $iLinuxVersion = 0; + + while (<$f>) { + # Strip comments. + $fInComment && (s+^.*?\*/+ +o ? ($fInComment = 0) : next); + m+/\*+o && (s+/\*.*?\*/+ +go, (s+/\*.*$+ +o && ($fInComment = 1))); + + # Pick up definitions. + if ( m/^\s*#/o ) { + $iLinuxVersion = $. if m/^\s*#\s*include\s*"linux\/version\.h"/o; + } + + # Strip strings. + $fInString && (s+^.*?"+ +o ? ($fInString = 0) : next); + m+"+o && (s+".*?"+ +go, (s+".*$+ +o && ($fInString = 1))); + + # Pick up definitions. + if ( m/^\s*#/o ) { + $iLinuxVersion = $. if m/^\s*#\s*include\s*<linux\/version\.h>/o; + } + + # Look for uses: LINUX_VERSION_CODE, KERNEL_VERSION, + # LINUX_VERSION_MAJOR, LINUX_VERSION_PATCHLEVEL, LINUX_VERSION_SUBLEVEL + if (($_ =~ /LINUX_VERSION_CODE/) || ($_ =~ /\WKERNEL_VERSION/) || + ($_ =~ /LINUX_VERSION_MAJOR/) || ($_ =~ /LINUX_VERSION_PATCHLEVEL/) || + ($_ =~ /LINUX_VERSION_SUBLEVEL/)) { + $fUseVersion = 1; + last if $iLinuxVersion; + } + } + + # Report used version IDs without include? + if ($fUseVersion && ! $iLinuxVersion) { + print "$file: $.: need linux/version.h\n"; + } + + # Report superfluous includes. + if ($iLinuxVersion && ! $fUseVersion) { + print "$file: $iLinuxVersion linux/version.h not needed.\n"; + } + + # debug: report OK results: + if ($debugging) { + if ($iLinuxVersion && $fUseVersion) { + print "$file: version use is OK ($iLinuxVersion)\n"; + } + if (! $iLinuxVersion && ! $fUseVersion) { + print "$file: version use is OK (none)\n"; + } + } + + close($f); +} diff --git a/scripts/clang-tools/gen_compile_commands.py b/scripts/clang-tools/gen_compile_commands.py new file mode 100755 index 000000000..8d14b81ef --- /dev/null +++ b/scripts/clang-tools/gen_compile_commands.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) Google LLC, 2018 +# +# Author: Tom Roeder <tmroeder@google.com> +# +"""A tool for generating compile_commands.json in the Linux kernel.""" + +import argparse +import json +import logging +import os +import re +import subprocess +import sys + +_DEFAULT_OUTPUT = 'compile_commands.json' +_DEFAULT_LOG_LEVEL = 'WARNING' + +_FILENAME_PATTERN = r'^\..*\.cmd$' +_LINE_PATTERN = r'^(saved)?cmd_[^ ]*\.o := (?P<command_prefix>.* )(?P<file_path>[^ ]*\.[cS]) *(;|$)' +_VALID_LOG_LEVELS = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] +# The tools/ directory adopts a different build system, and produces .cmd +# files in a different format. Do not support it. +_EXCLUDE_DIRS = ['.git', 'Documentation', 'include', 'tools'] + +def parse_arguments(): + """Sets up and parses command-line arguments. + + Returns: + log_level: A logging level to filter log output. + directory: The work directory where the objects were built. + ar: Command used for parsing .a archives. + output: Where to write the compile-commands JSON file. + paths: The list of files/directories to handle to find .cmd files. + """ + usage = 'Creates a compile_commands.json database from kernel .cmd files' + parser = argparse.ArgumentParser(description=usage) + + directory_help = ('specify the output directory used for the kernel build ' + '(defaults to the working directory)') + parser.add_argument('-d', '--directory', type=str, default='.', + help=directory_help) + + output_help = ('path to the output command database (defaults to ' + + _DEFAULT_OUTPUT + ')') + parser.add_argument('-o', '--output', type=str, default=_DEFAULT_OUTPUT, + help=output_help) + + log_level_help = ('the level of log messages to produce (defaults to ' + + _DEFAULT_LOG_LEVEL + ')') + parser.add_argument('--log_level', choices=_VALID_LOG_LEVELS, + default=_DEFAULT_LOG_LEVEL, help=log_level_help) + + ar_help = 'command used for parsing .a archives' + parser.add_argument('-a', '--ar', type=str, default='llvm-ar', help=ar_help) + + paths_help = ('directories to search or files to parse ' + '(files should be *.o, *.a, or modules.order). ' + 'If nothing is specified, the current directory is searched') + parser.add_argument('paths', type=str, nargs='*', help=paths_help) + + args = parser.parse_args() + + return (args.log_level, + os.path.realpath(args.directory), + args.output, + args.ar, + args.paths if len(args.paths) > 0 else [args.directory]) + + +def cmdfiles_in_dir(directory): + """Generate the iterator of .cmd files found under the directory. + + Walk under the given directory, and yield every .cmd file found. + + Args: + directory: The directory to search for .cmd files. + + Yields: + The path to a .cmd file. + """ + + filename_matcher = re.compile(_FILENAME_PATTERN) + exclude_dirs = [ os.path.join(directory, d) for d in _EXCLUDE_DIRS ] + + for dirpath, dirnames, filenames in os.walk(directory, topdown=True): + # Prune unwanted directories. + if dirpath in exclude_dirs: + dirnames[:] = [] + continue + + for filename in filenames: + if filename_matcher.match(filename): + yield os.path.join(dirpath, filename) + + +def to_cmdfile(path): + """Return the path of .cmd file used for the given build artifact + + Args: + Path: file path + + Returns: + The path to .cmd file + """ + dir, base = os.path.split(path) + return os.path.join(dir, '.' + base + '.cmd') + + +def cmdfiles_for_a(archive, ar): + """Generate the iterator of .cmd files associated with the archive. + + Parse the given archive, and yield every .cmd file used to build it. + + Args: + archive: The archive to parse + + Yields: + The path to every .cmd file found + """ + for obj in subprocess.check_output([ar, '-t', archive]).decode().split(): + yield to_cmdfile(obj) + + +def cmdfiles_for_modorder(modorder): + """Generate the iterator of .cmd files associated with the modules.order. + + Parse the given modules.order, and yield every .cmd file used to build the + contained modules. + + Args: + modorder: The modules.order file to parse + + Yields: + The path to every .cmd file found + """ + with open(modorder) as f: + for line in f: + obj = line.rstrip() + base, ext = os.path.splitext(obj) + if ext != '.o': + sys.exit('{}: module path must end with .o'.format(obj)) + mod = base + '.mod' + # Read from *.mod, to get a list of objects that compose the module. + with open(mod) as m: + for mod_line in m: + yield to_cmdfile(mod_line.rstrip()) + + +def process_line(root_directory, command_prefix, file_path): + """Extracts information from a .cmd line and creates an entry from it. + + Args: + root_directory: The directory that was searched for .cmd files. Usually + used directly in the "directory" entry in compile_commands.json. + command_prefix: The extracted command line, up to the last element. + file_path: The .c file from the end of the extracted command. + Usually relative to root_directory, but sometimes absolute. + + Returns: + An entry to append to compile_commands. + + Raises: + ValueError: Could not find the extracted file based on file_path and + root_directory or file_directory. + """ + # The .cmd files are intended to be included directly by Make, so they + # 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)) + if not os.path.exists(abs_path): + raise ValueError('File %s not found' % abs_path) + return { + 'directory': root_directory, + 'file': abs_path, + 'command': prefix + file_path, + } + + +def main(): + """Walks through the directory and finds and parses .cmd files.""" + log_level, directory, output, ar, paths = parse_arguments() + + level = getattr(logging, log_level) + logging.basicConfig(format='%(levelname)s: %(message)s', level=level) + + line_matcher = re.compile(_LINE_PATTERN) + + compile_commands = [] + + for path in paths: + # If 'path' is a directory, handle all .cmd files under it. + # Otherwise, handle .cmd files associated with the file. + # built-in objects are linked via vmlinux.a + # Modules are listed in modules.order. + if os.path.isdir(path): + cmdfiles = cmdfiles_in_dir(path) + elif os.path.basename(path) == 'libgcc.a': + cmdfiles = [] + elif path.endswith('.a'): + cmdfiles = cmdfiles_for_a(path, ar) + elif path.endswith('modules.order'): + cmdfiles = cmdfiles_for_modorder(path) + else: + sys.exit('{}: unknown file type'.format(path)) + + for cmdfile in cmdfiles: + with open(cmdfile, 'rt') as f: + result = line_matcher.match(f.readline()) + if result: + try: + entry = process_line(directory, result.group('command_prefix'), + result.group('file_path')) + compile_commands.append(entry) + except ValueError as err: + logging.info('Could not add line from %s: %s', + cmdfile, err) + + with open(output, 'wt') as f: + json.dump(sorted(compile_commands, key=lambda x: x["file"]), f, indent=2, sort_keys=True) + + +if __name__ == '__main__': + main() diff --git a/scripts/clang-tools/run-clang-tools.py b/scripts/clang-tools/run-clang-tools.py new file mode 100755 index 000000000..e78be82aa --- /dev/null +++ b/scripts/clang-tools/run-clang-tools.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) Google LLC, 2020 +# +# Author: Nathan Huckleberry <nhuck@google.com> +# +"""A helper routine run clang-tidy and the clang static-analyzer on +compile_commands.json. +""" + +import argparse +import json +import multiprocessing +import subprocess +import sys + + +def parse_arguments(): + """Set up and parses command-line arguments. + Returns: + args: Dict of parsed args + Has keys: [path, type] + """ + usage = """Run clang-tidy or the clang static-analyzer on a + compilation database.""" + parser = argparse.ArgumentParser(description=usage) + + type_help = "Type of analysis to be performed" + parser.add_argument("type", + choices=["clang-tidy", "clang-analyzer"], + help=type_help) + path_help = "Path to the compilation database to parse" + parser.add_argument("path", type=str, help=path_help) + + checks_help = "Checks to pass to the analysis" + parser.add_argument("-checks", type=str, default=None, help=checks_help) + header_filter_help = "Pass the -header-filter value to the tool" + parser.add_argument("-header-filter", type=str, default=None, help=header_filter_help) + + return parser.parse_args() + + +def init(l, a): + global lock + global args + lock = l + args = a + + +def run_analysis(entry): + # Disable all checks, then re-enable the ones we want + global args + checks = None + if args.checks: + checks = args.checks.split(',') + else: + checks = ["-*"] + if args.type == "clang-tidy": + checks.append("linuxkernel-*") + else: + checks.append("clang-analyzer-*") + checks.append("-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling") + file = entry["file"] + if not file.endswith(".c") and not file.endswith(".cpp"): + with lock: + print(f"Skipping non-C file: '{file}'", file=sys.stderr) + return + pargs = ["clang-tidy", "-p", args.path, "-checks=" + ",".join(checks)] + if args.header_filter: + pargs.append("-header-filter=" + args.header_filter) + pargs.append(file) + p = subprocess.run(pargs, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=entry["directory"]) + with lock: + sys.stderr.buffer.write(p.stdout) + + +def main(): + args = parse_arguments() + + # Read JSON data into the datastore variable + with open(args.path) as f: + datastore = json.load(f) + + lock = multiprocessing.Lock() + try: + with multiprocessing.Pool(initializer=init, initargs=(lock, args)) as pool: + pool.map(run_analysis, datastore) + except BrokenPipeError: + # Python flushes standard streams on exit; redirect remaining output + # to devnull to avoid another BrokenPipeError at shutdown + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + sys.exit(1) # Python exits with error code 1 on EPIPE + + +if __name__ == "__main__": + main() diff --git a/scripts/cleanfile b/scripts/cleanfile new file mode 100755 index 000000000..c00c69b91 --- /dev/null +++ b/scripts/cleanfile @@ -0,0 +1,178 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# Clean a text file -- or directory of text files -- of stealth whitespace. +# WARNING: this can be a highly destructive operation. Use with caution. +# + +use warnings; +use bytes; +use File::Basename; + +# Default options +$max_width = 79; + +# Clean up space-tab sequences, either by removing spaces or +# replacing them with tabs. +sub clean_space_tabs($) +{ + no bytes; # Tab alignment depends on characters + + my($li) = @_; + my($lo) = ''; + my $pos = 0; + my $nsp = 0; + my($i, $c); + + for ($i = 0; $i < length($li); $i++) { + $c = substr($li, $i, 1); + if ($c eq "\t") { + my $npos = ($pos+$nsp+8) & ~7; + my $ntab = ($npos >> 3) - ($pos >> 3); + $lo .= "\t" x $ntab; + $pos = $npos; + $nsp = 0; + } elsif ($c eq "\n" || $c eq "\r") { + $lo .= " " x $nsp; + $pos += $nsp; + $nsp = 0; + $lo .= $c; + $pos = 0; + } elsif ($c eq " ") { + $nsp++; + } else { + $lo .= " " x $nsp; + $pos += $nsp; + $nsp = 0; + $lo .= $c; + $pos++; + } + } + $lo .= " " x $nsp; + return $lo; +} + +# Compute the visual width of a string +sub strwidth($) { + no bytes; # Tab alignment depends on characters + + my($li) = @_; + my($c, $i); + my $pos = 0; + my $mlen = 0; + + for ($i = 0; $i < length($li); $i++) { + $c = substr($li,$i,1); + if ($c eq "\t") { + $pos = ($pos+8) & ~7; + } elsif ($c eq "\n") { + $mlen = $pos if ($pos > $mlen); + $pos = 0; + } else { + $pos++; + } + } + + $mlen = $pos if ($pos > $mlen); + return $mlen; +} + +$name = basename($0); + +@files = (); + +while (defined($a = shift(@ARGV))) { + if ($a =~ /^-/) { + if ($a eq '-width' || $a eq '-w') { + $max_width = shift(@ARGV)+0; + } else { + print STDERR "Usage: $name [-width #] files...\n"; + exit 1; + } + } else { + push(@files, $a); + } +} + +foreach $f ( @files ) { + print STDERR "$name: $f\n"; + + if (! -f $f) { + print STDERR "$f: not a file\n"; + next; + } + + if (!open(FILE, '+<', $f)) { + print STDERR "$name: Cannot open file: $f: $!\n"; + next; + } + + binmode FILE; + + # First, verify that it is not a binary file; consider any file + # with a zero byte to be a binary file. Is there any better, or + # additional, heuristic that should be applied? + $is_binary = 0; + + while (read(FILE, $data, 65536) > 0) { + if ($data =~ /\0/) { + $is_binary = 1; + last; + } + } + + if ($is_binary) { + print STDERR "$name: $f: binary file\n"; + next; + } + + seek(FILE, 0, 0); + + $in_bytes = 0; + $out_bytes = 0; + $blank_bytes = 0; + + @blanks = (); + @lines = (); + $lineno = 0; + + while ( defined($line = <FILE>) ) { + $lineno++; + $in_bytes += length($line); + $line =~ s/[ \t\r]*$//; # Remove trailing spaces + $line = clean_space_tabs($line); + + if ( $line eq "\n" ) { + push(@blanks, $line); + $blank_bytes += length($line); + } else { + push(@lines, @blanks); + $out_bytes += $blank_bytes; + push(@lines, $line); + $out_bytes += length($line); + @blanks = (); + $blank_bytes = 0; + } + + $l_width = strwidth($line); + if ($max_width && $l_width > $max_width) { + print STDERR + "$f:$lineno: line exceeds $max_width characters ($l_width)\n"; + } + } + + # Any blanks at the end of the file are discarded + + if ($in_bytes != $out_bytes) { + # Only write to the file if changed + seek(FILE, 0, 0); + print FILE @lines; + + if ( !defined($where = tell(FILE)) || + !truncate(FILE, $where) ) { + die "$name: Failed to truncate modified file: $f: $!\n"; + } + } + + close(FILE); +} diff --git a/scripts/cleanpatch b/scripts/cleanpatch new file mode 100755 index 000000000..9f1755271 --- /dev/null +++ b/scripts/cleanpatch @@ -0,0 +1,260 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# Clean a patch file -- or directory of patch files -- of stealth whitespace. +# WARNING: this can be a highly destructive operation. Use with caution. +# + +use warnings; +use bytes; +use File::Basename; + +# Default options +$max_width = 79; + +# Clean up space-tab sequences, either by removing spaces or +# replacing them with tabs. +sub clean_space_tabs($) +{ + no bytes; # Tab alignment depends on characters + + my($li) = @_; + my($lo) = ''; + my $pos = 0; + my $nsp = 0; + my($i, $c); + + for ($i = 0; $i < length($li); $i++) { + $c = substr($li, $i, 1); + if ($c eq "\t") { + my $npos = ($pos+$nsp+8) & ~7; + my $ntab = ($npos >> 3) - ($pos >> 3); + $lo .= "\t" x $ntab; + $pos = $npos; + $nsp = 0; + } elsif ($c eq "\n" || $c eq "\r") { + $lo .= " " x $nsp; + $pos += $nsp; + $nsp = 0; + $lo .= $c; + $pos = 0; + } elsif ($c eq " ") { + $nsp++; + } else { + $lo .= " " x $nsp; + $pos += $nsp; + $nsp = 0; + $lo .= $c; + $pos++; + } + } + $lo .= " " x $nsp; + return $lo; +} + +# Compute the visual width of a string +sub strwidth($) { + no bytes; # Tab alignment depends on characters + + my($li) = @_; + my($c, $i); + my $pos = 0; + my $mlen = 0; + + for ($i = 0; $i < length($li); $i++) { + $c = substr($li,$i,1); + if ($c eq "\t") { + $pos = ($pos+8) & ~7; + } elsif ($c eq "\n") { + $mlen = $pos if ($pos > $mlen); + $pos = 0; + } else { + $pos++; + } + } + + $mlen = $pos if ($pos > $mlen); + return $mlen; +} + +$name = basename($0); + +@files = (); + +while (defined($a = shift(@ARGV))) { + if ($a =~ /^-/) { + if ($a eq '-width' || $a eq '-w') { + $max_width = shift(@ARGV)+0; + } else { + print STDERR "Usage: $name [-width #] files...\n"; + exit 1; + } + } else { + push(@files, $a); + } +} + +foreach $f ( @files ) { + print STDERR "$name: $f\n"; + + if (! -f $f) { + print STDERR "$f: not a file\n"; + next; + } + + if (!open(FILE, '+<', $f)) { + print STDERR "$name: Cannot open file: $f: $!\n"; + next; + } + + binmode FILE; + + # First, verify that it is not a binary file; consider any file + # with a zero byte to be a binary file. Is there any better, or + # additional, heuristic that should be applied? + $is_binary = 0; + + while (read(FILE, $data, 65536) > 0) { + if ($data =~ /\0/) { + $is_binary = 1; + last; + } + } + + if ($is_binary) { + print STDERR "$name: $f: binary file\n"; + next; + } + + seek(FILE, 0, 0); + + $in_bytes = 0; + $out_bytes = 0; + $lineno = 0; + + @lines = (); + + $in_hunk = 0; + $err = 0; + + while ( defined($line = <FILE>) ) { + $lineno++; + $in_bytes += length($line); + + if (!$in_hunk) { + if ($line =~ + /^\@\@\s+\-([0-9]+),([0-9]+)\s+\+([0-9]+),([0-9]+)\s\@\@/) { + $minus_lines = $2; + $plus_lines = $4; + if ($minus_lines || $plus_lines) { + $in_hunk = 1; + @hunk_lines = ($line); + } + } else { + push(@lines, $line); + $out_bytes += length($line); + } + } else { + # We're in a hunk + + if ($line =~ /^\+/) { + $plus_lines--; + + $text = substr($line, 1); + $text =~ s/[ \t\r]*$//; # Remove trailing spaces + $text = clean_space_tabs($text); + + $l_width = strwidth($text); + if ($max_width && $l_width > $max_width) { + print STDERR + "$f:$lineno: adds line exceeds $max_width ", + "characters ($l_width)\n"; + } + + push(@hunk_lines, '+'.$text); + } elsif ($line =~ /^\-/) { + $minus_lines--; + push(@hunk_lines, $line); + } elsif ($line =~ /^ /) { + $plus_lines--; + $minus_lines--; + push(@hunk_lines, $line); + } else { + print STDERR "$name: $f: malformed patch\n"; + $err = 1; + last; + } + + if ($plus_lines < 0 || $minus_lines < 0) { + print STDERR "$name: $f: malformed patch\n"; + $err = 1; + last; + } elsif ($plus_lines == 0 && $minus_lines == 0) { + # End of a hunk. Process this hunk. + my $i; + my $l; + my @h = (); + my $adj = 0; + my $done = 0; + + for ($i = scalar(@hunk_lines)-1; $i > 0; $i--) { + $l = $hunk_lines[$i]; + if (!$done && $l eq "+\n") { + $adj++; # Skip this line + } elsif ($l =~ /^[ +]/) { + $done = 1; + unshift(@h, $l); + } else { + unshift(@h, $l); + } + } + + $l = $hunk_lines[0]; # Hunk header + undef @hunk_lines; # Free memory + + if ($adj) { + die unless + ($l =~ /^\@\@\s+\-([0-9]+),([0-9]+)\s+\+([0-9]+),([0-9]+)\s\@\@(.*)$/); + my $mstart = $1; + my $mlin = $2; + my $pstart = $3; + my $plin = $4; + my $tail = $5; # doesn't include the final newline + + $l = sprintf("@@ -%d,%d +%d,%d @@%s\n", + $mstart, $mlin, $pstart, $plin-$adj, + $tail); + } + unshift(@h, $l); + + # Transfer to the output array + foreach $l (@h) { + $out_bytes += length($l); + push(@lines, $l); + } + + $in_hunk = 0; + } + } + } + + if ($in_hunk) { + print STDERR "$name: $f: malformed patch\n"; + $err = 1; + } + + if (!$err) { + if ($in_bytes != $out_bytes) { + # Only write to the file if changed + seek(FILE, 0, 0); + print FILE @lines; + + if ( !defined($where = tell(FILE)) || + !truncate(FILE, $where) ) { + die "$name: Failed to truncate modified file: $f: $!\n"; + } + } + } + + close(FILE); +} diff --git a/scripts/coccicheck b/scripts/coccicheck new file mode 100755 index 000000000..a0a0e0f72 --- /dev/null +++ b/scripts/coccicheck @@ -0,0 +1,286 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# Linux kernel coccicheck +# +# Read Documentation/dev-tools/coccinelle.rst +# +# This script requires at least spatch +# version 1.0.0-rc11. + +DIR="$(dirname $(readlink -f $0))/.." +SPATCH="`which ${SPATCH:=spatch}`" + +if [ ! -x "$SPATCH" ]; then + echo 'spatch is part of the Coccinelle project and is available at https://coccinelle.gitlabpages.inria.fr/website' + exit 1 +fi + +SPATCH_VERSION=$($SPATCH --version | head -1 | awk '{print $3}') + +USE_JOBS="no" +$SPATCH --help | grep -e "--jobs" > /dev/null && USE_JOBS="yes" + +# The verbosity may be set by the environmental parameter V= +# as for example with 'make V=1 coccicheck' + +if [ -n "$V" -a "$V" != "0" ]; then + VERBOSE="$V" +else + VERBOSE=0 +fi + +FLAGS="--very-quiet" + +# You can use SPFLAGS to append extra arguments to coccicheck or override any +# heuristics done in this file as Coccinelle accepts the last options when +# options conflict. +# +# A good example for use of SPFLAGS is if you want to debug your cocci script, +# you can for instance use the following: +# +# $ export COCCI=scripts/coccinelle/misc/irqf_oneshot.cocci +# $ make coccicheck MODE=report DEBUG_FILE="all.err" SPFLAGS="--profile --show-trying" M=./drivers/mfd/arizona-irq.c +# +# "--show-trying" should show you what rule is being processed as it goes to +# stdout, you do not need a debug file for that. The profile output will be +# be sent to stdout, if you provide a DEBUG_FILE the profiling data can be +# inspected there. +# +# --profile will not output if --very-quiet is used, so avoid it. +echo $SPFLAGS | grep -E -e "--profile|--show-trying" 2>&1 > /dev/null +if [ $? -eq 0 ]; then + FLAGS="--quiet" +fi + +# spatch only allows include directories with the syntax "-I include" +# while gcc also allows "-Iinclude" and "-include include" +COCCIINCLUDE=${LINUXINCLUDE//-I/-I } +COCCIINCLUDE=${COCCIINCLUDE// -include/ --include} + +if [ "$C" = "1" -o "$C" = "2" ]; then + ONLINE=1 + + if [[ $# -le 0 ]]; then + echo '' + echo 'Specifying both the variable "C" and rule "coccicheck" in the make +command results in a shift count error.' + echo '' + echo 'Try specifying "scripts/coccicheck" as a value for the CHECK variable instead.' + echo '' + echo 'Example: make C=2 CHECK=scripts/coccicheck drivers/net/ethernet/ethoc.o' + echo '' + exit 1 + fi + + # Take only the last argument, which is the C file to test + shift $(( $# - 1 )) + OPTIONS="$COCCIINCLUDE $1" + + # No need to parallelize Coccinelle since this mode takes one input file. + NPROC=1 +else + ONLINE=0 + 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:]") + if [ -z "$J" ]; then + NPROC=$(getconf _NPROCESSORS_ONLN) + if [ $THREADS_PER_CORE -gt 1 -a $NPROC -gt 4 ] ; then + NPROC=$((NPROC/2)) + fi + else + NPROC="$J" + fi +fi + +if [ "$KBUILD_EXTMOD" != "" ] ; then + OPTIONS="--patch $srctree $OPTIONS" +fi + +# You can override by using SPFLAGS +if [ "$USE_JOBS" = "no" ]; then + trap kill_running SIGTERM SIGINT + declare -a SPATCH_PID +elif [ "$NPROC" != "1" ]; then + # Using 0 should work as well, refer to _SC_NPROCESSORS_ONLN use on + # https://github.com/rdicosmo/parmap/blob/master/setcore_stubs.c + OPTIONS="$OPTIONS --jobs $NPROC --chunksize 1" +fi + +if [ "$MODE" = "" ] ; then + if [ "$ONLINE" = "0" ] ; then + echo 'You have not explicitly specified the mode to use. Using default "report" mode.' + echo 'Available modes are the following: patch, report, context, org, chain' + echo 'You can specify the mode with "make coccicheck MODE=<mode>"' + echo 'Note however that some modes are not implemented by some semantic patches.' + fi + MODE="report" +fi + +if [ "$MODE" = "chain" ] ; then + if [ "$ONLINE" = "0" ] ; then + echo 'You have selected the "chain" mode.' + echo 'All available modes will be tried (in that order): patch, report, context, org' + fi +elif [ "$MODE" = "report" -o "$MODE" = "org" ] ; then + FLAGS="--no-show-diff $FLAGS" +fi + +if [ "$ONLINE" = "0" ] ; then + echo '' + echo 'Please check for false positives in the output before submitting a patch.' + echo 'When using "patch" mode, carefully review the patch before submitting it.' + echo '' +fi + +run_cmd_parmap() { + if [ $VERBOSE -ne 0 ] ; then + echo "Running ($NPROC in parallel): $@" + fi + if [ "$DEBUG_FILE" != "/dev/null" ]; then + echo $@>>$DEBUG_FILE + $@ 2>>$DEBUG_FILE + else + echo $@ + $@ 2>&1 + fi + + err=$? + if [[ $err -ne 0 ]]; then + echo "coccicheck failed" + exit $err + fi +} + +run_cmd_old() { + local i + if [ $VERBOSE -ne 0 ] ; then + echo "Running ($NPROC in parallel): $@" + fi + for i in $(seq 0 $(( NPROC - 1)) ); do + eval "$@ --max $NPROC --index $i &" + SPATCH_PID[$i]=$! + if [ $VERBOSE -eq 2 ] ; then + echo "${SPATCH_PID[$i]} running" + fi + done + wait +} + +run_cmd() { + if [ "$USE_JOBS" = "yes" ]; then + run_cmd_parmap $@ + else + run_cmd_old $@ + fi +} + +kill_running() { + for i in $(seq 0 $(( NPROC - 1 )) ); do + if [ $VERBOSE -eq 2 ] ; then + echo "Killing ${SPATCH_PID[$i]}" + fi + kill ${SPATCH_PID[$i]} 2>/dev/null + done +} + +# You can override heuristics with SPFLAGS, these must always go last +OPTIONS="$OPTIONS $SPFLAGS" + +coccinelle () { + COCCI="$1" + + OPT=`grep "Options:" $COCCI | cut -d':' -f2` + REQ=`grep "Requires:" $COCCI | cut -d':' -f2 | sed "s| ||"` + if [ -n "$REQ" ] && ! { echo "$REQ"; echo "$SPATCH_VERSION"; } | sort -CV ; then + echo "Skipping coccinelle SmPL patch: $COCCI" + echo "You have coccinelle: $SPATCH_VERSION" + echo "This SmPL patch requires: $REQ" + return + fi + +# The option '--parse-cocci' can be used to syntactically check the SmPL files. +# +# $SPATCH -D $MODE $FLAGS -parse_cocci $COCCI $OPT > /dev/null + + if [ $VERBOSE -ne 0 -a $ONLINE -eq 0 ] ; then + + FILE=${COCCI#$srctree/} + + echo "Processing `basename $COCCI`" + echo "with option(s) \"$OPT\"" + echo '' + echo 'Message example to submit a patch:' + + sed -ne 's|^///||p' $COCCI + + if [ "$MODE" = "patch" ] ; then + echo ' The semantic patch that makes this change is available' + elif [ "$MODE" = "report" ] ; then + echo ' The semantic patch that makes this report is available' + elif [ "$MODE" = "context" ] ; then + echo ' The semantic patch that spots this code is available' + elif [ "$MODE" = "org" ] ; then + echo ' The semantic patch that makes this Org report is available' + else + echo ' The semantic patch that makes this output is available' + fi + echo " in $FILE." + echo '' + echo ' More information about semantic patching is available at' + echo ' https://coccinelle.gitlabpages.inria.fr/website' + echo '' + + if [ "`sed -ne 's|^//#||p' $COCCI`" ] ; then + echo 'Semantic patch information:' + sed -ne 's|^//#||p' $COCCI + echo '' + fi + fi + + if [ "$MODE" = "chain" ] ; then + run_cmd $SPATCH -D patch \ + $FLAGS --cocci-file $COCCI $OPT $OPTIONS || \ + run_cmd $SPATCH -D report \ + $FLAGS --cocci-file $COCCI $OPT $OPTIONS --no-show-diff || \ + run_cmd $SPATCH -D context \ + $FLAGS --cocci-file $COCCI $OPT $OPTIONS || \ + run_cmd $SPATCH -D org \ + $FLAGS --cocci-file $COCCI $OPT $OPTIONS --no-show-diff || exit 1 + elif [ "$MODE" = "rep+ctxt" ] ; then + run_cmd $SPATCH -D report \ + $FLAGS --cocci-file $COCCI $OPT $OPTIONS --no-show-diff && \ + run_cmd $SPATCH -D context \ + $FLAGS --cocci-file $COCCI $OPT $OPTIONS || exit 1 + else + run_cmd $SPATCH -D $MODE $FLAGS --cocci-file $COCCI $OPT $OPTIONS || exit 1 + fi + +} + +if [ "$DEBUG_FILE" = "" ]; then + echo 'You have not explicitly specified the debug file to use.' + echo 'Using default "/dev/null" as debug file.' + echo 'Debug logs will be printed to stdout.' + echo 'You can specify the debug file with "make coccicheck DEBUG_FILE=<debug_file>"' + echo '' + DEBUG_FILE="/dev/null" +fi + +if [ -f $DEBUG_FILE ]; then + echo "Debug file $DEBUG_FILE exists, bailing" + exit +fi + +if [ "$COCCI" = "" ] ; then + for f in `find $srctree/scripts/coccinelle/ -name '*.cocci' -type f | sort`; do + if grep -q "virtual[[:space:]]\+$MODE" "$f"; then + coccinelle $f + else + echo "warning: Skipping $f as it does not match mode '$MODE'" + fi + done +else + coccinelle $COCCI +fi diff --git a/scripts/coccinelle/api/alloc/alloc_cast.cocci b/scripts/coccinelle/api/alloc/alloc_cast.cocci new file mode 100644 index 000000000..a4b83cc8e --- /dev/null +++ b/scripts/coccinelle/api/alloc/alloc_cast.cocci @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Remove casting the values returned by memory allocation functions +/// like kmalloc, kzalloc, kmem_cache_alloc, kmem_cache_zalloc etc. +/// +//# This makes an effort to find cases of casting of values returned by +//# kmalloc, kzalloc, kcalloc, kmem_cache_alloc, kmem_cache_zalloc, +//# kmem_cache_alloc_node, kmalloc_node and kzalloc_node and removes +//# the casting as it is not required. The result in the patch case may +//# need some reformatting. +// +// Confidence: High +// Copyright: (C) 2014 Himangi Saraogi +// Copyright: (C) 2017 Himanshu Jha +// Comments: +// Options: --no-includes --include-headers +// + +virtual context +virtual patch +virtual org +virtual report + +@m1@ +identifier i; +expression e; +type T; +position p1; +@@ + +#define i (T@p1 *)e + +@m2@ +identifier i; +expression e; +type T; +position p2; +@@ + +#define i(...) (T@p2 *)e + +@initialize:python@ +@@ +import re +pattern = '__' +m = re.compile(pattern) + +@r1 depends on context || patch@ +type T; +position p != {m1.p1,m2.p2}; +@@ + + (T@p *) + \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| + kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| + dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@script:python depends on context@ +t << r1.T; +@@ + +if m.search(t) != None: + cocci.include_match(False) + +@depends on context && r1@ +type r1.T; +@@ + +* (T *) + \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| + kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| + dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@script:python depends on patch@ +t << r1.T; +@@ + +if m.search(t) != None: + cocci.include_match(False) + +@depends on patch && r1@ +type r1.T; +@@ + +- (T *) + \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| + kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| + dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) + +//---------------------------------------------------------- +// For org and report mode +//---------------------------------------------------------- + +@r2 depends on org || report@ +type T; +position p != {m1.p1,m2.p2}; +@@ + + (T@p *) + \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| + kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| + dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) + +@script:python depends on org@ +p << r2.p; +t << r2.T; +@@ + +if m.search(t) != None: + cocci.include_match(False) +else: + coccilib.org.print_safe_todo(p[0], t) + +@script:python depends on report@ +p << r2.p; +t << r2.T; +@@ + +if m.search(t) != None: + cocci.include_match(False) +else: + msg="WARNING: casting value returned by memory allocation function to (%s *) is useless." % (t) + coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci b/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci new file mode 100644 index 000000000..07af8c743 --- /dev/null +++ b/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Use *_pool_zalloc rather than *_pool_alloc followed by memset with 0 +/// +// Copyright: (C) 2015 Intel Corp. +// Options: --no-includes --include-headers +// +// Keywords: dma_pool_zalloc +// + +virtual context +virtual patch +virtual org +virtual report + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@depends on context@ +expression x; +statement S; +@@ + +* x = dma_pool_alloc(...); + if ((x==NULL) || ...) S +* memset(x,0, ...); + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@depends on patch@ +expression x; +expression a,b,c; +statement S; +@@ + +- x = dma_pool_alloc(a,b,c); ++ x = dma_pool_zalloc(a,b,c); + if ((x==NULL) || ...) S +- memset(x,0,...); + +//---------------------------------------------------------- +// For org and report mode +//---------------------------------------------------------- + +@r depends on org || report@ +expression x; +expression a,b,c; +statement S; +position p; +@@ + + x =@p dma_pool_alloc(a,b,c); + if ((x==NULL) || ...) S + memset(x,0, ...); + +@script:python depends on org@ +p << r.p; +x << r.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r.p; +x << r.x; +@@ + +msg="WARNING: *_pool_zalloc should be used for %s, instead of *_pool_alloc/memset" % (x) +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/api/alloc/zalloc-simple.cocci b/scripts/coccinelle/api/alloc/zalloc-simple.cocci new file mode 100644 index 000000000..ae1d39e71 --- /dev/null +++ b/scripts/coccinelle/api/alloc/zalloc-simple.cocci @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Use zeroing allocator rather than allocator followed by memset with 0 +/// +/// This considers some simple cases that are common and easy to validate +/// Note in particular that there are no ...s in the rule, so all of the +/// matched code has to be contiguous +/// +// Confidence: High +// Copyright: (C) 2009-2010 Julia Lawall, Nicolas Palix, DIKU. +// Copyright: (C) 2009-2010 Gilles Muller, INRIA/LiP6. +// Copyright: (C) 2017 Himanshu Jha +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --no-includes --include-headers +// +// Keywords: kmalloc, kzalloc +// Version min: < 2.6.12 kmalloc +// Version min: 2.6.14 kzalloc +// + +virtual context +virtual patch +virtual org +virtual report + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@depends on context@ +type T, T2; +expression x; +expression E1; +statement S; +@@ + +* x = (T)\(kmalloc(E1, ...)\|vmalloc(E1)\|dma_alloc_coherent(...,E1,...)\| + kmalloc_node(E1, ...)\|kmem_cache_alloc(...)\| + devm_kmalloc(...,E1,...)\|kvmalloc(E1, ...)\|kvmalloc_node(E1,...)\); + if ((x==NULL) || ...) S +* memset((T2)x,0,E1); + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@depends on patch@ +type T, T2; +expression x; +expression E1,E2,E3,E4; +statement S; +@@ + +( +- x = kmalloc(E1,E2); ++ x = kzalloc(E1,E2); +| +- x = (T *)kmalloc(E1,E2); ++ x = kzalloc(E1,E2); +| +- x = (T)kmalloc(E1,E2); ++ x = (T)kzalloc(E1,E2); +| +- x = vmalloc(E1); ++ x = vzalloc(E1); +| +- x = (T *)vmalloc(E1); ++ x = vzalloc(E1); +| +- x = (T)vmalloc(E1); ++ x = (T)vzalloc(E1); +| +- x = kmalloc_node(E1,E2,E3); ++ x = kzalloc_node(E1,E2,E3); +| +- x = (T *)kmalloc_node(E1,E2,E3); ++ x = kzalloc_node(E1,E2,E3); +| +- x = (T)kmalloc_node(E1,E2,E3); ++ x = (T)kzalloc_node(E1,E2,E3); +| +- x = kmem_cache_alloc(E3,E4); ++ x = kmem_cache_zalloc(E3,E4); +| +- x = (T *)kmem_cache_alloc(E3,E4); ++ x = kmem_cache_zalloc(E3,E4); +| +- x = (T)kmem_cache_alloc(E3,E4); ++ x = (T)kmem_cache_zalloc(E3,E4); +| +- x = devm_kmalloc(E2,E1,E3); ++ x = devm_kzalloc(E2,E1,E3); +| +- x = (T *)devm_kmalloc(E2,E1,E3); ++ x = devm_kzalloc(E2,E1,E3); +| +- x = (T)devm_kmalloc(E2,E1,E3); ++ x = (T)devm_kzalloc(E2,E1,E3); +| +- x = kvmalloc(E1,E2); ++ x = kvzalloc(E1,E2); +| +- x = (T *)kvmalloc(E1,E2); ++ x = kvzalloc(E1,E2); +| +- x = (T)kvmalloc(E1,E2); ++ x = (T)kvzalloc(E1,E2); +| +- x = kvmalloc_node(E1,E2,E3); ++ x = kvzalloc_node(E1,E2,E3); +| +- x = (T *)kvmalloc_node(E1,E2,E3); ++ x = kvzalloc_node(E1,E2,E3); +| +- x = (T)kvmalloc_node(E1,E2,E3); ++ x = (T)kvzalloc_node(E1,E2,E3); +) + if ((x==NULL) || ...) S +- memset((T2)x,0,E1); + +@depends on patch@ +type T, T2; +expression x; +expression E1,E2,E3,E4; +statement S; +@@ + x = (T)dma_alloc_coherent(E1, E2, E3, E4); + if ((x==NULL) || ...) S +- memset((T2)x, 0, E2); + +//---------------------------------------------------------- +// For org mode +//---------------------------------------------------------- + +@r depends on org || report@ +type T, T2; +expression x; +expression E1,E2; +statement S; +position p; +@@ + + x = (T)kmalloc@p(E1,E2); + if ((x==NULL) || ...) S + memset((T2)x,0,E1); + +@script:python depends on org@ +p << r.p; +x << r.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r.p; +x << r.x; +@@ + +msg="WARNING: kzalloc should be used for %s, instead of kmalloc/memset" % (x) +coccilib.report.print_report(p[0], msg) + +//----------------------------------------------------------------- +@r1 depends on org || report@ +type T, T2; +expression x; +expression E1; +statement S; +position p; +@@ + + x = (T)vmalloc@p(E1); + if ((x==NULL) || ...) S + memset((T2)x,0,E1); + +@script:python depends on org@ +p << r1.p; +x << r1.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r1.p; +x << r1.x; +@@ + +msg="WARNING: vzalloc should be used for %s, instead of vmalloc/memset" % (x) +coccilib.report.print_report(p[0], msg) + +//----------------------------------------------------------------- +@r2 depends on org || report@ +type T, T2; +expression x; +expression E1,E2,E3,E4; +statement S; +position p; +@@ + + x = (T)dma_alloc_coherent@p(E1,E2,E3,E4); + if ((x==NULL) || ...) S + memset((T2)x,0,E2); + +@script:python depends on org@ +p << r2.p; +x << r2.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r2.p; +x << r2.x; +@@ + +msg="WARNING: dma_alloc_coherent used in %s already zeroes out memory, so memset is not needed" % (x) +coccilib.report.print_report(p[0], msg) + +//----------------------------------------------------------------- +@r3 depends on org || report@ +type T, T2; +expression x; +expression E1,E2,E3; +statement S; +position p; +@@ + + x = (T)kmalloc_node@p(E1,E2,E3); + if ((x==NULL) || ...) S + memset((T2)x,0,E1); + +@script:python depends on org@ +p << r3.p; +x << r3.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r3.p; +x << r3.x; +@@ + +msg="WARNING: kzalloc_node should be used for %s, instead of kmalloc_node/memset" % (x) +coccilib.report.print_report(p[0], msg) + +//----------------------------------------------------------------- +@r4 depends on org || report@ +type T, T2; +expression x; +expression E1,E2,E3; +statement S; +position p; +@@ + + x = (T)kmem_cache_alloc@p(E2,E3); + if ((x==NULL) || ...) S + memset((T2)x,0,E1); + +@script:python depends on org@ +p << r4.p; +x << r4.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r4.p; +x << r4.x; +@@ + +msg="WARNING: kmem_cache_zalloc should be used for %s, instead of kmem_cache_alloc/memset" % (x) +coccilib.report.print_report(p[0], msg) + +//----------------------------------------------------------------- +@r6 depends on org || report@ +type T, T2; +expression x; +expression E1,E2,E3; +statement S; +position p; +@@ + + x = (T)devm_kmalloc@p(E2,E1,E3); + if ((x==NULL) || ...) S + memset((T2)x,0,E1); + +@script:python depends on org@ +p << r6.p; +x << r6.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r6.p; +x << r6.x; +@@ + +msg="WARNING: devm_kzalloc should be used for %s, instead of devm_kmalloc/memset" % (x) +coccilib.report.print_report(p[0], msg) + +//----------------------------------------------------------------- +@r7 depends on org || report@ +type T, T2; +expression x; +expression E1,E2; +statement S; +position p; +@@ + + x = (T)kvmalloc@p(E1,E2); + if ((x==NULL) || ...) S + memset((T2)x,0,E1); + +@script:python depends on org@ +p << r7.p; +x << r7.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r7.p; +x << r7.x; +@@ + +msg="WARNING: kvzalloc should be used for %s, instead of kvmalloc/memset" % (x) +coccilib.report.print_report(p[0], msg) + +//----------------------------------------------------------------- +@r9 depends on org || report@ +type T, T2; +expression x; +expression E1,E2,E3; +statement S; +position p; +@@ + + x = (T)kvmalloc_node@p(E1,E2,E3); + if ((x==NULL) || ...) S + memset((T2)x,0,E1); + +@script:python depends on org@ +p << r9.p; +x << r9.x; +@@ + +msg="%s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r9.p; +x << r9.x; +@@ + +msg="WARNING: kvzalloc_node should be used for %s, instead of kvmalloc_node/memset" % (x) +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/api/atomic_as_refcounter.cocci b/scripts/coccinelle/api/atomic_as_refcounter.cocci new file mode 100644 index 000000000..af82b9e09 --- /dev/null +++ b/scripts/coccinelle/api/atomic_as_refcounter.cocci @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Check if refcount_t type and API should be used +// instead of atomic_t type when dealing with refcounters +// +// Copyright (c) 2016-2017, Elena Reshetova, Intel Corporation +// +// Confidence: Moderate +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --include-headers --very-quiet + +virtual report + +@r1 exists@ +identifier a, x; +position p1, p2; +identifier fname =~ ".*free.*"; +identifier fname2 =~ ".*destroy.*"; +identifier fname3 =~ ".*del.*"; +identifier fname4 =~ ".*queue_work.*"; +identifier fname5 =~ ".*schedule_work.*"; +identifier fname6 =~ ".*call_rcu.*"; + +@@ + +( + atomic_dec_and_test@p1(&(a)->x) +| + atomic_dec_and_lock@p1(&(a)->x, ...) +| + atomic_long_dec_and_test@p1(&(a)->x) +| + atomic64_dec_and_test@p1(&(a)->x) +| + local_dec_and_test@p1(&(a)->x) +) +... +( + fname@p2(a, ...); +| + fname2@p2(...); +| + fname3@p2(...); +| + fname4@p2(...); +| + fname5@p2(...); +| + fname6@p2(...); +) + + +@script:python depends on report@ +p1 << r1.p1; +p2 << r1.p2; +@@ +msg = "WARNING: atomic_dec_and_test variation before object free at line %s." +coccilib.report.print_report(p1[0], msg % (p2[0].line)) + +@r4 exists@ +identifier a, x, y; +position p1, p2; +identifier fname =~ ".*free.*"; + +@@ + +( + atomic_dec_and_test@p1(&(a)->x) +| + atomic_dec_and_lock@p1(&(a)->x, ...) +| + atomic_long_dec_and_test@p1(&(a)->x) +| + atomic64_dec_and_test@p1(&(a)->x) +| + local_dec_and_test@p1(&(a)->x) +) +... +y=a +... +fname@p2(y, ...); + + +@script:python depends on report@ +p1 << r4.p1; +p2 << r4.p2; +@@ +msg = "WARNING: atomic_dec_and_test variation before object free at line %s." +coccilib.report.print_report(p1[0], msg % (p2[0].line)) + +@r2 exists@ +identifier a, x; +position p1; +@@ + +( +atomic_add_unless(&(a)->x,-1,1)@p1 +| +atomic_long_add_unless(&(a)->x,-1,1)@p1 +| +atomic64_add_unless(&(a)->x,-1,1)@p1 +) + +@script:python depends on report@ +p1 << r2.p1; +@@ +msg = "WARNING: atomic_add_unless" +coccilib.report.print_report(p1[0], msg) + +@r3 exists@ +identifier x; +position p1; +@@ + +( +x = atomic_add_return@p1(-1, ...); +| +x = atomic_long_add_return@p1(-1, ...); +| +x = atomic64_add_return@p1(-1, ...); +) + +@script:python depends on report@ +p1 << r3.p1; +@@ +msg = "WARNING: x = atomic_add_return(-1, ...)" +coccilib.report.print_report(p1[0], msg) diff --git a/scripts/coccinelle/api/check_bq27xxx_data.cocci b/scripts/coccinelle/api/check_bq27xxx_data.cocci new file mode 100644 index 000000000..27366c616 --- /dev/null +++ b/scripts/coccinelle/api/check_bq27xxx_data.cocci @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Detect BQ27XXX_DATA structures with identical registers, dm registers or +/// properties. +//# Doesn't unfold macros used in register or property fields. +//# Requires OCaml scripting +/// +// Confidence: High +// Copyright: (C) 2017 Julia Lawall, Inria/LIP6, +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Requires: 1.0.7 +// Keywords: BQ27XXX_DATA + +virtual report + +@initialize:ocaml@ +@@ + +let print_report p msg = + let p = List.hd p in + Printf.printf "%s:%d:%d-%d: %s" p.file p.line p.col p.col_end msg + +@str depends on report@ +type t; +identifier i,i1,i2; +expression e1,e2; +@@ + +t i[] = { + ..., + [e1] = BQ27XXX_DATA(i1,...), + ..., + [e2] = BQ27XXX_DATA(i2,...), + ..., +}; + +@script:ocaml tocheck@ +i1 << str.i1; +i2 << str.i2; +i1regs; i2regs; +i1dmregs; i2dmregs; +i1props; i2props; +@@ + +if not(i1 = i2) +then + begin + i1regs := make_ident (i1 ^ "_regs"); + i2regs := make_ident (i2 ^ "_regs"); + i1dmregs := make_ident (i1 ^ "_dm_regs"); + i2dmregs := make_ident (i2 ^ "_dm_regs"); + i1props := make_ident (i1 ^ "_props"); + i2props := make_ident (i2 ^ "_props") + end + +(* ---------------------------------------------------------------- *) + +@getregs1@ +typedef u8; +identifier tocheck.i1regs; +initializer list i1regs_vals; +position p1; +@@ + +u8 i1regs@p1[...] = { i1regs_vals, }; + +@getregs2@ +identifier tocheck.i2regs; +initializer list i2regs_vals; +position p2; +@@ + +u8 i2regs@p2[...] = { i2regs_vals, }; + +@script:ocaml@ +(_,i1regs_vals) << getregs1.i1regs_vals; +(_,i2regs_vals) << getregs2.i2regs_vals; +i1regs << tocheck.i1regs; +i2regs << tocheck.i2regs; +p1 << getregs1.p1; +p2 << getregs2.p2; +@@ + +if i1regs < i2regs && + List.sort compare i1regs_vals = List.sort compare i2regs_vals +then + let msg = + Printf.sprintf + "WARNING %s and %s (line %d) are identical\n" + i1regs i2regs (List.hd p2).line in + print_report p1 msg + +(* ---------------------------------------------------------------- *) + +@getdmregs1@ +identifier tocheck.i1dmregs; +initializer list i1dmregs_vals; +position p1; +@@ + +struct bq27xxx_dm_reg i1dmregs@p1[] = { i1dmregs_vals, }; + +@getdmregs2@ +identifier tocheck.i2dmregs; +initializer list i2dmregs_vals; +position p2; +@@ + +struct bq27xxx_dm_reg i2dmregs@p2[] = { i2dmregs_vals, }; + +@script:ocaml@ +(_,i1dmregs_vals) << getdmregs1.i1dmregs_vals; +(_,i2dmregs_vals) << getdmregs2.i2dmregs_vals; +i1dmregs << tocheck.i1dmregs; +i2dmregs << tocheck.i2dmregs; +p1 << getdmregs1.p1; +p2 << getdmregs2.p2; +@@ + +if i1dmregs < i2dmregs && + List.sort compare i1dmregs_vals = List.sort compare i2dmregs_vals +then + let msg = + Printf.sprintf + "WARNING %s and %s (line %d) are identical\n" + i1dmregs i2dmregs (List.hd p2).line in + print_report p1 msg + +(* ---------------------------------------------------------------- *) + +@getprops1@ +identifier tocheck.i1props; +initializer list[n1] i1props_vals; +position p1; +@@ + +enum power_supply_property i1props@p1[] = { i1props_vals, }; + +@getprops2@ +identifier tocheck.i2props; +initializer list[n2] i2props_vals; +position p2; +@@ + +enum power_supply_property i2props@p2[] = { i2props_vals, }; + +@script:ocaml@ +(_,i1props_vals) << getprops1.i1props_vals; +(_,i2props_vals) << getprops2.i2props_vals; +i1props << tocheck.i1props; +i2props << tocheck.i2props; +p1 << getprops1.p1; +p2 << getprops2.p2; +@@ + +if i1props < i2props && + List.sort compare i1props_vals = List.sort compare i2props_vals +then + let msg = + Printf.sprintf + "WARNING %s and %s (line %d) are identical\n" + i1props i2props (List.hd p2).line in + print_report p1 msg diff --git a/scripts/coccinelle/api/d_find_alias.cocci b/scripts/coccinelle/api/d_find_alias.cocci new file mode 100644 index 000000000..348900137 --- /dev/null +++ b/scripts/coccinelle/api/d_find_alias.cocci @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 +/// Make sure calls to d_find_alias() have a corresponding call to dput(). +// +// Keywords: d_find_alias, dput +// +// Confidence: Moderate +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --include-headers + +virtual context +virtual org +virtual patch +virtual report + +@r exists@ +local idexpression struct dentry *dent; +expression E, E1; +statement S1, S2; +position p1, p2; +@@ +( + if (!(dent@p1 = d_find_alias(...))) S1 +| + dent@p1 = d_find_alias(...) +) + +<...when != dput(dent) + when != if (...) { <+... dput(dent) ...+> } + when != true !dent || ... + when != dent = E + when != E = dent +if (!dent || ...) S2 +...> +( + return <+...dent...+>; +| + return @p2 ...; +| + dent@p2 = E1; +| + E1 = dent; +) + +@depends on context@ +local idexpression struct dentry *r.dent; +position r.p1,r.p2; +@@ +* dent@p1 = ... + ... +( +* return@p2 ...; +| +* dent@p2 +) + + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +@@ +cocci.print_main("Missing call to dput()",p1) +cocci.print_secs("",p2) + +@depends on patch@ +local idexpression struct dentry *r.dent; +position r.p2; +@@ +( ++ dput(dent); + return @p2 ...; +| ++ dput(dent); + dent@p2 = ...; +) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +@@ +msg = "Missing call to dput() at line %s." +coccilib.report.print_report(p1[0], msg % (p2[0].line)) diff --git a/scripts/coccinelle/api/device_attr_show.cocci b/scripts/coccinelle/api/device_attr_show.cocci new file mode 100644 index 000000000..dfcf93041 --- /dev/null +++ b/scripts/coccinelle/api/device_attr_show.cocci @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// From Documentation/filesystems/sysfs.rst: +/// show() should only use sysfs_emit() or sysfs_emit_at() when formatting +/// the value to be returned to user space. +/// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS +// Options: --no-includes --include-headers +// + +virtual report +virtual org +virtual context +virtual patch + +@r depends on !patch@ +identifier show, dev, attr, buf; +position p; +@@ + +ssize_t show(struct device *dev, struct device_attribute *attr, char *buf) +{ + <... +* return snprintf@p(...); + ...> +} + +@rp depends on patch@ +identifier show, dev, attr, buf; +expression BUF, SZ, FORMAT; +@@ + +ssize_t show(struct device *dev, struct device_attribute *attr, char *buf) +{ + <... + return +- snprintf(BUF, SZ, FORMAT ++ sysfs_emit(BUF, FORMAT + ,...); + ...> +} + +@script: python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING: please use sysfs_emit or sysfs_emit_at") + +@script: python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING: please use sysfs_emit or sysfs_emit_at") diff --git a/scripts/coccinelle/api/err_cast.cocci b/scripts/coccinelle/api/err_cast.cocci new file mode 100644 index 000000000..7f9dc1212 --- /dev/null +++ b/scripts/coccinelle/api/err_cast.cocci @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Use ERR_CAST inlined function instead of ERR_PTR(PTR_ERR(...)) +/// +// Confidence: High +// Copyright: (C) 2009, 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2009, 2010 Julia Lawall, DIKU. +// Copyright: (C) 2009, 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: +// +// Keywords: ERR_PTR, PTR_ERR, ERR_CAST +// Version min: 2.6.25 +// + +virtual context +virtual patch +virtual org +virtual report + + +@ depends on context && !patch && !org && !report@ +expression x; +@@ + +* ERR_PTR(PTR_ERR(x)) + +@ depends on !context && patch && !org && !report @ +expression x; +@@ + +- ERR_PTR(PTR_ERR(x)) ++ ERR_CAST(x) + +@r depends on !context && !patch && (org || report)@ +expression x; +position p; +@@ + + ERR_PTR@p(PTR_ERR(x)) + +@script:python depends on org@ +p << r.p; +x << r.x; +@@ + +msg="WARNING ERR_CAST can be used with %s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r.p; +x << r.x; +@@ + +msg="WARNING: ERR_CAST can be used with %s" % (x) +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/api/kfree_mismatch.cocci b/scripts/coccinelle/api/kfree_mismatch.cocci new file mode 100644 index 000000000..bc90d0c07 --- /dev/null +++ b/scripts/coccinelle/api/kfree_mismatch.cocci @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Check that kvmalloc'ed memory is freed by kfree functions, +/// vmalloc'ed by vfree functions and kvmalloc'ed by kvfree +/// functions. +/// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS +// Options: --no-includes --include-headers +// + +virtual patch +virtual report +virtual org +virtual context + +@alloc@ +expression E, E1; +position kok, vok; +@@ + +( + if (...) { + ... + E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\| + kmalloc_node\|kzalloc_node\|kmalloc_array\| + kmalloc_array_node\|kcalloc_node\)(...)@kok + ... + } else { + ... + E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\| + vzalloc_node\|vmalloc_32\| + vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\| + __vmalloc_node\)(...)@vok + ... + } +| + E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\|kmalloc_node\|kzalloc_node\| + kmalloc_array\|kmalloc_array_node\|kcalloc_node\)(...)@kok + ... when != E = E1 + when any + if (E == NULL) { + ... + E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\| + vzalloc_node\|vmalloc_32\| + vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\| + __vmalloc_node\)(...)@vok + ... + } +) + +@free@ +expression E; +position fok; +@@ + + E = \(kvmalloc\|kvzalloc\|kvcalloc\|kvzalloc_node\|kvmalloc_node\| + kvmalloc_array\)(...) + ... + kvfree(E)@fok + +@vfree depends on !patch@ +expression E; +position a != alloc.kok; +position f != free.fok; +@@ + +* E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\|kmalloc_node\| +* kzalloc_node\|kmalloc_array\|kmalloc_array_node\| +* kcalloc_node\)(...)@a + ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } + when != is_vmalloc_addr(E) + when any +* \(vfree\|vfree_atomic\|kvfree\)(E)@f + +@depends on patch exists@ +expression E; +position a != alloc.kok; +position f != free.fok; +@@ + + E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\|kmalloc_node\| + kzalloc_node\|kmalloc_array\|kmalloc_array_node\| + kcalloc_node\)(...)@a + ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } + when != is_vmalloc_addr(E) + when any +- \(vfree\|vfree_atomic\|kvfree\)(E)@f ++ kfree(E) + +@kfree depends on !patch@ +expression E; +position a != alloc.vok; +position f != free.fok; +@@ + +* E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\| +* vmalloc_32\|vmalloc_32_user\|__vmalloc\| +* __vmalloc_node_range\|__vmalloc_node\)(...)@a + ... when != is_vmalloc_addr(E) + when any +* \(kfree\|kfree_sensitive\|kvfree\)(E)@f + +@depends on patch exists@ +expression E; +position a != alloc.vok; +position f != free.fok; +@@ + + E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\| + vmalloc_32\|vmalloc_32_user\|__vmalloc\| + __vmalloc_node_range\|__vmalloc_node\)(...)@a + ... when != is_vmalloc_addr(E) + when any +- \(kfree\|kvfree\)(E)@f ++ vfree(E) + +@kvfree depends on !patch@ +expression E; +position a, f; +@@ + +* E = \(kvmalloc\|kvzalloc\|kvcalloc\|kvzalloc_node\|kvmalloc_node\| +* kvmalloc_array\)(...)@a + ... when != is_vmalloc_addr(E) + when any +* \(kfree\|kfree_sensitive\|vfree\|vfree_atomic\)(E)@f + +@depends on patch exists@ +expression E; +@@ + + E = \(kvmalloc\|kvzalloc\|kvcalloc\|kvzalloc_node\|kvmalloc_node\| + kvmalloc_array\)(...) + ... when != is_vmalloc_addr(E) + when any +- \(kfree\|vfree\)(E) ++ kvfree(E) + +@kvfree_switch depends on !patch@ +expression alloc.E; +position f; +@@ + + ... when != is_vmalloc_addr(E) + when any +* \(kfree\|kfree_sensitive\|vfree\|vfree_atomic\)(E)@f + +@depends on patch exists@ +expression alloc.E; +position f; +@@ + + ... when != is_vmalloc_addr(E) + when any +( +- \(kfree\|vfree\)(E)@f ++ kvfree(E) +| +- kfree_sensitive(E)@f ++ kvfree_sensitive(E) +) + +@script: python depends on report@ +a << vfree.a; +f << vfree.f; +@@ + +msg = "WARNING kmalloc is used to allocate this memory at line %s" % (a[0].line) +coccilib.report.print_report(f[0], msg) + +@script: python depends on org@ +a << vfree.a; +f << vfree.f; +@@ + +msg = "WARNING kmalloc is used to allocate this memory at line %s" % (a[0].line) +coccilib.org.print_todo(f[0], msg) + +@script: python depends on report@ +a << kfree.a; +f << kfree.f; +@@ + +msg = "WARNING vmalloc is used to allocate this memory at line %s" % (a[0].line) +coccilib.report.print_report(f[0], msg) + +@script: python depends on org@ +a << kfree.a; +f << kfree.f; +@@ + +msg = "WARNING vmalloc is used to allocate this memory at line %s" % (a[0].line) +coccilib.org.print_todo(f[0], msg) + +@script: python depends on report@ +a << kvfree.a; +f << kvfree.f; +@@ + +msg = "WARNING kvmalloc is used to allocate this memory at line %s" % (a[0].line) +coccilib.report.print_report(f[0], msg) + +@script: python depends on org@ +a << kvfree.a; +f << kvfree.f; +@@ + +msg = "WARNING kvmalloc is used to allocate this memory at line %s" % (a[0].line) +coccilib.org.print_todo(f[0], msg) + +@script: python depends on report@ +ka << alloc.kok; +va << alloc.vok; +f << kvfree_switch.f; +@@ + +msg = "WARNING kmalloc (line %s) && vmalloc (line %s) are used to allocate this memory" % (ka[0].line, va[0].line) +coccilib.report.print_report(f[0], msg) + +@script: python depends on org@ +ka << alloc.kok; +va << alloc.vok; +f << kvfree_switch.f; +@@ + +msg = "WARNING kmalloc (line %s) && vmalloc (line %s) are used to allocate this memory" % (ka[0].line, va[0].line) +coccilib.org.print_todo(f[0], msg) diff --git a/scripts/coccinelle/api/kfree_sensitive.cocci b/scripts/coccinelle/api/kfree_sensitive.cocci new file mode 100644 index 000000000..8d980ebf3 --- /dev/null +++ b/scripts/coccinelle/api/kfree_sensitive.cocci @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Use kfree_sensitive, kvfree_sensitive rather than memset or +/// memzero_explicit followed by kfree. +/// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS +// Options: --no-includes --include-headers +// +// Keywords: kfree_sensitive, kvfree_sensitive +// + +virtual context +virtual patch +virtual org +virtual report + +@initialize:python@ +@@ +# kmalloc_oob_in_memset uses memset to explicitly trigger out-of-bounds access +filter = frozenset(['kmalloc_oob_in_memset', + 'kfree_sensitive', 'kvfree_sensitive']) + +def relevant(p): + return not (filter & {el.current_element for el in p}) + +@cond@ +position ok; +@@ + +if (...) + \(memset@ok\|memzero_explicit@ok\)(...); + +@r depends on !patch forall@ +expression E; +position p : script:python() { relevant(p) }; +position m != cond.ok; +type T; +@@ + +( +* memset@m((T)E, 0, ...); +| +* memzero_explicit@m((T)E, ...); +) + ... when != E + when strict +* \(kfree\|vfree\|kvfree\)(E)@p; + +@rp_memzero depends on patch@ +expression E, size; +position p : script:python() { relevant(p) }; +position m != cond.ok; +type T; +@@ + +- memzero_explicit@m((T)E, size); + ... when != E + when strict +( +- kfree(E)@p; ++ kfree_sensitive(E); +| +- \(vfree\|kvfree\)(E)@p; ++ kvfree_sensitive(E, size); +) + +@rp_memset depends on patch@ +expression E, size; +position p : script:python() { relevant(p) }; +position m != cond.ok; +type T; +@@ + +- memset@m((T)E, 0, size); + ... when != E + when strict +( +- kfree(E)@p; ++ kfree_sensitive(E); +| +- \(vfree\|kvfree\)(E)@p; ++ kvfree_sensitive(E, size); +) + +@script:python depends on report@ +p << r.p; +m << r.m; +@@ + +msg = "WARNING opportunity for kfree_sensitive/kvfree_sensitive (memset at line %s)" +coccilib.report.print_report(p[0], msg % (m[0].line)) + +@script:python depends on org@ +p << r.p; +m << r.m; +@@ + +msg = "WARNING opportunity for kfree_sensitive/kvfree_sensitive (memset at line %s)" +coccilib.org.print_todo(p[0], msg % (m[0].line)) diff --git a/scripts/coccinelle/api/kmalloc_objs.cocci b/scripts/coccinelle/api/kmalloc_objs.cocci new file mode 100644 index 000000000..e9a415b7b --- /dev/null +++ b/scripts/coccinelle/api/kmalloc_objs.cocci @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use kmalloc_obj family of macros for allocations +/// +// Confidence: High +// Options: --include-headers-for-types --all-includes --include-headers --keep-comments + +virtual patch + +@initialize:python@ +@@ +import sys + +def alloc_array(name): + func = "FAILED_RENAME" + if name == "kmalloc_array": + func = "kmalloc_objs" + elif name == "kvmalloc_array": + func = "kvmalloc_objs" + elif name == "kcalloc": + func = "kzalloc_objs" + elif name == "kvcalloc": + func = "kvzalloc_objs" + else: + print(f"Unknown transform for {name}", file=sys.stderr) + return func + +// This excludes anything that is assigning to or from integral types or +// string literals. Everything else gets the sizeof() extracted for the +// kmalloc_obj() type/var argument. sizeof(void *) is also excluded because +// it will need case-by-case double-checking to make sure the right type is +// being assigned. +@direct depends on patch && !(file in "tools") && !(file in "samples")@ +typedef u8, u16, u32, u64; +typedef __u8, __u16, __u32, __u64; +typedef uint8_t, uint16_t, uint32_t, uint64_t; +typedef uchar, ushort, uint, ulong; +typedef __le16, __le32, __le64; +typedef __be16, __be32, __be64; +typedef wchar_t; +type INTEGRAL = {u8,__u8,uint8_t,char,unsigned char,uchar,wchar_t, + u16,__u16,uint16_t,unsigned short,ushort, + u32,__u32,uint32_t,unsigned int,uint, + u64,__u64,uint64_t,unsigned long,ulong, + __le16,__le32,__le64,__be16,__be32,__be64}; +char [] STRING; +INTEGRAL *BYTES; +INTEGRAL **BYTES_PTRS; +type TYPE; +expression VAR; +expression GFP; +expression COUNT; +expression FLEX; +expression E; +identifier ALLOC =~ "^kv?[mz]alloc$"; +fresh identifier ALLOC_OBJ = ALLOC ## "_obj"; +fresh identifier ALLOC_FLEX = ALLOC ## "_flex"; +identifier ALLOC_ARRAY = {kmalloc_array,kvmalloc_array,kcalloc,kvcalloc}; +fresh identifier ALLOC_OBJS = script:python(ALLOC_ARRAY) { alloc_array(ALLOC_ARRAY) }; +@@ + +( +- VAR = ALLOC((sizeof(*VAR)), GFP) ++ VAR = ALLOC_OBJ(*VAR, GFP) +| + ALLOC((\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), GFP) +| + BYTES = ALLOC((sizeof(E)), GFP) +| + BYTES = ALLOC((sizeof(TYPE)), GFP) +| + BYTES_PTRS = ALLOC((sizeof(E)), GFP) +| + BYTES_PTRS = ALLOC((sizeof(TYPE)), GFP) +| + ALLOC((sizeof(void *)), GFP) +| +- ALLOC((sizeof(E)), GFP) ++ ALLOC_OBJ(E, GFP) +| +- ALLOC((sizeof(TYPE)), GFP) ++ ALLOC_OBJ(TYPE, GFP) +| + ALLOC_ARRAY(COUNT, (\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), GFP) +| + BYTES = ALLOC_ARRAY(COUNT, (sizeof(E)), GFP) +| + BYTES = ALLOC_ARRAY(COUNT, (sizeof(TYPE)), GFP) +| + BYTES_PTRS = ALLOC_ARRAY(COUNT, (sizeof(E)), GFP) +| + BYTES_PTRS = ALLOC_ARRAY(COUNT, (sizeof(TYPE)), GFP) +| + ALLOC_ARRAY((\(sizeof(STRING)\|sizeof(INTEGRAL)\|sizeof(INTEGRAL *)\)), COUNT, GFP) +| + BYTES = ALLOC_ARRAY((sizeof(E)), COUNT, GFP) +| + BYTES = ALLOC_ARRAY((sizeof(TYPE)), COUNT, GFP) +| + BYTES_PTRS = ALLOC_ARRAY((sizeof(E)), COUNT, GFP) +| + BYTES_PTRS = ALLOC_ARRAY((sizeof(TYPE)), COUNT, GFP) +| + ALLOC_ARRAY(COUNT, (sizeof(void *)), GFP) +| + ALLOC_ARRAY((sizeof(void *)), COUNT, GFP) +| +- ALLOC_ARRAY(COUNT, (sizeof(E)), GFP) ++ ALLOC_OBJS(E, COUNT, GFP) +| +- ALLOC_ARRAY(COUNT, (sizeof(TYPE)), GFP) ++ ALLOC_OBJS(TYPE, COUNT, GFP) +| +- ALLOC_ARRAY((sizeof(E)), COUNT, GFP) ++ ALLOC_OBJS(E, COUNT, GFP) +| +- ALLOC_ARRAY((sizeof(TYPE)), COUNT, GFP) ++ ALLOC_OBJS(TYPE, COUNT, GFP) +| +- ALLOC(struct_size(VAR, FLEX, COUNT), GFP) ++ ALLOC_FLEX(*VAR, FLEX, COUNT, GFP) +| +- ALLOC(struct_size_t(TYPE, FLEX, COUNT), GFP) ++ ALLOC_FLEX(TYPE, FLEX, COUNT, GFP) +) + +@drop_gfp_kernel depends on patch && !(file in "tools") && !(file in "samples")@ +identifier ALLOC = {kmalloc_obj,kmalloc_objs,kmalloc_flex, + kzalloc_obj,kzalloc_objs,kzalloc_flex, + kvmalloc_obj,kvmalloc_objs,kvmalloc_flex, + kvzalloc_obj,kvzalloc_objs,kvzalloc_flex}; +@@ + + ALLOC(... +- , GFP_KERNEL + ) diff --git a/scripts/coccinelle/api/kstrdup.cocci b/scripts/coccinelle/api/kstrdup.cocci new file mode 100644 index 000000000..8a6153467 --- /dev/null +++ b/scripts/coccinelle/api/kstrdup.cocci @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use kstrdup rather than duplicating its implementation +/// +// Confidence: High +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@depends on patch@ +expression from,to; +expression flag,E1,E2; +statement S; +@@ + +- to = kmalloc(strlen(from) + 1,flag); ++ to = kstrdup(from, flag); + ... when != \(from = E1 \| to = E1 \) + if (to==NULL || ...) S + ... when != \(from = E2 \| to = E2 \) +- strcpy(to, from); + +@depends on patch@ +expression x,from,to; +expression flag,E1,E2,E3; +statement S; +@@ + +- x = strlen(from) + 1; + ... when != \( x = E1 \| from = E1 \) +- to = \(kmalloc\|kzalloc\)(x,flag); ++ to = kstrdup(from, flag); + ... when != \(x = E2 \| from = E2 \| to = E2 \) + if (to==NULL || ...) S + ... when != \(x = E3 \| from = E3 \| to = E3 \) +- memcpy(to, from, x); + +// --------------------------------------------------------------------- + +@r1 depends on !patch exists@ +expression from,to; +expression flag,E1,E2; +statement S; +position p1,p2; +@@ + +* to = kmalloc@p1(strlen(from) + 1,flag); + ... when != \(from = E1 \| to = E1 \) + if (to==NULL || ...) S + ... when != \(from = E2 \| to = E2 \) +* strcpy@p2(to, from); + +@r2 depends on !patch exists@ +expression x,from,to; +expression flag,E1,E2,E3; +statement S; +position p1,p2; +@@ + +* x = strlen(from) + 1; + ... when != \( x = E1 \| from = E1 \) +* to = \(kmalloc@p1\|kzalloc@p1\)(x,flag); + ... when != \(x = E2 \| from = E2 \| to = E2 \) + if (to==NULL || ...) S + ... when != \(x = E3 \| from = E3 \| to = E3 \) +* memcpy@p2(to, from, x); + +@script:python depends on org@ +p1 << r1.p1; +p2 << r1.p2; +@@ + +cocci.print_main("WARNING opportunity for kstrdup",p1) +cocci.print_secs("strcpy",p2) + +@script:python depends on org@ +p1 << r2.p1; +p2 << r2.p2; +@@ + +cocci.print_main("WARNING opportunity for kstrdup",p1) +cocci.print_secs("memcpy",p2) + +@script:python depends on report@ +p1 << r1.p1; +p2 << r1.p2; +@@ + +msg = "WARNING opportunity for kstrdup (strcpy on line %s)" % (p2[0].line) +coccilib.report.print_report(p1[0], msg) + +@script:python depends on report@ +p1 << r2.p1; +p2 << r2.p2; +@@ + +msg = "WARNING opportunity for kstrdup (memcpy on line %s)" % (p2[0].line) +coccilib.report.print_report(p1[0], msg) diff --git a/scripts/coccinelle/api/kvmalloc.cocci b/scripts/coccinelle/api/kvmalloc.cocci new file mode 100644 index 000000000..5ddcb76b7 --- /dev/null +++ b/scripts/coccinelle/api/kvmalloc.cocci @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Find if/else condition with kmalloc/vmalloc calls. +/// Suggest to use kvmalloc instead. Same for kvfree. +/// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS +// Options: --no-includes --include-headers +// + +virtual patch +virtual report +virtual org +virtual context + +@initialize:python@ +@@ +filter = frozenset(['kvfree']) + +def relevant(p): + return not (filter & {el.current_element for el in p}) + +@kvmalloc depends on !patch@ +expression E, E1, size; +identifier flags; +binary operator cmp = {<=, <, ==, >, >=}; +identifier x; +type T; +position p; +@@ + +( +* if (size cmp E1 || ...)@p { + ... +* E = \(kmalloc\|kzalloc\|kcalloc\|kmalloc_node\|kzalloc_node\| +* kmalloc_array\|kmalloc_array_node\|kcalloc_node\) +* (..., size, \(flags\|GFP_KERNEL\|\(GFP_KERNEL\|flags\)|__GFP_NOWARN\), ...) + ... + } else { + ... +* E = \(vmalloc\|vzalloc\|vmalloc_node\|vzalloc_node\)(..., size, ...) + ... + } +| +* E = \(kmalloc\|kzalloc\|kcalloc\|kmalloc_node\|kzalloc_node\| +* kmalloc_array\|kmalloc_array_node\|kcalloc_node\) +* (..., size, \(flags\|GFP_KERNEL\|\(GFP_KERNEL\|flags\)|__GFP_NOWARN\), ...) + ... when != E = E1 + when != size = E1 + when any +* if (E == NULL)@p { + ... +* E = \(vmalloc\|vzalloc\|vmalloc_node\|vzalloc_node\)(..., size, ...) + ... + } +| +* T x = \(kmalloc\|kzalloc\|kcalloc\|kmalloc_node\|kzalloc_node\| +* kmalloc_array\|kmalloc_array_node\|kcalloc_node\) +* (..., size, \(flags\|GFP_KERNEL\|\(GFP_KERNEL\|flags\)|__GFP_NOWARN\), ...); + ... when != x = E1 + when != size = E1 + when any +* if (x == NULL)@p { + ... +* x = \(vmalloc\|vzalloc\|vmalloc_node\|vzalloc_node\)(..., size, ...) + ... + } +) + +@kvfree depends on !patch@ +expression E; +position p : script:python() { relevant(p) }; +@@ + +* if (is_vmalloc_addr(E))@p { + ... +* vfree(E) + ... + } else { + ... when != krealloc(E, ...) + when any +* \(kfree\|kfree_sensitive\)(E) + ... + } + +@depends on patch@ +expression E, E1, size, node; +binary operator cmp = {<=, <, ==, >, >=}; +identifier flags, x; +type T; +@@ + +( +- if (size cmp E1) +- E = kmalloc(size, flags); +- else +- E = vmalloc(size); ++ E = kvmalloc(size, flags); +| +- if (size cmp E1) +- E = kmalloc(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\)); +- else +- E = vmalloc(size); ++ E = kvmalloc(size, GFP_KERNEL); +| +- E = kmalloc(size, flags | __GFP_NOWARN); +- if (E == NULL) +- E = vmalloc(size); ++ E = kvmalloc(size, flags); +| +- E = kmalloc(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\)); +- if (E == NULL) +- E = vmalloc(size); ++ E = kvmalloc(size, GFP_KERNEL); +| +- T x = kmalloc(size, flags | __GFP_NOWARN); +- if (x == NULL) +- x = vmalloc(size); ++ T x = kvmalloc(size, flags); +| +- T x = kmalloc(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\)); +- if (x == NULL) +- x = vmalloc(size); ++ T x = kvmalloc(size, GFP_KERNEL); +| +- if (size cmp E1) +- E = kzalloc(size, flags); +- else +- E = vzalloc(size); ++ E = kvzalloc(size, flags); +| +- if (size cmp E1) +- E = kzalloc(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\)); +- else +- E = vzalloc(size); ++ E = kvzalloc(size, GFP_KERNEL); +| +- E = kzalloc(size, flags | __GFP_NOWARN); +- if (E == NULL) +- E = vzalloc(size); ++ E = kvzalloc(size, flags); +| +- E = kzalloc(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\)); +- if (E == NULL) +- E = vzalloc(size); ++ E = kvzalloc(size, GFP_KERNEL); +| +- T x = kzalloc(size, flags | __GFP_NOWARN); +- if (x == NULL) +- x = vzalloc(size); ++ T x = kvzalloc(size, flags); +| +- T x = kzalloc(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\)); +- if (x == NULL) +- x = vzalloc(size); ++ T x = kvzalloc(size, GFP_KERNEL); +| +- if (size cmp E1) +- E = kmalloc_node(size, flags, node); +- else +- E = vmalloc_node(size, node); ++ E = kvmalloc_node(size, flags, node); +| +- if (size cmp E1) +- E = kmalloc_node(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\), node); +- else +- E = vmalloc_node(size, node); ++ E = kvmalloc_node(size, GFP_KERNEL, node); +| +- E = kmalloc_node(size, flags | __GFP_NOWARN, node); +- if (E == NULL) +- E = vmalloc_node(size, node); ++ E = kvmalloc_node(size, flags, node); +| +- E = kmalloc_node(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\), node); +- if (E == NULL) +- E = vmalloc_node(size, node); ++ E = kvmalloc_node(size, GFP_KERNEL, node); +| +- T x = kmalloc_node(size, flags | __GFP_NOWARN, node); +- if (x == NULL) +- x = vmalloc_node(size, node); ++ T x = kvmalloc_node(size, flags, node); +| +- T x = kmalloc_node(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\), node); +- if (x == NULL) +- x = vmalloc_node(size, node); ++ T x = kvmalloc_node(size, GFP_KERNEL, node); +| +- if (size cmp E1) +- E = kvzalloc_node(size, flags, node); +- else +- E = vzalloc_node(size, node); ++ E = kvzalloc_node(size, flags, node); +| +- if (size cmp E1) +- E = kvzalloc_node(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\), node); +- else +- E = vzalloc_node(size, node); ++ E = kvzalloc_node(size, GFP_KERNEL, node); +| +- E = kvzalloc_node(size, flags | __GFP_NOWARN, node); +- if (E == NULL) +- E = vzalloc_node(size, node); ++ E = kvzalloc_node(size, flags, node); +| +- E = kvzalloc_node(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\), node); +- if (E == NULL) +- E = vzalloc_node(size, node); ++ E = kvzalloc_node(size, GFP_KERNEL, node); +| +- T x = kvzalloc_node(size, flags | __GFP_NOWARN, node); +- if (x == NULL) +- x = vzalloc_node(size, node); ++ T x = kvzalloc_node(size, flags, node); +| +- T x = kvzalloc_node(size, \(GFP_KERNEL\|GFP_KERNEL|__GFP_NOWARN\), node); +- if (x == NULL) +- x = vzalloc_node(size, node); ++ T x = kvzalloc_node(size, GFP_KERNEL, node); +) + +@depends on patch@ +expression E; +position p : script:python() { relevant(p) }; +@@ + +- if (is_vmalloc_addr(E))@p +- vfree(E); +- else +- kfree(E); ++ kvfree(E); + +@script: python depends on report@ +p << kvmalloc.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for kvmalloc") + +@script: python depends on org@ +p << kvmalloc.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING opportunity for kvmalloc") + +@script: python depends on report@ +p << kvfree.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for kvfree") + +@script: python depends on org@ +p << kvfree.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING opportunity for kvfree") diff --git a/scripts/coccinelle/api/memdup.cocci b/scripts/coccinelle/api/memdup.cocci new file mode 100644 index 000000000..d28741c69 --- /dev/null +++ b/scripts/coccinelle/api/memdup.cocci @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use kmemdup rather than duplicating its implementation +/// +// Confidence: High +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@r1@ +expression from,to; +expression flag; +position p; +@@ + + to = \(kmalloc@p\|kzalloc@p\)(strlen(from) + 1,flag); + +@r2@ +expression x,from,to; +expression flag,E1; +position p; +@@ + + x = strlen(from) + 1; + ... when != \( x = E1 \| from = E1 \) + to = \(kmalloc@p\|kzalloc@p\)(x,flag); + +@depends on patch@ +expression from,to,size,flag; +position p != {r1.p,r2.p}; +statement S; +@@ + +- to = \(kmalloc@p\|kzalloc@p\)(size,flag); ++ to = kmemdup(from,size,flag); + if (to==NULL || ...) S +- memcpy(to, from, size); + +@r depends on !patch@ +expression from,to,size,flag; +position p != {r1.p,r2.p}; +statement S; +@@ + +* to = \(kmalloc@p\|kzalloc@p\)(size,flag); + if (to==NULL || ...) S +* memcpy(to, from, size); + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING opportunity for kmemdup") + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for kmemdup") diff --git a/scripts/coccinelle/api/memdup_user.cocci b/scripts/coccinelle/api/memdup_user.cocci new file mode 100644 index 000000000..03e7afa09 --- /dev/null +++ b/scripts/coccinelle/api/memdup_user.cocci @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use memdup_user rather than duplicating its implementation +/// This is a little bit restricted to reduce false positives +/// +// Confidence: High +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@initialize:python@ +@@ +filter = frozenset(['memdup_user', 'vmemdup_user']) + +def relevant(p): + return not (filter & {el.current_element for el in p}) + +@depends on patch@ +expression from,to,size; +identifier l1,l2; +position p : script:python() { relevant(p) }; +@@ + +- to = \(kmalloc@p\|kzalloc@p\) +- (size,\(GFP_KERNEL\|GFP_USER\| +- \(GFP_KERNEL\|GFP_USER\)|__GFP_NOWARN\)); ++ to = memdup_user(from,size); + if ( +- to==NULL ++ IS_ERR(to) + || ...) { + <+... when != goto l1; +- -ENOMEM ++ PTR_ERR(to) + ...+> + } +- if (copy_from_user(to, from, size) != 0) { +- <+... when != goto l2; +- -EFAULT +- ...+> +- } + +@depends on patch@ +expression from,to,size; +identifier l1,l2; +position p : script:python() { relevant(p) }; +@@ + +- to = \(kvmalloc@p\|kvzalloc@p\)(size,\(GFP_KERNEL\|GFP_USER\)); ++ to = vmemdup_user(from,size); + if ( +- to==NULL ++ IS_ERR(to) + || ...) { + <+... when != goto l1; +- -ENOMEM ++ PTR_ERR(to) + ...+> + } +- if (copy_from_user(to, from, size) != 0) { +- <+... when != goto l2; +- -EFAULT +- ...+> +- } + +@r depends on !patch@ +expression from,to,size; +position p : script:python() { relevant(p) }; +statement S1,S2; +@@ + +* to = \(kmalloc@p\|kzalloc@p\) + (size,\(GFP_KERNEL\|GFP_USER\| + \(GFP_KERNEL\|GFP_USER\)|__GFP_NOWARN\)); + if (to==NULL || ...) S1 + if (copy_from_user(to, from, size) != 0) + S2 + +@rv depends on !patch@ +expression from,to,size; +position p : script:python() { relevant(p) }; +statement S1,S2; +@@ + +* to = \(kvmalloc@p\|kvzalloc@p\)(size,\(GFP_KERNEL\|GFP_USER\)); + if (to==NULL || ...) S1 + if (copy_from_user(to, from, size) != 0) + S2 + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING opportunity for memdup_user") + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for memdup_user") + +@script:python depends on org@ +p << rv.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING opportunity for vmemdup_user") + +@script:python depends on report@ +p << rv.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for vmemdup_user") diff --git a/scripts/coccinelle/api/platform_get_irq.cocci b/scripts/coccinelle/api/platform_get_irq.cocci new file mode 100644 index 000000000..06b6a95e2 --- /dev/null +++ b/scripts/coccinelle/api/platform_get_irq.cocci @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +/// Remove dev_err() messages after platform_get_irq*() failures +// +// Confidence: Medium +// Options: --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@depends on context@ +expression ret; +struct platform_device *E; +@@ + +ret = +( +platform_get_irq +| +platform_get_irq_byname +)(E, ...); + +if ( \( ret < 0 \| ret <= 0 \) ) +{ +( +if (ret != -EPROBE_DEFER) +{ ... +*dev_err(...); +... } +| +... +*dev_err(...); +) +... +} + +@depends on patch@ +expression ret; +struct platform_device *E; +@@ + +ret = +( +platform_get_irq +| +platform_get_irq_byname +)(E, ...); + +if ( \( ret < 0 \| ret <= 0 \) ) +{ +( +-if (ret != -EPROBE_DEFER) +-{ ... +-dev_err(...); +-... } +| +... +-dev_err(...); +) +... +} + +@r depends on org || report@ +position p1; +expression ret; +struct platform_device *E; +@@ + +ret = +( +platform_get_irq +| +platform_get_irq_byname +)(E, ...); + +if ( \( ret < 0 \| ret <= 0 \) ) +{ +( +if (ret != -EPROBE_DEFER) +{ ... +dev_err@p1(...); +... } +| +... +dev_err@p1(...); +) +... +} + +@script:python depends on org@ +p1 << r.p1; +@@ + +cocci.print_main(p1) + +@script:python depends on report@ +p1 << r.p1; +@@ + +msg = "line %s is redundant because platform_get_irq() already prints an error" % (p1[0].line) +coccilib.report.print_report(p1[0],msg) diff --git a/scripts/coccinelle/api/platform_no_drv_owner.cocci b/scripts/coccinelle/api/platform_no_drv_owner.cocci new file mode 100644 index 000000000..5e869858b --- /dev/null +++ b/scripts/coccinelle/api/platform_no_drv_owner.cocci @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Remove .owner field if calls are used which set it automatically +/// +// Confidence: High +// Copyright: (C) 2014 Wolfram Sang. + +virtual patch +virtual context +virtual org +virtual report + +@match1@ +declarer name builtin_i2c_driver; +declarer name builtin_platform_driver; +declarer name builtin_platform_driver_probe; +declarer name module_i2c_driver; +declarer name module_platform_driver; +declarer name module_platform_driver_probe; +identifier __driver; +@@ +( + builtin_i2c_driver(__driver); +| + builtin_platform_driver(__driver); +| + builtin_platform_driver_probe(__driver, ...); +| + module_i2c_driver(__driver); +| + module_platform_driver(__driver); +| + module_platform_driver_probe(__driver, ...); +) + +@fix1 depends on match1 && patch && !context && !org && !report@ +identifier match1.__driver; +@@ + static struct platform_driver __driver = { + .driver = { +- .owner = THIS_MODULE, + } + }; + +@fix1_i2c depends on match1 && patch && !context && !org && !report@ +identifier match1.__driver; +@@ + static struct i2c_driver __driver = { + .driver = { +- .owner = THIS_MODULE, + } + }; + +@match2@ +identifier __driver; +@@ +( + platform_driver_register(&__driver) +| + platform_driver_probe(&__driver, ...) +| + platform_create_bundle(&__driver, ...) +| + i2c_add_driver(&__driver) +) + +@fix2 depends on match2 && patch && !context && !org && !report@ +identifier match2.__driver; +@@ + static struct platform_driver __driver = { + .driver = { +- .owner = THIS_MODULE, + } + }; + +@fix2_i2c depends on match2 && patch && !context && !org && !report@ +identifier match2.__driver; +@@ + static struct i2c_driver __driver = { + .driver = { +- .owner = THIS_MODULE, + } + }; + +// ---------------------------------------------------------------------------- + +@fix1_context depends on match1 && !patch && (context || org || report)@ +identifier match1.__driver; +position j0; +@@ + + static struct platform_driver __driver = { + .driver = { +* .owner@j0 = THIS_MODULE, + } + }; + +@fix1_i2c_context depends on match1 && !patch && (context || org || report)@ +identifier match1.__driver; +position j0; +@@ + + static struct i2c_driver __driver = { + .driver = { +* .owner@j0 = THIS_MODULE, + } + }; + +@fix2_context depends on match2 && !patch && (context || org || report)@ +identifier match2.__driver; +position j0; +@@ + + static struct platform_driver __driver = { + .driver = { +* .owner@j0 = THIS_MODULE, + } + }; + +@fix2_i2c_context depends on match2 && !patch && (context || org || report)@ +identifier match2.__driver; +position j0; +@@ + + static struct i2c_driver __driver = { + .driver = { +* .owner@j0 = THIS_MODULE, + } + }; + +// ---------------------------------------------------------------------------- + +@script:python fix1_org depends on org@ +j0 << fix1_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.org.print_todo(j0[0], msg) + +@script:python fix1_i2c_org depends on org@ +j0 << fix1_i2c_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.org.print_todo(j0[0], msg) + +@script:python fix2_org depends on org@ +j0 << fix2_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.org.print_todo(j0[0], msg) + +@script:python fix2_i2c_org depends on org@ +j0 << fix2_i2c_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.org.print_todo(j0[0], msg) + +// ---------------------------------------------------------------------------- + +@script:python fix1_report depends on report@ +j0 << fix1_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.report.print_report(j0[0], msg) + +@script:python fix1_i2c_report depends on report@ +j0 << fix1_i2c_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.report.print_report(j0[0], msg) + +@script:python fix2_report depends on report@ +j0 << fix2_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.report.print_report(j0[0], msg) + +@script:python fix2_i2c_report depends on report@ +j0 << fix2_i2c_context.j0; +@@ + +msg = "No need to set .owner here. The core will do it." +coccilib.report.print_report(j0[0], msg) + diff --git a/scripts/coccinelle/api/pm_runtime.cocci b/scripts/coccinelle/api/pm_runtime.cocci new file mode 100644 index 000000000..b72048941 --- /dev/null +++ b/scripts/coccinelle/api/pm_runtime.cocci @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Make sure pm_runtime_* calls do not unnecessarily use IS_ERR_VALUE +/// +// Keywords: pm_runtime +// Confidence: Medium +// Copyright (C) 2013 Texas Instruments Incorporated - +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --include-headers + +virtual patch +virtual context +virtual org +virtual report + +//---------------------------------------------------------- +// Detection +//---------------------------------------------------------- + +@runtime_bad_err_handle exists@ +expression ret; +position p; +@@ +( +ret@p = \(pm_runtime_idle\| + pm_runtime_suspend\| + pm_runtime_autosuspend\| + pm_runtime_resume\| + pm_request_idle\| + pm_request_resume\| + pm_request_autosuspend\| + pm_runtime_get\| + pm_runtime_get_sync\| + pm_runtime_put\| + pm_runtime_put_autosuspend\| + pm_runtime_put_sync\| + pm_runtime_put_sync_suspend\| + pm_runtime_put_sync_autosuspend\| + pm_runtime_set_active\| + pm_schedule_suspend\| + pm_generic_runtime_suspend\| + pm_generic_runtime_resume\)(...); +... +IS_ERR_VALUE(ret) +... +) + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@depends on context@ +identifier pm_runtime_api; +expression ret; +position runtime_bad_err_handle.p; +@@ +( +ret@p = pm_runtime_api(...); +... +* IS_ERR_VALUE(ret) +... +) + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@depends on patch@ +identifier pm_runtime_api; +expression ret; +position runtime_bad_err_handle.p; +@@ +( +ret@p = pm_runtime_api(...); +... +- IS_ERR_VALUE(ret) ++ ret < 0 +... +) + +//---------------------------------------------------------- +// For org and report mode +//---------------------------------------------------------- + +@r depends on (org || report) exists@ +position p1, p2; +identifier pm_runtime_api; +expression ret; +position runtime_bad_err_handle.p; +@@ +( +ret@p = pm_runtime_api@p1(...); +... +IS_ERR_VALUE@p2(ret) +... +) + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +pm_runtime_api << r.pm_runtime_api; +@@ + +cocci.print_main(pm_runtime_api,p1) +cocci.print_secs("IS_ERR_VALUE",p2) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +pm_runtime_api << r.pm_runtime_api; +@@ + +msg = "%s returns < 0 as error. Unnecessary IS_ERR_VALUE at line %s" % (pm_runtime_api, p2[0].line) +coccilib.report.print_report(p1[0],msg) diff --git a/scripts/coccinelle/api/resource_size.cocci b/scripts/coccinelle/api/resource_size.cocci new file mode 100644 index 000000000..16857072d --- /dev/null +++ b/scripts/coccinelle/api/resource_size.cocci @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Use resource_size function on resource object +/// instead of explicit computation. +/// +// Confidence: High +// Copyright: (C) 2009, 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2009, 2010 Julia Lawall, DIKU. +// Copyright: (C) 2009, 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: +// +// Keywords: resource_size +// Version min: 2.6.27 resource_size +// + +virtual context +virtual patch +virtual org +virtual report + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@r_context depends on context && !patch && !org@ +struct resource *res; +@@ + +* (res->end - res->start) + 1 + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@r_patch depends on !context && patch && !org@ +struct resource *res; +@@ + +- (res->end - res->start) + 1 ++ resource_size(res) + +//---------------------------------------------------------- +// For org mode +//---------------------------------------------------------- + + +@r_org depends on !context && !patch && (org || report)@ +struct resource *res; +position p; +@@ + + (res->end@p - res->start) + 1 + +@rbad_org depends on !context && !patch && (org || report)@ +struct resource *res; +position p != r_org.p; +@@ + + res->end@p - res->start + +@script:python depends on org@ +p << r_org.p; +x << r_org.res; +@@ + +msg="ERROR with %s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << r_org.p; +x << r_org.res; +@@ + +msg="ERROR: Missing resource_size with %s" % (x) +coccilib.report.print_report(p[0], msg) + +@script:python depends on org@ +p << rbad_org.p; +x << rbad_org.res; +@@ + +msg="WARNING with %s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) + +@script:python depends on report@ +p << rbad_org.p; +x << rbad_org.res; +@@ + +msg="WARNING: Suspicious code. resource_size is maybe missing with %s" % (x) +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/api/simple_open.cocci b/scripts/coccinelle/api/simple_open.cocci new file mode 100644 index 000000000..c121876d5 --- /dev/null +++ b/scripts/coccinelle/api/simple_open.cocci @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 +/// Remove an open coded simple_open() function +/// and replace file operations references to the function +/// with simple_open() instead. +/// +// Confidence: High +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual report + +@ open depends on patch @ +identifier open_f != simple_open; +identifier i, f; +@@ +-int open_f(struct inode *i, struct file *f) +-{ +( +-if (i->i_private) +-f->private_data = i->i_private; +| +-f->private_data = i->i_private; +) +-return 0; +-} + +@ has_open depends on open @ +identifier fops; +identifier open.open_f; +@@ +struct file_operations fops = { +..., +-.open = open_f, ++.open = simple_open, +... +}; + +@ openr depends on report @ +identifier open_f != simple_open; +identifier i, f; +position p; +@@ +int open_f@p(struct inode *i, struct file *f) +{ +( +if (i->i_private) +f->private_data = i->i_private; +| +f->private_data = i->i_private; +) +return 0; +} + +@ has_openr depends on openr @ +identifier fops; +identifier openr.open_f; +position p; +@@ +struct file_operations fops = { +..., +.open = open_f@p, +... +}; + +@script:python@ +pf << openr.p; +ps << has_openr.p; +@@ + +coccilib.report.print_report(pf[0],"WARNING opportunity for simple_open, see also structure on line %s"%(ps[0].line)) diff --git a/scripts/coccinelle/api/stream_open.cocci b/scripts/coccinelle/api/stream_open.cocci new file mode 100644 index 000000000..50ab60c81 --- /dev/null +++ b/scripts/coccinelle/api/stream_open.cocci @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0 +// Author: Kirill Smelkov (kirr@nexedi.com) +// +// Search for stream-like files that are using nonseekable_open and convert +// them to stream_open. A stream-like file is a file that does not use ppos in +// its read and write. Rationale for the conversion is to avoid deadlock in +// between read and write. + +virtual report +virtual patch +virtual explain // explain decisions in the patch (SPFLAGS="-D explain") + +// stream-like reader & writer - ones that do not depend on f_pos. +@ stream_reader @ +identifier readstream, ppos; +identifier f, buf, len; +type loff_t; +@@ + ssize_t readstream(struct file *f, char *buf, size_t len, loff_t *ppos) + { + ... when != ppos + } + +@ stream_writer @ +identifier writestream, ppos; +identifier f, buf, len; +type loff_t; +@@ + ssize_t writestream(struct file *f, const char *buf, size_t len, loff_t *ppos) + { + ... when != ppos + } + + +// a function that blocks +@ blocks @ +identifier block_f; +identifier wait =~ "^wait_.*"; +@@ + block_f(...) { + ... when exists + wait(...) + ... when exists + } + +// stream_reader that can block inside. +// +// XXX wait_* can be called not directly from current function (e.g. func -> f -> g -> wait()) +// XXX currently reader_blocks supports only direct and 1-level indirect cases. +@ reader_blocks_direct @ +identifier stream_reader.readstream; +identifier wait =~ "^wait_.*"; +@@ + readstream(...) + { + ... when exists + wait(...) + ... when exists + } + +@ reader_blocks_1 @ +identifier stream_reader.readstream; +identifier blocks.block_f; +@@ + readstream(...) + { + ... when exists + block_f(...) + ... when exists + } + +@ reader_blocks depends on reader_blocks_direct || reader_blocks_1 @ +identifier stream_reader.readstream; +@@ + readstream(...) { + ... + } + + +// file_operations + whether they have _any_ .read, .write, .llseek ... at all. +// +// XXX add support for file_operations xxx[N] = ... (sound/core/pcm_native.c) +@ fops0 @ +identifier fops; +@@ + struct file_operations fops = { + ... + }; + +@ has_read @ +identifier fops0.fops; +identifier read_f; +@@ + struct file_operations fops = { + .read = read_f, + }; + +@ has_read_iter @ +identifier fops0.fops; +identifier read_iter_f; +@@ + struct file_operations fops = { + .read_iter = read_iter_f, + }; + +@ has_write @ +identifier fops0.fops; +identifier write_f; +@@ + struct file_operations fops = { + .write = write_f, + }; + +@ has_write_iter @ +identifier fops0.fops; +identifier write_iter_f; +@@ + struct file_operations fops = { + .write_iter = write_iter_f, + }; + +@ has_llseek @ +identifier fops0.fops; +identifier llseek_f; +@@ + struct file_operations fops = { + .llseek = llseek_f, + }; + +@ has_no_llseek @ +identifier fops0.fops; +@@ + struct file_operations fops = { + }; + +@ has_noop_llseek @ +identifier fops0.fops; +@@ + struct file_operations fops = { + .llseek = noop_llseek, + }; + +@ has_mmap @ +identifier fops0.fops; +identifier mmap_f; +@@ + struct file_operations fops = { + .mmap = mmap_f, + }; + +@ has_copy_file_range @ +identifier fops0.fops; +identifier copy_file_range_f; +@@ + struct file_operations fops = { + .copy_file_range = copy_file_range_f, + }; + +@ has_remap_file_range @ +identifier fops0.fops; +identifier remap_file_range_f; +@@ + struct file_operations fops = { + .remap_file_range = remap_file_range_f, + }; + +@ has_splice_read @ +identifier fops0.fops; +identifier splice_read_f; +@@ + struct file_operations fops = { + .splice_read = splice_read_f, + }; + +@ has_splice_write @ +identifier fops0.fops; +identifier splice_write_f; +@@ + struct file_operations fops = { + .splice_write = splice_write_f, + }; + + +// file_operations that is candidate for stream_open conversion - it does not +// use mmap and other methods that assume @offset access to file. +// +// XXX for simplicity require no .{read/write}_iter and no .splice_{read/write} for now. +// XXX maybe_steam.fops cannot be used in other rules - it gives "bad rule maybe_stream or bad variable fops". +@ maybe_stream depends on (!has_llseek || has_no_llseek || has_noop_llseek) && !has_mmap && !has_copy_file_range && !has_remap_file_range && !has_read_iter && !has_write_iter && !has_splice_read && !has_splice_write @ +identifier fops0.fops; +@@ + struct file_operations fops = { + }; + + +// ---- conversions ---- + +// XXX .open = nonseekable_open -> .open = stream_open +// XXX .open = func -> openfunc -> nonseekable_open + +// read & write +// +// if both are used in the same file_operations together with an opener - +// under that conditions we can use stream_open instead of nonseekable_open. +@ fops_rw depends on maybe_stream @ +identifier fops0.fops, openfunc; +identifier stream_reader.readstream; +identifier stream_writer.writestream; +@@ + struct file_operations fops = { + .open = openfunc, + .read = readstream, + .write = writestream, + }; + +@ report_rw depends on report @ +identifier fops_rw.openfunc; +position p1; +@@ + openfunc(...) { + <... + nonseekable_open@p1 + ...> + } + +@ script:python depends on report && reader_blocks @ +fops << fops0.fops; +p << report_rw.p1; +@@ +coccilib.report.print_report(p[0], + "ERROR: %s: .read() can deadlock .write(); change nonseekable_open -> stream_open to fix." % (fops,)) + +@ script:python depends on report && !reader_blocks @ +fops << fops0.fops; +p << report_rw.p1; +@@ +coccilib.report.print_report(p[0], + "WARNING: %s: .read() and .write() have stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) + + +@ explain_rw_deadlocked depends on explain && reader_blocks @ +identifier fops_rw.openfunc; +@@ + openfunc(...) { + <... +- nonseekable_open ++ nonseekable_open /* read & write (was deadlock) */ + ...> + } + + +@ explain_rw_nodeadlock depends on explain && !reader_blocks @ +identifier fops_rw.openfunc; +@@ + openfunc(...) { + <... +- nonseekable_open ++ nonseekable_open /* read & write (no direct deadlock) */ + ...> + } + +@ patch_rw depends on patch @ +identifier fops_rw.openfunc; +@@ + openfunc(...) { + <... +- nonseekable_open ++ stream_open + ...> + } + + +// read, but not write +@ fops_r depends on maybe_stream && !has_write @ +identifier fops0.fops, openfunc; +identifier stream_reader.readstream; +@@ + struct file_operations fops = { + .open = openfunc, + .read = readstream, + }; + +@ report_r depends on report @ +identifier fops_r.openfunc; +position p1; +@@ + openfunc(...) { + <... + nonseekable_open@p1 + ...> + } + +@ script:python depends on report @ +fops << fops0.fops; +p << report_r.p1; +@@ +coccilib.report.print_report(p[0], + "WARNING: %s: .read() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) + +@ explain_r depends on explain @ +identifier fops_r.openfunc; +@@ + openfunc(...) { + <... +- nonseekable_open ++ nonseekable_open /* read only */ + ...> + } + +@ patch_r depends on patch @ +identifier fops_r.openfunc; +@@ + openfunc(...) { + <... +- nonseekable_open ++ stream_open + ...> + } + + +// write, but not read +@ fops_w depends on maybe_stream && !has_read @ +identifier fops0.fops, openfunc; +identifier stream_writer.writestream; +@@ + struct file_operations fops = { + .open = openfunc, + .write = writestream, + }; + +@ report_w depends on report @ +identifier fops_w.openfunc; +position p1; +@@ + openfunc(...) { + <... + nonseekable_open@p1 + ...> + } + +@ script:python depends on report @ +fops << fops0.fops; +p << report_w.p1; +@@ +coccilib.report.print_report(p[0], + "WARNING: %s: .write() has stream semantic; safe to change nonseekable_open -> stream_open." % (fops,)) + +@ explain_w depends on explain @ +identifier fops_w.openfunc; +@@ + openfunc(...) { + <... +- nonseekable_open ++ nonseekable_open /* write only */ + ...> + } + +@ patch_w depends on patch @ +identifier fops_w.openfunc; +@@ + openfunc(...) { + <... +- nonseekable_open ++ stream_open + ...> + } + + +// no read, no write - don't change anything diff --git a/scripts/coccinelle/api/string_choices.cocci b/scripts/coccinelle/api/string_choices.cocci new file mode 100644 index 000000000..375045086 --- /dev/null +++ b/scripts/coccinelle/api/string_choices.cocci @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find places to use string_choices.h's various helpers. +// +// Confidence: Medium +// Options: --no-includes --include-headers +virtual patch +virtual context +virtual report + +@str_plural depends on patch@ +expression E; +@@ +( +- ((E == 1) ? "" : "s") ++ str_plural(E) +| +- ((E > 1) ? "s" : "") ++ str_plural(E) +) + +@str_plural_r depends on !patch@ +expression E; +position P; +@@ +( +* (E@P == 1) ? "" : "s" +| +* (E@P > 1) ? "s" : "" +) + +@script:python depends on report@ +p << str_plural_r.P; +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/api/vma_pages.cocci b/scripts/coccinelle/api/vma_pages.cocci new file mode 100644 index 000000000..10511b9bf --- /dev/null +++ b/scripts/coccinelle/api/vma_pages.cocci @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/// +/// Use vma_pages function on vma object instead of explicit computation. +/// +// Confidence: High +// Keywords: vma_pages vma +// Comment: Based on resource_size.cocci + +virtual context +virtual patch +virtual org +virtual report + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@r_context depends on context && !patch && !org && !report@ +struct vm_area_struct *vma; +@@ + +* (vma->vm_end - vma->vm_start) >> PAGE_SHIFT + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@r_patch depends on !context && patch && !org && !report@ +struct vm_area_struct *vma; +@@ + +- ((vma->vm_end - vma->vm_start) >> PAGE_SHIFT) ++ vma_pages(vma) + +//---------------------------------------------------------- +// For org mode +//---------------------------------------------------------- + +@r_org depends on !context && !patch && (org || report)@ +struct vm_area_struct *vma; +position p; +@@ + + (vma->vm_end@p - vma->vm_start) >> PAGE_SHIFT + +@script:python depends on report@ +p << r_org.p; +x << r_org.vma; +@@ + +msg="WARNING: Consider using vma_pages helper on %s" % (x) +coccilib.report.print_report(p[0], msg) + +@script:python depends on org@ +p << r_org.p; +x << r_org.vma; +@@ + +msg="WARNING: Consider using vma_pages helper on %s" % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +coccilib.org.print_todo(p[0], msg_safe) diff --git a/scripts/coccinelle/free/clk_put.cocci b/scripts/coccinelle/free/clk_put.cocci new file mode 100644 index 000000000..3c732cb72 --- /dev/null +++ b/scripts/coccinelle/free/clk_put.cocci @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find missing clk_puts. +/// +//# This only signals a missing clk_put when there is a clk_put later +//# in the same function. +//# False positives can be due to loops. +// +// Confidence: Moderate +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: + +virtual context +virtual org +virtual report + +@clk@ +expression e; +statement S,S1; +int ret; +position p1,p2,p3; +@@ + +e = clk_get@p1(...) +... when != clk_put(e) +if (<+...e...+>) S +... when any + when != clk_put(e) + when != if (...) { ... clk_put(e); ... } +( + if (ret == 0) S1 +| +if (...) + { ... + return 0; } +| +if (...) + { ... + return <+...e...+>; } +| +*if@p2 (...) + { ... when != clk_put(e) + when forall + return@p3 ...; } +) +... when any +clk_put(e); + +@script:python depends on org@ +p1 << clk.p1; +p2 << clk.p2; +p3 << clk.p3; +@@ + +cocci.print_main("clk_get",p1) +cocci.print_secs("if",p2) +cocci.print_secs("needed clk_put",p3) + +@script:python depends on report@ +p1 << clk.p1; +p2 << clk.p2; +p3 << clk.p3; +@@ + +msg = "ERROR: missing clk_put; clk_get on line %s and execution via conditional on line %s" % (p1[0].line,p2[0].line) +coccilib.report.print_report(p3[0],msg) diff --git a/scripts/coccinelle/free/devm_free.cocci b/scripts/coccinelle/free/devm_free.cocci new file mode 100644 index 000000000..947d7e685 --- /dev/null +++ b/scripts/coccinelle/free/devm_free.cocci @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find uses of standard freeing functons on values allocated using devm_ +/// functions. Values allocated using the devm_functions are freed when +/// the device is detached, and thus the use of the standard freeing +/// function would cause a double free. +/// See Documentation/driver-api/driver-model/devres.rst for more information. +/// +/// A difficulty of detecting this problem is that the standard freeing +/// function might be called from a different function than the one +/// containing the allocation function. It is thus necessary to make the +/// connection between the allocation function and the freeing function. +/// Here this is done using the specific argument text, which is prone to +/// false positives. There is no rule for the request_region and +/// request_mem_region variants because this heuristic seems to be a bit +/// less reliable in these cases. +/// +// Confidence: Moderate +// Copyright: (C) 2011 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2011 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report +virtual context + +@r depends on context || org || report@ +type T; +T x; +@@ + +( + x = devm_kmalloc(...) +| + x = devm_kvasprintf(...) +| + x = devm_kasprintf(...) +| + x = devm_kzalloc(...) +| + x = devm_kmalloc_array(...) +| + x = devm_kcalloc(...) +| + x = devm_kstrdup(...) +| + x = devm_kmemdup(...) +| + x = devm_get_free_pages(...) +| + x = devm_request_irq(...) +| + x = devm_ioremap(...) +| + x = devm_ioport_map(...) +) + +@safe depends on context || org || report exists@ +r.T x; +position p; +@@ + +( + x = kmalloc(...) +| + x = kmalloc_obj(...) +| + x = kmalloc_objs(...) +| + x = kvasprintf(...) +| + x = kasprintf(...) +| + x = kzalloc(...) +| + x = kzalloc_obj(...) +| + x = kzalloc_objs(...) +| + x = kmalloc_array(...) +| + x = kcalloc(...) +| + x = kstrdup(...) +| + x = kmemdup(...) +| + x = get_free_pages(...) +| + x = request_irq(...) +| + x = ioremap(...) +| + x = ioport_map(...) +) +... +( + kfree@p(x) +| + kfree_sensitive@p(x) +| + krealloc@p(x, ...) +| + free_pages@p(x, ...) +| + free_page@p(x) +| + free_irq@p(x) +| + iounmap@p(x) +| + ioport_unmap@p(x) +) + +@pb@ +r.T r.x; +position p != safe.p; +@@ + +( +* kfree@p(x) +| +* kfree_sensitive@p(x) +| +* krealloc@p(x, ...) +| +* free_pages@p(x, ...) +| +* free_page@p(x) +| +* free_irq@p(x) +| +* iounmap@p(x) +| +* ioport_unmap@p(x) +) + +@script:python depends on org@ +p << pb.p; +@@ + +msg="WARNING: invalid free of devm_ allocated data" +coccilib.org.print_todo(p[0], msg) + +@script:python depends on report@ +p << pb.p; +@@ + +msg="WARNING: invalid free of devm_ allocated data" +coccilib.report.print_report(p[0], msg) + diff --git a/scripts/coccinelle/free/ifnulldev_put.cocci b/scripts/coccinelle/free/ifnulldev_put.cocci new file mode 100644 index 000000000..1430d68ff --- /dev/null +++ b/scripts/coccinelle/free/ifnulldev_put.cocci @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Since commit b37a46683739 ("netdevice: add the case if dev is NULL"), +/// NULL check before dev_{put, hold} functions is not needed. +/// +/// Based on ifnullfree.cocci by Fabian Frederick. +/// +// Copyright: (C) 2022 Ziyang Xuan. +// Comments: - +// Options: --no-includes --include-headers +// Version min: 5.15 + +virtual patch +virtual org +virtual report +virtual context + +@r2 depends on patch@ +expression E; +@@ +- if (E != NULL) +( + __dev_put(E); +| + dev_put(E); +| + netdev_put(E, ...); +| + __dev_hold(E); +| + dev_hold(E); +| + netdev_hold(E, ...); +) + +@r depends on context || report || org @ +expression E; +position p; +@@ + +* if (E != NULL) +* \(__dev_put@p\|dev_put@p\|netdev_put@p\|__dev_hold@p\|dev_hold@p\| +* netdev_hold@p\)(E, ...); + +@script:python depends on org@ +p << r.p; +@@ + +cocci.print_main("NULL check before (net)dev_{put, hold} functions is not needed", p) + +@script:python depends on report@ +p << r.p; +@@ + +msg = "WARNING: NULL check before (net)dev_{put, hold} functions is not needed." +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/free/ifnullfree.cocci b/scripts/coccinelle/free/ifnullfree.cocci new file mode 100644 index 000000000..285b92d5c --- /dev/null +++ b/scripts/coccinelle/free/ifnullfree.cocci @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// NULL check before some freeing functions is not needed. +/// +/// Based on checkpatch warning +/// "kfree(NULL) is safe this check is probably not required" +/// and kfreeaddr.cocci by Julia Lawall. +/// +// Copyright: (C) 2014 Fabian Frederick. +// Comments: - +// Options: --no-includes --include-headers + +virtual patch +virtual org +virtual report +virtual context + +@r2 depends on patch@ +expression E; +@@ +- if (E != NULL) +( + kfree(E); +| + kvfree(E); +| + kfree_sensitive(E); +| + kvfree_sensitive(E, ...); +| + vfree(E); +| + debugfs_remove(E); +| + debugfs_remove_recursive(E); +| + usb_free_urb(E); +| + kmem_cache_destroy(E); +| + mempool_destroy(E); +| + dma_pool_destroy(E); +) + +@r depends on context || report || org @ +expression E; +position p; +@@ + +* if (E != NULL) +* \(kfree@p\|kvfree@p\|kfree_sensitive@p\|kvfree_sensitive@p\|vfree@p\| +* debugfs_remove@p\|debugfs_remove_recursive@p\| +* usb_free_urb@p\|kmem_cache_destroy@p\|mempool_destroy@p\| +* dma_pool_destroy@p\)(E, ...); + +@script:python depends on org@ +p << r.p; +@@ + +cocci.print_main("NULL check before that freeing function is not needed", p) + +@script:python depends on report@ +p << r.p; +@@ + +msg = "WARNING: NULL check before some freeing functions is not needed." +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/free/iounmap.cocci b/scripts/coccinelle/free/iounmap.cocci new file mode 100644 index 000000000..90d85fa7b --- /dev/null +++ b/scripts/coccinelle/free/iounmap.cocci @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find missing iounmaps. +/// +//# This only signals a missing iounmap when there is an iounmap later +//# in the same function. +//# False positives can be due to loops. +// +// Confidence: Moderate +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: + +virtual context +virtual org +virtual report + +@iom@ +expression e; +statement S,S1; +int ret; +position p1,p2,p3; +@@ + +e = \(ioremap@p1\)(...) +... when != iounmap(e) +if (<+...e...+>) S +... when any + when != iounmap(e) + when != if (...) { ... iounmap(e); ... } +( + if (ret == 0) S1 +| +if (...) + { ... + return 0; } +| +if (...) + { ... + return <+...e...+>; } +| +*if@p2 (...) + { ... when != iounmap(e) + when forall + return@p3 ...; } +) +... when any +iounmap(e); + +@script:python depends on org@ +p1 << iom.p1; +p2 << iom.p2; +p3 << iom.p3; +@@ + +cocci.print_main("ioremap",p1) +cocci.print_secs("if",p2) +cocci.print_secs("needed iounmap",p3) + +@script:python depends on report@ +p1 << iom.p1; +p2 << iom.p2; +p3 << iom.p3; +@@ + +msg = "ERROR: missing iounmap; ioremap on line %s and execution via conditional on line %s" % (p1[0].line,p2[0].line) +coccilib.report.print_report(p3[0],msg) diff --git a/scripts/coccinelle/free/kfree.cocci b/scripts/coccinelle/free/kfree.cocci new file mode 100644 index 000000000..6338bbac2 --- /dev/null +++ b/scripts/coccinelle/free/kfree.cocci @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find a use after free. +//# Values of variables may imply that some +//# execution paths are not possible, resulting in false positives. +//# Another source of false positives are macros such as +//# SCTP_DBG_OBJCNT_DEC that do not actually evaluate their argument +/// +// Confidence: Moderate +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report + +@free@ +expression E; +position p1; +@@ + +( + kfree@p1(E) +| + kfree_sensitive@p1(E) +) + +@print expression@ +constant char [] c; +expression free.E,E2; +type T; +position p; +identifier f; +@@ + +( + f(...,c,...,(T)E@p,...) +| + E@p == E2 +| + E@p != E2 +| + E2 == E@p +| + E2 != E@p +| + !E@p +| + E@p || ... +) + +@sz@ +expression free.E; +position p; +@@ + + sizeof(<+...E@p...+>) + +@loop exists@ +expression E; +identifier l; +position ok; +@@ + +while (1) { ... +( + kfree@ok(E) +| + kfree_sensitive@ok(E) +) + ... when != break; + when != goto l; + when forall +} + +@r exists@ +expression free.E, subE<=free.E, E2; +expression E1; +iterator iter; +statement S; +position free.p1!=loop.ok,p2!={print.p,sz.p}; +@@ + +( + kfree@p1(E,...) +| + kfree_sensitive@p1(E,...) +) +... +( + iter(...,subE,...) S // no use +| + list_remove_head(E1,subE,...) +| + subE = E2 +| + subE++ +| + ++subE +| + --subE +| + subE-- +| + &subE +| + BUG(...) +| + BUG_ON(...) +| + return_VALUE(...) +| + return_ACPI_STATUS(...) +| + E@p2 // bad use +) + +@script:python depends on org@ +p1 << free.p1; +p2 << r.p2; +@@ + +cocci.print_main("kfree",p1) +cocci.print_secs("ref",p2) + +@script:python depends on report@ +p1 << free.p1; +p2 << r.p2; +@@ + +msg = "ERROR: reference preceded by free on line %s" % (p1[0].line) +coccilib.report.print_report(p2[0],msg) diff --git a/scripts/coccinelle/free/kfreeaddr.cocci b/scripts/coccinelle/free/kfreeaddr.cocci new file mode 100644 index 000000000..85635a36f --- /dev/null +++ b/scripts/coccinelle/free/kfreeaddr.cocci @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Free of a structure field +/// +// Confidence: High +// Copyright: (C) 2013 Julia Lawall, INRIA/LIP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report +virtual context + +@r depends on context || report || org @ +expression e; +identifier f; +position p; +@@ + +( +* kfree@p(&e->f) +| +* kfree_sensitive@p(&e->f) +) + +@script:python depends on org@ +p << r.p; +@@ + +cocci.print_main("kfree",p) + +@script:python depends on report@ +p << r.p; +@@ + +msg = "ERROR: invalid free of structure field" +coccilib.report.print_report(p[0],msg) diff --git a/scripts/coccinelle/free/put_device.cocci b/scripts/coccinelle/free/put_device.cocci new file mode 100644 index 000000000..f09f1e79b --- /dev/null +++ b/scripts/coccinelle/free/put_device.cocci @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/// Find missing put_device for every of_find_device_by_node. +/// +// Confidence: Moderate +// Copyright: (C) 2018-2019 Wen Yang, ZTE. +// Comments: +// Options: --no-includes --include-headers + +virtual report +virtual org + +@search exists@ +local idexpression id; +expression x,e,e1; +position p1,p2; +type T,T1,T2,T3; +@@ + +id = of_find_device_by_node@p1(x) +... when != e = id +if (id == NULL || ...) { ... return ...; } +... when != put_device(&id->dev) + when != platform_device_put(id) + when != if (id) { ... put_device(&id->dev) ... } + when != e1 = (T)id + when != e1 = (T)(&id->dev) + when != e1 = get_device(&id->dev) + when != e1 = (T1)platform_get_drvdata(id) +( + return +( id +| (T2)dev_get_drvdata(&id->dev) +| (T3)platform_get_drvdata(id) +| &id->dev +); +| return@p2 ...; +) + +@script:python depends on report@ +p1 << search.p1; +p2 << search.p2; +@@ + +coccilib.report.print_report(p2[0], + "ERROR: missing put_device; call of_find_device_by_node on line " + + p1[0].line + + ", but without a corresponding object release within this function.") + +@script:python depends on org@ +p1 << search.p1; +p2 << search.p2; +@@ + +cocci.print_main("of_find_device_by_node", p1) +cocci.print_secs("needed put_device", p2) diff --git a/scripts/coccinelle/hid/ff_race.cocci b/scripts/coccinelle/hid/ff_race.cocci new file mode 100644 index 000000000..e861de00c --- /dev/null +++ b/scripts/coccinelle/hid/ff_race.cocci @@ -0,0 +1,35 @@ +/// Detect HID drivers that initialize force-feedback after hid_hw_start() +/// when HID_CONNECT_HIDINPUT is used. This is a lifecycle violation as +/// the input device is already registered. +// +// Confidence: High +// Copyright: (C) 2026 Gemini. GPLv2. + +virtual report + +@r exists@ +identifier probe_fn; +identifier hdev; +expression flags; +position p1, p2; +@@ + +probe_fn(struct hid_device *hdev, ...) { + ... when any + hid_hw_start@p1(hdev, flags) + ... + \(input_ff_create@p2\|input_ff_create_memless@p2\)(...) + ... when any +} + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +flags << r.flags; +@@ + +# Check if flags include HID_CONNECT_HIDINPUT (0x01) or HID_CONNECT_DEFAULT (0x0f) +# Note: HID_CONNECT_DEFAULT is 0x0f, HID_CONNECT_HIDINPUT is 0x01 +if "HID_CONNECT_HIDINPUT" in flags or "HID_CONNECT_DEFAULT" in flags: + msg = "WARNING: force-feedback initialized after hid_hw_start() with HID_CONNECT_HIDINPUT. Input device is already registered at this point. Use .input_configured() instead." + coccilib.report.print_report(p2[0], msg) diff --git a/scripts/coccinelle/iterators/device_node_continue.cocci b/scripts/coccinelle/iterators/device_node_continue.cocci new file mode 100644 index 000000000..5713c9cf5 --- /dev/null +++ b/scripts/coccinelle/iterators/device_node_continue.cocci @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Device node iterators put the previous value of the index variable, so an +/// explicit put causes a double put. +/// +// Confidence: High +// Copyright: (C) 2015 Julia Lawall, Inria. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --no-includes --include-headers +// Requires: 1.0.4 +// Keywords: for_each_child_of_node, etc. + +// This uses a conjunction, which requires at least coccinelle >= 1.0.4 + +virtual patch +virtual context +virtual org +virtual report + +@r exists@ +expression e1,e2; +local idexpression n; +iterator name for_each_node_by_name, for_each_node_by_type, +for_each_compatible_node, for_each_matching_node, +for_each_matching_node_and_match, for_each_child_of_node, +for_each_available_child_of_node, for_each_node_with_property; +iterator i; +position p1,p2; +statement S; +@@ + +( +( +for_each_node_by_name(n,e1) S +| +for_each_node_by_type(n,e1) S +| +for_each_compatible_node(n,e1,e2) S +| +for_each_matching_node(n,e1) S +| +for_each_matching_node_and_match(n,e1,e2) S +| +for_each_child_of_node(e1,n) S +| +for_each_available_child_of_node(e1,n) S +| +for_each_node_with_property(n,e1) S +) +& +i@p1(...) { + ... when != of_node_get(n) + when any + of_node_put@p2(n); + ... when any +} +) + +@s exists@ +local idexpression r.n; +statement S; +position r.p1,r.p2; +iterator i; +@@ + + of_node_put@p2(n); + ... when any + i@p1(..., n, ...) + S + +@t depends on s && patch && !context && !org && !report@ +local idexpression n; +position r.p2; +@@ + +- of_node_put@p2(n); + +// ---------------------------------------------------------------------------- + +@t_context depends on s && !patch && (context || org || report)@ +local idexpression n; +position r.p2; +position j0; +@@ + +* of_node_put@j0@p2(n); + +// ---------------------------------------------------------------------------- + +@script:python t_org depends on org@ +j0 << t_context.j0; +@@ + +msg = "ERROR: probable double put." +coccilib.org.print_todo(j0[0], msg) + +// ---------------------------------------------------------------------------- + +@script:python t_report depends on report@ +j0 << t_context.j0; +@@ + +msg = "ERROR: probable double put." +coccilib.report.print_report(j0[0], msg) + diff --git a/scripts/coccinelle/iterators/for_each_child.cocci b/scripts/coccinelle/iterators/for_each_child.cocci new file mode 100644 index 000000000..2ea98a61a --- /dev/null +++ b/scripts/coccinelle/iterators/for_each_child.cocci @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Adds missing of_node_put() before return/break/goto statement within a for_each iterator for child nodes. +//# False positives can be due to function calls within the for_each +//# loop that may encapsulate an of_node_put. +/// +// Confidence: High +// Copyright: (C) 2020 Sumera Priyadarsini +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@r@ +local idexpression n; +expression e1,e2; +iterator name for_each_node_by_name, for_each_node_by_type, +for_each_compatible_node, for_each_matching_node, +for_each_matching_node_and_match, for_each_child_of_node, +for_each_available_child_of_node, for_each_node_with_property; +iterator i; +statement S; +expression list [n1] es; +@@ + +( +( +for_each_node_by_name(n,e1) S +| +for_each_node_by_type(n,e1) S +| +for_each_compatible_node(n,e1,e2) S +| +for_each_matching_node(n,e1) S +| +for_each_matching_node_and_match(n,e1,e2) S +| +for_each_child_of_node(e1,n) S +| +for_each_available_child_of_node(e1,n) S +| +for_each_node_with_property(n,e1) S +) +& +i(es,n,...) S +) + +@ruleone depends on patch && !context && !org && !report@ + +local idexpression r.n; +iterator r.i,i1; +expression e; +expression list [r.n1] es; +statement S; +@@ + + i(es,n,...) { + ... +( + of_node_put(n); +| + e = n +| + return n; +| + i1(...,n,...) S +| +- return of_node_get(n); ++ return n; +| ++ of_node_put(n); +? return ...; +) + ... when any + } + +@ruletwo depends on patch && !context && !org && !report@ + +local idexpression r.n; +iterator r.i,i1,i2; +expression e,e1; +expression list [r.n1] es; +statement S,S2; +@@ + + i(es,n,...) { + ... +( + of_node_put(n); +| + e = n +| + i1(...,n,...) S +| ++ of_node_put(n); +? break; +) + ... when any + } +... when != n + when strict + when forall +( + n = e1; +| +?i2(...,n,...) S2 +) + +@rulethree depends on patch && !context && !org && !report exists@ + +local idexpression r.n; +iterator r.i,i1,i2; +expression e,e1; +identifier l; +expression list [r.n1] es; +statement S,S2; +@@ + + i(es,n,...) { + ... +( + of_node_put(n); +| + e = n +| + i1(...,n,...) S +| ++ of_node_put(n); +? goto l; +) + ... when any + } +... when exists +l: ... when != n + when strict + when forall +( + n = e1; +| +?i2(...,n,...) S2 +) + +// ---------------------------------------------------------------------------- + +@ruleone_context depends on !patch && (context || org || report) exists@ +statement S; +expression e; +expression list[r.n1] es; +iterator r.i, i1; +local idexpression r.n; +position j0, j1; +@@ + + i@j0(es,n,...) { + ... +( + of_node_put(n); +| + e = n +| + return n; +| + i1(...,n,...) S +| + return @j1 ...; +) + ... when any + } + +@ruleone_disj depends on !patch && (context || org || report)@ +expression list[r.n1] es; +iterator r.i; +local idexpression r.n; +position ruleone_context.j0, ruleone_context.j1; +@@ + +* i@j0(es,n,...) { + ... +*return @j1...; + ... when any + } + +@ruletwo_context depends on !patch && (context || org || report) exists@ +statement S, S2; +expression e, e1; +expression list[r.n1] es; +iterator r.i, i1, i2; +local idexpression r.n; +position j0, j2; +@@ + + i@j0(es,n,...) { + ... +( + of_node_put(n); +| + e = n +| + i1(...,n,...) S +| + break@j2; +) + ... when any + } +... when != n + when strict + when forall +( + n = e1; +| +?i2(...,n,...) S2 +) + +@ruletwo_disj depends on !patch && (context || org || report)@ +statement S2; +expression e1; +expression list[r.n1] es; +iterator r.i, i2; +local idexpression r.n; +position ruletwo_context.j0, ruletwo_context.j2; +@@ + +* i@j0(es,n,...) { + ... +*break @j2; + ... when any + } +... when != n + when strict + when forall +( + n = e1; +| +?i2(...,n,...) S2 +) + +@rulethree_context depends on !patch && (context || org || report) exists@ +identifier l; +statement S,S2; +expression e, e1; +expression list[r.n1] es; +iterator r.i, i1, i2; +local idexpression r.n; +position j0, j3; +@@ + + i@j0(es,n,...) { + ... +( + of_node_put(n); +| + e = n +| + i1(...,n,...) S +| + goto l@j3; +) + ... when any + } +... when exists +l: +... when != n + when strict + when forall +( + n = e1; +| +?i2(...,n,...) S2 +) + +@rulethree_disj depends on !patch && (context || org || report) exists@ +identifier l; +statement S2; +expression e1; +expression list[r.n1] es; +iterator r.i, i2; +local idexpression r.n; +position rulethree_context.j0, rulethree_context.j3; +@@ + +* i@j0(es,n,...) { + ... +*goto l@j3; + ... when any + } +... when exists + l: + ... when != n + when strict + when forall +( + n = e1; +| +?i2(...,n,...) S2 +) + +// ---------------------------------------------------------------------------- + +@script:python ruleone_org depends on org@ +i << r.i; +j0 << ruleone_context.j0; +j1 << ruleone_context. j1; +@@ + +msg = "WARNING: Function \"%s\" should have of_node_put() before return " % (i) +coccilib.org.print_safe_todo(j0[0], msg) +coccilib.org.print_link(j1[0], "") + +@script:python ruletwo_org depends on org@ +i << r.i; +j0 << ruletwo_context.j0; +j2 << ruletwo_context.j2; +@@ + +msg = "WARNING: Function \"%s\" should have of_node_put() before break " % (i) +coccilib.org.print_safe_todo(j0[0], msg) +coccilib.org.print_link(j2[0], "") + +@script:python rulethree_org depends on org@ +i << r.i; +j0 << rulethree_context.j0; +j3 << rulethree_context.j3; +@@ + +msg = "WARNING: Function \"%s\" should have of_node_put() before goto " % (i) +coccilib.org.print_safe_todo(j0[0], msg) +coccilib.org.print_link(j3[0], "") + +// ---------------------------------------------------------------------------- + +@script:python ruleone_report depends on report@ +i << r.i; +j0 << ruleone_context.j0; +j1 << ruleone_context.j1; +@@ + +msg = "WARNING: Function \"%s\" should have of_node_put() before return around line %s." % (i, j1[0].line) +coccilib.report.print_report(j0[0], msg) + +@script:python ruletwo_report depends on report@ +i << r.i; +j0 << ruletwo_context.j0; +j2 << ruletwo_context.j2; +@@ + +msg = "WARNING: Function \"%s\" should have of_node_put() before break around line %s." % (i,j2[0].line) +coccilib.report.print_report(j0[0], msg) + +@script:python rulethree_report depends on report@ +i << r.i; +j0 << rulethree_context.j0; +j3 << rulethree_context.j3; +@@ + +msg = "WARNING: Function \"%s\" should have of_node_put() before goto around lines %s." % (i,j3[0].line) +coccilib.report.print_report(j0[0], msg) diff --git a/scripts/coccinelle/iterators/itnull.cocci b/scripts/coccinelle/iterators/itnull.cocci new file mode 100644 index 000000000..7d34802f2 --- /dev/null +++ b/scripts/coccinelle/iterators/itnull.cocci @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Many iterators have the property that the first argument is always bound +/// to a real list element, never NULL. +//# False positives arise for some iterators that do not have this property, +//# or in cases when the loop cursor is reassigned. The latter should only +//# happen when the matched code is on the way to a loop exit (break, goto, +//# or return). +/// +// Confidence: Moderate +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@depends on patch@ +iterator I; +expression x,E,E1,E2; +statement S,S1,S2; +@@ + +I(x,...) { <... +( +- if (x == NULL && ...) S +| +- if (x != NULL || ...) + S +| +- (x == NULL) || + E +| +- (x != NULL) && + E +| +- (x == NULL && ...) ? E1 : + E2 +| +- (x != NULL || ...) ? + E1 +- : E2 +| +- if (x == NULL && ...) S1 else + S2 +| +- if (x != NULL || ...) + S1 +- else S2 +| ++ BAD( + x == NULL ++ ) +| ++ BAD( + x != NULL ++ ) +) + ...> } + +@r depends on !patch exists@ +iterator I; +expression x,E; +position p1,p2; +@@ + +*I@p1(x,...) +{ ... when != x = E +( +* x@p2 == NULL +| +* x@p2 != NULL +) + ... when any +} + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +@@ + +cocci.print_main("iterator-bound variable",p1) +cocci.print_secs("useless NULL test",p2) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +@@ + +msg = "ERROR: iterator variable bound on line %s cannot be NULL" % (p1[0].line) +coccilib.report.print_report(p2[0], msg) diff --git a/scripts/coccinelle/iterators/list_entry_update.cocci b/scripts/coccinelle/iterators/list_entry_update.cocci new file mode 100644 index 000000000..9a7593667 --- /dev/null +++ b/scripts/coccinelle/iterators/list_entry_update.cocci @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// list_for_each_entry uses its first argument to get from one element of +/// the list to the next, so it is usually not a good idea to reassign it. +/// The first rule finds such a reassignment and the second rule checks +/// that there is a path from the reassignment back to the top of the loop. +/// +// Confidence: High +// Copyright: (C) 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2010 Julia Lawall, DIKU. +// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@r exists@ +iterator name list_for_each_entry; +expression x,E; +position p1,p2; +@@ + +list_for_each_entry@p1(x,...) { <... x =@p2 E ...> } + +@depends on context && !org && !report@ +expression x,E; +position r.p1,r.p2; +statement S; +@@ + +*x =@p2 E +... +list_for_each_entry@p1(x,...) S + +// ------------------------------------------------------------------------ + +@back depends on (org || report) && !context exists@ +expression x,E; +position r.p1,r.p2; +statement S; +@@ + +x =@p2 E +... +list_for_each_entry@p1(x,...) S + +@script:python depends on back && org@ +p1 << r.p1; +p2 << r.p2; +@@ + +cocci.print_main("iterator",p1) +cocci.print_secs("update",p2) + +@script:python depends on back && report@ +p1 << r.p1; +p2 << r.p2; +@@ + +msg = "iterator with update on line %s" % (p2[0].line) +coccilib.report.print_report(p1[0],msg) diff --git a/scripts/coccinelle/iterators/use_after_iter.cocci b/scripts/coccinelle/iterators/use_after_iter.cocci new file mode 100644 index 000000000..f67110ba7 --- /dev/null +++ b/scripts/coccinelle/iterators/use_after_iter.cocci @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// If list_for_each_entry, etc complete a traversal of the list, the iterator +/// variable ends up pointing to an address at an offset from the list head, +/// and not a meaningful structure. Thus this value should not be used after +/// the end of the iterator. +//#False positives arise when there is a goto in the iterator and the +//#reported reference is at the label of this goto. Some flag tests +//#may also cause a report to be a false positive. +/// +// Confidence: Moderate +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LIP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@r exists@ +identifier c,member; +expression E,x; +iterator name list_for_each_entry; +iterator name list_for_each_entry_reverse; +iterator name list_for_each_entry_continue; +iterator name list_for_each_entry_continue_reverse; +iterator name list_for_each_entry_from; +iterator name list_for_each_entry_safe; +iterator name list_for_each_entry_safe_continue; +iterator name list_for_each_entry_safe_from; +iterator name list_for_each_entry_safe_reverse; +iterator name hlist_for_each_entry; +iterator name hlist_for_each_entry_continue; +iterator name hlist_for_each_entry_from; +iterator name hlist_for_each_entry_safe; +statement S; +position p1,p2; +type T; +@@ + +( +list_for_each_entry@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_reverse@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_continue@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_continue_reverse@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_from@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_safe@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_safe_continue@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_safe_from@p1(c,...,member) { ... when != break; + when forall + when strict +} +| +list_for_each_entry_safe_reverse@p1(c,...,member) { ... when != break; + when forall + when strict +} +) +... +( +list_for_each_entry(c,...) S +| +list_for_each_entry_reverse(c,...) S +| +list_for_each_entry_continue(c,...) S +| +list_for_each_entry_continue_reverse(c,...) S +| +list_for_each_entry_from(c,...) S +| +list_for_each_entry_safe(c,...) S +| +list_for_each_entry_safe(x,c,...) S +| +list_for_each_entry_safe_continue(c,...) S +| +list_for_each_entry_safe_continue(x,c,...) S +| +list_for_each_entry_safe_from(c,...) S +| +list_for_each_entry_safe_from(x,c,...) S +| +list_for_each_entry_safe_reverse(c,...) S +| +list_for_each_entry_safe_reverse(x,c,...) S +| +hlist_for_each_entry(c,...) S +| +hlist_for_each_entry_continue(c,...) S +| +hlist_for_each_entry_from(c,...) S +| +hlist_for_each_entry_safe(c,...) S +| +list_remove_head(x,c,...) +| +list_entry_is_head(c,...) +| +sizeof(<+...c...+>) +| + &c->member +| +T c; +| +c = E +| +*c@p2 +) + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +@@ + +cocci.print_main("invalid iterator index reference",p2) +cocci.print_secs("iterator",p1) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +@@ + +msg = "ERROR: invalid reference to the index variable of the iterator on line %s" % (p1[0].line) +coccilib.report.print_report(p2[0], msg) diff --git a/scripts/coccinelle/locks/call_kern.cocci b/scripts/coccinelle/locks/call_kern.cocci new file mode 100644 index 000000000..3720f86d6 --- /dev/null +++ b/scripts/coccinelle/locks/call_kern.cocci @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find functions that refer to GFP_KERNEL but are called with locks held. +//# The proposed change of converting the GFP_KERNEL is not necessarily the +//# correct one. It may be desired to unlock the lock, or to not call the +//# function under the lock in the first place. +/// +// Confidence: Moderate +// Copyright: (C) 2012 Nicolas Palix. +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@gfp exists@ +identifier fn; +position p; +@@ + +fn(...) { + ... when != read_unlock_irq(...) + when != write_unlock_irq(...) + when != read_unlock_irqrestore(...) + when != write_unlock_irqrestore(...) + when != spin_unlock(...) + when != spin_unlock_irq(...) + when != spin_unlock_irqrestore(...) + when != local_irq_enable(...) + when any + GFP_KERNEL@p + ... when any +} + +@locked exists@ +identifier gfp.fn; +position p1,p2; +@@ + +( +read_lock_irq@p1 +| +write_lock_irq@p1 +| +read_lock_irqsave@p1 +| +write_lock_irqsave@p1 +| +spin_lock@p1 +| +spin_trylock@p1 +| +spin_lock_irq@p1 +| +spin_lock_irqsave@p1 +| +local_irq_disable@p1 +) + (...) +... when != read_unlock_irq(...) + when != write_unlock_irq(...) + when != read_unlock_irqrestore(...) + when != write_unlock_irqrestore(...) + when != spin_unlock(...) + when != spin_unlock_irq(...) + when != spin_unlock_irqrestore(...) + when != local_irq_enable(...) +fn@p2(...) + +@depends on locked && patch@ +position gfp.p; +@@ + +- GFP_KERNEL@p ++ GFP_ATOMIC + +@depends on locked && !patch@ +position gfp.p; +@@ + +* GFP_KERNEL@p + +@script:python depends on !patch && org@ +p << gfp.p; +fn << gfp.fn; +p1 << locked.p1; +p2 << locked.p2; +@@ + +cocci.print_main("lock",p1) +cocci.print_secs("call",p2) +cocci.print_secs("GFP_KERNEL",p) + +@script:python depends on !patch && report@ +p << gfp.p; +fn << gfp.fn; +p1 << locked.p1; +p2 << locked.p2; +@@ + +msg = "ERROR: function %s called on line %s inside lock on line %s but uses GFP_KERNEL" % (fn,p2[0].line,p1[0].line) +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/locks/double_lock.cocci b/scripts/coccinelle/locks/double_lock.cocci new file mode 100644 index 000000000..381060849 --- /dev/null +++ b/scripts/coccinelle/locks/double_lock.cocci @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find double locks. False positives may occur when some paths cannot +/// occur at execution, due to the values of variables, and when there is +/// an intervening function call that releases the lock. +/// +// Confidence: Moderate +// Copyright: (C) 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2010 Julia Lawall, DIKU. +// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report + +@locked@ +position p1; +expression E1; +position p; +@@ + +( +mutex_lock@p1 +| +mutex_trylock@p1 +| +spin_lock@p1 +| +spin_trylock@p1 +| +read_lock@p1 +| +read_trylock@p1 +| +write_lock@p1 +| +write_trylock@p1 +) (E1@p,...); + +@r_candidate exists@ +expression x <= locked.E1; +expression locked.E1; +expression E2; +identifier lock; +position locked.p,p1,p2; +@@ + +lock@p1 (E1@p,...); +... when != E1 + when != \(x = E2\|&x\) +lock@p2 (E1,...); + +@balanced depends on r_candidate@ +position p1 != locked.p1; +position locked.p; +identifier lock,unlock; +expression x <= locked.E1; +expression E,locked.E1; +expression E2; +@@ + +if (E) { + <+... when != E1 + lock(E1@p,...) + ...+> +} +... when != E1 + when != \(x = E2\|&x\) + when forall +if (E) { + <+... when != E1 + unlock@p1(E1,...) + ...+> +} + +@r depends on !balanced exists@ +expression x <= locked.E1; +expression locked.E1; +expression E2; +identifier lock; +position locked.p,p1,p2; +@@ + +lock@p1 (E1@p,...); +... when != E1 + when != \(x = E2\|&x\) +lock@p2 (E1,...); + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +lock << r.lock; +@@ + +cocci.print_main(lock,p1) +cocci.print_secs("second lock",p2) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +lock << r.lock; +@@ + +msg = "second lock on line %s" % (p2[0].line) +coccilib.report.print_report(p1[0],msg) diff --git a/scripts/coccinelle/locks/flags.cocci b/scripts/coccinelle/locks/flags.cocci new file mode 100644 index 000000000..44acf20a9 --- /dev/null +++ b/scripts/coccinelle/locks/flags.cocci @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find nested lock+irqsave functions that use the same flags variables +/// +// Confidence: High +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@r exists@ +expression lock1,lock2,flags; +position p1,p2; +@@ + +( +spin_lock_irqsave@p1(lock1,flags) +| +read_lock_irqsave@p1(lock1,flags) +| +write_lock_irqsave@p1(lock1,flags) +) +... when != flags +( +spin_lock_irqsave(lock1,flags) +| +read_lock_irqsave(lock1,flags) +| +write_lock_irqsave(lock1,flags) +| +spin_lock_irqsave@p2(lock2,flags) +| +read_lock_irqsave@p2(lock2,flags) +| +write_lock_irqsave@p2(lock2,flags) +) + +@d exists@ +expression f <= r.flags; +expression lock1,lock2,flags; +position r.p1, r.p2; +@@ + +( +*spin_lock_irqsave@p1(lock1,flags) +| +*read_lock_irqsave@p1(lock1,flags) +| +*write_lock_irqsave@p1(lock1,flags) +) +... when != f +( +*spin_lock_irqsave@p2(lock2,flags) +| +*read_lock_irqsave@p2(lock2,flags) +| +*write_lock_irqsave@p2(lock2,flags) +) + +// ---------------------------------------------------------------------- + +@script:python depends on d && org@ +p1 << r.p1; +p2 << r.p2; +@@ + +cocci.print_main("original lock",p1) +cocci.print_secs("nested lock+irqsave that reuses flags",p2) + +@script:python depends on d && report@ +p1 << r.p1; +p2 << r.p2; +@@ + +msg="ERROR: nested lock+irqsave that reuses flags from line %s." % (p1[0].line) +coccilib.report.print_report(p2[0], msg) diff --git a/scripts/coccinelle/locks/mini_lock.cocci b/scripts/coccinelle/locks/mini_lock.cocci new file mode 100644 index 000000000..c65241c89 --- /dev/null +++ b/scripts/coccinelle/locks/mini_lock.cocci @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find missing unlocks. This semantic match considers the specific case +/// where the unlock is missing from an if branch, and there is a lock +/// before the if and an unlock after the if. False positives are due to +/// cases where the if branch represents a case where the function is +/// supposed to exit with the lock held, or where there is some preceding +/// function call that releases the lock. +/// +// Confidence: Moderate +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@prelocked@ +position p1,p; +expression E1; +@@ + +( +mutex_lock@p1 +| +mutex_trylock@p1 +| +spin_lock@p1 +| +spin_trylock@p1 +| +read_lock@p1 +| +read_trylock@p1 +| +write_lock@p1 +| +write_trylock@p1 +| +read_lock_irq@p1 +| +write_lock_irq@p1 +| +read_lock_irqsave@p1 +| +write_lock_irqsave@p1 +| +spin_lock_irq@p1 +| +spin_lock_irqsave@p1 +) (E1@p,...); + +@err_candidate exists@ +expression E1; +position prelocked.p; +position up != prelocked.p1; +position rc; +identifier lock,unlock; +@@ + +lock(E1@p,...); +... when != E1 + when any +if (...) { + ... when != E1 + return@rc ...; +} +... when != E1 + when any +unlock@up(E1,...); + +@looped exists@ +position err_candidate.rc; +position r; +@@ + +for(...;...;...) { <+... return@rc@r ...; ...+> } + +@err exists@ +expression E1; +position prelocked.p; +position up != prelocked.p1; +position r!=looped.r; +identifier lock,unlock; +@@ + +*lock(E1@p,...); +... when != E1 + when any +if (...) { + ... when != E1 +* return@r ...; +} +... when != E1 + when any +*unlock@up(E1,...); + +@script:python depends on org@ +p << prelocked.p1; +lock << err.lock; +unlock << err.unlock; +p2 << err.r; +@@ + +cocci.print_main(lock,p) +cocci.print_secs(unlock,p2) + +@script:python depends on report@ +p << prelocked.p1; +lock << err.lock; +unlock << err.unlock; +p2 << err.r; +@@ + +msg = "preceding lock on line %s" % (p[0].line) +coccilib.report.print_report(p2[0],msg) diff --git a/scripts/coccinelle/misc/add_namespace.cocci b/scripts/coccinelle/misc/add_namespace.cocci new file mode 100644 index 000000000..cbf161416 --- /dev/null +++ b/scripts/coccinelle/misc/add_namespace.cocci @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +/// Adds missing MODULE_IMPORT_NS statements to source files +/// +/// This script is usually called from scripts/nsdeps with -D ns=<namespace> to +/// add a missing namespace tag to a module source file. +/// + +virtual nsdeps +virtual report + +@has_ns_import@ +declarer name MODULE_IMPORT_NS; +identifier virtual.ns; +@@ +MODULE_IMPORT_NS(ns); + +// Add missing imports, but only adjacent to a MODULE_LICENSE statement. +// That ensures we are adding it only to the main module source file. +@do_import depends on !has_ns_import && nsdeps@ +declarer name MODULE_LICENSE; +expression license; +identifier virtual.ns; +@@ +MODULE_LICENSE(license); ++ MODULE_IMPORT_NS(ns); + +// Dummy rule for report mode that would otherwise be empty and make spatch +// fail ("No rules apply.") +@script:python depends on report@ +@@ diff --git a/scripts/coccinelle/misc/array_size.cocci b/scripts/coccinelle/misc/array_size.cocci new file mode 100644 index 000000000..4d2518749 --- /dev/null +++ b/scripts/coccinelle/misc/array_size.cocci @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use ARRAY_SIZE instead of dividing sizeof array with sizeof an element +/// +//# This makes an effort to find cases where ARRAY_SIZE can be used such as +//# where there is a division of sizeof the array by the sizeof its first +//# element or by any indexed element or the element type. It replaces the +//# division of the two sizeofs by ARRAY_SIZE. +// +// Confidence: High +// Copyright: (C) 2014 Himangi Saraogi. +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@i@ +@@ + +#include <linux/kernel.h> + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@depends on i&&context@ +type T; +T[] E; +@@ +( +* (sizeof(E)/sizeof(*E)) +| +* (sizeof(E)/sizeof(E[...])) +| +* (sizeof(E)/sizeof(T)) +) + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@depends on i&&patch@ +type T; +T[] E; +@@ +( +- (sizeof(E)/sizeof(*E)) ++ ARRAY_SIZE(E) +| +- (sizeof(E)/sizeof(E[...])) ++ ARRAY_SIZE(E) +| +- (sizeof(E)/sizeof(T)) ++ ARRAY_SIZE(E) +) + +//---------------------------------------------------------- +// For org and report mode +//---------------------------------------------------------- + +@r depends on (org || report)@ +type T; +T[] E; +position p; +@@ +( + (sizeof(E)@p /sizeof(*E)) +| + (sizeof(E)@p /sizeof(E[...])) +| + (sizeof(E)@p /sizeof(T)) +) + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING should use ARRAY_SIZE") + +@script:python depends on report@ +p << r.p; +@@ + +msg="WARNING: Use ARRAY_SIZE" +coccilib.report.print_report(p[0], msg) + diff --git a/scripts/coccinelle/misc/array_size_dup.cocci b/scripts/coccinelle/misc/array_size_dup.cocci new file mode 100644 index 000000000..fbc2ba140 --- /dev/null +++ b/scripts/coccinelle/misc/array_size_dup.cocci @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Check for array_size(), array3_size(), struct_size() duplicates. +/// These patterns are detected: +/// 1. An opencoded expression is used before array_size() to compute the same size +/// 2. An opencoded expression is used after array_size() to compute the same size +/// From security point of view only first case is relevant. These functions +/// perform arithmetic overflow check. Thus, if we use an opencoded expression +/// before a call to the *_size() function we can miss an overflow. +/// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS +// Options: --no-includes --include-headers --no-loops + +virtual context +virtual report +virtual org + +@as@ +expression E1, E2; +@@ + +array_size(E1, E2) + +@as_next@ +expression subE1 <= as.E1; +expression subE2 <= as.E2; +expression as.E1, as.E2, E3; +assignment operator aop; +position p1, p2; +@@ + +* E1 * E2@p1 + ... when != \(subE1\|subE2\) aop E3 + when != &\(subE1\|subE2\) +* array_size(E1, E2)@p2 + +@script:python depends on report@ +p1 << as_next.p1; +p2 << as_next.p2; +@@ + +msg = "WARNING: array_size is used later (line %s) to compute the same size" % (p2[0].line) +coccilib.report.print_report(p1[0], msg) + +@script:python depends on org@ +p1 << as_next.p1; +p2 << as_next.p2; +@@ + +msg = "WARNING: array_size is used later (line %s) to compute the same size" % (p2[0].line) +coccilib.org.print_todo(p1[0], msg) + +@as_prev@ +expression subE1 <= as.E1; +expression subE2 <= as.E2; +expression as.E1, as.E2, E3; +assignment operator aop; +position p1, p2; +@@ + +* array_size(E1, E2)@p1 + ... when != \(subE1\|subE2\) aop E3 + when != &\(subE1\|subE2\) +* E1 * E2@p2 + +@script:python depends on report@ +p1 << as_prev.p1; +p2 << as_prev.p2; +@@ + +msg = "WARNING: array_size is already used (line %s) to compute the same size" % (p1[0].line) +coccilib.report.print_report(p2[0], msg) + +@script:python depends on org@ +p1 << as_prev.p1; +p2 << as_prev.p2; +@@ + +msg = "WARNING: array_size is already used (line %s) to compute the same size" % (p1[0].line) +coccilib.org.print_todo(p2[0], msg) + +@as3@ +expression E1, E2, E3; +@@ + +array3_size(E1, E2, E3) + +@as3_next@ +expression subE1 <= as3.E1; +expression subE2 <= as3.E2; +expression subE3 <= as3.E3; +expression as3.E1, as3.E2, as3.E3, E4; +assignment operator aop; +position p1, p2; +@@ + +* E1 * E2 * E3@p1 + ... when != \(subE1\|subE2\|subE3\) aop E4 + when != &\(subE1\|subE2\|subE3\) +* array3_size(E1, E2, E3)@p2 + +@script:python depends on report@ +p1 << as3_next.p1; +p2 << as3_next.p2; +@@ + +msg = "WARNING: array3_size is used later (line %s) to compute the same size" % (p2[0].line) +coccilib.report.print_report(p1[0], msg) + +@script:python depends on org@ +p1 << as3_next.p1; +p2 << as3_next.p2; +@@ + +msg = "WARNING: array3_size is used later (line %s) to compute the same size" % (p2[0].line) +coccilib.org.print_todo(p1[0], msg) + +@as3_prev@ +expression subE1 <= as3.E1; +expression subE2 <= as3.E2; +expression subE3 <= as3.E3; +expression as3.E1, as3.E2, as3.E3, E4; +assignment operator aop; +position p1, p2; +@@ + +* array3_size(E1, E2, E3)@p1 + ... when != \(subE1\|subE2\|subE3\) aop E4 + when != &\(subE1\|subE2\|subE3\) +* E1 * E2 * E3@p2 + +@script:python depends on report@ +p1 << as3_prev.p1; +p2 << as3_prev.p2; +@@ + +msg = "WARNING: array3_size is already used (line %s) to compute the same size" % (p1[0].line) +coccilib.report.print_report(p2[0], msg) + +@script:python depends on org@ +p1 << as3_prev.p1; +p2 << as3_prev.p2; +@@ + +msg = "WARNING: array3_size is already used (line %s) to compute the same size" % (p1[0].line) +coccilib.org.print_todo(p2[0], msg) + +@ss@ +expression E1, E2, E3; +@@ + +struct_size(E1, E2, E3) + +@ss_next@ +expression subE3 <= ss.E3; +expression ss.E1, ss.E2, ss.E3, E4; +assignment operator aop; +position p1, p2; +@@ + +* E1 * E2 + E3@p1 + ... when != subE3 aop E4 + when != &subE3 +* struct_size(E1, E2, E3)@p2 + +@script:python depends on report@ +p1 << ss_next.p1; +p2 << ss_next.p2; +@@ + +msg = "WARNING: struct_size is used later (line %s) to compute the same size" % (p2[0].line) +coccilib.report.print_report(p1[0], msg) + +@script:python depends on org@ +p1 << ss_next.p1; +p2 << ss_next.p2; +@@ + +msg = "WARNING: struct_size is used later (line %s) to compute the same size" % (p2[0].line) +coccilib.org.print_todo(p1[0], msg) + +@ss_prev@ +expression subE3 <= ss.E3; +expression ss.E1, ss.E2, ss.E3, E4; +assignment operator aop; +position p1, p2; +@@ + +* struct_size(E1, E2, E3)@p1 + ... when != subE3 aop E4 + when != &subE3 +* E1 * E2 + E3@p2 + +@script:python depends on report@ +p1 << ss_prev.p1; +p2 << ss_prev.p2; +@@ + +msg = "WARNING: struct_size is already used (line %s) to compute the same size" % (p1[0].line) +coccilib.report.print_report(p2[0], msg) + +@script:python depends on org@ +p1 << ss_prev.p1; +p2 << ss_prev.p2; +@@ + +msg = "WARNING: struct_size is already used (line %s) to compute the same size" % (p1[0].line) +coccilib.org.print_todo(p2[0], msg) diff --git a/scripts/coccinelle/misc/badty.cocci b/scripts/coccinelle/misc/badty.cocci new file mode 100644 index 000000000..ed3e0b8f3 --- /dev/null +++ b/scripts/coccinelle/misc/badty.cocci @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Correct the size argument to alloc functions +/// +//# This makes an effort to find cases where the argument to sizeof is wrong +//# in memory allocation functions by checking the type of the allocated memory +//# when it is a double pointer and ensuring the sizeof argument takes a pointer +//# to the the memory being allocated. There are false positives in cases the +//# sizeof argument is not used in constructing the return value. The result +//# may need some reformatting. +// +// Confidence: Moderate +// Copyright: (C) 2014 Himangi Saraogi. +// Comments: +// Options: + +virtual patch +virtual context +virtual org +virtual report + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@depends on context disable sizeof_type_expr@ +type T; +T **x; +@@ + + x = + <+...sizeof( +* T + )...+> + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@depends on patch disable sizeof_type_expr@ +type T; +T **x; +@@ + + x = + <+...sizeof( +- T ++ *x + )...+> + +//---------------------------------------------------------- +// For org and report mode +//---------------------------------------------------------- + +@r depends on (org || report) disable sizeof_type_expr@ +type T; +T **x; +position p; +@@ + + x = + <+...sizeof( + T@p + )...+> + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING sizeof argument should be pointer type, not structure type") + +@script:python depends on report@ +p << r.p; +@@ + +msg="WARNING: Use correct pointer type argument for sizeof" +coccilib.report.print_report(p[0], msg) + diff --git a/scripts/coccinelle/misc/boolconv.cocci b/scripts/coccinelle/misc/boolconv.cocci new file mode 100644 index 000000000..392994e93 --- /dev/null +++ b/scripts/coccinelle/misc/boolconv.cocci @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Remove unneeded conversion to bool +/// +//# Relational and logical operators evaluate to bool, +//# explicit conversion is overly verbose and unneeded. +// +// Copyright: (C) 2016 Andrew F. Davis <afd@ti.com> + +virtual patch +virtual context +virtual org +virtual report + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@depends on patch@ +expression A, B; +symbol true, false; +@@ + +( + A == B +| + A != B +| + A > B +| + A < B +| + A >= B +| + A <= B +| + A && B +| + A || B +) +- ? true : false + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@r depends on !patch@ +expression A, B; +symbol true, false; +position p; +@@ + +( + A == B +| + A != B +| + A > B +| + A < B +| + A >= B +| + A <= B +| + A && B +| + A || B +) +* ? true : false@p + +//---------------------------------------------------------- +// For org mode +//---------------------------------------------------------- + +@script:python depends on r&&org@ +p << r.p; +@@ + +msg = "WARNING: conversion to bool not needed here" +coccilib.org.print_todo(p[0], msg) + +//---------------------------------------------------------- +// For report mode +//---------------------------------------------------------- + +@script:python depends on r&&report@ +p << r.p; +@@ + +msg = "WARNING: conversion to bool not needed here" +coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/misc/cond_no_effect.cocci b/scripts/coccinelle/misc/cond_no_effect.cocci new file mode 100644 index 000000000..91d16a81d --- /dev/null +++ b/scripts/coccinelle/misc/cond_no_effect.cocci @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-only +///Find conditions where if and else branch are functionally +// identical. +// +// There can be false positives in cases where the positional +// information is used (as with lockdep) or where the identity +// is a placeholder for not yet handled cases. +// Unfortunately there also seems to be a tendency to use +// the last if else/else as a "default behavior" - which some +// might consider a legitimate coding pattern. From discussion +// on kernelnewbies though it seems that this is not really an +// accepted pattern and if at all it would need to be commented +// +// In the Linux kernel it does not seem to actually report +// false positives except for those that were documented as +// being intentional. +// the two known cases are: +// arch/sh/kernel/traps_64.c:read_opcode() +// } else if ((pc & 1) == 0) { +// /* SHcompact */ +// /* TODO : provide handling for this. We don't really support +// user-mode SHcompact yet, and for a kernel fault, this would +// have to come from a module built for SHcompact. */ +// return -EFAULT; +// } else { +// /* misaligned */ +// return -EFAULT; +// } +// fs/kernfs/file.c:kernfs_fop_open() +// * Both paths of the branch look the same. They're supposed to +// * look that way and give @of->mutex different static lockdep keys. +// */ +// if (has_mmap) +// mutex_init(&of->mutex); +// else +// mutex_init(&of->mutex); +// +// All other cases look like bugs or at least lack of documentation +// +// Confidence: Moderate +// Copyright: (C) 2016 Nicholas Mc Guire, OSADL. +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report + +@cond@ +statement S1; +position p; +@@ + +* if@p (...) S1 else S1 + +@script:python depends on org@ +p << cond.p; +@@ + +cocci.print_main("WARNING: possible condition with no effect (if == else)",p) + +@script:python depends on report@ +p << cond.p; +@@ + +coccilib.report.print_report(p[0],"WARNING: possible condition with no effect (if == else)") diff --git a/scripts/coccinelle/misc/cstptr.cocci b/scripts/coccinelle/misc/cstptr.cocci new file mode 100644 index 000000000..74acf34be --- /dev/null +++ b/scripts/coccinelle/misc/cstptr.cocci @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// PTR_ERR should be applied before its argument is reassigned, typically +/// to NULL +/// +// Confidence: High +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report +virtual context + +@r exists@ +expression e,e1; +constant c; +position p1,p2; +@@ + +*e@p1 = c +... when != e = e1 + when != &e + when != true IS_ERR(e) +*PTR_ERR@p2(e) + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +@@ + +cocci.print_main("PTR_ERR",p2) +cocci.print_secs("assignment",p1) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +@@ + +msg = "ERROR: PTR_ERR applied after initialization to constant on line %s" % (p1[0].line) +coccilib.report.print_report(p2[0],msg) diff --git a/scripts/coccinelle/misc/do_div.cocci b/scripts/coccinelle/misc/do_div.cocci new file mode 100644 index 000000000..79db083c5 --- /dev/null +++ b/scripts/coccinelle/misc/do_div.cocci @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// do_div() does a 64-by-32 division. +/// When the divisor is long, unsigned long, u64, or s64, +/// do_div() truncates it to 32 bits, this means it can test +/// non-zero and be truncated to 0 for division on 64bit platforms. +/// +//# This makes an effort to find those inappropriate do_div() calls. +// +// Confidence: Moderate +// Copyright: (C) 2020 Wen Yang, Alibaba. +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@initialize:python@ +@@ + +def get_digit_type_and_value(str): + is_digit = False + value = 0 + + try: + if (str.isdigit()): + is_digit = True + value = int(str, 0) + elif (str.upper().endswith('ULL')): + is_digit = True + value = int(str[:-3], 0) + elif (str.upper().endswith('LL')): + is_digit = True + value = int(str[:-2], 0) + elif (str.upper().endswith('UL')): + is_digit = True + value = int(str[:-2], 0) + elif (str.upper().endswith('L')): + is_digit = True + value = int(str[:-1], 0) + elif (str.upper().endswith('U')): + is_digit = True + value = int(str[:-1], 0) + except Exception as e: + print('Error:',e) + is_digit = False + value = 0 + finally: + return is_digit, value + +def filter_out_safe_constants(str): + is_digit, value = get_digit_type_and_value(str) + if (is_digit): + if (value >= 0x100000000): + return True + else: + return False + else: + return True + +def construct_warnings(suggested_fun): + msg="WARNING: do_div() does a 64-by-32 division, please consider using %s instead." + return msg % suggested_fun + +@depends on context@ +expression f; +long l: script:python() { filter_out_safe_constants(l) }; +unsigned long ul : script:python() { filter_out_safe_constants(ul) }; +u64 ul64 : script:python() { filter_out_safe_constants(ul64) }; +s64 sl64 : script:python() { filter_out_safe_constants(sl64) }; + +@@ +( +* do_div(f, l); +| +* do_div(f, ul); +| +* do_div(f, ul64); +| +* do_div(f, sl64); +) + +@r depends on (org || report)@ +expression f; +position p; +long l: script:python() { filter_out_safe_constants(l) }; +unsigned long ul : script:python() { filter_out_safe_constants(ul) }; +u64 ul64 : script:python() { filter_out_safe_constants(ul64) }; +s64 sl64 : script:python() { filter_out_safe_constants(sl64) }; +@@ +( +do_div@p(f, l); +| +do_div@p(f, ul); +| +do_div@p(f, ul64); +| +do_div@p(f, sl64); +) + +@script:python depends on org@ +p << r.p; +ul << r.ul; +@@ + +coccilib.org.print_todo(p[0], construct_warnings("div64_ul")) + +@script:python depends on org@ +p << r.p; +l << r.l; +@@ + +coccilib.org.print_todo(p[0], construct_warnings("div64_long")) + +@script:python depends on org@ +p << r.p; +ul64 << r.ul64; +@@ + +coccilib.org.print_todo(p[0], construct_warnings("div64_u64")) + +@script:python depends on org@ +p << r.p; +sl64 << r.sl64; +@@ + +coccilib.org.print_todo(p[0], construct_warnings("div64_s64")) + +@script:python depends on report@ +p << r.p; +ul << r.ul; +@@ + +coccilib.report.print_report(p[0], construct_warnings("div64_ul")) + +@script:python depends on report@ +p << r.p; +l << r.l; +@@ + +coccilib.report.print_report(p[0], construct_warnings("div64_long")) + +@script:python depends on report@ +p << r.p; +sl64 << r.sl64; +@@ + +coccilib.report.print_report(p[0], construct_warnings("div64_s64")) + +@script:python depends on report@ +p << r.p; +ul64 << r.ul64; +@@ + +coccilib.report.print_report(p[0], construct_warnings("div64_u64")) diff --git a/scripts/coccinelle/misc/doubleinit.cocci b/scripts/coccinelle/misc/doubleinit.cocci new file mode 100644 index 000000000..7dbfde3f4 --- /dev/null +++ b/scripts/coccinelle/misc/doubleinit.cocci @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find duplicate field initializations. This has a high rate of false +/// positives due to #ifdefs, which Coccinelle is not aware of in a structure +/// initialization. +/// +// Confidence: Low +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: requires at least Coccinelle 0.2.4, lex or parse error otherwise +// Options: --no-includes --include-headers + +virtual org +virtual report + +@r@ +identifier I, s, fld; +position p0,p; +expression E; +@@ + +struct I s =@p0 { ..., .fld@p = E, ...}; + +@s@ +identifier I, s, r.fld; +position r.p0,p; +expression E; +@@ + +struct I s =@p0 { ..., .fld@p = E, ...}; + +@script:python depends on org@ +p0 << r.p0; +fld << r.fld; +ps << s.p; +pr << r.p; +@@ + +if int(ps[0].line) < int(pr[0].line) or (int(ps[0].line) == int(pr[0].line) and int(ps[0].column) < int(pr[0].column)): + cocci.print_main(fld,p0) + cocci.print_secs("s",ps) + cocci.print_secs("r",pr) + +@script:python depends on report@ +p0 << r.p0; +fld << r.fld; +ps << s.p; +pr << r.p; +@@ + +if int(ps[0].line) < int(pr[0].line) or (int(ps[0].line) == int(pr[0].line) and int(ps[0].column) < int(pr[0].column)): + msg = "%s: first occurrence line %s, second occurrence line %s" % (fld,ps[0].line,pr[0].line) + coccilib.report.print_report(p0[0],msg) diff --git a/scripts/coccinelle/misc/excluded_middle.cocci b/scripts/coccinelle/misc/excluded_middle.cocci new file mode 100644 index 000000000..ab28393e4 --- /dev/null +++ b/scripts/coccinelle/misc/excluded_middle.cocci @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Condition !A || A && B is equivalent to !A || B. +/// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@r depends on !patch@ +expression A, B; +position p; +@@ + +* !A || (A &&@p B) + +@depends on patch@ +expression A, B; +@@ + + !A || +- (A && B) ++ B + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING !A || A && B is equivalent to !A || B") + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING !A || A && B is equivalent to !A || B") diff --git a/scripts/coccinelle/misc/flexible_array.cocci b/scripts/coccinelle/misc/flexible_array.cocci new file mode 100644 index 000000000..f427fd68e --- /dev/null +++ b/scripts/coccinelle/misc/flexible_array.cocci @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Zero-length and one-element arrays are deprecated, see +/// Documentation/process/deprecated.rst +/// Flexible-array members should be used instead. +/// +// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS. +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual report +virtual org +virtual patch + +@initialize:python@ +@@ +def relevant(positions): + for p in positions: + if "uapi" in p.file: + return False + return True + +@r depends on !patch@ +identifier name, array; +type T; +position p : script:python() { relevant(p) }; +@@ + +( + struct name { + ... +* T array@p[\(0\|1\)]; + }; +| + struct { + ... +* T array@p[\(0\|1\)]; + }; +| + union name { + ... +* T array@p[\(0\|1\)]; + }; +| + union { + ... +* T array@p[\(0\|1\)]; + }; +) + +@only_field depends on patch@ +identifier name, array; +type T; +position q; +@@ + +( + struct name {@q + T array[0]; + }; +| + struct {@q + T array[0]; + }; +) + +@depends on patch@ +identifier name, array; +type T; +position p : script:python() { relevant(p) }; +// position @q with rule "only_field" simplifies +// handling of bitfields, arrays, etc. +position q != only_field.q; +@@ + +( + struct name {@q + ... + T array@p[ +- 0 + ]; + }; +| + struct {@q + ... + T array@p[ +- 0 + ]; + }; +) + +@script: python depends on report@ +p << r.p; +@@ + +msg = "WARNING use flexible-array member instead (https://www.kernel.org/doc/html/latest/process/deprecated.html#zero-length-and-one-element-arrays)" +coccilib.report.print_report(p[0], msg) + +@script: python depends on org@ +p << r.p; +@@ + +msg = "WARNING use flexible-array member instead (https://www.kernel.org/doc/html/latest/process/deprecated.html#zero-length-and-one-element-arrays)" +coccilib.org.print_todo(p[0], msg) diff --git a/scripts/coccinelle/misc/ifcol.cocci b/scripts/coccinelle/misc/ifcol.cocci new file mode 100644 index 000000000..442742467 --- /dev/null +++ b/scripts/coccinelle/misc/ifcol.cocci @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find confusingly indented code in or after an if. An if branch should +/// be indented. The code following an if should not be indented. +/// Sometimes, code after an if that is indented is actually intended to be +/// part of the if branch. +/// +//# This has a high rate of false positives, because Coccinelle's column +//# calculation does not distinguish between spaces and tabs, so code that +//# is not visually aligned may be considered to be in the same column. +// +// Confidence: Low +// Copyright: (C) 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2010 Julia Lawall, DIKU. +// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report + +@r disable braces4@ +position p1,p2; +statement S1,S2; +@@ + +( +if (...) { ... } +| +if (...) S1@p1 S2@p2 +) + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +@@ + +if (p1[0].column == p2[0].column): + cocci.print_main("branch",p1) + cocci.print_secs("after",p2) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +@@ + +if (p1[0].column == p2[0].column): + msg = "code aligned with following code on line %s" % (p2[0].line) + coccilib.report.print_report(p1[0],msg) diff --git a/scripts/coccinelle/misc/irqf_oneshot.cocci b/scripts/coccinelle/misc/irqf_oneshot.cocci new file mode 100644 index 000000000..9b6f404d0 --- /dev/null +++ b/scripts/coccinelle/misc/irqf_oneshot.cocci @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +/// Since commit 1c6c69525b40 ("genirq: Reject bogus threaded irq requests") +/// threaded IRQs without a primary handler need to be requested with +/// IRQF_ONESHOT, otherwise the request will fail. +/// +/// So pass the IRQF_ONESHOT flag in this case. +/// +// +// Confidence: Moderate +// Comments: +// Options: --no-includes + +virtual patch +virtual context +virtual org +virtual report + +@r1@ +expression dev, irq, thread_fn; +position p; +@@ +( +request_threaded_irq@p(irq, NULL, thread_fn, +( +IRQF_ONESHOT | ... +| +IRQF_ONESHOT +) +, ...) +| +devm_request_threaded_irq@p(dev, irq, NULL, thread_fn, +( +IRQF_ONESHOT | ... +| +IRQF_ONESHOT +) +, ...) +) + +@r2@ +expression dev, irq, thread_fn, flags, e; +position p != r1.p; +@@ +( +flags = IRQF_ONESHOT | ... +| +flags |= IRQF_ONESHOT | ... +) +... when != flags = e +( +request_threaded_irq@p(irq, NULL, thread_fn, flags, ...); +| +devm_request_threaded_irq@p(dev, irq, NULL, thread_fn, flags, ...); +) + +@depends on patch@ +expression dev, irq, thread_fn, flags; +position p != {r1.p,r2.p}; +@@ +( +request_threaded_irq@p(irq, NULL, thread_fn, +( +-0 ++IRQF_ONESHOT +| +-flags ++flags | IRQF_ONESHOT +) +, ...) +| +devm_request_threaded_irq@p(dev, irq, NULL, thread_fn, +( +-0 ++IRQF_ONESHOT +| +-flags ++flags | IRQF_ONESHOT +) +, ...) +) + +@depends on context@ +expression dev, irq; +position p != {r1.p,r2.p}; +@@ +( +*request_threaded_irq@p(irq, NULL, ...) +| +*devm_request_threaded_irq@p(dev, irq, NULL, ...) +) + + +@match depends on report || org@ +expression dev, irq; +position p != {r1.p,r2.p}; +@@ +( +request_threaded_irq@p(irq, NULL, ...) +| +devm_request_threaded_irq@p(dev, irq, NULL, ...) +) + +@script:python depends on org@ +p << match.p; +@@ +msg = "WARNING: Threaded IRQ with no primary handler requested without IRQF_ONESHOT (unless it is nested IRQ)" +coccilib.org.print_todo(p[0],msg) + +@script:python depends on report@ +p << match.p; +@@ +msg = "WARNING: Threaded IRQ with no primary handler requested without IRQF_ONESHOT (unless it is nested IRQ)" +coccilib.report.print_report(p[0],msg) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci new file mode 100644 index 000000000..b83d4a01e --- /dev/null +++ b/scripts/coccinelle/misc/minmax.cocci @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Check for opencoded min(), max() implementations. +/// Generated patches sometimes require adding a cast to fix compile warning. +/// Warnings/patches scope intentionally limited to a function body. +/// +// Confidence: Medium +// Copyright: (C) 2021 Denis Efremov ISPRAS +// Options: --no-includes --include-headers +// +// Keywords: min, max +// + + +virtual report +virtual org +virtual context +virtual patch + +@max_candidate disable not_int1, not_int2, neg_if_exp@ +expression E1, E2, E3, E4; +binary operator cmp = {>, >=}; +@@ + + E1 cmp E2 ? E3 : E4 + +@min_candidate disable not_int1, not_int2, neg_if_exp@ +expression E1, E2, E3, E4; +binary operator cmp = {<, <=}; +@@ + + E1 cmp E2 ? E3 : E4 + +@rmax depends on !patch && max_candidate disable not_int1, not_int2, neg_if_exp@ +identifier func; +expression x, y; +binary operator cmp = {>, >=}; +position p; +@@ + +func(...) +{ + <... +* (x) cmp@p (y) ? (x) : (y) + ...> +} + +@maxif_candidate disable not_int1, not_int2, neg_if@ +expression x, y; +expression max_val; +binary operator cmp = {>, >=}; +@@ + +if ((x) cmp (y)) { + max_val = (x); +} else { + max_val = (y); +} + +@rmaxif depends on !patch && maxif_candidate disable not_int1, not_int2, neg_if@ +identifier func; +expression x, y; +expression max_val; +binary operator cmp = {>, >=}; +position p; +@@ + +func(...) +{ + <... +* if ((x) cmp@p (y)) { +* max_val = (x); +* } else { +* max_val = (y); +* } + ...> +} + +// Ignore errcode returns. +@errcode depends on min_candidate disable not_int1, not_int2, neg_if_exp@ +position p; +identifier func; +expression x; +binary operator cmp = {<, <=}; +@@ + +func(...) +{ + <... + return ((x) cmp@p 0 ? (x) : 0); + ...> +} + +@rmin depends on !patch && min_candidate disable not_int1, not_int2, neg_if_exp@ +identifier func; +expression x, y; +binary operator cmp = {<, <=}; +position p != errcode.p; +@@ + +func(...) +{ + <... +* (x) cmp@p (y) ? (x) : (y) + ...> +} + +@minif_candidate disable not_int1, not_int2, neg_if@ +expression x, y; +expression min_val; +binary operator cmp = {<, <=}; +@@ + +if ((x) cmp (y)) { + min_val = (x); +} else { + min_val = (y); +} + +@rminif depends on !patch && minif_candidate disable not_int1, not_int2, neg_if@ +identifier func; +expression x, y; +expression min_val; +binary operator cmp = {<, <=}; +position p; +@@ + +func(...) +{ + <... +* if ((x) cmp@p (y)) { +* min_val = (x); +* } else { +* min_val = (y); +* } + ...> +} + +@pmax depends on patch && max_candidate disable not_int1, not_int2, neg_if_exp@ +identifier func; +expression x, y; +binary operator cmp = {>=, >}; +@@ + +func(...) +{ + <... +- ((x) cmp (y) ? (x) : (y)) ++ max(x, y) + ...> +} + +@pmaxif depends on patch && maxif_candidate disable not_int1, not_int2, neg_if@ +identifier func; +expression x, y; +expression max_val; +binary operator cmp = {>=, >}; +@@ + +func(...) +{ + <... +- if ((x) cmp (y)) { +- max_val = x; +- } else { +- max_val = y; +- } ++ max_val = max(x, y); + ...> +} + +@pmin depends on patch && min_candidate disable not_int1, not_int2, neg_if_exp@ +identifier func; +expression x, y; +binary operator cmp = {<=, <}; +position p != errcode.p; +@@ + +func(...) +{ + <... +- ((x) cmp@p (y) ? (x) : (y)) ++ min(x, y) + ...> +} + +@pminif depends on patch && minif_candidate disable not_int1, not_int2, neg_if@ +identifier func; +expression x, y; +expression min_val; +binary operator cmp = {<=, <}; +@@ + +func(...) +{ + <... +- if ((x) cmp (y)) { +- min_val = x; +- } else { +- min_val = y; +- } ++ min_val = min(x, y); + ...> +} + +@script:python depends on report@ +p << rmax.p; +@@ + +for p0 in p: + coccilib.report.print_report(p0, "WARNING opportunity for max()") + +@script:python depends on org@ +p << rmax.p; +@@ + +for p0 in p: + coccilib.org.print_todo(p0, "WARNING opportunity for max()") + +@script:python depends on report@ +p << rmaxif.p; +@@ + +for p0 in p: + coccilib.report.print_report(p0, "WARNING opportunity for max()") + +@script:python depends on org@ +p << rmaxif.p; +@@ + +for p0 in p: + coccilib.org.print_todo(p0, "WARNING opportunity for max()") + +@script:python depends on report@ +p << rmin.p; +@@ + +for p0 in p: + coccilib.report.print_report(p0, "WARNING opportunity for min()") + +@script:python depends on org@ +p << rmin.p; +@@ + +for p0 in p: + coccilib.org.print_todo(p0, "WARNING opportunity for min()") + +@script:python depends on report@ +p << rminif.p; +@@ + +for p0 in p: + coccilib.report.print_report(p0, "WARNING opportunity for min()") + +@script:python depends on org@ +p << rminif.p; +@@ + +for p0 in p: + coccilib.org.print_todo(p0, "WARNING opportunity for min()") diff --git a/scripts/coccinelle/misc/newline_in_nl_msg.cocci b/scripts/coccinelle/misc/newline_in_nl_msg.cocci new file mode 100644 index 000000000..2814f6b20 --- /dev/null +++ b/scripts/coccinelle/misc/newline_in_nl_msg.cocci @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Catch strings ending in newline with (GE)NL_SET_ERR_MSG*. +/// +// Confidence: Very High +// Copyright: (C) 2020 Intel Corporation +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@r depends on context || org || report@ +expression e; +constant m; +position 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; +@@ + +if not m.endswith("\\n\""): + cocci.include_match(False) + +@r1 depends on r@ +identifier fname; +expression r.e; +constant r.m; +position r.p; +@@ + fname(e,m@p,...) + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@depends on context && r@ +identifier r1.fname; +expression r.e; +constant r.m; +@@ +* fname(e,m,...) + +//---------------------------------------------------------- +// For org mode +//---------------------------------------------------------- + +@script:python depends on org@ +fname << r1.fname; +m << r.m; +p << r.p; +@@ + +if m.endswith("\\n\""): + msg="WARNING avoid newline at end of message in %s" % (fname) + msg_safe=msg.replace("[","@(").replace("]",")") + coccilib.org.print_todo(p[0], msg_safe) + +//---------------------------------------------------------- +// For report mode +//---------------------------------------------------------- + +@script:python depends on report@ +fname << r1.fname; +m << r.m; +p << r.p; +@@ + +if m.endswith("\\n\""): + msg="WARNING avoid newline at end of message in %s" % (fname) + coccilib.report.print_report(p[0], msg) diff --git a/scripts/coccinelle/misc/noderef.cocci b/scripts/coccinelle/misc/noderef.cocci new file mode 100644 index 000000000..37ee7358d --- /dev/null +++ b/scripts/coccinelle/misc/noderef.cocci @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// sizeof when applied to a pointer typed expression gives the size of +/// the pointer +/// +// Confidence: High +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report +virtual context +virtual patch + +@depends on patch@ +expression *x; +expression f; +expression i; +type T; +@@ + +( +x = <+... sizeof( +- x ++ *x + ) ...+> +| +f(...,(T)(x),...,sizeof( +- x ++ *x + ),...) +| +f(...,sizeof( +- x ++ *x + ),...,(T)(x),...) +| +f(...,(T)(x),...,i*sizeof( +- x ++ *x + ),...) +| +f(...,i*sizeof( +- x ++ *x + ),...,(T)(x),...) +) + +@r depends on !patch@ +expression *x; +expression f; +expression i; +position p; +type T; +@@ + +( +*x = <+... sizeof@p(x) ...+> +| +*f(...,(T)(x),...,sizeof@p(x),...) +| +*f(...,sizeof@p(x),...,(T)(x),...) +| +*f(...,(T)(x),...,i*sizeof@p(x),...) +| +*f(...,i*sizeof@p(x),...,(T)(x),...) +) + +@script:python depends on org@ +p << r.p; +@@ + +cocci.print_main("application of sizeof to pointer",p) + +@script:python depends on report@ +p << r.p; +@@ + +msg = "ERROR: application of sizeof to pointer" +coccilib.report.print_report(p[0],msg) diff --git a/scripts/coccinelle/misc/of_table.cocci b/scripts/coccinelle/misc/of_table.cocci new file mode 100644 index 000000000..17881cb08 --- /dev/null +++ b/scripts/coccinelle/misc/of_table.cocci @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/// Make sure (of/i2c/platform/spi)_device_id tables are NULL terminated +// +// Keywords: of_table i2c_table platform_table +// Confidence: Medium +// Options: --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@depends on context@ +identifier var, arr; +expression E; +@@ +( +struct \(of_device_id \| i2c_device_id \| platform_device_id \| spi_device_id\) arr[] = { + ..., + { + .var = E, +* } +}; +| +struct \(of_device_id \| i2c_device_id \| platform_device_id \| spi_device_id\) arr[] = { + ..., +* { ..., E, ... }, +}; +) + +@depends on patch@ +identifier var, arr; +expression E; +@@ +( +struct \(of_device_id \| i2c_device_id \| platform_device_id \| spi_device_id\) arr[] = { + ..., + { + .var = E, +- } ++ }, ++ { } +}; +| +struct \(of_device_id \| i2c_device_id \| platform_device_id \| spi_device_id\) arr[] = { + ..., + { ..., E, ... }, ++ { }, +}; +) + +@r depends on org || report@ +position p1; +identifier var, arr; +expression E; +@@ +( +struct \(of_device_id \| i2c_device_id \| platform_device_id \| spi_device_id\) arr[] = { + ..., + { + .var = E, + } + @p1 +}; +| +struct \(of_device_id \| i2c_device_id \| platform_device_id \| spi_device_id\) arr[] = { + ..., + { ..., E, ... } + @p1 +}; +) + +@script:python depends on org@ +p1 << r.p1; +arr << r.arr; +@@ + +cocci.print_main(arr,p1) + +@script:python depends on report@ +p1 << r.p1; +arr << r.arr; +@@ + +msg = "%s is not NULL terminated at line %s" % (arr, p1[0].line) +coccilib.report.print_report(p1[0],msg) diff --git a/scripts/coccinelle/misc/orplus.cocci b/scripts/coccinelle/misc/orplus.cocci new file mode 100644 index 000000000..3a1566f1a --- /dev/null +++ b/scripts/coccinelle/misc/orplus.cocci @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Check for constants that are added but are used elsewhere as bitmasks +/// The results should be checked manually to ensure that the nonzero +/// bits in the two constants are actually disjoint. +/// +// Confidence: Moderate +// Copyright: (C) 2013 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2013 Gilles Muller, INRIA/LIP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report +virtual context + +@r@ +constant c,c1; +identifier i,i1; +position p; +@@ + +( + c1 + c - 1 +| + c1@i1 +@p c@i +) + +@s@ +constant r.c, r.c1; +identifier i; +expression e; +@@ + +( +e | c@i +| +e & c@i +| +e |= c@i +| +e &= c@i +| +e | c1@i +| +e & c1@i +| +e |= c1@i +| +e &= c1@i +) + +@depends on s@ +position r.p; +constant c1,c2; +@@ + +* c1 +@p c2 + +@script:python depends on s && org@ +p << r.p; +@@ + +cocci.print_main("sum of probable bitmasks, consider |",p) + +@script:python depends on s && report@ +p << r.p; +@@ + +msg = "WARNING: sum of probable bitmasks, consider |" +coccilib.report.print_report(p[0],msg) diff --git a/scripts/coccinelle/misc/ptr_err_to_pe.cocci b/scripts/coccinelle/misc/ptr_err_to_pe.cocci new file mode 100644 index 000000000..0494c7709 --- /dev/null +++ b/scripts/coccinelle/misc/ptr_err_to_pe.cocci @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use %pe format specifier instead of PTR_ERR() for printing error pointers. +/// +/// For printing error pointers (i.e., a pointer for which IS_ERR() is true) +/// %pe will print a symbolic error name (e.g., -EINVAL), opposed to the raw +/// errno (e.g., -22) produced by PTR_ERR(). +/// It also makes the code cleaner by saving a redundant call to PTR_ERR(). +/// +// Confidence: High +// Copyright: (C) 2025 NVIDIA CORPORATION & AFFILIATES. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@r@ +expression ptr; +constant fmt; +position p; +identifier print_func; +@@ +* print_func(..., fmt, ..., PTR_ERR@p(ptr), ...) + +@script:python depends on r && report@ +p << r.p; +@@ +coccilib.report.print_report(p[0], "WARNING: Consider using %pe to print PTR_ERR()") + +@script:python depends on r && org@ +p << r.p; +@@ +coccilib.org.print_todo(p[0], "WARNING: Consider using %pe to print PTR_ERR()") diff --git a/scripts/coccinelle/misc/returnvar.cocci b/scripts/coccinelle/misc/returnvar.cocci new file mode 100644 index 000000000..7ffc55bf5 --- /dev/null +++ b/scripts/coccinelle/misc/returnvar.cocci @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Remove unneeded variable used to store return value. +/// +// Confidence: Moderate +// Copyright: (C) 2012 Peter Senna Tschudin, INRIA/LIP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: Comments on code can be deleted if near code that is removed. +// "when strict" can be removed to get more hits, but adds false +// positives +// Options: --no-includes --include-headers + +virtual patch +virtual report +virtual context +virtual org + +@depends on patch@ +type T; +constant C; +identifier ret; +@@ +- T ret = C; +... when != ret + when strict +return +- ret ++ C +; + +@depends on context@ +type T; +constant C; +identifier ret; +@@ +* T ret = C; +... when != ret + when strict +* return ret; + +@r1 depends on report || org@ +type T; +constant C; +identifier ret; +position p1, p2; +@@ +T ret@p1 = C; +... when != ret + when strict +return ret@p2; + +@script:python depends on report@ +p1 << r1.p1; +p2 << r1.p2; +C << r1.C; +ret << r1.ret; +@@ +coccilib.report.print_report(p1[0], "Unneeded variable: \"" + ret + "\". Return \"" + C + "\" on line " + p2[0].line) + +@script:python depends on org@ +p1 << r1.p1; +p2 << r1.p2; +C << r1.C; +ret << r1.ret; +@@ +cocci.print_main("unneeded \"" + ret + "\" variable", p1) +cocci.print_sec("return " + C + " here", p2) diff --git a/scripts/coccinelle/misc/secs_to_jiffies.cocci b/scripts/coccinelle/misc/secs_to_jiffies.cocci new file mode 100644 index 000000000..f3241ce75 --- /dev/null +++ b/scripts/coccinelle/misc/secs_to_jiffies.cocci @@ -0,0 +1,71 @@ +// 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 +// Options: --include-headers + +virtual patch +virtual report +virtual context + +@pconst depends on patch@ constant C; @@ + +- msecs_to_jiffies(C * 1000) ++ secs_to_jiffies(C) + +@pconstms depends on patch@ constant C; @@ + +- msecs_to_jiffies(C * MSEC_PER_SEC) ++ secs_to_jiffies(C) + +@pexpr depends on patch@ expression E; @@ + +- msecs_to_jiffies(E * 1000) ++ secs_to_jiffies(E) + +@pexprms depends on patch@ expression E; @@ + +- msecs_to_jiffies(E * MSEC_PER_SEC) ++ secs_to_jiffies(E) + +@r depends on report && !patch@ +constant C; +expression E; +position p; +@@ + +( + msecs_to_jiffies(C@p * 1000) +| + msecs_to_jiffies(C@p * MSEC_PER_SEC) +| + msecs_to_jiffies(E@p * 1000) +| + msecs_to_jiffies(E@p * MSEC_PER_SEC) +) + +@c depends on context && !patch@ +constant C; +expression E; +@@ + +( +* msecs_to_jiffies(C * 1000) +| +* msecs_to_jiffies(C * MSEC_PER_SEC) +| +* msecs_to_jiffies(E * 1000) +| +* msecs_to_jiffies(E * MSEC_PER_SEC) +) + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for secs_to_jiffies()") diff --git a/scripts/coccinelle/misc/semicolon.cocci b/scripts/coccinelle/misc/semicolon.cocci new file mode 100644 index 000000000..4476bf873 --- /dev/null +++ b/scripts/coccinelle/misc/semicolon.cocci @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Remove unneeded semicolon. +/// +// Confidence: Moderate +// Copyright: (C) 2012 Peter Senna Tschudin, INRIA/LIP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: Some false positives on empty default cases in switch statements. +// Options: --no-includes --include-headers + +virtual patch +virtual report +virtual context +virtual org + +@r_default@ +position p; +@@ +switch (...) +{ +default: ...;@p +} + +@r_case@ +position p; +@@ +( +switch (...) +{ +case ...:;@p +} +| +switch (...) +{ +case ...:... +case ...:;@p +} +| +switch (...) +{ +case ...:... +case ...: +case ...:;@p +} +) + +@r1@ +statement S; +position p1; +position p != {r_default.p, r_case.p}; +identifier label; +@@ +( +label:; +| +S@p1;@p +) + +@script:python@ +p << r1.p; +p1 << r1.p1; +@@ +if p[0].line != p1[0].line_end: + cocci.include_match(False) + +@depends on patch@ +position r1.p; +@@ +-;@p + +@script:python depends on report@ +p << r1.p; +@@ +coccilib.report.print_report(p[0],"Unneeded semicolon") + +@depends on context@ +position r1.p; +@@ +*;@p + +@script:python depends on org@ +p << r1.p; +@@ +cocci.print_main("Unneeded semicolon",p) diff --git a/scripts/coccinelle/misc/struct_size.cocci b/scripts/coccinelle/misc/struct_size.cocci new file mode 100644 index 000000000..406884d68 --- /dev/null +++ b/scripts/coccinelle/misc/struct_size.cocci @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Check for code that could use struct_size(). +/// +// Confidence: Medium +// Author: Jacob Keller <jacob.e.keller@intel.com> +// Copyright: (C) 2023 Intel Corporation +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +// the overflow Kunit tests have some code which intentionally does not use +// the macros, so we want to ignore this code when reporting potential +// issues. +@overflow_tests@ +identifier f = overflow_size_helpers_test; +@@ + +f + +//---------------------------------------------------------- +// For context mode +//---------------------------------------------------------- + +@depends on !overflow_tests && context@ +expression E1, E2; +identifier m; +@@ +* sizeof(*E1) + (E2 * sizeof(*E1->m)) + +//---------------------------------------------------------- +// For patch mode +//---------------------------------------------------------- + +@depends on !overflow_tests && patch@ +expression E1, E2; +identifier m; +@@ +( +- (sizeof(*E1) + (E2 * sizeof(*E1->m))) ++ struct_size(E1, m, E2) +) + +//---------------------------------------------------------- +// For org and report mode +//---------------------------------------------------------- + +@r depends on !overflow_tests && (org || report)@ +expression E1, E2; +identifier m; +position p; +@@ + sizeof(*E1)@p + (E2 * sizeof(*E1->m)) + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING should use struct_size") + +@script:python depends on report@ +p << r.p; +@@ + +msg="WARNING: Use struct_size" +coccilib.report.print_report(p[0], msg) + diff --git a/scripts/coccinelle/misc/swap.cocci b/scripts/coccinelle/misc/swap.cocci new file mode 100644 index 000000000..c5e71b7ef --- /dev/null +++ b/scripts/coccinelle/misc/swap.cocci @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Check for opencoded swap() implementation. +/// +// Confidence: High +// Copyright: (C) 2021 Denis Efremov ISPRAS +// Options: --no-includes --include-headers +// +// Keywords: swap +// + +virtual patch +virtual org +virtual report +virtual context + +@rvar depends on !patch@ +identifier tmp; +expression a, b; +type T; +position p; +@@ + +( +* T tmp; +| +* T tmp = 0; +| +* T *tmp = NULL; +) +... when != tmp +* tmp = a; +* a = b;@p +* b = tmp; +... when != tmp + +@r depends on !patch@ +identifier tmp; +expression a, b; +position p != rvar.p; +@@ + +* tmp = a; +* a = b;@p +* b = tmp; + +@rpvar depends on patch@ +identifier tmp; +expression a, b; +type T; +@@ + +( +- T tmp; +| +- T tmp = 0; +| +- T *tmp = NULL; +) +... when != tmp +- tmp = a; +- a = b; +- b = tmp ++ swap(a, b) + ; +... when != tmp + +@rp depends on patch@ +identifier tmp; +expression a, b; +@@ + +- tmp = a; +- a = b; +- b = tmp ++ swap(a, b) + ; + +@depends on patch && (rpvar || rp)@ +@@ + +( + for (...;...;...) +- { + swap(...); +- } +| + while (...) +- { + swap(...); +- } +| + if (...) +- { + swap(...); +- } +) + + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for swap()") + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING opportunity for swap()") + +@script:python depends on report@ +p << rvar.p; +@@ + +coccilib.report.print_report(p[0], "WARNING opportunity for swap()") + +@script:python depends on org@ +p << rvar.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING opportunity for swap()") diff --git a/scripts/coccinelle/misc/test_addr.cocci b/scripts/coccinelle/misc/test_addr.cocci new file mode 100644 index 000000000..d559b2974 --- /dev/null +++ b/scripts/coccinelle/misc/test_addr.cocci @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// The address of a variable or field is likely always to be non-zero. +/// +// Confidence: High +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual org +virtual report +virtual context + +@r@ +expression x; +position p; +@@ + +*\(&x@p == NULL \| &x@p != NULL\) + +@script:python depends on org@ +p << r.p; +@@ + +cocci.print_main("test of a variable/field address",p) + +@script:python depends on report@ +p << r.p; +@@ + +msg = "ERROR: test of a variable/field address" +coccilib.report.print_report(p[0],msg) diff --git a/scripts/coccinelle/misc/uninitialized_var.cocci b/scripts/coccinelle/misc/uninitialized_var.cocci new file mode 100644 index 000000000..69bbaae47 --- /dev/null +++ b/scripts/coccinelle/misc/uninitialized_var.cocci @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// Please, don't reintroduce uninitialized_var(). +/// +/// From Documentation/process/deprecated.rst, +/// commit 4b19bec97c88 ("docs: deprecated.rst: Add uninitialized_var()"): +/// For any compiler warnings about uninitialized variables, just add +/// an initializer. Using warning-silencing tricks is dangerous as it +/// papers over real bugs (or can in the future), and suppresses unrelated +/// compiler warnings (e.g. "unused variable"). If the compiler thinks it +/// is uninitialized, either simply initialize the variable or make compiler +/// changes. Keep in mind that in most cases, if an initialization is +/// obviously redundant, the compiler's dead-store elimination pass will make +/// sure there are no needless variable writes. +/// +/// Later, commit 3942ea7a10c9 ("deprecated.rst: Remove now removed +/// uninitialized_var") removed this section because all initializations of +/// this kind were cleaned-up from the kernel. This cocci rule checks that +/// the macro is not explicitly or implicitly reintroduced. +/// +// Confidence: High +// Copyright: (C) 2020 Denis Efremov ISPRAS +// Options: --no-includes --include-headers +// + +virtual context +virtual report +virtual org + +@r@ +identifier var; +type T; +position p; +@@ + +( +* T var =@p var; +| +* T var =@p *(&(var)); +| +* var =@p var +| +* var =@p *(&(var)) +) + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0], "WARNING this kind of initialization is deprecated") + +@script:python depends on org@ +p << r.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING this kind of initialization is deprecated") diff --git a/scripts/coccinelle/misc/warn.cocci b/scripts/coccinelle/misc/warn.cocci new file mode 100644 index 000000000..b5f428035 --- /dev/null +++ b/scripts/coccinelle/misc/warn.cocci @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Use WARN(1,...) rather than printk followed by WARN_ON(1) +/// +// Confidence: High +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@bad1@ +position p; +@@ + +printk(...); +printk@p(...); +WARN_ON(1); + +@r1 depends on context || report || org@ +position p != bad1.p; +@@ + + printk@p(...); +*WARN_ON(1); + +@script:python depends on org@ +p << r1.p; +@@ + +cocci.print_main("printk + WARN_ON can be just WARN",p) + +@script:python depends on report@ +p << r1.p; +@@ + +msg = "SUGGESTION: printk + WARN_ON can be just WARN" +coccilib.report.print_report(p[0],msg) + +@ok1 depends on patch@ +expression list es; +position p != bad1.p; +@@ + +-printk@p( ++WARN(1, + es); +-WARN_ON(1); + +@depends on patch@ +expression list ok1.es; +@@ + +if (...) +- { + WARN(1,es); +- } + +// -------------------------------------------------------------------- + +@bad2@ +position p; +@@ + +printk(...); +printk@p(...); +WARN_ON_ONCE(1); + +@r2 depends on context || report || org@ +position p != bad1.p; +@@ + + printk@p(...); +*WARN_ON_ONCE(1); + +@script:python depends on org@ +p << r2.p; +@@ + +cocci.print_main("printk + WARN_ON_ONCE can be just WARN_ONCE",p) + +@script:python depends on report@ +p << r2.p; +@@ + +msg = "SUGGESTION: printk + WARN_ON_ONCE can be just WARN_ONCE" +coccilib.report.print_report(p[0],msg) + +@ok2 depends on patch@ +expression list es; +position p != bad2.p; +@@ + +-printk@p( ++WARN_ONCE(1, + es); +-WARN_ON_ONCE(1); + +@depends on patch@ +expression list ok2.es; +@@ + +if (...) +- { + WARN_ONCE(1,es); +- } diff --git a/scripts/coccinelle/null/badzero.cocci b/scripts/coccinelle/null/badzero.cocci new file mode 100644 index 000000000..35d443825 --- /dev/null +++ b/scripts/coccinelle/null/badzero.cocci @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Compare pointer-typed values to NULL rather than 0 +/// +//# This makes an effort to choose between !x and x == NULL. !x is used +//# if it has previously been used with the function used to initialize x. +//# This relies on type information. More type information can be obtained +//# using the option -all_includes and the option -I to specify an +//# include path. +// +// Confidence: High +// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Requires: 1.0.0 +// Options: + +virtual patch +virtual context +virtual org +virtual report + +@initialize:ocaml@ +@@ +let negtable = Hashtbl.create 101 + +@depends on patch@ +expression *E; +identifier f; +@@ + +( + (E = f(...)) == +- 0 ++ NULL +| + (E = f(...)) != +- 0 ++ NULL +| +- 0 ++ NULL + == (E = f(...)) +| +- 0 ++ NULL + != (E = f(...)) +) + + +@t1 depends on !patch@ +expression *E; +identifier f; +position p; +@@ + +( + (E = f(...)) == +* 0@p +| + (E = f(...)) != +* 0@p +| +* 0@p + == (E = f(...)) +| +* 0@p + != (E = f(...)) +) + +@script:python depends on org@ +p << t1.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0") + +@script:python depends on report@ +p << t1.p; +@@ + +coccilib.report.print_report(p[0], "WARNING comparing pointer to 0") + +// Tests of returned values + +@s@ +identifier f; +expression E,E1; +@@ + + E = f(...) + ... when != E = E1 + !E + +@script:ocaml depends on s@ +f << s.f; +@@ + +try let _ = Hashtbl.find negtable f in () +with Not_found -> Hashtbl.add negtable f () + +@ r disable is_zero,isnt_zero exists @ +expression *E; +identifier f; +@@ + +E = f(...) +... +(E == 0 +|E != 0 +|0 == E +|0 != E +) + +@script:ocaml@ +f << r.f; +@@ + +try let _ = Hashtbl.find negtable f in () +with Not_found -> include_match false + +// This rule may lead to inconsistent path problems, if E is defined in two +// places +@ depends on patch disable is_zero,isnt_zero @ +expression *E; +expression E1; +identifier r.f; +@@ + +E = f(...) +<... +( +- E == 0 ++ !E +| +- E != 0 ++ E +| +- 0 == E ++ !E +| +- 0 != E ++ E +) +...> +?E = E1 + +@t2 depends on !patch disable is_zero,isnt_zero @ +expression *E; +expression E1; +identifier r.f; +position p1; +position p2; +@@ + +E = f(...) +<... +( +* E == 0@p1 +| +* E != 0@p2 +| +* 0@p1 == E +| +* 0@p1 != E +) +...> +?E = E1 + +@script:python depends on org@ +p << t2.p1; +@@ + +coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0, suggest !E") + +@script:python depends on org@ +p << t2.p2; +@@ + +coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0") + +@script:python depends on report@ +p << t2.p1; +@@ + +coccilib.report.print_report(p[0], "WARNING comparing pointer to 0, suggest !E") + +@script:python depends on report@ +p << t2.p2; +@@ + +coccilib.report.print_report(p[0], "WARNING comparing pointer to 0") + +@ depends on patch disable is_zero,isnt_zero @ +expression *E; +@@ + +( + E == +- 0 ++ NULL +| + E != +- 0 ++ NULL +| +- 0 ++ NULL + == E +| +- 0 ++ NULL + != E +) + +@ t3 depends on !patch disable is_zero,isnt_zero @ +expression *E; +position p; +@@ + +( +* E == 0@p +| +* E != 0@p +| +* 0@p == E +| +* 0@p != E +) + +@script:python depends on org@ +p << t3.p; +@@ + +coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0") + +@script:python depends on report@ +p << t3.p; +@@ + +coccilib.report.print_report(p[0], "WARNING comparing pointer to 0") diff --git a/scripts/coccinelle/null/deref_null.cocci b/scripts/coccinelle/null/deref_null.cocci new file mode 100644 index 000000000..fdf098d4f --- /dev/null +++ b/scripts/coccinelle/null/deref_null.cocci @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// +/// A variable is dereferenced under a NULL test. +/// Even though it is known to be NULL. +/// +// Confidence: Moderate +// Copyright: (C) 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2010 Julia Lawall, DIKU. +// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: -I ... -all_includes can give more complete results +// Options: + +virtual context +virtual org +virtual report + +// The following two rules are separate, because both can match a single +// expression in different ways +@pr1 expression@ +expression E; +identifier f; +position p1; +@@ + + (E != NULL && ...) ? <+...E->f@p1...+> : ... + +@pr2 expression@ +expression E; +identifier f; +position p2; +@@ + +( + (E != NULL) && ... && <+...E->f@p2...+> +| + (E == NULL) || ... || <+...E->f@p2...+> +| + sizeof(<+...E->f@p2...+>) +) + +@ifm@ +expression *E; +statement S1,S2; +position p1; +@@ + +if@p1 ((E == NULL && ...) || ...) S1 else S2 + +// For org and report modes + +@r depends on !context && (org || report) exists@ +expression subE <= ifm.E; +expression *ifm.E; +expression E1,E2; +identifier f; +statement S1,S2,S3,S4; +iterator iter; +position p!={pr1.p1,pr2.p2}; +position ifm.p1; +@@ + +if@p1 ((E == NULL && ...) || ...) +{ + ... when != if (...) S1 else S2 +( + iter(subE,...) S4 // no use +| + list_remove_head(E2,subE,...) +| + subE = E1 +| + for(subE = E1;...;...) S4 +| + subE++ +| + ++subE +| + --subE +| + subE-- +| + &subE +| + E->f@p // bad use +) + ... when any + return ...; +} +else S3 + +@script:python depends on !context && !org && report@ +p << r.p; +p1 << ifm.p1; +x << ifm.E; +@@ + +msg="ERROR: %s is NULL but dereferenced." % (x) +coccilib.report.print_report(p[0], msg) +cocci.include_match(False) + +@script:python depends on !context && org && !report@ +p << r.p; +p1 << ifm.p1; +x << ifm.E; +@@ + +msg="ERROR: %s is NULL but dereferenced." % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +cocci.print_main(msg_safe,p) +cocci.include_match(False) + +@s depends on !context && (org || report) exists@ +expression subE <= ifm.E; +expression *ifm.E; +expression E1,E2; +identifier f; +statement S1,S2,S3,S4; +iterator iter; +position p!={pr1.p1,pr2.p2}; +position ifm.p1; +@@ + +if@p1 ((E == NULL && ...) || ...) +{ + ... when != if (...) S1 else S2 +( + iter(subE,...) S4 // no use +| + list_remove_head(E2,subE,...) +| + subE = E1 +| + for(subE = E1;...;...) S4 +| + subE++ +| + ++subE +| + --subE +| + subE-- +| + &subE +| + E->f@p // bad use +) + ... when any +} +else S3 + +@script:python depends on !context && !org && report@ +p << s.p; +p1 << ifm.p1; +x << ifm.E; +@@ + +msg="ERROR: %s is NULL but dereferenced." % (x) +coccilib.report.print_report(p[0], msg) + +@script:python depends on !context && org && !report@ +p << s.p; +p1 << ifm.p1; +x << ifm.E; +@@ + +msg="ERROR: %s is NULL but dereferenced." % (x) +msg_safe=msg.replace("[","@(").replace("]",")") +cocci.print_main(msg_safe,p) + +// For context mode + +@depends on context && !org && !report exists@ +expression subE <= ifm.E; +expression *ifm.E; +expression E1,E2; +identifier f; +statement S1,S2,S3,S4; +iterator iter; +position p!={pr1.p1,pr2.p2}; +position ifm.p1; +@@ + +if@p1 ((E == NULL && ...) || ...) +{ + ... when != if (...) S1 else S2 +( + iter(subE,...) S4 // no use +| + list_remove_head(E2,subE,...) +| + subE = E1 +| + for(subE = E1;...;...) S4 +| + subE++ +| + ++subE +| + --subE +| + subE-- +| + &subE +| +* E->f@p // bad use +) + ... when any + return ...; +} +else S3 + +// The following three rules are duplicates of ifm, pr1 and pr2 respectively. +// It is need because the previous rule as already made a "change". + +@pr11 depends on context && !org && !report expression@ +expression E; +identifier f; +position p1; +@@ + + (E != NULL && ...) ? <+...E->f@p1...+> : ... + +@pr12 depends on context && !org && !report expression@ +expression E; +identifier f; +position p2; +@@ + +( + (E != NULL) && ... && <+...E->f@p2...+> +| + (E == NULL) || ... || <+...E->f@p2...+> +| + sizeof(<+...E->f@p2...+>) +) + +@ifm1 depends on context && !org && !report@ +expression *E; +statement S1,S2; +position p1; +@@ + +if@p1 ((E == NULL && ...) || ...) S1 else S2 + +@depends on context && !org && !report exists@ +expression subE <= ifm1.E; +expression *ifm1.E; +expression E1,E2; +identifier f; +statement S1,S2,S3,S4; +iterator iter; +position p!={pr11.p1,pr12.p2}; +position ifm1.p1; +@@ + +if@p1 ((E == NULL && ...) || ...) +{ + ... when != if (...) S1 else S2 +( + iter(subE,...) S4 // no use +| + list_remove_head(E2,subE,...) +| + subE = E1 +| + for(subE = E1;...;...) S4 +| + subE++ +| + ++subE +| + --subE +| + subE-- +| + &subE +| +* E->f@p // bad use +) + ... when any +} +else S3 diff --git a/scripts/coccinelle/null/eno.cocci b/scripts/coccinelle/null/eno.cocci new file mode 100644 index 000000000..7107d6c8d --- /dev/null +++ b/scripts/coccinelle/null/eno.cocci @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// The various basic memory allocation functions don't return ERR_PTR +/// +// Confidence: High +// Copyright: (C) 2010-2012 Nicolas Palix. +// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. +// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@depends on patch@ +expression x,E; +@@ + +x = \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\|kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\)(...) +... when != x = E +- IS_ERR(x) ++ !x + +@r depends on !patch exists@ +expression x,E; +position p1,p2; +@@ + +*x = \(kmalloc@p1\|kzalloc@p1\|kcalloc@p1\|kmem_cache_alloc@p1\|kmem_cache_zalloc@p1\|kmem_cache_alloc_node@p1\|kmalloc_node@p1\|kzalloc_node@p1\)(...) +... when != x = E +* IS_ERR@p2(x) + +@script:python depends on org@ +p1 << r.p1; +p2 << r.p2; +@@ + +cocci.print_main("alloc call",p1) +cocci.print_secs("IS_ERR that should be NULL tests",p2) + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +@@ + +msg = "ERROR: allocation function on line %s returns NULL not ERR_PTR on failure" % (p1[0].line) +coccilib.report.print_report(p2[0], msg) diff --git a/scripts/coccinelle/null/kmerr.cocci b/scripts/coccinelle/null/kmerr.cocci new file mode 100644 index 000000000..68db20de6 --- /dev/null +++ b/scripts/coccinelle/null/kmerr.cocci @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// This semantic patch looks for kmalloc etc that are not followed by a +/// NULL check. It only gives a report in the case where there is some +/// error handling code later in the function, which may be helpful +/// in determining what the error handling code for the call to kmalloc etc +/// should be. +/// +// Confidence: High +// Copyright: (C) 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2010 Julia Lawall, DIKU. +// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@withtest@ +expression x; +position p; +identifier f,fld; +@@ + +x@p = f(...); +... when != x->fld +\(x == NULL \| x != NULL\) + +@fixed depends on context && !org && !report@ +expression x,x1; +position p1 != withtest.p; +statement S; +position any withtest.p; +identifier f; +@@ + +*x@p1 = \(kmalloc\|kzalloc\|kcalloc\)(...); +... +*x1@p = f(...); +if (!x1) S + +// ------------------------------------------------------------------------ + +@rfixed depends on (org || report) && !context exists@ +expression x,x1; +position p1 != withtest.p; +position p2; +statement S; +position any withtest.p; +identifier f; +@@ + +x@p1 = \(kmalloc\|kzalloc\|kcalloc\)(...); +... +x1@p = f@p2(...); +if (!x1) S + +@script:python depends on org@ +p1 << rfixed.p1; +p2 << rfixed.p2; +@@ + +cocci.print_main("alloc call",p1) +cocci.print_secs("possible model",p2) + +@script:python depends on report@ +p1 << rfixed.p1; +p2 << rfixed.p2; +@@ + +msg = "alloc with no test, possible model on line %s" % (p2[0].line) +coccilib.report.print_report(p1[0],msg) diff --git a/scripts/coccinelle/tests/doublebitand.cocci b/scripts/coccinelle/tests/doublebitand.cocci new file mode 100644 index 000000000..025436a15 --- /dev/null +++ b/scripts/coccinelle/tests/doublebitand.cocci @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find bit operations that include the same argument more than once +//# One source of false positives is when the argument performs a side +//# effect. Another source of false positives is when a neutral value +//# such as 0 for | is used to indicate no information, to maintain the +//# same structure as other similar expressions +/// +// Confidence: Moderate +// Copyright: (C) 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2010 Julia Lawall, DIKU. +// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@r expression@ +expression E; +position p; +@@ + +( +* E@p + & ... & E +| +* E@p + | ... | E +| +* E@p + & ... & !E +| +* E@p + | ... | !E +| +* !E@p + & ... & E +| +* !E@p + | ... | E +) + +@script:python depends on org@ +p << r.p; +@@ + +cocci.print_main("duplicated argument to & or |",p) + +@script:python depends on report@ +p << r.p; +@@ + +coccilib.report.print_report(p[0],"duplicated argument to & or |") diff --git a/scripts/coccinelle/tests/doubletest.cocci b/scripts/coccinelle/tests/doubletest.cocci new file mode 100644 index 000000000..c3d94c319 --- /dev/null +++ b/scripts/coccinelle/tests/doubletest.cocci @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Find &&/|| operations that include the same argument more than once +//# A common source of false positives is when the expression, or +//# another expresssion in the same && or || operation, performs a +//# side effect. +/// +// Confidence: Moderate +// Copyright: (C) 2010 Nicolas Palix, DIKU. +// Copyright: (C) 2010 Julia Lawall, DIKU. +// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Comments: +// Options: --no-includes --include-headers + +virtual context +virtual org +virtual report + +@r expression@ +expression E; +position p; +@@ + +( + E@p || ... || E +| + E@p && ... && E +) + +@bad@ +expression r.E,e1,e2,fn; +position r.p; +assignment operator op; +@@ + +( +E@p +& + <+... \(fn(...)\|e1 op e2\|e1++\|e1--\|++e1\|--e1\) ...+> +) + +@depends on context && !bad@ +expression r.E; +position r.p; +@@ + +*E@p + +@script:python depends on org && !bad@ +p << r.p; +@@ + +cocci.print_main("duplicated argument to && or ||",p) + +@script:python depends on report && !bad@ +p << r.p; +@@ + +coccilib.report.print_report(p[0],"duplicated argument to && or ||") diff --git a/scripts/coccinelle/tests/odd_ptr_err.cocci b/scripts/coccinelle/tests/odd_ptr_err.cocci new file mode 100644 index 000000000..377436abe --- /dev/null +++ b/scripts/coccinelle/tests/odd_ptr_err.cocci @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// PTR_ERR should access the value just tested by IS_ERR +//# There can be false positives in the patch case, where it is the call to +//# IS_ERR that is wrong. +/// +// Confidence: High +// Copyright: (C) 2012, 2015 Julia Lawall, INRIA. +// Copyright: (C) 2012, 2015 Gilles Muller, INRIA. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --no-includes --include-headers + +virtual patch +virtual context +virtual org +virtual report + +@ok1 exists@ +expression x,e; +position p; +@@ + +if (IS_ERR(x=e) || ...) { + <... + PTR_ERR@p(x) + ...> +} + +@ok2 exists@ +expression x,e1,e2; +position p; +@@ + +if (IS_ERR(x) || ...) { + <... +( + PTR_ERR@p(\(e1 ? e2 : x\|e1 ? x : e2\)) +| + PTR_ERR@p(x) +) + ...> +} + +@r1 depends on patch && !context && !org && !report exists@ +expression x,y; +position p != {ok1.p,ok2.p}; +@@ + +if (IS_ERR(x) || ...) { + ... when any + when != IS_ERR(...) +( + PTR_ERR(x) +| + PTR_ERR@p( +- y ++ x + ) +) + ... when any +} + +// ---------------------------------------------------------------------------- + +@r1_context depends on !patch && (context || org || report) exists@ +position p != {ok1.p,ok2.p}; +expression x, y; +position j0, j1; +@@ + +if (IS_ERR@j0(x) || ...) { + ... when any + when != IS_ERR(...) +( + PTR_ERR(x) +| + PTR_ERR@j1@p( + y + ) +) + ... when any +} + +@r1_disj depends on !patch && (context || org || report) exists@ +position p != {ok1.p,ok2.p}; +expression x, y; +position r1_context.j0, r1_context.j1; +@@ + +* if (IS_ERR@j0(x) || ...) { + ... when any + when != IS_ERR(...) +* PTR_ERR@j1@p( + y + ) + ... when any +} + +// ---------------------------------------------------------------------------- + +@script:python r1_org depends on org@ +j0 << r1_context.j0; +j1 << r1_context.j1; +@@ + +msg = "inconsistent IS_ERR and PTR_ERR" +coccilib.org.print_todo(j0[0], msg) +coccilib.org.print_link(j1[0], "") + +// ---------------------------------------------------------------------------- + +@script:python r1_report depends on report@ +j0 << r1_context.j0; +j1 << r1_context.j1; +@@ + +msg = "inconsistent IS_ERR and PTR_ERR on line %s." % (j1[0].line) +coccilib.report.print_report(j0[0], msg) + diff --git a/scripts/coccinelle/tests/unsigned_lesser_than_zero.cocci b/scripts/coccinelle/tests/unsigned_lesser_than_zero.cocci new file mode 100644 index 000000000..5e188c62d --- /dev/null +++ b/scripts/coccinelle/tests/unsigned_lesser_than_zero.cocci @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only +/// Unsigned expressions cannot be lesser than zero. Presence of +/// comparisons 'unsigned (<|<=|>|>=) 0' often indicates a bug, +/// usually wrong type of variable. +/// +/// To reduce number of false positives following tests have been added: +/// - parts of range checks are skipped, eg. "if (u < 0 || u > 15) ...", +/// developers prefer to keep such code, +/// - comparisons "<= 0" and "> 0" are performed only on results of +/// signed functions/macros, +/// - hardcoded list of signed functions/macros with always non-negative +/// result is used to avoid false positives difficult to detect by other ways +/// +// Confidence: Average +// Copyright: (C) 2015 Andrzej Hajda, Samsung Electronics Co., Ltd. +// URL: https://coccinelle.gitlabpages.inria.fr/website +// Options: --all-includes + +virtual context +virtual org +virtual report + +@r_cmp@ +position p; +typedef bool, u8, u16, u32, u64; +{unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long, + size_t, bool, u8, u16, u32, u64} v; +expression e; +@@ + + \( v = e \| &v \) + ... + (\( v@p < 0 \| v@p <= 0 \| v@p >= 0 \| v@p > 0 \)) + +@r@ +position r_cmp.p; +typedef s8, s16, s32, s64; +{char, short, int, long, long long, ssize_t, s8, s16, s32, s64} vs; +expression c, e, v; +identifier f !~ "^(ata_id_queue_depth|btrfs_copy_from_user|dma_map_sg|dma_map_sg_attrs|fls|fls64|gameport_time|get_write_extents|nla_len|ntoh24|of_flat_dt_match|of_get_child_count|uart_circ_chars_pending|[A-Z0-9_]+)$"; +@@ + +( + v = f(...)@vs; + ... when != v = e; +* (\( v@p <=@e 0 \| v@p >@e 0 \)) + ... when any +| +( + (\( v@p < 0 \| v@p <= 0 \)) || ... || (\( v >= c \| v > c \)) +| + (\( v >= c \| v > c \)) || ... || (\( v@p < 0 \| v@p <= 0 \)) +| + (\( v@p >= 0 \| v@p > 0 \)) && ... && (\( v < c \| v <= c \)) +| + ((\( v < c \| v <= c \) && ... && \( v@p >= 0 \| v@p > 0 \))) +| +* (\( v@p <@e 0 \| v@p >=@e 0 \)) +) +) + +@script:python depends on org@ +p << r_cmp.p; +e << r.e; +@@ + +msg = "WARNING: Unsigned expression compared with zero: %s" % (e) +coccilib.org.print_todo(p[0], msg) + +@script:python depends on report@ +p << r_cmp.p; +e << r.e; +@@ + +msg = "WARNING: Unsigned expression compared with zero: %s" % (e) +coccilib.report.print_report(p[0], msg) diff --git a/scripts/config b/scripts/config new file mode 100755 index 000000000..32428ea90 --- /dev/null +++ b/scripts/config @@ -0,0 +1,240 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# Manipulate options in a .config file from the command line + +myname=${0##*/} + +# If no prefix forced, use the default CONFIG_ +CONFIG_="${CONFIG_-CONFIG_}" + +# We use an uncommon delimiter for sed substitutions +SED_DELIM=$(echo -en "\001") + +usage() { + cat >&2 <<EOL +Manipulate options in a .config file from the command line. +Usage: +$myname options command ... +commands: + --enable|-e option Enable option + --disable|-d option Disable option + --module|-m option Turn option into a module + --set-str option string + Set option to "string" + --set-val option value + Set option to value + --undefine|-u option Undefine option + --state|-s option Print state of option (n,y,m,undef) + + --enable-after|-E beforeopt option + Enable option directly after other option + --disable-after|-D beforeopt option + 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 + +options: + --file config-file .config file to change (default .config) + --keep-case|-k Keep next symbols' case (don't upper-case it) + +$myname doesn't check the validity of the .config file. This is done at next +make time. + +By default, $myname will upper-case the given symbol. Use --keep-case to keep +the case of all following symbols unchanged. + +$myname uses 'CONFIG_' as the default symbol prefix. Set the environment +variable CONFIG_ to the prefix to use. Eg.: CONFIG_="FOO_" $myname ... +EOL + exit 1 +} + +checkarg() { + ARG="$1" + if [ "$ARG" = "" ] ; then + usage + fi + case "$ARG" in + ${CONFIG_}*) + ARG="${ARG/${CONFIG_}/}" + ;; + esac + if [ "$MUNGE_CASE" = "yes" ] ; then + ARG="`echo $ARG | tr a-z A-Z`" + fi +} + +txt_append() { + local anchor="$1" + local insert="$2" + local infile="$3" + + # sed append cmd: 'a\' + newline + text + newline + cmd="$(printf "a\\%b$insert" "\n")" + + # We don't really need a backup file, but in-place editing with backup + # skipped is not portable due to different implementations parsing + # arguments in incompatible manners. + # Create a backup file anyway to ensure portability. The file will be + # deleted on exit. + sed -E -i.swp -e "/$anchor/$cmd" "$infile" + SED_EDITED=1 +} + +txt_subst() { + local before="$1" + local after="$2" + local infile="$3" + + sed -E -i.swp -e "s$SED_DELIM$before$SED_DELIM$after$SED_DELIM" "$infile" + SED_EDITED=1 +} + +txt_delete() { + local text="$1" + local infile="$2" + + sed -E -i.swp -e "/$text/d" "$infile" + SED_EDITED=1 +} + +set_var() { + local name=$1 new=$2 before=$3 + + name_re="^($name=.*|# $name is not set)" + before_re="^($before=|# $before is not set)" + if test -n "$before" && grep -Eq "$before_re" "$FN"; then + txt_append "$before_re" "$new" "$FN" + elif grep -Eq "$name_re" "$FN"; then + txt_subst "$name_re" "$new" "$FN" + else + echo "$new" >>"$FN" + fi +} + +undef_var() { + local name=$1 + + txt_delete "^($name=|# $name is not set)" "$FN" +} + +SED_EDITED=0 +on_exit() { + if [ "$SED_EDITED" -ge 1 ]; then + rm -f "$FN.swp" + fi +} +trap on_exit EXIT + +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 +done + +set -- "${CMDS[@]}" +if [ "$1" = "" ] ; then + usage +fi + +MUNGE_CASE=yes +while [ "$1" != "" ] ; do + CMD="$1" + shift + case "$CMD" in + --keep-case|-k) + MUNGE_CASE=no + continue + ;; + --refresh) + ;; + --*-after|-E|-D|-M) + checkarg "$1" + A=$ARG + checkarg "$2" + B=$ARG + shift 2 + ;; + -*) + checkarg "$1" + shift + ;; + esac + case "$CMD" in + --enable|-e) + set_var "${CONFIG_}$ARG" "${CONFIG_}$ARG=y" + ;; + + --disable|-d) + set_var "${CONFIG_}$ARG" "# ${CONFIG_}$ARG is not set" + ;; + + --module|-m) + set_var "${CONFIG_}$ARG" "${CONFIG_}$ARG=m" + ;; + + --set-str) + # sed swallows one level of escaping, so we need double-escaping + set_var "${CONFIG_}$ARG" "${CONFIG_}$ARG=\"${1//\"/\\\\\"}\"" + shift + ;; + + --set-val) + set_var "${CONFIG_}$ARG" "${CONFIG_}$ARG=$1" + shift + ;; + --undefine|-u) + undef_var "${CONFIG_}$ARG" + ;; + + --state|-s) + if grep -q "# ${CONFIG_}$ARG is not set" $FN ; then + echo n + else + V="$(grep "^${CONFIG_}$ARG=" $FN)" + if [ $? != 0 ] ; then + echo undef + else + V="${V/#${CONFIG_}$ARG=/}" + V="${V/#\"/}" + V="${V/%\"/}" + V="${V//\\\"/\"}" + echo "${V}" + fi + fi + ;; + + --enable-after|-E) + set_var "${CONFIG_}$B" "${CONFIG_}$B=y" "${CONFIG_}$A" + ;; + + --disable-after|-D) + set_var "${CONFIG_}$B" "# ${CONFIG_}$B is not set" "${CONFIG_}$A" + ;; + + --module-after|-M) + set_var "${CONFIG_}$B" "${CONFIG_}$B=m" "${CONFIG_}$A" + ;; + + --refresh) + yes "" | make oldconfig KCONFIG_CONFIG=$FN + ;; + + *) + echo "bad command: $CMD" >&2 + usage + ;; + esac +done diff --git a/scripts/const_structs.checkpatch b/scripts/const_structs.checkpatch new file mode 100644 index 000000000..6eb94fddc --- /dev/null +++ b/scripts/const_structs.checkpatch @@ -0,0 +1,113 @@ +acpi_dock_ops +address_space_operations +backlight_ops +bin_attribute +block_device_operations +bus_type +clk_ops +comedi_lrange +component_ops +ctl_table +dentry_operations +dev_pm_ops +device_type +dma_map_ops +driver_info +drm_connector_funcs +drm_encoder_funcs +drm_encoder_helper_funcs +dvb_frontend_ops +dvb_tuner_ops +ethtool_ops +extent_io_ops +fb_ops +file_lock_operations +file_operations +hv_ops +hwmon_ops +ib_device_ops +ide_dma_ops +ide_port_ops +ieee80211_ops +iio_buffer_setup_ops +inode_operations +intel_dvo_dev_ops +irq_domain_ops +item_operations +iwl_cfg +iwl_ops +kernel_param_ops +kgdb_arch +kgdb_io +kobj_type +kset_uevent_ops +lcd_ops +lock_manager_operations +machine_desc +microcode_ops +mlxsw_reg_info +mtd_ooblayout_ops +mtrr_ops +nand_controller_ops +neigh_ops +net_device_ops +nft_expr_ops +nlmsvc_binding +nvkm_device_chip +of_device_id +pci_raw_ops +phy_ops +pinconf_ops +pinctrl_ops +pinmux_ops +pipe_buf_operations +platform_hibernation_ops +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 +rtc_class_ops +sd_desc +sdhci_ops +seq_operations +sirfsoc_padmux +snd_ac97_build_ops +snd_pcm_ops +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 +tty_operations +uart_ops +usb_mon_operations +v4l2_ctrl_ops +v4l2_ioctl_ops +v4l2_subdev_core_ops +v4l2_subdev_internal_ops +v4l2_subdev_ops +v4l2_subdev_pad_ops +v4l2_subdev_video_ops +vb2_ops +vm_operations_struct +wacom_features +watchdog_ops +wd_ops +xattr_handler diff --git a/scripts/container b/scripts/container new file mode 100755 index 000000000..b05333d85 --- /dev/null +++ b/scripts/container @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2025 Guillaume Tucker + +"""Containerized builds""" + +import abc +import argparse +import logging +import os +import pathlib +import shutil +import subprocess +import sys +import uuid + + +class ContainerRuntime(abc.ABC): + """Base class for a container runtime implementation""" + + name = None # Property defined in each implementation class + + def __init__(self, args, logger): + self._uid = args.uid or os.getuid() + self._gid = args.gid or args.uid or os.getgid() + self._env_file = args.env_file + self._shell = args.shell + self._logger = logger + + @classmethod + def is_present(cls): + """Determine whether the runtime is present on the system""" + return shutil.which(cls.name) is not None + + @abc.abstractmethod + def _do_run(self, image, cmd, container_name): + """Runtime-specific handler to run a command in a container""" + + @abc.abstractmethod + def _do_abort(self, container_name): + """Runtime-specific handler to abort a running container""" + + def run(self, image, cmd): + """Run a command in a runtime container""" + container_name = str(uuid.uuid4()) + self._logger.debug("container: %s", container_name) + try: + return self._do_run(image, cmd, container_name) + except KeyboardInterrupt: + self._logger.error("user aborted") + self._do_abort(container_name) + return 1 + + +class CommonRuntime(ContainerRuntime): + """Common logic for Docker and Podman""" + + def _do_run(self, image, cmd, container_name): + cmdline = [self.name, 'run'] + cmdline += self._get_opts(container_name) + cmdline.append(image) + cmdline += cmd + self._logger.debug('command: %s', ' '.join(cmdline)) + return subprocess.call(cmdline) + + def _get_opts(self, container_name): + opts = [ + '--name', container_name, + '--rm', + '--volume', f'{pathlib.Path.cwd()}:/src', + '--workdir', '/src', + ] + if self._env_file: + opts += ['--env-file', self._env_file] + if self._shell: + opts += ['--interactive', '--tty'] + return opts + + def _do_abort(self, container_name): + subprocess.call([self.name, 'kill', container_name]) + + +class DockerRuntime(CommonRuntime): + """Run a command in a Docker container""" + + name = 'docker' + + def _get_opts(self, container_name): + return super()._get_opts(container_name) + [ + '--user', f'{self._uid}:{self._gid}' + ] + + +class PodmanRuntime(CommonRuntime): + """Run a command in a Podman container""" + + name = 'podman' + + def _get_opts(self, container_name): + return super()._get_opts(container_name) + [ + '--userns', f'keep-id:uid={self._uid},gid={self._gid}', + ] + + +class Runtimes: + """List of all supported runtimes""" + + runtimes = [PodmanRuntime, DockerRuntime] + + @classmethod + def get_names(cls): + """Get a list of all the runtime names""" + return list(runtime.name for runtime in cls.runtimes) + + @classmethod + def get(cls, name): + """Get a single runtime class matching the given name""" + for runtime in cls.runtimes: + if runtime.name == name: + if not runtime.is_present(): + raise ValueError(f"runtime not found: {name}") + return runtime + raise ValueError(f"unknown runtime: {name}") + + @classmethod + def find(cls): + """Find the first runtime present on the system""" + for runtime in cls.runtimes: + if runtime.is_present(): + return runtime + raise ValueError("no runtime found") + + +def _get_logger(verbose): + """Set up a logger with the appropriate level""" + logger = logging.getLogger('container') + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter( + fmt='[container {levelname}] {message}', style='{' + )) + logger.addHandler(handler) + logger.setLevel(logging.DEBUG if verbose is True else logging.INFO) + return logger + + +def main(args): + """Main entry point for the container tool""" + logger = _get_logger(args.verbose) + try: + cls = Runtimes.get(args.runtime) if args.runtime else Runtimes.find() + except ValueError as ex: + logger.error(ex) + return 1 + logger.debug("runtime: %s", cls.name) + logger.debug("image: %s", args.image) + return cls(args, logger).run(args.image, args.cmd) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + 'container', + description="See the documentation for more details: " + "https://docs.kernel.org/dev-tools/container.html" + ) + parser.add_argument( + '-e', '--env-file', + help="Path to an environment file to load in the container." + ) + parser.add_argument( + '-g', '--gid', + help="Group ID to use inside the container." + ) + parser.add_argument( + '-i', '--image', required=True, + help="Container image name." + ) + parser.add_argument( + '-r', '--runtime', choices=Runtimes.get_names(), + help="Container runtime name. If not specified, the first one found " + "on the system will be used i.e. Podman if present, otherwise Docker." + ) + parser.add_argument( + '-s', '--shell', action='store_true', + help="Run the container in an interactive shell." + ) + parser.add_argument( + '-u', '--uid', + help="User ID to use inside the container. If the -g option is not " + "specified, the user ID will also be set as the group ID." + ) + parser.add_argument( + '-v', '--verbose', action='store_true', + help="Enable verbose output." + ) + parser.add_argument( + 'cmd', nargs='+', + help="Command to run in the container" + ) + sys.exit(main(parser.parse_args(sys.argv[1:]))) diff --git a/scripts/context-analysis-suppression.txt b/scripts/context-analysis-suppression.txt new file mode 100644 index 000000000..1c51b6153 --- /dev/null +++ b/scripts/context-analysis-suppression.txt @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# The suppressions file should only match common paths such as header files. +# For individual subsytems use Makefile directive CONTEXT_ANALYSIS := [yn]. +# +# The suppressions are ignored when CONFIG_WARN_CONTEXT_ANALYSIS_ALL is +# selected. + +[thread-safety] +src:*arch/*/include/* +src:*include/acpi/* +src:*include/asm-generic/* +src:*include/linux/* +src:*include/net/* + +# Opt-in headers: +src:*include/linux/bit_spinlock.h=emit +src:*include/linux/cleanup.h=emit +src:*include/linux/kref.h=emit +src:*include/linux/list*.h=emit +src:*include/linux/local_lock*.h=emit +src:*include/linux/lockdep.h=emit +src:*include/linux/mutex*.h=emit +src:*include/linux/rcupdate.h=emit +src:*include/linux/refcount.h=emit +src:*include/linux/rhashtable.h=emit +src:*include/linux/rtmutex*.h=emit +src:*include/linux/rwlock*.h=emit +src:*include/linux/rwsem.h=emit +src:*include/linux/sched*=emit +src:*include/linux/seqlock*.h=emit +src:*include/linux/spinlock*.h=emit +src:*include/linux/srcu*.h=emit +src:*include/linux/ww_mutex.h=emit diff --git a/scripts/crypto/gen-aead-testvecs.py b/scripts/crypto/gen-aead-testvecs.py new file mode 100755 index 000000000..d77d646f7 --- /dev/null +++ b/scripts/crypto/gen-aead-testvecs.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script that generates known-good data used in the AEAD tests. +# +# Requires that python-cryptography be installed. +# +# Copyright 2026 Google LLC + +import hashlib +import sys +import cryptography.hazmat.primitives.ciphers.aead + + +# Deterministically generate 'length' random bytes. +def rand_bytes(length): + seed = length + out = [] + for _ in range(length): + seed = (seed * 25214903917 + 11) % 2**48 + out.append((seed >> 16) % 256) + return bytes(out) + + +# Deterministically generate many different AEAD inputs using exactly the same +# method that the test uses; encrypt them using an independent implementation of +# the algorithm; compute the checksum of all the resulting (ciphertext, authtag) +# pairs concatenated to each other; and print the checksum as a C struct. +def gen_monte_carlo_checksum(alg): + blake2s = hashlib.blake2s() + for data_len in range(1025): + ad_len = data_len % 293 + pt = rand_bytes(data_len) + ad = rand_bytes(ad_len) + if alg == "aes-ccm": + key_len = [16, 24, 32][data_len % 3] + key = rand_bytes(key_len) + nonce = rand_bytes([7, 8, 9, 10, 11, 12, 13][data_len % 7]) + tag_len = [4, 6, 8, 10, 12, 14, 16][data_len % 7] + ccm = cryptography.hazmat.primitives.ciphers.aead.AESCCM( + key, tag_length=tag_len + ) + ct_and_tag = ccm.encrypt(nonce, pt, ad) + elif alg == "aes-gcm": + key_len = [16, 24, 32][data_len % 3] + key = rand_bytes(key_len) + nonce = rand_bytes(12) + tag_len = [4, 8, 12, 13, 14, 15, 16][data_len % 7] + gcm = cryptography.hazmat.primitives.ciphers.aead.AESGCM(key) + # python-cryptography supports only 16-byte GCM tags. However, in + # GCM, shorter tags are simply truncated. Do that below. + ct_and_tag = gcm.encrypt(nonce, pt, ad)[: data_len + tag_len] + + blake2s.update(ct_and_tag) + + name = f"{alg.replace('-', '_')}_monte_carlo_checksum" + value = blake2s.digest() + print(f"static const u8 {name}[BLAKE2S_HASH_SIZE] = {{") + for i in range(0, len(value), 11): + line = "\t" + "".join(f"0x{b:02x}, " for b in value[i : i + 11]) + print(f"{line.rstrip()}") + print("};") + + +if len(sys.argv) != 2 or sys.argv[1] not in ("aes-ccm", "aes-gcm"): + sys.stderr.write("Usage: gen-aead-testvecs.py [aes-ccm|aes-gcm]\n") + sys.exit(1) + +gen_monte_carlo_checksum(sys.argv[1]) diff --git a/scripts/crypto/gen-fips-testvecs.py b/scripts/crypto/gen-fips-testvecs.py new file mode 100755 index 000000000..b8c8a78cb --- /dev/null +++ b/scripts/crypto/gen-fips-testvecs.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script that generates lib/crypto/fips-aes.h and lib/crypto/fips-sha.h +# +# Requires that python-cryptography be installed. +# +# Copyright 2025 Google LLC + +import cryptography.hazmat.primitives.ciphers +import cryptography.hazmat.primitives.ciphers.aead +import cryptography.hazmat.primitives.cmac +import hashlib +import hmac + + +def print_static_u8_array_definition(file, name, value): + print("", file=file) + print(f"static const u8 {name}[] __initconst __maybe_unused = {{", file=file) + for i in range(0, len(value), 8): + line = "\t" + "".join(f"0x{b:02x}, " for b in value[i : i + 8]) + print(f"{line.rstrip()}", file=file) + print("};", file=file) + + +def print_header(file): + print("/* SPDX-License-Identifier: GPL-2.0-or-later */", file=file) + print("/* This file was generated by: gen-fips-testvecs.py */", file=file) + print("/* clang-format off */", file=file) + print("", file=file) + print("#include <linux/fips.h>", file=file) + + +def gen_aes_test_data(file): + fips_test_data = b"fips test data\0\0" + fips_test_ad = b"fips test ad\0\0\0\0" + fips_test_iv = b"fips test iv\0\0\0\0" + fips_test_key = b"fips test key\0\0\0" + fips_test_xts_key = b"key1" + (b"\0" * 12) + b"key2" + (b"\0" * 12) + + print_header(file) + print_static_u8_array_definition(file, "fips_test_data", fips_test_data) + print_static_u8_array_definition(file, "fips_test_ad", fips_test_ad) + print_static_u8_array_definition(file, "fips_test_iv", fips_test_iv) + print_static_u8_array_definition(file, "fips_test_key", fips_test_key) + print_static_u8_array_definition(file, "fips_test_xts_key", fips_test_xts_key) + + aes = cryptography.hazmat.primitives.ciphers.algorithms.AES(fips_test_key) + + # AES-CMAC + aes_cmac = cryptography.hazmat.primitives.cmac.CMAC(aes) + aes_cmac.update(fips_test_data) + print_static_u8_array_definition( + file, "fips_test_aes_cmac_value", aes_cmac.finalize() + ) + + # AES-ECB + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.ECB() + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_ecb_ctext", ctext) + + # AES-CBC + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.CBC(fips_test_iv) + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_cbc_ctext", ctext) + + # AES-CBC-CTS + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.CBC(fips_test_iv) + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data * 2) + encryptor.finalize() + ctext = ctext[16:32] + ctext[0:16] + print_static_u8_array_definition(file, "fips_test_aes_cbc_cts_ctext", ctext) + + # AES-CTR + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + aes, cryptography.hazmat.primitives.ciphers.modes.CTR(fips_test_iv) + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_ctr_ctext", ctext) + + # AES-XTS + cipher = cryptography.hazmat.primitives.ciphers.Cipher( + cryptography.hazmat.primitives.ciphers.algorithms.AES(fips_test_xts_key), + cryptography.hazmat.primitives.ciphers.modes.XTS(fips_test_iv), + ) + encryptor = cipher.encryptor() + ctext = encryptor.update(fips_test_data) + encryptor.finalize() + print_static_u8_array_definition(file, "fips_test_aes_xts_ctext", ctext) + + # AES-GCM + cipher = cryptography.hazmat.primitives.ciphers.aead.AESGCM(fips_test_key) + ct_and_tag = cipher.encrypt( + nonce=fips_test_iv[:12], data=fips_test_data, associated_data=fips_test_ad + ) + print_static_u8_array_definition( + file, "fips_test_aes_gcm_ctext_and_tag", ct_and_tag + ) + + # AES-CCM + cipher = cryptography.hazmat.primitives.ciphers.aead.AESCCM( + fips_test_key, tag_length=16 + ) + ct_and_tag = cipher.encrypt( + nonce=fips_test_iv[:13], data=fips_test_data, associated_data=fips_test_ad + ) + print_static_u8_array_definition( + file, "fips_test_aes_ccm_ctext_and_tag", ct_and_tag + ) + + +def gen_sha_test_data(file): + fips_test_data = b"fips test data\0\0" + fips_test_key = b"fips test key\0\0\0" + + print_header(file) + print_static_u8_array_definition(file, "fips_test_data", fips_test_data) + print_static_u8_array_definition(file, "fips_test_key", fips_test_key) + + for alg in "sha1", "sha256", "sha512": + ctx = hmac.new(fips_test_key, digestmod=alg) + ctx.update(fips_test_data) + print_static_u8_array_definition( + file, f"fips_test_hmac_{alg}_value", ctx.digest() + ) + + print_static_u8_array_definition( + file, "fips_test_sha3_256_value", hashlib.sha3_256(fips_test_data).digest() + ) + + +filename = "lib/crypto/fips-aes.h" +with open(filename, "w") as file: + print(f"Generating {filename}") + gen_aes_test_data(file) + +filename = "lib/crypto/fips-sha.h" +with open(filename, "w") as file: + print(f"Generating {filename}") + gen_sha_test_data(file) diff --git a/scripts/crypto/gen-hash-testvecs.py b/scripts/crypto/gen-hash-testvecs.py new file mode 100755 index 000000000..f356f87e1 --- /dev/null +++ b/scripts/crypto/gen-hash-testvecs.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script that generates test vectors for the given hash function. +# +# Requires that python-cryptography be installed. +# +# Copyright 2025 Google LLC + +import cryptography.hazmat.primitives.ciphers +import cryptography.hazmat.primitives.cmac +import hashlib +import hmac +import sys + +DATA_LENS = [0, 1, 2, 3, 16, 32, 48, 49, 63, 64, 65, 127, 128, 129, 256, 511, + 513, 1000, 3333, 4096, 4128, 4160, 4224, 16384] + +# Generate the given number of random bytes, using the length itself as the seed +# for a simple linear congruential generator (LCG). The C test code uses the +# same LCG with the same seeding strategy to reconstruct the data, ensuring +# reproducibility without explicitly storing the data in the test vectors. +def rand_bytes(length): + seed = length + out = [] + for _ in range(length): + seed = (seed * 25214903917 + 11) % 2**48 + out.append((seed >> 16) % 256) + return bytes(out) + +AES_256_KEY_SIZE = 32 + +# AES-CMAC. Just wraps the implementation from python-cryptography. +class AesCmac: + def __init__(self, key): + aes = cryptography.hazmat.primitives.ciphers.algorithms.AES(key) + self.cmac = cryptography.hazmat.primitives.cmac.CMAC(aes) + + def update(self, data): + self.cmac.update(data) + + def digest(self): + return self.cmac.finalize() + +POLY1305_KEY_SIZE = 32 + +# A straightforward, unoptimized implementation of Poly1305. +# Reference: https://cr.yp.to/mac/poly1305-20050329.pdf +class Poly1305: + def __init__(self, key): + assert len(key) == POLY1305_KEY_SIZE + self.h = 0 + rclamp = 0x0ffffffc0ffffffc0ffffffc0fffffff + self.r = int.from_bytes(key[:16], byteorder='little') & rclamp + self.s = int.from_bytes(key[16:], byteorder='little') + + # Note: this supports partial blocks only at the end. + def update(self, data): + for i in range(0, len(data), 16): + chunk = data[i:i+16] + c = int.from_bytes(chunk, byteorder='little') + 2**(8 * len(chunk)) + self.h = ((self.h + c) * self.r) % (2**130 - 5) + return self + + # Note: gen_additional_poly1305_testvecs() relies on this being + # nondestructive, i.e. not changing any field of self. + def digest(self): + m = (self.h + self.s) % 2**128 + return m.to_bytes(16, byteorder='little') + +GHASH_POLY = sum((1 << i) for i in [128, 7, 2, 1, 0]) +GHASH_BLOCK_SIZE = 16 + +# A straightforward, unoptimized implementation of GHASH. +class Ghash: + + @staticmethod + def reflect_bits_in_bytes(v): + res = 0 + for offs in range(0, 128, 8): + for bit in range(8): + if (v & (1 << (offs + bit))) != 0: + res ^= 1 << (offs + 7 - bit) + return res + + @staticmethod + def bytes_to_poly(data): + return Ghash.reflect_bits_in_bytes(int.from_bytes(data, byteorder='little')) + + @staticmethod + def poly_to_bytes(poly): + return Ghash.reflect_bits_in_bytes(poly).to_bytes(16, byteorder='little') + + def __init__(self, key): + assert len(key) == 16 + self.h = Ghash.bytes_to_poly(key) + self.acc = 0 + + # Note: this supports partial blocks only at the end. + def update(self, data): + for i in range(0, len(data), 16): + # acc += block + self.acc ^= Ghash.bytes_to_poly(data[i:i+16]) + # acc = (acc * h) mod GHASH_POLY + product = 0 + for j in range(127, -1, -1): + if (self.h & (1 << j)) != 0: + product ^= self.acc << j + if (product & (1 << (128 + j))) != 0: + product ^= GHASH_POLY << j + self.acc = product + return self + + def digest(self): + return Ghash.poly_to_bytes(self.acc) + +POLYVAL_POLY = sum((1 << i) for i in [128, 127, 126, 121, 0]) +POLYVAL_BLOCK_SIZE = 16 + +# A straightforward, unoptimized implementation of POLYVAL. +# Reference: https://datatracker.ietf.org/doc/html/rfc8452 +class Polyval: + def __init__(self, key): + assert len(key) == 16 + self.h = int.from_bytes(key, byteorder='little') + self.acc = 0 + + # Note: this supports partial blocks only at the end. + def update(self, data): + for i in range(0, len(data), 16): + # acc += block + self.acc ^= int.from_bytes(data[i:i+16], byteorder='little') + # acc = (acc * h * x^-128) mod POLYVAL_POLY + product = 0 + for j in range(128): + if (self.h & (1 << j)) != 0: + product ^= self.acc << j + if (product & (1 << j)) != 0: + product ^= POLYVAL_POLY << j + self.acc = product >> 128 + return self + + def digest(self): + return self.acc.to_bytes(16, byteorder='little') + +def hash_init(alg): + # The keyed hash functions are assigned a fixed random key here, to present + # them as unkeyed hash functions. This allows all the test cases for + # unkeyed hash functions to work on them. + if alg == 'aes-cmac': + return AesCmac(rand_bytes(AES_256_KEY_SIZE)) + if alg == 'ghash': + return Ghash(rand_bytes(GHASH_BLOCK_SIZE)) + if alg == 'poly1305': + return Poly1305(rand_bytes(POLY1305_KEY_SIZE)) + if alg == 'polyval': + return Polyval(rand_bytes(POLYVAL_BLOCK_SIZE)) + return hashlib.new(alg) + +def hash_update(ctx, data): + ctx.update(data) + +def hash_final(ctx): + return ctx.digest() + +def compute_hash(alg, data): + ctx = hash_init(alg) + hash_update(ctx, data) + return hash_final(ctx) + +def print_bytes(prefix, value, bytes_per_line): + for i in range(0, len(value), bytes_per_line): + line = prefix + ''.join(f'0x{b:02x}, ' for b in value[i:i+bytes_per_line]) + print(f'{line.rstrip()}') + +def print_static_u8_array_definition(name, value): + print('') + print(f'static const u8 {name} = {{') + print_bytes('\t', value, 8) + print('};') + +def print_c_struct_u8_array_field(name, value): + print(f'\t\t.{name} = {{') + print_bytes('\t\t\t', value, 8) + print('\t\t},') + +def alg_digest_size_const(alg): + if alg == 'aes-cmac': + return 'AES_BLOCK_SIZE' + if alg.startswith('blake2'): + return f'{alg.upper()}_HASH_SIZE' + return f"{alg.upper().replace('-', '_')}_DIGEST_SIZE" + +def gen_unkeyed_testvecs(alg): + print('') + print('static const struct {') + print('\tsize_t data_len;') + print(f'\tu8 digest[{alg_digest_size_const(alg)}];') + print('} hash_testvecs[] = {') + for data_len in DATA_LENS: + data = rand_bytes(data_len) + print('\t{') + print(f'\t\t.data_len = {data_len},') + print_c_struct_u8_array_field('digest', compute_hash(alg, data)) + print('\t},') + print('};') + + data = rand_bytes(4096) + ctx = hash_init(alg) + for data_len in range(len(data) + 1): + hash_update(ctx, compute_hash(alg, data[:data_len])) + print_static_u8_array_definition( + f'hash_testvec_consolidated[{alg_digest_size_const(alg)}]', + hash_final(ctx)) + +def gen_additional_sha3_testvecs(): + max_len = 4096 + in_data = rand_bytes(max_len) + for alg in ['shake128', 'shake256']: + ctx = hashlib.new('sha3-256') + for in_len in range(max_len + 1): + out_len = (in_len * 293) % (max_len + 1) + out = hashlib.new(alg, data=in_data[:in_len]).digest(out_len) + ctx.update(out) + print_static_u8_array_definition(f'{alg}_testvec_consolidated[SHA3_256_DIGEST_SIZE]', + ctx.digest()) + +def gen_hmac_testvecs(alg): + ctx = hmac.new(rand_bytes(32), digestmod=alg) + data = rand_bytes(4096) + for data_len in range(len(data) + 1): + ctx.update(data[:data_len]) + key_len = data_len % 293 + key = rand_bytes(key_len) + mac = hmac.digest(key, data[:data_len], alg) + ctx.update(mac) + print_static_u8_array_definition( + f'hmac_testvec_consolidated[{alg.upper()}_DIGEST_SIZE]', + ctx.digest()) + +def gen_additional_blake2_testvecs(alg): + if alg == 'blake2s': + (max_key_size, max_hash_size) = (32, 32) + elif alg == 'blake2b': + (max_key_size, max_hash_size) = (64, 64) + else: + raise ValueError(f'Unsupported alg: {alg}') + hashes = b'' + for key_len in range(max_key_size + 1): + for out_len in range(1, max_hash_size + 1): + h = hashlib.new(alg, digest_size=out_len, key=rand_bytes(key_len)) + h.update(rand_bytes(100)) + hashes += h.digest() + print_static_u8_array_definition( + f'{alg}_keyed_testvec_consolidated[{alg_digest_size_const(alg)}]', + compute_hash(alg, hashes)) + +def nh_extract_int(bytestr, pos, length): + assert pos % 8 == 0 and length % 8 == 0 + return int.from_bytes(bytestr[pos//8 : pos//8 + length//8], byteorder='little') + +# The NH "almost-universal hash function" used in Adiantum. This is a +# straightforward translation of the pseudocode from Section 6.3 of the Adiantum +# paper (https://eprint.iacr.org/2018/720.pdf), except the outer loop is omitted +# because we assume len(msg) <= 1024. (The kernel's nh() function is only +# expected to handle up to 1024 bytes; it's just called repeatedly as needed.) +def nh(key, msg): + (w, s, r, u) = (32, 2, 4, 8192) + l = 8 * len(msg) + assert l <= u + assert l % (2*s*w) == 0 + h = bytes() + for i in range(0, 2*s*w*r, 2*s*w): + p = 0 + for j in range(0, l, 2*s*w): + for k in range(0, w*s, w): + a0 = nh_extract_int(key, i + j + k, w) + a1 = nh_extract_int(key, i + j + k + s*w, w) + b0 = nh_extract_int(msg, j + k, w) + b1 = nh_extract_int(msg, j + k + s*w, w) + p += ((a0 + b0) % 2**w) * ((a1 + b1) % 2**w) + h += (p % 2**64).to_bytes(8, byteorder='little') + return h + +def gen_nh_testvecs(): + NH_KEY_BYTES = 1072 + NH_MESSAGE_BYTES = 1024 + key = rand_bytes(NH_KEY_BYTES) + msg = rand_bytes(NH_MESSAGE_BYTES) + print_static_u8_array_definition('nh_test_key[NH_KEY_BYTES]', key) + print_static_u8_array_definition('nh_test_msg[NH_MESSAGE_BYTES]', msg) + for length in [16, 96, 256, 1024]: + print_static_u8_array_definition(f'nh_test_val{length}[NH_HASH_BYTES]', + nh(key, msg[:length])) + +def gen_additional_poly1305_testvecs(): + key = b'\xff' * POLY1305_KEY_SIZE + data = b'' + ctx = Poly1305(key) + for _ in range(32): + for j in range(0, 4097, 16): + ctx.update(b'\xff' * j) + data += ctx.digest() + print_static_u8_array_definition( + 'poly1305_allones_macofmacs[POLY1305_DIGEST_SIZE]', + Poly1305(key).update(data).digest()) + +def gen_additional_ghash_testvecs(): + key = b'\xff' * GHASH_BLOCK_SIZE + hashes = b'' + for data_len in range(0, 4097, 16): + hashes += Ghash(key).update(b'\xff' * data_len).digest() + print_static_u8_array_definition( + 'ghash_allones_hashofhashes[GHASH_DIGEST_SIZE]', + Ghash(key).update(hashes).digest()) + +def gen_additional_polyval_testvecs(): + key = b'\xff' * POLYVAL_BLOCK_SIZE + hashes = b'' + for data_len in range(0, 4097, 16): + hashes += Polyval(key).update(b'\xff' * data_len).digest() + print_static_u8_array_definition( + 'polyval_allones_hashofhashes[POLYVAL_DIGEST_SIZE]', + Polyval(key).update(hashes).digest()) + +if len(sys.argv) != 2: + sys.stderr.write('Usage: gen-hash-testvecs.py ALGORITHM\n') + sys.stderr.write('ALGORITHM may be any supported by Python hashlib;\n') + sys.stderr.write(' or aes-cmac, ghash, nh, poly1305, polyval, or sha3.\n') + sys.stderr.write('Example: gen-hash-testvecs.py sha512\n') + sys.exit(1) + +alg = sys.argv[1] +print('/* SPDX-License-Identifier: GPL-2.0-or-later */') +print(f'/* This file was generated by: {sys.argv[0]} {" ".join(sys.argv[1:])} */') +if alg == 'aes-cmac': + gen_unkeyed_testvecs(alg) +elif alg.startswith('blake2'): + gen_unkeyed_testvecs(alg) + gen_additional_blake2_testvecs(alg) +elif alg == 'ghash': + gen_unkeyed_testvecs(alg) + gen_additional_ghash_testvecs() +elif alg == 'nh': + gen_nh_testvecs() +elif alg == 'poly1305': + gen_unkeyed_testvecs(alg) + gen_additional_poly1305_testvecs() +elif alg == 'polyval': + gen_unkeyed_testvecs(alg) + gen_additional_polyval_testvecs() +elif alg == 'sha3': + print() + print('/* SHA3-256 test vectors */') + gen_unkeyed_testvecs('sha3-256') + print() + print('/* SHAKE test vectors */') + gen_additional_sha3_testvecs() +elif alg == 'sm3': + gen_unkeyed_testvecs(alg) + # Kernel doesn't implement HMAC-SM3 library functions yet. +else: + gen_unkeyed_testvecs(alg) + gen_hmac_testvecs(alg) diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh new file mode 100755 index 000000000..39d60d477 --- /dev/null +++ b/scripts/decode_stacktrace.sh @@ -0,0 +1,368 @@ +#!/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] -r <release>" + echo " $0 [-R] [<vmlinux> [<base_path>|auto [<modules_path>]]]" + echo " $0 -h" + echo "Options:" + echo " -R: decode return address instead of caller address." +} + +# Try to find a Rust demangler +if type llvm-cxxfilt >/dev/null 2>&1 ; then + cppfilt=llvm-cxxfilt +elif type c++filt >/dev/null 2>&1 ; then + cppfilt=c++filt + cppfilt_opts=-i +fi + +UTIL_SUFFIX= +if [[ -z ${LLVM:-} ]]; then + UTIL_PREFIX=${CROSS_COMPILE:-} +else + UTIL_PREFIX=llvm- + if [[ ${LLVM} == */ ]]; then + UTIL_PREFIX=${LLVM}${UTIL_PREFIX} + elif [[ ${LLVM} == -* ]]; then + UTIL_SUFFIX=${LLVM} + fi +fi + +READELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX} +ADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX} +NM=${UTIL_PREFIX}nm${UTIL_SUFFIX} +decode_retaddr=false + +if [[ $1 == "-h" ]] ; then + usage + exit 0 +elif [[ $1 == "-R" ]] ; then + decode_retaddr=true + shift 1 +fi + +if [[ $1 == "-r" ]] ; then + vmlinux="" + basepath="auto" + modpath="" + release=$2 + + for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do + if [ -e "$fn" ] ; then + vmlinux=$fn + break + fi + done + + if [[ $vmlinux == "" ]] ; then + echo "ERROR! vmlinux image for release $release is not found" >&2 + usage + exit 2 + fi +else + vmlinux=$1 + basepath=${2-auto} + modpath=$3 + release="" + debuginfod= + + # Can we use debuginfod-find? + if type debuginfod-find >/dev/null 2>&1 ; then + debuginfod=${1-only} + fi + + if [[ $vmlinux == "" && -z $debuginfod ]] ; then + echo "ERROR! vmlinux image must be specified" >&2 + usage + exit 1 + fi +fi + +declare aarray_support=true +declare -A cache 2>/dev/null +if [[ $? != 0 ]]; then + aarray_support=false +else + declare -A modcache +fi + +find_module() { + if [[ -n $debuginfod ]] ; then + if [[ -n $modbuildid ]] ; then + debuginfod-find debuginfo $modbuildid && return + fi + + # Only using debuginfod so don't try to find vmlinux module path + if [[ $debuginfod == "only" ]] ; then + return + fi + fi + + 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 + + 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 + + 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 +} + +parse_symbol() { + # The structure of symbol at this point is: + # ([name]+[offset]/[total length]) + # + # For example: + # do_basic_setup+0x9c/0xbf + + if [[ $module == "" ]] ; then + local objfile=$vmlinux + elif [[ $aarray_support == true && "${modcache[$module]+isset}" == "isset" ]]; then + local objfile=${modcache[$module]} + else + local objfile=$(find_module) + if [[ $objfile == "" ]] ; then + return + fi + if [[ $aarray_support == true ]]; then + modcache[$module]=$objfile + fi + fi + + # Remove the englobing parenthesis + symbol=${symbol#\(} + symbol=${symbol%\)} + + # Strip segment + local segment + if [[ $symbol == *:* ]] ; then + segment=${symbol%%:*}: + symbol=${symbol#*:} + fi + + # Strip the symbol name so that we could look it up + local name=${symbol%+*} + + # Use 'nm vmlinux' to figure out the base address of said symbol. + # It's actually faster to call it every time than to load it + # all into bash. + 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}') + if [[ $base_addr == "" ]] ; then + # address not found + return + fi + if [[ $aarray_support == true ]]; then + cache[$module,$name]="$base_addr" + fi + fi + # Let's start doing the math to get the exact address into the + # symbol. First, strip out the symbol total length. + local expr=${symbol%/*} + # Also parse the offset from symbol. + local offset=${expr#*+} + offset=$((offset)) + + # Now, replace the symbol name with the base address we found + # before. + expr=${expr/$name/0x$base_addr} + + # Evaluate it to find the actual address + # The stack trace shows the return address, which is the next + # instruction after the actual call, so as long as it's in the same + # symbol, subtract one from that to point the call instruction. + if [[ $decode_retaddr == false && $offset != 0 ]]; then + expr=$((expr-1)) + else + expr=$((expr)) + fi + local address=$(printf "%x\n" "$expr") + + # Pass it to addr2line to get filename and line number + # Could get more than one result + if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then + local code=${cache[$module,$address]} + else + local code=$(${ADDR2LINE} -i -e "$objfile" "$address" 2>/dev/null) + if [[ $aarray_support == true ]]; then + cache[$module,$address]=$code + fi + fi + + # addr2line doesn't return a proper error code if it fails, so + # we detect it using the value it prints so that we could preserve + # the offset/size into the function and bail out + if [[ $code == "??:0" ]]; then + return + fi + + # Strip out the base of the path on each line + code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") + + # In the case of inlines, move everything to same line + code=${code//$'\n'/' '} + + # Demangle if the name looks like a Rust symbol and if + # we got a Rust demangler + if [[ $name =~ ^_R && $cppfilt != "" ]] ; then + name=$("$cppfilt" "$cppfilt_opts" "$name") + fi + + # Replace old address with pretty line numbers + symbol="$segment$name ($code)" +} + +debuginfod_get_vmlinux() { + local vmlinux_buildid=${1##* } + + if [[ $vmlinux != "" ]]; then + return + fi + + if [[ $vmlinux_buildid =~ ^[0-9a-f]+ ]]; then + vmlinux=$(debuginfod-find debuginfo $vmlinux_buildid) + if [[ $? -ne 0 ]] ; then + echo "ERROR! vmlinux image not found via debuginfod-find" >&2 + usage + exit 2 + fi + return + fi + echo "ERROR! Build ID for vmlinux not found. Try passing -r or specifying vmlinux" >&2 + usage + exit 2 +} + +decode_code() { + local scripts=`dirname "${BASH_SOURCE[0]}"` + local lim="Code: " + + echo -n "${1%%${lim}*}" + echo "${lim}${1##*${lim}}" | $scripts/decodecode +} + +handle_line() { + if [[ $basepath == "auto" && $vmlinux != "" ]] ; then + module="" + symbol="kernel_init+0x0/0x0" + parse_symbol + basepath=${symbol#kernel_init (} + basepath=${basepath%/init/main.c:*)} + fi + + local words spaces + + # Tokenize: words and spaces to preserve the alignment + read -ra words <<<"$1" + IFS='#' read -ra spaces <<<"$(shopt -s extglob; echo "${1//+([^[:space:]])/#}")" + + # Remove hex numbers. Do it ourselves until it happens in the + # kernel + + # We need to know the index of the last element before we + # remove elements because arrays are sparse + local last=$(( ${#words[@]} - 1 )) + + for i in "${!words[@]}"; do + # Remove the address + if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then + unset words[$i] spaces[$i] + fi + done + + # 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] spaces[$last] + last=$(( $last - 1 )) + fi + + # Join module name with its build id if present, as these were + # split during tokenization (e.g. "[module" and "modbuildid]"). + if [[ ${words[$last]} =~ ^[0-9a-f]+\] ]]; then + words[$last-1]="${words[$last-1]} ${words[$last]}" + unset words[$last] spaces[$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#* } + module=${module% *} + if [[ $modbuildid == $module ]]; then + modbuildid= + fi + symbol=${words[$last-1]} + unset words[$last-1] spaces[$last-1] + else + # The symbol is the last element, process it + symbol=${words[$last]} + module= + modbuildid= + fi + + unset words[$last] + parse_symbol # modifies $symbol + + # Add up the line number to the symbol + for i in "${!words[@]}"; do + echo -n "${spaces[i]}${words[i]}" + done + echo "${spaces[$last]}${symbol}${module:+ ${module}}${info_str:+ ${info_str}}" +} + +while read line; do + # Strip unexpected carriage return at end of line + line=${line%$'\r'} + + # Let's see if we have an address in the line + if [[ $line =~ \[\<([^]]+)\>\] ]] || + [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then + # Translate address to line numbers + handle_line "$line" + # Is it a code line? + elif [[ $line == *Code:* ]]; then + decode_code "$line" + # Is it a version line? + elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then + debuginfod_get_vmlinux "$line" + else + # Nothing special in this line, show it as is + echo "$line" + fi +done diff --git a/scripts/decodecode b/scripts/decodecode new file mode 100755 index 000000000..01d25dc11 --- /dev/null +++ b/scripts/decodecode @@ -0,0 +1,251 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Disassemble the Code: line in Linux oopses +# usage: decodecode < oops.file +# +# options: set env. variable AFLAGS=options to pass options to "as"; +# e.g., to decode an i386 oops on an x86_64 system, use: +# AFLAGS=--32 decodecode < 386.oops +# PC=hex - the PC (program counter) the oops points to + +faultlinenum=1 + +cleanup() { + rm -f $T $T.s $T.o $T.oo $T.aa $T.dis +} + +die() { + echo "$@" + exit 1 +} + +trap cleanup EXIT + +T=`mktemp` || die "cannot create temp file" +code= +cont= + +while read i ; do + +case "$i" in +*Code:*) + code=$i + cont=yes + ;; +*) + [ -n "$cont" ] && { + xdump="$(echo $i | grep '^[[:xdigit:]<>[:space:]]\+$')" + if [ -n "$xdump" ]; then + code="$code $xdump" + else + cont= + fi + } + ;; +esac + +done + +if [ -z "$code" ]; then + rm $T + die "Code line not found" +fi + +echo $code +code=`echo $code | sed -e 's/.*Code: //'` + +width=`expr index "$code" ' '` +width=$((($width-1)/2)) +case $width in +1) type=byte ;; +2) type=2byte ;; +4) type=4byte ;; +esac + +if [ -z "$ARCH" ]; then + case `uname -m` in + aarch64*) ARCH=arm64 ;; + arm*) ARCH=arm ;; + loongarch*) ARCH=loongarch ;; + esac +fi + +# Params: (tmp_file, pc_sub) +disas() { + t=$1 + pc_sub=$2 + + ${CROSS_COMPILE}as $AFLAGS -o $t.o $t.s > /dev/null 2>&1 + + if [ "$ARCH" = "arm" ]; then + if [ $width -eq 2 ]; then + OBJDUMPFLAGS="-M force-thumb" + fi + + ${CROSS_COMPILE}strip $t.o + fi + + if [ "$ARCH" = "arm64" ]; then + if [ $width -eq 4 ]; then + type=inst + fi + + ${CROSS_COMPILE}strip $t.o + fi + + if [ "$ARCH" = "riscv" ]; then + OBJDUMPFLAGS="-M no-aliases --section=.text -D" + ${CROSS_COMPILE}strip $t.o + fi + + if [ "$ARCH" = "loongarch" ]; then + ${CROSS_COMPILE}strip $t.o + fi + + if [ $pc_sub -ne 0 ]; then + if [ $PC ]; then + adj_vma=$(( $PC - $pc_sub )) + OBJDUMPFLAGS="$OBJDUMPFLAGS --adjust-vma=$adj_vma" + fi + fi + + ${CROSS_COMPILE}objdump $OBJDUMPFLAGS -S $t.o | \ + grep -v "/tmp\|Disassembly\|\.text\|^$" > $t.dis 2>&1 +} + +# Match the maximum number of opcode bytes from @op_bytes contained within +# @opline +# +# Params: +# @op_bytes: The string of bytes from the Code: line +# @opline: The disassembled line coming from objdump +# +# Returns: +# The max number of opcode bytes from the beginning of @op_bytes which match +# the opcode bytes in the objdump line. +get_substr_opcode_bytes_num() +{ + local op_bytes=$1 + local opline=$2 + + local retval=0 + substr="" + + for opc in $op_bytes; + do + substr+="$opc" + + opcode="$substr" + if [ "$ARCH" = "riscv" ]; then + opcode=$(echo $opcode | tr ' ' '\n' | tac | tr -d '\n') + fi + + # return if opcode bytes do not match @opline anymore + if ! echo $opline | grep -q "$opcode"; + then + break + fi + + # add trailing space + substr+=" " + retval=$((retval+1)) + done + + return $retval +} + +# Return the line number in objdump output to where the IP marker in the Code: +# line points to +# +# Params: +# @all_code: code in bytes without the marker +# @dis_file: disassembled file +# @ip_byte: The byte to which the IP points to +get_faultlinenum() +{ + local all_code="$1" + local dis_file="$2" + + # num bytes including IP byte + local num_bytes_ip=$(( $3 + 1 * $width )) + + # Add the two header lines (we're counting from 1). + local retval=3 + + # remove marker + all_code=$(echo $all_code | sed -e 's/[<>()]//g') + + while read line + do + get_substr_opcode_bytes_num "$all_code" "$line" + ate_opcodes=$? + + if ! (( $ate_opcodes )); then + continue + fi + + num_bytes_ip=$((num_bytes_ip - ($ate_opcodes * $width) )) + if (( $num_bytes_ip <= 0 )); then + break + fi + + # Delete matched opcode bytes from all_code. For that, compute + # how many chars those opcodes are represented by and include + # trailing space. + # + # a byte is 2 chars, ate_opcodes is also the number of trailing + # spaces + del_chars=$(( ($ate_opcodes * $width * 2) + $ate_opcodes )) + + all_code=$(echo $all_code | sed -e "s!^.\{$del_chars\}!!") + + let "retval+=1" + + done < $dis_file + + return $retval +} + +marker=`expr index "$code" "\<"` +if [ $marker -eq 0 ]; then + marker=`expr index "$code" "\("` +fi + +touch $T.oo +if [ $marker -ne 0 ]; then + # How many bytes to subtract from the program counter + # in order to get to the beginning virtual address of the + # Code: + pc_sub=$(( (($marker - 1) / (2 * $width + 1)) * $width )) + echo All code >> $T.oo + echo ======== >> $T.oo + beforemark=`echo "$code"` + echo -n " .$type 0x" > $T.s + + echo $beforemark | sed -e 's/ /,0x/g; s/[<>()]//g' >> $T.s + + disas $T $pc_sub + + cat $T.dis >> $T.oo + + get_faultlinenum "$code" "$T.dis" $pc_sub + faultlinenum=$? + + # and fix code at-and-after marker + code=`echo "$code" | cut -c$((${marker} + 1))-` + + rm -f $T.o $T.s $T.dis +fi + +echo Code starting with the faulting instruction > $T.aa +echo =========================================== >> $T.aa +code=`echo $code | sed -e 's/\r//;s/ [<(]/ /;s/[>)] / /;s/ /,0x/g; s/[>)]$//'` +echo -n " .$type 0x" > $T.s +echo $code >> $T.s +disas $T 0 +cat $T.dis >> $T.aa + +cat $T.oo | sed -e "${faultlinenum}s/^\([^:]*:\)\(.*\)/\1\*\2\t\t<-- trapping instruction/" +echo +cat $T.aa +cleanup diff --git a/scripts/depmod.sh b/scripts/depmod.sh new file mode 100755 index 000000000..3c34fecac --- /dev/null +++ b/scripts/depmod.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# A depmod wrapper + +if test $# -ne 1; then + echo "Usage: $0 <kernelrelease>" >&2 + exit 1 +fi + +KERNELRELEASE=$1 + +: ${DEPMOD:=depmod} + +if ! test -r "${objtree}/System.map" ; then + echo "Warning: modules_install: missing 'System.map' file. Skipping depmod." >&2 + exit 0 +fi + +# legacy behavior: "depmod" in /sbin, no /sbin in PATH +PATH="$PATH:/sbin" +if [ -z $(command -v $DEPMOD) ]; then + echo "Warning: 'make modules_install' requires $DEPMOD. Please install it." >&2 + echo "This is probably in the kmod package." >&2 + exit 0 +fi + +set -- -ae -F "${objtree}/System.map" +if test -n "$INSTALL_MOD_PATH"; then + set -- "$@" -b "$INSTALL_MOD_PATH" +fi +exec "$DEPMOD" "$@" "$KERNELRELEASE" diff --git a/scripts/dev-needs.sh b/scripts/dev-needs.sh new file mode 100755 index 000000000..454cc304f --- /dev/null +++ b/scripts/dev-needs.sh @@ -0,0 +1,315 @@ +#! /bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2020, Google LLC. All rights reserved. +# Author: Saravana Kannan <saravanak@google.com> + +function help() { + cat << EOF +Usage: $(basename $0) [-c|-d|-m|-f] [filter options] <list of devices> + +This script needs to be run on the target device once it has booted to a +shell. + +The script takes as input a list of one or more device directories under +/sys/devices and then lists the probe dependency chain (suppliers and +parents) of these devices. It does a breadth first search of the dependency +chain, so the last entry in the output is close to the root of the +dependency chain. + +By default it lists the full path to the devices under /sys/devices. + +It also takes an optional modifier flag as the first parameter to change +what information is listed in the output. If the requested information is +not available, the device name is printed. + + -c lists the compatible string of the dependencies + -d lists the driver name of the dependencies that have probed + -m lists the module name of the dependencies that have a module + -f list the firmware node path of the dependencies + -g list the dependencies as edges and nodes for graphviz + -t list the dependencies as edges for tsort + +The filter options provide a way to filter out some dependencies: + --allow-no-driver By default dependencies that don't have a driver + attached are ignored. This is to avoid following + device links to "class" devices that are created + when the consumer probes (as in, not a probe + dependency). If you want to follow these links + anyway, use this flag. + + --exclude-devlinks Don't follow device links when tracking probe + dependencies. + + --exclude-parents Don't follow parent devices when tracking probe + dependencies. + +EOF +} + +function dev_to_detail() { + local i=0 + while [ $i -lt ${#OUT_LIST[@]} ] + do + local C=${OUT_LIST[i]} + local S=${OUT_LIST[i+1]} + local D="'$(detail_chosen $C $S)'" + if [ ! -z "$D" ] + then + # This weirdness is needed to work with toybox when + # using the -t option. + printf '%05u\t%s\n' ${i} "$D" | tr -d \' + fi + i=$((i+2)) + done +} + +function already_seen() { + local i=0 + while [ $i -lt ${#OUT_LIST[@]} ] + do + if [ "$1" = "${OUT_LIST[$i]}" ] + then + # if-statement treats 0 (no-error) as true + return 0 + fi + i=$(($i+2)) + done + + # if-statement treats 1 (error) as false + return 1 +} + +# Return 0 (no-error/true) if parent was added +function add_parent() { + + if [ ${ALLOW_PARENTS} -eq 0 ] + then + return 1 + fi + + local CON=$1 + # $CON could be a symlink path. So, we need to find the real path and + # then go up one level to find the real parent. + local PARENT=$(realpath $CON/..) + + while [ ! -e ${PARENT}/driver ] + do + if [ "$PARENT" = "/sys/devices" ] + then + return 1 + fi + PARENT=$(realpath $PARENT/..) + done + + CONSUMERS+=($PARENT) + OUT_LIST+=(${CON} ${PARENT}) + return 0 +} + +# Return 0 (no-error/true) if one or more suppliers were added +function add_suppliers() { + local CON=$1 + local RET=1 + + if [ ${ALLOW_DEVLINKS} -eq 0 ] + then + return 1 + fi + + SUPPLIER_LINKS=$(ls -1d $CON/supplier:* 2>/dev/null) + for SL in $SUPPLIER_LINKS; + do + SYNC_STATE=$(cat $SL/sync_state_only) + + # sync_state_only links are proxy dependencies. + # They can also have cycles. So, don't follow them. + if [ "$SYNC_STATE" != '0' ] + then + continue + fi + + SUPPLIER=$(realpath $SL/supplier) + + if [ ! -e $SUPPLIER/driver -a ${ALLOW_NO_DRIVER} -eq 0 ] + then + continue + fi + + CONSUMERS+=($SUPPLIER) + OUT_LIST+=(${CON} ${SUPPLIER}) + RET=0 + done + + return $RET +} + +function detail_compat() { + f=$1/of_node/compatible + if [ -e $f ] + then + echo -n $(cat $f) + else + echo -n $1 + fi +} + +function detail_module() { + f=$1/driver/module + if [ -e $f ] + then + echo -n $(basename $(realpath $f)) + else + echo -n $1 + fi +} + +function detail_driver() { + f=$1/driver + if [ -e $f ] + then + echo -n $(basename $(realpath $f)) + else + echo -n $1 + fi +} + +function detail_fwnode() { + f=$1/firmware_node + if [ ! -e $f ] + then + f=$1/of_node + fi + + if [ -e $f ] + then + echo -n $(realpath $f) + else + echo -n $1 + fi +} + +function detail_graphviz() { + if [ "$2" != "ROOT" ] + then + echo -n "\"$(basename $2)\"->\"$(basename $1)\"" + else + echo -n "\"$(basename $1)\"" + fi +} + +function detail_tsort() { + echo -n "\"$2\" \"$1\"" +} + +function detail_device() { echo -n $1; } + +alias detail=detail_device +ALLOW_NO_DRIVER=0 +ALLOW_DEVLINKS=1 +ALLOW_PARENTS=1 + +while [ $# -gt 0 ] +do + ARG=$1 + case $ARG in + --help) + help + exit 0 + ;; + -c) + alias detail=detail_compat + ;; + -m) + alias detail=detail_module + ;; + -d) + alias detail=detail_driver + ;; + -f) + alias detail=detail_fwnode + ;; + -g) + alias detail=detail_graphviz + ;; + -t) + alias detail=detail_tsort + ;; + --allow-no-driver) + ALLOW_NO_DRIVER=1 + ;; + --exclude-devlinks) + ALLOW_DEVLINKS=0 + ;; + --exclude-parents) + ALLOW_PARENTS=0 + ;; + *) + # Stop at the first argument that's not an option. + break + ;; + esac + shift +done + +function detail_chosen() { + detail $1 $2 +} + +if [ $# -eq 0 ] +then + help + exit 1 +fi + +CONSUMERS=($@) +OUT_LIST=() + +# Do a breadth first, non-recursive tracking of suppliers. The parent is also +# considered a "supplier" as a device can't probe without its parent. +i=0 +while [ $i -lt ${#CONSUMERS[@]} ] +do + CONSUMER=$(realpath ${CONSUMERS[$i]}) + i=$(($i+1)) + + if already_seen ${CONSUMER} + then + continue + fi + + # If this is not a device with a driver, we don't care about its + # suppliers. + if [ ! -e ${CONSUMER}/driver -a ${ALLOW_NO_DRIVER} -eq 0 ] + then + continue + fi + + ROOT=1 + + # Add suppliers to CONSUMERS list and output the consumer details. + # + # We don't need to worry about a cycle in the dependency chain causing + # infinite loops. That's because the kernel doesn't allow cycles in + # device links unless it's a sync_state_only device link. And we ignore + # sync_state_only device links inside add_suppliers. + if add_suppliers ${CONSUMER} + then + ROOT=0 + fi + + if add_parent ${CONSUMER} + then + ROOT=0 + fi + + if [ $ROOT -eq 1 ] + then + OUT_LIST+=(${CONSUMER} "ROOT") + fi +done + +# Can NOT combine sort and uniq using sort -suk2 because stable sort in toybox +# isn't really stable. +dev_to_detail | sort -k2 -k1 | uniq -f 1 | sort | cut -f2- + +exit 0 diff --git a/scripts/diffconfig b/scripts/diffconfig new file mode 100755 index 000000000..43f0f3d27 --- /dev/null +++ b/scripts/diffconfig @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# +# diffconfig - a tool to compare .config files. +# +# originally written in 2006 by Matt Mackall +# (at least, this was in his bloatwatch source code) +# last worked on 2008 by Tim Bird +# + +import sys, os + +def usage(): + print("""Usage: diffconfig [-h] [-m] [<config1> <config2>] + +Diffconfig is a simple utility for comparing two .config files. +Using standard diff to compare .config files often includes extraneous and +distracting information. This utility produces sorted output with only the +changes in configuration values between the two files. + +Added and removed items are shown with a leading plus or minus, respectively. +Changed items show the old and new values on a single line. + +If -m is specified, then output will be in "merge" style, which has the +changed and new values in kernel config option format. + +If no config files are specified, .config and .config.old are used. + +Example usage: + $ diffconfig .config config-with-some-changes +-EXT2_FS_XATTR n + CRAMFS n -> y + EXT2_FS y -> n + LOG_BUF_SHIFT 14 -> 16 + PRINTK_TIME n -> y +""") + sys.exit(0) + +# returns a dictionary of name/value pairs for config items in the file +def readconfig(config_file): + d = {} + for line in config_file: + line = line[:-1] + if line[:7] == "CONFIG_": + name, val = line[7:].split("=", 1) + d[name] = val + if line[-11:] == " is not set": + d[line[9:-11]] = "n" + return d + +def print_config(op, config, value, new_value): + global merge_style + + if merge_style: + if new_value: + if new_value=="n": + print("# CONFIG_%s is not set" % config) + else: + print("CONFIG_%s=%s" % (config, new_value)) + else: + if op=="-": + print("-%s %s" % (config, value)) + elif op=="+": + print("+%s %s" % (config, new_value)) + else: + print(" %s %s -> %s" % (config, value, new_value)) + +def show_diff(): + global merge_style + + # parse command line args + if ("-h" in sys.argv or "--help" in sys.argv): + usage() + + merge_style = 0 + if "-m" in sys.argv: + merge_style = 1 + sys.argv.remove("-m") + + argc = len(sys.argv) + if not (argc==1 or argc == 3): + print("Error: incorrect number of arguments or unrecognized option") + usage() + + if argc == 1: + # if no filenames given, assume .config and .config.old + build_dir="" + if "KBUILD_OUTPUT" in os.environ: + build_dir = os.environ["KBUILD_OUTPUT"]+"/" + configa_filename = build_dir + ".config.old" + configb_filename = build_dir + ".config" + else: + configa_filename = sys.argv[1] + configb_filename = sys.argv[2] + + try: + a = readconfig(open(configa_filename)) + b = readconfig(open(configb_filename)) + except (IOError): + e = sys.exc_info()[1] + print("I/O error[%s]: %s\n" % (e.args[0],e.args[1])) + usage() + + # print items in a but not b (accumulate, sort and print) + old = [] + for config in a: + if config not in b: + old.append(config) + old.sort() + for config in old: + print_config("-", config, a[config], None) + del a[config] + + # print items that changed (accumulate, sort, and print) + changed = [] + for config in a: + if a[config] != b[config]: + changed.append(config) + else: + del b[config] + changed.sort() + for config in changed: + print_config("->", config, a[config], b[config]) + del b[config] + + # now print items in b but not in a + # (items from b that were in a were removed above) + new = sorted(b.keys()) + for config in new: + print_config("+", config, None, b[config]) + +def main(): + try: + show_diff() + except BrokenPipeError: + # Python flushes standard streams on exit; redirect remaining output + # to devnull to avoid another BrokenPipeError at shutdown + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + sys.exit(1) # Python exits with error code 1 on EPIPE + + +if __name__ == '__main__': + main() diff --git a/scripts/dtc/.gitignore b/scripts/dtc/.gitignore new file mode 100644 index 000000000..e0b5c1d24 --- /dev/null +++ b/scripts/dtc/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +/dtc +/fdtoverlay diff --git a/scripts/dtc/Makefile b/scripts/dtc/Makefile new file mode 100644 index 000000000..b47f4daa4 --- /dev/null +++ b/scripts/dtc/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +# scripts/dtc makefile + +# *** Also keep .gitignore in sync when changing *** +hostprogs-always-$(CONFIG_DTC) += dtc fdtoverlay +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 +dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o + +# The upstream project builds libfdt as a separate library. We are choosing to +# instead directly link the libfdt object files into fdtoverlay. +libfdt-objs := fdt.o fdt_ro.o fdt_wip.o fdt_sw.o fdt_rw.o fdt_strerror.o fdt_empty_tree.o fdt_addresses.o fdt_overlay.o +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 $(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 $(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 new file mode 100644 index 000000000..946c1429e --- /dev/null +++ b/scripts/dtc/checks.c @@ -0,0 +1,2082 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2007. + */ + +#include "dtc.h" +#include "srcpos.h" + +#ifdef TRACE_CHECKS +#define TRACE(c, ...) \ + do { \ + fprintf(stderr, "=== %s: ", (c)->name); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) +#else +#define TRACE(c, fmt, ...) do { } while (0) +#endif + +enum checkstatus { + UNCHECKED = 0, + PREREQ, + PASSED, + FAILED, +}; + +struct check; + +typedef void (*check_fn)(struct check *c, struct dt_info *dti, struct node *node); + +struct check { + const char *name; + check_fn fn; + const void *data; + bool warn, error; + enum checkstatus status; + bool inprogress; + int num_prereqs; + struct check **prereq; +}; + +#define CHECK_ENTRY(nm_, fn_, d_, w_, e_, ...) \ + static struct check *nm_##_prereqs[] = { __VA_ARGS__ }; \ + static struct check nm_ = { \ + .name = #nm_, \ + .fn = (fn_), \ + .data = (d_), \ + .warn = (w_), \ + .error = (e_), \ + .status = UNCHECKED, \ + .num_prereqs = ARRAY_SIZE(nm_##_prereqs), \ + .prereq = nm_##_prereqs, \ + }; +#define WARNING(nm_, fn_, d_, ...) \ + CHECK_ENTRY(nm_, fn_, d_, true, false, __VA_ARGS__) +#define ERROR(nm_, fn_, d_, ...) \ + CHECK_ENTRY(nm_, fn_, d_, false, true, __VA_ARGS__) +#define CHECK(nm_, fn_, d_, ...) \ + CHECK_ENTRY(nm_, fn_, d_, false, false, __VA_ARGS__) + +static inline void PRINTF(5, 6) check_msg(struct check *c, struct dt_info *dti, + struct node *node, + struct property *prop, + const char *fmt, ...) +{ + va_list ap; + char *str = NULL; + struct srcpos *pos = NULL; + char *file_str; + + if (!(c->warn && (quiet < 1)) && !(c->error && (quiet < 2))) + return; + + if (prop && prop->srcpos) + pos = prop->srcpos; + else if (node && node->srcpos) + pos = node->srcpos; + + if (pos) { + file_str = srcpos_string(pos); + xasprintf(&str, "%s", file_str); + free(file_str); + } else if (streq(dti->outname, "-")) { + xasprintf(&str, "<stdout>"); + } else { + xasprintf(&str, "%s", dti->outname); + } + + xasprintf_append(&str, ": %s (%s): ", + (c->error) ? "ERROR" : "Warning", c->name); + + if (node) { + if (prop) + xasprintf_append(&str, "%s:%s: ", node->fullpath, prop->name); + else + xasprintf_append(&str, "%s: ", node->fullpath); + } + + va_start(ap, fmt); + xavsprintf_append(&str, fmt, ap); + va_end(ap); + + xasprintf_append(&str, "\n"); + + if (!prop && pos) { + pos = node->srcpos; + while (pos->next) { + pos = pos->next; + + file_str = srcpos_string(pos); + xasprintf_append(&str, " also defined at %s\n", file_str); + free(file_str); + } + } + + fputs(str, stderr); + free(str); +} + +#define FAIL(c, dti, node, ...) \ + do { \ + TRACE((c), "\t\tFAILED at %s:%d", __FILE__, __LINE__); \ + (c)->status = FAILED; \ + check_msg((c), dti, node, NULL, __VA_ARGS__); \ + } while (0) + +#define FAIL_PROP(c, dti, node, prop, ...) \ + do { \ + TRACE((c), "\t\tFAILED at %s:%d", __FILE__, __LINE__); \ + (c)->status = FAILED; \ + check_msg((c), dti, node, prop, __VA_ARGS__); \ + } while (0) + + +static void check_nodes_props(struct check *c, struct dt_info *dti, struct node *node) +{ + struct node *child; + + TRACE(c, "%s", node->fullpath); + if (c->fn) + c->fn(c, dti, node); + + for_each_child(node, child) + check_nodes_props(c, dti, child); +} + +static bool is_multiple_of(int multiple, int divisor) +{ + if (divisor == 0) + return multiple == 0; + else + return (multiple % divisor) == 0; +} + +static bool run_check(struct check *c, struct dt_info *dti) +{ + struct node *dt = dti->dt; + bool error = false; + int i; + + assert(!c->inprogress); + + if (c->status != UNCHECKED) + goto out; + + c->inprogress = true; + + for (i = 0; i < c->num_prereqs; i++) { + struct check *prq = c->prereq[i]; + error = error || run_check(prq, dti); + if (prq->status != PASSED) { + c->status = PREREQ; + check_msg(c, dti, NULL, NULL, "Failed prerequisite '%s'", + c->prereq[i]->name); + } + } + + if (c->status != UNCHECKED) + goto out; + + check_nodes_props(c, dti, dt); + + if (c->status == UNCHECKED) + c->status = PASSED; + + TRACE(c, "\tCompleted, status %d", c->status); + +out: + c->inprogress = false; + if ((c->status != PASSED) && (c->error)) + error = true; + return error; +} + +/* + * Utility check functions + */ + +/* A check which always fails, for testing purposes only */ +static inline void check_always_fail(struct check *c, struct dt_info *dti, + struct node *node) +{ + FAIL(c, dti, node, "always_fail check"); +} +CHECK(always_fail, check_always_fail, NULL); + +static void check_is_string(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + const char *propname = c->data; + + prop = get_property(node, propname); + if (!prop) + return; /* Not present, assumed ok */ + + if (!data_is_one_string(prop->val)) + FAIL_PROP(c, dti, node, prop, "property is not a string"); +} +#define WARNING_IF_NOT_STRING(nm, propname) \ + WARNING(nm, check_is_string, (propname)) +#define ERROR_IF_NOT_STRING(nm, propname) \ + ERROR(nm, check_is_string, (propname)) + +static void check_is_string_list(struct check *c, struct dt_info *dti, + struct node *node) +{ + int rem, l; + struct property *prop; + const char *propname = c->data; + char *str; + + prop = get_property(node, propname); + if (!prop) + return; /* Not present, assumed ok */ + + str = prop->val.val; + rem = prop->val.len; + while (rem > 0) { + l = strnlen(str, rem); + if (l == rem) { + FAIL_PROP(c, dti, node, prop, "property is not a string list"); + break; + } + rem -= l + 1; + str += l + 1; + } +} +#define WARNING_IF_NOT_STRING_LIST(nm, propname) \ + WARNING(nm, check_is_string_list, (propname)) +#define ERROR_IF_NOT_STRING_LIST(nm, propname) \ + ERROR(nm, check_is_string_list, (propname)) + +static void check_is_cell(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + const char *propname = c->data; + + prop = get_property(node, propname); + if (!prop) + return; /* Not present, assumed ok */ + + if (prop->val.len != sizeof(cell_t)) + FAIL_PROP(c, dti, node, prop, "property is not a single cell"); +} +#define WARNING_IF_NOT_CELL(nm, propname) \ + WARNING(nm, check_is_cell, (propname)) +#define ERROR_IF_NOT_CELL(nm, propname) \ + ERROR(nm, check_is_cell, (propname)) + +/* + * Structural check functions + */ + +static void check_duplicate_node_names(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct node *child, *child2; + + for_each_child(node, child) + for (child2 = child->next_sibling; + child2; + child2 = child2->next_sibling) + if (streq(child->name, child2->name)) + FAIL(c, dti, child2, "Duplicate node name"); +} +ERROR(duplicate_node_names, check_duplicate_node_names, NULL); + +static void check_duplicate_property_names(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop, *prop2; + + for_each_property(node, prop) { + for (prop2 = prop->next; prop2; prop2 = prop2->next) { + if (prop2->deleted) + continue; + if (streq(prop->name, prop2->name)) + FAIL_PROP(c, dti, node, prop, "Duplicate property name"); + } + } +} +ERROR(duplicate_property_names, check_duplicate_property_names, NULL); + +#define LOWERCASE "abcdefghijklmnopqrstuvwxyz" +#define UPPERCASE "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define DIGITS "0123456789" +#define NODECHARS LOWERCASE UPPERCASE DIGITS ",._+-@" +#define PROPCHARS LOWERCASE UPPERCASE DIGITS ",._+*#?-" +#define PROPNODECHARSSTRICT LOWERCASE UPPERCASE DIGITS ",-" + +static void check_node_name_chars(struct check *c, struct dt_info *dti, + struct node *node) +{ + size_t n = strspn(node->name, c->data); + + if (n < strlen(node->name)) + FAIL(c, dti, node, "Bad character '%c' in node name", + node->name[n]); +} +ERROR(node_name_chars, check_node_name_chars, NODECHARS); + +static void check_node_name_chars_strict(struct check *c, struct dt_info *dti, + struct node *node) +{ + size_t n = strspn(node->name, c->data); + + if (n < node->basenamelen) + FAIL(c, dti, node, "Character '%c' not recommended in node name", + node->name[n]); +} +CHECK(node_name_chars_strict, check_node_name_chars_strict, PROPNODECHARSSTRICT); + +static void check_node_name_format(struct check *c, struct dt_info *dti, + struct node *node) +{ + if (strchr(get_unitname(node), '@')) + FAIL(c, dti, node, "multiple '@' characters in node name"); +} +ERROR(node_name_format, check_node_name_format, NULL, &node_name_chars); + +static void check_node_name_not_empty(struct check *c, struct dt_info *dti, + struct node *node) +{ + if (node->basenamelen == 0 && node->parent != NULL) + FAIL(c, dti, node, "Empty node name"); +} +ERROR(node_name_not_empty, check_node_name_not_empty, NULL, &node_name_chars); + +static void check_node_name_vs_property_name(struct check *c, + struct dt_info *dti, + struct node *node) +{ + if (!node->parent) + return; + + if (get_property(node->parent, node->name)) { + FAIL(c, dti, node, "node name and property name conflict"); + } +} +WARNING(node_name_vs_property_name, check_node_name_vs_property_name, + NULL, &node_name_chars); + +static void check_unit_address_vs_reg(struct check *c, struct dt_info *dti, + struct node *node) +{ + const char *unitname = get_unitname(node); + struct property *prop = get_property(node, "reg"); + + if (get_subnode(node, "__overlay__")) { + /* HACK: Overlay fragments are a special case */ + return; + } + + if (!prop) { + prop = get_property(node, "ranges"); + if (prop && !prop->val.len) + prop = NULL; + } + + if (prop) { + if (!unitname[0]) + FAIL(c, dti, node, "node has a reg or ranges property, but no unit name"); + } else { + if (unitname[0]) + FAIL(c, dti, node, "node has a unit name, but no reg or ranges property"); + } +} +WARNING(unit_address_vs_reg, check_unit_address_vs_reg, NULL); + +static void check_property_name_chars(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + for_each_property(node, prop) { + size_t n = strspn(prop->name, c->data); + + if (n < strlen(prop->name)) + FAIL_PROP(c, dti, node, prop, "Bad character '%c' in property name", + prop->name[n]); + } +} +ERROR(property_name_chars, check_property_name_chars, PROPCHARS); + +static void check_property_name_chars_strict(struct check *c, + struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + for_each_property(node, prop) { + const char *name = prop->name; + size_t n = strspn(name, c->data); + + if (n == strlen(prop->name)) + continue; + + /* Certain names are whitelisted */ + if (streq(name, "device_type")) + continue; + + /* + * # is only allowed at the beginning of property names not counting + * the vendor prefix. + */ + if (name[n] == '#' && ((n == 0) || (name[n-1] == ','))) { + name += n + 1; + n = strspn(name, c->data); + } + if (n < strlen(name)) + FAIL_PROP(c, dti, node, prop, "Character '%c' not recommended in property name", + name[n]); + } +} +CHECK(property_name_chars_strict, check_property_name_chars_strict, PROPNODECHARSSTRICT); + +#define DESCLABEL_FMT "%s%s%s%s%s" +#define DESCLABEL_ARGS(node,prop,mark) \ + ((mark) ? "value of " : ""), \ + ((prop) ? "'" : ""), \ + ((prop) ? (prop)->name : ""), \ + ((prop) ? "' in " : ""), (node)->fullpath + +static void check_duplicate_label(struct check *c, struct dt_info *dti, + const char *label, struct node *node, + struct property *prop, struct marker *mark) +{ + struct node *dt = dti->dt; + struct node *othernode = NULL; + struct property *otherprop = NULL; + struct marker *othermark = NULL; + + othernode = get_node_by_label(dt, label); + + if (!othernode) + otherprop = get_property_by_label(dt, label, &othernode); + if (!othernode) + othermark = get_marker_label(dt, label, &othernode, + &otherprop); + + if (!othernode) + return; + + if ((othernode != node) || (otherprop != prop) || (othermark != mark)) + FAIL(c, dti, node, "Duplicate label '%s' on " DESCLABEL_FMT + " and " DESCLABEL_FMT, + label, DESCLABEL_ARGS(node, prop, mark), + DESCLABEL_ARGS(othernode, otherprop, othermark)); +} + +static void check_duplicate_label_node(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct label *l; + struct property *prop; + + for_each_label(node->labels, l) + check_duplicate_label(c, dti, l->label, node, NULL, NULL); + + for_each_property(node, prop) { + struct marker *m = prop->val.markers; + + for_each_label(prop->labels, l) + check_duplicate_label(c, dti, l->label, node, prop, NULL); + + for_each_marker_of_type(m, LABEL) + check_duplicate_label(c, dti, m->ref, node, prop, m); + } +} +ERROR(duplicate_label, check_duplicate_label_node, NULL); + +static cell_t check_phandle_prop(struct check *c, struct dt_info *dti, + struct node *node, const char *propname) +{ + struct node *root = dti->dt; + struct property *prop; + struct marker *m; + cell_t phandle; + + prop = get_property(node, propname); + if (!prop) + return 0; + + if (prop->val.len != sizeof(cell_t)) { + FAIL_PROP(c, dti, node, prop, "bad length (%d) %s property", + prop->val.len, prop->name); + return 0; + } + + m = prop->val.markers; + for_each_marker_of_type(m, REF_PHANDLE) { + assert(m->offset == 0); + if (node != get_node_by_ref(root, m->ref)) + /* "Set this node's phandle equal to some + * other node's phandle". That's nonsensical + * by construction. */ { + FAIL(c, dti, node, "%s is a reference to another node", + prop->name); + } + /* But setting this node's phandle equal to its own + * phandle is allowed - that means allocate a unique + * phandle for this node, even if it's not otherwise + * referenced. The value will be filled in later, so + * we treat it as having no phandle data for now. */ + return 0; + } + + phandle = propval_cell(prop); + + if (!phandle_is_valid(phandle)) { + FAIL_PROP(c, dti, node, prop, "bad value (0x%x) in %s property", + phandle, prop->name); + return 0; + } + + return phandle; +} + +static void check_explicit_phandles(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct node *root = dti->dt; + struct node *other; + cell_t phandle, linux_phandle; + + /* Nothing should have assigned phandles yet */ + assert(!node->phandle); + + phandle = check_phandle_prop(c, dti, node, "phandle"); + + linux_phandle = check_phandle_prop(c, dti, node, "linux,phandle"); + + if (!phandle && !linux_phandle) + /* No valid phandles; nothing further to check */ + return; + + if (linux_phandle && phandle && (phandle != linux_phandle)) + FAIL(c, dti, node, "mismatching 'phandle' and 'linux,phandle'" + " properties"); + + if (linux_phandle && !phandle) + phandle = linux_phandle; + + other = get_node_by_phandle(root, phandle); + if (other && (other != node)) { + FAIL(c, dti, node, "duplicated phandle 0x%x (seen before at %s)", + phandle, other->fullpath); + return; + } + + node->phandle = phandle; +} +ERROR(explicit_phandles, check_explicit_phandles, NULL); + +static void check_name_properties(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property **pp, *prop = NULL; + + for (pp = &node->proplist; *pp; pp = &((*pp)->next)) + if (streq((*pp)->name, "name")) { + prop = *pp; + break; + } + + if (!prop) + return; /* No name property, that's fine */ + + if ((prop->val.len != node->basenamelen + 1U) + || (memcmp(prop->val.val, node->name, node->basenamelen) != 0)) { + FAIL(c, dti, node, "\"name\" property is incorrect (\"%s\" instead" + " of base node name)", prop->val.val); + } else { + /* The name property is correct, and therefore redundant. + * Delete it */ + *pp = prop->next; + free(prop->name); + data_free(prop->val); + free(prop); + } +} +ERROR_IF_NOT_STRING(name_is_string, "name"); +ERROR(name_properties, check_name_properties, NULL, &name_is_string); + +/* + * Reference fixup functions + */ + +static void fixup_phandle_references(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct node *dt = dti->dt; + struct property *prop; + + for_each_property(node, prop) { + struct marker *m = prop->val.markers; + struct node *refnode; + cell_t phandle; + + for_each_marker_of_type(m, REF_PHANDLE) { + assert(m->offset + sizeof(cell_t) <= prop->val.len); + + refnode = get_node_by_ref(dt, m->ref); + if (! refnode) { + if (!(dti->dtsflags & DTSF_PLUGIN)) + FAIL(c, dti, node, "Reference to non-existent node or " + "label \"%s\"\n", m->ref); + else /* mark the entry as unresolved */ + *((fdt32_t *)(prop->val.val + m->offset)) = + cpu_to_fdt32(0xffffffff); + continue; + } + + phandle = get_node_phandle(dt, refnode); + *((fdt32_t *)(prop->val.val + m->offset)) = cpu_to_fdt32(phandle); + + reference_node(refnode); + } + } +} +ERROR(phandle_references, fixup_phandle_references, NULL, + &duplicate_node_names, &explicit_phandles); + +static void fixup_path_references(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct node *dt = dti->dt; + struct property *prop; + + for_each_property(node, prop) { + struct marker *m = prop->val.markers; + struct node *refnode; + char *path; + + for_each_marker_of_type(m, REF_PATH) { + assert(m->offset <= prop->val.len); + + refnode = get_node_by_ref(dt, m->ref); + if (!refnode) { + FAIL(c, dti, node, "Reference to non-existent node or label \"%s\"\n", + m->ref); + continue; + } + + path = refnode->fullpath; + prop->val = data_insert_at_marker(prop->val, m, path, + strlen(path) + 1); + + reference_node(refnode); + } + } +} +ERROR(path_references, fixup_path_references, NULL, &duplicate_node_names); + +static void fixup_omit_unused_nodes(struct check *c, struct dt_info *dti, + struct node *node) +{ + if (generate_symbols && node->labels) + return; + if (node->omit_if_unused && !node->is_referenced) + delete_node(node); +} +ERROR(omit_unused_nodes, fixup_omit_unused_nodes, NULL, &phandle_references, &path_references); + +/* + * Semantic checks + */ +WARNING_IF_NOT_CELL(address_cells_is_cell, "#address-cells"); +WARNING_IF_NOT_CELL(size_cells_is_cell, "#size-cells"); + +WARNING_IF_NOT_STRING(device_type_is_string, "device_type"); +WARNING_IF_NOT_STRING(model_is_string, "model"); +WARNING_IF_NOT_STRING(status_is_string, "status"); +WARNING_IF_NOT_STRING(label_is_string, "label"); + +WARNING_IF_NOT_STRING_LIST(compatible_is_string_list, "compatible"); + +static void check_names_is_string_list(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + for_each_property(node, prop) { + if (!strends(prop->name, "-names")) + continue; + + c->data = prop->name; + check_is_string_list(c, dti, node); + } +} +WARNING(names_is_string_list, check_names_is_string_list, NULL); + +static void check_alias_paths(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + if (!streq(node->name, "aliases")) + return; + + for_each_property(node, prop) { + if (streq(prop->name, "phandle") + || streq(prop->name, "linux,phandle")) { + continue; + } + + /* This check does not work for overlays with external paths */ + if (!(dti->dtsflags & DTSF_PLUGIN) && + (!prop->val.val || !get_node_by_path(dti->dt, prop->val.val))) { + FAIL_PROP(c, dti, node, prop, "aliases property is not a valid node (%s)", + prop->val.val); + continue; + } + + if (strspn(prop->name, LOWERCASE DIGITS "-") != strlen(prop->name)) + FAIL(c, dti, node, "aliases property name must include only lowercase and '-'"); + } +} +WARNING(alias_paths, check_alias_paths, NULL); + +static void fixup_addr_size_cells(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + node->addr_cells = -1; + node->size_cells = -1; + + prop = get_property(node, "#address-cells"); + if (prop) + node->addr_cells = propval_cell(prop); + + prop = get_property(node, "#size-cells"); + if (prop) + node->size_cells = propval_cell(prop); +} +WARNING(addr_size_cells, fixup_addr_size_cells, NULL, + &address_cells_is_cell, &size_cells_is_cell); + +#define node_addr_cells(n) \ + (((n)->addr_cells == -1) ? 2 : (n)->addr_cells) +#define node_size_cells(n) \ + (((n)->size_cells == -1) ? 1 : (n)->size_cells) + +static void check_reg_format(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + int addr_cells, size_cells, entrylen; + + prop = get_property(node, "reg"); + if (!prop) + return; /* No "reg", that's fine */ + + if (!node->parent) { + FAIL(c, dti, node, "Root node has a \"reg\" property"); + return; + } + + if (prop->val.len == 0) + FAIL_PROP(c, dti, node, prop, "property is empty"); + + addr_cells = node_addr_cells(node->parent); + size_cells = node_size_cells(node->parent); + entrylen = (addr_cells + size_cells) * sizeof(cell_t); + + if (!is_multiple_of(prop->val.len, entrylen)) + FAIL_PROP(c, dti, node, prop, "property has invalid length (%d bytes) " + "(#address-cells == %d, #size-cells == %d)", + prop->val.len, addr_cells, size_cells); +} +WARNING(reg_format, check_reg_format, NULL, &addr_size_cells); + +static void check_ranges_format(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + int c_addr_cells, p_addr_cells, c_size_cells, p_size_cells, entrylen; + const char *ranges = c->data; + + prop = get_property(node, ranges); + if (!prop) + return; + + if (!node->parent) { + FAIL_PROP(c, dti, node, prop, "Root node has a \"%s\" property", + ranges); + return; + } + + p_addr_cells = node_addr_cells(node->parent); + p_size_cells = node_size_cells(node->parent); + c_addr_cells = node_addr_cells(node); + c_size_cells = node_size_cells(node); + entrylen = (p_addr_cells + c_addr_cells + c_size_cells) * sizeof(cell_t); + + if (prop->val.len == 0) { + if (p_addr_cells != c_addr_cells) + FAIL_PROP(c, dti, node, prop, "empty \"%s\" property but its " + "#address-cells (%d) differs from %s (%d)", + ranges, c_addr_cells, node->parent->fullpath, + p_addr_cells); + if (p_size_cells != c_size_cells) + FAIL_PROP(c, dti, node, prop, "empty \"%s\" property but its " + "#size-cells (%d) differs from %s (%d)", + ranges, c_size_cells, node->parent->fullpath, + p_size_cells); + } else if (!is_multiple_of(prop->val.len, entrylen)) { + FAIL_PROP(c, dti, node, prop, "\"%s\" property has invalid length (%d bytes) " + "(parent #address-cells == %d, child #address-cells == %d, " + "#size-cells == %d)", ranges, prop->val.len, + p_addr_cells, c_addr_cells, c_size_cells); + } +} +WARNING(ranges_format, check_ranges_format, "ranges", &addr_size_cells); +WARNING(dma_ranges_format, check_ranges_format, "dma-ranges", &addr_size_cells); + +static const struct bus_type pci_bus = { + .name = "PCI", +}; + +static void check_pci_bridge(struct check *c, struct dt_info *dti, struct node *node) +{ + struct property *prop; + cell_t *cells; + + prop = get_property(node, "device_type"); + if (!prop || !streq(prop->val.val, "pci")) + return; + + node->bus = &pci_bus; + + if (!strprefixeq(node->name, node->basenamelen, "pci") && + !strprefixeq(node->name, node->basenamelen, "pcie")) + FAIL(c, dti, node, "node name is not \"pci\" or \"pcie\""); + + prop = get_property(node, "ranges"); + if (!prop) + FAIL(c, dti, node, "missing ranges for PCI bridge (or not a bridge)"); + + if (node_addr_cells(node) != 3) + FAIL(c, dti, node, "incorrect #address-cells for PCI bridge"); + if (node_size_cells(node) != 2) + FAIL(c, dti, node, "incorrect #size-cells for PCI bridge"); + + prop = get_property(node, "bus-range"); + if (!prop) + return; + + if (prop->val.len != (sizeof(cell_t) * 2)) { + FAIL_PROP(c, dti, node, prop, "value must be 2 cells"); + return; + } + cells = (cell_t *)prop->val.val; + if (fdt32_to_cpu(cells[0]) > fdt32_to_cpu(cells[1])) + FAIL_PROP(c, dti, node, prop, "1st cell must be less than or equal to 2nd cell"); + if (fdt32_to_cpu(cells[1]) > 0xff) + FAIL_PROP(c, dti, node, prop, "maximum bus number must be less than 256"); +} +WARNING(pci_bridge, check_pci_bridge, NULL, + &device_type_is_string, &addr_size_cells); + +static void check_pci_device_bus_num(struct check *c, struct dt_info *dti, struct node *node) +{ + struct property *prop; + unsigned int bus_num, min_bus, max_bus; + cell_t *cells; + + if (!node->parent || (node->parent->bus != &pci_bus)) + return; + + prop = get_property(node, "reg"); + if (!prop) + return; + + cells = (cell_t *)prop->val.val; + bus_num = (fdt32_to_cpu(cells[0]) & 0x00ff0000) >> 16; + + prop = get_property(node->parent, "bus-range"); + if (!prop) { + min_bus = max_bus = 0; + } else { + cells = (cell_t *)prop->val.val; + min_bus = fdt32_to_cpu(cells[0]); + max_bus = fdt32_to_cpu(cells[1]); + } + if ((bus_num < min_bus) || (bus_num > max_bus)) + FAIL_PROP(c, dti, node, prop, "PCI bus number %d out of range, expected (%d - %d)", + bus_num, min_bus, max_bus); +} +WARNING(pci_device_bus_num, check_pci_device_bus_num, NULL, ®_format, &pci_bridge); + +static void check_pci_device_reg(struct check *c, struct dt_info *dti, struct node *node) +{ + struct property *prop; + const char *unitname = get_unitname(node); + char unit_addr[5]; + unsigned int dev, func, reg; + cell_t *cells; + + if (!node->parent || (node->parent->bus != &pci_bus)) + return; + + prop = get_property(node, "reg"); + if (!prop) + return; + + cells = (cell_t *)prop->val.val; + if (cells[1] || cells[2]) + FAIL_PROP(c, dti, node, prop, "PCI reg config space address cells 2 and 3 must be 0"); + + reg = fdt32_to_cpu(cells[0]); + dev = (reg & 0xf800) >> 11; + func = (reg & 0x700) >> 8; + + if (reg & 0xff000000) + FAIL_PROP(c, dti, node, prop, "PCI reg address is not configuration space"); + if (reg & 0x000000ff) + FAIL_PROP(c, dti, node, prop, "PCI reg config space address register number must be 0"); + + if (func == 0) { + snprintf(unit_addr, sizeof(unit_addr), "%x", dev); + if (streq(unitname, unit_addr)) + return; + } + + snprintf(unit_addr, sizeof(unit_addr), "%x,%x", dev, func); + if (streq(unitname, unit_addr)) + return; + + FAIL(c, dti, node, "PCI unit address format error, expected \"%s\"", + unit_addr); +} +WARNING(pci_device_reg, check_pci_device_reg, NULL, ®_format, &pci_bridge); + +static const struct bus_type simple_bus = { + .name = "simple-bus", +}; + +static bool node_is_compatible(struct node *node, const char *compat) +{ + struct property *prop; + const char *str, *end; + + prop = get_property(node, "compatible"); + if (!prop) + return false; + + for (str = prop->val.val, end = str + prop->val.len; str < end; + str += strnlen(str, end - str) + 1) { + if (streq(str, compat)) + return true; + } + return false; +} + +static void check_simple_bus_bridge(struct check *c, struct dt_info *dti, struct node *node) +{ + if (node_is_compatible(node, "simple-bus")) + node->bus = &simple_bus; +} +WARNING(simple_bus_bridge, check_simple_bus_bridge, NULL, + &addr_size_cells, &compatible_is_string_list); + +static void check_simple_bus_reg(struct check *c, struct dt_info *dti, struct node *node) +{ + struct property *prop; + const char *unitname = get_unitname(node); + char unit_addr[17]; + unsigned int size; + uint64_t reg = 0; + cell_t *cells = NULL; + + if (!node->parent || (node->parent->bus != &simple_bus)) + return; + + prop = get_property(node, "reg"); + if (prop) + cells = (cell_t *)prop->val.val; + else { + prop = get_property(node, "ranges"); + if (prop && prop->val.len) + /* skip of child address */ + cells = ((cell_t *)prop->val.val) + node_addr_cells(node); + } + + if (!cells) { + if (node->parent->parent && !(node->bus == &simple_bus)) + FAIL(c, dti, node, "missing or empty reg/ranges property"); + return; + } + + size = node_addr_cells(node->parent); + while (size--) + reg = (reg << 32) | fdt32_to_cpu(*(cells++)); + + snprintf(unit_addr, sizeof(unit_addr), "%"PRIx64, reg); + if (!streq(unitname, unit_addr)) + FAIL(c, dti, node, "simple-bus unit address format error, expected \"%s\"", + unit_addr); +} +WARNING(simple_bus_reg, check_simple_bus_reg, NULL, ®_format, &simple_bus_bridge); + +static const struct bus_type i2c_bus = { + .name = "i2c-bus", +}; + +static void check_i2c_bus_bridge(struct check *c, struct dt_info *dti, struct node *node) +{ + if (strprefixeq(node->name, node->basenamelen, "i2c-bus") || + strprefixeq(node->name, node->basenamelen, "i2c-arb")) { + node->bus = &i2c_bus; + } else if (strprefixeq(node->name, node->basenamelen, "i2c")) { + struct node *child; + for_each_child(node, child) { + if (strprefixeq(child->name, child->basenamelen, "i2c-bus")) + return; + } + node->bus = &i2c_bus; + } else + return; + + if (!node->children) + return; + + if (node_addr_cells(node) != 1) + FAIL(c, dti, node, "incorrect #address-cells for I2C bus"); + if (node_size_cells(node) != 0) + FAIL(c, dti, node, "incorrect #size-cells for I2C bus"); + +} +WARNING(i2c_bus_bridge, check_i2c_bus_bridge, NULL, &addr_size_cells); + +#define I2C_OWN_SLAVE_ADDRESS (1U << 30) +#define I2C_TEN_BIT_ADDRESS (1U << 31) + +static void check_i2c_bus_reg(struct check *c, struct dt_info *dti, struct node *node) +{ + struct property *prop; + const char *unitname = get_unitname(node); + char unit_addr[17]; + uint32_t reg = 0; + int len; + cell_t *cells = NULL; + + if (!node->parent || (node->parent->bus != &i2c_bus)) + return; + + prop = get_property(node, "reg"); + if (prop) + cells = (cell_t *)prop->val.val; + + if (!cells) { + FAIL(c, dti, node, "missing or empty reg property"); + return; + } + + reg = fdt32_to_cpu(*cells); + /* Ignore I2C_OWN_SLAVE_ADDRESS */ + reg &= ~I2C_OWN_SLAVE_ADDRESS; + snprintf(unit_addr, sizeof(unit_addr), "%x", reg); + if (!streq(unitname, unit_addr)) + FAIL(c, dti, node, "I2C bus unit address format error, expected \"%s\"", + unit_addr); + + for (len = prop->val.len; len > 0; len -= 4) { + reg = fdt32_to_cpu(*(cells++)); + /* Ignore I2C_OWN_SLAVE_ADDRESS */ + reg &= ~I2C_OWN_SLAVE_ADDRESS; + + 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) + 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); + } +} +WARNING(i2c_bus_reg, check_i2c_bus_reg, NULL, ®_format, &i2c_bus_bridge); + +static const struct bus_type spi_bus = { + .name = "spi-bus", +}; + +static void check_spi_bus_bridge(struct check *c, struct dt_info *dti, struct node *node) +{ + int spi_addr_cells = 1; + + if (strprefixeq(node->name, node->basenamelen, "spi")) { + node->bus = &spi_bus; + } else { + /* Try to detect SPI buses which don't have proper node name */ + struct node *child; + + if (node_addr_cells(node) != 1 || node_size_cells(node) != 0) + return; + + for_each_child(node, child) { + struct property *prop; + for_each_property(child, prop) { + if (strstarts(prop->name, "spi-")) { + node->bus = &spi_bus; + break; + } + } + if (node->bus == &spi_bus) + break; + } + + if (node->bus == &spi_bus && get_property(node, "reg")) + FAIL(c, dti, node, "node name for SPI buses should be 'spi'"); + } + if (node->bus != &spi_bus || !node->children) + return; + + if (get_property(node, "spi-slave")) + spi_addr_cells = 0; + if (node_addr_cells(node) != spi_addr_cells) + FAIL(c, dti, node, "incorrect #address-cells for SPI bus"); + if (node_size_cells(node) != 0) + FAIL(c, dti, node, "incorrect #size-cells for SPI bus"); + +} +WARNING(spi_bus_bridge, check_spi_bus_bridge, NULL, &addr_size_cells); + +static void check_spi_bus_reg(struct check *c, struct dt_info *dti, struct node *node) +{ + struct property *prop; + const char *unitname = get_unitname(node); + char unit_addr[9]; + uint32_t reg = 0; + cell_t *cells = NULL; + + if (!node->parent || (node->parent->bus != &spi_bus)) + return; + + if (get_property(node->parent, "spi-slave")) + return; + + prop = get_property(node, "reg"); + if (prop) + cells = (cell_t *)prop->val.val; + + if (!cells) { + FAIL(c, dti, node, "missing or empty reg property"); + return; + } + + reg = fdt32_to_cpu(*cells); + snprintf(unit_addr, sizeof(unit_addr), "%x", reg); + if (!streq(unitname, unit_addr)) + FAIL(c, dti, node, "SPI bus unit address format error, expected \"%s\"", + unit_addr); +} +WARNING(spi_bus_reg, check_spi_bus_reg, NULL, ®_format, &spi_bus_bridge); + +static void check_unit_address_format(struct check *c, struct dt_info *dti, + struct node *node) +{ + const char *unitname = get_unitname(node); + + if (node->parent && node->parent->bus) + return; + + if (!unitname[0]) + return; + + if (!strncmp(unitname, "0x", 2)) { + FAIL(c, dti, node, "unit name should not have leading \"0x\""); + /* skip over 0x for next test */ + unitname += 2; + } + 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, + &node_name_format, &pci_bridge, &simple_bus_bridge); + +/* + * Style checks + */ +static void check_avoid_default_addr_size(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *reg, *ranges; + + if (!node->parent) + return; /* Ignore root node */ + + reg = get_property(node, "reg"); + ranges = get_property(node, "ranges"); + + if (!reg && !ranges) + return; + + if (node->parent->addr_cells == -1) + FAIL(c, dti, node, "Relying on default #address-cells value"); + + if (node->parent->size_cells == -1) + FAIL(c, dti, node, "Relying on default #size-cells value"); +} +WARNING(avoid_default_addr_size, check_avoid_default_addr_size, NULL, + &addr_size_cells); + +static void check_avoid_unnecessary_addr_size(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct node *child; + + if (!node->parent || node->addr_cells < 0 || node->size_cells < 0) + return; + + if (get_property(node, "ranges") || get_property(node, "dma-ranges") || !node->children) + return; + + for_each_child(node, child) { + /* + * Even if the child devices' address space is not mapped into + * the parent bus (no 'ranges' property on node), children can + * still have registers on a local bus, or map local addresses + * to another subordinate address space. The properties on the + * child nodes then make #address-cells/#size-cells necessary: + */ + if (get_property(child, "reg") || get_property(child, "ranges")) + return; + } + + FAIL(c, dti, node, "unnecessary #address-cells/#size-cells without \"ranges\", \"dma-ranges\" or child \"reg\" or \"ranges\" property"); +} +WARNING(avoid_unnecessary_addr_size, check_avoid_unnecessary_addr_size, NULL, &avoid_default_addr_size); + +static bool node_is_disabled(struct node *node) +{ + struct property *prop; + + prop = get_property(node, "status"); + if (prop) { + char *str = prop->val.val; + if (streq("disabled", str)) + return true; + } + + return false; +} + +static void check_unique_unit_address_common(struct check *c, + struct dt_info *dti, + struct node *node, + bool disable_check) +{ + struct node *childa; + + if (node->addr_cells < 0 || node->size_cells < 0) + return; + + if (!node->children) + return; + + for_each_child(node, childa) { + struct node *childb; + const char *addr_a = get_unitname(childa); + + if (!strlen(addr_a)) + continue; + + if (disable_check && node_is_disabled(childa)) + continue; + + for_each_child(node, childb) { + const char *addr_b = get_unitname(childb); + if (childa == childb) + break; + + if (disable_check && node_is_disabled(childb)) + continue; + + if (streq(addr_a, addr_b)) + FAIL(c, dti, childb, "duplicate unit-address (also used in node %s)", childa->fullpath); + } + } +} + +static void check_unique_unit_address(struct check *c, struct dt_info *dti, + struct node *node) +{ + check_unique_unit_address_common(c, dti, node, false); +} +WARNING(unique_unit_address, check_unique_unit_address, NULL, &avoid_default_addr_size); + +static void check_unique_unit_address_if_enabled(struct check *c, struct dt_info *dti, + struct node *node) +{ + check_unique_unit_address_common(c, dti, node, true); +} +CHECK_ENTRY(unique_unit_address_if_enabled, check_unique_unit_address_if_enabled, + NULL, false, false, &avoid_default_addr_size); + +static void check_obsolete_chosen_interrupt_controller(struct check *c, + struct dt_info *dti, + struct node *node) +{ + struct node *dt = dti->dt; + struct node *chosen; + struct property *prop; + + if (node != dt) + return; + + + chosen = get_node_by_path(dt, "/chosen"); + if (!chosen) + return; + + prop = get_property(chosen, "interrupt-controller"); + if (prop) + FAIL_PROP(c, dti, node, prop, + "/chosen has obsolete \"interrupt-controller\" property"); +} +WARNING(obsolete_chosen_interrupt_controller, + check_obsolete_chosen_interrupt_controller, NULL); + +static void check_chosen_node_is_root(struct check *c, struct dt_info *dti, + struct node *node) +{ + if (!streq(node->name, "chosen")) + return; + + if (node->parent != dti->dt) + FAIL(c, dti, node, "chosen node must be at root node"); +} +WARNING(chosen_node_is_root, check_chosen_node_is_root, NULL); + +static void check_chosen_node_bootargs(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + if (!streq(node->name, "chosen")) + return; + + prop = get_property(node, "bootargs"); + if (!prop) + return; + + c->data = prop->name; + check_is_string(c, dti, node); +} +WARNING(chosen_node_bootargs, check_chosen_node_bootargs, NULL); + +static void check_chosen_node_stdout_path(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + if (!streq(node->name, "chosen")) + return; + + prop = get_property(node, "stdout-path"); + if (!prop) { + prop = get_property(node, "linux,stdout-path"); + if (!prop) + return; + FAIL_PROP(c, dti, node, prop, "Use 'stdout-path' instead"); + } + + c->data = prop->name; + check_is_string(c, dti, node); +} +WARNING(chosen_node_stdout_path, check_chosen_node_stdout_path, NULL); + +struct provider { + const char *prop_name; + const char *cell_name; + bool optional; +}; + +static void check_property_phandle_args(struct check *c, + struct dt_info *dti, + struct node *node, + struct property *prop, + const struct provider *provider) +{ + struct node *root = dti->dt; + unsigned int cell, cellsize = 0; + + if (!is_multiple_of(prop->val.len, sizeof(cell_t))) { + FAIL_PROP(c, dti, node, prop, + "property size (%d) is invalid, expected multiple of %zu", + prop->val.len, sizeof(cell_t)); + return; + } + + for (cell = 0; cell < prop->val.len / sizeof(cell_t); cell += cellsize + 1) { + struct node *provider_node; + struct property *cellprop; + cell_t phandle; + unsigned int expected; + + phandle = propval_cell_n(prop, cell); + /* + * Some bindings use a cell value 0 or -1 to skip over optional + * entries when each index position has a specific definition. + */ + if (!phandle_is_valid(phandle)) { + /* Give up if this is an overlay with external references */ + if (dti->dtsflags & DTSF_PLUGIN) + break; + + cellsize = 0; + continue; + } + + /* If we have markers, verify the current cell is a phandle */ + if (prop->val.markers) { + struct marker *m = prop->val.markers; + for_each_marker_of_type(m, REF_PHANDLE) { + if (m->offset == (cell * sizeof(cell_t))) + break; + } + if (!m) + FAIL_PROP(c, dti, node, prop, + "cell %d is not a phandle reference", + cell); + } + + provider_node = get_node_by_phandle(root, phandle); + if (!provider_node) { + FAIL_PROP(c, dti, node, prop, + "Could not get phandle node for (cell %d)", + cell); + break; + } + + cellprop = get_property(provider_node, provider->cell_name); + if (cellprop) { + cellsize = propval_cell(cellprop); + } else if (provider->optional) { + cellsize = 0; + } else { + FAIL(c, dti, node, "Missing property '%s' in node %s or bad phandle (referred from %s[%d])", + provider->cell_name, + provider_node->fullpath, + prop->name, cell); + break; + } + + expected = (cell + cellsize + 1) * sizeof(cell_t); + if ((expected <= cell) || prop->val.len < expected) { + FAIL_PROP(c, dti, node, prop, + "property size (%d) too small for cell size %u", + prop->val.len, cellsize); + break; + } + } +} + +static void check_provider_cells_property(struct check *c, + struct dt_info *dti, + struct node *node) +{ + const struct provider *provider = c->data; + struct property *prop; + + prop = get_property(node, provider->prop_name); + if (!prop) + return; + + check_property_phandle_args(c, dti, node, prop, provider); +} +#define WARNING_PROPERTY_PHANDLE_CELLS(nm, propname, cells_name, ...) \ + static struct provider nm##_provider = { (propname), (cells_name), __VA_ARGS__ }; \ + WARNING_IF_NOT_CELL(nm##_is_cell, cells_name); \ + WARNING(nm##_property, check_provider_cells_property, &nm##_provider, &nm##_is_cell, &phandle_references); + +WARNING_PROPERTY_PHANDLE_CELLS(clocks, "clocks", "#clock-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(cooling_device, "cooling-device", "#cooling-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(dmas, "dmas", "#dma-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(hwlocks, "hwlocks", "#hwlock-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(interrupts_extended, "interrupts-extended", "#interrupt-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(io_channels, "io-channels", "#io-channel-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(iommus, "iommus", "#iommu-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(mboxes, "mboxes", "#mbox-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(msi_parent, "msi-parent", "#msi-cells", true); +WARNING_PROPERTY_PHANDLE_CELLS(mux_controls, "mux-controls", "#mux-control-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(phys, "phys", "#phy-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(power_domains, "power-domains", "#power-domain-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(pwms, "pwms", "#pwm-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(resets, "resets", "#reset-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(sound_dai, "sound-dai", "#sound-dai-cells"); +WARNING_PROPERTY_PHANDLE_CELLS(thermal_sensors, "thermal-sensors", "#thermal-sensor-cells"); + +static bool prop_is_gpio(struct property *prop) +{ + /* + * *-gpios and *-gpio can appear in property names, + * so skip over any false matches (only one known ATM) + */ + if (strends(prop->name, ",nr-gpios")) + return false; + + return strends(prop->name, "-gpios") || + streq(prop->name, "gpios") || + strends(prop->name, "-gpio") || + streq(prop->name, "gpio"); +} + +static void check_gpios_property(struct check *c, + struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + /* Skip GPIO hog nodes which have 'gpios' property */ + if (get_property(node, "gpio-hog")) + return; + + for_each_property(node, prop) { + struct provider provider; + + if (!prop_is_gpio(prop)) + continue; + + provider.prop_name = prop->name; + provider.cell_name = "#gpio-cells"; + provider.optional = false; + check_property_phandle_args(c, dti, node, prop, &provider); + } + +} +WARNING(gpios_property, check_gpios_property, NULL, &phandle_references); + +static void check_deprecated_gpio_property(struct check *c, + struct dt_info *dti, + struct node *node) +{ + struct property *prop; + + for_each_property(node, prop) { + if (!prop_is_gpio(prop)) + continue; + + if (!strends(prop->name, "gpio")) + continue; + + FAIL_PROP(c, dti, node, prop, + "'[*-]gpio' is deprecated, use '[*-]gpios' instead"); + } + +} +CHECK(deprecated_gpio_property, check_deprecated_gpio_property, NULL); + +static bool node_is_interrupt_provider(struct node *node) +{ + struct property *prop; + + prop = get_property(node, "interrupt-controller"); + if (prop) + return true; + + prop = get_property(node, "interrupt-map"); + if (prop) + return true; + + return false; +} + +static void check_interrupt_provider(struct check *c, + struct dt_info *dti, + struct node *node) +{ + struct property *prop; + bool irq_provider = node_is_interrupt_provider(node); + + prop = get_property(node, "#interrupt-cells"); + if (irq_provider && !prop) { + FAIL(c, dti, node, + "Missing '#interrupt-cells' in interrupt provider"); + return; + } + + if (!irq_provider && prop) { + FAIL(c, dti, node, + "'#interrupt-cells' found, but node is not an interrupt provider"); + return; + } +} +WARNING(interrupt_provider, check_interrupt_provider, NULL, &interrupts_extended_is_cell); + +static void check_interrupt_map(struct check *c, + struct dt_info *dti, + struct node *node) +{ + struct node *root = dti->dt; + struct property *prop, *irq_map_prop; + size_t cellsize, cell, map_cells; + + irq_map_prop = get_property(node, "interrupt-map"); + if (!irq_map_prop) + return; + + if (node->addr_cells < 0) { + FAIL(c, dti, node, + "Missing '#address-cells' in interrupt-map provider"); + return; + } + cellsize = node_addr_cells(node); + cellsize += propval_cell(get_property(node, "#interrupt-cells")); + + prop = get_property(node, "interrupt-map-mask"); + if (prop && (prop->val.len != (cellsize * sizeof(cell_t)))) + FAIL_PROP(c, dti, node, prop, + "property size (%d) is invalid, expected %zu", + prop->val.len, cellsize * sizeof(cell_t)); + + if (!is_multiple_of(irq_map_prop->val.len, sizeof(cell_t))) { + FAIL_PROP(c, dti, node, irq_map_prop, + "property size (%d) is invalid, expected multiple of %zu", + irq_map_prop->val.len, sizeof(cell_t)); + return; + } + + map_cells = irq_map_prop->val.len / sizeof(cell_t); + for (cell = 0; cell < map_cells; ) { + struct node *provider_node; + struct property *cellprop; + int phandle; + size_t parent_cellsize; + + if ((cell + cellsize) >= map_cells) { + FAIL_PROP(c, dti, node, irq_map_prop, + "property size (%d) too small, expected > %zu", + irq_map_prop->val.len, (cell + cellsize) * sizeof(cell_t)); + break; + } + cell += cellsize; + + phandle = propval_cell_n(irq_map_prop, cell); + if (!phandle_is_valid(phandle)) { + /* Give up if this is an overlay with external references */ + if (!(dti->dtsflags & DTSF_PLUGIN)) + FAIL_PROP(c, dti, node, irq_map_prop, + "Cell %zu is not a phandle(%d)", + cell, phandle); + break; + } + + provider_node = get_node_by_phandle(root, phandle); + if (!provider_node) { + FAIL_PROP(c, dti, node, irq_map_prop, + "Could not get phandle(%d) node for (cell %zu)", + phandle, cell); + break; + } + + cellprop = get_property(provider_node, "#interrupt-cells"); + if (cellprop) { + parent_cellsize = propval_cell(cellprop); + } else { + FAIL(c, dti, node, "Missing property '#interrupt-cells' in node %s or bad phandle (referred from interrupt-map[%zu])", + provider_node->fullpath, cell); + break; + } + + cellprop = get_property(provider_node, "#address-cells"); + if (cellprop) + parent_cellsize += propval_cell(cellprop); + else + FAIL_PROP(c, dti, node, irq_map_prop, + "Missing property '#address-cells' in node %s, using 0 as fallback", + provider_node->fullpath); + + 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); + +static void check_interrupts_property(struct check *c, + struct dt_info *dti, + struct node *node) +{ + struct node *root = dti->dt; + struct node *irq_node = NULL, *parent = node; + struct property *irq_prop, *prop = NULL; + cell_t irq_cells, phandle; + + irq_prop = get_property(node, "interrupts"); + if (!irq_prop) + return; + + if (!is_multiple_of(irq_prop->val.len, sizeof(cell_t))) + FAIL_PROP(c, dti, node, irq_prop, "size (%d) is invalid, expected multiple of %zu", + irq_prop->val.len, sizeof(cell_t)); + + while (parent && !prop) { + if (parent != node && node_is_interrupt_provider(parent)) { + irq_node = parent; + break; + } + + prop = get_property(parent, "interrupt-parent"); + if (prop) { + phandle = propval_cell(prop); + if (!phandle_is_valid(phandle)) { + /* Give up if this is an overlay with + * external references */ + if (dti->dtsflags & DTSF_PLUGIN) + return; + FAIL_PROP(c, dti, parent, prop, "Invalid phandle"); + continue; + } + + irq_node = get_node_by_phandle(root, phandle); + if (!irq_node) { + FAIL_PROP(c, dti, parent, prop, "Bad phandle"); + return; + } + if (!node_is_interrupt_provider(irq_node)) + FAIL(c, dti, irq_node, + "Missing interrupt-controller or interrupt-map property"); + + break; + } + + parent = parent->parent; + } + + if (!irq_node) { + FAIL(c, dti, node, "Missing interrupt-parent"); + return; + } + + prop = get_property(irq_node, "#interrupt-cells"); + if (!prop) { + /* We warn about that already in another test. */ + return; + } + + irq_cells = propval_cell(prop); + if (!is_multiple_of(irq_prop->val.len, irq_cells * sizeof(cell_t))) { + FAIL_PROP(c, dti, node, prop, + "size is (%d), expected multiple of %d", + irq_prop->val.len, (int)(irq_cells * sizeof(cell_t))); + } +} +WARNING(interrupts_property, check_interrupts_property, &phandle_references); + +static const struct bus_type graph_port_bus = { + .name = "graph-port", +}; + +static const struct bus_type graph_ports_bus = { + .name = "graph-ports", +}; + +static void check_graph_nodes(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct node *child; + + for_each_child(node, child) { + if (!(strprefixeq(child->name, child->basenamelen, "endpoint") || + 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 */ + if (!node->parent->bus && + (streq(node->parent->name, "ports") || get_property(node, "reg"))) + node->parent->bus = &graph_ports_bus; + + break; + } + +} +WARNING(graph_nodes, check_graph_nodes, NULL); + +static void check_graph_reg(struct check *c, struct dt_info *dti, + struct node *node) +{ + char unit_addr[9]; + const char *unitname = get_unitname(node); + struct property *prop; + + prop = get_property(node, "reg"); + if (!prop || !unitname) + return; + + if (!(prop->val.val && prop->val.len == sizeof(cell_t))) { + FAIL(c, dti, node, "graph node malformed 'reg' property"); + return; + } + + snprintf(unit_addr, sizeof(unit_addr), "%x", propval_cell(prop)); + if (!streq(unitname, unit_addr)) + FAIL(c, dti, node, "graph node unit address error, expected \"%s\"", + unit_addr); + + if (node->parent->addr_cells != 1) + FAIL_PROP(c, dti, node, get_property(node, "#address-cells"), + "graph node '#address-cells' is %d, must be 1", + node->parent->addr_cells); + if (node->parent->size_cells != 0) + FAIL_PROP(c, dti, node, get_property(node, "#size-cells"), + "graph node '#size-cells' is %d, must be 0", + node->parent->size_cells); +} + +static void check_graph_port(struct check *c, struct dt_info *dti, + struct node *node) +{ + 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'"); +} +WARNING(graph_port, check_graph_port, NULL, &graph_nodes); + +static struct node *get_remote_endpoint(struct check *c, struct dt_info *dti, + struct node *endpoint) +{ + cell_t phandle; + struct node *node; + struct property *prop; + + prop = get_property(endpoint, "remote-endpoint"); + if (!prop) + return NULL; + + phandle = propval_cell(prop); + /* Give up if this is an overlay with external references */ + if (!phandle_is_valid(phandle)) + return NULL; + + node = get_node_by_phandle(dti->dt, phandle); + if (!node) + FAIL_PROP(c, dti, endpoint, prop, "graph phandle is not valid"); + + return node; +} + +static void check_graph_endpoint(struct check *c, struct dt_info *dti, + struct node *node) +{ + struct node *remote_node; + + 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'"); + + remote_node = get_remote_endpoint(c, dti, node); + if (!remote_node) + return; + + if (get_remote_endpoint(c, dti, remote_node) != node) + FAIL(c, dti, node, "graph connection to node '%s' is not bidirectional", + remote_node->fullpath); +} +WARNING(graph_endpoint, check_graph_endpoint, NULL, &graph_nodes); + +static struct check *check_table[] = { + &duplicate_node_names, &duplicate_property_names, + &node_name_chars, &node_name_format, &node_name_not_empty, &property_name_chars, + &name_is_string, &name_properties, &node_name_vs_property_name, + + &duplicate_label, + + &explicit_phandles, + &phandle_references, &path_references, + &omit_unused_nodes, + + &address_cells_is_cell, &size_cells_is_cell, + &device_type_is_string, &model_is_string, &status_is_string, + &label_is_string, + + &compatible_is_string_list, &names_is_string_list, + + &property_name_chars_strict, + &node_name_chars_strict, + + &addr_size_cells, ®_format, &ranges_format, &dma_ranges_format, + + &unit_address_vs_reg, + &unit_address_format, + + &pci_bridge, + &pci_device_reg, + &pci_device_bus_num, + + &simple_bus_bridge, + &simple_bus_reg, + + &i2c_bus_bridge, + &i2c_bus_reg, + + &spi_bus_bridge, + &spi_bus_reg, + + &avoid_default_addr_size, + &avoid_unnecessary_addr_size, + &unique_unit_address, + &unique_unit_address_if_enabled, + &obsolete_chosen_interrupt_controller, + &chosen_node_is_root, &chosen_node_bootargs, &chosen_node_stdout_path, + + &clocks_property, + &clocks_is_cell, + &cooling_device_property, + &cooling_device_is_cell, + &dmas_property, + &dmas_is_cell, + &hwlocks_property, + &hwlocks_is_cell, + &interrupts_extended_property, + &interrupts_extended_is_cell, + &io_channels_property, + &io_channels_is_cell, + &iommus_property, + &iommus_is_cell, + &mboxes_property, + &mboxes_is_cell, + &msi_parent_property, + &msi_parent_is_cell, + &mux_controls_property, + &mux_controls_is_cell, + &phys_property, + &phys_is_cell, + &power_domains_property, + &power_domains_is_cell, + &pwms_property, + &pwms_is_cell, + &resets_property, + &resets_is_cell, + &sound_dai_property, + &sound_dai_is_cell, + &thermal_sensors_property, + &thermal_sensors_is_cell, + + &deprecated_gpio_property, + &gpios_property, + &interrupts_property, + &interrupt_provider, + &interrupt_map, + + &alias_paths, + + &graph_nodes, &graph_port, &graph_endpoint, + + &always_fail, +}; + +static void enable_warning_error(struct check *c, bool warn, bool error) +{ + int i; + + /* Raising level, also raise it for prereqs */ + if ((warn && !c->warn) || (error && !c->error)) + for (i = 0; i < c->num_prereqs; i++) + enable_warning_error(c->prereq[i], warn, error); + + c->warn = c->warn || warn; + c->error = c->error || error; +} + +static void disable_warning_error(struct check *c, bool warn, bool error) +{ + unsigned int i; + + /* Lowering level, also lower it for things this is the prereq + * for */ + if ((warn && c->warn) || (error && c->error)) { + for (i = 0; i < ARRAY_SIZE(check_table); i++) { + struct check *cc = check_table[i]; + int j; + + for (j = 0; j < cc->num_prereqs; j++) + if (cc->prereq[j] == c) + disable_warning_error(cc, warn, error); + } + } + + c->warn = c->warn && !warn; + c->error = c->error && !error; +} + +void parse_checks_option(bool warn, bool error, const char *arg) +{ + unsigned int i; + const char *name = arg; + bool enable = true; + + if ((strncmp(arg, "no-", 3) == 0) + || (strncmp(arg, "no_", 3) == 0)) { + name = arg + 3; + enable = false; + } + + for (i = 0; i < ARRAY_SIZE(check_table); i++) { + struct check *c = check_table[i]; + + if (streq(c->name, name)) { + if (enable) + enable_warning_error(c, warn, error); + else + disable_warning_error(c, warn, error); + return; + } + } + + die("Unrecognized check name \"%s\"\n", name); +} + +void process_checks(bool force, struct dt_info *dti) +{ + unsigned int i; + int error = 0; + + for (i = 0; i < ARRAY_SIZE(check_table); i++) { + struct check *c = check_table[i]; + + if (c->warn || c->error) + error = error || run_check(c, dti); + } + + if (error) { + if (!force) { + fprintf(stderr, "ERROR: Input tree has errors, aborting " + "(use -f to force output)\n"); + exit(2); + } else if (quiet < 3) { + fprintf(stderr, "Warning: Input tree has errors, " + "output forced\n"); + } + } +} diff --git a/scripts/dtc/data.c b/scripts/dtc/data.c new file mode 100644 index 000000000..5b25aa060 --- /dev/null +++ b/scripts/dtc/data.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include "dtc.h" + +void data_free(struct data d) +{ + struct marker *m, *nm; + + m = d.markers; + while (m) { + nm = m->next; + free(m->ref); + free(m); + m = nm; + } + + if (d.val) + free(d.val); +} + +struct data data_grow_for(struct data d, unsigned int xlen) +{ + struct data nd; + unsigned int newsize; + + if (xlen == 0) + return d; + + nd = d; + + newsize = xlen; + + while ((d.len + xlen) > newsize) + newsize *= 2; + + nd.val = xrealloc(d.val, newsize); + + return nd; +} + +struct data data_copy_mem(const char *mem, int len) +{ + struct data d; + + d = data_grow_for(empty_data, len); + + d.len = len; + memcpy(d.val, mem, len); + + return d; +} + +struct data data_copy_escape_string(const char *s, int len) +{ + int i = 0; + struct data d; + char *q; + + d = data_add_marker(empty_data, TYPE_STRING, NULL); + d = data_grow_for(d, len + 1); + + q = d.val; + while (i < len) { + char c = s[i++]; + + if (c == '\\') + c = get_escape_char(s, &i); + + q[d.len++] = c; + } + + q[d.len++] = '\0'; + return d; +} + +struct data data_copy_file(FILE *f, size_t maxlen) +{ + struct data d = empty_data; + + d = data_add_marker(d, TYPE_NONE, NULL); + while (!feof(f) && (d.len < maxlen)) { + size_t chunksize, ret; + + if (maxlen == (size_t)-1) + chunksize = 4096; + else + chunksize = maxlen - d.len; + + d = data_grow_for(d, chunksize); + ret = fread(d.val + d.len, 1, chunksize, f); + + if (ferror(f)) + die("Error reading file into data: %s", strerror(errno)); + + if (d.len + ret < d.len) + die("Overflow reading file into data\n"); + + d.len += ret; + } + + return d; +} + +struct data data_append_data(struct data d, const void *p, int len) +{ + d = data_grow_for(d, len); + memcpy(d.val + d.len, p, len); + d.len += len; + return d; +} + +struct data data_insert_at_marker(struct data d, struct marker *m, + const void *p, int len) +{ + d = data_grow_for(d, len); + memmove(d.val + m->offset + len, d.val + m->offset, d.len - m->offset); + memcpy(d.val + m->offset, p, len); + d.len += len; + + /* Adjust all markers after the one we're inserting at */ + m = m->next; + for_each_marker(m) + m->offset += len; + return d; +} + +static struct data data_append_markers(struct data d, struct marker *m) +{ + struct marker **mp = &d.markers; + + /* Find the end of the markerlist */ + while (*mp) + mp = &((*mp)->next); + *mp = m; + return d; +} + +struct data data_merge(struct data d1, struct data d2) +{ + struct data d; + struct marker *m2 = d2.markers; + + d = data_append_markers(data_append_data(d1, d2.val, d2.len), m2); + + /* Adjust for the length of d1 */ + for_each_marker(m2) + m2->offset += d1.len; + + d2.markers = NULL; /* So data_free() doesn't clobber them */ + data_free(d2); + + return d; +} + +struct data data_append_integer(struct data d, uint64_t value, int bits) +{ + uint8_t value_8; + fdt16_t value_16; + fdt32_t value_32; + fdt64_t value_64; + + switch (bits) { + case 8: + value_8 = value; + return data_append_data(d, &value_8, 1); + + case 16: + value_16 = cpu_to_fdt16(value); + return data_append_data(d, &value_16, 2); + + case 32: + value_32 = cpu_to_fdt32(value); + return data_append_data(d, &value_32, 4); + + case 64: + value_64 = cpu_to_fdt64(value); + return data_append_data(d, &value_64, 8); + + default: + die("Invalid literal size (%d)\n", bits); + } +} + +struct data data_append_re(struct data d, uint64_t address, uint64_t size) +{ + struct fdt_reserve_entry re; + + re.address = cpu_to_fdt64(address); + re.size = cpu_to_fdt64(size); + + return data_append_data(d, &re, sizeof(re)); +} + +struct data data_append_cell(struct data d, cell_t word) +{ + return data_append_integer(d, word, sizeof(word) * 8); +} + +struct data data_append_addr(struct data d, uint64_t addr) +{ + return data_append_integer(d, addr, sizeof(addr) * 8); +} + +struct data data_append_byte(struct data d, uint8_t byte) +{ + return data_append_data(d, &byte, 1); +} + +struct data data_append_zeroes(struct data d, int len) +{ + d = data_grow_for(d, len); + + memset(d.val + d.len, 0, len); + d.len += len; + return d; +} + +struct data data_append_align(struct data d, int align) +{ + int newlen = ALIGN(d.len, align); + return data_append_zeroes(d, newlen - d.len); +} + +struct data data_add_marker(struct data d, enum markertype type, char *ref) +{ + struct marker *m; + + m = alloc_marker(d.len, type, ref); + + return data_append_markers(d, m); +} + +bool data_is_one_string(struct data d) +{ + int i; + int len = d.len; + + if (len == 0) + return false; + + for (i = 0; i < len-1; i++) + if (d.val[i] == '\0') + return false; + + if (d.val[len-1] != '\0') + return false; + + return true; +} + +struct data data_insert_data(struct data d, struct marker *m, struct data old) +{ + unsigned int offset = m->offset; + struct marker *next = m->next; + struct marker *marker; + struct data new_data; + char *ref; + + new_data = data_insert_at_marker(d, m, old.val, old.len); + + /* Copy all markers from old value */ + marker = old.markers; + for_each_marker(marker) { + ref = NULL; + + if (marker->ref) + ref = xstrdup(marker->ref); + + m->next = alloc_marker(marker->offset + offset, marker->type, + ref); + m = m->next; + } + m->next = next; + + return new_data; +} + +struct marker *alloc_marker(unsigned int offset, enum markertype type, + char *ref) +{ + struct marker *m; + + m = xmalloc(sizeof(*m)); + m->offset = offset; + m->type = type; + m->ref = ref; + m->next = NULL; + + return m; +} diff --git a/scripts/dtc/dt-check-style b/scripts/dtc/dt-check-style new file mode 100755 index 000000000..96deffc0d --- /dev/null +++ b/scripts/dtc/dt-check-style @@ -0,0 +1,1205 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# +# Check DTS coding style on YAML binding examples and on +# .dts/.dtsi/.dtso source files. Enforces rules from +# Documentation/devicetree/bindings/dts-coding-style.rst. +# +# Two modes: +# --mode=relaxed (default) +# Only rules that produce zero warnings on the current tree. +# Suitable for dt_binding_check. +# --mode=strict +# All rules. Required for new submissions. +# +# Two input types (auto-detected by file extension): +# *.yaml -- DT binding; check each example block +# *.dts/*.dtsi/*.dtso -- DTS source; whole file is one block +# +# Rules are declared in a registry (see RULES below); each rule is +# tagged with the lowest mode that runs it. Promoting a rule from +# 'strict' to 'relaxed' is a one-line change. + +import argparse +import re +import sys +from enum import Enum, auto + +import ruamel.yaml + + +# --------------------------------------------------------------------------- +# Line classification +# --------------------------------------------------------------------------- + +class LineType(Enum): + BLANK = auto() + COMMENT = auto() # // ... or /* ... */ on one line + COMMENT_START = auto() # /* without closing */ + COMMENT_BODY = auto() # inside a multi-line comment + COMMENT_END = auto() # closing */ + PREPROCESSOR = auto() # #include / #define / #ifdef / ... + NODE_OPEN = auto() # something { (with optional label/name/addr) + NODE_CLOSE = auto() # }; + PROPERTY = auto() # name = value; or name; + CONTINUATION = auto() # continuation of a multi-line property + + +re_cpp_directive = re.compile( + r'^#\s*(include|define|undef|ifdef|ifndef|if|else|elif|endif|' + r'pragma|error|warning)\b') + +re_dtc_directive = re.compile( + r'^/(dts-v1|include)/') + +# label: name@addr { -- label and addr optional; name can be "/" +# Per the DT spec a node name may start with a digit (e.g. 1wire@...). +# The address part is captured loosely (any non-space, non-brace run) so +# malformed addresses (e.g. memory@0x1000) still reach +# check_unit_address_format() instead of silently bypassing the check. +re_node_header = re.compile( + r'^(?:([a-zA-Z_][a-zA-Z0-9_]*):\s*)?' + r'([a-zA-Z0-9][a-zA-Z0-9,._+-]*|/)' + r'(?:@([^\s{]+))?' + r'\s*\{$') + +re_ref_node = re.compile( + r'^&([a-zA-Z_][a-zA-Z0-9_]*)\s*\{$') + + +def is_preprocessor(stripped): + """Tell C preprocessor directives apart from DTS '#'-prefixed props.""" + if re_cpp_directive.match(stripped) is not None: + return True + if re_dtc_directive.match(stripped) is not None: + return True + return False + + +class DtsLine: + __slots__ = ('lineno', 'raw', 'linetype', 'indent_str', 'stripped', + 'prop_name', 'continuations', + 'node_name', 'node_addr', 'label', 'ref_name', 'depth', + 'closures') + + def __init__(self, lineno, raw, linetype, depth, indent_str, stripped): + self.lineno = lineno # 1-based within the block + self.raw = raw + self.linetype = linetype + self.indent_str = indent_str # leading whitespace as-is + self.depth = depth + self.stripped = stripped + self.prop_name = None + self.continuations = [] + self.node_name = None + self.node_addr = None + self.label = None + self.ref_name = None + self.closures = 1 # count of '}' on a NODE_CLOSE line + + +def _split_code(text): + """Return (code, opens_block) for a leading-stripped line: the + code portion with // and /* */ comments removed (string literals + kept verbatim), and whether a /* */ block comment is left open. + The code portion is right-stripped so the endswith() checks in + classify_lines see code only, not a trailing comment or blanks.""" + out = [] + i = 0 + n = len(text) + while i < n: + c = text[i] + if c == '"': + j = i + 1 + while j < n: + if text[j] == '\\': + j += 2 + continue + if text[j] == '"': + j += 1 + break + j += 1 + out.append(text[i:j]) + i = j + continue + if c == '/' and i + 1 < n and text[i + 1] == '/': + break + if c == '/' and i + 1 < n and text[i + 1] == '*': + end = text.find('*/', i + 2) + if end < 0: + return (''.join(out).rstrip(), True) + i = end + 2 + continue + out.append(c) + i += 1 + return (''.join(out).rstrip(), False) + + +re_only_closures = re.compile(r'(?:\}\s*;?\s*)+$') + + +def classify_lines(text): + """Return a list of DtsLine. Tracks { } depth and groups + continuation lines onto their leading PROPERTY line.""" + out = [] + in_block_comment = False + in_cpp_macro = False + prev_complete = True + depth = 0 + + # Split preserving the indent string verbatim + re_lead = re.compile(r'^([ \t]*)(.*)$') + + for i, raw in enumerate(text.split('\n'), start=1): + m = re_lead.match(raw) + indent_str = m.group(1) + stripped = m.group(2) + + # Continuation of a multi-line C preprocessor directive: the + # previous PREPROCESSOR line ended with a '\\' line splice, so + # this line is part of the same macro. Treat it as + # PREPROCESSOR until the splice chain ends (no trailing '\\' + # or a blank line). + if in_cpp_macro: + dl = DtsLine(i, raw, LineType.PREPROCESSOR, + depth, indent_str, stripped) + out.append(dl) + in_cpp_macro = (bool(stripped) and + stripped.rstrip().endswith('\\')) + continue + + if not stripped: + dl = DtsLine(i, raw, LineType.BLANK, depth, '', '') + out.append(dl) + continue + + if in_block_comment: + ltype = (LineType.COMMENT_END if '*/' in stripped + else LineType.COMMENT_BODY) + if ltype == LineType.COMMENT_END: + in_block_comment = False + dl = DtsLine(i, raw, ltype, depth, indent_str, stripped) + out.append(dl) + continue + + if (stripped.startswith('#') or stripped.startswith('/')) and is_preprocessor(stripped): + dl = DtsLine(i, raw, LineType.PREPROCESSOR, depth, + indent_str, stripped) + out.append(dl) + prev_complete = True + in_cpp_macro = stripped.rstrip().endswith('\\') + continue + + # Strip comments first so all later structural checks see code + # only. An unclosed /* sets in_block_comment for the next line. + code, opens_block = _split_code(stripped) + if opens_block: + in_block_comment = True + + # Pure-comment line: nothing left after stripping. Classify as + # COMMENT_START (carries to next line) or COMMENT, and skip the + # structural classification entirely. + if not code: + ltype = LineType.COMMENT_START if opens_block else LineType.COMMENT + dl = DtsLine(i, raw, ltype, depth, indent_str, stripped) + out.append(dl) + continue + + if not prev_complete: + dl = DtsLine(i, raw, LineType.CONTINUATION, depth, indent_str, code) + out.append(dl) + prev_complete = (code.endswith(';') or + code.endswith('{') or + code.endswith('};')) + continue + + # NODE_CLOSE: the canonical form is "}" or "};" alone. A line + # that is nothing but closures (e.g. "}; };") is still treated + # as NODE_CLOSE for depth tracking, but the multi-closure case + # is flagged separately by check_node_close_alone via + # dl.closures. + if re_only_closures.match(code): + closures = code.count('}') + depth = max(depth - closures, 0) + dl = DtsLine(i, raw, LineType.NODE_CLOSE, depth, indent_str, code) + dl.closures = closures + out.append(dl) + prev_complete = True + continue + + if code.endswith('{'): + dl = DtsLine(i, raw, LineType.NODE_OPEN, depth, indent_str, code) + parse_node_header(dl) + out.append(dl) + depth += 1 + prev_complete = True + continue + + # Property (or first line of a multi-line property). + dl = DtsLine(i, raw, LineType.PROPERTY, depth, indent_str, code) + parse_property_name(dl) + out.append(dl) + prev_complete = code.endswith(';') + + # Group continuation lines onto their leading PROPERTY. + last_prop = None + grouped = [] + for dl in out: + if dl.linetype == LineType.CONTINUATION and last_prop is not None: + last_prop.continuations.append(dl) + continue + if dl.linetype == LineType.PROPERTY: + last_prop = dl + elif dl.linetype != LineType.BLANK and \ + dl.linetype not in (LineType.COMMENT, LineType.COMMENT_BODY, + LineType.COMMENT_END, + LineType.COMMENT_START): + last_prop = None + grouped.append(dl) + return grouped + + +def parse_node_header(dl): + m = re_node_header.match(dl.stripped) + if m: + dl.label = m.group(1) + dl.node_name = m.group(2) + dl.node_addr = m.group(3) + return + m = re_ref_node.match(dl.stripped) + if m: + dl.ref_name = m.group(1) + + +def parse_property_name(dl): + m = re.match(r'^([a-zA-Z0-9#][a-zA-Z0-9,._+#-]*)\s*[=;]', dl.stripped) + if m: + dl.prop_name = m.group(1) + + +def collect_labels_and_refs(text): + """Return (defined_labels, referenced_labels) found anywhere outside + /* */ comments and string literals. Labels named fake_intc* (injected + by dt-extract-example) are skipped.""" + # Strip block comments first so labels inside them don't count + stripped = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) + # Strip line comments + stripped = re.sub(r'//[^\n]*', '', stripped) + # Strip string literals so words inside quotes (e.g. "Error: foo") + # are not picked up as label definitions or &-references. + stripped = re.sub(r'"(?:[^"\\]|\\.)*"', '""', stripped) + defined = set() + referenced = set() + # A label precedes a node header; the next non-space token may start + # with a letter (foo, &ref), a digit (1wire), or '/' (root node). + for m in re.finditer( + r'(?:^|[\s{])([a-zA-Z_][a-zA-Z0-9_]*):\s*[a-zA-Z0-9/&]', + stripped): + name = m.group(1) + if not name.startswith('fake_intc'): + defined.add(name) + for m in re.finditer(r'&([a-zA-Z_][a-zA-Z0-9_]*)', stripped): + referenced.add(m.group(1)) + return defined, referenced + + +# --------------------------------------------------------------------------- +# Rule registry +# --------------------------------------------------------------------------- + +class Ctx: + """Context passed to each rule check. Carries the parsed lines, + raw text, mode and kind.""" + + def __init__(self, lines, text, mode, kind): + self.lines = lines + self.text = text + self.mode = mode # 'relaxed' or 'strict' + if kind in DTS_FAMILY: + self.file_type = 'dts' + else: + self.file_type = 'yaml' + + +class Rule: + __slots__ = ('name', 'mode', 'description', 'check', 'applies_to') + + def __init__(self, name, mode, description, check, + applies_to=('yaml', 'dts', 'dtsi', 'dtso')): + self.name = name + self.mode = mode # 'relaxed' or 'strict' + self.description = description + self.check = check + self.applies_to = applies_to # input types this rule covers + + +# --- individual rule check functions -------------------------------------- + +def check_trailing_whitespace(ctx): + for dl in ctx.lines: + if dl.raw != dl.raw.rstrip(): + yield (dl.lineno, 'trailing whitespace') + + +def check_tab_in_yaml_example(ctx): + """Reject literal tabs in DTS lines when input is YAML. + + For YAML examples, indent and content must use spaces. Tabs inside + a #define value are tolerated (those are CPP macros, not DTS). + For .dts files, this rule does not apply -- tabs are required. + """ + if ctx.file_type != 'yaml': + return + for dl in ctx.lines: + if dl.linetype == LineType.PREPROCESSOR: + continue + if dl.linetype == LineType.BLANK: + continue + if '\t' in dl.raw: + yield (dl.lineno, 'tab character not allowed in DTS example') + + +def check_mixed_indent_chars(ctx): + """Indent must be all-tabs, except for aligning indentation (comments + or continued lines).""" + for dl in ctx.lines: + if not dl.indent_str: + continue + if dl.linetype == LineType.PREPROCESSOR: + continue + if re.search(r' \t', dl.indent_str): + yield (dl.lineno, 'mixed tabs and spaces in indent') + if dl.indent_str.count(' ') > 7: + yield (dl.lineno, 'too many space characters in indent (more than 7)') + for cont in dl.continuations: + if not cont.indent_str: + continue + if cont.linetype == LineType.PREPROCESSOR: + continue + if re.search(r' \t', cont.indent_str): + yield (cont.lineno, 'mixed tabs and spaces in indent') + + +def detect_indent_unit(ctx): + """Find the indent unit used at depth 1 in this block. + + Returns tuple of string (one of: ' ' (2 spaces), ' ' (4 spaces), + '\\t' (tab), or None if depth-1 is empty or ambiguous) and line number when + detection was made).""" + for dl in ctx.lines: + if dl.depth != 1: + continue + if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): + continue + if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): + continue + if not dl.indent_str: + continue + if dl.indent_str == '\t': + return ('\t', dl.lineno) + if dl.indent_str == ' ': + return (' ', dl.lineno) + if dl.indent_str == ' ': + return (' ', dl.lineno) + # Anything else at depth 1 is non-canonical; flag elsewhere. + return (dl.indent_str, dl.lineno) + return (None, None) + + +def check_indent_unit_relaxed(ctx): + """YAML examples: 2 or 4 spaces. Never tabs or other widths.""" + (unit, lineno) = detect_indent_unit(ctx) + if unit is None: + return + if unit not in (' ', ' '): + yield (lineno, 'indent unit must be 2 or 4 spaces, got %r' % unit) + + +def check_indent_unit_dts(ctx): + """DTS files: 1 tab per level. Always required.""" + (unit, lineno) = detect_indent_unit(ctx) + if unit is None: + return + if unit != '\t': + yield (lineno, 'indent unit must be 1 tab in DTS, got %r' % unit) + + +def check_indent_unit_strict(ctx): + """YAML: must be exactly 4 spaces. DTS: 1 tab (same as relaxed).""" + (unit, lineno) = detect_indent_unit(ctx) + if unit is None: + return + if ctx.file_type == 'yaml': + if unit != ' ': + yield (lineno, 'indent unit must be 4 spaces in strict mode, ' + 'got %r' % unit) + + +def check_indent_consistent(ctx): + """All indented lines must be a multiple of the detected unit.""" + (unit, lineno) = detect_indent_unit(ctx) + if unit is None: + return + if ctx.file_type == 'yaml': + if unit not in (' ', ' '): + return # let check_indent_unit_* report this + else: + if unit != '\t': + return + + for dl in ctx.lines: + if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): + continue + if dl.linetype == LineType.CONTINUATION: + continue # continuations align to <, not to indent unit + if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): + continue + if not dl.indent_str: + continue + # The indent must be 'unit' repeated dl.depth times, exactly. + # NODE_CLOSE lines have depth equal to the post-decrement value, + # which matches the indent expected. + expected = unit * dl.depth + if dl.indent_str != expected: + yield (dl.lineno, + 'indent mismatch (expected depth %d * %r)' % + (dl.depth, unit)) + + +def check_blank_lines(ctx): + """No two consecutive blank lines, no leading/trailing blank lines + in any node body.""" + lines = ctx.lines + # Consecutive blanks + for i in range(1, len(lines)): + if lines[i].linetype == LineType.BLANK and \ + lines[i - 1].linetype == LineType.BLANK: + yield (lines[i].lineno, 'consecutive blank lines') + # Blank right after { or right before } + for i, dl in enumerate(lines): + if dl.linetype != LineType.BLANK: + continue + prev = lines[i - 1] if i > 0 else None + nxt = lines[i + 1] if i + 1 < len(lines) else None + if prev is not None and prev.linetype == LineType.NODE_OPEN: + yield (dl.lineno, 'blank line at start of node body') + if nxt is not None and nxt.linetype == LineType.NODE_CLOSE: + yield (dl.lineno, 'blank line at end of node body') + + +def _walk_bodies(lines): + """Yield lists of immediate-child NODE_OPEN lines for each node body + in the input. Skips ref-nodes (&label) since those don't have an + intrinsic ordering.""" + body_stack = [[]] + for dl in lines: + if dl.linetype == LineType.NODE_OPEN: + body_stack[-1].append(dl) + body_stack.append([]) + continue + if dl.linetype == LineType.NODE_CLOSE: + if len(body_stack) <= 1: + # Unbalanced; ignore to avoid crashing on malformed input + continue + yield body_stack.pop() + continue + while body_stack: + yield body_stack.pop() + + +def _natural_sort_key(s): + """Split a string into a tuple of (kind, value) pairs that compares + numeric runs as ints, so 'foo10' sorts after 'foo2'.""" + parts = [] + for part in re.split(r'(\d+)', s): + if part.isdigit(): + parts.append((0, int(part))) + else: + parts.append((1, part)) + return tuple(parts) + + +def check_child_address_order(ctx): + """Addressed siblings (foo@N) must appear in ascending address + order within their parent node body.""" + for children in _walk_bodies(ctx.lines): + addressed = [] + for c in children: + if c.node_addr is None: + continue + try: + parts = tuple(int(p, 16) for p in c.node_addr.split(',')) + except ValueError: + continue + addressed.append((parts, c)) + for i in range(1, len(addressed)): + if addressed[i][0] < addressed[i - 1][0]: + dl = addressed[i][1] + yield (dl.lineno, + 'child node @%s out of address order' % + dl.node_addr) + + +def check_child_name_order(ctx): + """Unaddressed siblings must appear in natural-sort order by node + name within their parent node body. Addressed children are scoped + by check_child_address_order; reference nodes (&label { ... }) and + the root node are skipped.""" + for children in _walk_bodies(ctx.lines): + unaddressed = [] + for c in children: + if c.node_addr is not None: + continue + if c.node_name in (None, '/'): + continue + if c.ref_name is not None: + continue + unaddressed.append((_natural_sort_key(c.node_name), c)) + for i in range(1, len(unaddressed)): + if unaddressed[i][0] < unaddressed[i - 1][0]: + dl = unaddressed[i][1] + yield (dl.lineno, + 'child node %r out of name order' % dl.node_name) + + +def _property_bucket(name): + """Return the canonical bucket index for a property: + 0 device_type + 1 compatible + 2 reg / reg-names + 3 ranges + 4 standard properties (no vendor comma in #-stripped name) + 5 vendor-specific properties + 6 status + Plus a sub-key inside the bucket for fixed slots (device_type, compatible, + reg, reg-names, ranges, status). 'standard' and 'vendor' return None for + the sub-key, signalling that the within-bucket key is computed by + the pairing rules.""" + stripped = name.lstrip('#') + if name == 'device_type': + return (0, 0) + if name == 'compatible': + return (1, 0) + if name == 'reg': + return (2, 0) + if name == 'reg-names': + return (2, 1) + if name == 'ranges': + return (3, 0) + if name == 'status': + return (6, 0) + return (5 if ',' in stripped else 4, None) + + +# Declarative pairing rules: each is a callable +# (name, all_names) -> anchor_name_or_None +# If a rule returns an anchor, the property sorts immediately after the +# anchor. Rules are tried in order; the first match wins. If none +# matches, the within-bucket key falls back to natural sort by the +# #-stripped name. + +def _pair_pinctrl_names(name, all_names): + """pinctrl-names follows the highest pinctrl-N in the same node.""" + if name != 'pinctrl-names': + return None + cands = [n for n in all_names if re.match(r'^pinctrl-\d+$', n)] + if not cands: + return None + return max(cands, key=_natural_sort_key) + + +def _pair_x_names(name, all_names): + """Generic <x>-names follows its owning property. The owner is + usually plural (clocks/clock-names, dmas/dma-names, + resets/reset-names) but occasionally singular (reg/reg-names is + handled by the fixed slot above; this rule catches anything else).""" + if not name.endswith('-names'): + return None + base = name[:-len('-names')] + # Try plural and singular forms. + if (base + 's') in all_names: + return base + 's' + if base in all_names: + return base + return None + + +PAIRING_RULES = (_pair_pinctrl_names, _pair_x_names) + + +def _property_sort_key(name, all_names): + """Sort key for a property among its node-body siblings. + + Format: (bucket, within_key, tiebreak). 'within_key' for + standard/vendor buckets follows pairing rules: a property paired + with anchor X sorts as if it were X with a higher tiebreak.""" + bucket, fixed_sub = _property_bucket(name) + if fixed_sub is not None: + return (bucket, (), fixed_sub) + + for rule in PAIRING_RULES: + anchor = rule(name, all_names) + if anchor is not None: + return (bucket, _natural_sort_key(anchor.lstrip('#')), 1) + + return (bucket, _natural_sort_key(name.lstrip('#')), 0) + + +def check_property_order(ctx): + """Properties within a node body must appear in canonical order: + compatible, reg(/reg-names), ranges, then the standard group, then + the vendor-specific group, then status. Inside the standard and + vendor groups, pairing rules apply (e.g. <x>-names follows <x>); + everything else falls back to natural sort by the #-stripped name.""" + lines = ctx.lines + for i, dl in enumerate(lines): + if dl.linetype != LineType.NODE_OPEN: + continue + body_depth = dl.depth + 1 + props = [] + for j in range(i + 1, len(lines)): + d = lines[j] + if d.linetype == LineType.NODE_CLOSE and \ + d.depth == body_depth - 1: + break + if d.linetype == LineType.PROPERTY and d.depth == body_depth \ + and d.prop_name is not None: + props.append(d) + if len(props) < 2: + continue + all_names = [p.prop_name for p in props] + keyed = [(p, _property_sort_key(p.prop_name, all_names)) + for p in props] + for k in range(1, len(keyed)): + if keyed[k][1] < keyed[k - 1][1]: + p = keyed[k][0] + prev = keyed[k - 1][0] + yield (p.lineno, + 'property %r out of canonical order ' + '(should sort before %r)' % + (p.prop_name, prev.prop_name)) + + +def _strip_strings_and_comments(text): + """Remove string literals and /* */ + // comments from a single + line, replacing them with empty strings. Used so syntactic checks + (whitespace, hex case, etc.) don't false-positive on contents of + quoted strings or comments. An unclosed /* on the line is treated + as a comment running to end of line.""" + text = re.sub(r'"(?:[^"\\]|\\.)*"', '""', text) + text = re.sub(r'/\*.*?\*/', '', text) + text = re.sub(r'/\*.*$', '', text) + text = re.sub(r'//.*$', '', text) + return text + + +def check_required_blank_lines(ctx): + """A blank line must precede each child node and the 'status' + property within a node body, except when these are the first + substantive item in the body.""" + lines = ctx.lines + for i, open_dl in enumerate(lines): + if open_dl.linetype != LineType.NODE_OPEN: + continue + body_depth = open_dl.depth + 1 + prev_substantive = None + between_blanks = 0 + depth_inside = 0 + for j in range(i + 1, len(lines)): + d = lines[j] + if d.linetype == LineType.NODE_CLOSE and \ + d.depth == body_depth - 1 and depth_inside == 0: + break + # Track depth inside nested children so we only look at + # immediate-body items. + if d.linetype == LineType.NODE_OPEN and \ + d.depth >= body_depth and depth_inside > 0: + depth_inside += 1 + continue + if d.linetype == LineType.NODE_CLOSE and depth_inside > 0: + depth_inside -= 1 + continue + if depth_inside > 0: + continue + if d.linetype == LineType.BLANK: + if prev_substantive is not None: + between_blanks += 1 + continue + if d.linetype in (LineType.COMMENT, LineType.COMMENT_START, + LineType.COMMENT_BODY, LineType.COMMENT_END, + LineType.PREPROCESSOR): + continue + if d.linetype == LineType.CONTINUATION: + continue + + needs_blank = False + if d.linetype == LineType.NODE_OPEN: + needs_blank = True + depth_inside = 1 # entered the child body + elif d.linetype == LineType.PROPERTY and d.prop_name == 'status': + needs_blank = True + + if needs_blank and prev_substantive is not None and \ + between_blanks == 0: + if d.linetype == LineType.NODE_OPEN: + yield (d.lineno, + 'child node must be preceded by a blank line') + else: + yield (d.lineno, + '"status" must be preceded by a blank line') + + prev_substantive = d + between_blanks = 0 + + +def check_hex_case(ctx): + """Hex literals (0xN) must use lowercase digits and prefix.""" + for dl in ctx.lines: + if dl.linetype in (LineType.BLANK, LineType.COMMENT, + LineType.COMMENT_START, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.PREPROCESSOR): + continue + text = _strip_strings_and_comments(dl.raw) + for m in re.finditer(r'\b0[xX][0-9a-fA-F]+\b', text): + lit = m.group(0) + if any(c.isupper() for c in lit[2:]) or lit[1] == 'X': + yield (dl.lineno, + 'hex literal %r must be lowercase' % lit) + + +def check_unit_address_format(ctx): + """Unit addresses must be lowercase hex without leading zeros and + without a '0x' prefix. For multi-cell addresses (comma-separated), + each part is checked independently. A single '0' is permitted + (canonical zero).""" + for dl in ctx.lines: + if dl.linetype != LineType.NODE_OPEN: + continue + if dl.node_addr is None: + continue + addr = dl.node_addr + for part in addr.split(','): + if part[:2] in ('0x', '0X'): + yield (dl.lineno, + 'unit address %r must not have a "0x" prefix' % + addr) + break + if not re.match(r'^[0-9a-fA-F]+$', part): + yield (dl.lineno, + 'unit address %r is not valid hex' % addr) + break + if any(c in 'ABCDEF' for c in part): + yield (dl.lineno, + 'unit address %r must be lowercase hex' % addr) + break + if len(part) > 1 and part.startswith('0'): + yield (dl.lineno, + 'unit address %r has leading zeros' % addr) + break + + +def check_value_whitespace(ctx): + """A <...> cell list must have no whitespace directly after '<' + or directly before '>'. Continuation lines are joined onto the + property so a <...> split across lines is checked too; a '<' or + '>' at a line break is glued straight to the neighbouring value, + so the break itself is not counted as padding. Outside strings + and comments only.""" + for dl in ctx.lines: + if dl.linetype != LineType.PROPERTY: + continue + segs = [_strip_strings_and_comments(dl.raw).strip()] + for cont in dl.continuations: + segs.append(_strip_strings_and_comments(cont.stripped).strip()) + text = '' + for s in segs: + if not s: + continue + if not text or text.endswith('<') or s.startswith('>'): + text += s + else: + text += ' ' + s + for m in re.finditer(r'<([^<>]*)>', text): + content = m.group(1) + if content and content != content.strip(): + yield (dl.lineno, 'extra whitespace inside <...>') + break + + +def check_node_close_alone(ctx): + """The closing '};' of a node must be on its own line. The + classifier accepts a canonical "}" or "};" as NODE_CLOSE; a line + that is all closures (e.g. "}; };") is still NODE_CLOSE for depth + tracking but is flagged here via dl.closures. Any other line that + still contains '};' (in code, not in strings or comments) is + mixing a node close with something else.""" + for dl in ctx.lines: + if dl.linetype == LineType.NODE_CLOSE: + if dl.closures > 1: + yield (dl.lineno, + 'closing brace must be on its own line') + continue + if dl.linetype in (LineType.BLANK, LineType.COMMENT, + LineType.COMMENT_START, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.PREPROCESSOR): + continue + text = _strip_strings_and_comments(dl.raw) + if '};' in text: + yield (dl.lineno, + 'closing brace must be on its own line') + + +def _display_col(text): + """Visual column width of text, with tabs expanded to the next + 8-column stop, matching how printf and most editors render a + line and the kernel-wide line length convention.""" + col = 0 + for ch in text: + if ch == '\t': + col = (col // 8 + 1) * 8 + else: + col += 1 + return col + + +def check_line_length(ctx): + """Lines must not exceed 80 columns; tabs count as 8 (see + _display_col).""" + for dl in ctx.lines: + if dl.linetype == LineType.BLANK: + continue + cols = _display_col(dl.raw) + if cols > 80: + yield (dl.lineno, + 'line exceeds 80 columns (%d)' % cols) + + +def check_continuation_alignment(ctx): + """A multi-line property's continuation lines must align their + first non-whitespace character to the display column of the first + '<' or '"' after the '=' in the leading line. Display columns are + used so tab-indented .dts files (where a continuation aligns with + tabs plus spaces) are compared correctly.""" + for dl in ctx.lines: + if dl.linetype != LineType.PROPERTY: + continue + if not dl.continuations: + continue + eq = dl.raw.find('=') + if eq < 0: + continue + # First '<' or '"' after '=' + rest = dl.raw[eq + 1:] + m = re.search(r'[<"]', rest) + if not m: + continue + target_col = _display_col(dl.raw[:eq + 1 + m.start()]) + for cont in dl.continuations: + if _display_col(cont.indent_str) != target_col: + yield (cont.lineno, + 'continuation should align to column %d ' + '(under "<" or \\")' % (target_col + 1)) + + +def check_unclosed_block_comment(ctx): + """Every /* must have a matching */ in the same block. Catches both + a comment opened on its own line (COMMENT_START) and a tail comment + opened on a PROPERTY or other code line (where in_block_comment is + set by _split_code so the next line becomes COMMENT_BODY without a + preceding COMMENT_START).""" + open_lineno = None + for dl in ctx.lines: + if dl.linetype == LineType.COMMENT_START: + open_lineno = dl.lineno + elif dl.linetype == LineType.COMMENT_END: + open_lineno = None + elif dl.linetype == LineType.COMMENT_BODY and open_lineno is None: + # Block was opened by a /* tail on a code line; report at + # the first orphan body line since the originating line is + # already classified as something else. + open_lineno = dl.lineno + if open_lineno is not None: + yield (open_lineno, 'unclosed /* block comment') + + +def check_unused_labels(ctx): + """Labels defined but never referenced are clutter.""" + defined, referenced = collect_labels_and_refs(ctx.text) + for label in sorted(defined - referenced): + # Find the line where this label is defined for line-number + # reporting. + m = re.search(r'(?m)^.*\b' + re.escape(label) + r'\s*:', ctx.text) + lineno = ctx.text[:m.start()].count('\n') + 1 if m else 1 + yield (lineno, 'label %r defined but never &-referenced' % label) + + +# --- registry -------------------------------------------------------------- + +RULES = [ + # 'relaxed' is the default; rules in this group must produce zero + # output on a clean kernel tree (post the small prep-cleanup + # commit at the head of this series). + Rule('trailing-whitespace', 'relaxed', + 'no trailing whitespace on any line', + check_trailing_whitespace), + Rule('tab-in-yaml', 'relaxed', + 'YAML (also DTS examples) may not contain tab characters', + check_tab_in_yaml_example, applies_to=('yaml',)), + Rule('mixed-indent-chars', 'relaxed', + 'indent must not mix tabs and spaces', + check_mixed_indent_chars, applies_to=('dts', 'dtsi', 'dtso')), + Rule('unclosed-block-comment', 'relaxed', + 'every /* block comment must close with */', + check_unclosed_block_comment), + + # DTS files always use tabs; this is not negotiable per kernel + # coding style (.dts files are real source). Relaxed mode. + Rule('indent-unit-dts', 'relaxed', + 'DTS files: 1 tab per nesting level', + check_indent_unit_dts, + applies_to=('dts', 'dtsi', 'dtso')), + + # 'strict' rules are opt-in (e.g. for new submissions via + # checkpatch.pl in a follow-up series). They flag many existing + # files and can be promoted to relaxed once those are cleaned up. + Rule('indent-unit', 'strict', + 'YAML: 2 or 4 spaces per level', + check_indent_unit_relaxed, applies_to=('yaml',)), + Rule('indent-unit-strict', 'strict', + 'YAML: must be 4 spaces per level', + check_indent_unit_strict, applies_to=('yaml',)), + Rule('indent-consistent', 'strict', + 'every line indented at depth * unit', + check_indent_consistent), + Rule('blank-lines', 'strict', + 'no consecutive blanks; no blanks at node body edges', + check_blank_lines), + Rule('child-address-order', 'strict', + 'addressed siblings must be in ascending address order', + check_child_address_order), + Rule('child-name-order', 'strict', + 'unaddressed siblings must be in natural-sort name order', + check_child_name_order), + Rule('property-order', 'strict', + 'canonical bucket + pairing + natural-sort order of properties', + check_property_order), + Rule('required-blank-lines', 'strict', + 'blank line before child nodes and before "status"', + check_required_blank_lines), + Rule('hex-case', 'strict', + 'hex literals must be lowercase', + check_hex_case), + Rule('unit-address-format', 'strict', + 'unit addresses must be lowercase hex without leading zeros', + check_unit_address_format), + Rule('value-whitespace', 'strict', + 'no whitespace directly inside <...> brackets', + check_value_whitespace), + Rule('node-close-alone', 'strict', + 'closing brace must be on its own line', + check_node_close_alone), + Rule('line-length', 'strict', + 'lines must not exceed 80 columns', + check_line_length), + Rule('continuation-alignment', 'strict', + 'multi-line property continuations align under "<" or "\\""', + check_continuation_alignment), + Rule('unused-labels', 'strict', + 'every label must be &-referenced in the same example/file ' + '(skipped for .dtsi/.dtso since labels there are exported)', + check_unused_labels, applies_to=('yaml', 'dts')), +] + + +def select_rules(mode, input_kind): + """Return rules that apply to the given mode and input type.""" + rank = {'relaxed': 0, 'strict': 1} + out = [] + for r in RULES: + if rank[r.mode] > rank[mode]: + continue + if input_kind not in r.applies_to: + continue + out.append(r) + return out + + +# --------------------------------------------------------------------------- +# Block runner +# --------------------------------------------------------------------------- + +def check_block(text, mode, input_type): + """Run all selected rules on a single block of DTS text. Returns a + list of (lineno, rule_name, message) tuples.""" + lines = classify_lines(text) + ctx = Ctx(lines, text, mode, input_type) + rules = select_rules(mode, input_type) + findings = [] + for r in rules: + for lineno, msg in r.check(ctx): + findings.append((lineno, r.name, msg)) + findings.sort(key=lambda t: (t[0], t[1])) + return findings + + +# --------------------------------------------------------------------------- +# Input drivers (YAML examples vs raw DTS) +# --------------------------------------------------------------------------- + +def _yaml_loader(): + return ruamel.yaml.YAML() + + +def iter_yaml_examples(filepath): + """Yield (example_text, base_lineno_in_file, example_index) tuples.""" + yaml = _yaml_loader() + try: + with open(filepath, encoding='utf-8') as f: + data = yaml.load(f) + except Exception as e: + print('%s: error loading YAML: %s' % (filepath, e), + file=sys.stderr) + return + if not isinstance(data, dict) or 'examples' not in data: + return + examples = data['examples'] + if not hasattr(examples, '__iter__'): + return + for i, ex in enumerate(examples): + if not isinstance(ex, str): + continue + try: + base = examples.lc.item(i)[0] + 2 + except Exception: + base = 1 + yield (str(ex), base, i) + + +def iter_dts_file(filepath): + """Treat the whole file as a single block.""" + try: + with open(filepath, encoding='utf-8') as f: + text = f.read() + except Exception as e: + print('%s: error reading: %s' % (filepath, e), file=sys.stderr) + return + yield (text, 1, None) + + +# --------------------------------------------------------------------------- +# Top-level processing +# --------------------------------------------------------------------------- + +def input_kind(filepath): + p = filepath.lower() + if p.endswith('.yaml') or p.endswith('.yml'): + return 'yaml' + if p.endswith('.dts'): + return 'dts' + if p.endswith('.dtsi'): + return 'dtsi' + if p.endswith('.dtso'): + return 'dtso' + return None + + +# All input types that use tab indentation and follow DTS coding style. +DTS_FAMILY = ('dts', 'dtsi', 'dtso') + + +def collect_findings(filepath, mode): + """Return a (lines, count) pair for filepath. lines is a list of + formatted output strings; count is the number of findings.""" + kind = input_kind(filepath) + if kind == 'yaml': + iterator = iter_yaml_examples(filepath) + elif kind in DTS_FAMILY: + iterator = iter_dts_file(filepath) + else: + return (['%s: unknown file type, skipping' % filepath], 0) + + out = [] + for text, base, idx in iterator: + for lineno, rule, msg in check_block(text, mode, kind): + abs_line = base + lineno - 1 + ex_tag = '' if idx is None else ' example %d' % idx + out.append('%s:%d:%s [%s] %s' % + (filepath, abs_line, ex_tag, rule, msg)) + return (out, len(out)) + + +# Worker entry point for ProcessPoolExecutor.map(). Top-level so it is +# picklable on every platform. +def _worker(args): + filepath, mode = args + return collect_findings(filepath, mode) + + +def main(): + import os + ap = argparse.ArgumentParser( + description='Check DTS coding style on YAML examples and ' + '.dts/.dtsi/.dtso files.', + fromfile_prefix_chars='@') + ap.add_argument('--mode', choices=('relaxed', 'strict'), + default='relaxed', + help='which rule set to apply (default: relaxed)') + ap.add_argument('-j', '--jobs', type=int, default=0, + metavar='N', + help='run N workers in parallel (default: respect ' + 'the make jobserver via $PARALLELISM, otherwise ' + 'os.cpu_count(); use 1 to disable multiprocessing)') + ap.add_argument('--list-rules', action='store_true', + help='print all rules with their mode and exit') + ap.add_argument('files', nargs='*', metavar='file', + help='YAML binding files or .dts/.dtsi/.dtso files; ' + 'use @argfile to read paths from a file') + args = ap.parse_args() + + if args.list_rules: + for r in RULES: + applies = ','.join(r.applies_to) + print('%-22s %-7s [%s] %s' % + (r.name, r.mode, applies, r.description)) + return 0 + + if not args.files: + ap.error('no input files') + + if args.jobs > 0: + jobs = args.jobs + else: + # When invoked under scripts/jobserver-exec, $PARALLELISM + # holds the slot count make has reserved for us; this lets + # `make -j N dt_binding_check` constrain our worker pool to N. + try: + jobs = int(os.environ['PARALLELISM']) + except (KeyError, ValueError): + jobs = os.cpu_count() or 1 + # Single-process path: keep import surface small for tests and + # easy debugging. + if jobs == 1 or len(args.files) == 1: + total = 0 + for f in args.files: + lines, n = collect_findings(f, args.mode) + for line in lines: + print(line, file=sys.stderr) + total += n + return 1 if total else 0 + + # Multi-process path. ex.map preserves input order so output is + # deterministic across runs. + from concurrent.futures import ProcessPoolExecutor + total = 0 + work = [(f, args.mode) for f in args.files] + chunk = max(1, len(work) // (jobs * 8)) if work else 1 + with ProcessPoolExecutor(max_workers=jobs) as ex: + for lines, n in ex.map(_worker, work, chunksize=chunk): + for line in lines: + print(line, file=sys.stderr) + total += n + return 1 if total else 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/dtc/dt-extract-compatibles b/scripts/dtc/dt-extract-compatibles new file mode 100755 index 000000000..87999d707 --- /dev/null +++ b/scripts/dtc/dt-extract-compatibles @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only + +import fnmatch +import os +import re +import argparse + + +def parse_of_declare_macros(data, include_driver_macros=True): + """ Find all compatible strings in OF_DECLARE() style macros """ + compat_list = [] + # CPU_METHOD_OF_DECLARE does not have a compatible string + if include_driver_macros: + re_macros = r'(?<!CPU_METHOD_)(IRQCHIP|OF)_(DECLARE|MATCH)(_DRIVER)?\(.*?\)' + else: + re_macros = r'(?<!CPU_METHOD_)(IRQCHIP|OF)_(DECLARE|MATCH)\(.*?\)' + for m in re.finditer(re_macros, data): + try: + compat = re.search(r'"(.*?)"', m[0])[1] + except: + # Fails on compatible strings in #define, so just skip + continue + compat_list += [compat] + + return compat_list + + +def parse_of_device_id(data, match_table_list=None): + """ Find all compatible strings in of_device_id structs """ + compat_list = [] + for m in re.finditer(r'of_device_id(\s+\S+)?\s+(\S+)\[\](\s+\S+)?\s*=\s*({.*?);', data): + if match_table_list is not None and m[2] not in match_table_list: + continue + compat_list += re.findall(r'\.compatible\s+=\s+"(\S+)"', m[4]) + + return compat_list + + +def parse_of_match_table(data): + """ Find all driver's of_match_table """ + match_table_list = [] + for m in re.finditer(r'\.of_match_table\s+=\s+(of_match_ptr\()?([a-zA-Z0-9_-]+)', data): + match_table_list.append(m[2]) + + 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', '') + + if compat_ignore_list is not None: + # For a compatible in the DT to be matched to a driver it needs to show + # up in a driver's of_match_table + match_table_list = parse_of_match_table(data) + compat_list = parse_of_device_id(data, match_table_list) + + compat_list = [compat for compat in compat_list if compat not in 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, "for_each_compatible_node_scoped") + compat_list += parse_of_functions(data, "of_get_compatible_child") + + return compat_list + +def parse_compatibles_to_ignore(file): + with open(file, 'r', encoding='utf-8') as f: + data = f.read().replace('\n', '') + + # Compatibles that show up in OF_DECLARE macros can't be expected to + # match a driver, except for the _DRIVER ones. + return parse_of_declare_macros(data, include_driver_macros=False) + + +def print_compat(filename, compatibles): + if not compatibles: + return + if show_filename: + compat_str = ' '.join(compatibles) + print(filename + ": compatible(s): " + compat_str) + else: + print(*compatibles, sep='\n') + +def glob_without_symlinks(root, glob): + for path, dirs, files in os.walk(root): + # Ignore hidden directories + for d in dirs: + if fnmatch.fnmatch(d, ".*"): + dirs.remove(d) + for f in files: + if fnmatch.fnmatch(f, glob): + yield os.path.join(path, f) + +def files_to_parse(path_args): + for f in path_args: + if os.path.isdir(f): + for filename in glob_without_symlinks(f, "*.c"): + yield filename + else: + yield f + +show_filename = False + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument("cfile", type=str, nargs='*', help="C source files or directories to parse") + ap.add_argument('-H', '--with-filename', help="Print filename with compatibles", action="store_true") + ap.add_argument('-d', '--driver-match', help="Only print compatibles that should match to a driver", action="store_true") + args = ap.parse_args() + + show_filename = args.with_filename + compat_ignore_list = None + + if args.driver_match: + compat_ignore_list = [] + for f in files_to_parse(args.cfile): + compat_ignore_list.extend(parse_compatibles_to_ignore(f)) + + for f in files_to_parse(args.cfile): + compat_list = parse_compatibles(f, compat_ignore_list) + print_compat(f, compat_list) diff --git a/scripts/dtc/dt-style-selftest/bad/dts-child-name-order.dts b/scripts/dtc/dt-style-selftest/bad/dts-child-name-order.dts new file mode 100644 index 000000000..54be9f07b --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/dts-child-name-order.dts @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + pmu { + compatible = "example,pmu"; + + /* Include labels to be sure they do not affect sorting */ + foo: foo { + label = "foo"; + }; + + label_bar: bar { + label = "bar"; + }; + }; + + memory@a0000000 { + device_type = "memory"; + reg = <0x0 0xa0000000 0x0 0x0>; + }; + + pmu-2 { + compatible = "example,pmu"; + + /* Just reference labels to avoid strict warnings */ + example,foo = <&foo>, <&label_bar>; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/bad/dts-extend-node-child-name-order.dts b/scripts/dtc/dt-style-selftest/bad/dts-extend-node-child-name-order.dts new file mode 100644 index 000000000..296c58a54 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/dts-extend-node-child-name-order.dts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + pmu { + compatible = "example,pmu"; + + /* Just reference labels to avoid strict warnings */ + example,foo = <&foo>, <&label_bar>; + }; +}; + +&pmu { + /* Include labels to be sure they do not affect sorting */ + foo: foo { + label = "foo"; + }; + + label_bar: bar { + label = "bar"; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/bad/dts-extend-node-digit-node-order.dts b/scripts/dtc/dt-style-selftest/bad/dts-extend-node-digit-node-order.dts new file mode 100644 index 000000000..813900e33 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/dts-extend-node-digit-node-order.dts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + soc: soc@0 { + compatible = "simple-bus"; + ranges = <0 0 0 0xc0000000>; + + #address-cells = <1>; + #size-cells = <1>; + }; +}; + +&soc { + serial@20000 { + compatible = "example,serial"; + reg = <0x20000 0x1000>; + }; + + interrupt-controller@10000 { + compatible = "example,intc"; + reg = <0x10000 0x1000>; + interrupts = <1 2 3>; + }; + + serial@30000 { + compatible = "example,serial"; + reg = <0x30000 0x1000>; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/bad/dts-mixed-indent.dts b/scripts/dtc/dt-style-selftest/bad/dts-mixed-indent.dts new file mode 100644 index 000000000..cd3de04ec --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/dts-mixed-indent.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* Test fixture: a .dts using wrong indent. */ + +/dts-v1/; + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; + + soc@0 { + compatible = "simple-bus"; + ranges = <0 0 0 0xc0000000>; + + clocks = <1>, + <2>, + <3>, + <4>; + resets = <5>; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/bad/dts-property-order.dts b/scripts/dtc/dt-style-selftest/bad/dts-property-order.dts new file mode 100644 index 000000000..f31abb6ce --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/dts-property-order.dts @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: Incorrect property order + */ + +/dts-v1/; + +/ { + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + reg = <0x0 0x0>; + compatible = "arm,cortex-a57"; + device_type = "cpu"; + enable-method = "psci"; + }; + }; + + pmu { + compatible = "example,pmu"; + + status = "disabled"; + dma-coherent; + }; + + soc@0 { + ranges = <0 0 0 0xc0000000>; + compatible = "simple-bus"; + + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller@10000 { + reg = <0x10000 0x1000>; + interrupts = <1 2 3>, + <4 5 6>, + <7 8 9>; + compatible = "example,intc"; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/bad/dts-spaces.dts b/scripts/dtc/dt-style-selftest/bad/dts-spaces.dts new file mode 100644 index 000000000..9dad22adc --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/dts-spaces.dts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: a .dts using space indent (must use tabs). + */ + +/dts-v1/; + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; +}; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-child-addr-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-child-addr-order.yaml new file mode 100644 index 000000000..3df56e69a --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-child-addr-order.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-child-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with addressed children out of order + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-child-order + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@200 { + compatible = "example,test-child-order"; + reg = <0x200 0x10>; + }; + + device@100 { + compatible = "example,test-child-order"; + reg = <0x100 0x10>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-child-name-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-child-name-order.yaml new file mode 100644 index 000000000..35d85e557 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-child-name-order.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-child-name-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with unaddressed children out of name order + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-child-name-order + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + + foo { + label = "foo"; + }; + + bar { + label = "bar"; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-cont-align.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-cont-align.yaml new file mode 100644 index 000000000..92778540b --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-cont-align.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-cont-align.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with mis-aligned multi-line property + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-cont-align + reg: + maxItems: 2 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-cont-align"; + reg = <0x1000 0x100>, + <0x2000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-digit-node-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-digit-node-order.yaml new file mode 100644 index 000000000..44a9d25e5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-digit-node-order.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-digit-node-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with digit-leading nodes out of address order + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-digit-node-order + +required: + - compatible + +additionalProperties: false + +examples: + - | + bus@0 { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + + 3d-engine@20 { + compatible = "example,3d-engine"; + reg = <0x20 0x4>; + }; + + 1wire@10 { + compatible = "example,1wire"; + reg = <0x10 0x4>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-hex-case.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-hex-case.yaml new file mode 100644 index 000000000..b26d1bf58 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-hex-case.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-hex-case.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with uppercase hex literals + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-hex-case + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-hex-case"; + reg = <0xABCD 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-indent-strict.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-indent-strict.yaml new file mode 100644 index 000000000..bee4cf118 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-indent-strict.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-indent-strict.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture using 2-space indent (rejected by strict mode) + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-indent-strict + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-indent-strict"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-label-in-string.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-label-in-string.yaml new file mode 100644 index 000000000..ba512869b --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-label-in-string.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-label-in-string.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture where a label is only "referenced" inside a string + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-label-in-string + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo: device@1000 { + compatible = "example,test-label-in-string"; + reg = <0x1000 0x100>; + info = "see &foo for details"; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-line-length.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-line-length.yaml new file mode 100644 index 000000000..64427bf1c --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-line-length.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-line-length.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture exceeding 80 columns + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-line-length + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-line-length-this-is-a-very-long-name-indeed-yeah"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-mixed-indent.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-mixed-indent.yaml new file mode 100644 index 000000000..5401d1a42 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-mixed-indent.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-mixed-indent.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture mixing tabs and spaces in indent + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-mixed + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-mixed"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-multi-close.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-multi-close.yaml new file mode 100644 index 000000000..4d9fa27b5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-multi-close.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-multi-close.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with two closing braces on one line + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-multi-close + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@100 { + compatible = "example,test-multi-close"; + reg = <0x100 0x10>; + }; }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-node-close.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-node-close.yaml new file mode 100644 index 000000000..e107659fd --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-node-close.yaml @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-node-close.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with closing brace not on its own line + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-node-close + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + + empty {}; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-prop-order-device-type.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-prop-order-device-type.yaml new file mode 100644 index 000000000..e2c69e9ff --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-prop-order-device-type.yaml @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-prop-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with device_type + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-prop-order-device-type + reg: + maxItems: 1 + device_type: true + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-prop-order-device-type"; + device_type = "cpu"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-prop-order.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-prop-order.yaml new file mode 100644 index 000000000..75582a3d2 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-prop-order.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-prop-order.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with reg before compatible + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-prop-order + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + reg = <0x1000 0x100>; + compatible = "example,test-prop-order"; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-prop-pairing.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-prop-pairing.yaml new file mode 100644 index 000000000..767ab21c3 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-prop-pairing.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-prop-pairing.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture exercising <x>-names and pinctrl-names pairing + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-prop-pairing + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-prop-pairing"; + reg = <0x1000 0x100>; + clock-names = "bus"; + clocks = <&clk 0>; + pinctrl-names = "default"; + pinctrl-0 = <&p0>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-required-blank.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-required-blank.yaml new file mode 100644 index 000000000..8bb53240c --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-required-blank.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-required-blank.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture missing required blank lines + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-required-blank + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + status = "okay"; + child@100 { + reg = <0x100>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml new file mode 100644 index 000000000..487d07ff8 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-tab.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-tab.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with a tab in a DTS line + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-tab + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-tab"; + reg = <0x1000 0x100>; /* registers */ + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-trailing-comment.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-comment.yaml new file mode 100644 index 000000000..2368ada81 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-comment.yaml @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-trailing-comment.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with properties out of order behind trailing comments + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-trailing-comment + +required: + - compatible + +additionalProperties: false + +examples: + - | + foo@0 { /* the device node */ + reg = <0x0 0x4>; /* registers */ + compatible = "example,test-trailing-comment"; // misplaced + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-trailing-ws.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-ws.yaml new file mode 100644 index 000000000..5c4b4bd83 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-trailing-ws.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-trailing.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with trailing whitespace + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-trailing + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + device@1000 { + compatible = "example,test-trailing"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unclosed-comment.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unclosed-comment.yaml new file mode 100644 index 000000000..63c1c0871 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unclosed-comment.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unclosed-comment.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with an unclosed /* block comment + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unclosed-comment + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + /* this comment never closes + device@1000 { + compatible = "example,test-unclosed-comment"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr-prefix.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr-prefix.yaml new file mode 100644 index 000000000..9b3fe508c --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr-prefix.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unit-addr-prefix.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with 0x-prefixed unit address + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unit-addr-prefix + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + memory@0x1000 { + compatible = "example,test-unit-addr-prefix"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr.yaml new file mode 100644 index 000000000..93705cd45 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unit-addr.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unit-addr.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with malformed unit address + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unit-addr + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@01000 { + compatible = "example,test-unit-addr"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-unused-label.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-unused-label.yaml new file mode 100644 index 000000000..28d7176cb --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-unused-label.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-unused-label.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with an unused label + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-unused-label + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + dev: device@1000 { + compatible = "example,test-unused-label"; + reg = <0x1000 0x100>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-value-ws-multiline.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws-multiline.yaml new file mode 100644 index 000000000..504bf0931 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws-multiline.yaml @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-value-ws-multiline.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with extra whitespace in a multi-line cell array + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-value-ws-multiline + +required: + - compatible + +additionalProperties: false + +examples: + - | + foo@0 { + compatible = "example,test-value-ws-multiline"; + reg = < 0x0 0x4 + 0x8 0xc>; + }; diff --git a/scripts/dtc/dt-style-selftest/bad/yaml-value-ws.yaml b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws.yaml new file mode 100644 index 000000000..342ab9f39 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/bad/yaml-value-ws.yaml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-bad-value-ws.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture with extra whitespace inside <...> + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-value-ws + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + foo@1000 { + compatible = "example,test-value-ws"; + reg = < 0x1000 0x100 >; + }; diff --git a/scripts/dtc/dt-style-selftest/expected/dts-child-name-order.dts.txt b/scripts/dtc/dt-style-selftest/expected/dts-child-name-order.dts.txt new file mode 100644 index 000000000..e2eea0862 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/dts-child-name-order.dts.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/dts-child-name-order.dts:16: [child-name-order] child node 'bar' out of name order diff --git a/scripts/dtc/dt-style-selftest/expected/dts-extend-node-child-name-order.dts.txt b/scripts/dtc/dt-style-selftest/expected/dts-extend-node-child-name-order.dts.txt new file mode 100644 index 000000000..b6e74e6f9 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/dts-extend-node-child-name-order.dts.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/dts-extend-node-child-name-order.dts:22: [child-name-order] child node 'bar' out of name order diff --git a/scripts/dtc/dt-style-selftest/expected/dts-extend-node-digit-node-order.dts.txt b/scripts/dtc/dt-style-selftest/expected/dts-extend-node-digit-node-order.dts.txt new file mode 100644 index 000000000..bf252e4d9 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/dts-extend-node-digit-node-order.dts.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/dts-extend-node-digit-node-order.dts:23: [child-address-order] child node @10000 out of address order diff --git a/scripts/dtc/dt-style-selftest/expected/dts-mixed-indent.dts.txt b/scripts/dtc/dt-style-selftest/expected/dts-mixed-indent.dts.txt new file mode 100644 index 000000000..93146cfb5 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/dts-mixed-indent.dts.txt @@ -0,0 +1,9 @@ +# mode=strict +bad/dts-mixed-indent.dts:11: [indent-consistent] indent mismatch (expected depth 1 * '\t') +bad/dts-mixed-indent.dts:11: [mixed-indent-chars] too many space characters in indent (more than 7) +bad/dts-mixed-indent.dts:12: [indent-consistent] indent mismatch (expected depth 2 * '\t') +bad/dts-mixed-indent.dts:12: [mixed-indent-chars] mixed tabs and spaces in indent +bad/dts-mixed-indent.dts:13: [indent-consistent] indent mismatch (expected depth 2 * '\t') +bad/dts-mixed-indent.dts:13: [mixed-indent-chars] mixed tabs and spaces in indent +bad/dts-mixed-indent.dts:16: [mixed-indent-chars] mixed tabs and spaces in indent +bad/dts-mixed-indent.dts:19: [indent-consistent] indent mismatch (expected depth 2 * '\t') diff --git a/scripts/dtc/dt-style-selftest/expected/dts-property-order.dts.txt b/scripts/dtc/dt-style-selftest/expected/dts-property-order.dts.txt new file mode 100644 index 000000000..4bc213286 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/dts-property-order.dts.txt @@ -0,0 +1,6 @@ +# mode=strict +bad/dts-property-order.dts:15: [property-order] property 'compatible' out of canonical order (should sort before 'reg') +bad/dts-property-order.dts:16: [property-order] property 'device_type' out of canonical order (should sort before 'compatible') +bad/dts-property-order.dts:25: [property-order] property 'dma-coherent' out of canonical order (should sort before 'status') +bad/dts-property-order.dts:30: [property-order] property 'compatible' out of canonical order (should sort before 'ranges') +bad/dts-property-order.dts:40: [property-order] property 'compatible' out of canonical order (should sort before 'interrupts') diff --git a/scripts/dtc/dt-style-selftest/expected/dts-spaces.dts.txt b/scripts/dtc/dt-style-selftest/expected/dts-spaces.dts.txt new file mode 100644 index 000000000..5afdb101d --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/dts-spaces.dts.txt @@ -0,0 +1,2 @@ +# mode=relaxed +bad/dts-spaces.dts:9: [indent-unit-dts] indent unit must be 1 tab in DTS, got ' ' diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-child-addr-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-child-addr-order.yaml.txt new file mode 100644 index 000000000..f0db79a00 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-child-addr-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-child-addr-order.yaml:37: example 0 [child-address-order] child node @100 out of address order diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-child-name-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-child-name-order.yaml.txt new file mode 100644 index 000000000..bb434b126 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-child-name-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-child-name-order.yaml:34: example 0 [child-name-order] child node 'bar' out of name order diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-cont-align.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-cont-align.yaml.txt new file mode 100644 index 000000000..b5576dd0f --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-cont-align.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-cont-align.yaml:29: example 0 [continuation-alignment] continuation should align to column 11 (under "<" or \") diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-digit-node-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-digit-node-order.yaml.txt new file mode 100644 index 000000000..6de275e2d --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-digit-node-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-digit-node-order.yaml:33: example 0 [child-address-order] child node @10 out of address order diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-hex-case.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-hex-case.yaml.txt new file mode 100644 index 000000000..6600f7cd1 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-hex-case.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-hex-case.yaml:28: example 0 [hex-case] hex literal '0xABCD' must be lowercase diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-indent-strict.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-indent-strict.yaml.txt new file mode 100644 index 000000000..c4f31deea --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-indent-strict.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-indent-strict.yaml:27: example 0 [indent-unit-strict] indent unit must be 4 spaces in strict mode, got ' ' diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-label-in-string.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-label-in-string.yaml.txt new file mode 100644 index 000000000..05da06f81 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-label-in-string.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-label-in-string.yaml:26: example 0 [unused-labels] label 'foo' defined but never &-referenced diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-line-length.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-line-length.yaml.txt new file mode 100644 index 000000000..89b36360c --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-line-length.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-line-length.yaml:27: example 0 [line-length] line exceeds 80 columns (81) diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-mixed-indent.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-mixed-indent.yaml.txt new file mode 100644 index 000000000..bc3fc3cf0 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-mixed-indent.yaml.txt @@ -0,0 +1,2 @@ +# mode=relaxed +bad/yaml-mixed-indent.yaml:27: example 0 [tab-in-yaml] tab character not allowed in DTS example diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-multi-close.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-multi-close.yaml.txt new file mode 100644 index 000000000..637d0f8ea --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-multi-close.yaml.txt @@ -0,0 +1,3 @@ +# mode=strict +bad/yaml-multi-close.yaml:35: example 0 [indent-consistent] indent mismatch (expected depth 0 * ' ') +bad/yaml-multi-close.yaml:35: example 0 [node-close-alone] closing brace must be on its own line diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-node-close.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-node-close.yaml.txt new file mode 100644 index 000000000..ee894747b --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-node-close.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-node-close.yaml:30: example 0 [node-close-alone] closing brace must be on its own line diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-prop-order-device-type.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-prop-order-device-type.yaml.txt new file mode 100644 index 000000000..9350e2b80 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-prop-order-device-type.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-prop-order-device-type.yaml:29: example 0 [property-order] property 'device_type' out of canonical order (should sort before 'compatible') diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-prop-order.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-prop-order.yaml.txt new file mode 100644 index 000000000..578df7209 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-prop-order.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-prop-order.yaml:28: example 0 [property-order] property 'compatible' out of canonical order (should sort before 'reg') diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-prop-pairing.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-prop-pairing.yaml.txt new file mode 100644 index 000000000..e6e21349a --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-prop-pairing.yaml.txt @@ -0,0 +1,3 @@ +# mode=strict +bad/yaml-prop-pairing.yaml:30: example 0 [property-order] property 'clocks' out of canonical order (should sort before 'clock-names') +bad/yaml-prop-pairing.yaml:32: example 0 [property-order] property 'pinctrl-0' out of canonical order (should sort before 'pinctrl-names') diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-required-blank.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-required-blank.yaml.txt new file mode 100644 index 000000000..04ea0bacd --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-required-blank.yaml.txt @@ -0,0 +1,3 @@ +# mode=strict +bad/yaml-required-blank.yaml:29: example 0 [required-blank-lines] "status" must be preceded by a blank line +bad/yaml-required-blank.yaml:30: example 0 [required-blank-lines] child node must be preceded by a blank line diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-tab.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-tab.yaml.txt new file mode 100644 index 000000000..0bc67ae48 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-tab.yaml.txt @@ -0,0 +1,2 @@ +# mode=relaxed +bad/yaml-tab.yaml:28: example 0 [tab-in-yaml] tab character not allowed in DTS example diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-trailing-comment.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-comment.yaml.txt new file mode 100644 index 000000000..69dbb1d03 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-comment.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-trailing-comment.yaml:25: example 0 [property-order] property 'compatible' out of canonical order (should sort before 'reg') diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-trailing-ws.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-ws.yaml.txt new file mode 100644 index 000000000..cfdbc8476 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-trailing-ws.yaml.txt @@ -0,0 +1,2 @@ +# mode=relaxed +bad/yaml-trailing-ws.yaml:27: example 0 [trailing-whitespace] trailing whitespace diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unclosed-comment.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unclosed-comment.yaml.txt new file mode 100644 index 000000000..9a30ee714 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unclosed-comment.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unclosed-comment.yaml:26: example 0 [unclosed-block-comment] unclosed /* block comment diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr-prefix.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr-prefix.yaml.txt new file mode 100644 index 000000000..8dec6c117 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr-prefix.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unit-addr-prefix.yaml:26: example 0 [unit-address-format] unit address '0x1000' must not have a "0x" prefix diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr.yaml.txt new file mode 100644 index 000000000..b52f0ef20 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unit-addr.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unit-addr.yaml:26: example 0 [unit-address-format] unit address '01000' has leading zeros diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-unused-label.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-unused-label.yaml.txt new file mode 100644 index 000000000..4f00202f0 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-unused-label.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-unused-label.yaml:26: example 0 [unused-labels] label 'dev' defined but never &-referenced diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-value-ws-multiline.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws-multiline.yaml.txt new file mode 100644 index 000000000..3df55b176 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws-multiline.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-value-ws-multiline.yaml:25: example 0 [value-whitespace] extra whitespace inside <...> diff --git a/scripts/dtc/dt-style-selftest/expected/yaml-value-ws.yaml.txt b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws.yaml.txt new file mode 100644 index 000000000..cbb5f88fe --- /dev/null +++ b/scripts/dtc/dt-style-selftest/expected/yaml-value-ws.yaml.txt @@ -0,0 +1,2 @@ +# mode=strict +bad/yaml-value-ws.yaml:28: example 0 [value-whitespace] extra whitespace inside <...> diff --git a/scripts/dtc/dt-style-selftest/good/dts-child-name-order.dts b/scripts/dtc/dt-style-selftest/good/dts-child-name-order.dts new file mode 100644 index 000000000..af0adf5ab --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-child-name-order.dts @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + memory@a0000000 { + device_type = "memory"; + reg = <0x0 0xa0000000 0x0 0x0>; + }; + + pmu { + compatible = "example,pmu"; + + /* Include labels to be sure they do not affect sorting */ + label_bar: bar { + label = "bar"; + }; + + foo: foo { + label = "foo"; + }; + }; + + pmu-2 { + compatible = "example,pmu"; + + /* Just reference labels to avoid strict warnings */ + example,foo = <&foo>, <&label_bar>; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-cont-align.dts b/scripts/dtc/dt-style-selftest/good/dts-cont-align.dts new file mode 100644 index 000000000..36fb4eefc --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-cont-align.dts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: tab-indented .dts with a tab-and-space aligned + * multi-line property. Continuation lines mix tabs for indent and + * spaces for alignment by design; that must not be flagged. + */ + +/dts-v1/; + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller@10000 { + compatible = "example,intc"; + reg = <0x10000 0x1000>; + interrupts = <1 2 3>, + <4 5 6>, + <7 8 9>; + pinmux = < + 0x01 + 0x02 + >; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-digit-node-order.dts b/scripts/dtc/dt-style-selftest/good/dts-digit-node-order.dts new file mode 100644 index 000000000..cdf3f91eb --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-digit-node-order.dts @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + /* TODO: uncomment when child-address-order is fixed for top-level */ + /* + memory@a0000000 { + device_type = "memory"; + reg = <0x0 0xa0000000 0x0 0x0>; + }; + */ + + pmu { + compatible = "example,pmu"; + }; + + soc@0 { + compatible = "simple-bus"; + ranges = <0 0 0 0xc0000000>; + + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller@10000 { + compatible = "example,intc"; + reg = <0x10000 0x1000>; + interrupts = <1 2 3>; + }; + + serial@20000 { + compatible = "example,serial"; + reg = <0x20000 0x1000>; + }; + + serial@30000 { + compatible = "example,serial"; + reg = <0x30000 0x1000>; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-dtc.dts b/scripts/dtc/dt-style-selftest/good/dts-dtc.dts new file mode 100644 index 000000000..a32071962 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-dtc.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: dtc directive + */ + +/dts-v1/; + +/include/ "soc.dtsi" +/include/"soc-other.dtsi" + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; + + leds { + led-0 { + compatible = "example,led"; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-extend-node-child-name-order.dts b/scripts/dtc/dt-style-selftest/good/dts-extend-node-child-name-order.dts new file mode 100644 index 000000000..97d468051 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-extend-node-child-name-order.dts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + pmu { + compatible = "example,pmu"; + + /* Just reference labels to avoid strict warnings */ + example,foo = <&foo>, <&label_bar>; + }; +}; + +&pmu { + /* Include labels to be sure they do not affect sorting */ + label_bar: bar { + label = "bar"; + }; + + foo: foo { + label = "foo"; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-extend-node-digit-node-order.dts b/scripts/dtc/dt-style-selftest/good/dts-extend-node-digit-node-order.dts new file mode 100644 index 000000000..faf49962f --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-extend-node-digit-node-order.dts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + soc: soc@0 { + compatible = "simple-bus"; + ranges = <0 0 0 0xc0000000>; + + #address-cells = <1>; + #size-cells = <1>; + }; +}; + +&soc { + interrupt-controller@10000 { + compatible = "example,intc"; + reg = <0x10000 0x1000>; + interrupts = <1 2 3>; + }; + + serial@20000 { + compatible = "example,serial"; + reg = <0x20000 0x1000>; + }; + + serial@30000 { + compatible = "example,serial"; + reg = <0x30000 0x1000>; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-preprocessor.dts b/scripts/dtc/dt-style-selftest/good/dts-preprocessor.dts new file mode 100644 index 000000000..bd6d9ddde --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-preprocessor.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: preprocessor directive + */ + +/dts-v1/; + +#include "soc.dtsi" +#include<dt-bindings/gpio/gpio.h> + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; + + leds { + led-0 { + compatible = "example,led"; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-property-order.dts b/scripts/dtc/dt-style-selftest/good/dts-property-order.dts new file mode 100644 index 000000000..0e183e345 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-property-order.dts @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: Incorrect property order + */ + +/dts-v1/; + +/ { + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a57"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + }; + + pmu { + compatible = "example,pmu"; + dma-coherent; + + status = "disabled"; + }; + + soc@0 { + compatible = "simple-bus"; + ranges = <0 0 0 0xc0000000>; + + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller@10000 { + compatible = "example,intc"; + reg = <0x10000 0x1000>; + interrupts = <1 2 3>; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/dts-tab.dts b/scripts/dtc/dt-style-selftest/good/dts-tab.dts new file mode 100644 index 000000000..ab7b5d124 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/dts-tab.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * Test fixture: a properly formatted .dts using one-tab indent. + */ + +/dts-v1/; + +/ { + compatible = "example,test-board"; + #address-cells = <1>; + #size-cells = <1>; + + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@100 { + compatible = "example,test"; + reg = <0x100 0x10>; + }; + + device@200 { + compatible = "example,test"; + reg = <0x200 0x10>; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/soc.dtsi b/scripts/dtc/dt-style-selftest/good/soc.dtsi new file mode 100644 index 000000000..deec1a973 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/soc.dtsi @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a57"; + reg = <0x0 0x0>; + enable-method = "psci"; + }; + }; + + pmu { + compatible = "example,pmu"; + }; + + soc@0 { + compatible = "simple-bus"; + ranges = <0 0 0 0xc0000000>; + + #address-cells = <1>; + #size-cells = <1>; + + interrupt-controller@10000 { + compatible = "example,intc"; + reg = <0x10000 0x1000>; + interrupts = <1 2 3>, + <4 5 6>, + <7 8 9>; + }; + + serial@20000 { + compatible = "example,serial"; + reg = <0x20000 0x1000>; + }; + + serial@30000 { + compatible = "example,serial"; + reg = <0x30000 0x1000>; + }; + }; +}; diff --git a/scripts/dtc/dt-style-selftest/good/yaml-4space.yaml b/scripts/dtc/dt-style-selftest/good/yaml-4space.yaml new file mode 100644 index 000000000..1502f803c --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/yaml-4space.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-good-4space.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture for dt-check-style + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-4space + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + bus@10000 { + compatible = "simple-bus"; + reg = <0x10000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + + device@100 { + compatible = "example,test-4space"; + reg = <0x100 0x10>; + }; + + device@200 { + compatible = "example,test-4space"; + reg = <0x200 0x10>; + }; + }; diff --git a/scripts/dtc/dt-style-selftest/good/yaml-tricky-parsing.yaml b/scripts/dtc/dt-style-selftest/good/yaml-tricky-parsing.yaml new file mode 100644 index 000000000..a836d5f36 --- /dev/null +++ b/scripts/dtc/dt-style-selftest/good/yaml-tricky-parsing.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/test-good-tricky-parsing.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test fixture exercising parser corner cases + +description: | + Covers patterns that previously broke the classifier: + - "/* ... */ code;" with code following the closing of a one-line + block comment must still parse the code. + - a label on a node whose name starts with a digit (1wire@10). + - a multi-line C preprocessor macro using backslash continuations + must not be parsed as DTS lines. + - a /* comment that opens and closes on the same code line must + not leave the parser in block-comment state. + +maintainers: + - Test User <test@example.com> + +properties: + compatible: + const: example,test-tricky-parsing + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #define MY_REG(a, b) \ + ((a) << 16 | \ + (b) << 0) + + one_wire: 1wire@10 { + compatible = "example,test-tricky-parsing"; + reg = <MY_REG(0x10, 0)>; + /* inline-closed */ status = "okay"; + }; + + other: device@20 { + compatible = "example,test-tricky-parsing"; + reg = <0x20 0x10>; + }; + + &one_wire { + status = "okay"; + }; + + &other { + status = "okay"; + }; diff --git a/scripts/dtc/dt-style-selftest/run.sh b/scripts/dtc/dt-style-selftest/run.sh new file mode 100755 index 000000000..8117dd9be --- /dev/null +++ b/scripts/dtc/dt-style-selftest/run.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Run dt-check-style against fixtures under good/ and bad/. +# good/ files must produce no output and exit 0 in both modes. +# bad/ files must produce the expected output (in expected/<name>.txt) +# and exit 1. +# +# The mode used for a bad fixture is whichever produces a violation: +# trailing-whitespace and tab fixtures use the default (relaxed), +# the rest use --mode=strict. The expected output files name the +# mode in their first line. + +set -u + +here=$(cd "$(dirname "$0")" && pwd) +tool="$here/../dt-check-style" +fail=0 + +run() { + file=$1 + mode=$2 + "$tool" --mode="$mode" "$file" 2>&1 +} + +# good/ -- must exit 0 and produce no output in both modes +for f in "$here"/good/*; do + [ -e "$f" ] || continue + for mode in relaxed strict; do + out=$(run "$f" "$mode") + rc=$? + if [ -n "$out" ] || [ "$rc" -ne 0 ]; then + echo "FAIL good/$mode: $(basename "$f") (exit $rc, want 0):" + echo "$out" | sed 's/^/ /' + fail=$((fail + 1)) + fi + done +done + +# bad/ -- must match expected/<name>.txt +for f in "$here"/bad/*; do + [ -e "$f" ] || continue + name=$(basename "$f") + expected="$here/expected/$name.txt" + if [ ! -f "$expected" ]; then + echo "FAIL bad: missing $expected" + fail=$((fail + 1)) + continue + fi + mode=$(head -1 "$expected" | sed 's/^# mode=//') + body=$(tail -n +2 "$expected") + out=$(run "$f" "$mode") + rc=$? + # Strip the directory prefix so expected files are portable. + out=$(printf '%s\n' "$out" | sed "s|$here/bad/|bad/|g") + if [ "$out" != "$body" ] || [ "$rc" -ne 1 ]; then + echo "FAIL bad/$mode: $name (exit $rc, want 1):" + bf=$(mktemp) + printf '%s\n' "$body" > "$bf" + printf '%s\n' "$out" | diff -u "$bf" - | sed 's/^/ /' + rm -f "$bf" + fail=$((fail + 1)) + fi +done + +if [ "$fail" -eq 0 ]; then + echo "PASS" + exit 0 +fi +echo "FAILED ($fail)" +exit 1 diff --git a/scripts/dtc/dt_to_config b/scripts/dtc/dt_to_config new file mode 100755 index 000000000..70d6d5f06 --- /dev/null +++ b/scripts/dtc/dt_to_config @@ -0,0 +1,1212 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-only + +# Copyright 2016 by Frank Rowand +# Copyright 2016 by Gaurav Minocha +# + +use strict 'refs'; +use strict subs; + +use Getopt::Long; + +$VUFX = "160610a"; + +$script_name = $0; +$script_name =~ s|^.*/||; + + +# ----- constants for print_flags() + +# Position in string $pr_flags. Range of 0..($num_pr_flags - 1). +$pr_flag_pos_mcompatible = 0; +$pr_flag_pos_driver = 1; +$pr_flag_pos_mdriver = 2; +$pr_flag_pos_config = 3; +$pr_flag_pos_mconfig = 4; +$pr_flag_pos_node_not_enabled = 5; +$pr_flag_pos_white_list = 6; +$pr_flag_pos_hard_coded = 7; +$pr_flag_pos_config_hard_coded = 8; +$pr_flag_pos_config_none = 9; +$pr_flag_pos_config_m = 10; +$pr_flag_pos_config_y = 11; +$pr_flag_pos_config_test_fail = 12; + +$num_pr_flags = $pr_flag_pos_config_test_fail + 1; + +# flags in @pr_flag_value must be unique values to allow simple regular +# expessions to work for --include_flags and --exclude_flags. +# Convention: use upper case letters for potential issues or problems. + +@pr_flag_value = ('M', 'd', 'D', 'c', 'C', 'E', 'W', 'H', 'x', 'n', 'm', 'y', 'F'); + +@pr_flag_help = ( + "multiple compatibles found for this node", + "driver found for this compatible", + "multiple drivers found for this compatible", + "kernel config found for this driver", + "multiple config options found for this driver", + "node is not enabled", + "compatible is white listed", + "matching driver and/or kernel config is hard coded", + "kernel config hard coded in Makefile", + "one or more kernel config file options are not set", + "one or more kernel config file options are set to 'm'", + "one or more kernel config file options are set to 'y'", + "one or more kernel config file options fail to have correct value" +); + + +# ----- + +%driver_config = (); # driver config array, indexed by driver source file +%driver_count = (); # driver_cnt, indexed by compatible +%compat_driver = (); # compatible driver array, indexed by compatible +%existing_config = (); # existing config symbols present in given config file + # expected values are: "y", "m", a decimal number, a + # hex number, or a string + +# ----- magic compatibles, do not have a driver +# +# Will not search for drivers for these compatibles. + +%compat_white_list = ( + 'none' => '1', + 'pci' => '1', + 'simple-bus' => '1', +); + +# Will not search for drivers for these compatibles. +# +# These compatibles have a very large number of false positives. +# +# 'hardcoded_no_driver' is a magic value. Other code knows this +# magic value. Do not use 'no_driver' here! +# +# Revisit each 'hardcoded_no_driver' to see how the compatible +# is used. Are there drivers that can be provided? + +%driver_hard_code_list = ( + 'cache' => ['hardcoded_no_driver'], + 'eeprom' => ['hardcoded_no_driver'], + 'gpio' => ['hardcoded_no_driver'], + 'gpio-keys' => ['drivers/input/keyboard/gpio_keys.c'], + 'i2c-gpio' => ['drivers/i2c/busses/i2c-gpio.c'], + 'isa' => ['arch/mips/mti-malta/malta-dt.c', + 'arch/x86/kernel/devicetree.c'], + 'led' => ['hardcoded_no_driver'], + 'm25p32' => ['hardcoded_no_driver'], + 'm25p64' => ['hardcoded_no_driver'], + 'm25p80' => ['hardcoded_no_driver'], + 'mtd-ram' => ['drivers/mtd/maps/physmap_of.c'], + 'pwm-backlight' => ['drivers/video/backlight/pwm_bl.c'], + 'spidev' => ['hardcoded_no_driver'], + 'syscon' => ['drivers/mfd/syscon.c'], + 'tlv320aic23' => ['hardcoded_no_driver'], + 'wm8731' => ['hardcoded_no_driver'], +); + +# Use these config options instead of searching makefiles + +%driver_config_hard_code_list = ( + + # this one needed even if %driver_hard_code_list is empty + 'no_driver' => ['no_config'], + 'hardcoded_no_driver' => ['no_config'], + + # drivers/usb/host/ehci-ppc-of.c + # drivers/usb/host/ehci-xilinx-of.c + # are included from: + # drivers/usb/host/ehci-hcd.c + # thus the search of Makefile for the included .c files is incorrect + # ehci-hcd.c wraps the includes with ifdef CONFIG_USB_EHCI_HCD_..._OF + # + # similar model for ohci-hcd.c (but no ohci-xilinx-of.c) + # + # similarly, uhci-hcd.c includes uhci-platform.c + + 'drivers/usb/host/ehci-ppc-of.c' => ['CONFIG_USB_EHCI_HCD', + 'CONFIG_USB_EHCI_HCD_PPC_OF'], + 'drivers/usb/host/ohci-ppc-of.c' => ['CONFIG_USB_OHCI_HCD', + 'CONFIG_USB_OHCI_HCD_PPC_OF'], + + 'drivers/usb/host/ehci-xilinx-of.c' => ['CONFIG_USB_EHCI_HCD', + 'CONFIG_USB_EHCI_HCD_XILINX'], + + 'drivers/usb/host/uhci-platform.c' => ['CONFIG_USB_UHCI_HCD', + 'CONFIG_USB_UHCI_PLATFORM'], + + # scan_makefile will find only one of these config options: + # ifneq ($(CONFIG_SOC_IMX6)$(CONFIG_SOC_LS1021A),) + 'arch/arm/mach-imx/platsmp.c' => ['CONFIG_SOC_IMX6 && CONFIG_SMP', + 'CONFIG_SOC_LS1021A && CONFIG_SMP'], +); + + +# 'virt/kvm/arm/.*' are controlled by makefiles in other directories, +# using relative paths, such as 'KVM := ../../../virt/kvm'. Do not +# add complexity to find_kconfig() to deal with this. There is a long +# term intent to change the kvm related makefiles to the normal kernel +# style. After that is done, this entry can be removed from the +# black_list_driver. + +@black_list_driver = ( + # kvm no longer a problem after commit 503a62862e8f in 4.7-rc1 + # 'virt/kvm/arm/.*', +); + + +sub usage() +{ + print +" +Usage: $script_name [options] device-tree... + + device_tree is: dts_file | dtb_file | proc_device-tree + + +Valid options: + -c FILE Read kernel config options from FILE + --config FILE synonym for 'c' + --config-format config file friendly output format + --exclude-flag FLAG exclude entries with a matching flag + -h Display this message and exit + --help synonym for 'h' + --black-list-driver use driver black list + --white-list-config use config white list + --white-list-driver use driver white list + --include-flag FLAG include only entries with a matching flag + --include-suspect include only entries with an uppercase flag + --short-name do not show the path portion of the node name + --show-lists report of white and black lists + --version Display program version and exit + + + Report driver source files that match the compatibles in the device + tree file and the kernel config options that enable the driver source + files. + + This program must be run in the root directory of a Linux kernel + source tree. + + The default format is a report that is intended to be easily human + scannable. + + An alternate format can be selected by --config-format. This will + create output that can easily be edited to create a fragment that can + be appended to the existing kernel config file. Each entry consists of + multiple lines. The first line reports flags, the node path, compatible + value, driver file matching the compatible, configuration options, and + current values of the configuration options. For each configuration + option, the following lines report the current value and the value that + is required for the driver file to be included in the kernel. + + If a large number of drivers or config options is listed for a node, + and the '$pr_flag_value[$pr_flag_pos_hard_coded]' flag is set consider using --white-list-config and/or + --white-list-driver. If the white list option suppresses the correct + entry please report that as a bug. + + CAUTION: + This program uses heuristics to guess which driver(s) support each + compatible string and which config option(s) enables the driver(s). + Do not believe that the reported information is fully correct. + This program is intended to aid the process of determining the + proper kernel configuration for a device tree, but this is not + a fully automated process -- human involvement may still be + required! + + The driver match heuristic used is to search for source files + containing the compatible string enclosed in quotes. + + This program might not be able to find all drivers matching a + compatible string. + + Some makefiles are overly clever. This program was not made + complex enough to handle them. If no config option is listed + for a driver, look at the makefile for the driver source file. + Even if a config option is listed for a driver, some other + available config options may not be listed. + + FLAG values: +"; + + for ($k = 0; $k < $num_pr_flags; $k++) { + printf " %s %s\n", $pr_flag_value[$k], $pr_flag_help[$k]; + } + + print +" + Upper case letters indicate potential issues or problems. + + The flag: + +"; + + $k = $pr_flag_pos_hard_coded; + printf " %s %s\n", $pr_flag_value[$k], $pr_flag_help[$k]; + + print +" + will be set if the config or driver is in the white lists, even if + --white-list-config and --white-list-driver are not specified. + This is a hint that 1) many of these reported lines are likely to + be incorrect, and 2) using those options will reduce the number of + drivers and/or config options reported. + + --white-list-config and --white-list-driver may not be accurate if this + program is not well maintained. Use them with appropriate skepticism. + Use the --show-lists option to report the values in the list. + + Return value: + 0 if no error + 1 error processing command line + 2 unable to open or read kernel config file + 3 unable to open or process input device tree file(s) + + EXAMPLES: + + dt_to_config arch/arm/boot/dts/my_dts_file.dts + + Basic report. + + dt_to_config \\ + --config \${KBUILD_OUTPUT}/.config \\ + arch/\${ARCH}/boot/dts/my_dts_file.dts + + Full report, with config file issues noted. + + dt_to_config --include-suspect \\ + --config \${KBUILD_OUTPUT}/.config \\ + arch/\${ARCH}/boot/dts/my_dts_file.dts + + Report of node / compatible string / driver tuples that should + be further investigated. A node may have multiple compatible + strings. A compatible string may be matched by multiple drivers. + A driver may have config file issues noted. The compatible string + and/or driver may be in the white lists. + + dt_to_config --include-suspect --config-format \\ + --config ${KBUILD_OUTPUT}/.config \\ + arch/\${ARCH}/boot/dts/my_dts_file.dts + + Report of node / compatible string / driver tuples that should + be further investigated. The report can be edited to uncomment + the config options to select the desired tuple for a given node. + A node may have multiple compatible strings. A compatible string + may be matched by multiple drivers. A driver may have config file + issues noted. The compatible string and/or driver may be in the + white lists. + +"; +} + +sub set_flag() +{ + # pr_flags_ref is a reference to $pr_flags + + my $pr_flags_ref = shift; + my $pos = shift; + + substr $$pr_flags_ref, $pos, 1, $pr_flag_value[$pos]; + + return $pr_flags; +} + +sub print_flags() +{ + # return 1 if anything printed, else 0 + + # some fields of pn_arg_ref might not be used in this function, but + # extract all of them anyway. + my $pn_arg_ref = shift; + + my $compat = $pn_arg_ref->{compat}; + my $compatible_cnt = $pn_arg_ref->{compatible_cnt}; + my $config = $pn_arg_ref->{config}; + my $config_cnt = $pn_arg_ref->{config_cnt}; + my $driver = $pn_arg_ref->{driver}; + my $driver_cnt = $pn_arg_ref->{driver_cnt}; + my $full_node = $pn_arg_ref->{full_node}; + my $node = $pn_arg_ref->{node}; + my $node_enabled = $pn_arg_ref->{node_enabled}; + my $white_list = $pn_arg_ref->{white_list}; + + my $pr_flags = '-' x $num_pr_flags; + + + # ----- set flags in $pr_flags + + if ($compatible_cnt > 1) { + &set_flag(\$pr_flags, $pr_flag_pos_mcompatible); + } + + if ($config_cnt > 1) { + &set_flag(\$pr_flags, $pr_flag_pos_mconfig); + } + + if ($driver_cnt >= 1) { + &set_flag(\$pr_flags, $pr_flag_pos_driver); + } + + if ($driver_cnt > 1) { + &set_flag(\$pr_flags, $pr_flag_pos_mdriver); + } + + # These strings are the same way the linux kernel tests. + # The ePapr lists of values is slightly different. + if (!( + ($node_enabled eq "") || + ($node_enabled eq "ok") || + ($node_enabled eq "okay") + )) { + &set_flag(\$pr_flags, $pr_flag_pos_node_not_enabled); + } + + if ($white_list) { + &set_flag(\$pr_flags, $pr_flag_pos_white_list); + } + + if (exists($driver_hard_code_list{$compat}) || + (exists($driver_config_hard_code_list{$driver}) && + ($driver ne "no_driver"))) { + &set_flag(\$pr_flags, $pr_flag_pos_hard_coded); + } + + my @configs = split(' && ', $config); + for $configs (@configs) { + $not = $configs =~ /^!/; + $configs =~ s/^!//; + + if (($configs ne "no_config") && ($configs ne "no_makefile")) { + &set_flag(\$pr_flags, $pr_flag_pos_config); + } + + if (($config_cnt >= 1) && + ($configs !~ /CONFIG_/) && + (($configs ne "no_config") && ($configs ne "no_makefile"))) { + &set_flag(\$pr_flags, $pr_flag_pos_config_hard_coded); + } + + my $existing_config = $existing_config{$configs}; + if ($existing_config eq "m") { + &set_flag(\$pr_flags, $pr_flag_pos_config_m); + # Possible fail, depends on whether built in or + # module is desired. + &set_flag(\$pr_flags, $pr_flag_pos_config_test_fail); + } elsif ($existing_config eq "y") { + &set_flag(\$pr_flags, $pr_flag_pos_config_y); + if ($not) { + &set_flag(\$pr_flags, $pr_flag_pos_config_test_fail); + } + } elsif (($config_file) && ($configs =~ /CONFIG_/)) { + &set_flag(\$pr_flags, $pr_flag_pos_config_none); + if (!$not) { + &set_flag(\$pr_flags, $pr_flag_pos_config_test_fail); + } + } + } + + # ----- include / exclude filters + + if ($include_flag_pattern && ($pr_flags !~ m/$include_flag_pattern/)) { + return 0; + } + + if ($exclude_flag_pattern && ($pr_flags =~ m/$exclude_flag_pattern/)) { + return 0; + } + + if ($config_format) { + print "# "; + } + print "$pr_flags : "; + + return 1; +} + + +sub print_node() +{ + # return number of lines printed + + # some fields of pn_arg_ref might not be used in this function, but + # extract all of them anyway. + my $pn_arg_ref = shift; + + my $compat = $pn_arg_ref->{compat}; + my $compatible_cnt = $pn_arg_ref->{compatible_cnt}; + my $config = $pn_arg_ref->{config}; + my $config_cnt = $pn_arg_ref->{config_cnt}; + my $driver = $pn_arg_ref->{driver}; + my $driver_cnt = $pn_arg_ref->{driver_cnt}; + my $full_node = $pn_arg_ref->{full_node}; + my $node = $pn_arg_ref->{node}; + my $node_enabled = $pn_arg_ref->{node_enabled}; + my $white_list = $pn_arg_ref->{white_list}; + + my $separator; + + if (! &print_flags($pn_arg_ref)) { + return 0; + } + + + if ($short_name) { + print "$node"; + } else { + print "$full_node"; + } + print " : $compat : $driver : $config : "; + + my @configs = split(' && ', $config); + + if ($config_file) { + for $configs (@configs) { + $configs =~ s/^!//; + my $existing_config = $existing_config{$configs}; + if (!$existing_config) { + # check for /-m/, /-y/, or /-objs/ + if ($configs !~ /CONFIG_/) { + $existing_config = "x"; + }; + }; + if ($existing_config) { + print "$separator", "$existing_config"; + $separator = ", "; + } else { + print "$separator", "n"; + $separator = ", "; + } + } + } else { + print "none"; + } + + print "\n"; + + if ($config_format) { + for $configs (@configs) { + $not = $configs =~ /^!/; + $configs =~ s/^!//; + my $existing_config = $existing_config{$configs}; + + if ($not) { + if ($configs !~ /CONFIG_/) { + print "# $configs\n"; + } elsif ($existing_config eq "m") { + print "# $configs is m\n"; + print "# $configs=n\n"; + } elsif ($existing_config eq "y") { + print "# $configs is set\n"; + print "# $configs=n\n"; + } else { + print "# $configs is not set\n"; + print "# $configs=n\n"; + } + + } else { + if ($configs !~ /CONFIG_/) { + print "# $configs\n"; + } elsif ($existing_config eq "m") { + print "# $configs is m\n"; + print "# $configs=y\n"; + } elsif ($existing_config eq "y") { + print "# $configs is set\n"; + print "# $configs=y\n"; + } else { + print "# $configs is not set\n"; + print "# $configs=y\n"; + } + } + } + } + + return 1; +} + + +sub scan_makefile +{ + my $pn_arg_ref = shift; + my $driver = shift; + + # ----- Find Kconfig symbols that enable driver + + my ($dir, $base) = $driver =~ m{(.*)/(.*).c}; + + my $makefile = $dir . "/Makefile"; + if (! -r $makefile) { + $makefile = $dir . "/Kbuild"; + } + if (! -r $makefile) { + my $config; + + $config = 'no_makefile'; + push @{ $driver_config{$driver} }, $config; + return; + } + + if (!open(MAKEFILE_FILE, "<", "$makefile")) { + return; + } + + my $line; + my @config; + my @if_config; + my @make_var; + + NEXT_LINE: + while ($next_line = <MAKEFILE_FILE>) { + my $config; + my $if_config; + my $ifdef; + my $ifeq; + my $ifndef; + my $ifneq; + my $ifdef_config; + my $ifeq_config; + my $ifndef_config; + my $ifneq_config; + + chomp($next_line); + $line = $line . $next_line; + if ($next_line =~ /\\$/) { + $line =~ s/\\$/ /; + next NEXT_LINE; + } + if ($line =~ /^\s*#/) { + $line = ""; + next NEXT_LINE; + } + + # ----- condition ... else ... endif + + if ($line =~ /^([ ]\s*|)else\b/) { + $if_config = "!" . pop @if_config; + $if_config =~ s/^!!//; + push @if_config, $if_config; + $line =~ s/^([ ]\s*|)else\b//; + } + + ($null, $ifeq_config, $ifeq_config_val ) = $line =~ /^([ ]\s*|)ifeq\b.*\b(CONFIG_[A-Za-z0-9_]*)(.*)/; + ($null, $ifneq_config, $ifneq_config_val) = $line =~ /^([ ]\s*|)ifneq\b.*\b(CONFIG_[A-Za-z0-9_]*)(.*)/; + ($null, $ifdef_config) = $line =~ /^([ ]\s*|)ifdef\b.*\b(CONFIG_[A-Za-z0-9_]*)/; + ($null, $ifndef_config) = $line =~ /^([ ]\s*|)ifndef\b.*\b(CONFIG_[A-Za-z0-9_]*)/; + + ($null, $ifeq) = $line =~ /^([ ]\s*|)ifeq\b\s*(.*)/; + ($null, $ifneq) = $line =~ /^([ ]\s*|)ifneq\b\s*(.*)/; + ($null, $ifdef) = $line =~ /^([ ]\s*|)ifdef\b\s*(.*)/; + ($null, $ifndef) = $line =~ /^([ ]\s*|)ifndef\b\s*(.*)/; + + # Order of tests is important. Prefer "CONFIG_*" regex match over + # less specific regex match. + if ($ifdef_config) { + $if_config = $ifdef_config; + } elsif ($ifeq_config) { + if ($ifeq_config_val =~ /y/) { + $if_config = $ifeq_config; + } else { + $if_config = "!" . $ifeq_config; + } + } elsif ($ifndef_config) { + $if_config = "!" . $ifndef_config; + } elsif ($ifneq_config) { + if ($ifneq_config_val =~ /y/) { + $if_config = "!" . $ifneq_config; + } else { + $if_config = $ifneq_config; + } + } elsif ($ifdef) { + $if_config = $ifdef; + } elsif ($ifeq) { + $if_config = $ifeq; + } elsif ($ifndef) { + $if_config = "!" . $ifndef; + } elsif ($ifneq) { + $if_config = "!" . $ifneq; + } else { + $if_config = ""; + } + $if_config =~ s/^!!//; + + if ($if_config) { + push @if_config, $if_config; + $line = ""; + next NEXT_LINE; + } + + if ($line =~ /^([ ]\s*|)endif\b/) { + pop @if_config; + $line = ""; + next NEXT_LINE; + } + + # ----- simple CONFIG_* = *.[co] or xxx [+:?]*= *.[co] + # Most makefiles select on *.o, but + # arch/powerpc/boot/Makefile selects on *.c + + ($config) = $line =~ /(CONFIG_[A-Za-z0-9_]+).*\b$base.[co]\b/; + + # ----- match a make variable instead of *.[co] + # Recursively expanded variables are not handled. + + if (!$config) { + my $make_var; + ($make_var) = $line =~ /\s*(\S+?)\s*[+:\?]*=.*\b$base.[co]\b/; + if ($make_var) { + if ($make_var =~ /[a-zA-Z0-9]+-[ym]/) { + $config = $make_var; + } elsif ($make_var =~ /[a-zA-Z0-9]+-objs/) { + $config = $make_var; + } else { + push @make_var, $make_var; + } + } + } + + if (!$config) { + for $make_var (@make_var) { + ($config) = $line =~ /(CONFIG_[A-Za-z0-9_]+).*\b$make_var\b/; + last if ($config); + } + } + + if (!$config) { + for $make_var (@make_var) { + ($config) = $line =~ /\s*(\S+?)\s*[+:\?]*=.*\b$make_var\b/; + last if ($config); + } + } + + # ----- next if no config found + + if (!$config) { + $line = ""; + next NEXT_LINE; + } + + for $if_config (@if_config) { + $config = $if_config . " && " . $config; + } + + push @{ $driver_config{$driver} }, $config; + + $line = ""; + } + + close(MAKEFILE_FILE); + +} + + +sub find_kconfig +{ + my $pn_arg_ref = shift; + my $driver = shift; + + my $lines_printed = 0; + my @configs; + + if (!@{ $driver_config{$driver} }) { + &scan_makefile($pn_arg_ref, $driver); + if (!@{ $driver_config{$driver} }) { + push @{ $driver_config{$driver} }, "no_config"; + } + } + + @configs = @{ $driver_config{$driver} }; + + $$pn_arg_ref{config_cnt} = $#configs + 1; + for my $config (@configs) { + $$pn_arg_ref{config} = $config; + $lines_printed += &print_node($pn_arg_ref); + } + + return $lines_printed; +} + + +sub handle_compatible() +{ + my $full_node = shift; + my $node = shift; + my $compatible = shift; + my $node_enabled = shift; + + my $compat; + my $lines_printed = 0; + my %pn_arg = (); + + return if (!$node or !$compatible); + + # Do not process compatible property of root node, + # it is used to match board, not to bind a driver. + return if ($node eq "/"); + + $pn_arg{full_node} = $full_node; + $pn_arg{node} = $node; + $pn_arg{node_enabled} = $node_enabled; + + my @compatibles = split('", "', $compatible); + + $compatibles[0] =~ s/^"//; + $compatibles[$#compatibles] =~ s/"$//; + + $pn_arg{compatible_cnt} = $#compatibles + 1; + + COMPAT: + for $compat (@compatibles) { + + $pn_arg{compat} = $compat; + $pn_arg{driver_cnt} = 0; + $pn_arg{white_list} = 0; + + if (exists($compat_white_list{$compat})) { + $pn_arg{white_list} = 1; + $pn_arg{driver} = "no_driver"; + $pn_arg{config_cnt} = 1; + $pn_arg{config} = "no_config"; + $lines_printed += &print_node(\%pn_arg); + next COMPAT; + } + + # ----- if compat previously seen, use cached info + + if (exists($compat_driver{$compat})) { + for my $driver (@{ $compat_driver{$compat} }) { + $pn_arg{driver} = $driver; + $pn_arg{driver_cnt} = $driver_count{$compat}; + $pn_arg{config_cnt} = $#{ $driver_config{$driver}} + 1; + + for my $config (@{ $driver_config{$driver} }) { + $pn_arg{config} = $config; + $lines_printed += &print_node(\%pn_arg); + } + + if (!@{ $driver_config{$driver} }) { + # no config cached yet + # $driver in %driver_hard_code_list + # but not %driver_config_hard_code_list + $lines_printed += &find_kconfig(\%pn_arg, $driver); + } + } + next COMPAT; + } + + + # ----- Find drivers (source files that contain compatible) + + # this will miss arch/sparc/include/asm/parport.h + # It is better to move the compatible out of the .h + # than to add *.h. to the files list, because *.h generates + # a lot of false negatives. + my $files = '"*.c"'; + my $drivers = `git grep -l '"$compat"' -- $files`; + chomp($drivers); + if ($drivers eq "") { + $pn_arg{driver} = "no_driver"; + $pn_arg{config_cnt} = 1; + $pn_arg{config} = "no_config"; + push @{ $compat_driver{$compat} }, "no_driver"; + $lines_printed += &print_node(\%pn_arg); + next COMPAT; + } + + my @drivers = split("\n", $drivers); + $driver_count{$compat} = $#drivers + 1; + $pn_arg{driver_cnt} = $#drivers + 1; + + DRIVER: + for my $driver (@drivers) { + push @{ $compat_driver{$compat} }, $driver; + $pn_arg{driver} = $driver; + + # ----- if driver previously seen, use cached info + + $pn_arg{config_cnt} = $#{ $driver_config{$driver} } + 1; + for my $config (@{ $driver_config{$driver} }) { + $pn_arg{config} = $config; + $lines_printed += &print_node(\%pn_arg); + } + if (@{ $driver_config{$driver} }) { + next DRIVER; + } + + if ($black_list_driver) { + for $black (@black_list_driver) { + next DRIVER if ($driver =~ /^$black$/); + } + } + + + # ----- Find Kconfig symbols that enable driver + + $lines_printed += &find_kconfig(\%pn_arg, $driver); + + } + } + + # White space (line) between nodes for readability. + # Each node may report several compatibles. + # For each compatible, multiple drivers may be reported. + # For each driver, multiple CONFIG_ options may be reported. + if ($lines_printed) { + print "\n"; + } +} + +sub read_dts() +{ + my $file = shift; + + my $compatible = ""; + my $line; + my $node = ""; + my $node_enabled = ""; + + if (! -r $file) { + print STDERR "file '$file' is not readable or does not exist\n"; + exit 3; + } + + if (!open(DT_FILE, "-|", "$dtx_diff $file")) { + print STDERR "\n"; + print STDERR "shell command failed:\n"; + print STDERR " $dtx_diff $file\n"; + print STDERR "\n"; + exit 3; + } + + FILE: + while ($line = <DT_FILE>) { + chomp($line); + + if ($line =~ /{/) { + + &handle_compatible($full_node, $node, $compatible, + $node_enabled); + + while ($end_node_count-- > 0) { + pop @full_node; + }; + $end_node_count = 0; + $full_node = @full_node[-1]; + + $node = $line; + $node =~ s/^\s*(.*)\s+\{.*/$1/; + $node =~ s/.*: //; + if ($node eq '/' ) { + $full_node = '/'; + } elsif ($full_node ne '/') { + $full_node = $full_node . '/' . $node; + } else { + $full_node = '/' . $node; + } + push @full_node, $full_node; + + $compatible = ""; + $node_enabled = ""; + next FILE; + } + + if ($line =~ /}/) { + $end_node_count++; + } + + if ($line =~ /(\s+|^)status =/) { + $node_enabled = $line; + $node_enabled =~ s/^\t*//; + $node_enabled =~ s/^status = "//; + $node_enabled =~ s/";$//; + next FILE; + } + + if ($line =~ /(\s+|^)compatible =/) { + # Extract all compatible entries for this device + # White space matching here and in handle_compatible() is + # precise, because input format is the output of dtc, + # which is invoked by dtx_diff. + $compatible = $line; + $compatible =~ s/^\t*//; + $compatible =~ s/^compatible = //; + $compatible =~ s/;$//; + } + } + + &handle_compatible($full_node, $node, $compatible, $node_enabled); + + close(DT_FILE); +} + + +sub read_config_file() +{ + if (! -r $config_file) { + print STDERR "file '$config_file' is not readable or does not exist\n"; + exit 2; + } + + if (!open(CONFIG_FILE, "<", "$config_file")) { + print STDERR "open $config_file failed\n"; + exit 2; + } + + my @line; + + LINE: + while ($line = <CONFIG_FILE>) { + chomp($line); + next LINE if ($line =~ /^\s*#/); + next LINE if ($line =~ /^\s*$/); + @line = split /=/, $line; + $existing_config{@line[0]} = @line[1]; + } + + close(CONFIG_FILE); +} + + +sub cmd_line_err() +{ + my $msg = shift; + + print STDERR "\n"; + print STDERR " ERROR processing command line options\n"; + print STDERR " $msg\n" if ($msg ne ""); + print STDERR "\n"; + print STDERR " For help, type '$script_name --help'\n"; + print STDERR "\n"; +} + + +# ----------------------------------------------------------------------------- +# program entry point + +Getopt::Long::Configure("no_ignore_case", "bundling"); + +if (!GetOptions( + "c=s" => \$config_file, + "config=s" => \$config_file, + "config-format" => \$config_format, + "exclude-flag=s" => \@exclude_flag, + "h" => \$help, + "help" => \$help, + "black-list-driver" => \$black_list_driver, + "white-list-config" => \$white_list_config, + "white-list-driver" => \$white_list_driver, + "include-flag=s" => \@include_flag, + "include-suspect" => \$include_suspect, + "short-name" => \$short_name, + "show-lists" => \$show_lists, + "version" => \$version, + )) { + + &cmd_line_err(); + + exit 1; +} + + +my $exit_after_messages = 0; + +if ($version) { + print STDERR "\n$script_name $VUFX\n\n"; + $exit_after_messages = 1; +} + + +if ($help) { + &usage; + $exit_after_messages = 1; +} + + +if ($show_lists) { + + print "\n"; + print "These compatibles are hard coded to have no driver.\n"; + print "\n"; + for my $compat (sort keys %compat_white_list) { + print " $compat\n"; + } + + + print "\n\n"; + print "The driver for these compatibles is hard coded (white list).\n"; + print "\n"; + my $max_compat_len = 0; + for my $compat (sort keys %driver_hard_code_list) { + if (length $compat > $max_compat_len) { + $max_compat_len = length $compat; + } + } + for my $compat (sort keys %driver_hard_code_list) { + if (($driver ne "hardcoded_no_driver") && ($driver ne "no_driver")) { + my $first = 1; + for my $driver (@{ $driver_hard_code_list{$compat} }) { + if ($first) { + print " $compat"; + print " " x ($max_compat_len - length $compat); + $first = 0; + } else { + print " ", " " x $max_compat_len; + } + print " $driver\n"; + } + } + } + + + print "\n\n"; + print "The configuration option for these drivers is hard coded (white list).\n"; + print "\n"; + my $max_driver_len = 0; + for my $driver (sort keys %driver_config_hard_code_list) { + if (length $driver > $max_driver_len) { + $max_driver_len = length $driver; + } + } + for my $driver (sort keys %driver_config_hard_code_list) { + if (($driver ne "hardcoded_no_driver") && ($driver ne "no_driver")) { + my $first = 1; + for my $config (@{ $driver_config_hard_code_list{$driver} }) { + if ($first) { + print " $driver"; + print " " x ($max_driver_len - length $driver); + $first = 0; + } else { + print " ", " " x $max_driver_len; + } + print " $config\n"; + } + } + } + + + print "\n\n"; + print "These drivers are black listed.\n"; + print "\n"; + for my $driver (@black_list_driver) { + print " $driver\n"; + } + + print "\n"; + + $exit_after_messages = 1; +} + + +if ($exit_after_messages) { + exit 0; +} + + +$exclude_flag_pattern = "["; +for my $exclude_flag (@exclude_flag) { + $exclude_flag_pattern = $exclude_flag_pattern . $exclude_flag; +} +$exclude_flag_pattern = $exclude_flag_pattern . "]"; +# clean up if empty +$exclude_flag_pattern =~ s/^\[\]$//; + + +$include_flag_pattern = "["; +for my $include_flag (@include_flag) { + $include_flag_pattern = $include_flag_pattern . $include_flag; +} +$include_flag_pattern = $include_flag_pattern . "]"; +# clean up if empty +$include_flag_pattern =~ s/^\[\]$//; + + +if ($exclude_flag_pattern) { + my $found = 0; + for $pr_flag_value (@pr_flag_value) { + if ($exclude_flag_pattern =~ m/$pr_flag_value/) { + $found = 1; + } + } + if (!$found) { + &cmd_line_err("invalid value for FLAG in --exclude-flag\n"); + exit 1 + } +} + +if ($include_flag_pattern) { + my $found = 0; + for $pr_flag_value (@pr_flag_value) { + if ($include_flag_pattern =~ m/$pr_flag_value/) { + $found = 1; + } + } + if (!$found) { + &cmd_line_err("invalid value for FLAG in --include-flag\n"); + exit 1 + } +} + +if ($include_suspect) { + $include_flag_pattern =~ s/\[//; + $include_flag_pattern =~ s/\]//; + $include_flag_pattern = "[" . $include_flag_pattern . "A-Z]"; +} + +if ($exclude_flag_pattern =~ m/$include_flag_pattern/) { + &cmd_line_err("the same flag appears in both --exclude-flag and --include-flag or --include-suspect\n"); + exit 1 +} + + +# ($#ARGV < 0) is valid for --help, --version +if ($#ARGV < 0) { + &cmd_line_err("device-tree... is required"); + exit 1 +} + + +if ($config_file) { + &read_config_file(); +} + + +# avoid pushing duplicates for this value +$driver = "hardcoded_no_driver"; +for $config ( @{ $driver_config_hard_code_list{$driver} } ) { + push @{ $driver_config{$driver} }, $config; +} + +if ($white_list_driver) { + for my $compat (keys %driver_hard_code_list) { + for my $driver (@{ $driver_hard_code_list{$compat} }) { + push @{ $compat_driver{$compat} }, $driver; + if ($driver ne "hardcoded_no_driver") { + $driver_count{$compat} = scalar @{ $compat_driver{$compat} }; + } + } + } +} + +if ($white_list_config) { + for my $driver (keys %driver_config_hard_code_list) { + if ($driver ne "hardcoded_no_driver") { + for $config ( @{ $driver_config_hard_code_list{$driver} } ) { + push @{ $driver_config{$driver} }, $config; + } + } + } +} + +if (-x "scripts/dtc/dtx_diff") { + $dtx_diff = "scripts/dtc/dtx_diff"; +} else { + + print STDERR "\n"; + print STDERR "$script_name must be run from the root directory of a Linux kernel tree\n"; + print STDERR "\n"; + exit 3; +} + +for $file (@ARGV) { + &read_dts($file); +} diff --git a/scripts/dtc/dtc-lexer.l b/scripts/dtc/dtc-lexer.l new file mode 100644 index 000000000..1b129b118 --- /dev/null +++ b/scripts/dtc/dtc-lexer.l @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +%option noyywrap nounput noinput never-interactive + +%x BYTESTRING +%x PROPNODENAME +%s V1 + +PROPNODECHAR [a-zA-Z0-9,._+*#?@-] +PATHCHAR ({PROPNODECHAR}|[/]) +LABEL [a-zA-Z_][a-zA-Z0-9_]* +STRING \"([^\\"]|\\.)*\" +CHAR_LITERAL '([^']|\\')*' +WS [[:space:]] +COMMENT "/*"([^*]|\*+[^*/])*\*+"/" +LINECOMMENT "//".*\n + +%{ +#include "dtc.h" +#include "srcpos.h" +#include "dtc-parser.tab.h" + +extern bool treesource_error; + +/* CAUTION: this will stop working if we ever use yyless() or yyunput() */ +#define YY_USER_ACTION \ + { \ + srcpos_update(&yylloc, yytext, yyleng); \ + } + +/*#define LEXDEBUG 1*/ + +#ifdef LEXDEBUG +#define DPRINT(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) +#else +#define DPRINT(fmt, ...) do { } while (0) +#endif + +#define BEGIN_DEFAULT() DPRINT("<V1>\n"); \ + BEGIN(V1); \ + +static void push_input_file(const char *filename); +static bool pop_input_file(void); +static void PRINTF(1, 2) lexical_error(const char *fmt, ...); + +%} + +%% +<*>"/include/"{WS}*{STRING} { + char *name = strchr(yytext, '\"') + 1; + yytext[yyleng-1] = '\0'; + push_input_file(name); + } + +<*>^"#"(line)?[ \t]+[0-9]+[ \t]+{STRING}([ \t]+[0-9]+)* { + char *line, *fnstart, *fnend; + struct data fn; + /* skip text before line # */ + line = yytext; + while (!isdigit((unsigned char)*line)) + line++; + + /* regexp ensures that first and list " + * in the whole yytext are those at + * beginning and end of the filename string */ + fnstart = memchr(yytext, '"', yyleng); + for (fnend = yytext + yyleng - 1; + *fnend != '"'; fnend--) + ; + assert(fnstart && fnend && (fnend > fnstart)); + + fn = data_copy_escape_string(fnstart + 1, + fnend - fnstart - 1); + + /* Don't allow nuls in filenames */ + if (memchr(fn.val, '\0', fn.len - 1)) + lexical_error("nul in line number directive"); + + /* -1 since #line is the number of the next line */ + srcpos_set_line(xstrdup(fn.val), atoi(line) - 1); + data_free(fn); + } + +<*><<EOF>> { + if (!pop_input_file()) { + yyterminate(); + } + } + +<*>{STRING} { + DPRINT("String: %s\n", yytext); + yylval.data = data_copy_escape_string(yytext+1, + yyleng-2); + return DT_STRING; + } + +<*>"/dts-v1/" { + DPRINT("Keyword: /dts-v1/\n"); + BEGIN_DEFAULT(); + return DT_V1; + } + +<*>"/plugin/" { + DPRINT("Keyword: /plugin/\n"); + return DT_PLUGIN; + } + +<*>"/memreserve/" { + DPRINT("Keyword: /memreserve/\n"); + BEGIN_DEFAULT(); + return DT_MEMRESERVE; + } + +<*>"/bits/" { + DPRINT("Keyword: /bits/\n"); + BEGIN_DEFAULT(); + return DT_BITS; + } + +<*>"/delete-property/" { + DPRINT("Keyword: /delete-property/\n"); + DPRINT("<PROPNODENAME>\n"); + BEGIN(PROPNODENAME); + return DT_DEL_PROP; + } + +<*>"/delete-node/" { + DPRINT("Keyword: /delete-node/\n"); + DPRINT("<PROPNODENAME>\n"); + BEGIN(PROPNODENAME); + return DT_DEL_NODE; + } + +<*>"/omit-if-no-ref/" { + DPRINT("Keyword: /omit-if-no-ref/\n"); + DPRINT("<PROPNODENAME>\n"); + BEGIN(PROPNODENAME); + return DT_OMIT_NO_REF; + } + +<*>{LABEL}: { + DPRINT("Label: %s\n", yytext); + yylval.labelref = xstrdup(yytext); + yylval.labelref[yyleng-1] = '\0'; + return DT_LABEL; + } + +<V1>{LABEL} { + /* Missed includes or macro definitions while + * preprocessing can lead to unexpected identifiers in + * the input. Report a slightly more informative error + * in this case */ + + lexical_error("Unexpected '%s'", yytext); + + /* Treat it as a literal which often generates further + * useful error messages */ + + yylval.integer = 0; + return DT_LITERAL; + } + +<V1>([0-9]+|0[xX][0-9a-fA-F]+)(U|L|UL|LL|ULL)? { + char *e; + DPRINT("Integer Literal: '%s'\n", yytext); + + errno = 0; + yylval.integer = strtoull(yytext, &e, 0); + + if (*e && e[strspn(e, "UL")]) { + lexical_error("Bad integer literal '%s'", + yytext); + } + + if (errno == ERANGE) + lexical_error("Integer literal '%s' out of range", + yytext); + else + /* ERANGE is the only strtoull error triggerable + * by strings matching the pattern */ + assert(errno == 0); + return DT_LITERAL; + } + +<*>{CHAR_LITERAL} { + struct data d; + DPRINT("Character literal: %s\n", yytext); + + d = data_copy_escape_string(yytext+1, yyleng-2); + if (d.len == 1) { + lexical_error("Empty character literal"); + yylval.integer = 0; + } else { + yylval.integer = (unsigned char)d.val[0]; + + if (d.len > 2) + lexical_error("Character literal has %d" + " characters instead of 1", + d.len - 1); + } + + data_free(d); + return DT_CHAR_LITERAL; + } + +<*>\&{LABEL} { /* label reference */ + DPRINT("Ref: %s\n", yytext+1); + yylval.labelref = xstrdup(yytext+1); + return DT_LABEL_REF; + } + +<*>"&{"{PATHCHAR}*\} { /* new-style path reference */ + yytext[yyleng-1] = '\0'; + DPRINT("Ref: %s\n", yytext+2); + yylval.labelref = xstrdup(yytext+2); + return DT_PATH_REF; + } + +<BYTESTRING>[0-9a-fA-F]{2} { + yylval.byte = strtol(yytext, NULL, 16); + DPRINT("Byte: %02x\n", (int)yylval.byte); + return DT_BYTE; + } + +<BYTESTRING>"]" { + DPRINT("/BYTESTRING\n"); + BEGIN_DEFAULT(); + return ']'; + } + +<PROPNODENAME>\\?{PROPNODECHAR}+ { + DPRINT("PropNodeName: %s\n", yytext); + yylval.propnodename = xstrdup((yytext[0] == '\\') ? + yytext + 1 : yytext); + BEGIN_DEFAULT(); + return DT_PROPNODENAME; + } + +"/incbin/" { + DPRINT("Binary Include\n"); + return DT_INCBIN; + } + +<*>{WS}+ /* eat whitespace */ +<*>{COMMENT}+ /* eat C-style comments */ +<*>{LINECOMMENT}+ /* eat C++-style comments */ + +<*>"<<" { return DT_LSHIFT; }; +<*>">>" { return DT_RSHIFT; }; +<*>"<=" { return DT_LE; }; +<*>">=" { return DT_GE; }; +<*>"==" { return DT_EQ; }; +<*>"!=" { return DT_NE; }; +<*>"&&" { return DT_AND; }; +<*>"||" { return DT_OR; }; + +<*>. { + DPRINT("Char: %c (\\x%02x)\n", yytext[0], + (unsigned)yytext[0]); + if (yytext[0] == '[') { + DPRINT("<BYTESTRING>\n"); + BEGIN(BYTESTRING); + } + if ((yytext[0] == '{') + || (yytext[0] == ';')) { + DPRINT("<PROPNODENAME>\n"); + BEGIN(PROPNODENAME); + } + return yytext[0]; + } + +%% + +static void push_input_file(const char *filename) +{ + assert(filename); + + srcfile_push(filename); + + yyin = current_srcfile->f; + + yypush_buffer_state(yy_create_buffer(yyin, YY_BUF_SIZE)); +} + + +static bool pop_input_file(void) +{ + if (srcfile_pop() == 0) + return false; + + yypop_buffer_state(); + yyin = current_srcfile->f; + + return true; +} + +static void lexical_error(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + srcpos_verror(&yylloc, "Lexical error", fmt, ap); + va_end(ap); + + treesource_error = true; +} diff --git a/scripts/dtc/dtc-parser.y b/scripts/dtc/dtc-parser.y new file mode 100644 index 000000000..4d5eece52 --- /dev/null +++ b/scripts/dtc/dtc-parser.y @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ +%locations + +%{ +#include <stdio.h> +#include <inttypes.h> + +#include "dtc.h" +#include "srcpos.h" + +extern int yylex(void); +extern void yyerror(char const *s); +#define ERROR(loc, ...) \ + do { \ + srcpos_error((loc), "Error", __VA_ARGS__); \ + treesource_error = true; \ + } while (0) + +#define YYERROR_CALL(msg) yyerror(msg) + +extern struct dt_info *parser_output; +extern bool treesource_error; + +static bool is_ref_relative(const char *ref) +{ + return ref[0] != '/' && strchr(&ref[1], '/'); +} + +%} + +%union { + char *propnodename; + char *labelref; + uint8_t byte; + struct data data; + + struct { + struct data data; + int bits; + } array; + + struct property *prop; + struct property *proplist; + struct node *node; + struct node *nodelist; + struct reserve_info *re; + uint64_t integer; + unsigned int flags; +} + +%token DT_V1 +%token DT_PLUGIN +%token DT_MEMRESERVE +%token DT_LSHIFT DT_RSHIFT DT_LE DT_GE DT_EQ DT_NE DT_AND DT_OR +%token DT_BITS +%token DT_DEL_PROP +%token DT_DEL_NODE +%token DT_OMIT_NO_REF +%token <propnodename> DT_PROPNODENAME +%token <integer> DT_LITERAL +%token <integer> DT_CHAR_LITERAL +%token <byte> DT_BYTE +%token <data> DT_STRING +%token <labelref> DT_LABEL +%token <labelref> DT_LABEL_REF +%token <labelref> DT_PATH_REF +%token DT_INCBIN + +%type <data> propdata +%type <data> propdataprefix +%type <flags> header +%type <flags> headers +%type <re> memreserve +%type <re> memreserves +%type <array> arrayprefix +%type <data> bytestring +%type <prop> propdef +%type <proplist> proplist +%type <labelref> dt_ref + +%type <node> devicetree +%type <node> nodedef +%type <node> subnode +%type <nodelist> subnodes + +%type <integer> integer_prim +%type <integer> integer_unary +%type <integer> integer_mul +%type <integer> integer_add +%type <integer> integer_shift +%type <integer> integer_rela +%type <integer> integer_eq +%type <integer> integer_bitand +%type <integer> integer_bitxor +%type <integer> integer_bitor +%type <integer> integer_and +%type <integer> integer_or +%type <integer> integer_trinary +%type <integer> integer_expr + +%% + +sourcefile: + headers memreserves devicetree + { + parser_output = build_dt_info($1, $2, $3, + guess_boot_cpuid($3)); + } + ; + +header: + DT_V1 ';' + { + $$ = DTSF_V1; + } + | DT_V1 ';' DT_PLUGIN ';' + { + $$ = DTSF_V1 | DTSF_PLUGIN; + } + ; + +headers: + header + | header headers + { + if ($2 != $1) + ERROR(&@2, "Header flags don't match earlier ones"); + $$ = $1; + } + ; + +memreserves: + /* empty */ + { + $$ = NULL; + } + | memreserve memreserves + { + $$ = chain_reserve_entry($1, $2); + } + ; + +memreserve: + DT_MEMRESERVE integer_prim integer_prim ';' + { + $$ = build_reserve_entry($2, $3); + } + | DT_LABEL memreserve + { + add_label(&$2->labels, $1); + $$ = $2; + } + ; + +dt_ref: DT_LABEL_REF | DT_PATH_REF; + +devicetree: + '/' nodedef + { + $$ = name_node($2, ""); + } + | devicetree '/' nodedef + { + $$ = merge_nodes($1, $3); + } + | dt_ref nodedef + { + /* + * We rely on the rule being always: + * versioninfo plugindecl memreserves devicetree + * so $-1 is what we want (plugindecl) + */ + if (!($<flags>-1 & DTSF_PLUGIN)) + ERROR(&@2, "Label or path %s not found", $1); + else if (is_ref_relative($1)) + ERROR(&@2, "Label-relative reference %s not supported in plugin", $1); + $$ = add_orphan_node( + name_node(build_node(NULL, NULL, NULL), + ""), + $2, $1); + } + | devicetree DT_LABEL dt_ref nodedef + { + struct node *target = get_node_by_ref($1, $3); + + if (($<flags>-1 & DTSF_PLUGIN) && is_ref_relative($3)) + ERROR(&@2, "Label-relative reference %s not supported in plugin", $3); + + if (target) { + add_label(&target->labels, $2); + merge_nodes(target, $4); + } else + ERROR(&@3, "Label or path %s not found", $3); + $$ = $1; + } + | devicetree DT_PATH_REF nodedef + { + /* + * We rely on the rule being always: + * versioninfo plugindecl memreserves devicetree + * so $-1 is what we want (plugindecl) + */ + if ($<flags>-1 & DTSF_PLUGIN) { + if (is_ref_relative($2)) + ERROR(&@2, "Label-relative reference %s not supported in plugin", $2); + add_orphan_node($1, $3, $2); + } else { + struct node *target = get_node_by_ref($1, $2); + + if (target) + merge_nodes(target, $3); + else + ERROR(&@2, "Label or path %s not found", $2); + } + $$ = $1; + } + | devicetree DT_LABEL_REF nodedef + { + struct node *target = get_node_by_ref($1, $2); + + if (target) { + merge_nodes(target, $3); + } else { + /* + * We rely on the rule being always: + * versioninfo plugindecl memreserves devicetree + * so $-1 is what we want (plugindecl) + */ + if ($<flags>-1 & DTSF_PLUGIN) + add_orphan_node($1, $3, $2); + else + ERROR(&@2, "Label or path %s not found", $2); + } + $$ = $1; + } + | devicetree DT_DEL_NODE dt_ref ';' + { + struct node *target = get_node_by_ref($1, $3); + + if (target) + delete_node(target); + else + ERROR(&@3, "Label or path %s not found", $3); + + + $$ = $1; + } + | devicetree DT_OMIT_NO_REF dt_ref ';' + { + struct node *target = get_node_by_ref($1, $3); + + if (target) + omit_node_if_unused(target); + else + ERROR(&@3, "Label or path %s not found", $3); + + + $$ = $1; + } + ; + +nodedef: + '{' proplist subnodes '}' ';' + { + $$ = build_node($2, $3, &@$); + } + ; + +proplist: + /* empty */ + { + $$ = NULL; + } + | proplist propdef + { + $$ = chain_property($2, $1); + } + ; + +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 + { + add_label(&$2->labels, $1); + $$ = $2; + } + ; + +propdata: + propdataprefix DT_STRING + { + $$ = data_merge($1, $2); + } + | propdataprefix arrayprefix '>' + { + $$ = data_merge($1, $2.data); + } + | propdataprefix '[' bytestring ']' + { + $$ = data_merge($1, $3); + } + | propdataprefix dt_ref + { + $1 = data_add_marker($1, TYPE_STRING, $2); + $$ = data_add_marker($1, REF_PATH, $2); + } + | propdataprefix DT_INCBIN '(' DT_STRING ',' integer_prim ',' integer_prim ')' + { + FILE *f = srcfile_relative_open($4.val, NULL); + struct data d; + + if ($6 != 0) + if (fseek(f, $6, SEEK_SET) != 0) + die("Couldn't seek to offset %llu in \"%s\": %s", + (unsigned long long)$6, $4.val, + strerror(errno)); + + d = data_copy_file(f, $8); + + $$ = data_merge($1, d); + fclose(f); + } + | propdataprefix DT_INCBIN '(' DT_STRING ')' + { + FILE *f = srcfile_relative_open($4.val, NULL); + struct data d = empty_data; + + d = data_copy_file(f, -1); + + $$ = data_merge($1, d); + fclose(f); + } + | propdata DT_LABEL + { + $$ = data_add_marker($1, LABEL, $2); + } + ; + +propdataprefix: + /* empty */ + { + $$ = empty_data; + } + | propdata ',' + { + $$ = $1; + } + | propdataprefix DT_LABEL + { + $$ = data_add_marker($1, LABEL, $2); + } + ; + +arrayprefix: + DT_BITS DT_LITERAL '<' + { + unsigned long long bits; + enum markertype type = TYPE_UINT32; + + bits = $2; + + switch (bits) { + case 8: type = TYPE_UINT8; break; + case 16: type = TYPE_UINT16; break; + case 32: type = TYPE_UINT32; break; + case 64: type = TYPE_UINT64; break; + default: + ERROR(&@2, "Array elements must be" + " 8, 16, 32 or 64-bits"); + bits = 32; + } + + $$.data = data_add_marker(empty_data, type, NULL); + $$.bits = bits; + } + | '<' + { + $$.data = data_add_marker(empty_data, TYPE_UINT32, NULL); + $$.bits = 32; + } + | arrayprefix integer_prim + { + if ($1.bits < 64) { + uint64_t mask = (1ULL << $1.bits) - 1; + /* + * Bits above mask must either be all zero + * (positive within range of mask) or all one + * (negative and sign-extended). The second + * condition is true if when we set all bits + * within the mask to one (i.e. | in the + * mask), all bits are one. + */ + if (($2 > mask) && (($2 | mask) != -1ULL)) { + char *loc = srcpos_string(&@2); + fprintf(stderr, + "WARNING: %s: Value 0x%016" PRIx64 + " truncated to 0x%0*" PRIx64 "\n", + loc, $2, $1.bits / 4, ($2 & mask)); + free(loc); + } + } + + $$.data = data_append_integer($1.data, $2, $1.bits); + } + | arrayprefix dt_ref + { + uint64_t val = ~0ULL >> (64 - $1.bits); + + if ($1.bits == 32) + $1.data = data_add_marker($1.data, + REF_PHANDLE, + $2); + else + ERROR(&@2, "References are only allowed in " + "arrays with 32-bit elements."); + + $$.data = data_append_integer($1.data, val, $1.bits); + } + | arrayprefix DT_LABEL + { + $$.data = data_add_marker($1.data, LABEL, $2); + } + ; + +integer_prim: + DT_LITERAL + | DT_CHAR_LITERAL + | '(' integer_expr ')' + { + $$ = $2; + } + ; + +integer_expr: + integer_trinary + ; + +integer_trinary: + integer_or + | integer_or '?' integer_expr ':' integer_trinary { $$ = $1 ? $3 : $5; } + ; + +integer_or: + integer_and + | integer_or DT_OR integer_and { $$ = $1 || $3; } + ; + +integer_and: + integer_bitor + | integer_and DT_AND integer_bitor { $$ = $1 && $3; } + ; + +integer_bitor: + integer_bitxor + | integer_bitor '|' integer_bitxor { $$ = $1 | $3; } + ; + +integer_bitxor: + integer_bitand + | integer_bitxor '^' integer_bitand { $$ = $1 ^ $3; } + ; + +integer_bitand: + integer_eq + | integer_bitand '&' integer_eq { $$ = $1 & $3; } + ; + +integer_eq: + integer_rela + | integer_eq DT_EQ integer_rela { $$ = $1 == $3; } + | integer_eq DT_NE integer_rela { $$ = $1 != $3; } + ; + +integer_rela: + integer_shift + | integer_rela '<' integer_shift { $$ = $1 < $3; } + | integer_rela '>' integer_shift { $$ = $1 > $3; } + | integer_rela DT_LE integer_shift { $$ = $1 <= $3; } + | integer_rela DT_GE integer_shift { $$ = $1 >= $3; } + ; + +integer_shift: + integer_shift DT_LSHIFT integer_add { $$ = ($3 < 64) ? ($1 << $3) : 0; } + | integer_shift DT_RSHIFT integer_add { $$ = ($3 < 64) ? ($1 >> $3) : 0; } + | integer_add + ; + +integer_add: + integer_add '+' integer_mul { $$ = $1 + $3; } + | integer_add '-' integer_mul { $$ = $1 - $3; } + | integer_mul + ; + +integer_mul: + integer_mul '*' integer_unary { $$ = $1 * $3; } + | integer_mul '/' integer_unary + { + if ($3 != 0) { + $$ = $1 / $3; + } else { + ERROR(&@$, "Division by zero"); + $$ = 0; + } + } + | integer_mul '%' integer_unary + { + if ($3 != 0) { + $$ = $1 % $3; + } else { + ERROR(&@$, "Division by zero"); + $$ = 0; + } + } + | integer_unary + ; + +integer_unary: + integer_prim + | '-' integer_unary { $$ = -$2; } + | '~' integer_unary { $$ = ~$2; } + | '!' integer_unary { $$ = !$2; } + ; + +bytestring: + /* empty */ + { + $$ = data_add_marker(empty_data, TYPE_UINT8, NULL); + } + | bytestring DT_BYTE + { + $$ = data_append_byte($1, $2); + } + | bytestring DT_LABEL + { + $$ = data_add_marker($1, LABEL, $2); + } + ; + +subnodes: + /* empty */ + { + $$ = NULL; + } + | subnode subnodes + { + $$ = chain_node($1, $2); + } + | subnode propdef + { + ERROR(&@2, "Properties must precede subnodes"); + YYERROR; + } + ; + +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 + { + $$ = omit_node_if_unused($2); + } + | DT_LABEL subnode + { + add_label(&$2->labels, $1); + $$ = $2; + } + ; + +%% + +void yyerror(char const *s) +{ + ERROR(&yylloc, "%s", s); +} diff --git a/scripts/dtc/dtc.c b/scripts/dtc/dtc.c new file mode 100644 index 000000000..6dae60de0 --- /dev/null +++ b/scripts/dtc/dtc.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include <sys/stat.h> + +#include "dtc.h" +#include "srcpos.h" + +/* + * Command line options + */ +int quiet; /* Level of quietness */ +unsigned int reservenum;/* Number of memory reservation slots */ +int minsize; /* Minimum blob size */ +int padsize; /* Additional padding to blob */ +int alignsize; /* Additional padding to blob according to the alignsize */ +int phandle_format = PHANDLE_EPAPR; /* Use linux,phandle or phandle properties */ +int generate_symbols; /* enable symbols & fixup support */ +int generate_fixups; /* suppress generation of fixups on symbol support */ +int auto_label_aliases; /* auto generate labels -> aliases */ +int annotate; /* Level of annotation: 1 for input source location + >1 for full input source location. */ + +static int is_power_of_2(int x) +{ + return (x > 0) && ((x & (x - 1)) == 0); +} + +static void fill_fullpaths(struct node *tree, const char *prefix) +{ + struct node *child; + const char *unit; + + tree->fullpath = join_path(prefix, tree->name); + + unit = strchr(tree->name, '@'); + if (unit) + tree->basenamelen = unit - tree->name; + else + tree->basenamelen = strlen(tree->name); + + for_each_child(tree, child) + fill_fullpaths(child, tree->fullpath); +} + +/* 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:@LAThv"; +static struct option const usage_long_opts[] = { + {"quiet", no_argument, NULL, 'q'}, + {"in-format", a_argument, NULL, 'I'}, + {"out", a_argument, NULL, 'o'}, + {"out-format", a_argument, NULL, 'O'}, + {"out-version", a_argument, NULL, 'V'}, + {"out-dependency", a_argument, NULL, 'd'}, + {"reserve", a_argument, NULL, 'R'}, + {"space", a_argument, NULL, 'S'}, + {"pad", a_argument, NULL, 'p'}, + {"align", a_argument, NULL, 'a'}, + {"boot-cpu", a_argument, NULL, 'b'}, + {"force", no_argument, NULL, 'f'}, + {"include", a_argument, NULL, 'i'}, + {"sort", no_argument, NULL, 's'}, + {"phandle", a_argument, NULL, 'H'}, + {"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'}, + {"version", no_argument, NULL, 'v'}, + {NULL, no_argument, NULL, 0x0}, +}; +static const char * const usage_opts_help[] = { + "\n\tQuiet: -q suppress warnings, -qq errors, -qqq all", + "\n\tInput formats are:\n" + "\t\tdts - device tree source text\n" + "\t\tdtb - device tree blob\n" + "\t\tfs - /proc/device-tree style directory", + "\n\tOutput file", + "\n\tOutput formats are:\n" + "\t\tdts - device tree source text\n" + "\t\tdtb - device tree blob\n" +#ifndef NO_YAML + "\t\tyaml - device tree encoded as YAML\n" +#endif + "\t\tasm - assembler source", + "\n\tBlob version to produce, defaults to "stringify(DEFAULT_FDT_VERSION)" (for dtb and asm output)", + "\n\tOutput dependency file", + "\n\tMake space for <number> reserve map entries (for dtb and asm output)", + "\n\tMake the blob at least <bytes> long (extra space)", + "\n\tAdd padding to the blob of <bytes> long (extra space)", + "\n\tMake the blob align to the <bytes> (extra space)", + "\n\tSet the physical boot cpu", + "\n\tTry to produce output even if the input tree has errors", + "\n\tAdd a path to search for include files", + "\n\tSort nodes and properties before outputting (useful for comparing trees)", + "\n\tValid phandle formats are:\n" + "\t\tlegacy - \"linux,phandle\" properties only\n" + "\t\tepapr - \"phandle\" properties only\n" + "\t\tboth - Both \"linux,phandle\" and \"phandle\" properties", + "\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", + "\n\tPrint version and exit", + NULL, +}; + +static const char *guess_type_by_name(const char *fname, const char *fallback) +{ + const char *s; + + s = strrchr(fname, '.'); + if (s == NULL) + return fallback; + if (!strcasecmp(s, ".dts")) + return "dts"; + if (!strcasecmp(s, ".yaml")) + return "yaml"; + if (!strcasecmp(s, ".dtbo")) + return "dtb"; + if (!strcasecmp(s, ".dtb")) + return "dtb"; + return fallback; +} + +static const char *guess_input_format(const char *fname, const char *fallback) +{ + struct stat statbuf; + fdt32_t magic; + FILE *f; + + if (stat(fname, &statbuf) != 0) + return fallback; + + if (S_ISDIR(statbuf.st_mode)) + return "fs"; + + if (!S_ISREG(statbuf.st_mode)) + return fallback; + + f = fopen(fname, "r"); + if (f == NULL) + return fallback; + if (fread(&magic, 4, 1, f) != 1) { + fclose(f); + return fallback; + } + fclose(f); + + if (fdt32_to_cpu(magic) == FDT_MAGIC) + return "dtb"; + + return guess_type_by_name(fname, fallback); +} + +int main(int argc, char *argv[]) +{ + struct dt_info *dti; + const char *inform = NULL; + const char *outform = NULL; + const char *outname = "-"; + const char *depname = NULL; + bool force = false, sort = false; + const char *arg; + int opt; + FILE *outf = NULL; + int outversion = DEFAULT_FDT_VERSION; + long long cmdline_boot_cpuid = -1; + + quiet = 0; + reservenum = 0; + minsize = 0; + padsize = 0; + alignsize = 0; + + while ((opt = util_getopt_long()) != EOF) { + switch (opt) { + case 'I': + inform = optarg; + break; + case 'O': + outform = optarg; + break; + case 'o': + outname = optarg; + break; + case 'V': + outversion = strtol(optarg, NULL, 0); + break; + case 'd': + depname = optarg; + break; + case 'R': + reservenum = strtoul(optarg, NULL, 0); + break; + case 'S': + minsize = strtol(optarg, NULL, 0); + break; + case 'p': + padsize = strtol(optarg, NULL, 0); + break; + case 'a': + alignsize = strtol(optarg, NULL, 0); + if (!is_power_of_2(alignsize)) + die("Invalid argument \"%d\" to -a option\n", + alignsize); + break; + case 'f': + force = true; + break; + case 'q': + quiet++; + break; + case 'b': + cmdline_boot_cpuid = strtoll(optarg, NULL, 0); + break; + case 'i': + srcfile_add_search_path(optarg); + break; + case 'v': + util_version(); + case 'H': + if (streq(optarg, "legacy")) + phandle_format = PHANDLE_LEGACY; + else if (streq(optarg, "epapr")) + phandle_format = PHANDLE_EPAPR; + else if (streq(optarg, "both")) + phandle_format = PHANDLE_BOTH; + else + die("Invalid argument \"%s\" to -H option\n", + optarg); + break; + + case 's': + sort = true; + break; + + case 'W': + parse_checks_option(true, false, optarg); + break; + + case 'E': + parse_checks_option(false, true, optarg); + break; + + case '@': + generate_symbols = 1; + break; + + case 'L': + generate_fixups = 1; + break; + + case 'A': + auto_label_aliases = 1; + break; + case 'T': + annotate++; + break; + + case 'h': + usage(NULL); + default: + usage("unknown option"); + } + } + + if (argc > (optind+1)) + usage("missing files"); + else if (argc < (optind+1)) + arg = "-"; + else + arg = argv[optind]; + + /* minsize and padsize are mutually exclusive */ + if (minsize && padsize) + die("Can't set both -p and -S\n"); + + if (depname) { + depfile = fopen(depname, "w"); + if (!depfile) + die("Couldn't open dependency file %s: %s\n", depname, + strerror(errno)); + + fprint_path_escaped(depfile, outname); + fputc(':', depfile); + } + + if (inform == NULL) + inform = guess_input_format(arg, "dts"); + if (outform == NULL) { + outform = guess_type_by_name(outname, NULL); + if (outform == NULL) { + if (streq(inform, "dts")) + outform = "dtb"; + else + outform = "dts"; + } + } + if (annotate && (!streq(inform, "dts") || !streq(outform, "dts"))) + die("--annotate requires -I dts -O dts\n"); + if (streq(inform, "dts")) + dti = dt_from_source(arg); + else if (streq(inform, "fs")) + dti = dt_from_fs(arg); + else if(streq(inform, "dtb")) + dti = dt_from_blob(arg); + else + die("Unknown input format \"%s\"\n", inform); + + dti->outname = outname; + + if (depfile) { + fputc('\n', depfile); + fclose(depfile); + } + + if (cmdline_boot_cpuid != -1) + dti->boot_cpuid_phys = cmdline_boot_cpuid; + + fill_fullpaths(dti->dt, ""); + + /* on a plugin, generate by default */ + if (dti->dtsflags & DTSF_PLUGIN) { + generate_fixups = 1; + } + + process_checks(force, dti); + + if (auto_label_aliases) + generate_label_tree(dti, "aliases", false); + + generate_labels_from_tree(dti, "__symbols__"); + + if (generate_symbols) + generate_label_tree(dti, "__symbols__", true); + + fixup_phandles(dti, "__fixups__"); + local_fixup_phandles(dti, "__local_fixups__"); + + if (generate_fixups) { + generate_fixups_tree(dti, "__fixups__"); + generate_local_fixups_tree(dti, "__local_fixups__"); + } + + if (sort) + sort_tree(dti); + + if (streq(outname, "-")) { + outf = stdout; + } else { + outf = fopen(outname, "wb"); + if (! outf) + die("Couldn't open output file %s: %s\n", + outname, strerror(errno)); + } + + if (streq(outform, "dts")) { + dt_to_source(outf, dti); +#ifndef NO_YAML + } else if (streq(outform, "yaml")) { + if (!streq(inform, "dts")) + die("YAML output format requires dts input format\n"); + dt_to_yaml(outf, dti); +#endif + } else if (streq(outform, "dtb")) { + dt_to_blob(outf, dti, outversion); + } else if (streq(outform, "asm")) { + dt_to_asm(outf, dti, outversion); + } else if (streq(outform, "null")) { + /* do nothing */ + } else { + die("Unknown output format \"%s\"\n", outform); + } + + exit(0); +} diff --git a/scripts/dtc/dtc.h b/scripts/dtc/dtc.h new file mode 100644 index 000000000..473552ebf --- /dev/null +++ b/scripts/dtc/dtc.h @@ -0,0 +1,377 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef DTC_H +#define DTC_H + +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdarg.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <unistd.h> +#include <inttypes.h> + +#include <libfdt_env.h> +#include <fdt.h> + +#include "util.h" + +#ifdef DEBUG +#define debug(...) printf(__VA_ARGS__) +#else +#define debug(...) +#endif + +#define DEFAULT_FDT_VERSION 17 + +/* + * Command line options + */ +extern int quiet; /* Level of quietness */ +extern unsigned int reservenum; /* Number of memory reservation slots */ +extern int minsize; /* Minimum blob size */ +extern int padsize; /* Additional padding to blob */ +extern int alignsize; /* Additional padding to blob according to the alignsize */ +extern int phandle_format; /* Use linux,phandle or phandle properties */ +extern int generate_symbols; /* generate symbols for nodes with labels */ +extern int generate_fixups; /* generate fixups */ +extern int auto_label_aliases; /* auto generate labels -> aliases */ +extern int annotate; /* annotate .dts with input source location */ + +#define PHANDLE_LEGACY 0x1 +#define PHANDLE_EPAPR 0x2 +#define PHANDLE_BOTH 0x3 + +typedef uint32_t cell_t; + +static inline bool phandle_is_valid(cell_t phandle) +{ + return phandle != 0 && phandle != ~0U; +} + +static inline uint16_t dtb_ld16(const void *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint16_t)bp[0] << 8) + | bp[1]; +} + +static inline uint32_t dtb_ld32(const void *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint32_t)bp[0] << 24) + | ((uint32_t)bp[1] << 16) + | ((uint32_t)bp[2] << 8) + | bp[3]; +} + +static inline uint64_t dtb_ld64(const void *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint64_t)bp[0] << 56) + | ((uint64_t)bp[1] << 48) + | ((uint64_t)bp[2] << 40) + | ((uint64_t)bp[3] << 32) + | ((uint64_t)bp[4] << 24) + | ((uint64_t)bp[5] << 16) + | ((uint64_t)bp[6] << 8) + | bp[7]; +} + +#define streq(a, b) (strcmp((a), (b)) == 0) +#define strstarts(s, prefix) (strncmp((s), (prefix), strlen(prefix)) == 0) +#define strprefixeq(a, n, b) (strlen(b) == (n) && (memcmp(a, b, n) == 0)) +static inline bool strends(const char *str, const char *suffix) +{ + unsigned int len, suffix_len; + + len = strlen(str); + suffix_len = strlen(suffix); + if (len < suffix_len) + return false; + return streq(str + len - suffix_len, suffix); +} + +#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) + +/* Data blobs */ +enum markertype { + TYPE_NONE, + REF_PHANDLE, + REF_PATH, + LABEL, + TYPE_UINT8, + TYPE_UINT16, + TYPE_UINT32, + TYPE_UINT64, + TYPE_STRING, +}; + +static inline bool is_type_marker(enum markertype type) +{ + return type >= TYPE_UINT8; +} + +extern const char *markername(enum markertype markertype); + +struct marker { + enum markertype type; + unsigned int offset; + char *ref; + struct marker *next; +}; + +struct data { + unsigned int len; + char *val; + struct marker *markers; +}; + + +#define empty_data ((struct data){ 0 /* all .members = 0 or NULL */ }) + +#define for_each_marker(m) \ + for (; (m); (m) = (m)->next) +#define for_each_marker_of_type(m, t) \ + for_each_marker(m) \ + if ((m)->type == (t)) + +static inline struct marker *next_type_marker(struct marker *m) +{ + for_each_marker(m) + if (is_type_marker(m->type)) + break; + return m; +} + +static inline size_t type_marker_length(struct marker *m) +{ + struct marker *next = next_type_marker(m->next); + + if (next) + return next->offset - m->offset; + return 0; +} + +void data_free(struct data d); + +struct data data_grow_for(struct data d, unsigned int xlen); + +struct data data_copy_mem(const char *mem, int len); +struct data data_copy_escape_string(const char *s, int len); +struct data data_copy_file(FILE *f, size_t len); + +struct data data_append_data(struct data d, const void *p, int len); +struct data data_insert_at_marker(struct data d, struct marker *m, + const void *p, int len); +struct data data_merge(struct data d1, struct data d2); +struct data data_append_cell(struct data d, cell_t word); +struct data data_append_integer(struct data d, uint64_t word, int bits); +struct data data_append_re(struct data d, uint64_t address, uint64_t size); +struct data data_append_addr(struct data d, uint64_t addr); +struct data data_append_byte(struct data d, uint8_t byte); +struct data data_append_zeroes(struct data d, int len); +struct data data_append_align(struct data d, int align); +struct data data_insert_data(struct data d, struct marker *m, struct data old); + +struct marker *alloc_marker(unsigned int offset, enum markertype type, + char *ref); +struct data data_add_marker(struct data d, enum markertype type, char *ref); + +bool data_is_one_string(struct data d); + +/* DT constraints */ + +#define MAX_PROPNAME_LEN 31 +#define MAX_NODENAME_LEN 31 + +/* Live trees */ +struct label { + bool deleted; + char *label; + struct label *next; +}; + +struct bus_type { + const char *name; +}; + +struct property { + bool deleted; + char *name; + struct data val; + + struct property *next; + + struct label *labels; + struct srcpos *srcpos; +}; + +struct node { + bool deleted; + char *name; + struct property *proplist; + struct node *children; + + struct node *parent; + struct node *next_sibling; + + char *fullpath; + size_t basenamelen; + + cell_t phandle; + int addr_cells, size_cells; + + struct label *labels; + const struct bus_type *bus; + struct srcpos *srcpos; + + bool omit_if_unused, is_referenced; +}; + +#define for_each_label_withdel(l0, l) \ + for ((l) = (l0); (l); (l) = (l)->next) + +#define for_each_label(l0, l) \ + for_each_label_withdel(l0, l) \ + if (!(l)->deleted) + +#define for_each_property_withdel(n, p) \ + for ((p) = (n)->proplist; (p); (p) = (p)->next) + +#define for_each_property(n, p) \ + for_each_property_withdel(n, p) \ + if (!(p)->deleted) + +#define for_each_child_withdel(n, c) \ + for ((c) = (n)->children; (c); (c) = (c)->next_sibling) + +#define for_each_child(n, c) \ + for_each_child_withdel(n, c) \ + if (!(c)->deleted) + +void add_label(struct label **labels, char *label); +void delete_labels(struct label **labels); + +struct property *build_property(const char *name, struct data val, + struct srcpos *srcpos); +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, 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); +struct node *merge_nodes(struct node *old_node, struct node *new_node); +struct node *add_orphan_node(struct node *old_node, struct node *new_node, char *ref); + +void add_property(struct node *node, struct property *prop); +void delete_property_by_name(struct node *node, char *name); +void delete_property(struct property *prop); +void add_child(struct node *parent, struct node *child); +void delete_node_by_name(struct node *parent, char *name); +void delete_node(struct node *node); +void append_to_property(struct node *node, + char *name, const void *data, int len, + enum markertype type); + +const char *get_unitname(struct node *node); +struct property *get_property(struct node *node, const char *propname); +cell_t propval_cell(struct property *prop); +cell_t propval_cell_n(struct property *prop, unsigned int n); +struct property *get_property_by_label(struct node *tree, const char *label, + struct node **node); +struct marker *get_marker_label(struct node *tree, const char *label, + struct node **node, struct property **prop); +struct node *get_subnode(struct node *node, const char *nodename); +struct node *get_node_by_path(struct node *tree, const char *path); +struct node *get_node_by_label(struct node *tree, const char *label); +struct node *get_node_by_phandle(struct node *tree, cell_t phandle); +struct node *get_node_by_ref(struct node *tree, const char *ref); +cell_t get_node_phandle(struct node *root, struct node *node); + +uint32_t guess_boot_cpuid(struct node *tree); + +/* Boot info (tree plus memreserve information */ + +struct reserve_info { + uint64_t address, size; + + struct reserve_info *next; + + struct label *labels; +}; + +struct reserve_info *build_reserve_entry(uint64_t start, uint64_t len); +struct reserve_info *chain_reserve_entry(struct reserve_info *first, + struct reserve_info *list); +struct reserve_info *add_reserve_entry(struct reserve_info *list, + struct reserve_info *new); + + +struct dt_info { + unsigned int dtsflags; + struct reserve_info *reservelist; + uint32_t boot_cpuid_phys; + struct node *dt; /* the device tree */ + const char *outname; /* filename being written to, "-" for stdout */ +}; + +/* DTS version flags definitions */ +#define DTSF_V1 0x0001 /* /dts-v1/ */ +#define DTSF_PLUGIN 0x0002 /* /plugin/ */ + +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_labels_from_tree(struct dt_info *dti, const 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 fixup_phandles(struct dt_info *dti, const char *name); +void generate_local_fixups_tree(struct dt_info *dti, const char *name); +void local_fixup_phandles(struct dt_info *dti, const char *name); + +/* Checks */ + +void parse_checks_option(bool warn, bool error, const char *arg); +void process_checks(bool force, struct dt_info *dti); + +/* Flattened trees */ + +void dt_to_blob(FILE *f, struct dt_info *dti, int version); +void dt_to_asm(FILE *f, struct dt_info *dti, int version); + +struct dt_info *dt_from_blob(const char *fname); + +/* Tree source */ + +void property_add_marker(struct property *prop, + enum markertype type, unsigned int offset, char *ref); +void add_phandle_marker(struct dt_info *dti, struct property *prop, unsigned int offset); +void dt_to_source(FILE *f, struct dt_info *dti); +struct dt_info *dt_from_source(const char *f); + +/* YAML source */ + +void dt_to_yaml(FILE *f, struct dt_info *dti); + +/* FS trees */ + +struct dt_info *dt_from_fs(const char *dirname); + +#endif /* DTC_H */ diff --git a/scripts/dtc/dtx_diff b/scripts/dtc/dtx_diff new file mode 100755 index 000000000..f2bbde4bb --- /dev/null +++ b/scripts/dtc/dtx_diff @@ -0,0 +1,361 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0-only + +# Copyright (C) 2015 Frank Rowand +# + + +usage() { + + # use spaces instead of tabs in the usage message + cat >&2 <<eod + +Usage: + + `basename $0` DTx + decompile DTx + + `basename $0` DTx_1 DTx_2 + diff DTx_1 and DTx_2 + + + --annotate synonym for -T + --color synonym for -c (requires diff with --color support) + -c enable colored output + -f print full dts in diff (--unified=99999) + -h synonym for --help + -help synonym for --help + --help print this message and exit + -s SRCTREE linux kernel source tree is at path SRCTREE + (default is current directory) + -S linux kernel source tree is at root of current git repo + -T annotate output .dts with input source file and line + (-T -T for more details) + -u unsorted, do not sort DTx + + +Each DTx is processed by the dtc compiler to produce a sorted dts source +file. If DTx is a dts source file then it is pre-processed in the same +manner as done for the compile of the dts source file in the Linux kernel +build system ('#include' and '/include/' directives are processed). + +If two DTx are provided, the resulting dts source files are diffed. + +If DTx is a directory, it is treated as a DT subtree, such as + /proc/device-tree. + +If DTx contains the binary blob magic value in the first four bytes, + it is treated as a binary blob (aka .dtb or FDT). + +Otherwise DTx is treated as a dts source file (aka .dts). + + If this script is not run from the root of the linux source tree, + and DTx utilizes '#include' or '/include/' then the path of the + linux source tree can be provided by '-s SRCTREE' or '-S' so that + include paths will be set properly. + + The shell variable \${ARCH} must provide the architecture containing + the dts source file for include paths to be set properly for '#include' + or '/include/' to be processed. + + If DTx_1 and DTx_2 are in different architectures, then this script + may not work since \${ARCH} is part of the include path. The following + workaround can be used: + + `basename $0` ARCH=arch_of_dtx_1 DTx_1 >tmp_dtx_1.dts + `basename $0` ARCH=arch_of_dtx_2 DTx_2 >tmp_dtx_2.dts + `basename $0` tmp_dtx_1.dts tmp_dtx_2.dts + rm tmp_dtx_1.dts tmp_dtx_2.dts + + If DTx_1 and DTx_2 are in different directories, then this script will + add the path of DTx_1 and DTx_2 to the include paths. If DTx_2 includes + a local file that exists in both the path of DTx_1 and DTx_2 then the + file in the path of DTx_1 will incorrectly be included. Possible + workaround: + + `basename $0` DTx_1 >tmp_dtx_1.dts + `basename $0` DTx_2 >tmp_dtx_2.dts + `basename $0` tmp_dtx_1.dts tmp_dtx_2.dts + rm tmp_dtx_1.dts tmp_dtx_2.dts + +eod +} + + +compile_to_dts() { + + dtx="$1" + dtc_include="$2" + + if [ -d "${dtx}" ] ; then + + # ----- input is file tree + + if ( ! ${DTC} -I fs ${dtx} ) ; then + exit 3 + fi + + elif [ -f "${dtx}" ] && [ -r "${dtx}" ] ; then + + magic=`hexdump -n 4 -e '/1 "%02x"' ${dtx}` + if [ "${magic}" = "d00dfeed" ] ; then + + # ----- input is FDT (binary blob) + + if ( ! ${DTC} -I dtb ${dtx} ) ; then + exit 3 + fi + + return + + fi + + # ----- input is DTS (source) + + if ( cpp ${cpp_flags} -x assembler-with-cpp ${dtx} \ + | ${DTC} ${dtc_include} -I dts ) ; then + return + fi + + echo "" >&2 + echo "Possible hints to resolve the above error:" >&2 + echo " (hints might not fix the problem)" >&2 + + hint_given=0 + + if [ "${ARCH}" = "" ] ; then + hint_given=1 + echo "" >&2 + echo " shell variable \$ARCH not set" >&2 + fi + + dtx_arch=`echo "/${dtx}" | sed -e 's|.*/arch/||' -e 's|/.*||'` + + if [ "${dtx_arch}" != "" -a "${dtx_arch}" != "${ARCH}" ] ; then + hint_given=1 + echo "" >&2 + echo " architecture ${dtx_arch} is in file path," >&2 + echo " but does not match shell variable \$ARCH" >&2 + echo " >>\$ARCH<< is: >>${ARCH}<<" >&2 + fi + + if [ ! -d ${srctree}/arch/${ARCH} ] ; then + hint_given=1 + echo "" >&2 + echo " ${srctree}/arch/${ARCH}/ does not exist" >&2 + echo " Is \$ARCH='${ARCH}' correct?" >&2 + echo " Possible fix: use '-s' option" >&2 + + git_root=`git rev-parse --show-toplevel 2>/dev/null` + if [ -d ${git_root}/arch/ ] ; then + echo " Possible fix: use '-S' option" >&2 + fi + fi + + if [ $hint_given = 0 ] ; then + echo "" >&2 + echo " No hints available." >&2 + fi + + echo "" >&2 + + exit 3 + + else + echo "" >&2 + echo "ERROR: ${dtx} does not exist or is not readable" >&2 + echo "" >&2 + exit 2 + fi + +} + + +# ----- start of script + +annotate="" +cmd_diff=0 +diff_flags="-u" +diff_color="" +dtx_file_1="" +dtx_file_2="" +dtc_sort="-s" +help=0 +srctree="" + + +while [ $# -gt 0 ] ; do + + case $1 in + + -c | --color ) + if diff --color /dev/null /dev/null 2>/dev/null ; then + diff_color="--color=always" + fi + shift + ;; + + -f ) + diff_flags="--unified=999999" + shift + ;; + + -h | -help | --help ) + help=1 + shift + ;; + + -s ) + srctree="$2" + shift 2 + ;; + + -S ) + git_root=`git rev-parse --show-toplevel 2>/dev/null` + srctree="${git_root}" + shift + ;; + + -T | --annotate ) + if [ "${annotate}" = "" ] ; then + annotate="-T" + elif [ "${annotate}" = "-T" ] ; then + annotate="-T -T" + fi + shift + ;; + -u ) + dtc_sort="" + shift + ;; + + *) + if [ "${dtx_file_1}" = "" ] ; then + dtx_file_1="$1" + elif [ "${dtx_file_2}" = "" ] ; then + dtx_file_2="$1" + else + echo "" >&2 + echo "ERROR: Unexpected parameter: $1" >&2 + echo "" >&2 + exit 2 + fi + shift + ;; + + esac + +done + +if [ "${srctree}" = "" ] ; then + srctree="." +fi + +if [ "${dtx_file_2}" != "" ]; then + cmd_diff=1 +fi + +if (( ${help} )) ; then + usage + exit 1 +fi + +# this must follow check for ${help} +if [ "${dtx_file_1}" = "" ]; then + echo "" >&2 + echo "ERROR: parameter DTx required" >&2 + echo "" >&2 + exit 2 +fi + + +# ----- prefer dtc from linux kernel, allow fallback to dtc in $PATH + +if [ "${KBUILD_OUTPUT:0:2}" = ".." ] ; then + __KBUILD_OUTPUT="${srctree}/${KBUILD_OUTPUT}" +elif [ "${KBUILD_OUTPUT}" = "" ] ; then + __KBUILD_OUTPUT="." +else + __KBUILD_OUTPUT="${KBUILD_OUTPUT}" +fi + +DTC="${__KBUILD_OUTPUT}/scripts/dtc/dtc" + +if [ ! -x ${DTC} ] ; then + __DTC="dtc" + if grep -q "^CONFIG_DTC=y" ${__KBUILD_OUTPUT}/.config 2>/dev/null; then + make_command=' + make scripts' + else + make_command=' + Enable CONFIG_DTC in the kernel configuration + make scripts' + fi + if ( ! which ${__DTC} >/dev/null ) ; then + + # use spaces instead of tabs in the error message + cat >&2 <<eod + +ERROR: unable to find a 'dtc' program + + Preferred 'dtc' (built from Linux kernel source tree) was not found or + is not executable. + + 'dtc' is: ${DTC} + + If it does not exist, create it from the root of the Linux source tree: +${make_command} + + If not at the root of the Linux kernel source tree -s SRCTREE or -S + may need to be specified to find 'dtc'. + + If 'O=\${dir}' is specified in your Linux builds, this script requires + 'export KBUILD_OUTPUT=\${dir}' or add \${dir}/scripts/dtc to \$PATH + before running. + + If \${KBUILD_OUTPUT} is a relative path, then '-s SRCDIR', -S, or run + this script from the root of the Linux kernel source tree is required. + + Fallback '${__DTC}' was also not in \${PATH} or is not executable. + +eod + exit 2 + fi + DTC=${__DTC} +fi + + +# ----- cpp and dtc flags same as for linux source tree build of .dtb files, +# plus directories of the dtx file(s) + +dtx_path_1_dtc_include="-i `dirname ${dtx_file_1}`" + +dtx_path_2_dtc_include="" +if (( ${cmd_diff} )) ; then + dtx_path_2_dtc_include="-i `dirname ${dtx_file_2}`" +fi + +cpp_flags="\ + -nostdinc \ + -I${srctree}/scripts/dtc/include-prefixes \ + -undef -D__DTS__" + +DTC="\ + ${DTC} \ + -i ${srctree}/scripts/dtc/include-prefixes \ + -O dts -qq -f ${dtc_sort} ${annotate} -o -" + + +# ----- do the diff or decompile + +if (( ${cmd_diff} )) ; then + + diff ${diff_flags} ${diff_color} --label "${dtx_file_1}" --label "${dtx_file_2}" \ + <(compile_to_dts "${dtx_file_1}" "${dtx_path_1_dtc_include}") \ + <(compile_to_dts "${dtx_file_2}" "${dtx_path_2_dtc_include}") + +else + + compile_to_dts "${dtx_file_1}" "${dtx_path_1_dtc_include}" + +fi diff --git a/scripts/dtc/fdtget.c b/scripts/dtc/fdtget.c new file mode 100644 index 000000000..c922f8224 --- /dev/null +++ b/scripts/dtc/fdtget.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * + * Portions from U-Boot cmd_fdt.c (C) Copyright 2007 + * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com + * Based on code written by: + * Pantelis Antoniou <pantelis.antoniou@gmail.com> and + * Matthew McClintock <msm@freescale.com> + */ + +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libfdt.h> + +#include "util.h" + +enum display_mode { + MODE_SHOW_VALUE, /* show values for node properties */ + MODE_LIST_PROPS, /* list the properties for a node */ + MODE_LIST_SUBNODES, /* list the subnodes of a node */ +}; + +/* Holds information which controls our output and options */ +struct display_info { + int type; /* data type (s/i/u/x or 0 for default) */ + int size; /* data size (1/2/4) */ + enum display_mode mode; /* display mode that we are using */ + const char *default_val; /* default value if node/property not found */ +}; + +static void report_error(const char *where, int err) +{ + fprintf(stderr, "Error at '%s': %s\n", where, fdt_strerror(err)); +} + +/** + * Displays data of a given length according to selected options + * + * If a specific data type is provided in disp, then this is used. Otherwise + * we try to guess the data type / size from the contents. + * + * @param disp Display information / options + * @param data Data to display + * @param len Maximum length of buffer + * @return 0 if ok, -1 if data does not match format + */ +static int show_data(struct display_info *disp, const char *data, int len) +{ + int i, size; + const uint8_t *p = (const uint8_t *)data; + const char *s; + int value; + int is_string; + char fmt[3]; + + /* no data, don't print */ + if (len == 0) + return 0; + + is_string = (disp->type) == 's' || + (!disp->type && util_is_printable_string(data, len)); + if (is_string) { + if (data[len - 1] != '\0') { + fprintf(stderr, "Unterminated string\n"); + return -1; + } + for (s = data; s - data < len; s += strlen(s) + 1) { + if (s != data) + printf(" "); + printf("%s", (const char *)s); + } + return 0; + } + size = disp->size; + if (size == -1) { + size = (len % 4) == 0 ? 4 : 1; + } else if (len % size) { + fprintf(stderr, "Property length must be a multiple of " + "selected data size\n"); + return -1; + } + fmt[0] = '%'; + fmt[1] = disp->type ? disp->type : 'd'; + fmt[2] = '\0'; + for (i = 0; i < len; i += size, p += size) { + if (i) + printf(" "); + value = size == 4 ? fdt32_to_cpu(*(const uint32_t *)p) : + size == 2 ? (*p << 8) | p[1] : *p; + printf(fmt, value); + } + return 0; +} + +/** + * List all properties in a node, one per line. + * + * @param blob FDT blob + * @param node Node to display + * @return 0 if ok, or FDT_ERR... if not. + */ +static int list_properties(const void *blob, int node) +{ + const struct fdt_property *data; + const char *name; + int prop; + + prop = fdt_first_property_offset(blob, node); + do { + /* Stop silently when there are no more properties */ + if (prop < 0) + return prop == -FDT_ERR_NOTFOUND ? 0 : prop; + data = fdt_get_property_by_offset(blob, prop, NULL); + name = fdt_string(blob, fdt32_to_cpu(data->nameoff)); + if (name) + puts(name); + prop = fdt_next_property_offset(blob, prop); + } while (1); +} + +#define MAX_LEVEL 32 /* how deeply nested we will go */ + +/** + * List all subnodes in a node, one per line + * + * @param blob FDT blob + * @param node Node to display + * @return 0 if ok, or FDT_ERR... if not. + */ +static int list_subnodes(const void *blob, int node) +{ + int nextoffset; /* next node offset from libfdt */ + uint32_t tag; /* current tag */ + int level = 0; /* keep track of nesting level */ + const char *pathp; + int depth = 1; /* the assumed depth of this node */ + + while (level >= 0) { + tag = fdt_next_tag(blob, node, &nextoffset); + switch (tag) { + case FDT_BEGIN_NODE: + pathp = fdt_get_name(blob, node, NULL); + if (level <= depth) { + if (pathp == NULL) + pathp = "/* NULL pointer error */"; + if (*pathp == '\0') + pathp = "/"; /* root is nameless */ + if (level == 1) + puts(pathp); + } + level++; + if (level >= MAX_LEVEL) { + printf("Nested too deep, aborting.\n"); + return 1; + } + break; + case FDT_END_NODE: + level--; + if (level == 0) + level = -1; /* exit the loop */ + break; + case FDT_END: + return 1; + case FDT_PROP: + break; + default: + if (level <= depth) + printf("Unknown tag 0x%08X\n", tag); + return 1; + } + node = nextoffset; + } + return 0; +} + +/** + * Show the data for a given node (and perhaps property) according to the + * display option provided. + * + * @param blob FDT blob + * @param disp Display information / options + * @param node Node to display + * @param property Name of property to display, or NULL if none + * @return 0 if ok, -ve on error + */ +static int show_data_for_item(const void *blob, struct display_info *disp, + int node, const char *property) +{ + const void *value = NULL; + int len, err = 0; + + switch (disp->mode) { + case MODE_LIST_PROPS: + err = list_properties(blob, node); + break; + + case MODE_LIST_SUBNODES: + err = list_subnodes(blob, node); + break; + + default: + assert(property); + value = fdt_getprop(blob, node, property, &len); + if (value) { + if (show_data(disp, value, len)) + err = -1; + else + printf("\n"); + } else if (disp->default_val) { + puts(disp->default_val); + } else { + report_error(property, len); + err = -1; + } + break; + } + + return err; +} + +/** + * Run the main fdtget operation, given a filename and valid arguments + * + * @param disp Display information / options + * @param filename Filename of blob file + * @param arg List of arguments to process + * @param arg_count Number of arguments + * @param return 0 if ok, -ve on error + */ +static int do_fdtget(struct display_info *disp, const char *filename, + char **arg, int arg_count, int args_per_step) +{ + char *blob; + const char *prop; + int i, node; + + blob = utilfdt_read(filename); + if (!blob) + return -1; + + for (i = 0; i + args_per_step <= arg_count; i += args_per_step) { + node = fdt_path_offset(blob, arg[i]); + if (node < 0) { + if (disp->default_val) { + puts(disp->default_val); + continue; + } else { + report_error(arg[i], node); + return -1; + } + } + prop = args_per_step == 1 ? NULL : arg[i + 1]; + + if (show_data_for_item(blob, disp, node, prop)) + return -1; + } + return 0; +} + +static const char *usage_msg = + "fdtget - read values from device tree\n" + "\n" + "Each value is printed on a new line.\n\n" + "Usage:\n" + " fdtget <options> <dt file> [<node> <property>]...\n" + " fdtget -p <options> <dt file> [<node> ]...\n" + "Options:\n" + "\t-t <type>\tType of data\n" + "\t-p\t\tList properties for each node\n" + "\t-l\t\tList subnodes for each node\n" + "\t-d\t\tDefault value to display when the property is " + "missing\n" + "\t-h\t\tPrint this help\n\n" + USAGE_TYPE_MSG; + +static void usage(const char *msg) +{ + if (msg) + fprintf(stderr, "Error: %s\n\n", msg); + + fprintf(stderr, "%s", usage_msg); + exit(2); +} + +int main(int argc, char *argv[]) +{ + char *filename = NULL; + struct display_info disp; + int args_per_step = 2; + + /* set defaults */ + memset(&disp, '\0', sizeof(disp)); + disp.size = -1; + disp.mode = MODE_SHOW_VALUE; + for (;;) { + int c = getopt(argc, argv, "d:hlpt:"); + if (c == -1) + break; + + switch (c) { + case 'h': + case '?': + usage(NULL); + + case 't': + if (utilfdt_decode_type(optarg, &disp.type, + &disp.size)) + usage("Invalid type string"); + break; + + case 'p': + disp.mode = MODE_LIST_PROPS; + args_per_step = 1; + break; + + case 'l': + disp.mode = MODE_LIST_SUBNODES; + args_per_step = 1; + break; + + case 'd': + disp.default_val = optarg; + break; + } + } + + if (optind < argc) + filename = argv[optind++]; + if (!filename) + usage("Missing filename"); + + argv += optind; + argc -= optind; + + /* Allow no arguments, and silently succeed */ + if (!argc) + return 0; + + /* Check for node, property arguments */ + if (args_per_step == 2 && (argc % 2)) + usage("Must have an even number of arguments"); + + if (do_fdtget(&disp, filename, argv, argc, args_per_step)) + return 1; + return 0; +} diff --git a/scripts/dtc/fdtoverlay.c b/scripts/dtc/fdtoverlay.c new file mode 100644 index 000000000..ee1eb8f3a --- /dev/null +++ b/scripts/dtc/fdtoverlay.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Konsulko Group Inc. All rights reserved. + * + * Author: + * Pantelis Antoniou <pantelis.antoniou@konsulko.com> + */ + +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include <libfdt.h> + +#include "util.h" + +#define BUF_INCREMENT 65536 + +/* Usage related data. */ +static const char usage_synopsis[] = + "apply a number of overlays to a base blob\n" + " 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'}, + {"output", required_argument, NULL, 'o'}, + {"verbose", no_argument, NULL, 'v'}, + USAGE_COMMON_LONG_OPTS, +}; +static const char * const usage_opts_help[] = { + "Input base DT blob", + "Output DT blob", + "Verbose messages", + USAGE_COMMON_OPTS_HELP +}; + +int verbose = 0; + +static void *apply_one(char *base, const char *overlay, size_t *buf_len, + const char *name) +{ + char *tmp = NULL; + char *tmpo; + int ret; + bool has_symbols; + + /* + * We take copies first, because a failed apply can trash + * both the base blob and the overlay + */ + tmpo = xmalloc(fdt_totalsize(overlay)); + + do { + tmp = xrealloc(tmp, *buf_len); + ret = fdt_open_into(base, tmp, *buf_len); + if (ret) { + fprintf(stderr, + "\nFailed to make temporary copy: %s\n", + fdt_strerror(ret)); + goto fail; + } + ret = fdt_path_offset(tmp, "/__symbols__"); + has_symbols = ret >= 0; + + memcpy(tmpo, overlay, fdt_totalsize(overlay)); + + ret = fdt_overlay_apply(tmp, tmpo); + if (ret == -FDT_ERR_NOSPACE) { + *buf_len += BUF_INCREMENT; + } + } while (ret == -FDT_ERR_NOSPACE); + + if (ret) { + fprintf(stderr, "\nFailed to apply '%s': %s\n", + name, fdt_strerror(ret)); + if (!has_symbols) { + fprintf(stderr, + "base blob does not have a '/__symbols__' node, " + "make sure you have compiled the base blob with '-@' option\n"); + } + goto fail; + } + + free(base); + free(tmpo); + return tmp; + +fail: + free(tmpo); + if (tmp) + free(tmp); + + return NULL; +} +static int do_fdtoverlay(const char *input_filename, + const char *output_filename, + int argc, char *argv[]) +{ + char *blob = NULL; + char **ovblob = NULL; + size_t buf_len; + int i, ret = -1; + + blob = utilfdt_read(input_filename, &buf_len); + if (!blob) { + fprintf(stderr, "\nFailed to read '%s'\n", input_filename); + goto out_err; + } + if (fdt_totalsize(blob) > buf_len) { + fprintf(stderr, + "\nBase blob is incomplete (%lu / %" PRIu32 " bytes read)\n", + (unsigned long)buf_len, fdt_totalsize(blob)); + goto out_err; + } + + /* allocate blob pointer array */ + ovblob = xmalloc(sizeof(*ovblob) * argc); + memset(ovblob, 0, sizeof(*ovblob) * argc); + + /* read and keep track of the overlay blobs */ + for (i = 0; i < argc; i++) { + size_t ov_len; + ovblob[i] = utilfdt_read(argv[i], &ov_len); + if (!ovblob[i]) { + fprintf(stderr, "\nFailed to read '%s'\n", argv[i]); + goto out_err; + } + if (fdt_totalsize(ovblob[i]) > ov_len) { + fprintf(stderr, +"\nOverlay '%s' is incomplete (%lu / %" PRIu32 " bytes read)\n", + argv[i], (unsigned long)ov_len, + fdt_totalsize(ovblob[i])); + goto out_err; + } + } + + buf_len = fdt_totalsize(blob); + + /* apply the overlays in sequence */ + for (i = 0; i < argc; i++) { + blob = apply_one(blob, ovblob[i], &buf_len, argv[i]); + if (!blob) + goto out_err; + } + + fdt_pack(blob); + ret = utilfdt_write(output_filename, blob); + if (ret) + fprintf(stderr, "\nFailed to write '%s'\n", + output_filename); + +out_err: + if (ovblob) { + for (i = 0; i < argc; i++) { + if (ovblob[i]) + free(ovblob[i]); + } + free(ovblob); + } + free(blob); + + return ret; +} + +int main(int argc, char *argv[]) +{ + int opt, i; + char *input_filename = NULL; + char *output_filename = NULL; + + while ((opt = util_getopt_long()) != EOF) { + switch (opt) { + case_USAGE_COMMON_FLAGS + + case 'i': + input_filename = optarg; + break; + case 'o': + output_filename = optarg; + break; + case 'v': + verbose = 1; + break; + } + } + + if (!input_filename) + usage("missing input file"); + + if (!output_filename) + usage("missing output file"); + + argv += optind; + argc -= optind; + + if (argc <= 0) + usage("missing overlay file(s)"); + + if (verbose) { + printf("input = %s\n", input_filename); + printf("output = %s\n", output_filename); + for (i = 0; i < argc; i++) + printf("overlay[%d] = %s\n", i, argv[i]); + } + + if (do_fdtoverlay(input_filename, output_filename, argc, argv)) + return 1; + + return 0; +} diff --git a/scripts/dtc/fdtput.c b/scripts/dtc/fdtput.c new file mode 100644 index 000000000..a363c3cab --- /dev/null +++ b/scripts/dtc/fdtput.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + */ + +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libfdt.h> + +#include "util.h" + +/* These are the operations we support */ +enum oper_type { + OPER_WRITE_PROP, /* Write a property in a node */ + OPER_CREATE_NODE, /* Create a new node */ +}; + +struct display_info { + enum oper_type oper; /* operation to perform */ + int type; /* data type (s/i/u/x or 0 for default) */ + int size; /* data size (1/2/4) */ + int verbose; /* verbose output */ + int auto_path; /* automatically create all path components */ +}; + + +/** + * Report an error with a particular node. + * + * @param name Node name to report error on + * @param namelen Length of node name, or -1 to use entire string + * @param err Error number to report (-FDT_ERR_...) + */ +static void report_error(const char *name, int namelen, int err) +{ + if (namelen == -1) + namelen = strlen(name); + fprintf(stderr, "Error at '%1.*s': %s\n", namelen, name, + fdt_strerror(err)); +} + +/** + * Encode a series of arguments in a property value. + * + * @param disp Display information / options + * @param arg List of arguments from command line + * @param arg_count Number of arguments (may be 0) + * @param valuep Returns buffer containing value + * @param *value_len Returns length of value encoded + */ +static int encode_value(struct display_info *disp, char **arg, int arg_count, + char **valuep, int *value_len) +{ + char *value = NULL; /* holding area for value */ + int value_size = 0; /* size of holding area */ + char *ptr; /* pointer to current value position */ + int len; /* length of this cell/string/byte */ + int ival; + int upto; /* the number of bytes we have written to buf */ + char fmt[3]; + + upto = 0; + + if (disp->verbose) + fprintf(stderr, "Decoding value:\n"); + + fmt[0] = '%'; + fmt[1] = disp->type ? disp->type : 'd'; + fmt[2] = '\0'; + for (; arg_count > 0; arg++, arg_count--, upto += len) { + /* assume integer unless told otherwise */ + if (disp->type == 's') + len = strlen(*arg) + 1; + else + len = disp->size == -1 ? 4 : disp->size; + + /* enlarge our value buffer by a suitable margin if needed */ + if (upto + len > value_size) { + value_size = (upto + len) + 500; + value = realloc(value, value_size); + if (!value) { + fprintf(stderr, "Out of mmory: cannot alloc " + "%d bytes\n", value_size); + return -1; + } + } + + ptr = value + upto; + if (disp->type == 's') { + memcpy(ptr, *arg, len); + if (disp->verbose) + fprintf(stderr, "\tstring: '%s'\n", ptr); + } else { + int *iptr = (int *)ptr; + sscanf(*arg, fmt, &ival); + if (len == 4) + *iptr = cpu_to_fdt32(ival); + else + *ptr = (uint8_t)ival; + if (disp->verbose) { + fprintf(stderr, "\t%s: %d\n", + disp->size == 1 ? "byte" : + disp->size == 2 ? "short" : "int", + ival); + } + } + } + *value_len = upto; + *valuep = value; + if (disp->verbose) + fprintf(stderr, "Value size %d\n", upto); + return 0; +} + +static int store_key_value(void *blob, const char *node_name, + const char *property, const char *buf, int len) +{ + int node; + int err; + + node = fdt_path_offset(blob, node_name); + if (node < 0) { + report_error(node_name, -1, node); + return -1; + } + + err = fdt_setprop(blob, node, property, buf, len); + if (err) { + report_error(property, -1, err); + return -1; + } + return 0; +} + +/** + * Create paths as needed for all components of a path + * + * Any components of the path that do not exist are created. Errors are + * reported. + * + * @param blob FDT blob to write into + * @param in_path Path to process + * @return 0 if ok, -1 on error + */ +static int create_paths(void *blob, const char *in_path) +{ + const char *path = in_path; + const char *sep; + int node, offset = 0; + + /* skip leading '/' */ + while (*path == '/') + path++; + + for (sep = path; *sep; path = sep + 1, offset = node) { + /* equivalent to strchrnul(), but it requires _GNU_SOURCE */ + sep = strchr(path, '/'); + if (!sep) + sep = path + strlen(path); + + node = fdt_subnode_offset_namelen(blob, offset, path, + sep - path); + if (node == -FDT_ERR_NOTFOUND) { + node = fdt_add_subnode_namelen(blob, offset, path, + sep - path); + } + if (node < 0) { + report_error(path, sep - path, node); + return -1; + } + } + + return 0; +} + +/** + * Create a new node in the fdt. + * + * This will overwrite the node_name string. Any error is reported. + * + * TODO: Perhaps create fdt_path_offset_namelen() so we don't need to do this. + * + * @param blob FDT blob to write into + * @param node_name Name of node to create + * @return new node offset if found, or -1 on failure + */ +static int create_node(void *blob, const char *node_name) +{ + int node = 0; + char *p; + + p = strrchr(node_name, '/'); + if (!p) { + report_error(node_name, -1, -FDT_ERR_BADPATH); + return -1; + } + *p = '\0'; + + if (p > node_name) { + node = fdt_path_offset(blob, node_name); + if (node < 0) { + report_error(node_name, -1, node); + return -1; + } + } + + node = fdt_add_subnode(blob, node, p + 1); + if (node < 0) { + report_error(p + 1, -1, node); + return -1; + } + + return 0; +} + +static int do_fdtput(struct display_info *disp, const char *filename, + char **arg, int arg_count) +{ + char *value; + char *blob; + int len, ret = 0; + + blob = utilfdt_read(filename); + if (!blob) + return -1; + + switch (disp->oper) { + case OPER_WRITE_PROP: + /* + * Convert the arguments into a single binary value, then + * store them into the property. + */ + assert(arg_count >= 2); + if (disp->auto_path && create_paths(blob, *arg)) + return -1; + if (encode_value(disp, arg + 2, arg_count - 2, &value, &len) || + store_key_value(blob, *arg, arg[1], value, len)) + ret = -1; + break; + case OPER_CREATE_NODE: + for (; ret >= 0 && arg_count--; arg++) { + if (disp->auto_path) + ret = create_paths(blob, *arg); + else + ret = create_node(blob, *arg); + } + break; + } + if (ret >= 0) + ret = utilfdt_write(filename, blob); + + free(blob); + return ret; +} + +static const char *usage_msg = + "fdtput - write a property value to a device tree\n" + "\n" + "The command line arguments are joined together into a single value.\n" + "\n" + "Usage:\n" + " fdtput <options> <dt file> <node> <property> [<value>...]\n" + " fdtput -c <options> <dt file> [<node>...]\n" + "Options:\n" + "\t-c\t\tCreate nodes if they don't already exist\n" + "\t-p\t\tAutomatically create nodes as needed for the node path\n" + "\t-t <type>\tType of data\n" + "\t-v\t\tVerbose: display each value decoded from command line\n" + "\t-h\t\tPrint this help\n\n" + USAGE_TYPE_MSG; + +static void usage(const char *msg) +{ + if (msg) + fprintf(stderr, "Error: %s\n\n", msg); + + fprintf(stderr, "%s", usage_msg); + exit(2); +} + +int main(int argc, char *argv[]) +{ + struct display_info disp; + char *filename = NULL; + + memset(&disp, '\0', sizeof(disp)); + disp.size = -1; + disp.oper = OPER_WRITE_PROP; + for (;;) { + int c = getopt(argc, argv, "chpt:v"); + if (c == -1) + break; + + /* + * TODO: add options to: + * - delete property + * - delete node (optionally recursively) + * - rename node + * - pack fdt before writing + * - set amount of free space when writing + * - expand fdt if value doesn't fit + */ + switch (c) { + case 'c': + disp.oper = OPER_CREATE_NODE; + break; + case 'h': + case '?': + usage(NULL); + case 'p': + disp.auto_path = 1; + break; + case 't': + if (utilfdt_decode_type(optarg, &disp.type, + &disp.size)) + usage("Invalid type string"); + break; + + case 'v': + disp.verbose = 1; + break; + } + } + + if (optind < argc) + filename = argv[optind++]; + if (!filename) + usage("Missing filename"); + + argv += optind; + argc -= optind; + + if (disp.oper == OPER_WRITE_PROP) { + if (argc < 1) + usage("Missing node"); + if (argc < 2) + usage("Missing property"); + } + + if (do_fdtput(&disp, filename, argv, argc)) + return 1; + return 0; +} diff --git a/scripts/dtc/flattree.c b/scripts/dtc/flattree.c new file mode 100644 index 000000000..f3b698c17 --- /dev/null +++ b/scripts/dtc/flattree.c @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include "dtc.h" +#include "srcpos.h" + +#define FTF_FULLPATH 0x1 +#define FTF_VARALIGN 0x2 +#define FTF_NAMEPROPS 0x4 +#define FTF_BOOTCPUID 0x8 +#define FTF_STRTABSIZE 0x10 +#define FTF_STRUCTSIZE 0x20 +#define FTF_NOPS 0x40 + +static struct version_info { + int version; + int last_comp_version; + int hdr_size; + int flags; +} version_table[] = { + {1, 1, FDT_V1_SIZE, + FTF_FULLPATH|FTF_VARALIGN|FTF_NAMEPROPS}, + {2, 1, FDT_V2_SIZE, + FTF_FULLPATH|FTF_VARALIGN|FTF_NAMEPROPS|FTF_BOOTCPUID}, + {3, 1, FDT_V3_SIZE, + FTF_FULLPATH|FTF_VARALIGN|FTF_NAMEPROPS|FTF_BOOTCPUID|FTF_STRTABSIZE}, + {16, 16, FDT_V3_SIZE, + FTF_BOOTCPUID|FTF_STRTABSIZE|FTF_NOPS}, + {17, 16, FDT_V17_SIZE, + FTF_BOOTCPUID|FTF_STRTABSIZE|FTF_STRUCTSIZE|FTF_NOPS}, +}; + +struct emitter { + void (*cell)(void *, cell_t); + void (*string)(void *, const char *, int); + void (*align)(void *, int); + void (*data)(void *, struct data); + void (*beginnode)(void *, struct label *labels); + void (*endnode)(void *, struct label *labels); + void (*property)(void *, struct label *labels); +}; + +static void bin_emit_cell(void *e, cell_t val) +{ + struct data *dtbuf = e; + + *dtbuf = data_append_cell(*dtbuf, val); +} + +static void bin_emit_string(void *e, const char *str, int len) +{ + struct data *dtbuf = e; + + if (len == 0) + len = strlen(str); + + *dtbuf = data_append_data(*dtbuf, str, len); + *dtbuf = data_append_byte(*dtbuf, '\0'); +} + +static void bin_emit_align(void *e, int a) +{ + struct data *dtbuf = e; + + *dtbuf = data_append_align(*dtbuf, a); +} + +static void bin_emit_data(void *e, struct data d) +{ + struct data *dtbuf = e; + + *dtbuf = data_append_data(*dtbuf, d.val, d.len); +} + +static void bin_emit_beginnode(void *e, struct label *labels) +{ + bin_emit_cell(e, FDT_BEGIN_NODE); +} + +static void bin_emit_endnode(void *e, struct label *labels) +{ + bin_emit_cell(e, FDT_END_NODE); +} + +static void bin_emit_property(void *e, struct label *labels) +{ + bin_emit_cell(e, FDT_PROP); +} + +static struct emitter bin_emitter = { + .cell = bin_emit_cell, + .string = bin_emit_string, + .align = bin_emit_align, + .data = bin_emit_data, + .beginnode = bin_emit_beginnode, + .endnode = bin_emit_endnode, + .property = bin_emit_property, +}; + +static void emit_label(FILE *f, const char *prefix, const char *label) +{ + fprintf(f, "\t.globl\t%s_%s\n", prefix, label); + fprintf(f, "%s_%s:\n", prefix, label); + fprintf(f, "_%s_%s:\n", prefix, label); +} + +static void emit_offset_label(FILE *f, const char *label, int offset) +{ + fprintf(f, "\t.globl\t%s\n", label); + fprintf(f, "%s\t= . + %d\n", label, offset); +} + +#define ASM_EMIT_BELONG(f, fmt, ...) \ + { \ + fprintf((f), "\t.byte\t((" fmt ") >> 24) & 0xff\n", __VA_ARGS__); \ + fprintf((f), "\t.byte\t((" fmt ") >> 16) & 0xff\n", __VA_ARGS__); \ + fprintf((f), "\t.byte\t((" fmt ") >> 8) & 0xff\n", __VA_ARGS__); \ + fprintf((f), "\t.byte\t(" fmt ") & 0xff\n", __VA_ARGS__); \ + } + +static void asm_emit_cell(void *e, cell_t val) +{ + FILE *f = e; + + fprintf(f, "\t.byte\t0x%02x\n" "\t.byte\t0x%02x\n" + "\t.byte\t0x%02x\n" "\t.byte\t0x%02x\n", + (val >> 24) & 0xff, (val >> 16) & 0xff, + (val >> 8) & 0xff, val & 0xff); +} + +static void asm_emit_string(void *e, const char *str, int len) +{ + FILE *f = e; + + if (len != 0) + fprintf(f, "\t.asciz\t\"%.*s\"\n", len, str); + else + fprintf(f, "\t.asciz\t\"%s\"\n", str); +} + +static void asm_emit_align(void *e, int a) +{ + FILE *f = e; + + fprintf(f, "\t.balign\t%d, 0\n", a); +} + +static void asm_emit_data(void *e, struct data d) +{ + FILE *f = e; + unsigned int off = 0; + struct marker *m = d.markers; + + for_each_marker_of_type(m, LABEL) + emit_offset_label(f, m->ref, m->offset); + + while ((d.len - off) >= sizeof(uint32_t)) { + asm_emit_cell(e, dtb_ld32(d.val + off)); + off += sizeof(uint32_t); + } + + while ((d.len - off) >= 1) { + fprintf(f, "\t.byte\t0x%hhx\n", d.val[off]); + off += 1; + } + + assert(off == d.len); +} + +static void asm_emit_beginnode(void *e, struct label *labels) +{ + FILE *f = e; + struct label *l; + + for_each_label(labels, l) { + fprintf(f, "\t.globl\t%s\n", l->label); + fprintf(f, "%s:\n", l->label); + } + fprintf(f, "\t/* FDT_BEGIN_NODE */\n"); + asm_emit_cell(e, FDT_BEGIN_NODE); +} + +static void asm_emit_endnode(void *e, struct label *labels) +{ + FILE *f = e; + struct label *l; + + fprintf(f, "\t/* FDT_END_NODE */\n"); + asm_emit_cell(e, FDT_END_NODE); + for_each_label(labels, l) { + fprintf(f, "\t.globl\t%s_end\n", l->label); + fprintf(f, "%s_end:\n", l->label); + } +} + +static void asm_emit_property(void *e, struct label *labels) +{ + FILE *f = e; + struct label *l; + + for_each_label(labels, l) { + fprintf(f, "\t.globl\t%s\n", l->label); + fprintf(f, "%s:\n", l->label); + } + fprintf(f, "\t/* FDT_PROP */\n"); + asm_emit_cell(e, FDT_PROP); +} + +static struct emitter asm_emitter = { + .cell = asm_emit_cell, + .string = asm_emit_string, + .align = asm_emit_align, + .data = asm_emit_data, + .beginnode = asm_emit_beginnode, + .endnode = asm_emit_endnode, + .property = asm_emit_property, +}; + +static int stringtable_insert(struct data *d, const char *str) +{ + unsigned int i; + + /* FIXME: do this more efficiently? */ + + for (i = 0; i < d->len; i++) { + if (streq(str, d->val + i)) + return i; + } + + *d = data_append_data(*d, str, strlen(str)+1); + return i; +} + +static void flatten_tree(struct node *tree, struct emitter *emit, + void *etarget, struct data *strbuf, + struct version_info *vi) +{ + struct property *prop; + struct node *child; + bool seen_name_prop = false; + + if (tree->deleted) + return; + + emit->beginnode(etarget, tree->labels); + + if (vi->flags & FTF_FULLPATH) + emit->string(etarget, tree->fullpath, 0); + else + emit->string(etarget, tree->name, 0); + + emit->align(etarget, sizeof(cell_t)); + + for_each_property(tree, prop) { + int nameoff; + + if (streq(prop->name, "name")) + seen_name_prop = true; + + nameoff = stringtable_insert(strbuf, prop->name); + + emit->property(etarget, prop->labels); + emit->cell(etarget, prop->val.len); + emit->cell(etarget, nameoff); + + if ((vi->flags & FTF_VARALIGN) && (prop->val.len >= 8)) + emit->align(etarget, 8); + + emit->data(etarget, prop->val); + emit->align(etarget, sizeof(cell_t)); + } + + if ((vi->flags & FTF_NAMEPROPS) && !seen_name_prop) { + emit->property(etarget, NULL); + emit->cell(etarget, tree->basenamelen+1); + emit->cell(etarget, stringtable_insert(strbuf, "name")); + + if ((vi->flags & FTF_VARALIGN) && ((tree->basenamelen+1) >= 8)) + emit->align(etarget, 8); + + emit->string(etarget, tree->name, tree->basenamelen); + emit->align(etarget, sizeof(cell_t)); + } + + for_each_child(tree, child) { + flatten_tree(child, emit, etarget, strbuf, vi); + } + + emit->endnode(etarget, tree->labels); +} + +static struct data flatten_reserve_list(struct reserve_info *reservelist, + struct version_info *vi) +{ + struct reserve_info *re; + struct data d = empty_data; + unsigned int j; + + for (re = reservelist; re; re = re->next) { + d = data_append_re(d, re->address, re->size); + } + /* + * Add additional reserved slots if the user asked for them. + */ + for (j = 0; j < reservenum; j++) { + d = data_append_re(d, 0, 0); + } + + return d; +} + +static void make_fdt_header(struct fdt_header *fdt, + struct version_info *vi, + int reservesize, int dtsize, int strsize, + int boot_cpuid_phys) +{ + int reserve_off; + + reservesize += sizeof(struct fdt_reserve_entry); + + memset(fdt, 0xff, sizeof(*fdt)); + + fdt->magic = cpu_to_fdt32(FDT_MAGIC); + fdt->version = cpu_to_fdt32(vi->version); + fdt->last_comp_version = cpu_to_fdt32(vi->last_comp_version); + + /* Reserve map should be doubleword aligned */ + reserve_off = ALIGN(vi->hdr_size, 8); + + fdt->off_mem_rsvmap = cpu_to_fdt32(reserve_off); + fdt->off_dt_struct = cpu_to_fdt32(reserve_off + reservesize); + fdt->off_dt_strings = cpu_to_fdt32(reserve_off + reservesize + + dtsize); + fdt->totalsize = cpu_to_fdt32(reserve_off + reservesize + dtsize + strsize); + + if (vi->flags & FTF_BOOTCPUID) + fdt->boot_cpuid_phys = cpu_to_fdt32(boot_cpuid_phys); + if (vi->flags & FTF_STRTABSIZE) + fdt->size_dt_strings = cpu_to_fdt32(strsize); + if (vi->flags & FTF_STRUCTSIZE) + fdt->size_dt_struct = cpu_to_fdt32(dtsize); +} + +void dt_to_blob(FILE *f, struct dt_info *dti, int version) +{ + struct version_info *vi = NULL; + unsigned int i; + struct data blob = empty_data; + struct data reservebuf = empty_data; + struct data dtbuf = empty_data; + struct data strbuf = empty_data; + struct fdt_header fdt; + int padlen = 0; + + for (i = 0; i < ARRAY_SIZE(version_table); i++) { + if (version_table[i].version == version) + vi = &version_table[i]; + } + if (!vi) + die("Unknown device tree blob version %d\n", version); + + flatten_tree(dti->dt, &bin_emitter, &dtbuf, &strbuf, vi); + bin_emit_cell(&dtbuf, FDT_END); + + reservebuf = flatten_reserve_list(dti->reservelist, vi); + + /* Make header */ + make_fdt_header(&fdt, vi, reservebuf.len, dtbuf.len, strbuf.len, + dti->boot_cpuid_phys); + + /* + * If the user asked for more space than is used, adjust the totalsize. + */ + if (minsize > 0) { + padlen = minsize - fdt32_to_cpu(fdt.totalsize); + if (padlen < 0) { + padlen = 0; + if (quiet < 1) + fprintf(stderr, + "Warning: blob size %"PRIu32" >= minimum size %d\n", + fdt32_to_cpu(fdt.totalsize), minsize); + } + } + + if (padsize > 0) + padlen = padsize; + + if (alignsize > 0) + padlen = ALIGN(fdt32_to_cpu(fdt.totalsize) + padlen, alignsize) + - fdt32_to_cpu(fdt.totalsize); + + if (padlen > 0) { + int tsize = fdt32_to_cpu(fdt.totalsize); + tsize += padlen; + fdt.totalsize = cpu_to_fdt32(tsize); + } + + /* + * Assemble the blob: start with the header, add with alignment + * the reserve buffer, add the reserve map terminating zeroes, + * the device tree itself, and finally the strings. + */ + blob = data_append_data(blob, &fdt, vi->hdr_size); + blob = data_append_align(blob, 8); + blob = data_merge(blob, reservebuf); + blob = data_append_zeroes(blob, sizeof(struct fdt_reserve_entry)); + blob = data_merge(blob, dtbuf); + blob = data_merge(blob, strbuf); + + /* + * If the user asked for more space than is used, pad out the blob. + */ + if (padlen > 0) + blob = data_append_zeroes(blob, padlen); + + if (fwrite(blob.val, blob.len, 1, f) != 1) { + if (ferror(f)) + die("Error writing device tree blob: %s\n", + strerror(errno)); + else + die("Short write on device tree blob\n"); + } + + /* + * data_merge() frees the right-hand element so only the blob + * remains to be freed. + */ + data_free(blob); +} + +static void dump_stringtable_asm(FILE *f, struct data strbuf) +{ + const char *p; + int len; + + p = strbuf.val; + + while (p < (strbuf.val + strbuf.len)) { + len = strlen(p); + fprintf(f, "\t.asciz \"%s\"\n", p); + p += len+1; + } +} + +void dt_to_asm(FILE *f, struct dt_info *dti, int version) +{ + struct version_info *vi = NULL; + unsigned int i; + struct data strbuf = empty_data; + struct reserve_info *re; + const char *symprefix = "dt"; + + for (i = 0; i < ARRAY_SIZE(version_table); i++) { + if (version_table[i].version == version) + vi = &version_table[i]; + } + if (!vi) + die("Unknown device tree blob version %d\n", version); + + fprintf(f, "/* autogenerated by dtc, do not edit */\n\n"); + + emit_label(f, symprefix, "blob_start"); + emit_label(f, symprefix, "header"); + fprintf(f, "\t/* magic */\n"); + asm_emit_cell(f, FDT_MAGIC); + fprintf(f, "\t/* totalsize */\n"); + ASM_EMIT_BELONG(f, "_%s_blob_abs_end - _%s_blob_start", + symprefix, symprefix); + fprintf(f, "\t/* off_dt_struct */\n"); + ASM_EMIT_BELONG(f, "_%s_struct_start - _%s_blob_start", + symprefix, symprefix); + fprintf(f, "\t/* off_dt_strings */\n"); + ASM_EMIT_BELONG(f, "_%s_strings_start - _%s_blob_start", + symprefix, symprefix); + fprintf(f, "\t/* off_mem_rsvmap */\n"); + ASM_EMIT_BELONG(f, "_%s_reserve_map - _%s_blob_start", + symprefix, symprefix); + fprintf(f, "\t/* version */\n"); + asm_emit_cell(f, vi->version); + fprintf(f, "\t/* last_comp_version */\n"); + asm_emit_cell(f, vi->last_comp_version); + + if (vi->flags & FTF_BOOTCPUID) { + fprintf(f, "\t/* boot_cpuid_phys */\n"); + asm_emit_cell(f, dti->boot_cpuid_phys); + } + + if (vi->flags & FTF_STRTABSIZE) { + fprintf(f, "\t/* size_dt_strings */\n"); + ASM_EMIT_BELONG(f, "_%s_strings_end - _%s_strings_start", + symprefix, symprefix); + } + + if (vi->flags & FTF_STRUCTSIZE) { + fprintf(f, "\t/* size_dt_struct */\n"); + ASM_EMIT_BELONG(f, "_%s_struct_end - _%s_struct_start", + symprefix, symprefix); + } + + /* + * Reserve map entries. + * Align the reserve map to a doubleword boundary. + * Each entry is an (address, size) pair of u64 values. + * Always supply a zero-sized termination entry. + */ + asm_emit_align(f, 8); + emit_label(f, symprefix, "reserve_map"); + + fprintf(f, "/* Memory reserve map from source file */\n"); + + /* + * Use .long on high and low halves of u64s to avoid .quad + * as it appears .quad isn't available in some assemblers. + */ + for (re = dti->reservelist; re; re = re->next) { + struct label *l; + + for_each_label(re->labels, l) { + fprintf(f, "\t.globl\t%s\n", l->label); + fprintf(f, "%s:\n", l->label); + } + ASM_EMIT_BELONG(f, "0x%08x", (unsigned int)(re->address >> 32)); + ASM_EMIT_BELONG(f, "0x%08x", + (unsigned int)(re->address & 0xffffffff)); + ASM_EMIT_BELONG(f, "0x%08x", (unsigned int)(re->size >> 32)); + ASM_EMIT_BELONG(f, "0x%08x", (unsigned int)(re->size & 0xffffffff)); + } + for (i = 0; i < reservenum; i++) { + fprintf(f, "\t.long\t0, 0\n\t.long\t0, 0\n"); + } + + fprintf(f, "\t.long\t0, 0\n\t.long\t0, 0\n"); + + emit_label(f, symprefix, "struct_start"); + flatten_tree(dti->dt, &asm_emitter, f, &strbuf, vi); + + fprintf(f, "\t/* FDT_END */\n"); + asm_emit_cell(f, FDT_END); + emit_label(f, symprefix, "struct_end"); + + emit_label(f, symprefix, "strings_start"); + dump_stringtable_asm(f, strbuf); + emit_label(f, symprefix, "strings_end"); + + emit_label(f, symprefix, "blob_end"); + + /* + * If the user asked for more space than is used, pad it out. + */ + if (minsize > 0) { + fprintf(f, "\t.space\t%d - (_%s_blob_end - _%s_blob_start), 0\n", + minsize, symprefix, symprefix); + } + if (padsize > 0) { + fprintf(f, "\t.space\t%d, 0\n", padsize); + } + if (alignsize > 0) + asm_emit_align(f, alignsize); + emit_label(f, symprefix, "blob_abs_end"); + + data_free(strbuf); +} + +struct inbuf { + char *base, *limit, *ptr; +}; + +static void inbuf_init(struct inbuf *inb, void *base, void *limit) +{ + inb->base = base; + inb->limit = limit; + inb->ptr = inb->base; +} + +static void flat_read_chunk(struct inbuf *inb, void *p, int len) +{ + if ((inb->ptr + len) > inb->limit) + die("Premature end of data parsing flat device tree\n"); + + memcpy(p, inb->ptr, len); + + inb->ptr += len; +} + +static uint32_t flat_read_word(struct inbuf *inb) +{ + fdt32_t val; + + assert(((inb->ptr - inb->base) % sizeof(val)) == 0); + + flat_read_chunk(inb, &val, sizeof(val)); + + return fdt32_to_cpu(val); +} + +static void flat_realign(struct inbuf *inb, int align) +{ + int off = inb->ptr - inb->base; + + inb->ptr = inb->base + ALIGN(off, align); + if (inb->ptr > inb->limit) + die("Premature end of data parsing flat device tree\n"); +} + +static const char *flat_read_string(struct inbuf *inb) +{ + int len = 0; + const char *p = inb->ptr; + const char *str; + + do { + if (p >= inb->limit) + die("Premature end of data parsing flat device tree\n"); + len++; + } while ((*p++) != '\0'); + + str = inb->ptr; + + inb->ptr += len; + + flat_realign(inb, sizeof(uint32_t)); + + return str; +} + +static struct data flat_read_data(struct inbuf *inb, int len) +{ + struct data d = empty_data; + + if (len == 0) + return empty_data; + + d = data_grow_for(d, len); + d.len = len; + + flat_read_chunk(inb, d.val, len); + + flat_realign(inb, sizeof(uint32_t)); + + return d; +} + +static char *flat_read_stringtable(struct inbuf *inb, int offset) +{ + const char *p; + + p = inb->base + offset; + while (1) { + if (p >= inb->limit || p < inb->base) + die("String offset %d overruns string table\n", + offset); + + if (*p == '\0') + break; + + p++; + } + + return xstrdup(inb->base + offset); +} + +static struct property *flat_read_property(struct inbuf *dtbuf, + struct inbuf *strbuf, int flags) +{ + uint32_t proplen, stroff; + char *name; + struct data val; + + proplen = flat_read_word(dtbuf); + stroff = flat_read_word(dtbuf); + + name = flat_read_stringtable(strbuf, stroff); + + if ((flags & FTF_VARALIGN) && (proplen >= 8)) + flat_realign(dtbuf, 8); + + val = flat_read_data(dtbuf, proplen); + + return build_property(name, val, NULL); +} + + +static struct reserve_info *flat_read_mem_reserve(struct inbuf *inb) +{ + struct reserve_info *reservelist = NULL; + struct reserve_info *new; + struct fdt_reserve_entry re; + + /* + * Each entry is a pair of u64 (addr, size) values for 4 cell_t's. + * List terminates at an entry with size equal to zero. + * + * First pass, count entries. + */ + while (1) { + uint64_t address, size; + + flat_read_chunk(inb, &re, sizeof(re)); + address = fdt64_to_cpu(re.address); + size = fdt64_to_cpu(re.size); + if (size == 0) + break; + + new = build_reserve_entry(address, size); + reservelist = add_reserve_entry(reservelist, new); + } + + return reservelist; +} + + +static const char *nodename_from_path(const char *ppath, const char *cpath) +{ + int plen; + + plen = strlen(ppath); + + if (!strstarts(cpath, ppath)) + die("Path \"%s\" is not valid as a child of \"%s\"\n", + cpath, ppath); + + /* root node is a special case */ + if (!streq(ppath, "/")) + plen++; + + return cpath + plen; +} + +static struct node *unflatten_tree(struct inbuf *dtbuf, + struct inbuf *strbuf, + const char *parent_flatname, int flags) +{ + struct node *node; + const char *flatname; + uint32_t val; + + node = build_node(NULL, NULL, NULL); + + flatname = flat_read_string(dtbuf); + + if (flags & FTF_FULLPATH) + node->name = xstrdup(nodename_from_path(parent_flatname, + flatname)); + else + node->name = xstrdup(flatname); + + do { + struct property *prop; + struct node *child; + + val = flat_read_word(dtbuf); + switch (val) { + case FDT_PROP: + if (node->children) + fprintf(stderr, "Warning: Flat tree input has " + "subnodes preceding a property.\n"); + prop = flat_read_property(dtbuf, strbuf, flags); + add_property(node, prop); + break; + + case FDT_BEGIN_NODE: + child = unflatten_tree(dtbuf,strbuf, flatname, flags); + add_child(node, child); + break; + + case FDT_END_NODE: + break; + + case FDT_END: + die("Premature FDT_END in device tree blob\n"); + break; + + case FDT_NOP: + if (!(flags & FTF_NOPS)) + fprintf(stderr, "Warning: NOP tag found in flat tree" + " version <16\n"); + + /* Ignore */ + break; + + default: + die("Invalid opcode word %08x in device tree blob\n", + val); + } + } while (val != FDT_END_NODE); + + return node; +} + + +struct dt_info *dt_from_blob(const char *fname) +{ + FILE *f; + fdt32_t magic_buf, totalsize_buf; + uint32_t magic, totalsize, version, size_dt, boot_cpuid_phys; + uint32_t off_dt, off_str, off_mem_rsvmap; + int rc; + char *blob; + struct fdt_header *fdt; + char *p; + struct inbuf dtbuf, strbuf; + struct inbuf memresvbuf; + int sizeleft; + struct reserve_info *reservelist; + struct node *tree; + uint32_t val; + int flags = 0; + unsigned int dtsflags = DTSF_V1; + + f = srcfile_relative_open(fname, NULL); + + rc = fread(&magic_buf, sizeof(magic_buf), 1, f); + if (ferror(f)) + die("Error reading DT blob magic number: %s\n", + strerror(errno)); + if (rc < 1) { + if (feof(f)) + die("EOF reading DT blob magic number\n"); + else + die("Mysterious short read reading magic number\n"); + } + + magic = fdt32_to_cpu(magic_buf); + if (magic != FDT_MAGIC) + die("Blob has incorrect magic number\n"); + + rc = fread(&totalsize_buf, sizeof(totalsize_buf), 1, f); + if (ferror(f)) + die("Error reading DT blob size: %s\n", strerror(errno)); + if (rc < 1) { + if (feof(f)) + die("EOF reading DT blob size\n"); + else + die("Mysterious short read reading blob size\n"); + } + + totalsize = fdt32_to_cpu(totalsize_buf); + if (totalsize < FDT_V1_SIZE) + die("DT blob size (%d) is too small\n", totalsize); + + blob = xmalloc(totalsize); + + fdt = (struct fdt_header *)blob; + fdt->magic = cpu_to_fdt32(magic); + fdt->totalsize = cpu_to_fdt32(totalsize); + + sizeleft = totalsize - sizeof(magic) - sizeof(totalsize); + p = blob + sizeof(magic) + sizeof(totalsize); + + while (sizeleft) { + if (feof(f)) + die("EOF before reading %d bytes of DT blob\n", + totalsize); + + rc = fread(p, 1, sizeleft, f); + if (ferror(f)) + die("Error reading DT blob: %s\n", + strerror(errno)); + + sizeleft -= rc; + p += rc; + } + + off_dt = fdt32_to_cpu(fdt->off_dt_struct); + off_str = fdt32_to_cpu(fdt->off_dt_strings); + off_mem_rsvmap = fdt32_to_cpu(fdt->off_mem_rsvmap); + version = fdt32_to_cpu(fdt->version); + boot_cpuid_phys = fdt32_to_cpu(fdt->boot_cpuid_phys); + + if (off_mem_rsvmap >= totalsize) + die("Mem Reserve structure offset exceeds total size\n"); + + if (off_dt >= totalsize) + die("DT structure offset exceeds total size\n"); + + if (off_str > totalsize) + die("String table offset exceeds total size\n"); + + if (version >= 3) { + uint32_t size_str = fdt32_to_cpu(fdt->size_dt_strings); + if ((off_str+size_str < off_str) || (off_str+size_str > totalsize)) + die("String table extends past total size\n"); + inbuf_init(&strbuf, blob + off_str, blob + off_str + size_str); + } else { + inbuf_init(&strbuf, blob + off_str, blob + totalsize); + } + + if (version >= 17) { + size_dt = fdt32_to_cpu(fdt->size_dt_struct); + if ((off_dt+size_dt < off_dt) || (off_dt+size_dt > totalsize)) + die("Structure block extends past total size\n"); + } + + if (version < 16) { + flags |= FTF_FULLPATH | FTF_NAMEPROPS | FTF_VARALIGN; + } else { + flags |= FTF_NOPS; + } + + inbuf_init(&memresvbuf, + blob + off_mem_rsvmap, blob + totalsize); + inbuf_init(&dtbuf, blob + off_dt, blob + totalsize); + + reservelist = flat_read_mem_reserve(&memresvbuf); + + val = flat_read_word(&dtbuf); + + if (val != FDT_BEGIN_NODE) + die("Device tree blob doesn't begin with FDT_BEGIN_NODE (begins with 0x%08x)\n", val); + + tree = unflatten_tree(&dtbuf, &strbuf, "", flags); + + val = flat_read_word(&dtbuf); + if (val != FDT_END) + die("Device tree blob doesn't end with FDT_END\n"); + + free(blob); + + fclose(f); + + if (get_subnode(tree, "__fixups__") || get_subnode(tree, "__local_fixups__")) + dtsflags |= DTSF_PLUGIN; + + return build_dt_info(dtsflags, reservelist, tree, boot_cpuid_phys); +} diff --git a/scripts/dtc/fstree.c b/scripts/dtc/fstree.c new file mode 100644 index 000000000..0f9a534ba --- /dev/null +++ b/scripts/dtc/fstree.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include "dtc.h" + +#include <dirent.h> +#include <sys/stat.h> + +static struct node *read_fstree(const char *dirname) +{ + DIR *d; + struct dirent *de; + struct stat st; + struct node *tree; + + d = opendir(dirname); + if (!d) + die("Couldn't opendir() \"%s\": %s\n", dirname, strerror(errno)); + + tree = build_node(NULL, NULL, NULL); + + while ((de = readdir(d)) != NULL) { + char *tmpname; + + if (streq(de->d_name, ".") + || streq(de->d_name, "..")) + continue; + + tmpname = join_path(dirname, de->d_name); + + if (stat(tmpname, &st) < 0) + die("stat(%s): %s\n", tmpname, strerror(errno)); + + if (S_ISREG(st.st_mode)) { + struct property *prop; + FILE *pfile; + + pfile = fopen(tmpname, "rb"); + if (! pfile) { + fprintf(stderr, + "WARNING: Cannot open %s: %s\n", + tmpname, strerror(errno)); + } else { + prop = build_property(de->d_name, + data_copy_file(pfile, + st.st_size), + NULL); + add_property(tree, prop); + fclose(pfile); + } + } else if (S_ISDIR(st.st_mode)) { + struct node *newchild; + + newchild = read_fstree(tmpname); + newchild = name_node(newchild, xstrdup(de->d_name)); + add_child(tree, newchild); + } + + free(tmpname); + } + + closedir(d); + return tree; +} + +struct dt_info *dt_from_fs(const char *dirname) +{ + struct node *tree; + + tree = read_fstree(dirname); + tree = name_node(tree, ""); + + return build_dt_info(DTSF_V1, NULL, tree, guess_boot_cpuid(tree)); +} diff --git a/scripts/dtc/include-prefixes/arc b/scripts/dtc/include-prefixes/arc new file mode 120000 index 000000000..5d21b5a69 --- /dev/null +++ b/scripts/dtc/include-prefixes/arc @@ -0,0 +1 @@ +../../../arch/arc/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/arm b/scripts/dtc/include-prefixes/arm new file mode 120000 index 000000000..eb14d4515 --- /dev/null +++ b/scripts/dtc/include-prefixes/arm @@ -0,0 +1 @@ +../../../arch/arm/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/arm64 b/scripts/dtc/include-prefixes/arm64 new file mode 120000 index 000000000..275c42c21 --- /dev/null +++ b/scripts/dtc/include-prefixes/arm64 @@ -0,0 +1 @@ +../../../arch/arm64/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/dt-bindings b/scripts/dtc/include-prefixes/dt-bindings new file mode 120000 index 000000000..04fdbb3af --- /dev/null +++ b/scripts/dtc/include-prefixes/dt-bindings @@ -0,0 +1 @@ +../../../include/dt-bindings \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/microblaze b/scripts/dtc/include-prefixes/microblaze new file mode 120000 index 000000000..d9830330a --- /dev/null +++ b/scripts/dtc/include-prefixes/microblaze @@ -0,0 +1 @@ +../../../arch/microblaze/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/mips b/scripts/dtc/include-prefixes/mips new file mode 120000 index 000000000..ae8d4948d --- /dev/null +++ b/scripts/dtc/include-prefixes/mips @@ -0,0 +1 @@ +../../../arch/mips/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/nios2 b/scripts/dtc/include-prefixes/nios2 new file mode 120000 index 000000000..51772336d --- /dev/null +++ b/scripts/dtc/include-prefixes/nios2 @@ -0,0 +1 @@ +../../../arch/nios2/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/openrisc b/scripts/dtc/include-prefixes/openrisc new file mode 120000 index 000000000..71c3bc75c --- /dev/null +++ b/scripts/dtc/include-prefixes/openrisc @@ -0,0 +1 @@ +../../../arch/openrisc/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/powerpc b/scripts/dtc/include-prefixes/powerpc new file mode 120000 index 000000000..7cd6ec16e --- /dev/null +++ b/scripts/dtc/include-prefixes/powerpc @@ -0,0 +1 @@ +../../../arch/powerpc/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/riscv b/scripts/dtc/include-prefixes/riscv new file mode 120000 index 000000000..202509418 --- /dev/null +++ b/scripts/dtc/include-prefixes/riscv @@ -0,0 +1 @@ +../../../arch/riscv/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/sh b/scripts/dtc/include-prefixes/sh new file mode 120000 index 000000000..67d37808c --- /dev/null +++ b/scripts/dtc/include-prefixes/sh @@ -0,0 +1 @@ +../../../arch/sh/boot/dts \ No newline at end of file diff --git a/scripts/dtc/include-prefixes/xtensa b/scripts/dtc/include-prefixes/xtensa new file mode 120000 index 000000000..d1eaf6ec7 --- /dev/null +++ b/scripts/dtc/include-prefixes/xtensa @@ -0,0 +1 @@ +../../../arch/xtensa/boot/dts \ No newline at end of file diff --git a/scripts/dtc/libfdt/fdt.c b/scripts/dtc/libfdt/fdt.c new file mode 100644 index 000000000..56d4dcb2d --- /dev/null +++ b/scripts/dtc/libfdt/fdt.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +/* + * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks + * that the given buffer contains what appears to be a flattened + * device tree with sane information in its header. + */ +int32_t fdt_ro_probe_(const void *fdt) +{ + uint32_t totalsize = fdt_totalsize(fdt); + + if (can_assume(VALID_DTB)) + return totalsize; + + /* The device tree must be at an 8-byte aligned address */ + if ((uintptr_t)fdt & 7) + return -FDT_ERR_ALIGNMENT; + + if (fdt_magic(fdt) == FDT_MAGIC) { + /* Complete tree */ + if (!can_assume(LATEST)) { + if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + if (fdt_last_comp_version(fdt) > + FDT_LAST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + } + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + /* Unfinished sequential-write blob */ + if (!can_assume(VALID_INPUT) && fdt_size_dt_struct(fdt) == 0) + return -FDT_ERR_BADSTATE; + } else { + return -FDT_ERR_BADMAGIC; + } + + if (totalsize < INT32_MAX) + return totalsize; + else + return -FDT_ERR_TRUNCATED; +} + +static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off) +{ + return (off >= hdrsize) && (off <= totalsize); +} + +static int check_block_(uint32_t hdrsize, uint32_t totalsize, + uint32_t base, uint32_t size) +{ + if (!check_off_(hdrsize, totalsize, base)) + return 0; /* block start out of bounds */ + if ((base + size) < base) + return 0; /* overflow */ + if (!check_off_(hdrsize, totalsize, base + size)) + return 0; /* block end out of bounds */ + return 1; +} + +size_t fdt_header_size_(uint32_t version) +{ + if (version <= 1) + return FDT_V1_SIZE; + else if (version <= 2) + return FDT_V2_SIZE; + else if (version <= 3) + return FDT_V3_SIZE; + else if (version <= 16) + return FDT_V16_SIZE; + else + return FDT_V17_SIZE; +} + +size_t fdt_header_size(const void *fdt) +{ + return can_assume(LATEST) ? FDT_V17_SIZE : + fdt_header_size_(fdt_version(fdt)); +} + +int fdt_check_header(const void *fdt) +{ + size_t hdrsize; + + /* The device tree must be at an 8-byte aligned address */ + if ((uintptr_t)fdt & 7) + return -FDT_ERR_ALIGNMENT; + + if (fdt_magic(fdt) != FDT_MAGIC) + return -FDT_ERR_BADMAGIC; + if (!can_assume(LATEST)) { + if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) + || (fdt_last_comp_version(fdt) > + FDT_LAST_SUPPORTED_VERSION)) + return -FDT_ERR_BADVERSION; + if (fdt_version(fdt) < fdt_last_comp_version(fdt)) + return -FDT_ERR_BADVERSION; + } + hdrsize = fdt_header_size(fdt); + if (!can_assume(VALID_DTB)) { + if ((fdt_totalsize(fdt) < hdrsize) + || (fdt_totalsize(fdt) > INT_MAX)) + return -FDT_ERR_TRUNCATED; + + /* memrsv block must be 8 byte aligned */ + if (fdt_off_mem_rsvmap(fdt) % sizeof(uint64_t)) + return -FDT_ERR_ALIGNMENT; + + /* Structure block must be 4 byte aligned */ + if (fdt_off_dt_struct(fdt) % FDT_TAGSIZE) + return -FDT_ERR_ALIGNMENT; + + /* Bounds check memrsv block */ + if (!check_off_(hdrsize, fdt_totalsize(fdt), + fdt_off_mem_rsvmap(fdt))) + return -FDT_ERR_TRUNCATED; + + /* Bounds check structure block */ + if (!can_assume(LATEST) && fdt_version(fdt) < 17) { + if (!check_off_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_struct(fdt))) + return -FDT_ERR_TRUNCATED; + } else { + if (!check_block_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_struct(fdt), + fdt_size_dt_struct(fdt))) + return -FDT_ERR_TRUNCATED; + } + + /* Bounds check strings block */ + if (!check_block_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_strings(fdt), + fdt_size_dt_strings(fdt))) + return -FDT_ERR_TRUNCATED; + } + + return 0; +} + +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) +{ + unsigned int uoffset = offset; + unsigned int absoffset = offset + fdt_off_dt_struct(fdt); + + if (offset < 0) + return NULL; + + if (!can_assume(VALID_INPUT)) + if ((absoffset < uoffset) + || ((absoffset + len) < absoffset) + || (absoffset + len) > fdt_totalsize(fdt)) + return NULL; + + if (can_assume(LATEST) || fdt_version(fdt) >= 0x11) + if (((uoffset + len) < uoffset) + || ((offset + len) > fdt_size_dt_struct(fdt))) + return NULL; + + return fdt_offset_ptr_(fdt, offset); +} + +uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) +{ + const fdt32_t *tagp, *lenp; + uint32_t tag, len, sum; + int offset = startoffset; + const char *p; + + *nextoffset = -FDT_ERR_TRUNCATED; + tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); + if (!can_assume(VALID_DTB) && !tagp) + return FDT_END; /* premature end */ + tag = fdt32_to_cpu(*tagp); + offset += FDT_TAGSIZE; + + *nextoffset = -FDT_ERR_BADSTRUCTURE; + switch (tag) { + case FDT_BEGIN_NODE: + /* skip name */ + do { + p = fdt_offset_ptr(fdt, offset++, 1); + } while (p && (*p != '\0')); + if (!can_assume(VALID_DTB) && !p) + return FDT_END; /* premature end */ + break; + + case FDT_PROP: + lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); + if (!can_assume(VALID_DTB) && !lenp) + return FDT_END; /* premature end */ + + len = fdt32_to_cpu(*lenp); + sum = len + offset; + if (!can_assume(VALID_DTB) && + (INT_MAX <= sum || sum < (uint32_t) offset)) + return FDT_END; /* premature end */ + + /* skip-name offset, length and value */ + offset += sizeof(struct fdt_property) - FDT_TAGSIZE + len; + + if (!can_assume(LATEST) && + fdt_version(fdt) < 0x10 && len >= 8 && + ((offset - len) % 8) != 0) + offset += 4; + break; + + case FDT_END: + case FDT_END_NODE: + case FDT_NOP: + break; + + default: + return FDT_END; + } + + if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) + return FDT_END; /* premature end */ + + *nextoffset = FDT_TAGALIGN(offset); + return tag; +} + +int fdt_check_node_offset_(const void *fdt, int offset) +{ + if (!can_assume(VALID_INPUT) + && ((offset < 0) || (offset % FDT_TAGSIZE))) + return -FDT_ERR_BADOFFSET; + + if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int fdt_check_prop_offset_(const void *fdt, int offset) +{ + if (!can_assume(VALID_INPUT) + && ((offset < 0) || (offset % FDT_TAGSIZE))) + return -FDT_ERR_BADOFFSET; + + if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int fdt_next_node(const void *fdt, int offset, int *depth) +{ + int nextoffset = 0; + uint32_t tag; + + if (offset >= 0) + if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) + return nextoffset; + + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_PROP: + case FDT_NOP: + break; + + case FDT_BEGIN_NODE: + if (depth) + (*depth)++; + break; + + case FDT_END_NODE: + if (depth && ((--(*depth)) < 0)) + return nextoffset; + break; + + case FDT_END: + if ((nextoffset >= 0) + || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) + return -FDT_ERR_NOTFOUND; + else + return nextoffset; + } + } while (tag != FDT_BEGIN_NODE); + + return offset; +} + +int fdt_first_subnode(const void *fdt, int offset) +{ + int depth = 0; + + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth != 1) + return -FDT_ERR_NOTFOUND; + + return offset; +} + +int fdt_next_subnode(const void *fdt, int offset) +{ + int depth = 1; + + /* + * With respect to the parent, the depth of the next subnode will be + * the same as the last. + */ + do { + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth < 1) + return -FDT_ERR_NOTFOUND; + } while (depth > 1); + + return offset; +} + +const char *fdt_find_string_len_(const char *strtab, int tabsize, const char *s, + int slen) +{ + const char *last = strtab + tabsize - (slen + 1); + const char *p; + + for (p = strtab; p <= last; p++) + if (memcmp(p, s, slen) == 0 && p[slen] == '\0') + return p; + return NULL; +} + +int fdt_move(const void *fdt, void *buf, int bufsize) +{ + if (!can_assume(VALID_INPUT) && bufsize < 0) + return -FDT_ERR_NOSPACE; + + FDT_RO_PROBE(fdt); + + if (fdt_totalsize(fdt) > (unsigned int)bufsize) + return -FDT_ERR_NOSPACE; + + memmove(buf, fdt, fdt_totalsize(fdt)); + return 0; +} diff --git a/scripts/dtc/libfdt/fdt.h b/scripts/dtc/libfdt/fdt.h new file mode 100644 index 000000000..a07abfcc7 --- /dev/null +++ b/scripts/dtc/libfdt/fdt.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef FDT_H +#define FDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + */ + +#ifndef __ASSEMBLER__ + +struct fdt_header { + fdt32_t magic; /* magic word FDT_MAGIC */ + fdt32_t totalsize; /* total size of DT block */ + fdt32_t off_dt_struct; /* offset to structure */ + fdt32_t off_dt_strings; /* offset to strings */ + fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ + fdt32_t version; /* format version */ + fdt32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + fdt32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + fdt32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + fdt32_t size_dt_struct; /* size of the structure block */ +}; + +struct fdt_reserve_entry { + fdt64_t address; + fdt64_t size; +}; + +struct fdt_node_header { + fdt32_t tag; + char name[]; +}; + +struct fdt_property { + fdt32_t tag; + fdt32_t len; + fdt32_t nameoff; + char data[]; +}; + +#endif /* !__ASSEMBLER__ */ + +#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */ +#define FDT_TAGSIZE sizeof(fdt32_t) + +#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ +#define FDT_END_NODE 0x2 /* End node */ +#define FDT_PROP 0x3 /* Property: name off, + size, content */ +#define FDT_NOP 0x4 /* nop */ +#define FDT_END 0x9 + +#define FDT_V1_SIZE (7*sizeof(fdt32_t)) +#define FDT_V2_SIZE (FDT_V1_SIZE + sizeof(fdt32_t)) +#define FDT_V3_SIZE (FDT_V2_SIZE + sizeof(fdt32_t)) +#define FDT_V16_SIZE FDT_V3_SIZE +#define FDT_V17_SIZE (FDT_V16_SIZE + sizeof(fdt32_t)) + +#endif /* FDT_H */ diff --git a/scripts/dtc/libfdt/fdt_addresses.c b/scripts/dtc/libfdt/fdt_addresses.c new file mode 100644 index 000000000..c40ba094f --- /dev/null +++ b/scripts/dtc/libfdt/fdt_addresses.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2014 David Gibson <david@gibson.dropbear.id.au> + * Copyright (C) 2018 embedded brains GmbH + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +static int fdt_cells(const void *fdt, int nodeoffset, const char *name) +{ + const fdt32_t *c; + uint32_t val; + int len; + + c = fdt_getprop(fdt, nodeoffset, name, &len); + if (!c) + return len; + + if (len != sizeof(*c)) + return -FDT_ERR_BADNCELLS; + + val = fdt32_to_cpu(*c); + if (val > FDT_MAX_NCELLS) + return -FDT_ERR_BADNCELLS; + + return (int)val; +} + +int fdt_address_cells(const void *fdt, int nodeoffset) +{ + int val; + + val = fdt_cells(fdt, nodeoffset, "#address-cells"); + if (val == 0) + return -FDT_ERR_BADNCELLS; + if (val == -FDT_ERR_NOTFOUND) + return 2; + return val; +} + +int fdt_size_cells(const void *fdt, int nodeoffset) +{ + int val; + + val = fdt_cells(fdt, nodeoffset, "#size-cells"); + if (val == -FDT_ERR_NOTFOUND) + return 1; + return val; +} + +/* This function assumes that [address|size]_cells is 1 or 2 */ +int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, + const char *name, uint64_t addr, uint64_t size) +{ + int addr_cells, size_cells, ret; + uint8_t data[sizeof(fdt64_t) * 2], *prop; + + ret = fdt_address_cells(fdt, parent); + if (ret < 0) + return ret; + addr_cells = ret; + + ret = fdt_size_cells(fdt, parent); + if (ret < 0) + return ret; + size_cells = ret; + + /* check validity of address */ + prop = data; + if (addr_cells == 1) { + if ((addr > UINT32_MAX) || (((uint64_t) UINT32_MAX + 1 - addr) < size)) + return -FDT_ERR_BADVALUE; + + fdt32_st(prop, (uint32_t)addr); + } else if (addr_cells == 2) { + fdt64_st(prop, addr); + } else { + return -FDT_ERR_BADNCELLS; + } + + /* check validity of size */ + prop += addr_cells * sizeof(fdt32_t); + if (size_cells == 1) { + if (size > UINT32_MAX) + return -FDT_ERR_BADVALUE; + + fdt32_st(prop, (uint32_t)size); + } else if (size_cells == 2) { + fdt64_st(prop, size); + } else { + return -FDT_ERR_BADNCELLS; + } + + return fdt_appendprop(fdt, nodeoffset, name, data, + (addr_cells + size_cells) * sizeof(fdt32_t)); +} diff --git a/scripts/dtc/libfdt/fdt_empty_tree.c b/scripts/dtc/libfdt/fdt_empty_tree.c new file mode 100644 index 000000000..49d54d44b --- /dev/null +++ b/scripts/dtc/libfdt/fdt_empty_tree.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2012 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +int fdt_create_empty_tree(void *buf, int bufsize) +{ + int err; + + err = fdt_create(buf, bufsize); + if (err) + return err; + + err = fdt_finish_reservemap(buf); + if (err) + return err; + + err = fdt_begin_node(buf, ""); + if (err) + return err; + + err = fdt_end_node(buf); + if (err) + return err; + + err = fdt_finish(buf); + if (err) + return err; + + return fdt_open_into(buf, buf, bufsize); +} diff --git a/scripts/dtc/libfdt/fdt_overlay.c b/scripts/dtc/libfdt/fdt_overlay.c new file mode 100644 index 000000000..51a385962 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_overlay.c @@ -0,0 +1,1101 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +/** + * overlay_get_target_phandle - retrieves the target phandle of a fragment + * @fdto: pointer to the device tree overlay blob + * @fragment: node offset of the fragment in the overlay + * + * overlay_get_target_phandle() retrieves the target phandle of an + * overlay fragment when that fragment uses a phandle (target + * property) instead of a path (target-path property). + * + * returns: + * the phandle pointed by the target property + * 0, if the phandle was not found + * -1, if the phandle was malformed + */ +static uint32_t overlay_get_target_phandle(const void *fdto, int fragment) +{ + const fdt32_t *val; + int len; + + val = fdt_getprop(fdto, fragment, "target", &len); + if (!val) + return 0; + + if ((len != sizeof(*val)) || (fdt32_to_cpu(*val) == (uint32_t)-1)) + return (uint32_t)-1; + + return fdt32_to_cpu(*val); +} + +int fdt_overlay_target_offset(const void *fdt, const void *fdto, + int fragment_offset, char const **pathp) +{ + uint32_t phandle; + const char *path = NULL; + int path_len = 0, ret; + + /* Try first to do a phandle based lookup */ + phandle = overlay_get_target_phandle(fdto, fragment_offset); + if (phandle == (uint32_t)-1) + return -FDT_ERR_BADPHANDLE; + + /* no phandle, try path */ + if (!phandle) { + /* And then a path based lookup */ + path = fdt_getprop(fdto, fragment_offset, "target-path", &path_len); + if (path) + ret = fdt_path_offset(fdt, path); + else + ret = path_len; + } else + ret = fdt_node_offset_by_phandle(fdt, phandle); + + /* + * If we haven't found either a target or a + * target-path property in a node that contains a + * __overlay__ subnode (we wouldn't be called + * otherwise), consider it a improperly written + * overlay + */ + if (ret < 0 && path_len == -FDT_ERR_NOTFOUND) + ret = -FDT_ERR_BADOVERLAY; + + /* return on error */ + if (ret < 0) + return ret; + + /* return pointer to path (if available) */ + if (pathp) + *pathp = path ? path : NULL; + + return ret; +} + +/** + * overlay_phandle_add_offset - Increases a phandle by an offset + * @fdt: Base device tree blob + * @node: Device tree overlay blob + * @name: Name of the property to modify (phandle or linux,phandle) + * @delta: offset to apply + * + * overlay_phandle_add_offset() increments a node phandle by a given + * offset. + * + * returns: + * 0 on success. + * Negative error code on error + */ +static int overlay_phandle_add_offset(void *fdt, int node, + const char *name, uint32_t delta) +{ + fdt32_t *valp, val; + int len; + + valp = fdt_getprop_w(fdt, node, name, &len); + if (!valp) + return len; + + if (len != sizeof(val)) + return -FDT_ERR_BADPHANDLE; + + val = fdt32_ld(valp); + if (val + delta < val || val + delta == (uint32_t)-1) + return -FDT_ERR_NOPHANDLES; + + fdt32_st(valp, val + delta); + return 0; +} + +/** + * overlay_adjust_node_phandles - Offsets the phandles of a node + * @fdto: Device tree overlay blob + * @node: Offset of the node we want to adjust + * @delta: Offset to shift the phandles of + * + * overlay_adjust_node_phandles() adds a constant to all the phandles + * of a given node. This is mainly use as part of the overlay + * application process, when we want to update all the overlay + * phandles to not conflict with the overlays of the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_node_phandles(void *fdto, int node, + uint32_t delta) +{ + int child; + int ret; + + ret = overlay_phandle_add_offset(fdto, node, "phandle", delta); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + + ret = overlay_phandle_add_offset(fdto, node, "linux,phandle", delta); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + + fdt_for_each_subnode(child, fdto, node) { + ret = overlay_adjust_node_phandles(fdto, child, delta); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_adjust_local_phandles - Adjust the phandles of a whole overlay + * @fdto: Device tree overlay blob + * @delta: Offset to shift the phandles of + * + * overlay_adjust_local_phandles() adds a constant to all the + * phandles of an overlay. This is mainly use as part of the overlay + * application process, when we want to update all the overlay + * phandles to not conflict with the overlays of the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_local_phandles(void *fdto, uint32_t delta) +{ + /* + * Start adjusting the phandles from the overlay root + */ + return overlay_adjust_node_phandles(fdto, 0, delta); +} + +/** + * overlay_update_local_node_references - Adjust the overlay references + * @fdto: Device tree overlay blob + * @tree_node: Node offset of the node to operate on + * @fixup_node: Node offset of the matching local fixups node + * @delta: Offset to shift the phandles of + * + * overlay_update_local_nodes_references() update the phandles + * pointing to a node within the device tree overlay by adding a + * constant delta. + * + * This is mainly used as part of a device tree application process, + * where you want the device tree overlays phandles to not conflict + * with the ones from the base device tree before merging them. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_node_references(void *fdto, + int tree_node, + int fixup_node, + uint32_t delta) +{ + 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; + + refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); + + /* + * phandles to fixup can be unaligned, so use + * fdt32_{ld,st}() to read/write them. + */ + fdt32_st(refp, fdt32_ld(refp) + delta); + } + } + + 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_local_node_references(fdto, + tree_child, + fixup_child, + delta); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_local_references - Adjust the overlay references + * @fdto: Device tree overlay blob + * @delta: Offset to shift the phandles of + * + * overlay_update_local_references() update all the phandles pointing + * to a node within the device tree overlay by adding a constant + * delta to not conflict with the base overlay. + * + * This is mainly used as part of a device tree application process, + * where you want the device tree overlays phandles to not conflict + * with the ones from the base device tree before merging them. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_references(void *fdto, uint32_t delta) +{ + int fixups; + + fixups = fdt_path_offset(fdto, "/__local_fixups__"); + if (fixups < 0) { + /* There's no local phandles to adjust, bail out */ + if (fixups == -FDT_ERR_NOTFOUND) + return 0; + + return fixups; + } + + /* + * Update our local references from the root of the tree + */ + return overlay_update_local_node_references(fdto, 0, fixups, + delta); +} + +/** + * overlay_fixup_one_phandle - Set an overlay phandle to the base one + * @fdto: Device tree overlay blob + * @symbols_off: Node offset of the symbols node in the base device tree + * @path: Path to a node holding a phandle in the overlay + * @path_len: number of path characters to consider + * @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 + * @phandle: Phandle referencing the node + * + * overlay_fixup_one_phandle() resolves an overlay phandle pointing to + * a node in the base device tree. + * + * This is part of the device tree overlay application process, when + * you want all the phandles in the overlay to point to the actual + * base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_one_phandle(void *fdto, int symbols_off, + const char *path, uint32_t path_len, + const char *name, uint32_t name_len, + int poffset, uint32_t phandle) +{ + fdt32_t phandle_prop; + int fixup_off; + + if (symbols_off < 0) + return symbols_off; + + fixup_off = fdt_path_offset_namelen(fdto, path, path_len); + if (fixup_off == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (fixup_off < 0) + return fixup_off; + + phandle_prop = cpu_to_fdt32(phandle); + return fdt_setprop_inplace_namelen_partial(fdto, fixup_off, + name, name_len, poffset, + &phandle_prop, + sizeof(phandle_prop)); +} + +/** + * overlay_fixup_phandle - Set an overlay phandle to the base one + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * @symbols_off: Node offset of the symbols node in the base device tree + * @property: Property offset in the overlay holding the list of fixups + * + * overlay_fixup_phandle() resolves all the overlay phandles pointed + * to in a __fixups__ property, and updates them to match the phandles + * in use in the base device tree. + * + * This is part of the device tree overlay application process, when + * you want all the phandles in the overlay to point to the actual + * base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_phandle(void *fdt, void *fdto, int symbols_off, + int property) +{ + 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); + if (!value) { + if (len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + + 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; + uint32_t path_len, name_len; + uint32_t fixup_len; + const char *sep; + char *endptr; + int poffset, ret; + + fixup_end = memchr(value, '\0', len); + if (!fixup_end) + return -FDT_ERR_BADOVERLAY; + fixup_len = fixup_end - fixup_str; + + len -= fixup_len + 1; + value += fixup_len + 1; + + path = fixup_str; + sep = memchr(fixup_str, ':', fixup_len); + if (!sep || *sep != ':') + return -FDT_ERR_BADOVERLAY; + + path_len = sep - path; + if (path_len == (fixup_len - 1)) + return -FDT_ERR_BADOVERLAY; + + fixup_len -= path_len + 1; + name = sep + 1; + sep = memchr(name, ':', fixup_len); + if (!sep || *sep != ':') + return -FDT_ERR_BADOVERLAY; + + name_len = sep - name; + if (!name_len) + return -FDT_ERR_BADOVERLAY; + + poffset = strtoul(sep + 1, &endptr, 10); + if ((*endptr != '\0') || (endptr <= (sep + 1))) + return -FDT_ERR_BADOVERLAY; + + ret = overlay_fixup_one_phandle(fdto, symbols_off, + path, path_len, name, name_len, + poffset, phandle); + if (ret) + return ret; + } while (len > 0); + + return 0; +} + +/** + * overlay_fixup_phandles - Resolve the overlay phandles to the base + * device tree + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_fixup_phandles() resolves all the overlay phandles pointing + * to nodes in the base device tree. + * + * This is one of the steps of the device tree overlay application + * process, when you want all the phandles in the overlay to point to + * the actual base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_phandles(void *fdt, void *fdto) +{ + int fixups_off, symbols_off; + int property; + + /* We can have overlays without any fixups */ + fixups_off = fdt_path_offset(fdto, "/__fixups__"); + if (fixups_off == -FDT_ERR_NOTFOUND) + return 0; /* nothing to do */ + if (fixups_off < 0) + return fixups_off; + + /* And base DTs without symbols */ + symbols_off = fdt_path_offset(fdt, "/__symbols__"); + if ((symbols_off < 0 && (symbols_off != -FDT_ERR_NOTFOUND))) + return symbols_off; + + fdt_for_each_property_offset(property, fdto, fixups_off) { + int ret; + + ret = overlay_fixup_phandle(fdt, fdto, symbols_off, property); + if (ret) + return ret; + } + + return 0; +} + +/** + * 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 + * @fdto: Device tree overlay blob + * @node: Node offset in the overlay holding the changes to merge + * + * overlay_apply_node() merges a node into a target base device tree + * node pointed. + * + * This is part of the final step in the device tree overlay + * application process, when all the phandles have been adjusted and + * resolved and you just have to merge overlay into the base device + * tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_apply_node(void *fdt, int target, + void *fdto, int node) +{ + int property; + int subnode; + + fdt_for_each_property_offset(property, fdto, node) { + const char *name; + const void *prop; + int prop_len; + int ret; + + prop = fdt_getprop_by_offset(fdto, property, &name, + &prop_len); + if (prop_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + if (prop_len < 0) + return prop_len; + + ret = fdt_setprop(fdt, target, name, prop, prop_len); + if (ret) + return ret; + } + + fdt_for_each_subnode(subnode, fdto, node) { + const char *name = fdt_get_name(fdto, subnode, NULL); + int nnode; + int ret; + + nnode = fdt_add_subnode(fdt, target, name); + if (nnode == -FDT_ERR_EXISTS) { + nnode = fdt_subnode_offset(fdt, target, name); + if (nnode == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + } + + if (nnode < 0) + return nnode; + + ret = overlay_apply_node(fdt, nnode, fdto, subnode); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_merge - Merge an overlay into its base device tree + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_merge() merges an overlay into its base device tree. + * + * This is the next to last step in the device tree overlay application + * process, when all the phandles have been adjusted and resolved and + * you just have to merge overlay into the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_merge(void *fdt, void *fdto) +{ + int fragment; + + fdt_for_each_subnode(fragment, fdto, 0) { + int overlay; + int target; + int ret; + + /* + * Each fragments will have an __overlay__ node. If + * they don't, it's not supposed to be merged + */ + 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 < 0) + return target; + + ret = overlay_apply_node(fdt, target, fdto, overlay); + if (ret) + return ret; + } + + return 0; +} + +static int get_path_len(const void *fdt, int nodeoffset) +{ + int len = 0, namelen; + const char *name; + + FDT_RO_PROBE(fdt); + + for (;;) { + name = fdt_get_name(fdt, nodeoffset, &namelen); + if (!name) + return namelen; + + /* root? we're done */ + if (namelen == 0) + break; + + nodeoffset = fdt_parent_offset(fdt, nodeoffset); + if (nodeoffset < 0) + return nodeoffset; + len += namelen + 1; + } + + /* in case of root pretend it's "/" */ + if (len == 0) + len++; + return len; +} + +/** + * overlay_symbol_update - Update the symbols of base tree after a merge + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_symbol_update() updates the symbols of the base tree with the + * symbols of the applied overlay + * + * This is the last step in the device tree overlay application + * process, allowing the reference of overlay symbols by subsequent + * overlay operations. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_symbol_update(void *fdt, void *fdto) +{ + int root_sym, ov_sym, prop, path_len, fragment, target; + int len, frag_name_len, ret, rel_path_len; + const char *s, *e; + const char *path; + const char *name; + const char *frag_name; + const char *rel_path; + const char *target_path; + char *buf; + void *p; + + ov_sym = fdt_subnode_offset(fdto, 0, "__symbols__"); + + /* if no overlay symbols exist no problem */ + if (ov_sym < 0) + return 0; + + root_sym = fdt_subnode_offset(fdt, 0, "__symbols__"); + + /* it no root symbols exist we should create them */ + if (root_sym == -FDT_ERR_NOTFOUND) + root_sym = fdt_add_subnode(fdt, 0, "__symbols__"); + + /* any error is fatal now */ + if (root_sym < 0) + return root_sym; + + /* iterate over each overlay symbol */ + fdt_for_each_property_offset(prop, fdto, ov_sym) { + path = fdt_getprop_by_offset(fdto, prop, &name, &path_len); + if (!path) + return path_len; + + /* verify it's a string property (terminated by a single \0) */ + if (path_len < 1 || memchr(path, '\0', path_len) != &path[path_len - 1]) + return -FDT_ERR_BADVALUE; + + /* keep end marker to avoid strlen() */ + e = path + path_len; + + if (*path != '/') + return -FDT_ERR_BADVALUE; + + /* get fragment name first */ + s = strchr(path + 1, '/'); + if (!s) { + /* Symbol refers to something that won't end + * up in the target tree */ + continue; + } + + frag_name = path + 1; + frag_name_len = s - path - 1; + + /* verify format; safe since "s" lies in \0 terminated prop */ + len = sizeof("/__overlay__/") - 1; + if ((e - s) > len && (memcmp(s, "/__overlay__/", len) == 0)) { + /* /<fragment-name>/__overlay__/<relative-subnode-path> */ + rel_path = s + len; + rel_path_len = e - rel_path - 1; + } else if ((e - s) == len + && (memcmp(s, "/__overlay__", len - 1) == 0)) { + /* /<fragment-name>/__overlay__ */ + rel_path = ""; + rel_path_len = 0; + } else { + /* Symbol refers to something that won't end + * up in the target tree */ + continue; + } + + /* find the fragment index in which the symbol lies */ + ret = fdt_subnode_offset_namelen(fdto, 0, frag_name, + frag_name_len); + /* not found? */ + if (ret < 0) + return -FDT_ERR_BADOVERLAY; + fragment = ret; + + /* an __overlay__ subnode must exist */ + ret = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (ret < 0) + return -FDT_ERR_BADOVERLAY; + + /* get the target of the fragment */ + ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); + if (ret < 0) + return ret; + target = ret; + + /* if we have a target path use */ + if (!target_path) { + ret = get_path_len(fdt, target); + if (ret < 0) + return ret; + len = ret; + } else { + len = strlen(target_path); + } + + ret = fdt_setprop_placeholder(fdt, root_sym, name, + len + (len > 1) + rel_path_len + 1, &p); + if (ret < 0) + return ret; + + if (!target_path) { + /* again in case setprop_placeholder changed it */ + ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); + if (ret < 0) + return ret; + target = ret; + } + + buf = p; + if (len > 1) { /* target is not root */ + if (!target_path) { + ret = fdt_get_path(fdt, target, buf, len + 1); + if (ret < 0) + return ret; + } else + memcpy(buf, target_path, len + 1); + + } else + len--; + + buf[len] = '/'; + memcpy(buf + len + 1, rel_path, rel_path_len); + buf[len + 1 + rel_path_len] = '\0'; + } + + return 0; +} + +int fdt_overlay_apply(void *fdt, void *fdto) +{ + uint32_t delta; + int ret; + + FDT_RO_PROBE(fdt); + FDT_RO_PROBE(fdto); + + ret = fdt_find_max_phandle(fdt, &delta); + 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; + + ret = overlay_symbol_update(fdt, fdto); + if (ret) + goto err; + + /* + * The overlay has been damaged, erase its magic. + */ + fdt_set_magic(fdto, ~0); + + return 0; + +err: + /* + * The overlay might have been damaged, erase its magic. + */ + fdt_set_magic(fdto, ~0); + + /* + * The base device tree might have been damaged, erase its + * magic. + */ + fdt_set_magic(fdt, ~0); + + return ret; +} diff --git a/scripts/dtc/libfdt/fdt_ro.c b/scripts/dtc/libfdt/fdt_ro.c new file mode 100644 index 000000000..63494fb7a --- /dev/null +++ b/scripts/dtc/libfdt/fdt_ro.c @@ -0,0 +1,888 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +static int fdt_nodename_eq_(const void *fdt, int offset, + const char *s, int len) +{ + int olen; + const char *p = fdt_get_name(fdt, offset, &olen); + + if (!p || olen < len) + /* short match */ + return 0; + + if (memcmp(p, s, len) != 0) + return 0; + + if (p[len] == '\0') + return 1; + else if (!memchr(s, '@', len) && (p[len] == '@')) + return 1; + else + return 0; +} + +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp) +{ + int32_t totalsize; + uint32_t absoffset; + size_t len; + int err; + const char *s, *n; + + if (can_assume(VALID_INPUT)) { + s = (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; + + if (lenp) + *lenp = strlen(s); + return s; + } + totalsize = fdt_ro_probe_(fdt); + err = totalsize; + if (totalsize < 0) + goto fail; + + err = -FDT_ERR_BADOFFSET; + absoffset = stroffset + fdt_off_dt_strings(fdt); + if (absoffset >= (unsigned)totalsize) + goto fail; + len = totalsize - absoffset; + + if (fdt_magic(fdt) == FDT_MAGIC) { + if (stroffset < 0) + goto fail; + if (can_assume(LATEST) || fdt_version(fdt) >= 17) { + if ((unsigned)stroffset >= fdt_size_dt_strings(fdt)) + goto fail; + if ((fdt_size_dt_strings(fdt) - stroffset) < len) + len = fdt_size_dt_strings(fdt) - stroffset; + } + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + unsigned int sw_stroffset = -stroffset; + + if ((stroffset >= 0) || + (sw_stroffset > fdt_size_dt_strings(fdt))) + goto fail; + if (sw_stroffset < len) + len = sw_stroffset; + } else { + err = -FDT_ERR_INTERNAL; + goto fail; + } + + s = (const char *)fdt + absoffset; + n = memchr(s, '\0', len); + if (!n) { + /* missing terminating NULL */ + err = -FDT_ERR_TRUNCATED; + goto fail; + } + + if (lenp) + *lenp = n - s; + return s; + +fail: + if (lenp) + *lenp = err; + return NULL; +} + +const char *fdt_string(const void *fdt, int stroffset) +{ + return fdt_get_string(fdt, stroffset, NULL); +} + +static int fdt_string_eq_(const void *fdt, int stroffset, + const char *s, int len) +{ + int slen; + const char *p = fdt_get_string(fdt, stroffset, &slen); + + return p && (slen == len) && (memcmp(p, s, len) == 0); +} + +int fdt_find_max_phandle(const void *fdt, uint32_t *phandle) +{ + uint32_t max = 0; + int offset = -1; + + while (true) { + uint32_t value; + + offset = fdt_next_node(fdt, offset, NULL); + if (offset < 0) { + if (offset == -FDT_ERR_NOTFOUND) + break; + + return offset; + } + + value = fdt_get_phandle(fdt, offset); + + if (value > max) + max = value; + } + + if (phandle) + *phandle = max; + + return 0; +} + +int fdt_generate_phandle(const void *fdt, uint32_t *phandle) +{ + uint32_t max; + int err; + + err = fdt_find_max_phandle(fdt, &max); + if (err < 0) + return err; + + if (max == FDT_MAX_PHANDLE) + return -FDT_ERR_NOPHANDLES; + + if (phandle) + *phandle = max + 1; + + return 0; +} + +static const struct fdt_reserve_entry *fdt_mem_rsv(const void *fdt, int n) +{ + unsigned int offset = n * sizeof(struct fdt_reserve_entry); + unsigned int absoffset = fdt_off_mem_rsvmap(fdt) + offset; + + if (!can_assume(VALID_INPUT)) { + if (absoffset < fdt_off_mem_rsvmap(fdt)) + return NULL; + if (absoffset > fdt_totalsize(fdt) - + sizeof(struct fdt_reserve_entry)) + return NULL; + } + return fdt_mem_rsv_(fdt, n); +} + +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) +{ + const struct fdt_reserve_entry *re; + + FDT_RO_PROBE(fdt); + re = fdt_mem_rsv(fdt, n); + if (!can_assume(VALID_INPUT) && !re) + return -FDT_ERR_BADOFFSET; + + *address = fdt64_ld_(&re->address); + *size = fdt64_ld_(&re->size); + return 0; +} + +int fdt_num_mem_rsv(const void *fdt) +{ + int i; + const struct fdt_reserve_entry *re; + + for (i = 0; (re = fdt_mem_rsv(fdt, i)) != NULL; i++) { + if (fdt64_ld_(&re->size) == 0) + return i; + } + return -FDT_ERR_TRUNCATED; +} + +static int nextprop_(const void *fdt, int offset) +{ + uint32_t tag; + int nextoffset; + + do { + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_END: + if (nextoffset >= 0) + return -FDT_ERR_BADSTRUCTURE; + else + return nextoffset; + + case FDT_PROP: + return offset; + } + offset = nextoffset; + } while (tag == FDT_NOP); + + return -FDT_ERR_NOTFOUND; +} + +int fdt_subnode_offset_namelen(const void *fdt, int offset, + const char *name, int namelen) +{ + int depth; + + FDT_RO_PROBE(fdt); + + for (depth = 0; + (offset >= 0) && (depth >= 0); + offset = fdt_next_node(fdt, offset, &depth)) + if ((depth == 1) + && fdt_nodename_eq_(fdt, offset, name, namelen)) + return offset; + + if (depth < 0) + return -FDT_ERR_NOTFOUND; + return offset; /* error */ +} + +int fdt_subnode_offset(const void *fdt, int parentoffset, + const char *name) +{ + return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) +{ + const char *end = path + namelen; + const char *p = path; + int offset = 0; + + 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); + + if (!q) + q = end; + + p = fdt_get_alias_namelen(fdt, p, q - p); + if (!p) + return -FDT_ERR_BADPATH; + offset = fdt_path_offset(fdt, p); + + p = q; + } + + while (p < end) { + const char *q; + + while (*p == '/') { + p++; + if (p == end) + return offset; + } + q = memchr(p, '/', end - p); + if (! q) + q = end; + + offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p); + if (offset < 0) + return offset; + + p = q; + } + + return offset; +} + +int fdt_path_offset(const void *fdt, const char *path) +{ + return fdt_path_offset_namelen(fdt, path, strlen(path)); +} + +const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) +{ + const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset); + const char *nameptr; + int err; + + if (!can_assume(VALID_DTB) && (((err = fdt_ro_probe_(fdt)) < 0) + || ((err = fdt_check_node_offset_(fdt, nodeoffset)) < 0))) + goto fail; + + nameptr = nh->name; + + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + /* + * For old FDT versions, match the naming conventions of V16: + * give only the leaf name (after all /). The actual tree + * contents are loosely checked. + */ + const char *leaf; + leaf = strrchr(nameptr, '/'); + if (leaf == NULL) { + err = -FDT_ERR_BADSTRUCTURE; + goto fail; + } + nameptr = leaf+1; + } + + if (len) + *len = strlen(nameptr); + + return nameptr; + + fail: + if (len) + *len = err; + return NULL; +} + +int fdt_first_property_offset(const void *fdt, int nodeoffset) +{ + int offset; + + if ((offset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) + return offset; + + return nextprop_(fdt, offset); +} + +int fdt_next_property_offset(const void *fdt, int offset) +{ + if ((offset = fdt_check_prop_offset_(fdt, offset)) < 0) + return offset; + + return nextprop_(fdt, offset); +} + +static const struct fdt_property *fdt_get_property_by_offset_(const void *fdt, + int offset, + int *lenp) +{ + int err; + const struct fdt_property *prop; + + if (!can_assume(VALID_INPUT) && + (err = fdt_check_prop_offset_(fdt, offset)) < 0) { + if (lenp) + *lenp = err; + return NULL; + } + + prop = fdt_offset_ptr_(fdt, offset); + + if (lenp) + *lenp = fdt32_ld_(&prop->len); + + return prop; +} + +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp) +{ + /* Prior to version 16, properties may need realignment + * and this API does not work. fdt_getprop_*() will, however. */ + + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + if (lenp) + *lenp = -FDT_ERR_BADVERSION; + return NULL; + } + + return fdt_get_property_by_offset_(fdt, offset, lenp); +} + +static const struct fdt_property *fdt_get_property_namelen_(const void *fdt, + int offset, + const char *name, + int namelen, + int *lenp, + int *poffset) +{ + for (offset = fdt_first_property_offset(fdt, offset); + (offset >= 0); + (offset = fdt_next_property_offset(fdt, offset))) { + const struct fdt_property *prop; + + prop = fdt_get_property_by_offset_(fdt, offset, lenp); + if (!can_assume(LIBFDT_FLAWLESS) && !prop) { + offset = -FDT_ERR_INTERNAL; + break; + } + if (fdt_string_eq_(fdt, fdt32_ld_(&prop->nameoff), + name, namelen)) { + if (poffset) + *poffset = offset; + return prop; + } + } + + if (lenp) + *lenp = offset; + return NULL; +} + + +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int offset, + const char *name, + int namelen, int *lenp) +{ + /* Prior to version 16, properties may need realignment + * and this API does not work. fdt_getprop_*() will, however. */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + if (lenp) + *lenp = -FDT_ERR_BADVERSION; + return NULL; + } + + return fdt_get_property_namelen_(fdt, offset, name, namelen, lenp, + NULL); +} + + +const struct fdt_property *fdt_get_property(const void *fdt, + int nodeoffset, + const char *name, int *lenp) +{ + return fdt_get_property_namelen(fdt, nodeoffset, name, + strlen(name), lenp); +} + +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp) +{ + int poffset; + const struct fdt_property *prop; + + prop = fdt_get_property_namelen_(fdt, nodeoffset, name, namelen, lenp, + &poffset); + if (!prop) + return NULL; + + /* Handle realignment */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && + (poffset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) + return prop->data + 4; + return prop->data; +} + +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp) +{ + const struct fdt_property *prop; + + prop = fdt_get_property_by_offset_(fdt, offset, lenp); + if (!prop) + return NULL; + if (namep) { + const char *name; + int namelen; + + if (!can_assume(VALID_INPUT)) { + name = fdt_get_string(fdt, fdt32_ld_(&prop->nameoff), + &namelen); + *namep = name; + if (!name) { + if (lenp) + *lenp = namelen; + return NULL; + } + } else { + *namep = fdt_string(fdt, fdt32_ld_(&prop->nameoff)); + } + } + + /* Handle realignment */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && + (offset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) + return prop->data + 4; + return prop->data; +} + +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp); +} + +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) +{ + const fdt32_t *php; + int len; + + /* FIXME: This is a bit sub-optimal, since we potentially scan + * over all the properties twice. */ + php = fdt_getprop(fdt, nodeoffset, "phandle", &len); + if (!php || (len != sizeof(*php))) { + php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len); + if (!php || (len != sizeof(*php))) + return 0; + } + + 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 len; + const char *alias; + + 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 alias; +} + +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; + int offset, depth, namelen; + const char *name; + + FDT_RO_PROBE(fdt); + + if (buflen < 2) + return -FDT_ERR_NOSPACE; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + while (pdepth > depth) { + do { + p--; + } while (buf[p-1] != '/'); + pdepth--; + } + + if (pdepth >= depth) { + name = fdt_get_name(fdt, offset, &namelen); + if (!name) + return namelen; + if ((p + namelen + 1) <= buflen) { + memcpy(buf + p, name, namelen); + p += namelen; + buf[p++] = '/'; + pdepth++; + } + } + + if (offset == nodeoffset) { + if (pdepth < (depth + 1)) + return -FDT_ERR_NOSPACE; + + if (p > 1) /* special case so that root path is "/", not "" */ + p--; + buf[p] = '\0'; + return 0; + } + } + + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + + return offset; /* error from fdt_next_node() */ +} + +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth) +{ + int offset, depth; + int supernodeoffset = -FDT_ERR_INTERNAL; + + FDT_RO_PROBE(fdt); + + if (supernodedepth < 0) + return -FDT_ERR_NOTFOUND; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + if (depth == supernodedepth) + supernodeoffset = offset; + + if (offset == nodeoffset) { + if (nodedepth) + *nodedepth = depth; + + if (supernodedepth > depth) + return -FDT_ERR_NOTFOUND; + else + return supernodeoffset; + } + } + + if (!can_assume(VALID_INPUT)) { + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_depth(const void *fdt, int nodeoffset) +{ + int nodedepth; + int err; + + err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth); + if (err) + return (can_assume(LIBFDT_FLAWLESS) || err < 0) ? err : + -FDT_ERR_INTERNAL; + return nodedepth; +} + +int fdt_parent_offset(const void *fdt, int nodeoffset) +{ + int nodedepth = fdt_node_depth(fdt, nodeoffset); + + if (nodedepth < 0) + return nodedepth; + return fdt_supernode_atdepth_offset(fdt, nodeoffset, + nodedepth - 1, NULL); +} + +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen) +{ + int offset; + const void *val; + int len; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_getprop(), then if that didn't + * find what we want, we scan over them again making our way + * to the next node. Still it's the easiest to implement + * approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + val = fdt_getprop(fdt, offset, propname, &len); + if (val && (len == proplen) + && (memcmp(val, propval, len) == 0)) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) +{ + int offset; + + if ((phandle == 0) || (phandle == ~0U)) + return -FDT_ERR_BADPHANDLE; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we + * potentially scan each property of a node in + * fdt_get_phandle(), then if that didn't find what + * we want, we scan over them again making our way to the next + * node. Still it's the easiest to implement approach; + * performance can come later. */ + for (offset = fdt_next_node(fdt, -1, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + if (fdt_get_phandle(fdt, offset) == phandle) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) +{ + int len = strlen(str); + const char *p; + + while (listlen >= len) { + if (memcmp(str, strlist, len+1) == 0) + return 1; + p = memchr(strlist, '\0', listlen); + if (!p) + return 0; /* malformed strlist.. */ + listlen -= (p-strlist) + 1; + strlist = p + 1; + } + return 0; +} + +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property) +{ + const char *list, *end; + int length, count = 0; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + list += length; + count++; + } + + return count; +} + +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string) +{ + int length, len, idx = 0; + const char *list, *end; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + len = strlen(string) + 1; + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + if (length == len && memcmp(list, string, length) == 0) + return idx; + + list += length; + idx++; + } + + return -FDT_ERR_NOTFOUND; +} + +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int idx, + int *lenp) +{ + const char *list, *end; + int length; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) { + if (lenp) + *lenp = length; + + return NULL; + } + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) { + if (lenp) + *lenp = -FDT_ERR_BADVALUE; + + return NULL; + } + + if (idx == 0) { + if (lenp) + *lenp = length - 1; + + return list; + } + + list += length; + idx--; + } + + if (lenp) + *lenp = -FDT_ERR_NOTFOUND; + + return NULL; +} + +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible) +{ + const void *prop; + int len; + + prop = fdt_getprop(fdt, nodeoffset, "compatible", &len); + if (!prop) + return len; + + return !fdt_stringlist_contains(prop, len, compatible); +} + +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible) +{ + int offset, err; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_node_check_compatible(), then if + * that didn't find what we want, we scan over them again + * making our way to the next node. Still it's the easiest to + * implement approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + err = fdt_node_check_compatible(fdt, offset, compatible); + if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) + return err; + else if (err == 0) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} diff --git a/scripts/dtc/libfdt/fdt_rw.c b/scripts/dtc/libfdt/fdt_rw.c new file mode 100644 index 000000000..90ea14e94 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_rw.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +static int fdt_blocks_misordered_(const void *fdt, + int mem_rsv_size, int struct_size) +{ + return (fdt_off_mem_rsvmap(fdt) < FDT_ALIGN(sizeof(struct fdt_header), 8)) + || (fdt_off_dt_struct(fdt) < + (fdt_off_mem_rsvmap(fdt) + mem_rsv_size)) + || (fdt_off_dt_strings(fdt) < + (fdt_off_dt_struct(fdt) + struct_size)) + || (fdt_totalsize(fdt) < + (fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt))); +} + +static void fdt_downgrade_version(void *fdt) +{ + if (!can_assume(LATEST) && fdt_version(fdt) > FDT_LAST_SUPPORTED_VERSION) + fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); +} + +static int fdt_rw_probe_(void *fdt) +{ + if (can_assume(VALID_DTB)) + return 0; + FDT_RO_PROBE(fdt); + + if (!can_assume(LATEST) && fdt_version(fdt) < 17) + return -FDT_ERR_BADVERSION; + if (fdt_blocks_misordered_(fdt, sizeof(struct fdt_reserve_entry), + fdt_size_dt_struct(fdt))) + return -FDT_ERR_BADLAYOUT; + + fdt_downgrade_version(fdt); + return 0; +} + +#define FDT_RW_PROBE(fdt) \ + { \ + int err_; \ + if ((err_ = fdt_rw_probe_(fdt)) != 0) \ + return err_; \ + } + +static inline unsigned int fdt_data_size_(void *fdt) +{ + return fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); +} + +static int fdt_splice_(void *fdt, void *splicepoint, int oldlen, int newlen) +{ + char *p = splicepoint; + unsigned int dsize = fdt_data_size_(fdt); + size_t soff = p - (char *)fdt; + + if ((oldlen < 0) || (soff + oldlen < soff) || (soff + oldlen > dsize)) + return -FDT_ERR_BADOFFSET; + if ((p < (char *)fdt) || (dsize + newlen < (unsigned)oldlen)) + return -FDT_ERR_BADOFFSET; + if (dsize - oldlen + newlen > fdt_totalsize(fdt)) + return -FDT_ERR_NOSPACE; + memmove(p + newlen, p + oldlen, ((char *)fdt + dsize) - (p + oldlen)); + return 0; +} + +static int fdt_splice_mem_rsv_(void *fdt, struct fdt_reserve_entry *p, + int oldn, int newn) +{ + int delta = (newn - oldn) * sizeof(*p); + int err; + err = fdt_splice_(fdt, p, oldn * sizeof(*p), newn * sizeof(*p)); + if (err) + return err; + fdt_set_off_dt_struct(fdt, fdt_off_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +static int fdt_splice_struct_(void *fdt, void *p, + int oldlen, int newlen) +{ + int delta = newlen - oldlen; + int err; + + if ((err = fdt_splice_(fdt, p, oldlen, newlen))) + return err; + + fdt_set_size_dt_struct(fdt, fdt_size_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +/* Must only be used to roll back in case of error */ +static void fdt_del_last_string_(void *fdt, const char *s) +{ + int newlen = strlen(s) + 1; + + fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) - newlen); +} + +static int fdt_splice_string_(void *fdt, int newlen) +{ + void *p = (char *)fdt + + fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); + int err; + + if ((err = fdt_splice_(fdt, p, 0, newlen))) + return err; + + fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) + newlen); + return 0; +} + +/** + * fdt_find_add_string_() - Find or allocate a string + * + * @fdt: pointer to the device tree to check/adjust + * @s: string to find/add + * @allocated: Set to 0 if the string was found, 1 if not found and so + * allocated. Ignored if can_assume(NO_ROLLBACK) + * @return offset of string in the string table (whether found or added) + */ +static int fdt_find_add_string_(void *fdt, const char *s, int slen, + int *allocated) +{ + char *strtab = (char *)fdt + fdt_off_dt_strings(fdt); + const char *p; + char *new; + int err; + + if (!can_assume(NO_ROLLBACK)) + *allocated = 0; + + p = fdt_find_string_len_(strtab, fdt_size_dt_strings(fdt), s, slen); + if (p) + /* found it */ + return (p - strtab); + + new = strtab + fdt_size_dt_strings(fdt); + err = fdt_splice_string_(fdt, slen + 1); + if (err) + return err; + + if (!can_assume(NO_ROLLBACK)) + *allocated = 1; + + memcpy(new, s, slen); + new[slen] = '\0'; + + return (new - strtab); +} + +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size) +{ + struct fdt_reserve_entry *re; + int err; + + FDT_RW_PROBE(fdt); + + re = fdt_mem_rsv_w_(fdt, fdt_num_mem_rsv(fdt)); + err = fdt_splice_mem_rsv_(fdt, re, 0, 1); + if (err) + return err; + + re->address = cpu_to_fdt64(address); + re->size = cpu_to_fdt64(size); + return 0; +} + +int fdt_del_mem_rsv(void *fdt, int n) +{ + struct fdt_reserve_entry *re = fdt_mem_rsv_w_(fdt, n); + + FDT_RW_PROBE(fdt); + + if (n >= fdt_num_mem_rsv(fdt)) + return -FDT_ERR_NOTFOUND; + + return fdt_splice_mem_rsv_(fdt, re, 1, 0); +} + +static int fdt_resize_property_(void *fdt, int nodeoffset, + const char *name, int namelen, + int len, struct fdt_property **prop) +{ + int oldlen; + int err; + + *prop = fdt_get_property_namelen_w(fdt, nodeoffset, name, namelen, + &oldlen); + if (!*prop) + return oldlen; + + if ((err = fdt_splice_struct_(fdt, (*prop)->data, FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(len)))) + return err; + + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +static int fdt_add_property_(void *fdt, int nodeoffset, const char *name, + int namelen, int len, struct fdt_property **prop) +{ + int proplen; + int nextoffset; + int namestroff; + int err; + int allocated; + + if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) + return nextoffset; + + namestroff = fdt_find_add_string_(fdt, name, namelen, &allocated); + if (namestroff < 0) + return namestroff; + + *prop = fdt_offset_ptr_w_(fdt, nextoffset); + proplen = sizeof(**prop) + FDT_TAGALIGN(len); + + err = fdt_splice_struct_(fdt, *prop, 0, proplen); + if (err) { + /* Delete the string if we failed to add it */ + if (!can_assume(NO_ROLLBACK) && allocated) + fdt_del_last_string_(fdt, name); + return err; + } + + (*prop)->tag = cpu_to_fdt32(FDT_PROP); + (*prop)->nameoff = cpu_to_fdt32(namestroff); + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +int fdt_set_name(void *fdt, int nodeoffset, const char *name) +{ + char *namep; + int oldlen, newlen; + int err; + + FDT_RW_PROBE(fdt); + + namep = (char *)(uintptr_t)fdt_get_name(fdt, nodeoffset, &oldlen); + if (!namep) + return oldlen; + + newlen = strlen(name); + + err = fdt_splice_struct_(fdt, namep, FDT_TAGALIGN(oldlen+1), + FDT_TAGALIGN(newlen+1)); + if (err) + return err; + + memcpy(namep, name, newlen+1); + return 0; +} + +int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, int len, void **prop_data) +{ + struct fdt_property *prop; + int err; + + FDT_RW_PROBE(fdt); + + err = fdt_resize_property_(fdt, nodeoffset, name, namelen, len, &prop); + if (err == -FDT_ERR_NOTFOUND) + err = fdt_add_property_(fdt, nodeoffset, name, namelen, len, + &prop); + if (err) + return err; + + *prop_data = prop->data; + return 0; +} + +int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, const void *val, int len) +{ + void *prop_data; + int err; + + err = fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, namelen, + len, &prop_data); + if (err) + return err; + + if (len) + memcpy(prop_data, val, len); + return 0; +} + +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + struct fdt_property *prop; + int err, oldlen, newlen; + + FDT_RW_PROBE(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); + if (prop) { + newlen = len + oldlen; + err = fdt_splice_struct_(fdt, prop->data, + FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(newlen)); + if (err) + return err; + prop->len = cpu_to_fdt32(newlen); + memcpy(prop->data + oldlen, val, len); + } else { + err = fdt_add_property_(fdt, nodeoffset, name, strlen(name), + len, &prop); + if (err) + return err; + memcpy(prop->data, val, len); + } + return 0; +} + +int fdt_delprop(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len, proplen; + + FDT_RW_PROBE(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + proplen = sizeof(*prop) + FDT_TAGALIGN(len); + return fdt_splice_struct_(fdt, prop, proplen, 0); +} + +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen) +{ + struct fdt_node_header *nh; + int offset, nextoffset; + int nodelen; + int err; + uint32_t tag; + fdt32_t *endtag; + + FDT_RW_PROBE(fdt); + + offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen); + if (offset >= 0) + return -FDT_ERR_EXISTS; + else if (offset != -FDT_ERR_NOTFOUND) + return offset; + + /* Try to place the new node after the parent's properties */ + tag = fdt_next_tag(fdt, parentoffset, &nextoffset); + /* the fdt_subnode_offset_namelen() should ensure this never hits */ + if (!can_assume(LIBFDT_FLAWLESS) && (tag != FDT_BEGIN_NODE)) + return -FDT_ERR_INTERNAL; + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + } while ((tag == FDT_PROP) || (tag == FDT_NOP)); + + nh = fdt_offset_ptr_w_(fdt, offset); + nodelen = sizeof(*nh) + FDT_TAGALIGN(namelen+1) + FDT_TAGSIZE; + + err = fdt_splice_struct_(fdt, nh, 0, nodelen); + if (err) + return err; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memset(nh->name, 0, FDT_TAGALIGN(namelen+1)); + memcpy(nh->name, name, namelen); + endtag = (fdt32_t *)((char *)nh + nodelen - FDT_TAGSIZE); + *endtag = cpu_to_fdt32(FDT_END_NODE); + + return offset; +} + +int fdt_add_subnode(void *fdt, int parentoffset, const char *name) +{ + return fdt_add_subnode_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_del_node(void *fdt, int nodeoffset) +{ + int endoffset; + + FDT_RW_PROBE(fdt); + + endoffset = fdt_node_end_offset_(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + return fdt_splice_struct_(fdt, fdt_offset_ptr_w_(fdt, nodeoffset), + endoffset - nodeoffset, 0); +} + +static void fdt_packblocks_(const char *old, char *new, + int mem_rsv_size, + int struct_size, + int strings_size) +{ + int mem_rsv_off, struct_off, strings_off; + + mem_rsv_off = FDT_ALIGN(sizeof(struct fdt_header), 8); + struct_off = mem_rsv_off + mem_rsv_size; + strings_off = struct_off + struct_size; + + memmove(new + mem_rsv_off, old + fdt_off_mem_rsvmap(old), mem_rsv_size); + fdt_set_off_mem_rsvmap(new, mem_rsv_off); + + memmove(new + struct_off, old + fdt_off_dt_struct(old), struct_size); + fdt_set_off_dt_struct(new, struct_off); + fdt_set_size_dt_struct(new, struct_size); + + memmove(new + strings_off, old + fdt_off_dt_strings(old), strings_size); + fdt_set_off_dt_strings(new, strings_off); + fdt_set_size_dt_strings(new, fdt_size_dt_strings(old)); +} + +int fdt_open_into(const void *fdt, void *buf, int bufsize) +{ + int err; + int mem_rsv_size, struct_size; + int newsize; + const char *fdtstart = fdt; + const char *fdtend = fdtstart + fdt_totalsize(fdt); + char *tmp; + + FDT_RO_PROBE(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + + if (can_assume(LATEST) || fdt_version(fdt) >= 17) { + struct_size = fdt_size_dt_struct(fdt); + } else if (fdt_version(fdt) == 16) { + struct_size = 0; + while (fdt_next_tag(fdt, struct_size, &struct_size) != FDT_END) + ; + if (struct_size < 0) + return struct_size; + } else { + return -FDT_ERR_BADVERSION; + } + + if (can_assume(LIBFDT_ORDER) || + !fdt_blocks_misordered_(fdt, mem_rsv_size, struct_size)) { + /* no further work necessary */ + err = fdt_move(fdt, buf, bufsize); + if (err) + return err; + fdt_set_version(buf, 17); + fdt_set_size_dt_struct(buf, struct_size); + fdt_set_totalsize(buf, bufsize); + return 0; + } + + /* Need to reorder */ + newsize = FDT_ALIGN(sizeof(struct fdt_header), 8) + mem_rsv_size + + struct_size + fdt_size_dt_strings(fdt); + + if (bufsize < newsize) + return -FDT_ERR_NOSPACE; + + /* First attempt to build converted tree at beginning of buffer */ + tmp = buf; + /* But if that overlaps with the old tree... */ + if (((tmp + newsize) > fdtstart) && (tmp < fdtend)) { + /* Try right after the old tree instead */ + tmp = (char *)(uintptr_t)fdtend; + if ((tmp + newsize) > ((char *)buf + bufsize)) + return -FDT_ERR_NOSPACE; + } + + fdt_packblocks_(fdt, tmp, mem_rsv_size, struct_size, + fdt_size_dt_strings(fdt)); + memmove(buf, tmp, newsize); + + fdt_set_magic(buf, FDT_MAGIC); + fdt_set_totalsize(buf, bufsize); + fdt_set_version(buf, 17); + fdt_set_last_comp_version(buf, 16); + fdt_set_boot_cpuid_phys(buf, fdt_boot_cpuid_phys(fdt)); + + return 0; +} + +int fdt_pack(void *fdt) +{ + int mem_rsv_size; + + FDT_RW_PROBE(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + fdt_packblocks_(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt), + fdt_size_dt_strings(fdt)); + fdt_set_totalsize(fdt, fdt_data_size_(fdt)); + + return 0; +} diff --git a/scripts/dtc/libfdt/fdt_strerror.c b/scripts/dtc/libfdt/fdt_strerror.c new file mode 100644 index 000000000..d852b77e8 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_strerror.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +struct fdt_errtabent { + const char *str; +}; + +#define FDT_ERRTABENT(val) \ + [(val)] = { .str = #val, } + +static struct fdt_errtabent fdt_errtable[] = { + FDT_ERRTABENT(FDT_ERR_NOTFOUND), + FDT_ERRTABENT(FDT_ERR_EXISTS), + FDT_ERRTABENT(FDT_ERR_NOSPACE), + + FDT_ERRTABENT(FDT_ERR_BADOFFSET), + FDT_ERRTABENT(FDT_ERR_BADPATH), + FDT_ERRTABENT(FDT_ERR_BADPHANDLE), + FDT_ERRTABENT(FDT_ERR_BADSTATE), + + FDT_ERRTABENT(FDT_ERR_TRUNCATED), + FDT_ERRTABENT(FDT_ERR_BADMAGIC), + FDT_ERRTABENT(FDT_ERR_BADVERSION), + FDT_ERRTABENT(FDT_ERR_BADSTRUCTURE), + FDT_ERRTABENT(FDT_ERR_BADLAYOUT), + FDT_ERRTABENT(FDT_ERR_INTERNAL), + FDT_ERRTABENT(FDT_ERR_BADNCELLS), + FDT_ERRTABENT(FDT_ERR_BADVALUE), + FDT_ERRTABENT(FDT_ERR_BADOVERLAY), + FDT_ERRTABENT(FDT_ERR_NOPHANDLES), + FDT_ERRTABENT(FDT_ERR_BADFLAGS), + FDT_ERRTABENT(FDT_ERR_ALIGNMENT), +}; +#define FDT_ERRTABSIZE ((int)(sizeof(fdt_errtable) / sizeof(fdt_errtable[0]))) + +const char *fdt_strerror(int errval) +{ + if (errval > 0) + return "<valid offset/length>"; + else if (errval == 0) + return "<no error>"; + else if (-errval < FDT_ERRTABSIZE) { + const char *s = fdt_errtable[-errval].str; + + if (s) + return s; + } + + return "<unknown error>"; +} diff --git a/scripts/dtc/libfdt/fdt_sw.c b/scripts/dtc/libfdt/fdt_sw.c new file mode 100644 index 000000000..4c569ee7e --- /dev/null +++ b/scripts/dtc/libfdt/fdt_sw.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +static int fdt_sw_probe_(void *fdt) +{ + if (!can_assume(VALID_INPUT)) { + if (fdt_magic(fdt) == FDT_MAGIC) + return -FDT_ERR_BADSTATE; + else if (fdt_magic(fdt) != FDT_SW_MAGIC) + return -FDT_ERR_BADMAGIC; + } + + return 0; +} + +#define FDT_SW_PROBE(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_(fdt)) != 0) \ + return err; \ + } + +/* 'memrsv' state: Initial state after fdt_create() + * + * Allowed functions: + * fdt_add_reservemap_entry() + * fdt_finish_reservemap() [moves to 'struct' state] + */ +static int fdt_sw_probe_memrsv_(void *fdt) +{ + int err = fdt_sw_probe_(fdt); + if (err) + return err; + + if (!can_assume(VALID_INPUT) && fdt_off_dt_strings(fdt) != 0) + return -FDT_ERR_BADSTATE; + return 0; +} + +#define FDT_SW_PROBE_MEMRSV(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_memrsv_(fdt)) != 0) \ + return err; \ + } + +/* 'struct' state: Enter this state after fdt_finish_reservemap() + * + * Allowed functions: + * fdt_begin_node() + * fdt_end_node() + * fdt_property*() + * fdt_finish() [moves to 'complete' state] + */ +static int fdt_sw_probe_struct_(void *fdt) +{ + int err = fdt_sw_probe_(fdt); + if (err) + return err; + + if (!can_assume(VALID_INPUT) && + fdt_off_dt_strings(fdt) != fdt_totalsize(fdt)) + return -FDT_ERR_BADSTATE; + return 0; +} + +#define FDT_SW_PROBE_STRUCT(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_struct_(fdt)) != 0) \ + return err; \ + } + +static inline uint32_t sw_flags(void *fdt) +{ + /* assert: (fdt_magic(fdt) == FDT_SW_MAGIC) */ + return fdt_last_comp_version(fdt); +} + +/* 'complete' state: Enter this state after fdt_finish() + * + * Allowed functions: none + */ + +static void *fdt_grab_space_(void *fdt, size_t len) +{ + unsigned int offset = fdt_size_dt_struct(fdt); + unsigned int spaceleft; + + spaceleft = fdt_totalsize(fdt) - fdt_off_dt_struct(fdt) + - fdt_size_dt_strings(fdt); + + if ((offset + len < offset) || (offset + len > spaceleft)) + return NULL; + + fdt_set_size_dt_struct(fdt, offset + len); + return fdt_offset_ptr_w_(fdt, offset); +} + +int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags) +{ + const int hdrsize = FDT_ALIGN(sizeof(struct fdt_header), + sizeof(struct fdt_reserve_entry)); + void *fdt = buf; + + if (bufsize < hdrsize) + return -FDT_ERR_NOSPACE; + + if (flags & ~FDT_CREATE_FLAGS_ALL) + return -FDT_ERR_BADFLAGS; + + memset(buf, 0, bufsize); + + /* + * magic and last_comp_version keep intermediate state during the fdt + * creation process, which is replaced with the proper FDT format by + * fdt_finish(). + * + * flags should be accessed with sw_flags(). + */ + fdt_set_magic(fdt, FDT_SW_MAGIC); + fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); + fdt_set_last_comp_version(fdt, flags); + + fdt_set_totalsize(fdt, bufsize); + + fdt_set_off_mem_rsvmap(fdt, hdrsize); + fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); + fdt_set_off_dt_strings(fdt, 0); + + return 0; +} + +int fdt_create(void *buf, int bufsize) +{ + return fdt_create_with_flags(buf, bufsize, 0); +} + +int fdt_resize(void *fdt, void *buf, int bufsize) +{ + size_t headsize, tailsize; + char *oldtail, *newtail; + + FDT_SW_PROBE(fdt); + + if (bufsize < 0) + return -FDT_ERR_NOSPACE; + + headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + tailsize = fdt_size_dt_strings(fdt); + + if (!can_assume(VALID_DTB) && + headsize + tailsize > fdt_totalsize(fdt)) + return -FDT_ERR_INTERNAL; + + if ((headsize + tailsize) > (unsigned)bufsize) + return -FDT_ERR_NOSPACE; + + oldtail = (char *)fdt + fdt_totalsize(fdt) - tailsize; + newtail = (char *)buf + bufsize - tailsize; + + /* Two cases to avoid clobbering data if the old and new + * buffers partially overlap */ + if (buf <= fdt) { + memmove(buf, fdt, headsize); + memmove(newtail, oldtail, tailsize); + } else { + memmove(newtail, oldtail, tailsize); + memmove(buf, fdt, headsize); + } + + fdt_set_totalsize(buf, bufsize); + if (fdt_off_dt_strings(buf)) + fdt_set_off_dt_strings(buf, bufsize); + + return 0; +} + +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) +{ + struct fdt_reserve_entry *re; + int offset; + + FDT_SW_PROBE_MEMRSV(fdt); + + offset = fdt_off_dt_struct(fdt); + if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) + return -FDT_ERR_NOSPACE; + + re = (struct fdt_reserve_entry *)((char *)fdt + offset); + re->address = cpu_to_fdt64(addr); + re->size = cpu_to_fdt64(size); + + fdt_set_off_dt_struct(fdt, offset + sizeof(*re)); + + return 0; +} + +int fdt_finish_reservemap(void *fdt) +{ + int err = fdt_add_reservemap_entry(fdt, 0, 0); + + if (err) + return err; + + fdt_set_off_dt_strings(fdt, fdt_totalsize(fdt)); + return 0; +} + +int fdt_begin_node(void *fdt, const char *name) +{ + struct fdt_node_header *nh; + int namelen; + + FDT_SW_PROBE_STRUCT(fdt); + + namelen = strlen(name) + 1; + nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); + if (! nh) + return -FDT_ERR_NOSPACE; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memcpy(nh->name, name, namelen); + return 0; +} + +int fdt_end_node(void *fdt) +{ + fdt32_t *en; + + FDT_SW_PROBE_STRUCT(fdt); + + en = fdt_grab_space_(fdt, FDT_TAGSIZE); + if (! en) + return -FDT_ERR_NOSPACE; + + *en = cpu_to_fdt32(FDT_END_NODE); + return 0; +} + +static int fdt_add_string_(void *fdt, const char *s) +{ + char *strtab = (char *)fdt + fdt_totalsize(fdt); + unsigned int strtabsize = fdt_size_dt_strings(fdt); + unsigned int len = strlen(s) + 1; + unsigned int struct_top, offset; + + offset = strtabsize + len; + struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + if (fdt_totalsize(fdt) - offset < struct_top) + return 0; /* no more room :( */ + + memcpy(strtab - offset, s, len); + fdt_set_size_dt_strings(fdt, strtabsize + len); + return -offset; +} + +/* Must only be used to roll back in case of error */ +static void fdt_del_last_string_(void *fdt, const char *s) +{ + int strtabsize = fdt_size_dt_strings(fdt); + int len = strlen(s) + 1; + + fdt_set_size_dt_strings(fdt, strtabsize - len); +} + +static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) +{ + char *strtab = (char *)fdt + fdt_totalsize(fdt); + int strtabsize = fdt_size_dt_strings(fdt); + const char *p; + + *allocated = 0; + + p = fdt_find_string_(strtab - strtabsize, strtabsize, s); + if (p) + return p - strtab; + + *allocated = 1; + + return fdt_add_string_(fdt, s); +} + +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) +{ + struct fdt_property *prop; + int nameoff; + int allocated; + + FDT_SW_PROBE_STRUCT(fdt); + + /* String de-duplication can be slow, _NO_NAME_DEDUP skips it */ + if (sw_flags(fdt) & FDT_CREATE_FLAG_NO_NAME_DEDUP) { + allocated = 1; + nameoff = fdt_add_string_(fdt, name); + } else { + nameoff = fdt_find_add_string_(fdt, name, &allocated); + } + if (nameoff == 0) + return -FDT_ERR_NOSPACE; + + prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); + if (! prop) { + if (allocated) + fdt_del_last_string_(fdt, name); + return -FDT_ERR_NOSPACE; + } + + prop->tag = cpu_to_fdt32(FDT_PROP); + prop->nameoff = cpu_to_fdt32(nameoff); + prop->len = cpu_to_fdt32(len); + *valp = prop->data; + return 0; +} + +int fdt_property(void *fdt, const char *name, const void *val, int len) +{ + void *ptr; + int ret; + + ret = fdt_property_placeholder(fdt, name, len, &ptr); + if (ret) + return ret; + memcpy(ptr, val, len); + return 0; +} + +int fdt_finish(void *fdt) +{ + char *p = (char *)fdt; + fdt32_t *end; + int oldstroffset, newstroffset; + uint32_t tag; + int offset, nextoffset; + + FDT_SW_PROBE_STRUCT(fdt); + + /* Add terminator */ + end = fdt_grab_space_(fdt, sizeof(*end)); + if (! end) + return -FDT_ERR_NOSPACE; + *end = cpu_to_fdt32(FDT_END); + + /* Relocate the string table */ + oldstroffset = fdt_totalsize(fdt) - fdt_size_dt_strings(fdt); + newstroffset = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + memmove(p + newstroffset, p + oldstroffset, fdt_size_dt_strings(fdt)); + fdt_set_off_dt_strings(fdt, newstroffset); + + /* Walk the structure, correcting string offsets */ + offset = 0; + while ((tag = fdt_next_tag(fdt, offset, &nextoffset)) != FDT_END) { + if (tag == FDT_PROP) { + struct fdt_property *prop = + fdt_offset_ptr_w_(fdt, offset); + int nameoff; + + nameoff = fdt32_to_cpu(prop->nameoff); + nameoff += fdt_size_dt_strings(fdt); + prop->nameoff = cpu_to_fdt32(nameoff); + } + offset = nextoffset; + } + if (nextoffset < 0) + return nextoffset; + + /* Finally, adjust the header */ + fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); + + /* And fix up fields that were keeping intermediate state. */ + fdt_set_last_comp_version(fdt, FDT_LAST_COMPATIBLE_VERSION); + fdt_set_magic(fdt, FDT_MAGIC); + + return 0; +} diff --git a/scripts/dtc/libfdt/fdt_wip.c b/scripts/dtc/libfdt/fdt_wip.c new file mode 100644 index 000000000..c2d7566a6 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_wip.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#include "libfdt_internal.h" + +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len) +{ + void *propval; + int proplen; + + propval = fdt_getprop_namelen_w(fdt, nodeoffset, name, namelen, + &proplen); + if (!propval) + return proplen; + + if ((unsigned)proplen < (len + idx)) + return -FDT_ERR_NOSPACE; + + memcpy((char *)propval + idx, val, len); + return 0; +} + +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + const void *propval; + int proplen; + + propval = fdt_getprop(fdt, nodeoffset, name, &proplen); + if (!propval) + return proplen; + + if (proplen != len) + return -FDT_ERR_NOSPACE; + + return fdt_setprop_inplace_namelen_partial(fdt, nodeoffset, name, + strlen(name), 0, + val, len); +} + +static void fdt_nop_region_(void *start, int len) +{ + fdt32_t *p; + + for (p = start; (char *)p < ((char *)start + len); p++) + *p = cpu_to_fdt32(FDT_NOP); +} + +int fdt_nop_property(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len; + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + fdt_nop_region_(prop, len + sizeof(*prop)); + + return 0; +} + +int fdt_node_end_offset_(void *fdt, int offset) +{ + int depth = 0; + + while ((offset >= 0) && (depth >= 0)) + offset = fdt_next_node(fdt, offset, &depth); + + return offset; +} + +int fdt_nop_node(void *fdt, int nodeoffset) +{ + int endoffset; + + endoffset = fdt_node_end_offset_(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + fdt_nop_region_(fdt_offset_ptr_w(fdt, nodeoffset, 0), + endoffset - nodeoffset); + return 0; +} diff --git a/scripts/dtc/libfdt/libfdt.h b/scripts/dtc/libfdt/libfdt.h new file mode 100644 index 000000000..81aaf7cba --- /dev/null +++ b/scripts/dtc/libfdt/libfdt.h @@ -0,0 +1,2540 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_H +#define LIBFDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ + +#include "libfdt_env.h" +#include "fdt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FDT_FIRST_SUPPORTED_VERSION 0x02 +#define FDT_LAST_COMPATIBLE_VERSION 0x10 +#define FDT_LAST_SUPPORTED_VERSION 0x11 + +/* Error codes: informative error codes */ +#define FDT_ERR_NOTFOUND 1 + /* FDT_ERR_NOTFOUND: The requested node or property does not exist */ +#define FDT_ERR_EXISTS 2 + /* FDT_ERR_EXISTS: Attempted to create a node or property which + * already exists */ +#define FDT_ERR_NOSPACE 3 + /* FDT_ERR_NOSPACE: Operation needed to expand the device + * tree, but its buffer did not have sufficient space to + * contain the expanded tree. Use fdt_open_into() to move the + * device tree to a buffer with more space. */ + +/* Error codes: codes for bad parameters */ +#define FDT_ERR_BADOFFSET 4 + /* FDT_ERR_BADOFFSET: Function was passed a structure block + * offset which is out-of-bounds, or which points to an + * unsuitable part of the structure for the operation. */ +#define FDT_ERR_BADPATH 5 + /* FDT_ERR_BADPATH: Function was passed a badly formatted path + * (e.g. missing a leading / for a function which requires an + * absolute path) */ +#define FDT_ERR_BADPHANDLE 6 + /* FDT_ERR_BADPHANDLE: Function was passed an invalid phandle. + * This can be caused either by an invalid phandle property + * length, or the phandle value was either 0 or -1, which are + * not permitted. */ +#define FDT_ERR_BADSTATE 7 + /* FDT_ERR_BADSTATE: Function was passed an incomplete device + * tree created by the sequential-write functions, which is + * not sufficiently complete for the requested operation. */ + +/* Error codes: codes for bad device tree blobs */ +#define FDT_ERR_TRUNCATED 8 + /* FDT_ERR_TRUNCATED: FDT or a sub-block is improperly + * terminated (overflows, goes outside allowed bounds, or + * isn't properly terminated). */ +#define FDT_ERR_BADMAGIC 9 + /* FDT_ERR_BADMAGIC: Given "device tree" appears not to be a + * device tree at all - it is missing the flattened device + * tree magic number. */ +#define FDT_ERR_BADVERSION 10 + /* FDT_ERR_BADVERSION: Given device tree has a version which + * can't be handled by the requested operation. For + * read-write functions, this may mean that fdt_open_into() is + * required to convert the tree to the expected version. */ +#define FDT_ERR_BADSTRUCTURE 11 + /* FDT_ERR_BADSTRUCTURE: Given device tree has a corrupt + * structure block or other serious error (e.g. misnested + * nodes, or subnodes preceding properties). */ +#define FDT_ERR_BADLAYOUT 12 + /* FDT_ERR_BADLAYOUT: For read-write functions, the given + * device tree has it's sub-blocks in an order that the + * function can't handle (memory reserve map, then structure, + * then strings). Use fdt_open_into() to reorganize the tree + * into a form suitable for the read-write operations. */ + +/* "Can't happen" error indicating a bug in libfdt */ +#define FDT_ERR_INTERNAL 13 + /* FDT_ERR_INTERNAL: libfdt has failed an internal assertion. + * Should never be returned, if it is, it indicates a bug in + * libfdt itself. */ + +/* Errors in device tree content */ +#define FDT_ERR_BADNCELLS 14 + /* FDT_ERR_BADNCELLS: Device tree has a #address-cells, #size-cells + * or similar property with a bad format or value */ + +#define FDT_ERR_BADVALUE 15 + /* FDT_ERR_BADVALUE: Device tree has a property with an unexpected + * value. For example: a property expected to contain a string list + * is not NUL-terminated within the length of its value. */ + +#define FDT_ERR_BADOVERLAY 16 + /* FDT_ERR_BADOVERLAY: The device tree overlay, while + * correctly structured, cannot be applied due to some + * unexpected or missing value, property or node. */ + +#define FDT_ERR_NOPHANDLES 17 + /* FDT_ERR_NOPHANDLES: The device tree doesn't have any + * phandle available anymore without causing an overflow */ + +#define FDT_ERR_BADFLAGS 18 + /* FDT_ERR_BADFLAGS: The function was passed a flags field that + * contains invalid flags or an invalid combination of flags. */ + +#define FDT_ERR_ALIGNMENT 19 + /* FDT_ERR_ALIGNMENT: The device tree base address is not 8-byte + * aligned. */ + +#define FDT_ERR_MAX 19 + +/* constants */ +#define FDT_MAX_PHANDLE 0xfffffffe + /* Valid values for phandles range from 1 to 2^32-2. */ + +/**********************************************************************/ +/* Low-level functions (you probably don't need these) */ +/**********************************************************************/ + +/** + * fdt_offset_ptr - safely get a byte range within the device tree blob + * @fdt: Pointer to the device tree blob + * @offset: Offset within the blob to the desired byte range + * @checklen: Required length of the byte range + * + * fdt_offset_ptr() returns a pointer to the byte range of length @checklen at + * the given @offset within the device tree blob, after verifying that the byte + * range fits entirely within the blob and does not overflow. + * + * returns: + * pointer to the byte range, on success + * NULL, if the requested range does not fit within the blob + */ +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int checklen); +#endif +static inline void *fdt_offset_ptr_w(void *fdt, int offset, int checklen) +{ + return (void *)(uintptr_t)fdt_offset_ptr(fdt, offset, checklen); +} + +/** + * fdt_next_tag - get next tag in the device tree + * @fdt: Pointer to the device tree blob + * @offset: Offset within the blob to start searching + * @nextoffset: Pointer to variable to store the offset of the next tag + * + * fdt_next_tag() returns the tag type of the next tag in the device tree + * blob starting from the given @offset. If @nextoffset is non-NULL, it will + * be set to the offset immediately following the tag. + * + * returns: + * the tag type (FDT_BEGIN_NODE, FDT_END_NODE, FDT_PROP, FDT_NOP, FDT_END), + * FDT_END, if offset is out of bounds + */ +uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset); + +/* + * External helpers to access words from a device tree blob. They're built + * to work even with unaligned pointers on platforms (such as ARMv5) that don't + * like unaligned loads and stores. + */ +static inline uint16_t fdt16_ld(const fdt16_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint16_t)bp[0] << 8) | bp[1]; +} + +static inline uint32_t fdt32_ld(const fdt32_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint32_t)bp[0] << 24) + | ((uint32_t)bp[1] << 16) + | ((uint32_t)bp[2] << 8) + | bp[3]; +} + +static inline void fdt32_st(void *property, uint32_t value) +{ + uint8_t *bp = (uint8_t *)property; + + bp[0] = value >> 24; + bp[1] = (value >> 16) & 0xff; + bp[2] = (value >> 8) & 0xff; + bp[3] = value & 0xff; +} + +static inline uint64_t fdt64_ld(const fdt64_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint64_t)bp[0] << 56) + | ((uint64_t)bp[1] << 48) + | ((uint64_t)bp[2] << 40) + | ((uint64_t)bp[3] << 32) + | ((uint64_t)bp[4] << 24) + | ((uint64_t)bp[5] << 16) + | ((uint64_t)bp[6] << 8) + | bp[7]; +} + +static inline void fdt64_st(void *property, uint64_t value) +{ + uint8_t *bp = (uint8_t *)property; + + bp[0] = value >> 56; + bp[1] = (value >> 48) & 0xff; + bp[2] = (value >> 40) & 0xff; + bp[3] = (value >> 32) & 0xff; + bp[4] = (value >> 24) & 0xff; + bp[5] = (value >> 16) & 0xff; + bp[6] = (value >> 8) & 0xff; + bp[7] = value & 0xff; +} + +/**********************************************************************/ +/* Traversal functions */ +/**********************************************************************/ + +int fdt_next_node(const void *fdt, int offset, int *depth); + +/** + * fdt_first_subnode() - get offset of first direct subnode + * @fdt: FDT blob + * @offset: Offset of node to check + * + * Return: offset of first subnode, or -FDT_ERR_NOTFOUND if there is none + */ +int fdt_first_subnode(const void *fdt, int offset); + +/** + * fdt_next_subnode() - get offset of next direct subnode + * @fdt: FDT blob + * @offset: Offset of previous subnode + * + * After first calling fdt_first_subnode(), call this function repeatedly to + * get direct subnodes of a parent node. + * + * Return: offset of next subnode, or -FDT_ERR_NOTFOUND if there are no more + * subnodes + */ +int fdt_next_subnode(const void *fdt, int offset); + +/** + * fdt_for_each_subnode - iterate over all subnodes of a parent + * + * @node: child node (int, lvalue) + * @fdt: FDT blob (const void *) + * @parent: parent node (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_subnode(node, fdt, parent) { + * Use node + * ... + * } + * + * if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and @node is used as + * iterator in the loop. The parent variable be constant or even a + * literal. + */ +#define fdt_for_each_subnode(node, fdt, parent) \ + for (node = fdt_first_subnode(fdt, parent); \ + node >= 0; \ + node = fdt_next_subnode(fdt, node)) + +/**********************************************************************/ +/* General functions */ +/**********************************************************************/ +#define fdt_get_header(fdt, field) \ + (fdt32_ld(&((const struct fdt_header *)(fdt))->field)) +#define fdt_magic(fdt) (fdt_get_header(fdt, magic)) +#define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) +#define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) +#define fdt_off_dt_strings(fdt) (fdt_get_header(fdt, off_dt_strings)) +#define fdt_off_mem_rsvmap(fdt) (fdt_get_header(fdt, off_mem_rsvmap)) +#define fdt_version(fdt) (fdt_get_header(fdt, version)) +#define fdt_last_comp_version(fdt) (fdt_get_header(fdt, last_comp_version)) +#define fdt_boot_cpuid_phys(fdt) (fdt_get_header(fdt, boot_cpuid_phys)) +#define fdt_size_dt_strings(fdt) (fdt_get_header(fdt, size_dt_strings)) +#define fdt_size_dt_struct(fdt) (fdt_get_header(fdt, size_dt_struct)) + +#define fdt_set_hdr_(name) \ + static inline void fdt_set_##name(void *fdt, uint32_t val) \ + { \ + struct fdt_header *fdth = (struct fdt_header *)fdt; \ + fdth->name = cpu_to_fdt32(val); \ + } +fdt_set_hdr_(magic) +fdt_set_hdr_(totalsize) +fdt_set_hdr_(off_dt_struct) +fdt_set_hdr_(off_dt_strings) +fdt_set_hdr_(off_mem_rsvmap) +fdt_set_hdr_(version) +fdt_set_hdr_(last_comp_version) +fdt_set_hdr_(boot_cpuid_phys) +fdt_set_hdr_(size_dt_strings) +fdt_set_hdr_(size_dt_struct) +#undef fdt_set_hdr_ + +/** + * fdt_header_size - return the size of the tree's header + * @fdt: pointer to a flattened device tree + * + * Return: size of DTB header in bytes + */ +size_t fdt_header_size(const void *fdt); + +/** + * fdt_header_size_ - internal function to get header size from a version number + * @version: device tree version number + * + * Return: size of DTB header in bytes + */ +size_t fdt_header_size_(uint32_t version); + +/** + * fdt_check_header - sanity check a device tree header + * @fdt: pointer to data which might be a flattened device tree + * + * fdt_check_header() checks that the given buffer contains what + * appears to be a flattened device tree, and that the header contains + * valid information (to the extent that can be determined from the + * header alone). + * + * returns: + * 0, if the buffer appears to contain a valid device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_TRUNCATED, standard meanings, as above + */ +int fdt_check_header(const void *fdt); + +/** + * fdt_move - move a device tree around in memory + * @fdt: pointer to the device tree to move + * @buf: pointer to memory where the device is to be moved + * @bufsize: size of the memory space at buf + * + * fdt_move() relocates, if possible, the device tree blob located at + * fdt to the buffer at buf of size bufsize. The buffer may overlap + * with the existing device tree blob at fdt. Therefore, + * fdt_move(fdt, fdt, fdt_totalsize(fdt)) + * should always succeed. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient to contain the device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_move(const void *fdt, void *buf, int bufsize); + +/**********************************************************************/ +/* Read-only functions */ +/**********************************************************************/ + +/** + * fdt_check_full - check device tree validity + * @fdt: pointer to the device tree blob + * @bufsize: size of the buffer containing the device tree + * + * fdt_check_full() checks that the given buffer contains a valid + * flattened device tree and that the tree structure is internally + * consistent. This is a more thorough check than fdt_check_header(). + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_check_full(const void *fdt, size_t bufsize); + +/** + * fdt_get_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * @lenp: optional pointer to return the string's length + * + * fdt_get_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt, and optionally also + * returns the string's length in *lenp. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds, or doesn't point to a valid string + */ +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); + +/** + * fdt_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * + * fdt_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds, or doesn't point to a valid string + */ +const char *fdt_string(const void *fdt, int stroffset); + +/** + * fdt_find_max_phandle - find and return the highest phandle in a tree + * @fdt: pointer to the device tree blob + * @phandle: return location for the highest phandle value found in the tree + * + * fdt_find_max_phandle() finds the highest phandle value in the given device + * tree. The value returned in @phandle is only valid if the function returns + * success. + * + * returns: + * 0 on success or a negative error code on failure + */ +int fdt_find_max_phandle(const void *fdt, uint32_t *phandle); + +/** + * fdt_get_max_phandle - retrieves the highest phandle in a tree + * @fdt: pointer to the device tree blob + * + * fdt_get_max_phandle retrieves the highest phandle in the given + * device tree. This will ignore badly formatted phandles, or phandles + * with a value of 0 or -1. + * + * This function is deprecated in favour of fdt_find_max_phandle(). + * + * returns: + * the highest phandle on success + * 0, if no phandle was found in the device tree + * -1, if an error occurred + */ +static inline uint32_t fdt_get_max_phandle(const void *fdt) +{ + uint32_t phandle; + int err; + + err = fdt_find_max_phandle(fdt, &phandle); + if (err < 0) + return (uint32_t)-1; + + return phandle; +} + +/** + * fdt_generate_phandle - return a new, unused phandle for a device tree blob + * @fdt: pointer to the device tree blob + * @phandle: return location for the new phandle + * + * Walks the device tree blob and looks for the highest phandle value. On + * success, the new, unused phandle value (one higher than the previously + * highest phandle value in the device tree blob) will be returned in the + * @phandle parameter. + * + * Return: 0 on success or a negative error-code on failure + */ +int fdt_generate_phandle(const void *fdt, uint32_t *phandle); + +/** + * fdt_num_mem_rsv - retrieve the number of memory reserve map entries + * @fdt: pointer to the device tree blob + * + * Returns the number of entries in the device tree blob's memory + * reservation map. This does not include the terminating 0,0 entry + * or any other (0,0) entries reserved for expansion. + * + * returns: + * the number of entries + */ +int fdt_num_mem_rsv(const void *fdt); + +/** + * fdt_get_mem_rsv - retrieve one memory reserve map entry + * @fdt: pointer to the device tree blob + * @n: index of reserve map entry + * @address: pointer to 64-bit variable to hold the start address + * @size: pointer to 64-bit variable to hold the size of the entry + * + * On success, @address and @size will contain the address and size of + * the n-th reserve map entry from the device tree blob, in + * native-endian format. + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size); + +/** + * fdt_subnode_offset_namelen - find a subnode based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * @namelen: number of characters of name to consider + * + * Identical to fdt_subnode_offset(), but only examine the first + * namelen characters of name for matching the subnode name. This is + * useful for finding subnodes based on a portion of a larger string, + * such as a full path. + * + * Return: offset of the subnode or -FDT_ERR_NOTFOUND if name not found. + */ +#ifndef SWIG /* Not available in Python */ +int fdt_subnode_offset_namelen(const void *fdt, int parentoffset, + const char *name, int namelen); +#endif +/** + * fdt_subnode_offset - find a subnode of a given node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_subnode_offset() finds a subnode of the node at structure block + * offset parentoffset with the given name. name may include a unit + * address, in which case fdt_subnode_offset() will find the subnode + * with that unit address, or the unit address may be omitted, in + * which case fdt_subnode_offset() will find an arbitrary subnode + * whose name excluding unit address matches the given name. + * + * returns: + * structure block offset of the requested subnode (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_subnode_offset(const void *fdt, int parentoffset, const char *name); + +/** + * fdt_path_offset_namelen - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * @namelen: number of characters of path to consider + * + * Identical to fdt_path_offset(), but only consider the first namelen + * characters of path as the path name. + * + * Return: offset of the node or negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen); +#endif + +/** + * fdt_path_offset - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * + * fdt_path_offset() finds a node of a given path in the device tree. + * Each path component may omit the unit address portion, but the + * results of this are undefined if any such path component is + * ambiguous (that is if there are multiple nodes at the relevant + * 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 '/' 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, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_path_offset(const void *fdt, const char *path); + +/** + * fdt_get_name - retrieve the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the starting node + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_name() retrieves the name (including unit address) of the + * device tree node at structure block offset nodeoffset. If lenp is + * non-NULL, the length of this name is also returned, in the integer + * pointed to by lenp. + * + * returns: + * pointer to the node's name, on success + * If lenp is non-NULL, *lenp contains the length of that name + * (>=0) + * NULL, on error + * if lenp is non-NULL *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +const char *fdt_get_name(const void *fdt, int nodeoffset, int *lenp); + +/** + * fdt_first_property_offset - find the offset of a node's first property + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * + * fdt_first_property_offset() finds the first property of the node at + * the given structure block offset. + * + * returns: + * structure block offset of the property (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested node has no properties + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_first_property_offset(const void *fdt, int nodeoffset); + +/** + * fdt_next_property_offset - step through a node's properties + * @fdt: pointer to the device tree blob + * @offset: structure block offset of a property + * + * fdt_next_property_offset() finds the property immediately after the + * one at the given structure block offset. This will be a property + * of the same node as the given property. + * + * returns: + * structure block offset of the next property (>=0), on success + * -FDT_ERR_NOTFOUND, if the given property is the last in its node + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_next_property_offset(const void *fdt, int offset); + +/** + * fdt_for_each_property_offset - iterate over all properties of a node + * + * @property: property offset (int, lvalue) + * @fdt: FDT blob (const void *) + * @node: node offset (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_property_offset(property, fdt, node) { + * Use property + * ... + * } + * + * if ((property < 0) && (property != -FDT_ERR_NOTFOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and property is used as + * iterator in the loop. The node variable can be constant or even a + * literal. + */ +#define fdt_for_each_property_offset(property, fdt, node) \ + for (property = fdt_first_property_offset(fdt, node); \ + property >= 0; \ + property = fdt_next_property_offset(fdt, property)) + +/** + * fdt_get_property_by_offset - retrieve the property at a given offset + * @fdt: pointer to the device tree blob + * @offset: offset of the property to retrieve + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property_by_offset() retrieves a pointer to the + * fdt_property structure within the device tree blob at the given + * offset. If lenp is non-NULL, the length of the property value is + * also returned, in the integer pointed to by lenp. + * + * Note that this code only works on device tree versions >= 16. fdt_getprop() + * works on all versions. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp); +static inline struct fdt_property *fdt_get_property_by_offset_w(void *fdt, + int offset, + int *lenp) +{ + return (struct fdt_property *)(uintptr_t) + fdt_get_property_by_offset(fdt, offset, lenp); +} + +/** + * fdt_get_property_namelen - find a property based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_get_property(), but only examine the first namelen + * characters of name for matching the property name. + * + * Return: pointer to the structure representing the property, or NULL + * if not found + */ +#ifndef SWIG /* Not available in Python */ +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int nodeoffset, + const char *name, + int namelen, int *lenp); +static inline struct fdt_property * +fdt_get_property_namelen_w(void *fdt, int nodeoffset, const char *name, + int namelen, int *lenp) +{ + return (struct fdt_property *)(uintptr_t)fdt_get_property_namelen( + fdt, nodeoffset, name, namelen, lenp); +} +#endif + +/** + * fdt_get_property - find a given property in a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property() retrieves a pointer to the fdt_property + * structure within the device tree blob corresponding to the property + * named 'name' of the node at offset nodeoffset. If lenp is + * non-NULL, the length of the property value is also returned, in the + * integer pointed to by lenp. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline struct fdt_property *fdt_get_property_w(void *fdt, int nodeoffset, + const char *name, + int *lenp) +{ + return (struct fdt_property *)(uintptr_t) + fdt_get_property(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_getprop_by_offset - retrieve the value of a property at a given offset + * @fdt: pointer to the device tree blob + * @offset: offset of the property to read + * @namep: pointer to a string variable (will be overwritten) or NULL + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop_by_offset() retrieves a pointer to the value of the + * property at structure block offset 'offset' (this will be a pointer + * to within the device blob itself, not a copy of the value). If + * lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by lenp. If namep is non-NULL, + * the property's name will also be returned in the char * pointed to + * by namep (this will be a pointer to within the device tree's string + * block, not a new copy of the name). + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * if namep is non-NULL *namep contains a pointer to the property + * name. + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp); +#endif + +/** + * fdt_getprop_namelen - get property value based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_getprop(), but only examine the first namelen + * characters of name for matching the property name. + * + * Return: pointer to the property's value or NULL on error + */ +#ifndef SWIG /* Not available in Python */ +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp); +static inline void *fdt_getprop_namelen_w(void *fdt, int nodeoffset, + const char *name, int namelen, + int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop_namelen(fdt, nodeoffset, name, + namelen, lenp); +} +#endif + +/** + * fdt_getprop - retrieve the value of a given property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop() retrieves a pointer to the value of the property + * named @name of the node at offset @nodeoffset (this will be a + * pointer to within the device blob itself, not a copy of the value). + * If @lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by @lenp. + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline void *fdt_getprop_w(void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_get_phandle - retrieve the phandle of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the node + * + * fdt_get_phandle() retrieves the phandle of the device tree node at + * structure block offset nodeoffset. + * + * returns: + * the phandle of the node at nodeoffset, on success (!= 0, != -1) + * 0, if the node has no phandle, or another error occurs + */ +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset); + +/** + * fdt_get_alias_namelen - get alias based on substring + * @fdt: pointer to the device tree blob + * @name: name of the alias to look up + * @namelen: number of characters of name to consider + * + * Identical to fdt_get_alias(), but only examine the first @namelen + * characters of @name for matching the alias name. + * + * Return: a pointer to the expansion of the alias named @name, if it exists, + * NULL otherwise + */ +#ifndef SWIG /* Not available in Python */ +const char *fdt_get_alias_namelen(const void *fdt, + const char *name, int namelen); +#endif + +/** + * fdt_get_alias - retrieve the path referenced by a given alias + * @fdt: pointer to the device tree blob + * @name: name of the alias to look up + * + * fdt_get_alias() retrieves the value of a given alias. That is, the + * value of the property named @name in the node /aliases. + * + * returns: + * a pointer to the expansion of the alias named 'name', if it exists + * NULL, if the given alias or the /aliases node does not exist + */ +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 + * @buf: character buffer to contain the returned path (will be overwritten) + * @buflen: size of the character buffer at buf + * + * fdt_get_path() computes the full path of the node at offset + * nodeoffset, and records that path in the buffer at buf. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * 0, on success + * buf contains the absolute path of the node at + * nodeoffset, as a NUL-terminated string. + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOSPACE, the path of the given node is longer than (bufsize-1) + * characters and will not fit in the given buffer. + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); + +/** + * fdt_supernode_atdepth_offset - find a specific ancestor of a node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * @supernodedepth: depth of the ancestor to find + * @nodedepth: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_supernode_atdepth_offset() finds an ancestor of the given node + * at a specific depth from the root (where the root itself has depth + * 0, its immediate subnodes depth 1 and so forth). So + * fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, NULL); + * will always return 0, the offset of the root node. If the node at + * nodeoffset has depth D, then: + * fdt_supernode_atdepth_offset(fdt, nodeoffset, D, NULL); + * will return nodeoffset itself. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * structure block offset of the node at node offset's ancestor + * of depth supernodedepth (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOTFOUND, supernodedepth was greater than the depth of + * nodeoffset + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth); + +/** + * fdt_node_depth - find the depth of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_node_depth() finds the depth of a given node. The root node + * has depth 0, its immediate subnodes depth 1 and so forth. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * depth of the node at nodeoffset (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_depth(const void *fdt, int nodeoffset); + +/** + * fdt_parent_offset - find the parent of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_parent_offset() locates the parent node of a given node (that + * is, it finds the offset of the node which contains the node at + * nodeoffset as a subnode). + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset, *twice*. + * + * returns: + * structure block offset of the parent of the node at nodeoffset + * (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_parent_offset(const void *fdt, int nodeoffset); + +/** + * fdt_node_offset_by_prop_value - find nodes with a given property value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @propname: property name to check + * @propval: property value to search for + * @proplen: length of the value in propval + * + * fdt_node_offset_by_prop_value() returns the offset of the first + * node after startoffset, which has a property named propname whose + * value is of length proplen and has value equal to propval; or if + * startoffset is -1, the very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_prop_value(fdt, -1, propname, + * propval, proplen); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_prop_value(fdt, offset, propname, + * propval, proplen); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen); + +/** + * fdt_node_offset_by_phandle - find the node with a given phandle + * @fdt: pointer to the device tree blob + * @phandle: phandle value + * + * fdt_node_offset_by_phandle() returns the offset of the node + * which has the given phandle value. If there is more than one node + * in the tree with the given phandle (an invalid tree), results are + * undefined. + * + * returns: + * structure block offset of the located node (>= 0), on success + * -FDT_ERR_NOTFOUND, no node with that phandle exists + * -FDT_ERR_BADPHANDLE, given phandle value was invalid (0 or -1) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle); + +/** + * fdt_node_check_compatible - check a node's compatible property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @compatible: string to match against + * + * fdt_node_check_compatible() returns 0 if the given node contains a + * @compatible property with the given string as one of its elements, + * it returns non-zero otherwise, or on error. + * + * returns: + * 0, if the node has a 'compatible' property listing the given string + * 1, if the node has a 'compatible' property, but it does not list + * the given string + * -FDT_ERR_NOTFOUND, if the given node has no 'compatible' property + * -FDT_ERR_BADOFFSET, if nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible); + +/** + * fdt_node_offset_by_compatible - find nodes with a given 'compatible' value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @compatible: 'compatible' string to match against + * + * fdt_node_offset_by_compatible() returns the offset of the first + * node after startoffset, which has a 'compatible' property which + * lists the given compatible string; or if startoffset is -1, the + * very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_compatible(fdt, -1, compatible); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_compatible(fdt, offset, compatible); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible); + +/** + * fdt_stringlist_contains - check a string list property for a string + * @strlist: Property containing a list of strings to check + * @listlen: Length of property + * @str: String to search for + * + * This is a utility function provided for convenience. The list contains + * one or more strings, each terminated by \0, as is found in a device tree + * "compatible" property. + * + * Return: 1 if the string is found in the list, 0 not found, or invalid list + */ +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str); + +/** + * fdt_stringlist_count - count the number of strings in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * + * Return: + * the number of strings in the given property + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property); + +/** + * fdt_stringlist_search - find a string in a string list and return its index + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @string: string to look up in the string list + * + * Note that it is possible for this function to succeed on property values + * that are not NUL-terminated. That's because the function will stop after + * finding the first occurrence of @string. This can for example happen with + * small-valued cell properties, such as #address-cells, when searching for + * the empty string. + * + * return: + * the index of the string in the list of strings + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist or does not contain + * the given string + */ +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string); + +/** + * fdt_stringlist_get() - obtain the string at a given index in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @index: index of the string to return + * @lenp: return location for the string length or an error code on failure + * + * Note that this will successfully extract strings from properties with + * non-NUL-terminated values. For example on small-valued cell properties + * this function will return the empty string. + * + * If non-NULL, the length of the string (on success) or a negative error-code + * (on failure) will be stored in the integer pointer to by lenp. + * + * Return: + * A pointer to the string at the given index in the string list or NULL on + * failure. On success the length of the string will be stored in the memory + * location pointed to by the lenp parameter, if non-NULL. On failure one of + * the following negative error codes will be returned in the lenp parameter + * (if non-NULL): + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int index, + int *lenp); + +/**********************************************************************/ +/* Read-only functions (addressing related) */ +/**********************************************************************/ + +/** + * FDT_MAX_NCELLS - maximum value for #address-cells and #size-cells + * + * This is the maximum value for #address-cells, #size-cells and + * similar properties that will be processed by libfdt. IEE1275 + * requires that OF implementations handle values up to 4. + * Implementations may support larger values, but in practice higher + * values aren't used. + */ +#define FDT_MAX_NCELLS 4 + +/** + * fdt_address_cells - retrieve address size for a bus represented in the tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address size for + * + * When the node has a valid #address-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 2, if the node has no #address-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #address-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_address_cells(const void *fdt, int nodeoffset); + +/** + * fdt_size_cells - retrieve address range size for a bus represented in the + * tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address range size for + * + * When the node has a valid #size-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 1, if the node has no #size-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #size-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_size_cells(const void *fdt, int nodeoffset); + + +/**********************************************************************/ +/* Write-in-place functions */ +/**********************************************************************/ + +/** + * fdt_setprop_inplace_namelen_partial - change a property's value, + * but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: number of characters of name to consider + * @idx: index of the property to change in the array + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * Identical to fdt_setprop_inplace(), but modifies the given property + * starting from the given index, and using only the first characters + * of the name. It is useful when you want to manipulate only one value of + * an array and you have a string that doesn't end with \0. + * + * Return: 0 on success, negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len); +#endif + +/** + * fdt_setprop_inplace - change a property's value, but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * fdt_setprop_inplace() replaces the value of a given property with + * the data in val, of length len. This function cannot change the + * size of a property, and so will only work if len is equal to the + * current length of the property. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if len is not equal to the property's current length + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len); +#endif + +/** + * fdt_setprop_inplace_u32 - change the value of a 32-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to replace the property with + * + * fdt_setprop_inplace_u32() replaces the value of a given property + * with the 32-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 4. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 4 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_u64 - change the value of a 64-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to replace the property with + * + * fdt_setprop_inplace_u64() replaces the value of a given property + * with the 64-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 8. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 8 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_cell - change the value of a single-cell property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node containing the property + * @name: name of the property to change the value of + * @val: new value of the 32-bit cell + * + * This is an alternative name for fdt_setprop_inplace_u32() + * Return: 0 on success, negative libfdt error number otherwise. + */ +static inline int fdt_setprop_inplace_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_setprop_inplace_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_nop_property - replace a property with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_nop_property() will replace a given property's representation + * in the blob with FDT_NOP tags, effectively removing it from the + * tree. + * + * This function will alter only the bytes in the blob which contain + * the property, and will not alter or move any other part of the + * tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_property(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_nop_node - replace a node (subtree) with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_nop_node() will replace a given node's representation in the + * blob, including all its subnodes, if any, with FDT_NOP tags, + * effectively removing it from the tree. + * + * This function will alter only the bytes in the blob which contain + * the node and its properties and subnodes, and will not alter or + * move any other part of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_node(void *fdt, int nodeoffset); + +/**********************************************************************/ +/* Sequential write functions */ +/**********************************************************************/ + +/* fdt_create_with_flags flags */ +#define FDT_CREATE_FLAG_NO_NAME_DEDUP 0x1 + /* FDT_CREATE_FLAG_NO_NAME_DEDUP: Do not try to de-duplicate property + * names in the fdt. This can result in faster creation times, but + * a larger fdt. */ + +#define FDT_CREATE_FLAGS_ALL (FDT_CREATE_FLAG_NO_NAME_DEDUP) + +/** + * fdt_create_with_flags - begin creation of a new fdt + * @buf: pointer to memory allocated where fdt will be created + * @bufsize: size of the memory space at fdt + * @flags: a valid combination of FDT_CREATE_FLAG_ flags, or 0. + * + * fdt_create_with_flags() begins the process of creating a new fdt with + * the sequential write interface. + * + * fdt creation process must end with fdt_finish() to produce a valid fdt. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt + * -FDT_ERR_BADFLAGS, flags is not valid + */ +int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags); + +/** + * fdt_create - begin creation of a new fdt + * @buf: pointer to memory allocated where fdt will be created + * @bufsize: size of the memory space at fdt + * + * fdt_create() is equivalent to fdt_create_with_flags() with flags=0. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt + */ +int fdt_create(void *buf, int bufsize); + +/** + * fdt_resize - move and resize a device tree in sequential write state + * @fdt: Pointer to the device tree to resize + * @buf: Buffer where resized tree should be placed + * @bufsize: Size of the buffer at @buf + * + * fdt_resize() moves the device tree blob from @fdt to @buf and + * resizes it to fit in the new buffer size. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if @bufsize is too small + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_resize(void *fdt, void *buf, int bufsize); + +/** + * fdt_add_reservemap_entry - add an entry to the memory reserve map + * @fdt: Pointer to the device tree blob + * @addr: Start address of the reserve map entry + * @size: Size of the reserved region + * + * fdt_add_reservemap_entry() adds a memory reserve map entry to the + * device tree blob during the sequential write process. This function + * can only be called after fdt_create() and before fdt_finish_reservemap(). + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if there is insufficient space in the blob + * -FDT_ERR_BADSTATE, if not in the correct sequential write state + */ +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size); + +/** + * fdt_finish_reservemap - complete the memory reserve map + * @fdt: Pointer to the device tree blob + * + * fdt_finish_reservemap() completes the memory reserve map section + * of the device tree blob during sequential write. After calling this + * function, no more reserve map entries can be added and the blob + * moves to the structure creation phase. + * + * returns: + * 0, on success + * -FDT_ERR_BADSTATE, if not in the correct sequential write state + */ +int fdt_finish_reservemap(void *fdt); + +/** + * fdt_begin_node - start creation of a new node + * @fdt: Pointer to the device tree blob + * @name: Name of the node to create + * + * fdt_begin_node() starts the creation of a new node with the given + * @name during sequential write. After calling this function, properties + * can be added with fdt_property() and subnodes can be created with + * additional fdt_begin_node() calls. The node must be completed with + * fdt_end_node(). + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if there is insufficient space in the blob + * -FDT_ERR_BADSTATE, if not in the correct sequential write state + */ +int fdt_begin_node(void *fdt, const char *name); + +/** + * fdt_property - add a property to the current node + * @fdt: Pointer to the device tree blob + * @name: Name of the property to add + * @val: Pointer to the property value + * @len: Length of the property value in bytes + * + * fdt_property() adds a property with the given @name and value to + * the current node during sequential write. This function can only + * be called between fdt_begin_node() and fdt_end_node(). + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if there is insufficient space in the blob + * -FDT_ERR_BADSTATE, if not currently within a node + */ +int fdt_property(void *fdt, const char *name, const void *val, int len); +static inline int fdt_property_u32(void *fdt, const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} +static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} + +#ifndef SWIG /* Not available in Python */ +static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) +{ + return fdt_property_u32(fdt, name, val); +} +#endif + +/** + * fdt_property_placeholder - add a new property and return a ptr to its value + * + * @fdt: pointer to the device tree blob + * @name: name of property to add + * @len: length of property value in bytes + * @valp: returns a pointer to where the value should be placed + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_NOSPACE, standard meanings + */ +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp); + +#define fdt_property_string(fdt, name, str) \ + fdt_property(fdt, name, str, strlen(str)+1) + +/** + * fdt_end_node - complete the current node + * @fdt: Pointer to the device tree blob + * + * fdt_end_node() completes the current node during sequential write. This + * function must be called to close each node started with + * fdt_begin_node(). After calling this function, no more properties or subnodes + * can be added to the node. + * + * returns: + * 0, on success + * -FDT_ERR_BADSTATE, if not currently within a node + */ +int fdt_end_node(void *fdt); + +/** + * fdt_finish - complete device tree creation + * @fdt: Pointer to the device tree blob + * + * fdt_finish() completes the device tree creation process started with + * fdt_create(). This function finalizes the device tree blob and makes it ready + * for use. After calling this function, the blob is complete and can be used + * with libfdt read-only and read-write functions, but not with sequential write + * functions. + * + * returns: + * 0, on success + * -FDT_ERR_BADSTATE, if the sequential write process is incomplete + */ +int fdt_finish(void *fdt); + +/**********************************************************************/ +/* Read-write functions */ +/**********************************************************************/ + +/** + * fdt_create_empty_tree - create an empty device tree + * @buf: Buffer where the empty tree should be created + * @bufsize: Size of the buffer at @buf + * + * fdt_create_empty_tree() creates a minimal empty device tree blob + * in the given buffer. The tree contains only a root node with no + * properties or subnodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if @bufsize is too small for even an empty tree + */ +int fdt_create_empty_tree(void *buf, int bufsize); + +/** + * fdt_open_into - move a device tree into a new buffer and make editable + * @fdt: Pointer to the device tree to move + * @buf: Buffer where the editable tree should be placed + * @bufsize: Size of the buffer at @buf + * + * fdt_open_into() moves and reorganizes the device tree blob from @fdt + * into @buf, converting it to a format suitable for read-write operations. + * The new buffer should allow space for modifications. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if @bufsize is too small + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_open_into(const void *fdt, void *buf, int bufsize); + +/** + * fdt_pack - pack a device tree blob + * @fdt: Pointer to the device tree blob + * + * fdt_pack() reorganizes the device tree blob to eliminate any free space + * and pack it into the minimum possible size. This is useful after making + * modifications that might have left gaps in the blob. + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, standard meanings + */ +int fdt_pack(void *fdt); + +/** + * fdt_add_mem_rsv - add one memory reserve map entry + * @fdt: pointer to the device tree blob + * @address: 64-bit start address of the reserve map entry + * @size: 64-bit size of the reserved region + * + * Adds a reserve map entry to the given blob reserving a region at + * address address of length size. + * + * This function will insert data into the reserve map and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new reservation entry + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size); + +/** + * fdt_del_mem_rsv - remove a memory reserve map entry + * @fdt: pointer to the device tree blob + * @n: entry to remove + * + * fdt_del_mem_rsv() removes the n-th memory reserve map entry from + * the blob. + * + * This function will delete data from the reservation table and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, there is no entry of the given index (i.e. there + * are less than n+1 reserve map entries) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_mem_rsv(void *fdt, int n); + +/** + * fdt_set_name - change the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * @name: name to give the node + * + * fdt_set_name() replaces the name (including unit address, if any) + * of the given node with the given string. NOTE: this function can't + * efficiently check if the new name is unique amongst the given + * node's siblings; results are undefined if this function is invoked + * with a name equal to one of the given node's siblings. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob + * to contain the new name + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_set_name(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_setprop_namelen - create or change a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: length of the name + * @val: pointer to data to set the property value to + * @len: length of the property value + * + * fdt_setprop_namelen() sets the value of the named property in the given + * node to the given value and length, creating the property if it + * does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, const void *val, int len); + +/** + * fdt_setprop - create or change a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to set the property value to + * @len: length of the property value + * + * fdt_setprop() sets the value of the named property in the given + * node to the given value and length, creating the property if it + * does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + return fdt_setprop_namelen(fdt, nodeoffset, name, strlen(name), val, + len); +} + +/** + * fdt_setprop_placeholder_namelen - allocate space for a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: length of the name + * @len: length of the property value + * @prop_data: return pointer to property data + * + * fdt_setprop_placeholder_namelen() allocates the named property in the given node. + * If the property exists it is resized. In either case a pointer to the + * property data is returned. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, int len, void **prop_data); + +/** + * fdt_setprop_placeholder - allocate space for a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @len: length of the property value + * @prop_data: return pointer to property data + * + * fdt_setprop_placeholder() allocates the named property in the given node. + * If the property exists it is resized. In either case a pointer to the + * property data is returned. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_placeholder(void *fdt, int nodeoffset, + const char *name, int len, + void **prop_data) +{ + return fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, + strlen(name), len, prop_data); +} + +/** + * fdt_setprop_u32 - set a property to a 32-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value for the property (native endian) + * + * fdt_setprop_u32() sets the value of the named property in the given + * node to the given 32-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u32(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_u64 - set a property to a 64-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value for the property (native endian) + * + * fdt_setprop_u64() sets the value of the named property in the given + * node to the given 64-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u64(void *fdt, int nodeoffset, const char *name, + uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_cell - set a property to a single cell value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value for the property (native endian) + * + * This is an alternative name for fdt_setprop_u32() + * + * Return: 0 on success, negative libfdt error value otherwise. + */ +static inline int fdt_setprop_cell(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + return fdt_setprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_setprop_string - set a property to a string value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value for the property + * + * fdt_setprop_string() sets the value of the named property in the + * given node to the given string value (using the length of the + * string to determine the new length of the property), or creates a + * new property with that value if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_string(fdt, nodeoffset, name, str) \ + fdt_setprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + +/** + * fdt_setprop_namelen_string - set a property to a string value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: number of characters of name to consider + * @str: string value for the property + * + * fdt_setprop_namelen_string() sets the value of the named property in the + * given node to the given string value (using the length of the + * string to determine the new length of the property), or creates a + * new property with that value if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_namelen_string(fdt, nodeoffset, name, namelen, str) \ + fdt_setprop_namelen((fdt), (nodeoffset), (name), (namelen), (str), \ + strlen(str) + 1) + +/** + * fdt_setprop_empty - set a property to an empty value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * + * fdt_setprop_empty() sets the value of the named property in the + * given node to an empty (zero length) value, or creates a new empty + * property if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_empty(fdt, nodeoffset, name) \ + fdt_setprop((fdt), (nodeoffset), (name), NULL, 0) + +/** + * fdt_appendprop - append to or create a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to append to + * @val: pointer to data to append to the property value + * @len: length of the data to append to the property value + * + * fdt_appendprop() appends the value to the named property in the + * given node, creating the property if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len); + +/** + * fdt_appendprop_u32 - append a 32-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u32() appends the given 32-bit integer value + * (converting to big-endian if necessary) 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. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_u64 - append a 64-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u64() appends the given 64-bit integer value + * (converting to big-endian if necessary) 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. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_cell - append a single cell value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to append to the property (native endian) + * + * This is an alternative name for fdt_appendprop_u32() + * + * Return: 0 on success, negative libfdt error value otherwise. + */ +static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_appendprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_appendprop_string - append a string to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value to append to the property + * + * fdt_appendprop_string() appends the given string 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. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_appendprop_string(fdt, nodeoffset, name, str) \ + fdt_appendprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + +/** + * fdt_appendprop_addrrange - append a address range property + * @fdt: pointer to the device tree blob + * @parent: offset of the parent node + * @nodeoffset: offset of the node to add a property at + * @name: name of property + * @addr: start address of a given range + * @size: size of a given range + * + * fdt_appendprop_addrrange() appends an address range value (start + * 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. + * + * Cell sizes are determined by parent's #address-cells and #size-cells. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #address-cells property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADVALUE, addr or size doesn't fit to respective cells size + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain a new property + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, + const char *name, uint64_t addr, uint64_t size); + +/** + * fdt_delprop - delete a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_delprop() will delete the given property. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_delprop(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_add_subnode_namelen - creates a new node based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to create + * @namelen: number of characters of name to consider + * + * Identical to fdt_add_subnode(), but use only the first @namelen + * characters of @name as the name of the new node. This is useful for + * creating subnodes based on a portion of a larger string, such as a + * full path. + * + * Return: structure block offset of the created subnode (>=0), + * negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen); +#endif + +/** + * fdt_add_subnode - creates a new node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_add_subnode() creates a new node as a subnode of the node at + * structure block offset parentoffset, with the given name (which + * should include the unit address, if any). + * + * This function will insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * structure block offset of the created subnode (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_EXISTS, if the node at parentoffset already has a subnode of + * the given name + * -FDT_ERR_NOSPACE, if there is insufficient free space in the + * blob to contain the new node + * -FDT_ERR_NOSPACE + * -FDT_ERR_BADLAYOUT + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_add_subnode(void *fdt, int parentoffset, const char *name); + +/** + * fdt_del_node - delete a node (subtree) + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_del_node() will remove the given node, including all its + * subnodes if any, from the blob. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_node(void *fdt, int nodeoffset); + +/** + * fdt_overlay_apply - Applies a DT overlay on a base DT + * @fdt: pointer to the base device tree blob + * @fdto: pointer to the device tree overlay blob + * + * fdt_overlay_apply() will apply the given device tree overlay on the + * given base device tree. + * + * Expect the base device tree to be modified, even if the function + * returns an error. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there's not enough space in the base device tree + * -FDT_ERR_NOTFOUND, the overlay points to some nonexistent nodes or + * properties in the base DT + * -FDT_ERR_BADPHANDLE, + * -FDT_ERR_BADOVERLAY, + * -FDT_ERR_NOPHANDLES, + * -FDT_ERR_INTERNAL, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADOFFSET, + * -FDT_ERR_BADPATH, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_overlay_apply(void *fdt, void *fdto); + +/** + * fdt_overlay_target_offset - retrieves the offset of a fragment's target + * @fdt: Base device tree blob + * @fdto: Device tree overlay blob + * @fragment_offset: node offset of the fragment in the overlay + * @pathp: pointer which receives the path of the target (or NULL) + * + * fdt_overlay_target_offset() retrieves the target offset in the base + * device tree of a fragment, no matter how the actual targeting is + * done (through a phandle or a path) + * + * returns: + * the targeted node offset in the base device tree + * Negative error code on error + */ +int fdt_overlay_target_offset(const void *fdt, const void *fdto, + int fragment_offset, char const **pathp); + +/**********************************************************************/ +/* Debugging / informational functions */ +/**********************************************************************/ + +/** + * fdt_strerror - return string description of error code + * @errval: Error code returned by a libfdt function + * + * fdt_strerror() returns a string description of the error code passed + * in @errval. + * + * returns: + * pointer to a string describing the error code + */ +const char *fdt_strerror(int errval); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBFDT_H */ diff --git a/scripts/dtc/libfdt/libfdt_env.h b/scripts/dtc/libfdt/libfdt_env.h new file mode 100644 index 000000000..5580b483e --- /dev/null +++ b/scripts/dtc/libfdt/libfdt_env.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_ENV_H +#define LIBFDT_ENV_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + */ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#ifdef __CHECKER__ +#define FDT_FORCE __attribute__((force)) +#define FDT_BITWISE __attribute__((bitwise)) +#else +#define FDT_FORCE +#define FDT_BITWISE +#endif + +typedef uint16_t FDT_BITWISE fdt16_t; +typedef uint32_t FDT_BITWISE fdt32_t; +typedef uint64_t FDT_BITWISE fdt64_t; + +#define EXTRACT_BYTE(x, n) ((unsigned long long)((uint8_t *)&x)[n]) +#define CPU_TO_FDT16(x) ((EXTRACT_BYTE(x, 0) << 8) | EXTRACT_BYTE(x, 1)) +#define CPU_TO_FDT32(x) ((EXTRACT_BYTE(x, 0) << 24) | (EXTRACT_BYTE(x, 1) << 16) | \ + (EXTRACT_BYTE(x, 2) << 8) | EXTRACT_BYTE(x, 3)) +#define CPU_TO_FDT64(x) ((EXTRACT_BYTE(x, 0) << 56) | (EXTRACT_BYTE(x, 1) << 48) | \ + (EXTRACT_BYTE(x, 2) << 40) | (EXTRACT_BYTE(x, 3) << 32) | \ + (EXTRACT_BYTE(x, 4) << 24) | (EXTRACT_BYTE(x, 5) << 16) | \ + (EXTRACT_BYTE(x, 6) << 8) | EXTRACT_BYTE(x, 7)) + +static inline uint16_t fdt16_to_cpu(fdt16_t x) +{ + return (FDT_FORCE uint16_t)CPU_TO_FDT16(x); +} +static inline fdt16_t cpu_to_fdt16(uint16_t x) +{ + return (FDT_FORCE fdt16_t)CPU_TO_FDT16(x); +} + +static inline uint32_t fdt32_to_cpu(fdt32_t x) +{ + return (FDT_FORCE uint32_t)CPU_TO_FDT32(x); +} +static inline fdt32_t cpu_to_fdt32(uint32_t x) +{ + return (FDT_FORCE fdt32_t)CPU_TO_FDT32(x); +} + +static inline uint64_t fdt64_to_cpu(fdt64_t x) +{ + return (FDT_FORCE uint64_t)CPU_TO_FDT64(x); +} +static inline fdt64_t cpu_to_fdt64(uint64_t x) +{ + return (FDT_FORCE fdt64_t)CPU_TO_FDT64(x); +} +#undef CPU_TO_FDT64 +#undef CPU_TO_FDT32 +#undef CPU_TO_FDT16 +#undef EXTRACT_BYTE + +#endif /* LIBFDT_ENV_H */ diff --git a/scripts/dtc/libfdt/libfdt_internal.h b/scripts/dtc/libfdt/libfdt_internal.h new file mode 100644 index 000000000..0e103cafa --- /dev/null +++ b/scripts/dtc/libfdt/libfdt_internal.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_INTERNAL_H +#define LIBFDT_INTERNAL_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include <fdt.h> + +#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) +#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) + +int32_t fdt_ro_probe_(const void *fdt); +#define FDT_RO_PROBE(fdt) \ + { \ + if (!can_assume(VALID_DTB)) { \ + int32_t totalsize_; \ + if ((totalsize_ = fdt_ro_probe_(fdt)) < 0) \ + return totalsize_; \ + } \ + } + +int fdt_check_node_offset_(const void *fdt, int offset); +int fdt_check_prop_offset_(const void *fdt, int offset); + +const char *fdt_find_string_len_(const char *strtab, int tabsize, const char *s, + int s_len); +static inline const char *fdt_find_string_(const char *strtab, int tabsize, + const char *s) +{ + return fdt_find_string_len_(strtab, tabsize, s, strlen(s)); +} + +int fdt_node_end_offset_(void *fdt, int nodeoffset); + +static inline const void *fdt_offset_ptr_(const void *fdt, int offset) +{ + return (const char *)fdt + fdt_off_dt_struct(fdt) + offset; +} + +static inline void *fdt_offset_ptr_w_(void *fdt, int offset) +{ + return (void *)(uintptr_t)fdt_offset_ptr_(fdt, offset); +} + +static inline const struct fdt_reserve_entry *fdt_mem_rsv_(const void *fdt, int n) +{ + const struct fdt_reserve_entry *rsv_table = + (const struct fdt_reserve_entry *) + ((const char *)fdt + fdt_off_mem_rsvmap(fdt)); + + return rsv_table + n; +} +static inline struct fdt_reserve_entry *fdt_mem_rsv_w_(void *fdt, int n) +{ + return (void *)(uintptr_t)fdt_mem_rsv_(fdt, n); +} + +/* + * Internal helpers to access structural elements of the device tree + * blob (rather than for example reading integers from within property + * values). We assume that we are either given a naturally aligned + * address for the platform or if we are not, we are on a platform + * where unaligned memory reads will be handled in a graceful manner. + * If not the external helpers fdtXX_ld() from libfdt.h can be used + * instead. + */ +static inline uint32_t fdt32_ld_(const fdt32_t *p) +{ + return fdt32_to_cpu(*p); +} + +static inline uint64_t fdt64_ld_(const fdt64_t *p) +{ + return fdt64_to_cpu(*p); +} + +#define FDT_SW_MAGIC (~FDT_MAGIC) + +/**********************************************************************/ +/* Checking controls */ +/**********************************************************************/ + +#ifndef FDT_ASSUME_MASK +#define FDT_ASSUME_MASK 0 +#endif + +/* + * Defines assumptions which can be enabled. Each of these can be enabled + * individually. For maximum safety, don't enable any assumptions! + * + * For minimal code size and no safety, use ASSUME_PERFECT at your own risk. + * You should have another method of validating the device tree, such as a + * signature or hash check before using libfdt. + * + * For situations where security is not a concern it may be safe to enable + * ASSUME_PERFECT. + */ +enum { + /* + * This does essentially no checks. Only the latest device-tree + * version is correctly handled. Inconsistencies or errors in the device + * tree may cause undefined behaviour or crashes. Invalid parameters + * passed to libfdt may do the same. + * + * If an error occurs when modifying the tree it may leave the tree in + * an intermediate (but valid) state. As an example, adding a property + * where there is insufficient space may result in the property name + * being added to the string table even though the property itself is + * not added to the struct section. + * + * Only use this if you have a fully validated device tree with + * the latest supported version and wish to minimise code size. + */ + ASSUME_PERFECT = 0xff, + + /* + * This assumes that the device tree is sane. i.e. header metadata + * and basic hierarchy are correct. + * + * With this assumption enabled, normal device trees produced by libfdt + * and the compiler should be handled safely. Malicious device trees and + * complete garbage may cause libfdt to behave badly or crash. Truncated + * device trees (e.g. those only partially loaded) can also cause + * problems. + * + * Note: Only checks that relate exclusively to the device tree itself + * (not the parameters passed to libfdt) are disabled by this + * assumption. This includes checking headers, tags and the like. + */ + ASSUME_VALID_DTB = 1 << 0, + + /* + * This builds on ASSUME_VALID_DTB and further assumes that libfdt + * functions are called with valid parameters, i.e. not trigger + * FDT_ERR_BADOFFSET or offsets that are out of bounds. It disables any + * extensive checking of parameters and the device tree, making various + * assumptions about correctness. + * + * It doesn't make sense to enable this assumption unless + * ASSUME_VALID_DTB is also enabled. + */ + ASSUME_VALID_INPUT = 1 << 1, + + /* + * This disables checks for device-tree version and removes all code + * which handles older versions. + * + * Only enable this if you know you have a device tree with the latest + * version. + */ + ASSUME_LATEST = 1 << 2, + + /* + * This assumes that it is OK for a failed addition to the device tree, + * due to lack of space or some other problem, to skip any rollback + * steps (such as dropping the property name from the string table). + * This is safe to enable in most circumstances, even though it may + * leave the tree in a sub-optimal state. + */ + ASSUME_NO_ROLLBACK = 1 << 3, + + /* + * This assumes that the device tree components appear in a 'convenient' + * order, i.e. the memory reservation block first, then the structure + * block and finally the string block. + * + * This order is not specified by the device-tree specification, + * but is expected by libfdt. The device-tree compiler always created + * device trees with this order. + * + * This assumption disables a check in fdt_open_into() and removes the + * ability to fix the problem there. This is safe if you know that the + * device tree is correctly ordered. See fdt_blocks_misordered_(). + */ + ASSUME_LIBFDT_ORDER = 1 << 4, + + /* + * This assumes that libfdt itself does not have any internal bugs. It + * drops certain checks that should never be needed unless libfdt has an + * undiscovered bug. + * + * This can generally be considered safe to enable. + */ + ASSUME_LIBFDT_FLAWLESS = 1 << 5, +}; + +/** + * can_assume_() - check if a particular assumption is enabled + * + * @mask: Mask to check (ASSUME_...) + * @return true if that assumption is enabled, else false + */ +static inline bool can_assume_(int mask) +{ + return FDT_ASSUME_MASK & mask; +} + +/** helper macros for checking assumptions */ +#define can_assume(_assume) can_assume_(ASSUME_ ## _assume) + +#endif /* LIBFDT_INTERNAL_H */ diff --git a/scripts/dtc/livetree.c b/scripts/dtc/livetree.c new file mode 100644 index 000000000..5d72abceb --- /dev/null +++ b/scripts/dtc/livetree.c @@ -0,0 +1,1315 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include "dtc.h" +#include "srcpos.h" + +/* + * Tree building functions + */ + +void add_label(struct label **labels, char *label) +{ + struct label *new; + + /* Make sure the label isn't already there */ + for_each_label_withdel(*labels, new) + if (streq(new->label, label)) { + new->deleted = 0; + return; + } + + new = xmalloc(sizeof(*new)); + memset(new, 0, sizeof(*new)); + new->label = label; + new->next = *labels; + *labels = new; +} + +void delete_labels(struct label **labels) +{ + struct label *label; + + for_each_label(*labels, label) + label->deleted = 1; +} + +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 = xstrdup(name); + new->val = val; + new->srcpos = srcpos_copy(srcpos); + + return new; +} + +struct property *build_property_delete(const char *name) +{ + struct property *new = xmalloc(sizeof(*new)); + + memset(new, 0, sizeof(*new)); + + new->name = xstrdup(name); + new->deleted = 1; + + return new; +} + +struct property *chain_property(struct property *first, struct property *list) +{ + assert(first->next == NULL); + + first->next = list; + return first; +} + +struct property *reverse_properties(struct property *first) +{ + struct property *p = first; + struct property *head = NULL; + struct property *next; + + while (p) { + next = p->next; + p->next = head; + head = p; + p = next; + } + return head; +} + +struct node *build_node(struct property *proplist, struct node *children, + struct srcpos *srcpos) +{ + struct node *new = xmalloc(sizeof(*new)); + struct node *child; + + memset(new, 0, sizeof(*new)); + + new->proplist = reverse_properties(proplist); + new->children = children; + new->srcpos = srcpos_copy(srcpos); + + for_each_child(new, child) { + child->parent = new; + } + + return new; +} + +struct node *build_node_delete(struct srcpos *srcpos) +{ + struct node *new = xmalloc(sizeof(*new)); + + memset(new, 0, sizeof(*new)); + + new->deleted = 1; + new->srcpos = srcpos_copy(srcpos); + + return new; +} + +struct node *name_node(struct node *node, const char *name) +{ + assert(node->name == NULL); + + node->name = xstrdup(name); + + return node; +} + +struct node *omit_node_if_unused(struct node *node) +{ + node->omit_if_unused = 1; + + return node; +} + +struct node *reference_node(struct node *node) +{ + node->is_referenced = 1; + + return node; +} + +struct node *merge_nodes(struct node *old_node, struct node *new_node) +{ + struct property *new_prop, *old_prop; + struct node *new_child, *old_child; + struct label *l; + + old_node->deleted = 0; + + /* Add new node labels to old node */ + for_each_label_withdel(new_node->labels, l) + add_label(&old_node->labels, l->label); + + /* Move properties from the new node to the old node. If there + * is a collision, replace the old value with the new */ + while (new_node->proplist) { + /* Pop the property off the list */ + new_prop = new_node->proplist; + new_node->proplist = new_prop->next; + new_prop->next = NULL; + + if (new_prop->deleted) { + delete_property_by_name(old_node, new_prop->name); + free(new_prop); + continue; + } + + /* Look for a collision, set new value if there is */ + for_each_property_withdel(old_node, old_prop) { + if (streq(old_prop->name, new_prop->name)) { + /* Add new labels to old property */ + for_each_label_withdel(new_prop->labels, l) + add_label(&old_prop->labels, l->label); + + old_prop->val = new_prop->val; + old_prop->deleted = 0; + srcpos_free(old_prop->srcpos); + old_prop->srcpos = new_prop->srcpos; + free(new_prop); + new_prop = NULL; + break; + } + } + + /* if no collision occurred, add property to the old node. */ + if (new_prop) + add_property(old_node, new_prop); + } + + /* Move the override child nodes into the primary node. If + * there is a collision, then merge the nodes. */ + while (new_node->children) { + /* Pop the child node off the list */ + new_child = new_node->children; + new_node->children = new_child->next_sibling; + new_child->parent = NULL; + new_child->next_sibling = NULL; + + if (new_child->deleted) { + delete_node_by_name(old_node, new_child->name); + free(new_child); + continue; + } + + /* Search for a collision. Merge if there is */ + for_each_child_withdel(old_node, old_child) { + if (streq(old_child->name, new_child->name)) { + merge_nodes(old_child, new_child); + new_child = NULL; + break; + } + } + + /* if no collision occurred, add child to the old node. */ + if (new_child) + add_child(old_node, new_child); + } + + old_node->srcpos = srcpos_extend(old_node->srcpos, new_node->srcpos); + + /* The new node contents are now merged into the old node. Free + * the new node. */ + free(new_node); + + return old_node; +} + +struct node * add_orphan_node(struct node *dt, struct node *new_node, char *ref) +{ + static unsigned int next_orphan_fragment = 0; + struct node *node; + struct property *p; + struct data d = empty_data; + char *name; + + if (ref[0] == '/') { + d = data_add_marker(d, TYPE_STRING, ref); + d = data_append_data(d, ref, strlen(ref) + 1); + + p = build_property("target-path", d, NULL); + } else { + d = data_add_marker(d, REF_PHANDLE, ref); + d = data_append_integer(d, 0xffffffff, 32); + + p = build_property("target", d, NULL); + } + + xasprintf(&name, "fragment@%u", + next_orphan_fragment++); + name_node(new_node, "__overlay__"); + node = build_node(p, new_node, NULL); + name_node(node, name); + free(name); + + add_child(dt, node); + return dt; +} + +struct node *chain_node(struct node *first, struct node *list) +{ + assert(first->next_sibling == NULL); + + first->next_sibling = list; + return first; +} + +void add_property(struct node *node, struct property *prop) +{ + struct property **p; + + prop->next = NULL; + + p = &node->proplist; + while (*p) + p = &((*p)->next); + + *p = prop; +} + +void delete_property_by_name(struct node *node, char *name) +{ + struct property *prop = node->proplist; + + while (prop) { + if (streq(prop->name, name)) { + delete_property(prop); + return; + } + prop = prop->next; + } +} + +void delete_property(struct property *prop) +{ + prop->deleted = 1; + delete_labels(&prop->labels); +} + +void add_child(struct node *parent, struct node *child) +{ + struct node **p; + + child->next_sibling = NULL; + child->parent = parent; + + p = &parent->children; + while (*p) + p = &((*p)->next_sibling); + + *p = child; +} + +void delete_node_by_name(struct node *parent, char *name) +{ + struct node *node = parent->children; + + while (node) { + if (streq(node->name, name)) { + delete_node(node); + return; + } + node = node->next_sibling; + } +} + +void delete_node(struct node *node) +{ + struct property *prop; + struct node *child; + + node->deleted = 1; + for_each_child(node, child) + delete_node(child); + for_each_property(node, prop) + delete_property(prop); + delete_labels(&node->labels); +} + +void append_to_property(struct node *node, + char *name, const void *data, int len, + enum markertype type) +{ + struct property *p; + + p = get_property(node, name); + if (!p) { + p = build_property(name, empty_data, NULL); + add_property(node, p); + } + + p->val = data_add_marker(p->val, type, name); + p->val = data_append_data(p->val, data, len); +} + +static int append_unique_str_to_property(struct node *node, + char *name, const char *data, int len) +{ + struct property *p; + + p = get_property(node, name); + if (p) { + const char *s; + + if (p->val.len && p->val.val[p->val.len - 1] != '\0') + /* The current content doesn't look like a string */ + return -1; + + for (s = p->val.val; s < p->val.val + p->val.len; s = strchr(s, '\0') + 1) { + if (strcmp(data, s) == 0) + /* data already contained in node.name */ + return 0; + } + } else { + p = build_property(name, empty_data, NULL); + add_property(node, p); + } + + p->val = data_add_marker(p->val, TYPE_STRING, name); + p->val = data_append_data(p->val, data, len); + + return 0; +} + +static int append_unique_u32_to_property(struct node *node, char *name, fdt32_t value) +{ + struct property *p; + + p = get_property(node, name); + if (p) { + const fdt32_t *v, *val_end = (const fdt32_t *)p->val.val + p->val.len / 4; + + if (p->val.len % 4 != 0) + /* The current content doesn't look like a u32 array */ + return -1; + + for (v = (const void *)p->val.val; v < val_end; v++) { + if (*v == value) + /* value already contained */ + return 0; + } + } else { + p = build_property(name, empty_data, NULL); + add_property(node, p); + } + + p->val = data_add_marker(p->val, TYPE_UINT32, name); + p->val = data_append_data(p->val, &value, 4); + + return 0; +} + +struct reserve_info *build_reserve_entry(uint64_t address, uint64_t size) +{ + struct reserve_info *new = xmalloc(sizeof(*new)); + + memset(new, 0, sizeof(*new)); + + new->address = address; + new->size = size; + + return new; +} + +struct reserve_info *chain_reserve_entry(struct reserve_info *first, + struct reserve_info *list) +{ + assert(first->next == NULL); + + first->next = list; + return first; +} + +struct reserve_info *add_reserve_entry(struct reserve_info *list, + struct reserve_info *new) +{ + struct reserve_info *last; + + new->next = NULL; + + if (! list) + return new; + + for (last = list; last->next; last = last->next) + ; + + last->next = new; + + return list; +} + +struct dt_info *build_dt_info(unsigned int dtsflags, + struct reserve_info *reservelist, + struct node *tree, uint32_t boot_cpuid_phys) +{ + struct dt_info *dti; + + dti = xmalloc(sizeof(*dti)); + dti->dtsflags = dtsflags; + dti->reservelist = reservelist; + dti->dt = tree; + dti->boot_cpuid_phys = boot_cpuid_phys; + + return dti; +} + +/* + * Tree accessor functions + */ + +const char *get_unitname(struct node *node) +{ + if (node->name[node->basenamelen] == '\0') + return ""; + else + return node->name + node->basenamelen + 1; +} + +struct property *get_property(struct node *node, const char *propname) +{ + struct property *prop; + + for_each_property(node, prop) + if (streq(prop->name, propname)) + return prop; + + return NULL; +} + +cell_t propval_cell(struct property *prop) +{ + assert(prop->val.len == sizeof(cell_t)); + return fdt32_to_cpu(*((fdt32_t *)prop->val.val)); +} + +cell_t propval_cell_n(struct property *prop, unsigned int n) +{ + assert(prop->val.len / sizeof(cell_t) > n); + return fdt32_to_cpu(*((fdt32_t *)prop->val.val + n)); +} + +struct property *get_property_by_label(struct node *tree, const char *label, + struct node **node) +{ + struct property *prop; + struct node *c; + + *node = tree; + + for_each_property(tree, prop) { + struct label *l; + + for_each_label(prop->labels, l) + if (streq(l->label, label)) + return prop; + } + + for_each_child(tree, c) { + prop = get_property_by_label(c, label, node); + if (prop) + return prop; + } + + *node = NULL; + return NULL; +} + +struct marker *get_marker_label(struct node *tree, const char *label, + struct node **node, struct property **prop) +{ + struct marker *m; + struct property *p; + struct node *c; + + *node = tree; + + for_each_property(tree, p) { + *prop = p; + m = p->val.markers; + for_each_marker_of_type(m, LABEL) + if (streq(m->ref, label)) + return m; + } + + for_each_child(tree, c) { + m = get_marker_label(c, label, node, prop); + if (m) + return m; + } + + *prop = NULL; + *node = NULL; + return NULL; +} + +struct node *get_subnode(struct node *node, const char *nodename) +{ + struct node *child; + + for_each_child(node, child) + if (streq(child->name, nodename) && !child->deleted) + return child; + + return NULL; +} + +struct node *get_node_by_path(struct node *tree, const char *path) +{ + const char *p; + struct node *child; + + if (!path || ! (*path)) { + if (tree->deleted) + return NULL; + return tree; + } + + while (path[0] == '/') + path++; + + p = strchr(path, '/'); + + for_each_child(tree, child) { + if (p && strprefixeq(path, (size_t)(p - path), child->name)) + return get_node_by_path(child, p+1); + else if (!p && streq(path, child->name)) + return child; + } + + return NULL; +} + +struct node *get_node_by_label(struct node *tree, const char *label) +{ + struct node *child, *node; + struct label *l; + + assert(label && (strlen(label) > 0)); + + for_each_label(tree->labels, l) + if (streq(l->label, label)) + return tree; + + for_each_child(tree, child) { + node = get_node_by_label(child, label); + if (node) + return node; + } + + return NULL; +} + +struct node *get_node_by_phandle(struct node *tree, cell_t phandle) +{ + struct node *child, *node; + + if (!phandle_is_valid(phandle)) { + assert(generate_fixups); + return NULL; + } + + if (tree->phandle == phandle) { + if (tree->deleted) + return NULL; + return tree; + } + + for_each_child(tree, child) { + node = get_node_by_phandle(child, phandle); + if (node) + return node; + } + + return NULL; +} + +struct node *get_node_by_ref(struct node *tree, const char *ref) +{ + struct node *target = tree; + const char *label = NULL, *path = NULL; + + if (streq(ref, "/")) + return tree; + + if (ref[0] == '/') + path = ref; + else + label = ref; + + if (label) { + const char *slash = strchr(label, '/'); + char *buf = NULL; + + if (slash) { + buf = xstrndup(label, slash - label); + label = buf; + path = slash + 1; + } + + target = get_node_by_label(tree, label); + + free(buf); + + if (!target) + return NULL; + } + + if (path) + target = get_node_by_path(target, path); + + 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 */ + + if (phandle_is_valid(node->phandle)) + return node->phandle; + + while (get_node_by_phandle(root, phandle)) + phandle++; + + node->phandle = phandle; + + 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 + * fixed up momentarily in the caller */ + + return node->phandle; +} + +uint32_t guess_boot_cpuid(struct node *tree) +{ + struct node *cpus, *bootcpu; + struct property *reg; + + cpus = get_node_by_path(tree, "/cpus"); + if (!cpus) + return 0; + + + bootcpu = cpus->children; + if (!bootcpu) + return 0; + + reg = get_property(bootcpu, "reg"); + if (!reg || (reg->val.len != sizeof(uint32_t))) + return 0; + + /* FIXME: Sanity check node? */ + + return propval_cell(reg); +} + +static int cmp_reserve_info(const void *ax, const void *bx) +{ + const struct reserve_info *a, *b; + + a = *((const struct reserve_info * const *)ax); + b = *((const struct reserve_info * const *)bx); + + if (a->address < b->address) + return -1; + else if (a->address > b->address) + return 1; + else if (a->size < b->size) + return -1; + else if (a->size > b->size) + return 1; + else + return 0; +} + +static void sort_reserve_entries(struct dt_info *dti) +{ + struct reserve_info *ri, **tbl; + int n = 0, i = 0; + + for (ri = dti->reservelist; + ri; + ri = ri->next) + n++; + + if (n == 0) + return; + + tbl = xmalloc(n * sizeof(*tbl)); + + for (ri = dti->reservelist; + ri; + ri = ri->next) + tbl[i++] = ri; + + qsort(tbl, n, sizeof(*tbl), cmp_reserve_info); + + dti->reservelist = tbl[0]; + for (i = 0; i < (n-1); i++) + tbl[i]->next = tbl[i+1]; + tbl[n-1]->next = NULL; + + free(tbl); +} + +static int cmp_prop(const void *ax, const void *bx) +{ + const struct property *a, *b; + + a = *((const struct property * const *)ax); + b = *((const struct property * const *)bx); + + return strcmp(a->name, b->name); +} + +static void sort_properties(struct node *node) +{ + int n = 0, i = 0; + struct property *prop, **tbl; + + for_each_property_withdel(node, prop) + n++; + + if (n == 0) + return; + + tbl = xmalloc(n * sizeof(*tbl)); + + for_each_property_withdel(node, prop) + tbl[i++] = prop; + + qsort(tbl, n, sizeof(*tbl), cmp_prop); + + node->proplist = tbl[0]; + for (i = 0; i < (n-1); i++) + tbl[i]->next = tbl[i+1]; + tbl[n-1]->next = NULL; + + free(tbl); +} + +static int cmp_subnode(const void *ax, const void *bx) +{ + const struct node *a, *b; + + a = *((const struct node * const *)ax); + b = *((const struct node * const *)bx); + + return strcmp(a->name, b->name); +} + +static void sort_subnodes(struct node *node) +{ + int n = 0, i = 0; + struct node *subnode, **tbl; + + for_each_child_withdel(node, subnode) + n++; + + if (n == 0) + return; + + tbl = xmalloc(n * sizeof(*tbl)); + + for_each_child_withdel(node, subnode) + tbl[i++] = subnode; + + qsort(tbl, n, sizeof(*tbl), cmp_subnode); + + node->children = tbl[0]; + for (i = 0; i < (n-1); i++) + tbl[i]->next_sibling = tbl[i+1]; + tbl[n-1]->next_sibling = NULL; + + free(tbl); +} + +static void sort_node(struct node *node) +{ + struct node *c; + + sort_properties(node); + sort_subnodes(node); + for_each_child_withdel(node, c) + sort_node(c); +} + +void sort_tree(struct dt_info *dti) +{ + sort_reserve_entries(dti); + sort_node(dti->dt); +} + +/* utility helper to avoid code duplication */ +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, name); + add_child(parent, node); + + return node; +} + +static struct node *build_root_node(struct node *dt, const char *name) +{ + struct node *an; + + an = get_subnode(dt, name); + if (!an) + an = build_and_name_child_node(dt, name); + + if (!an) + die("Could not build root node /%s\n", name); + + return an; +} + +static bool any_label_tree(struct dt_info *dti, struct node *node) +{ + struct node *c; + + if (node->labels) + return true; + + for_each_child(node, c) + if (any_label_tree(dti, c)) + return true; + + return false; +} + +static void generate_label_tree_internal(struct dt_info *dti, + struct node *an, struct node *node, + bool allocph) +{ + struct node *dt = dti->dt; + struct node *c; + struct property *p; + struct label *l; + + /* if there are labels */ + if (node->labels) { + + /* now add the label in the node */ + for_each_label(node->labels, l) { + + /* check whether the label already exists */ + p = get_property(an, l->label); + if (p) { + fprintf(stderr, "WARNING: label %s already" + " exists in /%s", l->label, + an->name); + continue; + } + + /* insert it */ + p = build_property(l->label, + data_copy_escape_string(node->fullpath, + strlen(node->fullpath)), + NULL); + add_property(an, p); + } + + /* force allocation of a phandle for this node */ + if (allocph) + (void)get_node_phandle(dt, node); + } + + for_each_child(node, c) + generate_label_tree_internal(dti, an, c, allocph); +} + +static bool any_fixup_tree(struct dt_info *dti, struct node *node) +{ + struct node *c; + struct property *prop; + struct marker *m; + + for_each_property(node, prop) { + m = prop->val.markers; + for_each_marker_of_type(m, REF_PHANDLE) { + if (!get_node_by_ref(dti->dt, m->ref)) + return true; + } + } + + for_each_child(node, c) { + if (any_fixup_tree(dti, c)) + return true; + } + + return false; +} + +static int add_fixup_entry(struct dt_info *dti, struct node *fn, + struct node *node, struct property *prop, + struct marker *m) +{ + char *entry; + int ret; + + /* m->ref can only be a REF_PHANDLE, but check anyway */ + assert(m->type == REF_PHANDLE); + + /* The format only permits fixups for references to label, not + * references to path */ + if (strchr(m->ref, '/')) + die("Can't generate fixup for reference to path &{%s}\n", + m->ref); + + /* there shouldn't be any ':' in the arguments */ + if (strchr(node->fullpath, ':') || strchr(prop->name, ':')) + die("arguments should not contain ':'\n"); + + xasprintf(&entry, "%s:%s:%u", + node->fullpath, prop->name, m->offset); + ret = append_unique_str_to_property(fn, m->ref, entry, strlen(entry) + 1); + + free(entry); + + return ret; +} + +static int generate_fixups_tree_internal(struct dt_info *dti, + struct node *fn, + struct node *node) +{ + struct node *dt = dti->dt; + struct node *c; + struct property *prop; + struct marker *m; + struct node *refnode; + int ret = 0; + + for_each_property(node, prop) { + m = prop->val.markers; + for_each_marker_of_type(m, REF_PHANDLE) { + refnode = get_node_by_ref(dt, m->ref); + if (!refnode) + if (add_fixup_entry(dti, fn, node, prop, m)) + ret = -1; + } + } + + for_each_child(node, c) + if (generate_fixups_tree_internal(dti, fn, c)) + ret = -1; + + return ret; +} + +static bool any_local_fixup_tree(struct dt_info *dti, struct node *node) +{ + struct node *c; + struct property *prop; + struct marker *m; + + for_each_property(node, prop) { + m = prop->val.markers; + for_each_marker_of_type(m, REF_PHANDLE) { + if (get_node_by_ref(dti->dt, m->ref)) + return true; + } + } + + for_each_child(node, c) { + if (any_local_fixup_tree(dti, c)) + return true; + } + + return false; +} + +static int add_local_fixup_entry(struct dt_info *dti, + struct node *lfn, struct node *node, + struct property *prop, struct marker *m, + struct node *refnode) +{ + struct node *wn, *nwn; /* local fixup node, walk node, new */ + fdt32_t value_32; + char **compp; + int i, depth; + + /* walk back retrieving depth */ + depth = 0; + for (wn = node; wn; wn = wn->parent) + depth++; + + /* allocate name array */ + compp = xmalloc(sizeof(*compp) * depth); + + /* store names in the array */ + for (wn = node, i = depth - 1; wn; wn = wn->parent, i--) + compp[i] = wn->name; + + /* walk the path components creating nodes if they don't exist */ + for (wn = lfn, i = 1; i < depth; i++, wn = nwn) { + /* if no node exists, create it */ + nwn = build_root_node(wn, compp[i]); + } + + free(compp); + + value_32 = cpu_to_fdt32(m->offset); + return append_unique_u32_to_property(wn, prop->name, value_32); +} + +static int generate_local_fixups_tree_internal(struct dt_info *dti, + struct node *lfn, + struct node *node) +{ + struct node *dt = dti->dt; + struct node *c; + struct property *prop; + struct marker *m; + struct node *refnode; + int ret = 0; + + for_each_property(node, prop) { + m = prop->val.markers; + for_each_marker_of_type(m, REF_PHANDLE) { + refnode = get_node_by_ref(dt, m->ref); + if (refnode) + if (add_local_fixup_entry(dti, lfn, node, prop, m, refnode)) + ret = -1; + } + } + + for_each_child(node, c) + if (generate_local_fixups_tree_internal(dti, lfn, c)) + ret = -1; + + return ret; +} + +void generate_labels_from_tree(struct dt_info *dti, const char *name) +{ + struct node *an; + struct property *p; + + an = get_subnode(dti->dt, name); + if (!an) + return; + + for_each_property(an, p) { + struct node *labeled_node; + + labeled_node = get_node_by_path(dti->dt, p->val.val); + if (labeled_node) + add_label(&labeled_node->labels, p->name); + else if (quiet < 1) + fprintf(stderr, "Warning: Path %s referenced in property %s/%s missing", + p->val.val, name, p->name); + } +} + +void generate_label_tree(struct dt_info *dti, const char *name, bool allocph) +{ + if (!any_label_tree(dti, dti->dt)) + return; + generate_label_tree_internal(dti, build_root_node(dti->dt, name), + dti->dt, allocph); +} + +void generate_fixups_tree(struct dt_info *dti, const char *name) +{ + if (!any_fixup_tree(dti, dti->dt)) + return; + if (generate_fixups_tree_internal(dti, build_root_node(dti->dt, name), dti->dt)) + fprintf(stderr, + "Warning: Preexisting data in %s malformed, some content could not be added.\n", + name); +} + +void fixup_phandles(struct dt_info *dti, const char *name) +{ + struct node *an; + struct property *fp; + + an = get_subnode(dti->dt, name); + if (!an) + return; + + for_each_property(an, fp) { + char *fnext = fp->val.val; + char *fv; + unsigned int fl; + + while ((fl = fp->val.len - (fnext - fp->val.val))) { + char *propname, *soffset; + struct node *n; + struct property *p; + long offset; + + fv = fnext; + fnext = memchr(fv, 0, fl); + + if (!fnext) { + if (quiet < 1) + fprintf(stderr, "Warning: Malformed fixup entry for label %s\n", + fp->name); + break; + } + fnext += 1; + + propname = memchr(fv, ':', fnext - 1 - fv); + if (!propname) { + if (quiet < 1) + fprintf(stderr, "Warning: Malformed fixup entry for label %s\n", + fp->name); + continue; + } + propname++; + + soffset = memchr(propname, ':', fnext - 1 - propname); + if (!soffset) { + if (quiet < 1) + fprintf(stderr, "Warning: Malformed fixup entry for label %s\n", + fp->name); + continue; + } + soffset++; + + /* + * temporarily modify the property to not have to create + * a copy for the node path. + */ + *(propname - 1) = '\0'; + + n = get_node_by_path(dti->dt, fv); + if (!n && quiet < 1) + fprintf(stderr, "Warning: Label %s references non-existing node %s\n", + fp->name, fv); + + *(propname - 1) = ':'; + + if (!n) + continue; + + /* + * temporarily modify the property to not have to create + * a copy for the property name. + */ + *(soffset - 1) = '\0'; + + p = get_property(n, propname); + + if (!p && quiet < 1) + fprintf(stderr, "Warning: Label %s references non-existing property %s in node %s\n", + fp->name, n->fullpath, propname); + + *(soffset - 1) = ':'; + + if (!p) + continue; + + offset = strtol(soffset, NULL, 0); + if (offset < 0 || offset + 4 > p->val.len) { + if (quiet < 1) + fprintf(stderr, + "Warning: Label %s contains invalid offset for property %s in node %s\n", + fp->name, p->name, n->fullpath); + continue; + } + + property_add_marker(p, REF_PHANDLE, offset, fp->name); + } + } +} + +void generate_local_fixups_tree(struct dt_info *dti, const char *name) +{ + if (!any_local_fixup_tree(dti, dti->dt)) + return; + if (generate_local_fixups_tree_internal(dti, build_root_node(dti->dt, name), dti->dt)) + fprintf(stderr, + "Warning: Preexisting data in %s malformed, some content could not be added.\n", + name); +} + +static void local_fixup_phandles_node(struct dt_info *dti, struct node *lf, struct node *n) +{ + struct property *lfp; + struct node *lfsubnode; + + for_each_property(lf, lfp) { + struct property *p = get_property(n, lfp->name); + fdt32_t *offsets = (fdt32_t *)lfp->val.val; + size_t i; + + if (!p) { + if (quiet < 1) + fprintf(stderr, "Warning: Property %s in %s referenced in __local_fixups__ missing\n", + lfp->name, n->fullpath); + continue; + } + + /* + * Each property in the __local_fixups__ tree is a concatenation + * of offsets, so it must be a multiple of sizeof(fdt32_t). + */ + if (lfp->val.len % sizeof(fdt32_t)) { + if (quiet < 1) + fprintf(stderr, "Warning: property %s in /__local_fixups__%s malformed\n", + lfp->name, n->fullpath); + continue; + } + + for (i = 0; i < lfp->val.len / sizeof(fdt32_t); i++) + add_phandle_marker(dti, p, dtb_ld32(offsets + i)); + } + + for_each_child(lf, lfsubnode) { + struct node *subnode = get_subnode(n, lfsubnode->name); + + if (!subnode) { + if (quiet < 1) + fprintf(stderr, "Warning: node %s/%s referenced in __local_fixups__ missing\n", + lfsubnode->name, n->fullpath); + continue; + } + + local_fixup_phandles_node(dti, lfsubnode, subnode); + } +} + +void local_fixup_phandles(struct dt_info *dti, const char *name) +{ + struct node *an; + + an = get_subnode(dti->dt, name); + if (!an) + return; + + local_fixup_phandles_node(dti, an, dti->dt); +} diff --git a/scripts/dtc/of_unittest_expect b/scripts/dtc/of_unittest_expect new file mode 100755 index 000000000..0a535a8e9 --- /dev/null +++ b/scripts/dtc/of_unittest_expect @@ -0,0 +1,541 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright 2020, 2022 Sony Corporation +# +# Author: Frank Rowand + +# This program is meant to be an aid to reading the verbose output of +# on the console log that results from executing the Linux kernel +# devicetree unittest (drivers/of/unitest.c). + +$VUFX = "230211a"; + +use strict 'refs'; +use strict subs; + +use Getopt::Long; +use Text::Wrap; + +# strip off everything before final "/" +(undef, $script_name) = split(/^.*\//, $0); + +# following /usr/include/sysexits.h +$EX_OK=0; +$EX_USAGE=64; + + +#______________________________________________________________________________ +sub compare { + my ($expect, $got) = @_; + my $expect_next; + my $expect_next_lit; + my $got_next; + my $type; + + while ($expect) { + + ($expect_next, $type) = split(/<</, $expect); + ($type) = split(/>>/, $type); + $expect =~ s/^.*?>>//; # '?' is non-greedy, minimal match + + # literal, ignore all metacharacters when used in a regex + $expect_next_lit = quotemeta($expect_next); + + $got_next = $got; + $got_next =~ s/^($expect_next_lit).*/\1/; + $got =~ s/^$expect_next_lit//; + + if ($expect_next ne $got_next) { + return 0; + } + + if ($type eq "int") { + if ($got =~ /^[+-]*[0-9]+/) { + $got =~ s/^[+-]*[0-9]+//; + } else { + return 0; + } + } elsif ($type eq "hex") { + if ($got =~ /^(0x)*[0-9a-f]+/) { + $got =~ s/^(0x)*[0-9a-f]+//; + } else { + return 0; + } + } elsif ($type eq "all") { + return 1; + } elsif ($type eq "") { + if ($expect_next ne $got_next) { + return 0; + } else { + return 1; + } + } else { + $internal_err++; + print "** ERROR: special pattern not recognized: <<$type>>, CONSOLE_LOG line: $.\n"; + return 0; + } + + } + + # should not get here + $internal_err++; + print "** ERROR: $script_name internal error, at end of compare(), CONSOLE_LOG line: $.\n"; + + return 0; +} + + +#______________________________________________________________________________ +sub usage { + +# ***** when editing, be careful to not put tabs in the string printed: + + print STDERR +" +usage: + + $script_name CONSOLE_LOG + + -h print program usage + --help print program usage + --hide-expect suppress output of EXPECTed lines + --line-num report line number of CONSOLE_LOG + --no-expect-stats do not report EXPECT statistics + --no-strip-ts do not strip leading console timestamps + --verbose do not suppress EXPECT begin and end lines + --version print program version and exit + + + Process a console log for EXPECTed test related messages to either + highlight expected devicetree unittest related messages or suppress + the messages. Leading console timestamps will be stripped. + + Various unittests may trigger kernel messages from outside the + unittest code. The unittest annotates that it expects the message + to occur with an 'EXPECT \\ : text' (begin) before triggering the + message, and an 'EXPECT / : text' (end) after triggering the message. + + If an expected message does not occur, that will be reported. + + For each expected message, the 'EXPECT \\ : text' (begin) and + 'EXPECT / : text' (end), 'text' will contain the message text. + + If 'EXPECT \\' (begin) and 'EXPECT /' (end) lines do not contain + matching 'text', that will be reported. + + If EXPECT lines are nested, 'EXPECT /' (end) lines must be in the + reverse order of the corresponding 'EXPECT \\' (begin) lines. + + 'EXPECT \\ : text' (begin) and 'EXPECT / : text' (end) lines can + contain special patterns in 'text': + + <<int>> matches: [+-]*[0-9]+ + <<hex>> matches: (0x)*[0-9a-f]+ + <<all>> matches: anything to end of line + + 'EXPECT \\' (begin) and 'EXPECT /' (end) lines are suppressed. + + A prefix is added to every line of output: + + 'ok ' Line matches an enclosing EXPECT begin/end pair + + '** ' Line reports $script_name warning or error + + '-> ' Line reports start or end of the unittests + + '>> ' Line reports a unittest test FAIL + + ' ' Lines that are not otherwise prefixed + + Issues detected in CONSOLE_LOG are reported to STDOUT, not to STDERR. + + Known Issues: + + --line-num causes the CONSOLE_LOG line number to be printed in 4 columns. + If CONSOLE_LOG contains more than 9999 lines then more columns will be + used to report the line number for lines greater than 9999 (eg for + lines 10000 - 99999, 5 columns will be used). +"; + + return {}; +} + +#______________________________________________________________________________ +#______________________________________________________________________________ + +if (!GetOptions( + "h" => \$help, + "help" => \$help, + "hide-expect" => \$hide_expect, + "line-num" => \$print_line_num, + "no-expect-stats" => \$no_expect_stats, + "no-strip-ts" => \$no_strip_ts, + "verbose" => \$verbose, + "version" => \$version, + )) { + print STDERR "\n"; + print STDERR "ERROR processing command line options\n"; + print STDERR "\n"; + print STDERR "For help, type '$script_name --help'\n"; + print STDERR "\n"; + + exit $EX_OK; +} + + +if ($no_strip_ts) { + $strip_ts = 1; + $no_strip_ts = 0; +} else { + $strip_ts = 0; + $no_strip_ts = 1; +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if ($help){ + + &usage; + + exit $EX_OK; +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +if ($version) { + print STDERR "\n$script_name $VUFX\n\n"; + print STDERR "\n"; + + exit $EX_OK; +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if ($#ARGV != 0) { + + # Limit input files to exactly one. + # + # 'while ($line = <ARGV>) {' in the code below supports multiple file + # names on the command line, but the EXPECT statistics are reported + # once for all input - it is not an expected use case to generate one + # set of statistics for multiple input files. + + print STDERR "\n"; + print STDERR "Required arguments: CONSOLE_LOG\n"; + print STDERR "\n"; + + exit $EX_USAGE; +} + + +#______________________________________________________________________________ + +# Patterns to match 'EXPECT \ : ' (begin) and 'EXPECT / : ' (end) +# +# $exp_* are used as regex match patterns, +# so '\\\\' in $exp_begin matches a single '\' +# quotemeta() does not do the right thing in this case +# +# $pr_fmt is the prefix that unittest prints for every message + +$pr_fmt = "### dt-test ### "; +$exp_begin = "${pr_fmt}EXPECT \\\\ : "; +$exp_end = "${pr_fmt}EXPECT / : "; +$expnot_begin = "${pr_fmt}EXPECT_NOT \\\\ : "; +$expnot_end = "${pr_fmt}EXPECT_NOT / : "; + + +$line_num = ""; +$timestamp = ""; + +LINE: +while ($line = <ARGV>) { + + chomp $line; + + $suppress_line = 0; + + $prefix = " "; ## 2 characters + + + if ($strip_ts) { + + $timestamp = $line; + + if ($timestamp =~ /^\[\s*[0-9]+\.[0-9]*\] /) { + ($timestamp, $null) = split(/]/, $line); + $timestamp = $timestamp . "] "; + + } else { + $timestamp = ""; + } + } + + $line =~ s/^\[\s*[0-9]+\.[0-9]*\] //; + + + # ----- find EXPECT begin + + if ($line =~ /^\s*$exp_begin/) { + $data = $line; + $data =~ s/^\s*$exp_begin//; + push @exp_begin_stack, $data; + + if ($verbose) { + if ($print_line_num) { + $line_num = sprintf("%4s ", $.); + } + printf "%s %s%s%s\n", $prefix, $line_num, $timestamp, $line; + } + + next LINE; + } + + + # ----- find EXPECT end + + if ($line =~ /^\s*$exp_end/) { + $data = $line; + $data =~ s/^\s*$exp_end//; + + if ($verbose) { + if ($print_line_num) { + $line_num = sprintf("%4s ", $.); + } + printf "%s %s%s%s\n", $prefix, $line_num, $timestamp, $line; + } + + $found = 0; + $no_begin = 0; + if (@exp_found_or_begin > 0) { + $begin = pop @exp_found_or_begin; + if (compare($data, $begin)) { + $found = 1; + $exp_found++; + } + } elsif (@begin > 0) { + $begin = pop @exp_begin_stack; + } else { + $no_begin = 1; + } + + if ($no_begin) { + + $exp_missing_begin++; + print "** ERROR: EXPECT end without matching EXPECT begin:\n"; + print " end ---> $line\n"; + + } elsif (! $found) { + + if ($print_line_num) { + $line_num = sprintf("%4s ", $.); + } + + $exp_missing++; + printf "** %s%s$script_name WARNING - not found ---> %s\n", + $line_num, $timestamp, $data; + + } elsif (! compare($data, $begin) and ($data ne $begin)) { + + $exp_missing_end++; + print "** ERROR: EXPECT end does not match EXPECT begin:\n"; + print " begin -> $begin\n"; + print " end ---> $line\n"; + + } + + next LINE; + } + + + # ----- find EXPECT_NOT begin + + if ($line =~ /^\s*$expnot_begin/) { + $data = $line; + $data =~ s/^\s*$expnot_begin//; + push @expnot_begin_stack, $data; + + if ($verbose) { + if ($print_line_num) { + $line_num = sprintf("%4s ", $.); + } + printf "%s %s%s%s\n", $prefix, $line_num, $timestamp, $line; + } + + next LINE; + } + + + # ----- find EXPECT_NOT end + + if ($line =~ /^\s*$expnot_end/) { + $data = $line; + $data =~ s/^\s*$expnot_end//; + + if ($verbose) { + if ($print_line_num) { + $line_num = sprintf("%4s ", $.); + } + printf "%s %s%s%s\n", $prefix, $line_num, $timestamp, $line; + } + + $found = 0; + $no_begin = 0; + if (@expnot_found_or_begin > 0) { + $begin = pop @expnot_found_or_begin; + if (compare($data, $begin)) { + $found = 1; + $expnot_found++; + } + } elsif (@expnot_begin_stack <= 0) { + $no_begin = 1; + } + + if ($no_begin) { + + $expnot_missing_begin++; + print "** ERROR: EXPECT_NOT end without matching EXPECT_NOT begin:\n"; + print " end ---> $line\n"; + + } + + if ($found) { + + if ($print_line_num) { + $line_num = sprintf("%4s ", $.); + } + + printf "** %s%s$script_name WARNING - next line matches EXPECT_NOT\n", + $line_num, $timestamp; + printf "** %s%s%s\n", $line_num, $timestamp, $line; + + } else { + + $expnot_missing++; + + } + + if (@expnot_begin_stack > 0) { + $begin = pop @expnot_begin_stack; + + if (! compare($data, $begin) and ($data ne $begin)) { + + $expnot_missing_end++; + print "** ERROR: EXPECT_NOT end does not match EXPECT_NOT begin:\n"; + print " begin -> $begin\n"; + print " end ---> $line\n"; + + } + } + + next LINE; + } + + + # ----- not an EXPECT line + + if (($line =~ /^${pr_fmt}start of unittest - you will see error messages$/) || + ($line =~ /^${pr_fmt}end of unittest - [0-9]+ passed, [0-9]+ failed$/ ) ) { + $prefix = "->"; # 2 characters + } elsif ($line =~ /^${pr_fmt}FAIL /) { + $unittest_fail++; + $prefix = ">>"; # 2 characters + } + + $found = 0; + foreach $begin (@exp_begin_stack) { + if (compare($begin, $line)) { + $found = 1; + last; + } + } + + if ($found) { + $begin = shift @exp_begin_stack; + while (! compare($begin, $line)) { + push @exp_found_or_begin, $begin; + $begin = shift @exp_begin_stack; + } + push @exp_found_or_begin, $line; + + if ($hide_expect) { + $suppress_line = 1; + } + $prefix = "ok"; # 2 characters + } + + + $found = 0; + foreach $begin (@expnot_begin_stack) { + if (compare($begin, $line)) { + $found = 1; + last; + } + } + + if ($found) { + $begin = shift @begin; + while (! compare($begin, $line)) { + push @expnot_found_or_begin, $begin; + $begin = shift @begin; + } + push @expnot_found_or_begin, $line; + + if ($hide_expect) { + $suppress_line = 1; + } + $prefix = "**"; # 2 characters + } + + + if ($suppress_line) { + next LINE; + } + + if ($print_line_num) { + $line_num = sprintf("%4s ", $.); + } + + printf "%s %s%s%s\n", $prefix, $line_num, $timestamp, $line; +} + +if (! $no_expect_stats) { + print "\n"; + print "** EXPECT statistics:\n"; + print "**\n"; + printf "** non-zero values expected:\n"; + print "**\n"; + printf "** EXPECT found : %4i\n", $exp_found; + printf "** EXPECT_NOT not found : %4i\n", $expnot_missing; + print "**\n"; + printf "** zero values expected:\n"; + print "**\n"; + printf "** EXPECT not found : %4i\n", $exp_missing; + printf "** missing EXPECT begin : %4i\n", $exp_missing_begin; + printf "** missing EXPECT end : %4i\n", $exp_missing_end; + print "**\n"; + printf "** EXPECT_NOT found : %4i\n", $expnot_found; + printf "** missing EXPECT_NOT begin : %4i\n", $expnot_missing_begin; + printf "** missing EXPECT_NOT end : %4i\n", $expnot_missing_end; + print "**\n"; + printf "** unittest FAIL : %4i\n", $unittest_fail; + printf "** internal error : %4i\n", $internal_err; +} + +if (@exp_begin_stack) { + print "** ERROR: EXPECT begin without matching EXPECT end:\n"; + print " This list may be misleading.\n"; + foreach $begin (@exp_begin_stack) { + print " begin ---> $begin\n"; + } +} + +if (@expnot_begin_stack) { + print "** ERROR: EXPECT_NOT begin without matching EXPECT_NOT end:\n"; + print " This list may be misleading.\n"; + foreach $begin (@expnot_begin_stack) { + print " begin ---> $begin\n"; + } +} diff --git a/scripts/dtc/srcpos.c b/scripts/dtc/srcpos.c new file mode 100644 index 000000000..fef892fb6 --- /dev/null +++ b/scripts/dtc/srcpos.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2007 Jon Loeliger, Freescale Semiconductor, Inc. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> + +#include "dtc.h" +#include "srcpos.h" + +/* A node in our list of directories to search for source/include files */ +struct search_path { + struct search_path *next; /* next node in list, NULL for end */ + const char *dirname; /* name of directory to search */ +}; + +/* This is the list of directories that we search for source files */ +static struct search_path *search_path_head, **search_path_tail; + +/* Detect infinite include recursion. */ +#define MAX_SRCFILE_DEPTH (200) +static int srcfile_depth; /* = 0 */ + +static char *get_dirname(const char *path) +{ + const char *slash = strrchr(path, '/'); + + if (slash) { + int len = slash - path; + char *dir = xmalloc(len + 1); + + memcpy(dir, path, len); + dir[len] = '\0'; + return dir; + } + return NULL; +} + +FILE *depfile; /* = NULL */ +struct srcfile_state *current_srcfile; /* = NULL */ +static char *initial_path; /* = NULL */ +static int initial_pathlen; /* = 0 */ +static bool initial_cpp = true; + +static void set_initial_path(char *fname) +{ + int i, len = strlen(fname); + + xasprintf(&initial_path, "%s", fname); + initial_pathlen = 0; + for (i = 0; i != len; i++) + if (initial_path[i] == '/') + initial_pathlen++; +} + +static char *shorten_to_initial_path(char *fname) +{ + char *p1, *p2, *prevslash1 = NULL; + int slashes = 0; + + for (p1 = fname, p2 = initial_path; *p1 && *p2; p1++, p2++) { + if (*p1 != *p2) + break; + if (*p1 == '/') { + prevslash1 = p1; + slashes++; + } + } + p1 = prevslash1 + 1; + if (prevslash1) { + int diff = initial_pathlen - slashes, i, j; + int restlen = strlen(fname) - (p1 - fname); + char *res; + + res = xmalloc((3 * diff) + restlen + 1); + for (i = 0, j = 0; i != diff; i++) { + res[j++] = '.'; + res[j++] = '.'; + res[j++] = '/'; + } + strcpy(res + j, p1); + return res; + } + return NULL; +} + +/** + * Returns true if the given path is an absolute one. + * + * On Windows, it either needs to begin with a forward slash or with a drive + * letter (e.g. "C:"). + * On all other operating systems, it must begin with a forward slash to be + * considered an absolute path. + */ +static bool is_absolute_path(const char *path) +{ +#ifdef WIN32 + return ( + path[0] == '/' || + (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':') + ); +#else + return (path[0] == '/'); +#endif +} + +/** + * Try to open a file in a given directory. + * + * If the filename is an absolute path, then dirname is ignored. If it is a + * relative path, then we look in that directory for the file. + * + * @param dirname Directory to look in, or NULL for none + * @param fname Filename to look for + * @param fp Set to NULL if file did not open + * @return allocated filename on success (caller must free), NULL on failure + */ +static char *try_open(const char *dirname, const char *fname, FILE **fp) +{ + char *fullname; + + if (!dirname || is_absolute_path(fname)) + fullname = xstrdup(fname); + else + fullname = join_path(dirname, fname); + + *fp = fopen(fullname, "rb"); + if (!*fp) { + free(fullname); + fullname = NULL; + } + + return fullname; +} + +/** + * Open a file for read access + * + * If it is a relative filename, we search the full search path for it. + * + * @param fname Filename to open + * @param fp Returns pointer to opened FILE, or NULL on failure + * @return pointer to allocated filename, which caller must free + */ +static char *fopen_any_on_path(const char *fname, FILE **fp) +{ + const char *cur_dir = NULL; + struct search_path *node; + char *fullname; + + /* Try current directory first */ + assert(fp); + if (current_srcfile) + cur_dir = current_srcfile->dir; + fullname = try_open(cur_dir, fname, fp); + + /* Failing that, try each search path in turn */ + for (node = search_path_head; !*fp && node; node = node->next) + fullname = try_open(node->dirname, fname, fp); + + return fullname; +} + +FILE *srcfile_relative_open(const char *fname, char **fullnamep) +{ + FILE *f; + char *fullname; + + if (streq(fname, "-")) { + f = stdin; + fullname = xstrdup("<stdin>"); + } else { + fullname = fopen_any_on_path(fname, &f); + if (!f) + die("Couldn't open \"%s\": %s\n", fname, + strerror(errno)); + } + + if (depfile) { + fputc(' ', depfile); + fprint_path_escaped(depfile, fullname); + } + + if (fullnamep) + *fullnamep = fullname; + else + free(fullname); + + return f; +} + +void srcfile_push(const char *fname) +{ + struct srcfile_state *srcfile; + + if (srcfile_depth++ >= MAX_SRCFILE_DEPTH) + die("Includes nested too deeply"); + + srcfile = xmalloc(sizeof(*srcfile)); + + srcfile->f = srcfile_relative_open(fname, &srcfile->name); + srcfile->dir = get_dirname(srcfile->name); + srcfile->prev = current_srcfile; + + srcfile->lineno = 1; + srcfile->colno = 1; + + current_srcfile = srcfile; + + if (srcfile_depth == 1) + set_initial_path(srcfile->name); +} + +bool srcfile_pop(void) +{ + struct srcfile_state *srcfile = current_srcfile; + + assert(srcfile); + + current_srcfile = srcfile->prev; + + if (fclose(srcfile->f)) + die("Error closing \"%s\": %s\n", srcfile->name, + strerror(errno)); + + /* FIXME: We allow the srcfile_state structure to leak, + * because it could still be referenced from a location + * variable being carried through the parser somewhere. To + * fix this we could either allocate all the files from a + * table, or use a pool allocator. */ + + return current_srcfile ? true : false; +} + +void srcfile_add_search_path(const char *dirname) +{ + struct search_path *node; + + /* Create the node */ + node = xmalloc(sizeof(*node)); + node->next = NULL; + node->dirname = xstrdup(dirname); + + /* Add to the end of our list */ + if (search_path_tail) + *search_path_tail = node; + else + search_path_head = node; + search_path_tail = &node->next; +} + +void srcpos_update(struct srcpos *pos, const char *text, int len) +{ + int i; + + pos->file = current_srcfile; + + pos->first_line = current_srcfile->lineno; + pos->first_column = current_srcfile->colno; + + for (i = 0; i < len; i++) + if (text[i] == '\n') { + current_srcfile->lineno++; + current_srcfile->colno = 1; + } else { + current_srcfile->colno++; + } + + pos->last_line = current_srcfile->lineno; + pos->last_column = current_srcfile->colno; +} + +struct srcpos * +srcpos_copy(struct srcpos *pos) +{ + struct srcpos *pos_new; + struct srcfile_state *srcfile_state; + + if (!pos) + return NULL; + + pos_new = xmalloc(sizeof(struct srcpos)); + assert(pos->next == NULL); + memcpy(pos_new, pos, sizeof(struct srcpos)); + + /* allocate without free */ + srcfile_state = xmalloc(sizeof(struct srcfile_state)); + memcpy(srcfile_state, pos->file, sizeof(struct srcfile_state)); + pos_new->file = srcfile_state; + + return pos_new; +} + +struct srcpos *srcpos_extend(struct srcpos *pos, struct srcpos *newtail) +{ + struct srcpos *p; + + if (!pos) + return newtail; + + for (p = pos; p->next != NULL; p = p->next); + p->next = newtail; + return pos; +} + +void srcpos_free(struct srcpos *pos) +{ + struct srcpos *p_next; + + while (pos) { + p_next = pos->next; + free(pos); + pos = p_next; + } +} + +char * +srcpos_string(struct srcpos *pos) +{ + const char *fname = "<no-file>"; + char *pos_str; + + if (pos->file && pos->file->name) + fname = pos->file->name; + + + if (pos->first_line != pos->last_line) + xasprintf(&pos_str, "%s:%d.%d-%d.%d", fname, + pos->first_line, pos->first_column, + pos->last_line, pos->last_column); + else if (pos->first_column != pos->last_column) + xasprintf(&pos_str, "%s:%d.%d-%d", fname, + pos->first_line, pos->first_column, + pos->last_column); + else + xasprintf(&pos_str, "%s:%d.%d", fname, + pos->first_line, pos->first_column); + + return pos_str; +} + +static char * +srcpos_string_comment(struct srcpos *pos, bool first_line, int level) +{ + char *pos_str, *fresh_fname = NULL, *first, *rest; + const char *fname; + + if (!pos) { + if (level > 1) { + xasprintf(&pos_str, "<no-file>:<no-line>"); + return pos_str; + } else { + return NULL; + } + } + + if (!pos->file) + fname = "<no-file>"; + else if (!pos->file->name) + fname = "<no-filename>"; + else if (level > 1) + fname = pos->file->name; + else { + fresh_fname = shorten_to_initial_path(pos->file->name); + if (fresh_fname) + fname = fresh_fname; + else + fname = pos->file->name; + } + + if (level > 1) + xasprintf(&first, "%s:%d:%d-%d:%d", fname, + pos->first_line, pos->first_column, + pos->last_line, pos->last_column); + else + xasprintf(&first, "%s:%d", fname, + first_line ? pos->first_line : pos->last_line); + + if (fresh_fname) + free(fresh_fname); + + if (pos->next != NULL) { + rest = srcpos_string_comment(pos->next, first_line, level); + xasprintf(&pos_str, "%s, %s", first, rest); + free(first); + free(rest); + } else { + pos_str = first; + } + + return pos_str; +} + +char *srcpos_string_first(struct srcpos *pos, int level) +{ + return srcpos_string_comment(pos, true, level); +} + +char *srcpos_string_last(struct srcpos *pos, int level) +{ + return srcpos_string_comment(pos, false, level); +} + +void srcpos_verror(struct srcpos *pos, const char *prefix, + const char *fmt, va_list va) +{ + char *srcstr; + + srcstr = srcpos_string(pos); + + fprintf(stderr, "%s: %s ", prefix, srcstr); + vfprintf(stderr, fmt, va); + fprintf(stderr, "\n"); + + free(srcstr); +} + +void srcpos_error(struct srcpos *pos, const char *prefix, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + srcpos_verror(pos, prefix, fmt, va); + va_end(va); +} + +void srcpos_set_line(char *f, int l) +{ + current_srcfile->name = f; + current_srcfile->lineno = l; + + if (initial_cpp) { + initial_cpp = false; + set_initial_path(f); + } +} diff --git a/scripts/dtc/srcpos.h b/scripts/dtc/srcpos.h new file mode 100644 index 000000000..4d60b50e3 --- /dev/null +++ b/scripts/dtc/srcpos.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2007 Jon Loeliger, Freescale Semiconductor, Inc. + */ + +#ifndef SRCPOS_H +#define SRCPOS_H + +#include <stdio.h> +#include <stdbool.h> +#include "util.h" + +struct srcfile_state { + FILE *f; + char *name; + char *dir; + int lineno, colno; + struct srcfile_state *prev; +}; + +extern FILE *depfile; /* = NULL */ +extern struct srcfile_state *current_srcfile; /* = NULL */ + +/** + * Open a source file. + * + * If the source file is a relative pathname, then it is searched for in the + * current directory (the directory of the last source file read) and after + * that in the search path. + * + * We work through the search path in order from the first path specified to + * the last. + * + * If the file is not found, then this function does not return, but calls + * die(). + * + * @param fname Filename to search + * @param fullnamep If non-NULL, it is set to the allocated filename of the + * file that was opened. The caller is then responsible + * for freeing the pointer. + * @return pointer to opened FILE + */ +FILE *srcfile_relative_open(const char *fname, char **fullnamep); + +void srcfile_push(const char *fname); +bool srcfile_pop(void); + +/** + * Add a new directory to the search path for input files + * + * The new path is added at the end of the list. + * + * @param dirname Directory to add + */ +void srcfile_add_search_path(const char *dirname); + +struct srcpos { + int first_line; + int first_column; + int last_line; + int last_column; + struct srcfile_state *file; + struct srcpos *next; +}; + +#define YYLTYPE struct srcpos + +#define YYLLOC_DEFAULT(Current, Rhs, N) \ + do { \ + if (N) { \ + (Current).first_line = YYRHSLOC(Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC(Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC(Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + (Current).file = YYRHSLOC(Rhs, N).file; \ + } else { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC(Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC(Rhs, 0).last_column; \ + (Current).file = YYRHSLOC (Rhs, 0).file; \ + } \ + (Current).next = NULL; \ + } while (0) + + +extern void srcpos_update(struct srcpos *pos, const char *text, int len); +extern struct srcpos *srcpos_copy(struct srcpos *pos); +extern struct srcpos *srcpos_extend(struct srcpos *new_srcpos, + struct srcpos *old_srcpos); +extern void srcpos_free(struct srcpos *pos); +extern char *srcpos_string(struct srcpos *pos); +extern char *srcpos_string_first(struct srcpos *pos, int level); +extern char *srcpos_string_last(struct srcpos *pos, int level); + + +extern void PRINTF(3, 0) srcpos_verror(struct srcpos *pos, const char *prefix, + const char *fmt, va_list va); +extern void PRINTF(3, 4) srcpos_error(struct srcpos *pos, const char *prefix, + const char *fmt, ...); + +extern void srcpos_set_line(char *f, int l); + +#endif /* SRCPOS_H */ diff --git a/scripts/dtc/treesource.c b/scripts/dtc/treesource.c new file mode 100644 index 000000000..bf648bf6c --- /dev/null +++ b/scripts/dtc/treesource.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include "dtc.h" +#include "srcpos.h" + +extern FILE *yyin; +extern int yyparse(void); +extern YYLTYPE yylloc; + +struct dt_info *parser_output; +bool treesource_error; + +struct dt_info *dt_from_source(const char *fname) +{ + parser_output = NULL; + treesource_error = false; + + srcfile_push(fname); + yyin = current_srcfile->f; + yylloc.file = current_srcfile; + + if (yyparse() != 0) + die("Unable to parse input tree\n"); + + if (treesource_error) + die("Syntax error parsing input tree\n"); + + return parser_output; +} + +static void write_prefix(FILE *f, int level) +{ + int i; + + for (i = 0; i < level; i++) + fputc('\t', f); +} + +static bool isstring(char c) +{ + return (isprint((unsigned char)c) + || (c == '\0') + || strchr("\a\b\t\n\v\f\r", c)); +} + +static void write_propval_string(FILE *f, const char *s, size_t len) +{ + const char *end = s + len - 1; + + if (!len) + return; + + assert(*end == '\0'); + + fprintf(f, "\""); + while (s < end) { + char c = *s++; + switch (c) { + case '\a': + fprintf(f, "\\a"); + break; + case '\b': + fprintf(f, "\\b"); + break; + case '\t': + fprintf(f, "\\t"); + break; + case '\n': + fprintf(f, "\\n"); + break; + case '\v': + fprintf(f, "\\v"); + break; + case '\f': + fprintf(f, "\\f"); + break; + case '\r': + fprintf(f, "\\r"); + break; + case '\\': + fprintf(f, "\\\\"); + break; + case '\"': + fprintf(f, "\\\""); + break; + case '\0': + fprintf(f, "\\0"); + break; + default: + if (isprint((unsigned char)c)) + fprintf(f, "%c", c); + else + fprintf(f, "\\x%02"PRIx8, c); + } + } + fprintf(f, "\""); +} + +static void write_propval_int(FILE *f, const char *p, size_t len, size_t width) +{ + const char *end = p + len; + assert(len % width == 0); + + for (; p < end; p += width) { + switch (width) { + case 1: + fprintf(f, "%02"PRIx8, *(const uint8_t*)p); + break; + case 2: + fprintf(f, "0x%02"PRIx16, dtb_ld16(p)); + break; + case 4: + fprintf(f, "0x%02"PRIx32, dtb_ld32(p)); + break; + case 8: + fprintf(f, "0x%02"PRIx64, dtb_ld64(p)); + break; + } + if (p + width < end) + fputc(' ', f); + } +} + +static const char *delim_start[] = { + [TYPE_UINT8] = "[", + [TYPE_UINT16] = "/bits/ 16 <", + [TYPE_UINT32] = "<", + [TYPE_UINT64] = "/bits/ 64 <", + [TYPE_STRING] = "", +}; +static const char *delim_end[] = { + [TYPE_UINT8] = "]", + [TYPE_UINT16] = ">", + [TYPE_UINT32] = ">", + [TYPE_UINT64] = ">", + [TYPE_STRING] = "", +}; + +/* + * The invariants in the marker list are: + * - offsets are non-strictly monotonically increasing + * - for a single offset there is at most one type marker + * - for a single offset that has both a type marker and non-type markers, the + * type marker appears before the others. + */ +static struct marker **add_marker(struct marker **mi, + enum markertype type, unsigned int offset, char *ref) +{ + struct marker *nm; + + while (*mi && (*mi)->offset < offset) + mi = &(*mi)->next; + + if (*mi && (*mi)->offset == offset && is_type_marker((*mi)->type)) { + if (is_type_marker(type)) + return mi; + mi = &(*mi)->next; + } + + if (*mi && (*mi)->offset == offset && type == (*mi)->type) + return mi; + + nm = xmalloc(sizeof(*nm)); + nm->type = type; + nm->offset = offset; + nm->ref = ref; + nm->next = *mi; + *mi = nm; + + return &nm->next; +} + +void property_add_marker(struct property *prop, + enum markertype type, unsigned int offset, char *ref) +{ + add_marker(&prop->val.markers, type, offset, ref); +} + +static void add_string_markers(struct property *prop, unsigned int offset, int len) +{ + int l; + const char *p = prop->val.val + offset; + struct marker **mi = &prop->val.markers; + + for (l = strlen(p) + 1; l < len; l += strlen(p + l) + 1) + mi = add_marker(mi, TYPE_STRING, offset + l, NULL); +} + +void add_phandle_marker(struct dt_info *dti, struct property *prop, unsigned int offset) +{ + cell_t phandle; + struct node *refn; + char *ref; + + if (prop->val.len < offset + 4) { + if (quiet < 1) + fprintf(stderr, + "Warning: property %s too short to contain a phandle at offset %u\n", + prop->name, offset); + return; + } + + phandle = dtb_ld32(prop->val.val + offset); + refn = get_node_by_phandle(dti->dt, phandle); + + if (!refn) { + if (quiet < 1) + fprintf(stderr, + "Warning: node referenced by phandle 0x%x in property %s not found\n", + phandle, prop->name); + return; + } + + if (refn->labels) + ref = refn->labels->label; + else + ref = refn->fullpath; + + add_marker(&prop->val.markers, REF_PHANDLE, offset, ref); +} + +static enum markertype guess_value_type(struct property *prop, unsigned int offset, int len) +{ + const char *p = prop->val.val + offset; + int nnotstring = 0, nnul = 0; + int i; + + for (i = 0; i < len; i++) { + if (! isstring(p[i])) + nnotstring++; + if (p[i] == '\0') + nnul++; + } + + if ((p[len-1] == '\0') && (nnotstring == 0) && (nnul <= len - nnul)) { + if (nnul > 1) + add_string_markers(prop, offset, len); + return TYPE_STRING; + } else if ((len % sizeof(cell_t)) == 0) { + return TYPE_UINT32; + } + + return TYPE_UINT8; +} + +static void guess_type_markers(struct property *prop) +{ + struct marker **m = &prop->val.markers; + unsigned int offset = 0; + + for (m = &prop->val.markers; *m; m = &((*m)->next)) { + if (is_type_marker((*m)->type)) + /* assume the whole property is already marked */ + return; + + if ((*m)->offset > offset) { + m = add_marker(m, guess_value_type(prop, offset, (*m)->offset - offset), + offset, NULL); + + offset = (*m)->offset; + } + + if ((*m)->type == REF_PHANDLE) { + m = add_marker(m, TYPE_UINT32, offset, NULL); + offset += 4; + } + } + + if (offset < prop->val.len) + add_marker(m, guess_value_type(prop, offset, prop->val.len - offset), + offset, NULL); +} + +static void write_propval(FILE *f, struct property *prop) +{ + size_t len = prop->val.len; + struct marker *m; + enum markertype emit_type = TYPE_NONE; + char *srcstr; + + if (len == 0) { + fprintf(f, ";"); + if (annotate) { + srcstr = srcpos_string_first(prop->srcpos, annotate); + if (srcstr) { + fprintf(f, " /* %s */", srcstr); + free(srcstr); + } + } + fprintf(f, "\n"); + return; + } + + fprintf(f, " ="); + + guess_type_markers(prop); + m = prop->val.markers; + + for_each_marker(m) { + size_t chunk_len = (m->next ? m->next->offset : len) - m->offset; + size_t data_len = type_marker_length(m) ? : len - m->offset; + const char *p = &prop->val.val[m->offset]; + struct marker *m_phandle; + + if (is_type_marker(m->type)) { + emit_type = m->type; + fprintf(f, " %s", delim_start[emit_type]); + } else if (m->type == LABEL) + fprintf(f, " %s:", m->ref); + + if (emit_type == TYPE_NONE || chunk_len == 0) + continue; + + switch(emit_type) { + case TYPE_UINT16: + write_propval_int(f, p, chunk_len, 2); + break; + case TYPE_UINT32: + m_phandle = prop->val.markers; + for_each_marker_of_type(m_phandle, REF_PHANDLE) + if (m->offset == m_phandle->offset) + break; + + if (m_phandle) { + if (m_phandle->ref[0] == '/') + fprintf(f, "&{%s}", m_phandle->ref); + else + fprintf(f, "&%s", m_phandle->ref); + if (chunk_len > 4) { + fputc(' ', f); + write_propval_int(f, p + 4, chunk_len - 4, 4); + } + } 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); + break; + case TYPE_STRING: + write_propval_string(f, p, chunk_len); + break; + default: + write_propval_int(f, p, chunk_len, 1); + } + + if (chunk_len == data_len) { + size_t pos = m->offset + chunk_len; + fprintf(f, pos == len ? "%s" : "%s,", + delim_end[emit_type] ? : ""); + emit_type = TYPE_NONE; + } + } + fprintf(f, ";"); + if (annotate) { + srcstr = srcpos_string_first(prop->srcpos, annotate); + if (srcstr) { + fprintf(f, " /* %s */", srcstr); + free(srcstr); + } + } + fprintf(f, "\n"); +} + +static void write_tree_source_node(FILE *f, struct node *tree, int level) +{ + struct property *prop; + struct node *child; + struct label *l; + char *srcstr; + + write_prefix(f, level); + for_each_label(tree->labels, l) + fprintf(f, "%s: ", l->label); + if (tree->name && (*tree->name)) + fprintf(f, "%s {", tree->name); + else + fprintf(f, "/ {"); + + if (annotate) { + srcstr = srcpos_string_first(tree->srcpos, annotate); + if (srcstr) { + fprintf(f, " /* %s */", srcstr); + free(srcstr); + } + } + fprintf(f, "\n"); + + for_each_property(tree, prop) { + write_prefix(f, level+1); + for_each_label(prop->labels, l) + fprintf(f, "%s: ", l->label); + fprintf(f, "%s", prop->name); + write_propval(f, prop); + } + for_each_child(tree, child) { + fprintf(f, "\n"); + write_tree_source_node(f, child, level+1); + } + write_prefix(f, level); + fprintf(f, "};"); + if (annotate) { + srcstr = srcpos_string_last(tree->srcpos, annotate); + if (srcstr) { + fprintf(f, " /* %s */", srcstr); + free(srcstr); + } + } + fprintf(f, "\n"); +} + +void dt_to_source(FILE *f, struct dt_info *dti) +{ + struct reserve_info *re; + + fprintf(f, "/dts-v1/;\n"); + if (dti->dtsflags & DTSF_PLUGIN) + fprintf(f, "/plugin/;\n"); + fprintf(f, "\n"); + + for (re = dti->reservelist; re; re = re->next) { + struct label *l; + + for_each_label(re->labels, l) + fprintf(f, "%s: ", l->label); + fprintf(f, "/memreserve/\t0x%016llx 0x%016llx;\n", + (unsigned long long)re->address, + (unsigned long long)re->size); + } + + write_tree_source_node(f, dti->dt, 0); +} diff --git a/scripts/dtc/update-dtc-source.sh b/scripts/dtc/update-dtc-source.sh new file mode 100755 index 000000000..946275415 --- /dev/null +++ b/scripts/dtc/update-dtc-source.sh @@ -0,0 +1,80 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Simple script to update the version of DTC carried by the Linux kernel +# +# This script assumes that the dtc and the linux git trees are in the +# same directory. After building dtc in the dtc directory, it copies the +# source files and generated source file(s) into the scripts/dtc directory +# in the kernel and creates a git commit updating them to the new +# version. +# +# Usage: from the top level Linux source tree, run: +# $ ./scripts/dtc/update-dtc-source.sh +# +# The script will change into the dtc tree, build and test dtc, copy the +# relevant files into the kernel tree and create a git commit. The commit +# message will need to be modified to reflect the version of DTC being +# imported +# +# TODO: +# This script is pretty basic, but it is seldom used so a few manual tasks +# aren't a big deal. If anyone is interested in making it more robust, the +# the following would be nice: +# * Actually fail to complete if any testcase fails. +# - The dtc "make check" target needs to return a failure +# * Extract the version number from the dtc repo for the commit message +# * Build dtc in the kernel tree +# * run 'make check" on dtc built from the kernel tree + +set -ev + +DTC_UPSTREAM_PATH=`pwd`/../dtc +DTC_LINUX_PATH=`pwd`/scripts/dtc + +DTC_SOURCE="checks.c data.c dtc.c dtc.h flattree.c fstree.c livetree.c srcpos.c \ + srcpos.h treesource.c util.c util.h version_gen.h \ + dtc-lexer.l dtc-parser.y" +LIBFDT_SOURCE="fdt.c fdt.h fdt_addresses.c fdt_empty_tree.c \ + fdt_overlay.c fdt_ro.c fdt_rw.c fdt_strerror.c fdt_sw.c \ + fdt_wip.c libfdt.h libfdt_env.h libfdt_internal.h" +FDTOVERLAY_SOURCE=fdtoverlay.c + +get_last_dtc_version() { + git log --oneline scripts/dtc/ | grep 'upstream' | head -1 | sed -e 's/^.* \(.*\)/\1/' +} + +last_dtc_ver=$(get_last_dtc_version) + +# Build DTC +cd $DTC_UPSTREAM_PATH +make clean +make check +dtc_version=$(git describe HEAD) +dtc_log=$(git log --oneline ${last_dtc_ver}..) + + +# Copy the files into the Linux tree +cd $DTC_LINUX_PATH +for f in $DTC_SOURCE $FDTOVERLAY_SOURCE; do + cp ${DTC_UPSTREAM_PATH}/${f} ${f} + git add ${f} +done +for f in $LIBFDT_SOURCE; do + cp ${DTC_UPSTREAM_PATH}/libfdt/${f} libfdt/${f} + git add libfdt/${f} +done + +sed -i -- 's/#include <libfdt_env.h>/#include "libfdt_env.h"/g' ./libfdt/libfdt.h +sed -i -- 's/#include <fdt.h>/#include "fdt.h"/g' ./libfdt/libfdt.h +git add ./libfdt/libfdt.h + +commit_msg=$(cat << EOF +scripts/dtc: Update to upstream version ${dtc_version} + +This adds the following commits from upstream: + +${dtc_log} +EOF +) + +git commit -e -v -s -m "${commit_msg}" diff --git a/scripts/dtc/util.c b/scripts/dtc/util.c new file mode 100644 index 000000000..412592320 --- /dev/null +++ b/scripts/dtc/util.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2011 The Chromium Authors, All Rights Reserved. + * Copyright 2008 Jon Loeliger, Freescale Semiconductor, Inc. + * + * util_is_printable_string contributed by + * Pantelis Antoniou <pantelis.antoniou AT gmail.com> + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <assert.h> +#include <inttypes.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "libfdt.h" +#include "util.h" +#include "version_gen.h" + +void fprint_path_escaped(FILE *fp, const char *path) +{ + const char *p = path; + + while (*p) { + if (*p == ' ') { + fputc('\\', fp); + fputc(' ', fp); + } else { + fputc(*p, fp); + } + + p++; + } +} + +char *xstrdup(const char *s) +{ + int len = strlen(s) + 1; + char *d = xmalloc(len); + + memcpy(d, s, len); + + return d; +} + +char *xstrndup(const char *s, size_t n) +{ + size_t len = strnlen(s, n) + 1; + char *d = xmalloc(len); + + memcpy(d, s, len - 1); + d[len - 1] = '\0'; + + return d; +} + +int xavsprintf_append(char **strp, const char *fmt, va_list ap) +{ + int n, size = 0; /* start with 128 bytes */ + char *p; + va_list ap_copy; + + p = *strp; + if (p) + size = strlen(p); + + va_copy(ap_copy, ap); + n = vsnprintf(NULL, 0, fmt, ap_copy) + 1; + va_end(ap_copy); + + p = xrealloc(p, size + n); + + n = vsnprintf(p + size, n, fmt, ap); + + *strp = p; + return strlen(p); +} + +int xasprintf_append(char **strp, const char *fmt, ...) +{ + int n; + va_list ap; + + va_start(ap, fmt); + n = xavsprintf_append(strp, fmt, ap); + va_end(ap); + + return n; +} + +int xasprintf(char **strp, const char *fmt, ...) +{ + int n; + va_list ap; + + *strp = NULL; + + va_start(ap, fmt); + n = xavsprintf_append(strp, fmt, ap); + va_end(ap); + + return n; +} + +char *join_path(const char *path, const char *name) +{ + int lenp = strlen(path); + int lenn = strlen(name); + int len; + int needslash = 1; + char *str; + + len = lenp + lenn + 2; + if ((lenp > 0) && (path[lenp-1] == '/')) { + needslash = 0; + len--; + } + + str = xmalloc(len); + memcpy(str, path, lenp); + if (needslash) { + str[lenp] = '/'; + lenp++; + } + memcpy(str+lenp, name, lenn+1); + return str; +} + +bool util_is_printable_string(const void *data, int len) +{ + const char *s = data; + const char *ss, *se; + + /* zero length is not */ + if (len == 0) + return 0; + + /* must terminate with zero */ + if (s[len - 1] != '\0') + return 0; + + se = s + len; + + while (s < se) { + ss = s; + while (s < se && *s && isprint((unsigned char)*s)) + s++; + + /* not zero, or not done yet */ + if (*s != '\0' || s == ss) + return 0; + + s++; + } + + return 1; +} + +/* + * Parse a octal encoded character starting at index i in string s. The + * resulting character will be returned and the index i will be updated to + * point at the character directly after the end of the encoding, this may be + * the '\0' terminator of the string. + */ +static char get_oct_char(const char *s, int *i) +{ + char x[4]; + char *endx; + long val; + + x[3] = '\0'; + strncpy(x, s + *i, 3); + + val = strtol(x, &endx, 8); + + assert(endx > x); + + (*i) += endx - x; + return val; +} + +/* + * Parse a hexadecimal encoded character starting at index i in string s. The + * resulting character will be returned and the index i will be updated to + * point at the character directly after the end of the encoding, this may be + * the '\0' terminator of the string. + */ +static char get_hex_char(const char *s, int *i) +{ + char x[3]; + char *endx; + long val; + + x[2] = '\0'; + strncpy(x, s + *i, 2); + + val = strtol(x, &endx, 16); + if (!(endx > x)) + die("\\x used with no following hex digits\n"); + + (*i) += endx - x; + return val; +} + +char get_escape_char(const char *s, int *i) +{ + char c = s[*i]; + int j = *i + 1; + char val; + + switch (c) { + case 'a': + val = '\a'; + break; + case 'b': + val = '\b'; + break; + case 't': + val = '\t'; + break; + case 'n': + val = '\n'; + break; + case 'v': + val = '\v'; + break; + case 'f': + val = '\f'; + break; + case 'r': + val = '\r'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + j--; /* need to re-read the first digit as + * part of the octal value */ + val = get_oct_char(s, &j); + break; + case 'x': + val = get_hex_char(s, &j); + break; + default: + val = c; + } + + (*i) = j; + return val; +} + +int utilfdt_read_err(const char *filename, char **buffp, size_t *len) +{ + int fd = 0; /* assume stdin */ + char *buf = NULL; + size_t bufsize = 1024, offset = 0; + int ret = 0; + + *buffp = NULL; + if (strcmp(filename, "-") != 0) { + fd = open(filename, O_RDONLY); + if (fd < 0) + return errno; + } + + /* Loop until we have read everything */ + buf = xmalloc(bufsize); + do { + /* Expand the buffer to hold the next chunk */ + if (offset == bufsize) { + bufsize *= 2; + buf = xrealloc(buf, bufsize); + } + + ret = read(fd, &buf[offset], bufsize - offset); + if (ret < 0) { + ret = errno; + break; + } + offset += ret; + } while (ret != 0); + + /* Clean up, including closing stdin; return errno on error */ + close(fd); + if (ret) + free(buf); + else + *buffp = buf; + if (len) + *len = bufsize; + return ret; +} + +char *utilfdt_read(const char *filename, size_t *len) +{ + char *buff; + int ret = utilfdt_read_err(filename, &buff, len); + + if (ret) { + fprintf(stderr, "Couldn't open blob from '%s': %s\n", filename, + strerror(ret)); + return NULL; + } + /* Successful read */ + return buff; +} + +int utilfdt_write_err(const char *filename, const void *blob) +{ + int fd = 1; /* assume stdout */ + int totalsize; + int offset; + int ret = 0; + const char *ptr = blob; + + if (strcmp(filename, "-") != 0) { + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + return errno; + } + + totalsize = fdt_totalsize(blob); + offset = 0; + + while (offset < totalsize) { + ret = write(fd, ptr + offset, totalsize - offset); + if (ret < 0) { + ret = -errno; + break; + } + offset += ret; + } + /* Close the file/stdin; return errno on error */ + if (fd != 1) + close(fd); + return ret < 0 ? -ret : 0; +} + + +int utilfdt_write(const char *filename, const void *blob) +{ + int ret = utilfdt_write_err(filename, blob); + + if (ret) { + fprintf(stderr, "Couldn't write blob to '%s': %s\n", filename, + strerror(ret)); + } + return ret ? -1 : 0; +} + +int utilfdt_decode_type(const char *fmt, int *type, int *size) +{ + int qualifier = 0; + + if (!*fmt) + return -1; + + /* get the conversion qualifier */ + *size = -1; + if (strchr("hlLb", *fmt)) { + qualifier = *fmt++; + if (qualifier == *fmt) { + switch (*fmt++) { +/* TODO: case 'l': qualifier = 'L'; break;*/ + case 'h': + qualifier = 'b'; + break; + } + } + } + + /* we should now have a type */ + if ((*fmt == '\0') || !strchr("iuxsr", *fmt)) + return -1; + + /* convert qualifier (bhL) to byte size */ + if (*fmt != 's' && *fmt != 'r') + *size = qualifier == 'b' ? 1 : + qualifier == 'h' ? 2 : + qualifier == 'l' ? 4 : -1; + *type = *fmt++; + + /* that should be it! */ + if (*fmt) + return -1; + return 0; +} + +void utilfdt_print_data(const char *data, int len) +{ + int i; + const char *s; + + /* no data, don't print */ + if (len == 0) + return; + + if (util_is_printable_string(data, len)) { + printf(" = "); + + s = data; + do { + printf("\"%s\"", s); + s += strlen(s) + 1; + if (s < data + len) + printf(", "); + } while (s < data + len); + + } else if ((len % 4) == 0) { + const fdt32_t *cell = (const fdt32_t *)data; + + printf(" = <"); + for (i = 0, len /= 4; i < len; i++) + printf("0x%08" PRIx32 "%s", fdt32_to_cpu(cell[i]), + i < (len - 1) ? " " : ""); + printf(">"); + } else { + const unsigned char *p = (const unsigned char *)data; + printf(" = ["); + for (i = 0; i < len; i++) + printf("%02x%s", *p++, i < len - 1 ? " " : ""); + printf("]"); + } +} + +void NORETURN util_version(void) +{ + printf("Version: %s\n", DTC_VERSION); + exit(0); +} + +void NORETURN util_usage(const char *errmsg, const char *synopsis, + const char *short_opts, + struct option const long_opts[], + const char * const opts_help[]) +{ + FILE *fp = errmsg ? stderr : stdout; + const char a_arg[] = "<arg>"; + size_t a_arg_len = strlen(a_arg) + 1; + size_t i; + int optlen; + + fprintf(fp, + "Usage: %s\n" + "\n" + "Options: -[%s]\n", synopsis, short_opts); + + /* prescan the --long opt length to auto-align */ + optlen = 0; + for (i = 0; long_opts[i].name; ++i) { + /* +1 is for space between --opt and help text */ + int l = strlen(long_opts[i].name) + 1; + if (long_opts[i].has_arg == a_argument) + l += a_arg_len; + if (optlen < l) + optlen = l; + } + + for (i = 0; long_opts[i].name; ++i) { + /* helps when adding new applets or options */ + assert(opts_help[i] != NULL); + + /* first output the short flag if it has one */ + if (long_opts[i].val > '~') + fprintf(fp, " "); + else + fprintf(fp, " -%c, ", long_opts[i].val); + + /* then the long flag */ + if (long_opts[i].has_arg == no_argument) + fprintf(fp, "--%-*s", optlen, long_opts[i].name); + else + fprintf(fp, "--%s %s%*s", long_opts[i].name, a_arg, + (int)(optlen - strlen(long_opts[i].name) - a_arg_len), ""); + + /* finally the help text */ + fprintf(fp, "%s\n", opts_help[i]); + } + + if (errmsg) { + fprintf(fp, "\nError: %s\n", errmsg); + exit(EXIT_FAILURE); + } else + exit(EXIT_SUCCESS); +} diff --git a/scripts/dtc/util.h b/scripts/dtc/util.h new file mode 100644 index 000000000..800f2e2c5 --- /dev/null +++ b/scripts/dtc/util.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef UTIL_H +#define UTIL_H + +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <getopt.h> + +/* + * Copyright 2011 The Chromium Authors, All Rights Reserved. + * Copyright 2008 Jon Loeliger, Freescale Semiconductor, Inc. + */ + +#ifdef __GNUC__ +#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))) +#endif +#define NORETURN __attribute__((noreturn)) +#else +#define PRINTF(i, j) +#define NORETURN +#endif + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define stringify(s) stringify_(s) +#define stringify_(s) #s + +static inline void NORETURN PRINTF(1, 2) die(const char *str, ...) +{ + va_list ap; + + va_start(ap, str); + fprintf(stderr, "FATAL ERROR: "); + vfprintf(stderr, str, ap); + va_end(ap); + exit(1); +} + +/** + * Writes path to fp, escaping spaces with a backslash. + */ +void fprint_path_escaped(FILE *fp, const char *path); + +static inline void *xmalloc(size_t len) +{ + void *new = malloc(len); + + if (!new) + die("malloc() failed\n"); + + return new; +} + +static inline void *xrealloc(void *p, size_t len) +{ + void *new = realloc(p, len); + + if (!new) + die("realloc() failed (len=%zd)\n", len); + + return new; +} + +extern char *xstrdup(const char *s); +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 PRINTF(2, 0) xavsprintf_append(char **strp, const char *fmt, va_list ap); +extern char *join_path(const char *path, const char *name); + +/** + * Check a property of a given length to see if it is all printable and + * has a valid terminator. The property can contain either a single string, + * or multiple strings each of non-zero length. + * + * @param data The string to check + * @param len The string length including terminator + * @return 1 if a valid printable string, 0 if not + */ +bool util_is_printable_string(const void *data, int len); + +/* + * Parse an escaped character starting at index i in string s. The resulting + * character will be returned and the index i will be updated to point at the + * character directly after the end of the encoding, this may be the '\0' + * terminator of the string. + */ +char get_escape_char(const char *s, int *i); + +/** + * Read a device tree file into a buffer. This will report any errors on + * stderr. + * + * @param filename The filename to read, or - for stdin + * @param len If non-NULL, the amount of data we managed to read + * @return Pointer to allocated buffer containing fdt, or NULL on error + */ +char *utilfdt_read(const char *filename, size_t *len); + +/** + * Read a device tree file into a buffer. Does not report errors, but only + * returns them. The value returned can be passed to strerror() to obtain + * an error message for the user. + * + * @param filename The filename to read, or - for stdin + * @param buffp Returns pointer to buffer containing fdt + * @param len If non-NULL, the amount of data we managed to read + * @return 0 if ok, else an errno value representing the error + */ +int utilfdt_read_err(const char *filename, char **buffp, size_t *len); + +/** + * Write a device tree buffer to a file. This will report any errors on + * stderr. + * + * @param filename The filename to write, or - for stdout + * @param blob Pointer to buffer containing fdt + * @return 0 if ok, -1 on error + */ +int utilfdt_write(const char *filename, const void *blob); + +/** + * Write a device tree buffer to a file. Does not report errors, but only + * returns them. The value returned can be passed to strerror() to obtain + * an error message for the user. + * + * @param filename The filename to write, or - for stdout + * @param blob Pointer to buffer containing fdt + * @return 0 if ok, else an errno value representing the error + */ +int utilfdt_write_err(const char *filename, const void *blob); + +/** + * Decode a data type string. The purpose of this string + * + * The string consists of an optional character followed by the type: + * Modifier characters: + * hh or b 1 byte + * h 2 byte + * l 4 byte, default + * + * Type character: + * s string + * i signed integer + * u unsigned integer + * x hex + * r raw + * + * TODO: Implement ll modifier (8 bytes) + * TODO: Implement o type (octal) + * + * @param fmt Format string to process + * @param type Returns type found(s/d/u/x), or 0 if none + * @param size Returns size found(1,2,4,8) or 4 if none + * @return 0 if ok, -1 on error (no type given, or other invalid format) + */ +int utilfdt_decode_type(const char *fmt, int *type, int *size); + +/* + * This is a usage message fragment for the -t option. It is the format + * supported by utilfdt_decode_type. + */ + +#define USAGE_TYPE_MSG \ + "<type>\ts=string, i=int, u=unsigned, x=hex, r=raw\n" \ + "\tOptional modifier prefix:\n" \ + "\t\thh or b=byte, h=2 byte, l=4 byte (default)"; + +/** + * Print property data in a readable format to stdout + * + * Properties that look like strings will be printed as strings. Otherwise + * the data will be displayed either as cells (if len is a multiple of 4 + * bytes) or bytes. + * + * If len is 0 then this function does nothing. + * + * @param data Pointers to property data + * @param len Length of property data + */ +void utilfdt_print_data(const char *data, int len); + +/** + * Show source version and exit + */ +void NORETURN util_version(void); + +/** + * Show usage and exit + * + * This helps standardize the output of various utils. You most likely want + * to use the usage() helper below rather than call this. + * + * @param errmsg If non-NULL, an error message to display + * @param synopsis The initial example usage text (and possible examples) + * @param short_opts The string of short options + * @param long_opts The structure of long options + * @param opts_help An array of help strings (should align with long_opts) + */ +void NORETURN util_usage(const char *errmsg, const char *synopsis, + const char *short_opts, + struct option const long_opts[], + const char * const opts_help[]); + +/** + * Show usage and exit + * + * If you name all your usage variables with usage_xxx, then you can call this + * help macro rather than expanding all arguments yourself. + * + * @param errmsg If non-NULL, an error message to display + */ +#define usage(errmsg) \ + util_usage(errmsg, usage_synopsis, usage_short_opts, \ + usage_long_opts, usage_opts_help) + +/** + * Call getopt_long() with standard options + * + * Since all util code runs getopt in the same way, provide a helper. + */ +#define util_getopt_long() getopt_long(argc, argv, usage_short_opts, \ + usage_long_opts, NULL) + +/* Helper for aligning long_opts array */ +#define a_argument required_argument + +/* Helper for usage_short_opts string constant */ +#define USAGE_COMMON_SHORT_OPTS "hV" + +/* Helper for usage_long_opts option array */ +#define USAGE_COMMON_LONG_OPTS \ + {"help", no_argument, NULL, 'h'}, \ + {"version", no_argument, NULL, 'V'}, \ + {NULL, no_argument, NULL, 0x0} + +/* Helper for usage_opts_help array */ +#define USAGE_COMMON_OPTS_HELP \ + "Print this help and exit", \ + "Print version and exit", \ + NULL + +/* Helper for getopt case statements */ +#define case_USAGE_COMMON_FLAGS \ + case 'h': usage(NULL); \ + case 'V': util_version(); \ + case '?': usage("unknown option"); + +#endif /* UTIL_H */ diff --git a/scripts/dtc/version_gen.h b/scripts/dtc/version_gen.h new file mode 100644 index 000000000..122e684e7 --- /dev/null +++ b/scripts/dtc/version_gen.h @@ -0,0 +1 @@ +#define DTC_VERSION "DTC 1.7.2-g53373d13" diff --git a/scripts/dtc/yamltree.c b/scripts/dtc/yamltree.c new file mode 100644 index 000000000..55908c829 --- /dev/null +++ b/scripts/dtc/yamltree.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright Linaro, Ltd. 2018 + * (C) Copyright Arm Holdings. 2017 + * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. + */ + +#include <stdlib.h> +#include <yaml.h> +#include "dtc.h" +#include "srcpos.h" + +char *yaml_error_name[] = { + [YAML_NO_ERROR] = "no error", + [YAML_MEMORY_ERROR] = "memory error", + [YAML_READER_ERROR] = "reader error", + [YAML_SCANNER_ERROR] = "scanner error", + [YAML_PARSER_ERROR] = "parser error", + [YAML_COMPOSER_ERROR] = "composer error", + [YAML_WRITER_ERROR] = "writer error", + [YAML_EMITTER_ERROR] = "emitter error", +}; + +#define yaml_emitter_emit_or_die(emitter, event) ( \ +{ \ + if (!yaml_emitter_emit(emitter, event)) \ + die("yaml '%s': %s in %s, line %i\n", \ + yaml_error_name[(emitter)->error], \ + (emitter)->problem, __func__, __LINE__); \ +}) + +static void yaml_propval_int(yaml_emitter_t *emitter, struct marker *markers, + char *data, unsigned int seq_offset, unsigned int len, int width) +{ + yaml_event_t event; + void *tag; + unsigned int off; + + switch(width) { + case 1: tag = "!u8"; break; + case 2: tag = "!u16"; break; + case 4: tag = "!u32"; break; + case 8: tag = "!u64"; break; + default: + die("Invalid width %i", width); + } + assert(len % width == 0); + + yaml_sequence_start_event_initialize(&event, NULL, + (yaml_char_t *)tag, width == 4, YAML_FLOW_SEQUENCE_STYLE); + yaml_emitter_emit_or_die(emitter, &event); + + for (off = 0; off < len; off += width) { + char buf[32]; + struct marker *m; + bool is_phandle = false; + + switch(width) { + case 1: + sprintf(buf, "0x%"PRIx8, *(uint8_t*)(data + off)); + break; + case 2: + sprintf(buf, "0x%"PRIx16, dtb_ld16(data + off)); + break; + case 4: + sprintf(buf, "0x%"PRIx32, dtb_ld32(data + off)); + m = markers; + is_phandle = false; + for_each_marker_of_type(m, REF_PHANDLE) { + if (m->offset == (seq_offset + off)) { + is_phandle = true; + break; + } + } + break; + case 8: + sprintf(buf, "0x%"PRIx64, dtb_ld64(data + off)); + break; + } + + if (is_phandle) + yaml_scalar_event_initialize(&event, NULL, + (yaml_char_t*)"!phandle", (yaml_char_t *)buf, + strlen(buf), 0, 0, YAML_PLAIN_SCALAR_STYLE); + else + yaml_scalar_event_initialize(&event, NULL, + (yaml_char_t*)YAML_INT_TAG, (yaml_char_t *)buf, + strlen(buf), 1, 1, YAML_PLAIN_SCALAR_STYLE); + yaml_emitter_emit_or_die(emitter, &event); + } + + yaml_sequence_end_event_initialize(&event); + yaml_emitter_emit_or_die(emitter, &event); +} + +static void yaml_propval_string(yaml_emitter_t *emitter, char *str, int len) +{ + yaml_event_t event; + int i; + + assert(str[len-1] == '\0'); + + /* Make sure the entire string is in the lower 7-bit ascii range */ + for (i = 0; i < len; i++) + assert(isascii(str[i])); + + yaml_scalar_event_initialize(&event, NULL, + (yaml_char_t *)YAML_STR_TAG, (yaml_char_t*)str, + len-1, 0, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE); + yaml_emitter_emit_or_die(emitter, &event); +} + +static void yaml_propval(yaml_emitter_t *emitter, struct property *prop) +{ + yaml_event_t event; + unsigned int len = prop->val.len; + struct marker *m = prop->val.markers; + struct marker *markers = prop->val.markers; + + /* Emit the property name */ + yaml_scalar_event_initialize(&event, NULL, + (yaml_char_t *)YAML_STR_TAG, (yaml_char_t*)prop->name, + strlen(prop->name), 1, 1, YAML_PLAIN_SCALAR_STYLE); + yaml_emitter_emit_or_die(emitter, &event); + + /* Boolean properties are easiest to deal with. Length is zero, so just emit 'true' */ + if (len == 0) { + yaml_scalar_event_initialize(&event, NULL, + (yaml_char_t *)YAML_BOOL_TAG, + (yaml_char_t*)"true", + strlen("true"), 1, 0, YAML_PLAIN_SCALAR_STYLE); + yaml_emitter_emit_or_die(emitter, &event); + return; + } + + if (!m) + die("No markers present in property '%s' value\n", prop->name); + + yaml_sequence_start_event_initialize(&event, NULL, + (yaml_char_t *)YAML_SEQ_TAG, 1, YAML_FLOW_SEQUENCE_STYLE); + yaml_emitter_emit_or_die(emitter, &event); + + for_each_marker(m) { + int chunk_len; + char *data = &prop->val.val[m->offset]; + + if (m->type < TYPE_UINT8) + continue; + + chunk_len = type_marker_length(m) ? : len; + assert(chunk_len > 0); + len -= chunk_len; + + switch(m->type) { + case TYPE_UINT16: + yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 2); + break; + case TYPE_UINT32: + yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 4); + break; + case TYPE_UINT64: + yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 8); + break; + case TYPE_STRING: + yaml_propval_string(emitter, data, chunk_len); + break; + default: + yaml_propval_int(emitter, markers, data, m->offset, chunk_len, 1); + break; + } + } + + yaml_sequence_end_event_initialize(&event); + yaml_emitter_emit_or_die(emitter, &event); +} + + +static void yaml_tree(struct node *tree, yaml_emitter_t *emitter) +{ + struct property *prop; + struct node *child; + yaml_event_t event; + + if (tree->deleted) + return; + + yaml_mapping_start_event_initialize(&event, NULL, + (yaml_char_t *)YAML_MAP_TAG, 1, YAML_ANY_MAPPING_STYLE); + yaml_emitter_emit_or_die(emitter, &event); + + for_each_property(tree, prop) + yaml_propval(emitter, prop); + + /* Loop over all the children, emitting them into the map */ + for_each_child(tree, child) { + yaml_scalar_event_initialize(&event, NULL, + (yaml_char_t *)YAML_STR_TAG, (yaml_char_t*)child->name, + strlen(child->name), 1, 0, YAML_PLAIN_SCALAR_STYLE); + yaml_emitter_emit_or_die(emitter, &event); + yaml_tree(child, emitter); + } + + yaml_mapping_end_event_initialize(&event); + yaml_emitter_emit_or_die(emitter, &event); +} + +void dt_to_yaml(FILE *f, struct dt_info *dti) +{ + yaml_emitter_t emitter; + yaml_event_t event; + + yaml_emitter_initialize(&emitter); + yaml_emitter_set_output_file(&emitter, f); + yaml_stream_start_event_initialize(&event, YAML_UTF8_ENCODING); + yaml_emitter_emit_or_die(&emitter, &event); + + yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0); + yaml_emitter_emit_or_die(&emitter, &event); + + yaml_sequence_start_event_initialize(&event, NULL, (yaml_char_t *)YAML_SEQ_TAG, 1, YAML_ANY_SEQUENCE_STYLE); + yaml_emitter_emit_or_die(&emitter, &event); + + yaml_tree(dti->dt, &emitter); + + yaml_sequence_end_event_initialize(&event); + yaml_emitter_emit_or_die(&emitter, &event); + + yaml_document_end_event_initialize(&event, 0); + yaml_emitter_emit_or_die(&emitter, &event); + + yaml_stream_end_event_initialize(&event); + yaml_emitter_emit_or_die(&emitter, &event); + + yaml_emitter_delete(&emitter); +} diff --git a/scripts/dummy-tools/dummy-plugin-dir/include/plugin-version.h b/scripts/dummy-tools/dummy-plugin-dir/include/plugin-version.h new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/dummy-tools/gcc b/scripts/dummy-tools/gcc new file mode 100755 index 000000000..e6c41427c --- /dev/null +++ b/scripts/dummy-tools/gcc @@ -0,0 +1,116 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Staring v4.18, Kconfig evaluates compiler capabilities, and hides CONFIG +# options your compiler does not support. This works well if you configure and +# build the kernel on the same host machine. +# +# It is inconvenient if you prepare the .config that is carried to a different +# build environment (typically this happens when you package the kernel for +# distros) because using a different compiler potentially produces different +# CONFIG options than the real build environment. So, you probably want to make +# as many options visible as possible. In other words, you need to create a +# super-set of CONFIG options that cover any build environment. If some of the +# CONFIG options turned out to be unsupported on the build machine, they are +# automatically disabled by the nature of Kconfig. +# +# However, it is not feasible to get a full-featured compiler for every arch. +# Hence these dummy toolchains to make all compiler tests pass. +# +# Usage: +# +# From the top directory of the source tree, run +# +# $ make CROSS_COMPILE=scripts/dummy-tools/ oldconfig +# +# Most of compiler features are tested by cc-option, which simply checks the +# exit code of $(CC). This script does nothing and just exits with 0 in most +# cases. So, $(cc-option, ...) is evaluated as 'y'. +# +# This scripts caters to more checks; handle --version and pre-process __GNUC__ +# etc. to pretend to be GCC, and also do right things to satisfy some scripts. + +# Check if the first parameter appears in the rest. Succeeds if found. +# This helper is useful if a particular option was passed to this script. +# Typically used like this: +# arg_contain <word-you-are-searching-for> "$@" +arg_contain () +{ + search="$1" + shift + + while [ $# -gt 0 ] + do + if [ "$search" = "$1" ]; then + return 0 + fi + shift + done + + return 1 +} + +# To set CONFIG_CC_IS_GCC=y +if arg_contain --version "$@"; then + echo "gcc (scripts/dummy-tools/gcc)" + exit 0 +fi + +if arg_contain -E "$@"; then + # For scripts/cc-version.sh; This emulates GCC 20.0.0 + if arg_contain - "$@"; then + sed -n '/^GCC/{s/__GNUC__/20/; s/__GNUC_MINOR__/0/; s/__GNUC_PATCHLEVEL__/0/; p;}; s/__LONG_DOUBLE_128__/1/ p' + exit 0 + else + echo "no input files" >&2 + exit 1 + fi +fi + +# To set CONFIG_AS_IS_GNU +if arg_contain -Wa,--version "$@"; then + echo "GNU assembler (scripts/dummy-tools) 2.50" + exit 0 +fi + +if arg_contain -S "$@"; then + # For scripts/gcc-x86-*-has-stack-protector.sh + if arg_contain -fstack-protector "$@"; then + if arg_contain -mstack-protector-guard-reg=fs "$@"; then + echo "%fs" + else + echo "%gs" + fi + exit 0 + fi + + # For arch/powerpc/tools/gcc-check-mprofile-kernel.sh + if arg_contain -m64 "$@" && arg_contain -mprofile-kernel "$@"; then + if ! test -t 0 && ! grep -q notrace; then + echo "_mcount" + fi + exit 0 + fi + + # For arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh + if arg_contain -m64 "$@" && arg_contain -fpatchable-function-entry=2 "$@"; then + echo "func:" + echo ".section __patchable_function_entries" + echo ".localentry" + echo " nop" + echo " nop" + exit 0 + fi +fi + +# To set GCC_PLUGINS +if arg_contain -print-file-name=plugin "$@"; then + # Use $0 to find the in-tree dummy directory + echo "$(dirname "$(readlink -f "$0")")/dummy-plugin-dir" + exit 0 +fi + +# inverted return value +if arg_contain -D__SIZEOF_INT128__=0 "$@"; then + exit 1 +fi diff --git a/scripts/dummy-tools/ld b/scripts/dummy-tools/ld new file mode 100755 index 000000000..f68233050 --- /dev/null +++ b/scripts/dummy-tools/ld @@ -0,0 +1,30 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +# Dummy script that always succeeds. + +# Check if the first parameter appears in the rest. Succeeds if found. +# This helper is useful if a particular option was passed to this script. +# Typically used like this: +# arg_contain <word-you-are-searching-for> "$@" +arg_contain () +{ + search="$1" + shift + + while [ $# -gt 0 ] + do + if [ "$search" = "$1" ]; then + return 0 + fi + shift + done + + return 1 +} + +if arg_contain --version "$@" || arg_contain -v "$@"; then + progname=$(basename $0) + echo "GNU $progname (scripts/dummy-tools/$progname) 2.50" + exit 0 +fi diff --git a/scripts/dummy-tools/nm b/scripts/dummy-tools/nm new file mode 120000 index 000000000..c0648b38d --- /dev/null +++ b/scripts/dummy-tools/nm @@ -0,0 +1 @@ +ld \ No newline at end of file diff --git a/scripts/dummy-tools/objcopy b/scripts/dummy-tools/objcopy new file mode 120000 index 000000000..c0648b38d --- /dev/null +++ b/scripts/dummy-tools/objcopy @@ -0,0 +1 @@ +ld \ No newline at end of file diff --git a/scripts/dummy-tools/pahole b/scripts/dummy-tools/pahole new file mode 100755 index 000000000..53501a36f --- /dev/null +++ b/scripts/dummy-tools/pahole @@ -0,0 +1,4 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +echo v99.99 diff --git a/scripts/dummy-tools/python3 b/scripts/dummy-tools/python3 new file mode 100755 index 000000000..24c558486 --- /dev/null +++ b/scripts/dummy-tools/python3 @@ -0,0 +1,4 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +true diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c new file mode 100644 index 000000000..99869ff91 --- /dev/null +++ b/scripts/elf-parse.c @@ -0,0 +1,198 @@ +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "elf-parse.h" + +struct elf_funcs elf_parser; + +/* + * Get the whole file as a programming convenience in order to avoid + * malloc+lseek+read+free of many pieces. If successful, then mmap + * avoids copying unused pieces; else just read the whole file. + * Open for both read and write. + */ +static void *map_file(char const *fname, size_t *size) +{ + int fd; + struct stat sb; + void *addr = NULL; + + fd = open(fname, O_RDWR); + if (fd < 0) { + perror(fname); + return NULL; + } + if (fstat(fd, &sb) < 0) { + perror(fname); + goto out; + } + if (!S_ISREG(sb.st_mode)) { + fprintf(stderr, "not a regular file: %s\n", fname); + goto out; + } + + addr = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + fprintf(stderr, "Could not mmap file: %s\n", fname); + goto out; + } + + *size = sb.st_size; + +out: + close(fd); + return addr; +} + +static int elf_parse(const char *fname, void *addr, uint32_t types) +{ + Elf_Ehdr *ehdr = addr; + uint16_t type; + + switch (ehdr->e32.e_ident[EI_DATA]) { + case ELFDATA2LSB: + elf_parser.r = rle; + elf_parser.r2 = r2le; + elf_parser.r8 = r8le; + elf_parser.w = wle; + elf_parser.w8 = w8le; + break; + case ELFDATA2MSB: + elf_parser.r = rbe; + elf_parser.r2 = r2be; + elf_parser.r8 = r8be; + elf_parser.w = wbe; + elf_parser.w8 = w8be; + break; + default: + fprintf(stderr, "unrecognized ELF data encoding %d: %s\n", + ehdr->e32.e_ident[EI_DATA], fname); + return -1; + } + + if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 || + ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) { + fprintf(stderr, "unrecognized ELF file %s\n", fname); + return -1; + } + + type = elf_parser.r2(&ehdr->e32.e_type); + if (!((1 << type) & types)) { + fprintf(stderr, "Invalid ELF type file %s\n", fname); + return -1; + } + + switch (ehdr->e32.e_ident[EI_CLASS]) { + case ELFCLASS32: { + elf_parser.ehdr_shoff = ehdr32_shoff; + elf_parser.ehdr_shentsize = ehdr32_shentsize; + elf_parser.ehdr_shstrndx = ehdr32_shstrndx; + elf_parser.ehdr_shnum = ehdr32_shnum; + elf_parser.shdr_addr = shdr32_addr; + elf_parser.shdr_offset = shdr32_offset; + elf_parser.shdr_link = shdr32_link; + elf_parser.shdr_size = shdr32_size; + elf_parser.shdr_name = shdr32_name; + elf_parser.shdr_type = shdr32_type; + elf_parser.shdr_entsize = shdr32_entsize; + elf_parser.sym_type = sym32_type; + elf_parser.sym_name = sym32_name; + elf_parser.sym_value = sym32_value; + elf_parser.sym_shndx = sym32_shndx; + elf_parser.rela_offset = rela32_offset; + elf_parser.rela_info = rela32_info; + elf_parser.rela_addend = rela32_addend; + elf_parser.rela_write_addend = rela32_write_addend; + + if (elf_parser.r2(&ehdr->e32.e_ehsize) != sizeof(Elf32_Ehdr) || + elf_parser.r2(&ehdr->e32.e_shentsize) != sizeof(Elf32_Shdr)) { + fprintf(stderr, + "unrecognized ET_EXEC/ET_DYN file: %s\n", fname); + return -1; + } + + } + break; + case ELFCLASS64: { + elf_parser.ehdr_shoff = ehdr64_shoff; + elf_parser.ehdr_shentsize = ehdr64_shentsize; + elf_parser.ehdr_shstrndx = ehdr64_shstrndx; + elf_parser.ehdr_shnum = ehdr64_shnum; + elf_parser.shdr_addr = shdr64_addr; + elf_parser.shdr_offset = shdr64_offset; + elf_parser.shdr_link = shdr64_link; + elf_parser.shdr_size = shdr64_size; + elf_parser.shdr_name = shdr64_name; + elf_parser.shdr_type = shdr64_type; + elf_parser.shdr_entsize = shdr64_entsize; + elf_parser.sym_type = sym64_type; + elf_parser.sym_name = sym64_name; + elf_parser.sym_value = sym64_value; + elf_parser.sym_shndx = sym64_shndx; + elf_parser.rela_offset = rela64_offset; + elf_parser.rela_info = rela64_info; + elf_parser.rela_addend = rela64_addend; + elf_parser.rela_write_addend = rela64_write_addend; + + if (elf_parser.r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) || + elf_parser.r2(&ehdr->e64.e_shentsize) != sizeof(Elf64_Shdr)) { + fprintf(stderr, + "unrecognized ET_EXEC/ET_DYN file: %s\n", + fname); + return -1; + } + + } + break; + default: + fprintf(stderr, "unrecognized ELF class %d %s\n", + ehdr->e32.e_ident[EI_CLASS], fname); + return -1; + } + return 0; +} + +int elf_map_machine(void *addr) +{ + Elf_Ehdr *ehdr = addr; + + return elf_parser.r2(&ehdr->e32.e_machine); +} + +int elf_map_long_size(void *addr) +{ + Elf_Ehdr *ehdr = addr; + + return ehdr->e32.e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8; +} + +void *elf_map(char const *fname, size_t *size, uint32_t types) +{ + void *addr; + int ret; + + addr = map_file(fname, size); + if (!addr) + return NULL; + + ret = elf_parse(fname, addr, types); + if (ret < 0) { + elf_unmap(addr, *size); + return NULL; + } + + return addr; +} + +void elf_unmap(void *addr, size_t size) +{ + munmap(addr, size); +} diff --git a/scripts/elf-parse.h b/scripts/elf-parse.h new file mode 100644 index 000000000..f4411e030 --- /dev/null +++ b/scripts/elf-parse.h @@ -0,0 +1,305 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _SCRIPTS_ELF_PARSE_H +#define _SCRIPTS_ELF_PARSE_H + +#include <elf.h> + +#include <tools/be_byteshift.h> +#include <tools/le_byteshift.h> + +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; + +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); + uint32_t (*r)(const uint32_t *); + uint16_t (*r2)(const uint16_t *); + uint64_t (*r8)(const uint64_t *); + void (*w)(uint32_t, uint32_t *); + void (*w8)(uint64_t, uint64_t *); +}; + +extern struct elf_funcs elf_parser; + +static inline uint64_t ehdr64_shoff(Elf_Ehdr *ehdr) +{ + return elf_parser.r8(&ehdr->e64.e_shoff); +} + +static inline uint64_t ehdr32_shoff(Elf_Ehdr *ehdr) +{ + return elf_parser.r(&ehdr->e32.e_shoff); +} + +static inline uint64_t ehdr_shoff(Elf_Ehdr *ehdr) +{ + return elf_parser.ehdr_shoff(ehdr); +} + +#define EHDR_HALF(fn_name) \ +static inline uint16_t ehdr64_##fn_name(Elf_Ehdr *ehdr) \ +{ \ + return elf_parser.r2(&ehdr->e64.e_##fn_name); \ +} \ + \ +static inline uint16_t ehdr32_##fn_name(Elf_Ehdr *ehdr) \ +{ \ + return elf_parser.r2(&ehdr->e32.e_##fn_name); \ +} \ + \ +static inline uint16_t ehdr_##fn_name(Elf_Ehdr *ehdr) \ +{ \ + return elf_parser.ehdr_##fn_name(ehdr); \ +} + +EHDR_HALF(shentsize) +EHDR_HALF(shstrndx) +EHDR_HALF(shnum) + +#define SHDR_WORD(fn_name) \ +static inline uint32_t shdr64_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.r(&shdr->e64.sh_##fn_name); \ +} \ + \ +static inline uint32_t shdr32_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.r(&shdr->e32.sh_##fn_name); \ +} \ + \ +static inline uint32_t shdr_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.shdr_##fn_name(shdr); \ +} + +#define SHDR_ADDR(fn_name) \ +static inline uint64_t shdr64_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.r8(&shdr->e64.sh_##fn_name); \ +} \ + \ +static inline uint64_t shdr32_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.r(&shdr->e32.sh_##fn_name); \ +} \ + \ +static inline uint64_t shdr_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.shdr_##fn_name(shdr); \ +} + +#define SHDR_WORD(fn_name) \ +static inline uint32_t shdr64_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.r(&shdr->e64.sh_##fn_name); \ +} \ + \ +static inline uint32_t shdr32_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.r(&shdr->e32.sh_##fn_name); \ +} \ +static inline uint32_t shdr_##fn_name(Elf_Shdr *shdr) \ +{ \ + return elf_parser.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 inline uint64_t sym64_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.r8(&sym->e64.st_##fn_name); \ +} \ + \ +static inline uint64_t sym32_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.r(&sym->e32.st_##fn_name); \ +} \ + \ +static inline uint64_t sym_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.sym_##fn_name(sym); \ +} + +#define SYM_WORD(fn_name) \ +static inline uint32_t sym64_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.r(&sym->e64.st_##fn_name); \ +} \ + \ +static inline uint32_t sym32_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.r(&sym->e32.st_##fn_name); \ +} \ + \ +static inline uint32_t sym_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.sym_##fn_name(sym); \ +} + +#define SYM_HALF(fn_name) \ +static inline uint16_t sym64_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.r2(&sym->e64.st_##fn_name); \ +} \ + \ +static inline uint16_t sym32_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.r2(&sym->e32.st_##fn_name); \ +} \ + \ +static inline uint16_t sym_##fn_name(Elf_Sym *sym) \ +{ \ + return elf_parser.sym_##fn_name(sym); \ +} + +static inline uint8_t sym64_type(Elf_Sym *sym) +{ + return ELF64_ST_TYPE(sym->e64.st_info); +} + +static inline uint8_t sym32_type(Elf_Sym *sym) +{ + return ELF32_ST_TYPE(sym->e32.st_info); +} + +static inline uint8_t sym_type(Elf_Sym *sym) +{ + return elf_parser.sym_type(sym); +} + +SYM_ADDR(value) +SYM_WORD(name) +SYM_HALF(shndx) + +#define __maybe_unused __attribute__((__unused__)) + +#define RELA_ADDR(fn_name) \ +static inline uint64_t rela64_##fn_name(Elf_Rela *rela) \ +{ \ + return elf_parser.r8((uint64_t *)&rela->e64.r_##fn_name); \ +} \ + \ +static inline uint64_t rela32_##fn_name(Elf_Rela *rela) \ +{ \ + return elf_parser.r((uint32_t *)&rela->e32.r_##fn_name); \ +} \ + \ +static inline uint64_t __maybe_unused rela_##fn_name(Elf_Rela *rela) \ +{ \ + return elf_parser.rela_##fn_name(rela); \ +} + +RELA_ADDR(offset) +RELA_ADDR(info) +RELA_ADDR(addend) + +static inline void rela64_write_addend(Elf_Rela *rela, uint64_t val) +{ + elf_parser.w8(val, (uint64_t *)&rela->e64.r_addend); +} + +static inline void rela32_write_addend(Elf_Rela *rela, uint64_t val) +{ + elf_parser.w(val, (uint32_t *)&rela->e32.r_addend); +} + +static inline uint32_t rbe(const uint32_t *x) +{ + return get_unaligned_be32(x); +} + +static inline uint16_t r2be(const uint16_t *x) +{ + return get_unaligned_be16(x); +} + +static inline uint64_t r8be(const uint64_t *x) +{ + return get_unaligned_be64(x); +} + +static inline uint32_t rle(const uint32_t *x) +{ + return get_unaligned_le32(x); +} + +static inline uint16_t r2le(const uint16_t *x) +{ + return get_unaligned_le16(x); +} + +static inline uint64_t r8le(const uint64_t *x) +{ + return get_unaligned_le64(x); +} + +static inline void wbe(uint32_t val, uint32_t *x) +{ + put_unaligned_be32(val, x); +} + +static inline void wle(uint32_t val, uint32_t *x) +{ + put_unaligned_le32(val, x); +} + +static inline void w8be(uint64_t val, uint64_t *x) +{ + put_unaligned_be64(val, x); +} + +static inline void w8le(uint64_t val, uint64_t *x) +{ + put_unaligned_le64(val, x); +} + +void *elf_map(char const *fname, size_t *size, uint32_t types); +void elf_unmap(void *addr, size_t size); +int elf_map_machine(void *addr); +int elf_map_long_size(void *addr); + +#endif /* _SCRIPTS_ELF_PARSE_H */ diff --git a/scripts/extract-fwblobs b/scripts/extract-fwblobs new file mode 100755 index 000000000..53729124e --- /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/extract-ikconfig b/scripts/extract-ikconfig new file mode 100755 index 000000000..8df33e7d6 --- /dev/null +++ b/scripts/extract-ikconfig @@ -0,0 +1,69 @@ +#!/bin/sh +# ---------------------------------------------------------------------- +# extract-ikconfig - Extract the .config file from a kernel image +# +# This will only work when the kernel was compiled with CONFIG_IKCONFIG. +# +# The obscure use of the "tr" filter is to work around older versions of +# "grep" that report the byte offset of the line instead of the pattern. +# +# (c) 2009,2010 Dick Streefland <dick@streefland.net> +# Licensed under the terms of the GNU General Public License. +# ---------------------------------------------------------------------- + +cf1='IKCFG_ST\037\213\010' +cf2='0123456789' + +dump_config() +{ + if pos=`tr "$cf1\n$cf2" "\n$cf2=" < "$1" | grep -abo "^$cf2"` + then + pos=${pos%%:*} + tail -c+$(($pos+8)) "$1" | zcat > $tmp1 2> /dev/null + if [ $? != 1 ] + then # exit status must be 0 or 2 (trailing garbage warning) + cat $tmp1 + exit 0 + fi + fi +} + +try_decompress() +{ + for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` + do + pos=${pos%%:*} + tail -c+$pos "$img" | $3 > $tmp2 2> /dev/null + dump_config $tmp2 + done +} + +# Check invocation: +me=${0##*/} +img=$1 +if [ $# -ne 1 -o ! -s "$img" ] +then + echo "Usage: $me <kernel-image>" >&2 + exit 2 +fi + +# Prepare temp files: +tmp1=/tmp/ikconfig$$.1 +tmp2=/tmp/ikconfig$$.2 +trap "rm -f $tmp1 $tmp2" 0 + +# Initial attempt for uncompressed images or objects: +dump_config "$img" + +# That didn't work, so retry after decompression. +try_decompress '\037\213\010' xy gunzip +try_decompress '\3757zXZ\000' abcde unxz +try_decompress 'BZh' xy bunzip2 +try_decompress '\135\0\0\0' xxx unlzma +try_decompress '\211\114\132' xy 'lzop -d' +try_decompress '\002\041\114\030' xyy 'lz4 -d -l' +try_decompress '\050\265\057\375' xxx unzstd + +# Bail out: +echo "$me: Cannot find kernel config." >&2 +exit 1 diff --git a/scripts/extract-module-sig.pl b/scripts/extract-module-sig.pl new file mode 100755 index 000000000..36a2f59c4 --- /dev/null +++ b/scripts/extract-module-sig.pl @@ -0,0 +1,138 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# extract-mod-sig <part> <module-file> +# +# Reads the module file and writes out some or all of the signature +# section to stdout. Part is the bit to be written and is one of: +# +# -0: The unsigned module, no signature data at all +# -a: All of the signature data, including magic number +# -d: Just the descriptor values as a sequence of numbers +# -n: Just the signer's name +# -k: Just the key ID +# -s: Just the crypto signature or PKCS#7 message +# +use warnings; +use strict; + +die "Format: $0 -[0adnks] module-file >out\n" + if ($#ARGV != 1); + +my $part = $ARGV[0]; +my $modfile = $ARGV[1]; + +my $magic_number = "~Module signature appended~\n"; + +# +# Read the module contents +# +open FD, "<$modfile" || die $modfile; +binmode(FD); +my @st = stat(FD); +die "$modfile" unless (@st); +my $buf = ""; +my $len = sysread(FD, $buf, $st[7]); +die "$modfile" unless (defined($len)); +die "Short read on $modfile\n" unless ($len == $st[7]); +close(FD) || die $modfile; + +print STDERR "Read ", $len, " bytes from module file\n"; + +die "The file is too short to have a sig magic number and descriptor\n" + if ($len < 12 + length($magic_number)); + +# +# Check for the magic number and extract the information block +# +my $p = $len - length($magic_number); +my $raw_magic = substr($buf, $p); + +die "Magic number not found at $len\n" + if ($raw_magic ne $magic_number); +print STDERR "Found magic number at $len\n"; + +$p -= 12; +my $raw_info = substr($buf, $p, 12); + +my @info = unpack("CCCCCxxxN", $raw_info); +my ($algo, $hash, $id_type, $name_len, $kid_len, $sig_len) = @info; + +if ($id_type == 0) { + print STDERR "Found PGP key identifier\n"; +} elsif ($id_type == 1) { + print STDERR "Found X.509 cert identifier\n"; +} elsif ($id_type == 2) { + print STDERR "Found PKCS#7/CMS encapsulation\n"; +} else { + print STDERR "Found unsupported identifier type $id_type\n"; +} + +# +# Extract the three pieces of info data +# +die "Insufficient name+kid+sig data in file\n" + unless ($p >= $name_len + $kid_len + $sig_len); + +$p -= $sig_len; +my $raw_sig = substr($buf, $p, $sig_len); +$p -= $kid_len; +my $raw_kid = substr($buf, $p, $kid_len); +$p -= $name_len; +my $raw_name = substr($buf, $p, $name_len); + +my $module_len = $p; + +if ($sig_len > 0) { + print STDERR "Found $sig_len bytes of signature ["; + my $n = $sig_len > 16 ? 16 : $sig_len; + foreach my $i (unpack("C" x $n, substr($raw_sig, 0, $n))) { + printf STDERR "%02x", $i; + } + print STDERR "]\n"; +} + +if ($kid_len > 0) { + print STDERR "Found $kid_len bytes of key identifier ["; + my $n = $kid_len > 16 ? 16 : $kid_len; + foreach my $i (unpack("C" x $n, substr($raw_kid, 0, $n))) { + printf STDERR "%02x", $i; + } + print STDERR "]\n"; +} + +if ($name_len > 0) { + print STDERR "Found $name_len bytes of signer's name [$raw_name]\n"; +} + +# +# Produce the requested output +# +if ($part eq "-0") { + # The unsigned module, no signature data at all + binmode(STDOUT); + print substr($buf, 0, $module_len); +} elsif ($part eq "-a") { + # All of the signature data, including magic number + binmode(STDOUT); + print substr($buf, $module_len); +} elsif ($part eq "-d") { + # Just the descriptor values as a sequence of numbers + print join(" ", @info), "\n"; +} elsif ($part eq "-n") { + # Just the signer's name + print STDERR "No signer's name for PKCS#7 message type sig\n" + if ($id_type == 2); + binmode(STDOUT); + print $raw_name; +} elsif ($part eq "-k") { + # Just the key identifier + print STDERR "No key ID for PKCS#7 message type sig\n" + if ($id_type == 2); + binmode(STDOUT); + print $raw_kid; +} elsif ($part eq "-s") { + # Just the crypto signature or PKCS#7 message + binmode(STDOUT); + print $raw_sig; +} diff --git a/scripts/extract-sys-certs.pl b/scripts/extract-sys-certs.pl new file mode 100755 index 000000000..fa8ab1511 --- /dev/null +++ b/scripts/extract-sys-certs.pl @@ -0,0 +1,159 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +use warnings; +use strict; +use Math::BigInt; +use Fcntl "SEEK_SET"; + +die "Format: $0 [-s <systemmap-file>] <vmlinux-file> <keyring-file>\n" + if ($#ARGV != 1 && $#ARGV != 3 || + $#ARGV == 3 && $ARGV[0] ne "-s"); + +my $sysmap = ""; +if ($#ARGV == 3) { + shift; + $sysmap = $ARGV[0]; + shift; +} + +my $vmlinux = $ARGV[0]; +my $keyring = $ARGV[1]; + +# +# Parse the vmlinux section table +# +open FD, "objdump -h $vmlinux |" || die $vmlinux; +my @lines = <FD>; +close(FD) || die $vmlinux; + +my @sections = (); + +foreach my $line (@lines) { + chomp($line); + if ($line =~ /\s*([0-9]+)\s+(\S+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+2[*][*]([0-9]+)/ + ) { + my $seg = $1; + my $name = $2; + my $len = Math::BigInt->new("0x" . $3); + my $vma = Math::BigInt->new("0x" . $4); + my $lma = Math::BigInt->new("0x" . $5); + my $foff = Math::BigInt->new("0x" . $6); + my $align = 2 ** $7; + + push @sections, { name => $name, + vma => $vma, + len => $len, + foff => $foff }; + } +} + +print "Have $#sections sections\n"; + +# +# Try and parse the vmlinux symbol table. If the vmlinux file has been created +# from a vmlinuz file with extract-vmlinux then the symbol table will be empty. +# +open FD, "nm $vmlinux 2>/dev/null |" || die $vmlinux; +@lines = <FD>; +close(FD) || die $vmlinux; + +my %symbols = (); +my $nr_symbols = 0; + +sub parse_symbols(@) { + foreach my $line (@_) { + chomp($line); + if ($line =~ /([0-9a-f]+)\s([a-zA-Z])\s(\S+)/ + ) { + my $addr = "0x" . $1; + my $type = $2; + my $name = $3; + + $symbols{$name} = $addr; + $nr_symbols++; + } + } +} +parse_symbols(@lines); + +if ($nr_symbols == 0 && $sysmap ne "") { + print "No symbols in vmlinux, trying $sysmap\n"; + + open FD, "<$sysmap" || die $sysmap; + @lines = <FD>; + close(FD) || die $sysmap; + parse_symbols(@lines); +} + +die "No symbols available\n" + if ($nr_symbols == 0); + +print "Have $nr_symbols symbols\n"; + +die "Can't find system certificate list" + unless (exists($symbols{"__cert_list_start"}) && + exists($symbols{"system_certificate_list_size"})); + +my $start = Math::BigInt->new($symbols{"__cert_list_start"}); +my $end; +my $size; +my $size_sym = Math::BigInt->new($symbols{"system_certificate_list_size"}); + +open FD, "<$vmlinux" || die $vmlinux; +binmode(FD); + +my $s = undef; +foreach my $sec (@sections) { + my $s_name = $sec->{name}; + my $s_vma = $sec->{vma}; + my $s_len = $sec->{len}; + my $s_foff = $sec->{foff}; + my $s_vend = $s_vma + $s_len; + + next unless ($start >= $s_vma); + next if ($start >= $s_vend); + + die "Certificate list size was not found on the same section\n" + if ($size_sym < $s_vma || $size_sym > $s_vend); + + die "Cert object in multiple sections: ", $s_name, " and ", $s->{name}, "\n" + if ($s); + + my $size_off = $size_sym -$s_vma + $s_foff; + my $packed; + die $vmlinux if (!defined(sysseek(FD, $size_off, SEEK_SET))); + sysread(FD, $packed, 8); + $size = unpack 'L!', $packed; + $end = $start + $size; + + printf "Have %u bytes of certs at VMA 0x%x\n", $size, $start; + + die "Cert object partially overflows section $s_name\n" + if ($end > $s_vend); + + $s = $sec; +} + +die "Cert object not inside a section\n" + unless ($s); + +print "Certificate list in section ", $s->{name}, "\n"; + +my $foff = $start - $s->{vma} + $s->{foff}; + +printf "Certificate list at file offset 0x%x\n", $foff; + +die $vmlinux if (!defined(sysseek(FD, $foff, SEEK_SET))); +my $buf = ""; +my $len = sysread(FD, $buf, $size); +die "$vmlinux" if (!defined($len)); +die "Short read on $vmlinux\n" if ($len != $size); +close(FD) || die $vmlinux; + +open FD, ">$keyring" || die $keyring; +binmode(FD); +$len = syswrite(FD, $buf, $size); +die "$keyring" if (!defined($len)); +die "Short write on $keyring\n" if ($len != $size); +close(FD) || die $keyring; diff --git a/scripts/extract-vmlinux b/scripts/extract-vmlinux new file mode 100755 index 000000000..266df9bc7 --- /dev/null +++ b/scripts/extract-vmlinux @@ -0,0 +1,65 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# ---------------------------------------------------------------------- +# extract-vmlinux - Extract uncompressed vmlinux from a kernel image +# +# Inspired from extract-ikconfig +# (c) 2009,2010 Dick Streefland <dick@streefland.net> +# +# (c) 2011 Corentin Chary <corentin.chary@gmail.com> +# +# ---------------------------------------------------------------------- + +me=${0##*/} + +check_vmlinux() +{ + if file "$1" | grep -q 'Linux kernel.*boot executable' || + readelf -h "$1" > /dev/null 2>&1 + then + cat "$1" + echo "$me: Extracted vmlinux using '$2' from offset $3" >&2 + exit 0 + fi +} + +try_decompress() +{ + # The obscure use of the "tr" filter is to work around older versions of + # "grep" that report the byte offset of the line instead of the pattern. + + # Try to find the header ($1) and decompress from here + for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` + do + pos=${pos%%:*} + tail -c+$pos "$img" | $3 > $tmp 2> /dev/null + check_vmlinux $tmp "$3" $pos + done +} + +# Check invocation: +img=$1 +if [ $# -ne 1 -o ! -s "$img" ] +then + echo "Usage: $me <kernel-image>" >&2 + exit 2 +fi + +# Prepare temp files: +tmp=$(mktemp /tmp/vmlinux-XXX) +trap "rm -f $tmp" 0 + +# That didn't work, so retry after decompression. +try_decompress '\037\213\010' xy gunzip +try_decompress '\3757zXZ\000' abcde unxz +try_decompress 'BZh' xy bunzip2 +try_decompress '\135\0\0\0' xxx unlzma +try_decompress '\211\114\132' xy 'lzop -d' +try_decompress '\002!L\030' xxx 'lz4 -d' +try_decompress '(\265/\375' xxx unzstd + +# Finally check for uncompressed images or objects: +check_vmlinux "$img" cat 0 + +# Bail out: +echo "$me: Cannot find vmlinux." >&2 diff --git a/scripts/extract_xc3028.pl b/scripts/extract_xc3028.pl new file mode 100755 index 000000000..e1c9af25a --- /dev/null +++ b/scripts/extract_xc3028.pl @@ -0,0 +1,1717 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-only + +# Copyright (c) Mauro Carvalho Chehab <mchehab@kernel.org> +# +# In order to use, you need to: +# 1) Download the windows driver with something like: +# Version 2.4 +# wget http://www.twinhan.com/files/AW/BDA T/20080303_V1.0.6.7.zip +# or wget http://www.stefanringel.de/pub/20080303_V1.0.6.7.zip +# Version 2.7 +# wget http://www.steventoth.net/linux/xc5000/HVR-12x0-14x0-17x0_1_25_25271_WHQL.zip +# 2) Extract the files from the zip into the current dir: +# unzip -j 20080303_V1.0.6.7.zip 20080303_v1.0.6.7/UDXTTM6000.sys +# unzip -j HVR-12x0-14x0-17x0_1_25_25271_WHQL.zip Driver85/hcw85bda.sys +# 3) run the script: +# ./extract_xc3028.pl +# 4) copy the generated files: +# cp xc3028-v24.fw /lib/firmware +# cp xc3028-v27.fw /lib/firmware + +#use strict; +use IO::Handle; + +my $debug=0; + +sub verify ($$) +{ + my ($filename, $hash) = @_; + my ($testhash); + + if (system("which md5sum > /dev/null 2>&1")) { + die "This firmware requires the md5sum command - see http://www.gnu.org/software/coreutils/\n"; + } + + open(CMD, "md5sum ".$filename."|"); + $testhash = <CMD>; + $testhash =~ /([a-zA-Z0-9]*)/; + $testhash = $1; + close CMD; + die "Hash of extracted file does not match (found $testhash, expected $hash!\n" if ($testhash ne $hash); +} + +sub get_hunk ($$) +{ + my ($offset, $length) = @_; + my ($chunklength, $buf, $rcount, $out); + + sysseek(INFILE, $offset, SEEK_SET); + while ($length > 0) { + # Calc chunk size + $chunklength = 2048; + $chunklength = $length if ($chunklength > $length); + + $rcount = sysread(INFILE, $buf, $chunklength); + die "Ran out of data\n" if ($rcount != $chunklength); + $out .= $buf; + $length -= $rcount; + } + return $out; +} + +sub write_le16($) +{ + my $val = shift; + my $msb = ($val >> 8) &0xff; + my $lsb = $val & 0xff; + + syswrite(OUTFILE, chr($lsb).chr($msb)); +} + +sub write_le32($) +{ + my $val = shift; + my $l3 = ($val >> 24) & 0xff; + my $l2 = ($val >> 16) & 0xff; + my $l1 = ($val >> 8) & 0xff; + my $l0 = $val & 0xff; + + syswrite(OUTFILE, chr($l0).chr($l1).chr($l2).chr($l3)); +} + +sub write_le64($$) +{ + my $msb_val = shift; + my $lsb_val = shift; + my $l7 = ($msb_val >> 24) & 0xff; + my $l6 = ($msb_val >> 16) & 0xff; + my $l5 = ($msb_val >> 8) & 0xff; + my $l4 = $msb_val & 0xff; + + my $l3 = ($lsb_val >> 24) & 0xff; + my $l2 = ($lsb_val >> 16) & 0xff; + my $l1 = ($lsb_val >> 8) & 0xff; + my $l0 = $lsb_val & 0xff; + + syswrite(OUTFILE, + chr($l0).chr($l1).chr($l2).chr($l3). + chr($l4).chr($l5).chr($l6).chr($l7)); +} + +sub write_hunk($$) +{ + my ($offset, $length) = @_; + my $out = get_hunk($offset, $length); + + printf "(len %d) ",$length if ($debug); + + for (my $i=0;$i<$length;$i++) { + printf "%02x ",ord(substr($out,$i,1)) if ($debug); + } + printf "\n" if ($debug); + + syswrite(OUTFILE, $out); +} + +sub write_hunk_fix_endian($$) +{ + my ($offset, $length) = @_; + my $out = get_hunk($offset, $length); + + printf "(len_fix %d) ",$length if ($debug); + + for (my $i=0;$i<$length;$i++) { + printf "%02x ",ord(substr($out,$i,1)) if ($debug); + } + printf "\n" if ($debug); + + my $i=0; + while ($i<$length) { + my $size = ord(substr($out,$i,1))*256+ord(substr($out,$i+1,1)); + syswrite(OUTFILE, substr($out,$i+1,1)); + syswrite(OUTFILE, substr($out,$i,1)); + $i+=2; + if ($size>0 && $size <0x8000) { + for (my $j=0;$j<$size;$j++) { + syswrite(OUTFILE, substr($out,$j+$i,1)); + } + $i+=$size; + } + } +} + +sub main_firmware_24($$$$) +{ + my $out; + my $j=0; + my $outfile = shift; + my $name = shift; + my $version = shift; + my $nr_desc = shift; + + for ($j = length($name); $j <32; $j++) { + $name = $name.chr(0); + } + + open OUTFILE, ">$outfile"; + syswrite(OUTFILE, $name); + write_le16($version); + write_le16($nr_desc); + + # + # Firmware 0, type: BASE FW F8MHZ (0x00000003), id: (0000000000000000), size: 6635 + # + + write_le32(0x00000003); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(6635); # Size + write_hunk_fix_endian(257752, 6635); + + # + # Firmware 1, type: BASE FW F8MHZ MTS (0x00000007), id: (0000000000000000), size: 6635 + # + + write_le32(0x00000007); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(6635); # Size + write_hunk_fix_endian(264392, 6635); + + # + # Firmware 2, type: BASE FW FM (0x00000401), id: (0000000000000000), size: 6525 + # + + write_le32(0x00000401); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(6525); # Size + write_hunk_fix_endian(271040, 6525); + + # + # Firmware 3, type: BASE FW FM INPUT1 (0x00000c01), id: (0000000000000000), size: 6539 + # + + write_le32(0x00000c01); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(6539); # Size + write_hunk_fix_endian(277568, 6539); + + # + # Firmware 4, type: BASE FW (0x00000001), id: (0000000000000000), size: 6633 + # + + write_le32(0x00000001); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(6633); # Size + write_hunk_fix_endian(284120, 6633); + + # + # Firmware 5, type: BASE FW MTS (0x00000005), id: (0000000000000000), size: 6617 + # + + write_le32(0x00000005); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(6617); # Size + write_hunk_fix_endian(290760, 6617); + + # + # Firmware 6, type: STD FW (0x00000000), id: PAL/BG A2/A (0000000100000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000001, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(297384, 161); + + # + # Firmware 7, type: STD FW MTS (0x00000004), id: PAL/BG A2/A (0000000100000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000001, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(297552, 169); + + # + # Firmware 8, type: STD FW (0x00000000), id: PAL/BG A2/B (0000000200000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000002, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(297728, 161); + + # + # Firmware 9, type: STD FW MTS (0x00000004), id: PAL/BG A2/B (0000000200000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000002, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(297896, 169); + + # + # Firmware 10, type: STD FW (0x00000000), id: PAL/BG NICAM/A (0000000400000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000004, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(298072, 161); + + # + # Firmware 11, type: STD FW MTS (0x00000004), id: PAL/BG NICAM/A (0000000400000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000004, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(298240, 169); + + # + # Firmware 12, type: STD FW (0x00000000), id: PAL/BG NICAM/B (0000000800000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000008, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(298416, 161); + + # + # Firmware 13, type: STD FW MTS (0x00000004), id: PAL/BG NICAM/B (0000000800000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000008, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(298584, 169); + + # + # Firmware 14, type: STD FW (0x00000000), id: PAL/DK A2 (00000003000000e0), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le32(161); # Size + write_hunk_fix_endian(298760, 161); + + # + # Firmware 15, type: STD FW MTS (0x00000004), id: PAL/DK A2 (00000003000000e0), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le32(169); # Size + write_hunk_fix_endian(298928, 169); + + # + # Firmware 16, type: STD FW (0x00000000), id: PAL/DK NICAM (0000000c000000e0), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x0000000c, 0x000000e0); # ID + write_le32(161); # Size + write_hunk_fix_endian(299104, 161); + + # + # Firmware 17, type: STD FW MTS (0x00000004), id: PAL/DK NICAM (0000000c000000e0), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x0000000c, 0x000000e0); # ID + write_le32(169); # Size + write_hunk_fix_endian(299272, 169); + + # + # Firmware 18, type: STD FW (0x00000000), id: SECAM/K1 (0000000000200000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le32(161); # Size + write_hunk_fix_endian(299448, 161); + + # + # Firmware 19, type: STD FW MTS (0x00000004), id: SECAM/K1 (0000000000200000), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le32(169); # Size + write_hunk_fix_endian(299616, 169); + + # + # Firmware 20, type: STD FW (0x00000000), id: SECAM/K3 (0000000004000000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x04000000); # ID + write_le32(161); # Size + write_hunk_fix_endian(299792, 161); + + # + # Firmware 21, type: STD FW MTS (0x00000004), id: SECAM/K3 (0000000004000000), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x04000000); # ID + write_le32(169); # Size + write_hunk_fix_endian(299960, 169); + + # + # Firmware 22, type: STD FW D2633 DTV6 ATSC (0x00010030), id: (0000000000000000), size: 149 + # + + write_le32(0x00010030); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(300136, 149); + + # + # Firmware 23, type: STD FW D2620 DTV6 QAM (0x00000068), id: (0000000000000000), size: 149 + # + + write_le32(0x00000068); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(300296, 149); + + # + # Firmware 24, type: STD FW D2633 DTV6 QAM (0x00000070), id: (0000000000000000), size: 149 + # + + write_le32(0x00000070); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(300448, 149); + + # + # Firmware 25, type: STD FW D2620 DTV7 (0x00000088), id: (0000000000000000), size: 149 + # + + write_le32(0x00000088); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(300608, 149); + + # + # Firmware 26, type: STD FW D2633 DTV7 (0x00000090), id: (0000000000000000), size: 149 + # + + write_le32(0x00000090); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(300760, 149); + + # + # Firmware 27, type: STD FW D2620 DTV78 (0x00000108), id: (0000000000000000), size: 149 + # + + write_le32(0x00000108); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(300920, 149); + + # + # Firmware 28, type: STD FW D2633 DTV78 (0x00000110), id: (0000000000000000), size: 149 + # + + write_le32(0x00000110); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(301072, 149); + + # + # Firmware 29, type: STD FW D2620 DTV8 (0x00000208), id: (0000000000000000), size: 149 + # + + write_le32(0x00000208); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(301232, 149); + + # + # Firmware 30, type: STD FW D2633 DTV8 (0x00000210), id: (0000000000000000), size: 149 + # + + write_le32(0x00000210); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(301384, 149); + + # + # Firmware 31, type: STD FW FM (0x00000400), id: (0000000000000000), size: 135 + # + + write_le32(0x00000400); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(135); # Size + write_hunk_fix_endian(301554, 135); + + # + # Firmware 32, type: STD FW (0x00000000), id: PAL/I (0000000000000010), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00000010); # ID + write_le32(161); # Size + write_hunk_fix_endian(301688, 161); + + # + # Firmware 33, type: STD FW MTS (0x00000004), id: PAL/I (0000000000000010), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x00000010); # ID + write_le32(169); # Size + write_hunk_fix_endian(301856, 169); + + # + # Firmware 34, type: STD FW (0x00000000), id: SECAM/L AM (0000001000400000), size: 169 + # + + # + # Firmware 35, type: STD FW (0x00000000), id: SECAM/L NICAM (0000000c00400000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x0000000c, 0x00400000); # ID + write_le32(161); # Size + write_hunk_fix_endian(302032, 161); + + # + # Firmware 36, type: STD FW (0x00000000), id: SECAM/Lc (0000000000800000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00800000); # ID + write_le32(161); # Size + write_hunk_fix_endian(302200, 161); + + # + # Firmware 37, type: STD FW (0x00000000), id: NTSC/M Kr (0000000000008000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(161); # Size + write_hunk_fix_endian(302368, 161); + + # + # Firmware 38, type: STD FW LCD (0x00001000), id: NTSC/M Kr (0000000000008000), size: 161 + # + + write_le32(0x00001000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(161); # Size + write_hunk_fix_endian(302536, 161); + + # + # Firmware 39, type: STD FW LCD NOGD (0x00003000), id: NTSC/M Kr (0000000000008000), size: 161 + # + + write_le32(0x00003000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(161); # Size + write_hunk_fix_endian(302704, 161); + + # + # Firmware 40, type: STD FW MTS (0x00000004), id: NTSC/M Kr (0000000000008000), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(169); # Size + write_hunk_fix_endian(302872, 169); + + # + # Firmware 41, type: STD FW (0x00000000), id: NTSC PAL/M PAL/N (000000000000b700), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(161); # Size + write_hunk_fix_endian(303048, 161); + + # + # Firmware 42, type: STD FW LCD (0x00001000), id: NTSC PAL/M PAL/N (000000000000b700), size: 161 + # + + write_le32(0x00001000); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(161); # Size + write_hunk_fix_endian(303216, 161); + + # + # Firmware 43, type: STD FW LCD NOGD (0x00003000), id: NTSC PAL/M PAL/N (000000000000b700), size: 161 + # + + write_le32(0x00003000); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(161); # Size + write_hunk_fix_endian(303384, 161); + + # + # Firmware 44, type: STD FW (0x00000000), id: NTSC/M Jp (0000000000002000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00002000); # ID + write_le32(161); # Size + write_hunk_fix_endian(303552, 161); + + # + # Firmware 45, type: STD FW MTS (0x00000004), id: NTSC PAL/M PAL/N (000000000000b700), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(169); # Size + write_hunk_fix_endian(303720, 169); + + # + # Firmware 46, type: STD FW MTS LCD (0x00001004), id: NTSC PAL/M PAL/N (000000000000b700), size: 169 + # + + write_le32(0x00001004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(169); # Size + write_hunk_fix_endian(303896, 169); + + # + # Firmware 47, type: STD FW MTS LCD NOGD (0x00003004), id: NTSC PAL/M PAL/N (000000000000b700), size: 169 + # + + write_le32(0x00003004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(169); # Size + write_hunk_fix_endian(304072, 169); + + # + # Firmware 48, type: SCODE FW HAS IF (0x60000000), IF = 3.28 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3280); # IF + write_le32(192); # Size + write_hunk(309048, 192); + + # + # Firmware 49, type: SCODE FW HAS IF (0x60000000), IF = 3.30 MHz id: (0000000000000000), size: 192 + # + +# write_le32(0x60000000); # Type +# write_le64(0x00000000, 0x00000000); # ID +# write_le16(3300); # IF +# write_le32(192); # Size +# write_hunk(304440, 192); + + # + # Firmware 50, type: SCODE FW HAS IF (0x60000000), IF = 3.44 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3440); # IF + write_le32(192); # Size + write_hunk(309432, 192); + + # + # Firmware 51, type: SCODE FW HAS IF (0x60000000), IF = 3.46 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3460); # IF + write_le32(192); # Size + write_hunk(309624, 192); + + # + # Firmware 52, type: SCODE FW DTV6 ATSC OREN36 HAS IF (0x60210020), IF = 3.80 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60210020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3800); # IF + write_le32(192); # Size + write_hunk(306936, 192); + + # + # Firmware 53, type: SCODE FW HAS IF (0x60000000), IF = 4.00 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4000); # IF + write_le32(192); # Size + write_hunk(309240, 192); + + # + # Firmware 54, type: SCODE FW DTV6 ATSC TOYOTA388 HAS IF (0x60410020), IF = 4.08 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60410020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4080); # IF + write_le32(192); # Size + write_hunk(307128, 192); + + # + # Firmware 55, type: SCODE FW HAS IF (0x60000000), IF = 4.20 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4200); # IF + write_le32(192); # Size + write_hunk(308856, 192); + + # + # Firmware 56, type: SCODE FW MONO HAS IF (0x60008000), IF = 4.32 MHz id: NTSC/M Kr (0000000000008000), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le16(4320); # IF + write_le32(192); # Size + write_hunk(305208, 192); + + # + # Firmware 57, type: SCODE FW HAS IF (0x60000000), IF = 4.45 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4450); # IF + write_le32(192); # Size + write_hunk(309816, 192); + + # + # Firmware 58, type: SCODE FW MTS LCD NOGD MONO IF HAS IF (0x6002b004), IF = 4.50 MHz id: NTSC PAL/M PAL/N (000000000000b700), size: 192 + # + + write_le32(0x6002b004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le16(4500); # IF + write_le32(192); # Size + write_hunk(304824, 192); + + # + # Firmware 59, type: SCODE FW LCD NOGD IF HAS IF (0x60023000), IF = 4.60 MHz id: NTSC/M Kr (0000000000008000), size: 192 + # + + write_le32(0x60023000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le16(4600); # IF + write_le32(192); # Size + write_hunk(305016, 192); + + # + # Firmware 60, type: SCODE FW DTV6 QAM DTV7 DTV78 DTV8 ZARLINK456 HAS IF (0x620003e0), IF = 4.76 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x620003e0); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4760); # IF + write_le32(192); # Size + write_hunk(304440, 192); + + # + # Firmware 61, type: SCODE FW HAS IF (0x60000000), IF = 4.94 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4940); # IF + write_le32(192); # Size + write_hunk(308664, 192); + + # + # Firmware 62, type: SCODE FW HAS IF (0x60000000), IF = 5.26 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5260); # IF + write_le32(192); # Size + write_hunk(307704, 192); + + # + # Firmware 63, type: SCODE FW MONO HAS IF (0x60008000), IF = 5.32 MHz id: PAL/BG A2 NICAM (0000000f00000007), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x0000000f, 0x00000007); # ID + write_le16(5320); # IF + write_le32(192); # Size + write_hunk(307896, 192); + + # + # Firmware 64, type: SCODE FW DTV7 DTV78 DTV8 DIBCOM52 CHINA HAS IF (0x65000380), IF = 5.40 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x65000380); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5400); # IF + write_le32(192); # Size + write_hunk(304248, 192); + + # + # Firmware 65, type: SCODE FW DTV6 ATSC OREN538 HAS IF (0x60110020), IF = 5.58 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60110020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5580); # IF + write_le32(192); # Size + write_hunk(306744, 192); + + # + # Firmware 66, type: SCODE FW HAS IF (0x60000000), IF = 5.64 MHz id: PAL/BG A2 (0000000300000007), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000003, 0x00000007); # ID + write_le16(5640); # IF + write_le32(192); # Size + write_hunk(305592, 192); + + # + # Firmware 67, type: SCODE FW HAS IF (0x60000000), IF = 5.74 MHz id: PAL/BG NICAM (0000000c00000007), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x0000000c, 0x00000007); # ID + write_le16(5740); # IF + write_le32(192); # Size + write_hunk(305784, 192); + + # + # Firmware 68, type: SCODE FW HAS IF (0x60000000), IF = 5.90 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5900); # IF + write_le32(192); # Size + write_hunk(307512, 192); + + # + # Firmware 69, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.00 MHz id: PAL/DK PAL/I SECAM/K3 SECAM/L SECAM/Lc NICAM (0000000c04c000f0), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x0000000c, 0x04c000f0); # ID + write_le16(6000); # IF + write_le32(192); # Size + write_hunk(305576, 192); + + # + # Firmware 70, type: SCODE FW DTV6 QAM ATSC LG60 F6MHZ HAS IF (0x68050060), IF = 6.20 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x68050060); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(6200); # IF + write_le32(192); # Size + write_hunk(306552, 192); + + # + # Firmware 71, type: SCODE FW HAS IF (0x60000000), IF = 6.24 MHz id: PAL/I (0000000000000010), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000010); # ID + write_le16(6240); # IF + write_le32(192); # Size + write_hunk(305400, 192); + + # + # Firmware 72, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.32 MHz id: SECAM/K1 (0000000000200000), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le16(6320); # IF + write_le32(192); # Size + write_hunk(308472, 192); + + # + # Firmware 73, type: SCODE FW HAS IF (0x60000000), IF = 6.34 MHz id: SECAM/K1 (0000000000200000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le16(6340); # IF + write_le32(192); # Size + write_hunk(306360, 192); + + # + # Firmware 74, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.50 MHz id: PAL/DK SECAM/K3 SECAM/L NICAM (0000000c044000e0), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x0000000c, 0x044000e0); # ID + write_le16(6500); # IF + write_le32(192); # Size + write_hunk(308280, 192); + + # + # Firmware 75, type: SCODE FW DTV6 ATSC ATI638 HAS IF (0x60090020), IF = 6.58 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60090020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(6580); # IF + write_le32(192); # Size + write_hunk(304632, 192); + + # + # Firmware 76, type: SCODE FW HAS IF (0x60000000), IF = 6.60 MHz id: PAL/DK A2 (00000003000000e0), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le16(6600); # IF + write_le32(192); # Size + write_hunk(306168, 192); + + # + # Firmware 77, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.68 MHz id: PAL/DK A2 (00000003000000e0), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le16(6680); # IF + write_le32(192); # Size + write_hunk(308088, 192); + + # + # Firmware 78, type: SCODE FW DTV6 ATSC TOYOTA794 HAS IF (0x60810020), IF = 8.14 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60810020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(8140); # IF + write_le32(192); # Size + write_hunk(307320, 192); + + # + # Firmware 79, type: SCODE FW HAS IF (0x60000000), IF = 8.20 MHz id: (0000000000000000), size: 192 + # + +# write_le32(0x60000000); # Type +# write_le64(0x00000000, 0x00000000); # ID +# write_le16(8200); # IF +# write_le32(192); # Size +# write_hunk(308088, 192); +} + +sub main_firmware_27($$$$) +{ + my $out; + my $j=0; + my $outfile = shift; + my $name = shift; + my $version = shift; + my $nr_desc = shift; + + for ($j = length($name); $j <32; $j++) { + $name = $name.chr(0); + } + + open OUTFILE, ">$outfile"; + syswrite(OUTFILE, $name); + write_le16($version); + write_le16($nr_desc); + + # + # Firmware 0, type: BASE FW F8MHZ (0x00000003), id: (0000000000000000), size: 8718 + # + + write_le32(0x00000003); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(8718); # Size + write_hunk_fix_endian(813432, 8718); + + # + # Firmware 1, type: BASE FW F8MHZ MTS (0x00000007), id: (0000000000000000), size: 8712 + # + + write_le32(0x00000007); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(8712); # Size + write_hunk_fix_endian(822152, 8712); + + # + # Firmware 2, type: BASE FW FM (0x00000401), id: (0000000000000000), size: 8562 + # + + write_le32(0x00000401); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(8562); # Size + write_hunk_fix_endian(830872, 8562); + + # + # Firmware 3, type: BASE FW FM INPUT1 (0x00000c01), id: (0000000000000000), size: 8576 + # + + write_le32(0x00000c01); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(8576); # Size + write_hunk_fix_endian(839440, 8576); + + # + # Firmware 4, type: BASE FW (0x00000001), id: (0000000000000000), size: 8706 + # + + write_le32(0x00000001); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(8706); # Size + write_hunk_fix_endian(848024, 8706); + + # + # Firmware 5, type: BASE FW MTS (0x00000005), id: (0000000000000000), size: 8682 + # + + write_le32(0x00000005); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(8682); # Size + write_hunk_fix_endian(856736, 8682); + + # + # Firmware 6, type: STD FW (0x00000000), id: PAL/BG A2/A (0000000100000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000001, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(865424, 161); + + # + # Firmware 7, type: STD FW MTS (0x00000004), id: PAL/BG A2/A (0000000100000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000001, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(865592, 169); + + # + # Firmware 8, type: STD FW (0x00000000), id: PAL/BG A2/B (0000000200000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000002, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(865424, 161); + + # + # Firmware 9, type: STD FW MTS (0x00000004), id: PAL/BG A2/B (0000000200000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000002, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(865592, 169); + + # + # Firmware 10, type: STD FW (0x00000000), id: PAL/BG NICAM/A (0000000400000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000004, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(866112, 161); + + # + # Firmware 11, type: STD FW MTS (0x00000004), id: PAL/BG NICAM/A (0000000400000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000004, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(866280, 169); + + # + # Firmware 12, type: STD FW (0x00000000), id: PAL/BG NICAM/B (0000000800000007), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000008, 0x00000007); # ID + write_le32(161); # Size + write_hunk_fix_endian(866112, 161); + + # + # Firmware 13, type: STD FW MTS (0x00000004), id: PAL/BG NICAM/B (0000000800000007), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000008, 0x00000007); # ID + write_le32(169); # Size + write_hunk_fix_endian(866280, 169); + + # + # Firmware 14, type: STD FW (0x00000000), id: PAL/DK A2 (00000003000000e0), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le32(161); # Size + write_hunk_fix_endian(866800, 161); + + # + # Firmware 15, type: STD FW MTS (0x00000004), id: PAL/DK A2 (00000003000000e0), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le32(169); # Size + write_hunk_fix_endian(866968, 169); + + # + # Firmware 16, type: STD FW (0x00000000), id: PAL/DK NICAM (0000000c000000e0), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x0000000c, 0x000000e0); # ID + write_le32(161); # Size + write_hunk_fix_endian(867144, 161); + + # + # Firmware 17, type: STD FW MTS (0x00000004), id: PAL/DK NICAM (0000000c000000e0), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x0000000c, 0x000000e0); # ID + write_le32(169); # Size + write_hunk_fix_endian(867312, 169); + + # + # Firmware 18, type: STD FW (0x00000000), id: SECAM/K1 (0000000000200000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le32(161); # Size + write_hunk_fix_endian(867488, 161); + + # + # Firmware 19, type: STD FW MTS (0x00000004), id: SECAM/K1 (0000000000200000), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le32(169); # Size + write_hunk_fix_endian(867656, 169); + + # + # Firmware 20, type: STD FW (0x00000000), id: SECAM/K3 (0000000004000000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x04000000); # ID + write_le32(161); # Size + write_hunk_fix_endian(867832, 161); + + # + # Firmware 21, type: STD FW MTS (0x00000004), id: SECAM/K3 (0000000004000000), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x04000000); # ID + write_le32(169); # Size + write_hunk_fix_endian(868000, 169); + + # + # Firmware 22, type: STD FW D2633 DTV6 ATSC (0x00010030), id: (0000000000000000), size: 149 + # + + write_le32(0x00010030); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868176, 149); + + # + # Firmware 23, type: STD FW D2620 DTV6 QAM (0x00000068), id: (0000000000000000), size: 149 + # + + write_le32(0x00000068); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868336, 149); + + # + # Firmware 24, type: STD FW D2633 DTV6 QAM (0x00000070), id: (0000000000000000), size: 149 + # + + write_le32(0x00000070); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868488, 149); + + # + # Firmware 25, type: STD FW D2620 DTV7 (0x00000088), id: (0000000000000000), size: 149 + # + + write_le32(0x00000088); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868648, 149); + + # + # Firmware 26, type: STD FW D2633 DTV7 (0x00000090), id: (0000000000000000), size: 149 + # + + write_le32(0x00000090); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868800, 149); + + # + # Firmware 27, type: STD FW D2620 DTV78 (0x00000108), id: (0000000000000000), size: 149 + # + + write_le32(0x00000108); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868960, 149); + + # + # Firmware 28, type: STD FW D2633 DTV78 (0x00000110), id: (0000000000000000), size: 149 + # + + write_le32(0x00000110); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(869112, 149); + + # + # Firmware 29, type: STD FW D2620 DTV8 (0x00000208), id: (0000000000000000), size: 149 + # + + write_le32(0x00000208); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868648, 149); + + # + # Firmware 30, type: STD FW D2633 DTV8 (0x00000210), id: (0000000000000000), size: 149 + # + + write_le32(0x00000210); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(149); # Size + write_hunk_fix_endian(868800, 149); + + # + # Firmware 31, type: STD FW FM (0x00000400), id: (0000000000000000), size: 135 + # + + write_le32(0x00000400); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le32(135); # Size + write_hunk_fix_endian(869584, 135); + + # + # Firmware 32, type: STD FW (0x00000000), id: PAL/I (0000000000000010), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00000010); # ID + write_le32(161); # Size + write_hunk_fix_endian(869728, 161); + + # + # Firmware 33, type: STD FW MTS (0x00000004), id: PAL/I (0000000000000010), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x00000010); # ID + write_le32(169); # Size + write_hunk_fix_endian(869896, 169); + + # + # Firmware 34, type: STD FW (0x00000000), id: SECAM/L AM (0000001000400000), size: 169 + # + + write_le32(0x00000000); # Type + write_le64(0x00000010, 0x00400000); # ID + write_le32(169); # Size + write_hunk_fix_endian(870072, 169); + + # + # Firmware 35, type: STD FW (0x00000000), id: SECAM/L NICAM (0000000c00400000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x0000000c, 0x00400000); # ID + write_le32(161); # Size + write_hunk_fix_endian(870248, 161); + + # + # Firmware 36, type: STD FW (0x00000000), id: SECAM/Lc (0000000000800000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00800000); # ID + write_le32(161); # Size + write_hunk_fix_endian(870416, 161); + + # + # Firmware 37, type: STD FW (0x00000000), id: NTSC/M Kr (0000000000008000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(161); # Size + write_hunk_fix_endian(870584, 161); + + # + # Firmware 38, type: STD FW LCD (0x00001000), id: NTSC/M Kr (0000000000008000), size: 161 + # + + write_le32(0x00001000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(161); # Size + write_hunk_fix_endian(870752, 161); + + # + # Firmware 39, type: STD FW LCD NOGD (0x00003000), id: NTSC/M Kr (0000000000008000), size: 161 + # + + write_le32(0x00003000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(161); # Size + write_hunk_fix_endian(870920, 161); + + # + # Firmware 40, type: STD FW MTS (0x00000004), id: NTSC/M Kr (0000000000008000), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le32(169); # Size + write_hunk_fix_endian(871088, 169); + + # + # Firmware 41, type: STD FW (0x00000000), id: NTSC PAL/M PAL/N (000000000000b700), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(161); # Size + write_hunk_fix_endian(871264, 161); + + # + # Firmware 42, type: STD FW LCD (0x00001000), id: NTSC PAL/M PAL/N (000000000000b700), size: 161 + # + + write_le32(0x00001000); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(161); # Size + write_hunk_fix_endian(871432, 161); + + # + # Firmware 43, type: STD FW LCD NOGD (0x00003000), id: NTSC PAL/M PAL/N (000000000000b700), size: 161 + # + + write_le32(0x00003000); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(161); # Size + write_hunk_fix_endian(871600, 161); + + # + # Firmware 44, type: STD FW (0x00000000), id: NTSC/M Jp (0000000000002000), size: 161 + # + + write_le32(0x00000000); # Type + write_le64(0x00000000, 0x00002000); # ID + write_le32(161); # Size + write_hunk_fix_endian(871264, 161); + + # + # Firmware 45, type: STD FW MTS (0x00000004), id: NTSC PAL/M PAL/N (000000000000b700), size: 169 + # + + write_le32(0x00000004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(169); # Size + write_hunk_fix_endian(871936, 169); + + # + # Firmware 46, type: STD FW MTS LCD (0x00001004), id: NTSC PAL/M PAL/N (000000000000b700), size: 169 + # + + write_le32(0x00001004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(169); # Size + write_hunk_fix_endian(872112, 169); + + # + # Firmware 47, type: STD FW MTS LCD NOGD (0x00003004), id: NTSC PAL/M PAL/N (000000000000b700), size: 169 + # + + write_le32(0x00003004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le32(169); # Size + write_hunk_fix_endian(872288, 169); + + # + # Firmware 48, type: SCODE FW HAS IF (0x60000000), IF = 3.28 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3280); # IF + write_le32(192); # Size + write_hunk(811896, 192); + + # + # Firmware 49, type: SCODE FW HAS IF (0x60000000), IF = 3.30 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3300); # IF + write_le32(192); # Size + write_hunk(813048, 192); + + # + # Firmware 50, type: SCODE FW HAS IF (0x60000000), IF = 3.44 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3440); # IF + write_le32(192); # Size + write_hunk(812280, 192); + + # + # Firmware 51, type: SCODE FW HAS IF (0x60000000), IF = 3.46 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3460); # IF + write_le32(192); # Size + write_hunk(812472, 192); + + # + # Firmware 52, type: SCODE FW DTV6 ATSC OREN36 HAS IF (0x60210020), IF = 3.80 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60210020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(3800); # IF + write_le32(192); # Size + write_hunk(809784, 192); + + # + # Firmware 53, type: SCODE FW HAS IF (0x60000000), IF = 4.00 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4000); # IF + write_le32(192); # Size + write_hunk(812088, 192); + + # + # Firmware 54, type: SCODE FW DTV6 ATSC TOYOTA388 HAS IF (0x60410020), IF = 4.08 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60410020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4080); # IF + write_le32(192); # Size + write_hunk(809976, 192); + + # + # Firmware 55, type: SCODE FW HAS IF (0x60000000), IF = 4.20 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4200); # IF + write_le32(192); # Size + write_hunk(811704, 192); + + # + # Firmware 56, type: SCODE FW MONO HAS IF (0x60008000), IF = 4.32 MHz id: NTSC/M Kr (0000000000008000), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le16(4320); # IF + write_le32(192); # Size + write_hunk(808056, 192); + + # + # Firmware 57, type: SCODE FW HAS IF (0x60000000), IF = 4.45 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4450); # IF + write_le32(192); # Size + write_hunk(812664, 192); + + # + # Firmware 58, type: SCODE FW MTS LCD NOGD MONO IF HAS IF (0x6002b004), IF = 4.50 MHz id: NTSC PAL/M PAL/N (000000000000b700), size: 192 + # + + write_le32(0x6002b004); # Type + write_le64(0x00000000, 0x0000b700); # ID + write_le16(4500); # IF + write_le32(192); # Size + write_hunk(807672, 192); + + # + # Firmware 59, type: SCODE FW LCD NOGD IF HAS IF (0x60023000), IF = 4.60 MHz id: NTSC/M Kr (0000000000008000), size: 192 + # + + write_le32(0x60023000); # Type + write_le64(0x00000000, 0x00008000); # ID + write_le16(4600); # IF + write_le32(192); # Size + write_hunk(807864, 192); + + # + # Firmware 60, type: SCODE FW DTV6 QAM DTV7 DTV78 DTV8 ZARLINK456 HAS IF (0x620003e0), IF = 4.76 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x620003e0); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4760); # IF + write_le32(192); # Size + write_hunk(807288, 192); + + # + # Firmware 61, type: SCODE FW HAS IF (0x60000000), IF = 4.94 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(4940); # IF + write_le32(192); # Size + write_hunk(811512, 192); + + # + # Firmware 62, type: SCODE FW HAS IF (0x60000000), IF = 5.26 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5260); # IF + write_le32(192); # Size + write_hunk(810552, 192); + + # + # Firmware 63, type: SCODE FW MONO HAS IF (0x60008000), IF = 5.32 MHz id: PAL/BG A2 NICAM (0000000f00000007), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x0000000f, 0x00000007); # ID + write_le16(5320); # IF + write_le32(192); # Size + write_hunk(810744, 192); + + # + # Firmware 64, type: SCODE FW DTV7 DTV78 DTV8 DIBCOM52 CHINA HAS IF (0x65000380), IF = 5.40 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x65000380); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5400); # IF + write_le32(192); # Size + write_hunk(807096, 192); + + # + # Firmware 65, type: SCODE FW DTV6 ATSC OREN538 HAS IF (0x60110020), IF = 5.58 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60110020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5580); # IF + write_le32(192); # Size + write_hunk(809592, 192); + + # + # Firmware 66, type: SCODE FW HAS IF (0x60000000), IF = 5.64 MHz id: PAL/BG A2 (0000000300000007), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000003, 0x00000007); # ID + write_le16(5640); # IF + write_le32(192); # Size + write_hunk(808440, 192); + + # + # Firmware 67, type: SCODE FW HAS IF (0x60000000), IF = 5.74 MHz id: PAL/BG NICAM (0000000c00000007), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x0000000c, 0x00000007); # ID + write_le16(5740); # IF + write_le32(192); # Size + write_hunk(808632, 192); + + # + # Firmware 68, type: SCODE FW HAS IF (0x60000000), IF = 5.90 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(5900); # IF + write_le32(192); # Size + write_hunk(810360, 192); + + # + # Firmware 69, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.00 MHz id: PAL/DK PAL/I SECAM/K3 SECAM/L SECAM/Lc NICAM (0000000c04c000f0), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x0000000c, 0x04c000f0); # ID + write_le16(6000); # IF + write_le32(192); # Size + write_hunk(808824, 192); + + # + # Firmware 70, type: SCODE FW DTV6 QAM ATSC LG60 F6MHZ HAS IF (0x68050060), IF = 6.20 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x68050060); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(6200); # IF + write_le32(192); # Size + write_hunk(809400, 192); + + # + # Firmware 71, type: SCODE FW HAS IF (0x60000000), IF = 6.24 MHz id: PAL/I (0000000000000010), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000010); # ID + write_le16(6240); # IF + write_le32(192); # Size + write_hunk(808248, 192); + + # + # Firmware 72, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.32 MHz id: SECAM/K1 (0000000000200000), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le16(6320); # IF + write_le32(192); # Size + write_hunk(811320, 192); + + # + # Firmware 73, type: SCODE FW HAS IF (0x60000000), IF = 6.34 MHz id: SECAM/K1 (0000000000200000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00200000); # ID + write_le16(6340); # IF + write_le32(192); # Size + write_hunk(809208, 192); + + # + # Firmware 74, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.50 MHz id: PAL/DK SECAM/K3 SECAM/L NICAM (0000000c044000e0), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x0000000c, 0x044000e0); # ID + write_le16(6500); # IF + write_le32(192); # Size + write_hunk(811128, 192); + + # + # Firmware 75, type: SCODE FW DTV6 ATSC ATI638 HAS IF (0x60090020), IF = 6.58 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60090020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(6580); # IF + write_le32(192); # Size + write_hunk(807480, 192); + + # + # Firmware 76, type: SCODE FW HAS IF (0x60000000), IF = 6.60 MHz id: PAL/DK A2 (00000003000000e0), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le16(6600); # IF + write_le32(192); # Size + write_hunk(809016, 192); + + # + # Firmware 77, type: SCODE FW MONO HAS IF (0x60008000), IF = 6.68 MHz id: PAL/DK A2 (00000003000000e0), size: 192 + # + + write_le32(0x60008000); # Type + write_le64(0x00000003, 0x000000e0); # ID + write_le16(6680); # IF + write_le32(192); # Size + write_hunk(810936, 192); + + # + # Firmware 78, type: SCODE FW DTV6 ATSC TOYOTA794 HAS IF (0x60810020), IF = 8.14 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60810020); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(8140); # IF + write_le32(192); # Size + write_hunk(810168, 192); + + # + # Firmware 79, type: SCODE FW HAS IF (0x60000000), IF = 8.20 MHz id: (0000000000000000), size: 192 + # + + write_le32(0x60000000); # Type + write_le64(0x00000000, 0x00000000); # ID + write_le16(8200); # IF + write_le32(192); # Size + write_hunk(812856, 192); +} + + +sub extract_firmware { + my $sourcefile_24 = "UDXTTM6000.sys"; + my $hash_24 = "cb9deb5508a5e150af2880f5b0066d78"; + my $outfile_24 = "xc3028-v24.fw"; + my $name_24 = "xc2028 firmware"; + my $version_24 = 516; + my $nr_desc_24 = 77; + my $out; + + my $sourcefile_27 = "hcw85bda.sys"; + my $hash_27 = "0e44dbf63bb0169d57446aec21881ff2"; + my $outfile_27 = "xc3028-v27.fw"; + my $name_27 = "xc2028 firmware"; + my $version_27 = 519; + my $nr_desc_27 = 80; + my $out; + + if (-e $sourcefile_24) { + verify($sourcefile_24, $hash_24); + + open INFILE, "<$sourcefile_24"; + main_firmware_24($outfile_24, $name_24, $version_24, $nr_desc_24); + close INFILE; + } + + if (-e $sourcefile_27) { + verify($sourcefile_27, $hash_27); + + open INFILE, "<$sourcefile_27"; + main_firmware_27($outfile_27, $name_27, $version_27, $nr_desc_27); + close INFILE; + } +} + +extract_firmware; +printf "Firmwares generated.\n"; diff --git a/scripts/faddr2line b/scripts/faddr2line new file mode 100755 index 000000000..622875396 --- /dev/null +++ b/scripts/faddr2line @@ -0,0 +1,380 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# +# Translate stack dump function offsets. +# +# addr2line doesn't work with KASLR addresses. This works similarly to +# addr2line, but instead takes the 'func+0x123' format as input: +# +# $ ./scripts/faddr2line ~/k/vmlinux meminfo_proc_show+0x5/0x568 +# meminfo_proc_show+0x5/0x568: +# meminfo_proc_show at fs/proc/meminfo.c:27 +# +# If the address is part of an inlined function, the full inline call chain is +# printed: +# +# $ ./scripts/faddr2line ~/k/vmlinux native_write_msr+0x6/0x27 +# native_write_msr+0x6/0x27: +# arch_static_branch at arch/x86/include/asm/msr.h:121 +# (inlined by) static_key_false at include/linux/jump_label.h:125 +# (inlined by) native_write_msr at arch/x86/include/asm/msr.h:125 +# +# The function size after the '/' in the input is optional, but recommended. +# It's used to help disambiguate any duplicate symbol names, which can occur +# rarely. If the size is omitted for a duplicate symbol then it's possible for +# multiple code sites to be printed: +# +# $ ./scripts/faddr2line ~/k/vmlinux raw_ioctl+0x5 +# raw_ioctl+0x5/0x20: +# raw_ioctl at drivers/char/raw.c:122 +# +# raw_ioctl+0x5/0xb1: +# raw_ioctl at net/ipv4/raw.c:876 +# +# Multiple addresses can be specified on a single command line: +# +# $ ./scripts/faddr2line ~/k/vmlinux type_show+0x10/45 free_reserved_area+0x90 +# type_show+0x10/0x2d: +# type_show at drivers/video/backlight/backlight.c:213 +# +# free_reserved_area+0x90/0x123: +# free_reserved_area at mm/page_alloc.c:6429 (discriminator 2) + + +set -o errexit +set -o nounset + +usage() { + echo "usage: faddr2line [--list] <object file> <func+offset> <func+offset>..." >&2 + exit 1 +} + +warn() { + echo "$1" >&2 +} + +die() { + echo "ERROR: $1" >&2 + exit 1 +} + +UTIL_SUFFIX="" +if [[ "${LLVM:-}" == "" ]]; then + UTIL_PREFIX=${CROSS_COMPILE:-} +else + UTIL_PREFIX=llvm- + + if [[ "${LLVM}" == *"/" ]]; then + UTIL_PREFIX=${LLVM}${UTIL_PREFIX} + elif [[ "${LLVM}" == "-"* ]]; then + UTIL_SUFFIX=${LLVM} + fi +fi + +READELF="${UTIL_PREFIX}readelf${UTIL_SUFFIX}" +ADDR2LINE="${UTIL_PREFIX}addr2line${UTIL_SUFFIX}" +AWK="awk" +GREP="grep" + +# Enforce ASCII-only output from tools like readelf +# ensuring sed processes strings correctly. +export LANG=C + +command -v ${AWK} >/dev/null 2>&1 || die "${AWK} isn't installed" +command -v ${READELF} >/dev/null 2>&1 || die "${READELF} isn't installed" +command -v ${ADDR2LINE} >/dev/null 2>&1 || die "${ADDR2LINE} isn't installed" + +# Try to figure out the source directory prefix so we can remove it from the +# addr2line output. HACK ALERT: This assumes that start_kernel() is in +# init/main.c! This only works for vmlinux. Otherwise it falls back to +# printing the absolute path. +find_dir_prefix() { + local start_kernel_addr=$(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | + ${AWK} '$8 == "start_kernel" {printf "0x%s", $2}') + [[ -z $start_kernel_addr ]] && 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 + fi + + DIR_PREFIX=$prefix + return 0 +} + +run_readelf() { + local objfile=$1 + local tmpfile + tmpfile=$(mktemp) + + ${READELF} --file-header --section-headers --symbols --wide "$objfile" > "$tmpfile" + + # 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=$(sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/q;p' "$tmpfile") + ELF_SECHEADERS=$(sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/,$p' "$tmpfile" | sed -n '/Symbol table .* contains [0-9]* entries:/q;p') + ELF_SYMS=$(sed -n '/Symbol table .* contains [0-9]* entries:/,$p' "$tmpfile") + + rm -f -- "$tmpfile" +} + +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 + local dir_prefix=$3 + local print_warnings=$4 + + local sym_name=${func_addr%+*} + local func_offset=${func_addr#*+} + func_offset=${func_offset%/*} + local user_size= + [[ $func_addr =~ "/" ]] && user_size=${func_addr#*/} + + if [[ -z $sym_name ]] || [[ -z $func_offset ]] || [[ $sym_name = $func_addr ]]; then + warn "bad func+offset $func_addr" + DONE=1 + return + 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. + while read line; do + local fields=($line) + local sym_addr=0x${fields[1]} + local sym_elf_size=${fields[2]} + local sym_sec=${fields[6]} + local sec_size + local sec_name + + # Get the section size: + sec_size=$(echo "${ELF_SECHEADERS}" | sed 's/\[ /\[/' | + ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print "0x" $6; exit }') + + if [[ -z $sec_size ]]; then + warn "bad section size: section: $sym_sec" + DONE=1 + return + fi + + # Get the section name: + sec_name=$(echo "${ELF_SECHEADERS}" | sed 's/\[ /\[/' | + ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print $2; exit }') + + if [[ -z $sec_name ]]; then + warn "bad section name: section: $sym_sec" + DONE=1 + return + fi + + # Calculate the symbol size. + # + # Unfortunately we can't use the ELF size, because kallsyms + # also includes the padding bytes in its size calculation. For + # kallsyms, the size calculation is the distance between the + # symbol and the next symbol in a sorted list. + local sym_size + local cur_sym_addr + local found=0 + while read line; do + local fields=($line) + cur_sym_addr=0x${fields[1]} + local cur_sym_elf_size=${fields[2]} + local cur_sym_name=${fields[7]:-} + + # is_mapping_symbol(cur_sym_name) + if [[ ${cur_sym_name} =~ ^(\.L|L0|\$) ]]; then + continue + fi + + if [[ $cur_sym_addr = $sym_addr ]] && + [[ $cur_sym_elf_size = $sym_elf_size ]] && + [[ $cur_sym_name = $sym_name ]]; then + found=1 + continue + fi + + if [[ $found = 1 ]]; then + sym_size=$(($cur_sym_addr - $sym_addr)) + [[ $sym_size -lt $sym_elf_size ]] && continue; + found=2 + break + fi + 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" + DONE=1 + return + fi + + # If nothing was found after the symbol, assume it's the last + # symbol in the section. + [[ $found = 1 ]] && sym_size=$(($sec_size - $sym_addr)) + + if [[ -z $sym_size ]] || [[ $sym_size -le 0 ]]; then + warn "bad symbol size: sym_addr: $sym_addr cur_sym_addr: $cur_sym_addr" + DONE=1 + return + fi + + sym_size=0x$(printf %x $sym_size) + + # Calculate the address from user-supplied offset: + local addr=$(($sym_addr + $func_offset)) + if [[ -z $addr ]] || [[ $addr = 0 ]]; then + warn "bad address: $sym_addr + $func_offset" + DONE=1 + return + fi + addr=0x$(printf %x $addr) + + # If the user provided a size, make sure it matches the symbol's size: + if [[ -n $user_size ]] && [[ $user_size -ne $sym_size ]]; then + [[ $print_warnings = 1 ]] && + echo "skipping $sym_name address at $addr due to size mismatch ($user_size != $sym_size)" + continue; + fi + + # Make sure the provided offset is within the symbol's range: + if [[ $func_offset -gt $sym_size ]]; then + [[ $print_warnings = 1 ]] && + echo "skipping $sym_name address at $addr due to size mismatch ($func_offset > $sym_size)" + continue + fi + + # In case of duplicates or multiple addresses specified on the + # cmdline, separate multiple entries with a blank line: + [[ $FIRST = 0 ]] && echo + FIRST=0 + + echo "$sym_name+$func_offset/$sym_size:" + + # Pass section address to addr2line and strip absolute paths + # from the output: + run_addr2line $addr $sec_name + local output=$(echo "${ADDR2LINE_OUT}" | sed "s; $dir_prefix\(\./\)*; ;") + [[ -z $output ]] && continue + + # Default output (non --list): + if [[ $LIST = 0 ]]; then + echo "$output" | while read -r line + do + echo $line + done + DONE=1; + continue + fi + + # For --list, show each line with its corresponding source code: + echo "$output" | while read -r line + do + echo + echo $line + n=$(echo $line | sed 's/.*:\([0-9]\+\).*/\1/g') + n1=$[$n-5] + n2=$[$n+5] + f=$(echo $line | sed 's/.*at \(.\+\):.*/\1/g') + ${AWK} 'NR>=strtonum("'$n1'") && NR<=strtonum("'$n2'") { if (NR=='$n') printf(">%d<", NR); else printf(" %d ", NR); printf("\t%s\n", $0)}' $f + done + + DONE=1 + + done < <(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | ${AWK} -v fn=$sym_name '$8 == fn') +} + +[[ $# -lt 2 ]] && usage + +objfile=$1 + +LIST=0 +[[ "$objfile" == "--list" ]] && LIST=1 && shift && objfile=$1 + +[[ ! -f $objfile ]] && die "can't find objfile $objfile" +shift + +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 + +FIRST=1 +while [[ $# -gt 0 ]]; do + func_addr=$1 + shift + + # print any matches found + DONE=0 + __faddr2line $objfile $func_addr $DIR_PREFIX 0 + + # if no match was found, print warnings + if [[ $DONE = 0 ]]; then + __faddr2line $objfile $func_addr $DIR_PREFIX 1 + warn "no match for $func_addr" + fi +done diff --git a/scripts/file-size.sh b/scripts/file-size.sh new file mode 100755 index 000000000..7eb742341 --- /dev/null +++ b/scripts/file-size.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +set -- $(ls -dn "$1") +printf '%s\n' "$5" diff --git a/scripts/gcc-plugins/.gitignore b/scripts/gcc-plugins/.gitignore new file mode 100644 index 000000000..5cc385b9e --- /dev/null +++ b/scripts/gcc-plugins/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +/randomize_layout_seed.h diff --git a/scripts/gcc-plugins/Kconfig b/scripts/gcc-plugins/Kconfig new file mode 100644 index 000000000..6b34ba193 --- /dev/null +++ b/scripts/gcc-plugins/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +config HAVE_GCC_PLUGINS + bool + help + An arch should select this symbol if it supports building with + GCC plugins. + +menuconfig GCC_PLUGINS + bool "GCC plugins" + depends on HAVE_GCC_PLUGINS + depends on CC_IS_GCC + depends on $(success,test -e $(shell,$(CC) -print-file-name=plugin)/include/plugin-version.h) + default y + help + GCC plugins are loadable modules that provide extra features to the + compiler. They are useful for runtime instrumentation and static analysis. + + See Documentation/kbuild/gcc-plugins.rst for details. + +if GCC_PLUGINS + +config GCC_PLUGIN_LATENT_ENTROPY + bool "Generate some entropy during boot and runtime" + help + By saying Y here the kernel will instrument some kernel code to + extract some entropy from both original and artificially created + program state. This will help especially embedded systems where + there is little 'natural' source of entropy normally. The cost + is some slowdown of the boot process (about 0.5%) and fork and + irq processing. + + Note that entropy extracted this way is not cryptographically + secure! + + This plugin was ported from grsecurity/PaX. More information at: + * https://grsecurity.net/ + * https://pax.grsecurity.net/ + +endif diff --git a/scripts/gcc-plugins/Makefile b/scripts/gcc-plugins/Makefile new file mode 100644 index 000000000..05b14aba4 --- /dev/null +++ b/scripts/gcc-plugins/Makefile @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: GPL-2.0 + +$(obj)/randomize_layout_plugin.so: $(obj)/randomize_layout_seed.h +quiet_cmd_create_randomize_layout_seed = SEEDHDR $@ +cmd_create_randomize_layout_seed = \ + SEED=$$(cat $(filter-out FORCE,$^) </dev/null); \ + echo '/*' > $@; \ + echo ' * This file is automatically generated. Keep it private.' >> $@; \ + echo ' * Exposing this value will expose the layout of randomized structures.' >> $@; \ + echo ' */' >> $@; \ + echo "const char *randstruct_seed = \"$$SEED\";" >> $@ +$(obj)/randomize_layout_seed.h: $(objtree)/scripts/basic/randstruct.seed FORCE + $(call if_changed,create_randomize_layout_seed) +targets += randomize_layout_seed.h + +# Build rules for plugins +# +# No extra code is needed for single-file plugins. +# For multi-file plugins, use *-objs syntax to list the objects. +# +# If the plugin foo.so is compiled from foo.c and foo2.c, you can do: +# +# foo-objs := foo.o foo2.o + +always-y += $(GCC_PLUGIN) + +GCC_PLUGINS_DIR = $(shell $(CC) -print-file-name=plugin) + +plugin_cxxflags = -Wp,-MMD,$(depfile) $(KBUILD_HOSTCXXFLAGS) -fPIC \ + -include $(srctree)/include/linux/compiler-version.h \ + -DPLUGIN_VERSION=$(call stringify,$(KERNELVERSION)) \ + -I $(GCC_PLUGINS_DIR)/include -I $(obj) \ + -fno-rtti -fno-exceptions -fasynchronous-unwind-tables \ + -ggdb -Wno-narrowing -Wno-unused-variable \ + -Wno-format-diag + +plugin_ldflags = -shared + +plugin-single := $(foreach m, $(GCC_PLUGIN), $(if $($(m:%.so=%-objs)),,$(m))) +plugin-multi := $(filter-out $(plugin-single), $(GCC_PLUGIN)) +plugin-objs := $(sort $(foreach m, $(plugin-multi), $($(m:%.so=%-objs)))) + +targets += $(plugin-single) $(plugin-multi) $(plugin-objs) +clean-files += *.so + +plugin-single := $(addprefix $(obj)/, $(plugin-single)) +plugin-multi := $(addprefix $(obj)/, $(plugin-multi)) +plugin-objs := $(addprefix $(obj)/, $(plugin-objs)) + +quiet_cmd_plugin_cxx_so_c = HOSTCXX $@ + cmd_plugin_cxx_so_c = $(HOSTCXX) $(plugin_cxxflags) $(plugin_ldflags) -o $@ $< + +$(plugin-single): $(obj)/%.so: $(src)/%.c FORCE + $(call if_changed_dep,plugin_cxx_so_c) + +quiet_cmd_plugin_ld_so_o = HOSTLD $@ + cmd_plugin_ld_so_o = $(HOSTCXX) $(plugin_ldflags) -o $@ \ + $(addprefix $(obj)/, $($(target-stem)-objs)) + +$(plugin-multi): FORCE + $(call if_changed,plugin_ld_so_o) +$(foreach m, $(notdir $(plugin-multi)), $(eval $(obj)/$m: $(addprefix $(obj)/, $($(m:%.so=%-objs))))) + +quiet_cmd_plugin_cxx_o_c = HOSTCXX $@ + cmd_plugin_cxx_o_c = $(HOSTCXX) $(plugin_cxxflags) -c -o $@ $< + +$(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/gcc-common.h b/scripts/gcc-plugins/gcc-common.h new file mode 100644 index 000000000..abb1964c4 --- /dev/null +++ b/scripts/gcc-plugins/gcc-common.h @@ -0,0 +1,438 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef GCC_COMMON_H_INCLUDED +#define GCC_COMMON_H_INCLUDED + +#include "bversion.h" +#include "gcc-plugin.h" +#include "plugin-version.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "line-map.h" +#include "input.h" +#include "tree.h" + +#include "tree-inline.h" +#include "version.h" +#include "rtl.h" +#include "tm_p.h" +#include "flags.h" +#include "hard-reg-set.h" +#include "output.h" +#include "except.h" +#include "function.h" +#include "toplev.h" +#include "expr.h" +#include "basic-block.h" +#include "intl.h" +#include "ggc.h" +#include "timevar.h" + +#if BUILDING_GCC_VERSION < 10000 +#include "params.h" +#endif + +#include "hash-map.h" + +#include "memmodel.h" +#include "emit-rtl.h" +#include "debug.h" +#include "target.h" +#include "langhooks.h" +#include "cfgloop.h" +#include "cgraph.h" +#include "opts.h" +#include "tree-pretty-print.h" +#include "gimple-pretty-print.h" +#include "c-family/c-common.h" +#include "tree-cfgcleanup.h" +#include "tree-ssa-operands.h" +#include "tree-into-ssa.h" +#include "is-a.h" +#include "diagnostic.h" +#include "tree-dump.h" +#include "tree-pass.h" +#include "pass_manager.h" +#include "predict.h" +#include "ipa-utils.h" +#include "stringpool.h" +#include "attribs.h" +#include "varasm.h" +#include "stor-layout.h" +#include "internal-fn.h" +#include "gimple.h" +#include "gimple-expr.h" +#include "gimple-iterator.h" +#include "gimple-fold.h" +#include "context.h" +#include "tree-ssa-alias.h" +#include "tree-ssa.h" +#include "tree-vrp.h" +#include "tree-ssanames.h" +#include "print-tree.h" +#include "tree-eh.h" +#include "stmt.h" +#include "gimplify.h" +#include "tree-phinodes.h" +#include "tree-cfg.h" +#include "gimple-ssa.h" +#include "ssa-iterators.h" + +#include "builtins.h" + +/* missing from basic_block.h... */ +void debug_dominance_info(enum cdi_direction dir); +void debug_dominance_tree(enum cdi_direction dir, basic_block root); + +#ifndef __unused +#define __unused __attribute__((__unused__)) +#endif +#ifndef __visible +#define __visible __attribute__((visibility("default"))) +#endif + +#define DECL_NAME_POINTER(node) IDENTIFIER_POINTER(DECL_NAME(node)) +#define DECL_NAME_LENGTH(node) IDENTIFIER_LENGTH(DECL_NAME(node)) +#define TYPE_NAME_POINTER(node) IDENTIFIER_POINTER(TYPE_NAME(node)) +#define TYPE_NAME_LENGTH(node) IDENTIFIER_LENGTH(TYPE_NAME(node)) + +/* should come from c-tree.h if only it were installed for gcc 4.5... */ +#define C_TYPE_FIELDS_READONLY(TYPE) TREE_LANG_FLAG_1(TYPE) + +static inline tree build_const_char_string(int len, const char *str) +{ + tree cstr, elem, index, type; + + cstr = build_string(len, str); + elem = build_type_variant(char_type_node, 1, 0); + index = build_index_type(size_int(len - 1)); + type = build_array_type(elem, index); + TREE_TYPE(cstr) = type; + TREE_CONSTANT(cstr) = 1; + TREE_READONLY(cstr) = 1; + TREE_STATIC(cstr) = 1; + 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(), \ + .reference_pass_name = REF, \ + .ref_pass_instance_number = ID, \ + .pos_op = POS, \ +} + +#define add_referenced_var(var) +#define mark_sym_for_renaming(var) +#define varpool_mark_needed_node(node) +#define create_var_ann(var) +#define TODO_dump_func 0 +#define TODO_dump_cgraph 0 + +#define TODO_ggc_collect 0 +#define NODE_SYMBOL(node) (node) +#define NODE_DECL(node) (node)->decl +#define cgraph_node_name(node) (node)->name() +#define NODE_IMPLICIT_ALIAS(node) (node)->cpp_implicit_alias + +static inline opt_pass *get_pass_for_id(int id) +{ + return g->get_passes()->get_pass_for_id(id); +} + +#if BUILDING_GCC_VERSION < 16000 +#define TODO_verify_ssa TODO_verify_il +#define TODO_verify_flow TODO_verify_il +#define TODO_verify_stmts TODO_verify_il +#define TODO_verify_rtl_sharing TODO_verify_il +#else +#define TODO_verify_ssa 0 +#define TODO_verify_flow 0 +#define TODO_verify_stmts 0 +#define TODO_verify_rtl_sharing 0 +#endif + +#define INSN_DELETED_P(insn) (insn)->deleted() + +static inline const char *get_decl_section_name(const_tree decl) +{ + return DECL_SECTION_NAME(decl); +} + +/* symtab/cgraph related */ +#define debug_cgraph_node(node) (node)->debug() +#define cgraph_get_node(decl) cgraph_node::get(decl) +#define cgraph_get_create_node(decl) cgraph_node::get_create(decl) +#define cgraph_create_node(decl) cgraph_node::create(decl) +#define cgraph_n_nodes symtab->cgraph_count +#define cgraph_max_uid symtab->cgraph_max_uid +#define varpool_get_node(decl) varpool_node::get(decl) +#define dump_varpool_node(file, node) (node)->dump(file) + +#define cgraph_create_edge(caller, callee, call_stmt, count, freq) \ + (caller)->create_edge((callee), (call_stmt), (count)) + +#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), (reason)) + +typedef struct cgraph_node *cgraph_node_ptr; +typedef struct cgraph_edge *cgraph_edge_p; +typedef struct varpool_node *varpool_node_ptr; + +static inline void change_decl_assembler_name(tree decl, tree name) +{ + symtab->change_decl_assembler_name(decl, name); +} + +static inline void varpool_finalize_decl(tree decl) +{ + varpool_node::finalize_decl(decl); +} + +static inline void varpool_add_new_variable(tree decl) +{ + varpool_node::add(decl); +} + +static inline unsigned int rebuild_cgraph_edges(void) +{ + return cgraph_edge::rebuild_edges(); +} + +static inline cgraph_node_ptr cgraph_function_node(cgraph_node_ptr node, enum availability *availability) +{ + return node->function_symbol(availability); +} + +static inline cgraph_node_ptr cgraph_function_or_thunk_node(cgraph_node_ptr node, enum availability *availability = NULL) +{ + return node->ultimate_alias_target(availability); +} + +static inline bool cgraph_only_called_directly_p(cgraph_node_ptr node) +{ + return node->only_called_directly_p(); +} + +static inline enum availability cgraph_function_body_availability(cgraph_node_ptr node) +{ + return node->get_availability(); +} + +static inline cgraph_node_ptr cgraph_alias_target(cgraph_node_ptr node) +{ + return node->get_alias_target(); +} + +static inline bool cgraph_for_node_and_aliases(cgraph_node_ptr node, bool (*callback)(cgraph_node_ptr, void *), void *data, bool include_overwritable) +{ + return node->call_for_symbol_thunks_and_aliases(callback, data, include_overwritable); +} + +static inline struct cgraph_node_hook_list *cgraph_add_function_insertion_hook(cgraph_node_hook hook, void *data) +{ + return symtab->add_cgraph_insertion_hook(hook, data); +} + +static inline void cgraph_remove_function_insertion_hook(struct cgraph_node_hook_list *entry) +{ + symtab->remove_cgraph_insertion_hook(entry); +} + +static inline struct cgraph_node_hook_list *cgraph_add_node_removal_hook(cgraph_node_hook hook, void *data) +{ + return symtab->add_cgraph_removal_hook(hook, data); +} + +static inline void cgraph_remove_node_removal_hook(struct cgraph_node_hook_list *entry) +{ + symtab->remove_cgraph_removal_hook(entry); +} + +static inline struct cgraph_2node_hook_list *cgraph_add_node_duplication_hook(cgraph_2node_hook hook, void *data) +{ + return symtab->add_cgraph_duplication_hook(hook, data); +} + +static inline void cgraph_remove_node_duplication_hook(struct cgraph_2node_hook_list *entry) +{ + symtab->remove_cgraph_duplication_hook(entry); +} + +static inline void cgraph_call_node_duplication_hooks(cgraph_node_ptr node, cgraph_node_ptr node2) +{ + symtab->call_cgraph_duplication_hooks(node, node2); +} + +static inline void cgraph_call_edge_duplication_hooks(cgraph_edge *cs1, cgraph_edge *cs2) +{ + symtab->call_edge_duplication_hooks(cs1, cs2); +} + +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)) +#undef CONST_CAST_TREE +#define CONST_CAST_TREE(X) const_cast<tree>((X)) + +/* gimple related */ +static inline gimple gimple_build_assign_with_ops(enum tree_code subcode, tree lhs, tree op1, tree op2 MEM_STAT_DECL) +{ + return gimple_build_assign(lhs, subcode, op1, op2 PASS_MEM_STAT); +} + +#if BUILDING_GCC_VERSION < 10000 +template <> +template <> +inline bool is_a_helper<const ggoto *>::test(const_gimple gs) +{ + return gs->code == GIMPLE_GOTO; +} + +template <> +template <> +inline bool is_a_helper<const greturn *>::test(const_gimple gs) +{ + return gs->code == GIMPLE_RETURN; +} +#endif + +static inline gasm *as_a_gasm(gimple stmt) +{ + return as_a<gasm *>(stmt); +} + +static inline const gasm *as_a_const_gasm(const_gimple stmt) +{ + return as_a<const gasm *>(stmt); +} + +static inline gassign *as_a_gassign(gimple stmt) +{ + return as_a<gassign *>(stmt); +} + +static inline const gassign *as_a_const_gassign(const_gimple stmt) +{ + return as_a<const gassign *>(stmt); +} + +static inline gcall *as_a_gcall(gimple stmt) +{ + return as_a<gcall *>(stmt); +} + +static inline const gcall *as_a_const_gcall(const_gimple stmt) +{ + return as_a<const gcall *>(stmt); +} + +static inline ggoto *as_a_ggoto(gimple stmt) +{ + return as_a<ggoto *>(stmt); +} + +static inline const ggoto *as_a_const_ggoto(const_gimple stmt) +{ + return as_a<const ggoto *>(stmt); +} + +static inline gphi *as_a_gphi(gimple stmt) +{ + return as_a<gphi *>(stmt); +} + +static inline const gphi *as_a_const_gphi(const_gimple stmt) +{ + return as_a<const gphi *>(stmt); +} + +static inline greturn *as_a_greturn(gimple stmt) +{ + return as_a<greturn *>(stmt); +} + +static inline const greturn *as_a_const_greturn(const_gimple stmt) +{ + return as_a<const greturn *>(stmt); +} + +/* IPA/LTO related */ +#define ipa_ref_list_referring_iterate(L, I, P) \ + (L)->referring.iterate((I), &(P)) +#define ipa_ref_list_reference_iterate(L, I, P) \ + (L)->reference.iterate((I), &(P)) + +static inline cgraph_node_ptr ipa_ref_referring_node(struct ipa_ref *ref) +{ + return dyn_cast<cgraph_node_ptr>(ref->referring); +} + +static inline void ipa_remove_stmt_references(symtab_node *referring_node, gimple stmt) +{ + referring_node->remove_stmt_references(stmt); +} + +#define gen_rtx_set(ARG0, ARG1) gen_rtx_SET((ARG0), (ARG1)) + +#ifdef __cplusplus +static inline void debug_tree(const_tree t) +{ + debug_tree(CONST_CAST_TREE(t)); +} + +static inline void debug_gimple_stmt(const_gimple s) +{ + debug_gimple_stmt(CONST_CAST_GIMPLE(s)); +} +#else +#define debug_tree(t) debug_tree(CONST_CAST_TREE(t)) +#define debug_gimple_stmt(s) debug_gimple_stmt(CONST_CAST_GIMPLE(s)) +#endif + +#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) + +#if BUILDING_GCC_VERSION >= 14000 +#define last_stmt(x) last_nondebug_stmt(x) +#endif + +#endif diff --git a/scripts/gcc-plugins/gcc-generate-gimple-pass.h b/scripts/gcc-plugins/gcc-generate-gimple-pass.h new file mode 100644 index 000000000..503c07496 --- /dev/null +++ b/scripts/gcc-plugins/gcc-generate-gimple-pass.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Generator for GIMPLE pass related boilerplate code/data + * + * Supports gcc 4.5-6 + * + * Usage: + * + * 1. before inclusion define PASS_NAME + * 2. before inclusion define NO_* for unimplemented callbacks + * NO_GATE + * NO_EXECUTE + * 3. before inclusion define PROPERTIES_* and TODO_FLAGS_* to override + * the default 0 values + * 4. for convenience, all the above will be undefined after inclusion! + * 5. the only exported name is make_PASS_NAME_pass() to register with gcc + */ + +#ifndef PASS_NAME +#error at least PASS_NAME must be defined +#else +#define __GCC_PLUGIN_STRINGIFY(n) #n +#define _GCC_PLUGIN_STRINGIFY(n) __GCC_PLUGIN_STRINGIFY(n) +#define _GCC_PLUGIN_CONCAT2(x, y) x ## y +#define _GCC_PLUGIN_CONCAT3(x, y, z) x ## y ## z + +#define __PASS_NAME_PASS_DATA(n) _GCC_PLUGIN_CONCAT2(n, _pass_data) +#define _PASS_NAME_PASS_DATA __PASS_NAME_PASS_DATA(PASS_NAME) + +#define __PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT2(n, _pass) +#define _PASS_NAME_PASS __PASS_NAME_PASS(PASS_NAME) + +#define _PASS_NAME_NAME _GCC_PLUGIN_STRINGIFY(PASS_NAME) + +#define __MAKE_PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT3(make_, n, _pass) +#define _MAKE_PASS_NAME_PASS __MAKE_PASS_NAME_PASS(PASS_NAME) + +#ifdef NO_GATE +#define _GATE NULL +#define _HAS_GATE false +#else +#define __GATE(n) _GCC_PLUGIN_CONCAT2(n, _gate) +#define _GATE __GATE(PASS_NAME) +#define _HAS_GATE true +#endif + +#ifdef NO_EXECUTE +#define _EXECUTE NULL +#define _HAS_EXECUTE false +#else +#define __EXECUTE(n) _GCC_PLUGIN_CONCAT2(n, _execute) +#define _EXECUTE __EXECUTE(PASS_NAME) +#define _HAS_EXECUTE true +#endif + +#ifndef PROPERTIES_REQUIRED +#define PROPERTIES_REQUIRED 0 +#endif + +#ifndef PROPERTIES_PROVIDED +#define PROPERTIES_PROVIDED 0 +#endif + +#ifndef PROPERTIES_DESTROYED +#define PROPERTIES_DESTROYED 0 +#endif + +#ifndef TODO_FLAGS_START +#define TODO_FLAGS_START 0 +#endif + +#ifndef TODO_FLAGS_FINISH +#define TODO_FLAGS_FINISH 0 +#endif + +namespace { +static const pass_data _PASS_NAME_PASS_DATA = { + .type = GIMPLE_PASS, + .name = _PASS_NAME_NAME, + .optinfo_flags = OPTGROUP_NONE, + .tv_id = TV_NONE, + .properties_required = PROPERTIES_REQUIRED, + .properties_provided = PROPERTIES_PROVIDED, + .properties_destroyed = PROPERTIES_DESTROYED, + .todo_flags_start = TODO_FLAGS_START, + .todo_flags_finish = TODO_FLAGS_FINISH, +}; + +class _PASS_NAME_PASS : public gimple_opt_pass { +public: + _PASS_NAME_PASS() : gimple_opt_pass(_PASS_NAME_PASS_DATA, g) {} + +#ifndef NO_GATE + virtual bool gate(function *) { return _GATE(); } +#endif + + virtual opt_pass * clone () { return new _PASS_NAME_PASS(); } + +#ifndef NO_EXECUTE + virtual unsigned int execute(function *) { return _EXECUTE(); } +}; +} + +opt_pass *_MAKE_PASS_NAME_PASS(void) +{ + return new _PASS_NAME_PASS(); +} +#else +struct opt_pass *_MAKE_PASS_NAME_PASS(void) +{ + return &_PASS_NAME_PASS.pass; +} +#endif + +/* clean up user provided defines */ +#undef PASS_NAME +#undef NO_GATE +#undef NO_EXECUTE + +#undef PROPERTIES_DESTROYED +#undef PROPERTIES_PROVIDED +#undef PROPERTIES_REQUIRED +#undef TODO_FLAGS_FINISH +#undef TODO_FLAGS_START + +/* clean up generated defines */ +#undef _EXECUTE +#undef __EXECUTE +#undef _GATE +#undef __GATE +#undef _GCC_PLUGIN_CONCAT2 +#undef _GCC_PLUGIN_CONCAT3 +#undef _GCC_PLUGIN_STRINGIFY +#undef __GCC_PLUGIN_STRINGIFY +#undef _HAS_EXECUTE +#undef _HAS_GATE +#undef _MAKE_PASS_NAME_PASS +#undef __MAKE_PASS_NAME_PASS +#undef _PASS_NAME_NAME +#undef _PASS_NAME_PASS +#undef __PASS_NAME_PASS +#undef _PASS_NAME_PASS_DATA +#undef __PASS_NAME_PASS_DATA + +#endif /* PASS_NAME */ diff --git a/scripts/gcc-plugins/gcc-generate-ipa-pass.h b/scripts/gcc-plugins/gcc-generate-ipa-pass.h new file mode 100644 index 000000000..1e7f064e8 --- /dev/null +++ b/scripts/gcc-plugins/gcc-generate-ipa-pass.h @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Generator for IPA pass related boilerplate code/data + * + * Supports gcc 4.5-6 + * + * Usage: + * + * 1. before inclusion define PASS_NAME + * 2. before inclusion define NO_* for unimplemented callbacks + * NO_GENERATE_SUMMARY + * NO_READ_SUMMARY + * NO_WRITE_SUMMARY + * NO_READ_OPTIMIZATION_SUMMARY + * NO_WRITE_OPTIMIZATION_SUMMARY + * NO_STMT_FIXUP + * NO_FUNCTION_TRANSFORM + * NO_VARIABLE_TRANSFORM + * NO_GATE + * NO_EXECUTE + * 3. before inclusion define PROPERTIES_* and *TODO_FLAGS_* to override + * the default 0 values + * 4. for convenience, all the above will be undefined after inclusion! + * 5. the only exported name is make_PASS_NAME_pass() to register with gcc + */ + +#ifndef PASS_NAME +#error at least PASS_NAME must be defined +#else +#define __GCC_PLUGIN_STRINGIFY(n) #n +#define _GCC_PLUGIN_STRINGIFY(n) __GCC_PLUGIN_STRINGIFY(n) +#define _GCC_PLUGIN_CONCAT2(x, y) x ## y +#define _GCC_PLUGIN_CONCAT3(x, y, z) x ## y ## z + +#define __PASS_NAME_PASS_DATA(n) _GCC_PLUGIN_CONCAT2(n, _pass_data) +#define _PASS_NAME_PASS_DATA __PASS_NAME_PASS_DATA(PASS_NAME) + +#define __PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT2(n, _pass) +#define _PASS_NAME_PASS __PASS_NAME_PASS(PASS_NAME) + +#define _PASS_NAME_NAME _GCC_PLUGIN_STRINGIFY(PASS_NAME) + +#define __MAKE_PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT3(make_, n, _pass) +#define _MAKE_PASS_NAME_PASS __MAKE_PASS_NAME_PASS(PASS_NAME) + +#ifdef NO_GENERATE_SUMMARY +#define _GENERATE_SUMMARY NULL +#else +#define __GENERATE_SUMMARY(n) _GCC_PLUGIN_CONCAT2(n, _generate_summary) +#define _GENERATE_SUMMARY __GENERATE_SUMMARY(PASS_NAME) +#endif + +#ifdef NO_READ_SUMMARY +#define _READ_SUMMARY NULL +#else +#define __READ_SUMMARY(n) _GCC_PLUGIN_CONCAT2(n, _read_summary) +#define _READ_SUMMARY __READ_SUMMARY(PASS_NAME) +#endif + +#ifdef NO_WRITE_SUMMARY +#define _WRITE_SUMMARY NULL +#else +#define __WRITE_SUMMARY(n) _GCC_PLUGIN_CONCAT2(n, _write_summary) +#define _WRITE_SUMMARY __WRITE_SUMMARY(PASS_NAME) +#endif + +#ifdef NO_READ_OPTIMIZATION_SUMMARY +#define _READ_OPTIMIZATION_SUMMARY NULL +#else +#define __READ_OPTIMIZATION_SUMMARY(n) _GCC_PLUGIN_CONCAT2(n, _read_optimization_summary) +#define _READ_OPTIMIZATION_SUMMARY __READ_OPTIMIZATION_SUMMARY(PASS_NAME) +#endif + +#ifdef NO_WRITE_OPTIMIZATION_SUMMARY +#define _WRITE_OPTIMIZATION_SUMMARY NULL +#else +#define __WRITE_OPTIMIZATION_SUMMARY(n) _GCC_PLUGIN_CONCAT2(n, _write_optimization_summary) +#define _WRITE_OPTIMIZATION_SUMMARY __WRITE_OPTIMIZATION_SUMMARY(PASS_NAME) +#endif + +#ifdef NO_STMT_FIXUP +#define _STMT_FIXUP NULL +#else +#define __STMT_FIXUP(n) _GCC_PLUGIN_CONCAT2(n, _stmt_fixup) +#define _STMT_FIXUP __STMT_FIXUP(PASS_NAME) +#endif + +#ifdef NO_FUNCTION_TRANSFORM +#define _FUNCTION_TRANSFORM NULL +#else +#define __FUNCTION_TRANSFORM(n) _GCC_PLUGIN_CONCAT2(n, _function_transform) +#define _FUNCTION_TRANSFORM __FUNCTION_TRANSFORM(PASS_NAME) +#endif + +#ifdef NO_VARIABLE_TRANSFORM +#define _VARIABLE_TRANSFORM NULL +#else +#define __VARIABLE_TRANSFORM(n) _GCC_PLUGIN_CONCAT2(n, _variable_transform) +#define _VARIABLE_TRANSFORM __VARIABLE_TRANSFORM(PASS_NAME) +#endif + +#ifdef NO_GATE +#define _GATE NULL +#define _HAS_GATE false +#else +#define __GATE(n) _GCC_PLUGIN_CONCAT2(n, _gate) +#define _GATE __GATE(PASS_NAME) +#define _HAS_GATE true +#endif + +#ifdef NO_EXECUTE +#define _EXECUTE NULL +#define _HAS_EXECUTE false +#else +#define __EXECUTE(n) _GCC_PLUGIN_CONCAT2(n, _execute) +#define _EXECUTE __EXECUTE(PASS_NAME) +#define _HAS_EXECUTE true +#endif + +#ifndef PROPERTIES_REQUIRED +#define PROPERTIES_REQUIRED 0 +#endif + +#ifndef PROPERTIES_PROVIDED +#define PROPERTIES_PROVIDED 0 +#endif + +#ifndef PROPERTIES_DESTROYED +#define PROPERTIES_DESTROYED 0 +#endif + +#ifndef TODO_FLAGS_START +#define TODO_FLAGS_START 0 +#endif + +#ifndef TODO_FLAGS_FINISH +#define TODO_FLAGS_FINISH 0 +#endif + +#ifndef FUNCTION_TRANSFORM_TODO_FLAGS_START +#define FUNCTION_TRANSFORM_TODO_FLAGS_START 0 +#endif + +namespace { +static const pass_data _PASS_NAME_PASS_DATA = { + .type = IPA_PASS, + .name = _PASS_NAME_NAME, + .optinfo_flags = OPTGROUP_NONE, + .tv_id = TV_NONE, + .properties_required = PROPERTIES_REQUIRED, + .properties_provided = PROPERTIES_PROVIDED, + .properties_destroyed = PROPERTIES_DESTROYED, + .todo_flags_start = TODO_FLAGS_START, + .todo_flags_finish = TODO_FLAGS_FINISH, +}; + +class _PASS_NAME_PASS : public ipa_opt_pass_d { +public: + _PASS_NAME_PASS() : ipa_opt_pass_d(_PASS_NAME_PASS_DATA, + g, + _GENERATE_SUMMARY, + _WRITE_SUMMARY, + _READ_SUMMARY, + _WRITE_OPTIMIZATION_SUMMARY, + _READ_OPTIMIZATION_SUMMARY, + _STMT_FIXUP, + FUNCTION_TRANSFORM_TODO_FLAGS_START, + _FUNCTION_TRANSFORM, + _VARIABLE_TRANSFORM) {} + +#ifndef NO_GATE + virtual bool gate(function *) { return _GATE(); } + + virtual opt_pass *clone() { return new _PASS_NAME_PASS(); } + +#ifndef NO_EXECUTE + virtual unsigned int execute(function *) { return _EXECUTE(); } +#endif +}; +} + +opt_pass *_MAKE_PASS_NAME_PASS(void) +{ + return new _PASS_NAME_PASS(); +} +#else +struct opt_pass *_MAKE_PASS_NAME_PASS(void) +{ + return &_PASS_NAME_PASS.pass; +} +#endif + +/* clean up user provided defines */ +#undef PASS_NAME +#undef NO_GENERATE_SUMMARY +#undef NO_WRITE_SUMMARY +#undef NO_READ_SUMMARY +#undef NO_WRITE_OPTIMIZATION_SUMMARY +#undef NO_READ_OPTIMIZATION_SUMMARY +#undef NO_STMT_FIXUP +#undef NO_FUNCTION_TRANSFORM +#undef NO_VARIABLE_TRANSFORM +#undef NO_GATE +#undef NO_EXECUTE + +#undef FUNCTION_TRANSFORM_TODO_FLAGS_START +#undef PROPERTIES_DESTROYED +#undef PROPERTIES_PROVIDED +#undef PROPERTIES_REQUIRED +#undef TODO_FLAGS_FINISH +#undef TODO_FLAGS_START + +/* clean up generated defines */ +#undef _EXECUTE +#undef __EXECUTE +#undef _FUNCTION_TRANSFORM +#undef __FUNCTION_TRANSFORM +#undef _GATE +#undef __GATE +#undef _GCC_PLUGIN_CONCAT2 +#undef _GCC_PLUGIN_CONCAT3 +#undef _GCC_PLUGIN_STRINGIFY +#undef __GCC_PLUGIN_STRINGIFY +#undef _GENERATE_SUMMARY +#undef __GENERATE_SUMMARY +#undef _HAS_EXECUTE +#undef _HAS_GATE +#undef _MAKE_PASS_NAME_PASS +#undef __MAKE_PASS_NAME_PASS +#undef _PASS_NAME_NAME +#undef _PASS_NAME_PASS +#undef __PASS_NAME_PASS +#undef _PASS_NAME_PASS_DATA +#undef __PASS_NAME_PASS_DATA +#undef _READ_OPTIMIZATION_SUMMARY +#undef __READ_OPTIMIZATION_SUMMARY +#undef _READ_SUMMARY +#undef __READ_SUMMARY +#undef _STMT_FIXUP +#undef __STMT_FIXUP +#undef _VARIABLE_TRANSFORM +#undef __VARIABLE_TRANSFORM +#undef _WRITE_OPTIMIZATION_SUMMARY +#undef __WRITE_OPTIMIZATION_SUMMARY +#undef _WRITE_SUMMARY +#undef __WRITE_SUMMARY + +#endif /* PASS_NAME */ diff --git a/scripts/gcc-plugins/gcc-generate-rtl-pass.h b/scripts/gcc-plugins/gcc-generate-rtl-pass.h new file mode 100644 index 000000000..7cd46e8d5 --- /dev/null +++ b/scripts/gcc-plugins/gcc-generate-rtl-pass.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Generator for RTL pass related boilerplate code/data + * + * Supports gcc 4.5-6 + * + * Usage: + * + * 1. before inclusion define PASS_NAME + * 2. before inclusion define NO_* for unimplemented callbacks + * NO_GATE + * NO_EXECUTE + * 3. before inclusion define PROPERTIES_* and TODO_FLAGS_* to override + * the default 0 values + * 4. for convenience, all the above will be undefined after inclusion! + * 5. the only exported name is make_PASS_NAME_pass() to register with gcc + */ + +#ifndef PASS_NAME +#error at least PASS_NAME must be defined +#else +#define __GCC_PLUGIN_STRINGIFY(n) #n +#define _GCC_PLUGIN_STRINGIFY(n) __GCC_PLUGIN_STRINGIFY(n) +#define _GCC_PLUGIN_CONCAT2(x, y) x ## y +#define _GCC_PLUGIN_CONCAT3(x, y, z) x ## y ## z + +#define __PASS_NAME_PASS_DATA(n) _GCC_PLUGIN_CONCAT2(n, _pass_data) +#define _PASS_NAME_PASS_DATA __PASS_NAME_PASS_DATA(PASS_NAME) + +#define __PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT2(n, _pass) +#define _PASS_NAME_PASS __PASS_NAME_PASS(PASS_NAME) + +#define _PASS_NAME_NAME _GCC_PLUGIN_STRINGIFY(PASS_NAME) + +#define __MAKE_PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT3(make_, n, _pass) +#define _MAKE_PASS_NAME_PASS __MAKE_PASS_NAME_PASS(PASS_NAME) + +#ifdef NO_GATE +#define _GATE NULL +#define _HAS_GATE false +#else +#define __GATE(n) _GCC_PLUGIN_CONCAT2(n, _gate) +#define _GATE __GATE(PASS_NAME) +#define _HAS_GATE true +#endif + +#ifdef NO_EXECUTE +#define _EXECUTE NULL +#define _HAS_EXECUTE false +#else +#define __EXECUTE(n) _GCC_PLUGIN_CONCAT2(n, _execute) +#define _EXECUTE __EXECUTE(PASS_NAME) +#define _HAS_EXECUTE true +#endif + +#ifndef PROPERTIES_REQUIRED +#define PROPERTIES_REQUIRED 0 +#endif + +#ifndef PROPERTIES_PROVIDED +#define PROPERTIES_PROVIDED 0 +#endif + +#ifndef PROPERTIES_DESTROYED +#define PROPERTIES_DESTROYED 0 +#endif + +#ifndef TODO_FLAGS_START +#define TODO_FLAGS_START 0 +#endif + +#ifndef TODO_FLAGS_FINISH +#define TODO_FLAGS_FINISH 0 +#endif + +namespace { +static const pass_data _PASS_NAME_PASS_DATA = { + .type = RTL_PASS, + .name = _PASS_NAME_NAME, + .optinfo_flags = OPTGROUP_NONE, + .tv_id = TV_NONE, + .properties_required = PROPERTIES_REQUIRED, + .properties_provided = PROPERTIES_PROVIDED, + .properties_destroyed = PROPERTIES_DESTROYED, + .todo_flags_start = TODO_FLAGS_START, + .todo_flags_finish = TODO_FLAGS_FINISH, +}; + +class _PASS_NAME_PASS : public rtl_opt_pass { +public: + _PASS_NAME_PASS() : rtl_opt_pass(_PASS_NAME_PASS_DATA, g) {} + +#ifndef NO_GATE + virtual bool gate(function *) { return _GATE(); } +#endif + + virtual opt_pass *clone() { return new _PASS_NAME_PASS(); } + +#ifndef NO_EXECUTE + virtual unsigned int execute(function *) { return _EXECUTE(); } +#endif +}; +} + +opt_pass *_MAKE_PASS_NAME_PASS(void) +{ + return new _PASS_NAME_PASS(); +} + +/* clean up user provided defines */ +#undef PASS_NAME +#undef NO_GATE +#undef NO_EXECUTE + +#undef PROPERTIES_DESTROYED +#undef PROPERTIES_PROVIDED +#undef PROPERTIES_REQUIRED +#undef TODO_FLAGS_FINISH +#undef TODO_FLAGS_START + +/* clean up generated defines */ +#undef _EXECUTE +#undef __EXECUTE +#undef _GATE +#undef __GATE +#undef _GCC_PLUGIN_CONCAT2 +#undef _GCC_PLUGIN_CONCAT3 +#undef _GCC_PLUGIN_STRINGIFY +#undef __GCC_PLUGIN_STRINGIFY +#undef _HAS_EXECUTE +#undef _HAS_GATE +#undef _MAKE_PASS_NAME_PASS +#undef __MAKE_PASS_NAME_PASS +#undef _PASS_NAME_NAME +#undef _PASS_NAME_PASS +#undef __PASS_NAME_PASS +#undef _PASS_NAME_PASS_DATA +#undef __PASS_NAME_PASS_DATA + +#endif /* PASS_NAME */ diff --git a/scripts/gcc-plugins/gcc-generate-simple_ipa-pass.h b/scripts/gcc-plugins/gcc-generate-simple_ipa-pass.h new file mode 100644 index 000000000..33093ccc9 --- /dev/null +++ b/scripts/gcc-plugins/gcc-generate-simple_ipa-pass.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Generator for SIMPLE_IPA pass related boilerplate code/data + * + * Supports gcc 4.5-6 + * + * Usage: + * + * 1. before inclusion define PASS_NAME + * 2. before inclusion define NO_* for unimplemented callbacks + * NO_GATE + * NO_EXECUTE + * 3. before inclusion define PROPERTIES_* and TODO_FLAGS_* to override + * the default 0 values + * 4. for convenience, all the above will be undefined after inclusion! + * 5. the only exported name is make_PASS_NAME_pass() to register with gcc + */ + +#ifndef PASS_NAME +#error at least PASS_NAME must be defined +#else +#define __GCC_PLUGIN_STRINGIFY(n) #n +#define _GCC_PLUGIN_STRINGIFY(n) __GCC_PLUGIN_STRINGIFY(n) +#define _GCC_PLUGIN_CONCAT2(x, y) x ## y +#define _GCC_PLUGIN_CONCAT3(x, y, z) x ## y ## z + +#define __PASS_NAME_PASS_DATA(n) _GCC_PLUGIN_CONCAT2(n, _pass_data) +#define _PASS_NAME_PASS_DATA __PASS_NAME_PASS_DATA(PASS_NAME) + +#define __PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT2(n, _pass) +#define _PASS_NAME_PASS __PASS_NAME_PASS(PASS_NAME) + +#define _PASS_NAME_NAME _GCC_PLUGIN_STRINGIFY(PASS_NAME) + +#define __MAKE_PASS_NAME_PASS(n) _GCC_PLUGIN_CONCAT3(make_, n, _pass) +#define _MAKE_PASS_NAME_PASS __MAKE_PASS_NAME_PASS(PASS_NAME) + +#ifdef NO_GATE +#define _GATE NULL +#define _HAS_GATE false +#else +#define __GATE(n) _GCC_PLUGIN_CONCAT2(n, _gate) +#define _GATE __GATE(PASS_NAME) +#define _HAS_GATE true +#endif + +#ifdef NO_EXECUTE +#define _EXECUTE NULL +#define _HAS_EXECUTE false +#else +#define __EXECUTE(n) _GCC_PLUGIN_CONCAT2(n, _execute) +#define _EXECUTE __EXECUTE(PASS_NAME) +#define _HAS_EXECUTE true +#endif + +#ifndef PROPERTIES_REQUIRED +#define PROPERTIES_REQUIRED 0 +#endif + +#ifndef PROPERTIES_PROVIDED +#define PROPERTIES_PROVIDED 0 +#endif + +#ifndef PROPERTIES_DESTROYED +#define PROPERTIES_DESTROYED 0 +#endif + +#ifndef TODO_FLAGS_START +#define TODO_FLAGS_START 0 +#endif + +#ifndef TODO_FLAGS_FINISH +#define TODO_FLAGS_FINISH 0 +#endif + +namespace { +static const pass_data _PASS_NAME_PASS_DATA = { + .type = SIMPLE_IPA_PASS, + .name = _PASS_NAME_NAME, + .optinfo_flags = OPTGROUP_NONE, + .tv_id = TV_NONE, + .properties_required = PROPERTIES_REQUIRED, + .properties_provided = PROPERTIES_PROVIDED, + .properties_destroyed = PROPERTIES_DESTROYED, + .todo_flags_start = TODO_FLAGS_START, + .todo_flags_finish = TODO_FLAGS_FINISH, +}; + +class _PASS_NAME_PASS : public simple_ipa_opt_pass { +public: + _PASS_NAME_PASS() : simple_ipa_opt_pass(_PASS_NAME_PASS_DATA, g) {} + +#ifndef NO_GATE + virtual bool gate(function *) { return _GATE(); } +#endif + + virtual opt_pass *clone() { return new _PASS_NAME_PASS(); } + +#ifndef NO_EXECUTE + virtual unsigned int execute(function *) { return _EXECUTE(); } +#endif +}; +} + +opt_pass *_MAKE_PASS_NAME_PASS(void) +{ + return new _PASS_NAME_PASS(); +} + +/* clean up user provided defines */ +#undef PASS_NAME +#undef NO_GATE +#undef NO_EXECUTE + +#undef PROPERTIES_DESTROYED +#undef PROPERTIES_PROVIDED +#undef PROPERTIES_REQUIRED +#undef TODO_FLAGS_FINISH +#undef TODO_FLAGS_START + +/* clean up generated defines */ +#undef _EXECUTE +#undef __EXECUTE +#undef _GATE +#undef __GATE +#undef _GCC_PLUGIN_CONCAT2 +#undef _GCC_PLUGIN_CONCAT3 +#undef _GCC_PLUGIN_STRINGIFY +#undef __GCC_PLUGIN_STRINGIFY +#undef _HAS_EXECUTE +#undef _HAS_GATE +#undef _MAKE_PASS_NAME_PASS +#undef __MAKE_PASS_NAME_PASS +#undef _PASS_NAME_NAME +#undef _PASS_NAME_PASS +#undef __PASS_NAME_PASS +#undef _PASS_NAME_PASS_DATA +#undef __PASS_NAME_PASS_DATA + +#endif /* PASS_NAME */ diff --git a/scripts/gcc-plugins/latent_entropy_plugin.c b/scripts/gcc-plugins/latent_entropy_plugin.c new file mode 100644 index 000000000..ff0b192be --- /dev/null +++ b/scripts/gcc-plugins/latent_entropy_plugin.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2012-2016 by the PaX Team <pageexec@freemail.hu> + * Copyright 2016 by Emese Revfy <re.emese@gmail.com> + * + * 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 + * + * This gcc plugin helps generate a little bit of entropy from program state, + * used throughout the uptime of the kernel. Here is an instrumentation example: + * + * before: + * void __latent_entropy test(int argc, char *argv[]) + * { + * if (argc <= 1) + * printf("%s: no command arguments :(\n", *argv); + * else + * printf("%s: %d command arguments!\n", *argv, argc - 1); + * } + * + * after: + * void __latent_entropy test(int argc, char *argv[]) + * { + * // latent_entropy_execute() 1. + * unsigned long local_entropy; + * // init_local_entropy() 1. + * void *local_entropy_frameaddr; + * // init_local_entropy() 3. + * unsigned long tmp_latent_entropy; + * + * // init_local_entropy() 2. + * local_entropy_frameaddr = __builtin_frame_address(0); + * local_entropy = (unsigned long) local_entropy_frameaddr; + * + * // init_local_entropy() 4. + * tmp_latent_entropy = latent_entropy; + * // init_local_entropy() 5. + * local_entropy ^= tmp_latent_entropy; + * + * // latent_entropy_execute() 3. + * if (argc <= 1) { + * // perturb_local_entropy() + * local_entropy += 4623067384293424948; + * printf("%s: no command arguments :(\n", *argv); + * // perturb_local_entropy() + * } else { + * local_entropy ^= 3896280633962944730; + * printf("%s: %d command arguments!\n", *argv, argc - 1); + * } + * + * // latent_entropy_execute() 4. + * tmp_latent_entropy = rol(tmp_latent_entropy, local_entropy); + * latent_entropy = tmp_latent_entropy; + * } + * + * TODO: + * - add ipa pass to identify not explicitly marked candidate functions + * - mix in more program state (function arguments/return values, + * loop variables, etc) + * - more instrumentation control via attribute parameters + * + * BUGS: + * - none known + * + * Options: + * -fplugin-arg-latent_entropy_plugin-disable + * + * Attribute: __attribute__((latent_entropy)) + * The latent_entropy gcc attribute can be only on functions and variables. + * If it is on a function then the plugin will instrument it. If the attribute + * is on a variable then the plugin will initialize it with a random value. + * The variable must be an integer, an integer array type or a structure + * with integer fields. + */ + +#include "gcc-common.h" + +__visible int plugin_is_GPL_compatible; + +static GTY(()) tree latent_entropy_decl; + +static struct plugin_info latent_entropy_plugin_info = { + .version = PLUGIN_VERSION, + .help = "disable\tturn off latent entropy instrumentation\n", +}; + +static unsigned HOST_WIDE_INT deterministic_seed; +static unsigned HOST_WIDE_INT rnd_buf[32]; +static size_t rnd_idx = ARRAY_SIZE(rnd_buf); +static int urandom_fd = -1; + +static unsigned HOST_WIDE_INT get_random_const(void) +{ + if (deterministic_seed) { + unsigned HOST_WIDE_INT w = deterministic_seed; + w ^= w << 13; + w ^= w >> 7; + w ^= w << 17; + deterministic_seed = w; + return deterministic_seed; + } + + if (urandom_fd < 0) { + urandom_fd = open("/dev/urandom", O_RDONLY); + gcc_assert(urandom_fd >= 0); + } + if (rnd_idx >= ARRAY_SIZE(rnd_buf)) { + gcc_assert(read(urandom_fd, rnd_buf, sizeof(rnd_buf)) == sizeof(rnd_buf)); + rnd_idx = 0; + } + return rnd_buf[rnd_idx++]; +} + +static tree tree_get_random_const(tree type) +{ + unsigned long long mask; + + mask = 1ULL << (TREE_INT_CST_LOW(TYPE_SIZE(type)) - 1); + mask = 2 * (mask - 1) + 1; + + if (TYPE_UNSIGNED(type)) + return build_int_cstu(type, mask & get_random_const()); + return build_int_cst(type, mask & get_random_const()); +} + +static tree handle_latent_entropy_attribute(tree *node, tree name, + tree args __unused, + int flags __unused, + bool *no_add_attrs) +{ + tree type; + vec<constructor_elt, va_gc> *vals; + + switch (TREE_CODE(*node)) { + default: + *no_add_attrs = true; + error("%qE attribute only applies to functions and variables", + name); + break; + + case VAR_DECL: + if (DECL_INITIAL(*node)) { + *no_add_attrs = true; + error("variable %qD with %qE attribute must not be initialized", + *node, name); + break; + } + + if (!TREE_STATIC(*node)) { + *no_add_attrs = true; + error("variable %qD with %qE attribute must not be local", + *node, name); + break; + } + + type = TREE_TYPE(*node); + switch (TREE_CODE(type)) { + default: + *no_add_attrs = true; + error("variable %qD with %qE attribute must be an integer or a fixed length integer array type or a fixed sized structure with integer fields", + *node, name); + break; + + case RECORD_TYPE: { + tree fld, lst = TYPE_FIELDS(type); + unsigned int nelt = 0; + + for (fld = lst; fld; nelt++, fld = TREE_CHAIN(fld)) { + tree fieldtype; + + fieldtype = TREE_TYPE(fld); + if (TREE_CODE(fieldtype) == INTEGER_TYPE) + continue; + + *no_add_attrs = true; + error("structure variable %qD with %qE attribute has a non-integer field %qE", + *node, name, fld); + break; + } + + if (fld) + break; + + vec_alloc(vals, nelt); + + for (fld = lst; fld; fld = TREE_CHAIN(fld)) { + tree random_const, fld_t = TREE_TYPE(fld); + + random_const = tree_get_random_const(fld_t); + CONSTRUCTOR_APPEND_ELT(vals, fld, random_const); + } + + /* Initialize the fields with random constants */ + DECL_INITIAL(*node) = build_constructor(type, vals); + break; + } + + /* Initialize the variable with a random constant */ + case INTEGER_TYPE: + DECL_INITIAL(*node) = tree_get_random_const(type); + break; + + case ARRAY_TYPE: { + tree elt_type, array_size, elt_size; + unsigned int i, nelt; + HOST_WIDE_INT array_size_int, elt_size_int; + + elt_type = TREE_TYPE(type); + elt_size = TYPE_SIZE_UNIT(TREE_TYPE(type)); + array_size = TYPE_SIZE_UNIT(type); + + if (TREE_CODE(elt_type) != INTEGER_TYPE || !array_size + || TREE_CODE(array_size) != INTEGER_CST) { + *no_add_attrs = true; + error("array variable %qD with %qE attribute must be a fixed length integer array type", + *node, name); + break; + } + + array_size_int = TREE_INT_CST_LOW(array_size); + elt_size_int = TREE_INT_CST_LOW(elt_size); + nelt = array_size_int / elt_size_int; + + vec_alloc(vals, nelt); + + for (i = 0; i < nelt; i++) { + tree cst = size_int(i); + tree rand_cst = tree_get_random_const(elt_type); + + CONSTRUCTOR_APPEND_ELT(vals, cst, rand_cst); + } + + /* + * Initialize the elements of the array with random + * constants + */ + DECL_INITIAL(*node) = build_constructor(type, vals); + break; + } + } + break; + + case FUNCTION_DECL: + break; + } + + return NULL_TREE; +} + +static struct attribute_spec latent_entropy_attr = { }; + +static void register_attributes(void *event_data __unused, void *data __unused) +{ + latent_entropy_attr.name = "latent_entropy"; + latent_entropy_attr.decl_required = true; + latent_entropy_attr.handler = handle_latent_entropy_attribute; + + register_attribute(&latent_entropy_attr); +} + +static bool latent_entropy_gate(void) +{ + tree list; + + /* don't bother with noreturn functions for now */ + if (TREE_THIS_VOLATILE(current_function_decl)) + return false; + + /* gcc-4.5 doesn't discover some trivial noreturn functions */ + if (EDGE_COUNT(EXIT_BLOCK_PTR_FOR_FN(cfun)->preds) == 0) + return false; + + list = DECL_ATTRIBUTES(current_function_decl); + return lookup_attribute("latent_entropy", list) != NULL_TREE; +} + +static tree create_var(tree type, const char *name) +{ + tree var; + + var = create_tmp_var(type, name); + add_referenced_var(var); + mark_sym_for_renaming(var); + return var; +} + +/* + * Set up the next operation and its constant operand to use in the latent + * entropy PRNG. When RHS is specified, the request is for perturbing the + * local latent entropy variable, otherwise it is for perturbing the global + * latent entropy variable where the two operands are already given by the + * local and global latent entropy variables themselves. + * + * The operation is one of add/xor/rol when instrumenting the local entropy + * variable and one of add/xor when perturbing the global entropy variable. + * Rotation is not used for the latter case because it would transmit less + * entropy to the global variable than the other two operations. + */ +static enum tree_code get_op(tree *rhs) +{ + static enum tree_code op; + unsigned HOST_WIDE_INT random_const; + + random_const = get_random_const(); + + switch (op) { + case BIT_XOR_EXPR: + op = PLUS_EXPR; + break; + + case PLUS_EXPR: + if (rhs) { + op = LROTATE_EXPR; + /* + * This code limits the value of random_const to + * the size of a long for the rotation + */ + random_const %= TYPE_PRECISION(long_unsigned_type_node); + break; + } + + case LROTATE_EXPR: + default: + op = BIT_XOR_EXPR; + break; + } + if (rhs) + *rhs = build_int_cstu(long_unsigned_type_node, random_const); + return op; +} + +static gimple create_assign(enum tree_code code, tree lhs, tree op1, + tree op2) +{ + return gimple_build_assign_with_ops(code, lhs, op1, op2); +} + +static void perturb_local_entropy(basic_block bb, tree local_entropy) +{ + gimple_stmt_iterator gsi; + gimple assign; + tree rhs; + enum tree_code op; + + op = get_op(&rhs); + assign = create_assign(op, local_entropy, local_entropy, rhs); + gsi = gsi_after_labels(bb); + gsi_insert_before(&gsi, assign, GSI_NEW_STMT); + update_stmt(assign); +} + +static void __perturb_latent_entropy(gimple_stmt_iterator *gsi, + tree local_entropy) +{ + gimple assign; + tree temp; + enum tree_code op; + + /* 1. create temporary copy of latent_entropy */ + temp = create_var(long_unsigned_type_node, "temp_latent_entropy"); + + /* 2. read... */ + add_referenced_var(latent_entropy_decl); + mark_sym_for_renaming(latent_entropy_decl); + assign = gimple_build_assign(temp, latent_entropy_decl); + gsi_insert_before(gsi, assign, GSI_NEW_STMT); + update_stmt(assign); + + /* 3. ...modify... */ + op = get_op(NULL); + assign = create_assign(op, temp, temp, local_entropy); + gsi_insert_after(gsi, assign, GSI_NEW_STMT); + update_stmt(assign); + + /* 4. ...write latent_entropy */ + assign = gimple_build_assign(latent_entropy_decl, temp); + gsi_insert_after(gsi, assign, GSI_NEW_STMT); + update_stmt(assign); +} + +static bool handle_tail_calls(basic_block bb, tree local_entropy) +{ + gimple_stmt_iterator gsi; + + for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) { + gcall *call; + gimple stmt = gsi_stmt(gsi); + + if (!is_gimple_call(stmt)) + continue; + + call = as_a_gcall(stmt); + if (!gimple_call_tail_p(call)) + continue; + + __perturb_latent_entropy(&gsi, local_entropy); + return true; + } + + return false; +} + +static void perturb_latent_entropy(tree local_entropy) +{ + edge_iterator ei; + edge e, last_bb_e; + basic_block last_bb; + + gcc_assert(single_pred_p(EXIT_BLOCK_PTR_FOR_FN(cfun))); + last_bb_e = single_pred_edge(EXIT_BLOCK_PTR_FOR_FN(cfun)); + + FOR_EACH_EDGE(e, ei, last_bb_e->src->preds) { + if (ENTRY_BLOCK_PTR_FOR_FN(cfun) == e->src) + continue; + if (EXIT_BLOCK_PTR_FOR_FN(cfun) == e->src) + continue; + + handle_tail_calls(e->src, local_entropy); + } + + last_bb = single_pred(EXIT_BLOCK_PTR_FOR_FN(cfun)); + if (!handle_tail_calls(last_bb, local_entropy)) { + gimple_stmt_iterator gsi = gsi_last_bb(last_bb); + + __perturb_latent_entropy(&gsi, local_entropy); + } +} + +static void init_local_entropy(basic_block bb, tree local_entropy) +{ + gimple assign, call; + tree frame_addr, rand_const, tmp, fndecl, udi_frame_addr; + enum tree_code op; + unsigned HOST_WIDE_INT rand_cst; + gimple_stmt_iterator gsi = gsi_after_labels(bb); + + /* 1. create local_entropy_frameaddr */ + frame_addr = create_var(ptr_type_node, "local_entropy_frameaddr"); + + /* 2. local_entropy_frameaddr = __builtin_frame_address() */ + fndecl = builtin_decl_implicit(BUILT_IN_FRAME_ADDRESS); + call = gimple_build_call(fndecl, 1, integer_zero_node); + gimple_call_set_lhs(call, frame_addr); + gsi_insert_before(&gsi, call, GSI_NEW_STMT); + update_stmt(call); + + udi_frame_addr = fold_convert(long_unsigned_type_node, frame_addr); + assign = gimple_build_assign(local_entropy, udi_frame_addr); + gsi_insert_after(&gsi, assign, GSI_NEW_STMT); + update_stmt(assign); + + /* 3. create temporary copy of latent_entropy */ + tmp = create_var(long_unsigned_type_node, "temp_latent_entropy"); + + /* 4. read the global entropy variable into local entropy */ + add_referenced_var(latent_entropy_decl); + mark_sym_for_renaming(latent_entropy_decl); + assign = gimple_build_assign(tmp, latent_entropy_decl); + gsi_insert_after(&gsi, assign, GSI_NEW_STMT); + update_stmt(assign); + + /* 5. mix local_entropy_frameaddr into local entropy */ + assign = create_assign(BIT_XOR_EXPR, local_entropy, local_entropy, tmp); + gsi_insert_after(&gsi, assign, GSI_NEW_STMT); + update_stmt(assign); + + rand_cst = get_random_const(); + rand_const = build_int_cstu(long_unsigned_type_node, rand_cst); + op = get_op(NULL); + assign = create_assign(op, local_entropy, local_entropy, rand_const); + gsi_insert_after(&gsi, assign, GSI_NEW_STMT); + update_stmt(assign); +} + +static bool create_latent_entropy_decl(void) +{ + varpool_node_ptr node; + + if (latent_entropy_decl != NULL_TREE) + return true; + + FOR_EACH_VARIABLE(node) { + tree name, var = NODE_DECL(node); + + if (DECL_NAME_LENGTH(var) < sizeof("latent_entropy") - 1) + continue; + + name = DECL_NAME(var); + if (strcmp(IDENTIFIER_POINTER(name), "latent_entropy")) + continue; + + latent_entropy_decl = var; + break; + } + + return latent_entropy_decl != NULL_TREE; +} + +static unsigned int latent_entropy_execute(void) +{ + basic_block bb; + tree local_entropy; + + if (!create_latent_entropy_decl()) + return 0; + + /* prepare for step 2 below */ + 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))); + bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun)); + } + + /* 1. create the local entropy variable */ + local_entropy = create_var(long_unsigned_type_node, "local_entropy"); + + /* 2. initialize the local entropy variable */ + init_local_entropy(bb, local_entropy); + + bb = bb->next_bb; + + /* + * 3. instrument each BB with an operation on the + * local entropy variable + */ + while (bb != EXIT_BLOCK_PTR_FOR_FN(cfun)) { + perturb_local_entropy(bb, local_entropy); + bb = bb->next_bb; + } + + /* 4. mix local entropy into the global entropy variable */ + perturb_latent_entropy(local_entropy); + return 0; +} + +static void latent_entropy_start_unit(void *gcc_data __unused, + void *user_data __unused) +{ + tree type, id; + int quals; + + if (in_lto_p) + return; + + /* extern volatile unsigned long latent_entropy */ + quals = TYPE_QUALS(long_unsigned_type_node) | TYPE_QUAL_VOLATILE; + type = build_qualified_type(long_unsigned_type_node, quals); + id = get_identifier("latent_entropy"); + latent_entropy_decl = build_decl(UNKNOWN_LOCATION, VAR_DECL, id, type); + + TREE_STATIC(latent_entropy_decl) = 1; + TREE_PUBLIC(latent_entropy_decl) = 1; + TREE_USED(latent_entropy_decl) = 1; + DECL_PRESERVE_P(latent_entropy_decl) = 1; + TREE_THIS_VOLATILE(latent_entropy_decl) = 1; + DECL_EXTERNAL(latent_entropy_decl) = 1; + DECL_ARTIFICIAL(latent_entropy_decl) = 1; + lang_hooks.decls.pushdecl(latent_entropy_decl); +} + +#define PASS_NAME latent_entropy +#define PROPERTIES_REQUIRED PROP_gimple_leh | PROP_cfg +#define TODO_FLAGS_FINISH TODO_verify_ssa | TODO_verify_stmts | TODO_dump_func \ + | TODO_update_ssa +#include "gcc-generate-gimple-pass.h" + +__visible int plugin_init(struct plugin_name_args *plugin_info, + struct plugin_gcc_version *version) +{ + bool enabled = true; + const char * const plugin_name = plugin_info->base_name; + const int argc = plugin_info->argc; + const struct plugin_argument * const argv = plugin_info->argv; + int i; + + /* + * Call get_random_seed() with noinit=true, so that this returns + * 0 in the case where no seed has been passed via -frandom-seed. + */ + deterministic_seed = get_random_seed(true); + + static const struct ggc_root_tab gt_ggc_r_gt_latent_entropy[] = { + { + .base = &latent_entropy_decl, + .nelt = 1, + .stride = sizeof(latent_entropy_decl), + .cb = >_ggc_mx_tree_node, + .pchw = >_pch_nx_tree_node + }, + LAST_GGC_ROOT_TAB + }; + + PASS_INFO(latent_entropy, "optimized", 1, 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, "disable"))) { + enabled = false; + continue; + } + error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key); + } + + register_callback(plugin_name, PLUGIN_INFO, NULL, + &latent_entropy_plugin_info); + if (enabled) { + register_callback(plugin_name, PLUGIN_START_UNIT, + &latent_entropy_start_unit, NULL); + register_callback(plugin_name, PLUGIN_REGISTER_GGC_ROOTS, + NULL, (void *)>_ggc_r_gt_latent_entropy); + register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, + &latent_entropy_pass_info); + } + register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, + NULL); + + return 0; +} diff --git a/scripts/gcc-plugins/randomize_layout_plugin.c b/scripts/gcc-plugins/randomize_layout_plugin.c new file mode 100644 index 000000000..ff65a4f87 --- /dev/null +++ b/scripts/gcc-plugins/randomize_layout_plugin.c @@ -0,0 +1,881 @@ +/* + * Copyright 2014-2016 by Open Source Security, Inc., Brad Spengler <spender@grsecurity.net> + * and PaX Team <pageexec@freemail.hu> + * Licensed under the GPL v2 + * + * 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 + * + * 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 randomize_layout_plugin.so randomize_layout_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 randomize_layout_plugin.so randomize_layout_plugin.c + * $ gcc -fplugin=./randomize_layout_plugin.so test.c -O2 + */ + +#include "gcc-common.h" +#include "randomize_layout_seed.h" + +#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") + +#define INFORM(loc, msg, ...) inform(loc, "randstruct: " msg, ##__VA_ARGS__) +#define MISMATCH(loc, how, ...) INFORM(loc, "casting between randomized structure pointer types (" how "): %qT and %qT\n", __VA_ARGS__) + +__visible int plugin_is_GPL_compatible; + +static int performance_mode; + +static struct plugin_info randomize_layout_plugin_info = { + .version = PLUGIN_VERSION, + .help = "disable\t\t\tdo not activate plugin\n" + "performance-mode\tenable cacheline-aware layout randomization\n" +}; + +/* from old Linux dcache.h */ +static inline unsigned long +partial_name_hash(unsigned long c, unsigned long prevhash) +{ + return (prevhash + (c << 4) + (c >> 4)) * 11; +} +static inline unsigned int +name_hash(const unsigned char *name) +{ + unsigned long hash = 0; + unsigned int len = strlen((const char *)name); + while (len--) + hash = partial_name_hash(*name++, hash); + return (unsigned int)hash; +} + +static tree handle_randomize_layout_attr(tree *node, tree name, tree args, int flags, bool *no_add_attrs) +{ + tree type; + + *no_add_attrs = true; + if (TREE_CODE(*node) == FUNCTION_DECL) { + error("%qE attribute does not apply to functions (%qF)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) == PARM_DECL) { + error("%qE attribute does not apply to function parameters (%qD)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) == VAR_DECL) { + error("%qE attribute does not apply to variables (%qD)", name, *node); + return NULL_TREE; + } + + 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); + } + + if (TREE_CODE(type) != RECORD_TYPE) { + error("%qE attribute used on %qT applies to struct types only", name, type); + return NULL_TREE; + } + + if (lookup_attribute(IDENTIFIER_POINTER(name), TYPE_ATTRIBUTES(type))) { + error("%qE attribute is already applied to the type %qT", name, type); + return NULL_TREE; + } + + *no_add_attrs = false; + + return NULL_TREE; +} + +/* set on complete types that we don't need to inspect further at all */ +static tree handle_randomize_considered_attr(tree *node, tree name, tree args, int flags, bool *no_add_attrs) +{ + *no_add_attrs = false; + return NULL_TREE; +} + +/* + * set on types that we've performed a shuffle on, to prevent re-shuffling + * this does not preclude us from inspecting its fields for potential shuffles + */ +static tree handle_randomize_performed_attr(tree *node, tree name, tree args, int flags, bool *no_add_attrs) +{ + *no_add_attrs = false; + return NULL_TREE; +} + +/* + * 64bit variant of Bob Jenkins' public domain PRNG + * 256 bits of internal state + */ + +typedef unsigned long long u64; + +typedef struct ranctx { u64 a; u64 b; u64 c; u64 d; } ranctx; + +#define rot(x,k) (((x)<<(k))|((x)>>(64-(k)))) +static u64 ranval(ranctx *x) { + u64 e = x->a - rot(x->b, 7); + x->a = x->b ^ rot(x->c, 13); + x->b = x->c + rot(x->d, 37); + x->c = x->d + e; + x->d = e + x->a; + return x->d; +} + +static void raninit(ranctx *x, u64 *seed) { + int i; + + x->a = seed[0]; + x->b = seed[1]; + x->c = seed[2]; + x->d = seed[3]; + + for (i=0; i < 30; ++i) + (void)ranval(x); +} + +static u64 shuffle_seed[4]; + +struct partition_group { + tree tree_start; + unsigned long start; + unsigned long length; +}; + +static void partition_struct(tree *fields, unsigned long length, struct partition_group *size_groups, unsigned long *num_groups) +{ + unsigned long i; + unsigned long accum_size = 0; + unsigned long accum_length = 0; + unsigned long group_idx = 0; + + gcc_assert(length < INT_MAX); + + memset(size_groups, 0, sizeof(struct partition_group) * length); + + for (i = 0; i < length; i++) { + if (size_groups[group_idx].tree_start == NULL_TREE) { + size_groups[group_idx].tree_start = fields[i]; + size_groups[group_idx].start = i; + accum_length = 0; + accum_size = 0; + } + accum_size += (unsigned long)int_size_in_bytes(TREE_TYPE(fields[i])); + accum_length++; + if (accum_size >= 64) { + size_groups[group_idx].length = accum_length; + accum_length = 0; + group_idx++; + } + } + + if (size_groups[group_idx].tree_start != NULL_TREE && + !size_groups[group_idx].length) { + size_groups[group_idx].length = accum_length; + group_idx++; + } + + *num_groups = group_idx; +} + +static void performance_shuffle(tree *newtree, unsigned long length, ranctx *prng_state) +{ + unsigned long i, x, index; + struct partition_group size_group[length]; + unsigned long num_groups = 0; + unsigned long randnum; + + partition_struct(newtree, length, (struct partition_group *)&size_group, &num_groups); + + /* FIXME: this group shuffle is currently a no-op. */ + for (i = num_groups - 1; i > 0; i--) { + struct partition_group tmp; + randnum = ranval(prng_state) % (i + 1); + tmp = size_group[i]; + size_group[i] = size_group[randnum]; + size_group[randnum] = tmp; + } + + for (x = 0; x < num_groups; x++) { + for (index = size_group[x].length - 1; index > 0; index--) { + tree tmp; + + i = size_group[x].start + index; + if (DECL_BIT_FIELD_TYPE(newtree[i])) + continue; + randnum = ranval(prng_state) % (index + 1); + randnum += size_group[x].start; + // we could handle this case differently if desired + if (DECL_BIT_FIELD_TYPE(newtree[randnum])) + continue; + tmp = newtree[i]; + newtree[i] = newtree[randnum]; + newtree[randnum] = tmp; + } + } +} + +static void full_shuffle(tree *newtree, unsigned long length, ranctx *prng_state) +{ + unsigned long i, randnum; + + for (i = length - 1; i > 0; i--) { + tree tmp; + randnum = ranval(prng_state) % (i + 1); + tmp = newtree[i]; + newtree[i] = newtree[randnum]; + newtree[randnum] = tmp; + } +} + +/* modern in-place Fisher-Yates shuffle */ +static void shuffle(const_tree type, tree *newtree, unsigned long length) +{ + unsigned long i; + u64 seed[4]; + ranctx prng_state; + const unsigned char *structname; + + if (length == 0) + return; + + gcc_assert(TREE_CODE(type) == RECORD_TYPE); + + structname = ORIG_TYPE_NAME(type); + +#ifdef __DEBUG_PLUGIN + fprintf(stderr, "Shuffling struct %s %p\n", (const char *)structname, type); +#ifdef __DEBUG_VERBOSE + debug_tree((tree)type); +#endif +#endif + + for (i = 0; i < 4; i++) { + seed[i] = shuffle_seed[i]; + seed[i] ^= name_hash(structname); + } + + raninit(&prng_state, (u64 *)&seed); + + if (performance_mode) + performance_shuffle(newtree, length, &prng_state); + else + full_shuffle(newtree, length, &prng_state); +} + +static bool is_flexible_array(const_tree field) +{ + const_tree fieldtype; + const_tree typesize; + + fieldtype = TREE_TYPE(field); + typesize = TYPE_SIZE(fieldtype); + + if (TREE_CODE(fieldtype) != ARRAY_TYPE) + return false; + + /* size of type is represented in bits */ + + if (typesize == NULL_TREE && TYPE_DOMAIN(fieldtype) != NULL_TREE && + TYPE_MAX_VALUE(TYPE_DOMAIN(fieldtype)) == NULL_TREE) + return true; + + return false; +} + +static int relayout_struct(tree type) +{ + unsigned long num_fields = (unsigned long)list_length(TYPE_FIELDS(type)); + unsigned long shuffle_length = num_fields; + tree field; + tree newtree[num_fields]; + unsigned long i; + tree list; + tree variant; + tree main_variant; + expanded_location xloc; + bool has_flexarray = false; + + if (TYPE_FIELDS(type) == NULL_TREE) + return 0; + + if (num_fields < 2) + return 0; + + gcc_assert(TREE_CODE(type) == RECORD_TYPE); + + gcc_assert(num_fields < INT_MAX); + + if (lookup_attribute("randomize_performed", TYPE_ATTRIBUTES(type)) || + lookup_attribute("no_randomize_layout", TYPE_ATTRIBUTES(TYPE_MAIN_VARIANT(type)))) + return 0; + + /* Workaround for 3rd-party VirtualBox source that we can't modify ourselves */ + if (!strcmp((const char *)ORIG_TYPE_NAME(type), "INTNETTRUNKFACTORY") || + !strcmp((const char *)ORIG_TYPE_NAME(type), "RAWPCIFACTORY")) + return 0; + + /* throw out any structs in uapi */ + xloc = expand_location(DECL_SOURCE_LOCATION(TYPE_FIELDS(type))); + + if (strstr(xloc.file, "/uapi/")) + error(G_("attempted to randomize userland API struct %s"), ORIG_TYPE_NAME(type)); + + for (field = TYPE_FIELDS(type), i = 0; field; field = TREE_CHAIN(field), i++) { + gcc_assert(TREE_CODE(field) == FIELD_DECL); + newtree[i] = field; + } + + /* + * enforce that we don't randomize the layout of the last + * element of a struct if it's a proper flexible array + */ + if (is_flexible_array(newtree[num_fields - 1])) { + has_flexarray = true; + shuffle_length--; + } + + shuffle(type, (tree *)newtree, shuffle_length); + + 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) = newtree[0]; + + /* + * force a re-layout of the main variant + * the TYPE_SIZE for all variants will be recomputed + * by finalize_type_size() + */ + TYPE_SIZE(main_variant) = NULL_TREE; + layout_type(main_variant); + gcc_assert(TYPE_SIZE(main_variant) != NULL_TREE); + + return 1; +} + +/* from constify plugin */ +static const_tree get_field_type(const_tree field) +{ + return strip_array_types(TREE_TYPE(field)); +} + +/* from constify plugin */ +static bool is_fptr(const_tree fieldtype) +{ + if (TREE_CODE(fieldtype) != POINTER_TYPE) + return false; + + return TREE_CODE(TREE_TYPE(fieldtype)) == FUNCTION_TYPE; +} + +/* derived from constify plugin */ +static int is_pure_ops_struct(const_tree node) +{ + const_tree field; + + gcc_assert(TREE_CODE(node) == RECORD_TYPE || TREE_CODE(node) == UNION_TYPE); + + for (field = TYPE_FIELDS(node); field; field = TREE_CHAIN(field)) { + const_tree fieldtype = get_field_type(field); + enum tree_code code = TREE_CODE(fieldtype); + + if (node == fieldtype) + continue; + + if (code == RECORD_TYPE || code == UNION_TYPE) { + if (!is_pure_ops_struct(fieldtype)) + return 0; + continue; + } + + if (!is_fptr(fieldtype)) + return 0; + } + + return 1; +} + +static void randomize_type(tree type) +{ + tree variant; + + gcc_assert(TREE_CODE(type) == RECORD_TYPE); + + if (lookup_attribute("randomize_considered", TYPE_ATTRIBUTES(type))) + return; + + if (lookup_attribute("randomize_layout", TYPE_ATTRIBUTES(TYPE_MAIN_VARIANT(type))) || is_pure_ops_struct(type)) + relayout_struct(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 + debug_tree(type); +#endif +#endif +} + +static void update_decl_size(tree decl) +{ + tree lastval, lastidx, field, init, type, flexsize; + unsigned HOST_WIDE_INT len; + + type = TREE_TYPE(decl); + + if (!lookup_attribute("has_flexarray", TYPE_ATTRIBUTES(type))) + return; + + init = DECL_INITIAL(decl); + if (init == NULL_TREE || init == error_mark_node) + return; + + if (TREE_CODE(init) != CONSTRUCTOR) + return; + + len = CONSTRUCTOR_NELTS(init); + if (!len) + return; + + lastval = CONSTRUCTOR_ELT(init, CONSTRUCTOR_NELTS(init) - 1)->value; + lastidx = CONSTRUCTOR_ELT(init, CONSTRUCTOR_NELTS(init) - 1)->index; + + for (field = TYPE_FIELDS(TREE_TYPE(decl)); TREE_CHAIN(field); field = TREE_CHAIN(field)) + ; + + if (lastidx != field) + return; + + if (TREE_CODE(lastval) != STRING_CST) { + error("Only string constants are supported as initializers " + "for randomized structures with flexible arrays"); + return; + } + + flexsize = bitsize_int(TREE_STRING_LENGTH(lastval) * + tree_to_uhwi(TYPE_SIZE(TREE_TYPE(TREE_TYPE(lastval))))); + + DECL_SIZE(decl) = size_binop(PLUS_EXPR, TYPE_SIZE(type), flexsize); + + return; +} + + +static void randomize_layout_finish_decl(void *event_data, void *data) +{ + tree decl = (tree)event_data; + tree type; + + if (decl == NULL_TREE || decl == error_mark_node) + return; + + type = TREE_TYPE(decl); + + if (TREE_CODE(decl) != VAR_DECL) + return; + + if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) + return; + + if (!lookup_attribute("randomize_performed", TYPE_ATTRIBUTES(type))) + return; + + DECL_SIZE(decl) = 0; + DECL_SIZE_UNIT(decl) = 0; + SET_DECL_ALIGN(decl, 0); + SET_DECL_MODE (decl, VOIDmode); + SET_DECL_RTL(decl, 0); + update_decl_size(decl); + layout_decl(decl, 0); +} + +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) != RECORD_TYPE) + return; + + if (TYPE_FIELDS(type) == NULL_TREE) + return; + + if (lookup_attribute("randomize_considered", TYPE_ATTRIBUTES(type))) + return; + +#ifdef __DEBUG_PLUGIN + fprintf(stderr, "Calling randomize_type on %s\n", ORIG_TYPE_NAME(type)); +#endif +#ifdef __DEBUG_VERBOSE + debug_tree(type); +#endif + randomize_type(type); + + return; +} + +static struct attribute_spec randomize_layout_attr = { }; +static struct attribute_spec no_randomize_layout_attr = { }; +static struct attribute_spec randomize_considered_attr = { }; +static struct attribute_spec randomize_performed_attr = { }; + +static void register_attributes(void *event_data, void *data) +{ + randomize_layout_attr.name = "randomize_layout"; + randomize_layout_attr.type_required = true; + randomize_layout_attr.handler = handle_randomize_layout_attr; + randomize_layout_attr.affects_type_identity = true; + + no_randomize_layout_attr.name = "no_randomize_layout"; + no_randomize_layout_attr.type_required = true; + no_randomize_layout_attr.handler = handle_randomize_layout_attr; + no_randomize_layout_attr.affects_type_identity = true; + + randomize_considered_attr.name = "randomize_considered"; + randomize_considered_attr.type_required = true; + randomize_considered_attr.handler = handle_randomize_considered_attr; + + randomize_performed_attr.name = "randomize_performed"; + randomize_performed_attr.type_required = true; + randomize_performed_attr.handler = handle_randomize_performed_attr; + + register_attribute(&randomize_layout_attr); + register_attribute(&no_randomize_layout_attr); + register_attribute(&randomize_considered_attr); + register_attribute(&randomize_performed_attr); +} + +static void check_bad_casts_in_constructor(tree var, tree init) +{ + unsigned HOST_WIDE_INT idx; + tree field, val; + tree field_type, val_type; + + FOR_EACH_CONSTRUCTOR_ELT(CONSTRUCTOR_ELTS(init), idx, field, val) { + if (TREE_CODE(val) == CONSTRUCTOR) { + check_bad_casts_in_constructor(var, val); + continue; + } + + /* pipacs' plugin creates franken-arrays that differ from those produced by + normal code which all have valid 'field' trees. work around this */ + if (field == NULL_TREE) + continue; + field_type = TREE_TYPE(field); + val_type = TREE_TYPE(val); + + if (TREE_CODE(field_type) != POINTER_TYPE || TREE_CODE(val_type) != POINTER_TYPE) + continue; + + if (field_type == val_type) + continue; + + field_type = TYPE_MAIN_VARIANT(strip_array_types(TYPE_MAIN_VARIANT(TREE_TYPE(field_type)))); + val_type = TYPE_MAIN_VARIANT(strip_array_types(TYPE_MAIN_VARIANT(TREE_TYPE(val_type)))); + + if (field_type == void_type_node) + continue; + if (field_type == val_type) + continue; + if (TREE_CODE(val_type) != RECORD_TYPE) + continue; + + if (!lookup_attribute("randomize_performed", TYPE_ATTRIBUTES(val_type))) + continue; + MISMATCH(DECL_SOURCE_LOCATION(var), "constructor\n", TYPE_MAIN_VARIANT(field_type), TYPE_MAIN_VARIANT(val_type)); + } +} + +/* derived from the constify plugin */ +static void check_global_variables(void *event_data, void *data) +{ + struct varpool_node *node; + tree init; + + FOR_EACH_VARIABLE(node) { + tree var = NODE_DECL(node); + init = DECL_INITIAL(var); + if (init == NULL_TREE) + continue; + + if (TREE_CODE(init) != CONSTRUCTOR) + continue; + + check_bad_casts_in_constructor(var, init); + } +} + +static bool dominated_by_is_err(const_tree rhs, basic_block bb) +{ + basic_block dom; + gimple dom_stmt; + gimple call_stmt; + const_tree dom_lhs; + const_tree poss_is_err_cond; + const_tree poss_is_err_func; + const_tree is_err_arg; + + dom = get_immediate_dominator(CDI_DOMINATORS, bb); + if (!dom) + return false; + + dom_stmt = last_stmt(dom); + if (!dom_stmt) + return false; + + if (gimple_code(dom_stmt) != GIMPLE_COND) + return false; + + if (gimple_cond_code(dom_stmt) != NE_EXPR) + return false; + + if (!integer_zerop(gimple_cond_rhs(dom_stmt))) + return false; + + poss_is_err_cond = gimple_cond_lhs(dom_stmt); + + if (TREE_CODE(poss_is_err_cond) != SSA_NAME) + return false; + + call_stmt = SSA_NAME_DEF_STMT(poss_is_err_cond); + + if (gimple_code(call_stmt) != GIMPLE_CALL) + return false; + + dom_lhs = gimple_get_lhs(call_stmt); + poss_is_err_func = gimple_call_fndecl(call_stmt); + if (!poss_is_err_func) + return false; + if (dom_lhs != poss_is_err_cond) + return false; + if (strcmp(DECL_NAME_POINTER(poss_is_err_func), "IS_ERR")) + return false; + + is_err_arg = gimple_call_arg(call_stmt, 0); + if (!is_err_arg) + return false; + + if (is_err_arg != rhs) + return false; + + return true; +} + +static void handle_local_var_initializers(void) +{ + tree var; + unsigned int i; + + FOR_EACH_LOCAL_DECL(cfun, i, var) { + tree init = DECL_INITIAL(var); + if (!init) + continue; + if (TREE_CODE(init) != CONSTRUCTOR) + continue; + check_bad_casts_in_constructor(var, init); + } +} + +/* + * iterate over all statements to find "bad" casts: + * those where the address of the start of a structure is cast + * to a pointer of a structure of a different type, or a + * structure pointer type is cast to a different structure pointer type + */ +static unsigned int find_bad_casts_execute(void) +{ + basic_block bb; + + handle_local_var_initializers(); + + FOR_EACH_BB_FN(bb, cfun) { + gimple_stmt_iterator gsi; + + for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) { + gimple stmt; + const_tree lhs; + const_tree lhs_type; + const_tree rhs1; + const_tree rhs_type; + const_tree ptr_lhs_type; + const_tree ptr_rhs_type; + const_tree op0; + const_tree op0_type; + enum tree_code rhs_code; + + stmt = gsi_stmt(gsi); + +#ifdef __DEBUG_PLUGIN +#ifdef __DEBUG_VERBOSE + debug_gimple_stmt(stmt); + debug_tree(gimple_get_lhs(stmt)); +#endif +#endif + + if (gimple_code(stmt) != GIMPLE_ASSIGN) + continue; + +#ifdef __DEBUG_PLUGIN +#ifdef __DEBUG_VERBOSE + debug_tree(gimple_assign_rhs1(stmt)); +#endif +#endif + + + rhs_code = gimple_assign_rhs_code(stmt); + + if (rhs_code != ADDR_EXPR && rhs_code != SSA_NAME) + continue; + + lhs = gimple_get_lhs(stmt); + lhs_type = TREE_TYPE(lhs); + rhs1 = gimple_assign_rhs1(stmt); + rhs_type = TREE_TYPE(rhs1); + + if (TREE_CODE(rhs_type) != POINTER_TYPE || + TREE_CODE(lhs_type) != POINTER_TYPE) + continue; + + ptr_lhs_type = TYPE_MAIN_VARIANT(strip_array_types(TYPE_MAIN_VARIANT(TREE_TYPE(lhs_type)))); + ptr_rhs_type = TYPE_MAIN_VARIANT(strip_array_types(TYPE_MAIN_VARIANT(TREE_TYPE(rhs_type)))); + + if (ptr_rhs_type == void_type_node) + continue; + + if (ptr_lhs_type == void_type_node) + continue; + + if (dominated_by_is_err(rhs1, bb)) + continue; + + if (TREE_CODE(ptr_rhs_type) != RECORD_TYPE) { +#ifndef __DEBUG_PLUGIN + if (lookup_attribute("randomize_performed", TYPE_ATTRIBUTES(ptr_lhs_type))) +#endif + MISMATCH(gimple_location(stmt), "rhs", ptr_lhs_type, ptr_rhs_type); + continue; + } + + if (rhs_code == SSA_NAME && ptr_lhs_type == ptr_rhs_type) + continue; + + if (rhs_code == ADDR_EXPR) { + op0 = TREE_OPERAND(rhs1, 0); + + if (op0 == NULL_TREE) + continue; + + if (TREE_CODE(op0) != VAR_DECL) + continue; + + op0_type = TYPE_MAIN_VARIANT(strip_array_types(TYPE_MAIN_VARIANT(TREE_TYPE(op0)))); + if (op0_type == ptr_lhs_type) + continue; + +#ifndef __DEBUG_PLUGIN + if (lookup_attribute("randomize_performed", TYPE_ATTRIBUTES(op0_type))) +#endif + MISMATCH(gimple_location(stmt), "op0", ptr_lhs_type, op0_type); + } else { + const_tree ssa_name_var = SSA_NAME_VAR(rhs1); + /* skip bogus type casts introduced by container_of */ + if (ssa_name_var != NULL_TREE && DECL_NAME(ssa_name_var) && + !strcmp((const char *)DECL_NAME_POINTER(ssa_name_var), "__mptr")) + continue; +#ifndef __DEBUG_PLUGIN + if (lookup_attribute("randomize_performed", TYPE_ATTRIBUTES(ptr_rhs_type))) +#endif + MISMATCH(gimple_location(stmt), "ssa", ptr_lhs_type, ptr_rhs_type); + } + + } + } + return 0; +} + +#define PASS_NAME find_bad_casts +#define NO_GATE +#define TODO_FLAGS_FINISH TODO_dump_func +#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; + int obtained_seed = 0; + struct register_pass_info find_bad_casts_pass_info; + + find_bad_casts_pass_info.pass = make_find_bad_casts_pass(); + find_bad_casts_pass_info.reference_pass_name = "ssa"; + find_bad_casts_pass_info.ref_pass_instance_number = 1; + find_bad_casts_pass_info.pos_op = PASS_POS_INSERT_AFTER; + + 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, "performance-mode")) { + performance_mode = 1; + continue; + } + error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key); + } + + if (strlen(randstruct_seed) != 64) { + error(G_("invalid seed value supplied for %s plugin"), plugin_name); + return 1; + } + obtained_seed = sscanf(randstruct_seed, "%016llx%016llx%016llx%016llx", + &shuffle_seed[0], &shuffle_seed[1], &shuffle_seed[2], &shuffle_seed[3]); + if (obtained_seed != 4) { + error(G_("Invalid seed supplied for %s plugin"), plugin_name); + return 1; + } + + register_callback(plugin_name, PLUGIN_INFO, NULL, &randomize_layout_plugin_info); + if (enable) { + register_callback(plugin_name, PLUGIN_ALL_IPA_PASSES_START, check_global_variables, NULL); + register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &find_bad_casts_pass_info); + register_callback(plugin_name, PLUGIN_FINISH_TYPE, finish_type, NULL); + register_callback(plugin_name, PLUGIN_FINISH_DECL, randomize_layout_finish_decl, NULL); + } + register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL); + + return 0; +} diff --git a/scripts/gcc-plugins/stackleak_plugin.c b/scripts/gcc-plugins/stackleak_plugin.c new file mode 100644 index 000000000..e486488c8 --- /dev/null +++ b/scripts/gcc-plugins/stackleak_plugin.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2011-2017 by the PaX Team <pageexec@freemail.hu> + * Modified by Alexander Popov <alex.popov@linux.com> + * + * 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 + * + * This gcc plugin is needed for tracking the lowest border of the kernel stack. + * It instruments the kernel code inserting __sanitizer_cov_stack_depth() calls: + * - after alloca(); + * - for the functions with a stack frame size greater than or equal + * to the "track-min-size" plugin parameter. + * + * This plugin is ported from grsecurity/PaX. For more information see: + * https://grsecurity.net/ + * https://pax.grsecurity.net/ + * + * Debugging: + * - use fprintf() to stderr, debug_generic_expr(), debug_gimple_stmt(), + * print_rtl_single() and debug_rtx(); + * - add "-fdump-tree-all -fdump-rtl-all" to the plugin CFLAGS in + * Makefile.gcc-plugins to see the verbose dumps of the gcc passes; + * - use gcc -E to understand the preprocessing shenanigans; + * - use gcc with enabled CFG/GIMPLE/SSA verification (--enable-checking). + */ + +#include "gcc-common.h" + +__visible int plugin_is_GPL_compatible; + +static int track_frame_size = -1; +static bool build_for_x86 = false; +static const char track_function[] = "__sanitizer_cov_stack_depth"; +static bool disable = false; +static bool verbose = false; + +/* + * Mark these global variables (roots) for gcc garbage collector since + * they point to the garbage-collected memory. + */ +static GTY(()) tree track_function_decl; + +static struct plugin_info stackleak_plugin_info = { + .version = PLUGIN_VERSION, + .help = "track-min-size=nn\ttrack stack for functions with a stack frame size >= nn bytes\n" + "arch=target_arch\tspecify target build arch\n" + "disable\t\tdo not activate the plugin\n" + "verbose\t\tprint info about the instrumentation\n" +}; + +static void add_stack_tracking_gcall(gimple_stmt_iterator *gsi, bool after) +{ + gimple stmt; + gcall *gimple_call; + cgraph_node_ptr node; + basic_block bb; + + /* Insert calling __sanitizer_cov_stack_depth() */ + stmt = gimple_build_call(track_function_decl, 0); + gimple_call = as_a_gcall(stmt); + if (after) + gsi_insert_after(gsi, gimple_call, GSI_CONTINUE_LINKING); + else + gsi_insert_before(gsi, gimple_call, GSI_SAME_STMT); + + /* Update the cgraph */ + bb = gimple_bb(gimple_call); + node = cgraph_get_create_node(track_function_decl); + gcc_assert(node); + cgraph_create_edge(cgraph_get_node(current_function_decl), node, + gimple_call, bb->count, + compute_call_stmt_bb_frequency(current_function_decl, bb)); +} + +static bool is_alloca(gimple stmt) +{ + if (gimple_call_builtin_p(stmt, BUILT_IN_ALLOCA)) + return true; + + if (gimple_call_builtin_p(stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) + return true; + + return false; +} + +static tree get_current_stack_pointer_decl(void) +{ + varpool_node_ptr node; + + FOR_EACH_VARIABLE(node) { + tree var = NODE_DECL(node); + tree name = DECL_NAME(var); + + if (DECL_NAME_LENGTH(var) != sizeof("current_stack_pointer") - 1) + continue; + + if (strcmp(IDENTIFIER_POINTER(name), "current_stack_pointer")) + continue; + + return var; + } + + if (verbose) { + fprintf(stderr, "stackleak: missing current_stack_pointer in %s()\n", + DECL_NAME_POINTER(current_function_decl)); + } + return NULL_TREE; +} + +static void add_stack_tracking_gasm(gimple_stmt_iterator *gsi, bool after) +{ + gasm *asm_call = NULL; + tree sp_decl, input; + vec<tree, va_gc> *inputs = NULL; + + /* 'no_caller_saved_registers' is currently supported only for x86 */ + gcc_assert(build_for_x86); + + /* + * Insert calling __sanitizer_cov_stack_depth() in asm: + * asm volatile("call __sanitizer_cov_stack_depth" + * :: "r" (current_stack_pointer)) + * Use ASM_CALL_CONSTRAINT trick from arch/x86/include/asm/asm.h. + * This constraint is taken into account during gcc shrink-wrapping + * optimization. It is needed to be sure that __sanitizer_cov_stack_depth() + * call is inserted after the prologue of the containing function, + * when the stack frame is prepared. + */ + sp_decl = get_current_stack_pointer_decl(); + if (sp_decl == NULL_TREE) { + add_stack_tracking_gcall(gsi, after); + return; + } + input = build_tree_list(NULL_TREE, build_const_char_string(2, "r")); + input = chainon(NULL_TREE, build_tree_list(input, sp_decl)); + vec_safe_push(inputs, input); + asm_call = gimple_build_asm_vec("call __sanitizer_cov_stack_depth", + inputs, NULL, NULL, NULL); + gimple_asm_set_volatile(asm_call, true); + if (after) + gsi_insert_after(gsi, asm_call, GSI_CONTINUE_LINKING); + else + gsi_insert_before(gsi, asm_call, GSI_SAME_STMT); + update_stmt(asm_call); +} + +static void add_stack_tracking(gimple_stmt_iterator *gsi, bool after) +{ + /* + * The 'no_caller_saved_registers' attribute is used for + * __sanitizer_cov_stack_depth(). If the compiler supports this attribute for + * the target arch, we can add calling __sanitizer_cov_stack_depth() in asm. + * That improves performance: we avoid useless operations with the + * caller-saved registers in the functions from which we will remove + * __sanitizer_cov_stack_depth() call during the stackleak_cleanup pass. + */ + if (lookup_attribute_spec(get_identifier("no_caller_saved_registers"))) + add_stack_tracking_gasm(gsi, after); + else + add_stack_tracking_gcall(gsi, after); +} + +/* + * Work with the GIMPLE representation of the code. Insert the + * __sanitizer_cov_stack_depth() call after alloca() and into the beginning + * of the function if it is not instrumented. + */ +static unsigned int stackleak_instrument_execute(void) +{ + basic_block bb, entry_bb; + bool prologue_instrumented = false, is_leaf = true; + gimple_stmt_iterator gsi = { 0 }; + + /* + * ENTRY_BLOCK_PTR is a basic block which represents possible entry + * point of a function. This block does not contain any code and + * has a CFG edge to its successor. + */ + gcc_assert(single_succ_p(ENTRY_BLOCK_PTR_FOR_FN(cfun))); + entry_bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun)); + + /* + * Loop through the GIMPLE statements in each of cfun basic blocks. + * cfun is a global variable which represents the function that is + * currently processed. + */ + FOR_EACH_BB_FN(bb, cfun) { + for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) { + gimple stmt; + + stmt = gsi_stmt(gsi); + + /* Leaf function is a function which makes no calls */ + if (is_gimple_call(stmt)) + is_leaf = false; + + if (!is_alloca(stmt)) + continue; + + if (verbose) { + fprintf(stderr, "stackleak: be careful, alloca() in %s()\n", + DECL_NAME_POINTER(current_function_decl)); + } + + /* Insert __sanitizer_cov_stack_depth() call after alloca() */ + add_stack_tracking(&gsi, true); + if (bb == entry_bb) + prologue_instrumented = true; + } + } + + if (prologue_instrumented) + return 0; + + /* + * Special cases to skip the instrumentation. + * + * Taking the address of static inline functions materializes them, + * but we mustn't instrument some of them as the resulting stack + * alignment required by the function call ABI will break other + * assumptions regarding the expected (but not otherwise enforced) + * register clobbering ABI. + * + * Case in point: native_save_fl on amd64 when optimized for size + * clobbers rdx if it were instrumented here. + * + * TODO: any more special cases? + */ + if (is_leaf && + !TREE_PUBLIC(current_function_decl) && + DECL_DECLARED_INLINE_P(current_function_decl)) { + return 0; + } + + if (is_leaf && + !strncmp(IDENTIFIER_POINTER(DECL_NAME(current_function_decl)), + "_paravirt_", 10)) { + return 0; + } + + /* Insert __sanitizer_cov_stack_depth() call at the function beginning */ + bb = entry_bb; + if (!single_pred_p(bb)) { + /* gcc_assert(bb_loop_depth(bb) || + (bb->flags & BB_IRREDUCIBLE_LOOP)); */ + split_edge(single_succ_edge(ENTRY_BLOCK_PTR_FOR_FN(cfun))); + gcc_assert(single_succ_p(ENTRY_BLOCK_PTR_FOR_FN(cfun))); + bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun)); + } + gsi = gsi_after_labels(bb); + add_stack_tracking(&gsi, false); + + return 0; +} + +static bool large_stack_frame(void) +{ +#if BUILDING_GCC_VERSION >= 8000 + return maybe_ge(get_frame_size(), track_frame_size); +#else + return (get_frame_size() >= track_frame_size); +#endif +} + +static void remove_stack_tracking_gcall(void) +{ + rtx_insn *insn, *next; + + /* + * Find __sanitizer_cov_stack_depth() calls. Loop through the chain of insns, + * which is an RTL representation of the code for a function. + * + * The example of a matching insn: + * (call_insn 8 4 10 2 (call (mem (symbol_ref ("__sanitizer_cov_stack_depth") + * [flags 0x41] <function_decl 0x7f7cd3302a80 __sanitizer_cov_stack_depth>) + * [0 __sanitizer_cov_stack_depth S1 A8]) (0)) 675 {*call} (expr_list + * (symbol_ref ("__sanitizer_cov_stack_depth") [flags 0x41] <function_decl + * 0x7f7cd3302a80 __sanitizer_cov_stack_depth>) (expr_list (0) (nil))) (nil)) + */ + for (insn = get_insns(); insn; insn = next) { + rtx body; + + next = NEXT_INSN(insn); + + /* Check the expression code of the insn */ + if (!CALL_P(insn)) + continue; + + /* + * Check the expression code of the insn body, which is an RTL + * Expression (RTX) describing the side effect performed by + * that insn. + */ + body = PATTERN(insn); + + if (GET_CODE(body) == PARALLEL) + body = XVECEXP(body, 0, 0); + + if (GET_CODE(body) != CALL) + continue; + + /* + * Check the first operand of the call expression. It should + * be a mem RTX describing the needed subroutine with a + * symbol_ref RTX. + */ + body = XEXP(body, 0); + if (GET_CODE(body) != MEM) + continue; + + body = XEXP(body, 0); + if (GET_CODE(body) != SYMBOL_REF) + continue; + + if (SYMBOL_REF_DECL(body) != track_function_decl) + continue; + + /* Delete the __sanitizer_cov_stack_depth() call */ + delete_insn_and_edges(insn); +#if BUILDING_GCC_VERSION < 8000 + if (GET_CODE(next) == NOTE && + NOTE_KIND(next) == NOTE_INSN_CALL_ARG_LOCATION) { + insn = next; + next = NEXT_INSN(insn); + delete_insn_and_edges(insn); + } +#endif + } +} + +static bool remove_stack_tracking_gasm(void) +{ + bool removed = false; + rtx_insn *insn, *next; + + /* 'no_caller_saved_registers' is currently supported only for x86 */ + gcc_assert(build_for_x86); + + /* + * Find __sanitizer_cov_stack_depth() asm calls. Loop through the chain of + * insns, which is an RTL representation of the code for a function. + * + * The example of a matching insn: + * (insn 11 5 12 2 (parallel [ (asm_operands/v + * ("call __sanitizer_cov_stack_depth") ("") 0 + * [ (reg/v:DI 7 sp [ current_stack_pointer ]) ] + * [ (asm_input:DI ("r")) ] []) + * (clobber (reg:CC 17 flags)) ]) -1 (nil)) + */ + for (insn = get_insns(); insn; insn = next) { + rtx body; + + next = NEXT_INSN(insn); + + /* Check the expression code of the insn */ + if (!NONJUMP_INSN_P(insn)) + continue; + + /* + * Check the expression code of the insn body, which is an RTL + * Expression (RTX) describing the side effect performed by + * that insn. + */ + body = PATTERN(insn); + + if (GET_CODE(body) != PARALLEL) + continue; + + body = XVECEXP(body, 0, 0); + + if (GET_CODE(body) != ASM_OPERANDS) + continue; + + if (strcmp(ASM_OPERANDS_TEMPLATE(body), + "call __sanitizer_cov_stack_depth")) { + continue; + } + + delete_insn_and_edges(insn); + gcc_assert(!removed); + removed = true; + } + + return removed; +} + +/* + * Work with the RTL representation of the code. + * Remove the unneeded __sanitizer_cov_stack_depth() calls from the functions + * which don't call alloca() and don't have a large enough stack frame size. + */ +static unsigned int stackleak_cleanup_execute(void) +{ + const char *fn = DECL_NAME_POINTER(current_function_decl); + bool removed = false; + + /* + * Leave stack tracking in functions that call alloca(). + * Additional case: + * gcc before version 7 called allocate_dynamic_stack_space() from + * expand_stack_vars() for runtime alignment of constant-sized stack + * variables. That caused cfun->calls_alloca to be set for functions + * that in fact don't use alloca(). + * For more info see gcc commit 7072df0aae0c59ae437e. + * Let's leave such functions instrumented as well. + */ + if (cfun->calls_alloca) { + if (verbose) + fprintf(stderr, "stackleak: instrument %s(): calls_alloca\n", fn); + return 0; + } + + /* Leave stack tracking in functions with large stack frame */ + if (large_stack_frame()) { + if (verbose) + fprintf(stderr, "stackleak: instrument %s()\n", fn); + return 0; + } + + if (lookup_attribute_spec(get_identifier("no_caller_saved_registers"))) + removed = remove_stack_tracking_gasm(); + + if (!removed) + remove_stack_tracking_gcall(); + + return 0; +} + +/* + * STRING_CST may or may not be NUL terminated: + * https://gcc.gnu.org/onlinedocs/gccint/Constant-expressions.html + */ +static inline bool string_equal(tree node, const char *string, int length) +{ + if (TREE_STRING_LENGTH(node) < length) + return false; + if (TREE_STRING_LENGTH(node) > length + 1) + return false; + if (TREE_STRING_LENGTH(node) == length + 1 && + TREE_STRING_POINTER(node)[length] != '\0') + return false; + return !memcmp(TREE_STRING_POINTER(node), string, length); +} +#define STRING_EQUAL(node, str) string_equal(node, str, strlen(str)) + +static bool stackleak_gate(void) +{ + tree section; + + section = lookup_attribute("section", + DECL_ATTRIBUTES(current_function_decl)); + if (section && TREE_VALUE(section)) { + section = TREE_VALUE(TREE_VALUE(section)); + + if (STRING_EQUAL(section, ".init.text")) + return false; + if (STRING_EQUAL(section, ".devinit.text")) + return false; + if (STRING_EQUAL(section, ".cpuinit.text")) + return false; + if (STRING_EQUAL(section, ".meminit.text")) + return false; + if (STRING_EQUAL(section, ".noinstr.text")) + return false; + if (STRING_EQUAL(section, ".entry.text")) + return false; + if (STRING_EQUAL(section, ".head.text")) + return false; + } + + return track_frame_size >= 0; +} + +/* Build the function declaration for __sanitizer_cov_stack_depth() */ +static void stackleak_start_unit(void *gcc_data __unused, + void *user_data __unused) +{ + tree fntype; + + /* void __sanitizer_cov_stack_depth(void) */ + fntype = build_function_type_list(void_type_node, NULL_TREE); + track_function_decl = build_fn_decl(track_function, fntype); + DECL_ASSEMBLER_NAME(track_function_decl); /* for LTO */ + TREE_PUBLIC(track_function_decl) = 1; + TREE_USED(track_function_decl) = 1; + DECL_EXTERNAL(track_function_decl) = 1; + DECL_ARTIFICIAL(track_function_decl) = 1; + DECL_PRESERVE_P(track_function_decl) = 1; +} + +/* + * Pass gate function is a predicate function that gets executed before the + * corresponding pass. If the return value is 'true' the pass gets executed, + * otherwise, it is skipped. + */ +static bool stackleak_instrument_gate(void) +{ + return stackleak_gate(); +} + +#define PASS_NAME stackleak_instrument +#define PROPERTIES_REQUIRED PROP_gimple_leh | PROP_cfg +#define TODO_FLAGS_START TODO_verify_ssa | TODO_verify_flow | TODO_verify_stmts +#define TODO_FLAGS_FINISH TODO_verify_ssa | TODO_verify_stmts | TODO_dump_func \ + | TODO_update_ssa | TODO_rebuild_cgraph_edges +#include "gcc-generate-gimple-pass.h" + +static bool stackleak_cleanup_gate(void) +{ + return stackleak_gate(); +} + +#define PASS_NAME stackleak_cleanup +#define TODO_FLAGS_FINISH TODO_dump_func +#include "gcc-generate-rtl-pass.h" + +/* + * Every gcc plugin exports a plugin_init() function that is called right + * after the plugin is loaded. This function is responsible for registering + * the plugin callbacks and doing other required initialization. + */ +__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 * const argv = plugin_info->argv; + int i = 0; + + /* Extra GGC root tables describing our GTY-ed data */ + static const struct ggc_root_tab gt_ggc_r_gt_stackleak[] = { + { + .base = &track_function_decl, + .nelt = 1, + .stride = sizeof(track_function_decl), + .cb = >_ggc_mx_tree_node, + .pchw = >_pch_nx_tree_node + }, + LAST_GGC_ROOT_TAB + }; + + /* + * The stackleak_instrument pass should be executed before the + * "optimized" pass, which is the control flow graph cleanup that is + * performed just before expanding gcc trees to the RTL. In former + * versions of the plugin this new pass was inserted before the + * "tree_profile" pass, which is currently called "profile". + */ + PASS_INFO(stackleak_instrument, "optimized", 1, + PASS_POS_INSERT_BEFORE); + + /* + * The stackleak_cleanup pass should be executed before the "*free_cfg" + * pass. It's the moment when the stack frame size is already final, + * function prologues and epilogues are generated, and the + * machine-dependent code transformations are not done. + */ + PASS_INFO(stackleak_cleanup, "*free_cfg", 1, PASS_POS_INSERT_BEFORE); + + if (!plugin_default_version_check(version, &gcc_version)) { + error(G_("incompatible gcc/plugin versions")); + return 1; + } + + /* Parse the plugin arguments */ + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i].key, "track-min-size")) { + if (!argv[i].value) { + error(G_("no value supplied for option '-fplugin-arg-%s-%s'"), + plugin_name, argv[i].key); + return 1; + } + + track_frame_size = atoi(argv[i].value); + if (track_frame_size < 0) { + error(G_("invalid option argument '-fplugin-arg-%s-%s=%s'"), + plugin_name, argv[i].key, argv[i].value); + return 1; + } + } else if (!strcmp(argv[i].key, "arch")) { + 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].value, "x86")) + build_for_x86 = true; + } else if (!strcmp(argv[i].key, "disable")) { + disable = true; + } else if (!strcmp(argv[i].key, "verbose")) { + verbose = true; + } else { + error(G_("unknown option '-fplugin-arg-%s-%s'"), + plugin_name, argv[i].key); + return 1; + } + } + + if (disable) { + if (verbose) + fprintf(stderr, "stackleak: disabled for this translation unit\n"); + return 0; + } + + /* Give the information about the plugin */ + register_callback(plugin_name, PLUGIN_INFO, NULL, + &stackleak_plugin_info); + + /* Register to be called before processing a translation unit */ + register_callback(plugin_name, PLUGIN_START_UNIT, + &stackleak_start_unit, NULL); + + /* Register an extra GCC garbage collector (GGC) root table */ + register_callback(plugin_name, PLUGIN_REGISTER_GGC_ROOTS, NULL, + (void *)>_ggc_r_gt_stackleak); + + /* + * Hook into the Pass Manager to register new gcc passes. + * + * The stack frame size info is available only at the last RTL pass, + * when it's too late to insert complex code like a function call. + * So we register two gcc passes to instrument every function at first + * and remove the unneeded instrumentation later. + */ + register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, + &stackleak_instrument_pass_info); + register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, + &stackleak_cleanup_pass_info); + + return 0; +} diff --git a/scripts/gdb/Makefile b/scripts/gdb/Makefile new file mode 100644 index 000000000..3fca1937d --- /dev/null +++ b/scripts/gdb/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +subdir-y := linux diff --git a/scripts/gdb/linux/.gitignore b/scripts/gdb/linux/.gitignore new file mode 100644 index 000000000..43234cbcb --- /dev/null +++ b/scripts/gdb/linux/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +*.pyc +*.pyo +constants.py diff --git a/scripts/gdb/linux/Makefile b/scripts/gdb/linux/Makefile new file mode 100644 index 000000000..fcd32fcf3 --- /dev/null +++ b/scripts/gdb/linux/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 + +ifdef building_out_of_srctree + +symlinks := $(patsubst $(src)/%,%,$(wildcard $(src)/*.py)) + +quiet_cmd_symlink = SYMLINK $@ + cmd_symlink = ln -fsn $(patsubst $(obj)/%,$(abspath $(src))/%,$@) $@ + +always-y += $(symlinks) +$(addprefix $(obj)/, $(symlinks)): FORCE + $(call if_changed,symlink) + +endif + +quiet_cmd_gen_constants_py = GEN $@ + cmd_gen_constants_py = \ + $(CPP) -E -x c -P $(c_flags) $< > $@ ;\ + sed -i '1,/<!-- end-c-headers -->/d;' $@ + +always-y += constants.py +$(obj)/constants.py: $(src)/constants.py.in FORCE + $(call if_changed_dep,gen_constants_py) + +clean-files := *.pyc *.pyo diff --git a/scripts/gdb/linux/__init__.py b/scripts/gdb/linux/__init__.py new file mode 100644 index 000000000..4680fb176 --- /dev/null +++ b/scripts/gdb/linux/__init__.py @@ -0,0 +1 @@ +# nothing to do for the initialization of this package diff --git a/scripts/gdb/linux/bpf.py b/scripts/gdb/linux/bpf.py new file mode 100644 index 000000000..1870534ef --- /dev/null +++ b/scripts/gdb/linux/bpf.py @@ -0,0 +1,253 @@ +# SPDX-License-Identifier: GPL-2.0 + +import json +import subprocess +import tempfile + +import gdb + +from linux import constants, lists, radixtree, utils + + +if constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT: + bpf_ksym_type = utils.CachedType("struct bpf_ksym") +if constants.LX_CONFIG_BPF_SYSCALL: + bpf_prog_type = utils.CachedType("struct bpf_prog") + + +def get_ksym_name(ksym): + name = ksym["name"].bytes + end = name.find(b"\x00") + if end != -1: + name = name[:end] + return name.decode() + + +def list_ksyms(): + if not (constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT): + return [] + bpf_kallsyms = gdb.parse_and_eval("&bpf_kallsyms") + bpf_ksym_ptr_type = bpf_ksym_type.get_type().pointer() + return list(lists.list_for_each_entry(bpf_kallsyms, + bpf_ksym_ptr_type, + "lnode")) + + +class KsymAddBreakpoint(gdb.Breakpoint): + def __init__(self, monitor): + super(KsymAddBreakpoint, self).__init__("bpf_ksym_add", internal=True) + self.silent = True + self.monitor = monitor + + def stop(self): + self.monitor.add(gdb.parse_and_eval("ksym")) + return False + + +class KsymRemoveBreakpoint(gdb.Breakpoint): + def __init__(self, monitor): + super(KsymRemoveBreakpoint, self).__init__("bpf_ksym_del", + internal=True) + self.silent = True + self.monitor = monitor + + def stop(self): + self.monitor.remove(gdb.parse_and_eval("ksym")) + return False + + +class KsymMonitor: + def __init__(self, add, remove): + self.add = add + self.remove = remove + + self.add_bp = KsymAddBreakpoint(self) + self.remove_bp = KsymRemoveBreakpoint(self) + + self.notify_initial() + + def notify_initial(self): + for ksym in list_ksyms(): + self.add(ksym) + + def delete(self): + self.add_bp.delete() + self.remove_bp.delete() + + +def list_progs(): + if not constants.LX_CONFIG_BPF_SYSCALL: + return [] + idr_rt = gdb.parse_and_eval("&prog_idr.idr_rt") + bpf_prog_ptr_type = bpf_prog_type.get_type().pointer() + progs = [] + for _, slot in radixtree.for_each_slot(idr_rt): + prog = slot.dereference().cast(bpf_prog_ptr_type) + progs.append(prog) + # Subprogs are not registered in prog_idr, fetch them manually. + # func[0] is the current prog. + aux = prog["aux"] + func = aux["func"] + real_func_cnt = int(aux["real_func_cnt"]) + for i in range(1, real_func_cnt): + progs.append(func[i]) + return progs + + +class ProgAddBreakpoint(gdb.Breakpoint): + def __init__(self, monitor): + super(ProgAddBreakpoint, self).__init__("bpf_prog_kallsyms_add", + internal=True) + self.silent = True + self.monitor = monitor + + def stop(self): + self.monitor.add(gdb.parse_and_eval("fp")) + return False + + +class ProgRemoveBreakpoint(gdb.Breakpoint): + def __init__(self, monitor): + super(ProgRemoveBreakpoint, self).__init__("bpf_prog_free_id", + internal=True) + self.silent = True + self.monitor = monitor + + def stop(self): + self.monitor.remove(gdb.parse_and_eval("prog")) + return False + + +class ProgMonitor: + def __init__(self, add, remove): + self.add = add + self.remove = remove + + self.add_bp = ProgAddBreakpoint(self) + self.remove_bp = ProgRemoveBreakpoint(self) + + self.notify_initial() + + def notify_initial(self): + for prog in list_progs(): + self.add(prog) + + def delete(self): + self.add_bp.delete() + self.remove_bp.delete() + + +def btf_str_by_offset(btf, offset): + while offset < btf["start_str_off"]: + btf = btf["base_btf"] + + offset -= btf["start_str_off"] + if offset < btf["hdr"]["str_len"]: + return (btf["strings"] + offset).string() + + return None + + +def bpf_line_info_line_num(line_col): + return line_col >> 10 + + +def bpf_line_info_line_col(line_col): + return line_col & 0x3ff + + +class LInfoIter: + def __init__(self, prog): + # See bpf_prog_get_file_line() for details. + self.pos = 0 + self.nr_linfo = 0 + + if prog is None: + return + + self.bpf_func = int(prog["bpf_func"]) + aux = prog["aux"] + self.btf = aux["btf"] + linfo_idx = aux["linfo_idx"] + self.nr_linfo = int(aux["nr_linfo"]) - linfo_idx + if self.nr_linfo == 0: + return + + linfo_ptr = aux["linfo"] + tpe = linfo_ptr.type.target().array(self.nr_linfo).pointer() + self.linfo = (linfo_ptr + linfo_idx).cast(tpe).dereference() + jited_linfo_ptr = aux["jited_linfo"] + tpe = jited_linfo_ptr.type.target().array(self.nr_linfo).pointer() + self.jited_linfo = (jited_linfo_ptr + linfo_idx).cast(tpe).dereference() + + self.filenos = {} + + def get_code_off(self): + if self.pos >= self.nr_linfo: + return -1 + return self.jited_linfo[self.pos] - self.bpf_func + + def advance(self): + self.pos += 1 + + def get_fileno(self): + file_name_off = int(self.linfo[self.pos]["file_name_off"]) + fileno = self.filenos.get(file_name_off) + if fileno is not None: + return fileno, None + file_name = btf_str_by_offset(self.btf, file_name_off) + fileno = len(self.filenos) + 1 + self.filenos[file_name_off] = fileno + return fileno, file_name + + def get_line_col(self): + line_col = int(self.linfo[self.pos]["line_col"]) + return bpf_line_info_line_num(line_col), \ + bpf_line_info_line_col(line_col) + + +def generate_debug_obj(ksym, prog): + name = get_ksym_name(ksym) + # Avoid read_memory(); it throws bogus gdb.MemoryError in some contexts. + start = ksym["start"] + code = start.cast(gdb.lookup_type("unsigned char") + .array(int(ksym["end"]) - int(start)) + .pointer()).dereference().bytes + linfo_iter = LInfoIter(prog) + + result = tempfile.NamedTemporaryFile(suffix=".o", mode="wb") + try: + with tempfile.NamedTemporaryFile(suffix=".s", mode="w") as src: + # ".loc" does not apply to ".byte"s, only to ".insn"s, but since + # this needs to work for all architectures, the latter are not an + # option. Ask the assembler to apply ".loc"s to labels as well, + # and generate dummy labels after each ".loc". + src.write(".loc_mark_labels 1\n") + + src.write(".globl {}\n".format(name)) + src.write(".type {},@function\n".format(name)) + src.write("{}:\n".format(name)) + for code_off, code_byte in enumerate(code): + if linfo_iter.get_code_off() == code_off: + fileno, file_name = linfo_iter.get_fileno() + if file_name is not None: + src.write(".file {} {}\n".format( + fileno, json.dumps(file_name))) + line, col = linfo_iter.get_line_col() + src.write(".loc {} {} {}\n".format(fileno, line, col)) + src.write("0:\n") + linfo_iter.advance() + src.write(".byte {}\n".format(code_byte)) + src.write(".size {},{}\n".format(name, len(code))) + src.flush() + + try: + subprocess.check_call(["as", "-c", src.name, "-o", result.name]) + except FileNotFoundError: + # "as" is not installed. + result.close() + return None + return result + except: + result.close() + raise diff --git a/scripts/gdb/linux/clk.py b/scripts/gdb/linux/clk.py new file mode 100644 index 000000000..7a01fdc3e --- /dev/null +++ b/scripts/gdb/linux/clk.py @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) NXP 2019 + +import gdb +import sys + +from linux import utils, lists, constants + +clk_core_type = utils.CachedType("struct clk_core") + + +def clk_core_for_each_child(hlist_head): + return lists.hlist_for_each_entry(hlist_head, + clk_core_type.get_type().pointer(), "child_node") + + +class LxClkSummary(gdb.Command): + """Print clk tree summary + +Output is a subset of /sys/kernel/debug/clk/clk_summary + +No calls are made during printing, instead a (c) if printed after values which +are cached and potentially out of date""" + + def __init__(self): + super(LxClkSummary, self).__init__("lx-clk-summary", gdb.COMMAND_DATA) + + def show_subtree(self, clk, level): + gdb.write("%*s%-*s %7d %8d %8d %11lu%s\n" % ( + level * 3 + 1, "", + 30 - level * 3, + clk['name'].string(), + clk['enable_count'], + clk['prepare_count'], + clk['protect_count'], + clk['rate'], + '(c)' if clk['flags'] & constants.LX_CLK_GET_RATE_NOCACHE else ' ')) + + for child in clk_core_for_each_child(clk['children']): + self.show_subtree(child, level + 1) + + def invoke(self, arg, from_tty): + if utils.gdb_eval_or_none("clk_root_list") is None: + raise gdb.GdbError("No clocks registered") + gdb.write(" enable prepare protect \n") + gdb.write(" clock count count count rate \n") + gdb.write("------------------------------------------------------------------------\n") + for clk in clk_core_for_each_child(gdb.parse_and_eval("clk_root_list")): + self.show_subtree(clk, 0) + for clk in clk_core_for_each_child(gdb.parse_and_eval("clk_orphan_list")): + self.show_subtree(clk, 0) + + +LxClkSummary() + + +class LxClkCoreLookup(gdb.Function): + """Find struct clk_core by name""" + + def __init__(self): + super(LxClkCoreLookup, self).__init__("lx_clk_core_lookup") + + def lookup_hlist(self, hlist_head, name): + for child in clk_core_for_each_child(hlist_head): + if child['name'].string() == name: + return child + result = self.lookup_hlist(child['children'], name) + if result: + return result + + def invoke(self, name): + name = name.string() + return (self.lookup_hlist(gdb.parse_and_eval("clk_root_list"), name) or + self.lookup_hlist(gdb.parse_and_eval("clk_orphan_list"), name)) + + +LxClkCoreLookup() diff --git a/scripts/gdb/linux/config.py b/scripts/gdb/linux/config.py new file mode 100644 index 000000000..8843ab3cb --- /dev/null +++ b/scripts/gdb/linux/config.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright 2019 Google LLC. + +import gdb +import zlib + +from linux import utils + + +class LxConfigDump(gdb.Command): + """Output kernel config to the filename specified as the command + argument. Equivalent to 'zcat /proc/config.gz > config.txt' on + a running target""" + + def __init__(self): + super(LxConfigDump, self).__init__("lx-configdump", gdb.COMMAND_DATA, + gdb.COMPLETE_FILENAME) + + def invoke(self, arg, from_tty): + if len(arg) == 0: + filename = "config.txt" + else: + filename = arg + + try: + py_config_ptr = gdb.parse_and_eval("&kernel_config_data") + py_config_ptr_end = gdb.parse_and_eval("&kernel_config_data_end") + py_config_size = py_config_ptr_end - py_config_ptr + except gdb.error as e: + raise gdb.GdbError("Can't find config, enable CONFIG_IKCONFIG?") + + inf = gdb.inferiors()[0] + zconfig_buf = utils.read_memoryview(inf, py_config_ptr, + py_config_size).tobytes() + + config_buf = zlib.decompress(zconfig_buf, 16) + with open(filename, 'wb') as f: + f.write(config_buf) + + gdb.write("Dumped config to " + filename + "\n") + + +LxConfigDump() diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in new file mode 100644 index 000000000..dab8b80be --- /dev/null +++ b/scripts/gdb/linux/constants.py.in @@ -0,0 +1,175 @@ +/* + * gdb helper commands and functions for Linux kernel debugging + * + * Kernel constants derived from include files. + * + * Copyright (c) 2016 Linaro Ltd + * + * Authors: + * Kieran Bingham <kieran.bingham@linaro.org> + * + * This work is licensed under the terms of the GNU GPL version 2. + * + */ + +#include <linux/clk-provider.h> +#include <linux/fs.h> +#include <linux/hrtimer.h> +#include <linux/irq.h> +#include <linux/mount.h> +#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> + +/* We need to stringify expanded macros so that they can be parsed */ + +#define STRING(x) #x +#define XSTRING(x) STRING(x) + +#define LX_VALUE(x) LX_##x = x +#define LX_GDBPARSED(x) LX_##x = gdb.parse_and_eval(XSTRING(x)) + +/* + * IS_ENABLED generates (a || b) which is not compatible with python + * We can only switch on configuration items we know are available + * Therefore - IS_BUILTIN() is more appropriate + */ +#define LX_CONFIG(x) LX_##x = IS_BUILTIN(x) + +/* The build system will take care of deleting everything above this marker */ +<!-- end-c-headers --> + +import gdb + +LX_CONFIG(CONFIG_DEBUG_INFO_REDUCED) + +/* linux/clk-provider.h */ +if IS_BUILTIN(CONFIG_COMMON_CLK): + LX_GDBPARSED(CLK_GET_RATE_NOCACHE) + +/* linux/fs.h */ +LX_GDBPARSED(SB_RDONLY) +LX_GDBPARSED(SB_SYNCHRONOUS) +LX_GDBPARSED(SB_MANDLOCK) +LX_GDBPARSED(SB_DIRSYNC) +LX_GDBPARSED(SB_NOATIME) +LX_GDBPARSED(SB_NODIRATIME) + +/* linux/htimer.h */ +LX_GDBPARSED(hrtimer_resolution) + +/* linux/irq.h */ +LX_GDBPARSED(IRQD_LEVEL) +LX_GDBPARSED(IRQ_HIDDEN) + +/* linux/module.h */ +if IS_BUILTIN(CONFIG_MODULES): + LX_GDBPARSED(MOD_TEXT) + LX_GDBPARSED(MOD_DATA) + LX_GDBPARSED(MOD_RODATA) + LX_GDBPARSED(MOD_RO_AFTER_INIT) + +/* linux/mount.h */ +LX_GDBPARSED(MNT_NOSUID) +LX_GDBPARSED(MNT_NODEV) +LX_GDBPARSED(MNT_NOEXEC) +LX_GDBPARSED(MNT_NOATIME) +LX_GDBPARSED(MNT_NODIRATIME) +LX_GDBPARSED(MNT_RELATIME) + +/* linux/threads.h */ +LX_VALUE(NR_CPUS) + +/* linux/of_fdt.h> */ +LX_VALUE(OF_DT_HEADER) + +/* linux/radix-tree.h */ +LX_GDBPARSED(RADIX_TREE_ENTRY_MASK) +LX_GDBPARSED(RADIX_TREE_INTERNAL_NODE) +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) +LX_VALUE(VM_MAP) +LX_VALUE(VM_USERMAP) +LX_VALUE(VM_DMA_COHERENT) + +/* linux/page_ext.h */ +if IS_BUILTIN(CONFIG_PAGE_OWNER): + LX_GDBPARSED(PAGE_EXT_OWNER) + LX_GDBPARSED(PAGE_EXT_OWNER_ALLOCATED) + +/* linux/slab.h */ +LX_GDBPARSED(SLAB_RED_ZONE) +LX_GDBPARSED(SLAB_POISON) +LX_GDBPARSED(SLAB_KMALLOC) +LX_GDBPARSED(SLAB_HWCACHE_ALIGN) +LX_GDBPARSED(SLAB_CACHE_DMA) +LX_GDBPARSED(SLAB_CACHE_DMA32) +LX_GDBPARSED(SLAB_STORE_USER) +LX_GDBPARSED(SLAB_PANIC) + +/* Kernel Configs */ +LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS) +LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) +LX_CONFIG(CONFIG_HIGH_RES_TIMERS) +LX_CONFIG(CONFIG_NR_CPUS) +LX_CONFIG(CONFIG_OF) +LX_CONFIG(CONFIG_TICK_ONESHOT) +LX_CONFIG(CONFIG_GENERIC_IRQ_SHOW_LEVEL) +LX_CONFIG(CONFIG_X86_LOCAL_APIC) +LX_CONFIG(CONFIG_SMP) +LX_CONFIG(CONFIG_X86_THERMAL_VECTOR) +LX_CONFIG(CONFIG_X86_MCE_THRESHOLD) +LX_CONFIG(CONFIG_X86_MCE_AMD) +LX_CONFIG(CONFIG_X86_MCE) +LX_CONFIG(CONFIG_X86_IO_APIC) +/* + * CONFIG_KVM can be "m" but it affects common code too. Use CONFIG_KVM_COMMON + * as a proxy for IS_ENABLED(CONFIG_KVM). + */ +LX_CONFIG_KVM = IS_BUILTIN(CONFIG_KVM_COMMON) +LX_CONFIG(CONFIG_NUMA) +LX_CONFIG(CONFIG_ARM64) +LX_CONFIG(CONFIG_ARM64_4K_PAGES) +LX_CONFIG(CONFIG_ARM64_16K_PAGES) +LX_CONFIG(CONFIG_ARM64_64K_PAGES) +if IS_BUILTIN(CONFIG_ARM64): + LX_VALUE(CONFIG_ARM64_PA_BITS) + LX_VALUE(CONFIG_ARM64_VA_BITS) + LX_VALUE(CONFIG_ARCH_FORCE_MAX_ORDER) +LX_VALUE(CONFIG_PAGE_SHIFT) +LX_CONFIG(CONFIG_SPARSEMEM) +LX_CONFIG(CONFIG_SPARSEMEM_EXTREME) +LX_CONFIG(CONFIG_SPARSEMEM_VMEMMAP) +LX_CONFIG(CONFIG_KASAN) +LX_CONFIG(CONFIG_KASAN_GENERIC) +LX_CONFIG(CONFIG_KASAN_SW_TAGS) +LX_CONFIG(CONFIG_KASAN_HW_TAGS) +if IS_BUILTIN(CONFIG_KASAN_GENERIC) or IS_BUILTIN(CONFIG_KASAN_SW_TAGS): + LX_VALUE(CONFIG_KASAN_SHADOW_OFFSET) +LX_CONFIG(CONFIG_VMAP_STACK) +if IS_BUILTIN(CONFIG_NUMA): + LX_VALUE(CONFIG_NODES_SHIFT) +LX_CONFIG(CONFIG_DEBUG_VIRTUAL) +LX_CONFIG(CONFIG_STACKDEPOT) +LX_CONFIG(CONFIG_PAGE_OWNER) +LX_CONFIG(CONFIG_SLUB_DEBUG) +LX_CONFIG(CONFIG_SLAB_FREELIST_HARDENED) +LX_CONFIG(CONFIG_MMU) +LX_CONFIG(CONFIG_BPF) +LX_CONFIG(CONFIG_BPF_JIT) +LX_CONFIG(CONFIG_BPF_SYSCALL) diff --git a/scripts/gdb/linux/cpus.py b/scripts/gdb/linux/cpus.py new file mode 100644 index 000000000..6edf4ef61 --- /dev/null +++ b/scripts/gdb/linux/cpus.py @@ -0,0 +1,227 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# per-cpu tools +# +# Copyright (c) Siemens AG, 2011-2013 +# +# Authors: +# Jan Kiszka <jan.kiszka@siemens.com> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import gdb + +from linux import tasks, utils + + +task_type = utils.CachedType("struct task_struct") + + +MAX_CPUS = 4096 + + +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: + 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.") + + +def per_cpu(var_ptr, cpu): + if cpu == -1: + cpu = get_current_cpu() + if utils.is_target_arch("sparc:v9"): + offset = gdb.parse_and_eval( + "trap_block[{0}].__per_cpu_base".format(str(cpu))) + else: + try: + offset = gdb.parse_and_eval( + "__per_cpu_offset[{0}]".format(str(cpu))) + except gdb.error: + # !CONFIG_SMP case + offset = 0 + pointer = var_ptr.cast(utils.get_long_type()) + offset + return pointer.cast(var_ptr.type) + + +cpu_mask = {} + + +def cpu_mask_invalidate(event): + global cpu_mask + cpu_mask = {} + gdb.events.stop.disconnect(cpu_mask_invalidate) + if hasattr(gdb.events, 'new_objfile'): + gdb.events.new_objfile.disconnect(cpu_mask_invalidate) + + +def cpu_list(mask_name): + global cpu_mask + mask = None + if mask_name in cpu_mask: + mask = cpu_mask[mask_name] + if mask is None: + mask = gdb.parse_and_eval(mask_name + ".bits") + if hasattr(gdb, 'events'): + cpu_mask[mask_name] = mask + gdb.events.stop.connect(cpu_mask_invalidate) + if hasattr(gdb.events, 'new_objfile'): + gdb.events.new_objfile.connect(cpu_mask_invalidate) + bits_per_entry = mask[0].type.sizeof * 8 + num_entries = mask.type.sizeof * 8 / bits_per_entry + entry = -1 + bits = 0 + + while True: + while bits == 0: + entry += 1 + if entry == num_entries: + return + bits = mask[entry] + if bits != 0: + bit = 0 + break + + while bits & 1 == 0: + bits >>= 1 + bit += 1 + + cpu = entry * bits_per_entry + bit + + bits >>= 1 + bit += 1 + + yield int(cpu) + + +def each_online_cpu(): + for cpu in cpu_list("__cpu_online_mask"): + yield cpu + + +def each_present_cpu(): + for cpu in cpu_list("__cpu_present_mask"): + yield cpu + + +def each_possible_cpu(): + for cpu in cpu_list("__cpu_possible_mask"): + yield cpu + + +def each_active_cpu(): + for cpu in cpu_list("__cpu_active_mask"): + yield cpu + + +class LxCpus(gdb.Command): + """List CPU status arrays + +Displays the known state of each CPU based on the kernel masks +and can help identify the state of hotplugged CPUs""" + + def __init__(self): + super(LxCpus, self).__init__("lx-cpus", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + gdb.write("Possible CPUs : {}\n".format(list(each_possible_cpu()))) + gdb.write("Present CPUs : {}\n".format(list(each_present_cpu()))) + gdb.write("Online CPUs : {}\n".format(list(each_online_cpu()))) + gdb.write("Active CPUs : {}\n".format(list(each_active_cpu()))) + + +LxCpus() + + +class PerCpu(gdb.Function): + """Return per-cpu variable. + +$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, 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() + + if utils.is_target_arch("x86"): + if gdb.lookup_global_symbol("cpu_tasks"): + # This is a UML kernel, which stores the current task + # differently than other x86 sub architectures + var_ptr = gdb.parse_and_eval("(struct task_struct *)cpu_tasks[0].task") + return var_ptr.dereference() + else: + 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("(unsigned long)$SP_EL0") + if (current_task_addr >> 63) != 0: + current_task = current_task_addr.cast(task_ptr_type) + return current_task.dereference() + else: + raise gdb.GdbError("Sorry, obtaining the current task is not allowed " + "while running in userspace(EL0)") + elif utils.is_target_arch("riscv"): + current_tp = gdb.parse_and_eval("$tp") + scratch_reg = gdb.parse_and_eval("$sscratch") + + # by default tp points to current task + current_task = current_tp.cast(task_ptr_type) + + # scratch register is set 0 in trap handler after entering kernel. + # When hart is in user mode, scratch register is pointing to task_struct. + # and tp is used by user mode. So when scratch register holds larger value + # (negative address as ulong is larger value) than tp, then use scratch register. + if (scratch_reg.cast(utils.get_ulong_type()) > current_tp.cast(utils.get_ulong_type())): + current_task = scratch_reg.cast(task_ptr_type) + + return current_task.dereference() + else: + raise gdb.GdbError("Sorry, obtaining the current task is not yet " + "supported with this arch") + +class LxCurrentFunc(gdb.Function): + """Return current task. + +$lx_current([CPU]): Return the per-cpu task variable for the given CPU +number. If CPU is omitted, the CPU of the current context is used.""" + + def __init__(self): + super(LxCurrentFunc, self).__init__("lx_current") + + def invoke(self, cpu=-1): + return get_current_task(cpu) + + +LxCurrentFunc() diff --git a/scripts/gdb/linux/device.py b/scripts/gdb/linux/device.py new file mode 100644 index 000000000..0eabc5f4f --- /dev/null +++ b/scripts/gdb/linux/device.py @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) NXP 2019 + +import gdb + +from linux.utils import CachedType +from linux.utils import container_of +from linux.lists import list_for_each_entry + + +device_private_type = CachedType('struct device_private') +device_type = CachedType('struct device') + +subsys_private_type = CachedType('struct subsys_private') +kobject_type = CachedType('struct kobject') +kset_type = CachedType('struct kset') + +bus_type = CachedType('struct bus_type') +class_type = CachedType('struct class') + + +def dev_name(dev): + dev_init_name = dev['init_name'] + if dev_init_name: + return dev_init_name.string() + return dev['kobj']['name'].string() + + +def kset_for_each_object(kset): + return list_for_each_entry(kset['list'], + kobject_type.get_type().pointer(), "entry") + + +def for_each_bus(): + for kobj in kset_for_each_object(gdb.parse_and_eval('bus_kset')): + subsys = container_of(kobj, kset_type.get_type().pointer(), 'kobj') + subsys_priv = container_of(subsys, subsys_private_type.get_type().pointer(), 'subsys') + yield subsys_priv + + +def for_each_class(): + for kobj in kset_for_each_object(gdb.parse_and_eval('class_kset')): + subsys = container_of(kobj, kset_type.get_type().pointer(), 'kobj') + subsys_priv = container_of(subsys, subsys_private_type.get_type().pointer(), 'subsys') + yield subsys_priv + + +def get_bus_by_name(name): + for item in for_each_bus(): + if item['bus']['name'].string() == name: + return item + raise gdb.GdbError("Can't find bus type {!r}".format(name)) + + +def get_class_by_name(name): + for item in for_each_class(): + if item['class']['name'].string() == name: + return item + raise gdb.GdbError("Can't find device class {!r}".format(name)) + + +klist_type = CachedType('struct klist') +klist_node_type = CachedType('struct klist_node') + + +def klist_for_each(klist): + return list_for_each_entry(klist['k_list'], + klist_node_type.get_type().pointer(), 'n_node') + + +def bus_for_each_device(bus): + for kn in klist_for_each(bus['klist_devices']): + dp = container_of(kn, device_private_type.get_type().pointer(), 'knode_bus') + yield dp['device'] + + +def class_for_each_device(cls): + for kn in klist_for_each(cls['klist_devices']): + dp = container_of(kn, device_private_type.get_type().pointer(), 'knode_class') + yield dp['device'] + + +def device_for_each_child(dev): + for kn in klist_for_each(dev['p']['klist_children']): + dp = container_of(kn, device_private_type.get_type().pointer(), 'knode_parent') + yield dp['device'] + + +def _show_device(dev, level=0, recursive=False): + gdb.write('{}dev {}:\t{}\n'.format('\t' * level, dev_name(dev), dev)) + if recursive: + for child in device_for_each_child(dev): + _show_device(child, level + 1, recursive) + + +class LxDeviceListBus(gdb.Command): + '''Print devices on a bus (or all buses if not specified)''' + + def __init__(self): + super(LxDeviceListBus, self).__init__('lx-device-list-bus', gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + if not arg: + for bus in for_each_bus(): + gdb.write('bus {}:\t{}\n'.format(bus['bus']['name'].string(), bus)) + for dev in bus_for_each_device(bus): + _show_device(dev, level=1) + else: + bus = get_bus_by_name(arg) + if not bus: + raise gdb.GdbError("Can't find bus {!r}".format(arg)) + for dev in bus_for_each_device(bus): + _show_device(dev) + + +class LxDeviceListClass(gdb.Command): + '''Print devices in a class (or all classes if not specified)''' + + def __init__(self): + super(LxDeviceListClass, self).__init__('lx-device-list-class', gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + if not arg: + for cls in for_each_class(): + gdb.write("class {}:\t{}\n".format(cls['class']['name'].string(), cls)) + for dev in class_for_each_device(cls): + _show_device(dev, level=1) + else: + cls = get_class_by_name(arg) + for dev in class_for_each_device(cls): + _show_device(dev) + + +class LxDeviceListTree(gdb.Command): + '''Print a device and its children recursively''' + + def __init__(self): + super(LxDeviceListTree, self).__init__('lx-device-list-tree', gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + if not arg: + raise gdb.GdbError('Please provide pointer to struct device') + dev = gdb.parse_and_eval(arg) + if dev.type != device_type.get_type().pointer(): + raise gdb.GdbError('Please provide pointer to struct device') + _show_device(dev, level=0, recursive=True) + + +class LxDeviceFindByBusName(gdb.Function): + '''Find struct device by bus and name (both strings)''' + + def __init__(self): + super(LxDeviceFindByBusName, self).__init__('lx_device_find_by_bus_name') + + def invoke(self, bus, name): + name = name.string() + bus = get_bus_by_name(bus.string()) + for dev in bus_for_each_device(bus): + if dev_name(dev) == name: + return dev + + +class LxDeviceFindByClassName(gdb.Function): + '''Find struct device by class and name (both strings)''' + + def __init__(self): + super(LxDeviceFindByClassName, self).__init__('lx_device_find_by_class_name') + + def invoke(self, cls, name): + name = name.string() + cls = get_class_by_name(cls.string()) + for dev in class_for_each_device(cls): + if dev_name(dev) == name: + return dev + + +LxDeviceListBus() +LxDeviceListClass() +LxDeviceListTree() +LxDeviceFindByBusName() +LxDeviceFindByClassName() diff --git a/scripts/gdb/linux/dmesg.py b/scripts/gdb/linux/dmesg.py new file mode 100644 index 000000000..c771831eb --- /dev/null +++ b/scripts/gdb/linux/dmesg.py @@ -0,0 +1,152 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# kernel log buffer dump +# +# Copyright (c) Siemens AG, 2011, 2012 +# +# Authors: +# Jan Kiszka <jan.kiszka@siemens.com> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import gdb +import sys + +from linux import utils + +printk_info_type = utils.CachedType("struct printk_info") +prb_data_blk_lpos_type = utils.CachedType("struct prb_data_blk_lpos") +prb_desc_type = utils.CachedType("struct prb_desc") +prb_desc_ring_type = utils.CachedType("struct prb_desc_ring") +prb_data_ring_type = utils.CachedType("struct prb_data_ring") +printk_ringbuffer_type = utils.CachedType("struct printk_ringbuffer") + +class LxDmesg(gdb.Command): + """Print Linux kernel log buffer.""" + + def __init__(self): + super(LxDmesg, self).__init__("lx-dmesg", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + inf = gdb.inferiors()[0] + + # read in prb structure + prb_addr = int(str(gdb.parse_and_eval("(void *)'printk.c'::prb")).split()[0], 16) + sz = printk_ringbuffer_type.get_type().sizeof + prb = utils.read_memoryview(inf, prb_addr, sz).tobytes() + + # read in descriptor ring structure + off = printk_ringbuffer_type.get_type()['desc_ring'].bitpos // 8 + addr = prb_addr + off + sz = prb_desc_ring_type.get_type().sizeof + desc_ring = utils.read_memoryview(inf, addr, sz).tobytes() + + # read in descriptor count, size, and address + off = prb_desc_ring_type.get_type()['count_bits'].bitpos // 8 + desc_ring_count = 1 << utils.read_u32(desc_ring, off) + desc_sz = prb_desc_type.get_type().sizeof + off = prb_desc_ring_type.get_type()['descs'].bitpos // 8 + desc_addr = utils.read_ulong(desc_ring, off) + + # read in info size and address + info_sz = printk_info_type.get_type().sizeof + off = prb_desc_ring_type.get_type()['infos'].bitpos // 8 + info_addr = utils.read_ulong(desc_ring, off) + + # read in text data ring structure + off = printk_ringbuffer_type.get_type()['text_data_ring'].bitpos // 8 + addr = prb_addr + off + sz = prb_data_ring_type.get_type().sizeof + text_data_ring = utils.read_memoryview(inf, addr, sz).tobytes() + + # read in text data size and address + off = prb_data_ring_type.get_type()['size_bits'].bitpos // 8 + text_data_sz = 1 << utils.read_u32(text_data_ring, off) + off = prb_data_ring_type.get_type()['data'].bitpos // 8 + text_data_addr = utils.read_ulong(text_data_ring, off) + + sv_off = prb_desc_type.get_type()['state_var'].bitpos // 8 + + off = prb_desc_type.get_type()['text_blk_lpos'].bitpos // 8 + begin_off = off + (prb_data_blk_lpos_type.get_type()['begin'].bitpos // 8) + next_off = off + (prb_data_blk_lpos_type.get_type()['next'].bitpos // 8) + + ts_off = printk_info_type.get_type()['ts_nsec'].bitpos // 8 + len_off = printk_info_type.get_type()['text_len'].bitpos // 8 + + # definitions from kernel/printk/printk_ringbuffer.h + desc_committed = 1 + desc_finalized = 2 + desc_sv_bits = utils.get_long_type().sizeof * 8 + desc_flags_shift = desc_sv_bits - 2 + desc_flags_mask = 3 << desc_flags_shift + desc_id_mask = ~desc_flags_mask + + # read in tail and head descriptor ids + off = prb_desc_ring_type.get_type()['tail_id'].bitpos // 8 + tail_id = utils.read_atomic_long(desc_ring, off) + off = prb_desc_ring_type.get_type()['head_id'].bitpos // 8 + head_id = utils.read_atomic_long(desc_ring, off) + + did = tail_id + while True: + ind = did % desc_ring_count + desc_off = desc_sz * ind + info_off = info_sz * ind + + desc = utils.read_memoryview(inf, desc_addr + desc_off, desc_sz).tobytes() + + # skip non-committed record + state = 3 & (utils.read_atomic_long(desc, sv_off) >> desc_flags_shift) + if state != desc_committed and state != desc_finalized: + if did == head_id: + break + did = (did + 1) & desc_id_mask + continue + + begin = utils.read_ulong(desc, begin_off) % text_data_sz + end = utils.read_ulong(desc, next_off) % text_data_sz + + info = utils.read_memoryview(inf, info_addr + info_off, info_sz).tobytes() + + # handle data-less record + if begin & 1 == 1: + text = "" + else: + # handle wrapping data block + if begin > end: + begin = 0 + + # skip over descriptor id + text_start = begin + utils.get_long_type().sizeof + + text_len = utils.read_u16(info, len_off) + + # handle truncated message + if end - text_start < text_len: + text_len = end - text_start + + text_data = utils.read_memoryview(inf, text_data_addr + text_start, + text_len).tobytes() + text = text_data[0:text_len].decode(encoding='utf8', errors='replace') + + time_stamp = utils.read_u64(info, ts_off) + + for line in text.splitlines(): + msg = u"[{time:12.6f}] {line}\n".format( + time=time_stamp / 1000000000.0, + line=line) + # With python2 gdb.write will attempt to convert unicode to + # ascii and might fail so pass an utf8-encoded str instead. + if sys.hexversion < 0x03000000: + msg = msg.encode(encoding='utf8', errors='replace') + gdb.write(msg) + + if did == head_id: + break + did = (did + 1) & desc_id_mask + + +LxDmesg() diff --git a/scripts/gdb/linux/genpd.py b/scripts/gdb/linux/genpd.py new file mode 100644 index 000000000..b53649c0a --- /dev/null +++ b/scripts/gdb/linux/genpd.py @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) NXP 2019 + +import gdb +import sys + +from linux.utils import CachedType, gdb_eval_or_none +from linux.lists import list_for_each_entry + +generic_pm_domain_type = CachedType('struct generic_pm_domain') +pm_domain_data_type = CachedType('struct pm_domain_data') +device_link_type = CachedType('struct device_link') + + +def kobject_get_path(kobj): + path = kobj['name'].string() + parent = kobj['parent'] + if parent: + path = kobject_get_path(parent) + '/' + path + return path + + +def rtpm_status_str(dev): + if dev['power']['runtime_error']: + return 'error' + if dev['power']['disable_depth']: + return 'unsupported' + _RPM_STATUS_LOOKUP = [ + "active", + "resuming", + "suspended", + "suspending" + ] + return _RPM_STATUS_LOOKUP[dev['power']['runtime_status']] + + +class LxGenPDSummary(gdb.Command): + '''Print genpd summary + +Output is similar to /sys/kernel/debug/pm_genpd/pm_genpd_summary''' + + def __init__(self): + super(LxGenPDSummary, self).__init__('lx-genpd-summary', gdb.COMMAND_DATA) + + def summary_one(self, genpd): + if genpd['status'] == 0: + status_string = 'on' + else: + status_string = 'off-{}'.format(genpd['state_idx']) + + child_names = [] + for link in list_for_each_entry( + genpd['parent_links'], + device_link_type.get_type().pointer(), + 'parent_node'): + child_names.append(link['child']['name']) + + gdb.write('%-30s %-15s %s\n' % ( + genpd['name'].string(), + status_string, + ', '.join(child_names))) + + # Print devices in domain + for pm_data in list_for_each_entry(genpd['dev_list'], + pm_domain_data_type.get_type().pointer(), + 'list_node'): + dev = pm_data['dev'] + kobj_path = kobject_get_path(dev['kobj']) + gdb.write(' %-50s %s\n' % (kobj_path, rtpm_status_str(dev))) + + def invoke(self, arg, from_tty): + if gdb_eval_or_none("&gpd_list") is None: + raise gdb.GdbError("No power domain(s) registered") + gdb.write('domain status children\n'); + gdb.write(' /device runtime status\n'); + gdb.write('----------------------------------------------------------------------\n'); + for genpd in list_for_each_entry( + gdb.parse_and_eval('&gpd_list'), + generic_pm_domain_type.get_type().pointer(), + 'gpd_list_node'): + self.summary_one(genpd) + + +LxGenPDSummary() diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py new file mode 100644 index 000000000..a68ae91b4 --- /dev/null +++ b/scripts/gdb/linux/interrupts.py @@ -0,0 +1,200 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright 2023 Broadcom + +import gdb + +from linux import constants +from linux import cpus +from linux import utils +from linux import mapletree + +irq_desc_type = utils.CachedType("struct irq_desc") + +def irq_settings_is_hidden(desc): + return desc['status_use_accessors'] & constants.LX_IRQ_HIDDEN + +def irq_desc_is_chained(desc): + return desc['action'] and desc['action'] == gdb.parse_and_eval("&chained_action") + +def irqd_is_level(desc): + return desc['irq_data']['common']['state_use_accessors'] & constants.LX_IRQD_LEVEL + +def show_irq_desc(prec, chip_width, irq): + text = "" + + 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().pointer()) + if desc == 0: + return text + + if irq_settings_is_hidden(desc): + return text + + any_count = 0 + if desc['kstat_irqs']: + for cpu in cpus.each_online_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; + + text += "%*d: " % (prec, irq) + for cpu in cpus.each_online_cpu(): + if desc['kstat_irqs']: + count = cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt'] + else: + count = 0 + text += "%10u " % (count) + + name = "None" + if desc['irq_data']['chip']: + chip = desc['irq_data']['chip'] + if chip['name']: + name = chip['name'].string() + else: + name = "-" + + text += " %-*s" % (chip_width, name) + + if desc['irq_data']['domain']: + text += " %*lu" % (prec, desc['irq_data']['hwirq']) + else: + text += " %*s" % (prec, "") + + if constants.LX_CONFIG_GENERIC_IRQ_SHOW_LEVEL: + text += " %-8s" % ("Level" if irqd_is_level(desc) else "Edge") + + if desc['name']: + text += "-%-8s" % (desc['name'].string()) + + """ Some toolchains may not be able to provide information about irqaction """ + try: + gdb.lookup_type("struct irqaction") + action = desc['action'] + if action is not None: + text += " %s" % (action['name'].string()) + while True: + action = action['next'] + if action is not None: + break + if action['name']: + text += ", %s" % (action['name'].string()) + except: + pass + + text += "\n" + + return text + +def show_irq_err_count(prec): + cnt = utils.gdb_eval_or_none("irq_err_count") + text = "" + if cnt is not None: + text += "%*s: %10u\n" % (prec, "ERR", cnt['counter']) + return text + +def x86_show_irqstat(prec, pfx, idx, desc): + irq_stat = gdb.parse_and_eval("&irq_stat.counts[%d]" %idx) + text = "%*s: " % (prec, pfx) + for cpu in cpus.each_online_cpu(): + stat = cpus.per_cpu(irq_stat, cpu) + text += "%10u " % (stat.dereference()) + text += desc + return text + +def x86_show_interupts(prec): + info_type = gdb.lookup_type('struct irq_stat_info') + info = gdb.parse_and_eval('irq_stat_info') + bitmap = gdb.parse_and_eval('irq_stat_count_show') + bitsperlong = 8 * int(bitmap.type.target().sizeof) + + text = "" + for idx in range(int(info.type.sizeof / info_type.sizeof)): + show = bitmap[int(idx / bitsperlong)] + if not show & 1 << int(idx % bitsperlong): + continue + pfx = info[idx]['symbol'].string() + desc = info[idx]['text'].string() + text += x86_show_irqstat(prec, pfx, idx, desc) + + return text + +def arm_common_show_interrupts(prec): + text = "" + nr_ipi = utils.gdb_eval_or_none("nr_ipi") + ipi_desc = utils.gdb_eval_or_none("ipi_desc") + ipi_types = utils.gdb_eval_or_none("ipi_types") + if nr_ipi is None or ipi_desc is None or ipi_types is None: + return text + + for ipi in range(nr_ipi): + text += "%*s%u: " % (prec - 1, "IPI", ipi) + desc = ipi_desc[ipi].cast(irq_desc_type.get_type().pointer()) + if desc == 0: + continue + for cpu in cpus.each_online_cpu(): + text += "%10u " % (cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt']) + text += "%s" % (ipi_types[ipi].string()) + text += "\n" + return text + +def aarch64_show_interrupts(prec): + # Does not work for ARM64 as "ipi_desc" is not available there + text = arm_common_show_interrupts(prec) + text += "%*s: %10lu\n" % (prec, "ERR", gdb.parse_and_eval("irq_err_count")) + return text + +def arch_show_interrupts(prec): + text = "" + if utils.is_target_arch("x86"): + text += x86_show_interupts(prec) + elif utils.is_target_arch("aarch64"): + text += aarch64_show_interrupts(prec) + elif utils.is_target_arch("arm"): + text += arm_common_show_interrupts(prec) + elif utils.is_target_arch("mips"): + text += show_irq_err_count(prec) + else: + raise gdb.GdbError("Unsupported architecture: {}".format(target_arch)) + + return text + +class LxInterruptList(gdb.Command): + """Print /proc/interrupts""" + + def __init__(self): + super(LxInterruptList, self).__init__("lx-interruptlist", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + nr_irqs = gdb.parse_and_eval("total_nr_irqs") + constr = utils.gdb_eval_or_none('irq_proc_constraints') + + if constr: + prec = int(constr['num_prec']) + chip_width = int(constr['chip_width']) + else: + prec = 4 + j = 10000 + while prec < 10 and j <= nr_irqs: + prec += 1 + j *= 10 + chip_width = 8 + + gdb.write("%*s" % (prec + 8, "")) + for cpu in cpus.each_online_cpu(): + gdb.write("CPU%-8d" % cpu) + gdb.write("\n") + + 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, chip_width, irq)) + gdb.write(arch_show_interrupts(prec)) + + +LxInterruptList() diff --git a/scripts/gdb/linux/kasan.py b/scripts/gdb/linux/kasan.py new file mode 100644 index 000000000..56730b3fd --- /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/lists.py b/scripts/gdb/linux/lists.py new file mode 100644 index 000000000..bae4d70b7 --- /dev/null +++ b/scripts/gdb/linux/lists.py @@ -0,0 +1,136 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# list tools +# +# Copyright (c) Thiebaud Weksteen, 2015 +# +# Authors: +# Thiebaud Weksteen <thiebaud@weksteen.fr> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import gdb + +from linux import utils + +list_head = utils.CachedType("struct list_head") +hlist_head = utils.CachedType("struct hlist_head") +hlist_node = utils.CachedType("struct hlist_node") + + +def list_for_each(head): + if head.type == list_head.get_type().pointer(): + head = head.dereference() + elif head.type != list_head.get_type(): + raise TypeError("Must be struct list_head not {}" + .format(head.type)) + + if head['next'] == 0: + gdb.write("list_for_each: Uninitialized list '{}' treated as empty\n" + .format(head.address)) + return + + node = head['next'].dereference() + while node.address != head.address: + yield node.address + node = node['next'].dereference() + + +def list_for_each_entry(head, gdbtype, member): + for node in list_for_each(head): + yield utils.container_of(node, gdbtype, member) + + +def hlist_for_each(head): + if head.type == hlist_head.get_type().pointer(): + head = head.dereference() + elif head.type != hlist_head.get_type(): + raise TypeError("Must be struct hlist_head not {}" + .format(head.type)) + + node = head['first'].dereference() + while node.address: + yield node.address + node = node['next'].dereference() + + +def hlist_for_each_entry(head, gdbtype, member): + for node in hlist_for_each(head): + yield utils.container_of(node, gdbtype, member) + + +def list_check(head): + nb = 0 + if (head.type == list_head.get_type().pointer()): + head = head.dereference() + elif (head.type != list_head.get_type()): + raise gdb.GdbError('argument must be of type (struct list_head [*])') + c = head + try: + gdb.write("Starting with: {}\n".format(c)) + except gdb.MemoryError: + gdb.write('head is not accessible\n') + return + while True: + p = c['prev'].dereference() + n = c['next'].dereference() + try: + if p['next'] != c.address: + gdb.write('prev.next != current: ' + 'current@{current_addr}={current} ' + 'prev@{p_addr}={p}\n'.format( + current_addr=c.address, + current=c, + p_addr=p.address, + p=p, + )) + return + except gdb.MemoryError: + gdb.write('prev is not accessible: ' + 'current@{current_addr}={current}\n'.format( + current_addr=c.address, + current=c + )) + return + try: + if n['prev'] != c.address: + gdb.write('next.prev != current: ' + 'current@{current_addr}={current} ' + 'next@{n_addr}={n}\n'.format( + current_addr=c.address, + current=c, + n_addr=n.address, + n=n, + )) + return + except gdb.MemoryError: + gdb.write('next is not accessible: ' + 'current@{current_addr}={current}\n'.format( + current_addr=c.address, + current=c + )) + return + c = n + nb += 1 + if c == head: + gdb.write("list is consistent: {} node(s)\n".format(nb)) + return + + +class LxListChk(gdb.Command): + """Verify a list consistency""" + + def __init__(self): + super(LxListChk, self).__init__("lx-list-check", gdb.COMMAND_DATA, + gdb.COMPLETE_EXPRESSION) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + if len(argv) != 1: + raise gdb.GdbError("lx-list-check takes one argument") + list_check(gdb.parse_and_eval(argv[0])) + + +LxListChk() diff --git a/scripts/gdb/linux/mapletree.py b/scripts/gdb/linux/mapletree.py new file mode 100644 index 000000000..d52d51c0a --- /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 new file mode 100644 index 000000000..dffadccbb --- /dev/null +++ b/scripts/gdb/linux/mm.py @@ -0,0 +1,574 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2023 MediaTek Inc. +# +# Authors: +# Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com> +# + +import gdb +import math +from linux import utils, constants + +def DIV_ROUND_UP(n,d): + return ((n) + (d) - 1) // (d) + +def test_bit(nr, addr): + if addr.dereference() & (0x1 << nr): + return True + else: + return False + +class page_ops(): + ops = None + def __init__(self): + if not constants.LX_CONFIG_SPARSEMEM_VMEMMAP: + raise gdb.GdbError('Only support CONFIG_SPARSEMEM_VMEMMAP now') + if constants.LX_CONFIG_ARM64 and utils.is_target_arch('aarch64'): + self.ops = aarch64_page_ops() + elif utils.is_target_arch('x86_64') or utils.is_target_arch('x86-64'): + self.ops = x86_page_ops() + else: + raise gdb.GdbError('Only support aarch64 and x86_64 now') + +class x86_page_ops(): + def __init__(self): + self.struct_page_size = utils.get_page_type().sizeof + self.PAGE_SHIFT = constants.LX_CONFIG_PAGE_SHIFT + self.PAGE_SIZE = 1 << self.PAGE_SHIFT + self.PAGE_MASK = (~(self.PAGE_SIZE - 1)) & ((1 << 64) - 1) + + self.PAGE_OFFSET = int(gdb.parse_and_eval("page_offset_base")) + self.VMEMMAP_START = int(gdb.parse_and_eval("vmemmap_base")) + self.PHYS_BASE = int(gdb.parse_and_eval("(unsigned long) phys_base")) + self.START_KERNEL_map = 0xffffffff80000000 + + self.KERNEL_START = gdb.parse_and_eval("(unsigned long) &_text") + self.KERNEL_END = gdb.parse_and_eval("(unsigned long) &_end") + + self.VMALLOC_START = int(gdb.parse_and_eval("vmalloc_base")) + if self.VMALLOC_START == 0xffffc90000000000: + self.VMALLOC_END = self.VMALLOC_START + (32 * 1024 * 1024 * 1024 * 1024) - 1 + elif self.VMALLOC_START == 0xffa0000000000000: + self.VMALLOC_END = self.VMALLOC_START + (12800 * 1024 * 1024 * 1024 * 1024) - 1 + else: + self.VMALLOC_END = self.VMALLOC_START + (12800 * 1024 * 1024 * 1024 * 1024) - 1 + + self.MAX_PHYSMEM_BITS = 46 + self.SECTION_SIZE_BITS = 27 + self.MAX_ORDER = 10 + + self.SECTIONS_SHIFT = self.MAX_PHYSMEM_BITS - self.SECTION_SIZE_BITS + self.NR_MEM_SECTIONS = 1 << self.SECTIONS_SHIFT + self.PFN_SECTION_SHIFT = self.SECTION_SIZE_BITS - self.PAGE_SHIFT + self.PAGES_PER_SECTION = 1 << self.PFN_SECTION_SHIFT + self.PAGE_SECTION_MASK = (~(self.PAGES_PER_SECTION - 1)) & ((1 << 64) - 1) + + if constants.LX_CONFIG_SPARSEMEM_EXTREME: + self.SECTIONS_PER_ROOT = self.PAGE_SIZE // gdb.lookup_type("struct mem_section").sizeof + else: + self.SECTIONS_PER_ROOT = 1 + + self.NR_SECTION_ROOTS = DIV_ROUND_UP(self.NR_MEM_SECTIONS, self.SECTIONS_PER_ROOT) + self.SECTION_ROOT_MASK = self.SECTIONS_PER_ROOT - 1 + + try: + self.SECTION_HAS_MEM_MAP = 1 << int(gdb.parse_and_eval('SECTION_HAS_MEM_MAP_BIT')) + self.SECTION_IS_EARLY = 1 << int(gdb.parse_and_eval('SECTION_IS_EARLY_BIT')) + except: + self.SECTION_HAS_MEM_MAP = 1 << 0 + self.SECTION_IS_EARLY = 1 << 3 + + self.SUBSECTION_SHIFT = 21 + self.PAGES_PER_SUBSECTION = 1 << (self.SUBSECTION_SHIFT - self.PAGE_SHIFT) + + if constants.LX_CONFIG_NUMA and constants.LX_CONFIG_NODES_SHIFT: + self.NODE_SHIFT = constants.LX_CONFIG_NODES_SHIFT + else: + self.NODE_SHIFT = 0 + + self.MAX_NUMNODES = 1 << self.NODE_SHIFT + + self.vmemmap = gdb.Value(self.VMEMMAP_START).cast(utils.get_page_type().pointer()) + + def kasan_reset_tag(self, addr): + return addr + + def SECTION_NR_TO_ROOT(self, sec): + return sec // self.SECTIONS_PER_ROOT + + def __nr_to_section(self, nr): + root = self.SECTION_NR_TO_ROOT(nr) + mem_section = gdb.parse_and_eval("mem_section") + return mem_section[root][nr & self.SECTION_ROOT_MASK] + + def pfn_to_section_nr(self, pfn): + return pfn >> self.PFN_SECTION_SHIFT + + def section_nr_to_pfn(self, sec): + return sec << self.PFN_SECTION_SHIFT + + def __pfn_to_section(self, pfn): + return self.__nr_to_section(self.pfn_to_section_nr(pfn)) + + def pfn_to_section(self, pfn): + return self.__pfn_to_section(pfn) + + def subsection_map_index(self, pfn): + return (pfn & ~(self.PAGE_SECTION_MASK)) // self.PAGES_PER_SUBSECTION + + def pfn_section_valid(self, ms, pfn): + if constants.LX_CONFIG_SPARSEMEM_VMEMMAP: + idx = self.subsection_map_index(pfn) + return test_bit(idx, ms['usage']['subsection_map']) + else: + return True + + def valid_section(self, mem_section): + if mem_section != None and (mem_section['section_mem_map'] & self.SECTION_HAS_MEM_MAP): + return True + return False + + def early_section(self, mem_section): + if mem_section != None and (mem_section['section_mem_map'] & self.SECTION_IS_EARLY): + return True + return False + + def pfn_valid(self, pfn): + ms = None + if self.PHYS_PFN(self.PFN_PHYS(pfn)) != pfn: + return False + if self.pfn_to_section_nr(pfn) >= self.NR_MEM_SECTIONS: + return False + ms = self.__pfn_to_section(pfn) + + if not self.valid_section(ms): + return False + return self.early_section(ms) or self.pfn_section_valid(ms, pfn) + + def PFN_PHYS(self, pfn): + return pfn << self.PAGE_SHIFT + + def PHYS_PFN(self, phys): + return phys >> self.PAGE_SHIFT + + def __phys_to_virt(self, pa): + return pa + self.PAGE_OFFSET + + def __virt_to_phys(self, va): + if va >= self.START_KERNEL_map: + return va - self.START_KERNEL_map + self.PHYS_BASE + else: + return va - self.PAGE_OFFSET + + def virt_to_phys(self, va): + return self.__virt_to_phys(va) + + def virt_to_page(self, va): + return self.pfn_to_page(self.virt_to_pfn(va)) + + def __pa(self, va): + return self.__virt_to_phys(va) + + def __va(self, pa): + return self.__phys_to_virt(pa) + + def pfn_to_kaddr(self, pfn): + return self.__va(pfn << self.PAGE_SHIFT) + + def virt_to_pfn(self, va): + return self.PHYS_PFN(self.__virt_to_phys(va)) + + def sym_to_pfn(self, x): + return self.PHYS_PFN(self.__virt_to_phys(x)) + + def page_to_pfn(self, page): + return int(page.cast(utils.get_page_type().pointer()) - self.vmemmap) + + def pfn_to_page(self, pfn): + return self.vmemmap + pfn + + def page_to_phys(self, page): + return self.PFN_PHYS(self.page_to_pfn(page)) + + def page_to_virt(self, page): + return self.__va(self.page_to_phys(page)) + + def page_address(self, page): + return self.page_to_virt(page) + + def folio_address(self, folio): + return self.page_address(folio['page'].address) + +class aarch64_page_ops(): + def __init__(self): + self.SUBSECTION_SHIFT = 21 + self.SEBSECTION_SIZE = 1 << self.SUBSECTION_SHIFT + self.MODULES_VSIZE = 2 * 1024 * 1024 * 1024 + + if constants.LX_CONFIG_ARM64_64K_PAGES: + self.SECTION_SIZE_BITS = 29 + else: + self.SECTION_SIZE_BITS = 27 + self.MAX_PHYSMEM_BITS = constants.LX_CONFIG_ARM64_VA_BITS + + self.PAGE_SHIFT = constants.LX_CONFIG_PAGE_SHIFT + self.PAGE_SIZE = 1 << self.PAGE_SHIFT + self.PAGE_MASK = (~(self.PAGE_SIZE - 1)) & ((1 << 64) - 1) + + self.VA_BITS = constants.LX_CONFIG_ARM64_VA_BITS + if self.VA_BITS > 48: + 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 + self.kimage_voffset = gdb.parse_and_eval('kimage_voffset') & ((1 << 64) - 1) + + self.SECTIONS_SHIFT = self.MAX_PHYSMEM_BITS - self.SECTION_SIZE_BITS + + if str(constants.LX_CONFIG_ARCH_FORCE_MAX_ORDER).isdigit(): + self.MAX_ORDER = constants.LX_CONFIG_ARCH_FORCE_MAX_ORDER + else: + self.MAX_ORDER = 10 + + 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 + self.PAGE_SECTION_MASK = (~(self.PAGES_PER_SECTION - 1)) & ((1 << 64) - 1) + + if constants.LX_CONFIG_SPARSEMEM_EXTREME: + self.SECTIONS_PER_ROOT = self.PAGE_SIZE // gdb.lookup_type("struct mem_section").sizeof + else: + self.SECTIONS_PER_ROOT = 1 + + self.NR_SECTION_ROOTS = DIV_ROUND_UP(self.NR_MEM_SECTIONS, self.SECTIONS_PER_ROOT) + self.SECTION_ROOT_MASK = self.SECTIONS_PER_ROOT - 1 + self.SUBSECTION_SHIFT = 21 + self.SEBSECTION_SIZE = 1 << self.SUBSECTION_SHIFT + self.PFN_SUBSECTION_SHIFT = self.SUBSECTION_SHIFT - self.PAGE_SHIFT + self.PAGES_PER_SUBSECTION = 1 << self.PFN_SUBSECTION_SHIFT + + self.SECTION_HAS_MEM_MAP = 1 << int(gdb.parse_and_eval('SECTION_HAS_MEM_MAP_BIT')) + self.SECTION_IS_EARLY = 1 << int(gdb.parse_and_eval('SECTION_IS_EARLY_BIT')) + + self.struct_page_size = utils.get_page_type().sizeof + self.STRUCT_PAGE_MAX_SHIFT = (int)(math.log(self.struct_page_size, 2)) + + self.PAGE_OFFSET = self._PAGE_OFFSET(self.VA_BITS) + self.MODULES_VADDR = self._PAGE_END(self.VA_BITS_MIN) + self.MODULES_END = self.MODULES_VADDR + self.MODULES_VSIZE + + 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 + + self.memstart_addr = gdb.parse_and_eval("memstart_addr") + self.PHYS_OFFSET = self.memstart_addr + self.vmemmap = gdb.Value(self.VMEMMAP_START).cast(utils.get_page_type().pointer()) - (self.memstart_addr >> self.PAGE_SHIFT) + + self.KERNEL_START = gdb.parse_and_eval("_text") + self.KERNEL_END = gdb.parse_and_eval("_end") + + if constants.LX_CONFIG_KASAN_GENERIC or constants.LX_CONFIG_KASAN_SW_TAGS: + if constants.LX_CONFIG_KASAN_GENERIC: + self.KASAN_SHADOW_SCALE_SHIFT = 3 + else: + self.KASAN_SHADOW_SCALE_SHIFT = 4 + self.KASAN_SHADOW_OFFSET = constants.LX_CONFIG_KASAN_SHADOW_OFFSET + self.KASAN_SHADOW_END = (1 << (64 - self.KASAN_SHADOW_SCALE_SHIFT)) + self.KASAN_SHADOW_OFFSET + self.PAGE_END = self.KASAN_SHADOW_END - (1 << (self.vabits_actual - self.KASAN_SHADOW_SCALE_SHIFT)) + else: + self.PAGE_END = self._PAGE_END(self.VA_BITS_MIN) + + if constants.LX_CONFIG_NUMA and constants.LX_CONFIG_NODES_SHIFT: + self.NODE_SHIFT = constants.LX_CONFIG_NODES_SHIFT + else: + self.NODE_SHIFT = 0 + + self.MAX_NUMNODES = 1 << self.NODE_SHIFT + + def SECTION_NR_TO_ROOT(self, sec): + return sec // self.SECTIONS_PER_ROOT + + def __nr_to_section(self, nr): + root = self.SECTION_NR_TO_ROOT(nr) + mem_section = gdb.parse_and_eval("mem_section") + return mem_section[root][nr & self.SECTION_ROOT_MASK] + + def pfn_to_section_nr(self, pfn): + return pfn >> self.PFN_SECTION_SHIFT + + def section_nr_to_pfn(self, sec): + return sec << self.PFN_SECTION_SHIFT + + def __pfn_to_section(self, pfn): + return self.__nr_to_section(self.pfn_to_section_nr(pfn)) + + def pfn_to_section(self, pfn): + return self.__pfn_to_section(pfn) + + def subsection_map_index(self, pfn): + return (pfn & ~(self.PAGE_SECTION_MASK)) // self.PAGES_PER_SUBSECTION + + def pfn_section_valid(self, ms, pfn): + if constants.LX_CONFIG_SPARSEMEM_VMEMMAP: + idx = self.subsection_map_index(pfn) + return test_bit(idx, ms['usage']['subsection_map']) + else: + return True + + def valid_section(self, mem_section): + if mem_section != None and (mem_section['section_mem_map'] & self.SECTION_HAS_MEM_MAP): + return True + return False + + def early_section(self, mem_section): + if mem_section != None and (mem_section['section_mem_map'] & self.SECTION_IS_EARLY): + return True + return False + + def pfn_valid(self, pfn): + ms = None + if self.PHYS_PFN(self.PFN_PHYS(pfn)) != pfn: + return False + if self.pfn_to_section_nr(pfn) >= self.NR_MEM_SECTIONS: + return False + ms = self.__pfn_to_section(pfn) + + if not self.valid_section(ms): + return False + return self.early_section(ms) or self.pfn_section_valid(ms, pfn) + + def _PAGE_OFFSET(self, va): + return (-(1 << (va))) & 0xffffffffffffffff + + def _PAGE_END(self, va): + return (-(1 << (va - 1))) & 0xffffffffffffffff + + def kasan_reset_tag(self, addr): + if constants.LX_CONFIG_KASAN_SW_TAGS or constants.LX_CONFIG_KASAN_HW_TAGS: + return int(addr) | (0xff << 56) + else: + return addr + + def __is_lm_address(self, addr): + if (addr - self.PAGE_OFFSET) < (self.PAGE_END - self.PAGE_OFFSET): + return True + else: + return False + def __lm_to_phys(self, addr): + return addr - self.PAGE_OFFSET + self.PHYS_OFFSET + + def __kimg_to_phys(self, addr): + return addr - self.kimage_voffset + + def __virt_to_phys_nodebug(self, va): + untagged_va = self.kasan_reset_tag(va) + if self.__is_lm_address(untagged_va): + return self.__lm_to_phys(untagged_va) + else: + return self.__kimg_to_phys(untagged_va) + + def __virt_to_phys(self, va): + if constants.LX_CONFIG_DEBUG_VIRTUAL: + if not self.__is_lm_address(self.kasan_reset_tag(va)): + raise gdb.GdbError("Warning: virt_to_phys used for non-linear address: 0x%lx\n" % va) + return self.__virt_to_phys_nodebug(va) + + def virt_to_phys(self, va): + return self.__virt_to_phys(va) + + def PFN_PHYS(self, pfn): + return pfn << self.PAGE_SHIFT + + def PHYS_PFN(self, phys): + return phys >> self.PAGE_SHIFT + + def __phys_to_virt(self, pa): + return (pa - self.PHYS_OFFSET) | self.PAGE_OFFSET + + def __phys_to_pfn(self, pa): + return self.PHYS_PFN(pa) + + def __pfn_to_phys(self, pfn): + return self.PFN_PHYS(pfn) + + def __pa_symbol_nodebug(self, x): + return self.__kimg_to_phys(x) + + def __phys_addr_symbol(self, x): + if constants.LX_CONFIG_DEBUG_VIRTUAL: + if x < self.KERNEL_START or x > self.KERNEL_END: + raise gdb.GdbError("0x%x exceed kernel range" % x) + return self.__pa_symbol_nodebug(x) + + def __pa_symbol(self, x): + return self.__phys_addr_symbol(x) + + def __va(self, pa): + return self.__phys_to_virt(pa) + + def pfn_to_kaddr(self, pfn): + return self.__va(pfn << self.PAGE_SHIFT) + + def virt_to_pfn(self, va): + return self.__phys_to_pfn(self.__virt_to_phys(va)) + + def sym_to_pfn(self, x): + return self.__phys_to_pfn(self.__pa_symbol(x)) + + def page_to_pfn(self, page): + return int(page.cast(utils.get_page_type().pointer()) - self.vmemmap.cast(utils.get_page_type().pointer())) + + def page_to_phys(self, page): + return self.__pfn_to_phys(self.page_to_pfn(page)) + + def pfn_to_page(self, pfn): + return (self.vmemmap + pfn).cast(utils.get_page_type().pointer()) + + def page_to_virt(self, page): + if constants.LX_CONFIG_DEBUG_VIRTUAL: + return self.__va(self.page_to_phys(page)) + else: + __idx = int((page.cast(gdb.lookup_type("unsigned long")) - self.VMEMMAP_START).cast(utils.get_ulong_type())) // self.struct_page_size + return self.PAGE_OFFSET + (__idx * self.PAGE_SIZE) + + def virt_to_page(self, va): + if constants.LX_CONFIG_DEBUG_VIRTUAL: + return self.pfn_to_page(self.virt_to_pfn(va)) + else: + __idx = int(self.kasan_reset_tag(va) - self.PAGE_OFFSET) // self.PAGE_SIZE + addr = self.VMEMMAP_START + (__idx * self.struct_page_size) + return gdb.Value(addr).cast(utils.get_page_type().pointer()) + + def page_address(self, page): + return self.page_to_virt(page) + + def folio_address(self, folio): + return self.page_address(folio['page'].address) + +class LxPFN2Page(gdb.Command): + """PFN to struct page""" + + def __init__(self): + super(LxPFN2Page, self).__init__("lx-pfn_to_page", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + pfn = int(argv[0]) + page = page_ops().ops.pfn_to_page(pfn) + gdb.write("pfn_to_page(0x%x) = 0x%x\n" % (pfn, page)) + +LxPFN2Page() + +class LxPage2PFN(gdb.Command): + """struct page to PFN""" + + def __init__(self): + super(LxPage2PFN, self).__init__("lx-page_to_pfn", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + struct_page_addr = int(argv[0], 16) + page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer()) + pfn = page_ops().ops.page_to_pfn(page) + gdb.write("page_to_pfn(0x%x) = 0x%x\n" % (page, pfn)) + +LxPage2PFN() + +class LxPageAddress(gdb.Command): + """struct page to linear mapping address""" + + def __init__(self): + super(LxPageAddress, self).__init__("lx-page_address", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + struct_page_addr = int(argv[0], 16) + page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer()) + addr = page_ops().ops.page_address(page) + gdb.write("page_address(0x%x) = 0x%x\n" % (page, addr)) + +LxPageAddress() + +class LxPage2Phys(gdb.Command): + """struct page to physical address""" + + def __init__(self): + super(LxPage2Phys, self).__init__("lx-page_to_phys", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + struct_page_addr = int(argv[0], 16) + page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer()) + phys_addr = page_ops().ops.page_to_phys(page) + gdb.write("page_to_phys(0x%x) = 0x%x\n" % (page, phys_addr)) + +LxPage2Phys() + +class LxVirt2Phys(gdb.Command): + """virtual address to physical address""" + + def __init__(self): + super(LxVirt2Phys, self).__init__("lx-virt_to_phys", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + linear_addr = int(argv[0], 16) + phys_addr = page_ops().ops.virt_to_phys(linear_addr) + gdb.write("virt_to_phys(0x%x) = 0x%x\n" % (linear_addr, phys_addr)) + +LxVirt2Phys() + +class LxVirt2Page(gdb.Command): + """virtual address to struct page""" + + def __init__(self): + super(LxVirt2Page, self).__init__("lx-virt_to_page", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + linear_addr = int(argv[0], 16) + page = page_ops().ops.virt_to_page(linear_addr) + gdb.write("virt_to_page(0x%x) = 0x%x\n" % (linear_addr, page)) + +LxVirt2Page() + +class LxSym2PFN(gdb.Command): + """symbol address to PFN""" + + def __init__(self): + super(LxSym2PFN, self).__init__("lx-sym_to_pfn", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + sym_addr = int(argv[0], 16) + pfn = page_ops().ops.sym_to_pfn(sym_addr) + gdb.write("sym_to_pfn(0x%x) = %d\n" % (sym_addr, pfn)) + +LxSym2PFN() + +class LxPFN2Kaddr(gdb.Command): + """PFN to kernel address""" + + def __init__(self): + super(LxPFN2Kaddr, self).__init__("lx-pfn_to_kaddr", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + pfn = int(argv[0]) + kaddr = page_ops().ops.pfn_to_kaddr(pfn) + gdb.write("pfn_to_kaddr(%d) = 0x%x\n" % (pfn, kaddr)) + +LxPFN2Kaddr() diff --git a/scripts/gdb/linux/modules.py b/scripts/gdb/linux/modules.py new file mode 100644 index 000000000..fa15f872d --- /dev/null +++ b/scripts/gdb/linux/modules.py @@ -0,0 +1,134 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# module tools +# +# Copyright (c) Siemens AG, 2013 +# +# Authors: +# Jan Kiszka <jan.kiszka@siemens.com> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import gdb + +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") + if modules is None: + return + + module_ptr_type = module_type.get_type().pointer() + + for module in lists.list_for_each_entry(modules, module_ptr_type, "list"): + yield module + + +def find_module_by_name(name): + for module in module_list(): + if module['name'].string() == name: + return module + return None + + +class LxModule(gdb.Function): + """Find module by name and return the module variable. + +$lx_module("MODULE"): Given the name MODULE, iterate over all loaded modules +of the target and return that module variable which MODULE matches.""" + + def __init__(self): + super(LxModule, self).__init__("lx_module") + + def invoke(self, mod_name): + mod_name = mod_name.string() + module = find_module_by_name(mod_name) + if module: + return module.dereference() + else: + raise gdb.GdbError("Unable to find MODULE " + mod_name) + + +LxModule() + + +class LxLsmod(gdb.Command): + """List currently loaded modules.""" + + _module_use_type = utils.CachedType("struct module_use") + + def __init__(self): + super(LxLsmod, self).__init__("lx-lsmod", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + gdb.write( + "Address{0} Module Size Used by\n".format( + " " if utils.get_long_type().sizeof == 8 else "")) + + for module in module_list(): + text = module['mem'][constants.LX_MOD_TEXT] + text_addr = str(text['base']).split()[0] + total_size = 0 + + for i in range(constants.LX_MOD_TEXT, constants.LX_MOD_RO_AFTER_INIT + 1): + total_size += module['mem'][i]['size'] + + gdb.write("{address} {name:<19} {size:>8} {ref}".format( + address=text_addr, + name=module['name'].string(), + size=str(total_size), + ref=str(module['refcnt']['counter'] - 1))) + + t = self._module_use_type.get_type().pointer() + first = True + sources = module['source_list'] + for use in lists.list_for_each_entry(sources, t, "source_list"): + gdb.write("{separator}{name}".format( + separator=" " if first else ",", + name=use['source']['name'].string())) + first = False + + gdb.write("\n") + +LxLsmod() + +def help(): + t = """Usage: lx-getmod-by-textaddr [Heximal Address] + Example: lx-getmod-by-textaddr 0xffff800002d305ac\n""" + gdb.write("Unrecognized command\n") + raise gdb.GdbError(t) + +class LxFindTextAddrinMod(gdb.Command): + '''Look up loaded kernel module by text address.''' + + def __init__(self): + super(LxFindTextAddrinMod, self).__init__('lx-getmod-by-textaddr', gdb.COMMAND_SUPPORT) + + def invoke(self, arg, from_tty): + args = gdb.string_to_argv(arg) + + if len(args) != 1: + help() + + addr = gdb.Value(int(args[0], 16)).cast(utils.get_ulong_type()) + for mod in module_list(): + mod_text_start = mod['mem'][constants.LX_MOD_TEXT]['base'] + mod_text_end = mod_text_start + mod['mem'][constants.LX_MOD_TEXT]['size'].cast(utils.get_ulong_type()) + + if addr >= mod_text_start and addr < mod_text_end: + s = "0x%x" % addr + " is in " + mod['name'].string() + ".ko\n" + gdb.write(s) + return + gdb.write("0x%x is not in any module text section\n" % addr) + +LxFindTextAddrinMod() diff --git a/scripts/gdb/linux/page_owner.py b/scripts/gdb/linux/page_owner.py new file mode 100644 index 000000000..eeabaeed4 --- /dev/null +++ b/scripts/gdb/linux/page_owner.py @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2023 MediaTek Inc. +# +# Authors: +# Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com> +# + +import gdb +from linux import utils, stackdepot, constants, mm + +if constants.LX_CONFIG_PAGE_OWNER: + page_ext_t = utils.CachedType('struct page_ext') + page_owner_t = utils.CachedType('struct page_owner') + + PAGE_OWNER_STACK_DEPTH = 16 + PAGE_EXT_OWNER = constants.LX_PAGE_EXT_OWNER + PAGE_EXT_INVALID = 0x1 + PAGE_EXT_OWNER_ALLOCATED = constants.LX_PAGE_EXT_OWNER_ALLOCATED + +def help(): + t = """Usage: lx-dump-page-owner [Option] + Option: + --pfn [Decimal pfn] + Example: + lx-dump-page-owner --pfn 655360\n""" + gdb.write("Unrecognized command\n") + raise gdb.GdbError(t) + +class DumpPageOwner(gdb.Command): + """Dump page owner""" + + min_pfn = None + max_pfn = None + p_ops = None + migrate_reason_names = None + mr_never = None + + def __init__(self): + super(DumpPageOwner, self).__init__("lx-dump-page-owner", gdb.COMMAND_SUPPORT) + + def invoke(self, args, from_tty): + if not constants.LX_CONFIG_PAGE_OWNER: + raise gdb.GdbError('CONFIG_PAGE_OWNER does not enable') + + page_owner_inited = gdb.parse_and_eval('page_owner_inited') + if page_owner_inited['key']['enabled']['counter'] != 0x1: + raise gdb.GdbError('page_owner_inited is not enabled') + + self.p_ops = mm.page_ops().ops + self.get_page_owner_info() + argv = gdb.string_to_argv(args) + if len(argv) == 0: + self.read_page_owner() + elif len(argv) == 2: + if argv[0] == "--pfn": + pfn = int(argv[1]) + self.read_page_owner_by_addr(self.p_ops.pfn_to_page(pfn)) + else: + help() + else: + help() + + def get_page_owner_info(self): + self.min_pfn = int(gdb.parse_and_eval("min_low_pfn")) + self.max_pfn = int(gdb.parse_and_eval("max_pfn")) + self.page_ext_size = int(gdb.parse_and_eval("page_ext_size")) + self.migrate_reason_names = gdb.parse_and_eval('migrate_reason_names') + self.mr_never = int(gdb.parse_and_eval('MR_NEVER')) + + def page_ext_invalid(self, page_ext): + if page_ext == gdb.Value(0): + return True + if page_ext.cast(utils.get_ulong_type()) & PAGE_EXT_INVALID == PAGE_EXT_INVALID: + return True + return False + + def get_entry(self, base, index): + return (base.cast(utils.get_ulong_type()) + self.page_ext_size * index).cast(page_ext_t.get_type().pointer()) + + def lookup_page_ext(self, page): + pfn = self.p_ops.page_to_pfn(page) + section = self.p_ops.pfn_to_section(pfn) + page_ext = section["page_ext"] + if self.page_ext_invalid(page_ext): + return gdb.Value(0) + return self.get_entry(page_ext, pfn) + + def page_ext_get(self, page): + page_ext = self.lookup_page_ext(page) + if page_ext != gdb.Value(0): + return page_ext + else: + return gdb.Value(0) + + def get_page_owner(self, page_ext): + addr = page_ext.cast(utils.get_ulong_type()) + gdb.parse_and_eval("page_owner_ops")["offset"].cast(utils.get_ulong_type()) + return addr.cast(page_owner_t.get_type().pointer()) + + def read_page_owner_by_addr(self, struct_page_addr): + page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer()) + pfn = self.p_ops.page_to_pfn(page) + + if pfn < self.min_pfn or pfn > self.max_pfn or (not self.p_ops.pfn_valid(pfn)): + gdb.write("pfn is invalid\n") + return + + page = self.p_ops.pfn_to_page(pfn) + page_ext = self.page_ext_get(page) + + if page_ext == gdb.Value(0): + gdb.write("page_ext is null\n") + return + + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)): + gdb.write("page_owner flag is invalid\n") + raise gdb.GdbError('page_owner info is not present (never set?)\n') + + if mm.test_bit(PAGE_EXT_OWNER_ALLOCATED, page_ext['flags'].address): + gdb.write('page_owner tracks the page as allocated\n') + else: + gdb.write('page_owner tracks the page as freed\n') + + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)): + gdb.write("page_owner is not allocated\n") + + page_owner = self.get_page_owner(page_ext) + gdb.write("Page last allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\ + (page_owner["order"], page_owner["gfp_mask"],\ + page_owner["pid"], page_owner["tgid"], page_owner["comm"].string(),\ + page_owner["ts_nsec"], page_owner["free_ts_nsec"])) + gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags'])) + if page_owner["handle"] == 0: + gdb.write('page_owner allocation stack trace missing\n') + else: + stackdepot.stack_depot_print(page_owner["handle"]) + + if page_owner["free_handle"] == 0: + gdb.write('page_owner free stack trace missing\n') + else: + gdb.write('page last free stack trace:\n') + stackdepot.stack_depot_print(page_owner["free_handle"]) + if page_owner['last_migrate_reason'] != self.mr_never: + gdb.write('page has been migrated, last migrate reason: %s\n' % self.migrate_reason_names[page_owner['last_migrate_reason']]) + + def read_page_owner(self): + pfn = self.min_pfn + + # Find a valid PFN or the start of a MAX_ORDER_NR_PAGES area + while ((not self.p_ops.pfn_valid(pfn)) and (pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1))) != 0: + pfn += 1 + + while pfn < self.max_pfn: + # + # If the new page is in a new MAX_ORDER_NR_PAGES area, + # validate the area as existing, skip it if not + # + if ((pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1)) == 0) and (not self.p_ops.pfn_valid(pfn)): + pfn += (self.p_ops.MAX_ORDER_NR_PAGES - 1) + continue; + + page = self.p_ops.pfn_to_page(pfn) + page_ext = self.page_ext_get(page) + if page_ext == gdb.Value(0): + pfn += 1 + continue + + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)): + pfn += 1 + continue + if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)): + pfn += 1 + continue + + page_owner = self.get_page_owner(page_ext) + gdb.write("Page allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\ + (page_owner["order"], page_owner["gfp_mask"],\ + page_owner["pid"], page_owner["tgid"], page_owner["comm"].string(),\ + page_owner["ts_nsec"], page_owner["free_ts_nsec"])) + gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags'])) + stackdepot.stack_depot_print(page_owner["handle"]) + pfn += (1 << page_owner["order"]) + +DumpPageOwner() diff --git a/scripts/gdb/linux/pgtable.py b/scripts/gdb/linux/pgtable.py new file mode 100644 index 000000000..09aac2421 --- /dev/null +++ b/scripts/gdb/linux/pgtable.py @@ -0,0 +1,220 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# gdb helper commands and functions for Linux kernel debugging +# +# routines to introspect page table +# +# Authors: +# Dmitrii Bundin <dmitrii.bundin.a@gmail.com> +# + +import gdb + +from linux import utils + +PHYSICAL_ADDRESS_MASK = gdb.parse_and_eval('0xfffffffffffff') + + +def page_mask(level=1): + # 4KB + if level == 1: + return gdb.parse_and_eval('(u64) ~0xfff') + # 2MB + elif level == 2: + return gdb.parse_and_eval('(u64) ~0x1fffff') + # 1GB + elif level == 3: + return gdb.parse_and_eval('(u64) ~0x3fffffff') + else: + raise Exception(f'Unknown page level: {level}') + + +def _page_offset_base(): + pob_symbol = gdb.lookup_global_symbol('page_offset_base') + pob = pob_symbol.name + return gdb.parse_and_eval(pob) + + +def is_bit_defined_tupled(data, offset): + return offset, bool(data >> offset & 1) + +def content_tupled(data, bit_start, bit_end): + return (bit_start, bit_end), data >> bit_start & ((1 << (1 + bit_end - bit_start)) - 1) + +def entry_va(level, phys_addr, translating_va): + def start_bit(level): + if level == 5: + return 48 + elif level == 4: + return 39 + elif level == 3: + return 30 + elif level == 2: + return 21 + elif level == 1: + return 12 + else: + raise Exception(f'Unknown level {level}') + + entry_offset = ((translating_va >> start_bit(level)) & 511) * 8 + entry_va = _page_offset_base() + phys_addr + entry_offset + return entry_va + +class Cr3(): + def __init__(self, cr3, page_levels): + self.cr3 = cr3 + self.page_levels = page_levels + self.page_level_write_through = is_bit_defined_tupled(cr3, 3) + self.page_level_cache_disabled = is_bit_defined_tupled(cr3, 4) + self.next_entry_physical_address = cr3 & PHYSICAL_ADDRESS_MASK & page_mask() + + def next_entry(self, va): + next_level = self.page_levels + return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level) + + def mk_string(self): + return f"""\ +cr3: + {'cr3 binary data': <30} {hex(self.cr3)} + {'next entry physical address': <30} {hex(self.next_entry_physical_address)} + --- + {'bit' : <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]} + {'bit' : <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]} +""" + + +class PageHierarchyEntry(): + def __init__(self, address, level): + data = int.from_bytes( + memoryview(gdb.selected_inferior().read_memory(address, 8)), + "little" + ) + if level == 1: + self.is_page = True + self.entry_present = is_bit_defined_tupled(data, 0) + self.read_write = is_bit_defined_tupled(data, 1) + self.user_access_allowed = is_bit_defined_tupled(data, 2) + self.page_level_write_through = is_bit_defined_tupled(data, 3) + self.page_level_cache_disabled = is_bit_defined_tupled(data, 4) + self.entry_was_accessed = is_bit_defined_tupled(data, 5) + self.dirty = is_bit_defined_tupled(data, 6) + self.pat = is_bit_defined_tupled(data, 7) + self.global_translation = is_bit_defined_tupled(data, 8) + self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level) + self.next_entry_physical_address = None + self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11) + self.protection_key = content_tupled(data, 59, 62) + self.executed_disable = is_bit_defined_tupled(data, 63) + else: + page_size = is_bit_defined_tupled(data, 7) + page_size_bit = page_size[1] + self.is_page = page_size_bit + self.entry_present = is_bit_defined_tupled(data, 0) + self.read_write = is_bit_defined_tupled(data, 1) + self.user_access_allowed = is_bit_defined_tupled(data, 2) + self.page_level_write_through = is_bit_defined_tupled(data, 3) + self.page_level_cache_disabled = is_bit_defined_tupled(data, 4) + self.entry_was_accessed = is_bit_defined_tupled(data, 5) + self.page_size = page_size + self.dirty = is_bit_defined_tupled( + data, 6) if page_size_bit else None + self.global_translation = is_bit_defined_tupled( + data, 8) if page_size_bit else None + self.pat = is_bit_defined_tupled( + data, 12) if page_size_bit else None + self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level) if page_size_bit else None + self.next_entry_physical_address = None if page_size_bit else data & PHYSICAL_ADDRESS_MASK & page_mask() + self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11) + self.protection_key = content_tupled(data, 59, 62) if page_size_bit else None + self.executed_disable = is_bit_defined_tupled(data, 63) + self.address = address + self.page_entry_binary_data = data + self.page_hierarchy_level = level + + def next_entry(self, va): + if self.is_page or not self.entry_present[1]: + return None + + next_level = self.page_hierarchy_level - 1 + return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level) + + + def mk_string(self): + if not self.entry_present[1]: + return f"""\ +level {self.page_hierarchy_level}: + {'entry address': <30} {hex(self.address)} + {'page entry binary data': <30} {hex(self.page_entry_binary_data)} + --- + PAGE ENTRY IS NOT PRESENT! +""" + elif self.is_page: + def page_size_line(ps_bit, ps, level): + return "" if level == 1 else f"{'bit': <3} {ps_bit: <5} {'page size': <30} {ps}" + + return f"""\ +level {self.page_hierarchy_level}: + {'entry address': <30} {hex(self.address)} + {'page entry binary data': <30} {hex(self.page_entry_binary_data)} + {'page size': <30} {'1GB' if self.page_hierarchy_level == 3 else '2MB' if self.page_hierarchy_level == 2 else '4KB' if self.page_hierarchy_level == 1 else 'Unknown page size for level:' + self.page_hierarchy_level} + {'page physical address': <30} {hex(self.page_physical_address)} + --- + {'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]} + {'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]} + {'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]} + {'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]} + {'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]} + {'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]} + {"" if self.page_hierarchy_level == 1 else f"{'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]}"} + {'bit': <4} {self.dirty[0]: <10} {'page dirty': <30} {self.dirty[1]} + {'bit': <4} {self.global_translation[0]: <10} {'global translation': <30} {self.global_translation[1]} + {'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]} + {'bit': <4} {self.pat[0]: <10} {'pat': <30} {self.pat[1]} + {'bits': <4} {str(self.protection_key[0]): <10} {'protection key': <30} {self.protection_key[1]} + {'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]} +""" + else: + return f"""\ +level {self.page_hierarchy_level}: + {'entry address': <30} {hex(self.address)} + {'page entry binary data': <30} {hex(self.page_entry_binary_data)} + {'next entry physical address': <30} {hex(self.next_entry_physical_address)} + --- + {'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]} + {'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]} + {'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]} + {'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]} + {'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]} + {'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]} + {'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]} + {'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]} + {'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]} +""" + + +class TranslateVM(gdb.Command): + """Prints the entire paging structure used to translate a given virtual address. + +Having an address space of the currently executed process translates the virtual address +and prints detailed information of all paging structure levels used for the transaltion. +Currently supported arch: x86""" + + def __init__(self): + super(TranslateVM, self).__init__('translate-vm', gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + if utils.is_target_arch("x86"): + vm_address = gdb.parse_and_eval(f'{arg}') + cr3_data = gdb.parse_and_eval('$cr3') + cr4 = gdb.parse_and_eval('$cr4') + page_levels = 5 if cr4 & (1 << 12) else 4 + page_entry = Cr3(cr3_data, page_levels) + while page_entry: + gdb.write(page_entry.mk_string()) + page_entry = page_entry.next_entry(vm_address) + else: + gdb.GdbError("Virtual address translation is not" + "supported for this arch") + + +TranslateVM() diff --git a/scripts/gdb/linux/proc.py b/scripts/gdb/linux/proc.py new file mode 100644 index 000000000..65dd1bd12 --- /dev/null +++ b/scripts/gdb/linux/proc.py @@ -0,0 +1,277 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# gdb helper commands and functions for Linux kernel debugging +# +# Kernel proc information reader +# +# Copyright (c) 2016 Linaro Ltd +# +# Authors: +# Kieran Bingham <kieran.bingham@linaro.org> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import gdb +from linux import constants +from linux import utils +from linux import tasks +from linux import lists +from linux import vfs +from linux import rbtree +from struct import * + + +class LxCmdLine(gdb.Command): + """ Report the Linux Commandline used in the current kernel. + Equivalent to cat /proc/cmdline on a running target""" + + def __init__(self): + super(LxCmdLine, self).__init__("lx-cmdline", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + gdb.write(gdb.parse_and_eval("saved_command_line").string() + "\n") + + +LxCmdLine() + + +class LxVersion(gdb.Command): + """ Report the Linux Version of the current kernel. + Equivalent to cat /proc/version on a running target""" + + def __init__(self): + super(LxVersion, self).__init__("lx-version", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + # linux_banner should contain a newline + gdb.write(gdb.parse_and_eval("(char *)linux_banner").string()) + + +LxVersion() + + +# Resource Structure Printers +# /proc/iomem +# /proc/ioports + +def get_resources(resource, depth): + while resource: + yield resource, depth + + child = resource['child'] + if child: + for res, deep in get_resources(child, depth + 1): + yield res, deep + + resource = resource['sibling'] + + +def show_lx_resources(resource_str): + resource = gdb.parse_and_eval(resource_str) + width = 4 if resource['end'] < 0x10000 else 8 + # Iterate straight to the first child + for res, depth in get_resources(resource['child'], 0): + start = int(res['start']) + end = int(res['end']) + gdb.write(" " * depth * 2 + + "{0:0{1}x}-".format(start, width) + + "{0:0{1}x} : ".format(end, width) + + res['name'].string() + "\n") + + +class LxIOMem(gdb.Command): + """Identify the IO memory resource locations defined by the kernel + +Equivalent to cat /proc/iomem on a running target""" + + def __init__(self): + super(LxIOMem, self).__init__("lx-iomem", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + return show_lx_resources("iomem_resource") + + +LxIOMem() + + +class LxIOPorts(gdb.Command): + """Identify the IO port resource locations defined by the kernel + +Equivalent to cat /proc/ioports on a running target""" + + def __init__(self): + super(LxIOPorts, self).__init__("lx-ioports", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + return show_lx_resources("ioport_resource") + + +LxIOPorts() + + +# Mount namespace viewer +# /proc/mounts + +def info_opts(lst, opt): + opts = "" + for key, string in lst.items(): + if opt & key: + opts += string + return opts + + +FS_INFO = {constants.LX_SB_SYNCHRONOUS: ",sync", + constants.LX_SB_MANDLOCK: ",mand", + constants.LX_SB_DIRSYNC: ",dirsync", + constants.LX_SB_NOATIME: ",noatime", + constants.LX_SB_NODIRATIME: ",nodiratime"} + +MNT_INFO = {constants.LX_MNT_NOSUID: ",nosuid", + constants.LX_MNT_NODEV: ",nodev", + constants.LX_MNT_NOEXEC: ",noexec", + constants.LX_MNT_NOATIME: ",noatime", + constants.LX_MNT_NODIRATIME: ",nodiratime", + constants.LX_MNT_RELATIME: ",relatime"} + +mount_type = utils.CachedType("struct mount") +mount_ptr_type = mount_type.get_type().pointer() + + +class LxMounts(gdb.Command): + """Report the VFS mounts of the current process namespace. + +Equivalent to cat /proc/mounts on a running target +An integer value can be supplied to display the mount +values of that process namespace""" + + def __init__(self): + super(LxMounts, self).__init__("lx-mounts", gdb.COMMAND_DATA) + + # Equivalent to proc_namespace.c:show_vfsmnt + # However, that has the ability to call into s_op functions + # whereas we cannot and must make do with the information we can obtain. + def invoke(self, arg, from_tty): + argv = gdb.string_to_argv(arg) + if len(argv) >= 1: + try: + pid = int(argv[0]) + except gdb.error: + raise gdb.GdbError("Provide a PID as integer value") + else: + pid = 1 + + task = tasks.get_task_by_pid(pid) + if not task: + raise gdb.GdbError("Couldn't find a process with PID {}" + .format(pid)) + + namespace = task['nsproxy']['mnt_ns'] + if not namespace: + raise gdb.GdbError("No namespace for current process") + + gdb.write("{:^18} {:^15} {:>9} {} {} options\n".format( + "mount", "super_block", "devname", "pathname", "fstype")) + + 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" + + pathname = "" + parent = mnt + while True: + mntpoint = parent['mnt_mountpoint'] + pathname = vfs.dentry_name(mntpoint) + pathname + if (parent == parent['mnt_parent']): + break + parent = parent['mnt_parent'] + + if (pathname == ""): + pathname = "/" + + superblock = mnt['mnt']['mnt_sb'] + fstype = superblock['s_type']['name'].string() + s_flags = int(superblock['s_flags']) + m_flags = int(mnt['mnt']['mnt_flags']) + rd = "ro" if (s_flags & constants.LX_SB_RDONLY) else "rw" + + gdb.write("{} {} {} {} {} {}{}{} 0 0\n".format( + mnt.format_string(), superblock.format_string(), devname, + pathname, fstype, rd, info_opts(FS_INFO, s_flags), + info_opts(MNT_INFO, m_flags))) + + +LxMounts() + + +class LxFdtDump(gdb.Command): + """Output Flattened Device Tree header and dump FDT blob to the filename + specified as the command argument. Equivalent to + 'cat /proc/fdt > fdtdump.dtb' on a running target""" + + def __init__(self): + super(LxFdtDump, self).__init__("lx-fdtdump", gdb.COMMAND_DATA, + gdb.COMPLETE_FILENAME) + + def fdthdr_to_cpu(self, fdt_header): + + fdt_header_be = ">IIIIIII" + fdt_header_le = "<IIIIIII" + + if utils.get_target_endianness() == 1: + output_fmt = fdt_header_le + else: + output_fmt = fdt_header_be + + return unpack(output_fmt, pack(fdt_header_be, + fdt_header['magic'], + fdt_header['totalsize'], + fdt_header['off_dt_struct'], + fdt_header['off_dt_strings'], + fdt_header['off_mem_rsvmap'], + fdt_header['version'], + fdt_header['last_comp_version'])) + + def invoke(self, arg, from_tty): + + if not constants.LX_CONFIG_OF: + raise gdb.GdbError("Kernel not compiled with CONFIG_OF\n") + + if len(arg) == 0: + filename = "fdtdump.dtb" + else: + filename = arg + + py_fdt_header_ptr = gdb.parse_and_eval( + "(const struct fdt_header *) initial_boot_params") + py_fdt_header = py_fdt_header_ptr.dereference() + + fdt_header = self.fdthdr_to_cpu(py_fdt_header) + + if fdt_header[0] != constants.LX_OF_DT_HEADER: + raise gdb.GdbError("No flattened device tree magic found\n") + + gdb.write("fdt_magic: 0x{:02X}\n".format(fdt_header[0])) + gdb.write("fdt_totalsize: 0x{:02X}\n".format(fdt_header[1])) + gdb.write("off_dt_struct: 0x{:02X}\n".format(fdt_header[2])) + gdb.write("off_dt_strings: 0x{:02X}\n".format(fdt_header[3])) + gdb.write("off_mem_rsvmap: 0x{:02X}\n".format(fdt_header[4])) + gdb.write("version: {}\n".format(fdt_header[5])) + gdb.write("last_comp_version: {}\n".format(fdt_header[6])) + + inf = gdb.inferiors()[0] + fdt_buf = utils.read_memoryview(inf, py_fdt_header_ptr, + fdt_header[1]).tobytes() + + try: + f = open(filename, 'wb') + except gdb.error: + raise gdb.GdbError("Could not open file to dump fdt") + + f.write(fdt_buf) + f.close() + + gdb.write("Dumped fdt blob to " + filename + "\n") + + +LxFdtDump() diff --git a/scripts/gdb/linux/radixtree.py b/scripts/gdb/linux/radixtree.py new file mode 100644 index 000000000..bc2954e45 --- /dev/null +++ b/scripts/gdb/linux/radixtree.py @@ -0,0 +1,215 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Radix Tree Parser +# +# Copyright (c) 2016 Linaro Ltd +# Copyright (c) 2023 Broadcom +# +# Authors: +# Kieran Bingham <kieran.bingham@linaro.org> +# Florian Fainelli <f.fainelli@gmail.com> + +import gdb + +from linux import utils +from linux import constants + +radix_tree_root_type = utils.CachedType("struct xarray") +radix_tree_node_type = utils.CachedType("struct xa_node") + +def is_internal_node(node): + long_type = utils.get_long_type() + return ((node.cast(long_type) & constants.LX_RADIX_TREE_ENTRY_MASK) == constants.LX_RADIX_TREE_INTERNAL_NODE) + +def entry_to_node(node): + long_type = utils.get_long_type() + node_type = node.type + indirect_ptr = node.cast(long_type) & ~constants.LX_RADIX_TREE_INTERNAL_NODE + return indirect_ptr.cast(radix_tree_node_type.get_type().pointer()) + +def node_maxindex(node): + return (constants.LX_RADIX_TREE_MAP_SIZE << node['shift']) - 1 + +def resolve_root(root): + if root.type == radix_tree_root_type.get_type(): + return root + if root.type == radix_tree_root_type.get_type().pointer(): + return root.dereference() + raise gdb.GdbError("must be {} not {}" + .format(radix_tree_root_type.get_type(), root.type)) + +def lookup(root, index): + root = resolve_root(root) + node = root['xa_head'] + if node == 0: + return None + + if not (is_internal_node(node)): + if (index > 0): + return None + return node + + node = entry_to_node(node) + maxindex = node_maxindex(node) + + if (index > maxindex): + return None + + shift = node['shift'] + constants.LX_RADIX_TREE_MAP_SHIFT + + while True: + offset = (index >> node['shift']) & constants.LX_RADIX_TREE_MAP_MASK + slot = node['slots'][offset] + + if slot == 0: + return None + + node = slot.cast(node.type.pointer()).dereference() + if node == 0: + return None + + shift -= constants.LX_RADIX_TREE_MAP_SHIFT + if (shift <= 0): + break + + return node + +def descend(parent, index): + offset = (index >> int(parent["shift"])) & constants.LX_RADIX_TREE_MAP_MASK + return offset, parent["slots"][offset] + +def load_root(root): + node = root["xa_head"] + nodep = node + + if is_internal_node(node): + node = entry_to_node(node) + maxindex = node_maxindex(node) + return int(node["shift"]) + constants.LX_RADIX_TREE_MAP_SHIFT, \ + nodep, maxindex + + return 0, nodep, 0 + +class RadixTreeIter: + def __init__(self, start): + self.index = 0 + self.next_index = start + self.node = None + +def xa_mk_internal(v): + return (v << 2) | 2 + +LX_XA_RETRY_ENTRY = xa_mk_internal(256) +LX_RADIX_TREE_RETRY = LX_XA_RETRY_ENTRY + +def next_chunk(root, iter): + mask = (1 << (utils.get_ulong_type().sizeof * 8)) - 1 + + index = iter.next_index + if index == 0 and iter.index != 0: + return None + + restart = True + while restart: + restart = False + + _, child, maxindex = load_root(root) + if index > maxindex: + return None + if not child: + return None + + if not is_internal_node(child): + iter.index = index + iter.next_index = (maxindex + 1) & mask + iter.node = None + return root["xa_head"].address + + while True: + node = entry_to_node(child) + offset, child = descend(node, index) + + if not child: + while True: + offset += 1 + if offset >= constants.LX_RADIX_TREE_MAP_SIZE: + break + slot = node["slots"][offset] + if slot: + break + index &= ~node_maxindex(node) + index = (index + (offset << int(node["shift"]))) & mask + if index == 0: + return None + if offset == constants.LX_RADIX_TREE_MAP_SIZE: + restart = True + break + child = node["slots"][offset] + + if not child: + restart = True + break + if child == LX_XA_RETRY_ENTRY: + break + if not node["shift"] or not is_internal_node(child): + break + + iter.index = (index & ~node_maxindex(node)) | offset + iter.next_index = ((index | node_maxindex(node)) + 1) & mask + iter.node = node + + return node["slots"][offset].address + +def next_slot(slot, iter): + mask = (1 << (utils.get_ulong_type().sizeof * 8)) - 1 + for _ in range(iter.next_index - iter.index - 1): + slot += 1 + iter.index = (iter.index + 1) & mask + if slot.dereference(): + return slot + return None + +def for_each_slot(root, start=0): + iter = RadixTreeIter(start) + slot = None + while True: + if not slot: + slot = next_chunk(root, iter) + if not slot: + break + yield iter.index, slot + slot = next_slot(slot, iter) + +class LxRadixTreeLookup(gdb.Function): + """ Lookup and return a node from a RadixTree. + +$lx_radix_tree_lookup(root_node [, index]): Return the node at the given index. +If index is omitted, the root node is dereference and returned.""" + + def __init__(self): + super(LxRadixTreeLookup, self).__init__("lx_radix_tree_lookup") + + def invoke(self, root, index=0): + result = lookup(root, index) + if result is None: + raise gdb.GdbError("No entry in tree at index {}".format(index)) + + return result + +class LxRadixTree(gdb.Command): + """Show all values stored in a RadixTree.""" + + def __init__(self): + super(LxRadixTree, self).__init__("lx-radix-tree", gdb.COMMAND_DATA, + gdb.COMPLETE_NONE) + + def invoke(self, argument, from_tty): + args = gdb.string_to_argv(argument) + if len(args) != 1: + raise gdb.GdbError("Usage: lx-radix-tree ROOT") + root = gdb.parse_and_eval(args[0]) + for index, slot in for_each_slot(root): + gdb.write("[{}] = {}\n".format(index, slot.dereference())) + +LxRadixTree() +LxRadixTreeLookup() diff --git a/scripts/gdb/linux/rbtree.py b/scripts/gdb/linux/rbtree.py new file mode 100644 index 000000000..fcbcc5f41 --- /dev/null +++ b/scripts/gdb/linux/rbtree.py @@ -0,0 +1,189 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright 2019 Google LLC. + +import gdb + +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(): + node = root.address.cast(rb_root_type.get_type().pointer()) + elif root.type != rb_root_type.get_type().pointer(): + raise gdb.GdbError("Must be struct rb_root not {}".format(root.type)) + + node = root['rb_node'] + if node == 0: + return None + + while node['rb_left']: + node = node['rb_left'] + + return node + + +def rb_last(root): + if root.type == rb_root_type.get_type(): + node = root.address.cast(rb_root_type.get_type().pointer()) + elif root.type != rb_root_type.get_type().pointer(): + raise gdb.GdbError("Must be struct rb_root not {}".format(root.type)) + + node = root['rb_node'] + if node == 0: + return None + + while node['rb_right']: + node = node['rb_right'] + + return node + + +def rb_parent(node): + parent = gdb.Value(node['__rb_parent_color'] & ~3) + return parent.cast(rb_node_type.get_type().pointer()) + + +def rb_empty_node(node): + return node['__rb_parent_color'] == node.address + + +def rb_next(node): + if node.type == rb_node_type.get_type(): + node = node.address.cast(rb_node_type.get_type().pointer()) + elif node.type != rb_node_type.get_type().pointer(): + raise gdb.GdbError("Must be struct rb_node not {}".format(node.type)) + + if rb_empty_node(node): + return None + + if node['rb_right']: + node = node['rb_right'] + while node['rb_left']: + node = node['rb_left'] + return node + + parent = rb_parent(node) + while parent and node == parent['rb_right']: + node = parent + parent = rb_parent(node) + + return parent + + +def rb_prev(node): + if node.type == rb_node_type.get_type(): + node = node.address.cast(rb_node_type.get_type().pointer()) + elif node.type != rb_node_type.get_type().pointer(): + raise gdb.GdbError("Must be struct rb_node not {}".format(node.type)) + + if rb_empty_node(node): + return None + + if node['rb_left']: + node = node['rb_left'] + while node['rb_right']: + node = node['rb_right'] + return node.dereference() + + parent = rb_parent(node) + while parent and node == parent['rb_left'].dereference(): + node = parent + parent = rb_parent(node) + + return parent + + +class LxRbFirst(gdb.Function): + """Lookup and return a node from an RBTree + +$lx_rb_first(root): Return the node at the given index. +If index is omitted, the root node is dereferenced and returned.""" + + def __init__(self): + super(LxRbFirst, self).__init__("lx_rb_first") + + def invoke(self, root): + result = rb_first(root) + if result is None: + raise gdb.GdbError("No entry in tree") + + return result + + +LxRbFirst() + + +class LxRbLast(gdb.Function): + """Lookup and return a node from an RBTree. + +$lx_rb_last(root): Return the node at the given index. +If index is omitted, the root node is dereferenced and returned.""" + + def __init__(self): + super(LxRbLast, self).__init__("lx_rb_last") + + def invoke(self, root): + result = rb_last(root) + if result is None: + raise gdb.GdbError("No entry in tree") + + return result + + +LxRbLast() + + +class LxRbNext(gdb.Function): + """Lookup and return a node from an RBTree. + +$lx_rb_next(node): Return the node at the given index. +If index is omitted, the root node is dereferenced and returned.""" + + def __init__(self): + super(LxRbNext, self).__init__("lx_rb_next") + + def invoke(self, node): + result = rb_next(node) + if result is None: + raise gdb.GdbError("No entry in tree") + + return result + + +LxRbNext() + + +class LxRbPrev(gdb.Function): + """Lookup and return a node from an RBTree. + +$lx_rb_prev(node): Return the node at the given index. +If index is omitted, the root node is dereferenced and returned.""" + + def __init__(self): + super(LxRbPrev, self).__init__("lx_rb_prev") + + def invoke(self, node): + result = rb_prev(node) + if result is None: + raise gdb.GdbError("No entry in tree") + + return result + + +LxRbPrev() diff --git a/scripts/gdb/linux/slab.py b/scripts/gdb/linux/slab.py new file mode 100644 index 000000000..ddde25aec --- /dev/null +++ b/scripts/gdb/linux/slab.py @@ -0,0 +1,325 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2023 MediaTek Inc. +# +# Authors: +# Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com> +# + +import gdb +import re +import traceback +from linux import lists, utils, stackdepot, constants, mm + +SLAB_RED_ZONE = constants.LX_SLAB_RED_ZONE +SLAB_POISON = constants.LX_SLAB_POISON +SLAB_KMALLOC = constants.LX_SLAB_KMALLOC +SLAB_HWCACHE_ALIGN = constants.LX_SLAB_HWCACHE_ALIGN +SLAB_CACHE_DMA = constants.LX_SLAB_CACHE_DMA +SLAB_CACHE_DMA32 = constants.LX_SLAB_CACHE_DMA32 +SLAB_STORE_USER = constants.LX_SLAB_STORE_USER +SLAB_PANIC = constants.LX_SLAB_PANIC + +OO_SHIFT = 16 +OO_MASK = (1 << OO_SHIFT) - 1 + +if constants.LX_CONFIG_SLUB_DEBUG: + slab_type = utils.CachedType("struct slab") + slab_ptr_type = slab_type.get_type().pointer() + kmem_cache_type = utils.CachedType("struct kmem_cache") + kmem_cache_ptr_type = kmem_cache_type.get_type().pointer() + freeptr_t = utils.CachedType("freeptr_t") + freeptr_t_ptr = freeptr_t.get_type().pointer() + + track_type = gdb.lookup_type('struct track') + track_alloc = int(gdb.parse_and_eval('TRACK_ALLOC')) + track_free = int(gdb.parse_and_eval('TRACK_FREE')) + +def slab_folio(slab): + return slab.cast(gdb.lookup_type("struct folio").pointer()) + +def slab_address(slab): + p_ops = mm.page_ops().ops + folio = slab_folio(slab) + return p_ops.folio_address(folio) + +def for_each_object(cache, addr, slab_objects): + p = addr + if cache['flags'] & SLAB_RED_ZONE: + p += int(cache['red_left_pad']) + while p < addr + (slab_objects * cache['size']): + yield p + p = p + int(cache['size']) + +def get_info_end(cache): + if (cache['offset'] >= cache['inuse']): + return cache['inuse'] + gdb.lookup_type("void").pointer().sizeof + else: + return cache['inuse'] + +def get_orig_size(cache, obj): + if cache['flags'] & SLAB_STORE_USER and cache['flags'] & SLAB_KMALLOC: + p = mm.page_ops().ops.kasan_reset_tag(obj) + p += get_info_end(cache) + p += gdb.lookup_type('struct track').sizeof * 2 + p = p.cast(utils.get_uint_type().pointer()) + return p.dereference() + else: + return cache['object_size'] + +def get_track(cache, object_pointer, alloc): + p = object_pointer + get_info_end(cache) + p += (alloc * track_type.sizeof) + return p + +def oo_objects(x): + return int(x['x']) & OO_MASK + +def oo_order(x): + return int(x['x']) >> OO_SHIFT + +def reciprocal_divide(a, R): + t = (a * int(R['m'])) >> 32 + return (t + ((a - t) >> int(R['sh1']))) >> int(R['sh2']) + +def __obj_to_index(cache, addr, obj): + return reciprocal_divide(int(mm.page_ops().ops.kasan_reset_tag(obj)) - addr, cache['reciprocal_size']) + +def swab64(x): + result = (((x & 0x00000000000000ff) << 56) | \ + ((x & 0x000000000000ff00) << 40) | \ + ((x & 0x0000000000ff0000) << 24) | \ + ((x & 0x00000000ff000000) << 8) | \ + ((x & 0x000000ff00000000) >> 8) | \ + ((x & 0x0000ff0000000000) >> 24) | \ + ((x & 0x00ff000000000000) >> 40) | \ + ((x & 0xff00000000000000) >> 56)) + return result + +def freelist_ptr_decode(cache, ptr, ptr_addr): + if constants.LX_CONFIG_SLAB_FREELIST_HARDENED: + return ptr['v'] ^ cache['random'] ^ swab64(int(ptr_addr)) + else: + return ptr['v'] + +def get_freepointer(cache, obj): + obj = mm.page_ops().ops.kasan_reset_tag(obj) + ptr_addr = obj + cache['offset'] + p = ptr_addr.cast(freeptr_t_ptr).dereference() + return freelist_ptr_decode(cache, p, ptr_addr) + +def loc_exist(loc_track, addr, handle, waste): + for loc in loc_track: + if loc['addr'] == addr and loc['handle'] == handle and loc['waste'] == waste: + return loc + return None + +def add_location(loc_track, cache, track, orig_size): + jiffies = gdb.parse_and_eval("jiffies_64") + age = jiffies - track['when'] + handle = 0 + waste = cache['object_size'] - int(orig_size) + pid = int(track['pid']) + cpuid = int(track['cpu']) + addr = track['addr'] + if constants.LX_CONFIG_STACKDEPOT: + handle = track['handle'] + + loc = loc_exist(loc_track, addr, handle, waste) + if loc: + loc['count'] += 1 + if track['when']: + loc['sum_time'] += age + loc['min_time'] = min(loc['min_time'], age) + loc['max_time'] = max(loc['max_time'], age) + loc['min_pid'] = min(loc['min_pid'], pid) + loc['max_pid'] = max(loc['max_pid'], pid) + loc['cpus'].add(cpuid) + else: + loc_track.append({ + 'count' : 1, + 'addr' : addr, + 'sum_time' : age, + 'min_time' : age, + 'max_time' : age, + 'min_pid' : pid, + 'max_pid' : pid, + 'handle' : handle, + 'waste' : waste, + 'cpus' : {cpuid} + } + ) + +def slabtrace(alloc, cache_name): + + def __fill_map(obj_map, cache, slab): + p = slab['freelist'] + addr = slab_address(slab) + while p != gdb.Value(0): + index = __obj_to_index(cache, addr, p) + obj_map[index] = True # free objects + p = get_freepointer(cache, p) + + # process every slab page on the slab_list (partial and full list) + def process_slab(loc_track, slab_list, alloc, cache): + for slab in lists.list_for_each_entry(slab_list, slab_ptr_type, "slab_list"): + obj_map[:] = [False] * oo_objects(cache['oo']) + __fill_map(obj_map, cache, slab) + addr = slab_address(slab) + for object_pointer in for_each_object(cache, addr, slab['objects']): + if obj_map[__obj_to_index(cache, addr, object_pointer)] == True: + continue + p = get_track(cache, object_pointer, alloc) + track = gdb.Value(p).cast(track_type.pointer()) + if alloc == track_alloc: + size = get_orig_size(cache, object_pointer) + else: + size = cache['object_size'] + add_location(loc_track, cache, track, size) + continue + + slab_caches = gdb.parse_and_eval("slab_caches") + if mm.page_ops().ops.MAX_NUMNODES > 1: + nr_node_ids = int(gdb.parse_and_eval("nr_node_ids")) + else: + nr_node_ids = 1 + + target_cache = None + loc_track = [] + + for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'): + if cache['name'].string() == cache_name: + target_cache = cache + break + + obj_map = [False] * oo_objects(target_cache['oo']) + + if target_cache['flags'] & SLAB_STORE_USER: + for i in range(0, nr_node_ids): + cache_node = target_cache['per_node']['node'][i] + if cache_node['nr_slabs']['counter'] == 0: + continue + process_slab(loc_track, cache_node['partial'], alloc, target_cache) + process_slab(loc_track, cache_node['full'], alloc, target_cache) + else: + raise gdb.GdbError("SLAB_STORE_USER is not set in %s" % target_cache['name'].string()) + + for loc in sorted(loc_track, key=lambda x:x['count'], reverse=True): + if loc['addr']: + addr = loc['addr'].cast(utils.get_ulong_type().pointer()) + gdb.write("%d %s" % (loc['count'], str(addr).split(' ')[-1])) + else: + gdb.write("%d <not-available>" % loc['count']) + + if loc['waste']: + gdb.write(" waste=%d/%d" % (loc['count'] * loc['waste'], loc['waste'])) + + if loc['sum_time'] != loc['min_time']: + gdb.write(" age=%d/%d/%d" % (loc['min_time'], loc['sum_time']/loc['count'], loc['max_time'])) + else: + gdb.write(" age=%d" % loc['min_time']) + + if loc['min_pid'] != loc['max_pid']: + gdb.write(" pid=%d-%d" % (loc['min_pid'], loc['max_pid'])) + else: + gdb.write(" pid=%d" % loc['min_pid']) + + if constants.LX_NR_CPUS > 1: + nr_cpu = gdb.parse_and_eval('__num_online_cpus')['counter'] + if nr_cpu > 1: + gdb.write(" cpus=") + gdb.write(','.join(str(cpu) for cpu in loc['cpus'])) + gdb.write("\n") + if constants.LX_CONFIG_STACKDEPOT: + if loc['handle']: + stackdepot.stack_depot_print(loc['handle']) + gdb.write("\n") + +def help(): + t = """Usage: lx-slabtrace --cache_name [cache_name] [Options] + Options: + --alloc + print information of allocation trace of the allocated objects + --free + print information of freeing trace of the allocated objects + Example: + lx-slabtrace --cache_name kmalloc-1k --alloc + lx-slabtrace --cache_name kmalloc-1k --free\n""" + gdb.write("Unrecognized command\n") + raise gdb.GdbError(t) + +class LxSlabTrace(gdb.Command): + """Show specific cache slabtrace""" + + def __init__(self): + super(LxSlabTrace, self).__init__("lx-slabtrace", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + if not constants.LX_CONFIG_SLUB_DEBUG: + raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") + + argv = gdb.string_to_argv(arg) + alloc = track_alloc # default show alloc_traces + + if len(argv) == 3: + if argv[2] == '--alloc': + alloc = track_alloc + elif argv[2] == '--free': + alloc = track_free + else: + help() + if len(argv) >= 2 and argv[0] == '--cache_name': + slabtrace(alloc, argv[1]) + else: + help() +LxSlabTrace() + +def slabinfo(): + nr_node_ids = None + + if not constants.LX_CONFIG_SLUB_DEBUG: + raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled") + + def count_free(slab): + total_free = 0 + for slab in lists.list_for_each_entry(slab, slab_ptr_type, 'slab_list'): + total_free += int(slab['objects'] - slab['inuse']) + return total_free + + gdb.write("{:^18} | {:^20} | {:^12} | {:^12} | {:^8} | {:^11} | {:^13}\n".format('Pointer', 'name', 'active_objs', 'num_objs', 'objsize', 'objperslab', 'pagesperslab')) + gdb.write("{:-^18} | {:-^20} | {:-^12} | {:-^12} | {:-^8} | {:-^11} | {:-^13}\n".format('', '', '', '', '', '', '')) + + slab_caches = gdb.parse_and_eval("slab_caches") + if mm.page_ops().ops.MAX_NUMNODES > 1: + nr_node_ids = int(gdb.parse_and_eval("nr_node_ids")) + else: + nr_node_ids = 1 + + for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'): + nr_objs = 0 + nr_free = 0 + nr_slabs = 0 + for i in range(0, nr_node_ids): + cache_node = cache['per_node']['node'][i] + try: + nr_slabs += cache_node['nr_slabs']['counter'] + nr_objs = int(cache_node['total_objects']['counter']) + nr_free = count_free(cache_node['partial']) + except: + raise gdb.GdbError(traceback.format_exc()) + active_objs = nr_objs - nr_free + num_objs = nr_objs + active_slabs = nr_slabs + objects_per_slab = oo_objects(cache['oo']) + cache_order = oo_order(cache['oo']) + gdb.write("{:18s} | {:20.19s} | {:12} | {:12} | {:8} | {:11} | {:13}\n".format(hex(cache), cache['name'].string(), str(active_objs), str(num_objs), str(cache['size']), str(objects_per_slab), str(1 << cache_order))) + +class LxSlabInfo(gdb.Command): + """Show slabinfo""" + + def __init__(self): + super(LxSlabInfo, self).__init__("lx-slabinfo", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + slabinfo() +LxSlabInfo() diff --git a/scripts/gdb/linux/stackdepot.py b/scripts/gdb/linux/stackdepot.py new file mode 100644 index 000000000..37313a5a5 --- /dev/null +++ b/scripts/gdb/linux/stackdepot.py @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2023 MediaTek Inc. +# +# Authors: +# Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com> +# + +import gdb +from linux import utils, constants + +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 + + stack_depot_disabled = gdb.parse_and_eval('stack_depot_disabled') + + if stack_depot_disabled: + raise gdb.GdbError("stack_depot_disabled\n") + + handle_parts_t = gdb.lookup_type("union handle_parts") + parts = handle.cast(handle_parts_t) + offset = parts['offset'] << DEPOT_STACK_ALIGN + pools_num = gdb.parse_and_eval('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[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 + except Exception as e: + gdb.write("%s\n" % e) + return gdb.Value(0), 0 + +def stack_depot_print(handle): + if not constants.LX_CONFIG_STACKDEPOT: + raise gdb.GdbError("CONFIG_STACKDEPOT is not enabled") + + entries, nr_entries = stack_depot_fetch(handle) + if nr_entries > 0: + for i in range(0, nr_entries): + try: + 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 new file mode 100644 index 000000000..943ff1228 --- /dev/null +++ b/scripts/gdb/linux/symbols.py @@ -0,0 +1,338 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# load kernel and module symbols +# +# Copyright (c) Siemens AG, 2011-2013 +# +# Authors: +# Jan Kiszka <jan.kiszka@siemens.com> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import atexit +import gdb +import os +import re +import struct + +from itertools import count +from linux import bpf, constants, modules, utils + + +if hasattr(gdb, 'Breakpoint'): + class LoadModuleBreakpoint(gdb.Breakpoint): + def __init__(self, spec, gdb_command): + super(LoadModuleBreakpoint, self).__init__(spec, internal=True) + self.silent = True + self.gdb_command = gdb_command + + def stop(self): + module = gdb.parse_and_eval("mod") + module_name = module['name'].string() + cmd = self.gdb_command + + # enforce update if object file is not found + cmd.module_files_updated = False + + # 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. + 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 + + +def is_in_s390_decompressor(): + # DAT is always off in decompressor. Use this as an indicator. + # Note that in the kernel, DAT can be off during kexec() or restart. + # Accept this imprecision in order to avoid complicating things. + # It is unlikely that someone will run lx-symbols at these points. + pswm = int(gdb.parse_and_eval("$pswm")) + return (pswm & 0x0400000000000000) == 0 + + +def skip_decompressor(): + if utils.is_target_arch("s390"): + if is_in_s390_decompressor(): + # The address of the jump_to_kernel function is statically placed + # into svc_old_psw.addr (see ipl_data.c); read it from there. DAT + # is off, so we do not need to care about lowcore relocation. + svc_old_pswa = 0x148 + jump_to_kernel = int(gdb.parse_and_eval("*(unsigned long long *)" + + hex(svc_old_pswa))) + gdb.execute("tbreak *" + hex(jump_to_kernel)) + gdb.execute("continue") + while is_in_s390_decompressor(): + gdb.execute("stepi") + + +class LxSymbols(gdb.Command): + """(Re-)load symbols of Linux kernel and currently loaded modules. + +The kernel (vmlinux) is taken from the current working directly. Modules (.ko) +are scanned recursively, starting in the same directory. Optionally, the module +search path can be extended by a space separated list of paths passed to the +lx-symbols command. + +When the -bpf flag is specified, symbols from the currently loaded BPF programs +are loaded as well.""" + + module_paths = [] + module_files = [] + module_files_updated = False + loaded_modules = [] + breakpoint = None + bpf_prog_monitor = None + bpf_ksym_monitor = None + bpf_progs = {} + # The remove-symbol-file command, even when invoked with -a, requires the + # respective object file to exist, so keep them around. + bpf_debug_objs = {} + + def __init__(self): + super(LxSymbols, self).__init__("lx-symbols", gdb.COMMAND_FILES, + gdb.COMPLETE_FILENAME) + atexit.register(self.cleanup_bpf) + + def _update_module_files(self): + self.module_files = [] + for path in self.module_paths: + gdb.write("scanning for modules in {0}\n".format(path)) + for root, dirs, files in os.walk(path): + for name in files: + if name.endswith(".ko") or name.endswith(".ko.debug"): + self.module_files.append(root + "/" + name) + self.module_files_updated = True + + def _get_module_file(self, module_name): + module_pattern = r".*/{0}\.ko(?:.debug)?$".format( + module_name.replace("_", r"[_\-]")) + for name in self.module_files: + if re.match(module_pattern, name) and os.path.exists(name): + return name + return None + + def _section_arguments(self, module, module_addr): + try: + sect_attrs = module['sect_attrs'].dereference() + except gdb.error: + return str(module_addr) + + 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 = [] + for section_name in [".data", ".data..read_mostly", ".rodata", ".bss", + ".text.hot", ".text.unlikely"]: + address = section_name_to_address.get(section_name) + if address: + args.append(" -s {name} {addr}".format( + name=section_name, addr=str(address))) + return "{textaddr} {sections}".format( + textaddr=textaddr, sections="".join(args)) + + def load_module_symbols(self, module): + module_name = module['name'].string() + module_addr = str(module['mem'][constants.LX_MOD_TEXT]['base']).split()[0] + + module_file = self._get_module_file(module_name) + if not module_file and not self.module_files_updated: + self._update_module_files() + module_file = self._get_module_file(module_name) + + if module_file: + if utils.is_target_arch('s390'): + # Module text is preceded by PLT stubs on s390. + module_arch = module['arch'] + plt_offset = int(module_arch['plt_offset']) + plt_size = int(module_arch['plt_size']) + module_addr = hex(int(module_addr, 0) + plt_offset + plt_size) + gdb.write("loading @{addr}: {filename}\n".format( + addr=module_addr, filename=module_file)) + cmdline = "add-symbol-file {filename} {sections}".format( + filename=module_file, + sections=self._section_arguments(module, module_addr)) + gdb.execute(cmdline, to_string=True) + if module_name not in self.loaded_modules: + self.loaded_modules.append(module_name) + else: + gdb.write("no module object found for '{0}'\n".format(module_name)) + + def add_bpf_prog(self, prog): + if prog["jited"]: + self.bpf_progs[int(prog["bpf_func"])] = prog + + def remove_bpf_prog(self, prog): + self.bpf_progs.pop(int(prog["bpf_func"]), None) + + def add_bpf_ksym(self, ksym): + addr = int(ksym["start"]) + name = bpf.get_ksym_name(ksym) + with utils.pagination_off(): + gdb.write("loading @{addr}: {name}\n".format( + addr=hex(addr), name=name)) + debug_obj = bpf.generate_debug_obj(ksym, self.bpf_progs.get(addr)) + if debug_obj is None: + return + try: + cmdline = "add-symbol-file {obj} {addr}".format( + obj=debug_obj.name, addr=hex(addr)) + gdb.execute(cmdline, to_string=True) + except: + debug_obj.close() + raise + self.bpf_debug_objs[addr] = debug_obj + + def remove_bpf_ksym(self, ksym): + addr = int(ksym["start"]) + debug_obj = self.bpf_debug_objs.pop(addr, None) + if debug_obj is None: + return + try: + name = bpf.get_ksym_name(ksym) + gdb.write("unloading @{addr}: {name}\n".format( + addr=hex(addr), name=name)) + cmdline = "remove-symbol-file {path}".format(path=debug_obj.name) + gdb.execute(cmdline, to_string=True) + finally: + debug_obj.close() + + def cleanup_bpf(self): + self.bpf_progs = {} + while len(self.bpf_debug_objs) > 0: + self.bpf_debug_objs.popitem()[1].close() + + + def load_all_symbols(self): + gdb.write("loading vmlinux\n") + + # Dropping symbols will disable all breakpoints. So save their states + # and restore them afterward. + saved_states = [] + if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None: + for bp in gdb.breakpoints(): + saved_states.append({'breakpoint': bp, 'enabled': bp.enabled}) + + # drop all current symbols and reload vmlinux + orig_vmlinux = utils.get_vmlinux() + gdb.execute("symbol-file", to_string=True) + 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() + if not module_list: + gdb.write("no modules found\n") + else: + [self.load_module_symbols(module) for module in module_list] + + self.cleanup_bpf() + if self.bpf_prog_monitor is not None: + self.bpf_prog_monitor.notify_initial() + if self.bpf_ksym_monitor is not None: + self.bpf_ksym_monitor.notify_initial() + + for saved_state in saved_states: + saved_state['breakpoint'].enabled = saved_state['enabled'] + + def invoke(self, arg, from_tty): + skip_decompressor() + + monitor_bpf = False + self.module_paths = [] + for p in arg.split(): + if p == "-bpf": + monitor_bpf = True + else: + self.module_paths.append(os.path.abspath(os.path.expanduser(p))) + self.module_paths.append(os.getcwd()) + + if self.breakpoint is not None: + self.breakpoint.delete() + self.breakpoint = None + if self.bpf_prog_monitor is not None: + self.bpf_prog_monitor.delete() + self.bpf_prog_monitor = None + if self.bpf_ksym_monitor is not None: + self.bpf_ksym_monitor.delete() + self.bpf_ksym_monitor = None + + # enforce update + self.module_files = [] + self.module_files_updated = False + + self.load_all_symbols() + + if not hasattr(gdb, 'Breakpoint'): + gdb.write("Note: symbol update on module and BPF loading not " + "supported with this gdb version\n") + return + + if modules.has_modules(): + self.breakpoint = LoadModuleBreakpoint( + "kernel/module/main.c:do_init_module", self) + + if monitor_bpf: + if constants.LX_CONFIG_BPF_SYSCALL: + self.bpf_prog_monitor = bpf.ProgMonitor(self.add_bpf_prog, + self.remove_bpf_prog) + if constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT: + self.bpf_ksym_monitor = bpf.KsymMonitor(self.add_bpf_ksym, + self.remove_bpf_ksym) + + +LxSymbols() diff --git a/scripts/gdb/linux/tasks.py b/scripts/gdb/linux/tasks.py new file mode 100644 index 000000000..62348397c --- /dev/null +++ b/scripts/gdb/linux/tasks.py @@ -0,0 +1,127 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# task & thread tools +# +# Copyright (c) Siemens AG, 2011-2013 +# +# Authors: +# Jan Kiszka <jan.kiszka@siemens.com> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import gdb + +from linux import utils, lists + + +task_type = utils.CachedType("struct task_struct") + + +def task_lists(): + task_ptr_type = task_type.get_type().pointer() + init_task = gdb.parse_and_eval("init_task").address + t = init_task + + while True: + thread_head = t['signal']['thread_head'] + for thread in lists.list_for_each_entry(thread_head, task_ptr_type, 'thread_node'): + yield thread + + t = utils.container_of(t['tasks']['next'], + task_ptr_type, "tasks") + if t == init_task: + return + + +def get_task_by_pid(pid): + for task in task_lists(): + if int(task['pid']) == pid: + return task + return None + + +class LxTaskByPidFunc(gdb.Function): + """Find Linux task by PID and return the task_struct variable. + +$lx_task_by_pid(PID): Given PID, iterate over all tasks of the target and +return that task_struct variable which PID matches.""" + + def __init__(self): + super(LxTaskByPidFunc, self).__init__("lx_task_by_pid") + + def invoke(self, pid): + task = get_task_by_pid(pid) + if task: + return task.dereference() + else: + raise gdb.GdbError("No task of PID " + str(pid)) + + +LxTaskByPidFunc() + + +class LxPs(gdb.Command): + """Dump Linux tasks.""" + + def __init__(self): + super(LxPs, self).__init__("lx-ps", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + gdb.write("{:>10} {:>12} {:>7}\n".format("TASK", "PID", "COMM")) + for task in task_lists(): + gdb.write("{} {:^5} {}\n".format( + task.format_string().split()[0], + task["pid"].format_string(), + task["comm"].string())) + + +LxPs() + + +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.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() + + +class LxThreadInfoFunc (gdb.Function): + """Calculate Linux thread_info from task variable. + +$lx_thread_info(TASK): Given TASK, return the corresponding thread_info +variable.""" + + def __init__(self): + super(LxThreadInfoFunc, self).__init__("lx_thread_info") + + def invoke(self, task): + return get_thread_info(task) + + +LxThreadInfoFunc() + + +class LxThreadInfoByPidFunc (gdb.Function): + """Calculate Linux thread_info from task variable found by pid + +$lx_thread_info_by_pid(PID): Given PID, return the corresponding thread_info +variable.""" + + def __init__(self): + super(LxThreadInfoByPidFunc, self).__init__("lx_thread_info_by_pid") + + def invoke(self, pid): + task = get_task_by_pid(pid) + if task: + return get_thread_info(task.dereference()) + else: + raise gdb.GdbError("No task of PID " + str(pid)) + + +LxThreadInfoByPidFunc() diff --git a/scripts/gdb/linux/timerlist.py b/scripts/gdb/linux/timerlist.py new file mode 100644 index 000000000..744b032e4 --- /dev/null +++ b/scripts/gdb/linux/timerlist.py @@ -0,0 +1,216 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright 2019 Google LLC. + +import binascii +import gdb + +from linux import constants +from linux import cpus +from linux import rbtree +from linux import utils + +timerqueue_node_type = utils.CachedType("struct timerqueue_node").get_type() +hrtimer_type = utils.CachedType("struct hrtimer").get_type() + + +def ktime_get(): + """Returns the current time, but not very accurately + + We can't read the hardware timer itself to add any nanoseconds + that need to be added since we last stored the time in the + timekeeper. But this is probably good enough for debug purposes.""" + tk_core = gdb.parse_and_eval("&timekeeper_data[TIMEKEEPER_CORE]") + + return tk_core['timekeeper']['tkr_mono']['base'] + + +def print_timer(rb_node, idx): + timerqueue = utils.container_of(rb_node, timerqueue_node_type.pointer(), + "node") + timer = utils.container_of(timerqueue, hrtimer_type.pointer(), "node") + + function = str(timer['function']).split(" ")[1].strip("<>") + softexpires = timer['_softexpires'] + expires = timer['node']['expires'] + now = ktime_get() + + text = " #{}: <{}>, {}, ".format(idx, timer, function) + text += "S:{:02x}\n".format(int(timer['state'])) + text += " # expires at {}-{} nsecs [in {} to {} nsecs]\n".format( + softexpires, expires, softexpires - now, expires - now) + return text + + +def print_active_timers(base): + curr = base['active']['rb_root']['rb_leftmost'] + idx = 0 + while curr: + yield print_timer(curr, idx) + curr = rbtree.rb_next(curr) + idx += 1 + + +def print_base(base): + text = " .base: {}\n".format(base.address) + text += " .index: {}\n".format(base['index']) + + text += " .resolution: {} nsecs\n".format(constants.LX_hrtimer_resolution) + if constants.LX_CONFIG_HIGH_RES_TIMERS: + text += " .offset: {} nsecs\n".format(base['offset']) + text += "active timers:\n" + text += "".join([x for x in print_active_timers(base)]) + return text + + +def print_cpu(hrtimer_bases, cpu, max_clock_bases): + cpu_base = cpus.per_cpu(hrtimer_bases, cpu) + jiffies = gdb.parse_and_eval("jiffies_64") + tick_sched_ptr = gdb.parse_and_eval("&tick_cpu_sched") + ts = cpus.per_cpu(tick_sched_ptr, cpu) + + text = "cpu: {}\n".format(cpu) + for i in range(max_clock_bases): + text += " clock {}:\n".format(i) + text += print_base(cpu_base['clock_base'][i]) + + if constants.LX_CONFIG_HIGH_RES_TIMERS: + fmts = [(" .{} : {} nsecs", 'expires_next'), + (" .{} : {}", 'hres_active'), + (" .{} : {}", 'nr_events'), + (" .{} : {}", 'nr_retries'), + (" .{} : {}", 'nr_hangs'), + (" .{} : {}", 'max_hang_time')] + text += "\n".join([s.format(f, cpu_base[f]) for s, f in fmts]) + text += "\n" + + if constants.LX_CONFIG_TICK_ONESHOT: + 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_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" .{'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" + + return text + + +def print_tickdevice(td, cpu): + dev = td['evtdev'] + text = "Tick Device: mode: {}\n".format(td['mode']) + if cpu < 0: + text += "Broadcast device\n" + else: + text += "Per CPU device: {}\n".format(cpu) + + text += "Clock Event Device: " + if dev == 0: + text += "<NULL>\n" + return text + + text += "{}\n".format(dev['name']) + text += " max_delta_ns: {}\n".format(dev['max_delta_ns']) + text += " min_delta_ns: {}\n".format(dev['min_delta_ns']) + text += " mult: {}\n".format(dev['mult']) + text += " shift: {}\n".format(dev['shift']) + text += " mode: {}\n".format(dev['state_use_accessors']) + text += " next_event: {} nsecs\n".format(dev['next_event']) + + text += " set_next_event: {}\n".format(dev['set_next_event']) + + members = [('set_state_shutdown', " shutdown: {}\n"), + ('set_state_periodic', " periodic: {}\n"), + ('set_state_oneshot', " oneshot: {}\n"), + ('set_state_oneshot_stopped', " oneshot stopped: {}\n"), + ('tick_resume', " resume: {}\n")] + for member, fmt in members: + if dev[member]: + text += fmt.format(dev[member]) + + text += " event_handler: {}\n".format(dev['event_handler']) + text += " retries: {}\n".format(dev['retries']) + + return text + + +def pr_cpumask(mask): + nr_cpu_ids = 1 + if constants.LX_NR_CPUS > 1: + nr_cpu_ids = gdb.parse_and_eval("nr_cpu_ids") + + inf = gdb.inferiors()[0] + bits = mask['bits'] + num_bytes = (nr_cpu_ids + 7) / 8 + buf = utils.read_memoryview(inf, bits, num_bytes).tobytes() + buf = binascii.b2a_hex(buf) + if type(buf) is not str: + buf=buf.decode() + + chunks = [] + i = num_bytes + while i > 0: + i -= 1 + start = i * 2 + end = start + 2 + chunks.append(buf[start:end]) + if i != 0 and i % 4 == 0: + chunks.append(',') + + extra = nr_cpu_ids % 8 + if 0 < extra <= 4: + chunks[0] = chunks[0][0] # Cut off the first 0 + + return "".join(str(chunks)) + + +class LxTimerList(gdb.Command): + """Print /proc/timer_list""" + + def __init__(self): + super(LxTimerList, self).__init__("lx-timerlist", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + hrtimer_bases = gdb.parse_and_eval("&hrtimer_bases") + max_clock_bases = gdb.parse_and_eval("HRTIMER_MAX_CLOCK_BASES") + + text = "Timer List Version: gdb scripts\n" + text += "HRTIMER_MAX_CLOCK_BASES: {}\n".format( + max_clock_bases.type.fields()[max_clock_bases].enumval) + text += "now at {} nsecs\n".format(ktime_get()) + + for cpu in cpus.each_online_cpu(): + text += print_cpu(hrtimer_bases, cpu, max_clock_bases) + + if constants.LX_CONFIG_GENERIC_CLOCKEVENTS: + if constants.LX_CONFIG_GENERIC_CLOCKEVENTS_BROADCAST: + bc_dev = gdb.parse_and_eval("&tick_broadcast_device") + text += print_tickdevice(bc_dev, -1) + text += "\n" + mask = gdb.parse_and_eval("tick_broadcast_mask") + mask = pr_cpumask(mask) + text += "tick_broadcast_mask: {}\n".format(mask) + if constants.LX_CONFIG_TICK_ONESHOT: + mask = gdb.parse_and_eval("tick_broadcast_oneshot_mask") + mask = pr_cpumask(mask) + text += "tick_broadcast_oneshot_mask: {}\n".format(mask) + text += "\n" + + tick_cpu_devices = gdb.parse_and_eval("&tick_cpu_device") + for cpu in cpus.each_online_cpu(): + tick_dev = cpus.per_cpu(tick_cpu_devices, cpu) + text += print_tickdevice(tick_dev, cpu) + text += "\n" + + gdb.write(text) + + +LxTimerList() diff --git a/scripts/gdb/linux/utils.py b/scripts/gdb/linux/utils.py new file mode 100644 index 000000000..e11f6f679 --- /dev/null +++ b/scripts/gdb/linux/utils.py @@ -0,0 +1,273 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# common utilities +# +# Copyright (c) Siemens AG, 2011-2013 +# +# Authors: +# Jan Kiszka <jan.kiszka@siemens.com> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import contextlib +import dataclasses +import re +import typing + +import gdb + + +class CachedType: + def __init__(self, name): + self._type = None + self._name = name + + def _new_objfile_handler(self, event): + self._type = None + gdb.events.new_objfile.disconnect(self._new_objfile_handler) + + def get_type(self): + if self._type is None: + self._type = gdb.lookup_type(self._name) + if self._type is None: + raise gdb.GdbError( + "cannot resolve type '{0}'".format(self._name)) + if hasattr(gdb, 'events') and hasattr(gdb.events, 'new_objfile'): + gdb.events.new_objfile.connect(self._new_objfile_handler) + return self._type + + +long_type = CachedType("long") +ulong_type = CachedType("unsigned long") +uint_type = CachedType("unsigned int") +atomic_long_type = CachedType("atomic_long_t") +size_t_type = CachedType("size_t") +struct_page_type = CachedType("struct page") + +def get_uint_type(): + global uint_type + return uint_type.get_type() + +def get_page_type(): + global struct_page_type + return struct_page_type.get_type() + +def get_long_type(): + global long_type + return long_type.get_type() + +def get_ulong_type(): + global ulong_type + return ulong_type.get_type() + +def get_size_t_type(): + global size_t_type + return size_t_type.get_type() + +def offset_of(typeobj, field): + element = gdb.Value(0).cast(typeobj) + return int(str(element[field].address).split()[0], 16) + + +def container_of(ptr, typeobj, member): + return (ptr.cast(get_long_type()) - + offset_of(typeobj, member)).cast(typeobj) + + +class ContainerOf(gdb.Function): + """Return pointer to containing data structure. + +$container_of(PTR, "TYPE", "ELEMENT"): Given PTR, return a pointer to the +data structure of the type TYPE in which PTR is the address of ELEMENT. +Note that TYPE and ELEMENT have to be quoted as strings.""" + + def __init__(self): + super(ContainerOf, self).__init__("container_of") + + def invoke(self, ptr, typename, elementname): + return container_of(ptr, gdb.lookup_type(typename.string()).pointer(), + elementname.string()) + + +ContainerOf() + + +BIG_ENDIAN = 0 +LITTLE_ENDIAN = 1 +target_endianness = None + + +def get_target_endianness(): + global target_endianness + if target_endianness is None: + endian = gdb.execute("show endian", to_string=True) + if "little endian" in endian: + target_endianness = LITTLE_ENDIAN + elif "big endian" in endian: + target_endianness = BIG_ENDIAN + else: + raise gdb.GdbError("unknown endianness '{0}'".format(str(endian))) + return target_endianness + + +def read_memoryview(inf, start, length): + m = inf.read_memory(start, length) + if type(m) is memoryview: + return m + return memoryview(m) + + +def read_u16(buffer, offset): + buffer_val = buffer[offset:offset + 2] + value = [0, 0] + + if type(buffer_val[0]) is str: + value[0] = ord(buffer_val[0]) + value[1] = ord(buffer_val[1]) + else: + value[0] = buffer_val[0] + value[1] = buffer_val[1] + + if get_target_endianness() == LITTLE_ENDIAN: + return value[0] + (value[1] << 8) + else: + return value[1] + (value[0] << 8) + + +def read_u32(buffer, offset): + if get_target_endianness() == LITTLE_ENDIAN: + return read_u16(buffer, offset) + (read_u16(buffer, offset + 2) << 16) + else: + return read_u16(buffer, offset + 2) + (read_u16(buffer, offset) << 16) + + +def read_u64(buffer, offset): + if get_target_endianness() == LITTLE_ENDIAN: + return read_u32(buffer, offset) + (read_u32(buffer, offset + 4) << 32) + else: + return read_u32(buffer, offset + 4) + (read_u32(buffer, offset) << 32) + + +def read_ulong(buffer, offset): + if get_long_type().sizeof == 8: + return read_u64(buffer, offset) + else: + return read_u32(buffer, offset) + +atomic_long_counter_offset = atomic_long_type.get_type()['counter'].bitpos +atomic_long_counter_sizeof = atomic_long_type.get_type()['counter'].type.sizeof + +def read_atomic_long(buffer, offset): + global atomic_long_counter_offset + global atomic_long_counter_sizeof + + if atomic_long_counter_sizeof == 8: + return read_u64(buffer, offset + atomic_long_counter_offset) + else: + return read_u32(buffer, offset + atomic_long_counter_offset) + +target_arch = None + + +def is_target_arch(arch): + if hasattr(gdb.Frame, 'architecture'): + return arch in gdb.newest_frame().architecture().name() + else: + global target_arch + if target_arch is None: + target_arch = gdb.execute("show architecture", to_string=True) + return arch in target_arch + + +GDBSERVER_QEMU = 0 +GDBSERVER_KGDB = 1 +gdbserver_type = None + + +def get_gdbserver_type(): + def exit_handler(event): + global gdbserver_type + gdbserver_type = None + gdb.events.exited.disconnect(exit_handler) + + def probe_qemu(): + try: + return gdb.execute("monitor info version", to_string=True) != "" + except gdb.error: + return False + + def probe_kgdb(): + try: + thread_info = gdb.execute("info thread 1", to_string=True) + return "shadowCPU" in thread_info + except gdb.error: + return False + + global gdbserver_type + if gdbserver_type is None: + if probe_qemu(): + gdbserver_type = GDBSERVER_QEMU + elif probe_kgdb(): + gdbserver_type = GDBSERVER_KGDB + if gdbserver_type is not None and hasattr(gdb, 'events'): + gdb.events.exited.connect(exit_handler) + return gdbserver_type + + +def gdb_eval_or_none(expresssion): + try: + 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 new file mode 100644 index 000000000..9e921b645 --- /dev/null +++ b/scripts/gdb/linux/vfs.py @@ -0,0 +1,59 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# VFS tools +# +# Copyright (c) 2023 Glenn Washburn +# Copyright (c) 2016 Linaro Ltd +# +# Authors: +# Glenn Washburn <development@efficientek.com> +# Kieran Bingham <kieran.bingham@linaro.org> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import gdb +from linux import utils + + +def dentry_name(d): + parent = d['d_parent'] + if parent == d or parent == 0: + return "" + p = dentry_name(d['d_parent']) + "/" + return p + d['d_name']['name'].string() + +class DentryName(gdb.Function): + """Return string of the full path of a dentry. + +$lx_dentry_name(PTR): Given PTR to a dentry struct, return a string +of the full path of the dentry.""" + + def __init__(self): + super(DentryName, self).__init__("lx_dentry_name") + + def invoke(self, dentry_ptr): + return dentry_name(dentry_ptr) + +DentryName() + + +dentry_type = utils.CachedType("struct dentry") + +class InodeDentry(gdb.Function): + """Return dentry pointer for inode. + +$lx_i_dentry(PTR): Given PTR to an inode struct, return a pointer to +the associated dentry struct, if there is one.""" + + def __init__(self): + super(InodeDentry, self).__init__("lx_i_dentry") + + def invoke(self, inode_ptr): + d_u = inode_ptr["i_dentry"]["first"] + if d_u == 0: + return "" + return utils.container_of(d_u, dentry_type.get_type().pointer(), "d_u") + +InodeDentry() diff --git a/scripts/gdb/linux/vmalloc.py b/scripts/gdb/linux/vmalloc.py new file mode 100644 index 000000000..803f17371 --- /dev/null +++ b/scripts/gdb/linux/vmalloc.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2023 MediaTek Inc. +# +# Authors: +# Kuan-Ying Lee <Kuan-Ying.Lee@mediatek.com> +# + +import gdb +import re +from linux import lists, utils, stackdepot, constants, mm + +if constants.LX_CONFIG_MMU: + vmap_area_type = utils.CachedType('struct vmap_area') + vmap_area_ptr_type = vmap_area_type.get_type().pointer() + +def is_vmalloc_addr(x): + pg_ops = mm.page_ops().ops + addr = pg_ops.kasan_reset_tag(x) + return addr >= pg_ops.VMALLOC_START and addr < pg_ops.VMALLOC_END + +class LxVmallocInfo(gdb.Command): + """Show vmallocinfo""" + + def __init__(self): + super(LxVmallocInfo, self).__init__("lx-vmallocinfo", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + if not constants.LX_CONFIG_MMU: + raise gdb.GdbError("Requires MMU support") + + nr_vmap_nodes = gdb.parse_and_eval('nr_vmap_nodes') + for i in range(0, nr_vmap_nodes): + vn = gdb.parse_and_eval('&vmap_nodes[%d]' % i) + for vmap_area in lists.list_for_each_entry(vn['busy']['head'], vmap_area_ptr_type, "list"): + if not vmap_area['vm']: + gdb.write("0x%x-0x%x %10d vm_map_ram\n" % (vmap_area['va_start'], vmap_area['va_end'], + vmap_area['va_end'] - vmap_area['va_start'])) + continue + v = vmap_area['vm'] + gdb.write("0x%x-0x%x %10d" % (v['addr'], v['addr'] + v['size'], v['size'])) + if v['caller']: + gdb.write(" %s" % str(v['caller']).split(' ')[-1]) + if v['nr_pages']: + gdb.write(" pages=%d" % v['nr_pages']) + if v['phys_addr']: + gdb.write(" phys=0x%x" % v['phys_addr']) + if v['flags'] & constants.LX_VM_IOREMAP: + gdb.write(" ioremap") + if v['flags'] & constants.LX_VM_ALLOC: + gdb.write(" vmalloc") + if v['flags'] & constants.LX_VM_MAP: + gdb.write(" vmap") + if v['flags'] & constants.LX_VM_USERMAP: + gdb.write(" user") + if v['flags'] & constants.LX_VM_DMA_COHERENT: + gdb.write(" dma-coherent") + if is_vmalloc_addr(v['pages']): + gdb.write(" vpages") + gdb.write("\n") + +LxVmallocInfo() diff --git a/scripts/gdb/linux/xarray.py b/scripts/gdb/linux/xarray.py new file mode 100644 index 000000000..f4477b5de --- /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 new file mode 100644 index 000000000..d4eeed450 --- /dev/null +++ b/scripts/gdb/vmlinux-gdb.py @@ -0,0 +1,52 @@ +# +# gdb helper commands and functions for Linux kernel debugging +# +# loader module +# +# Copyright (c) Siemens AG, 2012, 2013 +# +# Authors: +# Jan Kiszka <jan.kiszka@siemens.com> +# +# This work is licensed under the terms of the GNU GPL version 2. +# + +import os + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/scripts/gdb") + +try: + gdb.parse_and_eval("0") + gdb.execute("", to_string=True) +except: + gdb.write("NOTE: gdb 7.2 or later required for Linux helper scripts to " + "work.\n") +else: + import linux.constants + if linux.constants.LX_CONFIG_DEBUG_INFO_REDUCED: + raise gdb.GdbError("Reduced debug information will prevent GDB " + "from having complete types.\n") + import linux.utils + import linux.symbols + import linux.modules + import linux.dmesg + import linux.tasks + import linux.config + import linux.cpus + import linux.lists + import linux.rbtree + import linux.proc + import linux.timerlist + import linux.clk + import linux.genpd + import linux.device + import linux.vfs + import linux.pgtable + import linux.radixtree + import linux.interrupts + import linux.mm + import linux.stackdepot + import linux.page_owner + import linux.slab + import linux.vmalloc + import linux.kasan diff --git a/scripts/gen-btf.sh b/scripts/gen-btf.sh new file mode 100755 index 000000000..8ca96eb10 --- /dev/null +++ b/scripts/gen-btf.sh @@ -0,0 +1,147 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 Meta Platforms, Inc. and affiliates. +# +# This script generates BTF data for the provided ELF file. +# +# Kernel BTF generation involves these conceptual steps: +# 1. pahole generates BTF from DWARF data +# 2. resolve_btfids applies kernel-specific btf2btf +# transformations and computes data for .BTF_ids section +# 3. the result gets linked/objcopied into the target binary +# +# How step (3) should be done differs between vmlinux, and +# kernel modules, which is the primary reason for the existence +# of this script. +# +# For modules the script expects vmlinux passed in as --btf_base. +# Generated .BTF, .BTF.base and .BTF_ids sections become embedded +# into the input ELF file with objcopy. +# +# For vmlinux the input file remains unchanged and two files are produced: +# - ${1}.btf.o ready for linking into vmlinux +# - ${1}.BTF_ids with .BTF_ids data blob +# This output is consumed by scripts/link-vmlinux.sh + +set -e + +usage() +{ + echo "Usage: $0 [--btf_base <file>] <target ELF file>" + exit 1 +} + +BTF_BASE="" + +while [ $# -gt 0 ]; do + case "$1" in + --btf_base) + BTF_BASE="$2" + shift 2 + ;; + -*) + echo "Unknown option: $1" >&2 + usage + ;; + *) + break + ;; + esac +done + +if [ $# -ne 1 ]; then + usage +fi + +ELF_FILE="$1" +shift + +is_enabled() { + grep -q "^$1=y" ${objtree}/include/config/auto.conf +} + +case "${KBUILD_VERBOSE}" in +*1*) + set -x + ;; +esac + +gen_btf_data() +{ + btf1="${ELF_FILE}.BTF.1" + ${PAHOLE} -J ${PAHOLE_FLAGS} \ + ${BTF_BASE:+--btf_base ${BTF_BASE}} \ + --btf_encode_detached=${btf1} \ + "${ELF_FILE}" + + ${RESOLVE_BTFIDS} ${RESOLVE_BTFIDS_FLAGS} \ + ${BTF_BASE:+--btf_base ${BTF_BASE}} \ + --btf ${btf1} "${ELF_FILE}" +} + +gen_btf_o() +{ + btf_data=${ELF_FILE}.btf.o + + # 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. + echo "" | ${CC} ${CLANG_FLAGS} ${KBUILD_CPPFLAGS} ${KBUILD_CFLAGS} -fno-lto -c -x c -o ${btf_data} - + ${OBJCOPY} --add-section .BTF=${ELF_FILE}.BTF \ + --set-section-flags .BTF=alloc,readonly ${btf_data} + ${OBJCOPY} --only-section=.BTF --strip-all ${btf_data} + + # 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 + et_rel='\0\1' + else + et_rel='\1\0' + fi + printf "${et_rel}" | dd of="${btf_data}" conv=notrunc bs=1 seek=16 status=none +} + +embed_btf_data() +{ + ${OBJCOPY} --add-section .BTF=${ELF_FILE}.BTF ${ELF_FILE} + + # a module might not have a .BTF_ids or .BTF.base section + btf_base="${ELF_FILE}.BTF.base" + if [ -f "${btf_base}" ]; then + ${OBJCOPY} --add-section .BTF.base=${btf_base} ${ELF_FILE} + fi + btf_ids="${ELF_FILE}.BTF_ids" + if [ -f "${btf_ids}" ]; then + ${RESOLVE_BTFIDS} --patch_btfids ${btf_ids} ${ELF_FILE} + fi +} + +cleanup() +{ + rm -f "${ELF_FILE}.BTF.1" + rm -f "${ELF_FILE}.BTF" + if [ "${BTFGEN_MODE}" = "module" ]; then + rm -f "${ELF_FILE}.BTF.base" + rm -f "${ELF_FILE}.BTF_ids" + fi +} +trap cleanup EXIT + +BTFGEN_MODE="vmlinux" +if [ -n "${BTF_BASE}" ]; then + BTFGEN_MODE="module" +fi + +gen_btf_data + +case "${BTFGEN_MODE}" in +vmlinux) + gen_btf_o + ;; +module) + embed_btf_data + ;; +esac + +exit 0 diff --git a/scripts/gen-crc-consts.py b/scripts/gen-crc-consts.py new file mode 100755 index 000000000..f9b44fc3a --- /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-randstruct-seed.sh b/scripts/gen-randstruct-seed.sh new file mode 100755 index 000000000..61017b36c --- /dev/null +++ b/scripts/gen-randstruct-seed.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +SEED=$(od -A n -t x8 -N 32 /dev/urandom | tr -d ' \n') +echo "$SEED" > "$1" +HASH=$(echo -n "$SEED" | sha256sum | cut -d" " -f1) +echo "#define RANDSTRUCT_HASHED_SEED \"$HASH\"" > "$2" diff --git a/scripts/gen_packed_field_checks.c b/scripts/gen_packed_field_checks.c new file mode 100644 index 000000000..60042b761 --- /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 000000000..0927f8d3c --- /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 000000000..6334c7d3c --- /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 000000000..1c640db93 --- /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(*ci)); + 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 000000000..052f7a3f9 --- /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(*cd)); + 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(*df)); + 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 000000000..e76d732f5 --- /dev/null +++ b/scripts/gendwarfksyms/dwarf.c @@ -0,0 +1,1185 @@ +// 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(state.kabi)); + + 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; + unsigned long override; + 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, &override); + value = override; + } + + 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 000000000..170733a3f --- /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 000000000..1f799eb7c --- /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 000000000..785b211d9 --- /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 000000000..88bc1bd60 --- /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 000000000..f5203d164 --- /dev/null +++ b/scripts/gendwarfksyms/gendwarfksyms.c @@ -0,0 +1,188 @@ +// 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?"); + } + + if (!symbol_read_exports(stdin)) + return 0; + + 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 000000000..32cec8f76 --- /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); +int 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 000000000..e3c2a3ccf --- /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(*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 000000000..42cd27c9c --- /dev/null +++ b/scripts/gendwarfksyms/symbols.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#include <inttypes.h> +#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; +} + +int 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(*sym)); + 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); + + return 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 (%" PRIu64 ") 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, %" PRIx64 " }", 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 000000000..9c3b053bf --- /dev/null +++ b/scripts/gendwarfksyms/types.c @@ -0,0 +1,589 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Google LLC + */ + +#define _GNU_SOURCE +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.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(*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(*e)); + 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 int cmp_expansion_name(const void *p1, const void *p2) +{ + struct type_expansion *const *e1 = p1; + struct type_expansion *const *e2 = p2; + + return strcmp((*e1)->name, (*e2)->name); +} + +static void type_map_write(FILE *file) +{ + struct type_expansion *e; + struct hlist_node *tmp; + struct type_expansion **es; + size_t count = 0; + size_t i = 0; + + if (!file) + return; + + hash_for_each_safe(type_map, e, tmp, hash) + ++count; + es = xmalloc(count * sizeof(*es)); + hash_for_each_safe(type_map, e, tmp, hash) + es[i++] = e; + + qsort(es, count, sizeof(*es), cmp_expansion_name); + + for (i = 0; i < count; ++i) { + checkp(fputs(es[i]->name, file)); + checkp(fputs(" ", file)); + type_list_write(&es[i]->expanded, file); + checkp(fputs("\n", file)); + } + + free(es); +} + +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 000000000..d4bd5c2b9 --- /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_initcall_order.pl b/scripts/generate_initcall_order.pl new file mode 100755 index 000000000..1a88d3f1b --- /dev/null +++ b/scripts/generate_initcall_order.pl @@ -0,0 +1,270 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# Generates a linker script that specifies the correct initcall order. +# +# Copyright (C) 2019 Google LLC + +use strict; +use warnings; +use IO::Handle; +use IO::Select; +use POSIX ":sys_wait_h"; + +my $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?"; +my $objtree = $ENV{'objtree'} || '.'; + +## currently active child processes +my $jobs = {}; # child process pid -> file handle +## results from child processes +my $results = {}; # object index -> [ { level, secname }, ... ] + +## reads _NPROCESSORS_ONLN to determine the maximum number of processes to +## start +sub get_online_processors { + open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |") + or die "$0: ERROR: failed to execute getconf: $!"; + my $procs = <$fh>; + close($fh); + + if (!($procs =~ /^\d+$/)) { + return 1; + } + + return int($procs); +} + +## writes results to the parent process +## format: <file index> <initcall level> <base initcall section name> +sub write_results { + my ($index, $initcalls) = @_; + + # sort by the counter value to ensure the order of initcalls within + # each object file is correct + foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) { + my $level = $initcalls->{$counter}->{'level'}; + + # section name for the initcall function + my $secname = $initcalls->{$counter}->{'module'} . '__' . + $counter . '_' . + $initcalls->{$counter}->{'line'} . '_' . + $initcalls->{$counter}->{'function'}; + + print "$index $level $secname\n"; + } +} + +## reads a result line from a child process and adds it to the $results array +sub read_results{ + my ($fh) = @_; + + # each child prints out a full line w/ autoflush and exits after the + # last line, so even if buffered I/O blocks here, it shouldn't block + # very long + my $data = <$fh>; + + if (!defined($data)) { + return 0; + } + + chomp($data); + + my ($index, $level, $secname) = $data =~ + /^(\d+)\ ([^\ ]+)\ (.*)$/; + + if (!defined($index) || + !defined($level) || + !defined($secname)) { + die "$0: ERROR: child process returned invalid data: $data\n"; + } + + $index = int($index); + + if (!exists($results->{$index})) { + $results->{$index} = []; + } + + push (@{$results->{$index}}, { + 'level' => $level, + 'secname' => $secname + }); + + return 1; +} + +## finds initcalls from an object file or all object files in an archive, and +## writes results back to the parent process +sub find_initcalls { + my ($index, $file) = @_; + + die "$0: ERROR: file $file doesn't exist?" if (! -f $file); + + open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |") + or die "$0: ERROR: failed to execute \"$nm\": $!"; + + my $initcalls = {}; + + while (<$fh>) { + chomp; + + # check for the start of a new object file (if processing an + # archive) + my ($path)= $_ =~ /^(.+)\:$/; + + if (defined($path)) { + write_results($index, $initcalls); + $initcalls = {}; + next; + } + + # look for an initcall + my ($module, $counter, $line, $symbol) = $_ =~ + /[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/; + + if (!defined($module)) { + $module = '' + } + + if (!defined($counter) || + !defined($line) || + !defined($symbol)) { + next; + } + + # parse initcall level + my ($function, $level) = $symbol =~ + /^(.*)((early|rootfs|con|[0-9])s?)$/; + + die "$0: ERROR: invalid initcall name $symbol in $file($path)" + if (!defined($function) || !defined($level)); + + $initcalls->{$counter} = { + 'module' => $module, + 'line' => $line, + 'function' => $function, + 'level' => $level, + }; + } + + close($fh); + write_results($index, $initcalls); +} + +## waits for any child process to complete, reads the results, and adds them to +## the $results array for later processing +sub wait_for_results { + my ($select) = @_; + + my $pid = 0; + do { + # unblock children that may have a full write buffer + foreach my $fh ($select->can_read(0)) { + read_results($fh); + } + + # check for children that have exited, read the remaining data + # from them, and clean up + $pid = waitpid(-1, WNOHANG); + if ($pid > 0) { + if (!exists($jobs->{$pid})) { + next; + } + + my $fh = $jobs->{$pid}; + $select->remove($fh); + + while (read_results($fh)) { + # until eof + } + + close($fh); + delete($jobs->{$pid}); + } + } while ($pid > 0); +} + +## forks a child to process each file passed in the command line and collects +## the results +sub process_files { + my $index = 0; + my $njobs = $ENV{'PARALLELISM'} || get_online_processors(); + my $select = IO::Select->new(); + + while (my $file = shift(@ARGV)) { + # fork a child process and read it's stdout + my $pid = open(my $fh, '-|'); + + if (!defined($pid)) { + die "$0: ERROR: failed to fork: $!"; + } elsif ($pid) { + # save the child process pid and the file handle + $select->add($fh); + $jobs->{$pid} = $fh; + } else { + # in the child process + STDOUT->autoflush(1); + find_initcalls($index, "$objtree/$file"); + exit; + } + + $index++; + + # limit the number of children to $njobs + if (scalar(keys(%{$jobs})) >= $njobs) { + wait_for_results($select); + } + } + + # wait for the remaining children to complete + while (scalar(keys(%{$jobs})) > 0) { + wait_for_results($select); + } +} + +sub generate_initcall_lds() { + process_files(); + + my $sections = {}; # level -> [ secname, ...] + + # sort results to retain link order and split to sections per + # initcall level + foreach my $index (sort { $a <=> $b } keys(%{$results})) { + foreach my $result (@{$results->{$index}}) { + my $level = $result->{'level'}; + + if (!exists($sections->{$level})) { + $sections->{$level} = []; + } + + push(@{$sections->{$level}}, $result->{'secname'}); + } + } + + die "$0: ERROR: no initcalls?" if (!keys(%{$sections})); + + # print out a linker script that defines the order of initcalls for + # each level + print "SECTIONS {\n"; + + foreach my $level (sort(keys(%{$sections}))) { + my $section; + + if ($level eq 'con') { + $section = '.con_initcall.init'; + } else { + $section = ".initcall${level}.init"; + } + + print "\t${section} : {\n"; + + foreach my $secname (@{$sections->{$level}}) { + print "\t\t*(${section}..${secname}) ;\n"; + } + + print "\t}\n"; + } + + print "}\n"; +} + +generate_initcall_lds(); diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py new file mode 100755 index 000000000..dc1219736 --- /dev/null +++ b/scripts/generate_rust_analyzer.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +"""generate_rust_analyzer - Generates the `rust-project.json` file for `rust-analyzer`. +""" + +import argparse +import json +import logging +import os +import pathlib +import subprocess +import sys +from typing import Dict, Iterable, List, Literal, Optional, TypedDict + +def invoke_rustc(args: List[str]) -> str: + return subprocess.check_output( + [os.environ["RUSTC"]] + args, + stdin=subprocess.DEVNULL, + ).decode('utf-8').strip() + +def args_crates_cfgs(cfgs: List[str]) -> Dict[str, List[str]]: + crates_cfgs = {} + for cfg in cfgs: + crate, vals = cfg.split("=", 1) + crates_cfgs[crate] = vals.split() + + return crates_cfgs + +def args_crates_envs(envs: List[str]) -> Dict[str, Dict[str, str]]: + crates_envs = {} + for env in envs: + crate, vals = env.split("=", 1) + crates_envs[crate] = dict(v.split("=", 1) for v in vals.split()) + + return crates_envs + +class Dependency(TypedDict): + crate: int + name: str + + +class Source(TypedDict): + include_dirs: List[str] + exclude_dirs: List[str] + + +class Crate(TypedDict): + display_name: str + root_module: str + is_workspace_member: bool + deps: List[Dependency] + cfg: List[str] + edition: str + env: Dict[str, str] + + +class ProcMacroCrate(Crate): + is_proc_macro: Literal[True] + proc_macro_dylib_path: str # `pathlib.Path` is not JSON serializable. + + +class CrateWithGenerated(Crate): + source: Source + + +def generate_crates( + srctree: pathlib.Path, + objtree: pathlib.Path, + sysroot_src: pathlib.Path, + external_src: Optional[pathlib.Path], + cfgs: List[str], + envs: List[str], + core_edition: str, +) -> List[Crate]: + # Generate the configuration list. + generated_cfg = [] + with open(objtree / "include" / "generated" / "rustc_cfg") as fd: + for line in fd: + line = line.replace("--cfg=", "") + line = line.replace("\n", "") + generated_cfg.append(line) + + # Now fill the crates list. + crates: List[Crate] = [] + crates_cfgs = args_crates_cfgs(cfgs) + crates_envs = args_crates_envs(envs) + + def get_crate_name(path: pathlib.Path) -> str: + return invoke_rustc(["--print", "crate-name", str(path)]) + + def build_crate( + display_name: str, + root_module: pathlib.Path, + deps: List[Dependency], + *, + cfg: Optional[List[str]], + is_workspace_member: Optional[bool], + edition: Optional[str], + ) -> Crate: + cfg = cfg if cfg is not None else crates_cfgs.get(display_name, []) + is_workspace_member = ( + is_workspace_member if is_workspace_member is not None else True + ) + edition = edition if edition is not None else "2021" + crate_env = { + "RUST_MODFILE": "This is only for rust-analyzer", + **crates_envs.get(display_name, {}), + } + return { + "display_name": display_name, + "root_module": str(root_module), + "is_workspace_member": is_workspace_member, + "deps": deps, + "cfg": cfg, + "edition": edition, + "env": crate_env, + } + + def append_proc_macro_crate( + display_name: str, + root_module: pathlib.Path, + deps: List[Dependency], + *, + cfg: Optional[List[str]] = None, + is_workspace_member: Optional[bool] = None, + edition: Optional[str] = None, + ) -> Dependency: + crate = build_crate( + display_name, + root_module, + deps, + cfg=cfg, + is_workspace_member=is_workspace_member, + edition=edition, + ) + proc_macro_dylib_name = invoke_rustc([ + "--print", + "file-names", + "--crate-name", + display_name, + "--crate-type", + "proc-macro", + "-", + ]) + proc_macro_crate: ProcMacroCrate = { + **crate, + "is_proc_macro": True, + "proc_macro_dylib_path": str(objtree / "rust" / proc_macro_dylib_name), + } + return register_crate(proc_macro_crate) + + def register_crate(crate: Crate) -> Dependency: + index = len(crates) + crates.append(crate) + return {"crate": index, "name": crate["display_name"]} + + def append_crate( + display_name: str, + root_module: pathlib.Path, + deps: List[Dependency], + *, + cfg: Optional[List[str]] = None, + is_workspace_member: Optional[bool] = None, + edition: Optional[str] = None, + ) -> Dependency: + return register_crate( + build_crate( + display_name, + root_module, + deps, + cfg=cfg, + is_workspace_member=is_workspace_member, + edition=edition, + ) + ) + + def append_sysroot_crate( + display_name: str, + deps: List[Dependency], + *, + cfg: Optional[List[str]] = None, + ) -> Dependency: + return append_crate( + display_name, + sysroot_src / display_name / "src" / "lib.rs", + deps, + cfg=cfg, + is_workspace_member=False, + # Miguel Ojeda writes: + # + # > ... in principle even the sysroot crates may have different + # > editions. + # > + # > For instance, in the move to 2024, it seems all happened at once + # > in 1.87.0 in these upstream commits: + # > + # > 0e071c2c6a58 ("Migrate core to Rust 2024") + # > f505d4e8e380 ("Migrate alloc to Rust 2024") + # > 0b2489c226c3 ("Migrate proc_macro to Rust 2024") + # > 993359e70112 ("Migrate std to Rust 2024") + # > + # > But in the previous move to 2021, `std` moved in 1.59.0, while + # > the others in 1.60.0: + # > + # > b656384d8398 ("Update stdlib to the 2021 edition") + # > 06a1c14d52a8 ("Switch all libraries to the 2021 edition") + # + # Link: https://lore.kernel.org/all/CANiq72kd9bHdKaAm=8xCUhSHMy2csyVed69bOc4dXyFAW4sfuw@mail.gmail.com/ + # + # At the time of writing all rust versions we support build the + # sysroot crates with the same edition. We may need to relax this + # assumption if future edition moves span multiple rust versions. + edition=core_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)`. + core = append_sysroot_crate("core", []) + alloc = append_sysroot_crate("alloc", [core]) + std = append_sysroot_crate("std", [alloc, core]) + proc_macro = append_sysroot_crate("proc_macro", [core, std]) + + compiler_builtins = append_crate( + "compiler_builtins", + srctree / "rust" / "compiler_builtins.rs", + [core], + ) + + proc_macro2 = append_crate( + "proc_macro2", + srctree / "rust" / "proc-macro2" / "lib.rs", + [core, alloc, std, proc_macro], + ) + + quote = append_crate( + "quote", + srctree / "rust" / "quote" / "lib.rs", + [core, alloc, std, proc_macro, proc_macro2], + edition="2018", + ) + + syn = append_crate( + "syn", + srctree / "rust" / "syn" / "lib.rs", + [std, proc_macro, proc_macro2, quote], + ) + + macros = append_proc_macro_crate( + "macros", + srctree / "rust" / "macros" / "lib.rs", + [std, proc_macro, proc_macro2, quote, syn], + ) + + zerocopy_derive = append_proc_macro_crate( + "zerocopy_derive", + srctree / "rust" / "zerocopy-derive" / "lib.rs", + [std, proc_macro, proc_macro2, quote, syn], + ) + + build_error = append_crate( + "build_error", + srctree / "rust" / "build_error.rs", + [core, compiler_builtins], + ) + + pin_init_internal = append_proc_macro_crate( + "pin_init_internal", + srctree / "rust" / "pin-init" / "internal" / "src" / "lib.rs", + [std, proc_macro, proc_macro2, quote, syn], + ) + + pin_init = append_crate( + "pin_init", + srctree / "rust" / "pin-init" / "src" / "lib.rs", + [core, compiler_builtins, pin_init_internal, macros], + ) + + ffi = append_crate( + "ffi", + srctree / "rust" / "ffi.rs", + [core, compiler_builtins], + ) + + zerocopy = append_crate( + "zerocopy", + srctree / "rust" / "zerocopy" / "src" / "lib.rs", + [core, compiler_builtins], + ) + + def append_crate_with_generated( + display_name: str, + deps: List[Dependency], + ) -> Dependency: + crate = build_crate( + display_name, + srctree / "rust"/ display_name / "lib.rs", + deps, + cfg=generated_cfg, + is_workspace_member=True, + edition=None, + ) + crate["env"]["OBJTREE"] = str(objtree.resolve(True)) + crate_with_generated: CrateWithGenerated = { + **crate, + "source": { + "include_dirs": [ + str(srctree / "rust" / display_name), + str(objtree / "rust"), + ], + "exclude_dirs": [], + }, + } + return register_crate(crate_with_generated) + + bindings = append_crate_with_generated("bindings", [core, ffi, pin_init]) + uapi = append_crate_with_generated("uapi", [core, ffi, pin_init]) + kernel = append_crate_with_generated( + "kernel", [core, macros, build_error, pin_init, ffi, bindings, uapi, zerocopy, zerocopy_derive] + ) + + scripts = srctree / "scripts" + makefile = (scripts / "Makefile").read_text() + for path in scripts.glob("*.rs"): + name = path.stem + if f"{name}-rust" not in makefile: + continue + append_crate( + name, + path, + [std], + ) + + def is_root_crate(build_file: pathlib.Path, target: str) -> bool: + try: + contents = build_file.read_text() + except FileNotFoundError: + return False + return f"{target}.o" in contents + + # Then, the rest outside of `rust/`. + # + # We explicitly mention the top-level folders we want to cover. + extra_dirs: Iterable[pathlib.Path] = ( + srctree / dir for dir in ("samples", "drivers") + ) + if external_src is not None: + extra_dirs = [external_src] + for folder in extra_dirs: + for path in folder.rglob("*.rs"): + logging.info("Checking %s", path) + file_name = path.stem + + # Skip those that are not crate roots. + if not is_root_crate(path.parent / "Makefile", file_name) and \ + not is_root_crate(path.parent / "Kbuild", file_name): + continue + + crate_name = get_crate_name(path) + logging.info("Adding %s", crate_name) + append_crate( + crate_name, + path, + [core, kernel, pin_init, zerocopy, zerocopy_derive], + cfg=generated_cfg, + ) + + return crates + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument('--verbose', '-v', action='store_true') + parser.add_argument('--cfgs', action='append', default=[]) + parser.add_argument('--envs', action='append', default=[]) + parser.add_argument("core_edition") + parser.add_argument("srctree", type=pathlib.Path) + parser.add_argument("objtree", type=pathlib.Path) + parser.add_argument("sysroot", type=pathlib.Path) + parser.add_argument("sysroot_src", type=pathlib.Path) + parser.add_argument("exttree", type=pathlib.Path, nargs="?") + + class Args(argparse.Namespace): + verbose: bool + cfgs: List[str] + envs: List[str] + srctree: pathlib.Path + objtree: pathlib.Path + sysroot: pathlib.Path + sysroot_src: pathlib.Path + exttree: Optional[pathlib.Path] + core_edition: str + + args = parser.parse_args(namespace=Args()) + + logging.basicConfig( + format="[%(asctime)s] [%(levelname)s] %(message)s", + level=logging.INFO if args.verbose else logging.WARNING + ) + + rust_project = { + "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs, args.envs, args.core_edition), + "sysroot": str(args.sysroot), + } + + json.dump(rust_project, sys.stdout, sort_keys=True, indent=4) + +if __name__ == "__main__": + main() diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs new file mode 100644 index 000000000..7687b0dd5 --- /dev/null +++ b/scripts/generate_rust_target.rs @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! The custom target specification file generator for `rustc`. +//! +//! To configure a target from scratch, a JSON-encoded file has to be passed +//! to `rustc` (introduced in [RFC 131]). These options and the file itself are +//! unstable. Eventually, `rustc` should provide a way to do this in a stable +//! manner. For instance, via command-line arguments. Therefore, this file +//! should avoid using keys which can be set via `-C` or `-Z` options. +//! +//! [RFC 131]: https://rust-lang.github.io/rfcs/0131-target-specification.html + +use std::{ + collections::HashMap, + fmt::{Display, Formatter, Result}, + io::BufRead, +}; + +enum Value { + Boolean(bool), + Number(i32), + String(String), + Array(Vec<Value>), + Object(Object), +} + +type Object = Vec<(String, Value)>; + +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 { + match self { + 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("{")?; + comma_sep(&object[..], formatter, |formatter, v| { + write!(formatter, "\"{}\": {}", v.0, v.1) + })?; + formatter.write_str("}") + } + } + } +} + +impl From<bool> for Value { + fn from(value: bool) -> Self { + Self::Boolean(value) + } +} + +impl From<i32> for Value { + fn from(value: i32) -> Self { + Self::Number(value) + } +} + +impl From<String> for Value { + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Self::String(value.to_string()) + } +} + +impl From<Object> for Value { + fn from(object: Object) -> Self { + Self::Object(object) + } +} + +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()) + } +} + +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())); + } +} + +impl Display for TargetSpec { + fn fmt(&self, formatter: &mut Formatter<'_>) -> Result { + // We add some newlines for clarity. + formatter.write_str("{\n")?; + if let [ref rest @ .., ref last] = self.0[..] { + for (key, value) in rest { + write!(formatter, " \"{}\": {},\n", key, value)?; + } + write!(formatter, " \"{}\": {}\n", last.0, last.1)?; + } + formatter.write_str("}") + } +} + +struct KernelConfig(HashMap<String, String>); + +impl KernelConfig { + /// Parses `include/config/auto.conf` from `stdin`. + fn from_stdin() -> KernelConfig { + let mut result = HashMap::new(); + + let stdin = std::io::stdin(); + let mut handle = stdin.lock(); + let mut line = String::new(); + + loop { + line.clear(); + + if handle.read_line(&mut line).unwrap() == 0 { + break; + } + + if line.starts_with('#') { + continue; + } + + let (key, value) = line.split_once('=').expect("Missing `=` in line."); + result.insert(key.to_string(), value.trim_end_matches('\n').to_string()); + } + + KernelConfig(result) + } + + /// Does the option exist in the configuration (any value)? + /// + /// The argument must be passed without the `CONFIG_` prefix. + /// This avoids repetition and it also avoids `fixdep` making us + /// depend on it. + fn has(&self, option: &str) -> bool { + 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() { + let cfg = KernelConfig::from_stdin(); + let mut ts = TargetSpec::new(); + + // `llvm-target`s are taken from `scripts/Makefile.clang`. + 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, 98, 0) { + ts.push("rustc-abi", "softfloat"); + } else 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-i128:128-f80:128-n8:16:32:64-S128", + ); + 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"; + } + if cfg.has("X86_NATIVE_CPU") { + // Prevent the backend from generating APX instructions. The kernel is not yet prepared + // for general in-kernel EGPR use. + features += ",-apxf"; + } + ts.push("features", features); + ts.push("llvm-target", "x86_64-linux-gnu"); + ts.push("supported-sanitizers", ["kcfi", "kernel-address"]); + if cfg.rustc_version_atleast(1, 91, 0) { + ts.push("target-pointer-width", 64); + } else { + 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, 98, 0) { + ts.push("rustc-abi", "softfloat"); + } else 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"); + if cfg.rustc_version_atleast(1, 91, 0) { + ts.push("target-pointer-width", 32); + } else { + ts.push("target-pointer-width", "32"); + } + } else if cfg.has("LOONGARCH") { + panic!("loongarch uses the builtin rustc loongarch64-unknown-none-softfloat target"); + } else if cfg.has("S390") { + panic!("s390 uses the builtin rustc s390x-unknown-none-softfloat target"); + } else { + panic!("Unsupported architecture"); + } + + ts.push("emit-debug-gdb-scripts", false); + ts.push("frame-pointer", "may-omit"); + ts.push( + "stack-probes", + vec![("kind".to_string(), Value::String("none".to_string()))], + ); + + // Everything else is LE, whether `CPU_LITTLE_ENDIAN` is declared or not + // (e.g. x86). It is also `rustc`'s default. + if cfg.has("CPU_BIG_ENDIAN") { + ts.push("target-endian", "big"); + } + + println!("{}", ts); +} diff --git a/scripts/genksyms/.gitignore b/scripts/genksyms/.gitignore new file mode 100644 index 000000000..0b275abf9 --- /dev/null +++ b/scripts/genksyms/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +/genksyms diff --git a/scripts/genksyms/Makefile b/scripts/genksyms/Makefile new file mode 100644 index 000000000..4350311fb --- /dev/null +++ b/scripts/genksyms/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 + +hostprogs-always-y += genksyms + +genksyms-objs := genksyms.o parse.tab.o lex.lex.o + +# -I needed for generated C source to include headers in source tree +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 new file mode 100644 index 000000000..83e48670c --- /dev/null +++ b/scripts/genksyms/genksyms.c @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Generate kernel symbol version hashes. + Copyright 1996, 1997 Linux International. + + New implementation contributed by Richard Henderson <rth@tamu.edu> + Based on original work by Bjorn Ekwall <bj0rn@blox.se> + + This file was part of the Linux modutils 2.4.22: moved back into the + kernel sources by Rusty Russell/Kai Germaschewski. + + */ + +#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" +/*----------------------------------------------------------------------*/ + +static HASHTABLE_DEFINE(symbol_hashtable, 1U << 12); +static FILE *debugfile; + +int cur_line = 1; +char *cur_filename; +int in_source_file; + +static int flag_debug, flag_dump_defs, flag_reference, flag_dump_types, + flag_preserve, flag_warnings; + +static int errors; +static int nsyms; + +static struct symbol *expansion_trail; +static struct symbol *visited_symbols; + +static const struct { + int n; + const char *name; +} symbol_types[] = { + [SYM_NORMAL] = { 0, NULL}, + [SYM_TYPEDEF] = {'t', "typedef"}, + [SYM_ENUM] = {'e', "enum"}, + [SYM_STRUCT] = {'s', "struct"}, + [SYM_UNION] = {'u', "union"}, + [SYM_ENUM_CONST] = {'E', "enum constant"}, +}; + +static int equal_list(struct string_list *a, struct string_list *b); +static void print_list(FILE * f, struct string_list *list); +static struct string_list *concat_list(struct string_list *start, ...); +static struct string_list *mk_node(const char *string); +static void print_location(void); +static void print_type_name(enum symbol_type type, const char *name); + +/*----------------------------------------------------------------------*/ + +static const uint32_t crctab32[] = { + 0x00000000U, 0x77073096U, 0xee0e612cU, 0x990951baU, 0x076dc419U, + 0x706af48fU, 0xe963a535U, 0x9e6495a3U, 0x0edb8832U, 0x79dcb8a4U, + 0xe0d5e91eU, 0x97d2d988U, 0x09b64c2bU, 0x7eb17cbdU, 0xe7b82d07U, + 0x90bf1d91U, 0x1db71064U, 0x6ab020f2U, 0xf3b97148U, 0x84be41deU, + 0x1adad47dU, 0x6ddde4ebU, 0xf4d4b551U, 0x83d385c7U, 0x136c9856U, + 0x646ba8c0U, 0xfd62f97aU, 0x8a65c9ecU, 0x14015c4fU, 0x63066cd9U, + 0xfa0f3d63U, 0x8d080df5U, 0x3b6e20c8U, 0x4c69105eU, 0xd56041e4U, + 0xa2677172U, 0x3c03e4d1U, 0x4b04d447U, 0xd20d85fdU, 0xa50ab56bU, + 0x35b5a8faU, 0x42b2986cU, 0xdbbbc9d6U, 0xacbcf940U, 0x32d86ce3U, + 0x45df5c75U, 0xdcd60dcfU, 0xabd13d59U, 0x26d930acU, 0x51de003aU, + 0xc8d75180U, 0xbfd06116U, 0x21b4f4b5U, 0x56b3c423U, 0xcfba9599U, + 0xb8bda50fU, 0x2802b89eU, 0x5f058808U, 0xc60cd9b2U, 0xb10be924U, + 0x2f6f7c87U, 0x58684c11U, 0xc1611dabU, 0xb6662d3dU, 0x76dc4190U, + 0x01db7106U, 0x98d220bcU, 0xefd5102aU, 0x71b18589U, 0x06b6b51fU, + 0x9fbfe4a5U, 0xe8b8d433U, 0x7807c9a2U, 0x0f00f934U, 0x9609a88eU, + 0xe10e9818U, 0x7f6a0dbbU, 0x086d3d2dU, 0x91646c97U, 0xe6635c01U, + 0x6b6b51f4U, 0x1c6c6162U, 0x856530d8U, 0xf262004eU, 0x6c0695edU, + 0x1b01a57bU, 0x8208f4c1U, 0xf50fc457U, 0x65b0d9c6U, 0x12b7e950U, + 0x8bbeb8eaU, 0xfcb9887cU, 0x62dd1ddfU, 0x15da2d49U, 0x8cd37cf3U, + 0xfbd44c65U, 0x4db26158U, 0x3ab551ceU, 0xa3bc0074U, 0xd4bb30e2U, + 0x4adfa541U, 0x3dd895d7U, 0xa4d1c46dU, 0xd3d6f4fbU, 0x4369e96aU, + 0x346ed9fcU, 0xad678846U, 0xda60b8d0U, 0x44042d73U, 0x33031de5U, + 0xaa0a4c5fU, 0xdd0d7cc9U, 0x5005713cU, 0x270241aaU, 0xbe0b1010U, + 0xc90c2086U, 0x5768b525U, 0x206f85b3U, 0xb966d409U, 0xce61e49fU, + 0x5edef90eU, 0x29d9c998U, 0xb0d09822U, 0xc7d7a8b4U, 0x59b33d17U, + 0x2eb40d81U, 0xb7bd5c3bU, 0xc0ba6cadU, 0xedb88320U, 0x9abfb3b6U, + 0x03b6e20cU, 0x74b1d29aU, 0xead54739U, 0x9dd277afU, 0x04db2615U, + 0x73dc1683U, 0xe3630b12U, 0x94643b84U, 0x0d6d6a3eU, 0x7a6a5aa8U, + 0xe40ecf0bU, 0x9309ff9dU, 0x0a00ae27U, 0x7d079eb1U, 0xf00f9344U, + 0x8708a3d2U, 0x1e01f268U, 0x6906c2feU, 0xf762575dU, 0x806567cbU, + 0x196c3671U, 0x6e6b06e7U, 0xfed41b76U, 0x89d32be0U, 0x10da7a5aU, + 0x67dd4accU, 0xf9b9df6fU, 0x8ebeeff9U, 0x17b7be43U, 0x60b08ed5U, + 0xd6d6a3e8U, 0xa1d1937eU, 0x38d8c2c4U, 0x4fdff252U, 0xd1bb67f1U, + 0xa6bc5767U, 0x3fb506ddU, 0x48b2364bU, 0xd80d2bdaU, 0xaf0a1b4cU, + 0x36034af6U, 0x41047a60U, 0xdf60efc3U, 0xa867df55U, 0x316e8eefU, + 0x4669be79U, 0xcb61b38cU, 0xbc66831aU, 0x256fd2a0U, 0x5268e236U, + 0xcc0c7795U, 0xbb0b4703U, 0x220216b9U, 0x5505262fU, 0xc5ba3bbeU, + 0xb2bd0b28U, 0x2bb45a92U, 0x5cb36a04U, 0xc2d7ffa7U, 0xb5d0cf31U, + 0x2cd99e8bU, 0x5bdeae1dU, 0x9b64c2b0U, 0xec63f226U, 0x756aa39cU, + 0x026d930aU, 0x9c0906a9U, 0xeb0e363fU, 0x72076785U, 0x05005713U, + 0x95bf4a82U, 0xe2b87a14U, 0x7bb12baeU, 0x0cb61b38U, 0x92d28e9bU, + 0xe5d5be0dU, 0x7cdcefb7U, 0x0bdbdf21U, 0x86d3d2d4U, 0xf1d4e242U, + 0x68ddb3f8U, 0x1fda836eU, 0x81be16cdU, 0xf6b9265bU, 0x6fb077e1U, + 0x18b74777U, 0x88085ae6U, 0xff0f6a70U, 0x66063bcaU, 0x11010b5cU, + 0x8f659effU, 0xf862ae69U, 0x616bffd3U, 0x166ccf45U, 0xa00ae278U, + 0xd70dd2eeU, 0x4e048354U, 0x3903b3c2U, 0xa7672661U, 0xd06016f7U, + 0x4969474dU, 0x3e6e77dbU, 0xaed16a4aU, 0xd9d65adcU, 0x40df0b66U, + 0x37d83bf0U, 0xa9bcae53U, 0xdebb9ec5U, 0x47b2cf7fU, 0x30b5ffe9U, + 0xbdbdf21cU, 0xcabac28aU, 0x53b39330U, 0x24b4a3a6U, 0xbad03605U, + 0xcdd70693U, 0x54de5729U, 0x23d967bfU, 0xb3667a2eU, 0xc4614ab8U, + 0x5d681b02U, 0x2a6f2b94U, 0xb40bbe37U, 0xc30c8ea1U, 0x5a05df1bU, + 0x2d02ef8dU +}; + +static uint32_t partial_crc32_one(uint8_t c, uint32_t crc) +{ + return crctab32[(crc ^ c) & 0xff] ^ (crc >> 8); +} + +static uint32_t partial_crc32(const char *s, uint32_t crc) +{ + while (*s) + crc = partial_crc32_one(*s++, crc); + return crc; +} + +static uint32_t crc32(const char *s) +{ + return partial_crc32(s, 0xffffffff) ^ 0xffffffff; +} + +/*----------------------------------------------------------------------*/ + +static enum symbol_type map_to_ns(enum symbol_type t) +{ + switch (t) { + case SYM_ENUM_CONST: + case SYM_NORMAL: + case SYM_TYPEDEF: + return SYM_NORMAL; + case SYM_ENUM: + case SYM_STRUCT: + case SYM_UNION: + return SYM_STRUCT; + } + return t; +} + +struct symbol *find_symbol(const char *name, enum symbol_type ns, int exact) +{ + struct symbol *sym; + + 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; + return sym; +} + +static int is_unknown_symbol(struct symbol *sym) +{ + struct string_list *defn; + + return ((sym->type == SYM_STRUCT || + sym->type == SYM_UNION || + sym->type == SYM_ENUM) && + (defn = sym->defn) && defn->tag == SYM_NORMAL && + strcmp(defn->string, "}") == 0 && + (defn = defn->next) && defn->tag == SYM_NORMAL && + strcmp(defn->string, "UNKNOWN") == 0 && + (defn = defn->next) && defn->tag == SYM_NORMAL && + strcmp(defn->string, "{") == 0); +} + +static struct string_list *process_enum(const char *name, enum symbol_type type, + struct string_list *defn) +{ + /* 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. + */ + static int enum_counter; + static struct string_list *last_enum_expr; + + if (type == SYM_ENUM_CONST) { + if (defn) { + free_list(last_enum_expr, NULL); + last_enum_expr = copy_list_range(defn, NULL); + enum_counter = 1; + } else { + struct string_list *expr; + char buf[20]; + + snprintf(buf, sizeof(buf), "%d", enum_counter++); + if (last_enum_expr) { + expr = copy_list_range(last_enum_expr, NULL); + defn = concat_list(mk_node("("), + expr, + mk_node(")"), + mk_node("+"), + mk_node(buf), NULL); + } else { + defn = mk_node(buf); + } + } + } else { + free_list(last_enum_expr, NULL); + last_enum_expr = NULL; + enum_counter = 0; + if (!name) + /* Anonymous enum definition, nothing more to do */ + return NULL; + } + + 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) { + hash_del(&sym->hnode); + + free_list(sym->defn, NULL); + free(sym->name); + free(sym); + --nsyms; + } + + sym = xmalloc(sizeof(*sym)); + sym->name = xstrdup(name); + sym->type = type; + sym->defn = defn; + sym->expansion_trail = NULL; + sym->visited = NULL; + sym->is_extern = is_extern; + + hash_add(symbol_hashtable, &sym->hnode, h); + + sym->is_declared = !is_reference; + sym->status = status; + sym->is_override = 0; + + if (flag_debug) { + if (symbol_types[type].name) + fprintf(debugfile, "Defn for %s %s == <", + symbol_types[type].name, name); + else + fprintf(debugfile, "Defn for type%d %s == <", + type, name); + if (is_extern) + fputs("extern ", debugfile); + print_list(debugfile, defn); + fputs(">\n", debugfile); + } + + ++nsyms; + return sym; +} + +struct symbol *add_symbol(const char *name, enum symbol_type type, + struct string_list *defn, int is_extern) +{ + return __add_symbol(name, type, defn, is_extern, 0); +} + +static struct symbol *add_reference_symbol(const char *name, enum symbol_type type, + struct string_list *defn, int is_extern) +{ + return __add_symbol(name, type, defn, is_extern, 1); +} + +/*----------------------------------------------------------------------*/ + +void free_node(struct string_list *node) +{ + free(node->string); + free(node); +} + +void free_list(struct string_list *s, struct string_list *e) +{ + while (s != e) { + struct string_list *next = s->next; + free_node(s); + s = next; + } +} + +static struct string_list *mk_node(const char *string) +{ + struct string_list *newnode; + + newnode = xmalloc(sizeof(*newnode)); + newnode->string = xstrdup(string); + newnode->tag = SYM_NORMAL; + newnode->next = NULL; + + return newnode; +} + +static struct string_list *concat_list(struct string_list *start, ...) +{ + va_list ap; + struct string_list *n, *n2; + + if (!start) + return NULL; + for (va_start(ap, start); (n = va_arg(ap, struct string_list *));) { + for (n2 = n; n2->next; n2 = n2->next) + ; + n2->next = start; + start = n; + } + va_end(ap); + return start; +} + +struct string_list *copy_node(struct string_list *node) +{ + struct string_list *newnode; + + newnode = xmalloc(sizeof(*newnode)); + newnode->string = xstrdup(node->string); + newnode->tag = node->tag; + + return newnode; +} + +struct string_list *copy_list_range(struct string_list *start, + struct string_list *end) +{ + struct string_list *res, *n; + + if (start == end) + return NULL; + n = res = copy_node(start); + for (start = start->next; start != end; start = start->next) { + n->next = copy_node(start); + n = n->next; + } + n->next = NULL; + return res; +} + +static int equal_list(struct string_list *a, struct string_list *b) +{ + while (a && b) { + if (a->tag != b->tag || strcmp(a->string, b->string)) + return 0; + a = a->next; + b = b->next; + } + + return !a && !b; +} + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static struct string_list *read_node(FILE *f) +{ + char buffer[256]; + struct string_list node = { + .string = buffer, + .tag = SYM_NORMAL }; + int c, in_string = 0; + + while ((c = fgetc(f)) != EOF) { + if (!in_string && c == ' ') { + if (node.string == buffer) + continue; + break; + } else if (c == '"') { + in_string = !in_string; + } else if (c == '\n') { + if (node.string == buffer) + return NULL; + ungetc(c, f); + break; + } + if (node.string >= buffer + sizeof(buffer) - 1) { + fprintf(stderr, "Token too long\n"); + exit(1); + } + *node.string++ = c; + } + if (node.string == buffer) + return NULL; + *node.string = 0; + node.string = buffer; + + if (node.string[1] == '#') { + size_t n; + + for (n = 0; n < ARRAY_SIZE(symbol_types); n++) { + if (node.string[0] == symbol_types[n].n) { + node.tag = n; + node.string += 2; + return copy_node(&node); + } + } + fprintf(stderr, "Unknown type %c\n", node.string[0]); + exit(1); + } + return copy_node(&node); +} + +static void read_reference(FILE *f) +{ + while (!feof(f)) { + struct string_list *defn = NULL; + struct string_list *sym, *def; + int is_extern = 0, is_override = 0; + struct symbol *subsym; + + sym = read_node(f); + if (sym && sym->tag == SYM_NORMAL && + !strcmp(sym->string, "override")) { + is_override = 1; + free_node(sym); + sym = read_node(f); + } + if (!sym) + continue; + def = read_node(f); + if (def && def->tag == SYM_NORMAL && + !strcmp(def->string, "extern")) { + is_extern = 1; + free_node(def); + def = read_node(f); + } + while (def) { + def->next = defn; + defn = def; + def = read_node(f); + } + subsym = add_reference_symbol(sym->string, sym->tag, + defn, is_extern); + subsym->is_override = is_override; + free_node(sym); + } +} + +static void print_node(FILE * f, struct string_list *list) +{ + if (symbol_types[list->tag].n) { + putc(symbol_types[list->tag].n, f); + putc('#', f); + } + fputs(list->string, f); +} + +static void print_list(FILE * f, struct string_list *list) +{ + struct string_list **e, **b; + struct string_list *tmp, **tmp2; + int elem = 1; + + if (list == NULL) { + fputs("(nil)", f); + return; + } + + tmp = list; + while ((tmp = tmp->next) != NULL) + elem++; + + b = alloca(elem * sizeof(*e)); + e = b + elem; + tmp2 = e - 1; + + (*tmp2--) = list; + while ((list = list->next) != NULL) + *(tmp2--) = list; + + while (b != e) { + print_node(f, *b++); + putc(' ', f); + } +} + +static uint32_t expand_and_crc_sym(struct symbol *sym, uint32_t crc) +{ + struct string_list *list = sym->defn; + struct string_list **e, **b; + struct string_list *tmp, **tmp2; + int elem = 1; + + if (!list) + return crc; + + tmp = list; + while ((tmp = tmp->next) != NULL) + elem++; + + b = alloca(elem * sizeof(*e)); + e = b + elem; + tmp2 = e - 1; + + *(tmp2--) = list; + while ((list = list->next) != NULL) + *(tmp2--) = list; + + while (b != e) { + struct string_list *cur; + struct symbol *subsym; + + cur = *(b++); + switch (cur->tag) { + case SYM_NORMAL: + if (flag_dump_defs) + fprintf(debugfile, "%s ", cur->string); + crc = partial_crc32(cur->string, crc); + crc = partial_crc32_one(' ', crc); + break; + + case SYM_ENUM_CONST: + case SYM_TYPEDEF: + subsym = find_symbol(cur->string, cur->tag, 0); + /* FIXME: Bad reference files can segfault here. */ + if (subsym->expansion_trail) { + if (flag_dump_defs) + fprintf(debugfile, "%s ", cur->string); + crc = partial_crc32(cur->string, crc); + crc = partial_crc32_one(' ', crc); + } else { + subsym->expansion_trail = expansion_trail; + expansion_trail = subsym; + crc = expand_and_crc_sym(subsym, crc); + } + break; + + case SYM_STRUCT: + case SYM_UNION: + case SYM_ENUM: + subsym = find_symbol(cur->string, cur->tag, 0); + if (!subsym) { + struct string_list *n; + + error_with_pos("expand undefined %s %s", + symbol_types[cur->tag].name, + cur->string); + n = concat_list(mk_node + (symbol_types[cur->tag].name), + mk_node(cur->string), + mk_node("{"), + mk_node("UNKNOWN"), + mk_node("}"), NULL); + subsym = + add_symbol(cur->string, cur->tag, n, 0); + } + if (subsym->expansion_trail) { + if (flag_dump_defs) { + fprintf(debugfile, "%s %s ", + symbol_types[cur->tag].name, + cur->string); + } + + crc = partial_crc32(symbol_types[cur->tag].name, + crc); + crc = partial_crc32_one(' ', crc); + crc = partial_crc32(cur->string, crc); + crc = partial_crc32_one(' ', crc); + } else { + subsym->expansion_trail = expansion_trail; + expansion_trail = subsym; + crc = expand_and_crc_sym(subsym, crc); + } + break; + } + } + + { + static struct symbol **end = &visited_symbols; + + if (!sym->visited) { + *end = sym; + end = &sym->visited; + sym->visited = (struct symbol *)-1L; + } + } + + return 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) { + error_with_pos("export undefined symbol %s", name); + 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, ", "); + } + print_type_name(sym->type, sym->name); + if (sym->status == STATUS_DEFINED) + fprintf(stderr, " (became defined)"); + has_changed = 1; + if (flag_preserve) + errors++; + } + sym->expansion_trail = 0; + sym = n; + } + if (has_changed) + fprintf(stderr, "\n"); + + if (flag_dump_defs) + fputs(">\n", debugfile); + + printf("#SYMVER %s 0x%08lx\n", name, (unsigned long)crc); +} + +/*----------------------------------------------------------------------*/ + +static void print_location(void) +{ + fprintf(stderr, "%s:%d: ", cur_filename ? : "<stdin>", cur_line); +} + +static void print_type_name(enum symbol_type type, const char *name) +{ + if (symbol_types[type].name) + fprintf(stderr, "%s %s", symbol_types[type].name, name); + else + fprintf(stderr, "%s", name); +} + +void error_with_pos(const char *fmt, ...) +{ + va_list args; + + if (flag_warnings) { + print_location(); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + putc('\n', stderr); + + errors++; + } +} + +static void genksyms_usage(void) +{ + fputs("Usage:\n" "genksyms [-adDTwqhVR] > /path/to/.tmp_obj.ver\n" "\n" + " -d, --debug Increment the debug level (repeatable)\n" + " -D, --dump Dump expanded symbol defs (for debugging only)\n" + " -r, --reference file Read reference symbols from a file\n" + " -T, --dump-types file Dump expanded types into file\n" + " -p, --preserve Preserve reference modversions or fail\n" + " -w, --warnings Enable warnings\n" + " -q, --quiet Disable warnings (default)\n" + " -h, --help Print this message\n" + " -V, --version Print the release version\n" + , stderr); +} + +int main(int argc, char **argv) +{ + FILE *dumpfile = NULL, *ref_file = NULL; + int o; + + struct option long_opts[] = { + {"debug", 0, 0, 'd'}, + {"warnings", 0, 0, 'w'}, + {"quiet", 0, 0, 'q'}, + {"dump", 0, 0, 'D'}, + {"reference", 1, 0, 'r'}, + {"dump-types", 1, 0, 'T'}, + {"preserve", 0, 0, 'p'}, + {"version", 0, 0, 'V'}, + {"help", 0, 0, 'h'}, + {0, 0, 0, 0} + }; + + while ((o = getopt_long(argc, argv, "dwqVDr:T:ph", + &long_opts[0], NULL)) != EOF) + switch (o) { + case 'd': + flag_debug++; + break; + case 'w': + flag_warnings = 1; + break; + case 'q': + flag_warnings = 0; + break; + case 'V': + fputs("genksyms version 2.5.60\n", stderr); + break; + case 'D': + flag_dump_defs = 1; + break; + case 'r': + flag_reference = 1; + ref_file = fopen(optarg, "r"); + if (!ref_file) { + perror(optarg); + return 1; + } + break; + case 'T': + flag_dump_types = 1; + dumpfile = fopen(optarg, "w"); + if (!dumpfile) { + perror(optarg); + return 1; + } + break; + case 'p': + flag_preserve = 1; + break; + case 'h': + genksyms_usage(); + return 0; + default: + genksyms_usage(); + return 1; + } + { + extern int yydebug; + extern int yy_flex_debug; + + yydebug = (flag_debug > 1); + yy_flex_debug = (flag_debug > 2); + + debugfile = stderr; + /* setlinebuf(debugfile); */ + } + + if (flag_reference) { + read_reference(ref_file); + fclose(ref_file); + } + + yyparse(); + + if (flag_dump_types && visited_symbols) { + while (visited_symbols != (struct symbol *)-1L) { + struct symbol *sym = visited_symbols; + + if (sym->is_override) + fputs("override ", dumpfile); + if (symbol_types[sym->type].n) { + putc(symbol_types[sym->type].n, dumpfile); + putc('#', dumpfile); + } + fputs(sym->name, dumpfile); + putc(' ', dumpfile); + if (sym->is_extern) + fputs("extern ", dumpfile); + print_list(dumpfile, sym->defn); + putc('\n', dumpfile); + + visited_symbols = sym->visited; + sym->visited = NULL; + } + } + + if (flag_debug) { + fprintf(debugfile, "Hash table occupancy %d/%zd = %g\n", + nsyms, HASH_SIZE(symbol_hashtable), + (double)nsyms / HASH_SIZE(symbol_hashtable)); + } + + if (dumpfile) + fclose(dumpfile); + + return errors != 0; +} diff --git a/scripts/genksyms/genksyms.h b/scripts/genksyms/genksyms.h new file mode 100644 index 000000000..0c355075f --- /dev/null +++ b/scripts/genksyms/genksyms.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Generate kernel symbol version hashes. + Copyright 1996, 1997 Linux International. + + New implementation contributed by Richard Henderson <rth@tamu.edu> + Based on original work by Bjorn Ekwall <bj0rn@blox.se> + + This file is part of the Linux modutils. + + */ + +#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 +}; + +enum symbol_status { + STATUS_UNCHANGED, STATUS_DEFINED, STATUS_MODIFIED +}; + +struct string_list { + struct string_list *next; + enum symbol_type tag; + int in_source_file; + char *string; +}; + +struct symbol { + struct hlist_node hnode; + char *name; + enum symbol_type type; + struct string_list *defn; + struct symbol *expansion_trail; + struct symbol *visited; + int is_extern; + int is_declared; + enum symbol_status status; + int is_override; +}; + +typedef struct string_list **yystype; +#define YYSTYPE yystype + +extern int cur_line; +extern char *cur_filename; +extern int in_source_file; + +struct symbol *find_symbol(const char *name, enum symbol_type ns, int exact); +struct symbol *add_symbol(const char *name, enum symbol_type type, + struct string_list *defn, int is_extern); +void export_symbol(const char *); + +void free_node(struct string_list *list); +void free_list(struct string_list *s, struct string_list *e); +struct string_list *copy_node(struct string_list *); +struct string_list *copy_list_range(struct string_list *start, + struct string_list *end); + +int yylex(void); +int yyparse(void); + +extern bool dont_want_type_specifier; + +void error_with_pos(const char *, ...) __attribute__ ((format(printf, 1, 2))); + +/*----------------------------------------------------------------------*/ +#define xmalloc(size) ({ void *__ptr = malloc(size); \ + if(!__ptr && size != 0) { \ + fprintf(stderr, "out of memory\n"); \ + exit(1); \ + } \ + __ptr; }) +#define xstrdup(str) ({ char *__str = strdup(str); \ + if (!__str) { \ + fprintf(stderr, "out of memory\n"); \ + exit(1); \ + } \ + __str; }) + +#endif /* genksyms.h */ diff --git a/scripts/genksyms/keywords.c b/scripts/genksyms/keywords.c new file mode 100644 index 000000000..ee1499d27 --- /dev/null +++ b/scripts/genksyms/keywords.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-only +static struct resword { + const char *name; + int token; +} keywords[] = { + { "__GENKSYMS_EXPORT_SYMBOL", EXPORT_SYMBOL_KEYW }, + { "__asm", ASM_KEYW }, + { "__asm__", ASM_KEYW }, + { "__attribute", ATTRIBUTE_KEYW }, + { "__attribute__", ATTRIBUTE_KEYW }, + { "__const", CONST_KEYW }, + { "__const__", CONST_KEYW }, + { "__extension__", EXTENSION_KEYW }, + { "__inline", INLINE_KEYW }, + { "__inline__", INLINE_KEYW }, + { "__signed", SIGNED_KEYW }, + { "__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 }, + + { "__int128", BUILTIN_INT_KEYW }, + { "__int128_t", BUILTIN_INT_KEYW }, + { "__uint128_t", BUILTIN_INT_KEYW }, + + // According to rth, c99 defines "_Bool", "__restrict", "__restrict__", "restrict". KAO + { "_Bool", BOOL_KEYW }, + { "__restrict", RESTRICT_KEYW }, + { "__restrict__", RESTRICT_KEYW }, + { "restrict", RESTRICT_KEYW }, + { "asm", ASM_KEYW }, + + // c11 keywords that can be used at module scope + { "_Static_assert", STATIC_ASSERT_KEYW }, + + // attribute commented out in modutils 2.4.2. People are using 'attribute' as a + // field name which breaks the genksyms parser. It is not a gcc keyword anyway. + // 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 }, + { "double", DOUBLE_KEYW }, + { "enum", ENUM_KEYW }, + { "extern", EXTERN_KEYW }, + { "float", FLOAT_KEYW }, + { "inline", INLINE_KEYW }, + { "int", INT_KEYW }, + { "long", LONG_KEYW }, + { "register", REGISTER_KEYW }, + { "short", SHORT_KEYW }, + { "signed", SIGNED_KEYW }, + { "static", STATIC_KEYW }, + { "struct", STRUCT_KEYW }, + { "typedef", TYPEDEF_KEYW }, + { "typeof", TYPEOF_KEYW }, + { "typeof_unqual", TYPEOF_KEYW }, + { "union", UNION_KEYW }, + { "unsigned", UNSIGNED_KEYW }, + { "void", VOID_KEYW }, + { "volatile", VOLATILE_KEYW }, +}; + +#define NR_KEYWORDS (sizeof(keywords)/sizeof(struct resword)) + +static int is_reserved_word(register const char *str, register unsigned int len) +{ + int i; + for (i = 0; i < NR_KEYWORDS; i++) { + struct resword *r = keywords + i; + int l = strlen(r->name); + if (len == l && !memcmp(str, r->name, len)) + return r->token; + } + return -1; +} diff --git a/scripts/genksyms/lex.l b/scripts/genksyms/lex.l new file mode 100644 index 000000000..f81033af1 --- /dev/null +++ b/scripts/genksyms/lex.l @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Lexical analysis for genksyms. + * Copyright 1996, 1997 Linux International. + * + * New implementation contributed by Richard Henderson <rth@tamu.edu> + * Based on original work by Bjorn Ekwall <bj0rn@blox.se> + * + * Taken from Linux modutils 2.4.22. + */ + +%{ + +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "genksyms.h" +#include "parse.tab.h" + +/* We've got a two-level lexer here. We let flex do basic tokenization + and then we categorize those basic tokens in the second stage. */ +#define YY_DECL static int yylex1(void) + +%} + +IDENT [A-Za-z_\$][A-Za-z0-9_\$]* + +O_INT 0[0-7]* +D_INT [1-9][0-9]* +X_INT 0[Xx][0-9A-Fa-f]+ +I_SUF [Uu]|[Ll]|[Uu][Ll]|[Ll][Uu] +INT ({O_INT}|{D_INT}|{X_INT}){I_SUF}? + +FRAC ([0-9]*\.[0-9]+)|([0-9]+\.) +EXP [Ee][+-]?[0-9]+ +F_SUF [FfLl] +REAL ({FRAC}{EXP}?{F_SUF}?)|([0-9]+{EXP}{F_SUF}?) + +STRING L?\"([^\\\"]*\\.)*[^\\\"]*\" +CHAR L?\'([^\\\']*\\.)*[^\\\']*\' + +MC_TOKEN ([~%^&*+=|<>/-]=)|(&&)|("||")|(->)|(<<)|(>>) + +/* We don't do multiple input files. */ +%option noyywrap + +%option noinput + +%% + +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; +^#.*\n cur_line++; +\n cur_line++; + + /* Ignore all other whitespace. */ +[ \t\f\v\r]+ ; + + +{STRING} return STRING; +{CHAR} return CHAR; +{IDENT} return IDENT; + + /* The Pedant requires that the other C multi-character tokens be + recognized as tokens. We don't actually use them since we don't + parse expressions, but we do want whitespace to be arranged + around them properly. */ +{MC_TOKEN} return OTHER; +{INT} return INT; +{REAL} return REAL; + +"..." return DOTS; + + /* All other tokens are single characters. */ +. return yytext[0]; + + +%% + +/* Bring in the keyword recognizer. */ + +#include "keywords.c" + + +/* Macros to append to our phrase collection list. */ + +/* + * We mark any token, that that equals to a known enumerator, as + * SYM_ENUM_CONST. The parser will change this for struct and union tags later, + * the only problem is struct and union members: + * enum e { a, b }; struct s { int a, b; } + * but in this case, the only effect will be, that the ABI checksums become + * more volatile, which is acceptable. Also, such collisions are quite rare, + * so far it was only observed in include/linux/telephony.h. + */ +#define _APP(T,L) do { \ + cur_node = next_node; \ + next_node = xmalloc(sizeof(*next_node)); \ + next_node->next = cur_node; \ + cur_node->string = memcpy(xmalloc(L+1), T, L+1); \ + cur_node->tag = \ + find_symbol(cur_node->string, SYM_ENUM_CONST, 1)?\ + SYM_ENUM_CONST : SYM_NORMAL ; \ + cur_node->in_source_file = in_source_file; \ + } while (0) + +#define APP _APP(yytext, yyleng) + + +/* 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) +{ + static enum { + ST_NOTSTARTED, ST_NORMAL, ST_ATTRIBUTE, ST_ASM, ST_TYPEOF, ST_TYPEOF_1, + ST_BRACKET, ST_BRACE, ST_EXPRESSION, ST_STATIC_ASSERT, + } lexstate = ST_NOTSTARTED; + + static int suppress_type_lookup, dont_want_brace_phrase; + static struct string_list *next_node; + static char *source_file; + + int token, count = 0; + struct string_list *cur_node; + + if (lexstate == ST_NOTSTARTED) + { + next_node = xmalloc(sizeof(*next_node)); + next_node->next = NULL; + lexstate = ST_NORMAL; + } + +repeat: + token = yylex1(); + + if (token == 0) + return 0; + else if (token == FILENAME) + { + char *file, *e; + + /* Save the filename and line number for later error messages. */ + + if (cur_filename) + free(cur_filename); + + file = strchr(yytext, '\"')+1; + e = strchr(file, '\"'); + *e = '\0'; + cur_filename = memcpy(xmalloc(e-file+1), file, e-file+1); + cur_line = atoi(yytext+2); + + if (!source_file) { + source_file = xstrdup(cur_filename); + in_source_file = 1; + } else { + in_source_file = (strcmp(cur_filename, source_file) == 0); + } + + goto repeat; + } + + switch (lexstate) + { + case ST_NORMAL: + APP; + switch (token) + { + case IDENT: + { + int r = is_reserved_word(yytext, yyleng); + if (r >= 0) + { + switch (token = r) + { + case ATTRIBUTE_KEYW: + lexstate = ST_ATTRIBUTE; + count = 0; + goto repeat; + case ASM_KEYW: + lexstate = ST_ASM; + count = 0; + goto repeat; + case TYPEOF_KEYW: + lexstate = ST_TYPEOF; + count = 0; + goto repeat; + + case STRUCT_KEYW: + case UNION_KEYW: + case ENUM_KEYW: + dont_want_brace_phrase = 3; + suppress_type_lookup = 2; + goto fini; + + case EXPORT_SYMBOL_KEYW: + goto fini; + + case STATIC_ASSERT_KEYW: + lexstate = ST_STATIC_ASSERT; + count = 0; + goto repeat; + } + } + if (!suppress_type_lookup && !dont_want_type_specifier) + { + if (find_symbol(yytext, SYM_TYPEDEF, 1)) + token = TYPE; + } + } + break; + + case '[': + lexstate = ST_BRACKET; + count = 1; + goto repeat; + + case '{': + if (dont_want_brace_phrase) + break; + lexstate = ST_BRACE; + count = 1; + goto repeat; + + case '=': case ':': + lexstate = ST_EXPRESSION; + break; + + default: + break; + } + break; + + case ST_ATTRIBUTE: + APP; + switch (token) + { + case '(': + ++count; + goto repeat; + case ')': + if (--count == 0) + { + lexstate = ST_NORMAL; + token = ATTRIBUTE_PHRASE; + break; + } + goto repeat; + default: + goto repeat; + } + break; + + case ST_ASM: + APP; + switch (token) + { + case '(': + ++count; + goto repeat; + case ')': + if (--count == 0) + { + lexstate = ST_NORMAL; + token = ASM_PHRASE; + break; + } + goto repeat; + default: + goto repeat; + } + break; + + case ST_TYPEOF_1: + if (token == IDENT) + { + if (is_reserved_word(yytext, yyleng) >= 0 + || find_symbol(yytext, SYM_TYPEDEF, 1)) + { + yyless(0); + unput('('); + lexstate = ST_NORMAL; + token = TYPEOF_KEYW; + break; + } + _APP("(", 1); + } + lexstate = ST_TYPEOF; + /* FALLTHRU */ + + case ST_TYPEOF: + switch (token) + { + case '(': + if ( ++count == 1 ) + lexstate = ST_TYPEOF_1; + else + APP; + goto repeat; + case ')': + APP; + if (--count == 0) + { + lexstate = ST_NORMAL; + token = TYPEOF_PHRASE; + break; + } + goto repeat; + default: + APP; + goto repeat; + } + break; + + case ST_BRACKET: + APP; + switch (token) + { + case '[': + ++count; + goto repeat; + case ']': + if (--count == 0) + { + lexstate = ST_NORMAL; + token = BRACKET_PHRASE; + break; + } + goto repeat; + default: + goto repeat; + } + break; + + case ST_BRACE: + APP; + switch (token) + { + case '{': + ++count; + goto repeat; + case '}': + if (--count == 0) + { + lexstate = ST_NORMAL; + token = BRACE_PHRASE; + break; + } + goto repeat; + default: + goto repeat; + } + break; + + case ST_EXPRESSION: + switch (token) + { + case '(': case '[': case '{': + ++count; + APP; + goto repeat; + case '}': + /* is this the last line of an enum declaration? */ + if (count == 0) + { + /* Put back the token we just read so's we can find it again + after registering the expression. */ + unput(token); + + lexstate = ST_NORMAL; + token = EXPRESSION_PHRASE; + break; + } + /* FALLTHRU */ + case ')': case ']': + --count; + APP; + goto repeat; + case ',': case ';': + if (count == 0) + { + /* Put back the token we just read so's we can find it again + after registering the expression. */ + unput(token); + + lexstate = ST_NORMAL; + token = EXPRESSION_PHRASE; + break; + } + APP; + goto repeat; + default: + APP; + goto repeat; + } + break; + + case ST_STATIC_ASSERT: + APP; + switch (token) + { + case '(': + ++count; + goto repeat; + case ')': + if (--count == 0) + { + lexstate = ST_NORMAL; + token = STATIC_ASSERT_PHRASE; + break; + } + goto repeat; + default: + goto repeat; + } + break; + + default: + exit(1); + } +fini: + + if (suppress_type_lookup > 0) + --suppress_type_lookup; + + /* + * __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; + + return token; +} diff --git a/scripts/genksyms/parse.y b/scripts/genksyms/parse.y new file mode 100644 index 000000000..cabcd146f --- /dev/null +++ b/scripts/genksyms/parse.y @@ -0,0 +1,539 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * C global declaration parser for genksyms. + * Copyright 1996, 1997 Linux International. + * + * New implementation contributed by Richard Henderson <rth@tamu.edu> + * Based on original work by Bjorn Ekwall <bj0rn@blox.se> + * + * This file is part of the Linux modutils. + */ + +%{ + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include "genksyms.h" + +static int is_typedef; +static int is_extern; +static char *current_name; +static struct string_list *decl_spec; + +static void yyerror(const char *); + +static inline void +remove_node(struct string_list **p) +{ + struct string_list *node = *p; + *p = node->next; + free_node(node); +} + +static inline void +remove_list(struct string_list **pb, struct string_list **pe) +{ + struct string_list *b = *pb, *e = *pe; + *pb = e; + free_list(b, e); +} + +/* Record definition of a struct/union/enum */ +static void record_compound(struct string_list **keyw, + struct string_list **ident, + struct string_list **body, + enum symbol_type type) +{ + struct string_list *b = *body, *i = *ident, *r; + + if (i->in_source_file) { + remove_node(keyw); + (*ident)->tag = type; + remove_list(body, ident); + return; + } + r = copy_node(i); r->tag = type; + r->next = (*keyw)->next; *body = r; (*keyw)->next = NULL; + add_symbol(i->string, type, b, is_extern); +} + +%} + +%token ASM_KEYW +%token ATTRIBUTE_KEYW +%token AUTO_KEYW +%token BOOL_KEYW +%token BUILTIN_INT_KEYW +%token CHAR_KEYW +%token CONST_KEYW +%token DOUBLE_KEYW +%token ENUM_KEYW +%token EXTERN_KEYW +%token EXTENSION_KEYW +%token FLOAT_KEYW +%token INLINE_KEYW +%token INT_KEYW +%token LONG_KEYW +%token REGISTER_KEYW +%token RESTRICT_KEYW +%token SHORT_KEYW +%token SIGNED_KEYW +%token STATIC_KEYW +%token STATIC_ASSERT_KEYW +%token STRUCT_KEYW +%token TYPEDEF_KEYW +%token UNION_KEYW +%token UNSIGNED_KEYW +%token VOID_KEYW +%token VOLATILE_KEYW +%token TYPEOF_KEYW +%token VA_LIST_KEYW + +%token X86_SEG_KEYW + +%token EXPORT_SYMBOL_KEYW + +%token ASM_PHRASE +%token ATTRIBUTE_PHRASE +%token TYPEOF_PHRASE +%token BRACE_PHRASE +%token BRACKET_PHRASE +%token EXPRESSION_PHRASE +%token STATIC_ASSERT_PHRASE + +%token CHAR +%token DOTS +%token IDENT +%token INT +%token REAL +%token STRING +%token TYPE +%token OTHER +%token FILENAME + +%% + +declaration_seq: + declaration + | declaration_seq declaration + ; + +declaration: + { is_typedef = 0; is_extern = 0; current_name = NULL; decl_spec = NULL; } + declaration1 + { free_list(*$2, NULL); *$2 = NULL; } + ; + +declaration1: + EXTENSION_KEYW TYPEDEF_KEYW { is_typedef = 1; } simple_declaration + { $$ = $4; } + | TYPEDEF_KEYW { is_typedef = 1; } simple_declaration + { $$ = $3; } + | simple_declaration + | function_definition + | asm_definition + | export_definition + | static_assert + | error ';' { $$ = $2; } + | error '}' { $$ = $2; } + ; + +simple_declaration: + decl_specifier_seq_opt init_declarator_list_opt ';' + { if (current_name) { + struct string_list *decl = (*$3)->next; + (*$3)->next = NULL; + add_symbol(current_name, + is_typedef ? SYM_TYPEDEF : SYM_NORMAL, + decl, is_extern); + current_name = NULL; + } + $$ = $3; + dont_want_type_specifier = false; + } + ; + +init_declarator_list_opt: + /* 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 ',' 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; + $$ = $4; + dont_want_type_specifier = true; + } + ; + +init_declarator: + declarator asm_phrase_opt attribute_opt initializer_opt + { $$ = $4 ? $4 : $3 ? $3 : $2 ? $2 : $1; } + ; + +/* Hang on to the specifiers so that we can reuse them. */ +decl_specifier_seq_opt: + /* empty */ { decl_spec = NULL; } + | decl_specifier_seq + ; + +decl_specifier_seq: + 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: + storage_class_specifier + { /* Version 2 checksumming ignores storage class, as that + is really irrelevant to the linkage. */ + remove_node($1); + $$ = $1; + } + | type_specifier { dont_want_type_specifier = true; $$ = $1; } + | type_qualifier + ; + +storage_class_specifier: + AUTO_KEYW + | REGISTER_KEYW + | STATIC_KEYW + | EXTERN_KEYW { is_extern = 1; $$ = $1; } + | INLINE_KEYW { is_extern = 0; $$ = $1; } + ; + +type_specifier: + simple_type_specifier + | 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 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 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; } + /* + * Anonymous enum definition. Tell add_symbol() to restart its counter. + */ + | ENUM_KEYW enum_body + { add_symbol(NULL, SYM_ENUM, NULL, 0); $$ = $2; } + /* Anonymous s/u definitions. Nothing needs doing. */ + | STRUCT_KEYW attribute_opt class_body { $$ = $3; } + | UNION_KEYW attribute_opt class_body { $$ = $3; } + ; + +simple_type_specifier: + CHAR_KEYW + | SHORT_KEYW + | INT_KEYW + | LONG_KEYW + | SIGNED_KEYW + | UNSIGNED_KEYW + | FLOAT_KEYW + | DOUBLE_KEYW + | VOID_KEYW + | BOOL_KEYW + | VA_LIST_KEYW + | BUILTIN_INT_KEYW + | TYPE { (*$1)->tag = SYM_TYPEDEF; $$ = $1; } + ; + +ptr_operator: + '*' type_qualifier_seq_opt + { $$ = $2 ? $2 : $1; } + ; + +type_qualifier_seq_opt: + /* empty */ { $$ = NULL; } + | type_qualifier_seq + ; + +type_qualifier_seq: + type_qualifier + | ATTRIBUTE_PHRASE + | type_qualifier_seq type_qualifier { $$ = $2; } + | type_qualifier_seq ATTRIBUTE_PHRASE { $$ = $2; } + ; + +type_qualifier: + X86_SEG_KEYW + | CONST_KEYW | VOLATILE_KEYW + | RESTRICT_KEYW + { /* restrict has no effect in prototypes so ignore it */ + remove_node($1); + $$ = $1; + } + ; + +declarator: + ptr_operator declarator { $$ = $2; } + | direct_declarator + ; + +direct_declarator: + IDENT + { 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; } + | direct_declarator '(' error ')' + { $$ = $4; } + | direct_declarator BRACKET_PHRASE + { $$ = $2; } + | '(' attribute_opt declarator ')' + { $$ = $4; } + ; + +/* Nested declarators differ from regular declarators in that they do + not record the symbols they find in the global symbol table. */ +nested_declarator: + ptr_operator nested_declarator { $$ = $2; } + | direct_nested_declarator + ; + +direct_nested_declarator: + direct_nested_declarator1 + | direct_nested_declarator1 '(' parameter_declaration_clause ')' + { $$ = $4; } + ; + +direct_nested_declarator1: + IDENT { $$ = $1; dont_want_type_specifier = false; } + | direct_nested_declarator1 '(' error ')' + { $$ = $4; } + | direct_nested_declarator1 BRACKET_PHRASE + { $$ = $2; } + | '(' attribute_opt nested_declarator ')' + { $$ = $4; } + | '(' error ')' + { $$ = $3; } + ; + +parameter_declaration_clause: + parameter_declaration_list_opt DOTS { $$ = $2; } + | parameter_declaration_list_opt + | parameter_declaration_list ',' DOTS { $$ = $3; } + ; + +parameter_declaration_list_opt: + /* empty */ { $$ = NULL; } + | parameter_declaration_list + ; + +parameter_declaration_list: + parameter_declaration + { $$ = $1; dont_want_type_specifier = false; } + | parameter_declaration_list ',' parameter_declaration + { $$ = $3; dont_want_type_specifier = false; } + ; + +parameter_declaration: + decl_specifier_seq abstract_declarator_opt + { $$ = $2 ? $2 : $1; } + ; + +abstract_declarator_opt: + /* empty */ { $$ = NULL; } + | abstract_declarator + ; + +abstract_declarator: + ptr_operator + | ptr_operator abstract_declarator + { $$ = $2 ? $2 : $1; } + | direct_abstract_declarator attribute_opt + { $$ = $2; dont_want_type_specifier = false; } + ; + +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; + } + | direct_abstract_declarator1 open_paren error ')' + { $$ = $4; } + | direct_abstract_declarator1 BRACKET_PHRASE + { $$ = $2; } + | open_paren attribute_opt abstract_declarator ')' + { $$ = $4; } + | open_paren error ')' + { $$ = $3; } + | BRACKET_PHRASE + ; + +open_paren: + '(' { $$ = $1; dont_want_type_specifier = false; } + ; + +function_definition: + decl_specifier_seq_opt declarator BRACE_PHRASE + { struct string_list *decl = *$2; + *$2 = NULL; + add_symbol(current_name, SYM_NORMAL, decl, is_extern); + $$ = $3; + } + ; + +initializer_opt: + /* empty */ { $$ = NULL; } + | initializer + ; + +/* We never care about the contents of an initializer. */ +initializer: + '=' EXPRESSION_PHRASE + { remove_list($2, &(*$1)->next); $$ = $2; } + ; + +class_body: + '{' member_specification_opt '}' { $$ = $3; } + | '{' error '}' { $$ = $3; } + ; + +member_specification_opt: + /* empty */ { $$ = NULL; } + | member_specification + ; + +member_specification: + member_declaration + | member_specification member_declaration { $$ = $2; } + ; + +member_declaration: + decl_specifier_seq_opt member_declarator_list_opt ';' + { $$ = $3; dont_want_type_specifier = false; } + | error ';' + { $$ = $2; dont_want_type_specifier = false; } + ; + +member_declarator_list_opt: + /* empty */ { $$ = NULL; } + | member_declarator_list + ; + +member_declarator_list: + member_declarator + { $$ = $1; dont_want_type_specifier = true; } + | member_declarator_list ',' member_declarator + { $$ = $3; dont_want_type_specifier = true; } + ; + +member_declarator: + nested_declarator attribute_opt { $$ = $2 ? $2 : $1; } + | IDENT member_bitfield_declarator { $$ = $2; } + | member_bitfield_declarator + ; + +member_bitfield_declarator: + ':' EXPRESSION_PHRASE { $$ = $2; } + ; + +attribute_opt: + /* empty */ { $$ = NULL; } + | attribute_opt ATTRIBUTE_PHRASE { $$ = $2; } + ; + +enum_body: + '{' enumerator_list '}' { $$ = $3; } + | '{' enumerator_list ',' '}' { $$ = $4; } + ; + +enumerator_list: + enumerator + | enumerator_list ',' enumerator + +enumerator: + IDENT + { + const char *name = (*$1)->string; + add_symbol(name, SYM_ENUM_CONST, NULL, 0); + } + | IDENT '=' EXPRESSION_PHRASE + { + const char *name = (*$1)->string; + struct string_list *expr = copy_list_range(*$3, *$2); + add_symbol(name, SYM_ENUM_CONST, expr, 0); + } + +asm_definition: + ASM_PHRASE ';' { $$ = $2; } + ; + +asm_phrase_opt: + /* empty */ { $$ = NULL; } + | ASM_PHRASE + ; + +export_definition: + EXPORT_SYMBOL_KEYW '(' IDENT ')' ';' + { export_symbol((*$3)->string); $$ = $5; } + ; + +/* Ignore any module scoped _Static_assert(...) */ +static_assert: + STATIC_ASSERT_PHRASE ';' { $$ = $2; } + ; + +%% + +static void +yyerror(const char *e) +{ + error_with_pos("%s", e); +} diff --git a/scripts/get_dvb_firmware b/scripts/get_dvb_firmware new file mode 100755 index 000000000..1a9080241 --- /dev/null +++ b/scripts/get_dvb_firmware @@ -0,0 +1,929 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-or-later +# DVB firmware extractor +# +# (c) 2004 Andrew de Quincey +# + +use File::Temp qw/ tempdir /; +use IO::Handle; + +@components = ( "sp8870", "sp887x", "tda10045", "tda10046", + "tda10046lifeview", "av7110", "dec2000t", "dec2540t", + "dec3000s", "vp7041", "vp7049", "dibusb", "nxt2002", "nxt2004", + "or51211", "or51132_qam", "or51132_vsb", "bluebird", + "opera1", "cx231xx", "cx18", "cx23885", "pvrusb2", "mpc718", + "af9015", "ngene", "az6027", "lme2510_lg", "lme2510c_s7395", + "lme2510c_s7395_old", "drxk", "drxk_terratec_h5", + "drxk_hauppauge_hvr930c", "tda10071", "it9135", "drxk_pctv", + "drxk_terratec_htc_stick", "sms1xxx_hcw", "si2165"); + +# Check args +syntax() if (scalar(@ARGV) != 1); +$cid = $ARGV[0]; + +# Do it! +for ($i=0; $i < scalar(@components); $i++) { + if ($cid eq $components[$i]) { + $outfile = eval($cid); + die $@ if $@; + print STDERR <<EOF; +Firmware(s) $outfile extracted successfully. +Now copy it(them) to either /usr/lib/hotplug/firmware or /lib/firmware +(depending on configuration of firmware hotplug). +EOF + exit(0); + } +} + +# If we get here, it wasn't found +print STDERR "Unknown component \"$cid\"\n"; +syntax(); + + + + +# --------------------------------------------------------------- +# Firmware-specific extraction subroutines + +sub sp8870 { + my $sourcefile = "tt_Premium_217g.zip"; + my $url = "http://www.softwarepatch.pl/9999ccd06a4813cb827dbb0005071c71/$sourcefile"; + my $hash = "53970ec17a538945a6d8cb608a7b3899"; + my $outfile = "dvb-fe-sp8870.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + verify("$tmpdir/software/OEM/HE/App/boot/SC_MAIN.MC", $hash); + copy("$tmpdir/software/OEM/HE/App/boot/SC_MAIN.MC", $outfile); + + $outfile; +} + +sub sp887x { + my $sourcefile = "Dvbt1.3.57.6.zip"; + my $url = "http://www.avermedia.com/software/$sourcefile"; + my $cabfile = "DVBT Net Ver1.3.57.6/disk1/data1.cab"; + my $hash = "237938d53a7f834c05c42b894ca68ac3"; + my $outfile = "dvb-fe-sp887x.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + checkunshield(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + unshield("$tmpdir/$cabfile", $tmpdir); + verify("$tmpdir/ZEnglish/sc_main.mc", $hash); + copy("$tmpdir/ZEnglish/sc_main.mc", $outfile); + + $outfile; +} + +sub tda10045 { + my $sourcefile = "tt_budget_217g.zip"; + my $url = "http://www.technotrend.de/new/217g/$sourcefile"; + my $hash = "2105fd5bf37842fbcdfa4bfd58f3594a"; + my $outfile = "dvb-fe-tda10045.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + extract("$tmpdir/software/OEM/PCI/App/ttlcdacc.dll", 0x37ef9, 30555, "$tmpdir/fwtmp"); + verify("$tmpdir/fwtmp", $hash); + copy("$tmpdir/fwtmp", $outfile); + + $outfile; +} + +sub tda10046 { + my $sourcefile = "TT_PCI_2.19h_28_11_2006.zip"; + my $url = "http://technotrend.com.ua/download/software/219/$sourcefile"; + my $hash = "6a7e1e2f2644b162ff0502367553c72d"; + my $outfile = "dvb-fe-tda10046.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + extract("$tmpdir/TT_PCI_2.19h_28_11_2006/software/OEM/PCI/App/ttlcdacc.dll", 0x65389, 24478, "$tmpdir/fwtmp"); + verify("$tmpdir/fwtmp", $hash); + copy("$tmpdir/fwtmp", $outfile); + + $outfile; +} + +sub tda10046lifeview { + my $sourcefile = "7%5Cdrv_2.11.02.zip"; + my $url = "http://www.lifeview.hk/dbimages/document/$sourcefile"; + my $hash = "1ea24dee4eea8fe971686981f34fd2e0"; + my $outfile = "dvb-fe-tda10046.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + extract("$tmpdir/LVHybrid.sys", 0x8b088, 24602, "$tmpdir/fwtmp"); + verify("$tmpdir/fwtmp", $hash); + copy("$tmpdir/fwtmp", $outfile); + + $outfile; +} + +sub av7110 { + my $sourcefile = "dvb-ttpci-01.fw-261d"; + my $url = "https://linuxtv.org/downloads/firmware/$sourcefile"; + my $hash = "603431b6259715a8e88f376a53b64e2f"; + my $outfile = "dvb-ttpci-01.fw"; + + checkstandard(); + + wgetfile($sourcefile, $url); + verify($sourcefile, $hash); + copy($sourcefile, $outfile); + + $outfile; +} + +sub dec2000t { + my $sourcefile = "dec217g.exe"; + my $url = "http://hauppauge.lightpath.net/de/$sourcefile"; + my $hash = "bd86f458cee4a8f0a8ce2d20c66215a9"; + my $outfile = "dvb-ttusb-dec-2000t.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + verify("$tmpdir/software/OEM/STB/App/Boot/STB_PC_T.bin", $hash); + copy("$tmpdir/software/OEM/STB/App/Boot/STB_PC_T.bin", $outfile); + + $outfile; +} + +sub dec2540t { + my $sourcefile = "dec217g.exe"; + my $url = "http://hauppauge.lightpath.net/de/$sourcefile"; + my $hash = "53e58f4f5b5c2930beee74a7681fed92"; + my $outfile = "dvb-ttusb-dec-2540t.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + verify("$tmpdir/software/OEM/STB/App/Boot/STB_PC_X.bin", $hash); + copy("$tmpdir/software/OEM/STB/App/Boot/STB_PC_X.bin", $outfile); + + $outfile; +} + +sub dec3000s { + my $sourcefile = "dec217g.exe"; + my $url = "http://hauppauge.lightpath.net/de/$sourcefile"; + my $hash = "b013ececea83f4d6d8d2a29ac7c1b448"; + my $outfile = "dvb-ttusb-dec-3000s.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + verify("$tmpdir/software/OEM/STB/App/Boot/STB_PC_S.bin", $hash); + copy("$tmpdir/software/OEM/STB/App/Boot/STB_PC_S.bin", $outfile); + + $outfile; +} +sub opera1{ + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 0); + + checkstandard(); + my $fwfile1="dvb-usb-opera1-fpga-01.fw"; + my $fwfile2="dvb-usb-opera-01.fw"; + extract("2830SCap2.sys", 0x62e8, 55024, "$tmpdir/opera1-fpga.fw"); + extract("2830SLoad2.sys",0x3178,0x3685-0x3178,"$tmpdir/fw1part1"); + extract("2830SLoad2.sys",0x0980,0x3150-0x0980,"$tmpdir/fw1part2"); + delzero("$tmpdir/fw1part1","$tmpdir/fw1part1-1"); + delzero("$tmpdir/fw1part2","$tmpdir/fw1part2-1"); + verify("$tmpdir/fw1part1-1","5e0909858fdf0b5b09ad48b9fe622e70"); + verify("$tmpdir/fw1part2-1","d6e146f321427e931df2c6fcadac37a1"); + verify("$tmpdir/opera1-fpga.fw","0f8133f5e9051f5f3c1928f7e5a1b07d"); + + my $RES1="\x01\x92\x7f\x00\x01\x00"; + my $RES0="\x01\x92\x7f\x00\x00\x00"; + my $DAT1="\x01\x00\xe6\x00\x01\x00"; + my $DAT0="\x01\x00\xe6\x00\x00\x00"; + open FW,">$tmpdir/opera.fw"; + print FW "$RES1"; + print FW "$DAT1"; + print FW "$RES1"; + print FW "$DAT1"; + appendfile(FW,"$tmpdir/fw1part1-1"); + print FW "$RES0"; + print FW "$DAT0"; + print FW "$RES1"; + print FW "$DAT1"; + appendfile(FW,"$tmpdir/fw1part2-1"); + print FW "$RES1"; + print FW "$DAT1"; + print FW "$RES0"; + print FW "$DAT0"; + copy ("$tmpdir/opera1-fpga.fw",$fwfile1); + copy ("$tmpdir/opera.fw",$fwfile2); + + $fwfile1.",".$fwfile2; +} + +sub vp7041 { + my $sourcefile = "2.422.zip"; + my $url = "http://www.twinhan.com/files/driver/USB-Ter/$sourcefile"; + my $hash = "e88c9372d1f66609a3e7b072c53fbcfe"; + my $outfile = "dvb-vp7041-2.422.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + extract("$tmpdir/VisionDTV/Drivers/Win2K&XP/UDTTload.sys", 12503, 3036, "$tmpdir/fwtmp1"); + extract("$tmpdir/VisionDTV/Drivers/Win2K&XP/UDTTload.sys", 2207, 10274, "$tmpdir/fwtmp2"); + + my $CMD = "\000\001\000\222\177\000"; + my $PAD = "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; + my ($FW); + open $FW, ">$tmpdir/fwtmp3"; + print $FW "$CMD\001$PAD"; + print $FW "$CMD\001$PAD"; + appendfile($FW, "$tmpdir/fwtmp1"); + print $FW "$CMD\000$PAD"; + print $FW "$CMD\001$PAD"; + appendfile($FW, "$tmpdir/fwtmp2"); + print $FW "$CMD\001$PAD"; + print $FW "$CMD\000$PAD"; + close($FW); + + verify("$tmpdir/fwtmp3", $hash); + copy("$tmpdir/fwtmp3", $outfile); + + $outfile; +} + +sub vp7049 { + my $fwfile = "dvb-usb-vp7049-0.95.fw"; + my $url = "http://ao2.it/sites/default/files/blog/2012/11/06/linux-support-digicom-digitune-s-vp7049-udtt7049/$fwfile"; + my $hash = "5609fd295168aea88b25ff43a6f79c36"; + + checkstandard(); + + wgetfile($fwfile, $url); + verify($fwfile, $hash); + + $fwfile; +} + +sub dibusb { + my $url = "https://linuxtv.org/downloads/firmware/dvb-usb-dibusb-5.0.0.11.fw"; + my $outfile = "dvb-dibusb-5.0.0.11.fw"; + my $hash = "fa490295a527360ca16dcdf3224ca243"; + + checkstandard(); + + wgetfile($outfile, $url); + verify($outfile,$hash); + + $outfile; +} + +sub nxt2002 { + my $sourcefile = "Technisat_DVB-PC_4_4_COMPACT.zip"; + my $url = "http://www.bbti.us/download/windows/$sourcefile"; + my $hash = "476befae8c7c1bb9648954060b1eec1f"; + my $outfile = "dvb-fe-nxt2002.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + verify("$tmpdir/SkyNET.sys", $hash); + extract("$tmpdir/SkyNET.sys", 331624, 5908, $outfile); + + $outfile; +} + +sub nxt2004 { + my $sourcefile = "AVerTVHD_MCE_A180_Drv_v1.2.2.16.zip"; + my $url = "http://www.avermedia-usa.com/support/Drivers/$sourcefile"; + my $hash = "111cb885b1e009188346d72acfed024c"; + my $outfile = "dvb-fe-nxt2004.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + verify("$tmpdir/3xHybrid.sys", $hash); + extract("$tmpdir/3xHybrid.sys", 465304, 9584, $outfile); + + $outfile; +} + +sub or51211 { + my $fwfile = "dvb-fe-or51211.fw"; + my $url = "https://linuxtv.org/downloads/firmware/$fwfile"; + my $hash = "d830949c771a289505bf9eafc225d491"; + + checkstandard(); + + wgetfile($fwfile, $url); + verify($fwfile, $hash); + + $fwfile; +} + +sub cx231xx { + my $fwfile = "v4l-cx231xx-avcore-01.fw"; + my $url = "https://linuxtv.org/downloads/firmware/$fwfile"; + my $hash = "7d3bb956dc9df0eafded2b56ba57cc42"; + + checkstandard(); + + wgetfile($fwfile, $url); + verify($fwfile, $hash); + + $fwfile; +} + +sub cx18 { + my $url = "https://linuxtv.org/downloads/firmware/"; + + my %files = ( + 'v4l-cx23418-apu.fw' => '588f081b562f5c653a3db1ad8f65939a', + 'v4l-cx23418-cpu.fw' => 'b6c7ed64bc44b1a6e0840adaeac39d79', + 'v4l-cx23418-dig.fw' => '95bc688d3e7599fd5800161e9971cc55', + ); + + checkstandard(); + + my $allfiles; + foreach my $fwfile (keys %files) { + wgetfile($fwfile, "$url/$fwfile"); + verify($fwfile, $files{$fwfile}); + $allfiles .= " $fwfile"; + } + + $allfiles =~ s/^\s//; + + $allfiles; +} + +sub mpc718 { + my $archive = 'Yuan MPC718 TV Tuner Card 2.13.10.1016.zip'; + my $url = "ftp://ftp.work.acer-euro.com/desktop/aspire_idea510/vista/Drivers/$archive"; + my $fwfile = "dvb-cx18-mpc718-mt352.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + wgetfile($archive, $url); + unzip($archive, $tmpdir); + + my $sourcefile = "$tmpdir/Yuan MPC718 TV Tuner Card 2.13.10.1016/mpc718_32bit/yuanrap.sys"; + my $found = 0; + + open IN, '<', $sourcefile or die "Couldn't open $sourcefile to extract $fwfile data\n"; + binmode IN; + open OUT, '>', $fwfile; + binmode OUT; + { + # Block scope because we change the line terminator variable $/ + my $prevlen = 0; + my $currlen; + + # Buried in the data segment are 3 runs of almost identical + # register-value pairs that end in 0x5d 0x01 which is a "TUNER GO" + # command for the MT352. + # Pull out the middle run (because it's easy) of register-value + # pairs to make the "firmware" file. + + local $/ = "\x5d\x01"; # MT352 "TUNER GO" + + while (<IN>) { + $currlen = length($_); + if ($prevlen == $currlen && $currlen <= 64) { + chop; chop; # Get rid of "TUNER GO" + s/^\0\0//; # get rid of leading 00 00 if it's there + printf OUT "$_"; + $found = 1; + last; + } + $prevlen = $currlen; + } + } + close OUT; + close IN; + if (!$found) { + unlink $fwfile; + die "Couldn't find valid register-value sequence in $sourcefile for $fwfile\n"; + } + $fwfile; +} + +sub cx23885 { + my $url = "https://linuxtv.org/downloads/firmware/"; + + my %files = ( + 'v4l-cx23885-avcore-01.fw' => 'a9f8f5d901a7fb42f552e1ee6384f3bb', + 'v4l-cx23885-enc.fw' => 'a9f8f5d901a7fb42f552e1ee6384f3bb', + ); + + checkstandard(); + + my $allfiles; + foreach my $fwfile (keys %files) { + wgetfile($fwfile, "$url/$fwfile"); + verify($fwfile, $files{$fwfile}); + $allfiles .= " $fwfile"; + } + + $allfiles =~ s/^\s//; + + $allfiles; +} + +sub pvrusb2 { + my $url = "https://linuxtv.org/downloads/firmware/"; + + my %files = ( + 'v4l-cx25840.fw' => 'dadb79e9904fc8af96e8111d9cb59320', + ); + + checkstandard(); + + my $allfiles; + foreach my $fwfile (keys %files) { + wgetfile($fwfile, "$url/$fwfile"); + verify($fwfile, $files{$fwfile}); + $allfiles .= " $fwfile"; + } + + $allfiles =~ s/^\s//; + + $allfiles; +} + +sub or51132_qam { + my $fwfile = "dvb-fe-or51132-qam.fw"; + my $url = "https://linuxtv.org/downloads/firmware/$fwfile"; + my $hash = "7702e8938612de46ccadfe9b413cb3b5"; + + checkstandard(); + + wgetfile($fwfile, $url); + verify($fwfile, $hash); + + $fwfile; +} + +sub or51132_vsb { + my $fwfile = "dvb-fe-or51132-vsb.fw"; + my $url = "https://linuxtv.org/downloads/firmware/$fwfile"; + my $hash = "c16208e02f36fc439a557ad4c613364a"; + + checkstandard(); + + wgetfile($fwfile, $url); + verify($fwfile, $hash); + + $fwfile; +} + +sub bluebird { + my $url = "https://linuxtv.org/download/dvb/firmware/dvb-usb-bluebird-01.fw"; + my $outfile = "dvb-usb-bluebird-01.fw"; + my $hash = "658397cb9eba9101af9031302671f49d"; + + checkstandard(); + + wgetfile($outfile, $url); + verify($outfile,$hash); + + $outfile; +} + +sub af9015 { + my $sourcefile = "download.ashx?file=57"; + my $url = "http://www.ite.com.tw/EN/Services/$sourcefile"; + my $hash = "e3f08935158038d385ad382442f4bb2d"; + my $outfile = "dvb-usb-af9015.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + my $fwoffset = 0x25690; + my $fwlength = 18725; + my ($chunklength, $buf, $rcount); + + checkstandard(); + + wgetfile($sourcefile, $url); + unzip($sourcefile, $tmpdir); + verify("$tmpdir/Driver/Files/AF15BDA.sys", $hash); + + open INFILE, '<', "$tmpdir/Driver/Files/AF15BDA.sys"; + open OUTFILE, '>', $outfile; + + sysseek(INFILE, $fwoffset, SEEK_SET); + while($fwlength > 0) { + $chunklength = 55; + $chunklength = $fwlength if ($chunklength > $fwlength); + $rcount = sysread(INFILE, $buf, $chunklength); + die "Ran out of data\n" if ($rcount != $chunklength); + syswrite(OUTFILE, $buf); + sysread(INFILE, $buf, 8); + $fwlength -= $rcount + 8; + } + + close OUTFILE; + close INFILE; +} + +sub ngene { + my $url = "http://www.digitaldevices.de/download/"; + my $file1 = "ngene_15.fw"; + my $hash1 = "d798d5a757121174f0dbc5f2833c0c85"; + my $file2 = "ngene_17.fw"; + my $hash2 = "26b687136e127b8ac24b81e0eeafc20b"; + my $url2 = "http://l4m-daten.de/downloads/firmware/dvb-s2/linux/all/"; + my $file3 = "ngene_18.fw"; + my $hash3 = "ebce3ea769a53e3e0b0197c3b3f127e3"; + + checkstandard(); + + wgetfile($file1, $url . $file1); + verify($file1, $hash1); + + wgetfile($file2, $url . $file2); + verify($file2, $hash2); + + wgetfile($file3, $url2 . $file3); + verify($file3, $hash3); + + "$file1, $file2, $file3"; +} + +sub az6027{ + my $firmware = "dvb-usb-az6027-03.fw"; + my $url = "http://linux.terratec.de/files/TERRATEC_S7/$firmware"; + + wgetfile($firmware, $url); + + $firmware; +} + +sub lme2510_lg { + my $sourcefile = "LMEBDA_DVBS.sys"; + my $hash = "fc6017ad01e79890a97ec53bea157ed2"; + my $outfile = "dvb-usb-lme2510-lg.fw"; + my $hasho = "caa065d5fdbd2c09ad57b399bbf55cad"; + + checkstandard(); + + verify($sourcefile, $hash); + extract($sourcefile, 4168, 3841, $outfile); + verify($outfile, $hasho); + $outfile; +} + +sub lme2510c_s7395 { + my $sourcefile = "US2A0D.sys"; + my $hash = "b0155a8083fb822a3bd47bc360e74601"; + my $outfile = "dvb-usb-lme2510c-s7395.fw"; + my $hasho = "3a3cf1aeebd17b6ddc04cebe131e94cf"; + + checkstandard(); + + verify($sourcefile, $hash); + extract($sourcefile, 37248, 3720, $outfile); + verify($outfile, $hasho); + $outfile; +} + +sub lme2510c_s7395_old { + my $sourcefile = "LMEBDA_DVBS7395C.sys"; + my $hash = "7572ae0eb9cdf91baabd7c0ba9e09b31"; + my $outfile = "dvb-usb-lme2510c-s7395.fw"; + my $hasho = "90430c5b435eb5c6f88fd44a9d950674"; + + checkstandard(); + + verify($sourcefile, $hash); + extract($sourcefile, 4208, 3881, $outfile); + verify($outfile, $hasho); + $outfile; +} + +sub drxk { + my $url = "http://l4m-daten.de/files/"; + my $zipfile = "DDTuner.zip"; + my $hash = "f5a37b9a20a3534997997c0b1382a3e5"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + my $drvfile = "DDTuner.sys"; + my $fwfile = "drxk_a3.mc"; + + checkstandard(); + + wgetfile($zipfile, $url . $zipfile); + verify($zipfile, $hash); + unzip($zipfile, $tmpdir); + extract("$tmpdir/$drvfile", 0x14dd8, 15634, "$fwfile"); + + "$fwfile" +} + +sub drxk_hauppauge_hvr930c { + my $url = "http://www.wintvcd.co.uk/drivers/"; + my $zipfile = "HVR-9x0_5_10_325_28153_SIGNED.zip"; + my $hash = "83ab82e7e9480ec8bf1ae0155ca63c88"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + my $drvfile = "HVR-900/emOEM.sys"; + my $fwfile = "dvb-usb-hauppauge-hvr930c-drxk.fw"; + + checkstandard(); + + wgetfile($zipfile, $url . $zipfile); + verify($zipfile, $hash); + unzip($zipfile, $tmpdir); + extract("$tmpdir/$drvfile", 0x117b0, 42692, "$fwfile"); + + "$fwfile" +} + +sub drxk_terratec_h5 { + my $url = "https://linuxtv.org/downloads/firmware/"; + my $hash = "19000dada8e2741162ccc50cc91fa7f1"; + my $fwfile = "dvb-usb-terratec-h5-drxk.fw"; + + checkstandard(); + + wgetfile($fwfile, $url . $fwfile); + verify($fwfile, $hash); + + "$fwfile" +} + +sub drxk_terratec_htc_stick { + my $url = "http://ftp.terratec.de/Receiver/Cinergy_HTC_Stick/Updates/History/"; + my $zipfile = "Cinergy_HTC_Stick_Drv_5.09.1202.00_XP_Vista_7.exe"; + my $hash = "6722a2442a05423b781721fbc069ed5e"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 0); + my $drvfile = "Cinergy HTC Stick/BDA Driver 5.09.1202.00/Windows 32 Bit/emOEM.sys"; + my $fwfile = "dvb-usb-terratec-htc-stick-drxk.fw"; + + checkstandard(); + + wgetfile($zipfile, $url . $zipfile); + verify($zipfile, $hash); + unzip($zipfile, $tmpdir); + extract("$tmpdir/$drvfile", 0x4e5c0, 42692, "$fwfile"); + + "$fwfile" +} + +sub it9135 { + my $url = "http://www.ite.com.tw/uploads/firmware/v3.25.0.0/"; + my $file1 = "dvb-usb-it9135-01.zip"; + my $fwfile1 = "dvb-usb-it9135-01.fw"; + my $hash1 = "02fcf11174eda84745dae7e61c5ff9ba"; + my $file2 = "dvb-usb-it9135-02.zip"; + my $fwfile2 = "dvb-usb-it9135-02.fw"; + my $hash2 = "d5e1437dc24358578e07999475d4cac9"; + + checkstandard(); + + wgetfile($file1, $url . $file1); + unzip($file1, ""); + verify("$fwfile1", $hash1); + + wgetfile($file2, $url . $file2); + unzip($file2, ""); + verify("$fwfile2", $hash2); + + "$file1 $file2" +} + +sub tda10071 { + my $sourcefile = "PCTV_460e_reference.zip"; + my $url = "ftp://ftp.pctvsystems.com/TV/driver/PCTV%2070e%2080e%20100e%20320e%20330e%20800e/"; + my $hash = "4403de903bf2593464c8d74bbc200a57"; + my $fwfile = "dvb-fe-tda10071.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url . $sourcefile); + verify($sourcefile, $hash); + unzip($sourcefile, $tmpdir); + extract("$tmpdir/PCTV\ 70e\ 80e\ 100e\ 320e\ 330e\ 800e/32\ bit/emOEM.sys", 0x67d38, 40504, $fwfile); + + "$fwfile"; +} + +sub drxk_pctv { + my $sourcefile = "PCTV_460e_reference.zip"; + my $url = "ftp://ftp.pctvsystems.com/TV/driver/PCTV%2070e%2080e%20100e%20320e%20330e%20800e/"; + my $hash = "4403de903bf2593464c8d74bbc200a57"; + my $fwfile = "dvb-demod-drxk-pctv.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url . $sourcefile); + verify($sourcefile, $hash); + unzip($sourcefile, $tmpdir); + extract("$tmpdir/PCTV\ 70e\ 80e\ 100e\ 320e\ 330e\ 800e/32\ bit/emOEM.sys", 0x72b80, 42692, $fwfile); + + "$fwfile"; +} + +sub sms1xxx_hcw { + my $url = "http://steventoth.net/linux/sms1xxx/"; + my %files = ( + 'sms1xxx-hcw-55xxx-dvbt-01.fw' => "afb6f9fb9a71d64392e8564ef9577e5a", + 'sms1xxx-hcw-55xxx-dvbt-02.fw' => "b44807098ba26e52cbedeadc052ba58f", + 'sms1xxx-hcw-55xxx-isdbt-02.fw' => "dae934eeea85225acbd63ce6cfe1c9e4", + ); + + checkstandard(); + + my $allfiles; + foreach my $fwfile (keys %files) { + wgetfile($fwfile, "$url/$fwfile"); + verify($fwfile, $files{$fwfile}); + $allfiles .= " $fwfile"; + } + + $allfiles =~ s/^\s//; + + $allfiles; +} + +sub si2165 { + my $sourcefile = "model_111xxx_122xxx_driver_6_0_119_31191_WHQL.zip"; + my $url = "http://www.hauppauge.de/files/drivers/"; + my $hash = "76633e7c76b0edee47c3ba18ded99336"; + my $fwfile = "dvb-demod-si2165.fw"; + my $tmpdir = tempdir(DIR => "/tmp", CLEANUP => 1); + + checkstandard(); + + wgetfile($sourcefile, $url . $sourcefile); + verify($sourcefile, $hash); + unzip($sourcefile, $tmpdir); + extract("$tmpdir/Driver10/Hcw10bda.sys", 0x80788, 0x81E08-0x80788, "$tmpdir/fw1"); + + delzero("$tmpdir/fw1","$tmpdir/fw1-1"); + #verify("$tmpdir/fw1","5e0909858fdf0b5b09ad48b9fe622e70"); + + my $CRC="\x0A\xCC"; + my $BLOCKS_MAIN="\x27"; + open FW,">$fwfile"; + print FW "\x01\x00"; # just a version id for the driver itself + print FW "\x9A"; # fw version + print FW "\x00"; # padding + print FW "$BLOCKS_MAIN"; # number of blocks of main part + print FW "\x00"; # padding + print FW "$CRC"; # 16bit crc value of main part + appendfile(FW,"$tmpdir/fw1"); + + "$fwfile"; +} + +# --------------------------------------------------------------- +# Utilities + +sub checkstandard { + if (system("which unzip > /dev/null 2>&1")) { + die "This firmware requires the unzip command - see ftp://ftp.info-zip.org/pub/infozip/UnZip.html\n"; + } + if (system("which md5sum > /dev/null 2>&1")) { + die "This firmware requires the md5sum command - see http://www.gnu.org/software/coreutils/\n"; + } + if (system("which wget > /dev/null 2>&1")) { + die "This firmware requires the wget command - see http://wget.sunsite.dk/\n"; + } +} + +sub checkunshield { + if (system("which unshield > /dev/null 2>&1")) { + die "This firmware requires the unshield command - see http://sourceforge.net/projects/synce/\n"; + } +} + +sub wgetfile { + my ($sourcefile, $url) = @_; + + if (! -f $sourcefile) { + system("wget -O \"$sourcefile\" \"$url\"") and die "wget failed - unable to download firmware"; + } +} + +sub unzip { + my ($sourcefile, $todir) = @_; + + $status = system("unzip -q -o -d \"$todir\" \"$sourcefile\" 2>/dev/null" ); + if ((($status >> 8) > 2) || (($status & 0xff) != 0)) { + die ("unzip failed - unable to extract firmware"); + } +} + +sub unshield { + my ($sourcefile, $todir) = @_; + + system("unshield x -d \"$todir\" \"$sourcefile\" > /dev/null" ) and die ("unshield failed - unable to extract firmware"); +} + +sub verify { + my ($filename, $hash) = @_; + my ($testhash); + + open(CMD, "md5sum \"$filename\"|"); + $testhash = <CMD>; + $testhash =~ /([a-zA-Z0-9]*)/; + $testhash = $1; + close CMD; + die "Hash of extracted file does not match!\n" if ($testhash ne $hash); +} + +sub copy { + my ($from, $to) = @_; + + system("cp -f \"$from\" \"$to\"") and die ("cp failed"); +} + +sub extract { + my ($infile, $offset, $length, $outfile) = @_; + my ($chunklength, $buf, $rcount); + + open INFILE, "<$infile"; + open OUTFILE, ">$outfile"; + sysseek(INFILE, $offset, SEEK_SET); + while($length > 0) { + # Calc chunk size + $chunklength = 2048; + $chunklength = $length if ($chunklength > $length); + + $rcount = sysread(INFILE, $buf, $chunklength); + die "Ran out of data\n" if ($rcount != $chunklength); + syswrite(OUTFILE, $buf); + $length -= $rcount; + } + close INFILE; + close OUTFILE; +} + +sub appendfile { + my ($FH, $infile) = @_; + my ($buf); + + open INFILE, "<$infile"; + while(1) { + $rcount = sysread(INFILE, $buf, 2048); + last if ($rcount == 0); + print $FH $buf; + } + close(INFILE); +} + +sub delzero{ + my ($infile,$outfile) =@_; + + open INFILE,"<$infile"; + open OUTFILE,">$outfile"; + while (1){ + $rcount=sysread(INFILE,$buf,22); + $len=ord(substr($buf,0,1)); + print OUTFILE substr($buf,0,1); + print OUTFILE substr($buf,2,$len+3); + last if ($rcount<1); + printf OUTFILE "%c",0; +#print $len." ".length($buf)."\n"; + + } + close(INFILE); + close(OUTFILE); +} + +sub syntax() { + print STDERR "syntax: get_dvb_firmware <component>\n"; + print STDERR "Supported components:\n"; + @components = sort @components; + for($i=0; $i < scalar(@components); $i++) { + print STDERR "\t" . $components[$i] . "\n"; + } + exit(1); +} diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl new file mode 100755 index 000000000..16b80a700 --- /dev/null +++ b/scripts/get_maintainer.pl @@ -0,0 +1,2673 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2007, Joe Perches <joe@perches.com> +# created from checkpatch.pl +# +# Print selected MAINTAINERS information for +# the files modified in a patch or for a file +# +# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch> +# perl scripts/get_maintainer.pl [OPTIONS] -f <file> + +use warnings; +use strict; + +my $P = $0; +my $V = '0.26'; + +use Getopt::Long qw(:config no_auto_abbrev); +use Cwd; +use File::Find; +use File::Spec::Functions; +use open qw(:std :encoding(UTF-8)); +use JSON::PP; + +my $cur_path = fastgetcwd() . '/'; +my $lk_path = "./"; +my $email = 1; +my $email_usename = 1; +my $email_maintainer = 1; +my $email_reviewer = 1; +my $email_fixes = 1; +my $email_list = 1; +my $email_moderated_list = 1; +my $email_subscriber_list = 0; +my $email_git_penguin_chiefs = 0; +my $email_git = 0; +my $email_git_all_signature_types = 0; +my $email_git_blame = 0; +my $email_git_blame_signatures = 1; +my $email_git_fallback = 1; +my $email_git_min_signatures = 1; +my $email_git_max_maintainers = 5; +my $email_git_min_percent = 5; +my $email_git_since = "1-year-ago"; +my $email_hg_since = "-365"; +my $interactive = 0; +my $email_remove_duplicates = 1; +my $email_use_mailmap = 1; +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 = ""; +my $keywords = 1; +my $keywords_in_file = 0; +my $sections = 0; +my $email_file_emails = 0; +my $from_filename = 0; +my $pattern_depth = 0; +my $self_test = undef; +my $version = 0; +my $help = 0; +my $json = 0; +my $find_maintainer_files = 0; +my $maintainer_path; +my $vcs_used = 0; + +my $exit = 0; + +my @files = (); +my @fixes = (); # If a patch description includes Fixes: lines +my @range = (); +my @keyword_tvi = (); +my @file_emails = (); + +my %commit_author_hash; +my %commit_signer_hash; + +my @penguin_chief = (); +push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org"); +#Andrew wants in on most everything - 2009/01/14 +#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org"); + +my @penguin_chief_names = (); +foreach my $chief (@penguin_chief) { + if ($chief =~ m/^(.*):(.*)/) { + my $chief_name = $1; + my $chief_addr = $2; + push(@penguin_chief_names, $chief_name); + } +} +my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)"; + +# Signature types of people who are either +# a) responsible for the code in question, or +# b) familiar enough with it to give relevant feedback +my @signature_tags = (); +push(@signature_tags, "Signed-off-by:"); +push(@signature_tags, "Reviewed-by:"); +push(@signature_tags, "Acked-by:"); + +my $signature_pattern = "\(" . join("|", @signature_tags) . "\)"; + +# rfc822 email address - preloaded methods go here. +my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; +my $rfc822_char = '[\\000-\\377]'; + +# VCS command support: class-like functions and strings + +my %VCS_cmds; + +my %VCS_cmds_git = ( + "execute_cmd" => \&git_execute_cmd, + "available" => '(which("git") ne "") && (-e ".git")', + "find_signers_cmd" => + "git log --no-color --follow --since=\$email_git_since " . + '--numstat --no-merges ' . + '--format="GitCommit: %H%n' . + 'GitAuthor: %an <%ae>%n' . + 'GitDate: %aD%n' . + 'GitSubject: %s%n' . + '%b%n"' . + " -- \$file", + "find_commit_signers_cmd" => + "git log --no-color " . + '--numstat ' . + '--format="GitCommit: %H%n' . + 'GitAuthor: %an <%ae>%n' . + 'GitDate: %aD%n' . + 'GitSubject: %s%n' . + '%b%n"' . + " -1 \$commit", + "find_commit_author_cmd" => + "git log --no-color " . + '--numstat ' . + '--format="GitCommit: %H%n' . + 'GitAuthor: %an <%ae>%n' . + 'GitDate: %aD%n' . + 'GitSubject: %s%n"' . + " -1 \$commit", + "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file", + "blame_file_cmd" => "git blame -l \$file", + "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})", + "blame_commit_pattern" => "^([0-9a-f]+) ", + "author_pattern" => "^GitAuthor: (.*)", + "subject_pattern" => "^GitSubject: (.*)", + "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$", + "file_exists_cmd" => "git ls-files \$file", + "list_files_cmd" => "git ls-files \$file", +); + +my %VCS_cmds_hg = ( + "execute_cmd" => \&hg_execute_cmd, + "available" => '(which("hg") ne "") && (-d ".hg")', + "find_signers_cmd" => + "hg log --date=\$email_hg_since " . + "--template='HgCommit: {node}\\n" . + "HgAuthor: {author}\\n" . + "HgSubject: {desc}\\n'" . + " -- \$file", + "find_commit_signers_cmd" => + "hg log " . + "--template='HgSubject: {desc}\\n'" . + " -r \$commit", + "find_commit_author_cmd" => + "hg log " . + "--template='HgCommit: {node}\\n" . + "HgAuthor: {author}\\n" . + "HgSubject: {desc|firstline}\\n'" . + " -r \$commit", + "blame_range_cmd" => "", # not supported + "blame_file_cmd" => "hg blame -n \$file", + "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})", + "blame_commit_pattern" => "^([ 0-9a-f]+):", + "author_pattern" => "^HgAuthor: (.*)", + "subject_pattern" => "^HgSubject: (.*)", + "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$", + "file_exists_cmd" => "hg files \$file", + "list_files_cmd" => "hg manifest -R \$file", +); + +my $conf = which_conf(".get_maintainer.conf"); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable .get_maintainer.conf file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +my @ignore_emails = (); +my $ignore_file = which_conf(".get_maintainer.ignore"); +if (-f $ignore_file) { + open(my $ignore, '<', "$ignore_file") + or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n"; + while (<$ignore>) { + my $line = $_; + + $line =~ s/\s*\n?$//; + $line =~ s/^\s*//; + $line =~ s/\s+$//; + $line =~ s/#.*$//; + + next if ($line =~ m/^\s*$/); + if (rfc822_valid($line)) { + push(@ignore_emails, $line); + } + } + close($ignore); +} + +if ($#ARGV > 0) { + foreach (@ARGV) { + if ($_ =~ /^-{1,2}self-test(?:=|$)/) { + die "$P: using --self-test does not allow any other option or argument\n"; + } + } +} + +if (!GetOptions( + 'email!' => \$email, + 'git!' => \$email_git, + 'git-all-signature-types!' => \$email_git_all_signature_types, + 'git-blame!' => \$email_git_blame, + 'git-blame-signatures!' => \$email_git_blame_signatures, + 'git-fallback!' => \$email_git_fallback, + 'git-chief-penguins!' => \$email_git_penguin_chiefs, + 'git-min-signatures=i' => \$email_git_min_signatures, + 'git-max-maintainers=i' => \$email_git_max_maintainers, + 'git-min-percent=i' => \$email_git_min_percent, + 'git-since=s' => \$email_git_since, + 'hg-since=s' => \$email_hg_since, + 'i|interactive!' => \$interactive, + 'remove-duplicates!' => \$email_remove_duplicates, + 'mailmap!' => \$email_use_mailmap, + 'm!' => \$email_maintainer, + 'r!' => \$email_reviewer, + 'n!' => \$email_usename, + 'l!' => \$email_list, + 'fixes!' => \$email_fixes, + 'moderated!' => \$email_moderated_list, + 's!' => \$email_subscriber_list, + 'multiline!' => \$output_multiline, + 'roles!' => \$output_roles, + 'rolestats!' => \$output_rolestats, + '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, + 'kf|keywords-in-file!' => \$keywords_in_file, + 'sections!' => \$sections, + 'fe|file-emails!' => \$email_file_emails, + 'f|file' => \$from_filename, + 'find-maintainer-files' => \$find_maintainer_files, + 'mpath|maintainer-path=s' => \$maintainer_path, + 'self-test:s' => \$self_test, + 'json!' => \$json, + 'v|version' => \$version, + 'h|help|usage' => \$help, + )) { + die "$P: invalid argument - use --help if necessary\n"; +} + +if ($help != 0) { + usage(); + exit 0; +} + +if ($version != 0) { + print("${P} ${V}\n"); + exit 0; +} + +if (defined $self_test) { + read_all_maintainer_files(); + self_test(); + exit 0; +} + +if (-t STDIN && !@ARGV) { + # We're talking to a terminal, but have no command line arguments. + die "$P: missing patchfile or -f file - use --help if necessary\n"; +} + +$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; + $email_list = 0; + $scm = 0; + $status = 0; + $subsystem = 0; + $web = 0; + $bug = 0; + $keywords = 0; + $keywords_in_file = 0; + $interactive = 0; +} else { + my $selections = $email + $scm + $status + $subsystem + $web + $bug; + if ($selections == 0) { + die "$P: Missing required option: email, scm, status, subsystem, web or bug\n"; + } +} + +if ($email && + ($email_maintainer + $email_reviewer + + $email_list + $email_subscriber_list + + $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) { + die "$P: Please select at least 1 email option\n"; +} + +if ($tree && !top_of_kernel_tree($lk_path)) { + die "$P: The current directory does not appear to be " + . "a linux kernel source tree.\n"; +} + +## Read MAINTAINERS for type/value pairs + +my @typevalue = (); +my %keyword_hash; +my @mfiles = (); +my @self_test_info = (); + +sub read_maintainer_file { + my ($file) = @_; + + open (my $maint, '<', "$file") + or die "$P: Can't open MAINTAINERS file '$file': $!\n"; + my $i = 1; + while (<$maint>) { + my $line = $_; + chomp $line; + + if ($line =~ m/^([A-Z]):\s*(.*)/) { + my $type = $1; + my $value = $2; + + ##Filename pattern matching + if ($type eq "F" || $type eq "X") { + $value =~ s@\.@\\\.@g; ##Convert . to \. + $value =~ s/\*\*/\x00/g; ##Convert ** to placeholder + $value =~ s/\*/\.\*/g; ##Convert * to .* + $value =~ s/\?/\./g; ##Convert ? to . + $value =~ s/\x00/(?:.*)/g; ##Convert placeholder to (?:.*) + ##if pattern is a directory and it lacks a trailing slash, add one + if ((-d $value)) { + $value =~ s@([^/])$@$1/@; + } + } elsif ($type eq "K") { + $keyword_hash{@typevalue} = $value; + } + push(@typevalue, "$type:$value"); + } elsif (!(/^\s*$/ || /^\s*\#/)) { + push(@typevalue, $line); + } + if (defined $self_test) { + push(@self_test_info, {file=>$file, linenr=>$i, line=>$line}); + } + $i++; + } + close($maint); +} + +sub find_is_maintainer_file { + my ($file) = $_; + return if ($file !~ m@/MAINTAINERS$@); + $file = $File::Find::name; + return if (! -f $file); + push(@mfiles, $file); +} + +sub find_ignore_git { + return grep { $_ !~ /^\.git$/; } @_; +} + +read_all_maintainer_files(); + +sub read_all_maintainer_files { + my $path = "${lk_path}MAINTAINERS"; + if (defined $maintainer_path) { + $path = $maintainer_path; + # Perl Cookbook tilde expansion if necessary + $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex; + } + + if (-d $path) { + $path .= '/' if ($path !~ m@/$@); + if ($find_maintainer_files) { + find( { wanted => \&find_is_maintainer_file, + preprocess => \&find_ignore_git, + no_chdir => 1, + }, "$path"); + } else { + opendir(DIR, "$path") or die $!; + my @files = readdir(DIR); + closedir(DIR); + foreach my $file (@files) { + push(@mfiles, "$path$file") if ($file !~ /^\./); + } + } + } elsif (-f "$path") { + push(@mfiles, "$path"); + } else { + die "$P: MAINTAINER file not found '$path'\n"; + } + die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0); + foreach my $file (@mfiles) { + read_maintainer_file("$file"); + } +} + +sub maintainers_in_file { + my ($file) = @_; + + return if ($file =~ m@\bMAINTAINERS$@); + + if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) { + open(my $f, '<', $file) + or die "$P: Can't open $file: $!\n"; + my $text = do { local($/) ; <$f> }; + close($f); + + my @poss_addr = $text =~ m$[\p{L}\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g; + push(@file_emails, clean_file_emails(@poss_addr)); + } +} + +# +# Read mail address map +# + +my $mailmap; + +read_mailmap(); + +sub read_mailmap { + $mailmap = { + names => {}, + addresses => {} + }; + + return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap")); + + open(my $mailmap_file, '<', "${lk_path}.mailmap") + or warn "$P: Can't open .mailmap: $!\n"; + + while (<$mailmap_file>) { + s/#.*$//; #strip comments + s/^\s+|\s+$//g; #trim + + next if (/^\s*$/); #skip empty lines + #entries have one of the following formats: + # name1 <mail1> + # <mail1> <mail2> + # name1 <mail1> <mail2> + # name1 <mail1> name2 <mail2> + # (see man git-shortlog) + + if (/^([^<]+)<([^>]+)>$/) { + my $real_name = $1; + my $address = $2; + + $real_name =~ s/\s+$//; + ($real_name, $address) = parse_email("$real_name <$address>"); + $mailmap->{names}->{$address} = $real_name; + + } elsif (/^<([^>]+)>\s*<([^>]+)>$/) { + my $real_address = $1; + my $wrong_address = $2; + + $mailmap->{addresses}->{$wrong_address} = $real_address; + + } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) { + my $real_name = $1; + my $real_address = $2; + my $wrong_address = $3; + + $real_name =~ s/\s+$//; + ($real_name, $real_address) = + parse_email("$real_name <$real_address>"); + $mailmap->{names}->{$wrong_address} = $real_name; + $mailmap->{addresses}->{$wrong_address} = $real_address; + + } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) { + my $real_name = $1; + my $real_address = $2; + my $wrong_name = $3; + my $wrong_address = $4; + + $real_name =~ s/\s+$//; + ($real_name, $real_address) = + parse_email("$real_name <$real_address>"); + + $wrong_name =~ s/\s+$//; + ($wrong_name, $wrong_address) = + parse_email("$wrong_name <$wrong_address>"); + + my $wrong_email = format_email($wrong_name, $wrong_address, 1); + $mailmap->{names}->{$wrong_email} = $real_name; + $mailmap->{addresses}->{$wrong_email} = $real_address; + } + } + close($mailmap_file); +} + +## use the filenames on the command line or find the filenames in the patchfiles + +if (!@ARGV) { + push(@ARGV, "&STDIN"); +} + +foreach my $file (@ARGV) { + if ($file ne "&STDIN") { + $file = canonpath($file); + ##if $file is a directory and it lacks a trailing slash, add one + if ((-d $file)) { + $file =~ s@([^/])$@$1/@; + } elsif (!(-f $file)) { + die "$P: file '${file}' not found\n"; + } + } + if ($from_filename && (vcs_exists() && !vcs_file_exists($file))) { + warn "$P: file '$file' not found in version control $!\n"; + } + if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) { + $file =~ s/^\Q${cur_path}\E//; #strip any absolute path + $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree + push(@files, $file); + if ($file ne "MAINTAINERS" && -f $file && $keywords && $keywords_in_file) { + open(my $f, '<', $file) + or die "$P: Can't open $file: $!\n"; + my $text = do { local($/) ; <$f> }; + close($f); + foreach my $line (keys %keyword_hash) { + if ($text =~ m/$keyword_hash{$line}/x) { + push(@keyword_tvi, $line); + } + } + } + } else { + my $file_cnt = @files; + my $lastfile; + + open(my $patch, "< $file") + or die "$P: Can't open $file: $!\n"; + + # We can check arbitrary information before the patch + # like the commit message, mail headers, etc... + # This allows us to match arbitrary keywords against any part + # of a git format-patch generated file (subject tags, etc...) + + my $patch_prefix = ""; #Parsing the intro + + while (<$patch>) { + my $patch_line = $_; + if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) { + my $filename = $1; + push(@files, $filename); + } elsif (m/^rename (?:from|to) (\S+)\s*$/) { + my $filename = $1; + push(@files, $filename); + } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) { + my $filename1 = $1; + my $filename2 = $2; + push(@files, $filename1); + push(@files, $filename2); + } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) { + push(@fixes, $1) if ($email_fixes); + } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) { + my $filename = $1; + $filename =~ s@^[^/]*/@@; + $filename =~ s@\n@@; + $lastfile = $filename; + push(@files, $filename); + $patch_prefix = "^[+-].*"; #Now parsing the actual patch + } elsif (m/^\@\@ -(\d+),(\d+)/) { + if ($email_git_blame) { + push(@range, "$lastfile:$1:$2"); + } + } elsif ($keywords) { + foreach my $line (keys %keyword_hash) { + if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) { + push(@keyword_tvi, $line); + } + } + } + } + close($patch); + + if ($file_cnt == @files) { + warn "$P: file '${file}' doesn't appear to be a patch. " + . "Add -f to options?\n"; + } + @files = sort_and_uniq(@files); + } +} + +@file_emails = uniq(@file_emails); +@fixes = uniq(@fixes); + +my %email_hash_name; +my %email_hash_address; +my @email_to = (); +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 = (); + +my @maintainers = get_maintainers(); + +@maintainers = merge_email(@maintainers) if (@maintainers); +@scm = uniq(@scm) if ($scm); +@substatus = uniq(@substatus) if ($output_substatus); +@status = uniq(@status) if ($status); +@subsystem = uniq(@subsystem) if ($subsystem); +@web = uniq(@web) if ($web); +@bug = uniq(@bug) if ($bug); + +if ($json) { + my @json_maintainers; + for my $m (@maintainers) { + my ($addr, $role); + if ($output_roles && $m =~ /^(.*?)\s+\((.+)\)\s*$/) { + $addr = $1; + $role = $2; + } else { + $addr = $m; + } + my ($name, $email_addr) = parse_email($addr); + my %entry = (name => $name, email => $email_addr); + $entry{role} = $role if (defined $role && $role ne ''); + push(@json_maintainers, \%entry); + } + + my %result = (maintainers => \@json_maintainers); + $result{scm} = \@scm if ($scm); + $result{status} = \@status if ($status); + $result{subsystem} = \@subsystem if ($subsystem); + $result{web} = \@web if ($web); + $result{bug} = \@bug if ($bug); + + my $json_encoder = JSON::PP->new->canonical->utf8; + print($json_encoder->encode(\%result) . "\n"); +} else { + output(@maintainers) if (@maintainers); + output(@scm) if ($scm); + output(@substatus) if ($output_substatus); + output(@status) if ($status); + output(@subsystem) if ($subsystem); + output(@web) if ($web); + output(@bug) if ($bug); +} + +exit($exit); + +sub self_test { + my @lsfiles = (); + my @good_links = (); + my @bad_links = (); + my @section_headers = (); + my $index = 0; + + @lsfiles = vcs_list_files($lk_path); + + for my $x (@self_test_info) { + $index++; + + ## Section header duplication and missing section content + if (($self_test eq "" || $self_test =~ /\bsections\b/) && + $x->{line} =~ /^\S[^:]/ && + defined $self_test_info[$index] && + $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) { + my $has_S = 0; + my $has_F = 0; + my $has_ML = 0; + my $status = ""; + if (grep(m@^\Q$x->{line}\E@, @section_headers)) { + print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n"); + } else { + push(@section_headers, $x->{line}); + } + my $nextline = $index; + while (defined $self_test_info[$nextline] && + $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) { + my $type = $1; + my $value = $2; + if ($type eq "S") { + $has_S = 1; + $status = $value; + } elsif ($type eq "F" || $type eq "N") { + $has_F = 1; + } elsif ($type eq "M" || $type eq "R" || $type eq "L") { + $has_ML = 1; + } + $nextline++; + } + if (!$has_ML && $status !~ /orphan|obsolete/i) { + print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n"); + } + if (!$has_S) { + print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n"); + } + if (!$has_F) { + print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n"); + } + } + + next if ($x->{line} !~ /^([A-Z]):\s*(.*)/); + + my $type = $1; + my $value = $2; + + ## Filename pattern matching + if (($type eq "F" || $type eq "X") && + ($self_test eq "" || $self_test =~ /\bpatterns\b/)) { + $value =~ s@\.@\\\.@g; ##Convert . to \. + $value =~ s/\*\*/\x00/g; ##Convert ** to placeholder + $value =~ s/\*/\.\*/g; ##Convert * to .* + $value =~ s/\?/\./g; ##Convert ? to . + $value =~ s/\x00/(?:.*)/g; ##Convert placeholder to (?:.*) + ##if pattern is a directory and it lacks a trailing slash, add one + if ((-d $value)) { + $value =~ s@([^/])$@$1/@; + } + if (!grep(m@^$value@, @lsfiles)) { + print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n"); + } + + ## Link reachability + } elsif (($type eq "W" || $type eq "Q" || $type eq "B") && + $value =~ /^https?:/ && + ($self_test eq "" || $self_test =~ /\blinks\b/)) { + next if (grep(m@^\Q$value\E$@, @good_links)); + my $isbad = 0; + if (grep(m@^\Q$value\E$@, @bad_links)) { + $isbad = 1; + } else { + my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`; + if ($? == 0) { + push(@good_links, $value); + } else { + push(@bad_links, $value); + $isbad = 1; + } + } + if ($isbad) { + print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n"); + } + + ## SCM reachability + } elsif ($type eq "T" && + ($self_test eq "" || $self_test =~ /\bscm\b/)) { + next if (grep(m@^\Q$value\E$@, @good_links)); + my $isbad = 0; + if (grep(m@^\Q$value\E$@, @bad_links)) { + $isbad = 1; + } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) { + print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n"); + } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) { + my $url = $1; + my $branch = ""; + $branch = $3 if $3; + my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`; + if ($? == 0) { + push(@good_links, $value); + } else { + push(@bad_links, $value); + $isbad = 1; + } + } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) { + my $url = $1; + my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`; + if ($? == 0) { + push(@good_links, $value); + } else { + push(@bad_links, $value); + $isbad = 1; + } + } + if ($isbad) { + print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n"); + } + } + } +} + +sub ignore_email_address { + my ($address) = @_; + + foreach my $ignore (@ignore_emails) { + return 1 if ($ignore eq $address); + } + + return 0; +} + +sub range_is_maintained { + my ($start, $end) = @_; + + for (my $i = $start; $i < $end; $i++) { + my $line = $typevalue[$i]; + if ($line =~ m/^([A-Z]):\s*(.*)/) { + my $type = $1; + my $value = $2; + if ($type eq 'S') { + if ($value =~ /(maintain|support)/i) { + return 1; + } + } + } + } + return 0; +} + +sub range_has_maintainer { + my ($start, $end) = @_; + + for (my $i = $start; $i < $end; $i++) { + my $line = $typevalue[$i]; + if ($line =~ m/^([A-Z]):\s*(.*)/) { + my $type = $1; + my $value = $2; + if ($type eq 'M') { + return 1; + } + } + } + return 0; +} + +sub get_maintainers { + %email_hash_name = (); + %email_hash_address = (); + %commit_author_hash = (); + %commit_signer_hash = (); + @email_to = (); + %hash_list_to = (); + @list_to = (); + @scm = (); + @web = (); + @bug = (); + @subsystem = (); + @status = (); + @substatus = (); + %deduplicate_name_hash = (); + %deduplicate_address_hash = (); + if ($email_git_all_signature_types) { + $signature_pattern = "(.+?)[Bb][Yy]:"; + } else { + $signature_pattern = "\(" . join("|", @signature_tags) . "\)"; + } + + # Find responsible parties + + my %exact_pattern_match_hash = (); + + foreach my $file (@files) { + + my %hash; + my $tvi = find_first_section(); + while ($tvi < @typevalue) { + my $start = find_starting_index($tvi); + my $end = find_ending_index($tvi); + my $exclude = 0; + my $i; + + #Do not match excluded file patterns + + for ($i = $start; $i < $end; $i++) { + my $line = $typevalue[$i]; + if ($line =~ m/^([A-Z]):\s*(.*)/) { + my $type = $1; + my $value = $2; + if ($type eq 'X') { + if (file_match_pattern($file, $value)) { + $exclude = 1; + last; + } + } + } + } + + if (!$exclude) { + for ($i = $start; $i < $end; $i++) { + my $line = $typevalue[$i]; + if ($line =~ m/^([A-Z]):\s*(.*)/) { + my $type = $1; + my $value = $2; + if ($type eq 'F') { + if (file_match_pattern($file, $value)) { + my $value_pd = ($value =~ tr@/@@); + my $file_pd = ($file =~ tr@/@@); + $value_pd++ if (substr($value,-1,1) ne "/"); + $value_pd = -1 if ($value =~ /^(\.\*|\(\?:\.\*\))/); + if ($value_pd >= $file_pd && + range_is_maintained($start, $end) && + range_has_maintainer($start, $end)) { + $exact_pattern_match_hash{$file} = 1; + } + if ($pattern_depth == 0 || + (($file_pd - $value_pd) < $pattern_depth)) { + $hash{$tvi} = $value_pd; + } + } + } elsif ($type eq 'N') { + if ($file =~ m/$value/x) { + $hash{$tvi} = 0; + } + } + } + } + } + $tvi = $end + 1; + } + + foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { + add_categories($line, ""); + if ($sections) { + my $i; + my $start = find_starting_index($line); + my $end = find_ending_index($line); + for ($i = $start; $i < $end; $i++) { + my $line = $typevalue[$i]; + if ($line =~ /^[FX]:/) { ##Restore file patterns + $line =~ s/([^\\])\.([^\*])/$1\?$2/g; + $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ? + $line =~ s/\\\./\./g; ##Convert \. to . + $line =~ s/\(\?:\.\*\)/\*\*/g; ##Convert (?:.*) to ** + $line =~ s/\.\*/\*/g; ##Convert .* to * + } + my $count = $line =~ s/^([A-Z]):/$1:\t/g; + if ($letters eq "" || (!$count || $letters =~ /$1/i)) { + print("$line\n"); + } + } + print("\n"); + } + } + + maintainers_in_file($file); + } + + if ($keywords) { + @keyword_tvi = sort_and_uniq(@keyword_tvi); + foreach my $line (@keyword_tvi) { + add_categories($line, ":Keyword:$keyword_hash{$line}"); + } + } + + foreach my $email (@email_to, @list_to) { + $email->[0] = deduplicate_email($email->[0]); + } + + foreach my $file (@files) { + if ($email && + ($email_git || + ($email_git_fallback && + $file !~ /MAINTAINERS$/ && + !$exact_pattern_match_hash{$file}))) { + vcs_file_signoffs($file); + } + if ($email && $email_git_blame) { + vcs_file_blame($file); + } + } + + if ($email) { + foreach my $chief (@penguin_chief) { + if ($chief =~ m/^(.*):(.*)/) { + my $email_address; + + $email_address = format_email($1, $2, $email_usename); + if ($email_git_penguin_chiefs) { + push(@email_to, [$email_address, 'chief penguin']); + } else { + @email_to = grep($_->[0] !~ /${email_address}/, @email_to); + } + } + } + + foreach my $email (@file_emails) { + $email = mailmap_email($email); + my ($name, $address) = parse_email($email); + + my $tmp_email = format_email($name, $address, $email_usename); + push_email_address($tmp_email, ''); + add_role($tmp_email, 'in file'); + } + } + + foreach my $fix (@fixes) { + vcs_add_commit_signers($fix, "blamed_fixes"); + } + + my @to = (); + if ($email || $email_list) { + if ($email) { + @to = (@to, @email_to); + } + if ($email_list) { + @to = (@to, @list_to); + } + } + + if ($interactive) { + @to = interactive_get_maintainers(\@to); + } + + return @to; +} + +sub file_match_pattern { + my ($file, $pattern) = @_; + if (substr($pattern, -1) eq "/") { + if ($file =~ m@^$pattern@) { + return 1; + } + } else { + if ($file =~ m@^$pattern@) { + my $s1 = ($file =~ tr@/@@); + my $s2 = ($pattern =~ tr@/@@); + if ($s1 == $s2 || $pattern =~ /\(\?:/) { + return 1; + } + } + } + return 0; +} + +sub usage { + print <<EOT; +usage: $P [options] patchfile + $P [options] -f file|directory +version: $V + +MAINTAINER field selection options: + --email => print email address(es) if any + --git => include recent git \*-by: signers + --git-all-signature-types => include signers regardless of signature type + or use only ${signature_pattern} signers (default: $email_git_all_signature_types) + --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback) + --git-chief-penguins => include ${penguin_chiefs} + --git-min-signatures => number of signatures required (default: $email_git_min_signatures) + --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers) + --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent) + --git-blame => use git blame to find modified commits for patch or file + --git-blame-signatures => when used with --git-blame, also include all commit signers + --git-since => git history to use (default: $email_git_since) + --hg-since => hg history to use (default: $email_hg_since) + --interactive => display a menu (mostly useful if used with the --git option) + --m => include maintainer(s) if any + --r => include reviewer(s) if any + --n => include name 'Full Name <addr\@domain.tld>' + --l => include list(s) if any + --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 (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 + using --separator also sets --nomultiline if --separator is not [, ] + --multiline => print 1 entry per line + --json => output results as JSON + +Other options: + --pattern-depth => Number of pattern directory traversals (default: 0 (all)) + --keywords => scan patch for keywords (default: $keywords) + --keywords-in-file => scan file for keywords (default: $keywords_in_file) + --sections => print all of the subsystem sections with pattern matches + --letters => print all matching 'letter' types from all matching sections + --mailmap => use .mailmap file (default: $email_use_mailmap) + --no-tree => run without a kernel tree + --self-test => show potential issues with MAINTAINERS file content + --version => show version + --help => show this help information + +Default options: + [--email --tree --nogit --git-fallback --m --r --n --l --multiline + --pattern-depth=0 --remove-duplicates --rolestats --keywords] + +Notes: + Using "-f directory" may give unexpected results: + Used with "--git", git signators for _all_ files in and below + directory are examined as git recurses directories. + Any specified X: (exclude) pattern matches are _not_ ignored. + Used with "--nogit", directory is used as a pattern match, + no individual file within the directory or subdirectory + is matched. + Used with "--git-blame", does not iterate all files in directory + Using "--git-blame" is slow and may add old committers and authors + that are no longer active maintainers to the output. + Using "--roles" or "--rolestats" with git send-email --cc-cmd or any + other automated tools that expect only ["name"] <email address> + may not work because of additional output after <email address>. + Using "--rolestats" and "--git-blame" shows the #/total=% commits, + not the percentage of the entire file authored. # of commits is + not a good measure of amount of code authored. 1 major commit may + contain a thousand lines, 5 trivial commits may modify a single line. + If git is not installed, but mercurial (hg) is installed and an .hg + repository exists, the following options apply to mercurial: + --git, + --git-min-signatures, --git-max-maintainers, --git-min-percent, and + --git-blame + Use --hg-since not --git-since to control date selection + File ".get_maintainer.conf", if it exists in the linux kernel source root + directory, can change whatever get_maintainer defaults are desired. + Entries in this file can be any command line argument. + This file is prepended to any additional command line arguments. + Multiple lines and # comments are allowed. + Most options have both positive and negative forms. + The negative forms for --<foo> are --no<foo> and --no-<foo>. + +EOT +} + +sub top_of_kernel_tree { + my ($lk_path) = @_; + + if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") { + $lk_path .= "/"; + } + if ( (-f "${lk_path}COPYING") + && (-f "${lk_path}CREDITS") + && (-f "${lk_path}Kbuild") + && (-e "${lk_path}MAINTAINERS") + && (-f "${lk_path}Makefile") + && (-f "${lk_path}README") + && (-d "${lk_path}Documentation") + && (-d "${lk_path}arch") + && (-d "${lk_path}include") + && (-d "${lk_path}drivers") + && (-d "${lk_path}fs") + && (-d "${lk_path}init") + && (-d "${lk_path}ipc") + && (-d "${lk_path}kernel") + && (-d "${lk_path}lib") + && (-d "${lk_path}scripts")) { + return 1; + } + return 0; +} + +sub escape_name { + my ($name) = @_; + + if ($name =~ /[^\w \-]/ai) { ##has "must quote" chars + $name =~ s/(?<!\\)"/\\"/g; ##escape quotes + $name = "\"$name\""; + } + + return $name; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $address = ""; + + if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) { + $name = $1; + $address = $2; + } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) { + $address = $1; + } elsif ($formatted_email =~ /^(.+\@\S*).*$/) { + $address = $1; + } + + $name =~ s/^\s+|\s+$//g; + $name =~ s/^\"|\"$//g; + $name = escape_name($name); + $address =~ s/^\s+|\s+$//g; + + return ($name, $address); +} + +sub format_email { + my ($name, $address, $usename) = @_; + + my $formatted_email; + + $name =~ s/^\s+|\s+$//g; + $name =~ s/^\"|\"$//g; + $name = escape_name($name); + $address =~ s/^\s+|\s+$//g; + + if ($usename) { + if ("$name" eq "") { + $formatted_email = "$address"; + } else { + $formatted_email = "$name <$address>"; + } + } else { + $formatted_email = $address; + } + + return $formatted_email; +} + +sub find_first_section { + my $index = 0; + + while ($index < @typevalue) { + my $tv = $typevalue[$index]; + if (($tv =~ m/^([A-Z]):\s*(.*)/)) { + last; + } + $index++; + } + + return $index; +} + +sub find_starting_index { + my ($index) = @_; + + while ($index > 0) { + my $tv = $typevalue[$index]; + if (!($tv =~ m/^([A-Z]):\s*(.*)/)) { + last; + } + $index--; + } + + return $index; +} + +sub find_ending_index { + my ($index) = @_; + + while ($index < @typevalue) { + my $tv = $typevalue[$index]; + if (!($tv =~ m/^([A-Z]):\s*(.*)/)) { + last; + } + $index++; + } + + return $index; +} + +sub get_subsystem_name { + my ($index) = @_; + + my $start = find_starting_index($index); + + my $subsystem = $typevalue[$start]; + if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) { + $subsystem = substr($subsystem, 0, $output_section_maxlen - 3); + $subsystem =~ s/\s*$//; + $subsystem = $subsystem . "..."; + } + return $subsystem; +} + +sub get_maintainer_role { + my ($index) = @_; + + my $i; + my $start = find_starting_index($index); + my $end = find_ending_index($index); + + my $role = "maintainer"; + my $subsystem = get_subsystem_name($index); + my $status = "unknown"; + + for ($i = $start + 1; $i < $end; $i++) { + my $tv = $typevalue[$i]; + if ($tv =~ m/^([A-Z]):\s*(.*)/) { + my $ptype = $1; + my $pvalue = $2; + if ($ptype eq "S") { + $status = $pvalue; + } + } + } + + $status = lc($status); + if ($status eq "buried alive in reporters") { + $role = "chief penguin"; + } + + return $role . ":" . $subsystem; +} + +sub get_list_role { + my ($index) = @_; + + my $subsystem = get_subsystem_name($index); + + if ($subsystem eq "THE REST") { + $subsystem = ""; + } + + return $subsystem; +} + +sub add_categories { + my ($index, $suffix) = @_; + + my $i; + my $start = find_starting_index($index); + my $end = find_ending_index($index); + + my $subsystem = $typevalue[$start]; + push(@subsystem, $subsystem); + my $status = "Unknown"; + + for ($i = $start + 1; $i < $end; $i++) { + my $tv = $typevalue[$i]; + if ($tv =~ m/^([A-Z]):\s*(.*)/) { + my $ptype = $1; + my $pvalue = $2; + if ($ptype eq "L") { + my $list_address = $pvalue; + my $list_additional = ""; + my $list_role = get_list_role($i); + + if ($list_role ne "") { + $list_role = ":" . $list_role; + } + if ($list_address =~ m/([^\s]+)\s+(.*)$/) { + $list_address = $1; + $list_additional = $2; + } + if ($list_additional =~ m/subscribers-only/) { + if ($email_subscriber_list) { + if (!$hash_list_to{lc($list_address)}) { + $hash_list_to{lc($list_address)} = 1; + push(@list_to, [$list_address, + "subscriber list${list_role}" . $suffix]); + } + } + } else { + if ($email_list) { + if (!$hash_list_to{lc($list_address)}) { + if ($list_additional =~ m/moderated/) { + if ($email_moderated_list) { + $hash_list_to{lc($list_address)} = 1; + push(@list_to, [$list_address, + "moderated list${list_role}" . $suffix]); + } + } else { + $hash_list_to{lc($list_address)} = 1; + push(@list_to, [$list_address, + "open list${list_role}" . $suffix]); + } + } + } + } + } elsif ($ptype eq "M") { + if ($email_maintainer) { + my $role = get_maintainer_role($i); + push_email_addresses($pvalue, $role . $suffix); + } + } elsif ($ptype eq "R") { + if ($email_reviewer) { + 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 { + my ($name, $address) = @_; + + return 1 if (($name eq "") && ($address eq "")); + return 1 if (($name ne "") && exists($email_hash_name{lc($name)})); + return 1 if (($address ne "") && exists($email_hash_address{lc($address)})); + + return 0; +} + +sub push_email_address { + my ($line, $role) = @_; + + my ($name, $address) = parse_email($line); + + if ($address eq "") { + return 0; + } + + if (!$email_remove_duplicates) { + push(@email_to, [format_email($name, $address, $email_usename), $role]); + } elsif (!email_inuse($name, $address)) { + push(@email_to, [format_email($name, $address, $email_usename), $role]); + $email_hash_name{lc($name)}++ if ($name ne ""); + $email_hash_address{lc($address)}++; + } + + return 1; +} + +sub push_email_addresses { + my ($address, $role) = @_; + + my @address_list = (); + + if (rfc822_valid($address)) { + push_email_address($address, $role); + } elsif (@address_list = rfc822_validlist($address)) { + my $array_count = shift(@address_list); + while (my $entry = shift(@address_list)) { + push_email_address($entry, $role); + } + } else { + if (!push_email_address($address, $role)) { + warn("Invalid MAINTAINERS address: '" . $address . "'\n"); + } + } +} + +sub add_role { + my ($line, $role) = @_; + + my ($name, $address) = parse_email($line); + my $email = format_email($name, $address, $email_usename); + + foreach my $entry (@email_to) { + if ($email_remove_duplicates) { + my ($entry_name, $entry_address) = parse_email($entry->[0]); + if (($name eq $entry_name || $address eq $entry_address) + && ($role eq "" || !($entry->[1] =~ m/$role/)) + ) { + if ($entry->[1] eq "") { + $entry->[1] = "$role"; + } else { + $entry->[1] = "$entry->[1],$role"; + } + } + } else { + if ($email eq $entry->[0] + && ($role eq "" || !($entry->[1] =~ m/$role/)) + ) { + if ($entry->[1] eq "") { + $entry->[1] = "$role"; + } else { + $entry->[1] = "$entry->[1],$role"; + } + } + } + } +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) = @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub mailmap_email { + my ($line) = @_; + + my ($name, $address) = parse_email($line); + my $email = format_email($name, $address, 1); + my $real_name = $name; + my $real_address = $address; + + if (exists $mailmap->{names}->{$email} || + exists $mailmap->{addresses}->{$email}) { + if (exists $mailmap->{names}->{$email}) { + $real_name = $mailmap->{names}->{$email}; + } + if (exists $mailmap->{addresses}->{$email}) { + $real_address = $mailmap->{addresses}->{$email}; + } + } else { + if (exists $mailmap->{names}->{$address}) { + $real_name = $mailmap->{names}->{$address}; + } + if (exists $mailmap->{addresses}->{$address}) { + $real_address = $mailmap->{addresses}->{$address}; + } + } + return format_email($real_name, $real_address, 1); +} + +sub mailmap { + my (@addresses) = @_; + + my @mapped_emails = (); + foreach my $line (@addresses) { + push(@mapped_emails, mailmap_email($line)); + } + merge_by_realname(@mapped_emails) if ($email_use_mailmap); + return @mapped_emails; +} + +sub merge_by_realname { + my %address_map; + my (@emails) = @_; + + foreach my $email (@emails) { + my ($name, $address) = parse_email($email); + if (exists $address_map{$name}) { + $address = $address_map{$name}; + $email = format_email($name, $address, 1); + } else { + $address_map{$name} = $address; + } + } +} + +sub git_execute_cmd { + my ($cmd) = @_; + my @lines = (); + + my $output = `$cmd`; + $output =~ s/^\s*//gm; + @lines = split("\n", $output); + + return @lines; +} + +sub hg_execute_cmd { + my ($cmd) = @_; + my @lines = (); + + my $output = `$cmd`; + @lines = split("\n", $output); + + return @lines; +} + +sub extract_formatted_signatures { + my (@signature_lines) = @_; + + my @type = @signature_lines; + + s/\s*(.*):.*/$1/ for (@type); + + # cut -f2- -d":" + s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines); + +## Reformat email addresses (with names) to avoid badly written signatures + + foreach my $signer (@signature_lines) { + $signer = deduplicate_email($signer); + } + + return (\@type, \@signature_lines); +} + +sub vcs_find_signers { + my ($cmd, $file) = @_; + my $commits; + my @lines = (); + my @signatures = (); + my @authors = (); + my @stats = (); + + @lines = &{$VCS_cmds{"execute_cmd"}}($cmd); + + my $pattern = $VCS_cmds{"commit_pattern"}; + my $author_pattern = $VCS_cmds{"author_pattern"}; + my $stat_pattern = $VCS_cmds{"stat_pattern"}; + + $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern + + $commits = grep(/$pattern/, @lines); # of commits + + @authors = grep(/$author_pattern/, @lines); + @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines); + @stats = grep(/$stat_pattern/, @lines); + +# print("stats: <@stats>\n"); + + return (0, \@signatures, \@authors, \@stats) if !@signatures; + + save_commits_by_author(@lines) if ($interactive); + save_commits_by_signer(@lines) if ($interactive); + + if (!$email_git_penguin_chiefs) { + @signatures = grep(!/${penguin_chiefs}/i, @signatures); + } + + my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors); + my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures); + + return ($commits, $signers_ref, $authors_ref, \@stats); +} + +sub vcs_find_author { + my ($cmd) = @_; + my @lines = (); + + @lines = &{$VCS_cmds{"execute_cmd"}}($cmd); + + if (!$email_git_penguin_chiefs) { + @lines = grep(!/${penguin_chiefs}/i, @lines); + } + + return @lines if !@lines; + + my @authors = (); + foreach my $line (@lines) { + if ($line =~ m/$VCS_cmds{"author_pattern"}/) { + my $author = $1; + my ($name, $address) = parse_email($author); + $author = format_email($name, $address, 1); + push(@authors, $author); + } + } + + save_commits_by_author(@lines) if ($interactive); + save_commits_by_signer(@lines) if ($interactive); + + return @authors; +} + +sub vcs_save_commits { + my ($cmd) = @_; + my @lines = (); + my @commits = (); + + @lines = &{$VCS_cmds{"execute_cmd"}}($cmd); + + foreach my $line (@lines) { + if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) { + push(@commits, $1); + } + } + + return @commits; +} + +sub vcs_blame { + my ($file) = @_; + my $cmd; + my @commits = (); + + return @commits if (!(-f $file)); + + if (@range && $VCS_cmds{"blame_range_cmd"} eq "") { + my @all_commits = (); + + $cmd = $VCS_cmds{"blame_file_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd + @all_commits = vcs_save_commits($cmd); + + foreach my $file_range_diff (@range) { + next if (!($file_range_diff =~ m/(.+):(.+):(.+)/)); + my $diff_file = $1; + my $diff_start = $2; + my $diff_length = $3; + next if ("$file" ne "$diff_file"); + for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) { + push(@commits, $all_commits[$i]); + } + } + } elsif (@range) { + foreach my $file_range_diff (@range) { + next if (!($file_range_diff =~ m/(.+):(.+):(.+)/)); + my $diff_file = $1; + my $diff_start = $2; + my $diff_length = $3; + next if ("$file" ne "$diff_file"); + $cmd = $VCS_cmds{"blame_range_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd + push(@commits, vcs_save_commits($cmd)); + } + } else { + $cmd = $VCS_cmds{"blame_file_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd + @commits = vcs_save_commits($cmd); + } + + foreach my $commit (@commits) { + $commit =~ s/^\^//g; + } + + return @commits; +} + +my $printed_novcs = 0; +sub vcs_exists { + %VCS_cmds = %VCS_cmds_git; + return 1 if eval $VCS_cmds{"available"}; + %VCS_cmds = %VCS_cmds_hg; + return 2 if eval $VCS_cmds{"available"}; + %VCS_cmds = (); + if (!$printed_novcs && $email_git) { + warn("$P: No supported VCS found. Add --nogit to options?\n"); + warn("Using a git repository produces better results.\n"); + warn("Try Linus Torvalds' latest git repository using:\n"); + warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n"); + $printed_novcs = 1; + } + return 0; +} + +sub vcs_is_git { + vcs_exists(); + return $vcs_used == 1; +} + +sub vcs_is_hg { + return $vcs_used == 2; +} + +sub vcs_add_commit_signers { + return if (!vcs_exists()); + + my ($commit, $desc) = @_; + my $commit_count = 0; + my $commit_authors_ref; + my $commit_signers_ref; + my $stats_ref; + my @commit_authors = (); + my @commit_signers = (); + my $cmd; + + $cmd = $VCS_cmds{"find_commit_signers_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd + + ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, ""); + @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref; + @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref; + + foreach my $signer (@commit_signers) { + $signer = deduplicate_email($signer); + } + + vcs_assign($desc, 1, @commit_signers); +} + +sub interactive_get_maintainers { + my ($list_ref) = @_; + my @list = @$list_ref; + + vcs_exists(); + + my %selected; + my %authored; + my %signed; + my $count = 0; + my $maintained = 0; + foreach my $entry (@list) { + $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i); + $selected{$count} = 1; + $authored{$count} = 0; + $signed{$count} = 0; + $count++; + } + + #menu loop + my $done = 0; + my $print_options = 0; + my $redraw = 1; + while (!$done) { + $count = 0; + if ($redraw) { + printf STDERR "\n%1s %2s %-65s", + "*", "#", "email/list and role:stats"; + if ($email_git || + ($email_git_fallback && !$maintained) || + $email_git_blame) { + print STDERR "auth sign"; + } + print STDERR "\n"; + foreach my $entry (@list) { + my $email = $entry->[0]; + my $role = $entry->[1]; + my $sel = ""; + $sel = "*" if ($selected{$count}); + my $commit_author = $commit_author_hash{$email}; + my $commit_signer = $commit_signer_hash{$email}; + my $authored = 0; + my $signed = 0; + $authored++ for (@{$commit_author}); + $signed++ for (@{$commit_signer}); + printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email; + printf STDERR "%4d %4d", $authored, $signed + if ($authored > 0 || $signed > 0); + printf STDERR "\n %s\n", $role; + if ($authored{$count}) { + my $commit_author = $commit_author_hash{$email}; + foreach my $ref (@{$commit_author}) { + print STDERR " Author: @{$ref}[1]\n"; + } + } + if ($signed{$count}) { + my $commit_signer = $commit_signer_hash{$email}; + foreach my $ref (@{$commit_signer}) { + print STDERR " @{$ref}[2]: @{$ref}[1]\n"; + } + } + + $count++; + } + } + my $date_ref = \$email_git_since; + $date_ref = \$email_hg_since if (vcs_is_hg()); + if ($print_options) { + $print_options = 0; + if (vcs_exists()) { + print STDERR <<EOT + +Version Control options: +g use git history [$email_git] +gf use git-fallback [$email_git_fallback] +b use git blame [$email_git_blame] +bs use blame signatures [$email_git_blame_signatures] +c# minimum commits [$email_git_min_signatures] +%# min percent [$email_git_min_percent] +d# history to use [$$date_ref] +x# max maintainers [$email_git_max_maintainers] +t all signature types [$email_git_all_signature_types] +m use .mailmap [$email_use_mailmap] +EOT + } + print STDERR <<EOT + +Additional options: +0 toggle all +tm toggle maintainers +tg toggle git entries +tl toggle open list entries +ts toggle subscriber list entries +f emails in file [$email_file_emails] +k keywords in file [$keywords] +r remove duplicates [$email_remove_duplicates] +p# pattern match depth [$pattern_depth] +EOT + } + print STDERR +"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): "; + + my $input = <STDIN>; + chomp($input); + + $redraw = 1; + my $rerun = 0; + my @wish = split(/[, ]+/, $input); + foreach my $nr (@wish) { + $nr = lc($nr); + my $sel = substr($nr, 0, 1); + my $str = substr($nr, 1); + my $val = 0; + $val = $1 if $str =~ /^(\d+)$/; + + if ($sel eq "y") { + $interactive = 0; + $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}; + } elsif ($sel eq "*" || $sel eq '^') { + my $toggle = 0; + $toggle = 1 if ($sel eq '*'); + for (my $i = 0; $i < $count; $i++) { + $selected{$i} = $toggle; + } + } elsif ($sel eq "0") { + for (my $i = 0; $i < $count; $i++) { + $selected{$i} = !$selected{$i}; + } + } elsif ($sel eq "t") { + if (lc($str) eq "m") { + for (my $i = 0; $i < $count; $i++) { + $selected{$i} = !$selected{$i} + if ($list[$i]->[1] =~ /^(maintainer|supporter)/i); + } + } elsif (lc($str) eq "g") { + for (my $i = 0; $i < $count; $i++) { + $selected{$i} = !$selected{$i} + if ($list[$i]->[1] =~ /^(author|commit|signer)/i); + } + } elsif (lc($str) eq "l") { + for (my $i = 0; $i < $count; $i++) { + $selected{$i} = !$selected{$i} + if ($list[$i]->[1] =~ /^(open list)/i); + } + } elsif (lc($str) eq "s") { + for (my $i = 0; $i < $count; $i++) { + $selected{$i} = !$selected{$i} + if ($list[$i]->[1] =~ /^(subscriber list)/i); + } + } + } elsif ($sel eq "a") { + if ($val > 0 && $val <= $count) { + $authored{$val - 1} = !$authored{$val - 1}; + } elsif ($str eq '*' || $str eq '^') { + my $toggle = 0; + $toggle = 1 if ($str eq '*'); + for (my $i = 0; $i < $count; $i++) { + $authored{$i} = $toggle; + } + } + } elsif ($sel eq "s") { + if ($val > 0 && $val <= $count) { + $signed{$val - 1} = !$signed{$val - 1}; + } elsif ($str eq '*' || $str eq '^') { + my $toggle = 0; + $toggle = 1 if ($str eq '*'); + for (my $i = 0; $i < $count; $i++) { + $signed{$i} = $toggle; + } + } + } elsif ($sel eq "o") { + $print_options = 1; + $redraw = 1; + } elsif ($sel eq "g") { + if ($str eq "f") { + bool_invert(\$email_git_fallback); + } else { + bool_invert(\$email_git); + } + $rerun = 1; + } elsif ($sel eq "b") { + if ($str eq "s") { + bool_invert(\$email_git_blame_signatures); + } else { + bool_invert(\$email_git_blame); + } + $rerun = 1; + } elsif ($sel eq "c") { + if ($val > 0) { + $email_git_min_signatures = $val; + $rerun = 1; + } + } elsif ($sel eq "x") { + if ($val > 0) { + $email_git_max_maintainers = $val; + $rerun = 1; + } + } elsif ($sel eq "%") { + if ($str ne "" && $val >= 0) { + $email_git_min_percent = $val; + $rerun = 1; + } + } elsif ($sel eq "d") { + if (vcs_is_git()) { + $email_git_since = $str; + } elsif (vcs_is_hg()) { + $email_hg_since = $str; + } + $rerun = 1; + } elsif ($sel eq "t") { + bool_invert(\$email_git_all_signature_types); + $rerun = 1; + } elsif ($sel eq "f") { + bool_invert(\$email_file_emails); + $rerun = 1; + } elsif ($sel eq "r") { + bool_invert(\$email_remove_duplicates); + $rerun = 1; + } elsif ($sel eq "m") { + bool_invert(\$email_use_mailmap); + read_mailmap(); + $rerun = 1; + } elsif ($sel eq "k") { + bool_invert(\$keywords); + $rerun = 1; + } elsif ($sel eq "p") { + if ($str ne "" && $val >= 0) { + $pattern_depth = $val; + $rerun = 1; + } + } elsif ($sel eq "h" || $sel eq "?") { + print STDERR <<EOT + +Interactive mode allows you to select the various maintainers, submitters, +commit signers and mailing lists that could be CC'd on a patch. + +Any *'d entry is selected. + +If you have git or hg installed, you can choose to summarize the commit +history of files in the patch. Also, each line of the current file can +be matched to its commit author and that commits signers with blame. + +Various knobs exist to control the length of time for active commit +tracking, the maximum number of commit authors and signers to add, +and such. + +Enter selections at the prompt until you are satisfied that the selected +maintainers are appropriate. You may enter multiple selections separated +by either commas or spaces. + +EOT + } else { + print STDERR "invalid option: '$nr'\n"; + $redraw = 0; + } + } + if ($rerun) { + print STDERR "git-blame can be very slow, please have patience..." + if ($email_git_blame); + goto &get_maintainers; + } + } + + #drop not selected entries + $count = 0; + my @new_emailto = (); + foreach my $entry (@list) { + if ($selected{$count}) { + push(@new_emailto, $list[$count]); + } + $count++; + } + return @new_emailto; +} + +sub bool_invert { + my ($bool_ref) = @_; + + if ($$bool_ref) { + $$bool_ref = 0; + } else { + $$bool_ref = 1; + } +} + +sub deduplicate_email { + my ($email) = @_; + + my $matched = 0; + my ($name, $address) = parse_email($email); + $email = format_email($name, $address, 1); + $email = mailmap_email($email); + + return $email if (!$email_remove_duplicates); + + ($name, $address) = parse_email($email); + + if ($name ne "" && $deduplicate_name_hash{lc($name)}) { + $name = $deduplicate_name_hash{lc($name)}->[0]; + $address = $deduplicate_name_hash{lc($name)}->[1]; + $matched = 1; + } elsif ($deduplicate_address_hash{lc($address)}) { + $name = $deduplicate_address_hash{lc($address)}->[0]; + $address = $deduplicate_address_hash{lc($address)}->[1]; + $matched = 1; + } + if (!$matched) { + $deduplicate_name_hash{lc($name)} = [ $name, $address ]; + $deduplicate_address_hash{lc($address)} = [ $name, $address ]; + } + $email = format_email($name, $address, 1); + $email = mailmap_email($email); + return $email; +} + +sub save_commits_by_author { + my (@lines) = @_; + + my @authors = (); + my @commits = (); + my @subjects = (); + + foreach my $line (@lines) { + if ($line =~ m/$VCS_cmds{"author_pattern"}/) { + my $author = $1; + $author = deduplicate_email($author); + push(@authors, $author); + } + push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/); + push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/); + } + + for (my $i = 0; $i < @authors; $i++) { + my $exists = 0; + foreach my $ref(@{$commit_author_hash{$authors[$i]}}) { + if (@{$ref}[0] eq $commits[$i] && + @{$ref}[1] eq $subjects[$i]) { + $exists = 1; + last; + } + } + if (!$exists) { + push(@{$commit_author_hash{$authors[$i]}}, + [ ($commits[$i], $subjects[$i]) ]); + } + } +} + +sub save_commits_by_signer { + my (@lines) = @_; + + my $commit = ""; + my $subject = ""; + + foreach my $line (@lines) { + $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/); + $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/); + if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) { + my @signatures = ($line); + my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures); + my @types = @$types_ref; + my @signers = @$signers_ref; + + my $type = $types[0]; + my $signer = $signers[0]; + + $signer = deduplicate_email($signer); + + my $exists = 0; + foreach my $ref(@{$commit_signer_hash{$signer}}) { + if (@{$ref}[0] eq $commit && + @{$ref}[1] eq $subject && + @{$ref}[2] eq $type) { + $exists = 1; + last; + } + } + if (!$exists) { + push(@{$commit_signer_hash{$signer}}, + [ ($commit, $subject, $type) ]); + } + } + } +} + +sub vcs_assign { + my ($role, $divisor, @lines) = @_; + + my %hash; + my $count = 0; + + return if (@lines <= 0); + + if ($divisor <= 0) { + warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n"); + $divisor = 1; + } + + @lines = mailmap(@lines); + + return if (@lines <= 0); + + @lines = sort(@lines); + + # uniq -c + $hash{$_}++ for @lines; + + # sort -rn + foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { + my $sign_offs = $hash{$line}; + my $percent = $sign_offs * 100 / $divisor; + + $percent = 100 if ($percent > 100); + next if (ignore_email_address($line)); + $count++; + last if ($sign_offs < $email_git_min_signatures || + $count > $email_git_max_maintainers || + $percent < $email_git_min_percent); + push_email_address($line, ''); + if ($output_rolestats) { + my $fmt_percent = sprintf("%.0f", $percent); + add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%"); + } else { + add_role($line, $role); + } + } +} + +sub vcs_file_signoffs { + my ($file) = @_; + + my $authors_ref; + my $signers_ref; + my $stats_ref; + my @authors = (); + my @signers = (); + my @stats = (); + my $commits; + + $vcs_used = vcs_exists(); + return if (!$vcs_used); + + my $cmd = $VCS_cmds{"find_signers_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd + + ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file); + + @signers = @{$signers_ref} if defined $signers_ref; + @authors = @{$authors_ref} if defined $authors_ref; + @stats = @{$stats_ref} if defined $stats_ref; + +# print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n"); + + foreach my $signer (@signers) { + $signer = deduplicate_email($signer); + } + + vcs_assign("commit_signer", $commits, @signers); + vcs_assign("authored", $commits, @authors); + if ($#authors == $#stats) { + my $stat_pattern = $VCS_cmds{"stat_pattern"}; + $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern + + my $added = 0; + my $deleted = 0; + for (my $i = 0; $i <= $#stats; $i++) { + if ($stats[$i] =~ /$stat_pattern/) { + $added += $1; + $deleted += $2; + } + } + my @tmp_authors = uniq(@authors); + foreach my $author (@tmp_authors) { + $author = deduplicate_email($author); + } + @tmp_authors = uniq(@tmp_authors); + my @list_added = (); + my @list_deleted = (); + foreach my $author (@tmp_authors) { + my $auth_added = 0; + my $auth_deleted = 0; + for (my $i = 0; $i <= $#stats; $i++) { + if ($author eq deduplicate_email($authors[$i]) && + $stats[$i] =~ /$stat_pattern/) { + $auth_added += $1; + $auth_deleted += $2; + } + } + for (my $i = 0; $i < $auth_added; $i++) { + push(@list_added, $author); + } + for (my $i = 0; $i < $auth_deleted; $i++) { + push(@list_deleted, $author); + } + } + vcs_assign("added_lines", $added, @list_added); + vcs_assign("removed_lines", $deleted, @list_deleted); + } +} + +sub vcs_file_blame { + my ($file) = @_; + + my @signers = (); + my @all_commits = (); + my @commits = (); + my $total_commits; + my $total_lines; + + $vcs_used = vcs_exists(); + return if (!$vcs_used); + + @all_commits = vcs_blame($file); + @commits = uniq(@all_commits); + $total_commits = @commits; + $total_lines = @all_commits; + + if ($email_git_blame_signatures) { + if (vcs_is_hg()) { + my $commit_count; + my $commit_authors_ref; + my $commit_signers_ref; + my $stats_ref; + my @commit_authors = (); + my @commit_signers = (); + my $commit = join(" -r ", @commits); + my $cmd; + + $cmd = $VCS_cmds{"find_commit_signers_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd + + ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file); + @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref; + @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref; + + push(@signers, @commit_signers); + } else { + foreach my $commit (@commits) { + my $commit_count; + my $commit_authors_ref; + my $commit_signers_ref; + my $stats_ref; + my @commit_authors = (); + my @commit_signers = (); + my $cmd; + + $cmd = $VCS_cmds{"find_commit_signers_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd + + ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file); + @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref; + @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref; + + push(@signers, @commit_signers); + } + } + } + + if ($from_filename) { + if ($output_rolestats) { + my @blame_signers; + if (vcs_is_hg()) {{ # Double brace for last exit + my $commit_count; + my @commit_signers = (); + @commits = uniq(@commits); + @commits = sort(@commits); + my $commit = join(" -r ", @commits); + my $cmd; + + $cmd = $VCS_cmds{"find_commit_author_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd + + my @lines = (); + + @lines = &{$VCS_cmds{"execute_cmd"}}($cmd); + + if (!$email_git_penguin_chiefs) { + @lines = grep(!/${penguin_chiefs}/i, @lines); + } + + last if !@lines; + + my @authors = (); + foreach my $line (@lines) { + if ($line =~ m/$VCS_cmds{"author_pattern"}/) { + my $author = $1; + $author = deduplicate_email($author); + push(@authors, $author); + } + } + + save_commits_by_author(@lines) if ($interactive); + save_commits_by_signer(@lines) if ($interactive); + + push(@signers, @authors); + }} + else { + foreach my $commit (@commits) { + my $i; + my $cmd = $VCS_cmds{"find_commit_author_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd + my @author = vcs_find_author($cmd); + next if !@author; + + my $formatted_author = deduplicate_email($author[0]); + + my $count = grep(/$commit/, @all_commits); + for ($i = 0; $i < $count ; $i++) { + push(@blame_signers, $formatted_author); + } + } + } + if (@blame_signers) { + vcs_assign("authored lines", $total_lines, @blame_signers); + } + } + foreach my $signer (@signers) { + $signer = deduplicate_email($signer); + } + vcs_assign("commits", $total_commits, @signers); + } else { + foreach my $signer (@signers) { + $signer = deduplicate_email($signer); + } + vcs_assign("modified commits", $total_commits, @signers); + } +} + +sub vcs_file_exists { + my ($file) = @_; + + my $exists; + + my $vcs_used = vcs_exists(); + return 0 if (!$vcs_used); + + my $cmd = $VCS_cmds{"file_exists_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd + $cmd .= " 2>&1"; + $exists = &{$VCS_cmds{"execute_cmd"}}($cmd); + + return 0 if ($? != 0); + + return $exists; +} + +sub vcs_list_files { + my ($file) = @_; + + my @lsfiles = (); + + my $vcs_used = vcs_exists(); + return 0 if (!$vcs_used); + + my $cmd = $VCS_cmds{"list_files_cmd"}; + $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd + @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd); + + return () if ($? != 0); + + return @lsfiles; +} + +sub uniq { + my (@parms) = @_; + + my %saw; + @parms = grep(!$saw{$_}++, @parms); + return @parms; +} + +sub sort_and_uniq { + my (@parms) = @_; + + my %saw; + @parms = sort @parms; + @parms = grep(!$saw{$_}++, @parms); + return @parms; +} + +sub clean_file_emails { + my (@file_emails) = @_; + my @fmt_emails = (); + + foreach my $email (@file_emails) { + $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g; + my ($name, $address) = parse_email($email); + + # Strip quotes for easier processing, format_email will add them back + $name =~ s/^"(.*)"$/$1/; + + # Split into name-like parts and remove stray punctuation particles + my @nw = split(/[^\p{L}\'\,\.\+-]/, $name); + @nw = grep(!/^[\'\,\.\+-]$/, @nw); + + # Make a best effort to extract the name, and only the name, by taking + # only the last two names, or in the case of obvious initials, the last + # three names. + if (@nw > 2) { + my $first = $nw[@nw - 3]; + my $middle = $nw[@nw - 2]; + my $last = $nw[@nw - 1]; + + if (((length($first) == 1 && $first =~ m/\p{L}/) || + (length($first) == 2 && substr($first, -1) eq ".")) || + (length($middle) == 1 || + (length($middle) == 2 && substr($middle, -1) eq "."))) { + $name = "$first $middle $last"; + } else { + $name = "$middle $last"; + } + } else { + $name = "@nw"; + } + + if (substr($name, -1) =~ /[,\.]/) { + $name = substr($name, 0, length($name) - 1); + } + + if (substr($name, 0, 1) =~ /[,\.]/) { + $name = substr($name, 1, length($name) - 1); + } + + my $fmt_email = format_email($name, $address, $email_usename); + push(@fmt_emails, $fmt_email); + } + return @fmt_emails; +} + +sub merge_email { + my @lines; + my %saw; + + for (@_) { + my ($address, $role) = @$_; + if (!$saw{$address}) { + if ($output_roles) { + push(@lines, "$address ($role)"); + } else { + push(@lines, $address); + } + $saw{$address} = 1; + } + } + + return @lines; +} + +sub output { + my (@parms) = @_; + + if ($output_multiline) { + foreach my $line (@parms) { + print("${line}\n"); + } + } else { + print(join($output_separator, @parms)); + print("\n"); + } +} + +my $rfc822re; + +sub make_rfc822re { +# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and +# comment. We must allow for rfc822_lwsp (or comments) after each of these. +# This regexp will only work on addresses which have had comments stripped +# and replaced with rfc822_lwsp. + + my $specials = '()<>@,;:\\\\".\\[\\]'; + my $controls = '\\000-\\037\\177'; + + my $dtext = "[^\\[\\]\\r\\\\]"; + my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*"; + + my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*"; + +# Use zero-width assertion to spot the limit of an atom. A simple +# $rfc822_lwsp* causes the regexp engine to hang occasionally. + my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))"; + my $word = "(?:$atom|$quoted_string)"; + my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*"; + + my $sub_domain = "(?:$atom|$domain_literal)"; + my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*"; + + my $addr_spec = "$localpart\@$rfc822_lwsp*$domain"; + + my $phrase = "$word*"; + my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)"; + my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*"; + my $mailbox = "(?:$addr_spec|$phrase$route_addr)"; + + my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*"; + my $address = "(?:$mailbox|$group)"; + + return "$rfc822_lwsp*$address"; +} + +sub rfc822_strip_comments { + my $s = shift; +# Recursively remove comments, and replace with a single space. The simpler +# regexps in the Email Addressing FAQ are imperfect - they will miss escaped +# chars in atoms, for example. + + while ($s =~ s/^((?:[^"\\]|\\.)* + (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*) + \((?:[^()\\]|\\.)*\)/$1 /osx) {} + return $s; +} + +# valid: returns true if the parameter is an RFC822 valid address +# +sub rfc822_valid { + my $s = rfc822_strip_comments(shift); + + if (!$rfc822re) { + $rfc822re = make_rfc822re(); + } + + return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/; +} + +# validlist: In scalar context, returns true if the parameter is an RFC822 +# valid list of addresses. +# +# In list context, returns an empty list on failure (an invalid +# address was found); otherwise a list whose first element is the +# number of addresses found and whose remaining elements are the +# addresses. This is needed to disambiguate failure (invalid) +# from success with no addresses found, because an empty string is +# a valid list. + +sub rfc822_validlist { + my $s = rfc822_strip_comments(shift); + + if (!$rfc822re) { + $rfc822re = make_rfc822re(); + } + # * null list items are valid according to the RFC + # * the '1' business is to aid in distinguishing failure from no results + + my @r; + if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so && + $s =~ m/^$rfc822_char*$/) { + while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) { + push(@r, $1); + } + return wantarray ? (scalar(@r), @r) : 1; + } + return wantarray ? () : 0; +} diff --git a/scripts/gfp-translate b/scripts/gfp-translate new file mode 100755 index 000000000..8385ae0d5 --- /dev/null +++ b/scripts/gfp-translate @@ -0,0 +1,118 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +# Translate the bits making up a GFP mask +# (c) 2009, Mel Gorman <mel@csn.ul.ie> +SOURCE= +GFPMASK=none + +# Helper function to report failures and exit +die() { + echo ERROR: $@ + if [ "$TMPFILE" != "" ]; then + rm -f $TMPFILE + fi + exit -1 +} + +usage() { + echo "usage: gfp-translate [-h] [ --source DIRECTORY ] gfpmask" + exit 0 +} + +# Parse command-line arguments +while [ $# -gt 0 ]; do + case $1 in + --source) + SOURCE=$2 + shift 2 + ;; + -h) + usage + ;; + --help) + usage + ;; + *) + GFPMASK=$1 + shift + ;; + esac +done + +# Guess the kernel source directory if it's not set. Preference is in order of +# o current directory +# o /usr/src/linux +if [ "$SOURCE" = "" ]; then + if [ -r "/usr/src/linux/Makefile" ]; then + SOURCE=/usr/src/linux + fi + if [ -r "`pwd`/Makefile" ]; then + SOURCE=`pwd` + fi +fi + +# Confirm that a source directory exists +if [ ! -r "$SOURCE/Makefile" ]; then + die "Could not locate kernel source directory or it is invalid" +fi + +# Confirm that a GFP mask has been specified +if [ "$GFPMASK" = "none" ]; then + usage +fi + +# Extract GFP flags from the kernel source +TMPFILE=`mktemp -t gfptranslate-XXXXXX.c` || exit 1 + +echo Source: $SOURCE +echo Parsing: $GFPMASK + +( + 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 000000000..e9b5940c0 --- /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/git.orderFile b/scripts/git.orderFile new file mode 100644 index 000000000..5102ba733 --- /dev/null +++ b/scripts/git.orderFile @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0 + +# order file for git, to produce patches which are easier to review +# by diffing the important stuff like header changes first. +# +# one-off usage: +# git diff -O scripts/git.orderFile ... +# +# add to git config: +# git config diff.orderFile scripts/git.orderFile +# + +MAINTAINERS + +# Documentation +Documentation/* +*.rst + +# git-specific +.gitignore +scripts/git.orderFile + +# build system +Kconfig* +*/Kconfig* +Kbuild* +*/Kbuild* +Makefile* +*/Makefile* +*.mak +*.mk +scripts/* + +# semantic patches +*.cocci + +# headers +*types.h +*.h + +# code +*.c diff --git a/scripts/head-object-list.txt b/scripts/head-object-list.txt new file mode 100644 index 000000000..7274dfc65 --- /dev/null +++ b/scripts/head-object-list.txt @@ -0,0 +1,41 @@ +# Head objects +# +# The objects listed here are placed at the head of vmlinux. A typical use-case +# is an object that contains the entry point. This is kept for compatibility +# with head-y, which Kbuild used to support. +# +# A counter approach is to control the section placement by the linker script. +# The code marked as __HEAD goes into the ".head.text" section, which is placed +# before the normal ".text" section. +# +# If you can achieve the correct code ordering by linker script, please delete +# the entry from this file. +# +arch/alpha/kernel/head.o +arch/arc/kernel/head.o +arch/arm/kernel/head-nommu.o +arch/arm/kernel/head.o +arch/csky/kernel/head.o +arch/hexagon/kernel/head.o +arch/loongarch/kernel/head.o +arch/m68k/68000/head.o +arch/m68k/coldfire/head.o +arch/m68k/kernel/head.o +arch/m68k/kernel/sun3-head.o +arch/microblaze/kernel/head.o +arch/nios2/kernel/head.o +arch/parisc/kernel/head.o +arch/powerpc/kernel/head_44x.o +arch/powerpc/kernel/head_64.o +arch/powerpc/kernel/head_8xx.o +arch/powerpc/kernel/head_85xx.o +arch/powerpc/kernel/head_book3s_32.o +arch/powerpc/kernel/prom_entry_64.o +arch/powerpc/kernel/fpu.o +arch/powerpc/kernel/vector.o +arch/powerpc/kernel/prom_init.o +arch/s390/kernel/head64.o +arch/sh/kernel/head_32.o +arch/sparc/kernel/head_32.o +arch/sparc/kernel/head_64.o +arch/xtensa/kernel/head.o diff --git a/scripts/headerdep.pl b/scripts/headerdep.pl new file mode 100755 index 000000000..ebfcbef43 --- /dev/null +++ b/scripts/headerdep.pl @@ -0,0 +1,193 @@ +#! /usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# Detect cycles in the header file dependency graph +# Vegard Nossum <vegardno@ifi.uio.no> +# + +use strict; +use warnings; + +use Getopt::Long; + +my $opt_all; +my @opt_include; +my $opt_graph; + +&Getopt::Long::Configure(qw(bundling pass_through)); +&GetOptions( + help => \&help, + version => \&version, + + all => \$opt_all, + "I=s" => \@opt_include, + graph => \$opt_graph, +); + +push @opt_include, 'include'; +my %deps = (); +my %linenos = (); + +my @headers = grep { strip($_) } @ARGV; + +parse_all(@headers); + +if($opt_graph) { + graph(); +} else { + detect_cycles(@headers); +} + + +sub help { + print "Usage: $0 [options] file...\n"; + print "\n"; + print "Options:\n"; + print " --all\n"; + print " --graph\n"; + print "\n"; + print " -I includedir\n"; + print "\n"; + print "To make nice graphs, try:\n"; + print " $0 --graph include/linux/kernel.h | dot -Tpng -o graph.png\n"; + exit; +} + +sub version { + print "headerdep version 2\n"; + exit; +} + +# Get a file name that is relative to our include paths +sub strip { + my $filename = shift; + + for my $i (@opt_include) { + my $stripped = $filename; + $stripped =~ s/^$i\///; + + return $stripped if $stripped ne $filename; + } + + return $filename; +} + +# Search for the file name in the list of include paths +sub search { + my $filename = shift; + return $filename if -f $filename; + + for my $i (@opt_include) { + my $path = "$i/$filename"; + return $path if -f $path; + } + return; +} + +sub parse_all { + # Parse all the headers. + my @queue = @_; + while(@queue) { + my $header = pop @queue; + next if exists $deps{$header}; + + $deps{$header} = [] unless exists $deps{$header}; + + my $path = search($header); + next unless $path; + + open(my $file, '<', $path) or die($!); + chomp(my @lines = <$file>); + close($file); + + for my $i (0 .. $#lines) { + my $line = $lines[$i]; + if(my($dep) = ($line =~ m/^#\s*include\s*<(.*?)>/)) { + push @queue, $dep; + push @{$deps{$header}}, [$i + 1, $dep]; + } + } + } +} + +sub print_cycle { + # $cycle[n] includes $cycle[n + 1]; + # $cycle[-1] will be the culprit + my $cycle = shift; + + # Adjust the line numbers + for my $i (0 .. $#$cycle - 1) { + $cycle->[$i]->[0] = $cycle->[$i + 1]->[0]; + } + $cycle->[-1]->[0] = 0; + + my $first = shift @$cycle; + my $last = pop @$cycle; + + my $msg = "In file included"; + printf "%s from %s,\n", $msg, $last->[1] if defined $last; + + for my $header (reverse @$cycle) { + printf "%s from %s:%d%s\n", + " " x length $msg, + $header->[1], $header->[0], + $header->[1] eq $last->[1] ? ' <-- here' : ''; + } + + printf "%s:%d: warning: recursive header inclusion\n", + $first->[1], $first->[0]; +} + +# Find and print the smallest cycle starting in the specified node. +sub detect_cycles { + my @queue = map { [[0, $_]] } @_; + while(@queue) { + my $top = pop @queue; + my $name = $top->[-1]->[1]; + + for my $dep (@{$deps{$name}}) { + my $chain = [@$top, [$dep->[0], $dep->[1]]]; + + # If the dep already exists in the chain, we have a + # cycle... + if(grep { $_->[1] eq $dep->[1] } @$top) { + print_cycle($chain); + next if $opt_all; + return; + } + + push @queue, $chain; + } + } +} + +sub mangle { + $_ = shift; + s/\//__/g; + s/\./_/g; + s/-/_/g; + $_; +} + +# Output dependency graph in GraphViz language. +sub graph { + print "digraph {\n"; + + print "\t/* vertices */\n"; + for my $header (keys %deps) { + printf "\t%s [label=\"%s\"];\n", + mangle($header), $header; + } + + print "\n"; + + print "\t/* edges */\n"; + for my $header (keys %deps) { + for my $dep (@{$deps{$header}}) { + printf "\t%s -> %s;\n", + mangle($header), mangle($dep->[1]); + } + } + + print "}\n"; +} diff --git a/scripts/headers_install.sh b/scripts/headers_install.sh new file mode 100755 index 000000000..2f1d1767c --- /dev/null +++ b/scripts/headers_install.sh @@ -0,0 +1,75 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +if [ $# -ne 2 ] +then + echo "Usage: headers_install.sh INFILE OUTFILE" + echo + echo "Prepares kernel header files for use by user space, by removing" + echo "all compiler.h definitions and #includes, removing any" + echo "#ifdef __KERNEL__ sections, and putting __underscores__ around" + echo "asm/inline/volatile keywords." + echo + echo "INFILE: header file to operate on" + echo "OUTFILE: output file which the processed header is written to" + + exit 1 +fi + +# Grab arguments +INFILE=$1 +OUTFILE=$2 +TMPFILE=$OUTFILE.tmp + +trap 'rm -f $OUTFILE $TMPFILE' EXIT + +# SPDX-License-Identifier with GPL variants must have "WITH Linux-syscall-note" +if [ -n "$(sed -n -e "/SPDX-License-Identifier:.*GPL-/{/WITH Linux-syscall-note/!p}" $INFILE)" ]; then + echo "error: $INFILE: missing \"WITH Linux-syscall-note\" for SPDX-License-Identifier" >&2 + exit 1 +fi + +sed -E -e ' + s/([[:space:](])(__user|__force|__iomem)[[:space:]]/\1/g + s/__attribute_const__([[:space:]]|$)/\1/g + s@^#include <linux/compiler.h>@@ + s/(^|[^a-zA-Z0-9])__packed([^a-zA-Z0-9_]|$)/\1__attribute__((packed))\2/g + s/(^|[[:space:](])(inline|asm|volatile)([[:space:](]|$)/\1__\2__\3/g + s@#(ifndef|define|endif[[:space:]]*/[*])[[:space:]]*_UAPI@#\1 @ + s/__ASSEMBLY__/__ASSEMBLER__/g +' $INFILE > $TMPFILE || exit 1 + +scripts/unifdef -U__KERNEL__ -D__EXPORTED_HEADERS__ $TMPFILE > $OUTFILE +[ $? -gt 1 ] && exit 1 + +# Remove /* ... */ style comments, and find CONFIG_ references in code +configs=$(sed -e ' +:comment + s:/\*[^*][^*]*:/*: + s:/\*\*\**\([^/]\):/*\1: + t comment + s:/\*\*/: : + t comment + /\/\*/! b check + N + b comment +:print + P + D +:check + s:^\(CONFIG_[[:alnum:]_]*\):\1\n: + t print + s:^[[:alnum:]_][[:alnum:]_]*:: + s:^[^[:alnum:]_][^[:alnum:]_]*:: + t check + d +' $OUTFILE) + +for c in $configs +do + echo "error: $INFILE: leak $c to user-space" >&2 + exit 1 +done + +rm -f $TMPFILE +trap - EXIT diff --git a/scripts/include/array_size.h b/scripts/include/array_size.h new file mode 100644 index 000000000..26ba78d86 --- /dev/null +++ b/scripts/include/array_size.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARRAY_SIZE_H +#define ARRAY_SIZE_H + +/** + * ARRAY_SIZE - get the number of elements in array @arr + * @arr: array to be sized + */ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#endif /* ARRAY_SIZE_H */ diff --git a/scripts/include/hash.h b/scripts/include/hash.h new file mode 100644 index 000000000..efa904368 --- /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 000000000..45abcb12b --- /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/include/list.h b/scripts/include/list.h new file mode 100644 index 000000000..8bdcaadca --- /dev/null +++ b/scripts/include/list.h @@ -0,0 +1,428 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef LIST_H +#define LIST_H + +#include <stddef.h> + +#include "list_types.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. + */ + +#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_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 + */ +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_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. + */ +#define list_next_entry(pos, member) \ + 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. + * @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_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 + * @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)) + +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +#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 + * @n: new entry to be added + * @h: hlist head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + +#define hlist_entry(ptr, type, member) container_of(ptr, type, member) + +#define hlist_entry_safe(ptr, type, member) \ + ({ typeof(ptr) ____ptr = (ptr); \ + ____ptr ? hlist_entry(____ptr, type, member) : NULL; \ + }) + +/** + * hlist_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 hlist_node within the struct. + */ +#define hlist_for_each_entry(pos, head, member) \ + for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\ + 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/include/list_types.h b/scripts/include/list_types.h new file mode 100644 index 000000000..d935b7c5a --- /dev/null +++ b/scripts/include/list_types.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef LIST_TYPES_H +#define LIST_TYPES_H + +struct list_head { + struct list_head *next, *prev; +}; + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#endif /* LIST_TYPES_H */ diff --git a/scripts/include/xalloc.h b/scripts/include/xalloc.h new file mode 100644 index 000000000..cdadb07d0 --- /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/insert-sys-cert.c b/scripts/insert-sys-cert.c new file mode 100644 index 000000000..8902836c2 --- /dev/null +++ b/scripts/insert-sys-cert.c @@ -0,0 +1,410 @@ +/* Write the contents of the <certfile> into kernel symbol system_extra_cert + * + * Copyright (C) IBM Corporation, 2015 + * + * Author: Mehmet Kayaalp <mkayaalp@linux.vnet.ibm.com> + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Usage: insert-sys-cert [-s <System.map> -b <vmlinux> -c <certfile> + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <limits.h> +#include <stdbool.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> +#include <elf.h> + +#define CERT_SYM "system_extra_cert" +#define USED_SYM "system_extra_cert_used" +#define LSIZE_SYM "system_certificate_list_size" + +#define info(format, args...) fprintf(stderr, "INFO: " format, ## args) +#define warn(format, args...) fprintf(stdout, "WARNING: " format, ## args) +#define err(format, args...) fprintf(stderr, "ERROR: " format, ## args) + +#if UINTPTR_MAX == 0xffffffff +#define CURRENT_ELFCLASS ELFCLASS32 +#define Elf_Ehdr Elf32_Ehdr +#define Elf_Shdr Elf32_Shdr +#define Elf_Sym Elf32_Sym +#else +#define CURRENT_ELFCLASS ELFCLASS64 +#define Elf_Ehdr Elf64_Ehdr +#define Elf_Shdr Elf64_Shdr +#define Elf_Sym Elf64_Sym +#endif + +static unsigned char endianness(void) +{ + uint16_t two_byte = 0x00FF; + uint8_t low_address = *((uint8_t *)&two_byte); + + if (low_address == 0) + return ELFDATA2MSB; + else + return ELFDATA2LSB; +} + +struct sym { + char *name; + unsigned long address; + unsigned long offset; + void *content; + int size; +}; + +static unsigned long get_offset_from_address(Elf_Ehdr *hdr, unsigned long addr) +{ + Elf_Shdr *x; + unsigned int i, num_sections; + + x = (void *)hdr + hdr->e_shoff; + if (hdr->e_shnum == SHN_UNDEF) + num_sections = x[0].sh_size; + else + num_sections = hdr->e_shnum; + + for (i = 1; i < num_sections; i++) { + unsigned long start = x[i].sh_addr; + unsigned long end = start + x[i].sh_size; + unsigned long offset = x[i].sh_offset; + + if (addr >= start && addr <= end) + return addr - start + offset; + } + return 0; +} + + +#define LINE_SIZE 100 + +static void get_symbol_from_map(Elf_Ehdr *hdr, FILE *f, char *name, + struct sym *s) +{ + char l[LINE_SIZE]; + char *w, *p, *n; + + s->size = 0; + s->address = 0; + s->offset = 0; + if (fseek(f, 0, SEEK_SET) != 0) { + perror("File seek failed"); + exit(EXIT_FAILURE); + } + while (fgets(l, LINE_SIZE, f)) { + p = strchr(l, '\n'); + if (!p) { + err("Missing line ending.\n"); + return; + } + n = strstr(l, name); + if (n) + break; + } + if (!n) { + err("Unable to find symbol: %s\n", name); + return; + } + w = strchr(l, ' '); + if (!w) + return; + + *w = '\0'; + s->address = strtoul(l, NULL, 16); + if (s->address == 0) + return; + s->offset = get_offset_from_address(hdr, s->address); + s->name = name; + s->content = (void *)hdr + s->offset; +} + +static Elf_Sym *find_elf_symbol(Elf_Ehdr *hdr, Elf_Shdr *symtab, char *name) +{ + Elf_Sym *sym, *symtab_start; + char *strtab, *symname; + unsigned int link; + Elf_Shdr *x; + int i, n; + + x = (void *)hdr + hdr->e_shoff; + link = symtab->sh_link; + symtab_start = (void *)hdr + symtab->sh_offset; + n = symtab->sh_size / symtab->sh_entsize; + strtab = (void *)hdr + x[link].sh_offset; + + for (i = 0; i < n; i++) { + sym = &symtab_start[i]; + symname = strtab + sym->st_name; + if (strcmp(symname, name) == 0) + return sym; + } + err("Unable to find symbol: %s\n", name); + return NULL; +} + +static void get_symbol_from_table(Elf_Ehdr *hdr, Elf_Shdr *symtab, + char *name, struct sym *s) +{ + Elf_Shdr *sec; + int secndx; + Elf_Sym *elf_sym; + Elf_Shdr *x; + + x = (void *)hdr + hdr->e_shoff; + s->size = 0; + s->address = 0; + s->offset = 0; + elf_sym = find_elf_symbol(hdr, symtab, name); + if (!elf_sym) + return; + secndx = elf_sym->st_shndx; + if (!secndx) + return; + sec = &x[secndx]; + s->size = elf_sym->st_size; + s->address = elf_sym->st_value; + s->offset = s->address - sec->sh_addr + + sec->sh_offset; + s->name = name; + s->content = (void *)hdr + s->offset; +} + +static Elf_Shdr *get_symbol_table(Elf_Ehdr *hdr) +{ + Elf_Shdr *x; + unsigned int i, num_sections; + + x = (void *)hdr + hdr->e_shoff; + if (hdr->e_shnum == SHN_UNDEF) + num_sections = x[0].sh_size; + else + num_sections = hdr->e_shnum; + + for (i = 1; i < num_sections; i++) + if (x[i].sh_type == SHT_SYMTAB) + return &x[i]; + return NULL; +} + +static void *map_file(char *file_name, int *size) +{ + struct stat st; + void *map; + int fd; + + fd = open(file_name, O_RDWR); + if (fd < 0) { + perror(file_name); + return NULL; + } + if (fstat(fd, &st)) { + perror("Could not determine file size"); + close(fd); + return NULL; + } + *size = st.st_size; + map = mmap(NULL, *size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + perror("Mapping to memory failed"); + close(fd); + return NULL; + } + close(fd); + return map; +} + +static char *read_file(char *file_name, int *size) +{ + struct stat st; + char *buf; + int fd; + + fd = open(file_name, O_RDONLY); + if (fd < 0) { + perror(file_name); + return NULL; + } + if (fstat(fd, &st)) { + perror("Could not determine file size"); + close(fd); + return NULL; + } + *size = st.st_size; + buf = malloc(*size); + if (!buf) { + perror("Allocating memory failed"); + close(fd); + return NULL; + } + if (read(fd, buf, *size) != *size) { + perror("File read failed"); + close(fd); + return NULL; + } + close(fd); + return buf; +} + +static void print_sym(Elf_Ehdr *hdr, struct sym *s) +{ + info("sym: %s\n", s->name); + info("addr: 0x%lx\n", s->address); + info("size: %d\n", s->size); + info("offset: 0x%lx\n", (unsigned long)s->offset); +} + +static void print_usage(char *e) +{ + printf("Usage %s [-s <System.map>] -b <vmlinux> -c <certfile>\n", e); +} + +int main(int argc, char **argv) +{ + char *system_map_file = NULL; + char *vmlinux_file = NULL; + char *cert_file = NULL; + int vmlinux_size; + int cert_size; + Elf_Ehdr *hdr; + char *cert; + FILE *system_map; + unsigned long *lsize; + int *used; + int opt; + Elf_Shdr *symtab = NULL; + struct sym cert_sym, lsize_sym, used_sym; + + while ((opt = getopt(argc, argv, "b:c:s:")) != -1) { + switch (opt) { + case 's': + system_map_file = optarg; + break; + case 'b': + vmlinux_file = optarg; + break; + case 'c': + cert_file = optarg; + break; + default: + break; + } + } + + if (!vmlinux_file || !cert_file) { + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + cert = read_file(cert_file, &cert_size); + if (!cert) + exit(EXIT_FAILURE); + + hdr = map_file(vmlinux_file, &vmlinux_size); + if (!hdr) + exit(EXIT_FAILURE); + + if (vmlinux_size < sizeof(*hdr)) { + err("Invalid ELF file.\n"); + exit(EXIT_FAILURE); + } + + if ((hdr->e_ident[EI_MAG0] != ELFMAG0) || + (hdr->e_ident[EI_MAG1] != ELFMAG1) || + (hdr->e_ident[EI_MAG2] != ELFMAG2) || + (hdr->e_ident[EI_MAG3] != ELFMAG3)) { + err("Invalid ELF magic.\n"); + exit(EXIT_FAILURE); + } + + if (hdr->e_ident[EI_CLASS] != CURRENT_ELFCLASS) { + err("ELF class mismatch.\n"); + exit(EXIT_FAILURE); + } + + if (hdr->e_ident[EI_DATA] != endianness()) { + err("ELF endian mismatch.\n"); + exit(EXIT_FAILURE); + } + + if (hdr->e_shoff > vmlinux_size) { + err("Could not find section header.\n"); + exit(EXIT_FAILURE); + } + + symtab = get_symbol_table(hdr); + if (!symtab) { + warn("Could not find the symbol table.\n"); + if (!system_map_file) { + err("Please provide a System.map file.\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } + + system_map = fopen(system_map_file, "r"); + if (!system_map) { + perror(system_map_file); + exit(EXIT_FAILURE); + } + get_symbol_from_map(hdr, system_map, CERT_SYM, &cert_sym); + get_symbol_from_map(hdr, system_map, USED_SYM, &used_sym); + get_symbol_from_map(hdr, system_map, LSIZE_SYM, &lsize_sym); + cert_sym.size = used_sym.address - cert_sym.address; + } else { + info("Symbol table found.\n"); + if (system_map_file) + warn("System.map is ignored.\n"); + get_symbol_from_table(hdr, symtab, CERT_SYM, &cert_sym); + get_symbol_from_table(hdr, symtab, USED_SYM, &used_sym); + get_symbol_from_table(hdr, symtab, LSIZE_SYM, &lsize_sym); + } + + if (!cert_sym.offset || !lsize_sym.offset || !used_sym.offset) + exit(EXIT_FAILURE); + + print_sym(hdr, &cert_sym); + print_sym(hdr, &used_sym); + print_sym(hdr, &lsize_sym); + + lsize = (unsigned long *)lsize_sym.content; + used = (int *)used_sym.content; + + if (cert_sym.size < cert_size) { + err("Certificate is larger than the reserved area!\n"); + exit(EXIT_FAILURE); + } + + /* If the existing cert is the same, don't overwrite */ + if (cert_size == *used && + strncmp(cert_sym.content, cert, cert_size) == 0) { + warn("Certificate was already inserted.\n"); + exit(EXIT_SUCCESS); + } + + if (*used > 0) + warn("Replacing previously inserted certificate.\n"); + + memcpy(cert_sym.content, cert, cert_size); + if (cert_size < cert_sym.size) + memset(cert_sym.content + cert_size, + 0, cert_sym.size - cert_size); + + *lsize = *lsize + cert_size - *used; + *used = cert_size; + info("Inserted the contents of %s into %lx.\n", cert_file, + cert_sym.address); + info("Used %d bytes out of %d bytes reserved.\n", *used, + cert_sym.size); + exit(EXIT_SUCCESS); +} diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 000000000..05d62ac51 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 1995 by Linus Torvalds +# +# Adapted from code in arch/i386/boot/Makefile by H. Peter Anvin +# Common code factored out by Masahiro Yamada + +set -e + +# Make sure the files actually exist +for file in "${KBUILD_IMAGE}" System.map +do + if [ ! -f "${file}" ]; then + echo >&2 + echo >&2 " *** Missing file: ${file}" + echo >&2 ' *** You need to run "make" before "make install".' + echo >&2 + exit 1 + 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}" \ + "${srctree}/arch/${SRCARCH}/install.sh" \ + "${srctree}/arch/${SRCARCH}/boot/install.sh" +do + if [ ! -x "${file}" ]; then + continue + fi + + # installkernel(8) says the parameters are like follows: + # + # installkernel version zImage System.map [directory] + exec "${file}" "${KERNELRELEASE}" "${KBUILD_IMAGE}" System.map "${INSTALL_PATH}" +done + +echo "No install script found" >&2 +exit 1 diff --git a/scripts/integer-wrap-ignore.scl b/scripts/integer-wrap-ignore.scl new file mode 100644 index 000000000..431c3053a --- /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 000000000..e87553fbb --- /dev/null +++ b/scripts/ipe/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +subdir-y := polgen diff --git a/scripts/ipe/polgen/.gitignore b/scripts/ipe/polgen/.gitignore new file mode 100644 index 000000000..b6f05cf3d --- /dev/null +++ b/scripts/ipe/polgen/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +polgen diff --git a/scripts/ipe/polgen/Makefile b/scripts/ipe/polgen/Makefile new file mode 100644 index 000000000..c20456a2f --- /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 000000000..01134cf89 --- /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/jobserver-exec b/scripts/jobserver-exec new file mode 100755 index 000000000..21b319e6c --- /dev/null +++ b/scripts/jobserver-exec @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ + +""" +Determines how many parallel tasks "make" is expecting, as it is +not exposed via any special variables, reserves them all, runs a subprocess +with PARALLELISM environment variable set, and releases the jobs back again. + +See: + https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver +""" + +import os +import sys + +LIB_DIR = "../tools/lib/python" +SRC_DIR = os.path.dirname(os.path.realpath(__file__)) + +sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) + +from jobserver import JobserverExec # pylint: disable=C0415 + + +def main(): + """Main program""" + if len(sys.argv) < 2: + name = os.path.basename(__file__) + sys.exit("usage: " + name +" command [args ...]\n" + __doc__) + + with JobserverExec() as jobserver: + return jobserver.run(sys.argv[1:]) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c new file mode 100644 index 000000000..494852ade --- /dev/null +++ b/scripts/kallsyms.c @@ -0,0 +1,716 @@ +/* Generate assembler source containing symbol information + * + * Copyright 2002 by Kai Germaschewski + * + * 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] 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 + * map char code 0xF7 to represent "write_" and then in every symbol where + * "write_" appears it can be replaced by 0xF7, saving 5 bytes. + * The used codes themselves are also placed in the table so that the + * decompresion can work without "special cases". + * Applied to kernel symbols, this usually produces a compression ratio + * of about 50%. + * + */ + +#include <errno.h> +#include <getopt.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> + +#include <xalloc.h> + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +#define KSYM_NAME_LEN 512 + +struct sym_entry { + unsigned long long addr; + unsigned int len; + unsigned int seq; + unsigned char sym[]; +}; + +struct addr_range { + const char *start_sym, *end_sym; + unsigned long long start, end; +}; + +static unsigned long long _text; +static struct addr_range text_ranges[] = { + { "_stext", "_etext" }, + { "_sinittext", "_einittext" }, +}; +#define text_range_text (&text_ranges[0]) +#define text_range_inittext (&text_ranges[1]) + +static struct sym_entry **table; +static unsigned int table_size, table_cnt; +static int all_symbols; +static int pc_relative; + +static int token_profit[0x10000]; + +/* the table that holds the result of the compression */ +static unsigned char best_table[256][2]; +static unsigned char best_table_len[256]; + + +static void usage(void) +{ + fprintf(stderr, "Usage: kallsyms [--all-symbols] in.map > out.S\n"); + exit(1); +} + +static char *sym_name(const struct sym_entry *s) +{ + return (char *)s->sym + 1; +} + +static bool is_ignored_symbol(const char *name, char type) +{ + if (type == 'u' || type == 'n') + return true; + + if (toupper(type) == 'A') { + /* Keep these useful absolute symbols */ + if (strcmp(name, "__kernel_syscall_via_break") && + strcmp(name, "__kernel_syscall_via_epc") && + strcmp(name, "__kernel_sigtramp") && + strcmp(name, "__gp")) + return true; + } + + return false; +} + +static void check_symbol_range(const char *sym, unsigned long long addr, + struct addr_range *ranges, int entries) +{ + size_t i; + struct addr_range *ar; + + for (i = 0; i < entries; ++i) { + ar = &ranges[i]; + + if (strcmp(sym, ar->start_sym) == 0) { + ar->start = addr; + return; + } else if (strcmp(sym, ar->end_sym) == 0) { + ar->end = addr; + return; + } + } +} + +static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len) +{ + char *name, type, *p; + unsigned long long addr; + size_t len; + ssize_t readlen; + struct sym_entry *sym; + + errno = 0; + readlen = getline(buf, buf_len, in); + if (readlen < 0) { + if (errno) { + perror("read_symbol"); + exit(EXIT_FAILURE); + } + return NULL; + } + + if ((*buf)[readlen - 1] == '\n') + (*buf)[readlen - 1] = 0; + + addr = strtoull(*buf, &p, 16); + + if (*buf == p || *p++ != ' ' || !isascii((type = *p++)) || *p++ != ' ') { + fprintf(stderr, "line format error\n"); + exit(EXIT_FAILURE); + } + + name = p; + len = strlen(name); + + if (len >= KSYM_NAME_LEN) { + fprintf(stderr, "Symbol %s too long for kallsyms (%zu >= %d).\n" + "Please increase KSYM_NAME_LEN both in kernel and kallsyms.c\n", + name, len, KSYM_NAME_LEN); + return NULL; + } + + if (strcmp(name, "_text") == 0) + _text = addr; + + /* Ignore most absolute/undefined (?) symbols. */ + if (is_ignored_symbol(name, type)) + return NULL; + + check_symbol_range(name, addr, text_ranges, ARRAY_SIZE(text_ranges)); + + /* include the type field in the symbol name, so that it gets + * compressed together */ + len++; + + sym = xmalloc(sizeof(*sym) + len + 1); + sym->addr = addr; + sym->len = len; + sym->sym[0] = type; + strcpy(sym_name(sym), name); + + return sym; +} + +static int symbol_in_range(const struct sym_entry *s, + const struct addr_range *ranges, int entries) +{ + size_t i; + const struct addr_range *ar; + + for (i = 0; i < entries; ++i) { + ar = &ranges[i]; + + if (s->addr >= ar->start && s->addr <= ar->end) + return 1; + } + + 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); + + /* 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; + /* Corner case. Discard any symbols with the same value as + * _etext _einittext; they can move between pass 1 and 2 when + * the kallsyms data are added. If these symbols move then + * they may get dropped in pass 2, which breaks the kallsyms + * rules. + */ + if ((s->addr == text_range_text->end && + strcmp(name, text_range_text->end_sym)) || + (s->addr == text_range_inittext->end && + strcmp(name, text_range_inittext->end_sym))) + return 0; + } + + return 1; +} + +/* remove all the invalid symbols from the table */ +static void shrink_table(void) +{ + unsigned int i, pos; + + pos = 0; + for (i = 0; i < table_cnt; i++) { + if (symbol_valid(table[i])) { + if (pos != i) + table[pos] = table[i]; + pos++; + } else { + free(table[i]); + } + } + table_cnt = pos; +} + +static void read_map(const char *in) +{ + FILE *fp; + struct sym_entry *sym; + char *buf = NULL; + size_t buflen = 0; + + fp = fopen(in, "r"); + if (!fp) { + perror(in); + exit(1); + } + + while (!feof(fp)) { + sym = read_symbol(fp, &buf, &buflen); + if (!sym) + continue; + + sym->seq = table_cnt; + + if (table_cnt >= table_size) { + table_size += 10000; + table = xrealloc(table, sizeof(*table) * table_size); + } + + table[table_cnt++] = sym; + } + + free(buf); + fclose(fp); +} + +static void output_label(const char *label) +{ + printf(".globl %s\n", label); + printf("\t.balign 4\n"); + printf("%s:\n", label); +} + +/* 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) +{ + int c, rlen, total=0; + + while (len) { + c = *data; + /* if the table holds a single char that is the same as the one + * we are looking for, then end the search */ + if (best_table[c][0]==c && best_table_len[c]==1) { + *result++ = c; + total++; + } else { + /* if not, recurse and expand */ + rlen = expand_symbol(best_table[c], best_table_len[c], result); + total += rlen; + result += rlen; + } + data++; + len--; + } + *result=0; + + return total; +} + +static int compare_names(const void *a, const void *b) +{ + int ret; + const struct sym_entry *sa = *(const struct sym_entry **)a; + const struct sym_entry *sb = *(const struct sym_entry **)b; + + ret = strcmp(sym_name(sa), sym_name(sb)); + if (!ret) { + if (sa->addr > sb->addr) + return 1; + else if (sa->addr < sb->addr) + return -1; + + /* keep old order */ + return (int)(sa->seq - sb->seq); + } + + return ret; +} + +static void sort_symbols_by_name(void) +{ + qsort(table, table_cnt, sizeof(table[0]), compare_names); +} + +static void write_src(void) +{ + unsigned int i, k, off; + unsigned int best_idx[256]; + unsigned int *markers, markers_cnt; + char buf[KSYM_NAME_LEN]; + + printf("\t.section .rodata, \"a\"\n"); + + output_label("kallsyms_num_syms"); + printf("\t.long\t%u\n", table_cnt); + printf("\n"); + + /* table of offset markers, that give the offset in the compressed stream + * every 256 symbols */ + markers_cnt = (table_cnt + 255) / 256; + markers = xmalloc(sizeof(*markers) * markers_cnt); + + output_label("kallsyms_names"); + off = 0; + for (i = 0; i < table_cnt; i++) { + if ((i & 0xFF) == 0) + markers[i >> 8] = off; + table[i]->seq = i; + + /* There cannot be any symbol of length zero. */ + if (table[i]->len == 0) { + fprintf(stderr, "kallsyms failure: " + "unexpected zero symbol length\n"); + exit(EXIT_FAILURE); + } + + /* Only lengths that fit in up-to-two-byte ULEB128 are supported. */ + if (table[i]->len > 0x3FFF) { + fprintf(stderr, "kallsyms failure: " + "unexpected huge symbol length\n"); + exit(EXIT_FAILURE); + } + + /* Encode length with ULEB128. */ + if (table[i]->len <= 0x7F) { + /* Most symbols use a single byte for the length. */ + printf("\t.byte 0x%02x", table[i]->len); + off += table[i]->len + 1; + } else { + /* "Big" symbols use two bytes. */ + printf("\t.byte 0x%02x, 0x%02x", + (table[i]->len & 0x7F) | 0x80, + (table[i]->len >> 7) & 0x7F); + off += table[i]->len + 2; + } + for (k = 0; k < table[i]->len; k++) + printf(", 0x%02x", table[i]->sym[k]); + + /* + * 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(".size kallsyms_names, . - kallsyms_names\n"); + printf("\n"); + + output_label("kallsyms_markers"); + for (i = 0; i < markers_cnt; i++) + printf("\t.long\t%u\n", markers[i]); + printf(".size kallsyms_markers, . - kallsyms_markers\n"); + printf("\n"); + + free(markers); + + output_label("kallsyms_token_table"); + off = 0; + for (i = 0; i < 256; i++) { + best_idx[i] = off; + expand_symbol(best_table[i], best_table_len[i], buf); + printf("\t.asciz\t\"%s\"\n", buf); + off += strlen(buf) + 1; + } + printf(".size kallsyms_token_table, . - kallsyms_token_table\n"); + printf("\n"); + + output_label("kallsyms_token_index"); + for (i = 0; i < 256; i++) + printf("\t.short\t%d\n", best_idx[i]); + printf("\n"); + + output_label("kallsyms_offsets"); + + for (i = 0; i < table_cnt; i++) { + if (pc_relative) { + long long offset = table[i]->addr - _text; + + if (offset < INT_MIN || offset > INT_MAX) { + fprintf(stderr, "kallsyms failure: " + "relative symbol value %#llx out of range\n", + table[i]->addr); + exit(EXIT_FAILURE); + } + printf("\t.long\t_text - . + (%d)\t/* %s */\n", + (int)offset, table[i]->sym); + } else { + printf("\t.long\t%#x\t/* %s */\n", + (unsigned int)table[i]->addr, table[i]->sym); + } + } + printf(".size kallsyms_offsets, . - kallsyms_offsets\n"); + 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\t/* %s */\n", + (unsigned char)(table[i]->seq >> 16), + (unsigned char)(table[i]->seq >> 8), + (unsigned char)(table[i]->seq >> 0), + table[i]->sym); + printf("\n"); +} + + +/* table lookup compression functions */ + +/* count all the possible tokens in a symbol */ +static void learn_symbol(const unsigned char *symbol, int len) +{ + int i; + + for (i = 0; i < len - 1; i++) + token_profit[ symbol[i] + (symbol[i + 1] << 8) ]++; +} + +/* decrease the count for all the possible tokens in a symbol */ +static void forget_symbol(const unsigned char *symbol, int len) +{ + int i; + + for (i = 0; i < len - 1; i++) + token_profit[ symbol[i] + (symbol[i + 1] << 8) ]--; +} + +/* do the initial token count */ +static void build_initial_token_table(void) +{ + unsigned int i; + + for (i = 0; i < table_cnt; i++) + learn_symbol(table[i]->sym, table[i]->len); +} + +static unsigned char *find_token(unsigned char *str, int len, + const unsigned char *token) +{ + int i; + + for (i = 0; i < len - 1; i++) { + if (str[i] == token[0] && str[i+1] == token[1]) + return &str[i]; + } + return NULL; +} + +/* replace a given token in all the valid symbols. Use the sampled symbols + * to update the counts */ +static void compress_symbols(const unsigned char *str, int idx) +{ + unsigned int i, len, size; + unsigned char *p1, *p2; + + for (i = 0; i < table_cnt; i++) { + + len = table[i]->len; + p1 = table[i]->sym; + + /* find the token on the symbol */ + p2 = find_token(p1, len, str); + if (!p2) continue; + + /* decrease the counts for this symbol's tokens */ + forget_symbol(table[i]->sym, len); + + size = len; + + do { + *p2 = idx; + p2++; + size -= (p2 - p1); + memmove(p2, p2 + 1, size); + p1 = p2; + len--; + + if (size < 2) break; + + /* find the token on the symbol */ + p2 = find_token(p1, size, str); + + } while (p2); + + table[i]->len = len; + + /* increase the counts for this symbol's new tokens */ + learn_symbol(table[i]->sym, len); + } +} + +/* search the token with the maximum profit */ +static int find_best_token(void) +{ + int i, best, bestprofit; + + bestprofit=-10000; + best = 0; + + for (i = 0; i < 0x10000; i++) { + if (token_profit[i] > bestprofit) { + best = i; + bestprofit = token_profit[i]; + } + } + return best; +} + +/* this is the core of the algorithm: calculate the "best" table */ +static void optimize_result(void) +{ + int i, best; + + /* using the '\0' symbol last allows compress_symbols to use standard + * fast string functions */ + for (i = 255; i >= 0; i--) { + + /* if this table slot is empty (it is not used by an actual + * original char code */ + if (!best_table_len[i]) { + + /* find the token with the best profit value */ + best = find_best_token(); + if (token_profit[best] == 0) + break; + + /* place it in the "best" table */ + best_table_len[i] = 2; + best_table[i][0] = best & 0xFF; + best_table[i][1] = (best >> 8) & 0xFF; + + /* replace this token in all the valid symbols */ + compress_symbols(best_table[i], i); + } + } +} + +/* start by placing the symbols that are actually used on the table */ +static void insert_real_symbols_in_table(void) +{ + unsigned int i, j, c; + + for (i = 0; i < table_cnt; i++) { + for (j = 0; j < table[i]->len; j++) { + c = table[i]->sym[j]; + best_table[c][0]=c; + best_table_len[c]=1; + } + } +} + +static void optimize_token_table(void) +{ + build_initial_token_table(); + + insert_real_symbols_in_table(); + + optimize_result(); +} + +/* guess for "linker script provide" symbol */ +static int may_be_linker_script_provide_symbol(const struct sym_entry *se) +{ + const char *symbol = sym_name(se); + int len = se->len - 1; + + if (len < 8) + return 0; + + if (symbol[0] != '_' || symbol[1] != '_') + return 0; + + /* __start_XXXXX */ + if (!memcmp(symbol + 2, "start_", 6)) + return 1; + + /* __stop_XXXXX */ + if (!memcmp(symbol + 2, "stop_", 5)) + return 1; + + /* __end_XXXXX */ + if (!memcmp(symbol + 2, "end_", 4)) + return 1; + + /* __XXXXX_start */ + if (!memcmp(symbol + len - 6, "_start", 6)) + return 1; + + /* __XXXXX_end */ + if (!memcmp(symbol + len - 4, "_end", 4)) + return 1; + + return 0; +} + +static int compare_symbols(const void *a, const void *b) +{ + const struct sym_entry *sa = *(const struct sym_entry **)a; + const struct sym_entry *sb = *(const struct sym_entry **)b; + int wa, wb; + + /* sort by address first */ + if (sa->addr > sb->addr) + return 1; + if (sa->addr < sb->addr) + return -1; + + /* sort by "weakness" type */ + wa = (sa->sym[0] == 'w') || (sa->sym[0] == 'W'); + wb = (sb->sym[0] == 'w') || (sb->sym[0] == 'W'); + if (wa != wb) + return wa - wb; + + /* sort by "linker script provide" type */ + wa = may_be_linker_script_provide_symbol(sa); + wb = may_be_linker_script_provide_symbol(sb); + if (wa != wb) + return wa - wb; + + /* sort by the number of prefix underscores */ + wa = strspn(sym_name(sa), "_"); + wb = strspn(sym_name(sb), "_"); + if (wa != wb) + return wa - wb; + + /* sort by initial order, so that other symbols are left undisturbed */ + return sa->seq - sb->seq; +} + +static void sort_symbols(void) +{ + qsort(table, table_cnt, sizeof(table[0]), compare_symbols); +} + +int main(int argc, char **argv) +{ + while (1) { + static const struct option long_options[] = { + {"all-symbols", no_argument, &all_symbols, 1}, + {"pc-relative", no_argument, &pc_relative, 1}, + {}, + }; + + int c = getopt_long(argc, argv, "", long_options, NULL); + + if (c == -1) + break; + if (c != 0) + usage(); + } + + if (optind >= argc) + usage(); + + read_map(argv[optind]); + shrink_table(); + sort_symbols(); + optimize_token_table(); + write_src(); + + return 0; +} diff --git a/scripts/kconfig/.gitignore b/scripts/kconfig/.gitignore new file mode 100644 index 000000000..0b2ff775b --- /dev/null +++ b/scripts/kconfig/.gitignore @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +/conf +/[gmnq]conf +/[gmnq]conf-bin +/[gmnq]conf-cflags +/[gmnq]conf-libs +/qconf-moc.cc diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile new file mode 100644 index 000000000..5baf1c44f --- /dev/null +++ b/scripts/kconfig/Makefile @@ -0,0 +1,236 @@ +# SPDX-License-Identifier: GPL-2.0 +# =========================================================================== +# Kernel configuration targets +# These targets are used from top-level makefile + +ifdef KBUILD_KCONFIG +Kconfig := $(KBUILD_KCONFIG) +else +Kconfig := Kconfig +endif + +ifndef KBUILD_DEFCONFIG +KBUILD_DEFCONFIG := defconfig +endif + +ifeq ($(quiet),silent_) +silent := -s +endif + +export KCONFIG_DEFCONFIG_LIST := +ifndef cross_compiling +kernel-release := $(shell uname -r) +KCONFIG_DEFCONFIG_LIST += \ + /lib/modules/$(kernel-release)/.config \ + /etc/kernel-config \ + /boot/config-$(kernel-release) +endif +KCONFIG_DEFCONFIG_LIST += arch/$(SRCARCH)/configs/$(KBUILD_DEFCONFIG) + +ifneq ($(findstring c, $(KBUILD_EXTRA_WARN)),) +export KCONFIG_WARN_UNKNOWN_SYMBOLS=1 +endif + +ifneq ($(findstring e, $(KBUILD_EXTRA_WARN)),) +export KCONFIG_WERROR=1 +endif + +# We need this, in case the user has it in its environment +unexport CONFIG_ + +config-prog := conf +menuconfig-prog := mconf +nconfig-prog := nconf +gconfig-prog := gconf +xconfig-prog := qconf + +define config_rule +PHONY += $(1) +$(1): $(obj)/$($(1)-prog) + $(Q)$$< $(silent) $(Kconfig) + +PHONY += build_$(1) +build_$(1): $(obj)/$($(1)-prog) +endef + +$(foreach c, config menuconfig nconfig gconfig xconfig, $(eval $(call config_rule,$(c)))) + +PHONY += localmodconfig localyesconfig +localyesconfig localmodconfig: $(obj)/conf + $(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; \ + mv -f .tmp.config .config; \ + $< $(silent) --oldconfig $(Kconfig); \ + mv -f .config.old.1 .config.old) \ + else \ + mv -f .tmp.config .config; \ + $< $(silent) --oldconfig $(Kconfig); \ + fi + $(Q)rm -f .tmp.config + +# These targets map 1:1 to the commandline options of 'conf' +# +# Note: +# syncconfig has become an internal implementation detail and is now +# deprecated for external use +simple-targets := oldconfig allnoconfig allyesconfig allmodconfig \ + alldefconfig randconfig listnewconfig olddefconfig syncconfig \ + helpnewconfig yes2modconfig mod2yesconfig mod2noconfig + +PHONY += $(simple-targets) + +$(simple-targets): $(obj)/conf + $(Q)$< $(silent) --$@ $(Kconfig) + +PHONY += savedefconfig defconfig + +savedefconfig: $(obj)/conf + $(Q)$< $(silent) --$@=defconfig $(Kconfig) + +defconfig: $(obj)/conf +ifneq ($(wildcard $(srctree)/arch/$(SRCARCH)/configs/$(KBUILD_DEFCONFIG)),) + @$(kecho) "*** Default configuration is based on '$(KBUILD_DEFCONFIG)'" + $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$(KBUILD_DEFCONFIG) $(Kconfig) +else + @$(kecho) "*** Default configuration is based on target '$(KBUILD_DEFCONFIG)'" + $(Q)$(MAKE) -f $(srctree)/Makefile $(KBUILD_DEFCONFIG) +endif + +%_defconfig: $(obj)/conf + $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig) + +configfiles = $(wildcard $(srctree)/kernel/configs/$(1) $(srctree)/arch/$(SRCARCH)/configs/$(1)) +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)) + $(call cmd,merge_fragments) + $(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig + +PHONY += tinyconfig +tinyconfig: + $(Q)KCONFIG_ALLCONFIG=kernel/configs/tiny-base.config $(MAKE) -f $(srctree)/Makefile allnoconfig + $(Q)$(MAKE) -f $(srctree)/Makefile tiny.config + +# CHECK: -o cache_dir=<path> working? +PHONY += testconfig +testconfig: $(obj)/conf + $(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 + +# Help text used by make help +help: + @echo 'Configuration targets:' + @echo ' config - Update current config utilising a line-oriented program' + @echo ' nconfig - Update current config utilising a ncurses menu based program' + @echo ' menuconfig - Update current config utilising a menu based program' + @echo ' xconfig - Update current config utilising a Qt based front-end' + @echo ' gconfig - Update current config utilising a GTK+ based front-end' + @echo ' oldconfig - Update current config utilising a provided .config as base' + @echo ' localmodconfig - Update current config disabling modules not loaded' + @echo ' except those preserved by LMC_KEEP environment variable' + @echo ' localyesconfig - Update current config converting local mods to core' + @echo ' except those preserved by LMC_KEEP environment variable' + @echo ' defconfig - New config with default from ARCH supplied defconfig' + @echo ' savedefconfig - Save current config as ./defconfig (minimal config)' + @echo ' allnoconfig - New config where all options are answered with no' + @echo ' allyesconfig - New config where all options are accepted with yes' + @echo ' allmodconfig - New config selecting modules when possible' + @echo ' alldefconfig - New config with all symbols set to default' + @echo ' randconfig - New config with random answer to all options' + @echo ' yes2modconfig - Change answers from yes to mod if possible' + @echo ' mod2yesconfig - Change answers from mod to yes if possible' + @echo ' mod2noconfig - Change answers from mod to no if possible' + @echo ' listnewconfig - List new options' + @echo ' helpnewconfig - List new options and help text' + @echo ' olddefconfig - Same as oldconfig but sets new symbols to their' + @echo ' default value without prompting' + @echo ' tinyconfig - Configure the tiniest possible kernel' + @echo ' testconfig - Run Kconfig unit tests (requires python3 and pytest)' + @echo '' + @echo 'Configuration topic targets:' + @$(foreach f, $(all-config-fragments), \ + if help=$$(grep -m1 '^# Help: ' $(f)); then \ + printf ' %-25s - %s\n' '$(notdir $(f))' "$${help#*: }"; \ + fi;) + +# =========================================================================== +# object files used by all kconfig flavours +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 $(src) +HOSTCFLAGS_parser.tab.o := -I $(src) + +# conf: Used for defconfig, oldconfig and related targets +hostprogs += conf +conf-objs := conf.o $(common-objs) + +# nconf: Used for the nconfig target based on ncurses +hostprogs += nconf +nconf-objs := nconf.o nconf.gui.o mnconf-common.o $(common-objs) + +HOSTLDLIBS_nconf = $(call read-file, $(obj)/nconf-libs) +HOSTCFLAGS_nconf.o = $(call read-file, $(obj)/nconf-cflags) +HOSTCFLAGS_nconf.gui.o = $(call read-file, $(obj)/nconf-cflags) + +$(obj)/nconf: | $(obj)/nconf-libs +$(obj)/nconf.o $(obj)/nconf.gui.o: | $(obj)/nconf-cflags + +# mconf: Used for the menuconfig target based on lxdialog +hostprogs += mconf +lxdialog := $(addprefix lxdialog/, \ + checklist.o inputbox.o menubox.o textbox.o util.o yesno.o) +mconf-objs := mconf.o $(lxdialog) mnconf-common.o $(common-objs) + +HOSTLDLIBS_mconf = $(call read-file, $(obj)/mconf-libs) +$(foreach f, mconf.o $(lxdialog), \ + $(eval HOSTCFLAGS_$f = $$(call read-file, $(obj)/mconf-cflags))) + +$(obj)/mconf: | $(obj)/mconf-libs +$(addprefix $(obj)/, mconf.o $(lxdialog)): | $(obj)/mconf-cflags + +# qconf: Used for the xconfig target based on Qt +hostprogs += qconf +qconf-cxxobjs := qconf.o qconf-moc.o +qconf-objs := $(common-objs) + +HOSTLDLIBS_qconf = $(call read-file, $(obj)/qconf-libs) +HOSTCXXFLAGS_qconf.o = -std=c++11 -fPIC $(call read-file, $(obj)/qconf-cflags) +HOSTCXXFLAGS_qconf-moc.o = -std=c++11 -fPIC $(call read-file, $(obj)/qconf-cflags) +$(obj)/qconf: | $(obj)/qconf-libs +$(obj)/qconf.o $(obj)/qconf-moc.o: | $(obj)/qconf-cflags + +quiet_cmd_moc = MOC $@ + cmd_moc = $(call read-file, $(obj)/qconf-bin)/moc $< -o $@ + +$(obj)/qconf-moc.cc: $(src)/qconf.h FORCE | $(obj)/qconf-bin + $(call if_changed,moc) + +targets += qconf-moc.cc + +# gconf: Used for the gconfig target based on GTK+ +hostprogs += gconf +gconf-objs := gconf.o $(common-objs) + +HOSTLDLIBS_gconf = $(call read-file, $(obj)/gconf-libs) +HOSTCFLAGS_gconf.o = $(call read-file, $(obj)/gconf-cflags) + +$(obj)/gconf: | $(obj)/gconf-libs +$(obj)/gconf.o: | $(obj)/gconf-cflags + +# check if necessary packages are available, and configure build flags +cmd_conf_cfg = $< $(addprefix $(obj)/$*conf-, cflags libs bin); touch $(obj)/$*conf-bin + +$(obj)/%conf-cflags $(obj)/%conf-libs $(obj)/%conf-bin: $(src)/%conf-cfg.sh + $(call cmd,conf_cfg) + +clean-files += *conf-cflags *conf-libs *conf-bin diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c new file mode 100644 index 000000000..fe8ba09b0 --- /dev/null +++ b/scripts/kconfig/conf.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <getopt.h> +#include <sys/time.h> +#include <errno.h> + +#include "internal.h" +#include "lkc.h" + +static void conf(struct menu *menu); +static void check_conf(struct menu *menu); + +enum input_mode { + oldaskconfig, + syncconfig, + oldconfig, + allnoconfig, + allyesconfig, + allmodconfig, + alldefconfig, + randconfig, + defconfig, + savedefconfig, + listnewconfig, + helpnewconfig, + olddefconfig, + yes2modconfig, + mod2yesconfig, + mod2noconfig, +}; +static enum input_mode input_mode = oldaskconfig; +static int input_mode_opt; +static int indent = 1; +static int tty_stdio; +static int sync_kconfig; +static int conf_cnt; +static char line[PATH_MAX]; +static struct menu *rootEntry; + +static void print_help(struct menu *menu) +{ + struct gstr help = str_new(); + + menu_get_ext_help(menu, &help); + + printf("\n%s\n", str_get(&help)); + str_free(&help); +} + +static void strip(char *str) +{ + char *p = str; + int l; + + while ((isspace(*p))) + p++; + l = strlen(p); + if (p != str) + memmove(str, p, l + 1); + if (!l) + return; + p = str + l - 1; + while ((isspace(*p))) + *p-- = 0; +} + +/* Helper function to facilitate fgets() by Jean Sacren. */ +static void xfgets(char *str, int size, FILE *in) +{ + if (!fgets(str, size, in)) + fprintf(stderr, "\nError in reading or end of file.\n"); + + if (!tty_stdio) + printf("%s", str); +} + +static void set_randconfig_seed(void) +{ + unsigned int seed; + char *env; + bool seed_set = false; + + env = getenv("KCONFIG_SEED"); + if (env && *env) { + char *endp; + + seed = strtol(env, &endp, 0); + if (*endp == '\0') + seed_set = true; + } + + if (!seed_set) { + struct timeval now; + + /* + * Use microseconds derived seed, compensate for systems where it may + * be zero. + */ + gettimeofday(&now, NULL); + seed = (now.tv_sec + 1) * (now.tv_usec + 1); + } + + printf("KCONFIG_SEED=0x%X\n", seed); + srand(seed); +} + +/** + * randomize_choice_values - randomize choice block + * + * @choice: menu entry for the choice + */ +static void randomize_choice_values(struct menu *choice) +{ + struct menu *menu; + int x; + int cnt = 0; + + /* + * 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. + */ + menu_for_each_sub_entry(menu, choice) { + struct symbol *sym = menu->sym; + + if (sym && !sym_has_value(sym)) + cnt++; + } + + while (cnt > 0) { + x = rand() % cnt; + + menu_for_each_sub_entry(menu, choice) { + struct symbol *sym = menu->sym; + + 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; + } + } + cnt--; + } +} + +enum conf_def_mode { + def_default, + def_yes, + def_mod, + def_no, + def_random +}; + +static void conf_set_all_new_symbols(enum conf_def_mode mode) +{ + struct menu *menu; + int cnt; + /* + * can't go as the default in switch-case below, otherwise gcc whines + * about -Wmaybe-uninitialized + */ + int pby = 50; /* probability of bool = y */ + int pty = 33; /* probability of tristate = y */ + int ptm = 33; /* probability of tristate = m */ + + if (mode == def_random) { + int n, p[3]; + char *env = getenv("KCONFIG_PROBABILITY"); + + n = 0; + while (env && *env) { + char *endp; + int tmp = strtol(env, &endp, 10); + + if (tmp >= 0 && tmp <= 100) { + p[n++] = tmp; + } else { + errno = ERANGE; + perror("KCONFIG_PROBABILITY"); + exit(1); + } + env = (*endp == ':') ? endp + 1 : endp; + if (n >= 3) + break; + } + switch (n) { + case 1: + pby = p[0]; + ptm = pby / 2; + pty = pby - ptm; + break; + case 2: + pty = p[0]; + ptm = p[1]; + pby = pty + ptm; + break; + case 3: + pby = p[0]; + pty = p[1]; + ptm = p[2]; + break; + } + + if (pty + ptm > 100) { + errno = ERANGE; + perror("KCONFIG_PROBABILITY"); + exit(1); + } + } + + menu_for_each_entry(menu) { + struct symbol *sym = menu->sym; + tristate val; + + if (!sym || !menu->prompt || sym_has_value(sym) || + (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) || + sym_is_choice_value(sym)) + continue; + + if (sym_is_choice(sym)) { + if (mode == def_random) + randomize_choice_values(menu); + continue; + } + + 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->def[S_DEF_USER].tri = val; + sym->flags |= SYMBOL_DEF_USER; + } + + sym_clear_all_valid(); +} + +static void conf_rewrite_tristates(tristate old_val, tristate new_val) +{ + struct symbol *sym; + + for_all_symbols(sym) { + if (sym_get_type(sym) == S_TRISTATE && + sym->def[S_DEF_USER].tri == old_val) + sym->def[S_DEF_USER].tri = new_val; + } + sym_clear_all_valid(); +} + +static int conf_askvalue(struct symbol *sym, const char *def) +{ + if (!sym_has_value(sym)) + printf("(NEW) "); + + line[0] = '\n'; + line[1] = 0; + + if (!sym_is_changeable(sym)) { + printf("%s\n", def ?: ""); + return 0; + } + + switch (input_mode) { + case oldconfig: + case syncconfig: + if (sym_has_value(sym)) { + printf("%s\n", def ?: ""); + return 0; + } + /* fall through */ + default: + fflush(stdout); + xfgets(line, sizeof(line), stdin); + break; + } + + return 1; +} + +static int conf_string(struct menu *menu) +{ + struct symbol *sym = menu->sym; + const char *def; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + printf("(%s) ", sym->name); + def = sym_get_string_value(sym); + if (def) + printf("[%s] ", def); + if (!conf_askvalue(sym, def)) + return 0; + switch (line[0]) { + case '\n': + break; + case '?': + /* print help */ + if (line[1] == '\n') { + print_help(menu); + def = NULL; + break; + } + /* fall through */ + default: + line[strlen(line)-1] = 0; + def = line; + } + if (def && sym_set_string_value(sym, def)) + return 0; + + /* + * A new int or hex symbol whose default fails validation + * cannot be set from an empty answer. When standard input is + * exhausted, as it is for a non-interactive oldconfig or + * syncconfig, re-asking would loop forever and grow the output + * until it exhausts memory. Stop with an error that names the + * symbol instead. String symbols accept any text, and bool and + * tristate symbols (conf_sym()) and choices (conf_choice()) + * accept the default on an empty line, so they are unaffected. + */ + if (feof(stdin)) { + fprintf(stderr, + "\nerror: no value for new symbol '%s' at end of input\n", + sym->name); + exit(1); + } + } +} + +static int conf_sym(struct menu *menu) +{ + struct symbol *sym = menu->sym; + tristate oldval, newval; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + if (sym->name) + printf("(%s) ", sym->name); + putchar('['); + oldval = sym_get_tristate_value(sym); + switch (oldval) { + case no: + putchar('N'); + break; + case mod: + putchar('M'); + break; + case yes: + putchar('Y'); + break; + } + if (oldval != no && sym_tristate_within_range(sym, no)) + printf("/n"); + if (oldval != mod && sym_tristate_within_range(sym, mod)) + printf("/m"); + if (oldval != yes && sym_tristate_within_range(sym, yes)) + printf("/y"); + printf("/?] "); + if (!conf_askvalue(sym, sym_get_string_value(sym))) + return 0; + strip(line); + + switch (line[0]) { + case 'n': + case 'N': + newval = no; + if (!line[1] || !strcmp(&line[1], "o")) + break; + continue; + case 'm': + case 'M': + newval = mod; + if (!line[1]) + break; + continue; + case 'y': + case 'Y': + newval = yes; + if (!line[1] || !strcmp(&line[1], "es")) + break; + continue; + case 0: + newval = oldval; + break; + case '?': + goto help; + default: + continue; + } + if (sym_set_tristate_value(sym, newval)) + return 0; +help: + print_help(menu); + } +} + +static void conf_choice(struct menu *menu) +{ + struct symbol *def_sym; + struct menu *child; + bool is_new = false; + + while (1) { + int cnt, def; + + printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); + def_sym = sym_calc_choice(menu); + cnt = def = 0; + line[0] = 0; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + if (!child->sym) { + printf("%*c %s\n", indent, '*', menu_get_prompt(child)); + continue; + } + cnt++; + if (child->sym == def_sym) { + def = cnt; + printf("%*c", indent, '>'); + } else + printf("%*c", indent, ' '); + 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, ""); + if (cnt == 1) { + printf("[1]: 1\n"); + goto conf_childs; + } + printf("[1-%d?]: ", cnt); + switch (input_mode) { + case oldconfig: + case syncconfig: + if (!is_new) { + cnt = def; + printf("%d\n", cnt); + break; + } + /* fall through */ + case oldaskconfig: + fflush(stdout); + xfgets(line, sizeof(line), stdin); + strip(line); + if (line[0] == '?') { + print_help(menu); + continue; + } + if (!line[0]) + cnt = def; + else if (isdigit(line[0])) + cnt = atoi(line); + else + continue; + break; + default: + break; + } + + conf_childs: + for (child = menu->list; child; child = child->next) { + if (!child->sym || !menu_is_visible(child)) + continue; + if (!--cnt) + break; + } + if (!child) + continue; + if (line[0] && line[strlen(line) - 1] == '?') { + print_help(child); + continue; + } + choice_set_value(menu, child->sym); + return; + } +} + +static void conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + prop = menu->prompt; + if (prop) { + const char *prompt; + + switch (prop->type) { + case P_MENU: + /* + * Except in oldaskconfig mode, we show only menus that + * contain new symbols. + */ + if (input_mode != oldaskconfig && rootEntry != menu) { + check_conf(menu); + return; + } + /* fall through */ + case P_COMMENT: + prompt = menu_get_prompt(menu); + if (prompt) + printf("%*c\n%*c %s\n%*c\n", + indent, '*', + indent, '*', prompt, + indent, '*'); + default: + ; + } + } + + if (!sym) + goto conf_childs; + + if (sym_is_choice(sym)) { + conf_choice(menu); + return; + } + + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + conf_string(menu); + break; + default: + conf_sym(menu); + break; + } + +conf_childs: + if (sym) + indent += 2; + for (child = menu->list; child; child = child->next) + conf(child); + if (sym) + indent -= 2; +} + +static void check_conf(struct menu *menu) +{ + struct symbol *sym; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + if (sym && !sym_has_value(sym) && sym_is_changeable(sym)) { + switch (input_mode) { + case listnewconfig: + if (sym->name) + print_symbol_for_listconfig(sym); + break; + case helpnewconfig: + printf("-----\n"); + print_help(menu); + printf("-----\n"); + break; + default: + if (!conf_cnt++) + printf("*\n* Restart config...\n*\n"); + rootEntry = menu_get_menu_or_parent_menu(menu); + conf(rootEntry); + break; + } + } + + for (child = menu->list; child; child = child->next) + check_conf(child); +} + +static const struct option long_opts[] = { + {"help", no_argument, NULL, 'h'}, + {"silent", no_argument, NULL, 's'}, + {"oldaskconfig", no_argument, &input_mode_opt, oldaskconfig}, + {"oldconfig", no_argument, &input_mode_opt, oldconfig}, + {"syncconfig", no_argument, &input_mode_opt, syncconfig}, + {"defconfig", required_argument, &input_mode_opt, defconfig}, + {"savedefconfig", required_argument, &input_mode_opt, savedefconfig}, + {"allnoconfig", no_argument, &input_mode_opt, allnoconfig}, + {"allyesconfig", no_argument, &input_mode_opt, allyesconfig}, + {"allmodconfig", no_argument, &input_mode_opt, allmodconfig}, + {"alldefconfig", no_argument, &input_mode_opt, alldefconfig}, + {"randconfig", no_argument, &input_mode_opt, randconfig}, + {"listnewconfig", no_argument, &input_mode_opt, listnewconfig}, + {"helpnewconfig", no_argument, &input_mode_opt, helpnewconfig}, + {"olddefconfig", no_argument, &input_mode_opt, olddefconfig}, + {"yes2modconfig", no_argument, &input_mode_opt, yes2modconfig}, + {"mod2yesconfig", no_argument, &input_mode_opt, mod2yesconfig}, + {"mod2noconfig", no_argument, &input_mode_opt, mod2noconfig}, + {NULL, 0, NULL, 0} +}; + +static void conf_usage(const char *progname) +{ + printf("Usage: %s [options] kconfig_file\n", progname); + printf("\n"); + printf("Generic options:\n"); + printf(" -h, --help Print this message and exit.\n"); + printf(" -s, --silent Do not print log.\n"); + printf("\n"); + printf("Mode options:\n"); + printf(" --listnewconfig List new options\n"); + printf(" --helpnewconfig List new options and help text\n"); + printf(" --oldaskconfig Start a new configuration using a line-oriented program\n"); + printf(" --oldconfig Update a configuration using a provided .config as base\n"); + printf(" --syncconfig Similar to oldconfig but generates configuration in\n" + " include/{generated/,config/}\n"); + printf(" --olddefconfig Same as oldconfig but sets new symbols to their default value\n"); + printf(" --defconfig <file> New config with default defined in <file>\n"); + printf(" --savedefconfig <file> Save the minimal current configuration to <file>\n"); + printf(" --allnoconfig New config where all options are answered with no\n"); + printf(" --allyesconfig New config where all options are answered with yes\n"); + printf(" --allmodconfig New config where all options are answered with mod\n"); + printf(" --alldefconfig New config with all symbols set to default\n"); + printf(" --randconfig New config with random answer to all options\n"); + printf(" --yes2modconfig Change answers from yes to mod if possible\n"); + 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) +{ + const char *progname = av[0]; + int opt; + const char *name, *defconfig_file = NULL /* gcc uninit */; + int no_conf_write = 0; + + tty_stdio = isatty(0) && isatty(1); + + while ((opt = getopt_long(ac, av, "hs", long_opts, NULL)) != -1) { + switch (opt) { + case 'h': + conf_usage(progname); + exit(1); + break; + case 's': + conf_set_message_callback(NULL); + break; + case 0: + input_mode = input_mode_opt; + switch (input_mode) { + case syncconfig: + /* + * syncconfig is invoked during the build stage. + * Suppress distracting + * "configuration written to ..." + */ + conf_set_message_callback(NULL); + sync_kconfig = 1; + break; + case defconfig: + case savedefconfig: + defconfig_file = optarg; + break; + case randconfig: + set_randconfig_seed(); + break; + default: + break; + } + default: + break; + } + } + if (ac == optind) { + fprintf(stderr, "%s: Kconfig file missing\n", av[0]); + conf_usage(progname); + exit(1); + } + conf_parse(av[optind]); + //zconfdump(stdout); + + switch (input_mode) { + case defconfig: + if (conf_read(defconfig_file)) { + fprintf(stderr, + "***\n" + "*** Can't find default configuration \"%s\"!\n" + "***\n", + defconfig_file); + exit(1); + } + break; + case savedefconfig: + case syncconfig: + case oldaskconfig: + case oldconfig: + case listnewconfig: + case helpnewconfig: + case olddefconfig: + case yes2modconfig: + case mod2yesconfig: + case mod2noconfig: + conf_read(NULL); + break; + case allnoconfig: + case allyesconfig: + case allmodconfig: + case alldefconfig: + case randconfig: + name = getenv("KCONFIG_ALLCONFIG"); + if (!name) + break; + if ((strcmp(name, "") != 0) && (strcmp(name, "1") != 0)) { + if (conf_read_simple(name, S_DEF_USER)) { + fprintf(stderr, + "*** Can't read seed configuration \"%s\"!\n", + name); + exit(1); + } + break; + } + switch (input_mode) { + case allnoconfig: name = "allno.config"; break; + case allyesconfig: name = "allyes.config"; break; + case allmodconfig: name = "allmod.config"; break; + case alldefconfig: name = "alldef.config"; break; + case randconfig: name = "allrandom.config"; break; + default: break; + } + if (conf_read_simple(name, S_DEF_USER) && + conf_read_simple("all.config", S_DEF_USER)) { + fprintf(stderr, + "*** KCONFIG_ALLCONFIG set, but no \"%s\" or \"all.config\" file found\n", + name); + exit(1); + } + break; + default: + break; + } + + if (conf_errors()) + exit(1); + + if (sync_kconfig) { + name = getenv("KCONFIG_NOSILENTUPDATE"); + if (name && *name) { + if (conf_get_changed()) { + fprintf(stderr, + "\n*** The configuration requires explicit update.\n\n"); + return 1; + } + no_conf_write = 1; + } + } + + switch (input_mode) { + case allnoconfig: + conf_set_all_new_symbols(def_no); + break; + case allyesconfig: + conf_set_all_new_symbols(def_yes); + break; + case allmodconfig: + conf_set_all_new_symbols(def_mod); + break; + case alldefconfig: + conf_set_all_new_symbols(def_default); + break; + case randconfig: + conf_set_all_new_symbols(def_random); + break; + case defconfig: + conf_set_all_new_symbols(def_default); + break; + case savedefconfig: + break; + case yes2modconfig: + conf_rewrite_tristates(yes, mod); + break; + case mod2yesconfig: + conf_rewrite_tristates(mod, yes); + break; + case mod2noconfig: + conf_rewrite_tristates(mod, no); + break; + case oldaskconfig: + rootEntry = &rootmenu; + conf(&rootmenu); + input_mode = oldconfig; + /* fall through */ + case oldconfig: + case listnewconfig: + case helpnewconfig: + case syncconfig: + /* Update until a loop caused no more changes */ + do { + conf_cnt = 0; + check_conf(&rootmenu); + } while (conf_cnt); + break; + case olddefconfig: + default: + break; + } + + if (sym_dep_errors()) + exit(1); + + if (input_mode == savedefconfig) { + if (conf_write_defconfig(defconfig_file)) { + fprintf(stderr, "n*** Error while saving defconfig to: %s\n\n", + defconfig_file); + return 1; + } + } else if (input_mode != listnewconfig && input_mode != helpnewconfig) { + if (!no_conf_write && conf_write(NULL)) { + fprintf(stderr, "\n*** Error during writing of the configuration.\n\n"); + exit(1); + } + + /* + * Create auto.conf if it does not exist. + * This prevents GNU Make 4.1 or older from emitting + * "include/config/auto.conf: No such file or directory" + * in the top-level Makefile + * + * syncconfig always creates or updates auto.conf because it is + * used during the build. + */ + if (conf_write_autoconf(sync_kconfig) && sync_kconfig) { + fprintf(stderr, + "\n*** Error during sync of the configuration.\n\n"); + return 1; + } + } + return 0; +} diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c new file mode 100644 index 000000000..4234a51d1 --- /dev/null +++ b/scripts/kconfig/confdata.c @@ -0,0 +1,1241 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <xalloc.h> +#include "internal.h" +#include "lkc.h" + +struct gstr autoconf_cmd; + +/* return true if 'path' exists, false otherwise */ +static bool is_present(const char *path) +{ + struct stat st; + + return !stat(path, &st); +} + +/* return true if 'path' exists and it is a directory, false otherwise */ +static bool is_dir(const char *path) +{ + struct stat st; + + if (stat(path, &st)) + return false; + + return S_ISDIR(st.st_mode); +} + +/* return true if the given two files are the same, false otherwise */ +static bool is_same(const char *file1, const char *file2) +{ + int fd1, fd2; + struct stat st1, st2; + void *map1, *map2; + bool ret = false; + + fd1 = open(file1, O_RDONLY); + if (fd1 < 0) + return ret; + + fd2 = open(file2, O_RDONLY); + if (fd2 < 0) + goto close1; + + ret = fstat(fd1, &st1); + if (ret) + goto close2; + ret = fstat(fd2, &st2); + if (ret) + goto close2; + + if (st1.st_size != st2.st_size) + goto close2; + + map1 = mmap(NULL, st1.st_size, PROT_READ, MAP_PRIVATE, fd1, 0); + if (map1 == MAP_FAILED) + goto close2; + + map2 = mmap(NULL, st2.st_size, PROT_READ, MAP_PRIVATE, fd2, 0); + if (map2 == MAP_FAILED) + goto close2; + + if (memcmp(map1, map2, st1.st_size)) + goto close2; + + ret = true; +close2: + close(fd2); +close1: + close(fd1); + + return ret; +} + +/* + * Create the parent directory of the given path. + * + * For example, if 'include/config/auto.conf' is given, create 'include/config'. + */ +static int make_parent_dir(const char *path) +{ + char tmp[PATH_MAX + 1]; + char *p; + + strncpy(tmp, path, sizeof(tmp)); + tmp[sizeof(tmp) - 1] = 0; + + /* Remove the base name. Just return if nothing is left */ + p = strrchr(tmp, '/'); + if (!p) + return 0; + *(p + 1) = 0; + + /* Just in case it is an absolute path */ + p = tmp; + while (*p == '/') + p++; + + while ((p = strchr(p, '/'))) { + *p = 0; + + /* skip if the directory exists */ + if (!is_dir(tmp) && mkdir(tmp, 0755)) + return -1; + + *p = '/'; + while (*p == '/') + p++; + } + + return 0; +} + +static char depfile_path[PATH_MAX]; +static size_t depfile_prefix_len; + +/* touch depfile for symbol 'name' */ +static int conf_touch_dep(const char *name) +{ + int fd; + + /* check overflow: prefix + name + '\0' must fit in buffer. */ + if (depfile_prefix_len + strlen(name) + 1 > sizeof(depfile_path)) + return -1; + + strcpy(depfile_path + depfile_prefix_len, name); + + fd = open(depfile_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) + return -1; + close(fd); + + return 0; +} + +static void conf_warning(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static void conf_message(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static const char *conf_filename; +static int conf_lineno, conf_warnings; + +bool conf_errors(void) +{ + if (conf_warnings) + return getenv("KCONFIG_WERROR"); + return false; +} + +static void conf_warning(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", conf_filename, conf_lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + conf_warnings++; +} + +static void conf_default_message_callback(const char *s) +{ + printf("#\n# "); + printf("%s", s); + printf("\n#\n"); +} + +static void (*conf_message_callback)(const char *s) = + conf_default_message_callback; +void conf_set_message_callback(void (*fn)(const char *s)) +{ + conf_message_callback = fn; +} + +static void conf_message(const char *fmt, ...) +{ + va_list ap; + char buf[4096]; + + if (!conf_message_callback) + return; + + va_start(ap, fmt); + + vsnprintf(buf, sizeof(buf), fmt, ap); + conf_message_callback(buf); + va_end(ap); +} + +static void conf_changed_input_warning(const char *s) +{ + fputs(s, stderr); +} + +static bool conf_warn_changed_input_enabled(void) +{ + const char *env = getenv("KCONFIG_WARN_CHANGED_INPUT"); + + return env && *env; +} + +static const char *sym_get_user_value_string(struct symbol *sym) +{ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (sym->def[S_DEF_USER].tri) { + case yes: + return "y"; + case mod: + return "m"; + default: + return "n"; + } + default: + return sym->def[S_DEF_USER].val ?: ""; + } +} + +static bool sym_user_value_changed(struct symbol *sym) +{ + if (!sym_has_value(sym) || sym->type == S_UNKNOWN) + return false; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + return sym->def[S_DEF_USER].tri != sym_get_tristate_value(sym); + default: + return strcmp(sym_get_user_value_string(sym), + sym_get_string_value(sym)); + } +} + +static void conf_clear_written_flags(void) +{ + struct symbol *sym; + + for_all_symbols(sym) + sym->flags &= ~SYMBOL_WRITTEN; +} + +static void conf_append_changed_input_warning(struct gstr *gs, + struct symbol *sym, + bool *changed_input_found) +{ + if (!sym_user_value_changed(sym)) + return; + + if (!*changed_input_found) { + str_printf(gs, + "warning: user-provided values changed by Kconfig:\n"); + *changed_input_found = true; + } + + str_printf(gs, " %s%s: %s -> %s\n", + CONFIG_, sym->name, + sym_get_user_value_string(sym), + sym_get_string_value(sym)); +} + +const char *conf_get_configname(void) +{ + char *name = getenv("KCONFIG_CONFIG"); + + return name ? name : ".config"; +} + +static const char *conf_get_autoconfig_name(void) +{ + char *name = getenv("KCONFIG_AUTOCONFIG"); + + return name ? name : "include/config/auto.conf"; +} + +static const char *conf_get_autoheader_name(void) +{ + char *name = getenv("KCONFIG_AUTOHEADER"); + + return name ? name : "include/generated/autoconf.h"; +} + +static const char *conf_get_rustccfg_name(void) +{ + char *name = getenv("KCONFIG_RUSTCCFG"); + + return name ? name : "include/generated/rustc_cfg"; +} + +static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p) +{ + char *p2; + + switch (sym->type) { + case S_TRISTATE: + if (p[0] == 'm') { + sym->def[def].tri = mod; + sym->flags |= def_flags; + break; + } + /* fall through */ + case S_BOOLEAN: + if (p[0] == 'y') { + sym->def[def].tri = yes; + sym->flags |= def_flags; + break; + } + if (p[0] == 'n') { + sym->def[def].tri = no; + sym->flags |= def_flags; + break; + } + if (def != S_DEF_AUTO) + conf_warning("symbol value '%s' invalid for %s", + p, sym->name); + return 1; + case S_STRING: + /* No escaping for S_DEF_AUTO (include/config/auto.conf) */ + if (def != S_DEF_AUTO) { + if (*p++ != '"') + break; + for (p2 = p; (p2 = strpbrk(p2, "\"\\")); p2++) { + if (*p2 == '"') { + *p2 = 0; + break; + } + memmove(p2, p2 + 1, strlen(p2)); + } + if (!p2) { + conf_warning("invalid string found"); + return 1; + } + } + /* fall through */ + case S_INT: + case S_HEX: + if (sym_string_valid(sym, p)) { + sym->def[def].val = xstrdup(p); + sym->flags |= def_flags; + } else { + if (def != S_DEF_AUTO) + conf_warning("symbol value '%s' invalid for %s", + p, sym->name); + return 1; + } + break; + default: + ; + } + return 0; +} + +/* like getline(), but the newline character is stripped away */ +static ssize_t getline_stripped(char **lineptr, size_t *n, FILE *stream) +{ + ssize_t len; + + len = getline(lineptr, n, stream); + + if (len > 0 && (*lineptr)[len - 1] == '\n') { + len--; + (*lineptr)[len] = '\0'; + + if (len > 0 && (*lineptr)[len - 1] == '\r') { + len--; + (*lineptr)[len] = '\0'; + } + } + + return len; +} + +int conf_read_simple(const char *name, int def) +{ + FILE *in = NULL; + char *line = NULL; + size_t line_asize = 0; + char *p, *val; + struct symbol *sym; + int def_flags; + const char *warn_unknown, *sym_name; + + warn_unknown = getenv("KCONFIG_WARN_UNKNOWN_SYMBOLS"); + if (name) { + in = zconf_fopen(name); + } else { + char *env; + + name = conf_get_configname(); + in = zconf_fopen(name); + if (in) + goto load; + conf_set_changed(true); + + env = getenv("KCONFIG_DEFCONFIG_LIST"); + if (!env) + return 1; + + while (1) { + bool is_last; + + while (isspace(*env)) + env++; + + if (!*env) + break; + + p = env; + while (*p && !isspace(*p)) + p++; + + is_last = (*p == '\0'); + + *p = '\0'; + + name = env; + + in = zconf_fopen(name); + if (in) { + conf_message("using defaults found in %s", + name); + goto load; + } + + if (is_last) + break; + + env = p + 1; + } + } + if (!in) + return 1; + +load: + conf_filename = name; + conf_lineno = 0; + conf_warnings = 0; + + def_flags = SYMBOL_DEF << def; + for_all_symbols(sym) { + sym->flags &= ~def_flags; + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + free(sym->def[def].val); + /* fall through */ + default: + sym->def[def].val = NULL; + sym->def[def].tri = no; + } + } + + 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 */ + continue; + + if (line[0] == '#') { + if (line[1] != ' ') + continue; + p = line + 2; + if (memcmp(p, CONFIG_, strlen(CONFIG_))) + continue; + sym_name = p + strlen(CONFIG_); + p = strchr(sym_name, ' '); + if (!p) + continue; + *p++ = 0; + if (strcmp(p, "is not set")) + continue; + + val = "n"; + } else { + if (memcmp(line, CONFIG_, strlen(CONFIG_))) { + conf_warning("unexpected data: %s", line); + continue; + } + + sym_name = line + strlen(CONFIG_); + p = strchr(sym_name, '='); + if (!p) { + conf_warning("unexpected data: %s", line); + continue; + } + *p = 0; + val = p + 1; + } + + sym = sym_find(sym_name); + if (!sym) { + if (def == S_DEF_AUTO) { + /* + * Reading from include/config/auto.conf. + * If CONFIG_FOO previously existed in auto.conf + * but it is missing now, include/config/FOO + * must be touched. + */ + conf_touch_dep(sym_name); + } else { + if (warn_unknown) + conf_warning("unknown symbol: %s", sym_name); + + conf_set_changed(true); + } + continue; + } + + if (sym->flags & def_flags) + conf_warning("override: reassigning to symbol %s", sym->name); + + if (conf_set_sym_val(sym, def, def_flags, val)) + continue; + + 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); + + return 0; +} + +int conf_read(const char *name) +{ + struct symbol *sym; + + conf_set_changed(false); + + if (conf_read_simple(name, S_DEF_USER)) { + sym_calc_value(modules_sym); + return 1; + } + + sym_calc_value(modules_sym); + + for_all_symbols(sym) { + sym_calc_value(sym); + if (sym_is_choice(sym)) + continue; + if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) { + /* check that calculated value agrees with saved value */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym->def[S_DEF_USER].tri == sym_get_tristate_value(sym)) + continue; + break; + default: + if (!strcmp(sym->curr.val, sym->def[S_DEF_USER].val)) + continue; + break; + } + } else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE)) + /* no previous value and not saved */ + continue; + conf_set_changed(true); + /* maybe print value in verbose mode... */ + } + + if (conf_warnings) + conf_set_changed(true); + + return 0; +} + +struct comment_style { + const char *decoration; + const char *prefix; + const char *postfix; +}; + +static const struct comment_style comment_style_pound = { + .decoration = "#", + .prefix = "#", + .postfix = "#", +}; + +static const struct comment_style comment_style_c = { + .decoration = " *", + .prefix = "/*", + .postfix = " */", +}; + +static void conf_write_heading(FILE *fp, const struct comment_style *cs) +{ + if (!cs) + return; + + fprintf(fp, "%s\n", cs->prefix); + + fprintf(fp, "%s Automatically generated file; DO NOT EDIT.\n", + cs->decoration); + + fprintf(fp, "%s %s\n", cs->decoration, rootmenu.prompt->text); + + fprintf(fp, "%s\n", cs->postfix); +} + +/* The returned pointer must be freed on the caller side */ +static char *escape_string_value(const char *in) +{ + const char *p; + char *out; + size_t len; + + len = strlen(in) + strlen("\"\"") + 1; + + p = in; + while (1) { + p += strcspn(p, "\"\\"); + + if (p[0] == '\0') + break; + + len++; + p++; + } + + out = xmalloc(len); + out[0] = '\0'; + + strcat(out, "\""); + + p = in; + while (1) { + len = strcspn(p, "\"\\"); + strncat(out, p, len); + p += len; + + if (p[0] == '\0') + break; + + strcat(out, "\\"); + strncat(out, p++, 1); + } + + strcat(out, "\""); + + return out; +} + +enum output_n { OUTPUT_N, OUTPUT_N_AS_UNSET, OUTPUT_N_NONE }; + +static void __print_symbol(FILE *fp, struct symbol *sym, enum output_n output_n, + bool escape_string) +{ + const char *val; + char *escaped = NULL; + + if (sym->type == S_UNKNOWN) + return; + + val = sym_get_string_value(sym); + + if ((sym->type == S_BOOLEAN || sym->type == S_TRISTATE) && + output_n != OUTPUT_N && *val == 'n') { + if (output_n == OUTPUT_N_AS_UNSET) + fprintf(fp, "# %s%s is not set\n", CONFIG_, sym->name); + return; + } + + if (sym->type == S_STRING && escape_string) { + escaped = escape_string_value(val); + val = escaped; + } + + fprintf(fp, "%s%s=%s\n", CONFIG_, sym->name, val); + + free(escaped); +} + +static void print_symbol_for_dotconfig(FILE *fp, struct symbol *sym) +{ + __print_symbol(fp, sym, OUTPUT_N_AS_UNSET, true); +} + +static void print_symbol_for_autoconf(FILE *fp, struct symbol *sym) +{ + __print_symbol(fp, sym, OUTPUT_N_NONE, false); +} + +void print_symbol_for_listconfig(struct symbol *sym) +{ + __print_symbol(stdout, sym, OUTPUT_N, true); +} + +static void print_symbol_for_c(FILE *fp, struct symbol *sym) +{ + const char *val; + const char *sym_suffix = ""; + const char *val_prefix = ""; + char *escaped = NULL; + + if (sym->type == S_UNKNOWN) + return; + + val = sym_get_string_value(sym); + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (*val) { + case 'n': + return; + case 'm': + sym_suffix = "_MODULE"; + /* fall through */ + default: + val = "1"; + } + break; + case S_HEX: + if (val[0] != '0' || (val[1] != 'x' && val[1] != 'X')) + val_prefix = "0x"; + break; + case S_STRING: + escaped = escape_string_value(val); + val = escaped; + default: + break; + } + + fprintf(fp, "#define %s%s%s %s%s\n", CONFIG_, sym->name, sym_suffix, + val_prefix, val); + + free(escaped); +} + +static void print_symbol_for_rustccfg(FILE *fp, struct symbol *sym) +{ + const char *val; + const char *val_prefix = ""; + char *val_prefixed = NULL; + size_t val_prefixed_len; + char *escaped = NULL; + + if (sym->type == S_UNKNOWN) + return; + + val = sym_get_string_value(sym); + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + /* + * We do not care about disabled ones, i.e. no need for + * what otherwise are "comments" in other printers. + */ + if (*val == 'n') + return; + + /* + * To have similar functionality to the C macro `IS_ENABLED()` + * we provide an empty `--cfg CONFIG_X` here in both `y` + * and `m` cases. + * + * Then, the common `fprintf()` below will also give us + * a `--cfg CONFIG_X="y"` or `--cfg CONFIG_X="m"`, which can + * be used as the equivalent of `IS_BUILTIN()`/`IS_MODULE()`. + */ + fprintf(fp, "--cfg=%s%s\n", CONFIG_, sym->name); + break; + case S_HEX: + if (val[0] != '0' || (val[1] != 'x' && val[1] != 'X')) + val_prefix = "0x"; + break; + default: + break; + } + + if (strlen(val_prefix) > 0) { + val_prefixed_len = strlen(val) + strlen(val_prefix) + 1; + val_prefixed = xmalloc(val_prefixed_len); + snprintf(val_prefixed, val_prefixed_len, "%s%s", val_prefix, val); + val = val_prefixed; + } + + /* All values get escaped: the `--cfg` option only takes strings */ + escaped = escape_string_value(val); + val = escaped; + + fprintf(fp, "--cfg=%s%s=%s\n", CONFIG_, sym->name, val); + + free(escaped); + free(val_prefixed); +} + +/* + * Write out a minimal config. + * All values that has default values are skipped as this is redundant. + */ +int conf_write_defconfig(const char *filename) +{ + struct symbol *sym; + struct menu *menu; + struct gstr gs; + FILE *out; + bool warn_changed_input = conf_warn_changed_input_enabled(); + bool changed_input_found = false; + + out = fopen(filename, "w"); + if (!out) + return 1; + gs = str_new(); + + sym_clear_all_valid(); + + menu_for_each_entry(menu) { + struct menu *choice; + + sym = menu->sym; + + if (!sym || sym_is_choice(sym) || sym->flags & SYMBOL_WRITTEN) + continue; + + sym_calc_value(sym); + if (warn_changed_input) + conf_append_changed_input_warning(&gs, sym, + &changed_input_found); + sym->flags |= SYMBOL_WRITTEN; + 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); + + conf_clear_written_flags(); + + if (changed_input_found) + conf_changed_input_warning(str_get(&gs)); + + str_free(&gs); + return 0; +} + +int conf_write(const char *name) +{ + FILE *out; + struct symbol *sym; + struct menu *menu; + const char *str; + char tmpname[PATH_MAX + 1], oldname[PATH_MAX + 1]; + char *env; + struct gstr gs; + bool need_newline = false; + bool warn_changed_input = conf_warn_changed_input_enabled(); + bool changed_input_found = false; + + if (!name) + name = conf_get_configname(); + + if (!*name) { + fprintf(stderr, "config name is empty\n"); + return -1; + } + + if (is_dir(name)) { + fprintf(stderr, "%s: Is a directory\n", name); + return -1; + } + + if (make_parent_dir(name)) + return -1; + + env = getenv("KCONFIG_OVERWRITECONFIG"); + if (env && *env) { + *tmpname = 0; + out = fopen(name, "w"); + } else { + snprintf(tmpname, sizeof(tmpname), "%s.%d.tmp", + name, (int)getpid()); + out = fopen(tmpname, "w"); + } + if (!out) + return 1; + gs = str_new(); + + conf_write_heading(out, &comment_style_pound); + + if (!conf_get_changed()) + sym_clear_all_valid(); + + menu = rootmenu.list; + while (menu) { + sym = menu->sym; + if (!sym) { + if (!menu_is_visible(menu)) + goto next; + str = menu_get_prompt(menu); + fprintf(out, "\n" + "#\n" + "# %s\n" + "#\n", str); + need_newline = false; + } else if (!sym_is_choice(sym) && + !(sym->flags & SYMBOL_WRITTEN)) { + sym_calc_value(sym); + if (warn_changed_input) + conf_append_changed_input_warning(&gs, sym, + &changed_input_found); + sym->flags |= SYMBOL_WRITTEN; + if (!(sym->flags & SYMBOL_WRITE)) + goto next; + if (need_newline) { + fprintf(out, "\n"); + need_newline = false; + } + print_symbol_for_dotconfig(out, sym); + } + +next: + if (menu->list) { + menu = menu->list; + continue; + } + +end_check: + if (!menu->sym && menu_is_visible(menu) && menu != &rootmenu && + menu->prompt->type == P_MENU) { + fprintf(out, "# end of %s\n", menu_get_prompt(menu)); + need_newline = true; + } + + if (menu->next) { + menu = menu->next; + } else { + menu = menu->parent; + if (menu) + goto end_check; + } + } + fclose(out); + + conf_clear_written_flags(); + + if (changed_input_found) + conf_changed_input_warning(str_get(&gs)); + + str_free(&gs); + + if (*tmpname) { + if (is_same(name, tmpname)) { + conf_message("No change to %s", name); + unlink(tmpname); + conf_set_changed(false); + return 0; + } + + snprintf(oldname, sizeof(oldname), "%s.old", name); + rename(name, oldname); + if (rename(tmpname, name)) + return 1; + } + + conf_message("configuration written to %s", name); + + conf_set_changed(false); + + return 0; +} + +/* write a dependency file as used by kbuild to track dependencies */ +static int conf_write_autoconf_cmd(const char *autoconf_name) +{ + char name[PATH_MAX], tmp[PATH_MAX]; + FILE *out; + int ret; + + ret = snprintf(name, sizeof(name), "%s.cmd", autoconf_name); + if (ret >= sizeof(name)) /* check truncation */ + return -1; + + if (make_parent_dir(name)) + return -1; + + ret = snprintf(tmp, sizeof(tmp), "%s.cmd.tmp", autoconf_name); + if (ret >= sizeof(tmp)) /* check truncation */ + return -1; + + out = fopen(tmp, "w"); + if (!out) { + perror("fopen"); + return -1; + } + + fprintf(out, "autoconfig := %s\n", autoconf_name); + + fputs(str_get(&autoconf_cmd), out); + + fflush(out); + ret = ferror(out); /* error check for all fprintf() calls */ + fclose(out); + if (ret) + return -1; + + if (rename(tmp, name)) { + perror("rename"); + return -1; + } + + return 0; +} + +static int conf_touch_deps(void) +{ + const char *name, *tmp; + struct symbol *sym; + int res; + + name = conf_get_autoconfig_name(); + tmp = strrchr(name, '/'); + depfile_prefix_len = tmp ? tmp - name + 1 : 0; + if (depfile_prefix_len + 1 > sizeof(depfile_path)) + return -1; + + strncpy(depfile_path, name, depfile_prefix_len); + depfile_path[depfile_prefix_len] = 0; + + conf_read_simple(name, S_DEF_AUTO); + + for_all_symbols(sym) { + if (sym_is_choice(sym)) + continue; + if (sym->flags & SYMBOL_WRITE) { + if (sym->flags & SYMBOL_DEF_AUTO) { + /* + * symbol has old and new value, + * so compare them... + */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym_get_tristate_value(sym) == + sym->def[S_DEF_AUTO].tri) + continue; + break; + case S_STRING: + case S_HEX: + case S_INT: + if (!strcmp(sym_get_string_value(sym), + sym->def[S_DEF_AUTO].val)) + continue; + break; + default: + break; + } + } else { + /* + * If there is no old value, only 'no' (unset) + * is allowed as new value. + */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym_get_tristate_value(sym) == no) + continue; + break; + default: + break; + } + } + } else if (!(sym->flags & SYMBOL_DEF_AUTO)) + /* There is neither an old nor a new value. */ + continue; + /* else + * There is an old value, but no new value ('no' (unset) + * isn't saved in auto.conf, so the old value is always + * different from 'no'). + */ + + res = conf_touch_dep(sym->name); + if (res) + return res; + } + + return 0; +} + +static int __conf_write_autoconf(const char *filename, + void (*print_symbol)(FILE *, struct symbol *), + const struct comment_style *comment_style) +{ + char tmp[PATH_MAX]; + FILE *file; + struct symbol *sym; + int ret; + + if (make_parent_dir(filename)) + return -1; + + ret = snprintf(tmp, sizeof(tmp), "%s.tmp", filename); + if (ret >= sizeof(tmp)) /* check truncation */ + return -1; + + file = fopen(tmp, "w"); + if (!file) { + perror("fopen"); + return -1; + } + + conf_write_heading(file, comment_style); + + for_all_symbols(sym) + if ((sym->flags & SYMBOL_WRITE) && sym->name) + print_symbol(file, sym); + + fflush(file); + /* check possible errors in conf_write_heading() and print_symbol() */ + ret = ferror(file); + fclose(file); + if (ret) + return -1; + + if (rename(tmp, filename)) { + perror("rename"); + return -1; + } + + return 0; +} + +int conf_write_autoconf(int overwrite) +{ + struct symbol *sym; + const char *autoconf_name = conf_get_autoconfig_name(); + int ret; + + if (!overwrite && is_present(autoconf_name)) + return 0; + + ret = conf_write_autoconf_cmd(autoconf_name); + if (ret) + 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); + if (ret) + return ret; + + ret = __conf_write_autoconf(conf_get_rustccfg_name(), + print_symbol_for_rustccfg, + NULL); + if (ret) + return ret; + + /* + * Create include/config/auto.conf. This must be the last step because + * Kbuild has a dependency on auto.conf and this marks the successful + * completion of the previous steps. + */ + ret = __conf_write_autoconf(conf_get_autoconfig_name(), + print_symbol_for_autoconf, + &comment_style_pound); + if (ret) + return ret; + + return 0; +} + +static bool conf_changed; +static void (*conf_changed_callback)(bool); + +void conf_set_changed(bool val) +{ + if (conf_changed_callback && conf_changed != val) + conf_changed_callback(val); + + conf_changed = val; +} + +bool conf_get_changed(void) +{ + return conf_changed; +} + +void conf_set_changed_callback(void (*fn)(bool)) +{ + conf_changed_callback = fn; +} diff --git a/scripts/kconfig/expr.c b/scripts/kconfig/expr.c new file mode 100644 index 000000000..2b91d16bf --- /dev/null +++ b/scripts/kconfig/expr.c @@ -0,0 +1,1213 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#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); + +/** + * 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; + 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) +{ + return expr_lookup(type, ce, NULL); +} + +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2) +{ + return expr_lookup(type, e1, e2); +} + +struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2) +{ + return expr_lookup(type, s1, s2); +} + +struct expr *expr_alloc_and(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_AND, e1, e2) : e1; +} + +struct expr *expr_alloc_or(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_OR, e1, e2) : e1; +} + +static int trans_count; + +/* + * expr_eliminate_eq() helper. + * + * Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does + * not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared + * against all other leaves. Two equal leaves are both replaced with either 'y' + * or 'n' as appropriate for 'type', to be eliminated later. + */ +static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ + struct expr *l, *r; + + /* Recurse down to leaves */ + + 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 ((*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; + } + + /* *ep1 and *ep2 are leaves. Compare them. */ + + 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(*ep1, *ep2)) + return; + + /* *ep1 and *ep2 are equal leaves. Prepare them for elimination. */ + + trans_count++; + switch (type) { + case E_OR: + *ep1 = expr_alloc_symbol(&symbol_no); + *ep2 = expr_alloc_symbol(&symbol_no); + break; + case E_AND: + *ep1 = expr_alloc_symbol(&symbol_yes); + *ep2 = expr_alloc_symbol(&symbol_yes); + break; + default: + ; + } +} + +/* + * Rewrites the expressions 'ep1' and 'ep2' to remove operands common to both. + * Example reductions: + * + * ep1: A && B -> ep1: y + * ep2: A && B && C -> ep2: C + * + * ep1: A || B -> ep1: n + * ep2: A || B || C -> ep2: C + * + * ep1: A && (B && FOO) -> ep1: FOO + * ep2: (BAR && B) && A -> ep2: BAR + * + * ep1: A && (B || C) -> ep1: y + * ep2: (C || B) && A -> ep2: y + * + * Comparisons are done between all operands at the same "level" of && or ||. + * For example, in the expression 'e1 && (e2 || e3) && (e4 || e5)', the + * following operands will be compared: + * + * - 'e1', 'e2 || e3', and 'e4 || e5', against each other + * - e2 against e3 + * - e4 against e5 + * + * Parentheses are irrelevant within a single level. 'e1 && (e2 && e3)' and + * '(e1 && e2) && e3' are both a single level. + * + * See __expr_eliminate_eq() as well. + */ +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2) +{ + if (!*ep1 || !*ep2) + return; + switch ((*ep1)->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq((*ep1)->type, ep1, ep2); + default: + ; + } + if ((*ep1)->type != (*ep2)->type) switch ((*ep2)->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq((*ep2)->type, ep1, ep2); + default: + ; + } + *ep1 = expr_eliminate_yn(*ep1); + *ep2 = expr_eliminate_yn(*ep2); +} + +/* + * 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. + */ +bool expr_eq(struct expr *e1, struct expr *e2) +{ + int old_count; + bool res; + + /* + * A NULL expr is taken to be yes, but there's also a different way to + * represent yes. expr_is_yes() checks for either representation. + */ + if (!e1 || !e2) + return expr_is_yes(e1) && expr_is_yes(e2); + + if (e1->type != e2->type) + return false; + switch (e1->type) { + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + return e1->left.sym == e2->left.sym && e1->right.sym == e2->right.sym; + case E_SYMBOL: + return e1->left.sym == e2->left.sym; + case E_NOT: + return expr_eq(e1->left.expr, e2->left.expr); + case E_AND: + case E_OR: + old_count = trans_count; + expr_eliminate_eq(&e1, &e2); + res = (e1->type == E_SYMBOL && e2->type == E_SYMBOL && + e1->left.sym == e2->left.sym); + trans_count = old_count; + return res; + case E_RANGE: + case E_NONE: + /* panic */; + } + + if (DEBUG_EXPR) { + expr_fprint(e1, stdout); + printf(" = "); + expr_fprint(e2, stdout); + printf(" ?\n"); + } + + return false; +} + +/* + * Recursively performs the following simplifications (as well as the + * corresponding simplifications with swapped operands): + * + * expr && n -> n + * expr && y -> expr + * expr || n -> expr + * expr || y -> y + * + * Returns the optimized expression. + */ +static struct expr *expr_eliminate_yn(struct expr *e) +{ + struct expr *l, *r; + + if (e) switch (e->type) { + case E_AND: + 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 (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: + 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 (r->type == E_SYMBOL) { + if (r->left.sym == &symbol_no) + return l; + else if (r->left.sym == &symbol_yes) + return r; + } + break; + default: + ; + } + return e; +} + +/* + * e1 || e2 -> ? + */ +static struct expr *expr_join_or(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + 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) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='m') -> (a!='n') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_no); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='n') -> (a!='m') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_mod); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) { + // (a='m') || (a='n') -> (a!='y') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_yes); + } + } + 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); + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") || ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +static struct expr *expr_join_and(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + 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) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_yes)) + // (a) && (a='y') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_no) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_no)) + // (a) && (a!='n') -> (a) + return expr_alloc_symbol(sym1); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_mod)) + // (a) && (a!='m') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_UNEQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e1->right.sym; + if ((e2->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e2->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_EQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e2->right.sym; + if ((e1->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e1->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_mod); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='m') -> (a='n') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_no); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) + // (a!='m') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_mod) || + (e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_yes)) + return NULL; + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") && ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +/* + * expr_eliminate_dups() helper. + * + * Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does + * not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared + * against all other leaves to look for simplifications. + */ +static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ + struct expr *tmp, *l, *r; + + /* Recurse down to leaves */ + + 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 ((*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; + } + + /* *ep1 and *ep2 are leaves. Compare and process them. */ + + switch (type) { + case E_OR: + tmp = expr_join_or(*ep1, *ep2); + if (tmp) { + *ep1 = expr_alloc_symbol(&symbol_no); + *ep2 = tmp; + trans_count++; + } + break; + case E_AND: + tmp = expr_join_and(*ep1, *ep2); + if (tmp) { + *ep1 = expr_alloc_symbol(&symbol_yes); + *ep2 = tmp; + trans_count++; + } + break; + default: + ; + } +} + +/* + * Rewrites 'e' in-place to remove ("join") duplicate and other redundant + * operands. + * + * Example simplifications: + * + * A || B || A -> A || B + * A && B && A=y -> A=y && B + * + * Returns the deduplicated expression. + */ +struct expr *expr_eliminate_dups(struct expr *e) +{ + int oldcount; + if (!e) + return e; + + oldcount = trans_count; + do { + struct expr *l, *r; + + trans_count = 0; + switch (e->type) { + case E_OR: case E_AND: + 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: + ; + } + e = expr_eliminate_yn(e); + } while (trans_count); /* repeat until we get no more simplifications */ + trans_count = oldcount; + return 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) +{ + if (!e) + return NULL; + switch (e->type) { + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + case E_SYMBOL: + break; + default: + e = expr_alloc_two(e->type, + expr_transform(e->left.expr), + expr_transform(e->right.expr)); + } + + switch (e->type) { + case E_EQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + // 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 = expr_alloc_symbol(&symbol_no); + break; + } + if (e->right.sym == &symbol_yes) { + // A=y -> A + e = expr_alloc_symbol(e->left.sym); + break; + } + break; + case E_UNEQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + // 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 = expr_alloc_symbol(&symbol_yes); + break; + } + if (e->right.sym == &symbol_yes) { + // 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 + e = e->left.expr->left.expr; + break; + case E_EQUAL: + case E_UNEQUAL: + // !(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<=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<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 + 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 + 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) + // !'y' -> 'n' + e = expr_alloc_symbol(&symbol_no); + else if (e->left.expr->left.sym == &symbol_mod) + // !'m' -> 'm' + e = expr_alloc_symbol(&symbol_mod); + else if (e->left.expr->left.sym == &symbol_no) + // !'n' -> 'y' + e = expr_alloc_symbol(&symbol_yes); + break; + default: + ; + } + break; + default: + ; + } + return e; +} + +bool expr_contains_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return false; + + switch (dep->type) { + case E_AND: + case E_OR: + return expr_contains_symbol(dep->left.expr, sym) || + expr_contains_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + return dep->left.sym == sym || + dep->right.sym == sym; + case E_NOT: + return expr_contains_symbol(dep->left.expr, sym); + default: + ; + } + return false; +} + +/* + * Check if the expression references 'sym' in a way that is satisfiable + * with 'sym' disabled, e.g.'sym!=y'. + * + * Expects that expr_transform() was already called on 'expr'. + */ +bool expr_contains_symbol_negated(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return false; + + switch (dep->type) { + case E_AND: + case E_OR: + return expr_contains_symbol_negated(dep->left.expr, sym) || + expr_contains_symbol_negated(dep->right.expr, sym); + case E_NOT: + return dep->left.expr->type == E_SYMBOL && + dep->left.expr->left.sym == sym; + case E_EQUAL: + /* sym=n */ + return dep->left.sym == sym && dep->right.sym == &symbol_no; + case E_UNEQUAL: + /* sym!=y, sym!=m */ + return dep->left.sym == sym && + (dep->right.sym == &symbol_yes || + dep->right.sym == &symbol_mod); + default: + break; + } + return false; +} + +bool expr_depends_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return false; + + switch (dep->type) { + case E_AND: + return expr_depends_symbol(dep->left.expr, sym) || + expr_depends_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_yes || dep->right.sym == &symbol_mod) + return true; + } + break; + case E_UNEQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_no) + return true; + } + break; + default: + ; + } + return false; +} + +/* + * Inserts explicit comparisons of type 'type' to symbol 'sym' into the + * expression 'e'. + * + * Examples transformations for type == E_UNEQUAL, sym == &symbol_no: + * + * A -> A!=n + * !A -> A=n + * A && B -> !(A=n || B=n) + * A || B -> !(A=n && B=n) + * A && (B || C) -> !(A=n || (B=n && C=n)) + * + * Allocates and returns a new expression. + */ +struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym) +{ + struct expr *e1, *e2; + + if (!e) { + e = expr_alloc_symbol(sym); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + } + switch (e->type) { + case E_AND: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_AND, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_OR, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_OR: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_OR, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_AND, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_NOT: + return expr_trans_compare(e->left.expr, type == E_EQUAL ? E_UNEQUAL : E_EQUAL, sym); + case E_UNEQUAL: + case E_LTH: + case E_LEQ: + case E_GTH: + case E_GEQ: + case E_EQUAL: + if (type == E_EQUAL) { + if (sym == &symbol_yes) + return e; + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_no); + if (sym == &symbol_no) + return expr_alloc_one(E_NOT, e); + } else { + if (sym == &symbol_yes) + return expr_alloc_one(E_NOT, e); + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_yes); + if (sym == &symbol_no) + return e; + } + break; + case E_SYMBOL: + return expr_alloc_comp(type, e->left.sym, sym); + case E_RANGE: + case E_NONE: + /* panic */; + } + return NULL; +} + +enum string_value_kind { + k_string, + k_signed, + k_unsigned, +}; + +union string_value { + unsigned long long u; + signed long long s; +}; + +static enum string_value_kind expr_parse_string(const char *str, + enum symbol_type type, + union string_value *val) +{ + char *tail; + enum string_value_kind kind; + + errno = 0; + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + val->s = !strcmp(str, "n") ? 0 : + !strcmp(str, "m") ? 1 : + !strcmp(str, "y") ? 2 : -1; + return k_signed; + case S_INT: + val->s = strtoll(str, &tail, 10); + kind = k_signed; + break; + case S_HEX: + val->u = strtoull(str, &tail, 16); + kind = k_unsigned; + break; + default: + val->s = strtoll(str, &tail, 0); + kind = k_signed; + break; + } + return !errno && !*tail && tail > str && isxdigit(tail[-1]) + ? kind : k_string; +} + +static tristate __expr_calc_value(struct expr *e) +{ + tristate val1, val2; + const char *str1, *str2; + enum string_value_kind k1 = k_string, k2 = k_string; + union string_value lval = {}, rval = {}; + int res; + + switch (e->type) { + case E_SYMBOL: + sym_calc_value(e->left.sym); + return e->left.sym->curr.tri; + case E_AND: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return EXPR_AND(val1, val2); + case E_OR: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return EXPR_OR(val1, val2); + case E_NOT: + val1 = expr_calc_value(e->left.expr); + return EXPR_NOT(val1); + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + break; + default: + printf("expr_calc_value: %d?\n", e->type); + return no; + } + + sym_calc_value(e->left.sym); + sym_calc_value(e->right.sym); + str1 = sym_get_string_value(e->left.sym); + str2 = sym_get_string_value(e->right.sym); + + if (e->left.sym->type != S_STRING || e->right.sym->type != S_STRING) { + k1 = expr_parse_string(str1, e->left.sym->type, &lval); + k2 = expr_parse_string(str2, e->right.sym->type, &rval); + } + + if (k1 == k_string || k2 == k_string) + res = strcmp(str1, str2); + else if (k1 == k_unsigned || k2 == k_unsigned) + res = (lval.u > rval.u) - (lval.u < rval.u); + else /* if (k1 == k_signed && k2 == k_signed) */ + res = (lval.s > rval.s) - (lval.s < rval.s); + + switch(e->type) { + case E_EQUAL: + return res ? no : yes; + case E_GEQ: + return res >= 0 ? yes : no; + case E_GTH: + return res > 0 ? yes : no; + case E_LEQ: + return res <= 0 ? yes : no; + case E_LTH: + return res < 0 ? yes : no; + case E_UNEQUAL: + return res ? yes : no; + default: + printf("expr_calc_value: relation %d?\n", e->type); + return no; + } +} + +/** + * 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) + return 0; + switch (t1) { + case E_LEQ: + case E_LTH: + case E_GEQ: + 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; + /* fallthrough */ + default: + break; + } + return 0; +} + +void expr_print(const struct expr *e, + void (*fn)(void *, struct symbol *, const char *), + void *data, int prevtoken) +{ + if (!e) { + fn(data, NULL, "y"); + return; + } + + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, NULL, "("); + switch (e->type) { + case E_SYMBOL: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, "<choice>"); + break; + case E_NOT: + fn(data, NULL, "!"); + expr_print(e->left.expr, fn, data, E_NOT); + break; + case E_EQUAL: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, "<choice>"); + fn(data, NULL, "="); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_LEQ: + case E_LTH: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, "<choice>"); + fn(data, NULL, e->type == E_LEQ ? "<=" : "<"); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_GEQ: + case E_GTH: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, "<choice>"); + fn(data, NULL, e->type == E_GEQ ? ">=" : ">"); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_UNEQUAL: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, "<choice>"); + fn(data, NULL, "!="); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_OR: + expr_print(e->left.expr, fn, data, E_OR); + fn(data, NULL, " || "); + expr_print(e->right.expr, fn, data, E_OR); + break; + case E_AND: + expr_print(e->left.expr, fn, data, E_AND); + fn(data, NULL, " && "); + expr_print(e->right.expr, fn, data, E_AND); + break; + case E_RANGE: + fn(data, NULL, "["); + fn(data, e->left.sym, e->left.sym->name); + fn(data, NULL, " "); + fn(data, e->right.sym, e->right.sym->name); + fn(data, NULL, "]"); + break; + default: + { + char buf[32]; + sprintf(buf, "<unknown type %d>", e->type); + fn(data, NULL, buf); + break; + } + } + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, NULL, ")"); +} + +static void expr_print_file_helper(void *data, struct symbol *sym, const char *str) +{ + xfwrite(str, strlen(str), 1, data); +} + +void expr_fprint(struct expr *e, FILE *out) +{ + expr_print(e, expr_print_file_helper, out, E_NONE); +} + +static void expr_print_gstr_helper(void *data, struct symbol *sym, const char *str) +{ + struct gstr *gs = (struct gstr*)data; + const char *sym_str = NULL; + + if (sym) + sym_str = sym_get_string_value(sym); + + if (gs->max_width) { + unsigned extra_length = strlen(str); + const char *last_cr = strrchr(gs->s, '\n'); + unsigned last_line_length; + + if (sym_str) + extra_length += 4 + strlen(sym_str); + + if (!last_cr) + last_cr = gs->s; + + last_line_length = strlen(gs->s) - (last_cr - gs->s); + + if ((last_line_length + extra_length) > gs->max_width) + str_append(gs, "\\\n"); + } + + str_append(gs, str); + if (sym && sym->type != S_UNKNOWN) + str_printf(gs, " [=%s]", sym_str); +} + +void expr_gstr_print(const struct expr *e, struct gstr *gs) +{ + expr_print(e, expr_print_gstr_helper, gs, E_NONE); +} + +/* + * Transform the top level "||" tokens into newlines and prepend each + * line with a minus. This makes expressions much easier to read. + * Suitable for reverse dependency expressions. + */ +static void expr_print_revdep(struct expr *e, + void (*fn)(void *, struct symbol *, const char *), + void *data, tristate pr_type, const char **title) +{ + if (e->type == E_OR) { + expr_print_revdep(e->left.expr, fn, data, pr_type, title); + expr_print_revdep(e->right.expr, fn, data, pr_type, title); + } else if (expr_calc_value(e) == pr_type) { + if (*title) { + fn(data, NULL, *title); + *title = NULL; + } + + fn(data, NULL, " - "); + expr_print(e, fn, data, E_NONE); + fn(data, NULL, "\n"); + } +} + +void expr_gstr_print_revdep(struct expr *e, struct gstr *gs, + tristate pr_type, const char *title) +{ + expr_print_revdep(e, expr_print_gstr_helper, gs, pr_type, &title); +} diff --git a/scripts/kconfig/expr.h b/scripts/kconfig/expr.h new file mode 100644 index 000000000..b580f9fa0 --- /dev/null +++ b/scripts/kconfig/expr.h @@ -0,0 +1,329 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#ifndef EXPR_H +#define EXPR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <assert.h> +#include <stdio.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +#include <list_types.h> + +typedef enum tristate { + no, mod, yes +} 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_SYMBOL, E_RANGE +}; + +union expr_data { + struct expr * const expr; + struct symbol * const sym; + void *_initdata; +}; + +/** + * struct expr - expression + * + * @node: link node for the hash table + * @type: expression 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; +}; + +#define EXPR_OR(dep1, dep2) (((dep1)>(dep2))?(dep1):(dep2)) +#define EXPR_AND(dep1, dep2) (((dep1)<(dep2))?(dep1):(dep2)) +#define EXPR_NOT(dep) (2-(dep)) + +struct expr_value { + struct expr *expr; + tristate tri; +}; + +struct symbol_value { + void *val; + tristate tri; +}; + +enum symbol_type { + S_UNKNOWN, S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING +}; + +/* enum values are used as index to symbol.def[] */ +enum { + S_DEF_USER, /* main user value */ + S_DEF_AUTO, /* values read from auto.conf */ + S_DEF_DEF3, /* Reserved for UI usage */ + S_DEF_DEF4, /* Reserved for UI usage */ + S_DEF_COUNT +}; + +/* + * Represents a configuration symbol. + * + * 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 */ + struct hlist_node node; + + /* The name of the symbol, e.g. "FOO" for 'config FOO' */ + char *name; + + /* S_BOOLEAN, S_TRISTATE, ... */ + enum symbol_type type; + + /* + * The calculated value of the symbol. The SYMBOL_VALID bit is set in + * 'flags' when this is up to date. Note that this value might differ + * from the user value set in e.g. a .config file, due to visibility. + */ + struct symbol_value curr; + + /* + * Values for the symbol provided from outside. def[S_DEF_USER] holds + * the .config value. + */ + struct symbol_value def[S_DEF_COUNT]; + + /* + * An upper bound on the tristate value the user can set for the symbol + * if it is a boolean or tristate. Calculated from prompt dependencies, + * which also inherit dependencies from enclosing menus, choices, and + * ifs. If 'n', the user value will be ignored. + * + * Symbols lacking prompts always have visibility 'n'. + */ + tristate visible; + + /* config entries associated with this symbol */ + struct list_head menus; + + struct list_head choice_link; + + /* SYMBOL_* flags */ + int flags; + + /* List of properties. See prop_type. */ + struct property *prop; + + /* Dependencies from enclosing menus, choices, and ifs */ + struct expr_value dir_dep; + + /* Reverse dependencies through being selected by other symbols */ + struct expr_value rev_dep; + + /* + * "Weak" reverse dependencies through being implied by other symbols + */ + struct expr_value implied; +}; + +#define SYMBOL_CONST 0x0001 /* symbol is const */ +#define SYMBOL_CHECK 0x0008 /* used during dependency checking */ +#define SYMBOL_VALID 0x0080 /* set when symbol.curr is calculated */ +#define SYMBOL_TRANS 0x0100 /* symbol is transitional only (not visible)*/ +#define SYMBOL_WRITE 0x0200 /* write symbol to file (KCONFIG_CONFIG) */ +#define SYMBOL_WRITTEN 0x0800 /* track info to avoid double-write to .config */ +#define SYMBOL_CHECKED 0x2000 /* used during dependency checking */ +#define SYMBOL_WARNED 0x8000 /* warning has been issued */ + +/* Set when symbol.def[] is used */ +#define SYMBOL_DEF 0x10000 /* First bit of SYMBOL_DEF */ +#define SYMBOL_DEF_USER 0x10000 /* symbol.def[S_DEF_USER] is valid */ +#define SYMBOL_DEF_AUTO 0x20000 /* symbol.def[S_DEF_AUTO] is valid */ +#define SYMBOL_DEF3 0x40000 /* symbol.def[S_DEF_3] is valid */ +#define SYMBOL_DEF4 0x80000 /* symbol.def[S_DEF_4] is valid */ + +#define SYMBOL_MAXLENGTH 256 + +/* A property represents the config options that can be associated + * with a config "symbol". + * Sample: + * config FOO + * default y + * prompt "foo prompt" + * select BAR + * config BAZ + * int "BAZ Value" + * range 1..255 + * + * Please, also check parser.y:print_symbol() when modifying the + * list of property types! + */ +enum prop_type { + P_UNKNOWN, + P_PROMPT, /* prompt "foo prompt" or "BAZ Value" */ + P_COMMENT, /* text associated with a comment */ + P_MENU, /* prompt associated with a menu or menuconfig symbol */ + P_DEFAULT, /* default y */ + P_SELECT, /* select BAR */ + P_IMPLY, /* imply BAR */ + P_RANGE, /* range 7..100 (for a symbol) */ +}; + +struct property { + struct property *next; /* next property - null if last */ + enum prop_type type; /* type of property */ + const char *text; /* the prompt value - P_PROMPT, P_MENU, P_COMMENT */ + 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_PROMPT, P_DEFAULT, P_MENU, P_COMMENT */ + const char *filename; /* what file was this property defined */ + int lineno; /* what lineno was this property defined */ +}; + +#define for_all_properties(sym, st, tok) \ + 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_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; + + /* The parent menu node, corresponding to e.g. a menu or choice */ + struct menu *parent; + + /* The first child menu node, for e.g. menus and choices */ + struct menu *list; + + /* + * The symbol associated with the menu node. Choices are implemented as + * a special kind of symbol. NULL for menus, comments, and ifs. + */ + struct symbol *sym; + + 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 + * type (P_PROMPT, P_MENU, etc.) + */ + struct property *prompt; + + /* + * 'visible if' dependencies. If more than one is given, they will be + * ANDed together. + */ + struct expr *visibility; + + /* + * Ordinary dependencies from e.g. 'depends on' and 'if', ANDed + * together + */ + struct expr *dep; + + /* MENU_* flags */ + unsigned int flags; + + /* Any help text associated with the node */ + char *help; + + /* The location where the menu node appears in the Kconfig files */ + const char *filename; + int lineno; + + /* For use by front ends that need to store auxiliary data */ + void *data; +}; + +/* + * Set on a menu node when the corresponding symbol changes state in some way. + * Can be checked by front ends. + */ +#define MENU_CHANGED 0x0001 + +#define MENU_ROOT 0x0002 + +struct jump_key { + struct list_head entries; + size_t offset; + struct menu *target; +}; + +extern struct symbol symbol_yes, symbol_no, symbol_mod; +extern struct symbol *modules_sym; +extern int cdebug; +struct expr *expr_alloc_symbol(struct symbol *sym); +struct expr *expr_alloc_one(enum expr_type type, struct expr *ce); +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2); +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); +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2); +bool expr_eq(struct expr *e1, struct expr *e2); +tristate expr_calc_value(struct expr *e); +struct expr *expr_eliminate_dups(struct expr *e); +struct expr *expr_transform(struct expr *e); +bool expr_contains_symbol(struct expr *dep, struct symbol *sym); +bool expr_contains_symbol_negated(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(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 bool expr_is_yes(const struct expr *e) +{ + return !e || (e->type == E_SYMBOL && e->left.sym == &symbol_yes); +} + +#ifdef __cplusplus +} +#endif + +#endif /* EXPR_H */ diff --git a/scripts/kconfig/gconf-cfg.sh b/scripts/kconfig/gconf-cfg.sh new file mode 100755 index 000000000..856c692f4 --- /dev/null +++ b/scripts/kconfig/gconf-cfg.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +set -eu + +cflags=$1 +libs=$2 + +PKG=gtk+-3.0 + +if [ -z "$(command -v ${HOSTPKG_CONFIG})" ]; then + echo >&2 "*" + echo >&2 "* 'make gconfig' requires '${HOSTPKG_CONFIG}'. Please install it." + echo >&2 "*" + exit 1 +fi + +if ! ${HOSTPKG_CONFIG} --exists $PKG; then + echo >&2 "*" + echo >&2 "* Unable to find the GTK+ installation. Please make sure that" + echo >&2 "* the GTK 3 development package is correctly installed." + echo >&2 "* You need $PKG" + echo >&2 "*" + exit 1 +fi + +${HOSTPKG_CONFIG} --cflags ${PKG} > ${cflags} +${HOSTPKG_CONFIG} --libs ${PKG} > ${libs} diff --git a/scripts/kconfig/gconf.c b/scripts/kconfig/gconf.c new file mode 100644 index 000000000..9f8586cb8 --- /dev/null +++ b/scripts/kconfig/gconf.c @@ -0,0 +1,1356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002-2003 Romain Lievin <roms@tilp.info> + */ + +#include <stdlib.h> +#include "lkc.h" + +#include <gtk/gtk.h> + +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <time.h> + +enum view_mode { + SINGLE_VIEW, SPLIT_VIEW, FULL_VIEW +}; + +enum { + OPT_NORMAL, OPT_ALL, OPT_PROMPT +}; + +static gint view_mode = FULL_VIEW; +static gboolean show_name = TRUE; +static gboolean show_range = TRUE; +static gboolean show_value = TRUE; +static int opt_mode = OPT_NORMAL; + +static GtkWidget *main_wnd; +static GtkWidget *tree1_w; // left frame +static GtkWidget *tree2_w; // right frame +static GtkWidget *text_w; +static GtkWidget *hpaned; +static GtkWidget *vpaned; +static GtkWidget *back_btn, *save_btn, *single_btn, *split_btn, *full_btn; +static GtkWidget *save_menu_item; + +static GtkTextTag *tag1, *tag2; + +static GtkTreeStore *tree1, *tree2; +static GdkPixbuf *pix_menu; + +static struct menu *browsed; // browsed menu for SINGLE/SPLIT view +static struct menu *selected; // selected entry + +enum { + COL_OPTION, COL_NAME, COL_NO, COL_MOD, COL_YES, COL_VALUE, + COL_MENU, COL_COLOR, COL_EDIT, COL_PIXBUF, + COL_PIXVIS, COL_BTNVIS, COL_BTNACT, COL_BTNINC, COL_BTNRAD, + COL_NUMBER +}; + +static void display_tree(GtkTreeStore *store, struct menu *menu); +static void recreate_tree(void); + +static void conf_changed(bool dirty) +{ + gtk_widget_set_sensitive(save_btn, dirty); + gtk_widget_set_sensitive(save_menu_item, dirty); +} + +/* Utility Functions */ + +static void text_insert_msg(const char *title, const char *msg) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_w), 15); + + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, title, -1, tag1, + NULL); + gtk_text_buffer_insert_at_cursor(buffer, "\n\n", 2); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, msg, -1, tag2, + NULL); +} + +static void text_insert_help(struct menu *menu) +{ + struct gstr help = str_new(); + + menu_get_ext_help(menu, &help); + text_insert_msg(menu_get_prompt(menu), str_get(&help)); + str_free(&help); +} + +static void _select_menu(GtkTreeView *view, GtkTreeModel *model, + GtkTreeIter *parent, struct menu *match) +{ + GtkTreeIter iter; + gboolean valid; + + valid = gtk_tree_model_iter_children(model, &iter, parent); + while (valid) { + struct menu *menu; + + gtk_tree_model_get(model, &iter, COL_MENU, &menu, -1); + + if (menu == match) { + GtkTreeSelection *selection; + GtkTreePath *path; + + /* + * Expand parents to reflect the selection, and + * scroll down to it. + */ + path = gtk_tree_model_get_path(model, &iter); + gtk_tree_view_expand_to_path(view, path); + gtk_tree_view_scroll_to_cell(view, path, NULL, TRUE, + 0.5, 0.0); + gtk_tree_path_free(path); + + selection = gtk_tree_view_get_selection(view); + gtk_tree_selection_select_iter(selection, &iter); + + text_insert_help(menu); + } + + _select_menu(view, model, &iter, match); + + valid = gtk_tree_model_iter_next(model, &iter); + } +} + +static void select_menu(GtkTreeView *view, struct menu *match) +{ + _select_menu(view, gtk_tree_view_get_model(view), NULL, match); +} + +static void _update_row_visibility(GtkTreeView *view) +{ + GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(view)); + + gtk_tree_model_filter_refilter(filter); +} + +static void update_row_visibility(void) +{ + if (view_mode == SPLIT_VIEW) + _update_row_visibility(GTK_TREE_VIEW(tree1_w)); + _update_row_visibility(GTK_TREE_VIEW(tree2_w)); +} + +static void set_node(GtkTreeStore *tree, GtkTreeIter *node, struct menu *menu) +{ + struct symbol *sym = menu->sym; + tristate val; + gchar *option; + const gchar *_no = ""; + const gchar *_mod = ""; + const gchar *_yes = ""; + const gchar *value = ""; + GdkRGBA color; + gboolean editable = FALSE; + gboolean btnvis = FALSE; + + option = g_strdup_printf("%s %s %s %s", + menu->type == M_COMMENT ? "***" : "", + menu_get_prompt(menu), + menu->type == M_COMMENT ? "***" : "", + sym && !sym_has_value(sym) ? "(NEW)" : ""); + + gdk_rgba_parse(&color, menu_is_visible(menu) ? "Black" : "DarkGray"); + + if (!sym) + goto set; + + sym_calc_value(sym); + + if (menu->type == M_CHOICE) { // parse children to get a final value + struct symbol *def_sym = sym_calc_choice(menu); + struct menu *def_menu = NULL; + + for (struct menu *child = menu->list; child; child = child->next) { + if (menu_is_visible(child) && child->sym == def_sym) + def_menu = child; + } + + if (def_menu) + value = menu_get_prompt(def_menu); + + goto set; + } + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + + btnvis = TRUE; + + val = sym_get_tristate_value(sym); + switch (val) { + case no: + _no = "N"; + value = "N"; + break; + case mod: + _mod = "M"; + value = "M"; + break; + case yes: + _yes = "Y"; + value = "Y"; + break; + } + + if (val != no && sym_tristate_within_range(sym, no)) + _no = "_"; + if (val != mod && sym_tristate_within_range(sym, mod)) + _mod = "_"; + if (val != yes && sym_tristate_within_range(sym, yes)) + _yes = "_"; + break; + default: + value = sym_get_string_value(sym); + editable = TRUE; + break; + } + +set: + gtk_tree_store_set(tree, node, + COL_OPTION, option, + COL_NAME, sym ? sym->name : "", + COL_NO, _no, + COL_MOD, _mod, + COL_YES, _yes, + COL_VALUE, value, + COL_MENU, (gpointer) menu, + COL_COLOR, &color, + COL_EDIT, editable, + COL_PIXBUF, pix_menu, + COL_PIXVIS, view_mode == SINGLE_VIEW && menu->type == M_MENU, + COL_BTNVIS, btnvis, + COL_BTNACT, _yes[0] == 'Y', + COL_BTNINC, _mod[0] == 'M', + COL_BTNRAD, sym && sym_is_choice_value(sym), + -1); + + g_free(option); +} + +static void _update_tree(GtkTreeStore *store, GtkTreeIter *parent) +{ + GtkTreeModel *model = GTK_TREE_MODEL(store); + GtkTreeIter iter; + gboolean valid; + + valid = gtk_tree_model_iter_children(model, &iter, parent); + while (valid) { + struct menu *menu; + + gtk_tree_model_get(model, &iter, COL_MENU, &menu, -1); + + if (menu) + set_node(store, &iter, menu); + + _update_tree(store, &iter); + + valid = gtk_tree_model_iter_next(model, &iter); + } +} + +static void update_tree(GtkTreeStore *store) +{ + _update_tree(store, NULL); + update_row_visibility(); +} + +static void update_trees(void) +{ + if (view_mode == SPLIT_VIEW) + update_tree(tree1); + update_tree(tree2); +} + +static void set_view_mode(enum view_mode mode) +{ + view_mode = mode; + + if (mode == SPLIT_VIEW) { // two panes + gint w; + + gtk_widget_show(tree1_w); + gtk_window_get_default_size(GTK_WINDOW(main_wnd), &w, NULL); + gtk_paned_set_position(GTK_PANED(hpaned), w / 2); + } else { + gtk_widget_hide(tree1_w); + gtk_paned_set_position(GTK_PANED(hpaned), 0); + } + + gtk_widget_set_sensitive(single_btn, TRUE); + gtk_widget_set_sensitive(split_btn, TRUE); + gtk_widget_set_sensitive(full_btn, TRUE); + + switch (mode) { + case SINGLE_VIEW: + if (selected) + browsed = menu_get_parent_menu(selected) ?: &rootmenu; + else + browsed = &rootmenu; + recreate_tree(); + text_insert_msg("", ""); + select_menu(GTK_TREE_VIEW(tree2_w), selected); + gtk_widget_set_sensitive(single_btn, FALSE); + break; + case SPLIT_VIEW: + browsed = selected; + while (browsed && !(browsed->flags & MENU_ROOT)) + browsed = browsed->parent; + gtk_tree_store_clear(tree1); + display_tree(tree1, &rootmenu); + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree1_w)); + gtk_tree_store_clear(tree2); + if (browsed) + display_tree(tree2, browsed); + text_insert_msg("", ""); + select_menu(GTK_TREE_VIEW(tree1_w), browsed); + select_menu(GTK_TREE_VIEW(tree2_w), selected); + gtk_widget_set_sensitive(split_btn, FALSE); + break; + case FULL_VIEW: + gtk_tree_store_clear(tree2); + display_tree(tree2, &rootmenu); + text_insert_msg("", ""); + select_menu(GTK_TREE_VIEW(tree2_w), selected); + gtk_widget_set_sensitive(full_btn, FALSE); + break; + } + + gtk_widget_set_sensitive(back_btn, + mode == SINGLE_VIEW && browsed != &rootmenu); +} + +/* Menu & Toolbar Callbacks */ + +static void on_load1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkWidget *dialog; + GtkFileChooser *chooser; + gint res; + + dialog = gtk_file_chooser_dialog_new("Load file...", + GTK_WINDOW(user_data), + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, + NULL); + + chooser = GTK_FILE_CHOOSER(dialog); + gtk_file_chooser_set_filename(chooser, conf_get_configname()); + + res = gtk_dialog_run(GTK_DIALOG(dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + + filename = gtk_file_chooser_get_filename(chooser); + + if (conf_read(filename)) + text_insert_msg("Error", + "Unable to load configuration!"); + else + update_trees(); + + g_free(filename); + } + + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + +static void on_save_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + if (conf_write(NULL)) + text_insert_msg("Error", "Unable to save configuration !"); + conf_write_autoconf(0); +} + +static void on_save_as1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkWidget *dialog; + GtkFileChooser *chooser; + gint res; + + dialog = gtk_file_chooser_dialog_new("Save file as...", + GTK_WINDOW(user_data), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL); + + chooser = GTK_FILE_CHOOSER(dialog); + gtk_file_chooser_set_filename(chooser, conf_get_configname()); + + res = gtk_dialog_run(GTK_DIALOG(dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + + filename = gtk_file_chooser_get_filename(chooser); + + if (conf_write(filename)) + text_insert_msg("Error", + "Unable to save configuration !"); + + g_free(filename); + } + + gtk_widget_destroy(dialog); +} + +static void on_show_name1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_name = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_NAME); + if (col) + gtk_tree_view_column_set_visible(col, show_name); +} + +static void on_show_range1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_range = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_NO); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_MOD); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_YES); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + +} + +static void on_show_data1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_value = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_VALUE); + if (col) + gtk_tree_view_column_set_visible(col, show_value); +} + +static void on_set_option_mode1_activate(GtkMenuItem *menuitem, + gpointer user_data) +{ + opt_mode = OPT_NORMAL; + update_row_visibility(); +} + +static void on_set_option_mode2_activate(GtkMenuItem *menuitem, + gpointer user_data) +{ + opt_mode = OPT_ALL; + update_row_visibility(); +} + +static void on_set_option_mode3_activate(GtkMenuItem *menuitem, + gpointer user_data) +{ + opt_mode = OPT_PROMPT; + update_row_visibility(); +} + +static void on_introduction1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *intro_text = + "Welcome to gconfig, the GTK+ graphical configuration tool.\n" + "For each option, a blank box indicates the feature is disabled, a\n" + "check indicates it is enabled, and a dot indicates that it is to\n" + "be compiled as a module. Clicking on the box will cycle through the three states.\n" + "\n" + "If you do not see an option (e.g., a device driver) that you\n" + "believe should be present, try turning on Show All Options\n" + "under the Options menu.\n" + "Although there is no cross reference yet to help you figure out\n" + "what other options must be enabled to support the option you\n" + "are interested in, you can still view the help of a grayed-out\n" + "option."; + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, "%s", intro_text); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +static void on_about1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *about_text = + "gconfig is copyright (c) 2002 Romain Lievin <roms@lpg.ticalc.org>.\n" + "Based on the source code from Roman Zippel.\n"; + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, "%s\nGTK version: %d.%d.%d", + about_text, + gtk_get_major_version(), + gtk_get_minor_version(), + gtk_get_micro_version()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +static void on_license1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *license_text = + "gconfig is released under the terms of the GNU GPL v2.\n" + "For more information, please see the source code or\n" + "visit http://www.fsf.org/licenses/licenses.html\n"; + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, "%s", license_text); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +/* toolbar handlers */ +static void on_back_clicked(GtkButton *button, gpointer user_data) +{ + browsed = menu_get_parent_menu(browsed) ?: &rootmenu; + + recreate_tree(); + + if (browsed == &rootmenu) + gtk_widget_set_sensitive(back_btn, FALSE); +} + +static void on_load_clicked(GtkButton *button, gpointer user_data) +{ + on_load1_activate(NULL, user_data); +} + +static void on_save_clicked(GtkButton *button, gpointer user_data) +{ + on_save_activate(NULL, user_data); +} + +static void on_single_clicked(GtkButton *button, gpointer user_data) +{ + set_view_mode(SINGLE_VIEW); +} + +static void on_split_clicked(GtkButton *button, gpointer user_data) +{ + set_view_mode(SPLIT_VIEW); +} + +static void on_full_clicked(GtkButton *button, gpointer user_data) +{ + set_view_mode(FULL_VIEW); +} + +static void on_collapse_clicked(GtkButton *button, gpointer user_data) +{ + gtk_tree_view_collapse_all(GTK_TREE_VIEW(tree2_w)); +} + +static void on_expand_clicked(GtkButton *button, gpointer user_data) +{ + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree2_w)); +} + +/* Main Windows Callbacks */ + +static void on_window1_destroy(GtkWidget *widget, gpointer user_data) +{ + gtk_main_quit(); +} + +static gboolean on_window1_configure(GtkWidget *self, + GdkEventConfigure *event, + gpointer user_data) +{ + gtk_paned_set_position(GTK_PANED(vpaned), 2 * event->height / 3); + return FALSE; +} + +static gboolean on_window1_delete_event(GtkWidget *widget, GdkEvent *event, + gpointer user_data) +{ + GtkWidget *dialog, *label, *content_area; + gint result; + gint ret = FALSE; + + if (!conf_get_changed()) + return FALSE; + + dialog = gtk_dialog_new_with_buttons("Warning !", + GTK_WINDOW(main_wnd), + (GtkDialogFlags) + (GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT), + "_OK", + GTK_RESPONSE_YES, + "_No", + GTK_RESPONSE_NO, + "_Cancel", + GTK_RESPONSE_CANCEL, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), + GTK_RESPONSE_CANCEL); + + label = gtk_label_new("\nSave configuration ?\n"); + content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_add(GTK_CONTAINER(content_area), label); + gtk_widget_show(label); + + result = gtk_dialog_run(GTK_DIALOG(dialog)); + switch (result) { + case GTK_RESPONSE_YES: + on_save_activate(NULL, NULL); + break; + case GTK_RESPONSE_NO: + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + default: + ret = TRUE; + break; + } + + gtk_widget_destroy(dialog); + + if (!ret) + g_object_unref(pix_menu); + + return ret; +} + +static void on_quit1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + if (!on_window1_delete_event(NULL, NULL, NULL)) + gtk_widget_destroy(GTK_WIDGET(main_wnd)); +} + +/* CTree Callbacks */ + +/* Change hex/int/string value in the cell */ +static void renderer_edited(GtkCellRendererText * cell, + const gchar * path_string, + const gchar * new_text, gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(user_data); + GtkTreeModel *model = gtk_tree_view_get_model(view); + GtkTreePath *path = gtk_tree_path_new_from_string(path_string); + GtkTreeIter iter; + const char *old_def, *new_def; + struct menu *menu; + struct symbol *sym; + + if (!gtk_tree_model_get_iter(model, &iter, path)) + goto free; + + gtk_tree_model_get(model, &iter, COL_MENU, &menu, -1); + sym = menu->sym; + + gtk_tree_model_get(model, &iter, COL_VALUE, &old_def, -1); + new_def = new_text; + + sym_set_string_value(sym, new_def); + + update_trees(); + +free: + gtk_tree_path_free(path); +} + +/* Change the value of a symbol and update the tree */ +static void change_sym_value(struct menu *menu, gint col) +{ + struct symbol *sym = menu->sym; + tristate newval; + + if (!sym) + return; + + if (col == COL_NO) + newval = no; + else if (col == COL_MOD) + newval = mod; + else if (col == COL_YES) + newval = yes; + else + return; + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + if (!sym_tristate_within_range(sym, newval)) + newval = yes; + sym_set_tristate_value(sym, newval); + update_trees(); + break; + case S_INT: + case S_HEX: + case S_STRING: + default: + break; + } +} + +static void toggle_sym_value(struct menu *menu) +{ + if (!menu->sym) + return; + + sym_toggle_tristate_value(menu->sym); + update_trees(); +} + +static gint column2index(GtkTreeViewColumn * column) +{ + gint i; + + for (i = 0; i < COL_NUMBER; i++) { + GtkTreeViewColumn *col; + + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), i); + if (col == column) + return i; + } + + return -1; +} + + +/* User click: update choice (full) or goes down (single) */ +static gboolean on_treeview2_button_press_event(GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreeModel *model = gtk_tree_view_get_model(view); + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeIter iter; + struct menu *menu; + gint col; + gint tx = (gint) event->x; + gint ty = (gint) event->y; + + gtk_tree_view_get_path_at_pos(view, tx, ty, &path, &column, NULL, NULL); + if (path == NULL) + return FALSE; + + if (!gtk_tree_model_get_iter(model, &iter, path)) + return FALSE; + gtk_tree_model_get(model, &iter, COL_MENU, &menu, -1); + + selected = menu; + + col = column2index(column); + if (event->type == GDK_2BUTTON_PRESS) { + enum prop_type ptype; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + + if (ptype == P_MENU && view_mode == SINGLE_VIEW && col == COL_OPTION) { + // goes down into menu + browsed = menu; + recreate_tree(); + gtk_widget_set_sensitive(back_btn, TRUE); + } else if (col == COL_OPTION) { + toggle_sym_value(menu); + gtk_tree_view_expand_row(view, path, TRUE); + } + } else { + if (col == COL_VALUE) { + toggle_sym_value(menu); + gtk_tree_view_expand_row(view, path, TRUE); + } else if (col == COL_NO || col == COL_MOD + || col == COL_YES) { + change_sym_value(menu, col); + gtk_tree_view_expand_row(view, path, TRUE); + } + } + + return FALSE; +} + +/* Key pressed: update choice */ +static gboolean on_treeview2_key_press_event(GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreeModel *model = gtk_tree_view_get_model(view); + GtkTreePath *path; + GtkTreeIter iter; + struct menu *menu; + gint col; + + gtk_tree_view_get_cursor(view, &path, NULL); + if (path == NULL) + return FALSE; + + if (event->keyval == GDK_KEY_space) { + if (gtk_tree_view_row_expanded(view, path)) + gtk_tree_view_collapse_row(view, path); + else + gtk_tree_view_expand_row(view, path, FALSE); + return TRUE; + } + + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, COL_MENU, &menu, -1); + + if (!strcasecmp(event->string, "n")) + col = COL_NO; + else if (!strcasecmp(event->string, "m")) + col = COL_MOD; + else if (!strcasecmp(event->string, "y")) + col = COL_YES; + else + col = -1; + change_sym_value(menu, col); + + return FALSE; +} + + +/* Row selection changed: update help */ +static void on_treeview2_cursor_changed(GtkTreeView *treeview, + gpointer user_data) +{ + GtkTreeModel *model = gtk_tree_view_get_model(treeview); + GtkTreeSelection *selection; + GtkTreeIter iter; + struct menu *menu; + + selection = gtk_tree_view_get_selection(treeview); + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + gtk_tree_model_get(model, &iter, COL_MENU, &menu, -1); + text_insert_help(menu); + } +} + + +/* User click: display sub-tree in the right frame. */ +static gboolean on_treeview1_button_press_event(GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreeModel *model = gtk_tree_view_get_model(view); + GtkTreePath *path; + GtkTreeIter iter; + struct menu *menu; + gint tx = (gint) event->x; + gint ty = (gint) event->y; + + gtk_tree_view_get_path_at_pos(view, tx, ty, &path, NULL, NULL, NULL); + if (path == NULL) + return FALSE; + + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, COL_MENU, &menu, -1); + + if (event->type == GDK_2BUTTON_PRESS) + toggle_sym_value(menu); + + selected = menu; + + if (menu->type == M_MENU) { + browsed = menu; + recreate_tree(); + } + + gtk_tree_view_set_cursor(view, path, NULL, FALSE); + gtk_widget_grab_focus(tree2_w); + + return FALSE; +} + +/* Display the whole tree (single/split/full view) */ +static void _display_tree(GtkTreeStore *tree, struct menu *menu, + GtkTreeIter *parent) +{ + struct menu *child; + GtkTreeIter iter; + + for (child = menu->list; child; child = child->next) { + /* + * REVISIT: + * menu_finalize() creates empty "if" entries. + * Do not confuse gtk_tree_model_get(), which would otherwise + * return "if" menu entry. + */ + if (child->type == M_IF) + continue; + + if ((view_mode == SPLIT_VIEW) + && !(child->flags & MENU_ROOT) && (tree == tree1)) + continue; + + if ((view_mode == SPLIT_VIEW) && (child->flags & MENU_ROOT) + && (tree == tree2)) + continue; + + gtk_tree_store_append(tree, &iter, parent); + set_node(tree, &iter, child); + + if (view_mode != SINGLE_VIEW || child->type != M_MENU) + _display_tree(tree, child, &iter); + } +} + +static void display_tree(GtkTreeStore *store, struct menu *menu) +{ + _display_tree(store, menu, NULL); +} + +/* Recreate the tree store starting at 'browsed' node */ +static void recreate_tree(void) +{ + gtk_tree_store_clear(tree2); + display_tree(tree2, browsed); + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree2_w)); +} + +static void fixup_rootmenu(struct menu *menu) +{ + struct menu *child; + static int menu_cnt = 0; + + menu->flags |= MENU_ROOT; + for (child = menu->list; child; child = child->next) { + if (child->prompt && child->prompt->type == P_MENU) { + menu_cnt++; + fixup_rootmenu(child); + menu_cnt--; + } else if (!menu_cnt) + fixup_rootmenu(child); + } +} + +/* Main Window Initialization */ +static void replace_button_icon(GtkWidget *widget, const char *filename) +{ + GdkPixbuf *pixbuf; + GtkWidget *image; + GError *err = NULL; + + char *env = getenv(SRCTREE); + gchar *path = g_strconcat(env ? env : g_get_current_dir(), "/scripts/kconfig/icons/", filename, NULL); + + pixbuf = gdk_pixbuf_new_from_file(path, &err); + g_free(path); + + if (err) { + g_warning("Failed to load icon %s: %s", filename, err->message); + g_error_free(err); + return; + } + + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + + gtk_widget_show(image); + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(widget), image); +} + +static void init_main_window(const gchar *glade_file) +{ + GtkBuilder *builder; + GtkWidget *widget; + GtkTextBuffer *txtbuf; + + builder = gtk_builder_new_from_file(glade_file); + if (!builder) + g_error("GUI loading failed !\n"); + + main_wnd = GTK_WIDGET(gtk_builder_get_object(builder, "window1")); + g_signal_connect(main_wnd, "destroy", + G_CALLBACK(on_window1_destroy), NULL); + g_signal_connect(main_wnd, "configure-event", + G_CALLBACK(on_window1_configure), NULL); + g_signal_connect(main_wnd, "delete-event", + G_CALLBACK(on_window1_delete_event), NULL); + + hpaned = GTK_WIDGET(gtk_builder_get_object(builder, "hpaned1")); + vpaned = GTK_WIDGET(gtk_builder_get_object(builder, "vpaned1")); + tree1_w = GTK_WIDGET(gtk_builder_get_object(builder, "treeview1")); + g_signal_connect(tree1_w, "cursor-changed", + G_CALLBACK(on_treeview2_cursor_changed), NULL); + g_signal_connect(tree1_w, "button-press-event", + G_CALLBACK(on_treeview1_button_press_event), NULL); + g_signal_connect(tree1_w, "key-press-event", + G_CALLBACK(on_treeview2_key_press_event), NULL); + + tree2_w = GTK_WIDGET(gtk_builder_get_object(builder, "treeview2")); + g_signal_connect(tree2_w, "cursor-changed", + G_CALLBACK(on_treeview2_cursor_changed), NULL); + g_signal_connect(tree2_w, "button-press-event", + G_CALLBACK(on_treeview2_button_press_event), NULL); + g_signal_connect(tree2_w, "key-press-event", + G_CALLBACK(on_treeview2_key_press_event), NULL); + + text_w = GTK_WIDGET(gtk_builder_get_object(builder, "textview3")); + + /* menubar */ + widget = GTK_WIDGET(gtk_builder_get_object(builder, "load1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_load1_activate), NULL); + + save_menu_item = GTK_WIDGET(gtk_builder_get_object(builder, "save1")); + g_signal_connect(save_menu_item, "activate", + G_CALLBACK(on_save_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "save_as1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_save_as1_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "quit1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_quit1_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "show_name1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_show_name1_activate), NULL); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_name); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "show_range1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_show_range1_activate), NULL); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_range); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "show_data1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_show_data1_activate), NULL); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_value); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "set_option_mode1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_set_option_mode1_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "set_option_mode2")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_set_option_mode2_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "set_option_mode3")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_set_option_mode3_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "introduction1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_introduction1_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "about1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_about1_activate), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "license1")); + g_signal_connect(widget, "activate", + G_CALLBACK(on_license1_activate), NULL); + + /* toolbar */ + back_btn = GTK_WIDGET(gtk_builder_get_object(builder, "button1")); + g_signal_connect(back_btn, "clicked", + G_CALLBACK(on_back_clicked), NULL); + gtk_widget_set_sensitive(back_btn, FALSE); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "button2")); + g_signal_connect(widget, "clicked", + G_CALLBACK(on_load_clicked), NULL); + + save_btn = GTK_WIDGET(gtk_builder_get_object(builder, "button3")); + g_signal_connect(save_btn, "clicked", + G_CALLBACK(on_save_clicked), NULL); + + single_btn = GTK_WIDGET(gtk_builder_get_object(builder, "button4")); + g_signal_connect(single_btn, "clicked", + G_CALLBACK(on_single_clicked), NULL); + replace_button_icon(single_btn, "single_view.xpm"); + + split_btn = GTK_WIDGET(gtk_builder_get_object(builder, "button5")); + g_signal_connect(split_btn, "clicked", + G_CALLBACK(on_split_clicked), NULL); + replace_button_icon(split_btn, "split_view.xpm"); + + full_btn = GTK_WIDGET(gtk_builder_get_object(builder, "button6")); + g_signal_connect(full_btn, "clicked", + G_CALLBACK(on_full_clicked), NULL); + replace_button_icon(full_btn, "tree_view.xpm"); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "button7")); + g_signal_connect(widget, "clicked", + G_CALLBACK(on_collapse_clicked), NULL); + + widget = GTK_WIDGET(gtk_builder_get_object(builder, "button8")); + g_signal_connect(widget, "clicked", + G_CALLBACK(on_expand_clicked), NULL); + + txtbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + tag1 = gtk_text_buffer_create_tag(txtbuf, "mytag1", + "foreground", "red", + "weight", PANGO_WEIGHT_BOLD, + NULL); + tag2 = gtk_text_buffer_create_tag(txtbuf, "mytag2", + /*"style", PANGO_STYLE_OBLIQUE, */ + NULL); + + gtk_window_set_title(GTK_WINDOW(main_wnd), rootmenu.prompt->text); + + gtk_widget_show_all(main_wnd); + + g_object_unref(builder); + + conf_set_changed_callback(conf_changed); +} + +static gboolean visible_func(GtkTreeModel *model, GtkTreeIter *iter, + gpointer data) +{ + struct menu *menu; + + gtk_tree_model_get(model, iter, COL_MENU, &menu, -1); + + if (!menu) + return FALSE; + + return menu_is_visible(menu) || opt_mode == OPT_ALL || + (opt_mode == OPT_PROMPT && menu_has_prompt(menu)); +} + +static void init_left_tree(void) +{ + GtkTreeView *view = GTK_TREE_VIEW(tree1_w); + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + GtkTreeViewColumn *column; + GtkTreeModel *filter; + + tree1 = gtk_tree_store_new(COL_NUMBER, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_RGBA, + G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + + filter = gtk_tree_model_filter_new(GTK_TREE_MODEL(tree1), NULL); + + gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter), + visible_func, NULL, NULL); + gtk_tree_view_set_model(view, filter); + + column = gtk_tree_view_column_new(); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_title(column, "Options"); + + renderer = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "active", COL_BTNACT, + "inconsistent", COL_BTNINC, + "visible", COL_BTNVIS, + "radio", COL_BTNRAD, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "text", COL_OPTION, + "foreground-rgba", + COL_COLOR, NULL); + + sel = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); +} + +static void init_right_tree(void) +{ + GtkTreeView *view = GTK_TREE_VIEW(tree2_w); + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + GtkTreeViewColumn *column; + GtkTreeModel *filter; + gint i; + + tree2 = gtk_tree_store_new(COL_NUMBER, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_RGBA, + G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + + filter = gtk_tree_model_filter_new(GTK_TREE_MODEL(tree2), NULL); + + gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter), + visible_func, NULL, NULL); + gtk_tree_view_set_model(view, filter); + + column = gtk_tree_view_column_new(); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_title(column, "Options"); + + renderer = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "pixbuf", COL_PIXBUF, + "visible", COL_PIXVIS, NULL); + renderer = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "active", COL_BTNACT, + "inconsistent", COL_BTNINC, + "visible", COL_BTNVIS, + "radio", COL_BTNRAD, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "text", COL_OPTION, + "foreground-rgba", + COL_COLOR, NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "Name", renderer, + "text", COL_NAME, + "foreground-rgba", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "N", renderer, + "text", COL_NO, + "foreground-rgba", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "M", renderer, + "text", COL_MOD, + "foreground-rgba", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "Y", renderer, + "text", COL_YES, + "foreground-rgba", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "Value", renderer, + "text", COL_VALUE, + "editable", + COL_EDIT, + "foreground-rgba", + COL_COLOR, NULL); + g_signal_connect(G_OBJECT(renderer), "edited", + G_CALLBACK(renderer_edited), tree2_w); + + char *env = getenv(SRCTREE); + gchar *path = g_strconcat(env ? env : g_get_current_dir(), "/scripts/kconfig/icons/menu.xpm", NULL); + GError *err = NULL; + + pix_menu = gdk_pixbuf_new_from_file(path, &err); + g_free(path); + + if (err) { + g_warning("Failed to load menu icon: %s", err->message); + g_error_free(err); + } + + for (i = 0; i < COL_VALUE; i++) { + column = gtk_tree_view_get_column(view, i); + gtk_tree_view_column_set_resizable(column, TRUE); + } + + sel = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); +} + +/* Main */ +int main(int ac, char *av[]) +{ + const char *name; + char *env; + gchar *glade_file; + + /* GTK stuffs */ + gtk_init(&ac, &av); + + /* Determine GUI path */ + env = getenv(SRCTREE); + if (env) + glade_file = g_strconcat(env, "/scripts/kconfig/gconf.ui", NULL); + else if (av[0][0] == '/') + glade_file = g_strconcat(av[0], ".ui", NULL); + else + glade_file = g_strconcat(g_get_current_dir(), "/", av[0], ".ui", NULL); + + /* Conf stuffs */ + if (ac > 1 && av[1][0] == '-') { + switch (av[1][1]) { + case 'a': + //showAll = 1; + break; + case 's': + conf_set_message_callback(NULL); + break; + case 'h': + case '?': + printf("%s [-s] <config>\n", av[0]); + exit(0); + } + name = av[2]; + } else + name = av[1]; + + conf_parse(name); + fixup_rootmenu(&rootmenu); + + /* Load the interface and connect signals */ + init_main_window(glade_file); + init_left_tree(); + init_right_tree(); + + conf_read(NULL); + + set_view_mode(view_mode); + + gtk_main(); + + return 0; +} diff --git a/scripts/kconfig/gconf.ui b/scripts/kconfig/gconf.ui new file mode 100644 index 000000000..ab4431255 --- /dev/null +++ b/scripts/kconfig/gconf.ui @@ -0,0 +1,536 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> + +<interface> + +<object class="GtkWindow" id="window1"> + <property name="visible">True</property> + <property name="title" translatable="yes">Gtk Kernel Configurator</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="default_width">640</property> + <property name="default_height">480</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + + <child> + <object class="GtkBox" id="vbox1"> + <property name="orientation">vertical</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <object class="GtkMenuBar" id="menubar1"> + <property name="visible">True</property> + + <child> + <object class="GtkMenuItem" id="file1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_File</property> + <property name="use_underline">True</property> + + <child type="submenu"> + <object class="GtkMenu" id="file1_menu"> + + <child> + <object class="GtkMenuItem" id="load1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Load a config file</property> + <property name="label" translatable="yes">_Load</property> + <property name="use_underline">True</property> + <accelerator key="L" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </object> + </child> + + <child> + <object class="GtkMenuItem" id="save1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Save the config in .config</property> + <property name="label" translatable="yes">_Save</property> + <property name="use_underline">True</property> + <accelerator key="S" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </object> + </child> + + <child> + <object class="GtkMenuItem" id="save_as1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Save the config in a file</property> + <property name="label" translatable="yes">Save _as</property> + <property name="use_underline">True</property> + </object> + </child> + + <child> + <object class="GtkSeparatorMenuItem" id="separator1"> + <property name="visible">True</property> + </object> + </child> + + <child> + <object class="GtkMenuItem" id="quit1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Quit</property> + <property name="use_underline">True</property> + <accelerator key="Q" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkMenuItem" id="options1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Options</property> + <property name="use_underline">True</property> + + <child type="submenu"> + <object class="GtkMenu" id="options1_menu"> + + <child> + <object class="GtkCheckMenuItem" id="show_name1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Show name</property> + <property name="label" translatable="yes">Show _name</property> + <property name="use_underline">True</property> + <property name="active">False</property> + </object> + </child> + + <child> + <object class="GtkCheckMenuItem" id="show_range1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Show range (Y/M/N)</property> + <property name="label" translatable="yes">Show _range</property> + <property name="use_underline">True</property> + <property name="active">False</property> + </object> + </child> + + <child> + <object class="GtkCheckMenuItem" id="show_data1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Show value of the option</property> + <property name="label" translatable="yes">Show _data</property> + <property name="use_underline">True</property> + <property name="active">False</property> + </object> + </child> + + <child> + <object class="GtkSeparatorMenuItem" id="separator2"> + <property name="visible">True</property> + </object> + </child> + + <child> + <object class="GtkRadioMenuItem" id="set_option_mode1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Show normal options</property> + <property name="label" translatable="yes">Show normal options</property> + <property name="use_underline">True</property> + <property name="active">True</property> + </object> + </child> + + <child> + <object class="GtkRadioMenuItem" id="set_option_mode2"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Show all options</property> + <property name="label" translatable="yes">Show all _options</property> + <property name="use_underline">True</property> + <property name="active">False</property> + <property name="group">set_option_mode1</property> + </object> + </child> + + <child> + <object class="GtkRadioMenuItem" id="set_option_mode3"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Show all options with prompts</property> + <property name="label" translatable="yes">Show all prompt options</property> + <property name="use_underline">True</property> + <property name="active">False</property> + <property name="group">set_option_mode1</property> + </object> + </child> + + </object> + </child> + </object> + </child> + + <child> + <object class="GtkMenuItem" id="help1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Help</property> + <property name="use_underline">True</property> + + <child type="submenu"> + <object class="GtkMenu" id="help1_menu"> + + <child> + <object class="GtkMenuItem" id="introduction1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Introduction</property> + <property name="use_underline">True</property> + <accelerator key="I" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </object> + </child> + + <child> + <object class="GtkMenuItem" id="about1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_About</property> + <property name="use_underline">True</property> + <accelerator key="A" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </object> + </child> + + <child> + <object class="GtkMenuItem" id="license1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_License</property> + <property name="use_underline">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <object class="GtkToolbar" id="toolbar1"> + <property name="visible">True</property> + <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> + <property name="toolbar_style">GTK_TOOLBAR_BOTH</property> + <property name="show_arrow">True</property> + + <child> + <object class="GtkToolButton" id="button1"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Goes up one level (single view)</property> + <property name="label" translatable="yes">Back</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-undo</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <object class="GtkToolItem" id="toolitem1"> + <property name="visible">True</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + + <child> + <object class="GtkVSeparator" id="vseparator1"> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + + <child> + <object class="GtkToolButton" id="button2"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Load a config file</property> + <property name="label" translatable="yes">Load</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-open</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <object class="GtkToolButton" id="button3"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Save a config file</property> + <property name="label" translatable="yes">Save</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-save</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <object class="GtkToolItem" id="toolitem2"> + <property name="visible">True</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + + <child> + <object class="GtkVSeparator" id="vseparator2"> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + + <child> + <object class="GtkToolButton" id="button4"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Single view</property> + <property name="label" translatable="yes">Single</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-missing-image</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <object class="GtkToolButton" id="button5"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Split view</property> + <property name="label" translatable="yes">Split</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-missing-image</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <object class="GtkToolButton" id="button6"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Full view</property> + <property name="label" translatable="yes">Full</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-missing-image</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <object class="GtkToolItem" id="toolitem3"> + <property name="visible">True</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + + <child> + <object class="GtkVSeparator" id="vseparator3"> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + + <child> + <object class="GtkToolButton" id="button7"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Collapse the whole tree in the right frame</property> + <property name="label" translatable="yes">Collapse</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-remove</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <object class="GtkToolButton" id="button8"> + <property name="visible">True</property> + <property name="tooltip-text" translatable="yes">Expand the whole tree in the right frame</property> + <property name="label" translatable="yes">Expand</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-add</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <object class="GtkPaned" id="hpaned1"> + <property name="width_request">1</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + + <child> + <object class="GtkTreeView" id="treeview1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">True</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">False</property> + </object> + </child> + </object> + <packing> + <property name="shrink">True</property> + <property name="resize">False</property> + </packing> + </child> + + <child> + <object class="GtkPaned" id="vpaned1"> + <property name="orientation">vertical</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + + <child> + <object class="GtkScrolledWindow" id="scrolledwindow2"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + + <child> + <object class="GtkTreeView" id="treeview2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="headers_visible">True</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">False</property> + </object> + </child> + </object> + <packing> + <property name="shrink">True</property> + <property name="resize">False</property> + </packing> + </child> + + <child> + <object class="GtkScrolledWindow" id="scrolledwindow3"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_NEVER</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + + <child> + <object class="GtkTextView" id="textview3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="overwrite">False</property> + <property name="accepts_tab">True</property> + <property name="justification">GTK_JUSTIFY_LEFT</property> + <property name="wrap_mode">GTK_WRAP_WORD</property> + <property name="cursor_visible">True</property> + <property name="pixels_above_lines">0</property> + <property name="pixels_below_lines">0</property> + <property name="pixels_inside_wrap">0</property> + <property name="left_margin">0</property> + <property name="right_margin">0</property> + <property name="indent">0</property> + </object> + </child> + </object> + <packing> + <property name="shrink">True</property> + <property name="resize">True</property> + </packing> + </child> + </object> + <packing> + <property name="shrink">True</property> + <property name="resize">True</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> +</object> + +</interface> diff --git a/scripts/kconfig/icons/back.xpm b/scripts/kconfig/icons/back.xpm new file mode 100644 index 000000000..2a4c30127 --- /dev/null +++ b/scripts/kconfig/icons/back.xpm @@ -0,0 +1,29 @@ +/* XPM */ +static char * back_xpm[] = { +"22 22 3 1", +". c None", +"# c #000083", +"a c #838183", +"......................", +"......................", +"......................", +"......................", +"......................", +"...........######a....", +"..#......##########...", +"..##...####......##a..", +"..###.###.........##..", +"..######..........##..", +"..#####...........##..", +"..######..........##..", +"..#######.........##..", +"..########.......##a..", +"...............a###...", +"...............###....", +"......................", +"......................", +"......................", +"......................", +"......................", +"......................" +}; diff --git a/scripts/kconfig/icons/choice_no.xpm b/scripts/kconfig/icons/choice_no.xpm new file mode 100644 index 000000000..306e314ed --- /dev/null +++ b/scripts/kconfig/icons/choice_no.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * choice_no_xpm[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .... ", +" .. .. ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .. .. ", +" .... ", +" " +}; diff --git a/scripts/kconfig/icons/choice_yes.xpm b/scripts/kconfig/icons/choice_yes.xpm new file mode 100644 index 000000000..edeb91067 --- /dev/null +++ b/scripts/kconfig/icons/choice_yes.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * choice_yes_xpm[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .... ", +" .. .. ", +" . . ", +" . .. . ", +" . .... . ", +" . .... . ", +" . .. . ", +" . . ", +" .. .. ", +" .... ", +" " +}; diff --git a/scripts/kconfig/icons/load.xpm b/scripts/kconfig/icons/load.xpm new file mode 100644 index 000000000..8c2d8725d --- /dev/null +++ b/scripts/kconfig/icons/load.xpm @@ -0,0 +1,31 @@ +/* XPM */ +static char * load_xpm[] = { +"22 22 5 1", +". c None", +"# c #000000", +"c c #838100", +"a c #ffff00", +"b c #ffffff", +"......................", +"......................", +"......................", +"............####....#.", +"...........#....##.##.", +"..................###.", +".................####.", +".####...........#####.", +"#abab##########.......", +"#babababababab#.......", +"#ababababababa#.......", +"#babababababab#.......", +"#ababab###############", +"#babab##cccccccccccc##", +"#abab##cccccccccccc##.", +"#bab##cccccccccccc##..", +"#ab##cccccccccccc##...", +"#b##cccccccccccc##....", +"###cccccccccccc##.....", +"##cccccccccccc##......", +"###############.......", +"......................" +}; diff --git a/scripts/kconfig/icons/menu.xpm b/scripts/kconfig/icons/menu.xpm new file mode 100644 index 000000000..8ae1b74b3 --- /dev/null +++ b/scripts/kconfig/icons/menu.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * menu_xpm[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . .. . ", +" . .... . ", +" . ...... . ", +" . ...... . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" " +}; diff --git a/scripts/kconfig/icons/menuback.xpm b/scripts/kconfig/icons/menuback.xpm new file mode 100644 index 000000000..f988c2c32 --- /dev/null +++ b/scripts/kconfig/icons/menuback.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * menuback_xpm[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . .. . ", +" . .... . ", +" . ...... . ", +" . ...... . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" " +}; diff --git a/scripts/kconfig/icons/save.xpm b/scripts/kconfig/icons/save.xpm new file mode 100644 index 000000000..f8be53d83 --- /dev/null +++ b/scripts/kconfig/icons/save.xpm @@ -0,0 +1,31 @@ +/* XPM */ +static char * save_xpm[] = { +"22 22 5 1", +". c None", +"# c #000000", +"a c #838100", +"b c #c5c2c5", +"c c #cdb6d5", +"......................", +".####################.", +".#aa#bbbbbbbbbbbb#bb#.", +".#aa#bbbbbbbbbbbb#bb#.", +".#aa#bbbbbbbbbcbb####.", +".#aa#bbbccbbbbbbb#aa#.", +".#aa#bbbccbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aaa############aaa#.", +".#aaaaaaaaaaaaaaaaaa#.", +".#aaaaaaaaaaaaaaaaaa#.", +".#aaa#############aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +"..##################..", +"......................" +}; diff --git a/scripts/kconfig/icons/single_view.xpm b/scripts/kconfig/icons/single_view.xpm new file mode 100644 index 000000000..33c3b239d --- /dev/null +++ b/scripts/kconfig/icons/single_view.xpm @@ -0,0 +1,28 @@ +/* XPM */ +static char * single_view_xpm[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"......................", +"......................" +}; diff --git a/scripts/kconfig/icons/split_view.xpm b/scripts/kconfig/icons/split_view.xpm new file mode 100644 index 000000000..09e22246d --- /dev/null +++ b/scripts/kconfig/icons/split_view.xpm @@ -0,0 +1,28 @@ +/* XPM */ +static char * split_view_xpm[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......................", +"......................" +}; diff --git a/scripts/kconfig/icons/symbol_mod.xpm b/scripts/kconfig/icons/symbol_mod.xpm new file mode 100644 index 000000000..769465fcb --- /dev/null +++ b/scripts/kconfig/icons/symbol_mod.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * symbol_mod_xpm[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . .. . ", +" . .... . ", +" . .... . ", +" . .. . ", +" . . ", +" . . ", +" .......... ", +" " +}; diff --git a/scripts/kconfig/icons/symbol_no.xpm b/scripts/kconfig/icons/symbol_no.xpm new file mode 100644 index 000000000..e4e9d46c9 --- /dev/null +++ b/scripts/kconfig/icons/symbol_no.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * symbol_no_xpm[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .......... ", +" " +}; diff --git a/scripts/kconfig/icons/symbol_yes.xpm b/scripts/kconfig/icons/symbol_yes.xpm new file mode 100644 index 000000000..dab7e10ae --- /dev/null +++ b/scripts/kconfig/icons/symbol_yes.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * symbol_yes_xpm[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . . . ", +" . .. . ", +" . . .. . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" " +}; diff --git a/scripts/kconfig/icons/tree_view.xpm b/scripts/kconfig/icons/tree_view.xpm new file mode 100644 index 000000000..290835b80 --- /dev/null +++ b/scripts/kconfig/icons/tree_view.xpm @@ -0,0 +1,28 @@ +/* XPM */ +static char * tree_view_xpm[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......................", +"......................" +}; diff --git a/scripts/kconfig/internal.h b/scripts/kconfig/internal.h new file mode 100644 index 000000000..d0ffce2df --- /dev/null +++ b/scripts/kconfig/internal.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef INTERNAL_H +#define INTERNAL_H + +#include <hashtable.h> + +#define SYMBOL_HASHSIZE (1U << 14) + +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; + +extern const char *cur_filename; +extern int cur_lineno; + +#endif /* INTERNAL_H */ diff --git a/scripts/kconfig/kconfig-sym-check.pl b/scripts/kconfig/kconfig-sym-check.pl new file mode 100755 index 000000000..daa5285fd --- /dev/null +++ b/scripts/kconfig/kconfig-sym-check.pl @@ -0,0 +1,132 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 + +use warnings; +use strict; + +my $srctree = shift @ARGV; +unless (defined $srctree) { + $srctree = `git rev-parse --show-toplevel 2>/dev/null`; + chomp $srctree; + my $msg = "Usage: $0 <srctree> [excludes file]\n"; + $msg .= "Please provide <srctree>."; + $msg .= " Is it '$srctree'?" if $srctree; + $msg .= "\n"; + die $msg; +} +my $kconfig_sym_check_excludes = defined $ARGV[0] ? $ARGV[0] : undef; + +sub indent_depth { + my ($ws) = @_; + my $col = 0; + for my $c (split //, $ws) { + $col = $c eq "\t" ? int($col / 8) * 8 + 8 : $col + 1; + } + return $col; +} + +my @files = `git -C \Q$srctree\E ls-files '*Kconfig*' 2>/dev/null`; +if (@files) { + chomp @files; + @files = map { "$srctree/$_" } @files; +} else { + @files = `find \Q$srctree\E -name '*Kconfig*'`; + chomp @files; +} + +@files = grep { !m{/scripts/kconfig/tests/} } @files; + +my %configs = (); +my %refs = (); + +foreach my $file (@files) { + open F, $file or die "Cannot open $file: $!"; + + my $help = 0; + my $help_level; + my $level; + + while (<F>) { + chomp; + + while (/\\\s*$/) { + s/\\\s*$/ /; + my $cont = <F> // last; + chomp $cont; + $_ .= $cont; + } + + next if /^\s*$/; + next if /^\s*#/; + + /^(\s*)/; + $level = indent_depth($1); + + if ($help && $level < $help_level) { + $help = 0; + } + + next if ($help); + + if (/^\s*(help|\-\-\-help\-\-\-)$/) { + $help = 1; + my $next; + while (defined($next = <F>)) { + last unless $next =~ /^\s*(?:#.*)?$/; + } + last unless defined $next; + $next =~ /^(\s*)/; + if (indent_depth($1) >= $level) { + $help_level = indent_depth($1); + } else { + $help = 0; + } + $_ = $next; + redo; + } + + if (/^\s*(config|menuconfig)\s+([a-zA-Z0-9_]+)\s*(#.*)?$/) { + $configs{$2}++; + next; + } + + if (/^\s*(default|def_bool|def_tristate|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) { + my $s = $2; + $s =~ s/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'//g; + $s =~ s/#.*//; + $s =~ s/\$\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\)//g; + $s =~ s/%%[^%]*%%//g; + my @syms = split /[^a-zA-Z0-9_]+/, $s; + map { + $refs{$_}++ if (/[a-zA-Z]/ && $_ ne "if" && $_ ne "y" && $_ ne "n" && $_ ne "m" && !/^0[xX][0-9a-fA-F]+$/); + } @syms + } + } + + close F; +} + +my %known_syms = (); +if (defined $kconfig_sym_check_excludes) { + my $file = $kconfig_sym_check_excludes; + open(F, "<", $file) or die "Cannot open $file: $!"; + while (<F>) { + chomp; + next if /^\s*$/; + next if /^\s*#/; + $known_syms{$1}++ if (/^\s*([a-zA-Z0-9_]+)\s*(#.*)?$/); + } +} + +my $ret = 0; +foreach my $k (sort keys %refs) { + next if (exists $configs{$k} || exists $known_syms{$k}); + + print "$k"; + print " - warning: '$k' is probably not what you want; Kconfig tristate literals are always lowercase ('n', 'y', 'm')" if ($k eq "N" || $k eq "Y" || $k eq "M"); + print "\n"; + + $ret = 1; +} + +exit $ret; diff --git a/scripts/kconfig/lexer.l b/scripts/kconfig/lexer.l new file mode 100644 index 000000000..a6155422b --- /dev/null +++ b/scripts/kconfig/lexer.l @@ -0,0 +1,461 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ +%option nostdinit noyywrap never-interactive full ecs +%option 8bit nodefault yylineno +%x ASSIGN_VAL HELP STRING +%{ + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <xalloc.h> +#include "lkc.h" +#include "preprocess.h" + +#include "parser.tab.h" + +#define YY_DECL static int yylex1(void) + +#define START_STRSIZE 16 + +/* The Kconfig file currently being parsed. */ +const char *cur_filename; + +/* + * The line number of the current statement. This does not match yylineno. + * yylineno is used by the lexer, while cur_lineno is used by the parser. + */ +int cur_lineno; + +static int prev_prev_token = T_EOL; +static int prev_token = T_EOL; +static char *text; +static int text_size, text_asize; + +struct buffer { + struct buffer *parent; + YY_BUFFER_STATE state; + int yylineno; + const char *filename; + int source_lineno; +}; + +static struct buffer *current_buf; + +static int last_ts, first_ts; + +static char *expand_token(const char *in, size_t n); +static void append_expanded_string(const char *in); +static void zconf_endhelp(void); +static void zconf_endfile(void); + +static void new_string(void) +{ + text = xmalloc(START_STRSIZE); + text_asize = START_STRSIZE; + text_size = 0; + *text = 0; +} + +static void append_string(const char *str, int size) +{ + int new_size = text_size + size + 1; + if (new_size > text_asize) { + new_size += START_STRSIZE - 1; + new_size &= -START_STRSIZE; + text = xrealloc(text, new_size); + text_asize = new_size; + } + memcpy(text + text_size, str, size); + text_size += size; + text[text_size] = 0; +} + +static void alloc_string(const char *str, int size) +{ + text = xmalloc(size + 1); + memcpy(text, str, size); + text[size] = 0; +} + +static void warn_ignored_character(char chr) +{ + fprintf(stderr, + "%s:%d:warning: ignoring unsupported character '%c'\n", + cur_filename, yylineno, chr); +} +%} + +n [A-Za-z0-9_-] + +%% + char open_quote = 0; + +#.* /* ignore comment */ +[ \t]* /* whitespaces */ +\\\n /* escaped new line */ +\n return T_EOL; +"bool" return T_BOOL; +"choice" return T_CHOICE; +"comment" return T_COMMENT; +"config" return T_CONFIG; +"def_bool" return T_DEF_BOOL; +"def_tristate" return T_DEF_TRISTATE; +"default" return T_DEFAULT; +"depends" return T_DEPENDS; +"endchoice" return T_ENDCHOICE; +"endif" return T_ENDIF; +"endmenu" return T_ENDMENU; +"help" return T_HELP; +"hex" return T_HEX; +"if" return T_IF; +"imply" return T_IMPLY; +"int" return T_INT; +"mainmenu" return T_MAINMENU; +"menu" return T_MENU; +"menuconfig" return T_MENUCONFIG; +"modules" return T_MODULES; +"on" return T_ON; +"prompt" return T_PROMPT; +"range" return T_RANGE; +"select" return T_SELECT; +"source" return T_SOURCE; +"string" return T_STRING; +"transitional" return T_TRANSITIONAL; +"tristate" return T_TRISTATE; +"visible" return T_VISIBLE; +"||" return T_OR; +"&&" return T_AND; +"=" return T_EQUAL; +"!=" return T_UNEQUAL; +"<" return T_LESS; +"<=" return T_LESS_EQUAL; +">" return T_GREATER; +">=" return T_GREATER_EQUAL; +"!" return T_NOT; +"(" return T_OPEN_PAREN; +")" return T_CLOSE_PAREN; +":=" return T_COLON_EQUAL; +"+=" return T_PLUS_EQUAL; +\"|\' { + open_quote = yytext[0]; + new_string(); + BEGIN(STRING); + } +{n}+ { + alloc_string(yytext, yyleng); + yylval.string = text; + return T_WORD; + } +({n}|$)+ { + /* this token includes at least one '$' */ + yylval.string = expand_token(yytext, yyleng); + if (strlen(yylval.string)) + return T_WORD; + free(yylval.string); + } +. warn_ignored_character(*yytext); + +<ASSIGN_VAL>{ + [^[:blank:]\n]+.* { + alloc_string(yytext, yyleng); + yylval.string = text; + return T_ASSIGN_VAL; + } + \n { BEGIN(INITIAL); return T_EOL; } + . +} + +<STRING>{ + "$".* append_expanded_string(yytext); + [^$'"\\\n]+ { + append_string(yytext, yyleng); + } + \\.? { + append_string(yytext + 1, yyleng - 1); + } + \'|\" { + if (open_quote == yytext[0]) { + BEGIN(INITIAL); + yylval.string = text; + return T_WORD_QUOTE; + } else + append_string(yytext, 1); + } + \n { + fprintf(stderr, + "%s:%d:warning: multi-line strings not supported\n", + cur_filename, cur_lineno); + unput('\n'); + BEGIN(INITIAL); + yylval.string = text; + return T_WORD_QUOTE; + } + <<EOF>> { + BEGIN(INITIAL); + yylval.string = text; + return T_WORD_QUOTE; + } +} + +<HELP>{ + [ \t]+ { + int ts, i; + + ts = 0; + for (i = 0; i < yyleng; i++) { + if (yytext[i] == '\t') + ts = (ts & ~7) + 8; + else + ts++; + } + last_ts = ts; + if (first_ts) { + if (ts < first_ts) { + zconf_endhelp(); + return T_HELPTEXT; + } + ts -= first_ts; + while (ts > 8) { + append_string(" ", 8); + ts -= 8; + } + append_string(" ", ts); + } + } + [ \t]*\n/[^ \t\n] { + zconf_endhelp(); + return T_HELPTEXT; + } + [ \t]*\n { + append_string("\n", 1); + } + [^ \t\n].* { + while (yyleng) { + if ((yytext[yyleng-1] != ' ') && (yytext[yyleng-1] != '\t')) + break; + yyleng--; + } + append_string(yytext, yyleng); + if (!first_ts) + first_ts = last_ts; + } + <<EOF>> { + zconf_endhelp(); + return T_HELPTEXT; + } +} + +<<EOF>> { + BEGIN(INITIAL); + + if (prev_token != T_EOL && prev_token != T_HELPTEXT) + fprintf(stderr, "%s:%d:warning: no new line at end of file\n", + cur_filename, yylineno); + + if (current_buf) { + zconf_endfile(); + return T_EOL; + } + fclose(yyin); + yyterminate(); +} + +%% + +/* second stage lexer */ +int yylex(void) +{ + int token; + +repeat: + token = yylex1(); + + if (prev_token == T_EOL || prev_token == T_HELPTEXT) { + if (token == T_EOL) + /* Do not pass unneeded T_EOL to the parser. */ + goto repeat; + else + /* + * For the parser, update lineno at the first token + * of each statement. Generally, \n is a statement + * terminator in Kconfig, but it is not always true + * because \n could be escaped by a backslash. + */ + cur_lineno = yylineno; + } + + if (prev_prev_token == T_EOL && prev_token == T_WORD && + (token == T_EQUAL || token == T_COLON_EQUAL || token == T_PLUS_EQUAL)) + BEGIN(ASSIGN_VAL); + + prev_prev_token = prev_token; + prev_token = token; + + return token; +} + +static char *expand_token(const char *in, size_t n) +{ + char *out; + int c; + char c2; + const char *rest, *end; + + new_string(); + append_string(in, n); + + /* + * get the whole line because we do not know the end of token. + * input() returns 0 (not EOF!) when it reachs the end of file. + */ + while ((c = input()) != 0) { + if (c == '\n') { + unput(c); + break; + } + c2 = c; + append_string(&c2, 1); + } + + rest = text; + out = expand_one_token(&rest); + + /* push back unused characters to the input stream */ + end = rest + strlen(rest); + while (end > rest) + unput(*--end); + + free(text); + + return out; +} + +static void append_expanded_string(const char *str) +{ + const char *end; + char *res; + + str++; + + res = expand_dollar(&str); + + /* push back unused characters to the input stream */ + end = str + strlen(str); + while (end > str) + unput(*--end); + + append_string(res, strlen(res)); + + free(res); +} + +void zconf_starthelp(void) +{ + new_string(); + last_ts = first_ts = 0; + BEGIN(HELP); +} + +static void zconf_endhelp(void) +{ + yylval.string = text; + BEGIN(INITIAL); +} + + +/* + * Try to open specified file with following names: + * ./name + * $(srctree)/name + * The latter is used when srctree is separate from objtree + * when compiling the kernel. + * Return NULL if file is not found. + */ +FILE *zconf_fopen(const char *name) +{ + char *env, fullname[PATH_MAX+1]; + FILE *f; + + f = fopen(name, "r"); + if (!f && name != NULL && name[0] != '/') { + env = getenv(SRCTREE); + if (env) { + snprintf(fullname, sizeof(fullname), + "%s/%s", env, name); + f = fopen(fullname, "r"); + } + } + return f; +} + +void zconf_initscan(const char *name) +{ + yyin = zconf_fopen(name); + if (!yyin) { + fprintf(stderr, "can't find file %s\n", name); + exit(1); + } + + cur_filename = file_lookup(name, NULL, 0); + yylineno = 1; +} + +void zconf_nextfile(const char *name) +{ + struct buffer *buf = xmalloc(sizeof(*buf)); + bool recur_include = false; + + buf->state = YY_CURRENT_BUFFER; + buf->yylineno = yylineno; + buf->filename = cur_filename; + buf->source_lineno = cur_lineno; + buf->parent = current_buf; + current_buf = buf; + yyin = zconf_fopen(name); + if (!yyin) { + fprintf(stderr, "%s:%d: can't open file \"%s\"\n", + cur_filename, cur_lineno, name); + exit(1); + } + yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); + + for (buf = current_buf; buf; buf = buf->parent) { + if (!strcmp(buf->filename, name)) + recur_include = true; + } + + if (recur_include) { + fprintf(stderr, + "Recursive inclusion detected.\n" + "Inclusion path:\n" + " current file : %s\n", name); + + for (buf = current_buf; buf; buf = buf->parent) + fprintf(stderr, " included from: %s:%d\n", + buf->filename, buf->source_lineno); + exit(1); + } + + yylineno = 1; + cur_filename = file_lookup(name, cur_filename, cur_lineno); +} + +static void zconf_endfile(void) +{ + struct buffer *tmp; + + fclose(yyin); + yy_delete_buffer(YY_CURRENT_BUFFER); + yy_switch_to_buffer(current_buf->state); + yylineno = current_buf->yylineno; + cur_filename = current_buf->filename; + tmp = current_buf; + current_buf = current_buf->parent; + free(tmp); +} diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h new file mode 100644 index 000000000..7e6f6ca29 --- /dev/null +++ b/scripts/kconfig/lkc.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#ifndef LKC_H +#define LKC_H + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#include "expr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lkc_proto.h" + +#define SRCTREE "srctree" + +#ifndef CONFIG_ +#define CONFIG_ "CONFIG_" +#endif +static inline const char *CONFIG_prefix(void) +{ + return getenv( "CONFIG_" ) ?: CONFIG_; +} +#undef CONFIG_ +#define CONFIG_ CONFIG_prefix() + +extern int yylineno; +void zconfdump(FILE *out); +void zconf_starthelp(void); +FILE *zconf_fopen(const char *name); +void zconf_initscan(const char *name); +void zconf_nextfile(const char *name); + +/* confdata.c */ +extern struct gstr autoconf_cmd; +const char *conf_get_configname(void); + +/* confdata.c and expr.c */ +static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out) +{ + assert(len != 0); + + if (fwrite(str, len, count, out) != count) + fprintf(stderr, "Error in writing or end of file.\n"); +} + +/* util.c */ +const char *file_lookup(const char *name, + const char *parent_name, int parent_lineno); + +/* lexer.l */ +int yylex(void); + +struct gstr { + size_t len; + char *s; + /* + * when max_width is not zero long lines in string s (if any) get + * wrapped not to exceed the max_width value + */ + int max_width; +}; +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(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(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, enum menu_type type); +void menu_add_dep(struct expr *dep, struct expr *cond); +void menu_add_visibility(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(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(const struct menu *menu); +const char *menu_get_prompt(const struct menu *menu); +struct menu *menu_get_parent_menu(struct menu *menu); +struct menu *menu_get_menu_or_parent_menu(struct menu *menu); +int get_jump_key_char(void); +struct gstr get_relations_str(struct symbol **sym_arr, struct list_head *head); +void menu_get_ext_help(struct menu *menu, struct gstr *help); +void menu_dump(void); + +/* symbol.c */ +void sym_clear_all_valid(void); +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(const struct property *prop); + +static inline tristate sym_get_tristate_value(const struct symbol *sym) +{ + return sym->curr.tri; +} + +static inline bool sym_is_choice(const struct symbol *sym) +{ + /* A choice is a symbol with no name */ + return sym->name == NULL; +} + +bool sym_is_choice_value(const struct symbol *sym); + +static inline bool sym_has_value(const struct symbol *sym) +{ + return sym->flags & SYMBOL_DEF_USER ? true : false; +} + +#ifdef __cplusplus +} +#endif + +#endif /* LKC_H */ diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h new file mode 100644 index 000000000..8914b4e8f --- /dev/null +++ b/scripts/kconfig/lkc_proto.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef LKC_PROTO_H +#define LKC_PROTO_H + +#include <stdarg.h> + +/* confdata.c */ +void conf_parse(const char *name); +int conf_read(const char *name); +int conf_read_simple(const char *name, int); +int conf_write_defconfig(const char *name); +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)(bool)); +void conf_set_message_callback(void (*fn)(const char *s)); +bool conf_errors(void); + +/* symbol.c */ +struct symbol * sym_lookup(const char *name, int flags); +struct symbol * sym_find(const char *name); +void print_symbol_for_listconfig(struct symbol *sym); +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(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(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(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/BIG.FAT.WARNING b/scripts/kconfig/lxdialog/BIG.FAT.WARNING new file mode 100644 index 000000000..7cb5a7ec9 --- /dev/null +++ b/scripts/kconfig/lxdialog/BIG.FAT.WARNING @@ -0,0 +1,4 @@ +This is NOT the official version of dialog. This version has been +significantly modified from the original. It is for use by the Linux +kernel configuration script. Please do not bother Savio Lam with +questions about this program. diff --git a/scripts/kconfig/lxdialog/checklist.c b/scripts/kconfig/lxdialog/checklist.c new file mode 100644 index 000000000..75493302f --- /dev/null +++ b/scripts/kconfig/lxdialog/checklist.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * checklist.c -- implements the checklist box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension + * Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +static int list_width, check_x, item_x; + +/* + * Print list item + */ +static void print_item(WINDOW * win, int choice, int selected) +{ + int i; + char *list_item = malloc(list_width + 1); + + strncpy(list_item, item_str(), list_width - item_x); + list_item[list_width - item_x] = '\0'; + + /* Clear 'residue' of last item */ + wattrset(win, dlg.menubox.atr); + wmove(win, choice, 0); + for (i = 0; i < list_width; i++) + waddch(win, ' '); + + wmove(win, choice, check_x); + wattrset(win, selected ? dlg.check_selected.atr + : dlg.check.atr); + if (!item_is_tag(':')) + wprintw(win, "(%c)", item_is_tag('X') ? 'X' : ' '); + + wattrset(win, selected ? dlg.tag_selected.atr : dlg.tag.atr); + mvwaddch(win, choice, item_x, list_item[0]); + wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr); + waddstr(win, list_item + 1); + if (selected) { + wmove(win, choice, check_x + 1); + wrefresh(win); + } + free(list_item); +} + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int choice, int item_no, int scroll, + int y, int x, int height) +{ + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, dlg.uarrow.atr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, dlg.menubox.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + + if ((height < item_no) && (scroll + choice < item_no - 1)) { + wattrset(win, dlg.darrow.atr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, dlg.menubox_border.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } +} + +/* + * Display the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, "Select", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with a list of options that can be turned on or off + * in the style of radiolist (only one option turned on at a time). + */ +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height) +{ + int i, x, y, box_x, box_y; + int key = 0, button = 0, choice = 0, scroll = 0, max_choice; + WINDOW *dialog, *list; + + /* which item to highlight */ + item_foreach() { + if (item_is_tag('X')) + choice = item_n(); + if (item_is_selected()) { + choice = item_n(); + break; + } + } + +do_resize: + if (getmaxy(stdscr) < (height + CHECKLIST_HEIGHT_MIN)) + return -ERRDISPLAYTOOSMALL; + if (getmaxx(stdscr) < (width + CHECKLIST_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + max_choice = MIN(list_height, item_count()); + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + list_width = width - 6; + box_y = height - list_height - 5; + box_x = (width - list_width) / 2 - 1; + + /* create new window for the list */ + list = subwin(dialog, list_height, list_width, y + box_y + 1, + x + box_x + 1); + + keypad(list, TRUE); + + /* draw a box around the list items */ + draw_box(dialog, box_y, box_x, list_height + 2, list_width + 2, + dlg.menubox_border.atr, dlg.menubox.atr); + + /* Find length of longest item in order to center checklist */ + check_x = 0; + item_foreach() + check_x = MAX(check_x, strlen(item_str()) + 4); + check_x = MIN(check_x, list_width); + + check_x = (list_width - check_x) / 2; + item_x = check_x + 4; + + if (choice >= list_height) { + scroll = choice - list_height + 1; + choice -= scroll; + } + + /* Print the list */ + for (i = 0; i < max_choice; i++) { + item_set(scroll + i); + print_item(list, i, i == choice); + } + + print_arrows(dialog, choice, item_count(), scroll, + box_y, box_x + check_x + 5, list_height); + + print_buttons(dialog, height, width, 0); + + wmove(list, choice, check_x + 1); + wrefresh(list); + + while (key != KEY_ESC) { + key = wgetch(dialog); + + for (i = 0; i < max_choice; i++) { + item_set(i + scroll); + if (toupper(key) == toupper(item_str()[0])) + break; + } + + if (i < max_choice || key == KEY_UP || key == KEY_DOWN || + key == '+' || key == '-') { + if (key == KEY_UP || key == '-') { + if (!choice) { + if (!scroll) + continue; + /* Scroll list down */ + if (list_height > 1) { + /* De-highlight current first item */ + item_set(scroll); + print_item(list, 0, FALSE); + scrollok(list, TRUE); + wscrl(list, -1); + scrollok(list, FALSE); + } + scroll--; + item_set(scroll); + print_item(list, 0, TRUE); + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice - 1; + } else if (key == KEY_DOWN || key == '+') { + if (choice == max_choice - 1) { + if (scroll + choice >= item_count() - 1) + continue; + /* Scroll list up */ + if (list_height > 1) { + /* De-highlight current last item before scrolling up */ + item_set(scroll + max_choice - 1); + print_item(list, + max_choice - 1, + FALSE); + scrollok(list, TRUE); + wscrl(list, 1); + scrollok(list, FALSE); + } + scroll++; + item_set(scroll + max_choice - 1); + print_item(list, max_choice - 1, TRUE); + + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice + 1; + } + if (i != choice) { + /* De-highlight current item */ + item_set(scroll + choice); + print_item(list, choice, FALSE); + /* Highlight new item */ + choice = i; + item_set(scroll + choice); + print_item(list, choice, TRUE); + wnoutrefresh(dialog); + wrefresh(list); + } + continue; /* wait for another key press */ + } + switch (key) { + case 'H': + case 'h': + case '?': + button = 1; + /* fall-through */ + case 'S': + case 's': + case ' ': + case '\n': + item_foreach() + item_set_selected(0); + item_set(scroll + choice); + item_set_selected(1); + delwin(list); + delwin(dialog); + return button; + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case 'X': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(list); + delwin(dialog); + on_key_resize(); + goto do_resize; + } + + /* Now, update everything... */ + doupdate(); + } + delwin(list); + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/dialog.h b/scripts/kconfig/lxdialog/dialog.h new file mode 100644 index 000000000..f6c2ebe6d --- /dev/null +++ b/scripts/kconfig/lxdialog/dialog.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * dialog.h -- common declarations for all dialog modules + * + * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + */ + +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#ifdef __sun__ +#define CURS_MACROS +#endif +#include <ncurses.h> + +#define TR(params) _tracef params + +#define KEY_ESC 27 +#define TAB 9 +#define MAX_LEN 2048 +#define BUF_SIZE (10*1024) +#define MIN(x,y) (x < y ? x : y) +#define MAX(x,y) (x > y ? x : y) + +#ifndef ACS_ULCORNER +#define ACS_ULCORNER '+' +#endif +#ifndef ACS_LLCORNER +#define ACS_LLCORNER '+' +#endif +#ifndef ACS_URCORNER +#define ACS_URCORNER '+' +#endif +#ifndef ACS_LRCORNER +#define ACS_LRCORNER '+' +#endif +#ifndef ACS_HLINE +#define ACS_HLINE '-' +#endif +#ifndef ACS_VLINE +#define ACS_VLINE '|' +#endif +#ifndef ACS_LTEE +#define ACS_LTEE '+' +#endif +#ifndef ACS_RTEE +#define ACS_RTEE '+' +#endif +#ifndef ACS_UARROW +#define ACS_UARROW '^' +#endif +#ifndef ACS_DARROW +#define ACS_DARROW 'v' +#endif + +/* error return codes */ +#define ERRDISPLAYTOOSMALL (KEY_MAX + 1) + +/* + * Color definitions + */ +struct dialog_color { + chtype atr; /* Color attribute */ + int fg; /* foreground */ + int bg; /* background */ + int hl; /* highlight this item */ +}; + +struct subtitle_list { + struct subtitle_list *next; + const char *text; +}; + +struct dialog_info { + const char *backtitle; + struct subtitle_list *subtitles; + struct dialog_color screen; + struct dialog_color shadow; + struct dialog_color dialog; + struct dialog_color title; + struct dialog_color border; + struct dialog_color button_active; + struct dialog_color button_inactive; + struct dialog_color button_key_active; + struct dialog_color button_key_inactive; + struct dialog_color button_label_active; + struct dialog_color button_label_inactive; + struct dialog_color inputbox; + struct dialog_color position_indicator; + struct dialog_color menubox; + struct dialog_color menubox_border; + struct dialog_color item; + struct dialog_color item_selected; + struct dialog_color tag; + struct dialog_color tag_selected; + struct dialog_color tag_key; + struct dialog_color tag_key_selected; + struct dialog_color check; + struct dialog_color check_selected; + struct dialog_color uarrow; + struct dialog_color darrow; +}; + +/* + * Global variables + */ +extern struct dialog_info dlg; +extern char dialog_input_result[]; +extern int saved_x, saved_y; /* Needed in signal handler in mconf.c */ + +/* + * Function prototypes + */ + +/* item list as used by checklist and menubox */ +void item_reset(void); +void item_make(const char *fmt, ...); +void item_add_str(const char *fmt, ...); +void item_set_tag(char tag); +void item_set_data(void *p); +void item_set_selected(int val); +int item_activate_selected(void); +void *item_data(void); +char item_tag(void); + +/* item list manipulation for lxdialog use */ +#define MAXITEMSTR 200 +struct dialog_item { + char str[MAXITEMSTR]; /* prompt displayed */ + char tag; + void *data; /* pointer to menu item - used by menubox+checklist */ + int selected; /* Set to 1 by dialog_*() function if selected. */ +}; + +/* list of lialog_items */ +struct dialog_list { + struct dialog_item node; + struct dialog_list *next; +}; + +extern struct dialog_list *item_cur; +extern struct dialog_list item_nil; +extern struct dialog_list *item_head; + +int item_count(void); +void item_set(int n); +int item_n(void); +const char *item_str(void); +int item_is_selected(void); +int item_is_tag(char tag); +#define item_foreach() \ + for (item_cur = item_head ? item_head: item_cur; \ + item_cur && (item_cur != &item_nil); item_cur = item_cur->next) + +/* generic key handlers */ +int on_key_esc(WINDOW *win); +int on_key_resize(void); + +/* minimum (re)size values */ +#define CHECKLIST_HEIGHT_MIN 6 /* For dialog_checklist() */ +#define CHECKLIST_WIDTH_MIN 6 +#define INPUTBOX_HEIGHT_MIN 2 /* For dialog_inputbox() */ +#define INPUTBOX_WIDTH_MIN 2 +#define MENUBOX_HEIGHT_MIN 15 /* For dialog_menu() */ +#define MENUBOX_WIDTH_MIN 65 +#define TEXTBOX_HEIGHT_MIN 8 /* For dialog_textbox() */ +#define TEXTBOX_WIDTH_MIN 8 +#define YESNO_HEIGHT_MIN 4 /* For dialog_yesno() */ +#define YESNO_WIDTH_MIN 4 +#define WINDOW_HEIGHT_MIN 19 /* For init_dialog() */ +#define WINDOW_WIDTH_MIN 80 + +int init_dialog(const char *backtitle); +void set_dialog_backtitle(const char *backtitle); +void set_dialog_subtitles(struct subtitle_list *subtitles); +void end_dialog(int x, int y); +void attr_clear(WINDOW * win, int height, int width, chtype attr); +void dialog_clear(void); +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x); +void print_button(WINDOW * win, const char *label, int y, int x, int selected); +void print_title(WINDOW *dialog, const char *title, int width); +void draw_box(WINDOW * win, int y, int x, int height, int width, chtype box, + chtype border); +void draw_shadow(WINDOW * win, int y, int x, int height, int width); + +int first_alpha(const char *string, const char *exempt); +int dialog_yesno(const char *title, const char *prompt, int height, int width); +int dialog_msgbox(const char *title, const char *prompt, int height, + int width, int pause); +int dialog_textbox(const char *title, const char *tbuf, int initial_height, + int initial_width, int *_vscroll, int *_hscroll, + int (*extra_key_cb)(int, size_t, size_t, void *), void *data); +int dialog_menu(const char *title, const char *prompt, + const void *selected, int *s_scroll); +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height); +int dialog_inputbox(const char *title, const char *prompt, int height, + int width, const char *init); diff --git a/scripts/kconfig/lxdialog/inputbox.c b/scripts/kconfig/lxdialog/inputbox.c new file mode 100644 index 000000000..5e4a13172 --- /dev/null +++ b/scripts/kconfig/lxdialog/inputbox.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * inputbox.c -- implements the input box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +char dialog_input_result[MAX_LEN + 1]; + +/* + * Print the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, " Ok ", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box for inputing a string + */ +int dialog_inputbox(const char *title, const char *prompt, int height, int width, + const char *init) +{ + int i, x, y, box_y, box_x, box_width; + int input_x = 0, key = 0, button = -1; + int show_x, len, pos; + char *instr = dialog_input_result; + WINDOW *dialog; + + if (!init) + instr[0] = '\0'; + else { + strncpy(instr, init, sizeof(dialog_input_result) - 1); + instr[sizeof(dialog_input_result) - 1] = '\0'; + } + +do_resize: + if (getmaxy(stdscr) <= (height - INPUTBOX_HEIGHT_MIN)) + return -ERRDISPLAYTOOSMALL; + if (getmaxx(stdscr) <= (width - INPUTBOX_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + /* Draw the input field box */ + box_width = width - 6; + getyx(dialog, y, x); + box_y = y + 2; + box_x = (width - box_width) / 2; + draw_box(dialog, y + 1, box_x - 1, 3, box_width + 2, + dlg.dialog.atr, dlg.border.atr); + + print_buttons(dialog, height, width, 0); + + /* Set up the initial value */ + wmove(dialog, box_y, box_x); + wattrset(dialog, dlg.inputbox.atr); + + len = strlen(instr); + pos = len; + + if (len >= box_width) { + show_x = len - box_width + 1; + input_x = box_width - 1; + for (i = 0; i < box_width - 1; i++) + waddch(dialog, instr[show_x + i]); + } else { + show_x = 0; + input_x = len; + waddstr(dialog, instr); + } + + wmove(dialog, box_y, box_x + input_x); + + wrefresh(dialog); + + while (key != KEY_ESC) { + key = wgetch(dialog); + + if (button == -1) { /* Input box selected */ + switch (key) { + case TAB: + case KEY_UP: + case KEY_DOWN: + break; + case KEY_BACKSPACE: + case 8: /* ^H */ + case 127: /* ^? */ + if (pos) { + wattrset(dialog, dlg.inputbox.atr); + if (input_x == 0) { + show_x--; + } else + input_x--; + + if (pos < len) { + for (i = pos - 1; i < len; i++) { + instr[i] = instr[i+1]; + } + } + + pos--; + len--; + instr[len] = '\0'; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } + continue; + case KEY_LEFT: + if (pos > 0) { + if (input_x > 0) { + wmove(dialog, box_y, --input_x + box_x); + } else if (input_x == 0) { + show_x--; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, box_x); + } + pos--; + } + continue; + case KEY_RIGHT: + if (pos < len) { + if (input_x < box_width - 1) { + wmove(dialog, box_y, ++input_x + box_x); + } else if (input_x == box_width - 1) { + show_x++; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + } + pos++; + } + continue; + default: + if (key < 0x100 && isprint(key)) { + if (len < MAX_LEN) { + wattrset(dialog, dlg.inputbox.atr); + if (pos < len) { + for (i = len; i > pos; i--) + instr[i] = instr[i-1]; + instr[pos] = key; + } else { + instr[len] = key; + } + pos++; + len++; + instr[len] = '\0'; + + if (input_x == box_width - 1) { + show_x++; + } else { + input_x++; + } + + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } else + flash(); /* Alarm user about overflow */ + continue; + } + } + } + switch (key) { + case 'O': + case 'o': + delwin(dialog); + return 0; + case 'H': + case 'h': + delwin(dialog); + return 1; + case KEY_UP: + case KEY_LEFT: + switch (button) { + case -1: + button = 1; /* Indicates "Help" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 0: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + case 1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + } + break; + case TAB: + case KEY_DOWN: + case KEY_RIGHT: + switch (button) { + case -1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + case 0: + button = 1; /* Indicates "Help" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 1: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + } + break; + case ' ': + case '\n': + delwin(dialog); + return (button == -1 ? 0 : button); + case 'X': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(dialog); + on_key_resize(); + goto do_resize; + } + } + + delwin(dialog); + return KEY_ESC; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/menubox.c b/scripts/kconfig/lxdialog/menubox.c new file mode 100644 index 000000000..d4c19b7be --- /dev/null +++ b/scripts/kconfig/lxdialog/menubox.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * menubox.c -- implements the menu box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcapw@cfw.com) + */ + +/* + * Changes by Clifford Wolf (god@clifford.at) + * + * [ 1998-06-13 ] + * + * *) A bugfix for the Page-Down problem + * + * *) Formerly when I used Page Down and Page Up, the cursor would be set + * to the first position in the menu box. Now lxdialog is a bit + * smarter and works more like other menu systems (just have a look at + * it). + * + * *) Formerly if I selected something my scrolling would be broken because + * lxdialog is re-invoked by the Menuconfig shell script, can't + * remember the last scrolling position, and just sets it so that the + * cursor is at the bottom of the box. Now it writes the temporary file + * lxdialog.scrltmp which contains this information. The file is + * deleted by lxdialog if the user leaves a submenu or enters a new + * one, but it would be nice if Menuconfig could make another "rm -f" + * just to be sure. Just try it out - you will recognise a difference! + * + * [ 1998-06-14 ] + * + * *) Now lxdialog is crash-safe against broken "lxdialog.scrltmp" files + * and menus change their size on the fly. + * + * *) If for some reason the last scrolling position is not saved by + * lxdialog, it sets the scrolling so that the selected item is in the + * middle of the menu box, not at the bottom. + * + * 02 January 1999, Michael Elizabeth Chastain (mec@shout.net) + * Reset 'scroll' to 0 if the value from lxdialog.scrltmp is bogus. + * This fixes a bug in Menuconfig where using ' ' to descend into menus + * would leave mis-synchronized lxdialog.scrltmp files lying around, + * fscanf would read in 'scroll', and eventually that value would get used. + */ + +#include "dialog.h" + +static int menu_width, item_x; + +/* + * Print menu item + */ +static void do_print_item(WINDOW * win, const char *item, int line_y, + int selected, int hotkey) +{ + int j; + char *menu_item = malloc(menu_width + 1); + + strncpy(menu_item, item, menu_width - item_x); + menu_item[menu_width - item_x] = '\0'; + j = first_alpha(menu_item, "YyNnMmHh"); + + /* Clear 'residue' of last item */ + wattrset(win, dlg.menubox.atr); + wmove(win, line_y, 0); + wclrtoeol(win); + wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr); + mvwaddstr(win, line_y, item_x, menu_item); + if (hotkey) { + wattrset(win, selected ? dlg.tag_key_selected.atr + : dlg.tag_key.atr); + mvwaddch(win, line_y, item_x + j, menu_item[j]); + } + if (selected) { + wmove(win, line_y, item_x + 1); + } + free(menu_item); + wrefresh(win); +} + +#define print_item(index, choice, selected) \ +do { \ + item_set(index); \ + do_print_item(menu, item_str(), choice, selected, !item_is_tag(':')); \ +} while (0) + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int item_no, int scroll, int y, int x, + int height) +{ + int cur_y, cur_x; + + getyx(win, cur_y, cur_x); + + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, dlg.uarrow.atr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, dlg.menubox.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + wrefresh(win); + + if ((height < item_no) && (scroll + height < item_no)) { + wattrset(win, dlg.darrow.atr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, dlg.menubox_border.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + wmove(win, cur_y, cur_x); + wrefresh(win); +} + +/* + * Display the termination buttons. + */ +static void print_buttons(WINDOW * win, int height, int width, int selected) +{ + int x = width / 2 - 28; + int y = height - 2; + + print_button(win, "Select", y, x, selected == 0); + print_button(win, " Exit ", y, x + 12, selected == 1); + print_button(win, " Help ", y, x + 24, selected == 2); + print_button(win, " Save ", y, x + 36, selected == 3); + print_button(win, " Load ", y, x + 48, selected == 4); + + wmove(win, y, x + 1 + 12 * selected); + wrefresh(win); +} + +/* scroll up n lines (n may be negative) */ +static void do_scroll(WINDOW *win, int *scroll, int n) +{ + /* Scroll menu up */ + scrollok(win, TRUE); + wscrl(win, n); + scrollok(win, FALSE); + *scroll = *scroll + n; + wrefresh(win); +} + +/* + * Display a menu for choosing among a number of options + */ +int dialog_menu(const char *title, const char *prompt, + const void *selected, int *s_scroll) +{ + int i, j, x, y, box_x, box_y; + int height, width, menu_height; + int key = 0, button = 0, scroll = 0, choice = 0; + int first_item = 0, max_choice; + WINDOW *dialog, *menu; + +do_resize: + height = getmaxy(stdscr); + width = getmaxx(stdscr); + if (height < MENUBOX_HEIGHT_MIN || width < MENUBOX_WIDTH_MIN) + return -ERRDISPLAYTOOSMALL; + + height -= 4; + width -= 5; + menu_height = height - 10; + + max_choice = MIN(menu_height, item_count()); + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + wbkgdset(dialog, dlg.dialog.atr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + menu_width = width - 6; + box_y = height - menu_height - 5; + box_x = (width - menu_width) / 2 - 1; + + /* create new window for the menu */ + menu = subwin(dialog, menu_height, menu_width, + y + box_y + 1, x + box_x + 1); + keypad(menu, TRUE); + + /* draw a box around the menu items */ + draw_box(dialog, box_y, box_x, menu_height + 2, menu_width + 2, + dlg.menubox_border.atr, dlg.menubox.atr); + + if (menu_width >= 80) + item_x = (menu_width - 70) / 2; + else + item_x = 4; + + /* Set choice to default item */ + item_foreach() + if (selected && (selected == item_data())) + choice = item_n(); + /* get the saved scroll info */ + scroll = *s_scroll; + if ((scroll <= choice) && (scroll + max_choice > choice) && + (scroll >= 0) && (scroll + max_choice <= item_count())) { + first_item = scroll; + choice = choice - scroll; + } else { + scroll = 0; + } + if ((choice >= max_choice)) { + if (choice >= item_count() - max_choice / 2) + scroll = first_item = item_count() - max_choice; + else + scroll = first_item = choice - max_choice / 2; + choice = choice - scroll; + } + + /* Print the menu */ + for (i = 0; i < max_choice; i++) { + print_item(first_item + i, i, i == choice); + } + + wnoutrefresh(menu); + + print_arrows(dialog, item_count(), scroll, + box_y, box_x + item_x + 1, menu_height); + + print_buttons(dialog, height, width, 0); + wmove(menu, choice, item_x + 1); + wrefresh(menu); + + while (key != KEY_ESC) { + key = wgetch(menu); + + if (key < 256 && isalpha(key)) + key = tolower(key); + + if (strchr("ynmh ", key)) + i = max_choice; + else { + for (i = choice + 1; i < max_choice; i++) { + item_set(scroll + i); + j = first_alpha(item_str(), "YyNnMmHh"); + if (key == tolower(item_str()[j])) + break; + } + if (i == max_choice) + for (i = 0; i < max_choice; i++) { + item_set(scroll + i); + j = first_alpha(item_str(), "YyNnMmHh"); + if (key == tolower(item_str()[j])) + break; + } + } + + if (item_count() != 0 && + (i < max_choice || + key == KEY_UP || key == KEY_DOWN || + key == '-' || key == '+' || + key == KEY_PPAGE || key == KEY_NPAGE)) { + /* Remove highligt of current item */ + print_item(scroll + choice, choice, FALSE); + + if (key == KEY_UP || key == '-') { + if (choice < 2 && scroll) { + /* Scroll menu down */ + do_scroll(menu, &scroll, -1); + + print_item(scroll, 0, FALSE); + } else + choice = MAX(choice - 1, 0); + + } else if (key == KEY_DOWN || key == '+') { + print_item(scroll+choice, choice, FALSE); + + if ((choice > max_choice - 3) && + (scroll + max_choice < item_count())) { + /* Scroll menu up */ + do_scroll(menu, &scroll, 1); + + print_item(scroll+max_choice - 1, + max_choice - 1, FALSE); + } else + choice = MIN(choice + 1, max_choice - 1); + + } else if (key == KEY_PPAGE) { + scrollok(menu, TRUE); + for (i = 0; (i < max_choice); i++) { + if (scroll > 0) { + do_scroll(menu, &scroll, -1); + print_item(scroll, 0, FALSE); + } else { + if (choice > 0) + choice--; + } + } + + } else if (key == KEY_NPAGE) { + for (i = 0; (i < max_choice); i++) { + if (scroll + max_choice < item_count()) { + do_scroll(menu, &scroll, 1); + print_item(scroll+max_choice-1, + max_choice - 1, FALSE); + } else { + if (choice + 1 < max_choice) + choice++; + } + } + } else + choice = i; + + print_item(scroll + choice, choice, TRUE); + + print_arrows(dialog, item_count(), scroll, + box_y, box_x + item_x + 1, menu_height); + + wnoutrefresh(dialog); + wrefresh(menu); + + continue; /* wait for another key press */ + } + + switch (key) { + case KEY_LEFT: + case TAB: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 4 : (button > 4 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(menu); + break; + case ' ': + case 's': + case 'y': + case 'n': + case 'm': + case '/': + case 'h': + case '?': + case 'z': + case '\n': + /* save scroll info */ + *s_scroll = scroll; + delwin(menu); + delwin(dialog); + item_set(scroll + choice); + item_set_selected(1); + switch (key) { + case 'h': + case '?': + return 2; + case 's': + case 'y': + return 5; + case 'n': + return 6; + case 'm': + return 7; + case ' ': + return 8; + case '/': + return 9; + case 'z': + return 10; + case '\n': + return button; + } + return 0; + case 'e': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(menu); + break; + case KEY_RESIZE: + on_key_resize(); + delwin(menu); + delwin(dialog); + goto do_resize; + } + } + delwin(menu); + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/textbox.c b/scripts/kconfig/lxdialog/textbox.c new file mode 100644 index 000000000..0abaf6359 --- /dev/null +++ b/scripts/kconfig/lxdialog/textbox.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * textbox.c -- implements the text box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +static int hscroll; +static int begin_reached, end_reached, page_length; +static const char *buf, *page; +static size_t start, end; + +/* + * Go back 'n' lines in text. Called by dialog_textbox(). + * 'page' will be updated to point to the desired line in 'buf'. + */ +static void back_lines(int n) +{ + int i; + + begin_reached = 0; + /* Go back 'n' lines */ + for (i = 0; i < n; i++) { + if (*page == '\0') { + if (end_reached) { + end_reached = 0; + continue; + } + } + if (page == buf) { + begin_reached = 1; + return; + } + page--; + do { + if (page == buf) { + begin_reached = 1; + return; + } + page--; + } while (*page != '\n'); + page++; + } +} + +/* + * Return current line of text. Called by dialog_textbox() and print_line(). + * 'page' should point to start of current line before calling, and will be + * updated to point to start of next line. + */ +static char *get_line(void) +{ + int i = 0; + static char line[MAX_LEN + 1]; + + end_reached = 0; + while (*page != '\n') { + if (*page == '\0') { + end_reached = 1; + break; + } else if (i < MAX_LEN) + line[i++] = *(page++); + else { + /* Truncate lines longer than MAX_LEN characters */ + if (i == MAX_LEN) + line[i++] = '\0'; + page++; + } + } + if (i <= MAX_LEN) + line[i] = '\0'; + if (!end_reached) + page++; /* move past '\n' */ + + return line; +} + +/* + * Print a new line of text. + */ +static void print_line(WINDOW *win, int row, int width) +{ + char *line; + + line = get_line(); + line += MIN(strlen(line), hscroll); /* Scroll horizontally */ + wmove(win, row, 0); /* move cursor to correct line */ + waddch(win, ' '); + waddnstr(win, line, MIN(strlen(line), width - 2)); + + /* Clear 'residue' of previous line */ + wclrtoeol(win); +} + +/* + * Print a new page of text. + */ +static void print_page(WINDOW *win, int height, int width) +{ + int i, passed_end = 0; + + page_length = 0; + for (i = 0; i < height; i++) { + print_line(win, i, width); + if (!passed_end) + page_length++; + if (end_reached && !passed_end) + passed_end = 1; + } + wnoutrefresh(win); +} + +/* + * Print current position + */ +static void print_position(WINDOW *win) +{ + int percent; + + wattrset(win, dlg.position_indicator.atr); + wbkgdset(win, dlg.position_indicator.atr & A_COLOR); + percent = (page - buf) * 100 / strlen(buf); + wmove(win, getmaxy(win) - 3, getmaxx(win) - 9); + wprintw(win, "(%3d%%)", percent); +} + +/* + * refresh window content + */ +static void refresh_text_box(WINDOW *dialog, WINDOW *box, int boxh, int boxw, + int cur_y, int cur_x) +{ + start = page - buf; + + print_page(box, boxh, boxw); + print_position(dialog); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + + end = page - buf; +} + +/* + * Display text from a file in a dialog box. + * + * keys is a null-terminated array + */ +int dialog_textbox(const char *title, const char *tbuf, int initial_height, + int initial_width, int *_vscroll, int *_hscroll, + int (*extra_key_cb)(int, size_t, size_t, void *), void *data) +{ + int i, x, y, cur_x, cur_y, key = 0; + int height, width, boxh, boxw; + WINDOW *dialog, *box; + bool done = false; + + begin_reached = 1; + end_reached = 0; + page_length = 0; + hscroll = 0; + buf = tbuf; + page = buf; /* page is pointer to start of page to be displayed */ + + if (_vscroll && *_vscroll) { + begin_reached = 0; + + for (i = 0; i < *_vscroll; i++) + get_line(); + } + if (_hscroll) + hscroll = *_hscroll; + +do_resize: + getmaxyx(stdscr, height, width); + if (height < TEXTBOX_HEIGHT_MIN || width < TEXTBOX_WIDTH_MIN) + return -ERRDISPLAYTOOSMALL; + if (initial_height != 0) + height = initial_height; + else + if (height > 4) + height -= 4; + else + height = 0; + if (initial_width != 0) + width = initial_width; + else + if (width > 5) + width -= 5; + else + width = 0; + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + /* Create window for box region, used for scrolling text */ + boxh = height - 4; + boxw = width - 2; + box = subwin(dialog, boxh, boxw, y + 1, x + 1); + wattrset(box, dlg.dialog.atr); + wbkgdset(box, dlg.dialog.atr & A_COLOR); + + keypad(box, TRUE); + + /* register the new window, along with its borders */ + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + wbkgdset(dialog, dlg.dialog.atr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + print_button(dialog, " Exit ", height - 2, width / 2 - 4, TRUE); + wnoutrefresh(dialog); + getyx(dialog, cur_y, cur_x); /* Save cursor position */ + + /* Print first page of text */ + attr_clear(box, boxh, boxw, dlg.dialog.atr); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + + while (!done) { + key = wgetch(dialog); + switch (key) { + case 'E': /* Exit */ + case 'e': + case 'X': + case 'x': + case 'q': + case '\n': + done = true; + break; + case 'g': /* First page */ + case KEY_HOME: + if (!begin_reached) { + begin_reached = 1; + page = buf; + refresh_text_box(dialog, box, boxh, boxw, + cur_y, cur_x); + } + break; + case 'G': /* Last page */ + case KEY_END: + + end_reached = 1; + /* point to last char in buf */ + page = buf + strlen(buf); + back_lines(boxh); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + break; + case 'K': /* Previous line */ + case 'k': + case KEY_UP: + if (begin_reached) + break; + + back_lines(page_length + 1); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + break; + case 'B': /* Previous page */ + case 'b': + case 'u': + case KEY_PPAGE: + if (begin_reached) + break; + back_lines(page_length + boxh); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + break; + case 'J': /* Next line */ + case 'j': + case KEY_DOWN: + if (end_reached) + break; + + back_lines(page_length - 1); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + break; + case KEY_NPAGE: /* Next page */ + case ' ': + case 'd': + if (end_reached) + break; + + begin_reached = 0; + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + break; + case '0': /* Beginning of line */ + case 'H': /* Scroll left */ + case 'h': + case KEY_LEFT: + if (hscroll <= 0) + break; + + if (key == '0') + hscroll = 0; + else + hscroll--; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + break; + case 'L': /* Scroll right */ + case 'l': + case KEY_RIGHT: + if (hscroll >= MAX_LEN) + break; + hscroll++; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x); + break; + case KEY_ESC: + if (on_key_esc(dialog) == KEY_ESC) + done = true; + break; + case KEY_RESIZE: + back_lines(height); + delwin(box); + delwin(dialog); + on_key_resize(); + goto do_resize; + default: + if (extra_key_cb && extra_key_cb(key, start, end, data)) { + done = true; + break; + } + } + } + delwin(box); + delwin(dialog); + if (_vscroll) { + const char *s; + + s = buf; + *_vscroll = 0; + back_lines(page_length); + while (s < page && (s = strchr(s, '\n'))) { + (*_vscroll)++; + s++; + } + } + if (_hscroll) + *_hscroll = hscroll; + return key; +} diff --git a/scripts/kconfig/lxdialog/util.c b/scripts/kconfig/lxdialog/util.c new file mode 100644 index 000000000..b34000beb --- /dev/null +++ b/scripts/kconfig/lxdialog/util.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * util.c + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include <stdarg.h> + +#include "dialog.h" + +/* Needed in signal handler in mconf.c */ +int saved_x, saved_y; + +struct dialog_info dlg; + +static void set_mono_theme(void) +{ + dlg.title.atr = A_BOLD; + 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.position_indicator.atr = A_BOLD; + dlg.item_selected.atr = A_REVERSE; + dlg.tag.atr = A_BOLD; + dlg.tag_selected.atr = A_REVERSE; + dlg.tag_key.atr = A_BOLD; + dlg.tag_key_selected.atr = A_REVERSE; + dlg.check.atr = A_BOLD; + dlg.check_selected.atr = A_REVERSE; + dlg.uarrow.atr = A_BOLD; + dlg.darrow.atr = A_BOLD; +} + +#define DLG_COLOR(dialog, f, b, h) \ +do { \ + dlg.dialog.fg = (f); \ + dlg.dialog.bg = (b); \ + dlg.dialog.hl = (h); \ +} while (0) + +static void set_classic_theme(void) +{ + DLG_COLOR(screen, COLOR_CYAN, COLOR_BLUE, true); + DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, true); + DLG_COLOR(dialog, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(title, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(button_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(button_inactive, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(button_key_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_WHITE, false); + DLG_COLOR(button_label_active, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(button_label_inactive, COLOR_BLACK, COLOR_WHITE, true); + DLG_COLOR(inputbox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(position_indicator, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(menubox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(menubox_border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(item, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(item_selected, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(tag, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(tag_key, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(check, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(check_selected, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(uarrow, COLOR_GREEN, COLOR_WHITE, true); + DLG_COLOR(darrow, COLOR_GREEN, COLOR_WHITE, true); +} + +static void set_blackbg_theme(void) +{ + DLG_COLOR(screen, COLOR_RED, COLOR_BLACK, true); + DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, false); + DLG_COLOR(dialog, COLOR_WHITE, COLOR_BLACK, false); + DLG_COLOR(title, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(button_active, COLOR_YELLOW, COLOR_RED, false); + DLG_COLOR(button_inactive, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_RED, true); + DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_RED, false); + DLG_COLOR(button_label_inactive, COLOR_WHITE, COLOR_BLACK, false); + + DLG_COLOR(inputbox, COLOR_YELLOW, COLOR_BLACK, false); + + DLG_COLOR(position_indicator, COLOR_RED, COLOR_BLACK, false); + + DLG_COLOR(menubox, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(menubox_border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(item, COLOR_WHITE, COLOR_BLACK, false); + DLG_COLOR(item_selected, COLOR_WHITE, COLOR_RED, false); + + DLG_COLOR(tag, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_RED, true); + DLG_COLOR(tag_key, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_RED, true); + + DLG_COLOR(check, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(check_selected, COLOR_YELLOW, COLOR_RED, true); + + DLG_COLOR(uarrow, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(darrow, COLOR_RED, COLOR_BLACK, false); +} + +static void set_bluetitle_theme(void) +{ + set_classic_theme(); + DLG_COLOR(title, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(position_indicator, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(tag, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(tag_key, COLOR_BLUE, COLOR_WHITE, true); + +} + +/* + * Select color theme + */ +static int set_theme(const char *theme) +{ + int use_color = 1; + if (!theme) + set_bluetitle_theme(); + else if (strcmp(theme, "classic") == 0) + set_classic_theme(); + else if (strcmp(theme, "bluetitle") == 0) + set_bluetitle_theme(); + else if (strcmp(theme, "blackbg") == 0) + set_blackbg_theme(); + else if (strcmp(theme, "mono") == 0) + use_color = 0; + + return use_color; +} + +static void init_one_color(struct dialog_color *color) +{ + static int pair = 0; + + pair++; + init_pair(pair, color->fg, color->bg); + if (color->hl) + color->atr = A_BOLD | COLOR_PAIR(pair); + else + color->atr = COLOR_PAIR(pair); +} + +static void init_dialog_colors(void) +{ + init_one_color(&dlg.screen); + init_one_color(&dlg.shadow); + init_one_color(&dlg.dialog); + init_one_color(&dlg.title); + init_one_color(&dlg.border); + init_one_color(&dlg.button_active); + init_one_color(&dlg.button_inactive); + init_one_color(&dlg.button_key_active); + init_one_color(&dlg.button_key_inactive); + init_one_color(&dlg.button_label_active); + init_one_color(&dlg.button_label_inactive); + init_one_color(&dlg.inputbox); + init_one_color(&dlg.position_indicator); + init_one_color(&dlg.menubox); + init_one_color(&dlg.menubox_border); + init_one_color(&dlg.item); + init_one_color(&dlg.item_selected); + init_one_color(&dlg.tag); + init_one_color(&dlg.tag_selected); + init_one_color(&dlg.tag_key); + init_one_color(&dlg.tag_key_selected); + init_one_color(&dlg.check); + init_one_color(&dlg.check_selected); + init_one_color(&dlg.uarrow); + init_one_color(&dlg.darrow); +} + +/* + * Setup for color display + */ +static void color_setup(const char *theme) +{ + int use_color; + + use_color = set_theme(theme); + if (use_color && has_colors()) { + start_color(); + init_dialog_colors(); + } else + set_mono_theme(); +} + +/* + * Set window to attribute 'attr' + */ +void attr_clear(WINDOW * win, int height, int width, chtype attr) +{ + int i, j; + + wattrset(win, attr); + for (i = 0; i < height; i++) { + wmove(win, i, 0); + for (j = 0; j < width; j++) + waddch(win, ' '); + } + touchwin(win); +} + +void dialog_clear(void) +{ + int lines, columns; + + lines = getmaxy(stdscr); + columns = getmaxx(stdscr); + + attr_clear(stdscr, lines, columns, dlg.screen.atr); + /* Display background title if it exists ... - SLH */ + if (dlg.backtitle != NULL) { + int i, len = 0, skip = 0; + struct subtitle_list *pos; + + wattrset(stdscr, dlg.screen.atr); + mvwaddstr(stdscr, 0, 1, (char *)dlg.backtitle); + + for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { + /* 3 is for the arrow and spaces */ + len += strlen(pos->text) + 3; + } + + wmove(stdscr, 1, 1); + if (len > columns - 2) { + const char *ellipsis = "[...] "; + waddstr(stdscr, ellipsis); + skip = len - (columns - 2 - strlen(ellipsis)); + } + + for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { + if (skip == 0) + waddch(stdscr, ACS_RARROW); + else + skip--; + + if (skip == 0) + waddch(stdscr, ' '); + else + skip--; + + if (skip < strlen(pos->text)) { + waddstr(stdscr, pos->text + skip); + skip = 0; + } else + skip -= strlen(pos->text); + + if (skip == 0) + waddch(stdscr, ' '); + else + skip--; + } + + for (i = len + 1; i < columns - 1; i++) + waddch(stdscr, ACS_HLINE); + } + wnoutrefresh(stdscr); +} + +/* + * Do some initialization for dialog + */ +int init_dialog(const char *backtitle) +{ + int height, width; + + initscr(); /* Init curses */ + + /* Get current cursor position for signal handler in mconf.c */ + getyx(stdscr, saved_y, saved_x); + + getmaxyx(stdscr, height, width); + if (height < WINDOW_HEIGHT_MIN || width < WINDOW_WIDTH_MIN) { + endwin(); + return -ERRDISPLAYTOOSMALL; + } + + dlg.backtitle = backtitle; + color_setup(getenv("MENUCONFIG_COLOR")); + + keypad(stdscr, TRUE); + cbreak(); + noecho(); + dialog_clear(); + + return 0; +} + +void set_dialog_backtitle(const char *backtitle) +{ + dlg.backtitle = backtitle; +} + +void set_dialog_subtitles(struct subtitle_list *subtitles) +{ + dlg.subtitles = subtitles; +} + +/* + * End using dialog functions. + */ +void end_dialog(int x, int y) +{ + /* move cursor back to original position */ + move(y, x); + refresh(); + endwin(); +} + +/* Print the title of the dialog. Center the title and truncate + * tile if wider than dialog (- 2 chars). + **/ +void print_title(WINDOW *dialog, const char *title, int width) +{ + if (title) { + int tlen = MIN(width - 2, strlen(title)); + wattrset(dialog, dlg.title.atr); + mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' '); + mvwaddnstr(dialog, 0, (width - tlen)/2, title, tlen); + waddch(dialog, ' '); + } +} + +/* + * Print a string of text in a window, automatically wrap around to the + * next line if the string is too long to fit on one line. Newline + * characters '\n' are properly processed. We start on a new line + * if there is no room for at least 4 nonblanks following a double-space. + */ +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x) +{ + int newl, cur_x, cur_y; + int prompt_len, room, wlen; + char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0; + + snprintf(tempstr, sizeof(tempstr), "%s", prompt); + prompt_len = strlen(tempstr); + + if (prompt_len <= width - x * 2) { /* If prompt is short */ + wmove(win, y, (width - prompt_len) / 2); + waddstr(win, tempstr); + } else { + cur_x = x; + cur_y = y; + newl = 1; + word = tempstr; + while (word && *word) { + sp = strpbrk(word, "\n "); + if (sp && *sp == '\n') + newline_separator = sp; + + if (sp) + *sp++ = 0; + + /* Wrap to next line if either the word does not fit, + or it is the first word of a new sentence, and it is + short, and the next word does not fit. */ + room = width - cur_x; + wlen = strlen(word); + if (wlen > room || + (newl && wlen < 4 && sp + && wlen + 1 + strlen(sp) > room + && (!(sp2 = strpbrk(sp, "\n ")) + || wlen + 1 + (sp2 - sp) > room))) { + cur_y++; + cur_x = x; + } + wmove(win, cur_y, cur_x); + waddstr(win, word); + getyx(win, cur_y, cur_x); + + /* Move to the next line if the word separator was a newline */ + if (newline_separator) { + cur_y++; + cur_x = x; + newline_separator = 0; + } else + cur_x++; + + if (sp && *sp == ' ') { + cur_x++; /* double space */ + while (*++sp == ' ') ; + newl = 1; + } else + newl = 0; + word = sp; + } + } +} + +/* + * Print a button + */ +void print_button(WINDOW * win, const char *label, int y, int x, int selected) +{ + int i, temp; + + wmove(win, y, x); + wattrset(win, selected ? dlg.button_active.atr + : dlg.button_inactive.atr); + waddstr(win, "<"); + temp = strspn(label, " "); + label += temp; + wattrset(win, selected ? dlg.button_label_active.atr + : dlg.button_label_inactive.atr); + for (i = 0; i < temp; i++) + waddch(win, ' '); + wattrset(win, selected ? dlg.button_key_active.atr + : dlg.button_key_inactive.atr); + waddch(win, label[0]); + wattrset(win, selected ? dlg.button_label_active.atr + : dlg.button_label_inactive.atr); + waddstr(win, (char *)label + 1); + wattrset(win, selected ? dlg.button_active.atr + : dlg.button_inactive.atr); + waddstr(win, ">"); + wmove(win, y, x + temp + 1); +} + +/* + * Draw a rectangular box with line drawing characters + */ +void +draw_box(WINDOW * win, int y, int x, int height, int width, + chtype box, chtype border) +{ + int i, j; + + wattrset(win, 0); + for (i = 0; i < height; i++) { + wmove(win, y + i, x); + for (j = 0; j < width; j++) + if (!i && !j) + waddch(win, border | ACS_ULCORNER); + else if (i == height - 1 && !j) + waddch(win, border | ACS_LLCORNER); + else if (!i && j == width - 1) + waddch(win, box | ACS_URCORNER); + else if (i == height - 1 && j == width - 1) + waddch(win, box | ACS_LRCORNER); + else if (!i) + waddch(win, border | ACS_HLINE); + else if (i == height - 1) + waddch(win, box | ACS_HLINE); + else if (!j) + waddch(win, border | ACS_VLINE); + else if (j == width - 1) + waddch(win, box | ACS_VLINE); + else + waddch(win, box | ' '); + } +} + +/* + * Draw shadows along the right and bottom edge to give a more 3D look + * to the boxes + */ +void draw_shadow(WINDOW * win, int y, int x, int height, int width) +{ + int i; + + if (has_colors()) { /* Whether terminal supports color? */ + wattrset(win, dlg.shadow.atr); + wmove(win, y + height, x + 2); + for (i = 0; i < width; i++) + waddch(win, winch(win) & A_CHARTEXT); + for (i = y + 1; i < y + height + 1; i++) { + wmove(win, i, x + width); + waddch(win, winch(win) & A_CHARTEXT); + waddch(win, winch(win) & A_CHARTEXT); + } + wnoutrefresh(win); + } +} + +/* + * Return the position of the first alphabetic character in a string. + */ +int first_alpha(const char *string, const char *exempt) +{ + int i, in_paren = 0, c; + + for (i = 0; i < strlen(string); i++) { + c = tolower(string[i]); + + if (strchr("<[(", c)) + ++in_paren; + if (strchr(">])", c) && in_paren > 0) + --in_paren; + + if ((!in_paren) && isalpha(c) && strchr(exempt, c) == 0) + return i; + } + + return 0; +} + +/* + * ncurses uses ESC to detect escaped char sequences. This resutl in + * a small timeout before ESC is actually delivered to the application. + * lxdialog suggest <ESC> <ESC> which is correctly translated to two + * times esc. But then we need to ignore the second esc to avoid stepping + * out one menu too much. Filter away all escaped key sequences since + * keypad(FALSE) turn off ncurses support for escape sequences - and that's + * needed to make notimeout() do as expected. + */ +int on_key_esc(WINDOW *win) +{ + int key; + int key2; + int key3; + + nodelay(win, TRUE); + keypad(win, FALSE); + key = wgetch(win); + key2 = wgetch(win); + do { + key3 = wgetch(win); + } while (key3 != ERR); + nodelay(win, FALSE); + keypad(win, TRUE); + if (key == KEY_ESC && key2 == ERR) + return KEY_ESC; + else if (key != ERR && key != KEY_ESC && key2 == ERR) + ungetch(key); + + return -1; +} + +/* redraw screen in new size */ +int on_key_resize(void) +{ + dialog_clear(); + return KEY_RESIZE; +} + +struct dialog_list *item_cur; +struct dialog_list item_nil; +struct dialog_list *item_head; + +void item_reset(void) +{ + struct dialog_list *p, *next; + + for (p = item_head; p; p = next) { + next = p->next; + free(p); + } + item_head = NULL; + item_cur = &item_nil; +} + +void item_make(const char *fmt, ...) +{ + va_list ap; + struct dialog_list *p = malloc(sizeof(*p)); + + if (item_head) + item_cur->next = p; + else + item_head = p; + item_cur = p; + memset(p, 0, sizeof(*p)); + + va_start(ap, fmt); + vsnprintf(item_cur->node.str, sizeof(item_cur->node.str), fmt, ap); + va_end(ap); +} + +void item_add_str(const char *fmt, ...) +{ + va_list ap; + size_t avail; + + avail = sizeof(item_cur->node.str) - strlen(item_cur->node.str); + + va_start(ap, fmt); + vsnprintf(item_cur->node.str + strlen(item_cur->node.str), + avail, fmt, ap); + item_cur->node.str[sizeof(item_cur->node.str) - 1] = '\0'; + va_end(ap); +} + +void item_set_tag(char tag) +{ + item_cur->node.tag = tag; +} +void item_set_data(void *ptr) +{ + item_cur->node.data = ptr; +} + +void item_set_selected(int val) +{ + item_cur->node.selected = val; +} + +int item_activate_selected(void) +{ + item_foreach() + if (item_is_selected()) + return 1; + return 0; +} + +void *item_data(void) +{ + return item_cur->node.data; +} + +char item_tag(void) +{ + return item_cur->node.tag; +} + +int item_count(void) +{ + int n = 0; + struct dialog_list *p; + + for (p = item_head; p; p = p->next) + n++; + return n; +} + +void item_set(int n) +{ + int i = 0; + item_foreach() + if (i++ == n) + return; +} + +int item_n(void) +{ + int n = 0; + struct dialog_list *p; + + for (p = item_head; p; p = p->next) { + if (p == item_cur) + return n; + n++; + } + return 0; +} + +const char *item_str(void) +{ + return item_cur->node.str; +} + +int item_is_selected(void) +{ + return (item_cur->node.selected != 0); +} + +int item_is_tag(char tag) +{ + return (item_cur->node.tag == tag); +} diff --git a/scripts/kconfig/lxdialog/yesno.c b/scripts/kconfig/lxdialog/yesno.c new file mode 100644 index 000000000..b57d25e15 --- /dev/null +++ b/scripts/kconfig/lxdialog/yesno.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * yesno.c -- implements the yes/no box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +/* + * Display termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 10; + int y = height - 2; + + print_button(dialog, " Yes ", y, x, selected == 0); + print_button(dialog, " No ", y, x + 13, selected == 1); + + wmove(dialog, y, x + 1 + 13 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with two buttons - Yes and No + */ +int dialog_yesno(const char *title, const char *prompt, int height, int width) +{ + int i, x, y, key = 0, button = 0; + WINDOW *dialog; + +do_resize: + if (getmaxy(stdscr) < (height + YESNO_HEIGHT_MIN)) + return -ERRDISPLAYTOOSMALL; + if (getmaxx(stdscr) < (width + YESNO_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + print_buttons(dialog, height, width, 0); + + while (key != KEY_ESC) { + key = wgetch(dialog); + switch (key) { + case 'Y': + case 'y': + delwin(dialog); + return 0; + case 'N': + case 'n': + delwin(dialog); + return 1; + + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case ' ': + case '\n': + delwin(dialog); + return button; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(dialog); + on_key_resize(); + goto do_resize; + } + } + + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/scripts/kconfig/mconf-cfg.sh b/scripts/kconfig/mconf-cfg.sh new file mode 100755 index 000000000..1bc304dc2 --- /dev/null +++ b/scripts/kconfig/mconf-cfg.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +set -eu + +cflags=$1 +libs=$2 + +PKG="ncursesw" +PKG2="ncurses" + +if [ -n "$(command -v ${HOSTPKG_CONFIG})" ]; then + if ${HOSTPKG_CONFIG} --exists $PKG; then + ${HOSTPKG_CONFIG} --cflags ${PKG} > ${cflags} + ${HOSTPKG_CONFIG} --libs ${PKG} > ${libs} + exit 0 + fi + + if ${HOSTPKG_CONFIG} --exists ${PKG2}; then + ${HOSTPKG_CONFIG} --cflags ${PKG2} > ${cflags} + ${HOSTPKG_CONFIG} --libs ${PKG2} > ${libs} + exit 0 + fi +fi + +# Check the default paths in case pkg-config is not installed. +# (Even if it is installed, some distributions such as openSUSE cannot +# find ncurses by pkg-config.) +if [ -f /usr/include/ncursesw/ncurses.h ]; then + echo -D_GNU_SOURCE -I/usr/include/ncursesw > ${cflags} + echo -lncursesw > ${libs} + exit 0 +fi + +if [ -f /usr/include/ncurses/ncurses.h ]; then + echo -D_GNU_SOURCE -I/usr/include/ncurses > ${cflags} + echo -lncurses > ${libs} + exit 0 +fi + +# As a final fallback before giving up, check if $HOSTCC knows of a default +# ncurses installation (e.g. from a vendor-specific sysroot). +if echo '#include <ncurses.h>' | ${HOSTCC} -E - >/dev/null 2>&1; then + echo -D_GNU_SOURCE > ${cflags} + echo -lncurses > ${libs} + exit 0 +fi + +echo >&2 "*" +echo >&2 "* Unable to find the ncurses package." +echo >&2 "* Install ncurses (ncurses-devel or libncurses-dev" +echo >&2 "* depending on your distribution)." +echo >&2 "*" +echo >&2 "* You may also need to install ${HOSTPKG_CONFIG} to find the" +echo >&2 "* ncurses installed in a non-default location." +echo >&2 "*" +exit 1 diff --git a/scripts/kconfig/mconf.c b/scripts/kconfig/mconf.c new file mode 100644 index 000000000..b8b7bba84 --- /dev/null +++ b/scripts/kconfig/mconf.c @@ -0,0 +1,966 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * + * Introduced single menu mode (show all sub-menus in one large tree). + * 2002-11-06 Petr Baudis <pasky@ucw.cz> + * + * i18n, 2005, Arnaldo Carvalho de Melo <acme@conectiva.com.br> + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <locale.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <signal.h> +#include <unistd.h> + +#include <list.h> +#include <xalloc.h> +#include "lkc.h" +#include "lxdialog/dialog.h" +#include "mnconf-common.h" + +static const char mconf_readme[] = +"Overview\n" +"--------\n" +"This interface lets you select features and parameters for the build.\n" +"Features can either be built-in, modularized, or ignored. Parameters\n" +"must be entered in as decimal or hexadecimal numbers or text.\n" +"\n" +"Menu items beginning with following braces represent features that\n" +" [ ] can be built in or removed\n" +" < > can be built in, modularized or removed\n" +" { } can be built in or modularized (selected by other feature)\n" +" - - are selected by other feature,\n" +"while *, M or whitespace inside braces means to build in, build as\n" +"a module or to exclude the feature respectively.\n" +"\n" +"To change any of these features, highlight it with the cursor\n" +"keys and press <Y> to build it in, <M> to make it a module or\n" +"<N> to remove it. You may also press the <Space Bar> to cycle\n" +"through the available options (i.e. Y->N->M->Y).\n" +"\n" +"Some additional keyboard hints:\n" +"\n" +"Menus\n" +"----------\n" +"o Use the Up/Down arrow keys (cursor keys) to highlight the item you\n" +" wish to change or the submenu you wish to select and press <Enter>.\n" +" Submenus are designated by \"--->\", empty ones by \"----\".\n" +"\n" +" Shortcut: Press the option's highlighted letter (hotkey).\n" +" Pressing a hotkey more than once will sequence\n" +" through all visible items which use that hotkey.\n" +"\n" +" You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n" +" unseen options into view.\n" +"\n" +"o To exit a menu use the cursor keys to highlight the <Exit> button\n" +" and press <ENTER>.\n" +"\n" +" Shortcut: Press <ESC><ESC> or <E> or <X> if there is no hotkey\n" +" using those letters. You may press a single <ESC>, but\n" +" there is a delayed response which you may find annoying.\n" +"\n" +" Also, the <TAB> and cursor keys will cycle between <Select>,\n" +" <Exit>, <Help>, <Save>, and <Load>.\n" +"\n" +"o To get help with an item, use the cursor keys to highlight <Help>\n" +" and press <ENTER>.\n" +"\n" +" Shortcut: Press <H> or <?>.\n" +"\n" +"o To toggle the display of hidden options, press <Z>.\n" +"\n" +"\n" +"Radiolists (Choice lists)\n" +"-----------\n" +"o Use the cursor keys to select the option you wish to set and press\n" +" <S> or the <SPACE BAR>.\n" +"\n" +" Shortcut: Press the first letter of the option you wish to set then\n" +" press <S> or <SPACE BAR>.\n" +"\n" +"o To see available help for the item, use the cursor keys to highlight\n" +" <Help> and Press <ENTER>.\n" +"\n" +" Shortcut: Press <H> or <?>.\n" +"\n" +" Also, the <TAB> and cursor keys will cycle between <Select> and\n" +" <Help>\n" +"\n" +"\n" +"Data Entry\n" +"-----------\n" +"o Enter the requested information and press <ENTER>\n" +" If you are entering hexadecimal values, it is not necessary to\n" +" add the '0x' prefix to the entry.\n" +"\n" +"o For help, use the <TAB> or cursor keys to highlight the help option\n" +" and press <ENTER>. You can try <TAB><H> as well.\n" +"\n" +"\n" +"Text Box (Help Window)\n" +"--------\n" +"o Use the cursor keys to scroll up/down/left/right. The VI editor\n" +" keys h,j,k,l function here as do <u>, <d>, <SPACE BAR> and <B> for\n" +" those who are familiar with less and lynx.\n" +"\n" +"o Press <E>, <X>, <q>, <Enter> or <Esc><Esc> to exit.\n" +"\n" +"\n" +"Alternate Configuration Files\n" +"-----------------------------\n" +"Menuconfig supports the use of alternate configuration files for\n" +"those who, for various reasons, find it necessary to switch\n" +"between different configurations.\n" +"\n" +"The <Save> button will let you save the current configuration to\n" +"a file of your choosing. Use the <Load> button to load a previously\n" +"saved alternate configuration.\n" +"\n" +"Even if you don't use alternate configuration files, but you find\n" +"during a Menuconfig session that you have completely messed up your\n" +"settings, you may use the <Load> button to restore your previously\n" +"saved settings from \".config\" without restarting Menuconfig.\n" +"\n" +"Other information\n" +"-----------------\n" +"If you use Menuconfig in an XTERM window, make sure you have your\n" +"$TERM variable set to point to an xterm definition which supports\n" +"color. Otherwise, Menuconfig will look rather bad. Menuconfig will\n" +"not display correctly in an RXVT window because rxvt displays only one\n" +"intensity of color, bright.\n" +"\n" +"Menuconfig will display larger menus on screens or xterms which are\n" +"set to display more than the standard 25 row by 80 column geometry.\n" +"In order for this to work, the \"stty size\" command must be able to\n" +"display the screen's current row and column geometry. I STRONGLY\n" +"RECOMMEND that you make sure you do NOT have the shell variables\n" +"LINES and COLUMNS exported into your environment. Some distributions\n" +"export those variables via /etc/profile. Some ncurses programs can\n" +"become confused when those variables (LINES & COLUMNS) don't reflect\n" +"the true screen size.\n" +"\n" +"Optional personality available\n" +"------------------------------\n" +"If you prefer to have all of the options listed in a single menu,\n" +"rather than the default multimenu hierarchy, run the menuconfig with\n" +"MENUCONFIG_MODE environment variable set to single_menu. Example:\n" +"\n" +"make MENUCONFIG_MODE=single_menu menuconfig\n" +"\n" +"<Enter> will then unroll the appropriate category, or enfold it if it\n" +"is already unrolled.\n" +"\n" +"Note that this mode can eventually be a little more CPU expensive\n" +"(especially with a larger number of unrolled categories) than the\n" +"default mode.\n" +"\n" + +"Search\n" +"-------\n" +"Pressing the forward-slash (/) anywhere brings up a search dialog box.\n" +"\n" + +"Different color themes available\n" +"--------------------------------\n" +"It is possible to select different color themes using the variable\n" +"MENUCONFIG_COLOR. To select a theme use:\n" +"\n" +"make MENUCONFIG_COLOR=<theme> menuconfig\n" +"\n" +"Available themes are\n" +" mono => selects colors suitable for monochrome displays\n" +" blackbg => selects a color scheme with black background\n" +" classic => theme with blue background. The classic look\n" +" bluetitle => an LCD friendly version of classic. (default)\n" +"\n", +menu_instructions[] = + "Arrow keys navigate the menu. " + "<Enter> selects submenus ---> (or empty submenus ----). " + "Highlighted letters are hotkeys. " + "Pressing <Y> includes, <N> excludes, <M> modularizes features. " + "Press <Esc><Esc> to exit, <?> for Help, </> for Search. " + "Legend: [*] built-in [ ] excluded <M> module < > module capable", +radiolist_instructions[] = + "Use the arrow keys to navigate this window or " + "press the hotkey of the item you wish to select " + "followed by the <SPACE BAR>. " + "Press <?> for additional information about this option.", +inputbox_instructions_int[] = + "Please enter a decimal value. " + "Fractions will not be accepted. " + "Use the <TAB> key to move from the input field to the buttons below it.", +inputbox_instructions_hex[] = + "Please enter a hexadecimal value. " + "Use the <TAB> key to move from the input field to the buttons below it.", +inputbox_instructions_string[] = + "Please enter a string value. " + "Use the <TAB> key to move from the input field to the buttons below it.", +setmod_text[] = + "This feature depends on another which has been configured as a module.\n" + "As a result, this feature will be built as a module.", +load_config_text[] = + "Enter the name of the configuration file you wish to load. " + "Accept the name shown to restore the configuration you " + "last retrieved. Leave blank to abort.", +load_config_help[] = + "\n" + "For various reasons, one may wish to keep several different\n" + "configurations available on a single machine.\n" + "\n" + "If you have saved a previous configuration in a file other than the\n" + "default one, entering its name here will allow you to modify that\n" + "configuration.\n" + "\n" + "If you are uncertain, then you have probably never used alternate\n" + "configuration files. You should therefore leave this blank to abort.\n", +save_config_text[] = + "Enter a filename to which this configuration should be saved " + "as an alternate. Leave blank to abort.", +save_config_help[] = + "\n" + "For various reasons, one may wish to keep different configurations\n" + "available on a single machine.\n" + "\n" + "Entering a file name here will allow you to later retrieve, modify\n" + "and use the current configuration as an alternate to whatever\n" + "configuration options you have selected at that time.\n" + "\n" + "If you are uncertain what all this means then you should probably\n" + "leave this blank.\n", +search_help[] = + "\n" + "Search for symbols and display their relations.\n" + "Regular expressions are allowed.\n" + "Example: search for \"^FOO\"\n" + "Result:\n" + "-----------------------------------------------------------------\n" + "Symbol: FOO [=m]\n" + "Type : tristate\n" + "Prompt: Foo bus is used to drive the bar HW\n" + " Location:\n" + " -> Bus options (PCI, PCMCIA, EISA, ISA)\n" + " -> PCI support (PCI [=y])\n" + "(1) -> PCI access mode (<choice> [=y])\n" + " Defined at drivers/pci/Kconfig:47\n" + " Depends on: X86_LOCAL_APIC && X86_IO_APIC\n" + " Selects: LIBCRC32\n" + " Selected by: BAR [=n]\n" + "-----------------------------------------------------------------\n" + "o The line 'Type:' shows the type of the configuration option for\n" + " this symbol (bool, tristate, string, ...)\n" + "o The line 'Prompt:' shows the text used in the menu structure for\n" + " this symbol\n" + "o The 'Defined at' line tells at what file / line number the symbol\n" + " is defined\n" + "o The 'Depends on:' line tells what symbols need to be defined for\n" + " this symbol to be visible in the menu (selectable)\n" + "o The 'Location:' lines tells where in the menu structure this symbol\n" + " is located\n" + " A location followed by a [=y] indicates that this is a\n" + " selectable menu item - and the current value is displayed inside\n" + " brackets.\n" + " Press the key in the (#) prefix to jump directly to that\n" + " location. You will be returned to the current search results\n" + " after exiting this new menu.\n" + "o The 'Selects:' line tells what symbols will be automatically\n" + " selected if this symbol is selected (y or m)\n" + "o The 'Selected by' line tells what symbol has selected this symbol\n" + "\n" + "Only relevant lines are shown.\n" + "\n\n" + "Search examples:\n" + "Examples: USB => find all symbols containing USB\n" + " ^USB => find all symbols starting with USB\n" + " USB$ => find all symbols ending with USB\n" + "\n"; + +static int indent; +static struct menu *current_menu; +static int child_count; +static int single_menu_mode; +static int show_all_options; +static int save_and_exit; +static int silent; + +static void conf(struct menu *menu, struct menu *active_menu); + +static char filename[PATH_MAX+1]; +static void set_config_filename(const char *config_filename) +{ + static char menu_backtitle[PATH_MAX+128]; + + snprintf(menu_backtitle, sizeof(menu_backtitle), "%s - %s", + config_filename, rootmenu.prompt->text); + set_dialog_backtitle(menu_backtitle); + + snprintf(filename, sizeof(filename), "%s", config_filename); +} + +struct subtitle_part { + struct list_head entries; + const char *text; +}; +static LIST_HEAD(trail); + +static struct subtitle_list *subtitles; +static void set_subtitle(void) +{ + struct subtitle_part *sp; + struct subtitle_list *pos, *tmp; + + for (pos = subtitles; pos != NULL; pos = tmp) { + tmp = pos->next; + free(pos); + } + + subtitles = NULL; + list_for_each_entry(sp, &trail, entries) { + if (sp->text) { + if (pos) { + pos->next = xcalloc(1, sizeof(*pos)); + pos = pos->next; + } else { + subtitles = pos = xcalloc(1, sizeof(*pos)); + } + pos->text = sp->text; + } + } + + set_dialog_subtitles(subtitles); +} + +static void reset_subtitle(void) +{ + struct subtitle_list *pos, *tmp; + + for (pos = subtitles; pos != NULL; pos = tmp) { + tmp = pos->next; + free(pos); + } + subtitles = NULL; + set_dialog_subtitles(subtitles); +} + +static int show_textbox_ext(const char *title, const char *text, int r, int c, + int *vscroll, int *hscroll, + int (*extra_key_cb)(int, size_t, size_t, void *), + void *data) +{ + dialog_clear(); + return dialog_textbox(title, text, r, c, vscroll, hscroll, + extra_key_cb, data); +} + +static void show_textbox(const char *title, const char *text, int r, int c) +{ + show_textbox_ext(title, text, r, c, NULL, NULL, NULL, NULL); +} + +static void show_helptext(const char *title, const char *text) +{ + show_textbox(title, text, 0, 0); +} + +static void show_help(struct menu *menu) +{ + struct gstr help = str_new(); + + help.max_width = getmaxx(stdscr) - 10; + menu_get_ext_help(menu, &help); + + show_helptext(menu_get_prompt(menu), str_get(&help)); + str_free(&help); +} + +static void search_conf(void) +{ + struct symbol **sym_arr; + struct gstr res; + struct gstr title; + char *dialog_input; + int dres, vscroll = 0, hscroll = 0; + bool again; + struct gstr sttext; + struct subtitle_part stpart; + + title = str_new(); + str_printf( &title, "Enter (sub)string or regexp to search for " + "(with or without \"%s\")", CONFIG_); + +again: + dialog_clear(); + dres = dialog_inputbox("Search Configuration Parameter", + str_get(&title), + 10, 75, ""); + switch (dres) { + case 0: + break; + case 1: + show_helptext("Search Configuration", search_help); + goto again; + default: + str_free(&title); + return; + } + + /* strip the prefix if necessary */ + dialog_input = dialog_input_result; + if (strncasecmp(dialog_input_result, CONFIG_, strlen(CONFIG_)) == 0) + dialog_input += strlen(CONFIG_); + + sttext = str_new(); + str_printf(&sttext, "Search (%s)", dialog_input_result); + stpart.text = str_get(&sttext); + list_add_tail(&stpart.entries, &trail); + + sym_arr = sym_re_search(dialog_input); + do { + LIST_HEAD(head); + struct search_data data = { + .head = &head, + }; + struct jump_key *pos, *tmp; + + jump_key_char = 0; + res = get_relations_str(sym_arr, &head); + set_subtitle(); + dres = show_textbox_ext("Search Results", str_get(&res), 0, 0, + &vscroll, &hscroll, + handle_search_keys, &data); + again = false; + if (dres >= '1' && dres <= '9') { + assert(data.target != NULL); + conf(data.target->parent, data.target); + again = true; + } + str_free(&res); + list_for_each_entry_safe(pos, tmp, &head, entries) + free(pos); + } while (again); + free(sym_arr); + str_free(&title); + list_del(trail.prev); + str_free(&sttext); +} + +static void build_conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + int type, tmp, doint = 2; + tristate val; + char ch; + bool visible; + + /* + * note: menu_is_visible() has side effect that it will + * recalc the value of the symbol. + */ + visible = menu_is_visible(menu); + if (show_all_options && !menu_has_prompt(menu)) + return; + else if (!show_all_options && !visible) + return; + + sym = menu->sym; + prop = menu->prompt; + if (!sym) { + if (prop && menu != current_menu) { + const char *prompt = menu_get_prompt(menu); + switch (prop->type) { + case P_MENU: + child_count++; + if (single_menu_mode) { + item_make("%s%*c%s", + menu->data ? "-->" : "++>", + indent + 1, ' ', prompt); + } else + item_make(" %*c%s %s", + indent + 1, ' ', prompt, + menu_is_empty(menu) ? "----" : "--->"); + item_set_tag('m'); + item_set_data(menu); + if (single_menu_mode && menu->data) + goto conf_childs; + return; + case P_COMMENT: + if (prompt) { + child_count++; + item_make(" %*c*** %s ***", indent + 1, ' ', prompt); + item_set_tag(':'); + item_set_data(menu); + } + break; + default: + if (prompt) { + child_count++; + item_make("---%*c%s", indent + 1, ' ', prompt); + item_set_tag(':'); + item_set_data(menu); + } + } + } else + doint = 0; + goto conf_childs; + } + + type = sym_get_type(sym); + if (sym_is_choice(sym)) { + struct symbol *def_sym = sym_calc_choice(menu); + struct menu *def_menu = NULL; + + child_count++; + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child) && child->sym == def_sym) + def_menu = child; + } + + item_make(" "); + item_set_tag(def_menu ? 't' : ':'); + item_set_data(menu); + + item_add_str("%*c%s", indent + 1, ' ', menu_get_prompt(menu)); + 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)); + item_set_tag(':'); + item_set_data(menu); + goto conf_childs; + } + child_count++; + val = sym_get_tristate_value(sym); + 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; + } + item_add_str("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changeable(sym)) ? + "" : " (NEW)"); + if (menu->prompt->type == P_MENU) { + item_add_str(" %s", menu_is_empty(menu) ? "----" : "--->"); + return; + } + } + +conf_childs: + indent += doint; + for (child = menu->list; child; child = child->next) + build_conf(child); + indent -= doint; +} + +static void conf_choice(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + struct menu *child; + struct symbol *active; + + active = sym_calc_choice(menu); + while (1) { + int res; + int selected; + item_reset(); + + current_menu = menu; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + if (child->sym) + item_make("%s", menu_get_prompt(child)); + else { + item_make("*** %s ***", menu_get_prompt(child)); + item_set_tag(':'); + } + item_set_data(child); + if (child->sym == active) + item_set_selected(1); + if (child->sym == sym_calc_choice(menu)) + item_set_tag('X'); + } + dialog_clear(); + res = dialog_checklist(prompt ? prompt : "Main Menu", + radiolist_instructions, + MENUBOX_HEIGHT_MIN, + MENUBOX_WIDTH_MIN, + CHECKLIST_HEIGHT_MIN); + selected = item_activate_selected(); + switch (res) { + case 0: + if (selected) { + child = item_data(); + if (!child->sym) + break; + + choice_set_value(menu, child->sym); + } + return; + case 1: + if (selected) { + child = item_data(); + show_help(child); + active = child->sym; + } else + show_help(menu); + break; + case KEY_ESC: + return; + case -ERRDISPLAYTOOSMALL: + return; + } + } +} + +static void conf_string(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + + while (1) { + int res; + const char *heading; + + switch (sym_get_type(menu->sym)) { + case S_INT: + heading = inputbox_instructions_int; + break; + case S_HEX: + heading = inputbox_instructions_hex; + break; + case S_STRING: + heading = inputbox_instructions_string; + break; + default: + heading = "Internal mconf error!"; + } + dialog_clear(); + res = dialog_inputbox(prompt ? prompt : "Main Menu", + heading, 10, 75, + sym_get_string_value(menu->sym)); + switch (res) { + case 0: + if (sym_set_string_value(menu->sym, dialog_input_result)) + return; + show_textbox(NULL, "You have made an invalid entry.", 5, 43); + break; + case 1: + show_help(menu); + break; + case KEY_ESC: + return; + } + } +} + +static void conf_load(void) +{ + + while (1) { + int res; + dialog_clear(); + res = dialog_inputbox(NULL, load_config_text, + 11, 55, filename); + switch(res) { + case 0: + if (!dialog_input_result[0]) + return; + if (!conf_read(dialog_input_result)) { + set_config_filename(dialog_input_result); + conf_set_changed(true); + return; + } + show_textbox(NULL, "File does not exist!", 5, 38); + break; + case 1: + show_helptext("Load Alternate Configuration", load_config_help); + break; + case KEY_ESC: + return; + } + } +} + +static void conf_save(void) +{ + while (1) { + int res; + dialog_clear(); + res = dialog_inputbox(NULL, save_config_text, + 11, 55, filename); + switch(res) { + case 0: + if (!dialog_input_result[0]) + return; + if (!conf_write(dialog_input_result)) { + set_config_filename(dialog_input_result); + return; + } + show_textbox(NULL, "Can't create file!", 5, 60); + break; + case 1: + show_helptext("Save Alternate Configuration", save_config_help); + break; + case KEY_ESC: + return; + } + } +} + +static void conf(struct menu *menu, struct menu *active_menu) +{ + struct menu *submenu; + const char *prompt = menu_get_prompt(menu); + struct subtitle_part stpart; + struct symbol *sym; + int res; + int s_scroll = 0; + + if (menu != &rootmenu) + stpart.text = menu_get_prompt(menu); + else + stpart.text = NULL; + list_add_tail(&stpart.entries, &trail); + + while (1) { + item_reset(); + current_menu = menu; + build_conf(menu); + if (!child_count) + break; + set_subtitle(); + dialog_clear(); + res = dialog_menu(prompt ? prompt : "Main Menu", + menu_instructions, + active_menu, &s_scroll); + if (res == 1 || res == KEY_ESC || res == -ERRDISPLAYTOOSMALL) + break; + if (item_count() != 0) { + if (!item_activate_selected()) + continue; + if (!item_tag()) + continue; + } + submenu = item_data(); + active_menu = item_data(); + if (submenu) + sym = submenu->sym; + else + sym = NULL; + + switch (res) { + case 0: + switch (item_tag()) { + case 'm': + if (single_menu_mode) + submenu->data = (void *) (long) !submenu->data; + else + conf(submenu, NULL); + break; + case 't': + if (sym_is_choice(sym)) + conf_choice(submenu); + else if (submenu->prompt->type == P_MENU) + conf(submenu, NULL); + break; + case 's': + conf_string(submenu); + break; + } + break; + case 2: + if (sym) + show_help(submenu); + else { + reset_subtitle(); + show_helptext("README", mconf_readme); + } + break; + case 3: + reset_subtitle(); + conf_save(); + break; + case 4: + reset_subtitle(); + conf_load(); + break; + case 5: + if (item_is_tag('t')) { + if (sym_set_tristate_value(sym, yes)) + break; + if (sym_set_tristate_value(sym, mod)) + show_textbox(NULL, setmod_text, 6, 74); + } + break; + case 6: + if (item_is_tag('t')) + sym_set_tristate_value(sym, no); + break; + case 7: + if (item_is_tag('t')) + sym_set_tristate_value(sym, mod); + break; + case 8: + if (item_is_tag('t')) + sym_toggle_tristate_value(sym); + else if (item_is_tag('m')) + conf(submenu, NULL); + break; + case 9: + search_conf(); + break; + case 10: + show_all_options = !show_all_options; + break; + } + } + + list_del(trail.prev); +} + +static void conf_message_callback(const char *s) +{ + if (save_and_exit) { + if (!silent) + printf("%s", s); + } else { + show_textbox(NULL, s, 6, 60); + } +} + +static int handle_exit(void) +{ + int res; + + save_and_exit = 1; + reset_subtitle(); + dialog_clear(); + if (conf_get_changed()) + res = dialog_yesno(NULL, + "Do you wish to save your new configuration?\n" + "(Press <ESC><ESC> to continue kernel configuration.)", + 6, 60); + else + res = -1; + + end_dialog(saved_x, saved_y); + + switch (res) { + case 0: + if (conf_write(filename)) { + fprintf(stderr, "\n\n" + "Error while writing of the configuration.\n" + "Your configuration changes were NOT saved." + "\n\n"); + return 1; + } + conf_write_autoconf(0); + /* fall through */ + case -1: + if (!silent) + printf("\n\n" + "*** End of the configuration.\n" + "*** Execute 'make' to start the build or try 'make help'." + "\n\n"); + res = 0; + break; + default: + if (!silent) + fprintf(stderr, "\n\n" + "Your configuration changes were NOT saved." + "\n\n"); + if (res != KEY_ESC) + res = 0; + } + + return res; +} + +static void sig_handler(int signo) +{ + exit(handle_exit()); +} + +int main(int ac, char **av) +{ + char *mode; + int res; + + signal(SIGINT, sig_handler); + + setlocale(LC_ALL, ""); + + if (ac > 1 && strcmp(av[1], "-s") == 0) { + silent = 1; + /* Silence conf_read() until the real callback is set up */ + conf_set_message_callback(NULL); + av++; + } + conf_parse(av[1]); + conf_read(NULL); + + mode = getenv("MENUCONFIG_MODE"); + if (mode) { + if (!strcasecmp(mode, "single_menu")) + single_menu_mode = 1; + } + + if (init_dialog(NULL)) { + fprintf(stderr, "Your display is too small to run Menuconfig!\n"); + fprintf(stderr, "It must be at least 19 lines by 80 columns.\n"); + return 1; + } + + set_config_filename(conf_get_configname()); + conf_set_message_callback(conf_message_callback); + do { + conf(&rootmenu, NULL); + res = handle_exit(); + } while (res == KEY_ESC); + + return res; +} diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c new file mode 100644 index 000000000..9c079e92a --- /dev/null +++ b/scripts/kconfig/menu.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include <list.h> +#include <xalloc.h> +#include "lkc.h" +#include "internal.h" + +static const char nohelp_text[] = "There is no help available for this option."; + +struct menu rootmenu = { .type = M_MENU }; +static struct menu **last_entry_ptr; + +/** + * 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); + fprintf(stderr, "%s:%d:warning: ", menu->filename, menu->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +static void prop_warn(const struct property *prop, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", prop->filename, prop->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +void _menu_init(void) +{ + current_entry = current_menu = &rootmenu; + last_entry_ptr = &rootmenu.list; +} + +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; + menu->lineno = cur_lineno; + + *last_entry_ptr = menu; + last_entry_ptr = &menu->next; + current_entry = menu; + if (sym) + list_add_tail(&menu->link, &sym->menus); +} + +struct menu *menu_add_menu(void) +{ + last_entry_ptr = ¤t_entry->list; + current_menu = current_entry; + return current_menu; +} + +void menu_end_menu(void) +{ + last_entry_ptr = ¤t_menu->next; + current_menu = current_menu->parent; +} + +/* + * Rewrites 'm' to 'm' && MODULES, so that it evaluates to 'n' when running + * without modules + */ +static struct expr *rewrite_m(struct expr *e) +{ + if (!e) + return e; + + switch (e->type) { + case E_NOT: + e = expr_alloc_one(E_NOT, rewrite_m(e->left.expr)); + break; + case E_OR: + case E_AND: + 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 */ + if (e->left.sym == &symbol_mod) + return expr_alloc_and(e, expr_alloc_symbol(modules_sym)); + break; + default: + break; + } + return e; +} + +void menu_add_dep(struct expr *dep, struct expr *cond) +{ + if (cond) { + /* + * We have "depends on X if Y" and we want: + * Y != n --> X + * Y == n --> y + * That simplifies to: (X || (Y == n)) + */ + dep = expr_alloc_or(dep, + expr_trans_compare(cond, E_EQUAL, &symbol_no)); + } + current_entry->dep = expr_alloc_and(current_entry->dep, dep); +} + +void menu_set_type(int type) +{ + struct symbol *sym = current_entry->sym; + + if (sym->type == type) + return; + if (sym->type == S_UNKNOWN) { + sym->type = type; + return; + } + menu_warn(current_entry, + "ignoring type redefinition of '%s' from '%s' to '%s'", + sym->name ? sym->name : "<choice>", + sym_type_name(sym->type), sym_type_name(type)); +} + +static struct property *menu_add_prop(enum prop_type type, struct expr *expr, + struct expr *dep) +{ + struct property *prop; + + prop = xmalloc(sizeof(*prop)); + memset(prop, 0, sizeof(*prop)); + prop->type = type; + prop->filename = cur_filename; + prop->lineno = cur_lineno; + prop->menu = current_entry; + prop->expr = expr; + prop->visible.expr = dep; + + /* append property to the prop list of symbol */ + if (current_entry->sym) { + struct property **propp; + + for (propp = ¤t_entry->sym->prop; + *propp; + propp = &(*propp)->next) + ; + *propp = prop; + } + + return prop; +} + +struct property *menu_add_prompt(enum prop_type type, const char *prompt, + struct expr *dep) +{ + struct property *prop = menu_add_prop(type, NULL, dep); + + if (isspace(*prompt)) { + prop_warn(prop, "leading whitespace ignored"); + while (isspace(*prompt)) + prompt++; + } + if (current_entry->prompt) + prop_warn(prop, "prompt redefined"); + + /* Apply all upper menus' visibilities to actual prompts. */ + if (type == P_PROMPT) { + struct menu *menu = current_entry; + + while ((menu = menu->parent) != NULL) { + + if (!menu->visibility) + continue; + prop->visible.expr = expr_alloc_and(prop->visible.expr, + menu->visibility); + } + } + + current_entry->prompt = prop; + prop->text = prompt; + + return prop; +} + +void menu_add_visibility(struct expr *expr) +{ + current_entry->visibility = expr_alloc_and(current_entry->visibility, + expr); +} + +void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep) +{ + menu_add_prop(type, expr, dep); +} + +void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) +{ + menu_add_prop(type, expr_alloc_symbol(sym), dep); +} + +static int menu_validate_number(struct symbol *sym, struct symbol *sym2) +{ + return sym2->type == S_INT || sym2->type == S_HEX || + (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); +} + +static void sym_check_prop(struct symbol *sym) +{ + struct property *prop; + struct symbol *sym2; + char *use; + + for (prop = sym->prop; prop; prop = prop->next) { + switch (prop->type) { + case P_DEFAULT: + if ((sym->type == S_STRING || sym->type == S_INT || sym->type == S_HEX) && + prop->expr->type != E_SYMBOL) + prop_warn(prop, + "default for config symbol '%s'" + " must be a single symbol", sym->name); + if (prop->expr->type != E_SYMBOL) + break; + sym2 = prop_get_symbol(prop); + if (sym->type == S_HEX || sym->type == S_INT) { + if (!menu_validate_number(sym, sym2)) + prop_warn(prop, + "'%s': number is invalid", + sym->name); + } + if (sym_is_choice(sym)) { + struct menu *choice = sym_get_choice_menu(sym2); + + if (!choice || choice->sym != sym) + prop_warn(prop, + "choice default symbol '%s' is not contained in the choice", + sym2->name); + } + break; + case P_SELECT: + case P_IMPLY: + use = prop->type == P_SELECT ? "select" : "imply"; + sym2 = prop_get_symbol(prop); + if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) + prop_warn(prop, + "config symbol '%s' uses %s, but is " + "not bool or tristate", sym->name, use); + else if (sym2->type != S_UNKNOWN && + sym2->type != S_BOOLEAN && + sym2->type != S_TRISTATE) + prop_warn(prop, + "'%s' has wrong type. '%s' only " + "accept arguments of bool and " + "tristate type", sym2->name, use); + break; + case P_RANGE: + if (sym->type != S_INT && sym->type != S_HEX) + prop_warn(prop, "range is only allowed " + "for int or hex symbols"); + if (!menu_validate_number(sym, prop->expr->left.sym) || + !menu_validate_number(sym, prop->expr->right.sym)) + prop_warn(prop, "range is invalid"); + break; + default: + ; + } + } +} + +static void _menu_finalize(struct menu *parent, bool inside_choice) +{ + struct menu *menu, *last_menu; + struct symbol *sym; + struct property *prop; + struct expr *basedep, *dep, *dep2; + + sym = parent->sym; + if (parent->list) { + /* + * This menu node has children. We (recursively) process them + * and propagate parent dependencies before moving on. + */ + + /* For each child menu node... */ + for (menu = parent->list; menu; menu = menu->next) { + /* + * Propagate parent dependencies to the child menu + * node, also rewriting and simplifying expressions + */ + basedep = rewrite_m(menu->dep); + basedep = expr_transform(basedep); + basedep = expr_alloc_and(parent->dep, basedep); + basedep = expr_eliminate_dups(basedep); + menu->dep = basedep; + + if (menu->sym) + /* + * Note: For symbols, all prompts are included + * too in the symbol's own property list + */ + prop = menu->sym->prop; + else + /* + * For non-symbol menu nodes, we just need to + * handle the prompt + */ + prop = menu->prompt; + + /* For each property... */ + for (; prop; prop = prop->next) { + if (prop->menu != menu) + /* + * Two possibilities: + * + * 1. The property lacks dependencies + * and so isn't location-specific, + * e.g. an 'option' + * + * 2. The property belongs to a symbol + * defined in multiple locations and + * is from some other location. It + * will be handled there in that + * case. + * + * Skip the property. + */ + continue; + + /* + * Propagate parent dependencies to the + * property's condition, rewriting and + * simplifying expressions at the same time + */ + dep = rewrite_m(prop->visible.expr); + dep = expr_transform(dep); + dep = expr_alloc_and(basedep, dep); + dep = expr_eliminate_dups(dep); + prop->visible.expr = dep; + + /* + * Handle selects and implies, which modify the + * dependencies of the selected/implied symbol + */ + 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), 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), dep)); + } + } + } + + /* + * Recursively process children in the same fashion before + * moving on + */ + for (menu = parent->list; menu; menu = menu->next) + _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 + * all depend on sym, then the following menu structure is + * created: + * + * sym + * +-A + * +-B + * +-C + * ... + * + * This also works recursively, giving the following structure + * if A is a symbol and B depends on A: + * + * sym + * +-A + * | +-B + * +-C + * ... + */ + + basedep = parent->prompt ? parent->prompt->visible.expr : NULL; + basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no); + basedep = expr_eliminate_dups(expr_transform(basedep)); + + /* Examine consecutive elements after sym */ + last_menu = NULL; + for (menu = parent->next; menu; menu = menu->next) { + dep = menu->prompt ? menu->prompt->visible.expr : menu->dep; + if (!expr_contains_symbol(dep, sym)) + /* No dependency, quit */ + break; + /* + * Note that it's actually possible to depend on both + * 'SYM!=y' and 'SYM=y', so we need to first check if + * it's a positive dependency before checking if it's + * a negative dependency. See example: + * 'SFC && MTD && !(SFC=y && MTD=m)' + */ + if (expr_depends_symbol(dep, sym)) + /* Absolute dependency, put in submenu */ + goto next; + if (expr_contains_symbol_negated(dep, sym)) + /* Negative dependency, quit */ + break; + + /* + * Also consider it a dependency on sym if our + * dependencies contain sym and are a "superset" of + * sym's dependencies, e.g. '(sym || Q) && R' when sym + * depends on R. + * + * Note that 'R' might be from an enclosing menu or if, + * making this a more common case than it might seem. + */ + dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no); + dep = expr_eliminate_dups(expr_transform(dep)); + dep2 = basedep; + expr_eliminate_eq(&dep, &dep2); + if (!expr_is_yes(dep2)) { + /* Not superset, quit */ + break; + } + /* Superset, put in submenu */ + next: + _menu_finalize(menu, false); + menu->parent = parent; + last_menu = menu; + } + if (last_menu) { + parent->list = parent->next; + parent->next = last_menu->next; + last_menu->next = NULL; + } + + sym->dir_dep.expr = expr_alloc_or(sym->dir_dep.expr, parent->dep); + } + for (menu = parent->list; menu; menu = menu->next) { + /* + * This code serves two purposes: + * + * (1) Flattening 'if' blocks, which do not specify a submenu + * and only add dependencies. + * + * (Automatic submenu creation might still create a submenu + * from an 'if' before this code runs.) + * + * (2) "Undoing" any automatic submenus created earlier below + * promptless symbols. + * + * Before: + * + * A + * if ... (or promptless symbol) + * +-B + * +-C + * D + * + * After: + * + * A + * if ... (or promptless symbol) + * B + * C + * D + */ + if (menu->list && (!menu->prompt || !menu->prompt->text)) { + for (last_menu = menu->list; ; last_menu = last_menu->next) { + last_menu->parent = parent; + if (!last_menu->next) + break; + } + last_menu->next = menu->next; + menu->next = menu->list; + menu->list = NULL; + } + } + + if (sym && !(sym->flags & SYMBOL_WARNED)) { + if (sym->type == S_UNKNOWN) + menu_warn(parent, "config symbol defined without type"); + + /* Check properties connected to this symbol */ + sym_check_prop(sym); + sym->flags |= SYMBOL_WARNED; + } +} + +void menu_finalize(void) +{ + _menu_finalize(&rootmenu, false); +} + +bool menu_has_prompt(const struct menu *menu) +{ + if (!menu->prompt) + return false; + return true; +} + +/* + * Determine if a menu is empty. + * A menu is considered empty if it contains no or only + * invisible entries. + */ +bool menu_is_empty(struct menu *menu) +{ + struct menu *child; + + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child)) + return(false); + } + return(true); +} + +bool menu_is_visible(struct menu *menu) +{ + struct menu *child; + struct symbol *sym; + tristate visible; + + if (!menu->prompt) + return false; + + if (menu->visibility) { + if (expr_calc_value(menu->visibility) == no) + return false; + } + + sym = menu->sym; + if (sym) { + sym_calc_value(sym); + visible = menu->prompt->visible.tri; + } else + visible = menu->prompt->visible.tri = expr_calc_value(menu->prompt->visible.expr); + + if (visible != no) + return true; + + if (!sym || sym_get_tristate_value(menu->sym) == no) + return false; + + for (child = menu->list; child; child = child->next) + if (menu_is_visible(child)) + return true; + + return false; +} + +const char *menu_get_prompt(const struct menu *menu) +{ + if (menu->prompt) + return menu->prompt->text; + else if (menu->sym) + return menu->sym->name; + return NULL; +} + +/** + * menu_get_parent_menu - return the parent menu or NULL + * @menu: pointer to the menu + * return: the parent menu, or NULL if there is no parent. + */ +struct menu *menu_get_parent_menu(struct menu *menu) +{ + for (menu = menu->parent; menu; menu = menu->parent) + if (menu->type == M_MENU) + return menu; + + return NULL; +} + +/** + * menu_get_menu_or_parent_menu - return the parent menu or the menu itself + * @menu: pointer to the menu + * return: the parent menu. If the given argument is already a menu, return + * itself. + */ +struct menu *menu_get_menu_or_parent_menu(struct menu *menu) +{ + enum prop_type type; + + for (; menu != &rootmenu; menu = menu->parent) { + type = menu->prompt ? menu->prompt->type : 0; + if (type == P_MENU) + break; + } + return 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, const struct expr *expr, + const char *prefix) +{ + if (!expr_is_yes(expr)) { + str_append(r, prefix); + expr_gstr_print(expr, r); + str_append(r, "\n"); + } +} + +int __attribute__((weak)) get_jump_key_char(void) +{ + return -1; +} + +static void get_prompt_str(struct gstr *r, struct property *prop, + struct list_head *head) +{ + int i, j; + struct menu *submenu[8], *menu, *location = NULL; + struct jump_key *jump = NULL; + + str_printf(r, " Prompt: %s\n", prop->text); + + get_dep_str(r, prop->menu->dep, " Depends on: "); + /* + * Most prompts in Linux have visibility that exactly matches their + * dependencies. For these, we print only the dependencies to improve + * readability. However, prompts with inline "if" expressions and + * prompts with a parent that has a "visible if" expression have + * differing dependencies and visibility. In these rare cases, we + * print both. + */ + if (!expr_eq(prop->menu->dep, prop->visible.expr)) + get_dep_str(r, prop->visible.expr, " Visible if: "); + + menu = prop->menu; + for (i = 0; menu != &rootmenu && i < 8; menu = menu->parent) { + submenu[i++] = menu; + if (location == NULL && menu_is_visible(menu)) + location = menu; + } + if (head && location) { + jump = xmalloc(sizeof(struct jump_key)); + jump->target = location; + list_add_tail(&jump->entries, head); + } + + str_printf(r, " Location:\n"); + for (j = 0; --i >= 0; j++) { + int jk = -1; + int indent = 2 * j + 4; + + menu = submenu[i]; + if (jump && menu == location) { + jump->offset = strlen(r->s); + jk = get_jump_key_char(); + } + + if (jk >= 0) { + str_printf(r, "(%c)", jk); + indent -= 3; + } + + str_printf(r, "%*c-> %s", indent, ' ', menu_get_prompt(menu)); + if (menu->sym) { + str_printf(r, " (%s [=%s])", menu->sym->name ? + menu->sym->name : "<choice>", + sym_get_string_value(menu->sym)); + } + str_append(r, "\n"); + } +} + +static void get_symbol_props_str(struct gstr *r, struct symbol *sym, + enum prop_type tok, const char *prefix) +{ + bool hit = false; + struct property *prop; + + for_all_properties(sym, prop, tok) { + if (!hit) { + str_append(r, prefix); + hit = true; + } else + str_printf(r, " && "); + expr_gstr_print(prop->expr, r); + } + if (hit) + str_append(r, "\n"); +} + +/* + * head is optional and may be NULL + */ +static void get_symbol_str(struct gstr *r, struct symbol *sym, + struct list_head *head) +{ + struct property *prop; + struct menu *menu; + + if (sym && sym->name) { + str_printf(r, "Symbol: %s [=%s]\n", sym->name, + sym_get_string_value(sym)); + str_printf(r, "Type : %s\n", sym_type_name(sym->type)); + if (sym->type == S_INT || sym->type == S_HEX) { + prop = sym_get_range_prop(sym); + if (prop) { + str_printf(r, "Range : "); + expr_gstr_print(prop->expr, r); + str_append(r, "\n"); + } + } + } + + /* Print the definitions with prompts before the ones without */ + list_for_each_entry(menu, &sym->menus, link) { + if (menu->prompt) { + get_def_str(r, menu); + get_prompt_str(r, menu->prompt, head); + } + } + + list_for_each_entry(menu, &sym->menus, link) { + if (!menu->prompt) { + get_def_str(r, menu); + get_dep_str(r, menu->dep, " Depends on: "); + } + } + + get_symbol_props_str(r, sym, P_SELECT, "Selects: "); + if (sym->rev_dep.expr) { + expr_gstr_print_revdep(sym->rev_dep.expr, r, yes, "Selected by [y]:\n"); + expr_gstr_print_revdep(sym->rev_dep.expr, r, mod, "Selected by [m]:\n"); + expr_gstr_print_revdep(sym->rev_dep.expr, r, no, "Selected by [n]:\n"); + } + + get_symbol_props_str(r, sym, P_IMPLY, "Implies: "); + if (sym->implied.expr) { + expr_gstr_print_revdep(sym->implied.expr, r, yes, "Implied by [y]:\n"); + expr_gstr_print_revdep(sym->implied.expr, r, mod, "Implied by [m]:\n"); + expr_gstr_print_revdep(sym->implied.expr, r, no, "Implied by [n]:\n"); + } + + str_append(r, "\n\n"); +} + +struct gstr get_relations_str(struct symbol **sym_arr, struct list_head *head) +{ + struct symbol *sym; + struct gstr res = str_new(); + int i; + + for (i = 0; sym_arr && (sym = sym_arr[i]); i++) + get_symbol_str(&res, sym, head); + if (!i) + str_append(&res, "No matches found.\n"); + return res; +} + + +void menu_get_ext_help(struct menu *menu, struct gstr *help) +{ + struct symbol *sym = menu->sym; + const char *help_text = nohelp_text; + + if (menu->help) { + if (sym->name) + str_printf(help, "%s%s:\n\n", CONFIG_, sym->name); + help_text = menu->help; + } + str_printf(help, "%s\n", help_text); + if (sym) + get_symbol_str(help, sym, NULL); +} + +/** + * menu_dump - dump all menu entries in a tree-like format + */ +void menu_dump(void) +{ + struct menu *menu = &rootmenu; + unsigned long long bits = 0; + int indent = 0; + + while (menu) { + + for (int i = indent - 1; i >= 0; i--) { + if (bits & (1ULL << i)) { + if (i > 0) + printf("| "); + else + printf("|-- "); + } else { + if (i > 0) + printf(" "); + else + printf("`-- "); + } + } + + switch (menu->type) { + case M_CHOICE: + printf("choice \"%s\"\n", menu->prompt->text); + break; + case M_COMMENT: + printf("comment \"%s\"\n", menu->prompt->text); + break; + case M_IF: + printf("if\n"); + break; + case M_MENU: + printf("menu \"%s\"", menu->prompt->text); + if (!menu->sym) { + printf("\n"); + break; + } + printf(" + "); + /* fallthrough */ + case M_NORMAL: + printf("symbol %s\n", menu->sym->name); + break; + } + if (menu->list) { + bits <<= 1; + menu = menu->list; + if (menu->next) + bits |= 1; + else + bits &= ~1; + indent++; + continue; + } + + while (menu && !menu->next) { + menu = menu->parent; + bits >>= 1; + indent--; + } + + if (menu) { + menu = menu->next; + if (menu->next) + bits |= 1; + else + bits &= ~1; + } + } +} diff --git a/scripts/kconfig/merge_config.sh b/scripts/kconfig/merge_config.sh new file mode 100755 index 000000000..ec242e03f --- /dev/null +++ b/scripts/kconfig/merge_config.sh @@ -0,0 +1,383 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# merge_config.sh - Takes a list of config fragment values, and merges +# them one by one. Provides warnings on overridden values, and specified +# values that did not make it to the resulting .config file (due to missed +# dependencies or config symbol removal). +# +# Portions reused from kconf_check and generate_cfg: +# http://git.yoctoproject.org/cgit/cgit.cgi/yocto-kernel-tools/tree/tools/kconf_check +# http://git.yoctoproject.org/cgit/cgit.cgi/yocto-kernel-tools/tree/tools/generate_cfg +# +# Copyright (c) 2009-2010 Wind River Systems, Inc. +# Copyright 2011 Linaro + +set -e + +clean_up() { + rm -f "$TMP_FILE" + rm -f "$TMP_FILE.new" +} + +usage() { + echo "Usage: $0 [OPTIONS] [CONFIG [...]]" + echo " -h display this help text" + echo " -m only merge the fragments, do not execute the make command" + echo " -n use allnoconfig instead of alldefconfig" + echo " -r list redundant entries when merging fragments" + echo " -y make builtin have precedence over modules" + echo " -O dir to put generated output files. Consider setting \$KCONFIG_CONFIG instead." + echo " -s strict mode. Fail if the fragment redefines any value." + echo " -Q disable warning messages for overridden options." + echo + echo "Used prefix: '$CONFIG_PREFIX'. You can redefine it with \$CONFIG_ environment variable." +} + +RUNMAKE=true +ALLTARGET=alldefconfig +WARNREDUN=false +BUILTIN=false +OUTPUT=. +STRICT=false +CONFIG_PREFIX=${CONFIG_-CONFIG_} +WARNOVERRIDE=echo + +if [ -z "$AWK" ]; then + AWK=awk +fi + +while true; do + case $1 in + "-n") + ALLTARGET=allnoconfig + shift + continue + ;; + "-m") + RUNMAKE=false + shift + continue + ;; + "-h") + usage + exit + ;; + "-r") + WARNREDUN=true + shift + continue + ;; + "-y") + BUILTIN=true + shift + continue + ;; + "-O") + if [ -d $2 ];then + OUTPUT=$(echo $2 | sed 's/\/*$//') + else + echo "output directory $2 does not exist" 1>&2 + exit 1 + fi + shift 2 + continue + ;; + "-s") + STRICT=true + shift + continue + ;; + "-Q") + WARNOVERRIDE=true + shift + continue + ;; + *) + break + ;; + esac +done + +if [ "$#" -lt 1 ] ; then + usage + exit +fi + +if [ -z "$KCONFIG_CONFIG" ]; then + if [ "$OUTPUT" != . ]; then + KCONFIG_CONFIG=$(readlink -m -- "$OUTPUT/.config") + else + KCONFIG_CONFIG=.config + fi +fi + +INITFILE=$1 +shift; + +if [ ! -r "$INITFILE" ]; then + echo "The base file '$INITFILE' does not exist. Creating one..." >&2 + touch "$INITFILE" +fi + +MERGE_LIST=$* + +TMP_FILE=$(mktemp --tmpdir="$OUTPUT" .tmp.config.XXXXXXXXXX) + +echo "Using $INITFILE as base" + +trap clean_up EXIT + +cat $INITFILE > $TMP_FILE + +PROCESSED_FILES="" + +# Merge files, printing warnings on overridden values +for ORIG_MERGE_FILE in $MERGE_LIST ; do + echo "Merging $ORIG_MERGE_FILE" + if [ ! -r "$ORIG_MERGE_FILE" ]; then + echo "The merge file '$ORIG_MERGE_FILE' does not exist. Exit." >&2 + exit 1 + fi + + # Check for duplicate input files + case " $PROCESSED_FILES " in + *" $ORIG_MERGE_FILE "*) + ${WARNOVERRIDE} "WARNING: Input file provided multiple times: $ORIG_MERGE_FILE" + ;; + esac + + # Use awk for single-pass processing instead of per-symbol grep/sed + if ! "$AWK" -v prefix="$CONFIG_PREFIX" \ + -v warnoverride="$WARNOVERRIDE" \ + -v strict="$STRICT" \ + -v outfile="$TMP_FILE.new" \ + -v builtin="$BUILTIN" \ + -v warnredun="$WARNREDUN" ' + BEGIN { + strict_violated = 0 + cfg_regex = "^" prefix "[a-zA-Z0-9_]+" + notset_regex = "^# " prefix "[a-zA-Z0-9_]+ is not set$" + } + + # Extract config name from a line, returns "" if not a config line + function get_cfg(line) { + if (match(line, cfg_regex)) { + return substr(line, RSTART, RLENGTH) + } else if (match(line, notset_regex)) { + # Extract CONFIG_FOO from "# CONFIG_FOO is not set" + sub(/^# /, "", line) + sub(/ is not set$/, "", line) + return line + } + return "" + } + + function warn_builtin(cfg, prev, new) { + if (warnoverride == "true") return + print cfg ": -y passed, will not demote y to m" + print "Previous value: " prev + print "New value: " new + print "" + } + + function warn_redefined(cfg, prev, new) { + if (warnoverride == "true") return + print "Value of " cfg " is redefined by fragment " mergefile ":" + print "Previous value: " prev + print "New value: " new + print "" + } + + function warn_redundant(cfg) { + if (warnredun != "true" || warnoverride == "true") return + print "Value of " cfg " is redundant by fragment " mergefile ":" + } + + # First pass: read merge file, store all lines and index + FILENAME == ARGV[1] { + mergefile = FILENAME + merge_lines[FNR] = $0 + merge_total = FNR + cfg = get_cfg($0) + if (cfg != "") { + merge_cfg[cfg] = $0 + merge_cfg_line[cfg] = FNR + } + next + } + + # Second pass: process base file (TMP_FILE) + FILENAME == ARGV[2] { + cfg = get_cfg($0) + + # Not a config or not in merge file - keep it + if (cfg == "" || !(cfg in merge_cfg)) { + print $0 >> outfile + next + } + + prev_val = $0 + new_val = merge_cfg[cfg] + + # BUILTIN: do not demote y to m + if (builtin == "true" && new_val ~ /=m$/ && prev_val ~ /=y$/) { + warn_builtin(cfg, prev_val, new_val) + print $0 >> outfile + skip_merge[merge_cfg_line[cfg]] = 1 + next + } + + # Values equal - redundant + if (prev_val == new_val) { + warn_redundant(cfg) + next + } + + # "=n" is the same as "is not set" + if (prev_val ~ /=n$/ && new_val ~ / is not set$/) { + print $0 >> outfile + next + } + + # Values differ - redefined + warn_redefined(cfg, prev_val, new_val) + if (strict == "true") { + strict_violated = 1 + } + } + + END { + # Newline in case base file lacks trailing newline + print "" >> outfile + # Append merge file, skipping lines marked for builtin preservation + for (i = 1; i <= merge_total; i++) { + if (!(i in skip_merge)) { + print merge_lines[i] >> outfile + } + } + if (strict_violated) { + exit 1 + } + }' \ + "$ORIG_MERGE_FILE" "$TMP_FILE"; then + # awk exited non-zero, strict mode was violated + STRICT_MODE_VIOLATED=true + fi + mv "$TMP_FILE.new" "$TMP_FILE" + PROCESSED_FILES="$PROCESSED_FILES $ORIG_MERGE_FILE" +done +if [ "$STRICT_MODE_VIOLATED" = "true" ]; then + echo "The fragment redefined a value and strict mode had been passed." + exit 1 +fi + +if [ "$RUNMAKE" = "false" ]; then + cp -T -- "$TMP_FILE" "$KCONFIG_CONFIG" + echo "#" + echo "# merged configuration written to $KCONFIG_CONFIG (needs make)" + echo "#" + exit +fi + +# If we have an output dir, setup the O= argument, otherwise leave +# it blank, since O=. will create an unnecessary ./source softlink +OUTPUT_ARG="" +if [ "$OUTPUT" != "." ] ; then + OUTPUT_ARG="O=$OUTPUT" +fi + + +# Use the merged file as the starting point for: +# alldefconfig: Fills in any missing symbols with Kconfig default +# allnoconfig: Fills in any missing symbols with # CONFIG_* is not set +make KCONFIG_ALLCONFIG=$TMP_FILE $OUTPUT_ARG $ALLTARGET + +# Check all specified config values took effect (might have missed-dependency issues) +if ! "$AWK" -v prefix="$CONFIG_PREFIX" \ + -v warnoverride="$WARNOVERRIDE" \ + -v strict="$STRICT" \ + -v warnredun="$WARNREDUN" ' +BEGIN { + strict_violated = 0 + cfg_regex = "^" prefix "[a-zA-Z0-9_]+" + notset_regex = "^# " prefix "[a-zA-Z0-9_]+ is not set$" +} + +# Extract config name from a line, returns "" if not a config line +function get_cfg(line) { + if (match(line, cfg_regex)) { + return substr(line, RSTART, RLENGTH) + } else if (match(line, notset_regex)) { + # Extract CONFIG_FOO from "# CONFIG_FOO is not set" + sub(/^# /, "", line) + sub(/ is not set$/, "", line) + return line + } + return "" +} + +function warn_mismatch(cfg, merged, final) { + if (warnredun == "true") return + if (final == "" && !(merged ~ / is not set$/ || merged ~ /=n$/)) { + print "WARNING: Value requested for " cfg " not in final .config" + print "Requested value: " merged + print "Actual value: " final + } else if (final == "" && merged ~ / is not set$/) { + # not set, pass + } else if (merged == "" && final != "") { + print "WARNING: " cfg " not in merged config but added in final .config:" + print "Requested value: " merged + print "Actual value: " final + } else { + print "WARNING: " cfg " differs:" + print "Requested value: " merged + print "Actual value: " final + } +} + +# First pass: read effective config file, store all lines +FILENAME == ARGV[1] { + cfg = get_cfg($0) + if (cfg != "") { + config_cfg[cfg] = $0 + } + next +} + +# Second pass: process merged config and compare against effective config +{ + cfg = get_cfg($0) + if (cfg == "") next + + # strip trailing comment + sub(/[[:space:]]+#.*/, "", $0) + merged_val = $0 + final_val = config_cfg[cfg] + + if (merged_val == final_val) next + + if (merged_val ~ /=n$/ && final_val ~ / is not set$/) next + if (merged_val ~ /=n$/ && final_val == "") next + + warn_mismatch(cfg, merged_val, final_val) + + if (strict == "true") { + strict_violated = 1 + } +} + +END { + if (strict_violated) { + exit 1 + } +}' \ +"$KCONFIG_CONFIG" "$TMP_FILE"; then + # awk exited non-zero, strict mode was violated + STRICT_MODE_VIOLATED=true +fi + +if [ "$STRICT" = "true" ] && [ "$STRICT_MODE_VIOLATED" = "true" ]; then + echo "Requested and effective config differ" + exit 1 +fi diff --git a/scripts/kconfig/mnconf-common.c b/scripts/kconfig/mnconf-common.c new file mode 100644 index 000000000..8e24b0712 --- /dev/null +++ b/scripts/kconfig/mnconf-common.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <list.h> +#include "expr.h" +#include "mnconf-common.h" + +int jump_key_char; + +int next_jump_key(int key) +{ + if (key < '1' || key > '9') + return '1'; + + key++; + + if (key > '9') + key = '1'; + + return key; +} + +int handle_search_keys(int key, size_t start, size_t end, void *_data) +{ + struct search_data *data = _data; + struct jump_key *pos; + int index = 0; + + if (key < '1' || key > '9') + return 0; + + list_for_each_entry(pos, data->head, entries) { + index = next_jump_key(index); + + if (pos->offset < start) + continue; + + if (pos->offset >= end) + break; + + if (key == index) { + data->target = pos->target; + return 1; + } + } + + return 0; +} + +int get_jump_key_char(void) +{ + jump_key_char = next_jump_key(jump_key_char); + + return jump_key_char; +} diff --git a/scripts/kconfig/mnconf-common.h b/scripts/kconfig/mnconf-common.h new file mode 100644 index 000000000..53bd7292e --- /dev/null +++ b/scripts/kconfig/mnconf-common.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef MNCONF_COMMON_H +#define MNCONF_COMMON_H + +#include <stddef.h> + +#include <list_types.h> + +struct search_data { + struct list_head *head; + struct menu *target; +}; + +extern int jump_key_char; + +int next_jump_key(int key); +int handle_search_keys(int key, size_t start, size_t end, void *_data); +int get_jump_key_char(void); + +#endif /* MNCONF_COMMON_H */ diff --git a/scripts/kconfig/nconf-cfg.sh b/scripts/kconfig/nconf-cfg.sh new file mode 100755 index 000000000..4d08453f9 --- /dev/null +++ b/scripts/kconfig/nconf-cfg.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +set -eu + +cflags=$1 +libs=$2 + +# Keep library order for static linking (HOSTCC='cc -static') +PKG="menuw panelw ncursesw" +PKG2="menu panel ncurses" + +if [ -n "$(command -v ${HOSTPKG_CONFIG})" ]; then + if ${HOSTPKG_CONFIG} --exists $PKG; then + ${HOSTPKG_CONFIG} --cflags ${PKG} > ${cflags} + ${HOSTPKG_CONFIG} --libs ${PKG} > ${libs} + exit 0 + fi + + if ${HOSTPKG_CONFIG} --exists $PKG2; then + ${HOSTPKG_CONFIG} --cflags ${PKG2} > ${cflags} + ${HOSTPKG_CONFIG} --libs ${PKG2} > ${libs} + exit 0 + fi +fi + +# Check the default paths in case pkg-config is not installed. +# (Even if it is installed, some distributions such as openSUSE cannot +# find ncurses by pkg-config.) +if [ -f /usr/include/ncursesw/ncurses.h ]; then + echo -D_GNU_SOURCE -I/usr/include/ncursesw > ${cflags} + echo -lmenuw -lpanelw -lncursesw > ${libs} + exit 0 +fi + +if [ -f /usr/include/ncurses/ncurses.h ]; then + echo -D_GNU_SOURCE -I/usr/include/ncurses > ${cflags} + echo -lmenu -lpanel -lncurses > ${libs} + exit 0 +fi + +if [ -f /usr/include/ncurses.h ]; then + echo -D_GNU_SOURCE > ${cflags} + echo -lmenu -lpanel -lncurses > ${libs} + exit 0 +fi + +echo >&2 "*" +echo >&2 "* Unable to find the ncurses package." +echo >&2 "* Install ncurses (ncurses-devel or libncurses-dev" +echo >&2 "* depending on your distribution)." +echo >&2 "*" +echo >&2 "* You may also need to install ${HOSTPKG_CONFIG} to find the" +echo >&2 "* ncurses installed in a non-default location." +echo >&2 "*" +exit 1 diff --git a/scripts/kconfig/nconf.c b/scripts/kconfig/nconf.c new file mode 100644 index 000000000..521700ed7 --- /dev/null +++ b/scripts/kconfig/nconf.c @@ -0,0 +1,1560 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2008 Nir Tzachar <nir.tzachar@gmail.com> + * + * Derived from menuconfig. + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include <locale.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> + +#include <list.h> +#include <xalloc.h> +#include "lkc.h" +#include "mnconf-common.h" +#include "nconf.h" +#include <ctype.h> + +static const char nconf_global_help[] = +"Help windows\n" +"------------\n" +"o Global help: Unless in a data entry window, pressing <F1> will give \n" +" you the global help window, which you are just reading.\n" +"\n" +"o A short version of the global help is available by pressing <F3>.\n" +"\n" +"o Local help: To get help related to the current menu entry, use any\n" +" of <?> <h>, or if in a data entry window then press <F1>.\n" +"\n" +"\n" +"Menu entries\n" +"------------\n" +"This interface lets you select features and parameters for the kernel\n" +"build. Kernel features can either be built-in, modularized, or removed.\n" +"Parameters must be entered as text or decimal or hexadecimal numbers.\n" +"\n" +"Menu entries beginning with following braces represent features that\n" +" [ ] can be built in or removed\n" +" < > can be built in, modularized or removed\n" +" { } can be built in or modularized, are selected by another feature\n" +" - - are selected by another feature\n" +" XXX cannot be selected. Symbol Info <F2> tells you why.\n" +"*, M or whitespace inside braces means to build in, build as a module\n" +"or to exclude the feature respectively.\n" +"\n" +"To change any of these features, highlight it with the movement keys\n" +"listed below and press <y> to build it in, <m> to make it a module or\n" +"<n> to remove it. You may press the <Space> key to cycle through the\n" +"available options.\n" +"\n" +"A trailing \"--->\" designates a submenu, a trailing \"----\" an\n" +"empty submenu.\n" +"\n" +"Menu navigation keys\n" +"----------------------------------------------------------------------\n" +"Linewise up <Up> <k>\n" +"Linewise down <Down> <j>\n" +"Pagewise up <Page Up>\n" +"Pagewise down <Page Down>\n" +"First entry <Home>\n" +"Last entry <End>\n" +"Enter a submenu <Right> <Enter>\n" +"Go back to parent menu <Left> <Esc> <F5>\n" +"Close a help window <Enter> <Esc> <F5>\n" +"Close entry window, apply <Enter>\n" +"Close entry window, forget <Esc> <F5>\n" +"Start incremental, case-insensitive search for STRING in menu entries,\n" +" no regex support, STRING is displayed in upper left corner\n" +" </>STRING\n" +" Remove last character <Backspace>\n" +" Jump to next hit <Down>\n" +" Jump to previous hit <Up>\n" +"Exit menu search mode </> <Esc>\n" +"Search for configuration variables with or without leading CONFIG_\n" +" <F8>RegExpr<Enter>\n" +"Verbose search help <F8><F1>\n" +"----------------------------------------------------------------------\n" +"\n" +"Unless in a data entry window, key <1> may be used instead of <F1>,\n" +"<2> instead of <F2>, etc.\n" +"\n" +"\n" +"Radiolist (Choice list)\n" +"-----------------------\n" +"Use the movement keys listed above to select the option you wish to set\n" +"and press <Space>.\n" +"\n" +"\n" +"Data entry\n" +"----------\n" +"Enter the requested information and press <Enter>. Hexadecimal values\n" +"may be entered without the \"0x\" prefix.\n" +"\n" +"\n" +"Text Box (Help Window)\n" +"----------------------\n" +"Use movement keys as listed in table above.\n" +"\n" +"Press any of <Enter> <Esc> <q> <F5> <F9> to exit.\n" +"\n" +"\n" +"Alternate configuration files\n" +"-----------------------------\n" +"nconfig supports switching between different configurations.\n" +"Press <F6> to save your current configuration. Press <F7> and enter\n" +"a file name to load a previously saved configuration.\n" +"\n" +"\n" +"Terminal configuration\n" +"----------------------\n" +"If you use nconfig in a xterm window, make sure your TERM environment\n" +"variable specifies a terminal configuration which supports at least\n" +"16 colors. Otherwise nconfig will look rather bad.\n" +"\n" +"If the \"stty size\" command reports the current terminalsize correctly,\n" +"nconfig will adapt to sizes larger than the traditional 80x25 \"standard\"\n" +"and display longer menus properly.\n" +"\n" +"\n" +"Single menu mode\n" +"----------------\n" +"If you prefer to have all of the menu entries listed in a single menu,\n" +"rather than the default multimenu hierarchy, run nconfig with\n" +"NCONFIG_MODE environment variable set to single_menu. Example:\n" +"\n" +"make NCONFIG_MODE=single_menu nconfig\n" +"\n" +"<Enter> will then unfold the appropriate category, or fold it if it\n" +"is already unfolded. Folded menu entries will be designated by a\n" +"leading \"++>\" and unfolded entries by a leading \"-->\".\n" +"\n" +"Note that this mode can eventually be a little more CPU expensive than\n" +"the default mode, especially with a larger number of unfolded submenus.\n" +"\n", +menu_no_f_instructions[] = +"Legend: [*] built-in [ ] excluded <M> module < > module capable.\n" +"Submenus are designated by a trailing \"--->\", empty ones by \"----\".\n" +"\n" +"Use the following keys to navigate the menus:\n" +"Move up or down with <Up> and <Down>.\n" +"Enter a submenu with <Enter> or <Right>.\n" +"Exit a submenu to its parent menu with <Esc> or <Left>.\n" +"Pressing <y> includes, <n> excludes, <m> modularizes features.\n" +"Pressing <Space> cycles through the available options.\n" +"To search for menu entries press </>.\n" +"<Esc> always leaves the current window.\n" +"\n" +"You do not have function keys support.\n" +"Press <1> instead of <F1>, <2> instead of <F2>, etc.\n" +"For verbose global help use key <1>.\n" +"For help related to the current menu entry press <?> or <h>.\n", +menu_instructions[] = +"Legend: [*] built-in [ ] excluded <M> module < > module capable.\n" +"Submenus are designated by a trailing \"--->\", empty ones by \"----\".\n" +"\n" +"Use the following keys to navigate the menus:\n" +"Move up or down with <Up> or <Down>.\n" +"Enter a submenu with <Enter> or <Right>.\n" +"Exit a submenu to its parent menu with <Esc> or <Left>.\n" +"Pressing <y> includes, <n> excludes, <m> modularizes features.\n" +"Pressing <Space> cycles through the available options.\n" +"To search for menu entries press </>.\n" +"<Esc> always leaves the current window.\n" +"\n" +"Pressing <1> may be used instead of <F1>, <2> instead of <F2>, etc.\n" +"For verbose global help press <F1>.\n" +"For help related to the current menu entry press <?> or <h>.\n", +radiolist_instructions[] = +"Press <Up>, <Down>, <Home> or <End> to navigate a radiolist, select\n" +"with <Space>.\n" +"For help related to the current entry press <?> or <h>.\n" +"For global help press <F1>.\n", +inputbox_instructions_int[] = +"Please enter a decimal value.\n" +"Fractions will not be accepted.\n" +"Press <Enter> to apply, <Esc> to cancel.", +inputbox_instructions_hex[] = +"Please enter a hexadecimal value.\n" +"Press <Enter> to apply, <Esc> to cancel.", +inputbox_instructions_string[] = +"Please enter a string value.\n" +"Press <Enter> to apply, <Esc> to cancel.", +setmod_text[] = +"This feature depends on another feature which has been configured as a\n" +"module. As a result, the current feature will be built as a module too.", +load_config_text[] = +"Enter the name of the configuration file you wish to load.\n" +"Accept the name shown to restore the configuration you last\n" +"retrieved. Leave empty to abort.", +load_config_help[] = +"For various reasons, one may wish to keep several different\n" +"configurations available on a single machine.\n" +"\n" +"If you have saved a previous configuration in a file other than the\n" +"default one, entering its name here will allow you to load and modify\n" +"that configuration.\n" +"\n" +"Leave empty to abort.\n", +save_config_text[] = +"Enter a filename to which this configuration should be saved\n" +"as an alternate. Leave empty to abort.", +save_config_help[] = +"For various reasons, one may wish to keep several different\n" +"configurations available on a single machine.\n" +"\n" +"Entering a file name here will allow you to later retrieve, modify\n" +"and use the current configuration as an alternate to whatever\n" +"configuration options you have selected at that time.\n" +"\n" +"Leave empty to abort.\n", +search_help[] = +"Search for symbols (configuration variable names CONFIG_*) and display\n" +"their relations. Regular expressions are supported.\n" +"Example: Search for \"^FOO\".\n" +"Result:\n" +"-----------------------------------------------------------------\n" +"Symbol: FOO [ = m]\n" +"Prompt: Foo bus is used to drive the bar HW\n" +"Defined at drivers/pci/Kconfig:47\n" +"Depends on: X86_LOCAL_APIC && X86_IO_APIC\n" +"Location:\n" +" -> Bus options (PCI, PCMCIA, EISA, ISA)\n" +" -> PCI support (PCI [ = y])\n" +"(1) -> PCI access mode (<choice> [ = y])\n" +"Selects: LIBCRC32\n" +"Selected by: BAR\n" +"-----------------------------------------------------------------\n" +"o The line 'Prompt:' shows the text displayed for this symbol in\n" +" the menu hierarchy.\n" +"o The 'Defined at' line tells at what file / line number the symbol is\n" +" defined.\n" +"o The 'Depends on:' line lists symbols that need to be defined for\n" +" this symbol to be visible and selectable in the menu.\n" +"o The 'Location:' lines tell, where in the menu structure this symbol\n" +" is located.\n" +" A location followed by a [ = y] indicates that this is\n" +" a selectable menu item, and the current value is displayed inside\n" +" brackets.\n" +" Press the key in the (#) prefix to jump directly to that\n" +" location. You will be returned to the current search results\n" +" after exiting this new menu.\n" +"o The 'Selects:' line tells, what symbol will be automatically selected\n" +" if this symbol is selected (y or m).\n" +"o The 'Selected by' line tells what symbol has selected this symbol.\n" +"\n" +"Only relevant lines are shown.\n" +"\n\n" +"Search examples:\n" +"USB => find all symbols containing USB\n" +"^USB => find all symbols starting with USB\n" +"USB$ => find all symbols ending with USB\n" +"\n"; + +struct mitem { + char str[256]; + char tag; + void *usrptr; + int is_visible; +}; + +#define MAX_MENU_ITEMS 4096 +static int show_all_items; +static int indent; +static struct menu *current_menu; +static int child_count; +static int single_menu_mode; +/* the window in which all information appears */ +static WINDOW *main_window; +/* the largest size of the menu window */ +static int mwin_max_lines; +static int mwin_max_cols; +/* the window in which we show option buttons */ +static MENU *curses_menu; +static ITEM *curses_menu_items[MAX_MENU_ITEMS]; +static struct mitem k_menu_items[MAX_MENU_ITEMS]; +static unsigned int items_num; +static int global_exit; +/* the currently selected button */ +static const char *current_instructions = menu_instructions; + +static char *dialog_input_result; +static int dialog_input_result_len; + +static void selected_conf(struct menu *menu, struct menu *active_menu); +static void conf(struct menu *menu); +static void conf_choice(struct menu *menu); +static void conf_string(struct menu *menu); +static void conf_load(void); +static void conf_save(void); +static void show_help(struct menu *menu); +static int do_exit(void); +static void setup_windows(void); +static void search_conf(void); + +typedef void (*function_key_handler_t)(int *key, struct menu *menu); +static void handle_f1(int *key, struct menu *current_item); +static void handle_f2(int *key, struct menu *current_item); +static void handle_f3(int *key, struct menu *current_item); +static void handle_f4(int *key, struct menu *current_item); +static void handle_f5(int *key, struct menu *current_item); +static void handle_f6(int *key, struct menu *current_item); +static void handle_f7(int *key, struct menu *current_item); +static void handle_f8(int *key, struct menu *current_item); +static void handle_f9(int *key, struct menu *current_item); + +struct function_keys { + const char *key_str; + const char *func; + function_key key; + function_key_handler_t handler; +}; + +static const int function_keys_num = 9; +static struct function_keys function_keys[] = { + { + .key_str = "F1", + .func = "Help", + .key = F_HELP, + .handler = handle_f1, + }, + { + .key_str = "F2", + .func = "SymInfo", + .key = F_SYMBOL, + .handler = handle_f2, + }, + { + .key_str = "F3", + .func = "Help 2", + .key = F_INSTS, + .handler = handle_f3, + }, + { + .key_str = "F4", + .func = "ShowAll", + .key = F_CONF, + .handler = handle_f4, + }, + { + .key_str = "F5", + .func = "Back", + .key = F_BACK, + .handler = handle_f5, + }, + { + .key_str = "F6", + .func = "Save", + .key = F_SAVE, + .handler = handle_f6, + }, + { + .key_str = "F7", + .func = "Load", + .key = F_LOAD, + .handler = handle_f7, + }, + { + .key_str = "F8", + .func = "SymSearch", + .key = F_SEARCH, + .handler = handle_f8, + }, + { + .key_str = "F9", + .func = "Exit", + .key = F_EXIT, + .handler = handle_f9, + }, +}; + +static void print_function_line(void) +{ + int i; + int offset = 1; + const int skip = 1; + int lines = getmaxy(stdscr); + + for (i = 0; i < function_keys_num; i++) { + wattrset(main_window, attr_function_highlight); + mvwprintw(main_window, lines-3, offset, + "%s", + function_keys[i].key_str); + wattrset(main_window, attr_function_text); + offset += strlen(function_keys[i].key_str); + mvwprintw(main_window, lines-3, + offset, "%s", + function_keys[i].func); + offset += strlen(function_keys[i].func) + skip; + } + wattrset(main_window, attr_normal); +} + +/* help */ +static void handle_f1(int *key, struct menu *current_item) +{ + show_scroll_win(main_window, + "Global help", nconf_global_help); + return; +} + +/* symbole help */ +static void handle_f2(int *key, struct menu *current_item) +{ + show_help(current_item); + return; +} + +/* instructions */ +static void handle_f3(int *key, struct menu *current_item) +{ + show_scroll_win(main_window, + "Short help", + current_instructions); + return; +} + +/* config */ +static void handle_f4(int *key, struct menu *current_item) +{ + int res = btn_dialog(main_window, + "Show all symbols?", + 2, + " <Show All> ", + "<Don't show all>"); + if (res == 0) + show_all_items = 1; + else if (res == 1) + show_all_items = 0; + + return; +} + +/* back */ +static void handle_f5(int *key, struct menu *current_item) +{ + *key = KEY_LEFT; + return; +} + +/* save */ +static void handle_f6(int *key, struct menu *current_item) +{ + conf_save(); + return; +} + +/* load */ +static void handle_f7(int *key, struct menu *current_item) +{ + conf_load(); + return; +} + +/* search */ +static void handle_f8(int *key, struct menu *current_item) +{ + search_conf(); + return; +} + +/* exit */ +static void handle_f9(int *key, struct menu *current_item) +{ + do_exit(); + return; +} + +/* return != 0 to indicate the key was handled */ +static int process_special_keys(int *key, struct menu *menu) +{ + int i; + + if (*key == KEY_RESIZE) { + setup_windows(); + return 1; + } + + for (i = 0; i < function_keys_num; i++) { + if (*key == KEY_F(function_keys[i].key) || + *key == '0' + function_keys[i].key){ + function_keys[i].handler(key, menu); + return 1; + } + } + + return 0; +} + +static void clean_items(void) +{ + int i; + for (i = 0; curses_menu_items[i]; i++) + free_item(curses_menu_items[i]); + bzero(curses_menu_items, sizeof(curses_menu_items)); + bzero(k_menu_items, sizeof(k_menu_items)); + items_num = 0; +} + +typedef enum {MATCH_TINKER_PATTERN_UP, MATCH_TINKER_PATTERN_DOWN, + FIND_NEXT_MATCH_DOWN, FIND_NEXT_MATCH_UP} match_f; + +/* return the index of the matched item, or -1 if no such item exists */ +static int get_mext_match(const char *match_str, match_f flag) +{ + int match_start, index; + + /* Do not search if the menu is empty (i.e. items_num == 0) */ + match_start = item_index(current_item(curses_menu)); + if (match_start == ERR) + return -1; + + if (flag == FIND_NEXT_MATCH_DOWN) + ++match_start; + else if (flag == FIND_NEXT_MATCH_UP) + --match_start; + + match_start = (match_start + items_num) % items_num; + index = match_start; + while (true) { + char *str = k_menu_items[index].str; + if (strcasestr(str, match_str) != NULL) + return index; + if (flag == FIND_NEXT_MATCH_UP || + flag == MATCH_TINKER_PATTERN_UP) + --index; + else + ++index; + index = (index + items_num) % items_num; + if (index == match_start) + return -1; + } +} + +/* Make a new item. */ +static void item_make(struct menu *menu, char tag, const char *fmt, ...) +{ + va_list ap; + + if (items_num > MAX_MENU_ITEMS-1) + return; + + bzero(&k_menu_items[items_num], sizeof(k_menu_items[0])); + k_menu_items[items_num].tag = tag; + k_menu_items[items_num].usrptr = menu; + if (menu != NULL) + k_menu_items[items_num].is_visible = + menu_is_visible(menu); + else + k_menu_items[items_num].is_visible = 1; + + va_start(ap, fmt); + vsnprintf(k_menu_items[items_num].str, + sizeof(k_menu_items[items_num].str), + fmt, ap); + va_end(ap); + + if (!k_menu_items[items_num].is_visible) + memcpy(k_menu_items[items_num].str, "XXX", 3); + + curses_menu_items[items_num] = new_item( + k_menu_items[items_num].str, + k_menu_items[items_num].str); + set_item_userptr(curses_menu_items[items_num], + &k_menu_items[items_num]); + /* + if (!k_menu_items[items_num].is_visible) + item_opts_off(curses_menu_items[items_num], O_SELECTABLE); + */ + + items_num++; + curses_menu_items[items_num] = NULL; +} + +/* very hackish. adds a string to the last item added */ +static void item_add_str(const char *fmt, ...) +{ + va_list ap; + int index = items_num-1; + char new_str[256]; + char tmp_str[256]; + + if (index < 0) + return; + + va_start(ap, fmt); + vsnprintf(new_str, sizeof(new_str), fmt, ap); + va_end(ap); + snprintf(tmp_str, sizeof(tmp_str), "%s%s", + k_menu_items[index].str, new_str); + strncpy(k_menu_items[index].str, + tmp_str, + sizeof(k_menu_items[index].str)); + + k_menu_items[index].str[sizeof(k_menu_items[index].str) - 1] = '\0'; + + free_item(curses_menu_items[index]); + curses_menu_items[index] = new_item( + k_menu_items[index].str, + k_menu_items[index].str); + set_item_userptr(curses_menu_items[index], + &k_menu_items[index]); +} + +/* get the tag of the currently selected item */ +static char item_tag(void) +{ + ITEM *cur; + struct mitem *mcur; + + cur = current_item(curses_menu); + if (cur == NULL) + return 0; + mcur = (struct mitem *) item_userptr(cur); + return mcur->tag; +} + +static int curses_item_index(void) +{ + return item_index(current_item(curses_menu)); +} + +static void *item_data(void) +{ + ITEM *cur; + struct mitem *mcur; + + cur = current_item(curses_menu); + if (!cur) + return NULL; + mcur = (struct mitem *) item_userptr(cur); + return mcur->usrptr; + +} + +static int item_is_tag(char tag) +{ + return item_tag() == tag; +} + +static char filename[PATH_MAX+1]; +static char menu_backtitle[PATH_MAX+128]; +static void set_config_filename(const char *config_filename) +{ + snprintf(menu_backtitle, sizeof(menu_backtitle), "%s - %s", + config_filename, rootmenu.prompt->text); + + snprintf(filename, sizeof(filename), "%s", config_filename); +} + +/* return = 0 means we are successful. + * -1 means go on doing what you were doing + */ +static int do_exit(void) +{ + int res; + if (!conf_get_changed()) { + global_exit = 1; + return 0; + } + res = btn_dialog(main_window, + "Do you wish to save your new configuration?\n" + "<ESC> to cancel and resume nconfig.", + 2, + " <save> ", + "<don't save>"); + if (res == KEY_EXIT) { + global_exit = 0; + return -1; + } + + /* if we got here, the user really wants to exit */ + switch (res) { + case 0: + res = conf_write(filename); + if (res) + btn_dialog( + main_window, + "Error during writing of configuration.\n" + "Your configuration changes were NOT saved.", + 1, + "<OK>"); + conf_write_autoconf(0); + break; + default: + btn_dialog( + main_window, + "Your configuration changes were NOT saved.", + 1, + "<OK>"); + break; + } + global_exit = 1; + return 0; +} + + +static void search_conf(void) +{ + struct symbol **sym_arr; + struct gstr res; + struct gstr title; + char *dialog_input; + int dres, vscroll = 0, hscroll = 0; + bool again; + + title = str_new(); + str_printf( &title, "Enter (sub)string or regexp to search for " + "(with or without \"%s\")", CONFIG_); + +again: + dres = dialog_inputbox(main_window, + "Search Configuration Parameter", + str_get(&title), + "", &dialog_input_result, &dialog_input_result_len); + switch (dres) { + case 0: + break; + case 1: + show_scroll_win(main_window, + "Search Configuration", search_help); + goto again; + default: + str_free(&title); + return; + } + + /* strip the prefix if necessary */ + dialog_input = dialog_input_result; + if (strncasecmp(dialog_input_result, CONFIG_, strlen(CONFIG_)) == 0) + dialog_input += strlen(CONFIG_); + + sym_arr = sym_re_search(dialog_input); + + do { + LIST_HEAD(head); + struct search_data data = { + .head = &head, + .target = NULL, + }; + jump_key_char = 0; + res = get_relations_str(sym_arr, &head); + dres = show_scroll_win_ext(main_window, + "Search Results", str_get(&res), + &vscroll, &hscroll, + handle_search_keys, &data); + again = false; + if (dres >= '1' && dres <= '9') { + assert(data.target != NULL); + selected_conf(data.target->parent, data.target); + again = true; + } + str_free(&res); + } while (again); + free(sym_arr); + str_free(&title); +} + + +static void build_conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + int type, tmp, doint = 2; + tristate val; + char ch; + + if (!menu || (!show_all_items && !menu_is_visible(menu))) + return; + + sym = menu->sym; + prop = menu->prompt; + if (!sym) { + if (prop && menu != current_menu) { + const char *prompt = menu_get_prompt(menu); + enum prop_type ptype; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + switch (ptype) { + case P_MENU: + child_count++; + if (single_menu_mode) { + item_make(menu, 'm', + "%s%*c%s", + menu->data ? "-->" : "++>", + indent + 1, ' ', prompt); + } else + item_make(menu, 'm', + " %*c%s %s", + indent + 1, ' ', prompt, + menu_is_empty(menu) ? "----" : "--->"); + + if (single_menu_mode && menu->data) + goto conf_childs; + return; + case P_COMMENT: + if (prompt) { + child_count++; + item_make(menu, ':', + " %*c*** %s ***", + indent + 1, ' ', + prompt); + } + break; + default: + if (prompt) { + child_count++; + item_make(menu, ':', "---%*c%s", + indent + 1, ' ', + prompt); + } + } + } else + doint = 0; + goto conf_childs; + } + + type = sym_get_type(sym); + if (sym_is_choice(sym)) { + struct symbol *def_sym = sym_calc_choice(menu); + struct menu *def_menu = NULL; + + child_count++; + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child) && child->sym == def_sym) + def_menu = child; + } + + val = sym_get_tristate_value(sym); + item_make(menu, def_menu ? 't' : ':', " "); + + item_add_str("%*c%s", indent + 1, + ' ', menu_get_prompt(menu)); + if (def_menu) + item_add_str(" (%s) --->", menu_get_prompt(def_menu)); + return; + } else { + if (menu == current_menu) { + item_make(menu, ':', + "---%*c%s", indent + 1, + ' ', menu_get_prompt(menu)); + goto conf_childs; + } + child_count++; + val = sym_get_tristate_value(sym); + 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 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); + 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), + (sym_has_value(sym) || !sym_is_changeable(sym)) ? + "" : " (NEW)"); + if (menu->prompt && menu->prompt->type == P_MENU) { + item_add_str(" %s", menu_is_empty(menu) ? "----" : "--->"); + return; + } + } + +conf_childs: + indent += doint; + for (child = menu->list; child; child = child->next) + build_conf(child); + indent -= doint; +} + +static void reset_menu(void) +{ + unpost_menu(curses_menu); + clean_items(); +} + +/* adjust the menu to show this item. + * prefer not to scroll the menu if possible*/ +static void center_item(int selected_index, int *last_top_row) +{ + int toprow; + + set_top_row(curses_menu, *last_top_row); + toprow = top_row(curses_menu); + if (selected_index < toprow || + selected_index >= toprow+mwin_max_lines) { + toprow = max(selected_index-mwin_max_lines/2, 0); + if (toprow >= item_count(curses_menu)-mwin_max_lines) + toprow = item_count(curses_menu)-mwin_max_lines; + set_top_row(curses_menu, toprow); + } + set_current_item(curses_menu, + curses_menu_items[selected_index]); + *last_top_row = toprow; + post_menu(curses_menu); + refresh_all_windows(main_window); +} + +/* this function assumes reset_menu has been called before */ +static void show_menu(const char *prompt, const char *instructions, + int selected_index, int *last_top_row) +{ + int maxx, maxy; + WINDOW *menu_window; + + current_instructions = instructions; + + clear(); + print_in_middle(stdscr, 1, getmaxx(stdscr), + menu_backtitle, + attr_main_heading); + + wattrset(main_window, attr_main_menu_box); + box(main_window, 0, 0); + wattrset(main_window, attr_main_menu_heading); + mvwprintw(main_window, 0, 3, " %s ", prompt); + wattrset(main_window, attr_normal); + + set_menu_items(curses_menu, curses_menu_items); + + /* position the menu at the middle of the screen */ + scale_menu(curses_menu, &maxy, &maxx); + maxx = min(maxx, mwin_max_cols-2); + maxy = mwin_max_lines; + menu_window = derwin(main_window, + maxy, + maxx, + 2, + (mwin_max_cols-maxx)/2); + keypad(menu_window, TRUE); + set_menu_win(curses_menu, menu_window); + set_menu_sub(curses_menu, menu_window); + + /* must reassert this after changing items, otherwise returns to a + * default of 16 + */ + set_menu_format(curses_menu, maxy, 1); + center_item(selected_index, last_top_row); + set_menu_format(curses_menu, maxy, 1); + + print_function_line(); + + /* Post the menu */ + post_menu(curses_menu); + refresh_all_windows(main_window); +} + +static void adj_match_dir(match_f *match_direction) +{ + if (*match_direction == FIND_NEXT_MATCH_DOWN) + *match_direction = + MATCH_TINKER_PATTERN_DOWN; + else if (*match_direction == FIND_NEXT_MATCH_UP) + *match_direction = + MATCH_TINKER_PATTERN_UP; + /* else, do no change.. */ +} + +struct match_state +{ + int in_search; + match_f match_direction; + char pattern[256]; +}; + +/* Return 0 means I have handled the key. In such a case, ans should hold the + * item to center, or -1 otherwise. + * Else return -1 . + */ +static int do_match(int key, struct match_state *state, int *ans) +{ + char c = (char) key; + int terminate_search = 0; + *ans = -1; + if (key == '/' || (state->in_search && key == 27)) { + move(0, 0); + refresh(); + clrtoeol(); + state->in_search = 1-state->in_search; + bzero(state->pattern, sizeof(state->pattern)); + state->match_direction = MATCH_TINKER_PATTERN_DOWN; + return 0; + } else if (!state->in_search) + return 1; + + if (isalnum(c) || isgraph(c) || c == ' ') { + state->pattern[strlen(state->pattern)] = c; + state->pattern[strlen(state->pattern)] = '\0'; + adj_match_dir(&state->match_direction); + *ans = get_mext_match(state->pattern, + state->match_direction); + } else if (key == KEY_DOWN) { + state->match_direction = FIND_NEXT_MATCH_DOWN; + *ans = get_mext_match(state->pattern, + state->match_direction); + } else if (key == KEY_UP) { + state->match_direction = FIND_NEXT_MATCH_UP; + *ans = get_mext_match(state->pattern, + state->match_direction); + } else if (key == KEY_BACKSPACE || key == 8 || key == 127) { + state->pattern[strlen(state->pattern)-1] = '\0'; + adj_match_dir(&state->match_direction); + } else + terminate_search = 1; + + if (terminate_search) { + state->in_search = 0; + bzero(state->pattern, sizeof(state->pattern)); + move(0, 0); + refresh(); + clrtoeol(); + return -1; + } + return 0; +} + +static void conf(struct menu *menu) +{ + selected_conf(menu, NULL); +} + +static void selected_conf(struct menu *menu, struct menu *active_menu) +{ + struct menu *submenu = NULL; + struct symbol *sym; + int i, res; + int current_index = 0; + int last_top_row = 0; + struct match_state match_state = { + .in_search = 0, + .match_direction = MATCH_TINKER_PATTERN_DOWN, + .pattern = "", + }; + + while (!global_exit) { + reset_menu(); + current_menu = menu; + build_conf(menu); + if (!child_count) + break; + + if (active_menu != NULL) { + for (i = 0; i < items_num; i++) { + struct mitem *mcur; + + mcur = (struct mitem *) item_userptr(curses_menu_items[i]); + if ((struct menu *) mcur->usrptr == active_menu) { + current_index = i; + break; + } + } + active_menu = NULL; + } + + show_menu(menu_get_prompt(menu), menu_instructions, + current_index, &last_top_row); + keypad((menu_win(curses_menu)), TRUE); + while (!global_exit) { + if (match_state.in_search) { + mvprintw(0, 0, + "searching: %s", match_state.pattern); + clrtoeol(); + } + refresh_all_windows(main_window); + res = wgetch(menu_win(curses_menu)); + if (!res) + break; + if (do_match(res, &match_state, ¤t_index) == 0) { + if (current_index != -1) + center_item(current_index, + &last_top_row); + continue; + } + if (process_special_keys(&res, + (struct menu *) item_data())) + break; + switch (res) { + case KEY_DOWN: + case 'j': + menu_driver(curses_menu, REQ_DOWN_ITEM); + break; + case KEY_UP: + case 'k': + menu_driver(curses_menu, REQ_UP_ITEM); + break; + case KEY_NPAGE: + menu_driver(curses_menu, REQ_SCR_DPAGE); + break; + case KEY_PPAGE: + menu_driver(curses_menu, REQ_SCR_UPAGE); + break; + case KEY_HOME: + menu_driver(curses_menu, REQ_FIRST_ITEM); + break; + case KEY_END: + menu_driver(curses_menu, REQ_LAST_ITEM); + break; + case 'h': + case '?': + show_help((struct menu *) item_data()); + break; + } + if (res == 10 || res == 27 || + res == 32 || res == 'n' || res == 'y' || + res == KEY_LEFT || res == KEY_RIGHT || + res == 'm') + break; + refresh_all_windows(main_window); + } + + refresh_all_windows(main_window); + /* if ESC or left*/ + if (res == 27 || (menu != &rootmenu && res == KEY_LEFT)) + break; + + /* remember location in the menu */ + last_top_row = top_row(curses_menu); + current_index = curses_item_index(); + + if (!item_tag()) + continue; + + submenu = (struct menu *) item_data(); + if (!submenu || !menu_is_visible(submenu)) + continue; + sym = submenu->sym; + + switch (res) { + case ' ': + if (item_is_tag('t')) + sym_toggle_tristate_value(sym); + else if (item_is_tag('m')) + conf(submenu); + break; + case KEY_RIGHT: + case 10: /* ENTER WAS PRESSED */ + switch (item_tag()) { + case 'm': + if (single_menu_mode) + submenu->data = + (void *) (long) !submenu->data; + else + conf(submenu); + break; + case 't': + if (sym_is_choice(sym)) + conf_choice(submenu); + else if (submenu->prompt && + submenu->prompt->type == P_MENU) + conf(submenu); + else if (res == 10) + sym_toggle_tristate_value(sym); + break; + case 's': + conf_string(submenu); + break; + } + break; + case 'y': + if (item_is_tag('t')) { + if (sym_set_tristate_value(sym, yes)) + break; + if (sym_set_tristate_value(sym, mod)) + btn_dialog(main_window, setmod_text, 0); + } + break; + case 'n': + if (item_is_tag('t')) + sym_set_tristate_value(sym, no); + break; + case 'm': + if (item_is_tag('t')) + sym_set_tristate_value(sym, mod); + break; + } + } +} + +static void conf_message_callback(const char *s) +{ + btn_dialog(main_window, s, 1, "<OK>"); +} + +static void show_help(struct menu *menu) +{ + struct gstr help; + + if (!menu) + return; + + help = str_new(); + menu_get_ext_help(menu, &help); + show_scroll_win(main_window, menu_get_prompt(menu), str_get(&help)); + str_free(&help); +} + +static void conf_choice(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + struct menu *child = NULL; + struct symbol *active; + int selected_index = 0; + int last_top_row = 0; + int res, i = 0; + struct match_state match_state = { + .in_search = 0, + .match_direction = MATCH_TINKER_PATTERN_DOWN, + .pattern = "", + }; + + active = sym_calc_choice(menu); + /* this is mostly duplicated from the conf() function. */ + while (!global_exit) { + reset_menu(); + + for (i = 0, child = menu->list; child; child = child->next) { + if (!show_all_items && !menu_is_visible(child)) + continue; + + if (child->sym == sym_calc_choice(menu)) + item_make(child, ':', "<X> %s", + menu_get_prompt(child)); + else if (child->sym) + item_make(child, ':', " %s", + menu_get_prompt(child)); + else + item_make(child, ':', "*** %s ***", + menu_get_prompt(child)); + + if (child->sym == active){ + last_top_row = top_row(curses_menu); + selected_index = i; + } + i++; + } + show_menu(prompt ? prompt : "Choice Menu", + radiolist_instructions, + selected_index, + &last_top_row); + while (!global_exit) { + if (match_state.in_search) { + mvprintw(0, 0, "searching: %s", + match_state.pattern); + clrtoeol(); + } + refresh_all_windows(main_window); + res = wgetch(menu_win(curses_menu)); + if (!res) + break; + if (do_match(res, &match_state, &selected_index) == 0) { + if (selected_index != -1) + center_item(selected_index, + &last_top_row); + continue; + } + if (process_special_keys( + &res, + (struct menu *) item_data())) + break; + switch (res) { + case KEY_DOWN: + case 'j': + menu_driver(curses_menu, REQ_DOWN_ITEM); + break; + case KEY_UP: + case 'k': + menu_driver(curses_menu, REQ_UP_ITEM); + break; + case KEY_NPAGE: + menu_driver(curses_menu, REQ_SCR_DPAGE); + break; + case KEY_PPAGE: + menu_driver(curses_menu, REQ_SCR_UPAGE); + break; + case KEY_HOME: + menu_driver(curses_menu, REQ_FIRST_ITEM); + break; + case KEY_END: + menu_driver(curses_menu, REQ_LAST_ITEM); + break; + case 'h': + case '?': + show_help((struct menu *) item_data()); + break; + } + if (res == 10 || res == 27 || res == ' ' || + res == KEY_LEFT){ + break; + } + refresh_all_windows(main_window); + } + /* if ESC or left */ + if (res == 27 || res == KEY_LEFT) + break; + + child = item_data(); + if (!child || !menu_is_visible(child) || !child->sym) + continue; + switch (res) { + case ' ': + case 10: + case KEY_RIGHT: + choice_set_value(menu, child->sym); + return; + case 'h': + case '?': + show_help(child); + active = child->sym; + break; + case KEY_EXIT: + return; + } + } +} + +static void conf_string(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + + while (1) { + int res; + const char *heading; + + switch (sym_get_type(menu->sym)) { + case S_INT: + heading = inputbox_instructions_int; + break; + case S_HEX: + heading = inputbox_instructions_hex; + break; + case S_STRING: + heading = inputbox_instructions_string; + break; + default: + heading = "Internal nconf error!"; + } + res = dialog_inputbox(main_window, + prompt ? prompt : "Main Menu", + heading, + sym_get_string_value(menu->sym), + &dialog_input_result, + &dialog_input_result_len); + switch (res) { + case 0: + if (sym_set_string_value(menu->sym, + dialog_input_result)) + return; + btn_dialog(main_window, + "You have made an invalid entry.", 0); + break; + case 1: + show_help(menu); + break; + case KEY_EXIT: + return; + } + } +} + +static void conf_load(void) +{ + while (1) { + int res; + res = dialog_inputbox(main_window, + NULL, load_config_text, + filename, + &dialog_input_result, + &dialog_input_result_len); + switch (res) { + case 0: + if (!dialog_input_result[0]) + return; + if (!conf_read(dialog_input_result)) { + set_config_filename(dialog_input_result); + conf_set_changed(true); + return; + } + btn_dialog(main_window, "File does not exist!", 0); + break; + case 1: + show_scroll_win(main_window, + "Load Alternate Configuration", + load_config_help); + break; + case KEY_EXIT: + return; + } + } +} + +static void conf_save(void) +{ + while (1) { + int res; + res = dialog_inputbox(main_window, + NULL, save_config_text, + filename, + &dialog_input_result, + &dialog_input_result_len); + switch (res) { + case 0: + if (!dialog_input_result[0]) + return; + res = conf_write(dialog_input_result); + if (!res) { + set_config_filename(dialog_input_result); + return; + } + btn_dialog(main_window, "Can't create file!", + 1, "<OK>"); + break; + case 1: + show_scroll_win(main_window, + "Save Alternate Configuration", + save_config_help); + break; + case KEY_EXIT: + return; + } + } +} + +static void setup_windows(void) +{ + int lines, columns; + + getmaxyx(stdscr, lines, columns); + + if (main_window != NULL) + delwin(main_window); + + /* set up the menu and menu window */ + main_window = newwin(lines-2, columns-2, 2, 1); + keypad(main_window, TRUE); + mwin_max_lines = lines-7; + mwin_max_cols = columns-6; + + /* panels order is from bottom to top */ + new_panel(main_window); +} + +int main(int ac, char **av) +{ + int lines, columns; + char *mode; + + setlocale(LC_ALL, ""); + + if (ac > 1 && strcmp(av[1], "-s") == 0) { + /* Silence conf_read() until the real callback is set up */ + conf_set_message_callback(NULL); + av++; + } + conf_parse(av[1]); + conf_read(NULL); + + mode = getenv("NCONFIG_MODE"); + if (mode) { + if (!strcasecmp(mode, "single_menu")) + single_menu_mode = 1; + } + + /* Initialize curses */ + initscr(); + /* set color theme */ + set_colors(); + + cbreak(); + noecho(); + keypad(stdscr, TRUE); + curs_set(0); + + getmaxyx(stdscr, lines, columns); + if (columns < 75 || lines < 20) { + endwin(); + printf("Your terminal should have at " + "least 20 lines and 75 columns\n"); + return 1; + } + + notimeout(stdscr, FALSE); +#if NCURSES_REENTRANT + set_escdelay(1); +#else + ESCDELAY = 1; +#endif + + /* set btns menu */ + curses_menu = new_menu(curses_menu_items); + menu_opts_off(curses_menu, O_SHOWDESC); + menu_opts_on(curses_menu, O_SHOWMATCH); + menu_opts_on(curses_menu, O_ONEVALUE); + menu_opts_on(curses_menu, O_NONCYCLIC); + menu_opts_on(curses_menu, O_IGNORECASE); + set_menu_mark(curses_menu, " "); + set_menu_fore(curses_menu, attr_main_menu_fore); + set_menu_back(curses_menu, attr_main_menu_back); + set_menu_grey(curses_menu, attr_main_menu_grey); + + set_config_filename(conf_get_configname()); + setup_windows(); + + /* check for KEY_FUNC(1) */ + if (has_key(KEY_F(1)) == FALSE) { + show_scroll_win(main_window, + "Instructions", + menu_no_f_instructions); + } + + conf_set_message_callback(conf_message_callback); + /* do the work */ + while (!global_exit) { + conf(&rootmenu); + if (!global_exit && do_exit() == 0) + break; + } + /* ok, we are done */ + unpost_menu(curses_menu); + free_menu(curses_menu); + delwin(main_window); + clear(); + refresh(); + endwin(); + return 0; +} diff --git a/scripts/kconfig/nconf.gui.c b/scripts/kconfig/nconf.gui.c new file mode 100644 index 000000000..2d097bc7e --- /dev/null +++ b/scripts/kconfig/nconf.gui.c @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2008 Nir Tzachar <nir.tzachar@gmail.com> + * + * Derived from menuconfig. + */ +#include <xalloc.h> +#include "nconf.h" +#include "lkc.h" + +int attr_normal; +int attr_main_heading; +int attr_main_menu_box; +int attr_main_menu_fore; +int attr_main_menu_back; +int attr_main_menu_grey; +int attr_main_menu_heading; +int attr_scrollwin_text; +int attr_scrollwin_heading; +int attr_scrollwin_box; +int attr_dialog_text; +int attr_dialog_menu_fore; +int attr_dialog_menu_back; +int attr_dialog_box; +int attr_input_box; +int attr_input_heading; +int attr_input_text; +int attr_input_field; +int attr_function_text; +int attr_function_highlight; + +#define COLOR_ATTR(_at, _fg, _bg, _hl) \ + { .attr = &(_at), .has_color = true, .color_fg = _fg, .color_bg = _bg, .highlight = _hl } +#define NO_COLOR_ATTR(_at, _hl) \ + { .attr = &(_at), .has_color = false, .highlight = _hl } +#define COLOR_DEFAULT -1 + +struct nconf_attr_param { + int *attr; + bool has_color; + int color_fg; + int color_bg; + int highlight; +}; + +static const struct nconf_attr_param color_theme_params[] = { + COLOR_ATTR(attr_normal, COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_main_heading, COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD | A_UNDERLINE), + COLOR_ATTR(attr_main_menu_box, COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_main_menu_fore, COLOR_DEFAULT, COLOR_DEFAULT, A_REVERSE), + COLOR_ATTR(attr_main_menu_back, COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_main_menu_grey, COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_main_menu_heading, COLOR_GREEN, COLOR_DEFAULT, A_BOLD), + COLOR_ATTR(attr_scrollwin_text, COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_scrollwin_heading, COLOR_GREEN, COLOR_DEFAULT, A_BOLD), + COLOR_ATTR(attr_scrollwin_box, COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), + COLOR_ATTR(attr_dialog_text, COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), + COLOR_ATTR(attr_dialog_menu_fore, COLOR_RED, COLOR_DEFAULT, A_STANDOUT), + COLOR_ATTR(attr_dialog_menu_back, COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_dialog_box, COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), + COLOR_ATTR(attr_input_box, COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_input_heading, COLOR_GREEN, COLOR_DEFAULT, A_BOLD), + COLOR_ATTR(attr_input_text, COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), + COLOR_ATTR(attr_input_field, COLOR_DEFAULT, COLOR_DEFAULT, A_UNDERLINE), + COLOR_ATTR(attr_function_text, COLOR_YELLOW, COLOR_DEFAULT, A_REVERSE), + COLOR_ATTR(attr_function_highlight, COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), + { /* sentinel */ } +}; + +static const struct nconf_attr_param no_color_theme_params[] = { + NO_COLOR_ATTR(attr_normal, A_NORMAL), + NO_COLOR_ATTR(attr_main_heading, A_BOLD | A_UNDERLINE), + NO_COLOR_ATTR(attr_main_menu_box, A_NORMAL), + NO_COLOR_ATTR(attr_main_menu_fore, A_STANDOUT), + NO_COLOR_ATTR(attr_main_menu_back, A_NORMAL), + NO_COLOR_ATTR(attr_main_menu_grey, A_NORMAL), + NO_COLOR_ATTR(attr_main_menu_heading, A_BOLD), + NO_COLOR_ATTR(attr_scrollwin_text, A_NORMAL), + NO_COLOR_ATTR(attr_scrollwin_heading, A_BOLD), + NO_COLOR_ATTR(attr_scrollwin_box, A_BOLD), + NO_COLOR_ATTR(attr_dialog_text, A_NORMAL), + NO_COLOR_ATTR(attr_dialog_menu_fore, A_STANDOUT), + NO_COLOR_ATTR(attr_dialog_menu_back, A_NORMAL), + NO_COLOR_ATTR(attr_dialog_box, A_BOLD), + NO_COLOR_ATTR(attr_input_box, A_BOLD), + NO_COLOR_ATTR(attr_input_heading, A_BOLD), + NO_COLOR_ATTR(attr_input_text, A_NORMAL), + NO_COLOR_ATTR(attr_input_field, A_UNDERLINE), + NO_COLOR_ATTR(attr_function_text, A_REVERSE), + NO_COLOR_ATTR(attr_function_highlight, A_BOLD), + { /* sentinel */ } +}; + +void set_colors(void) +{ + const struct nconf_attr_param *p; + int pair = 0; + + if (has_colors()) { + start_color(); + use_default_colors(); + p = color_theme_params; + } else { + p = no_color_theme_params; + } + + for (; p->attr; p++) { + int attr = p->highlight; + + if (p->has_color) { + pair++; + init_pair(pair, p->color_fg, p->color_bg); + attr |= COLOR_PAIR(pair); + } + + *p->attr = attr; + } +} + +/* this changes the windows attributes !!! */ +void print_in_middle(WINDOW *win, int y, int width, const char *str, int attrs) +{ + wattrset(win, attrs); + mvwprintw(win, y, (width - strlen(str)) / 2, "%s", str); +} + +int get_line_no(const char *text) +{ + int i; + int total = 1; + + if (!text) + return 0; + + for (i = 0; text[i] != '\0'; i++) + if (text[i] == '\n') + total++; + return total; +} + +const char *get_line(const char *text, int line_no) +{ + int i; + int lines = 0; + + if (!text) + return NULL; + + for (i = 0; text[i] != '\0' && lines < line_no; i++) + if (text[i] == '\n') + lines++; + return text+i; +} + +int get_line_length(const char *line) +{ + int res = 0; + while (*line != '\0' && *line != '\n') { + line++; + res++; + } + return res; +} + +/* print all lines to the window. */ +void fill_window(WINDOW *win, const char *text) +{ + int x, y; + int total_lines = get_line_no(text); + int i; + + getmaxyx(win, y, x); + /* do not go over end of line */ + total_lines = min(total_lines, y); + for (i = 0; i < total_lines; i++) { + const char *line = get_line(text, i); + int len = min(get_line_length(line), x); + + mvwprintw(win, i, 0, "%.*s", len, line); + } +} + +/* get the message, and buttons. + * each button must be a char* + * return the selected button + * + * this dialog is used for 2 different things: + * 1) show a text box, no buttons. + * 2) show a dialog, with horizontal buttons + */ +int btn_dialog(WINDOW *main_window, const char *msg, int btn_num, ...) +{ + va_list ap; + char *btn; + int btns_width = 0; + int msg_lines = 0; + int msg_width = 0; + int total_width; + int win_rows = 0; + WINDOW *win; + WINDOW *msg_win; + WINDOW *menu_win; + MENU *menu; + ITEM *btns[btn_num+1]; + int i, x, y; + int res = -1; + + + va_start(ap, btn_num); + for (i = 0; i < btn_num; i++) { + btn = va_arg(ap, char *); + btns[i] = new_item(btn, ""); + btns_width += strlen(btn)+1; + } + va_end(ap); + btns[btn_num] = NULL; + + /* find the widest line of msg: */ + msg_lines = get_line_no(msg); + for (i = 0; i < msg_lines; i++) { + const char *line = get_line(msg, i); + int len = get_line_length(line); + if (msg_width < len) + msg_width = len; + } + + total_width = max(msg_width, btns_width); + /* place dialog in middle of screen */ + y = (getmaxy(stdscr)-(msg_lines+4))/2; + x = (getmaxx(stdscr)-(total_width+4))/2; + + + /* create the windows */ + if (btn_num > 0) + win_rows = msg_lines+4; + else + win_rows = msg_lines+2; + + win = newwin(win_rows, total_width+4, y, x); + keypad(win, TRUE); + menu_win = derwin(win, 1, btns_width, win_rows-2, + 1+(total_width+2-btns_width)/2); + menu = new_menu(btns); + msg_win = derwin(win, win_rows-2, msg_width, 1, + 1+(total_width+2-msg_width)/2); + + set_menu_fore(menu, attr_dialog_menu_fore); + set_menu_back(menu, attr_dialog_menu_back); + + wattrset(win, attr_dialog_box); + box(win, 0, 0); + + /* print message */ + wattrset(msg_win, attr_dialog_text); + fill_window(msg_win, msg); + + set_menu_win(menu, win); + set_menu_sub(menu, menu_win); + set_menu_format(menu, 1, btn_num); + menu_opts_off(menu, O_SHOWDESC); + menu_opts_off(menu, O_SHOWMATCH); + menu_opts_on(menu, O_ONEVALUE); + menu_opts_on(menu, O_NONCYCLIC); + set_menu_mark(menu, ""); + post_menu(menu); + + + touchwin(win); + refresh_all_windows(main_window); + while ((res = wgetch(win))) { + switch (res) { + case KEY_LEFT: + menu_driver(menu, REQ_LEFT_ITEM); + break; + 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 ' ': + case KEY_F(F_BACK): + case KEY_F(F_EXIT): + break; + } + touchwin(win); + refresh_all_windows(main_window); + + if (res == 10 || res == ' ') { + res = item_index(current_item(menu)); + break; + } else if (res == 27 || res == KEY_F(F_BACK) || + res == KEY_F(F_EXIT)) { + res = KEY_EXIT; + break; + } + } + + unpost_menu(menu); + free_menu(menu); + for (i = 0; i < btn_num; i++) + free_item(btns[i]); + + delwin(win); + return res; +} + +int dialog_inputbox(WINDOW *main_window, + const char *title, const char *prompt, + const char *init, char **resultp, int *result_len) +{ + int prompt_lines = 0; + int prompt_width = 0; + WINDOW *win; + WINDOW *prompt_win; + WINDOW *form_win; + PANEL *panel; + int i, x, y, lines, columns, win_lines, win_cols; + int res = -1; + int cursor_position = strlen(init); + int cursor_form_win; + char *result = *resultp; + + getmaxyx(stdscr, lines, columns); + + if (strlen(init)+1 > *result_len) { + *result_len = strlen(init)+1; + *resultp = result = xrealloc(result, *result_len); + } + + /* find the widest line of msg: */ + prompt_lines = get_line_no(prompt); + for (i = 0; i < prompt_lines; i++) { + const char *line = get_line(prompt, i); + int len = get_line_length(line); + prompt_width = max(prompt_width, len); + } + + if (title) + prompt_width = max(prompt_width, strlen(title)); + + win_lines = min(prompt_lines+6, lines-2); + win_cols = min(prompt_width+7, columns-2); + prompt_lines = max(win_lines-6, 0); + prompt_width = max(win_cols-7, 0); + + /* place dialog in middle of screen */ + y = (lines-win_lines)/2; + x = (columns-win_cols)/2; + + strncpy(result, init, *result_len); + result[*result_len - 1] = '\0'; + + /* create the windows */ + win = newwin(win_lines, win_cols, y, x); + prompt_win = derwin(win, prompt_lines+1, prompt_width, 2, 2); + form_win = derwin(win, 1, prompt_width, prompt_lines+3, 2); + keypad(form_win, TRUE); + + wattrset(form_win, attr_input_field); + + wattrset(win, attr_input_box); + box(win, 0, 0); + wattrset(win, attr_input_heading); + if (title) + mvwprintw(win, 0, 3, "%s", title); + + /* print message */ + wattrset(prompt_win, attr_input_text); + fill_window(prompt_win, prompt); + + mvwprintw(form_win, 0, 0, "%*s", prompt_width, " "); + cursor_form_win = min(cursor_position, prompt_width-1); + mvwprintw(form_win, 0, 0, "%s", + result + cursor_position-cursor_form_win); + + /* create panels */ + panel = new_panel(win); + + /* show the cursor */ + curs_set(1); + + touchwin(win); + refresh_all_windows(main_window); + while ((res = wgetch(form_win))) { + int len = strlen(result); + switch (res) { + case 10: /* ENTER */ + case 27: /* ESCAPE */ + case KEY_F(F_HELP): + case KEY_F(F_EXIT): + case KEY_F(F_BACK): + break; + case 8: /* ^H */ + case 127: /* ^? */ + case KEY_BACKSPACE: + if (cursor_position > 0) { + memmove(&result[cursor_position-1], + &result[cursor_position], + len-cursor_position+1); + cursor_position--; + cursor_form_win--; + len--; + } + break; + case KEY_DC: + if (cursor_position >= 0 && cursor_position < len) { + memmove(&result[cursor_position], + &result[cursor_position+1], + len-cursor_position+1); + len--; + } + break; + case KEY_UP: + case KEY_RIGHT: + if (cursor_position < len) { + cursor_position++; + cursor_form_win++; + } + break; + case KEY_DOWN: + case KEY_LEFT: + if (cursor_position > 0) { + cursor_position--; + cursor_form_win--; + } + break; + case KEY_HOME: + cursor_position = 0; + cursor_form_win = 0; + break; + case KEY_END: + cursor_position = len; + cursor_form_win = min(cursor_position, prompt_width-1); + break; + default: + if ((isgraph(res) || isspace(res))) { + /* one for new char, one for '\0' */ + if (len+2 > *result_len) { + *result_len = len+2; + *resultp = result = realloc(result, + *result_len); + } + /* insert the char at the proper position */ + memmove(&result[cursor_position+1], + &result[cursor_position], + len-cursor_position+1); + result[cursor_position] = res; + cursor_position++; + cursor_form_win++; + len++; + } else { + mvprintw(0, 0, "unknown key: %d\n", res); + } + break; + } + if (cursor_form_win < 0) + cursor_form_win = 0; + else if (cursor_form_win > prompt_width-1) + cursor_form_win = prompt_width-1; + + wmove(form_win, 0, 0); + wclrtoeol(form_win); + mvwprintw(form_win, 0, 0, "%*s", prompt_width, " "); + mvwprintw(form_win, 0, 0, "%s", + result + cursor_position-cursor_form_win); + wmove(form_win, 0, cursor_form_win); + touchwin(win); + refresh_all_windows(main_window); + + if (res == 10) { + res = 0; + break; + } else if (res == 27 || res == KEY_F(F_BACK) || + res == KEY_F(F_EXIT)) { + res = KEY_EXIT; + break; + } else if (res == KEY_F(F_HELP)) { + res = 1; + break; + } + } + + /* hide the cursor */ + curs_set(0); + del_panel(panel); + delwin(prompt_win); + delwin(form_win); + delwin(win); + return res; +} + +/* refresh all windows in the correct order */ +void refresh_all_windows(WINDOW *main_window) +{ + update_panels(); + touchwin(main_window); + refresh(); +} + +void show_scroll_win(WINDOW *main_window, + const char *title, + const char *text) +{ + (void)show_scroll_win_ext(main_window, title, (char *)text, NULL, NULL, NULL, NULL); +} + +/* layman's scrollable window... */ +int show_scroll_win_ext(WINDOW *main_window, const char *title, char *text, + int *vscroll, int *hscroll, + extra_key_cb_fn extra_key_cb, void *data) +{ + int res; + int total_lines = get_line_no(text); + int x, y, lines, columns; + int start_x = 0, start_y = 0; + int text_lines = 0, text_cols = 0; + int total_cols = 0; + int win_cols = 0; + int win_lines = 0; + int i = 0; + WINDOW *win; + WINDOW *pad; + PANEL *panel; + bool done = false; + + if (hscroll) + start_x = *hscroll; + if (vscroll) + start_y = *vscroll; + + getmaxyx(stdscr, lines, columns); + + /* find the widest line of msg: */ + total_lines = get_line_no(text); + for (i = 0; i < total_lines; i++) { + const char *line = get_line(text, i); + int len = get_line_length(line); + total_cols = max(total_cols, len+2); + } + + /* create the pad */ + pad = newpad(total_lines+10, total_cols+10); + wattrset(pad, attr_scrollwin_text); + fill_window(pad, text); + + win_lines = min(total_lines+4, lines-2); + win_cols = min(total_cols+2, columns-2); + text_lines = max(win_lines-4, 0); + text_cols = max(win_cols-2, 0); + + /* place window in middle of screen */ + y = (lines-win_lines)/2; + x = (columns-win_cols)/2; + + win = newwin(win_lines, win_cols, y, x); + keypad(win, TRUE); + /* show the help in the help window, and show the help panel */ + wattrset(win, attr_scrollwin_box); + box(win, 0, 0); + wattrset(win, attr_scrollwin_heading); + mvwprintw(win, 0, 3, " %s ", title); + panel = new_panel(win); + + /* handle scrolling */ + while (!done) { + copywin(pad, win, start_y, start_x, 2, 2, text_lines, + text_cols, 0); + print_in_middle(win, + text_lines+2, + text_cols, + "<OK>", + attr_dialog_menu_fore); + wrefresh(win); + + res = wgetch(win); + switch (res) { + case KEY_NPAGE: + case ' ': + case 'd': + start_y += text_lines-2; + break; + case KEY_PPAGE: + case 'u': + start_y -= text_lines+2; + break; + case KEY_HOME: + start_y = 0; + break; + case KEY_END: + start_y = total_lines-text_lines; + break; + case KEY_DOWN: + case 'j': + start_y++; + break; + case KEY_UP: + case 'k': + start_y--; + break; + case KEY_LEFT: + case 'h': + start_x--; + break; + case KEY_RIGHT: + case 'l': + start_x++; + break; + default: + if (extra_key_cb) { + size_t start = (get_line(text, start_y) - text); + size_t end = (get_line(text, start_y + text_lines) - text); + + if (extra_key_cb(res, start, end, data)) { + done = true; + break; + } + } + } + if (res == 0 || res == 10 || res == 27 || res == 'q' || + res == KEY_F(F_HELP) || res == KEY_F(F_BACK) || + res == KEY_F(F_EXIT)) + break; + if (start_y < 0) + start_y = 0; + if (start_y >= total_lines-text_lines) + start_y = total_lines-text_lines; + if (start_x < 0) + start_x = 0; + if (start_x >= total_cols-text_cols) + start_x = total_cols-text_cols; + } + + if (hscroll) + *hscroll = start_x; + if (vscroll) + *vscroll = start_y; + del_panel(panel); + delwin(win); + refresh_all_windows(main_window); + return res; +} diff --git a/scripts/kconfig/nconf.h b/scripts/kconfig/nconf.h new file mode 100644 index 000000000..ab836d582 --- /dev/null +++ b/scripts/kconfig/nconf.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2008 Nir Tzachar <nir.tzachar@gmail.com> + * + * Derived from menuconfig. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ncurses.h> +#include <menu.h> +#include <panel.h> +#include <form.h> + +#include <stdio.h> +#include <time.h> +#include <sys/time.h> + +#define max(a, b) ({\ + typeof(a) _a = a;\ + typeof(b) _b = b;\ + _a > _b ? _a : _b; }) + +#define min(a, b) ({\ + typeof(a) _a = a;\ + typeof(b) _b = b;\ + _a < _b ? _a : _b; }) + +extern int attr_normal; +extern int attr_main_heading; +extern int attr_main_menu_box; +extern int attr_main_menu_fore; +extern int attr_main_menu_back; +extern int attr_main_menu_grey; +extern int attr_main_menu_heading; +extern int attr_scrollwin_text; +extern int attr_scrollwin_heading; +extern int attr_scrollwin_box; +extern int attr_dialog_text; +extern int attr_dialog_menu_fore; +extern int attr_dialog_menu_back; +extern int attr_dialog_box; +extern int attr_input_box; +extern int attr_input_heading; +extern int attr_input_text; +extern int attr_input_field; +extern int attr_function_text; +extern int attr_function_highlight; + +typedef enum { + F_HELP = 1, + F_SYMBOL = 2, + F_INSTS = 3, + F_CONF = 4, + F_BACK = 5, + F_SAVE = 6, + F_LOAD = 7, + F_SEARCH = 8, + F_EXIT = 9, +} function_key; + +void set_colors(void); + +typedef int (*extra_key_cb_fn)(int, size_t, size_t, void *); + +/* this changes the windows attributes !!! */ +void print_in_middle(WINDOW *win, int y, int width, const char *str, int attrs); +int get_line_length(const char *line); +int get_line_no(const char *text); +const char *get_line(const char *text, int line_no); +void fill_window(WINDOW *win, const char *text); +int btn_dialog(WINDOW *main_window, const char *msg, int btn_num, ...); +int dialog_inputbox(WINDOW *main_window, + const char *title, const char *prompt, + const char *init, char **resultp, int *result_len); +void refresh_all_windows(WINDOW *main_window); +int show_scroll_win_ext(WINDOW *main_window, const char *title, char *text, + int *vscroll, int *hscroll, + extra_key_cb_fn extra_key_cb, void *data); +void show_scroll_win(WINDOW *main_window, + const char *title, + const char *text); diff --git a/scripts/kconfig/parser.y b/scripts/kconfig/parser.y new file mode 100644 index 000000000..5fb6f07b6 --- /dev/null +++ b/scripts/kconfig/parser.y @@ -0,0 +1,797 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ +%{ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#include <xalloc.h> +#include "lkc.h" +#include "internal.h" +#include "preprocess.h" + +#define printd(mask, fmt...) if (cdebug & (mask)) printf(fmt) + +#define PRINTD 0x0001 +#define DEBUG_PARSE 0x0002 + +int cdebug = PRINTD; + +static void yyerror(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, *current_choice; + +%} + +%union +{ + char *string; + struct symbol *symbol; + struct expr *expr; + struct menu *menu; + enum symbol_type type; + enum variable_flavor flavor; +} + +%token <string> T_HELPTEXT +%token <string> T_WORD +%token <string> T_WORD_QUOTE +%token T_BOOL +%token T_CHOICE +%token T_CLOSE_PAREN +%token T_COLON_EQUAL +%token T_COMMENT +%token T_CONFIG +%token T_DEFAULT +%token T_DEF_BOOL +%token T_DEF_TRISTATE +%token T_DEPENDS +%token T_ENDCHOICE +%token T_ENDIF +%token T_ENDMENU +%token T_HELP +%token T_HEX +%token T_IF +%token T_IMPLY +%token T_INT +%token T_MAINMENU +%token T_MENU +%token T_MENUCONFIG +%token T_MODULES +%token T_ON +%token T_OPEN_PAREN +%token T_PLUS_EQUAL +%token T_PROMPT +%token T_RANGE +%token T_SELECT +%token T_SOURCE +%token T_STRING +%token T_TRANSITIONAL +%token T_TRISTATE +%token T_VISIBLE +%token T_EOL +%token <string> T_ASSIGN_VAL + +%left T_OR +%left T_AND +%left T_EQUAL T_UNEQUAL +%left T_LESS T_LESS_EQUAL T_GREATER T_GREATER_EQUAL +%nonassoc T_NOT + +%type <symbol> nonconst_symbol +%type <symbol> symbol +%type <type> type default +%type <expr> expr +%type <expr> if_expr +%type <string> end +%type <menu> if_entry menu_entry choice_entry +%type <string> assign_val +%type <flavor> assign_op + +%destructor { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + $$->filename, $$->lineno); + if (current_menu == $$) + menu_end_menu(); +} if_entry menu_entry choice_entry + +%% +input: mainmenu_stmt stmt_list | stmt_list; + +/* mainmenu entry */ + +mainmenu_stmt: T_MAINMENU T_WORD_QUOTE T_EOL +{ + menu_add_prompt(P_MENU, $2, NULL); +}; + +stmt_list: + /* empty */ + | stmt_list assignment_stmt + | stmt_list choice_stmt + | stmt_list comment_stmt + | stmt_list config_stmt + | stmt_list if_stmt + | stmt_list menu_stmt + | stmt_list menuconfig_stmt + | stmt_list source_stmt + | stmt_list T_WORD error T_EOL { zconf_error("unknown statement \"%s\"", $2); } + | stmt_list error T_EOL { zconf_error("invalid statement"); } +; + +stmt_list_in_choice: + /* empty */ + | stmt_list_in_choice comment_stmt + | stmt_list_in_choice config_stmt + | stmt_list_in_choice if_stmt_in_choice + | stmt_list_in_choice error T_EOL { zconf_error("invalid statement"); } +; + +/* config/menuconfig entry */ + +config_entry_start: T_CONFIG nonconst_symbol T_EOL +{ + 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++; + } + + 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 +{ + menu_add_entry($2, M_MENU); + printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", cur_filename, cur_lineno, $2->name); +}; + +menuconfig_stmt: menuconfig_entry_start config_option_list +{ + if (current_entry->prompt) + current_entry->prompt->type = P_MENU; + else + zconf_error("menuconfig statement without prompt"); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", cur_filename, cur_lineno); +}; + +config_option_list: + /* empty */ + | config_option_list config_option + | config_option_list depends + | config_option_list help +; + +config_option: type prompt_stmt_opt T_EOL +{ + menu_set_type($1); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", cur_filename, cur_lineno, $1); +}; + +config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno); +}; + +config_option: T_TRANSITIONAL T_EOL +{ + current_entry->sym->flags |= SYMBOL_TRANS; + printd(DEBUG_PARSE, "%s:%d:transitional\n", cur_filename, cur_lineno); +}; + +config_option: default expr if_expr T_EOL +{ + menu_add_expr(P_DEFAULT, $2, $3); + if ($1 != S_UNKNOWN) + menu_set_type($1); + printd(DEBUG_PARSE, "%s:%d:default(%u)\n", cur_filename, cur_lineno, + $1); +}; + +config_option: T_SELECT nonconst_symbol if_expr T_EOL +{ + menu_add_symbol(P_SELECT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:select\n", cur_filename, cur_lineno); +}; + +config_option: T_IMPLY nonconst_symbol if_expr T_EOL +{ + menu_add_symbol(P_IMPLY, $2, $3); + printd(DEBUG_PARSE, "%s:%d:imply\n", cur_filename, cur_lineno); +}; + +config_option: T_RANGE symbol symbol if_expr T_EOL +{ + menu_add_expr(P_RANGE, expr_alloc_comp(E_RANGE,$2, $3), $4); + printd(DEBUG_PARSE, "%s:%d:range\n", cur_filename, cur_lineno); +}; + +config_option: T_MODULES T_EOL +{ + if (modules_sym) + zconf_error("symbol '%s' redefines option 'modules' already defined by symbol '%s'", + current_entry->sym->name, modules_sym->name); + modules_sym = current_entry->sym; +}; + +/* choice entry */ + +choice: T_CHOICE T_EOL +{ + 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); +}; + +choice_entry: choice choice_option_list +{ + if (!current_entry->prompt) { + fprintf(stderr, "%s:%d: error: choice must have a prompt\n", + current_entry->filename, current_entry->lineno); + yynerrs++; + } + + $$ = 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); + } +}; + +choice_stmt: choice_entry stmt_list_in_choice choice_end +; + +choice_option_list: + /* empty */ + | choice_option_list choice_option + | choice_option_list depends + | choice_option_list help +; + +choice_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno); +}; + +choice_option: T_DEFAULT nonconst_symbol if_expr T_EOL +{ + menu_add_symbol(P_DEFAULT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:default\n", cur_filename, cur_lineno); +}; + +type: + T_BOOL { $$ = S_BOOLEAN; } + | T_TRISTATE { $$ = S_TRISTATE; } + | T_INT { $$ = S_INT; } + | T_HEX { $$ = S_HEX; } + | T_STRING { $$ = S_STRING; } + +default: + T_DEFAULT { $$ = S_UNKNOWN; } + | T_DEF_BOOL { $$ = S_BOOLEAN; } + | T_DEF_TRISTATE { $$ = S_TRISTATE; } + +/* if entry */ + +if_entry: T_IF expr T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:if\n", cur_filename, cur_lineno); + menu_add_entry(NULL, M_IF); + menu_add_dep($2, NULL); + $$ = menu_add_menu(); +}; + +if_end: end +{ + if (zconf_endtoken($1, "if")) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endif\n", cur_filename, cur_lineno); + } +}; + +if_stmt: if_entry stmt_list if_end +; + +if_stmt_in_choice: if_entry stmt_list_in_choice if_end +; + +/* menu entry */ + +menu: T_MENU T_WORD_QUOTE T_EOL +{ + menu_add_entry(NULL, M_MENU); + menu_add_prompt(P_MENU, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:menu\n", cur_filename, cur_lineno); +}; + +menu_entry: menu menu_option_list +{ + $$ = menu_add_menu(); +}; + +menu_end: end +{ + if (zconf_endtoken($1, "menu")) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endmenu\n", cur_filename, cur_lineno); + } +}; + +menu_stmt: menu_entry stmt_list menu_end +; + +menu_option_list: + /* empty */ + | menu_option_list visible + | menu_option_list depends +; + +source_stmt: T_SOURCE T_WORD_QUOTE T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:source %s\n", cur_filename, cur_lineno, $2); + zconf_nextfile($2); + free($2); +}; + +/* comment entry */ + +comment: T_COMMENT T_WORD_QUOTE T_EOL +{ + menu_add_entry(NULL, M_COMMENT); + menu_add_prompt(P_COMMENT, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:comment\n", cur_filename, cur_lineno); +}; + +comment_stmt: comment comment_option_list +; + +comment_option_list: + /* empty */ + | comment_option_list depends +; + +/* help option */ + +help_start: T_HELP T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:help\n", cur_filename, cur_lineno); + zconf_starthelp(); +}; + +help: help_start T_HELPTEXT +{ + if (current_entry->help) { + free(current_entry->help); + 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') + zconf_error("'%s' defined with blank help text", + current_entry->sym->name ?: "<choice>"); + + current_entry->help = $2; +}; + +/* depends option */ + +depends: T_DEPENDS T_ON expr if_expr T_EOL +{ + menu_add_dep($3, $4); + printd(DEBUG_PARSE, "%s:%d:depends on\n", cur_filename, cur_lineno); +}; + +/* visibility option */ +visible: T_VISIBLE if_expr T_EOL +{ + menu_add_visibility($2); +}; + +/* prompt statement */ + +prompt_stmt_opt: + /* empty */ + | T_WORD_QUOTE if_expr +{ + menu_add_prompt(P_PROMPT, $1, $2); +}; + +end: T_ENDMENU T_EOL { $$ = "menu"; } + | T_ENDCHOICE T_EOL { $$ = "choice"; } + | T_ENDIF T_EOL { $$ = "if"; } +; + +if_expr: /* empty */ { $$ = NULL; } + | T_IF expr { $$ = $2; } +; + +expr: symbol { $$ = expr_alloc_symbol($1); } + | symbol T_LESS symbol { $$ = expr_alloc_comp(E_LTH, $1, $3); } + | symbol T_LESS_EQUAL symbol { $$ = expr_alloc_comp(E_LEQ, $1, $3); } + | symbol T_GREATER symbol { $$ = expr_alloc_comp(E_GTH, $1, $3); } + | symbol T_GREATER_EQUAL symbol { $$ = expr_alloc_comp(E_GEQ, $1, $3); } + | symbol T_EQUAL symbol { $$ = expr_alloc_comp(E_EQUAL, $1, $3); } + | symbol T_UNEQUAL symbol { $$ = expr_alloc_comp(E_UNEQUAL, $1, $3); } + | T_OPEN_PAREN expr T_CLOSE_PAREN { $$ = $2; } + | T_NOT expr { $$ = expr_alloc_one(E_NOT, $2); } + | expr T_OR expr { $$ = expr_alloc_two(E_OR, $1, $3); } + | expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); } +; + +/* For symbol definitions, selects, etc., where quotes are not accepted */ +nonconst_symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); }; + +symbol: nonconst_symbol + | T_WORD_QUOTE { $$ = sym_lookup($1, SYMBOL_CONST); free($1); } +; + +/* assignment statement */ + +assignment_stmt: T_WORD assign_op assign_val T_EOL { variable_add($1, $3, $2); free($1); free($3); } + +assign_op: + T_EQUAL { $$ = VAR_RECURSIVE; } + | T_COLON_EQUAL { $$ = VAR_SIMPLE; } + | T_PLUS_EQUAL { $$ = VAR_APPEND; } +; + +assign_val: + /* empty */ { $$ = xstrdup(""); }; + | T_ASSIGN_VAL +; + +%% + +/** + * transitional_check_sanity - check transitional symbols have no other + * properties + * + * @menu: menu of the potentially transitional symbol + * + * Return: -1 if an error is found, 0 otherwise. + */ +static int transitional_check_sanity(const struct menu *menu) +{ + struct property *prop; + + if (!menu->sym || !(menu->sym->flags & SYMBOL_TRANS)) + return 0; + + /* Check for depends and visible conditions. */ + if ((menu->dep && !expr_is_yes(menu->dep)) || + (menu->visibility && !expr_is_yes(menu->visibility))) { + fprintf(stderr, "%s:%d: error: %s", + menu->filename, menu->lineno, + "transitional symbols can only have help sections\n"); + return -1; + } + + /* Check for any property other than "help". */ + for (prop = menu->sym->prop; prop; prop = prop->next) { + if (prop->type != P_COMMENT) { + fprintf(stderr, "%s:%d: error: %s", + prop->filename, prop->lineno, + "transitional symbols can only have help sections\n"); + return -1; + } + } + + return 0; +} + +/** + * 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) { + fprintf(stderr, "%s:%d: error: %s", + prop->filename, prop->lineno, + "choice value must not have a prompt in another entry\n"); + ret = -1; + } + } + + return ret; +} + +void conf_parse(const char *name) +{ + struct menu *menu; + + autoconf_cmd = str_new(); + + str_printf(&autoconf_cmd, "\ndeps_config := \\\n"); + + zconf_initscan(name); + + _menu_init(); + + if (getenv("ZCONF_DEBUG")) + yydebug = 1; + yyparse(); + + str_printf(&autoconf_cmd, + "\n" + "$(autoconfig): $(deps_config)\n" + "$(deps_config): ;\n"); + + env_write_dep(&autoconf_cmd); + + /* Variables are expanded in the parse phase. We can free them here. */ + variable_all_del(); + + if (yynerrs) + exit(1); + if (!modules_sym) + modules_sym = &symbol_no; + + if (!menu_has_prompt(&rootmenu)) { + current_entry = &rootmenu; + menu_add_prompt(P_MENU, "Main menu", NULL); + } + + menu_finalize(); + + menu_for_each_entry(menu) { + struct menu *child; + + if (menu->sym && sym_check_deps(menu->sym)) + yynerrs++; + + if (transitional_check_sanity(menu)) + yynerrs++; + + if (menu->sym && sym_is_choice(menu->sym)) { + menu_for_each_sub_entry(child, menu) + if (child->sym && choice_check_sanity(child)) + yynerrs++; + } + } + + if (yynerrs) + exit(1); + conf_set_changed(true); +} + +static bool zconf_endtoken(const char *tokenname, + const char *expected_tokenname) +{ + if (strcmp(tokenname, expected_tokenname)) { + zconf_error("unexpected '%s' within %s block", + tokenname, expected_tokenname); + yynerrs++; + return false; + } + if (strcmp(current_menu->filename, cur_filename)) { + zconf_error("'%s' in different file than '%s'", + tokenname, expected_tokenname); + fprintf(stderr, "%s:%d: location of the '%s'\n", + current_menu->filename, current_menu->lineno, + expected_tokenname); + yynerrs++; + return false; + } + return true; +} + +static void zconf_error(const char *err, ...) +{ + va_list ap; + + yynerrs++; + fprintf(stderr, "%s:%d: ", cur_filename, cur_lineno); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void yyerror(const char *err) +{ + fprintf(stderr, "%s:%d: %s\n", cur_filename, cur_lineno, err); +} + +static void print_quoted_string(FILE *out, const char *str) +{ + const char *p; + int len; + + putc('"', out); + while ((p = strchr(str, '"'))) { + len = p - str; + if (len) + fprintf(out, "%.*s", len, str); + fputs("\\\"", out); + str = p + 1; + } + fputs(str, out); + putc('"', out); +} + +static void print_symbol(FILE *out, const struct menu *menu) +{ + struct symbol *sym = menu->sym; + struct property *prop; + + if (sym_is_choice(sym)) + fprintf(out, "\nchoice\n"); + else + fprintf(out, "\nconfig %s\n", sym->name); + switch (sym->type) { + case S_BOOLEAN: + fputs(" bool\n", out); + break; + case S_TRISTATE: + fputs(" tristate\n", out); + break; + case S_STRING: + fputs(" string\n", out); + break; + case S_INT: + fputs(" integer\n", out); + break; + case S_HEX: + fputs(" hex\n", out); + break; + default: + fputs(" ???\n", out); + break; + } + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + switch (prop->type) { + case P_PROMPT: + fputs(" prompt ", out); + print_quoted_string(out, prop->text); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_DEFAULT: + fputs( " default ", out); + expr_fprint(prop->expr, out); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_SELECT: + fputs( " select ", out); + expr_fprint(prop->expr, out); + fputc('\n', out); + break; + case P_IMPLY: + fputs( " imply ", out); + expr_fprint(prop->expr, out); + fputc('\n', out); + break; + case P_RANGE: + fputs( " range ", out); + expr_fprint(prop->expr, out); + fputc('\n', out); + break; + case P_MENU: + fputs( " menu ", out); + print_quoted_string(out, prop->text); + fputc('\n', out); + break; + default: + fprintf(out, " unknown prop %d!\n", prop->type); + break; + } + } + if (menu->help) { + int len = strlen(menu->help); + while (menu->help[--len] == '\n') + menu->help[len] = 0; + fprintf(out, " help\n%s\n", menu->help); + } +} + +void zconfdump(FILE *out) +{ + struct property *prop; + struct symbol *sym; + struct menu *menu; + + menu = rootmenu.list; + while (menu) { + if ((sym = menu->sym)) + print_symbol(out, menu); + else if ((prop = menu->prompt)) { + switch (prop->type) { + case P_COMMENT: + fputs("\ncomment ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + case P_MENU: + fputs("\nmenu ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + default: + ; + } + if (!expr_is_yes(prop->visible.expr)) { + fputs(" depends ", out); + expr_fprint(prop->visible.expr, out); + fputc('\n', out); + } + } + + if (menu->list) + menu = menu->list; + else if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->prompt && menu->prompt->type == P_MENU) + fputs("\nendmenu\n", out); + if (menu->next) { + menu = menu->next; + break; + } + } + } +} diff --git a/scripts/kconfig/preprocess.c b/scripts/kconfig/preprocess.c new file mode 100644 index 000000000..783abcaa5 --- /dev/null +++ b/scripts/kconfig/preprocess.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@socionext.com> + +#include <ctype.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <array_size.h> +#include <list.h> +#include <xalloc.h> +#include "internal.h" +#include "lkc.h" +#include "preprocess.h" + +static char *expand_string_with_args(const char *in, int argc, char *argv[]); +static char *expand_string(const char *in); + +static void __attribute__((noreturn)) pperror(const char *format, ...) +{ + va_list ap; + + fprintf(stderr, "%s:%d: ", cur_filename, yylineno); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fprintf(stderr, "\n"); + + exit(1); +} + +/* + * Environment variables + */ +static LIST_HEAD(env_list); + +struct env { + char *name; + char *value; + struct list_head node; +}; + +static void env_add(const char *name, const char *value) +{ + struct env *e; + + e = xmalloc(sizeof(*e)); + e->name = xstrdup(name); + e->value = xstrdup(value); + + list_add_tail(&e->node, &env_list); +} + +static void env_del(struct env *e) +{ + list_del(&e->node); + free(e->name); + free(e->value); + free(e); +} + +/* The returned pointer must be freed when done */ +static char *env_expand(const char *name) +{ + struct env *e; + const char *value; + + if (!*name) + return NULL; + + list_for_each_entry(e, &env_list, node) { + if (!strcmp(name, e->name)) + return xstrdup(e->value); + } + + value = getenv(name); + if (!value) + return NULL; + + /* + * We need to remember all referenced environment variables. + * They will be written out to include/config/auto.conf.cmd + */ + env_add(name, value); + + return xstrdup(value); +} + +void env_write_dep(struct gstr *s) +{ + struct env *e, *tmp; + + list_for_each_entry_safe(e, tmp, &env_list, node) { + str_printf(s, + "\n" + "ifneq \"$(%s)\" \"%s\"\n" + "$(autoconfig): FORCE\n" + "endif\n", + e->name, e->value); + env_del(e); + } +} + +/* + * Built-in functions + */ +struct function { + const char *name; + unsigned int min_args; + unsigned int max_args; + char *(*func)(int argc, char *argv[]); +}; + +static char *do_error_if(int argc, char *argv[]) +{ + if (!strcmp(argv[0], "y")) + pperror("%s", argv[1]); + + return xstrdup(""); +} + +static char *do_filename(int argc, char *argv[]) +{ + return xstrdup(cur_filename); +} + +static char *do_info(int argc, char *argv[]) +{ + printf("%s\n", argv[0]); + + return xstrdup(""); +} + +static char *do_lineno(int argc, char *argv[]) +{ + char buf[16]; + + sprintf(buf, "%d", yylineno); + + return xstrdup(buf); +} + +static char *do_shell(int argc, char *argv[]) +{ + FILE *p; + char buf[4096]; + char *cmd; + size_t nread; + int i; + + cmd = argv[0]; + + p = popen(cmd, "r"); + if (!p) { + perror(cmd); + exit(1); + } + + nread = fread(buf, 1, sizeof(buf), p); + if (nread == sizeof(buf)) + nread--; + + /* remove trailing new lines */ + while (nread > 0 && buf[nread - 1] == '\n') + nread--; + + buf[nread] = 0; + + /* replace a new line with a space */ + for (i = 0; i < nread; i++) { + if (buf[i] == '\n') + buf[i] = ' '; + } + + if (pclose(p) == -1) { + perror(cmd); + exit(1); + } + + return xstrdup(buf); +} + +static char *do_warning_if(int argc, char *argv[]) +{ + if (!strcmp(argv[0], "y")) + fprintf(stderr, "%s:%d: %s\n", cur_filename, yylineno, argv[1]); + + return xstrdup(""); +} + +static const struct function function_table[] = { + /* Name MIN MAX Function */ + { "error-if", 2, 2, do_error_if }, + { "filename", 0, 0, do_filename }, + { "info", 1, 1, do_info }, + { "lineno", 0, 0, do_lineno }, + { "shell", 1, 1, do_shell }, + { "warning-if", 2, 2, do_warning_if }, +}; + +#define FUNCTION_MAX_ARGS 16 + +static char *function_expand(const char *name, int argc, char *argv[]) +{ + const struct function *f; + int i; + + for (i = 0; i < ARRAY_SIZE(function_table); i++) { + f = &function_table[i]; + if (strcmp(f->name, name)) + continue; + + if (argc < f->min_args) + pperror("too few function arguments passed to '%s'", + name); + + if (argc > f->max_args) + pperror("too many function arguments passed to '%s'", + name); + + return f->func(argc, argv); + } + + return NULL; +} + +/* + * Variables (and user-defined functions) + */ +static LIST_HEAD(variable_list); + +struct variable { + char *name; + char *value; + enum variable_flavor flavor; + int exp_count; + struct list_head node; +}; + +static struct variable *variable_lookup(const char *name) +{ + struct variable *v; + + list_for_each_entry(v, &variable_list, node) { + if (!strcmp(name, v->name)) + return v; + } + + return NULL; +} + +static char *variable_expand(const char *name, int argc, char *argv[]) +{ + struct variable *v; + char *res; + + v = variable_lookup(name); + if (!v) + return NULL; + + if (argc == 0 && v->exp_count) + pperror("Recursive variable '%s' references itself (eventually)", + name); + + if (v->exp_count > 1000) + pperror("Too deep recursive expansion"); + + v->exp_count++; + + if (v->flavor == VAR_RECURSIVE) + res = expand_string_with_args(v->value, argc, argv); + else + res = xstrdup(v->value); + + v->exp_count--; + + return res; +} + +void variable_add(const char *name, const char *value, + enum variable_flavor flavor) +{ + struct variable *v; + char *new_value; + bool append = false; + + v = variable_lookup(name); + if (v) { + /* For defined variables, += inherits the existing flavor */ + if (flavor == VAR_APPEND) { + flavor = v->flavor; + append = true; + } else { + free(v->value); + } + } else { + /* For undefined variables, += assumes the recursive flavor */ + if (flavor == VAR_APPEND) + flavor = VAR_RECURSIVE; + + v = xmalloc(sizeof(*v)); + v->name = xstrdup(name); + v->exp_count = 0; + list_add_tail(&v->node, &variable_list); + } + + v->flavor = flavor; + + if (flavor == VAR_SIMPLE) + new_value = expand_string(value); + else + new_value = xstrdup(value); + + if (append) { + v->value = xrealloc(v->value, + strlen(v->value) + strlen(new_value) + 2); + strcat(v->value, " "); + strcat(v->value, new_value); + free(new_value); + } else { + v->value = new_value; + } +} + +static void variable_del(struct variable *v) +{ + list_del(&v->node); + free(v->name); + free(v->value); + free(v); +} + +void variable_all_del(void) +{ + struct variable *v, *tmp; + + list_for_each_entry_safe(v, tmp, &variable_list, node) + variable_del(v); +} + +/* + * Evaluate a clause with arguments. argc/argv are arguments from the upper + * function call. + * + * Returned string must be freed when done + */ +static char *eval_clause(const char *str, size_t len, int argc, char *argv[]) +{ + char *tmp, *name, *res, *endptr, *prev, *p; + int new_argc = 0; + char *new_argv[FUNCTION_MAX_ARGS]; + int nest = 0; + int i; + unsigned long n; + + tmp = xstrndup(str, len); + + /* + * If variable name is '1', '2', etc. It is generally an argument + * from a user-function call (i.e. local-scope variable). If not + * available, then look-up global-scope variables. + */ + n = strtoul(tmp, &endptr, 10); + if (!*endptr && n > 0 && n <= argc) { + res = xstrdup(argv[n - 1]); + goto free_tmp; + } + + prev = p = tmp; + + /* + * Split into tokens + * The function name and arguments are separated by a comma. + * For example, if the function call is like this: + * $(foo,$(x),$(y)) + * + * The input string for this helper should be: + * foo,$(x),$(y) + * + * and split into: + * new_argv[0] = 'foo' + * new_argv[1] = '$(x)' + * new_argv[2] = '$(y)' + */ + while (*p) { + if (nest == 0 && *p == ',') { + *p = 0; + if (new_argc >= FUNCTION_MAX_ARGS) + pperror("too many function arguments"); + new_argv[new_argc++] = prev; + prev = p + 1; + } else if (*p == '(') { + nest++; + } else if (*p == ')') { + nest--; + } + + p++; + } + + if (new_argc >= FUNCTION_MAX_ARGS) + pperror("too many function arguments"); + new_argv[new_argc++] = prev; + + /* + * Shift arguments + * new_argv[0] represents a function name or a variable name. Put it + * into 'name', then shift the rest of the arguments. This simplifies + * 'const' handling. + */ + name = expand_string_with_args(new_argv[0], argc, argv); + new_argc--; + for (i = 0; i < new_argc; i++) + new_argv[i] = expand_string_with_args(new_argv[i + 1], + argc, argv); + + /* Search for variables */ + res = variable_expand(name, new_argc, new_argv); + if (res) + goto free; + + /* Look for built-in functions */ + res = function_expand(name, new_argc, new_argv); + if (res) + goto free; + + /* Last, try environment variable */ + if (new_argc == 0) { + res = env_expand(name); + if (res) + goto free; + } + + res = xstrdup(""); +free: + for (i = 0; i < new_argc; i++) + free(new_argv[i]); + free(name); +free_tmp: + free(tmp); + + return res; +} + +/* + * Expand a string that follows '$' + * + * For example, if the input string is + * ($(FOO)$($(BAR)))$(BAZ) + * this helper evaluates + * $($(FOO)$($(BAR))) + * and returns a new string containing the expansion (note that the string is + * recursively expanded), also advancing 'str' to point to the next character + * after the corresponding closing parenthesis, in this case, *str will be + * $(BAR) + */ +static char *expand_dollar_with_args(const char **str, int argc, char *argv[]) +{ + const char *p = *str; + const char *q; + int nest = 0; + + /* + * In Kconfig, variable/function references always start with "$(". + * Neither single-letter variables as in $A nor curly braces as in ${CC} + * are supported. '$' not followed by '(' loses its special meaning. + */ + if (*p != '(') { + *str = p; + return xstrdup("$"); + } + + p++; + q = p; + while (*q) { + if (*q == '(') { + nest++; + } else if (*q == ')') { + if (nest-- == 0) + break; + } + q++; + } + + if (!*q) + pperror("unterminated reference to '%s': missing ')'", p); + + /* Advance 'str' to after the expanded initial portion of the string */ + *str = q + 1; + + return eval_clause(p, q - p, argc, argv); +} + +char *expand_dollar(const char **str) +{ + return expand_dollar_with_args(str, 0, NULL); +} + +static char *__expand_string(const char **str, bool (*is_end)(char c), + int argc, char *argv[]) +{ + const char *in, *p; + char *expansion, *out; + size_t in_len, out_len; + + out = xmalloc(1); + *out = 0; + out_len = 1; + + p = in = *str; + + while (1) { + if (*p == '$') { + in_len = p - in; + p++; + expansion = expand_dollar_with_args(&p, argc, argv); + out_len += in_len + strlen(expansion); + out = xrealloc(out, out_len); + strncat(out, in, in_len); + strcat(out, expansion); + free(expansion); + in = p; + continue; + } + + if (is_end(*p)) + break; + + p++; + } + + in_len = p - in; + out_len += in_len; + out = xrealloc(out, out_len); + strncat(out, in, in_len); + + /* Advance 'str' to the end character */ + *str = p; + + return out; +} + +static bool is_end_of_str(char c) +{ + return !c; +} + +/* + * Expand variables and functions in the given string. Undefined variables + * expand to an empty string. + * The returned string must be freed when done. + */ +static char *expand_string_with_args(const char *in, int argc, char *argv[]) +{ + return __expand_string(&in, is_end_of_str, argc, argv); +} + +static char *expand_string(const char *in) +{ + return expand_string_with_args(in, 0, NULL); +} + +static bool is_end_of_token(char c) +{ + return !(isalnum(c) || c == '_' || c == '-'); +} + +/* + * Expand variables in a token. The parsing stops when a token separater + * (in most cases, it is a whitespace) is encountered. 'str' is updated to + * point to the next character. + * + * The returned string must be freed when done. + */ +char *expand_one_token(const char **str) +{ + return __expand_string(str, is_end_of_token, 0, NULL); +} diff --git a/scripts/kconfig/preprocess.h b/scripts/kconfig/preprocess.h new file mode 100644 index 000000000..a7e4a5506 --- /dev/null +++ b/scripts/kconfig/preprocess.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef PREPROCESS_H +#define PREPROCESS_H + +enum variable_flavor { + VAR_SIMPLE, + VAR_RECURSIVE, + VAR_APPEND, +}; + +struct gstr; +void env_write_dep(struct gstr *gs); +void variable_add(const char *name, const char *value, + enum variable_flavor flavor); +void variable_all_del(void); +char *expand_dollar(const char **str); +char *expand_one_token(const char **str); + +#endif /* PREPROCESS_H */ diff --git a/scripts/kconfig/qconf-cfg.sh b/scripts/kconfig/qconf-cfg.sh new file mode 100755 index 000000000..bb2df6636 --- /dev/null +++ b/scripts/kconfig/qconf-cfg.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +set -eu + +cflags=$1 +libs=$2 +bin=$3 + +PKG5="Qt5Core Qt5Gui Qt5Widgets" +PKG6="Qt6Core Qt6Gui Qt6Widgets" + +if [ -z "$(command -v ${HOSTPKG_CONFIG})" ]; then + echo >&2 "*" + echo >&2 "* 'make xconfig' requires '${HOSTPKG_CONFIG}'. Please install it." + echo >&2 "*" + exit 1 +fi + +if ${HOSTPKG_CONFIG} --exists $PKG6; then + ${HOSTPKG_CONFIG} --cflags ${PKG6} > ${cflags} + # Qt6 requires C++17. + echo -std=c++17 >> ${cflags} + ${HOSTPKG_CONFIG} --libs ${PKG6} > ${libs} + ${HOSTPKG_CONFIG} --variable=libexecdir Qt6Core > ${bin} + exit 0 +fi + +if ${HOSTPKG_CONFIG} --exists $PKG5; then + ${HOSTPKG_CONFIG} --cflags ${PKG5} > ${cflags} + ${HOSTPKG_CONFIG} --libs ${PKG5} > ${libs} + ${HOSTPKG_CONFIG} --variable=host_bins Qt5Core > ${bin} + exit 0 +fi + +echo >&2 "*" +echo >&2 "* Could not find Qt6 or Qt5 via ${HOSTPKG_CONFIG}." +echo >&2 "* Please install Qt6 or Qt5 and make sure it's in PKG_CONFIG_PATH" +echo >&2 "* You need $PKG6 for Qt6" +echo >&2 "* You need $PKG5 for Qt5" +echo >&2 "*" +exit 1 diff --git a/scripts/kconfig/qconf.cc b/scripts/kconfig/qconf.cc new file mode 100644 index 000000000..b02ead7a3 --- /dev/null +++ b/scripts/kconfig/qconf.cc @@ -0,0 +1,1862 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Copyright (C) 2015 Boris Barbulovski <bbarbulovski@gmail.com> + */ + +#include <QAction> +#include <QActionGroup> +#include <QApplication> +#include <QCloseEvent> +#include <QDebug> +#include <QFileDialog> +#include <QLabel> +#include <QLayout> +#include <QList> +#include <QMenu> +#include <QMenuBar> +#include <QMessageBox> +#include <QRegularExpression> +#include <QScreen> +#include <QToolBar> + +#include <stdlib.h> + +#include <xalloc.h> +#include "lkc.h" +#include "qconf.h" + + +static QApplication *configApp; +static ConfigSettings *configSettings; + +QAction *ConfigMainWindow::saveAction; + +ConfigSettings::ConfigSettings() + : QSettings("kernel.org", "qconf") +{ + beginGroup("/kconfig/qconf"); +} + +ConfigSettings::~ConfigSettings() +{ + endGroup(); +} + +/** + * Reads a list of integer values from the application settings. + */ +QList<int> ConfigSettings::readSizes(const QString& key, bool *ok) +{ + QList<int> result; + + if (contains(key)) + { + QStringList entryList = value(key).toStringList(); + QStringList::Iterator it; + + for (it = entryList.begin(); it != entryList.end(); ++it) + result.push_back((*it).toInt()); + + *ok = true; + } + else + *ok = false; + + return result; +} + +/** + * Writes a list of integer values to the application settings. + */ +bool ConfigSettings::writeSizes(const QString& key, const QList<int>& value) +{ + QStringList stringList; + QList<int>::ConstIterator it; + + for (it = value.begin(); it != value.end(); ++it) + stringList.push_back(QString::number(*it)); + setValue(key, stringList); + + return true; +} + +QIcon ConfigItem::symbolYesIcon; +QIcon ConfigItem::symbolModIcon; +QIcon ConfigItem::symbolNoIcon; +QIcon ConfigItem::choiceYesIcon; +QIcon ConfigItem::choiceNoIcon; +QIcon ConfigItem::menuIcon; +QIcon ConfigItem::menubackIcon; + +/* + * update the displayed of a menu entry + */ +void ConfigItem::updateMenu(void) +{ + ConfigList* list; + struct symbol* sym; + QString prompt; + int type; + tristate expr; + + list = listView(); + if (goParent) { + setIcon(promptColIdx, menubackIcon); + prompt = ".."; + goto set_prompt; + } + + sym = menu->sym; + prompt = menu_get_prompt(menu); + + switch (menu->type) { + case M_MENU: + if (list->mode == singleMode) { + /* a menuconfig entry is displayed differently + * depending whether it's at the view root or a child. + */ + if (sym && list->rootEntry == menu) + break; + setIcon(promptColIdx, menuIcon); + } else { + if (sym) + break; + setIcon(promptColIdx, QIcon()); + } + goto set_prompt; + case M_COMMENT: + setIcon(promptColIdx, QIcon()); + prompt = "*** " + prompt + " ***"; + goto set_prompt; + case M_CHOICE: + setIcon(promptColIdx, QIcon()); + sym = sym_calc_choice(menu); + if (sym) + setText(dataColIdx, sym->name); + goto set_prompt; + default: + ; + } + if (!sym) + goto set_prompt; + + setText(nameColIdx, sym->name); + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + char ch; + + if (!sym_is_changeable(sym) && list->optMode == normalOpt) { + setIcon(promptColIdx, QIcon()); + break; + } + expr = sym_get_tristate_value(sym); + switch (expr) { + case yes: + if (sym_is_choice_value(sym)) + setIcon(promptColIdx, choiceYesIcon); + else + setIcon(promptColIdx, symbolYesIcon); + ch = 'Y'; + break; + case mod: + setIcon(promptColIdx, symbolModIcon); + ch = 'M'; + break; + default: + if (sym_is_choice_value(sym)) + setIcon(promptColIdx, choiceNoIcon); + else + setIcon(promptColIdx, symbolNoIcon); + ch = 'N'; + break; + } + + setText(dataColIdx, QChar(ch)); + break; + case S_INT: + case S_HEX: + case S_STRING: + setText(dataColIdx, sym_get_string_value(sym)); + break; + } + if (!sym_has_value(sym)) + prompt += " (NEW)"; +set_prompt: + setText(promptColIdx, prompt); +} + +void ConfigItem::testUpdateMenu(void) +{ + ConfigItem* i; + + if (!menu) + return; + + if (menu->type == M_CHOICE) + sym_calc_choice(menu); + else + sym_calc_value(menu->sym); + + if (menu->flags & MENU_CHANGED) { + /* the menu entry changed, so update all list items */ + menu->flags &= ~MENU_CHANGED; + for (i = (ConfigItem*)menu->data; i; i = i->nextItem) + i->updateMenu(); + } else if (listView()->updateAll) + updateMenu(); +} + + +/* + * construct a menu entry + */ +void ConfigItem::init(void) +{ + if (menu) { + ConfigList* list = listView(); + nextItem = (ConfigItem*)menu->data; + menu->data = this; + + if (list->mode != fullMode) + setExpanded(true); + sym_calc_value(menu->sym); + + if (menu->sym) { + enum symbol_type type = menu->sym->type; + + // Allow to edit "int", "hex", and "string" in-place in + // the data column. Unfortunately, you cannot specify + // the flags per column. Set ItemIsEditable for all + // columns here, and check the column in createEditor(). + if (type == S_INT || type == S_HEX || type == S_STRING) + setFlags(flags() | Qt::ItemIsEditable); + } + } + updateMenu(); +} + +/* + * destruct a menu entry + */ +ConfigItem::~ConfigItem(void) +{ + if (menu) { + ConfigItem** ip = (ConfigItem**)&menu->data; + for (; *ip; ip = &(*ip)->nextItem) { + if (*ip == this) { + *ip = nextItem; + break; + } + } + } +} + +QWidget *ConfigItemDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + ConfigItem *item; + + // Only the data column is editable + if (index.column() != dataColIdx) + return nullptr; + + // You cannot edit invisible menus + item = static_cast<ConfigItem *>(index.internalPointer()); + if (!item || !item->menu || !menu_is_visible(item->menu)) + return nullptr; + + return QStyledItemDelegate::createEditor(parent, option, index); +} + +void ConfigItemDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ + QLineEdit *lineEdit; + ConfigItem *item; + struct symbol *sym; + bool success; + + lineEdit = qobject_cast<QLineEdit *>(editor); + // If this is not a QLineEdit, use the parent's default. + // (does this happen?) + if (!lineEdit) + goto parent; + + item = static_cast<ConfigItem *>(index.internalPointer()); + if (!item || !item->menu) + goto parent; + + sym = item->menu->sym; + if (!sym) + goto parent; + + success = sym_set_string_value(sym, lineEdit->text().toUtf8().data()); + if (success) { + ConfigList::updateListForAll(); + } else { + QMessageBox::information(editor, "qconf", + "Cannot set the data (maybe due to out of range).\n" + "Setting the old value."); + lineEdit->setText(sym_get_string_value(sym)); + } + +parent: + QStyledItemDelegate::setModelData(editor, model, index); +} + +ConfigList::ConfigList(QWidget *parent, const char *name) + : QTreeWidget(parent), + updateAll(false), + showName(false), mode(singleMode), optMode(normalOpt), + rootEntry(0), headerPopup(0) +{ + setObjectName(name); + setSortingEnabled(false); + + setVerticalScrollMode(ScrollPerPixel); + setHorizontalScrollMode(ScrollPerPixel); + + setHeaderLabels(QStringList() << "Option" << "Name" << "Value"); + + connect(this, &ConfigList::itemSelectionChanged, + this, &ConfigList::updateSelection); + + if (name) { + configSettings->beginGroup(name); + showName = configSettings->value("/showName", false).toBool(); + optMode = (enum optionMode)configSettings->value("/optionMode", 0).toInt(); + configSettings->endGroup(); + connect(configApp, &QApplication::aboutToQuit, + this, &ConfigList::saveSettings); + } + + showColumn(promptColIdx); + + setItemDelegate(new ConfigItemDelegate(this)); + + allLists.append(this); + + reinit(); +} + +ConfigList::~ConfigList() +{ + allLists.removeOne(this); +} + +bool ConfigList::menuSkip(struct menu *menu) +{ + if (optMode == normalOpt && menu_is_visible(menu)) + return false; + if (optMode == promptOpt && menu_has_prompt(menu)) + return false; + if (optMode == allOpt) + return false; + return true; +} + +void ConfigList::reinit(void) +{ + hideColumn(nameColIdx); + + if (showName) + showColumn(nameColIdx); + + updateListAll(); +} + +void ConfigList::setOptionMode(QAction *action) +{ + if (action == showNormalAction) + optMode = normalOpt; + else if (action == showAllAction) + optMode = allOpt; + else + optMode = promptOpt; + + updateListAll(); +} + +void ConfigList::saveSettings(void) +{ + if (!objectName().isEmpty()) { + configSettings->beginGroup(objectName()); + configSettings->setValue("/showName", showName); + configSettings->setValue("/optionMode", (int)optMode); + configSettings->endGroup(); + } +} + +ConfigItem* ConfigList::findConfigItem(struct menu *menu) +{ + ConfigItem* item = (ConfigItem*)menu->data; + + for (; item; item = item->nextItem) { + if (this == item->listView()) + break; + } + + return item; +} + +void ConfigList::updateSelection(void) +{ + struct menu *menu; + enum prop_type type; + + if (selectedItems().count() == 0) + return; + + ConfigItem* item = (ConfigItem*)selectedItems().first(); + if (!item) + return; + + menu = item->menu; + emit menuChanged(menu); + if (!menu) + return; + type = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (mode == menuMode && type == P_MENU) + emit menuSelected(menu); +} + +void ConfigList::updateList() +{ + ConfigItem* last = 0; + ConfigItem *item; + + if (!rootEntry) { + if (mode != listMode) + goto update; + QTreeWidgetItemIterator it(this); + + while (*it) { + item = (ConfigItem*)(*it); + if (!item->menu) + continue; + item->testUpdateMenu(); + + ++it; + } + return; + } + + if (rootEntry != &rootmenu && mode == singleMode) { + item = (ConfigItem *)topLevelItem(0); + if (!item) + 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); + else + item->testUpdateMenu(); + + updateMenuList(item, rootEntry); + update(); + resizeColumnToContents(0); + return; + } +update: + updateMenuList(rootEntry); + update(); + resizeColumnToContents(0); +} + +void ConfigList::updateListForAll() +{ + QListIterator<ConfigList *> it(allLists); + + while (it.hasNext()) { + ConfigList *list = it.next(); + + list->updateList(); + } +} + +void ConfigList::updateListAllForAll() +{ + QListIterator<ConfigList *> it(allLists); + + while (it.hasNext()) { + ConfigList *list = it.next(); + + list->updateListAll(); + } +} + +void ConfigList::setValue(ConfigItem* item, tristate val) +{ + struct symbol* sym; + int type; + tristate oldval; + + sym = item->menu ? item->menu->sym : 0; + if (!sym) + return; + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + oldval = sym_get_tristate_value(sym); + + if (!sym_set_tristate_value(sym, val)) + return; + if (oldval == no && item->menu->list) + item->setExpanded(true); + ConfigList::updateListForAll(); + break; + } +} + +void ConfigList::changeValue(ConfigItem* item) +{ + struct symbol* sym; + struct menu* menu; + int type, oldexpr, newexpr; + + menu = item->menu; + if (!menu) + return; + sym = menu->sym; + if (!sym) { + if (item->menu->list) + item->setExpanded(!item->isExpanded()); + return; + } + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + oldexpr = sym_get_tristate_value(sym); + newexpr = sym_toggle_tristate_value(sym); + if (item->menu->list) { + if (oldexpr == newexpr) + item->setExpanded(!item->isExpanded()); + else if (oldexpr == no) + item->setExpanded(true); + } + if (oldexpr != newexpr) + ConfigList::updateListForAll(); + break; + default: + break; + } +} + +void ConfigList::setRootMenu(struct menu *menu) +{ + enum prop_type type; + + if (rootEntry == menu) + return; + type = menu && menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (type != P_MENU) + return; + updateMenuList(0); + rootEntry = menu; + updateListAll(); + if (currentItem()) { + setSelected(currentItem(), hasFocus()); + scrollToItem(currentItem()); + } +} + +void ConfigList::setParentMenu(void) +{ + ConfigItem* item; + struct menu *oldroot; + + oldroot = rootEntry; + if (rootEntry == &rootmenu) + return; + setRootMenu(menu_get_menu_or_parent_menu(rootEntry->parent)); + + QTreeWidgetItemIterator it(this); + while (*it) { + item = (ConfigItem *)(*it); + if (item->menu == oldroot) { + setCurrentItem(item); + scrollToItem(item); + break; + } + + ++it; + } +} + +/* + * update all the children of a menu entry + * removes/adds the entries from the parent widget as necessary + * + * parent: either the menu list widget or a menu entry widget + * menu: entry to be updated + */ +void ConfigList::updateMenuList(ConfigItem *parent, struct menu* menu) +{ + struct menu* child; + ConfigItem* item; + ConfigItem* last; + enum prop_type type; + + if (!menu) { + while (parent->childCount() > 0) + { + delete parent->takeChild(0); + } + + return; + } + + last = parent->firstChild(); + if (last && !last->goParent) + last = 0; + for (child = menu->list; child; child = child->next) { + item = last ? last->nextSibling() : parent->firstChild(); + type = child->prompt ? child->prompt->type : P_UNKNOWN; + + switch (mode) { + case menuMode: + if (!(child->flags & MENU_ROOT)) + goto hide; + break; + case symbolMode: + if (child->flags & MENU_ROOT) + goto hide; + break; + default: + break; + } + + if (!menuSkip(child)) { + if (!child->sym && !child->list && !child->prompt) + continue; + if (!item || item->menu != child) + item = new ConfigItem(parent, last, child); + else + item->testUpdateMenu(); + + if (mode == fullMode || mode == menuMode || type != P_MENU) + updateMenuList(item, child); + else + updateMenuList(item, 0); + last = item; + continue; + } +hide: + if (item && item->menu == child) { + last = parent->firstChild(); + if (last == item) + last = 0; + else while (last->nextSibling() != item) + last = last->nextSibling(); + delete item; + } + } +} + +void ConfigList::updateMenuList(struct menu *menu) +{ + struct menu* child; + ConfigItem* item; + ConfigItem* last; + enum prop_type type; + + if (!menu) { + while (topLevelItemCount() > 0) + { + delete takeTopLevelItem(0); + } + + return; + } + + last = (ConfigItem *)topLevelItem(0); + if (last && !last->goParent) + last = 0; + for (child = menu->list; child; child = child->next) { + item = last ? last->nextSibling() : (ConfigItem *)topLevelItem(0); + type = child->prompt ? child->prompt->type : P_UNKNOWN; + + switch (mode) { + case menuMode: + if (!(child->flags & MENU_ROOT)) + goto hide; + break; + case symbolMode: + if (child->flags & MENU_ROOT) + goto hide; + break; + default: + break; + } + + if (!menuSkip(child)) { + if (!child->sym && !child->list && !child->prompt) + continue; + if (!item || item->menu != child) + item = new ConfigItem(this, last, child); + else + item->testUpdateMenu(); + + if (mode == fullMode || mode == menuMode || type != P_MENU) + updateMenuList(item, child); + else + updateMenuList(item, 0); + last = item; + continue; + } +hide: + if (item && item->menu == child) { + last = (ConfigItem *)topLevelItem(0); + if (last == item) + last = 0; + else while (last->nextSibling() != item) + last = last->nextSibling(); + delete item; + } + } +} + +void ConfigList::keyPressEvent(QKeyEvent* ev) +{ + QTreeWidgetItem* i = currentItem(); + ConfigItem* item; + struct menu *menu; + enum prop_type type; + + if (ev->key() == Qt::Key_Escape && mode == singleMode) { + emit parentSelected(); + ev->accept(); + return; + } + + if (!i) { + Parent::keyPressEvent(ev); + return; + } + item = (ConfigItem*)i; + + switch (ev->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + if (item->goParent) { + emit parentSelected(); + break; + } + menu = item->menu; + if (!menu) + break; + type = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (type == P_MENU && rootEntry != menu && + mode != fullMode && mode != menuMode) { + if (mode == menuMode) + emit menuSelected(menu); + else + emit itemSelected(menu); + break; + } + case Qt::Key_Space: + changeValue(item); + break; + case Qt::Key_N: + setValue(item, no); + break; + case Qt::Key_M: + setValue(item, mod); + break; + case Qt::Key_Y: + setValue(item, yes); + break; + default: + Parent::keyPressEvent(ev); + return; + } + ev->accept(); +} + +void ConfigList::mouseReleaseEvent(QMouseEvent* e) +{ + QPoint p = e->pos(); + ConfigItem* item = (ConfigItem*)itemAt(p); + struct menu *menu; + enum prop_type ptype; + QIcon icon; + int idx, x; + + if (!item) + goto skip; + + menu = item->menu; + x = header()->offset() + p.x(); + idx = header()->logicalIndexAt(x); + switch (idx) { + case promptColIdx: + icon = item->icon(promptColIdx); + if (!icon.isNull()) { + int off = header()->sectionPosition(0) + visualRect(indexAt(p)).x() + 4; // 4 is Hardcoded image offset. There might be a way to do it properly. + if (x >= off && x < off + icon.availableSizes().first().width()) { + if (item->goParent) { + emit parentSelected(); + break; + } else if (!menu) + break; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (ptype == P_MENU && rootEntry != menu && + mode != fullMode && mode != menuMode && + mode != listMode) + emit menuSelected(menu); + else + changeValue(item); + } + } + break; + case dataColIdx: + changeValue(item); + break; + } + +skip: + //printf("contentsMouseReleaseEvent: %d,%d\n", p.x(), p.y()); + Parent::mouseReleaseEvent(e); +} + +void ConfigList::mouseDoubleClickEvent(QMouseEvent* e) +{ + QPoint p = e->pos(); + ConfigItem* item = (ConfigItem*)itemAt(p); + struct menu *menu; + enum prop_type ptype; + + if (!item) + goto skip; + if (item->goParent) { + emit parentSelected(); + goto skip; + } + menu = item->menu; + if (!menu) + goto skip; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (ptype == P_MENU && mode != listMode) { + if (mode == singleMode) + emit itemSelected(menu); + else if (mode == symbolMode) + emit menuSelected(menu); + } else if (menu->sym) + changeValue(item); + +skip: + //printf("contentsMouseDoubleClickEvent: %d,%d\n", p.x(), p.y()); + Parent::mouseDoubleClickEvent(e); +} + +void ConfigList::focusInEvent(QFocusEvent *e) +{ + struct menu *menu = NULL; + + Parent::focusInEvent(e); + + ConfigItem* item = (ConfigItem *)currentItem(); + if (item) { + setSelected(item, true); + menu = item->menu; + } + emit gotFocus(menu); +} + +void ConfigList::contextMenuEvent(QContextMenuEvent *e) +{ + if (!headerPopup) { + QAction *action; + + headerPopup = new QMenu(this); + action = new QAction("Show Name", this); + action->setCheckable(true); + connect(action, &QAction::toggled, + this, &ConfigList::setShowName); + connect(this, &ConfigList::showNameChanged, + action, &QAction::setChecked); + action->setChecked(showName); + headerPopup->addAction(action); + } + + headerPopup->exec(e->globalPos()); + e->accept(); +} + +void ConfigList::setShowName(bool on) +{ + if (showName == on) + return; + + showName = on; + reinit(); + emit showNameChanged(on); +} + +QList<ConfigList *> ConfigList::allLists; +QAction *ConfigList::showNormalAction; +QAction *ConfigList::showAllAction; +QAction *ConfigList::showPromptAction; + +void ConfigList::setAllOpen(bool open) +{ + QTreeWidgetItemIterator it(this); + + while (*it) { + (*it)->setExpanded(open); + + ++it; + } +} + +ConfigInfoView::ConfigInfoView(QWidget* parent, const char *name) + : Parent(parent), sym(0), _menu(0) +{ + setObjectName(name); + setOpenLinks(false); + + if (!objectName().isEmpty()) { + configSettings->beginGroup(objectName()); + setShowDebug(configSettings->value("/showDebug", false).toBool()); + configSettings->endGroup(); + connect(configApp, &QApplication::aboutToQuit, + this, &ConfigInfoView::saveSettings); + } + + contextMenu = createStandardContextMenu(); + QAction *action = new QAction("Show Debug Info", contextMenu); + + action->setCheckable(true); + connect(action, &QAction::toggled, + this, &ConfigInfoView::setShowDebug); + connect(this, &ConfigInfoView::showDebugChanged, + action, &QAction::setChecked); + action->setChecked(showDebug()); + contextMenu->addSeparator(); + contextMenu->addAction(action); +} + +void ConfigInfoView::saveSettings(void) +{ + if (!objectName().isEmpty()) { + configSettings->beginGroup(objectName()); + configSettings->setValue("/showDebug", showDebug()); + configSettings->endGroup(); + } +} + +void ConfigInfoView::setShowDebug(bool b) +{ + if (_showDebug != b) { + _showDebug = b; + if (_menu) + menuInfo(); + else if (sym) + symbolInfo(); + emit showDebugChanged(b); + } +} + +void ConfigInfoView::setInfo(struct menu *m) +{ + if (_menu == m) + return; + _menu = m; + sym = NULL; + if (!_menu) + clear(); + else + menuInfo(); +} + +void ConfigInfoView::symbolInfo(void) +{ + QString str; + + str += "<big>Symbol: <b>"; + str += print_filter(sym->name); + str += "</b></big><br><br>value: "; + str += print_filter(sym_get_string_value(sym)); + str += "<br>visibility: "; + str += sym->visible == yes ? "y" : sym->visible == mod ? "m" : "n"; + str += "<br>"; + str += debug_info(sym); + + setText(str); +} + +void ConfigInfoView::menuInfo(void) +{ + struct symbol* sym; + QString info; + QTextStream stream(&info); + + sym = _menu->sym; + if (sym) { + if (_menu->prompt) { + stream << "<big><b>"; + stream << print_filter(_menu->prompt->text); + stream << "</b></big>"; + if (sym->name) { + stream << " ("; + if (showDebug()) + stream << "<a href=\"" << sym->name << "\">"; + stream << print_filter(sym->name); + if (showDebug()) + stream << "</a>"; + stream << ")"; + } + } else if (sym->name) { + stream << "<big><b>"; + if (showDebug()) + stream << "<a href=\"" << sym->name << "\">"; + stream << print_filter(sym->name); + if (showDebug()) + stream << "</a>"; + stream << "</b></big>"; + } + stream << "<br><br>"; + + if (showDebug()) + stream << debug_info(sym); + + struct gstr help_gstr = str_new(); + + menu_get_ext_help(_menu, &help_gstr); + stream << print_filter(str_get(&help_gstr)); + str_free(&help_gstr); + } else if (_menu->prompt) { + stream << "<big><b>"; + stream << print_filter(_menu->prompt->text); + stream << "</b></big><br><br>"; + if (showDebug()) { + if (_menu->prompt->visible.expr) { + stream << "  dep: "; + expr_print(_menu->prompt->visible.expr, + expr_print_help, &stream, E_NONE); + stream << "<br><br>"; + } + + stream << "defined at " << _menu->filename << ":" + << _menu->lineno << "<br><br>"; + } + } + + setText(info); +} + +QString ConfigInfoView::debug_info(struct symbol *sym) +{ + QString debug; + QTextStream stream(&debug); + + stream << "type: "; + stream << print_filter(sym_type_name(sym->type)); + if (sym_is_choice(sym)) + stream << " (choice)"; + debug += "<br>"; + if (sym->rev_dep.expr) { + stream << "reverse dep: "; + expr_print(sym->rev_dep.expr, expr_print_help, &stream, E_NONE); + stream << "<br>"; + } + for (struct property *prop = sym->prop; prop; prop = prop->next) { + switch (prop->type) { + case P_PROMPT: + case P_MENU: + stream << "prompt: "; + stream << print_filter(prop->text); + stream << "<br>"; + break; + case P_DEFAULT: + case P_SELECT: + case P_RANGE: + case P_COMMENT: + case P_IMPLY: + stream << prop_get_type_name(prop->type); + stream << ": "; + expr_print(prop->expr, expr_print_help, + &stream, E_NONE); + stream << "<br>"; + break; + default: + stream << "unknown property: "; + stream << prop_get_type_name(prop->type); + stream << "<br>"; + } + if (prop->visible.expr) { + stream << "    dep: "; + expr_print(prop->visible.expr, expr_print_help, + &stream, E_NONE); + stream << "<br>"; + } + } + stream << "<br>"; + + return debug; +} + +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;) { + const QString n = patterns.value(res[i], QString()); + if (!n.isEmpty()) { + res.replace(i, 1, n); + i += n.length(); + } + } + return res; +} + +void ConfigInfoView::expr_print_help(void *data, struct symbol *sym, const char *str) +{ + QTextStream *stream = reinterpret_cast<QTextStream *>(data); + + if (sym && sym->name && !(sym->flags & SYMBOL_CONST)) { + *stream << "<a href=\"" << sym->name << "\">"; + *stream << print_filter(str); + *stream << "</a>"; + } else { + *stream << print_filter(str); + } +} + +void ConfigInfoView::clicked(const QUrl &url) +{ + struct menu *m; + + sym = sym_find(url.toEncoded().constData()); + + m = sym_get_prompt_menu(sym); + if (!m) { + /* Symbol is not visible as a menu */ + symbolInfo(); + emit showDebugChanged(true); + } else { + emit menuSelected(m); + } +} + +void ConfigInfoView::contextMenuEvent(QContextMenuEvent *event) +{ + contextMenu->popup(event->globalPos()); + event->accept(); +} + +ConfigSearchWindow::ConfigSearchWindow(ConfigMainWindow *parent) + : Parent(parent), result(NULL) +{ + setObjectName("search"); + setWindowTitle("Search Config"); + + QVBoxLayout* layout1 = new QVBoxLayout(this); + layout1->setContentsMargins(11, 11, 11, 11); + layout1->setSpacing(6); + + QHBoxLayout* layout2 = new QHBoxLayout(); + layout2->setContentsMargins(0, 0, 0, 0); + layout2->setSpacing(6); + layout2->addWidget(new QLabel("Find:", this)); + editField = new QLineEdit(this); + connect(editField, &QLineEdit::returnPressed, + this, &ConfigSearchWindow::search); + layout2->addWidget(editField); + searchButton = new QPushButton("Search", this); + searchButton->setAutoDefault(false); + connect(searchButton, &QPushButton::clicked, + this, &ConfigSearchWindow::search); + layout2->addWidget(searchButton); + layout1->addLayout(layout2); + + split = new QSplitter(Qt::Vertical, this); + list = new ConfigList(split, "search"); + list->mode = listMode; + info = new ConfigInfoView(split, "search"); + connect(list, &ConfigList::menuChanged, + info, &ConfigInfoView::setInfo); + connect(list, &ConfigList::menuChanged, + parent, &ConfigMainWindow::setMenuLink); + + layout1->addWidget(split); + + QVariant x, y; + int width, height; + bool ok; + + configSettings->beginGroup("search"); + width = configSettings->value("/window width", parent->width() / 2).toInt(); + height = configSettings->value("/window height", parent->height() / 2).toInt(); + resize(width, height); + x = configSettings->value("/window x"); + y = configSettings->value("/window y"); + if (x.isValid() && y.isValid()) + move(x.toInt(), y.toInt()); + QList<int> sizes = configSettings->readSizes("/split", &ok); + if (ok) + split->setSizes(sizes); + configSettings->endGroup(); + connect(configApp, &QApplication::aboutToQuit, + this, &ConfigSearchWindow::saveSettings); +} + +void ConfigSearchWindow::saveSettings(void) +{ + if (!objectName().isEmpty()) { + configSettings->beginGroup(objectName()); + configSettings->setValue("/window x", pos().x()); + configSettings->setValue("/window y", pos().y()); + configSettings->setValue("/window width", size().width()); + configSettings->setValue("/window height", size().height()); + configSettings->writeSizes("/split", split->sizes()); + configSettings->endGroup(); + } +} + +void ConfigSearchWindow::search(void) +{ + struct symbol **p; + struct property *prop; + ConfigItem *lastItem = NULL; + + free(result); + list->clear(); + info->clear(); + + result = sym_re_search(editField->text().toLatin1()); + if (!result) + return; + for (p = result; *p; p++) { + for_all_prompts((*p), prop) + lastItem = new ConfigItem(list, lastItem, prop->menu); + } +} + +/* + * Construct the complete config widget + */ +ConfigMainWindow::ConfigMainWindow(void) + : searchWindow(0) +{ + bool ok = true; + QVariant x, y; + int width, height; + char title[256]; + + snprintf(title, sizeof(title), "%s%s", + rootmenu.prompt->text, + "" + ); + setWindowTitle(title); + + QRect g = configApp->primaryScreen()->geometry(); + width = configSettings->value("/window width", g.width() - 64).toInt(); + height = configSettings->value("/window height", g.height() - 64).toInt(); + resize(width, height); + x = configSettings->value("/window x"); + y = configSettings->value("/window y"); + if ((x.isValid())&&(y.isValid())) + move(x.toInt(), y.toInt()); + + // set up icons + QString iconsDir = QString(getenv(SRCTREE) ? getenv(SRCTREE) : QDir::currentPath()) + "/scripts/kconfig/icons/"; + ConfigItem::symbolYesIcon = QIcon(QPixmap(iconsDir + "symbol_yes.xpm")); + ConfigItem::symbolModIcon = QIcon(QPixmap(iconsDir + "symbol_mod.xpm")); + ConfigItem::symbolNoIcon = QIcon(QPixmap(iconsDir + "symbol_no.xpm")); + ConfigItem::choiceYesIcon = QIcon(QPixmap(iconsDir + "choice_yes.xpm")); + ConfigItem::choiceNoIcon = QIcon(QPixmap(iconsDir + "choice_no.xpm")); + ConfigItem::menuIcon = QIcon(QPixmap(iconsDir + "menu.xpm")); + ConfigItem::menubackIcon = QIcon(QPixmap(iconsDir + "menuback.xpm")); + + QWidget *widget = new QWidget(this); + setCentralWidget(widget); + + QVBoxLayout *layout = new QVBoxLayout(widget); + + split2 = new QSplitter(Qt::Vertical, widget); + layout->addWidget(split2); + split2->setChildrenCollapsible(false); + + split1 = new QSplitter(Qt::Horizontal, split2); + split1->setChildrenCollapsible(false); + + configList = new ConfigList(split1, "config"); + + menuList = new ConfigList(split1, "menu"); + + helpText = new ConfigInfoView(split2, "help"); + setTabOrder(configList, helpText); + + configList->setFocus(); + + backAction = new QAction(QPixmap(iconsDir + "back.xpm"), "Back", this); + backAction->setShortcut(QKeySequence::Back); + connect(backAction, &QAction::triggered, + this, &ConfigMainWindow::goBack); + + QAction *quitAction = new QAction("&Quit", this); + quitAction->setShortcut(QKeySequence::Quit); + connect(quitAction, &QAction::triggered, + this, &ConfigMainWindow::close); + + QAction *loadAction = new QAction(QPixmap(iconsDir + "load.xpm"), "&Open", this); + loadAction->setShortcut(QKeySequence::Open); + connect(loadAction, &QAction::triggered, + this, &ConfigMainWindow::loadConfig); + + saveAction = new QAction(QPixmap(iconsDir + "save.xpm"), "&Save", this); + saveAction->setShortcut(QKeySequence::Save); + connect(saveAction, &QAction::triggered, + this, &ConfigMainWindow::saveConfig); + + conf_set_changed_callback(conf_changed); + + 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(QKeySequence::Find); + connect(searchAction, &QAction::triggered, + this, &ConfigMainWindow::searchConfig); + singleViewAction = new QAction(QPixmap(iconsDir + "single_view.xpm"), "Single View", this); + singleViewAction->setCheckable(true); + connect(singleViewAction, &QAction::triggered, + this, &ConfigMainWindow::showSingleView); + splitViewAction = new QAction(QPixmap(iconsDir + "split_view.xpm"), "Split View", this); + splitViewAction->setCheckable(true); + connect(splitViewAction, &QAction::triggered, + this, &ConfigMainWindow::showSplitView); + fullViewAction = new QAction(QPixmap(iconsDir + "tree_view.xpm"), "Full View", this); + fullViewAction->setCheckable(true); + connect(fullViewAction, &QAction::triggered, + this, &ConfigMainWindow::showFullView); + + QAction *showNameAction = new QAction("Show Name", this); + showNameAction->setCheckable(true); + connect(showNameAction, &QAction::toggled, + configList, &ConfigList::setShowName); + showNameAction->setChecked(configList->showName); + + QActionGroup *optGroup = new QActionGroup(this); + optGroup->setExclusive(true); + connect(optGroup, &QActionGroup::triggered, + configList, &ConfigList::setOptionMode); + connect(optGroup, &QActionGroup::triggered, + menuList, &ConfigList::setOptionMode); + + ConfigList::showNormalAction = new QAction("Show Normal Options", optGroup); + ConfigList::showNormalAction->setCheckable(true); + ConfigList::showAllAction = new QAction("Show All Options", optGroup); + ConfigList::showAllAction->setCheckable(true); + ConfigList::showPromptAction = new QAction("Show Prompt Options", optGroup); + ConfigList::showPromptAction->setCheckable(true); + + switch (configList->optMode) { + case allOpt: + ConfigList::showAllAction->setChecked(true); + break; + case promptOpt: + ConfigList::showPromptAction->setChecked(true); + break; + case normalOpt: + default: + ConfigList::showNormalAction->setChecked(true); + break; + } + + QAction *showDebugAction = new QAction("Show Debug Info", this); + showDebugAction->setCheckable(true); + connect(showDebugAction, &QAction::toggled, + helpText, &ConfigInfoView::setShowDebug); + showDebugAction->setChecked(helpText->showDebug()); + + QAction *showIntroAction = new QAction("Introduction", this); + connect(showIntroAction, &QAction::triggered, + this, &ConfigMainWindow::showIntro); + QAction *showAboutAction = new QAction("About", this); + connect(showAboutAction, &QAction::triggered, + this, &ConfigMainWindow::showAbout); + + // init tool bar + QToolBar *toolBar = addToolBar("Tools"); + toolBar->addAction(backAction); + toolBar->addSeparator(); + toolBar->addAction(loadAction); + toolBar->addAction(saveAction); + toolBar->addSeparator(); + toolBar->addAction(singleViewAction); + toolBar->addAction(splitViewAction); + toolBar->addAction(fullViewAction); + + // create file menu + QMenu *menu = menuBar()->addMenu("&File"); + menu->addAction(loadAction); + menu->addAction(saveAction); + menu->addAction(saveAsAction); + menu->addSeparator(); + menu->addAction(quitAction); + + // create edit menu + menu = menuBar()->addMenu("&Edit"); + menu->addAction(searchAction); + + // create options menu + menu = menuBar()->addMenu("&Option"); + menu->addAction(showNameAction); + menu->addSeparator(); + menu->addActions(optGroup->actions()); + menu->addSeparator(); + menu->addAction(showDebugAction); + + // create help menu + menu = menuBar()->addMenu("&Help"); + menu->addAction(showIntroAction); + menu->addAction(showAboutAction); + + connect(helpText, &ConfigInfoView::anchorClicked, + helpText, &ConfigInfoView::clicked); + + connect(configList, &ConfigList::menuChanged, + helpText, &ConfigInfoView::setInfo); + connect(configList, &ConfigList::menuSelected, + this, &ConfigMainWindow::changeMenu); + connect(configList, &ConfigList::itemSelected, + this, &ConfigMainWindow::changeItens); + connect(configList, &ConfigList::parentSelected, + this, &ConfigMainWindow::goBack); + connect(menuList, &ConfigList::menuChanged, + helpText, &ConfigInfoView::setInfo); + connect(menuList, &ConfigList::menuSelected, + this, &ConfigMainWindow::changeMenu); + + connect(configList, &ConfigList::gotFocus, + helpText, &ConfigInfoView::setInfo); + connect(menuList, &ConfigList::gotFocus, + helpText, &ConfigInfoView::setInfo); + connect(menuList, &ConfigList::gotFocus, + this, &ConfigMainWindow::listFocusChanged); + 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(); + else if (listMode == "full") + showFullView(); + else /*if (listMode == "split")*/ + showSplitView(); + + // UI setup done, restore splitter positions + QList<int> sizes = configSettings->readSizes("/split1", &ok); + if (ok) + split1->setSizes(sizes); + + sizes = configSettings->readSizes("/split2", &ok); + if (ok) + split2->setSizes(sizes); +} + +void ConfigMainWindow::loadConfig(void) +{ + QString str; + + str = QFileDialog::getOpenFileName(this, QString(), configname); + if (str.isEmpty()) + return; + + if (conf_read(str.toLocal8Bit().constData())) + QMessageBox::information(this, "qconf", "Unable to load configuration!"); + + configname = str; + + ConfigList::updateListAllForAll(); +} + +bool ConfigMainWindow::saveConfig(void) +{ + if (conf_write(configname.toLocal8Bit().constData())) { + QMessageBox::information(this, "qconf", "Unable to save configuration!"); + return false; + } + conf_write_autoconf(0); + + return true; +} + +void ConfigMainWindow::saveConfigAs(void) +{ + QString str; + + str = QFileDialog::getSaveFileName(this, QString(), configname); + if (str.isEmpty()) + return; + + if (conf_write(str.toLocal8Bit().constData())) { + QMessageBox::information(this, "qconf", "Unable to save configuration!"); + } + conf_write_autoconf(0); + + configname = str; +} + +void ConfigMainWindow::searchConfig(void) +{ + if (!searchWindow) + searchWindow = new ConfigSearchWindow(this); + searchWindow->show(); +} + +void ConfigMainWindow::changeItens(struct menu *menu) +{ + configList->setRootMenu(menu); +} + +void ConfigMainWindow::changeMenu(struct menu *menu) +{ + menuList->setRootMenu(menu); +} + +void ConfigMainWindow::setMenuLink(struct menu *menu) +{ + struct menu *parent; + ConfigList* list = NULL; + ConfigItem* item; + + if (configList->menuSkip(menu)) + return; + + switch (configList->mode) { + case singleMode: + list = configList; + parent = menu_get_menu_or_parent_menu(menu); + if (!parent) + return; + list->setRootMenu(parent); + break; + case menuMode: + if (menu->flags & MENU_ROOT) { + menuList->setRootMenu(menu); + configList->clearSelection(); + list = configList; + } else { + parent = menu_get_menu_or_parent_menu(menu->parent); + if (!parent) + return; + + /* Select the config view */ + item = configList->findConfigItem(parent); + if (item) { + configList->setSelected(item, true); + configList->scrollToItem(item); + } + + menuList->setRootMenu(parent); + menuList->clearSelection(); + list = menuList; + } + break; + case fullMode: + list = configList; + break; + default: + break; + } + + if (list) { + item = list->findConfigItem(menu); + if (item) { + list->setSelected(item, true); + list->scrollToItem(item); + list->setFocus(); + helpText->setInfo(menu); + } + } +} + +void ConfigMainWindow::listFocusChanged(void) +{ + if (menuList->mode == menuMode) + configList->clearSelection(); +} + +void ConfigMainWindow::goBack(void) +{ + configList->setParentMenu(); +} + +void ConfigMainWindow::showSingleView(void) +{ + singleViewAction->setEnabled(false); + singleViewAction->setChecked(true); + splitViewAction->setEnabled(true); + splitViewAction->setChecked(false); + fullViewAction->setEnabled(true); + fullViewAction->setChecked(false); + + backAction->setEnabled(true); + + menuList->hide(); + menuList->setRootMenu(0); + configList->mode = singleMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setFocus(); +} + +void ConfigMainWindow::showSplitView(void) +{ + singleViewAction->setEnabled(true); + singleViewAction->setChecked(false); + splitViewAction->setEnabled(false); + splitViewAction->setChecked(true); + fullViewAction->setEnabled(true); + fullViewAction->setChecked(false); + + backAction->setEnabled(false); + + configList->mode = menuMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setAllOpen(true); + configApp->processEvents(); + menuList->mode = symbolMode; + menuList->setRootMenu(&rootmenu); + menuList->setAllOpen(true); + menuList->show(); + menuList->setFocus(); +} + +void ConfigMainWindow::showFullView(void) +{ + singleViewAction->setEnabled(true); + singleViewAction->setChecked(false); + splitViewAction->setEnabled(true); + splitViewAction->setChecked(false); + fullViewAction->setEnabled(false); + fullViewAction->setChecked(true); + + backAction->setEnabled(false); + + menuList->hide(); + menuList->setRootMenu(0); + configList->mode = fullMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setFocus(); +} + +/* + * ask for saving configuration before quitting + */ +void ConfigMainWindow::closeEvent(QCloseEvent* e) +{ + if (!conf_get_changed()) { + e->accept(); + return; + } + + QMessageBox mb(QMessageBox::Icon::Warning, "qconf", + "Save configuration?"); + + QPushButton *yb = mb.addButton(QMessageBox::Yes); + QPushButton *db = mb.addButton(QMessageBox::No); + QPushButton *cb = mb.addButton(QMessageBox::Cancel); + + yb->setText("&Save Changes"); + db->setText("&Discard Changes"); + cb->setText("Cancel Exit"); + + mb.setDefaultButton(yb); + mb.setEscapeButton(cb); + + switch (mb.exec()) { + case QMessageBox::Yes: + if (saveConfig()) + e->accept(); + else + e->ignore(); + break; + case QMessageBox::No: + e->accept(); + break; + case QMessageBox::Cancel: + e->ignore(); + break; + } +} + +void ConfigMainWindow::showIntro(void) +{ + static const QString str = + "Welcome to the qconf graphical configuration tool.\n" + "\n" + "For bool and tristate options, a blank box indicates the " + "feature is disabled, a check indicates it is enabled, and a " + "dot indicates that it is to be compiled as a module. Clicking " + "on the box will cycle through the three states. For int, hex, " + "and string options, double-clicking or pressing F2 on the " + "Value cell will allow you to edit the value.\n" + "\n" + "If you do not see an option (e.g., a device driver) that you " + "believe should be present, try turning on Show All Options " + "under the Options menu. Enabling Show Debug Info will help you" + "figure out what other options must be enabled to support the " + "option you are interested in, and hyperlinks will navigate to " + "them.\n" + "\n" + "Toggling Show Debug Info under the Options menu will show the " + "dependencies, which you can then match by examining other " + "options.\n"; + + QMessageBox::information(this, "qconf", str); +} + +void ConfigMainWindow::showAbout(void) +{ + static const QString str = "qconf is Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>.\n" + "Copyright (C) 2015 Boris Barbulovski <bbarbulovski@gmail.com>.\n" + "\n" + "Bug reports and feature request can also be entered at http://bugzilla.kernel.org/\n" + "\n" + "Qt Version: "; + + QMessageBox::information(this, "qconf", str + qVersion()); +} + +void ConfigMainWindow::saveSettings(void) +{ + configSettings->setValue("/window x", pos().x()); + configSettings->setValue("/window y", pos().y()); + configSettings->setValue("/window width", size().width()); + configSettings->setValue("/window height", size().height()); + + QString entry; + switch(configList->mode) { + case singleMode : + entry = "single"; + break; + + case symbolMode : + entry = "split"; + break; + + case fullMode : + entry = "full"; + break; + + default: + break; + } + configSettings->setValue("/listMode", entry); + + configSettings->writeSizes("/split1", split1->sizes()); + configSettings->writeSizes("/split2", split2->sizes()); +} + +void ConfigMainWindow::conf_changed(bool dirty) +{ + if (saveAction) + saveAction->setEnabled(dirty); +} + +void fixup_rootmenu(struct menu *menu) +{ + struct menu *child; + static int menu_cnt = 0; + + menu->flags |= MENU_ROOT; + for (child = menu->list; child; child = child->next) { + if (child->prompt && child->prompt->type == P_MENU) { + menu_cnt++; + fixup_rootmenu(child); + menu_cnt--; + } else if (!menu_cnt) + fixup_rootmenu(child); + } +} + +static const char *progname; + +static void usage(void) +{ + printf("%s [-s] <config>\n", progname); + exit(0); +} + +int main(int ac, char** av) +{ + ConfigMainWindow* v; + const char *name; + + progname = av[0]; + if (ac > 1 && av[1][0] == '-') { + switch (av[1][1]) { + case 's': + conf_set_message_callback(NULL); + break; + case 'h': + case '?': + usage(); + } + name = av[2]; + } else + name = av[1]; + if (!name) + usage(); + + conf_parse(name); + fixup_rootmenu(&rootmenu); + //zconfdump(stdout); + + configApp = new QApplication(ac, av); + + configSettings = new ConfigSettings(); + v = new ConfigMainWindow(); + + //zconfdump(stdout); + + v->show(); + configApp->exec(); + + delete configSettings; + delete v; + delete configApp; + + return 0; +} diff --git a/scripts/kconfig/qconf.h b/scripts/kconfig/qconf.h new file mode 100644 index 000000000..ab4e51f12 --- /dev/null +++ b/scripts/kconfig/qconf.h @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#include <QCheckBox> +#include <QDialog> +#include <QHeaderView> +#include <QLineEdit> +#include <QMainWindow> +#include <QPushButton> +#include <QSettings> +#include <QSplitter> +#include <QStyledItemDelegate> +#include <QTextBrowser> +#include <QTreeWidget> + +#include "expr.h" + +class ConfigList; +class ConfigItem; +class ConfigMainWindow; + +class ConfigSettings : public QSettings { +public: + ConfigSettings(); + ~ConfigSettings(void); + QList<int> readSizes(const QString& key, bool *ok); + bool writeSizes(const QString& key, const QList<int>& value); +}; + +enum colIdx { + promptColIdx, nameColIdx, dataColIdx +}; +enum listMode { + singleMode, menuMode, symbolMode, fullMode, listMode +}; +enum optionMode { + normalOpt = 0, allOpt, promptOpt +}; + +class ConfigList : public QTreeWidget { + Q_OBJECT + typedef class QTreeWidget Parent; +public: + ConfigList(QWidget *parent, const char *name = 0); + ~ConfigList(); + void reinit(void); + ConfigItem* findConfigItem(struct menu *); + void setSelected(QTreeWidgetItem *item, bool enable) { + for (int i = 0; i < selectedItems().size(); i++) + selectedItems().at(i)->setSelected(false); + + item->setSelected(enable); + } + +protected: + void keyPressEvent(QKeyEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseDoubleClickEvent(QMouseEvent *e); + void focusInEvent(QFocusEvent *e); + void contextMenuEvent(QContextMenuEvent *e); + +public slots: + void setRootMenu(struct menu *menu); + + void updateList(); + void setValue(ConfigItem* item, tristate val); + void changeValue(ConfigItem* item); + void updateSelection(void); + void saveSettings(void); + void setOptionMode(QAction *action); + void setShowName(bool on); + +signals: + void menuChanged(struct menu *menu); + void menuSelected(struct menu *menu); + void itemSelected(struct menu *menu); + void parentSelected(void); + void gotFocus(struct menu *); + void showNameChanged(bool on); + +public: + void updateListAll(void) + { + updateAll = true; + updateList(); + updateAll = false; + } + void setAllOpen(bool open); + void setParentMenu(void); + + bool menuSkip(struct menu *); + + void updateMenuList(ConfigItem *parent, struct menu*); + void updateMenuList(struct menu *menu); + + bool updateAll; + + bool showName; + enum listMode mode; + enum optionMode optMode; + struct menu *rootEntry; + QPalette disabledColorGroup; + QPalette inactivedColorGroup; + QMenu* headerPopup; + + static QList<ConfigList *> allLists; + static void updateListForAll(); + static void updateListAllForAll(); + + static QAction *showNormalAction, *showAllAction, *showPromptAction; +}; + +class ConfigItem : public QTreeWidgetItem { + typedef class QTreeWidgetItem Parent; +public: + 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) + : Parent(parent, after), nextItem(0), menu(m), goParent(false) + { + init(); + } + 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(void); + ConfigList* listView() const + { + return (ConfigList*)Parent::treeWidget(); + } + ConfigItem* firstChild() const + { + return (ConfigItem *)Parent::child(0); + } + ConfigItem* nextSibling() + { + ConfigItem *ret = NULL; + ConfigItem *_parent = (ConfigItem *)parent(); + + if(_parent) { + ret = (ConfigItem *)_parent->child(_parent->indexOfChild(this)+1); + } else { + QTreeWidget *_treeWidget = treeWidget(); + ret = (ConfigItem *)_treeWidget->topLevelItem(_treeWidget->indexOfTopLevelItem(this)+1); + } + + return ret; + } + // TODO: Implement paintCell + + ConfigItem* nextItem; + struct menu *menu; + bool goParent; + + static QIcon symbolYesIcon, symbolModIcon, symbolNoIcon; + static QIcon choiceYesIcon, choiceNoIcon; + static QIcon menuIcon, menubackIcon; +}; + +class ConfigItemDelegate : public QStyledItemDelegate +{ +private: + struct menu *menu; +public: + ConfigItemDelegate(QObject *parent = nullptr) + : QStyledItemDelegate(parent) {} + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const override; +}; + +class ConfigInfoView : public QTextBrowser { + Q_OBJECT + typedef class QTextBrowser Parent; + QMenu *contextMenu; +public: + ConfigInfoView(QWidget* parent, const char *name = 0); + bool showDebug(void) const { return _showDebug; } + +public slots: + void setInfo(struct menu *menu); + void saveSettings(void); + void setShowDebug(bool); + void clicked (const QUrl &url); + +signals: + void showDebugChanged(bool); + void menuSelected(struct menu *); + +protected: + void symbolInfo(void); + void menuInfo(void); + QString debug_info(struct symbol *sym); + static QString print_filter(const QString &str); + static void expr_print_help(void *data, struct symbol *sym, const char *str); + void contextMenuEvent(QContextMenuEvent *event); + + struct symbol *sym; + struct menu *_menu; + bool _showDebug; +}; + +class ConfigSearchWindow : public QDialog { + Q_OBJECT + typedef class QDialog Parent; +public: + ConfigSearchWindow(ConfigMainWindow *parent); + +public slots: + void saveSettings(void); + void search(void); + +protected: + QLineEdit* editField; + QPushButton* searchButton; + QSplitter* split; + ConfigList *list; + ConfigInfoView* info; + + struct symbol **result; +}; + +class ConfigMainWindow : public QMainWindow { + Q_OBJECT + + QString configname; + static QAction *saveAction; + static void conf_changed(bool); +public: + ConfigMainWindow(void); +public slots: + void changeMenu(struct menu *); + void changeItens(struct menu *); + void setMenuLink(struct menu *); + void listFocusChanged(void); + void goBack(void); + void loadConfig(void); + bool saveConfig(void); + void saveConfigAs(void); + void searchConfig(void); + void showSingleView(void); + void showSplitView(void); + void showFullView(void); + void showIntro(void); + void showAbout(void); + void saveSettings(void); + +protected: + void closeEvent(QCloseEvent *e); + + ConfigSearchWindow *searchWindow; + ConfigList *menuList; + ConfigList *configList; + ConfigInfoView *helpText; + QAction *backAction; + QAction *singleViewAction; + QAction *splitViewAction; + QAction *fullViewAction; + QSplitter *split1; + QSplitter *split2; +}; diff --git a/scripts/kconfig/streamline_config.pl b/scripts/kconfig/streamline_config.pl new file mode 100755 index 000000000..8677d1ca0 --- /dev/null +++ b/scripts/kconfig/streamline_config.pl @@ -0,0 +1,716 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright 2005-2009 - Steven Rostedt +# +# It's simple enough to figure out how this works. +# If not, then you can ask me at stripconfig@goodmis.org +# +# What it does? +# +# If you have installed a Linux kernel from a distribution +# that turns on way too many modules than you need, and +# you only want the modules you use, then this program +# is perfect for you. +# +# It gives you the ability to turn off all the modules that are +# not loaded on your system. +# +# Howto: +# +# 1. Boot up the kernel that you want to stream line the config on. +# 2. Change directory to the directory holding the source of the +# kernel that you just booted. +# 3. Copy the configuration file to this directory as .config +# 4. Have all your devices that you need modules for connected and +# operational (make sure that their corresponding modules are loaded) +# 5. Run this script redirecting the output to some other file +# like config_strip. +# 6. Back up your old config (if you want too). +# 7. copy the config_strip file to .config +# 8. Run "make oldconfig" +# +# Now your kernel is ready to be built with only the modules that +# are loaded. +# +# Here's what I did with my Debian distribution. +# +# cd /usr/src/linux-2.6.10 +# cp /boot/config-2.6.10-1-686-smp .config +# ~/bin/streamline_config > config_strip +# mv .config config_sav +# mv config_strip .config +# make oldconfig +# +use warnings; +use strict; +use Getopt::Long; + +# set the environment variable LOCALMODCONFIG_DEBUG to get +# debug output. +my $debugprint = 0; +$debugprint = 1 if (defined($ENV{LOCALMODCONFIG_DEBUG})); + +sub dprint { + return if (!$debugprint); + print STDERR @_; +} + +my $uname = `uname -r`; +chomp $uname; + +my @searchconfigs = ( + { + "file" => ".config", + "exec" => "cat", + }, + { + "file" => "/proc/config.gz", + "exec" => "zcat", + }, + { + "file" => "/boot/config-$uname", + "exec" => "cat", + }, + { + "file" => "/boot/vmlinuz-$uname", + "exec" => "scripts/extract-ikconfig", + "test" => "scripts/extract-ikconfig", + }, + { + "file" => "vmlinux", + "exec" => "scripts/extract-ikconfig", + "test" => "scripts/extract-ikconfig", + }, + { + "file" => "/lib/modules/$uname/kernel/kernel/configs.ko", + "exec" => "scripts/extract-ikconfig", + "test" => "scripts/extract-ikconfig", + }, + { + "file" => "kernel/configs.ko", + "exec" => "scripts/extract-ikconfig", + "test" => "scripts/extract-ikconfig", + }, + { + "file" => "kernel/configs.o", + "exec" => "scripts/extract-ikconfig", + "test" => "scripts/extract-ikconfig", + }, +); + +sub read_config { + foreach my $conf (@searchconfigs) { + my $file = $conf->{"file"}; + + next if ( ! -f "$file"); + + if (defined($conf->{"test"})) { + `$conf->{"test"} $conf->{"file"} 2>/dev/null`; + next if ($?); + } + + my $exec = $conf->{"exec"}; + + print STDERR "using config: '$file'\n"; + + open(my $infile, '-|', "$exec $file") || die "Failed to run $exec $file"; + my @x = <$infile>; + close $infile; + return @x; + } + die "No config file found"; +} + +my @config_file = read_config; + +# Parse options +my $localmodconfig = 0; +my $localyesconfig = 0; + +GetOptions("localmodconfig" => \$localmodconfig, + "localyesconfig" => \$localyesconfig); + +# Get the build source and top level Kconfig file (passed in) +my $ksource = ($ARGV[0] ? $ARGV[0] : '.'); +my $kconfig = $ARGV[1]; +my $lsmod_file = $ENV{'LSMOD'}; + +my @makefiles = `find $ksource -name Makefile -or -name Kbuild 2>/dev/null`; +chomp @makefiles; + +my %depends; +my %selects; +my %prompts; +my %objects; +my %config2kfile; +my %defaults; +my $var; +my $iflevel = 0; +my @ifdeps; + +# prevent recursion +my %read_kconfigs; + +sub read_kconfig { + my ($kconfig) = @_; + + my $state = "NONE"; + my $config; + + my $cont = 0; + my $line; + + my $source = "$ksource/$kconfig"; + my $last_source = ""; + + # Check for any environment variables used + while ($source =~ /\$\((\w+)\)/ && $last_source ne $source) { + my $env = $1; + $last_source = $source; + $source =~ s/\$\($env\)/$ENV{$env}/; + } + + open(my $kinfile, '<', $source) || die "Can't open $source"; + while (<$kinfile>) { + chomp; + + # Make sure that lines ending with \ continue + if ($cont) { + $_ = $line . " " . $_; + } + + if (s/\\$//) { + $cont = 1; + $line = $_; + next; + } + + $cont = 0; + + # collect any Kconfig sources + if (/^source\s+"?([^"]+)/) { + my $kconfig = $1; + # prevent reading twice. + if (!defined($read_kconfigs{$kconfig})) { + $read_kconfigs{$kconfig} = 1; + read_kconfig($kconfig); + } + next; + } + + # configs found + if (/^\s*(menu)?config\s+(\S+)\s*$/) { + $state = "NEW"; + $config = $2; + $config2kfile{"CONFIG_$config"} = $kconfig; + + # Add depends for 'if' nesting + for (my $i = 0; $i < $iflevel; $i++) { + if ($i) { + $depends{$config} .= " " . $ifdeps[$i]; + } else { + $depends{$config} = $ifdeps[$i]; + } + $state = "DEP"; + } + + # collect the depends for the config + } elsif ($state eq "NEW" && /^\s*depends\s+on\s+(.*)$/) { + $state = "DEP"; + $depends{$config} = $1; + } elsif ($state eq "DEP" && /^\s*depends\s+on\s+(.*)$/) { + $depends{$config} .= " " . $1; + } 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; + dprint "Added default depends $dep to $config\n"; + } + + # Get the configs that select this config + } elsif ($state ne "NONE" && /^\s*select\s+(\S+)/) { + my $conf = $1; + if (defined($selects{$conf})) { + $selects{$conf} .= " " . $config; + } else { + $selects{$conf} = $config; + } + + # configs without prompts must be selected + } elsif ($state ne "NONE" && /^\s*(tristate\s+\S|prompt\b)/) { + # note if the config has a prompt + $prompts{$config} = 1; + + # Check for if statements + } elsif (/^if\s+(.*\S)\s*$/) { + my $deps = $1; + # remove beginning and ending non text + $deps =~ s/^[^a-zA-Z0-9_]*//; + $deps =~ s/[^a-zA-Z0-9_]*$//; + + my @deps = split /[^a-zA-Z0-9_]+/, $deps; + + $ifdeps[$iflevel++] = join ':', @deps; + + } elsif (/^endif/) { + + $iflevel-- if ($iflevel); + + # stop on "help" and keywords that end a menu entry + } elsif (/^\s*(---)?help(---)?\s*$/ || /^(comment|choice|menu)\b/) { + $state = "NONE"; + } + } + close($kinfile); +} + +if ($kconfig) { + read_kconfig($kconfig); +} + +# Makefiles can use variables to define their dependencies +sub convert_vars { + my ($line, %vars) = @_; + + my $process = ""; + + while ($line =~ s/^(.*?)(\$\((.*?)\))//) { + my $start = $1; + my $variable = $2; + my $var = $3; + + if (defined($vars{$var})) { + $process .= $start . $vars{$var}; + } else { + $process .= $start . $variable; + } + } + + $process .= $line; + + return $process; +} + +# Read all Makefiles to map the configs to the objects +foreach my $makefile (@makefiles) { + + my $line = ""; + my %make_vars; + + open(my $infile, '<', $makefile) || die "Can't open $makefile"; + while (<$infile>) { + # if this line ends with a backslash, continue + chomp; + if (/^(.*)\\$/) { + $line .= $1; + next; + } + + $line .= $_; + $_ = $line; + $line = ""; + + my $objs; + + # Convert variables in a line (could define configs) + $_ = convert_vars($_, %make_vars); + + # collect objects after obj-$(CONFIG_FOO_BAR) + if (/obj-\$[({](CONFIG_[^})]*)[)}]\s*[+:]?=\s*(.*)/) { + $var = $1; + $objs = $2; + + # check if variables are set + } elsif (/^\s*(\S+)\s*[:]?=\s*(.*\S)/) { + $make_vars{$1} = $2; + } + if (defined($objs)) { + foreach my $obj (split /\s+/,$objs) { + $obj =~ s/-/_/g; + if ($obj =~ /(.*)\.o$/) { + # Objects may be enabled by more than one config. + # Store configs in an array. + my @arr; + + if (defined($objects{$1})) { + @arr = @{$objects{$1}}; + } + + $arr[$#arr+1] = $var; + + # The objects have a hash mapping to a reference + # of an array of configs. + $objects{$1} = \@arr; + } + } + } + } + close($infile); +} + +my %modules; +my $linfile; + +if (defined($lsmod_file)) { + if ( ! -f $lsmod_file) { + if ( -f $ENV{'objtree'}."/".$lsmod_file) { + $lsmod_file = $ENV{'objtree'}."/".$lsmod_file; + } else { + die "$lsmod_file not found"; + } + } + + my $otype = ( -x $lsmod_file) ? '-|' : '<'; + open($linfile, $otype, $lsmod_file); + +} else { + + # see what modules are loaded on this system + my $lsmod; + + foreach my $dir ( ("/sbin", "/bin", "/usr/sbin", "/usr/bin") ) { + if ( -x "$dir/lsmod" ) { + $lsmod = "$dir/lsmod"; + last; + } + } + if (!defined($lsmod)) { + # try just the path + $lsmod = "lsmod"; + } + + open($linfile, '-|', $lsmod) || die "Can not call lsmod with $lsmod"; +} + +while (<$linfile>) { + next if (/^Module/); # Skip the first line. + if (/^(\S+)/) { + $modules{$1} = 1; + } +} +close ($linfile); + +# add to the configs hash all configs that are needed to enable +# a loaded module. This is a direct obj-${CONFIG_FOO} += bar.o +# where we know we need bar.o so we add FOO to the list. +my %configs; +foreach my $module (keys(%modules)) { + if (defined($objects{$module})) { + my @arr = @{$objects{$module}}; + foreach my $conf (@arr) { + $configs{$conf} = $module; + dprint "$conf added by direct ($module)\n"; + if ($debugprint) { + my $c=$conf; + $c =~ s/^CONFIG_//; + if (defined($depends{$c})) { + dprint " deps = $depends{$c}\n"; + } else { + dprint " no deps\n"; + } + } + } + } else { + # Most likely, someone has a custom (binary?) module loaded. + print STDERR "$module config not found!\n"; + } +} + +# Read the current config, and see what is enabled. We want to +# ignore configs that we would not enable anyway. + +my %orig_configs; +my $valid = "A-Za-z_0-9"; + +foreach my $line (@config_file) { + $_ = $line; + + if (/(CONFIG_[$valid]*)=(m|y)/) { + $orig_configs{$1} = $2; + } +} + +my $repeat = 1; + +my $depconfig; + +# +# Note, we do not care about operands (like: &&, ||, !) we want to add any +# config that is in the depend list of another config. This script does +# not enable configs that are not already enabled. If we come across a +# config A that depends on !B, we can still add B to the list of depends +# to keep on. If A was on in the original config, B would not have been +# and B would not be turned on by this script. +# +sub parse_config_depends +{ + my ($p) = @_; + + while ($p =~ /[$valid]/) { + + if ($p =~ /^[^$valid]*([$valid]+)/) { + my $conf = "CONFIG_" . $1; + + $p =~ s/^[^$valid]*[$valid]+//; + + # We only need to process if the depend config is a module + if (!defined($orig_configs{$conf}) || $orig_configs{$conf} eq "y") { + next; + } + + if (!defined($configs{$conf})) { + # We must make sure that this config has its + # dependencies met. + $repeat = 1; # do again + dprint "$conf selected by depend $depconfig\n"; + $configs{$conf} = 1; + } + } else { + die "this should never happen"; + } + } +} + +# Select is treated a bit differently than depends. We call this +# when a config has no prompt and requires another config to be +# selected. We use to just select all configs that selected this +# config, but found that that can balloon into enabling hundreds +# of configs that we do not care about. +# +# The idea is we look at all the configs that select it. If one +# is already in our list of configs to enable, then there's nothing +# else to do. If there isn't, we pick the first config that was +# enabled in the original config and use that. +sub parse_config_selects +{ + my ($config, $p) = @_; + + my $next_config; + + while ($p =~ /[$valid]/) { + + if ($p =~ /^[^$valid]*([$valid]+)/) { + my $conf = "CONFIG_" . $1; + + $p =~ s/^[^$valid]*[$valid]+//; + + # Make sure that this config exists in the current .config file + if (!defined($orig_configs{$conf})) { + dprint "$conf not set for $config select\n"; + next; + } + + # 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"; + # we are good with this + return; + } + if (defined($configs{$conf})) { + dprint "$conf selects $config so we are good\n"; + # A set config selects this config, we are good + return; + } + # Set this config to be selected + if (!defined($next_config)) { + $next_config = $conf; + } + } else { + die "this should never happen"; + } + } + + # If no possible config selected this, then something happened. + if (!defined($next_config)) { + + # 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; + } + + # If we are here, then we found no config that is set and + # selects this config. Repeat. + $repeat = 1; + # Make this config need to be selected + $configs{$next_config} = 1; + dprint "$next_config selected by select $config\n"; +} + +my %process_selects; + +# loop through all configs, select their dependencies. +sub loop_depend { + $repeat = 1; + + while ($repeat) { + $repeat = 0; + + forloop: + foreach my $config (keys %configs) { + + # If this config is not a module, we do not need to process it + if (defined($orig_configs{$config}) && $orig_configs{$config} ne "m") { + next forloop; + } + + $config =~ s/^CONFIG_//; + $depconfig = $config; + + if (defined($depends{$config})) { + # This config has dependencies. Make sure they are also included + parse_config_depends $depends{$config}; + } + + # If the config has no prompt, then we need to check if a config + # that is enabled selected it. Or if we need to enable one. + if (!defined($prompts{$config}) && defined($selects{$config})) { + $process_selects{$config} = 1; + } + } + } +} + +sub loop_select { + + foreach my $config (keys %process_selects) { + $config =~ s/^CONFIG_//; + + dprint "Process select $config\n"; + + # config has no prompt and must be selected. + parse_config_selects $config, $selects{$config}; + } +} + +while ($repeat) { + # Get the first set of configs and their dependencies. + loop_depend; + + $repeat = 0; + + # Now we need to see if we have to check selects; + loop_select; +} + +my %setconfigs; +my @preserved_kconfigs; +if (defined($ENV{'LMC_KEEP'})) { + @preserved_kconfigs = split(/:/,$ENV{LMC_KEEP}); +} + +sub in_preserved_kconfigs { + my $kconfig = $config2kfile{$_[0]}; + if (!defined($kconfig)) { + return 0; + } + foreach my $excl (@preserved_kconfigs) { + if($kconfig =~ /^$excl/) { + return 1; + } + } + return 0; +} + +# Finally, read the .config file and turn off any module enabled that +# we could not find a reason to keep enabled. +foreach my $line (@config_file) { + $_ = $line; + + if (/CONFIG_IKCONFIG/) { + if (/# CONFIG_IKCONFIG is not set/) { + # enable IKCONFIG at least as a module + print "CONFIG_IKCONFIG=m\n"; + # don't ask about PROC + print "# CONFIG_IKCONFIG_PROC is not set\n"; + } else { + print; + } + next; + } + + if (/CONFIG_MODULE_SIG_KEY="(.+)"/) { + my $orig_cert = $1; + my $default_cert = "certs/signing_key.pem"; + + # Check that the logic in this script still matches the one in Kconfig + if (!defined($depends{"MODULE_SIG_KEY"}) || + $depends{"MODULE_SIG_KEY"} !~ /"\Q$default_cert\E"/) { + print STDERR "WARNING: MODULE_SIG_KEY assertion failure, ", + "update needed to ", __FILE__, " line ", __LINE__, "\n"; + print; + } elsif ($orig_cert ne $default_cert && ! -f $orig_cert) { + print STDERR "Module signature verification enabled but ", + "module signing key \"$orig_cert\" not found. Resetting ", + "signing key to default value.\n"; + print "CONFIG_MODULE_SIG_KEY=\"$default_cert\"\n"; + } else { + print; + } + next; + } + + if (/CONFIG_SYSTEM_TRUSTED_KEYS="(.+)"/) { + my $orig_keys = $1; + + if (! -f $orig_keys) { + print STDERR "System keyring enabled but keys \"$orig_keys\" ", + "not found. Resetting keys to default value.\n"; + print "CONFIG_SYSTEM_TRUSTED_KEYS=\"\"\n"; + } else { + print; + } + next; + } + + if (/^(CONFIG.*)=(m|y)/) { + if (in_preserved_kconfigs($1)) { + dprint "Preserve config $1"; + print; + next; + } + if (defined($configs{$1})) { + if ($localyesconfig) { + $setconfigs{$1} = 'y'; + print "$1=y\n"; + next; + } else { + $setconfigs{$1} = $2; + } + } elsif ($2 eq "m") { + print "# $1 is not set\n"; + next; + } + } + print; +} + +# Integrity check, make sure all modules that we want enabled do +# indeed have their configs set. +loop: +foreach my $module (keys(%modules)) { + if (defined($objects{$module})) { + my @arr = @{$objects{$module}}; + foreach my $conf (@arr) { + if (defined($setconfigs{$conf})) { + next loop; + } + } + print STDERR "module $module did not have configs"; + foreach my $conf (@arr) { + print STDERR " " , $conf; + } + print STDERR "\n"; + } +} + +# vim: softtabstop=4 diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c new file mode 100644 index 000000000..7e81b3676 --- /dev/null +++ b/scripts/kconfig/symbol.c @@ -0,0 +1,1366 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + */ + +#include <sys/types.h> +#include <ctype.h> +#include <stdlib.h> +#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, +}; + +struct symbol symbol_mod = { + .name = "m", + .type = S_TRISTATE, + .curr = { "m", mod }, + .menus = LIST_HEAD_INIT(symbol_mod.menus), + .flags = SYMBOL_CONST|SYMBOL_VALID, +}; + +struct symbol symbol_no = { + .name = "n", + .type = S_TRISTATE, + .curr = { "n", no }, + .menus = LIST_HEAD_INIT(symbol_no.menus), + .flags = SYMBOL_CONST|SYMBOL_VALID, +}; + +struct symbol *modules_sym; +static tristate modules_val; +static int sym_warnings; + +enum symbol_type sym_get_type(const struct symbol *sym) +{ + enum symbol_type type = sym->type; + + if (type == S_TRISTATE && modules_val == no) + type = S_BOOLEAN; + return type; +} + +const char *sym_type_name(enum symbol_type type) +{ + switch (type) { + case S_BOOLEAN: + return "bool"; + case S_TRISTATE: + return "tristate"; + case S_INT: + return "integer"; + case S_HEX: + return "hex"; + case S_STRING: + return "string"; + case S_UNKNOWN: + return "unknown"; + } + return "???"; +} + +/** + * 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 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; + + return NULL; +} + +static struct property *sym_get_default_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_defaults(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +struct property *sym_get_range_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_properties(sym, prop, P_RANGE) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +static long long sym_get_range_val(struct symbol *sym, int base) +{ + sym_calc_value(sym); + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + break; + } + return strtoll(sym->curr.val, NULL, base); +} + +static void sym_validate_range(struct symbol *sym) +{ + struct property *prop; + struct symbol *range_sym; + int base; + long long val, val2; + + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + return; + } + prop = sym_get_range_prop(sym); + if (!prop) + return; + val = strtoll(sym->curr.val, NULL, base); + range_sym = prop->expr->left.sym; + val2 = sym_get_range_val(range_sym, base); + if (val >= val2) { + range_sym = prop->expr->right.sym; + val2 = sym_get_range_val(range_sym, base); + if (val <= val2) + return; + } + sym->curr.val = range_sym->curr.val; +} + +static void sym_set_changed(struct symbol *sym) +{ + struct menu *menu; + + list_for_each_entry(menu, &sym->menus, link) + menu->flags |= MENU_CHANGED; + + menu = sym_get_choice_menu(sym); + if (menu) + menu->flags |= MENU_CHANGED; +} + +static void sym_set_all_changed(void) +{ + struct symbol *sym; + + for_all_symbols(sym) + sym_set_changed(sym); +} + +static void sym_calc_visibility(struct symbol *sym) +{ + struct property *prop; + tristate tri; + + if (sym->flags & SYMBOL_TRANS) { + sym->visible = yes; + return; + } + + /* any prompt visible? */ + tri = no; + for_all_prompts(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + tri = EXPR_OR(tri, prop->visible.tri); + } + if (tri == mod && (sym->type != S_TRISTATE || modules_val == no)) + tri = yes; + if (sym->visible != tri) { + sym->visible = tri; + sym_set_changed(sym); + } + if (sym_is_choice_value(sym)) + return; + /* defaulting to "yes" if no explicit "depends on" are given */ + tri = yes; + if (sym->dir_dep.expr) + tri = expr_calc_value(sym->dir_dep.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->dir_dep.tri != tri) { + sym->dir_dep.tri = tri; + sym_set_changed(sym); + } + tri = no; + if (sym->rev_dep.expr) + tri = expr_calc_value(sym->rev_dep.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->rev_dep.tri != tri) { + sym->rev_dep.tri = tri; + sym_set_changed(sym); + } + tri = no; + if (sym->implied.expr) + tri = expr_calc_value(sym->implied.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->implied.tri != tri) { + sym->implied.tri = tri; + sym_set_changed(sym); + } +} + +/* + * Find the default symbol for a choice. + * First try the default values for the choice symbol + * Next locate the first visible choice value + * Return NULL if none was found + */ +struct symbol *sym_choice_default(struct menu *choice) +{ + struct menu *menu; + struct symbol *def_sym; + struct property *prop; + + /* any of the defaults visible? */ + for_all_defaults(choice->sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri == no) + continue; + def_sym = prop_get_symbol(prop); + if (def_sym->visible != no) + return def_sym; + } + + /* just get the first visible value */ + menu_for_each_sub_entry(menu, choice) + if (menu->sym && menu->sym->visible != no) + return menu->sym; + + /* failed to locate any defaults */ + return NULL; +} + +/* + * 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 *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; + } + } + + /* + * 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; + } + + /* Still not found. Pick up the first visible, user-unspecified symbol. */ + if (!res) { + menu_for_each_sub_entry(menu, choice) { + sym = menu->sym; + + if (!sym || sym->visible == no || sym_has_value(sym)) + continue; + + 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; + + 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(const struct symbol *sym) +{ + struct gstr gs = str_new(); + + str_printf(&gs, + "\nWARNING: unmet direct dependencies detected for %s\n", + sym->name); + str_printf(&gs, + " Depends on [%c]: ", + sym->dir_dep.tri == mod ? 'm' : 'n'); + expr_gstr_print(sym->dir_dep.expr, &gs); + str_printf(&gs, "\n"); + + expr_gstr_print_revdep(sym->rev_dep.expr, &gs, yes, + " Selected by [y]:\n"); + expr_gstr_print_revdep(sym->rev_dep.expr, &gs, mod, + " Selected by [m]:\n"); + + fputs(str_get(&gs), stderr); + str_free(&gs); + sym_warnings++; +} + +bool sym_dep_errors(void) +{ + if (sym_warnings) + return getenv("KCONFIG_WERROR"); + return false; +} + +void sym_calc_value(struct symbol *sym) +{ + struct symbol_value newval, oldval; + struct property *prop = NULL; + struct menu *choice_menu; + + if (!sym) + return; + + if (sym->flags & SYMBOL_VALID) + return; + + sym->flags |= SYMBOL_VALID; + + oldval = sym->curr; + + newval.tri = no; + + switch (sym->type) { + case S_INT: + newval.val = "0"; + break; + case S_HEX: + newval.val = "0x0"; + break; + case S_STRING: + newval.val = ""; + break; + case S_BOOLEAN: + case S_TRISTATE: + newval.val = "n"; + break; + default: + sym->curr.val = sym->name; + sym->curr.tri = no; + return; + } + sym->flags &= ~SYMBOL_WRITE; + + sym_calc_visibility(sym); + + if (sym->visible != no) + sym->flags |= SYMBOL_WRITE; + + /* set default if recursively called */ + sym->curr = newval; + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + 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 + * if available, otherwise try the default value + */ + if (sym_has_value(sym)) { + newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, + sym->visible); + goto calc_newval; + } + } + if (sym->rev_dep.tri != no) + sym->flags |= SYMBOL_WRITE; + if (!sym_is_choice(sym)) { + prop = sym_get_default_prop(sym); + if (prop) { + newval.tri = EXPR_AND(expr_calc_value(prop->expr), + prop->visible.tri); + if (newval.tri != no) + sym->flags |= SYMBOL_WRITE; + } + if (sym->implied.tri != no) { + sym->flags |= SYMBOL_WRITE; + newval.tri = EXPR_OR(newval.tri, sym->implied.tri); + newval.tri = EXPR_AND(newval.tri, + sym->dir_dep.tri); + } + } + calc_newval: + if (sym->dir_dep.tri < sym->rev_dep.tri) + sym_warn_unmet_dep(sym); + newval.tri = EXPR_OR(newval.tri, sym->rev_dep.tri); + } + if (newval.tri == mod && sym_get_type(sym) == S_BOOLEAN) + newval.tri = yes; + break; + case S_STRING: + case S_HEX: + case S_INT: + if (sym->visible != no && sym_has_value(sym)) { + newval.val = sym->def[S_DEF_USER].val; + break; + } + prop = sym_get_default_prop(sym); + if (prop) { + struct symbol *ds = prop_get_symbol(prop); + if (ds) { + sym->flags |= SYMBOL_WRITE; + sym_calc_value(ds); + newval.val = ds->curr.val; + } + } + break; + default: + ; + } + + /* + * If the symbol lacks a user value but its value comes from a + * single transitional symbol with an existing user value, mark + * this symbol as having a user value to avoid prompting. + */ + if (prop && !sym_has_value(sym)) { + struct symbol *ds = prop_get_symbol(prop); + if (ds && (ds->flags & SYMBOL_TRANS) && sym_has_value(ds)) { + sym->def[S_DEF_USER] = newval; + sym->flags |= SYMBOL_DEF_USER; + } + } + + sym->curr = newval; + sym_validate_range(sym); + + if (memcmp(&oldval, &sym->curr, sizeof(oldval))) { + sym_set_changed(sym); + if (modules_sym == sym) { + sym_set_all_changed(); + modules_val = modules_sym->curr.tri; + } + } + + if (sym_is_choice(sym) || sym->flags & SYMBOL_TRANS) + sym->flags &= ~SYMBOL_WRITE; +} + +void sym_clear_all_valid(void) +{ + struct symbol *sym; + + 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(const struct symbol *sym, tristate val) +{ + int type = sym_get_type(sym); + + if (sym->visible == no) + return false; + + if (type != S_BOOLEAN && type != S_TRISTATE) + return false; + + if (type == S_BOOLEAN && val == mod) + return false; + if (sym->visible <= sym->rev_dep.tri) + return false; + return val >= sym->rev_dep.tri && val <= sym->visible; +} + +bool sym_set_tristate_value(struct symbol *sym, tristate val) +{ + tristate oldval = sym_get_tristate_value(sym); + + if (!sym_tristate_within_range(sym, val)) + return false; + + 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); + } + + 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) { + case no: + newval = mod; + break; + case mod: + newval = yes; + break; + case yes: + newval = no; + break; + } + if (sym_set_tristate_value(sym, newval)) + break; + } while (oldval != newval); + return newval; +} + +bool sym_string_valid(struct symbol *sym, const char *str) +{ + signed char ch; + + switch (sym->type) { + case S_STRING: + return true; + case S_INT: + ch = *str++; + if (ch == '-') + ch = *str++; + if (!isdigit(ch)) + return false; + if (ch == '0' && *str != 0) + return false; + while ((ch = *str++)) { + if (!isdigit(ch)) + return false; + } + return true; + case S_HEX: + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) + str += 2; + ch = *str++; + do { + if (!isxdigit(ch)) + return false; + } while ((ch = *str++)); + return true; + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + case 'm': case 'M': + case 'n': case 'N': + return true; + } + return false; + default: + return false; + } +} + +bool sym_string_within_range(struct symbol *sym, const char *str) +{ + struct property *prop; + long long val; + + switch (sym->type) { + case S_STRING: + return sym_string_valid(sym, str); + case S_INT: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtoll(str, NULL, 10); + return val >= sym_get_range_val(prop->expr->left.sym, 10) && + val <= sym_get_range_val(prop->expr->right.sym, 10); + case S_HEX: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtoll(str, NULL, 16); + return val >= sym_get_range_val(prop->expr->left.sym, 16) && + val <= sym_get_range_val(prop->expr->right.sym, 16); + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + return sym_tristate_within_range(sym, yes); + case 'm': case 'M': + return sym_tristate_within_range(sym, mod); + case 'n': case 'N': + return sym_tristate_within_range(sym, no); + } + return false; + default: + return false; + } +} + +bool sym_set_string_value(struct symbol *sym, const char *newval) +{ + const char *oldval; + char *val; + int size; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (newval[0]) { + case 'y': case 'Y': + return sym_set_tristate_value(sym, yes); + case 'm': case 'M': + return sym_set_tristate_value(sym, mod); + case 'n': case 'N': + return sym_set_tristate_value(sym, no); + } + return false; + default: + ; + } + + if (!sym_string_within_range(sym, newval)) + return false; + + if (!(sym->flags & SYMBOL_DEF_USER)) { + sym->flags |= SYMBOL_DEF_USER; + sym_set_changed(sym); + } + + oldval = sym->def[S_DEF_USER].val; + size = strlen(newval) + 1; + if (sym->type == S_HEX && (newval[0] != '0' || (newval[1] != 'x' && newval[1] != 'X'))) { + size += 2; + sym->def[S_DEF_USER].val = val = xmalloc(size); + *val++ = '0'; + *val++ = 'x'; + } else if (!oldval || strcmp(oldval, newval)) + sym->def[S_DEF_USER].val = val = xmalloc(size); + else + return true; + + strcpy(val, newval); + free((void *)oldval); + sym_clear_all_valid(); + + return true; +} + +/* + * Find the default value associated to a symbol. + * For tristate symbol handle the modules=n case + * in which case "m" becomes "y". + * If the symbol does not have any default then fallback + * to the fixed default values. + */ +const char *sym_get_string_default(struct symbol *sym) +{ + struct property *prop; + struct symbol *ds; + const char *str = ""; + tristate val; + + sym_calc_visibility(sym); + sym_calc_value(modules_sym); + val = symbol_no.curr.tri; + + /* If symbol has a default value look it up */ + prop = sym_get_default_prop(sym); + if (prop != NULL) { + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + /* The visibility may limit the value from yes => mod */ + val = EXPR_AND(expr_calc_value(prop->expr), prop->visible.tri); + break; + default: + /* + * The following fails to handle the situation + * where a default value is further limited by + * the valid range. + */ + ds = prop_get_symbol(prop); + if (ds != NULL) { + sym_calc_value(ds); + str = (const char *)ds->curr.val; + } + } + } + + /* Handle select statements */ + val = EXPR_OR(val, sym->rev_dep.tri); + + /* transpose mod to yes if modules are not enabled */ + if (val == mod) + if (!sym_is_choice_value(sym) && modules_sym->curr.tri == no) + val = yes; + + /* transpose mod to yes if type is bool */ + if (sym->type == S_BOOLEAN && val == mod) + val = yes; + + /* adjust the default value if this symbol is implied by another */ + if (val < sym->implied.tri) + val = sym->implied.tri; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (val) { + case no: return "n"; + case mod: return "m"; + case yes: return "y"; + } + case S_INT: + if (!str[0]) + str = "0"; + break; + case S_HEX: + if (!str[0]) + str = "0x0"; + break; + default: + break; + } + return str; +} + +const char *sym_get_string_value(struct symbol *sym) +{ + tristate val; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + val = sym_get_tristate_value(sym); + switch (val) { + case no: + return "n"; + case mod: + return "m"; + case yes: + return "y"; + } + break; + default: + ; + } + return sym->curr.val; +} + +bool sym_is_changeable(const struct symbol *sym) +{ + 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); + +struct symbol *sym_lookup(const char *name, int flags) +{ + struct symbol *symbol; + char *new_name; + int hash; + + if (name) { + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + 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))) + return symbol; + } + new_name = xstrdup(name); + } else { + new_name = NULL; + hash = 0; + } + + symbol = xmalloc(sizeof(*symbol)); + memset(symbol, 0, sizeof(*symbol)); + symbol->name = new_name; + 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); + + return symbol; +} + +struct symbol *sym_find(const char *name) +{ + struct symbol *symbol = NULL; + int hash = 0; + + if (!name) + return NULL; + + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + hash = hash_str(name); + + hash_for_each_possible(sym_hashtable, symbol, node, hash) { + if (symbol->name && + !strcmp(symbol->name, name) && + !(symbol->flags & SYMBOL_CONST)) + break; + } + + return symbol; +} + +struct sym_match { + struct symbol *sym; + off_t so, eo; +}; + +/* Compare matched symbols as thus: + * - first, symbols that match exactly + * - then, alphabetical sort + */ +static int sym_rel_comp(const void *sym1, const void *sym2) +{ + const struct sym_match *s1 = sym1; + const struct sym_match *s2 = sym2; + int exact1, exact2; + + /* Exact match: + * - if matched length on symbol s1 is the length of that symbol, + * then this symbol should come first; + * - if matched length on symbol s2 is the length of that symbol, + * then this symbol should come first. + * Note: since the search can be a regexp, both symbols may match + * exactly; if this is the case, we can't decide which comes first, + * and we fallback to sorting alphabetically. + */ + exact1 = (s1->eo - s1->so) == strlen(s1->sym->name); + exact2 = (s2->eo - s2->so) == strlen(s2->sym->name); + if (exact1 && !exact2) + return -1; + if (!exact1 && exact2) + return 1; + + /* As a fallback, sort symbols alphabetically */ + return strcmp(s1->sym->name, s2->sym->name); +} + +struct symbol **sym_re_search(const char *pattern) +{ + struct symbol *sym, **sym_arr = NULL; + struct sym_match *sym_match_arr = NULL; + int i, cnt, size; + regex_t re; + regmatch_t match[1]; + + cnt = size = 0; + /* Skip if empty */ + if (strlen(pattern) == 0) + return NULL; + if (regcomp(&re, pattern, REG_EXTENDED|REG_ICASE)) + return NULL; + + for_all_symbols(sym) { + if (sym->flags & SYMBOL_CONST || !sym->name) + continue; + if (regexec(&re, sym->name, 1, match, 0)) + continue; + if (cnt >= size) { + void *tmp; + size += 16; + tmp = realloc(sym_match_arr, size * sizeof(struct sym_match)); + if (!tmp) + goto sym_re_search_free; + sym_match_arr = tmp; + } + sym_calc_value(sym); + /* As regexec returned 0, we know we have a match, so + * we can use match[0].rm_[se]o without further checks + */ + sym_match_arr[cnt].so = match[0].rm_so; + sym_match_arr[cnt].eo = match[0].rm_eo; + sym_match_arr[cnt++].sym = sym; + } + if (sym_match_arr) { + qsort(sym_match_arr, cnt, sizeof(struct sym_match), sym_rel_comp); + sym_arr = malloc((cnt+1) * sizeof(struct symbol *)); + if (!sym_arr) + goto sym_re_search_free; + for (i = 0; i < cnt; i++) + sym_arr[i] = sym_match_arr[i].sym; + sym_arr[cnt] = NULL; + } +sym_re_search_free: + /* sym_match_arr can be NULL if no match, but free(NULL) is OK */ + free(sym_match_arr); + regfree(&re); + + return sym_arr; +} + +/* + * When we check for recursive dependencies we use a stack to save + * current state so we can print out relevant info to user. + * The entries are located on the call stack so no need to free memory. + * Note insert() remove() must always match to properly clear the stack. + */ +static struct dep_stack { + struct dep_stack *prev, *next; + struct symbol *sym; + struct property *prop; + struct expr **expr; +} *check_top; + +static void dep_stack_insert(struct dep_stack *stack, struct symbol *sym) +{ + memset(stack, 0, sizeof(*stack)); + if (check_top) + check_top->next = stack; + stack->prev = check_top; + stack->sym = sym; + check_top = stack; +} + +static void dep_stack_remove(void) +{ + check_top = check_top->prev; + if (check_top) + check_top->next = NULL; +} + +/* + * Called when we have detected a recursive dependency. + * check_top point to the top of the stact so we use + * the ->prev pointer to locate the bottom of the stack. + */ +static void sym_check_print_recursive(struct symbol *last_sym) +{ + struct dep_stack *stack; + struct symbol *sym, *next_sym; + struct menu *choice; + struct dep_stack cv_stack; + enum prop_type type; + + choice = sym_get_choice_menu(last_sym); + if (choice) { + dep_stack_insert(&cv_stack, last_sym); + last_sym = choice->sym; + } + + for (stack = check_top; stack != NULL; stack = stack->prev) + if (stack->sym == last_sym) + break; + if (!stack) { + fprintf(stderr, "unexpected recursive dependency error\n"); + return; + } + + for (; stack; stack = stack->next) { + sym = stack->sym; + next_sym = stack->next ? stack->next->sym : last_sym; + type = stack->prop ? stack->prop->type : P_UNKNOWN; + + if (stack->sym == last_sym) + fprintf(stderr, "error: recursive dependency detected!\n"); + + 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>", + choice->filename, choice->lineno); + } else if (stack->expr == &sym->dir_dep.expr) { + fprintf(stderr, "\tsymbol %s depends on %s\n", + sym->name ? sym->name : "<choice>", + next_sym->name); + } else if (stack->expr == &sym->rev_dep.expr) { + fprintf(stderr, "\tsymbol %s is selected by %s\n", + sym->name, next_sym->name); + } else if (stack->expr == &sym->implied.expr) { + fprintf(stderr, "\tsymbol %s is implied by %s\n", + sym->name, next_sym->name); + } else if (stack->expr) { + fprintf(stderr, "\tsymbol %s %s value contains %s\n", + sym->name ? sym->name : "<choice>", + prop_get_type_name(type), + next_sym->name); + } else { + fprintf(stderr, "\tsymbol %s %s is visible depending on %s\n", + sym->name ? sym->name : "<choice>", + prop_get_type_name(type), + next_sym->name); + } + } + + fprintf(stderr, + "For a resolution refer to Documentation/kbuild/kconfig-language.rst\n" + "subsection \"Kconfig recursive dependency limitations\"\n" + "\n"); + + if (check_top == &cv_stack) + dep_stack_remove(); +} + +static struct symbol *sym_check_expr_deps(const struct expr *e) +{ + struct symbol *sym; + + if (!e) + return NULL; + switch (e->type) { + case E_OR: + case E_AND: + sym = sym_check_expr_deps(e->left.expr); + if (sym) + return sym; + return sym_check_expr_deps(e->right.expr); + case E_NOT: + return sym_check_expr_deps(e->left.expr); + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + sym = sym_check_deps(e->left.sym); + if (sym) + return sym; + return sym_check_deps(e->right.sym); + case E_SYMBOL: + return sym_check_deps(e->left.sym); + default: + break; + } + fprintf(stderr, "Oops! How to check %d?\n", e->type); + return NULL; +} + +/* return NULL when dependencies are OK */ +static struct symbol *sym_check_sym_deps(struct symbol *sym) +{ + struct symbol *sym2; + struct property *prop; + struct dep_stack stack; + + dep_stack_insert(&stack, sym); + + stack.expr = &sym->dir_dep.expr; + sym2 = sym_check_expr_deps(sym->dir_dep.expr); + if (sym2) + goto out; + + stack.expr = &sym->rev_dep.expr; + sym2 = sym_check_expr_deps(sym->rev_dep.expr); + if (sym2) + goto out; + + stack.expr = &sym->implied.expr; + sym2 = sym_check_expr_deps(sym->implied.expr); + if (sym2) + goto out; + + stack.expr = NULL; + + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->type == P_SELECT || prop->type == P_IMPLY) + continue; + stack.prop = prop; + sym2 = sym_check_expr_deps(prop->visible.expr); + if (sym2) + break; + if (prop->type != P_DEFAULT || sym_is_choice(sym)) + continue; + stack.expr = &prop->expr; + sym2 = sym_check_expr_deps(prop->expr); + if (sym2) + break; + stack.expr = NULL; + } + +out: + dep_stack_remove(); + + return sym2; +} + +static struct symbol *sym_check_choice_deps(struct symbol *choice) +{ + struct menu *choice_menu, *menu; + struct symbol *sym2; + struct dep_stack stack; + + dep_stack_insert(&stack, choice); + + 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); + choice->flags &= ~SYMBOL_CHECK; + if (sym2) + goto out; + + menu_for_each_sub_entry(menu, choice_menu) { + if (!menu->sym) + continue; + sym2 = sym_check_sym_deps(menu->sym); + if (sym2) + break; + } +out: + menu_for_each_sub_entry(menu, choice_menu) + if (menu->sym) + menu->sym->flags &= ~SYMBOL_CHECK; + + if (sym2) { + struct menu *choice_menu2; + + choice_menu2 = sym_get_choice_menu(sym2); + if (choice_menu2 == choice_menu) + sym2 = choice; + } + + dep_stack_remove(); + + return sym2; +} + +struct symbol *sym_check_deps(struct symbol *sym) +{ + struct menu *choice; + struct symbol *sym2; + + if (sym->flags & SYMBOL_CHECK) { + sym_check_print_recursive(sym); + return sym; + } + if (sym->flags & SYMBOL_CHECKED) + return NULL; + + 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); + sym2 = sym_check_deps(choice->sym); + dep_stack_remove(); + } else if (sym_is_choice(sym)) { + sym2 = sym_check_choice_deps(sym); + } else { + sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); + sym2 = sym_check_sym_deps(sym); + sym->flags &= ~SYMBOL_CHECK; + } + + return sym2; +} + +struct symbol *prop_get_symbol(const struct property *prop) +{ + if (prop->expr && prop->expr->type == E_SYMBOL) + return prop->expr->left.sym; + return NULL; +} + +const char *prop_get_type_name(enum prop_type type) +{ + switch (type) { + case P_PROMPT: + return "prompt"; + case P_COMMENT: + return "comment"; + case P_MENU: + return "menu"; + case P_DEFAULT: + return "default"; + case P_SELECT: + return "select"; + case P_IMPLY: + return "imply"; + case P_RANGE: + return "range"; + case P_UNKNOWN: + break; + } + return "unknown"; +} diff --git a/scripts/kconfig/tests/auto_submenu/Kconfig b/scripts/kconfig/tests/auto_submenu/Kconfig new file mode 100644 index 000000000..b20761edc --- /dev/null +++ b/scripts/kconfig/tests/auto_submenu/Kconfig @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0 + +config A + bool "A" + default y + +config A0 + bool "A0" + depends on A + default y + help + This depends on A, so should be a submenu of A. + +config A0_0 + bool "A1_0" + depends on A0 + help + Submenus are created recursively. + This should be a submenu of A0. + +config A1 + bool "A1" + depends on A + default y + help + This should line up with A0. + +choice + prompt "choice" + depends on A1 + help + Choice should become a submenu as well. + +config A1_0 + bool "A1_0" + +config A1_1 + bool "A1_1" + +endchoice + +config B + bool "B" + help + This is independent of A. + +config C + bool "C" + depends on A + help + This depends on A, but not a consecutive item, so can/should not + be a submenu. diff --git a/scripts/kconfig/tests/auto_submenu/__init__.py b/scripts/kconfig/tests/auto_submenu/__init__.py new file mode 100644 index 000000000..25abd927f --- /dev/null +++ b/scripts/kconfig/tests/auto_submenu/__init__.py @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Create submenu for symbols that depend on the preceding one. + +If a symbols has dependency on the preceding symbol, the menu entry +should become the submenu of the preceding one, and displayed with +deeper indentation. +""" + + +def test(conf): + assert conf.oldaskconfig() == 0 + assert conf.stdout_contains('expected_stdout') diff --git a/scripts/kconfig/tests/auto_submenu/expected_stdout b/scripts/kconfig/tests/auto_submenu/expected_stdout new file mode 100644 index 000000000..bf5236f39 --- /dev/null +++ b/scripts/kconfig/tests/auto_submenu/expected_stdout @@ -0,0 +1,10 @@ +A (A) [Y/n/?] (NEW) + A0 (A0) [Y/n/?] (NEW) + A1_0 (A0_0) [N/y/?] (NEW) + A1 (A1) [Y/n/?] (NEW) + choice + > 1. A1_0 (A1_0) (NEW) + 2. A1_1 (A1_1) (NEW) + choice[1-2?]: +B (B) [N/y/?] (NEW) +C (C) [N/y/?] (NEW) diff --git a/scripts/kconfig/tests/choice/Kconfig b/scripts/kconfig/tests/choice/Kconfig new file mode 100644 index 000000000..cd252579a --- /dev/null +++ b/scripts/kconfig/tests/choice/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 + +choice + prompt "boolean choice" + default BOOL_CHOICE1 + +config BOOL_CHOICE0 + bool "choice 0" + +config BOOL_CHOICE1 + bool "choice 1" + +endchoice diff --git a/scripts/kconfig/tests/choice/__init__.py b/scripts/kconfig/tests/choice/__init__.py new file mode 100644 index 000000000..0fc7bf9b5 --- /dev/null +++ b/scripts/kconfig/tests/choice/__init__.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Basic choice tests. +""" + + +def test_oldask0(conf): + assert conf.oldaskconfig() == 0 + assert conf.stdout_contains('oldask0_expected_stdout') + + +def test_allyes(conf): + assert conf.allyesconfig() == 0 + assert conf.config_contains('allyes_expected_config') + + +def test_allmod(conf): + assert conf.allmodconfig() == 0 + assert conf.config_contains('allmod_expected_config') + + +def test_allno(conf): + assert conf.allnoconfig() == 0 + assert conf.config_contains('allno_expected_config') + + +def test_alldef(conf): + assert conf.alldefconfig() == 0 + assert conf.config_contains('alldef_expected_config') diff --git a/scripts/kconfig/tests/choice/alldef_expected_config b/scripts/kconfig/tests/choice/alldef_expected_config new file mode 100644 index 000000000..b359a2e64 --- /dev/null +++ b/scripts/kconfig/tests/choice/alldef_expected_config @@ -0,0 +1,2 @@ +# CONFIG_BOOL_CHOICE0 is not set +CONFIG_BOOL_CHOICE1=y diff --git a/scripts/kconfig/tests/choice/allmod_expected_config b/scripts/kconfig/tests/choice/allmod_expected_config new file mode 100644 index 000000000..b359a2e64 --- /dev/null +++ b/scripts/kconfig/tests/choice/allmod_expected_config @@ -0,0 +1,2 @@ +# CONFIG_BOOL_CHOICE0 is not set +CONFIG_BOOL_CHOICE1=y diff --git a/scripts/kconfig/tests/choice/allno_expected_config b/scripts/kconfig/tests/choice/allno_expected_config new file mode 100644 index 000000000..b359a2e64 --- /dev/null +++ b/scripts/kconfig/tests/choice/allno_expected_config @@ -0,0 +1,2 @@ +# CONFIG_BOOL_CHOICE0 is not set +CONFIG_BOOL_CHOICE1=y diff --git a/scripts/kconfig/tests/choice/allyes_expected_config b/scripts/kconfig/tests/choice/allyes_expected_config new file mode 100644 index 000000000..b359a2e64 --- /dev/null +++ b/scripts/kconfig/tests/choice/allyes_expected_config @@ -0,0 +1,2 @@ +# CONFIG_BOOL_CHOICE0 is not set +CONFIG_BOOL_CHOICE1=y diff --git a/scripts/kconfig/tests/choice/oldask0_expected_stdout b/scripts/kconfig/tests/choice/oldask0_expected_stdout new file mode 100644 index 000000000..80ec34c61 --- /dev/null +++ b/scripts/kconfig/tests/choice/oldask0_expected_stdout @@ -0,0 +1,4 @@ +boolean choice + 1. choice 0 (BOOL_CHOICE0) (NEW) +> 2. choice 1 (BOOL_CHOICE1) (NEW) +choice[1-2?]: diff --git a/scripts/kconfig/tests/choice_randomize/Kconfig b/scripts/kconfig/tests/choice_randomize/Kconfig new file mode 100644 index 000000000..93a1699ce --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize/Kconfig @@ -0,0 +1,22 @@ +choice + prompt "choose A or B" + +config A + bool "A" + +config B + bool "B" + +endchoice + +choice + prompt "choose X or Y" + depends on B + +config X + bool "X" + +config Y + bool "Y" + +endchoice diff --git a/scripts/kconfig/tests/choice_randomize/__init__.py b/scripts/kconfig/tests/choice_randomize/__init__.py new file mode 100644 index 000000000..d380045be --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize/__init__.py @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only +""" +Randomize all dependent choices + +This is a somewhat tricky case for randconfig; the visibility of one choice is +determined by a member of another choice. Randconfig should be able to generate +all possible patterns. +""" + + +def test(conf): + + expected0 = False + expected1 = False + expected2 = False + + for i in range(100): + assert conf.randconfig(seed=i) == 0 + + if conf.config_matches('expected_config0'): + expected0 = True + elif conf.config_matches('expected_config1'): + expected1 = True + elif conf.config_matches('expected_config2'): + expected2 = True + else: + assert False + + if expected0 and expected1 and expected2: + break + + assert expected0 + assert expected1 + assert expected2 diff --git a/scripts/kconfig/tests/choice_randomize/expected_config0 b/scripts/kconfig/tests/choice_randomize/expected_config0 new file mode 100644 index 000000000..f69227323 --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize/expected_config0 @@ -0,0 +1,6 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_A=y +# CONFIG_B is not set diff --git a/scripts/kconfig/tests/choice_randomize/expected_config1 b/scripts/kconfig/tests/choice_randomize/expected_config1 new file mode 100644 index 000000000..bf83784c9 --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize/expected_config1 @@ -0,0 +1,8 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +# CONFIG_A is not set +CONFIG_B=y +CONFIG_X=y +# CONFIG_Y is not set diff --git a/scripts/kconfig/tests/choice_randomize/expected_config2 b/scripts/kconfig/tests/choice_randomize/expected_config2 new file mode 100644 index 000000000..38f93a8f3 --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize/expected_config2 @@ -0,0 +1,8 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +# CONFIG_A is not set +CONFIG_B=y +# CONFIG_X is not set +CONFIG_Y=y diff --git a/scripts/kconfig/tests/choice_randomize2/Kconfig b/scripts/kconfig/tests/choice_randomize2/Kconfig new file mode 100644 index 000000000..530cf2ef7 --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize2/Kconfig @@ -0,0 +1,32 @@ +choice + prompt "This is always invisible" + depends on n + +config DUMMY + bool "DUMMY" + +endchoice + +choice + prompt "Choose A or B" + +config A + bool "A" + +config B + bool "B" + +endchoice + +config FOO + bool "FOO" + depends on A + +choice + prompt "Choose X" + depends on FOO + +config X + bool "X" + +endchoice diff --git a/scripts/kconfig/tests/choice_randomize2/__init__.py b/scripts/kconfig/tests/choice_randomize2/__init__.py new file mode 100644 index 000000000..2066757b8 --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize2/__init__.py @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +""" +Randomize choices with correct dependencies + +When shuffling a choice may potentially disrupt certain dependencies, symbol +values must be recalculated. + +Related Linux commits: + - c8fb7d7e48d11520ad24808cfce7afb7b9c9f798 +""" + + +def test(conf): + for i in range(20): + assert conf.randconfig(seed=i) == 0 + assert (conf.config_matches('expected_config0') or + conf.config_matches('expected_config1') or + conf.config_matches('expected_config2')) diff --git a/scripts/kconfig/tests/choice_randomize2/expected_config0 b/scripts/kconfig/tests/choice_randomize2/expected_config0 new file mode 100644 index 000000000..5c9e1c172 --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize2/expected_config0 @@ -0,0 +1,8 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_A=y +# CONFIG_B is not set +CONFIG_FOO=y +CONFIG_X=y diff --git a/scripts/kconfig/tests/choice_randomize2/expected_config1 b/scripts/kconfig/tests/choice_randomize2/expected_config1 new file mode 100644 index 000000000..5b975d91b --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize2/expected_config1 @@ -0,0 +1,7 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_A=y +# CONFIG_B is not set +# CONFIG_FOO is not set diff --git a/scripts/kconfig/tests/choice_randomize2/expected_config2 b/scripts/kconfig/tests/choice_randomize2/expected_config2 new file mode 100644 index 000000000..5a5ebb90d --- /dev/null +++ b/scripts/kconfig/tests/choice_randomize2/expected_config2 @@ -0,0 +1,6 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +# CONFIG_A is not set +CONFIG_B=y diff --git a/scripts/kconfig/tests/conditional_dep/Kconfig b/scripts/kconfig/tests/conditional_dep/Kconfig new file mode 100644 index 000000000..2015dfbce --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/Kconfig @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test Kconfig file for conditional dependencies. + +# Enable module support for tristate testing +config MODULES + bool "Enable loadable module support" + modules + default y + +config FOO + bool "FOO symbol" + +config BAR + bool "BAR symbol" + +config TEST_BASIC + bool "Test basic conditional dependency" + depends on FOO if BAR + default y + +config TEST_COMPLEX + bool "Test complex conditional dependency" + depends on (FOO && BAR) if (FOO || BAR) + default y + +config BAZ + tristate "BAZ symbol" + +config TEST_OPTIONAL + tristate "Test simple optional dependency" + depends on BAZ if BAZ + default y diff --git a/scripts/kconfig/tests/conditional_dep/__init__.py b/scripts/kconfig/tests/conditional_dep/__init__.py new file mode 100644 index 000000000..ab16df648 --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/__init__.py @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Correctly handle conditional dependencies. +""" + +def test(conf): + assert conf.oldconfig('test_config1') == 0 + assert conf.config_matches('expected_config1') + + assert conf.oldconfig('test_config2') == 0 + assert conf.config_matches('expected_config2') + + assert conf.oldconfig('test_config3') == 0 + assert conf.config_matches('expected_config3') diff --git a/scripts/kconfig/tests/conditional_dep/expected_config1 b/scripts/kconfig/tests/conditional_dep/expected_config1 new file mode 100644 index 000000000..826ed7f54 --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/expected_config1 @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_MODULES=y +CONFIG_FOO=y +CONFIG_BAR=y +CONFIG_TEST_BASIC=y +CONFIG_TEST_COMPLEX=y +CONFIG_BAZ=m +CONFIG_TEST_OPTIONAL=m diff --git a/scripts/kconfig/tests/conditional_dep/expected_config2 b/scripts/kconfig/tests/conditional_dep/expected_config2 new file mode 100644 index 000000000..10d2354f6 --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/expected_config2 @@ -0,0 +1,9 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_MODULES=y +# CONFIG_FOO is not set +CONFIG_BAR=y +CONFIG_BAZ=y +CONFIG_TEST_OPTIONAL=y diff --git a/scripts/kconfig/tests/conditional_dep/expected_config3 b/scripts/kconfig/tests/conditional_dep/expected_config3 new file mode 100644 index 000000000..b04fa6fdf --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/expected_config3 @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_MODULES=y +# CONFIG_FOO is not set +# CONFIG_BAR is not set +CONFIG_TEST_BASIC=y +CONFIG_TEST_COMPLEX=y +# CONFIG_BAZ is not set +CONFIG_TEST_OPTIONAL=y diff --git a/scripts/kconfig/tests/conditional_dep/test_config1 b/scripts/kconfig/tests/conditional_dep/test_config1 new file mode 100644 index 000000000..9b05f3ce8 --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/test_config1 @@ -0,0 +1,6 @@ +# Basic check that everything can be configured if selected. +CONFIG_FOO=y +CONFIG_BAR=y +CONFIG_BAZ=m +# Ensure that TEST_OPTIONAL=y with BAZ=m is converted to TEST_OPTIONAL=m +CONFIG_TEST_OPTIONAL=y diff --git a/scripts/kconfig/tests/conditional_dep/test_config2 b/scripts/kconfig/tests/conditional_dep/test_config2 new file mode 100644 index 000000000..5e66d230a --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/test_config2 @@ -0,0 +1,7 @@ +# If FOO is not selected, then TEST_BASIC should fail the conditional +# dependency since BAR is set. +# TEST_COMPLEX will fail dependency as it depends on both FOO and BAR +# if either of those is selected. +CONFIG_FOO=n +CONFIG_BAR=y +CONFIG_BAZ=y diff --git a/scripts/kconfig/tests/conditional_dep/test_config3 b/scripts/kconfig/tests/conditional_dep/test_config3 new file mode 100644 index 000000000..86304f3aa --- /dev/null +++ b/scripts/kconfig/tests/conditional_dep/test_config3 @@ -0,0 +1,6 @@ +# If FOO is not selected, but BAR is also not selected, then TEST_BASIC +# should pass since the dependency on FOO is conditional on BAR. +# TEST_COMPLEX should be also set since neither FOO nor BAR are selected +# so it has no dependencies. +CONFIG_FOO=n +CONFIG_BAR=n diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py new file mode 100644 index 000000000..66f95e4ed --- /dev/null +++ b/scripts/kconfig/tests/conftest.py @@ -0,0 +1,318 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@socionext.com> +# + +""" +Kconfig unit testing framework. + +This provides fixture functions commonly used from test files. +""" + +import os +import pytest +import shutil +import subprocess +import tempfile + +CONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf')) + + +class Conf: + """Kconfig runner and result checker. + + This class provides methods to run text-based interface of Kconfig + (scripts/kconfig/conf) and retrieve the resulted configuration, + stdout, and stderr. It also provides methods to compare those + results with expectations. + """ + + def __init__(self, request): + """Create a new Conf instance. + + request: object to introspect the requesting test module + """ + # the directory of the test being run + self._test_dir = os.path.dirname(str(request.fspath)) + + # runners + def _run_conf(self, mode, dot_config=None, out_file='.config', + interactive=False, in_keys=None, extra_env={}, + silent=False): + """Run text-based Kconfig executable and save the result. + + mode: input mode option (--oldaskconfig, --defconfig=<file> etc.) + dot_config: .config file to use for configuration base + out_file: file name to contain the output config data + interactive: flag to specify the interactive mode + in_keys: key inputs for interactive modes + extra_env: additional environments + returncode: exit status of the Kconfig executable + """ + command = [CONF_PATH] + if silent: + command.append('-s') + command += [mode, 'Kconfig'] + + # Override 'srctree' environment to make the test as the top directory + extra_env['srctree'] = self._test_dir + + # Clear KCONFIG_DEFCONFIG_LIST to keep unit tests from being affected + # by the user's environment. + extra_env['KCONFIG_DEFCONFIG_LIST'] = '' + + # Run Kconfig in a temporary directory. + # This directory is automatically removed when done. + with tempfile.TemporaryDirectory() as temp_dir: + + # if .config is given, copy it to the working directory + if dot_config: + shutil.copyfile(os.path.join(self._test_dir, dot_config), + os.path.join(temp_dir, '.config')) + + ps = subprocess.Popen(command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=temp_dir, + env=dict(os.environ, **extra_env)) + + # If input key sequence is given, feed it to stdin. + if in_keys: + ps.stdin.write(in_keys.encode('utf-8')) + + while ps.poll() is None: + # For interactive modes such as oldaskconfig, oldconfig, + # send 'Enter' key until the program finishes. + if interactive: + try: + ps.stdin.write(b'\n') + ps.stdin.flush() + except (BrokenPipeError, OSError): + # Process has exited, stop sending input + break + + # Close stdin gracefully + try: + ps.stdin.close() + except (BrokenPipeError, OSError): + # Ignore broken pipe on close + pass + + # Wait for process to complete + ps.wait() + + self.retcode = ps.returncode + self.stdout = ps.stdout.read().decode() + self.stderr = ps.stderr.read().decode() + + # Retrieve the resulted config data only when .config is supposed + # to exist. If the command fails, the .config does not exist. + # 'listnewconfig' does not produce .config in the first place. + if self.retcode == 0 and out_file: + with open(os.path.join(temp_dir, out_file)) as f: + self.config = f.read() + else: + self.config = None + + # Logging: + # Pytest captures the following information by default. In failure + # of tests, the captured log will be displayed. This will be useful to + # figure out what has happened. + + print("[command]\n{}\n".format(' '.join(command))) + + print("[retcode]\n{}\n".format(self.retcode)) + + print("[stdout]") + print(self.stdout) + + print("[stderr]") + print(self.stderr) + + if self.config is not None: + print("[output for '{}']".format(out_file)) + print(self.config) + + return self.retcode + + def oldaskconfig(self, dot_config=None, in_keys=None): + """Run oldaskconfig. + + dot_config: .config file to use for configuration base (optional) + in_key: key inputs (optional) + returncode: exit status of the Kconfig executable + """ + return self._run_conf('--oldaskconfig', dot_config=dot_config, + interactive=True, in_keys=in_keys) + + def oldconfig(self, dot_config=None, in_keys=None): + """Run oldconfig. + + dot_config: .config file to use for configuration base (optional) + in_key: key inputs (optional) + returncode: exit status of the Kconfig executable + """ + return self._run_conf('--oldconfig', dot_config=dot_config, + interactive=True, in_keys=in_keys) + + def olddefconfig(self, dot_config=None): + """Run olddefconfig. + + dot_config: .config file to use for configuration base (optional) + returncode: exit status of the Kconfig executable + """ + return self._run_conf('--olddefconfig', dot_config=dot_config) + + def defconfig(self, defconfig): + """Run defconfig. + + defconfig: defconfig file for input + returncode: exit status of the Kconfig executable + """ + defconfig_path = os.path.join(self._test_dir, defconfig) + return self._run_conf('--defconfig={}'.format(defconfig_path)) + + def _allconfig(self, mode, all_config, extra_env={}): + if all_config: + all_config_path = os.path.join(self._test_dir, all_config) + extra_env['KCONFIG_ALLCONFIG'] = all_config_path + + return self._run_conf('--{}config'.format(mode), extra_env=extra_env) + + def allyesconfig(self, all_config=None): + """Run allyesconfig. + + all_config: fragment config file for KCONFIG_ALLCONFIG (optional) + returncode: exit status of the Kconfig executable + """ + return self._allconfig('allyes', all_config) + + def allmodconfig(self, all_config=None): + """Run allmodconfig. + + all_config: fragment config file for KCONFIG_ALLCONFIG (optional) + returncode: exit status of the Kconfig executable + """ + return self._allconfig('allmod', all_config) + + def allnoconfig(self, all_config=None): + """Run allnoconfig. + + all_config: fragment config file for KCONFIG_ALLCONFIG (optional) + returncode: exit status of the Kconfig executable + """ + return self._allconfig('allno', all_config) + + def alldefconfig(self, all_config=None): + """Run alldefconfig. + + all_config: fragment config file for KCONFIG_ALLCONFIG (optional) + returncode: exit status of the Kconfig executable + """ + return self._allconfig('alldef', all_config) + + def randconfig(self, all_config=None, seed=None): + """Run randconfig. + + all_config: fragment config file for KCONFIG_ALLCONFIG (optional) + seed: the seed for randconfig (optional) + returncode: exit status of the Kconfig executable + """ + if seed is not None: + extra_env = {'KCONFIG_SEED': hex(seed)} + else: + extra_env = {} + + return self._allconfig('rand', all_config, extra_env=extra_env) + + def savedefconfig(self, dot_config): + """Run savedefconfig. + + dot_config: .config file for input + returncode: exit status of the Kconfig executable + """ + return self._run_conf('--savedefconfig', out_file='defconfig') + + def listnewconfig(self, dot_config=None): + """Run listnewconfig. + + dot_config: .config file to use for configuration base (optional) + returncode: exit status of the Kconfig executable + """ + return self._run_conf('--listnewconfig', dot_config=dot_config, + out_file=None) + + # checkers + def _read_and_compare(self, compare, expected): + """Compare the result with expectation. + + compare: function to compare the result with expectation + expected: file that contains the expected data + """ + with open(os.path.join(self._test_dir, expected)) as f: + expected_data = f.read() + return compare(self, expected_data) + + def _contains(self, attr, expected): + return self._read_and_compare( + lambda s, e: getattr(s, attr).find(e) >= 0, + expected) + + def _matches(self, attr, expected): + return self._read_and_compare(lambda s, e: getattr(s, attr) == e, + expected) + + def config_contains(self, expected): + """Check if resulted configuration contains expected data. + + expected: file that contains the expected data + returncode: True if result contains the expected data, False otherwise + """ + return self._contains('config', expected) + + def config_matches(self, expected): + """Check if resulted configuration exactly matches expected data. + + expected: file that contains the expected data + returncode: True if result matches the expected data, False otherwise + """ + return self._matches('config', expected) + + def stdout_contains(self, expected): + """Check if resulted stdout contains expected data. + + expected: file that contains the expected data + returncode: True if result contains the expected data, False otherwise + """ + return self._contains('stdout', expected) + + def stdout_matches(self, expected): + """Check if resulted stdout exactly matches expected data. + + expected: file that contains the expected data + returncode: True if result matches the expected data, False otherwise + """ + return self._matches('stdout', expected) + + def stderr_contains(self, expected): + """Check if resulted stderr contains expected data. + + expected: file that contains the expected data + returncode: True if result contains the expected data, False otherwise + """ + return self._contains('stderr', expected) + + def stderr_matches(self, expected): + """Check if resulted stderr exactly matches expected data. + + expected: file that contains the expected data + returncode: True if result matches the expected data, False otherwise + """ + return self._matches('stderr', expected) + + +@pytest.fixture(scope="module") +def conf(request): + """Create a Conf instance and provide it to test functions.""" + return Conf(request) diff --git a/scripts/kconfig/tests/err_recursive_dep/Kconfig b/scripts/kconfig/tests/err_recursive_dep/Kconfig new file mode 100644 index 000000000..ebdb3ffd8 --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_dep/Kconfig @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 + +# depends on itself + +config A + bool "A" + depends on A + +# select itself + +config B + bool + select B + +# depends on each other + +config C1 + bool "C1" + depends on C2 + +config C2 + bool "C2" + depends on C1 + +# depends on and select + +config D1 + bool "D1" + depends on D2 + select D2 + +config D2 + bool + +# depends on and imply + +config E1 + bool "E1" + depends on E2 + imply E2 + +config E2 + bool "E2" + +# property + +config F1 + bool "F1" + default F2 + +config F2 + bool "F2" + depends on F1 + +# menu + +menu "menu depending on its content" + depends on G + +config G + bool "G" + +endmenu diff --git a/scripts/kconfig/tests/err_recursive_dep/__init__.py b/scripts/kconfig/tests/err_recursive_dep/__init__.py new file mode 100644 index 000000000..5f3821b43 --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_dep/__init__.py @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Detect recursive dependency error. + +Recursive dependency should be treated as an error. +""" + +def test(conf): + assert conf.oldaskconfig() == 1 + assert conf.stderr_contains('expected_stderr') diff --git a/scripts/kconfig/tests/err_recursive_dep/expected_stderr b/scripts/kconfig/tests/err_recursive_dep/expected_stderr new file mode 100644 index 000000000..fc2e860af --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_dep/expected_stderr @@ -0,0 +1,38 @@ +error: recursive dependency detected! + symbol A depends on A +For a resolution refer to Documentation/kbuild/kconfig-language.rst +subsection "Kconfig recursive dependency limitations" + +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" + +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" + +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" + +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" + +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" + +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/err_recursive_inc/Kconfig b/scripts/kconfig/tests/err_recursive_inc/Kconfig new file mode 100644 index 000000000..c6f4adec7 --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +source "Kconfig.inc1" diff --git a/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1 b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1 new file mode 100644 index 000000000..01cbf0d69 --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1 @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + + + +source "Kconfig.inc2" diff --git a/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2 b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2 new file mode 100644 index 000000000..82351075a --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2 @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + + +source "Kconfig.inc3" diff --git a/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3 b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3 new file mode 100644 index 000000000..c6f4adec7 --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3 @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +source "Kconfig.inc1" diff --git a/scripts/kconfig/tests/err_recursive_inc/__init__.py b/scripts/kconfig/tests/err_recursive_inc/__init__.py new file mode 100644 index 000000000..27aa1895a --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_inc/__init__.py @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Detect recursive inclusion error. + +If recursive inclusion is detected, it should fail with error messages. +""" + + +def test(conf): + assert conf.oldaskconfig() != 0 + assert conf.stderr_contains('expected_stderr') diff --git a/scripts/kconfig/tests/err_recursive_inc/expected_stderr b/scripts/kconfig/tests/err_recursive_inc/expected_stderr new file mode 100644 index 000000000..b070a31fd --- /dev/null +++ b/scripts/kconfig/tests/err_recursive_inc/expected_stderr @@ -0,0 +1,6 @@ +Recursive inclusion detected. +Inclusion path: + current file : Kconfig.inc1 + included from: Kconfig.inc3:2 + included from: Kconfig.inc2:4 + included from: Kconfig.inc1:5 diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig b/scripts/kconfig/tests/err_repeated_inc/Kconfig new file mode 100644 index 000000000..09a88fd29 --- /dev/null +++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "Kconfig.inc1" diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc1 b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc1 new file mode 100644 index 000000000..495dc3831 --- /dev/null +++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc1 @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "Kconfig.inc2" +source "Kconfig.inc3" diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc2 b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc2 new file mode 100644 index 000000000..2b630eec2 --- /dev/null +++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc2 @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "Kconfig.inc3" diff --git a/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc3 b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc3 new file mode 100644 index 000000000..a4e40e534 --- /dev/null +++ b/scripts/kconfig/tests/err_repeated_inc/Kconfig.inc3 @@ -0,0 +1 @@ +# SPDX-License-Identifier: GPL-2.0-only diff --git a/scripts/kconfig/tests/err_repeated_inc/__init__.py b/scripts/kconfig/tests/err_repeated_inc/__init__.py new file mode 100644 index 000000000..129d740a8 --- /dev/null +++ b/scripts/kconfig/tests/err_repeated_inc/__init__.py @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Detect repeated inclusion error. + +If repeated inclusion is detected, it should fail with error message. +""" + +def test(conf): + assert conf.oldaskconfig() != 0 + assert conf.stderr_contains('expected_stderr') diff --git a/scripts/kconfig/tests/err_repeated_inc/expected_stderr b/scripts/kconfig/tests/err_repeated_inc/expected_stderr new file mode 100644 index 000000000..53071430e --- /dev/null +++ b/scripts/kconfig/tests/err_repeated_inc/expected_stderr @@ -0,0 +1,2 @@ +Kconfig.inc1:4: error: repeated inclusion of Kconfig.inc3 +Kconfig.inc2:3: note: location of first inclusion of Kconfig.inc3 diff --git a/scripts/kconfig/tests/err_transitional/Kconfig b/scripts/kconfig/tests/err_transitional/Kconfig new file mode 100644 index 000000000..a75ed3b2f --- /dev/null +++ b/scripts/kconfig/tests/err_transitional/Kconfig @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test that transitional symbols cannot have properties other than help + +config BAD_DEFAULT + bool + transitional + default y + help + This transitional symbol illegally has a default property. + +config BAD_PROMPT + bool + transitional + prompt "Bad prompt" + help + This transitional symbol illegally has a prompt. + +config BAD_SELECT + bool + transitional + select OTHER_SYMBOL + help + This transitional symbol illegally has a select. + +config BAD_IMPLY + bool + transitional + imply OTHER_SYMBOL + help + This transitional symbol illegally has an imply. + +config BAD_DEPENDS + bool + transitional + depends on OTHER_SYMBOL + help + This transitional symbol illegally has a depends. + +config BAD_RANGE + int + transitional + range 1 10 + help + This transitional symbol illegally has a range. + +config BAD_NO_TYPE + transitional + help + This transitional symbol illegally has no type specified. + +config OTHER_SYMBOL + bool diff --git a/scripts/kconfig/tests/err_transitional/__init__.py b/scripts/kconfig/tests/err_transitional/__init__.py new file mode 100644 index 000000000..7dffb5b08 --- /dev/null +++ b/scripts/kconfig/tests/err_transitional/__init__.py @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Test that transitional symbols with invalid properties are rejected. + +Transitional symbols can only have help sections. Any other properties +(default, select, depends, etc.) should cause a parser error. +""" + +def test(conf): + # This should fail with exit code 1 due to invalid transitional symbol + assert conf.olddefconfig() == 1 + + # Check that the error message is about transitional symbols + assert conf.stderr_contains('expected_stderr') diff --git a/scripts/kconfig/tests/err_transitional/expected_stderr b/scripts/kconfig/tests/err_transitional/expected_stderr new file mode 100644 index 000000000..b52db4f68 --- /dev/null +++ b/scripts/kconfig/tests/err_transitional/expected_stderr @@ -0,0 +1,7 @@ +Kconfig:46:warning: config symbol defined without type +Kconfig:7: error: transitional symbols can only have help sections +Kconfig:14: error: transitional symbols can only have help sections +Kconfig:21: error: transitional symbols can only have help sections +Kconfig:28: error: transitional symbols can only have help sections +Kconfig:32: error: transitional symbols can only have help sections +Kconfig:42: error: transitional symbols can only have help sections diff --git a/scripts/kconfig/tests/new_choice_with_dep/Kconfig b/scripts/kconfig/tests/new_choice_with_dep/Kconfig new file mode 100644 index 000000000..127731c04 --- /dev/null +++ b/scripts/kconfig/tests/new_choice_with_dep/Kconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0 + +config A + bool "A" + help + This is a new symbol. + +choice + prompt "Choice ?" + depends on A + help + "depends on A" has been newly added. + +config CHOICE_B + bool "Choice B" + +config CHOICE_C + bool "Choice C" + help + This is a new symbol, so should be asked. + +endchoice + +choice + prompt "Choice2 ?" + +config CHOICE_D + bool "Choice D" + +config CHOICE_E + bool "Choice E" + +config CHOICE_F + bool "Choice F" + depends on A + help + This is a new symbol, so should be asked. + +endchoice diff --git a/scripts/kconfig/tests/new_choice_with_dep/__init__.py b/scripts/kconfig/tests/new_choice_with_dep/__init__.py new file mode 100644 index 000000000..fe9d32253 --- /dev/null +++ b/scripts/kconfig/tests/new_choice_with_dep/__init__.py @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Ask new choice values when they become visible. + +If new choice values are added with new dependency, and they become +visible during user configuration, oldconfig should recognize them +as (NEW), and ask the user for choice. + +Related Linux commit: 5d09598d488f081e3be23f885ed65cbbe2d073b5 +""" + + +def test(conf): + assert conf.oldconfig('config', 'y') == 0 + assert conf.stdout_contains('expected_stdout') diff --git a/scripts/kconfig/tests/new_choice_with_dep/config b/scripts/kconfig/tests/new_choice_with_dep/config new file mode 100644 index 000000000..47ef95d56 --- /dev/null +++ b/scripts/kconfig/tests/new_choice_with_dep/config @@ -0,0 +1,3 @@ +CONFIG_CHOICE_B=y +# CONFIG_CHOICE_D is not set +CONFIG_CHOICE_E=y diff --git a/scripts/kconfig/tests/new_choice_with_dep/expected_stdout b/scripts/kconfig/tests/new_choice_with_dep/expected_stdout new file mode 100644 index 000000000..74dc0bcb2 --- /dev/null +++ b/scripts/kconfig/tests/new_choice_with_dep/expected_stdout @@ -0,0 +1,10 @@ +A (A) [N/y/?] (NEW) y + Choice ? + > 1. Choice B (CHOICE_B) + 2. Choice C (CHOICE_C) (NEW) + choice[1-2?]: +Choice2 ? + 1. Choice D (CHOICE_D) +> 2. Choice E (CHOICE_E) + 3. Choice F (CHOICE_F) (NEW) +choice[1-3?]: diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig b/scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig new file mode 100644 index 000000000..4767aab6d --- /dev/null +++ b/scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +config A + bool "A" + +choice + prompt "Choice ?" + depends on A + +config CHOICE_B + bool "Choice B" + +config CHOICE_C + bool "Choice C" + +endchoice diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py new file mode 100644 index 000000000..791ed659c --- /dev/null +++ b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Do not write choice values to .config if the dependency is unmet. + +"# CONFIG_... is not set" should not be written into the .config file +for symbols with unmet dependency. + +This was not working correctly for choice values because choice needs +a bit different symbol computation. + +This checks that no unneeded "# CONFIG_... is not set" is contained in +the .config file. + +Related Linux commit: cb67ab2cd2b8abd9650292c986c79901e3073a59 +""" + + +def test(conf): + assert conf.oldaskconfig('config', 'n') == 0 + assert conf.config_matches('expected_config') diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/config b/scripts/kconfig/tests/no_write_if_dep_unmet/config new file mode 100644 index 000000000..abd280e2f --- /dev/null +++ b/scripts/kconfig/tests/no_write_if_dep_unmet/config @@ -0,0 +1 @@ +CONFIG_A=y diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/expected_config b/scripts/kconfig/tests/no_write_if_dep_unmet/expected_config new file mode 100644 index 000000000..473228810 --- /dev/null +++ b/scripts/kconfig/tests/no_write_if_dep_unmet/expected_config @@ -0,0 +1,5 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +# CONFIG_A is not set diff --git a/scripts/kconfig/tests/preprocess/builtin_func/Kconfig b/scripts/kconfig/tests/preprocess/builtin_func/Kconfig new file mode 100644 index 000000000..baa328827 --- /dev/null +++ b/scripts/kconfig/tests/preprocess/builtin_func/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 + +# 'info' prints the argument to stdout. +$(info,hello world 0) + +# 'warning-if', if the first argument is y, sends the second argument to stderr, +# and the message is prefixed with the current file name and line number. +$(warning-if,y,hello world 1) + +# 'error-if' is similar, but it terminates the parsing immediately. +# The following is just no-op since the first argument is not y. +$(error-if,n,this should not be printed) + +# Shorthand +warning = $(warning-if,y,$(1)) + +# 'shell' executes a command, and returns its stdout. +$(warning,$(shell,echo hello world 3)) + +# Every newline in the output is replaced with a space, +# but any trailing newlines are deleted. +$(warning,$(shell,printf 'hello\nworld\n\n4\n\n\n')) + +# 'filename' is expanded to the currently parsed file name, +# 'lineno' to the line number. +$(warning,filename=$(filename)) +$(warning,lineno=$(lineno)) diff --git a/scripts/kconfig/tests/preprocess/builtin_func/__init__.py b/scripts/kconfig/tests/preprocess/builtin_func/__init__.py new file mode 100644 index 000000000..2e53ba08f --- /dev/null +++ b/scripts/kconfig/tests/preprocess/builtin_func/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Built-in function tests. +""" + +def test(conf): + assert conf.oldaskconfig() == 0 + assert conf.stdout_contains('expected_stdout') + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/preprocess/builtin_func/expected_stderr b/scripts/kconfig/tests/preprocess/builtin_func/expected_stderr new file mode 100644 index 000000000..33ea9ca38 --- /dev/null +++ b/scripts/kconfig/tests/preprocess/builtin_func/expected_stderr @@ -0,0 +1,5 @@ +Kconfig:8: hello world 1 +Kconfig:18: hello world 3 +Kconfig:22: hello world 4 +Kconfig:26: filename=Kconfig +Kconfig:27: lineno=27 diff --git a/scripts/kconfig/tests/preprocess/builtin_func/expected_stdout b/scripts/kconfig/tests/preprocess/builtin_func/expected_stdout new file mode 100644 index 000000000..82de3a7e9 --- /dev/null +++ b/scripts/kconfig/tests/preprocess/builtin_func/expected_stdout @@ -0,0 +1 @@ +hello world 0 diff --git a/scripts/kconfig/tests/preprocess/circular_expansion/Kconfig b/scripts/kconfig/tests/preprocess/circular_expansion/Kconfig new file mode 100644 index 000000000..6838997c2 --- /dev/null +++ b/scripts/kconfig/tests/preprocess/circular_expansion/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +X = $(Y) +Y = $(X) +$(info $(X)) diff --git a/scripts/kconfig/tests/preprocess/circular_expansion/__init__.py b/scripts/kconfig/tests/preprocess/circular_expansion/__init__.py new file mode 100644 index 000000000..419bda3e0 --- /dev/null +++ b/scripts/kconfig/tests/preprocess/circular_expansion/__init__.py @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Detect circular variable expansion. + +If a recursively expanded variable references itself (eventually), +it should fail with an error message. +""" + +def test(conf): + assert conf.oldaskconfig() != 0 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr b/scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr new file mode 100644 index 000000000..cde68fa98 --- /dev/null +++ b/scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr @@ -0,0 +1 @@ +Kconfig:5: Recursive variable 'X' references itself (eventually) diff --git a/scripts/kconfig/tests/preprocess/escape/Kconfig b/scripts/kconfig/tests/preprocess/escape/Kconfig new file mode 100644 index 000000000..4e3f44445 --- /dev/null +++ b/scripts/kconfig/tests/preprocess/escape/Kconfig @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Shorthand +warning = $(warning-if,y,$(1)) + +# You can not pass commas directly to a function since they are treated as +# delimiters. You can use the following trick to do so. +comma := , +$(warning,hello$(comma) world) + +# Like Make, single quotes, double quotes, spaces are treated verbatim. +# The following prints the text as-is. +$(warning, ' " '" ' ''' "'") + +# Unlike Make, '$' has special meaning only when it is followed by '('. +# No need to escape '$' itself. +$(warning,$) +$(warning,$$) +$ := 1 +$(warning,$($)) + +# You need a trick to escape '$' followed by '(' +# The following should print "$(X)". It should not be expanded further. +dollar := $ +$(warning,$(dollar)(X)) + +# You need a trick to treat unbalanced parentheses. +# The following should print "(". +left_paren := ( +$(warning,$(left_paren)) + +# A simple expanded should not be expanded multiple times. +# The following should print "$(X)". It should not be expanded further. +Y := $(dollar)(X) +$(warning,$(Y)) + +# The following should print "$(X)" as well. +Y = $(dollar)(X) +$(warning,$(Y)) + +# The following should print "$(". +# It should not be emit "unterminated reference" error. +unterminated := $(dollar)( +$(warning,$(unterminated)) diff --git a/scripts/kconfig/tests/preprocess/escape/__init__.py b/scripts/kconfig/tests/preprocess/escape/__init__.py new file mode 100644 index 000000000..7ee8e747f --- /dev/null +++ b/scripts/kconfig/tests/preprocess/escape/__init__.py @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Escape sequence tests. +""" + +def test(conf): + assert conf.oldaskconfig() == 0 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/preprocess/escape/expected_stderr b/scripts/kconfig/tests/preprocess/escape/expected_stderr new file mode 100644 index 000000000..1c00957dd --- /dev/null +++ b/scripts/kconfig/tests/preprocess/escape/expected_stderr @@ -0,0 +1,10 @@ +Kconfig:9: hello, world +Kconfig:13: ' " '" ' ''' "'" +Kconfig:17: $ +Kconfig:18: $$ +Kconfig:20: 1 +Kconfig:25: $(X) +Kconfig:30: ( +Kconfig:35: $(X) +Kconfig:39: $(X) +Kconfig:44: $( diff --git a/scripts/kconfig/tests/preprocess/variable/Kconfig b/scripts/kconfig/tests/preprocess/variable/Kconfig new file mode 100644 index 000000000..9ce2f95cb --- /dev/null +++ b/scripts/kconfig/tests/preprocess/variable/Kconfig @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Shorthand +warning = $(warning-if,y,$(1)) + +# Simply expanded variable. +X := 1 +SIMPLE := $(X) +X := 2 +$(warning,SIMPLE = $(SIMPLE)) + +# Recursively expanded variable. +X := 1 +RECURSIVE = $(X) +X := 2 +$(warning,RECURSIVE = $(RECURSIVE)) + +# Append something to a simply expanded variable. +Y := 3 +SIMPLE += $(Y) +Y := 4 +$(warning,SIMPLE = $(SIMPLE)) + +# Append something to a recursively expanded variable. +Y := 3 +RECURSIVE += $(Y) +Y := 4 +$(warning,RECURSIVE = $(RECURSIVE)) + +# Use += operator to an undefined variable. +# This works as a recursively expanded variable. +Y := 3 +UNDEFINED_VARIABLE += $(Y) +Y := 4 +$(warning,UNDEFINED_VARIABLE = $(UNDEFINED_VARIABLE)) + +# You can use variable references for the lefthand side of assignment statement. +X := A +Y := B +$(X)$(Y) := 5 +$(warning,AB = $(AB)) + +# User-defined function. +greeting = $(1), my name is $(2). +$(warning,$(greeting,Hello,John)) + +# The number of arguments is not checked for user-defined functions. +# If some arguments are optional, it is useful to pass fewer parameters. +# $(2) will be blank in this case. +$(warning,$(greeting,Hello)) + +# Unreferenced parameters are just ignored. +$(warning,$(greeting,Hello,John,ignored,ignored)) diff --git a/scripts/kconfig/tests/preprocess/variable/__init__.py b/scripts/kconfig/tests/preprocess/variable/__init__.py new file mode 100644 index 000000000..e88b1708d --- /dev/null +++ b/scripts/kconfig/tests/preprocess/variable/__init__.py @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Variable and user-defined function tests. +""" + +def test(conf): + assert conf.oldaskconfig() == 0 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/preprocess/variable/expected_stderr b/scripts/kconfig/tests/preprocess/variable/expected_stderr new file mode 100644 index 000000000..a4841c3fd --- /dev/null +++ b/scripts/kconfig/tests/preprocess/variable/expected_stderr @@ -0,0 +1,9 @@ +Kconfig:10: SIMPLE = 1 +Kconfig:16: RECURSIVE = 2 +Kconfig:22: SIMPLE = 1 3 +Kconfig:28: RECURSIVE = 2 4 +Kconfig:35: UNDEFINED_VARIABLE = 4 +Kconfig:41: AB = 5 +Kconfig:45: Hello, my name is John. +Kconfig:50: Hello, my name is . +Kconfig:53: Hello, my name is John. diff --git a/scripts/kconfig/tests/pytest.ini b/scripts/kconfig/tests/pytest.ini new file mode 100644 index 000000000..85d7ce8e4 --- /dev/null +++ b/scripts/kconfig/tests/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +addopts = --verbose + +# Pytest requires that test files have unique names, because pytest imports +# them as top-level modules. It is silly to prefix or suffix a test file with +# the directory name that contains it. Use __init__.py for all test files. +python_files = __init__.py diff --git a/scripts/kconfig/tests/transitional/Kconfig b/scripts/kconfig/tests/transitional/Kconfig new file mode 100644 index 000000000..faa4d396f --- /dev/null +++ b/scripts/kconfig/tests/transitional/Kconfig @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test transitional symbols for config migration with all Kconfig types + +# Enable module support for tristate testing +config MODULES + bool "Enable loadable module support" + modules + default y + +# Basic migration tests for all types +config NEW_BOOL + bool "New bool option" + default OLD_BOOL + +config OLD_BOOL + bool + transitional + +config NEW_TRISTATE + tristate "New tristate option" + default OLD_TRISTATE + +config OLD_TRISTATE + tristate + transitional + +config NEW_STRING + string "New string option" + default OLD_STRING + +config OLD_STRING + string + transitional + +config NEW_HEX + hex "New hex option" + default OLD_HEX + +config OLD_HEX + hex + transitional + +config NEW_INT + int "New int option" + default OLD_INT + +config OLD_INT + int + transitional + +# Precedence tests for all types +config NEW_BOOL_PRECEDENCE + bool "New bool option with precedence" + default OLD_BOOL_PRECEDENCE + +config OLD_BOOL_PRECEDENCE + bool + transitional + +config NEW_STRING_PRECEDENCE + string "New string option with precedence" + default OLD_STRING_PRECEDENCE + +config OLD_STRING_PRECEDENCE + string + transitional + +config NEW_TRISTATE_PRECEDENCE + tristate "New tristate option with precedence" + default OLD_TRISTATE_PRECEDENCE + +config OLD_TRISTATE_PRECEDENCE + tristate + transitional + +config NEW_HEX_PRECEDENCE + hex "New hex option with precedence" + default OLD_HEX_PRECEDENCE + +config OLD_HEX_PRECEDENCE + hex + transitional + +config NEW_INT_PRECEDENCE + int "New int option with precedence" + default OLD_INT_PRECEDENCE + +config OLD_INT_PRECEDENCE + int + transitional + +# Test that help sections are allowed for transitional symbols +config OLD_WITH_HELP + bool + transitional + help + This transitional symbol has a help section to validate that help is allowed. + +# Test that we can set something to =n via transitional symbol +config NEW_DISABLED + tristate "Check for setting to disabled" + default OLD_DISABLED + +config OLD_DISABLED + tristate + transitional + +# Test that a potential new value disappears if it lacks a prompt +config NEW_DISABLED_UNSAVED + tristate + default OLD_DISABLED + +config OLD_DISABLED_UNSAVED + tristate + transitional + +# Test conditional default: transitional value should not prevent prompting +# when default visibility makes the expression evaluate to 'no' +config DEPENDENCY_TEST + bool "Dependency for testing" + default n + +config NEW_CONDITIONAL_DEFAULT + bool "New option with conditional default" + default OLD_CONDITIONAL_DEFAULT if DEPENDENCY_TEST + +config OLD_CONDITIONAL_DEFAULT + bool + transitional + +config REGULAR_OPTION + bool "Regular option" diff --git a/scripts/kconfig/tests/transitional/__init__.py b/scripts/kconfig/tests/transitional/__init__.py new file mode 100644 index 000000000..b50ba2397 --- /dev/null +++ b/scripts/kconfig/tests/transitional/__init__.py @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Test transitional symbol migration functionality for all Kconfig types. + +This tests that: +- OLD_* options in existing .config cause NEW_* options to be set +- OLD_* options are not written to the new .config file +- NEW_* options appear in the new .config file with correct values +- NEW_* options with defaults from transitional symbols are not prompted +- All Kconfig types work correctly: bool, tristate, string, hex, int +- User-set NEW values take precedence over conflicting OLD transitional values +""" + +def test(conf): + # Run olddefconfig to process the migration with the initial config + assert conf.olddefconfig(dot_config='initial_config') == 0 + + # Check that the configuration matches expected output + assert conf.config_contains('expected_config') + + # Test oldconfig to ensure symbols with transitional defaults are not prompted + assert conf.oldconfig(dot_config='initial_config', in_keys='n\n') == 0 + + # Except for when conditional default evaluates to 'no' + assert conf.stdout_contains('expected_stdout') diff --git a/scripts/kconfig/tests/transitional/expected_config b/scripts/kconfig/tests/transitional/expected_config new file mode 100644 index 000000000..e01f5f070 --- /dev/null +++ b/scripts/kconfig/tests/transitional/expected_config @@ -0,0 +1,15 @@ +CONFIG_MODULES=y +CONFIG_NEW_BOOL=y +CONFIG_NEW_TRISTATE=m +CONFIG_NEW_STRING="test string" +CONFIG_NEW_HEX=0x1234 +CONFIG_NEW_INT=42 +# CONFIG_NEW_BOOL_PRECEDENCE is not set +CONFIG_NEW_STRING_PRECEDENCE="user value" +CONFIG_NEW_TRISTATE_PRECEDENCE=y +CONFIG_NEW_HEX_PRECEDENCE=0xABCD +CONFIG_NEW_INT_PRECEDENCE=100 +# CONFIG_NEW_DISABLED is not set +# CONFIG_DEPENDENCY_TEST is not set +# CONFIG_NEW_CONDITIONAL_DEFAULT is not set +# CONFIG_REGULAR_OPTION is not set diff --git a/scripts/kconfig/tests/transitional/expected_stdout b/scripts/kconfig/tests/transitional/expected_stdout new file mode 100644 index 000000000..6f0b285d6 --- /dev/null +++ b/scripts/kconfig/tests/transitional/expected_stdout @@ -0,0 +1 @@ +New option with conditional default (NEW_CONDITIONAL_DEFAULT) [N/y/?] (NEW) n diff --git a/scripts/kconfig/tests/transitional/initial_config b/scripts/kconfig/tests/transitional/initial_config new file mode 100644 index 000000000..68b7da672 --- /dev/null +++ b/scripts/kconfig/tests/transitional/initial_config @@ -0,0 +1,20 @@ +CONFIG_MODULES=y +CONFIG_OLD_BOOL=y +CONFIG_OLD_TRISTATE=m +CONFIG_OLD_STRING="test string" +CONFIG_OLD_HEX=0x1234 +CONFIG_OLD_INT=42 +# CONFIG_NEW_BOOL_PRECEDENCE is not set +CONFIG_OLD_BOOL_PRECEDENCE=y +CONFIG_NEW_STRING_PRECEDENCE="user value" +CONFIG_OLD_STRING_PRECEDENCE="old value" +CONFIG_NEW_TRISTATE_PRECEDENCE=y +CONFIG_OLD_TRISTATE_PRECEDENCE=m +CONFIG_NEW_HEX_PRECEDENCE=0xABCD +CONFIG_OLD_HEX_PRECEDENCE=0x5678 +CONFIG_NEW_INT_PRECEDENCE=100 +CONFIG_OLD_INT_PRECEDENCE=200 +# CONFIG_OLD_DISABLED is not set +# CONFIG_OLD_DISABLED_UNSAVED is not set +# CONFIG_DEPENDENCY_TEST is not set +CONFIG_OLD_CONDITIONAL_DEFAULT=y diff --git a/scripts/kconfig/tests/warn_changed_input/Kconfig b/scripts/kconfig/tests/warn_changed_input/Kconfig new file mode 100644 index 000000000..69845e2f3 --- /dev/null +++ b/scripts/kconfig/tests/warn_changed_input/Kconfig @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DEP + bool "DEP" + help + Test dependency symbol for Kconfig warning coverage. + This is used by the warn_changed_input selftest. + It intentionally stays unset in the input fragment. + The test checks how dependent user input is adjusted. + +config A + bool "A" + depends on DEP + help + Test bool symbol for changed-input diagnostics. + The input fragment requests this symbol as built-in. + The unmet dependency on DEP forces the final value to n. + The warning should report that downgrade. + +config NUM + int "NUM" + range 10 20 + help + Test integer symbol for changed-input diagnostics. + The input fragment requests a value outside the allowed range. + Kconfig resolves it to the constrained in-range value. + The warning should report that adjustment. + +config DUP + bool "DUP" + depends on DEP + help + Test duplicate-definition handling for changed-input diagnostics. + The input fragment requests this symbol as built-in. + The duplicate definition below must not produce a duplicate warning. + This keeps the warning output stable for repeated menu entries. + +config DUP + bool + depends on DEP diff --git a/scripts/kconfig/tests/warn_changed_input/__init__.py b/scripts/kconfig/tests/warn_changed_input/__init__.py new file mode 100644 index 000000000..4c3bca6af --- /dev/null +++ b/scripts/kconfig/tests/warn_changed_input/__init__.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Test optional warnings for user-provided values changed by Kconfig. + +Warnings should stay disabled by default, and should only appear when +KCONFIG_WARN_CHANGED_INPUT is enabled. +""" + + +def test(conf): + assert conf.olddefconfig('config') == 0 + assert 'user-provided values changed by Kconfig' not in conf.stderr + + assert conf._run_conf('--olddefconfig', dot_config='config', + extra_env={ + 'KCONFIG_WARN_CHANGED_INPUT': '1', + }) == 0 + assert conf.stderr_contains('expected_stderr') + assert conf.config_matches('expected_config') + + assert conf._run_conf('--olddefconfig', dot_config='config', + extra_env={ + 'KCONFIG_WARN_CHANGED_INPUT': '1', + }, silent=True) == 0 + assert conf.stderr_contains('expected_stderr') + + assert conf._run_conf('--savedefconfig=defconfig', dot_config='config', + out_file='defconfig', + extra_env={ + 'KCONFIG_WARN_CHANGED_INPUT': '1', + }) == 0 + assert conf.stderr_contains('expected_stderr') + assert conf.config_matches('expected_defconfig') diff --git a/scripts/kconfig/tests/warn_changed_input/config b/scripts/kconfig/tests/warn_changed_input/config new file mode 100644 index 000000000..dbe93ff26 --- /dev/null +++ b/scripts/kconfig/tests/warn_changed_input/config @@ -0,0 +1,3 @@ +CONFIG_A=y +CONFIG_NUM=30 +CONFIG_DUP=y diff --git a/scripts/kconfig/tests/warn_changed_input/expected_config b/scripts/kconfig/tests/warn_changed_input/expected_config new file mode 100644 index 000000000..fe8bbec66 --- /dev/null +++ b/scripts/kconfig/tests/warn_changed_input/expected_config @@ -0,0 +1,6 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +# CONFIG_DEP is not set +CONFIG_NUM=20 diff --git a/scripts/kconfig/tests/warn_changed_input/expected_defconfig b/scripts/kconfig/tests/warn_changed_input/expected_defconfig new file mode 100644 index 000000000..af9e34851 --- /dev/null +++ b/scripts/kconfig/tests/warn_changed_input/expected_defconfig @@ -0,0 +1 @@ +CONFIG_NUM=20 diff --git a/scripts/kconfig/tests/warn_changed_input/expected_stderr b/scripts/kconfig/tests/warn_changed_input/expected_stderr new file mode 100644 index 000000000..9ec8446b4 --- /dev/null +++ b/scripts/kconfig/tests/warn_changed_input/expected_stderr @@ -0,0 +1,4 @@ +warning: user-provided values changed by Kconfig: + CONFIG_A: y -> n + CONFIG_NUM: 30 -> 20 + CONFIG_DUP: y -> n diff --git a/scripts/kconfig/util.c b/scripts/kconfig/util.c new file mode 100644 index 000000000..0809aa061 --- /dev/null +++ b/scripts/kconfig/util.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002-2005 Roman Zippel <zippel@linux-m68k.org> + * Copyright (C) 2002-2005 Sam Ravnborg <sam@ravnborg.org> + */ + +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include <hash.h> +#include <hashtable.h> +#include <xalloc.h> +#include "lkc.h" + +/* hash table of all parsed Kconfig files */ +static HASHTABLE_DEFINE(file_hashtable, 1U << 11); + +struct file { + struct hlist_node node; + struct { + const char *name; + int lineno; + } parent; + char name[]; +}; + +static void die_duplicated_include(struct file *file, + const char *parent, int lineno) +{ + fprintf(stderr, + "%s:%d: error: repeated inclusion of %s\n" + "%s:%d: note: location of first inclusion of %s\n", + parent, lineno, file->name, + file->parent.name, file->parent.lineno, file->name); + exit(1); +} + +/* file already present in list? If not add it */ +const char *file_lookup(const char *name, + const char *parent_name, int parent_lineno) +{ + const char *parent = NULL; + struct file *file; + size_t len; + int hash = hash_str(name); + + if (parent_name) + parent = file_lookup(parent_name, NULL, 0); + + hash_for_each_possible(file_hashtable, file, node, hash) + if (!strcmp(name, file->name)) { + if (!parent_name) + return file->name; + die_duplicated_include(file, parent, parent_lineno); + } + + len = strlen(name); + file = xmalloc(sizeof(*file) + len + 1); + memset(file, 0, sizeof(*file)); + memcpy(file->name, name, len); + file->name[len] = '\0'; + file->parent.name = parent; + file->parent.lineno = parent_lineno; + + hash_add(file_hashtable, &file->node, hash); + + str_printf(&autoconf_cmd, "\t%s \\\n", name); + + return file->name; +} + +/* Allocate initial growable string */ +struct gstr str_new(void) +{ + struct gstr gs; + gs.s = xmalloc(sizeof(char) * 64); + gs.len = 64; + gs.max_width = 0; + strcpy(gs.s, "\0"); + return gs; +} + +/* Free storage for growable string */ +void str_free(struct gstr *gs) +{ + free(gs->s); + gs->s = NULL; + gs->len = 0; +} + +/* Append to growable string */ +void str_append(struct gstr *gs, const char *s) +{ + size_t l; + if (s) { + l = strlen(gs->s) + strlen(s) + 1; + if (l > gs->len) { + gs->s = xrealloc(gs->s, l); + gs->len = l; + } + strcat(gs->s, s); + } +} + +/* Append printf formatted string to growable string */ +void str_printf(struct gstr *gs, const char *fmt, ...) +{ + va_list ap; + char s[10000]; /* big enough... */ + va_start(ap, fmt); + vsnprintf(s, sizeof(s), fmt, ap); + str_append(gs, s); + va_end(ap); +} + +/* Retrieve value of growable string */ +char *str_get(const struct gstr *gs) +{ + return gs->s; +} diff --git a/scripts/kernel-doc b/scripts/kernel-doc new file mode 120000 index 000000000..9cc1459ff --- /dev/null +++ b/scripts/kernel-doc @@ -0,0 +1 @@ +../tools/docs/kernel-doc \ No newline at end of file diff --git a/scripts/ksymoops/README b/scripts/ksymoops/README new file mode 100644 index 000000000..413043980 --- /dev/null +++ b/scripts/ksymoops/README @@ -0,0 +1,7 @@ +ksymoops has been removed from the kernel. It was always meant to be a +free standing utility, not linked to any particular kernel version. +The latest version can be found in +https://www.kernel.org/pub/linux/utils/kernel/ksymoops together with patches to +other utilities in order to give more accurate Oops debugging. + +Keith Owens <kaos@ocs.com.au> Sat Jun 19 10:30:34 EST 1999 diff --git a/scripts/ld-version.sh b/scripts/ld-version.sh new file mode 100755 index 000000000..b9513d224 --- /dev/null +++ b/scripts/ld-version.sh @@ -0,0 +1,78 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Print the linker name and its version in a 5 or 6-digit form. +# Also, perform the minimum version check. + +set -e + +# Convert the version string x.y.z to a canonical 5 or 6-digit form. +get_canonical_version() +{ + IFS=. + set -- $1 + + # If the 2nd or 3rd field is missing, fill it with a zero. + # + # The 4th field, if present, is ignored. + # This occurs in development snapshots as in 2.35.1.20201116 + echo $((10000 * $1 + 100 * ${2:-0} + ${3:-0})) +} + +orig_args="$@" + +# Get the first line of the --version output. +IFS=' +' +set -- $(LC_ALL=C "$@" --version) + +# Split the line on spaces. +IFS=' ' +set -- $1 + +min_tool_version=$(dirname $0)/min-tool-version.sh + +if [ "$1" = GNU -a "$2" = ld ]; then + shift $(($# - 1)) + version=$1 + min_version=$($min_tool_version binutils) + name=BFD + disp_name="GNU ld" +elif [ "$1" = GNU -a "$2" = gold ]; then + echo "gold linker is not supported as it is not capable of linking the kernel proper." >&2 + exit 1 +else + while [ $# -gt 1 -a "$1" != "LLD" ]; do + shift + done + + if [ "$1" = LLD ]; then + version=$2 + min_version=$($min_tool_version llvm) + name=LLD + disp_name=LLD + else + echo "$orig_args: unknown linker" >&2 + exit 1 + fi +fi + +# 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) + +if [ "$cversion" -lt "$min_cversion" ]; then + echo >&2 "***" + echo >&2 "*** Linker is too old." + echo >&2 "*** Your $disp_name version: $version" + echo >&2 "*** Minimum $disp_name version: $min_version" + echo >&2 "***" + exit 1 +fi + +echo $name $cversion diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl new file mode 100755 index 000000000..8e992b18b --- /dev/null +++ b/scripts/leaking_addresses.pl @@ -0,0 +1,706 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-only +# +# (c) 2017 Tobin C. Harding <me@tobin.cc> +# +# leaking_addresses.pl: Scan the kernel for potential leaking addresses. +# - Scans dmesg output. +# - Walks directory tree and parses each file (for each directory in @DIRS). +# +# Use --debug to output path before parsing, this is useful to find files that +# cause the script to choke. + +# +# When the system is idle it is likely that most files under /proc/PID will be +# identical for various processes. Scanning _all_ the PIDs under /proc is +# unnecessary and implies that we are thoroughly scanning /proc. This is _not_ +# the case because there may be ways userspace can trigger creation of /proc +# files that leak addresses but were not present during a scan. For these two +# reasons we exclude all PID directories under /proc except '1/' + +use warnings; +use strict; +use POSIX; +use File::Basename; +use File::Spec; +use File::Temp qw/tempfile/; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); +use Getopt::Long qw(:config no_auto_abbrev); +use Config; +use bigint qw/hex/; +use feature 'state'; + +my $P = $0; + +# Directories to scan. +my @DIRS = ('/proc', '/sys'); + +# Timer for parsing each file, in seconds. +my $TIMEOUT = 10; + +# Kernel addresses vary by architecture. We can only auto-detect the following +# architectures (using `uname -m`). (flag --32-bit overrides auto-detection.) +my @SUPPORTED_ARCHITECTURES = ('x86_64', 'ppc64', 'x86'); + +# Command line options. +my $help = 0; +my $debug = 0; +my $raw = 0; +my $output_raw = ""; # Write raw results to file. +my $input_raw = ""; # Read raw results from file instead of scanning. +my $suppress_dmesg = 0; # Don't show dmesg in output. +my $squash_by_path = 0; # Summary report grouped by absolute path. +my $squash_by_filename = 0; # Summary report grouped by filename. +my $kallsyms_file = ""; # Kernel symbols file. +my $kernel_config_file = ""; # Kernel configuration file. +my $opt_32bit = 0; # Scan 32-bit kernel. +my $page_offset_32bit = 0; # Page offset for 32-bit kernel. + +my @kallsyms = (); + +# Skip these absolute paths. +my @skip_abs = ( + '/proc/kmsg', + '/proc/device-tree', + '/proc/1/syscall', + '/sys/firmware/devicetree', + '/sys/kernel/tracing/trace_pipe', + '/sys/kernel/debug/tracing/trace_pipe', + '/sys/kernel/security/apparmor/revision'); + +# Skip these under any subdirectory. +my @skip_any = ( + 'pagemap', + 'events', + 'access', + 'registers', + 'snapshot_raw', + 'trace_pipe_raw', + 'ptmx', + 'trace_pipe', + 'fd', + 'usbmon'); + +sub help +{ + my ($exitcode) = @_; + + print << "EOM"; + +Usage: $P [OPTIONS] + +Options: + + -o, --output-raw=<file> Save results for future processing. + -i, --input-raw=<file> Read results from file instead of scanning. + --raw Show raw results (default). + --suppress-dmesg Do not show dmesg results. + --squash-by-path Show one result per unique path. + --squash-by-filename Show one result per unique filename. + --kernel-config-file=<file> Kernel configuration file (e.g /boot/config) + --kallsyms=<file> Read kernel symbol addresses from file (for + scanning binary files). + --32-bit Scan 32-bit kernel. + --page-offset-32-bit=o Page offset (for 32-bit kernel 0xABCD1234). + -d, --debug Display debugging output. + -h, --help Display this help and exit. + +Scans the running kernel for potential leaking addresses. + +EOM + exit($exitcode); +} + +GetOptions( + 'd|debug' => \$debug, + 'h|help' => \$help, + 'o|output-raw=s' => \$output_raw, + 'i|input-raw=s' => \$input_raw, + 'suppress-dmesg' => \$suppress_dmesg, + 'squash-by-path' => \$squash_by_path, + 'squash-by-filename' => \$squash_by_filename, + 'raw' => \$raw, + 'kallsyms=s' => \$kallsyms_file, + 'kernel-config-file=s' => \$kernel_config_file, + '32-bit' => \$opt_32bit, + 'page-offset-32-bit=o' => \$page_offset_32bit, +) or help(1); + +help(0) if ($help); + +if ($input_raw) { + format_output($input_raw); + exit(0); +} + +if (!$input_raw and ($squash_by_path or $squash_by_filename)) { + printf "\nSummary reporting only available with --input-raw=<file>\n"; + printf "(First run scan with --output-raw=<file>.)\n"; + exit(128); +} + +if (!(is_supported_architecture() or $opt_32bit or $page_offset_32bit)) { + printf "\nScript does not support your architecture, sorry.\n"; + printf "\nCurrently we support: \n\n"; + foreach(@SUPPORTED_ARCHITECTURES) { + printf "\t%s\n", $_; + } + printf("\n"); + + printf("If you are running a 32-bit architecture you may use:\n"); + printf("\n\t--32-bit or --page-offset-32-bit=<page offset>\n\n"); + + my $archname = `uname -m`; + printf("Machine hardware name (`uname -m`): %s\n", $archname); + + exit(129); +} + +if ($output_raw) { + open my $fh, '>', $output_raw or die "$0: $output_raw: $!\n"; + select $fh; +} + +if ($kallsyms_file) { + open my $fh, '<', $kallsyms_file or die "$0: $kallsyms_file: $!\n"; + while (<$fh>) { + chomp; + my @entry = split / /, $_; + my $addr_text = $entry[0]; + if ($addr_text !~ /^0/) { + # TODO: Why is hex() so impossibly slow? + my $addr = hex($addr_text); + my $symbol = $entry[2]; + # Only keep kernel text addresses. + my $long = pack("J", $addr); + my $entry = [$long, $symbol]; + push @kallsyms, $entry; + } + } + close $fh; +} + +parse_dmesg(); +walk(@DIRS); + +exit 0; + +sub dprint +{ + printf(STDERR @_) if $debug; +} + +sub is_supported_architecture +{ + return (is_x86_64() or is_ppc64() or is_ix86_32()); +} + +sub is_32bit +{ + # Allow --32-bit or --page-offset-32-bit to override + if ($opt_32bit or $page_offset_32bit) { + return 1; + } + + return is_ix86_32(); +} + +sub is_ix86_32 +{ + state $arch = `uname -m`; + + chomp $arch; + if ($arch =~ m/i[3456]86/) { + return 1; + } + return 0; +} + +sub is_arch +{ + my ($desc) = @_; + my $arch = `uname -m`; + + chomp $arch; + if ($arch eq $desc) { + return 1; + } + return 0; +} + +sub is_x86_64 +{ + state $is = is_arch('x86_64'); + return $is; +} + +sub is_ppc64 +{ + state $is = is_arch('ppc64'); + return $is; +} + +# Gets config option value from kernel config file. +# Returns "" on error or if config option not found. +sub get_kernel_config_option +{ + my ($option) = @_; + my $value = ""; + my $tmp_fh; + my $tmp_file = ""; + my @config_files; + + # Allow --kernel-config-file to override. + if ($kernel_config_file ne "") { + @config_files = ($kernel_config_file); + } elsif (-R "/proc/config.gz") { + ($tmp_fh, $tmp_file) = tempfile("config.gz-XXXXXX", + UNLINK => 1); + + if (system("gunzip < /proc/config.gz > $tmp_file")) { + dprint("system(gunzip < /proc/config.gz) failed\n"); + return ""; + } else { + @config_files = ($tmp_file); + } + } else { + my $file = '/boot/config-' . `uname -r`; + chomp $file; + @config_files = ($file, '/boot/config'); + } + + foreach my $file (@config_files) { + dprint("parsing config file: $file\n"); + $value = option_from_file($option, $file); + if ($value ne "") { + last; + } + } + + return $value; +} + +# Parses $file and returns kernel configuration option value. +sub option_from_file +{ + my ($option, $file) = @_; + my $str = ""; + my $val = ""; + + open(my $fh, "<", $file) or return ""; + while (my $line = <$fh> ) { + if ($line =~ /^$option/) { + ($str, $val) = split /=/, $line; + chomp $val; + last; + } + } + + close $fh; + return $val; +} + +sub is_false_positive +{ + my ($match) = @_; + + if (is_32bit()) { + return is_false_positive_32bit($match); + } + + # Ignore 64 bit false positives: + # 0xfffffffffffffff[0-f] + # 0x0000000000000000 + if ($match =~ '\b(0x)?(f|F){15}[0-9a-f]\b' or + $match =~ '\b(0x)?0{16}\b') { + return 1; + } + + if (is_x86_64() and is_in_vsyscall_memory_region($match)) { + return 1; + } + + return 0; +} + +sub is_false_positive_32bit +{ + my ($match) = @_; + state $page_offset = get_page_offset(); + + if ($match =~ '\b(0x)?(f|F){7}[0-9a-f]\b') { + return 1; + } + + if (hex($match) < $page_offset) { + return 1; + } + + return 0; +} + +# returns integer value +sub get_page_offset +{ + my $page_offset; + my $default_offset = 0xc0000000; + + # Allow --page-offset-32bit to override. + if ($page_offset_32bit != 0) { + return $page_offset_32bit; + } + + $page_offset = get_kernel_config_option('CONFIG_PAGE_OFFSET'); + if (!$page_offset) { + return $default_offset; + } + return $page_offset; +} + +sub is_in_vsyscall_memory_region +{ + my ($match) = @_; + + my $hex = hex($match); + my $region_min = hex("0xffffffffff600000"); + my $region_max = hex("0xffffffffff601000"); + + return ($hex >= $region_min and $hex <= $region_max); +} + +# True if argument potentially contains a kernel address. +sub may_leak_address +{ + my ($path, $line) = @_; + my $address_re; + + # Ignore Signal masks. + if ($line =~ '^SigBlk:' or + $line =~ '^SigIgn:' or + $line =~ '^SigCgt:') { + return 0; + } + + # Ignore input device reporting. + # /proc/bus/input/devices: B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe + # /sys/devices/platform/i8042/serio0/input/input1/uevent: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe + # /sys/devices/platform/i8042/serio0/input/input1/capabilities/key: 402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe + if ($line =~ '\bKEY=[[:xdigit:]]{9,14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b' or + ($path =~ '\bkey$' and + $line =~ '\b[[:xdigit:]]{9,14} [[:xdigit:]]{16} [[:xdigit:]]{16}\b')) { + return 0; + } + + $address_re = get_address_re(); + while ($line =~ /($address_re)/g) { + if (!is_false_positive($1)) { + return 1; + } + } + + return 0; +} + +sub get_address_re +{ + if (is_ppc64()) { + return '\b(0x)?[89abcdef]00[[:xdigit:]]{13}\b'; + } elsif (is_32bit()) { + return '\b(0x)?[[:xdigit:]]{8}\b'; + } + + return get_x86_64_re(); +} + +sub get_x86_64_re +{ + # We handle page table levels but only if explicitly configured using + # CONFIG_PGTABLE_LEVELS. If config file parsing fails or config option + # is not found we default to using address regular expression suitable + # for 4 page table levels. + state $ptl = get_kernel_config_option('CONFIG_PGTABLE_LEVELS'); + + if ($ptl == 5) { + return '\b(0x)?ff[[:xdigit:]]{14}\b'; + } + return '\b(0x)?ffff[[:xdigit:]]{12}\b'; +} + +sub parse_dmesg +{ + open my $cmd, '-|', 'dmesg'; + while (<$cmd>) { + if (may_leak_address("dmesg", $_)) { + print 'dmesg: ' . $_; + } + } + close $cmd; +} + +# True if we should skip this path. +sub skip +{ + my ($path) = @_; + + foreach (@skip_abs) { + return 1 if (/^$path$/); + } + + my($filename, $dirs, $suffix) = fileparse($path); + foreach (@skip_any) { + return 1 if (/^$filename$/); + } + + return 0; +} + +sub timed_parse_file +{ + my ($file) = @_; + + eval { + local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required. + alarm $TIMEOUT; + parse_file($file); + alarm 0; + }; + + if ($@) { + die unless $@ eq "alarm\n"; # Propagate unexpected errors. + printf STDERR "timed out parsing: %s\n", $file; + } +} + +sub parse_binary +{ + my ($file) = @_; + + open my $fh, "<:raw", $file or return; + local $/ = undef; + my $bytes = <$fh>; + close $fh; + + foreach my $entry (@kallsyms) { + my $addr = $entry->[0]; + my $symbol = $entry->[1]; + my $offset = index($bytes, $addr); + if ($offset != -1) { + printf("$file: $symbol @ $offset\n"); + } + } +} + +sub parse_file +{ + my ($file) = @_; + + if (! -R $file) { + return; + } + + if (! -T $file) { + if ($file =~ m|^/sys/kernel/btf/| or + $file =~ m|^/sys/devices/pci| or + $file =~ m|^/sys/firmware/efi/efivars/| or + $file =~ m|^/proc/bus/pci/|) { + return; + } + if (scalar @kallsyms > 0) { + parse_binary($file); + } + return; + } + + open my $fh, "<", $file or return; + while ( <$fh> ) { + chomp; + if (may_leak_address($file, $_)) { + printf("$file: $_\n"); + } + } + close $fh; +} + +# Checks if the actual path name is leaking a kernel address. +sub check_path_for_leaks +{ + my ($path) = @_; + + if (may_leak_address($path, $path)) { + printf("Path name may contain address: $path\n"); + } +} + +# Recursively walk directory tree. +sub walk +{ + my @dirs = @_; + + while (my $pwd = shift @dirs) { + next if (!opendir(DIR, $pwd)); + my @files = readdir(DIR); + closedir(DIR); + + foreach my $file (@files) { + next if ($file eq '.' or $file eq '..'); + + my $path = "$pwd/$file"; + next if (-l $path); + + # skip /proc/PID except /proc/1 + next if (($path =~ /^\/proc\/[0-9]+$/) && + ($path !~ /^\/proc\/1$/)); + + next if (skip($path)); + + check_path_for_leaks($path); + + if (-d $path) { + push @dirs, $path; + next; + } + + dprint("parsing: $path\n"); + timed_parse_file($path); + } + } +} + +sub format_output +{ + my ($file) = @_; + + # Default is to show raw results. + if ($raw or (!$squash_by_path and !$squash_by_filename)) { + dump_raw_output($file); + return; + } + + my ($total, $dmesg, $paths, $files) = parse_raw_file($file); + + printf "\nTotal number of results from scan (incl dmesg): %d\n", $total; + + if (!$suppress_dmesg) { + print_dmesg($dmesg); + } + + if ($squash_by_filename) { + squash_by($files, 'filename'); + } + + if ($squash_by_path) { + squash_by($paths, 'path'); + } +} + +sub dump_raw_output +{ + my ($file) = @_; + + open (my $fh, '<', $file) or die "$0: $file: $!\n"; + while (<$fh>) { + if ($suppress_dmesg) { + if ("dmesg:" eq substr($_, 0, 6)) { + next; + } + } + print $_; + } + close $fh; +} + +sub parse_raw_file +{ + my ($file) = @_; + + my $total = 0; # Total number of lines parsed. + my @dmesg; # dmesg output. + my %files; # Unique filenames containing leaks. + my %paths; # Unique paths containing leaks. + + open (my $fh, '<', $file) or die "$0: $file: $!\n"; + while (my $line = <$fh>) { + $total++; + + if ("dmesg:" eq substr($line, 0, 6)) { + push @dmesg, $line; + next; + } + + cache_path(\%paths, $line); + cache_filename(\%files, $line); + } + + return $total, \@dmesg, \%paths, \%files; +} + +sub print_dmesg +{ + my ($dmesg) = @_; + + print "\ndmesg output:\n"; + + if (@$dmesg == 0) { + print "<no results>\n"; + return; + } + + foreach(@$dmesg) { + my $index = index($_, ': '); + $index += 2; # skid ': ' + print substr($_, $index); + } +} + +sub squash_by +{ + my ($ref, $desc) = @_; + + print "\nResults squashed by $desc (excl dmesg). "; + print "Displaying [<number of results> <$desc>], <example result>\n"; + + if (keys %$ref == 0) { + print "<no results>\n"; + return; + } + + foreach(keys %$ref) { + my $lines = $ref->{$_}; + my $length = @$lines; + printf "[%d %s] %s", $length, $_, @$lines[0]; + } +} + +sub cache_path +{ + my ($paths, $line) = @_; + + my $index = index($line, ': '); + my $path = substr($line, 0, $index); + + $index += 2; # skip ': ' + add_to_cache($paths, $path, substr($line, $index)); +} + +sub cache_filename +{ + my ($files, $line) = @_; + + my $index = index($line, ': '); + my $path = substr($line, 0, $index); + my $filename = basename($path); + + $index += 2; # skip ': ' + add_to_cache($files, $filename, substr($line, $index)); +} + +sub add_to_cache +{ + my ($cache, $key, $value) = @_; + + if (!$cache->{$key}) { + $cache->{$key} = (); + } + push @{$cache->{$key}}, $value; +} diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh new file mode 100755 index 000000000..ab0b8125c --- /dev/null +++ b/scripts/link-vmlinux.sh @@ -0,0 +1,315 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# link vmlinux +# +# vmlinux is linked from the objects in vmlinux.a and $(KBUILD_VMLINUX_LIBS). +# vmlinux.a contains objects that are linked unconditionally. +# $(KBUILD_VMLINUX_LIBS) are archives which are linked conditionally +# (not within --whole-archive), and do not require symbol indexes added. +# +# vmlinux +# ^ +# | +# +--< vmlinux.a +# | +# +--< $(KBUILD_VMLINUX_LIBS) +# | +--< lib/lib.a + more +# | +# +-< ${kallsymso} (see description in KALLSYMS section) +# +# vmlinux version (uname -v) cannot be updated during normal +# descending-into-subdirs phase since we do not yet know if we need to +# update vmlinux. +# Therefore this step is delayed until just before final link of vmlinux. +# +# System.map is generated to document addresses of all kernel symbols + +# Error out on error +set -e + +LD="$1" +KBUILD_LDFLAGS="$2" +LDFLAGS_vmlinux="$3" +VMLINUX="$4" + +is_enabled() { + grep -q "^$1=y" include/config/auto.conf +} + +# Nice output in kbuild format +# Will be suppressed by "make -s" +info() +{ + printf " %-7s %s\n" "${1}" "${2}" +} + +# Link of vmlinux +# ${1} - output file +vmlinux_link() +{ + local output=${1} + local objs + local libs + local ld + local ldflags + local ldlibs + + info LD ${output} + + # skip output file argument + shift + + if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT || + is_enabled CONFIG_KLP_BUILD; then + # Use vmlinux.o instead of performing the slow LTO link again. + objs=vmlinux.o + libs= + else + objs=vmlinux.a + libs="${KBUILD_VMLINUX_LIBS}" + fi + + if is_enabled CONFIG_GENERIC_BUILTIN_DTB; then + objs="${objs} .builtin-dtbs.o" + fi + + objs="${objs} .vmlinux.export.o" + objs="${objs} init/version-timestamp.o" + + if [ "${SRCARCH}" = "um" ]; then + wl=-Wl, + ld="${CC}" + ldflags="${CFLAGS_vmlinux}" + ldlibs="-lutil -lrt -lpthread" + else + wl= + ld="${LD}" + ldflags="${KBUILD_LDFLAGS} ${LDFLAGS_vmlinux}" + ldlibs= + fi + + ldflags="${ldflags} ${wl}--script=${objtree}/${KBUILD_LDS}" + + # The kallsyms linking does not need debug symbols included. + if [ -n "${strip_debug}" ] ; then + ldflags="${ldflags} ${wl}--strip-debug" + fi + + 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 \ + ${kallsymso} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs} +} + +# Check if kallsymso_prev and kallsymso differ +# If symbol sizes within ${kallsymso} change, any symbols within vmlinux are +# likely to shift, invalidating ${kallsymso}. +# Since file size can remain unchanged even if symbol sizes change, compare the +# actual symbols instead of relying on file size only. +kallsymso_changed() +{ + ${NM} -n "${kallsymso_prev}" > "${kallsymso_prev}.sym" + ${NM} -n "${kallsymso}" > "${kallsymso}.sym" + ! cmp -s "${kallsymso_prev}.sym" "${kallsymso}.sym" +} + +# Create ${2}.o file with all symbols from the ${1} object file +kallsyms() +{ + local kallsymopt; + + if is_enabled CONFIG_KALLSYMS_ALL; then + kallsymopt="${kallsymopt} --all-symbols" + fi + + if is_enabled CONFIG_64BIT || is_enabled CONFIG_RELOCATABLE; then + kallsymopt="${kallsymopt} --pc-relative" + fi + + info KSYMS "${2}.S" + scripts/kallsyms ${kallsymopt} "${1}" > "${2}.S" + + info AS "${2}.o" + ${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \ + ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} -c -o "${2}.o" "${2}.S" + + kallsymso_prev="${kallsymso:-}" + kallsymso=${2}.o +} + +# Perform kallsyms for the given temporary vmlinux. +sysmap_and_kallsyms() +{ + mksysmap "${1}" "${1}.syms" + kallsyms "${1}.syms" "${1}.kallsyms" + + kallsyms_sysmap=${1}.syms +} + +# Create map file with all symbols from ${1} +# See mksymap for additional details +mksysmap() +{ + info NM ${2} + ${NM} -n "${1}" | sed -f "${srctree}/scripts/mksysmap" > "${2}" +} + +sorttable() +{ + ${NM} -S ${1} > .tmp_vmlinux.nm-sort + ${objtree}/scripts/sorttable -s .tmp_vmlinux.nm-sort ${1} +} + +cleanup() +{ + rm -f .btf.* + rm -f .tmp_vmlinux.nm-sort + rm -f System.map + rm -f vmlinux + rm -f vmlinux.map +} + +# Use "make V=1" to debug this script +case "${KBUILD_VERBOSE}" in +*1*) + set -x + ;; +esac + +if [ "$1" = "clean" ]; then + cleanup + exit 0 +fi + +${MAKE} -f "${srctree}/scripts/Makefile.build" obj=init init/version-timestamp.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= +btfids_vmlinux= +kallsymso= +strip_debug= +generate_map= + +# Use "make UT=1" to trigger warnings on unused tracepoints +case "${WARN_ON_UNUSED_TRACEPOINTS}" in +*1*) + ${objtree}/scripts/tracepoint-update vmlinux.o + ;; +esac + +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 + info BTF .tmp_vmlinux1 + if ! ${CONFIG_SHELL} ${srctree}/scripts/gen-btf.sh .tmp_vmlinux1; then + echo >&2 "Failed to generate BTF for vmlinux" + echo >&2 "Try to disable CONFIG_DEBUG_INFO_BTF" + exit 1 + fi + btf_vmlinux_bin_o=.tmp_vmlinux1.btf.o + btfids_vmlinux=.tmp_vmlinux1.BTF_ids +fi + +if is_enabled CONFIG_KALLSYMS; then + + # kallsyms support + # Generate section listing all symbols and add it into vmlinux + # 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_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_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 + # pass if that is the case. In theory it's possible this results + # in even more stubs, but unlikely. + # KALLSYMS_EXTRA_PASS=1 may also used to debug or work around + # other bugs. + # 4) The correct ${kallsymso} is linked into the final vmlinux. + # + # a) Verify that the System.map from vmlinux matches the map from + # ${kallsymso}. + + # 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}) + + vmlinux_link .tmp_vmlinux2 + sysmap_and_kallsyms .tmp_vmlinux2 + size2=$(${CONFIG_SHELL} "${srctree}/scripts/file-size.sh" ${kallsymso}) + + # Due to alignment, file size of the kallsymso object file might remain + # unchanged even if individual symbols within change size. Changed + # symbol sizes can still shift other symbols, though. Therefore, don't + # rely on file size alone. + if [ $size1 -ne $size2 ] || kallsymso_changed || \ + [ -n "${KALLSYMS_EXTRA_PASS}" ]; then + vmlinux_link .tmp_vmlinux3 + sysmap_and_kallsyms .tmp_vmlinux3 + fi +fi + +strip_debug= + +if is_enabled CONFIG_VMLINUX_MAP; then + generate_map=1 +fi + +vmlinux_link "${VMLINUX}" + +if is_enabled CONFIG_DEBUG_INFO_BTF; then + info BTFIDS ${VMLINUX} + ${RESOLVE_BTFIDS} --patch_btfids ${btfids_vmlinux} ${VMLINUX} +fi + +mksysmap "${VMLINUX}" System.map + +if is_enabled CONFIG_BUILDTIME_TABLE_SORT; then + info SORTTAB "${VMLINUX}" + if ! sorttable "${VMLINUX}"; then + echo >&2 Failed to sort kernel tables + exit 1 + fi +fi + +# step a (see comment above) +if is_enabled CONFIG_KALLSYMS; 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 + fi +fi + +# For fixdep +echo "${VMLINUX}: $0" > ".${VMLINUX}.d" diff --git a/scripts/livepatch/Makefile b/scripts/livepatch/Makefile new file mode 100644 index 000000000..17b590213 --- /dev/null +++ b/scripts/livepatch/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +# Standalone Makefile for developer tooling (not part of kbuild). + +SHELLCHECK := $(shell which shellcheck 2> /dev/null) + +SRCS := \ + klp-build + +.DEFAULT_GOAL := help +.PHONY: help +help: + @echo " check - Run shellcheck on $(SRCS)" + @echo " help - Show this help message" + +.PHONY: check +check: +ifndef SHELLCHECK + $(error shellcheck is not installed. Please install it to run checks) +endif + @$(SHELLCHECK) $(SHELLCHECK_OPTIONS) $(SRCS) diff --git a/scripts/livepatch/fix-patch-lines b/scripts/livepatch/fix-patch-lines new file mode 100755 index 000000000..fa7d4f659 --- /dev/null +++ b/scripts/livepatch/fix-patch-lines @@ -0,0 +1,79 @@ +#!/usr/bin/awk -f +# SPDX-License-Identifier: GPL-2.0 +# +# Use #line directives to preserve original __LINE__ numbers across patches to +# avoid unwanted compilation changes. + +BEGIN { + in_hunk = 0 + skip = 0 +} + +/^--- / { + skip = $2 !~ /\.(c|h)$/ + print + next +} + +/^@@/ { + if (skip) { + print + next + } + + in_hunk = 1 + + # @@ -1,3 +1,4 @@: + # 1: line number in old file + # 3: how many lines the hunk covers in old file + # 1: line number in new file + # 4: how many lines the hunk covers in new file + + match($0, /^@@ -([0-9]+)(,([0-9]+))? \+([0-9]+)(,([0-9]+))? @@/, m) + + # Set 'cur' to the old file's line number at the start of the hunk. It + # gets incremented for every context line and every line removal, so + # that it always represents the old file's current line number. + cur = m[1] + + # last = last line number of current hunk + last = cur + (m[3] ? m[3] : 1) - 1 + + need_line_directive = 0 + + print + next +} + +{ + if (skip || !in_hunk || $0 ~ /^\\ No newline at end of file/) { + print + next + } + + # change line + if ($0 ~ /^[+-]/) { + # inject #line after this group of changes + need_line_directive = 1 + + if ($0 ~ /^-/) + cur++ + + print + next + } + + # If this is the first context line after a group of changes, inject + # the #line directive to force the compiler to correct the line + # numbering to match the original file. + if (need_line_directive) { + print "+#line " cur + need_line_directive = 0 + } + + if (cur == last) + in_hunk = 0 + + cur++ + print +} diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c new file mode 100644 index 000000000..16aff8f73 --- /dev/null +++ b/scripts/livepatch/init.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Init code for a livepatch kernel module + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/livepatch.h> + +static struct klp_patch *patch; + +static int __init livepatch_mod_init(void) +{ + struct klp_object_ext *obj_exts; + size_t obj_exts_sec_size; + struct klp_object *objs; + unsigned int nr_objs; + int ret; + + obj_exts = klp_find_section_by_name(THIS_MODULE, ".init.klp_objects", + &obj_exts_sec_size); + nr_objs = obj_exts_sec_size / sizeof(*obj_exts); + if (!nr_objs) { + pr_err("nothing to patch!\n"); + ret = -EINVAL; + goto err; + } + + patch = kzalloc_obj(*patch); + if (!patch) { + ret = -ENOMEM; + goto err; + } + + objs = kzalloc(sizeof(struct klp_object) * (nr_objs + 1), GFP_KERNEL); + if (!objs) { + ret = -ENOMEM; + goto err_free_patch; + } + + for (int i = 0; i < nr_objs; i++) { + struct klp_object_ext *obj_ext = obj_exts + i; + struct klp_func_ext *funcs_ext = obj_ext->funcs; + unsigned int nr_funcs = obj_ext->nr_funcs; + struct klp_func *funcs = objs[i].funcs; + struct klp_object *obj = objs + i; + + funcs = kzalloc(sizeof(struct klp_func) * (nr_funcs + 1), GFP_KERNEL); + if (!funcs) { + ret = -ENOMEM; + for (int j = 0; j < i; j++) + kfree(objs[j].funcs); + goto err_free_objs; + } + + for (int j = 0; j < nr_funcs; j++) { + funcs[j].old_name = funcs_ext[j].old_name; + funcs[j].new_func = funcs_ext[j].new_func; + funcs[j].old_sympos = funcs_ext[j].sympos; + } + + obj->name = obj_ext->name; + obj->funcs = funcs; + + memcpy(&obj->callbacks, &obj_ext->callbacks, sizeof(struct klp_callbacks)); + } + + patch->mod = THIS_MODULE; + patch->objs = objs; + + /* TODO patch->states */ + +#ifdef KLP_NO_REPLACE + patch->replace = false; +#else + patch->replace = true; +#endif + + return klp_enable_patch(patch); + +err_free_objs: + kfree(objs); +err_free_patch: + kfree(patch); +err: + return ret; +} + +static void __exit livepatch_mod_exit(void) +{ + struct klp_object *obj; + + klp_for_each_object_static(patch, obj) + kfree(obj->funcs); + + kfree(patch->objs); + kfree(patch); +} + +module_init(livepatch_mod_init); +module_exit(livepatch_mod_exit); +MODULE_LICENSE("GPL"); +MODULE_INFO(livepatch, "Y"); +MODULE_DESCRIPTION("Livepatch module"); diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build new file mode 100755 index 000000000..b52a8489d --- /dev/null +++ b/scripts/livepatch/klp-build @@ -0,0 +1,943 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Build a livepatch module + +# shellcheck disable=SC1090,SC2155,SC2164 + +if (( BASH_VERSINFO[0] < 4 || \ + (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then + echo "error: this script requires bash 4.4+" >&2 + exit 1 +fi + +set -o errtrace +set -o pipefail +set -o nounset + +# Allow doing 'cmd | mapfile -t array' instead of 'mapfile -t array < <(cmd)'. +# This helps keep execution in pipes so pipefail+ERR trap can catch errors. +shopt -s lastpipe + +unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE + +REPLACE=1 +SHORT_CIRCUIT=0 +JOBS="$(getconf _NPROCESSORS_ONLN)" +shopt -o xtrace | grep -q 'on' && XTRACE=1 + +# Avoid removing the previous $TMP_DIR until args have been fully processed. +KEEP_TMP=1 + +SCRIPT="$(basename "$0")" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines" + +OBJTOOL="$PWD/tools/objtool/objtool" +CONFIG="$PWD/.config" +TMP_DIR="$PWD/klp-tmp" + +ORIG_DIR="$TMP_DIR/1-orig" +PATCHED_DIR="$TMP_DIR/2-patched" +ORIG_CSUM_DIR="$TMP_DIR/3-checksum-orig" +PATCHED_CSUM_DIR="$TMP_DIR/3-checksum-patched" +DIFF_DIR="$TMP_DIR/4-diff" +KMOD_DIR="$TMP_DIR/5-kmod" + +STASH_DIR="$TMP_DIR/stash" +TIMESTAMP="$TMP_DIR/timestamp" +PATCH_TMP_DIR="$TMP_DIR/tmp" + +KLP_DIFF_LOG="$DIFF_DIR/diff.log" + +# Terminal output colors +read -r COLOR_RESET COLOR_BOLD COLOR_ERROR COLOR_WARN <<< "" +if [[ -t 1 && -t 2 ]]; then + COLOR_RESET="\033[0m" + COLOR_BOLD="\033[1m" + COLOR_ERROR="\033[0;31m" + COLOR_WARN="\033[0;33m" +fi + +grep0() { + # shellcheck disable=SC2317 + command grep "$@" || true +} + +# Because pipefail is enabled, the grep0 helper should be used instead of +# grep, otherwise a failed match can propagate to an error. +grep() { + echo "error: $SCRIPT: use grep0 or 'command grep' instead of bare grep" >&2 + exit 1 +} + +status() { + echo -e "${COLOR_BOLD}$*${COLOR_RESET}" +} + +warn() { + echo -e "${COLOR_WARN}warning${COLOR_RESET}: $SCRIPT: $*" >&2 +} + +die() { + echo -e "${COLOR_ERROR}error${COLOR_RESET}: $SCRIPT: $*" >&2 + exit 1 +} + +declare -a STASHED_FILES + +stash_file() { + local file="$1" + local rel_file="${file#"$PWD"/}" + + [[ ! -e "$file" ]] && die "no file to stash: $file" + + mkdir -p "$STASH_DIR/$(dirname "$rel_file")" + cp -f "$file" "$STASH_DIR/$rel_file" + + STASHED_FILES+=("$rel_file") +} + +restore_files() { + local file + + for file in "${STASHED_FILES[@]}"; do + mv -f "$STASH_DIR/$file" "$PWD/$file" || warn "can't restore file: $file" + done + + STASHED_FILES=() +} + +cleanup() { + set +o nounset + revert_patches + restore_files + [[ "$KEEP_TMP" -eq 0 ]] && rm -rf "$TMP_DIR" + return 0 +} + +trap_err() { + die "line ${BASH_LINENO[0]}: '$BASH_COMMAND'" +} + +trap cleanup EXIT INT TERM HUP +trap trap_err ERR + +__usage() { + cat <<EOF +Usage: $SCRIPT [OPTIONS] PATCH_FILE(s) +Generate a livepatch module. + +Options: + -f, --show-first-changed Show address of first changed instruction + -j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS] + -o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko] + --no-replace Disable livepatch atomic replace + -v, --verbose Pass V=1 to kernel/module builds + +Advanced Options: + -d, --debug Show symbol/reloc cloning decisions + -S, --short-circuit=STEP Start at build step (requires prior --keep-tmp) + 1|orig Build original kernel (default) + 2|patched Build patched kernel + 3|checksum Generate checksums + 4|diff Diff objects + 5|kmod Build patch module + -T, --keep-tmp Preserve tmp dir on exit + +EOF +} + +usage() { + __usage >&2 +} + +process_args() { + local keep_tmp=0 + local short + local long + local args + local patch + + short="hfj:o:vdS:T" + long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp" + + args=$(getopt --options "$short" --longoptions "$long" -- "$@") || { + echo; usage; exit + } + eval set -- "$args" + + while true; do + case "$1" in + -h | --help) + usage + exit 0 + ;; + -f | --show-first-changed) + DIFF_CHECKSUM=1 + shift + ;; + -j | --jobs) + JOBS="$2" + shift 2 + ;; + -o | --output) + [[ "$2" != *.ko ]] && die "output filename should end with .ko" + OUTFILE="$2" + NAME="$(basename "$OUTFILE")" + NAME="${NAME%.ko}" + NAME="$(module_name_string "$NAME")" + shift 2 + ;; + --no-replace) + REPLACE=0 + shift + ;; + -v | --verbose) + VERBOSE=1 + shift + ;; + -d | --debug) + DEBUG_CLONE=1 + keep_tmp=1 + shift + ;; + -S | --short-circuit) + [[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir" + keep_tmp=1 + case "$2" in + 1 | orig) SHORT_CIRCUIT=1; ;; + 2 | patched) SHORT_CIRCUIT=2; ;; + 3 | checksum) SHORT_CIRCUIT=3; ;; + 4 | diff) SHORT_CIRCUIT=4; ;; + 5 | kmod) SHORT_CIRCUIT=5; ;; + *) die "invalid short-circuit step '$2'" ;; + esac + shift 2 + ;; + -T | --keep-tmp) + keep_tmp=1 + shift + ;; + --) + shift + break + ;; + *) + usage + exit 1 + ;; + esac + done + + if [[ $# -eq 0 ]] && (( SHORT_CIRCUIT <= 2 )); then + usage + exit 1 + fi + + KEEP_TMP="$keep_tmp" + PATCHES=("$@") + + for patch in "${PATCHES[@]}"; do + [[ -f "$patch" ]] || die "$patch doesn't exist" + done +} + +# temporarily disable xtrace for especially verbose code +xtrace_save() { + [[ -v XTRACE ]] && set +x + return 0 +} + +xtrace_restore() { + [[ -v XTRACE ]] && set -x + return 0 +} + +validate_config() { + xtrace_save "reading .config" + source "$CONFIG" || die "no .config file in $(dirname "$CONFIG")" + xtrace_restore + + [[ -v CONFIG_LIVEPATCH ]] || \ + die "CONFIG_LIVEPATCH not enabled" + + [[ -v CONFIG_KLP_BUILD ]] || \ + die "CONFIG_KLP_BUILD not enabled" + + [[ -v CONFIG_GCC_PLUGIN_LATENT_ENTROPY ]] && \ + die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported" + + [[ -v CONFIG_GCC_PLUGIN_RANDSTRUCT ]] && \ + die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported" + + [[ -v CONFIG_LD_DEAD_CODE_DATA_ELIMINATION ]] && \ + die "kernel option 'CONFIG_LD_DEAD_CODE_DATA_ELIMINATION' not supported" + + [[ -v CONFIG_AS_IS_LLVM ]] && \ + [[ "$CONFIG_AS_VERSION" -lt 200000 ]] && \ + die "Clang assembler version < 20 not supported" + + [[ -x "$OBJTOOL" ]] && "$OBJTOOL" klp 2>&1 | command grep -q "not implemented" && \ + die "objtool not built with KLP support; install xxhash-devel/libxxhash-dev (version >= 0.8) and recompile" + + return 0 +} + +# Only allow alphanumerics and '_' and '-' in the module name. Everything else +# is replaced with '-'. Also truncate to 55 chars so the full name + NUL +# terminator fits in the kernel's 56-byte module name array. +module_name_string() { + echo "${1//[^a-zA-Z0-9_-]/-}" | cut -c 1-55 +} + +# If the module name wasn't specified on the cmdline with --output, give it a +# name based on the patch name. +set_module_name() { + [[ -v NAME ]] && return 0 + + if [[ "${#PATCHES[@]}" -eq 1 ]]; then + NAME="$(basename "${PATCHES[0]}")" + NAME="${NAME%.*}" + else + NAME="patch" + fi + + NAME="livepatch-$NAME" + NAME="$(module_name_string "$NAME")" + + OUTFILE="$NAME.ko" +} + +# Hardcode the value printed by the localversion script to prevent patch +# application from appending it with '+' due to a dirty working tree. +set_kernelversion() { + local file="$PWD/scripts/setlocalversion" + local kernelrelease + + stash_file "$file" + + if [[ -n "$(make -s listnewconfig 2>/dev/null)" ]]; then + die ".config mismatch, check your .config or run 'make olddefconfig'" + fi + make syncconfig &>/dev/null || die "make syncconfig failed" + + kernelrelease="$(make -s kernelrelease)" + [[ -z "$kernelrelease" ]] && die "failed to get kernel version" + + sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion +} + +get_patch_input_files() { + local patch="$1" + + grep0 -E '^--- ' "$patch" \ + | grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \ + | gawk '{print $2}' \ + | sed 's|^[^/]*/||' \ + | sort -u +} + +get_patch_output_files() { + local patch="$1" + + grep0 -E '^\+\+\+ ' "$patch" \ + | grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \ + | gawk '{print $2}' \ + | sed 's|^[^/]*/||' \ + | sort -u +} + +get_patch_files() { + local patch="$1" + + { get_patch_input_files "$patch"; get_patch_output_files "$patch"; } \ + | sort -u +} + +check_unsupported_patches() { + local patch + + for patch in "${PATCHES[@]}"; do + local files=() + + get_patch_files "$patch" | mapfile -t files + + for file in "${files[@]}"; do + case "$file" in + lib/*|*/vdso/*|*/realmode/rm/*|*.S) + die "${patch}: unsupported patch to $file" + ;; + esac + done + done +} + +apply_patch() { + local patch="$1" + shift + local extra_args=("$@") + local drift_regex="with fuzz|offset [0-9]+ line" + local output + local status + + [[ ! -f "$patch" ]] && die "$patch doesn't exist" + status=0 + output=$(patch -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? + if [[ "$status" -ne 0 ]]; then + echo "$output" >&2 + die "$patch did not apply" + elif [[ "$output" =~ $drift_regex ]]; then + [[ -v VERBOSE ]] && echo "$output" >&2 + warn "${patch} applied with fuzz" + fi + + APPLIED_PATCHES+=("$patch") + patch -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" +} + +revert_patch() { + local patch="$1" + local tmp=() + + patch -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true + + for p in "${APPLIED_PATCHES[@]}"; do + [[ "$p" == "$patch" ]] && continue + tmp+=("$p") + done + + APPLIED_PATCHES=("${tmp[@]}") +} + +apply_patches() { + local extra_args=("$@") + local patch + + for patch in "${PATCHES[@]}"; do + apply_patch "$patch" "${extra_args[@]}" + done +} + +revert_patches() { + local patches=("${APPLIED_PATCHES[@]}") + + for (( i=${#patches[@]}-1 ; i>=0 ; i-- )) ; do + revert_patch "${patches[$i]}" + done + + APPLIED_PATCHES=() +} + +validate_patches() { + check_unsupported_patches + apply_patches + revert_patches +} + +do_init() { + # We're not yet smart enough to handle anything other than in-tree + # builds in pwd. + [[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" + + if (( SHORT_CIRCUIT >= 2 )); then + [[ -f "$ORIG_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_DIR" + fi + if (( SHORT_CIRCUIT >= 3 )); then + [[ -f "$PATCHED_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_DIR" + fi + if (( SHORT_CIRCUIT >= 4 )); then + [[ -f "$ORIG_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_CSUM_DIR" + [[ -f "$PATCHED_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_CSUM_DIR" + fi + if (( SHORT_CIRCUIT >= 5 )); then + [[ -f "$DIFF_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $DIFF_DIR" + fi + + (( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR" + mkdir -p "$TMP_DIR" + + APPLIED_PATCHES=() + + [[ -x "$FIX_PATCH_LINES" ]] || die "can't find fix-patch-lines" + command -v recountdiff &>/dev/null || die "recountdiff not found (install patchutils)" + + validate_config + set_module_name + set_kernelversion +} + +# Refresh the patch hunk headers, specifically the line numbers and counts. +refresh_patch() { + local patch="$1" + local tmpdir="$PATCH_TMP_DIR" + local input_files=() + local output_files=() + + rm -rf "$tmpdir" + mkdir -p "$tmpdir/a" + mkdir -p "$tmpdir/b" + + # Get all source files affected by the patch + get_patch_input_files "$patch" | mapfile -t input_files + get_patch_output_files "$patch" | mapfile -t output_files + + # Copy orig source files to 'a' + echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" + + # Copy patched source files to 'b' + apply_patch "$patch" "--silent" + echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" + revert_patch "$patch" + + # Diff 'a' and 'b' to make a clean patch + ( cd "$tmpdir" && diff -Nupr a b > "$patch" ) || true +} + +# Copy the patches to a temporary directory, fix their lines so as not to +# affect the __LINE__ macro for otherwise unchanged functions further down the +# file, and update $PATCHES to point to the fixed patches. +fix_patches() { + local idx + local i + + rm -f "$TMP_DIR"/*.patch + + idx=0001 + for i in "${!PATCHES[@]}"; do + local old_patch="${PATCHES[$i]}" + local tmp_patch="$TMP_DIR/tmp.patch" + local patch="${PATCHES[$i]}" + local new_patch + + new_patch="$TMP_DIR/$idx-fixed-$(basename "$patch")" + + cp -f "$old_patch" "$tmp_patch" + refresh_patch "$tmp_patch" + "$FIX_PATCH_LINES" "$tmp_patch" | recountdiff > "$new_patch" + + PATCHES[i]="$new_patch" + + rm -f "$tmp_patch" + idx=$(printf "%04d" $(( 10#$idx + 1 ))) + done +} + +clean_kernel() { + local cmd=() + + cmd=("make") + cmd+=("--silent") + cmd+=("-j$JOBS") + cmd+=("clean") + + "${cmd[@]}" +} + +build_kernel() { + local build="$1" + local log="$TMP_DIR/build.log" + local cmd=() + + cmd=("make") + + # When a patch to a kernel module references a newly created unexported + # symbol which lives in vmlinux or another kernel module, the patched + # kernel build fails with the following error: + # + # ERROR: modpost: "klp_string" [fs/xfs/xfs.ko] undefined! + # + # The undefined symbols are working as designed in that case. They get + # resolved later when the livepatch module build link pulls all the + # disparate objects together into the same kernel module. + # + # It would be good to have a way to tell modpost to skip checking for + # undefined symbols altogether. For now, just convert the error to a + # warning with KBUILD_MODPOST_WARN, and grep out the warning to avoid + # confusing the user. + # + cmd+=("KBUILD_MODPOST_WARN=1") + + cmd+=("KLP_SYMIDS=1") + + if [[ -v VERBOSE ]]; then + cmd+=("V=1") + else + cmd+=("-s") + fi + cmd+=("-j$JOBS") + cmd+=("KCFLAGS=-ffunction-sections -fdata-sections") + cmd+=("vmlinux") + cmd+=("modules") + + "${cmd[@]}" \ + 1> >(tee -a "$log") \ + 2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2) \ + || die "$build kernel build failed" +} + +find_objects() { + local opts=("$@") + + # Find root-level vmlinux.o and non-root-level .ko files, + # excluding klp-tmp/ and hidden directories. + find "$PWD" -mindepth 1 \ + \( -path "$TMP_DIR" -o -name ".*" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ + -type f "${opts[@]}" \ + \( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \ + -printf '%P\n' +} + +# Copy all .o archives to $ORIG_DIR +copy_orig_objects() { + local files=() + + rm -rf "$ORIG_DIR" + mkdir -p "$ORIG_DIR" + + find_objects | mapfile -t files + + xtrace_save "copying original objects" + for _file in "${files[@]}"; do + local rel_file="${_file/.ko/.o}" + local file="$PWD/$rel_file" + local orig_file="$ORIG_DIR/$rel_file" + local orig_dir="$(dirname "$orig_file")" + + [[ ! -f "$file" ]] && die "missing $(basename "$file") for $_file" + + mkdir -p "$orig_dir" + cp -f "$file" "$orig_dir" + done + xtrace_restore + + cp -f "$PWD/vmlinux" "$ORIG_DIR" || die "missing vmlinux" + + mv -f "$TMP_DIR/build.log" "$ORIG_DIR" + touch "$TIMESTAMP" + touch "$ORIG_DIR/.complete" +} + +# Copy all changed objects to $PATCHED_DIR +copy_patched_objects() { + local files=() + local opts=() + local found=0 + + rm -rf "$PATCHED_DIR" + mkdir -p "$PATCHED_DIR" + + # Note this doesn't work with some configs, thus the 'cmp' below. + opts=("-newer") + opts+=("$TIMESTAMP") + + find_objects "${opts[@]}" | mapfile -t files + + xtrace_save "copying changed objects" + for _file in "${files[@]}"; do + local rel_file="${_file/.ko/.o}" + local file="$PWD/$rel_file" + local orig_file="$ORIG_DIR/$rel_file" + local patched_file="$PATCHED_DIR/$rel_file" + local patched_dir="$(dirname "$patched_file")" + + [[ ! -f "$file" ]] && die "missing $(basename "$file") for $_file" + + cmp -s "$orig_file" "$file" && continue + + mkdir -p "$patched_dir" + cp -f "$file" "$patched_dir" + found=1 + done + xtrace_restore + + (( found == 0 )) && die "no changes detected" + + mv -f "$TMP_DIR/build.log" "$PATCHED_DIR" + touch "$PATCHED_DIR/.complete" +} + +# Copy .o files to a separate directory and run "objtool klp checksum" on each +# copy. The checksums are written to a .discard.sym_checksum section. +# +# If match_dir is given, only process files which also exist there. +generate_checksums() { + local src_dir="$1" + local dest_dir="$2" + local match_dir="${3:-}" + local files=() + local file + + rm -rf "$dest_dir" + mkdir -p "$dest_dir" + + find "$src_dir" -type f -name "*.o" | mapfile -t files + for file in "${files[@]}"; do + local rel="${file#"$src_dir"/}" + local dest="$dest_dir/$rel" + + [[ -n "$match_dir" && ! -f "$match_dir/$rel" ]] && continue + + mkdir -p "$(dirname "$dest")" + cp -f "$file" "$dest" + "$OBJTOOL" klp checksum "$dest" + done + + [[ -f "$src_dir/vmlinux" ]] && cp -f "$src_dir/vmlinux" "$dest_dir" + + touch "$dest_dir/.complete" +} + +# Diff changed objects, writing output object to $DIFF_DIR +diff_objects() { + local log="$KLP_DIFF_LOG" + local files=() + local opts=() + + rm -rf "$DIFF_DIR" + mkdir -p "$DIFF_DIR" + + find "$PATCHED_CSUM_DIR" -type f -name "*.o" | mapfile -t files + [[ ${#files[@]} -eq 0 ]] && die "no changes detected" + + [[ -v DEBUG_CLONE ]] && opts=("--debug") + + # Diff all changed objects + for file in "${files[@]}"; do + local rel_file="${file#"$PATCHED_CSUM_DIR"/}" + local orig_file="$rel_file" + local patched_file="$PATCHED_CSUM_DIR/$rel_file" + local out_file="$DIFF_DIR/$rel_file" + local filter=() + local cmd=() + + mkdir -p "$(dirname "$out_file")" + + cmd=("$OBJTOOL") + cmd+=("klp") + cmd+=("diff") + (( ${#opts[@]} > 0 )) && cmd+=("${opts[@]}") + cmd+=("$orig_file") + cmd+=("$patched_file") + cmd+=("$out_file") + + if [[ -v DIFF_CHECKSUM ]]; then + filter=("grep0") + filter+=("-Ev") + filter+=("DEBUG: .*checksum: ") + else + filter=("cat") + fi + + ( + cd "$ORIG_CSUM_DIR" + [[ -v VERBOSE ]] && echo "cd $ORIG_CSUM_DIR && ${cmd[*]}" + "${cmd[@]}" \ + 1> >(tee -a "$log") \ + 2> >(tee -a "$log" | "${filter[@]}" >&2) || \ + die "objtool klp diff failed" + ) + done + + touch "$DIFF_DIR/.complete" +} + +# For each changed object, run "objtool klp checksum" with --debug-checksum to +# get the per-instruction checksums, and then diff those to find the first +# changed instruction for each function. +diff_checksums() { + local orig_log="$ORIG_DIR/checksum.log" + local patched_log="$PATCHED_DIR/checksum.log" + local -A funcs + local cmd=() + local line + local file + local func + + gawk '/\.o: changed function: / { + sub(/:$/, "", $1) + print $1, $NF + }' "$KLP_DIFF_LOG" | mapfile -t lines + + for line in "${lines[@]}"; do + read -r file func <<< "$line" + if [[ ! -v funcs["$file"] ]]; then + funcs["$file"]="$func" + else + funcs["$file"]+=" $func" + fi + done + + cmd=("$OBJTOOL") + cmd+=("klp" "checksum") + cmd+=("--dry-run") + + for file in "${!funcs[@]}"; do + local opt="--debug-checksum=${funcs[$file]// /,}" + + ( + cd "$ORIG_DIR" + "${cmd[@]}" "$opt" "$file" &> "$orig_log" || \ + ( cat "$orig_log" >&2; die "objtool klp checksum failed" ) + + cd "$PATCHED_DIR" + "${cmd[@]}" "$opt" "$file" &> "$patched_log" || \ + ( cat "$patched_log" >&2; die "objtool klp checksum failed" ) + ) + + for func in ${funcs[$file]}; do + local -a orig patched + paste <(grep0 -E "^DEBUG: .*checksum: $func " "$orig_log") \ + <(grep0 -E "^DEBUG: .*checksum: $func " "$patched_log") | + while IFS= read -r line; do + read -ra orig <<< "${line%%$'\t'*}" + read -ra patched <<< "${line#*$'\t'}" + + if [[ ${#patched[@]} -eq 0 ]]; then + printf "%s: %s: %s (removed)\n" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}" + break + elif [[ ${#orig[@]} -eq 0 ]]; then + printf "%s: %s: %s (added)\n" "${patched[1]%:}" "${patched[3]}" "${patched[-2]}" + break + fi + + [[ "${orig[-1]}" == "${patched[-1]}" ]] && continue + + printf "%s: %s: %s" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}" + [[ "${orig[-2]}" != "${patched[-2]}" ]] && \ + printf " (patched: %s)" "${patched[-2]}" + printf "\n" + break + done || true + done + done +} + +# Build and post-process livepatch module in $KMOD_DIR +build_patch_module() { + local makefile="$KMOD_DIR/Kbuild" + local log="$KMOD_DIR/build.log" + local kmod_file + local cflags=() + local files=() + local cmd=() + + rm -rf "$KMOD_DIR" + mkdir -p "$KMOD_DIR" + + cp -f "$SCRIPT_DIR/init.c" "$KMOD_DIR" + + echo "obj-m := $NAME.o" > "$makefile" + echo -n "$NAME-y := init.o" >> "$makefile" + + find "$DIFF_DIR" -type f -name "*.o" | mapfile -t files + [[ ${#files[@]} -eq 0 ]] && die "no changes detected" + + for file in "${files[@]}"; do + local rel_file="${file#"$DIFF_DIR"/}" + local orig_file="$ORIG_DIR/$rel_file" + local orig_dir="$(dirname "$orig_file")" + local kmod_file="$KMOD_DIR/$rel_file" + local kmod_dir="$(dirname "$kmod_file")" + local cmd_file="$kmod_dir/.$(basename "$file").cmd" + + mkdir -p "$kmod_dir" + cp -f "$file" "$kmod_dir" + + # Tell kbuild this is a prebuilt object + cp -f "$file" "${kmod_file}_shipped" + + # Make modpost happy + touch "$cmd_file" + + echo -n " $rel_file" >> "$makefile" + done + + echo >> "$makefile" + + cflags=("-ffunction-sections") + cflags+=("-fdata-sections") + [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE") + + cmd=("make") + if [[ -v VERBOSE ]]; then + cmd+=("V=1") + else + cmd+=("-s") + fi + cmd+=("-j$JOBS") + cmd+=("--directory=.") + cmd+=("M=$KMOD_DIR") + cmd+=("KCFLAGS=${cflags[*]}") + + # Build a "normal" kernel module with init.c and the diffed objects + "${cmd[@]}" \ + 1> >(tee -a "$log") \ + 2> >(tee -a "$log" >&2) + + kmod_file="$KMOD_DIR/$NAME.ko" + + # Save off the intermediate binary for debugging + cp -f "$kmod_file" "$kmod_file.orig" + + # Work around issue where slight .config change makes corrupt BTF + objcopy --remove-section=.BTF "$kmod_file" + + # Fix (and work around) linker wreckage for klp syms / relocs + "$OBJTOOL" klp post-link "$kmod_file" || die "objtool klp post-link failed" + + cp -f "$kmod_file" "$OUTFILE" +} + + +################################################################################ + +process_args "$@" +do_init + +if (( SHORT_CIRCUIT <= 2 )); then + status "Validating patch(es)" + validate_patches +fi + +if (( SHORT_CIRCUIT <= 1 )); then + status "Building original kernel" + clean_kernel + build_kernel "original" + status "Copying original object files" + copy_orig_objects +fi + +if (( SHORT_CIRCUIT <= 2 )); then + status "Fixing patch(es)" + fix_patches + apply_patches "--silent" + status "Building patched kernel" + build_kernel "patched" + revert_patches + status "Copying patched object files" + copy_patched_objects +fi + +if (( SHORT_CIRCUIT <= 3 )); then + status "Generating original checksums" + generate_checksums "$ORIG_DIR" "$ORIG_CSUM_DIR" "$PATCHED_DIR" + status "Generating patched checksums" + generate_checksums "$PATCHED_DIR" "$PATCHED_CSUM_DIR" +fi + +if (( SHORT_CIRCUIT <= 4 )); then + status "Diffing objects" + diff_objects + if [[ -v DIFF_CHECKSUM ]]; then + status "Finding first changed instructions" + diff_checksums + fi +fi + +if (( SHORT_CIRCUIT <= 5 )); then + status "Building patch module: $OUTFILE" + build_patch_module +fi + +status "SUCCESS" diff --git a/scripts/macro_checker.py b/scripts/macro_checker.py new file mode 100755 index 000000000..ba550982e --- /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 000000000..15ba26974 --- /dev/null +++ b/scripts/make_fit.py @@ -0,0 +1,444 @@ +#!/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 + -r /boot/initrd.img-6.14.0-27-generic @arch/arm64/boot/dts/dtbs-list + -E -c gzip + +Creates a FIT containing the supplied kernel, an optional ramdisk, and a set of +devicetree files, either specified individually or listed in a file (with an +'@' prefix). + +Use -r to specify an existing ramdisk/initrd file. + +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. +""" + +import argparse +import collections +import multiprocessing +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', 'pbzip2,bzip2'), + 'gzip': CompTool('.gz', 'pigz,gzip'), + 'lz4': CompTool('.lz4', 'lz4'), + 'lzma': CompTool('.lzma', 'lzma'), + 'lzo': CompTool('.lzo', 'lzop'), + 'xz': CompTool('.xz', 'xz'), + '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('-r', '--ramdisk', type=str, + help='Specifies the ramdisk/initrd input file') + 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 = 16 << 20 + 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 write_ramdisk(fsw, data, args): + """Write out the ramdisk image + + Writes a ramdisk 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' + """ + with fsw.add_node('ramdisk'): + fsw.property_string('description', 'Ramdisk') + fsw.property_string('type', 'ramdisk') + fsw.property_string('arch', args.arch) + fsw.property_string('compression', 'none') + fsw.property_string('os', args.os) + fsw.property('data', data) + + +def finish_fit(fsw, entries, has_ramdisk=False): + """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 + has_ramdisk (bool): True if a ramdisk is included in the FIT + """ + 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') + if has_ramdisk: + fsw.property_string('ramdisk', 'ramdisk') + 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: + # Add parallel flags for tools that support them + cmd = [tool] + if tool in ('zstd', 'xz'): + cmd.extend(['-T0']) # Use all available cores + cmd.append('-c') + subprocess.call(cmd, 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 compress_dtb(fname, compress): + """Compress a single DTB file + + Args: + fname (str): Filename containing the DTB + compress (str): Compression algorithm, e.g. 'gzip' + + Returns: + tuple: (str: fname, bytes: compressed_data) + """ + with open(fname, 'rb') as inf: + compressed = compress_data(inf, compress) + return fname, compressed + + +def output_dtb(fsw, seq, fname, arch, compress, data=None): + """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 (str): FIT architecture, e.g. 'arm64' + compress (str): Compressed algorithm, e.g. 'gzip' + data (bytes): Pre-compressed data (optional) + """ + 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) + + if data is None: + with open(fname, 'rb') as inf: + data = compress_data(inf, compress) + fsw.property('data', data) + + +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 _process_dtbs(args, fsw, entries, fdts): + """Process all DTB files and add them to the FIT + + Args: + args: Program arguments + fsw: FIT writer object + entries: List to append entries to + fdts: Dictionary of processed DTBs + + Returns: + tuple: + Number of files processed + Total size of files processed + """ + seq = 0 + size = 0 + + # First figure out the unique DTB files that need compression + todo = [] + file_info = [] # List of (fname, model, compat, files) tuples + + 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 + + file_info.append((fname, model, compat, files)) + for fn in files: + if fn not in fdts and fn not in todo: + todo.append(fn) + + # Compress all DTBs in parallel + cache = {} + if todo and args.compress != 'none': + if args.verbose: + print(f'Compressing {len(todo)} DTBs...') + + with multiprocessing.Pool() as pool: + compress_args = [(fn, args.compress) for fn in todo] + # unpacks each tuple, calls compress_dtb(fn, compress) in parallel + results = pool.starmap(compress_dtb, compress_args) + + cache = dict(results) + + # Now write all DTBs to the FIT using pre-compressed data + for fname, model, compat, files in file_info: + 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, + cache.get(fn)) + fdts[fn] = seq + + files_seq = [fdts[fn] for fn in files] + entries.append([model, compat, files_seq]) + + return seq, size + + +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 + """ + 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) + + # Handle the ramdisk if provided. Compression is not supported as it is + # already compressed. + if args.ramdisk: + with open(args.ramdisk, 'rb') as inf: + data = inf.read() + size += len(data) + write_ramdisk(fsw, data, args) + + count, fdt_size = _process_dtbs(args, fsw, entries, fdts) + size += fdt_size + + finish_fit(fsw, entries, bool(args.ramdisk)) + + # Include the kernel itself in the returned file count + fdt = fsw.as_fdt() + fdt.pack() + return fdt.as_bytearray(), count + 1 + bool(args.ramdisk), 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/makelst b/scripts/makelst new file mode 100755 index 000000000..e432af073 --- /dev/null +++ b/scripts/makelst @@ -0,0 +1,32 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# A script to dump mixed source code & assembly +# with correct relocations from System.map +# Requires the following lines in makefile: +#%.lst: %.c +# $(CC) $(c_flags) -g -c -o $*.o $< && +# $(srctree)/scripts/makelst $*.o System.map $(OBJDUMP) > $@ +# +# Copyright (C) 2000 IBM Corporation +# Author(s): DJ Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com) +# William Stearns <wstearns@pobox.com> +# + +# awk style field access +field() { + shift $1 ; echo $1 +} + +t1=`$3 --syms $1 | grep .text | grep -m1 " F "` +if [ -n "$t1" ]; then + t2=`field 6 $t1` + if [ ! -r $2 ]; then + echo "No System.map" >&2 + else + t3=`grep $t2 $2` + t4=`field 1 $t3` + t5=`field 1 $t1` + t6=`printf "%lu" $((0x$t4 - 0x$t5))` + fi +fi +$3 -r --source --adjust-vma=${t6:-0} $1 diff --git a/scripts/markup_oops.pl b/scripts/markup_oops.pl new file mode 100755 index 000000000..e476caf52 --- /dev/null +++ b/scripts/markup_oops.pl @@ -0,0 +1,366 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-only + +use File::Basename; +use Math::BigInt; +use Getopt::Long; + +# Copyright 2008, Intel Corporation +# +# This file is part of the Linux kernel +# +# Authors: +# Arjan van de Ven <arjan@linux.intel.com> + + +my $cross_compile = ""; +my $vmlinux_name = ""; +my $modulefile = ""; + +# Get options +Getopt::Long::GetOptions( + 'cross-compile|c=s' => \$cross_compile, + 'module|m=s' => \$modulefile, + 'help|h' => \&usage, +) || usage (); +my $vmlinux_name = $ARGV[0]; +if (!defined($vmlinux_name)) { + my $kerver = `uname -r`; + chomp($kerver); + $vmlinux_name = "/lib/modules/$kerver/build/vmlinux"; + print "No vmlinux specified, assuming $vmlinux_name\n"; +} +my $filename = $vmlinux_name; + +# Parse the oops to find the EIP value + +my $target = "0"; +my $function; +my $module = ""; +my $func_offset = 0; +my $vmaoffset = 0; + +my %regs; + + +sub parse_x86_regs +{ + my ($line) = @_; + if ($line =~ /EAX: ([0-9a-f]+) EBX: ([0-9a-f]+) ECX: ([0-9a-f]+) EDX: ([0-9a-f]+)/) { + $regs{"%eax"} = $1; + $regs{"%ebx"} = $2; + $regs{"%ecx"} = $3; + $regs{"%edx"} = $4; + } + if ($line =~ /ESI: ([0-9a-f]+) EDI: ([0-9a-f]+) EBP: ([0-9a-f]+) ESP: ([0-9a-f]+)/) { + $regs{"%esi"} = $1; + $regs{"%edi"} = $2; + $regs{"%esp"} = $4; + } + if ($line =~ /RAX: ([0-9a-f]+) RBX: ([0-9a-f]+) RCX: ([0-9a-f]+)/) { + $regs{"%eax"} = $1; + $regs{"%ebx"} = $2; + $regs{"%ecx"} = $3; + } + if ($line =~ /RDX: ([0-9a-f]+) RSI: ([0-9a-f]+) RDI: ([0-9a-f]+)/) { + $regs{"%edx"} = $1; + $regs{"%esi"} = $2; + $regs{"%edi"} = $3; + } + if ($line =~ /RBP: ([0-9a-f]+) R08: ([0-9a-f]+) R09: ([0-9a-f]+)/) { + $regs{"%r08"} = $2; + $regs{"%r09"} = $3; + } + if ($line =~ /R10: ([0-9a-f]+) R11: ([0-9a-f]+) R12: ([0-9a-f]+)/) { + $regs{"%r10"} = $1; + $regs{"%r11"} = $2; + $regs{"%r12"} = $3; + } + if ($line =~ /R13: ([0-9a-f]+) R14: ([0-9a-f]+) R15: ([0-9a-f]+)/) { + $regs{"%r13"} = $1; + $regs{"%r14"} = $2; + $regs{"%r15"} = $3; + } +} + +sub reg_name +{ + my ($reg) = @_; + $reg =~ s/r(.)x/e\1x/; + $reg =~ s/r(.)i/e\1i/; + $reg =~ s/r(.)p/e\1p/; + return $reg; +} + +sub process_x86_regs +{ + my ($line, $cntr) = @_; + my $str = ""; + if (length($line) < 40) { + return ""; # not an asm istruction + } + + # find the arguments to the instruction + if ($line =~ /([0-9a-zA-Z\,\%\(\)\-\+]+)$/) { + $lastword = $1; + } else { + return ""; + } + + # we need to find the registers that get clobbered, + # since their value is no longer relevant for previous + # instructions in the stream. + + $clobber = $lastword; + # first, remove all memory operands, they're read only + $clobber =~ s/\([a-z0-9\%\,]+\)//g; + # then, remove everything before the comma, thats the read part + $clobber =~ s/.*\,//g; + + # if this is the instruction that faulted, we haven't actually done + # the write yet... nothing is clobbered. + if ($cntr == 0) { + $clobber = ""; + } + + foreach $reg (keys(%regs)) { + my $clobberprime = reg_name($clobber); + my $lastwordprime = reg_name($lastword); + my $val = $regs{$reg}; + if ($val =~ /^[0]+$/) { + $val = "0"; + } else { + $val =~ s/^0*//; + } + + # first check if we're clobbering this register; if we do + # we print it with a =>, and then delete its value + if ($clobber =~ /$reg/ || $clobberprime =~ /$reg/) { + if (length($val) > 0) { + $str = $str . " $reg => $val "; + } + $regs{$reg} = ""; + $val = ""; + } + # now check if we're reading this register + if ($lastword =~ /$reg/ || $lastwordprime =~ /$reg/) { + if (length($val) > 0) { + $str = $str . " $reg = $val "; + } + } + } + return $str; +} + +# parse the oops +while (<STDIN>) { + my $line = $_; + if ($line =~ /EIP: 0060:\[\<([a-z0-9]+)\>\]/) { + $target = $1; + } + if ($line =~ /RIP: 0010:\[\<([a-z0-9]+)\>\]/) { + $target = $1; + } + if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) { + $function = $1; + $func_offset = $2; + } + if ($line =~ /RIP: 0010:\[\<[0-9a-f]+\>\] \[\<[0-9a-f]+\>\] ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) { + $function = $1; + $func_offset = $2; + } + + # check if it's a module + if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+(0x[0-9a-f]+)\/0x[a-f0-9]+\W\[([a-zA-Z0-9\_\-]+)\]/) { + $module = $3; + } + if ($line =~ /RIP: 0010:\[\<[0-9a-f]+\>\] \[\<[0-9a-f]+\>\] ([a-zA-Z0-9\_]+)\+(0x[0-9a-f]+)\/0x[a-f0-9]+\W\[([a-zA-Z0-9\_\-]+)\]/) { + $module = $3; + } + parse_x86_regs($line); +} + +my $decodestart = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$func_offset"); +my $decodestop = Math::BigInt->from_hex("0x$target") + 8192; +if ($target eq "0") { + print "No oops found!\n"; + usage(); +} + +# if it's a module, we need to find the .ko file and calculate a load offset +if ($module ne "") { + if ($modulefile eq "") { + $modulefile = `modinfo -F filename $module`; + chomp($modulefile); + } + $filename = $modulefile; + if ($filename eq "") { + print "Module .ko file for $module not found. Aborting\n"; + exit; + } + # ok so we found the module, now we need to calculate the vma offset + open(FILE, $cross_compile."objdump -dS $filename |") || die "Cannot start objdump"; + while (<FILE>) { + if ($_ =~ /^([0-9a-f]+) \<$function\>\:/) { + my $fu = $1; + $vmaoffset = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$fu") - Math::BigInt->from_hex("0x$func_offset"); + } + } + close(FILE); +} + +my $counter = 0; +my $state = 0; +my $center = -1; +my @lines; +my @reglines; + +sub InRange { + my ($address, $target) = @_; + my $ad = "0x".$address; + my $ta = "0x".$target; + my $delta = Math::BigInt->from_hex($ad) - Math::BigInt->from_hex($ta); + + if (($delta > -4096) && ($delta < 4096)) { + return 1; + } + return 0; +} + + + +# first, parse the input into the lines array, but to keep size down, +# we only do this for 4Kb around the sweet spot + +open(FILE, $cross_compile."objdump -dS --adjust-vma=$vmaoffset --start-address=$decodestart --stop-address=$decodestop $filename |") || die "Cannot start objdump"; + +while (<FILE>) { + my $line = $_; + chomp($line); + if ($state == 0) { + if ($line =~ /^([a-f0-9]+)\:/) { + if (InRange($1, $target)) { + $state = 1; + } + } + } + if ($state == 1) { + if ($line =~ /^([a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]+)\:/) { + my $val = $1; + if (!InRange($val, $target)) { + last; + } + if ($val eq $target) { + $center = $counter; + } + } + $lines[$counter] = $line; + + $counter = $counter + 1; + } +} + +close(FILE); + +if ($counter == 0) { + print "No matching code found \n"; + exit; +} + +if ($center == -1) { + print "No matching code found \n"; + exit; +} + +my $start; +my $finish; +my $codelines = 0; +my $binarylines = 0; +# now we go up and down in the array to find how much we want to print + +$start = $center; + +while ($start > 1) { + $start = $start - 1; + my $line = $lines[$start]; + if ($line =~ /^([a-f0-9]+)\:/) { + $binarylines = $binarylines + 1; + } else { + $codelines = $codelines + 1; + } + if ($codelines > 10) { + last; + } + if ($binarylines > 20) { + last; + } +} + + +$finish = $center; +$codelines = 0; +$binarylines = 0; +while ($finish < $counter) { + $finish = $finish + 1; + my $line = $lines[$finish]; + if ($line =~ /^([a-f0-9]+)\:/) { + $binarylines = $binarylines + 1; + } else { + $codelines = $codelines + 1; + } + if ($codelines > 10) { + last; + } + if ($binarylines > 20) { + last; + } +} + + +my $i; + + +# start annotating the registers in the asm. +# this goes from the oopsing point back, so that the annotator +# can track (opportunistically) which registers got written and +# whos value no longer is relevant. + +$i = $center; +while ($i >= $start) { + $reglines[$i] = process_x86_regs($lines[$i], $center - $i); + $i = $i - 1; +} + +$i = $start; +while ($i < $finish) { + my $line; + if ($i == $center) { + $line = "*$lines[$i] "; + } else { + $line = " $lines[$i] "; + } + print $line; + if (defined($reglines[$i]) && length($reglines[$i]) > 0) { + my $c = 60 - length($line); + while ($c > 0) { print " "; $c = $c - 1; }; + print "| $reglines[$i]"; + } + if ($i == $center) { + print "<--- faulting instruction"; + } + print "\n"; + $i = $i +1; +} + +sub usage { + print <<EOT; +Usage: + dmesg | perl $0 [OPTION] [VMLINUX] + +OPTION: + -c, --cross-compile CROSS_COMPILE Specify the prefix used for toolchain. + -m, --module MODULE_DIRNAME Specify the module filename. + -h, --help Help. +EOT + exit; +} diff --git a/scripts/min-tool-version.sh b/scripts/min-tool-version.sh new file mode 100755 index 000000000..99bfdcaa3 --- /dev/null +++ b/scripts/min-tool-version.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Print the minimum supported version of the given tool. +# When you raise the minimum version, please update +# Documentation/process/changes.rst as well. + +set -e + +if [ $# != 1 ]; then + echo "Usage: $0 toolname" >&2 + exit 1 +fi + +case "$1" in +binutils) + echo 2.30.0 + ;; +gcc) + if [ "$ARCH" = parisc64 ]; then + echo 12.0.0 + else + echo 8.1.0 + fi + ;; +llvm) + if [ "$SRCARCH" = loongarch ]; then + echo 18.0.0 + else + echo 17.0.1 + fi + ;; +rustc) + if [ "$SRCARCH" = "s390" ]; then + echo 1.96.0 + elif [ "$ARCH" = powerpc ]; then + echo 1.95.0 + else + echo 1.85.0 + fi + ;; +bindgen) + echo 0.71.1 + ;; +*) + echo "$1: unknown tool" >&2 + exit 1 + ;; +esac diff --git a/scripts/misc-check b/scripts/misc-check new file mode 100755 index 000000000..40e5a4b01 --- /dev/null +++ b/scripts/misc-check @@ -0,0 +1,76 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +set -e + +# Detect files that are tracked but ignored by git. +check_tracked_ignored_files () { + 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 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)?|_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)?|_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/mkcompile_h b/scripts/mkcompile_h new file mode 100755 index 000000000..2596f78e5 --- /dev/null +++ b/scripts/mkcompile_h @@ -0,0 +1,27 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +UTS_MACHINE=$1 +CC_VERSION="$2" +LD=$3 + +if test -z "$KBUILD_BUILD_USER"; then + LINUX_COMPILE_BY=$(whoami | sed 's/\\/\\\\/') +else + LINUX_COMPILE_BY=$KBUILD_BUILD_USER +fi +if test -z "$KBUILD_BUILD_HOST"; then + LINUX_COMPILE_HOST=`uname -n` +else + LINUX_COMPILE_HOST=$KBUILD_BUILD_HOST +fi + +LD_VERSION=$(LC_ALL=C $LD -v | head -n1 | + sed -e 's/(compatible with [^)]*)//' -e 's/[[:space:]]*$//') + +cat <<EOF +#define UTS_MACHINE "${UTS_MACHINE}" +#define LINUX_COMPILE_BY "${LINUX_COMPILE_BY}" +#define LINUX_COMPILE_HOST "${LINUX_COMPILE_HOST}" +#define LINUX_COMPILER "${CC_VERSION}, ${LD_VERSION}" +EOF diff --git a/scripts/mksysmap b/scripts/mksysmap new file mode 100755 index 000000000..856b26ba2 --- /dev/null +++ b/scripts/mksysmap @@ -0,0 +1,94 @@ +#!/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. +# +# 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'. +# +# --------------------------------------------------------------------------- +# Ignored symbol types +# + +# a: local absolute symbols +# N: debugging symbols +# U: undefined global symbols +# w: local weak symbols +/ [aNUw] /d + +# --------------------------------------------------------------------------- +# Ignored prefixes +# (do not forget a space before each pattern) + +# local symbols for ARM, MIPS, etc. +/ \$/d + +# local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc. +/ \.L/d + +# arm64 EFI stub namespace +/ __efistub_/d + +# arm64 local symbols in PIE namespace +/ __pi_\$/d +/ __pi_\.L/d + +# arm64 local symbols in non-VHE KVM namespace +/ __kvm_nvhe_\$/d +/ __kvm_nvhe_\.L/d + +# lld arm/aarch64/mips thunks +/ __[[:alnum:]]*Thunk_/d + +# CFI type identifiers +/ __kcfi_typeid_/d +/ __kvm_nvhe___kcfi_typeid_/d +/ __pi___kcfi_typeid_/d + +# CRC from modversions +/ __crc_/d + +# EXPORT_SYMBOL (symbol name) +/ __kstrtab_/d + +# EXPORT_SYMBOL (namespace) +/ __kstrtabns_/d + +# MODULE_DEVICE_TABLE (symbol name) +/ __mod_device_table__/d + +# --------------------------------------------------------------------------- +# Ignored suffixes +# (do not forget '$' after each pattern) + +# arm +/_from_arm$/d +/_from_thumb$/d +/_veneer$/d + +# --------------------------------------------------------------------------- +# Ignored symbols (exact match) +# (do not forget a space before and '$' after each pattern) + +# for LoongArch? +/ L0$/d + +# ppc +/ _SDA_BASE_$/d +/ _SDA2_BASE_$/d + +# MODULE_INFO() +/ __UNIQUE_ID_modinfo_[0-9]*$/d + +# --------------------------------------------------------------------------- +# Ignored patterns +# (symbols that contain the pattern are ignored) + +# ppc stub +/\.long_branch\./d +/\.plt_branch\./d diff --git a/scripts/mkuboot.sh b/scripts/mkuboot.sh new file mode 100755 index 000000000..4b1fe09e9 --- /dev/null +++ b/scripts/mkuboot.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# +# Build U-Boot image when `mkimage' tool is available. +# + +MKIMAGE=$(type -path "${CROSS_COMPILE}mkimage") + +if [ -z "${MKIMAGE}" ]; then + MKIMAGE=$(type -path mkimage) + if [ -z "${MKIMAGE}" ]; then + # Doesn't exist + echo '"mkimage" command not found - U-Boot images will not be built' >&2 + exit 1; + fi +fi + +# Call "mkimage" to create U-Boot image +${MKIMAGE} "$@" diff --git a/scripts/mod/.gitignore b/scripts/mod/.gitignore new file mode 100644 index 000000000..0465ec33c --- /dev/null +++ b/scripts/mod/.gitignore @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +/devicetable-offsets.h +/elfconfig.h +/mk_elfconfig +/modpost diff --git a/scripts/mod/Makefile b/scripts/mod/Makefile new file mode 100644 index 000000000..c729bc936 --- /dev/null +++ b/scripts/mod/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS_REMOVE_empty.o += $(CC_FLAGS_LTO) + +hostprogs-always-y += modpost mk_elfconfig +always-y += empty.o + +modpost-objs := modpost.o file2alias.o sumversion.o symsearch.o + +devicetable-offsets-file := devicetable-offsets.h + +$(obj)/$(devicetable-offsets-file): $(obj)/devicetable-offsets.s FORCE + $(call filechk,offsets,__DEVICETABLE_OFFSETS_H__) + +targets += $(devicetable-offsets-file) devicetable-offsets.s + +# dependencies on generated files need to be listed explicitly + +$(obj)/modpost.o $(obj)/file2alias.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h +$(obj)/file2alias.o: $(obj)/$(devicetable-offsets-file) + +quiet_cmd_elfconfig = MKELF $@ + cmd_elfconfig = $(obj)/mk_elfconfig < $< > $@ + +$(obj)/elfconfig.h: $(obj)/empty.o $(obj)/mk_elfconfig FORCE + $(call if_changed,elfconfig) + +targets += elfconfig.h diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c new file mode 100644 index 000000000..b4178c42d --- /dev/null +++ b/scripts/mod/devicetable-offsets.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +#define COMPILE_OFFSETS +#include <linux/kbuild.h> +#include <linux/mod_devicetable.h> + +#define DEVID(devid) DEFINE(SIZE_##devid, sizeof(struct devid)) +#define DEVID_FIELD(devid, field) \ + DEFINE(OFF_##devid##_##field, offsetof(struct devid, field)) + +int main(void) +{ + DEVID(usb_device_id); + DEVID_FIELD(usb_device_id, match_flags); + DEVID_FIELD(usb_device_id, idVendor); + DEVID_FIELD(usb_device_id, idProduct); + DEVID_FIELD(usb_device_id, bcdDevice_lo); + DEVID_FIELD(usb_device_id, bcdDevice_hi); + DEVID_FIELD(usb_device_id, bDeviceClass); + DEVID_FIELD(usb_device_id, bDeviceSubClass); + DEVID_FIELD(usb_device_id, bDeviceProtocol); + DEVID_FIELD(usb_device_id, bInterfaceClass); + DEVID_FIELD(usb_device_id, bInterfaceSubClass); + DEVID_FIELD(usb_device_id, bInterfaceProtocol); + DEVID_FIELD(usb_device_id, bInterfaceNumber); + + DEVID(hid_device_id); + DEVID_FIELD(hid_device_id, bus); + DEVID_FIELD(hid_device_id, group); + DEVID_FIELD(hid_device_id, vendor); + DEVID_FIELD(hid_device_id, product); + + DEVID(ieee1394_device_id); + DEVID_FIELD(ieee1394_device_id, match_flags); + DEVID_FIELD(ieee1394_device_id, vendor_id); + DEVID_FIELD(ieee1394_device_id, model_id); + DEVID_FIELD(ieee1394_device_id, specifier_id); + DEVID_FIELD(ieee1394_device_id, version); + + DEVID(pci_device_id); + DEVID_FIELD(pci_device_id, vendor); + DEVID_FIELD(pci_device_id, device); + DEVID_FIELD(pci_device_id, subvendor); + DEVID_FIELD(pci_device_id, subdevice); + DEVID_FIELD(pci_device_id, class); + DEVID_FIELD(pci_device_id, class_mask); + DEVID_FIELD(pci_device_id, override_only); + + DEVID(ccw_device_id); + DEVID_FIELD(ccw_device_id, match_flags); + DEVID_FIELD(ccw_device_id, cu_type); + DEVID_FIELD(ccw_device_id, cu_model); + DEVID_FIELD(ccw_device_id, dev_type); + DEVID_FIELD(ccw_device_id, dev_model); + + DEVID(ap_device_id); + DEVID_FIELD(ap_device_id, dev_type); + + DEVID(css_device_id); + DEVID_FIELD(css_device_id, type); + + DEVID(serio_device_id); + DEVID_FIELD(serio_device_id, type); + DEVID_FIELD(serio_device_id, proto); + DEVID_FIELD(serio_device_id, id); + DEVID_FIELD(serio_device_id, extra); + + DEVID(acpi_device_id); + DEVID_FIELD(acpi_device_id, id); + DEVID_FIELD(acpi_device_id, cls); + DEVID_FIELD(acpi_device_id, cls_msk); + + DEVID(pnp_device_id); + DEVID_FIELD(pnp_device_id, id); + + DEVID(pnp_card_device_id); + DEVID_FIELD(pnp_card_device_id, devs); + + DEVID(pcmcia_device_id); + DEVID_FIELD(pcmcia_device_id, match_flags); + DEVID_FIELD(pcmcia_device_id, manf_id); + DEVID_FIELD(pcmcia_device_id, card_id); + DEVID_FIELD(pcmcia_device_id, func_id); + DEVID_FIELD(pcmcia_device_id, function); + DEVID_FIELD(pcmcia_device_id, device_no); + DEVID_FIELD(pcmcia_device_id, prod_id_hash); + + DEVID(of_device_id); + DEVID_FIELD(of_device_id, name); + DEVID_FIELD(of_device_id, type); + DEVID_FIELD(of_device_id, compatible); + + DEVID(vio_device_id); + DEVID_FIELD(vio_device_id, type); + DEVID_FIELD(vio_device_id, compat); + + DEVID(input_device_id); + DEVID_FIELD(input_device_id, flags); + DEVID_FIELD(input_device_id, bustype); + DEVID_FIELD(input_device_id, vendor); + DEVID_FIELD(input_device_id, product); + DEVID_FIELD(input_device_id, version); + DEVID_FIELD(input_device_id, evbit); + DEVID_FIELD(input_device_id, keybit); + DEVID_FIELD(input_device_id, relbit); + DEVID_FIELD(input_device_id, absbit); + DEVID_FIELD(input_device_id, mscbit); + DEVID_FIELD(input_device_id, ledbit); + DEVID_FIELD(input_device_id, sndbit); + DEVID_FIELD(input_device_id, ffbit); + DEVID_FIELD(input_device_id, swbit); + + DEVID(eisa_device_id); + DEVID_FIELD(eisa_device_id, sig); + + DEVID(parisc_device_id); + DEVID_FIELD(parisc_device_id, hw_type); + DEVID_FIELD(parisc_device_id, hversion); + DEVID_FIELD(parisc_device_id, hversion_rev); + DEVID_FIELD(parisc_device_id, sversion); + + DEVID(sdio_device_id); + DEVID_FIELD(sdio_device_id, class); + DEVID_FIELD(sdio_device_id, vendor); + DEVID_FIELD(sdio_device_id, device); + + DEVID(ssb_device_id); + DEVID_FIELD(ssb_device_id, vendor); + DEVID_FIELD(ssb_device_id, coreid); + DEVID_FIELD(ssb_device_id, revision); + + DEVID(bcma_device_id); + DEVID_FIELD(bcma_device_id, manuf); + DEVID_FIELD(bcma_device_id, id); + DEVID_FIELD(bcma_device_id, rev); + DEVID_FIELD(bcma_device_id, class); + + DEVID(virtio_device_id); + DEVID_FIELD(virtio_device_id, device); + DEVID_FIELD(virtio_device_id, vendor); + + DEVID(hv_vmbus_device_id); + DEVID_FIELD(hv_vmbus_device_id, guid); + + DEVID(rpmsg_device_id); + DEVID_FIELD(rpmsg_device_id, name); + + DEVID(i2c_device_id); + DEVID_FIELD(i2c_device_id, name); + + DEVID(i3c_device_id); + DEVID_FIELD(i3c_device_id, match_flags); + DEVID_FIELD(i3c_device_id, dcr); + DEVID_FIELD(i3c_device_id, manuf_id); + 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); + + DEVID(dmi_system_id); + DEVID_FIELD(dmi_system_id, matches); + + DEVID(platform_device_id); + DEVID_FIELD(platform_device_id, name); + + DEVID(mdio_device_id); + DEVID_FIELD(mdio_device_id, phy_id); + DEVID_FIELD(mdio_device_id, phy_id_mask); + + DEVID(zorro_device_id); + DEVID_FIELD(zorro_device_id, id); + + DEVID(isapnp_device_id); + DEVID_FIELD(isapnp_device_id, vendor); + DEVID_FIELD(isapnp_device_id, function); + + DEVID(ipack_device_id); + DEVID_FIELD(ipack_device_id, format); + DEVID_FIELD(ipack_device_id, vendor); + DEVID_FIELD(ipack_device_id, device); + + DEVID(amba_id); + DEVID_FIELD(amba_id, id); + DEVID_FIELD(amba_id, mask); + + DEVID(mips_cdmm_device_id); + DEVID_FIELD(mips_cdmm_device_id, type); + + DEVID(x86_cpu_id); + DEVID_FIELD(x86_cpu_id, feature); + DEVID_FIELD(x86_cpu_id, family); + DEVID_FIELD(x86_cpu_id, model); + DEVID_FIELD(x86_cpu_id, vendor); + + DEVID(cpu_feature); + DEVID_FIELD(cpu_feature, feature); + + DEVID(mcb_device_id); + DEVID_FIELD(mcb_device_id, device); + + DEVID(mei_cl_device_id); + DEVID_FIELD(mei_cl_device_id, name); + DEVID_FIELD(mei_cl_device_id, uuid); + DEVID_FIELD(mei_cl_device_id, version); + + DEVID(rio_device_id); + DEVID_FIELD(rio_device_id, did); + DEVID_FIELD(rio_device_id, vid); + DEVID_FIELD(rio_device_id, asm_did); + DEVID_FIELD(rio_device_id, asm_vid); + + DEVID(ulpi_device_id); + DEVID_FIELD(ulpi_device_id, vendor); + DEVID_FIELD(ulpi_device_id, product); + + DEVID(hda_device_id); + DEVID_FIELD(hda_device_id, vendor_id); + DEVID_FIELD(hda_device_id, rev_id); + DEVID_FIELD(hda_device_id, api_version); + + DEVID(sdw_device_id); + DEVID_FIELD(sdw_device_id, mfg_id); + DEVID_FIELD(sdw_device_id, part_id); + DEVID_FIELD(sdw_device_id, sdw_version); + DEVID_FIELD(sdw_device_id, class_id); + + DEVID(fsl_mc_device_id); + DEVID_FIELD(fsl_mc_device_id, vendor); + DEVID_FIELD(fsl_mc_device_id, obj_type); + + DEVID(tb_service_id); + DEVID_FIELD(tb_service_id, match_flags); + DEVID_FIELD(tb_service_id, protocol_key); + DEVID_FIELD(tb_service_id, protocol_id); + DEVID_FIELD(tb_service_id, protocol_version); + DEVID_FIELD(tb_service_id, protocol_revision); + + DEVID(typec_device_id); + DEVID_FIELD(typec_device_id, svid); + + DEVID(tee_client_device_id); + DEVID_FIELD(tee_client_device_id, uuid); + + DEVID(wmi_device_id); + DEVID_FIELD(wmi_device_id, guid_string); + + DEVID(mhi_device_id); + DEVID_FIELD(mhi_device_id, chan); + + DEVID(auxiliary_device_id); + DEVID_FIELD(auxiliary_device_id, name); + + DEVID(ssam_device_id); + DEVID_FIELD(ssam_device_id, match_flags); + DEVID_FIELD(ssam_device_id, domain); + DEVID_FIELD(ssam_device_id, category); + DEVID_FIELD(ssam_device_id, target); + DEVID_FIELD(ssam_device_id, instance); + DEVID_FIELD(ssam_device_id, function); + + DEVID(dfl_device_id); + DEVID_FIELD(dfl_device_id, type); + DEVID_FIELD(dfl_device_id, feature_id); + + DEVID(ishtp_device_id); + DEVID_FIELD(ishtp_device_id, guid); + + DEVID(cdx_device_id); + DEVID_FIELD(cdx_device_id, vendor); + DEVID_FIELD(cdx_device_id, device); + DEVID_FIELD(cdx_device_id, subvendor); + DEVID_FIELD(cdx_device_id, subdevice); + DEVID_FIELD(cdx_device_id, class); + DEVID_FIELD(cdx_device_id, class_mask); + DEVID_FIELD(cdx_device_id, override_only); + + DEVID(vchiq_device_id); + DEVID_FIELD(vchiq_device_id, name); + + DEVID(coreboot_device_id); + DEVID_FIELD(coreboot_device_id, tag); + + return 0; +} diff --git a/scripts/mod/empty.c b/scripts/mod/empty.c new file mode 100644 index 000000000..49839cc4f --- /dev/null +++ b/scripts/mod/empty.c @@ -0,0 +1 @@ +/* empty file to figure out endianness / word size */ diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c new file mode 100644 index 000000000..8d36c74de --- /dev/null +++ b/scripts/mod/file2alias.c @@ -0,0 +1,1610 @@ +/* Simple code to turn various tables in an ELF file into alias definitions. + * This deals with kernel datastructures where they should be + * dealt with: in the kernel source. + * + * Copyright 2002-2003 Rusty Russell, IBM Corporation + * 2003 Kai Germaschewski + * + * + * This software may be used and distributed according to the terms + * 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" + +/* We use the ELF typedefs for kernel_ulong_t but bite the bullet and + * use either stdint.h or inttypes.h for the rest. */ +#if KERNEL_ELFCLASS == ELFCLASS32 +typedef Elf32_Addr kernel_ulong_t; +#define BITS_PER_LONG 32 +#else +typedef Elf64_Addr kernel_ulong_t; +#define BITS_PER_LONG 64 +#endif +#ifdef __sun__ +#include <inttypes.h> +#else +#include <stdint.h> +#endif + +#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; + } + } + + new->builtin_modname = NULL; + list_add_tail(&new->node, &mod->aliases); +} + +typedef uint32_t __u32; +typedef uint16_t __u16; +typedef unsigned char __u8; + +/* UUID types for backward compatibility, don't use in new code */ +typedef struct { + __u8 b[16]; +} guid_t; + +typedef struct { + __u8 b[16]; +} uuid_t; + +#define UUID_STRING_LEN 36 + +/* MEI UUID type, don't use anywhere else */ +typedef struct { + __u8 b[16]; +} uuid_le; + +/* Big exception to the "don't include kernel headers into userspace, which + * even potentially has different endianness and word sizes, since + * we handle those differences explicitly below */ +#include "../../include/linux/mod_devicetable.h" + +struct devtable { + const char *device_id; + unsigned long id_size; + void (*do_entry)(struct module *mod, void *symval); +}; + +/* 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 = \ + 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) \ + typeof(((struct devid *)0)->f) *f = ((m) + OFF_##devid##_##f) + +#define ADD(str, sep, cond, field) \ +do { \ + strcat(str, sep); \ + if (cond) \ + sprintf(str + strlen(str), \ + sizeof(field) == 1 ? "%02X" : \ + sizeof(field) == 2 ? "%04X" : \ + sizeof(field) == 4 ? "%08X" : "", \ + field); \ + else \ + sprintf(str + strlen(str), "*"); \ +} while(0) + +static inline void add_uuid(char *str, uuid_le uuid) +{ + int len = strlen(str); + + sprintf(str + len, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.b[3], uuid.b[2], uuid.b[1], uuid.b[0], + uuid.b[5], uuid.b[4], uuid.b[7], uuid.b[6], + uuid.b[8], uuid.b[9], uuid.b[10], uuid.b[11], + uuid.b[12], uuid.b[13], uuid.b[14], uuid.b[15]); +} + +static inline void add_guid(char *str, guid_t guid) +{ + int len = strlen(str); + + sprintf(str + len, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.b[3], guid.b[2], guid.b[1], guid.b[0], + guid.b[5], guid.b[4], guid.b[7], guid.b[6], + guid.b[8], guid.b[9], guid.b[10], guid.b[11], + guid.b[12], guid.b[13], guid.b[14], guid.b[15]); +} + +/* USB is special because the bcdDevice can be matched against a numeric range */ +/* Looks like "usb:vNpNdNdcNdscNdpNicNiscNipNinN" */ +static void do_usb_entry(void *symval, + unsigned int bcdDevice_initial, int bcdDevice_initial_digits, + unsigned char range_lo, unsigned char range_hi, + unsigned char max, struct module *mod) +{ + char alias[500]; + DEF_FIELD(symval, usb_device_id, match_flags); + DEF_FIELD(symval, usb_device_id, idVendor); + DEF_FIELD(symval, usb_device_id, idProduct); + DEF_FIELD(symval, usb_device_id, bcdDevice_lo); + DEF_FIELD(symval, usb_device_id, bDeviceClass); + DEF_FIELD(symval, usb_device_id, bDeviceSubClass); + DEF_FIELD(symval, usb_device_id, bDeviceProtocol); + DEF_FIELD(symval, usb_device_id, bInterfaceClass); + DEF_FIELD(symval, usb_device_id, bInterfaceSubClass); + DEF_FIELD(symval, usb_device_id, bInterfaceProtocol); + DEF_FIELD(symval, usb_device_id, bInterfaceNumber); + + strcpy(alias, "usb:"); + ADD(alias, "v", match_flags&USB_DEVICE_ID_MATCH_VENDOR, + idVendor); + ADD(alias, "p", match_flags&USB_DEVICE_ID_MATCH_PRODUCT, + idProduct); + + strcat(alias, "d"); + if (bcdDevice_initial_digits) + sprintf(alias + strlen(alias), "%0*X", + bcdDevice_initial_digits, bcdDevice_initial); + if (range_lo == range_hi) + sprintf(alias + strlen(alias), "%X", range_lo); + else if (range_lo > 0 || range_hi < max) { + if (range_lo > 0x9 || range_hi < 0xA) + sprintf(alias + strlen(alias), + "[%X-%X]", + range_lo, + range_hi); + else { + sprintf(alias + strlen(alias), + range_lo < 0x9 ? "[%X-9" : "[%X", + range_lo); + sprintf(alias + strlen(alias), + range_hi > 0xA ? "A-%X]" : "%X]", + range_hi); + } + } + if (bcdDevice_initial_digits < (sizeof(bcdDevice_lo) * 2 - 1)) + strcat(alias, "*"); + + ADD(alias, "dc", match_flags&USB_DEVICE_ID_MATCH_DEV_CLASS, + bDeviceClass); + ADD(alias, "dsc", match_flags&USB_DEVICE_ID_MATCH_DEV_SUBCLASS, + bDeviceSubClass); + ADD(alias, "dp", match_flags&USB_DEVICE_ID_MATCH_DEV_PROTOCOL, + bDeviceProtocol); + ADD(alias, "ic", match_flags&USB_DEVICE_ID_MATCH_INT_CLASS, + bInterfaceClass); + ADD(alias, "isc", match_flags&USB_DEVICE_ID_MATCH_INT_SUBCLASS, + bInterfaceSubClass); + ADD(alias, "ip", match_flags&USB_DEVICE_ID_MATCH_INT_PROTOCOL, + bInterfaceProtocol); + ADD(alias, "in", match_flags&USB_DEVICE_ID_MATCH_INT_NUMBER, + bInterfaceNumber); + + module_alias_printf(mod, true, "%s", alias); +} + +/* Handles increment/decrement of BCD formatted integers */ +/* Returns the previous value, so it works like i++ or i-- */ +static unsigned int incbcd(unsigned int *bcd, + int inc, + unsigned char max, + size_t chars) +{ + unsigned int init = *bcd, i, j; + unsigned long long c, dec = 0; + + /* If bcd is not in BCD format, just increment */ + if (max > 0x9) { + *bcd += inc; + return init; + } + + /* Convert BCD to Decimal */ + for (i=0 ; i < chars ; i++) { + c = (*bcd >> (i << 2)) & 0xf; + c = c > 9 ? 9 : c; /* force to bcd just in case */ + for (j=0 ; j < i ; j++) + c = c * 10; + dec += c; + } + + /* Do our increment/decrement */ + dec += inc; + *bcd = 0; + + /* Convert back to BCD */ + for (i=0 ; i < chars ; i++) { + for (c=1,j=0 ; j < i ; j++) + c = c * 10; + c = (dec / c) % 10; + *bcd += c << (i << 2); + } + return init; +} + +static void do_usb_entry_multi(struct module *mod, void *symval) +{ + unsigned int devlo, devhi; + unsigned char chi, clo, max; + int ndigits; + + DEF_FIELD(symval, usb_device_id, match_flags); + DEF_FIELD(symval, usb_device_id, idVendor); + DEF_FIELD(symval, usb_device_id, idProduct); + DEF_FIELD(symval, usb_device_id, bcdDevice_lo); + DEF_FIELD(symval, usb_device_id, bcdDevice_hi); + DEF_FIELD(symval, usb_device_id, bDeviceClass); + DEF_FIELD(symval, usb_device_id, bInterfaceClass); + + devlo = match_flags & USB_DEVICE_ID_MATCH_DEV_LO ? + bcdDevice_lo : 0x0U; + devhi = match_flags & USB_DEVICE_ID_MATCH_DEV_HI ? + bcdDevice_hi : ~0x0U; + + /* Figure out if this entry is in bcd or hex format */ + max = 0x9; /* Default to decimal format */ + for (ndigits = 0 ; ndigits < sizeof(bcdDevice_lo) * 2 ; ndigits++) { + clo = (devlo >> (ndigits << 2)) & 0xf; + chi = ((devhi > 0x9999 ? 0x9999 : devhi) >> (ndigits << 2)) & 0xf; + if (clo > max || chi > max) { + max = 0xf; + break; + } + } + + /* + * Some modules (visor) have empty slots as placeholder for + * run-time specification that results in catch-all alias + */ + if (!(idVendor | idProduct | bDeviceClass | bInterfaceClass)) + return; + + /* Convert numeric bcdDevice range into fnmatch-able pattern(s) */ + for (ndigits = sizeof(bcdDevice_lo) * 2 - 1; devlo <= devhi; ndigits--) { + clo = devlo & 0xf; + chi = devhi & 0xf; + if (chi > max) /* If we are in bcd mode, truncate if necessary */ + chi = max; + devlo >>= 4; + devhi >>= 4; + + if (devlo == devhi || !ndigits) { + do_usb_entry(symval, devlo, ndigits, clo, chi, max, mod); + break; + } + + if (clo > 0x0) + do_usb_entry(symval, + incbcd(&devlo, 1, max, + sizeof(bcdDevice_lo) * 2), + ndigits, clo, max, max, mod); + + if (chi < max) + do_usb_entry(symval, + incbcd(&devhi, -1, max, + sizeof(bcdDevice_lo) * 2), + ndigits, 0x0, chi, max, mod); + } +} + +static void do_of_entry(struct module *mod, void *symval) +{ + char alias[500]; + int len; + char *tmp; + + DEF_FIELD_ADDR(symval, of_device_id, name); + DEF_FIELD_ADDR(symval, of_device_id, type); + DEF_FIELD_ADDR(symval, of_device_id, compatible); + + len = sprintf(alias, "of:N%sT%s", (*name)[0] ? *name : "*", + (*type)[0] ? *type : "*"); + + if ((*compatible)[0]) + sprintf(&alias[len], "%sC%s", (*type)[0] ? "*" : "", + *compatible); + + /* Replace all whitespace with underscores */ + for (tmp = alias; tmp && *tmp; tmp++) + if (isspace(*tmp)) + *tmp = '_'; + + module_alias_printf(mod, false, "%s", alias); + module_alias_printf(mod, false, "%sC*", alias); +} + +/* Looks like: hid:bNvNpN */ +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); + + 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); + + module_alias_printf(mod, false, "hid:%s", alias); +} + +/* Looks like: ieee1394:venNmoNspNverN */ +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); + + ADD(alias, "ven", match_flags & IEEE1394_MATCH_VENDOR_ID, + vendor_id); + ADD(alias, "mo", match_flags & IEEE1394_MATCH_MODEL_ID, + model_id); + ADD(alias, "sp", match_flags & IEEE1394_MATCH_SPECIFIER_ID, + specifier_id); + ADD(alias, "ver", match_flags & IEEE1394_MATCH_VERSION, + version); + + module_alias_printf(mod, true, "ieee1394:%s", alias); +} + +/* Looks like: pci:vNdNsvNsdNbcNscNiN or <prefix>_pci:vNdNsvNsdNbcNscNiN. */ +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; + + DEF_FIELD(symval, pci_device_id, vendor); + DEF_FIELD(symval, pci_device_id, device); + DEF_FIELD(symval, pci_device_id, subvendor); + DEF_FIELD(symval, pci_device_id, subdevice); + DEF_FIELD(symval, pci_device_id, class); + DEF_FIELD(symval, pci_device_id, class_mask); + DEF_FIELD(symval, pci_device_id, override_only); + + switch (override_only) { + case 0: + strcpy(alias, "pci:"); + break; + case PCI_ID_F_VFIO_DRIVER_OVERRIDE: + strcpy(alias, "vfio_pci:"); + break; + default: + warn("Unknown PCI driver_override alias %08X\n", + override_only); + } + + ADD(alias, "v", vendor != PCI_ANY_ID, vendor); + ADD(alias, "d", device != PCI_ANY_ID, device); + ADD(alias, "sv", subvendor != PCI_ANY_ID, subvendor); + ADD(alias, "sd", subdevice != PCI_ANY_ID, subdevice); + + baseclass = (class) >> 16; + baseclass_mask = (class_mask) >> 16; + subclass = (class) >> 8; + subclass_mask = (class_mask) >> 8; + interface = class; + interface_mask = class_mask; + + if ((baseclass_mask != 0 && baseclass_mask != 0xFF) + || (subclass_mask != 0 && subclass_mask != 0xFF) + || (interface_mask != 0 && interface_mask != 0xFF)) { + warn("Can't handle masks in %s:%04X\n", + 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); + + module_alias_printf(mod, true, "%s", alias); +} + +/* looks like: "ccw:tNmNdtNdmN" */ +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); + + ADD(alias, "t", match_flags&CCW_DEVICE_ID_MATCH_CU_TYPE, + cu_type); + ADD(alias, "m", match_flags&CCW_DEVICE_ID_MATCH_CU_MODEL, + cu_model); + ADD(alias, "dt", match_flags&CCW_DEVICE_ID_MATCH_DEVICE_TYPE, + dev_type); + ADD(alias, "dm", match_flags&CCW_DEVICE_ID_MATCH_DEVICE_MODEL, + dev_model); + + module_alias_printf(mod, true, "ccw:%s", alias); +} + +/* looks like: "ap:tN" */ +static void do_ap_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, ap_device_id, dev_type); + + module_alias_printf(mod, false, "ap:t%02X*", dev_type); +} + +/* looks like: "css:tN" */ +static void do_css_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, css_device_id, type); + + module_alias_printf(mod, false, "css:t%01X", type); +} + +/* Looks like: "serio:tyNprNidNexN" */ +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); + + 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); + + module_alias_printf(mod, true, "serio:%s", alias); +} + +/* looks like: "acpi:ACPI0003" or "acpi:PNP0C0B" or "acpi:LNXVIDEO" or + * "acpi:bbsspp" (bb=base-class, ss=sub-class, pp=prog-if) + * + * NOTE: Each driver should use one of the following : _HID, _CIDs + * or _CLS. Also, bb, ss, and pp can be substituted with ?? + * as don't care byte. + */ +static void do_acpi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, acpi_device_id, id); + DEF_FIELD(symval, acpi_device_id, cls); + DEF_FIELD(symval, acpi_device_id, cls_msk); + + if ((*id)[0]) + module_alias_printf(mod, false, "acpi*:%s:*", *id); + else { + char alias[256]; + int i, byte_shift, cnt = 0; + unsigned int msk; + + for (i = 1; i <= 3; i++) { + byte_shift = 8 * (3-i); + msk = (cls_msk >> byte_shift) & 0xFF; + if (msk) + sprintf(&alias[cnt], "%02x", + (cls >> byte_shift) & 0xFF); + else + sprintf(&alias[cnt], "??"); + cnt += 2; + } + module_alias_printf(mod, false, "acpi*:%s:*", alias); + } +} + +/* looks like: "pnp:dD" */ +static void do_pnp_device_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, pnp_device_id, id); + char acpi_id[sizeof(*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_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, pnp_card_device_id, devs); + + for (unsigned int i = 0; i < PNP_MAX_DEVICES; i++) { + const char *id = (char *)(*devs)[i].id; + char acpi_id[PNP_ID_LEN]; + + if (!id[0]) + break; + + /* fix broken pnp bus lowercasing */ + for (unsigned int j = 0; j < sizeof(acpi_id); j++) + acpi_id[j] = toupper(id[j]); + + /* 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 void do_pcmcia_entry(struct module *mod, void *symval) +{ + 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); + DEF_FIELD(symval, pcmcia_device_id, func_id); + DEF_FIELD(symval, pcmcia_device_id, function); + DEF_FIELD(symval, pcmcia_device_id, device_no); + DEF_FIELD_ADDR(symval, pcmcia_device_id, prod_id_hash); + + ADD(alias, "m", match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID, + manf_id); + ADD(alias, "c", match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID, + card_id); + ADD(alias, "f", match_flags & PCMCIA_DEV_ID_MATCH_FUNC_ID, + func_id); + ADD(alias, "fn", match_flags & PCMCIA_DEV_ID_MATCH_FUNCTION, + 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, + 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)); + + module_alias_printf(mod, true, "pcmcia:%s", 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); + + sprintf(alias, "vio:T%sS%s", (*type)[0] ? *type : "*", + (*compat)[0] ? *compat : "*"); + + /* Replace all whitespace with underscores */ + for (tmp = alias; tmp && *tmp; tmp++) + if (isspace (*tmp)) + *tmp = '_'; + + module_alias_printf(mod, true, "%s", alias); +} + +static void __attribute__((format(printf, 3, 4))) +alias_append(char *alias, size_t size, const char *fmt, ...) +{ + size_t len = strlen(alias); + va_list args; + int n; + + if (len >= size) + fatal("alias buffer (%zu) overflow before append\n", size); + + va_start(args, fmt); + n = vsnprintf(alias + len, size - len, fmt, args); + va_end(args); + + if (n < 0 || (size_t)n >= size - len) + fatal("alias buffer (%zu) overflow on append (need %d, have %zu)\n", + size, n, size - len); +} + +static void do_input(char *alias, size_t size, + kernel_ulong_t *arr, unsigned int min, unsigned int max) +{ + unsigned int i; + + for (i = min; i <= max; i++) + if (get_unaligned_native(arr + i / BITS_PER_LONG) & + (1ULL << (i % BITS_PER_LONG))) + alias_append(alias, size, "%X,*", i); +} + +/* input:b0v0p0e0-eXkXrXaXmXlXsXfXwX where X is comma-separated %02X. */ +static void do_input_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + const size_t sizeof_alias = sizeof(alias); + + DEF_FIELD(symval, input_device_id, flags); + DEF_FIELD(symval, input_device_id, bustype); + DEF_FIELD(symval, input_device_id, vendor); + DEF_FIELD(symval, input_device_id, product); + DEF_FIELD(symval, input_device_id, version); + DEF_FIELD_ADDR(symval, input_device_id, evbit); + DEF_FIELD_ADDR(symval, input_device_id, keybit); + DEF_FIELD_ADDR(symval, input_device_id, relbit); + DEF_FIELD_ADDR(symval, input_device_id, absbit); + DEF_FIELD_ADDR(symval, input_device_id, mscbit); + DEF_FIELD_ADDR(symval, input_device_id, ledbit); + DEF_FIELD_ADDR(symval, input_device_id, sndbit); + DEF_FIELD_ADDR(symval, input_device_id, ffbit); + DEF_FIELD_ADDR(symval, input_device_id, swbit); + + 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); + ADD(alias, "e", flags & INPUT_DEVICE_ID_MATCH_VERSION, version); + + alias_append(alias, sizeof_alias, "-e*"); + if (flags & INPUT_DEVICE_ID_MATCH_EVBIT) + do_input(alias, sizeof_alias, *evbit, 0, INPUT_DEVICE_ID_EV_MAX); + alias_append(alias, sizeof_alias, "k*"); + if (flags & INPUT_DEVICE_ID_MATCH_KEYBIT) + do_input(alias, sizeof_alias, *keybit, + INPUT_DEVICE_ID_KEY_MIN_INTERESTING, + INPUT_DEVICE_ID_KEY_MAX); + alias_append(alias, sizeof_alias, "r*"); + if (flags & INPUT_DEVICE_ID_MATCH_RELBIT) + do_input(alias, sizeof_alias, *relbit, 0, INPUT_DEVICE_ID_REL_MAX); + alias_append(alias, sizeof_alias, "a*"); + if (flags & INPUT_DEVICE_ID_MATCH_ABSBIT) + do_input(alias, sizeof_alias, *absbit, 0, INPUT_DEVICE_ID_ABS_MAX); + alias_append(alias, sizeof_alias, "m*"); + if (flags & INPUT_DEVICE_ID_MATCH_MSCIT) + do_input(alias, sizeof_alias, *mscbit, 0, INPUT_DEVICE_ID_MSC_MAX); + alias_append(alias, sizeof_alias, "l*"); + if (flags & INPUT_DEVICE_ID_MATCH_LEDBIT) + do_input(alias, sizeof_alias, *ledbit, 0, INPUT_DEVICE_ID_LED_MAX); + alias_append(alias, sizeof_alias, "s*"); + if (flags & INPUT_DEVICE_ID_MATCH_SNDBIT) + do_input(alias, sizeof_alias, *sndbit, 0, INPUT_DEVICE_ID_SND_MAX); + alias_append(alias, sizeof_alias, "f*"); + if (flags & INPUT_DEVICE_ID_MATCH_FFBIT) + do_input(alias, sizeof_alias, *ffbit, 0, INPUT_DEVICE_ID_FF_MAX); + alias_append(alias, sizeof_alias, "w*"); + if (flags & INPUT_DEVICE_ID_MATCH_SWBIT) + do_input(alias, sizeof_alias, *swbit, 0, INPUT_DEVICE_ID_SW_MAX); + + module_alias_printf(mod, false, "input:%s", alias); +} + +static void do_eisa_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, eisa_device_id, sig); + module_alias_printf(mod, false, EISA_DEVICE_MODALIAS_FMT "*", *sig); +} + +/* Looks like: parisc:tNhvNrevNsvN */ +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); + + 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); + + module_alias_printf(mod, true, "parisc:%s", alias); +} + +/* Looks like: sdio:cNvNdN. */ +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); + + 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); + + module_alias_printf(mod, true, "sdio:%s", alias); +} + +/* Looks like: ssb:vNidNrevN. */ +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); + + ADD(alias, "v", vendor != SSB_ANY_VENDOR, vendor); + ADD(alias, "id", coreid != SSB_ANY_ID, coreid); + ADD(alias, "rev", revision != SSB_ANY_REV, revision); + + module_alias_printf(mod, true, "ssb:%s", alias); +} + +/* Looks like: bcma:mNidNrevNclN. */ +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); + + 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); + + module_alias_printf(mod, true, "bcma:%s", alias); +} + +/* Looks like: virtio:dNvN */ +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); + + ADD(alias, "d", device != VIRTIO_DEV_ANY_ID, device); + ADD(alias, "v", vendor != VIRTIO_DEV_ANY_ID, vendor); + + module_alias_printf(mod, true, "virtio:%s", alias); +} + +/* + * Looks like: vmbus:guid + * Each byte of the guid will be represented by two hex characters + * in the name. + */ +static void do_vmbus_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, hv_vmbus_device_id, guid); + char guid_name[sizeof(*guid) * 2 + 1]; + + for (int i = 0; i < sizeof(*guid); i++) + sprintf(&guid_name[i * 2], "%02x", guid->b[i]); + + module_alias_printf(mod, false, "vmbus:%s", guid_name); +} + +/* Looks like: rpmsg:S */ +static void do_rpmsg_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, rpmsg_device_id, name); + + module_alias_printf(mod, false, RPMSG_DEVICE_MODALIAS_FMT, *name); +} + +/* Looks like: i2c:S */ +static void do_i2c_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, i2c_device_id, name); + + module_alias_printf(mod, false, I2C_MODULE_PREFIX "%s", *name); +} + +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); + + 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); + + 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 void do_spi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, spi_device_id, name); + + module_alias_printf(mod, false, SPI_MODULE_PREFIX "%s", *name); +} + +static const struct dmifield { + const char *prefix; + int field; +} dmi_fields[] = { + { "bvn", DMI_BIOS_VENDOR }, + { "bvr", DMI_BIOS_VERSION }, + { "bd", DMI_BIOS_DATE }, + { "br", DMI_BIOS_RELEASE }, + { "efr", DMI_EC_FIRMWARE_RELEASE }, + { "svn", DMI_SYS_VENDOR }, + { "pn", DMI_PRODUCT_NAME }, + { "pvr", DMI_PRODUCT_VERSION }, + { "rvn", DMI_BOARD_VENDOR }, + { "rn", DMI_BOARD_NAME }, + { "rvr", DMI_BOARD_VERSION }, + { "cvn", DMI_CHASSIS_VENDOR }, + { "ct", DMI_CHASSIS_TYPE }, + { "cvr", DMI_CHASSIS_VERSION }, + { NULL, DMI_NONE } +}; + +static void dmi_ascii_filter(char *d, size_t avail, const char *s) +{ + /* Filter out characters we don't want to see in the modalias string */ + for (; *s; s++) + if (*s > ' ' && *s < 127 && *s != ':') { + if (avail <= 1) + fatal("%s: alias buffer overflow\n", __func__); + *(d++) = *s; + avail--; + } + + *d = 0; +} + + +static void do_dmi_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + const size_t sizeof_alias = sizeof(alias); + size_t len; + int i, j; + DEF_FIELD_ADDR(symval, dmi_system_id, matches); + + for (i = 0; i < ARRAY_SIZE(dmi_fields); i++) { + for (j = 0; j < 4; j++) { + if ((*matches)[j].slot && + (*matches)[j].slot == dmi_fields[i].field) { + alias_append(alias, sizeof_alias, ":%s*", + dmi_fields[i].prefix); + len = strlen(alias); + dmi_ascii_filter(alias + len, sizeof_alias - len, + (*matches)[j].substr); + alias_append(alias, sizeof_alias, "*"); + } + } + } + + module_alias_printf(mod, false, "dmi*%s:", alias); +} + +static void do_platform_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, platform_device_id, name); + + module_alias_printf(mod, false, PLATFORM_MODULE_PREFIX "%s", *name); +} + +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); + + for (i = 0; i < 32; i++) { + if (!((phy_id_mask >> (31-i)) & 1)) + id[i] = '?'; + else if ((phy_id >> (31-i)) & 1) + id[i] = '1'; + else + id[i] = '0'; + } + + /* Terminate the string */ + id[32] = '\0'; + + module_alias_printf(mod, false, MDIO_MODULE_PREFIX "%s", id); +} + +/* Looks like: zorro:iN. */ +static void do_zorro_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + DEF_FIELD(symval, zorro_device_id, id); + + ADD(alias, "i", id != ZORRO_WILDCARD, id); + + module_alias_printf(mod, false, "zorro:%s", alias); +} + +/* looks like: "pnp:dD" */ +static void do_isapnp_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, isapnp_device_id, vendor); + DEF_FIELD(symval, isapnp_device_id, function); + 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); +} + +/* Looks like: "ipack:fNvNdN". */ +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); + + ADD(alias, "f", format != IPACK_ANY_FORMAT, format); + ADD(alias, "v", vendor != IPACK_ANY_ID, vendor); + ADD(alias, "d", device != IPACK_ANY_ID, device); + + module_alias_printf(mod, true, "ipack:%s", alias); +} + +/* + * Append a match expression for a single masked hex digit. + * outp points to a pointer to the character at which to append. + * *outp is updated on return to point just after the appended text, + * to facilitate further appending. + */ +static void append_nibble_mask(char **outp, + unsigned int nibble, unsigned int mask) +{ + char *p = *outp; + unsigned int i; + + switch (mask) { + case 0: + *p++ = '?'; + break; + + case 0xf: + p += sprintf(p, "%X", nibble); + break; + + default: + /* + * Dumbly emit a match pattern for all possible matching + * digits. This could be improved in some cases using ranges, + * but it has the advantage of being trivially correct, and is + * often optimal. + */ + *p++ = '['; + for (i = 0; i < 0x10; i++) + if ((i & mask) == nibble) + p += sprintf(p, "%X", i); + *p++ = ']'; + } + + /* Ensure that the string remains NUL-terminated: */ + *p = '\0'; + + /* Advance the caller's end-of-string pointer: */ + *outp = p; +} + +/* + * looks like: "amba:dN" + * + * N is exactly 8 digits, where each is an upper-case hex digit, or + * a ? or [] pattern matching exactly one digit. + */ +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); + DEF_FIELD(symval, amba_id, mask); + + 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", + mod->name, id, mask); + + for (digit = 0; digit < 8; digit++) + append_nibble_mask(&p, + (id >> (4 * (7 - digit))) & 0xf, + (mask >> (4 * (7 - digit))) & 0xf); + + module_alias_printf(mod, false, "amba:d%s", alias); +} + +/* + * looks like: "mipscdmm:tN" + * + * N is exactly 2 digits, where each is an upper-case hex digit, or + * a ? or [] pattern matching exactly one digit. + */ +static void do_mips_cdmm_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, mips_cdmm_device_id, type); + + module_alias_printf(mod, false, "mipscdmm:t%02X*", type); +} + +/* LOOKS like cpu:type:x86,venVVVVfamFFFFmodMMMM:feature:*,FEAT,* + * All fields are numbers. It would be nicer to use strings for vendor + * and feature, but getting those out of the build system here is too + * complicated. + */ + +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); + + 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); + + module_alias_printf(mod, false, "cpu:type:x86,%s", alias); +} + +/* LOOKS like cpu:type:*:feature:*FEAT* */ +static void do_cpu_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, cpu_feature, feature); + + module_alias_printf(mod, false, "cpu:type:*:feature:*%04X*", feature); +} + +/* Looks like: mcb:16zN */ +static void do_mcb_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, mcb_device_id, device); + + module_alias_printf(mod, false, "mcb:16z%03d", device); +} + +/* Looks like: mei:S:uuid:N:* */ +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); + + add_uuid(alias, *uuid); + ADD(alias, ":", version != MEI_CL_VERSION_ANY, version); + + module_alias_printf(mod, false, MEI_CL_MODULE_PREFIX "%s:%s:*", + (*name)[0] ? *name : "*", alias); +} + +/* Looks like: rapidio:vNdNavNadN */ +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); + + 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); + + module_alias_printf(mod, true, "rapidio:%s", alias); +} + +/* Looks like: ulpi:vNpN */ +static void do_ulpi_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, ulpi_device_id, vendor); + DEF_FIELD(symval, ulpi_device_id, product); + + module_alias_printf(mod, false, "ulpi:v%04xp%04x", vendor, product); +} + +/* Looks like: hdaudio:vNrNaN */ +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); + + ADD(alias, "v", vendor_id != 0, vendor_id); + ADD(alias, "r", rev_id != 0, rev_id); + ADD(alias, "a", api_version != 0, api_version); + + module_alias_printf(mod, true, "hdaudio:%s", alias); +} + +/* Looks like: sdw:mNpNvNcN */ +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); + + 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); + + module_alias_printf(mod, true, "sdw:%s", alias); +} + +/* Looks like: fsl-mc:vNdN */ +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); + + module_alias_printf(mod, false, "fsl-mc:v%08Xd%s", vendor, *obj_type); +} + +/* Looks like: tbsvc:kSpNvNrN */ +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); + + if (match_flags & TBSVC_MATCH_PROTOCOL_KEY) + sprintf(alias + strlen(alias), "k%s", *protocol_key); + else + strcat(alias + strlen(alias), "k*"); + ADD(alias, "p", match_flags & TBSVC_MATCH_PROTOCOL_ID, protocol_id); + ADD(alias, "v", match_flags & TBSVC_MATCH_PROTOCOL_VERSION, + protocol_version); + ADD(alias, "r", match_flags & TBSVC_MATCH_PROTOCOL_REVISION, + protocol_revision); + + module_alias_printf(mod, true, "tbsvc:%s", alias); +} + +/* Looks like: typec:idN */ +static void do_typec_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, typec_device_id, svid); + + module_alias_printf(mod, false, "typec:id%04X", svid); +} + +/* Looks like: tee:uuid */ +static void do_tee_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, tee_client_device_id, uuid); + + 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]); +} + +/* Looks like: wmi:guid */ +static void do_wmi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, wmi_device_id, guid_string); + char result[sizeof(*guid_string)]; + int i; + + if (strlen(*guid_string) != UUID_STRING_LEN) { + warn("Invalid WMI device id 'wmi:%s' in '%s'\n", + *guid_string, mod->name); + return; + } + + for (i = 0; i < UUID_STRING_LEN; i++) { + char value = (*guid_string)[i]; + bool valid = false; + + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (value == '-') + valid = true; + } else { + if (isxdigit(value)) + valid = true; + } + + if (!valid) { + warn("Invalid character %c inside WMI GUID string '%s' in '%s'\n", + value, *guid_string, mod->name); + return; + } + + /* Some GUIDs from BMOF definitions contain lowercase characters */ + result[i] = toupper(value); + } + + result[i] = '\0'; + + module_alias_printf(mod, false, WMI_MODULE_PREFIX "%s", result); +} + +/* Looks like: mhi:S */ +static void do_mhi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, mhi_device_id, chan); + module_alias_printf(mod, false, MHI_DEVICE_MODALIAS_FMT, *chan); +} + +/* Looks like: mhi_ep:S */ +static void do_mhi_ep_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, mhi_device_id, chan); + + module_alias_printf(mod, false, MHI_EP_DEVICE_MODALIAS_FMT, *chan); +} + +/* Looks like: ishtp:{guid} */ +static void do_ishtp_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + DEF_FIELD_ADDR(symval, ishtp_device_id, guid); + + add_guid(alias, *guid); + + module_alias_printf(mod, false, ISHTP_MODULE_PREFIX "{%s}", alias); +} + +static void do_auxiliary_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, auxiliary_device_id, name); + + module_alias_printf(mod, false, AUXILIARY_MODULE_PREFIX "%s", *name); +} + +/* + * Looks like: ssam:dNcNtNiNfN + * + * N is exactly 2 digits, where each is an upper-case hex digit. + */ +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); + DEF_FIELD(symval, ssam_device_id, target); + DEF_FIELD(symval, ssam_device_id, instance); + DEF_FIELD(symval, ssam_device_id, function); + + 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); + + module_alias_printf(mod, false, "ssam:d%02Xc%02X%s", + domain, category, alias); +} + +/* Looks like: dfl:tNfN */ +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); + + module_alias_printf(mod, true, "dfl:t%04Xf%04X", type, feature_id); +} + +/* Looks like: cdx:vNdN */ +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); + DEF_FIELD(symval, cdx_device_id, subdevice); + DEF_FIELD(symval, cdx_device_id, class); + DEF_FIELD(symval, cdx_device_id, class_mask); + DEF_FIELD(symval, cdx_device_id, override_only); + + switch (override_only) { + case 0: + strcpy(alias, "cdx:"); + break; + case CDX_ID_F_VFIO_DRIVER_OVERRIDE: + strcpy(alias, "vfio_cdx:"); + break; + default: + warn("Unknown CDX driver_override alias %08X\n", + override_only); + return; + } + + ADD(alias, "v", vendor != CDX_ANY_ID, vendor); + ADD(alias, "d", device != CDX_ANY_ID, device); + ADD(alias, "sv", subvendor != CDX_ANY_ID, subvendor); + ADD(alias, "sd", subdevice != CDX_ANY_ID, subdevice); + ADD(alias, "c", class_mask == 0xFFFFFF, class); + + module_alias_printf(mod, false, "%s", alias); +} + +static void do_vchiq_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, vchiq_device_id, name); + + module_alias_printf(mod, false, "vchiq:%s", *name); +} + +/* Looks like: coreboot:tN */ +static void do_coreboot_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, coreboot_device_id, tag); + + module_alias_printf(mod, false, "coreboot:t%08X", tag); +} + +/* Does namelen bytes of name exactly match the symbol? */ +static bool sym_is(const char *name, unsigned namelen, const char *symbol) +{ + if (namelen != strlen(symbol)) + return false; + + return memcmp(name, symbol, namelen) == 0; +} + +static void do_table(const char *name, void *symval, unsigned long size, + unsigned long id_size, + const char *device_id, + void (*do_entry)(struct module *mod, void *symval), + struct module *mod) +{ + unsigned int i; + + 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; + } + + /* 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[] = { + {"hid", SIZE_hid_device_id, do_hid_entry}, + {"ieee1394", SIZE_ieee1394_device_id, do_ieee1394_entry}, + {"pci", SIZE_pci_device_id, do_pci_entry}, + {"ccw", SIZE_ccw_device_id, do_ccw_entry}, + {"ap", SIZE_ap_device_id, do_ap_entry}, + {"css", SIZE_css_device_id, do_css_entry}, + {"serio", SIZE_serio_device_id, do_serio_entry}, + {"acpi", SIZE_acpi_device_id, do_acpi_entry}, + {"pcmcia", SIZE_pcmcia_device_id, do_pcmcia_entry}, + {"vio", SIZE_vio_device_id, do_vio_entry}, + {"input", SIZE_input_device_id, do_input_entry}, + {"eisa", SIZE_eisa_device_id, do_eisa_entry}, + {"parisc", SIZE_parisc_device_id, do_parisc_entry}, + {"sdio", SIZE_sdio_device_id, do_sdio_entry}, + {"ssb", SIZE_ssb_device_id, do_ssb_entry}, + {"bcma", SIZE_bcma_device_id, do_bcma_entry}, + {"virtio", SIZE_virtio_device_id, do_virtio_entry}, + {"vmbus", SIZE_hv_vmbus_device_id, do_vmbus_entry}, + {"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}, + {"mdio", SIZE_mdio_device_id, do_mdio_entry}, + {"zorro", SIZE_zorro_device_id, do_zorro_entry}, + {"isapnp", SIZE_isapnp_device_id, do_isapnp_entry}, + {"ipack", SIZE_ipack_device_id, do_ipack_entry}, + {"amba", SIZE_amba_id, do_amba_entry}, + {"mipscdmm", SIZE_mips_cdmm_device_id, do_mips_cdmm_entry}, + {"x86cpu", SIZE_x86_cpu_id, do_x86cpu_entry}, + {"cpu", SIZE_cpu_feature, do_cpu_entry}, + {"mcb", SIZE_mcb_device_id, do_mcb_entry}, + {"mei", SIZE_mei_cl_device_id, do_mei_entry}, + {"rapidio", SIZE_rio_device_id, do_rio_entry}, + {"ulpi", SIZE_ulpi_device_id, do_ulpi_entry}, + {"hdaudio", SIZE_hda_device_id, do_hda_entry}, + {"sdw", SIZE_sdw_device_id, do_sdw_entry}, + {"fslmc", SIZE_fsl_mc_device_id, do_fsl_mc_entry}, + {"tbsvc", SIZE_tb_service_id, do_tbsvc_entry}, + {"typec", SIZE_typec_device_id, do_typec_entry}, + {"tee", SIZE_tee_client_device_id, do_tee_entry}, + {"wmi", SIZE_wmi_device_id, do_wmi_entry}, + {"mhi", SIZE_mhi_device_id, do_mhi_entry}, + {"mhi_ep", SIZE_mhi_device_id, do_mhi_ep_entry}, + {"auxiliary", SIZE_auxiliary_device_id, do_auxiliary_entry}, + {"ssam", SIZE_ssam_device_id, do_ssam_entry}, + {"dfl", SIZE_dfl_device_id, do_dfl_entry}, + {"ishtp", SIZE_ishtp_device_id, do_ishtp_entry}, + {"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. + * At this time, we cannot write the actual output C source yet, + * so we write into the mod->dev_table_buf buffer. */ +void handle_moddevtable(struct module *mod, struct elf_info *info, + Elf_Sym *sym, const char *symname) +{ + void *symval; + char *zeros = NULL; + const char *type, *name, *modname; + size_t typelen, modnamelen; + 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) + return; + + /* We're looking for an object */ + if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT) + return; + + /* All our symbols are of form __mod_device_table__kmod_<modname>__<type>__<name>. */ + if (!strstarts(symname, prefix)) + return; + + modname = strstr(symname, "__kmod_"); + if (!modname) + return; + modname += strlen("__kmod_"); + + type = strstr(modname, "__"); + if (!type) + return; + modnamelen = type - modname; + type += strlen("__"); + + name = strstr(type, "__"); + if (!name) + return; + typelen = name - type; + name += strlen("__"); + + /* Handle all-NULL symbols allocated into .bss */ + if (info->sechdrs[get_secindex(info, sym)].sh_type & SHT_NOBITS) { + zeros = calloc(1, sym->st_size); + symval = zeros; + } else { + symval = sym_get_data(info, sym); + } + + for (int i = 0; i < ARRAY_SIZE(devtable); i++) { + const struct devtable *p = &devtable[i]; + + 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; + } + } + + if (mod->is_vmlinux) { + struct module_alias *alias; + + /* + * If this is vmlinux, record the name of the builtin module. + * Traverse the linked list in the reverse order, and set the + * builtin_modname unless it has already been set in the + * previous call. + */ + list_for_each_entry_reverse(alias, &mod->aliases, node) { + if (alias->builtin_modname) + break; + alias->builtin_modname = xstrndup(modname, modnamelen); + } + } + + free(zeros); +} diff --git a/scripts/mod/mk_elfconfig.c b/scripts/mod/mk_elfconfig.c new file mode 100644 index 000000000..e8cee4e4b --- /dev/null +++ b/scripts/mod/mk_elfconfig.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <elf.h> + +int +main(int argc, char **argv) +{ + unsigned char ei[EI_NIDENT]; + + if (fread(ei, 1, EI_NIDENT, stdin) != EI_NIDENT) { + fprintf(stderr, "Error: input truncated\n"); + return 1; + } + if (memcmp(ei, ELFMAG, SELFMAG) != 0) { + fprintf(stderr, "Error: not ELF\n"); + return 1; + } + switch (ei[EI_CLASS]) { + case ELFCLASS32: + printf("#define KERNEL_ELFCLASS ELFCLASS32\n"); + break; + case ELFCLASS64: + printf("#define KERNEL_ELFCLASS ELFCLASS64\n"); + break; + default: + exit(1); + } + + return 0; +} diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c new file mode 100644 index 000000000..75374c64b --- /dev/null +++ b/scripts/mod/modpost.c @@ -0,0 +1,2420 @@ +/* Postprocess module symbol versions + * + * Copyright 2003 Kai Germaschewski + * Copyright 2002-2004 Rusty Russell, IBM Corporation + * Copyright 2006-2008 Sam Ravnborg + * Based in part on module-init-tools/depmod.c,file2alias + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Usage: modpost vmlinux module1.o module2.o ... + */ + +#define _GNU_SOURCE +#include <elf.h> +#include <fnmatch.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#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 */ +static bool warn_unresolved; + +static int sec_mismatch_count; +static bool sec_mismatch_warn_only = true; +/* Trim EXPORT_SYMBOLs that are unused by in-tree modules */ +static bool trim_unused_exports; + +/* ignore missing files */ +static bool ignore_missing_files; +/* If set to 1, only warn (instead of error) about missing ns imports */ +static bool allow_missing_ns_imports; + +static bool error_occurred; + +static bool extra_warn __attribute__((unused)); + +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.) + */ +#define MAX_UNRESOLVED_REPORTS 10 +static unsigned int nr_unresolved; + +/* In kernel, this size is defined in linux/module.h; + * here we use Elf_Addr instead of long for covering cross-compile + */ + +#define MODULE_NAME_LEN (64 - sizeof(Elf_Addr)) + +void modpost_log(bool is_error, struct module *mod, const char *fmt, ...) +{ + va_list arglist; + + if (is_error) { + fprintf(stderr, "ERROR: "); + error_occurred = true; + } else { + fprintf(stderr, "WARNING: "); + } + + fprintf(stderr, "modpost: "); + + if (mod) + fprintf(stderr, "%s%s: ", mod->name, mod->is_vmlinux ? "" : ".ko"); + + va_start(arglist, fmt); + vfprintf(stderr, fmt, arglist); + va_end(arglist); +} + +#define mod_warn(mod, fmt, args...) modpost_log(false, mod, fmt, ##args) +#define mod_error(mod, fmt, args...) modpost_log(true, mod, fmt, ##args) + +static inline bool strends(const char *str, const char *postfix) +{ + if (strlen(str) < strlen(postfix)) + return false; + + return strcmp(str + strlen(str) - strlen(postfix), postfix) == 0; +} + +/** + * get_basename - return the last part of a pathname. + * + * @path: path to extract the filename from. + */ +const char *get_basename(const char *path) +{ + const char *tail = strrchr(path, '/'); + + return tail ? tail + 1 : path; +} + +char *read_text_file(const char *filename) +{ + struct stat st; + size_t nbytes; + int fd; + char *buf; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + perror(filename); + exit(1); + } + + if (fstat(fd, &st) < 0) { + perror(filename); + exit(1); + } + + buf = xmalloc(st.st_size + 1); + + nbytes = st.st_size; + + while (nbytes) { + ssize_t bytes_read; + + bytes_read = read(fd, buf, nbytes); + if (bytes_read < 0) { + perror(filename); + exit(1); + } + + nbytes -= bytes_read; + } + buf[st.st_size] = '\0'; + + close(fd); + + return buf; +} + +char *get_line(char **stringp) +{ + char *orig = *stringp, *next; + + /* do not return the unwanted extra line at EOF */ + if (!orig || *orig == '\0') + return NULL; + + /* don't use strsep here, it is not available everywhere */ + next = strchr(orig, '\n'); + if (next) + *next++ = '\0'; + + *stringp = next; + + return orig; +} + +/* A list of all modules we processed */ +LIST_HEAD(modules); + +static struct module *find_module(const char *filename, const char *modname) +{ + struct module *mod; + + list_for_each_entry(mod, &modules, list) { + if (!strcmp(mod->dump_file, filename) && + !strcmp(mod->name, modname)) + return mod; + } + return NULL; +} + +static struct module *new_module(const char *name, size_t namelen) +{ + struct module *mod; + + 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'; + mod->is_vmlinux = (strcmp(mod->name, "vmlinux") == 0); + + /* + * Set mod->is_gpl_compatible to true by default. If MODULE_LICENSE() + * is missing, do not check the use for EXPORT_SYMBOL_GPL() because + * modpost will exit with an error anyway. + */ + mod->is_gpl_compatible = true; + + list_add_tail(&mod->list, &modules); + + return mod; +} + +struct symbol { + 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; + unsigned int crc; + bool crc_valid; + bool weak; + bool is_func; + bool is_gpl_only; /* exported by EXPORT_SYMBOL_GPL */ + bool used; /* there exists a user of this symbol */ + char name[]; +}; + +static HASHTABLE_DEFINE(symbol_hashtable, 1U << 10); + +/** + * Allocate a new symbols for use in the hash of exported symbols or + * the list of unresolved symbols per module + **/ +static struct symbol *alloc_symbol(const char *name) +{ + struct symbol *s = xmalloc(sizeof(*s) + strlen(name) + 1); + + memset(s, 0, sizeof(*s)); + strcpy(s->name, name); + + return s; +} + +static uint8_t get_symbol_flags(const struct symbol *sym) +{ + return sym->is_gpl_only ? KSYM_FLAG_GPL_ONLY : 0; +} + +/* For the hash of exported symbols */ +static void hash_add_symbol(struct symbol *sym) +{ + hash_add(symbol_hashtable, &sym->hnode, hash_str(sym->name)); +} + +static void sym_add_unresolved(const char *name, struct module *mod, bool weak) +{ + struct symbol *sym; + + sym = alloc_symbol(name); + sym->weak = weak; + + list_add_tail(&sym->list, &mod->unresolved_symbols); +} + +static struct symbol *sym_find_with_module(const char *name, struct module *mod) +{ + struct symbol *s; + + /* For our purposes, .foo matches foo. PPC64 needs this. */ + if (name[0] == '.') + name++; + + hash_for_each_possible(symbol_hashtable, s, hnode, hash_str(name)) { + if (strcmp(s->name, name) == 0 && (!mod || s->module == mod)) + return s; + } + return NULL; +} + +static struct symbol *find_symbol(const char *name) +{ + return sym_find_with_module(name, NULL); +} + +struct namespace_list { + struct list_head list; + char namespace[]; +}; + +static bool contains_namespace(struct list_head *head, const char *namespace) +{ + struct namespace_list *list; + + /* + * The default namespace is null string "", which is always implicitly + * contained. + */ + if (!namespace[0]) + return true; + + list_for_each_entry(list, head, list) { + if (!strcmp(list->namespace, namespace)) + return true; + } + + return false; +} + +static void add_namespace(struct list_head *head, const char *namespace) +{ + struct namespace_list *ns_entry; + + if (!contains_namespace(head, namespace)) { + ns_entry = xmalloc(sizeof(*ns_entry) + strlen(namespace) + 1); + strcpy(ns_entry->namespace, namespace); + list_add_tail(&ns_entry->list, head); + } +} + +static void *sym_get_data_by_offset(const struct elf_info *info, + unsigned int secindex, unsigned long offset) +{ + Elf_Shdr *sechdr = &info->sechdrs[secindex]; + + return (void *)info->hdr + sechdr->sh_offset + offset; +} + +void *sym_get_data(const struct elf_info *info, const Elf_Sym *sym) +{ + return sym_get_data_by_offset(info, get_secindex(info, sym), + sym->st_value); +} + +static const char *sech_name(const struct elf_info *info, Elf_Shdr *sechdr) +{ + return sym_get_data_by_offset(info, info->secindex_strings, + sechdr->sh_name); +} + +static const char *sec_name(const struct elf_info *info, unsigned int secindex) +{ + /* + * If sym->st_shndx is a special section index, there is no + * corresponding section header. + * Return "" if the index is out of range of info->sechdrs[] array. + */ + if (secindex >= info->num_sections) + return ""; + + return sech_name(info, &info->sechdrs[secindex]); +} + +static struct symbol *sym_add_exported(const char *name, struct module *mod, + bool gpl_only, const char *namespace) +{ + struct symbol *s = find_symbol(name); + + if (s && (!external_module || s->module->is_vmlinux || s->module == mod)) { + mod_error(mod, "symbol '%s' exported twice. Previous export was in %s%s\n", + name, s->module->name, s->module->is_vmlinux ? "" : ".ko"); + } + + s = alloc_symbol(name); + s->module = mod; + s->is_gpl_only = gpl_only; + s->namespace = xstrdup(namespace); + list_add_tail(&s->list, &mod->exported_symbols); + hash_add_symbol(s); + + return s; +} + +static void sym_set_crc(struct symbol *sym, unsigned int crc) +{ + sym->crc = crc; + sym->crc_valid = true; +} + +static void *grab_file(const char *filename, size_t *size) +{ + struct stat st; + void *map = MAP_FAILED; + int fd; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return NULL; + if (fstat(fd, &st)) + goto failed; + + *size = st.st_size; + map = mmap(NULL, *size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); + +failed: + close(fd); + if (map == MAP_FAILED) + return NULL; + return map; +} + +static void release_file(void *file, size_t size) +{ + munmap(file, size); +} + +static int parse_elf(struct elf_info *info, const char *filename) +{ + unsigned int i; + Elf_Ehdr *hdr; + Elf_Shdr *sechdrs; + Elf_Sym *sym; + const char *secstrings; + unsigned int symtab_idx = ~0U, symtab_shndx_idx = ~0U; + + hdr = grab_file(filename, &info->size); + if (!hdr) { + if (ignore_missing_files) { + fprintf(stderr, "%s: %s (ignored)\n", filename, + strerror(errno)); + return 0; + } + perror(filename); + exit(1); + } + info->hdr = hdr; + if (info->size < sizeof(*hdr)) { + /* file too small, assume this is an empty .o file */ + return 0; + } + /* Is this a valid ELF file? */ + if ((hdr->e_ident[EI_MAG0] != ELFMAG0) || + (hdr->e_ident[EI_MAG1] != ELFMAG1) || + (hdr->e_ident[EI_MAG2] != ELFMAG2) || + (hdr->e_ident[EI_MAG3] != ELFMAG3)) { + /* 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); + hdr->e_version = TO_NATIVE(hdr->e_version); + hdr->e_entry = TO_NATIVE(hdr->e_entry); + hdr->e_phoff = TO_NATIVE(hdr->e_phoff); + hdr->e_shoff = TO_NATIVE(hdr->e_shoff); + hdr->e_flags = TO_NATIVE(hdr->e_flags); + hdr->e_ehsize = TO_NATIVE(hdr->e_ehsize); + hdr->e_phentsize = TO_NATIVE(hdr->e_phentsize); + hdr->e_phnum = TO_NATIVE(hdr->e_phnum); + hdr->e_shentsize = TO_NATIVE(hdr->e_shentsize); + hdr->e_shnum = TO_NATIVE(hdr->e_shnum); + hdr->e_shstrndx = TO_NATIVE(hdr->e_shstrndx); + sechdrs = (void *)hdr + hdr->e_shoff; + info->sechdrs = sechdrs; + + /* modpost only works for relocatable objects */ + if (hdr->e_type != ET_REL) + fatal("%s: not relocatable object.", filename); + + /* Check if file offset is correct */ + if (hdr->e_shoff > info->size) + fatal("section header offset=%lu in file '%s' is bigger than filesize=%zu\n", + (unsigned long)hdr->e_shoff, filename, info->size); + + if (hdr->e_shnum == SHN_UNDEF) { + /* + * There are more than 64k sections, + * read count from .sh_size. + */ + info->num_sections = TO_NATIVE(sechdrs[0].sh_size); + } + else { + info->num_sections = hdr->e_shnum; + } + if (hdr->e_shstrndx == SHN_XINDEX) { + info->secindex_strings = TO_NATIVE(sechdrs[0].sh_link); + } + else { + info->secindex_strings = hdr->e_shstrndx; + } + + /* Fix endianness in section headers */ + for (i = 0; i < info->num_sections; i++) { + sechdrs[i].sh_name = TO_NATIVE(sechdrs[i].sh_name); + sechdrs[i].sh_type = TO_NATIVE(sechdrs[i].sh_type); + sechdrs[i].sh_flags = TO_NATIVE(sechdrs[i].sh_flags); + sechdrs[i].sh_addr = TO_NATIVE(sechdrs[i].sh_addr); + sechdrs[i].sh_offset = TO_NATIVE(sechdrs[i].sh_offset); + sechdrs[i].sh_size = TO_NATIVE(sechdrs[i].sh_size); + sechdrs[i].sh_link = TO_NATIVE(sechdrs[i].sh_link); + sechdrs[i].sh_info = TO_NATIVE(sechdrs[i].sh_info); + sechdrs[i].sh_addralign = TO_NATIVE(sechdrs[i].sh_addralign); + sechdrs[i].sh_entsize = TO_NATIVE(sechdrs[i].sh_entsize); + } + /* Find symbol table. */ + secstrings = (void *)hdr + sechdrs[info->secindex_strings].sh_offset; + for (i = 1; i < info->num_sections; i++) { + const char *secname; + int nobits = sechdrs[i].sh_type == SHT_NOBITS; + + if (!nobits && sechdrs[i].sh_offset > info->size) + fatal("%s is truncated. sechdrs[i].sh_offset=%lu > sizeof(*hrd)=%zu\n", + filename, (unsigned long)sechdrs[i].sh_offset, + sizeof(*hdr)); + + secname = secstrings + sechdrs[i].sh_name; + if (strcmp(secname, ".modinfo") == 0) { + if (nobits) + fatal("%s has NOBITS .modinfo\n", filename); + info->modinfo = (void *)hdr + sechdrs[i].sh_offset; + 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) { + unsigned int sh_link_idx; + symtab_idx = i; + info->symtab_start = (void *)hdr + + sechdrs[i].sh_offset; + info->symtab_stop = (void *)hdr + + sechdrs[i].sh_offset + sechdrs[i].sh_size; + sh_link_idx = sechdrs[i].sh_link; + info->strtab = (void *)hdr + + sechdrs[sh_link_idx].sh_offset; + } + + /* 32bit section no. table? ("more than 64k sections") */ + if (sechdrs[i].sh_type == SHT_SYMTAB_SHNDX) { + symtab_shndx_idx = i; + info->symtab_shndx_start = (void *)hdr + + sechdrs[i].sh_offset; + info->symtab_shndx_stop = (void *)hdr + + sechdrs[i].sh_offset + sechdrs[i].sh_size; + } + } + if (!info->symtab_start) + fatal("%s has no symtab?\n", filename); + + /* Fix endianness in symbols */ + for (sym = info->symtab_start; sym < info->symtab_stop; sym++) { + sym->st_shndx = TO_NATIVE(sym->st_shndx); + sym->st_name = TO_NATIVE(sym->st_name); + sym->st_value = TO_NATIVE(sym->st_value); + sym->st_size = TO_NATIVE(sym->st_size); + } + + if (symtab_shndx_idx != ~0U) { + Elf32_Word *p; + if (symtab_idx != sechdrs[symtab_shndx_idx].sh_link) + fatal("%s: SYMTAB_SHNDX has bad sh_link: %u!=%u\n", + filename, sechdrs[symtab_shndx_idx].sh_link, + symtab_idx); + /* Fix endianness */ + for (p = info->symtab_shndx_start; p < info->symtab_shndx_stop; + p++) + *p = TO_NATIVE(*p); + } + + symsearch_init(info); + + return 1; +} + +static void parse_elf_finish(struct elf_info *info) +{ + symsearch_finish(info); + release_file(info->hdr, info->size); +} + +static int ignore_undef_symbol(struct elf_info *info, const char *symname) +{ + /* ignore __this_module, it will be resolved shortly */ + if (strcmp(symname, "__this_module") == 0) + return 1; + /* ignore global offset table */ + if (strcmp(symname, "_GLOBAL_OFFSET_TABLE_") == 0) + return 1; + if (info->hdr->e_machine == EM_PPC) + /* Special register function linked on all modules during final link of .ko */ + if (strstarts(symname, "_restgpr_") || + strstarts(symname, "_savegpr_") || + strstarts(symname, "_rest32gpr_") || + strstarts(symname, "_save32gpr_") || + strstarts(symname, "_restvr_") || + strstarts(symname, "_savevr_")) + return 1; + if (info->hdr->e_machine == EM_PPC64) + /* Special register function linked on all modules during final link of .ko */ + if (strstarts(symname, "_restgpr0_") || + strstarts(symname, "_savegpr0_") || + strstarts(symname, "_restgpr1_") || + strstarts(symname, "_savegpr1_") || + strstarts(symname, "_restfpr_") || + strstarts(symname, "_savefpr_") || + strstarts(symname, "_restvr_") || + strstarts(symname, "_savevr_") || + strcmp(symname, ".TOC.") == 0) + return 1; + + /* ignore linker-created section bounds variables */ + if (strstarts(symname, "__start_") || strstarts(symname, "__stop_")) + return 1; + + /* Do not ignore this symbol */ + return 0; +} + +static void handle_symbol(struct module *mod, struct elf_info *info, + const Elf_Sym *sym, const char *symname) +{ + switch (sym->st_shndx) { + case SHN_COMMON: + if (strstarts(symname, "__gnu_lto_")) { + /* Should warn here, but modpost runs before the linker */ + } else + mod_warn(mod, "'%s' is COMMON symbol\n", symname); + break; + case SHN_UNDEF: + /* undefined symbol */ + if (ELF_ST_BIND(sym->st_info) != STB_GLOBAL && + ELF_ST_BIND(sym->st_info) != STB_WEAK) + break; + if (ignore_undef_symbol(info, symname)) + break; + if (info->hdr->e_machine == EM_SPARC || + info->hdr->e_machine == EM_SPARCV9) { + /* Ignore register directives. */ + if (ELF_ST_TYPE(sym->st_info) == STT_SPARC_REGISTER) + break; + if (symname[0] == '.') { + char *munged = xstrdup(symname); + munged[0] = '_'; + munged[1] = toupper(munged[1]); + symname = munged; + } + } + + sym_add_unresolved(symname, mod, + ELF_ST_BIND(sym->st_info) == STB_WEAK); + break; + default: + if (strcmp(symname, "init_module") == 0) + mod->has_init = true; + if (strcmp(symname, "cleanup_module") == 0) + mod->has_cleanup = true; + break; + } +} + +/** + * Parse tag=value strings from .modinfo section + **/ +static char *next_string(char *string, unsigned long *secsize) +{ + /* Skip non-zero chars */ + while (string[0]) { + string++; + if ((*secsize)-- <= 1) + return NULL; + } + + /* Skip any zero padding. */ + while (!string[0]) { + string++; + if ((*secsize)-- <= 1) + return NULL; + } + return string; +} + +static char *get_next_modinfo(struct elf_info *info, const char *tag, + char *prev) +{ + char *p; + unsigned int taglen = strlen(tag); + char *modinfo = info->modinfo; + unsigned long size = info->modinfo_len; + + if (prev) { + size -= prev - modinfo; + modinfo = next_string(prev, &size); + } + + for (p = modinfo; p; p = next_string(p, &size)) { + if (strncmp(p, tag, taglen) == 0 && p[taglen] == '=') + return p + taglen + 1; + } + return NULL; +} + +static char *get_modinfo(struct elf_info *info, const char *tag) + +{ + return get_next_modinfo(info, tag, NULL); +} + +static const char *sym_name(struct elf_info *elf, Elf_Sym *sym) +{ + return sym ? elf->strtab + sym->st_name : ""; +} + +/* + * Check whether the 'string' argument matches one of the 'patterns', + * an array of shell wildcard patterns (glob). + * + * Return true is there is a match. + */ +static bool match(const char *string, const char *const patterns[]) +{ + const char *pattern; + + while ((pattern = *patterns++)) { + if (!fnmatch(pattern, string, 0)) + return true; + } + + return false; +} + +/* useful to pass patterns to match() directly */ +#define PATTERNS(...) \ + ({ \ + static const char *const patterns[] = {__VA_ARGS__, NULL}; \ + patterns; \ + }) + +/* sections that we do not want to do full section mismatch check on */ +static const char *const section_white_list[] = +{ + ".comment*", + ".debug*", + ".zdebug*", /* Compressed debug sections. */ + ".GCC.command.line", /* record-gcc-switches */ + ".mdebug*", /* alpha, score, mips etc. */ + ".pdr", /* alpha, score, mips etc. */ + ".stab*", + ".note*", + ".got*", + ".toc*", + ".xt.prop", /* xtensa */ + ".xt.lit", /* xtensa */ + ".arcextmap*", /* arc */ + ".gnu.linkonce.arcext*", /* arc : modules */ + ".cmem*", /* EZchip */ + ".fmt_slot*", /* EZchip */ + ".gnu.lto*", + ".discard.*", + ".llvm.call-graph-profile", /* call graph */ + "__llvm_covfun", + "__llvm_covmap", + ".klp.symid", /* objtool --klp-symids */ + NULL +}; + +/* + * This is used to find sections missing the SHF_ALLOC flag. + * The cause of this is often a section specified in assembler + * without "ax" / "aw". + */ +static void check_section(struct module *mod, struct elf_info *elf, + Elf_Shdr *sechdr) +{ + const char *sec = sech_name(elf, sechdr); + + if (sechdr->sh_type == SHT_PROGBITS && + !(sechdr->sh_flags & SHF_ALLOC) && + !match(sec, section_white_list)) { + mod_warn(mod, "unexpected non-allocatable section '%s'.\n" + "Did you forget to use \"ax\"/\"aw\" in a .S file?\n" + "Note that for example <linux/init.h> contains\n" + "section definitions for use in .S files.\n\n", + sec); + } +} + + + +#define ALL_INIT_DATA_SECTIONS \ + ".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_INIT_SECTIONS ".init.*" +#define ALL_EXIT_SECTIONS ".exit.*" + +#define DATA_SECTIONS ".data", ".data.rel" +#define TEXT_SECTIONS ".text", ".text.*", ".sched.text", \ + ".kprobes.text", ".cpuidle.text", ".noinstr.text", \ + ".ltext", ".ltext.*" +#define OTHER_TEXT_SECTIONS ".ref.text", ".head.text", ".spinlock.text", \ + ".fixup", ".entry.text", ".exception.text", \ + ".coldtext", ".softirqentry.text", ".irqentry.text" + +#define ALL_TEXT_SECTIONS ".init.text", ".exit.text", \ + TEXT_SECTIONS, OTHER_TEXT_SECTIONS + +enum mismatch { + TEXTDATA_TO_ANY_INIT_EXIT, + XXXINIT_TO_SOME_INIT, + ANY_INIT_TO_ANY_EXIT, + ANY_EXIT_TO_ANY_INIT, + EXTABLE_TO_NON_TEXT, +}; + +/** + * Describe how to match sections on different criteria: + * + * @fromsec: Array of sections to be matched. + * + * @bad_tosec: Relocations applied to a section in @fromsec to a section in + * this array is forbidden (black-list). Can be empty. + * + * @good_tosec: Relocations applied to a section in @fromsec must be + * targeting sections in this array (white-list). Can be empty. + * + * @mismatch: Type of mismatch. + */ +struct sectioncheck { + const char *fromsec[20]; + const char *bad_tosec[20]; + const char *good_tosec[20]; + enum mismatch mismatch; +}; + +static const struct sectioncheck sectioncheck[] = { +/* Do not reference init/exit code/data from + * normal code and data + */ +{ + .fromsec = { TEXT_SECTIONS, DATA_SECTIONS, NULL }, + .bad_tosec = { ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS, NULL }, + .mismatch = TEXTDATA_TO_ANY_INIT_EXIT, +}, +/* Do not use exit code/data from init code */ +{ + .fromsec = { ALL_INIT_SECTIONS, NULL }, + .bad_tosec = { ALL_EXIT_SECTIONS, NULL }, + .mismatch = ANY_INIT_TO_ANY_EXIT, +}, +/* Do not use init code/data from exit code */ +{ + .fromsec = { ALL_EXIT_SECTIONS, NULL }, + .bad_tosec = { ALL_INIT_SECTIONS, NULL }, + .mismatch = ANY_EXIT_TO_ANY_INIT, +}, +{ + .fromsec = { ALL_PCI_INIT_SECTIONS, NULL }, + .bad_tosec = { ALL_INIT_SECTIONS, NULL }, + .mismatch = ANY_INIT_TO_ANY_EXIT, +}, +{ + .fromsec = { "__ex_table", NULL }, + /* If you're adding any new black-listed sections in here, consider + * adding a special 'printer' for them in scripts/check_extable. + */ + .bad_tosec = { ".altinstr_replacement", NULL }, + .good_tosec = {ALL_TEXT_SECTIONS , NULL}, + .mismatch = EXTABLE_TO_NON_TEXT, +} +}; + +static const struct sectioncheck *section_mismatch( + const char *fromsec, const char *tosec) +{ + int i; + + /* + * The target section could be the SHT_NUL section when we're + * handling relocations to un-resolved symbols, trying to match it + * doesn't make much sense and causes build failures on parisc + * architectures. + */ + if (*tosec == '\0') + return NULL; + + for (i = 0; i < ARRAY_SIZE(sectioncheck); i++) { + const struct sectioncheck *check = §ioncheck[i]; + + if (match(fromsec, check->fromsec)) { + if (check->bad_tosec[0] && match(tosec, check->bad_tosec)) + return check; + if (check->good_tosec[0] && !match(tosec, check->good_tosec)) + return check; + } + } + return NULL; +} + +/** + * Whitelist to allow certain references to pass with no warning. + * + * Pattern 1: + * If a module parameter is declared __initdata and permissions=0 + * then this is legal despite the warning generated. + * We cannot see value of permissions here, so just ignore + * this pattern. + * The pattern is identified by: + * tosec = .init.data + * fromsec = .data* + * atsym =__param* + * + * Pattern 1a: + * module_param_call() ops can refer to __init set function if permissions=0 + * The pattern is identified by: + * tosec = .init.text + * fromsec = .data* + * atsym = __param_ops_* + * + * Pattern 3: + * Whitelist all references from .head.text to any init section + * + * Pattern 4: + * Some symbols belong to init section but still it is ok to reference + * these from non-init sections as these symbols don't have any memory + * allocated for them and symbol address and value are same. So even + * if init section is freed, its ok to reference those symbols. + * For ex. symbols marking the init section boundaries. + * This pattern is identified by + * refsymname = __init_begin, _sinittext, _einittext + * + * Pattern 5: + * GCC may optimize static inlines when fed constant arg(s) resulting + * in functions like cpumask_empty() -- generating an associated symbol + * cpumask_empty.constprop.3 that appears in the audit. If the const that + * is passed in comes from __init, like say nmi_ipi_mask, we get a + * meaningless section warning. May need to add isra symbols too... + * This pattern is identified by + * tosec = init section + * fromsec = text section + * refsymname = *.constprop.* + * + **/ +static int secref_whitelist(const char *fromsec, const char *fromsym, + const char *tosec, const char *tosym) +{ + /* Check for pattern 1 */ + if (match(tosec, PATTERNS(ALL_INIT_DATA_SECTIONS)) && + match(fromsec, PATTERNS(DATA_SECTIONS)) && + strstarts(fromsym, "__param")) + return 0; + + /* Check for pattern 1a */ + if (strcmp(tosec, ".init.text") == 0 && + match(fromsec, PATTERNS(DATA_SECTIONS)) && + strstarts(fromsym, "__param_ops_")) + return 0; + + /* symbols in data sections that may refer to any init/exit sections */ + if (match(fromsec, PATTERNS(DATA_SECTIONS)) && + match(tosec, PATTERNS(ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS)) && + match(fromsym, PATTERNS("*_ops", "*_ops.llvm.*", "*_console"))) + return 0; + + /* Check for pattern 3 */ + if (strstarts(fromsec, ".head.text") && + match(tosec, PATTERNS(ALL_INIT_SECTIONS))) + return 0; + + /* Check for pattern 4 */ + if (match(tosym, PATTERNS("__init_begin", "_sinittext", "_einittext"))) + return 0; + + /* Check for pattern 5 */ + if (match(fromsec, PATTERNS(ALL_TEXT_SECTIONS)) && + match(tosec, PATTERNS(ALL_INIT_SECTIONS)) && + match(fromsym, PATTERNS("*.constprop.*"))) + return 0; + + return 1; +} + +static Elf_Sym *find_fromsym(struct elf_info *elf, Elf_Addr addr, + unsigned int secndx) +{ + return symsearch_find_nearest(elf, addr, secndx, false, ~0); +} + +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; + + /* + * Strive to find a better symbol name, but the resulting name may not + * match the symbol referenced in the original code. + */ + 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) +{ + if (secndx >= elf->num_sections) + return false; + + return (elf->sechdrs[secndx].sh_flags & SHF_EXECINSTR) != 0; +} + +static void default_mismatch_handler(struct module *mod, struct elf_info *elf, + const struct sectioncheck* const mismatch, + Elf_Sym *tsym, + unsigned int fsecndx, const char *fromsec, Elf_Addr faddr, + const char *tosec, Elf_Addr taddr) +{ + Elf_Sym *from; + const char *tosym; + const char *fromsym; + char taddr_str[16]; + + from = find_fromsym(elf, faddr, fsecndx); + fromsym = sym_name(elf, from); + + tsym = find_tosym(elf, taddr, tsym); + tosym = sym_name(elf, tsym); + + /* check whitelist - we may ignore it */ + if (!secref_whitelist(fromsec, fromsym, tosec, tosym)) + return; + + sec_mismatch_count++; + + 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> + */ + mod_warn(mod, "section mismatch in reference: %s%s0x%x (section: %s) -> %s (section: %s)\n", + 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)) + fatal("The relocation at %s+0x%lx references\n" + "section \"%s\" which is black-listed.\n" + "Something is seriously wrong and should be fixed.\n" + "You might get more information about where this is\n" + "coming from by using scripts/check_extable.sh %s\n", + fromsec, (long)faddr, tosec, mod->name); + else if (is_executable_section(elf, get_secindex(elf, tsym))) + warn("The relocation at %s+0x%lx references\n" + "section \"%s\" which is not in the list of\n" + "authorized sections. If you're adding a new section\n" + "and/or if this reference is valid, add \"%s\" to the\n" + "list of authorized sections to jump to on fault.\n" + "This can be achieved by adding \"%s\" to\n" + "OTHER_TEXT_SECTIONS in scripts/mod/modpost.c.\n", + fromsec, (long)faddr, tosec, tosec, tosec); + else + error("%s+0x%lx references non-executable section '%s'\n", + fromsec, (long)faddr, tosec); + } +} + +static void check_export_symbol(struct module *mod, struct elf_info *elf, + Elf_Addr faddr, const char *secname, + Elf_Sym *sym) +{ + static const char *prefix = "__export_symbol_"; + const char *label_name, *name, *data; + Elf_Sym *label; + struct symbol *s; + bool is_gpl; + + label = find_fromsym(elf, faddr, elf->export_symbol_secndx); + label_name = sym_name(elf, label); + + if (!strstarts(label_name, prefix)) { + mod_error(mod, ".export_symbol section contains strange symbol '%s'\n", + label_name); + return; + } + + if (ELF_ST_BIND(sym->st_info) != STB_GLOBAL && + ELF_ST_BIND(sym->st_info) != STB_WEAK) { + mod_error(mod, "local symbol '%s' was exported\n", + label_name + strlen(prefix)); + return; + } + + name = sym_name(elf, sym); + if (strcmp(label_name + strlen(prefix), name)) { + mod_error(mod, ".export_symbol section references '%s', but it does not seem to be an export symbol\n", + name); + return; + } + + data = sym_get_data(elf, label); /* license */ + if (!strcmp(data, "GPL")) { + is_gpl = true; + } else if (!strcmp(data, "")) { + is_gpl = false; + } else { + mod_error(mod, "unknown license '%s' was specified for '%s'\n", + data, name); + return; + } + + data += strlen(data) + 1; /* namespace */ + s = sym_add_exported(name, mod, is_gpl, data); + + /* + * We need to be aware whether we are exporting a function or + * a data on some architectures. + */ + s->is_func = (ELF_ST_TYPE(sym->st_info) == STT_FUNC); + + /* + * For parisc64, symbols prefixed $$ from the library have the symbol type + * STT_LOPROC. They should be handled as functions too. + */ + if (elf->hdr->e_ident[EI_CLASS] == ELFCLASS64 && + elf->hdr->e_machine == EM_PARISC && + ELF_ST_TYPE(sym->st_info) == STT_LOPROC) + s->is_func = true; + + if (match(secname, PATTERNS(ALL_INIT_SECTIONS))) + mod_warn(mod, "EXPORT_SYMBOL used for init symbol '%s'. Remove __init or EXPORT_SYMBOL.\n", + name); + else if (match(secname, PATTERNS(ALL_EXIT_SECTIONS))) + mod_warn(mod, "EXPORT_SYMBOL used for exit symbol '%s'. Remove __exit or EXPORT_SYMBOL.\n", + name); +} + +static void check_section_mismatch(struct module *mod, struct elf_info *elf, + Elf_Sym *sym, + unsigned int fsecndx, const char *fromsec, + Elf_Addr faddr, Elf_Addr taddr) +{ + const char *tosec = sec_name(elf, get_secindex(elf, sym)); + const struct sectioncheck *mismatch; + + if (module_enabled && elf->export_symbol_secndx == fsecndx) { + check_export_symbol(mod, elf, faddr, tosec, sym); + return; + } + + mismatch = section_mismatch(fromsec, tosec); + if (!mismatch) + return; + + default_mismatch_handler(mod, elf, mismatch, sym, + fsecndx, fromsec, faddr, + tosec, taddr); +} + +static Elf_Addr addend_386_rel(uint32_t *location, unsigned int r_type) +{ + switch (r_type) { + case R_386_32: + return get_unaligned_native(location); + case R_386_PC32: + return get_unaligned_native(location) + 4; + } + + return (Elf_Addr)(-1); +} + +static int32_t sign_extend32(int32_t value, int index) +{ + uint8_t shift = 31 - index; + + return (int32_t)(value << shift) >> shift; +} + +static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type) +{ + uint32_t inst, upper, lower, sign, j1, j2; + int32_t offset; + + switch (r_type) { + case R_ARM_ABS32: + case R_ARM_REL32: + inst = get_unaligned_native((uint32_t *)loc); + return inst + sym->st_value; + case R_ARM_MOVW_ABS_NC: + case R_ARM_MOVT_ABS: + 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 = 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 = 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) | + (lower & 0x00ff), + 15); + return offset + sym->st_value; + case R_ARM_THM_JUMP19: + /* + * Encoding T3: + * S = upper[10] + * imm6 = upper[5:0] + * J1 = lower[13] + * J2 = lower[11] + * imm11 = lower[10:0] + * imm32 = SignExtend(S:J2:J1:imm6:imm11:'0') + */ + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); + + sign = (upper >> 10) & 1; + j1 = (lower >> 13) & 1; + j2 = (lower >> 11) & 1; + offset = sign_extend32((sign << 20) | (j2 << 19) | (j1 << 18) | + ((upper & 0x03f) << 12) | + ((lower & 0x07ff) << 1), + 20); + return offset + sym->st_value + 4; + case R_ARM_THM_PC22: + case R_ARM_THM_JUMP24: + /* + * Encoding T4: + * S = upper[10] + * imm10 = upper[9:0] + * J1 = lower[13] + * J2 = lower[11] + * imm11 = lower[10:0] + * I1 = NOT(J1 XOR S) + * I2 = NOT(J2 XOR S) + * imm32 = SignExtend(S:I1:I2:imm10:imm11:'0') + */ + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); + + sign = (upper >> 10) & 1; + j1 = (lower >> 13) & 1; + j2 = (lower >> 11) & 1; + offset = sign_extend32((sign << 24) | + ((~(j1 ^ sign) & 1) << 23) | + ((~(j2 ^ sign) & 1) << 22) | + ((upper & 0x03ff) << 12) | + ((lower & 0x07ff) << 1), + 24); + return offset + sym->st_value + 4; + } + + return (Elf_Addr)(-1); +} + +static Elf_Addr addend_mips_rel(uint32_t *location, unsigned int r_type) +{ + uint32_t inst; + + inst = get_unaligned_native(location); + switch (r_type) { + case R_MIPS_LO16: + return inst & 0xffff; + case R_MIPS_26: + return (inst & 0x03ffffff) << 2; + case R_MIPS_32: + return inst; + } + return (Elf_Addr)(-1); +} + +#ifndef EM_RISCV +#define EM_RISCV 243 +#endif + +#ifndef R_RISCV_SUB32 +#define R_RISCV_SUB32 39 +#endif + +#ifndef EM_LOONGARCH +#define EM_LOONGARCH 258 +#endif + +#ifndef R_LARCH_SUB32 +#define R_LARCH_SUB32 55 +#endif + +#ifndef R_LARCH_RELAX +#define R_LARCH_RELAX 100 +#endif + +#ifndef R_LARCH_ALIGN +#define R_LARCH_ALIGN 102 +#endif + +static void get_rel_type_and_sym(struct elf_info *elf, uint64_t r_info, + unsigned int *r_type, unsigned int *r_sym) +{ + typedef struct { + Elf64_Word r_sym; /* Symbol index */ + unsigned char r_ssym; /* Special symbol for 2nd relocation */ + unsigned char r_type3; /* 3rd relocation type */ + unsigned char r_type2; /* 2nd relocation type */ + unsigned char r_type; /* 1st relocation type */ + } Elf64_Mips_R_Info; + + bool is_64bit = (elf->hdr->e_ident[EI_CLASS] == ELFCLASS64); + + if (elf->hdr->e_machine == EM_MIPS && is_64bit) { + Elf64_Mips_R_Info *mips64_r_info = (void *)&r_info; + + *r_type = mips64_r_info->r_type; + *r_sym = TO_NATIVE(mips64_r_info->r_sym); + return; + } + + if (is_64bit) + r_info = TO_NATIVE((Elf64_Xword)r_info); + else + r_info = TO_NATIVE((Elf32_Word)r_info); + + *r_type = ELF_R_TYPE(r_info); + *r_sym = ELF_R_SYM(r_info); +} + +static void section_rela(struct module *mod, struct elf_info *elf, + unsigned int fsecndx, const char *fromsec, + const Elf_Rela *start, const Elf_Rela *stop) +{ + const Elf_Rela *rela; + + for (rela = start; rela < stop; rela++) { + Elf_Sym *tsym; + Elf_Addr taddr, r_offset; + unsigned int r_type, r_sym; + + r_offset = TO_NATIVE(rela->r_offset); + get_rel_type_and_sym(elf, rela->r_info, &r_type, &r_sym); + + tsym = elf->symtab_start + r_sym; + taddr = tsym->st_value + TO_NATIVE(rela->r_addend); + + switch (elf->hdr->e_machine) { + case EM_RISCV: + if (!strcmp("__ex_table", fromsec) && + r_type == R_RISCV_SUB32) + continue; + break; + case EM_LOONGARCH: + switch (r_type) { + case R_LARCH_SUB32: + if (!strcmp("__ex_table", fromsec)) + continue; + break; + case R_LARCH_RELAX: + case R_LARCH_ALIGN: + /* These relocs do not refer to symbols */ + continue; + } + break; + } + + check_section_mismatch(mod, elf, tsym, + fsecndx, fromsec, r_offset, taddr); + } +} + +static void section_rel(struct module *mod, struct elf_info *elf, + unsigned int fsecndx, const char *fromsec, + const Elf_Rel *start, const Elf_Rel *stop) +{ + const Elf_Rel *rel; + + for (rel = start; rel < stop; rel++) { + Elf_Sym *tsym; + Elf_Addr taddr, r_offset; + unsigned int r_type, r_sym; + void *loc; + + r_offset = TO_NATIVE(rel->r_offset); + get_rel_type_and_sym(elf, rel->r_info, &r_type, &r_sym); + + loc = sym_get_data_by_offset(elf, fsecndx, r_offset); + tsym = elf->symtab_start + r_sym; + + switch (elf->hdr->e_machine) { + case EM_386: + taddr = addend_386_rel(loc, r_type); + break; + case EM_ARM: + taddr = addend_arm_rel(loc, tsym, r_type); + break; + case EM_MIPS: + taddr = addend_mips_rel(loc, r_type); + break; + default: + fatal("Please add code to calculate addend for this architecture\n"); + } + + check_section_mismatch(mod, elf, tsym, + fsecndx, fromsec, r_offset, taddr); + } +} + +/** + * A module includes a number of sections that are discarded + * either when loaded or when used as built-in. + * For loaded modules all functions marked __init and all data + * marked __initdata will be discarded when the module has been initialized. + * Likewise for modules used built-in the sections marked __exit + * are discarded because __exit marked function are supposed to be called + * only when a module is unloaded which never happens for built-in modules. + * The check_sec_ref() function traverses all relocation records + * to find all references to a section that reference a section that will + * be discarded and warns about it. + **/ +static void check_sec_ref(struct module *mod, struct elf_info *elf) +{ + int i; + + /* Walk through all sections */ + for (i = 0; i < elf->num_sections; i++) { + Elf_Shdr *sechdr = &elf->sechdrs[i]; + + check_section(mod, elf, sechdr); + /* We want to process only relocation sections and not .init */ + if (sechdr->sh_type == SHT_REL || sechdr->sh_type == SHT_RELA) { + /* section to which the relocation applies */ + unsigned int secndx = sechdr->sh_info; + const char *secname = sec_name(elf, secndx); + const void *start, *stop; + + /* If the section is known good, skip it */ + if (match(secname, section_white_list)) + continue; + + start = sym_get_data_by_offset(elf, i, 0); + stop = start + sechdr->sh_size; + + if (sechdr->sh_type == SHT_RELA) + section_rela(mod, elf, secndx, secname, + start, stop); + else + section_rel(mod, elf, secndx, secname, + start, stop); + } + } +} + +static char *remove_dot(char *s) +{ + size_t n = strcspn(s, "."); + + if (n && s[n]) { + size_t m = strspn(s + n + 1, "0123456789"); + if (m && (s[n + m + 1] == '.' || s[n + m + 1] == 0)) + s[n] = 0; + } + return s; +} + +/* + * The CRCs are recorded in .*.cmd files in the form of: + * #SYMVER <name> <crc> + */ +static void extract_crcs_for_object(const char *object, struct module *mod) +{ + char cmd_file[PATH_MAX]; + char *buf, *p; + const char *base; + int dirlen, baselen_without_suffix, ret; + + base = get_basename(object); + dirlen = base - object; + + baselen_without_suffix = strlen(object) - dirlen - strlen(".o"); + + /* + * When CONFIG_LTO_CLANG_THIN_DIST=y, the ELF is *.thinlto-native.o + * but the symbol CRCs are recorded in *.o.cmd file. + */ + if (strends(object, ".thinlto-native.o")) + baselen_without_suffix -= strlen(".thinlto-native"); + + ret = snprintf(cmd_file, sizeof(cmd_file), "%.*s.%.*s.o.cmd", + dirlen, object, baselen_without_suffix, base); + if (ret >= sizeof(cmd_file)) { + error("%s: too long path was truncated\n", cmd_file); + return; + } + + buf = read_text_file(cmd_file); + p = buf; + + while ((p = strstr(p, "\n#SYMVER "))) { + char *name; + size_t namelen; + unsigned int crc; + struct symbol *sym; + + name = p + strlen("\n#SYMVER "); + + p = strchr(name, ' '); + if (!p) + break; + + namelen = p - name; + p++; + + if (!isdigit(*p)) + continue; /* skip this line */ + + crc = strtoul(p, &p, 0); + if (*p != '\n') + continue; /* skip this line */ + + name[namelen] = '\0'; + + /* + * sym_find_with_module() may return NULL here. + * It typically occurs when CONFIG_TRIM_UNUSED_KSYMS=y. + * Since commit e1327a127703, genksyms calculates CRCs of all + * symbols, including trimmed ones. Ignore orphan CRCs. + */ + sym = sym_find_with_module(name, mod); + if (sym) + sym_set_crc(sym, crc); + } + + free(buf); +} + +/* + * The symbol versions (CRC) are recorded in the .*.cmd files. + * Parse them to retrieve CRCs for the current module. + */ +static void mod_set_crcs(struct module *mod) +{ + char objlist[PATH_MAX]; + char *buf, *p, *obj; + int ret; + + if (mod->is_vmlinux) { + strcpy(objlist, ".vmlinux.objs"); + } else { + /* objects for a module are listed in the *.mod file. */ + ret = snprintf(objlist, sizeof(objlist), "%s.mod", mod->name); + if (ret >= sizeof(objlist)) { + error("%s: too long path was truncated\n", objlist); + return; + } + } + + buf = read_text_file(objlist); + p = buf; + + while ((obj = strsep(&p, "\n")) && obj[0]) + extract_crcs_for_object(obj, mod); + + free(buf); +} + +static void read_symbols(const char *modname) +{ + const char *symname; + char *version; + char *license; + char *namespace; + struct module *mod; + struct elf_info info = { }; + Elf_Sym *sym; + + if (!strends(modname, ".o")) { + error("%s: filename must be suffixed with .o\n", modname); + return; + } + + if (!parse_elf(&info, modname)) + return; + + /* 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) + mod_error(mod, "missing MODULE_LICENSE()\n"); + while (license) { + if (!license_is_gpl_compatible(license)) { + mod->is_gpl_compatible = false; + break; + } + license = get_next_modinfo(&info, "license", license); + } + + for (namespace = get_modinfo(&info, "import_ns"); + namespace; + namespace = get_next_modinfo(&info, "import_ns", namespace)) { + if (strstarts(namespace, MODULE_NS_PREFIX)) + mod_error(mod, "explicitly importing namespace '%s' is not allowed.\n", + namespace); + + add_namespace(&mod->imported_namespaces, namespace); + } + + if (!get_modinfo(&info, "description")) + mod_warn(mod, "missing MODULE_DESCRIPTION()\n"); + } + + for (sym = info.symtab_start; sym < info.symtab_stop; sym++) { + symname = remove_dot(info.strtab + sym->st_name); + + handle_symbol(mod, &info, sym, symname); + handle_moddevtable(mod, &info, sym, symname); + } + + check_sec_ref(mod, &info); + + if (!mod->is_vmlinux) { + version = get_modinfo(&info, "version"); + if (version || all_versions) + get_src_version(mod->name, mod->srcversion, + sizeof(mod->srcversion) - 1); + } + + parse_elf_finish(&info); + + if (modversions) { + /* + * Our trick to get versioning for module struct etc. - it's + * never passed as an argument to an exported function, so + * the automatic versioning doesn't pick it up, but it's really + * important anyhow. + */ + sym_add_unresolved("module_layout", mod, false); + + mod_set_crcs(mod); + } +} + +static void read_symbols_from_files(const char *filename) +{ + FILE *in = stdin; + char fname[PATH_MAX]; + + in = fopen(filename, "r"); + if (!in) + fatal("Can't open filenames file %s: %m", filename); + + while (fgets(fname, PATH_MAX, in) != NULL) { + if (strends(fname, "\n")) + fname[strlen(fname)-1] = '\0'; + read_symbols(fname); + } + + fclose(in); +} + +#define SZ 500 + +/* We first write the generated file into memory using the + * following helper, then compare to the file on disk and + * only update the later if anything changed */ + +void __attribute__((format(printf, 2, 3))) buf_printf(struct buffer *buf, + const char *fmt, ...) +{ + char tmp[SZ]; + int len; + va_list ap; + + va_start(ap, fmt); + len = vsnprintf(tmp, SZ, fmt, ap); + va_end(ap); + + if (len < 0) { + perror("vsnprintf failed"); + exit(1); + } + if (len >= SZ) + fatal("buf_printf output truncated for string %s: %d bytes needed, %d available\n", + tmp, len + 1, SZ); + + buf_write(buf, tmp, len); +} + +void buf_write(struct buffer *buf, const char *s, int len) +{ + if (buf->size - buf->pos < len) { + buf->size += len + SZ; + 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; + + list_for_each_entry(s, &mod->unresolved_symbols, list) { + const char *basename; + exp = find_symbol(s->name); + if (!exp) { + if (!s->weak && nr_unresolved++ < MAX_UNRESOLVED_REPORTS) + modpost_log(!warn_unresolved, mod, + "symbol '%s' undefined!\n", + s->name); + continue; + } + if (exp->module == mod) { + mod_error(mod, "symbol '%s' was exported without definition\n", s->name); + continue; + } + + exp->used = true; + s->module = exp->module; + s->crc_valid = exp->crc_valid; + s->crc = exp->crc; + + basename = get_basename(mod->name); + + if (!verify_module_namespace(exp->namespace, basename) && + !contains_namespace(&mod->imported_namespaces, exp->namespace)) { + modpost_log(!allow_missing_ns_imports, mod, + "module uses symbol '%s' from namespace '%s', but does not import it.\n", + exp->name, exp->namespace); + add_namespace(&mod->missing_namespaces, exp->namespace); + } + + if (!mod->is_gpl_compatible && exp->is_gpl_only) + mod_error(mod, "GPL-incompatible module uses GPL-only symbol '%s'\n", + exp->name); + } +} + +static void handle_white_list_exports(const char *white_list) +{ + char *buf, *p, *name; + + buf = read_text_file(white_list); + p = buf; + + while ((name = strsep(&p, "\n"))) { + struct symbol *sym = find_symbol(name); + + if (sym) + sym->used = true; + } + + 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 = get_basename(mod->name); + + if (strlen(mod_name) >= MODULE_NAME_LEN) + mod_error(mod, "module name is too long\n"); +} + +/** + * Header for the generated file + **/ +static void add_header(struct buffer *b, struct module *mod) +{ + buf_printf(b, "#include <linux/module.h>\n"); + buf_printf(b, "#include <linux/export-internal.h>\n"); + buf_printf(b, "#include <linux/compiler.h>\n"); + buf_printf(b, "\n"); + buf_printf(b, "MODULE_INFO(name, KBUILD_MODNAME);\n"); + buf_printf(b, "\n"); + buf_printf(b, "__visible struct module __this_module\n"); + buf_printf(b, "__section(\".gnu.linkonce.this_module\") = {\n"); + buf_printf(b, "\t.name = KBUILD_MODNAME,\n"); + if (mod->has_init) + buf_printf(b, "\t.init = init_module,\n"); + if (mod->has_cleanup) + buf_printf(b, "#ifdef CONFIG_MODULE_UNLOAD\n" + "\t.exit = cleanup_module,\n" + "#endif\n"); + buf_printf(b, "\t.arch = MODULE_ARCH_INIT,\n"); + buf_printf(b, "};\n"); + + if (!external_module) + buf_printf(b, "\nMODULE_INFO(intree, \"Y\");\n"); + + if (strstarts(mod->name, "drivers/staging")) + buf_printf(b, "\nMODULE_INFO(staging, \"Y\");\n"); + + if (strstarts(mod->name, "tools/testing")) + buf_printf(b, "\nMODULE_INFO(test, \"Y\");\n"); +} + +static void add_exported_symbols(struct buffer *buf, struct module *mod) +{ + struct symbol *sym; + + /* generate struct for exported symbols */ + buf_printf(buf, "\n"); + list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + + buf_printf(buf, "KSYMTAB_%s(%s, \"%s\");\n", + sym->is_func ? "FUNC" : "DATA", sym->name, + sym->namespace); + + buf_printf(buf, "SYMBOL_FLAGS(%s, 0x%02x);\n", + sym->name, get_symbol_flags(sym)); + } + + if (!modversions) + return; + + /* record CRCs for exported symbols */ + buf_printf(buf, "\n"); + list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + + if (!sym->crc_valid) + mod_warn(mod, "EXPORT symbol '%s' version generation failed, symbol will not be versioned.\n" + "Is '%s' prototyped in <asm/asm-prototypes.h>?\n", + sym->name, sym->name); + + buf_printf(buf, "SYMBOL_CRC(%s, 0x%08x);\n", + sym->name, sym->crc); + } +} + +/** + * 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) { + mod_warn(mod, "symbol '%s' has no CRC!\n", s->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 (!basic_modversions) + return; + + buf_printf(b, "\n"); + buf_printf(b, "static const struct modversion_info ____versions[]\n"); + buf_printf(b, "__used __section(\"__versions\") = {\n"); + + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (!s->module) + continue; + if (!s->crc_valid) { + mod_warn(mod, "symbol '%s' has no CRC!\n", s->name); + continue; + } + if (strlen(s->name) >= MODULE_NAME_LEN) { + if (extended_modversions) { + /* this symbol will only be in the extended info */ + continue; + } else { + mod_error(mod, "too long symbol '%s'\n", s->name); + break; + } + } + buf_printf(b, "\t{ 0x%08x, \"%s\" },\n", + s->crc, s->name); + } + + buf_printf(b, "};\n"); +} + +static void add_depends(struct buffer *b, struct module *mod) +{ + struct symbol *s; + int first = 1; + + /* Clear ->seen flag of modules that own symbols needed by this. */ + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (s->module) + s->module->seen = s->module->is_vmlinux; + } + + buf_printf(b, "\n"); + buf_printf(b, "MODULE_INFO(depends, \""); + list_for_each_entry(s, &mod->unresolved_symbols, list) { + const char *p; + if (!s->module) + continue; + + if (s->module->seen) + continue; + + s->module->seen = true; + p = get_basename(s->module->name); + buf_printf(b, "%s%s", first ? "" : ",", p); + first = 0; + } + buf_printf(b, "\");\n"); +} + +static void add_srcversion(struct buffer *b, struct module *mod) +{ + if (mod->srcversion[0]) { + buf_printf(b, "\n"); + buf_printf(b, "MODULE_INFO(srcversion, \"%s\");\n", + mod->srcversion); + } +} + +static void write_buf(struct buffer *b, const char *fname) +{ + FILE *file; + + if (error_occurred) + return; + + file = fopen(fname, "w"); + if (!file) { + perror(fname); + exit(1); + } + if (fwrite(b->p, 1, b->pos, file) != b->pos) { + perror(fname); + exit(1); + } + if (fclose(file) != 0) { + perror(fname); + exit(1); + } +} + +static void write_if_changed(struct buffer *b, const char *fname) +{ + char *tmp; + FILE *file; + struct stat st; + + file = fopen(fname, "r"); + if (!file) + goto write; + + if (fstat(fileno(file), &st) < 0) + goto close_write; + + if (st.st_size != b->pos) + goto close_write; + + tmp = xmalloc(b->pos); + if (fread(tmp, 1, b->pos, file) != b->pos) + goto free_write; + + if (memcmp(tmp, b->p, b->pos) != 0) + goto free_write; + + free(tmp); + fclose(file); + return; + + free_write: + free(tmp); + close_write: + fclose(file); + write: + write_buf(b, fname); +} + +static void write_vmlinux_export_c_file(struct module *mod) +{ + struct buffer buf = { }; + struct module_alias *alias, *next; + + buf_printf(&buf, + "#include <linux/export-internal.h>\n"); + + add_exported_symbols(&buf, mod); + + buf_printf(&buf, + "#include <linux/module.h>\n" + "#undef __MODULE_INFO_PREFIX\n" + "#define __MODULE_INFO_PREFIX\n"); + + list_for_each_entry_safe(alias, next, &mod->aliases, node) { + buf_printf(&buf, "MODULE_INFO(%s.alias, \"%s\");\n", + alias->builtin_modname, alias->str); + list_del(&alias->node); + free(alias->builtin_modname); + free(alias); + } + + write_if_changed(&buf, ".vmlinux.export.c"); + free(buf.p); +} + +/* do sanity checks, and generate *.mod.c file */ +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); + + 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); + if (ret >= sizeof(fname)) { + error("%s: too long path was truncated\n", fname); + goto free; + } + + write_if_changed(&buf, fname); + +free: + free(buf.p); +} + +/* parse Module.symvers file. line format: + * 0x12345678<tab>symbol<tab>module<tab>export<tab>namespace + **/ +static void read_dump(const char *fname) +{ + char *buf, *pos, *line; + + buf = read_text_file(fname); + if (!buf) + /* No symbol versions, silently ignore */ + return; + + pos = buf; + + while ((line = get_line(&pos))) { + char *symname, *namespace, *modname, *d, *export; + unsigned int crc; + struct module *mod; + struct symbol *s; + bool gpl_only; + + if (!(symname = strchr(line, '\t'))) + goto fail; + *symname++ = '\0'; + if (!(modname = strchr(symname, '\t'))) + goto fail; + *modname++ = '\0'; + if (!(export = strchr(modname, '\t'))) + goto fail; + *export++ = '\0'; + if (!(namespace = strchr(export, '\t'))) + goto fail; + *namespace++ = '\0'; + + crc = strtoul(line, &d, 16); + if (*symname == '\0' || *modname == '\0' || *d != '\0') + goto fail; + + if (!strcmp(export, "EXPORT_SYMBOL_GPL")) { + gpl_only = true; + } else if (!strcmp(export, "EXPORT_SYMBOL")) { + gpl_only = false; + } else { + error("%s: unknown license %s. skip", symname, export); + continue; + } + + mod = find_module(fname, modname); + if (!mod) { + mod = new_module(modname, strlen(modname)); + mod->dump_file = fname; + } + s = sym_add_exported(symname, mod, gpl_only, namespace); + sym_set_crc(s, crc); + } + free(buf); + return; +fail: + free(buf); + fatal("parse error in symbol dump file\n"); +} + +static void write_dump(const char *fname) +{ + struct buffer buf = { }; + struct module *mod; + struct symbol *sym; + + list_for_each_entry(mod, &modules, list) { + if (mod->dump_file) + continue; + list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + + buf_printf(&buf, "0x%08x\t%s\t%s\tEXPORT_SYMBOL%s\t%s\n", + sym->crc, sym->name, mod->name, + sym->is_gpl_only ? "_GPL" : "", + sym->namespace); + } + } + write_buf(&buf, fname); + free(buf.p); +} + +static void write_namespace_deps_files(const char *fname) +{ + struct module *mod; + struct namespace_list *ns; + struct buffer ns_deps_buf = {}; + + list_for_each_entry(mod, &modules, list) { + + if (mod->dump_file || list_empty(&mod->missing_namespaces)) + continue; + + buf_printf(&ns_deps_buf, "%s.ko:", mod->name); + + list_for_each_entry(ns, &mod->missing_namespaces, list) + buf_printf(&ns_deps_buf, " %s", ns->namespace); + + buf_printf(&ns_deps_buf, "\n"); + } + + write_if_changed(&ns_deps_buf, fname); + free(ns_deps_buf.p); +} + +struct dump_list { + struct list_head 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; + char *missing_namespace_deps = NULL; + char *unused_exports_white_list = NULL; + char *dump_write = NULL, *files_source = NULL; + int opt; + LIST_HEAD(dump_lists); + struct dump_list *dl, *dl2; + + while ((opt = getopt(argc, argv, "ei:MmnT:to:au:WwENd:xb")) != -1) { + switch (opt) { + case 'e': + external_module = true; + break; + case 'i': + dl = xmalloc(sizeof(*dl)); + dl->file = optarg; + list_add_tail(&dl->list, &dump_lists); + break; + case 'M': + module_enabled = true; + break; + case 'm': + modversions = true; + break; + case 'n': + ignore_missing_files = true; + break; + case 'o': + dump_write = optarg; + break; + case 'a': + all_versions = true; + break; + case 'T': + files_source = optarg; + break; + case 't': + trim_unused_exports = true; + break; + case 'u': + unused_exports_white_list = optarg; + break; + case 'W': + extra_warn = true; + break; + case 'w': + warn_unresolved = true; + break; + case 'E': + sec_mismatch_warn_only = false; + break; + case 'N': + allow_missing_ns_imports = true; + break; + 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); + free(dl); + } + + while (optind < argc) + read_symbols(argv[optind++]); + + if (files_source) + read_symbols_from_files(files_source); + + list_for_each_entry(mod, &modules, list) { + keep_no_trim_symbols(mod); + + if (mod->dump_file || mod->is_vmlinux) + continue; + + check_modname_len(mod); + check_exports(mod); + } + + if (unused_exports_white_list) + handle_white_list_exports(unused_exports_white_list); + + list_for_each_entry(mod, &modules, list) { + if (mod->dump_file) + continue; + + if (mod->is_vmlinux) + write_vmlinux_export_c_file(mod); + else + write_mod_c_file(mod); + } + + if (missing_namespace_deps) + write_namespace_deps_files(missing_namespace_deps); + + if (dump_write) + write_dump(dump_write); + if (sec_mismatch_count && !sec_mismatch_warn_only) + error("Section mismatches detected.\n" + "Set CONFIG_SECTION_MISMATCH_WARN_ONLY=y to allow them.\n"); + + if (nr_unresolved > MAX_UNRESOLVED_REPORTS) + warn("suppressed %u unresolved symbol warnings because there were too many)\n", + nr_unresolved - MAX_UNRESOLVED_REPORTS); + + return error_occurred ? 1 : 0; +} diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h new file mode 100644 index 000000000..d5f6d8283 --- /dev/null +++ b/scripts/mod/modpost.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <byteswap.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> +#include <elf.h> +#include "../../include/linux/module_symbol.h" + +#include <list_types.h> +#include "elfconfig.h" + +/* On BSD-alike OSes elf.h defines these according to host's word size */ +#undef ELF_ST_BIND +#undef ELF_ST_TYPE +#undef ELF_R_SYM +#undef ELF_R_TYPE + +#if KERNEL_ELFCLASS == ELFCLASS32 + +#define Elf_Ehdr Elf32_Ehdr +#define Elf_Shdr Elf32_Shdr +#define Elf_Sym Elf32_Sym +#define Elf_Addr Elf32_Addr +#define Elf_Section Elf32_Half +#define ELF_ST_BIND ELF32_ST_BIND +#define ELF_ST_TYPE ELF32_ST_TYPE + +#define Elf_Rel Elf32_Rel +#define Elf_Rela Elf32_Rela +#define ELF_R_SYM ELF32_R_SYM +#define ELF_R_TYPE ELF32_R_TYPE +#else + +#define Elf_Ehdr Elf64_Ehdr +#define Elf_Shdr Elf64_Shdr +#define Elf_Sym Elf64_Sym +#define Elf_Addr Elf64_Addr +#define Elf_Section Elf64_Half +#define ELF_ST_BIND ELF64_ST_BIND +#define ELF_ST_TYPE ELF64_ST_TYPE + +#define Elf_Rel Elf64_Rel +#define Elf_Rela Elf64_Rela +#define ELF_R_SYM ELF64_R_SYM +#define ELF_R_TYPE ELF64_R_TYPE +#endif + +#define bswap(x) \ +({ \ + _Static_assert(sizeof(x) == 1 || sizeof(x) == 2 || \ + sizeof(x) == 4 || sizeof(x) == 8, "bug"); \ + (typeof(x))(sizeof(x) == 2 ? bswap_16(x) : \ + sizeof(x) == 4 ? bswap_32(x) : \ + sizeof(x) == 8 ? bswap_64(x) : \ + x); \ +}) + +#define TO_NATIVE(x) \ + (target_is_big_endian == host_is_big_endian ? x : bswap(x)) + +#define __get_unaligned_t(type, ptr) ({ \ + const struct { type x; } __attribute__((__packed__)) *__pptr = \ + (typeof(__pptr))(ptr); \ + __pptr->x; \ +}) + +#define get_unaligned(ptr) __get_unaligned_t(typeof(*(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])) + +#define strstarts(str, prefix) (strncmp(str, prefix, strlen(prefix)) == 0) + +struct buffer { + char *p; + int pos; + int size; +}; + +void __attribute__((format(printf, 2, 3))) +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 + * @modname: name of the builtin module (only for vmlinux) + * @str: a string for MODULE_ALIAS() + */ +struct module_alias { + struct list_head node; + char *builtin_modname; + 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 is_vmlinux; + bool seen; + bool has_init; + bool has_cleanup; + 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[]; +}; + +struct elf_info { + size_t size; + Elf_Ehdr *hdr; + Elf_Shdr *sechdrs; + Elf_Sym *symtab_start; + Elf_Sym *symtab_stop; + unsigned int export_symbol_secndx; /* .export_symbol section */ + char *strtab; + char *modinfo; + unsigned int modinfo_len; + char *no_trim_symbol; + unsigned int no_trim_symbol_len; + + /* support for 32bit section numbers */ + + unsigned int num_sections; /* max_secindex + 1 */ + unsigned int secindex_strings; + /* if Nth symbol table entry has .st_shndx = SHN_XINDEX, + * take shndx from symtab_shndx_start[N] instead */ + Elf32_Word *symtab_shndx_start; + Elf32_Word *symtab_shndx_stop; + + struct symsearch *symsearch; +}; + +/* Accessor for sym->st_shndx, hides ugliness of "64k sections" */ +static inline unsigned int get_secindex(const struct elf_info *info, + const Elf_Sym *sym) +{ + unsigned int index = sym->st_shndx; + + /* + * Elf{32,64}_Sym::st_shndx is 2 byte. Big section numbers are available + * in the .symtab_shndx section. + */ + if (index == SHN_XINDEX) + return info->symtab_shndx_start[sym - info->symtab_start]; + + /* + * Move reserved section indices SHN_LORESERVE..SHN_HIRESERVE out of + * the way to UINT_MAX-255..UINT_MAX, to avoid conflicting with real + * section indices. + */ + if (index >= SHN_LORESERVE && index <= SHN_HIRESERVE) + return index - SHN_HIRESERVE - 1; + + return index; +} + +/* + * If there's no name there, ignore it; likewise, ignore it if it's + * one of the magic symbols emitted used by current tools. + * + * Internal symbols created by tools should be ignored by modpost. + */ +static inline bool is_valid_name(struct elf_info *elf, Elf_Sym *sym) +{ + const char *name = elf->strtab + sym->st_name; + + if (!name || !strlen(name)) + return false; + return !is_mapping_symbol(name); +} + +/* symsearch.c */ +void symsearch_init(struct elf_info *elf); +void symsearch_finish(struct elf_info *elf); +Elf_Sym *symsearch_find_nearest(struct elf_info *elf, Elf_Addr addr, + unsigned int secndx, bool allow_negative, + Elf_Addr min_distance); + +/* file2alias.c */ +void handle_moddevtable(struct module *mod, struct elf_info *info, + Elf_Sym *sym, const char *symname); + +/* 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); + +void __attribute__((format(printf, 3, 4))) +modpost_log(bool is_error, struct module *mod, const char *fmt, ...); + +/* + * warn - show the given message, then let modpost continue running, still + * allowing modpost to exit successfully. This should be used when + * we still allow to generate vmlinux and modules. + * + * error - show the given message, then let modpost continue running, but fail + * in the end. This should be used when we should stop building vmlinux + * or modules, but we can continue running modpost to catch as many + * issues as possible. + * + * 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(false, NULL, fmt, ##args) +#define error(fmt, args...) modpost_log(true, NULL, 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 new file mode 100644 index 000000000..3dd28b4d0 --- /dev/null +++ b/scripts/mod/sumversion.c @@ -0,0 +1,413 @@ +#include <netinet/in.h> +#ifdef __sun__ +#include <inttypes.h> +#else +#include <stdint.h> +#endif +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <limits.h> + +#include <xalloc.h> +#include "modpost.h" + +/* + * Stolen form Cryptographic API. + * + * MD4 Message Digest Algorithm (RFC1320). + * + * Implementation derived from Andrew Tridgell and Steve French's + * CIFS MD4 implementation, and the cryptoapi implementation + * originally based on the public domain implementation written + * by Colin Plumb in 1993. + * + * Copyright (c) Andrew Tridgell 1997-1998. + * Modified by Steve French (sfrench@us.ibm.com) 2002 + * Copyright (c) Cryptoapi developers. + * Copyright (c) 2002 David S. Miller (davem@redhat.com) + * Copyright (c) 2002 James Morris <jmorris@intercode.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#define MD4_DIGEST_SIZE 16 +#define MD4_HMAC_BLOCK_SIZE 64 +#define MD4_BLOCK_WORDS 16 +#define MD4_HASH_WORDS 4 + +struct md4_ctx { + uint32_t hash[MD4_HASH_WORDS]; + uint32_t block[MD4_BLOCK_WORDS]; + uint64_t byte_count; +}; + +static inline uint32_t lshift(uint32_t x, unsigned int s) +{ + x &= 0xFFFFFFFF; + return ((x << s) & 0xFFFFFFFF) | (x >> (32 - s)); +} + +static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | ((~x) & z); +} + +static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | (x & z) | (y & z); +} + +static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z) +{ + return x ^ y ^ z; +} + +#define ROUND1(a,b,c,d,k,s) (a = lshift(a + F(b,c,d) + k, s)) +#define ROUND2(a,b,c,d,k,s) (a = lshift(a + G(b,c,d) + k + (uint32_t)0x5A827999,s)) +#define ROUND3(a,b,c,d,k,s) (a = lshift(a + H(b,c,d) + k + (uint32_t)0x6ED9EBA1,s)) + +/* XXX: this stuff can be optimized */ +static inline void le32_to_cpu_array(uint32_t *buf, unsigned int words) +{ + while (words--) { + *buf = ntohl(*buf); + buf++; + } +} + +static inline void cpu_to_le32_array(uint32_t *buf, unsigned int words) +{ + while (words--) { + *buf = htonl(*buf); + buf++; + } +} + +static void md4_transform(uint32_t *hash, uint32_t const *in) +{ + uint32_t a, b, c, d; + + a = hash[0]; + b = hash[1]; + c = hash[2]; + d = hash[3]; + + ROUND1(a, b, c, d, in[0], 3); + ROUND1(d, a, b, c, in[1], 7); + ROUND1(c, d, a, b, in[2], 11); + ROUND1(b, c, d, a, in[3], 19); + ROUND1(a, b, c, d, in[4], 3); + ROUND1(d, a, b, c, in[5], 7); + ROUND1(c, d, a, b, in[6], 11); + ROUND1(b, c, d, a, in[7], 19); + ROUND1(a, b, c, d, in[8], 3); + ROUND1(d, a, b, c, in[9], 7); + ROUND1(c, d, a, b, in[10], 11); + ROUND1(b, c, d, a, in[11], 19); + ROUND1(a, b, c, d, in[12], 3); + ROUND1(d, a, b, c, in[13], 7); + ROUND1(c, d, a, b, in[14], 11); + ROUND1(b, c, d, a, in[15], 19); + + ROUND2(a, b, c, d,in[ 0], 3); + ROUND2(d, a, b, c, in[4], 5); + ROUND2(c, d, a, b, in[8], 9); + ROUND2(b, c, d, a, in[12], 13); + ROUND2(a, b, c, d, in[1], 3); + ROUND2(d, a, b, c, in[5], 5); + ROUND2(c, d, a, b, in[9], 9); + ROUND2(b, c, d, a, in[13], 13); + ROUND2(a, b, c, d, in[2], 3); + ROUND2(d, a, b, c, in[6], 5); + ROUND2(c, d, a, b, in[10], 9); + ROUND2(b, c, d, a, in[14], 13); + ROUND2(a, b, c, d, in[3], 3); + ROUND2(d, a, b, c, in[7], 5); + ROUND2(c, d, a, b, in[11], 9); + ROUND2(b, c, d, a, in[15], 13); + + ROUND3(a, b, c, d,in[ 0], 3); + ROUND3(d, a, b, c, in[8], 9); + ROUND3(c, d, a, b, in[4], 11); + ROUND3(b, c, d, a, in[12], 15); + ROUND3(a, b, c, d, in[2], 3); + ROUND3(d, a, b, c, in[10], 9); + ROUND3(c, d, a, b, in[6], 11); + ROUND3(b, c, d, a, in[14], 15); + ROUND3(a, b, c, d, in[1], 3); + ROUND3(d, a, b, c, in[9], 9); + ROUND3(c, d, a, b, in[5], 11); + ROUND3(b, c, d, a, in[13], 15); + ROUND3(a, b, c, d, in[3], 3); + ROUND3(d, a, b, c, in[11], 9); + ROUND3(c, d, a, b, in[7], 11); + ROUND3(b, c, d, a, in[15], 15); + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; +} + +static inline void md4_transform_helper(struct md4_ctx *ctx) +{ + le32_to_cpu_array(ctx->block, ARRAY_SIZE(ctx->block)); + md4_transform(ctx->hash, ctx->block); +} + +static void md4_init(struct md4_ctx *mctx) +{ + mctx->hash[0] = 0x67452301; + mctx->hash[1] = 0xefcdab89; + mctx->hash[2] = 0x98badcfe; + mctx->hash[3] = 0x10325476; + mctx->byte_count = 0; +} + +static void md4_update(struct md4_ctx *mctx, + const unsigned char *data, unsigned int len) +{ + const uint32_t avail = sizeof(mctx->block) - (mctx->byte_count & 0x3f); + + mctx->byte_count += len; + + if (avail > len) { + memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), + data, len); + return; + } + + memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), + data, avail); + + md4_transform_helper(mctx); + data += avail; + len -= avail; + + while (len >= sizeof(mctx->block)) { + memcpy(mctx->block, data, sizeof(mctx->block)); + md4_transform_helper(mctx); + data += sizeof(mctx->block); + len -= sizeof(mctx->block); + } + + memcpy(mctx->block, data, len); +} + +static void md4_final_ascii(struct md4_ctx *mctx, char *out, unsigned int len) +{ + const unsigned int offset = mctx->byte_count & 0x3f; + char *p = (char *)mctx->block + offset; + int padding = 56 - (offset + 1); + + *p++ = 0x80; + if (padding < 0) { + memset(p, 0x00, padding + sizeof (uint64_t)); + md4_transform_helper(mctx); + p = (char *)mctx->block; + padding = 56; + } + + memset(p, 0, padding); + mctx->block[14] = mctx->byte_count << 3; + mctx->block[15] = mctx->byte_count >> 29; + le32_to_cpu_array(mctx->block, (sizeof(mctx->block) - + sizeof(uint64_t)) / sizeof(uint32_t)); + md4_transform(mctx->hash, mctx->block); + cpu_to_le32_array(mctx->hash, ARRAY_SIZE(mctx->hash)); + + snprintf(out, len, "%08X%08X%08X%08X", + mctx->hash[0], mctx->hash[1], mctx->hash[2], mctx->hash[3]); +} + +static inline void add_char(unsigned char c, struct md4_ctx *md) +{ + md4_update(md, &c, 1); +} + +static int parse_string(const char *file, unsigned long len, + struct md4_ctx *md) +{ + unsigned long i; + + add_char(file[0], md); + for (i = 1; i < len; i++) { + add_char(file[i], md); + if (file[i] == '"' && file[i-1] != '\\') + break; + } + return i; +} + +static int parse_comment(const char *file, unsigned long len) +{ + unsigned long i; + + for (i = 2; i < len; i++) { + if (file[i-1] == '*' && file[i] == '/') + break; + } + return i; +} + +/* FIXME: Handle .s files differently (eg. # starts comments) --RR */ +static int parse_file(const char *fname, struct md4_ctx *md) +{ + char *file; + unsigned long i, len; + + file = read_text_file(fname); + len = strlen(file); + + for (i = 0; i < len; i++) { + /* Collapse and ignore \ and CR. */ + if (file[i] == '\\' && (i+1 < len) && file[i+1] == '\n') { + i++; + continue; + } + + /* Ignore whitespace */ + if (isspace(file[i])) + continue; + + /* Handle strings as whole units */ + if (file[i] == '"') { + i += parse_string(file+i, len - i, md); + continue; + } + + /* Comments: ignore */ + if (file[i] == '/' && file[i+1] == '*') { + i += parse_comment(file+i, len - i); + continue; + } + + add_char(file[i], md); + } + free(file); + return 1; +} +/* Check whether the file is a static library or not */ +static bool is_static_library(const char *objfile) +{ + int len = strlen(objfile); + + return objfile[len - 2] == '.' && objfile[len - 1] == 'a'; +} + +/* We have dir/file.o. Open dir/.file.o.cmd, look for source_ and deps_ line + * to figure out source files. */ +static int parse_source_files(const char *objfile, struct md4_ctx *md) +{ + char *cmd, *file, *line, *dir, *pos; + const char *base; + int dirlen, ret = 0, check_files = 0; + + cmd = xmalloc(strlen(objfile) + sizeof("..cmd")); + + 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'; + + file = read_text_file(cmd); + + pos = file; + + /* Sum all files in the same dir or subdirs. */ + while ((line = get_line(&pos))) { + char* p; + + /* trim the leading spaces away */ + while (isspace(*line)) + line++; + p = line; + + if (strstarts(line, "source_")) { + p = strrchr(line, ' '); + if (!p) { + warn("malformed line: %s\n", line); + goto out_file; + } + p++; + if (!parse_file(p, md)) { + warn("could not open %s: %s\n", + p, strerror(errno)); + goto out_file; + } + continue; + } + if (strstarts(line, "deps_")) { + check_files = 1; + continue; + } + if (!check_files) + continue; + + /* Continue until line does not end with '\' */ + if ( *(p + strlen(p)-1) != '\\') + break; + /* Terminate line at first space, to get rid of final ' \' */ + while (*p) { + if (isspace(*p)) { + *p = '\0'; + break; + } + p++; + } + + /* Check if this file is in same dir as objfile */ + if ((strstr(line, dir)+strlen(dir)-1) == strrchr(line, '/')) { + if (!parse_file(line, md)) { + warn("could not open %s: %s\n", + line, strerror(errno)); + goto out_file; + } + + } + + } + + /* Everyone parsed OK */ + ret = 1; +out_file: + free(file); + free(dir); + free(cmd); + return ret; +} + +/* Calc and record src checksum. */ +void get_src_version(const char *modname, char sum[], unsigned sumlen) +{ + char *buf, *pos; + struct md4_ctx md; + char *fname; + char filelist[PATH_MAX + 1]; + + /* objects for a module are listed in the first line of *.mod file. */ + snprintf(filelist, sizeof(filelist), "%s.mod", modname); + + buf = read_text_file(filelist); + pos = buf; + + md4_init(&md); + while ((fname = strsep(&pos, "\n"))) { + if (!*fname) + continue; + if (!(is_static_library(fname)) && + !parse_source_files(fname, &md)) + goto free; + } + + md4_final_ascii(&md, sum, sumlen); +free: + free(buf); +} diff --git a/scripts/mod/symsearch.c b/scripts/mod/symsearch.c new file mode 100644 index 000000000..b9737b92f --- /dev/null +++ b/scripts/mod/symsearch.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Helper functions for finding the symbol in an ELF which is "nearest" + * to a given address. + */ +#include <xalloc.h> +#include "modpost.h" + +struct syminfo { + unsigned int symbol_index; + unsigned int section_index; + Elf_Addr addr; +}; + +/* + * Container used to hold an entire binary search table. + * Entries in table are ascending, sorted first by section_index, + * then by addr, and last by symbol_index. The sorting by + * symbol_index is used to ensure predictable behavior when + * multiple symbols are present with the same address; all + * symbols past the first are effectively ignored, by eliding + * them in symsearch_fixup(). + */ +struct symsearch { + unsigned int table_size; + struct syminfo table[]; +}; + +static int syminfo_compare(const void *s1, const void *s2) +{ + const struct syminfo *sym1 = s1; + const struct syminfo *sym2 = s2; + + if (sym1->section_index > sym2->section_index) + return 1; + if (sym1->section_index < sym2->section_index) + return -1; + if (sym1->addr > sym2->addr) + return 1; + if (sym1->addr < sym2->addr) + return -1; + if (sym1->symbol_index > sym2->symbol_index) + return 1; + if (sym1->symbol_index < sym2->symbol_index) + return -1; + return 0; +} + +static unsigned int symbol_count(struct elf_info *elf) +{ + unsigned int result = 0; + + for (Elf_Sym *sym = elf->symtab_start; sym < elf->symtab_stop; sym++) { + if (is_valid_name(elf, sym)) + result++; + } + return result; +} + +/* + * Populate the search array that we just allocated. + * Be slightly paranoid here. The ELF file is mmap'd and could + * conceivably change between symbol_count() and symsearch_populate(). + * If we notice any difference, bail out rather than potentially + * propagating errors or crashing. + */ +static void symsearch_populate(struct elf_info *elf, + struct syminfo *table, + unsigned int table_size) +{ + bool is_arm = (elf->hdr->e_machine == EM_ARM); + + for (Elf_Sym *sym = elf->symtab_start; sym < elf->symtab_stop; sym++) { + if (is_valid_name(elf, sym)) { + if (table_size-- == 0) + fatal("%s: size mismatch\n", __func__); + table->symbol_index = sym - elf->symtab_start; + table->section_index = get_secindex(elf, sym); + table->addr = sym->st_value; + + /* + * For ARM Thumb instruction, the bit 0 of st_value is + * set if the symbol is STT_FUNC type. Mask it to get + * the address. + */ + if (is_arm && ELF_ST_TYPE(sym->st_info) == STT_FUNC) + table->addr &= ~1; + + table++; + } + } + + if (table_size != 0) + fatal("%s: size mismatch\n", __func__); +} + +/* + * Do any fixups on the table after sorting. + * For now, this just finds adjacent entries which have + * the same section_index and addr, and it propagates + * the first symbol_index over the subsequent entries, + * so that only one symbol_index is seen for any given + * section_index and addr. This ensures that whether + * we're looking at an address from "above" or "below" + * that we see the same symbol_index. + * This does leave some duplicate entries in the table; + * in practice, these are a small fraction of the + * total number of entries, and they are harmless to + * the binary search algorithm other than a few occasional + * unnecessary comparisons. + */ +static void symsearch_fixup(struct syminfo *table, unsigned int table_size) +{ + /* Don't look at index 0, it will never change. */ + for (unsigned int i = 1; i < table_size; i++) { + if (table[i].addr == table[i - 1].addr && + table[i].section_index == table[i - 1].section_index) { + table[i].symbol_index = table[i - 1].symbol_index; + } + } +} + +void symsearch_init(struct elf_info *elf) +{ + unsigned int table_size = symbol_count(elf); + + 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); + qsort(elf->symsearch->table, table_size, + sizeof(struct syminfo), syminfo_compare); + + symsearch_fixup(elf->symsearch->table, table_size); +} + +void symsearch_finish(struct elf_info *elf) +{ + free(elf->symsearch); + elf->symsearch = NULL; +} + +/* + * Find the syminfo which is in secndx and "nearest" to addr. + * allow_negative: allow returning a symbol whose address is > addr. + * min_distance: ignore symbols which are further away than this. + * + * Returns a pointer into the symbol table for success. + * Returns NULL if no legal symbol is found within the requested range. + */ +Elf_Sym *symsearch_find_nearest(struct elf_info *elf, Elf_Addr addr, + unsigned int secndx, bool allow_negative, + Elf_Addr min_distance) +{ + unsigned int hi = elf->symsearch->table_size; + unsigned int lo = 0; + struct syminfo *table = elf->symsearch->table; + struct syminfo target; + + target.addr = addr; + target.section_index = secndx; + target.symbol_index = ~0; /* compares greater than any actual index */ + while (hi > lo) { + unsigned int mid = lo + (hi - lo) / 2; /* Avoids overflow */ + + if (syminfo_compare(&table[mid], &target) > 0) + hi = mid; + else + lo = mid + 1; + } + + /* + * table[hi], if it exists, is the first entry in the array which + * lies beyond target. table[hi - 1], if it exists, is the last + * entry in the array which comes before target, including the + * case where it perfectly matches the section and the address. + * + * Note -- if the address we're looking up falls perfectly + * in the middle of two symbols, this is written to always + * prefer the symbol with the lower address. + */ + Elf_Sym *result = NULL; + + if (allow_negative && + hi < elf->symsearch->table_size && + table[hi].section_index == secndx && + table[hi].addr - addr <= min_distance) { + min_distance = table[hi].addr - addr; + result = &elf->symtab_start[table[hi].symbol_index]; + } + if (hi > 0 && + table[hi - 1].section_index == secndx && + addr - table[hi - 1].addr <= min_distance) { + result = &elf->symtab_start[table[hi - 1].symbol_index]; + } + return result; +} diff --git a/scripts/module-common.c b/scripts/module-common.c new file mode 100644 index 000000000..12fbc6d3a --- /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 new file mode 100644 index 000000000..d0f200428 --- /dev/null +++ b/scripts/module.lds.S @@ -0,0 +1,63 @@ +/* + * Common module linker script, always used when linking a module. + * Archs are free to supply their own linker scripts. ld will + * combine them automatically. + */ + +#include <asm-generic/codetag.lds.h> + +SECTIONS { + /DISCARD/ : { + *(.discard) + *(.discard.*) + *(.export_symbol) + *(.no_trim_symbol) + } + + __ksymtab 0 : ALIGN(8) { *(SORT(___ksymtab+*)) } + __kcrctab 0 : ALIGN(4) { *(SORT(___kcrctab+*)) } + __kflagstab 0 : ALIGN(1) { *(SORT(___kflagstab+*)) } + + .ctors 0 : ALIGN(8) { *(SORT(.ctors.*)) *(.ctors) } + .init_array 0 : ALIGN(8) { *(SORT(.init_array.*)) *(.init_array) } + + .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 0 : { *(__patchable_function_entries) } + + .init.klp_funcs 0 : ALIGN(8) { KEEP(*(.init.klp_funcs)) } + .init.klp_objects 0 : ALIGN(8) { KEEP(*(.init.klp_objects)) } + +#ifdef CONFIG_ARCH_USES_CFI_TRAPS + __kcfi_traps 0 : { KEEP(*(.kcfi_traps)) } +#endif + +#ifndef CONFIG_ARCH_WANTS_MODULES_TEXT_SECTIONS + .text 0 : { + *(.text .text.[0-9a-zA-Z_]*) + } +#endif + + .bss 0 : { + *(.bss .bss.[0-9a-zA-Z_]*) + *(.bss..L*) + } + + .data 0 : { + *(.data .data.[0-9a-zA-Z_]*) + *(.data..L*) + } + + .rodata 0 : { + *(.rodata .rodata.[0-9a-zA-Z_]*) + *(.rodata..L*) + } + + MOD_SEPARATE_CODETAG_SECTIONS() +} + +/* bring in arch-specific sections */ +#include <asm/module.lds.h> diff --git a/scripts/modules-check.sh b/scripts/modules-check.sh new file mode 100755 index 000000000..4c8da90de --- /dev/null +++ b/scripts/modules-check.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +set -e + +if [ $# != 1 ]; then + echo "Usage: $0 <modules.order>" >& 2 + exit 1 +fi + +exit_code=0 + +# Check uniqueness of module names +check_same_name_modules() +{ + for m in $(sed 's:.*/::' "$1" | sort | uniq -d) + do + echo "error: the following would cause module name conflict:" >&2 + sed -n "/\/$m/s:^\(.*\)\.o\$: \1.ko:p" "$1" >&2 + exit_code=1 + done +} + +check_same_name_modules "$1" + +exit $exit_code diff --git a/scripts/nsdeps b/scripts/nsdeps new file mode 100644 index 000000000..d4b5ebcc2 --- /dev/null +++ b/scripts/nsdeps @@ -0,0 +1,54 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Linux kernel symbol namespace import generator +# +# This script requires a minimum spatch version. +SPATCH_REQ_VERSION="1.0.4" + +DIR="$(dirname $(readlink -f $0))/.." +SPATCH="`which ${SPATCH:=spatch}`" +if [ ! -x "$SPATCH" ]; then + echo 'spatch is part of the Coccinelle project and is available at https://coccinelle.gitlabpages.inria.fr/website' + exit 1 +fi + +SPATCH_VERSION=$($SPATCH --version | head -1 | awk '{print $3}') + +if ! { echo "$SPATCH_REQ_VERSION"; echo "$SPATCH_VERSION"; } | sort -CV ; then + echo "spatch needs to be version $SPATCH_REQ_VERSION or higher" + exit 1 +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 +} + +generate_deps() { + local mod=${1%.ko:} + shift + local namespaces="$*" + 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." + generate_deps_for_ns $ns "$mod_source_files" + # sort the imports + for source_file in $mod_source_files; do + sed '/MODULE_IMPORT_NS/Q' $source_file > ${source_file}.tmp + offset=$(wc -l ${source_file}.tmp | awk '{print $1;}') + cat $source_file | grep MODULE_IMPORT_NS | LC_ALL=C sort -u >> ${source_file}.tmp + tail -n +$((offset +1)) ${source_file} | grep -v MODULE_IMPORT_NS >> ${source_file}.tmp + if ! diff -q ${source_file} ${source_file}.tmp; then + mv ${source_file}.tmp ${source_file} + else + rm ${source_file}.tmp + fi + done + done +} + +while read line +do + generate_deps $line +done < modules.nsdeps diff --git a/scripts/objdiff b/scripts/objdiff new file mode 100755 index 000000000..0685bc3ce --- /dev/null +++ b/scripts/objdiff @@ -0,0 +1,162 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only + +# objdiff - a small script for validating that a commit or series of commits +# didn't change object code. +# +# Copyright 2014, Jason Cooper <jason@lakedaemon.net> +# + +# usage example: +# +# $ git checkout COMMIT_A +# $ <your fancy build command here> +# $ ./scripts/objdiff record path/to/*.o +# +# $ git checkout COMMIT_B +# $ <your fancy build command here> +# $ ./scripts/objdiff record path/to/*.o +# +# $ ./scripts/objdiff diff COMMIT_A COMMIT_B +# $ + +# And to clean up (everything is in .objdiff/*) +# $ ./scripts/objdiff clean all +# +# Note: 'make mrproper' will also remove .objdiff + +SRCTREE=$(cd $(git rev-parse --show-toplevel 2>/dev/null); pwd) + +if [ -z "$SRCTREE" ]; then + echo >&2 "ERROR: Not a git repository." + exit 1 +fi + +TMPD=$SRCTREE/.objdiff + +usage() { + echo >&2 "Usage: $0 <command> <args>" + echo >&2 " record <list of object files or directories>" + echo >&2 " diff <commitA> <commitB>" + echo >&2 " clean all | <commit>" + exit 1 +} + +get_output_dir() { + dir=${1%/*} + + if [ "$dir" = "$1" ]; then + dir=. + fi + + dir=$(cd $dir; pwd) + + echo $TMPD/$CMT${dir#$SRCTREE} +} + +do_objdump() { + dir=$(get_output_dir $1) + base=${1##*/} + stripped=$dir/${base%.o}.stripped + dis=$dir/${base%.o}.dis + + [ ! -d "$dir" ] && mkdir -p $dir + + # remove addresses for a cleaner diff + # http://dummdida.tumblr.com/post/60924060451/binary-diff-between-libc-from-scientificlinux-and + $STRIP -g $1 -R __bug_table -R .note -R .comment -o $stripped + $OBJDUMP -D $stripped | sed -e "s/^[[:space:]]\+[0-9a-f]\+//" -e "s:^$stripped:$1:" > $dis +} + +dorecord() { + [ $# -eq 0 ] && usage + + FILES="$*" + + CMT="`git rev-parse --short HEAD`" + + STRIP="${CROSS_COMPILE}strip" + OBJDUMP="${CROSS_COMPILE}objdump" + + for d in $FILES; do + if [ -d "$d" ]; then + for f in $(find $d -name '*.o') + do + do_objdump $f + done + else + do_objdump $d + fi + done +} + +dodiff() { + [ $# -ne 2 ] && [ $# -ne 0 ] && usage + + if [ $# -eq 0 ]; then + SRC="`git rev-parse --short HEAD^`" + DST="`git rev-parse --short HEAD`" + else + SRC="`git rev-parse --short $1`" + DST="`git rev-parse --short $2`" + fi + + DIFF="`which colordiff`" + + if [ ${#DIFF} -eq 0 ] || [ ! -x "$DIFF" ]; then + DIFF="`which diff`" + fi + + SRCD="$TMPD/$SRC" + DSTD="$TMPD/$DST" + + if [ ! -d "$SRCD" ]; then + echo >&2 "ERROR: $SRCD doesn't exist" + exit 1 + fi + + if [ ! -d "$DSTD" ]; then + echo >&2 "ERROR: $DSTD doesn't exist" + exit 1 + fi + + $DIFF -Nurd $SRCD $DSTD +} + +doclean() { + [ $# -eq 0 ] && usage + [ $# -gt 1 ] && usage + + if [ "x$1" = "xall" ]; then + rm -rf $TMPD/* + else + CMT="`git rev-parse --short $1`" + + if [ -d "$TMPD/$CMT" ]; then + rm -rf $TMPD/$CMT + else + echo >&2 "$CMT not found" + fi + fi +} + +[ $# -eq 0 ] && usage + +case "$1" in + record) + shift + dorecord $* + ;; + diff) + shift + dodiff $* + ;; + clean) + shift + doclean $* + ;; + *) + echo >&2 "Unrecognized command '$1'" + exit 1 + ;; +esac diff --git a/scripts/objdump-func b/scripts/objdump-func new file mode 100755 index 000000000..7b15b873d --- /dev/null +++ b/scripts/objdump-func @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Disassemble a single function. +# +# usage: objdump-func <file> <func> [<func> ...] + +set -o errexit +set -o nounset + +OBJDUMP="${CROSS_COMPILE:-}objdump" + +command -v gawk >/dev/null 2>&1 || die "gawk isn't installed" + +usage() { + echo "usage: objdump-func <file> <func> [<func> ...]" >&2 + exit 1 +} + +[[ $# -lt 2 ]] && usage + +OBJ=$1; shift +FUNCS=("$@") + +${OBJDUMP} -wdr $OBJ | gawk -M -v _funcs="${FUNCS[*]}" ' + BEGIN { split(_funcs, funcs); } + /^$/ { func_match=0; } + /<.*>:/ { + f = gensub(/.*<(.*)>:/, "\\1", 1); + for (i in funcs) { + # match compiler-added suffixes like ".cold", etc + if (f ~ "^" funcs[i] "(\\..*)?") { + func_match = 1; + base = strtonum("0x" $1); + break; + } + } + } + { + if (func_match) { + addr = strtonum("0x" $1); + printf("%04x ", addr - base); + print; + } + }' diff --git a/scripts/orc_hash.sh b/scripts/orc_hash.sh new file mode 100644 index 000000000..466611aa0 --- /dev/null +++ b/scripts/orc_hash.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) Meta Platforms, Inc. and affiliates. + +set -e + +printf '%s' '#define ORC_HASH ' + +awk ' +/^#define ORC_(REG|TYPE)_/ { print } +/^struct orc_entry {$/ { p=1 } +p { print } +/^}/ { p=0 }' | + sha1sum | + cut -d " " -f 1 | + sed 's/\([0-9a-f]\{2\}\)/0x\1,/g' diff --git a/scripts/package/PKGBUILD b/scripts/package/PKGBUILD new file mode 100644 index 000000000..66e4b6a37 --- /dev/null +++ b/scripts/package/PKGBUILD @@ -0,0 +1,134 @@ +# 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="$(echo "${KERNELRELEASE}" | sed 's/-\(rc[0-9]\+\)/\1/;s/-/_/g')" +# 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" + + echo "Installing unstripped vDSO(s)..." + ${MAKE} INSTALL_MOD_PATH="${pkgdir}/usr" vdso_install +} + +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 new file mode 100755 index 000000000..ba1defc61 --- /dev/null +++ b/scripts/package/builddeb @@ -0,0 +1,176 @@ +#!/bin/sh +# +# builddeb 1.3 +# Copyright 2003 Wichert Akkerman <wichert@wiggy.net> +# +# 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. 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 -eu + +is_enabled() { + grep -q "^$1=y" include/config/auto.conf +} + +if_enabled_echo() { + if is_enabled "$1"; then + echo -n "$2" + elif [ $# -ge 3 ]; then + echo -n "$3" + fi +} + +install_linux_image () { + pname=$1 + pdir=debian/$1 + + # Only some architectures with OF support have this target + if is_enabled CONFIG_OF_EARLY_FLATTREE && [ -d "${srctree}/arch/${SRCARCH}/boot/dts" ]; then + ${MAKE} -f ${srctree}/Makefile INSTALL_DTBS_PATH="${pdir}/usr/lib/linux-image-${KERNELRELEASE}" dtbs_install + fi + + ${MAKE} -f ${srctree}/Makefile INSTALL_MOD_PATH="${pdir}" INSTALL_MOD_STRIP=1 modules_install + rm -f "${pdir}/lib/modules/${KERNELRELEASE}/build" + + # Install the kernel + if [ "${ARCH}" = um ] ; then + mkdir -p "${pdir}/usr/lib/uml/modules" + mv "${pdir}/lib/modules/${KERNELRELEASE}" "${pdir}/usr/lib/uml/modules/${KERNELRELEASE}" + mkdir -p "${pdir}/usr/bin" "${pdir}/usr/share/doc/${pname}" + cp System.map "${pdir}/usr/lib/uml/modules/${KERNELRELEASE}/System.map" + cp ${KCONFIG_CONFIG} "${pdir}/usr/share/doc/${pname}/config" + gzip "${pdir}/usr/share/doc/${pname}/config" + else + mkdir -p "${pdir}/boot" + cp System.map "${pdir}/boot/System.map-${KERNELRELEASE}" + cp ${KCONFIG_CONFIG} "${pdir}/boot/config-${KERNELRELEASE}" + fi + + # Not all arches have the same installed path in debian + # XXX: have each arch Makefile export a variable of the canonical image install + # path instead + case "${SRCARCH}" in + um) + installed_image_path="usr/bin/linux-${KERNELRELEASE}";; + parisc|mips|powerpc) + installed_image_path="boot/vmlinux-${KERNELRELEASE}";; + *) + installed_image_path="boot/vmlinuz-${KERNELRELEASE}";; + 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 /usr/share/kernel} + for script in postinst postrm preinst prerm; do + mkdir -p "${pdir}/DEBIAN" + cat <<-EOF > "${pdir}/DEBIAN/${script}" + #!/bin/sh + + set -e + + # Pass maintainer script parameters to hook scripts + export DEB_MAINT_PARAMS="\$*" + + # Tell initramfs builder whether it's wanted + export INITRD=$(if_enabled_echo CONFIG_BLK_DEV_INITRD Yes No) + + # 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}" + done +} + +install_linux_image_dbg () { + pdir=debian/$1 + + # Parse modules.order directly because 'make modules_install' may sign, + # compress modules, and then run unneeded depmod. + 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 + # perf + mkdir -p ${pdir}/usr/lib/debug/lib/modules/${KERNELRELEASE}/ + cp vmlinux ${pdir}/usr/lib/debug/lib/modules/${KERNELRELEASE}/ + # systemtap + mkdir -p ${pdir}/usr/lib/debug/boot/ + ln -s ../lib/modules/${KERNELRELEASE}/vmlinux ${pdir}/usr/lib/debug/boot/vmlinux-${KERNELRELEASE} + # kdump-tools + ln -s lib/modules/${KERNELRELEASE}/vmlinux ${pdir}/usr/lib/debug/vmlinux-${KERNELRELEASE} +} + +install_kernel_headers () { + pdir=debian/$1 + version=${1#linux-headers-} + + # Override $CC only for cross-compiles, to not unnecessarily rebuild + # scripts/ including plugins, which may lead to a full kernel rebuild. + if [ -n "${CROSS_COMPILE}" ]; then + CC="${DEB_HOST_GNU_TYPE}-gcc" "${srctree}/scripts/package/install-extmod-build" "${pdir}/usr/src/linux-headers-${version}" + else + "${srctree}/scripts/package/install-extmod-build" "${pdir}/usr/src/linux-headers-${version}" + fi + + mkdir -p $pdir/lib/modules/$version/ + ln -s /usr/src/linux-headers-$version $pdir/lib/modules/$version/build +} + +install_libc_headers () { + pdir=debian/$1 + + $MAKE -f $srctree/Makefile headers_install INSTALL_HDR_PATH=$pdir/usr + + # move asm headers to /usr/include/<libc-machine>/asm to match the structure + # used by Debian-based distros (to support multi-arch) + mkdir "$pdir/usr/include/${DEB_HOST_MULTIARCH}" + mv "$pdir/usr/include/asm" "$pdir/usr/include/${DEB_HOST_MULTIARCH}" +} + +package=$1 + +case "${package}" in +*-dbg) + install_linux_image_dbg "${package}";; +linux-image-*|user-mode-linux-*) + install_linux_image "${package}";; +linux-libc-dev) + install_libc_headers "${package}";; +linux-headers-*) + install_kernel_headers "${package}";; +esac diff --git a/scripts/package/buildtar b/scripts/package/buildtar new file mode 100755 index 000000000..cc87a473c --- /dev/null +++ b/scripts/package/buildtar @@ -0,0 +1,110 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +# +# buildtar 0.0.5 +# +# (C) 2004-2006 by Jan-Benedict Glaw <jbglaw@lug-owl.de> +# +# This script is used to compile a tarball from the currently +# prepared kernel. Based upon the builddeb script from +# Wichert Akkerman <wichert@wiggy.net>. +# + +set -eu + +# +# Some variables and settings used throughout the script +# +tmpdir=$1 + +# +# Clean-up and re-create the temporary directory +# +rm -rf -- "${tmpdir}" +mkdir -p -- "${tmpdir}/boot" + + +# +# Try to install dtbs +# +if grep -q '^CONFIG_OF_EARLY_FLATTREE=y' include/config/auto.conf; then + # Only some architectures with OF support have this target + if [ -d "${srctree}/arch/${SRCARCH}/boot/dts" ]; then + $MAKE ARCH="${ARCH}" -f ${srctree}/Makefile INSTALL_DTBS_PATH="${tmpdir}/boot/dtbs/${KERNELRELEASE}" dtbs_install + fi +fi + + +# +# Install modules +# +make ARCH="${ARCH}" -f ${srctree}/Makefile INSTALL_MOD_PATH="${tmpdir}" modules_install + + +# +# Install basic kernel files +# +cp -v -- "${objtree}/System.map" "${tmpdir}/boot/System.map-${KERNELRELEASE}" +cp -v -- "${KCONFIG_CONFIG}" "${tmpdir}/boot/config-${KERNELRELEASE}" +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 + alpha) + cp -v -- "${objtree}/arch/alpha/boot/vmlinux.gz" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" + ;; + parisc*) + 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 + cp -v -- "${objtree}/arch/mips/boot/compressed/vmlinux.ecoff" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" + elif [ -f "${objtree}/arch/mips/boot/compressed/vmlinux.srec" ]; then + cp -v -- "${objtree}/arch/mips/boot/compressed/vmlinux.srec" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" + elif [ -f "${objtree}/vmlinux.32" ]; then + cp -v -- "${objtree}/vmlinux.32" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" + elif [ -f "${objtree}/vmlinux.64" ]; then + cp -v -- "${objtree}/vmlinux.64" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" + elif [ -f "${objtree}/arch/mips/boot/vmlinux.bin" ]; then + cp -v -- "${objtree}/arch/mips/boot/vmlinux.bin" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" + elif [ -f "${objtree}/arch/mips/boot/vmlinux.ecoff" ]; then + cp -v -- "${objtree}/arch/mips/boot/vmlinux.ecoff" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" + elif [ -f "${objtree}/arch/mips/boot/vmlinux.srec" ]; then + cp -v -- "${objtree}/arch/mips/boot/vmlinux.srec" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" + elif [ -f "${objtree}/vmlinux" ]; then + cp -v -- "${objtree}/vmlinux" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}" + 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}" + break + fi + done + ;; + riscv) + case "${KBUILD_IMAGE##*/}" in + Image.*|vmlinuz.efi) + cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}";; + *) + cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinux-${KERNELRELEASE}";; + esac + ;; + *) + cp -v -- "${KBUILD_IMAGE}" "${tmpdir}/boot/vmlinuz-${KERNELRELEASE}" + ;; +esac diff --git a/scripts/package/debian/copyright b/scripts/package/debian/copyright new file mode 100644 index 000000000..4f1f06221 --- /dev/null +++ b/scripts/package/debian/copyright @@ -0,0 +1,16 @@ +This is a packaged upstream version of the Linux kernel. + +The sources may be found at most Linux archive sites, including: +https://www.kernel.org/pub/linux/kernel + +Copyright: 1991 - 2023 Linus Torvalds and others. + +The git repository for mainline kernel development is at: +git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991. + +On Debian GNU/Linux systems, the complete text of the GNU General Public +License version 2 can be found in `/usr/share/common-licenses/GPL-2'. diff --git a/scripts/package/debian/rules b/scripts/package/debian/rules new file mode 100755 index 000000000..a417a7f8b --- /dev/null +++ b/scripts/package/debian/rules @@ -0,0 +1,97 @@ +#!/usr/bin/make -f +# SPDX-License-Identifier: GPL-2.0-only + +# in case debian/rules is executed directly +export DEB_RULES_REQUIRES_ROOT := no + +include debian/rules.vars + +ifneq (,$(filter-out parallel=1,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))) + NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) + MAKEFLAGS += -j$(NUMJOBS) +endif + +# When KBUILD_VERBOSE is undefined (presumably you are directly working with +# the debianized tree), show verbose logs unless DEB_BUILD_OPTION=terse is set. +ifeq ($(origin KBUILD_VERBOSE),undefined) + ifeq (,$(filter terse,$(DEB_BUILD_OPTIONS))) + export KBUILD_VERBOSE := 1 + else + Q := @ + endif +endif + +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) \ + $(addprefix KBUILD_BUILD_VERSION=,$(revision)) \ + $(addprefix CROSS_COMPILE=,$(CROSS_COMPILE)) + +binary-targets := $(addprefix binary-, image image-dbg headers libc-dev) + +all-packages = $(shell dh_listpackages) +image-package = $(filter linux-image-% user-%, $(filter-out %-dbg, $(all-packages))) +image-dbg-package = $(filter %-dbg, $(all-packages)) +libc-dev-package = $(filter linux-libc-dev, $(all-packages)) +headers-package = $(filter linux-headers-%, $(all-packages)) + +mk-files = $(patsubst binary-%,debian/%.files,$1) +package = $($(@:binary-%=%-package)) + +# DH_OPTION is an environment variable common for all debhelper commands. +# We could 'export' it, but here it is passed from the command line to clarify +# 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) + $(Q)dh_prep $(DH_OPTIONS) + $(Q)+$(MAKE) $(make-opts) run-command KBUILD_RUN_COMMAND='+$$(srctree)/scripts/package/builddeb $(package)' + $(Q)dh_installdocs $(DH_OPTIONS) + $(Q)dh_installchangelogs $(DH_OPTIONS) + $(Q)dh_compress $(DH_OPTIONS) + $(Q)dh_fixperms $(DH_OPTIONS) + $(Q)dh_gencontrol $(DH_OPTIONS) -- -f$(call mk-files,$@) + $(Q)dh_md5sums $(DH_OPTIONS) + $(Q)dh_builddeb $(DH_OPTIONS) -- $(addprefix -Z,$(KDEB_COMPRESS)) +endef + +.PHONY: $(binary-targets) +$(binary-targets): build-arch + $(Q)truncate -s0 $(call mk-files,$@) + $(if $(package),$(binary)) + +.PHONY: binary binary-indep binary-arch +binary: binary-arch binary-indep +binary-indep: build-indep +binary-arch: $(binary-targets) + $(Q)cat $(call mk-files,$^) > debian/files + +.PHONY: build build-indep build-arch +build: build-arch build-indep +build-indep: +build-arch: + $(Q)$(MAKE) $(make-opts) olddefconfig + $(Q)$(MAKE) $(make-opts) $(if $(filter um,$(ARCH)),,headers) all + +.PHONY: clean +clean: + $(Q)dh_clean + $(Q)rm -rf debian/deb-env.vars* debian/*.files + $(Q)$(MAKE) ARCH=$(ARCH) clean + +# If DEB_HOST_ARCH is empty, it is likely that debian/rules was executed +# directly. Run 'dpkg-architecture --print-set --print-format=make' to +# generate a makefile construct that exports all DEB_* variables. +ifndef DEB_HOST_ARCH +include debian/deb-env.vars + +debian/deb-env.vars: + $(Q)dpkg-architecture -a$$(cat debian/arch) --print-set --print-format=make > $@.tmp + $(Q)mv $@.tmp $@ +endif diff --git a/scripts/package/gen-diff-patch b/scripts/package/gen-diff-patch new file mode 100755 index 000000000..f272f7770 --- /dev/null +++ b/scripts/package/gen-diff-patch @@ -0,0 +1,38 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +set -eu + +diff_patch=$1 + +mkdir -p "$(dirname "${diff_patch}")" + +git -C "${srctree:-.}" diff HEAD > "${diff_patch}" + +if [ ! -s "${diff_patch}" ] || + [ -z "$(git -C "${srctree:-.}" ls-files --other --exclude-standard | head -n1)" ]; then + exit +fi + +# The source tarball, which is generated by 'git archive', contains everything +# you committed in the repository. If you have local diff ('git diff HEAD'), +# it will go into ${diff_patch}. If untracked files are remaining, the resulting +# source package may not be correct. +# +# Examples: +# - You modified a source file to add #include "new-header.h" +# but forgot to add new-header.h +# - You modified a Makefile to add 'obj-$(CONFIG_FOO) += new-dirver.o' +# but you forgot to add new-driver.c +# +# You need to commit them, or at least stage them by 'git add'. +# +# This script does not take care of untracked files because doing so would +# introduce additional complexity. Instead, print a warning message here if +# untracked files are found. +# If all untracked files are just garbage, you can ignore this warning. +echo >&2 "============================ WARNING ============================" +echo >&2 "Your working tree has diff from HEAD, and also untracked file(s)." +echo >&2 "Please make sure you did 'git add' for all new files you need in" +echo >&2 "the source package." +echo >&2 "=================================================================" diff --git a/scripts/package/install-extmod-build b/scripts/package/install-extmod-build new file mode 100755 index 000000000..f12e1ffe4 --- /dev/null +++ b/scripts/package/install-extmod-build @@ -0,0 +1,75 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +set -eu + +destdir=${1} + +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 "arch/${SRCARCH}" -name generated -prune -o -name include -type d -print + find "arch/${SRCARCH}" -name Kbuild.platforms -o -name Platform + 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}" + +{ + if is_enabled CONFIG_OBJTOOL; then + echo tools/objtool/objtool + fi + + if is_enabled CONFIG_DEBUG_INFO_BTF_MODULES; then + echo tools/bpf/resolve_btfids/resolve_btfids + fi + + 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}" + +# 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-to=. "${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 new file mode 100644 index 000000000..46e80970f --- /dev/null +++ b/scripts/package/kernel.spec @@ -0,0 +1,226 @@ +# _arch is undefined if /usr/lib/rpm/platform/*/macros was not included. +%{!?_arch: %define _arch dummy} +%{!?make: %define make make} +%define makeflags %{?_smp_mflags} ARCH=%{ARCH} + +Name: kernel +Summary: The Linux Kernel +Version: %(echo %{KERNELRELEASE} | sed -e 's/-/_/g') +Release: %{pkg_release}%{?dist} +License: GPL +Group: System Environment/Kernel +Vendor: The Linux Community +URL: https://www.kernel.org +Source0: linux.tar.gz +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 + +%description +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 < %{version} +Provides: kernel-headers = %{version} +%description headers +Kernel-headers includes the C header files that specify the interface +between the Linux kernel and userspace libraries and programs. The +header files define structures and constants that are needed for +building most standard programs and are also needed for rebuilding the +glibc package. + +%if %{with_devel} +%package devel +Summary: Development package for building kernel modules to match the %{version} kernel +Group: System Environment/Kernel +AutoReqProv: no +%description -n kernel-devel +This package provides kernel headers and makefiles sufficient to build modules +against the %{version} kernel package. +%endif + +%if %{with_debuginfo_manual} +%package debuginfo +Summary: Debug information package for the Linux kernel +Group: Development/Debug +AutoReq: 0 +AutoProv: 1 +%description debuginfo +This package provides debug information for the kernel image and modules from the +%{version} package. +%define install_mod_strip 1 +%endif + +%if %{with_debuginfo_rpm} +# 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 + +# Preserve .BTF and .BTF.base sections in kernel modules during debuginfo +# stripping. find-debuginfo.sh uses eu-strip which removes non-allocated ELF +# sections like .BTF by default. .BTF.base is required for BTF distillation +# support; without it, module BTF validation fails. +%global with_keep_section %(%{__find_debuginfo} --help 2>&1 | grep -c keep-section) +%if %{with_keep_section} +%global _find_debuginfo_opts -r --keep-section .BTF --keep-section .BTF.base +%else +%global _find_debuginfo_opts -r +%endif + +%global _missing_build_ids_terminate_build 1 +%global _no_recompute_build_ids 1 +%{debug_package} + +# 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 +%else +%define __spec_install_post /usr/lib/rpm/brp-compress || : +%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} + +%prep +%setup -q -n linux +cp %{SOURCE1} .config +patch -p1 < %{SOURCE2} + +%build +%{make} %{makeflags} KERNELRELEASE=%{KERNELRELEASE} KBUILD_BUILD_VERSION=%{release} + +%install +mkdir -p %{buildroot}/lib/modules/%{KERNELRELEASE} +cp $(%{make} %{makeflags} -s image_name) %{buildroot}/lib/modules/%{KERNELRELEASE}/vmlinuz +# DEPMOD=true makes depmod no-op. We do not package depmod-generated files. +%{make} %{makeflags} INSTALL_MOD_PATH=%{buildroot} %{?install_mod_strip:INSTALL_MOD_STRIP=1} 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 +if %{make} %{makeflags} run-command KBUILD_RUN_COMMAND='test -d ${srctree}/arch/${SRCARCH}/boot/dts' 2>/dev/null; then + %{make} %{makeflags} INSTALL_DTBS_PATH=%{buildroot}/lib/modules/%{KERNELRELEASE}/dtb dtbs_install +fi +ln -fns /usr/src/kernels/%{KERNELRELEASE} %{buildroot}/lib/modules/%{KERNELRELEASE}/build +%if %{with_devel} +%{make} %{makeflags} run-command KBUILD_RUN_COMMAND='${srctree}/scripts/package/install-extmod-build %{buildroot}/usr/src/kernels/%{KERNELRELEASE}' +%endif + +{ + echo "/lib/modules/%{KERNELRELEASE}" + + for x in alias alias.bin builtin.alias.bin builtin.bin dep dep.bin \ + devname softdep symbols symbols.bin weakdep; do + echo "%ghost /lib/modules/%{KERNELRELEASE}/modules.${x}" + done + + for x in System.map config vmlinuz; do + echo "%ghost /boot/${x}-%{KERNELRELEASE}" + done + + if [ -d "%{buildroot}/lib/modules/%{KERNELRELEASE}/dtb" ];then + find "%{buildroot}/lib/modules/%{KERNELRELEASE}/dtb" -printf "%%%ghost /boot/dtb-%{KERNELRELEASE}/%%P\n" + fi + + echo "%exclude /lib/modules/%{KERNELRELEASE}/build" +} > %{buildroot}/kernel.list + +%if 0%{with_debuginfo_manual}%{with_debuginfo_rpm} > 0 +# 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 + +%if %{with_debuginfo_rpm} +# 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 +%endif + +%if %{with_debuginfo_manual} +echo /usr/lib/debug/lib/modules/%{KERNELRELEASE}/vmlinux > %{buildroot}/debuginfo.list +while read -r mod; do + mod="${mod%.o}.ko" + dbg="%{buildroot}/usr/lib/debug/lib/modules/%{KERNELRELEASE}/kernel/${mod}" + buildid=$("${READELF:-readelf}" -n "${mod}" | sed -n 's@^.*Build ID: \(..\)\(.*\)@\1/\2@p') + link="%{buildroot}/usr/lib/debug/.build-id/${buildid}.debug" + + mkdir -p "${dbg%/*}" "${link%/*}" + "${OBJCOPY:-objcopy}" --only-keep-debug "${mod}" "${dbg}" + ln -sf --relative "${dbg}" "${link}" + + echo "${dbg#%{buildroot}}" >> %{buildroot}/debuginfo.list + echo "${link#%{buildroot}}" >> %{buildroot}/debuginfo.list +done < modules.order +%endif + +%clean +rm -rf %{buildroot} +%if %{with_debuginfo_rpm} +rm -f debugfiles.list debuglinks.list debugsourcefiles.list debugsources.list \ + elfbins.list +%endif + +%post +if [ -x /usr/bin/kernel-install ]; then + /usr/bin/kernel-install add %{KERNELRELEASE} /lib/modules/%{KERNELRELEASE}/vmlinuz +fi +for file in vmlinuz System.map config; do + if ! cmp --silent "/lib/modules/%{KERNELRELEASE}/${file}" "/boot/${file}-%{KERNELRELEASE}"; then + cp "/lib/modules/%{KERNELRELEASE}/${file}" "/boot/${file}-%{KERNELRELEASE}" + fi +done +if [ -d "/lib/modules/%{KERNELRELEASE}/dtb" ] && \ + ! diff -rq "/lib/modules/%{KERNELRELEASE}/dtb" "/boot/dtb-%{KERNELRELEASE}" >/dev/null 2>&1; then + rm -rf "/boot/dtb-%{KERNELRELEASE}" + cp -r "/lib/modules/%{KERNELRELEASE}/dtb" "/boot/dtb-%{KERNELRELEASE}" +fi +if [ ! -e "/lib/modules/%{KERNELRELEASE}/modules.dep" ]; then + /usr/sbin/depmod %{KERNELRELEASE} +fi + +%preun +if [ -x /usr/bin/kernel-install ]; then +kernel-install remove %{KERNELRELEASE} +fi + +%files -f %{buildroot}/kernel.list +%defattr (-, root, root) +%exclude /kernel.list + +%files headers +%defattr (-, root, root) +/usr/include + +%if %{with_devel} +%files devel +%defattr (-, root, root) +/usr/src/kernels/%{KERNELRELEASE} +/lib/modules/%{KERNELRELEASE}/build +%endif + +%if %{with_debuginfo_manual} +%files -f %{buildroot}/debuginfo.list debuginfo +%defattr (-, root, root) +%exclude /debuginfo.list +%endif diff --git a/scripts/package/mkdebian b/scripts/package/mkdebian new file mode 100755 index 000000000..d4b007b38 --- /dev/null +++ b/scripts/package/mkdebian @@ -0,0 +1,273 @@ +#!/bin/sh +# +# Copyright 2003 Wichert Akkerman <wichert@wiggy.net> +# +# Simple script to generate a debian/ directory for a Linux kernel. + +set -eu + +is_enabled() { + grep -q "^$1=y" include/config/auto.conf +} + +if_enabled_echo() { + if is_enabled "$1"; then + echo -n "$2" + elif [ $# -ge 3 ]; then + echo -n "$3" + fi +} + +set_debarch() { + if [ "${KBUILD_DEBARCH:+set}" ]; then + debarch="$KBUILD_DEBARCH" + return + fi + + # Attempt to find the correct Debian architecture + case "$UTS_MACHINE" in + i386|alpha|m68k|riscv*) + debarch="$UTS_MACHINE" ;; + x86_64) + debarch=amd64 ;; + sparc*) + debarch=sparc$(if_enabled_echo CONFIG_64BIT 64) ;; + s390*) + debarch=s390x ;; + ppc*) + if is_enabled CONFIG_64BIT; then + debarch=ppc64$(if_enabled_echo CONFIG_CPU_LITTLE_ENDIAN el) + else + debarch=powerpc$(if_enabled_echo CONFIG_SPE spe) + fi + ;; + parisc*) + debarch=hppa ;; + mips*) + if is_enabled CONFIG_CPU_LITTLE_ENDIAN; then + debarch=mips$(if_enabled_echo CONFIG_64BIT 64)$(if_enabled_echo CONFIG_CPU_MIPSR6 r6)el + elif is_enabled CONFIG_CPU_MIPSR6; then + debarch=mips$(if_enabled_echo CONFIG_64BIT 64)r6 + else + debarch=mips + fi + ;; + aarch64|arm64) + debarch=arm64 ;; + arm*) + if is_enabled CONFIG_AEABI; then + debarch=arm$(if_enabled_echo CONFIG_VFP hf el) + else + debarch=arm + fi + ;; + openrisc) + debarch=or1k ;; + sh) + if is_enabled CONFIG_CPU_SH3; then + debarch=sh3$(if_enabled_echo CONFIG_CPU_BIG_ENDIAN eb) + elif is_enabled CONFIG_CPU_SH4; then + 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) + echo "" >&2 + echo "** ** ** WARNING ** ** **" >&2 + echo "" >&2 + echo "Your architecture doesn't have its equivalent" >&2 + echo "Debian userspace architecture defined!" >&2 + echo "Falling back to the current host architecture ($debarch)." >&2 + echo "Please add support for $UTS_MACHINE to ${0} ..." >&2 + echo "" >&2 + fi +} + +# Create debian/source/ if it is a source package build +gen_source () +{ + mkdir -p debian/source + + echo "3.0 (quilt)" > debian/source/format + + { + echo "diff-ignore" + echo "extend-diff-ignore = .*" + } > debian/source/local-options + + # Add .config as a patch + mkdir -p debian/patches + { + echo "Subject: Add .config" + echo "Author: ${maintainer}" + echo + echo "--- /dev/null" + echo "+++ linux/.config" + diff -u /dev/null "${KCONFIG_CONFIG}" | tail -n +3 + } > debian/patches/config.patch + echo config.patch > debian/patches/series + + "${srctree}/scripts/package/gen-diff-patch" debian/patches/diff.patch + if [ -s debian/patches/diff.patch ]; then + sed -i " + 1iSubject: Add local diff + 1iAuthor: ${maintainer} + 1i + " debian/patches/diff.patch + + echo diff.patch >> debian/patches/series + else + rm -f debian/patches/diff.patch + fi +} + +rm -rf debian +mkdir debian + +user=${KBUILD_BUILD_USER:-$(id -nu)} +name=${DEBFULLNAME:-${user}} +if [ "${DEBEMAIL:+set}" ]; then + email=${DEBEMAIL} +else + buildhost=${KBUILD_BUILD_HOST:-$(hostname -f 2>/dev/null || hostname)} + email="${user}@${buildhost}" +fi +maintainer="${name} <${email}>" + +while [ $# -gt 0 ]; do + case "$1" in + --need-source) + gen_source + shift + ;; + *) + break + ;; + esac +done + +# Some variables and settings used throughout the script +if [ "${KDEB_PKGVERSION:+set}" ]; then + packageversion=$KDEB_PKGVERSION +else + 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} + +if [ "$ARCH" = "um" ] ; then + packagename=user-mode-linux +else + packagename=linux-image +fi + +debarch= +set_debarch + +# Try to determine distribution +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 + : # nothing to do in this case +else + distribution="unstable" + echo >&2 "Using default distribution of 'unstable' in the changelog" + echo >&2 "Install lsb-release or set \$KDEB_CHANGELOG_DIST explicitly" +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 + + * Custom built Linux kernel. + + -- $maintainer $(date -R) +EOF + +# Generate a control file +cat <<EOF > debian/control +Source: $sourcename +Section: kernel +Priority: optional +Maintainer: $maintainer +Rules-Requires-Root: no +Build-Depends: debhelper-compat (= 12) +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-${KERNELRELEASE} +Architecture: $debarch +Description: Linux kernel, version ${KERNELRELEASE} + This package contains the Linux kernel, modules and corresponding other + files, version: ${KERNELRELEASE}. +EOF + +if [ "${SRCARCH}" != um ]; then +cat <<EOF >> debian/control + +Package: linux-libc-dev +Section: devel +Provides: linux-kernel-headers +Architecture: $debarch +Description: Linux support headers for userspace development + This package provides userspaces headers from the Linux kernel. These headers + are used by the installed headers for GNU glibc and other system libraries. +Multi-Arch: same +EOF + +if is_enabled CONFIG_MODULES; then +cat <<EOF >> debian/control + +Package: linux-headers-${KERNELRELEASE} +Architecture: $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 +fi +fi + +if is_enabled CONFIG_DEBUG_INFO; then +cat <<EOF >> debian/control + +Package: linux-image-${KERNELRELEASE}-dbg +Section: debug +Architecture: $debarch +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 +fi + +cat <<EOF > debian/rules.vars +ARCH := ${ARCH} +KERNELRELEASE := ${KERNELRELEASE} +EOF + +cp "${srctree}/scripts/package/debian/copyright" debian/ +cp "${srctree}/scripts/package/debian/rules" debian/ + +exit 0 diff --git a/scripts/package/mkspec b/scripts/package/mkspec new file mode 100755 index 000000000..c604f8c17 --- /dev/null +++ b/scripts/package/mkspec @@ -0,0 +1,97 @@ +#!/bin/sh +# +# Output a simple RPM spec file. +# This version assumes a minimum of RPM 4.13 +# +# The only gothic bit here is redefining install_post to avoid +# stripping the symbols from files in the kernel which we want +# +# Patched for non-x86 by Opencon (L) 2002 <opencon@rio.skydome.net> +# + +set -eu + +output=$1 + +mkdir -p "$(dirname "${output}")" + +exec >"${output}" + +if grep -q CONFIG_MODULES=y include/config/auto.conf; then +echo '%define with_devel %{?_without_devel: 0} %{?!_without_devel: 1}' +else +echo '%define with_devel 0' +fi + +# use %{debug_package} machinery to generate -debuginfo +with_debuginfo_rpm=0 +# manually generate -debuginfo package +with_debuginfo_manual=0 +# 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 + # If module signing is enabled (which may be required to boot with + # lockdown enabled), the find-debuginfo.sh machinery cannot be used + # because the signatures will be stripped off the modules. However, due + # to an rpm bug in versions prior to 4.20.0 + # + # https://github.com/rpm-software-management/rpm/issues/3057 + # https://github.com/rpm-software-management/rpm/commit/49f906998f3cf1f4152162ca61ac0869251c380f + # + # We cannot provide our own debuginfo package because it does not listen + # to our custom files list, failing the build due to unpackaged files. + # Manually generate the debug info package if using rpm 4.20.0. If not + # using rpm 4.20.0, avoid generating a -debuginfo package altogether, + # as it is not safe. + if grep -q CONFIG_MODULE_SIG=y include/config/auto.conf; then + rpm_ver_str=$(rpm --version 2>/dev/null) + # Split the version on spaces + IFS=' ' + set -- $rpm_ver_str + if [ "${1:-}" = RPM -a "${2:-}" = version ]; then + IFS=. + set -- $3 + rpm_ver=$(( 1000000 * $1 + 10000 * $2 + 100 * $3 + ${4:-0} )) + if [ "$rpm_ver" -ge 4200000 ]; then + with_debuginfo_manual='%{?_without_debuginfo:0}%{?!_without_debuginfo:1}' + fi + fi + else + with_debuginfo_rpm='%{?_without_debuginfo:0}%{?!_without_debuginfo:1}' + fi +fi +echo "%define with_debuginfo_manual $with_debuginfo_manual" +echo "%define with_debuginfo_rpm $with_debuginfo_rpm" + +cat<<EOF +%define ARCH ${ARCH} +%define KERNELRELEASE ${KERNELRELEASE} +%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/package/snapcraft.template b/scripts/package/snapcraft.template new file mode 100644 index 000000000..85d5e07d1 --- /dev/null +++ b/scripts/package/snapcraft.template @@ -0,0 +1,14 @@ +name: kernel +version: KERNELRELEASE +summary: Linux kernel +description: The upstream Linux kernel +grade: stable +confinement: strict +type: kernel + +parts: + kernel: + plugin: kernel + source: SRCTREE + source-type: local + kernel-with-firmware: false diff --git a/scripts/pahole-version.sh b/scripts/pahole-version.sh new file mode 100755 index 000000000..f8a32ab93 --- /dev/null +++ b/scripts/pahole-version.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Usage: $ ./pahole-version.sh pahole +# +# Prints pahole's version in a 3-digit form, such as 119 for v1.19. + +if [ ! -x "$(command -v "$@")" ]; then + echo 0 + exit 1 +fi + +"$@" --version | sed -E 's/v([0-9]+)\.([0-9]+)/\1\2/' diff --git a/scripts/parse-maintainers.pl b/scripts/parse-maintainers.pl new file mode 100755 index 000000000..2ca4eb3f1 --- /dev/null +++ b/scripts/parse-maintainers.pl @@ -0,0 +1,194 @@ +#!/usr/bin/perl -w +# SPDX-License-Identifier: GPL-2.0 + +use strict; +use Getopt::Long qw(:config no_auto_abbrev); + +my $input_file = "MAINTAINERS"; +my $output_file = "MAINTAINERS.new"; +my $output_section = "SECTION.new"; +my $help = 0; +my $order = 0; +my $P = $0; + +if (!GetOptions( + 'input=s' => \$input_file, + 'output=s' => \$output_file, + 'section=s' => \$output_section, + 'order!' => \$order, + 'h|help|usage' => \$help, + )) { + die "$P: invalid argument - use --help if necessary\n"; +} + +if ($help != 0) { + usage(); + exit 0; +} + +sub usage { + print <<EOT; +usage: $P [options] <pattern matching regexes> + + --input => MAINTAINERS file to read (default: MAINTAINERS) + --output => sorted MAINTAINERS file to write (default: MAINTAINERS.new) + --section => new sorted MAINTAINERS file to write to (default: SECTION.new) + --order => Use the preferred section content output ordering (default: 0) + Preferred ordering of section output is: + M: Person acting as a maintainer + R: Person acting as a patch reviewer + L: Mailing list where patches should be sent + S: Maintenance status + W: URI for general information + Q: URI for patchwork tracking + B: URI for bug tracking/submission + C: URI for chat + P: URI or file for subsystem specific coding styles + T: SCM tree type and location + F: File and directory pattern + X: File and directory exclusion pattern + N: File glob + K: Keyword - patch content regex + +If <pattern match regexes> exist, then the sections that match the +regexes are not written to the output file but are written to the +section file. + +EOT +} + +# sort comparison functions +sub by_category($$) { + my ($a, $b) = @_; + + $a = uc $a; + $b = uc $b; + + # This always sorts last + $a =~ s/THE REST/ZZZZZZ/g; + $b =~ s/THE REST/ZZZZZZ/g; + + return $a cmp $b; +} + +sub by_pattern($$) { + my ($a, $b) = @_; + my $preferred_order = 'MRLSWQBCPTFXNK'; + + my $a1 = uc(substr($a, 0, 1)); + my $b1 = uc(substr($b, 0, 1)); + + my $a_index = index($preferred_order, $a1); + my $b_index = index($preferred_order, $b1); + + $a_index = 1000 if ($a_index == -1); + $b_index = 1000 if ($b_index == -1); + + if (($a1 =~ /^F$/ && $b1 =~ /^F$/) || + ($a1 =~ /^X$/ && $b1 =~ /^X$/)) { + return $a cmp $b; + } + + if ($a_index < $b_index) { + return -1; + } elsif ($a_index == $b_index) { + return 0; + } else { + return 1; + } +} + +sub trim { + my $s = shift; + $s =~ s/\s+$//; + $s =~ s/^\s+//; + return $s; +} + +sub alpha_output { + my ($hashref, $filename) = (@_); + + return if ! scalar(keys %$hashref); + + open(my $file, '>', "$filename") or die "$P: $filename: open failed - $!\n"; + my $separator; + foreach my $key (sort by_category keys %$hashref) { + if ($key eq " ") { + print $file $$hashref{$key}; + } else { + if (! defined $separator) { + $separator = "\n"; + } else { + print $file $separator; + } + print $file $key . "\n"; + if ($order) { + foreach my $pattern (sort by_pattern split('\n', %$hashref{$key})) { + print $file ($pattern . "\n"); + } + } else { + foreach my $pattern (split('\n', %$hashref{$key})) { + print $file ($pattern . "\n"); + } + } + } + } + close($file); +} + +sub file_input { + my ($hashref, $filename) = (@_); + + my $lastline = ""; + my $case = " "; + $$hashref{$case} = ""; + + open(my $file, '<', "$filename") or die "$P: $filename: open failed - $!\n"; + + while (<$file>) { + my $line = $_; + + # Pattern line? + if ($line =~ m/^([A-Z]):\s*(.*)/) { + $line = $1 . ":\t" . trim($2) . "\n"; + if ($lastline eq "") { + $$hashref{$case} = $$hashref{$case} . $line; + next; + } + $case = trim($lastline); + exists $$hashref{$case} and die "Header '$case' already exists"; + $$hashref{$case} = $line; + $lastline = ""; + next; + } + + if ($case eq " ") { + $$hashref{$case} = $$hashref{$case} . $lastline; + $lastline = $line; + next; + } + trim($lastline) eq "" or die ("Odd non-pattern line '$lastline' for '$case'"); + $lastline = $line; + } + $$hashref{$case} = $$hashref{$case} . $lastline; + close($file); +} + +my %hash; +my %new_hash; + +file_input(\%hash, $input_file); + +foreach my $type (@ARGV) { + foreach my $key (keys %hash) { + if ($key =~ /$type/ || $hash{$key} =~ /$type/) { + $new_hash{$key} = $hash{$key}; + delete $hash{$key}; + } + } +} + +alpha_output(\%hash, $output_file); +alpha_output(\%new_hash, $output_section); + +exit(0); diff --git a/scripts/patch-kernel b/scripts/patch-kernel new file mode 100755 index 000000000..033d59167 --- /dev/null +++ b/scripts/patch-kernel @@ -0,0 +1,332 @@ +#! /bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Script to apply kernel patches. +# usage: patch-kernel [ sourcedir [ patchdir [ stopversion ] [ -acxx ] ] ] +# The source directory defaults to /usr/src/linux, and the patch +# directory defaults to the current directory. +# e.g. +# scripts/patch-kernel . .. +# Update the kernel tree in the current directory using patches in the +# directory above to the latest Linus kernel +# scripts/patch-kernel . .. -ac +# Get the latest Linux kernel and patch it with the latest ac patch +# scripts/patch-kernel . .. 2.4.9 +# Gets standard kernel 2.4.9 +# scripts/patch-kernel . .. 2.4.9 -ac +# Gets 2.4.9 with latest ac patches +# scripts/patch-kernel . .. 2.4.9 -ac11 +# Gets 2.4.9 with ac patch ac11 +# Note: It uses the patches relative to the Linus kernels, not the +# ac to ac relative patches +# +# It determines the current kernel version from the top-level Makefile. +# It then looks for patches for the next sublevel in the patch directory. +# This is applied using "patch -p1 -s" from within the kernel directory. +# A check is then made for "*.rej" files to see if the patch was +# successful. If it is, then all of the "*.orig" files are removed. +# +# Nick Holloway <Nick.Holloway@alfie.demon.co.uk>, 2nd January 1995. +# +# Added support for handling multiple types of compression. What includes +# gzip, bzip, bzip2, zip, compress, and plaintext. +# +# Adam Sulmicki <adam@cfar.umd.edu>, 1st January 1997. +# +# Added ability to stop at a given version number +# Put the full version number (i.e. 2.3.31) as the last parameter +# Dave Gilbert <linux@treblig.org>, 11th December 1999. + +# Fixed previous patch so that if we are already at the correct version +# not to patch up. +# +# Added -ac option, use -ac or -ac9 (say) to stop at a particular version +# Dave Gilbert <linux@treblig.org>, 29th September 2001. +# +# Add support for (use of) EXTRAVERSION (to support 2.6.8.x, e.g.); +# update usage message; +# fix some whitespace damage; +# be smarter about stopping when current version is larger than requested; +# Randy Dunlap <rdunlap@xenotime.net>, 2004-AUG-18. +# +# Add better support for (non-incremental) 2.6.x.y patches; +# If an ending version number if not specified, the script automatically +# increments the SUBLEVEL (x in 2.6.x.y) until no more patch files are found; +# however, EXTRAVERSION (y in 2.6.x.y) is never automatically incremented +# but must be specified fully. +# +# patch-kernel does not normally support reverse patching, but does so when +# applying EXTRAVERSION (x.y) patches, so that moving from 2.6.11.y to 2.6.11.z +# is easy and handled by the script (reverse 2.6.11.y and apply 2.6.11.z). +# Randy Dunlap <rdunlap@xenotime.net>, 2005-APR-08. + +PNAME=patch-kernel + +# Set directories from arguments, or use defaults. +sourcedir=${1-/usr/src/linux} +patchdir=${2-.} +stopvers=${3-default} + +if [ "$1" = -h -o "$1" = --help -o ! -r "$sourcedir/Makefile" ]; then +cat << USAGE +usage: $PNAME [-h] [ sourcedir [ patchdir [ stopversion ] [ -acxx ] ] ] + source directory defaults to /usr/src/linux, + patch directory defaults to the current directory, + stopversion defaults to <all in patchdir>. +USAGE +exit 1 +fi + +# See if we have any -ac options +for PARM in $* +do + case $PARM in + -ac*) + gotac=$PARM; + + esac; +done + +# --------------------------------------------------------------------------- +# arg1 is filename +noFile () { + echo "cannot find patch file: ${patch}" + exit 1 +} + +# --------------------------------------------------------------------------- +backwards () { + echo "$PNAME does not support reverse patching" + exit 1 +} + +# --------------------------------------------------------------------------- +# Find a file, first parameter is basename of file +# it tries many compression mechanisms and sets variables to say how to get it +findFile () { + filebase=$1; + + if [ -r ${filebase}.gz ]; then + ext=".gz" + name="gzip" + uncomp="gunzip -dc" + elif [ -r ${filebase}.bz ]; then + ext=".bz" + name="bzip" + uncomp="bunzip -dc" + elif [ -r ${filebase}.bz2 ]; then + ext=".bz2" + name="bzip2" + uncomp="bunzip2 -dc" + elif [ -r ${filebase}.xz ]; then + ext=".xz" + name="xz" + uncomp="xz -dc" + elif [ -r ${filebase}.zip ]; then + ext=".zip" + name="zip" + uncomp="unzip -d" + elif [ -r ${filebase}.Z ]; then + ext=".Z" + name="uncompress" + uncomp="uncompress -c" + elif [ -r ${filebase} ]; then + ext="" + name="plaintext" + uncomp="cat" + else + return 1; + fi + + return 0; +} + +# --------------------------------------------------------------------------- +# Apply a patch and check it goes in cleanly +# First param is patch name (e.g. patch-2.4.9-ac5) - without path or extension + +applyPatch () { + echo -n "Applying $1 (${name})... " + if $uncomp ${patchdir}/$1${ext} | patch -p1 -s -N -E -d $sourcedir + then + echo "done." + else + echo "failed. Clean up yourself." + return 1; + fi + if [ "`find $sourcedir/ '(' -name '*.rej' -o -name '.*.rej' ')' -print`" ] + then + echo "Aborting. Reject files found." + return 1; + fi + # Remove backup files + find $sourcedir/ '(' -name '*.orig' -o -name '.*.orig' ')' -exec rm -f {} \; + + return 0; +} + +# --------------------------------------------------------------------------- +# arg1 is patch filename +reversePatch () { + echo -n "Reversing $1 (${name}) ... " + if $uncomp ${patchdir}/"$1"${ext} | patch -p1 -Rs -N -E -d $sourcedir + then + echo "done." + else + echo "failed. Clean it up." + exit 1 + fi + if [ "`find $sourcedir/ '(' -name '*.rej' -o -name '.*.rej' ')' -print`" ] + then + echo "Aborting. Reject files found." + return 1 + fi + # Remove backup files + find $sourcedir/ '(' -name '*.orig' -o -name '.*.orig' ')' -exec rm -f {} \; + + return 0 +} + +# set current VERSION, PATCHLEVEL, SUBLEVEL, EXTRAVERSION +# force $TMPFILEs below to be in local directory: a slash character prevents +# the dot command from using the search path. +TMPFILE=`mktemp ./.tmpver.XXXXXX` || { echo "cannot make temp file" ; exit 1; } +grep -E "^(VERSION|PATCHLEVEL|SUBLEVEL|EXTRAVERSION)" $sourcedir/Makefile > $TMPFILE +tr -d [:blank:] < $TMPFILE > $TMPFILE.1 +. $TMPFILE.1 +rm -f $TMPFILE* +if [ -z "$VERSION" -o -z "$PATCHLEVEL" -o -z "$SUBLEVEL" ] +then + echo "unable to determine current kernel version" >&2 + exit 1 +fi + +NAME=`grep ^NAME $sourcedir/Makefile` +NAME=${NAME##*=} + +echo "Current kernel version is $VERSION.$PATCHLEVEL.$SUBLEVEL${EXTRAVERSION} ($NAME)" + +# strip EXTRAVERSION to just a number (drop leading '.' and trailing additions) +EXTRAVER= +if [ x$EXTRAVERSION != "x" ] +then + EXTRAVER=${EXTRAVERSION#.} + EXTRAVER=${EXTRAVER%%[[:punct:]]*} + #echo "$PNAME: changing EXTRAVERSION from $EXTRAVERSION to $EXTRAVER" +fi + +#echo "stopvers=$stopvers" +if [ $stopvers != "default" ]; then + STOPSUBLEVEL=`echo $stopvers | cut -d. -f3` + STOPEXTRA=`echo $stopvers | cut -d. -f4` + STOPFULLVERSION=${stopvers%%.$STOPEXTRA} + #echo "#___STOPSUBLEVEL=/$STOPSUBLEVEL/, STOPEXTRA=/$STOPEXTRA/" +else + STOPSUBLEVEL=9999 + STOPEXTRA=9999 +fi + +# This all assumes a 2.6.x[.y] kernel tree. +# Don't allow backwards/reverse patching. +if [ $STOPSUBLEVEL -lt $SUBLEVEL ]; then + backwards +fi + +if [ x$EXTRAVER != "x" ]; then + CURRENTFULLVERSION="$VERSION.$PATCHLEVEL.$SUBLEVEL.$EXTRAVER" +else + CURRENTFULLVERSION="$VERSION.$PATCHLEVEL.$SUBLEVEL" +fi + +if [ x$EXTRAVER != "x" ]; then + echo "backing up to: $VERSION.$PATCHLEVEL.$SUBLEVEL" + patch="patch-${CURRENTFULLVERSION}" + findFile $patchdir/${patch} || noFile ${patch} + reversePatch ${patch} || exit 1 +fi + +# now current is 2.6.x, with no EXTRA applied, +# so update to target SUBLEVEL (2.6.SUBLEVEL) +# and then to target EXTRAVER (2.6.SUB.EXTRAVER) if requested. +# If not ending sublevel is specified, it is incremented until +# no further sublevels are found. + +if [ $STOPSUBLEVEL -gt $SUBLEVEL ]; then +while : # incrementing SUBLEVEL (s in v.p.s) +do + CURRENTFULLVERSION="$VERSION.$PATCHLEVEL.$SUBLEVEL" + EXTRAVER= + if [ x$STOPFULLVERSION = x$CURRENTFULLVERSION ]; then + echo "Stopping at $CURRENTFULLVERSION base as requested." + break + fi + + SUBLEVEL=$(($SUBLEVEL + 1)) + FULLVERSION="$VERSION.$PATCHLEVEL.$SUBLEVEL" + #echo "#___ trying $FULLVERSION ___" + + if [ $(($SUBLEVEL)) -gt $(($STOPSUBLEVEL)) ]; then + echo "Stopping since sublevel ($SUBLEVEL) is beyond stop-sublevel ($STOPSUBLEVEL)" + exit 1 + fi + + patch=patch-$FULLVERSION + # See if the file exists and find extension + findFile $patchdir/${patch} || noFile ${patch} + + # Apply the patch and check all is OK + applyPatch $patch || break +done +#echo "#___sublevel all done" +fi + +# There is no incremental searching for extraversion... +if [ "$STOPEXTRA" != "" ]; then +while : # just to allow break +do +# apply STOPEXTRA directly (not incrementally) (x in v.p.s.x) + FULLVERSION="$VERSION.$PATCHLEVEL.$SUBLEVEL.$STOPEXTRA" + #echo "#... trying $FULLVERSION ..." + patch=patch-$FULLVERSION + + # See if the file exists and find extension + findFile $patchdir/${patch} || noFile ${patch} + + # Apply the patch and check all is OK + applyPatch $patch || break + #echo "#___extraver all done" + break +done +fi + +if [ x$gotac != x ]; then + # Out great user wants the -ac patches + # They could have done -ac (get latest) or -acxx where xx=version they want + if [ $gotac = "-ac" ]; then + # They want the latest version + HIGHESTPATCH=0 + for PATCHNAMES in $patchdir/patch-${CURRENTFULLVERSION}-ac*\.* + do + ACVALUE=`echo $PATCHNAMES | sed -e 's/^.*patch-[0-9.]*-ac\([0-9]*\).*/\1/'` + # Check it is actually a recognised patch type + findFile $patchdir/patch-${CURRENTFULLVERSION}-ac${ACVALUE} || break + + if [ $ACVALUE -gt $HIGHESTPATCH ]; then + HIGHESTPATCH=$ACVALUE + fi + done + + if [ $HIGHESTPATCH -ne 0 ]; then + findFile $patchdir/patch-${CURRENTFULLVERSION}-ac${HIGHESTPATCH} || break + applyPatch patch-${CURRENTFULLVERSION}-ac${HIGHESTPATCH} + else + echo "No -ac patches found" + fi + else + # They want an exact version + findFile $patchdir/patch-${CURRENTFULLVERSION}${gotac} || { + echo "Sorry, I couldn't find the $gotac patch for $CURRENTFULLVERSION. Hohum." + exit 1 + } + applyPatch patch-${CURRENTFULLVERSION}${gotac} + fi +fi diff --git a/scripts/profile2linkerlist.pl b/scripts/profile2linkerlist.pl new file mode 100755 index 000000000..316e71918 --- /dev/null +++ b/scripts/profile2linkerlist.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 + +# +# Takes a (sorted) output of readprofile and turns it into a list suitable for +# linker scripts +# +# usage: +# readprofile | sort -rn | perl profile2linkerlist.pl > functionlist +# +use strict; + +while (<>) { + my $line = $_; + + $_ =~ /\W*[0-9]+\W*([a-zA-Z\_0-9]+)\W*[0-9]+/; + + print "*(.text.$1)\n" + unless ($line =~ /unknown/) || ($line =~ /total/); +} diff --git a/scripts/prune-kernel b/scripts/prune-kernel new file mode 100755 index 000000000..dadfd0e47 --- /dev/null +++ b/scripts/prune-kernel @@ -0,0 +1,25 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# because I use CONFIG_LOCALVERSION_AUTO, not the same version again and +# again, /boot and /lib/modules/ eventually fill up. +# Dumb script to purge that stuff: + +for f in "$@" +do + if rpm -qf "/lib/modules/$f" >/dev/null; then + echo "keeping $f (installed from rpm)" + elif [ $(uname -r) = "$f" ]; then + echo "keeping $f (running kernel) " + else + echo "removing $f" + rm -f "/boot/initramfs-$f.img" "/boot/System.map-$f" + rm -f "/boot/vmlinuz-$f" "/boot/config-$f" + rm -rf "/lib/modules/$f" + if [ -x "$(command -v new-kernel-pkg)" ]; then + new-kernel-pkg --remove $f + elif [ -x "$(command -v kernel-install)" ]; then + kernel-install remove $f + fi + fi +done diff --git a/scripts/recordmcount.c b/scripts/recordmcount.c new file mode 100644 index 000000000..3e4f54799 --- /dev/null +++ b/scripts/recordmcount.c @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * recordmcount.c: construct a table of the locations of calls to 'mcount' + * so that ftrace can find them quickly. + * Copyright 2009 John F. Reiser <jreiser@BitWagon.com>. All rights reserved. + * + * Restructured to fit Linux format, as well as other updates: + * Copyright 2010 Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. + */ + +/* + * Strategy: alter the .o file in-place. + * + * Append a new STRTAB that has the new section names, followed by a new array + * ElfXX_Shdr[] that has the new section headers, followed by the section + * contents for __mcount_loc and its relocations. The old shstrtab strings, + * and the old ElfXX_Shdr[] array, remain as "garbage" (commonly, a couple + * kilobytes.) Subsequent processing by /bin/ld (or the kernel module loader) + * will ignore the garbage regions, because they are not designated by the + * new .e_shoff nor the new ElfXX_Shdr[]. [In order to remove the garbage, + * then use "ld -r" to create a new file that omits the garbage.] + */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <getopt.h> +#include <elf.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifndef EM_AARCH64 +#define EM_AARCH64 183 +#define R_AARCH64_NONE 0 +#define R_AARCH64_ABS64 257 +#endif + +#ifndef EM_LOONGARCH +#define EM_LOONGARCH 258 +#define R_LARCH_32 1 +#define R_LARCH_64 2 +#define R_LARCH_MARK_LA 20 +#define R_LARCH_SOP_PUSH_PLT_PCREL 29 +#endif + +#define R_ARM_PC24 1 +#define R_ARM_THM_CALL 10 +#define R_ARM_CALL 28 + +#define R_AARCH64_CALL26 283 + +static int fd_map; /* File descriptor for file being modified. */ +static int mmap_failed; /* Boolean flag. */ +static char gpfx; /* prefix for global symbol name (sometimes '_') */ +static struct stat sb; /* Remember .st_size, etc. */ +static const char *altmcount; /* alternate mcount symbol name */ +static int warn_on_notrace_sect; /* warn when section has mcount not being recorded */ +static void *file_map; /* pointer of the mapped file */ +static void *file_end; /* pointer to the end of the mapped file */ +static int file_updated; /* flag to state file was changed */ +static void *file_ptr; /* current file pointer location */ + +static void *file_append; /* added to the end of the file */ +static size_t file_append_size; /* how much is added to end of file */ + +/* Per-file resource cleanup when multiple files. */ +static void file_append_cleanup(void) +{ + free(file_append); + file_append = NULL; + file_append_size = 0; + file_updated = 0; +} + +static void mmap_cleanup(void) +{ + if (!mmap_failed) + munmap(file_map, sb.st_size); + else + free(file_map); + file_map = NULL; +} + +/* ulseek, uwrite, ...: Check return value for errors. */ + +static off_t ulseek(off_t const offset, int const whence) +{ + switch (whence) { + case SEEK_SET: + file_ptr = file_map + offset; + break; + case SEEK_CUR: + file_ptr += offset; + break; + case SEEK_END: + file_ptr = file_map + (sb.st_size - offset); + break; + } + if (file_ptr < file_map) { + fprintf(stderr, "lseek: seek before file\n"); + return -1; + } + return file_ptr - file_map; +} + +static ssize_t uwrite(void const *const buf, size_t const count) +{ + size_t cnt = count; + off_t idx = 0; + void *p = NULL; + + file_updated = 1; + + if (file_ptr + count >= file_end) { + off_t aoffset = (file_ptr + count) - file_end; + + if (aoffset > file_append_size) { + p = realloc(file_append, aoffset); + if (!p) + free(file_append); + file_append = p; + file_append_size = aoffset; + } + if (!file_append) { + perror("write"); + file_append_cleanup(); + mmap_cleanup(); + return -1; + } + if (file_ptr < file_end) { + cnt = file_end - file_ptr; + } else { + cnt = 0; + idx = aoffset - count; + } + } + + if (cnt) + memcpy(file_ptr, buf, cnt); + + if (cnt < count) + memcpy(file_append + idx, buf + cnt, count - cnt); + + file_ptr += count; + return count; +} + +static void * umalloc(size_t size) +{ + void *const addr = malloc(size); + if (addr == 0) { + fprintf(stderr, "malloc failed: %zu bytes\n", size); + file_append_cleanup(); + mmap_cleanup(); + return NULL; + } + return addr; +} + +/* + * Get the whole file as a programming convenience in order to avoid + * malloc+lseek+read+free of many pieces. If successful, then mmap + * avoids copying unused pieces; else just read the whole file. + * Open for both read and write; new info will be appended to the file. + * Use MAP_PRIVATE so that a few changes to the in-memory ElfXX_Ehdr + * do not propagate to the file until an explicit overwrite at the last. + * This preserves most aspects of consistency (all except .st_size) + * for simultaneous readers of the file while we are appending to it. + * However, multiple writers still are bad. We choose not to use + * locking because it is expensive and the use case of kernel build + * makes multiple writers unlikely. + */ +static void *mmap_file(char const *fname) +{ + /* Avoid problems if early cleanup() */ + fd_map = -1; + mmap_failed = 1; + file_map = NULL; + file_ptr = NULL; + file_updated = 0; + sb.st_size = 0; + + fd_map = open(fname, O_RDONLY); + if (fd_map < 0) { + perror(fname); + return NULL; + } + if (fstat(fd_map, &sb) < 0) { + perror(fname); + goto out; + } + if (!S_ISREG(sb.st_mode)) { + fprintf(stderr, "not a regular file: %s\n", fname); + goto out; + } + file_map = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, + fd_map, 0); + if (file_map == MAP_FAILED) { + mmap_failed = 1; + file_map = umalloc(sb.st_size); + if (!file_map) { + perror(fname); + goto out; + } + if (read(fd_map, file_map, sb.st_size) != sb.st_size) { + perror(fname); + free(file_map); + file_map = NULL; + goto out; + } + } else + mmap_failed = 0; +out: + close(fd_map); + fd_map = -1; + + file_end = file_map + sb.st_size; + + return file_map; +} + + +static unsigned char ideal_nop5_x86_64[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; +static unsigned char ideal_nop5_x86_32[5] = { 0x3e, 0x8d, 0x74, 0x26, 0x00 }; +static unsigned char *ideal_nop; + +static char rel_type_nop; + +static int (*make_nop)(void *map, size_t const offset); + +static int make_nop_x86(void *map, size_t const offset) +{ + uint32_t *ptr; + unsigned char *op; + + /* Confirm we have 0xe8 0x0 0x0 0x0 0x0 */ + ptr = map + offset; + if (*ptr != 0) + return -1; + + op = map + offset - 1; + if (*op != 0xe8) + return -1; + + /* convert to nop */ + if (ulseek(offset - 1, SEEK_SET) < 0) + return -1; + if (uwrite(ideal_nop, 5) < 0) + return -1; + return 0; +} + +static unsigned char ideal_nop4_arm_le[4] = { 0x00, 0x00, 0xa0, 0xe1 }; /* mov r0, r0 */ +static unsigned char ideal_nop4_arm_be[4] = { 0xe1, 0xa0, 0x00, 0x00 }; /* mov r0, r0 */ +static unsigned char *ideal_nop4_arm; + +static unsigned char bl_mcount_arm_le[4] = { 0xfe, 0xff, 0xff, 0xeb }; /* bl */ +static unsigned char bl_mcount_arm_be[4] = { 0xeb, 0xff, 0xff, 0xfe }; /* bl */ +static unsigned char *bl_mcount_arm; + +static unsigned char push_arm_le[4] = { 0x04, 0xe0, 0x2d, 0xe5 }; /* push {lr} */ +static unsigned char push_arm_be[4] = { 0xe5, 0x2d, 0xe0, 0x04 }; /* push {lr} */ +static unsigned char *push_arm; + +static unsigned char ideal_nop2_thumb_le[2] = { 0x00, 0xbf }; /* nop */ +static unsigned char ideal_nop2_thumb_be[2] = { 0xbf, 0x00 }; /* nop */ +static unsigned char *ideal_nop2_thumb; + +static unsigned char push_bl_mcount_thumb_le[6] = { 0x00, 0xb5, 0xff, 0xf7, 0xfe, 0xff }; /* push {lr}, bl */ +static unsigned char push_bl_mcount_thumb_be[6] = { 0xb5, 0x00, 0xf7, 0xff, 0xff, 0xfe }; /* push {lr}, bl */ +static unsigned char *push_bl_mcount_thumb; + +static int make_nop_arm(void *map, size_t const offset) +{ + char *ptr; + int cnt = 1; + int nop_size; + size_t off = offset; + + ptr = map + offset; + if (memcmp(ptr, bl_mcount_arm, 4) == 0) { + if (memcmp(ptr - 4, push_arm, 4) == 0) { + off -= 4; + cnt = 2; + } + ideal_nop = ideal_nop4_arm; + nop_size = 4; + } else if (memcmp(ptr - 2, push_bl_mcount_thumb, 6) == 0) { + cnt = 3; + nop_size = 2; + off -= 2; + ideal_nop = ideal_nop2_thumb; + } else + return -1; + + /* Convert to nop */ + if (ulseek(off, SEEK_SET) < 0) + return -1; + + do { + if (uwrite(ideal_nop, nop_size) < 0) + return -1; + } while (--cnt > 0); + + return 0; +} + +static unsigned char ideal_nop4_arm64[4] = {0x1f, 0x20, 0x03, 0xd5}; +static int make_nop_arm64(void *map, size_t const offset) +{ + uint32_t *ptr; + + ptr = map + offset; + /* bl <_mcount> is 0x94000000 before relocation */ + if (*ptr != 0x94000000) + return -1; + + /* Convert to nop */ + if (ulseek(offset, SEEK_SET) < 0) + return -1; + if (uwrite(ideal_nop, 4) < 0) + return -1; + return 0; +} + +static int write_file(const char *fname) +{ + char tmp_file[strlen(fname) + 4]; + size_t n; + + if (!file_updated) + return 0; + + sprintf(tmp_file, "%s.rc", fname); + + /* + * After reading the entire file into memory, delete it + * and write it back, to prevent weird side effects of modifying + * an object file in place. + */ + fd_map = open(tmp_file, O_WRONLY | O_TRUNC | O_CREAT, sb.st_mode); + if (fd_map < 0) { + perror(fname); + return -1; + } + n = write(fd_map, file_map, sb.st_size); + if (n != sb.st_size) { + perror("write"); + close(fd_map); + return -1; + } + if (file_append_size) { + n = write(fd_map, file_append, file_append_size); + if (n != file_append_size) { + perror("write"); + close(fd_map); + return -1; + } + } + close(fd_map); + if (rename(tmp_file, fname) < 0) { + perror(fname); + return -1; + } + return 0; +} + +/* w8rev, w8nat, ...: Handle endianness. */ + +static uint64_t w8rev(uint64_t const x) +{ + return ((0xff & (x >> (0 * 8))) << (7 * 8)) + | ((0xff & (x >> (1 * 8))) << (6 * 8)) + | ((0xff & (x >> (2 * 8))) << (5 * 8)) + | ((0xff & (x >> (3 * 8))) << (4 * 8)) + | ((0xff & (x >> (4 * 8))) << (3 * 8)) + | ((0xff & (x >> (5 * 8))) << (2 * 8)) + | ((0xff & (x >> (6 * 8))) << (1 * 8)) + | ((0xff & (x >> (7 * 8))) << (0 * 8)); +} + +static uint32_t w4rev(uint32_t const x) +{ + return ((0xff & (x >> (0 * 8))) << (3 * 8)) + | ((0xff & (x >> (1 * 8))) << (2 * 8)) + | ((0xff & (x >> (2 * 8))) << (1 * 8)) + | ((0xff & (x >> (3 * 8))) << (0 * 8)); +} + +static uint32_t w2rev(uint16_t const x) +{ + return ((0xff & (x >> (0 * 8))) << (1 * 8)) + | ((0xff & (x >> (1 * 8))) << (0 * 8)); +} + +static uint64_t w8nat(uint64_t const x) +{ + return x; +} + +static uint32_t w4nat(uint32_t const x) +{ + return x; +} + +static uint32_t w2nat(uint16_t const x) +{ + return x; +} + +static uint64_t (*w8)(uint64_t); +static uint32_t (*w)(uint32_t); +static uint32_t (*w2)(uint16_t); + +/* Names of the sections that could contain calls to mcount. */ +static int is_mcounted_section_name(char const *const txtname) +{ + return strncmp(".text", txtname, 5) == 0 || + strcmp(".init.text", txtname) == 0 || + strcmp(".ref.text", txtname) == 0 || + strcmp(".sched.text", txtname) == 0 || + strcmp(".spinlock.text", txtname) == 0 || + strcmp(".irqentry.text", txtname) == 0 || + strcmp(".softirqentry.text", txtname) == 0 || + strcmp(".kprobes.text", txtname) == 0 || + strcmp(".cpuidle.text", txtname) == 0; +} + +static char const *already_has_rel_mcount = "success"; /* our work here is done! */ + +/* 32 bit and 64 bit are very similar */ +#include "recordmcount.h" +#define RECORD_MCOUNT_64 +#include "recordmcount.h" + +static int arm_is_fake_mcount(Elf32_Rel const *rp) +{ + switch (ELF32_R_TYPE(w(rp->r_info))) { + case R_ARM_THM_CALL: + case R_ARM_CALL: + case R_ARM_PC24: + return 0; + } + + return 1; +} + +static int arm64_is_fake_mcount(Elf64_Rel const *rp) +{ + return ELF64_R_TYPE(w8(rp->r_info)) != R_AARCH64_CALL26; +} + +static int LARCH32_is_fake_mcount(Elf32_Rel const *rp) +{ + switch (ELF64_R_TYPE(w(rp->r_info))) { + case R_LARCH_MARK_LA: + case R_LARCH_SOP_PUSH_PLT_PCREL: + return 0; + } + + return 1; +} + +static int LARCH64_is_fake_mcount(Elf64_Rel const *rp) +{ + switch (ELF64_R_TYPE(w(rp->r_info))) { + case R_LARCH_MARK_LA: + case R_LARCH_SOP_PUSH_PLT_PCREL: + return 0; + } + + return 1; +} + +/* 64-bit EM_MIPS has weird ELF64_Rela.r_info. + * http://techpubs.sgi.com/library/manuals/4000/007-4658-001/pdf/007-4658-001.pdf + * We interpret Table 29 Relocation Operation (Elf64_Rel, Elf64_Rela) [p.40] + * to imply the order of the members; the spec does not say so. + * typedef unsigned char Elf64_Byte; + * fails on MIPS64 because their <elf.h> already has it! + */ + +typedef uint8_t myElf64_Byte; /* Type for a 8-bit quantity. */ + +union mips_r_info { + Elf64_Xword r_info; + struct { + Elf64_Word r_sym; /* Symbol index. */ + myElf64_Byte r_ssym; /* Special symbol. */ + myElf64_Byte r_type3; /* Third relocation. */ + myElf64_Byte r_type2; /* Second relocation. */ + myElf64_Byte r_type; /* First relocation. */ + } r_mips; +}; + +static uint64_t MIPS64_r_sym(Elf64_Rel const *rp) +{ + return w(((union mips_r_info){ .r_info = rp->r_info }).r_mips.r_sym); +} + +static void MIPS64_r_info(Elf64_Rel *const rp, unsigned sym, unsigned type) +{ + rp->r_info = ((union mips_r_info){ + .r_mips = { .r_sym = w(sym), .r_type = type } + }).r_info; +} + +static int do_file(char const *const fname) +{ + unsigned int reltype = 0; + Elf32_Ehdr *ehdr; + int rc = -1; + + ehdr = mmap_file(fname); + if (!ehdr) + goto out; + + w = w4nat; + w2 = w2nat; + w8 = w8nat; + switch (ehdr->e_ident[EI_DATA]) { + static unsigned int const endian = 1; + default: + fprintf(stderr, "unrecognized ELF data encoding %d: %s\n", + ehdr->e_ident[EI_DATA], fname); + goto out; + case ELFDATA2LSB: + if (*(unsigned char const *)&endian != 1) { + /* main() is big endian, file.o is little endian. */ + w = w4rev; + w2 = w2rev; + w8 = w8rev; + } + ideal_nop4_arm = ideal_nop4_arm_le; + bl_mcount_arm = bl_mcount_arm_le; + push_arm = push_arm_le; + ideal_nop2_thumb = ideal_nop2_thumb_le; + push_bl_mcount_thumb = push_bl_mcount_thumb_le; + break; + case ELFDATA2MSB: + if (*(unsigned char const *)&endian != 0) { + /* main() is little endian, file.o is big endian. */ + w = w4rev; + w2 = w2rev; + w8 = w8rev; + } + ideal_nop4_arm = ideal_nop4_arm_be; + bl_mcount_arm = bl_mcount_arm_be; + push_arm = push_arm_be; + ideal_nop2_thumb = ideal_nop2_thumb_be; + push_bl_mcount_thumb = push_bl_mcount_thumb_be; + break; + } /* end switch */ + if (memcmp(ELFMAG, ehdr->e_ident, SELFMAG) != 0 || + w2(ehdr->e_type) != ET_REL || + ehdr->e_ident[EI_VERSION] != EV_CURRENT) { + fprintf(stderr, "unrecognized ET_REL file %s\n", fname); + goto out; + } + + gpfx = '_'; + switch (w2(ehdr->e_machine)) { + default: + fprintf(stderr, "unrecognized e_machine %u %s\n", + w2(ehdr->e_machine), fname); + goto out; + case EM_386: + reltype = R_386_32; + rel_type_nop = R_386_NONE; + make_nop = make_nop_x86; + ideal_nop = ideal_nop5_x86_32; + mcount_adjust_32 = -1; + gpfx = 0; + break; + case EM_ARM: + reltype = R_ARM_ABS32; + altmcount = "__gnu_mcount_nc"; + make_nop = make_nop_arm; + rel_type_nop = R_ARM_NONE; + is_fake_mcount32 = arm_is_fake_mcount; + gpfx = 0; + break; + case EM_AARCH64: + reltype = R_AARCH64_ABS64; + make_nop = make_nop_arm64; + rel_type_nop = R_AARCH64_NONE; + ideal_nop = ideal_nop4_arm64; + is_fake_mcount64 = arm64_is_fake_mcount; + break; + case EM_MIPS: /* reltype: e_class */ break; + case EM_LOONGARCH: /* reltype: e_class */ break; + case EM_PPC: reltype = R_PPC_ADDR32; break; + case EM_PPC64: reltype = R_PPC64_ADDR64; break; + case EM_S390: /* reltype: e_class */ break; + case EM_SH: reltype = R_SH_DIR32; gpfx = 0; break; + case EM_SPARCV9: reltype = R_SPARC_64; break; + case EM_X86_64: + make_nop = make_nop_x86; + ideal_nop = ideal_nop5_x86_64; + reltype = R_X86_64_64; + rel_type_nop = R_X86_64_NONE; + mcount_adjust_64 = -1; + gpfx = 0; + break; + } /* end switch */ + + switch (ehdr->e_ident[EI_CLASS]) { + default: + fprintf(stderr, "unrecognized ELF class %d %s\n", + ehdr->e_ident[EI_CLASS], fname); + goto out; + case ELFCLASS32: + if (w2(ehdr->e_ehsize) != sizeof(Elf32_Ehdr) + || w2(ehdr->e_shentsize) != sizeof(Elf32_Shdr)) { + fprintf(stderr, + "unrecognized ET_REL file: %s\n", fname); + goto out; + } + if (w2(ehdr->e_machine) == EM_MIPS) { + reltype = R_MIPS_32; + is_fake_mcount32 = MIPS32_is_fake_mcount; + } + if (w2(ehdr->e_machine) == EM_LOONGARCH) { + reltype = R_LARCH_32; + is_fake_mcount32 = LARCH32_is_fake_mcount; + } + if (do32(ehdr, fname, reltype) < 0) + goto out; + break; + case ELFCLASS64: { + Elf64_Ehdr *const ghdr = (Elf64_Ehdr *)ehdr; + if (w2(ghdr->e_ehsize) != sizeof(Elf64_Ehdr) + || w2(ghdr->e_shentsize) != sizeof(Elf64_Shdr)) { + fprintf(stderr, + "unrecognized ET_REL file: %s\n", fname); + goto out; + } + if (w2(ghdr->e_machine) == EM_S390) { + reltype = R_390_64; + mcount_adjust_64 = -14; + } + if (w2(ghdr->e_machine) == EM_MIPS) { + reltype = R_MIPS_64; + Elf64_r_sym = MIPS64_r_sym; + Elf64_r_info = MIPS64_r_info; + is_fake_mcount64 = MIPS64_is_fake_mcount; + } + if (w2(ghdr->e_machine) == EM_LOONGARCH) { + reltype = R_LARCH_64; + is_fake_mcount64 = LARCH64_is_fake_mcount; + } + if (do64(ghdr, fname, reltype) < 0) + goto out; + break; + } + } /* end switch */ + + rc = write_file(fname); +out: + file_append_cleanup(); + mmap_cleanup(); + return rc; +} + +int main(int argc, char *argv[]) +{ + const char ftrace[] = "/ftrace.o"; + int ftrace_size = sizeof(ftrace) - 1; + int n_error = 0; /* gcc-4.3.0 false positive complaint */ + int c; + int i; + + while ((c = getopt(argc, argv, "w")) >= 0) { + switch (c) { + case 'w': + warn_on_notrace_sect = 1; + break; + default: + fprintf(stderr, "usage: recordmcount [-w] file.o...\n"); + return 0; + } + } + + if ((argc - optind) < 1) { + fprintf(stderr, "usage: recordmcount [-w] file.o...\n"); + return 0; + } + + /* Process each file in turn, allowing deep failure. */ + for (i = optind; i < argc; i++) { + char *file = argv[i]; + int len; + + /* + * The file kernel/trace/ftrace.o references the mcount + * function but does not call it. Since ftrace.o should + * not be traced anyway, we just skip it. + */ + len = strlen(file); + if (len >= ftrace_size && + strcmp(file + (len - ftrace_size), ftrace) == 0) + continue; + + if (do_file(file)) { + fprintf(stderr, "%s: failed\n", file); + ++n_error; + } + } + return !!n_error; +} diff --git a/scripts/recordmcount.h b/scripts/recordmcount.h new file mode 100644 index 000000000..1e9baa5c4 --- /dev/null +++ b/scripts/recordmcount.h @@ -0,0 +1,697 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * recordmcount.h + * + * This code was taken out of recordmcount.c written by + * Copyright 2009 John F. Reiser <jreiser@BitWagon.com>. All rights reserved. + * + * The original code had the same algorithms for both 32bit + * and 64bit ELF files, but the code was duplicated to support + * the difference in structures that were used. This + * file creates a macro of everything that is different between + * the 64 and 32 bit code, such that by including this header + * twice we can create both sets of functions by including this + * header once with RECORD_MCOUNT_64 undefined, and again with + * it defined. + * + * This conversion to macros was done by: + * Copyright 2010 Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. + */ +#undef append_func +#undef is_fake_mcount +#undef fn_is_fake_mcount +#undef MIPS_is_fake_mcount +#undef mcount_adjust +#undef sift_rel_mcount +#undef nop_mcount +#undef find_secsym_ndx +#undef __has_rel_mcount +#undef has_rel_mcount +#undef tot_relsize +#undef get_mcountsym +#undef find_symtab +#undef get_shnum +#undef set_shnum +#undef get_shstrndx +#undef get_symindex +#undef get_sym_str_and_relp +#undef do_func +#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 _w +#undef _align +#undef _size + +#ifdef RECORD_MCOUNT_64 +# define append_func append64 +# define sift_rel_mcount sift64_rel_mcount +# define nop_mcount nop_mcount_64 +# define find_secsym_ndx find64_secsym_ndx +# define __has_rel_mcount __has64_rel_mcount +# define has_rel_mcount has64_rel_mcount +# define tot_relsize tot64_relsize +# define find_symtab find_symtab64 +# define get_shnum get_shnum64 +# define set_shnum set_shnum64 +# define get_shstrndx get_shstrndx64 +# define get_symindex get_symindex64 +# define get_sym_str_and_relp get_sym_str_and_relp_64 +# define do_func do64 +# define get_mcountsym get_mcountsym_64 +# define is_fake_mcount is_fake_mcount64 +# define fn_is_fake_mcount fn_is_fake_mcount64 +# define MIPS_is_fake_mcount MIPS64_is_fake_mcount +# define mcount_adjust mcount_adjust_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 _w w8 +# define _align 7u +# define _size 8 +#else +# define append_func append32 +# define sift_rel_mcount sift32_rel_mcount +# define nop_mcount nop_mcount_32 +# define find_secsym_ndx find32_secsym_ndx +# define __has_rel_mcount __has32_rel_mcount +# define has_rel_mcount has32_rel_mcount +# define tot_relsize tot32_relsize +# define find_symtab find_symtab32 +# define get_shnum get_shnum32 +# define set_shnum set_shnum32 +# define get_shstrndx get_shstrndx32 +# define get_symindex get_symindex32 +# define get_sym_str_and_relp get_sym_str_and_relp_32 +# define do_func do32 +# define get_mcountsym get_mcountsym_32 +# define is_fake_mcount is_fake_mcount32 +# define fn_is_fake_mcount fn_is_fake_mcount32 +# define MIPS_is_fake_mcount MIPS32_is_fake_mcount +# define mcount_adjust mcount_adjust_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 _w w +# define _align 3u +# define _size 4 +#endif + +/* Functions and pointers that do_file() may override for specific e_machine. */ +static int fn_is_fake_mcount(Elf_Rel const *rp) +{ + return 0; +} +static int (*is_fake_mcount)(Elf_Rel const *rp) = fn_is_fake_mcount; + +static uint_t fn_ELF_R_SYM(Elf_Rel const *rp) +{ + return ELF_R_SYM(_w(rp->r_info)); +} +static uint_t (*Elf_r_sym)(Elf_Rel const *rp) = fn_ELF_R_SYM; + +static void fn_ELF_R_INFO(Elf_Rel *const rp, unsigned sym, unsigned type) +{ + rp->r_info = _w(ELF_R_INFO(sym, type)); +} +static void (*Elf_r_info)(Elf_Rel *const rp, unsigned sym, unsigned type) = fn_ELF_R_INFO; + +static int mcount_adjust = 0; + +/* + * MIPS mcount long call has 2 _mcount symbols, only the position of the 1st + * _mcount symbol is needed for dynamic function tracer, with it, to disable + * tracing(ftrace_make_nop), the instruction in the position is replaced with + * the "b label" instruction, to enable tracing(ftrace_make_call), replace the + * instruction back. So, here, we set the 2nd one as fake and filter it. + * + * c: 3c030000 lui v1,0x0 <--> b label + * c: R_MIPS_HI16 _mcount + * c: R_MIPS_NONE *ABS* + * c: R_MIPS_NONE *ABS* + * 10: 64630000 daddiu v1,v1,0 + * 10: R_MIPS_LO16 _mcount + * 10: R_MIPS_NONE *ABS* + * 10: R_MIPS_NONE *ABS* + * 14: 03e0082d move at,ra + * 18: 0060f809 jalr v1 + * label: + */ +#define MIPS_FAKEMCOUNT_OFFSET 4 + +static int MIPS_is_fake_mcount(Elf_Rel const *rp) +{ + static Elf_Addr old_r_offset = ~(Elf_Addr)0; + Elf_Addr current_r_offset = _w(rp->r_offset); + int is_fake; + + is_fake = (old_r_offset != ~(Elf_Addr)0) && + (current_r_offset - old_r_offset == MIPS_FAKEMCOUNT_OFFSET); + old_r_offset = current_r_offset; + + return is_fake; +} + +static unsigned int get_symindex(Elf_Sym const *sym, Elf32_Word const *symtab, + Elf32_Word const *symtab_shndx) +{ + unsigned long offset; + unsigned short shndx = w2(sym->st_shndx); + int index; + + if (shndx > SHN_UNDEF && shndx < SHN_LORESERVE) + return shndx; + + if (shndx == SHN_XINDEX) { + offset = (unsigned long)sym - (unsigned long)symtab; + index = offset / sizeof(*sym); + + return w(symtab_shndx[index]); + } + + return 0; +} + +static unsigned int get_shnum(Elf_Ehdr const *ehdr, Elf_Shdr const *shdr0) +{ + if (shdr0 && !ehdr->e_shnum) + return w(shdr0->sh_size); + + return w2(ehdr->e_shnum); +} + +static void set_shnum(Elf_Ehdr *ehdr, Elf_Shdr *shdr0, unsigned int new_shnum) +{ + if (new_shnum >= SHN_LORESERVE) { + ehdr->e_shnum = 0; + shdr0->sh_size = w(new_shnum); + } else + ehdr->e_shnum = w2(new_shnum); +} + +static int get_shstrndx(Elf_Ehdr const *ehdr, Elf_Shdr const *shdr0) +{ + if (ehdr->e_shstrndx != SHN_XINDEX) + return w2(ehdr->e_shstrndx); + + return w(shdr0->sh_link); +} + +static void find_symtab(Elf_Ehdr *const ehdr, Elf_Shdr const *shdr0, + unsigned const nhdr, Elf32_Word **symtab, + Elf32_Word **symtab_shndx) +{ + Elf_Shdr const *relhdr; + unsigned k; + + *symtab = NULL; + *symtab_shndx = NULL; + + for (relhdr = shdr0, k = nhdr; k; --k, ++relhdr) { + if (relhdr->sh_type == SHT_SYMTAB) + *symtab = (void *)ehdr + relhdr->sh_offset; + else if (relhdr->sh_type == SHT_SYMTAB_SHNDX) + *symtab_shndx = (void *)ehdr + relhdr->sh_offset; + + if (*symtab && *symtab_shndx) + break; + } +} + +/* Append the new shstrtab, Elf_Shdr[], __mcount_loc and its relocations. */ +static int append_func(Elf_Ehdr *const ehdr, + Elf_Shdr *const shstr, + uint_t const *const mloc0, + uint_t const *const mlocp, + Elf_Rel const *const mrel0, + Elf_Rel const *const mrelp, + unsigned int const rel_entsize, + unsigned int const symsec_sh_link) +{ + /* Begin constructing output file */ + Elf_Shdr mcsec; + char const *mc_name = (sizeof(Elf_Rela) == rel_entsize) + ? ".rela__mcount_loc" + : ".rel__mcount_loc"; + uint_t const old_shoff = _w(ehdr->e_shoff); + uint_t const old_shstr_sh_size = _w(shstr->sh_size); + uint_t const old_shstr_sh_offset = _w(shstr->sh_offset); + Elf_Shdr *const shdr0 = (Elf_Shdr *)(old_shoff + (void *)ehdr); + unsigned int const old_shnum = get_shnum(ehdr, shdr0); + unsigned int const new_shnum = 2 + old_shnum; /* {.rel,}__mcount_loc */ + uint_t t = 1 + strlen(mc_name) + _w(shstr->sh_size); + uint_t new_e_shoff; + + shstr->sh_size = _w(t); + shstr->sh_offset = _w(sb.st_size); + t += sb.st_size; + t += (_align & -t); /* word-byte align */ + new_e_shoff = t; + + set_shnum(ehdr, shdr0, new_shnum); + + /* body for new shstrtab */ + if (ulseek(sb.st_size, SEEK_SET) < 0) + return -1; + if (uwrite(old_shstr_sh_offset + (void *)ehdr, old_shstr_sh_size) < 0) + return -1; + if (uwrite(mc_name, 1 + strlen(mc_name)) < 0) + return -1; + + /* old(modified) Elf_Shdr table, word-byte aligned */ + if (ulseek(t, SEEK_SET) < 0) + return -1; + t += sizeof(Elf_Shdr) * old_shnum; + if (uwrite(old_shoff + (void *)ehdr, + sizeof(Elf_Shdr) * old_shnum) < 0) + return -1; + + /* new sections __mcount_loc and .rel__mcount_loc */ + t += 2*sizeof(mcsec); + mcsec.sh_name = w((sizeof(Elf_Rela) == rel_entsize) + strlen(".rel") + + old_shstr_sh_size); + mcsec.sh_type = w(SHT_PROGBITS); + mcsec.sh_flags = _w(SHF_ALLOC); + mcsec.sh_addr = 0; + mcsec.sh_offset = _w(t); + mcsec.sh_size = _w((void *)mlocp - (void *)mloc0); + mcsec.sh_link = 0; + mcsec.sh_info = 0; + mcsec.sh_addralign = _w(_size); + mcsec.sh_entsize = _w(_size); + if (uwrite(&mcsec, sizeof(mcsec)) < 0) + return -1; + + mcsec.sh_name = w(old_shstr_sh_size); + mcsec.sh_type = (sizeof(Elf_Rela) == rel_entsize) + ? w(SHT_RELA) + : w(SHT_REL); + mcsec.sh_flags = 0; + mcsec.sh_addr = 0; + mcsec.sh_offset = _w((void *)mlocp - (void *)mloc0 + t); + mcsec.sh_size = _w((void *)mrelp - (void *)mrel0); + mcsec.sh_link = w(symsec_sh_link); + mcsec.sh_info = w(old_shnum); + mcsec.sh_addralign = _w(_size); + mcsec.sh_entsize = _w(rel_entsize); + + if (uwrite(&mcsec, sizeof(mcsec)) < 0) + return -1; + + if (uwrite(mloc0, (void *)mlocp - (void *)mloc0) < 0) + return -1; + if (uwrite(mrel0, (void *)mrelp - (void *)mrel0) < 0) + return -1; + + ehdr->e_shoff = _w(new_e_shoff); + if (ulseek(0, SEEK_SET) < 0) + return -1; + if (uwrite(ehdr, sizeof(*ehdr)) < 0) + return -1; + return 0; +} + +static unsigned get_mcountsym(Elf_Sym const *const sym0, + Elf_Rel const *relp, + char const *const str0) +{ + unsigned mcountsym = 0; + + Elf_Sym const *const symp = + &sym0[Elf_r_sym(relp)]; + char const *symname = &str0[w(symp->st_name)]; + char const *mcount = gpfx == '_' ? "_mcount" : "mcount"; + char const *fentry = "__fentry__"; + + if (symname[0] == '.') + ++symname; /* ppc64 hack */ + if (strcmp(mcount, symname) == 0 || + (altmcount && strcmp(altmcount, symname) == 0) || + (strcmp(fentry, symname) == 0)) + mcountsym = Elf_r_sym(relp); + + return mcountsym; +} + +static void get_sym_str_and_relp(Elf_Shdr const *const relhdr, + Elf_Ehdr const *const ehdr, + Elf_Sym const **sym0, + char const **str0, + Elf_Rel const **relp) +{ + Elf_Shdr *const shdr0 = (Elf_Shdr *)(_w(ehdr->e_shoff) + + (void *)ehdr); + unsigned const symsec_sh_link = w(relhdr->sh_link); + Elf_Shdr const *const symsec = &shdr0[symsec_sh_link]; + Elf_Shdr const *const strsec = &shdr0[w(symsec->sh_link)]; + Elf_Rel const *const rel0 = (Elf_Rel const *)(_w(relhdr->sh_offset) + + (void *)ehdr); + + *sym0 = (Elf_Sym const *)(_w(symsec->sh_offset) + + (void *)ehdr); + + *str0 = (char const *)(_w(strsec->sh_offset) + + (void *)ehdr); + + *relp = rel0; +} + +/* + * Look at the relocations in order to find the calls to mcount. + * Accumulate the section offsets that are found, and their relocation info, + * onto the end of the existing arrays. + */ +static uint_t *sift_rel_mcount(uint_t *mlocp, + unsigned const offbase, + Elf_Rel **const mrelpp, + Elf_Shdr const *const relhdr, + Elf_Ehdr const *const ehdr, + unsigned const recsym, + uint_t const recval, + unsigned const reltype) +{ + uint_t *const mloc0 = mlocp; + Elf_Rel *mrelp = *mrelpp; + Elf_Sym const *sym0; + char const *str0; + Elf_Rel const *relp; + unsigned rel_entsize = _w(relhdr->sh_entsize); + unsigned const nrel = _w(relhdr->sh_size) / rel_entsize; + unsigned mcountsym = 0; + unsigned t; + + get_sym_str_and_relp(relhdr, ehdr, &sym0, &str0, &relp); + + for (t = nrel; t; --t) { + if (!mcountsym) + mcountsym = get_mcountsym(sym0, relp, str0); + + if (mcountsym && mcountsym == Elf_r_sym(relp) && + !is_fake_mcount(relp)) { + uint_t const addend = + _w(_w(relp->r_offset) - recval + mcount_adjust); + mrelp->r_offset = _w(offbase + + ((void *)mlocp - (void *)mloc0)); + Elf_r_info(mrelp, recsym, reltype); + if (rel_entsize == sizeof(Elf_Rela)) { + ((Elf_Rela *)mrelp)->r_addend = addend; + *mlocp++ = 0; + } else + *mlocp++ = addend; + + mrelp = (Elf_Rel *)(rel_entsize + (void *)mrelp); + } + relp = (Elf_Rel const *)(rel_entsize + (void *)relp); + } + *mrelpp = mrelp; + return mlocp; +} + +/* + * Read the relocation table again, but this time its called on sections + * that are not going to be traced. The mcount calls here will be converted + * into nops. + */ +static int nop_mcount(Elf_Shdr const *const relhdr, + Elf_Ehdr const *const ehdr, + const char *const txtname) +{ + Elf_Shdr *const shdr0 = (Elf_Shdr *)(_w(ehdr->e_shoff) + + (void *)ehdr); + Elf_Sym const *sym0; + char const *str0; + Elf_Rel const *relp; + Elf_Shdr const *const shdr = &shdr0[w(relhdr->sh_info)]; + unsigned rel_entsize = _w(relhdr->sh_entsize); + unsigned const nrel = _w(relhdr->sh_size) / rel_entsize; + unsigned mcountsym = 0; + unsigned t; + int once = 0; + + get_sym_str_and_relp(relhdr, ehdr, &sym0, &str0, &relp); + + for (t = nrel; t; --t) { + int ret = -1; + + if (!mcountsym) + mcountsym = get_mcountsym(sym0, relp, str0); + + if (mcountsym == Elf_r_sym(relp) && !is_fake_mcount(relp)) { + if (make_nop) + ret = make_nop((void *)ehdr, _w(shdr->sh_offset) + _w(relp->r_offset)); + if (warn_on_notrace_sect && !once) { + printf("Section %s has mcount callers being ignored\n", + txtname); + once = 1; + /* just warn? */ + if (!make_nop) + return 0; + } + } + + /* + * If we successfully removed the mcount, mark the relocation + * as a nop (don't do anything with it). + */ + if (!ret) { + Elf_Rel rel; + rel = *(Elf_Rel *)relp; + Elf_r_info(&rel, Elf_r_sym(relp), rel_type_nop); + if (ulseek((void *)relp - (void *)ehdr, SEEK_SET) < 0) + return -1; + if (uwrite(&rel, sizeof(rel)) < 0) + return -1; + } + relp = (Elf_Rel const *)(rel_entsize + (void *)relp); + } + return 0; +} + +/* + * Find a symbol in the given section, to be used as the base for relocating + * the table of offsets of calls to mcount. A local or global symbol suffices, + * but avoid a Weak symbol because it may be overridden; the change in value + * would invalidate the relocations of the offsets of the calls to mcount. + * Often the found symbol will be the unnamed local symbol generated by + * GNU 'as' for the start of each section. For example: + * Num: Value Size Type Bind Vis Ndx Name + * 2: 00000000 0 SECTION LOCAL DEFAULT 1 + */ +static int find_secsym_ndx(unsigned const txtndx, + char const *const txtname, + uint_t *const recvalp, + unsigned int *sym_index, + Elf_Shdr const *const symhdr, + Elf32_Word const *symtab, + Elf32_Word const *symtab_shndx, + Elf_Ehdr const *const ehdr) +{ + Elf_Sym const *const sym0 = (Elf_Sym const *)(_w(symhdr->sh_offset) + + (void *)ehdr); + unsigned const nsym = _w(symhdr->sh_size) / _w(symhdr->sh_entsize); + Elf_Sym const *symp; + unsigned t; + + for (symp = sym0, t = nsym; t; --t, ++symp) { + unsigned int const st_bind = ELF_ST_BIND(symp->st_info); + + if (txtndx == get_symindex(symp, symtab, symtab_shndx) + /* avoid STB_WEAK */ + && (STB_LOCAL == st_bind || STB_GLOBAL == st_bind)) { + /* function symbols on ARM have quirks, avoid them */ + if (w2(ehdr->e_machine) == EM_ARM + && ELF_ST_TYPE(symp->st_info) == STT_FUNC) + continue; + + *recvalp = _w(symp->st_value); + *sym_index = symp - sym0; + return 0; + } + } + fprintf(stderr, "Cannot find symbol for section %u: %s.\n", + txtndx, txtname); + return -1; +} + +/* Evade ISO C restriction: no declaration after statement in has_rel_mcount. */ +static char const * __has_rel_mcount(Elf_Shdr const *const relhdr, /* reltype */ + Elf_Shdr const *const shdr0, + char const *const shstrtab, + char const *const fname) +{ + /* .sh_info depends on .sh_type == SHT_REL[,A] */ + Elf_Shdr const *const txthdr = &shdr0[w(relhdr->sh_info)]; + char const *const txtname = &shstrtab[w(txthdr->sh_name)]; + + if (strcmp("__mcount_loc", txtname) == 0) { + fprintf(stderr, "warning: __mcount_loc already exists: %s\n", + fname); + return already_has_rel_mcount; + } + if (w(txthdr->sh_type) != SHT_PROGBITS || + !(_w(txthdr->sh_flags) & SHF_EXECINSTR)) + return NULL; + return txtname; +} + +static char const *has_rel_mcount(Elf_Shdr const *const relhdr, + Elf_Shdr const *const shdr0, + char const *const shstrtab, + char const *const fname) +{ + if (w(relhdr->sh_type) != SHT_REL && w(relhdr->sh_type) != SHT_RELA) + return NULL; + return __has_rel_mcount(relhdr, shdr0, shstrtab, fname); +} + + +static unsigned tot_relsize(Elf_Shdr const *const shdr0, + unsigned nhdr, + const char *const shstrtab, + const char *const fname) +{ + unsigned totrelsz = 0; + Elf_Shdr const *shdrp = shdr0; + char const *txtname; + + for (; nhdr; --nhdr, ++shdrp) { + txtname = has_rel_mcount(shdrp, shdr0, shstrtab, fname); + if (txtname == already_has_rel_mcount) { + totrelsz = 0; + break; + } + if (txtname && is_mcounted_section_name(txtname)) + totrelsz += _w(shdrp->sh_size); + } + return totrelsz; +} + +/* Overall supervision for Elf32 ET_REL file. */ +static int do_func(Elf_Ehdr *const ehdr, char const *const fname, + unsigned const reltype) +{ + Elf_Shdr *const shdr0 = (Elf_Shdr *)(_w(ehdr->e_shoff) + + (void *)ehdr); + unsigned const nhdr = get_shnum(ehdr, shdr0); + Elf_Shdr *const shstr = &shdr0[get_shstrndx(ehdr, shdr0)]; + char const *const shstrtab = (char const *)(_w(shstr->sh_offset) + + (void *)ehdr); + + Elf_Shdr const *relhdr; + unsigned k; + + Elf32_Word *symtab; + Elf32_Word *symtab_shndx; + + /* Upper bound on space: assume all relevant relocs are for mcount. */ + unsigned totrelsz; + + Elf_Rel * mrel0; + Elf_Rel * mrelp; + + uint_t * mloc0; + uint_t * mlocp; + + unsigned rel_entsize = 0; + unsigned symsec_sh_link = 0; + + int result = 0; + + totrelsz = tot_relsize(shdr0, nhdr, shstrtab, fname); + if (totrelsz == 0) + return 0; + mrel0 = umalloc(totrelsz); + mrelp = mrel0; + if (!mrel0) + return -1; + + /* 2*sizeof(address) <= sizeof(Elf_Rel) */ + mloc0 = umalloc(totrelsz>>1); + mlocp = mloc0; + if (!mloc0) { + free(mrel0); + return -1; + } + + find_symtab(ehdr, shdr0, nhdr, &symtab, &symtab_shndx); + + for (relhdr = shdr0, k = nhdr; k; --k, ++relhdr) { + char const *const txtname = has_rel_mcount(relhdr, shdr0, + shstrtab, fname); + if (txtname == already_has_rel_mcount) { + result = 0; + file_updated = 0; + goto out; /* Nothing to be done; don't append! */ + } + if (txtname && is_mcounted_section_name(txtname)) { + unsigned int recsym; + uint_t recval = 0; + + symsec_sh_link = w(relhdr->sh_link); + result = find_secsym_ndx(w(relhdr->sh_info), txtname, + &recval, &recsym, + &shdr0[symsec_sh_link], + symtab, symtab_shndx, + ehdr); + if (result) + goto out; + + rel_entsize = _w(relhdr->sh_entsize); + mlocp = sift_rel_mcount(mlocp, + (void *)mlocp - (void *)mloc0, &mrelp, + relhdr, ehdr, recsym, recval, reltype); + } else if (txtname && (warn_on_notrace_sect || make_nop)) { + /* + * This section is ignored by ftrace, but still + * has mcount calls. Convert them to nops now. + */ + if (nop_mcount(relhdr, ehdr, txtname) < 0) { + result = -1; + goto out; + } + } + } + if (!result && mloc0 != mlocp) + result = append_func(ehdr, shstr, mloc0, mlocp, mrel0, mrelp, + rel_entsize, symsec_sh_link); +out: + free(mrel0); + free(mloc0); + return result; +} diff --git a/scripts/recordmcount.pl b/scripts/recordmcount.pl new file mode 100755 index 000000000..861b56dda --- /dev/null +++ b/scripts/recordmcount.pl @@ -0,0 +1,596 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-only +# (c) 2008, Steven Rostedt <srostedt@redhat.com> +# +# recordmcount.pl - makes a section called __mcount_loc that holds +# all the offsets to the calls to mcount. +# +# +# What we want to end up with this is that each object file will have a +# section called __mcount_loc that will hold the list of pointers to mcount +# callers. After final linking, the vmlinux will have within .init.data the +# list of all callers to mcount between __start_mcount_loc and __stop_mcount_loc. +# Later on boot up, the kernel will read this list, save the locations and turn +# them into nops. When tracing or profiling is later enabled, these locations +# will then be converted back to pointers to some function. +# +# This is no easy feat. This script is called just after the original +# object is compiled and before it is linked. +# +# When parse this object file using 'objdump', the references to the call +# sites are offsets from the section that the call site is in. Hence, all +# functions in a section that has a call site to mcount, will have the +# offset from the beginning of the section and not the beginning of the +# function. +# +# But where this section will reside finally in vmlinx is undetermined at +# this point. So we can't use this kind of offsets to record the final +# address of this call site. +# +# The trick is to change the call offset referring the start of a section to +# referring a function symbol in this section. During the link step, 'ld' will +# compute the final address according to the information we record. +# +# e.g. +# +# .section ".sched.text", "ax" +# [...] +# func1: +# [...] +# call mcount (offset: 0x10) +# [...] +# ret +# .globl fun2 +# func2: (offset: 0x20) +# [...] +# [...] +# ret +# func3: +# [...] +# call mcount (offset: 0x30) +# [...] +# +# Both relocation offsets for the mcounts in the above example will be +# offset from .sched.text. If we choose global symbol func2 as a reference and +# make another file called tmp.s with the new offsets: +# +# .section __mcount_loc +# .quad func2 - 0x10 +# .quad func2 + 0x10 +# +# We can then compile this tmp.s into tmp.o, and link it back to the original +# object. +# +# In our algorithm, we will choose the first global function we meet in this +# section as the reference. But this gets hard if there is no global functions +# in this section. In such a case we have to select a local one. E.g. func1: +# +# .section ".sched.text", "ax" +# func1: +# [...] +# call mcount (offset: 0x10) +# [...] +# ret +# func2: +# [...] +# call mcount (offset: 0x20) +# [...] +# .section "other.section" +# +# If we make the tmp.s the same as above, when we link together with +# the original object, we will end up with two symbols for func1: +# one local, one global. After final compile, we will end up with +# an undefined reference to func1 or a wrong reference to another global +# func1 in other files. +# +# Since local objects can reference local variables, we need to find +# a way to make tmp.o reference the local objects of the original object +# file after it is linked together. To do this, we convert func1 +# into a global symbol before linking tmp.o. Then after we link tmp.o +# we will only have a single symbol for func1 that is global. +# We can convert func1 back into a local symbol and we are done. +# +# Here are the steps we take: +# +# 1) Record all the local and weak symbols by using 'nm' +# 2) Use objdump to find all the call site offsets and sections for +# mcount. +# 3) Compile the list into its own object. +# 4) Do we have to deal with local functions? If not, go to step 8. +# 5) Make an object that converts these local functions to global symbols +# with objcopy. +# 6) Link together this new object with the list object. +# 7) Convert the local functions back to local symbols and rename +# the result as the original object. +# 8) Link the object with the list object. +# 9) Move the result back to the original object. +# + +use warnings; +use strict; + +my $P = $0; +$P =~ s@.*/@@g; + +my $V = '0.1'; + +if ($#ARGV != 11) { + print "usage: $P arch endian bits objdump objcopy cc ld nm rm mv is_module inputfile\n"; + print "version: $V\n"; + exit(1); +} + +my ($arch, $endian, $bits, $objdump, $objcopy, $cc, + $ld, $nm, $rm, $mv, $is_module, $inputfile) = @ARGV; + +# This file refers to mcount and shouldn't be ftraced, so lets' ignore it +if ($inputfile =~ m,kernel/trace/ftrace\.o$,) { + exit(0); +} + +# Acceptable sections to record. +my %text_sections = ( + ".text" => 1, + ".init.text" => 1, + ".ref.text" => 1, + ".sched.text" => 1, + ".spinlock.text" => 1, + ".irqentry.text" => 1, + ".softirqentry.text" => 1, + ".kprobes.text" => 1, + ".cpuidle.text" => 1, + ".text.unlikely" => 1, +); + +# Acceptable section-prefixes to record. +my %text_section_prefixes = ( + ".text." => 1, +); + +# Note: we are nice to C-programmers here, thus we skip the '||='-idiom. +$objdump = 'objdump' if (!$objdump); +$objcopy = 'objcopy' if (!$objcopy); +$cc = 'gcc' if (!$cc); +$ld = 'ld' if (!$ld); +$nm = 'nm' if (!$nm); +$rm = 'rm' if (!$rm); +$mv = 'mv' if (!$mv); + +#print STDERR "running: $P '$arch' '$objdump' '$objcopy' '$cc' '$ld' " . +# "'$nm' '$rm' '$mv' '$inputfile'\n"; + +my %locals; # List of local (static) functions +my %weak; # List of weak functions +my %convert; # List of local functions used that needs conversion + +my $type; +my $local_regex; # Match a local function (return function) +my $weak_regex; # Match a weak function (return function) +my $section_regex; # Find the start of a section +my $function_regex; # Find the name of a function + # (return offset and func name) +my $mcount_regex; # Find the call site to mcount (return offset) +my $mcount_adjust; # Address adjustment to mcount offset +my $alignment; # The .align value to use for $mcount_section +my $section_type; # Section header plus possible alignment command + +if ($arch =~ /(x86(_64)?)|(i386)/) { + if ($bits == 64) { + $arch = "x86_64"; + } else { + $arch = "i386"; + } +} + +# +# We base the defaults off of i386, the other archs may +# feel free to change them in the below if statements. +# +$local_regex = "^[0-9a-fA-F]+\\s+t\\s+(\\S+)"; +$weak_regex = "^[0-9a-fA-F]+\\s+([wW])\\s+(\\S+)"; +$section_regex = "Disassembly of section\\s+(\\S+):"; +$function_regex = "^([0-9a-fA-F]+)\\s+<([^^]*?)>:"; +$mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s(mcount|__fentry__)\$"; +$section_type = '@progbits'; +$mcount_adjust = 0; +$type = ".long"; + +if ($arch eq "x86_64") { + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s(mcount|__fentry__)([+-]0x[0-9a-zA-Z]+)?\$"; + $type = ".quad"; + $alignment = 8; + $mcount_adjust = -1; + + # force flags for this arch + $ld .= " -m elf_x86_64"; + $objdump .= " -M x86-64"; + $objcopy .= " -O elf64-x86-64"; + $cc .= " -m64"; + +} elsif ($arch eq "i386") { + $alignment = 4; + $mcount_adjust = -1; + + # force flags for this arch + $ld .= " -m elf_i386"; + $objdump .= " -M i386"; + $objcopy .= " -O elf32-i386"; + $cc .= " -m32"; + +} elsif ($arch eq "s390" && $bits == 64) { + if ($cc =~ /-DCC_USING_HOTPATCH/) { + $mcount_regex = "^\\s*([0-9a-fA-F]+):\\s*c0 04 00 00 00 00\\s*(brcl\\s*0,|jgnop\\s*)[0-9a-f]+ <([^\+]*)>\$"; + $mcount_adjust = 0; + } + $alignment = 8; + $type = ".quad"; + $ld .= " -m elf64_s390"; + $cc .= " -m64"; + +} elsif ($arch eq "sh") { + $alignment = 2; + + # force flags for this arch + $ld .= " -m shlelf_linux"; + if ($endian eq "big") { + $objcopy .= " -O elf32-shbig-linux"; + } else { + $objcopy .= " -O elf32-sh-linux"; + } + +} elsif ($arch eq "powerpc") { + my $ldemulation; + + $local_regex = "^[0-9a-fA-F]+\\s+t\\s+(\\.?\\S+)"; + # See comment in the sparc64 section for why we use '\w'. + $function_regex = "^([0-9a-fA-F]+)\\s+<(\\.?\\w*?)>:"; + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s\\.?_mcount\$"; + + if ($endian eq "big") { + $cc .= " -mbig-endian "; + $ld .= " -EB "; + $ldemulation = "ppc" + } else { + $cc .= " -mlittle-endian "; + $ld .= " -EL "; + $ldemulation = "lppc" + } + if ($bits == 64) { + $type = ".quad"; + $cc .= " -m64 "; + $ld .= " -m elf64".$ldemulation." "; + } else { + $cc .= " -m32 "; + $ld .= " -m elf32".$ldemulation." "; + } + +} elsif ($arch eq "arm") { + $alignment = 2; + $section_type = '%progbits'; + $mcount_regex = "^\\s*([0-9a-fA-F]+):\\s*R_ARM_(CALL|PC24|THM_CALL)" . + "\\s+(__gnu_mcount_nc|mcount)\$"; + +} elsif ($arch eq "arm64") { + $alignment = 3; + $section_type = '%progbits'; + $mcount_regex = "^\\s*([0-9a-fA-F]+):\\s*R_AARCH64_CALL26\\s+_mcount\$"; + $type = ".quad"; +} elsif ($arch eq "sparc64") { + # In the objdump output there are giblets like: + # 0000000000000000 <igmp_net_exit-0x18>: + # As there's some data blobs that get emitted into the + # text section before the first instructions and the first + # real symbols. We don't want to match that, so to combat + # this we use '\w' so we'll match just plain symbol names, + # and not those that also include hex offsets inside of the + # '<>' brackets. Actually the generic function_regex setting + # could safely use this too. + $function_regex = "^([0-9a-fA-F]+)\\s+<(\\w*?)>:"; + + # Sparc64 calls '_mcount' instead of plain 'mcount'. + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s_mcount\$"; + + $alignment = 8; + $type = ".xword"; + $ld .= " -m elf64_sparc"; + $cc .= " -m64"; + $objcopy .= " -O elf64-sparc"; +} elsif ($arch eq "mips") { + # To enable module support, we need to enable the -mlong-calls option + # of gcc for module, after using this option, we can not get the real + # offset of the calling to _mcount, but the offset of the lui + # instruction or the addiu one. herein, we record the address of the + # first one, and then we can replace this instruction by a branch + # instruction to jump over the profiling function to filter the + # indicated functions, or switch back to the lui instruction to trace + # them, which means dynamic tracing. + # + # c: 3c030000 lui v1,0x0 + # c: R_MIPS_HI16 _mcount + # c: R_MIPS_NONE *ABS* + # c: R_MIPS_NONE *ABS* + # 10: 64630000 daddiu v1,v1,0 + # 10: R_MIPS_LO16 _mcount + # 10: R_MIPS_NONE *ABS* + # 10: R_MIPS_NONE *ABS* + # 14: 03e0082d move at,ra + # 18: 0060f809 jalr v1 + # + # for the kernel: + # + # 10: 03e0082d move at,ra + # 14: 0c000000 jal 0 <loongson_halt> + # 14: R_MIPS_26 _mcount + # 14: R_MIPS_NONE *ABS* + # 14: R_MIPS_NONE *ABS* + # 18: 00020021 nop + if ($is_module eq "0") { + $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_26\\s+_mcount\$"; + } else { + $mcount_regex = "^\\s*([0-9a-fA-F]+): R_MIPS_HI16\\s+_mcount\$"; + } + $objdump .= " -Melf-trad".$endian."mips "; + + if ($endian eq "big") { + $endian = " -EB "; + $ld .= " -melf".$bits."btsmip"; + } else { + $endian = " -EL "; + $ld .= " -melf".$bits."ltsmip"; + } + + $cc .= " -mno-abicalls -fno-pic -mabi=" . $bits . $endian; + $ld .= $endian; + + if ($bits == 64) { + $function_regex = + "^([0-9a-fA-F]+)\\s+<(.|[^\$]L.*?|\$[^L].*?|[^\$][^L].*?)>:"; + $type = ".dword"; + } +} elsif ($arch eq "microblaze") { + # Microblaze calls '_mcount' instead of plain 'mcount'. + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s_mcount\$"; +} elsif ($arch eq "riscv") { + $function_regex = "^([0-9a-fA-F]+)\\s+<([^.0-9][0-9a-zA-Z_\\.]+)>:"; + $mcount_regex = "^\\s*([0-9a-fA-F]+):\\sR_RISCV_CALL(_PLT)?\\s_mcount\$"; + $type = ".quad"; + $alignment = 2; +} elsif ($arch eq "csky") { + $mcount_regex = "^\\s*([0-9a-fA-F]+):\\s*R_CKCORE_PCREL_JSR_IMM26BY2\\s+_mcount\$"; + $alignment = 2; +} else { + die "Arch $arch is not supported with CONFIG_DYNAMIC_FTRACE"; +} + +my $text_found = 0; +my $read_function = 0; +my $opened = 0; +my $mcount_section = "__mcount_loc"; + +my $dirname; +my $filename; +my $prefix; +my $ext; + +if ($inputfile =~ m,^(.*)/([^/]*)$,) { + $dirname = $1; + $filename = $2; +} else { + $dirname = "."; + $filename = $inputfile; +} + +if ($filename =~ m,^(.*)(\.\S),) { + $prefix = $1; + $ext = $2; +} else { + $prefix = $filename; + $ext = ""; +} + +my $mcount_s = $dirname . "/.tmp_mc_" . $prefix . ".s"; +my $mcount_o = $dirname . "/.tmp_mc_" . $prefix . ".o"; + +# +# Step 1: find all the local (static functions) and weak symbols. +# 't' is local, 'w/W' is weak +# +open (IN, "$nm $inputfile|") || die "error running $nm"; +while (<IN>) { + if (/$local_regex/) { + $locals{$1} = 1; + } elsif (/$weak_regex/) { + $weak{$2} = $1; + } +} +close(IN); + +my @offsets; # Array of offsets of mcount callers +my $ref_func; # reference function to use for offsets +my $offset = 0; # offset of ref_func to section beginning + +## +# update_funcs - print out the current mcount callers +# +# Go through the list of offsets to callers and write them to +# the output file in a format that can be read by an assembler. +# +sub update_funcs +{ + return unless ($ref_func and @offsets); + + # Sanity check on weak function. A weak function may be overwritten by + # another function of the same name, making all these offsets incorrect. + if (defined $weak{$ref_func}) { + die "$inputfile: ERROR: referencing weak function" . + " $ref_func for mcount\n"; + } + + # is this function static? If so, note this fact. + if (defined $locals{$ref_func}) { + $convert{$ref_func} = 1; + } + + # Loop through all the mcount caller offsets and print a reference + # to the caller based from the ref_func. + if (!$opened) { + open(FILE, ">$mcount_s") || die "can't create $mcount_s\n"; + $opened = 1; + print FILE "\t.section $mcount_section,\"a\",$section_type\n"; + print FILE "\t.align $alignment\n" if (defined($alignment)); + } + foreach my $cur_offset (@offsets) { + printf FILE "\t%s %s + %d\n", $type, $ref_func, $cur_offset - $offset; + } +} + +# +# Step 2: find the sections and mcount call sites +# +open(IN, "LC_ALL=C $objdump -hdr $inputfile|") || die "error running $objdump"; + +my $text; + + +# read headers first +my $read_headers = 1; + +while (<IN>) { + + if ($read_headers && /$mcount_section/) { + # + # Somehow the make process can execute this script on an + # object twice. If it does, we would duplicate the mcount + # section and it will cause the function tracer self test + # to fail. Check if the mcount section exists, and if it does, + # warn and exit. + # + print STDERR "ERROR: $mcount_section already in $inputfile\n" . + "\tThis may be an indication that your build is corrupted.\n" . + "\tDelete $inputfile and try again. If the same object file\n" . + "\tstill causes an issue, then disable CONFIG_DYNAMIC_FTRACE.\n"; + exit(-1); + } + + # is it a section? + if (/$section_regex/) { + $read_headers = 0; + + # Only record text sections that we know are safe + $read_function = defined($text_sections{$1}); + if (!$read_function) { + foreach my $prefix (keys %text_section_prefixes) { + if (substr($1, 0, length $prefix) eq $prefix) { + $read_function = 1; + last; + } + } + } + # print out any recorded offsets + update_funcs(); + + # reset all markers and arrays + $text_found = 0; + undef($ref_func); + undef(@offsets); + + # section found, now is this a start of a function? + } elsif ($read_function && /$function_regex/) { + $text_found = 1; + $text = $2; + + # if this is either a local function or a weak function + # keep looking for functions that are global that + # we can use safely. + if (!defined($locals{$text}) && !defined($weak{$text})) { + $ref_func = $text; + $read_function = 0; + $offset = hex $1; + } else { + # if we already have a function, and this is weak, skip it + if (!defined($ref_func) && !defined($weak{$text}) && + # PPC64 can have symbols that start with .L and + # gcc considers these special. Don't use them! + $text !~ /^\.L/) { + $ref_func = $text; + $offset = hex $1; + } + } + } + # is this a call site to mcount? If so, record it to print later + if ($text_found && /$mcount_regex/) { + push(@offsets, (hex $1) + $mcount_adjust); + } +} + +# dump out anymore offsets that may have been found +update_funcs(); + +# If we did not find any mcount callers, we are done (do nothing). +if (!$opened) { + exit(0); +} + +close(FILE); + +# +# Step 3: Compile the file that holds the list of call sites to mcount. +# +`$cc -o $mcount_o -c $mcount_s`; + +my @converts = keys %convert; + +# +# Step 4: Do we have sections that started with local functions? +# +if ($#converts >= 0) { + my $globallist = ""; + my $locallist = ""; + + foreach my $con (@converts) { + $globallist .= " --globalize-symbol $con"; + $locallist .= " --localize-symbol $con"; + } + + my $globalobj = $dirname . "/.tmp_gl_" . $filename; + my $globalmix = $dirname . "/.tmp_mx_" . $filename; + + # + # Step 5: set up each local function as a global + # + `$objcopy $globallist $inputfile $globalobj`; + + # + # Step 6: Link the global version to our list. + # + `$ld -r $globalobj $mcount_o -o $globalmix`; + + # + # Step 7: Convert the local functions back into local symbols + # + `$objcopy $locallist $globalmix $inputfile`; + + # Remove the temp files + `$rm $globalobj $globalmix`; + +} else { + + my $mix = $dirname . "/.tmp_mx_" . $filename; + + # + # Step 8: Link the object with our list of call sites object. + # + `$ld -r $inputfile $mcount_o -o $mix`; + + # + # Step 9: Move the result back to the original object. + # + `$mv $mix $inputfile`; +} + +# Clean up the temp files +`$rm $mcount_o $mcount_s`; + +exit(0); + +# vim: softtabstop=4 diff --git a/scripts/relocs_check.sh b/scripts/relocs_check.sh new file mode 100755 index 000000000..137c66049 --- /dev/null +++ b/scripts/relocs_check.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +# Get a list of all the relocations, remove from it the relocations +# that are known to be legitimate and return this list to arch specific +# script that will look for suspicious relocations. + +objdump="$1" +nm="$2" +vmlinux="$3" + +# Remove from the possible bad relocations those that match an undefined +# weak symbol which will result in an absolute relocation to 0. +# Weak unresolved symbols are of that form in nm output: +# " w _binary__btf_vmlinux_bin_end" +undef_weak_symbols=$($nm "$vmlinux" | awk '$1 ~ /w/ { print $2 }') + +$objdump -R "$vmlinux" | + grep -E '\<R_' | + ([ "$undef_weak_symbols" ] && grep -F -w -v "$undef_weak_symbols" || cat) diff --git a/scripts/remove-stale-files b/scripts/remove-stale-files new file mode 100755 index 000000000..6e39fa854 --- /dev/null +++ b/scripts/remove-stale-files @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +# When you move, remove or rename generated files, you probably also update +# .gitignore and cleaning rules in the Makefile. This is the right thing +# to do. However, people usually do 'git pull', 'git bisect', etc. without +# running 'make clean'. Then, the stale generated files are left over, often +# causing build issues. +# +# Also, 'git status' shows such stale build artifacts as untracked files. +# What is worse, some people send a wrong patch to get them back to .gitignore +# without checking the commit history. +# +# So, when you (re)move generated files, please move the cleaning rules from +# the Makefile to this script. This is run before Kbuild starts building +# anything, so people will not be annoyed by such garbage files. +# +# This script is not intended to grow endlessly. Rather, it is a temporary scrap +# 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. + +# 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 new file mode 100755 index 000000000..c30983562 --- /dev/null +++ b/scripts/rust_is_available.sh @@ -0,0 +1,259 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Tests whether a suitable Rust toolchain is available. + +set -e + +min_tool_version=$(dirname $0)/min-tool-version.sh + +# 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)) +} + +# Print a reference to the Quick Start guide in the documentation. +print_docs_reference() +{ + echo >&2 "***" + echo >&2 "*** Please see Documentation/rust/quick-start.rst for details" + echo >&2 "*** on how to set up the Rust support." + echo >&2 "***" +} + +# Print an explanation about the fact that the script is meant to be called from Kbuild. +print_kbuild_explanation() +{ + echo >&2 "***" + echo >&2 "*** This script is intended to be called from Kbuild." + echo >&2 "*** Please use the 'rustavailable' target to call it instead." + echo >&2 "*** Otherwise, the results may not be meaningful." + exit 1 +} + +# If the script fails for any reason, or if there was any warning, then +# print a reference to the documentation on exit. +warning=0 +trap 'if [ $? -ne 0 ] || [ $warning -ne 0 ]; then print_docs_reference; fi' EXIT + +# Check that the expected environment variables are set. +if [ -z "${RUSTC+x}" ]; then + echo >&2 "***" + echo >&2 "*** Environment variable 'RUSTC' is not set." + print_kbuild_explanation +fi + +if [ -z "${BINDGEN+x}" ]; then + echo >&2 "***" + echo >&2 "*** Environment variable 'BINDGEN' is not set." + print_kbuild_explanation +fi + +if [ -z "${CC+x}" ]; then + echo >&2 "***" + echo >&2 "*** Environment variable 'CC' is not set." + print_kbuild_explanation +fi + +# Check that the Rust compiler exists. +if ! command -v "$RUSTC" >/dev/null; then + echo >&2 "***" + echo >&2 "*** Rust compiler '$RUSTC' could not be found." + echo >&2 "***" + exit 1 +fi + +# Check that the Rust bindings generator exists. +if ! command -v "$BINDGEN" >/dev/null; then + echo >&2 "***" + echo >&2 "*** Rust bindings generator '$BINDGEN' could not be found." + echo >&2 "***" + exit 1 +fi + +# Check that the Rust compiler version is suitable. +# +# Non-stable and distributions' versions may have a version suffix, e.g. `-dev`. +rust_compiler_output=$( \ + LC_ALL=C "$RUSTC" --version 2>/dev/null +) || rust_compiler_code=$? +if [ -n "$rust_compiler_code" ]; then + echo >&2 "***" + echo >&2 "*** Running '$RUSTC' to check the Rust compiler version failed with" + echo >&2 "*** code $rust_compiler_code. See output and docs below for details:" + echo >&2 "***" + echo >&2 "$rust_compiler_output" + echo >&2 "***" + exit 1 +fi +rust_compiler_version=$( \ + echo "$rust_compiler_output" \ + | sed -nE '1s:.*rustc ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p' +) +if [ -z "$rust_compiler_version" ]; then + echo >&2 "***" + echo >&2 "*** Running '$RUSTC' to check the Rust compiler version did not return" + echo >&2 "*** an expected output. See output and docs below for details:" + echo >&2 "***" + echo >&2 "$rust_compiler_output" + echo >&2 "***" + exit 1 +fi +rust_compiler_min_version=$($min_tool_version rustc) +rust_compiler_cversion=$(get_canonical_version $rust_compiler_version) +rust_compiler_min_cversion=$(get_canonical_version $rust_compiler_min_version) +if [ "$rust_compiler_cversion" -lt "$rust_compiler_min_cversion" ]; then + echo >&2 "***" + echo >&2 "*** Rust compiler '$RUSTC' is too old." + echo >&2 "*** Your version: $rust_compiler_version" + echo >&2 "*** Minimum version: $rust_compiler_min_version" + echo >&2 "***" + exit 1 +fi + +# Check that the Rust bindings generator is suitable. +# +# Non-stable and distributions' versions may have a version suffix, e.g. `-dev`. +rust_bindings_generator_output=$( \ + LC_ALL=C "$BINDGEN" --version 2>/dev/null +) || rust_bindings_generator_code=$? +if [ -n "$rust_bindings_generator_code" ]; then + echo >&2 "***" + echo >&2 "*** Running '$BINDGEN' to check the Rust bindings generator version failed with" + echo >&2 "*** code $rust_bindings_generator_code. See output and docs below for details:" + echo >&2 "***" + echo >&2 "$rust_bindings_generator_output" + echo >&2 "***" + exit 1 +fi +rust_bindings_generator_version=$( \ + echo "$rust_bindings_generator_output" \ + | sed -nE '1s:.*bindgen ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p' +) +if [ -z "$rust_bindings_generator_version" ]; then + echo >&2 "***" + echo >&2 "*** Running '$BINDGEN' to check the bindings generator version did not return" + echo >&2 "*** an expected output. See output and docs below for details:" + echo >&2 "***" + echo >&2 "$rust_bindings_generator_output" + echo >&2 "***" + exit 1 +fi +rust_bindings_generator_min_version=$($min_tool_version bindgen) +rust_bindings_generator_cversion=$(get_canonical_version $rust_bindings_generator_version) +rust_bindings_generator_min_cversion=$(get_canonical_version $rust_bindings_generator_min_version) +if [ "$rust_bindings_generator_cversion" -lt "$rust_bindings_generator_min_cversion" ]; then + echo >&2 "***" + echo >&2 "*** Rust bindings generator '$BINDGEN' is too old." + echo >&2 "*** Your version: $rust_bindings_generator_version" + echo >&2 "*** Minimum version: $rust_bindings_generator_min_version" + echo >&2 "***" + exit 1 +fi + +# Check that the `libclang` used by the Rust bindings generator is suitable. +# +# In order to do that, first invoke `bindgen` to get the `libclang` version +# found by `bindgen`. This step may already fail if, for instance, `libclang` +# is not found, thus inform the user in such a case. +bindgen_libclang_output=$( \ + LC_ALL=C "$BINDGEN" $(dirname $0)/rust_is_available_bindgen_libclang.h 2>&1 >/dev/null +) || bindgen_libclang_code=$? +if [ -n "$bindgen_libclang_code" ]; then + echo >&2 "***" + echo >&2 "*** Running '$BINDGEN' to check the libclang version (used by the Rust" + echo >&2 "*** bindings generator) failed with code $bindgen_libclang_code. This may be caused by" + echo >&2 "*** a failure to locate libclang. See output and docs below for details:" + echo >&2 "***" + echo >&2 "$bindgen_libclang_output" + echo >&2 "***" + exit 1 +fi + +# `bindgen` returned successfully, thus use the output to check that the version +# of the `libclang` found by the Rust bindings generator is suitable. +# +# Unlike other version checks, note that this one does not necessarily appear +# in the first line of the output, thus no `sed` address is provided. +bindgen_libclang_version=$( \ + echo "$bindgen_libclang_output" \ + | sed -nE 's:.*clang version ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p' +) +if [ -z "$bindgen_libclang_version" ]; then + echo >&2 "***" + echo >&2 "*** Running '$BINDGEN' to check the libclang version (used by the Rust" + echo >&2 "*** bindings generator) did not return an expected output. See output" + echo >&2 "*** and docs below for details:" + echo >&2 "***" + echo >&2 "$bindgen_libclang_output" + echo >&2 "***" + exit 1 +fi +bindgen_libclang_min_version=$($min_tool_version llvm) +bindgen_libclang_cversion=$(get_canonical_version $bindgen_libclang_version) +bindgen_libclang_min_cversion=$(get_canonical_version $bindgen_libclang_min_version) +if [ "$bindgen_libclang_cversion" -lt "$bindgen_libclang_min_cversion" ]; then + echo >&2 "***" + echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN') is too old." + echo >&2 "*** Your version: $bindgen_libclang_version" + echo >&2 "*** Minimum version: $bindgen_libclang_min_version" + echo >&2 "***" + exit 1 +fi + +if [ "$bindgen_libclang_cversion" -ge 2200000 ] && + [ "$rust_bindings_generator_cversion" -lt 7201 ]; then + # Distributions may have patched the issue. + if ! "$BINDGEN" $(dirname $0)/rust_is_available_bindgen_libclang_22.h | grep -q 'pub foo'; then + echo >&2 "***" + echo >&2 "*** Rust bindings generator '$BINDGEN' < 0.72.1 together with libclang >= 22" + echo >&2 "*** may not work due to a bug (https://github.com/rust-lang/rust-bindgen/pull/3278)." + 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. +# +# In the future, we might be able to perform a full version check, see +# https://github.com/rust-lang/rust-bindgen/issues/2138. +cc_name=$($(dirname $0)/cc-version.sh $CC | cut -f1 -d' ') +if [ "$cc_name" = Clang ]; then + clang_version=$( \ + LC_ALL=C $CC --version 2>/dev/null \ + | sed -nE '1s:.*version ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p' + ) + if [ "$clang_version" != "$bindgen_libclang_version" ]; then + echo >&2 "***" + echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN')" + echo >&2 "*** version does not match Clang's. This may be a problem." + echo >&2 "*** libclang version: $bindgen_libclang_version" + echo >&2 "*** Clang version: $clang_version" + echo >&2 "***" + warning=1 + fi +fi + +# Check that the source code for the `core` standard library exists. +# +# `$KRUSTFLAGS` is passed in case the user added `--sysroot`. +rustc_sysroot=$("$RUSTC" $KRUSTFLAGS --print sysroot) +rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"} +rustc_src_core="$rustc_src/core/src/lib.rs" +if [ ! -e "$rustc_src_core" ]; then + echo >&2 "***" + echo >&2 "*** Source code for the 'core' standard library could not be found" + echo >&2 "*** at '$rustc_src_core'." + echo >&2 "***" + exit 1 +fi diff --git a/scripts/rust_is_available_bindgen_libclang.h b/scripts/rust_is_available_bindgen_libclang.h new file mode 100644 index 000000000..0ef6db10d --- /dev/null +++ b/scripts/rust_is_available_bindgen_libclang.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#pragma message("clang version " __clang_version__) diff --git a/scripts/rust_is_available_bindgen_libclang_22.h b/scripts/rust_is_available_bindgen_libclang_22.h new file mode 100644 index 000000000..6b33544c1 --- /dev/null +++ b/scripts/rust_is_available_bindgen_libclang_22.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +struct S; +struct S { + int foo; +}; diff --git a/scripts/rust_is_available_test.py b/scripts/rust_is_available_test.py new file mode 100755 index 000000000..22bdff980 --- /dev/null +++ b/scripts/rust_is_available_test.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +"""Tests the `rust_is_available.sh` script. + +Some of the tests require the real programs to be available in `$PATH` +under their canonical name (and with the expected versions). +""" + +import enum +import os +import pathlib +import stat +import subprocess +import tempfile +import unittest + +class TestRustIsAvailable(unittest.TestCase): + @enum.unique + class Expected(enum.Enum): + SUCCESS = enum.auto() + SUCCESS_WITH_WARNINGS = enum.auto() + SUCCESS_WITH_EXTRA_OUTPUT = enum.auto() + FAILURE = enum.auto() + + @classmethod + def generate_executable(cls, content): + path = pathlib.Path(cls.tempdir.name) + name = str(len(tuple(path.iterdir()))) + path = path / name + with open(path, "w") as file_: + file_.write(content) + os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR) + return path + + @classmethod + def generate_clang(cls, stdout): + return cls.generate_executable(f"""#!/usr/bin/env python3 +import sys +if "-E" in " ".join(sys.argv): + print({repr("Clang " + " ".join(cls.llvm_default_version.split(" ")))}) +else: + print({repr(stdout)}) +""") + + @classmethod + def generate_rustc(cls, stdout): + return cls.generate_executable(f"""#!/usr/bin/env python3 +import sys +if "--print sysroot" in " ".join(sys.argv): + print({repr(cls.rust_default_sysroot)}) +else: + print({repr(stdout)}) +""") + + @classmethod + def generate_bindgen(cls, version_stdout, libclang_stderr, libclang_22_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 libclang_22_patched: + libclang_22_case = "print('pub foo: ::std::os::raw::c_int,')" + else: + libclang_22_case = "pass" + + return cls.generate_executable(f"""#!/usr/bin/env python3 +import sys +if "rust_is_available_bindgen_libclang.h" in " ".join(sys.argv): + {libclang_case} +elif "rust_is_available_bindgen_libclang_22.h" in " ".join(sys.argv): + {libclang_22_case} +else: + print({repr(version_stdout)}) +""") + + @classmethod + def generate_bindgen_version(cls, stdout): + return cls.generate_bindgen(stdout, cls.bindgen_default_bindgen_libclang_stderr) + + @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): + return cls.generate_bindgen(cls.bindgen_default_bindgen_version_stdout, stderr) + + @classmethod + def setUpClass(cls): + cls.tempdir = tempfile.TemporaryDirectory() + + cls.missing = pathlib.Path(cls.tempdir.name) / "missing" + + cls.nonexecutable = pathlib.Path(cls.tempdir.name) / "nonexecutable" + with open(cls.nonexecutable, "w") as file_: + file_.write("nonexecutable") + + cls.unexpected_binary = "true" + + cls.rustc_default_version = subprocess.check_output(("scripts/min-tool-version.sh", "rustc")).decode().strip() + cls.bindgen_default_version = subprocess.check_output(("scripts/min-tool-version.sh", "bindgen")).decode().strip() + cls.llvm_default_version = subprocess.check_output(("scripts/min-tool-version.sh", "llvm")).decode().strip() + 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}") + cls.default_bindgen = cls.generate_bindgen(cls.bindgen_default_bindgen_version_stdout, cls.bindgen_default_bindgen_libclang_stderr) + cls.default_cc = cls.generate_clang(f"clang version {cls.llvm_default_version}") + + def run_script(self, expected, override_env): + env = { + "RUSTC": self.default_rustc, + "BINDGEN": self.default_bindgen, + "CC": self.default_cc, + } + + for key, value in override_env.items(): + if value is None: + del env[key] + continue + env[key] = value + + result = subprocess.run("scripts/rust_is_available.sh", env=env, capture_output=True) + + # The script should never output anything to `stdout`. + self.assertEqual(result.stdout, b"") + + if expected == self.Expected.SUCCESS: + # When expecting a success, the script should return 0 + # and it should not output anything to `stderr`. + self.assertEqual(result.returncode, 0) + self.assertEqual(result.stderr, b"") + elif expected == self.Expected.SUCCESS_WITH_EXTRA_OUTPUT: + # When expecting a success with extra output (that is not warnings, + # which is the common case), the script should return 0 and it + # should output at least something to `stderr` (the output should + # be checked further by the test). + self.assertEqual(result.returncode, 0) + self.assertNotEqual(result.stderr, b"") + elif expected == self.Expected.SUCCESS_WITH_WARNINGS: + # When expecting a success with warnings, the script should return 0 + # and it should output at least the instructions to `stderr`. + self.assertEqual(result.returncode, 0) + self.assertIn(b"Please see Documentation/rust/quick-start.rst for details", result.stderr) + else: + # When expecting a failure, the script should return non-0 + # and it should output at least the instructions to `stderr`. + self.assertNotEqual(result.returncode, 0) + self.assertIn(b"Please see Documentation/rust/quick-start.rst for details", result.stderr) + + # The output will generally be UTF-8 (i.e. unless the user has + # put strange values in the environment). + result.stderr = result.stderr.decode() + + return result + + def test_rustc_unset(self): + result = self.run_script(self.Expected.FAILURE, { "RUSTC": None }) + self.assertIn("Environment variable 'RUSTC' is not set.", result.stderr) + self.assertIn("This script is intended to be called from Kbuild.", result.stderr) + + def test_bindgen_unset(self): + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": None }) + self.assertIn("Environment variable 'BINDGEN' is not set.", result.stderr) + self.assertIn("This script is intended to be called from Kbuild.", result.stderr) + + def test_cc_unset(self): + result = self.run_script(self.Expected.FAILURE, { "CC": None }) + self.assertIn("Environment variable 'CC' is not set.", result.stderr) + self.assertIn("This script is intended to be called from Kbuild.", result.stderr) + + def test_rustc_missing(self): + result = self.run_script(self.Expected.FAILURE, { "RUSTC": self.missing }) + self.assertIn(f"Rust compiler '{self.missing}' could not be found.", result.stderr) + + def test_bindgen_missing(self): + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": self.missing }) + self.assertIn(f"Rust bindings generator '{self.missing}' could not be found.", result.stderr) + + def test_rustc_nonexecutable(self): + result = self.run_script(self.Expected.FAILURE, { "RUSTC": self.nonexecutable }) + self.assertTrue( + # `dash`. + f"Running '{self.nonexecutable}' to check the Rust compiler version failed with" in result.stderr or + # `bash`. + f"Rust compiler '{self.nonexecutable}' could not be found." in result.stderr, + f"Unexpected `stderr`:\n{result.stderr}" + ) + + def test_rustc_unexpected_binary(self): + result = self.run_script(self.Expected.FAILURE, { "RUSTC": self.unexpected_binary }) + self.assertIn(f"Running '{self.unexpected_binary}' to check the Rust compiler version did not return", result.stderr) + + def test_rustc_unexpected_name(self): + rustc = self.generate_rustc(f"unexpected {self.rustc_default_version} (a8314ef7d 2022-06-27)") + result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc }) + self.assertIn(f"Running '{rustc}' to check the Rust compiler version did not return", result.stderr) + + def test_rustc_unexpected_version(self): + rustc = self.generate_rustc("rustc unexpected (a8314ef7d 2022-06-27)") + result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc }) + self.assertIn(f"Running '{rustc}' to check the Rust compiler version did not return", result.stderr) + + def test_rustc_no_minor(self): + rustc = self.generate_rustc(f"rustc {'.'.join(self.rustc_default_version.split('.')[:2])} (a8314ef7d 2022-06-27)") + result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc }) + self.assertIn(f"Running '{rustc}' to check the Rust compiler version did not return", result.stderr) + + def test_rustc_old_version(self): + rustc = self.generate_rustc("rustc 1.60.0 (a8314ef7d 2022-06-27)") + result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc }) + self.assertIn(f"Rust compiler '{rustc}' is too old.", result.stderr) + + def test_bindgen_nonexecutable(self): + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": self.nonexecutable }) + self.assertTrue( + # `dash`. + f"Running '{self.nonexecutable}' to check the Rust bindings generator version failed with" in result.stderr or + # `bash`. + f"Rust bindings generator '{self.nonexecutable}' could not be found." in result.stderr, + f"Unexpected `stderr`:\n{result.stderr}" + ) + + def test_bindgen_unexpected_binary(self): + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": self.unexpected_binary }) + self.assertIn(f"Running '{self.unexpected_binary}' to check the bindings generator version did not return", result.stderr) + + def test_bindgen_unexpected_name(self): + bindgen = self.generate_bindgen_version(f"unexpected {self.bindgen_default_version}") + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) + self.assertIn(f"Running '{bindgen}' to check the bindings generator version did not return", result.stderr) + + def test_bindgen_unexpected_version(self): + bindgen = self.generate_bindgen_version("bindgen unexpected") + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) + self.assertIn(f"Running '{bindgen}' to check the bindings generator version did not return", result.stderr) + + def test_bindgen_no_minor(self): + bindgen = self.generate_bindgen_version(f"bindgen {'.'.join(self.bindgen_default_version.split('.')[:2])}") + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) + self.assertIn(f"Running '{bindgen}' to check the bindings generator version did not return", result.stderr) + + def test_bindgen_old_version(self): + bindgen = self.generate_bindgen_version("bindgen 0.50.0") + result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) + self.assertIn(f"Rust bindings generator '{bindgen}' is too old.", result.stderr) + + def test_bindgen_libclang_failure(self): + 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") + 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("bindings generator) did not return an expected output. See output", result.stderr) + + def test_bindgen_libclang_old_version(self): + bindgen = self.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version 10.0.0 [-W#pragma-messages], err: false") + 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_22(self): + for (bindgen_version, libclang_version, expected_not_patched) in ( + ("0.71.1", "21.1.0", self.Expected.SUCCESS), + ("0.71.1", "22.0.0", self.Expected.SUCCESS_WITH_WARNINGS), + ("0.71.1", "22.1.0", self.Expected.SUCCESS_WITH_WARNINGS), + + ("0.72.0", "22.0.0", self.Expected.SUCCESS_WITH_WARNINGS), + + ("0.72.1", "22.0.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.72.1 together with libclang >= 22", result.stderr) + + bindgen = self.generate_bindgen(f"bindgen {bindgen_version}", libclang_stderr, libclang_22_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 }) + self.assertIn("version does not match Clang's. This may be a problem.", result.stderr) + + def test_clang_matches_bindgen_libclang_different_clang(self): + cc = self.generate_clang("clang version 999.0.0") + result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "CC": cc }) + self.assertIn("version does not match Clang's. This may be a problem.", result.stderr) + + def test_rustc_src_core_krustflags(self): + result = self.run_script(self.Expected.FAILURE, { "PATH": os.environ["PATH"], "RUSTC": "rustc", "KRUSTFLAGS": f"--sysroot={self.missing}" }) + self.assertIn("Source code for the 'core' standard library could not be found", result.stderr) + + def test_rustc_src_core_rustlibsrc(self): + result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing }) + self.assertIn("Source code for the 'core' standard library could not be found", result.stderr) + + def test_success_cc_unknown(self): + result = self.run_script(self.Expected.SUCCESS_WITH_EXTRA_OUTPUT, { "CC": self.missing }) + self.assertIn("unknown C compiler", result.stderr) + + def test_success_cc_multiple_arguments_ccache(self): + clang = self.generate_clang(f"""Ubuntu clang version {self.llvm_default_version}-1ubuntu1 +Target: x86_64-pc-linux-gnu +Thread model: posix +InstalledDir: /usr/bin +""") + result = self.run_script(self.Expected.SUCCESS, { "CC": f"{clang} clang" }) + + def test_success_rustc_version(self): + for rustc_stdout in ( + f"rustc {self.rustc_default_version} (a8314ef7d 2022-06-27)", + f"rustc {self.rustc_default_version}-dev (a8314ef7d 2022-06-27)", + f"rustc {self.rustc_default_version}-1.60.0 (a8314ef7d 2022-06-27)", + ): + with self.subTest(rustc_stdout=rustc_stdout): + rustc = self.generate_rustc(rustc_stdout) + result = self.run_script(self.Expected.SUCCESS, { "RUSTC": rustc }) + + def test_success_bindgen_version(self): + for bindgen_stdout in ( + f"bindgen {self.bindgen_default_version}", + f"bindgen {self.bindgen_default_version}-dev", + f"bindgen {self.bindgen_default_version}-0.999.0", + ): + with self.subTest(bindgen_stdout=bindgen_stdout): + bindgen = self.generate_bindgen_version(bindgen_stdout) + result = self.run_script(self.Expected.SUCCESS, { "BINDGEN": bindgen }) + + def test_success_bindgen_libclang(self): + for stderr in ( + f"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (https://github.com/llvm/llvm-project.git 4a2c05b05ed07f1f620e94f6524a8b4b2760a0b1) [-W#pragma-messages], err: false", + f"/home/jd/Documents/dev/kernel-module-flake/linux-6.1/outputs/dev/lib/modules/6.1.0-development/source/scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} [-W#pragma-messages], err: false", + f"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (Fedora 13.0.0-3.fc35) [-W#pragma-messages], err: false", + f""" +/nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c) +scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} [-W#pragma-messages], err: false +""", + f""" +/nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1.0-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c) +/home/jd/Documents/dev/kernel-module-flake/linux-6.1/outputs/dev/lib/modules/6.1.0-development/source/scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (Fedora 13.0.0-3.fc35) [-W#pragma-messages], err: false +""" + ): + with self.subTest(stderr=stderr): + bindgen = self.generate_bindgen_libclang(stderr) + result = self.run_script(self.Expected.SUCCESS, { "BINDGEN": bindgen }) + + def test_success_clang_version(self): + for clang_stdout in ( + f"clang version {self.llvm_default_version} (https://github.com/llvm/llvm-project.git 4a2c05b05ed07f1f620e94f6524a8b4b2760a0b1)", + f"clang version {self.llvm_default_version}-dev", + f"clang version {self.llvm_default_version}-2~ubuntu20.04.1", + f"Ubuntu clang version {self.llvm_default_version}-2~ubuntu20.04.1", + ): + with self.subTest(clang_stdout=clang_stdout): + clang = self.generate_clang(clang_stdout) + result = self.run_script(self.Expected.SUCCESS, { "CC": clang }) + + def test_success_real_programs(self): + for cc in ["gcc", "clang"]: + with self.subTest(cc=cc): + result = self.run_script(self.Expected.SUCCESS, { + "PATH": os.environ["PATH"], + "RUSTC": "rustc", + "BINDGEN": "bindgen", + "CC": cc, + }) + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/rustc-llvm-version.sh b/scripts/rustc-llvm-version.sh new file mode 100755 index 000000000..a500d1ae3 --- /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 000000000..4e22593e2 --- /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 new file mode 100644 index 000000000..df864437c --- /dev/null +++ b/scripts/rustdoc_test_builder.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Test builder for `rustdoc`-generated tests. +//! +//! This script is a hack to extract the test from `rustdoc`'s output. Ideally, `rustdoc` would +//! have an option to generate this information instead, e.g. as JSON output. +//! +//! The `rustdoc`-generated test names look like `{file}_{line}_{number}`, e.g. +//! `...path_rust_kernel_sync_arc_rs_42_0`. `number` is the "test number", needed in cases like +//! a macro that expands into items with doctests is invoked several times within the same line. +//! +//! However, since these names are used for bisection in CI, the line number makes it not stable +//! at all. In the future, we would like `rustdoc` to give us the Rust item path associated with +//! the test, plus a "test number" (for cases with several examples per item) and generate a name +//! from that. For the moment, we generate ourselves a new name, `{file}_{number}` instead, in +//! the `gen` script (done there since we need to be aware of all the tests in a given file). + +use std::io::Read; + +fn main() { + let mut stdin = std::io::stdin().lock(); + let mut body = String::new(); + stdin.read_to_string(&mut body).unwrap(); + + // Find the generated function name looking for the inner function inside `main()`. + // + // The line we are looking for looks like one of the following: + // + // ``` + // 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> { + // ``` + // + // It should be unlikely that doctest code matches such lines (when code is formatted properly). + let rustdoc_function_name = body + .lines() + .find_map(|line| { + Some( + line.split_once("fn main() {")? + .1 + .split_once("fn ")? + .1 + .split_once("()")? + .0, + ) + .filter(|x| x.chars().all(|c| c.is_alphanumeric() || c == '_')) + }) + .expect("No test function found in `rustdoc`'s output."); + + // Replicate `rustdoc` 1.87+ behaviour [1] by fully qualifying `Result` to avoid the collision + // with our own `Result` coming from the prelude. + // + // [1]: https://github.com/rust-lang/rust/pull/137807 + // + // TODO: Remove this when MSRV is bumped above 1.87. + let body = body.replace( + &format!("{rustdoc_function_name}() -> Result<(), impl core::fmt::Debug> {{"), + // This intentionally does not use absolute paths to match `rustdoc` 1.87 behaviour. + &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 + // the return value to check there were no returned errors. Instead, we use our assert macro + // since we want to just fail the test, not panic the kernel. + // + // We save the result in a variable so that the failed assertion message looks nicer. + let body = body.replace( + &format!("}} {rustdoc_function_name}().unwrap() }}"), + &format!("}} let test_return_value = {rustdoc_function_name}(); assert!(test_return_value.is_ok()); }}"), + ); + + // Figure out a smaller test name based on the generated function name. + let name = rustdoc_function_name.split_once("_rust_kernel_").unwrap().1; + + let path = format!("rust/test/doctests/kernel/{name}"); + + std::fs::write(path, body.as_bytes()).unwrap(); +} diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs new file mode 100644 index 000000000..d087c0d9f --- /dev/null +++ b/scripts/rustdoc_test_gen.rs @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Generates KUnit tests from saved `rustdoc`-generated tests. +//! +//! KUnit passes a context (`struct kunit *`) to each test, which should be forwarded to the other +//! KUnit functions and macros. +//! +//! However, we want to keep this as an implementation detail because: +//! +//! - Test code should not care about the implementation. +//! +//! - Documentation looks worse if it needs to carry extra details unrelated to the piece +//! being described. +//! +//! - 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` 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`). +//! +//! Note that this means other threads/tasks potentially spawned by a given test, if failing, will +//! report the failure in the kernel log but will not fail the actual test. Saving the pointer in +//! e.g. a `static` per test does not fully solve the issue either, because currently KUnit does +//! not support assertions (only expectations) from other tasks. Thus leave that feature for +//! the future, which simplifies the code here too. We could also simply not allow `assert`s in +//! other tasks, but that seems overly constraining, and we do want to support them, eventually. + +use std::{ + fs, + fs::File, + io::{ + BufWriter, + Read, + Write, // + }, + path::{ + Path, + PathBuf, // + }, // +}; + +/// Find the real path to the original file based on the `file` portion of the test name. +/// +/// `rustdoc` generated `file`s look like `sync_locked_by_rs`. Underscores (except the last one) +/// may represent an actual underscore in a directory/file, or a path separator. Thus the actual +/// file might be `sync_locked_by.rs`, `sync/locked_by.rs`, `sync_locked/by.rs` or +/// `sync/locked/by.rs`. This function walks the file system to determine which is the real one. +/// +/// This does require that ambiguities do not exist, but that seems fair, especially since this is +/// all supposed to be temporary until `rustdoc` gives us proper metadata to build this. If such +/// ambiguities are detected, they are diagnosed and the script panics. +fn find_real_path<'a>(srctree: &Path, valid_paths: &'a mut Vec<PathBuf>, file: &str) -> &'a str { + valid_paths.clear(); + + let potential_components: Vec<&str> = file.strip_suffix("_rs").unwrap().split('_').collect(); + + find_candidates(srctree, valid_paths, Path::new(""), &potential_components); + fn find_candidates( + srctree: &Path, + valid_paths: &mut Vec<PathBuf>, + prefix: &Path, + potential_components: &[&str], + ) { + // The base case: check whether all the potential components left, joined by underscores, + // is a file. + let joined_potential_components = potential_components.join("_") + ".rs"; + if srctree + .join("rust/kernel") + .join(prefix) + .join(&joined_potential_components) + .is_file() + { + // Avoid `srctree` here in order to keep paths relative to it in the KTAP output. + valid_paths.push( + Path::new("rust/kernel") + .join(prefix) + .join(joined_potential_components), + ); + } + + // In addition, check whether each component prefix, joined by underscores, is a directory. + // If not, there is no need to check for combinations with that prefix. + for i in 1..potential_components.len() { + let (components_prefix, components_rest) = potential_components.split_at(i); + let prefix = prefix.join(components_prefix.join("_")); + if srctree.join("rust/kernel").join(&prefix).is_dir() { + find_candidates(srctree, valid_paths, &prefix, components_rest); + } + } + } + + match valid_paths.as_slice() { + [] => panic!( + "No path candidates found for `{file}`. This is likely a bug in the build system, or \ + some files went away while compiling." + ), + [valid_path] => valid_path.to_str().unwrap(), + valid_paths => { + use std::fmt::Write; + + let mut candidates = String::new(); + for path in valid_paths { + writeln!(&mut candidates, " {path:?}").unwrap(); + } + panic!( + "Several path candidates found for `{file}`, please resolve the ambiguity by \ + renaming a file or folder. Candidates:\n{candidates}", + ); + } + } +} + +fn main() { + let srctree = std::env::var("srctree").unwrap(); + let srctree = Path::new(&srctree); + + let mut paths = fs::read_dir("rust/test/doctests/kernel") + .unwrap() + .map(|entry| entry.unwrap().path()) + .collect::<Vec<_>>(); + + // Sort paths. + paths.sort(); + + let mut rust_tests = String::new(); + let mut c_test_declarations = String::new(); + let mut c_test_cases = String::new(); + let mut body = String::new(); + let mut last_file = String::new(); + let mut number = 0; + let mut valid_paths: Vec<PathBuf> = Vec::new(); + let mut real_path: &str = ""; + for path in paths { + // The `name` follows the `{file}_{line}_{number}` pattern (see description in + // `scripts/rustdoc_test_builder.rs`). Discard the `number`. + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + + // Extract the `file` and the `line`, discarding the `number`. + let (file, line) = name.rsplit_once('_').unwrap().0.rsplit_once('_').unwrap(); + + // Generate an ID sequence ("test number") for each one in the file. + if file == last_file { + number += 1; + } else { + number = 0; + last_file = file.to_string(); + + // Figure out the real path, only once per file. + real_path = find_real_path(srctree, &mut valid_paths, file); + } + + // Generate a KUnit name (i.e. test name and C symbol) for this test. + // + // We avoid the line number, like `rustdoc` does, to make things slightly more stable for + // bisection purposes. However, to aid developers in mapping back what test failed, we will + // print a diagnostics line in the KTAP report. + let kunit_name = format!("rust_doctest_kernel_{file}_{number}"); + + // Read the test's text contents to dump it below. + body.clear(); + File::open(path).unwrap().read_to_string(&mut body).unwrap(); + + // Calculate how many lines before `main` function (including the `main` function line). + let body_offset = body + .lines() + .take_while(|line| !line.contains("fn main() {")) + .count() + + 1; + + use std::fmt::Write; + write!( + 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) {{ + /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. + #[allow(unused)] + macro_rules! assert {{ + ($cond:expr $(,)?) => {{{{ + ::kernel::kunit_assert!( + "{kunit_name}", c"{real_path}", __DOCTEST_ANCHOR - {line}, $cond + ); + }}}} + }} + + /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead. + #[allow(unused)] + macro_rules! assert_eq {{ + ($left:expr, $right:expr $(,)?) => {{{{ + ::kernel::kunit_assert_eq!( + "{kunit_name}", c"{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right + ); + }}}} + }} + + // Many tests need the prelude, so provide it by default. + #[allow(unused)] + 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. + // + // This information is also printed when assertions fail, but this helps in the successful cases + // when the user is running KUnit manually, or when passing `--raw_output` to `kunit.py`. + // + // 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(fmt!(" # {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} + 2; + {{ + #![allow(unreachable_pub, clippy::disallowed_names)] + {body} + main(); + }} +}} + +"# + ) + .unwrap(); + + write!(c_test_declarations, "void {kunit_name}(struct kunit *);\n").unwrap(); + write!(c_test_cases, " KUNIT_CASE({kunit_name}),\n").unwrap(); + } + + let rust_tests = rust_tests.trim(); + let c_test_declarations = c_test_declarations.trim(); + let c_test_cases = c_test_cases.trim(); + + write!( + BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()), + r#"//! `kernel` crate documentation tests. + +const __LOG_PREFIX: &[u8] = b"rust_doctests_kernel\0"; + +/// Dummy module type for doctest context. +struct LocalModule; + +use kernel::{{ + str::CStr, + ModuleMetadata, + ThisModule, // +}}; +use core::ptr::null_mut; + +impl ModuleMetadata for LocalModule {{ + const NAME: &'static CStr = c"rust_doctests_kernel"; + const THIS_MODULE: ThisModule = {{ + // SAFETY: `try_module_get`/`module_put` handle null module pointers gracefully. + unsafe {{ ThisModule::from_ptr(null_mut()) }} + }}; +}} + +{rust_tests} +"# + ) + .unwrap(); + + write!( + BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()), + r#"/* + * `kernel` crate documentation tests. + */ + +#include <kunit/test.h> + +{c_test_declarations} + +static struct kunit_case test_cases[] = {{ + {c_test_cases} + {{ }} +}}; + +static struct kunit_suite test_suite = {{ + .name = "rust_doctests_kernel", + .test_cases = test_cases, +}}; + +kunit_test_suite(test_suite); + +MODULE_LICENSE("GPL"); +"# + ) + .unwrap(); +} diff --git a/scripts/sbom/sbom.py b/scripts/sbom/sbom.py new file mode 100644 index 000000000..764175b9c --- /dev/null +++ b/scripts/sbom/sbom.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +""" +Compute software bill of materials in SPDX format describing a kernel build. +""" + +import json +import logging +import os +import sys +import time +import uuid +import sbom.sbom_logging as sbom_logging +from sbom.config import get_config +from sbom.path_utils import is_relative_to +from sbom.spdx import JsonLdSpdxDocument, SpdxIdGenerator +from sbom.spdx.core import CreationInfo, SpdxDocument +from sbom.spdx_graph import SpdxIdGeneratorCollection, build_spdx_graphs +from sbom.cmd_graph import CmdGraph + + +def _exit_with_summary(write_output_on_error: bool = False) -> None: + warning_summary = sbom_logging.summarize_warnings() + error_summary = sbom_logging.summarize_errors() + if warning_summary: + logging.warning(warning_summary) + if error_summary: + logging.error(error_summary) + if not write_output_on_error: + logging.info( + "Use --write-output-on-error to generate output documents even when errors occur. " + "Note that in this case the generated documents may be incomplete." + ) + sys.exit(1) + + +def main(): + # Read config + config = get_config() + + # Configure logging + logging.basicConfig( + level=logging.DEBUG if config.debug else logging.INFO, + format="[%(levelname)s] %(message)s", + ) + + # Build cmd graph + logging.debug("Start building cmd graph") + start_time = time.time() + cmd_graph = CmdGraph.create(config.root_paths, config) + logging.debug(f"Built cmd graph in {time.time() - start_time} seconds") + + # Save used files document + if config.generate_used_files: + if config.src_tree == config.obj_tree: + logging.info( + f"Extracting all files from the cmd graph to {config.used_files_file_name} " + "instead of only source files because source files cannot be " + "reliably classified when the source and object trees are identical.", + ) + used_files = [os.path.relpath(node.absolute_path, config.src_tree) for node in cmd_graph] + logging.debug(f"Found {len(used_files)} files in cmd graph.") + else: + used_files = [ + os.path.relpath(node.absolute_path, config.src_tree) + for node in cmd_graph + if is_relative_to(node.absolute_path, config.src_tree) + and not is_relative_to(node.absolute_path, config.obj_tree) + ] + logging.debug(f"Found {len(used_files)} source files in cmd graph") + if not sbom_logging.has_errors() or config.write_output_on_error: + used_files_path = os.path.join(config.output_directory, config.used_files_file_name) + with open(used_files_path, "w", encoding="utf-8") as f: + f.write("\n".join(str(file_path) for file_path in used_files)) + logging.debug(f"Successfully saved {used_files_path}") + + if config.generate_spdx is False: + _exit_with_summary(config.write_output_on_error) + return + + # Build SPDX Documents + logging.debug("Start generating SPDX graph based on cmd graph") + start_time = time.time() + + # The real uuid will be generated based on the content of the SPDX graphs + # to ensure that the same SPDX document is always assigned the same uuid. + PLACEHOLDER_UUID = "00000000-0000-0000-0000-000000000000" + spdx_id_base_namespace = f"{config.spdxId_prefix}{PLACEHOLDER_UUID}/" + spdx_id_generators = SpdxIdGeneratorCollection( + base=SpdxIdGenerator(prefix="p", namespace=spdx_id_base_namespace), + source=SpdxIdGenerator(prefix="s", namespace=f"{spdx_id_base_namespace}source/"), + build=SpdxIdGenerator(prefix="b", namespace=f"{spdx_id_base_namespace}build/"), + output=SpdxIdGenerator(prefix="o", namespace=f"{spdx_id_base_namespace}output/"), + ) + + spdx_graphs = build_spdx_graphs( + cmd_graph, + spdx_id_generators, + config, + ) + spdx_id_uuid = uuid.uuid5( + uuid.NAMESPACE_URL, + "".join( + json.dumps(element.to_dict()) for spdx_graph in spdx_graphs.values() for element in spdx_graph.to_list() + ), + ) + logging.debug(f"Generated SPDX graph in {time.time() - start_time} seconds") + + if not sbom_logging.has_errors() or config.write_output_on_error: + for kernel_sbom_kind, spdx_graph in spdx_graphs.items(): + spdx_graph_objects = spdx_graph.to_list() + # Add warning and error summary to creation info comment + creation_info = next(element for element in spdx_graph_objects if isinstance(element, CreationInfo)) + creation_info.comment = "\n".join([ + sbom_logging.summarize_warnings(), + sbom_logging.summarize_errors(), + ]).strip() + # Replace Placeholder uuid with real uuid for spdxIds + spdx_document = next(element for element in spdx_graph_objects if isinstance(element, SpdxDocument)) + for namespaceMap in spdx_document.namespaceMap: + namespaceMap.namespace = namespaceMap.namespace.replace(PLACEHOLDER_UUID, str(spdx_id_uuid)) + # Serialize SPDX graph to JSON-LD + spdx_doc = JsonLdSpdxDocument(graph=spdx_graph_objects) + save_path = os.path.join(config.output_directory, config.spdx_file_names[kernel_sbom_kind]) + spdx_doc.save(save_path, config.prettify_json) + logging.debug(f"Successfully saved {save_path}") + + _exit_with_summary(config.write_output_on_error) + + +# Call main method +if __name__ == "__main__": + main() diff --git a/scripts/sbom/sbom/__init__.py b/scripts/sbom/sbom/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/sbom/sbom/cmd_graph/__init__.py b/scripts/sbom/sbom/cmd_graph/__init__.py new file mode 100644 index 000000000..9d661a5c3 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/__init__.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from .cmd_graph import CmdGraph +from .cmd_graph_node import CmdGraphNode, CmdGraphNodeConfig + +__all__ = ["CmdGraph", "CmdGraphNode", "CmdGraphNodeConfig"] diff --git a/scripts/sbom/sbom/cmd_graph/cmd_file.py b/scripts/sbom/sbom/cmd_graph/cmd_file.py new file mode 100644 index 000000000..dcd63e284 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/cmd_file.py @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import os +import re +from dataclasses import dataclass, field +from sbom.cmd_graph.deps_parser import parse_cmd_file_deps +from sbom.cmd_graph.savedcmd_parser import parse_inputs_from_commands +import sbom.sbom_logging as sbom_logging +from sbom.path_utils import PathStr + +SAVEDCMD_PATTERN = re.compile(r"^(saved)?cmd_.*?:=\s*(?P<full_command>.+)$") +SOURCE_PATTERN = re.compile(r"^source.*?:=\s*(?P<source_file>.+)$") + + +@dataclass +class CmdFile: + cmd_file_path: PathStr + savedcmd: str + source: PathStr | None = None + deps: list[str] = field(default_factory=list) + make_rules: list[str] = field(default_factory=list) + + @classmethod + def create(cls, cmd_file_path: PathStr) -> "CmdFile | None": + """ + Parses a .cmd file. + .cmd files are assumed to have one of the following structures: + 1. Full Cmd File + (saved)?cmd_<output> := <command> + source_<output> := <main_input> + deps_<output> := \ + <dependencies> + <output> := $(deps_<output>) + $(deps_<output>): + + 2. Command Only Cmd File + (saved)?cmd_<output> := <command> + + 3. Single Dependency Cmd File + (saved)?cmd_<output> := <command> + <output> : <dependency> + + Args: + cmd_file_path (Path): absolute Path to a .cmd file + + Returns: + cmd_file (CmdFile): Parsed cmd file. + """ + with open(cmd_file_path, "rt", encoding="utf-8") as f: + lines = [line.strip() for line in f.readlines() if line.strip() != "" and not line.startswith("#")] + + # savedcmd + match = SAVEDCMD_PATTERN.match(lines[0] if lines else "") + if match is None: + sbom_logging.error( + "Skip parsing '{cmd_file_path}' because no 'savedcmd_' command was found.", cmd_file_path=cmd_file_path + ) + return None + savedcmd = match.group("full_command") + + # Command Only Cmd File + if len(lines) == 1: + return CmdFile(cmd_file_path, savedcmd) + + # Single Dependency Cmd File + if len(lines) == 2: + parts = lines[1].split(":", 1) + if len(parts) != 2: + sbom_logging.error( + "Skip parsing '{cmd_file_path}'. Expected dependency line '<output>: <dependency>' but got {second_line}", cmd_file_path=cmd_file_path, second_line=lines[1] + ) + return None + dep = parts[1].strip() + return CmdFile(cmd_file_path, savedcmd, deps=[dep]) + + # Full Cmd File + # source + line1 = SOURCE_PATTERN.match(lines[1]) + if line1 is None: + sbom_logging.error( + "Skip parsing '{cmd_file_path}' because no 'source_' entry was found.", cmd_file_path=cmd_file_path + ) + return CmdFile(cmd_file_path, savedcmd) + source = line1.group("source_file") + + # deps + deps: list[str] = [] + i = 3 # lines[2] includes the variable assignment but no actual dependency, so we need to start at lines[3]. + while i < len(lines): + if not lines[i].endswith("\\"): + break + deps.append(lines[i][:-1].strip()) + i += 1 + + # make_rules + make_rules = lines[i:] + + return CmdFile(cmd_file_path, savedcmd, source, deps, make_rules) + + def get_dependencies( + self: "CmdFile", target_path: PathStr, obj_tree: PathStr, fail_on_unknown_build_command: bool + ) -> list[PathStr]: + """ + Parses all dependencies required to build a target file from its cmd file. + + Args: + target_path: path to the target file relative to `obj_tree`. + obj_tree: absolute path to the object tree. + fail_on_unknown_build_command: Whether to fail if an unknown build command is encountered. + + Returns: + list[PathStr]: dependency file paths relative to `obj_tree`. + """ + input_files: list[PathStr] = [ + str(p) for p in parse_inputs_from_commands(self.savedcmd, fail_on_unknown_build_command) + ] + if self.deps: + input_files += [str(p) for p in parse_cmd_file_deps(self.deps)] + input_files = _expand_resolve_files(input_files, obj_tree) + + cmd_file_dependencies: list[PathStr] = [] + for input_file in input_files: + # input files are either absolute or relative to the object tree + if os.path.isabs(input_file): + input_file = os.path.relpath(input_file, obj_tree) + if input_file == target_path: + # Skip target file to prevent cycles. This is necessary because some multi stage commands first create an output and then pass it as input to the next command, e.g., objcopy. + continue + cmd_file_dependencies.append(input_file) + unique_cmd_file_dependencies = list(dict.fromkeys(cmd_file_dependencies)) + return unique_cmd_file_dependencies + + +def _expand_resolve_files(input_files: list[PathStr], obj_tree: PathStr) -> list[PathStr]: + """ + Expands resolve files which may reference additional files via '@' notation. + + Args: + input_files (list[PathStr]): List of file paths relative to the object tree, where paths starting with '@' refer to files + containing further file paths, each on a separate line. + obj_tree: Absolute path to the root of the object tree. + + Returns: + list[PathStr]: Flattened list of all input file paths, with any nested '@' file references resolved recursively. + """ + expanded_input_files: list[PathStr] = [] + for input_file in input_files: + if not input_file.startswith("@"): + expanded_input_files.append(input_file) + continue + resolve_file_path = os.path.join(obj_tree, input_file.removeprefix("@")) + if not os.path.exists(resolve_file_path): + sbom_logging.error( + "Skip resolving '{resolve_file_path}' because the response file does not exist.", + resolve_file_path=resolve_file_path, + ) + continue + with open(resolve_file_path, "rt", encoding="utf-8") as f: + resolve_file_content = [line_stripped for line in f.readlines() if (line_stripped := line.strip())] + expanded_input_files += _expand_resolve_files(resolve_file_content, obj_tree) + return expanded_input_files diff --git a/scripts/sbom/sbom/cmd_graph/cmd_graph.py b/scripts/sbom/sbom/cmd_graph/cmd_graph.py new file mode 100644 index 000000000..2f5796523 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/cmd_graph.py @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from collections import deque +from dataclasses import dataclass, field +from typing import Iterator + +from sbom.cmd_graph.cmd_graph_node import CmdGraphNode, CmdGraphNodeConfig +from sbom.path_utils import PathStr + + +@dataclass +class CmdGraph: + """Directed acyclic graph of build dependencies primarily inferred from .cmd files produced during kernel builds""" + + roots: list[CmdGraphNode] = field(default_factory=list) + + @classmethod + def create(cls, root_paths: list[PathStr], config: CmdGraphNodeConfig) -> "CmdGraph": + """ + Recursively builds a dependency graph starting from `root_paths`. + Dependencies are mainly discovered by parsing the `.cmd` files. + + Args: + root_paths (list[PathStr]): List of paths to root outputs relative to obj_tree + config (CmdGraphNodeConfig): Configuration options + + Returns: + CmdGraph: A graph of all build dependencies for the given root files. + """ + node_cache: dict[PathStr, CmdGraphNode] = {} + root_nodes = [CmdGraphNode.create(root_path, config, node_cache) for root_path in root_paths] + return CmdGraph(root_nodes) + + def __iter__(self) -> Iterator[CmdGraphNode]: + """Traverse the graph in breadth-first order, yielding each unique node.""" + visited: set[PathStr] = set() + node_stack: deque[CmdGraphNode] = deque(self.roots) + while len(node_stack) > 0: + node = node_stack.popleft() + if node.absolute_path in visited: + continue + + visited.add(node.absolute_path) + node_stack.extend(node.children) + yield node diff --git a/scripts/sbom/sbom/cmd_graph/cmd_graph_node.py b/scripts/sbom/sbom/cmd_graph/cmd_graph_node.py new file mode 100644 index 000000000..61f3a8140 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/cmd_graph_node.py @@ -0,0 +1,142 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass, field +from itertools import chain +import logging +import os +from typing import Iterator, Protocol + +from sbom import sbom_logging +from sbom.cmd_graph.cmd_file import CmdFile +from sbom.cmd_graph.hardcoded_dependencies import get_hardcoded_dependencies +from sbom.cmd_graph.incbin_parser import parse_incbin_statements +from sbom.path_utils import PathStr, has_link, is_relative_to + + +@dataclass +class IncbinDependency: + node: "CmdGraphNode" + full_statement: str + + +class CmdGraphNodeConfig(Protocol): + obj_tree: PathStr + src_tree: PathStr + fail_on_unknown_build_command: bool + + +@dataclass +class CmdGraphNode: + """A node in the cmd graph representing a single file and its dependencies.""" + + absolute_path: PathStr + """Absolute path to the file this node represents.""" + + cmd_file: CmdFile | None = None + """Parsed .cmd file describing how the file at absolute_path was built, or None if not available.""" + + cmd_file_dependencies: list["CmdGraphNode"] = field(default_factory=list) + incbin_dependencies: list[IncbinDependency] = field(default_factory=list) + hardcoded_dependencies: list["CmdGraphNode"] = field(default_factory=list) + + @property + def children(self) -> Iterator["CmdGraphNode"]: + seen: set[PathStr] = set() + for node in chain( + self.cmd_file_dependencies, + (dep.node for dep in self.incbin_dependencies), + self.hardcoded_dependencies, + ): + if node.absolute_path not in seen: + seen.add(node.absolute_path) + yield node + + @classmethod + def create( + cls, + target_path: PathStr, + config: CmdGraphNodeConfig, + cache: dict[PathStr, "CmdGraphNode"] | None = None, + depth: int = 0, + ) -> "CmdGraphNode": + """ + Recursively builds a dependency graph starting from `target_path`. + Dependencies are mainly discovered by parsing the `.<target_path.name>.cmd` file. + + Args: + target_path: Path to the target file relative to obj_tree. + config: Config options + cache: Tracks processed nodes to prevent cycles. + depth: Internal parameter to track the current recursion depth. + + Returns: + CmdGraphNode: cmd graph node representing the target file + """ + if cache is None: + cache = {} + + target_path_absolute = ( + os.path.realpath(p) + if has_link(p:=os.path.join(config.obj_tree, target_path)) + else os.path.normpath(p) + ) + + if target_path_absolute in cache: + return cache[target_path_absolute] + + if depth == 0: + logging.debug(f"Build node: {target_path}") + + cmd_file_path = _to_cmd_path(target_path_absolute) + cmd_file = CmdFile.create(cmd_file_path) if os.path.exists(cmd_file_path) else None + node = CmdGraphNode(target_path_absolute, cmd_file) + cache[target_path_absolute] = node + + if not os.path.exists(target_path_absolute): + error_or_warning = ( + sbom_logging.error + if is_relative_to(target_path_absolute, config.obj_tree) + or is_relative_to(target_path_absolute, config.src_tree) + else sbom_logging.warning + ) + error_or_warning( + "Skip parsing '{target_path_absolute}' because file does not exist", + target_path_absolute=target_path_absolute, + ) + return node + + # Search for dependencies to add to the graph as child nodes. Child paths are always relative to the output tree. + def _build_child_node(child_path: PathStr) -> "CmdGraphNode": + return CmdGraphNode.create(child_path, config, cache, depth + 1) + + node.hardcoded_dependencies = [ + _build_child_node(hardcoded_dependency_path) + for hardcoded_dependency_path in get_hardcoded_dependencies( + target_path_absolute, config.obj_tree, config.src_tree + ) + ] + + if cmd_file is not None: + node.cmd_file_dependencies = [ + _build_child_node(cmd_file_dependency_path) + for cmd_file_dependency_path in cmd_file.get_dependencies( + target_path, config.obj_tree, config.fail_on_unknown_build_command + ) + ] + + if node.absolute_path.endswith(".S"): + node.incbin_dependencies = [ + IncbinDependency( + node=_build_child_node(incbin_statement.path), + full_statement=incbin_statement.full_statement, + ) + for incbin_statement in parse_incbin_statements(node.absolute_path) + ] + + return node + + +def _to_cmd_path(path: PathStr) -> PathStr: + name = os.path.basename(path) + return path.removesuffix(name) + f".{name}.cmd" diff --git a/scripts/sbom/sbom/cmd_graph/deps_parser.py b/scripts/sbom/sbom/cmd_graph/deps_parser.py new file mode 100644 index 000000000..6a2d92f07 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/deps_parser.py @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import re +import sbom.sbom_logging as sbom_logging +from sbom.path_utils import PathStr + +# Match dependencies on config files +# Example match: "$(wildcard include/config/CONFIG_SOMETHING)" +CONFIG_PATTERN = re.compile(r"\$\(wildcard (include/config/[^)]+)\)") + +# Match dependencies on the objtool binary +# Example match: "$(wildcard ./tools/objtool/objtool)" +OBJTOOL_PATTERN = re.compile(r"\$\(wildcard \./tools/objtool/objtool\)") + +# Match any Makefile wildcard reference +# Example match: "$(wildcard path/to/file)" +WILDCARD_PATTERN = re.compile(r"\$\(wildcard (?P<path>[^)]+)\)") + +# Match ordinary paths: +# - ^(\/)?: Optionally starts with a '/' +# - (([\w\-\.,+~=@ ]*)\/)*: Zero or more directory levels +# - [\w\-\.,+~=@ ]+$: Path component (file or directory) +# Example matches: "/foo/bar.c", "dir1/dir2/file.txt", "plainfile" +VALID_PATH_PATTERN = re.compile(r"^(\/)?(([\w\-\.,+~=@ ]*)\/)*[\w\-\.,+~=@ ]+$") + + +def parse_cmd_file_deps(deps: list[str]) -> list[PathStr]: + """ + Parse dependency strings of a .cmd file and return valid input file paths. + + Args: + deps: List of dependency strings as found in `.cmd` files. + + Returns: + input_files: List of input file paths + """ + input_files: list[PathStr] = [] + for dep in deps: + dep = dep.strip() + match dep: + case _ if CONFIG_PATTERN.match(dep) or OBJTOOL_PATTERN.match(dep): + # config paths like include/config/<CONFIG_NAME> should not be included in the graph + continue + case _ if match := WILDCARD_PATTERN.match(dep): + path = match.group("path") + input_files.append(path) + case _ if VALID_PATH_PATTERN.match(dep): + input_files.append(dep) + case _: + sbom_logging.error("Skip parsing dependency {dep} because of unrecognized format", dep=dep) + return input_files diff --git a/scripts/sbom/sbom/cmd_graph/hardcoded_dependencies.py b/scripts/sbom/sbom/cmd_graph/hardcoded_dependencies.py new file mode 100644 index 000000000..2eb04d30f --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/hardcoded_dependencies.py @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import os +from typing import Callable +import sbom.sbom_logging as sbom_logging +from sbom.path_utils import PathStr, is_relative_to +from sbom.environment import Environment + +HARDCODED_DEPENDENCIES: dict[str, list[str]] = { + # defined in linux/Kbuild + "include/generated/rq-offsets.h": ["kernel/sched/rq-offsets.s"], + "kernel/sched/rq-offsets.s": ["include/generated/asm-offsets.h"], + "include/generated/bounds.h": ["kernel/bounds.s"], + "include/generated/asm-offsets.h": ["arch/{arch}/kernel/asm-offsets.s"], +} +""" +Maps file paths to the list of dependencies required to build them +which are not tracked by the .cmd dependency mechanism. +Paths are relative to either the source tree or the object tree. +""" + +def get_hardcoded_dependencies(path: PathStr, obj_tree: PathStr, src_tree: PathStr) -> list[PathStr]: + """ + Some files in the kernel build process are not tracked by the .cmd dependency mechanism. + Parsing these dependencies programmatically is too complex for the scope of this project. + Therefore, this function provides manually defined dependencies to be added to the build graph. + + Args: + path: absolute path to a file within the src tree or object tree. + obj_tree: absolute Path to the base directory of the object tree. + src_tree: absolute Path to the `linux` source directory. + + Returns: + list[PathStr]: A list of dependency file paths (relative to the object tree) required to build the file at the given path. + """ + if is_relative_to(path, obj_tree): + path = os.path.relpath(path, obj_tree) + elif is_relative_to(path, src_tree): + path = os.path.relpath(path, src_tree) + + if path not in HARDCODED_DEPENDENCIES: + return [] + + template_variables: dict[str, Callable[[], str | None]] = { + "arch": lambda: _get_arch(path), + } + + dependencies: list[PathStr] = [] + for dependency_template in HARDCODED_DEPENDENCIES[path]: + dependency = _evaluate_template(dependency_template, template_variables) + if dependency is None: + continue + if os.path.exists(os.path.join(obj_tree, dependency)): + dependencies.append(dependency) + elif os.path.exists(dependency_absolute := os.path.join(src_tree, dependency)): + dependencies.append(os.path.relpath(dependency_absolute, obj_tree)) + else: + sbom_logging.error( + "Skip hardcoded dependency '{dependency}' for '{path}' because the dependency lies neither in the src tree nor the object tree.", + dependency=dependency, + path=path, + ) + + return dependencies + + +def _evaluate_template(template: str, variables: dict[str, Callable[[], str | None]]) -> str | None: + for key, value_function in variables.items(): + template_key = "{" + key + "}" + if template_key in template: + value = value_function() + if value is None: + return None + template = template.replace(template_key, value) + return template + + +def _get_arch(path: PathStr): + srcarch = Environment.SRCARCH() + if srcarch is None: + sbom_logging.error( + "Skipped architecture specific hardcoded dependency for '{path}' because the SRCARCH environment variable was not set.", + path=path, + ) + return None + return srcarch diff --git a/scripts/sbom/sbom/cmd_graph/incbin_parser.py b/scripts/sbom/sbom/cmd_graph/incbin_parser.py new file mode 100644 index 000000000..ca289c2b8 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/incbin_parser.py @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass +import re + +from sbom.path_utils import PathStr + +INCBIN_PATTERN = re.compile(r'\s*\.incbin\s+"(?P<path>[^"]+)"') +"""Regex pattern for matching `.incbin "<path>"` statements.""" + + +@dataclass +class IncbinStatement: + """A parsed `.incbin "<path>"` directive.""" + + path: PathStr + """path to the file referenced by the `.incbin` directive.""" + + full_statement: str + """Full `.incbin "<path>"` statement as it originally appeared in the file.""" + + +def parse_incbin_statements(absolute_path: PathStr) -> list[IncbinStatement]: + """ + Parses `.incbin` directives from an `.S` assembly file. + + Args: + absolute_path: Absolute path to the `.S` assembly file. + + Returns: + list[IncbinStatement]: Parsed `.incbin` statements. + """ + with open(absolute_path, "rt", encoding="utf-8") as f: + content = f.read() + return [ + IncbinStatement( + path=match.group("path"), + full_statement=match.group(0).strip(), + ) + for match in INCBIN_PATTERN.finditer(content) + ] diff --git a/scripts/sbom/sbom/cmd_graph/savedcmd_parser/__init__.py b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/__init__.py new file mode 100644 index 000000000..d13876af4 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/__init__.py @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from sbom.cmd_graph.savedcmd_parser.savedcmd_parser import parse_inputs_from_commands + +__all__ = ["parse_inputs_from_commands"] diff --git a/scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_parser_registry.py b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_parser_registry.py new file mode 100644 index 000000000..a48040b2c --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_parser_registry.py @@ -0,0 +1,516 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import re +import shlex +from typing import Callable, Iterator + +import sbom.sbom_logging as sbom_logging +from sbom.environment import Environment +from sbom.cmd_graph.savedcmd_parser.command_splitter import IfBlock, split_commands +from sbom.cmd_graph.savedcmd_parser.tokenizer import ( + CmdParsingError, + Option, + Positional, + tokenize_single_command, + tokenize_single_command_positionals_only, +) +from sbom.path_utils import PathStr + +CommandParser = Callable[[str], list[PathStr]] +CommandParserRegistryEntry = tuple[re.Pattern[str], CommandParser] + + +def _parse_dd_command(command: str) -> list[PathStr]: + match = re.match(r"dd.*?if=(\S+)", command) + if match: + return [match.group(1)] + return [] + + +def _parse_cat_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["cat", input1, input2, ...] + return [p for p in positionals[1:]] + + +def _parse_compound_command(command: str) -> list[PathStr]: + compound_command_parsers: list[CommandParserRegistryEntry] = [ + (re.compile(r"dd\b"), _parse_dd_command), + (re.compile(r"cat.*?\|"), lambda c: _parse_cat_command(c.split("|")[0])), + (re.compile(r"cat\b[^|>]*$"), _parse_cat_command), + (re.compile(r"echo\b"), _parse_noop), + (re.compile(r"\S+="), _parse_noop), + (re.compile(r"printf\b"), _parse_noop), + (re.compile(r"sed\b"), _parse_sed_command), + ( + re.compile(r"(.*/)scripts/bin2c\s*<"), + lambda c: [input] if (input := c.split("<")[1].split(">")[0].strip()) != "/dev/null" else [], + ), + (re.compile(r"^:$"), _parse_noop), + ] + + match = re.match(r"\s*[\(\{](.*)[\)\}]\s*>", command, re.DOTALL) + if match is None: + raise CmdParsingError("No inner commands found for compound command") + input_files: list[PathStr] = [] + inner_commands = split_commands(match.group(1)) + for inner_command in inner_commands: + if isinstance(inner_command, IfBlock): + sbom_logging.error( + "Skip parsing inner command {inner_command} of compound command because IfBlock is not supported", + inner_command=inner_command, + ) + continue + + parser = next((parser for pattern, parser in compound_command_parsers if pattern.match(inner_command)), None) + if parser is None: + sbom_logging.error( + "Skip parsing inner command {inner_command} of compound command because no matching parser was found", + inner_command=inner_command, + ) + continue + try: + input_files += parser(inner_command) + except (CmdParsingError, IndexError) as e: + sbom_logging.error( + "Skip parsing inner command {inner_command} of compound command because of command parsing error: {error_message}", + inner_command=inner_command, + error_message=str(e), + ) + return input_files + + +def _parse_objcopy_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command, flag_options=["-S", "-w"]) + positionals = [part.value for part in command_parts if isinstance(part, Positional)] + # expect positionals to be ['objcopy', input_file] or ['objcopy', input_file, output_file] + return [positionals[1]] + + +def _parse_link_vmlinux_command(command: str) -> list[PathStr]: + """ + For simplicity we do not parse the `scripts/link-vmlinux.sh` script. + Instead the `vmlinux.a` dependency is just hardcoded for now. + """ + return ["vmlinux.a"] + + +def _parse_cp_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["cp", input1, ..., destination] + return positionals[1:-1] + + +def _parse_noop(command: str) -> list[PathStr]: + """ + No-op parser for commands with no input files (e.g., 'rm', 'true'). + Returns an empty list. + """ + return [] + + +def _parse_ar_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ['ar', flags, output, input1, input2, ...] + flags = positionals[1] + if "r" not in flags: + # 'r' option indicates that new files are added to the archive. + # If this option is missing we won't find any relevant input files. + return [] + return positionals[3:] + + +def _parse_ar_piped_xargs_command(command: str) -> list[PathStr]: + printf_command, _ = command.split("|", 1) + positionals = tokenize_single_command_positionals_only(printf_command.strip()) + # expect positionals to be ['printf', '{prefix_path}%s ', input1, input2, ...] + prefix_path = positionals[1].removesuffix("%s ") + return [f"{prefix_path}{filename}" for filename in positionals[2:]] + + +def _parse_gcc_or_clang_command(command: str) -> list[PathStr]: + parts = shlex.split(command) + # compile mode: expect last positional argument ending in a source file extension to be the input file + for part in reversed(parts): + if not part.startswith("-") and any(part.endswith(suffix) for suffix in [".c", ".S", ".dts"]): + return [part] + + # linking mode: expect all .o files to be the inputs + return [p for p in parts if p.endswith(".o")] + + +def _parse_rustc_command(command: str) -> list[PathStr]: + parts = shlex.split(command) + # expect last positional argument ending in `.rs` to be the input file + for part in reversed(parts): + if not part.startswith("-") and part.endswith(".rs"): + return [part] + raise CmdParsingError("Could not find .rs input source file") + + +def _parse_rustdoc_command(command: str) -> list[PathStr]: + parts = shlex.split(command) + # expect last positional argument ending in `.rs` to be the input file + for part in reversed(parts): + if not part.startswith("-") and part.endswith(".rs"): + return [part] + raise CmdParsingError("Could not find .rs input source file") + + +def _parse_syscallhdr_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command.strip(), flag_options=["--emit-nr"]) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["sh", path/to/syscallhdr.sh, input, output] + return [positionals[2]] + + +def _parse_syscalltbl_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command.strip()) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["sh", path/to/syscalltbl.sh, input, output] + return [positionals[2]] + + +def _parse_mkcapflags_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["sh", path/to/mkcapflags.sh, output, input1, input2] + return [positionals[3], positionals[4]] + + +def _parse_orc_hash_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["sh", path/to/orc_hash.sh, '<', input, '>', output] + return [positionals[3]] + + +def _parse_xen_hypercalls_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["sh", path/to/xen-hypercalls.sh, output, input1, input2, ...] + return positionals[3:] + + +def _parse_gen_initramfs_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["sh", path/to/gen_initramfs.sh, input1, input2, ...] + return positionals[2:] + + +def _parse_vdso2c_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ['vdso2c', raw_input, stripped_input, output] + return [positionals[1], positionals[2]] + + +def _parse_vdsomunge_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ['vdsomunge', input, output] + return [positionals[1]] + + +def _parse_ld_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command( + command=command.strip(), + flag_options=[ + "-shared", + "--no-undefined", + "--eh-frame-hdr", + "-Bsymbolic", + "-r", + "--no-ld-generated-unwind-info", + "--no-dynamic-linker", + "-pie", + "--no-dynamic-linker--whole-archive", + "--whole-archive", + "--no-whole-archive", + "--start-group", + "--end-group", + ], + ) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["ld", input1, input2, ...] + return positionals[1:] + + +def _parse_sed_command(command: str) -> list[PathStr]: + command_parts = shlex.split(command) + # expect command parts to be ["sed", *, input] + input = command_parts[-1] + if input == "/dev/null": + return [] + return [input] + + +def _parse_awk(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command) + options = [p for p in command_parts if isinstance(p, Option)] + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + has_script_file = any(p.name == "-f" for p in options) + # With -f option: expect ["awk", input1, input2, ...] + # Without -f option: expect ["awk", inline_program, input1, input2, ...] + return positionals[1:] if has_script_file else positionals[2:] + + +def _parse_nm_piped_command(command: str) -> list[PathStr]: + nm_command, _ = command.split("|", 1) + command_parts = tokenize_single_command( + command=nm_command.strip(), + flag_options=["-p", "--defined-only"], + ) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["nm", input1, input2, ...] + return [p for p in positionals[1:]] + + +def _parse_pnm_to_logo_command(command: str) -> list[PathStr]: + command_parts = shlex.split(command) + # expect command parts to be ["pnmtologo", <options>, input] + return [command_parts[-1]] + + +def _parse_relacheck(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["relacheck", input, log_reference] + return [positionals[1]] + + +def _parse_gen_hyprel_command(command: str) -> list[PathStr]: + gen_hyprel_command, _ = command.split(">", 1) + command_parts = shlex.split(gen_hyprel_command) + # expect command_parts to be ["gen-hyprel", input] + return [command_parts[1]] + + +def _parse_perl_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command.strip()) + # expect positionals to be ["perl", input] + return [positionals[1]] + + +def _parse_strip_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command, flag_options=["--strip-debug"]) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["strip", input1, input2, ...] + return positionals[1:] + + +def _parse_mkpiggy_command(command: str) -> list[PathStr]: + mkpiggy_command, _ = command.split(">", 1) + positionals = tokenize_single_command_positionals_only(mkpiggy_command) + # expect positionals to be ["mkpiggy", input] + return [positionals[1]] + + +def _parse_relocs_command(command: str) -> list[PathStr]: + if ">" not in command: + # Only consider relocs commands that redirect output to a file. + # If there's no redirection, we assume it produces no output file and therefore has no input we care about. + return [] + relocs_command, _ = command.split(">", 1) + command_parts = shlex.split(relocs_command) + # expect command_parts to be ["relocs", options, input] + return [command_parts[-1]] + + +def _parse_mk_elfconfig_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["mk_elfconfig", "<", input, ">", output] + return [positionals[2]] + + +def _parse_flex_command(command: str) -> list[PathStr]: + parts = shlex.split(command) + # expect last positional argument ending in `.l` to be the input file + for part in reversed(parts): + if not part.startswith("-") and part.endswith(".l"): + return [part] + raise CmdParsingError("Could not find .l input source file in command") + + +def _parse_bison_command(command: str) -> list[PathStr]: + parts = shlex.split(command) + # expect last positional argument ending in `.y` to be the input file + for part in reversed(parts): + if not part.startswith("-") and part.endswith(".y"): + return [part] + raise CmdParsingError("Could not find input .y input source file in command") + + +def _parse_tools_build_command(command: str) -> list[PathStr]: + positionals = tokenize_single_command_positionals_only(command) + # expect positionals to be ["tools/build", "input1", "input2", "input3", "output"] + return positionals[1:-1] + + +def _parse_extract_cert_command(command: str) -> list[PathStr]: + command_parts = shlex.split(command) + # expect command parts to be [path/to/extract-cert, input, output] + input = command_parts[1] + if not input: + return [] + return [input] + + +def _parse_dtc_command(command: str) -> list[PathStr]: + wno_flags = [command_part for command_part in shlex.split(command) if command_part.startswith("-Wno-")] + command_parts = tokenize_single_command(command, flag_options=wno_flags) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be [path/to/dtc, input] + return [positionals[1]] + + +def _parse_bindgen_command(command: str) -> list[PathStr]: + command_parts = shlex.split(command) + header_file_input_paths = [part for part in command_parts if part.endswith(".h")] + return header_file_input_paths + + +def _parse_gen_header(command: str) -> list[PathStr]: + command_parts = shlex.split(command) + # expect command parts to be ["python3", path/to/gen_headers.py, ..., "--xml", input] + i = next((i for i, token in enumerate(command_parts) if token == "--xml"), None) + if i is None: + raise CmdParsingError(f"Expected --xml input file in gen_headers command but got {command}") + return [command_parts[i + 1]] + +def _parse_mkuboot_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command) + # mkuboot.sh passes all args to mkimage; -d specifies the data/input image file + for part in command_parts: + if isinstance(part, Option) and part.name == "-d" and part.value is not None: + return [part.value] + raise CmdParsingError("Could not find -d (data file) option in mkuboot.sh command") + + +def _parse_syscallnr_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command.strip()) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["sh", path/to/syscallnr.sh, input, output] + return [positionals[2]] + + +def _parse_gen_kernel_hwcaps_command(command: str) -> list[PathStr]: + command_parts = tokenize_single_command(command.strip(), flag_options=["-e"]) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + # expect positionals to be ["sh", path/to/gen-kernel-hwcaps.sh, input] + return [positionals[2]] + + +class CommandParserRegistry: + """ + Registry mapping command patterns to their input-file parsers. + """ + + def __init__(self, entries: list[CommandParserRegistryEntry]) -> None: + self._entries = entries + + def __iter__(self) -> Iterator[CommandParserRegistryEntry]: + return iter(self._entries) + + @staticmethod + def create() -> "CommandParserRegistry": + def env_or_default_pattern(env_value: str | None, default_pattern: str) -> str: + if env_value is None or not env_value.strip(): + return default_pattern + return rf"(?:{re.escape(env_value.strip())}|{default_pattern})" + + cc_pattern = env_or_default_pattern(Environment.CC(), r"([^\s]+-)?(gcc|clang)") + ld_pattern = env_or_default_pattern(Environment.LD(), r"([^\s]+-)?ld") + ar_pattern = env_or_default_pattern(Environment.AR(), r"([^\s]+-)?ar") + nm_pattern = env_or_default_pattern(Environment.NM(), r"([^\s]+-)?nm") + objcopy_pattern = env_or_default_pattern(Environment.OBJCOPY(), r"([^\s]+-)?objcopy") + strip_pattern = env_or_default_pattern(Environment.STRIP(), r"([^\s]+-)?strip") + + entries: list[CommandParserRegistryEntry] = [ + # Compound commands + (re.compile(r"\(.*?\)\s*>", re.DOTALL), _parse_compound_command), + (re.compile(r"\{.*?\}\s*>", re.DOTALL), _parse_compound_command), + # Standard Unix utilities and system tools + (re.compile(r"^rm\b"), _parse_noop), + (re.compile(r"^mkdir\b"), _parse_noop), + (re.compile(r"^touch\b"), _parse_noop), + (re.compile(r"^cp\b"), _parse_cp_command), + (re.compile(r"^truncate\b"), _parse_noop), + (re.compile(r"^cat\b.*?[\|>]"), lambda c: _parse_cat_command(c.split("|")[0].split(">")[0])), + (re.compile(r"^echo[^|]*$"), _parse_noop), + (re.compile(r"^sed.*?>"), lambda c: _parse_sed_command(c.split(">")[0])), + (re.compile(r"^sed\b"), _parse_noop), + (re.compile(r"^awk.*?<.*?>"), lambda c: [c.split("<")[1].split(">")[0]]), + (re.compile(r"^awk.*?>"), lambda c: _parse_awk(c.split(">")[0])), + (re.compile(r"^(/bin/)?true\b"), _parse_noop), + (re.compile(r"^(/bin/)?false\b"), _parse_noop), + (re.compile(r"^openssl\s+req.*?-new.*?-keyout"), _parse_noop), + # Compilers and code generators + # (C/LLVM toolchain, Rust, Flex/Bison, Bindgen, Perl, etc.) + ( + re.compile(rf"^{cc_pattern}\b"), + lambda command: _parse_gcc_or_clang_command(re.sub(rf"^{cc_pattern}\b", "gcc", command, count=1)), + ), + ( + re.compile(rf"^{ld_pattern}\b"), + lambda command: _parse_ld_command(re.sub(rf"^{ld_pattern}\b", "ld", command, count=1)), + ), + ( + re.compile(rf"^printf\b.*\| xargs {ar_pattern}\b"), + lambda command: _parse_ar_piped_xargs_command( + re.sub(rf"xargs {ar_pattern}\b", "xargs ar", command, count=1) + ), + ), + ( + re.compile(rf"^{ar_pattern}\b"), + lambda command: _parse_ar_command(re.sub(rf"^{ar_pattern}\b", "ar", command, count=1)), + ), + ( + re.compile(rf"^{nm_pattern}\b.*?\|"), + lambda command: _parse_nm_piped_command(re.sub(rf"^{nm_pattern}\b", "nm", command, count=1)), + ), + ( + re.compile(rf"^{objcopy_pattern}\b"), + lambda command: _parse_objcopy_command(re.sub(rf"^{objcopy_pattern}\b", "objcopy", command, count=1)), + ), + ( + re.compile(rf"^{strip_pattern}\b"), + lambda command: _parse_strip_command(re.sub(rf"^{strip_pattern}\b", "strip", command, count=1)), + ), + (re.compile(r".*?rustc\b"), _parse_rustc_command), + (re.compile(r".*?rustdoc\b"), _parse_rustdoc_command), + (re.compile(r"^flex\b"), _parse_flex_command), + (re.compile(r"^bison\b"), _parse_bison_command), + (re.compile(r"^bindgen\b"), _parse_bindgen_command), + (re.compile(r"^perl\b"), _parse_perl_command), + # Kernel-specific build scripts and tools + (re.compile(r"^(.*/)?link-vmlinux\.sh\b"), _parse_link_vmlinux_command), + (re.compile(r"sh (.*/)?syscallhdr\.sh\b"), _parse_syscallhdr_command), + (re.compile(r"sh (.*/)?syscalltbl\.sh\b"), _parse_syscalltbl_command), + (re.compile(r"sh (.*/)?mkcapflags\.sh\b"), _parse_mkcapflags_command), + (re.compile(r"sh (.*/)?orc_hash\.sh\b"), _parse_orc_hash_command), + (re.compile(r"sh (.*/)?xen-hypercalls\.sh\b"), _parse_xen_hypercalls_command), + (re.compile(r"sh (.*/)?gen_initramfs\.sh\b"), _parse_gen_initramfs_command), + (re.compile(r"sh (.*/)?checkundef\.sh\b"), _parse_noop), + (re.compile(r"(bash|sh) (.*/)?mkuboot\.sh\b"), _parse_mkuboot_command), + (re.compile(r"sh (.*/)?syscallnr\.sh\b"), _parse_syscallnr_command), + (re.compile(r"(/bin/)?sh (.*/)?gen-kernel-hwcaps\.sh\b"), lambda c: _parse_gen_kernel_hwcaps_command(c.split(">")[0])), + (re.compile(r"(.*/)?vdso2c\b"), _parse_vdso2c_command), + (re.compile(r"(.*/)?vdsomunge\b"), _parse_vdsomunge_command), + (re.compile(r"^(.*/)?mkpiggy.*?>"), _parse_mkpiggy_command), + (re.compile(r"^(.*/)?relocs\b"), _parse_relocs_command), + (re.compile(r"^(.*/)?mk_elfconfig.*?<.*?>"), _parse_mk_elfconfig_command), + (re.compile(r"^(.*/)?tools/build\b"), _parse_tools_build_command), + (re.compile(r"^(.*/)?certs/extract-cert"), _parse_extract_cert_command), + (re.compile(r"^(.*/)?scripts/dtc/dtc\b"), _parse_dtc_command), + (re.compile(r"^(.*/)?pnmtologo\b"), _parse_pnm_to_logo_command), + (re.compile(r"^(.*/)?kernel/pi/relacheck"), _parse_relacheck), + (re.compile(r"^(.*/)?gen-hyprel\b"), _parse_gen_hyprel_command), + (re.compile(r"^drivers/gpu/drm/radeon/mkregtable"), lambda c: [c.split(" ")[1]]), + (re.compile(r"(.*/)?genheaders\b"), _parse_noop), + (re.compile(r"^(.*/)?mkcpustr\s+>"), _parse_noop), + (re.compile(r"^(.*/)polgen\b"), _parse_noop), + (re.compile(r"make -f .*/arch/x86/Makefile\.postlink"), _parse_noop), + (re.compile(r"^(.*/)?raid6/mktables\s+>"), _parse_noop), + (re.compile(r"^(.*/)?objtool\b"), _parse_noop), + (re.compile(r"^(.*/)?module/gen_test_kallsyms.sh"), _parse_noop), + (re.compile(r"^(.*/)?gen_header.py"), _parse_gen_header), + (re.compile(r"^(.*/)?scripts/rustdoc_test_gen"), _parse_noop), + ] + return CommandParserRegistry(entries) diff --git a/scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_splitter.py b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_splitter.py new file mode 100644 index 000000000..4749f4bd6 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/command_splitter.py @@ -0,0 +1,128 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import re +from dataclasses import dataclass + + +# If Block pattern to match a simple, single-level if-then-fi block. Nested If blocks are not supported. +IF_BLOCK_PATTERN = re.compile( + r""" + ^if(.*?);\s* # Match 'if <condition>;' (non-greedy) + then(.*?);\s* # Match 'then <body>;' (non-greedy) + fi\b # Match 'fi' + """, + re.VERBOSE, +) + + +@dataclass +class IfBlock: + condition: str + then_statement: str + + +def _unwrap_outer_parentheses(s: str) -> str: + s = s.strip() + if not (s.startswith("(") and s.endswith(")")): + return s + + count = 0 + for i, char in enumerate(s): + if char == "(": + count += 1 + elif char == ")": + count -= 1 + # If count is 0 before the end, outer parentheses don't match + if count == 0 and i != len(s) - 1: + return s + + # outer parentheses do match, unwrap once + return _unwrap_outer_parentheses(s[1:-1]) + + +def _find_first_top_level_command_separator( + commands: str, separators: list[str] = [";", "&&"] +) -> tuple[int | None, int | None]: + def is_escaped(index: int) -> bool: + preceding = commands[:index] + return (len(preceding) - len(preceding.rstrip("\\"))) % 2 == 1 + + in_single_quote = False + in_double_quote = False + in_curly_braces = 0 + in_braces = 0 + for i, char in enumerate(commands): + if char == "'" and not in_double_quote and not is_escaped(i): + # Toggle single quote state (unless inside double quotes or escaped) + in_single_quote = not in_single_quote + elif char == '"' and not in_single_quote and not is_escaped(i): + # Toggle double quote state (unless inside single quotes or escaped) + in_double_quote = not in_double_quote + + if in_single_quote or in_double_quote: + continue + + # Toggle braces state + if char == "{": + in_curly_braces += 1 + if char == "}": + in_curly_braces -= 1 + + if char == "(": + in_braces += 1 + if char == ")": + in_braces -= 1 + + if in_curly_braces > 0 or in_braces > 0: + continue + + # return found separator position and separator length + for separator in separators: + if commands[i : i + len(separator)] == separator: + return i, len(separator) + + return None, None + + +def split_commands(commands: str) -> list[str | IfBlock]: + """ + Splits a string of command-line commands into individual parts. + + This function handles: + - Top-level command separators (e.g., `;` and `&&`) to split multiple commands. + - Conditional if-blocks, returning them as `IfBlock` instances. + - Preserves the order of commands and trims whitespace. + + Args: + commands (str): The raw command string. + + Returns: + list[str | IfBlock]: A list of single commands or `IfBlock` objects. + """ + single_commands: list[str | IfBlock] = [] + remaining_commands = _unwrap_outer_parentheses(commands) + while len(remaining_commands) > 0: + remaining_commands = remaining_commands.strip() + + # if block + matched_if = IF_BLOCK_PATTERN.match(remaining_commands) + if matched_if: + condition, then_statement = matched_if.groups() + single_commands.append(IfBlock(condition.strip(), then_statement.strip())) + full_matched = matched_if.group(0) + remaining_commands = remaining_commands.removeprefix(full_matched).lstrip("; \n") + continue + + # command until next separator + separator_position, separator_length = _find_first_top_level_command_separator(remaining_commands) + if separator_position is not None and separator_length is not None: + single_commands.append(remaining_commands[:separator_position].strip()) + remaining_commands = remaining_commands[separator_position + separator_length :].strip() + continue + + # single last command + single_commands.append(remaining_commands) + break + + return single_commands diff --git a/scripts/sbom/sbom/cmd_graph/savedcmd_parser/savedcmd_parser.py b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/savedcmd_parser.py new file mode 100644 index 000000000..d2ca842a7 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/savedcmd_parser.py @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import sbom.sbom_logging as sbom_logging +from sbom.cmd_graph.savedcmd_parser.command_splitter import IfBlock, split_commands +from sbom.cmd_graph.savedcmd_parser.command_parser_registry import CommandParserRegistry +from sbom.cmd_graph.savedcmd_parser.tokenizer import CmdParsingError +from sbom.path_utils import PathStr + +DEFAULT_COMMAND_PARSER_REGISTRY = CommandParserRegistry.create() + + +def parse_inputs_from_commands( + commands: str, + fail_on_unknown_build_command: bool, + registry: CommandParserRegistry | None = None, +) -> list[PathStr]: + """ + Extract input files referenced in a set of command-line commands. + + Args: + commands (str): Command line expression to parse. + fail_on_unknown_build_command (bool): Whether to fail if an unknown build command is encountered. If False, errors are logged as warnings. + registry (CommandParserRegistry | None): Registry of single command parsers. + + Returns: + list[PathStr]: List of input file paths required by the commands. + """ + + def log_error_or_warning(message: str, /, **kwargs: str) -> None: + if fail_on_unknown_build_command: + sbom_logging.error(message, **kwargs) + else: + sbom_logging.warning(message, **kwargs) + + if registry is None: + registry = DEFAULT_COMMAND_PARSER_REGISTRY + + input_files: list[PathStr] = [] + for single_command in split_commands(commands): + if isinstance(single_command, IfBlock): + inputs = parse_inputs_from_commands(single_command.then_statement, fail_on_unknown_build_command, registry) + if inputs: + log_error_or_warning( + "Skipped parsing command {then_statement} because input files in IfBlock 'then' statement are not supported", + then_statement=single_command.then_statement, + ) + continue + + matched_parser = next((parser for pattern, parser in registry if pattern.match(single_command)), None) + if matched_parser is None: + log_error_or_warning( + "Skipped parsing command {single_command} because no matching parser was found", + single_command=single_command, + ) + continue + try: + inputs = matched_parser(single_command) + input_files.extend(inputs) + except (CmdParsingError, IndexError, ValueError) as e: + log_error_or_warning( + "Skipped parsing command {single_command} because of command parsing error: {error_message}", + single_command=single_command, + error_message=str(e), + ) + + return [input.strip().rstrip("/") for input in input_files] diff --git a/scripts/sbom/sbom/cmd_graph/savedcmd_parser/tokenizer.py b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/tokenizer.py new file mode 100644 index 000000000..1bf081f40 --- /dev/null +++ b/scripts/sbom/sbom/cmd_graph/savedcmd_parser/tokenizer.py @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import re +import shlex +from dataclasses import dataclass +from typing import Union + + +class CmdParsingError(Exception): + pass + + +@dataclass +class Option: + name: str + value: str | None = None + + +@dataclass +class Positional: + value: str + + +_SUBCOMMAND_PATTERN = re.compile(r"\$\$\(([^()]*)\)") +"""Pattern to match $$(...) blocks""" + + +def tokenize_single_command(command: str, flag_options: list[str] | None = None) -> list[Union[Option, Positional]]: + """ + Parse a shell command into a list of Options and Positionals. + - Positional: the command and any positional arguments. + - Options: handles flags and options with values provided as space-separated, or equals-sign + (e.g., '--opt val', '--opt=val', '--flag'). + + Args: + command: Command line string. + flag_options: Options that are flags without values (e.g., '--verbose'). + + Returns: + List of `Option` and `Positional` objects in command order. + """ + + # Wrap all $$(...) blocks in double quotes to prevent shlex from splitting them. + command_with_protected_subcommands = _SUBCOMMAND_PATTERN.sub(lambda m: f'"$$({m.group(1)})"', command) + tokens = shlex.split(command_with_protected_subcommands) + + parsed: list[Option | Positional] = [] + i = 0 + while i < len(tokens): + token = tokens[i] + + # Positional + if not token.startswith("-"): + parsed.append(Positional(token)) + i += 1 + continue + + # Option without value (--flag) + if (token.startswith("-") and i + 1 < len(tokens) and tokens[i + 1].startswith("-")) or ( + flag_options and token in flag_options + ): + parsed.append(Option(name=token)) + i += 1 + continue + + # Option with equals sign (--opt=val) + if "=" in token: + name, value = token.split("=", 1) + parsed.append(Option(name=name, value=value)) + i += 1 + continue + + # Option with space-separated value (--opt val) + if i + 1 < len(tokens) and not tokens[i + 1].startswith("-"): + parsed.append(Option(name=token, value=tokens[i + 1])) + i += 2 + continue + + raise CmdParsingError(f"Unrecognized token: {token} in command {command}") + + return parsed + + +def tokenize_single_command_positionals_only(command: str) -> list[str]: + command_parts = tokenize_single_command(command) + positionals = [p.value for p in command_parts if isinstance(p, Positional)] + if len(positionals) != len(command_parts): + raise CmdParsingError( + f"Invalid command format: expected positional arguments only but got options in command {command}." + ) + return positionals diff --git a/scripts/sbom/sbom/config.py b/scripts/sbom/sbom/config.py new file mode 100644 index 000000000..6811f7829 --- /dev/null +++ b/scripts/sbom/sbom/config.py @@ -0,0 +1,320 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import argparse +from dataclasses import dataclass +from datetime import datetime, timezone +from enum import Enum +import os +from typing import Any +from sbom.path_utils import PathStr + + +class KernelSpdxDocumentKind(Enum): + SOURCE = "source" + BUILD = "build" + OUTPUT = "output" + + +@dataclass +class KernelSbomConfig: + src_tree: PathStr + """Absolute path to the Linux kernel source directory.""" + + obj_tree: PathStr + """Absolute path to the build output directory.""" + + root_paths: list[PathStr] + """List of paths to root outputs (relative to obj_tree) to base the SBOM on.""" + + generate_spdx: bool + """Whether to generate SPDX SBOM documents. If False, no SPDX files are created.""" + + spdx_file_names: dict[KernelSpdxDocumentKind, str] + """If `generate_spdx` is True, defines the file names for each SPDX SBOM kind + (source, build, output) to store on disk.""" + + generate_used_files: bool + """Whether to generate a flat list of all source files used in the build. + If False, no used-files document is created.""" + + used_files_file_name: str + """If `generate_used_files` is True, specifies the file name for the used-files document.""" + + output_directory: PathStr + """Path to the directory where the generated output documents will be saved.""" + + debug: bool + """Whether to enable debug logging.""" + + fail_on_unknown_build_command: bool + """Whether to fail if an unknown build command is encountered in a .cmd file.""" + + write_output_on_error: bool + """Whether to write output documents even if errors occur.""" + + created: datetime + """Datetime to use for the SPDX created property of the CreationInfo element.""" + + spdxId_prefix: str + """Prefix to use for all SPDX element IDs.""" + + build_type: str + """SPDX buildType property to use for all Build elements.""" + + build_id: str | None + """SPDX buildId property to use for all Build elements.""" + + package_license: str + """License expression applied to all SPDX Packages.""" + + package_version: str | None + """Version string applied to all SPDX Packages.""" + + package_copyright_text: str | None + """Copyright text applied to all SPDX Packages.""" + + prettify_json: bool + """Whether to pretty-print generated SPDX JSON documents.""" + + +def _parse_cli_arguments(parser: argparse.ArgumentParser) -> dict[str, Any]: + """ + Parse command-line arguments using argparse. + + Returns: + Dictionary of parsed arguments. + """ + parser.add_argument( + "--src-tree", + default="../linux", + help="Path to the kernel source tree (default: ../linux)", + ) + parser.add_argument( + "--obj-tree", + default="../linux/kernel_build", + help="Path to the build output directory (default: ../linux/kernel_build)", + ) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument( + "--roots", + nargs="+", + help="Space-separated list of paths relative to obj-tree for which the SBOM will be created.\n" + "Cannot be used together with --roots-file.", + ) + group.add_argument( + "--roots-file", + help="Path to a file containing the root paths (one per line). Cannot be used together with --roots.", + ) + parser.add_argument( + "--generate-spdx", + action="store_true", + default=False, + help=( + "Whether to create sbom-source.spdx.json, sbom-build.spdx.json and " + "sbom-output.spdx.json documents (default: False)" + ), + ) + parser.add_argument( + "--generate-used-files", + action="store_true", + default=False, + help=( + "Whether to create the sbom.used-files.txt file, a flat list of all " + "source files used for the kernel build.\n" + "If src-tree and obj-tree are equal it is not possible to reliably " + "classify source files.\n" + "In this case sbom.used-files.txt will contain all files used for the " + "kernel build including all build artifacts. (default: False)" + ), + ) + parser.add_argument( + "--output-directory", + default=".", + help="Path to the directory where the generated output documents will be stored (default: .)", + ) + parser.add_argument( + "--debug", + action="store_true", + default=False, + help="Enable debug logs (default: False)", + ) + + # Error handling settings + parser.add_argument( + "--do-not-fail-on-unknown-build-command", + action="store_true", + default=False, + help=( + "Whether to fail if an unknown build command is encountered in a .cmd file.\n" + "If set to True, errors are logged as warnings instead. (default: False)" + ), + ) + parser.add_argument( + "--write-output-on-error", + action="store_true", + default=False, + help=( + "Write output documents even if errors occur. The resulting documents " + "may be incomplete.\n" + "A summary of warnings and errors can be found in the 'comment' property " + "of the CreationInfo element. (default: False)" + ), + ) + + # SPDX specific options + spdx_group = parser.add_argument_group("SPDX options", "Options for customizing SPDX document generation") + spdx_group.add_argument( + "--spdxId-prefix", + default="urn:spdx.dev:", + help="The prefix to use for all spdxId properties. (default: urn:spdx.dev:)", + ) + spdx_group.add_argument( + "--build-type", + default="urn:spdx.dev:Kbuild", + help="The SPDX buildType property to use for all Build elements. (default: urn:spdx.dev:Kbuild)", + ) + spdx_group.add_argument( + "--build-id", + default=None, + help="The SPDX buildId property to use for all Build elements.\n" + "If not provided the spdxId of the high level Build element is used as the buildId. (default: None)", + ) + spdx_group.add_argument( + "--package-license", + default="NOASSERTION", + help=( + "The SPDX licenseExpression property to use for the LicenseExpression " + "linked to all SPDX Package elements. (default: NOASSERTION)" + ), + ) + spdx_group.add_argument( + "--package-version", + default=None, + help="The SPDX packageVersion property to use for all SPDX Package elements. (default: None)", + ) + spdx_group.add_argument( + "--package-copyright-text", + default=None, + help=( + "The SPDX copyrightText property to use for all SPDX Package elements.\n" + "If not specified, and if a COPYING file exists in the source tree,\n" + "the package-copyright-text is set to the content of this file. " + "(default: None)" + ), + ) + spdx_group.add_argument( + "--prettify-json", + action="store_true", + default=False, + help="Whether to pretty print the generated spdx.json documents (default: False)", + ) + + args = vars(parser.parse_args()) + return args + + +def get_config() -> KernelSbomConfig: + """ + Parse command-line arguments and construct the configuration object. + + Returns: + KernelSbomConfig: Configuration object with all settings for SBOM generation. + """ + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter, + description="Generate SPDX SBOM documents for kernel builds", + ) + args = _parse_cli_arguments(parser) + + # Extract and validate cli arguments + src_tree = os.path.realpath(args["src_tree"]) + obj_tree = os.path.realpath(args["obj_tree"]) + root_paths = [] + if args["roots_file"]: + with open(args["roots_file"], "rt", encoding="utf-8") as f: + root_paths = [root.strip() for root in f.readlines()] + if len(root_paths) == 0: + parser.error("--roots-file must contain at least one path") + else: + root_paths = args["roots"] + _validate_path_arguments(parser, src_tree, obj_tree, root_paths) + + generate_spdx = args["generate_spdx"] + generate_used_files = args["generate_used_files"] + output_directory = os.path.realpath(args["output_directory"]) + debug = args["debug"] + + fail_on_unknown_build_command = not args["do_not_fail_on_unknown_build_command"] + write_output_on_error = args["write_output_on_error"] + + created = datetime.fromtimestamp( + max([os.path.getmtime(os.path.join(obj_tree, root_path)) for root_path in root_paths]), + tz=timezone.utc, + ) + spdxId_prefix = args["spdxId_prefix"] + build_type = args["build_type"] + build_id = args["build_id"] + package_license = args["package_license"] + package_version = args["package_version"] if args["package_version"] is not None else None + package_copyright_text: str | None = None + if args["package_copyright_text"] is not None: + package_copyright_text = args["package_copyright_text"] + elif os.path.isfile(copying_path := os.path.join(src_tree, "COPYING")): + with open(copying_path, "r", encoding="utf-8") as f: + package_copyright_text = f.read() + prettify_json = args["prettify_json"] + + # Hardcoded config + spdx_file_names = { + KernelSpdxDocumentKind.SOURCE: "sbom-source.spdx.json", + KernelSpdxDocumentKind.BUILD: "sbom-build.spdx.json", + KernelSpdxDocumentKind.OUTPUT: "sbom-output.spdx.json", + } + used_files_file_name = "sbom.used-files.txt" + + return KernelSbomConfig( + src_tree=src_tree, + obj_tree=obj_tree, + root_paths=root_paths, + generate_spdx=generate_spdx, + spdx_file_names=spdx_file_names, + generate_used_files=generate_used_files, + used_files_file_name=used_files_file_name, + output_directory=output_directory, + debug=debug, + fail_on_unknown_build_command=fail_on_unknown_build_command, + write_output_on_error=write_output_on_error, + created=created, + spdxId_prefix=spdxId_prefix, + build_type=build_type, + build_id=build_id, + package_license=package_license, + package_version=package_version, + package_copyright_text=package_copyright_text, + prettify_json=prettify_json, + ) + + +def _validate_path_arguments( + parser: argparse.ArgumentParser, + src_tree: PathStr, + obj_tree: PathStr, + root_paths: list[PathStr], +) -> None: + """ + Validate that the provided paths exist. + + Args: + parser: The argument parser, used to emit well-formatted error messages. + src_tree: Absolute path to the source tree. + obj_tree: Absolute path to the object tree. + root_paths: List of root paths relative to obj_tree. + """ + if not os.path.exists(src_tree): + parser.error(f"--src-tree {src_tree} does not exist") + if not os.path.exists(obj_tree): + parser.error(f"--obj-tree {obj_tree} does not exist") + for root_path in root_paths: + if not os.path.isfile(root_path_absolute := os.path.join(obj_tree, root_path)): + parser.error(f"path to root artifact {root_path_absolute} is not a file") diff --git a/scripts/sbom/sbom/environment.py b/scripts/sbom/sbom/environment.py new file mode 100644 index 000000000..4304066fe --- /dev/null +++ b/scripts/sbom/sbom/environment.py @@ -0,0 +1,192 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import os + +KERNEL_BUILD_VARIABLES_ALLOWLIST = [ + "AFLAGS_KERNEL", + "AFLAGS_MODULE", + "AR", + "ARCH", + "ARCH_CORE", + "ARCH_DRIVERS", + "ARCH_LIB", + "AWK", + "BASH", + "BINDGEN", + "BITS", + "CC", + "CC_FLAGS_FPU", + "CC_FLAGS_NO_FPU", + "CFLAGS_GCOV", + "CFLAGS_KERNEL", + "CFLAGS_MODULE", + "CHECK", + "CHECKFLAGS", + "CLIPPY_CONF_DIR", + "CONFIG_SHELL", + "CPP", + "CROSS_COMPILE", + "CURDIR", + "GNUMAKEFLAGS", + "HOSTCC", + "HOSTCXX", + "HOSTPKG_CONFIG", + "HOSTRUSTC", + "INSTALLKERNEL", + "INSTALL_DTBS_PATH", + "INSTALL_HDR_PATH", + "INSTALL_PATH", + "KBUILD_AFLAGS", + "KBUILD_AFLAGS_KERNEL", + "KBUILD_AFLAGS_MODULE", + "KBUILD_BUILTIN", + "KBUILD_CFLAGS", + "KBUILD_CFLAGS_KERNEL", + "KBUILD_CFLAGS_MODULE", + "KBUILD_CHECKSRC", + "KBUILD_CLIPPY", + "KBUILD_CPPFLAGS", + "KBUILD_EXTMOD", + "KBUILD_EXTRA_WARN", + "KBUILD_HOSTCFLAGS", + "KBUILD_HOSTCXXFLAGS", + "KBUILD_HOSTLDFLAGS", + "KBUILD_HOSTLDLIBS", + "KBUILD_HOSTRUSTFLAGS", + "KBUILD_IMAGE", + "KBUILD_LDFLAGS", + "KBUILD_LDFLAGS_MODULE", + "KBUILD_LDS", + "KBUILD_MODULES", + "KBUILD_PROCMACROLDFLAGS", + "KBUILD_RUSTFLAGS", + "KBUILD_RUSTFLAGS_KERNEL", + "KBUILD_RUSTFLAGS_MODULE", + "KBUILD_USERCFLAGS", + "KBUILD_USERLDFLAGS", + "KBUILD_VERBOSE", + "KBUILD_VMLINUX_LIBS", + "KBZIP2", + "KCONFIG_CONFIG", + "KERNELDOC", + "KERNELRELEASE", + "KERNELVERSION", + "KGZIP", + "KLZOP", + "LC_COLLATE", + "LC_NUMERIC", + "LD", + "LDFLAGS_MODULE", + "LEX", + "LINUXINCLUDE", + "LZ4", + "LZMA", + "MAKE", + "MAKEFILES", + "MAKEFILE_LIST", + "MAKEFLAGS", + "MAKELEVEL", + "MAKEOVERRIDES", + "MAKE_COMMAND", + "MAKE_HOST", + "MAKE_TERMERR", + "MAKE_TERMOUT", + "MAKE_VERSION", + "MFLAGS", + "MODLIB", + "NM", + "NOSTDINC_FLAGS", + "O", + "OBJCOPY", + "OBJCOPYFLAGS", + "OBJDUMP", + "PAHOLE", + "PATCHLEVEL", + "PERL", + "PYTHON3", + "Q", + "RCS_FIND_IGNORE", + "READELF", + "REALMODE_CFLAGS", + "RESOLVE_BTFIDS", + "RETHUNK_CFLAGS", + "RETHUNK_RUSTFLAGS", + "RETPOLINE_CFLAGS", + "RETPOLINE_RUSTFLAGS", + "RETPOLINE_VDSO_CFLAGS", + "RUSTC", + "RUSTC_BOOTSTRAP", + "RUSTC_OR_CLIPPY", + "RUSTC_OR_CLIPPY_QUIET", + "RUSTDOC", + "RUSTFLAGS_KERNEL", + "RUSTFLAGS_MODULE", + "RUSTFMT", + "SRCARCH", + "STRIP", + "SUBLEVEL", + "SUFFIXES", + "TAR", + "UTS_MACHINE", + "VERSION", + "VPATH", + "XZ", + "YACC", + "ZSTD", + "building_out_of_srctree", + "cross_compiling", + "objtree", + "quiet", + "rust_common_flags", + "srcroot", + "srctree", + "sub_make_done", + "subdir", +] + + +class Environment: + """ + Read-only accessor for kernel build environment variables. + """ + + @classmethod + def KERNEL_BUILD_VARIABLES(cls) -> dict[str, str]: + return { + name: value.strip() + for name in KERNEL_BUILD_VARIABLES_ALLOWLIST + if (value := os.getenv(name)) is not None and value.strip() + } + + @classmethod + def ARCH(cls) -> str | None: + return os.getenv("ARCH") + + @classmethod + def SRCARCH(cls) -> str | None: + return os.getenv("SRCARCH") + + @classmethod + def CC(cls) -> str | None: + return os.getenv("CC") + + @classmethod + def LD(cls) -> str | None: + return os.getenv("LD") + + @classmethod + def AR(cls) -> str | None: + return os.getenv("AR") + + @classmethod + def NM(cls) -> str | None: + return os.getenv("NM") + + @classmethod + def OBJCOPY(cls) -> str | None: + return os.getenv("OBJCOPY") + + @classmethod + def STRIP(cls) -> str | None: + return os.getenv("STRIP") diff --git a/scripts/sbom/sbom/path_utils.py b/scripts/sbom/sbom/path_utils.py new file mode 100644 index 000000000..29820046d --- /dev/null +++ b/scripts/sbom/sbom/path_utils.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import os +from functools import lru_cache + +PathStr = str +"""Filesystem path represented as a plain string for better performance than pathlib.Path.""" + + +def is_relative_to(path: PathStr, base: PathStr) -> bool: + return os.path.commonpath([path, base]) == base + +@lru_cache(maxsize=None) +def has_link(path: PathStr) -> bool: + """Returns True if path or any of its ancestor directories is a symlink. Results are cached to avoid duplicate lstat syscalls.""" + if os.path.islink(path): + return True + parent = os.path.dirname(path) + if parent == path: + return False + return has_link(parent) diff --git a/scripts/sbom/sbom/sbom_logging.py b/scripts/sbom/sbom/sbom_logging.py new file mode 100644 index 000000000..fbc53cc77 --- /dev/null +++ b/scripts/sbom/sbom/sbom_logging.py @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import logging +import inspect +from typing import Literal + + +MessageTemplate = str + + +class MessageLogger: + """Logger that suppresses repeated messages and stores a summary of all logged messages.""" + + _messages: dict[MessageTemplate, list[str]] + _message_counts: dict[MessageTemplate, int] + _repeated_logs_limit: int + """Maximum number of repeated messages of the same type to log before suppressing further output.""" + + def __init__(self, level: Literal["error", "warning"], repeated_logs_limit: int = 3) -> None: + self._level = level + self._messages = {} + self._message_counts = {} + self._repeated_logs_limit = repeated_logs_limit + + def log(self, template: MessageTemplate, /, **kwargs: str) -> None: + """Log a message based on a template and optional variables. Example: `log("Missing {path}", path=str(p))`.""" + message = template + for key, value in kwargs.items(): + message = message.replace("{" + key + "}", value) + if template not in self._messages: + self._messages[template] = [] + self._message_counts[template] = 0 + self._message_counts[template] += 1 + if self._message_counts[template] <= self._repeated_logs_limit: + if self._level == "error": + logging.error(message) + elif self._level == "warning": + logging.warning(message) + self._messages[template].append(message) + + def get_summary(self) -> str: + if len(self._messages) == 0: + return "" + summary: list[str] = [f"Summarize {self._level}s:"] + for template, messages in self._messages.items(): + for message in messages: + summary.append(message) + n_suppressed_messages = self._message_counts[template] - self._repeated_logs_limit + if n_suppressed_messages > 0: + instances = "instance" if n_suppressed_messages == 1 else "instances" + summary.append(f"... (Found {n_suppressed_messages} more {instances} of this {self._level})") + return "\n".join(summary) + + def has_messages(self) -> bool: + return len(self._message_counts) > 0 + + +_warning_logger: MessageLogger +_error_logger: MessageLogger + + +def warning(msg_template: MessageTemplate, /, **kwargs: str) -> None: + _warning_logger.log(msg_template, **kwargs) + + +def error(msg_template: MessageTemplate, /, **kwargs: str) -> None: + frame = inspect.currentframe() + caller_frame = frame.f_back if frame else None + info = inspect.getframeinfo(caller_frame) if caller_frame else None + if info: + msg_template = f'File "{info.filename}", line {info.lineno}, in {info.function}\n{msg_template}' + _error_logger.log(msg_template, **kwargs) + + +def summarize_warnings() -> str: + return _warning_logger.get_summary() + + +def summarize_errors() -> str: + return _error_logger.get_summary() + + +def has_errors() -> bool: + return _error_logger.has_messages() + + +def init() -> None: + global _warning_logger, _error_logger + _warning_logger = MessageLogger("warning") + _error_logger = MessageLogger("error") + + +init() diff --git a/scripts/sbom/sbom/spdx/__init__.py b/scripts/sbom/sbom/spdx/__init__.py new file mode 100644 index 000000000..4097b59f8 --- /dev/null +++ b/scripts/sbom/sbom/spdx/__init__.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from .spdxId import SpdxId, SpdxIdGenerator +from .serialization import JsonLdSpdxDocument + +__all__ = ["JsonLdSpdxDocument", "SpdxId", "SpdxIdGenerator"] diff --git a/scripts/sbom/sbom/spdx/build.py b/scripts/sbom/sbom/spdx/build.py new file mode 100644 index 000000000..a39ec9c09 --- /dev/null +++ b/scripts/sbom/sbom/spdx/build.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass, field +from sbom.spdx.core import DictionaryEntry, Element, Hash + + +@dataclass(kw_only=True) +class Build(Element): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Build/Classes/Build/""" + + type: str = field(init=False, default="build_Build") + build_buildType: str + build_buildId: str + build_environment: list[DictionaryEntry] = field(default_factory=list) + build_configSourceUri: list[str] = field(default_factory=list) + build_configSourceDigest: list[Hash] = field(default_factory=list) diff --git a/scripts/sbom/sbom/spdx/core.py b/scripts/sbom/sbom/spdx/core.py new file mode 100644 index 000000000..7eb376a1c --- /dev/null +++ b/scripts/sbom/sbom/spdx/core.py @@ -0,0 +1,170 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass, field + +from typing import Any, Literal +from sbom.spdx.spdxId import SpdxId + +SPDX_SPEC_VERSION = "3.0.1" + +ExternalIdentifierType = Literal["email", "gitoid", "urlScheme"] +HashAlgorithm = Literal["sha256", "sha512"] +ProfileIdentifierType = Literal["core", "software", "build", "lite", "simpleLicensing"] +RelationshipType = Literal[ + "contains", + "generates", + "hasDeclaredLicense", + "hasInput", + "hasOutput", + "ancestorOf", + "hasDistributionArtifact", + "dependsOn", +] +RelationshipCompleteness = Literal["complete", "incomplete", "noAssertion"] + + +@dataclass +class SpdxObject: + def to_dict(self) -> dict[str, Any]: + def _to_dict(v: Any): + return v.to_dict() if hasattr(v, "to_dict") else v + + d: dict[str, Any] = {} + for field_name in self.__dataclass_fields__: + value = getattr(self, field_name) + if value is None or value == [] or value == "": + continue + + if isinstance(value, Element): + d[field_name] = value.spdxId + elif isinstance(value, list) and len(value) > 0 and isinstance(value[0], Element): # type: ignore + value: list[Element] = value + d[field_name] = [v.spdxId for v in value] + else: + d[field_name] = [_to_dict(v) for v in value] if isinstance(value, list) else _to_dict(value) # type: ignore + return d + + +@dataclass(kw_only=True) +class IntegrityMethod(SpdxObject): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/IntegrityMethod/""" + + +@dataclass(kw_only=True) +class Hash(IntegrityMethod): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/Hash/""" + + type: str = field(init=False, default="Hash") + hashValue: str + algorithm: HashAlgorithm + + +@dataclass(kw_only=True) +class Element(SpdxObject): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/Element/""" + + type: str = field(init=False, default="Element") + spdxId: SpdxId + creationInfo: str = "_:creationinfo" + name: str | None = None + verifiedUsing: list[Hash] = field(default_factory=list) + comment: str | None = None + + +@dataclass(kw_only=True) +class ExternalMap(SpdxObject): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/ExternalMap/""" + + type: str = field(init=False, default="ExternalMap") + externalSpdxId: SpdxId + + +@dataclass(kw_only=True) +class NamespaceMap(SpdxObject): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/NamespaceMap/""" + + type: str = field(init=False, default="NamespaceMap") + prefix: str + namespace: str + + +@dataclass(kw_only=True) +class ElementCollection(Element): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/ElementCollection/""" + + type: str = field(init=False, default="ElementCollection") + element: list[Element] = field(default_factory=list) + rootElement: list[Element] = field(default_factory=list) + profileConformance: list[ProfileIdentifierType] = field(default_factory=list) + + +@dataclass(kw_only=True) +class SpdxDocument(ElementCollection): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/SpdxDocument/""" + + type: str = field(init=False, default="SpdxDocument") + import_: list[ExternalMap] = field(default_factory=list) + namespaceMap: list[NamespaceMap] = field(default_factory=list) + + def to_dict(self) -> dict[str, Any]: + return {("import" if k == "import_" else k): v for k, v in super().to_dict().items()} + + +@dataclass(kw_only=True) +class Agent(Element): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/Agent/""" + + type: str = field(init=False, default="Agent") + + +@dataclass(kw_only=True) +class SoftwareAgent(Agent): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/SoftwareAgent/""" + + type: str = field(init=False, default="SoftwareAgent") + + +@dataclass(kw_only=True) +class CreationInfo(SpdxObject): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/CreationInfo/""" + + type: str = field(init=False, default="CreationInfo") + id: SpdxId = "_:creationinfo" + specVersion: str = SPDX_SPEC_VERSION + createdBy: list[Agent] + created: str + comment: str | None = None + + def to_dict(self) -> dict[str, Any]: + return {("@id" if k == "id" else k): v for k, v in super().to_dict().items()} + + +@dataclass(kw_only=True) +class Relationship(Element): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/Relationship/""" + + type: str = field(init=False, default="Relationship") + relationshipType: RelationshipType + from_: Element # underscore because 'from' is a reserved keyword + to: list[Element] + completeness: RelationshipCompleteness | None = None + + def to_dict(self) -> dict[str, Any]: + return {("from" if k == "from_" else k): v for k, v in super().to_dict().items()} + + +@dataclass(kw_only=True) +class Artifact(Element): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/Artifact/""" + + type: str = field(init=False, default="Artifact") + + +@dataclass(kw_only=True) +class DictionaryEntry(SpdxObject): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Core/Classes/DictionaryEntry/""" + + type: str = field(init=False, default="DictionaryEntry") + key: str + value: str diff --git a/scripts/sbom/sbom/spdx/serialization.py b/scripts/sbom/sbom/spdx/serialization.py new file mode 100644 index 000000000..b4df7d368 --- /dev/null +++ b/scripts/sbom/sbom/spdx/serialization.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import json +from typing import Any +from sbom.path_utils import PathStr +from sbom.spdx.core import SPDX_SPEC_VERSION, SpdxDocument, SpdxObject + + +class JsonLdSpdxDocument: + """Represents an SPDX document in JSON-LD format for serialization.""" + + graph: list[SpdxObject] + + def __init__(self, graph: list[SpdxObject]) -> None: + """ + Initialize a JSON-LD SPDX document from a graph of SPDX objects. + The graph must contain a single SpdxDocument element. + + Args: + graph: List of SPDX objects representing the complete SPDX document. + """ + self.graph = graph + + @property + def context(self) -> list[str | dict[str, str]]: + spdx_document = next(element for element in self.graph if isinstance(element, SpdxDocument)) + return [ + f"https://spdx.org/rdf/{SPDX_SPEC_VERSION}/spdx-context.jsonld", + {ns.prefix: ns.namespace for ns in spdx_document.namespaceMap}, + ] + + def to_dict(self) -> dict[str, Any]: + """ + Convert the SPDX document to a dictionary representation suitable for JSON serialization. + + Returns: + Dictionary with @context and @graph keys following JSON-LD format. + """ + def _item_to_dict(item: SpdxObject) -> dict: + d = item.to_dict() + if isinstance(item, SpdxDocument): + d.pop("namespaceMap", None) + return d + return { + "@context": self.context, + "@graph": [_item_to_dict(item) for item in self.graph], + } + + def save(self, path: PathStr, prettify: bool) -> None: + """ + Save the SPDX document to a JSON file. + + Args: + path: File path where the document will be saved. + prettify: Whether to pretty-print the JSON with indentation. + """ + with open(path, "w", encoding="utf-8") as f: + if prettify: + json.dump(self.to_dict(), f, indent=2) + else: + json.dump(self.to_dict(), f, separators=(",", ":")) diff --git a/scripts/sbom/sbom/spdx/simplelicensing.py b/scripts/sbom/sbom/spdx/simplelicensing.py new file mode 100644 index 000000000..750ddd24a --- /dev/null +++ b/scripts/sbom/sbom/spdx/simplelicensing.py @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass, field +from sbom.spdx.core import Element + + +@dataclass(kw_only=True) +class AnyLicenseInfo(Element): + """https://spdx.github.io/spdx-spec/v3.0.1/model/SimpleLicensing/Classes/AnyLicenseInfo/""" + + type: str = field(init=False, default="simplelicensing_AnyLicenseInfo") + + +@dataclass(kw_only=True) +class LicenseExpression(AnyLicenseInfo): + """https://spdx.github.io/spdx-spec/v3.0.1/model/SimpleLicensing/Classes/LicenseExpression/""" + + type: str = field(init=False, default="simplelicensing_LicenseExpression") + simplelicensing_licenseExpression: str diff --git a/scripts/sbom/sbom/spdx/software.py b/scripts/sbom/sbom/spdx/software.py new file mode 100644 index 000000000..2f46de7c3 --- /dev/null +++ b/scripts/sbom/sbom/spdx/software.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass, field +from typing import Literal +from sbom.spdx.core import Artifact, ElementCollection, IntegrityMethod + + +SbomType = Literal["source", "build"] +FileKindType = Literal["file", "directory"] +SoftwarePurpose = Literal[ + "source", + "archive", + "library", + "file", + "data", + "configuration", + "executable", + "module", + "application", + "documentation", + "other", +] +ContentIdentifierType = Literal["gitoid", "swhid"] + + +@dataclass(kw_only=True) +class Sbom(ElementCollection): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Software/Classes/Sbom/""" + + type: str = field(init=False, default="software_Sbom") + software_sbomType: list[SbomType] = field(default_factory=list) + + +@dataclass(kw_only=True) +class ContentIdentifier(IntegrityMethod): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Software/Classes/ContentIdentifier/""" + + type: str = field(init=False, default="software_ContentIdentifier") + software_contentIdentifierType: ContentIdentifierType + software_contentIdentifierValue: str + + +@dataclass(kw_only=True) +class SoftwareArtifact(Artifact): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Software/Classes/SoftwareArtifact/""" + + type: str = field(init=False, default="software_Artifact") + software_primaryPurpose: SoftwarePurpose | None = None + software_copyrightText: str | None = None + software_contentIdentifier: list[ContentIdentifier] = field(default_factory=list) + + +@dataclass(kw_only=True) +class Package(SoftwareArtifact): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Software/Classes/Package/""" + + type: str = field(init=False, default="software_Package") + name: str # type: ignore + software_packageVersion: str | None = None + + +@dataclass(kw_only=True) +class File(SoftwareArtifact): + """https://spdx.github.io/spdx-spec/v3.0.1/model/Software/Classes/File/""" + + type: str = field(init=False, default="software_File") + name: str # type: ignore + software_fileKind: FileKindType | None = None diff --git a/scripts/sbom/sbom/spdx/spdxId.py b/scripts/sbom/sbom/spdx/spdxId.py new file mode 100644 index 000000000..589e85c5f --- /dev/null +++ b/scripts/sbom/sbom/spdx/spdxId.py @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from itertools import count +from typing import Iterator + +SpdxId = str + + +class SpdxIdGenerator: + _namespace: str + _prefix: str | None = None + _counter: Iterator[int] + + def __init__(self, namespace: str, prefix: str | None = None) -> None: + """ + Initialize the SPDX ID generator with a namespace. + + Args: + namespace: The full namespace to use for generated IDs. + prefix: Optional. If provided, generated IDs will use this prefix instead of the full namespace. + """ + self._namespace = namespace + self._prefix = prefix + self._counter = count(0) + + def generate(self) -> SpdxId: + return f"{f'{self._prefix}:' if self._prefix else self._namespace}{next(self._counter)}" + + @property + def prefix(self) -> str | None: + return self._prefix + + @property + def namespace(self) -> str: + return self._namespace diff --git a/scripts/sbom/sbom/spdx_graph/__init__.py b/scripts/sbom/sbom/spdx_graph/__init__.py new file mode 100644 index 000000000..3557b1d51 --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/__init__.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from .build_spdx_graphs import build_spdx_graphs +from .spdx_graph_model import SpdxIdGeneratorCollection + +__all__ = ["build_spdx_graphs", "SpdxIdGeneratorCollection"] diff --git a/scripts/sbom/sbom/spdx_graph/build_spdx_graphs.py b/scripts/sbom/sbom/spdx_graph/build_spdx_graphs.py new file mode 100644 index 000000000..ee24e9eaf --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/build_spdx_graphs.py @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from datetime import datetime +from typing import Protocol + +import logging +from sbom.config import KernelSpdxDocumentKind +from sbom.cmd_graph import CmdGraph +from sbom.path_utils import PathStr +from sbom.spdx_graph.kernel_file import KernelFileCollection +from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection +from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements +from sbom.spdx_graph.spdx_source_graph import SpdxSourceGraph +from sbom.spdx_graph.spdx_build_graph import SpdxBuildGraph +from sbom.spdx_graph.spdx_output_graph import SpdxOutputGraph + + +class SpdxGraphConfig(Protocol): + obj_tree: PathStr + src_tree: PathStr + created: datetime + build_type: str + build_id: str | None + package_license: str + package_version: str | None + package_copyright_text: str | None + + +def build_spdx_graphs( + cmd_graph: CmdGraph, + spdx_id_generators: SpdxIdGeneratorCollection, + config: SpdxGraphConfig, +) -> dict[KernelSpdxDocumentKind, SpdxGraph]: + """ + Builds SPDX graphs (output, source, and build) based on a cmd dependency graph. + If the source and object trees are identical, no dedicated source graph can be created. + In that case the source files are added to the build graph instead. + + Args: + cmd_graph: The dependency graph of a kernel build. + spdx_id_generators: Collection of SPDX ID generators. + config: Configuration options. + + Returns: + Dictionary of SPDX graphs + """ + shared_elements = SharedSpdxElements.create(spdx_id_generators.base, config.created) + kernel_files = KernelFileCollection.create(cmd_graph, config.obj_tree, config.src_tree, spdx_id_generators) + output_graph = SpdxOutputGraph.create( + root_files=list(kernel_files.output.values()), + shared_elements=shared_elements, + spdx_id_generators=spdx_id_generators, + config=config, + ) + spdx_graphs: dict[KernelSpdxDocumentKind, SpdxGraph] = { + KernelSpdxDocumentKind.OUTPUT: output_graph, + } + + if len(kernel_files.source) > 0: + spdx_graphs[KernelSpdxDocumentKind.SOURCE] = SpdxSourceGraph.create( + source_files=list(kernel_files.source.values()), + external_files=list(kernel_files.external.values()), + shared_elements=shared_elements, + spdx_id_generators=spdx_id_generators, + ) + else: + logging.info( + "Skipped creating a dedicated source SBOM because source files cannot be " + "reliably classified when the source and object trees are identical. " + "Added source files to the build SBOM instead." + ) + + build_graph = SpdxBuildGraph.create( + cmd_graph, + kernel_files, + shared_elements, + output_graph.high_level_build_element, + spdx_id_generators, + ) + spdx_graphs[KernelSpdxDocumentKind.BUILD] = build_graph + + return spdx_graphs diff --git a/scripts/sbom/sbom/spdx_graph/kernel_file.py b/scripts/sbom/sbom/spdx_graph/kernel_file.py new file mode 100644 index 000000000..505f25f66 --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/kernel_file.py @@ -0,0 +1,315 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass +from enum import Enum +import hashlib +import os +import re +from sbom.cmd_graph import CmdGraph +from sbom.path_utils import PathStr, is_relative_to +from sbom.spdx import SpdxId, SpdxIdGenerator +from sbom.spdx.core import Hash +from sbom.spdx.software import ContentIdentifier, File, SoftwarePurpose +import sbom.sbom_logging as sbom_logging +from sbom.spdx_graph.spdx_graph_model import SpdxIdGeneratorCollection + + +class KernelFileLocation(Enum): + """Represents the location of a file relative to the source/object trees.""" + + SOURCE_TREE = "source_tree" + """File is located in the source tree.""" + OBJ_TREE = "obj_tree" + """File is located in the object tree.""" + EXTERNAL = "external" + """File is located outside both source and object trees.""" + BOTH = "both" + """File is located in a folder that is both source and object tree.""" + + +@dataclass +class KernelFile: + """kernel-specific metadata used to generate an SPDX File element.""" + + absolute_path: PathStr + """Absolute path of the file.""" + file_location: KernelFileLocation + """Location of the file relative to the source/object trees.""" + name: str + """Name of the file element. Should be relative to the source tree if + file_location equals SOURCE_TREE and relative to the object tree if + file_location equals OBJ_TREE. If file_location equals EXTERNAL, the + absolute path is used.""" + license_identifier: str | None + """SPDX license ID if file_location equals SOURCE_TREE or BOTH; otherwise None.""" + spdx_id_generator: SpdxIdGenerator + """Generator for the SPDX ID of the file element.""" + + _spdx_file_element: File | None = None + + @classmethod + def create( + cls, + absolute_path: PathStr, + obj_tree: PathStr, + src_tree: PathStr, + spdx_id_generators: SpdxIdGeneratorCollection, + is_output: bool, + ) -> "KernelFile": + is_in_obj_tree = is_relative_to(absolute_path, obj_tree) + is_in_src_tree = is_relative_to(absolute_path, src_tree) + + # file element name should be relative to output or src tree if possible + if not is_in_src_tree and not is_in_obj_tree: + file_element_name = str(absolute_path) + file_location = KernelFileLocation.EXTERNAL + spdx_id_generator = spdx_id_generators.source if src_tree != obj_tree else spdx_id_generators.build + elif is_in_src_tree and src_tree == obj_tree: + file_element_name = os.path.relpath(absolute_path, obj_tree) + file_location = KernelFileLocation.BOTH + spdx_id_generator = spdx_id_generators.output if is_output else spdx_id_generators.build + elif is_in_obj_tree: + file_element_name = os.path.relpath(absolute_path, obj_tree) + file_location = KernelFileLocation.OBJ_TREE + spdx_id_generator = spdx_id_generators.output if is_output else spdx_id_generators.build + else: + file_element_name = os.path.relpath(absolute_path, src_tree) + file_location = KernelFileLocation.SOURCE_TREE + spdx_id_generator = spdx_id_generators.source + + # parse spdx license identifier + license_identifier = ( + _parse_spdx_license_identifier(absolute_path) + if file_location == KernelFileLocation.SOURCE_TREE or file_location == KernelFileLocation.BOTH + else None + ) + + return KernelFile( + absolute_path, + file_location, + file_element_name, + license_identifier, + spdx_id_generator, + ) + + @property + def spdx_file_element(self) -> File: + if self._spdx_file_element is None: + self._spdx_file_element = _build_file_element( + self.absolute_path, + self.name, + self.spdx_id_generator.generate(), + self.file_location, + ) + return self._spdx_file_element + + +@dataclass +class KernelFileCollection: + """Collection of kernel files.""" + + source: dict[PathStr, KernelFile] + build: dict[PathStr, KernelFile] + output: dict[PathStr, KernelFile] + external: dict[PathStr, KernelFile] + + @classmethod + def create( + cls, + cmd_graph: CmdGraph, + obj_tree: PathStr, + src_tree: PathStr, + spdx_id_generators: SpdxIdGeneratorCollection, + ) -> "KernelFileCollection": + source: dict[PathStr, KernelFile] = {} + build: dict[PathStr, KernelFile] = {} + output: dict[PathStr, KernelFile] = {} + external: dict[PathStr, KernelFile] = {} + root_node_paths = {node.absolute_path for node in cmd_graph.roots} + for node in cmd_graph: + is_root = node.absolute_path in root_node_paths + kernel_file = KernelFile.create( + node.absolute_path, + obj_tree, + src_tree, + spdx_id_generators, + is_root, + ) + if is_root: + output[kernel_file.absolute_path] = kernel_file + elif kernel_file.file_location == KernelFileLocation.SOURCE_TREE: + source[kernel_file.absolute_path] = kernel_file + elif kernel_file.file_location == KernelFileLocation.EXTERNAL: + external[kernel_file.absolute_path] = kernel_file + else: + build[kernel_file.absolute_path] = kernel_file + + return KernelFileCollection(source, build, output, external) + + def to_dict(self) -> dict[PathStr, KernelFile]: + return {**self.source, **self.build, **self.output, **self.external} + + +def _build_file_element(absolute_path: PathStr, name: str, spdx_id: SpdxId, file_location: KernelFileLocation) -> File: + verifiedUsing: list[Hash] = [] + content_identifier: list[ContentIdentifier] = [] + if os.path.isfile(absolute_path): + verifiedUsing = [Hash(algorithm="sha256", hashValue=_sha256(absolute_path))] + content_identifier = [ + ContentIdentifier( + software_contentIdentifierType="gitoid", + software_contentIdentifierValue=_git_blob_oid(absolute_path), + ) + ] + elif file_location == KernelFileLocation.EXTERNAL: + sbom_logging.warning( + "Cannot compute hash for {absolute_path} because file does not exist.", + absolute_path=absolute_path, + ) + else: + sbom_logging.error( + "Cannot compute hash for {absolute_path} because file does not exist.", + absolute_path=absolute_path, + ) + + # primary purpose + primary_purpose = _get_primary_purpose(absolute_path) + + return File( + spdxId=spdx_id, + name=name, + verifiedUsing=verifiedUsing, + software_primaryPurpose=primary_purpose, + software_contentIdentifier=content_identifier, + ) + + +def _sha256(file_path: PathStr, chunk_size: int = 1 << 20) -> str: + """Compute the SHA-256 hex digest of a file, reading it in chunks of chunk_size bytes.""" + h = hashlib.sha256() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(chunk_size), b""): + h.update(chunk) + return h.hexdigest() + + +def _git_blob_oid(file_path: str, chunk_size: int = 1 << 20) -> str: + """Compute the Git blob object ID (SHA-1 hex) for a file, like `git hash-object`, reading it in chunks of chunk_size bytes.""" + h = hashlib.sha1() + h.update(f"blob {os.path.getsize(file_path)}\0".encode()) + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(chunk_size), b""): + h.update(chunk) + return h.hexdigest() + + +# REUSE-IgnoreStart +SPDX_LICENSE_IDENTIFIER_PATTERN = re.compile( + r"SPDX-License-Identifier:" # literal tag + r"\s*" # optional whitespace after colon + r"(?P<id>.*?)" # license expression (non-greedy, stops before terminator) + r"(?:\s*" # optional whitespace before terminator (not captured) + r"(-->|\*/|$))", # terminator: XML "-->", C-style "*/", or end of line + re.MULTILINE, # match end of each line, not just end of string +) +# REUSE-IgnoreEnd + + +def _parse_spdx_license_identifier(absolute_path: str, max_bytes: int = 512) -> str | None: + """ + Extracts the SPDX-License-Identifier from the beginning of a source file. + + Args: + absolute_path: Path to the source file. + max_bytes: Maximum number of bytes to scan for the license identifier. + + Returns: + The license identifier string (e.g., 'GPL-2.0-only') if found, otherwise None. + """ + try: + with open(absolute_path, "r", encoding="utf-8") as f: + match = SPDX_LICENSE_IDENTIFIER_PATTERN.search(f.read(max_bytes)) + if match: + return match.group("id") + except (UnicodeDecodeError, OSError): + return None + return None + + +def _get_primary_purpose(absolute_path: PathStr) -> SoftwarePurpose | None: + def ends_with(suffixes: list[str]) -> bool: + return any(absolute_path.endswith(suffix) for suffix in suffixes) + + def includes_path_segments(path_segments: list[str]) -> bool: + return any(segment in absolute_path for segment in path_segments) + + # Source code + if ends_with([".c", ".h", ".S", ".s", ".rs", ".pl", "gen_smb1_mapping", "gen_smb2_mapping"]): + return "source" + + # Libraries + if ends_with([".a", ".so", ".so.raw", ".rlib"]): + return "library" + + # Archives + if ends_with([".xz", ".cpio", ".gz", ".tar", ".zip", "piggy_data"]): + return "archive" + + # Applications + if ends_with(["bzImage", "Image", ".efi"]): + return "application" + + # Executables / machine code + if ends_with([".bin", ".elf", "vmlinux", "vmlinux.unstripped", "vmlinuz", "bpfilter_umh"]): + return "executable" + + # Kernel modules + if ends_with([".ko"]): + return "module" + + # Data files + if ends_with( + [ + ".tbl", + ".relocs", + ".rmeta", + ".in", + ".dbg", + ".x509", + ".pbm", + ".ppm", + ".dtb", + ".uc", + ".inc", + ".dts", + ".dtsi", + ".dtbo", + ".xml", + ".ro", + "initramfs_inc_data", + "default_cpio_list", + "x509_certificate_list", + "utf8data.c_shipped", + "blacklist_hash_list", + "x509_revocation_list", + "cpucaps", + "sysreg", + "mach-types", + ] + ) or includes_path_segments(["drivers/gpu/drm/radeon/reg_srcs/"]): + return "data" + + # Configuration files + if ends_with([".pem", ".key", ".conf", ".config", ".cfg", ".bconf"]): + return "configuration" + + # Documentation + if ends_with([".md"]): + return "documentation" + + # Other / miscellaneous + if ends_with([".o", ".tmp"]): + return "other" + + sbom_logging.warning("Could not infer primary purpose for {absolute_path}", absolute_path=absolute_path) diff --git a/scripts/sbom/sbom/spdx_graph/shared_spdx_elements.py b/scripts/sbom/sbom/spdx_graph/shared_spdx_elements.py new file mode 100644 index 000000000..115e8778a --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/shared_spdx_elements.py @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass +from datetime import datetime, timezone +from sbom.spdx.core import CreationInfo, SoftwareAgent +from sbom.spdx.spdxId import SpdxIdGenerator + + +@dataclass(frozen=True) +class SharedSpdxElements: + agent: SoftwareAgent + creation_info: CreationInfo + + @classmethod + def create(cls, spdx_id_generator: SpdxIdGenerator, created: datetime) -> "SharedSpdxElements": + """ + Creates shared SPDX elements used across multiple documents. + + Args: + spdx_id_generator: Generator for creating SPDX IDs. + created: SPDX 'created' property used for the creation info. + + Returns: + SharedSpdxElements with agent and creation info. + """ + agent = SoftwareAgent( + spdxId=spdx_id_generator.generate(), + name="KernelSbom", + ) + creation_info = CreationInfo(createdBy=[agent], created=created.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")) + return SharedSpdxElements(agent=agent, creation_info=creation_info) diff --git a/scripts/sbom/sbom/spdx_graph/spdx_build_graph.py b/scripts/sbom/sbom/spdx_graph/spdx_build_graph.py new file mode 100644 index 000000000..4d738bc3b --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/spdx_build_graph.py @@ -0,0 +1,318 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass +from typing import Mapping +from sbom.cmd_graph import CmdGraph +from sbom.path_utils import PathStr +from sbom.spdx import SpdxIdGenerator +from sbom.spdx.build import Build +from sbom.spdx.core import ExternalMap, NamespaceMap, Relationship, SpdxDocument +from sbom.spdx.software import File, Sbom +from sbom.spdx_graph.kernel_file import KernelFileCollection +from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements +from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection +from sbom.spdx_graph.spdx_source_graph import source_file_license_elements + + +@dataclass +class SpdxBuildGraph(SpdxGraph): + """SPDX graph representing build dependencies connecting source files and + distributable output files""" + + @classmethod + def create( + cls, + cmd_graph: CmdGraph, + kernel_files: KernelFileCollection, + shared_elements: SharedSpdxElements, + high_level_build_element: Build, + spdx_id_generators: SpdxIdGeneratorCollection, + ) -> "SpdxBuildGraph": + if len(kernel_files.source) > 0: + return _create_spdx_build_graph( + cmd_graph, + kernel_files, + shared_elements, + high_level_build_element, + spdx_id_generators, + ) + else: + return _create_spdx_build_graph_with_mixed_sources( + cmd_graph, + kernel_files, + shared_elements, + high_level_build_element, + spdx_id_generators, + ) + + +def _create_spdx_build_graph( + cmd_graph: CmdGraph, + kernel_files: KernelFileCollection, + shared_elements: SharedSpdxElements, + high_level_build_element: Build, + spdx_id_generators: SpdxIdGeneratorCollection, +) -> SpdxBuildGraph: + """ + Creates an SPDX build graph where source and output files are referenced + from external documents. + + Args: + cmd_graph: The dependency graph of a kernel build. + kernel_files: Collection of categorized kernel files involved in the build. + shared_elements: SPDX elements shared across multiple documents. + high_level_build_element: The high-level Build element referenced by the build graph. + spdx_id_generators: Collection of generators for SPDX element IDs. + + Returns: + SpdxBuildGraph: The SPDX build graph connecting source files and distributable output files. + """ + # SpdxDocument + build_spdx_document = SpdxDocument( + spdxId=spdx_id_generators.build.generate(), + profileConformance=["core", "software", "build"], + namespaceMap=[ + NamespaceMap(prefix=generator.prefix, namespace=generator.namespace) + for generator in [ + spdx_id_generators.build, + spdx_id_generators.source, + spdx_id_generators.output, + spdx_id_generators.base, + ] + if generator.prefix is not None + ], + ) + + # Sbom + build_sbom = Sbom( + spdxId=spdx_id_generators.build.generate(), + software_sbomType=["build"], + ) + + # Src and object tree elements + obj_tree_element = File( + spdxId=spdx_id_generators.build.generate(), + name="$(obj_tree)", + software_fileKind="directory", + ) + obj_tree_contains_relationship = Relationship( + spdxId=spdx_id_generators.build.generate(), + relationshipType="contains", + from_=obj_tree_element, + to=[], + ) + + # File elements + build_file_elements = [file.spdx_file_element for file in kernel_files.build.values()] + file_relationships = _file_relationships( + cmd_graph=cmd_graph, + file_elements={key: file.spdx_file_element for key, file in kernel_files.to_dict().items()}, + high_level_build_element=high_level_build_element, + spdx_id_generator=spdx_id_generators.build, + ) + + # Update relationships + build_spdx_document.rootElement = [build_sbom] + + build_spdx_document.import_ = [ + *( + ExternalMap(externalSpdxId=file.spdx_file_element.spdxId) + for file in (*kernel_files.source.values(), *kernel_files.external.values()) + ), + ExternalMap(externalSpdxId=high_level_build_element.spdxId), + *(ExternalMap(externalSpdxId=file.spdx_file_element.spdxId) for file in kernel_files.output.values()), + ] + + build_sbom.rootElement = [obj_tree_element] + build_sbom.element = [ + obj_tree_element, + obj_tree_contains_relationship, + *build_file_elements, + *file_relationships, + ] + + obj_tree_contains_relationship.to = [ + *build_file_elements, + *(file.spdx_file_element for file in kernel_files.output.values()), + ] + + # create Spdx graphs + build_graph = SpdxBuildGraph( + build_spdx_document, + shared_elements.agent, + shared_elements.creation_info, + build_sbom, + ) + return build_graph + + +def _create_spdx_build_graph_with_mixed_sources( + cmd_graph: CmdGraph, + kernel_files: KernelFileCollection, + shared_elements: SharedSpdxElements, + high_level_build_element: Build, + spdx_id_generators: SpdxIdGeneratorCollection, +) -> SpdxBuildGraph: + """ + Creates an SPDX build graph where only output files are referenced from + an external document. Source files are included directly in the build graph. + + Args: + cmd_graph: The dependency graph of a kernel build. + kernel_files: Collection of categorized kernel files involved in the build. + shared_elements: SPDX elements shared across multiple documents. + high_level_build_element: The high-level Build element referenced by the build graph. + spdx_id_generators: Collection of generators for SPDX element IDs. + + Returns: + SpdxBuildGraph: The SPDX build graph connecting source files and distributable output files. + """ + # SpdxDocument + build_spdx_document = SpdxDocument( + spdxId=spdx_id_generators.build.generate(), + profileConformance=["core", "software", "build"], + namespaceMap=[ + NamespaceMap(prefix=generator.prefix, namespace=generator.namespace) + for generator in [ + spdx_id_generators.build, + spdx_id_generators.output, + spdx_id_generators.base, + ] + if generator.prefix is not None + ], + ) + + # Sbom + build_sbom = Sbom( + spdxId=spdx_id_generators.build.generate(), + software_sbomType=["build"], + ) + + # File elements + build_file_elements = [file.spdx_file_element for file in kernel_files.build.values()] + external_file_elements = [file.spdx_file_element for file in kernel_files.external.values()] + file_relationships = _file_relationships( + cmd_graph=cmd_graph, + file_elements={key: file.spdx_file_element for key, file in kernel_files.to_dict().items()}, + high_level_build_element=high_level_build_element, + spdx_id_generator=spdx_id_generators.build, + ) + + # Source file license elements + source_file_license_identifiers, source_file_license_relationships = source_file_license_elements( + list(kernel_files.build.values()), spdx_id_generators.build + ) + + # Update relationships + build_spdx_document.rootElement = [build_sbom] + root_file_elements = [file.spdx_file_element for file in kernel_files.output.values()] + build_spdx_document.import_ = [ + ExternalMap(externalSpdxId=high_level_build_element.spdxId), + *(ExternalMap(externalSpdxId=file.spdxId) for file in root_file_elements), + ] + + build_sbom.rootElement = [*root_file_elements] + build_sbom.element = [ + *build_file_elements, + *external_file_elements, + *source_file_license_identifiers, + *source_file_license_relationships, + *file_relationships, + ] + + build_graph = SpdxBuildGraph( + build_spdx_document, + shared_elements.agent, + shared_elements.creation_info, + build_sbom, + ) + return build_graph + + +def _file_relationships( + cmd_graph: CmdGraph, + file_elements: Mapping[PathStr, File], + high_level_build_element: Build, + spdx_id_generator: SpdxIdGenerator, +) -> list[Build | Relationship]: + """ + Construct SPDX Build and Relationship elements representing dependency + relationships in the cmd graph. + + Args: + cmd_graph: The dependency graph of a kernel build. + file_elements: Mapping of filesystem paths (PathStr) to their + corresponding SPDX File elements. + high_level_build_element: The SPDX Build element representing the overall build process/root. + spdx_id_generator: Generator for unique SPDX IDs. + + Returns: + list[Build | Relationship]: List of SPDX Build and Relationship elements + """ + high_level_build_ancestorOf_relationship = Relationship( + spdxId=spdx_id_generator.generate(), + relationshipType="ancestorOf", + from_=high_level_build_element, + completeness="complete", + to=[], + ) + + # Create a relationship between each node (output file) + # and its children (input files) + build_and_relationship_elements: list[Build | Relationship] = [high_level_build_ancestorOf_relationship] + for node in cmd_graph: + # .cmd file dependencies + if node.cmd_file is not None: + build_element = Build( + spdxId=spdx_id_generator.generate(), + build_buildType=high_level_build_element.build_buildType, + build_buildId=high_level_build_element.build_buildId, + comment=node.cmd_file.savedcmd, + ) + build_and_relationship_elements.append(build_element) + + if node.cmd_file_dependencies: + hasInput_relationship = Relationship( + spdxId=spdx_id_generator.generate(), + relationshipType="hasInput", + from_=build_element, + to=[file_elements[dep.absolute_path] for dep in node.cmd_file_dependencies], + ) + build_and_relationship_elements.append(hasInput_relationship) + + hasOutput_relationship = Relationship( + spdxId=spdx_id_generator.generate(), + relationshipType="hasOutput", + from_=build_element, + to=[file_elements[node.absolute_path]], + ) + build_and_relationship_elements.append(hasOutput_relationship) + + high_level_build_ancestorOf_relationship.to.append(build_element) + + # incbin dependencies + if len(node.incbin_dependencies) > 0: + incbin_dependsOn_relationship = Relationship( + spdxId=spdx_id_generator.generate(), + relationshipType="dependsOn", + comment="\n".join([incbin_dependency.full_statement for incbin_dependency in node.incbin_dependencies]), + from_=file_elements[node.absolute_path], + to=[ + file_elements[incbin_dependency.node.absolute_path] + for incbin_dependency in node.incbin_dependencies + ], + ) + build_and_relationship_elements.append(incbin_dependsOn_relationship) + + # hardcoded dependencies + if len(node.hardcoded_dependencies) > 0: + hardcoded_dependency_relationship = Relationship( + spdxId=spdx_id_generator.generate(), + relationshipType="dependsOn", + from_=file_elements[node.absolute_path], + to=[file_elements[n.absolute_path] for n in node.hardcoded_dependencies], + ) + build_and_relationship_elements.append(hardcoded_dependency_relationship) + + return build_and_relationship_elements diff --git a/scripts/sbom/sbom/spdx_graph/spdx_graph_model.py b/scripts/sbom/sbom/spdx_graph/spdx_graph_model.py new file mode 100644 index 000000000..682194d43 --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/spdx_graph_model.py @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass +from sbom.spdx.core import CreationInfo, SoftwareAgent, SpdxDocument, SpdxObject +from sbom.spdx.software import Sbom +from sbom.spdx.spdxId import SpdxIdGenerator + + +@dataclass +class SpdxGraph: + """Represents the complete graph of a single SPDX document.""" + + spdx_document: SpdxDocument + agent: SoftwareAgent + creation_info: CreationInfo + sbom: Sbom + + def to_list(self) -> list[SpdxObject]: + return [ + self.spdx_document, + self.agent, + self.creation_info, + self.sbom, + *self.sbom.element, + ] + + +@dataclass +class SpdxIdGeneratorCollection: + """Holds SPDX ID generators for different document types to ensure globally unique SPDX IDs.""" + + base: SpdxIdGenerator + source: SpdxIdGenerator + build: SpdxIdGenerator + output: SpdxIdGenerator diff --git a/scripts/sbom/sbom/spdx_graph/spdx_output_graph.py b/scripts/sbom/sbom/spdx_graph/spdx_output_graph.py new file mode 100644 index 000000000..ff9b2c31f --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/spdx_output_graph.py @@ -0,0 +1,187 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass +import os +from typing import Protocol +from sbom.environment import Environment +from sbom.path_utils import PathStr +from sbom.spdx.build import Build +from sbom.spdx.core import DictionaryEntry, NamespaceMap, Relationship, SpdxDocument +from sbom.spdx.simplelicensing import LicenseExpression +from sbom.spdx.software import File, Package, Sbom +from sbom.spdx.spdxId import SpdxIdGenerator +from sbom.spdx_graph.kernel_file import KernelFile +from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements +from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection + + +class SpdxOutputGraphConfig(Protocol): + obj_tree: PathStr + src_tree: PathStr + build_type: str + build_id: str | None + package_license: str + package_version: str | None + package_copyright_text: str | None + + +@dataclass +class SpdxOutputGraph(SpdxGraph): + """SPDX graph representing distributable output files""" + + high_level_build_element: Build + + @classmethod + def create( + cls, + root_files: list[KernelFile], + shared_elements: SharedSpdxElements, + spdx_id_generators: SpdxIdGeneratorCollection, + config: SpdxOutputGraphConfig, + ) -> "SpdxOutputGraph": + """ + Args: + root_files: List of distributable output files which act as roots + of the dependency graph. + shared_elements: Shared SPDX elements used across multiple documents. + spdx_id_generators: Collection of SPDX ID generators. + config: Configuration options. + + Returns: + SpdxOutputGraph: The SPDX output graph. + """ + # SpdxDocument + spdx_document = SpdxDocument( + spdxId=spdx_id_generators.output.generate(), + profileConformance=["core", "software", "build", "simpleLicensing"], + namespaceMap=[ + NamespaceMap(prefix=generator.prefix, namespace=generator.namespace) + for generator in [spdx_id_generators.output, spdx_id_generators.base] + if generator.prefix is not None + ], + ) + + # Sbom + sbom = Sbom( + spdxId=spdx_id_generators.output.generate(), + software_sbomType=["build"], + ) + + # High-level Build elements + config_source_element = KernelFile.create( + absolute_path=os.path.join(config.obj_tree, ".config"), + obj_tree=config.obj_tree, + src_tree=config.src_tree, + spdx_id_generators=spdx_id_generators, + is_output=True, + ).spdx_file_element + high_level_build_element, high_level_build_element_hasOutput_relationship = _high_level_build_elements( + config.build_type, + config.build_id, + config_source_element, + spdx_id_generators.output, + ) + + # Root file elements + root_file_elements: list[File] = [file.spdx_file_element for file in root_files] + + # Package elements + package_elements = [ + Package( + spdxId=spdx_id_generators.output.generate(), + name=_get_package_name(file.name), + software_packageVersion=config.package_version, + software_copyrightText=config.package_copyright_text, + comment=f"Architecture={arch}" if (arch := Environment.ARCH() or Environment.SRCARCH()) else None, + software_primaryPurpose=file.software_primaryPurpose, + ) + for file in root_file_elements + ] + package_hasDistributionArtifact_file_relationships = [ + Relationship( + spdxId=spdx_id_generators.output.generate(), + relationshipType="hasDistributionArtifact", + from_=package, + to=[file], + ) + for package, file in zip(package_elements, root_file_elements) + ] + package_license_expression = LicenseExpression( + spdxId=spdx_id_generators.output.generate(), + simplelicensing_licenseExpression=config.package_license, + ) + package_hasDeclaredLicense_relationships = [ + Relationship( + spdxId=spdx_id_generators.output.generate(), + relationshipType="hasDeclaredLicense", + from_=package, + to=[package_license_expression], + ) + for package in package_elements + ] + + # Update relationships + spdx_document.rootElement = [sbom] + + sbom.rootElement = [*package_elements] + sbom.element = [ + config_source_element, + high_level_build_element, + high_level_build_element_hasOutput_relationship, + *root_file_elements, + *package_elements, + *package_hasDistributionArtifact_file_relationships, + package_license_expression, + *package_hasDeclaredLicense_relationships, + ] + + high_level_build_element_hasOutput_relationship.to = [*root_file_elements] + + output_graph = SpdxOutputGraph( + spdx_document, + shared_elements.agent, + shared_elements.creation_info, + sbom, + high_level_build_element, + ) + return output_graph + + +def _get_package_name(filename: str) -> str: + """ + Generates a SPDX package name from a filename. + Kernel images (bzImage, Image) get a descriptive name, others use the basename of the file. + """ + KERNEL_FILENAMES = ["bzImage", "Image"] + basename = os.path.basename(filename) + return f"Linux Kernel ({basename})" if basename in KERNEL_FILENAMES else basename + + +def _high_level_build_elements( + build_type: str, + build_id: str | None, + config_source_element: File, + spdx_id_generator: SpdxIdGenerator, +) -> tuple[Build, Relationship]: + build_spdxId = spdx_id_generator.generate() + high_level_build_element = Build( + spdxId=build_spdxId, + build_buildType=build_type, + build_buildId=build_id if build_id is not None else build_spdxId, + build_environment=[ + DictionaryEntry(key=key, value=value) + for key, value in Environment.KERNEL_BUILD_VARIABLES().items() + if value + ], + build_configSourceUri=[config_source_element.spdxId], + build_configSourceDigest=config_source_element.verifiedUsing, + ) + + high_level_build_element_hasOutput_relationship = Relationship( + spdxId=spdx_id_generator.generate(), + relationshipType="hasOutput", + from_=high_level_build_element, + to=[], + ) + return high_level_build_element, high_level_build_element_hasOutput_relationship diff --git a/scripts/sbom/sbom/spdx_graph/spdx_source_graph.py b/scripts/sbom/sbom/spdx_graph/spdx_source_graph.py new file mode 100644 index 000000000..90880212d --- /dev/null +++ b/scripts/sbom/sbom/spdx_graph/spdx_source_graph.py @@ -0,0 +1,130 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +from dataclasses import dataclass +from sbom.spdx import SpdxIdGenerator +from sbom.spdx.core import Element, NamespaceMap, Relationship, SpdxDocument +from sbom.spdx.simplelicensing import LicenseExpression +from sbom.spdx.software import File, Sbom +from sbom.spdx_graph.kernel_file import KernelFile +from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements +from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection + + +@dataclass +class SpdxSourceGraph(SpdxGraph): + """SPDX graph representing source files""" + + @classmethod + def create( + cls, + source_files: list[KernelFile], + external_files: list[KernelFile], + shared_elements: SharedSpdxElements, + spdx_id_generators: SpdxIdGeneratorCollection, + ) -> "SpdxSourceGraph": + """ + Args: + source_files: List of files within the kernel source tree. + external_files: Files outside both source and object trees. + shared_elements: Shared SPDX elements used across multiple documents. + spdx_id_generators: Collection of SPDX ID generators. + + Returns: + SpdxSourceGraph: The SPDX source graph. + """ + # SpdxDocument + source_spdx_document = SpdxDocument( + spdxId=spdx_id_generators.source.generate(), + profileConformance=["core", "software", "simpleLicensing"], + namespaceMap=[ + NamespaceMap(prefix=generator.prefix, namespace=generator.namespace) + for generator in [spdx_id_generators.source, spdx_id_generators.base] + if generator.prefix is not None + ], + ) + + # Sbom + source_sbom = Sbom( + spdxId=spdx_id_generators.source.generate(), + software_sbomType=["source"], + ) + + # Src Tree Elements + src_tree_element = File( + spdxId=spdx_id_generators.source.generate(), + name="$(src_tree)", + software_fileKind="directory", + ) + src_tree_contains_relationship = Relationship( + spdxId=spdx_id_generators.source.generate(), + relationshipType="contains", + from_=src_tree_element, + to=[], + ) + + # Source file elements + source_file_elements: list[Element] = [file.spdx_file_element for file in source_files] + external_file_elements: list[Element] = [file.spdx_file_element for file in external_files] + + # Source file license elements + source_file_license_identifiers, source_file_license_relationships = source_file_license_elements( + source_files, spdx_id_generators.source + ) + + # Update relationships + source_spdx_document.rootElement = [source_sbom] + source_sbom.rootElement = [src_tree_element] + source_sbom.element = [ + src_tree_element, + src_tree_contains_relationship, + *source_file_elements, + *external_file_elements, + *source_file_license_identifiers, + *source_file_license_relationships, + ] + src_tree_contains_relationship.to = source_file_elements + + source_graph = SpdxSourceGraph( + source_spdx_document, + shared_elements.agent, + shared_elements.creation_info, + source_sbom, + ) + return source_graph + + +def source_file_license_elements( + source_files: list[KernelFile], spdx_id_generator: SpdxIdGenerator +) -> tuple[list[LicenseExpression], list[Relationship]]: + """ + Creates SPDX license expressions and links them to the given source files + via hasDeclaredLicense relationships. + + Args: + source_files: List of files within the kernel source tree. + spdx_id_generator: Generator for unique SPDX IDs. + + Returns: + Tuple of (license expressions, hasDeclaredLicense relationships). + """ + license_expressions: dict[str, LicenseExpression] = {} + for file in source_files: + if file.license_identifier is None or file.license_identifier in license_expressions: + continue + license_expressions[file.license_identifier] = LicenseExpression( + spdxId=spdx_id_generator.generate(), + simplelicensing_licenseExpression=file.license_identifier, + ) + + source_file_license_relationships = [ + Relationship( + spdxId=spdx_id_generator.generate(), + relationshipType="hasDeclaredLicense", + from_=file.spdx_file_element, + to=[license_expressions[file.license_identifier]], + ) + for file in source_files + if file.license_identifier is not None + ] + return ([*license_expressions.values()], source_file_license_relationships) diff --git a/scripts/sbom/tests/__init__.py b/scripts/sbom/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/sbom/tests/cmd_graph/__init__.py b/scripts/sbom/tests/cmd_graph/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py b/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py new file mode 100644 index 000000000..d7776f072 --- /dev/null +++ b/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py @@ -0,0 +1,468 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import os +import unittest +from unittest.mock import patch + +from sbom.cmd_graph.savedcmd_parser import parse_inputs_from_commands +from sbom.cmd_graph.savedcmd_parser.command_parser_registry import CommandParserRegistry +import sbom.sbom_logging as sbom_logging + + +class TestSavedCmdParser(unittest.TestCase): + def _assert_parsing(self, cmd: str, expected: str, registry: CommandParserRegistry | None = None) -> None: + sbom_logging.init() + parsed = parse_inputs_from_commands(cmd, fail_on_unknown_build_command=False, registry=registry) + target = [] if expected == "" else expected.split(" ") + self.assertEqual(parsed, target) + errors = sbom_logging._error_logger._message_counts # type: ignore + self.assertEqual(errors, {}) + + # Error handling tests + def test_malformed_shell_quoting(self): + command = 'gcc "unterminated' + with patch.object(sbom_logging, "warning") as warning: + parsed = parse_inputs_from_commands(command, fail_on_unknown_build_command=False) + + self.assertEqual(parsed, []) + warning.assert_called_once_with( + "Skipped parsing command {single_command} because of command parsing error: {error_message}", + single_command=command, + error_message="No closing quotation", + ) + + def test_missing_positional_argument(self): + command = "objcopy" + with patch.object(sbom_logging, "warning") as warning: + parsed = parse_inputs_from_commands(command, fail_on_unknown_build_command=False) + + self.assertEqual(parsed, []) + warning.assert_called_once_with( + "Skipped parsing command {single_command} because of command parsing error: {error_message}", + single_command=command, + error_message="list index out of range", + ) + + # Compound command tests + def test_dd_cat(self): + cmd = "(dd if=arch/x86/boot/setup.bin bs=4k conv=sync status=none; cat arch/x86/boot/vmlinux.bin) >arch/x86/boot/bzImage" + expected = "arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin" + self._assert_parsing(cmd, expected) + + def test_manual_file_creation(self): + cmd = """{ symbase=__dtbo_overlay_bad_unresolved; echo '$(pound)include <asm-generic/vmlinux.lds.h>'; echo '.section .rodata,"a"'; echo '.balign STRUCT_ALIGNMENT'; echo ".global $${symbase}_begin"; echo "$${symbase}_begin:"; echo '.incbin "drivers/of/unittest-data/overlay_bad_unresolved.dtbo" '; echo ".global $${symbase}_end"; echo "$${symbase}_end:"; echo '.balign STRUCT_ALIGNMENT'; } > drivers/of/unittest-data/overlay_bad_unresolved.dtbo.S""" + expected = "" + self._assert_parsing(cmd, expected) + + def test_cat_xz_wrap(self): + cmd = "{ cat arch/x86/boot/compressed/vmlinux.bin | sh ../scripts/xz_wrap.sh; printf \\130\\064\\024\\000; } > arch/x86/boot/compressed/vmlinux.bin.xz" + expected = "arch/x86/boot/compressed/vmlinux.bin" + self._assert_parsing(cmd, expected) + + def test_printf_sed(self): + cmd = r"""{ printf 'static char tomoyo_builtin_profile[] __initdata =\n'; sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/\t"\1\\n"/' -- /dev/null; printf '\t"";\n'; printf 'static char tomoyo_builtin_exception_policy[] __initdata =\n'; sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/\t"\1\\n"/' -- ../security/tomoyo/policy/exception_policy.conf.default; printf '\t"";\n'; printf 'static char tomoyo_builtin_domain_policy[] __initdata =\n'; sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/\t"\1\\n"/' -- /dev/null; printf '\t"";\n'; printf 'static char tomoyo_builtin_manager[] __initdata =\n'; sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/\t"\1\\n"/' -- /dev/null; printf '\t"";\n'; printf 'static char tomoyo_builtin_stat[] __initdata =\n'; sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/\t"\1\\n"/' -- /dev/null; printf '\t"";\n'; } > security/tomoyo/builtin-policy.h""" + expected = "../security/tomoyo/policy/exception_policy.conf.default" + self._assert_parsing(cmd, expected) + + def test_bin2c_echo(self): + cmd = """(echo "static char tomoyo_builtin_profile[] __initdata ="; ./scripts/bin2c </dev/null; echo ";"; echo "static char tomoyo_builtin_exception_policy[] __initdata ="; ./scripts/bin2c <../security/tomoyo/policy/exception_policy.conf.default; echo ";"; echo "static char tomoyo_builtin_domain_policy[] __initdata ="; ./scripts/bin2c </dev/null; echo ";"; echo "static char tomoyo_builtin_manager[] __initdata ="; ./scripts/bin2c </dev/null; echo ";"; echo "static char tomoyo_builtin_stat[] __initdata ="; ./scripts/bin2c </dev/null; echo ";") >security/tomoyo/builtin-policy.h""" + expected = "../security/tomoyo/policy/exception_policy.conf.default" + self._assert_parsing(cmd, expected) + + def test_cat_colon(self): + cmd = "{ cat init/modules.order; cat usr/modules.order; cat arch/x86/modules.order; cat arch/x86/boot/startup/modules.order; cat kernel/modules.order; cat certs/modules.order; cat mm/modules.order; cat fs/modules.order; cat ipc/modules.order; cat security/modules.order; cat crypto/modules.order; cat block/modules.order; cat io_uring/modules.order; cat lib/modules.order; cat arch/x86/lib/modules.order; cat drivers/modules.order; cat sound/modules.order; cat samples/modules.order; cat net/modules.order; cat virt/modules.order; cat arch/x86/pci/modules.order; cat arch/x86/power/modules.order; cat arch/x86/video/modules.order; :; } > modules.order" + expected = "init/modules.order usr/modules.order arch/x86/modules.order arch/x86/boot/startup/modules.order kernel/modules.order certs/modules.order mm/modules.order fs/modules.order ipc/modules.order security/modules.order crypto/modules.order block/modules.order io_uring/modules.order lib/modules.order arch/x86/lib/modules.order drivers/modules.order sound/modules.order samples/modules.order net/modules.order virt/modules.order arch/x86/pci/modules.order arch/x86/power/modules.order arch/x86/video/modules.order" + self._assert_parsing(cmd, expected) + + def test_cat_zstd(self): + cmd = "{ cat arch/x86/boot/compressed/vmlinux.bin arch/x86/boot/compressed/vmlinux.relocs | zstd -22 --ultra; printf \\340\\362\\066\\003; } > arch/x86/boot/compressed/vmlinux.bin.zst" + expected = "arch/x86/boot/compressed/vmlinux.bin arch/x86/boot/compressed/vmlinux.relocs" + self._assert_parsing(cmd, expected) + + # cat command tests + def test_cat_redirect(self): + cmd = "cat ../fs/unicode/utf8data.c_shipped > fs/unicode/utf8data.c" + expected = "../fs/unicode/utf8data.c_shipped" + self._assert_parsing(cmd, expected) + + def test_cat_piped(self): + cmd = "cat arch/x86/boot/compressed/vmlinux.bin arch/x86/boot/compressed/vmlinux.relocs | gzip -n -f -9 > arch/x86/boot/compressed/vmlinux.bin.gz" + expected = "arch/x86/boot/compressed/vmlinux.bin arch/x86/boot/compressed/vmlinux.relocs" + self._assert_parsing(cmd, expected) + + # sed command tests + def test_sed(self): + cmd = "sed -n 's/.*define *BLIST_\\([A-Z0-9_]*\\) *.*/BLIST_FLAG_NAME(\\1),/p' ../include/scsi/scsi_devinfo.h > drivers/scsi/scsi_devinfo_tbl.c" + expected = "../include/scsi/scsi_devinfo.h" + self._assert_parsing(cmd, expected) + + # awk command tests + def test_awk(self): + cmd = "awk -f ../arch/arm64/tools/gen-cpucaps.awk ../arch/arm64/tools/cpucaps > arch/arm64/include/generated/asm/cpucap-defs.h" + expected = "../arch/arm64/tools/cpucaps" + self._assert_parsing(cmd, expected) + + def test_awk_with_input_redirection(self): + cmd = "awk -v N=1 -f ../lib/raid6/unroll.awk < ../lib/raid6/int.uc > lib/raid6/int1.c" + expected = "../lib/raid6/int.uc" + self._assert_parsing(cmd, expected) + + # openssl command tests + def test_openssl(self): + cmd = "openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 -config certs/x509.genkey -outform PEM -out certs/signing_key.pem -keyout certs/signing_key.pem 2>&1" + expected = "" + self._assert_parsing(cmd, expected) + + # gcc/clang command tests + def test_gcc(self): + cmd = ( + "gcc -Wp,-MMD,arch/x86/pci/.i386.o.d -nostdinc -I../arch/x86/include -I./arch/x86/include/generated -I../include -I./include -I../arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I../include/uapi -I./include/generated/uapi -include ../include/linux/compiler-version.h -include ../include/linux/kconfig.h -include ../include/linux/compiler_types.h -D__KERNEL__ -fmacro-prefix-map=../= -Werror -std=gnu11 -fshort-wchar -funsigned-char -fno-common -fno-PIE -fno-strict-aliasing -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -fcf-protection=branch -fno-jump-tables -m64 -falign-jumps=1 -falign-loops=1 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -march=x86-64 -mtune=generic -mno-red-zone -mcmodel=kernel -mstack-protector-guard-reg=gs -mstack-protector-guard-symbol=__ref_stack_chk_guard -Wno-sign-compare -fno-asynchronous-unwind-tables -mindirect-branch=thunk-extern -mindirect-branch-register -mindirect-branch-cs-prefix -mfunction-return=thunk-extern -fno-jump-tables -fpatchable-function-entry=16,16 -fno-delete-null-pointer-checks -O2 -fno-allow-store-data-races -fstack-protector-strong -fomit-frame-pointer -fno-stack-clash-protection -falign-functions=16 -fno-strict-overflow -fno-stack-check -fconserve-stack -fno-builtin-wcslen -Wall -Wextra -Wundef -Werror=implicit-function-declaration -Werror=implicit-int -Werror=return-type -Werror=strict-prototypes -Wno-format-security -Wno-trigraphs -Wno-frame-address -Wno-address-of-packed-member -Wmissing-declarations -Wmissing-prototypes -Wframe-larger-than=2048 -Wno-main -Wvla-larger-than=1 -Wno-pointer-sign -Wcast-function-type -Wno-array-bounds -Wno-stringop-overflow -Wno-alloc-size-larger-than -Wimplicit-fallthrough=5 -Werror=date-time -Werror=incompatible-pointer-types -Werror=designated-init -Wenum-conversion -Wunused -Wno-unused-but-set-variable -Wno-unused-const-variable -Wno-packed-not-aligned -Wno-format-overflow -Wno-format-truncation -Wno-stringop-truncation -Wno-override-init -Wno-missing-field-initializers -Wno-type-limits -Wno-shift-negative-value -Wno-maybe-uninitialized -Wno-sign-compare -Wno-unused-parameter -I../arch/x86/pci -Iarch/x86/pci -DKBUILD_MODFILE=" + "arch/x86/pci/i386" + " -DKBUILD_BASENAME=" + "i386" + " -DKBUILD_MODNAME=" + "i386" + " -D__KBUILD_MODNAME=kmod_i386 -c -o arch/x86/pci/i386.o ../arch/x86/pci/i386.c " + ) + expected = "../arch/x86/pci/i386.c" + self._assert_parsing(cmd, expected) + + def test_gcc_linking(self): + cmd = "gcc -o arch/x86/tools/relocs arch/x86/tools/relocs_32.o arch/x86/tools/relocs_64.o arch/x86/tools/relocs_common.o" + expected = "arch/x86/tools/relocs_32.o arch/x86/tools/relocs_64.o arch/x86/tools/relocs_common.o" + self._assert_parsing(cmd, expected) + + def test_gcc_without_compile_flag(self): + cmd = "gcc -Wp,-MMD,arch/x86/boot/compressed/.mkpiggy.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -I ../scripts/include -I../tools/include -I arch/x86/boot/compressed -o arch/x86/boot/compressed/mkpiggy ../arch/x86/boot/compressed/mkpiggy.c" + expected = "../arch/x86/boot/compressed/mkpiggy.c" + self._assert_parsing(cmd, expected) + + def test_gcc_with_env_override(self): + with patch.dict(os.environ, {"CC": "ccache gcc"}): + registry = CommandParserRegistry.create() + cmd = "gcc -o arch/x86/tools/relocs arch/x86/tools/relocs_32.o arch/x86/tools/relocs_64.o arch/x86/tools/relocs_common.o" + expected = "arch/x86/tools/relocs_32.o arch/x86/tools/relocs_64.o arch/x86/tools/relocs_common.o" + self._assert_parsing(cmd, expected, registry) + self._assert_parsing(f"ccache {cmd}", expected, registry) + + def test_gcc_dts_preprocessing(self): + cmd = "gcc -E -Wp,-MMD,drivers/of/.empty_root.dtb.d.pre.tmp -nostdinc -I ../scripts/dtc/include-prefixes -undef -D__DTS__ -x assembler-with-cpp -o drivers/of/.empty_root.dtb.dts.tmp ../drivers/of/empty_root.dts" + expected = "../drivers/of/empty_root.dts" + self._assert_parsing(cmd, expected) + + def test_clang(self): + cmd = """clang -Wp,-MMD,arch/x86/entry/.entry_64_compat.o.d -nostdinc -I../arch/x86/include -I./arch/x86/include/generated -I../include -I./include -I../arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I../include/uapi -I./include/generated/uapi -include ../include/linux/compiler-version.h -include ../include/linux/kconfig.h -D__KERNEL__ --target=x86_64-linux-gnu -fintegrated-as -Werror=unknown-warning-option -Werror=ignored-optimization-argument -Werror=option-ignored -Werror=unused-command-line-argument -fmacro-prefix-map=../= -Werror -D__ASSEMBLY__ -fno-PIE -m64 -I../arch/x86/entry -Iarch/x86/entry -DKBUILD_MODFILE='"arch/x86/entry/entry_64_compat"' -DKBUILD_MODNAME='"entry_64_compat"' -D__KBUILD_MODNAME=kmod_entry_64_compat -c -o arch/x86/entry/entry_64_compat.o ../arch/x86/entry/entry_64_compat.S""" + expected = "../arch/x86/entry/entry_64_compat.S" + self._assert_parsing(cmd, expected) + + # ld command tests + def test_ld(self): + cmd = r'ld -o arch/x86/entry/vdso/vdso64.so.dbg -shared --hash-style=both --build-id=sha1 --no-undefined --eh-frame-hdr -Bsymbolic -z noexecstack -m elf_x86_64 -soname linux-vdso.so.1 -z max-page-size=4096 -T arch/x86/entry/vdso/vdso.lds arch/x86/entry/vdso/vdso-note.o arch/x86/entry/vdso/vclock_gettime.o arch/x86/entry/vdso/vgetcpu.o arch/x86/entry/vdso/vgetrandom.o arch/x86/entry/vdso/vgetrandom-chacha.o; if readelf -rW arch/x86/entry/vdso/vdso64.so.dbg | grep -v _NONE | grep -q " R_\w*_"; then (echo >&2 "arch/x86/entry/vdso/vdso64.so.dbg: dynamic relocations are not supported"; rm -f arch/x86/entry/vdso/vdso64.so.dbg; /bin/false); fi' + expected = "arch/x86/entry/vdso/vdso-note.o arch/x86/entry/vdso/vclock_gettime.o arch/x86/entry/vdso/vgetcpu.o arch/x86/entry/vdso/vgetrandom.o arch/x86/entry/vdso/vgetrandom-chacha.o" + self._assert_parsing(cmd, expected) + + def test_ld_with_env_override(self): + with patch.dict(os.environ, {"LD": "some-tool ld"}): + registry = CommandParserRegistry.create() + cmd = r'ld -o arch/x86/entry/vdso/vdso64.so.dbg -shared --hash-style=both --build-id=sha1 --no-undefined --eh-frame-hdr -Bsymbolic -z noexecstack -m elf_x86_64 -soname linux-vdso.so.1 -z max-page-size=4096 -T arch/x86/entry/vdso/vdso.lds arch/x86/entry/vdso/vdso-note.o arch/x86/entry/vdso/vclock_gettime.o arch/x86/entry/vdso/vgetcpu.o arch/x86/entry/vdso/vgetrandom.o arch/x86/entry/vdso/vgetrandom-chacha.o; if readelf -rW arch/x86/entry/vdso/vdso64.so.dbg | grep -v _NONE | grep -q " R_\w*_"; then (echo >&2 "arch/x86/entry/vdso/vdso64.so.dbg: dynamic relocations are not supported"; rm -f arch/x86/entry/vdso/vdso64.so.dbg; /bin/false); fi' + expected = "arch/x86/entry/vdso/vdso-note.o arch/x86/entry/vdso/vclock_gettime.o arch/x86/entry/vdso/vgetcpu.o arch/x86/entry/vdso/vgetrandom.o arch/x86/entry/vdso/vgetrandom-chacha.o" + self._assert_parsing(cmd, expected, registry) + self._assert_parsing(f"some-tool {cmd}", expected, registry) + + def test_ld_whole_archive(self): + cmd = "ld -m elf_x86_64 -z noexecstack -r -o vmlinux.o --whole-archive vmlinux.a --no-whole-archive --start-group --end-group" + expected = "vmlinux.a" + self._assert_parsing(cmd, expected) + + def test_ld_with_at_symbol(self): + cmd = "ld.lld -m elf_x86_64 -z noexecstack -r -o fs/efivarfs/efivarfs.o @fs/efivarfs/efivarfs.mod ; ./tools/objtool/objtool --hacks=jump_label --hacks=noinstr --hacks=skylake --ibt --orc --retpoline --rethunk --static-call --uaccess --prefix=16 --link --module fs/efivarfs/efivarfs.o" + expected = "@fs/efivarfs/efivarfs.mod" + self._assert_parsing(cmd, expected) + + def test_ld_if_objdump(self): + cmd = """ld -o arch/x86/entry/vdso/vdso64.so.dbg -shared --hash-style=both --build-id=sha1 --eh-frame-hdr -Bsymbolic -z noexecstack -m elf_x86_64 -soname linux-vdso.so.1 --no-undefined -z max-page-size=4096 -T arch/x86/entry/vdso/vdso.lds arch/x86/entry/vdso/vdso-note.o arch/x86/entry/vdso/vclock_gettime.o arch/x86/entry/vdso/vgetcpu.o arch/x86/entry/vdso/vsgx.o && sh ./arch/x86/entry/vdso/checkundef.sh 'nm' 'arch/x86/entry/vdso/vdso64.so.dbg'; if objdump -R arch/x86/entry/vdso/vdso64.so.dbg | grep -E -h "R_X86_64_JUMP_SLOT|R_X86_64_GLOB_DAT|R_X86_64_RELATIVE| R_386_GLOB_DAT|R_386_JMP_SLOT|R_386_RELATIVE"; then (echo >&2 "arch/x86/entry/vdso/vdso64.so.dbg: dynamic relocations are not supported"; rm -f arch/x86/entry/vdso/vdso64.so.dbg; /bin/false); fi""" + expected = "arch/x86/entry/vdso/vdso-note.o arch/x86/entry/vdso/vclock_gettime.o arch/x86/entry/vdso/vgetcpu.o arch/x86/entry/vdso/vsgx.o" + self._assert_parsing(cmd, expected) + + # printf | xargs ar command tests + def test_ar_printf(self): + cmd = 'rm -f built-in.a; printf "./%s " init/built-in.a usr/built-in.a arch/x86/built-in.a arch/x86/boot/startup/built-in.a kernel/built-in.a certs/built-in.a mm/built-in.a fs/built-in.a ipc/built-in.a security/built-in.a crypto/built-in.a block/built-in.a io_uring/built-in.a lib/built-in.a arch/x86/lib/built-in.a drivers/built-in.a sound/built-in.a net/built-in.a virt/built-in.a arch/x86/pci/built-in.a arch/x86/power/built-in.a arch/x86/video/built-in.a | xargs ar cDPrST built-in.a' + expected = "./init/built-in.a ./usr/built-in.a ./arch/x86/built-in.a ./arch/x86/boot/startup/built-in.a ./kernel/built-in.a ./certs/built-in.a ./mm/built-in.a ./fs/built-in.a ./ipc/built-in.a ./security/built-in.a ./crypto/built-in.a ./block/built-in.a ./io_uring/built-in.a ./lib/built-in.a ./arch/x86/lib/built-in.a ./drivers/built-in.a ./sound/built-in.a ./net/built-in.a ./virt/built-in.a ./arch/x86/pci/built-in.a ./arch/x86/power/built-in.a ./arch/x86/video/built-in.a" + self._assert_parsing(cmd, expected) + + def test_ar_printf_nested(self): + cmd = 'rm -f arch/x86/pci/built-in.a; printf "arch/x86/pci/%s " i386.o init.o mmconfig_64.o direct.o mmconfig-shared.o fixup.o acpi.o legacy.o irq.o common.o early.o bus_numa.o amd_bus.o | xargs ar cDPrST arch/x86/pci/built-in.a' + expected = "arch/x86/pci/i386.o arch/x86/pci/init.o arch/x86/pci/mmconfig_64.o arch/x86/pci/direct.o arch/x86/pci/mmconfig-shared.o arch/x86/pci/fixup.o arch/x86/pci/acpi.o arch/x86/pci/legacy.o arch/x86/pci/irq.o arch/x86/pci/common.o arch/x86/pci/early.o arch/x86/pci/bus_numa.o arch/x86/pci/amd_bus.o" + self._assert_parsing(cmd, expected) + + # ar command tests + def test_ar_reordering(self): + cmd = "rm -f vmlinux.a; ar cDPrST vmlinux.a built-in.a lib/lib.a arch/x86/lib/lib.a; ar mPiT $$(ar t vmlinux.a | sed -n 1p) vmlinux.a $$(ar t vmlinux.a | grep -F -f ../scripts/head-object-list.txt)" + expected = "built-in.a lib/lib.a arch/x86/lib/lib.a" + self._assert_parsing(cmd, expected) + + def test_ar_default(self): + cmd = "rm -f lib/lib.a; ar cDPrsT lib/lib.a lib/argv_split.o lib/bug.o lib/buildid.o lib/clz_tab.o lib/cmdline.o lib/cpumask.o lib/ctype.o lib/dec_and_lock.o lib/decompress.o lib/decompress_bunzip2.o lib/decompress_inflate.o lib/decompress_unlz4.o lib/decompress_unlzma.o lib/decompress_unlzo.o lib/decompress_unxz.o lib/decompress_unzstd.o lib/dump_stack.o lib/earlycpio.o lib/extable.o lib/flex_proportions.o lib/idr.o lib/iomem_copy.o lib/irq_regs.o lib/is_single_threaded.o lib/klist.o lib/kobject.o lib/kobject_uevent.o lib/logic_pio.o lib/maple_tree.o lib/memcat_p.o lib/nmi_backtrace.o lib/objpool.o lib/plist.o lib/radix-tree.o lib/ratelimit.o lib/rbtree.o lib/seq_buf.o lib/siphash.o lib/string.o lib/sys_info.o lib/timerqueue.o lib/union_find.o lib/vsprintf.o lib/win_minmax.o lib/xarray.o" + expected = "lib/argv_split.o lib/bug.o lib/buildid.o lib/clz_tab.o lib/cmdline.o lib/cpumask.o lib/ctype.o lib/dec_and_lock.o lib/decompress.o lib/decompress_bunzip2.o lib/decompress_inflate.o lib/decompress_unlz4.o lib/decompress_unlzma.o lib/decompress_unlzo.o lib/decompress_unxz.o lib/decompress_unzstd.o lib/dump_stack.o lib/earlycpio.o lib/extable.o lib/flex_proportions.o lib/idr.o lib/iomem_copy.o lib/irq_regs.o lib/is_single_threaded.o lib/klist.o lib/kobject.o lib/kobject_uevent.o lib/logic_pio.o lib/maple_tree.o lib/memcat_p.o lib/nmi_backtrace.o lib/objpool.o lib/plist.o lib/radix-tree.o lib/ratelimit.o lib/rbtree.o lib/seq_buf.o lib/siphash.o lib/string.o lib/sys_info.o lib/timerqueue.o lib/union_find.o lib/vsprintf.o lib/win_minmax.o lib/xarray.o" + self._assert_parsing(cmd, expected) + + def test_ar_llvm(self): + cmd = "llvm-ar mPiT $$(llvm-ar t vmlinux.a | sed -n 1p) vmlinux.a $$(llvm-ar t vmlinux.a | grep -F -f ../scripts/head-object-list.txt)" + expected = "" + self._assert_parsing(cmd, expected) + + # nm command tests + def test_nm(self): + cmd = """llvm-nm -p --defined-only rust/core.o | awk '$$2~/(T|R|D|B)/ && $$3!~/__(pfx|cfi|odr_asan)/ { printf "EXPORT_SYMBOL_RUST_GPL(%s);\n",$$3 }' > rust/exports_core_generated.h""" + expected = "rust/core.o" + self._assert_parsing(cmd, expected) + + def test_nm_vmlinux(self): + cmd = r"nm vmlinux | sed -n -e 's/^\([0-9a-fA-F]*\) [ABbCDGRSTtVW] \(_text\|__start_rodata\|__bss_start\|_end\)$/#define VO_\2 _AC(0x\1,UL)/p' > arch/x86/boot/voffset.h" + expected = "vmlinux" + self._assert_parsing(cmd, expected) + + # objcopy command tests + def test_objcopy(self): + cmd = "objcopy --remove-section='.rel*' --remove-section=!'.rel*.dyn' vmlinux.unstripped vmlinux" + expected = "vmlinux.unstripped" + self._assert_parsing(cmd, expected) + + def test_objcopy_llvm(self): + cmd = "llvm-objcopy --remove-section='.rel*' --remove-section=!'.rel*.dyn' vmlinux.unstripped vmlinux" + expected = "vmlinux.unstripped" + self._assert_parsing(cmd, expected) + + # strip command tests + def test_strip(self): + cmd = "strip --strip-debug -o drivers/firmware/efi/libstub/mem.stub.o drivers/firmware/efi/libstub/mem.o" + expected = "drivers/firmware/efi/libstub/mem.o" + self._assert_parsing(cmd, expected) + + # cp command tests + def test_cp_truncate(self): + cmd = "cp arch/arm64/boot/Image arch/arm64/boot/vmlinux.bin; truncate -s $$(hexdump -s16 -n4 -e '\"%u\"' arch/arm64/boot/Image) arch/arm64/boot/vmlinux.bin" + expected = "arch/arm64/boot/Image" + self._assert_parsing(cmd, expected) + + # rustc command tests + def test_rustc(self): + cmd = """OBJTREE=/workspace/linux/kernel_build rustc -Zbinary_dep_depinfo=y -Astable_features -Dnon_ascii_idents -Dunsafe_op_in_unsafe_fn -Wmissing_docs -Wrust_2018_idioms -Wclippy::all -Wclippy::as_ptr_cast_mut -Wclippy::as_underscore -Wclippy::cast_lossless -Wclippy::ignored_unit_patterns -Wclippy::mut_mut -Wclippy::needless_bitwise_bool -Aclippy::needless_lifetimes -Wclippy::no_mangle_with_rust_abi -Wclippy::ptr_as_ptr -Wclippy::ptr_cast_constness -Wclippy::ref_as_ptr -Wclippy::undocumented_unsafe_blocks -Wclippy::unnecessary_safety_comment -Wclippy::unnecessary_safety_doc -Wrustdoc::missing_crate_level_docs -Wrustdoc::unescaped_backticks -Cpanic=abort -Cembed-bitcode=n -Clto=n -Cforce-unwind-tables=n -Ccodegen-units=1 -Csymbol-mangling-version=v0 -Crelocation-model=static -Zfunction-sections=n -Wclippy::float_arithmetic --target=./scripts/target.json -Ctarget-feature=-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2 -Zcf-protection=branch -Zno-jump-tables -Ctarget-cpu=x86-64 -Ztune-cpu=generic -Cno-redzone=y -Ccode-model=kernel -Zfunction-return=thunk-extern -Zpatchable-function-entry=16,16 -Copt-level=2 -Cdebug-assertions=n -Coverflow-checks=y -Dwarnings @./include/generated/rustc_cfg --edition=2021 --cfg no_fp_fmt_parse --emit=dep-info=rust/.core.o.d --emit=obj=rust/core.o --emit=metadata=rust/libcore.rmeta --crate-type rlib -L./rust --crate-name core /usr/lib/rust-1.84/lib/rustlib/src/rust/library/core/src/lib.rs --sysroot=/dev/null ;llvm-objcopy --redefine-sym __addsf3=__rust__addsf3 --redefine-sym __eqsf2=__rust__eqsf2 --redefine-sym __extendsfdf2=__rust__extendsfdf2 --redefine-sym __gesf2=__rust__gesf2 --redefine-sym __lesf2=__rust__lesf2 --redefine-sym __ltsf2=__rust__ltsf2 --redefine-sym __mulsf3=__rust__mulsf3 --redefine-sym __nesf2=__rust__nesf2 --redefine-sym __truncdfsf2=__rust__truncdfsf2 --redefine-sym __unordsf2=__rust__unordsf2 --redefine-sym __adddf3=__rust__adddf3 --redefine-sym __eqdf2=__rust__eqdf2 --redefine-sym __ledf2=__rust__ledf2 --redefine-sym __ltdf2=__rust__ltdf2 --redefine-sym __muldf3=__rust__muldf3 --redefine-sym __unorddf2=__rust__unorddf2 --redefine-sym __muloti4=__rust__muloti4 --redefine-sym __multi3=__rust__multi3 --redefine-sym __udivmodti4=__rust__udivmodti4 --redefine-sym __udivti3=__rust__udivti3 --redefine-sym __umodti3=__rust__umodti3 rust/core.o""" + expected = "/usr/lib/rust-1.84/lib/rustlib/src/rust/library/core/src/lib.rs rust/core.o" + self._assert_parsing(cmd, expected) + + # rustdoc command tests + def test_rustdoc(self): + cmd = """OBJTREE=/workspace/linux/kernel_build rustdoc --test --edition=2021 -Zbinary_dep_depinfo=y -Astable_features -Dnon_ascii_idents -Dunsafe_op_in_unsafe_fn -Wmissing_docs -Wrust_2018_idioms -Wunreachable_pub -Wclippy::all -Wclippy::as_ptr_cast_mut -Wclippy::as_underscore -Wclippy::cast_lossless -Wclippy::ignored_unit_patterns -Wclippy::mut_mut -Wclippy::needless_bitwise_bool -Aclippy::needless_lifetimes -Wclippy::no_mangle_with_rust_abi -Wclippy::ptr_as_ptr -Wclippy::ptr_cast_constness -Wclippy::ref_as_ptr -Wclippy::undocumented_unsafe_blocks -Wclippy::unnecessary_safety_comment -Wclippy::unnecessary_safety_doc -Wrustdoc::missing_crate_level_docs -Wrustdoc::unescaped_backticks -Cpanic=abort -Cembed-bitcode=n -Clto=n -Cforce-unwind-tables=n -Ccodegen-units=1 -Csymbol-mangling-version=v0 -Crelocation-model=static -Zfunction-sections=n -Wclippy::float_arithmetic --target=aarch64-unknown-none -Ctarget-feature="-neon" -Cforce-unwind-tables=n -Zbranch-protection=pac-ret -Copt-level=2 -Cdebug-assertions=y -Coverflow-checks=y -Dwarnings -Cforce-frame-pointers=y -Zsanitizer=kernel-address -Zsanitizer-recover=kernel-address -Cllvm-args=-asan-mapping-offset=0xdfff800000000000 -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 -Cllvm-args=-sanitizer-coverage-trace-pc -Cllvm-args=-sanitizer-coverage-trace-compares @./include/generated/rustc_cfg -L./rust --extern ffi --extern pin_init --extern kernel --extern build_error --extern macros --extern bindings --extern uapi --no-run --crate-name kernel -Zunstable-options --sysroot=/dev/null --test-builder ./scripts/rustdoc_test_builder ../rust/kernel/lib.rs >/dev/null""" + expected = "../rust/kernel/lib.rs" + self._assert_parsing(cmd, expected) + + def test_rustdoc_test_gen(self): + cmd = "./scripts/rustdoc_test_gen" + expected = "" + self._assert_parsing(cmd, expected) + + # flex command tests + def test_flex(self): + cmd = "flex -oscripts/kconfig/lexer.lex.c -L ../scripts/kconfig/lexer.l" + expected = "../scripts/kconfig/lexer.l" + self._assert_parsing(cmd, expected) + + # bison command tests + def test_bison(self): + cmd = "bison -o scripts/kconfig/parser.tab.c --defines=scripts/kconfig/parser.tab.h -t -l ../scripts/kconfig/parser.y" + expected = "../scripts/kconfig/parser.y" + self._assert_parsing(cmd, expected) + + # bindgen command tests + def test_bindgen(self): + cmd = ( + "bindgen ../rust/bindings/bindings_helper.h " + "--blocklist-type __kernel_s?size_t --blocklist-type __kernel_ptrdiff_t " + "--opaque-type xregs_state --opaque-type desc_struct --no-doc-comments " + "--rust-target 1.68 --use-core --with-derive-default -o rust/bindings/bindings_generated.rs " + "-- -Wp,-MMD,rust/bindings/.bindings_generated.rs.d -nostdinc -I../arch/x86/include " + "-include ../include/linux/compiler-version.h -D__KERNEL__ -fintegrated-as -fno-builtin -DMODULE; " + "sed -Ei 's/pub const RUST_CONST_HELPER_([a-zA-Z0-9_]*)/pub const \\1/g' rust/bindings/bindings_generated.rs" + ) + expected = "../rust/bindings/bindings_helper.h ../include/linux/compiler-version.h" + self._assert_parsing(cmd, expected) + + # perl command tests + def test_perl(self): + cmd = "perl ../lib/crypto/x86/poly1305-x86_64-cryptogams.pl > lib/crypto/x86/poly1305-x86_64-cryptogams.S" + expected = "../lib/crypto/x86/poly1305-x86_64-cryptogams.pl" + self._assert_parsing(cmd, expected) + + # link-vmlinux.sh command tests + def test_link_vmlinux(self): + cmd = '../scripts/link-vmlinux.sh "ld" "-m elf_x86_64 -z noexecstack" "-z max-page-size=0x200000 --build-id=sha1 --orphan-handling=error --emit-relocs --discard-none" "vmlinux.unstripped"; true' + expected = "vmlinux.a" + self._assert_parsing(cmd, expected) + + def test_link_vmlinux_postlink(self): + cmd = '../scripts/link-vmlinux.sh "ld" "-m elf_x86_64 -z noexecstack --no-warn-rwx-segments" "--emit-relocs --discard-none -z max-page-size=0x200000 --build-id=sha1 -X --orphan-handling=error"; make -f ../arch/x86/Makefile.postlink vmlinux' + expected = "vmlinux.a" + self._assert_parsing(cmd, expected) + + # syscallhdr.sh command tests + def test_syscallhdr(self): + cmd = "sh ../scripts/syscallhdr.sh --abis common,64 --emit-nr ../arch/x86/entry/syscalls/syscall_64.tbl arch/x86/include/generated/uapi/asm/unistd_64.h" + expected = "../arch/x86/entry/syscalls/syscall_64.tbl" + self._assert_parsing(cmd, expected) + + # syscalltbl.sh command tests + def test_syscalltbl(self): + cmd = "sh ../scripts/syscalltbl.sh --abis common,64 ../arch/x86/entry/syscalls/syscall_64.tbl arch/x86/include/generated/asm/syscalls_64.h" + expected = "../arch/x86/entry/syscalls/syscall_64.tbl" + self._assert_parsing(cmd, expected) + + # mkcapflags.sh command tests + def test_mkcapflags(self): + cmd = "sh ../arch/x86/kernel/cpu/mkcapflags.sh arch/x86/kernel/cpu/capflags.c ../arch/x86/kernel/cpu/../../include/asm/cpufeatures.h ../arch/x86/kernel/cpu/../../include/asm/vmxfeatures.h ../arch/x86/kernel/cpu/mkcapflags.sh FORCE" + expected = "../arch/x86/kernel/cpu/../../include/asm/cpufeatures.h ../arch/x86/kernel/cpu/../../include/asm/vmxfeatures.h" + self._assert_parsing(cmd, expected) + + # orc_hash.sh command tests + def test_orc_hash(self): + cmd = "mkdir -p arch/x86/include/generated/asm/; sh ../scripts/orc_hash.sh < ../arch/x86/include/asm/orc_types.h > arch/x86/include/generated/asm/orc_hash.h" + expected = "../arch/x86/include/asm/orc_types.h" + self._assert_parsing(cmd, expected) + + # xen-hypercalls.sh command tests + def test_xen_hypercalls(self): + cmd = "sh '../scripts/xen-hypercalls.sh' arch/x86/include/generated/asm/xen-hypercalls.h ../include/xen/interface/xen-mca.h ../include/xen/interface/xen.h ../include/xen/interface/xenpmu.h" + expected = "../include/xen/interface/xen-mca.h ../include/xen/interface/xen.h ../include/xen/interface/xenpmu.h" + self._assert_parsing(cmd, expected) + + # gen_initramfs.sh command tests + def test_gen_initramfs(self): + cmd = "sh ../usr/gen_initramfs.sh -o usr/initramfs_data.cpio -l usr/.initramfs_data.cpio.d ../usr/default_cpio_list" + expected = "../usr/default_cpio_list" + self._assert_parsing(cmd, expected) + + # mkuboot.sh command tests + def test_mkuboot(self): + cmd = "bash ../scripts/mkuboot.sh -A arm -O linux -C none -T kernel -a 0x8000 -e 0x8000 -n 'Linux-6.15.0' -d arch/arm/boot/zImage arch/arm/boot/uImage" + expected = "arch/arm/boot/zImage" + self._assert_parsing(cmd, expected) + + # syscallnr.sh command tests + def test_syscallnr(self): + cmd = "sh ../arch/arm/tools/syscallnr.sh ../arch/arm/tools/syscall.tbl arch/arm/include/generated/asm/unistd-nr.h" + expected = "../arch/arm/tools/syscall.tbl" + self._assert_parsing(cmd, expected) + + # gen-kernel-hwcaps.sh command tests + def test_gen_kernel_hwcaps(self): + cmd = "/bin/sh -e ../arch/arm64/tools/gen-kernel-hwcaps.sh ../arch/arm64/include/uapi/asm/hwcap.h > arch/arm64/include/generated/asm/kernel-hwcap.h" + expected = "../arch/arm64/include/uapi/asm/hwcap.h" + self._assert_parsing(cmd, expected) + + # vdso2c command tests + def test_vdso2c(self): + cmd = "arch/x86/entry/vdso/vdso2c arch/x86/entry/vdso/vdso64.so.dbg arch/x86/entry/vdso/vdso64.so arch/x86/entry/vdso/vdso-image-64.c" + expected = "arch/x86/entry/vdso/vdso64.so.dbg arch/x86/entry/vdso/vdso64.so" + self._assert_parsing(cmd, expected) + + # vdsomunge command tests + def test_vdsomunge(self): + cmd = "arch/arm64/kernel/vdso32/../../../arm/vdso/vdsomunge arch/arm64/kernel/vdso32/vdso.so.raw arch/arm64/kernel/vdso32/vdso32.so.dbg" + expected = "arch/arm64/kernel/vdso32/vdso.so.raw" + self._assert_parsing(cmd, expected) + + # mkpiggy command tests + def test_mkpiggy(self): + cmd = "arch/x86/boot/compressed/mkpiggy arch/x86/boot/compressed/vmlinux.bin.gz > arch/x86/boot/compressed/piggy.S" + expected = "arch/x86/boot/compressed/vmlinux.bin.gz" + self._assert_parsing(cmd, expected) + + # relocs command tests + def test_relocs(self): + cmd = "arch/x86/tools/relocs vmlinux.unstripped > arch/x86/boot/compressed/vmlinux.relocs;arch/x86/tools/relocs --abs-relocs vmlinux.unstripped" + expected = "vmlinux.unstripped" + self._assert_parsing(cmd, expected) + + def test_relocs_with_realmode(self): + cmd = ( + "arch/x86/tools/relocs --realmode arch/x86/realmode/rm/realmode.elf > arch/x86/realmode/rm/realmode.relocs" + ) + expected = "arch/x86/realmode/rm/realmode.elf" + self._assert_parsing(cmd, expected) + + # mk_elfconfig command tests + def test_mk_elfconfig(self): + cmd = "scripts/mod/mk_elfconfig < scripts/mod/empty.o > scripts/mod/elfconfig.h" + expected = "scripts/mod/empty.o" + self._assert_parsing(cmd, expected) + + # tools/build command tests + def test_build(self): + cmd = "arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage" + expected = "arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h" + self._assert_parsing(cmd, expected) + + # extract-cert command tests + def test_extract_cert(self): + cmd = 'certs/extract-cert "" certs/signing_key.x509' + expected = "" + self._assert_parsing(cmd, expected) + + # dtc command tests + def test_dtc_cat(self): + cmd = "./scripts/dtc/dtc -o drivers/of/empty_root.dtb -b 0 -i../drivers/of/ -i../scripts/dtc/include-prefixes -Wno-unique_unit_address -Wno-unit_address_vs_reg -Wno-avoid_unnecessary_addr_size -Wno-alias_paths -Wno-graph_child_address -Wno-simple_bus_reg -d drivers/of/.empty_root.dtb.d.dtc.tmp drivers/of/.empty_root.dtb.dts.tmp ; cat drivers/of/.empty_root.dtb.d.pre.tmp drivers/of/.empty_root.dtb.d.dtc.tmp > drivers/of/.empty_root.dtb.d" + expected = "drivers/of/.empty_root.dtb.dts.tmp drivers/of/.empty_root.dtb.d.pre.tmp drivers/of/.empty_root.dtb.d.dtc.tmp" + self._assert_parsing(cmd, expected) + + # pnmtologo command tests + def test_pnmtologo(self): + cmd = "drivers/video/logo/pnmtologo -t clut224 -n logo_linux_clut224 -o drivers/video/logo/logo_linux_clut224.c ../drivers/video/logo/logo_linux_clut224.ppm" + expected = "../drivers/video/logo/logo_linux_clut224.ppm" + self._assert_parsing(cmd, expected) + + # relacheck command tests + def test_relacheck(self): + cmd = "arch/arm64/kernel/pi/relacheck arch/arm64/kernel/pi/idreg-override.pi.o arch/arm64/kernel/pi/idreg-override.o" + expected = "arch/arm64/kernel/pi/idreg-override.pi.o" + self._assert_parsing(cmd, expected) + + # gen-hyprel command tests + def test_gen_hyprel(self): + cmd = "arch/arm64/kvm/hyp/nvhe/gen-hyprel arch/arm64/kvm/hyp/nvhe/kvm_nvhe.tmp.o > arch/arm64/kvm/hyp/nvhe/hyp-reloc.S" + expected = "arch/arm64/kvm/hyp/nvhe/kvm_nvhe.tmp.o" + self._assert_parsing(cmd, expected) + + # mkregtable command tests + def test_mkregtable(self): + cmd = "drivers/gpu/drm/radeon/mkregtable ../drivers/gpu/drm/radeon/reg_srcs/r100 > drivers/gpu/drm/radeon/r100_reg_safe.h" + expected = "../drivers/gpu/drm/radeon/reg_srcs/r100" + self._assert_parsing(cmd, expected) + + # genheaders command tests + def test_genheaders(self): + cmd = "security/selinux/genheaders security/selinux/flask.h security/selinux/av_permissions.h" + expected = "" + self._assert_parsing(cmd, expected) + + # mkcpustr command tests + def test_mkcpustr(self): + cmd = "arch/x86/boot/mkcpustr > arch/x86/boot/cpustr.h" + expected = "" + self._assert_parsing(cmd, expected) + + # polgen command tests + def test_polgen(self): + cmd = "scripts/ipe/polgen/polgen security/ipe/boot_policy.c" + expected = "" + self._assert_parsing(cmd, expected) + + # gen_header.py command tests + def test_gen_header(self): + cmd = "mkdir -p drivers/gpu/drm/msm/generated && python3 ../drivers/gpu/drm/msm/registers/gen_header.py --no-validate --rnn ../drivers/gpu/drm/msm/registers --xml ../drivers/gpu/drm/msm/registers/adreno/a2xx.xml c-defines > drivers/gpu/drm/msm/generated/a2xx.xml.h" + expected = "../drivers/gpu/drm/msm/registers/adreno/a2xx.xml" + self._assert_parsing(cmd, expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/sbom/tests/spdx_graph/__init__.py b/scripts/sbom/tests/spdx_graph/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/sbom/tests/spdx_graph/test_kernel_file.py b/scripts/sbom/tests/spdx_graph/test_kernel_file.py new file mode 100644 index 000000000..35a63a768 --- /dev/null +++ b/scripts/sbom/tests/spdx_graph/test_kernel_file.py @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Copyright (C) 2025 TNG Technology Consulting GmbH + +import unittest +from pathlib import Path +import tempfile +from sbom.spdx_graph.kernel_file import _parse_spdx_license_identifier # type: ignore + + +class TestKernelFile(unittest.TestCase): + def setUp(self): + self.tmpdir = tempfile.TemporaryDirectory() + self.src_tree = Path(self.tmpdir.name) + + def tearDown(self): + self.tmpdir.cleanup() + + def test_parse_spdx_license_identifier(self): + # REUSE-IgnoreStart + test_cases: list[tuple[str, str | None]] = [ + ("/* SPDX-License-Identifier: MIT*/", "MIT"), + ("// SPDX-License-Identifier: GPL-2.0-only", "GPL-2.0-only"), + ("# SPDX-License-Identifier: GPL-2.0-only", "GPL-2.0-only"), + ("#!/bin/bash\n# SPDX-License-Identifier: GPL-2.0-only", "GPL-2.0-only"), + ("/* SPDX-License-Identifier: GPL-2.0-or-later OR MIT */", "GPL-2.0-or-later OR MIT"), + ("/* SPDX-License-Identifier: Apache-2.0 */\n extra text", "Apache-2.0"), + ("<!-- SPDX-License-Identifier: GPL-2.0 -->", "GPL-2.0"), + ("int main() { return 0; }", None), + ] + # REUSE-IgnoreEnd + + for i, (file_content, expected_identifier) in enumerate(test_cases): + file_path = self.src_tree / f"file_{i}.c" + file_path.write_text(file_content) + self.assertEqual(_parse_spdx_license_identifier(str(file_path)), expected_identifier) diff --git a/scripts/selinux/Makefile b/scripts/selinux/Makefile new file mode 100644 index 000000000..4b1308fa5 --- /dev/null +++ b/scripts/selinux/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +subdir-y := mdp diff --git a/scripts/selinux/README b/scripts/selinux/README new file mode 100644 index 000000000..5ba679c5b --- /dev/null +++ b/scripts/selinux/README @@ -0,0 +1,2 @@ +Please see Documentation/admin-guide/LSM/SELinux.rst for information on +installing a dummy SELinux policy. diff --git a/scripts/selinux/install_policy.sh b/scripts/selinux/install_policy.sh new file mode 100755 index 000000000..77368a73f --- /dev/null +++ b/scripts/selinux/install_policy.sh @@ -0,0 +1,81 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +set -e +if [ `id -u` -ne 0 ]; then + echo "$0: must be root to install the selinux policy" + exit 1 +fi + +SF=`which setfiles` || { + echo "Could not find setfiles" + echo "Do you have policycoreutils installed?" + exit 1 +} + +CP=`which checkpolicy` || { + echo "Could not find checkpolicy" + echo "Do you have checkpolicy installed?" + exit 1 +} +VERS=`$CP -V | awk '{print $1}'` + +ENABLED=`which selinuxenabled` || { + echo "Could not find selinuxenabled" + echo "Do you have libselinux-utils installed?" + exit 1 +} + +if selinuxenabled; then + echo "SELinux is already enabled" + echo "This prevents safely relabeling all files." + echo "Boot with selinux=0 on the kernel command-line." + exit 1 +fi + +cd mdp +./mdp -m policy.conf file_contexts +$CP -U allow -M -o policy.$VERS policy.conf + +mkdir -p /etc/selinux/dummy/policy +mkdir -p /etc/selinux/dummy/contexts/files + +echo "__default__:user_u:s0" > /etc/selinux/dummy/seusers +echo "base_r:base_t:s0" > /etc/selinux/dummy/contexts/failsafe_context +echo "base_r:base_t:s0 base_r:base_t:s0" > /etc/selinux/dummy/default_contexts +cat > /etc/selinux/dummy/contexts/x_contexts <<EOF +client * user_u:base_r:base_t:s0 +property * user_u:object_r:base_t:s0 +extension * user_u:object_r:base_t:s0 +selection * user_u:object_r:base_t:s0 +event * user_u:object_r:base_t:s0 +EOF +touch /etc/selinux/dummy/contexts/virtual_domain_context +touch /etc/selinux/dummy/contexts/virtual_image_context + +cp file_contexts /etc/selinux/dummy/contexts/files +cp dbus_contexts /etc/selinux/dummy/contexts +cp policy.$VERS /etc/selinux/dummy/policy +FC_FILE=/etc/selinux/dummy/contexts/files/file_contexts + +if [ ! -d /etc/selinux ]; then + mkdir -p /etc/selinux +fi +if [ -f /etc/selinux/config ]; then + echo "/etc/selinux/config exists, moving to /etc/selinux/config.bak." + mv /etc/selinux/config /etc/selinux/config.bak +fi +echo "Creating new /etc/selinux/config for dummy policy." +cat > /etc/selinux/config << EOF +SELINUX=permissive +SELINUXTYPE=dummy +EOF + +cd /etc/selinux/dummy/contexts/files +$SF -F file_contexts / + +mounts=`cat /proc/$$/mounts | \ + grep -E "ext[234]|jfs|xfs|jffs2|gfs2|btrfs|f2fs|ocfs2" | \ + awk '{ print $2 '}` +$SF -F file_contexts $mounts + +echo "-F" > /.autorelabel diff --git a/scripts/selinux/mdp/.gitignore b/scripts/selinux/mdp/.gitignore new file mode 100644 index 000000000..a7482287e --- /dev/null +++ b/scripts/selinux/mdp/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +mdp diff --git a/scripts/selinux/mdp/Makefile b/scripts/selinux/mdp/Makefile new file mode 100644 index 000000000..673782e32 --- /dev/null +++ b/scripts/selinux/mdp/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +hostprogs-always-y += mdp +HOST_EXTRACFLAGS += \ + -I$(srctree)/include \ + -I$(srctree)/security/selinux/include -I$(objtree)/include + +clean-files := policy.* file_contexts diff --git a/scripts/selinux/mdp/dbus_contexts b/scripts/selinux/mdp/dbus_contexts new file mode 100644 index 000000000..116e684f9 --- /dev/null +++ b/scripts/selinux/mdp/dbus_contexts @@ -0,0 +1,6 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <selinux> + </selinux> +</busconfig> diff --git a/scripts/selinux/mdp/mdp.c b/scripts/selinux/mdp/mdp.c new file mode 100644 index 000000000..ea7fbe595 --- /dev/null +++ b/scripts/selinux/mdp/mdp.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * mdp - make dummy policy + * + * When pointed at a kernel tree, builds a dummy policy for that kernel + * with exactly one type with full rights to itself. + * + * Copyright (C) IBM Corporation, 2006 + * + * Authors: Serge E. Hallyn <serue@us.ibm.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <linux/kconfig.h> + +static void usage(char *name) +{ + printf("usage: %s [-m] policy_file context_file\n", name); + exit(1); +} + +/* Class/perm mapping support */ +struct security_class_mapping { + const char *name; + const char *perms[sizeof(unsigned) * 8 + 1]; +}; + +#include "classmap.h" +#include "initial_sid_to_string.h" +#include "policycap_names.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +int main(int argc, char *argv[]) +{ + int i, j, mls = 0; + int initial_sid_to_string_len; + char **arg, *polout, *ctxout; + + FILE *fout; + + if (argc < 3) + usage(argv[0]); + arg = argv+1; + if (argc==4 && strcmp(argv[1], "-m") == 0) { + mls = 1; + arg++; + } + polout = *arg++; + ctxout = *arg; + + fout = fopen(polout, "w"); + if (!fout) { + printf("Could not open %s for writing\n", polout); + usage(argv[0]); + } + + /* print out the classes */ + for (i = 0; secclass_map[i].name; i++) + fprintf(fout, "class %s\n", secclass_map[i].name); + fprintf(fout, "\n"); + + initial_sid_to_string_len = sizeof(initial_sid_to_string) / sizeof (char *); + /* print out the sids */ + for (i = 1; i < initial_sid_to_string_len; i++) { + const char *name = initial_sid_to_string[i]; + + if (name) + fprintf(fout, "sid %s\n", name); + else + fprintf(fout, "sid unused%d\n", i); + } + fprintf(fout, "\n"); + + /* print out the class permissions */ + for (i = 0; secclass_map[i].name; i++) { + const struct security_class_mapping *map = &secclass_map[i]; + fprintf(fout, "class %s\n", map->name); + fprintf(fout, "{\n"); + for (j = 0; map->perms[j]; j++) + fprintf(fout, "\t%s\n", map->perms[j]); + fprintf(fout, "}\n\n"); + } + fprintf(fout, "\n"); + + /* print out mls declarations and constraints */ + if (mls) { + fprintf(fout, "sensitivity s0;\n"); + fprintf(fout, "sensitivity s1;\n"); + fprintf(fout, "dominance { s0 s1 }\n"); + fprintf(fout, "category c0;\n"); + fprintf(fout, "category c1;\n"); + fprintf(fout, "level s0:c0.c1;\n"); + fprintf(fout, "level s1:c0.c1;\n"); +#define SYSTEMLOW "s0" +#define SYSTEMHIGH "s1:c0.c1" + for (i = 0; secclass_map[i].name; i++) { + const struct security_class_mapping *map = &secclass_map[i]; + + fprintf(fout, "mlsconstrain %s {\n", map->name); + for (j = 0; map->perms[j]; j++) + fprintf(fout, "\t%s\n", map->perms[j]); + /* + * This requires all subjects and objects to be + * single-level (l2 eq h2), and that the subject + * level dominate the object level (h1 dom h2) + * in order to have any permissions to it. + */ + fprintf(fout, "} (l2 eq h2 and h1 dom h2);\n\n"); + } + } + + /* enable all policy capabilities */ + for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++) + fprintf(fout, "policycap %s;\n", selinux_policycap_names[i]); + + /* types, roles, and allows */ + fprintf(fout, "type base_t;\n"); + fprintf(fout, "role base_r;\n"); + fprintf(fout, "role base_r types { base_t };\n"); + for (i = 0; secclass_map[i].name; i++) + fprintf(fout, "allow base_t base_t:%s *;\n", + secclass_map[i].name); + fprintf(fout, "user user_u roles { base_r }"); + if (mls) + fprintf(fout, " level %s range %s - %s", SYSTEMLOW, + SYSTEMLOW, SYSTEMHIGH); + fprintf(fout, ";\n"); + +#define SUBJUSERROLETYPE "user_u:base_r:base_t" +#define OBJUSERROLETYPE "user_u:object_r:base_t" + + /* default sids */ + for (i = 1; i < initial_sid_to_string_len; i++) { + const char *name = initial_sid_to_string[i]; + + if (name) + fprintf(fout, "sid %s ", name); + else + fprintf(fout, "sid unused%d\n", i); + fprintf(fout, SUBJUSERROLETYPE "%s\n", + mls ? ":" SYSTEMLOW : ""); + } + fprintf(fout, "\n"); + +#define FS_USE(behavior, fstype) \ + fprintf(fout, "fs_use_%s %s " OBJUSERROLETYPE "%s;\n", \ + behavior, fstype, mls ? ":" SYSTEMLOW : "") + + /* + * Filesystems whose inode labels can be fetched via getxattr. + */ +#ifdef CONFIG_EXT2_FS_SECURITY + FS_USE("xattr", "ext2"); +#endif +#ifdef CONFIG_EXT4_FS_SECURITY +#ifdef CONFIG_EXT4_USE_FOR_EXT2 + FS_USE("xattr", "ext2"); +#endif + FS_USE("xattr", "ext3"); + FS_USE("xattr", "ext4"); +#endif +#ifdef CONFIG_JFS_SECURITY + FS_USE("xattr", "jfs"); +#endif +#ifdef CONFIG_JFFS2_FS_SECURITY + FS_USE("xattr", "jffs2"); +#endif +#ifdef CONFIG_XFS_FS + FS_USE("xattr", "xfs"); +#endif +#ifdef CONFIG_GFS2_FS + FS_USE("xattr", "gfs2"); +#endif +#ifdef CONFIG_BTRFS_FS + FS_USE("xattr", "btrfs"); +#endif +#ifdef CONFIG_F2FS_FS_SECURITY + FS_USE("xattr", "f2fs"); +#endif +#ifdef CONFIG_OCFS2_FS + FS_USE("xattr", "ocsfs2"); +#endif +#ifdef CONFIG_OVERLAY_FS + FS_USE("xattr", "overlay"); +#endif +#ifdef CONFIG_SQUASHFS_XATTR + FS_USE("xattr", "squashfs"); +#endif + + /* + * Filesystems whose inodes are labeled from allocating task. + */ + FS_USE("task", "pipefs"); + FS_USE("task", "sockfs"); + + /* + * Filesystems whose inode labels are computed from both + * the allocating task and the superblock label. + */ +#ifdef CONFIG_UNIX98_PTYS + FS_USE("trans", "devpts"); +#endif +#ifdef CONFIG_HUGETLBFS + FS_USE("trans", "hugetlbfs"); +#endif +#ifdef CONFIG_TMPFS + FS_USE("trans", "tmpfs"); +#endif +#ifdef CONFIG_DEVTMPFS + FS_USE("trans", "devtmpfs"); +#endif +#ifdef CONFIG_POSIX_MQUEUE + FS_USE("trans", "mqueue"); +#endif + +#define GENFSCON(fstype, prefix) \ + fprintf(fout, "genfscon %s %s " OBJUSERROLETYPE "%s\n", \ + fstype, prefix, mls ? ":" SYSTEMLOW : "") + + /* + * Filesystems whose inodes are labeled from path prefix match + * relative to the filesystem root. Depending on the filesystem, + * only a single label for all inodes may be supported. Here + * we list the filesystem types for which per-file labeling is + * supported using genfscon; any other filesystem type can also + * be added by only with a single entry for all of its inodes. + */ +#ifdef CONFIG_PROC_FS + GENFSCON("proc", "/"); +#endif +#ifdef CONFIG_SECURITY_SELINUX + GENFSCON("selinuxfs", "/"); +#endif +#ifdef CONFIG_SYSFS + GENFSCON("sysfs", "/"); +#endif +#ifdef CONFIG_DEBUG_FS + GENFSCON("debugfs", "/"); +#endif +#ifdef CONFIG_TRACING + GENFSCON("tracefs", "/"); +#endif +#ifdef CONFIG_PSTORE + GENFSCON("pstore", "/"); +#endif + GENFSCON("cgroup", "/"); + GENFSCON("cgroup2", "/"); + + fclose(fout); + + fout = fopen(ctxout, "w"); + if (!fout) { + printf("Wrote policy, but cannot open %s for writing\n", ctxout); + usage(argv[0]); + } + fprintf(fout, "/ " OBJUSERROLETYPE "%s\n", mls ? ":" SYSTEMLOW : ""); + fprintf(fout, "/.* " OBJUSERROLETYPE "%s\n", mls ? ":" SYSTEMLOW : ""); + fclose(fout); + + return 0; +} diff --git a/scripts/setlocalversion b/scripts/setlocalversion new file mode 100755 index 000000000..28169d7e1 --- /dev/null +++ b/scripts/setlocalversion @@ -0,0 +1,212 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# This scripts adds local version information from the version +# control system git. +# +# If something goes wrong, send a mail the kernel build mailinglist +# (see MAINTAINERS) and CC Nico Schottelius +# <nico-linuxsetlocalversion -at- schottelius.org>. +# +# + +set -e + +usage() { + echo "Usage: $0 [--no-local] [srctree]" >&2 + exit 1 +} + +no_local=false +if test "$1" = "--no-local"; then + no_local=true + shift +fi + +srctree=. +if test $# -gt 0; then + srctree=$1 + shift +fi +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 + local no_dirty=false + local tag + + while [ $# -gt 0 ]; + do + case "$1" in + --short) + short=true;; + --no-dirty) + no_dirty=true;; + esac + shift + done + + cd "$srctree" + + if test -n "$(git rev-parse --show-cdup 2>/dev/null)"; then + return + fi + + if ! head=$(git rev-parse --verify HEAD 2>/dev/null); then + return + fi + + # mainline kernel: 6.2.0-rc5 -> v6.2-rc5 + # 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. + 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 "${count}" ] && [ -n "${file_localversion}" ]; then + try_tag "${version_tag}${file_localversion}" + fi + + # Otherwise, default to the annotated tag derived from KERNELVERSION. + 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 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 + if $short; then + echo "+" + return + fi + + # If we are past the tagged commit, we pretty print it. + # (like 6.1.0-14595-g292a089d78d3) + if [ -n "${count}" ]; then + printf "%s%05d" "-" "${count}" + fi + + # Add -g and exactly 12 hex chars. + printf '%s%.12s' -g "$head" + fi + + if ${no_dirty}; then + return + fi + + # Check for uncommitted changes. + # This script must avoid any write attempt to the source tree, which + # might be read-only. + # You cannot use 'git describe --dirty' because it tries to create + # .git/index.lock . + # First, with git-status, but --no-optional-locks is only supported in + # git >= 2.14, so fall back to git-diff-index if it fails. Note that + # git-diff-index does not refresh the index, so it may give misleading + # results. + # See git-update-index(1), git-diff-index(1), and git-status(1). + if { + git --no-optional-locks status -uno --porcelain 2>/dev/null || + git diff-index --name-only HEAD + } | read dummy; then + printf '%s' -dirty + fi +} + +collect_files() +{ + local file res= + + for file; do + case "$file" in + *\~*) + continue + ;; + esac + if test -e "$file"; then + res="$res$(cat "$file")" + fi + done + echo "$res" +} + +if [ -z "${KERNELVERSION}" ]; then + echo "KERNELVERSION is not set" >&2 + exit 1 +fi + +# localversion* files in the build and source directory +file_localversion="$(collect_files localversion*)" +if test ! "$srctree" -ef .; then + file_localversion="${file_localversion}$(collect_files "$srctree"/localversion*)" +fi + +if ${no_local}; then + echo "${KERNELVERSION}$(scm_version --no-dirty)" + exit 0 +fi + +if ! test -e include/config/auto.conf; then + echo "Error: kernelrelease not valid - run 'make prepare' to update it" >&2 + exit 1 +fi + +# version string from CONFIG_LOCALVERSION +config_localversion=$(sed -n 's/^CONFIG_LOCALVERSION=\(.*\)$/\1/p' include/config/auto.conf) + +# scm version string if not at the kernel version tag or at the file_localversion +if grep -q "^CONFIG_LOCALVERSION_AUTO=y$" include/config/auto.conf; then + # full scm version string + scm_version="$(scm_version)" +elif [ "${LOCALVERSION+set}" != "set" ]; then + # If the variable LOCALVERSION is not set, append a plus + # sign if the repository is not in a clean annotated or + # signed tagged state (as git describe only looks at signed + # or annotated tags - git tag -a/-s). + # + # If the variable LOCALVERSION is set (including being set + # to an empty string), we don't want to append a plus sign. + scm_version="$(scm_version --short)" +fi + +echo "${KERNELVERSION}${file_localversion}${config_localversion}${LOCALVERSION}${scm_version}" diff --git a/scripts/show_delta b/scripts/show_delta new file mode 100755 index 000000000..3755b6c6e --- /dev/null +++ b/scripts/show_delta @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# +# show_deltas: Read list of printk messages instrumented with +# time data, and format with time deltas. +# +# Also, you can show the times relative to a fixed point. +# +# Copyright 2003 Sony Corporation +# + +import sys +import string + +def usage(): + print ("""usage: show_delta [<options>] <filename> + +This program parses the output from a set of printk message lines which +have time data prefixed because the CONFIG_PRINTK_TIME option is set, or +the kernel command line option "time" is specified. When run with no +options, the time information is converted to show the time delta between +each printk line and the next. When run with the '-b' option, all times +are relative to a single (base) point in time. + +Options: + -h Show this usage help. + -b <base> Specify a base for time references. + <base> can be a number or a string. + If it is a string, the first message line + which matches (at the beginning of the + line) is used as the time reference. + +ex: $ dmesg >timefile + $ show_delta -b NET4 timefile + +will show times relative to the line in the kernel output +starting with "NET4". +""") + sys.exit(1) + +# returns a tuple containing the seconds and text for each message line +# seconds is returned as a float +# raise an exception if no timing data was found +def get_time(line): + if line[0]!="[": + raise ValueError + + # split on closing bracket + (time_str, rest) = string.split(line[1:],']',1) + time = string.atof(time_str) + + #print "time=", time + return (time, rest) + + +# average line looks like: +# [ 0.084282] VFS: Mounted root (romfs filesystem) readonly +# time data is expressed in seconds.useconds, +# convert_line adds a delta for each line +last_time = 0.0 +def convert_line(line, base_time): + global last_time + + try: + (time, rest) = get_time(line) + except: + # if any problem parsing time, don't convert anything + return line + + if base_time: + # show time from base + delta = time - base_time + else: + # just show time from last line + delta = time - last_time + last_time = time + + return ("[%5.6f < %5.6f >]" % (time, delta)) + rest + +def main(): + base_str = "" + filein = "" + for arg in sys.argv[1:]: + if arg=="-b": + base_str = sys.argv[sys.argv.index("-b")+1] + elif arg=="-h": + usage() + else: + filein = arg + + if not filein: + usage() + + try: + lines = open(filein,"r").readlines() + except: + print ("Problem opening file: %s" % filein) + sys.exit(1) + + if base_str: + print ('base= "%s"' % base_str) + # assume a numeric base. If that fails, try searching + # for a matching line. + try: + base_time = float(base_str) + except: + # search for line matching <base> string + found = 0 + for line in lines: + try: + (time, rest) = get_time(line) + except: + continue + if string.find(rest, base_str)==1: + base_time = time + found = 1 + # stop at first match + break + if not found: + print ('Couldn\'t find line matching base pattern "%s"' % base_str) + sys.exit(1) + else: + base_time = 0.0 + + for line in lines: + print (convert_line(line, base_time),) + +if __name__ == "__main__": + main() diff --git a/scripts/sign-file.c b/scripts/sign-file.c new file mode 100644 index 000000000..86b010ac1 --- /dev/null +++ b/scripts/sign-file.c @@ -0,0 +1,358 @@ +/* Sign a module file using the given key. + * + * Copyright © 2014-2016 Red Hat, Inc. All Rights Reserved. + * Copyright © 2015 Intel Corporation. + * Copyright © 2016 Hewlett Packard Enterprise Development LP + * + * Authors: David Howells <dhowells@redhat.com> + * David Woodhouse <dwmw2@infradead.org> + * Juerg Haefliger <juerg.haefliger@hpe.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the licence, or (at your option) any later version. + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <getopt.h> +#include <err.h> +#include <arpa/inet.h> +#include <openssl/opensslv.h> +#include <openssl/bio.h> +#include <openssl/cms.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +# 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" + +#include <linux/module_signature.h> + +static __attribute__((noreturn)) +void format(void) +{ + fprintf(stderr, + "Usage: scripts/sign-file [-dp] <hash algo> <key> <x509> <module> [<dest>]\n"); + fprintf(stderr, + " scripts/sign-file -s <raw sig> <hash algo> <x509> <module> [<dest>]\n"); + exit(2); +} + +static const char *key_pass; + +static int pem_pw_cb(char *buf, int len, int w, void *v) +{ + int pwlen; + + if (!key_pass) + return -1; + + pwlen = strlen(key_pass); + if (pwlen >= len) + return -1; + + strcpy(buf, key_pass); + + /* If it's wrong, don't keep trying it. */ + key_pass = NULL; + + return pwlen; +} + +static EVP_PKEY *read_private_key_pkcs11(const char *private_key_name) +{ + 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)) { + return read_private_key_pkcs11(private_key_name); + } else { + EVP_PKEY *private_key; + BIO *b; + + b = BIO_new_file(private_key_name, "rb"); + ERR(!b, "%s", private_key_name); + private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, + NULL); + ERR(!private_key, "%s", private_key_name); + BIO_free(b); + + return private_key; + } +} + +static X509 *read_x509(const char *x509_name) +{ + unsigned char buf[2]; + X509 *x509; + BIO *b; + int n; + + b = BIO_new_file(x509_name, "rb"); + ERR(!b, "%s", x509_name); + + /* Look at the first two bytes of the file to determine the encoding */ + n = BIO_read(b, buf, 2); + if (n != 2) { + if (BIO_should_retry(b)) { + fprintf(stderr, "%s: Read wanted retry\n", x509_name); + exit(1); + } + if (n >= 0) { + fprintf(stderr, "%s: Short read\n", x509_name); + exit(1); + } + ERR(1, "%s", x509_name); + } + + ERR(BIO_reset(b) != 0, "%s", x509_name); + + if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84) + /* Assume raw DER encoded X.509 */ + x509 = d2i_X509_bio(b, NULL); + else + /* Assume PEM encoded X.509 */ + x509 = PEM_read_bio_X509(b, NULL, NULL, NULL); + + BIO_free(b); + ERR(!x509, "%s", x509_name); + + return x509; +} + +int main(int argc, char **argv) +{ + struct module_signature sig_info = { .id_type = MODULE_SIGNATURE_TYPE_PKCS7 }; + char *hash_algo = NULL; + char *private_key_name = NULL, *raw_sig_name = NULL; + char *x509_name, *module_name, *dest_name; + bool save_sig = false, replace_orig; + bool sign_only = false; + bool raw_sig = false; + unsigned char buf[4096]; + unsigned long module_size, sig_size; + const EVP_MD *digest_algo; + EVP_PKEY *private_key; + CMS_ContentInfo *cms = NULL; + unsigned int use_keyid = 0; + X509 *x509; + BIO *bd, *bm; + int opt, n; + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + ERR_clear_error(); + + key_pass = getenv("KBUILD_SIGN_PIN"); + + do { + opt = getopt(argc, argv, "sdpk"); + switch (opt) { + case 's': raw_sig = true; break; + case 'p': save_sig = true; break; + case 'd': sign_only = true; save_sig = true; break; + case 'k': use_keyid = CMS_USE_KEYID; break; + case -1: break; + default: format(); + } + } while (opt != -1); + + argc -= optind; + argv += optind; + if (argc < 4 || argc > 5) + format(); + + if (raw_sig) { + raw_sig_name = argv[0]; + hash_algo = argv[1]; + } else { + hash_algo = argv[0]; + private_key_name = argv[1]; + } + x509_name = argv[2]; + module_name = argv[3]; + if (argc == 5 && strcmp(argv[3], argv[4]) != 0) { + dest_name = argv[4]; + replace_orig = false; + } else { + ERR(asprintf(&dest_name, "%s.~signed~", module_name) < 0, + "asprintf"); + replace_orig = true; + } + + /* Open the module file */ + bm = BIO_new_file(module_name, "rb"); + ERR(!bm, "%s", module_name); + + if (!raw_sig) { + /* Read the private key and the X.509 cert the PKCS#7 message + * will point to. + */ + private_key = read_private_key(private_key_name); + x509 = read_x509(x509_name); + + /* Digest the module data. */ + OpenSSL_add_all_digests(); + drain_openssl_errors(__LINE__, 0); + digest_algo = EVP_get_digestbyname(hash_algo); + ERR(!digest_algo, "EVP_get_digestbyname"); + + unsigned int flags = + CMS_NOCERTS | + CMS_NOATTR | + CMS_PARTIAL | + CMS_BINARY | + CMS_DETACHED | + CMS_STREAM | + CMS_NOSMIMECAP | +#ifdef CMS_NO_SIGNING_TIME + CMS_NO_SIGNING_TIME | +#endif + use_keyid; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_VERSION_NUMBER < 0x40000000L + if (EVP_PKEY_is_a(private_key, "ML-DSA-44") || + EVP_PKEY_is_a(private_key, "ML-DSA-65") || + EVP_PKEY_is_a(private_key, "ML-DSA-87")) { + /* ML-DSA + CMS_NOATTR is not supported in openssl-3.5 + * and before. + */ + flags &= ~CMS_NOATTR; + } +#endif + + /* Load the signature message from the digest buffer. */ + cms = CMS_sign(NULL, NULL, NULL, NULL, flags); + ERR(!cms, "CMS_sign"); + + ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo, flags), + "CMS_add1_signer"); + ERR(CMS_final(cms, bm, NULL, flags) != 1, + "CMS_final"); + + if (save_sig) { + char *sig_file_name; + BIO *b; + + ERR(asprintf(&sig_file_name, "%s.p7s", module_name) < 0, + "asprintf"); + b = BIO_new_file(sig_file_name, "wb"); + ERR(!b, "%s", sig_file_name); + ERR(i2d_CMS_bio_stream(b, cms, NULL, 0) != 1, + "%s", sig_file_name); + BIO_free(b); + } + + if (sign_only) { + BIO_free(bm); + return 0; + } + } + + /* Open the destination file now so that we can shovel the module data + * across as we read it. + */ + bd = BIO_new_file(dest_name, "wb"); + ERR(!bd, "%s", dest_name); + + /* Append the marker and the PKCS#7 message to the destination file */ + ERR(BIO_reset(bm) < 0, "%s", module_name); + while ((n = BIO_read(bm, buf, sizeof(buf))), + n > 0) { + ERR(BIO_write(bd, buf, n) < 0, "%s", dest_name); + } + BIO_free(bm); + ERR(n < 0, "%s", module_name); + module_size = BIO_number_written(bd); + + if (!raw_sig) { + ERR(i2d_CMS_bio_stream(bd, cms, NULL, 0) != 1, "%s", dest_name); + } else { + BIO *b; + + /* Read the raw signature file and write the data to the + * destination file + */ + b = BIO_new_file(raw_sig_name, "rb"); + ERR(!b, "%s", raw_sig_name); + while ((n = BIO_read(b, buf, sizeof(buf))), n > 0) + ERR(BIO_write(bd, buf, n) < 0, "%s", dest_name); + BIO_free(b); + } + + sig_size = BIO_number_written(bd) - module_size; + sig_info.sig_len = htonl(sig_size); + ERR(BIO_write(bd, &sig_info, sizeof(sig_info)) < 0, "%s", dest_name); + ERR(BIO_write(bd, MODULE_SIGNATURE_MARKER, sizeof(MODULE_SIGNATURE_MARKER) - 1) < 0, + "%s", dest_name); + + ERR(BIO_free(bd) != 1, "%s", dest_name); + + /* Finally, if we're signing in place, replace the original. */ + if (replace_orig) + ERR(rename(dest_name, module_name) < 0, "%s", dest_name); + + return 0; +} diff --git a/scripts/sorttable.c b/scripts/sorttable.c new file mode 100644 index 000000000..d7b50581c --- /dev/null +++ b/scripts/sorttable.c @@ -0,0 +1,1010 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * sorttable.c: Sort the kernel's table + * + * 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. + * + * Based on code taken from recortmcount.c which is: + * + * Copyright 2009 John F. Reiser <jreiser@BitWagon.com>. All rights reserved. + * + * Restructured to fit Linux format, as well as other updates: + * Copyright 2010 Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. + */ + +/* + * Strategy: alter the vmlinux file in-place. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <getopt.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <pthread.h> + +#include "elf-parse.h" + +#ifndef EM_ARCOMPACT +#define EM_ARCOMPACT 93 +#endif + +#ifndef EM_XTENSA +#define EM_XTENSA 94 +#endif + +#ifndef EM_AARCH64 +#define EM_AARCH64 183 +#endif + +#ifndef EM_MICROBLAZE +#define EM_MICROBLAZE 189 +#endif + +#ifndef EM_ARCV2 +#define EM_ARCV2 195 +#endif + +#ifndef EM_RISCV +#define EM_RISCV 243 +#endif + +#ifndef EM_LOONGARCH +#define EM_LOONGARCH 258 +#endif + +typedef void (*table_sort_t)(char *, int); + +/* + * Move reserved section indices SHN_LORESERVE..SHN_HIRESERVE out of + * the way to -256..-1, to avoid conflicting with real section + * indices. + */ +#define SPECIAL(i) ((i) - (SHN_HIRESERVE + 1)) + +static inline int is_shndx_special(unsigned int i) +{ + return i != SHN_XINDEX && i >= SHN_LORESERVE && i <= SHN_HIRESERVE; +} + +/* Accessor for sym->st_shndx, hides ugliness of "64k sections" */ +static inline unsigned int get_secindex(unsigned int shndx, + unsigned int sym_offs, + const Elf32_Word *symtab_shndx_start) +{ + if (is_shndx_special(shndx)) + return SPECIAL(shndx); + if (shndx != SHN_XINDEX) + return shndx; + return elf_parser.r(&symtab_shndx_start[sym_offs]); +} + +static int compare_extable_32(const void *a, const void *b) +{ + Elf32_Addr av = elf_parser.r(a); + Elf32_Addr bv = elf_parser.r(b); + + if (av < bv) + return -1; + return av > bv; +} + +static int compare_extable_64(const void *a, const void *b) +{ + Elf64_Addr av = elf_parser.r8(a); + Elf64_Addr bv = elf_parser.r8(b); + + if (av < bv) + return -1; + return av > bv; +} + +static int (*compare_extable)(const void *a, const void *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 __maybe_unused; + +#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) +{ + elf_parser.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 = elf_parser.r(addrs); + else + *(uint64_t *)ptr = elf_parser.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) + elf_parser.w(*(uint32_t *)ptr, addrs); + else + elf_parser.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 */ + elf_parser.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) +{ + int32_t av = (int32_t)elf_parser.r(a); + int32_t bv = (int32_t)elf_parser.r(b); + + if (av < bv) + return -1; + if (av > bv) + return 1; + return 0; +} + +static void sort_relative_table(char *extab_image, int image_size) +{ + int i = 0; + + /* + * Do the same thing the runtime sort does, first normalize to + * being relative to the start of the section. + */ + while (i < image_size) { + uint32_t *loc = (uint32_t *)(extab_image + i); + elf_parser.w(elf_parser.r(loc) + i, loc); + i += 4; + } + + qsort(extab_image, image_size / 8, 8, compare_relative_table); + + /* Now denormalize. */ + i = 0; + while (i < image_size) { + uint32_t *loc = (uint32_t *)(extab_image + i); + elf_parser.w(elf_parser.r(loc) - i, loc); + i += 4; + } +} + +static void sort_relative_table_with_data(char *extab_image, int image_size) +{ + int i = 0; + + while (i < image_size) { + uint32_t *loc = (uint32_t *)(extab_image + i); + + elf_parser.w(elf_parser.r(loc) + i, loc); + elf_parser.w(elf_parser.r(loc + 1) + i + 4, loc + 1); + /* Don't touch the fixup type or data */ + + i += sizeof(uint32_t) * 3; + } + + qsort(extab_image, image_size / 12, 12, compare_relative_table); + + i = 0; + while (i < image_size) { + uint32_t *loc = (uint32_t *)(extab_image + i); + + elf_parser.w(elf_parser.r(loc) - i, loc); + elf_parser.w(elf_parser.r(loc + 1) - (i + 4), loc + 1); + /* Don't touch the fixup type or data */ + + i += sizeof(uint32_t) * 3; + } +} + +static int do_file(char const *const fname, void *addr) +{ + Elf_Ehdr *ehdr = addr; + table_sort_t custom_sort = NULL; + + switch (elf_map_machine(ehdr)) { +#ifdef MCOUNT_SORT_ENABLED + case EM_AARCH64: + /* arm64 also needs RELA-based weak-function fixups. */ + sort_reloc = true; + rela_type = 0x403; + /* fallthrough */ + case EM_RISCV: + /* arm64 and RISC-V place patchable entries before the function. */ + before_func = 8; +#else + case EM_AARCH64: + case EM_RISCV: +#endif + /* fallthrough */ + case EM_386: + case EM_LOONGARCH: + case EM_S390: + case EM_X86_64: + custom_sort = sort_relative_table_with_data; + break; + case EM_PARISC: + case EM_PPC: + case EM_PPC64: + custom_sort = sort_relative_table; + break; + case EM_ARCOMPACT: + case EM_ARCV2: + case EM_ARM: + case EM_MICROBLAZE: + case EM_MIPS: + case EM_XTENSA: + break; + default: + fprintf(stderr, "unrecognized e_machine %d %s\n", + elf_parser.r2(&ehdr->e32.e_machine), fname); + return -1; + } + + switch (elf_map_long_size(addr)) { + case 4: + compare_extable = compare_extable_32, + long_size = 4; + extable_ent_size = 8; + + if (elf_parser.r2(&ehdr->e32.e_ehsize) != sizeof(Elf32_Ehdr) || + elf_parser.r2(&ehdr->e32.e_shentsize) != sizeof(Elf32_Shdr)) { + fprintf(stderr, + "unrecognized ET_EXEC/ET_DYN file: %s\n", fname); + return -1; + } + + break; + case 8: + compare_extable = compare_extable_64, + long_size = 8; + extable_ent_size = 16; + + if (elf_parser.r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) || + elf_parser.r2(&ehdr->e64.e_shentsize) != sizeof(Elf64_Shdr)) { + fprintf(stderr, + "unrecognized ET_EXEC/ET_DYN file: %s\n", + fname); + return -1; + } + + break; + default: + fprintf(stderr, "unrecognized ELF class %d %s\n", + ehdr->e32.e_ident[EI_CLASS], fname); + return -1; + } + + return do_sort(ehdr, fname, custom_sort); +} + +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 - optind) < 1) { + fprintf(stderr, "usage: sorttable vmlinux...\n"); + return 0; + } + + /* Process each file in turn, allowing deep failure. */ + for (i = optind; i < argc; i++) { + addr = elf_map(argv[i], &size, (1 << ET_EXEC) | (1 << ET_DYN)); + if (!addr) { + ++n_error; + continue; + } + + if (do_file(argv[i], addr)) + ++n_error; + + elf_unmap(addr, size); + } + + return !!n_error; +} diff --git a/scripts/spdxcheck-test.sh b/scripts/spdxcheck-test.sh new file mode 100644 index 000000000..9f6d1a74d --- /dev/null +++ b/scripts/spdxcheck-test.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# run check on a text and a binary file +for FILE in Makefile Documentation/images/logo.gif; do + python3 scripts/spdxcheck.py $FILE + python3 scripts/spdxcheck.py - < $FILE +done + +# run check on complete tree to catch any other issues +python3 scripts/spdxcheck.py > /dev/null diff --git a/scripts/spdxcheck.py b/scripts/spdxcheck.py new file mode 100755 index 000000000..908029e45 --- /dev/null +++ b/scripts/spdxcheck.py @@ -0,0 +1,456 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright Linutronix GmbH, Thomas Gleixner <tglx@kernel.org> + +from argparse import ArgumentParser +from ply import lex, yacc +import locale +import traceback +import fnmatch +import sys +import git +import re +import os + +class ParserException(Exception): + def __init__(self, tok, txt): + self.tok = tok + self.txt = txt + +class SPDXException(Exception): + def __init__(self, el, txt): + self.el = el + self.txt = txt + +class SPDXdata(object): + def __init__(self): + self.license_files = 0 + self.exception_files = 0 + self.licenses = [ ] + self.exceptions = { } + +class dirinfo(object): + def __init__(self): + self.missing = 0 + self.total = 0 + self.files = [] + + def update(self, fname, basedir, miss): + self.total += 1 + self.missing += miss + if miss: + fname = './' + fname + bdir = os.path.dirname(fname) + if bdir == basedir.rstrip('/'): + self.files.append(fname) + +# Read the spdx data from the LICENSES directory +def read_spdxdata(repo): + + # The subdirectories of LICENSES in the kernel source + # Note: exceptions needs to be parsed as last directory. + license_dirs = [ "preferred", "dual", "deprecated", "exceptions" ] + lictree = repo.head.commit.tree['LICENSES'] + + spdx = SPDXdata() + + for d in license_dirs: + for el in lictree[d].traverse(): + if not os.path.isfile(el.path): + continue + + exception = None + for l in open(el.path, encoding="utf-8").readlines(): + if l.startswith('Valid-License-Identifier:'): + lid = l.split(':')[1].strip().upper() + if lid in spdx.licenses: + raise SPDXException(el, 'Duplicate License Identifier: %s' %lid) + else: + spdx.licenses.append(lid) + + elif l.startswith('SPDX-Exception-Identifier:'): + exception = l.split(':')[1].strip().upper() + spdx.exceptions[exception] = [] + + elif l.startswith('SPDX-Licenses:'): + for lic in l.split(':')[1].upper().strip().replace(' ', '').replace('\t', '').split(','): + if not lic in spdx.licenses: + raise SPDXException(None, 'Exception %s missing license %s' %(exception, lic)) + spdx.exceptions[exception].append(lic) + + elif l.startswith("License-Text:"): + if exception: + if not len(spdx.exceptions[exception]): + raise SPDXException(el, 'Exception %s is missing SPDX-Licenses' %exception) + spdx.exception_files += 1 + else: + spdx.license_files += 1 + break + return spdx + +class id_parser(object): + + reserved = [ 'AND', 'OR', 'WITH' ] + tokens = [ 'LPAR', 'RPAR', 'ID', 'EXC' ] + reserved + + precedence = ( ('nonassoc', 'AND', 'OR'), ) + + t_ignore = ' \t' + + def __init__(self, spdx): + self.spdx = spdx + self.lasttok = None + self.lastid = None + self.lexer = lex.lex(module = self, reflags = re.UNICODE) + # Initialize the parser. No debug file and no parser rules stored on disk + # The rules are small enough to be generated on the fly + self.parser = yacc.yacc(module = self, write_tables = False, debug = False) + self.lines_checked = 0 + self.checked = 0 + self.excluded = 0 + self.spdx_valid = 0 + self.spdx_errors = 0 + self.spdx_dirs = {} + self.dirdepth = -1 + self.basedir = '.' + self.curline = 0 + self.deepest = 0 + + def set_dirinfo(self, basedir, dirdepth): + if dirdepth >= 0: + self.basedir = basedir + bdir = basedir.lstrip('./').rstrip('/') + if bdir != '': + parts = bdir.split('/') + else: + parts = [] + self.dirdepth = dirdepth + len(parts) + + # Validate License and Exception IDs + def validate(self, tok): + id = tok.value.upper() + if tok.type == 'ID': + if not id in self.spdx.licenses: + raise ParserException(tok, 'Invalid License ID') + self.lastid = id + elif tok.type == 'EXC': + if id not in self.spdx.exceptions: + raise ParserException(tok, 'Invalid Exception ID') + if self.lastid not in self.spdx.exceptions[id]: + raise ParserException(tok, 'Exception not valid for license %s' %self.lastid) + self.lastid = None + elif tok.type != 'WITH': + self.lastid = None + + # Lexer functions + def t_RPAR(self, tok): + r'\)' + self.lasttok = tok.type + return tok + + def t_LPAR(self, tok): + r'\(' + self.lasttok = tok.type + return tok + + def t_ID(self, tok): + r'[A-Za-z.0-9\-+]+' + + if self.lasttok == 'EXC': + print(tok) + raise ParserException(tok, 'Missing parentheses') + + tok.value = tok.value.strip() + val = tok.value.upper() + + if val in self.reserved: + tok.type = val + elif self.lasttok == 'WITH': + tok.type = 'EXC' + + self.lasttok = tok.type + self.validate(tok) + return tok + + def t_error(self, tok): + raise ParserException(tok, 'Invalid token') + + def p_expr(self, p): + '''expr : ID + | ID WITH EXC + | expr AND expr + | expr OR expr + | LPAR expr RPAR''' + pass + + def p_error(self, p): + if not p: + raise ParserException(None, 'Unfinished license expression') + else: + raise ParserException(p, 'Syntax error') + + def parse(self, expr): + self.lasttok = None + self.lastid = None + self.parser.parse(expr, lexer = self.lexer) + + def parse_lines(self, fd, maxlines, fname): + self.checked += 1 + self.curline = 0 + fail = 1 + try: + for line in fd: + line = line.decode(locale.getpreferredencoding(False), errors='ignore') + self.curline += 1 + if self.curline > maxlines: + break + self.lines_checked += 1 + if line.find("SPDX-License-Identifier:") < 0: + continue + expr = line.split(':')[1].strip() + # Remove trailing comment closure + if line.strip().endswith('*/'): + expr = expr.rstrip('*/').strip() + # 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 + # + # Should we check for more SPDX ids in the same file and + # complain if there are any? + # + fail = 0 + break + + except ParserException as pe: + if pe.tok: + col = line.find(expr) + pe.tok.lexpos + tok = pe.tok.value + sys.stdout.write('%s: %d:%d %s: %s\n' %(fname, self.curline, col, pe.txt, tok)) + else: + sys.stdout.write('%s: %d:0 %s\n' %(fname, self.curline, pe.txt)) + self.spdx_errors += 1 + + if fname == '-': + return + + base = os.path.dirname(fname) + if self.dirdepth > 0: + parts = base.split('/') + i = 0 + base = '.' + while i < self.dirdepth and i < len(parts) and len(parts[i]): + base += '/' + parts[i] + i += 1 + elif self.dirdepth == 0: + base = self.basedir + else: + base = './' + base.rstrip('/') + base += '/' + + di = self.spdx_dirs.get(base, dirinfo()) + di.update(fname, base, fail) + self.spdx_dirs[base] = di + +class pattern(object): + def __init__(self, line): + self.pattern = line + self.match = self.match_file + if line == '.*': + self.match = self.match_dot + elif line.endswith('/'): + self.pattern = line[:-1] + self.match = self.match_dir + elif line.startswith('/'): + self.pattern = line[1:] + self.match = self.match_fn + + def match_dot(self, fpath): + return os.path.basename(fpath).startswith('.') + + def match_file(self, fpath): + return os.path.basename(fpath) == self.pattern + + def match_fn(self, fpath): + return fnmatch.fnmatchcase(fpath, self.pattern) + + def match_dir(self, fpath): + if self.match_fn(os.path.dirname(fpath)): + return True + return fpath.startswith(self.pattern) + +def exclude_file(fpath): + for rule in exclude_rules: + if rule.match(fpath): + return True + return False + +def scan_git_tree(tree, basedir, dirdepth): + parser.set_dirinfo(basedir, dirdepth) + for el in tree.traverse(): + if not os.path.isfile(el.path): + continue + if exclude_file(el.path): + parser.excluded += 1 + continue + with open(el.path, 'rb') as fd: + parser.parse_lines(fd, args.maxlines, el.path) + +def scan_git_subtree(tree, path, dirdepth): + for p in path.strip('/').split('/'): + tree = tree[p] + scan_git_tree(tree, path.strip('/'), dirdepth) + +def read_exclude_file(fname): + rules = [] + if not fname: + return rules + with open(fname) as fd: + for line in fd: + line = line.strip() + if line.startswith('#'): + continue + if not len(line): + continue + rules.append(pattern(line)) + return rules + +if __name__ == '__main__': + + ap = ArgumentParser(description='SPDX expression checker') + ap.add_argument('path', nargs='*', help='Check path or file. If not given full git tree scan. For stdin use "-"') + ap.add_argument('-d', '--dirs', action='store_true', + help='Show [sub]directory statistics.') + ap.add_argument('-D', '--depth', type=int, default=-1, + help='Directory depth for -d statistics. Default: unlimited') + ap.add_argument('-e', '--exclude', + help='File containing file patterns to exclude. Default: scripts/spdxexclude') + ap.add_argument('-f', '--files', action='store_true', + help='Show files without SPDX.') + ap.add_argument('-m', '--maxlines', type=int, default=15, + help='Maximum number of lines to scan in a file. Default 15') + ap.add_argument('-v', '--verbose', action='store_true', help='Verbose statistics output') + args = ap.parse_args() + + # Sanity check path arguments + if '-' in args.path and len(args.path) > 1: + sys.stderr.write('stdin input "-" must be the only path argument\n') + sys.exit(1) + + try: + # Use git to get the valid license expressions + repo = git.Repo(os.getcwd()) + assert not repo.bare + + # Initialize SPDX data + spdx = read_spdxdata(repo) + + # Initialize the parser + parser = id_parser(spdx) + + except SPDXException as se: + if se.el: + sys.stderr.write('%s: %s\n' %(se.el.path, se.txt)) + else: + sys.stderr.write('%s\n' %se.txt) + sys.exit(1) + + except Exception as ex: + sys.stderr.write('FAIL: %s\n' %ex) + sys.stderr.write('%s\n' %traceback.format_exc()) + sys.exit(1) + + try: + fname = args.exclude + if not fname: + fname = os.path.join(os.path.dirname(__file__), 'spdxexclude') + exclude_rules = read_exclude_file(fname) + except Exception as ex: + sys.stderr.write('FAIL: Reading exclude file %s: %s\n' %(fname, ex)) + sys.exit(1) + + try: + if len(args.path) and args.path[0] == '-': + stdin = os.fdopen(sys.stdin.fileno(), 'rb') + parser.parse_lines(stdin, args.maxlines, '-') + else: + if args.path: + for p in args.path: + if os.path.isfile(p): + parser.parse_lines(open(p, 'rb'), args.maxlines, p) + elif os.path.isdir(p): + scan_git_subtree(repo.head.reference.commit.tree, p, + args.depth) + else: + sys.stderr.write('path %s does not exist\n' %p) + sys.exit(1) + else: + # Full git tree scan + scan_git_tree(repo.head.commit.tree, '.', args.depth) + + ndirs = len(parser.spdx_dirs) + dirsok = 0 + if ndirs: + for di in parser.spdx_dirs.values(): + if not di.missing: + dirsok += 1 + + if args.verbose: + sys.stderr.write('\n') + sys.stderr.write('License files: %12d\n' %spdx.license_files) + sys.stderr.write('Exception files: %12d\n' %spdx.exception_files) + sys.stderr.write('License IDs %12d\n' %len(spdx.licenses)) + sys.stderr.write('Exception IDs %12d\n' %len(spdx.exceptions)) + sys.stderr.write('\n') + sys.stderr.write('Files excluded: %12d\n' %parser.excluded) + sys.stderr.write('Files checked: %12d\n' %parser.checked) + sys.stderr.write('Lines checked: %12d\n' %parser.lines_checked) + 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') + sys.stderr.write('Directories accounted: %8d\n' %ndirs) + pc = int(100 * dirsok / ndirs) + sys.stderr.write('Directories complete: %8d %3d%%\n' %(dirsok, pc)) + + if ndirs and ndirs != dirsok and args.dirs: + if args.verbose: + sys.stderr.write('\n') + sys.stderr.write('Incomplete directories: SPDX in Files\n') + for f in sorted(parser.spdx_dirs.keys()): + di = parser.spdx_dirs[f] + if di.missing: + valid = di.total - di.missing + pc = int(100 * valid / di.total) + sys.stderr.write(' %-80s: %5d of %5d %3d%%\n' %(f, valid, di.total, pc)) + + if ndirs and ndirs != dirsok and args.files: + if args.verbose or args.dirs: + sys.stderr.write('\n') + sys.stderr.write('Files without SPDX:\n') + for f in sorted(parser.spdx_dirs.keys()): + di = parser.spdx_dirs[f] + for f in sorted(di.files): + sys.stderr.write(' %s\n' %f) + + sys.exit(0) + + except Exception as ex: + sys.stderr.write('FAIL: %s\n' %ex) + sys.stderr.write('%s\n' %traceback.format_exc()) + sys.exit(1) diff --git a/scripts/spdxexclude b/scripts/spdxexclude new file mode 100644 index 000000000..81bdb13ed --- /dev/null +++ b/scripts/spdxexclude @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Patterns for excluding files and directories + +# Ignore the license directory and the licensing documentation which would +# create lots of noise for no value +LICENSES/ +license-rules.rst + +# Ignore config files and snippets. The majority is generated +# by the Kconfig tools +kernel/configs/ +arch/*/configs/ + +# Other files without copyrightable content +/CREDITS +/MAINTAINERS +/README diff --git a/scripts/spelling.txt b/scripts/spelling.txt new file mode 100644 index 000000000..3372873cd --- /dev/null +++ b/scripts/spelling.txt @@ -0,0 +1,1804 @@ +# Originally from Debian's Lintian tool. Various false positives have been +# removed, and various additions have been made as they've been discovered +# in the kernel source. +# +# License: GPLv2 +# +# The format of each line is: +# mistake||correction +# +abandonning||abandoning +abigious||ambiguous +abitrary||arbitrary +abitrate||arbitrate +abnornally||abnormally +abnrormal||abnormal +abord||abort +aboslute||absolute +abov||above +abreviated||abbreviated +absense||absence +absolut||absolute +absoulte||absolute +acccess||access +acceess||access +accelaration||acceleration +accelearion||acceleration +acceleratoin||acceleration +accelleration||acceleration +accelrometer||accelerometer +accesing||accessing +accesnt||accent +accessable||accessible +accesss||access +accidentaly||accidentally +accidentually||accidentally +acclerated||accelerated +accoding||according +accomodate||accommodate +accomodates||accommodates +accordign||according +accoring||according +accout||account +accquire||acquire +accquired||acquired +accross||across +accumalate||accumulate +accumalator||accumulator +acessable||accessible +acess||access +acessing||accessing +achitecture||architecture +acient||ancient +acitions||actions +acitve||active +acknowldegement||acknowledgment +acknowledgement||acknowledgment +ackowledge||acknowledge +ackowledged||acknowledged +acording||according +actived||activated +activete||activate +actualy||actually +actvie||active +acumulating||accumulating +acumulative||accumulative +acumulator||accumulator +acutally||actually +adapater||adapter +adddress||address +adderted||asserted +addional||additional +additionaly||additionally +additonal||additional +addres||address +addreses||addresses +addresss||address +addrress||address +aditional||additional +aditionally||additionally +aditionaly||additionally +adminstrative||administrative +adress||address +adresses||addresses +adrresses||addresses +advertisment||advertisement +adviced||advised +afecting||affecting +againt||against +agaist||against +aggreataon||aggregation +aggreation||aggregation +ajust||adjust +albumns||albums +alegorical||allegorical +algined||aligned +algorith||algorithm +algorithmical||algorithmically +algorithmn||algorithm +algoritm||algorithm +algoritms||algorithms +algorrithm||algorithm +algorritm||algorithm +aligment||alignment +alignement||alignment +allign||align +alligned||aligned +alllocate||allocate +alloated||allocated +allocatote||allocate +allocatrd||allocated +allocte||allocate +allocted||allocated +allpication||application +alocate||allocate +alogirhtms||algorithms +alogrithm||algorithm +alot||a lot +alow||allow +alows||allows +alreay||already +alredy||already +altough||although +alue||value +ambigious||ambiguous +ambigous||ambiguous +amoung||among +amount of times||number of times +amout||amount +amplifer||amplifier +amplifyer||amplifier +analysator||analyzer +ang||and +anniversery||anniversary +annoucement||announcement +anomolies||anomalies +anomoly||anomaly +an one||a one +anonynous||anonymous +an union||a union +an user||a user +an userspace||a userspace +anway||anyway +apeared||appeared +aplication||application +appearence||appearance +applicaion||application +appliction||application +applictions||applications +applys||applies +appplications||applications +appropiate||appropriate +appropriatly||appropriately +approriate||appropriate +approriately||appropriately +apropriate||appropriate +aquainted||acquainted +aquired||acquired +aquires||acquires +aquisition||acquisition +arbitary||arbitrary +architechture||architecture +archtecture||architecture +arguement||argument +arguements||arguments +arithmatic||arithmetic +aritmetic||arithmetic +arne't||aren't +arraival||arrival +artifical||artificial +artillary||artillery +asign||assign +asser||assert +assertation||assertion +assertting||asserting +assgined||assigned +assiged||assigned +assigment||assignment +assigments||assignments +assinged||assigned +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 +asychronous||asynchronous +asycronous||asynchronous +asymetric||asymmetric +asymmeric||asymmetric +asynchnous||asynchronous +asynchrnous||asynchronous +asynchromous||asynchronous +asynchronus||asynchronous +atempt||attempt +atleast||at least +atomatically||automatically +atomicly||atomically +atrributes||attributes +attachement||attachment +attatch||attach +attched||attached +attemp||attempt +attemping||attempting +attemps||attempts +attepmpt||attempt +attnetion||attention +attruibutes||attributes +authenicated||authenticated +authentification||authentication +automaticaly||automatically +automaticly||automatically +automatize||automate +automatized||automated +automatizes||automates +autonymous||autonomous +auxillary||auxiliary +auxilliary||auxiliary +avaiable||available +avaialable||available +avaible||available +availabe||available +availabled||available +availablity||availability +availaible||available +availale||available +availavility||availability +availble||available +availiable||available +availible||available +avalable||available +avaliable||available +aysnc||async +backgroud||background +backword||backward +backwords||backwards +bahavior||behavior +bakup||backup +baloon||balloon +baloons||balloons +bandwith||bandwidth +banlance||balance +batery||battery +battey||battery +beacuse||because +becasue||because +becomming||becoming +becuase||because +beeing||being +befor||before +begining||beginning +beter||better +betweeen||between +bianries||binaries +binded||bound +bitmast||bitmask +bitwiedh||bitwidth +boardcast||broadcast +borad||board +boundry||boundary +brievely||briefly +brigde||bridge +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 +calle||called +callibration||calibration +callled||called +callser||caller +calucate||calculate +calulate||calculate +cancelation||cancellation +cancle||cancel +cannnot||cannot +cann't||can't +canot||cannot +cant'||can't +cant||can't +capabiity||capability +capabilites||capabilities +capabilties||capabilities +capabilty||capability +capabitilies||capabilities +capablity||capability +capapbilities||capabilities +capatibilities||capabilities +captuer||capture +caputure||capture +carefuly||carefully +cariage||carriage +casued||caused +catagory||category +cehck||check +chache||cache +challange||challenge +challanges||challenges +chanell||channel +changable||changeable +chanined||chained +channle||channel +channnel||channel +charachter||character +charachters||characters +charactor||character +charater||character +charaters||characters +charcter||character +chcek||check +chck||check +checksumed||checksummed +checksuming||checksumming +childern||children +childs||children +chiled||child +chked||checked +chnage||change +chnages||changes +chnange||change +chnnel||channel +choosen||chosen +chouse||chose +circumvernt||circumvent +claread||cleared +clared||cleared +clearify||clarify +closeing||closing +clustred||clustered +cnfiguration||configuration +coexistance||coexistence +colescing||coalescing +collapsable||collapsible +colorfull||colorful +comand||command +comaptible||compatible +comit||commit +commerical||commercial +comming||coming +comminucation||communication +commited||committed +commiting||committing +committ||commit +commmand||command +commnunication||communication +commoditiy||commodity +compability||compatibility +compaibility||compatibility +comparsion||comparison +compatability||compatibility +compatable||compatible +compatibililty||compatibility +compatibiliy||compatibility +compatibilty||compatibility +compatiblity||compatibility +competion||completion +compilant||compliant +compleatly||completely +completition||completion +completly||completely +complient||compliant +compoment||component +componnents||components +comppatible||compatible +compres||compress +compresion||compression +compresser||compressor +comression||compression +comsume||consume +comsumed||consumed +comsumer||consumer +comsuming||consuming +comunicate||communicate +comunication||communication +conbination||combination +concurent||concurrent +conditionaly||conditionally +conditon||condition +condtional||conditional +condtion||condition +conected||connected +conector||connector +configed||configured +configration||configuration +configred||configured +configuartion||configuration +configuation||configuration +configued||configured +configuratoin||configuration +configuraton||configuration +configuretion||configuration +configutation||configuration +congiuration||configuration +conider||consider +conjuction||conjunction +connction||connection +connecetd||connected +connectinos||connections +connetor||connector +connnection||connection +connnections||connections +consistancy||consistency +consistant||consistent +consits||consists +constructred||constructed +containes||contains +containts||contains +contaisn||contains +contant||contact +contence||contents +contiguos||contiguous +continious||continuous +continous||continuous +continously||continuously +continueing||continuing +contiuous||continuous +contol||control +contoller||controller +contraints||constraints +controled||controlled +controler||controller +controll||control +contruct||construct +contruction||construction +contry||country +conuntry||country +convertion||conversion +convertor||converter +convienient||convenient +convinient||convenient +corected||corrected +correponding||corresponding +correponds||corresponds +correspoding||corresponding +cotrol||control +cound||could +couter||counter +coutner||counter +creationg||creating +cryptocraphic||cryptographic +cummulative||cumulative +cunter||counter +curent||current +curently||currently +cylic||cyclic +dafault||default +deactive||deactivate +deafult||default +deamon||daemon +debouce||debounce +decendant||descendant +decendants||descendants +decompres||decompress +decrese||decrease +decription||description +decsribed||described +dectected||detected +defailt||default +deferal||deferral +deffered||deferred +defferred||deferred +definate||definite +definately||definitely +definiation||definition +definiton||definition +defintion||definition +defintions||definitions +defualt||default +defult||default +deintialized||deinitialized +deintialize||deinitialize +deintializing||deinitializing +deivce||device +delared||declared +delare||declare +delares||declares +delaring||declaring +delemiter||delimiter +deley||delay +delibrately||deliberately +delievered||delivered +demension||dimension +demodualtor||demodulator +dependancies||dependencies +dependancy||dependency +dependant||dependent +dependend||dependent +depreacted||deprecated +depreacte||deprecate +desactivate||deactivate +desciptor||descriptor +desciptors||descriptors +descripto||descriptor +descripton||description +descrition||description +descritpor||descriptor +descritptor||descriptor +desctiptor||descriptor +desination||destination +desriptor||descriptor +desriptors||descriptors +destionation||destination +destoried||destroyed +destory||destroy +destoryed||destroyed +destorys||destroys +destroied||destroyed +detabase||database +detault||default +deteced||detected +detecion||detection +detectt||detect +detroyed||destroyed +develope||develop +developement||development +developped||developed +developpement||development +developper||developer +developpment||development +deveolpment||development +devided||divided +deviece||device +devision||division +diabled||disabled +diable||disable +dicline||decline +diconnected||disconnected +dictionnary||dictionary +didnt||didn't +diferent||different +differenciate||differentiate +differrence||difference +diffreential||differential +diffrent||different +diffrentiate||differentiate +difinition||definition +digial||digital +dimention||dimension +dimesions||dimensions +diplay||display +direcly||directly +directon||direction +direectly||directly +diregard||disregard +disabed||disabled +disapear||disappear +disapeared||disappeared +disappared||disappeared +disasembler||disassembler +disassocation||disassociation +disassocative||disassociative +disbaled||disabled +disbale||disable +disbled||disabled +disble||disable +disble||disable +disconnet||disconnect +discontinous||discontinuous +disgest||digest +disharge||discharge +disired||desired +disnabled||disabled +dispalying||displaying +dispertion||dispersion +dissable||disable +dissapeared||disappeared +dissapears||disappears +dissconect||disconnect +distiction||distinction +divisable||divisible +divsiors||divisors +docuentation||documentation +documantation||documentation +documentaion||documentation +documment||document +doesnt||doesn't +donwload||download +donwloading||downloading +dorp||drop +dosen||doesn +downlad||download +downlads||downloads +droped||dropped +droput||dropout +druing||during +dsiabled||disabled +dyanmic||dynamic +dynmaic||dynamic +eanable||enable +eanble||enable +easilly||easily +ecspecially||especially +edditable||editable +editting||editing +efective||effective +effectivness||effectiveness +efficently||efficiently +ehther||ether +eigth||eight +elementry||elementary +eletronic||electronic +embeded||embedded +emtpy||empty +enabledi||enabled +enbale||enable +enble||enable +enchanced||enhanced +encorporating||incorporating +encrupted||encrypted +encryped||encrypted +encrypiton||encryption +encryptio||encryption +endianess||endianness +enhaced||enhanced +enlightnment||enlightenment +enocded||encoded +enought||enough +enpoint||endpoint +enqueing||enqueuing +enterily||entirely +entires||entries +entites||entities +entrys||entries +enviroiment||environment +enviroment||environment +environement||environment +environent||environment +eqivalent||equivalent +equiped||equipped +equivelant||equivalent +equivilant||equivalent +eror||error +errorr||error +errror||error +estbalishment||establishment +etsablishment||establishment +etsbalishment||establishment +evalute||evaluate +evalutes||evaluates +evalution||evaluation +evaulated||evaluated +exaclty||exactly +excceed||exceed +excecutable||executable +exceded||exceeded +exceds||exceeds +exceeed||exceed +excellant||excellent +exchnage||exchange +execeeded||exceeded +execeeds||exceeds +exeed||exceed +exeeds||exceeds +exeuction||execution +existance||existence +existant||existent +exixt||exist +exlcude||exclude +exlcuding||excluding +exlcusive||exclusive +exlicitly||explicitly +exlusive||exclusive +exmaple||example +expecially||especially +experies||expires +explicite||explicit +explicitely||explicitly +explicity||explicitly +explictely||explicitly +explict||explicit +explictly||explicitly +expresion||expression +exprienced||experienced +exprimental||experimental +exsits||exists +exteneded||extended +extened||extended +extensability||extensibility +extenstion||extension +extention||extension +extracter||extractor +faied||failed +faield||failed +failded||failed +faild||failed +failer||failure +failied||failed +faill||fail +faillure||failure +failng||failing +failue||failure +failuer||failure +faireness||fairness +falied||failed +faliure||failure +fallbck||fallback +familar||familiar +fatser||faster +feauture||feature +feautures||features +fetaure||feature +fetaures||features +fetcing||fetching +fileystem||filesystem +fimrware||firmware +fimware||firmware +finanize||finalize +findn||find +finilizes||finalizes +finsih||finish +firmare||firmware +firmaware||firmware +firtly||firstly +firware||firmware +firwmare||firmware +fliter||filter +flusing||flushing +folloing||following +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 +frequancy||frequency +frequence||frequency +frequncy||frequency +frome||from +fronend||frontend +fucntion||function +fuction||function +fuctions||functions +fullill||fulfill +funcation||function +funcion||function +functionallity||functionality +functionaly||functionally +functionnality||functionality +functonality||functionality +funtion||function +funtions||functions +furthur||further +futhermore||furthermore +futrue||future +gatable||gateable +gateing||gating +gauage||gauge +gaurenteed||guaranteed +genereate||generate +genereted||generated +generiously||generously +genric||generic +gerenal||general +geting||getting +globel||global +grabing||grabbing +grahical||graphical +grahpical||graphical +granularty||granularity +grapic||graphic +grranted||granted +grups||groups +guage||gauge +guarenteed||guaranteed +guarentee||guarantee +halfs||halves +hander||handler +handfull||handful +hanlde||handle +hanled||handled +happend||happened +hardare||hardware +hardward||hardware +harware||hardware +havind||having +hearbeat||heartbeat +heigth||height +heirachy||hierarchy +heirarchically||hierarchically +heirarchy||hierarchy +helpfull||helpful +heterogenous||heterogeneous +hexdecimal||hexadecimal +hiearchy||hierarchy +hierachy||hierarchy +hierarchie||hierarchy +homogenous||homogeneous +horizental||horizontal +howver||however +hsould||should +hybernate||hibernate +hypervior||hypervisor +hypter||hyper +idel||idle +identidier||identifier +iligal||illegal +illgal||illegal +illigal||illegal +imblance||imbalance +immeadiately||immediately +immedaite||immediate +immedate||immediate +immediatelly||immediately +immediatly||immediately +immidiate||immediate +immutible||immutable +impelentation||implementation +impementated||implemented +implemantation||implementation +implemenation||implementation +implementaiton||implementation +implementated||implemented +implementd||implemented +implemention||implementation +implemetation||implementation +implemntation||implementation +implentation||implementation +implmentation||implementation +implmenting||implementing +inavlid||invalid +incative||inactive +incomming||incoming +incompaitiblity||incompatibility +incompatabilities||incompatibilities +incompatable||incompatible +incompatble||incompatible +inconsistant||inconsistent +increas||increase +incremeted||incremented +incrment||increment +incuding||including +inculde||include +indendation||indentation +indended||intended +independant||independent +independantly||independently +independed||independent +indiate||indicate +indicat||indicate +inexpect||inexpected +infalte||inflate +inferface||interface +infinit||infinite +infomation||information +informatiom||information +informations||information +informtion||information +infromation||information +ingore||ignore +inheritence||inheritance +inital||initial +initalised||initialized +initalise||initialize +initalized||initialized +initalize||initialize +initation||initiation +initators||initiators +initialiazation||initialization +initializationg||initialization +initializiation||initialization +initializtion||initialization +initialzed||initialized +initialze||initialize +initialzing||initializing +initilization||initialization +initilized||initialized +initilize||initialize +initliaze||initialize +inofficial||unofficial +inrerface||interface +insititute||institute +instace||instance +instal||install +instanciated||instantiated +instanciate||instantiate +instuments||instruments +insufficent||insufficient +intead||instead +inteface||interface +integreated||integrated +integrety||integrity +integrey||integrity +intendet||intended +intented||intended +interal||internal +interanl||internal +interchangable||interchangeable +interferring||interfering +interger||integer +intergrated||integrated +intermittant||intermittent +internel||internal +interoprability||interoperability +interrface||interface +interrrupt||interrupt +interrup||interrupt +interrups||interrupts +interruptted||interrupted +interupted||interrupted +interupt||interrupt +interupts||interrupts +interurpt||interrupt +interuupt||interrupt +intiailized||initialized +intial||initial +intialisation||initialisation +intialised||initialised +intialise||initialise +intialization||initialization +intialized||initialized +intialize||initialize +intregral||integral +intrerrupt||interrupt +intrrupt||interrupt +intterrupt||interrupt +intuative||intuitive +invaid||invalid +invaild||invalid +invailid||invalid +invalde||invalid +invald||invalid +invalide||invalid +invalidiate||invalidate +invalud||invalid +invididual||individual +invokation||invocation +invokations||invocations +iomaped||iomapped +ireelevant||irrelevant +irrelevent||irrelevant +isnt||isn't +isssue||issue +issus||issues +iteraions||iterations +iternations||iterations +itertation||iteration +itslef||itself +ivalid||invalid +jave||java +jeffies||jiffies +jumpimng||jumping +juse||just +jus||just +kown||known +lable||label +langage||language +langauage||language +langauge||language +langugage||language +lauch||launch +layed||laid +legnth||length +leightweight||lightweight +lengh||length +lenght||length +lenth||length +lesstiff||lesstif +libaries||libraries +libary||library +librairies||libraries +libraris||libraries +licenceing||licencing +limted||limited +logaritmic||logarithmic +loggging||logging +loggin||login +logile||logfile +loobpack||loopback +loosing||losing +losted||lost +maangement||management +machinary||machinery +maibox||mailbox +mailformed||malformed +maintainance||maintenance +maintainence||maintenance +maintan||maintain +makeing||making +malplaced||misplaced +malplace||misplace +managable||manageable +managament||management +managment||management +mangement||management +manger||manager +manoeuvering||maneuvering +manufaucturing||manufacturing +maping||mapping +mappping||mapping +matchs||matches +mathimatical||mathematical +mathimatic||mathematic +mathimatics||mathematics +maximium||maximum +maxium||maximum +maxmium||maximum +mechamism||mechanism +mechanim||mechanism +meetign||meeting +memeory||memory +memmber||member +memoery||memory +memomry||memory +memroy||memory +ment||meant +mergable||mergeable +mesage||message +mesages||messages +messags||messages +messgaes||messages +messsage||message +messsages||messages +metdata||metadata +micropone||microphone +microprocesspr||microprocessor +migrateable||migratable +miliseconds||milliseconds +millenium||millennium +milliseonds||milliseconds +minimam||minimum +minimim||minimum +minimun||minimum +minium||minimum +miniumum||minimum +minumum||minimum +misalinged||misaligned +miscelleneous||miscellaneous +misformed||malformed +mising||missing +mismactch||mismatch +mispelled||misspelled +mispelt||misspelt +missign||missing +missmanaged||mismanaged +missmatch||mismatch +misssing||missing +miximum||maximum +mmnemonic||mnemonic +mnay||many +modfiy||modify +modifer||modifier +modul||module +modulues||modules +momery||memory +monitring||monitoring +monochorome||monochrome +monochromo||monochrome +monocrome||monochrome +mopdule||module +mroe||more +muliple||multiple +mulitplied||multiplied +multidimensionnal||multidimensional +multipe||multiple +multipler||multiplier +multple||multiple +mumber||number +muticast||multicast +mutilcast||multicast +mutiple||multiple +mutli||multi +nams||names +navagating||navigating +nead||need +neccecary||necessary +neccesary||necessary +neccessary||necessary +necesary||necessary +neded||needed +negaive||negative +negoitation||negotiation +negotation||negotiation +nerver||never +nescessary||necessary +nessessary||necessary +none existent||non-existent +notfify||notify +noticable||noticeable +notication||notification +notications||notifications +notifcations||notifications +notifed||notified +notifer||notifier +notity||notify +nubmer||number +numebr||number +numer||number +numner||number +nunber||number +obtaion||obtain +obusing||abusing +occassionally||occasionally +occationally||occasionally +occurance||occurrence +occurances||occurrences +occurd||occurred +occured||occurred +occurence||occurrence +occure||occurred +occuring||occurring +ocurrence||occurrence +offet||offset +offlaod||offload +offloded||offloaded +offser||offset +offseting||offsetting +oflload||offload +omited||omitted +omiting||omitting +omitt||omit +ommiting||omitting +ommitted||omitted +onself||oneself +onthe||on the +ony||only +openning||opening +operatione||operation +opertaions||operations +opportunies||opportunities +optionnal||optional +optmizations||optimizations +orientatied||orientated +orientied||oriented +originial||original +orignal||original +orphanded||orphaned +otherise||otherwise +ouput||output +oustanding||outstanding +oveflow||overflow +overaall||overall +overflw||overflow +overhread||overhead +overide||override +overlaping||overlapping +overlfow||overflow +overrided||overridden +overriden||overridden +overrrun||overrun +overun||overrun +overwriten||overwritten +overwritting||overwriting +pacakge||package +pachage||package +packacge||package +packege||package +packge||package +packtes||packets +pakage||package +paket||packet +pallette||palette +palne||plane +paln||plan +paramameters||parameters +paramater||parameter +paramaters||parameters +paramenters||parameters +parametes||parameters +parametised||parametrised +paramter||parameter +paramters||parameters +parmaters||parameters +particuarly||particularly +particularily||particularly +partion||partition +partions||partitions +partiton||partition +pased||passed +passin||passing +pathes||paths +pattrns||patterns +pecularities||peculiarities +peformance||performance +peforming||performing +peice||piece +pendantic||pedantic +peprocessor||preprocessor +perfomance||performance +perfoming||performing +perfomring||performing +periperal||peripheral +peripherial||peripheral +permissons||permissions +permited||permitted +peroid||period +persistance||persistence +persistant||persistent +phoneticly||phonetically +pipline||pipeline +plaform||platform +plalform||platform +platfoem||platform +platfomr||platform +platfrom||platform +plattform||platform +pleaes||please +ploting||plotting +plugable||pluggable +poinnter||pointer +pointeur||pointer +poiter||pointer +posible||possible +positon||position +possibilites||possibilities +postion||position +potocol||protocol +powerfull||powerful +pramater||parameter +preambule||preamble +preamle||preamble +preample||preamble +preapre||prepare +preceeded||preceded +preceeding||preceding +preceed||precede +precendence||precedence +precission||precision +predicition||prediction +preemptable||preemptible +prefered||preferred +prefferably||preferably +prefitler||prefilter +preform||perform +premption||preemption +prepaired||prepared +prepate||prepare +preperation||preparation +preprare||prepare +pressre||pressure +presuambly||presumably +previleged||privileged +previlege||privilege +previosuly||previously +previsously||previously +primative||primitive +princliple||principle +priorty||priority +priting||printing +privilaged||privileged +privilage||privilege +priviledged||privileged +priviledge||privilege +priviledges||privileges +privleges||privileges +probabalistic||probabilistic +probaly||probably +procceed||proceed +proccesors||processors +procesed||processed +procesing||processing +proces||process +processessing||processing +processess||processes +processpr||processor +processsed||processed +processsing||processing +procteted||protected +prodecure||procedure +progamming||programming +progams||programs +progess||progress +programable||programmable +programers||programmers +programm||program +programms||programs +progres||progress +progresss||progress +prohibitted||prohibited +prohibitting||prohibiting +promiscous||promiscuous +promixity||proximity +promps||prompts +pronnounced||pronounced +prononciation||pronunciation +pronouce||pronounce +pronunce||pronounce +propery||property +propigate||propagate +propigation||propagation +propogate||propagate +propogation||propagation +prosess||process +protable||portable +protcol||protocol +protecion||protection +protedcted||protected +protocoll||protocol +psudo||pseudo +psuedo||pseudo +psychadelic||psychedelic +purgable||purgeable +pwoer||power +queing||queuing +quering||querying +querrying||querying +queus||queues +randomally||randomly +raoming||roaming +readyness||readiness +reasearcher||researcher +reasearchers||researchers +reasearch||research +recalcualte||recalculate +receieve||receive +recepient||recipient +recevied||received +receving||receiving +recievd||received +recieved||received +recieve||receive +reciever||receiver +recieves||receives +recieving||receiving +recogniced||recognised +recognizeable||recognizable +recommanded||recommended +recompte||recompute +recyle||recycle +redect||reject +redircet||redirect +redirectrion||redirection +redundacy||redundancy +reename||rename +refcounf||refcount +refence||reference +refered||referred +referenace||reference +referencce||reference +refererence||reference +refering||referring +refernces||references +refernnce||reference +refrence||reference +regiser||register +registed||registered +registeration||registration +registerd||registered +registeresd||registered +registerred||registered +registes||registers +registraration||registration +regsiter||register +regster||register +regualar||regular +reguator||regulator +regulamentations||regulations +reigstration||registration +releated||related +relevent||relevant +reloade||reload +remoote||remote +remore||remote +removeable||removable +repectively||respectively +repective||respective +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 +requsted||requested +requst||request +reregisteration||reregistration +reseting||resetting +reseved||reserved +reseverd||reserved +resizeable||resizable +resonable||reasonable +resotre||restore +resouce||resource +resouces||resources +resoures||resources +responce||response +resrouce||resource +ressizes||resizes +ressource||resource +ressources||resources +restesting||retesting +resumbmitting||resubmitting +retransmited||retransmitted +retreived||retrieved +retreive||retrieve +retreiving||retrieving +retrived||retrieved +retrive||retrieve +retrun||return +retuned||returned +retun||return +reudce||reduce +reuest||request +reuqest||request +reutnred||returned +revsion||revision +rewritting||rewriting +rmeoved||removed +rmeove||remove +rmeoves||removes +rountine||routine +routins||routines +rquest||request +runing||running +runned||ran +runnnig||running +runnning||running +runtine||runtime +sacrifying||sacrificing +safly||safely +safty||safety +satify||satisfy +satisifed||satisfied +savable||saveable +scaleing||scaling +scaned||scanned +scaning||scanning +scarch||search +schdule||schedule +seach||search +searchs||searches +secion||section +secquence||sequence +secund||second +segement||segment +seleted||selected +semaphone||semaphore +senario||scenario +senarios||scenarios +sentivite||sensitive +separatly||separately +sepcify||specify +seperated||separated +seperately||separately +seperate||separate +seperatly||separately +seperator||separator +sepperate||separate +seqeuencer||sequencer +seqeuncer||sequencer +seqeunce||sequence +sequece||sequence +sequemce||sequence +sequencial||sequential +serivce||service +serveral||several +servive||service +sesion||session +setts||sets +settting||setting +shapshot||snapshot +shoft||shift +shotdown||shutdown +shoud||should +shouldnt||shouldn't +shoule||should +shrinked||shrunk +siginificantly||significantly +signabl||signal +significanly||significantly +similary||similarly +similiar||similar +simlar||similar +simliar||similar +simpified||simplified +simultaneusly||simultaneously +simultanous||simultaneous +singaled||signaled +singal||signal +singed||signed +slect||select +sleeped||slept +sliped||slipped +softwade||software +softwares||software +soley||solely +soluation||solution +souce||source +speach||speech +specfication||specification +specfic||specific +specfield||specified +speciefied||specified +specifc||specific +specifed||specified +specificatin||specification +specificaton||specification +specificed||specified +specifing||specifying +specifiying||specifying +specifiy||specify +speficied||specified +speicify||specify +speling||spelling +spinlcok||spinlock +spinock||spinlock +splitted||split +spreaded||spread +spurrious||spurious +sructure||structure +stablilization||stabilization +staically||statically +staion||station +standardss||standards +standartization||standardization +standart||standard +standy||standby +stardard||standard +staticly||statically +statisitcs||statistics +statuss||status +stoped||stopped +stoping||stopping +stoppped||stopped +straming||streaming +struc||struct +structres||structures +strucuture||structure +stuct||struct +stucture||structure +sturcture||structure +subdirectoires||subdirectories +suble||subtle +submited||submitted +submition||submission +substract||subtract +succeded||succeeded +succesfully||successfully +succesful||successful +succesfuly||successfully +successed||succeeded +successfull||successful +successfuly||successfully +suceed||succeed +sucessfully||successfully +sucessful||successful +sucess||success +superflous||superfluous +superseeded||superseded +suplied||supplied +suported||supported +suport||support +suppored||supported +supporing||supporting +supportet||supported +supportin||supporting +suppoted||supported +suppported||supported +suppport||support +supprot||support +supress||suppress +surpressed||suppressed +surpresses||suppresses +susbsystem||subsystem +suspeneded||suspended +suspicously||suspiciously +suspsend||suspend +swaping||swapping +switchs||switches +swithable||switchable +swithced||switched +swithcing||switching +swithc||switch +swithed||switched +swithing||switching +swith||switch +swtich||switch +sychronization||synchronization +sychronously||synchronously +syfs||sysfs +symetric||symmetric +synax||syntax +synchonized||synchronized +synchronuously||synchronously +syncronized||synchronized +syncronize||synchronize +syncronizing||synchronizing +syncronus||synchronous +syste||system +sytem||system +sythesis||synthesis +tagert||target +taht||that +tained||tainted +tansition||transition +tansmit||transmit +tarffic||traffic +targetted||targeted +targetting||targeting +taskelt||tasklet +teh||the +temeprature||temperature +temorary||temporary +temperture||temperature +temproarily||temporarily +theads||threads +therfore||therefore +thier||their +threds||threads +threee||three +threshhold||threshold +thresold||threshold +throtting||throttling +throught||through +thses||these +tiggered||triggered +tiggerring||triggering +tiggers||triggers +timeing||timing +timming||timing +timout||timeout +tipically||typically +tmis||this +tolarance||tolerance +toogle||toggle +torerable||tolerable +torlence||tolerance +trackling||tracking +traget||target +traking||tracking +tramsmitted||transmitted +tramsmit||transmit +tranasction||transaction +tranceiver||transceiver +tranfer||transfer +tranmission||transmission +tranport||transport +transcevier||transceiver +transciever||transceiver +transferd||transferred +transfered||transferred +transfering||transferring +transision||transition +transistioned||transitioned +transmittd||transmitted +transormed||transformed +trasaction||transaction +trasfer||transfer +trasmission||transmission +trasmitter||transmitter +treshold||threshold +trigerred||triggered +trigerring||triggering +trigged||triggered +triggerd||triggered +troughput||throughput +trun||turn +trys||tries +tunning||tuning +ture||true +tyep||type +udpate||update +uesd||used +uknown||unknown +unamed||unnamed +uncommited||uncommitted +uncompatible||incompatible +uncomressed||uncompressed +unconditionaly||unconditionally +undeflow||underflow +undelying||underlying +underun||underrun +unecessary||unnecessary +uneeded||unneeded +unexecpted||unexpected +unexepected||unexpected +unexpcted||unexpected +unexpectd||unexpected +unexpeted||unexpected +unexpexted||unexpected +unfortunatelly||unfortunately +unifiy||unify +uninterruptable||uninterruptible +unintialized||uninitialized +uniterrupted||uninterrupted +unitialized||uninitialized +unkmown||unknown +unknonw||unknown +unknouwn||unknown +unknow||unknown +unknwon||unknown +unkown||unknown +unmached||unmatched +unneccecary||unnecessary +unneccesary||unnecessary +unneccessary||unnecessary +unnecesary||unnecessary +unneded||unneeded +unneedingly||unnecessarily +unnsupported||unsupported +unprecise||imprecise +unpriviledged||unprivileged +unpriviliged||unprivileged +unregester||unregister +unresgister||unregister +unrgesiter||unregister +unsinged||unsigned +unsolicitied||unsolicited +unsolicted||unsolicited +unstabel||unstable +unsuccessfull||unsuccessful +unsuported||unsupported +untill||until +ununsed||unused +unuseful||useless +unuspported||unsupported +unvalid||invalid +upate||update +updtes||updates +upsupported||unsupported +upto||up to +usccess||success +useable||usable +usefule||useful +usefull||useful +usege||usage +usera||users +usualy||usually +usupported||unsupported +utilites||utilities +utillities||utilities +utilties||utilities +utiltity||utility +utitity||utility +utitlty||utility +vaid||valid +vaild||valid +validationg||validating +valide||valid +variantions||variations +varible||variable +varient||variant +vaule||value +veify||verify +verbse||verbose +verfication||verification +veriosn||version +verisons||versions +verison||version +veritical||vertical +versoin||version +verson||version +vicefersa||vice-versa +virtal||virtual +virtaul||virtual +virtiual||virtual +visiters||visitors +vitual||virtual +vunerable||vulnerable +wakeus||wakeups +was't||wasn't +wathdog||watchdog +wating||waiting +wether||whether +whataver||whatever +whcih||which +whenver||whenever +wheter||whether +whe||when +wiat||wait +wierd||weird +wihout||without +wiil||will +wirte||write +withing||within +wnat||want +wont||won't +workarould||workaround +writeing||writing +writting||writing +wtih||with +zombe||zombie +zomebie||zombie diff --git a/scripts/ssl-common.h b/scripts/ssl-common.h new file mode 100644 index 000000000..2db0e1811 --- /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/stackdelta b/scripts/stackdelta new file mode 100755 index 000000000..44d2dfd62 --- /dev/null +++ b/scripts/stackdelta @@ -0,0 +1,60 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 + +# Read two files produced by the stackusage script, and show the +# delta between them. +# +# Currently, only shows changes for functions listed in both files. We +# could add an option to show also functions which have vanished or +# appeared (which would often be due to gcc making other inlining +# decisions). +# +# Another possible option would be a minimum absolute value for the +# delta. +# +# A third possibility is for sorting by delta, but that can be +# achieved by piping to sort -k5,5g. + +sub read_stack_usage_file { + my %su; + my $f = shift; + open(my $fh, '<', $f) + or die "cannot open $f: $!"; + while (<$fh>) { + chomp; + my ($file, $func, $size, $type) = split; + # Old versions of gcc (at least 4.7) have an annoying quirk in + # that a (static) function whose name has been changed into + # for example ext4_find_unwritten_pgoff.isra.11 will show up + # in the .su file with a name of just "11". Since such a + # numeric suffix is likely to change across different + # commits/compilers/.configs or whatever else we're trying to + # tweak, we can't really track those functions, so we just + # silently skip them. + # + # Newer gcc (at least 5.0) report the full name, so again, + # since the suffix is likely to change, we strip it. + next if $func =~ m/^[0-9]+$/; + $func =~ s/\..*$//; + # Line numbers are likely to change; strip those. + $file =~ s/:[0-9]+$//; + $su{"${file}\t${func}"} = {size => $size, type => $type}; + } + close($fh); + return \%su; +} + +@ARGV == 2 + or die "usage: $0 <old> <new>"; + +my $old = read_stack_usage_file($ARGV[0]); +my $new = read_stack_usage_file($ARGV[1]); +my @common = sort grep {exists $new->{$_}} keys %$old; +for (@common) { + my $x = $old->{$_}{size}; + my $y = $new->{$_}{size}; + my $delta = $y - $x; + if ($delta) { + printf "%s\t%d\t%d\t%+d\n", $_, $x, $y, $delta; + } +} diff --git a/scripts/stackusage b/scripts/stackusage new file mode 100755 index 000000000..56ef1ab67 --- /dev/null +++ b/scripts/stackusage @@ -0,0 +1,34 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +outfile="" +now=`date +%s` + +while [ $# -gt 0 ] +do + case "$1" in + -o) + outfile="$2" + shift 2;; + -h) + echo "usage: $0 [-o outfile] <make options/args>" + exit 0;; + *) break;; + esac +done + +if [ -z "$outfile" ] +then + outfile=`mktemp --tmpdir stackusage.$$.XXXX` +fi + +KCFLAGS="${KCFLAGS} -fstack-usage" make "$@" + +# Prepend directory name to file names, remove column information, +# make file:line/function/size/type properly tab-separated. +find . -name '*.su' -newermt "@${now}" -print | \ + xargs perl -MFile::Basename -pe \ + '$d = dirname($ARGV); s#([^:]+:[0-9]+):[0-9]+:#$d/$1\t#;' | \ + sort -k3,3nr > "${outfile}" + +echo "$0: output written to ${outfile}" diff --git a/scripts/subarch.include b/scripts/subarch.include new file mode 100644 index 000000000..c4592d59d --- /dev/null +++ b/scripts/subarch.include @@ -0,0 +1,13 @@ +# SUBARCH tells the usermode build what the underlying arch is. That is set +# first, and if a usermode build is happening, the "ARCH=um" on the command +# line overrides the setting of ARCH below. If a native build is happening, +# then ARCH is assigned, getting whatever value it gets normally, and +# SUBARCH is subsequently ignored. + +SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \ + -e s/sun4u/sparc64/ \ + -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/ \ + -e s/riscv.*/riscv/ -e s/loongarch.*/loongarch/) diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl new file mode 100644 index 000000000..0ab531605 --- /dev/null +++ b/scripts/syscall.tbl @@ -0,0 +1,415 @@ +# 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 +468 common file_getattr sys_file_getattr +469 common file_setattr sys_file_setattr +470 common listns sys_listns +471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/scripts/syscallhdr.sh b/scripts/syscallhdr.sh new file mode 100755 index 000000000..22e34cd46 --- /dev/null +++ b/scripts/syscallhdr.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Generate a syscall number header. +# +# Each line of the syscall table should have the following format: +# +# NR ABI NAME [NATIVE] [COMPAT] +# +# NR syscall number +# ABI ABI name +# NAME syscall name +# NATIVE native entry point (optional) +# COMPAT compat entry point (optional) + +set -e + +usage() { + echo >&2 "usage: $0 [--abis ABIS] [--emit-nr] [--offset OFFSET] [--prefix PREFIX] INFILE OUTFILE" >&2 + echo >&2 + echo >&2 " INFILE input syscall table" + echo >&2 " OUTFILE output header file" + echo >&2 + echo >&2 "options:" + echo >&2 " --abis ABIS ABI(s) to handle (By default, all lines are handled)" + echo >&2 " --emit-nr Emit the macro of the number of syscalls (__NR_syscalls)" + echo >&2 " --offset OFFSET The offset of syscall numbers" + echo >&2 " --prefix PREFIX The prefix to the macro like __NR_<PREFIX><NAME>" + exit 1 +} + +# default unless specified by options +abis= +emit_nr= +offset= +prefix= + +while [ $# -gt 0 ] +do + case $1 in + --abis) + abis=$(echo "($2)" | tr ',' '|') + shift 2;; + --emit-nr) + emit_nr=1 + shift 1;; + --offset) + offset=$2 + shift 2;; + --prefix) + prefix=$2 + shift 2;; + -*) + echo "$1: unknown option" >&2 + usage;; + *) + break;; + esac +done + +if [ $# -ne 2 ]; then + usage +fi + +infile="$1" +outfile="$2" + +guard=_UAPI_ASM_$(basename "$outfile" | + sed -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' \ + -e 's/[^A-Z0-9_]/_/g' -e 's/__/_/g') + +grep -E "^[0-9A-Fa-fXx]+[[:space:]]+$abis" "$infile" | { + echo "#ifndef $guard" + echo "#define $guard" + echo + + max=0 + while read nr abi name native compat ; do + + max=$nr + + if [ -n "$offset" ]; then + nr="($offset + $nr)" + fi + + echo "#define __NR_$prefix$name $nr" + done + + if [ -n "$emit_nr" ]; then + echo + echo "#ifdef __KERNEL__" + echo "#define __NR_${prefix}syscalls $(($max + 1))" + echo "#endif" + fi + + echo + echo "#endif /* $guard */" +} > "$outfile" diff --git a/scripts/syscallnr.sh b/scripts/syscallnr.sh new file mode 100755 index 000000000..3aa29e0dc --- /dev/null +++ b/scripts/syscallnr.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Generate a syscall number header. +# +# Each line of the syscall table should have the following format: +# +# NR ABI NAME [NATIVE] [COMPAT] +# +# NR syscall number +# ABI ABI name +# NAME syscall name +# NATIVE native entry point (optional) +# COMPAT compat entry point (optional) +set -e + +usage() { + echo >&2 "usage: $0 [--abis ABIS] [--prefix PREFIX] INFILE OUTFILE" >&2 + echo >&2 + echo >&2 " INFILE input syscall table" + echo >&2 " OUTFILE output header file" + echo >&2 + echo >&2 "options:" + echo >&2 " --abis ABIS ABI(s) to handle (By default, all lines are handled)" + echo >&2 " --prefix PREFIX The prefix to the macro like __NR_<PREFIX><NAME>" + exit 1 +} + +# default unless specified by options +abis= +prefix= + +while [ $# -gt 0 ] +do + case $1 in + --abis) + abis=$(echo "($2)" | tr ',' '|') + shift 2;; + --prefix) + prefix=$2 + shift 2;; + -*) + echo "$1: unknown option" >&2 + usage;; + *) + break;; + esac +done + +if [ $# -ne 2 ]; then + usage +fi + +infile="$1" +outfile="$2" + +guard=_ASM_$(basename "$outfile" | + sed -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' \ + -e 's/[^A-Z0-9_]/_/g' -e 's/__/_/g') + +grep -E "^[0-9A-Fa-fXx]+[[:space:]]+$abis" "$infile" | sort -n | { + echo "#ifndef $guard" + echo "#define $guard" + echo + + max=0 + while read nr abi name native compat ; do + max=$nr + done + + echo "#define __NR_${prefix}syscalls $(($max + 1))" + echo + echo "#endif /* $guard */" +} > "$outfile" diff --git a/scripts/syscalltbl.sh b/scripts/syscalltbl.sh new file mode 100755 index 000000000..6a903b87a --- /dev/null +++ b/scripts/syscalltbl.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Generate a syscall table header. +# +# Each line of the syscall table should have the following format: +# +# NR ABI NAME [NATIVE] [COMPAT] +# +# NR syscall number +# ABI ABI name +# NAME syscall name +# NATIVE native entry point (optional) +# COMPAT compat entry point (optional) + +set -e + +usage() { + echo >&2 "usage: $0 [--abis ABIS] INFILE OUTFILE" >&2 + echo >&2 + echo >&2 " INFILE input syscall table" + echo >&2 " OUTFILE output header file" + echo >&2 + echo >&2 "options:" + echo >&2 " --abis ABIS ABI(s) to handle (By default, all lines are handled)" + exit 1 +} + +# default unless specified by options +abis= + +while [ $# -gt 0 ] +do + case $1 in + --abis) + abis=$(echo "($2)" | tr ',' '|') + shift 2;; + -*) + echo "$1: unknown option" >&2 + usage;; + *) + break;; + esac +done + +if [ $# -ne 2 ]; then + usage +fi + +infile="$1" +outfile="$2" + +nxt=0 + +grep -E "^[0-9]+[[:space:]]+$abis" "$infile" | { + + 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 + exit 1 + fi + + while [ $nxt -lt $nr ]; do + echo "__SYSCALL($nxt, sys_ni_syscall)" + nxt=$((nxt + 1)) + done + + 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)" + else + echo "__SYSCALL($nr, sys_ni_syscall)" + fi + nxt=$((nr + 1)) + done +} > "$outfile" diff --git a/scripts/tags.sh b/scripts/tags.sh new file mode 100755 index 000000000..41e38df96 --- /dev/null +++ b/scripts/tags.sh @@ -0,0 +1,384 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +# Generate tags or cscope files +# Usage tags.sh <mode> +# +# mode may be any of: tags, gtags, TAGS, cscope +# +# Uses the following environment variables: +# SUBARCH, SRCARCH, srctree + +if [[ "$KBUILD_VERBOSE" =~ 1 ]]; then + set -x +fi + +# RCS_FIND_IGNORE has escaped ()s -- remove them. +ignore="$(echo "$RCS_FIND_IGNORE" | sed 's|\\||g' )" +# tags and cscope files should also ignore MODVERSION *.mod.c files +ignore="$ignore ( -name *.mod.c ) -prune -o" + +# ignore arbitrary directories +if [ -n "${IGNORE_DIRS}" ]; then + for i in ${IGNORE_DIRS}; do + ignore="${ignore} ( -path $i ) -prune -o" + done +fi + +# Use make KBUILD_ABS_SRCTREE=1 {tags|cscope} +# to force full paths for a non-O= build +if [ "${srctree}" = "." -o -z "${srctree}" ]; then + tree= +else + tree=${srctree}/ +fi + +# gtags(1) refuses to index any file outside of its current working dir. +# If gtags indexing is requested and the build output directory is not +# the kernel source tree, index all files in absolute-path form. +if [[ "$1" == "gtags" && -n "${tree}" ]]; then + tree=$(realpath "$tree")/ +fi + +# Detect if ALLSOURCE_ARCHS is set. If not, we assume SRCARCH +if [ "${ALLSOURCE_ARCHS}" = "" ]; then + ALLSOURCE_ARCHS=${SRCARCH} +elif [ "${ALLSOURCE_ARCHS}" = "all" ]; then + ALLSOURCE_ARCHS=$(find ${tree}arch/ -mindepth 1 -maxdepth 1 -type d -printf '%f ') +fi + +setup_name_pattern() +{ + pattern=() + for ext; do + if [ ${#pattern[@]} -gt 0 ]; then + pattern+=("-o" "-name" "$ext") + else + pattern+=("(" "-name" "$ext") + fi + done + if [ ${#pattern[@]} -gt 0 ]; then + pattern+=(")") + fi +} + +# find sources in arch/$1 +find_arch_sources() +{ + for i in $archincludedir; do + local prune="$prune ( -path $i ) -prune -o" + done + local src=${tree}arch/$1 + shift + setup_name_pattern "$@" + find $src $ignore $prune "${pattern[@]}" -not -type l -print; +} + +# find sources in arch/$1/include +find_arch_include_sources() +{ + local include=$(find ${tree}arch/$1/ -name include -type d -print); + if [ -n "$include" ]; then + archincludedir="$archincludedir $include" + shift + setup_name_pattern "$@" + find $include $ignore "${pattern[@]}" -not -type l -print; + fi +} + +# find sources in include/ +find_include_sources() +{ + setup_name_pattern "$@" + find ${tree}include $ignore -name config -prune -o "${pattern[@]}" \ + -not -type l -print; +} + +# find sources in rest of tree +# we could benefit from a list of dirs to search in here +find_other_sources() +{ + setup_name_pattern "$@" + find ${tree}* $ignore \ + \( -path ${tree}include -o -path ${tree}arch -o -name '.tmp_*' \) -prune -o \ + "${pattern[@]}" -not -type l -print; +} + +all_sources() +{ + find_arch_include_sources ${SRCARCH} '*.[chS]' '*.rs' + if [ -n "$archinclude" ]; then + find_arch_include_sources $archinclude '*.[chS]' '*.rs' + fi + find_include_sources '*.[chS]' '*.rs' + for arch in $ALLSOURCE_ARCHS + do + find_arch_sources $arch '*.[chS]' '*.rs' + done + find_other_sources '*.[chS]' '*.rs' +} + +all_compiled_sources() +{ + { + echo include/generated/autoconf.h + find $ignore -name "*.cmd" -exec \ + grep -Poh '(?<=^ )\S+\.([chS]|rs)(?=\s)|(?<== )\S+\.(?1)(?=$)' {} \+ | + awk '!a[$0]++' + } | xargs realpath -esq $([ -z "$KBUILD_ABS_SRCTREE" ] && echo --relative-to=.) | + sort -u +} + +all_target_sources() +{ + if [ -n "$COMPILED_SOURCE" ]; then + all_compiled_sources + else + all_sources + fi +} + +all_kconfigs() +{ + find ${tree}arch/ -maxdepth 1 $ignore \ + -name "Kconfig*" -not -type l -print; + for arch in $ALLSOURCE_ARCHS; do + find_arch_sources $arch 'Kconfig*' + done + find_other_sources 'Kconfig*' +} + +docscope() +{ + (echo \-k; echo \-q; all_target_sources) > cscope.files + cscope -b -f cscope.out +} + +dogtags() +{ + all_target_sources | gtags -i -C "${tree:-.}" -f - "$PWD" +} + +# Basic regular expressions with an optional /kind-spec/ for ctags and +# the following limitations: +# - No regex modifiers +# - Use \{0,1\} instead of \?, because etags expects an unescaped ? +# - \s is not working with etags, use a space or [ \t] +# - \w works, but does not match underscores in etags +# - etags regular expressions have to match at the start of a line; +# 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/' + '/^DEFINE_EVENT([^,)]*,[[:space:]]*\([[:alnum:]_]*\).*/trace_\1/' + '/^DEFINE_INSN_CACHE_OPS([[:space:]]*\([[:alnum:]_]*\).*/get_\1_slot/' + '/^DEFINE_INSN_CACHE_OPS([[:space:]]*\([[:alnum:]_]*\).*/free_\1_slot/' + '/^PAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/Page\1/' + '/^PAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/SetPage\1/' + '/^PAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/ClearPage\1/' + '/^TESTSETFLAG([[:space:]]*\([[:alnum:]_]*\).*/TestSetPage\1/' + '/^TESTPAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/Page\1/' + '/^SETPAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/SetPage\1/' + '/\<__SETPAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/__SetPage\1/' + '/\<TESTCLEARFLAG([[:space:]]*\([[:alnum:]_]*\).*/TestClearPage\1/' + '/\<__TESTCLEARFLAG([[:space:]]*\([[:alnum:]_]*\).*/TestClearPage\1/' + '/\<CLEARPAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/ClearPage\1/' + '/\<__CLEARPAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/__ClearPage\1/' + '/^__PAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/__SetPage\1/' + '/^__PAGEFLAG([[:space:]]*\([[:alnum:]_]*\).*/__ClearPage\1/' + '/^PAGEFLAG_FALSE([[:space:]]*\([[:alnum:]_]*\).*/Page\1/' + '/\<TESTSCFLAG([[:space:]]*\([[:alnum:]_]*\).*/TestSetPage\1/' + '/\<TESTSCFLAG([[:space:]]*\([[:alnum:]_]*\).*/TestClearPage\1/' + '/\<SETPAGEFLAG_NOOP([[:space:]]*\([[:alnum:]_]*\).*/SetPage\1/' + '/\<CLEARPAGEFLAG_NOOP([[:space:]]*\([[:alnum:]_]*\).*/ClearPage\1/' + '/\<__CLEARPAGEFLAG_NOOP([[:space:]]*\([[:alnum:]_]*\).*/__ClearPage\1/' + '/\<TESTCLEARFLAG_FALSE([[:space:]]*\([[:alnum:]_]*\).*/TestClearPage\1/' + '/^PAGE_TYPE_OPS([[:space:]]*\([[:alnum:]_]*\).*/Page\1/' + '/^PAGE_TYPE_OPS([[:space:]]*\([[:alnum:]_]*\).*/__SetPage\1/' + '/^PAGE_TYPE_OPS([[:space:]]*\([[:alnum:]_]*\).*/__ClearPage\1/' + '/^TASK_PFA_TEST([^,]*,[[:space:]]*\([[:alnum:]_]*\))/task_\1/' + '/^TASK_PFA_SET([^,]*,[[:space:]]*\([[:alnum:]_]*\))/task_set_\1/' + '/^TASK_PFA_CLEAR([^,]*,[[:space:]]*\([[:alnum:]_]*\))/task_clear_\1/' + '/^DEF_MMIO_\(IN\|OUT\)_[XD]([[:space:]]*\([[:alnum:]_]*\),[^)]*)/\2/' + '/^DEBUGGER_BOILERPLATE([[:space:]]*\([[:alnum:]_]*\))/\1/' + '/^DEF_PCI_AC_\(\|NO\)RET([[:space:]]*\([[:alnum:]_]*\).*/\2/' + '/^PCI_OP_READ([[:space:]]*\(\w*\).*[1-4])/pci_bus_read_config_\1/' + '/^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/' + '/\(^\|\s\)\(\|L\|H\)LIST_HEAD([[:space:]]*\([[:alnum:]_]*\)/\3/v/' + '/\(^\|\s\)RADIX_TREE([[:space:]]*\([[:alnum:]_]*\)/\2/v/' + '/\<DEFINE_PER_CPU([^,]*,[[:space:]]*\([[:alnum:]_]*\)/\1/v/' + '/\<DEFINE_PER_CPU_SHARED_ALIGNED([^,]*,[[:space:]]*\([[:alnum:]_]*\)/\1/v/' + '/\<DECLARE_WAIT_QUEUE_HEAD([[:space:]]*\([[:alnum:]_]*\)/\1/v/' + '/\<DECLARE_\(TASKLET\|WORK\|DELAYED_WORK\)([[:space:]]*\([[:alnum:]_]*\)/\2/v/' + '/\(^\s\)OFFSET([[:space:]]*\([[:alnum:]_]*\)/\2/v/' + '/\(^\s\)DEFINE([[:space:]]*\([[:alnum:]_]*\)/\2/v/' + '/\<\(DEFINE\|DECLARE\)_HASHTABLE([[:space:]]*\([[:alnum:]_]*\)/\2/v/' + '/\<DEFINE_ID\(R\|A\)([[:space:]]*\([[:alnum:]_]\+\)/\2/' + '/\<DEFINE_WD_CLASS([[:space:]]*\([[:alnum:]_]\+\)/\1/' + '/\<ATOMIC_NOTIFIER_HEAD([[:space:]]*\([[:alnum:]_]\+\)/\1/' + '/\<RAW_NOTIFIER_HEAD([[:space:]]*\([[:alnum:]_]\+\)/\1/' + '/\<DECLARE_FAULT_ATTR([[:space:]]*\([[:alnum:]_]\+\)/\1/' + '/\<BLOCKING_NOTIFIER_HEAD([[:space:]]*\([[:alnum:]_]\+\)/\1/' + '/\<DEVICE_ATTR_\(RW\|RO\|WO\)([[:space:]]*\([[:alnum:]_]\+\)/dev_attr_\2/' + '/\<DRIVER_ATTR_\(RW\|RO\|WO\)([[:space:]]*\([[:alnum:]_]\+\)/driver_attr_\2/' + '/\<\(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/' + '/^context_lock_struct(\([^,)]*\)[^)]*)/struct \1/' +) +regex_kconfig=( + '/^[[:blank:]]*\(menu\|\)config[[:blank:]]\+\([[:alnum:]_]\+\)/\2/' + '/^[[:blank:]]*\(menu\|\)config[[:blank:]]\+\([[:alnum:]_]\+\)/CONFIG_\2/' +) +setup_regex() +{ + local mode=$1 lang tmp=() r + shift + + regex=() + for lang; do + case "$lang" in + asm) tmp=("${regex_asm[@]}") ;; + c) tmp=("${regex_c[@]}") ;; + kconfig) tmp=("${regex_kconfig[@]}") ;; + esac + for r in "${tmp[@]}"; do + if test "$mode" = "exuberant"; then + regex[${#regex[@]}]="--regex-$lang=${r}b" + else + # Remove ctags /kind-spec/ + case "$r" in + /*/*/?/) + r=${r%?/} + esac + # Prepend ^[^#] unless already anchored + case "$r" in + /^*) ;; + *) + r="/^[^#]*${r#/}" + esac + regex[${#regex[@]}]="--regex=$r" + fi + done + done +} + +exuberant() +{ + CTAGS_EXTRA="extra" + if $1 --version 2>&1 | grep -iq universal; then + CTAGS_EXTRA="extras" + fi + setup_regex exuberant asm c + # 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[@]}" + + KCONFIG_ARGS=() + if ! $1 --list-languages | grep -iq kconfig; then + setup_regex exuberant kconfig + KCONFIG_ARGS=(--langdef=kconfig --language-force=kconfig "${regex[@]}") + fi + all_kconfigs | xargs $1 -a "${KCONFIG_ARGS[@]}" +} + +emacs() +{ + setup_regex emacs asm c + all_target_sources | xargs $1 -a "${regex[@]}" + + setup_regex emacs kconfig + all_kconfigs | xargs $1 -a "${regex[@]}" +} + +xtags() +{ + if $1 --version 2>&1 | grep -iq exuberant; then + exuberant $1 + elif $1 --version 2>&1 | grep -iq emacs; then + emacs $1 + else + all_target_sources | xargs $1 -a + fi +} + +# Support um (which uses SUBARCH) +if [ "${ARCH}" = "um" ]; then + if [ "$SUBARCH" = "i386" ]; then + archinclude=x86 + elif [ "$SUBARCH" = "x86_64" ]; then + archinclude=x86 + else + archinclude=${SUBARCH} + fi +fi + +remove_structs= +case "$1" in + "cscope") + docscope + ;; + + "gtags") + dogtags + ;; + + "tags") + rm -f tags + xtags ${CTAGS:-ctags} + remove_structs=y + ;; + + "TAGS") + rm -f TAGS + xtags etags + remove_structs=y + ;; +esac + +# Remove structure forward declarations. +if [ -n "$remove_structs" ]; then + LC_ALL=C sed -i -e '/^\([a-zA-Z_][a-zA-Z0-9_]*\)\t.*\t\/\^struct \1;.*\$\/;"\tx$/d' $1 +fi diff --git a/scripts/timer_migration_tree.py b/scripts/timer_migration_tree.py new file mode 100755 index 000000000..faac9de85 --- /dev/null +++ b/scripts/timer_migration_tree.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +Draw the timer migration tree. + +1) Boot with trace_event==tmigr_connect_cpu_parent,tmigr_connect_child_parent +2) ./timer_migration_tree.py < /sys/kernel/tracing/trace +""" + +import re, sys +from ete3 import Tree + +class Node: + def __init__(self, group): + self.group = group + self.children = [] + self.parent = None + self.num_children = 0 + self.groupmask = 0 + self.lvl = -1 + + def set_groupmask(self, groupmask): + self.groupmask = groupmask + + def set_parent(self, parent): + self.parent = parent + + def add_child(self, child): + self.children.append(child) + + def set_lvl(self, lvl): + self.lvl = lvl + + def set_numa(self, numa): + self.numa = numa + + def set_num_children(self, num_children): + self.num_children = num_children + + def __repr__(self): + if self.parent: + parent_grp = self.parent.group + else: + parent_grp = "-" + return "Group: %s mask: %s parent: %s lvl: %d numa: %d num_children: %d" % (self.group, self.groupmask, parent_grp, self.lvl, self.numa, self.num_children) + +hierarchies = { } + +def get_hierarchy(capacity): + if capacity not in hierarchies: + hierarchies[capacity] = {} + return hierarchies[capacity] + +def get_node(capacity, group): + hier = get_hierarchy(capacity) + if group in hier: + return hier[group] + else: + n = Node(group) + hier[group] = n + return n + +def tmigr_connect_cpu_parent(ts, line): + s = re.search("tmigr_connect_cpu_parent: cpu=([0-9]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) numa=([-]?[0-9]+) capacity=([-]?[0-9]+) num_children=([0-9]+)", line) + if s is None: + return False + (cpu, groupmask, parent, lvl, numa, capacity, num_children) = (int(s.group(1)), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6)), int(s.group(7))) + n = get_node(capacity, cpu) + p = get_node(capacity, parent) + n.set_parent(p) + n.set_groupmask(groupmask) + n.set_lvl(-1) + p.set_lvl(lvl) + p.set_numa(numa) + n.set_numa(numa) + p.set_num_children(num_children) + p.add_child(n) + +def tmigr_connect_child_parent(ts, line): + s = re.search("tmigr_connect_child_parent: group=([0-9a-zA-Z]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) numa=([-]?[0-9]+) capacity=([-]?[0-9]+) num_children=([0-9]+)", line) + if s is None: + return False + (group, groupmask, parent, lvl, numa, capacity, num_children) = (s.group(1), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6)), int(s.group(7))) + n = get_node(capacity, group) + p = get_node(capacity, parent) + n.set_parent(p) + n.set_groupmask(groupmask) + p.set_lvl(lvl) + p.set_numa(numa) + p.set_num_children(num_children) + p.add_child(n) + +def populate(enode, node): + enode = enode.add_child(name = node.group) + enode.add_feature("groupmask", "m:%s" % node.groupmask) + enode.add_feature("lvl", "lvl:%d" % node.lvl) + enode.add_feature("numa", "node %d" % node.numa) + enode.add_feature("num_children", "c=%d" % node.num_children) + for child in node.children: + populate(enode, child) + +if __name__ == "__main__": + for line in sys.stdin: + s = re.search("([0-9]+[.][0-9]{6}): (.+?)$", line, re.S) + if s is not None: + if tmigr_connect_cpu_parent(float(s.group(1)), s.group(2)): + continue + if tmigr_connect_child_parent(float(s.group(1)), s.group(2)): + continue + + for cap in hierarchies: + h = hierarchies[cap] + print("Tree for capacity %d" % cap) + for k in h: + n = h[k] + while n.parent != None: + n = n.parent + root = Tree() + populate(root, n) + print(root.get_ascii(show_internal=True, attributes=["name", "numa", "lvl"])) + break diff --git a/scripts/tools-support-relr.sh b/scripts/tools-support-relr.sh new file mode 100755 index 000000000..4c121946e --- /dev/null +++ b/scripts/tools-support-relr.sh @@ -0,0 +1,21 @@ +#!/bin/sh -eu +# SPDX-License-Identifier: GPL-2.0 + +tmp_file=$(mktemp) +trap "rm -f $tmp_file.o $tmp_file $tmp_file.bin" EXIT + +cat << "END" | $CC -c -x c - -o $tmp_file.o >/dev/null 2>&1 +void *p = &p; +END + +# ld.lld before 15 did not support -z pack-relative-relocs. +if ! $LD $tmp_file.o -shared -Bsymbolic --pack-dyn-relocs=relr -o $tmp_file 2>/dev/null; then + $LD $tmp_file.o -shared -Bsymbolic -z pack-relative-relocs -o $tmp_file 2>&1 | + grep -q pack-relative-relocs && exit 1 +fi + +# Despite printing an error message, GNU nm still exits with exit code 0 if it +# sees a relr section. So we need to check that nothing is printed to stderr. +test -z "$($NM $tmp_file 2>&1 >/dev/null)" + +$OBJCOPY -O binary $tmp_file $tmp_file.bin diff --git a/scripts/tracepoint-update.c b/scripts/tracepoint-update.c new file mode 100644 index 000000000..5cf43c0aa --- /dev/null +++ b/scripts/tracepoint-update.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <sys/types.h> +#include <sys/stat.h> +#include <getopt.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <pthread.h> + +#include "elf-parse.h" + +static Elf_Shdr *check_data_sec; +static Elf_Shdr *tracepoint_data_sec; + +static inline void *get_index(void *start, int entsize, int index) +{ + return start + (entsize * index); +} + +static int compare_strings(const void *a, const void *b) +{ + const char *av = *(const char **)a; + const char *bv = *(const char **)b; + + return strcmp(av, bv); +} + +struct elf_tracepoint { + Elf_Ehdr *ehdr; + const char **array; + int count; +}; + +#define REALLOC_SIZE (1 << 10) +#define REALLOC_MASK (REALLOC_SIZE - 1) + +static int add_string(const char *str, const char ***vals, int *count) +{ + const char **array = *vals; + + if (!(*count & REALLOC_MASK)) { + int size = (*count) + REALLOC_SIZE; + + array = realloc(array, sizeof(char *) * size); + if (!array) { + fprintf(stderr, "Failed memory allocation\n"); + free(*vals); + *vals = NULL; + return -1; + } + *vals = array; + } + + array[(*count)++] = str; + return 0; +} + +/** + * for_each_shdr_str - iterator that reads strings that are in an ELF section. + * @len: "int" to hold the length of the current string + * @ehdr: A pointer to the ehdr of the ELF file + * @sec: The section that has the strings to iterate on + * + * This is a for loop that iterates over all the nul terminated strings + * that are in a given ELF section. The variable "str" will hold + * the current string for each iteration and the passed in @len will + * contain the strlen() of that string. + */ +#define for_each_shdr_str(len, ehdr, sec) \ + for (const char *str = (void *)(ehdr) + shdr_offset(sec), \ + *end = str + shdr_size(sec); \ + len = strlen(str), str < end; \ + str += (len) + 1) + + +static void make_trace_array(struct elf_tracepoint *etrace) +{ + Elf_Ehdr *ehdr = etrace->ehdr; + const char **vals = NULL; + int count = 0; + int len; + + etrace->array = NULL; + + /* + * The __tracepoint_check section is filled with strings of the + * names of tracepoints (in tracepoint_strings). Create an array + * that points to each string and then sort the array. + */ + for_each_shdr_str(len, ehdr, check_data_sec) { + if (!len) + continue; + if (add_string(str, &vals, &count) < 0) + return; + } + + /* If CONFIG_TRACEPOINT_VERIFY_USED is not set, there's nothing to do */ + if (!count) + return; + + qsort(vals, count, sizeof(char *), compare_strings); + + etrace->array = vals; + etrace->count = count; +} + +static int find_event(const char *str, void *array, size_t size) +{ + return bsearch(&str, array, size, sizeof(char *), compare_strings) != NULL; +} + +static void check_tracepoints(struct elf_tracepoint *etrace, const char *fname) +{ + Elf_Ehdr *ehdr = etrace->ehdr; + int len; + + if (!etrace->array) + return; + + /* + * The __tracepoints_strings section holds all the names of the + * defined tracepoints. If any of them are not in the + * __tracepoint_check_section it means they are not used. + */ + for_each_shdr_str(len, ehdr, tracepoint_data_sec) { + if (!len) + continue; + if (!find_event(str, etrace->array, etrace->count)) { + fprintf(stderr, "warning: tracepoint '%s' is unused", str); + if (fname) + fprintf(stderr, " in module %s\n", fname); + else + fprintf(stderr, "\n"); + } + } + + free(etrace->array); +} + +static void *tracepoint_check(struct elf_tracepoint *etrace, const char *fname) +{ + make_trace_array(etrace); + check_tracepoints(etrace, fname); + + return NULL; +} + +static int process_tracepoints(bool mod, void *addr, const char *fname) +{ + struct elf_tracepoint etrace = {0}; + Elf_Ehdr *ehdr = addr; + Elf_Shdr *shdr_start; + Elf_Shdr *string_sec; + const char *secstrings; + unsigned int shnum; + unsigned int shstrndx; + int shentsize; + int idx; + int done = 2; + + 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 (int i = 0; done && i < shnum; i++) { + Elf_Shdr *shdr = get_index(shdr_start, shentsize, i); + + idx = shdr_name(shdr); + + /* locate the __tracepoint_check in vmlinux */ + if (!strcmp(secstrings + idx, "__tracepoint_check")) { + check_data_sec = shdr; + done--; + } + + /* locate the __tracepoints_ptrs section in vmlinux */ + if (!strcmp(secstrings + idx, "__tracepoints_strings")) { + tracepoint_data_sec = shdr; + done--; + } + } + + /* + * Modules may not have either section. But if it has one section, + * it should have both of them. + */ + if (mod && !check_data_sec && !tracepoint_data_sec) + return 0; + + if (!check_data_sec) { + if (mod) { + fprintf(stderr, "warning: Module %s has only unused tracepoints\n", fname); + /* Do not fail build */ + return 0; + } + fprintf(stderr, "no __tracepoint_check in file: %s\n", fname); + return -1; + } + + if (!tracepoint_data_sec) { + /* A module may reference only exported tracepoints */ + if (mod) + return 0; + fprintf(stderr, "no __tracepoint_strings in file: %s\n", fname); + return -1; + } + + if (!mod) + fname = NULL; + + etrace.ehdr = ehdr; + tracepoint_check(&etrace, fname); + return 0; +} + +int main(int argc, char *argv[]) +{ + int n_error = 0; + size_t size = 0; + void *addr = NULL; + bool mod = false; + + if (argc > 1 && strcmp(argv[1], "--module") == 0) { + mod = true; + argc--; + argv++; + } + + if (argc < 2) { + if (mod) + fprintf(stderr, "usage: tracepoint-update --module module...\n"); + else + fprintf(stderr, "usage: tracepoint-update vmlinux...\n"); + return 0; + } + + /* Process each file in turn, allowing deep failure. */ + for (int i = 1; i < argc; i++) { + addr = elf_map(argv[i], &size, 1 << ET_REL); + if (!addr) { + ++n_error; + continue; + } + + if (process_tracepoints(mod, addr, argv[i])) + ++n_error; + + elf_unmap(addr, size); + } + + return !!n_error; +} diff --git a/scripts/tracing/ftrace-bisect.sh b/scripts/tracing/ftrace-bisect.sh new file mode 100755 index 000000000..bb4f59262 --- /dev/null +++ b/scripts/tracing/ftrace-bisect.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Here's how to use this: +# +# This script is used to help find functions that are being traced by function +# tracer or function graph tracing that causes the machine to reboot, hang, or +# crash. Here's the steps to take. +# +# First, determine if function tracing is working with a single function: +# +# (note, if this is a problem with function_graph tracing, then simply +# replace "function" with "function_graph" in the following steps). +# +# # cd /sys/kernel/tracing +# # echo schedule > set_ftrace_filter +# # echo function > current_tracer +# +# If this works, then we know that something is being traced that shouldn't be. +# +# # echo nop > current_tracer +# +# Starting with v5.1 this can be done with numbers, making it much faster: +# +# The old (slow) way, for kernels before v5.1. +# +# [old-way] # cat available_filter_functions > ~/full-file +# +# [old-way] *** Note *** this process will take several minutes to update the +# [old-way] filters. Setting multiple functions is an O(n^2) operation, and we +# [old-way] are dealing with thousands of functions. So go have coffee, talk +# [old-way] with your coworkers, read facebook. And eventually, this operation +# [old-way] will end. +# +# The new way (using numbers) is an O(n) operation, and usually takes less than a second. +# +# seq `wc -l available_filter_functions | cut -d' ' -f1` > ~/full-file +# +# This will create a sequence of numbers that match the functions in +# available_filter_functions, and when echoing in a number into the +# set_ftrace_filter file, it will enable the corresponding function in +# O(1) time. Making enabling all functions O(n) where n is the number of +# functions to enable. +# +# For either the new or old way, the rest of the operations remain the same. +# +# # ftrace-bisect ~/full-file ~/test-file ~/non-test-file +# # cat ~/test-file > set_ftrace_filter +# +# # echo function > current_tracer +# +# If it crashes, we know that ~/test-file has a bad function. +# +# Reboot back to test kernel. +# +# # cd /sys/kernel/tracing +# # mv ~/test-file ~/full-file +# +# If it didn't crash. +# +# # echo nop > current_tracer +# # mv ~/non-test-file ~/full-file +# +# Get rid of the other test file from previous run (or save them off somewhere). +# # rm -f ~/test-file ~/non-test-file +# +# And start again: +# +# # ftrace-bisect ~/full-file ~/test-file ~/non-test-file +# +# The good thing is, because this cuts the number of functions in ~/test-file +# by half, the cat of it into set_ftrace_filter takes half as long each +# iteration, so don't talk so much at the water cooler the second time. +# +# Eventually, if you did this correctly, you will get down to the problem +# function, and all we need to do is to notrace it. +# +# The way to figure out if the problem function is bad, just do: +# +# # echo <problem-function> > set_ftrace_notrace +# # echo > set_ftrace_filter +# # echo function > current_tracer +# +# And if it doesn't crash, we are done. +# +# If it does crash, do this again (there's more than one problem function) +# but you need to echo the problem function(s) into set_ftrace_notrace before +# enabling function tracing in the above steps. Or if you can compile the +# kernel, annotate the problem functions with "notrace" and start again. +# + + +if [ $# -ne 3 ]; then + echo 'usage: ftrace-bisect full-file test-file non-test-file' + exit +fi + +full=$1 +test=$2 +nontest=$3 + +x=`cat $full | wc -l` +if [ $x -eq 1 ]; then + echo "There's only one function left, must be the bad one" + cat $full + exit 0 +fi + +let x=$x/2 +let y=$x+1 + +if [ ! -f $full ]; then + echo "$full does not exist" + exit 1 +fi + +if [ -f $test ]; then + echo -n "$test exists, delete it? [y/N]" + read a + if [ "$a" != "y" -a "$a" != "Y" ]; then + exit 1 + fi +fi + +if [ -f $nontest ]; then + echo -n "$nontest exists, delete it? [y/N]" + read a + if [ "$a" != "y" -a "$a" != "Y" ]; then + exit 1 + fi +fi + +sed -ne "1,${x}p" $full > $test +sed -ne "$y,\$p" $full > $nontest diff --git a/scripts/unifdef.c b/scripts/unifdef.c new file mode 100644 index 000000000..ff15efd6e --- /dev/null +++ b/scripts/unifdef.c @@ -0,0 +1,1225 @@ +/* + * Copyright (c) 2002 - 2011 Tony Finch <dot@dotat.at> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * unifdef - remove ifdef'ed lines + * + * This code was derived from software contributed to Berkeley by Dave Yost. + * It was rewritten to support ANSI C by Tony Finch. The original version + * of unifdef carried the 4-clause BSD copyright licence. None of its code + * remains in this version (though some of the names remain) so it now + * carries a more liberal licence. + * + * Wishlist: + * provide an option which will append the name of the + * appropriate symbol after #else's and #endif's + * provide an option which will check symbols after + * #else's and #endif's to see that they match their + * corresponding #ifdef or #ifndef + * + * These require better buffer handling, which would also make + * it possible to handle all "dodgy" directives correctly. + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +const char copyright[] = + "@(#) $Version: unifdef-2.5 $\n" + "@(#) $Author: Tony Finch (dot@dotat.at) $\n" + "@(#) $URL: http://dotat.at/prog/unifdef $\n" +; + +/* types of input lines: */ +typedef enum { + LT_TRUEI, /* a true #if with ignore flag */ + LT_FALSEI, /* a false #if with ignore flag */ + LT_IF, /* an unknown #if */ + LT_TRUE, /* a true #if */ + LT_FALSE, /* a false #if */ + LT_ELIF, /* an unknown #elif */ + LT_ELTRUE, /* a true #elif */ + LT_ELFALSE, /* a false #elif */ + LT_ELSE, /* #else */ + LT_ENDIF, /* #endif */ + LT_DODGY, /* flag: directive is not on one line */ + LT_DODGY_LAST = LT_DODGY + LT_ENDIF, + LT_PLAIN, /* ordinary line */ + LT_EOF, /* end of file */ + LT_ERROR, /* unevaluable #if */ + LT_COUNT +} Linetype; + +static char const * const linetype_name[] = { + "TRUEI", "FALSEI", "IF", "TRUE", "FALSE", + "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF", + "DODGY TRUEI", "DODGY FALSEI", + "DODGY IF", "DODGY TRUE", "DODGY FALSE", + "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE", + "DODGY ELSE", "DODGY ENDIF", + "PLAIN", "EOF", "ERROR" +}; + +/* state of #if processing */ +typedef enum { + IS_OUTSIDE, + IS_FALSE_PREFIX, /* false #if followed by false #elifs */ + IS_TRUE_PREFIX, /* first non-false #(el)if is true */ + IS_PASS_MIDDLE, /* first non-false #(el)if is unknown */ + IS_FALSE_MIDDLE, /* a false #elif after a pass state */ + IS_TRUE_MIDDLE, /* a true #elif after a pass state */ + IS_PASS_ELSE, /* an else after a pass state */ + IS_FALSE_ELSE, /* an else after a true state */ + IS_TRUE_ELSE, /* an else after only false states */ + IS_FALSE_TRAILER, /* #elifs after a true are false */ + IS_COUNT +} Ifstate; + +static char const * const ifstate_name[] = { + "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX", + "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE", + "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE", + "FALSE_TRAILER" +}; + +/* state of comment parser */ +typedef enum { + NO_COMMENT = false, /* outside a comment */ + C_COMMENT, /* in a comment like this one */ + CXX_COMMENT, /* between // and end of line */ + STARTING_COMMENT, /* just after slash-backslash-newline */ + FINISHING_COMMENT, /* star-backslash-newline in a C comment */ + CHAR_LITERAL, /* inside '' */ + STRING_LITERAL /* inside "" */ +} Comment_state; + +static char const * const comment_name[] = { + "NO", "C", "CXX", "STARTING", "FINISHING", "CHAR", "STRING" +}; + +/* state of preprocessor line parser */ +typedef enum { + LS_START, /* only space and comments on this line */ + LS_HASH, /* only space, comments, and a hash */ + LS_DIRTY /* this line can't be a preprocessor line */ +} Line_state; + +static char const * const linestate_name[] = { + "START", "HASH", "DIRTY" +}; + +/* + * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1 + */ +#define MAXDEPTH 64 /* maximum #if nesting */ +#define MAXLINE 4096 /* maximum length of line */ +#define MAXSYMS 4096 /* maximum number of symbols */ + +/* + * Sometimes when editing a keyword the replacement text is longer, so + * we leave some space at the end of the tline buffer to accommodate this. + */ +#define EDITSLOP 10 + +/* + * For temporary filenames + */ +#define TEMPLATE "unifdef.XXXXXX" + +/* + * Globals. + */ + +static bool compblank; /* -B: compress blank lines */ +static bool lnblank; /* -b: blank deleted lines */ +static bool complement; /* -c: do the complement */ +static bool debugging; /* -d: debugging reports */ +static bool iocccok; /* -e: fewer IOCCC errors */ +static bool strictlogic; /* -K: keep ambiguous #ifs */ +static bool killconsts; /* -k: eval constant #ifs */ +static bool lnnum; /* -n: add #line directives */ +static bool symlist; /* -s: output symbol list */ +static bool symdepth; /* -S: output symbol depth */ +static bool text; /* -t: this is a text file */ + +static const char *symname[MAXSYMS]; /* symbol name */ +static const char *value[MAXSYMS]; /* -Dsym=value */ +static bool ignore[MAXSYMS]; /* -iDsym or -iUsym */ +static int nsyms; /* number of symbols */ + +static FILE *input; /* input file pointer */ +static const char *filename; /* input file name */ +static int linenum; /* current line number */ +static FILE *output; /* output file pointer */ +static const char *ofilename; /* output file name */ +static bool overwriting; /* output overwrites input */ +static char tempname[FILENAME_MAX]; /* used when overwriting */ + +static char tline[MAXLINE+EDITSLOP];/* input buffer plus space */ +static char *keyword; /* used for editing #elif's */ + +static const char *newline; /* input file format */ +static const char newline_unix[] = "\n"; +static const char newline_crlf[] = "\r\n"; + +static Comment_state incomment; /* comment parser state */ +static Line_state linestate; /* #if line parser state */ +static Ifstate ifstate[MAXDEPTH]; /* #if processor state */ +static bool ignoring[MAXDEPTH]; /* ignore comments state */ +static int stifline[MAXDEPTH]; /* start of current #if */ +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 constexpression; /* constant #if expression */ +static bool zerosyms = true; /* to format symdepth output */ +static bool firstsym; /* ditto */ + +static int exitstat; /* program exit status */ + +static void addsym(bool, bool, char *); +static void closeout(void); +static void debug(const char *, ...); +static void done(void); +static void error(const char *); +static int findsym(const char *); +static void flushline(bool); +static Linetype parseline(void); +static Linetype ifeval(const char **); +static void ignoreoff(void); +static void ignoreon(void); +static void keywordedit(const char *); +static void nest(void); +static void process(void); +static const char *skipargs(const char *); +static const char *skipcomment(const char *); +static const char *skipsym(const char *); +static void state(Ifstate); +static int strlcmp(const char *, const char *, size_t); +static void unnest(void); +static void usage(void); +static void version(void); + +#define endsym(c) (!isalnum((unsigned char)c) && c != '_') + +/* + * The main program. + */ +int +main(int argc, char *argv[]) +{ + int opt; + + while ((opt = getopt(argc, argv, "i:D:U:I:o:bBcdeKklnsStV")) != -1) + switch (opt) { + case 'i': /* treat stuff controlled by these symbols as text */ + /* + * For strict backwards-compatibility the U or D + * should be immediately after the -i but it doesn't + * matter much if we relax that requirement. + */ + opt = *optarg++; + if (opt == 'D') + addsym(true, true, optarg); + else if (opt == 'U') + addsym(true, false, optarg); + else + usage(); + break; + case 'D': /* define a symbol */ + addsym(false, true, optarg); + break; + case 'U': /* undef a symbol */ + addsym(false, false, optarg); + break; + case 'I': /* no-op for compatibility with cpp */ + break; + case 'b': /* blank deleted lines instead of omitting them */ + case 'l': /* backwards compatibility */ + lnblank = true; + break; + case 'B': /* compress blank lines around removed section */ + compblank = true; + break; + case 'c': /* treat -D as -U and vice versa */ + complement = true; + break; + case 'd': + debugging = true; + break; + case 'e': /* fewer errors from dodgy lines */ + iocccok = true; + break; + case 'K': /* keep ambiguous #ifs */ + strictlogic = true; + break; + case 'k': /* process constant #ifs */ + killconsts = true; + break; + case 'n': /* add #line directive after deleted lines */ + lnnum = true; + break; + case 'o': /* output to a file */ + ofilename = optarg; + break; + case 's': /* only output list of symbols that control #ifs */ + symlist = true; + break; + case 'S': /* list symbols with their nesting depth */ + symlist = symdepth = true; + break; + case 't': /* don't parse C comments */ + text = true; + break; + case 'V': /* print version */ + version(); + default: + usage(); + } + argc -= optind; + argv += optind; + if (compblank && lnblank) + errx(2, "-B and -b are mutually exclusive"); + if (argc > 1) { + errx(2, "can only do one file"); + } else if (argc == 1 && strcmp(*argv, "-") != 0) { + filename = *argv; + input = fopen(filename, "rb"); + if (input == NULL) + err(2, "can't open %s", filename); + } else { + filename = "[stdin]"; + input = stdin; + } + if (ofilename == NULL) { + ofilename = "[stdout]"; + output = stdout; + } else { + struct stat ist, ost; + if (stat(ofilename, &ost) == 0 && + fstat(fileno(input), &ist) == 0) + overwriting = (ist.st_dev == ost.st_dev + && ist.st_ino == ost.st_ino); + if (overwriting) { + const char *dirsep; + int ofd; + + dirsep = strrchr(ofilename, '/'); + if (dirsep != NULL) + snprintf(tempname, sizeof(tempname), + "%.*s/" TEMPLATE, + (int)(dirsep - ofilename), ofilename); + else + snprintf(tempname, sizeof(tempname), + TEMPLATE); + ofd = mkstemp(tempname); + if (ofd != -1) + output = fdopen(ofd, "wb+"); + if (output == NULL) + err(2, "can't create temporary file"); + fchmod(ofd, ist.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)); + } else { + output = fopen(ofilename, "wb"); + if (output == NULL) + err(2, "can't open %s", ofilename); + } + } + process(); + abort(); /* bug */ +} + +static void +version(void) +{ + const char *c = copyright; + for (;;) { + while (*++c != '$') + if (*c == '\0') + exit(0); + while (*++c != '$') + putc(*c, stderr); + putc('\n', stderr); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: unifdef [-bBcdeKknsStV] [-Ipath]" + " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n"); + exit(2); +} + +/* + * A state transition function alters the global #if processing state + * in a particular way. The table below is indexed by the current + * processing state and the type of the current line. + * + * Nesting is handled by keeping a stack of states; some transition + * functions increase or decrease the depth. They also maintain the + * ignore state on a stack. In some complicated cases they have to + * alter the preprocessor directive, as follows. + * + * When we have processed a group that starts off with a known-false + * #if/#elif sequence (which has therefore been deleted) followed by a + * #elif that we don't understand and therefore must keep, we edit the + * latter into a #if to keep the nesting correct. We use memcpy() to + * overwrite the 4 byte token "elif" with "if " without a '\0' byte. + * + * When we find a true #elif in a group, the following block will + * always be kept and the rest of the sequence after the next #elif or + * #else will be discarded. We edit the #elif into a #else and the + * following directive to #endif since this has the desired behaviour. + * + * "Dodgy" directives are split across multiple lines, the most common + * example being a multi-line comment hanging off the right of the + * directive. We can handle them correctly only if there is no change + * from printing to dropping (or vice versa) caused by that directive. + * If the directive is the first of a group we have a choice between + * failing with an error, or passing it through unchanged instead of + * evaluating it. The latter is not the default to avoid questions from + * users about unifdef unexpectedly leaving behind preprocessor directives. + */ +typedef void state_fn(void); + +/* report an error */ +static void Eelif (void) { error("Inappropriate #elif"); } +static void Eelse (void) { error("Inappropriate #else"); } +static void Eendif(void) { error("Inappropriate #endif"); } +static void Eeof (void) { error("Premature EOF"); } +static void Eioccc(void) { error("Obfuscated preprocessor control line"); } +/* plain line handling */ +static void print (void) { flushline(true); } +static void drop (void) { flushline(false); } +/* output lacks group's start line */ +static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX); } +static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX); } +static void Selse (void) { drop(); state(IS_TRUE_ELSE); } +/* print/pass this block */ +static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); } +static void Pelse (void) { print(); state(IS_PASS_ELSE); } +static void Pendif(void) { print(); unnest(); } +/* discard this block */ +static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER); } +static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE); } +static void Delse (void) { drop(); state(IS_FALSE_ELSE); } +static void Dendif(void) { drop(); unnest(); } +/* first line of group */ +static void Fdrop (void) { nest(); Dfalse(); } +static void Fpass (void) { nest(); Pelif(); } +static void Ftrue (void) { nest(); Strue(); } +static void Ffalse(void) { nest(); Sfalse(); } +/* variable pedantry for obfuscated lines */ +static void Oiffy (void) { if (!iocccok) Eioccc(); Fpass(); ignoreon(); } +static void Oif (void) { if (!iocccok) Eioccc(); Fpass(); } +static void Oelif (void) { if (!iocccok) Eioccc(); Pelif(); } +/* ignore comments in this block */ +static void Idrop (void) { Fdrop(); ignoreon(); } +static void Itrue (void) { Ftrue(); ignoreon(); } +static void Ifalse(void) { Ffalse(); ignoreon(); } +/* modify this line */ +static void Mpass (void) { memcpy(keyword, "if ", 4); Pelif(); } +static void Mtrue (void) { keywordedit("else"); state(IS_TRUE_MIDDLE); } +static void Melif (void) { keywordedit("endif"); state(IS_FALSE_TRAILER); } +static void Melse (void) { keywordedit("endif"); state(IS_FALSE_ELSE); } + +static state_fn * const trans_table[IS_COUNT][LT_COUNT] = { +/* IS_OUTSIDE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eendif, + print, done, abort }, +/* IS_FALSE_PREFIX */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc, + drop, Eeof, abort }, +/* IS_TRUE_PREFIX */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc, + print, Eeof, abort }, +/* IS_PASS_MIDDLE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Pelif, Oelif, Oelif, Pelse, Pendif, + print, Eeof, abort }, +/* IS_FALSE_MIDDLE */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc, + drop, Eeof, abort }, +/* IS_TRUE_MIDDLE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Pendif, + print, Eeof, abort }, +/* IS_PASS_ELSE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Pendif, + print, Eeof, abort }, +/* IS_FALSE_ELSE */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc, + drop, Eeof, abort }, +/* IS_TRUE_ELSE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eioccc, + print, Eeof, abort }, +/* IS_FALSE_TRAILER */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc, + drop, Eeof, abort } +/*TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF + TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF (DODGY) + PLAIN EOF ERROR */ +}; + +/* + * State machine utility functions + */ +static void +ignoreoff(void) +{ + if (depth == 0) + abort(); /* bug */ + ignoring[depth] = ignoring[depth-1]; +} +static void +ignoreon(void) +{ + ignoring[depth] = true; +} +static void +keywordedit(const char *replacement) +{ + snprintf(keyword, tline + sizeof(tline) - keyword, + "%s%s", replacement, newline); + print(); +} +static void +nest(void) +{ + if (depth > MAXDEPTH-1) + abort(); /* bug */ + if (depth == MAXDEPTH-1) + error("Too many levels of nesting"); + depth += 1; + stifline[depth] = linenum; +} +static void +unnest(void) +{ + if (depth == 0) + abort(); /* bug */ + depth -= 1; +} +static void +state(Ifstate is) +{ + ifstate[depth] = is; +} + +/* + * Write a line to the output or not, according to command line options. + */ +static void +flushline(bool keep) +{ + if (symlist) + return; + if (keep ^ complement) { + bool blankline = tline[strspn(tline, " \t\r\n")] == '\0'; + if (blankline && compblank && blankcount != blankmax) { + delcount += 1; + blankcount += 1; + } else { + if (lnnum && delcount > 0) + printf("#line %d%s", linenum, newline); + fputs(tline, output); + delcount = 0; + blankmax = blankcount = blankline ? blankcount + 1 : 0; + } + } else { + if (lnblank) + fputs(newline, output); + exitstat = 1; + delcount += 1; + blankcount = 0; + } + if (debugging) + fflush(output); +} + +/* + * The driver for the state machine. + */ +static void +process(void) +{ + /* When compressing blank lines, act as if the file + is preceded by a large number of blank lines. */ + blankmax = blankcount = 1000; + for (;;) { + Linetype lineval = parseline(); + trans_table[ifstate[depth]][lineval](); + debug("process line %d %s -> %s depth %d", + linenum, linetype_name[lineval], + ifstate_name[ifstate[depth]], depth); + } +} + +/* + * Flush the output and handle errors. + */ +static void +closeout(void) +{ + if (symdepth && !zerosyms) + printf("\n"); + if (fclose(output) == EOF) { + warn("couldn't write to %s", ofilename); + if (overwriting) { + unlink(tempname); + errx(2, "%s unchanged", filename); + } else { + exit(2); + } + } +} + +/* + * Clean up and exit. + */ +static void +done(void) +{ + if (incomment) + error("EOF in comment"); + closeout(); + if (overwriting && rename(tempname, ofilename) == -1) { + warn("couldn't rename temporary file"); + unlink(tempname); + errx(2, "%s unchanged", ofilename); + } + exit(exitstat); +} + +/* + * Parse a line and determine its type. We keep the preprocessor line + * parser state between calls in the global variable linestate, with + * help from skipcomment(). + */ +static Linetype +parseline(void) +{ + const char *cp; + int cursym; + int kwlen; + Linetype retval; + Comment_state wascomment; + + linenum++; + if (fgets(tline, MAXLINE, input) == NULL) + return (LT_EOF); + if (newline == NULL) { + if (strrchr(tline, '\n') == strrchr(tline, '\r') + 1) + newline = newline_crlf; + else + newline = newline_unix; + } + retval = LT_PLAIN; + wascomment = incomment; + cp = skipcomment(tline); + if (linestate == LS_START) { + if (*cp == '#') { + linestate = LS_HASH; + firstsym = true; + cp = skipcomment(cp + 1); + } else if (*cp != '\0') + linestate = LS_DIRTY; + } + if (!incomment && linestate == LS_HASH) { + keyword = tline + (cp - tline); + cp = skipsym(cp); + kwlen = cp - keyword; + /* no way can we deal with a continuation inside a keyword */ + if (strncmp(cp, "\\\r\n", 3) == 0 || + strncmp(cp, "\\\n", 2) == 0) + Eioccc(); + if (strlcmp("ifdef", keyword, kwlen) == 0 || + strlcmp("ifndef", keyword, kwlen) == 0) { + cp = skipcomment(cp); + if ((cursym = findsym(cp)) < 0) + retval = LT_IF; + else { + retval = (keyword[2] == 'n') + ? LT_FALSE : LT_TRUE; + if (value[cursym] == NULL) + retval = (retval == LT_TRUE) + ? LT_FALSE : LT_TRUE; + if (ignore[cursym]) + retval = (retval == LT_TRUE) + ? LT_TRUEI : LT_FALSEI; + } + cp = skipsym(cp); + } else if (strlcmp("if", keyword, kwlen) == 0) + retval = ifeval(&cp); + else if (strlcmp("elif", keyword, kwlen) == 0) + retval = ifeval(&cp) - LT_IF + LT_ELIF; + else if (strlcmp("else", keyword, kwlen) == 0) + retval = LT_ELSE; + else if (strlcmp("endif", keyword, kwlen) == 0) + retval = LT_ENDIF; + else { + linestate = LS_DIRTY; + retval = LT_PLAIN; + } + cp = skipcomment(cp); + if (*cp != '\0') { + linestate = LS_DIRTY; + if (retval == LT_TRUE || retval == LT_FALSE || + retval == LT_TRUEI || retval == LT_FALSEI) + retval = LT_IF; + if (retval == LT_ELTRUE || retval == LT_ELFALSE) + retval = LT_ELIF; + } + if (retval != LT_PLAIN && (wascomment || incomment)) { + retval += LT_DODGY; + if (incomment) + linestate = LS_DIRTY; + } + /* skipcomment normally changes the state, except + if the last line of the file lacks a newline, or + if there is too much whitespace in a directive */ + if (linestate == LS_HASH) { + size_t len = cp - tline; + if (fgets(tline + len, MAXLINE - len, input) == NULL) { + /* append the missing newline */ + strcpy(tline + len, newline); + cp += strlen(newline); + linestate = LS_START; + } else { + linestate = LS_DIRTY; + } + } + } + if (linestate == LS_DIRTY) { + while (*cp != '\0') + cp = skipcomment(cp + 1); + } + debug("parser line %d state %s comment %s line", linenum, + comment_name[incomment], linestate_name[linestate]); + return (retval); +} + +/* + * These are the binary operators that are supported by the expression + * evaluator. + */ +static Linetype op_strict(int *p, int v, Linetype at, Linetype bt) { + if(at == LT_IF || bt == LT_IF) return (LT_IF); + return (*p = v, v ? LT_TRUE : LT_FALSE); +} +static Linetype op_lt(int *p, Linetype at, int a, Linetype bt, int b) { + return op_strict(p, a < b, at, bt); +} +static Linetype op_gt(int *p, Linetype at, int a, Linetype bt, int b) { + return op_strict(p, a > b, at, bt); +} +static Linetype op_le(int *p, Linetype at, int a, Linetype bt, int b) { + return op_strict(p, a <= b, at, bt); +} +static Linetype op_ge(int *p, Linetype at, int a, Linetype bt, int b) { + return op_strict(p, a >= b, at, bt); +} +static Linetype op_eq(int *p, Linetype at, int a, Linetype bt, int b) { + return op_strict(p, a == b, at, bt); +} +static Linetype op_ne(int *p, Linetype at, int a, Linetype bt, int b) { + return op_strict(p, a != b, at, bt); +} +static Linetype op_or(int *p, Linetype at, int a, Linetype bt, int b) { + if (!strictlogic && (at == LT_TRUE || bt == LT_TRUE)) + return (*p = 1, LT_TRUE); + return op_strict(p, a || b, at, bt); +} +static Linetype op_and(int *p, Linetype at, int a, Linetype bt, int b) { + if (!strictlogic && (at == LT_FALSE || bt == LT_FALSE)) + return (*p = 0, LT_FALSE); + return op_strict(p, a && b, at, bt); +} + +/* + * An evaluation function takes three arguments, as follows: (1) a pointer to + * an element of the precedence table which lists the operators at the current + * level of precedence; (2) a pointer to an integer which will receive the + * value of the expression; and (3) a pointer to a char* that points to the + * expression to be evaluated and that is updated to the end of the expression + * when evaluation is complete. The function returns LT_FALSE if the value of + * the expression is zero, LT_TRUE if it is non-zero, LT_IF if the expression + * depends on an unknown symbol, or LT_ERROR if there is a parse failure. + */ +struct ops; + +typedef Linetype eval_fn(const struct ops *, int *, const char **); + +static eval_fn eval_table, eval_unary; + +/* + * The precedence table. Expressions involving binary operators are evaluated + * in a table-driven way by eval_table. When it evaluates a subexpression it + * calls the inner function with its first argument pointing to the next + * element of the table. Innermost expressions have special non-table-driven + * handling. + */ +static const struct ops { + eval_fn *inner; + struct op { + const char *str; + Linetype (*fn)(int *, Linetype, int, Linetype, int); + } op[5]; +} eval_ops[] = { + { eval_table, { { "||", op_or } } }, + { eval_table, { { "&&", op_and } } }, + { eval_table, { { "==", op_eq }, + { "!=", op_ne } } }, + { eval_unary, { { "<=", op_le }, + { ">=", op_ge }, + { "<", op_lt }, + { ">", op_gt } } } +}; + +/* + * Function for evaluating the innermost parts of expressions, + * viz. !expr (expr) number defined(symbol) symbol + * We reset the constexpression flag in the last two cases. + */ +static Linetype +eval_unary(const struct ops *ops, int *valp, const char **cpp) +{ + const char *cp; + char *ep; + int sym; + bool defparen; + Linetype lt; + + cp = skipcomment(*cpp); + if (*cp == '!') { + debug("eval%d !", ops - eval_ops); + cp++; + lt = eval_unary(ops, valp, &cp); + if (lt == LT_ERROR) + return (LT_ERROR); + if (lt != LT_IF) { + *valp = !*valp; + lt = *valp ? LT_TRUE : LT_FALSE; + } + } else if (*cp == '(') { + cp++; + debug("eval%d (", ops - eval_ops); + lt = eval_table(eval_ops, valp, &cp); + if (lt == LT_ERROR) + return (LT_ERROR); + cp = skipcomment(cp); + if (*cp++ != ')') + return (LT_ERROR); + } else if (isdigit((unsigned char)*cp)) { + debug("eval%d number", ops - eval_ops); + *valp = strtol(cp, &ep, 0); + if (ep == cp) + return (LT_ERROR); + lt = *valp ? LT_TRUE : LT_FALSE; + cp = skipsym(cp); + } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) { + cp = skipcomment(cp+7); + debug("eval%d defined", ops - eval_ops); + if (*cp == '(') { + cp = skipcomment(cp+1); + defparen = true; + } else { + defparen = false; + } + sym = findsym(cp); + if (sym < 0) { + lt = LT_IF; + } else { + *valp = (value[sym] != NULL); + lt = *valp ? LT_TRUE : LT_FALSE; + } + cp = skipsym(cp); + cp = skipcomment(cp); + if (defparen && *cp++ != ')') + return (LT_ERROR); + constexpression = false; + } else if (!endsym(*cp)) { + debug("eval%d symbol", ops - eval_ops); + sym = findsym(cp); + cp = skipsym(cp); + if (sym < 0) { + lt = LT_IF; + cp = skipargs(cp); + } else if (value[sym] == NULL) { + *valp = 0; + lt = LT_FALSE; + } else { + *valp = strtol(value[sym], &ep, 0); + if (*ep != '\0' || ep == value[sym]) + return (LT_ERROR); + lt = *valp ? LT_TRUE : LT_FALSE; + cp = skipargs(cp); + } + constexpression = false; + } else { + debug("eval%d bad expr", ops - eval_ops); + return (LT_ERROR); + } + + *cpp = cp; + debug("eval%d = %d", ops - eval_ops, *valp); + return (lt); +} + +/* + * Table-driven evaluation of binary operators. + */ +static Linetype +eval_table(const struct ops *ops, int *valp, const char **cpp) +{ + const struct op *op; + const char *cp; + int val; + Linetype lt, rt; + + debug("eval%d", ops - eval_ops); + cp = *cpp; + lt = ops->inner(ops+1, valp, &cp); + if (lt == LT_ERROR) + return (LT_ERROR); + for (;;) { + cp = skipcomment(cp); + for (op = ops->op; op->str != NULL; op++) + if (strncmp(cp, op->str, strlen(op->str)) == 0) + break; + if (op->str == NULL) + break; + cp += strlen(op->str); + debug("eval%d %s", ops - eval_ops, op->str); + rt = ops->inner(ops+1, &val, &cp); + if (rt == LT_ERROR) + return (LT_ERROR); + lt = op->fn(valp, lt, *valp, rt, val); + } + + *cpp = cp; + debug("eval%d = %d", ops - eval_ops, *valp); + debug("eval%d lt = %s", ops - eval_ops, linetype_name[lt]); + return (lt); +} + +/* + * Evaluate the expression on a #if or #elif line. If we can work out + * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we + * return just a generic LT_IF. + */ +static Linetype +ifeval(const char **cpp) +{ + int ret; + int val = 0; + + debug("eval %s", *cpp); + constexpression = killconsts ? false : true; + ret = eval_table(eval_ops, &val, cpp); + debug("eval = %d", val); + return (constexpression ? LT_IF : ret == LT_ERROR ? LT_IF : ret); +} + +/* + * Skip over comments, strings, and character literals and stop at the + * next character position that is not whitespace. Between calls we keep + * the comment state in the global variable incomment, and we also adjust + * the global variable linestate when we see a newline. + * XXX: doesn't cope with the buffer splitting inside a state transition. + */ +static const char * +skipcomment(const char *cp) +{ + if (text || ignoring[depth]) { + for (; isspace((unsigned char)*cp); cp++) + if (*cp == '\n') + linestate = LS_START; + return (cp); + } + while (*cp != '\0') + /* don't reset to LS_START after a line continuation */ + if (strncmp(cp, "\\\r\n", 3) == 0) + cp += 3; + else if (strncmp(cp, "\\\n", 2) == 0) + cp += 2; + else switch (incomment) { + case NO_COMMENT: + if (strncmp(cp, "/\\\r\n", 4) == 0) { + incomment = STARTING_COMMENT; + cp += 4; + } else if (strncmp(cp, "/\\\n", 3) == 0) { + incomment = STARTING_COMMENT; + cp += 3; + } else if (strncmp(cp, "/*", 2) == 0) { + incomment = C_COMMENT; + cp += 2; + } else if (strncmp(cp, "//", 2) == 0) { + incomment = CXX_COMMENT; + cp += 2; + } else if (strncmp(cp, "\'", 1) == 0) { + incomment = CHAR_LITERAL; + linestate = LS_DIRTY; + cp += 1; + } else if (strncmp(cp, "\"", 1) == 0) { + incomment = STRING_LITERAL; + linestate = LS_DIRTY; + cp += 1; + } else if (strncmp(cp, "\n", 1) == 0) { + linestate = LS_START; + cp += 1; + } else if (strchr(" \r\t", *cp) != NULL) { + cp += 1; + } else + return (cp); + continue; + case CXX_COMMENT: + if (strncmp(cp, "\n", 1) == 0) { + incomment = NO_COMMENT; + linestate = LS_START; + } + cp += 1; + continue; + case CHAR_LITERAL: + case STRING_LITERAL: + if ((incomment == CHAR_LITERAL && cp[0] == '\'') || + (incomment == STRING_LITERAL && cp[0] == '\"')) { + incomment = NO_COMMENT; + cp += 1; + } else if (cp[0] == '\\') { + if (cp[1] == '\0') + cp += 1; + else + cp += 2; + } else if (strncmp(cp, "\n", 1) == 0) { + if (incomment == CHAR_LITERAL) + error("unterminated char literal"); + else + error("unterminated string literal"); + } else + cp += 1; + continue; + case C_COMMENT: + if (strncmp(cp, "*\\\r\n", 4) == 0) { + incomment = FINISHING_COMMENT; + cp += 4; + } else if (strncmp(cp, "*\\\n", 3) == 0) { + incomment = FINISHING_COMMENT; + cp += 3; + } else if (strncmp(cp, "*/", 2) == 0) { + incomment = NO_COMMENT; + cp += 2; + } else + cp += 1; + continue; + case STARTING_COMMENT: + if (*cp == '*') { + incomment = C_COMMENT; + cp += 1; + } else if (*cp == '/') { + incomment = CXX_COMMENT; + cp += 1; + } else { + incomment = NO_COMMENT; + linestate = LS_DIRTY; + } + continue; + case FINISHING_COMMENT: + if (*cp == '/') { + incomment = NO_COMMENT; + cp += 1; + } else + incomment = C_COMMENT; + continue; + default: + abort(); /* bug */ + } + return (cp); +} + +/* + * Skip macro arguments. + */ +static const char * +skipargs(const char *cp) +{ + const char *ocp = cp; + int level = 0; + cp = skipcomment(cp); + if (*cp != '(') + return (cp); + do { + if (*cp == '(') + level++; + if (*cp == ')') + level--; + cp = skipcomment(cp+1); + } while (level != 0 && *cp != '\0'); + if (level == 0) + return (cp); + else + /* Rewind and re-detect the syntax error later. */ + return (ocp); +} + +/* + * Skip over an identifier. + */ +static const char * +skipsym(const char *cp) +{ + while (!endsym(*cp)) + ++cp; + return (cp); +} + +/* + * Look for the symbol in the symbol table. If it is found, we return + * the symbol table index, else we return -1. + */ +static int +findsym(const char *str) +{ + const char *cp; + int symind; + + cp = skipsym(str); + if (cp == str) + return (-1); + if (symlist) { + if (symdepth && firstsym) + printf("%s%3d", zerosyms ? "" : "\n", depth); + firstsym = zerosyms = false; + printf("%s%.*s%s", + symdepth ? " " : "", + (int)(cp-str), str, + symdepth ? "" : "\n"); + /* we don't care about the value of the symbol */ + return (0); + } + for (symind = 0; symind < nsyms; ++symind) { + if (strlcmp(symname[symind], str, cp-str) == 0) { + debug("findsym %s %s", symname[symind], + value[symind] ? value[symind] : ""); + return (symind); + } + } + return (-1); +} + +/* + * Add a symbol to the symbol table. + */ +static void +addsym(bool ignorethis, bool definethis, char *sym) +{ + int symind; + char *val; + + symind = findsym(sym); + if (symind < 0) { + if (nsyms >= MAXSYMS) + errx(2, "too many symbols"); + symind = nsyms++; + } + symname[symind] = sym; + ignore[symind] = ignorethis; + val = sym + (skipsym(sym) - sym); + if (definethis) { + if (*val == '=') { + value[symind] = val+1; + *val = '\0'; + } else if (*val == '\0') + value[symind] = "1"; + else + usage(); + } else { + if (*val != '\0') + usage(); + value[symind] = NULL; + } + debug("addsym %s=%s", symname[symind], + value[symind] ? value[symind] : "undef"); +} + +/* + * Compare s with n characters of t. + * The same as strncmp() except that it checks that s[n] == '\0'. + */ +static int +strlcmp(const char *s, const char *t, size_t n) +{ + while (n-- && *t != '\0') + if (*s != *t) + return ((unsigned char)*s - (unsigned char)*t); + else + ++s, ++t; + return ((unsigned char)*s); +} + +/* + * Diagnostics. + */ +static void +debug(const char *msg, ...) +{ + va_list ap; + + if (debugging) { + va_start(ap, msg); + vwarnx(msg, ap); + va_end(ap); + } +} + +static void +error(const char *msg) +{ + if (depth == 0) + warnx("%s: %d: %s", filename, linenum, msg); + else + warnx("%s: %d: %s (#if line %d depth %d)", + filename, linenum, msg, stifline[depth], depth); + closeout(); + errx(2, "output may be truncated"); +} diff --git a/scripts/update-intel-ucode-defs.py b/scripts/update-intel-ucode-defs.py new file mode 100755 index 000000000..9d6cc2c60 --- /dev/null +++ b/scripts/update-intel-ucode-defs.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +import argparse +import re +import shutil +import subprocess +import sys +import os + +script = os.path.relpath(__file__) + +DESCRIPTION = f""" +For Intel CPUs, update the microcode revisions that determine +X86_BUG_OLD_MICROCODE. + +This script is intended to be run in response to releases of the +official Intel microcode GitHub repository: +https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files.git + +It takes the Intel microcode files as input and uses iucode-tool to +extract the revision information. It prints the output in the format +expected by intel-ucode-defs.h. + +Usage: + ./{script} /path/to/microcode/files > /path/to/intel-ucode-defs.h + +Typically, someone at Intel would see a new public release, wait for at +least three months to ensure the update is stable, run this script to +refresh the intel-ucode-defs.h file, and send a patch upstream to update +the mainline and stable versions. + +Any exception to this process should be supported with an appropriate +justification. +""" + +SIG_RE = re.compile(r'sig (0x[0-9a-fA-F]+)') +PFM_RE = re.compile(r'pf_mask (0x[0-9a-fA-F]+)') +REV_RE = re.compile(r'rev (0x[0-9a-fA-F]+)') + +# Functions to extract family, model, and stepping +def bits(val, bottom, top): + mask = (1 << (top + 1 - bottom)) - 1 + return (val >> bottom) & mask + +def family(sig): + if bits(sig, 8, 11) == 0xf: + return bits(sig, 8, 11) + bits(sig, 20, 27) + return bits(sig, 8, 11) + +def model(sig): + return bits(sig, 4, 7) | (bits(sig, 16, 19) << 4) + +def step(sig): + return bits(sig, 0, 3) + +class Ucode: + def __init__(self, sig, pfm, rev): + self.family = family(sig) + self.model = model(sig) + self.steppings = 1 << step(sig) + self.platforms = pfm + self.rev = rev + + self.key = (self.family, self.model, self.steppings, self.platforms) + + def __eq__(self, other): + return self.key == other.key + + def __hash__(self): + return hash(self.key) + + def __str__(self): + return "{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x%x, .model = 0x%02x, .steppings = 0x%04x, .platform_mask = 0x%02x, .driver_data = 0x%x }," % \ + (self.family, self.model, self.steppings, self.platforms, self.rev) + +def main(): + parser = argparse.ArgumentParser(description=DESCRIPTION, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('ucode_files', nargs='+', help='Path(s) to the microcode files') + + args = parser.parse_args() + + # Process the microcode files using iucode-tool + iucode_tool = shutil.which("iucode-tool") or shutil.which("iucode_tool") + if iucode_tool is None: + print("Error: iucode-tool not found, please install it", file=sys.stderr) + sys.exit(1) + + cmd = [iucode_tool, '--list-all'] + args.ucode_files + + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print("Error: iucode-tool ran into an error, exiting", file=sys.stderr) + if result.stderr: + print(result.stderr, file=sys.stderr, end='') + sys.exit(1) + + ucodes = set() + + # Parse the output of iucode-tool + for line in result.stdout.splitlines(): + sig_match = SIG_RE.search(line) + pfm_match = PFM_RE.search(line) + rev_match = REV_RE.search(line) + + if not (sig_match and pfm_match and rev_match): + continue + + sig = int(sig_match.group(1), 16) + pfm = int(pfm_match.group(1), 16) + rev = int(rev_match.group(1), 16) + debug_rev = bits(rev, 31, 31) + if debug_rev != 0: + print("Error: Debug ucode file found, exiting", file=sys.stderr) + sys.exit(1) + + ucodes.add(Ucode(sig, pfm, rev)) + + if not ucodes: + print("Error: No valid microcode files found, exiting", file=sys.stderr) + sys.exit(1) + + # Sort and print the microcode entries + print("/* SPDX-License-Identifier: GPL-2.0 */") + print("/* Auto-generated by scripts/update-intel-ucode-defs.py */") + for u in sorted(ucodes, key=lambda x: x.key): + print(u) + +if __name__ == "__main__": + main() diff --git a/scripts/ver_linux b/scripts/ver_linux new file mode 100755 index 000000000..00bdaf30d --- /dev/null +++ b/scripts/ver_linux @@ -0,0 +1,97 @@ +#!/usr/bin/awk -f +# SPDX-License-Identifier: GPL-2.0 +# Before running this script please ensure that your PATH is +# typical as you use for compilation/installation. I use +# /bin /sbin /usr/bin /usr/sbin /usr/local/bin, but it may +# differ on your system. + +BEGIN { + usage = "If some fields are empty or look unusual you may have an old version.\n" + usage = usage "Compare to the current minimal requirements in Documentation/process/changes.rst\n" + print usage + + system("uname -a") + printf("\n") + + vernum = "[0-9]+([.]?[0-9]+)+" + libc = "libc[.]so[.][0-9]+$" + libcpp = "(libg|stdc)[+]+[.]so([.][0-9]+)+$" + + printversion("bash", version("bash --version")) + printversion("bc", version("bc --version")) + printversion("bindgen", version("bindgen --version")) + printversion("binutils", version("ld -v")) + printversion("bison", version("bison --version")) + printversion("btrfs-progs", version("btrfs --version")) + printversion("Clang", version("clang --version")) + printversion("Console-tools", version("loadkeys -V")) + printversion("Dynamic linker (ldd)", version("ldd --version")) + printversion("e2fsprogs", version("e2fsck -V")) + printversion("flex", version("flex --version")) + printversion("gdb", version("gdb -version")) + printversion("GNU awk", version("gawk --version")) + printversion("GNU C", version("gcc -dumpversion")) + printversion("GNU make", version("make --version")) + printversion("GNU tar", version("tar --version")) + printversion("GRUB2", version("grub2-install --version")) + printversion("GRUB", version("grub-install --version")) + printversion("gtags", version("gtags --version")) + printversion("iptables", version("iptables -V")) + printversion("Isdn4k-utils", version("isdnctrl")) + printversion("jfsutils", version("fsck.jfs -V")) + printversion("Kbd", version("loadkeys -V")) + printversion("kmod", version("kmod -V")) + + while ("ldconfig -p 2>/dev/null" | getline > 0) + if ($NF ~ libc || $NF ~ libcpp) + if (!seen[ver = version("readlink " $NF)]++) + printversion("Linux C" ($NF ~ libcpp? "++" : "") " Library", ver) + + printversion("mcelog", version("mcelog --version")) + printversion("mkimage", version("mkimage --version")) + printversion("Module-init-tools", version("depmod -V")) + printversion("Mount", version("mount --version")) + printversion("Net-tools", version("ifconfig --version")) + printversion("nfs-utils", version("showmount --version")) + printversion("openssl", version("openssl version")) + printversion("pahole", version("pahole --version")) + printversion("Pcmcia-cs", version("cardmgr -V")) + printversion("pcmciautils", version("pccardctl -V")) + printversion("PPP", version("pppd --version")) + printversion("procps", version("ps --version")) + printversion("Python", version("python3 -V")) + printversion("quota-tools", version("quota -V")) + printversion("Rust", version("rustc --version")) + printversion("Sh-utils", version("expr --v")) + printversion("Sphinx", version("sphinx-build --version")) + printversion("squashfs-tools", version("mksquashfs -version")) + printversion("udev", version("udevadm --version")) + printversion("util-linux", version("mount --version")) + printversion("Wireless-tools", version("iwconfig --version")) + printversion("xfsprogs", version("xfs_db -V")) + + while ("sort /proc/modules" | getline > 0) { + mods = mods sep $1 + sep = " " + } + printversion("Modules Loaded", mods) +} + +function version(cmd, ver) { + cmd = cmd " 2>&1" + while (cmd | getline > 0) { + if (match($0, vernum)) { + ver = substr($0, RSTART, RLENGTH) + break + } + } + close(cmd) + return ver +} + +function printversion(name, value, ofmt) { + if (value != "") { + ofmt = "%-20s\t%s\n" + printf(ofmt, name, value) + } +} diff --git a/scripts/verify_builtin_ranges.awk b/scripts/verify_builtin_ranges.awk new file mode 100755 index 000000000..0de7ed521 --- /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/xen-hypercalls.sh b/scripts/xen-hypercalls.sh new file mode 100755 index 000000000..f18b00843 --- /dev/null +++ b/scripts/xen-hypercalls.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +out="$1" +shift +in="$@" + +for i in $in; do + eval $CPP $LINUXINCLUDE -dD -imacros "$i" -x c /dev/null +done | \ +awk '$1 == "#define" && $2 ~ /__HYPERVISOR_[a-z][a-z_0-9]*/ { v[$3] = $2 } + END { print "/* auto-generated by scripts/xen-hypercall.sh */" + for (i in v) if (!(v[i] in v)) + print "HYPERCALL("substr(v[i], 14)")"}' | sort -u >$out diff --git a/scripts/xz_wrap.sh b/scripts/xz_wrap.sh new file mode 100755 index 000000000..f19369687 --- /dev/null +++ b/scripts/xz_wrap.sh @@ -0,0 +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. +# +# 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= + +# 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 + 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 + +# 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 -- cgit v1.3.1