1

I discovered that my Nikon LS-9000 ED scanner was not supported by Linux SANE and decided to make my own driver with the Linux Firewire kernel API libraw1394.

The Library Programs and Command API Specifications for the scanner was recently released by Nikon. The scanner uses the Serial Bus Protocol 2 (SBP-2) and the IEEE Std 1394-1995 standard.

I made a simple test-program with libraw1394 and discovered that I can not read (or write) to most registers specific to the Serial Bus. The test-program for reading was as follows:

// gcc -Wall -o read read.c -l raw1394
#include <stdio.h>
#include <libraw1394/csr.h>
#include <libraw1394/raw1394.h>


int main()
{

    raw1394handle_t handle;

    handle = raw1394_new_handle_on_port(0);

    printf("node: %X\n", raw1394_get_local_id(handle));


    quadlet_t read;

    for (int i = 0; i <= 1048576; i++)  // 0 to 0x100000
    {
        int result = raw1394_read(handle, 
                                  raw1394_get_local_id(handle), 
                                  CSR_REGISTER_BASE + (i*4), 
                                  sizeof(quadlet_t), 
                                  &read);

        if (result == 0)
            printf("%X\n", i*4);            
    }

    raw1394_destroy_handle(handle);

}

The output (in abbreviated form) was:

$ sudo ./read
node: FFC1
0
4
8
18
1C
200
204
210
218
21C
220
224
228
230
234
400 - 7FC
1000 - 13FC

These offsets do not include the all-important MANAGEMENT_AGENT register, which for the Nikon LS9000 ED is at 0x30000. I could not write to this register either.

There must be a kind of memory access restriction in the kernel. How can I write commands to the MANAGEMENT_AGENT register, for example a Query login ORB?

Before the scanner was connected:

$ lsmod | grep firewire
firewire_ohci          40960  0
firewire_core          65536  1 firewire_ohci
crc_itu_t              16384  1 firewire_core

$ dmesg | grep firewire
[    0.776039] firewire_ohci 0000:03:00.0: added OHCI v1.10 device as card  0, 4 IR + 8 IT contexts, quirks 0x2
[    1.276095] firewire_core 0000:03:00.0: created device fw0: GUID 000000000000017e, S400

After the scanner was connected:

$ lsmod | grep firewire
firewire_sbp2          24576  0
firewire_ohci          40960  0
firewire_core          65536  2 firewire_ohci,firewire_sbp2
crc_itu_t              16384  1 firewire_core

$ dmesg | grep firewire
[    0.776039] firewire_ohci 0000:03:00.0: added OHCI v1.10 device as card 0, 4 IR + 8 IT contexts, quirks 0x2
[    1.276095] firewire_core 0000:03:00.0: created device fw0: GUID 000000000000017e, S400
[ 3289.660782] firewire_core 0000:03:00.0: rediscovered device fw0
[ 3292.688185] firewire_core 0000:03:00.0: created device fw1: GUID 0090b54003ffffff, S400
[ 3292.688190] firewire_core 0000:03:00.0: phy config: new root=ffc0, gap_count=5
[ 3292.922459] firewire_sbp2 fw1.0: logged in to LUN 0000 (0 retries)

It seems the kernel module firewire_sbp2 starts when the scanner is connected and apparently does the login. Can the functions in firewire_sbp2 be used from an application?

Rhett
  • 51
  • 1
  • 1
  • 3

3 Answers3

0

SBP2 is a protocol to transport SCSI commands over FireWire.

In Linux, you can use the SCSI Generic driver (sg) to send SCSI commands to such a device.

CL.
  • 173,858
  • 17
  • 217
  • 259
  • Thank you for this information. I will look carefully into this driver and report back in some weeks. – Rhett Nov 29 '17 at 09:55
0

I am glad to say that I managed to use the SCSI Generic driver for my purposes.

Here is a complete program that is tailor-made to eject the film tray of a Nikon LS-9000 ED scanner.

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

