1# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton, 2# Espressif Systems (Shanghai) CO LTD, other contributors as noted. 3# 4# SPDX-License-Identifier: GPL-2.0-or-later 5 6import errno 7import os 8import struct 9import time 10 11from .util import FatalError, PrintOnce 12 13# Used for resetting into bootloader on Unix-like systems 14if os.name != "nt": 15 import fcntl 16 import termios 17 18 # Constants used for terminal status lines reading/setting. 19 # Taken from pySerial's backend for IO: 20 # https://github.com/pyserial/pyserial/blob/master/serial/serialposix.py 21 TIOCMSET = getattr(termios, "TIOCMSET", 0x5418) 22 TIOCMGET = getattr(termios, "TIOCMGET", 0x5415) 23 TIOCM_DTR = getattr(termios, "TIOCM_DTR", 0x002) 24 TIOCM_RTS = getattr(termios, "TIOCM_RTS", 0x004) 25 26DEFAULT_RESET_DELAY = 0.05 # default time to wait before releasing boot pin after reset 27 28 29class ResetStrategy(object): 30 print_once = PrintOnce() 31 32 def __init__(self, port, reset_delay=DEFAULT_RESET_DELAY): 33 self.port = port 34 self.reset_delay = reset_delay 35 36 def __call__(self): 37 """ 38 On targets with USB modes, the reset process can cause the port to 39 disconnect / reconnect during reset. 40 This will retry reconnections on ports that 41 drop out during the reset sequence. 42 """ 43 for retry in reversed(range(3)): 44 try: 45 if not self.port.isOpen(): 46 self.port.open() 47 self.reset() 48 break 49 except OSError as e: 50 # ENOTTY for TIOCMSET; EINVAL for TIOCMGET 51 if e.errno in [errno.ENOTTY, errno.EINVAL]: 52 self.print_once( 53 "WARNING: Chip was NOT reset. Setting RTS/DTR lines is not " 54 f"supported for port '{self.port.name}'. Set --before and --after " 55 "arguments to 'no_reset' and switch to bootloader manually to " 56 "avoid this warning." 57 ) 58 break 59 elif not retry: 60 raise 61 self.port.close() 62 time.sleep(0.5) 63 64 def reset(self): 65 pass 66 67 def _setDTR(self, state): 68 self.port.setDTR(state) 69 70 def _setRTS(self, state): 71 self.port.setRTS(state) 72 # Work-around for adapters on Windows using the usbser.sys driver: 73 # generate a dummy change to DTR so that the set-control-line-state 74 # request is sent with the updated RTS state and the same DTR state 75 self.port.setDTR(self.port.dtr) 76 77 def _setDTRandRTS(self, dtr=False, rts=False): 78 status = struct.unpack( 79 "I", fcntl.ioctl(self.port.fileno(), TIOCMGET, struct.pack("I", 0)) 80 )[0] 81 if dtr: 82 status |= TIOCM_DTR 83 else: 84 status &= ~TIOCM_DTR 85 if rts: 86 status |= TIOCM_RTS 87 else: 88 status &= ~TIOCM_RTS 89 fcntl.ioctl(self.port.fileno(), TIOCMSET, struct.pack("I", status)) 90 91 92class ClassicReset(ResetStrategy): 93 """ 94 Classic reset sequence, sets DTR and RTS lines sequentially. 95 """ 96 97 def reset(self): 98 self._setDTR(False) # IO0=HIGH 99 self._setRTS(True) # EN=LOW, chip in reset 100 time.sleep(0.1) 101 self._setDTR(True) # IO0=LOW 102 self._setRTS(False) # EN=HIGH, chip out of reset 103 time.sleep(self.reset_delay) 104 self._setDTR(False) # IO0=HIGH, done 105 106 107class UnixTightReset(ResetStrategy): 108 """ 109 UNIX-only reset sequence with custom implementation, 110 which allows setting DTR and RTS lines at the same time. 111 """ 112 113 def reset(self): 114 self._setDTRandRTS(False, False) 115 self._setDTRandRTS(True, True) 116 self._setDTRandRTS(False, True) # IO0=HIGH & EN=LOW, chip in reset 117 time.sleep(0.1) 118 self._setDTRandRTS(True, False) # IO0=LOW & EN=HIGH, chip out of reset 119 time.sleep(self.reset_delay) 120 self._setDTRandRTS(False, False) # IO0=HIGH, done 121 self._setDTR(False) # Needed in some environments to ensure IO0=HIGH 122 123 124class USBJTAGSerialReset(ResetStrategy): 125 """ 126 Custom reset sequence, which is required when the device 127 is connecting via its USB-JTAG-Serial peripheral. 128 """ 129 130 def reset(self): 131 self._setRTS(False) 132 self._setDTR(False) # Idle 133 time.sleep(0.1) 134 self._setDTR(True) # Set IO0 135 self._setRTS(False) 136 time.sleep(0.1) 137 self._setRTS(True) # Reset. Calls inverted to go through (1,1) instead of (0,0) 138 self._setDTR(False) 139 self._setRTS(True) # RTS set as Windows only propagates DTR on RTS setting 140 time.sleep(0.1) 141 self._setDTR(False) 142 self._setRTS(False) # Chip out of reset 143 144 145class HardReset(ResetStrategy): 146 """ 147 Reset sequence for hard resetting the chip. 148 Can be used to reset out of the bootloader or to restart a running app. 149 """ 150 151 def __init__(self, port, uses_usb=False): 152 super().__init__(port) 153 self.uses_usb = uses_usb 154 155 def reset(self): 156 self._setRTS(True) # EN->LOW 157 if self.uses_usb: 158 # Give the chip some time to come out of reset, 159 # to be able to handle further DTR/RTS transitions 160 time.sleep(0.2) 161 self._setRTS(False) 162 time.sleep(0.2) 163 else: 164 time.sleep(0.1) 165 self._setRTS(False) 166 167 168class CustomReset(ResetStrategy): 169 """ 170 Custom reset strategy defined with a string. 171 172 CustomReset object is created as "rst = CustomReset(port, seq_str)" 173 and can be later executed simply with "rst()" 174 175 The seq_str input string consists of individual commands divided by "|". 176 Commands (e.g. R0) are defined by a code (R) and an argument (0). 177 178 The commands are: 179 D: setDTR - 1=True / 0=False 180 R: setRTS - 1=True / 0=False 181 U: setDTRandRTS (Unix-only) - 0,0 / 0,1 / 1,0 / or 1,1 182 W: Wait (time delay) - positive float number 183 184 e.g. 185 "D0|R1|W0.1|D1|R0|W0.05|D0" represents the ClassicReset strategy 186 "U1,1|U0,1|W0.1|U1,0|W0.05|U0,0" represents the UnixTightReset strategy 187 """ 188 189 format_dict = { 190 "D": "self.port.setDTR({})", 191 "R": "self.port.setRTS({})", 192 "W": "time.sleep({})", 193 "U": "self._setDTRandRTS({})", 194 } 195 196 def reset(self): 197 exec(self.constructed_strategy) 198 199 def __init__(self, port, seq_str): 200 super().__init__(port) 201 self.constructed_strategy = self._parse_string_to_seq(seq_str) 202 203 def _parse_string_to_seq(self, seq_str): 204 try: 205 cmds = seq_str.split("|") 206 fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds] 207 except Exception as e: 208 raise FatalError(f'Invalid "custom_reset_sequence" option format: {e}') 209 return "\n".join(fn_calls_list) 210