#!/usr/bin/perl use strict; use warnings; use File::Temp qw/ tempdir /; my $DTMERGE = "dtmerge"; my $TMPDIR = tempdir( CLEANUP => 1); my $MERGED_DTB = "$TMPDIR/merged.dtb"; my @base_files = ( "bcm2712-rpi-5-b", "bcm2712-rpi-cm5-cm5io", "bcm2711-rpi-4-b", "bcm2711-rpi-cm4", "bcm2711-rpi-cm4s", "bcm2708-rpi-b", "bcm2708-rpi-b-rev1", "bcm2708-rpi-b-plus", "bcm2708-rpi-zero", "bcm2708-rpi-zero-w", "bcm2708-rpi-cm", "bcm2709-rpi-2-b", "bcm2710-rpi-3-b", "bcm2710-rpi-3-b-plus", "bcm2710-rpi-cm3", "bcm2710-rpi-zero-2-w", ); my @extra_base_files = ( "bcm2709-rpi-cm2", "bcm2710-rpi-cm0", "bcm2710-rpi-2-b", "bcm2711-rpi-400", "bcm2712-rpi-500", "bcm2712-rpi-cm5-cm4io", "bcm2712-rpi-cm5l-cm5io", "bcm2712-rpi-cm5l-cm4io", ); my %platform_reps = ( 'bcm2835' => 'bcm2710-rpi-3-b', 'bcm2711' => 'bcm2711-rpi-4-b', 'bcm2712' => 'bcm2712-rpi-5-b', ); $ENV{'LD_LIBRARY_PATH'} = "$ENV{'HOME'}/lib"; my %overlay_checkers = ( # With the current overlays, the exclusions are no longer necessary, but an # example for i2c-sensor would be: # '^(addr|lm75addr|int_pin|i2c\d|i2c_csi_dsi|no_timeout)$' 'i2c-rtc' => [ \&container_checker, undef ], 'i2c-fan' => [ \&container_checker, undef ], 'i2c-sensor' => [ \&container_checker, undef ], ); my $word_pattern = '[0-9a-zA-Z0-9][-_a-zA-Z0-9]*'; my $readme_param_pattern = '([-_a-zA-Z0-9]|<[a-z]>|<[a-z]-[a-z]>)*'; my $verbose; my $strict_dtc; my $try_all; my $fail = 0; while (@ARGV && $ARGV[0] =~ /^-/) { my $arg = shift @ARGV; if ($arg eq '-h') { usage(); exit(0); } elsif ($arg eq '-s') { $strict_dtc = 1; } elsif ($arg eq '-t') { $try_all = 1; } elsif ($arg eq '-v') { $verbose = 1; } else { fatal_error("Unknown option '$arg'"); } } my @overlays = @ARGV; # Aims: # * Check that each overlay has an entry in the README # * Check that the parameters of each overlay are in the README # * Check that the parameters of the base DTBs are in the README # * Check that each overlay has an entry in the Makefile # Process: # 1) Parse the base dts's and overlays # 2) Parse the README # 3) Compare the two # 4) Parse the Makefile, and compare with the others my $kerndir = `git rev-parse --show-toplevel 2>/dev/null`; chomp($kerndir); fatal_error("This isn't a Linux repository") if (!-d "$kerndir/kernel"); # 32-bit first, to avoid the #include "arm/broadcom..." versions in the arm64 tree my $dtspath = (-f $kerndir."/arch/arm/boot/dts/broadcom/Makefile") ? $kerndir."/arch/arm/boot/dts/broadcom" : $kerndir."/arch/arm/boot/dts"; my $dts64path = (-f $kerndir."/arch/arm64/boot/dts/broadcom/Makefile") ? $kerndir."/arch/arm64/boot/dts/broadcom" : $kerndir."/arch/arm64/boot/dts"; my (@base32_files, @base64_files, @extra_base32_files, @extra_base64_files); foreach my $base (@base_files) { if (-f $dtspath."/".$base.".dts") { push @base32_files, $base; } elsif (-f $dts64path."/".$base.".dts") { push @base64_files, $base; } } foreach my $base (@extra_base_files) { if (-f $dtspath."/".$base.".dts") { push @extra_base32_files, $base; } elsif (-f $dts64path."/".$base.".dts") { push @extra_base64_files, $base; } } my @dtsgroups = ( [ $dts64path, \@base64_files ], [ $dtspath, \@base32_files ], ); my @extra_dtsgroups = ( [ $dts64path, \@extra_base64_files ], [ $dtspath, \@extra_base32_files ], ); my $overlaysdir = $kerndir."/arch/arm/boot/dts/overlays"; my @cpp_files = ('arm-linux-gnueabihf-cpp', 'aarch64-linux-gnu-cpp', 'cpp'); my $cpp; foreach my $cmd (@cpp_files) { if (system("command -v $cmd >/dev/null 2>&1") == 0) { $cpp = $cmd; last; } } die "* no suitable cpp found\n" if (!$cpp); my @cpp_cmd = ( $cpp, '-nostdinc', '-I.', "-I$kerndir/include", '-Ioverlays', '-undef', '-D__DTS__', '-x', 'assembler-with-cpp' ); my $DTC = "$kerndir/scripts/dtc/dtc"; my $exclusions_file = "/usr/share/raspi-utils/overlaycheck_exclusions.txt"; my @warnings_to_suppress = ( 'unit_address_vs_reg', 'simple_bus_reg', 'unit_address_format', 'interrupts_property', 'gpios_property', 'label_is_string', 'unique_unit_address', 'avoid_unnecessary_addr_size' ); push @warnings_to_suppress, ( 'pci_device_reg', 'pci_device_bus_num', 'reg_format', 'interrupt_provider', 'dma_ranges_format', 'avoid_default_addr_size' ) if (!$strict_dtc); my @dtc_opts; foreach my $warn (@warnings_to_suppress) { push @dtc_opts, '-W', "no-$warn" if (system("$DTC -W no-$warn -v >/dev/null 2>&1") == 0); } my $dtc_opts = join(' ', @dtc_opts); # Parse the exclusions my ($ignore_missing, $ignore_vestigial) = parse_exclusions($exclusions_file); my $documentation = {}; # Parse the README chdir($overlaysdir); my $readme = parse_readme("README"); # Parse the base dts files and overlays my $source = parse_source_files($overlaysdir, @dtsgroups, @extra_dtsgroups); # Now some build/runtime checks foreach my $group (@dtsgroups, @extra_dtsgroups) { chdir($group->[0]); foreach my $base (@{$group->[1]}) { dtc_cpp("$base.dts", "$TMPDIR/$base.dtb"); if (system("ovmerge -q $base.dts") >> 8) { error(" Error ^ in base file $base.dts\n"); } } } if (!@overlays) { @overlays = sort(keys(%$source)); # Compare the two my ($left_only, $common, $right_only) = compare_sets(\@overlays, [keys(%$readme)]); ($left_only) = compare_sets($left_only, [keys(%$ignore_missing)]); ($right_only) = compare_sets($right_only, [keys(%$ignore_vestigial)]); list_print("Overlays without documentation", $left_only); list_print("Vestigial overlay documentation", $right_only); $fail ||= (@$left_only || @$right_only); foreach my $overlay (@$common) { for (my $i = 0; $i < @{$readme->{$overlay}}; $i++) { my $param = ${$readme->{$overlay}}[$i]; if ($documentation->{$param}) { my $expansion = $readme->{$param}; splice(@{$readme->{$overlay}}, $i, 1, @$expansion); $i += @$expansion - 1; } } my ($l, $b, $r) = compare_sets($source->{$overlay}, $readme->{$overlay}); my $rpos = 0; while ($rpos < @$r) { my $rv = $r->[$rpos]; if (($rv =~ s/<[i-z]>/[0-9a-fA-F]+/g) || ($rv =~ s/<[a-h]>/[a-z]/g)) { my $lpos = 0; my $found = 0; while ($lpos < @$l) { if ($l->[$lpos] =~ /^$rv$/) { splice(@$l, $lpos, 1); $found = 1; } else { $lpos++; } } splice(@$r, $rpos, 1) if ($found); $rpos++ if (!$found); } else { $rpos++; } } ($l) = compare_sets($l, $ignore_missing->{$overlay}); ($r) = compare_sets($r, $ignore_vestigial->{$overlay}); list_print("$overlay undocumented parameters", $l); list_print("$overlay vestigial parameter documentation", $r); $fail ||= (@$l || @$r); } # Parse the Makefile chdir($overlaysdir); my $makefile = parse_makefile("Makefile"); ($left_only, $common, $right_only) = compare_sets(\@overlays, $makefile); list_print("Overlays missing from the Makefile", $left_only); list_print("Vestigial overlay Makefile entries", $right_only); $fail ||= (@$left_only || @$right_only); } chdir($overlaysdir); dtc_cpp("overlay_map.dts", "$TMPDIR/overlay_map.dtb") if (-r "overlay_map.dts"); foreach my $overlay (@overlays) { next if ($overlay =~ /^> 8) { error(" Error ^ in overlay $overlay\n"); } dtc_cpp("${overlay}-overlay.dts", "$TMPDIR/$overlay.dtbo"); } foreach my $overlay (@overlays) { next if ($overlay =~ /^{$overlay}[0]; my $base; while (my ($plat, $dts) = each(%platform_reps)) { if ($overlay_props->{$plat}) { $base = $dts; last; } } print("[ dtmerge $TMPDIR/$base.dtb $MERGED_DTB $TMPDIR/$overlay.dtbo ]\n") if ($verbose && $base); if ($base && (system($DTMERGE, "$TMPDIR/$base.dtb", $MERGED_DTB, "$TMPDIR/$overlay.dtbo") >> 8)) { error(" Error ^ in overlay $overlay\n"); } dormant_checker($overlay); pinctrl_checker($overlay); my $checker = $overlay_checkers{$overlay}; ($checker->[0])->($overlay, $checker->[1], $source->{$overlay}) if ($checker); } if ($try_all) { foreach my $overlay (@overlays) { next if ($overlay =~ /^{$overlay}[0]; foreach my $base (@base_files) { next if (!-f "$TMPDIR/$base.dtb"); next if (($overlay =~ /(wifi|bt)/) && ($base !~ /^(bcm2708-rpi-zero-w|bcm2710-rpi-zero-2|bcm2710-rpi-3-|bcm2711-rpi-(4|cm4$)|bcm2712-rpi-(5|cm5))/)); next if ($overlay eq 'vl805' && $base eq 'bcm2711-rpi-cm4s'); next if ($overlay_props->{'bcm2711'} && $base !~ /^bcm2711/); next if ($overlay_props->{'bcm2712'} && $base !~ /^bcm2712/); next if (system("$DTMERGE $TMPDIR/$base.dtb $MERGED_DTB $TMPDIR/$overlay.dtbo >/dev/null 2>&1") == ((-2 & 0xff) << 8)); print("[ dtmerge $TMPDIR/$base.dtb $MERGED_DTB $TMPDIR/$overlay.dtbo ]\n") if ($verbose); error("Failed to merge $overlay with $base") if (system($DTMERGE, $verbose ? ('-d') : (), "$TMPDIR/$base.dtb", $MERGED_DTB, "$TMPDIR/$overlay.dtbo") != 0); } } } rmdir($TMPDIR); printf("%s\n", $fail ? "Failed" : "OK"); exit($fail); sub compare_sets { my ($l, $r) = @_; my (@lonly, @both, @ronly); my @l = ($l ? sort(not_ref(@$l)) : ()); my @r = ($r ? sort(not_ref(@$r)) : ()); my $lidx = 0; my $ridx = 0; while (($lidx < @l) && ($ridx < @r)) { my $l = $l[$lidx]; my $r = $r[$ridx]; my $cmp = $l cmp $r; if ($cmp == -1) { push @lonly, $l; $lidx++; } elsif ($cmp == 1) { push @ronly, $r; $ridx++; } else { push @both, $l; $lidx++; $ridx++; } } push @lonly, @l[$lidx..$#l]; push @ronly, @r[$ridx..$#r]; return (\@lonly, \@both, \@ronly); } sub not_ref { my @res; foreach my $i (@_) { push @res, $i if (!ref $i); } return @res; } sub list_print { my ($label, $list) = @_; if (@$list) { print ("$label:\n"); foreach my $x (@$list) { print (" $x\n"); } } } sub parse_source_files { my ($overlaydir, @dtsgroups) = @_; my $overlays = {}; my %params; foreach my $group (@dtsgroups) { chdir($group->[0]); foreach my $file (@{$group->[1]}) { foreach my $param (get_params($file.".dts")) { next if (ref $param); $params{$param} = 1; } } } chdir($overlaydir); $overlays->{''} = [ sort(keys(%params)) ]; foreach my $file (glob("*-overlay.dts")) { my $overlay = ($file =~ /^(.*)-overlay.dts$/)[0]; my $redo_cmd = `grep '// redo: ' $file`; if ($redo_cmd =~ /\/\/ redo: ([^\r\n]+)/) { system("cp $file overlaycheck.dts"); system("ovmerge -r overlaycheck.dts > $overlay-overlay.dts"); system("rm overlaycheck.dts"); if (system("git diff --quiet $file") >> 8) { print("* '$overlay' overlay changed after redo\n"); } } $overlays->{ $overlay } = [ get_params($file) ]; } return $overlays; } sub parse_readme { my ($file) = @_; my $overlays = {}; my $fh; error("Failed to open '$file'") if (!open($fh, '<', $file)); my $overlay; my $last_overlay = ''; my $params; my $has_params; my $in_params; my $blank_count; my $linenum = 0; my $descr_column = 32; while (my $line = <$fh>) { $linenum++; chomp($line); error("TABs in README ($linenum)") if ($line =~ /\t/); error("Trailing whitespace in README ($linenum)") if ($line =~ /\s$/); error("Line too long in README ($linenum)") if (length($line) > 80 && $line !~ /^\s*[^\s]+$/); if ($overlay && !$line) { $blank_count++; if ($blank_count == 2) { error("Missing params for overlay $overlay ($linenum)") if (!$params); if (ref $params) { $overlays->{ $overlay } = [ sort(@{$params}) ]; } elsif ($params) { $overlays->{ $overlay } = $params; } else { $overlays->{ $overlay } = [ ]; } $overlay = undef; $params = undef; $in_params = 0; } next; } $blank_count = 0; if (($line =~ /^\w+:\s/) && ($line !~ /^(Name|Info|Load|Params):/)) { error("Bad label ($linenum)"); } if ($line =~ /^Name:(\s*)(.*)\s*$/) { error("Bad formatting in README ($linenum)") if ($1 ne ' '); error("Missing blank lines after overlay? ($linenum)") if ($params); $overlay = $2; $params = undef; $in_params = 0; if ($overlay !~ /^$word_pattern$/ && $overlay ne '') { error("Illegal overlay name '$overlay' in README ($linenum)"); } error("Overlay '$overlay' - order violation in README ($linenum)") if (($overlay cmp $last_overlay) != 1); $last_overlay = ($overlay ne '') ? $overlay : ' '; } elsif ($line =~ /^Info:(\s*)(.*)\s*$/) { error("Bad formatting in README ($linenum)") if ($1 ne ' '); error("Info label with no Name? ($linenum)") if (!$overlay); my $info = $2; if ($2 =~ /^See ($word_pattern)(?:\s+\(.*)?$/) { $params = $1; } } elsif ($line =~ /^Load:(\s*)(.*)\s*$/) { error("Bad formatting in README ($linenum)") if ($1 ne ' '); error("Load label with no Name? ($linenum)") if (!$overlay); my $cmd = $2; $has_params = 0; if ($overlay ne '') { if ($cmd eq '') { # Ignore this overlay $ignore_missing->{$overlay} = 1; $ignore_vestigial->{$overlay} = 1; $overlay = undef; } elsif ($cmd eq '') { # Not a real overlay - a placeholder for common documentation $documentation->{$overlay} = 1; $ignore_missing->{$overlay} = 1; $ignore_vestigial->{$overlay} = 1; $has_params = 1; } else { if ($cmd !~ /^dtoverlay=($word_pattern)(,(?:=|\[=\])?)?$/) { error("Invalid Load example ($linenum)"); } error("Wrong overlay name in Load example ($linenum)") if ($1 ne $overlay); $has_params = 1 if ($2); } } } elsif (defined($overlay) && ($line =~ /^Params:(.*)$/)) { $blank_count = 0; my $rol = $1; $params = []; $in_params = 1; if ($rol) { if ($rol eq ' ') { error("Parameter presence mismatch ($linenum)") if ($has_params); next; } error("Bad formatting in README ($linenum)") if ($rol !~ /^ ([^ ]+)( *)/); error("Parameter presence mismatch ($linenum)") if (!$has_params); my ($param, $indent2) = ($1, length($2)); if ($param !~ /^$readme_param_pattern$/) { error("Invalid parameter name '$param' in README ($linenum)"); } my $descr_indent = 8 + length($param) + $indent2; if (($descr_indent != $descr_column) && ($indent2 != 0)) { error("Bad formatting in README ($linenum)"); } push @$params, $param; } } elsif ($in_params && ($line =~ /^( +)([^ ]+)( *)/)) { my ($indent, $param, $indent2) = (length($1), $2, length($3)); if ($indent == 8) { my $descr_indent = 8 + length($2) + $indent2; # Try to spot a comment if ((($indent2 == 1) || ($indent2 == 0 && $param =~ /:$/)) && ($descr_indent < $descr_column)) { $in_params = 0; next; } if ($param !~ /^$readme_param_pattern$/) { error("Invalid parameter name '$param' in README ($linenum)"); } if (($descr_indent != $descr_column) && ($indent2 != 0)) { error("Bad formatting in README ($linenum)"); } push @$params, $param; } else { error("Bad formatting in README ($linenum)") if (($indent < 8) || (($indent > 8) && ($indent < $descr_column))); } } } # Resolve inter-overlay ("See") references foreach my $overlay (keys(%$overlays)) { my $src = $overlays->{$overlay}; if (!ref $src) { $src = $overlays->{$src}; if (ref $src) { $overlays->{$overlay} = $src; } else { error("Chained 'See' link from $overlay to $src") if (!ref $src) } } } return $overlays; } sub parse_makefile { my ($file) = @_; my $overlays = []; my $fh; error("Failed to open '$file'") if (!open($fh, '<', $file)); push @$overlays, ''; my $last_overlay = ''; my $linenum = 0; my $in_multiline = 0; while (my $line = <$fh>) { $linenum++; chomp($line); error("Trailing whitespace in Makefile ($linenum)") if ($line =~ /\s$/); my $overlay; if ($in_multiline) { if ($line =~ /^\t(.+)\.dtbo( \\)?$/) { $overlay = $1; $in_multiline = 0 if (!$2); } else { error("Syntax error in Makefile ($linenum)"); } } elsif ($line =~ /^dtbo-\$\(RPI_DT_OVERLAYS\) \+= (.+)\.dtbo$/) { $overlay = $1; } elsif ($line =~ /^dtbo-\$\(CONFIG_ARCH_BCM2835\) \+= \\$/) { $in_multiline = 1; } if ($overlay) { error("Overlay '$overlay' - order violation in Makefile ($linenum)") if (($overlay cmp $last_overlay) != 1); push @$overlays, $overlay; $last_overlay = $overlay; } } return $overlays; } sub parse_exclusions { my ($file) = @_; my $missing = {}; my $vestigial = {}; my $fh; error("Failed to open '$file'") if (!open($fh, '<', $file)); my $overlay; my $linenum = 0; while (my $line = <$fh>) { $linenum++; chomp($line); if ($line =~ /^=\s*(.+)$/) { $overlay = $1; $missing->{$overlay} = []; $vestigial->{$overlay} = []; } elsif ($line =~ /^-\s*(.+)$/) { push @{$missing->{$overlay}}, $1; } elsif ($line =~ /^\+\s*(.+)$/) { push @{$vestigial->{$overlay}}, $1; } } return ($missing, $vestigial); } sub get_params { my ($file) = @_; my @params; my $props = {}; print ("[ get_params $file ]\n") if ($verbose); # Run the file through DTS to expand and clean up my $cpp_cmd = join(" ", @cpp_cmd); my @lines = `$cpp_cmd $file | $DTC $dtc_opts -i overlays -@ -I dts -O dts`; my $is_overlay = ($file =~ /-overlay\.dts$/); for (my $i = 0; $i < @lines; $i++) { my $line = $lines[$i]; if ($line =~ /^\s*(__overrides__ \{|target-path\s*=\s*"\/__overrides__"\s*;)\s*$/) { # Support parameters pushed into the base DTB if (0 && $1 =~ /^target-path/) { while ($lines[++$i] !~ /^\s*__overlay__\s*{\s*$/) { } } while ($lines[++$i] =~ /^\s*([^ \}]+)(?: = |;)/) { my $param = $1; my $linenum = $i + 1; error("Invalid parameter name '$param' in '$file'") if ($param !~ /^$word_pattern$/); push @params, $param; if ($lines[$i] =~ /(deadbeef|de ad be ef)/) { printf("$file: $param\n"); $fail = 1; } } } elsif ($is_overlay && $line =~ /^\s*\/\s*{\s*$/) { while ($line !~ /^\s*compatible\s*=/) { $line = $lines[++$i]; if (!$line || $line =~ /{/) { error("Missing overlay compatible string in '$file'"); last; } } my ($comp) = ($line =~ /^\s*compatible\s*=\s*(.*)?\s*;\s*$/); if ($comp) { $comp =~ s/\s+//g; if ($comp !~ /^\"brcm,(bcm2835|bcm2711|bcm2712)\"$/) { error("Invalid overlay compatible string '$comp' in '$file'"); } $props->{$1} = 1; } } elsif ($line =~ /^\s+(?:__symbols__|__fixups__|__local_fixups__)/) { last; } } return ($props, sort(@params)); } sub dtc_cpp { my ($infile, $outfile) = @_; my @dtc_cmd = ( $DTC, @dtc_opts, '-i', 'overlays', '-@', '-I', 'dts', '-O', 'dtb', '-o' ); my $tmpfile = $outfile.".tmp"; error("Failed to CPP '$infile'") if (system(@cpp_cmd, '-o', $tmpfile, $infile) != 0); error("Failed to compile '$infile'") if (system(@dtc_cmd, $outfile, $tmpfile) != 0); unlink($tmpfile); } sub container_checker { my ($name, $exclude, $params) = @_; my $basename = $base_files[0]; my $base = "$TMPDIR/$basename.dtb"; foreach my $param (@$params) { next if (ref $param || ($exclude && $param =~ $exclude)); my $paramx = $param; $paramx =~ s/\d$/x/; my $target = `fdtget -t bx "$TMPDIR/$name.dtbo" /__overrides__ $param`; chomp($target); next if ($target !~ /^0 0 0 0 /); error("Failed to merge $name with $basename") if (system($DTMERGE, $verbose ? ('-d') : (), $base, $MERGED_DTB, "$TMPDIR/$name.dtbo", $param) != 0); my $diffs = `dtdiff $base $MERGED_DTB`; if ($diffs !~ /^\+\s*$param@/m && $diffs !~ /^\+\s*$paramx@/m) { error("container_checker($name): parameter '$param' doesn't enable matching node"); } } } sub dormant_checker { my ($overlay) = @_; my $ph; my $fragnum; my %is_dormant; my %is_wakeable; my $in_overrides = 0; die if (!open($ph, '-|', "ovmerge -N ${overlay}-overlay.dts")); while (my $line = <$ph>) { chomp($line); if ($in_overrides) { if ($line =~ /^\s*};/) { $in_overrides = 0; } elsif ($line =~ /^\s*([-\w]+) = (.*?;)$/) { my ($param, $rol) = ($1,$2); while ($rol =~ /\G<(&\w+|0)>, "([^"]+)"(?:, |;)/cg) { my ($ref, $override) = ($1, $2); if ($ref eq '0') { while ($override =~ /\G([-+=!])(\d+)/cg) { my ($type, $num) = ($1, $2); $is_wakeable{$num} = 1 if ($type ne '-'); } } elsif ($override =~ /=$/) { $rol =~ /\G[^,;]+(?:, |;)/cg; } } } } elsif ($line =~ /\bfragment@(\d+)\s\{$/) { $fragnum = $1; } elsif ($line =~ /\b__dormant__ \{$/) { $is_dormant{$fragnum} = 1; } elsif ($line =~ /\b__overrides__ \{$/) { $in_overrides = 1; } } foreach my $frag (sort { $a <=> $b} keys(%is_dormant)) { error("$overlay: fragment $frag is dormant and cannot be activated") if (!$is_wakeable{$frag}); } } sub pinctrl_checker { my ($overlay) = @_; my $ph; my $fragnum; my $depth = 0; my $matchdepth = 0; my %pinctrl_refs; my ($label, $node); my %fraglabels; my %gpio_labels; my %unlabelled_nodes; my @overrides; my $in_overrides = 0; die if (!open($ph, '-|', "ovmerge -N ${overlay}-overlay.dts")); while (my $line = <$ph>) { chomp($line); # print("$depth,$matchdepth: $line\n"); if ($line =~ /\{$/) { $depth++; } elsif ($line =~ /\};/) { $in_overrides = 0 if ($depth == $in_overrides); $depth--; $matchdepth-- if ($matchdepth); next; } push @overrides, $line if ($in_overrides); $in_overrides = $depth if ($line =~ /^\s*__overrides__ \{/); if ($line =~ /(?:(\w+): )?\bfragment@(\d+)\s\{/) { my $label = $1; $fraglabels{$label} = 1 if ($label); $fragnum = $2; $matchdepth = 1; } elsif ($matchdepth == 1) { if ($line =~ /target = <&gpio>/) { $matchdepth++; } else { $matchdepth = 0; } } elsif ($matchdepth == 2) { if ($line =~ /__(?:overlay|dormant)__ \{/) { $matchdepth++; } else { $matchdepth = 0; } } elsif ($matchdepth == 3) { # Must cope with multiple subnodes if ($line =~ /^\s*(?:(\w+): )?([-_0-9a-zA-Z]+) \{/) { $label = $1 // ""; $node = $2; $matchdepth++; if ($label) { $gpio_labels{$label} = 1; } else { $unlabelled_nodes{$node} = 1; } } else { $matchdepth = 0; } } elsif ($matchdepth == 4) { if ($line =~ /^\s*gpio-hog;/) { delete $unlabelled_nodes{$node}; } } # The regex is deliberately relaxed to match pinctrl-0 properties # _and_ overrides that target pinctrl-0 while ($line =~ /\bpinctrl-0(?: |:\d+)=[^<]+<([^>]+)>/g) { my $refs = $1; while ($refs =~ /&(\w+)/g) { $pinctrl_refs{$1} = 1; } } } my %bad_labels; foreach my $line (@overrides) { my @elements; if ($line =~ /^\s*([-_a-z0-9]+) = (.*);$/) { my $override = $2; # 1. Split into an array of items, as separated by the commas not within strings. while ($override =~ /\G(<[^>]+>|"[^"]*")(?:, |$)/cg) { push @elements, $1; } for (my $i = 1; $i < @elements; $i++) { my $elem = $elements[$i]; if ($elem =~ /^"([-_,a-z0-9]+)/) { my $prop = $1; my $target = $elements[$i - 1]; if ($target =~ /^<&(\w+)>/) { my $label = $1; if ($fraglabels{$label} && $prop !~ /^target(?:-path)?/ && !$bad_labels{$label}) { error("$overlay: misused fragment label $label"); $bad_labels{$label} = 1; } } } } } } foreach my $n (sort(keys(%unlabelled_nodes))) { error("$overlay: gpio node $node has no label"); } foreach my $label (sort(keys(%gpio_labels))) { if (!$pinctrl_refs{$label}) { error("$overlay: gpio node label $label is not referenced"); } } } sub error { print("* $_[0]\n"); $fail = 1; } sub fatal_error { error(@_); exit(1); } sub usage { print("Usage: overlaycheck [] [ ...]\n"); print(" where can be any of:\n"); print("\n"); print(" -h Show this help message\n"); print(" -s Show more strict/picky warnings\n"); print(" -t Try all overlays on all base files\n"); print(" -v Enable verbose output\n"); }