/* A program to eject the tray of a Nikon LS-9000 ED scanner
*  using the Linux SCSI generic (sg) driver.
*
*  Based on the example by D. Gilbert at
*  http://tldp.org/HOWTO/SCSI-Generic-HOWTO/pexample.html
*/



#define OPCODE_EXECUTE  0xC1
#define OPCODE_SET      0xE0                

#define CMD_EXECUTE_LEN 6
#define CMD_SET_LEN     10

#define EJECT_TRAY      0xD0    


#define PARAM_LEN       0xD



int main(int argc, char * argv[])
{

    int sg_device;

    unsigned char setCmdBlk[CMD_SET_LEN] =
        {OPCODE_SET, 0, EJECT_TRAY, 0, 0, 0, 0, 0, PARAM_LEN, 0};

    unsigned char exeCmdBlk[CMD_EXECUTE_LEN] =
        {OPCODE_EXECUTE, 0, 0, 0, 0, 0};

    unsigned char sense_buffer[32];
    sg_io_hdr_t io_hdr;


    if (argc != 2) 
    {
        printf("Usage: 'eject /dev/sg<device number>'\n");
        return 1;
    }

    if ((sg_device = open(argv[1], O_RDWR)) < 0) 
    {
        perror("Error opening device");
        return 1;
    }


    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.cmd_len = sizeof(setCmdBlk);
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.dxfer_direction = SG_DXFER_NONE;
    io_hdr.dxfer_len = 0;
    io_hdr.dxferp = NULL;
    io_hdr.cmdp = setCmdBlk;
    io_hdr.sbp = sense_buffer;
    io_hdr.timeout = 20000; 

    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("SET PARAM ioctl error");
        return 1;
    }


    io_hdr.cmd_len = sizeof(exeCmdBlk);
    io_hdr.cmdp = exeCmdBlk;

    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("EXECUTE ioctl error");
        return 1;
    }


    close(sg_device);
    return 0;

}

I presume that communication with and use of my Nikon scanner under Linux should now in principle be solved.

Obviously a lot of work remains to actually produce scans and implement all the settings that control the scanner. I plan on making a library of functions and a desktop application. The goal is to make an interface that broadly resembles Nikon Scan 4.0.3.

Thanks for the help.

Rhett
  • 51
  • 1
  • 1
  • 3
  • For the code above to work consistently (as I now understand it) you need to add a TEST UNIT READY ioctl between the SET PARAM ioctl and the EXECUTE ioctl. – Rhett Jan 26 '18 at 18:03
0

I am now happy to say that I have made a driver that can create full, correctly exposed scans with my Nikon Super Coolscan 9000ED on Linux.

The driver consists of a series of C programs. In addition I use the Linux utility Convert to make tiffs from bins. The driver in its current raw state can be found on GitHub. Note that it currently works only with the FH-869S Brownie Strip Film holder.

I enclose the complete C program for making thumbnails:

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



#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte)  \
  (byte & 0x80 ? '1' : '0'), \
  (byte & 0x40 ? '1' : '0'), \
  (byte & 0x20 ? '1' : '0'), \
  (byte & 0x10 ? '1' : '0'), \
  (byte & 0x08 ? '1' : '0'), \
  (byte & 0x04 ? '1' : '0'), \
  (byte & 0x02 ? '1' : '0'), \
  (byte & 0x01 ? '1' : '0')



#define MODESELECT_OPCODE   0x15
#define MODESELECT_CMD_LEN  6
#define PAGEFORMAT          0x10    // 00010000
#define PAGECODE            0x03    // 00 0000 11
#define MODE_LENGTH         0x14    // 20d 



#define GET_OPCODE          0x25    // p.46 25h
#define GET_CMD_LEN         10
#define WINDOW_HEADER       8
#define SINGLE              0x01    
#define GET_LENGTH_MSB      0x00   
#define GET_LENGTH_LSB      0x3A    // 58



#define SET_OPCODE          0x24
#define SET_CMD_LEN         10
#define SET_LENGTH_MSB      0x00   
#define SET_LENGTH_LSB      0x3A


