diff options
Diffstat (limited to 'perl-install/detect_devices.pm')
-rw-r--r-- | perl-install/detect_devices.pm | 1396 |
1 files changed, 1102 insertions, 294 deletions
diff --git a/perl-install/detect_devices.pm b/perl-install/detect_devices.pm index 03544bf70..48dadadbb 100644 --- a/perl-install/detect_devices.pm +++ b/perl-install/detect_devices.pm @@ -1,4 +1,4 @@ -package detect_devices; # $Id$ +package detect_devices; use diagnostics; use strict; @@ -8,11 +8,46 @@ use vars qw($pcitable_addons $usbtable_addons); #- misc imports #-###################################################################################### use log; +use MDK::Common; # help perl_checker use common; use devices; use run_program; use modules; use c; +use feature 'state'; + + +=head1 SYNOPSYS + +The B<detect_devices> modules offers a high level API for detecting devices.* +It mostly relies on the L<c> modules for gluing libldetect back into the Perl world, and thus +being able to enumerate DMI/HID/PCI/USB devices. + +Other devices are mostly detected through C</proc> & C</sys>. + +Then the L<list_modules> enables to map modules into categories such as: + +=over 4 + +=item * C<network/ethernet,> + +=item * C<network/wireless,> + +=item * C<network/wifi,> + +=item * C<disk/sata,> + +=item * C<disk/scsi,> + +=item * ... + +=back + +This enables to detect a category by mapping drivers to categories. + +=head1 Listing block devices + +=cut #-##################################################################################### #- Globals @@ -22,8 +57,14 @@ my %serialprobe; #-###################################################################################### #- Functions #-###################################################################################### -sub dev_is_devfs() { -e "/dev/.devfsd" } #- no $::prefix, returns false during install and that's nice :) +=over + +=item get() + +Returns a list of all block devices (IDE, SCSI, SATA, virtual, RAID, ...) + +=cut sub get() { #- Detect the default BIOS boot harddrive is kind of tricky. We may have IDE, @@ -33,7 +74,7 @@ sub get() { #- 2. The first SCSI device if SCSI exists. Or #- 3. The first RAID device if RAID exists. - getIDE(), getSCSI(), getDAC960(), getCompaqSmartArray(), getATARAID(); + getIDE(), getSCSI(), getMmcBlk(), getNVMe(), getXenBlk(), getVirtIO(), getDAC960(), getCompaqSmartArray(); } sub hds() { grep { may_be_a_hd($_) } get() } sub tapes() { grep { $_->{media_type} eq 'tape' } get() } @@ -41,34 +82,43 @@ sub cdroms() { grep { $_->{media_type} eq 'cdrom' } get() } sub burners() { grep { isBurner($_) } cdroms() } sub dvdroms() { grep { isDvdDrive($_) } cdroms() } sub raw_zips() { grep { member($_->{media_type}, 'fd', 'hd') && isZipDrive($_) } get() } -#-sub jazzs { grep { member($_->{media_type}, 'fd', 'hd') && isJazzDrive($_) } get() } sub ls120s() { grep { member($_->{media_type}, 'fd', 'hd') && isLS120Drive($_) } get() } sub zips() { map { $_->{device} .= 4; - $_->{devfs_device} = $_->{devfs_prefix} . '/part4'; $_; } raw_zips(); } -sub floppies() { +sub floppies { + my ($o_not_detect_legacy_floppies) = @_; require modules; - my @fds; - my @dmis = dmidecode(); - # do not try to load floppy if there's no floppy drive: - if ($::isInstall || !@dmis || find { $_->{'Internal Reference Designator'} =~ /^(FLOPPY|FDD)$/ && $_->{name} eq 'Port Connector' } @dmis) { - eval { modules::load("floppy") if $::isInstall }; - if (!$@) { + state @fds; + state $legacy_already_detected; + + $o_not_detect_legacy_floppies = 1 if arch() =~ /arm/; + + if (!$o_not_detect_legacy_floppies && !$legacy_already_detected) { + $legacy_already_detected = 1; + if ($::isInstall) { + eval { modules::load("floppy") }; + system(qw(udevadm settle)) if !$@; + } + #- do not bother probing /dev/fd0 and loading floppy device uselessly, + #- it takes time and it is already done by boot process (if not in install): + #- /dev/fd0 is created by start_udev (/etc/udev/devices.d/default.nodes) + #- then hal probes /dev/fd0 and triggers floppy module loading through kernel's kmod + if (any { (split)[1] eq 'fd' } cat_("/proc/devices")) { @fds = map { - my $info = (!dev_is_devfs() || -e "/dev/fd$_") && c::floppy_info(devices::make("fd$_")); - if_($info && $info ne '(null)', { device => "fd$_", devfs_device => "floppy/$_", media_type => 'fd', info => $info }); + my $info = c::floppy_info("/dev/fd$_"); + if_($info && $info ne '(null)', { device => "fd$_", media_type => 'fd', info => $info }); } qw(0 1); } } - my @ide = ls120s() and eval { modules::load("ide-floppy") }; + my @ide = ls120s() and eval { modules::load("ide_floppy") }; - eval { modules::load("usb-storage") } if usbStorage(); + eval { modules::load("usb_storage") } if $::isInstall && usbStorage(); my @scsi = grep { $_->{media_type} eq 'fd' } getSCSI(); @ide, @scsi, @fds; } @@ -109,61 +159,27 @@ sub get_sys_cdrom_info { } } -sub get_usb_storage_info_24 { - my (@l) = @_; - - my %usbs = map { - my $s = cat_(glob_("$_/*")); - my ($host) = $s =~ /^\s*Host scsi(\d+):/m; #-# - my ($vendor_name) = $s =~ /^\s*Vendor: (.*)/m; - my ($vendor, $id) = $s =~ /^\s*GUID: (....)(....)/m; - if_(defined $host, $host => { vendor_name => $vendor_name, usb_vendor => hex $vendor, usb_id => hex $id }); - } glob_('/proc/scsi/usb-storage-*') or return; - - #- only the entries matching the following conditions can be usb-storage devices - @l = grep { $_->{channel} == 0 && $_->{id} == 0 && $_->{lun} == 0 } @l; - my %l; push @{$l{$_->{host}}}, $_ foreach @l; - - foreach my $host (keys %usbs) { - my @choices = @{$l{$host} || []} or log::l("weird, host$host from /proc/scsi/usb-storage-*/* is not in /proc/scsi/scsi"), next; - if (@choices > 1) { - @choices = grep { $_->{info} =~ /^\Q$usbs{$host}{vendor_name}/ } @choices; - @choices or log::l("weird, can not find the good entry host$host from /proc/scsi/usb-storage-*/* in /proc/scsi/scsi"), next; - @choices == 1 or log::l("argh, can not determine the good entry host$host from /proc/scsi/usb-storage-*/* in /proc/scsi/scsi"), next; - } - add2hash($choices[0], $usbs{$host}); - } - complete_usb_storage_info(@l); - - @l; -} - sub complete_usb_storage_info { my (@l) = @_; my @usb = grep { exists $_->{usb_vendor} } @l; foreach my $usb (usb_probe()) { - if (my $e = find { $_->{usb_vendor} == $usb->{vendor} && $_->{usb_id} == $usb->{id} } @usb) { - $e->{"usb_$_"} = $usb->{$_} foreach keys %$usb; + if (my $e = find { !$_->{found} && $_->{usb_vendor} == $usb->{vendor} && $_->{usb_id} == $usb->{id} } @usb) { + my $host = get_sysfs_usbpath_for_block($e->{device}); + if ($host) { + my $file = "/sys/block/$host/../serial"; + $e->{info} = chomp_(cat_($file)) if -e $file; + $e->{usb_description} = join('|', + chomp_(cat_("/sys/block/$host/../manufacturer")), + chomp_(cat_("/sys/block/$host/../product"))); + } + local $e->{found} = 1; + $e->{"usb_$_"} ||= $usb->{$_} foreach keys %$usb; } } } -sub get_devfs_devices { - my (@l) = @_; - - my %h = (cdrom => 'cd', hd => 'disc'); - - foreach (@l) { - $_->{devfs_prefix} = sprintf('scsi/host%d/bus%d/target%d/lun%d', $_->{host}, $_->{channel}, $_->{id}, $_->{lun}) - if $_->{bus} eq 'SCSI'; - - my $t = $h{$_->{media_type}} or next; - $_->{devfs_device} = $_->{devfs_prefix} . '/' . $t; - } -} - sub isBurner { my ($e) = @_; $e->{capacity} =~ /burner/ and return 1; @@ -181,9 +197,7 @@ sub isDvdDrive { $f && c::isDvdDrive(fileno($f)); } sub isZipDrive { $_[0]{info} =~ /ZIP\s+\d+/ } #- accept ZIP 100, untested for bigger ZIP drive. -sub isJazzDrive { $_[0]{info} =~ /\bJAZZ?\b/i } #- accept "iomega jaz 1GB" sub isLS120Drive { $_[0]{info} =~ /LS-?120|144MB/ } -sub isRemovableUsb { begins_with($_[0]{usb_media_type} || '', 'Mass Storage') && usb2removable($_[0]) } sub isKeyUsb { begins_with($_[0]{usb_media_type} || '', 'Mass Storage') && $_[0]{media_type} eq 'hd' } sub isFloppyUsb { $_[0]{usb_driver} && $_[0]{usb_driver} eq 'Removable:floppy' } sub may_be_a_hd { @@ -195,60 +209,38 @@ sub may_be_a_hd { ); } +sub get_sysfs_field_from_link { + my ($device, $field) = @_; + my $l = readlink("$device/$field"); + $l =~ s!.*/!!; + $l; +} + +sub get_sysfs_usbpath_for_block { + my ($device) = @_; + my $host = readlink("/sys/block/$device"); + $host =~ s!/host.*!!; + $host; +} + sub get_scsi_driver { my (@l) = @_; - my %host2driver = map { if_(m!.*/(.*)/(.*)!, $2, $1) } glob("/proc/scsi/*/*"); + # find driver of host controller from sysfs: foreach (@l) { - if (my $driver = $host2driver{$_->{host}}) { - $_->{driver} = $driver; - } + next if $_->{driver}; + my $host = get_sysfs_usbpath_for_block($_->{device}); + require list_modules; + $_->{driver} = list_modules::filename2modname(get_sysfs_field_from_link("/sys/block/$host", 'driver')); } } -sub getSCSI_24() { - my $err = sub { log::l("ERROR: unexpected line in /proc/scsi/scsi: $_[0]") }; - - my ($first, @l) = common::join_lines(cat_("/proc/scsi/scsi")) or return; - $first =~ /^Attached devices:/ or $err->($first); - - @l = map_index { - my ($host, $channel, $id, $lun) = m/^Host: scsi(\d+) Channel: (\d+) Id: (\d+) Lun: (\d+)/ or $err->($_); - my ($vendor, $model) = /^\s*Vendor:\s*(.*?)\s+Model:\s*(.*?)\s+Rev:/m or $err->($_); - my ($type) = /^\s*Type:\s*(.*)/m or $err->($_); - { info => "$vendor $model", host => $host, channel => $channel, id => $id, lun => $lun, - device => "sg$::i", raw_type => $type, bus => 'SCSI' }; - } @l; - - get_usb_storage_info_24(@l); - - each_index { - my $dev = "sd" . chr($::i + ord('a')); - put_in_hash $_, { device => $dev, media_type => isFloppyUsb($_) ? 'fd' : 'hd' }; - } grep { $_->{raw_type} =~ /Direct-Access|Optical Device/ } @l; +=item getSCSI() - each_index { - put_in_hash $_, { device => "st$::i", media_type => 'tape' }; - } grep { $_->{raw_type} =~ /Sequential-Access/ } @l; +Returns a list of all SCSI device. - each_index { - put_in_hash $_, { device => "sr$::i", media_type => 'cdrom' }; - } grep { $_->{raw_type} =~ /CD-ROM|WORM/ } @l; +=cut - # Old hp scanners report themselves as "Processor"s - # (see linux/include/scsi/scsi.h and sans-find-scanner.1) - each_index { - put_in_hash $_, { media_type => 'scanner' }; - } grep { $_->{raw_type} =~ /Scanner/ || $_->{raw_type} =~ /Processor / } @l; - - delete $_->{raw_type} foreach @l; - - get_devfs_devices(@l); - get_sys_cdrom_info(@l); - get_scsi_driver(@l); - @l; -} - -sub getSCSI_26() { +sub getSCSI() { my $dev_dir = '/sys/bus/scsi/devices'; my @scsi_types = ( @@ -264,34 +256,50 @@ sub getSCSI_26() { "Communications", ); - my @l = map { - my ($host, $channel, $id, $lun) = split ':' or log::l("bad entry in $dev_dir: $_"), next; + my @l; + foreach (all($dev_dir)) { + my ($host, $channel, $id, $lun) = split ':'; + defined $lun or next; my $dir = "$dev_dir/$_"; + + # handle both old and new kernels: + my $node = find { -e $_ } "$dir/block", top(glob_("$dir/block*")), "$dir/tape", top(glob_("$dir/scsi_generic*")); + my ($device) = readlink($node) =~ m!/(?:scsi_(?:generic|tape)|block)/(.*)!; + if (!$device) { + ($device) = top(glob_("$node/*")) =~ m!/(?:scsi_(?:generic|tape)|block)/(.*)!; + } + warn("cannot get info for device ($_)"), next if !$device; + + my $usb_dir = readlink($dir) =~ m!/usb! && "$dir/../../../.."; + my $get_usb = sub { chomp_(cat_("$usb_dir/$_[0]")) }; + my $get = sub { my $s = cat_("$dir/$_[0]"); $s =~ s/\s+$//; $s; }; - my $usb_dir = readlink("$dir/block/device") =~ m!/usb! && "$dir/block/device/../../.."; - my $get_usb = sub { chomp_(cat_("$usb_dir/$_[0]")) }; - - my ($device) = readlink("$dir/block") =~ m!/block/(.*)!; - - my $media_type = ${{ st => 'tape', sr => 'cdrom', sd => 'hd' }}{substr($device, 0, 2)}; - # Old hp scanners report themselves as "Processor"s + # Old HP scanners report themselves as "Processor"s # (see linux/include/scsi/scsi.h and sans-find-scanner.1) my $raw_type = $scsi_types[$get->('type')]; - $media_type ||= 'scanner' if $raw_type =~ /Scanner|Processor/; - { info => $get->('vendor') . ' ' . $get->('model'), host => $host, channel => $channel, id => $id, lun => $lun, + my $media_type = ${{ st => 'tape', sr => 'cdrom', sd => 'hd', sg => 'generic' }}{substr($device, 0, 2)} || + $raw_type =~ /Scanner|Processor/ && 'scanner'; + + my ($vendor, $model) = ($get->('vendor'), $get->('model')); + my ($v, $m) = _get_hd_vendor($model); + if ($v && $m) { + ($vendor, $model) = ($v, $m); + } + push @l, { info => $vendor . ' ' . $model, host => $host, channel => $channel, id => $id, lun => $lun, + description => join('|', $vendor, $model), bus => 'SCSI', media_type => $media_type, device => $device, $usb_dir ? ( usb_vendor => hex($get_usb->('idVendor')), usb_id => hex($get_usb->('idProduct')), ) : (), }; - } all($dev_dir); + } @l = sort { $a->{host} <=> $b->{host} || $a->{channel} <=> $b->{channel} || $a->{id} <=> $b->{id} || $a->{lun} <=> $b->{lun} } @l; @@ -301,21 +309,21 @@ sub getSCSI_26() { $_->{media_type} = 'fd' if $_->{media_type} eq 'hd' && isFloppyUsb($_); } - get_devfs_devices(@l); get_sys_cdrom_info(@l); get_scsi_driver(@l); @l; } -sub getSCSI() { c::kernel_version() =~ /^\Q2.6/ ? getSCSI_26() : getSCSI_24() } -my %eide_hds = ( +my %hd_vendors = ( "ASUS" => "Asus", + "ATA Maxtor" => "Maxtor", "CD-ROM CDU" => "Sony", "CD-ROM Drive/F5D" => "ASUSTeK", "Compaq" => "Compaq", "CONNER" => "Conner Peripherals", "IBM" => "IBM", + "INTEL" => "Intel", "FUJITSU" => "Fujitsu", "HITACHI" => "Hitachi", "Lite-On" => "Lite-On Technology Corp.", @@ -336,6 +344,21 @@ my %eide_hds = ( "WDC" => "Western Digital Corp.", ); +# return ($vendor, $model) +sub _get_hd_vendor { + my ($info) = @_; + foreach my $name (keys %hd_vendors) { + next if !$name; + return ($hd_vendors{$name}, $2) if $info =~ /^$name(-|\s)*(.*)/; + } + return ("Hitachi", $info) if $info =~ /^HD[ST][0-9]/; +} + +=item getIDE() + +Returns a list of all IDE device. + +=cut sub getIDE() { my @idi; @@ -345,27 +368,21 @@ sub getIDE() { #- Great. 2.2 kernel, things are much easier and less error prone. foreach my $d (sort @{[glob_('/proc/ide/hd*')]}) { - cat_("$d/driver") =~ /ide-scsi/ and next; #- already appears in /proc/scsi/scsi my $t = chomp_(cat_("$d/media")); my $type = ${{ disk => 'hd', cdrom => 'cdrom', tape => 'tape', floppy => 'fd' }}{$t} or next; my $info = chomp_(cat_("$d/model")) || "(none)"; my $num = ord(($d =~ /(.)$/)[0]) - ord 'a'; - my ($vendor, $model) = map { - if_($info =~ /^$_(-|\s)*(.*)/, $eide_hds{$_}, $2); - } keys %eide_hds; + my ($vendor, $model) = _get_hd_vendor($info); my $host = $num; ($host, my $id) = divide($host, 2); ($host, my $channel) = divide($host, 2); - my $devfs_prefix = sprintf('ide/host%d/bus%d/target%d/lun0', $host, $channel, $id); push @idi, { media_type => $type, device => basename($d), - devfs_prefix => $devfs_prefix, info => $info, host => $host, channel => $channel, id => $id, bus => 'ide', if_($vendor, Vendor => $vendor), if_($model, Model => $model) }; } - get_devfs_devices(@idi); get_sys_cdrom_info(@idi); @idi; } @@ -373,19 +390,27 @@ sub getIDE() { sub block_devices() { -d '/sys/block' ? map { s|!|/|; $_ } all('/sys/block') - : map { $_->{dev} } devices::read_proc_partitions_raw(); + : map { $_->{dev} } do { require fs::proc_partitions; fs::proc_partitions::read_raw() }; } +=item getCompaqSmartArray() + +Returns a list of all CCISS devices (Compaq Smart Array). + +=cut + sub getCompaqSmartArray() { my (@idi, $f); foreach ('array/ida', 'cpqarray/ida', 'cciss/cciss') { - my $prefix = "/proc/driver/$_"; #- kernel 2.4 places it here - $prefix = "/proc/$_" if !-e "${prefix}0"; #- kernel 2.2 - + my $prefix = "/proc/driver/$_"; my ($name) = m|/(.*)|; for (my $i = 0; -r ($f = "${prefix}$i"); $i++) { my @raw_devices = cat_($f) =~ m|^\s*($name/.*?):|gm; + + #- this is ugly and buggy. keeping it for 2007.0 + #- on a cciss, cciss/cciss0 didn't contain c0d0, but cciss/cciss1 did contain c0d1 + #- the line below adds both c0d0 and c0d1 for cciss0, and so some duplicates @raw_devices or @raw_devices = grep { m!^$name/! } block_devices(); foreach my $raw_device (@raw_devices) { @@ -396,7 +421,8 @@ sub getCompaqSmartArray() { } } } - @idi; + #- workaround the buggy code above. this should be safe though + uniq_ { $_->{device} } @idi; } sub getDAC960() { @@ -406,30 +432,60 @@ sub getDAC960() { #- /dev/rd/c0d0: RAID-7, Online, 17928192 blocks, Write Thru0123456790123456789012 foreach (syslog()) { my ($device, $info) = m|/dev/(rd/.*?): (.*?),| or next; - $idi{$device} = { info => $info, media_type => 'hd', device => $device, prefix => $device . 'p', bus => 'dac960' }; + $idi{$device} = { info => $info, media_type => 'hd', device => $device, bus => 'dac960' }; } values %idi; } -sub getATARAID() { - my %l; - foreach (syslog()) { - my ($device) = m|^\s*(ataraid/d\d+):| or next; - $l{$device} = { info => 'ATARAID block device', media_type => 'hd', device => $device, prefix => $device . 'p', bus => 'ataraid' }; - log::l("ATARAID: $device"); +=item getXenBlk() + +Returns a list of all Xen block devices (C</dev/xvd*>). + +=cut + +sub getXenBlk() { + -d '/sys/bus/xen/devices' or return; + map { + s/block://; + { device => basename($_), info => "Xen block device", media_type => 'hd', bus => 'xen' }; + } glob("/sys/block/xvd*"); +} + +=item getVirtIO() + +Returns a list of all VirtIO block devices (/dev/C<vd*>). + +=cut + +sub getVirtIO() { + -d '/sys/bus/virtio/devices' or return; + map { + { device => basename($_), info => "VirtIO block device", media_type => 'hd', bus => 'virtio' }; + } + glob("/sys/bus/virtio/devices/*/block/*"); +} + +sub getMmcBlk() { + -d '/sys/bus/mmc/devices' or return; + map { + { device => basename($_), info => "MMC block device", media_type => 'hd', bus => 'mmc' }; } - values %l; + glob("/sys/bus/mmc/devices/*/block/*"); } +sub getNVMe() { + -d '/sys/class/nvme' or return; + map { + { device => basename($_), info => "NVMe block device", media_type => 'hd', bus => 'pci_express' }; + } + glob("/sys/block/nvme*"); +} -# cpu_name : arch() =~ /^alpha/ ? "cpu " : -# arch() =~ /^ppc/ ? "processor" : "vendor_id" +=item getCPUs() -# cpu_model : arch() =~ /^alpha/ ? "cpu model" : -# arch() =~ /^ppc/ ? "cpu " : "model name" +Returns a list of all CPUs. -# cpu_freq = arch() =~ /^alpha/ ? "cycle frequency [Hz]" : -# arch() =~ /^ppc/ ? "clock" : "cpu MHz" +=cut sub getCPUs() { my (@cpus, $cpu); @@ -445,46 +501,202 @@ sub getCPUs() { @cpus; } +sub ix86_cpu_frequency() { + cat_('/proc/cpuinfo') =~ /cpu MHz\s*:\s*(\d+)/ && $1; +} + +=item probe_category($category) + +Returns a list of devices which drivers are in the asked category. eg: + + my @eth_cards = probe_category('network/ethernet'); + +=cut + +sub probe_category { + my ($category) = @_; + + require list_modules; + my @modules = list_modules::category2modules($category); + + grep { + if ($category eq 'network/isdn') { + my $b = $_->{driver} =~ /ISDN:([^,]*),?([^,]*)(?:,firmware=(.*))?/; + if ($b) { + $_->{driver} = $1; + $_->{type} = $2; + $_->{type} =~ s/type=//; + $_->{firmware} = $3; + $_->{driver} eq "hisax" and $_->{options} .= " id=HiSax"; + } + $b; + } else { + member($_->{driver}, @modules); + } + } probeall(); +} + sub getSoundDevices() { - modules::probe_category('multimedia/sound'); + probe_category('multimedia/sound'); } -sub isTVcard { member($_[0]->{driver}, qw(bttv saa7134)) } +sub isTVcardConfigurable { member($_[0]{driver}, qw(bttv cx88 saa7134)) } -sub getTVcards() { modules::probe_category('multimedia/tv') } +sub getTVcards() { probe_category('multimedia/tv') } sub getInputDevices() { + state @input_devices; + return @input_devices if @input_devices; my (@devices, $device); + my $event; foreach (cat_('/proc/bus/input/devices')) { if (/^I:/) { - push @devices, $device if $device; $device = {}; - $device->{vendor} = $1 if /Vendor=([0-9a-f]+)/; - $device->{id} = $1 if /Product=([0-9a-f]+)/; - } - $device->{description} = "|$1" if /N: Name="(.*)"/; - $device->{driver} = $1 if /H: Handlers=(\w+)/; - if (/P: Phys=(.*)/) { + $device->{vendor} = /Vendor=(\w+)/ && $1; + $device->{id} = /Product=(\w+)/ && $1; + $device->{bustype} = /Bus=(\w+)/ && $1; + } elsif (/N: Name="(.*)"/) { + my $descr = $1; + $device->{description} = "|$descr"; + + #- I: Bus=0011 Vendor=0002 Product=0008 Version=7321 + #- N: Name="AlpsPS/2 ALPS GlidePoint" + #- P: Phys=isa0060/serio1/input0 + #- H: Handlers=mouse1 event2 ts1 + #- B: EV=f + #- B: KEY=420 0 70000 0 0 0 0 0 0 0 0 #=> BTN_LEFT BTN_RIGHT BTN_MIDDLE BTN_TOOL_FINGER BTN_TOUCH + #- or B: KEY=420 0 670000 0 0 0 0 0 0 0 0 #=> same with BTN_BACK + #- B: REL=3 #=> X Y + #- B: ABS=1000003 #=> X Y PRESSURE + + #- I: Bus=0011 Vendor=0002 Product=0008 Version=2222 + #- N: Name="AlpsPS/2 ALPS DualPoint TouchPad" + #- P: Phys=isa0060/serio1/input0 + #- S: Sysfs=/class/input/input2 + #- H: Handlers=mouse1 ts1 event2 + #- B: EV=f + #- B: KEY=420 0 70000 0 0 0 0 0 0 0 0 + #- B: REL=3 + #- B: ABS=1000003 + + #- I: Bus=0011 Vendor=0002 Product=0007 Version=0000 + #- N: Name="SynPS/2 Synaptics TouchPad" + #- P: Phys=isa0060/serio1/input0 + #- S: Sysfs=/class/input/input1 + #- H: Handlers=mouse0 event1 ts0 + #- B: EV=b + #- B: KEY=6420 0 70000 0 0 0 0 0 0 0 0 #=> BTN_LEFT BTN_RIGHT BTN_MIDDLE BTN_TOOL_FINGER BTN_TOUCH BTN_TOOL_DOUBLETAP BTN_TOOL_TRIPLETAP + #- or B: KEY=6420 0 670000 0 0 0 0 0 0 0 0 #=> same with BTN_BACK + #- or B: KEY=420 30000 670000 0 0 0 0 0 0 0 0 #=> same without BTN_TOOL_TRIPLETAP but with BTN_B + #- B: ABS=11000003 #=> X Y PRESSURE TOOL_WIDTH + + #- I: Bus=0003 Vendor=056a Product=0065 Version=0108 + #- N: Name="Wacom Bamboo" + #- B: KEY=1c63 0 70033 0 0 0 0 0 0 0 0 #=> BTN_0 BTN_1 BTN_4 BTN_5 BTN_LEFT BTN_RIGHT BTN_MIDDLE TOOL_PEN TOOL_RUBBER TOOL_BRUSH TOOL_FINGER TOOL_MOUSE TOUCH STYLUS STYLUS2 + #- B: ABS=100 3000103 #=> X Y WHEEL PRESSURE DISTANCE MISC + + #- I: Bus=0003 Vendor=056a Product=0011 Version=0201 + #- N: Name="Wacom Graphire2 4x5" + #- B: KEY=1c43 0 70000 0 0 0 0 0 0 0 0 #=> BTN_LEFT BTN_RIGHT BTN_MIDDLE TOOL_PEN TOOL_RUBBER TOOL_FINGER TOOL_MOUSE TOUCH STYLUS STYLUS2 + #- B: ABS=100 3000003 #=> X Y PRESSURE DISTANCE MISC + + #I: Bus=0003 Vendor=049f Product=0024 Version=0001 + #N: Name="Compaq Compaq Numeric Keypad" + #P: Phys=usb-0000:00:03.2-2/input0 + #S: Sysfs=/class/input/input7 + #H: Handlers=kbd event3 + #B: EV=120003 + #B: KEY=10000 7 ff800000 7ff febeffdf ffefffff ffffffff fffffffe + #B: LED=1f + + $device->{Synaptics} = $descr eq 'SynPS/2 Synaptics TouchPad'; + $device->{ALPS} = $descr =~ m!^AlpsPS/2 ALPS!; + $device->{Elantech} = $descr eq 'ETPS/2 Elantech Touchpad'; + $device->{Numpad} = $descr =~ /Numeric Keypad/; + + } elsif (/H: Handlers=(.*)/) { + my @l = split(' ', $1); + $device->{driver} = $l[0]; #- keep it for compatibility + $device->{Handlers} = +{ map { (/^(.*?)\d*$/ ? $1 : $_, $_) } split(' ', $1) }; + $event = $device->{Handlers}{event}; + } elsif (/S: Sysfs=(.+)/) { + $device->{sysfs_path} = $1; + } elsif (/P: Phys=(.*)/) { $device->{location} = $1; $device->{bus} = 'isa' if $device->{location} =~ /^isa/; $device->{bus} = 'usb' if $device->{location} =~ /^usb/i; - } + } elsif (/B: REL=(.* )?(.*)/) { + #- REL=3 #=> X Y + #- REL=103 #=> X Y WHEEL + #- REL=143 #=> X Y HWHEEL WHEEL + #- REL=1c3 #=> X Y HWHEEL DIAL WHEEL + my $REL = hex($2); + $device->{HWHEEL} = 1 if $REL & (1 << 6); + $device->{WHEEL} = 1 if $REL & (1 << 8); #- not reliable ("Mitsumi Apple USB Mouse" says REL=103 and KEY=1f0000 ...) + + } elsif (/B: KEY=(\S+)/) { + #- some KEY explained: + #- (but note that BTN_MIDDLE can be reported even if missing) + #- (and "Mitsumi Apple USB Mouse" reports 1f0000) + #- KEY=30000 0 0 0 0 0 0 0 0 #=> BTN_LEFT BTN_RIGHT + #- KEY=70000 0 0 0 0 0 0 0 0 #=> BTN_LEFT BTN_RIGHT BTN_MIDDLE + #- KEY=1f0000 0 0 0 0 0 0 0 0 #=> BTN_LEFT BTN_RIGHT BTN_MIDDLE BTN_SIDE BTN_EXTRA + if (!$> && ! -f "/dev/input/$event") { + devices::make("/dev/input/$event"); + } + if (-r "/dev/input/$event") { + my @KEYS = c::EVIocGBitKey("/dev/input/$event"); + $device->{SIDE} = 1 if $KEYS[0] & (1 << 0x13); + } else { + my $KEY = hex($1); + $device->{SIDE} = 1 if $KEY & (1 << 0x13); + } + + } elsif (/^\s*$/) { + push @devices, $device if $device; + undef $device; + } + } + @input_devices = @devices; +} + +sub getInputDevices_and_usb() { + my @l = getInputDevices(); + + foreach my $usb (usb_probe()) { + if (my $e = find { hex($_->{vendor}) == $usb->{vendor} && hex($_->{id}) == $usb->{id} } @l) { + $e->{usb} = $usb; + } } - push @devices, $device if $device; - @devices; + + @l; +} + +sub serialPorts() { map { "ttyS$_" } 0..7 } +sub serialPort2text { + $_[0] =~ /ttyS(\d+)/ ? "$_[0] / COM" . ($1 + 1) : $_[0]; } +=back + +=head1 Network + +=over + +=cut + sub getSerialModem { - my ($modules_conf, $o_mouse) = @_; + my ($_modules_conf, $o_mouse) = @_; my $mouse = $o_mouse || {}; $mouse->{device} = readlink "/dev/mouse"; - my $serdev = arch() =~ /ppc/ ? "macserial" : "serial"; + my $serdev = "serial"; + eval { modules::load($serdev) }; my @modems; probeSerialDevices(); - foreach my $port (map { "ttyS$_" } (0..7)) { + foreach my $port (serialPorts()) { next if $mouse->{device} =~ /$port/; my $device = "/dev/$port"; next if !-e $device || !hasModem($device); @@ -493,28 +705,30 @@ sub getSerialModem { } my @devs = pcmcia_probe(); foreach my $modem (@modems) { - #- add an alias for macserial on PPC - $modules_conf->set_alias('serial', $serdev) if arch() =~ /ppc/ && $modem->{device}; - foreach (@devs) { $_->{type} =~ /serial/ and $modem->{device} = $_->{device} } + foreach (@devs) { $_->{device} and $modem->{device} = $_->{device} } } @modems; } +=item getModem() + +Return list of modems (serial, WinModems) +=cut + +our $detect_serial_modem = 1; sub getModem { my ($modules_conf) = @_; - getSerialModem($modules_conf, {}), matching_driver__regexp('www\.linmodems\.org'), - matching_driver(list_modules::category2modules('network/modem'), list_modules::category2modules('network/slmodem')); + ($detect_serial_modem ? getSerialModem($modules_conf, {}) : ()), get_winmodems(); } -sub getSpeedtouch() { - grep { $_->{description} eq 'Alcatel|USB ADSL Modem (Speed Touch)' } probeall(); +sub get_winmodems() { + matching_driver__regexp('www\.linmodems\.org'), + matching_driver(list_modules::category2modules('network/modem'), + list_modules::category2modules('network/slmodem')); } sub getBewan() { - matching_desc__regexp('Bewan Systems\|.*ADSL|BEWAN ADSL USB'); -} -sub getSagem() { - matching_driver('eagle-usb'); + matching_desc__regexp('Bewan Systems\|.*ADSL|BEWAN ADSL USB|\[Unicorn\]'); } # generate from the following from eci driver sources: @@ -549,35 +763,176 @@ sub getECI() { grep { member(sprintf("%04x%04x%04x%04x", $_->{vendor}, $_->{id}, $_->{subvendor}, $_->{subid}), @ids) } usb_probe(); } -sub getNet() { - # we want LAN like interfaces here (eg: ath|br|eth|fddi|plip|tr|usb|wifi|wlan). - # there's also bnep%d for bluetooth, bcp%d... - # we do this by blacklisting the following interfaces: - # - sit0 which is *always* created by net/ipv6/sit.c, thus is always created since net.agent loads ipv6 module - # - ippp|isdn|plip|ppp (initscripts suggest that isdn%d can be created but kernel sources claim not) - # ippp%d are created by drivers/isdn/i4l/isdn_ppp.c - # plip%d are created by drivers/net/plip.c - # ppp%d are created by drivers/net/ppp_generic.c - # - # we need both detection schemes since: - # - get_netdevices() use the SIOCGIFCONF ioctl that does not list interfaces that are down - # - /proc/net/dev does not list VLAN and IP aliased interfaces - - grep { !/^(?:lo|ippp|isdn|plip|ppp|sit0)/ } - uniq( - (map { if_(/^\s*([A-Za-z0-9:\.]*):/, $1) } cat_("/proc/net/dev")), - c::get_netdevices(), - ); - } - -#sub getISDN() { -# mapgrep(sub {member (($_[0] =~ /\s*(\w*):/), @netdevices), $1 }, split(/\n/, cat_("/proc/net/dev"))); -#} +=item get_xdsl_usb_devices() + +Return list of xDSL devices. + +=cut + +sub get_xdsl_usb_devices() { + my @bewan = detect_devices::getBewan(); + $_->{driver} = $_->{bus} eq 'USB' ? 'unicorn_usb_atm' : 'unicorn_pci_atm' foreach @bewan; + my @eci = detect_devices::getECI(); + $_->{driver} = 'eciusb' foreach @eci; + my @usb = detect_devices::probe_category('network/usb_dsl'); + $_->{description} = "USB ADSL modem (eagle chipset)" foreach + grep { $_->{driver} eq 'ueagle_atm' && $_->{description} eq '(null)' } @usb; + @usb, @bewan, @eci; +} + +=item is_lan_interface($device) + +Is it a LAN interface (blacklist some interfaces (PPP, ...)? + +=cut + +sub is_lan_interface { + #- we want LAN like interfaces here (eg: ath|br|eth|fddi|plip|ra|tr|usb|wlan). + #- there's also bnep%d for bluetooth, bcp%d... + #- we do this by blacklisting the following interfaces: + #- hso%d are created by drivers/net/usb/hso.c + #- ippp|isdn|plip|ppp (initscripts suggest that isdn%d can be created but kernel sources claim not) + #- ippp%d are created by drivers/isdn/i4l/isdn_ppp.c + #- plip%d are created by drivers/net/plip.c + #- ppp%d are created by drivers/net/ppp_generic.c + #- pan%d are created by bnep + is_useful_interface($_[0]) && + $_[0] !~ /^(?:hso|ippp|isdn|plip|ppp|pan)/; +} + +=item is_useful_interface($device) + +Is it a usefull interface (blacklist some interfaces (loopback, sit, wifi, ...)? + +=cut + +sub is_useful_interface { + #- sit0 which is *always* created by net/ipv6/sit.c, thus is always created since net.agent loads ipv6 module + #- wifi%d are created by 3rdparty/hostap/hostap_hw.c (pseudo statistics devices, #14523) + #- wmaster%d are created by net/mac80211/ieee80211.c ("master" 802.11 device) + #- ax*, rose*, nr*, bce* and scc* are Hamradio devices (#28776) + $_[0] !~ /^(?:lo|sit0|wifi|wmaster|ax|rose|nr|bce|scc)/; +} + +=item is_wireless_interface($device) + +Is it a WiFi interface? + +=cut + +sub is_wireless_interface { + my ($interface) = @_; + #- some wireless drivers don't always support the SIOCGIWNAME ioctl + #- ralink devices need to be up to support it + #- wlan-ng (prism2_*) need some special tweaks to support it + #- use sysfs as fallback to detect wireless interfaces, + #- i.e interfaces for which get_wireless_stats() is available + c::isNetDeviceWirelessAware($interface) + || -e "/sys/class/net/$interface/wireless" + || -e "/sys/class/net/$interface/phy80211"; +} + +=item get_all_net_devices() + +Returns list of all network devices + +=cut + +sub get_all_net_devices() { + #- we need both detection schemes since: + #- get_netdevices() use the SIOCGIFCONF ioctl that does not list interfaces that are down + #- /proc/net/dev does not list VLAN and IP aliased interfaces + uniq( + (map { if_(/^\s*([A-Za-z0-9:\.]*):/, $1) } cat_("/proc/net/dev")), + c::get_netdevices(), + ); +} + +=item get_lan_interfaces() + +Returns list of all LAN devices + +=cut + +sub get_lan_interfaces() { grep { is_lan_interface($_) } get_all_net_devices() } + +=item get_net_interfaces() + +Returns list of all useful network devices + +=cut + +sub get_net_interfaces() { grep { is_useful_interface($_) } get_all_net_devices() } + +=item get_wireless_interface() + +Returns list of all Wireless devices + +=cut + +sub get_wireless_interface() { find { is_wireless_interface($_) } get_lan_interfaces() } + +=item is_bridge_interface($interface) + +Is it a bridge? + +=cut + +sub is_bridge_interface { + my ($interface) = @_; + -f "/sys/class/net/$interface/bridge/bridge_id"; +} + +=back + +=head1 Enumerating devices + +=over + +=cut + +sub get_ids_from_sysfs_device { + my ($dev_path) = @_; + my $dev_cat = sub { chomp_(cat_("$dev_path/$_[0]")) }; + my $usb_root = -f "$dev_path/bInterfaceNumber" && "../" || -f "$dev_path/idVendor" && ""; + my $is_pcmcia = -f "$dev_path/card_id"; + my $sysfs_ids; + my $bus = get_sysfs_field_from_link($dev_path, "subsystem"); + #- FIXME: use $bus + if ($is_pcmcia) { + $sysfs_ids = { modalias => $dev_cat->('modalias') }; + } else { + $sysfs_ids = $bus eq 'ieee1394' ? + { + version => "../vendor_id", + specifier_id => "specifier_id", + specifier_version => "version", + } : + defined $usb_root ? + { id => $usb_root . 'idProduct', vendor => $usb_root . 'idVendor' } : + { id => "device", subid => "subsystem_device", vendor => "vendor", subvendor => "subsystem_vendor" }; + $_ = hex($dev_cat->($_)) foreach values %$sysfs_ids; + if ($bus eq 'pci') { + my $device = basename(readlink $dev_path); + my @ids = $device =~ /^(.{4}):(.{2}):(.{2})\.(.+)$/; + @$sysfs_ids{qw(pci_domain pci_bus pci_device pci_function)} = map { hex($_) } @ids if @ids; + } + } + $sysfs_ids; +} + +sub device_matches_sysfs_ids { + my ($device, $sysfs_ids) = @_; + every { defined $device->{$_} && member($device->{$_}, $sysfs_ids->{$_}, 0xffff) } keys %$sysfs_ids; +} + +sub device_matches_sysfs_device { + my ($device, $dev_path) = @_; + device_matches_sysfs_ids($device, get_ids_from_sysfs_device($dev_path)); +} sub getUPS() { # MGE serial PnP devices: - my @usb_devices = map { ($_->{name} = $_->{description}) =~ s/.*\|//; $_ } usb_probe(); - (map { $_->{port} = $_->{DEVICE}; $_->{bus} = "Serial"; @@ -587,14 +942,24 @@ sub getUPS() { $_->{description} = "MGE UPS SYSTEMS|UPS - Uninterruptible Power Supply" if $_->{MODEL} =~ /000[12]/; $_; } grep { $_->{DESCRIPTION} =~ /MGE UPS/ } values %serialprobe), - # USB UPSs; - (grep { $_->{description} =~ /American Power Conversion\|Back-UPS/ } @usb_devices), - (map { - $_->{port} = "auto"; - $_->{media_type} = 'UPS'; - $_->{driver} = 'newhidups'; - $_; - } grep { $_->{driver} =~ /ups$/ } @usb_devices); + # USB UPSs; + (map { ($_->{name} = $_->{description}) =~ s/.*\|//; $_ } + map { + if ($_->{description} =~ /Back-UPS/ && $_->{driver} eq 'usbhid') { + #- FIXME: should not be hardcoded, use $_->{sysfs_device} . */usb:(hiddev\d+) + #- the device should also be assigned to the ups user + $_->{port} = "/dev/hiddev0"; + $_->{driver} = 'hidups'; + $_; + } elsif ($_->{description} =~ /^MGE UPS Systems\|/ && $_->{driver} =~ /ups$/) { + $_->{port} = "auto"; + $_->{media_type} = 'UPS'; + $_->{driver} = 'newhidups'; + $_; + } else { + (); + } + } usb_probe()); } $pcitable_addons = <<'EOF'; @@ -637,29 +1002,72 @@ sub add_addons { @l; } -sub pci_probe() { +sub get_pci_sysfs_path { + my ($l) = @_; + sprintf('%04x:%02x:%02x.%d', $l->{pci_domain}, $l->{pci_bus}, $l->{pci_device}, $l->{pci_function}); +} + + +my (@pci, @usb); + +sub pci_probe__real() { add_addons($pcitable_addons, map { - my %l; - @l{qw(vendor id subvendor subid pci_bus pci_device pci_function media_type driver description)} = split "\t"; - $l{$_} = hex $l{$_} foreach qw(vendor id subvendor subid); - $l{bus} = 'PCI'; - \%l; + my $l = $_; + $l->{bus} = 'PCI'; + $l->{sysfs_device} = '/sys/bus/pci/devices/' . get_pci_sysfs_path($l); + $l; } c::pci_probe()); } -sub usb_probe() { - -e "/proc/bus/usb/devices" or return; +=item pci_probe() + +Cache the result of C<c::pci_probe()> and return the list of items in the PCI devices. + +=cut + +sub pci_probe() { + state $done; + if (!$done) { + @pci = pci_probe__real() if !@pci; + foreach (@pci) { + $_->{nice_bus} = $_->{is_pciexpress} ? "PCI Express" : "PCI"; + } + } + @pci; +} + +sub usb_probe__real() { + -e "/sys/kernel/debug/usb/devices" or return; add_addons($usbtable_addons, map { - my %l; - @l{qw(vendor id media_type driver description pci_bus pci_device)} = split "\t"; - $l{media_type} = join('|', grep { $_ ne '(null)' } split('\|', $l{media_type})); - $l{$_} = hex $l{$_} foreach qw(vendor id); - $l{bus} = 'USB'; - \%l; + my $l = $_; + $l->{media_type} = join('|', grep { $_ ne '(null)' } split('\|', $l->{media_type})); + $l->{sysfs_device} = "/sys/bus/usb/devices/$l->{pci_bus}-" . ($l->{usb_port} + 1); + $l->{bus} = 'USB'; + $l; } c::usb_probe()); } +=item usb_probe() + +Cache the result of C<c::usb_probe()> and return the list of items in the USB devices. + +=cut + +sub usb_probe() { + if ($::isStandalone && @usb) { + @usb; + } else { + @usb = usb_probe__real(); + } +} + +=item firewire_probe() + +Return list of Firewire controllers + +=cut + sub firewire_probe() { my $dev_dir = '/sys/bus/ieee1394/devices'; my @l = map { @@ -670,6 +1078,7 @@ sub firewire_probe() { specifier_id => hex($get->("$dir/specifier_id")), specifier_version => hex($get->("$dir/version")), bus => 'Firewire', + sysfs_device => $dir, }; } grep { -f "$dev_dir/$_/specifier_id" } all($dev_dir); @@ -698,40 +1107,113 @@ sub firewire_probe() { @l; } +=item pcmcia_controller_probe() + +Return list of PCMCIA controllers + +=cut + sub pcmcia_controller_probe() { - require list_modules; - my @modules = list_modules::category2modules('bus/pcmcia'); - grep { member($_->{driver}, @modules) } probeall(); + my ($controller) = probe_category('bus/pcmcia'); + if (!$controller && !$::testing && !$::noauto && arch() =~ /i.86/) { + my $driver = c::pcmcia_probe(); + $controller = { driver => $driver, description => "PCMCIA controller ($driver)" } if $driver; + } + $controller; } -sub real_pcmcia_probe() { - return if $::testing; +=item pcmcia_probe() - c::pcmcia_probe() || first(map { $_->{driver} } pcmcia_controller_probe()); -} +Return list of PCMCIA devices (eg: Ethernet PCMCIA cards, ...) + +=cut sub pcmcia_probe() { - -e '/var/run/stab' || -e '/var/lib/pcmcia/stab' or return (); + require modalias; + require modules; + my $dev_dir = '/sys/bus/pcmcia/devices'; + map { + my $dir = "$dev_dir/$_"; + my $get = sub { chomp_(cat_("$dir/$_[0]")) }; + my $class_dev = first(glob_("$dir/tty/tty*")); + my $device = $class_dev && basename($class_dev); + my $modalias = $get->('modalias'); + my $driver = get_sysfs_field_from_link($dir, 'driver'); + #- fallback on modalias result + #- but only if the module isn't loaded yet (else, it would already be binded) + #- this prevents from guessing the wrong driver for multi-function devices + my $module = $modalias && first(modalias::get_modules($modalias)); + $driver ||= !member($module, modules::loaded_modules()) && $module; + { + description => join(' ', grep { $_ } map { $get->("prod_id$_") } 1 .. 4), + driver => $driver, + if_($modalias, modalias => $modalias), + if_($device, device => $device), + bus => 'PCMCIA', + sysfs_device => $dir, + }; + } all($dev_dir); +} - my (@devs, $desc); - foreach (cat_('/var/run/stab'), cat_('/var/lib/pcmcia/stab')) { - if (/^Socket\s+\d+:\s+(.*)/) { - $desc = $1; - } else { - my (undef, $type, $module, undef, $device) = split; - push @devs, { description => $desc, driver => $module, type => $type, device => $device }; - } +=item dmi_probe() + +Cache the result of c::dmi_probe() (aka C<dmidecode>) and return the list of items in the DMI table + +=cut + +sub dmi_probe() { + state $dmi_probe; + return if arch() !~ /86/; + $dmi_probe ||= $> ? [] : [ c::dmi_probe() ]; + @$dmi_probe; +} + +=item acpi_probe() + +Return list of devices that are only discoverable via ACPI + +=cut + +my %acpi_device_info = ( + "80860F28" => { vendor => 0x8086, id => 0x0f28, description => 'Intel Corporation|Bay Trail SST Audio DSP', + nice_media_type => 'Audio device', driver => 'snd_sof_acpi_intel_byt' }, + "808622A8" => { vendor => 0x8086, id => 0x22a8, description => 'Intel Corporation|Cherry Trail SST Audio DSP', + nice_media_type => 'Audio device', driver => 'snd_sof_acpi_intel_byt' }, + "INT3438" => { vendor => 0x8086, id => 0x3438, description => 'Intel Corporation|Broadwell SST Audio DSP', + nice_media_type => 'Audio device', driver => 'snd_sof_acpi_intel_bdw' } +); + +sub acpi_probe() { + my $dev_dir = '/sys/bus/acpi/devices'; + my @l; + foreach (glob("$dev_dir/*")) { + my $dev_name = basename($_); + my ($prefix, $suffix) = split(':', $dev_name); + my $e = $acpi_device_info{$prefix}; + if ($e) { + add2hash($e, { bus => 'ACPI', sysfs_device => "$dev_dir/$dev_name" }); + push @l, $e; + } } - @devs; + @l; } +=item probeall() + +Returns a list of all PCI/USB/Firewire/PCMCIA/DMI/HID devices. +It's usually called through a higher level filtering function. + +=cut + # pcmcia_probe provides field "device", used in network.pm -# => probeall with $probe_type is unsafe sub probeall() { return if $::noauto; - require sbus_probing::main; - pci_probe(), usb_probe(), firewire_probe(), pcmcia_probe(), sbus_probing::main::probe(); + pci_probe(), usb_probe(), firewire_probe(), pcmcia_probe(), dmi_probe(), acpi_probe(), getInputDevices_and_usb(); +} +sub probeall_update_cache() { + return if $::noauto; + @pci = pci_probe__real(), @usb = usb_probe__real(), firewire_probe(), pcmcia_probe(), dmi_probe(); } sub matching_desc__regexp { my ($regexp) = @_; @@ -746,14 +1228,29 @@ sub matching_driver { my (@list) = @_; grep { member($_->{driver}, @list) } probeall(); } +sub probe_name { + my ($name) = @_; + map { $_->{driver} =~ /^$name:(.*)/ } probeall(); +} +sub probe_unique_name { + my ($name) = @_; + my @l = uniq(probe_name($name)); + if (@l > 1) { + log::l("oops, more than one $name from probe: ", join(' ', @l)); + } + $l[0]; +} -sub stringlist() { +sub stringlist { + my ($b_verbose) = @_; map { + my $ids = $b_verbose || $_->{description} eq '(null)' ? sprintf("vendor:%04x device:%04x", $_->{vendor}, $_->{id}) : ''; + my $subids = $_->{subid} && $_->{subid} != 0xffff ? sprintf("subv:%04x subd:%04x", $_->{subvendor}, $_->{subid}) : ''; sprintf("%-16s: %s%s%s", $_->{driver} || 'unknown', - $_->{description} eq '(null)' ? sprintf("Vendor=0x%04x Device=0x%04x", $_->{vendor}, $_->{id}) : $_->{description}, + $_->{description}, $_->{media_type} ? sprintf(" [%s]", $_->{media_type}) : '', - $_->{subid} && $_->{subid} != 0xffff ? sprintf(" SubVendor=0x%04x SubDevice=0x%04x", $_->{subvendor}, $_->{subid}) : '', + $ids || $subids ? " ($ids" . ($ids && $subids && " ") . "$subids)" : '', ); } probeall(); } @@ -768,14 +1265,18 @@ sub tryWrite($) { sysopen($F, devices::make($_[0]), 1 | c::O_NONBLOCK()) && $F; } +my @dmesg; sub syslog() { - -r "/tmp/syslog" and return map { /<\d+>(.*)/ } cat_("/tmp/syslog"); - my $LD_LOADER = $ENV{LD_LOADER} || ""; - `$LD_LOADER /bin/dmesg`; + if (-r "/tmp/syslog") { + map { /<\d+>(.*)/ } cat_("/tmp/syslog"); + } else { + @dmesg = `/bin/dmesg` if !@dmesg; + @dmesg; + } } sub get_mac_model() { - my $mac_model = cat_("/proc/device-tree/model") || die "Can not open /proc/device-tree/model"; + my $mac_model = cat_("/proc/device-tree/model") || die "Cannot open /proc/device-tree/model"; log::l("Mac model: $mac_model"); $mac_model; } @@ -786,75 +1287,351 @@ sub get_mac_generation() { sub hasSMP() { return if $::testing; - c::detectSMP() || any { /\bProcessor #(\d+)\s+(\S*)/ && $1 > 0 && $2 ne 'invalid' } syslog(); + (any { /NR_CPUS limit of 1 reached/ } syslog()) || + (any { /^processor\s*:\s*(\d+)/ && $1 > 0 } cat_('/proc/cpuinfo')) || + any { /\bProcessor #(\d+)\s+(\S*)/ && $1 > 0 && $2 ne 'invalid' } syslog(); } -sub hasPCMCIA() { $::o->{pcmcia} } #- because /proc/pcmcia seems not to be present on 2.4 at least (or use /var/run/stab) +sub hasPCMCIA() { $::o->{pcmcia} } my (@dmis, $dmidecode_already_runned); +=item dmidecode() + +Return list of DMI categories from DMI table + +=cut # we return a list b/c several DMIs have the same name: sub dmidecode() { - return @dmis if $dmidecode_already_runned; - - foreach (run_program::get_stdout('dmidecode')) { - if (/^\t\t(.*)/) { - $dmis[-1]{string} .= "$1\n"; - $dmis[-1]{$1} = $2 if /^\t\t(.*): (.*)$/; - } elsif (my ($s) = /^\t(.*)/) { - next if $s =~ /^DMI type /; - $s =~ s/ Information$//; - push @dmis, { name => $s }; - } + return @dmis if $dmidecode_already_runned; + + return if $>; + my ($_ver, @l) = arch() =~ /86/ ? run_program::get_stdout('dmidecode') : (); + + #- drop header + shift @l while @l && $l[0] ne "\n"; + + foreach (@l) { + next if /TRUNCATED/; + if (/^\t(.*)/) { + $dmis[-1]{string} .= "$1\n"; + $dmis[-1]{$1} = $2 if /^\t(.*): (.*)$/; + } elsif (my ($s) = /^(.*)/) { + next if $s =~ /^$/ || $s =~ /\bDMI type \d+/; + $s =~ s/ Information$//; + push @dmis, { name => $s }; } - $dmidecode_already_runned = 1; + } + $dmidecode_already_runned = 1; @dmis; } +=item dmi_detect_memory($category) + +Return only one category from DMI table + +=cut + +sub dmidecode_category { + my ($cat) = @_; + my @l = grep { $_->{name} eq $cat } dmidecode(); + wantarray() ? @l : $l[0] || {}; +} + +=item dmi_detect_memory() + +Return RAM size in MB according to DMI table + +=cut + +sub dmi_detect_memory() { + my @l1 = map { $_->{'Enabled Size'} =~ /(\d+) MB/ && $1 } dmidecode_category('Memory Module'); + my @l2 = map { $_->{'Form Factor'} =~ /^(SIMM|SIP|DIP|DIMM|FB-DIMM|RIMM|SODIMM|SRIMM)$/ && + ($_->{Size} =~ /(\d+) MB/ && $1 || $_->{Size} =~ /(\d+) kB/ && $1 * 1024); + } dmidecode_category('Memory Device'); + max(sum(@l1), sum(@l2)); +} + +=back + +=head1 Test helpers + +=over + +=item computer_info() + +Analyse "Chassis" & "Bios" in dmidecode output and return a hash of flags/values (isLaptop, isServer, BIOS_Year) + +=cut + sub computer_info() { - my @l = dmidecode(); - my $chassis = (find { $_->{name} eq 'Chassis' } @l) || { string => '' }; - my $Chassis = $chassis->{string} =~ /^Type:\s*(\S+)/m && $1; - my $BIOS = (find { $_->{name} eq 'BIOS' } @l) || { string => '' }; - my $BIOS_Year = $BIOS->{string} =~ m!^Release Date:.*?(\d{4})!m && $1 || - $BIOS->{string} =~ m!^Release Date:.*?\d\d/\d\d/(\d\d)!m && "20$1"; + my $Chassis = dmidecode_category('Chassis')->{Type} =~ /(\S+)/ && $1; + + my $date = dmidecode_category('BIOS')->{'Release Date'} || ''; + my $BIOS_Year = $date =~ m!(\d{4})! && $1 || + $date =~ m!\d\d/\d\d/(\d\d)! && "20$1"; - +{ - isLaptop => member($Chassis, 'Portable', 'Laptop', 'Notebook', 'Sub Notebook', 'Docking Station'), - if_($BIOS_Year, BIOS_Year => $BIOS_Year), - }; + +{ + isLaptop => member($Chassis, 'Portable', 'Laptop', 'Notebook', 'Hand Held', 'Sub Notebook', 'Docking Station'), + isServer => member($Chassis, 'Pizza Box', 'Main Server Chassis', 'Rack Mount Chassis', 'Blade'), + if_($BIOS_Year, BIOS_Year => $BIOS_Year), + }; } -#- try to detect a laptop, we assume pcmcia service is an indication of a laptop or -#- the following regexp to match graphics card apparently only used for such systems. +=item isLaptop() + +try to detect a laptop. We assume the following is an indication of a laptop: + +=over 4 + +=item * + +pcmcia service + +=item * + +C<computer_info()> (really C<dmidecode>) telling us it's a laptop + +=item * + +ACPI lid button + +=item * + +a regexp to match graphics card apparently only used for such systems. + +=item * + +Mobility CPU + +=item * + +having Type as Laptop in some device + +=item * + +Intel ipw2100/2200/3945 Wireless + +=back + +=cut + sub isLaptop() { - arch() =~ /ppc/ ? - get_mac_model() =~ /Book/ : computer_info()->{isLaptop} + || glob_("/sys/bus/acpi/devices/PNP0C0D:*") #- ACPI lid button || (matching_desc__regexp('C&T.*655[45]\d') || matching_desc__regexp('C&T.*68554') || matching_desc__regexp('Neomagic.*Magic(Media|Graph)') || matching_desc__regexp('ViRGE.MX') || matching_desc__regexp('S3.*Savage.*[IM]X') || - matching_desc__regexp('ATI.*(Mobility|LT)')) - || cat_('/proc/cpuinfo') =~ /\bmobile\b/i; + matching_desc__regexp('Intel Corporation\|Mobile') || + matching_desc__regexp('\bATI\b.*(Mobility|\bLT\b)')) + || (any { $_->{'model name'} =~ /\b(mobile|C7-M)\b/i } getCPUs()) + || probe_unique_name("Type") eq 'laptop' + #- ipw2100/2200/3945 are Mini-PCI (Express) adapters + || (any { member($_->{driver}, qw(ipw2100 ipw2200 ipw3945)) } pci_probe()); +} + +=item isServer() + +Is it a server? + +=cut + +sub isServer() { + computer_info()->{isServer} + || (any { $_->{Type} =~ /ECC/ } dmidecode_category('Memory Module')) + || dmidecode_category('System Information')->{Manufacturer} =~ /Supermicro/i + || dmidecode_category('System Information')->{'Product Name'} =~ /NetServer|Proliant|PowerEdge|eServer|IBM System x|ThinkServer/i + || matching_desc__regexp('LSI Logic.*SCSI') + || matching_desc__regexp('MegaRAID') + || matching_desc__regexp('NetServer') + || (any { $_->{'model name'} =~ /(Xeon|Opteron)/i } getCPUs()); } sub BIGMEM() { - arch() !~ /x86_64|ia64/ && $> == 0 && c::dmiDetectMemory() > 4 * 1024; + arch() !~ /x86_64/ && $> == 0 && dmi_detect_memory() > 4 * 1024; +} + +sub is_arm_openrd_client() { + to_bool(cat_('/proc/cpuinfo') =~ /OpenRD Client/); +} + +sub is_arm_versatile() { + to_bool(cat_('/proc/cpuinfo') =~ /ARM-Versatile/); +} + +sub is_arm_efikamix() { + to_bool(cat_('/proc/cpuinfo') =~ /Efika MX/); +} + +sub is_xbox() { + any { $_->{vendor} == 0x10de && $_->{id} == 0x02a5 } pci_probe(); +} + +=item virt_technology() + +Returns the virtualization technology (eg: kvm, oracle, ...) + +=cut + +sub virt_technology() { + state $tech; + $tech ||= chomp_(run_program::get_stdout('systemd-detect-virt')); +} + +=item is_hyperv() + +Are we running under Hyper-V hypervisor? + +=cut + +sub is_hyperv() { + virt_technology() eq 'microsoft'; +} + +=item is_qemu() + +Are we running under Qemu hypervisor? + +=cut + +sub is_qemu() { + member(virt_technology(), qw(kvm qemu)); +} + +=item is_virtualbox() + +Are we running under VirtualBox hypervisor? + +=cut + +sub is_virtualbox() { + virt_technology() eq 'oracle'; } -sub is_i586() { - my $cpuinfo = cat_('/proc/cpuinfo'); - $cpuinfo =~ /^cpu family\s*:\s*(\d+)/m && $1 < 6 || - $cpuinfo !~ /^flags.*\bcmov\b/m; +=item is_vmware() + +Are we running under VMware hypervisor? + +=cut + +sub is_vmware() { + virt_technology() eq 'vmware'; +} + +=item is_netbook_nettop() + +Is it a NetBook? + +=cut + +sub is_netbook_nettop() { + my @cpus = getCPUs(); + (any { $_->{'model name'} =~ /(\bIntel\(R\) Atom\(TM\)\B)/i } @cpus) || + (any { $_->{'model name'} =~ /(\bIntel\(R\) Celeron\(R\) M processor\b|\bVIA C7-M Processor\b|\bGeode\(TM\)\B)/i && $_->{'cpu MHz'} < 1500 } @cpus); } -sub matching_type { - my ($type) = @_; - $type =~ /laptop/i && isLaptop(); +=item has_low_resources() + +Is it a low resource machine? + +=cut + +sub has_low_resources() { + availableRamMB() < 100 || arch() =~ /i.86/ && ix86_cpu_frequency() < 350; } -sub usbMice() { grep { $_->{media_type} =~ /\|Mouse/ && $_->{driver} !~ /wacom/ || - $_->{driver} =~ /Mouse:USB/ } usb_probe() } -sub usbWacom() { grep { $_->{driver} =~ /wacom/ } usb_probe() } +=item need_light_desktop() + +Does it need a light desktop (netbook or low resources machine)? + +=cut + +sub need_light_desktop() { + has_low_resources() || is_netbook_nettop(); +} + +=item has_cpu_flag($flag) + +Does CPU has this flag + +=cut + +sub has_cpu_flag { + my ($flag) = @_; + cat_('/proc/cpuinfo') =~ /^flags.*\b$flag\b/m; +} + +=item sub matching_types() + +Returns a hash of flags (laptop, hyperv, touchpad, 64bit, wireless, ...) + +=cut + +sub matching_types() { + +{ + '64bit' => to_bool(arch() =~ /64/), + hyperv => is_hyperv(), + laptop => isLaptop(), + numpad => hasNumpad(), + touchpad => hasTouchpad(), + qemu => is_qemu(), + virtualbox => is_virtualbox(), + vmware => is_vmware(), + wireless => to_bool(get_wireless_interface() || probe_category('network/wireless')), + }; +} + +=item sub hasCPUMicrocode() + +Does CPU need microcode updates? + +=cut + +sub hasCPUMicrocode() { + state $hasCPUMicrocode; + if (!defined $hasCPUMicrocode) { + $hasCPUMicrocode = to_bool(find { 'microcode' } modules::loaded_modules()); + } + return $hasCPUMicrocode; +} + +=item sub hasCPUFreq() + +Does CPU support cpufreq? + +=cut + +sub hasCPUFreq() { + require cpufreq; + to_bool(cpufreq::get_modules()) + || cat_('/proc/cpuinfo') =~ /AuthenticAMD/ && arch() =~ /x86_64/ + || cat_('/proc/cpuinfo') =~ /model name.*Intel\(R\) Core\(TM\)2 CPU/; +} + +=item sub hasWacom() + +is there a Wacom tablet? + +=cut + +sub hasWacom() { find { $_->{vendor} == 0x056a || $_->{driver} =~ /wacom/ } usb_probe() } + +=item sub hasTouchpad() + +is there a touchpad? + +=cut + +sub hasTouchpad() { any { $_->{Synaptics} || $_->{ALPS} || $_->{Elantech} } getInputDevices() } + +=item sub hasNumpad() + +is there a numeric pad? + +=cut + +sub hasNumpad() { any { $_->{Numpad} } getInputDevices() } + +sub usbWacom() { grep { $_->{vendor} eq '056a' } getInputDevices() } sub usbKeyboards() { grep { $_->{media_type} =~ /\|Keyboard/ } usb_probe() } sub usbStorage() { grep { $_->{media_type} =~ /Mass Storage\|/ } usb_probe() } sub has_mesh() { find { /mesh/ } all_files_rec("/proc/device-tree") } @@ -863,6 +1640,7 @@ sub has_53c94() { find { /53c94/ } all_files_rec("/proc/device-tree") } sub usbKeyboard2country_code { my ($usb_kbd) = @_; my ($F, $tmp); + # FIXME: usbfs is dead! we should poke somewhere in /sys/ !!!! sysopen($F, sprintf("/proc/bus/usb/%03d/%03d", $usb_kbd->{pci_bus}, $usb_kbd->{pci_device}), 0) and sysseek $F, 0x28, 0 and sysread $F, $tmp, 1 and @@ -872,8 +1650,7 @@ sub usbKeyboard2country_code { sub probeSerialDevices() { require list_modules; require modules; - modules::load_category($::o->{modules_conf}, 'various/serial'); - modules::append_to_modules_loaded_at_startup_for_all_kernels($_) foreach list_modules::category2modules('various/serial'); + modules::append_to_modules_loaded_at_startup_for_all_kernels(modules::load_category($::o->{modules_conf}, 'various/serial')); foreach (0..3) { #- make sure the device are created before probing, devices::make("/dev/ttyS$_"); @@ -918,6 +1695,24 @@ sub hasMousePS2 { my $t; sysread(tryOpen($_[0]) || return, $t, 256) != 1 || $t ne "\xFE"; } +sub probeall_unavailable_modules() { + map { + my $driver = $_->{driver}; + $driver !~ /:/ && + !member($driver, 'hub', 'unknown', 'amd64_agp') && + !modules::module_is_available($driver) ? + $driver : + @{[]}; + } probeall(); +} + +sub probeall_dkms_modules() { + my @unavailable_modules = probeall_unavailable_modules() or return; + require modalias; + my $dkms_modules = modalias::parse_file_modules($::prefix . "/usr/share/ldetect-lst/dkms-modules.alias"); + intersection([ keys(%$dkms_modules) ], \@unavailable_modules); +} + sub usb_description2removable { local ($_) = @_; return 'camera' if /\bcamera\b/i; @@ -958,4 +1753,17 @@ sub suggest_mount_point { $name; } +=back + +=head1 SEE ALSO + +See L<hardware_detection> for the overall view. + +=cut + 1; + +#- Local Variables: +#- mode:cperl +#- tab-width:8 +#- End: |