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