
/*
 * 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 <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <glib.h>
#include "blktool.h"

#if 0
static const char *word_bits[16] = {
	[15]	= "",
	[14]	= "",
	[13]	= "",
	[12]	= "",
	[11]	= "",
	[10]	= "",
	[9]	= "",
	[8]	= "",
	[7]	= "",
	[6]	= "",
	[5]	= "",
	[4]	= "",
	[3]	= "",
	[2]	= "",
	[1]	= "",
	[0]	= "",
};
#endif

static GPtrArray *enab_arr;
static GPtrArray *supp_arr;
static const char *id_dev_name[4] = { "ata", "n/a", "atapi", "reserved" };
static const char *id_dev_number[4] = { "unknown1", "jumper", "cable-sel",
					"unknown2" };
static const char *id_atapi_drq_speed[4] = { "3 ms", "obsolete",
					     "50 us", "reserved" };
static const char *id_atapi_cdb_size[4] = { "12 bytes", "16 bytes",
					     "reserved1", "reserved2" };

static const char *ata_minor_rev[] = {
	[0x0001] = "Obsolete",
	[0x0002] = "Obsolete",
	[0x0003] = "Obsolete",
	[0x0004] = "Obsolete",
	[0x0005] = "Obsolete",
	[0x0006] = "Obsolete",
	[0x0007] = "Obsolete",
	[0x0008] = "Obsolete",
	[0x0009] = "Obsolete",
	[0x000A] = "Obsolete",
	[0x000B] = "Obsolete",
	[0x000C] = "Obsolete",
	[0x000D] = "ATA/ATAPI-4 X3T13 1153D revision 6",
	[0x000E] = "ATA/ATAPI-4 T13 1153D revision 13",
	[0x000F] = "ATA/ATAPI-4 X3T13 1153D revision 7",
	[0x0010] = "ATA/ATAPI-4 T13 1153D revision 18",
	[0x0011] = "ATA/ATAPI-4 T13 1153D revision 15",
	[0x0012] = "ATA/ATAPI-4 published, ANSI INCITS 317-1998",
	[0x0013] = "ATA/ATAPI-5 T13 1321D revision 3",
	[0x0014] = "ATA/ATAPI-4 T13 1153D revision 14",
	[0x0015] = "ATA/ATAPI-5 T13 1321D revision 1",
	[0x0016] = "ATA/ATAPI-5 published, ANSI INCITS 340-2000",
	[0x0017] = "ATA/ATAPI-4 T13 1153D revision 17",
	[0x0018] = "ATA/ATAPI-6 T13 1410D revision 0",
	[0x0019] = "ATA/ATAPI-6 T13 1410D revision 3a",
	[0x001A] = "ATA/ATAPI-7 T13 1532D revision 1",
	[0x001B] = "ATA/ATAPI-6 T13 1410D revision 2",
	[0x001C] = "ATA/ATAPI-6 T13 1410D revision 1",
	[0x001D] = "Reserved",
	[0x001E] = "ATA/ATAPI-7 T13 1532D revision 0",
	[0x001F] = "Reserved",
	[0x0020] = "Reserved",
	[0x0021] = "ATA/ATAPI-7 T13 1532D revision 4a",
	[0x0022] = "ATA/ATAPI-6 published, ANSI INCITS 361-2002",
};

static const char *word82_bits[16] = {
	[15]	= NULL,
	[14]	= "nop-cmd",
	[13]	= "read-buffer-cmd",
	[12]	= "write-buffer-cmd",
	[11]	= NULL,
	[10]	= "host-protected-area",
	[9]	= "device-reset-cmd",
	[8]	= "service-intr",
	[7]	= "release-intr",
	[6]	= "read-ahead",
	[5]	= "writeback-cache",
	[4]	= "packet-cmd",
	[3]	= "power-management",
	[2]	= "removeable-media",
	[1]	= "security-mode",
	[0]	= "smart",
};

static const char *word83_bits[16] = {
	[15]	= NULL,
	[14]	= NULL,
	[13]	= "flush-cache-ext",
	[12]	= "flush-cache",
	[11]	= "device-config-overlay",
	[10]	= "lba48",
	[9]	= "acoustic-mgmt",
	[8]	= "set-max",
	[7]	= "addr-offset-reserved-area",
	[6]	= "spinup-required",
	[5]	= "power-up-standby",
	[4]	= "removeable-media-status",
	[3]	= "advanced-pm",
	[2]	= "cfa",
	[1]	= "dma-queued",
	[0]	= "download-microcode",
};

static const char *word83_bits_atapi[16] = {
	[15]	= NULL,
	[14]	= NULL,
	[13]	= NULL,
	[12]	= "flush-cache",
	[11]	= "device-config-overlay",
	[10]	= NULL,
	[9]	= "acoustic-mgmt",
	[8]	= "set-max",
	[7]	= "addr-offset-reserved-area",
	[6]	= "spinup-required",
	[5]	= "power-up-standby",
	[4]	= "removeable-media-status",
	[3]	= NULL,
	[2]	= NULL,
	[1]	= NULL,
	[0]	= "download-microcode",
};

static const char *word84_bits[16] = {
	[15]	= NULL,
	[14]	= NULL,
	[13]	= "idle-immediate-unload",
	[12]	= NULL,
	[11]	= NULL,
	[10]	= "urg-write-stream",
	[9]	= "urg-read-stream",
	[8]	= "wwn64",
	[7]	= "dma-queued-fua-cmd",
	[6]	= "fua-cmd",
	[5]	= "logging",
	[4]	= "streaming",
	[3]	= "media-card-passthru",
	[2]	= "media-serial",
	[1]	= "smart-self-test",
	[0]	= "smart-error-log",
};

static inline int ata_valid(u16 val)
{
	return (val != 0) && (val != 0xffff);
}

static inline u32 ata_id_u32(u16 *buf)
{
	u32 tmp = (buf[1] << 16) | buf[0];

	return tmp;
}

static inline u64 ata_id_u64(u16 *buf)
{
	return   (u64)buf[0] |
		((u64)buf[1] << 16) |
		((u64)buf[2] << 32) |
		((u64)buf[3] << 48);
}

static char *bitstring(u32 mask, int max)
{
	int i;
	static char bitstring_buf[128];
	char s[3] = { 0, ' ', 0 };

	bitstring_buf[0] = 0;
	for (i = 0; i <= max; i++)
		if (mask & (1 << i)) {
			s[0] = '0' + i;
			strcat(bitstring_buf, s);
		}

	return bitstring_buf;
}

static int drive_cmd(u8 *cmd)
{
	int rc = ioctl(blkdev, HDIO_DRIVE_CMD, cmd);
	if (rc == 0)
		return 0;
	return -errno;
}

static void handle_ata_read_ahead(int do_32)
{
	u8 ata_cmd[4] = { ATA_CMD_SET_FEATURES };

	ata_cmd[2] = do_32 ? SETFEAT_READ_AHEAD : SETFEAT_READ_AHEAD_OFF;
	if (drive_cmd(&ata_cmd[0]))
		pdie("HDIO_DRIVE_CMD(dev read ahead)", 1);
}

void handle_dev_keep_settings(int argc, char **argv, struct command *cmd)
{
	int do_32 = get_bool(argc, argv, cmd);
	u8 ata_cmd[4] = { ATA_CMD_SET_FEATURES };

	ata_cmd[2] = do_32 ? SETFEAT_REVERT_TO_DEF : SETFEAT_REVERT_TO_DEF_OFF;
	if (drive_cmd(&ata_cmd[0]))
		pdie("HDIO_DRIVE_CMD(dev keep settings)", 1);
}

void handle_defect_mgmt(int argc, char **argv, struct command *cmd)
{
	int do_32 = get_bool(argc, argv, cmd);
	u8 ata_cmd[4] = { ATA_CMD_SET_FEATURES };

	ata_cmd[2] = do_32 ? SETFEAT_DEFECT_MGMT_ON : SETFEAT_DEFECT_MGMT_OFF;
	if (drive_cmd(&ata_cmd[0]))
		pdie("HDIO_DRIVE_CMD(dev defect mgmt)", 1);
}

static void handle_ata_wcache(int do_32)
{
	u8 ata_cmd[4] = { ATA_CMD_SET_FEATURES };

	ata_cmd[2] = do_32 ? SETFEAT_WCACHE_ON : SETFEAT_WCACHE_OFF;
	if (drive_cmd(&ata_cmd[0]))
		pdie("HDIO_DRIVE_CMD(wcache)", 1);
}

static void handle_ata_media(int do_32)
{
	u8 ata_cmd[4] = { };

	ata_cmd[0] = do_32 ? ATA_CMD_MEDIA_LOCK : ATA_CMD_MEDIA_UNLOCK;
	if (drive_cmd(&ata_cmd[0]))
		pdie("HDIO_DRIVE_CMD(media lock/unlock)", 1);
}

void handle_pm_mode(int argc, char **argv, struct command *cmd)
{
	int do_32;
	u8 ata_cmd[4] = { ATA_CMD_SET_FEATURES };

	if ((argc == 4) && (!strcmp(argv[optind], "off")))
		do_32 = 255;
	else
		do_32 = get_int(argc, argv, cmd);
	if ((do_32 < 1) || (do_32 > 255))
		do_32 = 255;

	if (do_32 == 255)
		ata_cmd[2] = SETFEAT_APM_OFF;
	else {
		ata_cmd[2] = SETFEAT_APM_ON;
		ata_cmd[1] = do_32;
	}
	if (drive_cmd(&ata_cmd[0]))
		pdie("HDIO_DRIVE_CMD(apm mode)", 1);
}

void handle_sleep(int argc, char **argv, struct command *cmd)
{
	u8 ata_cmd[4] = { ATA_CMD_SLEEP };

	if (drive_cmd(ata_cmd) == 0)
		return;
	perror("(warning) handle_sleep");

	ata_cmd[0] = ATA_CMD_SLEEP_2;
	if (drive_cmd(ata_cmd) == 0)
		return;

	pdie("handle_sleep", 1);
}

static void handle_ata_standby(void)
{
	u8 ata_cmd[4] = { ATA_CMD_STANDBY };

	if (drive_cmd(ata_cmd) == 0)
		return;
	perror("(warning) handle_standby");

	ata_cmd[0] = ATA_CMD_STANDBY_2;
	if (drive_cmd(ata_cmd) == 0)
		return;

	pdie("handle_standby", 1);
}

void handle_reset(int argc, char **argv, struct command *cmd)
{
	IOCTL(HDIO_DRIVE_RESET, NULL);
}

static void handle_id_str(char *s, u16 *buf, int len)
{
	int i;

	memcpy(s, buf, len);

	s[len - 1] = 0;

	i = len - 2;
	while (s[i] == ' ')
		s[i] = 0;
}

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

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

static void lists_dump(void)
{
	list_dump("flags", flag_arr);
	list_dump("supported", supp_arr);
	list_dump("enabled", enab_arr);
}

static void supp_push(u16 supp_mask, u16 enab_mask, const char **strlist)
{
	int i;

	for (i = 15; i >= 0; i--)
		if ((strlist[i] != NULL) && (supp_mask & (1 << i)))
			g_ptr_array_add(supp_arr, (gpointer) strlist[i]);
	for (i = 15; i >= 0; i--)
		if ((strlist[i] != NULL) && (enab_mask & (1 << i)))
			g_ptr_array_add(enab_arr, (gpointer) strlist[i]);
}

static void print_id_word(u16 word, const char *name)
{
	if (ata_valid(word))
		printf("%s: %u\n", name, word);
}

static void handle_ata_id(void)
{
	u16 id[256], tmp;
	int is_ata = 0, is_atapi = 0;
	const char *s;
	char tmp_s[64];

	lists_init();

	IOCTL(HDIO_GET_IDENTITY, &id);

	tmp = id[0];
	s = id_dev_name[tmp >> 14];
	is_ata = (strcmp(s, "ata") == 0);
	is_atapi = (strcmp(s, "atapi") == 0);

	printf("device-type: %s\n", s);
	if (tmp & (1 << 7))
		flag_push("removable-media-device");
	if (tmp & (1 << 2))
		flag_push("response-incomplete");

	if (is_atapi) {
		printf("drq-deadline: %s\n",
			id_atapi_drq_speed[(tmp >> 5) & 0x3]);
		printf("cdb-size: %s\n",
			id_atapi_cdb_size[tmp & 0x3]);
	}

	handle_id_str(tmp_s, &id[10], 20);
	printf("serial-number: %s\n", tmp_s);

	handle_id_str(tmp_s, &id[23], 8);
	printf("firmware-revision: %s\n", tmp_s);

	handle_id_str(tmp_s, &id[27], 40);
	printf("model-number: %s\n", tmp_s);

	if (is_ata) {
		tmp = id[47] & 0xff;
		printf("max-drq-block-sz: %u\n", tmp);
	}

	tmp = id[49];
	if (is_ata) {
		if (tmp & (1 << 13))
			flag_push("std-standby-timer");
		if (tmp & (1 << 11))
			flag_push("iordy");
		if (tmp & (1 << 10))
			flag_push("iordy-disable");
		if (tmp & (1 << 9))
			flag_push("lba");
		if (tmp & (1 << 8))
			flag_push("dma");
	}
	else if (is_atapi) {
		if (tmp & (1 << 15))
			flag_push("interleaved-dma");
		if (tmp & (1 << 14))
			flag_push("command-queueing");
		if (tmp & (1 << 13))
			flag_push("overlap");
		if (tmp & (1 << 12))
			flag_push("srst-required");
		if (tmp & (1 << 11))
			flag_push("iordy");
		if (tmp & (1 << 10))
			flag_push("iordy-disable");
		if (tmp & (1 << 8))
			flag_push("dma");
	}

	tmp = id[59];
	if (is_ata && (tmp & (1 << 8))) {
		tmp &= 0xff;
		printf("cur-drq-block-sz: %u\n", tmp);
	}

	if (is_ata)
		printf("sectors: %u\n", ata_id_u32(&id[60]));

	tmp = id[62];
	if (is_atapi && ata_valid(tmp)) {
		if (tmp && (1 << 15))
			flag_push("dmadir");
	}

	tmp = id[63];
	if (tmp & 0x7)
		printf("mwdma-supported: %s\n", bitstring(tmp & 0x7, 2));
	tmp >>= 8;
	if (tmp & 0x7)
		printf("mwdma-selected: %s\n", bitstring(tmp & 0x7, 2));

	tmp = id[64] << 3;
	tmp |= 0x7;
	printf("pio-supported: %s\n", bitstring(tmp & 0x1f, 4));

	printf("mwdma-cycle-time: %u min %u reco\n",
		id[65], id[66]);
	printf("pio-cycle-time: %u no-iordy %u iordy\n",
		id[67], id[68]);

	if (is_atapi) {
		tmp = id[71];
		if (ata_valid(tmp))
			printf("pkt-bus-rel: %d ns\n", tmp);
		tmp = id[72];
		if (ata_valid(tmp))
			printf("service-bsy-clear: %d ns\n", tmp);
	}

	printf("max-queue-depth: %u\n", (id[75] & 0x1f) + 1);

	tmp = id[80];
	if (ata_valid(tmp))
		printf("ata-major-versions: %s\n", bitstring(tmp, 15));

	tmp = id[81];
	if (ata_valid(tmp)) {
		if (tmp < ARRAY_SIZE(ata_minor_rev))
			s = ata_minor_rev[tmp];
		else
			s = "Reserved";

		printf("ata-minor-version: %s\n", s);
	}

	supp_push(id[82], id[85], word82_bits);

	if (ata_valid(id[83]))
		supp_push(id[83], id[86],
			  is_atapi ? word83_bits_atapi : word83_bits);
	if (is_ata && ata_valid(id[84]))
		supp_push(id[84], id[87], word84_bits);

	tmp = id[88];
	if (tmp & 0xff)
		printf("udma-supported: %s\n", bitstring(tmp & 0xff, 7));
	tmp >>= 8;
	if (tmp & 0xff)
		printf("udma-selected: %s\n", bitstring(tmp & 0xff, 7));

	if (is_ata) {
		print_id_word(id[89], "security-erase-time");
		print_id_word(id[90], "enhanced-security-erase-time");
		print_id_word(id[91], "advanced-pm");
		print_id_word(id[92], "master-pw-rev");
	}

	tmp = id[93];
	if (ata_valid(tmp)) {
		int dev1_num = (tmp >> 9) & 0x3;
		int dev0_num = (tmp >> 1) & 0x3;
		printf("hardware-reset: cblid-%s dev0-by-%s dev1-by-%s%s%s%s%s%s\n",
		       (tmp & (1 << 13)) ? "high" : "low",
		       id_dev_number[dev0_num],
		       id_dev_number[dev1_num],
		       tmp & (1 << 11) ? " dev1-pdiag" : "",
		       tmp & (1 << 6) ? " dev1-sel-dev0" : "",
		       tmp & (1 << 5) ? " dev0-dasp" : "",
		       tmp & (1 << 4) ? " dev0-pdiag" : "",
		       tmp & (1 << 4) ? " dev0-diag-ok" : "");
	}

	tmp = id[94];
	if (ata_valid(tmp)) {
		printf("acoustic-mgmt: %u reco %u current\n",
			tmp >> 8,
			tmp & 0xff);
	}

	tmp = id[84] & (1 << 4);
	if (is_ata && tmp) {
		u32 tmp32;

		print_id_word(id[95], "stream-min-req-sz");
		print_id_word(id[104], "stream-xfer-time-pio");
		print_id_word(id[96], "stream-xfer-time-dma");
		print_id_word(id[97], "stream-latency");

		tmp32 = ata_id_u32(&id[98]);
		printf("streaming-perf-grain: %u\n", tmp32);
	}

	tmp = id[83] & (1 << 10);
	if (is_ata && tmp)
		printf("sectors: %Lu\n", ata_id_u64(&id[100]));

	tmp = id[106];
	if (is_ata && ata_valid(tmp)) {
		if (tmp & (1 << 13))
			flag_push("multiple-logical-sectors");
		if (tmp & (1 << 12))
			flag_push("large-logical-sector");
		printf("logical-sector-shift: %u\n", tmp & 0xf);
	}

	lists_dump();
	lists_free();

	if (is_atapi) {
		printf("\n");
		scsi_operations.id();
	}
}

static struct class_operations ata_operations = {
	.id		= handle_ata_id,
	.media		= handle_ata_media,
	.read_ahead	= handle_ata_read_ahead,
	.standby	= handle_ata_standby,
	.wcache		= handle_ata_wcache,
};

void ata_init(void)
{
	ops = &ata_operations;
}

