1#! /usr/bin/perl 2 3# ex:ts=8 sw=4: 4# $OpenBSD: FwUpdate.pm,v 1.32 2020/02/11 16:20:05 stsp Exp $ 5# 6# Copyright (c) 2014 Marc Espie <espie@openbsd.org> 7# 8# Permission to use, copy, modify, and distribute this software for any 9# purpose with or without fee is hereby granted, provided that the above 10# copyright notice and this permission notice appear in all copies. 11# 12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19# 20use strict; 21use warnings; 22use OpenBSD::PkgAdd; 23use OpenBSD::PackageRepository; 24use OpenBSD::PackageLocator; 25 26package OpenBSD::FwUpdate::Locator; 27our @ISA = qw(OpenBSD::PackageLocator); 28 29sub add_default 30{ 31 my ($self, $state, $p) = @_; 32 my $path = $state->opt('p'); 33 if (!$path) { 34 my $dir = OpenBSD::Paths->os_directory; 35 if (!defined $dir) { 36 $state->fatal("Couldn't find/parse OS version"); 37 } 38 $path = "http://firmware.openbsd.org/firmware/$dir/"; 39 } 40 $p->add(OpenBSD::PackageRepository->new($path, $state)); 41} 42 43package OpenBSD::FwUpdate::State; 44our @ISA = qw(OpenBSD::PkgAdd::State); 45 46sub cache_directory 47{ 48 return undef; 49} 50 51sub locator 52{ 53 return "OpenBSD::FwUpdate::Locator"; 54} 55 56sub handle_options 57{ 58 my $state = shift; 59 $state->OpenBSD::State::handle_options('adinp:', 60 '[-adinv] [-D keyword] [-p path] [driver...]'); 61 $state->{not} = $state->opt('n'); 62 if ($state->opt('i')) { 63 $state->{not} = 1; 64 } 65 $main::not = $state->{not}; 66 $state->progress->setup(0, 0, $state); 67 $state->{localbase} = OpenBSD::Paths->localbase; 68 $state->{destdir} = ''; 69 $state->{wantntogo} = 0; 70 $state->{interactive} = OpenBSD::InteractiveStub->new($state); 71 $state->{subst}->add('repair', 1); 72 if ($state->opt('a') && @ARGV != 0) { 73 $state->usage; 74 } 75 $state->{fw_verbose} = $state->{v}; 76 if ($state->{v}) { 77 $state->{v}--; 78 } 79 if ($state->{fw_verbose}) { 80 $state->say("Path to firmware: #1", 81 $state->locator->printable_default_path($state)); 82 } 83 $state->{subst}->add('NO_SCP', 1); 84} 85 86sub finish_init 87{ 88 my $state = shift; 89 delete $state->{signer_list}; # XXX uncache value 90 $state->{subst}->add('FW_UPDATE', 1); 91} 92 93sub installed_drivers 94{ 95 my $self = shift; 96 return keys %{$self->{installed_drivers}}; 97} 98 99sub is_installed 100{ 101 my ($self, $driver) = @_; 102 return $self->{installed_drivers}{$driver}; 103} 104 105sub machine_drivers 106{ 107 my $self = shift; 108 return keys %{$self->{machine_drivers}}; 109} 110 111sub all_drivers 112{ 113 my $self = shift; 114 return keys %{$self->{all_drivers}}; 115} 116 117sub is_needed 118{ 119 my ($self, $driver) = @_; 120 return $self->{machine_drivers}{$driver}; 121} 122 123sub display_timestamp 124{ 125 my ($state, $pkgname, $timestamp) = @_; 126 return unless $state->verbose; 127 $state->SUPER::display_timestamp($pkgname, $timestamp); 128} 129 130sub fw_status 131{ 132 my ($state, $msg, $list) = @_; 133 return if @$list == 0; 134 $state->say("#1: #2", $msg, join(' ', @$list)); 135} 136 137package OpenBSD::FwUpdate::Update; 138our @ISA = qw(OpenBSD::Update); 139 140package OpenBSD::FwUpdate; 141our @ISA = qw(OpenBSD::PkgAdd); 142 143OpenBSD::Auto::cache(updater, 144 sub { 145 require OpenBSD::Update; 146 return OpenBSD::FwUpdate::Update->new; 147 }); 148 149my %possible_drivers = map {($_, "$_-firmware")} 150 (qw(acx amdgpu athn bwfm bwi intel inteldrm ipw iwi 151 iwm iwn iwx malo ogx otus pgt radeondrm rsu rtwn 152 uath upgt urtwn uvideo vmm wpi)); 153 154my %match = map {($_, qr{^\Q$_\E\d+\s+at\s})} (keys %possible_drivers); 155$match{'intel'} = qr{^cpu\d+: Intel}; 156 157sub parse_dmesg 158{ 159 my ($self, $f, $search, $found) = @_; 160 161 while (<$f>) { 162 chomp; 163 for my $driver (keys %$search) { 164 next unless $_ =~ $match{$driver}; 165 $found->{$driver} = 1; 166 delete $search->{$driver}; 167 } 168 } 169} 170 171sub find_machine_drivers 172{ 173 my ($self, $state) = @_; 174 $state->{machine_drivers} = {}; 175 $state->{all_drivers} = \%possible_drivers; 176 my %search = %possible_drivers; 177 if (open(my $f, '<', '/var/run/dmesg.boot')) { 178 $self->parse_dmesg($f, \%search, $state->{machine_drivers}); 179 close($f); 180 } else { 181 $state->errsay("Can't open dmesg.boot: #1", $!); 182 } 183 if (open(my $cmd, '-|', 'dmesg')) { 184 $self->parse_dmesg($cmd, \%search, $state->{machine_drivers}); 185 close($cmd); 186 } else { 187 $state->errsay("Can't run dmesg: #1", $!); 188 } 189} 190 191sub driver2firmware 192{ 193 my $k = shift; 194 return $possible_drivers{$k}; 195} 196 197sub find_installed_drivers 198{ 199 my ($self, $state) = @_; 200 my $inst = $state->repo->installed; 201 for my $driver (keys %possible_drivers) { 202 my $search = OpenBSD::Search::Stem->new(driver2firmware($driver)); 203 my $l = $inst->match_locations($search); 204 if (@$l > 0) { 205 $state->{installed_drivers}{$driver} = 206 OpenBSD::Handle->from_location($l->[0]); 207 } 208 } 209} 210 211 212sub new_state 213{ 214 my ($self, $cmd) = @_; 215 return OpenBSD::FwUpdate::State->new($cmd); 216} 217 218sub find_handle 219{ 220 my ($self, $state, $driver) = @_; 221 my $pkgname = driver2firmware($driver); 222 my $set; 223 my $h = $state->is_installed($driver); 224 if ($h) { 225 $set = $state->updateset->add_older($h); 226 } else { 227 $set = $state->updateset->add_hints($pkgname); 228 } 229 return $set; 230} 231 232sub mark_set_for_deletion 233{ 234 my ($self, $set, $state) = @_; 235 # XXX to be simplified. Basically, we pre-do the work of the updater... 236 for my $h ($set->older) { 237 $h->{update_found} = 1; 238 } 239 $set->{updates}++; 240} 241 242# no quirks for firmware, bypass entirely 243sub do_quirks 244{ 245 my ($self, $state) = @_; 246 $state->finish_init; 247} 248 249sub to_remove 250{ 251 my ($self, $state, $driver) = @_; 252 $self->mark_set_for_deletion($self->to_add_or_update($state, $driver)); 253} 254 255sub to_add_or_update 256{ 257 my ($self, $state, $driver) = @_; 258 my $set = $self->find_handle($state, $driver); 259 push(@{$state->{setlist}}, $set); 260 return $set; 261} 262 263sub show_info 264{ 265 my ($self, $state) = @_; 266 my (@installed, @unneeded, @needed); 267 for my $d ($state->installed_drivers) { 268 my $h = $state->is_installed($d)->pkgname; 269 if ($state->is_needed($d)) { 270 push(@installed, $h); 271 } else { 272 push(@unneeded, $h); 273 } 274 } 275 for my $d ($state->machine_drivers) { 276 if (!$state->is_installed($d)) { 277 push(@needed, driver2firmware($d)); 278 } 279 } 280 $state->fw_status("Installed", \@installed); 281 $state->fw_status("Installed, extra", \@unneeded); 282 $state->fw_status("Missing", \@needed); 283} 284 285sub silence_children 286{ 287 0 288} 289 290sub process_parameters 291{ 292 my ($self, $state) = @_; 293 294 $self->find_machine_drivers($state); 295 $self->find_installed_drivers($state); 296 297 if ($state->opt('i')) { 298 $self->show_info($state); 299 exit(0); 300 } 301 if (@ARGV == 0) { 302 if ($state->opt('d')) { 303 for my $driver ($state->installed_drivers) { 304 if ($state->opt('a') || 305 !$state->is_needed($driver)) { 306 $self->to_remove($state, $driver); 307 } 308 } 309 } else { 310 if ($state->opt('a')) { 311 for my $driver ($state->all_drivers) { 312 $self->to_add_or_update($state, 313 $driver); 314 } 315 } else { 316 for my $driver ($state->machine_drivers) { 317 $self->to_add_or_update($state, 318 $driver); 319 } 320 for my $driver ($state->installed_drivers) { 321 # XXX skip already done up there ^ 322 next if $state->is_needed($driver); 323 $self->to_add_or_update($state, 324 $driver); 325 } 326 } 327 } 328 if (!(defined $state->{setlist}) && $state->{fw_verbose}) { 329 $state->say($state->opt('d') ? 330 "No firmware to delete." : 331 "No devices found which need firmware files to be downloaded."); 332 exit(0); 333 } 334 } else { 335 for my $driver (@ARGV) { 336 $driver =~ s/\-firmware(\-\d.*)?$//; 337 if (!$possible_drivers{$driver}) { 338 $state->errsay("#1: unknown driver", $driver); 339 exit(1); 340 } 341 if ($state->opt('d') && 342 !$state->is_installed($driver)) { 343 $state->errsay("Can't delete uninstalled driver: #1", $driver); 344 next; 345 } 346 347 my $set = $self->to_add_or_update($state, $driver); 348 if ($state->opt('d')) { 349 $self->mark_set_for_deletion($set); 350 } 351 } 352 } 353 if ($state->{fw_verbose}) { 354 my (@deleting, @updating, @installing); 355 for my $set (@{$state->{setlist}}) { 356 for my $h ($set->older) { 357 if ($h->{update_found}) { 358 push(@deleting, $h->pkgname); 359 } else { 360 push(@updating, $h->pkgname); 361 } 362 } 363 for my $h ($set->hints) { 364 push(@installing, $h->pkgname); 365 } 366 } 367 $state->fw_status("Installing", \@installing); 368 $state->fw_status("Deleting", \@deleting); 369 $state->fw_status("Updating", \@updating); 370 } 371} 372 3731; 374