1 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import os, fcntl, logging, struct, random 6 7 from autotest_lib.client.bin import test, utils 8 from autotest_lib.client.common_lib import error 9 10 11 class hardware_TrimIntegrity(test.test): 12 """ 13 Performs data integrity trim test on an unmounted partition. 14 15 This test will write 1 GB of data and verify that trimmed data are gone and 16 untrimmed data are unaffected. The verification will be run in 5 passes with 17 0%, 25%, 50%, 75%, and 100% of data trimmed. 18 19 Also, perform 4K random read QD32 before and after trim. We should see some 20 speed / latency difference if the device firmware trim data properly. 21 22 Condition for test result: 23 - Trim command is not supported 24 -> Target disk is a harddisk : TestNA 25 -> Target disk is SCSI disk w/o trim : TestNA 26 -> Otherwise : TestFail 27 - Can not verify integrity of untrimmed data 28 -> All case : TestFail 29 - Trim data is not Zero 30 -> SSD with RZAT : TestFail 31 -> Otherwise : TestNA 32 """ 33 34 version = 1 35 FILE_SIZE = 1024 * 1024 * 1024 36 CHUNK_SIZE = 192 * 1024 37 TRIM_RATIO = [0, 0.25, 0.5, 0.75, 1] 38 39 hdparm_trim = 'Data Set Management TRIM supported' 40 hdparm_rzat = 'Deterministic read ZEROs after TRIM' 41 42 # Use hash value to check integrity of the random data. 43 HASH_CMD = 'sha256sum | cut -d" " -f 1' 44 # 0x1277 is ioctl BLKDISCARD command 45 IOCTL_TRIM_CMD = 0x1277 46 IOCTL_NOT_SUPPORT_ERRNO = 95 47 48 def _get_hash(self, chunk_count, chunk_size): 49 """ 50 Get hash for every chunk of data. 51 """ 52 cmd = str('for i in $(seq 0 %d); do dd if=%s of=/dev/stdout bs=%d' 53 ' count=1 skip=$i iflag=direct | %s; done' % 54 (chunk_count - 1, self._filename, chunk_size, self.HASH_CMD)) 55 return utils.run(cmd).stdout.split() 56 57 def _do_trim(self, fd, offset, size): 58 """ 59 Invoke ioctl to trim command. 60 """ 61 fcntl.ioctl(fd, self.IOCTL_TRIM_CMD, struct.pack('QQ', offset, size)) 62 63 def _verify_trim_support(self, size): 64 """ 65 Check for trim support in ioctl. Raise TestNAError if not support. 66 67 @param size: size to try the trim command 68 """ 69 try: 70 fd = os.open(self._filename, os.O_RDWR, 0666) 71 self._do_trim(fd, 0, size) 72 except IOError, err: 73 if err.errno == self.IOCTL_NOT_SUPPORT_ERRNO: 74 reason = 'IOCTL Does not support trim.' 75 msg = utils.get_storage_error_msg(self._diskname, reason) 76 77 if utils.is_disk_scsi(self._diskname): 78 if utils.is_disk_harddisk(self._diskname): 79 msg += ' Disk is a hard disk.' 80 raise error.TestNAError(msg) 81 if utils.verify_hdparm_feature(self._diskname, 82 self.hdparm_trim): 83 msg += ' Disk claims trim supported.' 84 else: 85 msg += ' Disk does not claim trim supported.' 86 raise error.TestNAError(msg) 87 # SSD with trim support / mmc / sd card 88 raise error.TestFail(msg) 89 else: 90 raise 91 finally: 92 os.close(fd) 93 94 def initialize(self): 95 self.job.use_sequence_number = True 96 97 def run_once(self, filename=None, file_size=FILE_SIZE, 98 chunk_size=CHUNK_SIZE, trim_ratio=TRIM_RATIO): 99 """ 100 Executes the test and logs the output. 101 @param file_name: file/disk name to test 102 default: spare partition of internal disk 103 @param file_size: size of data to test. default: 1GB 104 @param chunk_size: size of chunk to calculate hash/trim. default: 64KB 105 @param trim_ratio: list of ratio of file size to trim data 106 default: [0, 0.25, 0.5, 0.75, 1] 107 """ 108 109 if not filename: 110 self._diskname = utils.get_fixed_dst_drive() 111 if self._diskname == utils.get_root_device(): 112 self._filename = utils.get_free_root_partition() 113 else: 114 self._filename = self._diskname 115 else: 116 self._filename = filename 117 self._diskname = utils.get_disk_from_filename(filename) 118 119 if file_size == 0: 120 fulldisk = True 121 file_size = utils.get_disk_size(self._filename) 122 if file_size == 0: 123 cmd = ('%s seem to have 0 storage block. Is the media present?' 124 % filename) 125 raise error.TestError(cmd) 126 else: 127 fulldisk = False 128 129 # Make file size multiple of 4 * chunk size 130 file_size -= file_size % (4 * chunk_size) 131 132 if fulldisk: 133 fio_file_size = 0 134 else: 135 fio_file_size = file_size 136 137 logging.info('filename: %s, filesize: %d', self._filename, file_size) 138 139 self._verify_trim_support(chunk_size) 140 141 # Calculate hash value for zero'ed and one'ed data 142 cmd = str('dd if=/dev/zero bs=%d count=1 | %s' % 143 (chunk_size, self.HASH_CMD)) 144 zero_hash = utils.run(cmd).stdout.strip() 145 146 cmd = str("dd if=/dev/zero bs=%d count=1 | tr '\\0' '\\xff' | %s" % 147 (chunk_size, self.HASH_CMD)) 148 one_hash = utils.run(cmd).stdout.strip() 149 150 trim_hash = "" 151 152 # Write random data to disk 153 chunk_count = file_size / chunk_size 154 cmd = str('dd if=/dev/urandom of=%s bs=%d count=%d oflag=direct' % 155 (self._filename, chunk_size, chunk_count)) 156 utils.run(cmd) 157 158 ref_hash = self._get_hash(chunk_count, chunk_size) 159 160 # Check read speed/latency when reading real data. 161 self.job.run_test('hardware_StorageFio', 162 disable_sysinfo=True, 163 filesize=fio_file_size, 164 requirements=[('4k_read_qd32', [])], 165 tag='before_trim') 166 167 # Generate random order of chunk to trim 168 trim_order = list(range(0, chunk_count)) 169 random.shuffle(trim_order) 170 trim_status = [False] * chunk_count 171 172 # Init stat variable 173 data_verify_count = 0 174 data_verify_match = 0 175 trim_verify_count = 0 176 trim_verify_zero = 0 177 trim_verify_one = 0 178 trim_verify_non_delete = 0 179 trim_deterministic = True 180 181 last_ratio = 0 182 for ratio in trim_ratio: 183 184 # Do trim 185 begin_trim_chunk = int(last_ratio * chunk_count) 186 end_trim_chunk = int(ratio * chunk_count) 187 fd = os.open(self._filename, os.O_RDWR, 0666) 188 for chunk in trim_order[begin_trim_chunk:end_trim_chunk]: 189 self._do_trim(fd, chunk * chunk_size, chunk_size) 190 trim_status[chunk] = True 191 os.close(fd) 192 last_ratio = ratio 193 194 cur_hash = self._get_hash(chunk_count, chunk_size) 195 196 trim_verify_count += int(ratio * chunk_count) 197 data_verify_count += chunk_count - int(ratio * chunk_count) 198 199 # Verify hash 200 for cur, ref, trim in zip(cur_hash, ref_hash, trim_status): 201 if trim: 202 if not trim_hash: 203 trim_hash = cur 204 elif cur != trim_hash: 205 trim_deterministic = False 206 207 if cur == zero_hash: 208 trim_verify_zero += 1 209 elif cur == one_hash: 210 trim_verify_one += 1 211 elif cur == ref: 212 trim_verify_non_delete += 1 213 else: 214 if cur == ref: 215 data_verify_match += 1 216 217 keyval = dict() 218 keyval['data_verify_count'] = data_verify_count 219 keyval['data_verify_match'] = data_verify_match 220 keyval['trim_verify_count'] = trim_verify_count 221 keyval['trim_verify_zero'] = trim_verify_zero 222 keyval['trim_verify_one'] = trim_verify_one 223 keyval['trim_verify_non_delete'] = trim_verify_non_delete 224 keyval['trim_deterministic'] = trim_deterministic 225 self.write_perf_keyval(keyval) 226 227 # Check read speed/latency when reading from trimmed data. 228 self.job.run_test('hardware_StorageFio', 229 disable_sysinfo=True, 230 filesize=fio_file_size, 231 requirements=[('4k_read_qd32', [])], 232 tag='after_trim') 233 234 if data_verify_match < data_verify_count: 235 reason = 'Fail to verify untrimmed data.' 236 msg = utils.get_storage_error_msg(self._diskname, reason) 237 raise error.TestFail(msg) 238 239 if trim_verify_zero < trim_verify_count: 240 reason = 'Trimmed data are not zeroed.' 241 msg = utils.get_storage_error_msg(self._diskname, reason) 242 if utils.is_disk_scsi(self._diskname): 243 if utils.verify_hdparm_feature(self._diskname, 244 self.hdparm_rzat): 245 msg += ' Disk claim deterministic read zero after trim.' 246 raise error.TestFail(msg) 247 raise error.TestNAError(msg) 248