Search code examples
clinuxioctlscsi

How send a specific command to a scsi device in Linux?


I'm actually working on a fingerprint reader (FP reader) which I plug in USB. This FP reader is also plug on a stm32f4 board. If I understand correctly, the FP reader contains a really small database FP templates. To modify those templates, we plug the FP reader on USB and use a program on Windows to modify it. Since I'm working on Linux (and for curiosity), I'm trying to make a program which permit us to modify templates on Linux.

This FP reader is seen as a CD-ROM reader. I'm trying to communicate to it with the help of sg package (I'm following this document http://www.tldp.org/HOWTO/SCSI-Generic-HOWTO/). According to the documentation of the FP reader (you can find it here http://www.adh-tech.com.tw/files/GT-511C3_datasheet_V1%201_20131127.pdf) I should send a buffer (of 12 bytes) like that [55 aa 0001 00000000 0001 0101] to make the "open" command.

Here is my code to make this command (I tried to make a readable minimal example):

#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;
}

What I expected was the rcv to have the following value [55 aa 00 01 00 00 00 00 00 30 01 30].

But instead, I received nothing (or something I don't understand) and the sense_data get the following value: 70 00 05 00 00 00 00 0A 00 00 00 00 20 00 00 00 00 00 which correspond to an Illegal Request (according to http://blog.disksurvey.org/knowledge-base/scsi-sense/ blog). I also tried to use the same scheme as scsi_inquiry.c as said in that forum http://www.linuxquestions.org/questions/programming-9/linux-scsi-passthrough-porting-windows-routine-4175528749/ and I get the same sense_data. I think I don't really understand how sg driver work. Is that the driver which gives the sense_data or the device ? I also try to make some read() and write() on /dev/sr1 but it didn't work (seems like I only can read some information about the format of the memory of the FP reader)

Some additional information given by sg commands in a terminal:

>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

If you need any more information, tell me, I will add it to that post.

Resume of the question: How can I send a particular command (buffer) to a fingerprint reader by using scsi driver in Linux (sg) or any other program ?

Thank you for your (maybe) future help.

EDIT1: Here is the exact value of snd buffer sent to the device (given by gdb)

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

Solution

  • Resume of the question: How can I send a particular command (buffer) to a fingerprint reader by using scsi driver in Linux (sg) or any other program ?

    Don't.

    Unfortunately "SCSI" is often a synonym for "vaguely like SCSI, but not strictly compliant with SCSI"; and USB devices often provide several interfaces (e.g. a "vaguely like SCSI but not SCSI" interface with crippled functionality for when the OS doesn't have a useful driver, plus a native interface that's used when there is a driver for the device).

    What this means is that it's extremely likely that you will need to write a USB device driver specifically for the device.

    Note that, if you look at this device's datasheet you'll see that none of the commands have anything to do with SCSI whatsoever, and the only thing that looks like a CD is the "Upgrade ISO CD Image()" function that is documented as "not supported".