#define RED     1
#define GREEN   2
#define BLUE    3


#define SCAN_OPCODE         0x1B    // SCAN 
#define CMD_SCAN_LEN        6
#define SCAN_LENGTH         0x03    // 0 for BW  3 for RGB ?


#define READ_OPCODE         0x28    // p.53 28h
#define READ_CMD_LEN        10
#define DATA_TYPE_CODE      0x00        
#define DATA_TYPE_QUAL_MSB  0x00   
#define DATA_TYPE_QUAL_LSB  0x00
#define READ_LENGTH_MSB     0x00   
#define READ_LENGTH_LSB     0x00    
#define READ_DATA_HEADER    6   




int sg_device;



unsigned char modeCmd[MODESELECT_CMD_LEN] =
    {MODESELECT_OPCODE, PAGEFORMAT, 0, 0, MODE_LENGTH, 0};


unsigned char getCmd[GET_CMD_LEN] =
    {GET_OPCODE, SINGLE, 0, 0, 0, RED, GET_LENGTH_MSB, GET_LENGTH_MSB, GET_LENGTH_LSB, 0};


unsigned char setCmd[SET_CMD_LEN] =
    {SET_OPCODE, 0, 0, 0, 0, 0, SET_LENGTH_MSB, SET_LENGTH_MSB, SET_LENGTH_LSB, 0};


unsigned char scanCommand[CMD_SCAN_LEN] =
    {SCAN_OPCODE, 0, 0, 0, SCAN_LENGTH, 0}; 


unsigned char readCmd[READ_CMD_LEN] =
    {READ_OPCODE, 0, DATA_TYPE_CODE, 0, DATA_TYPE_QUAL_MSB, DATA_TYPE_QUAL_LSB, 
     READ_LENGTH_MSB, READ_LENGTH_MSB, READ_LENGTH_LSB, 0};




unsigned char sense_buffer[32];     // p.6 'Status of this unit' 8 quadlets




sg_io_hdr_t io_hdr;




void printSense()
{

    printf("0: "BYTE_TO_BINARY_PATTERN" %02X %02X%02X\n", BYTE_TO_BINARY(sense_buffer[0]), sense_buffer[1], sense_buffer[2], sense_buffer[3]);
    printf("1: %02X%02X%02X%02X\n", sense_buffer[4], sense_buffer[5], sense_buffer[6], sense_buffer[7]);
    printf("2: "BYTE_TO_BINARY_PATTERN" "BYTE_TO_BINARY_PATTERN" %02X %02X\n", BYTE_TO_BINARY(sense_buffer[8]), 
                BYTE_TO_BINARY(sense_buffer[9]), sense_buffer[10], sense_buffer[11]);
    printf("3: %02X%02X%02X%02X\n", sense_buffer[12], sense_buffer[13], sense_buffer[14], sense_buffer[15]);
    printf("4: %02X%02X%02X%02X\n", sense_buffer[16], sense_buffer[17], sense_buffer[18], sense_buffer[19]);
    printf("5: %02X%02X%02X%02X\n", sense_buffer[20], sense_buffer[21], sense_buffer[22], sense_buffer[23]);
    printf("6: %02X%02X%02X%02X\n", sense_buffer[24], sense_buffer[25], sense_buffer[26], sense_buffer[27]);
    printf("7: %02X%02X%02X%02X\n\n", sense_buffer[28], sense_buffer[29], sense_buffer[30], sense_buffer[31]);

}




