1 /*
2 * Copyright (c) 2006-2022 Douglas Gilbert.
3 * All rights reserved.
4 * Use of this source code is governed by a BSD-style
5 * license that can be found in the BSD_LICENSE file.
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <errno.h>
17 #include <string.h>
18 #include <getopt.h>
19 #define __STDC_FORMAT_MACROS 1
20 #include <inttypes.h>
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "sg_lib.h"
27 #include "sg_cmds_basic.h"
28 #include "sg_cmds_extra.h"
29 #include "sg_pr2serr.h"
30
31 static const char * version_str = "1.15 20220425";
32
33 /* This program uses a ATA PASS-THROUGH SCSI command. This usage is
34 * defined in the SCSI to ATA Translation (SAT) drafts and standards.
35 * See https://www.t10.org for drafts. SAT is a standard: SAT ANSI INCITS
36 * 431-2007 (draft prior to that is sat-r09.pdf). SAT-2 is also a
37 * standard: SAT-2 ANSI INCITS 465-2010 and the draft prior to that is
38 * sat2r09.pdf . The SAT-3 project has started and the most recent draft
39 * is sat3r01.pdf .
40 */
41
42 /* This program uses a ATA PASS-THROUGH (16 or 12) SCSI command defined
43 * by SAT to package an ATA READ LOG EXT (2Fh) command to fetch
44 * log page 11h. That page contains SATA phy event counters.
45 * For ATA READ LOG EXT command see ATA-8/ACS at www.t13.org .
46 * For SATA phy counter definitions see SATA 2.5 .
47 *
48 * Invocation: see the usage() function below
49 */
50
51 #define SAT_ATA_PASS_THROUGH16 0x85
52 #define SAT_ATA_PASS_THROUGH16_LEN 16
53 #define SAT_ATA_PASS_THROUGH12 0xa1 /* clashes with MMC BLANK command */
54 #define SAT_ATA_PASS_THROUGH12_LEN 12
55 #define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
56 #define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d
57
58 #define ATA_READ_LOG_EXT 0x2f
59 #define SATA_PHY_EVENT_LPAGE 0x11
60 #define READ_LOG_EXT_RESPONSE_LEN 512
61
62 #define DEF_TIMEOUT 20
63
64 #define EBUFF_SZ 256
65
66 static struct option long_options[] = {
67 {"ck_cond", no_argument, 0, 'c'},
68 {"ck-cond", no_argument, 0, 'c'},
69 {"extend", no_argument, 0, 'e'},
70 {"hex", no_argument, 0, 'H'},
71 {"ignore", no_argument, 0, 'i'},
72 {"len", no_argument, 0, 'l'},
73 {"raw", no_argument, 0, 'r'},
74 {"reset", no_argument, 0, 'R'},
75 {"help", no_argument, 0, 'h'},
76 {"verbose", no_argument, 0, 'v'},
77 {"version", no_argument, 0, 'V'},
78 {0, 0, 0, 0},
79 };
80
81 struct phy_event_t {
82 int id;
83 const char * desc;
84 };
85
86 static struct phy_event_t phy_event_arr[] = { /* SATA 2.5 section 13.7.2 */
87 {0x1, "Command failed and ICRC error bit set in Error register"}, /* M */
88 {0x2, "R_ERR(p) response for data FIS"},
89 {0x3, "R_ERR(p) response for device-to-host data FIS"},
90 {0x4, "R_ERR(p) response for host-to-device data FIS"},
91 {0x5, "R_ERR(p) response for non-data FIS"},
92 {0x6, "R_ERR(p) response for device-to-host non-data FIS"},
93 {0x7, "R_ERR(p) response for host-to-device non-data FIS"},
94 {0x8, "Device-to-host non-data FIS retries"},
95 {0x9, "Transition from drive PHYRDY to drive PHYRDYn"},
96 {0xa, "Signature device-to-host register FISes due to COMRESET"}, /* M */
97 {0xb, "CRC errors within host-to-device FIS"},
98 {0xd, "non CRC errors within host-to-device FIS"},
99 {0xf, "R_ERR(p) response for host-to-device data FIS, CRC"},
100 {0x10, "R_ERR(p) response for host-to-device data FIS, non-CRC"},
101 {0x12, "R_ERR(p) response for host-to-device non-data FIS, CRC"},
102 {0x13, "R_ERR(p) response for host-to-device non-data FIS, non-CRC"},
103 {0xc00, "PM: host-to-device non-data FIS, R_ERR(p) due to collision"},
104 {0xc01, "PM: signature register - device-to-host FISes"},
105 {0xc02, "PM: corrupts CRC propagation of device-to-host FISes"},
106 {0x0, NULL}, /* end marker */ /* M(andatory) */
107 };
108
109 static void
usage()110 usage()
111 {
112 pr2serr("Usage: sg_sat_phy_event [--ck_cond] [--extend] [--help] [--hex] "
113 "[--ignore]\n"
114 " [--len=16|12] [--raw] [--reset] "
115 "[--verbose]\n"
116 " [--version] DEVICE\n"
117 " where:\n"
118 " --ck_cond|-c sets ck_cond bit in cdb (def: 0)\n"
119 " --extend|-e sets extend bit in cdb (def: 0)\n"
120 " --help|-h print this usage message then exit\n"
121 " --hex|-H output response in hex bytes, use twice for\n"
122 " hex words\n"
123 " --ignore|-i ignore identifier names, output id value "
124 "instead\n"
125 " --len=16|12 | -l 16|12 cdb length: 16 or 12 bytes "
126 "(default: 16)\n"
127 " --raw|-r output response in binary to stdout\n"
128 " --reset|-R reset counters (after read)\n"
129 " --verbose|-v increase verbosity\n"
130 " --version|-V print version string then exit\n\n"
131 "Sends an ATA READ LOG EXT command via a SAT pass through to "
132 "fetch\nlog page 11h which contains SATA phy event counters\n");
133 }
134
135 static const char *
find_phy_desc(int id)136 find_phy_desc(int id)
137 {
138 const struct phy_event_t * pep;
139
140 for (pep = phy_event_arr; pep->desc; ++pep) {
141 if ((id & 0xfff) == pep->id)
142 return pep->desc;
143 }
144 return NULL;
145 }
146
147 static void
dStrRaw(const uint8_t * str,int len)148 dStrRaw(const uint8_t * str, int len)
149 {
150 int k;
151
152 for (k =0; k < len; ++k)
153 printf("%c", str[k]);
154 }
155
156 /* ATA READ LOG EXT command [2Fh, PIO data-in] */
157 /* N.B. "log_addr" is the log page number, "page_in_log" is usually 0 */
158 static int
do_read_log_ext(int sg_fd,int log_addr,int page_in_log,int feature,int blk_count,void * resp,int mx_resp_len,int cdb_len,bool ck_cond,bool extend,int do_hex,bool do_raw,int verbose)159 do_read_log_ext(int sg_fd, int log_addr, int page_in_log, int feature,
160 int blk_count, void * resp, int mx_resp_len, int cdb_len,
161 bool ck_cond, bool extend, int do_hex, bool do_raw,
162 int verbose)
163 {
164 /* Following for ATA READ/WRITE MULTIPLE (EXT) cmds, normally 0 */
165 #if 0
166 bool t_type = false;/* false -> 512 byte LBs, true -> device's LB size */
167 #endif
168 bool t_dir = true; /* false -> to device, 1 -> from device */
169 bool byte_block = true; /* false -> bytes, true -> 512 byte blocks (if
170 t_type=false) */
171 bool got_ard = false; /* got ATA result descriptor */
172 bool ok;
173 int res, ret;
174 int multiple_count = 0;
175 int protocol = 4; /* PIO data-in */
176 int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */
177 int resid = 0;
178 int sb_sz;
179 struct sg_scsi_sense_hdr ssh;
180 uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
181 uint8_t ata_return_desc[16] SG_C_CPP_ZERO_INIT;
182 uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
183 {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
184 0, 0, 0, 0, 0, 0, 0, 0};
185 uint8_t apt12_cdb[SAT_ATA_PASS_THROUGH12_LEN] =
186 {SAT_ATA_PASS_THROUGH12, 0, 0, 0, 0, 0, 0, 0,
187 0, 0, 0, 0};
188
189 sb_sz = sizeof(sense_buffer);
190 ok = false;
191 if (SAT_ATA_PASS_THROUGH16_LEN == cdb_len) {
192 /* Prepare ATA PASS-THROUGH COMMAND (16) command */
193 apt_cdb[3] = (feature >> 8) & 0xff; /* feature(15:8) */
194 apt_cdb[4] = feature & 0xff; /* feature(7:0) */
195 apt_cdb[5] = (blk_count >> 8) & 0xff; /* sector_count(15:8) */
196 apt_cdb[6] = blk_count & 0xff; /* sector_count(7:0) */
197 apt_cdb[8] = log_addr & 0xff; /* lba_low(7:0) == LBA(7:0) */
198 apt_cdb[9] = (page_in_log >> 8) & 0xff;
199 /* lba_mid(15:8) == LBA(39:32) */
200 apt_cdb[10] = page_in_log & 0xff; /* lba_mid(7:0) == LBA(15:8) */
201 apt_cdb[14] = ATA_READ_LOG_EXT;
202 apt_cdb[1] = (multiple_count << 5) | (protocol << 1);
203 if (extend)
204 apt_cdb[1] |= 0x1;
205 apt_cdb[2] = t_length;
206 if (ck_cond)
207 apt_cdb[2] |= 0x20;
208 #if 0
209 if (t_type)
210 apt_cdb[2] |= 0x10;
211 #endif
212 if (t_dir)
213 apt_cdb[2] |= 0x8;
214 if (byte_block)
215 apt_cdb[2] |= 0x4;
216 res = sg_ll_ata_pt(sg_fd, apt_cdb, cdb_len, DEF_TIMEOUT, resp,
217 NULL /* doutp */, mx_resp_len, sense_buffer,
218 sb_sz, ata_return_desc,
219 sizeof(ata_return_desc), &resid, verbose);
220 } else {
221 /* Prepare ATA PASS-THROUGH COMMAND (12) command */
222 apt12_cdb[3] = feature & 0xff; /* feature(7:0) */
223 apt12_cdb[4] = blk_count & 0xff; /* sector_count(7:0) */
224 apt12_cdb[5] = log_addr & 0xff; /* lba_low(7:0) == LBA(7:0) */
225 apt12_cdb[6] = page_in_log & 0xff; /* lba_mid(7:0) == LBA(15:8) */
226 apt12_cdb[9] = ATA_READ_LOG_EXT;
227 apt12_cdb[1] = (multiple_count << 5) | (protocol << 1);
228 apt12_cdb[2] = t_length;
229 if (ck_cond)
230 apt12_cdb[2] |= 0x20;
231 #if 0
232 if (t_type)
233 apt12_cdb[2] |= 0x10;
234 #endif
235 if (t_dir)
236 apt12_cdb[2] |= 0x8;
237 if (byte_block)
238 apt12_cdb[2] |= 0x4;
239 res = sg_ll_ata_pt(sg_fd, apt12_cdb, cdb_len, DEF_TIMEOUT, resp,
240 NULL /* doutp */, mx_resp_len, sense_buffer,
241 sb_sz, ata_return_desc,
242 sizeof(ata_return_desc), &resid, verbose);
243 }
244 if (0 == res) {
245 ok = true;
246 if (verbose > 2)
247 pr2serr("command completed with SCSI GOOD status\n");
248 } else if ((res > 0) && (res & SAM_STAT_CHECK_CONDITION)) {
249 if (verbose > 1) {
250 pr2serr("ATA pass through:\n");
251 sg_print_sense(NULL, sense_buffer, sb_sz,
252 ((verbose > 2) ? 1 : 0));
253 }
254 if (sg_scsi_normalize_sense(sense_buffer, sb_sz, &ssh)) {
255 switch (ssh.sense_key) {
256 case SPC_SK_ILLEGAL_REQUEST:
257 if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) {
258 ret = SG_LIB_CAT_INVALID_OP;
259 if (verbose < 2)
260 pr2serr("ATA PASS-THROUGH (%d) not supported\n",
261 cdb_len);
262 } else {
263 ret = SG_LIB_CAT_ILLEGAL_REQ;
264 if (verbose < 2)
265 pr2serr("ATA PASS-THROUGH (%d), bad field in cdb\n",
266 cdb_len);
267 }
268 return ret;
269 case SPC_SK_NO_SENSE:
270 case SPC_SK_RECOVERED_ERROR:
271 if ((0x0 == ssh.asc) &&
272 (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
273 if (SAT_ATA_RETURN_DESC != ata_return_desc[0]) {
274 if (verbose)
275 pr2serr("did not find ATA Return (sense) "
276 "Descriptor\n");
277 return SG_LIB_CAT_RECOVERED;
278 }
279 got_ard = true;
280 break;
281 } else if (SPC_SK_RECOVERED_ERROR == ssh.sense_key)
282 return SG_LIB_CAT_RECOVERED;
283 else {
284 if ((0x0 == ssh.asc) && (0x0 == ssh.ascq))
285 break;
286 return SG_LIB_CAT_SENSE;
287 }
288 case SPC_SK_UNIT_ATTENTION:
289 if (verbose < 2)
290 pr2serr("ATA PASS-THROUGH (%d), Unit Attention detected\n",
291 cdb_len);
292 return SG_LIB_CAT_UNIT_ATTENTION;
293 case SPC_SK_NOT_READY:
294 if (verbose < 2)
295 pr2serr("ATA PASS-THROUGH (%d), device not ready\n",
296 cdb_len);
297 return SG_LIB_CAT_NOT_READY;
298 case SPC_SK_MEDIUM_ERROR:
299 case SPC_SK_HARDWARE_ERROR:
300 if (verbose < 2)
301 pr2serr("ATA PASS-THROUGH (%d), medium or hardware "
302 "error\n", cdb_len);
303 return SG_LIB_CAT_MEDIUM_HARD;
304 case SPC_SK_ABORTED_COMMAND:
305 if (0x10 == ssh.asc) {
306 pr2serr("Aborted command: protection information\n");
307 return SG_LIB_CAT_PROTECTION;
308 } else {
309 pr2serr("Aborted command\n");
310 return SG_LIB_CAT_ABORTED_COMMAND;
311 }
312 case SPC_SK_DATA_PROTECT:
313 pr2serr("ATA PASS-THROUGH (%d): data protect, read only "
314 "media?\n", cdb_len);
315 return SG_LIB_CAT_DATA_PROTECT;
316 default:
317 if (verbose < 2)
318 pr2serr("ATA PASS-THROUGH (%d), some sense data, use "
319 "'-v' for more information\n", cdb_len);
320 return SG_LIB_CAT_SENSE;
321 }
322 } else {
323 pr2serr("CHECK CONDITION without response code ??\n");
324 return SG_LIB_CAT_SENSE;
325 }
326 if (0x72 != (sense_buffer[0] & 0x7f)) {
327 pr2serr("expected descriptor sense format, response code=0x%x\n",
328 sense_buffer[0]);
329 return SG_LIB_CAT_MALFORMED;
330 }
331 } else if (res > 0) {
332 if (SAM_STAT_RESERVATION_CONFLICT == res) {
333 pr2serr("SCSI status: RESERVATION CONFLICT\n");
334 return SG_LIB_CAT_RES_CONFLICT;
335 } else {
336 pr2serr("Unexpected SCSI status=0x%x\n", res);
337 return SG_LIB_CAT_MALFORMED;
338 }
339 } else {
340 pr2serr("ATA pass through (%d) failed\n", cdb_len);
341 if (verbose < 2)
342 pr2serr(" try adding '-v' for more information\n");
343 return -1;
344 }
345
346 if ((SAT_ATA_RETURN_DESC == ata_return_desc[0]) && (! got_ard))
347 pr2serr("Seem to have got ATA Result Descriptor but it was not "
348 "indicated\n");
349 if (got_ard) {
350 if (ata_return_desc[3] & 0x4) {
351 pr2serr("error indication in returned FIS: aborted command\n");
352 return SG_LIB_CAT_ABORTED_COMMAND;
353 }
354 ok = true;
355 }
356
357 if (ok) { /* output result if ok and --hex or --raw given */
358 if (do_raw)
359 dStrRaw((const uint8_t *)resp, mx_resp_len);
360 else if (1 == do_hex)
361 hex2stdout((const uint8_t *)resp, mx_resp_len, 0);
362 else if (do_hex > 1)
363 dWordHex((const unsigned short *)resp, mx_resp_len / 2, 0,
364 sg_is_big_endian());
365 }
366 return 0;
367 }
368
369
main(int argc,char * argv[])370 int main(int argc, char * argv[])
371 {
372 bool ck_cond = false; /* set to true to read register(s) back */
373 bool extend = false;
374 bool ignore = false;
375 bool raw = false;
376 bool reset = false;
377 bool verbose_given = false;
378 bool version_given = false;
379 int sg_fd, c, k, j, res, id, len, vendor, err;
380 char * device_name = 0;
381 char ebuff[EBUFF_SZ];
382 uint8_t inBuff[READ_LOG_EXT_RESPONSE_LEN];
383 int cdb_len = 16;
384 int hex = 0;
385 int verbose = 0;
386 int ret = 0;
387 uint64_t ull;
388 const char * cp;
389
390 memset(inBuff, 0, sizeof(inBuff));
391 while (1) {
392 int option_index = 0;
393
394 c = getopt_long(argc, argv, "cehHil:rRvV",
395 long_options, &option_index);
396 if (c == -1)
397 break;
398
399 switch (c) {
400 case 'c':
401 ck_cond = true;
402 break;
403 case 'e':
404 extend = true;
405 break;
406 case 'h':
407 case '?':
408 usage();
409 exit(0);
410 case 'H':
411 ++hex;
412 break;
413 case 'i':
414 ignore = true;
415 break;
416 case 'l':
417 cdb_len = sg_get_num(optarg);
418 if (! ((cdb_len == 12) || (cdb_len == 16))) {
419 pr2serr("argument to '--len' should be 12 or 16\n");
420 return SG_LIB_SYNTAX_ERROR;
421 }
422 break;
423 case 'r':
424 raw = true;
425 break;
426 case 'R':
427 reset = true;
428 break;
429 case 'v':
430 verbose_given = true;
431 ++verbose;
432 break;
433 case 'V':
434 version_given = true;
435 break;
436 default:
437 pr2serr("unrecognised option code %c [0x%x]\n", c, c);
438 usage();
439 return SG_LIB_SYNTAX_ERROR;
440 }
441 }
442 if (optind < argc) {
443 if (NULL == device_name) {
444 device_name = argv[optind];
445 ++optind;
446 }
447 if (optind < argc) {
448 for (; optind < argc; ++optind)
449 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
450 usage();
451 return SG_LIB_SYNTAX_ERROR;
452 }
453 }
454 #ifdef DEBUG
455 pr2serr("In DEBUG mode, ");
456 if (verbose_given && version_given) {
457 pr2serr("but override: '-vV' given, zero verbose and continue\n");
458 verbose_given = false;
459 version_given = false;
460 verbose = 0;
461 } else if (! verbose_given) {
462 pr2serr("set '-vv'\n");
463 verbose = 2;
464 } else
465 pr2serr("keep verbose=%d\n", verbose);
466 #else
467 if (verbose_given && version_given)
468 pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
469 #endif
470 if (version_given) {
471 pr2serr("version: %s\n", version_str);
472 return 0;
473 }
474 if (0 == device_name) {
475 pr2serr("no DEVICE name detected\n\n");
476 usage();
477 return SG_LIB_SYNTAX_ERROR;
478 }
479 if (raw) {
480 if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
481 perror("sg_set_binary_mode");
482 return SG_LIB_FILE_ERROR;
483 }
484 }
485
486 if ((sg_fd = open(device_name, O_RDWR)) < 0) {
487 err = errno;
488 snprintf(ebuff, EBUFF_SZ,
489 "sg_sat_phy_event: error opening file: %s", device_name);
490 perror(ebuff);
491 return sg_convert_errno(err);
492 }
493 ret = do_read_log_ext(sg_fd, SATA_PHY_EVENT_LPAGE,
494 0 /* page_in_log */,
495 (reset ? 1 : 0) /* feature */,
496 1 /* blk_count */, inBuff,
497 READ_LOG_EXT_RESPONSE_LEN, cdb_len, ck_cond,
498 extend, hex, raw, verbose);
499
500 if ((0 == ret) && (0 == hex) && (! raw)) {
501 printf("SATA phy event counters:\n");
502 for (k = 4; k < 512; k += (len + 2)) {
503 id = (inBuff[k + 1] << 8) + inBuff[k];
504 if (0 == id)
505 break;
506 len = ((id >> 12) & 0x7) * 2;
507 vendor = !!(id & 0x8000);
508 id = id & 0xfff;
509 ull = 0;
510 for (j = len - 1; j >= 0; --j) {
511 if (j < (len - 1))
512 ull <<= 8;
513 ull |= inBuff[k + 2 + j];
514 }
515 cp = NULL;
516 if ((0 == vendor) && (! ignore))
517 cp = find_phy_desc(id);
518 if (cp)
519 printf(" %s: %" PRIu64 "\n", cp, ull);
520 else
521 printf(" id=0x%x, vendor=%d, data_len=%d, "
522 "val=%" PRIu64 "\n", id, vendor, len, ull);
523 }
524 }
525
526 res = close(sg_fd);
527 if (res < 0) {
528 err = errno;
529 pr2serr("close error: %s\n", safe_strerror(err));
530 if (0 == ret)
531 ret = sg_convert_errno(err);
532 }
533 return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
534 }
535