#!/usr/bin/perl -w # # Copyright (C) 2007-2008 Jean Delvare # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA # # This script feeds the i2c-stub driver with dump data from a real # I2C or SMBus chip. This can be useful when writing a driver for # a device you do not have access to, but of which you have a dump. use strict; use vars qw($bus_nr $addr $bytes $words $err); # Kernel version detection code by Mark M. Hoffman, # copied from sensors-detect. use vars qw(@kernel_version); sub initialize_kernel_version { `uname -r` =~ /(\d+)\.(\d+)\.(\d+)(.*)/; @kernel_version = ($1, $2, $3, $4); } sub kernel_version_at_least { my ($vers, $plvl, $slvl) = @_; return 1 if ($kernel_version[0] > $vers || ($kernel_version[0] == $vers && ($kernel_version[1] > $plvl || ($kernel_version[1] == $plvl && ($kernel_version[2] >= $slvl))))); return 0; } # Find out the i2c bus number of i2c-stub sub get_i2c_stub_bus_number { my $nr; open(FH, "i2cdetect -l |") || die "Can't run i2cdetect"; while () { next unless m/^i2c-(\d+).*\tSMBus stub/; $nr = $1; last; } close(FH); return $nr; } # Load the required kernel drivers if needed sub load_kernel_drivers { local $_; my $addr = oct shift; my $nr; # Maybe everything is already loaded $nr = get_i2c_stub_bus_number(); if (defined $nr) { if (kernel_version_at_least(2, 6, 19)) { # Check if the chip address we need is there open(CHIP_ADDR, "/sys/module/i2c_stub/parameters/chip_addr"); $_ = ; chomp; my @stub_addr = split ','; close(CHIP_ADDR); foreach (@stub_addr) { return $nr if $addr == $_; } printf STDERR "i2c-stub already loaded without support for address 0x%02x\n", $addr; exit 2; } return $nr; } system("/sbin/modprobe", "i2c-dev") == 0 || exit 1; if (kernel_version_at_least(2, 6, 19)) { system("/sbin/modprobe", "i2c-stub", "chip_addr=$addr") == 0 || exit 1; } else { system("/sbin/modprobe", "i2c-stub") == 0 || exit 1; } sleep(1); # udev may take some time to create the device node $nr = get_i2c_stub_bus_number(); if (!defined($nr)) { print STDERR "Please load i2c-stub first\n"; exit 2; } return $nr; } sub process_dump { my $dump = shift; my $err = 0; open(DUMP, $dump) || die "Can't open $dump: $!\n"; OUTER_LOOP: while () { if (m/^([0-9a-f]0):(( [0-9a-fX]{2}){16})/) { # Byte dump my $offset = hex($1); my @values = split(/ /, $2); shift(@values); for (my $i = 0; $i < 16 && (my $val = shift(@values)); $i++) { next if $val =~ m/X/; if (system("i2cset", "-y", $bus_nr, $addr, sprintf("0x\%02x", $offset+$i), "0x$val", "b")) { $err = 3; last OUTER_LOOP; } $bytes++; } } elsif (m/^([0-9a-f][08]):(( [0-9a-fX]{4}){8})/) { # Word dump my $offset = hex($1); my @values = split(/ /, $2); shift(@values); for (my $i = 0; $i < 8 && (my $val = shift(@values)); $i++) { next if $val =~ m/X/; if (system("i2cset", "-y", $bus_nr, $addr, sprintf("0x\%02x", $offset+$i), "0x$val", "w")) { $err = 3; last OUTER_LOOP; } $words++; } } } close(DUMP); return $err; } if ($>) { print "You must be root to use this script\n"; exit 1; } if (@ARGV != 2) { print STDERR "Usage: i2c-stub-from-dump \n"; exit 1; } # Check the parameters $addr = $ARGV[0]; if ($addr !~ m/^0x[0-7][0-9a-f]$/i) { print STDERR "Invalid address $addr\n"; exit 1; } initialize_kernel_version(); $bus_nr = load_kernel_drivers($addr); $bytes = $words = 0; # We don't want to see the output of 256 i2cset open(SAVEOUT, ">&STDOUT"); open(STDOUT, ">/dev/null"); $err = process_dump($ARGV[1]); close(STDOUT); if ($bytes) { printf SAVEOUT "$bytes byte values written to \%d-\%04x\n", $bus_nr, oct($addr); } if ($words) { printf SAVEOUT "$words word values written to \%d-\%04x\n", $bus_nr, oct($addr); } if (!$err && ($bytes + $words == 0)) { printf SAVEOUT "Only garbage found in dump file $ARGV[1]\n"; exit(1); } exit($err);