/*
 * s390-tools/tunedasd/src/disk.c
 *   Functions to handle DASD-IOCTLs.
 *
 * (C) Copyright IBM Corp. 2004
 *
 * Author(s): Horst Hummel (horst.hummel@de.ibm.com)
 */

#include "disk.h"
#include "tunedasd.h"

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


#define BUS_ID_SIZE 30

/*
 * DASD DEFINITIONS (copied from dasd.h) 
 */

#define DASD_IOCTL_LETTER 'D'

/* 
 * struct profile_info_t
 * holds the profiling information 
 */
typedef struct dasd_profile_info_t {
        unsigned int dasd_io_reqs;	  /* # of requests processed at all */
        unsigned int dasd_io_sects;	  /* # of sectors processed at all */
        unsigned int dasd_io_secs[32];	  /* request's sizes */
        unsigned int dasd_io_times[32];	  /* requests's times */
        unsigned int dasd_io_timps[32];	  /* requests's times per sector */
        unsigned int dasd_io_time1[32];	  /* time from build to start */
        unsigned int dasd_io_time2[32];	  /* time from start to irq */
        unsigned int dasd_io_time2ps[32]; /*time from start to irq */
        unsigned int dasd_io_time3[32];	  /* time from irq to end */
        unsigned int dasd_io_nr_req[32];  /* # of requests in chanq */
} dasd_profile_info_t;


/* 
 * struct attrib_data_t
 * represents the operation (cache) bits for the device.
 * Used in DE to influence caching of the DASD.
 */
typedef struct attrib_data_t {
	unsigned char   operation:3;     /* cache operation mode */
	unsigned char   reserved:5;      
	unsigned short  nr_cyl;          /* no of cyliners for read ahaed */
	unsigned char   reserved2[29];   /* for future use */
} __attribute__ ((packed)) attrib_data_t;

/* definition of operation (cache) bits within attributes of DE */
#define DASD_NORMAL_CACHE  0x0
#define DASD_BYPASS_CACHE  0x1
#define DASD_INHIBIT_LOAD  0x2
#define DASD_SEQ_ACCESS    0x3
#define DASD_SEQ_PRESTAGE  0x4
#define DASD_REC_ACCESS    0x5

/*
 * DASD-IOCTLs (copied from dasd.h) 
 */
/* Issue a reserve/release command, rsp. */
#define BIODASDRSRV    _IO (DASD_IOCTL_LETTER,2) /* reserve */
#define BIODASDRLSE    _IO (DASD_IOCTL_LETTER,3) /* release */
#define BIODASDSLCK    _IO (DASD_IOCTL_LETTER,4) /* steal lock */
/* reset profiling information of a device */
#define BIODASDPRRST   _IO (DASD_IOCTL_LETTER,5)

/* retrieve profiling information of a device */
#define BIODASDPRRD    _IOR (DASD_IOCTL_LETTER,2,dasd_profile_info_t)
/* Get Attributes (cache operations) */
#define BIODASDGATTR   _IOR(DASD_IOCTL_LETTER,5,attrib_data_t)

/* Set Attributes (cache operations) */
#define BIODASDSATTR   _IOW (DASD_IOCTL_LETTER,2,attrib_data_t) 


/* id definition for profile items */
enum prof_id {
	prof_reqs       =  0, 
	prof_sects      =  1,
	prof_sizes      =  2,
	prof_total	=  3,
	prof_totsect	=  4,
	prof_start	=  5,
	prof_irq	=  6,
	prof_irqsect	=  7,
	prof_end        =  8,
	prof_queue      =  9
};

/* Mapping for caching modes */
static struct {
        char* mode; 
        int id;
} mode_list[] = {
        { "normal",     DASD_NORMAL_CACHE },
        { "bypass",     DASD_BYPASS_CACHE },
        { "inhibit",    DASD_INHIBIT_LOAD },
        { "sequential", DASD_SEQ_ACCESS   },
        { "prestage",   DASD_SEQ_PRESTAGE },
        { "record",     DASD_REC_ACCESS   }
};	
	

/*
 * Check for valid cache parameters.
 */
int
check_cache (char* cache)
{
	int i;

	/* Check for valid parameters */
	for (i = 0; i < sizeof(mode_list)/sizeof(mode_list[0]); i++) {
		if (!strcmp (cache, mode_list[i].mode)) {
			return mode_list[i].id;
		}
	}
	error_print ("Invalid caching mode '%s' given", cache);
	return -1;
}

/*
 * Retrieve name of cache mode identified by number.
 */
char *
get_cache_name (int id)
{
	unsigned int i;
	
	for (i = 0; i < sizeof (mode_list) / sizeof (mode_list[0]);
	     i++) {
		if (id == mode_list[i].id) {
			return mode_list[i].mode;
		}
	}
	return "<unknown>";
}


/*
 * Check for valid number of cylinders.
 */