int modeselect()
{



    unsigned char parameter_buffer[MODE_LENGTH];



    parameter_buffer[0] = 0x13; 
    parameter_buffer[1] = 0x00;     
    parameter_buffer[2] = 0x00;
    parameter_buffer[3] = 0x08;     


    parameter_buffer[4] = 0x00; 
    parameter_buffer[5] = 0x00;     
    parameter_buffer[6] = 0x00;
    parameter_buffer[7] = 0x00;
    parameter_buffer[8] = 0x00;
    parameter_buffer[9] = 0x00;
    parameter_buffer[10] = 0x00;
    parameter_buffer[11] = 0x01;

    parameter_buffer[12] = 0x03; 
    parameter_buffer[13] = 0x06;    
    parameter_buffer[14] = 0x00;
    parameter_buffer[15] = 0x00;
    parameter_buffer[16] = 0x0F;        // max resolution
    parameter_buffer[17] = 0xA0;        // max resolution
    parameter_buffer[18] = 0x00;
    parameter_buffer[19] = 0x00;




    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;
    io_hdr.dxfer_len = sizeof(parameter_buffer);
    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(modeCmd);
    io_hdr.cmdp = modeCmd;
    io_hdr.dxfer_direction = SG_DXFER_TO_DEV;               //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000; 



    for (int i = 0; i < 10; i++)
    {

        if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
        {
            perror("MODESELECT ioctl error");
            return 1;
        }

        if (io_hdr.status == 0)
            break;

        usleep(20000);

    }   


    printf("ModeSelect: %02X\n\n", io_hdr.status);





    return 0; 

}




int set(int color, uint32_t exposure)
{



    unsigned char parameter_buffer[58];


    parameter_buffer[0] = 0x00; 
    parameter_buffer[1] = 0x00;     
    parameter_buffer[2] = 0x00;
    parameter_buffer[3] = 0x00;
    parameter_buffer[4] = 0x00;
    parameter_buffer[5] = 0x00;
    parameter_buffer[6] = 0x00;
    parameter_buffer[7] = 0x32;     // 50   <<<<< this is 50, not 58 >>>>>




    parameter_buffer[WINDOW_HEADER+0] = 0x00 | color;

    parameter_buffer[WINDOW_HEADER+1] = 0x00;

    parameter_buffer[WINDOW_HEADER+2] = 0x00 | 0x00;        // 83=0053  666=029A
    parameter_buffer[WINDOW_HEADER+3] = 0x00 | 0x53;

    parameter_buffer[WINDOW_HEADER+4] = 0x00 | 0x00;        // 
    parameter_buffer[WINDOW_HEADER+5] = 0x00 | 0x53;


    parameter_buffer[WINDOW_HEADER+6] = 0x00;       // Upper Left X Offset
    parameter_buffer[WINDOW_HEADER+7] = 0x00;
    parameter_buffer[WINDOW_HEADER+8] = 0x00 | 0x02;        
    parameter_buffer[WINDOW_HEADER+9] = 0x00 | 0x06;

    parameter_buffer[WINDOW_HEADER+10] = 0x00;      // Upper Left Y Offset
    parameter_buffer[WINDOW_HEADER+11] = 0x00;
    parameter_buffer[WINDOW_HEADER+12] = 0x00 | 0x08;       
    parameter_buffer[WINDOW_HEADER+13] = 0x00 | 0xBC;

    parameter_buffer[WINDOW_HEADER+14] = 0x00;      // Window Width (X)
    parameter_buffer[WINDOW_HEADER+15] = 0x00;
    parameter_buffer[WINDOW_HEADER+16] = 0x00 | 0x23;   // 8964 px   
    parameter_buffer[WINDOW_HEADER+17] = 0x00 | 0x04;   // 4000 / 83 = 48;
                                                        // 8964 / 48 = 186*3*2 = 1116 bytes pr. line

    parameter_buffer[WINDOW_HEADER+18] = 0x00;      // Window Length (Y)
    parameter_buffer[WINDOW_HEADER+19] = 0x00;  
    parameter_buffer[WINDOW_HEADER+20] = 0x00 | 0x75;   // 8754h - (2x8BCh) = 30172                                                                 
    parameter_buffer[WINDOW_HEADER+21] = 0x00 | 0xC0;   // 30172 - 12 = 30160 - 16 = 30144 (75C0) / 48 = 628 lines


    parameter_buffer[WINDOW_HEADER+22] = 0x00;
    parameter_buffer[WINDOW_HEADER+23] = 0x00;
    parameter_buffer[WINDOW_HEADER+24] = 0x00;

    parameter_buffer[WINDOW_HEADER+25] = 0x00 | 0x05;   // RBG (02 = BW) 
    parameter_buffer[WINDOW_HEADER+26] = 0x00 | 0x10;   // 16 bits (not 8 bits)

    parameter_buffer[WINDOW_HEADER+27] = 0x00;      // 
    parameter_buffer[WINDOW_HEADER+28] = 0x00;

    parameter_buffer[WINDOW_HEADER+29] = 0x00; 

    parameter_buffer[WINDOW_HEADER+30] = 0x00;
    parameter_buffer[WINDOW_HEADER+31] = 0x00;
    parameter_buffer[WINDOW_HEADER+32] = 0x00;
    parameter_buffer[WINDOW_HEADER+33] = 0x00;
    parameter_buffer[WINDOW_HEADER+34] = 0x00;
    parameter_buffer[WINDOW_HEADER+35] = 0x00;
    parameter_buffer[WINDOW_HEADER+36] = 0x00;
    parameter_buffer[WINDOW_HEADER+37] = 0x00;
    parameter_buffer[WINDOW_HEADER+38] = 0x00;
    parameter_buffer[WINDOW_HEADER+39] = 0x00;

    parameter_buffer[WINDOW_HEADER+40] = 0x00;          // color 0=gray
    parameter_buffer[WINDOW_HEADER+41] = 0x00 | 0x01;   // 1=positive





    parameter_buffer[WINDOW_HEADER+42] = 0x00 | 0x02;       // 2=Thumbnail 
    parameter_buffer[WINDOW_HEADER+43] = 0x00 | 0x02;       // 4=high speed
    parameter_buffer[WINDOW_HEADER+44] = 0x00 | 0x02;
    parameter_buffer[WINDOW_HEADER+45] = 0x00;              // 

    parameter_buffer[WINDOW_HEADER+46] = (exposure >> 24) & 0xFF;   
    parameter_buffer[WINDOW_HEADER+47] = (exposure >> 16) & 0xFF;               
    parameter_buffer[WINDOW_HEADER+48] = (exposure >> 8) & 0xFF;                  
    parameter_buffer[WINDOW_HEADER+49] = exposure & 0xFF;




    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;
    io_hdr.dxfer_len = sizeof(parameter_buffer);
    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(setCmd);
    io_hdr.cmdp = setCmd;
    io_hdr.dxfer_direction = SG_DXFER_TO_DEV;               //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000; 




    for (int i = 0; i < 50; i++)
    {       
        if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
        {
            perror("SET ioctl error");
            return 1;
        }

        if (io_hdr.status == 0)
            break;

        usleep(20000);
    }

    printf("\nCOLOR:%d Status:%02X\n", color, io_hdr.status);



    return 0; 

}



