SAS理解

一、常见的SCSI命令
命令用途
Test unit ready查询设备是否已经准备好进行传输
Inquiry请求设备基本信息
Request sense请求之前命令的错误信息
Read capacity请求存储容量信息
Read从设备读取数据
Write向设备写入数据
二、内核中SCSI命令字,错误码,状态定义

文件在:/usr/include/scsi/scsi.h

/*
 *      SCSI opcodes
 */

#define TEST_UNIT_READY       0x00
#define REZERO_UNIT           0x01
#define REQUEST_SENSE         0x03
#define FORMAT_UNIT           0x04
#define READ_BLOCK_LIMITS     0x05
#define REASSIGN_BLOCKS       0x07
#define READ_6                0x08
#define WRITE_6               0x0a
#define SEEK_6                0x0b
#define READ_REVERSE          0x0f
#define WRITE_FILEMARKS       0x10
#define SPACE                 0x11
#define INQUIRY               0x12
#define RECOVER_BUFFERED_DATA 0x14
#define MODE_SELECT           0x15
#define RESERVE               0x16
#define RELEASE               0x17
#define COPY                  0x18
#define ERASE                 0x19
#define MODE_SENSE            0x1a
#define START_STOP            0x1b
#define RECEIVE_DIAGNOSTIC    0x1c
#define SEND_DIAGNOSTIC       0x1d
#define ALLOW_MEDIUM_REMOVAL  0x1e

/*
 *  Status codes
 */
 #define GOOD                 0x00
#define CHECK_CONDITION      0x01
#define CONDITION_GOOD       0x02
#define BUSY                 0x04
#define INTERMEDIATE_GOOD    0x08
#define INTERMEDIATE_C_GOOD  0x0a
#define RESERVATION_CONFLICT 0x0c
#define COMMAND_TERMINATED   0x11
#define QUEUE_FULL           0x14

#define STATUS_MASK          0x3e

/*
 *  SENSE KEYS
 */

#define NO_SENSE            0x00
#define RECOVERED_ERROR     0x01
#define NOT_READY           0x02
#define MEDIUM_ERROR        0x03
#define HARDWARE_ERROR      0x04
#define ILLEGAL_REQUEST     0x05
#define UNIT_ATTENTION      0x06
#define DATA_PROTECT        0x07
#define BLANK_CHECK         0x08
#define COPY_ABORTED        0x0a
#define ABORTED_COMMAND     0x0b
#define VOLUME_OVERFLOW     0x0d
#define MISCOMPARE          0x0e


/*
 *  DEVICE TYPES
 */

#define TYPE_DISK           0x00
#define TYPE_TAPE           0x01
#define TYPE_PROCESSOR      0x03    /* HP scanners use this */
#define TYPE_WORM           0x04    /* Treated as ROM by our system */
#define TYPE_ROM            0x05
#define TYPE_SCANNER        0x06
#define TYPE_MOD            0x07    /* Magneto-optical disk -
                     * - treated as TYPE_DISK */
#define TYPE_MEDIUM_CHANGER 0x08
#define TYPE_ENCLOSURE      0x0d    /* Enclosure Services Device */
#define TYPE_NO_LUN         0x7f
三、SAS,SCSI分层以及关系简图

在这里插入图片描述
在这里插入图片描述

四、设备理解
  • 块设备:一个磁盘或者一个分区可以是一个块设备,只要是一个块设备可以对应一个 /dev/下面的文件来表示。
  • gendisk:内核使用,描述一个磁盘。
  • struct request_queue(IO请求队列);每一个磁盘(没有分区)都会有一个IO请求队列。内核使用。
  • struct scsi_device:表示一个SCSI设备:内核使用。每个SCSI设备可以用一个4元组表示(host-channel-target-lun).
  • host:struct Scsi_Host表示,比如一张HBA卡。主机侧每一个SCSI Adapter称为一个SCSI HOST。
  • channel:一个SCSI Adapter可以有多个CHANNEL(又称为BUS)
  • target:struct scsi_target,连接到一个SCSI CHANNEL上的对端主体1,即执行主机侧下发命令的主体
  • lun:一个TARGET上可以划分多个LUN,形成多个SCSI设备
  • SCSI DISK:一个SCSI DISK对象会对应一个磁盘对象(struct gendisk),至少一个块设备对象(struct block_device)以及一个IO队列(struct request_queue)。这样一个SCSI对象就和一个block对象联系起来了。就可以通过block对象反问SCSI对象了。
