如何将特定命令发送到 Linux 中的 scsi 设备?

Posted

技术标签:

【中文标题】如何将特定命令发送到 Linux 中的 scsi 设备?【英文标题】:How send a specific command to a scsi device in Linux? 【发布时间】:2017-06-09 13:19:29 【问题描述】:

我实际上正在开发插入 USB 的指纹读取器(FP 读取器)。这个 FP 阅读器也可以插在 stm32f4 板上。如果我理解正确的话,FP 阅读器包含一个非常小的数据库 FP 模板。要修改这些模板,我们将 FP 阅读器插入 USB 并使用 Windows 上的程序对其进行修改。由于我在 Linux 上工作(出于好奇),我正在尝试制作一个允许我们在 Linux 上修改模板的程序。

此 FP 阅读器被视为 CD-ROM 阅读器。我正在尝试在 sg 包的帮助下与它沟通(我正在关注此文档http://www.tldp.org/HOWTO/SCSI-Generic-HOWTO/)。根据 FP 阅读器的文档(你可以在这里找到它http://www.adh-tech.com.tw/files/GT-511C3_datasheet_V1%201_20131127.pdf),我应该发送一个像 [55 aa 0001 00000000 0001 0101] 这样的缓冲区(12 字节)来制作“打开”命令。

这是我制作此命令的代码(我试图制作一个可读的最小示例):

#include <errno.h>
#include <fcntl.h>
#include <scsi/sg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define FP_PACKET_SZ 12

const uint8_t fp_packet_sz = FP_PACKET_SZ;
static unsigned char sense_buffer[32];

#define OPEN_CMD

static void init_snd(uint8_t buf[fp_packet_sz]) 
  size_t offset = 0;
  const uint16_t deviceID = 1;
  const uint16_t cmd = 1;
  const uint32_t parameter = 0;
  buf[offset++] = 0x55;
  buf[offset++] = 0xAA;
  memcpy(buf + offset, &deviceID, sizeof(deviceID));
  offset += sizeof(deviceID);
  memcpy(buf + offset, &parameter, sizeof(parameter));
  offset += sizeof(parameter);
  memcpy(buf + offset, &cmd, sizeof(cmd));
  offset += sizeof(cmd);

  uint16_t checksum = 0;
  for (unsigned int i = 0 ; i < offset ; i++)
    checksum += buf[i];
  memcpy(buf + offset, &checksum, sizeof(checksum));



int main(int argc, char *argv[]) 
  int fd = 0, res = 0;
  char * filename = 0;

  sg_io_hdr_t header;
  uint8_t snd[fp_packet_sz];
  uint8_t rcv[fp_packet_sz];
  memset (snd, 0, sizeof(snd));
  memset (rcv, 0, sizeof(rcv));

  if (argc < 2) 
    fprintf(stderr, "argument missing\n");  
    return EXIT_FAILURE;
  
  filename = argv[1];
  fd = open(filename, O_RDWR);
  if (fd < 0) 
    fprintf(stderr, "open %s failed\n", filename);
    return EXIT_FAILURE;
  

  init_snd(snd);
  header.interface_id = 'S';
  header.dxfer_direction = SG_DXFER_TO_FROM_DEV;

  header.cmd_len = fp_packet_sz;
  header.cmdp = snd;

  header.mx_sb_len = sizeof (sense_buffer);
  header.sbp = sense_buffer;

  header.iovec_count = 0;
  header.dxfer_len = fp_packet_sz;
  header.dxferp = rcv;

  header.timeout = 60000;
  header.flags = 0;

  if ((res = ioctl(fd, SG_IO, &header)) < 0) 
    fprintf(stderr, "ioctl failed and return errno: %s \n", strerror(errno));
    exit(EXIT_FAILURE);
  

  fprintf(stdout, "receive buffer:");
  for (int i = 0 ; i < fp_packet_sz ; i++)
    fprintf(stdout, " %02x", rcv[i]); 
  fprintf(stdout, "\n");


  fprintf(stdout, "sense data:");
  for (int i = 0 ; i < header.sb_len_wr ; i++)
    fprintf(stdout, " %02x", sense_buffer[i]); 
  fprintf(stdout, "\n");

  return EXIT_SUCCESS;

我期望rcv 具有以下值[55 aa 00 01 00 00 00 00 00 30 01 30]

但相反,我什么也没收到(或我不明白的东西),sense_data 得到以下值:70 00 05 00 00 00 00 0A 00 00 00 00 20 00 00 00 00 00 对应于 Illegal Request(根据 http://blog.disksurvey.org/knowledge-base/scsi-sense/ 博客)。我还尝试使用与论坛http://www.linuxquestions.org/questions/programming-9/linux-scsi-passthrough-porting-windows-routine-4175528749/ 中所说的scsi_inquiry.c 相同的方案,我得到了相同的sense_data。我想我真的不明白 sg 驱动程序是如何工作的。是提供sense_data 或设备的驱动程序吗?我也尝试在/dev/sr1 上制作一些read()write(),但没有成功(好像我只能读取一些关于FP 阅读器内存格式的信息)

终端中 sg 命令给出的一些附加信息:

>sg_map
/dev/sg3  /dev/sr1

>sg_inq /dev/sg3
invalid VPD response; probably a STANDARD INQUIRY response
standard INQUIRY:
  PQual=0  Device_type=5  RMB=1  LU_CONG=0  version=0x06  [SPC-4]
  [AERC=0]  [TrmTsk=0]  NormACA=0  HiSUP=0  Resp_data_format=2
  SCCS=0  ACC=0  TPGS=0  3PC=0  Protect=0  [BQue=0]
  EncServ=0  MultiP=0  [MChngr=0]  [ACKREQQ=0]  Addr16=0
  [RelAdr=0]  WBus16=0  Sync=0  [Linked=0]  [TranDis=0]  CmdQue=0
    length=36 (0x24)   Peripheral device type: cd/dvd
 Vendor identification:         
 Product identification: Fingerprint     
 Product revision level: 0.01

如果您需要更多信息,请告诉我,我会将其添加到该帖子中。

问题的继续:如何在 Linux (sg) 或任何其他程序中使用 scsi 驱动程序将特定命令(缓冲区)发送到指纹读取器?

感谢您(也许)未来的帮助。

编辑1: 这是发送到设备的 snd 缓冲区的确切值(由 gdb 给出)

gdb> x /3xw snd
0x0001aa55  0x00000000  0x01010001

【问题讨论】:

看来您正在发送整数 deviceID 、 cmd 和参数的内存内容。您是否以您应该发送的正确字节序发送这些?可能您使用的是小端机器,而示例 [55 aa 0001 00000000 0001 0101] 似乎在大端中具有这些字段 @nos 我的机器是 little endian,指纹读取器也是 little endian。准备发送缓冲区的函数是init_snd。我使用的示例是这样编写的,以便更具可读性。我从来没有真正擅长字节序。我会检查是否可以,谢谢 我添加了header.cmdp 上的 snd 缓冲区的值。有没有可能该设备与我的计算机没有相同的字长,从而给出了具有相同字节序的不同结果? 我试过了,还是一样的sense_data 【参考方案1】:

问题的简历:如何发送特定命令(缓冲区) 使用 Linux (sg) 中的 scsi 驱动程序或任何 其他程序?

不要。

不幸的是,“SCSI”通常是“有点像 SCSI,但不严格符合 SCSI”的同义词;和 USB 设备通常提供多个接口(例如,当操作系统没有有用的驱动程序时,“模糊地像 SCSI 但不是 SCSI”接口具有残缺的功能,加上当有设备驱动程序时使用的本机接口)。

这意味着您极有可能需要专门为该设备编写 USB 设备驱动程序。

请注意,如果您查看此设备的数据表,您会发现所有命令都与 SCSI 无关,唯一看起来像 CD 的是“升级 ISO CD Image()”函数记录为“不支持”。

【讨论】:

谢谢。事实上,我之所以谈到 SCSI,是因为我有在 Windows 上使用指纹的程序的源代码,并且它使用类似于 direct_buffer_pass_through 的 SCSI。我将看看如何创建驱动程序。这将是一个很好的体验。

以上是关于如何将特定命令发送到 Linux 中的 scsi 设备?的主要内容,如果未能解决你的问题,请参考以下文章

Linux从入门到精通——iscsi

如何通过linux上的命令行发送嵌入在邮件中的png文件?

无需重启 在Linux下热添加或移除SCSI硬盘

如何让机器人在不使用命令的情况下向特定频道中的特定公会发送消息

Linux命令|容易被误读的IOSTAT

如何将消息发送到 Kafka 中的特定分区?