| /* |
| * Copyright (c) 2020, Mediatek Inc. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| /* |
| * UFS3.0(UFSv3.0 JESD220D spec.) allows to retrieve the error history by using |
| * the READ BUFFER command. |
| */ |
| |
| #include "ufs_err_hist.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <endian.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <errno.h> |
| |
| #include "ufs.h" |
| #include "ufs_cmds.h" |
| #include "options.h" |
| #include "ioctl.h" |
| |
| #define MIN(a, b) (((a) < (b))?(a):(b)) |
| #define MAX(a, b) (((a) > (b))?(a):(b)) |
| |
| /* |
| * The spec actualy says: "and ALLOCATION LENGTH set to at least 2088 |
| * (i.e., large enough to transfer the complete error history directory)." |
| * This is apparently an error because it doesn't adds up to the entries |
| * count and sizes: The error history header is 32bytes, and there can be |
| * up to (0xEF – 0x10 + 1) = 224 entries. Each entry is 8bytes, so the |
| * directory should weight 224*8 + 32 = 1824bytes, and not 2088. |
| */ |
| #define EHS_DIR_ALLOC_LEN 1824 |
| /* According to the spec, BUF ID can be between 0x10 0xEF range */ |
| #define EHS_MIN_BUF_ID 0x10 |
| #define EHS_MAX_BUF_ID 0xEF |
| #define EHS_MAX_ENTRIES (EHS_MAX_BUF_ID - EHS_MIN_BUF_ID + 1) |
| /* 3 bytes for Allocation Length field + 3 bytes for Buffer offset field*/ |
| #define READ_BUF_MAX_AVAIL_LEN (0xFFFFFF + 0xFFFFFF) |
| #define BLOCKS_IN_FAD_BLOCK (MAX_IOCTL_BUF_SIZE / BLOCK_SIZE) |
| |
| struct ehs_directory_entry { |
| u_int8_t buffer_id; |
| u_int8_t reserved[3]; |
| u_int32_t length; |
| }; |
| |
| struct ehs_directory_header { |
| u_int8_t vendor_id[8]; |
| u_int8_t version; |
| u_int8_t reserved1; |
| u_int8_t reserved2[20]; |
| u_int16_t length; |
| }; |
| |
| struct ehs_directory_buffer { |
| struct ehs_directory_header hdr; |
| struct ehs_directory_entry entries[EHS_MAX_ENTRIES]; |
| }; |
| |
| static inline int write_single_fad(const int file, const void *buffer, int sz) |
| { |
| if (write(file, buffer, sz) != sz) |
| return -EIO; |
| else |
| return 0; |
| } |
| |
| static int log_ehs_buffer(int fd, int file, __u8 *buf, __u8 buf_id, |
| __u32 len, __u8 sg_type) |
| { |
| int rc = -EINVAL; |
| __u32 sent = 0; |
| int i = 0; |
| |
| printf("\nPlease wait for error history extraction\n"); |
| while (sent < len) { |
| __u32 ofst = i * MAX_IOCTL_BUF_SIZE; |
| __u32 sz = |
| (len - sent >= MAX_IOCTL_BUF_SIZE) ? MAX_IOCTL_BUF_SIZE : |
| (len - sent); |
| |
| rc = read_buffer(fd, buf, BUFFER_EHS_MODE, buf_id, ofst, sz, |
| sg_type); |
| if (rc) { |
| print_error("read_buffer buff_id 0x%x fad %d", |
| buf_id, i); |
| goto out; |
| } |
| |
| rc = write_single_fad(file, buf, sz); |
| if (rc) { |
| print_error("write buf_id 0x%x fad %d", buf_id, i); |
| goto out; |
| } |
| |
| sent += sz; |
| i++; |
| memset(buf, 0x0, MAX_IOCTL_BUF_SIZE); |
| } |
| |
| rc = 0; |
| out: |
| return rc; |
| } |
| |
| static int log_error_history(int fd, struct ehs_directory_entry *entries, |
| __u8 buffers_cnt, __u8 sg_type) |
| { |
| int file; |
| __u8 *buf = NULL; |
| int rc = -EINVAL; |
| int i; |
| |
| file = open("error_history.dat", O_RDWR | O_CREAT | O_TRUNC | O_SYNC, |
| S_IWUSR | S_IRUSR); |
| if (file == -1) { |
| perror("open"); |
| goto out; |
| } |
| |
| /* IO size is limited by max_sectors_kb which is usually 512k - set a |
| * slightly smaller chunk - 256k. |
| */ |
| buf = calloc(1, MAX_IOCTL_BUF_SIZE); |
| if (!buf) { |
| rc = -ENOMEM; |
| goto out; |
| } |
| |
| for (i = 0; i < buffers_cnt; i++) { |
| struct ehs_directory_entry *entry = entries + i; |
| u_int8_t buf_id = entry->buffer_id; |
| u_int32_t len = be32toh(entry->length); |
| |
| if (buf_id < EHS_MIN_BUF_ID || buf_id > EHS_MAX_BUF_ID) { |
| print_error("illegal buffer id 0x%x entry %d", |
| buf_id, i); |
| goto out; |
| } |
| |
| if (!len || len > READ_BUF_MAX_AVAIL_LEN) { |
| print_error("illegal len 0x%x entry %d", len, i); |
| goto out; |
| } |
| |
| rc = log_ehs_buffer(fd, file, buf, buf_id, len, sg_type); |
| if (rc) { |
| print_error("log_ehs_buffer buffer id 0x%x", buf_id); |
| goto out; |
| } |
| } |
| |
| rc = 0; |
| out: |
| if (buf) |
| free(buf); |
| |
| if (file != -1) |
| close(file); |
| return rc; |
| } |
| |
| static int decode_ehs_directory(struct ehs_directory_buffer *ehs_dir, |
| __u8 *buffers_cnt) |
| { |
| int rc = -EINVAL; |
| u_int16_t length = 0; |
| |
| if (!ehs_dir) |
| goto out; |
| |
| length = be16toh(ehs_dir->hdr.length); |
| if (!length || length % sizeof(struct ehs_directory_entry)) { |
| print_error("Illegal directory length 0x%x", length); |
| goto out; |
| } |
| |
| *buffers_cnt = length / sizeof(struct ehs_directory_entry); |
| if (*buffers_cnt > EHS_MAX_ENTRIES) { |
| print_error("Illegal buffers count %d", *buffers_cnt); |
| goto out; |
| } |
| |
| rc = 0; |
| out: |
| return rc; |
| } |
| |
| int do_err_hist(struct tool_options *opt) |
| { |
| int rc = INVALID; |
| int fd; |
| __u8 *ehs_buf = NULL; |
| struct ehs_directory_buffer *ehs_dir = NULL; |
| __u8 ehs_buffer_cnt = 0; |
| |
| fd = open(opt->path, O_RDWR | O_SYNC); |
| if (fd < 0) { |
| perror("open"); |
| return ERROR; |
| } |
| |
| WRITE_LOG("Start : %s cmd type %d", __func__, opt->idn); |
| ehs_buf = calloc(1, EHS_DIR_ALLOC_LEN); |
| if (!ehs_buf) { |
| rc = -ENOMEM; |
| goto out; |
| } |
| |
| rc = read_buffer(fd, ehs_buf, BUFFER_EHS_MODE, 0, 0, |
| EHS_DIR_ALLOC_LEN, opt->sg_type); |
| if (rc) |
| goto out; |
| |
| rc = write_file("error_history_directory.dat", ehs_buf, |
| EHS_DIR_ALLOC_LEN); |
| if (rc) |
| goto out; |
| |
| printf("error_history_directory.dat is created\n"); |
| |
| ehs_dir = (struct ehs_directory_buffer *)ehs_buf; |
| rc = decode_ehs_directory(ehs_dir, &ehs_buffer_cnt); |
| if (rc) |
| goto out; |
| |
| printf("retrieving error history, this may take a while\n\n"); |
| rc = log_error_history(fd, ehs_dir->entries, ehs_buffer_cnt, |
| opt->sg_type); |
| if (rc) |
| goto out; |
| |
| printf("\nerror_history.dat is created\n"); |
| |
| out: |
| if (ehs_buf) |
| free(ehs_buf); |
| close(fd); |
| |
| return rc; |
| } |
| |
| void err_hist_help(char *tool_name) |
| { |
| printf("\n Error history command usage:\n"); |
| printf("\n\t%s err_hist [-p] <path to device> \n", tool_name); |
| printf("\n\t-g\tsg struct ver - 0: SG_IO_VER4 (default), 1: SG_IO_VER3\n"); |
| printf("\n\t-p\tPath to the bsg device\n"); |
| } |