/*
 * ATDISK -- PC Tech Journal AT Hard Disk Performance Test
 *
 * Version 1.00
 * Last modified 05/23/86
 * Copyright (c) 1986, Ziff Communications Company
 * Program by: Paul Pierce, Ted Forgeron, Steven Armbrust
 *
 * Uses BIOS INT 13H to measure low-level performance.
 * Uses DOS  INT 21H to measure high-level performance.
 */

#include "dos.h"

/* System clock tick rate in ticks/second */
#define TICK_RATE 18.20648

/* Number of trials for each test */
#define TRIALS 1000

struct diskparam {
        int     sectors;
        int     heads;
        int     cylinders;
        int     drives;
} diskparam[10];

char buffer[50 * 512];

/* Number of files for DOS file I/O test */
#define FILES   10
/* Size of each file for DOS file I/O test */
#define FILESIZE 20000
/* Size of each read/write request for DOS file I/O test */
#define FILEBUF 2000

char filename[] = "C:ATDISK.~~0";

unsigned long tick();
unsigned rand();

double drand()
{

        return (double)rand() / 32767;
}

#define READ_SECTORS    2
#define GET_PARAM       8
#define SEEK            12

main(argc, argv)
int argc;
char *argv[];
{
        register i, j, k;
        unsigned long start;
        unsigned long total;
        unsigned trials;
        int      f;

        trials = TRIALS;

        /*
         * Get the disk drive parameters for each hard disk
         * in the system.
         */

        disk_io(GET_PARAM, 0x80, &diskparam[0], 0, 0, 0, 0);
        for (i = 1; i < diskparam[0].drives; i++) {
                disk_io(GET_PARAM, 0x80 + i, &diskparam[i],
                        0, 0, 0, 0);
        }

        /*
         * Display the disk characteristics.
         */

        printf("\nATDISK -- PC Tech Journal AT Hard Disk ");
        printf("Performance Test");
        printf("\nVersion 1.00, Copyright (c) 1986 Ziff ");
        printf("Communications Co.");
        printf("\n\nHARD DISK CHARACTERISTICS\n");
        printf("Drive      Sectors  Heads  Cylinders  Total ");
        printf("space\n");
        for (i = 0; i < diskparam[0].drives; i++) {
                total = diskparam[i].sectors * diskparam[i].heads;
                total *= diskparam[i].cylinders;
                total *= 512;
                printf("%3d       %5d   %5d     %5d     %10lu\n",
                                i,
                                diskparam[i].sectors,
                                diskparam[i].heads,
                                diskparam[i].cylinders,
                                total);
        }

        printf("\nBIOS TEST OF DISK HARDWARE PERFORMANCE\n");

        /*
         * Seek back and forth between tracks 0 and 1.
         */

        for (i = 0; i < diskparam[0].drives; i++) {
                disk_io(SEEK, 0x80 + i, buffer, 1, 0, 0, 0);
                start = tick();
                for (j = 0; j < trials/2; j++) {
                        disk_io(SEEK, 0x80 + i, buffer, 1, 0, 0,
                                1);
                        disk_io(SEEK, 0x80 + i, buffer, 1, 0, 0,
                                0);
                }
                total = tick() - start;
                printf("Drive %d track-track seek time: %4.1f ms",
                        i, (double)total / ((double)trials) *
                           1000.0 / TICK_RATE);
                printf("\n");
        }

        /*
         * Seek to random tracks.
         */

        for (i = 0; i < diskparam[0].drives; i++) {
                disk_io(SEEK, 0x80 + i, buffer, 1, 0, 0, 0);
                start = tick();
                for (j = 0; j < trials; j++) {
                        disk_io(SEEK, 0x80 + i, buffer, 1, 0, 0,
                                (short)(drand()*
                                (diskparam[i].cylinders - 1)));
                }
                total = tick() - start;
                printf("Drive %d average seek time: %4.1f ms\n",
                        i, (double)total / ((double)trials) *
                           1000.0 / TICK_RATE);
        }

        /*
         * Measure transfer rate by repeatedly reading sectors
         */

        for (i = 0; i < diskparam[0].drives; i++) {
                disk_io(READ_SECTORS, 0x80 + i, buffer, 1, 0, 0,
                        0);
                start = tick();
                for (j = 0; j < trials; j++) {
                        disk_io(READ_SECTORS, 0x80 + i, buffer,
                                diskparam[i].sectors, 0, 0, 0);
                }
                total = tick() - start;
                printf("Drive %d effective transfer rate: ", i);
                printf("%6.1f Kbytes/second\n",
                        (((double)diskparam[i].sectors)*512.0) *
                        (double)trials / ((double)total) /
                        1024 * TICK_RATE);
        }

        /*
         * This section indicates overall system disk performance
         * by using DOS calls to manipulate files.  It is only
         * executed if the -d command line switch is used since
         * the test writes to disk.
         */

        if (argc == 1) {
                printf("\nType \"atdisk -d\" to run the DOS ");
                printf("TEST OF DISK SYSTEM PERFORMANCE.\n");
                printf("This test creates, writes, reads and ");
                printf("deletes ten files named \n");
                printf("atdisk.~~0 through atdisk.~~9.\n");
        }
        if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'd'){
                printf("\nDOS TEST OF DISK SYSTEM PERFORMANCE\n");
                printf("This test creates, writes, reads and ");
                printf("deletes ten 20,000 byte files.\n");
                printf("Best results are achieved with blank, ");
                printf("freshly-formatted disks.\n");
                for (k = 0; k < diskparam[0].drives; k++) {
                        filename[0] = 'C' + k;
                        start = tick();

                        /*
                         * First create each file, write to it,
                         * and close it.
                         */

                        for (i = 0; i < FILES; i++) {
                                filename[11] = '0' + i;
                                f = dos_create(filename, 0);
                                for (j = 0; j < FILESIZE/FILEBUF;
                                     j++) {
                                        dos_write(f, buffer,
                                                  FILEBUF);
                                }
                                dos_close(f);
                        }

                        /*
                         * Now reopen each file, read it, and
                         * then delete it.
                         */

                        for (i = 0; i < FILES; i++) {
                                filename[11] = '0' + i;
                                f = dos_open(filename, 0);
                                for (j = 0; j < FILESIZE/FILEBUF;
                                     j++) {
                                        dos_read(f, buffer,
                                                 FILEBUF);
                                }
                                dos_close(f);
                                dos_delete(filename);
                        }

                        total = tick() - start;
                        printf("Drive %c:",'C' + k);
                        printf("%6.1f seconds\n",
                                ((double)total) / TICK_RATE);
                }
        }
}

