Introduce utility for parsing CBMEM contents.
[coreboot.git] / util / cbmem / cbmem.py
diff --git a/util/cbmem/cbmem.py b/util/cbmem/cbmem.py
new file mode 100755 (executable)
index 0000000..3e8476d
--- /dev/null
@@ -0,0 +1,204 @@
+#!/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 re
+import struct
+import sys
+import time
+
+# These definitions follow src/include/cbmem.h
+CBMEM_MAGIC = 0x434f5245
+CBMEM_MAX_ENTRIES = 16
+
+CBMEM_ENTRY_FORMAT = '@LLQQ'
+CONSOLE_HEADER_FORMAT = '@LL'
+TIMESTAMP_HEADER_FORMAT = '@QLL'
+TIMESTAMP_ENTRY_FORMAT = '@LQ'
+
+mf_fileno = 0  # File number of the file providing access to memory.
+
+def align_up(base, alignment):
+    '''Increment to the alignment boundary.
+
+    Return the next integer larger than 'base' and divisible by 'alignment'.
+    '''
+
+    return base + alignment - base % alignment
+
+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 get_mem_size():
+    '''Retrieve amount of memory available to the CPU from /proc/meminfo.'''
+    mult = {
+        'kB': 1024
+        }
+    meminfo = open('/proc/meminfo').read()
+    m = re.search('MemTotal:.*\n', meminfo)
+    mem_string = re.search('MemTotal:.*\n', meminfo).group(0)
+    (_, size, mult_name) = mem_string.split()
+    return int(size) * mult[mult_name]
+
+def parse_mem_at(addr, format):
+    '''Read and parse a memory location.
+
+    This function reads memory at the passed in address, parses it according
+    to the passed in format specification and returns a list of values.
+
+    The first value in the list is the size of data matching the format
+    expression, and the rest of the elements of the list are the actual values
+    retrieved using the format.
+    '''
+
+    size = struct.calcsize(format)
+    delta = addr % 4096   # mmap requires the offset to be page size aligned.
+    mm = mmap.mmap(mf_fileno, size + delta,
+                   mmap.MAP_PRIVATE, offset=(addr - delta))
+    buf = mm.read(size + delta)
+    mm.close()
+    rv = [size,] + list(struct.unpack(format, buf[delta:size + delta + 1]))
+    return rv
+
+def dprint(text):
+    '''Debug print function.
+
+    Edit it to get the debug output.
+    '''
+
+    if False:
+        print text
+
+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.
+    '''
+
+    (step, base_time, max_entr, entr) = parse_mem_at(
+        base, TIMESTAMP_HEADER_FORMAT)
+
+    print('\ntime base %d, total entries %d' % (base_time, entr))
+    clock_freq = get_cpu_freq()
+    base = base + step
+    for i in range(entr):
+        (step, timer_id, timer_value) = parse_mem_at(
+            base, TIMESTAMP_ENTRY_FORMAT)
+        print '%d:%s ' % (timer_id, normalize_timer(timer_value, clock_freq)),
+        base = base + step
+    print
+
+def process_console(base):
+    '''Dump the console log buffer contents found at address base.'''
+
+    (step, size, cursor) = parse_mem_at(base, CONSOLE_HEADER_FORMAT)
+    print 'cursor at %d\n' % cursor
+
+    cons_string_format = '%ds' % min(cursor, size)
+    (_, cons_text) = parse_mem_at(base + step, cons_string_format)
+    print cons_text
+    print '\n'
+
+mem_alignment = 1024 * 1024 * 1024 # 1 GBytes
+table_alignment = 128 * 1024
+
+mem_size = get_mem_size()
+
+# start at memory address aligned at 128K.
+offset = align_up(mem_size, table_alignment)
+
+dprint('mem_size %x offset %x' %(mem_size, offset))
+mf = open("/dev/mem")
+mf_fileno = mf.fileno()
+
+while offset % mem_alignment: # do not cross the 1G boundary while searching
+    (step, magic, mid, base, size) = parse_mem_at(offset, CBMEM_ENTRY_FORMAT)
+    if magic == CBMEM_MAGIC:
+        offset = offset + step
+        break
+    offset += table_alignment
+else:
+    print 'Did not find the CBMEM'
+    sys.exit(0)
+
+for i in (range(1, CBMEM_MAX_ENTRIES)):
+    (_, magic, mid, base, size) = parse_mem_at(offset, CBMEM_ENTRY_FORMAT)
+    if mid == 0:
+        break
+
+    print '%x, %x, %x' % (mid, base, size)
+    if mid == 0x54494d45:
+        process_timers(base)
+    if mid == 0x434f4e53:
+        process_console(base)
+
+    offset = offset + step
+
+mf.close()