blob: 9b19af6f3fb71e7d82bd5f932a26a277b8099e1a [file] [log] [blame]
Andreas Wundsam76db0062013-11-15 13:34:41 -08001# Copyright 2013, Big Switch Networks, Inc.
2#
3# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
4# the following special exception:
5#
6# LOXI Exception
7#
8# As a special exception to the terms of the EPL, you may distribute libraries
9# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
10# that copyright and licensing notices generated by LoxiGen are not altered or removed
11# from the LoxiGen Libraries and the notice provided below is (i) included in
12# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
13# documentation for the LoxiGen Libraries, if distributed in binary form.
14#
15# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
16#
17# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
18# a copy of the EPL at:
19#
20# http://www.eclipse.org/legal/epl-v10.html
21#
22# Unless required by applicable law or agreed to in writing, software
23# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
25# EPL for the specific language governing permissions and limitations
26# under the EPL.
27
28import sys
29
30import re
31import string
32import os
33import glob
34import copy
35import collections
36import c_gen.of_g_legacy as of_g
37import c_gen.type_maps as type_maps
38import c_gen.loxi_utils_legacy as loxi_utils
39import loxi_globals
40import c_gen.identifiers as identifiers
41import pyparsing
42import loxi_front_end.parser as parser
43import c_gen.translation as translation
44import loxi_front_end.frontend as frontend
45from loxi_ir import *
46from generic_utils import *
47
48root_dir = os.path.dirname(os.path.realpath(__file__))
49
50versions = {}
51# TODO: Put these in a class so they get documented
52
53## Dict indexed by version giving all info related to version
54#
55# This is local; after processing, the information is stored in
56# of_g variables.
57
58def add_class(wire_version, cls, members):
59 """
60 Process a class for the given version and update the unified
61 list of classes as needed.
62
63 @param wire_version The wire version for this class defn
64 @param cls The name of the class being added
65 @param members The list of members with offsets calculated
66 """
67 memid = 0
68
69 sig = loxi_utils.class_signature(members)
70 if cls in of_g.unified:
71 uc = of_g.unified[cls]
72 if wire_version in uc:
73 debug("Error adding %s to unified. Wire ver %d exists" %
74 (cls, wire_version))
75 sys.exit(1)
76 uc[wire_version] = {}
77 # Check for a matching signature
78 for wver in uc:
79 if type(wver) != type(0): continue
80 if wver == wire_version: continue
81 if not "use_version" in uc[wver]:
82 if sig == loxi_utils.class_signature(uc[wver]["members"]):
83 log("Matched %s, ver %d to ver %d" %
84 (cls, wire_version, wver))
85 # have a match with existing version
86 uc[wire_version]["use_version"] = wver
87 # What else to do?
88 return
89 else: # Haven't seen this entry before
90 log("Adding %s to unified list, ver %d" % (cls, wire_version))
91 of_g.unified[cls] = dict(union={})
92 uc = of_g.unified[cls]
93
94 # At this point, need to add members for this version
95 uc[wire_version] = dict(members = members)
96
97 # Per member processing:
98 # Add to union list (I'm sure there's a better way)
99 # Check if it's a list
100 union = uc["union"]
101 if not cls in of_g.ordered_members:
102 of_g.ordered_members[cls] = []
103 for member in members:
104 m_name = member["name"]
105 m_type = member["m_type"]
106 if m_name.find("pad") == 0:
107 continue
108 if m_name in union:
109 if not m_type == union[m_name]["m_type"]:
110 debug("ERROR: CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
111 (cls, wire_version, m_name, m_type))
112 debug(" Type conflict adding member to unified set.")
113 debug(" Current union[%s]:" % m_name)
114 debug(union[m_name])
115 sys.exit(1)
116 else:
117 union[m_name] = dict(m_type=m_type, memid=memid)
118 memid += 1
119 if not m_name in of_g.ordered_members[cls]:
120 of_g.ordered_members[cls].append(m_name)
121
122def update_offset(cls, wire_version, name, offset, m_type):
123 """
124 Update (and return) the offset based on type.
125 @param cls The parent class
126 @param wire_version The wire version being processed
127 @param name The name of the data member
128 @param offset The current offset
129 @param m_type The type declaration being processed
130 @returns A pair (next_offset, len_update) next_offset is the new offset
131 of the next object or -1 if this is a var-length object. len_update
132 is the increment that should be added to the length. Note that (for
133 of_match_v3) it is variable length, but it adds 8 bytes to the fixed
134 length of the object
135 If offset is already -1, do not update
136 Otherwise map to base type and count and update (if possible)
137 """
138 if offset < 0: # Don't update offset once set to -1
139 return offset, 0
140
141 count, base_type = loxi_utils.type_dec_to_count_base(m_type)
142
143 len_update = 0
144 if base_type in of_g.of_mixed_types:
145 base_type = of_g.of_mixed_types[base_type][wire_version]
146
147 base_class = base_type[:-2]
148 if (base_class, wire_version) in of_g.is_fixed_length:
149 bytes = of_g.base_length[(base_class, wire_version)]
150 else:
151 if base_type == "of_match_v3_t":
152 # This is a special case: it has non-zero min length
153 # but is variable length
154 bytes = -1
155 len_update = 8
Rich Lane90020b42014-04-07 12:05:45 -0700156 elif base_type == "of_oxm_header_t":
157 # This is a special case: it has non-zero min length
158 # but is variable length
159 bytes = -1
160 len_update = 4
Wilson Ngd6181882014-04-14 16:28:35 -0700161 elif base_type == "of_bsn_vport_header_t":
162 # This is a special case: it has non-zero min length
163 # but is variable length
164 bytes = -1
165 len_update = 4
Andreas Wundsam76db0062013-11-15 13:34:41 -0800166 elif base_type in of_g.of_base_types:
167 bytes = of_g.of_base_types[base_type]["bytes"]
168 else:
169 print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
170 log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
171 bytes = -1
172
173 # If bytes
174 if bytes > 0:
175 len_update = count * bytes
176
177 if bytes == -1:
178 return -1, len_update
179
180 return offset + (count * bytes), len_update
181
182def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
183 """
184 Generate the offsets for fixed offset class members
185 Also calculate the class_sizes when possible.
186
187 @param classes The classes to process
188 @param wire_version The wire version for this set of classes
189
190 Updates global variables
191 """
192
193 lists = set()
194
195 # Generate offsets
196 for cls in ordered_classes:
197 fixed_offset = 0 # The last "good" offset seen
198 offset = 0
199 last_offset = 0
200 last_name = "-"
201 for member in classes[cls]:
202 m_type = member["m_type"]
203 name = member["name"]
204 if last_offset == -1:
205 if name == "pad":
206 log("Skipping pad for special offset for %s" % cls)
207 else:
208 log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
209 (name, last_name, cls, wire_version))
210 if (((cls, name) in of_g.special_offsets) and
211 (of_g.special_offsets[(cls, name)] != last_name)):
212 debug("ERROR: special offset prev name changed")
213 debug(" cls %s. name %s. version %d. was %s. now %s" %
214 cls, name, wire_version,
215 of_g.special_offsets[(cls, name)], last_name)
216 sys.exit(1)
217 of_g.special_offsets[(cls, name)] = last_name
218
219 member["offset"] = offset
220 if m_type.find("list(") == 0:
221 (list_name, base_type) = loxi_utils.list_name_extract(m_type)
222 lists.add(list_name)
223 member["m_type"] = list_name + "_t"
224 offset = -1
225 elif m_type.find("struct") == 0:
226 debug("ERROR found struct: %s.%s " % (cls, name))
227 sys.exit(1)
228 elif m_type == "octets":
229 log("offset gen skipping octets: %s.%s " % (cls, name))
230 offset = -1
231 else:
232 offset, len_update = update_offset(cls, wire_version, name,
233 offset, m_type)
234 if offset != -1:
235 fixed_offset = offset
236 else:
237 fixed_offset += len_update
238 log("offset is -1 for %s.%s version %d " %
239 (cls, name, wire_version))
240 last_offset = offset
241 last_name = name
242 of_g.base_length[(cls, wire_version)] = fixed_offset
243 if (offset != -1):
244 of_g.is_fixed_length.add((cls, wire_version))
Rich Lane2cc2b862014-06-13 14:50:17 -0700245
Andreas Wundsam76db0062013-11-15 13:34:41 -0800246 for list_type in lists:
247 classes[list_type] = []
248 of_g.ordered_classes[wire_version].append(list_type)
249 of_g.base_length[(list_type, wire_version)] = 0
250
251def order_and_assign_object_ids():
252 """
253 Order all classes and assign object ids to all classes.
254
255 This is done to promote a reasonable order of the objects, putting
256 messages first followed by non-messages. No assumptions should be
257 made about the order, nor about contiguous numbering. However, the
258 numbers should all be reasonably small allowing arrays indexed by
259 these enum values to be defined.
260 """
261
262 # Generate separate message and non-message ordered lists
263 for cls in of_g.unified:
264 if loxi_utils.class_is_message(cls):
265 of_g.ordered_messages.append(cls)
266 elif loxi_utils.class_is_list(cls):
267 of_g.ordered_list_objects.append(cls)
268 else:
269 of_g.ordered_non_messages.append(cls)
270
271 of_g.ordered_messages.sort()
272 of_g.ordered_pseudo_objects.sort()
273 of_g.ordered_non_messages.sort()
274 of_g.ordered_list_objects.sort()
275 of_g.standard_class_order.extend(of_g.ordered_messages)
276 of_g.standard_class_order.extend(of_g.ordered_non_messages)
277 of_g.standard_class_order.extend(of_g.ordered_list_objects)
278
279 # This includes pseudo classes for which most code is not generated
280 of_g.all_class_order.extend(of_g.ordered_messages)
281 of_g.all_class_order.extend(of_g.ordered_non_messages)
282 of_g.all_class_order.extend(of_g.ordered_list_objects)
283 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
284
285 # Assign object IDs
286 for cls in of_g.ordered_messages:
287 of_g.unified[cls]["object_id"] = of_g.object_id
288 of_g.object_id += 1
289 for cls in of_g.ordered_non_messages:
290 of_g.unified[cls]["object_id"] = of_g.object_id
291 of_g.object_id += 1
292 for cls in of_g.ordered_list_objects:
293 of_g.unified[cls]["object_id"] = of_g.object_id
294 of_g.object_id += 1
Andreas Wundsam76db0062013-11-15 13:34:41 -0800295
296
297def initialize_versions():
298 """
299 Create an empty datastructure for each target version.
300 """
301
302 for version in loxi_globals.OFVersions.target_versions:
303 wire_version = version.wire_version
304 version_name = of_g.of_version_wire2name[wire_version]
305 of_g.wire_ver_map[wire_version] = version_name
306 versions[version_name] = dict(
307 version_name = version_name,
308 wire_version = wire_version,
309 classes = {})
310 of_g.ordered_classes[wire_version] = []
311
312 of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
313
314def build_ordered_classes():
315 """
316 Read in from files given on command line and update global state
317
318 @fixme Should select versions to support from command line
319 """
320
321 for version, protocol in loxi_globals.ir.items():
322 wire_version = version.wire_version
323 # Populate global state
324 version_name = of_g.of_version_wire2name[wire_version]
325
326 for ofclass in protocol.classes:
Andreas Wundsam76db0062013-11-15 13:34:41 -0800327 of_g.ordered_classes[wire_version].append(ofclass.name)
328 legacy_members = []
329 pad_count = 0
330 for m in ofclass.members:
331 if type(m) == OFPadMember:
332 m_name = 'pad%d' % pad_count
333 if m_name == 'pad0': m_name = 'pad'
334 legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
335 name=m_name))
336 pad_count += 1
337 else:
338 # HACK the C backend does not yet support of_oxm_t
339 if m.oftype == 'of_oxm_t':
Rich Lane90020b42014-04-07 12:05:45 -0700340 m_type = 'of_oxm_header_t'
Wilson Ngd6181882014-04-14 16:28:35 -0700341 # HACK the C backend does not yet support of_bsn_vport_t
342 elif m.oftype == 'of_bsn_vport_t':
343 m_type = 'of_bsn_vport_header_t'
Andreas Wundsam76db0062013-11-15 13:34:41 -0800344 else:
345 enum = find(lambda e: e.name == m.oftype, protocol.enums)
346 if enum and "wire_type" in enum.params:
347 m_type = enum.params["wire_type"]
348 else:
349 m_type = m.oftype
350 legacy_members.append(dict(m_type=m_type, name=m.name))
351 versions[version_name]['classes'][ofclass.name] = legacy_members
352
353 for enum in protocol.enums:
354 for entry in enum.entries:
355 identifiers.add_identifier(
356 translation.loxi_name(entry.name),
357 entry.name, enum.name, entry.value, wire_version,
358 of_g.identifiers, of_g.identifiers_by_group)
359
360def populate_type_maps():
361 """
362 Use the type members in the IR to fill out the legacy type_maps.
363 """
364
365 def split_inherited_cls(cls):
366 if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
367 return None, None
368 for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
369 if cls.startswith(parent):
370 return (parent, cls[len(parent)+1:])
371 return None, None
372
373 def find_experimenter(parent, cls):
374 for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
375 prefix = parent + '_' + experimenter
376 if cls.startswith(prefix) and cls != prefix:
377 return experimenter
378 return None
379
380 def find_type_value(ofclass, m_name):
381 for m in ofclass.members:
382 if isinstance(m, OFTypeMember) and m.name == m_name:
383 return m.value
384 raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
385
386 # Most inheritance classes: actions, instructions, etc
387 for version, protocol in loxi_globals.ir.items():
388 wire_version = version.wire_version
389 for ofclass in protocol.classes:
390 cls = ofclass.name
391 parent, subcls = split_inherited_cls(cls)
392 if not (parent and subcls):
393 continue
394 if parent == 'of_oxm':
395 type_len = find_type_value(ofclass, 'type_len')
396 oxm_class = (type_len >> 16) & 0xffff
397 if oxm_class != 0x8000:
398 # Do not include experimenter OXMs in the main table
399 val = type_maps.invalid_type
400 else:
401 val = (type_len >> 8) & 0xff
402 else:
403 val = find_type_value(ofclass, 'type')
404 type_maps.inheritance_data[parent][wire_version][subcls] = val
405
406 # Extensions (only actions for now)
407 experimenter = find_experimenter(parent, cls)
408 if parent == 'of_action' and experimenter:
409 val = find_type_value(ofclass, 'subtype')
410 type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
411 if wire_version >= of_g.VERSION_1_3:
412 cls2 = parent + "_id" + cls[len(parent):]
413 type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
Rich Laneef7b9942013-11-18 16:29:28 -0800414 elif parent == 'of_instruction' and experimenter:
415 val = find_type_value(ofclass, 'subtype')
416 type_maps.extension_instruction_subtype[wire_version][experimenter][cls] = val
Andreas Wundsam76db0062013-11-15 13:34:41 -0800417
418 # Messages
419 for version, protocol in loxi_globals.ir.items():
420 wire_version = version.wire_version
421 for ofclass in protocol.classes:
422 cls = ofclass.name
423 # HACK (though this is what loxi_utils.class_is_message() does)
424 if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
425 continue
426 if type_maps.class_is_virtual(cls):
427 continue
Andreas Wundsam76db0062013-11-15 13:34:41 -0800428 subcls = cls[3:]
429 val = find_type_value(ofclass, 'type')
430 if not val in type_maps.message_types[wire_version].values():
431 type_maps.message_types[wire_version][subcls] = val
432
433 # Extensions
434 experimenter = find_experimenter('of', cls)
Andreas Wundsam2c0a2d72013-11-15 15:16:36 -0800435 if experimenter and ofclass.is_subclassof("of_experimenter"):
Andreas Wundsam76db0062013-11-15 13:34:41 -0800436 val = find_type_value(ofclass, 'subtype')
437 type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
438
439 type_maps.generate_maps()
440
441def analyze_input():
442 """
443 Add information computed from the input, including offsets and
444 lengths of struct members and the set of list and action_id types.
445 """
446
447 # Generate header classes for inheritance parents
448 for wire_version, ordered_classes in of_g.ordered_classes.items():
449 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
450 for cls in ordered_classes:
451 if cls in type_maps.inheritance_map:
452 new_cls = cls + '_header'
453 of_g.ordered_classes[wire_version].append(new_cls)
454 classes[new_cls] = classes[cls]
455
Andreas Wundsam76db0062013-11-15 13:34:41 -0800456 for wire_version in of_g.wire_ver_map.keys():
457 version_name = of_g.of_version_wire2name[wire_version]
458 calculate_offsets_and_lengths(
459 of_g.ordered_classes[wire_version],
460 versions[version_name]['classes'],
461 wire_version)
462
463def unify_input():
464 """
465 Create Unified View of Objects
466 """
467
468 global versions
469
470 # Add classes to unified in wire-format order so that it is easier
471 # to generate things later
472 keys = versions.keys()
473 keys.sort(reverse=True)
474 for version in keys:
475 wire_version = versions[version]["wire_version"]
476 classes = versions[version]["classes"]
477 for cls in of_g.ordered_classes[wire_version]:
478 add_class(wire_version, cls, classes[cls])