Makefile cleanups, change initial version to 0.1
[bitfield] / bitfield
1 #!/usr/bin/python2.4
2 #
3 # Utility to decode register values
4 # Copyright (c) 2006 Jeremy Kerr <jk@ozlabs.org>
5 # Released under the GNU General Public License version 2 or later
6 #
7 # Documentation and updates at: http://ozlabs.org/~jk/code/bitfield
8
9 import os
10 import sys
11 import pprint
12 from pyparsing import Literal, Word, ZeroOrMore, Group, Dict, Optional, \
13         printables, ParseException, restOfLine
14 from getopt import getopt, GetoptError
15
16 # List of paths to look for configuration files. If a directory is specified,
17 # it will be (recursively) scanned for .conf files.
18 configs = [os.path.join(os.getenv("HOME"), ".bitfield.conf"),
19                 os.path.join(os.getenv("HOME"), ".bitfield.d"),
20                 "/etc/bitfield.d", "/etc/bitfield", ]
21
22 class bitfield:
23         def __init__(self, bits, name):
24                 self.bits = bits
25                 self.name = name
26                 self.values = {}
27
28         def width(self):
29                 return len(self.bits)
30
31         def add_value(self, value, description):
32                 self.values[int(value)] = description
33
34         def mask(self, reg_width, value):
35                 ret = 0
36                 out_len = len(self.bits)
37                 for out_bit in range(0, out_len):
38                         in_bit = self.bits[out_bit]
39                         # shift this bit down to the LSB (and mask the rest)
40                         i = (value >> (reg_width - in_bit - 1)) & 1
41                         # shift back to the output position in the field
42                         i <<= out_len - out_bit - 1
43                         ret |= i
44                 return ret
45
46         def value(self, value):
47                 if value in self.values:
48                         return self.values[value]
49                 return None
50
51         @staticmethod
52         def parse_bitfield(line):
53                 a = line.split(None, 1)
54                 if len(a) != 2:
55                         return None
56                 (range_str, name) = a
57
58                 bits = []
59                 for s in range_str.split(','):
60                         if ':' in s:
61                                 (start, end) = s.split(':')
62                                 bits.extend(range(int(start), int(end) + 1, 1))
63                         else:
64                                 bits.append(int(s))
65  
66                 return bitfield(bits, name)
67
68         @staticmethod
69         def parse_value(line):
70                 a = line.split(None, 1)
71                 if len(a) != 2:
72                         return None
73                 return a
74
75 class register:
76         def __init__(self, id, name, width):
77                 self.id = id
78                 self.name = name
79                 self.width = width
80                 self.fields = []
81
82         def add_field(self, field):
83                 self.fields.append(field)
84
85         def decode(self, value, ignore_zero):
86                 field_width = (self.width + 3) / 4
87                 name_width = max(map(lambda f: len(f.name), self.fields))
88
89                 str = "0x%0*lx [%d]\n" % (field_width, value, value)
90
91                 for field in self.fields:
92                         v = field.mask(self.width, value);
93                         if ignore_zero and v == 0:
94                                 continue
95                         desc = field.value(v)
96                         if desc is not None:
97                                 str += "%*s: 0x%x [%s]\n" \
98                                         % (name_width, field.name, v, desc)
99                         else:
100                                 str += "%*s: 0x%x\n" \
101                                         % (name_width, field.name, v)
102                 return str
103
104 def list_regs(regs):
105         for (id, r) in regs.iteritems():
106                 print "%18s : %s" % (id, r.name)
107
108 def search_regs(regs, str):
109         return dict((k, regs[k]) for k in regs \
110                         if str.lower() in regs[k].name.lower() + k.lower())
111
112 class ConfigurationError(Exception):
113         def __init__(self, file, message):
114                 self.file = file
115                 self.message = message
116
117 def parse_config(bnf, regs, file):
118         f = open(file)
119
120         tokens = bnf.parseString(f.read())
121
122         for tok in tokens:
123                 ts = tok.asList()
124                 id = ts.pop(0)
125
126                 if regs.has_key(id):
127                         raise ConfigurationError(file,
128                                 "Register %s is already defined" % id)
129
130                 # default to 64 bit registers
131                 width = 64
132                 name = None
133                 fields = []
134
135                 for t in ts:
136                         if t[0] == 'name':
137                                 name = t[1]
138                                 name = name.strip()
139                         elif t[0] == 'width':
140                                 width = int(t[1])
141                         elif t[0] == 'field':
142                                 f = bitfield.parse_bitfield(t[1])
143                                 if f is None:
144                                         raise ConfigurationError(file,
145                                                 "Invalid field in %s" % id)
146                                 fields.append(f)
147                         elif t[0] == 'value':
148                                 if len(fields) == 0:
149                                         raise ConfigurationError(file,
150                                                 "No field for value in %s" % id)
151                                 v = bitfield.parse_value(t[1])
152                                 if v is None:
153                                         raise ConfigurationError(file,
154                                                 "Invalid value in %s" % id)
155
156                                 fields[-1].add_value(v[0], v[1])
157
158                 if name is None or name == '':
159                         raise ConfigurationError(file,
160                                 "No name for entry %s" % id)
161
162                 if len(fields) == 0:
163                         raise ConfigurationError(file,
164                                 "Register %s has no fields" % id)
165
166                 r = register(id, name, width)
167                 for f in fields:
168                         r.add_field(f)
169
170                 regs[id] = r
171
172 def parse_config_dir(data, dir, fnames):
173         (bnf, regs) = data
174         for fname in fnames:
175                 full_fname = os.path.join(dir, fname)
176
177                 if os.path.isdir(full_fname):
178                         continue
179
180                 if fname.endswith('.conf'):
181                         parse_config(bnf, regs, full_fname)
182
183 def parse_all_configs(configs):
184         regs = {}
185
186         # set up the bnf to be used for each file
187         lbrack = Literal("[").suppress()
188         rbrack = Literal("]").suppress()
189         colon  = Literal(":").suppress()
190         semi   = Literal(";")
191
192         comment = semi + Optional(restOfLine)
193
194         nonrbrack = "".join([c for c in printables if c != "]"]) + " \t"
195         noncolon  = "".join([c for c in printables if c != ":"]) + " \t"
196
197         sectionDef = lbrack + Word(nonrbrack) + rbrack
198         keyDef = ~lbrack + Word(noncolon) + colon + restOfLine
199
200         bnf = Dict(ZeroOrMore(Group(sectionDef + ZeroOrMore(Group(keyDef)))))
201         bnf.ignore(comment)
202
203         # bundle into a single var that can be passed to os.path.walk
204         conf_data = (bnf, regs)
205
206         for conf in configs:
207                 if not os.path.exists(conf):
208                         continue
209                 if os.path.isdir(conf):
210                         os.path.walk(conf, parse_config_dir, conf_data)
211                 else:
212                         parse_config(bnf, regs, conf)
213         return regs
214
215 def usage(prog):
216         print "Usage: %s <-l> | <-s pattern> | [-n] register [value...]" % prog
217
218 def decode_value(reg, value, options):
219         try:
220                 i = long(value, 0)
221         except ValueError, e:
222                 print "error: invalid value '%s'" % value
223                 return
224
225         if i > ((1 << reg.width) - 1):
226                 print ("error: value '%s' is too large " + \
227                         "for %d-bit register '%s'") % (value, reg.width, reg.id)
228                 return
229
230         print reg.decode(i, options['non-zero'])
231
232
233 def main():
234         try:
235                 (opts, args) = getopt(sys.argv[1:], "hlns:", \
236                         ["help", "list", "non-zero", "search="])
237         except GetoptError:
238                 usage(sys.argv[0])
239                 return 1
240
241         try:
242                 regs = parse_all_configs(configs)
243         except ConfigurationError, e:
244                 print "Error parsing configuration file %s:\n\t%s" % \
245                         (e.file, e.message)
246                 return 1
247
248         if regs == {}:
249                 print "No configuration available"
250                 return 1
251
252         options = {}
253         options['non-zero'] = False
254
255         for o, a in opts:
256                 if o in ("-h", "--help"):
257                         usage(sys.argv[0])
258                         return
259
260                 if o in ("-l", "--list"):
261                         list_regs(regs)
262                         return
263
264                 if o in ("-s", "--search"):
265                         list_regs(search_regs(regs, a))
266                         return
267
268                 if o in ("-n", "--non-zero"):
269                         options['non-zero'] = True
270
271         if not args:
272                 usage(sys.argv[0])
273                 return 1
274
275         reg_id = args.pop(0)
276         if not regs.has_key(reg_id):
277                 print "No such register '%s'" % reg_id
278                 return 1
279
280         reg = regs[reg_id]
281         print "decoding as %s" % reg.name
282
283         if args:
284                 value_iter = args.__iter__()
285         else:
286                 value_iter = iter(sys.stdin.readline, '')
287                 
288         try:
289                 for value in value_iter:
290                         decode_value(reg, value.strip(), options)
291         except KeyboardInterrupt, e:
292                 pass
293
294         return 0
295
296 if __name__ == "__main__":
297         sys.exit(main())