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.
This commit is contained in:
KonstantinKondrashov 2021-05-21 16:21:03 +08:00 committed by bot
parent ff29aded19
commit 29f853633d
3 changed files with 226 additions and 67 deletions

View File

@ -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 <assert.h>',
@ -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

View File

@ -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()

View File

@ -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
-----------------------