blob: ee92d6b3af8e3c047fb2a578992d0d95efa22df3 [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 Lanea06d0c32013-03-25 08:52:03 -070088
89from generic_utils import *
90
91root_dir = os.path.dirname(os.path.realpath(__file__))
92
93# TODO: Put these in a class so they get documented
94
95## Dict indexed by version giving all info related to version
96#
97# This is local; after processing, the information is stored in
98# of_g variables.
99versions = {}
100
101def config_sanity_check():
102 """
103 Check the configuration for basic consistency
104
105 @fixme Needs update for generic language support
106 """
107
108 rv = True
109 # For now, only "error" supported for get returns
110 if config_check("copy_semantics") != "read":
111 debug("Only 'read' is supported for copy_semantics");
Andreas Wundsam53256162013-05-02 14:05:53 -0700112 rv = False
Rich Lanea06d0c32013-03-25 08:52:03 -0700113 if config_check("get_returns") != "error":
114 debug("Only 'error' is supported for get-accessor return types\m");
Andreas Wundsam53256162013-05-02 14:05:53 -0700115 rv = False
Rich Lanea06d0c32013-03-25 08:52:03 -0700116 if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
117 debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
118 rv = False
119 if config_check("use_obj_id"):
120 debug("use_obj_id is set but not yet supported (change \
121config_sanity_check if it is)")
122 rv = False
123 if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
124 and config_check("gen_unified_macro_lower"):
125 debug("Conflict: Cannot generate unified functions and lower case \
126unified macros")
127 rv = False
Andreas Wundsam53256162013-05-02 14:05:53 -0700128
Rich Lanea06d0c32013-03-25 08:52:03 -0700129 return rv
130
131def add_class(wire_version, cls, members):
132 """
Andreas Wundsam53256162013-05-02 14:05:53 -0700133 Process a class for the given version and update the unified
Rich Lanea06d0c32013-03-25 08:52:03 -0700134 list of classes as needed.
135
136 @param wire_version The wire version for this class defn
137 @param cls The name of the class being added
138 @param members The list of members with offsets calculated
139 """
140 memid = 0
141
142 sig = loxi_utils.class_signature(members)
143 if cls in of_g.unified:
144 uc = of_g.unified[cls]
145 if wire_version in uc:
146 debug("Error adding %s to unified. Wire ver %d exists" %
147 (cls, wire_version))
148 sys.exit(1)
149 uc[wire_version] = {}
150 # Check for a matching signature
151 for wver in uc:
152 if type(wver) != type(0): continue
153 if wver == wire_version: continue
154 if not "use_version" in uc[wver]:
155 if sig == loxi_utils.class_signature(uc[wver]["members"]):
Andreas Wundsam53256162013-05-02 14:05:53 -0700156 log("Matched %s, ver %d to ver %d" %
Rich Lanea06d0c32013-03-25 08:52:03 -0700157 (cls, wire_version, wver))
158 # have a match with existing version
159 uc[wire_version]["use_version"] = wver
160 # What else to do?
161 return
162 else: # Haven't seen this entry before
163 log("Adding %s to unified list, ver %d" % (cls, wire_version))
164 of_g.unified[cls] = dict(union={})
165 uc = of_g.unified[cls]
166
167 # At this point, need to add members for this version
168 uc[wire_version] = dict(members = members)
169
170 # Per member processing:
171 # Add to union list (I'm sure there's a better way)
172 # Check if it's a list
173 union = uc["union"]
174 if not cls in of_g.ordered_members:
175 of_g.ordered_members[cls] = []
176 for member in members:
177 m_name = member["name"]
178 m_type = member["m_type"]
179 if m_name.find("pad") == 0:
180 continue
181 if m_name in union:
182 if not m_type == union[m_name]["m_type"]:
183 debug("ERROR: CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
184 (cls, wire_version, m_name, m_type))
185 debug(" Type conflict adding member to unified set.")
186 debug(" Current union[%s]:" % m_name)
187 debug(union[m_name])
188 sys.exit(1)
189 else:
190 union[m_name] = dict(m_type=m_type, memid=memid)
191 memid += 1
192 if not m_name in of_g.ordered_members[cls]:
193 of_g.ordered_members[cls].append(m_name)
194
195def update_offset(cls, wire_version, name, offset, m_type):
196 """
197 Update (and return) the offset based on type.
198 @param cls The parent class
199 @param wire_version The wire version being processed
200 @param name The name of the data member
201 @param offset The current offset
202 @param m_type The type declaration being processed
203 @returns A pair (next_offset, len_update) next_offset is the new offset
204 of the next object or -1 if this is a var-length object. len_update
205 is the increment that should be added to the length. Note that (for
206 of_match_v3) it is variable length, but it adds 8 bytes to the fixed
207 length of the object
208 If offset is already -1, do not update
209 Otherwise map to base type and count and update (if possible)
210 """
211 if offset < 0: # Don't update offset once set to -1
212 return offset, 0
213
214 count, base_type = c_parse_utils.type_dec_to_count_base(m_type)
215
216 len_update = 0
217 if base_type in of_g.of_mixed_types:
218 base_type = of_g.of_mixed_types[base_type][wire_version]
219
220 base_class = base_type[:-2]
221 if (base_class, wire_version) in of_g.is_fixed_length:
222 bytes = of_g.base_length[(base_class, wire_version)]
223 else:
224 if base_type == "of_match_v3_t":
225 # This is a special case: it has non-zero min length
226 # but is variable length
227 bytes = -1
228 len_update = 8
229 elif base_type in of_g.of_base_types:
230 bytes = of_g.of_base_types[base_type]["bytes"]
231 else:
232 print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
233 log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
234 bytes = -1
235
236 # If bytes
237 if bytes > 0:
238 len_update = count * bytes
239
240 if bytes == -1:
241 return -1, len_update
242
243 return offset + (count * bytes), len_update
244
245def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
246 """
247 Generate the offsets for fixed offset class members
248 Also calculate the class_sizes when possible.
249
250 @param classes The classes to process
251 @param wire_version The wire version for this set of classes
252
253 Updates global variables
254 """
255
256 lists = set()
257
258 # Generate offsets
259 for cls in ordered_classes:
260 fixed_offset = 0 # The last "good" offset seen
261 offset = 0
262 last_offset = 0
263 last_name = "-"
264 for member in classes[cls]:
265 m_type = member["m_type"]
266 name = member["name"]
267 if last_offset == -1:
268 if name == "pad":
269 log("Skipping pad for special offset for %s" % cls)
270 else:
Andreas Wundsam53256162013-05-02 14:05:53 -0700271 log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
Rich Lanea06d0c32013-03-25 08:52:03 -0700272 (name, last_name, cls, wire_version))
273 if (((cls, name) in of_g.special_offsets) and
274 (of_g.special_offsets[(cls, name)] != last_name)):
275 debug("ERROR: special offset prev name changed")
276 debug(" cls %s. name %s. version %d. was %s. now %s" %
Andreas Wundsam53256162013-05-02 14:05:53 -0700277 cls, name, wire_version,
Rich Lanea06d0c32013-03-25 08:52:03 -0700278 of_g.special_offsets[(cls, name)], last_name)
279 sys.exit(1)
280 of_g.special_offsets[(cls, name)] = last_name
281
282 member["offset"] = offset
283 if m_type.find("list(") == 0:
284 (list_name, base_type) = loxi_utils.list_name_extract(m_type)
285 lists.add(list_name)
286 member["m_type"] = list_name + "_t"
287 offset = -1
288 elif m_type.find("struct") == 0:
289 debug("ERROR found struct: %s.%s " % (cls, name))
290 sys.exit(1)
291 elif m_type == "octets":
292 log("offset gen skipping octets: %s.%s " % (cls, name))
293 offset = -1
294 else:
Andreas Wundsam53256162013-05-02 14:05:53 -0700295 offset, len_update = update_offset(cls, wire_version, name,
Rich Lanea06d0c32013-03-25 08:52:03 -0700296 offset, m_type)
297 if offset != -1:
298 fixed_offset = offset
299 else:
300 fixed_offset += len_update
Andreas Wundsam53256162013-05-02 14:05:53 -0700301 log("offset is -1 for %s.%s version %d " %
Rich Lanea06d0c32013-03-25 08:52:03 -0700302 (cls, name, wire_version))
303 last_offset = offset
304 last_name = name
305 of_g.base_length[(cls, wire_version)] = fixed_offset
306 if (offset != -1):
307 of_g.is_fixed_length.add((cls, wire_version))
308 for list_type in lists:
309 classes[list_type] = []
310 of_g.ordered_classes[wire_version].append(list_type)
311 of_g.base_length[(list_type, wire_version)] = 0
312
313def process_input_file(filename):
314 """
315 Process an input file
316
317 @param filename The input filename
318
319 @returns (wire_version, classes), where wire_version is the integer wire
320 protocol number and classes is the dict of all classes processed from the
321 file.
Rich Lanea06d0c32013-03-25 08:52:03 -0700322 """
323
324 # Parse the input file
325 try:
326 ast = parser.parse(open(filename, 'r').read())
327 except pyparsing.ParseBaseException as e:
328 print "Parse error in %s: %s" % (os.path.basename(filename), str(e))
329 sys.exit(1)
330
331 ofinput = of_g.OFInput()
332
333 # Now for each structure, generate lists for each member
334 for s in ast:
335 if s[0] == 'struct':
336 name = s[1].replace("ofp_", "of_", 1)
337 members = [dict(m_type=x[0], name=x[1]) for x in s[2]]
338 ofinput.classes[name] = members
339 ofinput.ordered_classes.append(name)
340 if name in type_maps.inheritance_map:
341 # Clone class into header class and add to list
342 ofinput.classes[name + "_header"] = members[:]
343 ofinput.ordered_classes.append(name + "_header")
Rich Lane38388e62013-04-08 14:09:46 -0700344 if s[0] == 'enum':
345 name = s[1]
346 members = s[2]
347 ofinput.enums[name] = [(x[0], x[1]) for x in members]
Rich Lanea06d0c32013-03-25 08:52:03 -0700348 elif s[0] == 'metadata':
349 if s[1] == 'version':
350 log("Found version: wire version " + s[2])
351 if s[2] == 'any':
352 ofinput.wire_versions.update(of_g.wire_ver_map.keys())
353 elif int(s[2]) in of_g.supported_wire_protos:
354 ofinput.wire_versions.add(int(s[2]))
355 else:
356 debug("Unrecognized wire protocol version")
357 sys.exit(1)
358 found_wire_version = True
359
360 if not ofinput.wire_versions:
361 debug("Missing #version metadata")
362 sys.exit(1)
363
364 return ofinput
365
366def order_and_assign_object_ids():
367 """
368 Order all classes and assign object ids to all classes.
369
370 This is done to promote a reasonable order of the objects, putting
371 messages first followed by non-messages. No assumptions should be
372 made about the order, nor about contiguous numbering. However, the
Andreas Wundsam53256162013-05-02 14:05:53 -0700373 numbers should all be reasonably small allowing arrays indexed by
Rich Lanea06d0c32013-03-25 08:52:03 -0700374 these enum values to be defined.
375 """
376
377 # Generate separate message and non-message ordered lists
378 for cls in of_g.unified:
379 if loxi_utils.class_is_message(cls):
380 of_g.ordered_messages.append(cls)
381 elif loxi_utils.class_is_list(cls):
382 of_g.ordered_list_objects.append(cls)
383 else:
384 of_g.ordered_non_messages.append(cls)
385
386 of_g.ordered_pseudo_objects.append("of_stats_request")
387 of_g.ordered_pseudo_objects.append("of_stats_reply")
388 of_g.ordered_pseudo_objects.append("of_flow_mod")
389
390 of_g.ordered_messages.sort()
391 of_g.ordered_pseudo_objects.sort()
392 of_g.ordered_non_messages.sort()
393 of_g.ordered_list_objects.sort()
394 of_g.standard_class_order.extend(of_g.ordered_messages)
395 of_g.standard_class_order.extend(of_g.ordered_non_messages)
396 of_g.standard_class_order.extend(of_g.ordered_list_objects)
397
398 # This includes pseudo classes for which most code is not generated
399 of_g.all_class_order.extend(of_g.ordered_messages)
400 of_g.all_class_order.extend(of_g.ordered_non_messages)
401 of_g.all_class_order.extend(of_g.ordered_list_objects)
402 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
403
404 # Assign object IDs
405 for cls in of_g.ordered_messages:
406 of_g.unified[cls]["object_id"] = of_g.object_id
407 of_g.object_id += 1
408 for cls in of_g.ordered_non_messages:
409 of_g.unified[cls]["object_id"] = of_g.object_id
410 of_g.object_id += 1
411 for cls in of_g.ordered_list_objects:
412 of_g.unified[cls]["object_id"] = of_g.object_id
413 of_g.object_id += 1
414 for cls in of_g.ordered_pseudo_objects:
415 of_g.unified[cls] = {}
416 of_g.unified[cls]["object_id"] = of_g.object_id
417 of_g.object_id += 1
418
Rich Lanea06d0c32013-03-25 08:52:03 -0700419
420def initialize_versions():
421 """
422 Create an empty datastructure for each target version.
423 """
424
425 for wire_version in of_g.target_version_list:
426 version_name = of_g.of_version_wire2name[wire_version]
427 of_g.wire_ver_map[wire_version] = version_name
428 versions[version_name] = dict(
429 version_name = version_name,
430 wire_version = wire_version,
431 classes = {})
432 of_g.ordered_classes[wire_version] = []
433
434
435def read_input():
436 """
437 Read in from files given on command line and update global state
438
439 @fixme Should select versions to support from command line
440 """
441
442 filenames = sorted(glob.glob("%s/openflow_input/*" % root_dir))
443
444 for filename in filenames:
445 log("Processing struct file: " + filename)
446 ofinput = process_input_file(filename)
447
448 # Populate global state
449 for wire_version in ofinput.wire_versions:
450 version_name = of_g.of_version_wire2name[wire_version]
451 versions[version_name]['classes'].update(copy.deepcopy(ofinput.classes))
452 of_g.ordered_classes[wire_version].extend(ofinput.ordered_classes)
453
Rich Lane38388e62013-04-08 14:09:46 -0700454 for enum_name, members in ofinput.enums.items():
455 for member_name, value in members:
456 identifiers.add_identifier(
457 translation.loxi_name(member_name),
458 member_name, enum_name, value, wire_version,
459 of_g.identifiers, of_g.identifiers_by_group)
460
Rich Lanea06d0c32013-03-25 08:52:03 -0700461def add_extra_classes():
462 """
463 Add classes that are generated by Python code instead of from the
464 input files.
465 """
466
467 for wire_version in [of_g.VERSION_1_2, of_g.VERSION_1_3]:
468 version_name = of_g.of_version_wire2name[wire_version]
469 oxm.add_oxm_classes_1_2(versions[version_name]['classes'], wire_version)
470
471def analyze_input():
472 """
473 Add information computed from the input, including offsets and
474 lengths of struct members and the set of list and action_id types.
475 """
476
477 # Generate action_id classes for OF 1.3
478 for wire_version, ordered_classes in of_g.ordered_classes.items():
479 if not wire_version in [of_g.VERSION_1_3]:
480 continue
481 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
482 for cls in ordered_classes:
483 if not loxi_utils.class_is_action(cls):
484 continue
485 action = cls[10:]
486 if action == '' or action == 'header':
487 continue
488 name = "of_action_id_" + action
489 members = classes["of_action"][:]
490 of_g.ordered_classes[wire_version].append(name)
491 if type_maps.action_id_is_extension(name, wire_version):
492 # Copy the base action classes thru subtype
493 members = classes["of_action_" + action][:4]
494 classes[name] = members
495
496 # @fixme If we support extended actions in OF 1.3, need to add IDs
497 # for them here
498
499 for wire_version in of_g.wire_ver_map.keys():
500 version_name = of_g.of_version_wire2name[wire_version]
501 calculate_offsets_and_lengths(
502 of_g.ordered_classes[wire_version],
503 versions[version_name]['classes'],
504 wire_version)
505
506def unify_input():
507 """
508 Create Unified View of Objects
509 """
510
511 global versions
512
Andreas Wundsam53256162013-05-02 14:05:53 -0700513 # Add classes to unified in wire-format order so that it is easier
Rich Lanea06d0c32013-03-25 08:52:03 -0700514 # to generate things later
515 keys = versions.keys()
516 keys.sort(reverse=True)
517 for version in keys:
518 wire_version = versions[version]["wire_version"]
519 classes = versions[version]["classes"]
520 for cls in of_g.ordered_classes[wire_version]:
521 add_class(wire_version, cls, classes[cls])
522
523
524def log_all_class_info():
525 """
526 Log the results of processing the input
527
528 Debug function
529 """
530
531 for cls in of_g.unified:
532 for v in of_g.unified[cls]:
533 if type(v) == type(0):
534 log("cls: %s. ver: %d. base len %d. %s" %
535 (str(cls), v, of_g.base_length[(cls, v)],
536 loxi_utils.class_is_var_len(cls,v) and "not fixed"
537 or "fixed"))
538 if "use_version" in of_g.unified[cls][v]:
Andreas Wundsam53256162013-05-02 14:05:53 -0700539 log("cls %s: v %d mapped to %d" % (str(cls), v,
Rich Lanea06d0c32013-03-25 08:52:03 -0700540 of_g.unified[cls][v]["use_version"]))
541 if "members" in of_g.unified[cls][v]:
542 for member in of_g.unified[cls][v]["members"]:
543 log(" %-20s: type %-20s. offset %3d" %
544 (member["name"], member["m_type"],
545 member["offset"]))
546
547def generate_all_files():
548 """
549 Create the files for the language target
550 """
551 for (name, fn) in lang_module.targets.items():
552 path = of_g.options.install_dir + '/' + name
553 os.system("mkdir -p %s" % os.path.dirname(path))
554 with open(path, "w") as outfile:
555 fn(outfile, os.path.basename(name))
556 print("Wrote contents for " + name)
557
558if __name__ == '__main__':
559 of_g.loxigen_log_file = open("loxigen.log", "w")
560 of_g.loxigen_dbg_file = sys.stdout
561
562 of_g.process_commandline()
563 # @fixme Use command line params to select log
564
565 if not config_sanity_check():
566 debug("Config sanity check failed\n")
567 sys.exit(1)
568
569 # Import the language file
570 lang_file = "lang_%s" % of_g.options.lang
571 lang_module = __import__(lang_file)
572
573 # If list files, just list auto-gen files to stdout and exit
574 if of_g.options.list_files:
575 for name in lang_module.targets:
576 print of_g.options.install_dir + '/' + name
577 sys.exit(0)
578
579 log("\nGenerating files for target language %s\n" % of_g.options.lang)
580
Rich Lanea06d0c32013-03-25 08:52:03 -0700581 initialize_versions()
582 read_input()
583 add_extra_classes()
584 analyze_input()
585 unify_input()
586 order_and_assign_object_ids()
587 log_all_class_info()
588 generate_all_files()