blob: 4d6381befd9cd674a67e85f1f377dbc31f5d5375 [file] [log] [blame]
/*
* Copyright (c) 2020, Mediatek Inc. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma GCC diagnostic ignored "-Wall"
#pragma GCC diagnostic ignored "-Wextra"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdint.h>
#include <endian.h>
#include <fcntl.h>
#include <unistd.h>
#include "ioctl.h"
#include "ufs.h"
#include "scsi_bsg_util.h"
#include "ufs_rpmb.h"
#define UPIU_HEADER_DWORD(byte3, byte2, byte1, byte0)\
htobe32((byte3 << 24) | (byte2 << 16) |\
(byte1 << 8) | (byte0))
#define SEC_SPEC_OFFSET 2
#define SEC_TRANS_LEN_OFFSET 6
/* description of the sense key values */
static const char *const snstext[] = {
"No Sense", /* 0: There is no sense information */
"Recovered Error", /* 1: The last command completed successfully
but used error correction */
"Not Ready", /* 2: The addressed target is not ready */
"Medium Error", /* 3: Data error detected on the medium */
"Hardware Error", /* 4: Controller or device failure */
"Illegal Request", /* 5: Error in request */
"Unit Attention", /* 6: Removable medium was changed, or
the target has been reset, or ... */
"Data Protect", /* 7: Access to the data is blocked */
"Blank Check", /* 8: Reached unexpected written or unwritten
region of the medium */
"Vendor Specific",
"Copy Aborted", /* A: COPY or COMPARE was aborted */
"Aborted Command", /* B: The target aborted the command */
"Equal", /* C: A SEARCH DATA command found data equal */
"Volume Overflow", /* D: Medium full with still data to be written */
"Miscompare", /* E: Source data and data on the medium
do not agree */
};
static int send_scsi_cmd(int fd, const __u8 *cdb, void *buf,
__u8 cmd_len, __u32 byte_cnt, int dir, __u8 sg_type);
/* Get sense key string or NULL if not available */
static const char *sense_key_string(__u8 key)
{
if (key <= 0xE)
return snstext[key];
return NULL;
}
static inline void put_unaligned_be24(__u32 val, void *p)
{
((__u8 *)p)[0] = (val >> 16) & 0xff;
((__u8 *)p)[1] = (val >> 8) & 0xff;
((__u8 *)p)[2] = val & 0xff;
}
static int write_file_with_counter(const char *pattern, const void *buffer,
int length)
{
#ifdef DEBUG
static int counter = 1;
char filename[1024] = {0};
sprintf(filename, pattern, counter++);
return write_file(filename, buffer, length);
#else
return 0;
#endif
}
int write_buffer(int fd, __u8 *buf, __u8 mode, __u8 buf_id, __u32 buf_offset,
int byte_count, __u8 sg_type)
{
int ret;
unsigned char write_buf_cmd [WRITE_BUF_CMDLEN] = {
WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if (fd < 0 || buf == NULL || byte_count <= 0) {
perror("scsi write cmd: wrong parameters");
return -EINVAL;
}
write_buf_cmd[1] = mode;
write_buf_cmd[2] = buf_id;
put_unaligned_be24((uint32_t)buf_offset, write_buf_cmd + 3);
put_unaligned_be24(byte_count, write_buf_cmd + 6);
WRITE_LOG("Start : %s mode %d , buf_id %d", __func__, mode, buf_id);
ret = send_scsi_cmd(fd, write_buf_cmd, buf,
WRITE_BUF_CMDLEN, byte_count,
SG_DXFER_TO_DEV, sg_type);
if (ret < 0) {
print_error("SG_IO WRITE BUFFER data error ret %d", ret);
}
return ret;
}
int read_buffer(int fd, __u8 *buf, __u8 mode, __u8 buf_id,
__u32 buf_offset, int byte_count, __u8 sg_type)
{
int ret;
unsigned char read_buf_cmd[READ_BUF_CMDLEN] = {READ_BUFFER_CMD,
0, 0, 0, 0, 0, 0, 0, 0, 0};
if (fd < 0 || buf == NULL || byte_count <= 0) {
print_error("scsi read cmd: wrong parameters");
return -EINVAL;
}
read_buf_cmd[1] = mode;
read_buf_cmd[2] = buf_id;
put_unaligned_be24((__u32)buf_offset, read_buf_cmd + 3);
put_unaligned_be24((__u32)byte_count, read_buf_cmd + 6);
WRITE_LOG("Start : %s\n", __func__);
ret = send_scsi_cmd(fd, read_buf_cmd, buf,
READ_BUF_CMDLEN, byte_count,
SG_DXFER_FROM_DEV, sg_type);
if (ret < 0) {
print_error("SG_IO READ BUFFER data error ret %d", ret);
}
return ret;
}
int scsi_security_in(int fd, struct rpmb_frame *frame, int cnt, __u8 region,
__u8 sg_type)
{
int ret;
__u32 trans_len = cnt * sizeof(struct rpmb_frame);
__u16 sec_spec = (region << 8) | SEC_SPECIFIC_UFS_RPMB;
unsigned char sec_in_cmd[SEC_PROTOCOL_CMD_SIZE] = {
SECURITY_PROTOCOL_IN, SEC_PROTOCOL_UFS,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
WRITE_LOG("Start : %s\n", __func__);
if (fd < 0 || frame == NULL || cnt <= 0) {
print_error("scsi sec_in cmd: wrong parameters");
return ERROR;
}
*(__u16 *)(sec_in_cmd + SEC_SPEC_OFFSET) = htobe16(sec_spec);
*(__u32 *)(sec_in_cmd + SEC_TRANS_LEN_OFFSET) = htobe32(trans_len);
ret = send_scsi_cmd(fd, sec_in_cmd, frame, SEC_PROTOCOL_CMD_SIZE,
trans_len, SG_DXFER_FROM_DEV, sg_type);
return ret;
}
int scsi_security_out(int fd, struct rpmb_frame *frame_in,
unsigned int cnt, __u8 region, __u8 sg_type)
{
int ret;
__u32 trans_len = cnt * sizeof(struct rpmb_frame);
__u16 sec_spec = (region << 8) | SEC_SPECIFIC_UFS_RPMB;
unsigned char sec_out_cmd[SEC_PROTOCOL_CMD_SIZE] = {
SECURITY_PROTOCOL_OUT, SEC_PROTOCOL_UFS,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if (fd < 0 || frame_in == NULL || cnt <= 0) {
print_error("scsi sec_out cmd: wrong parameters");
return ERROR;
}
*(__u16 *)(sec_out_cmd + SEC_SPEC_OFFSET) = htobe16(sec_spec);
*(__u32 *)(sec_out_cmd + SEC_TRANS_LEN_OFFSET) = htobe32(trans_len);
ret = send_scsi_cmd(fd, sec_out_cmd, frame_in,
SEC_PROTOCOL_CMD_SIZE, trans_len,
SG_DXFER_TO_DEV, sg_type);
return ret;
}
void prepare_upiu(struct ufs_bsg_request *bsg_req,
__u8 query_req_func, __u16 data_len,
__u8 opcode, __u8 idn, __u8 index, __u8 sel)
{
bsg_req->msgcode = UPIU_TRANSACTION_QUERY_REQ;
/* Fill UPIU header */
bsg_req->upiu_req.header.dword_0 =
UPIU_HEADER_DWORD(UPIU_TRANSACTION_QUERY_REQ, 0, 0, 0);
bsg_req->upiu_req.header.dword_1 =
UPIU_HEADER_DWORD(0, query_req_func, 0, 0);
bsg_req->upiu_req.header.dword_2 =
UPIU_HEADER_DWORD(0, 0, data_len >> 8, (__u8)data_len);
/* Fill Transaction Specific Fields */
bsg_req->upiu_req.qr.opcode = opcode;
bsg_req->upiu_req.qr.idn = idn;
bsg_req->upiu_req.qr.index = index;
bsg_req->upiu_req.qr.selector = sel;
bsg_req->upiu_req.qr.length = htobe16(data_len);
}
/**
* send_scsi_cmd - Utility function for SCSI command sending
* @fd: bsg driver file descriptor
* @cdb: pointer to SCSI cmd cdb buffer
* @buf: pointer to the SCSI cmd data buffer
* @cmd_len: SCSI command length
* @byte_cnt: SCSI data length
* @dir: The cmd direction
*
**/
static int send_scsi_cmd(int fd, const __u8 *cdb, void *buf, __u8 cmd_len,
__u32 byte_cnt, int dir, __u8 sg_type)
{
int ret;
void *sg_struct;
struct sg_io_v4 io_hdr_v4 = { 0 };
struct sg_io_hdr io_hdr_v3 = { 0 };
__u8 sense_buffer[SENSE_BUFF_LEN] = { 0 };
if ((byte_cnt && buf == NULL) || cdb == NULL) {
print_error("send_scsi_cmd: wrong parameters");
return -EINVAL;
}
if (sg_type == SG4_TYPE) {
io_hdr_v4.guard = 'Q';
io_hdr_v4.protocol = BSG_PROTOCOL_SCSI;
io_hdr_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
io_hdr_v4.response = (__u64)sense_buffer;
io_hdr_v4.max_response_len = SENSE_BUFF_LEN;
io_hdr_v4.request_len = cmd_len;
if (dir == SG_DXFER_FROM_DEV) {
io_hdr_v4.din_xfer_len = (__u32)byte_cnt;
io_hdr_v4.din_xferp = (__u64)buf;
} else {
io_hdr_v4.dout_xfer_len = (__u32)byte_cnt;
io_hdr_v4.dout_xferp = (__u64)buf;
}
io_hdr_v4.request = (__u64)cdb;
sg_struct = &io_hdr_v4;
}
else {
io_hdr_v3.interface_id = 'S';
io_hdr_v3.cmd_len = cmd_len;
io_hdr_v3.mx_sb_len = SENSE_BUFF_LEN;
io_hdr_v3.dxfer_direction = dir;
io_hdr_v3.dxfer_len = byte_cnt;
io_hdr_v3.dxferp = buf;
/* pointer to command buf (rbufCmdBlk) */
io_hdr_v3.cmdp = (unsigned char *)cdb;
io_hdr_v3.sbp = sense_buffer;
io_hdr_v3.timeout = DEF_TIMEOUT_MSEC;
sg_struct = &io_hdr_v3;
}
WRITE_LOG("Start : %s cmd = %x len %d sg_type %d\n", __func__, cdb[0],
byte_cnt, sg_type);
write_file_with_counter("scsi_cmd_cdb_%d.bin",
cdb, cmd_len);
while (((ret = ioctl(fd, SG_IO, sg_struct)) < 0) &&
((errno == EINTR) || (errno == EAGAIN)));
if (sg_type == SG4_TYPE) {
if (io_hdr_v4.info != 0) {
print_error("Command fail with status %x , senseKey %s, asc 0x%02x, ascq 0x%02x",
io_hdr_v4.info,
sense_key_string(sense_buffer[2]),
sense_buffer[12],
sense_buffer[13]);
ret = -EINVAL;
}
}
else {
if (io_hdr_v3.status) {
print_error("Command fail with status %x , senseKey %s, asc 0x%02x, ascq 0x%02x",
io_hdr_v3.status,
sense_key_string(sense_buffer[2]),
sense_buffer[12],
sense_buffer[13]);
ret = -EINVAL;
}
}
return ret;
}
/**
* send_bsg_scsi_trs - Utility function for SCSI transport cmd sending
* @fd: ufs bsg driver file descriptor
* @request_buff: pointer to the Query Request
* @reply_buff: pointer to the Query Response
* @req_buf_len: Query Request data length
* @reply_buf_len: Query Response data length
* @data_buf: pointer to the data buffer
*
* The function using ufs bsg infrastructure in linux kernel (/dev/ufs-bsg)
* in order to send Query request command
**/
int send_bsg_scsi_trs(int fd, struct ufs_bsg_request *request_buff,
struct ufs_bsg_reply *reply_buff, __u32 req_buf_len,
__u32 reply_buf_len, __u8 *data_buf)
{
int ret;
struct sg_io_v4 io_hdr_v4 = { 0 };
if (request_buff == NULL || reply_buff == NULL) {
print_error("%s: wrong parameters", __func__);
return -EINVAL;
}
if (req_buf_len != 0 || reply_buf_len != 0) {
if (data_buf == NULL) {
print_error("%s: data_buf is NULL", __func__);
return -EINVAL;
}
}
io_hdr_v4.guard = 'Q';
io_hdr_v4.protocol = BSG_PROTOCOL_SCSI;
io_hdr_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT;
io_hdr_v4.response = (__u64)reply_buff;
io_hdr_v4.max_response_len = BSG_REPLY_SZ;
io_hdr_v4.request_len = BSG_REQUEST_SZ;
io_hdr_v4.request = (__u64)request_buff;
if (req_buf_len > 0) {
/* write descriptor */
io_hdr_v4.dout_xferp = (__u64)(data_buf);
io_hdr_v4.dout_xfer_len = req_buf_len;
} else if (reply_buf_len > 0) {
/* read descriptor */
io_hdr_v4.din_xferp = (__u64)(data_buf);
io_hdr_v4.din_xfer_len = reply_buf_len;
}
WRITE_LOG("%s cmd = %x req_len %d , res_len %d\n", __func__,
request_buff->upiu_req.qr.idn, req_buf_len,
reply_buf_len);
write_file_with_counter("bsg_reg_%d.bin",
&request_buff->upiu_req,
sizeof(struct utp_upiu_req));
while (((ret = ioctl(fd, SG_IO, &io_hdr_v4)) < 0) &&
((errno == EINTR) || (errno == EAGAIN)))
;
if (io_hdr_v4.info != 0) {
print_error("Command fail with status %x ",
io_hdr_v4.info);
ret = -EINVAL;
}
write_file_with_counter("bsg_rsp_%d.bin", reply_buff,
BSG_REPLY_SZ);
WRITE_LOG("%s res_len %d\n", __func__,
reply_buff->reply_payload_rcv_len);
return ret;
}