18c2ecf20Sopenharmony_ci#!/usr/bin/env perl
28c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
38c2ecf20Sopenharmony_ci#
48c2ecf20Sopenharmony_ci# Generates a linker script that specifies the correct initcall order.
58c2ecf20Sopenharmony_ci#
68c2ecf20Sopenharmony_ci# Copyright (C) 2019 Google LLC
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ciuse strict;
98c2ecf20Sopenharmony_ciuse warnings;
108c2ecf20Sopenharmony_ciuse IO::Handle;
118c2ecf20Sopenharmony_ciuse IO::Select;
128c2ecf20Sopenharmony_ciuse POSIX ":sys_wait_h";
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cimy $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?";
158c2ecf20Sopenharmony_cimy $objtree = $ENV{'objtree'} || '.';
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci## currently active child processes
188c2ecf20Sopenharmony_cimy $jobs = {};		# child process pid -> file handle
198c2ecf20Sopenharmony_ci## results from child processes
208c2ecf20Sopenharmony_cimy $results = {};	# object index -> [ { level, secname }, ... ]
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci## reads _NPROCESSORS_ONLN to determine the maximum number of processes to
238c2ecf20Sopenharmony_ci## start
248c2ecf20Sopenharmony_cisub get_online_processors {
258c2ecf20Sopenharmony_ci	open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |")
268c2ecf20Sopenharmony_ci		or die "$0: ERROR: failed to execute getconf: $!";
278c2ecf20Sopenharmony_ci	my $procs = <$fh>;
288c2ecf20Sopenharmony_ci	close($fh);
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	if (!($procs =~ /^\d+$/)) {
318c2ecf20Sopenharmony_ci		return 1;
328c2ecf20Sopenharmony_ci	}
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	return int($procs);
358c2ecf20Sopenharmony_ci}
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci## writes results to the parent process
388c2ecf20Sopenharmony_ci## format: <file index> <initcall level> <base initcall section name>
398c2ecf20Sopenharmony_cisub write_results {
408c2ecf20Sopenharmony_ci	my ($index, $initcalls) = @_;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	# sort by the counter value to ensure the order of initcalls within
438c2ecf20Sopenharmony_ci	# each object file is correct
448c2ecf20Sopenharmony_ci	foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) {
458c2ecf20Sopenharmony_ci		my $level = $initcalls->{$counter}->{'level'};
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci		# section name for the initcall function
488c2ecf20Sopenharmony_ci		my $secname = $initcalls->{$counter}->{'module'} . '__' .
498c2ecf20Sopenharmony_ci			      $counter . '_' .
508c2ecf20Sopenharmony_ci			      $initcalls->{$counter}->{'line'} . '_' .
518c2ecf20Sopenharmony_ci			      $initcalls->{$counter}->{'function'};
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci		print "$index $level $secname\n";
548c2ecf20Sopenharmony_ci	}
558c2ecf20Sopenharmony_ci}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci## reads a result line from a child process and adds it to the $results array
588c2ecf20Sopenharmony_cisub read_results{
598c2ecf20Sopenharmony_ci	my ($fh) = @_;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	# each child prints out a full line w/ autoflush and exits after the
628c2ecf20Sopenharmony_ci	# last line, so even if buffered I/O blocks here, it shouldn't block
638c2ecf20Sopenharmony_ci	# very long
648c2ecf20Sopenharmony_ci	my $data = <$fh>;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	if (!defined($data)) {
678c2ecf20Sopenharmony_ci		return 0;
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	chomp($data);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	my ($index, $level, $secname) = $data =~
738c2ecf20Sopenharmony_ci		/^(\d+)\ ([^\ ]+)\ (.*)$/;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	if (!defined($index) ||
768c2ecf20Sopenharmony_ci		!defined($level) ||
778c2ecf20Sopenharmony_ci		!defined($secname)) {
788c2ecf20Sopenharmony_ci		die "$0: ERROR: child process returned invalid data: $data\n";
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	$index = int($index);
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	if (!exists($results->{$index})) {
848c2ecf20Sopenharmony_ci		$results->{$index} = [];
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	push (@{$results->{$index}}, {
888c2ecf20Sopenharmony_ci		'level'   => $level,
898c2ecf20Sopenharmony_ci		'secname' => $secname
908c2ecf20Sopenharmony_ci	});
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	return 1;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci## finds initcalls from an object file or all object files in an archive, and
968c2ecf20Sopenharmony_ci## writes results back to the parent process
978c2ecf20Sopenharmony_cisub find_initcalls {
988c2ecf20Sopenharmony_ci	my ($index, $file) = @_;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	die "$0: ERROR: file $file doesn't exist?" if (! -f $file);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |")
1038c2ecf20Sopenharmony_ci		or die "$0: ERROR: failed to execute \"$nm\": $!";
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	my $initcalls = {};
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	while (<$fh>) {
1088c2ecf20Sopenharmony_ci		chomp;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci		# check for the start of a new object file (if processing an
1118c2ecf20Sopenharmony_ci		# archive)
1128c2ecf20Sopenharmony_ci		my ($path)= $_ =~ /^(.+)\:$/;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci		if (defined($path)) {
1158c2ecf20Sopenharmony_ci			write_results($index, $initcalls);
1168c2ecf20Sopenharmony_ci			$initcalls = {};
1178c2ecf20Sopenharmony_ci			next;
1188c2ecf20Sopenharmony_ci		}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci		# look for an initcall
1218c2ecf20Sopenharmony_ci		my ($module, $counter, $line, $symbol) = $_ =~
1228c2ecf20Sopenharmony_ci			/[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci		if (!defined($module)) {
1258c2ecf20Sopenharmony_ci			$module = ''
1268c2ecf20Sopenharmony_ci		}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci		if (!defined($counter) ||
1298c2ecf20Sopenharmony_ci			!defined($line) ||
1308c2ecf20Sopenharmony_ci			!defined($symbol)) {
1318c2ecf20Sopenharmony_ci			next;
1328c2ecf20Sopenharmony_ci		}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci		# parse initcall level
1358c2ecf20Sopenharmony_ci		my ($function, $level) = $symbol =~
1368c2ecf20Sopenharmony_ci			/^(.*)((early|rootfs|con|[0-9])s?)$/;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci		die "$0: ERROR: invalid initcall name $symbol in $file($path)"
1398c2ecf20Sopenharmony_ci			if (!defined($function) || !defined($level));
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		$initcalls->{$counter} = {
1428c2ecf20Sopenharmony_ci			'module'   => $module,
1438c2ecf20Sopenharmony_ci			'line'     => $line,
1448c2ecf20Sopenharmony_ci			'function' => $function,
1458c2ecf20Sopenharmony_ci			'level'    => $level,
1468c2ecf20Sopenharmony_ci		};
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	close($fh);
1508c2ecf20Sopenharmony_ci	write_results($index, $initcalls);
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci## waits for any child process to complete, reads the results, and adds them to
1548c2ecf20Sopenharmony_ci## the $results array for later processing
1558c2ecf20Sopenharmony_cisub wait_for_results {
1568c2ecf20Sopenharmony_ci	my ($select) = @_;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	my $pid = 0;
1598c2ecf20Sopenharmony_ci	do {
1608c2ecf20Sopenharmony_ci		# unblock children that may have a full write buffer
1618c2ecf20Sopenharmony_ci		foreach my $fh ($select->can_read(0)) {
1628c2ecf20Sopenharmony_ci			read_results($fh);
1638c2ecf20Sopenharmony_ci		}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		# check for children that have exited, read the remaining data
1668c2ecf20Sopenharmony_ci		# from them, and clean up
1678c2ecf20Sopenharmony_ci		$pid = waitpid(-1, WNOHANG);
1688c2ecf20Sopenharmony_ci		if ($pid > 0) {
1698c2ecf20Sopenharmony_ci			if (!exists($jobs->{$pid})) {
1708c2ecf20Sopenharmony_ci				next;
1718c2ecf20Sopenharmony_ci			}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci			my $fh = $jobs->{$pid};
1748c2ecf20Sopenharmony_ci			$select->remove($fh);
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci			while (read_results($fh)) {
1778c2ecf20Sopenharmony_ci				# until eof
1788c2ecf20Sopenharmony_ci			}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci			close($fh);
1818c2ecf20Sopenharmony_ci			delete($jobs->{$pid});
1828c2ecf20Sopenharmony_ci		}
1838c2ecf20Sopenharmony_ci	} while ($pid > 0);
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci## forks a child to process each file passed in the command line and collects
1878c2ecf20Sopenharmony_ci## the results
1888c2ecf20Sopenharmony_cisub process_files {
1898c2ecf20Sopenharmony_ci	my $index = 0;
1908c2ecf20Sopenharmony_ci	my $njobs = $ENV{'PARALLELISM'} || get_online_processors();
1918c2ecf20Sopenharmony_ci	my $select = IO::Select->new();
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	while (my $file = shift(@ARGV)) {
1948c2ecf20Sopenharmony_ci		# fork a child process and read it's stdout
1958c2ecf20Sopenharmony_ci		my $pid = open(my $fh, '-|');
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci		if (!defined($pid)) {
1988c2ecf20Sopenharmony_ci			die "$0: ERROR: failed to fork: $!";
1998c2ecf20Sopenharmony_ci		} elsif ($pid) {
2008c2ecf20Sopenharmony_ci			# save the child process pid and the file handle
2018c2ecf20Sopenharmony_ci			$select->add($fh);
2028c2ecf20Sopenharmony_ci			$jobs->{$pid} = $fh;
2038c2ecf20Sopenharmony_ci		} else {
2048c2ecf20Sopenharmony_ci			# in the child process
2058c2ecf20Sopenharmony_ci			STDOUT->autoflush(1);
2068c2ecf20Sopenharmony_ci			find_initcalls($index, "$objtree/$file");
2078c2ecf20Sopenharmony_ci			exit;
2088c2ecf20Sopenharmony_ci		}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci		$index++;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		# limit the number of children to $njobs
2138c2ecf20Sopenharmony_ci		if (scalar(keys(%{$jobs})) >= $njobs) {
2148c2ecf20Sopenharmony_ci			wait_for_results($select);
2158c2ecf20Sopenharmony_ci		}
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	# wait for the remaining children to complete
2198c2ecf20Sopenharmony_ci	while (scalar(keys(%{$jobs})) > 0) {
2208c2ecf20Sopenharmony_ci		wait_for_results($select);
2218c2ecf20Sopenharmony_ci	}
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_cisub generate_initcall_lds() {
2258c2ecf20Sopenharmony_ci	process_files();
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	my $sections = {};	# level -> [ secname, ...]
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	# sort results to retain link order and split to sections per
2308c2ecf20Sopenharmony_ci	# initcall level
2318c2ecf20Sopenharmony_ci	foreach my $index (sort { $a <=> $b } keys(%{$results})) {
2328c2ecf20Sopenharmony_ci		foreach my $result (@{$results->{$index}}) {
2338c2ecf20Sopenharmony_ci			my $level = $result->{'level'};
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci			if (!exists($sections->{$level})) {
2368c2ecf20Sopenharmony_ci				$sections->{$level} = [];
2378c2ecf20Sopenharmony_ci			}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci			push(@{$sections->{$level}}, $result->{'secname'});
2408c2ecf20Sopenharmony_ci		}
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	die "$0: ERROR: no initcalls?" if (!keys(%{$sections}));
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	# print out a linker script that defines the order of initcalls for
2468c2ecf20Sopenharmony_ci	# each level
2478c2ecf20Sopenharmony_ci	print "SECTIONS {\n";
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	foreach my $level (sort(keys(%{$sections}))) {
2508c2ecf20Sopenharmony_ci		my $section;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci		if ($level eq 'con') {
2538c2ecf20Sopenharmony_ci			$section = '.con_initcall.init';
2548c2ecf20Sopenharmony_ci		} else {
2558c2ecf20Sopenharmony_ci			$section = ".initcall${level}.init";
2568c2ecf20Sopenharmony_ci		}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci		print "\t${section} : {\n";
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci		foreach my $secname (@{$sections->{$level}}) {
2618c2ecf20Sopenharmony_ci			print "\t\t*(${section}..${secname}) ;\n";
2628c2ecf20Sopenharmony_ci		}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci		print "\t}\n";
2658c2ecf20Sopenharmony_ci	}
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	print "}\n";
2688c2ecf20Sopenharmony_ci}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cigenerate_initcall_lds();
271