
/*
 * Copyright 2004 Jeff Garzik
 *
 * This software may be used and distributed according to the terms
 * of the GNU General Public License, incorporated herein by reference.
 *
 */

#include "blktool-config.h"
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>

#include <linux/hdreg.h>
#include <scsi/scsi.h>
#include <scsi/sg.h>

#include <glib.h>
#include "blktool.h"

enum {
	max_sense		= 128,
	max_cdb			= 32,
	max_buf			= 8192,
};

static u8 buf[max_buf];
static u8 sensebuf[max_sense];
static u8 cdb[max_cdb];
static struct sg_io_hdr sg_io_hdr;

static inline u32 scsi_u32(u8 *buf)
{
	return  ((u32)buf[0] << 24) |
		((u32)buf[1] << 16) |
		((u32)buf[2] << 8) |
		((u32)buf[3]);
}

static void init_sg_hdr(void)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;

	memset(hdr, 0, sizeof(struct sg_io_hdr));
	hdr->interface_id = 'S';
	hdr->dxfer_direction = SG_DXFER_NONE;
	hdr->cmd_len = 12;
	hdr->mx_sb_len = max_sense;
	hdr->dxferp = buf;
	hdr->cmdp = cdb;
	hdr->sbp = sensebuf;
	hdr->timeout = 5000; /* millisec */
	hdr->flags = SG_FLAG_NO_DXFER;

	memset(cdb, 0, sizeof(cdb));
	memset(sensebuf, 0, sizeof(sensebuf));
}

static void init_inquiry(void)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;

	init_sg_hdr();

	hdr->dxfer_direction = SG_DXFER_FROM_DEV;
	hdr->dxfer_len = 128;
	hdr->flags &= ~SG_FLAG_NO_DXFER;

	cdb[0] = INQUIRY;
	cdb[4] = 128;
}

static void init_read_capacity(void)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;

	init_sg_hdr();

	hdr->dxfer_direction = SG_DXFER_FROM_DEV;
	hdr->dxfer_len = 8;
	hdr->flags &= ~SG_FLAG_NO_DXFER;

	cdb[0] = READ_CAPACITY;
}

static void init_media_lock(void)
{
	init_sg_hdr();

	cdb[0] = ALLOW_MEDIUM_REMOVAL;
}

static void init_mode_sense(void)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;

	init_sg_hdr();

	hdr->dxfer_direction = SG_DXFER_FROM_DEV;
	hdr->dxfer_len = 128;
	hdr->flags &= ~SG_FLAG_NO_DXFER;

	cdb[0] = MODE_SENSE_10;
	cdb[1] = (1 << 3);	/* do not return any block descriptors */
	cdb[8] = 128;		/* return max 128 bytes */
}

static void init_mode_select(void)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;

	init_sg_hdr();

	hdr->dxfer_direction = SG_DXFER_TO_DEV;
	hdr->flags &= ~SG_FLAG_NO_DXFER;

	cdb[0] = MODE_SELECT_10;
}

static void handle_scsi_standby(void)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;

	memset(buf, 0, 128);
	buf[0] = 0x1a;	/* power condition mode page */
	buf[1] = 0x0a;	/* page length - 2 */
	buf[3] = 0x01;	/* bit 0: standby */

	/* because bytes 8-11 (standby condition timer) are zero,
	 * the device should transition immediately to standby
	 */

	init_mode_select();
	hdr->dxfer_len = 12;
	cdb[8] = 12;
	IOCTL(SG_IO, hdr);
}

static void handle_scsi_wcache(int do_32)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;
	int wce;

	init_mode_sense();
	cdb[2] = 0x8;			/* caching mode page */
	IOCTL(SG_IO, hdr);

	wce = buf[2] & (1 << 2) ? 1 : 0; /* bit 2: write cache enable */
	if (do_32 == wce)
		return;			/* nothing to do */

	if (do_32)
		buf[2] |= (1 << 2);
	else
		buf[2] &= ~(1 << 2);

	init_mode_select();
	hdr->dxfer_len = 20;		/* len of caching mode page */
	cdb[8] = 20;
	IOCTL(SG_IO, hdr);
}

static void handle_scsi_read_ahead(int do_32)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;
	int ra;

	init_mode_sense();
	cdb[2] = 0x8;			/* caching mode page */
	IOCTL(SG_IO, hdr);

	ra = buf[12] & (1 << 5) ? 0 : 1; /* bit 5: disable read-ahead */
	if (do_32 == ra)
		return;			/* nothing to do */

	if (do_32)
		buf[12] &= ~(1 << 5);
	else
		buf[12] |= (1 << 5);

	init_mode_select();
	hdr->dxfer_len = 20;		/* len of caching mode page */
	cdb[8] = 20;
	IOCTL(SG_IO, hdr);
}

