260 lines
7.9 KiB
Python
Executable File
260 lines
7.9 KiB
Python
Executable File
#!/usr/bin/python
|
|
#
|
|
# cbmem.py - Linux space CBMEM contents parser
|
|
#
|
|
# Copyright (C) 2011 The ChromiumOS Authors. All rights reserved.
|
|
#
|
|
# 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; version 2 of the License
|
|
#
|
|
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
#
|
|
'''
|
|
Parse and display CBMEM contents.
|
|
|
|
This module is meant to run on systems with coreboot based firmware.
|
|
|
|
When started, it determines the amount of DRAM installed on the system, and
|
|
then scans the top area of DRAM (right above the available memory size)
|
|
looking for the CBMEM base signature at locations aligned at 0x20000
|
|
boundaries.
|
|
|
|
Once it finds the CBMEM signature, the utility parses the contents, reporting
|
|
the section IDs/sizes and also reporting the contents of the tiemstamp and
|
|
console sections.
|
|
'''
|
|
|
|
import mmap
|
|
import struct
|
|
import sys
|
|
|
|
def get_phys_mem(addr, size):
|
|
'''Read size bytes from address addr by mmaping /dev/mem'''
|
|
|
|
mf = open("/dev/mem")
|
|
delta = addr % 4096
|
|
mm = mmap.mmap(mf.fileno(), size + delta,
|
|
mmap.MAP_PRIVATE, offset=(addr - delta))
|
|
buf = mm.read(size + delta)
|
|
mf.close()
|
|
return buf[delta:]
|
|
|
|
# This class and metaclass make it easier to define and access structures
|
|
# which live in physical memory. To use them, inherit from CStruct and define
|
|
# a class member called struct_members which is a tuple of pairs. The first
|
|
# item in the pair is the type format specifier that should be used with
|
|
# struct.unpack to read that member from memory. The second item is the name
|
|
# that member should have in the resulting object.
|
|
|
|
class MetaCStruct(type):
|
|
def __init__(cls, name, bases, dct):
|
|
struct_members = dct["struct_members"]
|
|
cls.struct_fmt = "<"
|
|
for char, name in struct_members:
|
|
cls.struct_fmt += char
|
|
cls.struct_len = struct.calcsize(cls.struct_fmt)
|
|
super(MetaCStruct, cls).__init__(name, bases, dct)
|
|
|
|
class CStruct(object):
|
|
__metaclass__ = MetaCStruct
|
|
struct_members = ()
|
|
|
|
def __init__(self, addr):
|
|
self.raw_memory = get_phys_mem(addr, self.struct_len)
|
|
values = struct.unpack(self.struct_fmt, self.raw_memory)
|
|
names = (name for char, name in self.struct_members)
|
|
for name, value in zip(names, values):
|
|
setattr(self, name, value)
|
|
|
|
def normalize_timer(value, freq):
|
|
'''Convert timer reading into microseconds.
|
|
|
|
Get the free running clock counter value, divide it by the clock frequency
|
|
and multiply by 1 million to get reading in microseconds.
|
|
|
|
Then convert the value into an ASCII string with groups of three digits
|
|
separated by commas.
|
|
|
|
Inputs:
|
|
value: int, the clock reading
|
|
freq: float, the clock frequency
|
|
|
|
Returns:
|
|
A string presenting 'value' in microseconds.
|
|
'''
|
|
|
|
result = []
|
|
value = int(value * 1000000.0 / freq)
|
|
svalue = '%d' % value
|
|
vlength = len(svalue)
|
|
remainder = vlength % 3
|
|
if remainder:
|
|
result.append(svalue[0:remainder])
|
|
while remainder < vlength:
|
|
result.append(svalue[remainder:remainder+3])
|
|
remainder = remainder + 3
|
|
return ','.join(result)
|
|
|
|
def get_cpu_freq():
|
|
'''Retrieve CPU frequency from sysfs.
|
|
|
|
Use /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq as the source.
|
|
'''
|
|
freq_str = open('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq'
|
|
).read()
|
|
# Convert reading into Hertz
|
|
return float(freq_str) * 1000.0
|
|
|
|
def process_timers(base):
|
|
'''Scan the array of timestamps found in CBMEM at address base.
|
|
|
|
For each timestamp print the timer ID and the value in microseconds.
|
|
'''
|
|
|
|
class TimestampHeader(CStruct):
|
|
struct_members = (
|
|
("Q", "base_time"),
|
|
("L", "max_entr"),
|
|
("L", "entr")
|
|
)
|
|
|
|
class TimestampEntry(CStruct):
|
|
struct_members = (
|
|
("L", "timer_id"),
|
|
("Q", "timer_value")
|
|
)
|
|
|
|
header = TimestampHeader(base)
|
|
print('\ntime base %d, total entries %d' % (header.base_time, header.entr))
|
|
clock_freq = get_cpu_freq()
|
|
base = base + header.struct_len
|
|
prev_time = 0
|
|
for i in range(header.entr):
|
|
timestamp = TimestampEntry(base)
|
|
print '%d:%s ' % (timestamp.timer_id,
|
|
normalize_timer(timestamp.timer_value, clock_freq)),
|
|
if prev_time:
|
|
print '(%s)' % normalize_timer(
|
|
timestamp.timer_value - prev_time, clock_freq),
|
|
prev_time = timestamp.timer_value
|
|
print
|
|
base = base + timestamp.struct_len
|
|
print
|
|
|
|
def process_console(base):
|
|
'''Dump the console log buffer contents found at address base.'''
|
|
|
|
class ConsoleHeader(CStruct):
|
|
struct_members = (
|
|
("L", "size"),
|
|
("L", "cursor")
|
|
)
|
|
|
|
header = ConsoleHeader(base)
|
|
print 'cursor at %d\n' % header.cursor
|
|
|
|
cons_addr = base + header.struct_len
|
|
cons_length = min(header.cursor, header.size)
|
|
cons_text = get_phys_mem(cons_addr, cons_length)
|
|
print cons_text
|
|
print '\n'
|
|
|
|
def ipchksum(buf):
|
|
'''Checksumming function used on the coreboot tables. The buffer being
|
|
checksummed is summed up as if it was an array of 16 bit unsigned
|
|
integers. If there are an odd number of bytes, the last element is zero
|
|
extended.'''
|
|
|
|
size = len(buf)
|
|
odd = size % 2
|
|
fmt = "<%dH" % ((size - odd) / 2)
|
|
if odd:
|
|
fmt += "B"
|
|
shorts = struct.unpack(fmt, buf)
|
|
checksum = sum(shorts)
|
|
checksum = (checksum >> 16) + (checksum & 0xffff)
|
|
checksum += (checksum >> 16)
|
|
checksum = ~checksum & 0xffff
|
|
return checksum
|
|
|
|
def parse_tables(base, length):
|
|
'''Find the coreboot tables in memory and process whatever we can.'''
|
|
|
|
class CBTableHeader(CStruct):
|
|
struct_members = (
|
|
("4s", "signature"),
|
|
("I", "header_bytes"),
|
|
("I", "header_checksum"),
|
|
("I", "table_bytes"),
|
|
("I", "table_checksum"),
|
|
("I", "table_entries")
|
|
)
|
|
|
|
class CBTableEntry(CStruct):
|
|
struct_members = (
|
|
("I", "tag"),
|
|
("I", "size")
|
|
)
|
|
|
|
class CBTableForward(CBTableEntry):
|
|
struct_members = CBTableEntry.struct_members + (
|
|
("Q", "forward"),
|
|
)
|
|
|
|
class CBMemTab(CBTableEntry):
|
|
struct_members = CBTableEntry.struct_members + (
|
|
("L", "cbmem_tab"),
|
|
)
|
|
|
|
for addr in range(base, base + length, 16):
|
|
header = CBTableHeader(addr)
|
|
if header.signature == "LBIO":
|
|
break
|
|
else:
|
|
return -1
|
|
|
|
if header.header_bytes == 0:
|
|
return -1
|
|
|
|
if ipchksum(header.raw_memory) != 0:
|
|
print "Bad header checksum"
|
|
return -1
|
|
|
|
addr += header.header_bytes
|
|
table = get_phys_mem(addr, header.table_bytes)
|
|
if ipchksum(table) != header.table_checksum:
|
|
print "Bad table checksum"
|
|
return -1
|
|
|
|
for i in range(header.table_entries):
|
|
entry = CBTableEntry(addr)
|
|
if entry.tag == 0x11: # Forwarding entry
|
|
return parse_tables(CBTableForward(addr).forward, length)
|
|
elif entry.tag == 0x16: # Timestamps
|
|
process_timers(CBMemTab(addr).cbmem_tab)
|
|
elif entry.tag == 0x17: # CBMEM console
|
|
process_console(CBMemTab(addr).cbmem_tab)
|
|
|
|
addr += entry.size
|
|
|
|
return 0
|
|
|
|
def main():
|
|
for base, length in (0x00000000, 0x1000), (0x000f0000, 0x1000):
|
|
if parse_tables(base, length):
|
|
break
|
|
else:
|
|
print "Didn't find the coreboot tables"
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|