int scan()
{


    unsigned char parameter_buffer[3];

    parameter_buffer[0] = 0x01;
    parameter_buffer[1] = 0x02;     
    parameter_buffer[2] = 0x03;



    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;
    io_hdr.dxfer_len = sizeof(parameter_buffer);
    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(scanCommand);
    io_hdr.cmdp = scanCommand;
    io_hdr.dxfer_direction = SG_DXFER_TO_DEV;               //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000; 



    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("SCAN ioctl error");
        return 1;
    }



    printf("scan: %02X\n", io_hdr.status);
    printSense(); 


    return 0;

}




int maxValue(int color)
{

    readCmd[2] = 0x00 | 0x81;   // Data type code       
    readCmd[4] = 0x00 | color;  // DATA_TYPE_QUAL_MSB
    readCmd[5] = 0x00 | 0x01;   // DATA_TYPE_QUAL_LSB   2-byte-data     
    readCmd[8] = 0x00 | 0x08;   // READ_DATA_HEADER + 2




    unsigned char parameter_buffer[8];

    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;
    io_hdr.dxfer_len = sizeof(parameter_buffer);
    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(readCmd);
    io_hdr.cmdp = readCmd;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;             //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000;




    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("READ ioctl error");
        return 1;
    }     


    printf("Max color %d: %02X%02X\n", color, parameter_buffer[6] & 0x3F, parameter_buffer[7]);


    return 0;   

}


