blob: 4d37b36ca82b472665486787da6721a1dbbbf1a9 [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))
245 for list_type in lists:
246 classes[list_type] = []
247 of_g.ordered_classes[wire_version].append(list_type)
248 of_g.base_length[(list_type, wire_version)] = 0
249
250def order_and_assign_object_ids():
251 """
252 Order all classes and assign object ids to all classes.
253
254 This is done to promote a reasonable order of the objects, putting
255 messages first followed by non-messages. No assumptions should be
256 made about the order, nor about contiguous numbering. However, the
257 numbers should all be reasonably small allowing arrays indexed by
258 these enum values to be defined.
259 """
260
261 # Generate separate message and non-message ordered lists
262 for cls in of_g.unified:
263 if loxi_utils.class_is_message(cls):
264 of_g.ordered_messages.append(cls)
265 elif loxi_utils.class_is_list(cls):
266 of_g.ordered_list_objects.append(cls)
267 else:
268 of_g.ordered_non_messages.append(cls)
269
270 of_g.ordered_messages.sort()
271 of_g.ordered_pseudo_objects.sort()
272 of_g.ordered_non_messages.sort()
273 of_g.ordered_list_objects.sort()
274 of_g.standard_class_order.extend(of_g.ordered_messages)
275 of_g.standard_class_order.extend(of_g.ordered_non_messages)
276 of_g.standard_class_order.extend(of_g.ordered_list_objects)
277
278 # This includes pseudo classes for which most code is not generated
279 of_g.all_class_order.extend(of_g.ordered_messages)
280 of_g.all_class_order.extend(of_g.ordered_non_messages)
281 of_g.all_class_order.extend(of_g.ordered_list_objects)
282 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
283
284 # Assign object IDs
285 for cls in of_g.ordered_messages:
286 of_g.unified[cls]["object_id"] = of_g.object_id
287 of_g.object_id += 1
288 for cls in of_g.ordered_non_messages:
289 of_g.unified[cls]["object_id"] = of_g.object_id
290 of_g.object_id += 1
291 for cls in of_g.ordered_list_objects:
292 of_g.unified[cls]["object_id"] = of_g.object_id
293 of_g.object_id += 1
Andreas Wundsam76db0062013-11-15 13:34:41 -0800294
295
296def initialize_versions():
297 """
298 Create an empty datastructure for each target version.
299 """
300
301 for version in loxi_globals.OFVersions.target_versions:
302 wire_version = version.wire_version
303 version_name = of_g.of_version_wire2name[wire_version]
304 of_g.wire_ver_map[wire_version] = version_name
305 versions[version_name] = dict(
306 version_name = version_name,
307 wire_version = wire_version,
308 classes = {})
309 of_g.ordered_classes[wire_version] = []
310
311 of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
312
313def build_ordered_classes():
314 """
315 Read in from files given on command line and update global state
316
317 @fixme Should select versions to support from command line
318 """
319
320 for version, protocol in loxi_globals.ir.items():
321 wire_version = version.wire_version
322 # Populate global state
323 version_name = of_g.of_version_wire2name[wire_version]
324
325 for ofclass in protocol.classes:
Andreas Wundsam76db0062013-11-15 13:34:41 -0800326 of_g.ordered_classes[wire_version].append(ofclass.name)
327 legacy_members = []
328 pad_count = 0
329 for m in ofclass.members:
330 if type(m) == OFPadMember:
331 m_name = 'pad%d' % pad_count
332 if m_name == 'pad0': m_name = 'pad'
333 legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
334 name=m_name))
335 pad_count += 1
336 else:
337 # HACK the C backend does not yet support of_oxm_t
338 if m.oftype == 'of_oxm_t':
Rich Lane90020b42014-04-07 12:05:45 -0700339 m_type = 'of_oxm_header_t'
Wilson Ngd6181882014-04-14 16:28:35 -0700340 # HACK the C backend does not yet support of_bsn_vport_t
341 elif m.oftype == 'of_bsn_vport_t':
342 m_type = 'of_bsn_vport_header_t'
Andreas Wundsam76db0062013-11-15 13:34:41 -0800343 else:
344 enum = find(lambda e: e.name == m.oftype, protocol.enums)
345 if enum and "wire_type" in enum.params:
346 m_type = enum.params["wire_type"]
347 else:
348 m_type = m.oftype
349 legacy_members.append(dict(m_type=m_type, name=m.name))
350 versions[version_name]['classes'][ofclass.name] = legacy_members
351
352 for enum in protocol.enums:
353 for entry in enum.entries:
354 identifiers.add_identifier(
355 translation.loxi_name(entry.name),
356 entry.name, enum.name, entry.value, wire_version,
357 of_g.identifiers, of_g.identifiers_by_group)
358
359def populate_type_maps():
360 """
361 Use the type members in the IR to fill out the legacy type_maps.
362 """
363
364 def split_inherited_cls(cls):
365 if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
366 return None, None
367 for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
368 if cls.startswith(parent):
369 return (parent, cls[len(parent)+1:])
370 return None, None
371
372 def find_experimenter(parent, cls):
373 for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
374 prefix = parent + '_' + experimenter
375 if cls.startswith(prefix) and cls != prefix:
376 return experimenter
377 return None
378
379 def find_type_value(ofclass, m_name):
380 for m in ofclass.members:
381 if isinstance(m, OFTypeMember) and m.name == m_name:
382 return m.value
383 raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
384
385 # Most inheritance classes: actions, instructions, etc
386 for version, protocol in loxi_globals.ir.items():
387 wire_version = version.wire_version
388 for ofclass in protocol.classes:
389 cls = ofclass.name
390 parent, subcls = split_inherited_cls(cls)
391 if not (parent and subcls):
392 continue
393 if parent == 'of_oxm':
394 type_len = find_type_value(ofclass, 'type_len')
395 oxm_class = (type_len >> 16) & 0xffff
396 if oxm_class != 0x8000:
397 # Do not include experimenter OXMs in the main table
398 val = type_maps.invalid_type
399 else:
400 val = (type_len >> 8) & 0xff
401 else:
402 val = find_type_value(ofclass, 'type')
403 type_maps.inheritance_data[parent][wire_version][subcls] = val
404
405 # Extensions (only actions for now)
406 experimenter = find_experimenter(parent, cls)
407 if parent == 'of_action' and experimenter:
408 val = find_type_value(ofclass, 'subtype')
409 type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
410 if wire_version >= of_g.VERSION_1_3:
411 cls2 = parent + "_id" + cls[len(parent):]
412 type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
Rich Laneef7b9942013-11-18 16:29:28 -0800413 elif parent == 'of_instruction' and experimenter:
414 val = find_type_value(ofclass, 'subtype')
415 type_maps.extension_instruction_subtype[wire_version][experimenter][cls] = val
Andreas Wundsam76db0062013-11-15 13:34:41 -0800416
417 # Messages
418 for version, protocol in loxi_globals.ir.items():
419 wire_version = version.wire_version
420 for ofclass in protocol.classes:
421 cls = ofclass.name
422 # HACK (though this is what loxi_utils.class_is_message() does)
423 if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
424 continue
425 if type_maps.class_is_virtual(cls):
426 continue
Andreas Wundsam76db0062013-11-15 13:34:41 -0800427 subcls = cls[3:]
428 val = find_type_value(ofclass, 'type')
429 if not val in type_maps.message_types[wire_version].values():
430 type_maps.message_types[wire_version][subcls] = val
431
432 # Extensions
433 experimenter = find_experimenter('of', cls)
Andreas Wundsam2c0a2d72013-11-15 15:16:36 -0800434 if experimenter and ofclass.is_subclassof("of_experimenter"):
Andreas Wundsam76db0062013-11-15 13:34:41 -0800435 val = find_type_value(ofclass, 'subtype')
436 type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
437
438 type_maps.generate_maps()
439
440def analyze_input():
441 """
442 Add information computed from the input, including offsets and
443 lengths of struct members and the set of list and action_id types.
444 """
445
446 # Generate header classes for inheritance parents
447 for wire_version, ordered_classes in of_g.ordered_classes.items():
448 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
449 for cls in ordered_classes:
450 if cls in type_maps.inheritance_map:
451 new_cls = cls + '_header'
452 of_g.ordered_classes[wire_version].append(new_cls)
453 classes[new_cls] = classes[cls]
454
Andreas Wundsam76db0062013-11-15 13:34:41 -0800455 for wire_version in of_g.wire_ver_map.keys():
456 version_name = of_g.of_version_wire2name[wire_version]
457 calculate_offsets_and_lengths(
458 of_g.ordered_classes[wire_version],
459 versions[version_name]['classes'],
460 wire_version)
461
462def unify_input():
463 """
464 Create Unified View of Objects
465 """
466
467 global versions
468
469 # Add classes to unified in wire-format order so that it is easier
470 # to generate things later
471 keys = versions.keys()
472 keys.sort(reverse=True)
473 for version in keys:
474 wire_version = versions[version]["wire_version"]
475 classes = versions[version]["classes"]
476 for cls in of_g.ordered_classes[wire_version]:
477 add_class(wire_version, cls, classes[cls])