#!/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 and writes a human-readable annotated representation to stdout. The data may be - binary data, i.e. a big endian or little endian quadlet array, - a firewire-ohci debug log, - read results from the tool firecontrol. 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 - Read the configuration ROM by means of firecontrol. The example assumes that the device has the node ID ffc0 and that it is attached to the first of all installed FireWire controllers: { for i in {4,5,6,7}{0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f}{0,4,8,c}; do echo "r . 0 0xfffff0000$i 4"; done } | firecontrol | crpp 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 2010-04-25: added firecontrol input """ 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_firecontrol_output(s, rom): i = -1 for line in s.splitlines(): # fixme: should check that node IDs stay the same and no resets happened if line[0:18] == "reading from node ": j = int(line[-20:-8], 16) if j < 0xfffff0000400 or j >= 0xfffff0000800: i = -1 continue i = (j - 0xfffff0000400) / 4 if i == 0: rom[:] = [0] * 256 continue if len(line) == 11 and \ line[2:3] == " " and line[5:6] == " " and line[8:9] == " " and \ i > -1: b1 = int(line[0:2], 16) b2 = int(line[3:5], 16) b3 = int(line[6:8], 16) b4 = int(line[9:11], 16) rom[i] = (b1 << 24) + (b2 << 16) + (b3 << 8) + b4 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) elif l > 20 and s[0:12] == "firecontrol ": read_firecontrol_output(s, 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())