From 29f853633df38701c614e7cbcc9f4533d4a568f2 Mon Sep 17 00:00:00 2001 From: KonstantinKondrashov Date: Fri, 21 May 2021 16:21:03 +0800 Subject: [PATCH] efuse: Adds support structure of efuses in efuse_table Supported a new format of efuse description using '.' in the name. It means that RD_DIS.KEYx belongs to the range of the RD_DIS name. RD_DIS, EFUSE_BLK0, 32, 7, Read protection RD_DIS.KEY0, EFUSE_BLK0, 32, 1, Read protection for EFUSE_BLK4. RD_DIS.KEY1, EFUSE_BLK0, 33, 1, Read protection for EFUSE_BLK5. --- components/efuse/efuse_table_gen.py | 129 +++++++++------- .../efuse/test_efuse_host/efuse_tests.py | 139 ++++++++++++++++-- docs/en/api-reference/system/efuse.rst | 25 +++- 3 files changed, 226 insertions(+), 67 deletions(-) diff --git a/components/efuse/efuse_table_gen.py b/components/efuse/efuse_table_gen.py index 5b66f73ab1..79b5d1be3b 100755 --- a/components/efuse/efuse_table_gen.py +++ b/components/efuse/efuse_table_gen.py @@ -4,19 +4,9 @@ # # Converts efuse table to header file efuse_table.h. # -# Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +# SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 from __future__ import division, print_function import argparse @@ -24,6 +14,7 @@ import hashlib import os import re import sys +from datetime import datetime __version__ = '1.0' @@ -31,20 +22,15 @@ quiet = False max_blk_len = 256 idf_target = 'esp32' -copyright = '''// Copyright 2017-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at", -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License + +def get_copyright(): + copyright_str = '''/* + * SPDX-FileCopyrightText: 2017-%d Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ ''' + return copyright_str % datetime.today().year def status(msg): @@ -130,6 +116,7 @@ class FuseTable(list): def verify_duplicate_name(self): # check on duplicate name names = [p.field_name for p in self] + names += [name.replace('.', '_') for name in names if '.' in name] duplicates = set(n for n in names if names.count(n) > 1) # print sorted duplicate partitions by name @@ -144,20 +131,70 @@ class FuseTable(list): if fl_error is True: raise InputError('Field names must be unique') + def check_struct_field_name(self): + # check that stuctured fields have a root field + for p in self: + if '.' in p.field_name: + name = '' + for sub in p.field_name.split('.')[:-1]: + name = sub if name == '' else name + '.' + sub + missed_name = True + for d in self: + if p is not d and p.efuse_block == d.efuse_block and name == d.field_name: + missed_name = False + if missed_name: + raise InputError('%s is not found' % name) + def verify(self, type_table=None): + def check(p, n): + left = n.bit_start + right = n.bit_start + n.bit_count - 1 + start = p.bit_start + end = p.bit_start + p.bit_count - 1 + if left <= start <= right: + if left <= end <= right: + return 'included in' # [n [p...p] n] + return 'intersected with' # [n [p..n]..p] + if left <= end <= right: + return 'intersected with' # [p..[n..p] n] + if start <= left and right <= end: + return 'wraps' # [p [n...n] p] + return 'ok' # [p] [n] or [n] [p] + + def print_error(p, n, state): + raise InputError('Field at %s, %s, %s, %s %s %s, %s, %s, %s' % + (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)) for p in self: p.verify(type_table) self.verify_duplicate_name() + if type_table != 'custom_table': + # check will be done for common and custom tables together + self.check_struct_field_name() # check for overlaps - last = None - for p in sorted(self, key=lambda x:(x.efuse_block, x.bit_start)): - if last is not None and last.efuse_block == p.efuse_block and p.bit_start < last.bit_start + last.bit_count: - raise InputError('Field at %s, %s, %s, %s overlaps %s, %s, %s, %s' % - (p.field_name, p.efuse_block, p.bit_start, p.bit_count, - last.field_name, last.efuse_block, last.bit_start, last.bit_count)) - last = p + for p in self: + for n in self: + if p is not n and p.efuse_block == n.efuse_block: + state = check(p, n) + if state != 'ok': + if '.' in p.field_name: + name = '' + for sub in p.field_name.split('.'): + name = sub if name == '' else name + '.' + sub + for d in self: + if p is not d and p.efuse_block == d.efuse_block and name == d.field_name: + state = check(p, d) + if state == 'included in': + break + elif state != 'intersected with': + state = 'out of range' + print_error(p, d, state) + continue + elif '.' in n.field_name: + continue + print_error(p, n, state) def calc_md5(self): txt_table = '' @@ -204,7 +241,7 @@ class FuseTable(list): return str(last_used_bit) def to_header(self, file_name): - rows = [copyright] + rows = [get_copyright()] rows += ['#ifdef __cplusplus', 'extern "C" {', '#endif', @@ -221,7 +258,7 @@ class FuseTable(list): last_field_name = '' for p in self: if (p.field_name != last_field_name): - rows += ['extern const esp_efuse_desc_t* ' + 'ESP_EFUSE_' + p.field_name + '[];'] + rows += ['extern const esp_efuse_desc_t* ' + 'ESP_EFUSE_' + p.field_name.replace('.', '_') + '[];'] last_field_name = p.field_name rows += ['', @@ -232,7 +269,7 @@ class FuseTable(list): return '\n'.join(rows) def to_c_file(self, file_name, debug): - rows = [copyright] + rows = [get_copyright()] rows += ['#include "sdkconfig.h"', '#include "esp_efuse.h"', '#include ', @@ -282,7 +319,7 @@ class FuseTable(list): if (p.field_name != last_name): if last_name != '': rows += ['};\n'] - rows += ['static const esp_efuse_desc_t ' + p.field_name + '[] = {'] + rows += ['static const esp_efuse_desc_t ' + p.field_name.replace('.', '_') + '[] = {'] last_name = p.field_name rows += [p.to_struct(debug) + ','] rows += ['};\n'] @@ -294,10 +331,10 @@ class FuseTable(list): if last_name != '': rows += [' NULL', '};\n'] - rows += ['const esp_efuse_desc_t* ' + 'ESP_EFUSE_' + p.field_name + '[] = {'] + rows += ['const esp_efuse_desc_t* ' + 'ESP_EFUSE_' + p.field_name.replace('.', '_') + '[] = {'] last_name = p.field_name index = str(0) if str(p.group) == '' else str(p.group) - rows += [' &' + p.field_name + '[' + index + '], \t\t// ' + p.comment] + rows += [' &' + p.field_name.replace('.', '_') + '[' + index + '], \t\t// ' + p.comment] rows += [' NULL', '};\n'] @@ -376,26 +413,10 @@ class FuseDefinition(object): raise ValidationError(self, 'efuse_block field is not set') if self.bit_count is None: raise ValidationError(self, 'bit_count field is not set') - - if type_table is not None: - if type_table == 'custom_table': - if self.efuse_block != 'EFUSE_BLK3': - raise ValidationError(self, 'custom_table should use only EFUSE_BLK3') - max_bits = self.get_max_bits_of_block() - if self.bit_start + self.bit_count > max_bits: raise ValidationError(self, 'The field is outside the boundaries(max_bits = %d) of the %s block' % (max_bits, self.efuse_block)) - def get_full_name(self): - def get_postfix(group): - postfix = '' - if group != '': - postfix = '_PART_' + group - return postfix - - return self.field_name + get_postfix(self.group) - def get_bit_count(self, check_define=True): if check_define is True and self.define is not None: return self.define diff --git a/components/efuse/test_efuse_host/efuse_tests.py b/components/efuse/test_efuse_host/efuse_tests.py index ec5a17acac..d98f68e926 100755 --- a/components/efuse/test_efuse_host/efuse_tests.py +++ b/components/efuse/test_efuse_host/efuse_tests.py @@ -155,7 +155,7 @@ name1, EFUSE_BLK3, 1, name2, EFUSE_BLK3, 5, 4, Use for test name 2 """ t = efuse_table_gen.FuseTable.from_csv(csv) - with self.assertRaisesRegex(efuse_table_gen.InputError, 'overlap'): + with self.assertRaisesRegex(efuse_table_gen.InputError, 'intersected with'): t.verify() def test_empty_field_name_fail(self): @@ -305,16 +305,6 @@ name2_1, EFUSE_BLK2, 5, self.assertEqual(t[3].bit_start, 5) self.assertEqual(t[3].bit_count, 4) - def test_custom_use_only_BLK3(self): - csv = """ -# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment -name1, EFUSE_BLK3, 0, 5, Use for test name 1 -name2, EFUSE_BLK2, 5, 4, Use for test name 2 - """ - t = efuse_table_gen.FuseTable.from_csv(csv) - with self.assertRaisesRegex(efuse_table_gen.ValidationError, 'custom_table should use only EFUSE_BLK3'): - t.verify('custom_table') - def test_common_and_custom_table_use_the_same_bits(self): csv_common = """ # field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment @@ -334,9 +324,134 @@ name4, EFUSE_BLK3, 4, custom_table.verify('custom_table') two_tables += custom_table - with self.assertRaisesRegex(efuse_table_gen.InputError, 'overlaps'): + with self.assertRaisesRegex(efuse_table_gen.InputError, 'intersected with'): two_tables.verify() + def test_common_and_custom_table_use_nested_fields(self): + csv_common = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +namet1, EFUSE_BLK3, 0, 5, comment +namet2, EFUSE_BLK1, 8, 4, comment + """ + common_table = efuse_table_gen.FuseTable.from_csv(csv_common) + common_table.verify('common_table') + two_tables = common_table + + csv_custom = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +namet1.D1, EFUSE_BLK3, 0, 2, comment +namet1.D1.D11, EFUSE_BLK3, 0, 1, comment +namet1.D1.D12, EFUSE_BLK3, 1, 1, comment +namet1.D2, EFUSE_BLK3, 2, 3, comment +namet2.F1, EFUSE_BLK1, 9, 1, comment + """ + custom_table = efuse_table_gen.FuseTable.from_csv(csv_custom) + custom_table.verify('custom_table') + + two_tables += custom_table + two_tables.verify() + + def test_common_and_custom_table_use_nested_fields2(self): + csv_common = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +namet3, EFUSE_BLK3, 0, 5, comment +namet2, EFUSE_BLK1, 8, 4, comment + """ + common_table = efuse_table_gen.FuseTable.from_csv(csv_common) + common_table.verify('common_table') + two_tables = common_table + + csv_custom = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +namet1.D1, EFUSE_BLK3, 0, 2, comment +namet1.D1.D11, EFUSE_BLK3, 0, 1, comment +namet1.D1.D12, EFUSE_BLK3, 1, 1, comment +namet1.D2, EFUSE_BLK3, 2, 3, comment +namet2.F1, EFUSE_BLK1, 9, 1, comment + """ + custom_table = efuse_table_gen.FuseTable.from_csv(csv_custom) + custom_table.verify('custom_table') + + two_tables += custom_table + with self.assertRaisesRegex(efuse_table_gen.InputError, 'namet1 is not found'): + two_tables.verify() + + def test_nested_fields1(self): + csv = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +name1, EFUSE_BLK3, 0, 5, comment +name1.D1, EFUSE_BLK3, 0, 4, comment +name1.D1.D2, EFUSE_BLK3, 0, 3, comment +name1.D1.D2.D3, EFUSE_BLK3, 0, 2, comment +name1.D1.D2.D3.D4, EFUSE_BLK3, 0, 1, comment + """ + t = efuse_table_gen.FuseTable.from_csv(csv) + t.verify() + + for i in range(0, 5): + self.assertEqual(t[i].bit_start, 0) + self.assertEqual(t[i].bit_count, 5 - i) + + def test_nested_fields2(self): + csv = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +name1, EFUSE_BLK3, 0, 5, comment +name1.D1, EFUSE_BLK3, 1, 4, comment +name1.D1.D2, EFUSE_BLK3, 2, 3, comment +name1.D1.D2.D3, EFUSE_BLK3, 3, 2, comment +name1.D1.D2.D3.D4, EFUSE_BLK3, 4, 1, comment + """ + t = efuse_table_gen.FuseTable.from_csv(csv) + t.verify() + + for i in range(0, 5): + self.assertEqual(t[i].bit_start, i) + self.assertEqual(t[i].bit_count, 5 - i) + + def test_nested_fields_fail1(self): + csv = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +name1, EFUSE_BLK3, 0, 5, comment +name1.D1, EFUSE_BLK3, 1, 4, comment +name1.D1.D2, EFUSE_BLK3, 0, 1, comment + """ + t = efuse_table_gen.FuseTable.from_csv(csv) + with self.assertRaisesRegex(efuse_table_gen.InputError, 'out of range'): + t.verify() + + def test_nested_fields_fail2(self): + csv = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +name1, EFUSE_BLK3, 0, 5, comment +namet2, EFUSE_BLK2, 8, 4, comment +namet2.F1, EFUSE_BLK2, 5, 4, comment + """ + t = efuse_table_gen.FuseTable.from_csv(csv) + with self.assertRaisesRegex(efuse_table_gen.InputError, 'intersected with'): + t.verify() + + def test_nested_fields_fail3(self): + csv = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +name1, EFUSE_BLK3, 10, 5, comment +name11, EFUSE_BLK3, 5, 1, comment +namet2.F1, EFUSE_BLK2, 22, 1, comment + """ + t = efuse_table_gen.FuseTable.from_csv(csv) + with self.assertRaisesRegex(efuse_table_gen.InputError, 'namet2 is not found'): + t.verify() + + def test_nested_fields_fail4(self): + csv = """ +# field_name, efuse_block(EFUSE_BLK0..EFUSE_BLK3), bit_start(0..255), bit_count, comment +name1, EFUSE_BLK3, 10, 5, comment +name2, EFUSE_BLK3, 5, 1, comment +name2.F1, EFUSE_BLK2, 22, 1, comment + """ + t = efuse_table_gen.FuseTable.from_csv(csv) + with self.assertRaisesRegex(efuse_table_gen.InputError, 'name2 is not found'): + t.verify() + if __name__ == '__main__': unittest.main() diff --git a/docs/en/api-reference/system/efuse.rst b/docs/en/api-reference/system/efuse.rst index fab51f203e..b67977902f 100644 --- a/docs/en/api-reference/system/efuse.rst +++ b/docs/en/api-reference/system/efuse.rst @@ -71,7 +71,7 @@ Table header: Individual params in CSV file the following meanings: field_name - Name of field. The prefix `ESP_EFUSE_` will be added to the name, and this field name will be available in the code. This name will be used to access the fields. The name must be unique for all fields. If the line has an empty name, then this line is combined with the previous field. This allows you to set an arbitrary order of bits in the field, and expand the field as well (see ``MAC_FACTORY`` field in the common table). + Name of field. The prefix `ESP_EFUSE_` will be added to the name, and this field name will be available in the code. This name will be used to access the fields. The name must be unique for all fields. If the line has an empty name, then this line is combined with the previous field. This allows you to set an arbitrary order of bits in the field, and expand the field as well (see ``MAC_FACTORY`` field in the common table). The field_name supports structured format using `.` to show that the field belongs to another field (see ``WR_DIS`` and ``RD_DIS`` in the common table). efuse_block Block number. It determines where the eFuse bits will be placed for this field. Available EFUSE_BLK0..{IDF_TARGET_MAX_EFUSE_BLK}. @@ -108,6 +108,29 @@ If a non-sequential bit order is required to describe a field, then the field de This field will available in code as ESP_EFUSE_MAC_FACTORY and ESP_EFUSE_MAC_FACTORY_CRC. +Structured efuse fields +----------------------- + +.. code-block:: none + + WR_DIS, EFUSE_BLK0, 0, 32, Write protection + WR_DIS.RD_DIS, EFUSE_BLK0, 0, 1, Write protection for RD_DIS + WR_DIS.FIELD_1, EFUSE_BLK0, 1, 1, Write protection for FIELD_1 + WR_DIS.FIELD_2, EFUSE_BLK0, 2, 4, Write protection for FIELD_2 (includes B1 and B2) + WR_DIS.FIELD_2.B1, EFUSE_BLK0, 2, 2, Write protection for FIELD_2.B1 + WR_DIS.FIELD_2.B2, EFUSE_BLK0, 4, 2, Write protection for FIELD_2.B2 + WR_DIS.FIELD_3, EFUSE_BLK0, 5, 1, Write protection for FIELD_3 + WR_DIS.FIELD_3.ALIAS, EFUSE_BLK0, 5, 1, Write protection for FIELD_3 (just a alias for WR_DIS.FIELD_3) + WR_DIS.FIELD_4, EFUSE_BLK0, 7, 1, Write protection for FIELD_4 + +The structured eFuse field looks like ``WR_DIS.RD_DIS`` where the dot points that this field belongs to the parent field - ``WR_DIS`` and can not be out of the parent's range. + +It is possible to use some levels of structured fields as WR_DIS.FIELD_2.B1 and B2. These fields should not be crossed each other and should be in the range of two fields: ``WR_DIS`` and ``WR_DIS.FIELD_2``. + +It is possible to create aliases for fields with the same range, see ``WR_DIS.FIELD_3`` and ``WR_DIS.FIELD_3.ALIAS``. + +The IDF names for structured efuse fields should be unique. The ``efuse_table_gen`` tool will generate the final names where the dot will be replaced by ``_``. The names for using in IDF are ESP_EFUSE_WR_DIS, ESP_EFUSE_WR_DIS_RD_DIS, ESP_EFUSE_WR_DIS_FIELD_2_B1, etc. + efuse_table_gen.py tool -----------------------