[PATCH] scripts/dtc: dt_to_config - report kernel config options for a devicetree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




From: Frank Rowand <frank.rowand@xxxxxxxxxxx>

Determining which kernel config options need to be enabled for a
given devicetree can be a painful process.  Create a new tool to
find the drivers that may match a devicetree node compatible,
find the kernel config options that enable the driver, and
optionally report whether the kernel config option is enabled.

Signed-off-by: Gaurav Minocha <gaurav.minocha.os@xxxxxxxxx>
Signed-off-by: Frank Rowand <frank.rowand@xxxxxxxxxxx>

---
 scripts/dtc/dt_to_config | 1061 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1061 insertions(+)

Index: b/scripts/dtc/dt_to_config
===================================================================
--- /dev/null
+++ b/scripts/dtc/dt_to_config
@@ -0,0 +1,1061 @@
+#!/usr/bin/perl
+
+#   Copyright 2016 by Frank Rowand
+# © Copyright 2016 by Gaurav Minocha
+#
+# This file is subject to the terms and conditions of the GNU General Public
+# License v2.
+
+use strict 'refs';
+use strict subs;
+
+use Getopt::Long;
+
+$VUFX = "160428a";
+
+$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 is not set",
+    "one or more kernel config file options is set to 'm'",
+    "one or more kernel config file options is set to 'y'",
+    "one of more kernel config file options fails 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 = (
+	'fixed-partitions'	=> '1',
+	'none'			=> '1',
+	'pci'			=> '1',
+	'simple-bus'		=> '1',
+);
+
+# magic compatibles, have a driver
+#
+# Will not search for drivers for these compatibles.
+# Will instead use the drivers and config options listed here.
+#
+# If you add an entry to this hash, add the corresponding entry
+# to %driver_config_hard_code_list.
+#
+# 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!
+#
+# TODO: 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'],
+	'gpios'			=> ['drivers/leds/leds-tca6507.c'],
+	'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'],
+);
+
+%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/leds/leds-tca6507.c'		=> ['CONFIG_LEDS_TCA6507'],
+	'drivers/input/keyboard/gpio_keys.c'	=> ['CONFIG_KEYBOARD_GPIO'],
+	'drivers/i2c/busses/i2c-gpio.c'		=> ['CONFIG_I2C_GPIO'],
+	'arch/mips/mti-malta/malta-dt.c'	=> ['obj-y'],
+	'arch/x86/kernel/devicetree.c'		=> ['CONFIG_OF'],
+	'drivers/mtd/maps/physmap_of.c'		=> ['CONFIG_MTD_PHYSMAP_OF'],
+	'drivers/video/backlight/pwm_bl.c'	=> ['CONFIG_BACKLIGHT_PWM'],
+	'drivers/mfd/syscon.c'			=> ['CONFIG_MFD_SYSCON'],
+
+	# 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
+# driver_black_list.
+
+@driver_black_list = (
+	'virt/kvm/arm/.*',
+);
+
+
+sub usage()
+{
+	print STDERR
+"
+Usage: $script_name [options] device-tree...
+
+    device_tree is: dts_file | dtb_file | proc_device-tree
+
+
+Valid options:
+     -b                  ignore driver black list
+     -c FILE             Read kernel config options from FILE
+    --config FILE        synonym for 'c'
+    --exclude-flag FLAG  exclude entries with a matching flag
+     -h                  Display this message and exit
+    --help               synonym for 'h'
+    --include-flag FLAG  include only entries with a matching flag
+    --include-suspect    include only entries with an uppercase flag
+    --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.
+
+  CAUTION:
+     This program uses heuristics to guess which driver(s) support each
+     compatible string and which config option 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 STDERR "     %s   %s\n",
+		              $pr_flag_value[$k], $pr_flag_help[$k];
+	}
+
+	print STDERR
+"
+     Upper case letters indicate potential issues or problems.
+
+
+  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)
+
+";
+}
+
+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 $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);
+			if ($not) {
+				&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;
+	}
+
+	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 $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;
+	}
+
+	print "$node : $compat : $driver : $config : ";
+
+	if ($config_file) {
+		my @configs = split(' && ', $config);
+		for $configs (@configs) {
+			$configs =~ s/^!//;
+			my $existing_config = $existing_config{$configs};
+			if (!$existing_config) {
+				# check for /-m/, /-y/, or /-objs/
+				if (($configs !~ /CONFIG_/) &&
+				    ($configs ne "no_config") &&
+				    ($configs ne "no_makefile")) {
+				$existing_config = "x";
+				};
+			};
+			if ($existing_config) {
+				print "$separator", "$existing_config";
+				$separator = ", ";
+			} else {
+				print "$separator", "n";
+				$separator = ", ";
+			}
+		}
+	} else {
+		print "none";
+	}
+
+	print "\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 $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{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);
+				}
+			}
+			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 (!$ignore_driver_black_list) {
+				for $black (@driver_black_list) {
+					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 (! `which dtx_diff`) {
+		print STDERR "\n";
+		print STDERR "file 'dtx_diff' is not executable or does not exist\n";
+		print STDERR "   Is scripts/dtc/ in the shell \$PATH variable?\n";
+		print STDERR "   Are you in the root directory of a kernel tree?\n";
+		print STDERR "\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($node, $compatible, $node_enabled);
+
+			$node = $line;
+			$node =~ s/^\s*(.*)\s+\{.*/$1/;
+			$node =~ s/.*: //;
+
+			$compatible = "";
+			$node_enabled = "";
+			next FILE;
+		}
+
+		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($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(
+	"b"               => \$ignore_driver_black_list,
+	"c=s"             => \$config_file,
+	"h"               => \$help,
+	"config=s"        => \$config_file,
+	"exclude-flag=s"  => \@exclude_flag,
+	"help"            => \$help,
+	"include-flag=s"  => \@include_flag,
+	"include-suspect" => \$include_suspect,
+	"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 white listed 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.\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")) {
+			for my $driver (@{ $driver_hard_code_list{$compat} }) {
+				print "   $compat";
+				print " " x ($max_compat_len - length $compat);
+				print "  $driver\n";
+			}
+		}
+	}
+
+
+	print "\n\n";
+	print "The configuration option for these drivers is hard coded.\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")) {
+			for my $config (@{ $driver_config_hard_code_list{$driver} }) {
+				print "   $driver";
+				print " " x ($max_driver_len - length $driver);
+				print "  $config\n";
+			}
+		}
+	}
+
+
+	print "\n\n";
+	print "These compatibles are black listed.  They will not be reported without '-b'.\n";
+	print "\n";
+	for my $driver (@driver_black_list) {
+		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;
+}
+
+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} };
+		}
+	}
+}
+
+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;
+		}
+	}
+}
+
+
+for $file (@ARGV) {
+	&read_dts($file);
+}
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux