1#!/usr/bin/perl 2# 3# Original version were part of Gerd Knorr's v4l scripts. 4# 5# Several improvements by (c) 2005-2007 Mauro Carvalho Chehab 6# 7# Largely re-written (C) 2007 Trent Piepho <xyzzy@speakeasy.org> 8# Stolen for DRM usage by airlied 9# 10# Theory of Operation 11# 12# This acts as a sort of mini version of cpp, which will process 13# #if/#elif/#ifdef/etc directives to strip out code used to support 14# multiple kernel versions or otherwise not wanted to be sent upstream to 15# git. 16# 17# Conditional compilation directives fall into two catagories, 18# "processed" and "other". The "other" directives are ignored and simply 19# output as they come in without changes (see 'keep' exception). The 20# "processed" variaty are evaluated and only the lines in the 'true' part 21# are kept, like cpp would do. 22# 23# If gentree knows the result of an expression, that directive will be 24# "processed", otherwise it will be an "other". gentree knows the value 25# of LINUX_VERSION_CODE, BTTV_VERSION_CODE, the KERNEL_VERSION(x,y,z) 26# macro, numeric constants like 0 and 1, and a few defines like MM_KERNEL 27# and STV0297_CS2. 28# 29# An exception is if the comment "/*KEEP*/" appears after the expression, 30# in which case that directive will be considered an "other" and not 31# processed, other than to remove the keep comment. 32# 33# Known bugs: 34# don't specify the root directory e.g. '/' or even '////' 35# directives continued with a back-slash will always be ignored 36# you can't modify a source tree in-place, i.e. source dir == dest dir 37 38use strict; 39use File::Find; 40use Fcntl ':mode'; 41 42my $VERSION = shift; 43my $SRC = shift; 44my $DESTDIR = shift; 45 46if (!defined($DESTDIR)) { 47 print "Usage:\ngentree.pl\t<version> <source dir> <dest dir>\n\n"; 48 exit; 49} 50 51my $BTTVCODE = KERNEL_VERSION(0,9,17); 52my ($LINUXCODE, $extra) = kernel_version($VERSION); 53my $DEBUG = 0; 54 55my %defs = ( 56 'LINUX_VERSION_CODE' => $LINUXCODE, 57 'MM_KERNEL' => ($extra =~ /-mm/)?1:0, 58 'DRM_ODD_MM_COMPAT' => 0, 59 'I915_HAVE_FENCE' => 1, 60 'I915_HAVE_BUFFER' => 1, 61 'VIA_HAVE_DMABLIT' => 1, 62 'VIA_HAVE_CORE_MM' => 1, 63 'VIA_HAVE_FENCE' => 1, 64 'VIA_HAVE_BUFFER' => 1, 65 'SIS_HAVE_CORE_MM' => 1, 66 'DRM_FULL_MM_COMPAT' => 1, 67 '__linux__' => 1, 68); 69 70################################################################# 71# helpers 72 73sub kernel_version($) { 74 $_[0] =~ m/(\d+)\.(\d+)\.(\d+)(.*)/; 75 return ($1*65536 + $2*256 + $3, $4); 76} 77 78# used in eval() 79sub KERNEL_VERSION($$$) { return $_[0]*65536 + $_[1]*256 + $_[2]; } 80 81sub evalexp($) { 82 local $_ = shift; 83 s|/\*.*?\*/||go; # delete /* */ comments 84 s|//.*$||o; # delete // comments 85 s/\bdefined\s*\(/(/go; # defined(foo) to (foo) 86 while (/\b([_A-Za-z]\w*)\b/go) { 87 if (exists $defs{$1}) { 88 my $id = $1; my $pos = $-[0]; 89 s/$id/$defs{$id}/; 90 pos = $-[0]; 91 } elsif ($1 ne 'KERNEL_VERSION') { 92 return(undef); 93 } 94 } 95 return(eval($_) ? 1 : 0); 96} 97 98################################################################# 99# filter out version-specific code 100 101sub filter_source ($$) { 102 my ($in,$out) = @_; 103 my $line; 104 my $level=0; 105 my %if = (); 106 my %state = (); 107 108 my @dbgargs = \($level, %state, %if, $line); 109 sub dbgline($\@) { 110 my $level = ${$_[1][0]}; 111 printf STDERR ("/* BP %4d $_[0] state=$_[1][1]->{$level} if=$_[1][2]->{$level} level=$level (${$_[1][3]}) */\n", $.) if $DEBUG; 112 } 113 114 open IN, '<', $in or die "Error opening $in: $!\n"; 115 open OUT, '>', $out or die "Error opening $out: $!\n"; 116 117 print STDERR "File: $in, for kernel $VERSION($LINUXCODE)/\n" if $DEBUG; 118 119 while ($line = <IN>) { 120 chomp $line; 121 next if ($line =~ m/^#include \"compat.h\"/o); 122# next if ($line =~ m/[\$]Id:/); 123 124 # For "#if 0 /*KEEP*/;" the ; should be dropped too 125 if ($line =~ m@^\s*#\s*if(n?def)?\s.*?(\s*/\*\s*(?i)keep\s*\*/;?)@) { 126 $state{$level} = "ifother"; 127 $if{$level} = 1; 128 dbgline "#if$1 (keep)", @dbgargs; 129 $line =~ s/\Q$2\E//; 130 $level++; 131 } 132 # handle all ifdef/ifndef lines 133 elsif ($line =~ /^\s*#\s*if(n?)def\s*(\w+)/o) { 134 if (exists $defs{$2}) { 135 $state{$level} = 'if'; 136 $if{$level} = ($1 eq 'n') ? !$defs{$2} : $defs{$2}; 137 dbgline "#if$1def $2", @dbgargs; 138 $level++; 139 next; 140 } 141 $state{$level} = "ifother"; 142 $if{$level} = 1; 143 dbgline "#if$1def (other)", @dbgargs; 144 $level++; 145 } 146 # handle all ifs 147 elsif ($line =~ /^\s*#\s*if\s+(.*)$/o) { 148 my $res = evalexp($1); 149 if (defined $res) { 150 $state{$level} = 'if'; 151 $if{$level} = $res; 152 dbgline '#if '.($res?'(yes)':'(no)'), @dbgargs; 153 $level++; 154 next; 155 } else { 156 $state{$level} = 'ifother'; 157 $if{$level} = 1; 158 dbgline '#if (other)', @dbgargs; 159 $level++; 160 } 161 } 162 # handle all elifs 163 elsif ($line =~ /^\s*#\s*elif\s+(.*)$/o) { 164 my $exp = $1; 165 $level--; 166 $level < 0 and die "more elifs than ifs"; 167 $state{$level} =~ /if/ or die "unmatched elif"; 168 169 if ($state{$level} eq 'if' && !$if{$level}) { 170 my $res = evalexp($exp); 171 defined $res or die 'moving from if to ifother'; 172 $state{$level} = 'if'; 173 $if{$level} = $res; 174 dbgline '#elif1 '.($res?'(yes)':'(no)'), @dbgargs; 175 $level++; 176 next; 177 } elsif ($state{$level} ne 'ifother') { 178 $if{$level} = 0; 179 $state{$level} = 'elif'; 180 dbgline '#elif0', @dbgargs; 181 $level++; 182 next; 183 } 184 $level++; 185 } 186 elsif ($line =~ /^\s*#\s*else/o) { 187 $level--; 188 $level < 0 and die "more elses than ifs"; 189 $state{$level} =~ /if/ or die "unmatched else"; 190 $if{$level} = !$if{$level} if ($state{$level} eq 'if'); 191 $state{$level} =~ s/^if/else/o; # if -> else, ifother -> elseother, elif -> elif 192 dbgline '#else', @dbgargs; 193 $level++; 194 next if $state{$level-1} !~ /other$/o; 195 } 196 elsif ($line =~ /^\s*#\s*endif/o) { 197 $level--; 198 $level < 0 and die "more endifs than ifs"; 199 dbgline '#endif', @dbgargs; 200 next if $state{$level} !~ /other$/o; 201 } 202 203 my $print = 1; 204 for (my $i=0;$i<$level;$i++) { 205 next if $state{$i} =~ /other$/o; # keep code in ifother/elseother blocks 206 if (!$if{$i}) { 207 $print = 0; 208 dbgline 'DEL', @{[\$i, \%state, \%if, \$line]}; 209 last; 210 } 211 } 212 print OUT "$line\n" if $print; 213 } 214 close IN; 215 close OUT; 216} 217 218################################################################# 219 220sub parse_dir { 221 my $file = $File::Find::name; 222 223 return if ($file =~ /CVS/); 224 return if ($file =~ /~$/); 225 226 my $f2 = $file; 227 $f2 =~ s/^\Q$SRC\E/$DESTDIR/; 228 229 my $mode = (stat($file))[2]; 230 if ($mode & S_IFDIR) { 231 print("mkdir -p '$f2'\n"); 232 system("mkdir -p '$f2'"); # should check for error 233 return; 234 } 235 print "from $file to $f2\n"; 236 237 if ($file =~ m/.*\.[ch]$/) { 238 filter_source($file, $f2); 239 } else { 240 system("cp $file $f2"); 241 } 242} 243 244 245# main 246 247printf "kernel is %s (0x%x)\n",$VERSION,$LINUXCODE; 248 249# remove any trailing slashes from dir names. don't pass in just '/' 250$SRC =~ s|/*$||; $DESTDIR =~ s|/*$||; 251 252print "finding files at $SRC\n"; 253 254find({wanted => \&parse_dir, no_chdir => 1}, $SRC); 255