static void handle_scsi_media(int do_32)
{
	struct sg_io_hdr *hdr = &sg_io_hdr;

	init_media_lock();

	if (do_32)
		cdb[4] = 1;

	IOCTL(SG_IO, hdr);
}

void handle_bus_id(int argc, char **argv, struct command *cmd)
{
	char bus_id[64];

	memset(&bus_id, 0, sizeof(bus_id));

	IOCTL(SCSI_IOCTL_GET_PCI, &bus_id[0]);

	printf("%s\n", bus_id);
}

static void lists_init(void)
{
	flag_arr = g_ptr_array_new();
}

static void lists_free(void)
{
#ifdef FREE_ON_EXIT
	g_ptr_array_free(flag_arr, FALSE);
#endif
}

static void lists_dump(void)
{
	list_dump("flags", flag_arr);
}

static const char *periph_type[] = {
	"direct-access",
	"sequential-access",
	"printer",
	"processor",
	"write-once",
	"cd-rom",
	"scanner",
	"optimal-memory",
	"medium-changer",
	"comm",
	"prepress1",
	"prepress2",
	"storage-array",
	"enclosure",
	"simple-direct",
	"optical-reader",
};

static const char *inq_flags3[8] = {
	[5] = "norm-aca",
	[4] = "hi-sup",
};

static const char *inq_flags5[8] = {
	[7] = "sccs",
	[6] = "acc",
	[3] = "3pc",
};

static const char *inq_flags6[8] = {
	[7] = "bque",
	[6] = "enc-serv",
	[5] = "vs1",
	[4] = "multi-p",
	[3] = "media-changer",
	[2] = NULL,
	[1] = NULL,
	[0] = "addr16",
};

static const char *inq_flags7[8] = {
	[7] = "reladdr",
	[6] = NULL,
	[5] = "wbus16",
	[4] = "sync",
	[3] = "linked",
	[2] = NULL,
	[1] = "cmdque",
	[0] = "vs2",
};

#if 0
static const char *inq_flags5[8] = {
	[7] = "",
	[6] = "",
	[5] = "",
	[4] = "",
	[3] = "",
	[2] = "",
	[1] = "",
	[0] = "",
};
#endif

static void flag_push8(u8 mask, const char **strlist)
{
	int i;

	for (i = 7; i >= 0; i--)
		if ((strlist[i] != NULL) && (mask & (1 << i)))
			flag_push(strlist[i]);
}

static void handle_scsi_id(void)
{
	char s[128];
	struct sg_io_hdr *hdr = &sg_io_hdr;
	u8 dev_type;
	const char *sp;

	lists_init();

	init_inquiry();
	IOCTL(SG_IO, hdr);

	printf("scsi-version: %u\n", buf[2]);

	memset(s, 0, sizeof(s));
	memcpy(s, &buf[8], 15 - 8 + 1);
	printf("vendor-id: %s\n", s);

	memset(s, 0, sizeof(s));
	memcpy(s, &buf[16], 31 - 16 + 1);
	printf("product-id: %s\n", s);

	memset(s, 0, sizeof(s));
	memcpy(s, &buf[32], 35 - 32 + 1);
	printf("product-rev: %s\n", s);

	dev_type = buf[0] & 0x1f;
	if (dev_type < ARRAY_SIZE(periph_type))
		sp = periph_type[dev_type];
	else
		sp = "unknown";
	printf("device-type: %s\n", sp);

	flag_push8(buf[0x3], inq_flags3);
	flag_push8(buf[0x5], inq_flags5);
	flag_push8(buf[0x6], inq_flags6);
	flag_push8(buf[0x7], inq_flags7);

	lists_dump();
	lists_free();

	if ((dev_type == 0x00) || (dev_type == 0x0E)) {
		init_read_capacity();
		IOCTL(SG_IO, hdr);

		printf("sectors: %u\n", scsi_u32(&buf[0]) + 1);
		printf("sector-size: %u\n", scsi_u32(&buf[4]));
	}
}

struct class_operations scsi_operations = {
	.id		= handle_scsi_id,
	.media		= handle_scsi_media,
	.read_ahead	= handle_scsi_read_ahead,
	.standby	= handle_scsi_standby,
	.wcache		= handle_scsi_wcache,
};

void scsi_init(void)
{
	ops = &scsi_operations;
}