/*
 * DOS file calls
 *
 * Each of these routines sets up the regs structure as
 * specified in the DOS technical reference and then
 * calls DOS using the intdos() function.
 */


/*
 * DOS call 3C - create file
 */

int
dos_create(file, attribute)
        char    *file;
        int     attribute;
{
        union REGS regs;

        regs.x.dx =(int)file;
        regs.h.ah = 0x3C;
        regs.x.cx = attribute;
        intdos(&regs, &regs);
        if (regs.x.cflag) {
                printf("DOS error %d in create\n",
                        regs.x.ax);
                exit(1);
        }
        return regs.x.ax;
}

/*
 * DOS call 3D - open file
 */

int
dos_open(file, mode)
        char    *file;
        int     mode;
{
        union REGS regs;

        regs.x.dx = (int)file;
        regs.h.ah = 0x3D;
        regs.h.al = mode;
        intdos(&regs, &regs);
        if (regs.x.cflag) {
                printf("DOS error %d in open\n",
                        regs.x.ax);
                exit(1);
        }
        return regs.x.ax;
}

/*
 * DOS call 3E - close file
 */

dos_close(handle)
        int     handle;
{
        union REGS regs;

        regs.x.bx = handle;
        regs.h.ah = 0x3E;
        intdos(&regs, &regs);
        if (regs.x.cflag) {
                printf("DOS error %d in close\n",
                        regs.x.ax);
                exit(1);
        }
}

/*
 * DOS call 3F - read file
 */

int
dos_read(handle, buffer, count)
        int     handle;
        char    *buffer;
        int     count;
{
        union REGS regs;

        regs.x.bx = handle;
        regs.x.cx = count;
        regs.x.dx = (int)buffer;
        regs.h.ah = 0x3F;
        intdos(&regs, &regs);
        if (regs.x.cflag) {
                printf("DOS error %d in read\n",
                        regs.x.ax);
                exit(1);
        }
        return regs.x.ax;
}

/*
 * DOS call 40 - write file
 */

int
dos_write(handle, buffer, count)
        int     handle;
        char    *buffer;
        int     count;
{
        union REGS regs;

        regs.x.bx = handle;
        regs.x.cx = count;
        regs.x.dx = (int)buffer;
        regs.h.ah = 0x40;
        intdos(&regs, &regs);
        if (regs.x.cflag) {
                printf("DOS error %d in write\n",
                        regs.x.ax);
                exit(1);
        }
        return regs.x.ax;
}

/*
 * DOS call 41 - delete file
 */

dos_delete(file)
        char    *file;
{
        union REGS regs;

        regs.x.dx = (int)file;
        regs.h.ah = 0x41;
        intdos(&regs, &regs);
        if (regs.x.cflag) {
                printf("DOS error %d in delete\n",
                        regs.x.ax);
                exit(1);
        }
}
