blob: bf7e15d43fa822c689b607fe73ace08748ed65b6 [file] [log] [blame]
/*
* 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");
}