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 ################################################################################
78 ################################################################################
80 ################################################################################
83 # Given (hint), use it to locate a driver information file.
84 # (Possible extension; support drivers in gzipped tarballs...)
86 proc findDrvFile_try {hint} {
88 # points to something already
89 if {[file exists $hint]} {
90 # unwind symbolic links
91 while {[file type $hint] == "link"} {
92 set hint [file readlink $hint];
94 switch [file type $hint] {
96 # run with it as it is
100 # look for a drvinfo file in the directory
101 set candidate [glob -nocomplain "$hint/*.drvinfo"];
102 switch [llength $candidate] {
110 error "multiple driver info files in directory : $hint";
115 error "driver info file may be a typewriter : $hint";
119 # maybe we need an extension
120 if {[file exists $hint.drvinfo]} {
121 return $hint.drvinfo;
123 error "can't find a driver info file using '$hint'";
126 proc findDrvFile {hint} {
128 set result [findDrvFile_try $hint];
132 set result [findDrvFile_try ${hint}.drvinfo];
136 error "can't find driver information file using : $hint";
139 ################################################################################
142 # Reads the contents of (fname), which are expected to be in the format
143 # described above, and fill in the global Drv array.
145 proc readDrvFile {fname} {
149 if {$Options(verbose)} {puts "+ read options from '$fname'";}
150 set fh [open $fname r];
153 set Drv(description) "";
155 set Drv(filesi386) "";
156 set Drv(filesconf) "";
157 set Drv(optionsi386) "";
158 set Drv(optionsconf) "";
160 set Drv(linttext) "";
162 while {[gets $fh line] >= 0} {
164 # blank lines/comments
165 if {([llength $line] == 0) ||
166 ([string index $line 0] == "\#")} {
170 # get keyword, process
171 switch -- [lindex $line 0] {
173 set Drv(description) [lindex $line 1];
176 set Drv(driver) [lindex $line 1];
179 set path [lindex $line 1];
180 set plast [expr [string length $path] -1];
181 if {[string index $path $plast] != "/"} {
184 set name [lindex $line 2];
185 set Drv(filei386:$name) $path;
186 lappend Drv(filesi386) $name;
189 set path [lindex $line 1];
190 set plast [expr [string length $path] -1];
191 if {[string index $path $plast] != "/"} {
194 set name [lindex $line 2];
195 set Drv(fileconf:$name) $path;
196 lappend Drv(filesconf) $name;
199 set opt [lindex $line 1];
200 set hdr [lindex $line 2];
201 lappend Drv(optionsi386) $opt;
202 set Drv(optioni386:$opt) $hdr;
205 set opt [lindex $line 1];
206 set hdr [lindex $line 2];
207 lappend Drv(optionsconf) $opt;
208 set Drv(optionconf:$opt) $hdr;
211 lappend Drv(patches) [lindex $line 1];
214 while {[gets $fh line] >= 0} {
215 if {$line == "end"} {
218 lappend Drv(linttext) $line;
224 if {$Options(verbose)} {
229 ################################################################################
232 # With the global Drv filled in, check that the files required are all in
233 # (dir), and that the kernel config at (kpath) can be written.
235 proc validateDrvPackage {dir kpath} {
239 if {$Options(verbose)} {puts "+ checking driver package...";}
243 # check files, patches
244 foreach f $Drv(filesi386) {
245 if {![file readable $dir$f]} {
249 foreach f $Drv(filesconf) {
250 if {![file readable $dir$f]} {
254 foreach f $Drv(patches) {
255 if {![file readable $dir$f]} {
259 if {$missing != ""} {
260 error "missing files : $missing";
264 if {$Options(verbose)} {puts "+ checking kernel source writability...";}
265 foreach f $Drv(filesi386) {
266 set p $Drv(filei386:$f);
267 if {![file isdirectory $kpath$p]} {
270 if {![file writable $kpath$p]} {
271 if {[lsearch -exact $unwritable $p] == -1} {
272 lappend unwritable $p;
277 foreach f $Drv(filesconf) {
278 set p $Drv(fileconf:$f);
279 if {![file isdirectory $kpath$p]} {
282 if {![file writable $kpath$p]} {
283 if {[lsearch -exact $unwritable $p] == -1} {
284 lappend unwritable $p;
291 "i386/conf/files.i386" \
292 "i386/conf/options.i386" \
294 if {![file writable $kpath$f]} {
295 lappend unwritable $f;
298 if {$missing != ""} {
299 error "missing directories : $missing";
301 if {$unwritable != ""} {
302 error "can't write to : $unwritable";
306 ################################################################################
309 # Install the files listed in the global Drv into (kpath) from (dir)
311 proc installDrvFiles {dir kpath} {
315 # clear 'installed' record
316 set Drv(installedi386) "";
317 set Drv(installedconf) "";
320 if {$Options(verbose)} {puts "+ installing driver files...";}
321 foreach f $Drv(filesi386) {
322 if {$Options(verbose)} {puts "$f -> $kpath$Drv(filei386:$f)";}
323 if {$Options(real)} {
324 if {[catch {exec cp $dir$f $kpath$Drv(filei386:$f)} msg]} {
327 lappend Drv(installedi386) $f;
331 foreach f $Drv(filesconf) {
332 if {$Options(verbose)} {puts "$f -> $kpath$Drv(fileconf:$f)";}
333 if {$Options(real)} {
334 if {[catch {exec cp $dir$f $kpath$Drv(fileconf:$f)} msg]} {
337 lappend Drv(installedconf) $f;
342 error "failed to install files : $failed";
346 ################################################################################
349 # Remove files from a failed installation in (kpath)
351 proc backoutDrvChanges {kpath} {
355 if {$Options(verbose)} {puts "+ backing out installed files...";}
356 # delete installed files
357 foreach f $Drv(installedi386) {
358 exec rm -f $kpath$Drv(filei386:$f)$f;
360 foreach f $Drv(installedconf) {
361 exec rm -f $kpath$Drv(fileconf:$f)$f;
365 ################################################################################
368 # Adds an entry to i386/conf/files.i386 and conf/files for the .c files in the driver.
369 # (kpath) points to the kernel.
371 # A comment is added to the file preceding the new entries :
373 # ## driver: <drivername>
375 # # filei386: <path><file>
376 # <file spec (.c files only)>
379 # We only append to the end of the file.
381 # Add linttext to the LINT file.
382 # Add options to i386/conf/options.i386 if any are specified
384 proc registerDrvFiles {kpath} {
388 if {$Options(verbose)} {puts "+ registering installed files...";}
391 if {$Drv(linttext) != ""} {
393 if {$Options(verbose)} {puts "+ updating LINT...";}
394 if {$Options(real)} {
395 set fname [format "%si386/conf/LINT" $kpath];
396 set fh [open $fname a];
399 puts $fh "\#\# driver: $Drv(driver)";
400 puts $fh "\# $Drv(description)";
401 foreach l $Drv(linttext) {
404 puts $fh "\#\# enddriver";
410 if {$Options(real)} {
411 set fname [format "%si386/conf/files.i386" $kpath];
412 set fh [open $fname a];
415 puts $fh "\#\# driver: $Drv(driver)";
416 puts $fh "\# $Drv(description)";
418 foreach f $Drv(filesi386) {
419 puts $fh "\# file: $Drv(filei386:$f)$f";
420 # is it a compilable object?
421 if {[string match "*.c" $f]} {
422 puts $fh "$Drv(filei386:$f)$f\t\toptional\t$Drv(driver)\tdevice-driver";
425 puts $fh "\#\# enddriver";
428 if {$Drv(optionsi386) != ""} {
429 if {$Options(verbose)} {puts "+ adding options...";}
430 if {$Options(real)} {
431 set fname [format "%si386/conf/options.i386" $kpath];
432 set fh [open $fname a];
435 puts $fh "\#\# driver: $Drv(driver)";
436 puts $fh "\# $Drv(description)";
438 foreach opt $Drv(optionsi386) {
439 puts $fh "$opt\t$Drv(optioni386:$opt)";
441 puts $fh "\#\# enddriver";
447 if {$Options(real)} {
448 set fname [format "%sconf/files" $kpath];
449 set fh [open $fname a];
452 puts $fh "\#\# driver: $Drv(driver)";
453 puts $fh "\# $Drv(description)";
455 foreach f $Drv(filesconf) {
456 puts $fh "\# file: $Drv(fileconf:$f)$f";
457 # is it a compilable object?
458 if {[string match "*.c" $f]} {
459 puts $fh "$Drv(fileconf:$f)$f\t\toptional\t$Drv(driver)\tdevice-driver";
462 puts $fh "\#\# enddriver";
465 if {$Drv(optionsconf) != ""} {
466 if {$Options(verbose)} {puts "+ adding options...";}
467 if {$Options(real)} {
468 set fname [format "%sconf/options" $kpath];
469 set fh [open $fname a];
472 puts $fh "\#\# driver: $Drv(driver)";
473 puts $fh "\# $Drv(description)";
475 foreach opt $Drv(optionsconf) {
476 puts $fh "$opt\t$Drv(optionconf:$opt)";
478 puts $fh "\#\# enddriver";
485 ################################################################################
488 # List all drivers recorded as installed, in the kernel at (kpath)
490 # XXX : fix me so I understand conf/{options,files} stuff!
491 proc listInstalledDrv {kpath} {
495 # pick up all the i386 options information first
496 set fname [format "%si386/conf/options.i386" $kpath];
497 if {![file readable $fname]} {
498 error "not a kernel directory";
500 set fh [open $fname r];
502 while {[gets $fh line] >= 0} {
505 if {[scan $line "\#\# driver: %s" driver] == 1} {
506 # read driver details, ignore
508 # loop reading option details
509 while {[gets $fh line] >= 0} {
511 if {$line == "\#\# enddriver"} {
514 # parse option/header tuple
515 if {[scan $line "%s %s" opt hdr] == 2} {
516 # remember that this driver uses this option
517 lappend drivers($driver:optionsi386) $opt;
518 # remember that this option goes in this header
519 set optionsi386($opt) $hdr;
526 # pick up all the conf options information first
527 set fname [format "%sconf/options" $kpath];
528 if {![file readable $fname]} {
529 error "not a kernel directory";
531 set fh [open $fname r];
533 while {[gets $fh line] >= 0} {
536 if {[scan $line "\#\# driver: %s" driver] == 1} {
537 # read driver details, ignore
539 # loop reading option details
540 while {[gets $fh line] >= 0} {
542 if {$line == "\#\# enddriver"} {
545 # parse option/header tuple
546 if {[scan $line "%s %s" opt hdr] == 2} {
547 # remember that this driver uses this option
548 lappend drivers($driver:optionsconf) $opt;
549 # remember that this option goes in this header
550 set optionsconf($opt) $hdr;
557 set fname [format "%si386/conf/files.i386" $kpath];
558 set fh [open $fname r];
560 while {[gets $fh line] >= 0} {
563 if {[scan $line "\#\# driver: %s" driver] == 1} {
564 # clear global and reset
566 set Drv(driver) $driver;
567 # read driver details
569 set Drv(description) [string range $line 2 end];
570 set Drv(filesi386) "";
572 if {[info exists drivers($Drv(driver):optionsi386)]} {
573 set Drv(optionsi386) $drivers($Drv(driver):optionsi386);
575 foreach opt $Drv(optionsi386) {
576 set Drv(optioni386:$opt) $optionsi386($opt);
579 # loop reading file details
580 while {[gets $fh line] >= 0} {
581 if {$line == "\#\# enddriver"} {
582 # print this driver and loop
586 if {[scan $line "\# filei386: %s" fpath] == 1} {
587 set f [file tail $fpath];
588 set Drv(filei386:$f) "[file dirname $fpath]/";
589 lappend Drv(filesi386) $f;
596 set fname [format "%sconf/files" $kpath];
597 set fh [open $fname r];
599 while {[gets $fh line] >= 0} {
602 if {[scan $line "\#\# driver: %s" driver] == 1} {
603 # clear global and reset
605 set Drv(driver) $driver;
606 # read driver details
608 set Drv(description) [string range $line 2 end];
609 set Drv(filesconf) "";
611 if {[info exists drivers($Drv(driver):optionsconf)]} {
612 set Drv(optionsconf) $drivers($Drv(driver):optionsconf);
614 foreach opt $Drv(optionsconf) {
615 set Drv(optionconf:$opt) $optionsconf($opt);
618 # loop reading file details
619 while {[gets $fh line] >= 0} {
620 if {$line == "\#\# enddriver"} {
621 # print this driver and loop
625 if {[scan $line "\# fileconf: %s" fpath] == 1} {
626 set f [file tail $fpath];
627 set Drv(fileconf:$f) "[file dirname $fpath]/";
628 lappend Drv(filesconf) $f;
636 ################################################################################
639 # Print the contents of the global Drv.
645 puts "$Drv(driver) : $Drv(description)";
646 if {$Options(verbose)} {
647 foreach f $Drv(filesi386) {
648 puts " $Drv(filei386:$f)$f"
650 foreach f $Drv(filesconf) {
651 puts " $Drv(fileconf:$f)$f"
653 if {[info exists Drv(optionsi386)]} {
654 foreach opt $Drv(optionsi386) {
655 puts " $opt in $Drv(optioni386:$opt)";
658 if {[info exists Drv(optionsconf)]} {
659 foreach opt $Drv(optionsconf) {
660 puts " $opt in $Drv(optionconf:$opt)";
666 ################################################################################
669 # Given a kernel tree at (kpath), get driver details about an installed
673 proc findInstalledDrvi386 {drvname kpath} {
677 set fname [format "%si386/conf/files.i386" $kpath];
678 set fh [open $fname r];
680 puts "checking i386/conf/files.i386";
682 while {[gets $fh line] >= 0} {
683 if {[scan $line "\#\# driver: %s" name] == 1} {
684 if {$name != $drvname} {
688 set Drv(driver) $drvname;
690 set Drv(description) [string range $line 2 end];
691 set Drv(filesi386) "";
692 # loop reading file details
693 while {[gets $fh line] >= 0} {
694 if {$line == "\#\# enddriver"} {
698 if {[scan $line "\# file: %s" fpath] == 1} {
699 set f [file tail $fpath];
700 set Drv(filei386:$f) "[file dirname $fpath]/";
701 lappend Drv(filesi386) $f;
705 error "unexpected EOF reading '$fname'";
713 proc findInstalledDrvconf {drvname kpath} {
717 set fname [format "%sconf/files" $kpath];
718 set fh [open $fname r];
720 puts "checking conf/files";
722 while {[gets $fh line] >= 0} {
723 if {[scan $line "\#\# driver: %s" name] == 1} {
724 if {$name != $drvname} {
728 set Drv(driver) $drvname;
730 set Drv(description) [string range $line 2 end];
731 set Drv(filesconf) "";
732 # loop reading file details
733 while {[gets $fh line] >= 0} {
734 if {$line == "\#\# enddriver"} {
738 if {[scan $line "\# file: %s" fpath] == 1} {
739 set f [file tail $fpath];
740 set Drv(fileconf:$f) "[file dirname $fpath]/";
741 lappend Drv(filesconf) $f;
745 error "unexpected EOF reading '$fname'";
753 proc findInstalledDrv {drvname kpath} {
757 if {$Options(verbose)} {puts "+ look for driver '$drvname' in '$kpath'";}
759 # Whoops... won't work in a single if statement due to expression shortcircuiting
760 set a [findInstalledDrvi386 $drvname $kpath];
761 set b [findInstalledDrvconf $drvname $kpath];
766 error "driver '$drvname' not recorded as installed";
769 ################################################################################
772 # Verify that we can remove the driver described in the global Drv installed
775 proc validateDrvRemoval {kpath} {
782 if {$Options(verbose)} {puts "+ checking for removabilty...";}
786 "i386/conf/files.i386" \
787 "i386/conf/options.i386" \
791 if {![file exists $kpath$f]} {
792 lappend missing $kpath$f;
794 if {![file writable $kpath$f]} {
795 lappend unwritable $f;
800 foreach f $Drv(filesi386) {
801 set p $Drv(filei386:$f);
802 if {![file isdirectory $kpath$p]} {
805 if {![file writable $kpath$p]} {
806 if {[lsearch -exact $unwritable $p] == -1} {
807 lappend unwritable $p;
812 foreach f $Drv(filesconf) {
813 set p $Drv(fileconf:$f);
814 if {![file isdirectory $kpath$p]} {
817 if {![file writable $kpath$p]} {
818 if {[lsearch -exact $unwritable $p] == -1} {
819 lappend unwritable $p;
824 if {$missing != ""} {
825 error "files/directories missing : $missing";
827 if {$unwritable != ""} {
828 error "can't write to : $unwritable";
832 ################################################################################
835 # Delete the files belonging to the driver devfined in the global Drv in
836 # the kernel tree at (kpath)
838 proc deleteDrvFiles {kpath} {
842 if {$Options(verbose)} {puts "+ delete driver files...";}
844 # loop deleting files
845 foreach f $Drv(filesi386) {
846 if {$Options(verbose)} {puts "- $Drv(filei386:$f)$f";}
847 if {$Options(real)} {
848 exec rm $kpath$Drv(filei386:$f)$f;
851 foreach f $Drv(filesconf) {
852 if {$Options(verbose)} {puts "- $Drv(fileconf:$f)$f";}
853 if {$Options(real)} {
854 exec rm $kpath$Drv(fileconf:$f)$f;
859 ################################################################################
862 # Remove any mention of the current driver from the files.i386 and LINT
865 proc unregisterDrvFiles {ksrc} {
869 if {$Options(verbose)} {puts "+ deregister driver files...";}
871 # don't really do it?
872 if {!$Options(real)} { return ; }
875 "i386/conf/files.i386" \
876 "i386/conf/options.i386" \
880 set ifh [open $ksrc$f r];
881 set ofh [open $ksrc$f.new w];
884 while {[gets $ifh line] >= 0} {
886 if {[scan $line "\#\# driver: %s" name] == 1} {
887 if {$name == $Drv(driver)} {
888 set copying 0; # don't copy this one
892 puts $ofh $line; # copy through
894 if {$line == "\#\# enddriver"} { # end of driver detail
900 exec mv $ksrc$f.new $ksrc$f; # move new over old
904 ################################################################################
907 # Remind the user what goes where
913 set progname [file tail $argv0];
915 puts stderr "Usage is :";
916 puts stderr " $progname \[-v -n\] add <drvinfo> \[<kpath>\]";
917 puts stderr " $progname \[-v -n\] delete <drvname> \[<kpath>\]";
918 puts stderr " $progname \[-v\] list \[<kpath>\]";
919 puts stderr " <drvinfo> is a driver info file";
920 puts stderr " <drvname> is a driver name";
921 puts stderr " <kpath> is the path to the kernel source (default /sys/)";
922 puts stderr " -v be verbose";
923 puts stderr " -n don't actually do anything";
927 ################################################################################
930 # Parse commandline options, return anything that doesn't look like an option
937 set Options(verbose) 0;
940 for {set index 0} {$index < [llength $argv]} {incr index} {
942 switch -- [lindex $argv $index] {
945 set Options(real) 0; # 'do-nothing' mode
948 set Options(verbose) 1; # brag
951 lappend ret [lindex $argv $index];
958 ################################################################################
961 # Given (hint), return the kernel path. If (hint) is empty, return /sys.
962 # If the kernel path is not a directory, complain and dump the usage.
964 proc getKpath {hint} {
968 # check the kernel path
974 if {![file isdirectory $kpath]} {
975 puts "not a directory : $kpath";
978 set plast [expr [string length $kpath] -1];
979 if {[string index $kpath $plast] != "/"} {
985 ################################################################################
988 # Start somewhere here.
994 # Work out what we're trying to do
995 set cmdline [getOptions];
996 set mode [lindex $cmdline 0];
1001 set hint [lindex $cmdline 1];
1002 set kpath [getKpath [lindex $cmdline 2]];
1004 # check driver file argument
1005 if {[catch {set drv [findDrvFile $hint]} msg]} {
1009 if {([file type $drv] != "file") ||
1010 ![file readable $drv]} {
1011 puts "can't read driver file : $drv";
1014 set drvdir "[file dirname $drv]/";
1017 if {[catch {readDrvFile $drv} msg]} {
1022 if {[catch {validateDrvPackage $drvdir $kpath} msg]} {
1027 if {[catch {installDrvFiles $drvdir $kpath} msg]} {
1028 backoutDrvChanges $kpath; # oops, unwind
1032 # register files in config
1033 if {[catch {registerDrvFiles $kpath} msg]} {
1034 backoutDrvChanges $kpath; # oops, unwind
1040 set drv [lindex $cmdline 1];
1041 set kpath [getKpath [lindex $cmdline 2]];
1043 if {[string last ".drvinfo" $drv] != -1} {
1044 set drv [string range $drv 0 [expr [string length $drv] - 9]];
1045 puts "Driver name ends in .drvinfo, removing, is now $drv";
1048 if {[catch {findInstalledDrv $drv $kpath} msg]} {
1052 if {[catch {validateDrvRemoval $kpath} msg]} {
1056 if {[catch {unregisterDrvFiles $kpath} msg]} {
1060 if {[catch {deleteDrvFiles $kpath} msg]} {
1066 set kpath [getKpath [lindex $cmdline 1]];
1067 if {[catch {listInstalledDrv $kpath} msg]} {
1068 puts stderr "can't list drivers in '$kpath' : $msg";
1072 puts stderr "unknown command '$mode'";
1080 ################################################################################