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