def st_ptr_inc(self, data): """ Store data to the pointer location with pointer post-increment :param data: data to store """ self.logger.debug("ST8 to *ptr++") self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8, data[0]]) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: if len(response) > 0: self.logger.error("Expecting ACK after ST8 *ptr++. Got %d.", num, request[0]) else: self.logger.error("Expecting ACK after ST8 *ptr++. Got nothing.", num) raise PymcuprogError("ACK error with st_ptr_inc") num = 1 while num < len(data): self.updi_phy.send([data[num]]) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: if len(response) > 0: self.logger.error("Expecting ACK after ST8 *ptr++, after byte %d. Got 0x%00X}", num, request[0]) else: self.logger.error("Expecting ACK after ST8 *ptr++, after byte %d. Got nothing", num) raise PymcuprogError("Error with st_ptr_inc") num += 1
def _st_data_phase(self, values): """ Performs data phase of transaction: receive ACK send data :param values: bytearray of value(s) to send """ response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: if len(response >= 0): self.logger.error("expecting ACK after ST, but got: %02x", response[0]) else: self.logger.error("expecting ACK after ST, got nothing.") raise PymcuprogError("Error with st") self.updi_phy.send(values) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: if len(response >= 0): self.logger.error( "expecting ACK after ST value, but got: %02x", response[0]) else: self.logger.error("expecting ACK after ST value, got nothing.") raise PymcuprogError("Error with st")
def st_ptr_inc16(self, data): """ Store a 16-bit word value to the pointer location with pointer post-increment :param data: data to store """ self.logger.debug("ST16 to *ptr++") self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_16, data[0], data[1]]) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: if len(response) > 0: self.logger.error("Expecting ACK after ST16 *ptr++. Got {}}", response[0]) else: self.logger.error("Expecting ACK after ST16 *ptr++. Got nothing") raise PymcuprogError("Error with st_ptr_inc16") num = 2 while num < len(data): self.updi_phy.send([data[num], data[num + 1]]) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: if len(response) > 0: self.logger.error("Expecting ACK after ST16 *ptr++, after word %d. 0x%00X}", num, request[0]) else: self.logger.error("Expecting ACK after ST16 *ptr++, after word %d. Got nothing", num) raise PymcuprogError("Error with st_ptr_inc16") num += 2
def write_fuse(self, address, data): """ Writes one fuse value (v0) :param address: address to write to :param data: data to write """ # Check that NVM controller is ready if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready before page buffer clear ") # Write address to NVMCTRL ADDR self.logger.debug("Load NVM address") self.readwrite.write_byte( self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL, address & 0xFF) self.readwrite.write_byte( self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF) # Write data self.logger.debug("Load fuse data") self.readwrite.write_byte( self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL, data[0] & 0xFF) # Execute self.logger.debug("Execute fuse write") self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE) if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready before page buffer clear ")
def st_ptr_inc(self, data): """ Store data to the pointer location with pointer post-increment :param data: data to store """ self.logger.debug("ST8 to *ptr++") self.updi_phy.send([ constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8, data[0] ]) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: raise PymcuprogError("ACK error with st_ptr_inc") num = 1 while num < len(data): self.updi_phy.send([data[num]]) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: raise PymcuprogError("Error with st_ptr_inc") num += 1
def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE): """ Writes a page of data to NVM (v3) By default the PAGE_WRITE command is used, which requires that the page is already erased. By default word access is used (flash) :param address: address to write to :param data: data to write :param use_word_access: write whole words? :param nvmcommand: command to use for commit """ # Check that NVM controller is ready if not self.wait_nvm_ready(): raise PymcuprogError( "Timeout waiting for NVM controller to be ready before page buffer clear" ) # Clear the page buffer self.logger.debug("Clear page buffer") self.execute_nvm_command( constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR) # Wait for NVM controller to be ready if not self.wait_nvm_ready(): raise PymcuprogError( "Timeout waiting for NVM controller to be ready after page buffer clear" ) # Load the page buffer by writing directly to location if use_word_access: self.readwrite.write_data_words(address, data) else: self.readwrite.write_data(address, data) # Write the page to NVM, maybe erase first self.logger.debug("Committing data") self.execute_nvm_command(nvmcommand) # Wait for NVM controller to be ready again if not self.wait_nvm_ready(): raise PymcuprogError( "Timeout waiting for NVM controller to be ready after page write" ) # Remove command self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
def _st_data_phase(self, values): """ Performs data phase of transaction: receive ACK send data :param values: bytearray of value(s) to send """ response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: raise PymcuprogError("Error with st") self.updi_phy.send(values) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: raise PymcuprogError("Error with st")
def write_data(self, address, data): """ Writes a number of bytes to memory :param address: address to write to :param data: data to write """ # Special case of 1 byte if len(data) == 1: return self.datalink.st(address, data[0]) # Special case of 2 byte if len(data) == 2: self.datalink.st(address, data[0]) return self.datalink.st(address + 1, data[1]) # Range check if len(data) > constants.UPDI_MAX_REPEAT_SIZE: raise PymcuprogError("Invalid length") # Store the address self.datalink.st_ptr(address) # Fire up the repeat self.datalink.repeat(len(data)) return self.datalink.st_ptr_inc(data)
def memory_info_by_address_range(self, start, stop, address_type=DeviceMemoryInfoKeys.ADDRESS, size_type=DeviceMemoryInfoKeys.SIZE): """ Returns a list of all memories applicable for the address range(start, stop) :param start: Start address (byte) :param stop: End address (byte) :param address_type: Selects between normal addresses and addresses used in hex files (address vs hexfile_address) :param size_type: Selects between normal size and size used in hexfiles (size vs hexfile_size) """ # We do not support negative memory ranges if start > stop: raise PymcuprogError( "Cannot parse reverse memory range {} to {}".format( start, stop)) memtypes = [] # Loop through all known memory types for this device for memtype in self.mem_by_name: address = self.mem_by_name[memtype][address_type] size = self.mem_by_name[memtype][size_type] # Check if any of the addresses between start and stop is within the memory type range if start < address + size and stop > address: memtypes.append(self.mem_by_name[memtype]) return memtypes
def write_data_words(self, address, data, blocksize): """ Writes a number of words to memory :param address: address to write to :param data: data to write :blocksize: max number of bytes being sent """ # Special-case of 1 word if len(data) == 2: value = data[0] + (data[1] << 8) return self.datalink.st16(address, value) # Range check if len(data) > constants.UPDI_MAX_REPEAT_SIZE << 1: raise PymcuprogError("Invalid length") # Store the address self.datalink.st_ptr(address) # For performance, we want to do this with Response Signature Disable set, otherwise the USB Serial latency kills you # Just setting RSD here then turning it off at the end - it helps a lot, but you run up against the USB latency. So # now, EVERYTHING was moved into the st_ptr_inc16_RSD() function. Unless blocksize precludes it, the whole thing # is sent to the serial adapter in a single transfer. # the st_pty_inc16_RSD routine does the repeat and rsd enable/disable stu return self.datalink.st_ptr_inc16_RSD(data, blocksize)
def load_device_model(self, device_name, packpath): """ Load the device model :param device_name: device name :param packpath: path to pack """ # Locate and load the model from the pack if packpath is None: raise PymcuprogError("No path to pack repo provided!") # Import from pack. sys.path.append(os.path.normpath(packpath)) sys.path.append(os.path.normpath(packpath + "//common")) # Use tool-type indicating a Config generator try: from debugprovider import ConfigGeneratorTool #pylint: disable=import-error, import-outside-toplevel except ModuleNotFoundError: raise PymcuprogError("Unable to import debugprovider from '{}'! Is this path correct?".format(packpath)) self.tool = ConfigGeneratorTool() # Load the programming model programmer = Programmer(self.tool) # Load the device programmer.load_device(device_name) # Setup the device programmer.setup_device(interface=None, packpath=packpath) # Fetch the memory model self.device_memory_info = programmer.get_device_memory_info() # Extract the pic from level n of the stack self.pic = programmer.device_model.pic # Extract the source from level n+1 of the same stack self.source = inspect.getsource(programmer.device_model.pic.device_model) # Check to see if this device model can be abstracted: # Some devices are so antiquated that they only have support for incrementing the PC and reseting to 0 # These devices currently abstract in Python in such a way that drag and drop will not work with these scripts. if 'RESET_ADDRESS' in dir(self.pic.device_model): raise PymcuprogError("This device model does not support drag and drop by primitives")
def key(self, size, key): """ Write a key :param size: size of key (0=64B, 1=128B, 2=256B) :param key: key value """ self.logger.debug("Writing key") if len(key) != 8 << size: raise PymcuprogError("Invalid KEY length!") self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_KEY | constants.UPDI_KEY_KEY | size]) self.updi_phy.send(list(reversed(list(key))))
def init_datalink(self): """ Init DL layer """ self._init_session_parameters() # Check if not self._check_datalink(): # Send double break if all is not well, and re-check self.updi_phy.send_double_break() self._init_session_parameters() if not self._check_datalink(): raise PymcuprogError("UPDI initialisation failed")
def unlock(self): """ Unlock by chip erase """ # Put in the key self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_CHIPERASE) # Check key status key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS) self.logger.debug("Key status = 0x%02X", key_status) if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_CHIPERASE): raise PymcuprogError("Key not accepted") # Toggle reset self.reset(apply_reset=True) self.reset(apply_reset=False) # And wait for unlock if not self.wait_unlocked(100): raise PymcuprogError("Failed to chip erase using key")
def st_ptr(self, address): """ Set the pointer location :param address: address to write """ self.logger.info("ST to ptr") self.updi_phy.send( [constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_ADDRESS | constants.UPDI_DATA_24, address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF]) response = self.updi_phy.receive(1) if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: raise PymcuprogError("Error with st_ptr")
def ldcs(self, address): """ Load data from Control/Status space :param address: address to load """ self.logger.debug("LDCS from 0x%02X", address) self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LDCS | (address & 0x0F)]) response = self.updi_phy.receive(self.LDCS_RESPONSE_BYTES) numbytes_received = len(response) if numbytes_received != self.LDCS_RESPONSE_BYTES: raise PymcuprogError("Unexpected number of bytes in response: " "{} byte(s) expected {} byte(s)".format(numbytes_received, self.LDCS_RESPONSE_BYTES)) return response[0]
def write_fuse(self, address, data, write_delay=1): """ Writes one fuse value (v0) :param address: address to write to :param data: data to write :param write_delay: only default (1) is used ever. pause after every write, as fusewrite failures have been encountered without it. """ # Check that NVM controller is ready if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready before page buffer clear ") # Write address to NVMCTRL ADDR self.logger.debug("Load NVM address") self.readwrite.write_byte( self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL, address & 0xFF) self.readwrite.write_byte( self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF) # Write data self.logger.debug("Load fuse data") self.readwrite.write_byte( self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL, data[0] & 0xFF) # Execute self.logger.debug("Execute fuse write") self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE) if write_delay > 0: pause_mod.milliseconds(write_delay) if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready before page buffer clear ")
def process_programming_functions(self): """ Crunch through all programming functions and accumulate results """ if not self.pic: raise PymcuprogError("Device model not loaded!") # Enter TMOD self.pic.enter_tmod() # Read ID self.pic.read_id() # Exit TMOD self.pic.exit_tmod() # Erase self.pic.erase() # Parameterised # This import must be late as it depends on the common folder in the packpath which is not available until # after load_device_model has been called from primitiveutils import ParametricValueToken #pylint: disable=import-error, import-outside-toplevel # Flash if MemoryNames.FLASH in self.device_memory_info.mem_by_name: flash_info = self.device_memory_info.memory_info_by_name(MemoryNames.FLASH) flash_page_size = flash_info[DeviceMemoryInfoKeys.PAGE_SIZE] self.pic._write_flash_page(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), data=bytearray(flash_page_size)) # Config if MemoryNames.CONFIG_WORD in self.device_memory_info.mem_by_name: config_words_info = self.device_memory_info.memory_info_by_name(MemoryNames.CONFIG_WORD) config_words_write_size = config_words_info[DeviceMemoryInfoKeys.WRITE_SIZE] self.pic._write_config_word(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), data=bytearray(config_words_write_size)) # EEPROM if MemoryNames.EEPROM in self.device_memory_info.mem_by_name: eeprom_info = self.device_memory_info.memory_info_by_name(MemoryNames.EEPROM) eeprom_write_size = eeprom_info[DeviceMemoryInfoKeys.WRITE_SIZE] self.pic._write_eeprom_block(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), data=bytearray(eeprom_write_size)) # User IDs if MemoryNames.USER_ID in self.device_memory_info.mem_by_name: user_id_info = self.device_memory_info.memory_info_by_name(MemoryNames.USER_ID) user_id_write_size = user_id_info[DeviceMemoryInfoKeys.WRITE_SIZE] self.pic._write_user_id_word(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), data=bytearray(user_id_write_size))
def read_data(self, address, size): """ Reads a number of bytes of data from UPDI :param address: address to write to :param size: number of bytes to read """ self.logger.debug("Reading %d bytes from 0x%04X", size, address) # Range check if size > constants.UPDI_MAX_REPEAT_SIZE: raise PymcuprogError("Cant read that many bytes in one go") # Store the address self.datalink.st_ptr(address) # Fire up the repeat if size > 1: self.datalink.repeat(size) # Do the read(s) return self.datalink.ld_ptr_inc(size)
def read_data_words(self, address, words): """ Reads a number of words of data from UPDI :param address: address to write to :param words: number of words to read """ self.logger.debug("Reading %d words from 0x%04X", words, address) # Range check if words > constants.UPDI_MAX_REPEAT_SIZE: raise PymcuprogError("Cant read that many words in one go") # special case for single word - so we can optimize ld_ptr_inc16 for >1 word to improve performance. if words == 1: return self.datalink.ld16(address) # Otherwise, store the address self.datalink.st_ptr(address) # For performance, managing repeat count is done in ld_ptr_inc16() return self.datalink.ld_ptr_inc16(words)
def read_data_words(self, address, words): """ Reads a number of words of data from UPDI :param address: address to write to :param words: number of words to read """ self.logger.debug("Reading %d words from 0x%04X", words, address) # Range check if words > constants.UPDI_MAX_REPEAT_SIZE >> 1: raise PymcuprogError("Cant read that many words in one go") # Store the address self.datalink.st_ptr(address) # Fire up the repeat if words > 1: self.datalink.repeat(words) # Do the read return self.datalink.ld_ptr_inc16(words)
def write_data_words(self, address, data): """ Writes a number of words to memory :param address: address to write to :param data: data to write """ # Special-case of 1 word if len(data) == 2: value = data[0] + (data[1] << 8) return self.datalink.st16(address, value) # Range check if len(data) > constants.UPDI_MAX_REPEAT_SIZE << 1: raise PymcuprogError("Invalid length") # Store the address self.datalink.st_ptr(address) # Fire up the repeat self.datalink.repeat(len(data) >> 1) return self.datalink.st_ptr_inc16(data)
def memory_info_by_address(self, byte_address, address_type=DeviceMemoryInfoKeys.ADDRESS, size_type=DeviceMemoryInfoKeys.SIZE): """ Returns information about the memory type for a given byte address :param byte_address: Memory address to check :param address_type: Selects between normal addresses and addresses used in hex files (ADDRESS vs HEXFILE_ADDRESS) :param size_type: Selects between normal size and size used in hexfiles (size vs hexfile_size) """ memtype = None for memory in self.mem_by_name: if self.mem_by_name[memory][address_type] <= byte_address < \ self.mem_by_name[memory][address_type] + self.mem_by_name[memory][size_type]: if memtype is not None: raise PymcuprogError( "Duplicate memory area found for byte address '{}'". format(byte_address)) memtype = self.mem_by_name[memory] return memtype
def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE, blocksize=2, bulkwrite=0, pagewrite_delay=0): """ Writes a page of data to NVM (v0) By default the PAGE_WRITE command is used, which requires that the page is already erased. By default word access is used (flash) :param address: address to write to :param data: data to write :param use_word_access: write whole words? :param nvmcommand: command to use for commit :param bulkwrite: Passed down from nvmserialupdi 0 = normal or single write. 1 means it's part of writing the whole flash. In that case we only st ptr if address = 0. :param pagewrite_delay: (ms) delay before pagewrite """ # unless we are in a bulk (whole flash) write, in which case we skip almost everything. if (bulkwrite == 0 ) or address == 0x8000 or address == 0x4000 or not use_word_access: # Check that NVM controller is ready # I will grudgingly check this at the very start. I am extremely skeptical about the usefulness of this test. # If it's not ready, they'll get another error will they not? Every command like this costs about a half second # on every upload when using serialupdi - at any bsaud rate, assuming 256 pages. It's all USB latency. if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready before page buffer clear " ) # Clear the page buffer self.logger.debug("Clear page buffer") self.execute_nvm_command( constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) # Wait for NVM controller to be ready if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready after page buffer clear") # Load the page buffer by writing directly to location if use_word_access: self.readwrite.write_data_words(address, data, blocksize) else: self.readwrite.write_data(address, data) # Write the page to NVM, maybe erase first self.logger.debug("Committing data") self.execute_nvm_command(nvmcommand) if pagewrite_delay > 0: pause_mod.milliseconds(pagewrite_delay) # SACRIFICES SPEED FOR COMPATIBILITY - above line should execute only when --pagepause command line parameter is 1 or more (default 0), so we can adjust it externally if not bulkwrite == 1: # do a final NVM status check only if not doing a bulk write, or after the last chunk (when bulkwrite = 2) # not doing this every page made uploads about 15% faster if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready after page write ")
def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE, blocksize=2, bulkwrite=0): """ Writes a page of data to NVM (v0) By default the PAGE_WRITE command is used, which requires that the page is already erased. By default word access is used (flash) :param address: address to write to :param data: data to write :param use_word_access: write whole words? :param nvmcommand: command to use for commit :param bulkwrite: Passed down from nvmserialupdi 0 = normal or single write. 1 means it's part of writing the whole flash. In that case we only st ptr if address = 0. """ # unless we are in a bulk (whole flash) write, in which case we skip almost everything. if (bulkwrite == 0 ) or address == 0x8000 or address == 0x4000 or not use_word_access: # Check that NVM controller is ready # I will grudgingly check this at the very start. I am extremely skeptical about the usefulness of this test. # If it's not ready, they'll get another error will they not? Every command like this costs about a half second # on every upload when using serialupdi - at any bsaud rate, assuming 256 pages. It's all USB latency. if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready before page buffer clear " ) # Clear the page buffer self.logger.debug("Clear page buffer") self.execute_nvm_command( constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) # Wait for NVM controller to be ready if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready after page buffer clear") # Load the page buffer by writing directly to location if use_word_access: self.readwrite.write_data_words(address, data, blocksize) else: self.readwrite.write_data(address, data) # Write the page to NVM, maybe erase first self.logger.debug("Committing data") self.execute_nvm_command(nvmcommand) # I examine the logs, there are never any cases whee more than one read of this is done. # So since this isn't meeded to handle normal operations, only error conditions, # we can let verify catch those - it's worth less helpful information on rare errors - difference in upload speed can be up to 15% # every USB Latency Period that is removed from the stuff that haoppens every page cuts more than a half second off the upload time! if not bulkwrite == 1: # do a final NVM status check only if not doing a bulk write, or after the last chunk (when bulkwrite = 2) # not doing this every page made uploads about 15% faster if not self.wait_flash_ready(): raise PymcuprogError( "Timeout waiting for flash ready after page write ")