summaryrefslogtreecommitdiff
path: root/util/cbmem/cbmem.py
blob: 3e8476dbec36d68e2ab1d32b4d08fb55e71f5629 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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()