/* 
   PSPi: Demonstrates Sony's vendor-specific SCSI commands for the PSP.

   Copyright (c) 2006  Jim Paris <jim@jtan.com>
   Provided under BSD license, see LICENSE for details.

   $Id: pspi.c 2013 2006-11-08 07:07:17Z jim $
*/

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

/* Print a hexadecimal dump of a block of data */
void hexdump(void *data, int size)
{
    unsigned char *p = data;
    unsigned char c;
    int n;
    char bytestr[4] = {0};
    char addrstr[10] = {0};
    char hexstr[ 16*3 + 5] = {0};
    char charstr[16*1 + 5] = {0};
    for(n=1;n<=size;n++) {
        if (n%16 == 1) {
            /* store address for this line */
            snprintf(addrstr, sizeof(addrstr), "%.4x",
               ((unsigned int)p-(unsigned int)data) );
        }
            
        c = *p;
        if (isalnum(c) == 0) {
            c = '.';
        }

        /* store hex str (for left side) */
        snprintf(bytestr, sizeof(bytestr), "%02X ", *p);
        strncat(hexstr, bytestr, sizeof(hexstr)-strlen(hexstr)-1);

        /* store char str (for right side) */
        snprintf(bytestr, sizeof(bytestr), "%c", c);
        strncat(charstr, bytestr, sizeof(charstr)-strlen(charstr)-1);

        if(n%16 == 0) { 
            /* line completed */
            printf("  [%4.4s]  %-49.49s %s\n", addrstr, hexstr, charstr);
            hexstr[0] = 0;
            charstr[0] = 0;
        } else if(n%8 == 0) {
            /* half line: add whitespaces */
            strncat(hexstr, " ", sizeof(hexstr)-strlen(hexstr)-1);
        }
        p++; /* next byte */
    }

    if (strlen(hexstr) > 0) {
        /* print rest of buffer if not empty */
        printf("  [%4.4s]  %-49.49s %s\n", addrstr, hexstr, charstr);
    }
}

/* Send a SCSI command block and display the result. */
void do_command(int fd, char *desc, unsigned char *cmd, int len)
{
	sg_io_hdr_t io;
	unsigned char buf[512];
	
	memset(buf, 0, sizeof(buf));

	memset(&io, 0, sizeof(io));
	io.interface_id = 'S';
	io.cmd_len = len;
	io.mx_sb_len = 0;
	io.dxfer_direction = SG_DXFER_FROM_DEV;
	io.dxfer_len = sizeof(buf);
	io.dxferp = buf;
	io.cmdp = cmd;
	
	printf("Command: %s\n", desc);
	hexdump(cmd, len);

	if (ioctl(fd, SG_IO, &io) < 0) {
		printf("Error: %s\n", strerror(errno));
		return;
	}

	if ((io.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
		printf("Failed with status 0x%02x\n", io.masked_status);
		return;
	}

	len = io.dxfer_len - io.resid;
	printf("Response: %d %s\n", len, (len == 1) ? "byte" : "bytes");
	hexdump(buf, len);
}

/* A few test commands to send */
struct {
	char *description;
	int len;
	unsigned char cmd[16];
} test_commands[] = {
	{ "SCSI Inquiry", 6, 
	  { 0x12, 0x00, 0x00, 0x00, 0xFF, 0x00 } },
//	{ "Sony 0xFA (format memory stick)", 10,  
//	  { 0xFA, 0x00, 0xA0, 0x4d, 0x47, 0x66, 0x6d, 0x74, 0x01, 0x00 } },
	{ "Sony 0xFB 0xB4 (unknown)", 10, 
	  { 0xFB, 0x00, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00 } },
	{ "Sony 0xFB 0xB5 (USB string 3)", 10, 
	  { 0xFB, 0x00, 0xB5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 } },
	{ "Sony 0xFC 0x90 (firmware revision)", 10, 
	  { 0xFC, 0x00, 0x90, 0x50, 0x53, 0x50, 0x69, 0x00, 0x00, 0x00 } },
	{ "Sony 0xFC 0x91 (nickname)", 10, 
	  { 0xFC, 0x00, 0x91, 0x50, 0x53, 0x50, 0x69, 0x00, 0x00, 0x00 } },
	{ NULL, 0, { 0 } },
};

int main(int argc, char **argv)
{
	int fd;
	int i = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s /dev/sda\n", *argv);
		return 1;
	}

	if ((fd = open(argv[1], O_RDWR)) < 0) {
		perror(argv[1]);
		return 1;
	}

	if ((ioctl(fd, SG_GET_VERSION_NUM, &i) < 0) || (i < 30000)) {
		fprintf(stderr,"%s is not a sg device\n", argv[1]);
		close(fd);
		return 1;
	}

	printf("Opened %s", argv[1]);

	/* Send each of the test commands */
	for (i = 0; test_commands[i].description != NULL; i++) {
		printf("\n");
		do_command(fd, 
			   test_commands[i].description,
			   test_commands[i].cmd, 
			   test_commands[i].len);
	}

	close(fd);
	return 0;
}


