blob: 2096381802e53299efc317fe05907201d61c94a9 [file] [log] [blame]
Rich Lanea06d0c32013-03-25 08:52:03 -07001#!/usr/bin/python
2# Copyright 2013, Big Switch Networks, Inc.
3#
4# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
5# the following special exception:
6#
7# LOXI Exception
8#
9# As a special exception to the terms of the EPL, you may distribute libraries
10# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
11# that copyright and licensing notices generated by LoxiGen are not altered or removed
12# from the LoxiGen Libraries and the notice provided below is (i) included in
13# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
14# documentation for the LoxiGen Libraries, if distributed in binary form.
15#
16# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
17#
18# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
19# a copy of the EPL at:
20#
21# http://www.eclipse.org/legal/epl-v10.html
22#
23# Unless required by applicable law or agreed to in writing, software
24# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
25# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
26# EPL for the specific language governing permissions and limitations
27# under the EPL.
28
29"""
30@brief
31Process openflow header files to create language specific LOXI interfaces
32
33First cut at simple python script for processing input files
34
35Internal notes
36
37An input file for each supported OpenFlow version is passed in
38on the command line.
39
40Expected input file format:
41
42These will probably be collapsed into a python dict or something
43
44The first line has the ofC version identifier and nothing else
45The second line has the openflow wire protocol value and nothing else
46
47The main content is struct elements for each OF recognized class.
48These are taken from current versions of openflow.h but are modified
49a bit. See Overview for more information.
50
Andreas Wundsam53256162013-05-02 14:05:53 -070051Class canonical form: A list of entries, each of which is a
Rich Lanea06d0c32013-03-25 08:52:03 -070052pair "type, name;". The exception is when type is the keyword
53'list' in which the syntax is "list(type) name;".
54
55From this, internal representations are generated: For each
56version, a dict indexed by class name. One element (members) is
57an array giving the member name and type. From this, wire offsets
58can be calculated.
59
60
61@fixme Clean up the lang module architecture. It should provide a
62list of files that it wants to generate and maps to the filenames,
Andreas Wundsam53256162013-05-02 14:05:53 -070063subdirectory names and generation functions. It should also be
64defined as a class, probably with the constructor taking the
Rich Lanea06d0c32013-03-25 08:52:03 -070065language target.
66
67@fixme Clean up global data structures such as versions and of_g
68structures. They should probably be a class or classes as well.
69
70"""
71
72import sys
73
74import re
75import string
76import os
77import glob
78import copy
79import of_g
80import loxi_front_end.oxm as oxm
81import loxi_front_end.type_maps as type_maps
82import loxi_utils.loxi_utils as loxi_utils
Rich Lanea06d0c32013-03-25 08:52:03 -070083import loxi_front_end.c_parse_utils as c_parse_utils
84import loxi_front_end.identifiers as identifiers
85import pyparsing
86import loxi_front_end.parser as parser
Rich Lane38388e62013-04-08 14:09:46 -070087import loxi_front_end.translation as translation
Rich Laned47e5a22013-05-09 14:21:16 -070088import loxi_front_end.frontend as frontend
Rich Lanea06d0c32013-03-25 08:52:03 -070089
90from generic_utils import *
91
92root_dir = os.path.dirname(os.path.realpath(__file__))
93
94# TODO: Put these in a class so they get documented
95
96## Dict indexed by version giving all info related to version
97#
98# This is local; after processing, the information is stored in
99# of_g variables.
100versions = {}
101
102def config_sanity_check():
103 """
104 Check the configuration for basic consistency
105
106 @fixme Needs update for generic language support
107 """
108
109 rv = True
110 # For now, only "error" supported for get returns
111 if config_check("copy_semantics") != "read":
112 debug("Only 'read' is supported for copy_semantics");
Andreas Wundsam53256162013-05-02 14:05:53 -0700113 rv = False
Rich Lanea06d0c32013-03-25 08:52:03 -0700114 if config_check("get_returns") != "error":
115 debug("Only 'error' is supported for get-accessor return types\m");
Andreas Wundsam53256162013-05-02 14:05:53 -0700116 rv = False
Rich Lanea06d0c32013-03-25 08:52:03 -0700117 if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
118 debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
119 rv = False
120 if config_check("use_obj_id"):
121 debug("use_obj_id is set but not yet supported (change \
122config_sanity_check if it is)")
123 rv = False
124 if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
125 and config_check("gen_unified_macro_lower"):
126 debug("Conflict: Cannot generate unified functions and lower case \
127unified macros")
128 rv = False
Andreas Wundsam53256162013-05-02 14:05:53 -0700129
Rich Lanea06d0c32013-03-25 08:52:03 -0700130 return rv
131
132def add_class(wire_version, cls, members):
133 """
Andreas Wundsam53256162013-05-02 14:05:53 -0700134 Process a class for the given version and update the unified
Rich Lanea06d0c32013-03-25 08:52:03 -0700135 list of classes as needed.
136
137 @param wire_version The wire version for this class defn
138 @param cls The name of the class being added
139 @param members The list of members with offsets calculated
140 """
141 memid = 0
142
143 sig = loxi_utils.class_signature(members)
144 if cls in of_g.unified:
145 uc = of_g.unified[cls]
146 if wire_version in uc:
147 debug("Error adding %s to unified. Wire ver %d exists" %
148 (cls, wire_version))
149 sys.exit(1)
150 uc[wire_version] = {}
151 # Check for a matching signature
152 for wver in uc:
153 if type(wver) != type(0): continue
154 if wver == wire_version: continue
155 if not "use_version" in uc[wver]:
156 if sig == loxi_utils.class_signature(uc[wver]["members"]):
Andreas Wundsam53256162013-05-02 14:05:53 -0700157 log("Matched %s, ver %d to ver %d" %
Rich Lanea06d0c32013-03-25 08:52:03 -0700158 (cls, wire_version, wver))
159 # have a match with existing version
160 uc[wire_version]["use_version"] = wver
161 # What else to do?
162 return
163 else: # Haven't seen this entry before
164 log("Adding %s to unified list, ver %d" % (cls, wire_version))
165 of_g.unified[cls] = dict(union={})
166 uc = of_g.unified[cls]
167
168 # At this point, need to add members for this version
169 uc[wire_version] = dict(members = members)
170
171 # Per member processing:
172 # Add to union list (I'm sure there's a better way)
173 # Check if it's a list
174 union = uc["union"]
175 if not cls in of_g.ordered_members:
176 of_g.ordered_members[cls] = []
177 for member in members:
178 m_name = member["name"]
179 m_type = member["m_type"]
180 if m_name.find("pad") == 0:
181 continue
182 if m_name in union:
183 if not m_type == union[m_name]["m_type"]:
184 debug("ERROR: CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
185 (cls, wire_version, m_name, m_type))
186 debug(" Type conflict adding member to unified set.")
187 debug(" Current union[%s]:" % m_name)
188 debug(union[m_name])
189 sys.exit(1)
190 else:
191 union[m_name] = dict(m_type=m_type, memid=memid)
192 memid += 1
193 if not m_name in of_g.ordered_members[cls]:
194 of_g.ordered_members[cls].append(m_name)
195
196def update_offset(cls, wire_version, name, offset, m_type):
197 """
198 Update (and return) the offset based on type.
199 @param cls The parent class
200 @param wire_version The wire version being processed
201 @param name The name of the data member
202 @param offset The current offset
203 @param m_type The type declaration being processed
204 @returns A pair (next_offset, len_update) next_offset is the new offset
205 of the next object or -1 if this is a var-length object. len_update
206 is the increment that should be added to the length. Note that (for
207 of_match_v3) it is variable length, but it adds 8 bytes to the fixed
208 length of the object
209 If offset is already -1, do not update
210 Otherwise map to base type and count and update (if possible)
211 """
212 if offset < 0: # Don't update offset once set to -1
213 return offset, 0
214
215 count, base_type = c_parse_utils.type_dec_to_count_base(m_type)
216
217 len_update = 0
218 if base_type in of_g.of_mixed_types:
219 base_type = of_g.of_mixed_types[base_type][wire_version]
220
221 base_class = base_type[:-2]
222 if (base_class, wire_version) in of_g.is_fixed_length:
223 bytes = of_g.base_length[(base_class, wire_version)]
224 else:
225 if base_type == "of_match_v3_t":
226 # This is a special case: it has non-zero min length
227 # but is variable length
228 bytes = -1
229 len_update = 8
230 elif base_type in of_g.of_base_types:
231 bytes = of_g.of_base_types[base_type]["bytes"]
232 else:
233 print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
234 log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
235 bytes = -1
236
237 # If bytes
238 if bytes > 0:
239 len_update = count * bytes
240
241 if bytes == -1:
242 return -1, len_update
243
244 return offset + (count * bytes), len_update
245
246def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
247 """
248 Generate the offsets for fixed offset class members
249 Also calculate the class_sizes when possible.
250
251 @param classes The classes to process
252 @param wire_version The wire version for this set of classes
253
254 Updates global variables
255 """
256
257 lists = set()
258
259 # Generate offsets
260 for cls in ordered_classes:
261 fixed_offset = 0 # The last "good" offset seen
262 offset = 0
263 last_offset = 0
264 last_name = "-"
265 for member in classes[cls]:
266 m_type = member["m_type"]
267 name = member["name"]
268 if last_offset == -1:
269 if name == "pad":
270 log("Skipping pad for special offset for %s" % cls)
271 else:
Andreas Wundsam53256162013-05-02 14:05:53 -0700272 log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
Rich Lanea06d0c32013-03-25 08:52:03 -0700273 (name, last_name, cls, wire_version))
274 if (((cls, name) in of_g.special_offsets) and
275 (of_g.special_offsets[(cls, name)] != last_name)):
276 debug("ERROR: special offset prev name changed")
277 debug(" cls %s. name %s. version %d. was %s. now %s" %
Andreas Wundsam53256162013-05-02 14:05:53 -0700278 cls, name, wire_version,
Rich Lanea06d0c32013-03-25 08:52:03 -0700279 of_g.special_offsets[(cls, name)], last_name)
280 sys.exit(1)
281 of_g.special_offsets[(cls, name)] = last_name
282
283 member["offset"] = offset
284 if m_type.find("list(") == 0:
285 (list_name, base_type) = loxi_utils.list_name_extract(m_type)
286 lists.add(list_name)
287 member["m_type"] = list_name + "_t"
288 offset = -1
289 elif m_type.find("struct") == 0:
290 debug("ERROR found struct: %s.%s " % (cls, name))
291 sys.exit(1)
292 elif m_type == "octets":
293 log("offset gen skipping octets: %s.%s " % (cls, name))
294 offset = -1
295 else:
Andreas Wundsam53256162013-05-02 14:05:53 -0700296 offset, len_update = update_offset(cls, wire_version, name,
Rich Lanea06d0c32013-03-25 08:52:03 -0700297 offset, m_type)
298 if offset != -1:
299 fixed_offset = offset
300 else:
301 fixed_offset += len_update
Andreas Wundsam53256162013-05-02 14:05:53 -0700302 log("offset is -1 for %s.%s version %d " %
Rich Lanea06d0c32013-03-25 08:52:03 -0700303 (cls, name, wire_version))
304 last_offset = offset
305 last_name = name
306 of_g.base_length[(cls, wire_version)] = fixed_offset
307 if (offset != -1):
308 of_g.is_fixed_length.add((cls, wire_version))
309 for list_type in lists:
310 classes[list_type] = []
311 of_g.ordered_classes[wire_version].append(list_type)
312 of_g.base_length[(list_type, wire_version)] = 0
313
314def process_input_file(filename):
315 """
316 Process an input file
317
Rich Laned47e5a22013-05-09 14:21:16 -0700318 Does not modify global state.
319
Rich Lanea06d0c32013-03-25 08:52:03 -0700320 @param filename The input filename
321
Rich Laned47e5a22013-05-09 14:21:16 -0700322 @returns An OFInput object
Rich Lanea06d0c32013-03-25 08:52:03 -0700323 """
324
325 # Parse the input file
326 try:
Rich Laned47e5a22013-05-09 14:21:16 -0700327 with open(filename, 'r') as f:
328 ast = parser.parse(f.read())
Rich Lanea06d0c32013-03-25 08:52:03 -0700329 except pyparsing.ParseBaseException as e:
330 print "Parse error in %s: %s" % (os.path.basename(filename), str(e))
331 sys.exit(1)
332
Rich Laned47e5a22013-05-09 14:21:16 -0700333 # Create the OFInput from the AST
334 try:
335 ofinput = frontend.create_ofinput(ast)
336 except frontend.InputError as e:
337 print "Error in %s: %s" % (os.path.basename(filename), str(e))
Rich Lanea06d0c32013-03-25 08:52:03 -0700338 sys.exit(1)
339
340 return ofinput
341
342def order_and_assign_object_ids():
343 """
344 Order all classes and assign object ids to all classes.
345
346 This is done to promote a reasonable order of the objects, putting
347 messages first followed by non-messages. No assumptions should be
348 made about the order, nor about contiguous numbering. However, the
Andreas Wundsam53256162013-05-02 14:05:53 -0700349 numbers should all be reasonably small allowing arrays indexed by
Rich Lanea06d0c32013-03-25 08:52:03 -0700350 these enum values to be defined.
351 """
352
353 # Generate separate message and non-message ordered lists
354 for cls in of_g.unified:
355 if loxi_utils.class_is_message(cls):
356 of_g.ordered_messages.append(cls)
357 elif loxi_utils.class_is_list(cls):
358 of_g.ordered_list_objects.append(cls)
359 else:
360 of_g.ordered_non_messages.append(cls)
361
362 of_g.ordered_pseudo_objects.append("of_stats_request")
363 of_g.ordered_pseudo_objects.append("of_stats_reply")
364 of_g.ordered_pseudo_objects.append("of_flow_mod")
365
366 of_g.ordered_messages.sort()
367 of_g.ordered_pseudo_objects.sort()
368 of_g.ordered_non_messages.sort()
369 of_g.ordered_list_objects.sort()
370 of_g.standard_class_order.extend(of_g.ordered_messages)
371 of_g.standard_class_order.extend(of_g.ordered_non_messages)
372 of_g.standard_class_order.extend(of_g.ordered_list_objects)
373
374 # This includes pseudo classes for which most code is not generated
375 of_g.all_class_order.extend(of_g.ordered_messages)
376 of_g.all_class_order.extend(of_g.ordered_non_messages)
377 of_g.all_class_order.extend(of_g.ordered_list_objects)
378 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
379
380 # Assign object IDs
381 for cls in of_g.ordered_messages:
382 of_g.unified[cls]["object_id"] = of_g.object_id
383 of_g.object_id += 1
384 for cls in of_g.ordered_non_messages:
385 of_g.unified[cls]["object_id"] = of_g.object_id
386 of_g.object_id += 1
387 for cls in of_g.ordered_list_objects:
388 of_g.unified[cls]["object_id"] = of_g.object_id
389 of_g.object_id += 1
390 for cls in of_g.ordered_pseudo_objects:
391 of_g.unified[cls] = {}
392 of_g.unified[cls]["object_id"] = of_g.object_id
393 of_g.object_id += 1
394
Rich Lanea06d0c32013-03-25 08:52:03 -0700395
396def initialize_versions():
397 """
398 Create an empty datastructure for each target version.
399 """
400
401 for wire_version in of_g.target_version_list:
402 version_name = of_g.of_version_wire2name[wire_version]
403 of_g.wire_ver_map[wire_version] = version_name
404 versions[version_name] = dict(
405 version_name = version_name,
406 wire_version = wire_version,
407 classes = {})
408 of_g.ordered_classes[wire_version] = []
409
410
411def read_input():
412 """
413 Read in from files given on command line and update global state
414
415 @fixme Should select versions to support from command line
416 """
417
418 filenames = sorted(glob.glob("%s/openflow_input/*" % root_dir))
419
420 for filename in filenames:
421 log("Processing struct file: " + filename)
422 ofinput = process_input_file(filename)
423
424 # Populate global state
425 for wire_version in ofinput.wire_versions:
426 version_name = of_g.of_version_wire2name[wire_version]
427 versions[version_name]['classes'].update(copy.deepcopy(ofinput.classes))
428 of_g.ordered_classes[wire_version].extend(ofinput.ordered_classes)
429
Rich Lane38388e62013-04-08 14:09:46 -0700430 for enum_name, members in ofinput.enums.items():
431 for member_name, value in members:
432 identifiers.add_identifier(
433 translation.loxi_name(member_name),
434 member_name, enum_name, value, wire_version,
435 of_g.identifiers, of_g.identifiers_by_group)
436
Rich Lanea06d0c32013-03-25 08:52:03 -0700437def add_extra_classes():
438 """
439 Add classes that are generated by Python code instead of from the
440 input files.
441 """
442
443 for wire_version in [of_g.VERSION_1_2, of_g.VERSION_1_3]:
444 version_name = of_g.of_version_wire2name[wire_version]
445 oxm.add_oxm_classes_1_2(versions[version_name]['classes'], wire_version)
446
447def analyze_input():
448 """
449 Add information computed from the input, including offsets and
450 lengths of struct members and the set of list and action_id types.
451 """
452
453 # Generate action_id classes for OF 1.3
454 for wire_version, ordered_classes in of_g.ordered_classes.items():
455 if not wire_version in [of_g.VERSION_1_3]:
456 continue
457 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
458 for cls in ordered_classes:
459 if not loxi_utils.class_is_action(cls):
460 continue
461 action = cls[10:]
462 if action == '' or action == 'header':
463 continue
464 name = "of_action_id_" + action
465 members = classes["of_action"][:]
466 of_g.ordered_classes[wire_version].append(name)
467 if type_maps.action_id_is_extension(name, wire_version):
468 # Copy the base action classes thru subtype
469 members = classes["of_action_" + action][:4]
470 classes[name] = members
471
472 # @fixme If we support extended actions in OF 1.3, need to add IDs
473 # for them here
474
475 for wire_version in of_g.wire_ver_map.keys():
476 version_name = of_g.of_version_wire2name[wire_version]
477 calculate_offsets_and_lengths(
478 of_g.ordered_classes[wire_version],
479 versions[version_name]['classes'],
480 wire_version)
481
482def unify_input():
483 """
484 Create Unified View of Objects
485 """
486
487 global versions
488
Andreas Wundsam53256162013-05-02 14:05:53 -0700489 # Add classes to unified in wire-format order so that it is easier
Rich Lanea06d0c32013-03-25 08:52:03 -0700490 # to generate things later
491 keys = versions.keys()
492 keys.sort(reverse=True)
493 for version in keys:
494 wire_version = versions[version]["wire_version"]
495 classes = versions[version]["classes"]
496 for cls in of_g.ordered_classes[wire_version]:
497 add_class(wire_version, cls, classes[cls])
498
499
500def log_all_class_info():
501 """
502 Log the results of processing the input
503
504 Debug function
505 """
506
507 for cls in of_g.unified:
508 for v in of_g.unified[cls]:
509 if type(v) == type(0):
510 log("cls: %s. ver: %d. base len %d. %s" %
511 (str(cls), v, of_g.base_length[(cls, v)],
512 loxi_utils.class_is_var_len(cls,v) and "not fixed"
513 or "fixed"))
514 if "use_version" in of_g.unified[cls][v]:
Andreas Wundsam53256162013-05-02 14:05:53 -0700515 log("cls %s: v %d mapped to %d" % (str(cls), v,
Rich Lanea06d0c32013-03-25 08:52:03 -0700516 of_g.unified[cls][v]["use_version"]))
517 if "members" in of_g.unified[cls][v]:
518 for member in of_g.unified[cls][v]["members"]:
519 log(" %-20s: type %-20s. offset %3d" %
520 (member["name"], member["m_type"],
521 member["offset"]))
522
523def generate_all_files():
524 """
525 Create the files for the language target
526 """
527 for (name, fn) in lang_module.targets.items():
528 path = of_g.options.install_dir + '/' + name
529 os.system("mkdir -p %s" % os.path.dirname(path))
530 with open(path, "w") as outfile:
531 fn(outfile, os.path.basename(name))
532 print("Wrote contents for " + name)
533
534if __name__ == '__main__':
535 of_g.loxigen_log_file = open("loxigen.log", "w")
536 of_g.loxigen_dbg_file = sys.stdout
537
538 of_g.process_commandline()
539 # @fixme Use command line params to select log
540
541 if not config_sanity_check():
542 debug("Config sanity check failed\n")
543 sys.exit(1)
544
545 # Import the language file
546 lang_file = "lang_%s" % of_g.options.lang
547 lang_module = __import__(lang_file)
548
549 # If list files, just list auto-gen files to stdout and exit
550 if of_g.options.list_files:
551 for name in lang_module.targets:
552 print of_g.options.install_dir + '/' + name
553 sys.exit(0)
554
555 log("\nGenerating files for target language %s\n" % of_g.options.lang)
556
Rich Lanea06d0c32013-03-25 08:52:03 -0700557 initialize_versions()
558 read_input()
559 add_extra_classes()
560 analyze_input()
561 unify_input()
562 order_and_assign_object_ids()
563 log_all_class_info()
564 generate_all_files()