/*
* DDI - Device information program
*
* Written By:	Steve Rodgers
* Conception:	5-4-86
*
* Copyright (C) 1987 Steve Rodgers
*
*
* Functional Description
* ----------------------
*
* This program displays information about installable device drivers
* loaded under DOS 2.0 or later. If the command is typed without any
* arguments, a summary will be printed which shows the order of the devices
* in the chain. If a device name is typed following the command, detailed
* information pertinent to that device will be displayed. This program
* works with either character or block devices as arguments.
*
*
* Revision History
* ----------------
*
* First Release Version 0.1 released 5-7-86
* Second Release Version 0.2 released 3-12-87
* Third Release Version 1.0 released 2-18-88
*
* Compilation instructions
* ------------------------
*
* Use the Microsoft C compiler version 3.00 or later.
* Compile with the -Zp switch.
* 
* Dependencies
* ------------
*
* This module requires the following include files:
*
* stdio.h 
* dos.h	
* string.h
*
*
* Linking instructions
* --------------------
*
* Use the Microsoft linker version 3.00 or later.
*
* LINK COMMAND LINE : link di;
* 
*
*
*/

#include <stdio.h>
#include <dos.h>
#include <string.h>

/*
* Device attribute definitions
*/

#define	CHARACTER_DEVICE	0x8000
#define IOCTL_SUPPORT		0x4000
#define NON_IBM_FORMAT		0x2000
#define OPEN_CLOSE_SUPPORT	0x800
#define	IS_CLOCK		8
#define IS_NUL			4
#define IS_STDIN		2
#define IS_STDOUT		1

/*
* Structure definitions
*
*
* Device header
*/

struct DEV_HDR
	{
	struct DEV_HDR far	*next_hdr ;	/* pointer next device header */
	unsigned int		attribute ;	/* attribute bits */
	unsigned int		strategy ;	/* strategy offset */
	unsigned int		interruptx ;	/* interrupt offset */
	unsigned char		name[8] ;	/* device name */
	} ;

/*
* Disk Parameter Block - Returned by DOS function 32H
*/

struct DPB
	{
	unsigned char		ad ;		/* current assigned disk */
	unsigned char		alt_ad ;	/* alternate assigned disk */
	unsigned int		bps ;		/* bytes per sector */
	unsigned char		last_sic ;	/* max sector addr in a cluster */
	unsigned char		last_head ;	/* max useable head address */
	unsigned int		rs ;		/* total reserved sectors */
	unsigned char		cf ;		/* copies of the FAT */
	unsigned int		d ;		/* max root directory entries */
	unsigned int		fus ;		/* first useable sector */
	unsigned int		tccplusone ;	/* total clusters available + 1 */
	unsigned char		spf ;		/* sectors per FAT */
	unsigned int		fds ;		/* first directory sector */
	struct DEV_HDR far	*dda ;		/* device driver address */
	unsigned char		md ;		/* media discriptor */
	struct DISK_DPB far	*next_dpb ;	/* address of next dpb in chain */
	unsigned int		cwd_cluster ; 	/* cluster of CWD (DOS 2.x only) */
	unsigned char		cwd_string[64] ; /* CWD string (DOS 2.x only) */
	} ;

/*
* Function declarations
*/

struct DPB far *disk_dpb() ;
struct DEV_HDR far *device_chain() ;

/*
* Static Declarations
*/

union REGS ir,or ;
struct SREGS sr ;

int is_disk = 0 ;

/*
* Attribute bit table
*/

int attr_bit_masks[] = {CHARACTER_DEVICE,IOCTL_SUPPORT,NON_IBM_FORMAT,
			OPEN_CLOSE_SUPPORT,IS_CLOCK,IS_NUL,IS_STDIN,IS_STDOUT};

/*
* Global pointer variables 
*/

struct DEV_HDR far *current_device, far *previous_device ;
struct DPB far *current_dpb ;

/*
* Global device type counters
*/

int block_drivers = 0,character_drivers = 0 ;

/*
* Function: main()
*/