五、块设备和SCSI设备的关系如下图。

在这里插入图片描述

  • upper层作用:这一层把SCSI设备(比如一个磁盘,一个磁带或者一个光驱)通过Linux内核模块SD驱动注册成为一个块设备。此时一个SCSI设备[host:channel:target:lun]形式映射为Linux块设备对象。
  • lower层:主要是指的这层使用的是什么驱动,比如scsi_transport_sas或者scsi_transport_fc.如下图我的机器上是mpt3sas和scsi_transport_sas,这说明本地硬盘是SAS硬盘。scsi_transport_xxx完成各种传输协议到SCSI层的适配,把底层的传输设备和SCSI层关联起来。这一层驱动直接和底层的设备(磁盘,SAN存储)打交道,接受上层下风的SCS命令,然后使用本机的设备对应的协议比如SAS通过驱动mpt3sas进行SAS协议封装,然后发送到盘上进行处理。在驱动初始化的时候,会建立SCSI_HOST对象并注册SCSI命令入队函数,IO经SCSI层转化为SCSI命令后,会调用mpt3sas注册的queuecommand函数进入mpt3sas驱动,mpt3sas驱动再把SCSI命令封装为SAS协议发送出去。
  • 在这里插入图片描述
六、操作SCSI设备块设备工具简单介绍

sg3_utils
sg3_utils 是Linux下用来直接使用 SCSI命令集访问设备.只要支持scsi命令集就可以使用,如FC/USB Storage/ATAPI/SAS/SATA/iscsi等设备, 也可以访问SATA兼容设备, 如:
sg_inq: 查询/dev/sdc的信息
sgdisk: 硬盘分区查看,设置等.
sg_dd/sg_pdd: 顺序读写硬盘
sg_read/sg_write/sg_read_buffer/sg_write_buffer: 读写硬盘
sginfo: 查看硬盘信息
sg_format: 格式化硬盘(低级格式化)
sg_log: 查看硬盘的log信息
sg_luns: 查看target上有多少个lun
sg_map: 查看硬盘映射情况, 查看硬盘所在的bus, chan, id,lun,type.
sgpio: 控制盘位的LED灯
sg_ses: scsi enclosure service控制, ses是对接机箱状态监控,设置等服务
sg_scan: 扫描系统中的硬盘
sg_raw: 可以直接发送scsi命令, 如sg_raw -r 1k /dev/sg0 12 00 00 00 60 00 是inquiry命令

smp_utils
smp_utils是linux下来访问sas的smp协议的辅助工具, 是SAS系统管理的重要工具.

udevadm
udev的控制管理命令 udevadm info -a -p /block/sda 可以查看sda设备相关信息

要查询某个SCSI设备(sg)对应的是那个块设备(sd)可以使用命令sg_map -i查询。
在这里插入图片描述

七、参考:

https://yalungdotblog.files.wordpress.com/2012/02/scsie5ad90e7b3bbe7bb9fe79fa5e8af86e680bbe7bb93.pdf

八、SCSI命令编程实例

一个用户态程序通过ioctl发送SCSI命令访问SCSI设备(可以是sd设备也可以是一个sg设备)。

$cat scsi_sample.c
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <scsi/scsi_ioctl.h>

#define SENSE_LEN 255
#define BLOCK_LEN 32
#define SCSI_TIMEOUT 20000

unsigned char sense_buffer[SENSE_LEN];
unsigned char data_buffer[BLOCK_LEN*256];

void show_hdr_outputs(struct sg_io_hdr * hdr) {
	printf("status:%d\n", hdr->status);
	printf("masked_status:%d\n", hdr->masked_status);
	printf("msg_status:%d\n", hdr->msg_status);
	printf("sb_len_wr:%d\n", hdr->sb_len_wr);
	printf("host_status:%d\n", hdr->host_status);
	printf("driver_status:%d\n", hdr->driver_status);
	printf("resid:%d\n", hdr->resid);
	printf("duration:%d\n", hdr->duration);
	printf("info:%d\n", hdr->info);
}

void show_sense_buffer(struct sg_io_hdr * hdr) {
	unsigned char * buffer = hdr->sbp;
	int i;
	for (i=0; i<hdr->mx_sb_len; ++i) {
		putchar(buffer[i]);
	}
}

