| #!/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(); |