Revamp cbmem.py to use the coreboot tables.
[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 struct
37 import sys
38
39 def get_phys_mem(addr, size):
40     '''Read size bytes from address addr by mmaping /dev/mem'''
41
42     mf = open("/dev/mem")
43     delta = addr % 4096
44     mm = mmap.mmap(mf.fileno(), size + delta,
45                    mmap.MAP_PRIVATE, offset=(addr - delta))
46     buf = mm.read(size + delta)
47     mf.close()
48     return buf[delta:]
49
50 # This class and metaclass make it easier to define and access structures
51 # which live in physical memory. To use them, inherit from CStruct and define
52 # a class member called struct_members which is a tuple of pairs. The first
53 # item in the pair is the type format specifier that should be used with
54 # struct.unpack to read that member from memory. The second item is the name
55 # that member should have in the resulting object.
56
57 class MetaCStruct(type):
58     def __init__(cls, name, bases, dct):
59         struct_members = dct["struct_members"]
60         cls.struct_fmt = "@"
61         for char, name in struct_members:
62             cls.struct_fmt += char
63         cls.struct_len = struct.calcsize(cls.struct_fmt)
64         super(MetaCStruct, cls).__init__(name, bases, dct)
65
66 class CStruct(object):
67     __metaclass__ = MetaCStruct
68     struct_members = ()
69
70     def __init__(self, addr):
71         self.raw_memory = get_phys_mem(addr, self.struct_len)
72         values = struct.unpack(self.struct_fmt, self.raw_memory)
73         names = (name for char, name in self.struct_members)
74         for name, value in zip(names, values):
75             setattr(self, name, value)
76
77 def normalize_timer(value, freq):
78     '''Convert timer reading into microseconds.
79
80     Get the free running clock counter value, divide it by the clock frequency
81     and multiply by 1 million to get reading in microseconds.
82
83     Then convert the value into an ASCII string with groups of three digits
84     separated by commas.
85
86     Inputs:
87       value: int, the clock reading
88       freq: float, the clock frequency
89
90     Returns:
91       A string presenting 'value' in microseconds.
92     '''
93
94     result = []
95     value = int(value * 1000000.0 / freq)
96     svalue = '%d' % value
97     vlength = len(svalue)
98     remainder = vlength % 3
99     if remainder:
100         result.append(svalue[0:remainder])
101     while remainder < vlength:
102         result.append(svalue[remainder:remainder+3])
103         remainder = remainder + 3
104     return ','.join(result)
105
106 def get_cpu_freq():
107     '''Retrieve CPU frequency from sysfs.
108
109     Use /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq as the source.
110     '''
111     freq_str = open('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq'
112                     ).read()
113     # Convert reading into Hertz
114     return float(freq_str) * 1000.0
115
116 def process_timers(base):
117     '''Scan the array of timestamps found in CBMEM at address base.
118
119     For each timestamp print the timer ID and the value in microseconds.
120     '''
121
122     class TimestampHeader(CStruct):
123         struct_members = (
124             ("Q", "base_time"),
125             ("L", "max_entr"),
126             ("L", "entr")
127         )
128
129     class TimestampEntry(CStruct):
130         struct_members = (
131             ("L", "timer_id"),
132             ("Q", "timer_value")
133         )
134
135     header = TimestampHeader(base)
136     print('\ntime base %d, total entries %d' % (header.base_time, header.entr))
137     clock_freq = get_cpu_freq()
138     base = base + header.struct_len
139     for i in range(header.entr):
140         timestamp = TimestampEntry(base)
141         print '%d:%s ' % (timestamp.timer_id,
142             normalize_timer(timestamp.timer_value, clock_freq)),
143         base = base + timestamp.struct_len
144     print
145
146 def process_console(base):
147     '''Dump the console log buffer contents found at address base.'''
148
149     class ConsoleHeader(CStruct):
150         struct_members = (
151             ("L", "size"),
152             ("L", "cursor")
153         )
154
155     header = ConsoleHeader(base)
156     print 'cursor at %d\n' % header.cursor
157
158     cons_addr = base + header.struct_len
159     cons_length = min(header.cursor, header.size)
160     cons_text = get_phys_mem(cons_addr, cons_length)
161     print cons_text
162     print '\n'
163
164 def ipchksum(buf):
165     '''Checksumming function used on the coreboot tables. The buffer being
166     checksummed is summed up as if it was an array of 16 bit unsigned
167     integers. If there are an odd number of bytes, the last element is zero
168     extended.'''
169
170     size = len(buf)
171     odd = size % 2
172     fmt = "<%dH" % ((size - odd) / 2)
173     if odd:
174         fmt += "B"
175     shorts = struct.unpack(fmt, buf)
176     checksum = sum(shorts)
177     checksum = (checksum >> 16) + (checksum & 0xffff)
178     checksum += (checksum >> 16)
179     checksum = ~checksum & 0xffff
180     return checksum
181
182 def parse_tables(base, length):
183     '''Find the coreboot tables in memory and process whatever we can.'''
184
185     class CBTableHeader(CStruct):
186         struct_members = (
187             ("4s", "signature"),
188             ("I", "header_bytes"),
189             ("I", "header_checksum"),
190             ("I", "table_bytes"),
191             ("I", "table_checksum"),
192             ("I", "table_entries")
193         )
194
195     class CBTableEntry(CStruct):
196         struct_members = (
197             ("I", "tag"),
198             ("I", "size")
199         )
200
201     class CBTableForward(CBTableEntry):
202         struct_members = CBTableEntry.struct_members + (
203             ("Q", "forward"),
204         )
205
206     class CBMemTab(CBTableEntry):
207         struct_members = CBTableEntry.struct_members + (
208             ("L", "cbmem_tab"),
209         )
210
211     for addr in range(base, base + length, 16):
212         header = CBTableHeader(addr)
213         if header.signature == "LBIO":
214             break
215     else:
216         return -1
217
218     if header.header_bytes == 0:
219         return -1
220
221     if ipchksum(header.raw_memory) != 0:
222         print "Bad header checksum"
223         return -1
224
225     addr += header.header_bytes
226     table = get_phys_mem(addr, header.table_bytes)
227     if ipchksum(table) != header.table_checksum:
228         print "Bad table checksum"
229         return -1
230
231     for i in range(header.table_entries):
232         entry = CBTableEntry(addr)
233         if entry.tag == 0x11: # Forwarding entry
234             return parse_tables(CBTableForward(addr).forward, length)
235         elif entry.tag == 0x16: # Timestamps
236             process_timers(CBMemTab(addr).cbmem_tab)
237         elif entry.tag == 0x17: # CBMEM console
238             process_console(CBMemTab(addr).cbmem_tab)
239
240         addr += entry.size
241
242     return 0
243
244 def main():
245     for base, length in (0x00000000, 0x1000), (0x000f0000, 0x1000):
246         if parse_tables(base, length):
247             break
248     else:
249         print "Didn't find the coreboot tables"
250         return 0
251
252 if __name__ == "__main__":
253     sys.exit(main())