|
| 1 | +#! /usr/bin/python |
| 2 | +# Copyright 2012 Cumulus Networks LLC, all rights reserved |
| 3 | + |
| 4 | +############################################################################# |
| 5 | +# Base eeprom class containing the main logic for reading, writing, and |
| 6 | +# setting the eeprom. The format definition is a list of tuples of: |
| 7 | +# ('data name', 'data type', 'size in bytes') |
| 8 | +# data type is one of 's', 'C', and 'x' (string, char, and ignore) |
| 9 | +# 'burn' as a data name indicates the corresponding number of bytes are to |
| 10 | +# be ignored |
| 11 | + |
| 12 | +from __future__ import print_function |
| 13 | + |
| 14 | +try: |
| 15 | + import exceptions # Python 2 |
| 16 | +except ImportError: |
| 17 | + import builtins as exceptions # Python 3 |
| 18 | +try: |
| 19 | + import binascii |
| 20 | + import optparse |
| 21 | + import os |
| 22 | + import io |
| 23 | + import sys |
| 24 | + import struct |
| 25 | + import subprocess |
| 26 | + import fcntl |
| 27 | +except ImportError as e: |
| 28 | + raise ImportError (str(e) + "- required module not found") |
| 29 | + |
| 30 | + |
| 31 | +class EepromDecoder(object): |
| 32 | + def __init__(self, path, format, start, status, readonly): |
| 33 | + self.p = path |
| 34 | + self.f = format |
| 35 | + self.s = start |
| 36 | + self.u = status |
| 37 | + self.r = readonly |
| 38 | + self.cache_name = None |
| 39 | + self.cache_update_needed = False |
| 40 | + self.lock_file = None |
| 41 | + |
| 42 | + def check_status(self): |
| 43 | + if self.u != '': |
| 44 | + F = open(self.u, "r") |
| 45 | + d = F.readline().rstrip() |
| 46 | + F.close() |
| 47 | + return d |
| 48 | + else: |
| 49 | + return 'ok' |
| 50 | + |
| 51 | + def set_cache_name(self, name): |
| 52 | + # before accessing the eeprom we acquire an exclusive lock on the eeprom file. |
| 53 | + # this will prevent a race condition where multiple instances of this app |
| 54 | + # could try to update the cache at the same time |
| 55 | + self.cache_name = name |
| 56 | + self.lock_file = open(self.p, 'r') |
| 57 | + fcntl.flock(self.lock_file, fcntl.LOCK_EX) |
| 58 | + |
| 59 | + def is_read_only(self): |
| 60 | + return self.r |
| 61 | + |
| 62 | + def decoder(self, s, t): |
| 63 | + return t |
| 64 | + |
| 65 | + def encoder(self, I, v): |
| 66 | + return v |
| 67 | + |
| 68 | + def checksum_field_size(self): |
| 69 | + return 4 # default |
| 70 | + |
| 71 | + def is_checksum_field(self, I): |
| 72 | + return I[0] == 'crc' # default |
| 73 | + |
| 74 | + def checksum_type(self): |
| 75 | + return 'crc32' |
| 76 | + |
| 77 | + def encode_checksum(self, crc): |
| 78 | + if self.checksum_field_size() == 4: |
| 79 | + return struct.pack('>I', crc) |
| 80 | + elif self.checksum_field_size() == 1: |
| 81 | + return struct.pack('>B', crc) |
| 82 | + print('checksum type not yet supported') |
| 83 | + exit(1) |
| 84 | + |
| 85 | + def compute_2s_complement(self, e, size): |
| 86 | + crc = 0 |
| 87 | + loc = 0 |
| 88 | + end = len(e) |
| 89 | + while loc != end: |
| 90 | + crc += int('0x' + binascii.b2a_hex(e[loc:loc+size]), 0) |
| 91 | + loc += size |
| 92 | + T = 1 << (size * 8) |
| 93 | + return (T - crc) & (T - 1) |
| 94 | + |
| 95 | + def compute_dell_crc(self, message): |
| 96 | + poly = 0x8005 |
| 97 | + reg = 0x0000 |
| 98 | + message += '\x00\x00' |
| 99 | + for byte in message: |
| 100 | + mask = 0x80 |
| 101 | + while (mask > 0): |
| 102 | + reg<<=1 |
| 103 | + if ord(byte) & mask: |
| 104 | + reg += 1 |
| 105 | + mask>>=1 |
| 106 | + if reg > 0xffff: |
| 107 | + reg &= 0xffff |
| 108 | + reg ^= poly |
| 109 | + return reg |
| 110 | + |
| 111 | + def calculate_checksum(self, e): |
| 112 | + if self.checksum_type() == 'crc32': |
| 113 | + return binascii.crc32(e) & 0xffffffff |
| 114 | + |
| 115 | + if self.checksum_type() == '2s-complement': |
| 116 | + size = self.checksum_field_size() |
| 117 | + return self.compute_2s_complement(e, size) |
| 118 | + |
| 119 | + if self.checksum_type() == 'dell-crc': |
| 120 | + return self.compute_dell_crc(e) |
| 121 | + print('checksum type not yet supported') |
| 122 | + exit(1) |
| 123 | + |
| 124 | + def is_checksum_valid(self, e): |
| 125 | + offset = 0 - self.checksum_field_size() |
| 126 | + crc = self.calculate_checksum(e[:offset]) |
| 127 | + |
| 128 | + loc = 0 |
| 129 | + for I in self.f: |
| 130 | + end = loc + I[2] |
| 131 | + t = e[loc:end] |
| 132 | + loc = end |
| 133 | + if self.is_checksum_field(I): |
| 134 | + i = self.decoder(I[0], t) |
| 135 | + if int(i, 0) == crc: |
| 136 | + return (True, crc) |
| 137 | + else: |
| 138 | + return (False, crc) |
| 139 | + else: |
| 140 | + continue |
| 141 | + return (False, crc) |
| 142 | + |
| 143 | + def decode_eeprom(self, e): |
| 144 | + loc = 0 |
| 145 | + for I in self.f: |
| 146 | + end = loc + I[2] |
| 147 | + t = e[loc:end] |
| 148 | + loc = end |
| 149 | + if I[0] == 'burn': |
| 150 | + continue |
| 151 | + elif I[1] == 's': |
| 152 | + i = t |
| 153 | + else: |
| 154 | + i = self.decoder(I[0], t) |
| 155 | + print("%-20s: %s" %(I[0], i)) |
| 156 | + |
| 157 | + def set_eeprom(self, e, cmd_args): |
| 158 | + line = '' |
| 159 | + loc = 0 |
| 160 | + ndict = {} |
| 161 | + fields = list(I[0] for I in list(self.f)) |
| 162 | + if len(cmd_args): |
| 163 | + for arg in cmd_args[0].split(','): |
| 164 | + k, v = arg.split('=') |
| 165 | + k = k.strip() |
| 166 | + v = v.strip() |
| 167 | + if k not in fields: |
| 168 | + print("Error: invalid field '%s'" %(k)) |
| 169 | + exit(1) |
| 170 | + ndict[k] = v |
| 171 | + |
| 172 | + for I in self.f: |
| 173 | + # print the original value |
| 174 | + end = loc + I[2] |
| 175 | + sl = e[loc:end] |
| 176 | + loc = end |
| 177 | + if I[0] == 'burn': |
| 178 | + #line += sl |
| 179 | + # fill with zeros |
| 180 | + line = line.ljust(len(line) + I[2], '\x00') |
| 181 | + continue |
| 182 | + elif I[1] == 's': |
| 183 | + i = sl |
| 184 | + else: |
| 185 | + i = self.decoder(I[0], sl) |
| 186 | + |
| 187 | + if len(cmd_args) == 0: |
| 188 | + if self.is_checksum_field(I): |
| 189 | + print("%-20s: %s " %(I[0], i)) |
| 190 | + continue |
| 191 | + |
| 192 | + # prompt for new value |
| 193 | + v = raw_input("%-20s: [%s] " %(I[0], i)) |
| 194 | + if v == '': |
| 195 | + v = i |
| 196 | + else: |
| 197 | + if I[0] not in ndict.keys(): |
| 198 | + v = i |
| 199 | + else: |
| 200 | + v = ndict[I[0]] |
| 201 | + |
| 202 | + line += self.encoder(I, v) |
| 203 | + |
| 204 | + # compute and append crc at the end |
| 205 | + crc = self.encode_checksum(self.calculate_checksum(line)) |
| 206 | + |
| 207 | + line += crc |
| 208 | + |
| 209 | + return line |
| 210 | + |
| 211 | + def open_eeprom(self): |
| 212 | + ''' |
| 213 | + Open the EEPROM device file. |
| 214 | + If a cache file exists, use that instead of the EEPROM. |
| 215 | + ''' |
| 216 | + using_eeprom = True |
| 217 | + eeprom_file = self.p |
| 218 | + try: |
| 219 | + if os.path.isfile(self.cache_name): |
| 220 | + eeprom_file = self.cache_name |
| 221 | + using_eeprom = False |
| 222 | + except: |
| 223 | + pass |
| 224 | + self.cache_update_needed = using_eeprom |
| 225 | + return io.open(eeprom_file, "rb") |
| 226 | + |
| 227 | + def read_eeprom(self): |
| 228 | + sizeof_info = 0 |
| 229 | + for I in self.f: |
| 230 | + sizeof_info += I[2] |
| 231 | + o = self.read_eeprom_bytes(sizeof_info) |
| 232 | + return o |
| 233 | + |
| 234 | + def read_eeprom_bytes(self, byteCount, offset=0): |
| 235 | + F = self.open_eeprom() |
| 236 | + F.seek(self.s + offset) |
| 237 | + o = F.read(byteCount) |
| 238 | + |
| 239 | + # If we read from the cache file and the byte count isn't what we |
| 240 | + # expect, the file may be corrupt. Delete it and try again, this |
| 241 | + # time reading from the actual EEPROM. |
| 242 | + if len(o) != byteCount and not self.cache_update_needed: |
| 243 | + os.remove(self.cache_name) |
| 244 | + self.cache_update_needed = True |
| 245 | + F.close() |
| 246 | + F = self.open_eeprom() |
| 247 | + F.seek(self.s + offset) |
| 248 | + o = F.read(byteCount) |
| 249 | + |
| 250 | + if len(o) != byteCount: |
| 251 | + raise RuntimeError("Expected to read %d bytes from %s, " |
| 252 | + % (byteCount, self.p) + |
| 253 | + "but only read %d" % (len(o))) |
| 254 | + F.close() |
| 255 | + return o |
| 256 | + |
| 257 | + def read_eeprom_db(self): |
| 258 | + return 0 |
| 259 | + |
| 260 | + def write_eeprom(self, e): |
| 261 | + F = open(self.p, "wb") |
| 262 | + F.seek(self.s) |
| 263 | + F.write(e) |
| 264 | + F.close() |
| 265 | + self.write_cache(e) |
| 266 | + |
| 267 | + def write_cache(self, e): |
| 268 | + if self.cache_name: |
| 269 | + F = open(self.cache_name, "wb") |
| 270 | + F.seek(self.s) |
| 271 | + F.write(e) |
| 272 | + F.close() |
| 273 | + |
| 274 | + def update_cache(self, e): |
| 275 | + if self.cache_update_needed: |
| 276 | + self.write_cache(e) |
| 277 | + fcntl.flock(self.lock_file, fcntl.LOCK_UN) |
| 278 | + |
| 279 | + def update_eeprom_db(self, e): |
| 280 | + return 0 |
| 281 | + |
| 282 | + def diff_mac(self, mac1, mac2): |
| 283 | + if mac1 == '' or mac2 == '': |
| 284 | + return 0 |
| 285 | + mac1_octets = [] |
| 286 | + mac1_octets = mac1.split(':') |
| 287 | + mac1val = int(mac1_octets[5], 16) | int(mac1_octets[4], 16) << 8 | int(mac1_octets[3], 16) << 16 |
| 288 | + mac2_octets = [] |
| 289 | + mac2_octets = mac2.split(':') |
| 290 | + mac2val = int(mac2_octets[5], 16) | int(mac2_octets[4], 16) << 8 | int(mac2_octets[3], 16) << 16 |
| 291 | + # check oui matches |
| 292 | + if (mac1_octets[0] != mac2_octets[0] |
| 293 | + or mac1_octets[1] != mac2_octets[1] |
| 294 | + or mac1_octets[2] != mac2_octets[2]) : |
| 295 | + return 0 |
| 296 | + |
| 297 | + if mac2val < mac1val: |
| 298 | + return 0 |
| 299 | + |
| 300 | + return (mac2val - mac1val) |
| 301 | + |
| 302 | + def increment_mac(self, mac): |
| 303 | + if mac != "": |
| 304 | + mac_octets = [] |
| 305 | + mac_octets = mac.split(':') |
| 306 | + ret_mac = int(mac_octets[5], 16) | int(mac_octets[4], 16) << 8 | int(mac_octets[3], 16) << 16 |
| 307 | + ret_mac = ret_mac + 1 |
| 308 | + |
| 309 | + if (ret_mac & 0xff000000): |
| 310 | + print('Error: increment carries into OUI') |
| 311 | + return '' |
| 312 | + |
| 313 | + mac_octets[5] = hex(ret_mac & 0xff)[2:].zfill(2) |
| 314 | + mac_octets[4] = hex((ret_mac >> 8) & 0xff)[2:].zfill(2) |
| 315 | + mac_octets[3] = hex((ret_mac >> 16) & 0xff)[2:].zfill(2) |
| 316 | + |
| 317 | + return ':'.join(mac_octets).upper() |
| 318 | + |
| 319 | + return '' |
| 320 | + |
| 321 | + @classmethod |
| 322 | + def find_field(cls, e, name): |
| 323 | + if not hasattr(cls, 'brd_fmt'): |
| 324 | + raise RuntimeError("Class %s does not have brb_fmt" % cls) |
| 325 | + if not e: |
| 326 | + raise RuntimeError("EEPROM can not be empty") |
| 327 | + brd_fmt = cls.brd_fmt |
| 328 | + loc = 0 |
| 329 | + for f in brd_fmt: |
| 330 | + end = loc + f[2] |
| 331 | + t = e[loc:end] |
| 332 | + loc = end |
| 333 | + if f[0] == name: |
| 334 | + return t |
| 335 | + |
| 336 | + def base_mac_addr(self, e): |
| 337 | + ''' |
| 338 | + Returns the base MAC address found in the EEPROM. |
| 339 | +
|
| 340 | + Sub-classes must override this method as reading the EEPROM |
| 341 | + and finding the base MAC address entails platform specific |
| 342 | + details. |
| 343 | +
|
| 344 | + See also mgmtaddrstr() and switchaddrstr(). |
| 345 | + ''' |
| 346 | + print("ERROR: Platform did not implement base_mac_addr()") |
| 347 | + raise NotImplementedError |
| 348 | + |
| 349 | + def mgmtaddrstr(self, e): |
| 350 | + ''' |
| 351 | + Returns the base MAC address to use for the Ethernet |
| 352 | + management interface(s) on the CPU complex. |
| 353 | +
|
| 354 | + By default this is the same as the base MAC address listed in |
| 355 | + the EEPROM. |
| 356 | +
|
| 357 | + See also switchaddrstr(). |
| 358 | + ''' |
| 359 | + return self.base_mac_addr(e) |
| 360 | + |
| 361 | + def switchaddrstr(self, e): |
| 362 | + ''' |
| 363 | + Returns the base MAC address to use for the switch ASIC |
| 364 | + interfaces. |
| 365 | +
|
| 366 | + By default this is *next* address after the base MAC address |
| 367 | + listed in the EEPROM. |
| 368 | +
|
| 369 | + See also mgmtaddrstr(). |
| 370 | + ''' |
| 371 | + return self.increment_mac(self.base_mac_addr(e)) |
| 372 | + |
| 373 | + def switchaddrrange(self, e): |
| 374 | + # this function is in the base class only to catch errors |
| 375 | + # the platform specific import should have an override of this method |
| 376 | + # to provide the allocated mac range from syseeprom or flash sector or |
| 377 | + # wherever that platform stores this info |
| 378 | + print("Platform did not indicate allocated mac address range") |
| 379 | + raise NotImplementedError |
| 380 | + |
| 381 | + def serial_number_str(self, e): |
| 382 | + raise NotImplementedError("Platform did not indicate serial number") |
0 commit comments