xref: /aosp_15_r20/external/sg3_utils/src/sg_sat_phy_event.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
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