Arrow / Mbed OS DAPLink Reset
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers msd_test.py Source File

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