void show_vendor(struct sg_io_hdr * hdr) {
	unsigned char * buffer = hdr->dxferp;
	int i;
	printf("the device type: %x \n",buffer[0]&(1<<5 -1));
	//设备类型:0 磁盘  1磁带 12存储阵列(raid) 13拓展柜(expander)
	printf("vendor id:");
	for (i=8; i<16; ++i) {
		putchar(buffer[i]);
	}
	putchar('\n');
}
void show_product(struct sg_io_hdr * hdr) {
	unsigned char * buffer = hdr->dxferp;
	int i;
	printf("product id:");
	for (i=16; i<32; ++i) {
		putchar(buffer[i]);
	}
	putchar('\n');
}

void show_product_rev(struct sg_io_hdr * hdr) {
	unsigned char * buffer = hdr->dxferp;
	int i;
	printf("product ver:");
	for (i=32; i<36; ++i) {
		putchar(buffer[i]);
	}
	putchar('\n');
}
/***************************************************************************
 * name: execute_Inquiry
 * parameter:
 * fd: file descripter
 * page_code: cdb page code
 * evpd: cdb evpd
 * p_hdr: poiter to sg_io_hdr struct
 * function: make Inquiry cdb and execute it.
 * **************************************************************************/
int execute_Inquiry(int fd, int page_code, int evpd, struct sg_io_hdr * p_hdr) {
	unsigned char cdb[6];
	/* set the cdb format */
	cdb[0] = 0x12; /*This is for Inquery*/
	cdb[1] = evpd & 1;
	cdb[2] = page_code & 0xff;
	cdb[3] = 0;
	cdb[4] = 0xff;
	cdb[5] = 0; /*For control filed, just use 0*/

	p_hdr->dxfer_direction = SG_DXFER_FROM_DEV;
	p_hdr->cmdp = cdb;
	p_hdr->cmd_len = 6;

	int ret = ioctl(fd, SG_IO, p_hdr);
	if (ret<0) {
		printf("Sending SCSI Command failed.\n");
		close(fd);
		exit(1);
	}
	return p_hdr->status;
}

void test_execute_Inquiry(char * path, int evpd, int page_code) {
    int status = 0;
    //初始化p_hdr
	struct sg_io_hdr * p_hdr = (struct sg_io_hdr *)malloc(sizeof(struct sg_io_hdr));
	memset(p_hdr, 0, sizeof(struct sg_io_hdr));
	p_hdr->interface_id = 'S'; /* this is the only choice we have! */
	p_hdr->flags = SG_FLAG_LUN_INHIBIT; /* this would put the LUN to 2nd byte of cdb*/

    //设置传输数据buffer
	p_hdr->dxferp = data_buffer;  //INQUIRY 数据格式 SPC-3
	p_hdr->dxfer_len = BLOCK_LEN*256;

    //设置sense buffer
    p_hdr->sbp = sense_buffer;
	p_hdr->mx_sb_len = SENSE_LEN;

    printf("the device name:%s \n",path);
	int fd = open(path, O_RDWR);
	if (fd>0) {
		status = execute_Inquiry(fd, page_code, evpd, p_hdr);
		if (status!=0) {
			show_sense_buffer(p_hdr);
		} else{
			show_vendor(p_hdr);
			show_product(p_hdr);
			show_product_rev(p_hdr);
			show_sense_buffer(p_hdr);
		}
	} else {
		printf("failed to open sg file %s\n", path);
	}
	close(fd);
	free(p_hdr);
}

int main(int argc, char * argv[]) {
	test_execute_Inquiry(argv[1], 0, 0);
	return EXIT_SUCCESS;
}

//编译
//gcc scsi_sample.c -o test -g
//./test /dev/nvme9n1

$sudo ./test /dev/nvme9n1
the device name:/dev/nvme9n1
the device type: 0
vendor id:NVMe
product id:INTEL SSDPE2MX02
product ver:01H0

这里借助一个结构体struct sg_io_hdr对象p_hdr通过ioctl从用户态发送命令到内核态
int ret = ioctl(fd, SG_IO, p_hdr);

