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