#!/usr/bin/python """IEEE 1212/ IEEE 1394 Configuration ROM pretty printer Copyright 2010 Stefan Richter You may freely use, modify, and/or redistribute this program. Reads Configuration ROM data from stdin (binary data, i.e. a big endian or little endian quadlet array, or a firewire-ohci debug log) and writes a human-readable annotated representation to stdout. Usage examples: - Read a configuration ROM as seen in sysfs: (plug device in, find out which fw* it became) crpp < /sys/bus/firewire/devices/fw1/config_rom - Read a debug log from when the drivers attempt to probe the device: echo 1 > /sys/module/firewire_ohci/parameters/debug (plug device in, wait a few seconds) dmesg | crpp echo 0 > /sys/module/firewire_ohci/parameters/debug If you want company IDs being translated to names, you need a file called oui.db in /usr/share/misc/ or in the current working directory. This file can be obtained from linux-2.6.20 or older kernel soures in linux/drivers/ieee1394/ or can be freshly generated by wget -O - http://standards.ieee.org/regauth/oui/oui.txt | grep -E '(base 16).*\w+.*$' | sed -e 's/\s*(base 16)\s*/ /' > oui.db which will download ~2 MB data and result in a ~0.4 MB large oui.db. History: 2010-02-16: initial release 2010-02-18: small fixes; added protocol entries, OUI lookup, CRC checks """ ouidb_search_paths = ( "/usr/share/misc/oui.db", "oui.db", ) def crc16(rom, i, l): def nextcrc(c, q): for j in xrange(28, -1, -4): s = (c >> 12 ^ q >> j) & 0xf c = c << 4 ^ s << 12 ^ s << 5 ^ s return c & 0xffff return reduce(nextcrc, rom[i:min(i + l, 256)], 0) def u8_to_char(n): # separator/ terminator if n == 0: return "" # printable character from minimal ASCII if n in range(0x20, 0x23) + range(0x25, 0x5b) + [0x5f] + range(0x61, 0x7b): return chr(n) # other character return "~" def u32_to_string(n): if n == 0: return "" s = "\"" for i in 24, 16, 8, 0: s += u8_to_char(n >> i & 0xff) # add separator in c0c0, c0cc, c00c, or 0c0c if n & 0xff000000 and n & 0x0000ffff and not n & 0x00ff0000 or \ n & 0x00ff0000 and n & 0x000000ff and not n & 0xff00ff00: s = s[:2] + "\", \"" + s[2:] # add separator in cc0c if n & 0xff000000 and n & 0x00ff0000 and n & 0xff and not n & 0xff00: s = s[:3] + "\", \"" + s[3] return s + "\"" def language(n): n &= 0xffff if n == 0: return "en" s = "" for i in 10, 5, 0: c = n >> i & 0x1f s += char(ord("a") - 1 + c) if c >= 1 and c <= 26 else " " return s def bcd_version_details(n): return "v%s%d.%d%s" % ( "%d" % v >> 20 & 0xf if v & 0xf00000 else "", v >> 16 & 0xf, v >> 12 & 0xf, "%d" % v >> 8 & 0xf if v & 0xf00 else ""), def csr_address(offset): return 0xfffff0000000 + 4 * offset dpp111_protocol_entries = { 0x38: lambda v: "command set spec id" + (": " + protocols[v][0] if v in protocols else ""), 0x39: lambda v: "command set" + {0xb081f2: ": DPC", 0x020000: ": FTC"}.get(v, ""), 0x3a: lambda v: "command set details", 0x3c: lambda v: "write transaction interval %dms" % (v), 0x3d: lambda v: "unit sw details: %s, sdu_write_order %d" % ( bcd_version_details(v), v & 1), 0x7b: lambda v: "connection CSR at %012x" % (csr_address(v)), 0xd4: lambda v: "command set directory", } iicp_protocol_entries = { 0x38: lambda v: "IICP details: %s" % (bcd_version_details(v)), 0x39: lambda v: "command set spec id" + (": " + protocols[v][0] if v in protocols else ""), 0x3a: lambda v: "command set" + {0x4b661f: ": IICP only", 0xc27f10: ": IICP488"}.get(v, ""), 0x3b: lambda v: "command set details: %s" % (bcd_version_details(v)), 0x3d: lambda v: "IICP capabilities: hl proto %d, IICP %d, " \ "ccli %d, cmgr %d, maxIntLength %s" % ( v >> 16, v >> 6 & 0x3f, v >> 5 & 1, v >> 4 & 1, "%d bytes" % (2 << (v & 0xf)) if v & 0xf else "-"), 0x7c: lambda v: "connection CSR at %012x" % (csr_address(v)), 0x7e: lambda v: "interrupt_enable CSR at %012x" % (csr_address(v)), 0x7f: lambda v: "interrupt_handlr CSR at %012x" % (csr_address(v)), } iidc104_protocol_entries = { 0x40: lambda v: "command_regs_base at %012x" % (csr_address(v)), 0x81: lambda v: "vendor name leaf", 0x82: lambda v: "model name leaf", } iidc131_protocol_entries = { 0x38: lambda v: "unit sub sw version v1.3%d" % (v >> 4), 0x39: lambda v: "(reserved)", 0x3a: lambda v: "(reserved)", 0x3b: lambda v: "(reserved)", 0x3c: lambda v: "vendor_unique_info_0", 0x3d: lambda v: "vendor_unique_info_1", 0x3e: lambda v: "vendor_unique_info_2", 0x3f: lambda v: "vendor_unique_info_3", 0x40: lambda v: "command_regs_base at %012x" % (csr_address(v)), 0x81: lambda v: "vendor name leaf", 0x82: lambda v: "model name leaf", } sbp3_protocol_entries = { 0x14: lambda v: "logical unit number: %sordered %d, %s" \ "type %s, lun %04x" % ( ("", "extended_status 1, ")[v >> 23 & 1], v >> 22 & 1, ("", "isoch 1, ")[v >> 21 & 1], { 0x00: "Disk", 0x01: "Tape", 0x02: "Printer", 0x03: "Processor", 0x04: "WORM", 0x05: "CD/DVD", 0x06: "Scanner", 0x07: "MOD", 0x08: "Changer", 0x09: "Comm", 0x0a: "Prepress", 0x0b: "Prepress", 0x0c: "RAID", 0x0d: "Enclosure", 0x0e: "RBC", 0x0f: "OCRW", 0x10: "Bridge", 0x11: "OSD", 0x12: "ADC-2", 0x1e: "w.k.LUN", 0x1f: "unnown", }.get(v >> 16 & 0x1f, "%02x?" % (v >> 16 & 0x1f)), v & 0xffff), 0x21: lambda v: "revision %d%s" % (v, (" = SBP-2", " = SBP-3")[v] if v < 2 else ""), 0x32: lambda v: "/ SBP-3 plug control register: %sPCR, " \ "plug_index %d" % (("i", "o")[v >> 5 & 1], v & 0x1f), 0x38: lambda v: "command set spec id" + (": " + protocols[v][0] if v in protocols else ""), 0x39: lambda v: "command set" + { 0x0104d8: ": SCSI Primary Commands 2 and related standards", 0x010001: ": AV/C", }.get(v, ""), 0x3a: lambda v: "unit char.: %smgt_ORB_timeout %gs, ORB_size " \ "%d quadlets" % (("", "distrib. data 1, ")[v >> 16 & 1], (v >> 8 & 0xff) * .5, v & 0xff), 0x3b: lambda v: "command set revision", 0x3c: lambda v: "firmware revision %06x" % (v), 0x3d: lambda v: "reconnect timeout: max_reconnect_hold %ds" % ((v & 0xffff) + 1), 0x3e: lambda v: "/ SBP-3 fast start: max_payload " + ( "%d bytes" % (v >> 8 << 2) if v & 0xff00 else "per max_rec") + (", offset %d" % (v & 0xff)), 0x54: lambda v: "management agent CSR at %012x" % (csr_address(v)), 0x8d: lambda v: "unit unique id", 0xd4: lambda v: "logical unit directory", } protocols = { 0x00005e: ("IANA", { 0x000001: ("IPv4 over 1394 (RFC 2734)", {}), 0x000002: ("IPv6 over 1394 (RFC 3146)", {}), }), 0x00609e: ("INCITS", { 0x010483: ("SBP-2", sbp3_protocol_entries), 0x0105bb: ("AV/C over SBP-3", sbp3_protocol_entries), }), 0x00a02d: ("1394 TA", { 0x010001: ("AV/C", {}), 0x010002: ("CAL", {}), 0x010004: ("EHS", {}), 0x010008: ("HAVi", {}), 0x014000: ("Vender Unique", {}), 0x014001: ("Vender Unique and AV/C", {}), 0x000100: ("IIDC 1.04", iidc104_protocol_entries), 0x000101: ("IIDC 1.20", iidc104_protocol_entries), 0x000102: ("IIDC 1.30", iidc131_protocol_entries), 0x0A6BE2: ("DPP 1.0", dpp111_protocol_entries), 0x4B661F: ("IICP 1.0", iicp_protocol_entries), }), } def proto_of(spec, ver): return protocols[spec][1][ver] \ if spec in protocols and ver in protocols[spec][1] \ else (None, {}) def oui_of(ouidb, dummy, v): return ": " + protocols[v][0] if v in protocols \ else ": " + ouidb[v] if v in ouidb \ else "" ieee1212_key_ids2 = { 0x03: oui_of, 0x0c: lambda ouidb, spec, v: " per IEEE 1394" if v & 0x83c0 == 0x83c0 \ else "", 0x12: oui_of, 0x13: lambda ouidb, spec, v: ": " + protocols[spec][1][v][0] \ if spec in protocols and v in protocols[spec][1] \ else "", 0x1c: oui_of, } ieee1212_key_ids = { 0x01: "descriptor", 0x02: "bus dependent info", 0x03: "vendor", 0x04: "hardware version", 0x07: "module", 0x0c: "node capabilities", 0x0d: "eui-64", 0x11: "unit", 0x12: "specifier id", 0x13: "version", 0x14: "dependent info", 0x15: "unit location", 0x17: "model", 0x18: "instance", 0x19: "keyword", 0x1a: "feature", 0x1b: "extended rom", 0x1c: "extended key specifier id", 0x1d: "extended key", 0x1e: "extended data", 0x1f: "modifiable descriptor", 0x20: "directory id", 0x21: "revision", } ieee1212_types = { 0: "immediate", 1: "csr offset", 2: "leaf", 3: "directory", } def write_directory(f, ouidb, rom, blocks, o, i, end, context): dummy, dummy, dummy, spec, ver = context proto = proto_of(spec, ver) while i <= end: r = rom[i] t = r >> 30 k = r >> 24 & 0x3f v = r & 0xffffff if t == 0: if k == 0x12: spec = v proto = proto_of(spec, ver) elif k == 0x13: ver = v proto = proto_of(spec, ver) headline = proto[0] + " " + proto[1][k](v) if k in proto[1] \ else ieee1212_key_ids[k] + \ ieee1212_key_ids2[k](ouidb, spec, v) if k in ieee1212_key_ids2 \ else ieee1212_key_ids[k] if k in ieee1212_key_ids \ else "(immediate value)" elif t == 1: headline = proto[0] + " " + proto[1][r >> 24](v) if r >> 24 in proto[1] \ else "CSR at %012x" % (csr_address(v)) else: headline = proto[0] + " " + proto[1][r >> 24](v) + " at %x" % \ (o + 4 * v) if r >> 24 in proto[1] \ else "%s%s at %x" % \ (ieee1212_key_ids[k] + " " if k in ieee1212_key_ids else "", ieee1212_types[t], o + 4 * v) blocks[i + v] = (headline, t, k, spec, ver) f.write("%x %08x %s%s\n" % (o, r, "--> " if t else "", headline)) i += 1 o += 4 return i def write_eui64_hi(f, ouidb, rom, o, i): hi = rom[i] f.write("%x %08x company_id %06x | %s\n" % (o, hi, hi >> 8, ouidb.get(hi >> 8, ""))) def write_eui64_lo(f, ouidb, rom, o, i): hi = rom[i - 1] lo = rom[i] f.write("%x %08x device_id %02x%08x | EUI-64 %08x%08x\n" % (o, lo, hi & 0xff, lo, hi, lo)) def write_leaf(f, ouidb, rom, o, i, end, context): dummy, dummy, key_id, spec, ver = context descriptor_type_and_spec = None is_minimal_ascii = None j = 0 while i <= end: r = rom[i] if spec == 0x00a02d and ver in (0x000100, 0x000101, 0x000102) and \ key_id in (0x01, 0x02) and j < 2: # IIDC vendor or model name is_minimal_ascii = j and not r and not rom[i - 1]; f.write("%x %08x\n" % (o, r)) elif key_id == 0x01 and j == 0: # descriptor leaf, general header descriptor_type_and_spec = r if descriptor_type_and_spec == 0x00000000: f.write("%x %08x textual descriptor\n" % (o, r)) elif descriptor_type_and_spec == 0x01000000: f.write("%x %08x icon descriptor\n" % (o, r)) else: f.write("%x %08x descriptor_type %02x, specifier_ID %x\n" % (o, r, r >> 24, r & 0xffffff)) elif key_id == 0x1f: # modifiable descriptor if j == 0: f.write("%x %08x max_descriptor_size %d, " "descriptor_address_hi %d\n" % (o, r, r >> 16, r & 0xffff)) elif j == 1: f.write("%x %08x descriptor_address_lo %d\n" % (o, r, r)) elif (key_id == 0x07 or # primary node unique ID leaf key_id == 0x0d) and j < 2: # eui-64 leaf (write_eui64_hi, write_eui64_lo)[j](f, ouidb, rom, o, i) elif key_id == 0x19: # keyword leaf f.write("%x %08x %s\n" % (o, r, u32_to_string(r))) elif descriptor_type_and_spec == 0 and j == 1: # textual descriptor is_minimal_ascii = r >> 16 == 0 if is_minimal_ascii: f.write("%x %08x minimal ASCII\n" % (o, r)) else: f.write("%x %08x width %d, character_set %d, language %s\n" % (o, r, r >> 28, r >> 16 & 0xfff, language(r))) elif is_minimal_ascii: f.write("%x %08x %s\n" % (o, r, u32_to_string(r))) else: f.write("%x %08x\n" % (o, r)) i += 1 j += 1 o += 4 return i def write_block(f, ouidb, rom, blocks, i, context): headline, t, k, spec, ver = context r = rom[i] o = 0x400 + i * 4 l = r >> 16 c = r & 0xffff crc = crc16(rom, i + 1, l) f.write(" %s\n" " -----------------------------------------------------------------\n" "%x %08x %s_length %d, crc %d%s\n" % (headline, o, r, ieee1212_types[t], l, c, " (should be %d)" % (crc) if crc <> c else "")) end = min(i + l, 255) i += 1 o += 4 if t == 3: i = write_directory(f, ouidb, rom, blocks, o, i, end, context) else: i = write_leaf(f, ouidb, rom, o, i, end, context) return i def write_bus_info_block(f, ouidb, rom, blocks): f.write(" ROM header and bus information block\n" " -----------------------------------------------------------------\n") r = rom[0] bib_len = r >> 24 crc_len = r >> 16 & 0xff c = r & 0xffff crc = crc16(rom, 1, crc_len) f.write("400 %08x bus_info_length %d, crc_length %d, crc %d%s\n" % (r, bib_len, crc_len, c, " (should be %d)" % (crc) if crc <> c else "")) r = rom[1] f.write("404 %08x bus_name %s\n" % (r, u32_to_string(r))) if r == 0x31333934: r = rom[2] gen = r >> 4 & 0xf if gen: f.write("408 %08x irmc %d, cmc %d, isc %d, bmc %d, pmc %d, " "cyc_clk_acc %d,\n max_rec %d (%d), " "max_rom %d, gen %d, spd %d (S%d00)\n" % (r, r >> 31, r >> 30 & 1, r >> 29 & 1, r >> 28 & 1, r >> 27 & 1, r >> 16 & 0xff, r >> 12 & 0xf, 2 << (r >> 12 & 0xf), r >> 8 & 3, gen, r & 7, 1 << (r & 7))) else: f.write("408 %08x irmc %d, cmc %d, isc %d, bmc %d, " "cyc_clk_acc %d, max_rec %d (%d)\n" % (r, r >> 31, r >> 30 & 1, r >> 29 & 1, r >> 28 & 1, r >> 16 & 0xff, r >> 12 & 0xf, 2 << (r >> 12 & 0xf))) else: f.write("408 %08x bus-dependent information\n" % (rom[2])) write_eui64_hi(f, ouidb, rom, 0x40c, 3) write_eui64_lo(f, ouidb, rom, 0x410, 4) i = 5 while i <= bib_len: f.write("%x %08x bus-dependent information\n" % (0x400 + i * 4, rom[i])) i += 1 blocks[i] = ("root directory", 3, None, None, None) return i def write_config_rom(f, ouidb, rom): blocks = {} i = write_bus_info_block(f, ouidb, rom, blocks) f.write("\n") need_linefeed = False while i < 256: if i in blocks: if need_linefeed: f.write("\n") i = write_block(f, ouidb, rom, blocks, i, blocks[i]) f.write("\n") need_linefeed = False elif rom[i]: f.write("%x %08x (unreferenced data)\n" % (0x400 + i * 4, rom[i])) need_linefeed = True i += 1 else: if need_linefeed: f.write("\n") need_linefeed = False i += 1 if need_linefeed: f.write("\n") def read_le32_data(s, l, rom): for i in xrange(0, l / 4): j = i * 4 rom[i] = (ord(s[j ]) ) + \ (ord(s[j + 1]) << 8) + \ (ord(s[j + 2]) << 16) + \ (ord(s[j + 3]) << 24) def read_be32_data(s, l, rom): for i in xrange(0, l / 4): j = i * 4 rom[i] = (ord(s[j ]) << 24) + \ (ord(s[j + 1]) << 16) + \ (ord(s[j + 2]) << 8) + \ (ord(s[j + 3]) ) def read_log_data(s, rom): i = -1 for line in s.splitlines(): # fixme: should take node IDs and transaction labels into account if line.find("firewire_ohci: AT spd ") >= 0 and \ line.find(", ack_pending , QR req, fffff0000") >= 0: j = int(line[-12:], 16) if j < 0xfffff0000400 or j >= 0xfffff0000800: i = -1 continue i = (j - 0xfffff0000400) / 4 if i == 0: rom[:] = [0] * 256 continue if line.find("firewire_ohci: AR spd ") >= 0 and \ line.find(", ack_complete, QR resp = ") >= 0 and \ i > -1: rom[i] = int(line[-8:], 16) def build_config_rom(f): rom = [0] * 256 s = f.read() l = len(s) may_be_binary = l > 20 and l <= 1024 and l & 3 == 0 if may_be_binary and s[4:8] == "4931": read_le32_data(s, l, rom) elif may_be_binary and s[4:8] == "1394": read_be32_data(s, l, rom) else: read_log_data(s, rom) return rom def build_oui24_db(paths): for p in paths: try: return dict(map(lambda l: (int(l[:6], 16), l[7:-1]), file(p, "r"))) except (IOError, ValueError): continue # fallback: specifiers from the protocols dictionary return dict(map(lambda proto: (proto[0], proto[1][0]), protocols.iteritems())) def main(): import sys rom = build_config_rom(sys.stdin) if not rom[0] >> 24: return "Nothing read." ouidb = build_oui24_db(ouidb_search_paths) write_config_rom(sys.stdout, ouidb, rom) if __name__ == "__main__": import sys sys.exit(main())