1# This file helps to parse CSV eFuse tables 2# 3# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD 4# 5# SPDX-License-Identifier: GPL-2.0-or-later 6 7import os 8import re 9import sys 10 11 12class CSVFuseTable(list): 13 @classmethod 14 def from_csv(cls, csv_contents): 15 res = CSVFuseTable() 16 lines = csv_contents.splitlines() 17 18 def expand_vars(f): 19 f = os.path.expandvars(f) 20 m = re.match(r"(?<!\\)\$([A-Za-z_]\w*)", f) 21 if m: 22 raise InputError(f"unknown variable '{m.group(1)}'") 23 return f 24 25 for line_no, line in enumerate(lines): 26 line = expand_vars(line).strip() 27 if line.startswith("#") or len(line) == 0: 28 continue 29 try: 30 res.append(FuseDefinition.from_csv(line)) 31 except InputError as err: 32 raise InputError(f"Error at line {line_no + 1}: {err}") 33 except Exception: 34 sys.stderr.write(f"Unexpected error parsing line {line_no + 1}: {line}") 35 raise 36 37 # fix up missing bit_start 38 last_efuse_block = None 39 for i in res: 40 if last_efuse_block != i.efuse_block: 41 last_end = 0 42 if i.bit_start is None: 43 i.bit_start = last_end 44 last_end = i.bit_start + i.bit_count 45 last_efuse_block = i.efuse_block 46 47 res.verify_duplicate_name() 48 49 # fix up missing field_name 50 last_field = None 51 for i in res: 52 if i.field_name == "": 53 if last_field is None: 54 raise InputError( 55 f"Error at line {line_no + 1}: {i} missing field name" 56 ) 57 elif last_field is not None: 58 i.field_name = last_field.field_name 59 last_field = i 60 61 # fill group 62 names = [p.field_name for p in res] 63 duplicates = set(n for n in names if names.count(n) > 1) 64 for dname in duplicates: 65 i_count = 0 66 for p in res: 67 if p.field_name != dname: 68 continue 69 if len(duplicates.intersection([p.field_name])) != 0: 70 p.field_name = f"{p.field_name}_{i_count}" 71 if p.alt_names: 72 p.alt_names = f"{p.alt_names}_{i_count}" 73 i_count += 1 74 else: 75 i_count = 0 76 77 for p in res: 78 p.field_name = p.field_name.replace(".", "_") 79 if p.alt_names: 80 p.alt_names = p.alt_names.replace(".", "_") 81 res.verify_duplicate_name() 82 return res 83 84 def verify_duplicate_name(self): 85 # check on duplicate name 86 names = [p.field_name for p in self] 87 names += [name.replace(".", "_") for name in names if "." in name] 88 duplicates = set(n for n in names if names.count(n) > 1) 89 90 # print sorted duplicate partitions by name 91 if len(duplicates) != 0: 92 fl_error = False 93 for p in self: 94 field_name = p.field_name + p.group 95 if field_name != "" and len(duplicates.intersection([field_name])) != 0: 96 fl_error = True 97 print( 98 f"Field at {p.field_name}, {p.efuse_block}, " 99 f"{p.bit_start}, {p.bit_count} have duplicate field_name" 100 ) 101 if fl_error is True: 102 raise InputError("Field names must be unique") 103 104 def check_struct_field_name(self): 105 # check that structured fields have a root field 106 for p in self: 107 if "." in p.field_name: 108 name = "" 109 for sub in p.field_name.split(".")[:-1]: 110 name = sub if name == "" else name + "." + sub 111 missed_name = True 112 for d in self: 113 if ( 114 p is not d 115 and p.efuse_block == d.efuse_block 116 and name == d.field_name 117 ): 118 missed_name = False 119 if missed_name: 120 raise InputError(f"{name} is not found") 121 122 def verify(self, type_table=None): 123 def check(p, n): 124 left = n.bit_start 125 right = n.bit_start + n.bit_count - 1 126 start = p.bit_start 127 end = p.bit_start + p.bit_count - 1 128 if left <= start <= right: 129 if left <= end <= right: 130 return "included in" # [n [p...p] n] 131 return "intersected with" # [n [p..n]..p] 132 if left <= end <= right: 133 return "intersected with" # [p..[n..p] n] 134 if start <= left and right <= end: 135 return "wraps" # [p [n...n] p] 136 return "ok" # [p] [n] or [n] [p] 137 138 def print_error(p, n, state): 139 raise InputError( 140 f"Field at {p.field_name}, {p.efuse_block}, {p.bit_start}, {p.bit_count} {state} {n.field_name}, {n.efuse_block}, {n.bit_start}, {n.bit_count}" 141 ) 142 143 for p in self: 144 p.verify(type_table) 145 146 self.verify_duplicate_name() 147 if type_table != "custom_table": 148 # check will be done for common and custom tables together 149 self.check_struct_field_name() 150 151 # check for overlaps 152 for p in self: 153 for n in self: 154 if p is not n and p.efuse_block == n.efuse_block: 155 state = check(p, n) 156 if state != "ok": 157 if "." in p.field_name: 158 name = "" 159 for sub in p.field_name.split("."): 160 name = sub if name == "" else name + "." + sub 161 for d in self: 162 if ( 163 p is not d 164 and p.efuse_block == d.efuse_block 165 and name == d.field_name 166 ): 167 state = check(p, d) 168 if state == "included in": 169 break 170 elif state != "intersected with": 171 state = "out of range" 172 print_error(p, d, state) 173 continue 174 elif "." in n.field_name: 175 continue 176 print_error(p, n, state) 177 178 179class FuseDefinition(object): 180 def __init__(self): 181 self.field_name = "" 182 self.group = "" 183 self.efuse_block = "" 184 self.bit_start = None 185 self.bit_count = None 186 self.define = None 187 self.comment = "" 188 self.alt_names = "" 189 self.MAX_BITS_OF_BLOCK = 256 190 191 @classmethod 192 def from_csv(cls, line): 193 """Parse a line from the CSV""" 194 line_w_defaults = line + ",,,," 195 fields = [f.strip() for f in line_w_defaults.split(",")] 196 197 res = FuseDefinition() 198 res.field_name = fields[0] 199 res.efuse_block = res.parse_block(fields[1]) 200 res.bit_start = res.parse_num(fields[2]) 201 res.bit_count = res.parse_bit_count(fields[3]) 202 if res.bit_count is None or res.bit_count == 0: 203 raise InputError("Field bit_count can't be empty") 204 res.comment = fields[4].rstrip("\\").rstrip() 205 res.comment += f" ({res.bit_start}-{res.bit_start + res.bit_count - 1})" 206 res.alt_names = res.get_alt_names(res.comment) 207 return res 208 209 def parse_num(self, strval): 210 if strval == "": 211 return None 212 return self.parse_int(strval) 213 214 def parse_bit_count(self, strval): 215 if strval == "MAX_BLK_LEN": 216 self.define = strval 217 return self.MAX_BITS_OF_BLOCK 218 else: 219 return self.parse_num(strval) 220 221 def parse_int(self, v): 222 try: 223 return int(v, 0) 224 except ValueError: 225 raise InputError(f"Invalid field value {v}") 226 227 def parse_block(self, strval): 228 if strval == "": 229 raise InputError("Field 'efuse_block' can't be left empty.") 230 return self.parse_int(strval.lstrip("EFUSE_BLK")) 231 232 def verify(self, type_table): 233 if self.efuse_block is None: 234 raise ValidationError(self, "efuse_block field is not set") 235 if self.bit_count is None: 236 raise ValidationError(self, "bit_count field is not set") 237 max_bits = self.MAX_BITS_OF_BLOCK 238 if self.bit_start + self.bit_count > max_bits: 239 raise ValidationError( 240 self, 241 f"The field is outside the boundaries(max_bits = {max_bits}) of the {self.efuse_block} block", 242 ) 243 244 def get_bit_count(self, check_define=True): 245 if check_define is True and self.define is not None: 246 return self.define 247 else: 248 return self.bit_count 249 250 def get_alt_names(self, comment): 251 result = re.search(r"^\[(.*?)\]", comment) 252 if result: 253 return result.group(1) 254 return "" 255 256 257class InputError(RuntimeError): 258 def __init__(self, e): 259 super(InputError, self).__init__(e) 260 261 262class ValidationError(InputError): 263 def __init__(self, p, message): 264 super(ValidationError, self).__init__( 265 f"Entry {p.field_name} invalid: {message}" 266 ) 267