uint32_t wbValue(int color)
{


    readCmd[2] = 0x00 | 0x8C;           // Data type code
    readCmd[4] = 0x00 | color;          // DATA_TYPE_QUAL_MSB   
    readCmd[5] = 0x00 | 0x03;           // DATA_TYPE_QUAL_LSB   
    readCmd[8] = 0x00 | 0x0A;           // READ_DATA_HEADER + 4



    unsigned char parameter_buffer[10];




    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;
    io_hdr.dxfer_len = sizeof(parameter_buffer);
    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(readCmd);
    io_hdr.cmdp = readCmd;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;             //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000;



    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("READ ioctl error");
        return 1;
    }


    uint32_t result = ((parameter_buffer[6] << 24) | 
                       (parameter_buffer[7] << 16) |
                       (parameter_buffer[8] << 8) |
                        parameter_buffer[9]);

    printf("WB%d: %08X\n", color, result);


    return result; 

}



float analogGain()
{


    readCmd[2] = 0x00 | 0x8A;           // Data type code
    readCmd[4] = 0x00 | 0x00;           // DATA_TYPE_QUAL_MSB   
    readCmd[5] = 0x00 | 0x03;           // DATA_TYPE_QUAL_LSB   
    readCmd[8] = 0x00 | 0x0E;           // READ_DATA_HEADER + 8



    unsigned char parameter_buffer[14];




    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;
    io_hdr.dxfer_len = sizeof(parameter_buffer);
    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(readCmd);
    io_hdr.cmdp = readCmd;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;             //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000;


    printf("\nAnalog Gain\n");

    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("READ ioctl error");
        return 1;
    }     
    printf("Read: %02X\n", io_hdr.status);


    printf("0    : %02X\n", parameter_buffer[0]);
    printf("1    : %02X\n", parameter_buffer[1]);
    printf("2-5  : %02X%02X%02X%02X\n", parameter_buffer[2], parameter_buffer[3], parameter_buffer[4], parameter_buffer[5]);


    printf(" 6: %02X\n", parameter_buffer[6]);
    printf(" 7: %02X\n", parameter_buffer[7]);
    printf(" 8: %02X\n", parameter_buffer[8]);
    printf(" 9: %02X\n", parameter_buffer[9]);

    printf("10: %02X\n", parameter_buffer[10]);
    printf("11: %02X\n", parameter_buffer[11]);
    printf("12: %02X\n", parameter_buffer[12]);
    printf("13: %02X\n", parameter_buffer[13]);



    union 
    {
        float result;
        unsigned char bytearray[sizeof(float)];
    } u;



    u.bytearray[3] = parameter_buffer[10];
    u.bytearray[2] = parameter_buffer[11];
    u.bytearray[1] = parameter_buffer[12];
    u.bytearray[0] = parameter_buffer[13];





    printf("Analog gain: %.7f\n", u.result);             





    return u.result;    

}



int coopActionParameter()
{

    readCmd[2] = 0x00 | 0x87;           // Data type code
    readCmd[3] = 0x00;
    readCmd[4] = 0x00;                  // no meaning
    readCmd[5] = 0x00;                  // 1 byte
    readCmd[6] = 0x00;
    readCmd[7] = 0x00;





    unsigned char parameter_buffer[24];




    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;

    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(readCmd);
    io_hdr.cmdp = readCmd;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;             //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000;





    printf("\nInitiator cooperative action parameter\n");


    readCmd[8] = 0x00 | 0x06;                       // READ_DATA_HEADER
    io_hdr.dxfer_len = 0x0 | 0x06;



    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("READ ioctl error");
        return 1;
    }

    printf("Read: %02X\n", io_hdr.status);

    printf("0    : %02X\n", parameter_buffer[0]);
    printf("1    : %02X\n", parameter_buffer[1]);
    printf("2-5  : %02X%02X%02X%02X\n", parameter_buffer[2], parameter_buffer[3], parameter_buffer[4], parameter_buffer[5]);




    readCmd[8] = 0x00 | 0x18;                       // READ_DATA_HEADER + 18    
    io_hdr.dxfer_len = sizeof(parameter_buffer);



    if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
    {
        perror("READ ioctl error");
        return 1;
    } 

    printf("Read: %02X\n", io_hdr.status);

    for (int i = READ_DATA_HEADER; i < READ_DATA_HEADER + parameter_buffer[5]; i++)
        printf("%d : %02X\n", i, parameter_buffer[i]);

    return 0;   

}





