Skip to content

Instantly share code, notes, and snippets.

@andy-shev
Last active Nov 25, 2020
Embed
What would you like to do?
Dumb SFI table dumper
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: ts=4 sw=4 et ai si
from __future__ import print_function
import operator
import os
import struct
import sys
TABLE_HEADER = [
['signature', '4s'],
['length', 'I'],
['revision', 'B'],
['checksum', 'B'],
['oemid', '6s'],
['oemtableid', '8s'],
]
# Registered device types
DEVS_TYPES = [
'SPI',
'I2C',
'UART',
'HSI',
'IPC',
'SD',
'xxx',
]
TABLE_APIC_ENTRY = [
['phys', 'Q'],
]
TABLE_CPUS_ENTRY = [
['id', 'I'],
]
TABLE_DEVS_ENTRY = [
['type', 'B'],
['num', 'B'],
['addr', 'H'],
['irq', 'B'],
['freq', 'I'],
['name', '16s'],
]
TABLE_FREQ_ENTRY = [
['freq', 'I'],
['latency', 'I'],
['value', 'I'],
]
TABLE_GPIO_ENTRY = [
['controller', '16s'],
['pin', 'H'],
['name', '16s'],
]
TABLE_MTMR_ENTRY = [
['phys', 'Q'],
['freq', 'I'],
['irq', 'I'],
]
TABLE_MCFG_HEADER = [
['oemrevision', 'I'],
['creatorid', 'I'],
['creatorrevision', 'I'],
['reserved', 'Q'],
]
TABLE_MCFG_ENTRY = [
['base', 'Q'],
['segment', 'H'],
['start', 'B'],
['end', 'B'],
['reserved', 'I'],
]
MMAP_TYPES = [
'MEM RESERVED',
'LOADER CODE',
'LOADER DATA',
'BOOT SERVICE CODE',
'BOOT SERVICE DATA',
'RUNTIME SERVICE CODE',
'RUNTIME SERVICE DATA',
'MEM CONV',
'MEM UNUSABLE',
'ACPI RECLAIM',
'ACPI NVS',
'MEM MMIO',
'MEM IOPORT',
'PAL CODE',
'MEM TYPEMAX',
]
EFI_MEMORY_ATTRS = [
("UC", 0x0000000000000001),
("WC", 0x0000000000000002),
("WT", 0x0000000000000004),
("WB", 0x0000000000000008),
("UCE", 0x0000000000000010),
("WP", 0x0000000000001000),
("RP", 0x0000000000002000),
("XP", 0x0000000000004000),
("NV", 0x0000000000008000),
("MORE_RELIABLE", 0x0000000000010000),
("RO", 0x0000000000020000),
("RUNTIME", 0x8000000000000000),
]
TABLE_EFI_MEMORY_DESCRIPTOR = [
['type', 'I'],
['physical', 'Q'],
['virtual', 'Q'],
['pages', 'Q'],
['attribute', 'Q'],
]
TABLE_ENTRIES = {
'APIC': TABLE_APIC_ENTRY,
'CPUS': TABLE_CPUS_ENTRY,
'DEVS': TABLE_DEVS_ENTRY,
'FREQ': TABLE_FREQ_ENTRY,
'GPIO': TABLE_GPIO_ENTRY,
'MTMR': TABLE_MTMR_ENTRY,
'MCFG': TABLE_MCFG_ENTRY,
'MMAP': TABLE_EFI_MEMORY_DESCRIPTOR,
}
def get_format_str(desc):
return '<' + ''.join(operator.itemgetter(1)(field) for field in desc)
def get_string_from_field(field):
return field.split(b'\0')[0].decode()
def parse_APIC(table):
fmt = get_format_str(TABLE_APIC_ENTRY)
size = struct.calcsize(fmt)
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
print("\tAPIC Physical address 0x%016x" % d[0])
table = table[size:]
def parse_CPUS(table):
fmt = get_format_str(TABLE_CPUS_ENTRY)
size = struct.calcsize(fmt)
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
print("\tAPIC ID 0x%08x" % d[0])
table = table[size:]
def parse_DEVS(table):
fmt = get_format_str(TABLE_DEVS_ENTRY)
size = struct.calcsize(fmt)
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
print("\t[%s]\t0x%02x:%04x\tIRQ:%d\t%10dHz\t%-16s" % (DEVS_TYPES[d[0]], d[1], d[2], d[3], d[4], get_string_from_field(d[5])))
table = table[size:]
def parse_FREQ(table):
fmt = get_format_str(TABLE_FREQ_ENTRY)
size = struct.calcsize(fmt)
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
print("\tTransition to %u MHz in %u us: 0x%08x" % (d[0], d[1], d[2]))
table = table[size:]
def parse_GPIO(table):
fmt = get_format_str(TABLE_GPIO_ENTRY)
size = struct.calcsize(fmt)
gpio = {}
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
controller = get_string_from_field(d[0])
if controller not in gpio:
gpio[controller] = []
gpio[controller].append(d[1:3])
table = table[size:]
for controller in gpio:
print("\t%s:" % controller)
gpio[controller].sort(key=operator.itemgetter(0))
for entry in gpio[controller]:
print("\t\t%3d\t%s" % (entry[0], get_string_from_field(entry[1])))
def parse_MTMR(table):
fmt = get_format_str(TABLE_MTMR_ENTRY)
size = struct.calcsize(fmt)
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
print("\tTimer 0x%016x, %uHz, IRQ: %u" % (d[0], d[1], d[2]))
table = table[size:]
def get_efi_memory_attr(attribute):
result = []
for k, v in EFI_MEMORY_ATTRS:
if attribute & v:
result.append(k)
return '|'.join(result)
def parse_MCFG(table):
# Skip header
fmt = get_format_str(TABLE_MCFG_HEADER)
size = struct.calcsize(fmt)
table = table[size:]
fmt = get_format_str(TABLE_MCFG_ENTRY)
size = struct.calcsize(fmt)
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
attr = get_efi_memory_attr(d[4])
print("\t%04x [bus%02x-%02x]\t%016x" % (d[1], d[2], d[3], d[0]))
table = table[size:]
def parse_MMAP(table):
fmt = get_format_str(TABLE_EFI_MEMORY_DESCRIPTOR)
size = struct.calcsize(fmt)
while len(table) >= size:
d = struct.unpack(fmt, table[:size])
attr = get_efi_memory_attr(d[4])
print("\t%016x-%016x\t[%s]\t%s" % (d[1], d[1] + d[3] * 4096 - 1, attr, MMAP_TYPES[d[0]]))
table = table[size:]
# https://codereview.stackexchange.com/a/161620
def hexdump(data, base=0, offset=0, rowsize=16, dumpascii=True):
not_shown = [' ']
leader = (base + offset) % rowsize
next_n = offset - leader + rowsize
while data[offset:]:
col0 = '%08x' % (base + offset - leader)
col1 = not_shown * leader
col2 = ' ' * leader
leader = 0
for i in bytearray(data[offset:next_n]):
col1 += ['%02x' % i]
col2 += chr(i) if 0x1f < i < 0x7f else '.'
trailer = rowsize - len(col1)
if trailer:
col1 += not_shown * trailer
col2 += ' ' * trailer
if dumpascii:
yield ' '.join((col0, ' '.join(col1), '', col2))
else:
yield ' '.join((col0, ' '.join(col1)))
offset = next_n
next_n += rowsize
MODULE = sys.modules[globals()['__name__']]
def list_parse_functions():
""" List supported parsers """
for attr in dir(MODULE):
if attr.startswith('parse_'):
yield attr.replace('parse_', '')
def do_parse(name, table):
""" Get parse function by its name """
for func in list_parse_functions():
if func.upper() == name.upper():
return getattr(MODULE, "parse_" + func.upper())(table)
print("Don't know how to parse %s..." % name)
print('\n'.join(hexdump(table, 0x18)))
def parse_HEADER(table):
fmt = get_format_str(TABLE_HEADER)
header = list(struct.unpack(fmt, table[:struct.calcsize(fmt)]))
for idx in (0, 4, 5):
header[idx] = get_string_from_field(header[idx])
print("Parsing table %s for %s %s:" % (header[0], header[4], header[5]))
table = table[struct.calcsize(fmt):]
do_parse(header[0], table)
def usage():
return "Usage: %s <TABLE>" % os.path.basename(sys.argv[0])
def parse_one_table(filename):
data = open(filename, 'rb').read()
parse_HEADER(data)
def main(argv):
"""MAIN."""
if len(argv) < 2:
raise SystemExit(usage())
pathname = os.path.realpath(argv[1])
if os.path.isfile(pathname):
parse_one_table(pathname)
elif os.path.isdir(pathname):
for root, dummy, files in os.walk(pathname):
for table in files:
parse_one_table(os.path.join(root, table))
if __name__ == '__main__':
sys.exit(main(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment