1#!/usr/bin/env python
2# This file includes the common operations with eFuses for chips
3#
4# Copyright (C) 2020 Espressif Systems (Shanghai) PTE LTD
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; either version 2 of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along with
15# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
16# Street, Fifth Floor, Boston, MA 02110-1301 USA.
17from __future__ import division, print_function
18
19import argparse
20import json
21import sys
22
23from bitstring import BitString
24
25import esptool
26
27from . import base_fields
28from . import util
29
30
31def add_common_commands(subparsers, efuses):
32    class ActionEfuseValuePair(argparse.Action):
33        def __init__(self, option_strings, dest, nargs=None, **kwargs):
34            self._nargs = nargs
35            self._choices = kwargs.get("efuse_choices")
36            self.efuses = kwargs.get("efuses")
37            del kwargs["efuse_choices"]
38            del kwargs["efuses"]
39            super(ActionEfuseValuePair, self).__init__(option_strings, dest, nargs=nargs, **kwargs)
40
41        def __call__(self, parser, namespace, values, option_string=None):
42            def check_efuse_name(efuse_name, efuse_list):
43                if efuse_name not in self._choices:
44                    raise esptool.FatalError("Invalid the efuse name '{}'. Available the efuse names: {}".format(efuse_name, self._choices))
45
46            efuse_value_pairs = {}
47            if len(values) > 1:
48                if len(values) % 2:
49                    raise esptool.FatalError("The list does not have a valid pair (name value) {}".format(values))
50                for i in range(0, len(values), 2):
51                    efuse_name, new_value = values[i:i + 2:]
52                    check_efuse_name(efuse_name, self._choices)
53                    check_arg = base_fields.CheckArgValue(self.efuses, efuse_name)
54                    efuse_value_pairs[efuse_name] = check_arg(new_value)
55            else:
56                # For the case of compatibility, when only the efuse_name is given
57                # The fields with 'bitcount' and 'bool' types can be without new_value arg
58                efuse_name = values[0]
59                check_efuse_name(efuse_name, self._choices)
60                check_arg = base_fields.CheckArgValue(self.efuses, efuse_name)
61                efuse_value_pairs[efuse_name] = check_arg(None)
62            setattr(namespace, self.dest, efuse_value_pairs)
63
64    burn = subparsers.add_parser('burn_efuse', help='Burn the efuse with the specified name')
65    burn.add_argument('name_value_pairs', help='Name of efuse register and New value pairs to burn',
66                      action=ActionEfuseValuePair,
67                      nargs="+",
68                      metavar="[EFUSE_NAME VALUE] [{} VALUE".format(" VALUE] [".join([e.name for e in efuses.efuses])),
69                      efuse_choices=[e.name for e in efuses.efuses],
70                      efuses=efuses)
71
72    read_protect_efuse = subparsers.add_parser('read_protect_efuse', help='Disable readback for the efuse with the specified name')
73    read_protect_efuse.add_argument('efuse_name', help='Name of efuse register to burn', nargs="+",
74                                    choices=[e.name for e in efuses.efuses if e.read_disable_bit is not None])
75
76    write_protect_efuse = subparsers.add_parser('write_protect_efuse', help='Disable writing to the efuse with the specified name')
77    write_protect_efuse.add_argument('efuse_name', help='Name of efuse register to burn', nargs="+",
78                                     choices=[e.name for e in efuses.efuses if e.write_disable_bit is not None])
79
80    burn_block_data = subparsers.add_parser('burn_block_data', help="Burn non-key data to EFUSE blocks. "
81                                            "(Don't use this command to burn key data for Flash Encryption or ESP32 Secure Boot V1, "
82                                            "as the byte order of keys is swapped (use burn_key)).")
83    add_force_write_always(burn_block_data)
84    burn_block_data.add_argument('--offset', '-o', help='Byte offset in the efuse block', type=int, default=0)
85    burn_block_data.add_argument('block', help='Efuse block to burn.', action='append', choices=efuses.BURN_BLOCK_DATA_NAMES)
86    burn_block_data.add_argument('datafile', help='File containing data to burn into the efuse block', action='append', type=argparse.FileType('rb'))
87    for _ in range(0, len(efuses.BURN_BLOCK_DATA_NAMES)):
88        burn_block_data.add_argument('block', help='Efuse block to burn.', metavar="BLOCK", nargs="?", action='append',
89                                     choices=efuses.BURN_BLOCK_DATA_NAMES)
90        burn_block_data.add_argument('datafile', nargs="?", help='File containing data to burn into the efuse block',
91                                     metavar="DATAFILE", action='append', type=argparse.FileType('rb'))
92
93    set_bit_cmd = subparsers.add_parser('burn_bit', help="Burn bit in the efuse block.")
94    add_force_write_always(set_bit_cmd)
95    set_bit_cmd.add_argument('block', help='Efuse block to burn.', choices=efuses.BURN_BLOCK_DATA_NAMES)
96    set_bit_cmd.add_argument('bit_number', help='Bit number in the efuse block [0..BLK_LEN-1]', nargs="+", type=int)
97
98    subparsers.add_parser('adc_info', help='Display information about ADC calibration data stored in efuse.')
99
100    dump_cmd = subparsers.add_parser('dump', help='Dump raw hex values of all efuses')
101    dump_cmd.add_argument('--file_name', help='Saves dump for each block into separate file. Provide the common path name /path/blk.bin,'
102                          ' it will create: blk0.bin, blk1.bin ... blkN.bin. Use burn_block_data to write it back to another chip.')
103
104    summary_cmd = subparsers.add_parser('summary', help='Print human-readable summary of efuse values')
105    summary_cmd.add_argument('--format', help='Select the summary format', choices=['summary', 'json'], default='summary')
106    summary_cmd.add_argument('--file', help='File to save the efuse summary', type=argparse.FileType('w'), default=sys.stdout)
107
108    execute_scripts = subparsers.add_parser('execute_scripts', help='Executes scripts to burn at one time.')
109    execute_scripts.add_argument('scripts', help='The special format of python scripts.', nargs="+", type=argparse.FileType('r'))
110
111    subparsers.add_parser('check_error', help='Checks eFuse errors')
112
113
114def add_force_write_always(p):
115    p.add_argument('--force-write-always', help="Write the efuse even if it looks like it's already been written, or is write protected. "
116                   "Note that this option can't disable write protection, or clear any bit which has already been set.", action='store_true')
117
118
119def summary(esp, efuses, args):
120    """ Print a human-readable summary of efuse contents """
121    ROW_FORMAT = "%-50s %-50s%s = %s %s %s"
122    human_output = (args.format == 'summary')
123    json_efuse = {}
124    if args.file != sys.stdout:
125        print("Saving efuse values to " + args.file.name)
126    if human_output:
127        print(ROW_FORMAT.replace("-50", "-12") % ("EFUSE_NAME (Block)", "Description", "", "[Meaningful Value]", "[Readable/Writeable]", "(Hex Value)"),
128              file=args.file)
129        print("-" * 88, file=args.file)
130    for category in sorted(set(e.category for e in efuses), key=lambda c: c.title()):
131        if human_output:
132            print("%s fuses:" % category.title(), file=args.file)
133        for e in (e for e in efuses if e.category == category):
134            if e.efuse_type.startswith("bytes"):
135                raw = ""
136            else:
137                raw = "({})".format(e.get_bitstring())
138            (readable, writeable) = (e.is_readable(), e.is_writeable())
139            if readable and writeable:
140                perms = "R/W"
141            elif readable:
142                perms = "R/-"
143            elif writeable:
144                perms = "-/W"
145            else:
146                perms = "-/-"
147            base_value = e.get_meaning()
148            value = str(base_value)
149            if not readable:
150                value = value.replace("0", "?")
151            if human_output:
152                print(ROW_FORMAT % (e.get_info(), e.description[:50], "\n  " if len(value) > 20 else "", value, perms, raw), file=args.file)
153                desc_len = len(e.description[50:])
154                if desc_len:
155                    desc_len += 50
156                    for i in range(50, desc_len, 50):
157                        print("%-50s %-50s" % ("", e.description[i:(50 + i)]), file=args.file)
158            if args.format == 'json':
159                json_efuse[e.name] = {
160                    'name': e.name,
161                    'value': base_value if readable else value,
162                    'readable': readable,
163                    'writeable': writeable,
164                    'description': e.description,
165                    'category': e.category,
166                    'block': e.block,
167                    'word': e.word,
168                    'pos': e.pos,
169                    'efuse_type': e.efuse_type,
170                    'bit_len': e.bit_len}
171        if human_output:
172            print("", file=args.file)
173    if human_output:
174        print(efuses.summary(), file=args.file)
175        warnings = efuses.get_coding_scheme_warnings()
176        if warnings:
177            print("WARNING: Coding scheme has encoding bit error warnings", file=args.file)
178        if args.file != sys.stdout:
179            args.file.close()
180            print("Done")
181    if args.format == 'json':
182        json.dump(json_efuse, args.file, sort_keys=True, indent=4)
183        print("")
184
185
186def dump(esp, efuses, args):
187    """ Dump raw efuse data registers """
188    # Using --debug option allows to print dump.
189    # Nothing to do here. The log will be print during EspEfuses.__init__() in self.read_blocks()
190    if args.file_name:
191        # save dump to the file
192        for block in efuses.blocks:
193            file_dump_name = args.file_name
194            place_for_index = file_dump_name.find(".bin")
195            file_dump_name = file_dump_name[:place_for_index] + str(block.id) + file_dump_name[place_for_index:]
196            print(file_dump_name)
197            with open(file_dump_name, "wb") as f:
198                block.get_bitstring().byteswap()
199                block.get_bitstring().tofile(f)
200
201
202def burn_efuse(esp, efuses, args):
203    def print_attention(blocked_efuses_after_burn):
204        if len(blocked_efuses_after_burn):
205            print("    ATTENTION! This BLOCK uses NOT the NONE coding scheme and after 'BURN', these efuses can not be burned in the feature:")
206            for i in range(0, len(blocked_efuses_after_burn), 5):
207                print("              ", "".join("{}".format(blocked_efuses_after_burn[i:i + 5:])))
208
209    efuse_name_list = [name for name in args.name_value_pairs.keys()]
210    burn_efuses_list = [efuses[name] for name in efuse_name_list]
211    old_value_list = [efuses[name].get_raw() for name in efuse_name_list]
212    new_value_list = [value for value in args.name_value_pairs.values()]
213    util.check_duplicate_name_in_list(efuse_name_list)
214
215    attention = ""
216    print("The efuses to burn:")
217    for block in efuses.blocks:
218        burn_list_a_block = [e for e in burn_efuses_list if e.block == block.id]
219        if len(burn_list_a_block):
220            print("  from BLOCK%d" % (block.id))
221            for field in burn_list_a_block:
222                print("     - %s" % (field.name))
223                if efuses.blocks[field.block].get_coding_scheme() != efuses.REGS.CODING_SCHEME_NONE:
224                    using_the_same_block_names = [e.name for e in efuses if e.block == field.block]
225                    wr_names = [e.name for e in burn_list_a_block]
226                    blocked_efuses_after_burn = [name for name in using_the_same_block_names if name not in wr_names]
227                    attention = " (see 'ATTENTION!' above)"
228            if attention:
229                print_attention(blocked_efuses_after_burn)
230
231    print("\nBurning efuses{}:".format(attention))
232    for efuse, new_value in zip(burn_efuses_list, new_value_list):
233        print("\n    - '{}' ({}) {} -> {}".format(efuse.name, efuse.description, efuse.get_bitstring(), efuse.convert_to_bitstring(new_value)))
234        efuse.save(new_value)
235    if args.only_burn_at_end:
236        return
237    efuses.burn_all()
238
239    print("Checking efuses...")
240    raise_error = False
241    for efuse, old_value, new_value in zip(burn_efuses_list, old_value_list, new_value_list):
242        if not efuse.is_readable():
243            print("Efuse %s is read-protected. Read back the burn value is not possible." % efuse.name)
244        else:
245            new_value = efuse.convert_to_bitstring(new_value)
246            burned_value = efuse.get_bitstring()
247            if burned_value != new_value:
248                print(burned_value, "->", new_value, "Efuse %s failed to burn. Protected?" % efuse.name)
249                raise_error = True
250    if raise_error:
251        raise esptool.FatalError("The burn was not successful.")
252    else:
253        print("Successful")
254
255
256def read_protect_efuse(esp, efuses, args):
257    util.check_duplicate_name_in_list(args.efuse_name)
258
259    for efuse_name in args.efuse_name:
260        efuse = efuses[efuse_name]
261        if not efuse.is_readable():
262            print("Efuse %s is already read protected" % efuse.name)
263        else:
264            if esp.CHIP_NAME == "ESP32":
265                if efuse_name == 'BLOCK2' and not efuses['ABS_DONE_0'].get() and "revision 3" in esp.get_chip_description():
266                    if efuses['ABS_DONE_1'].get():
267                        raise esptool.FatalError("Secure Boot V2 is on (ABS_DONE_1 = True), BLOCK2 must be readable, stop this operation!")
268                    else:
269                        print("In case using Secure Boot V2, the BLOCK2 must be readable, please stop this operation!")
270            else:
271                for block in efuses.Blocks.BLOCKS:
272                    block = efuses.Blocks.get(block)
273                    if block.name == efuse_name and block.key_purpose is not None:
274                        if not efuses[block.key_purpose].need_rd_protect(efuses[block.key_purpose].get()):
275                            raise esptool.FatalError("%s must be readable, stop this operation!" % efuse_name)
276                        break
277            # make full list of which efuses will be disabled (ie share a read disable bit)
278            all_disabling = [e for e in efuses if e.read_disable_bit == efuse.read_disable_bit]
279            names = ", ".join(e.name for e in all_disabling)
280            print("Permanently read-disabling efuse%s %s" % ("s" if len(all_disabling) > 1 else "", names))
281            efuse.disable_read()
282
283    if args.only_burn_at_end:
284        return
285    efuses.burn_all()
286
287    print("Checking efuses...")
288    raise_error = False
289    for efuse_name in args.efuse_name:
290        efuse = efuses[efuse_name]
291        if efuse.is_readable():
292            print("Efuse %s is not read-protected." % efuse.name)
293            raise_error = True
294    if raise_error:
295        raise esptool.FatalError("The burn was not successful.")
296    else:
297        print("Successful")
298
299
300def write_protect_efuse(esp, efuses, args):
301    util.check_duplicate_name_in_list(args.efuse_name)
302    for efuse_name in args.efuse_name:
303        efuse = efuses[efuse_name]
304        if not efuse.is_writeable():
305            print("Efuse %s is already write protected" % efuse.name)
306        else:
307            # make full list of which efuses will be disabled (ie share a write disable bit)
308            all_disabling = [e for e in efuses if e.write_disable_bit == efuse.write_disable_bit]
309            names = ", ".join(e.name for e in all_disabling)
310            print("Permanently write-disabling efuse%s %s" % ("s" if len(all_disabling) > 1 else "", names))
311            efuse.disable_write()
312
313    if args.only_burn_at_end:
314        return
315    efuses.burn_all()
316
317    print("Checking efuses...")
318    raise_error = False
319    for efuse_name in args.efuse_name:
320        efuse = efuses[efuse_name]
321        if efuse.is_writeable():
322            print("Efuse %s is not write-protected." % efuse.name)
323            raise_error = True
324    if raise_error:
325        raise esptool.FatalError("The burn was not successful.")
326    else:
327        print("Successful")
328
329
330def burn_block_data(esp, efuses, args):
331    block_name_list = args.block[0:len([name for name in args.block if name is not None]):]
332    datafile_list = args.datafile[0:len([name for name in args.datafile if name is not None]):]
333    efuses.force_write_always = args.force_write_always
334
335    util.check_duplicate_name_in_list(block_name_list)
336    if args.offset and len(block_name_list) > 1:
337        raise esptool.FatalError("The 'offset' option is not applicable when a few blocks are passed. With 'offset', should only one block be used.")
338    else:
339        offset = args.offset
340        if offset:
341            num_block = efuses.get_index_block_by_name(block_name_list[0])
342            block = efuses.blocks[num_block]
343            num_bytes = block.get_block_len()
344            if offset >= num_bytes:
345                raise esptool.FatalError("Invalid offset: the block%d only holds %d bytes." % (block.id, num_bytes))
346    if len(block_name_list) != len(datafile_list):
347        raise esptool.FatalError("The number of block_name (%d) and datafile (%d) should be the same." % (len(block_name_list), len(datafile_list)))
348
349    for block_name, datafile in zip(block_name_list, datafile_list):
350        num_block = efuses.get_index_block_by_name(block_name)
351        block = efuses.blocks[num_block]
352        data = datafile.read()
353        num_bytes = block.get_block_len()
354        if offset != 0:
355            data = (b'\x00' * offset) + data
356            data = data + (b'\x00' * (num_bytes - len(data)))
357        if len(data) != num_bytes:
358            raise esptool.FatalError("Data does not fit: the block%d size is %d bytes, data file is %d bytes, offset %d" %
359                                     (block.id, num_bytes, len(data), offset))
360        print("[{:02}] {:20} size={:02} bytes, offset={:02} - > [{}].".format(block.id, block.name, len(data), offset, util.hexify(data, " ")))
361        block.save(data)
362
363    if args.only_burn_at_end:
364        return
365    efuses.burn_all()
366    print("Successful")
367
368
369def burn_bit(esp, efuses, args):
370    efuses.force_write_always = args.force_write_always
371    num_block = efuses.get_index_block_by_name(args.block)
372    block = efuses.blocks[num_block]
373    data_block = BitString(block.get_block_len() * 8)
374    data_block.set(0)
375    try:
376        data_block.set(True, args.bit_number)
377    except IndexError:
378        raise esptool.FatalError("%s has bit_number in [0..%d]" % (args.block, data_block.len - 1))
379    data_block.reverse()
380    print("bit_number:   [%-03d]........................................................[0]" % (data_block.len - 1))
381    print("BLOCK%-2d   :" % block.id, data_block)
382    block.print_block(data_block, "regs_to_write", debug=True)
383    block.save(data_block.bytes[::-1])
384
385    if args.only_burn_at_end:
386        return
387    efuses.burn_all()
388    print("Successful")
389
390
391def check_error(esp, efuses, args):
392    if efuses.get_coding_scheme_warnings():
393        raise esptool.FatalError("Error(s) were detected in eFuses")
394    print("No errors detected")
395