162306a36Sopenharmony_ci#!/usr/bin/env perl 262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 362306a36Sopenharmony_ci# 462306a36Sopenharmony_ci# Generates a linker script that specifies the correct initcall order. 562306a36Sopenharmony_ci# 662306a36Sopenharmony_ci# Copyright (C) 2019 Google LLC 762306a36Sopenharmony_ci 862306a36Sopenharmony_ciuse strict; 962306a36Sopenharmony_ciuse warnings; 1062306a36Sopenharmony_ciuse IO::Handle; 1162306a36Sopenharmony_ciuse IO::Select; 1262306a36Sopenharmony_ciuse POSIX ":sys_wait_h"; 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_cimy $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?"; 1562306a36Sopenharmony_cimy $objtree = $ENV{'objtree'} || '.'; 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci## currently active child processes 1862306a36Sopenharmony_cimy $jobs = {}; # child process pid -> file handle 1962306a36Sopenharmony_ci## results from child processes 2062306a36Sopenharmony_cimy $results = {}; # object index -> [ { level, secname }, ... ] 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci## reads _NPROCESSORS_ONLN to determine the maximum number of processes to 2362306a36Sopenharmony_ci## start 2462306a36Sopenharmony_cisub get_online_processors { 2562306a36Sopenharmony_ci open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |") 2662306a36Sopenharmony_ci or die "$0: ERROR: failed to execute getconf: $!"; 2762306a36Sopenharmony_ci my $procs = <$fh>; 2862306a36Sopenharmony_ci close($fh); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci if (!($procs =~ /^\d+$/)) { 3162306a36Sopenharmony_ci return 1; 3262306a36Sopenharmony_ci } 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci return int($procs); 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci## writes results to the parent process 3862306a36Sopenharmony_ci## format: <file index> <initcall level> <base initcall section name> 3962306a36Sopenharmony_cisub write_results { 4062306a36Sopenharmony_ci my ($index, $initcalls) = @_; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci # sort by the counter value to ensure the order of initcalls within 4362306a36Sopenharmony_ci # each object file is correct 4462306a36Sopenharmony_ci foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) { 4562306a36Sopenharmony_ci my $level = $initcalls->{$counter}->{'level'}; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci # section name for the initcall function 4862306a36Sopenharmony_ci my $secname = $initcalls->{$counter}->{'module'} . '__' . 4962306a36Sopenharmony_ci $counter . '_' . 5062306a36Sopenharmony_ci $initcalls->{$counter}->{'line'} . '_' . 5162306a36Sopenharmony_ci $initcalls->{$counter}->{'function'}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci print "$index $level $secname\n"; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci## reads a result line from a child process and adds it to the $results array 5862306a36Sopenharmony_cisub read_results{ 5962306a36Sopenharmony_ci my ($fh) = @_; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci # each child prints out a full line w/ autoflush and exits after the 6262306a36Sopenharmony_ci # last line, so even if buffered I/O blocks here, it shouldn't block 6362306a36Sopenharmony_ci # very long 6462306a36Sopenharmony_ci my $data = <$fh>; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (!defined($data)) { 6762306a36Sopenharmony_ci return 0; 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci chomp($data); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci my ($index, $level, $secname) = $data =~ 7362306a36Sopenharmony_ci /^(\d+)\ ([^\ ]+)\ (.*)$/; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci if (!defined($index) || 7662306a36Sopenharmony_ci !defined($level) || 7762306a36Sopenharmony_ci !defined($secname)) { 7862306a36Sopenharmony_ci die "$0: ERROR: child process returned invalid data: $data\n"; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci $index = int($index); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (!exists($results->{$index})) { 8462306a36Sopenharmony_ci $results->{$index} = []; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci push (@{$results->{$index}}, { 8862306a36Sopenharmony_ci 'level' => $level, 8962306a36Sopenharmony_ci 'secname' => $secname 9062306a36Sopenharmony_ci }); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci return 1; 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci## finds initcalls from an object file or all object files in an archive, and 9662306a36Sopenharmony_ci## writes results back to the parent process 9762306a36Sopenharmony_cisub find_initcalls { 9862306a36Sopenharmony_ci my ($index, $file) = @_; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci die "$0: ERROR: file $file doesn't exist?" if (! -f $file); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |") 10362306a36Sopenharmony_ci or die "$0: ERROR: failed to execute \"$nm\": $!"; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci my $initcalls = {}; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci while (<$fh>) { 10862306a36Sopenharmony_ci chomp; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci # check for the start of a new object file (if processing an 11162306a36Sopenharmony_ci # archive) 11262306a36Sopenharmony_ci my ($path)= $_ =~ /^(.+)\:$/; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (defined($path)) { 11562306a36Sopenharmony_ci write_results($index, $initcalls); 11662306a36Sopenharmony_ci $initcalls = {}; 11762306a36Sopenharmony_ci next; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci # look for an initcall 12162306a36Sopenharmony_ci my ($module, $counter, $line, $symbol) = $_ =~ 12262306a36Sopenharmony_ci /[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci if (!defined($module)) { 12562306a36Sopenharmony_ci $module = '' 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (!defined($counter) || 12962306a36Sopenharmony_ci !defined($line) || 13062306a36Sopenharmony_ci !defined($symbol)) { 13162306a36Sopenharmony_ci next; 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci # parse initcall level 13562306a36Sopenharmony_ci my ($function, $level) = $symbol =~ 13662306a36Sopenharmony_ci /^(.*)((early|rootfs|con|[0-9])s?)$/; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci die "$0: ERROR: invalid initcall name $symbol in $file($path)" 13962306a36Sopenharmony_ci if (!defined($function) || !defined($level)); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci $initcalls->{$counter} = { 14262306a36Sopenharmony_ci 'module' => $module, 14362306a36Sopenharmony_ci 'line' => $line, 14462306a36Sopenharmony_ci 'function' => $function, 14562306a36Sopenharmony_ci 'level' => $level, 14662306a36Sopenharmony_ci }; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci close($fh); 15062306a36Sopenharmony_ci write_results($index, $initcalls); 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci## waits for any child process to complete, reads the results, and adds them to 15462306a36Sopenharmony_ci## the $results array for later processing 15562306a36Sopenharmony_cisub wait_for_results { 15662306a36Sopenharmony_ci my ($select) = @_; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci my $pid = 0; 15962306a36Sopenharmony_ci do { 16062306a36Sopenharmony_ci # unblock children that may have a full write buffer 16162306a36Sopenharmony_ci foreach my $fh ($select->can_read(0)) { 16262306a36Sopenharmony_ci read_results($fh); 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci # check for children that have exited, read the remaining data 16662306a36Sopenharmony_ci # from them, and clean up 16762306a36Sopenharmony_ci $pid = waitpid(-1, WNOHANG); 16862306a36Sopenharmony_ci if ($pid > 0) { 16962306a36Sopenharmony_ci if (!exists($jobs->{$pid})) { 17062306a36Sopenharmony_ci next; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci my $fh = $jobs->{$pid}; 17462306a36Sopenharmony_ci $select->remove($fh); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci while (read_results($fh)) { 17762306a36Sopenharmony_ci # until eof 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci close($fh); 18162306a36Sopenharmony_ci delete($jobs->{$pid}); 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci } while ($pid > 0); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci## forks a child to process each file passed in the command line and collects 18762306a36Sopenharmony_ci## the results 18862306a36Sopenharmony_cisub process_files { 18962306a36Sopenharmony_ci my $index = 0; 19062306a36Sopenharmony_ci my $njobs = $ENV{'PARALLELISM'} || get_online_processors(); 19162306a36Sopenharmony_ci my $select = IO::Select->new(); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci while (my $file = shift(@ARGV)) { 19462306a36Sopenharmony_ci # fork a child process and read it's stdout 19562306a36Sopenharmony_ci my $pid = open(my $fh, '-|'); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (!defined($pid)) { 19862306a36Sopenharmony_ci die "$0: ERROR: failed to fork: $!"; 19962306a36Sopenharmony_ci } elsif ($pid) { 20062306a36Sopenharmony_ci # save the child process pid and the file handle 20162306a36Sopenharmony_ci $select->add($fh); 20262306a36Sopenharmony_ci $jobs->{$pid} = $fh; 20362306a36Sopenharmony_ci } else { 20462306a36Sopenharmony_ci # in the child process 20562306a36Sopenharmony_ci STDOUT->autoflush(1); 20662306a36Sopenharmony_ci find_initcalls($index, "$objtree/$file"); 20762306a36Sopenharmony_ci exit; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci $index++; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci # limit the number of children to $njobs 21362306a36Sopenharmony_ci if (scalar(keys(%{$jobs})) >= $njobs) { 21462306a36Sopenharmony_ci wait_for_results($select); 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci # wait for the remaining children to complete 21962306a36Sopenharmony_ci while (scalar(keys(%{$jobs})) > 0) { 22062306a36Sopenharmony_ci wait_for_results($select); 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cisub generate_initcall_lds() { 22562306a36Sopenharmony_ci process_files(); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci my $sections = {}; # level -> [ secname, ...] 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci # sort results to retain link order and split to sections per 23062306a36Sopenharmony_ci # initcall level 23162306a36Sopenharmony_ci foreach my $index (sort { $a <=> $b } keys(%{$results})) { 23262306a36Sopenharmony_ci foreach my $result (@{$results->{$index}}) { 23362306a36Sopenharmony_ci my $level = $result->{'level'}; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci if (!exists($sections->{$level})) { 23662306a36Sopenharmony_ci $sections->{$level} = []; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci push(@{$sections->{$level}}, $result->{'secname'}); 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci die "$0: ERROR: no initcalls?" if (!keys(%{$sections})); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci # print out a linker script that defines the order of initcalls for 24662306a36Sopenharmony_ci # each level 24762306a36Sopenharmony_ci print "SECTIONS {\n"; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci foreach my $level (sort(keys(%{$sections}))) { 25062306a36Sopenharmony_ci my $section; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci if ($level eq 'con') { 25362306a36Sopenharmony_ci $section = '.con_initcall.init'; 25462306a36Sopenharmony_ci } else { 25562306a36Sopenharmony_ci $section = ".initcall${level}.init"; 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci print "\t${section} : {\n"; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci foreach my $secname (@{$sections->{$level}}) { 26162306a36Sopenharmony_ci print "\t\t*(${section}..${secname}) ;\n"; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci print "\t}\n"; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci print "}\n"; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cigenerate_initcall_lds(); 271