#!/usr/bin/ruby1.9.1 $acronym_regex = "." #$acronym_regex = "[\w\d_]" class Reg private def valid_bitnumber(current, new) return (new == 31 or new == 15 or new == 7) if (not current) or current < 0 return current == new end def warning(current, start, line) expected = current expected ||= "7, 15 or 31" $stderr.puts "WARNING: Expected bit #{expected} but got bit #{start}: #{line}" end public def initialize_parse lines current = start = stop = nil total_width = 0 acronym_begin = nil for line in lines re = %r<(?x) ^ \s* (?: (?'start'\d{1,2}) (?:\s+|\s*:\s*) (?'stop'\d{1,2}) | (?'something'\d{1,4}) )? \s+ (?'acronym'#{$acronym_regex}+?) \s* $ > m = re.match line if not m $stderr.puts "WARNING: Invalid line: #{line}" next end if acronym_begin acronym = acronym_begin + m[:acronym] acronym_begin = nil else acronym = m[:acronym] end if m[:start] start = m[:start].to_i if not valid_bitnumber(current, start) warning current, start, line end stop = m["stop"].to_i elsif m[:something] start = m[:something].to_i if not valid_bitnumber(current, start) start, stop = m[:something].scan(/^(\d{1,2})(\d{1,2})$/).first start, stop = start.to_i, stop.to_i start, stop = stop, start if start < stop if not valid_bitnumber(current, start) warning current, start, line end else stop = start end else acronym_begin = m[:acronym] next end current = stop - 1 width = start - stop + 1 total_width += width name = (acronym =~ /reserved|rsvd/i ? "" : acronym) @fields << Field.new(start, stop, name) end if total_width != 8 and total_width != 16 and total_width != 32 $stderr.puts "WARNING: Total register width is #{total_width} bits" end end def initialize_from_map m @fields += m["fields"].map { |f| start, stop = f["range"] stop = start unless stop Field.new(start, stop, f["acronym"]) } end class Field attr_accessor :start, :stop, :name def initialize start, stop, name="" @start, @stop, @name = start, stop, name end end def self.parse lines self.new.tap {|s| s.initialize_parse lines } end def self.from_map m, name self.new(name).tap {|s| s.initialize_from_map m } end attr_reader :name def initialize name=nil @name = name @fields = [] end def print_bitfield for f in @fields width = f.start - f.stop + 1 tabs = "\t" * [2 - f.name.length / 8, 0].max puts "\t#{f.name}#{tabs}:#{width}," end end def diffanalyze value, old=nil, stream=$stdout fields = @fields. zip(fvs(value), fvs(old)). select {|f, fv, oldfv| fv != oldfv} return if fields.empty? longest, longest_name = find_longest fields.transpose[0] for f, fv, _ in fields analyze_one f, fv, longest, longest_name, stream end end # FVS = return a table of values def fvs value @fields.map do |f| width = f.start - f.stop + 1 ((value >> f.stop) & ((1 << width) - 1)) end end def find_longest fields=@fields longest = fields.map{|f| f.start - f.stop + 1 }.max longest_name = fields.map{|f| f.name.length }.max [longest, longest_name] end def analyze_and_or andv, orv, stream=$stdout longest, longest_name = find_longest value = orv # yo, lol bits_changed = 0 | ~andv | orv for f, fv in @fields.zip fvs(value) width = f.start - f.stop + 1 if 0 != (bits_changed >> f.stop) & ((1 << width) - 1) if 0 != ~(bits_changed >> f.stop) & ((1 << width) - 1) stream << "WARNING: some bits not changed in following field!\n" end analyze_one f, fv, longest, longest_name, stream end end end def analyze value, stream=$stdout longest, longest_name = find_longest for f, fv in @fields.zip fvs(value) analyze_one f, fv, longest, longest_name, stream end end def cnalyze value, stream=$stdout stream << @fields.zip(fvs(value)). reject!{|f,fv| fv == 0 }. map{|f,fv| "#{fv} << #{@name}_#{f.name}" }. join(" | ") << "\n" end def analyze_one f, fv, longest, longest_name, stream=$stdout tabs = "\t" * [2 - f.name.length / 8, 0].max width = f.start - f.stop + 1 format = "%2s:%-2s %-#{longest_name}s %#{longest}sb " << "% #{(longest / 4.0).ceil}sh % #{(longest / 3.0).ceil}so\n" stream << sprintf( format, f.start.to_s, f.stop.to_s, f.name, fv.to_bin(width), fv.to_s(16), fv.to_s(8)) end end class Numeric def between(a,b) a <= self and self < b end end class Integer def to_bin(width) s = to_s(2) s = "0" * (width - s.length) + s if s.length < width s end end class BlackHole class << self def << a end end end $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'trollop' usage = < #{$PROGRAM_NAME} #{$PROGRAM_NAME} < END opts = Trollop::options do banner usage opt :l, "Read log and print a more verbose version" opt :d, "Read memory dump and print a more verbose version" opt :c, "Produce C code instead of human-readable description" end def load_data f require 'yaml' YAML.load_file f end $canonical = {} def reg_from_name data, name, stream=BlackHole regname = name.sub(/^E1000_/, "") regname = $canonical[regname] if $canonical[regname] unless data[regname] regshort = regname[/^[\w\d_]+/] if (regs = data.keys.select{|i|i.start_with? regshort}).empty? stream << " (register #{regname} does not exist!)" return nil else regname = $canonical[regname] = regs.first stream << " (interpreted as register #{regname})" end end reg = Reg.from_map(data[regname], regname) end def reg_from_number data, str, stream=BlackHole rega = str.to_i 0 if rega != 0 or str =~ /^(0x)?0+$/ reg = data.select do |k,v| if v["recurring"] (rega - v["offset"] / (v["size"] / 8)).between(0, v["recurring"]) else v["offset"] == rega end end.to_a[0] if reg.nil? stream << " Unknown register!" return nil end return Reg.from_map(reg[1], reg[0]) end nil end def reg_from_str data, str, stream=BlackHole reg_from_number(data, str, stream) or reg_from_name(data, str, stream) end if opts.l data = load_data ARGV[0] state = {} while (line = $stdin.gets) line.chomp! begin annotation = "" if line =~ /^([^ ]+) (<-|->) (0x.+)$/ regname = $1 value = $3.to_i(0) is_read = ($2 == "->") end reg = reg_from_name data, regname, annotation next unless reg annotation << "\n" if state[regname] reg.diffanalyze(value, state[regname], annotation) else reg.analyze(value, annotation) end state[regname] = value ensure print line puts "#{annotation}" end end exit end if opts.d data = load_data ARGV[0] while (line = $stdin.gets) line.chomp! begin annotation = "" unless line =~ /^\s*([^\s]+)\s+([^\s]+)\s*$/ annotation << "isn't a valid line!\n" next end rega = $1 value = $2.to_i 16 reg = reg_from_number data, rega, annotation next unless reg annotation << " " << reg.name << "\n" reg.analyze(value, annotation) ensure print line puts annotation end end exit end case ARGV.length when 0 Trollop::die "Not enough arguments" when 1 lines = [] lines << $_ while $stdin.gets reg = Reg.parse(lines) reg.analyze(ARGV[0].to_i(0)) when 2 Trollop::die "Not enough arguments" when 3 data = load_data ARGV[0] reg = reg_from_str(data, ARGV[1], $stdout) unless reg puts exit end value = ARGV[2].to_i 0 if opts.c reg.cnalyze(value) else reg.analyze(value) end when 4 data = load_data ARGV[0] reg = reg_from_str(data, ARGV[1], $stdout) unless reg puts exit end andv = ARGV[2].to_i 0 orv = ARGV[3].to_i 0 reg.analyze_and_or(andv, orv) end