main(argc,argv)
int argc ;
char *argv[] ;
{
	int i ;
	char *device_name ;

	printf("\nDDI   -              Device Driver Information Program\n") ;
	printf("Version 1.0 (2-18-88) Copyright (C) 1988 Steve Rodgers\n\n") ;

	if(argc < 2) /* if no argument, print the device list */ 
	{
		list_devices() ;
		exit(0) ;
	}

	if((device_name = strupr(strdup(argv[1]))) == NULL)
		abort() ;
	print_single_device(device_name) ;
}

/*
* Function: print_single_device() 
*
* Print information for a single device.
*/

print_single_device(device_name)
char device_name[] ;
{ 
	int i ;

/*
*
* Function: get_device() ;
*
* Get pointer for a specific device.
* If it doesn't exist print an error message.
*/

	get_device(device_name) ;
	if(current_device == NULL)
	{
		printf("No such device: %s\n",device_name) ;
		exit(1) ;
	}

/*
* Print the device address, type, and name.
*/

	print_device_columns() ;
	print_device_info(current_device) ;

/*
* Print the detailed information for the device
*/

	printf("\n") ;
	printf("                          A T T R I B U T E S\n") ;
	printf("------------------------------------------------------------------------\n") ;
	printf("| CHAR   | IOCTL  | NONIBM | OPNCLS | STDCLK | NULDEV | STDOUT | STDIN |\n") ;
	for(i = 0 ; i < 8 ; i++)
	{
		printf("   ") ;
		if((attr_bit_masks[i] & current_device->attribute))
			printf("yes   ") ;
		else
			printf("no    ") ;
	}
	printf("\n\n") ;
	printf("Strategy entry point\t\t: %04X:%04X\n",
			FP_SEG(current_device),
			current_device->strategy) ;
	
	printf("Interrupt entry point\t\t: %04X:%04X\n",
			FP_SEG(current_device),
			current_device->interruptx) ;

/*
* Return to DOS
*/

	exit(0) ;
}

/*
* Function: list_devices()
*
* Print a summary of all devices in the chain.
*/

list_devices()
{

/*
* Get the head of the device list.
* If this isn't possible, print an error message and exit
*/

	if((current_device = device_chain()) == NULL)
		no_device_chain() ;

/*
* Print the column information
*/

	print_device_columns() ;

/*
* Follow the chain until a -1 is reached
*/

	while(FP_OFF(current_device) != -1)
	{
		print_device_info(current_device) ;
		current_device = current_device->next_hdr ;
	}
	printf("\nSystem has %d character drivers, and %d block drivers\n",
		character_drivers,block_drivers) ;
}

/*
* Function: get_device()
*
* Find character or block device driver.
* Fill in current device pointer, previous device pointer, and
* current DPB if block device.
*
*/

get_device(d)
char d[] ;
{
	int i ;
	char device_string[9] ;

	current_device = NULL ;
	current_dpb = NULL ;
	if(strlen(d) > 8)
		return ;

/*
* If there is a colon in the second character position, user
* is requesting information about a block device.
*/

	if(d[1] != ':')
	{ 

/*
* The name specified is probably a character device.
* If a colon follows the device name, delete it from the string. 
*/

		if(d[strlen(d) - 1] == ':') /* kill colon at end of device name */
			d[strlen(d) - 1] = 0 ;
/*
* Attempt to get the pointer to the first device in the chain.
* if it comes back NULL, the user probably did not install the
* trace device driver.
*/

		if((current_device = device_chain()) == NULL)
			no_device_chain() ;  /* no device chain available */

/*
* Trace the device chain until device name matches a
* device in the chain
*/

		while(FP_OFF(current_device) != -1)
		{
			for(i = 0 ; i < 8 ; i++)
				device_string[i] = current_device->name[i] ;
			device_string[8] = 0 ;
			if(!strncmp(device_string,d,strlen(d)))
				if((strlen(d) == 8) ||
					(current_device->name[strlen(d)] == ' '))
					return ;
			previous_device = current_device ;
			current_device = current_device->next_hdr ;
		}

/*
* If no match, set the current device to NULL and return
*/

		current_device = NULL ;
		return ;
	}

/*
* A colon was found in the second character position of the device
* name, check for a valid block device.
*/

	else
		is_disk = 1 ; /* set a flag to signify a disk device */

	if(strlen(d) > 2)	/* length must be less than or equal to 2 */
		return ;

/*
* Get the disk DPB for the drive specified.
* If the disk doesn't exist, the current_dpb pointer will be
* set to NULL, print a message and exit if this is the case.
*/

	if((current_dpb = disk_dpb(d[0] - 0x40)) == NULL)
		no_disk_dpb() ;
	current_device = current_dpb->dda ;
	previous_device = NULL ;
	return ;
}		

