2 # Tcl magic -*- tcl -*- \
4 ################################################################################
6 # KernelDriver - FreeBSD driver source installer
8 ################################################################################
11 # Michael Smith. All rights reserved.
13 # Redistribution and use in source and binary forms, with or without
14 # modification, are permitted provided that the following conditions
16 # 1. Redistributions of source code must retain the above copyright
17 # notice, this list of conditions and the following disclaimer.
18 # 2. Redistributions in binary form must reproduce the above copyright
19 # notice, this list of conditions and the following disclaimer in the
20 # documentation and/or other materials provided with the distribution.
21 # 3. Neither the name of the author nor the names of any co-contributors
22 # may be used to endorse or promote products derived from this software
23 # without specific prior written permission.
25 # THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND
26 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 # ARE DISCLAIMED. IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
29 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 ################################################################################
39 # KernelDriver provides a means for installing source-form drivers into FreeBSD
40 # kernel source trees in an automated fashion. It can also remove drivers it
43 # Driver information is read from a control file, with the following syntax :
45 # description {<text>} Driver description; used in comments inserted into
47 # driver <name> The name of the driver. (Note that this can't end in .drvinfo :)
48 # filei386 <path> <name> The file <name> in the driver package is installed into
49 # <path> in the kernel source tree. Files whose names
50 # end in '.c' have an entry added to i386/conf/files.i386.
51 # fileconf <path> <name> The file <name> in the driver package is installed into
52 # <path> in the kernel source tree. Files whose names
53 # end in '.c' have an entry added to conf/files.
54 # optioni386 <name> <hdr> Adds an entry to i386/conf/options.i386, such that
55 # the option <name> will be placed in the header <hdr>.
56 # optionconf <name> <hdr> Adds an entry to conf/options, such that
57 # the option <name> will be placed in the header <hdr>.
58 # linttext Lines between this and a subsequent 'end' line are added
59 # to the LINT file to provide configuration examples,
61 # end Ends a text region.
63 # Possible additions :
65 # patch <name> Applies the patch contained in <name>; patch is invoked
66 # at the top level of the kernel source tree, and the
67 # patch must apply cleanly (this is checked).
69 # option <name> <file> Adds an entry to i386/conf/options.i386
71 # Lines beginning with '#' or blanks are considered comments, except in
74 ################################################################################
77 ################################################################################
79 ################################################################################
82 # Given (hint), use it to locate a driver information file.
83 # (Possible extension; support drivers in gzipped tarballs...)
85 proc findDrvFile_try {hint} {
87 # points to something already
88 if {[file exists $hint]} {
89 # unwind symbolic links
90 while {[file type $hint] == "link"} {
91 set hint [file readlink $hint];
93 switch [file type $hint] {
95 # run with it as it is
99 # look for a drvinfo file in the directory
100 set candidate [glob -nocomplain "$hint/*.drvinfo"];
101 switch [llength $candidate] {
109 error "multiple driver info files in directory : $hint";
114 error "driver info file may be a typewriter : $hint";
118 # maybe we need an extension
119 if {[file exists $hint.drvinfo]} {
120 return $hint.drvinfo;
122 error "can't find a driver info file using '$hint'";
125 proc findDrvFile {hint} {
127 set result [findDrvFile_try $hint];
131 set result [findDrvFile_try ${hint}.drvinfo];
135 error "can't find driver information file using : $hint";
138 ################################################################################
141 # Reads the contents of (fname), which are expected to be in the format
142 # described above, and fill in the global Drv array.
144 proc readDrvFile {fname} {
148 if {$Options(verbose)} {puts "+ read options from '$fname'";}
149 set fh [open $fname r];
152 set Drv(description) "";
154 set Drv(filesi386) "";
155 set Drv(filesconf) "";
156 set Drv(optionsi386) "";
157 set Drv(optionsconf) "";
159 set Drv(linttext) "";
161 while {[gets $fh line] >= 0} {
163 # blank lines/comments
164 if {([llength $line] == 0) ||
165 ([string index $line 0] == "\#")} {
169 # get keyword, process
170 switch -- [lindex $line 0] {
172 set Drv(description) [lindex $line 1];
175 set Drv(driver) [lindex $line 1];
178 set path [lindex $line 1];
179 set plast [expr [string length $path] -1];
180 if {[string index $path $plast] != "/"} {
183 set name [lindex $line 2];
184 set Drv(filei386:$name) $path;
185 lappend Drv(filesi386) $name;
188 set path [lindex $line 1];
189 set plast [expr [string length $path] -1];
190 if {[string index $path $plast] != "/"} {
193 set name [lindex $line 2];
194 set Drv(fileconf:$name) $path;
195 lappend Drv(filesconf) $name;
198 set opt [lindex $line 1];
199 set hdr [lindex $line 2];
200 lappend Drv(optionsi386) $opt;
201 set Drv(optioni386:$opt) $hdr;
204 set opt [lindex $line 1];
205 set hdr [lindex $line 2];
206 lappend Drv(optionsconf) $opt;
207 set Drv(optionconf:$opt) $hdr;
210 lappend Drv(patches) [lindex $line 1];
213 while {[gets $fh line] >= 0} {
214 if {$line == "end"} {
217 lappend Drv(linttext) $line;
223 if {$Options(verbose)} {
228 ################################################################################
231 # With the global Drv filled in, check that the files required are all in
232 # (dir), and that the kernel config at (kpath) can be written.
234 proc validateDrvPackage {dir kpath} {
238 if {$Options(verbose)} {puts "+ checking driver package...";}
242 # check files, patches
243 foreach f $Drv(filesi386) {
244 if {![file readable $dir$f]} {
248 foreach f $Drv(filesconf) {
249 if {![file readable $dir$f]} {
253 foreach f $Drv(patches) {
254 if {![file readable $dir$f]} {
258 if {$missing != ""} {
259 error "missing files : $missing";
263 if {$Options(verbose)} {puts "+ checking kernel source writability...";}
264 foreach f $Drv(filesi386) {
265 set p $Drv(filei386:$f);
266 if {![file isdirectory $kpath$p]} {
269 if {![file writable $kpath$p]} {
270 if {[lsearch -exact $unwritable $p] == -1} {
271 lappend unwritable $p;
276 foreach f $Drv(filesconf) {
277 set p $Drv(fileconf:$f);
278 if {![file isdirectory $kpath$p]} {
281 if {![file writable $kpath$p]} {
282 if {[lsearch -exact $unwritable $p] == -1} {
283 lappend unwritable $p;
290 "i386/conf/files.i386" \
291 "i386/conf/options.i386" \
293 if {![file writable $kpath$f]} {
294 lappend unwritable $f;
297 if {$missing != ""} {
298 error "missing directories : $missing";
300 if {$unwritable != ""} {
301 error "can't write to : $unwritable";
305 ################################################################################
308 # Install the files listed in the global Drv into (kpath) from (dir)
310 proc installDrvFiles {dir kpath} {
314 # clear 'installed' record
315 set Drv(installedi386) "";
316 set Drv(installedconf) "";
319 if {$Options(verbose)} {puts "+ installing driver files...";}
320 foreach f $Drv(filesi386) {
321 if {$Options(verbose)} {puts "$f -> $kpath$Drv(filei386:$f)";}
322 if {$Options(real)} {
323 if {[catch {exec cp $dir$f $kpath$Drv(filei386:$f)} msg]} {
326 lappend Drv(installedi386) $f;
330 foreach f $Drv(filesconf) {
331 if {$Options(verbose)} {puts "$f -> $kpath$Drv(fileconf:$f)";}
332 if {$Options(real)} {
333 if {[catch {exec cp $dir$f $kpath$Drv(fileconf:$f)} msg]} {
336 lappend Drv(installedconf) $f;
341 error "failed to install files : $failed";
345 ################################################################################
348 # Remove files from a failed installation in (kpath)
350 proc backoutDrvChanges {kpath} {
354 if {$Options(verbose)} {puts "+ backing out installed files...";}
355 # delete installed files
356 foreach f $Drv(installedi386) {
357 exec rm -f $kpath$Drv(filei386:$f)$f;
359 foreach f $Drv(installedconf) {
360 exec rm -f $kpath$Drv(fileconf:$f)$f;
364 ################################################################################
367 # Adds an entry to i386/conf/files.i386 and conf/files for the .c files in the driver.
368 # (kpath) points to the kernel.
370 # A comment is added to the file preceding the new entries :
372 # ## driver: <drivername>
374 # # filei386: <path><file>
375 # <file spec (.c files only)>
378 # We only append to the end of the file.
380 # Add linttext to the LINT file.
381 # Add options to i386/conf/options.i386 if any are specified
383 proc registerDrvFiles {kpath} {
387 if {$Options(verbose)} {puts "+ registering installed files...";}
390 if {$Drv(linttext) != ""} {
392 if {$Options(verbose)} {puts "+ updating LINT...";}
393 if {$Options(real)} {
394 set fname [format "%si386/conf/LINT" $kpath];
395 set fh [open $fname a];
398 puts $fh "\#\# driver: $Drv(driver)";
399 puts $fh "\# $Drv(description)";
400 foreach l $Drv(linttext) {
403 puts $fh "\#\# enddriver";
409 if {$Options(real)} {
410 set fname [format "%si386/conf/files.i386" $kpath];
411 set fh [open $fname a];
414 puts $fh "\#\# driver: $Drv(driver)";
415 puts $fh "\# $Drv(description)";
417 foreach f $Drv(filesi386) {
418 puts $fh "\# file: $Drv(filei386:$f)$f";
419 # is it a compilable object?
420 if {[string match "*.c" $f]} {
421 puts $fh "$Drv(filei386:$f)$f\t\toptional\t$Drv(driver)\tdevice-driver";
424 puts $fh "\#\# enddriver";
427 if {$Drv(optionsi386) != ""} {
428 if {$Options(verbose)} {puts "+ adding options...";}
429 if {$Options(real)} {
430 set fname [format "%si386/conf/options.i386" $kpath];
431 set fh [open $fname a];
434 puts $fh "\#\# driver: $Drv(driver)";
435 puts $fh "\# $Drv(description)";
437 foreach opt $Drv(optionsi386) {
438 puts $fh "$opt\t$Drv(optioni386:$opt)";
440 puts $fh "\#\# enddriver";
446 if {$Options(real)} {
447 set fname [format "%sconf/files" $kpath];
448 set fh [open $fname a];
451 puts $fh "\#\# driver: $Drv(driver)";
452 puts $fh "\# $Drv(description)";
454 foreach f $Drv(filesconf) {
455 puts $fh "\# file: $Drv(fileconf:$f)$f";
456 # is it a compilable object?
457 if {[string match "*.c" $f]} {
458 puts $fh "$Drv(fileconf:$f)$f\t\toptional\t$Drv(driver)\tdevice-driver";
461 puts $fh "\#\# enddriver";
464 if {$Drv(optionsconf) != ""} {
465 if {$Options(verbose)} {puts "+ adding options...";}
466 if {$Options(real)} {
467 set fname [format "%sconf/options" $kpath];
468 set fh [open $fname a];
471 puts $fh "\#\# driver: $Drv(driver)";
472 puts $fh "\# $Drv(description)";
474 foreach opt $Drv(optionsconf) {
475 puts $fh "$opt\t$Drv(optionconf:$opt)";
477 puts $fh "\#\# enddriver";
484 ################################################################################
487 # List all drivers recorded as installed, in the kernel at (kpath)
489 # XXX : fix me so I understand conf/{options,files} stuff!
490 proc listInstalledDrv {kpath} {
494 # pick up all the i386 options information first
495 set fname [format "%si386/conf/options.i386" $kpath];
496 if {![file readable $fname]} {
497 error "not a kernel directory";
499 set fh [open $fname r];
501 while {[gets $fh line] >= 0} {
504 if {[scan $line "\#\# driver: %s" driver] == 1} {
505 # read driver details, ignore
507 # loop reading option details
508 while {[gets $fh line] >= 0} {
510 if {$line == "\#\# enddriver"} {
513 # parse option/header tuple
514 if {[scan $line "%s %s" opt hdr] == 2} {
515 # remember that this driver uses this option
516 lappend drivers($driver:optionsi386) $opt;
517 # remember that this option goes in this header
518 set optionsi386($opt) $hdr;
525 # pick up all the conf options information first
526 set fname [format "%sconf/options" $kpath];
527 if {![file readable $fname]} {
528 error "not a kernel directory";
530 set fh [open $fname r];
532 while {[gets $fh line] >= 0} {
535 if {[scan $line "\#\# driver: %s" driver] == 1} {
536 # read driver details, ignore
538 # loop reading option details
539 while {[gets $fh line] >= 0} {
541 if {$line == "\#\# enddriver"} {
544 # parse option/header tuple
545 if {[scan $line "%s %s" opt hdr] == 2} {
546 # remember that this driver uses this option
547 lappend drivers($driver:optionsconf) $opt;
548 # remember that this option goes in this header
549 set optionsconf($opt) $hdr;
556 set fname [format "%si386/conf/files.i386" $kpath];
557 set fh [open $fname r];
559 while {[gets $fh line] >= 0} {
562 if {[scan $line "\#\# driver: %s" driver] == 1} {
563 # clear global and reset
565 set Drv(driver) $driver;
566 # read driver details
568 set Drv(description) [string range $line 2 end];
569 set Drv(filesi386) "";
571 if {[info exists drivers($Drv(driver):optionsi386)]} {
572 set Drv(optionsi386) $drivers($Drv(driver):optionsi386);
574 foreach opt $Drv(optionsi386) {
575 set Drv(optioni386:$opt) $optionsi386($opt);
578 # loop reading file details
579 while {[gets $fh line] >= 0} {
580 if {$line == "\#\# enddriver"} {
581 # print this driver and loop
585 if {[scan $line "\# filei386: %s" fpath] == 1} {
586 set f [file tail $fpath];
587 set Drv(filei386:$f) "[file dirname $fpath]/";
588 lappend Drv(filesi386) $f;
595 set fname [format "%sconf/files" $kpath];
596 set fh [open $fname r];
598 while {[gets $fh line] >= 0} {
601 if {[scan $line "\#\# driver: %s" driver] == 1} {
602 # clear global and reset
604 set Drv(driver) $driver;
605 # read driver details
607 set Drv(description) [string range $line 2 end];
608 set Drv(filesconf) "";
610 if {[info exists drivers($Drv(driver):optionsconf)]} {
611 set Drv(optionsconf) $drivers($Drv(driver):optionsconf);
613 foreach opt $Drv(optionsconf) {
614 set Drv(optionconf:$opt) $optionsconf($opt);
617 # loop reading file details
618 while {[gets $fh line] >= 0} {
619 if {$line == "\#\# enddriver"} {
620 # print this driver and loop
624 if {[scan $line "\# fileconf: %s" fpath] == 1} {
625 set f [file tail $fpath];
626 set Drv(fileconf:$f) "[file dirname $fpath]/";
627 lappend Drv(filesconf) $f;
635 ################################################################################
638 # Print the contents of the global Drv.
644 puts "$Drv(driver) : $Drv(description)";
645 if {$Options(verbose)} {
646 foreach f $Drv(filesi386) {
647 puts " $Drv(filei386:$f)$f"
649 foreach f $Drv(filesconf) {
650 puts " $Drv(fileconf:$f)$f"
652 if {[info exists Drv(optionsi386)]} {
653 foreach opt $Drv(optionsi386) {
654 puts " $opt in $Drv(optioni386:$opt)";
657 if {[info exists Drv(optionsconf)]} {
658 foreach opt $Drv(optionsconf) {
659 puts " $opt in $Drv(optionconf:$opt)";
665 ################################################################################
668 # Given a kernel tree at (kpath), get driver details about an installed
672 proc findInstalledDrvi386 {drvname kpath} {
676 set fname [format "%si386/conf/files.i386" $kpath];
677 set fh [open $fname r];
679 puts "checking i386/conf/files.i386";
681 while {[gets $fh line] >= 0} {
682 if {[scan $line "\#\# driver: %s" name] == 1} {
683 if {$name != $drvname} {
687 set Drv(driver) $drvname;
689 set Drv(description) [string range $line 2 end];
690 set Drv(filesi386) "";
691 # loop reading file details
692 while {[gets $fh line] >= 0} {
693 if {$line == "\#\# enddriver"} {
697 if {[scan $line "\# file: %s" fpath] == 1} {
698 set f [file tail $fpath];
699 set Drv(filei386:$f) "[file dirname $fpath]/";
700 lappend Drv(filesi386) $f;
704 error "unexpected EOF reading '$fname'";
712 proc findInstalledDrvconf {drvname kpath} {
716 set fname [format "%sconf/files" $kpath];
717 set fh [open $fname r];
719 puts "checking conf/files";
721 while {[gets $fh line] >= 0} {
722 if {[scan $line "\#\# driver: %s" name] == 1} {
723 if {$name != $drvname} {
727 set Drv(driver) $drvname;
729 set Drv(description) [string range $line 2 end];
730 set Drv(filesconf) "";
731 # loop reading file details
732 while {[gets $fh line] >= 0} {
733 if {$line == "\#\# enddriver"} {
737 if {[scan $line "\# file: %s" fpath] == 1} {
738 set f [file tail $fpath];
739 set Drv(fileconf:$f) "[file dirname $fpath]/";
740 lappend Drv(filesconf) $f;
744 error "unexpected EOF reading '$fname'";
752 proc findInstalledDrv {drvname kpath} {
756 if {$Options(verbose)} {puts "+ look for driver '$drvname' in '$kpath'";}
758 # Whoops... won't work in a single if statement due to expression shortcircuiting
759 set a [findInstalledDrvi386 $drvname $kpath];
760 set b [findInstalledDrvconf $drvname $kpath];
765 error "driver '$drvname' not recorded as installed";
768 ################################################################################
771 # Verify that we can remove the driver described in the global Drv installed
774 proc validateDrvRemoval {kpath} {
781 if {$Options(verbose)} {puts "+ checking for removabilty...";}
785 "i386/conf/files.i386" \
786 "i386/conf/options.i386" \
790 if {![file exists $kpath$f]} {
791 lappend missing $kpath$f;
793 if {![file writable $kpath$f]} {
794 lappend unwritable $f;
799 foreach f $Drv(filesi386) {
800 set p $Drv(filei386:$f);
801 if {![file isdirectory $kpath$p]} {
804 if {![file writable $kpath$p]} {
805 if {[lsearch -exact $unwritable $p] == -1} {
806 lappend unwritable $p;
811 foreach f $Drv(filesconf) {
812 set p $Drv(fileconf:$f);
813 if {![file isdirectory $kpath$p]} {
816 if {![file writable $kpath$p]} {
817 if {[lsearch -exact $unwritable $p] == -1} {
818 lappend unwritable $p;
823 if {$missing != ""} {
824 error "files/directories missing : $missing";
826 if {$unwritable != ""} {
827 error "can't write to : $unwritable";
831 ################################################################################
834 # Delete the files belonging to the driver devfined in the global Drv in
835 # the kernel tree at (kpath)
837 proc deleteDrvFiles {kpath} {
841 if {$Options(verbose)} {puts "+ delete driver files...";}
843 # loop deleting files
844 foreach f $Drv(filesi386) {
845 if {$Options(verbose)} {puts "- $Drv(filei386:$f)$f";}
846 if {$Options(real)} {
847 exec rm $kpath$Drv(filei386:$f)$f;
850 foreach f $Drv(filesconf) {
851 if {$Options(verbose)} {puts "- $Drv(fileconf:$f)$f";}
852 if {$Options(real)} {
853 exec rm $kpath$Drv(fileconf:$f)$f;
858 ################################################################################
861 # Remove any mention of the current driver from the files.i386 and LINT
864 proc unregisterDrvFiles {ksrc} {
868 if {$Options(verbose)} {puts "+ deregister driver files...";}
870 # don't really do it?
871 if {!$Options(real)} { return ; }
874 "i386/conf/files.i386" \
875 "i386/conf/options.i386" \
879 set ifh [open $ksrc$f r];
880 set ofh [open $ksrc$f.new w];
883 while {[gets $ifh line] >= 0} {
885 if {[scan $line "\#\# driver: %s" name] == 1} {
886 if {$name == $Drv(driver)} {
887 set copying 0; # don't copy this one
891 puts $ofh $line; # copy through
893 if {$line == "\#\# enddriver"} { # end of driver detail
899 exec mv $ksrc$f.new $ksrc$f; # move new over old
903 ################################################################################
906 # Remind the user what goes where
912 set progname [file tail $argv0];
914 puts stderr "Usage is :";
915 puts stderr " $progname \[-v -n\] add <drvinfo> \[<kpath>\]";
916 puts stderr " $progname \[-v -n\] delete <drvname> \[<kpath>\]";
917 puts stderr " $progname \[-v\] list \[<kpath>\]";
918 puts stderr " <drvinfo> is a driver info file";
919 puts stderr " <drvname> is a driver name";
920 puts stderr " <kpath> is the path to the kernel source (default /sys/)";
921 puts stderr " -v be verbose";
922 puts stderr " -n don't actually do anything";
926 ################################################################################
929 # Parse commandline options, return anything that doesn't look like an option
936 set Options(verbose) 0;
939 for {set index 0} {$index < [llength $argv]} {incr index} {
941 switch -- [lindex $argv $index] {
944 set Options(real) 0; # 'do-nothing' mode
947 set Options(verbose) 1; # brag
950 lappend ret [lindex $argv $index];
957 ################################################################################
960 # Given (hint), return the kernel path. If (hint) is empty, return /sys.
961 # If the kernel path is not a directory, complain and dump the usage.
963 proc getKpath {hint} {
967 # check the kernel path
973 if {![file isdirectory $kpath]} {
974 puts "not a directory : $kpath";
977 set plast [expr [string length $kpath] -1];
978 if {[string index $kpath $plast] != "/"} {
984 ################################################################################
987 # Start somewhere here.
993 # Work out what we're trying to do
994 set cmdline [getOptions];
995 set mode [lindex $cmdline 0];
1000 set hint [lindex $cmdline 1];
1001 set kpath [getKpath [lindex $cmdline 2]];
1003 # check driver file argument
1004 if {[catch {set drv [findDrvFile $hint]} msg]} {
1008 if {([file type $drv] != "file") ||
1009 ![file readable $drv]} {
1010 puts "can't read driver file : $drv";
1013 set drvdir "[file dirname $drv]/";
1016 if {[catch {readDrvFile $drv} msg]} {
1021 if {[catch {validateDrvPackage $drvdir $kpath} msg]} {
1026 if {[catch {installDrvFiles $drvdir $kpath} msg]} {
1027 backoutDrvChanges $kpath; # oops, unwind
1031 # register files in config
1032 if {[catch {registerDrvFiles $kpath} msg]} {
1033 backoutDrvChanges $kpath; # oops, unwind
1039 set drv [lindex $cmdline 1];
1040 set kpath [getKpath [lindex $cmdline 2]];
1042 if {[string last ".drvinfo" $drv] != -1} {
1043 set drv [string range $drv 0 [expr [string length $drv] - 9]];
1044 puts "Driver name ends in .drvinfo, removing, is now $drv";
1047 if {[catch {findInstalledDrv $drv $kpath} msg]} {
1051 if {[catch {validateDrvRemoval $kpath} msg]} {
1055 if {[catch {unregisterDrvFiles $kpath} msg]} {
1059 if {[catch {deleteDrvFiles $kpath} msg]} {
1065 set kpath [getKpath [lindex $cmdline 1]];
1066 if {[catch {listInstalledDrv $kpath} msg]} {
1067 puts stderr "can't list drivers in '$kpath' : $msg";
1071 puts stderr "unknown command '$mode'";
1079 ################################################################################