int
check_no_cyl (char* no_cyl)
{
	int nr_cyl;
	char* err_ptr;
	if (!no_cyl) {
		/* set default = 2 Cylinders - if option is missing */
		return 2;
	} else {
		err_ptr = NULL;
		nr_cyl = strtoul (no_cyl, &err_ptr, 0);
		
		if ((errno) ||
		    (*err_ptr != '\0')) { 
			error_print ("Invalid number of cylinders given '%s'",
				     no_cyl);
			return -1;
		}
	}
	return nr_cyl;
}


/*
 * Check for valid profile item parameters.
 */
int
check_prof_item (char* prof_item)
{
        /* Mapping for profile items */
	static struct {
		char* item; 
		int id;
	} prof_list[] = {
		{ "reqs",    prof_reqs    },
		{ "sects",   prof_sects   },
		{ "sizes",   prof_sizes   },
		{ "total",   prof_total   },
		{ "totsect", prof_totsect },
		{ "start",   prof_start   },
		{ "irq",     prof_irq     },   
		{ "irqsect", prof_irqsect },   
		{ "end",     prof_end     },
		{ "queue",   prof_queue   }
	};
	
	int i;

	/* Check for valid parameters */
	for (i = 0; i < sizeof(prof_list)/sizeof(prof_list[0]); i++) {
		if (!strcmp (prof_item, prof_list[i].item)) {
			return prof_list[i].id;
		}
	}
	error_print ("Invalid profile item '%s' given", prof_item);
	return -1;
}


/*
 * Get the caching algorithm used for the channel programs of this device.
 * 'cache' is the caching mode (see ESS docu for more info) and 'no_cyl'
 * the number of cylinders to be cached.
 */
int
disk_get_cache (char* device)
{
	int fd;
	attrib_data_t attrib_data;
	
	/* Open device file */
	fd = open (device, O_RDWR);
	if (fd == -1) {
		error_print ("<%s> - %s", device, strerror (errno));
		return -1;
	}

	/* Get the given caching attributes */
	if (ioctl (fd, BIODASDGATTR, &attrib_data)) {
		error_print ("Could not get cache attributes for device <%s>",
			     device);
		close (fd);
		return -1;
	}

	printf ("%s (%i cyl)\n",
		get_cache_name(attrib_data.operation),
		attrib_data.nr_cyl);

	close (fd);
	return 0;
}


/*
 * Set the caching algorithm used for the channel programs of this device.
 * 'cache' is the caching mode (see ESS docu for more info) and 'no_cyl'
 * the number of cylinders to be cached.
 */
int
disk_set_cache (char* device, char* cache, char* no_cyl)
{
	int fd;
	attrib_data_t attrib_data;
	
        /* get caching mode and # cylinders */
	attrib_data.operation = check_cache (cache);
	attrib_data.nr_cyl = check_no_cyl (no_cyl);

	if (attrib_data.nr_cyl > 40) {
		printf ("WARNING: This is a very large number of "
			"cylinders ;) %i\n", 
			attrib_data.nr_cyl);
	}

	/* Open device file */
	fd = open (device, O_RDWR);
	if (fd == -1) {
		error_print ("<%s> - %s", device, strerror (errno));
		return -1;
	}

	/* Set the given caching attributes */
	printf ("Setting cache mode for device <%s>...\n", device);
	if (ioctl (fd, BIODASDSATTR, &attrib_data)) {
		error_print ("Could not set caching for device <%s>", device);
		close (fd);
		return -1;
	}

	printf ("Done.\n");
	close (fd);
	return 0;
}


/*
 * Reserve the device.
 */
int 
disk_reserve (char* device)
{
	int fd;

	/* Open device file */
	fd = open (device, O_RDWR);
	if (fd == -1) {
		error_print ("<%s> - %s", device, strerror (errno));
		return -1;
	}

	/* Reserve device */
	printf ("Reserving device <%s>...\n", device);
	if (ioctl (fd, BIODASDRSRV)) {
		error_print ("Could not reserve device <%s>", device);
		close (fd);
		return -1;
	}

	printf ("Done.\n");
	close (fd);
	return 0;
}


/*
 * Release the device.
 */
int 
disk_release (char* device)
{
	int fd;

	/* Open device file */
	fd = open (device, O_RDWR);
	if (fd == -1) {
		error_print ("<%s> - %s", device, strerror (errno));
		return -1;
	}

	/* Release device */
	printf ("Releasing device <%s>...\n", device);
	if (ioctl (fd, BIODASDRLSE)) {
		error_print ("Could not release device <%s>", device);
		close (fd);
		return -1;
	}

	printf ("Done.\n");
	close (fd);
	return 0;
}


/*
 * Unconditional reserve the device.
 * This means to reserve the device even if it was already reserved.
 * The current reserve is broken (steal lock).
 */
int 
disk_slock (char* device)
{
	int fd;

	/* Open device file */
	fd = open (device, O_RDWR);
	if (fd == -1) {
		error_print ("<%s> - %s", device, strerror (errno));
		return -1;
	}

	/* Unconditional reserve device */
	printf ("Unconditional reserving device <%s>...\n", device);
	if (ioctl (fd, BIODASDSLCK)) {
		error_print ("Could not unconditional reserve device <%s>", 
			     device);
		close (fd);
		return -1;
	}

	printf ("Done.\n");
	close (fd);
	return 0;
}

int 
disk_profile_summary (dasd_profile_info_t dasd_profile_info)
{
	int shift, i, help;

        /* prevent counter 'overflow' on output */
	for (shift = 0, help = dasd_profile_info.dasd_io_reqs;
	     help > 9999999; help = help >> 1, shift++) ;

	/* print the profile info */
	printf ("\n%d dasd I/O requests\n", dasd_profile_info.dasd_io_reqs);
	printf ("with %d sectors(512B each)\n\n", 
		dasd_profile_info.dasd_io_sects);

	printf ("   __<4    ___8    __16    __32    __64 "
		"   _128    _256    _512    __1k    __2k "
		"   __4k    __8k    _16k    _32k    _64k "
		"   128k\n");
	
	printf ("   _256    _512    __1M    __2M    __4M "
		"   __8M    _16M    _32M    _64M    128M "
		"   256M    512M    __1G    __2G    __4G "
		"   _>4G\n");

	printf ("Histogram of sizes (512B secs)\n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ", dasd_profile_info.dasd_io_secs[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_secs[i] >> shift);
	}
	printf ("\n");

	printf ("Histogram of I/O times (microseconds)\n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_times[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_times[i] >> shift);
	}
	printf ("\n");

	printf ("Histogram of I/O times per sector\n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_timps[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_timps[i] >> shift);
	}
	printf ("\n");

	printf ("Histogram of I/O time till ssch\n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time1[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time1[i] >> shift);
	}
	printf ("\n");

	printf ("Histogram of I/O time between ssch and irq\n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time2[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time2[i] >> shift);
	}
	printf ("\n");

	printf ("Histogram of I/O time between ssch and irq per "
		"sector\n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time2ps[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time2ps[i] >> shift);
	}
	printf ("\n");

	printf ("Histogram of I/O time between irq and end\n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time3[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_time3[i] >> shift);
	}
	printf ("\n");

	printf ("# of req in chanq at enqueuing (1..32) \n");
	for (i = 0; i < 16; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_nr_req[i] >> shift);
	}
	printf ("\n");
	for (; i < 32; i++) {
		printf ("%7d ",	dasd_profile_info.dasd_io_nr_req[i] >> shift);
	}
	printf ("\n");
	
	return 0;
}


int
disk_profile_item (dasd_profile_info_t dasd_profile_info, char *prof_item)
{
	int i;

	/* Check for given profile item*/
	switch (check_prof_item (prof_item)) {
	case prof_reqs:
		printf ("%d", dasd_profile_info.dasd_io_reqs);
		break;
	case prof_sects:
		printf ("%d", dasd_profile_info.dasd_io_sects);
		break;
	case prof_sizes:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_secs[i]);
		}
		break;
	case prof_total:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_times[i]);
		}
		break;
	case prof_totsect:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_timps[i]);
		}
		break;
	case prof_start:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_time1[i]);
		}
		break;
	case prof_irq:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_time2[i]);
		}
		break;
	case prof_irqsect:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_time2ps[i]);
		}
		break;
	case prof_end:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_time3[i]);
		}
		break;
	case prof_queue:
		for (i = 0; i < 32; i++) {
			printf ("%10d|", dasd_profile_info.dasd_io_nr_req[i]);
		}
		break;
	}

	printf ("\n");
	return 0;
}

/*
 * Get and print the profiling info of the device.
 */
int 
disk_profile (char* device, char* prof_item)
{
	int fd, rc;
	dasd_profile_info_t dasd_profile_info;

	/* Open device file */
	fd = open (device, O_RDWR);
	if (fd == -1) {
		error_print ("<%s> - %s", device, strerror (errno));
		return -1;
	}

	/* Get the profile info */
	if (ioctl (fd, BIODASDPRRD, &dasd_profile_info)) {
		error_print ("Could not get profile info for device <%s>.", 
			     device);
		close (fd);
		return -1;
	}

	/* check for valid profile info */
	if (!dasd_profile_info.dasd_io_reqs) {
		error_print ("Profiling (on device <%s>) seems to be inactive.",
			     device); 
		close (fd);
		return -1;
	}
	
	/* Check for profile item or summary */
	if (!prof_item) {
		rc = disk_profile_summary (dasd_profile_info);
	} else {
		rc = disk_profile_item (dasd_profile_info, prof_item);
	}
	
	close (fd);
	return rc;
}


/*
 * Reset the profiling counters of the device.
 */
int 
disk_reset_prof (char* device)
{
	int fd;

	/* Open device file */
	fd = open (device, O_RDWR);
	if (fd == -1) {
		error_print ("<%s> - %s", device, strerror (errno));
		return -1;
	}

	/* reset profile info */
	printf ("Resetting profile info for device <%s>...\n", device);
	if (ioctl (fd, BIODASDPRRST)) {
		error_print ("Could not reset profile info for device <%s>",
			     device);
		close (fd);
		return -1;
	}

	printf ("Done.\n");
	close (fd);
	return 0;
}