/*
* Function: print_device_columns()
*
* This function prints the headings and columns when listing
* all device drivers.
*/

print_device_columns()
{
	printf("Base Address\t\t\tType\t\t\tName/Units\n") ;
	printf("------------\t\t\t----\t\t\t----------\n") ;
}

/*
* Function: print_device_info()
*
* This function prints detailed information for a specific device.
*/

print_device_info(device)
struct DEV_HDR far *device ;
{
	char device_string[9] ;
	int i ;
	printf("%04X:%04X\t\t\t",FP_SEG(device),FP_OFF(device)) ; /* print base address */
	if(device->attribute & CHARACTER_DEVICE)
	{
		++character_drivers ;
		for(i = 0 ; i < 8 ; i++)
			device_string[i] = device->name[i] ; /* copy it into local data segment */
		device_string[8] = 0 ;
		printf("Char\t\t\t%s\n",device_string) ; /* print "Char" and name */
	}
	else
	{
		++block_drivers ;
		printf("Block\t\t\t%u units\n",(int) device->name[0]) ;/* block dev.*/
	}
}

/*
*
* Function: disk_dpb()
*
* Return the address of the Disk Parameter Block (DPB) for the
* drive specified. This DOS function is undocumented in current
* DOS documentation. 
*/

struct DPB far *disk_dpb(drive)
int drive ;
{
	char far *dsk_dpb ;

	ir.x.ax = 0x3200 ; /* Function call 32H */
	ir.h.dl = drive ;
	intdosx(&ir,&or,&sr) ;
	if(++or.h.al == 0)
		return (char far *) NULL ;
	FP_SEG(dsk_dpb) = sr.ds ;
	FP_OFF(dsk_dpb) = or.x.bx ;
	return (struct DPB far *) dsk_dpb ;
}

/*
* Function: device_chain()
*
* Return the address of the first device in the device
* chain.
*/


struct DEV_HDR far *device_chain()
{
	struct DEV_HDR far *dev_chain ;
	auto char far *search_ptr = (char far *) 0x00600000 ; /* start search at 0060:0000 */
	static char *nuldev = "NUL     " ;
	unsigned int x,i ;

	for(x = 0, i = 0 ; x < 65535 ; x++)
	{
		if(*search_ptr == *nuldev)    /* if first character matches, check others*/
		{
			for(i = 0 ; i <  8  ; i++)
			{
				if(*(search_ptr + i) != nuldev[i])
					break ;
			}
			if(i == 8)      /* if i = 8 then the NUL device has been found */
				break ;
		}
		search_ptr++ ;
	}
	if(x == 0)
		return NULL ;

	search_ptr -=  10 ; /* get to device header  */
	return (struct DEV_HDR far *) search_ptr ;
}

/*
*
* Function: no_device_chain()
*
* Print message "Can't find device chain" and exit
*/

no_device_chain()
{
	fprintf(stderr,"Can't find device chain\n") ;
	exit(1) ;
}

/*
*
* Function: no_disk_dpb()
*
* Print  message "Can't get DPB - Possible invalid drive\n" and exit
*/

no_disk_dpb()
{
	fprintf(stderr,"Can't get DPB - Possible invalid drive\n") ;
	exit(1) ;
}
