logpage.c revision 328712
1252277Sjimharris/*- 2252277Sjimharris * Copyright (c) 2013 EMC Corp. 3252277Sjimharris * All rights reserved. 4252277Sjimharris * 5252277Sjimharris * Copyright (C) 2012-2013 Intel Corporation 6252277Sjimharris * All rights reserved. 7252277Sjimharris * 8252277Sjimharris * Redistribution and use in source and binary forms, with or without 9252277Sjimharris * modification, are permitted provided that the following conditions 10252277Sjimharris * are met: 11252277Sjimharris * 1. Redistributions of source code must retain the above copyright 12252277Sjimharris * notice, this list of conditions and the following disclaimer. 13252277Sjimharris * 2. Redistributions in binary form must reproduce the above copyright 14252277Sjimharris * notice, this list of conditions and the following disclaimer in the 15252277Sjimharris * documentation and/or other materials provided with the distribution. 16252277Sjimharris * 17252277Sjimharris * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18252277Sjimharris * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19252277Sjimharris * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20252277Sjimharris * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21252277Sjimharris * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22252277Sjimharris * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23252277Sjimharris * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24252277Sjimharris * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25252277Sjimharris * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26252277Sjimharris * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27252277Sjimharris * SUCH DAMAGE. 28252277Sjimharris */ 29252277Sjimharris 30252277Sjimharris#include <sys/cdefs.h> 31252277Sjimharris__FBSDID("$FreeBSD: stable/11/sbin/nvmecontrol/logpage.c 328712 2018-02-01 19:33:51Z mav $"); 32252277Sjimharris 33252277Sjimharris#include <sys/param.h> 34252277Sjimharris#include <sys/ioccom.h> 35252277Sjimharris 36252277Sjimharris#include <ctype.h> 37253109Sjimharris#include <err.h> 38252277Sjimharris#include <fcntl.h> 39252277Sjimharris#include <stdbool.h> 40252277Sjimharris#include <stddef.h> 41252277Sjimharris#include <stdio.h> 42252277Sjimharris#include <stdlib.h> 43252277Sjimharris#include <string.h> 44252277Sjimharris#include <unistd.h> 45328668Smav#include <sys/endian.h> 46252277Sjimharris 47328668Smav#if _BYTE_ORDER != _LITTLE_ENDIAN 48328668Smav#error "Code only works on little endian machines" 49328668Smav#endif 50328668Smav 51252277Sjimharris#include "nvmecontrol.h" 52252277Sjimharris 53252277Sjimharris#define DEFAULT_SIZE (4096) 54252277Sjimharris#define MAX_FW_SLOTS (7) 55252277Sjimharris 56252277Sjimharristypedef void (*print_fn_t)(void *buf, uint32_t size); 57252277Sjimharris 58328674Smavstruct kv_name 59328674Smav{ 60328674Smav uint32_t key; 61328674Smav const char *name; 62328674Smav}; 63328674Smav 64328674Smavstatic const char * 65328674Smavkv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key) 66328674Smav{ 67328674Smav static char bad[32]; 68328674Smav size_t i; 69328674Smav 70328674Smav for (i = 0; i < kv_count; i++, kv++) 71328674Smav if (kv->key == key) 72328674Smav return kv->name; 73328674Smav snprintf(bad, sizeof(bad), "Attribute %#x", key); 74328674Smav return bad; 75328674Smav} 76328674Smav 77328668Smav/* 78328709Smav * 128-bit integer augments to standard values. On i386 this 79328709Smav * doesn't exist, so we use 64-bit values. The 128-bit counters 80328709Smav * are crazy anyway, since for this purpose, you'd need a 81328709Smav * billion IOPs for billions of seconds to overflow them. 82328709Smav * So, on 32-bit i386, you'll get truncated values. 83328668Smav */ 84328668Smav#define UINT128_DIG 39 85328709Smav#ifdef __i386__ 86328709Smavtypedef uint64_t uint128_t; 87328709Smav#else 88328668Smavtypedef __uint128_t uint128_t; 89328709Smav#endif 90328668Smav 91328668Smavstatic inline uint128_t 92328668Smavto128(void *p) 93328668Smav{ 94328668Smav return *(uint128_t *)p; 95328668Smav} 96328668Smav 97328668Smavstatic char * 98328668Smavuint128_to_str(uint128_t u, char *buf, size_t buflen) 99328668Smav{ 100328668Smav char *end = buf + buflen - 1; 101328668Smav 102328668Smav *end-- = '\0'; 103328668Smav if (u == 0) 104328668Smav *end-- = '0'; 105328668Smav while (u && end >= buf) { 106328668Smav *end-- = u % 10 + '0'; 107328668Smav u /= 10; 108328668Smav } 109328668Smav end++; 110328668Smav if (u != 0) 111328668Smav return NULL; 112328668Smav 113328668Smav return end; 114328668Smav} 115328668Smav 116328711Smav/* "Missing" from endian.h */ 117328708Smavstatic __inline uint64_t 118328708Smavle48dec(const void *pp) 119328708Smav{ 120328708Smav uint8_t const *p = (uint8_t const *)pp; 121328708Smav 122328708Smav return (((uint64_t)le16dec(p + 4) << 32) | le32dec(p)); 123328708Smav} 124328708Smav 125252277Sjimharrisstatic void * 126253109Sjimharrisget_log_buffer(uint32_t size) 127252277Sjimharris{ 128252277Sjimharris void *buf; 129252277Sjimharris 130253109Sjimharris if ((buf = malloc(size)) == NULL) 131253109Sjimharris errx(1, "unable to malloc %u bytes", size); 132253109Sjimharris 133252277Sjimharris memset(buf, 0, size); 134252277Sjimharris return (buf); 135252277Sjimharris} 136252277Sjimharris 137252277Sjimharrisvoid 138328673Smavread_logpage(int fd, uint8_t log_page, int nsid, void *payload, 139252277Sjimharris uint32_t payload_size) 140252277Sjimharris{ 141252277Sjimharris struct nvme_pt_command pt; 142252277Sjimharris 143252277Sjimharris memset(&pt, 0, sizeof(pt)); 144252277Sjimharris pt.cmd.opc = NVME_OPC_GET_LOG_PAGE; 145252277Sjimharris pt.cmd.nsid = nsid; 146252277Sjimharris pt.cmd.cdw10 = ((payload_size/sizeof(uint32_t)) - 1) << 16; 147252277Sjimharris pt.cmd.cdw10 |= log_page; 148252277Sjimharris pt.buf = payload; 149252277Sjimharris pt.len = payload_size; 150252277Sjimharris pt.is_read = 1; 151252277Sjimharris 152253109Sjimharris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 153253109Sjimharris err(1, "get log page request failed"); 154252277Sjimharris 155253109Sjimharris if (nvme_completion_is_error(&pt.cpl)) 156253109Sjimharris errx(1, "get log page request returned error"); 157252277Sjimharris} 158252277Sjimharris 159252277Sjimharrisstatic void 160252277Sjimharrisprint_log_error(void *buf, uint32_t size) 161252277Sjimharris{ 162252277Sjimharris int i, nentries; 163252277Sjimharris struct nvme_error_information_entry *entry = buf; 164252277Sjimharris struct nvme_status *status; 165252277Sjimharris 166252277Sjimharris printf("Error Information Log\n"); 167252277Sjimharris printf("=====================\n"); 168252277Sjimharris 169252277Sjimharris if (entry->error_count == 0) { 170252277Sjimharris printf("No error entries found\n"); 171252277Sjimharris return; 172252277Sjimharris } 173252277Sjimharris 174252277Sjimharris nentries = size/sizeof(struct nvme_error_information_entry); 175252277Sjimharris for (i = 0; i < nentries; i++, entry++) { 176252277Sjimharris if (entry->error_count == 0) 177252277Sjimharris break; 178252277Sjimharris 179252277Sjimharris status = &entry->status; 180252277Sjimharris printf("Entry %02d\n", i + 1); 181252277Sjimharris printf("=========\n"); 182252277Sjimharris printf(" Error count: %ju\n", entry->error_count); 183252277Sjimharris printf(" Submission queue ID: %u\n", entry->sqid); 184252277Sjimharris printf(" Command ID: %u\n", entry->cid); 185252277Sjimharris /* TODO: Export nvme_status_string structures from kernel? */ 186252277Sjimharris printf(" Status:\n"); 187252277Sjimharris printf(" Phase tag: %d\n", status->p); 188252277Sjimharris printf(" Status code: %d\n", status->sc); 189252277Sjimharris printf(" Status code type: %d\n", status->sct); 190252277Sjimharris printf(" More: %d\n", status->m); 191252277Sjimharris printf(" DNR: %d\n", status->dnr); 192252277Sjimharris printf(" Error location: %u\n", entry->error_location); 193252277Sjimharris printf(" LBA: %ju\n", entry->lba); 194252277Sjimharris printf(" Namespace ID: %u\n", entry->nsid); 195252277Sjimharris printf(" Vendor specific info: %u\n", entry->vendor_specific); 196252277Sjimharris } 197252277Sjimharris} 198252277Sjimharris 199252277Sjimharrisstatic void 200328669Smavprint_temp(uint16_t t) 201328669Smav{ 202328669Smav printf("%u K, %2.2f C, %3.2f F\n", t, (float)t - 273.15, (float)t * 9 / 5 - 459.67); 203328669Smav} 204328669Smav 205328669Smav 206328669Smavstatic void 207252277Sjimharrisprint_log_health(void *buf, uint32_t size __unused) 208252277Sjimharris{ 209252277Sjimharris struct nvme_health_information_page *health = buf; 210328668Smav char cbuf[UINT128_DIG + 1]; 211328669Smav int i; 212252277Sjimharris 213252277Sjimharris printf("SMART/Health Information Log\n"); 214252277Sjimharris printf("============================\n"); 215252277Sjimharris 216252277Sjimharris printf("Critical Warning State: 0x%02x\n", 217252277Sjimharris health->critical_warning.raw); 218252277Sjimharris printf(" Available spare: %d\n", 219252277Sjimharris health->critical_warning.bits.available_spare); 220252277Sjimharris printf(" Temperature: %d\n", 221252277Sjimharris health->critical_warning.bits.temperature); 222252277Sjimharris printf(" Device reliability: %d\n", 223252277Sjimharris health->critical_warning.bits.device_reliability); 224252277Sjimharris printf(" Read only: %d\n", 225252277Sjimharris health->critical_warning.bits.read_only); 226252277Sjimharris printf(" Volatile memory backup: %d\n", 227252277Sjimharris health->critical_warning.bits.volatile_memory_backup); 228328669Smav printf("Temperature: "); 229328669Smav print_temp(health->temperature); 230252277Sjimharris printf("Available spare: %u\n", 231252277Sjimharris health->available_spare); 232252277Sjimharris printf("Available spare threshold: %u\n", 233252277Sjimharris health->available_spare_threshold); 234252277Sjimharris printf("Percentage used: %u\n", 235252277Sjimharris health->percentage_used); 236252277Sjimharris 237328669Smav printf("Data units (512,000 byte) read: %s\n", 238328668Smav uint128_to_str(to128(health->data_units_read), cbuf, sizeof(cbuf))); 239328669Smav printf("Data units written: %s\n", 240328668Smav uint128_to_str(to128(health->data_units_written), cbuf, sizeof(cbuf))); 241328668Smav printf("Host read commands: %s\n", 242328668Smav uint128_to_str(to128(health->host_read_commands), cbuf, sizeof(cbuf))); 243328668Smav printf("Host write commands: %s\n", 244328668Smav uint128_to_str(to128(health->host_write_commands), cbuf, sizeof(cbuf))); 245328668Smav printf("Controller busy time (minutes): %s\n", 246328668Smav uint128_to_str(to128(health->controller_busy_time), cbuf, sizeof(cbuf))); 247328668Smav printf("Power cycles: %s\n", 248328668Smav uint128_to_str(to128(health->power_cycles), cbuf, sizeof(cbuf))); 249328668Smav printf("Power on hours: %s\n", 250328668Smav uint128_to_str(to128(health->power_on_hours), cbuf, sizeof(cbuf))); 251328668Smav printf("Unsafe shutdowns: %s\n", 252328668Smav uint128_to_str(to128(health->unsafe_shutdowns), cbuf, sizeof(cbuf))); 253328668Smav printf("Media errors: %s\n", 254328668Smav uint128_to_str(to128(health->media_errors), cbuf, sizeof(cbuf))); 255328668Smav printf("No. error info log entries: %s\n", 256328668Smav uint128_to_str(to128(health->num_error_info_log_entries), cbuf, sizeof(cbuf))); 257328669Smav 258328669Smav printf("Warning Temp Composite Time: %d\n", health->warning_temp_time); 259328669Smav printf("Error Temp Composite Time: %d\n", health->error_temp_time); 260328669Smav for (i = 0; i < 7; i++) { 261328669Smav if (health->temp_sensor[i] == 0) 262328669Smav continue; 263328669Smav printf("Temperature Sensor %d: ", i + 1); 264328669Smav print_temp(health->temp_sensor[i]); 265328669Smav } 266252277Sjimharris} 267252277Sjimharris 268252277Sjimharrisstatic void 269252277Sjimharrisprint_log_firmware(void *buf, uint32_t size __unused) 270252277Sjimharris{ 271252277Sjimharris int i; 272252277Sjimharris const char *status; 273252277Sjimharris struct nvme_firmware_page *fw = buf; 274252277Sjimharris 275252277Sjimharris printf("Firmware Slot Log\n"); 276252277Sjimharris printf("=================\n"); 277252277Sjimharris 278252277Sjimharris for (i = 0; i < MAX_FW_SLOTS; i++) { 279252277Sjimharris printf("Slot %d: ", i + 1); 280252277Sjimharris if (fw->afi.slot == i + 1) 281252277Sjimharris status = " Active"; 282252277Sjimharris else 283252277Sjimharris status = "Inactive"; 284252277Sjimharris 285252277Sjimharris if (fw->revision[i] == 0LLU) 286252277Sjimharris printf("Empty\n"); 287252277Sjimharris else 288252277Sjimharris if (isprint(*(char *)&fw->revision[i])) 289252277Sjimharris printf("[%s] %.8s\n", status, 290252277Sjimharris (char *)&fw->revision[i]); 291252277Sjimharris else 292252277Sjimharris printf("[%s] %016jx\n", status, 293252277Sjimharris fw->revision[i]); 294252277Sjimharris } 295252277Sjimharris} 296252277Sjimharris 297328708Smav/* 298328708Smav * Intel specific log pages from 299328708Smav * http://www.intel.com/content/dam/www/public/us/en/documents/product-specifications/ssd-dc-p3700-spec.pdf 300328708Smav * 301328708Smav * Though the version as of this date has a typo for the size of log page 0xca, 302328708Smav * offset 147: it is only 1 byte, not 6. 303328708Smav */ 304328673Smavstatic void 305328673Smavprint_intel_temp_stats(void *buf, uint32_t size __unused) 306328673Smav{ 307328673Smav struct intel_log_temp_stats *temp = buf; 308328673Smav 309328673Smav printf("Intel Temperature Log\n"); 310328673Smav printf("=====================\n"); 311328673Smav 312328673Smav printf("Current: "); 313328673Smav print_temp(temp->current); 314328673Smav printf("Overtemp Last Flags %#jx\n", (uintmax_t)temp->overtemp_flag_last); 315328673Smav printf("Overtemp Lifetime Flags %#jx\n", (uintmax_t)temp->overtemp_flag_life); 316328673Smav printf("Max Temperature "); 317328673Smav print_temp(temp->max_temp); 318328673Smav printf("Min Temperature "); 319328673Smav print_temp(temp->min_temp); 320328673Smav printf("Max Operating Temperature "); 321328673Smav print_temp(temp->max_oper_temp); 322328673Smav printf("Min Operating Temperature "); 323328673Smav print_temp(temp->min_oper_temp); 324328673Smav printf("Estimated Temperature Offset: %ju C/K\n", (uintmax_t)temp->est_offset); 325328673Smav} 326328673Smav 327328712Smav/* 328328712Smav * Format from Table 22, section 5.7 IO Command Latency Statistics. 329328712Smav * Read and write stats pages have identical encoding. 330328712Smav */ 331328708Smavstatic void 332328712Smavprint_intel_read_write_lat_log(void *buf, uint32_t size __unused) 333328712Smav{ 334328712Smav const char *walker = buf; 335328712Smav int i; 336328712Smav 337328712Smav printf("Major: %d\n", le16dec(walker + 0)); 338328712Smav printf("Minor: %d\n", le16dec(walker + 2)); 339328712Smav for (i = 0; i < 32; i++) 340328712Smav printf("%4dus-%4dus: %ju\n", i * 32, (i + 1) * 32, (uintmax_t)le32dec(walker + 4 + i * 4)); 341328712Smav for (i = 1; i < 32; i++) 342328712Smav printf("%4dms-%4dms: %ju\n", i, i + 1, (uintmax_t)le32dec(walker + 132 + i * 4)); 343328712Smav for (i = 1; i < 32; i++) 344328712Smav printf("%4dms-%4dms: %ju\n", i * 32, (i + 1) * 32, (uintmax_t)le32dec(walker + 256 + i * 4)); 345328712Smav} 346328712Smav 347328712Smavstatic void 348328712Smavprint_intel_read_lat_log(void *buf, uint32_t size) 349328712Smav{ 350328712Smav 351328712Smav printf("Intel Read Latency Log\n"); 352328712Smav printf("======================\n"); 353328712Smav print_intel_read_write_lat_log(buf, size); 354328712Smav} 355328712Smav 356328712Smavstatic void 357328712Smavprint_intel_write_lat_log(void *buf, uint32_t size) 358328712Smav{ 359328712Smav 360328712Smav printf("Intel Write Latency Log\n"); 361328712Smav printf("=======================\n"); 362328712Smav print_intel_read_write_lat_log(buf, size); 363328712Smav} 364328712Smav 365328712Smav/* 366328712Smav * Table 19. 5.4 SMART Attributes 367328712Smav */ 368328712Smavstatic void 369328708Smavprint_intel_add_smart(void *buf, uint32_t size __unused) 370328708Smav{ 371328708Smav uint8_t *walker = buf; 372328708Smav uint8_t *end = walker + 150; 373328708Smav const char *name; 374328708Smav uint64_t raw; 375328708Smav uint8_t normalized; 376328708Smav 377328708Smav static struct kv_name kv[] = 378328708Smav { 379328708Smav { 0xab, "Program Fail Count" }, 380328708Smav { 0xac, "Erase Fail Count" }, 381328708Smav { 0xad, "Wear Leveling Count" }, 382328708Smav { 0xb8, "End to End Error Count" }, 383328708Smav { 0xc7, "CRC Error Count" }, 384328708Smav { 0xe2, "Timed: Media Wear" }, 385328708Smav { 0xe3, "Timed: Host Read %" }, 386328708Smav { 0xe4, "Timed: Elapsed Time" }, 387328708Smav { 0xea, "Thermal Throttle Status" }, 388328708Smav { 0xf0, "Retry Buffer Overflows" }, 389328708Smav { 0xf3, "PLL Lock Loss Count" }, 390328708Smav { 0xf4, "NAND Bytes Written" }, 391328708Smav { 0xf5, "Host Bytes Written" }, 392328708Smav }; 393328708Smav 394328708Smav printf("Additional SMART Data Log\n"); 395328708Smav printf("=========================\n"); 396328708Smav /* 397328708Smav * walker[0] = Key 398328708Smav * walker[1,2] = reserved 399328708Smav * walker[3] = Normalized Value 400328708Smav * walker[4] = reserved 401328708Smav * walker[5..10] = Little Endian Raw value 402328708Smav * (or other represenations) 403328708Smav * walker[11] = reserved 404328708Smav */ 405328708Smav while (walker < end) { 406328708Smav name = kv_lookup(kv, nitems(kv), *walker); 407328708Smav normalized = walker[3]; 408328708Smav raw = le48dec(walker + 5); 409328708Smav switch (*walker){ 410328708Smav case 0: 411328708Smav break; 412328708Smav case 0xad: 413328708Smav printf("%-32s: %3d min: %u max: %u ave: %u\n", name, normalized, 414328708Smav le16dec(walker + 5), le16dec(walker + 7), le16dec(walker + 9)); 415328708Smav break; 416328708Smav case 0xe2: 417328708Smav printf("%-32s: %3d %.3f%%\n", name, normalized, raw / 1024.0); 418328708Smav break; 419328708Smav case 0xea: 420328708Smav printf("%-32s: %3d %d%% %d times\n", name, normalized, walker[5], le32dec(walker+6)); 421328708Smav break; 422328708Smav default: 423328708Smav printf("%-32s: %3d %ju\n", name, normalized, (uintmax_t)raw); 424328708Smav break; 425328708Smav } 426328708Smav walker += 12; 427328708Smav } 428328708Smav} 429328708Smav 430328673Smav/* 431328674Smav * HGST's 0xc1 page. This is a grab bag of additional data. Please see 432328674Smav * https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf 433328674Smav * https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf 434328674Smav * Appendix A for details 435328674Smav */ 436328674Smav 437328674Smavtypedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 438328674Smav 439328674Smavstruct subpage_print 440328674Smav{ 441328674Smav uint16_t key; 442328674Smav subprint_fn_t fn; 443328674Smav}; 444328674Smav 445328674Smavstatic void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 446328674Smavstatic void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 447328674Smavstatic void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 448328674Smavstatic void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 449328674Smavstatic void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 450328674Smavstatic void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 451328674Smavstatic void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 452328674Smavstatic void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 453328674Smavstatic void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 454328674Smavstatic void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size); 455328674Smav 456328674Smavstatic struct subpage_print hgst_subpage[] = { 457328674Smav { 0x02, print_hgst_info_write_errors }, 458328674Smav { 0x03, print_hgst_info_read_errors }, 459328674Smav { 0x05, print_hgst_info_verify_errors }, 460328674Smav { 0x10, print_hgst_info_self_test }, 461328674Smav { 0x15, print_hgst_info_background_scan }, 462328674Smav { 0x30, print_hgst_info_erase_errors }, 463328674Smav { 0x31, print_hgst_info_erase_counts }, 464328674Smav { 0x32, print_hgst_info_temp_history }, 465328674Smav { 0x37, print_hgst_info_ssd_perf }, 466328674Smav { 0x38, print_hgst_info_firmware_load }, 467328674Smav}; 468328674Smav 469328674Smav/* Print a subpage that is basically just key value pairs */ 470328674Smavstatic void 471328674Smavprint_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size, 472328674Smav const struct kv_name *kv, size_t kv_count) 473328674Smav{ 474328674Smav uint8_t *wsp, *esp; 475328674Smav uint16_t ptype; 476328674Smav uint8_t plen; 477328674Smav uint64_t param; 478328674Smav int i; 479328674Smav 480328674Smav wsp = buf; 481328674Smav esp = wsp + size; 482328674Smav while (wsp < esp) { 483328674Smav ptype = le16dec(wsp); 484328674Smav wsp += 2; 485328674Smav wsp++; /* Flags, just ignore */ 486328674Smav plen = *wsp++; 487328674Smav param = 0; 488328674Smav for (i = 0; i < plen; i++) 489328674Smav param |= (uint64_t)*wsp++ << (i * 8); 490328674Smav printf(" %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), (uintmax_t)param); 491328674Smav } 492328674Smav} 493328674Smav 494328674Smavstatic void 495328674Smavprint_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 496328674Smav{ 497328674Smav static struct kv_name kv[] = 498328674Smav { 499328674Smav { 0x0000, "Corrected Without Delay" }, 500328674Smav { 0x0001, "Corrected Maybe Delayed" }, 501328674Smav { 0x0002, "Re-Writes" }, 502328674Smav { 0x0003, "Errors Corrected" }, 503328674Smav { 0x0004, "Correct Algorithm Used" }, 504328674Smav { 0x0005, "Bytes Processed" }, 505328674Smav { 0x0006, "Uncorrected Errors" }, 506328674Smav { 0x8000, "Flash Write Commands" }, 507328674Smav { 0x8001, "HGST Special" }, 508328674Smav }; 509328674Smav 510328674Smav printf("Write Errors Subpage:\n"); 511328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 512328674Smav} 513328674Smav 514328674Smavstatic void 515328674Smavprint_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 516328674Smav{ 517328674Smav static struct kv_name kv[] = 518328674Smav { 519328674Smav { 0x0000, "Corrected Without Delay" }, 520328674Smav { 0x0001, "Corrected Maybe Delayed" }, 521328674Smav { 0x0002, "Re-Reads" }, 522328674Smav { 0x0003, "Errors Corrected" }, 523328674Smav { 0x0004, "Correct Algorithm Used" }, 524328674Smav { 0x0005, "Bytes Processed" }, 525328674Smav { 0x0006, "Uncorrected Errors" }, 526328674Smav { 0x8000, "Flash Read Commands" }, 527328674Smav { 0x8001, "XOR Recovered" }, 528328674Smav { 0x8002, "Total Corrected Bits" }, 529328674Smav }; 530328674Smav 531328674Smav printf("Read Errors Subpage:\n"); 532328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 533328674Smav} 534328674Smav 535328674Smavstatic void 536328674Smavprint_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 537328674Smav{ 538328674Smav static struct kv_name kv[] = 539328674Smav { 540328674Smav { 0x0000, "Corrected Without Delay" }, 541328674Smav { 0x0001, "Corrected Maybe Delayed" }, 542328674Smav { 0x0002, "Re-Reads" }, 543328674Smav { 0x0003, "Errors Corrected" }, 544328674Smav { 0x0004, "Correct Algorithm Used" }, 545328674Smav { 0x0005, "Bytes Processed" }, 546328674Smav { 0x0006, "Uncorrected Errors" }, 547328674Smav { 0x8000, "Commands Processed" }, 548328674Smav }; 549328674Smav 550328674Smav printf("Verify Errors Subpage:\n"); 551328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 552328674Smav} 553328674Smav 554328674Smavstatic void 555328674Smavprint_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 556328674Smav{ 557328674Smav size_t i; 558328674Smav uint8_t *walker = buf; 559328674Smav uint16_t code, hrs; 560328674Smav uint32_t lba; 561328674Smav 562328674Smav printf("Self Test Subpage:\n"); 563328674Smav for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */ 564328674Smav code = le16dec(walker); 565328674Smav walker += 2; 566328674Smav walker++; /* Ignore fixed flags */ 567328674Smav if (*walker == 0) /* Last entry is zero length */ 568328674Smav break; 569328674Smav if (*walker++ != 0x10) { 570328674Smav printf("Bad length for self test report\n"); 571328674Smav return; 572328674Smav } 573328674Smav printf(" %-30s: %d\n", "Recent Test", code); 574328674Smav printf(" %-28s: %#x\n", "Self-Test Results", *walker & 0xf); 575328674Smav printf(" %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7); 576328674Smav walker++; 577328674Smav printf(" %-28s: %#x\n", "Self-Test Number", *walker++); 578328674Smav hrs = le16dec(walker); 579328674Smav walker += 2; 580328674Smav lba = le32dec(walker); 581328674Smav walker += 4; 582328674Smav printf(" %-28s: %u\n", "Total Power On Hrs", hrs); 583328674Smav printf(" %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, (uintmax_t)lba); 584328674Smav printf(" %-28s: %#x\n", "Sense Key", *walker++ & 0xf); 585328674Smav printf(" %-28s: %#x\n", "Additional Sense Code", *walker++); 586328674Smav printf(" %-28s: %#x\n", "Additional Sense Qualifier", *walker++); 587328674Smav printf(" %-28s: %#x\n", "Vendor Specific Detail", *walker++); 588328674Smav } 589328674Smav} 590328674Smav 591328674Smavstatic void 592328674Smavprint_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 593328674Smav{ 594328674Smav uint8_t *walker = buf; 595328674Smav uint8_t status; 596328674Smav uint16_t code, nscan, progress; 597328674Smav uint32_t pom, nand; 598328674Smav 599328674Smav printf("Background Media Scan Subpage:\n"); 600328674Smav /* Decode the header */ 601328674Smav code = le16dec(walker); 602328674Smav walker += 2; 603328674Smav walker++; /* Ignore fixed flags */ 604328674Smav if (*walker++ != 0x10) { 605328674Smav printf("Bad length for background scan header\n"); 606328674Smav return; 607328674Smav } 608328674Smav if (code != 0) { 609328674Smav printf("Expceted code 0, found code %#x\n", code); 610328674Smav return; 611328674Smav } 612328674Smav pom = le32dec(walker); 613328674Smav walker += 4; 614328674Smav walker++; /* Reserved */ 615328674Smav status = *walker++; 616328674Smav nscan = le16dec(walker); 617328674Smav walker += 2; 618328674Smav progress = le16dec(walker); 619328674Smav walker += 2; 620328674Smav walker += 6; /* Reserved */ 621328674Smav printf(" %-30s: %d\n", "Power On Minutes", pom); 622328674Smav printf(" %-30s: %x (%s)\n", "BMS Status", status, 623328674Smav status == 0 ? "idle" : (status == 1 ? "active" : (status == 8 ? "suspended" : "unknown"))); 624328674Smav printf(" %-30s: %d\n", "Number of BMS", nscan); 625328674Smav printf(" %-30s: %d\n", "Progress Current BMS", progress); 626328674Smav /* Report retirements */ 627328674Smav if (walker - (uint8_t *)buf != 20) { 628328674Smav printf("Coding error, offset not 20\n"); 629328674Smav return; 630328674Smav } 631328674Smav size -= 20; 632328674Smav printf(" %-30s: %d\n", "BMS retirements", size / 0x18); 633328674Smav while (size > 0) { 634328674Smav code = le16dec(walker); 635328674Smav walker += 2; 636328674Smav walker++; 637328674Smav if (*walker++ != 0x14) { 638328674Smav printf("Bad length parameter\n"); 639328674Smav return; 640328674Smav } 641328674Smav pom = le32dec(walker); 642328674Smav walker += 4; 643328674Smav /* 644328674Smav * Spec sheet says the following are hard coded, if true, just 645328674Smav * print the NAND retirement. 646328674Smav */ 647328674Smav if (walker[0] == 0x41 && 648328674Smav walker[1] == 0x0b && 649328674Smav walker[2] == 0x01 && 650328674Smav walker[3] == 0x00 && 651328674Smav walker[4] == 0x00 && 652328674Smav walker[5] == 0x00 && 653328674Smav walker[6] == 0x00 && 654328674Smav walker[7] == 0x00) { 655328674Smav walker += 8; 656328674Smav walker += 4; /* Skip reserved */ 657328674Smav nand = le32dec(walker); 658328674Smav walker += 4; 659328674Smav printf(" %-30s: %d\n", "Retirement number", code); 660328674Smav printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand); 661328674Smav } else { 662328674Smav printf("Parameter %#x entry corrupt\n", code); 663328674Smav walker += 16; 664328674Smav } 665328674Smav } 666328674Smav} 667328674Smav 668328674Smavstatic void 669328674Smavprint_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) 670328674Smav{ 671328674Smav static struct kv_name kv[] = 672328674Smav { 673328674Smav { 0x0000, "Corrected Without Delay" }, 674328674Smav { 0x0001, "Corrected Maybe Delayed" }, 675328674Smav { 0x0002, "Re-Erase" }, 676328674Smav { 0x0003, "Errors Corrected" }, 677328674Smav { 0x0004, "Correct Algorithm Used" }, 678328674Smav { 0x0005, "Bytes Processed" }, 679328674Smav { 0x0006, "Uncorrected Errors" }, 680328674Smav { 0x8000, "Flash Erase Commands" }, 681328674Smav { 0x8001, "Mfg Defect Count" }, 682328674Smav { 0x8002, "Grown Defect Count" }, 683328674Smav { 0x8003, "Erase Count -- User" }, 684328674Smav { 0x8004, "Erase Count -- System" }, 685328674Smav }; 686328674Smav 687328674Smav printf("Erase Errors Subpage:\n"); 688328674Smav print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); 689328674Smav} 690328674Smav 691328674Smavstatic void 692328674Smavprint_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) 693328674Smav{ 694328674Smav /* My drive doesn't export this -- so not coding up */ 695328674Smav printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size); 696328674Smav} 697328674Smav 698328674Smavstatic void 699328674Smavprint_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 700328674Smav{ 701328674Smav uint8_t *walker = buf; 702328674Smav uint32_t min; 703328674Smav 704328674Smav printf("Temperature History:\n"); 705328674Smav printf(" %-30s: %d C\n", "Current Temperature", *walker++); 706328674Smav printf(" %-30s: %d C\n", "Reference Temperature", *walker++); 707328674Smav printf(" %-30s: %d C\n", "Maximum Temperature", *walker++); 708328674Smav printf(" %-30s: %d C\n", "Minimum Temperature", *walker++); 709328674Smav min = le32dec(walker); 710328674Smav walker += 4; 711328674Smav printf(" %-30s: %d:%02d:00\n", "Max Temperture Time", min / 60, min % 60); 712328674Smav min = le32dec(walker); 713328674Smav walker += 4; 714328674Smav printf(" %-30s: %d:%02d:00\n", "Over Temperture Duration", min / 60, min % 60); 715328674Smav min = le32dec(walker); 716328674Smav walker += 4; 717328674Smav printf(" %-30s: %d:%02d:00\n", "Min Temperture Time", min / 60, min % 60); 718328674Smav} 719328674Smav 720328674Smavstatic void 721328674Smavprint_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused) 722328674Smav{ 723328674Smav uint8_t *walker = buf; 724328674Smav uint64_t val; 725328674Smav 726328674Smav printf("SSD Performance Subpage Type %d:\n", res); 727328674Smav val = le64dec(walker); 728328674Smav walker += 8; 729328674Smav printf(" %-30s: %ju\n", "Host Read Commands", val); 730328674Smav val = le64dec(walker); 731328674Smav walker += 8; 732328674Smav printf(" %-30s: %ju\n", "Host Read Blocks", val); 733328674Smav val = le64dec(walker); 734328674Smav walker += 8; 735328674Smav printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val); 736328674Smav val = le64dec(walker); 737328674Smav walker += 8; 738328674Smav printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val); 739328674Smav val = le64dec(walker); 740328674Smav walker += 8; 741328674Smav printf(" %-30s: %ju\n", "Host Read Commands Stalled", val); 742328674Smav val = le64dec(walker); 743328674Smav walker += 8; 744328674Smav printf(" %-30s: %ju\n", "Host Write Commands", val); 745328674Smav val = le64dec(walker); 746328674Smav walker += 8; 747328674Smav printf(" %-30s: %ju\n", "Host Write Blocks", val); 748328674Smav val = le64dec(walker); 749328674Smav walker += 8; 750328674Smav printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val); 751328674Smav val = le64dec(walker); 752328674Smav walker += 8; 753328674Smav printf(" %-30s: %ju\n", "Host Write Odd End Commands", val); 754328674Smav val = le64dec(walker); 755328674Smav walker += 8; 756328674Smav printf(" %-30s: %ju\n", "Host Write Commands Stalled", val); 757328674Smav val = le64dec(walker); 758328674Smav walker += 8; 759328674Smav printf(" %-30s: %ju\n", "NAND Read Commands", val); 760328674Smav val = le64dec(walker); 761328674Smav walker += 8; 762328674Smav printf(" %-30s: %ju\n", "NAND Read Blocks", val); 763328674Smav val = le64dec(walker); 764328674Smav walker += 8; 765328674Smav printf(" %-30s: %ju\n", "NAND Write Commands", val); 766328674Smav val = le64dec(walker); 767328674Smav walker += 8; 768328674Smav printf(" %-30s: %ju\n", "NAND Write Blocks", val); 769328674Smav val = le64dec(walker); 770328674Smav walker += 8; 771328674Smav printf(" %-30s: %ju\n", "NAND Read Before Writes", val); 772328674Smav} 773328674Smav 774328674Smavstatic void 775328674Smavprint_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) 776328674Smav{ 777328674Smav uint8_t *walker = buf; 778328674Smav 779328674Smav printf("Firmware Load Subpage:\n"); 780328674Smav printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker)); 781328674Smav} 782328674Smav 783328674Smavstatic void 784328674Smavkv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp) 785328674Smav{ 786328674Smav size_t i; 787328674Smav 788328674Smav for (i = 0; i < nsp; i++, sp++) { 789328674Smav if (sp->key == subtype) { 790328674Smav sp->fn(buf, subtype, res, size); 791328674Smav return; 792328674Smav } 793328674Smav } 794328674Smav printf("No handler for page type %x\n", subtype); 795328674Smav} 796328674Smav 797328674Smavstatic void 798328674Smavprint_hgst_info_log(void *buf, uint32_t size __unused) 799328674Smav{ 800328674Smav uint8_t *walker, *end, *subpage; 801328674Smav int pages; 802328674Smav uint16_t len; 803328674Smav uint8_t subtype, res; 804328674Smav 805328674Smav printf("HGST Extra Info Log\n"); 806328674Smav printf("===================\n"); 807328674Smav 808328674Smav walker = buf; 809328674Smav pages = *walker++; 810328674Smav walker++; 811328674Smav len = le16dec(walker); 812328674Smav walker += 2; 813328674Smav end = walker + len; /* Length is exclusive of this header */ 814328674Smav 815328674Smav while (walker < end) { 816328674Smav subpage = walker + 4; 817328674Smav subtype = *walker++ & 0x3f; /* subtype */ 818328674Smav res = *walker++; /* Reserved */ 819328674Smav len = le16dec(walker); 820328674Smav walker += len + 2; /* Length, not incl header */ 821328674Smav if (walker > end) { 822328674Smav printf("Ooops! Off the end of the list\n"); 823328674Smav break; 824328674Smav } 825328674Smav kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage)); 826328674Smav } 827328674Smav} 828328674Smav 829328674Smav/* 830328673Smav * Table of log page printer / sizing. 831328673Smav * 832328673Smav * This includes Intel specific pages that are widely implemented. Not 833328673Smav * sure how best to switch between different vendors. 834328673Smav */ 835252302Sglebiusstatic struct logpage_function { 836252277Sjimharris uint8_t log_page; 837328710Smav const char *vendor; 838328672Smav print_fn_t print_fn; 839328672Smav size_t size; 840252277Sjimharris} logfuncs[] = { 841328710Smav {NVME_LOG_ERROR, NULL, print_log_error, 842328672Smav 0}, 843328710Smav {NVME_LOG_HEALTH_INFORMATION, NULL, print_log_health, 844328672Smav sizeof(struct nvme_health_information_page)}, 845328710Smav {NVME_LOG_FIRMWARE_SLOT, NULL, print_log_firmware, 846328672Smav sizeof(struct nvme_firmware_page)}, 847328712Smav {HGST_INFO_LOG, "hgst", print_hgst_info_log, 848328712Smav DEFAULT_SIZE}, 849328710Smav {INTEL_LOG_TEMP_STATS, "intel", print_intel_temp_stats, 850328673Smav sizeof(struct intel_log_temp_stats)}, 851328712Smav {INTEL_LOG_READ_LAT_LOG, "intel", print_intel_read_lat_log, 852328712Smav DEFAULT_SIZE}, 853328712Smav {INTEL_LOG_WRITE_LAT_LOG, "intel", print_intel_write_lat_log, 854328712Smav DEFAULT_SIZE}, 855328710Smav {INTEL_LOG_ADD_SMART, "intel", print_intel_add_smart, 856328708Smav DEFAULT_SIZE}, 857328710Smav {0, NULL, NULL, 0}, 858252277Sjimharris}; 859252277Sjimharris 860252277Sjimharrisstatic void 861252277Sjimharrislogpage_usage(void) 862252277Sjimharris{ 863252277Sjimharris fprintf(stderr, "usage:\n"); 864252277Sjimharris fprintf(stderr, LOGPAGE_USAGE); 865253109Sjimharris exit(1); 866252277Sjimharris} 867252277Sjimharris 868252277Sjimharrisvoid 869252277Sjimharrislogpage(int argc, char *argv[]) 870252277Sjimharris{ 871253114Sjimharris int fd, nsid; 872252277Sjimharris int log_page = 0, pageflag = false; 873253114Sjimharris int hexflag = false, ns_specified; 874253114Sjimharris char ch, *p; 875253114Sjimharris char cname[64]; 876253109Sjimharris uint32_t size; 877252277Sjimharris void *buf; 878328710Smav const char *vendor = NULL; 879252277Sjimharris struct logpage_function *f; 880252277Sjimharris struct nvme_controller_data cdata; 881252277Sjimharris print_fn_t print_fn; 882252277Sjimharris 883328710Smav while ((ch = getopt(argc, argv, "p:xv:")) != -1) { 884252277Sjimharris switch (ch) { 885252277Sjimharris case 'p': 886252277Sjimharris /* TODO: Add human-readable ASCII page IDs */ 887252277Sjimharris log_page = strtol(optarg, &p, 0); 888252277Sjimharris if (p != NULL && *p != '\0') { 889252277Sjimharris fprintf(stderr, 890252277Sjimharris "\"%s\" not valid log page id.\n", 891252277Sjimharris optarg); 892252277Sjimharris logpage_usage(); 893252277Sjimharris } 894252277Sjimharris pageflag = true; 895252277Sjimharris break; 896252277Sjimharris case 'x': 897252277Sjimharris hexflag = true; 898252277Sjimharris break; 899328710Smav case 'v': 900328710Smav vendor = optarg; 901328710Smav break; 902252277Sjimharris } 903252277Sjimharris } 904252277Sjimharris 905252277Sjimharris if (!pageflag) { 906252277Sjimharris printf("Missing page_id (-p).\n"); 907252277Sjimharris logpage_usage(); 908252277Sjimharris } 909252277Sjimharris 910252277Sjimharris /* Check that a controller and/or namespace was specified. */ 911252277Sjimharris if (optind >= argc) 912252277Sjimharris logpage_usage(); 913252277Sjimharris 914253114Sjimharris if (strstr(argv[optind], NVME_NS_PREFIX) != NULL) { 915253114Sjimharris ns_specified = true; 916253114Sjimharris parse_ns_str(argv[optind], cname, &nsid); 917253114Sjimharris open_dev(cname, &fd, 1, 1); 918253114Sjimharris } else { 919253114Sjimharris ns_specified = false; 920253114Sjimharris nsid = NVME_GLOBAL_NAMESPACE_TAG; 921253114Sjimharris open_dev(argv[optind], &fd, 1, 1); 922253114Sjimharris } 923253114Sjimharris 924285796Sjimharris read_controller_data(fd, &cdata); 925285796Sjimharris 926252277Sjimharris /* 927252277Sjimharris * The log page attribtues indicate whether or not the controller 928252277Sjimharris * supports the SMART/Health information log page on a per 929252277Sjimharris * namespace basis. 930252277Sjimharris */ 931253114Sjimharris if (ns_specified) { 932253114Sjimharris if (log_page != NVME_LOG_HEALTH_INFORMATION) 933253114Sjimharris errx(1, "log page %d valid only at controller level", 934253114Sjimharris log_page); 935253114Sjimharris if (cdata.lpa.ns_smart == 0) 936253114Sjimharris errx(1, 937253114Sjimharris "controller does not support per namespace " 938253114Sjimharris "smart/health information"); 939253114Sjimharris } 940252277Sjimharris 941252277Sjimharris print_fn = print_hex; 942328672Smav size = DEFAULT_SIZE; 943252277Sjimharris if (!hexflag) { 944252277Sjimharris /* 945328710Smav * See if there is a pretty print function for the specified log 946328710Smav * page. If one isn't found, we just revert to the default 947328710Smav * (print_hex). If there was a vendor specified bt the user, and 948328710Smav * the page is vendor specific, don't match the print function 949328710Smav * unless the vendors match. 950252277Sjimharris */ 951328710Smav for (f = logfuncs; f->log_page > 0; f++) { 952328710Smav if (f->vendor != NULL && vendor != NULL && 953328710Smav strcmp(f->vendor, vendor) != 0) 954328710Smav continue; 955328710Smav if (log_page != f->log_page) 956328710Smav continue; 957328710Smav print_fn = f->print_fn; 958328710Smav size = f->size; 959328710Smav break; 960252277Sjimharris } 961252277Sjimharris } 962252277Sjimharris 963328672Smav if (log_page == NVME_LOG_ERROR) { 964252277Sjimharris size = sizeof(struct nvme_error_information_entry); 965252277Sjimharris size *= (cdata.elpe + 1); 966252277Sjimharris } 967252277Sjimharris 968328672Smav /* Read the log page */ 969252277Sjimharris buf = get_log_buffer(size); 970252277Sjimharris read_logpage(fd, log_page, nsid, buf, size); 971252277Sjimharris print_fn(buf, size); 972252277Sjimharris 973252277Sjimharris close(fd); 974253109Sjimharris exit(0); 975252277Sjimharris} 976