#define READBUFFER_MSB  0x04        // 2 * 3 * 186 = 1116 (045C)
#define READBUFFER_LSB  0x5C
#define READBUFFER      1116        // 



void readData()
{

    readCmd[2] = 0x00;          // Data type code
    readCmd[3] = 0x00;
    readCmd[4] = 0x00;
    readCmd[5] = 0x00;
    readCmd[6] = 0x00;
    readCmd[7] = 0x00 | READBUFFER_MSB;
    readCmd[8] = 0x00 | READBUFFER_LSB;





    unsigned char parameter_buffer[READBUFFER];



    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.sbp = sense_buffer;
    io_hdr.dxfer_len = sizeof(parameter_buffer);
    io_hdr.dxferp = parameter_buffer;     
    io_hdr.cmd_len = sizeof(readCmd);
    io_hdr.cmdp = readCmd;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;             //  /usr/include/scsi/sg.h
    io_hdr.timeout = 20000;



    unsigned char *buffer;

    buffer = NULL;


    // 1116 * 628 = 700848 bytes < 685*1024

    size_t mem_size = 685 * 1024;
    buffer = malloc(mem_size);
    if (buffer == NULL) 
    {
        printf("Buffer=NULL\n");
        return;
    } 



    printf("Read data\n");

    int bytes = 0;


    while (1)
    {

        if (ioctl(sg_device, SG_IO, &io_hdr) < 0) 
        {
            perror("READ ioctl error");
            return;
        }    

        if (io_hdr.status != 0)
            break;


        for (int i = 0; i < READBUFFER; i++)
            buffer[bytes + i] = parameter_buffer[i];

        bytes += READBUFFER;



        usleep(45000);      //microseconds

    }


    printf("Bytes: %d\n", bytes);



    FILE *write_ptr;

    write_ptr = fopen("../tmp/testThumb.bin", "wb");  // w for write, b for binary

    fwrite(buffer, 1, bytes, write_ptr); 

    fclose(write_ptr);

    free(buffer);


}






int main(int argc, char * argv[])
{


    if (argc != 2) 
    {
        printf("Usage: 'thumb /dev/sg<device number>'\n");
        return 1;
    }



    if ((sg_device = open(argv[1], O_RDWR)) < 0) 
    {
        perror("Error opening device");
        return 1;
    }



    modeselect();


    uint32_t wbR = wbValue(RED);
    uint32_t wbG = wbValue(GREEN);
    uint32_t wbB = wbValue(BLUE);


    maxValue(1);     
    maxValue(2);
    maxValue(3);

    analogGain();



    set(0, wbG);            // default same as GREEN
    set(RED, wbR);    
    set(GREEN, wbG);
    set(BLUE, wbB);



    scan();


    coopActionParameter();

    scan(); 


    sleep(3);  
    readData();





    close(sg_device);
    return 0;

}

I discovered that the 9000ED has no auto-exposure so I had to make code for this too. There is obviously a lot more work to do before the "driver" becomes anything like a practical and general utility, but I have solved all the principle issues. I will of course appreciate all help, advice and comments.

This project has been a great learning experience for me and I will continue to work on it (on and off) in the years to come.

Rhett
  • 51
  • 1
  • 1
  • 3