xref: /OK3568_Linux_fs/kernel/scripts/generate_initcall_order.pl (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env perl
2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# Generates a linker script that specifies the correct initcall order.
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun# Copyright (C) 2019 Google LLC
7*4882a593Smuzhiyun
8*4882a593Smuzhiyunuse strict;
9*4882a593Smuzhiyunuse warnings;
10*4882a593Smuzhiyunuse IO::Handle;
11*4882a593Smuzhiyunuse IO::Select;
12*4882a593Smuzhiyunuse POSIX ":sys_wait_h";
13*4882a593Smuzhiyun
14*4882a593Smuzhiyunmy $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?";
15*4882a593Smuzhiyunmy $objtree = $ENV{'objtree'} || '.';
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun## currently active child processes
18*4882a593Smuzhiyunmy $jobs = {};		# child process pid -> file handle
19*4882a593Smuzhiyun## results from child processes
20*4882a593Smuzhiyunmy $results = {};	# object index -> [ { level, secname }, ... ]
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun## reads _NPROCESSORS_ONLN to determine the maximum number of processes to
23*4882a593Smuzhiyun## start
24*4882a593Smuzhiyunsub get_online_processors {
25*4882a593Smuzhiyun	open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |")
26*4882a593Smuzhiyun		or die "$0: ERROR: failed to execute getconf: $!";
27*4882a593Smuzhiyun	my $procs = <$fh>;
28*4882a593Smuzhiyun	close($fh);
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun	if (!($procs =~ /^\d+$/)) {
31*4882a593Smuzhiyun		return 1;
32*4882a593Smuzhiyun	}
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun	return int($procs);
35*4882a593Smuzhiyun}
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun## writes results to the parent process
38*4882a593Smuzhiyun## format: <file index> <initcall level> <base initcall section name>
39*4882a593Smuzhiyunsub write_results {
40*4882a593Smuzhiyun	my ($index, $initcalls) = @_;
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun	# sort by the counter value to ensure the order of initcalls within
43*4882a593Smuzhiyun	# each object file is correct
44*4882a593Smuzhiyun	foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) {
45*4882a593Smuzhiyun		my $level = $initcalls->{$counter}->{'level'};
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun		# section name for the initcall function
48*4882a593Smuzhiyun		my $secname = $initcalls->{$counter}->{'module'} . '__' .
49*4882a593Smuzhiyun			      $counter . '_' .
50*4882a593Smuzhiyun			      $initcalls->{$counter}->{'line'} . '_' .
51*4882a593Smuzhiyun			      $initcalls->{$counter}->{'function'};
52*4882a593Smuzhiyun
53*4882a593Smuzhiyun		print "$index $level $secname\n";
54*4882a593Smuzhiyun	}
55*4882a593Smuzhiyun}
56*4882a593Smuzhiyun
57*4882a593Smuzhiyun## reads a result line from a child process and adds it to the $results array
58*4882a593Smuzhiyunsub read_results{
59*4882a593Smuzhiyun	my ($fh) = @_;
60*4882a593Smuzhiyun
61*4882a593Smuzhiyun	# each child prints out a full line w/ autoflush and exits after the
62*4882a593Smuzhiyun	# last line, so even if buffered I/O blocks here, it shouldn't block
63*4882a593Smuzhiyun	# very long
64*4882a593Smuzhiyun	my $data = <$fh>;
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun	if (!defined($data)) {
67*4882a593Smuzhiyun		return 0;
68*4882a593Smuzhiyun	}
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun	chomp($data);
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun	my ($index, $level, $secname) = $data =~
73*4882a593Smuzhiyun		/^(\d+)\ ([^\ ]+)\ (.*)$/;
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun	if (!defined($index) ||
76*4882a593Smuzhiyun		!defined($level) ||
77*4882a593Smuzhiyun		!defined($secname)) {
78*4882a593Smuzhiyun		die "$0: ERROR: child process returned invalid data: $data\n";
79*4882a593Smuzhiyun	}
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun	$index = int($index);
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun	if (!exists($results->{$index})) {
84*4882a593Smuzhiyun		$results->{$index} = [];
85*4882a593Smuzhiyun	}
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun	push (@{$results->{$index}}, {
88*4882a593Smuzhiyun		'level'   => $level,
89*4882a593Smuzhiyun		'secname' => $secname
90*4882a593Smuzhiyun	});
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun	return 1;
93*4882a593Smuzhiyun}
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun## finds initcalls from an object file or all object files in an archive, and
96*4882a593Smuzhiyun## writes results back to the parent process
97*4882a593Smuzhiyunsub find_initcalls {
98*4882a593Smuzhiyun	my ($index, $file) = @_;
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun	die "$0: ERROR: file $file doesn't exist?" if (! -f $file);
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun	open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |")
103*4882a593Smuzhiyun		or die "$0: ERROR: failed to execute \"$nm\": $!";
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun	my $initcalls = {};
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun	while (<$fh>) {
108*4882a593Smuzhiyun		chomp;
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun		# check for the start of a new object file (if processing an
111*4882a593Smuzhiyun		# archive)
112*4882a593Smuzhiyun		my ($path)= $_ =~ /^(.+)\:$/;
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun		if (defined($path)) {
115*4882a593Smuzhiyun			write_results($index, $initcalls);
116*4882a593Smuzhiyun			$initcalls = {};
117*4882a593Smuzhiyun			next;
118*4882a593Smuzhiyun		}
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun		# look for an initcall
121*4882a593Smuzhiyun		my ($module, $counter, $line, $symbol) = $_ =~
122*4882a593Smuzhiyun			/[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/;
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun		if (!defined($module)) {
125*4882a593Smuzhiyun			$module = ''
126*4882a593Smuzhiyun		}
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun		if (!defined($counter) ||
129*4882a593Smuzhiyun			!defined($line) ||
130*4882a593Smuzhiyun			!defined($symbol)) {
131*4882a593Smuzhiyun			next;
132*4882a593Smuzhiyun		}
133*4882a593Smuzhiyun
134*4882a593Smuzhiyun		# parse initcall level
135*4882a593Smuzhiyun		my ($function, $level) = $symbol =~
136*4882a593Smuzhiyun			/^(.*)((early|rootfs|con|[0-9])s?)$/;
137*4882a593Smuzhiyun
138*4882a593Smuzhiyun		die "$0: ERROR: invalid initcall name $symbol in $file($path)"
139*4882a593Smuzhiyun			if (!defined($function) || !defined($level));
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun		$initcalls->{$counter} = {
142*4882a593Smuzhiyun			'module'   => $module,
143*4882a593Smuzhiyun			'line'     => $line,
144*4882a593Smuzhiyun			'function' => $function,
145*4882a593Smuzhiyun			'level'    => $level,
146*4882a593Smuzhiyun		};
147*4882a593Smuzhiyun	}
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun	close($fh);
150*4882a593Smuzhiyun	write_results($index, $initcalls);
151*4882a593Smuzhiyun}
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun## waits for any child process to complete, reads the results, and adds them to
154*4882a593Smuzhiyun## the $results array for later processing
155*4882a593Smuzhiyunsub wait_for_results {
156*4882a593Smuzhiyun	my ($select) = @_;
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun	my $pid = 0;
159*4882a593Smuzhiyun	do {
160*4882a593Smuzhiyun		# unblock children that may have a full write buffer
161*4882a593Smuzhiyun		foreach my $fh ($select->can_read(0)) {
162*4882a593Smuzhiyun			read_results($fh);
163*4882a593Smuzhiyun		}
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun		# check for children that have exited, read the remaining data
166*4882a593Smuzhiyun		# from them, and clean up
167*4882a593Smuzhiyun		$pid = waitpid(-1, WNOHANG);
168*4882a593Smuzhiyun		if ($pid > 0) {
169*4882a593Smuzhiyun			if (!exists($jobs->{$pid})) {
170*4882a593Smuzhiyun				next;
171*4882a593Smuzhiyun			}
172*4882a593Smuzhiyun
173*4882a593Smuzhiyun			my $fh = $jobs->{$pid};
174*4882a593Smuzhiyun			$select->remove($fh);
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun			while (read_results($fh)) {
177*4882a593Smuzhiyun				# until eof
178*4882a593Smuzhiyun			}
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun			close($fh);
181*4882a593Smuzhiyun			delete($jobs->{$pid});
182*4882a593Smuzhiyun		}
183*4882a593Smuzhiyun	} while ($pid > 0);
184*4882a593Smuzhiyun}
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun## forks a child to process each file passed in the command line and collects
187*4882a593Smuzhiyun## the results
188*4882a593Smuzhiyunsub process_files {
189*4882a593Smuzhiyun	my $index = 0;
190*4882a593Smuzhiyun	my $njobs = $ENV{'PARALLELISM'} || get_online_processors();
191*4882a593Smuzhiyun	my $select = IO::Select->new();
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun	while (my $file = shift(@ARGV)) {
194*4882a593Smuzhiyun		# fork a child process and read it's stdout
195*4882a593Smuzhiyun		my $pid = open(my $fh, '-|');
196*4882a593Smuzhiyun
197*4882a593Smuzhiyun		if (!defined($pid)) {
198*4882a593Smuzhiyun			die "$0: ERROR: failed to fork: $!";
199*4882a593Smuzhiyun		} elsif ($pid) {
200*4882a593Smuzhiyun			# save the child process pid and the file handle
201*4882a593Smuzhiyun			$select->add($fh);
202*4882a593Smuzhiyun			$jobs->{$pid} = $fh;
203*4882a593Smuzhiyun		} else {
204*4882a593Smuzhiyun			# in the child process
205*4882a593Smuzhiyun			STDOUT->autoflush(1);
206*4882a593Smuzhiyun			find_initcalls($index, "$objtree/$file");
207*4882a593Smuzhiyun			exit;
208*4882a593Smuzhiyun		}
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun		$index++;
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun		# limit the number of children to $njobs
213*4882a593Smuzhiyun		if (scalar(keys(%{$jobs})) >= $njobs) {
214*4882a593Smuzhiyun			wait_for_results($select);
215*4882a593Smuzhiyun		}
216*4882a593Smuzhiyun	}
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun	# wait for the remaining children to complete
219*4882a593Smuzhiyun	while (scalar(keys(%{$jobs})) > 0) {
220*4882a593Smuzhiyun		wait_for_results($select);
221*4882a593Smuzhiyun	}
222*4882a593Smuzhiyun}
223*4882a593Smuzhiyun
224*4882a593Smuzhiyunsub generate_initcall_lds() {
225*4882a593Smuzhiyun	process_files();
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun	my $sections = {};	# level -> [ secname, ...]
228*4882a593Smuzhiyun
229*4882a593Smuzhiyun	# sort results to retain link order and split to sections per
230*4882a593Smuzhiyun	# initcall level
231*4882a593Smuzhiyun	foreach my $index (sort { $a <=> $b } keys(%{$results})) {
232*4882a593Smuzhiyun		foreach my $result (@{$results->{$index}}) {
233*4882a593Smuzhiyun			my $level = $result->{'level'};
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun			if (!exists($sections->{$level})) {
236*4882a593Smuzhiyun				$sections->{$level} = [];
237*4882a593Smuzhiyun			}
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun			push(@{$sections->{$level}}, $result->{'secname'});
240*4882a593Smuzhiyun		}
241*4882a593Smuzhiyun	}
242*4882a593Smuzhiyun
243*4882a593Smuzhiyun	die "$0: ERROR: no initcalls?" if (!keys(%{$sections}));
244*4882a593Smuzhiyun
245*4882a593Smuzhiyun	# print out a linker script that defines the order of initcalls for
246*4882a593Smuzhiyun	# each level
247*4882a593Smuzhiyun	print "SECTIONS {\n";
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun	foreach my $level (sort(keys(%{$sections}))) {
250*4882a593Smuzhiyun		my $section;
251*4882a593Smuzhiyun
252*4882a593Smuzhiyun		if ($level eq 'con') {
253*4882a593Smuzhiyun			$section = '.con_initcall.init';
254*4882a593Smuzhiyun		} else {
255*4882a593Smuzhiyun			$section = ".initcall${level}.init";
256*4882a593Smuzhiyun		}
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun		print "\t${section} : {\n";
259*4882a593Smuzhiyun
260*4882a593Smuzhiyun		foreach my $secname (@{$sections->{$level}}) {
261*4882a593Smuzhiyun			print "\t\t*(${section}..${secname}) ;\n";
262*4882a593Smuzhiyun		}
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun		print "\t}\n";
265*4882a593Smuzhiyun	}
266*4882a593Smuzhiyun
267*4882a593Smuzhiyun	print "}\n";
268*4882a593Smuzhiyun}
269*4882a593Smuzhiyun
270*4882a593Smuzhiyungenerate_initcall_lds();
271