fcb3ea321c4ac40591093d79894284ef5539ed5e
[mknbi.git] / mknbi.pl
1 #!/usr/bin/perl -w
2
3 # Program to create a netboot image for ROM/FreeDOS/DOS/Linux
4 # Placed under GNU Public License by Ken Yap, December 2000
5
6 # 2003.04.28 R. Main
7 #  Tweaks to work with new first-dos.S for large disk images
8
9 BEGIN {
10         push(@INC, '@@LIBDIR@@');
11 }
12
13 use strict;
14 use Getopt::Long;
15 use Fcntl qw(SEEK_SET);
16 use Socket;
17
18 use TruncFD;
19 use Nbi;
20 use Elf;
21
22 use constant;
23 use constant DEBUG => 0;
24 use constant LUA_VERSION => 0x04000100; # 4.0.1.0
25
26 use bytes;
27
28 use vars qw($libdir $version $format $target $output $module $relocseg $relocsegstr
29         $progreturns $param $append $rootdir $rootmode $ip $ramdisk $rdbase
30         $simhd $dishd $squashfd $first32 $showversion);
31
32 sub check_file
33 {
34         my ($f, $status);
35
36         $status = 1;
37         foreach $f (@_) {
38                 if (!-e $f) {
39                         print STDERR "$f: file not found\n";
40                         $status = 0;
41                 } elsif (!-f $f) {
42                         print STDERR "$f: not a plain file\n";
43                         $status = 0;
44                 } elsif (!-r $f) {
45                         print STDERR "$f: file not readable\n";
46                         $status = 0;
47                 }
48         }
49         return ($status);
50 }
51
52 sub mknbi_rom ($)
53 {
54         my ($format) = @_;
55         my ($romdesc);
56
57         $#ARGV >= 0 or die "Usage: $0 romimage\n";
58         return unless check_file($ARGV[0]);
59         $format->add_header("mknbi-rom-$version", $relocseg + 0x3E0, 0x6000, 6);
60         $romdesc = { file => $ARGV[0],
61                 segment => 0x6000,
62                 maxlen => 0x10000,
63                 id => 16,
64                 end => 1 };
65         $format->add_segment($romdesc);
66         $format->dump_segments();
67         $format->copy_file($romdesc);
68 }
69
70 sub inet_aton_warn
71 {
72         my ($ip);
73
74         print STDERR "Warning: $_[0] cannot be resolved to an IP address\n" unless defined($ip = inet_aton($_[0]));
75         return ($ip);
76 }
77
78 sub resolve_names
79 {
80         my ($i);
81
82         my ($client, $server, $gateway, $netmask, $hostname) = split(/:/, $_[0], 5);
83         unless (defined($hostname)) {
84                 print STDERR "$_[0]: invalid specification\n";
85                 return ($_[0]);
86         }
87         $client = inet_ntoa($i) if defined($i = &inet_aton_warn($client));
88         $server = inet_ntoa($i) if defined($i = &inet_aton_warn($server));
89         $gateway = inet_ntoa($i) if defined($i = &inet_aton_warn($gateway));
90         return (join(':', $client, $server, $gateway, $netmask, $hostname));
91 }
92
93 sub make_paramstring ($)
94 {
95         my ($paramsize) = @_;
96         my ($string, $nfsroot);
97
98         # --param= overrides everything
99         return ($param) if (defined($param));
100         # String substitute various options, should do sanity checks also
101         if (!defined($rootdir)) {
102                 $rootdir = '/dev/nfs';
103         } elsif ($rootdir !~ m(^/dev/)) {
104                 $nfsroot = $rootdir;
105                 undef($nfsroot) if ($nfsroot eq 'kernel');
106                 $rootdir = '/dev/nfs';
107         }
108         if (defined($ip)) {
109                 if ($ip eq 'kernel') {
110                         undef($ip);
111                 } elsif ($ip !~ /^(rom|off|none|on|any|dhcp|bootp|rarp|both)$/) {
112                         $ip = &resolve_names($ip);
113                 }
114         } elsif (!defined($ramdisk)) {
115                 print STDERR "Warning: The --ip option was not used; you may need it if you use NFSroot.\n\tPlease see the documentation.\n";
116         }
117         die "Ramdisk mode should be one of: top asis 0xNNNNNNNN (hex address)\n"
118                 if (defined($rdbase) and $rdbase !~ /^(top|asis|0x[\da-fA-F]{1,8})$/);
119         # If rootmode is set, then check if it's rw or ro, and if so, use it
120         if (defined($rootmode) and $rootmode !~ /^(rw|ro)$/) {
121                 die "-rootmode should be either rw or ro\n";
122                 undef($rootmode);
123         }
124         $string = defined($rootmode) ? $rootmode : 'rw';
125         $string .= " root=$rootdir";
126         $string .= " nfsroot=$nfsroot" if (defined($nfsroot));
127         $string .= " ip=$ip" if (defined($ip));
128         $string .= " rdbase=$rdbase" if (defined($rdbase));
129         $string .= " $append" if (defined($append));
130         return ($string);
131 }
132
133 use constant HEADER_SEG_OFFSET => 0x220;        # in units of 16 bytes
134 use constant START_OFFSET => 0x280;             # in units of 16 bytes
135 use constant START_MAX_LENGTH => 6144;
136 use constant PARAM_SEG_OFFSET => 0x240;         # in units of 16 bytes
137 use constant PARAM_MAX_LENGTH => 1024;
138
139 sub mknbi_linux ($)
140 {
141         my ($format) = @_;
142         my ($startaddr, $setupfile, $setupfile32, $libfile, $kernelfile, $setupdesc);
143         my ($paramseg, $paramstring, $bootseg, $block);
144         my ($setupseg, $kernelseg, $kernellen, $ramdiskseg, $rdloc);
145         my ($setupsects, $flags, $syssize, $swapdev,
146                 $ramsize, $vidmode, $rootdev, $sig, $ver, $bigker);
147
148         $startaddr = sprintf("%#x", ($relocseg + START_OFFSET) * 0x10);
149         $libfile = ($main::format eq 'elf') ? "first32elf\@${startaddr}.linux" : "first32\@${startaddr}.linux";
150         # if empty, use default
151         $setupfile = $first32 eq '' ? "$libdir/$libfile" : $first32;
152         $#ARGV >= 0 or die "Usage: $0 kernelimage [ramdisk]\n";
153         $kernelfile = $ARGV[0];
154         return unless check_file($setupfile, $kernelfile);
155         if (defined($ramdisk = $ARGV[1])) {
156                 return unless check_file($ramdisk);
157         }
158         $format->add_pm_header("mknbi-linux-$version", $relocseg + HEADER_SEG_OFFSET, hex($startaddr), $progreturns);
159         $setupdesc = { file => $setupfile,
160                 segment => hex($startaddr) / 0x10,
161                 maxlen => START_MAX_LENGTH,
162                 id => 16 };
163         $paramstring = &make_paramstring(PARAM_MAX_LENGTH);
164         $paramseg = { string => $paramstring,
165                 segment => $relocseg + PARAM_SEG_OFFSET,
166                 maxlen => 2048,
167                 id => 17 };
168         $bootseg = { file => $kernelfile,
169                 segment => $relocseg + 0x0,
170                 len => 512,
171                 maxlen => 512,
172                 id => 18 };
173         $format->peek_file($bootseg, \$block, 512) == 512
174                 or die "Error reading boot sector of $kernelfile\n";
175         (undef, $setupsects, $flags, $syssize, $swapdev, $ramsize, $vidmode,
176                 $rootdev, $sig) = unpack('a497Cv7', $block);
177         if ($sig != 0xAA55) {
178                 print STDERR "$kernelfile: not a Linux kernel image\n";
179                 return;
180         }
181         print STDERR 'setupsects flags syssize swapdev ramsize vidmode rootdev sig', "\n" if (DEBUG);
182         print STDERR "$setupsects $flags $syssize $swapdev $ramsize $vidmode $rootdev $sig\n" if (DEBUG);
183         $setupseg = { file => $kernelfile,
184                 segment => $relocseg + 0x20,
185                 fromoff => 512,
186                 len => $setupsects * 512,
187                 maxlen => 8192,
188                 id => 19 };
189         $format->peek_file($setupseg, \$block, 512) == 512
190                 or die "Error reading first setup sector of $kernelfile\n";
191         (undef, $sig, $ver, undef, undef, undef, undef, undef, $flags) =
192                 unpack('va4v5C2', $block);
193         print STDERR 'sig ver flags', "\n" if (DEBUG);
194         print STDERR "$sig $ver $flags\n" if (DEBUG);
195         if ($sig ne 'HdrS' or $ver < 0x201) {
196                 print STDERR "$kernelfile: not a Linux kernel image\n";
197                 return;
198         }
199         $bigker = ($flags & 0x1);
200         $kernelseg = { file => $ARGV[0],
201                 segment => $bigker ? 0x10000 : 0x1000,
202                 maxlen => $bigker ? undef : 1024 * 512, 
203                 fromoff => $setupsects * 512 + 512,
204                 id => 20,
205                 end => 1 };
206         $ramdiskseg = { file => $ramdisk,
207                 segment => 0x10000,
208                 align => 4096,
209                 id => 21,
210                 end => 1 };
211         $$kernelseg{'end'} = 0 if (defined($ramdisk));
212         $format->add_segment($setupdesc);
213         $format->add_segment($paramseg);
214         $format->add_segment($bootseg);
215         $format->add_segment($setupseg);
216         $kernellen = $format->add_segment($kernelseg);
217         if (!$bigker and $kernellen > (($relocseg - 0x1000) * 16)) {
218                 print STDERR "Warning, zImage kernel may collide with Etherboot\n";
219         }
220         # Put ramdisk following kernel at next 4096 byte boundary
221         $$ramdiskseg{'segment'} += (($kernellen + 0xFFF) & ~0xFFF) >> 4 if ($bigker);
222         # should be 0, 1 or 2 depending on rdbase
223         $format->add_segment($ramdiskseg, "\x00") if (defined($ramdisk));
224         $format->dump_segments();
225         $format->copy_file($setupdesc);
226         $format->copy_string($paramseg);
227         $format->copy_file($bootseg);
228         $format->copy_file($setupseg);
229         $format->copy_file($kernelseg);
230         $format->copy_file($ramdiskseg) if (defined($ramdisk));
231 }
232
233 sub get_geom ($$)
234 {
235         my ($file, $block) = @_;
236         my ($usedsize, $declsize, $firsttracksize, $geom_string, $fstype);
237         my ($secttot, $secttrk, $heads, $bigsectors, $bootid, $sig, $cyltot);
238
239         ($usedsize = $squashfd ? &TruncFD::truncfd($file) : -s $file) > 0
240                 or die "Error reading $file\n";
241         (undef, $secttot, undef, $secttrk, $heads, undef, $bigsectors, $bootid, undef,
242                 $fstype, $sig) = unpack('a19va3vva4VCa17a5@510a2', $$block);
243         print STDERR "Warning, this doesn't appear to be a DOS boot sector\n"
244                 if ($sig ne "\x55\xAA");
245         
246         if ($secttot == 0) {
247             $secttot = $bigsectors;
248         }
249         
250         print STDERR "Geometry...\n";
251         print STDERR "totSect:$secttot,spt:$secttrk,hd:$heads,bid:$bootid,fsType:$fstype,sig:$sig,simhd:$simhd\n";
252
253         if ($simhd) {
254                 # change MediaDescriptor
255                 substr($$block, 0x15, 1) = "\xF8";
256                 # change HiddenSectors
257                 substr($$block, 0x1c, 4) = pack('V', $secttrk);
258                 # change the boot drive
259                 substr($$block, 0x24, 1) = "\x80";
260         }
261         $cyltot = $secttot / ($secttrk * $heads);
262         $declsize = $secttot * 512;
263         $firsttracksize = $secttrk * 512;
264         print STDERR "Warning, used size $usedsize is greater than declared size $declsize\n"
265                 if ($usedsize > $declsize);
266                 
267         print STDERR "cyl:$cyltot,decl_size:$declsize,used_size:$usedsize\n";
268                 
269         $geom_string = pack('Vv3C2', $secttot, $heads, $secttrk, $cyltot, $simhd ? 0x80 : 0, $dishd);
270         return ($usedsize, $declsize, $firsttracksize, $geom_string, $fstype);
271 }
272
273 sub mod_geom_string ($)
274 {
275         my ($geom_string) = @_;
276         my ($secttot, $heads, $secttrk, $cyltot, $simhd, $dishd) = unpack('Vv3C2', $geom_string);
277         $cyltot++;      # for partition table
278         return (pack('Vv3C2', $secttot, $heads, $secttrk, $cyltot, $simhd, $dishd));
279 }
280
281 sub encode_chs ($$$)
282 {
283         my ($c, $h, $s) = @_;
284
285         $s = ($s & 0x3F) | (($c & 0x300) >> 2);
286         $c &= 0xFF;
287         return ($h, $s, $c);
288 }
289
290 sub make_mbr ($$)
291 {
292         my ($geom_string, $fstype) = @_;
293         my ($bootsect);
294         my ($secttot, $heads, $secttrk, $cyltot, $simhd, $x) = unpack('Vv3C2', $geom_string);
295
296         $cyltot--;
297         # $cyltot was incremented in mod_geom_string
298 #       $heads = $secttot / ($secttrk * $cyltot);
299         # bootsect is first sector of track 1
300         $bootsect = $secttrk;
301         
302         print STDERR "--- Making MBR\n";
303         print STDERR "cyls:$cyltot,heads:$heads,spt:$secttrk,totalSect:$secttot,FSTYPE:$fstype,BS:$bootsect\n";
304         
305         # CHS stupidity:
306         # cylinders is 0 based, heads is 0 based, but sectors is 1 based
307         # 0x01 for FAT12, 0x04 for FAT16
308         return (pack('@446C8V2@510v', 0x80, &encode_chs(0, 1, 1),
309                 $fstype eq 'FAT12' ? 0x01 : 0x04, &encode_chs($cyltot, $heads - 1, $secttrk),
310                 $bootsect, $secttot, 0xAA55));
311 }
312
313 sub mknbi_fdos ($)
314 {
315         my ($format) = @_;
316         my ($setupfile, $bootblock);
317         my ($usedsize, $declsize, $firsttracksize, $geom_string, $fstype);
318         my ($setupdesc, $kerneldesc, $firsttrackdesc, $bootdesc, $floppydesc);
319
320         $setupfile = "$libdir/first.fdos";
321         $#ARGV >= 1 or die "Usage: $0 kernel.sys floppyimage\n";
322         return unless check_file($setupfile, $ARGV[0], $ARGV[1]);
323         $format->add_header("mknbi-fdos-$version", $relocseg + 0x200, $relocseg + 0x300, 0);
324         $setupdesc = { file => $setupfile,
325                 segment => $relocseg + 0x300,
326                 maxlen => 4096,
327                 id => 16 };
328         $kerneldesc = { file => $ARGV[0],
329                 segment => @@FDKSEG@@,
330                 id => 17 };
331         die "Ramdisk base should be of the form 0xNNNNNNNN (linear hex address)\n"
332                 if (defined($rdbase) and $rdbase !~ /^0x[\da-fA-F]{1,8}$/);
333         $floppydesc = { file => $ARGV[1],
334                 segment => (defined($rdbase) ? (hex($rdbase) >> 4) : 0x11000),
335                 id => 18,
336                 end => 1 };
337         $format->add_segment($setupdesc);
338         $format->add_segment($kerneldesc);
339         $format->peek_file($floppydesc, \$bootblock, 512) == 512
340                 or die "Error reading boot sector of $ARGV[1]\n";
341         ($usedsize, $declsize, $firsttracksize, $geom_string, $fstype)
342                 = &get_geom($ARGV[1], \$bootblock);
343         $firsttrackdesc = { align => $firsttracksize };
344         $$floppydesc{'fromoff'} = 512;
345         $$floppydesc{'len'} = $usedsize;
346         $$floppydesc{'len'} += $firsttracksize if $simhd;
347         $$floppydesc{'maxlen'} = $declsize;
348         $geom_string = &mod_geom_string($geom_string) if $simhd;
349         $format->add_segment($floppydesc, $geom_string);
350         $format->dump_segments();
351         $format->copy_file($setupdesc);
352         $format->copy_file($kerneldesc);
353         if ($simhd) {
354                 $$firsttrackdesc{'string'} = &make_mbr($geom_string, $fstype);
355                 $format->copy_string($firsttrackdesc);
356         }
357         # write out modified bootblock, not the one in the file
358         $bootdesc = { string => $bootblock };
359         $format->copy_string($bootdesc);
360         # Restore correct value of len and account for bootblock skipped
361         $$floppydesc{'len'} = $usedsize - 512;
362         $format->copy_file($floppydesc);
363 }
364
365 sub mknbi_dos ($)
366 {
367         my ($format) = @_;
368         my ($setupfile, $bootblock);
369         my ($usedsize, $declsize, $firsttracksize, $geom_string, $fstype);
370         my ($setupdesc, $firsttrackdesc, $bootdesc, $floppydesc);
371
372         $setupfile = "$libdir/first.dos";
373         $#ARGV >= 0 or die "Usage: $0 floppyimage\n";
374         return unless check_file($setupfile, $ARGV[0]);
375         $format->add_header("mknbi-dos-$version", 0x1000, 0x1040, 0);
376         $setupdesc = { file => $setupfile,
377                 segment => 0x1040,
378                 maxlen => 64512,
379                 id => 16 };
380         die "Ramdisk base should be of the form 0xNNNNNNNN (linear hex address)\n"
381                 if (defined($rdbase) and $rdbase !~ /^0x[\da-fA-F]{1,8}$/);
382         $floppydesc = { file => $ARGV[0],
383                 segment => (defined($rdbase) ? (hex($rdbase) >> 4) : 0x11000),
384                 id => 17,
385                 end => 1 };
386         $format->add_segment($setupdesc);
387         $format->peek_file($floppydesc, \$bootblock, 512) == 512
388                 or die "Error reading boot sector of $ARGV[0]\n";
389         ($usedsize, $declsize, $firsttracksize, $geom_string, $fstype)
390                 = &get_geom($ARGV[0], \$bootblock);
391         $firsttrackdesc = { align => $firsttracksize };
392         $$floppydesc{'fromoff'} = 512;
393         $$floppydesc{'len'} = $usedsize;
394         $$floppydesc{'len'} += $firsttracksize if $simhd;
395         $$floppydesc{'maxlen'} = $declsize;
396         $geom_string = &mod_geom_string($geom_string) if $simhd;
397         $format->add_segment($floppydesc, $geom_string);
398         $format->dump_segments();
399         $format->copy_file($setupdesc);
400         if ($simhd) {
401                 $$firsttrackdesc{'string'} = &make_mbr($geom_string, $fstype);
402                 $format->copy_string($firsttrackdesc);
403         }
404         # write out modified bootblock, not the one in the file
405         $bootdesc = { string => $bootblock };
406         $format->copy_string($bootdesc);
407         # Restore correct value of len and account for bootblock skipped
408         $$floppydesc{'len'} = $usedsize - 512;
409         $format->copy_file($floppydesc);
410 }
411
412 sub mknbi_menu ($)
413 {
414         my ($module) = @_;
415         my ($menudesc, $datadesc);
416
417         $#ARGV >= -1 or die "Usage: mk$format-menu [menudata]\n";
418         print STDERR "Warning: mk$format-menu requires Etherboot 5.0 or later\n";
419         return unless check_file("$libdir/menu");
420         # $progreturns == 1
421         $module->add_pm_header("mknbi-menu-$version", $relocseg + 0x0, 0x60000, 1);
422         $menudesc = { file => "$libdir/menu",
423                 segment => 0x6000,
424                 maxlen => 0x10000,
425                 id => 16 };
426         $module->add_segment($menudesc);
427         if ($#ARGV >= 0) {
428                 return unless check_file($ARGV[0]);
429                 $datadesc = { file => $ARGV[0],
430                         segment => 0x7000,
431                         maxlen => 0x10000,
432                         id => 17,
433                         end => 1 };
434                 $module->add_segment($datadesc);
435         } else {
436                 $$menudesc{'end'} = 1;
437         }
438         $module->dump_segments();
439         $module->copy_file($menudesc);
440         $module->copy_file($datadesc) if ($#ARGV >= 0);
441 }
442
443 sub mknbi_nfl ($)
444 {
445         my ($module) = @_;
446         my ($menudesc, $datadesc);
447
448         $#ARGV >= -1 or die "Usage: mk$format-nfl [menudata]\n";
449         print STDERR "Warning: mk$format-nfl requires Etherboot 5.0 or later\n";
450         return unless check_file("$libdir/nfl");
451         # $progreturns == 1
452         $module->add_pm_header("mknbi-nfl-$version", $relocseg + 0x0, 0x60000, 1);
453         $menudesc = { file => "$libdir/nfl",
454                 segment => 0x6000,
455                 maxlen => 0x10000,
456                 id => 16 };
457         $module->add_segment($menudesc);
458         if ($#ARGV >= 0) {
459                 return unless check_file($ARGV[0]);
460                 $datadesc = { file => $ARGV[0],
461                         segment => 0x7000,
462                         maxlen => 0x10000,
463                         id => 17,
464                         end => 1 };
465                 $module->add_segment($datadesc);
466         } else {
467                 $$menudesc{'end'} = 1;
468         }
469         $module->dump_segments();
470         $module->copy_file($menudesc);
471         $module->copy_file($datadesc) if ($#ARGV >= 0);
472 }
473
474 #
475 #       Packing of LUA program:
476 #       LUA version as network int32, i.e. 4.0.1.0
477 #       progname rounded to int32 boundary
478 #       length of program as network int32
479 #       program
480 #
481 sub read_lua_prog ($) {
482         my ($progfile) = @_;
483
484         open(P, $progfile) or die "Shouldn't happen, we already did check_file!\n";
485         # remove all but last component of filename
486         $progfile =~ s:.*/::;
487         $progfile .= "\x00";
488         local $/;
489         local $_ = <P>;
490         close(P);
491         my $len = length($progfile);
492         $len = ($len + 3) & ~0x3;
493         return (pack("Na${len}Na*", LUA_VERSION, $progfile, length($_), $_));
494 }
495
496 sub mkelf_lua ($)
497 {
498         my ($module) = @_;
499         my ($menudesc, $datadesc);
500
501         $format eq 'elf' or die "Only ELF images are catered for\n";
502         $#ARGV >= 0 or die "Usage: mkelf-lua luaprog\n";
503         print STDERR "Warning: mkelf-lua requires Etherboot 5.0 or later\n";
504         return unless check_file("$libdir/lua", $ARGV[0]);
505         # $progreturns == 1
506         $module->add_pm_header("mkelf-lua-$version", 0x7c0, 0x60000, 1);
507         $menudesc = { file => "$libdir/lua",
508                 segment => 0x6000,
509                 maxlen => 0x20000,
510                 id => 16 };
511         $module->add_segment($menudesc);
512         my $progstring = &read_lua_prog($ARGV[0]);
513         my $progdesc = { string => $progstring,
514                 segment => 0x8000,
515                 maxlen => 0x4000,
516                 id => 17,
517                 end => 1 };
518         $module->add_segment($progdesc);
519         $module->dump_segments();
520         $module->copy_file($menudesc);
521         $module->copy_string($progdesc);
522 }
523
524 $libdir = '@@LIBDIR@@';         # where config and auxiliary files are stored
525
526 $version = '@@VERSION@@';
527 $showversion = '';
528 $simhd = 0;
529 $dishd = 0;
530 $squashfd = 1;
531 $relocsegstr = '0x9000';
532 $progreturns = 0;
533 GetOptions('format=s' => \$format,
534         'target=s' => \$target,
535         'output=s' => \$output,
536         'param=s' => \$param,
537         'append=s' => \$append,
538         'rootdir=s' => \$rootdir,
539         'rootmode=s' => \$rootmode,
540         'ip=s' => \$ip,
541         'rdbase=s' => \$rdbase,
542         'harddisk!' => \$simhd,
543         'disableharddisk!' => \$dishd,
544         'squash!' => \$squashfd,
545         'first32:s' => \$first32,
546         'progreturns!' => \$progreturns,
547         'relocseg=s' => \$relocsegstr,
548         'version' => \$showversion);
549
550 if ($showversion) {
551         print STDERR "$version\n";
552         exit 0;
553 }
554
555 if ($ENV{LANG} =~ /\.UTF-8$/i) {
556         print STDERR <<'EOF';
557 Warning: Perl 5.8 may have a bug that affects handing of strings in Unicode
558 locales that may cause misbehaviour with binary files.  To work around this
559 problem, set $LANG to not have a suffix of .UTF-8 before running this program.
560 EOF
561 }
562
563 $0 =~ /mk([a-z]*)-([a-z]+)$/ and ($format = $1, $target = $2);
564 if (!defined($format)) {
565         print STDERR "No format specified with program name or --format=\n";
566         exit 1;
567 }
568 if (!defined($target)) {
569         print STDERR "No target specified with program name or --target=\n";
570         exit 1;
571 }
572 if (defined($output)) {
573         die "$output: $!\n" unless open(STDOUT, ">$output");
574 }
575 binmode(STDOUT);
576
577 if ($format eq 'nbi') {
578         $first32 = '' if !defined($first32);
579         $module = Nbi->new($libdir);
580 } elsif ($format eq 'elf') {
581         $first32 = '' if !defined($first32);
582         $module = Elf->new($libdir);
583         die "Output must be file\n" unless (seek(STDOUT, 0, SEEK_SET));
584 } else {
585         die "Format $format not supported\n";
586 }
587 if ($relocsegstr eq '0x9000' or $relocsegstr eq '0x8000') {
588         $relocseg = hex($relocsegstr);
589 } else {
590         print STDERR "relocseg must be 0x9000 or 0x8000 only, setting to 0x9000\n";
591         $relocseg = 0x9000;
592 }
593 if ($target eq 'rom') {
594         &mknbi_rom($module);
595 } elsif ($target eq 'linux') {
596         &mknbi_linux($module);
597 } elsif ($target eq 'fdos') {
598         if ($simhd and $dishd) {
599                 print STDERR "Warning: --harddisk and --disableharddisk are incompatible\n";
600         }
601         &mknbi_fdos($module);
602 } elsif ($target eq 'dos') {
603         if ($simhd and $dishd) {
604                 print STDERR "Warning: --harddisk and --disableharddisk are incompatible\n";
605         }
606         &mknbi_dos($module);
607 } elsif ($target eq 'menu') {
608         &mknbi_menu($module);
609 } elsif ($target eq 'nfl') {
610         &mknbi_nfl($module);
611 } elsif ($target eq 'lua') {
612         &mkelf_lua($module);
613 } else {
614         print STDERR "Target $target not supported\n";
615         exit;
616 }
617 $module->finalise_image();
618 close(STDOUT);
619 exit 0;
620
621 __END__
622
623 =head1 NAME
624
625 mknbi - make network bootable image
626
627 =head1 SYNOPSIS
628
629 B<mknbi> --version
630
631 B<mknbi> --format=I<format> --target=I<target> [--output=I<outputfile>] I<target-specific-arguments>
632
633 B<mknbi-linux> [--output=I<outputfile>] I<kernelimage> [I<ramdisk>]
634
635 B<mkelf-linux> [--output=I<outputfile>] I<kernelimage> [I<ramdisk>]
636
637 B<mknbi-rom> [--output=I<outputfile>] I<ROM-image>
638
639 B<mknbi-menu> [--output=I<outputfile>] [I<dataimage>]
640
641 B<mkelf-menu> [--output=I<outputfile>] [I<dataimage>]
642
643 B<mknbi-nfl> [--output=I<outputfile>] [I<dataimage>]
644
645 B<mkelf-nfl> [--output=I<outputfile>] [I<dataimage>]
646
647 B<mkelf-lua> [--output=I<outputfile>] I<luabin>
648
649 B<mknbi-fdos> [--output=I<outputfile>] I<kernel.sys floppyimage>
650
651 B<mknbi-dos> [--output=I<outputfile>] I<floppyimage>
652
653 =head1 DESCRIPTION
654
655 B<mknbi> is a program that makes network bootable images for various
656 operating systems suitable for network loading by Etherboot or Netboot,
657 which are ROM boot loaders.  If you are looking to boot using PXE, look
658 no further, mknbi is not what you want. You probably want something like
659 PXELINUX which is part of the SYSLINUX package.
660
661 B<mknbi> --version prints the current version. Use this before reporting
662 problems.
663
664 B<mknbi> can be invoked with the B<--format> and B<--target> options or
665 links can be made to it under format and target specific names. E.g.
666 mkelf-linux is the same as mknbi --format=elf --target=linux.
667
668 B<--format>=I<format> Specify the format of the output. Currently
669 available are nbi and elf.  ELF format only works with linux and menu.
670 Otherwise the invocation is the same as for mknbi. In discussions below,
671 the mknbi form is used.
672
673 B<--target>=I<target> Specify the target binary. Currently available are
674 linux, menu, rom, fdos and dos. B<mknbi> is not needed for booting
675 FreeBSD.
676
677 B<--output=>I<outputfile> Specify the output file, can be used with
678 all variants.  Stdout is the default.
679
680 The package must be installed in the destination location before the
681 executables can be run, because it looks for library files.
682
683 Each of the variants will be described separately.
684
685 =head1 MKNBI-LINUX
686
687 B<mknbi-linux> makes a tagged image from a Linux kernel image, either
688 a zImage or a bzImage.
689
690 =head1 MKNBI-LINUX OPTIONS
691
692 B<--param=>I<string> Replace the default parameter string with the
693 specified one. This option overrides all the following options so you
694 should know what you are doing.
695
696 B<--append>=I<string> Appends the specified string to the existing
697 parameter string. This option operates after the other parameter options
698 have been evaluated.
699
700 B<--rootdir>=I<rootdir> Define name of directory to mount via NFS from
701 the boot server.
702
703 In the absence of this option, the default is to use the directory
704 C</tftpboot/>I<%s>, with the I<%s> representing the hostname or
705 IP-address of the booting system, depending on whether the hostname
706 attribute is present in the BOOTP/DHCP reply.
707
708 If C<rom> is given, and if the BOOTP/DHCP server is able to handle the RFC 1497
709 extensions, the value of the rootpath option is used as the root directory.
710
711 If the name given to the option starts with C</dev/>, the corresponding
712 device is used as the root device, and no NFS directory will be mounted.
713
714 B<--rootmode>=C<ro|rw> Defines whether the root device will be mounted
715 read-only or read-write respectively. Without this parameter, the
716 default is C<rw>.
717
718 B<--ip=>I<string> Define client and server IP addresses.
719
720 In the absence of this option no IP addresses are defined, and the
721 kernel will determine the IP addresses by itself, usually by using DHCP,
722 BOOTP or RARP.  Note that the kernel's query is I<in addition to> the
723 query made by the bootrom, and requires the IP: kernel level
724 autoconfiguration (CONFIG_IP_PNP) feature to be included in the kernel.
725
726 Important note: In Linux kernels 2.2.x where x >= 18, and 2.4.x where x
727 >= 5, it is B<necessary> to specify one of the enabling options in the
728 next paragraph to cause the IP autoconfiguration to be activated.
729 Unlike in previous kernels, IP autoconfiguration does not happen by
730 default. Also note that IP autoconfiguration and NFSroot are likely to
731 go away in Linux 2.6 and that userspace IP configuration methods using
732 ramdisk and userspace DHCP daemons are preferred now.
733
734 If one of the following: C<off, none, on, any, dhcp, bootp, rarp, both>,
735 is given, then the option will be passed unmodified to the kernel and
736 cause that autoconfig option to be chosen.
737
738 If C<rom> is given as the argument to this option, all necessary IP
739 addresses for NFS root mounting will be inherited from the BOOTP/DHCP
740 answer the bootrom got from the server.
741
742 It's also possible to define the addresses during compilation of the boot
743 image. Then, all addresses must be separated by a colon, and ordered in
744 the following way:
745
746 C<--ip=>I<client:server:gateway:netmask:hostname[:dev[:proto]]>
747
748 Using this option B<mknbi-linux> will automatically convert system names
749 into decimal IP addresses for the first three entries in this string.
750 The B<hostname> entry will be used by the kernel to set the host name of
751 the booted Linux diskless client.  When more than one network interface
752 is installed in the diskless client, it is possible to specify the name
753 of the interface to use for mounting the root directory via NFS by
754 giving the optional value C<dev>.  This entry has to start with the
755 string C<eth> followed by a number from 0 to 9. However, if only one
756 interface is installed in the client, this I<dev> entry including the
757 preceding semicolon can be left out. The I<proto> argument is one of the
758 IP autoconfiguration enabling options listed above.  (Author: it's not
759 clear to me what the IP autoconfiguration does when the parameters are
760 already specified.  Perhaps it's to obtain parameters not specified,
761 e.g. NIS domain.)
762
763 B<--rdbase=>I<top|asis|0xNNNNNNNN> Set the ramdisk load address.  C<top>
764 moves the ramdisk to the top of memory before jumping to the kernel.
765 This is the default if rdbase is not specified.  This option requires
766 that first-linux's kernel sizing work correctly.  C<asis> loads it at
767 0x100000 (1MB) if the kernel is loaded low; or leaves it just after the
768 kernel in memory, if the kernel is loaded high. For this option to work,
769 the kernel must be able to handle ramdisks at these addresses.
770 I<0xNNNNNNNN> moves the ramdisk to the hex address specified. The onus
771 is on the user to specify a suitable address that is acceptable to the
772 kernel and doesn't overlap with any other segments. It will have to be
773 aligned to a 4k byte boundary so you should ensure that this is so. (The
774 last three hex digits must be 0.)
775
776 B<--first32=>I<program> Override the default first stage setup
777 program.  It can be used to call extensions to the Etherboot code, which
778 paves the way for additional useful functionality without enlarging the
779 size of the Etherboot footprint.  --first32 is implied by the ELF
780 format.
781
782 B<--progreturns> This option is used in conjunction with and only valid
783 with the --first32 option to indicate to the Etherboot loader that the
784 called program will return to loader and hence Etherboot should not
785 disable the network device as is the case when the program will never
786 return to Etherboot.
787
788 B<--relocseg=>I<segaddr> This option is used to specify a relocation of
789 the Linux first, boot, setup, and parameter segments to another 64k
790 band.  Currently the only valid values are 0x9000 and 0x8000,
791 corresponding to linear addresses of 0x90000 and 0x80000 upwards. The
792 default is 0x9000.  Usually you use this option if you have relocated
793 Etherboot to 0x84000 to avoid other code in the 0x90000 segment like
794 DOC. The Linux kernel must support relocation which implies a 2.4 kernel
795 or later. --relocseg only works reliably with ELF or --first32=.
796
797 B<mem=>I<memsize> This is not a command line option but a kernel
798 parameter that is intercepted by the first32 stage and used as the top
799 of memory, to match Linux's interpretation. I<memsize> can be suffixed
800 by C<G> to indicate gibibytes (times 2^30), C<M> to indicate mebibytes
801 (times 2^20) or C<K> to indicate kibibytes (times 2^10). Note that the
802 suffixes are uppercase. This kernel parameter can be specified in
803 --append= or option-129 of the DHCP/BOOTP record.
804
805 Run the program thus:
806
807 C<mknbi-linux> I<kernel-image> [I<ramdisk-image>] > C<linux.nb>
808
809 Then move F<linux.nb> to where the network booting process expects to
810 find it.
811
812 =head1 MKNBI-LINUX BOOTP/DHCP VENDOR TAGS
813
814 B<mknbi-linux> includes a startup code at the beginning of the Linux
815 kernel which is able to detect certain BOOTP vendor defined tags. These
816 can be used to modify the kernel loading process at runtime. To use
817 these tags with bootpd, a publicly available BOOTP server daemon, you
818 can use the following syntax in the F</etc/bootptab> file:
819
820 C<T>I<number>C<=">I<string>C<">
821
822 For example, to specify a different root NFS device, you can use:
823
824 C<T130="eth1">
825
826 The following tags are presently supported by B<mknbi-linux>:
827
828 B<129> The I<string> value given with this tag is appended verbatim to
829 the end of the kernel command line.  It can be used to specify arguments
830 like I/O addresses or DMA channels required for special hardware
831 like SCSI adapters, network cards etc. Please consult the Linux kernel
832 documentation about the syntax required by those options. It is the same
833 as the B<--append> command line option to B<mknbi-linux>, but works at
834 boot time instead of image build time.
835
836 B<130> With this tag it is possible to the select the network adapter
837 used for mounting root via NFS on a multihomed diskless client. The
838 syntax for the I<string> value is the same as for the C<dev> entry used
839 with the B<--ip=> option as described above. However note that the
840 B<mknbi-linux> runtime setup routine does not check the syntax of the
841 string.
842
843 The same tags will work in DHCP with the appropriate syntax for your
844 DHCP server configuration file.
845
846 Remember that you need to specify tag 128 in the correct format in order
847 for the previous tags to be valid. See the documentation file
848 vendortags.
849
850 =head1 MKNBI-ROM
851
852 B<mknbi-rom> makes a tagged image from an Etherboot C<.rom> or C<.lzrom>
853 boot ROM image.  This allows it to be netbooted using an existing
854 ROM. This is useful for developing Etherboot drivers or to load a newer
855 version of Etherboot with an older one.
856
857 Run mknbi like this:
858
859 C<mknbi-rom nic.lzrom> > C<nic.nb>
860
861 Move F<nic.nb> to where the network booting process expects to find it.
862 The boot ROM will load this as the I<operating system> and execute the
863 ROM image.
864
865 =head1 MKNBI-MENU
866
867 B<mknbi-menu> and B<mkelf-menu> make a tagged or ELF image from an
868 auxiliary menu program. Etherboot has the ability to load an auxiliary
869 program which can interact with the user, modify the DHCP structure, and
870 return a status.  Based on the status, Etherboot can load another
871 binary, restart or exit.  This makes it possible to have elaborate user
872 interface programs without having to modify Etherboot. The specification
873 for auxiliary program is documented in the Etherboot Developer's Manual.
874
875 B<mknbi-menu> and B<mkelf-menu> take a binary named C<menu> from the
876 library directory, which is assumed to have an entry point of 0x60000.
877 An optional argument is accepted, and this is loaded at 0x80000. This
878 can be a data file used by the menu program.
879
880 Currently, the menu binary provided duplicates the builtin menu facility
881 of Etherboot with the exception of a couple of small differences: no
882 server or gateway specifications are used and nested TFTP loads don't
883 work. You should not have MOTD or IMAGE_MENU defined in your Etherboot
884 build to be able to use this external menu binary. The specifications of
885 the DHCP tags required is in the vendortags document in the Etherboot
886 documentation.
887
888 Typical usage is like this:
889
890 C<mkelf-menu> > C<menu.nb>
891
892 Then put menu.nb in the TFTP boot directory and edit your DHCP tags
893 according to the documentation.
894
895 Alternate user interface programs are highly encouraged.
896
897 =head1 MKNBI-NFL
898
899 B<mknbi-nfl> and B<mkelf-nfl> make a tagged or ELF image from the NFL
900 menu program. This menu program takes the names of images from a
901 menu-text-file file which just contains lines with the filenames
902 (relative to the tftpd root directory) of images to load. The
903 user-interface is a light-bar, similar to that used in GRUB.  There is a
904 sample menu-text-file in C<menu-nfl.eg>.
905
906 Typical usage is:
907
908 C<mknbi-nfl> C<menu-text-file> > C<nfl.nb>
909
910 Then put nfl.nb in the TFTP boot directory and specify as the boot
911 image. Chaining to other menus works.
912
913 Enhancements to the menu format accepted to specify other features such
914 as titles, timeout, colours, and so forth are highly encouraged.
915
916 =head1 MKELF-LUA
917
918 B<mkelf-lua> makes an ELF image from a precompiled Lua
919 (C<http://www.tecgraf.puc-rio.br/lua/>) program.
920
921 Typical usage is:
922
923 C<mkelf-lua> C<hello.lb> > C<luaprog.nb>
924
925 where C<hello.lb> was generated from a Lua program by:
926
927 C<luac -o hello.lb hello.lua>
928
929 The functions available to Lua programs in this environment is described
930 in a separate document.
931
932 =head1 MKNBI-FDOS
933
934 B<mknbi-fdos> makes a tagged image from a FreeDOS kernel file and a
935 floppy image.  Note that the kernel image is not read from the floppy
936 section of the tagged image, but is a separate section in the tagged
937 image. The bootloader has been adjusted to jump to it directly. This
938 means the space that would be taken up on the I<floppy> by the kernel
939 image file can now be used for applications and data.
940
941 Obtain a distribution of FreeDOS with a recent kernel, probably at least
942 2006. It has been tested with 2012 but nothing older. You can get the
943 FreeDOS kernel here:
944
945 C<http://freedos.sourceforge.net/>
946
947 Follow the instructions to make a bootable floppy. Then get an image
948 of the floppy with:
949
950 C<dd if=/dev/fd0 of=/tmp/floppyimage>
951
952 Also extract F<kernel.sys> from the floppy. You can do this from the
953 image using the mtools package, by specifying a file as a I<drive>
954 with a declaration like this in F<~/.mtoolsrc>:
955
956 C<drive x: file="/tmp/floppyimage">
957
958 Then run:
959
960 C<mcopy x:kernel.sys .>
961
962 Then run mknbi by:
963
964 C<mknbi-fdos kernel.sys /tmp/floppyimage> > C<freedos.nb>
965
966 where F<kernel.sys> and F</tmp/floppyimage> are the files extracted above.
967 Then move F<freedos.nb> to where the network booting process expects to
968 find it.
969
970 If you have got it to netboot successfully, then you can go back and
971 add your files to the floppy image. You can delete F<kernel.sys> in
972 the floppy image to save space, that is not needed. Note that you can
973 create a floppy image of any size you desire with the mformat program
974 from mtools, you are not restricted to the actual size of the boot floppy.
975
976 =head1 MKNBI-FDOS OPTIONS
977
978 B<--harddisk> Make the boot ramdisk the first hard disk, i.e. C:. One
979 reason you might want to do this is because you want to use the real
980 floppy. The limit on "disk size" in the boot image is not raised by this
981 option so that is not a reason to use this option. This option is
982 incompatible with --disableharddisk.
983
984 B<--disableharddisk> When the ramdisk is simulating a floppy disk drive,
985 this switch will disable hard disk accesses.  This is necessary if the
986 client should use a network file system as drive C:, which is only
987 possible if there are no hard disks found by DOS. This option is
988 incompatible with --harddisk.
989
990 B<--nosquash> Do not try to chop unused sectors from the end of the
991 floppy image. This increases the tagged image size and hence loading
992 time if the FAT filesystem on the floppy is mostly empty but you may
993 wish to use this option if you have doubts as to whether the squashing
994 algorithm is working correctly.
995
996 B<--rdbase=>I<0xNNNNNNNN> Set the ramdisk load address. The default
997 load address for the ramdisk is 0x110000. It can be moved higher
998 (lower will not work) if for some reason you need to load other stuff
999 at the address it currently occupies. As this is a linear address and
1000 not a segment address, the last 4 bits are not used and should be 0.
1001
1002 =head1 MKNBI-DOS
1003
1004 B<mknbi-dos> makes a tagged image from a floppy image containing a
1005 bootable DOS filesystem.  It is not necessary to build the filesystem
1006 on a physical floppy if you have the mtools package, but you need a
1007 bootable floppy of any size to start with. First extract the boot block
1008 from the floppy, this boot block must match the DOS kernel files you
1009 will copy in the next step:
1010
1011 C<dd if=/dev/fd0 of=bootblock bs=512 count=1>
1012
1013 Then get the DOS kernel files (this is correct for DR-DOS, the names
1014 are different in MS-DOS, IO.SYS and MSDOS.SYS):
1015
1016 C<mcopy a:IBMBIO.COM a:IBMDOS.COM a:COMMAND.COM .>
1017
1018 Next make an entry in F<~/.mtoolsrc> to declare a floppy to be mapped
1019 to a file:
1020
1021 C<drive x: file="/tmp/floppyimage">
1022
1023 Now format a floppy of the desired size, in this example a 2.88 MB floppy,
1024 at the same time writing the bootblock onto it:
1025
1026 C<mformat -C -t 80 -s 36 -h 2 -B bootblock x:>
1027
1028 The size of the "floppy" is only limited by the limits on the number of
1029 cylinders, sectors and heads, which are 1023, 63 and 255 respectively,
1030 and the amount of RAM you are willing to allocate to the "floppy" in
1031 memory. As RAM is precious, choose a size slightly bigger than what is
1032 needed to hold your "floppy" files.
1033
1034 Finally, copy all your desired files onto the floppy:
1035
1036 C<mcopy IBMBIO.COM x:>
1037
1038 C<mcopy IBMDOS.COM x:>
1039
1040 C<mcopy COMMAND.COM x:>
1041
1042 C<mcopy CONFIG.SYS AUTOEXEC.BAT APP.EXE APP.DAT ... x:>
1043
1044 For MS-DOS substitute IO.SYS for IBMIO.COM, and MSDOS.SYS for
1045 IBMDOS.COM.  The case of the files must be preserved, it may not work if
1046 VFAT lower case names are generated in the floppy image.  Pay attention
1047 to the order of copying as the boot block may expect the first two
1048 entries on a newly formatted disk to be IO.SYS, MSDOS.SYS.  Possibly too
1049 COMMAND.COM has to be the third entry so we play safe.  Thanks to Phil
1050 Davey and Phillip Roa for these tips.
1051
1052 I have reports that the bootblock of MS-DOS 6.22 sometimes fails to boot
1053 the ramdisk.  You could try using the boot block from Netboot instead of
1054 getting the boot block off the floppy. I have provided this boot block
1055 in the distribution as altboot.bin, and in source form as altboot.S and
1056 boot.inc. One essential thing is to make IO.SYS the first file on the
1057 disk, or this bootblock will not work.
1058
1059 If you happen to have a media of the same size you could test if the
1060 image is bootable by copying it onto the media, and then booting it:
1061
1062 C<dd if=/tmp/floppyimage of=/dev/fd0>
1063
1064 Then run mknbi-dos over the image F</tmp/floppyimage> to create a
1065 tagged image:
1066
1067 C<mknbi-dos /tmp/floppyimage> > C<dos.nb>
1068
1069 Move F<dos.nb> to where the network booting process expects to find it.
1070
1071 =head1 MKNBI-DOS OPTIONS
1072
1073 B<--harddisk> Make the boot ramdisk the first hard disk, i.e. C:. One
1074 reason you might want to do this is because you want to use the real
1075 floppy. The limit on "disk size" in the boot image is not raised by this
1076 option so that is not a reason to use this option. This option is
1077 incompatible with --disableharddisk.
1078
1079 B<--disableharddisk> When the ramdisk is simulating a floppy disk drive,
1080 this switch will disable hard disk accesses.  This is necessary if the
1081 client should use a network file system as drive C:, which is only
1082 possible if there are no hard disks found by DOS. This option is
1083 incompatible with --harddisk.
1084
1085 B<--nosquash> Do not try to chop unused sectors from the end of the
1086 floppy image. This increases the tagged image size and hence loading
1087 time if the FAT filesystem on the floppy is mostly empty but you may
1088 wish to use this option if you have doubts as to whether the squashing
1089 algorithm is working correctly.
1090
1091 B<--rdbase=>I<0xNNNNNNNN> Set the ramdisk load address. The default
1092 load address for the ramdisk is 0x110000. It can be moved higher
1093 (lower will not work) if for some reason you need to load other stuff
1094 at the address it currently occupies. As this is a linear address and
1095 not a segment address, the last 4 bits are not used and should be 0.
1096
1097 =head1 BUGS
1098
1099 Please report all bugs to the author.
1100
1101 =head1 SEE ALSO
1102
1103 Etherboot tutorial at C<http://etherboot.sourceforge.net/> Mtools package
1104 is at C<http://wauug.erols.com/pub/knaff/mtools/> Make sure you have a
1105 recent version, the ability to map a drive to a file is not present in
1106 old versions.
1107
1108 =head1 COPYRIGHT
1109
1110 B<mknbi> is under the GNU Public License
1111
1112 =head1 AUTHOR
1113
1114 Ken Yap (C<ken_yap@users.sourceforge.net>)
1115
1116 mk{nbi,elf}-nfl was contributed by Robb Main of Genedyne.
1117
1118 =head1 DATE
1119
1120 See man page footer for date and version. Sorry, not available in the
1121 HTML version.