typedef struct sg_io_hdr
{
  int interface_id;           /* [i] 'S' for SCSI generic (required) */
  int dxfer_direction;        /* [i] data transfer direction  */
  unsigned char cmd_len;      /* [i] SCSI command length ( <= 16 bytes) */
  unsigned char mx_sb_len;    /* [i] max length to write to sbp */
  unsigned short int iovec_count; /* [i] 0 implies no scatter gather */
  unsigned int dxfer_len;     /* [i] byte count of data transfer */
  void * dxferp;              /* [i], [*io] points to data transfer memory
				 or scatter gather list */
  unsigned char * cmdp;       /* [i], [*i] points to command to perform */
  unsigned char * sbp;        /* [i], [*o] points to sense_buffer memory */
  unsigned int timeout;       /* [i] MAX_UINT->no timeout (unit: millisec) */
  unsigned int flags;         /* [i] 0 -> default, see SG_FLAG... */
  int pack_id;                /* [i->o] unused internally (normally) */
  void * usr_ptr;             /* [i->o] unused internally */
  unsigned char status;       /* [o] scsi status */
  unsigned char masked_status;/* [o] shifted, masked scsi status */
  unsigned char msg_status;   /* [o] messaging level data (optional) */
  unsigned char sb_len_wr;    /* [o] byte count actually written to sbp */
  unsigned short int host_status; /* [o] errors from host adapter */
  unsigned short int driver_status;/* [o] errors from software driver */
  int resid;                  /* [o] dxfer_len - actual_transferred */
  unsigned int duration;      /* [o] time taken by cmd (unit: millisec) */
  unsigned int info;          /* [o] auxiliary information */
} sg_io_hdr_t;

/*
interface_id:一般应该设置为 S。
dxfer_direction:用于确定数据传输的方向;常常使用以下值之一:
SG_DXFER_NONE:不需要传输数据。比如 SCSI Test Unit Ready 命令。
SG_DXFER_TO_DEV:将数据传输到设备。使用 SCSI WRITE 命令。
SG_DXFER_FROM_DEV:从设备输出数据。使用 SCSI READ 命令。
SG_DXFER_TO_FROM_DEV:双向传输数据。
SG_DXFER_UNKNOWN:数据的传输方向未知。
cmd_len:指向 SCSI 命令的 cmdp 的字节长度。
mx_sb_len:当 sense_buffer 为输出时,可以写回到 sbp 的最大大小。
dxfer_len:数据传输的用户内存的长度。
dxferp:指向数据传输时长度至少为 dxfer_len 字节的用户内存的指针。
cmdp:指向将要执行的 SCSI 命令的指针。
sbp:缓冲检测指针。
timeout:用于使特定命令超时。
status:由 SCSI 标准定义的 SCSI 状态字节*/

一个SCSI inquiry命令:
在这里插入图片描述
如果 EVPD 参数位(用于启用关键产品数据)为 0 并且 Page Code 参数字节为 0,那么目标将返回标准 inquiry 数据。如果 EVPD 参数为 1,那么目标将返回对应 page code 字段的特定于供应商的数据。
SCSI Inquiry Command(Page Code 和 EVPD 字段皆设置为 0)的标准响应很复杂。根据标准,供应商 ID 从第 8 字节扩展到第 15 字节,产品 ID 从第 16 字节扩展到第 31 字节,产品版本从第 32 字节扩展到第 35 字节。必须获取这些信息,以检查命令是否成功执行。

九、sense key含义:

在这里插入图片描述

十、SAS网络中目前有三种应用协议:SSP、STP、SMP。

SSP是一套用于Initiator和Target之间传输SCSI指令的传输保证协议,与FC中的FCP层充当相同的角色,在SCSI3规范中,已经将SCSI上层指令与底层传输系统相分离,SSP在此就作为SCSI指令的传输系统,SSP会保障SCSI指令和数据以及对指令的相应成功地被传送和接收。一共有五种SSP帧类型,包含COMMAND、TASK、XFER_RDY、DATA、RESPONSE类型,TASK和RESPONSE类型指令用于SSP设备控制指令步骤,COMMAND和RESPONSE类型用于SSP发起SCSI指令,DATA类型用于SCSI Read,XFER_RDY用于写指令。
  STP是一套用于在Initiator和Target之间传送SATA指令的传输保障协议。由于SATA协议与SCSI协议是完全两套上层协议,不仅指令描述方式和结构不同,而且在底层传输中的控制上也不同,STP就是将SATA协议的底层传输逻辑拿来过来,并将其承载于SAS底层进行传输。但是记住,SCSI和SATA,他们都是在不同程度的Tunnel+Map的方式被承载于SAS网络上传递。
  SMP是一种对SAS网络内所有Expander进行管理的协议,包括Expander拓扑发现和路由协议。由Initiator端建立连接后向Target端发起查询请求,Target只能响应Initiator的查询而不能主动向Initiator发送消息,SMP中的Target对应的整个Expander本身。Expander接收到针对这个Target的消息,就会自己做处理而不是转发到其他某个PHY。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值