Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
msd_test.py
00001 # 00002 # DAPLink Interface Firmware 00003 # Copyright (c) 2009-2016, ARM Limited, All Rights Reserved 00004 # SPDX-License-Identifier: Apache-2.0 00005 # 00006 # Licensed under the Apache License, Version 2.0 (the "License"); you may 00007 # not use this file except in compliance with the License. 00008 # You may obtain a copy of the License at 00009 # 00010 # http://www.apache.org/licenses/LICENSE-2.0 00011 # 00012 # Unless required by applicable law or agreed to in writing, software 00013 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 00014 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00015 # See the License for the specific language governing permissions and 00016 # limitations under the License. 00017 # 00018 00019 from __future__ import absolute_import 00020 from __future__ import division 00021 import os 00022 import time 00023 import shutil 00024 import six 00025 import info 00026 import intelhex 00027 from test_info import TestInfo 00028 00029 from pyocd.core.helpers import ConnectHelper 00030 from pyocd.core.memory_map import MemoryType 00031 00032 def _same(d1, d2): 00033 assert type(d1) is bytearray 00034 assert type(d2) is bytearray 00035 for i in range(min(len(d1), len(d2))): 00036 if d1[i] != d2[i]: 00037 return False 00038 if len(d1) != len(d2): 00039 return False 00040 return True 00041 00042 MOCK_DIR_LIST = [ 00043 "test", 00044 "blarg", 00045 "very_long_directory_name", 00046 "very_long_directory_name/and_subdirectory_name" 00047 ] 00048 00049 MOCK_FILE_LIST = [ 00050 (".test", "blarg"), 00051 ("test/file1", "asdofahweaw"), 00052 ("file.jpg", "file contents here") 00053 ] 00054 00055 MOCK_DIR_LIST_AFTER = [ 00056 "test2", 00057 "blarg2", 00058 "very_long_directory_name2", 00059 "very_long_directory_name2/and_subdirectory_name" 00060 ] 00061 00062 MOCK_FILE_LIST_AFTER = [ 00063 (".test2", "blarg"), 00064 ("test2/file12", "asdofahweaw"), 00065 ("file2.jpg", "file contents here") 00066 ] 00067 00068 class MassStorageTester(object): 00069 00070 RETRY_COUNT = 5 00071 DELAY_BEFORE_RETRY_S = 30 00072 00073 def __init__ (self, board, parent_test, test_name): 00074 self.board = board 00075 self.parent_test = parent_test 00076 self.test_name = test_name 00077 self._expected_failure_msg = None 00078 self._flush_time = 0.1 00079 self._load_with_shutils = None 00080 self._flush_size = None 00081 self._programming_data = None 00082 self._mock_file_list = [] 00083 self._mock_dir_list = [] 00084 self._mock_file_list_after = [] 00085 self._mock_dir_list_after = [] 00086 self._programming_file_name = None 00087 self._start = 0 00088 00089 def set_shutils_copy(self, source_file_name): 00090 """ 00091 Change mode to shutil file copy 00092 00093 This option cannot be used with set_programming_data. 00094 """ 00095 assert type(source_file_name) is str 00096 assert self._load_with_shutils is None 00097 self._source_file_name = source_file_name 00098 self._load_with_shutils = True 00099 00100 def set_programming_data(self, data, file_name): 00101 """ 00102 Set data to program over mass storage 00103 00104 Data should be the conetents of the hex or binary file 00105 being loaded. This option cannot be used with set_shutils_copy. 00106 """ 00107 assert type(data) is bytearray 00108 assert type(file_name) is str 00109 assert(self._load_with_shutils is False or 00110 self._load_with_shutils is None) 00111 self._load_with_shutils = False 00112 self._programming_data = data 00113 self._programming_file_name = file_name 00114 00115 def set_flush_size(self, size): 00116 """Set the block size to simulate a flush of""" 00117 assert isinstance(size, six.integer_types) 00118 self._flush_size = size 00119 00120 def set_expected_data(self, data, start=0): 00121 """Data that should have been written to the device""" 00122 assert data is None or type(data) is bytearray 00123 self._expected_data = data 00124 self._start = start 00125 00126 def set_expected_failure_msg(self, msg, error_type): 00127 """Set the expected failure message as a string""" 00128 assert msg is None or type(msg) is str 00129 self._expected_failure_msg = msg 00130 self._expected_failure_type = error_type 00131 00132 def add_mock_files(self, file_list): 00133 """Add a list of tuples containing a file and contents""" 00134 self._mock_file_list.extend(file_list) 00135 00136 def add_mock_dirs(self, dir_list): 00137 """Add a list of directoies""" 00138 self._mock_dir_list.extend(dir_list) 00139 00140 def add_mock_files_after_load(self, file_list): 00141 """Add a list of tuples containing a file and contents""" 00142 self._mock_file_list_after.extend(file_list) 00143 00144 def add_mock_dirs_after_load(self, dir_list): 00145 """Add a list of directoies""" 00146 self._mock_dir_list_after.extend(dir_list) 00147 00148 def _check_data_correct(self, expected_data, _): 00149 """Return True if the actual data written matches the expected""" 00150 data_len = len(expected_data) 00151 data_loaded = self.board.read_target_memory(self._start, data_len) 00152 return _same(expected_data, data_loaded) 00153 00154 def run(self): 00155 for retry_count in range(self.RETRY_COUNT): 00156 test_info = TestInfo(self.test_name) 00157 if retry_count > 0: 00158 test_info.info('Previous attempts %s' % retry_count) 00159 try: 00160 self._run(test_info) 00161 except IOError: 00162 time.sleep(self.DELAY_BEFORE_RETRY_S) 00163 # Update board info since a remount could have occurred 00164 self.board.update_board_info() 00165 continue 00166 self.parent_test.attach_subtest(test_info) 00167 break 00168 else: 00169 raise Exception("Flashing failed after %i retries" % 00170 self.RETRY_COUNT) 00171 00172 def _run(self, test_info): 00173 # Expected data must be set, even if to None 00174 assert hasattr(self, '_expected_data') 00175 00176 # Windows 8/10 workaround 00177 # ---- 00178 # By default Windows 8 and 10 access and write to removable drives 00179 # shortly after they are connected. If this occurs at the same time 00180 # as a file copy the file could be sent out of order causing DAPLink 00181 # programming to terminate early and report an error. 00182 # 00183 # This causes testing to intermittently fail with errors such as: 00184 # - "The transfer timed out." 00185 # - "File sent out of order by PC. Target might 00186 # not be programmed correctly." 00187 # 00188 # To prevent Windows from writing to removable drives on connect 00189 # drive indexing can be turned off with the following procedure: 00190 # - Start the program "gpedit.msc" 00191 # - Navigate to "Computer Configuration \ Administrative Templates 00192 # \ Windows Components \ Search" 00193 # - Enable the policy "Do not allow locations on removable drives 00194 # to be added to libraries." 00195 # 00196 # Rather than requiring all testers of DAPLink make this setting 00197 # change the below sleep has been added. This added delay allows 00198 # windows to complete the writes it performs shortly after connect. 00199 # This allows testing to be performed without interruption. 00200 # 00201 # Note - if drive indexing is turned off as mentioned above then 00202 # this sleep is not needed. 00203 time.sleep(2) 00204 00205 # Copy mock files before test 00206 self._mock_file_list = [] 00207 for dir_name in self._mock_dir_list: 00208 dir_path = self.board.get_file_path(dir_name) 00209 os.mkdir(dir_path) 00210 for file_name, file_contents in self._mock_file_list: 00211 file_path = self.board.get_file_path(file_name) 00212 with open(file_path, 'wb') as file_handle: 00213 file_handle.write(file_contents) 00214 00215 programming_file_name = None 00216 if self._programming_file_name is not None: 00217 programming_file_name = \ 00218 self.board.get_file_path(self._programming_file_name) 00219 00220 # Write data to the file 00221 start = time.time() 00222 if self._load_with_shutils: 00223 # Copy with shutils 00224 shutil.copy(self._source_file_name, self.board.get_mount_point()) 00225 elif self._flush_size is not None: 00226 # Simulate flushes during the file transfer 00227 # Note - The file is explicitly opened and closed to more 00228 # consistently simulate the undesirable behavior flush can 00229 # cause. On Windows flushing a file causes the data to be 00230 # written out immediately, but only sometimes causes the 00231 # filesize to get updated. 00232 size = len(self._programming_data) 00233 for addr in range(0, size, self._flush_size): 00234 data = self._programming_data[addr:addr + self._flush_size] 00235 with open(programming_file_name, 'ab') as file_handle: 00236 file_handle.write(data) 00237 time.sleep(self._flush_time) 00238 else: 00239 # Perform a normal copy 00240 with open(programming_file_name, 'wb') as load_file: 00241 load_file.write(self._programming_data) 00242 stop = time.time() 00243 diff = stop - start 00244 test_info.info('Loading took %ss' % diff) 00245 if self._expected_data is not None: 00246 test_info.info('Programming rate %sB/s' % 00247 (len(self._expected_data) / diff)) 00248 if self._programming_data is not None: 00249 test_info.info('MSD transfer rate %sB/s' % 00250 (len(self._programming_data) / diff)) 00251 00252 # Copy mock files after loading 00253 self._mock_file_list = [] 00254 for dir_name in self._mock_dir_list_after: 00255 dir_path = self.board.get_file_path(dir_name) 00256 os.mkdir(dir_path) 00257 for file_name, file_contents in self._mock_file_list_after: 00258 file_path = self.board.get_file_path(file_name) 00259 with open(file_path, 'w') as file_handle: 00260 file_handle.write(file_contents) 00261 00262 self.board.wait_for_remount(test_info) 00263 00264 # Verify the disk is still valid 00265 self.board.test_fs(test_info) 00266 00267 # Check various failure cases 00268 msg, error_type = self.board.get_failure_message_and_type() 00269 failure_expected = self._expected_failure_msg is not None 00270 failure_occured = msg is not None 00271 if failure_occured and not failure_expected: 00272 test_info.failure('Device reported failure: "%s"' % msg.strip()) 00273 return 00274 if failure_expected and not failure_occured: 00275 test_info.failure('Failure expected but did not occur') 00276 return 00277 if failure_expected and failure_occured: 00278 if msg == self._expected_failure_msg and error_type == self._expected_failure_type: 00279 test_info.info( 00280 'Failure as expected: "%s, %s"' % 00281 (msg.strip(), error_type.strip())) 00282 elif msg != self._expected_failure_msg: 00283 test_info.failure('Failure but wrong string: "%s" vs "%s"' % 00284 (msg.strip(), 00285 self._expected_failure_msg.strip())) 00286 else: 00287 test_info.failure( 00288 'Failure but wrong type: "%s" vs "%s"' % 00289 (error_type.strip(), self._expected_failure_type.strip())) 00290 return 00291 00292 # These cases should have been handled above 00293 assert not failure_expected 00294 assert not failure_occured 00295 00296 # If there is expected data then compare 00297 if self._expected_data: 00298 if self._check_data_correct(self._expected_data, test_info): 00299 test_info.info("Data matches") 00300 else: 00301 test_info.failure('Data does not match') 00302 00303 00304 def test_mass_storage(workspace, parent_test): 00305 """Test the mass storage endpoint 00306 00307 Requirements: 00308 None 00309 00310 Positional arguments: 00311 filename - A string containing the name of the file to load 00312 00313 Return: 00314 True if the test passed, False otherwise 00315 """ 00316 test_info = parent_test.create_subtest('test_mass_storage') 00317 00318 # Setup test 00319 board = workspace.board 00320 target = workspace.target 00321 bin_file = target.bin_path 00322 hex_file = target.hex_path 00323 with open(bin_file, 'rb') as test_file: 00324 bin_file_contents = bytearray(test_file.read()) 00325 with open(hex_file, 'rb') as test_file: 00326 hex_file_contents = bytearray(test_file.read()) 00327 blank_bin_contents = bytearray([0xff]) * 0x2000 00328 vectors_and_pad = bin_file_contents[0:32] + blank_bin_contents 00329 locked_when_erased = board.get_board_id() in info.BOARD_ID_LOCKED_WHEN_ERASED 00330 page_erase_supported = board.get_board_id() in info.BOARD_ID_SUPPORTING_PAGE_ERASE 00331 bad_vector_table = target.name in info.TARGET_WITH_BAD_VECTOR_TABLE_LIST 00332 00333 intel_hex = intelhex.IntelHex(hex_file) 00334 addresses = intel_hex.addresses() 00335 addresses.sort() 00336 start = addresses[0] 00337 00338 # Test loading a binary file with shutils 00339 if not bad_vector_table: 00340 test = MassStorageTester(board, test_info, "Shutil binary file load") 00341 test.set_shutils_copy(bin_file) 00342 test.set_expected_data(bin_file_contents, start) 00343 test.run() 00344 00345 # Test loading a binary file with flushes 00346 if not bad_vector_table: 00347 test = MassStorageTester(board, test_info, "Load binary with flushes") 00348 test.set_programming_data(bin_file_contents, 'image.bin') 00349 test.set_expected_data(bin_file_contents, start) 00350 test.set_flush_size(0x1000) 00351 test.run() 00352 00353 # Test loading a hex file with shutils 00354 test = MassStorageTester(board, test_info, "Shutil hex file load") 00355 test.set_shutils_copy(hex_file) 00356 test.set_expected_data(bin_file_contents, start) 00357 test.run() 00358 00359 # Test loading a hex file with flushes 00360 test = MassStorageTester(board, test_info, "Load hex with flushes") 00361 test.set_programming_data(hex_file_contents, 'image.hex') 00362 test.set_expected_data(bin_file_contents, start) 00363 test.set_flush_size(0x1000) 00364 test.run() 00365 00366 # Test loading a binary smaller than a sector 00367 if not bad_vector_table: 00368 test = MassStorageTester(board, test_info, "Load .bin smaller than sector") 00369 test_data_size = 0x789 00370 test_data = bin_file_contents[0:0 + test_data_size] 00371 test.set_programming_data(test_data, 'image.bin') 00372 test.set_expected_data(test_data, start) 00373 test.run() 00374 00375 # Test loading a blank binary - this image should cause a timeout 00376 # since it doesn't have a valid vector table 00377 test = MassStorageTester(board, test_info, "Load blank binary") 00378 test.set_programming_data(blank_bin_contents, 'image.bin') 00379 test.set_expected_failure_msg("The transfer timed out.", "transient, user") 00380 test.set_expected_data(None, start) 00381 test.run() 00382 00383 # Test loading a blank binary with a vector table but padded with 0xFF. 00384 # A blank image can lock some devices. 00385 if not bad_vector_table: 00386 test = MassStorageTester(board, test_info, "Load blank binary + vector table") 00387 test.set_programming_data(vectors_and_pad, 'image.bin') 00388 if locked_when_erased: 00389 test.set_expected_failure_msg("The interface firmware ABORTED programming. Image is trying to set security bits", "user") 00390 test.set_expected_data(None, start) 00391 else: 00392 test.set_expected_data(vectors_and_pad, start) 00393 test.run() 00394 00395 # Test a normal load with dummy files created beforehand 00396 test = MassStorageTester(board, test_info, "Extra Files") 00397 test.set_programming_data(hex_file_contents, 'image.hex') 00398 test.add_mock_dirs(MOCK_DIR_LIST) 00399 test.add_mock_files(MOCK_FILE_LIST) 00400 test.add_mock_dirs_after_load(MOCK_DIR_LIST_AFTER) 00401 test.add_mock_files_after_load(MOCK_FILE_LIST_AFTER) 00402 test.set_expected_data(bin_file_contents, start) 00403 test.run() 00404 # Note - it is not unexpected for an "Extra Files" test to fail 00405 # when a binary file is loaded, since there is no way to 00406 # tell where the end of the file is. 00407 00408 if page_erase_supported: 00409 # Test page erase, a.k.a. sector erase by generating iHex with discrete addresses, 00410 # programing the device then comparing device memory against expected content. 00411 test = MassStorageTester(board, test_info, "Sector Erase") 00412 with ConnectHelper.session_with_chosen_probe(unique_id=board.get_unique_id(), open_session=False) as session: 00413 memory_map = session.target.memory_map 00414 flash_regions = memory_map.get_regions_of_type(MemoryType.FLASH) 00415 00416 max_address = intel_hex.maxaddr() 00417 # Create an object. We'll add the addresses of unused even blocks to it first, then unused odd blocks for each region 00418 ih = intelhex.IntelHex() 00419 # Add the content from test bin first 00420 expected_bin_contents = bin_file_contents 00421 for region_index, the_region in enumerate(flash_regions): 00422 if the_region.is_boot_memory is False: 00423 continue 00424 flash_start = the_region.start 00425 flash_length = the_region.length 00426 block_size = the_region.blocksize 00427 00428 number_of_blocks = flash_length // block_size 00429 00430 # Sanity check the regions are contiguous 00431 if region_index: 00432 assert flash_start == (flash_regions[region_index - 1].start + flash_regions[region_index - 1].length) 00433 00434 if max_address >= (flash_start + flash_length): 00435 # This bin image crosses this region, don't modify the content, go to the next region 00436 continue 00437 elif max_address >= flash_start: 00438 # This bin image occupies partial region. Skip the used portion to avoid touching any security bits and pad the rest 00439 expected_bin_contents += bytearray([0xff]) * (flash_start + flash_length - max_address - 1) 00440 # Calculate the starting block after the image to avoid stumbling upon security bits 00441 block_start = (max_address - flash_start) // block_size + 1 00442 else: 00443 # This bin image doesn't reach this region 00444 expected_bin_contents += bytearray([0xff]) * flash_length 00445 block_start = 0 00446 # For all even blocks, overwrite all addresses with 0x55; for all odd blocks, overwrite all addresses with 0xAA 00447 for pass_number in range (2): 00448 if pass_number == 0: 00449 modifier = 0x55 00450 else: 00451 modifier = 0xAA 00452 block_start += 1 00453 for block_idx in range(block_start, number_of_blocks, 2): 00454 for address_to_modify in range (flash_start + block_idx * block_size, flash_start + (block_idx + 1) * block_size): 00455 expected_bin_contents[address_to_modify] = modifier 00456 ih[address_to_modify] = modifier 00457 if not os.path.exists("tmp"): 00458 os.makedirs("tmp") 00459 # Write out the modified iHex to file 00460 ih.tofile("tmp/interleave.hex", format='hex') 00461 # Load this hex file with shutils 00462 test.set_shutils_copy("tmp/interleave.hex") 00463 test.set_expected_data(expected_bin_contents, start) 00464 test.run() 00465 00466 # Finally, load a good binary 00467 test = MassStorageTester(board, test_info, "Load good file to restore state") 00468 test.set_programming_data(hex_file_contents, 'image.hex') 00469 test.set_expected_data(bin_file_contents, start) 00470 test.run() 00471 00472 # Ideas for future tests - contributions welcome 00473 # -Zero length file 00474 # -Corrupt hex file 00475 # -Dummy files loaded before test 00476 # -Very large file 00477 # -Any MSD regressions 00478 # -Security bits in hex files 00479 # -Hex file with data at the end ** 00480 # -Hidden files 00481 # -change file extension 00482 # -Change size (make smaller) 00483 # -change starting address
Generated on Tue Jul 12 2022 15:37:21 by
1.7.2