3e8476dbec36d68e2ab1d32b4d08fb55e71f5629
[coreboot.git] / util / cbmem / cbmem.py
1 #!/usr/bin/python
2 #
3 # cbmem.py - Linux space CBMEM contents parser
4 #
5 # Copyright (C) 2011 The ChromiumOS Authors.  All rights reserved.
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 of the License
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 #
20 '''
21 Parse and display CBMEM contents.
22
23 This module is meant to run on systems with coreboot based firmware.
24
25 When started, it determines the amount of DRAM installed on the system, and
26 then scans the top area of DRAM (right above the available memory size)
27 looking for the CBMEM base signature at locations aligned at 0x20000
28 boundaries.
29
30 Once it finds the CBMEM signature, the utility parses the contents, reporting
31 the section IDs/sizes and also reporting the contents of the tiemstamp and
32 console sections.
33 '''
34
35 import mmap
36 import re
37 import struct
38 import sys
39 import time
40
41 # These definitions follow src/include/cbmem.h
42 CBMEM_MAGIC = 0x434f5245
43 CBMEM_MAX_ENTRIES = 16
44
45 CBMEM_ENTRY_FORMAT = '@LLQQ'
46 CONSOLE_HEADER_FORMAT = '@LL'
47 TIMESTAMP_HEADER_FORMAT = '@QLL'
48 TIMESTAMP_ENTRY_FORMAT = '@LQ'
49
50 mf_fileno = 0  # File number of the file providing access to memory.
51
52 def align_up(base, alignment):
53     '''Increment to the alignment boundary.
54
55     Return the next integer larger than 'base' and divisible by 'alignment'.
56     '''
57
58     return base + alignment - base % alignment
59
60 def normalize_timer(value, freq):
61     '''Convert timer reading into microseconds.
62
63     Get the free running clock counter value, divide it by the clock frequency
64     and multiply by 1 million to get reading in microseconds.
65
66     Then convert the value into an ASCII string with groups of three digits
67     separated by commas.
68
69     Inputs:
70       value: int, the clock reading
71       freq: float, the clock frequency
72
73     Returns:
74       A string presenting 'value' in microseconds.
75     '''
76
77     result = []
78     value = int(value * 1000000.0 / freq)
79     svalue = '%d' % value
80     vlength = len(svalue)
81     remainder = vlength % 3
82     if remainder:
83         result.append(svalue[0:remainder])
84     while remainder < vlength:
85         result.append(svalue[remainder:remainder+3])
86         remainder = remainder + 3
87     return ','.join(result)
88
89 def get_cpu_freq():
90     '''Retrieve CPU frequency from sysfs.
91
92     Use /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq as the source.
93     '''
94     freq_str = open('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq'
95                     ).read()
96     # Convert reading into Hertz
97     return float(freq_str) * 1000.0
98
99 def get_mem_size():
100     '''Retrieve amount of memory available to the CPU from /proc/meminfo.'''
101     mult = {
102         'kB': 1024
103         }
104     meminfo = open('/proc/meminfo').read()
105     m = re.search('MemTotal:.*\n', meminfo)
106     mem_string = re.search('MemTotal:.*\n', meminfo).group(0)
107     (_, size, mult_name) = mem_string.split()
108     return int(size) * mult[mult_name]
109
110 def parse_mem_at(addr, format):
111     '''Read and parse a memory location.
112
113     This function reads memory at the passed in address, parses it according
114     to the passed in format specification and returns a list of values.
115
116     The first value in the list is the size of data matching the format
117     expression, and the rest of the elements of the list are the actual values
118     retrieved using the format.
119     '''
120
121     size = struct.calcsize(format)
122     delta = addr % 4096   # mmap requires the offset to be page size aligned.
123     mm = mmap.mmap(mf_fileno, size + delta,
124                    mmap.MAP_PRIVATE, offset=(addr - delta))
125     buf = mm.read(size + delta)
126     mm.close()
127     rv = [size,] + list(struct.unpack(format, buf[delta:size + delta + 1]))
128     return rv
129
130 def dprint(text):
131     '''Debug print function.
132
133     Edit it to get the debug output.
134     '''
135
136     if False:
137         print text
138
139 def process_timers(base):
140     '''Scan the array of timestamps found in CBMEM at address base.
141
142     For each timestamp print the timer ID and the value in microseconds.
143     '''
144
145     (step, base_time, max_entr, entr) = parse_mem_at(
146         base, TIMESTAMP_HEADER_FORMAT)
147
148     print('\ntime base %d, total entries %d' % (base_time, entr))
149     clock_freq = get_cpu_freq()
150     base = base + step
151     for i in range(entr):
152         (step, timer_id, timer_value) = parse_mem_at(
153             base, TIMESTAMP_ENTRY_FORMAT)
154         print '%d:%s ' % (timer_id, normalize_timer(timer_value, clock_freq)),
155         base = base + step
156     print
157
158 def process_console(base):
159     '''Dump the console log buffer contents found at address base.'''
160
161     (step, size, cursor) = parse_mem_at(base, CONSOLE_HEADER_FORMAT)
162     print 'cursor at %d\n' % cursor
163
164     cons_string_format = '%ds' % min(cursor, size)
165     (_, cons_text) = parse_mem_at(base + step, cons_string_format)
166     print cons_text
167     print '\n'
168
169 mem_alignment = 1024 * 1024 * 1024 # 1 GBytes
170 table_alignment = 128 * 1024
171
172 mem_size = get_mem_size()
173
174 # start at memory address aligned at 128K.
175 offset = align_up(mem_size, table_alignment)
176
177 dprint('mem_size %x offset %x' %(mem_size, offset))
178 mf = open("/dev/mem")
179 mf_fileno = mf.fileno()
180
181 while offset % mem_alignment: # do not cross the 1G boundary while searching
182     (step, magic, mid, base, size) = parse_mem_at(offset, CBMEM_ENTRY_FORMAT)
183     if magic == CBMEM_MAGIC:
184         offset = offset + step
185         break
186     offset += table_alignment
187 else:
188     print 'Did not find the CBMEM'
189     sys.exit(0)
190
191 for i in (range(1, CBMEM_MAX_ENTRIES)):
192     (_, magic, mid, base, size) = parse_mem_at(offset, CBMEM_ENTRY_FORMAT)
193     if mid == 0:
194         break
195
196     print '%x, %x, %x' % (mid, base, size)
197     if mid == 0x54494d45:
198         process_timers(base)
199     if mid == 0x434f4e53:
200         process_console(base)
201
202     offset = offset + step
203
204 mf.close()