Source to bsd/dev/vol.c


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @[email protected]
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @[email protected]
 */

/*	@(#)vol.c	2.0	03/19/90	(c) 1990 NeXT	*/

/* 
 * vol.c - volume insertion device
 *
 * HISTORY
 * 03-19-90	Doug Mitchell at NeXT
 *	Created.
 */ 

#import <sys/types.h>
#import <sys/errno.h>
#import <sys/ioctl.h>
#import <sys/systm.h>
#import <sys/disktab.h>
#import <kern/kern_port.h>
#import <kern/thread.h> 
#import <kern/lock.h>
#import <kernserv/insertmsg.h>
#import <bsd/dev/disk.h>
#import <kernserv/prototypes.h>
#import <bsd/dev/voldev.h>
#import <kern/queue.h>
#import <kernserv/kern_notify.h>
#import <mach/notify.h>
#if 	hppa
# import <bsd/dev/hppa/sdvol.h>
#endif	hppa
#import "driverkit.h"

/* #define VOL_DEBUG	1	/* */

#ifdef	VOL_DEBUG
#define vol_debug(x)	printf x
#else	VOL_DEBUG
#define vol_debug(x)
#endif	VOL_DEBUG

/*
 * Struct used for queueing notifications prior to first DKIOCMNOTIFY
 */
struct vol_notify_entry {
	queue_chain_t		link;
	int 			dev_desc;	/* IND_DD_DEV, IND_DD_PORT */
	dev_t 			block_dev;	/* for IND_DD_DEV */
	dev_t 			raw_dev;	/* ditto */
	kern_port_t 		dev_port;	/* for IND_DD_PORT */
   	int 			vol_state;	/* IND_VS_LABEL, etc. */
	char			form_type[FORM_TYPE_LENGTH];
	char 			dev_str[OID_DEVSTR_LEN];
	int 			flags;
};

typedef struct vol_notify_entry *vne_t;

/*
 * Struct for maintaining state of panel requests
 */

struct vol_panel_entry {
	queue_chain_t		link;
	int			tag;
	vpt_func		fnc;		/* to be called upon receipt of
						 * vol_panel_resp message */ 	
	void			*param;
};

typedef struct vol_panel_entry *vpe_t;


/*
 * Local functions.
 */
static kern_return_t vol_notify_com(int in_dev_desc,	
	dev_t block_dev, 
	dev_t raw_dev,
	kern_port_t dev_port, 
   	int vol_state,
	const char *form_type,
	const char *dev_str,
	int flags);
static void vol_thread();
static vpe_t vol_panel_get_entry(int tag);
static void vol_port_death(notification_t *nmsg);

/*
 * Prototypes for kernel functions.
 */
msg_return_t msg_send_from_kernel(msg_header_t *msgptr, msg_option_t option, 
	msg_timeout_t tout);

int volopen(dev_t dev);
int volclose(dev_t dev);
int volioctl(dev_t dev, 
	int cmd, 
	caddr_t data, 
	int flag);

/*
 * Globals.
 */
static char vol_dev_open;
static char vol_dev_init_flag;
static kern_port_t vol_check_port=PORT_NULL;	
					/* port to which disk insertion 
					 * messages are sent; usually allocated 
					 * by autodiskmount. */
kern_port_t panel_req_port=PORT_NULL;
					/* port to which Panel Requests are
				 	 * sent. */
static kern_port_t vol_local_port=PORT_NULL;	
					/* port on which vol_thread listens. */
static port_t	vol_receive_port = PORT_NULL;
static queue_head_t vol_notify_q = {&vol_notify_q, &vol_notify_q};
					/* hold notifications prior to
					 * first DKIOCMNOTIFY */
static lock_data_t vol_notify_lock;	/* protects vol_notify_q */
static queue_head_t vol_panel_q = {&vol_panel_q, &vol_panel_q};
static int vol_panel_tag;
static vol_thread_alive=0;
#if 	DRIVERKIT
static int manual_poll;
#endif	DRIVERKIT

/*
 * message templates.
 */
static struct insert_notify insert_notify_temp = { 
	{					/* in_header */
		0,				/* msg_unused */
		FALSE,				/* msg_simple */
		0,				/* msg_size */
		MSG_TYPE_NORMAL, 		/* msg_type */
		PORT_NULL, 			/* msg_local_port */
		PORT_NULL, 			/* msg_remote_port */
		VOL_CHECK_MSG_ID 		/* msg_id */
	},
	{					/* in_vd_type */
		MSG_TYPE_INTEGER_32, 		/* msg_type_name */
		32, 				/* msg_type_size */
		2, 				/* msg_type_number */
		TRUE, 				/* msg_type_inline */
		FALSE, 				/* msg_type_longform */
		FALSE, 				/* msg_type_deallocate */
	},
	0,	 				/* in_vol_state */
	0, 					/* in_dev_desc */
	{					/* in_flags_type */
		MSG_TYPE_INTEGER_32, 		/* msg_type_name */
		32, 				/* msg_type_size */
		1, 				/* msg_type_number */
		TRUE, 				/* msg_type_inline */
		FALSE, 				/* msg_type_longform */
		FALSE, 				/* msg_type_deallocate */
	},
	0,	 				/* in_flags */
	{					/* in_form_type */
		MSG_TYPE_CHAR,  		/* msg_type_name */
		8, 				/* msg_type_size */
		FORM_TYPE_LENGTH,  		/* msg_type_number */
		TRUE, 				/* msg_type_inline */
		FALSE, 				/* msg_type_longform */
		FALSE, 				/* msg_type_deallocate */
	},
	NULL					/* in_form_type */
};

static struct vol_panel_req vol_panel_req_temp = {
	{					/* pr_header */
		0,				/* msg_unused */
		TRUE, 				/* msg_simple */
		sizeof(struct vol_panel_req),	/* msg_size */
		MSG_TYPE_NORMAL,  		/* msg_type */
		PORT_NULL, 			/* msg_local_port */
		PORT_NULL, 			/* msg_remote_port */
		VOL_PANEL_REQ 			/* msg_id */
	},
	{					/* pr_int_desc */
		MSG_TYPE_INTEGER_32,  		/* msg_type_name */
		32, 				/* msg_type_size */
		7, 				/* msg_type_number */
		TRUE, 				/* msg_type_inline */
		FALSE, 				/* msg_type_longform */
		FALSE, 				/* msg_type_deallocate */
	},
	0, 					/* pr_panel_type */
	0, 			 		/* pr_resp_type */
	0, 			 		/* pr_tag */
	0,	 				/* p1 */
	0, 					/* p2 */
	0, 					/* p3 */
	0, 					/* p4 */
	{					/* pr_string_desc */
		MSG_TYPE_CHAR,  		/* msg_type_name */
		8, 				/* msg_type_size */
		2 * VP_STRING_LEN, 		/* msg_type_number */
		TRUE, 				/* msg_type_inline */
		FALSE, 				/* msg_type_longform */
		FALSE 				/* msg_type_deallocate */
	},
	NULL,					/* string1 */
	NULL					/* string2 */
};

static struct vol_panel_cancel vol_panel_cancel_temp = {
	{					/* pc_header */
		0,				/* msg_unused */
		TRUE, 				/* msg_simple */
		sizeof(struct vol_panel_cancel),/* msg_size */
		MSG_TYPE_NORMAL, 		/* msg_type */
		PORT_NULL, 			/* msg_local_port */
		PORT_NULL,  			/* msg_remote_port */
		VOL_PANEL_CANCEL 		/* msg_id */
	},
	{					/* pc_int_desc */
		MSG_TYPE_INTEGER_32,  		/* msg_type_name */
		32, 				/* msg_type_size */
		1, 				/* msg_type_number */
		TRUE, 				/* msg_type_inline */
		FALSE,				/* msg_type_longform */
		FALSE 				/* msg_type_deallocate */
	},
	0					/* pc_tag */
};

int volopen(dev_t dev)
{
	if(vol_dev_open)
		return(EBUSY);
	else {
		vol_dev_open = 1;
		return(0);
	}
}

int volclose(dev_t dev)
{
	vol_dev_open = 0;
	return(0);
}

int volioctl(dev_t dev, 
	int cmd, 
	caddr_t data, 
	int flag)
{
	int rtn=0;
	port_t remote_port;
	dev_t mydev;
	
	switch (cmd) {
	    case DKIOCMNOTIFY:
	    	/*
		 * Send disk insert notification to port specified in *data.
		 */
		vol_debug(("volioctl: registering NOTIFY port\n"));
		remote_port = *(port_t *)data;
		if(get_kern_port(current_task(), remote_port, &vol_check_port)) 
			rtn = EINVAL;
		else {
			/* 
			 * Send notification for each entry in vol_notify_q.
			 */
			vne_t vnep, vne_next_p;
			
			lock_write(&vol_notify_lock);
			vnep = (vne_t)queue_first(&vol_notify_q);
			while(!queue_end(&vol_notify_q, (queue_entry_t)vnep)) {
				vne_next_p = (vne_t)vnep->link.next;
				queue_remove(&vol_notify_q,
					vnep,
					vne_t,
					link); 
				if(vnep->block_dev != rootdev) {
					/*
					 * skip notification for root device 
					 */
					vol_notify_com(vnep->dev_desc,	
						vnep->block_dev, 
						vnep->raw_dev,
						vnep->dev_port, 
						vnep->vol_state,
						vnep->form_type,
						vnep->dev_str,
						vnep->flags);
				}
				kfree(vnep, sizeof(struct vol_notify_entry));
				vnep = vne_next_p;
			}
			lock_done(&vol_notify_lock);
		}
		/*
		 * Register this port so we'll know when it dies.
		 */
		if(rtn == 0) 
			port_request_notification(vol_check_port, 
				vol_local_port);
		break;

	    case DKIOCPANELPRT:
	    	/*
		 * register port to which we send Panel request messages.
		 */
		vol_debug(("volioctl: registering PANEL port\n"));
		remote_port = *(port_t *)data;
		if(get_kern_port(current_task(), remote_port, &panel_req_port)) 
			rtn = EINVAL;
		/*
		 * Register this port so we'll know when it dies.
		 */
		port_request_notification(panel_req_port, vol_local_port);
		break;
		
#if 	DRIVERKIT
	    case DKIOCCHECKINSERT:
#if 0
#if	hppa
		sd_check_disks(SD_CHECK_DISKS);
#endif	hppa
#endif 0
		manual_poll = 1;
		break;
#endif	DRIVERKIT

           case DKIOCCANCELAUTOMOUNT:
		mydev = *(dev_t *)data;
		vol_notify_cancel(mydev);

/* note: for hppa, if we go with the different vol driver behavior,
 * this function needs to be taken from the 3.2 HP kernel
 */
#if 0
#ifdef hppa
		sd_ignore_vol(mydev);
#endif
#endif

		break;

	    default:
	    	rtn = EINVAL;
		break;
	}
	return(rtn);
} /* volioctl() */

/*
 * Any driver may call these functions to notify appropriate user task of disk
 * insertion. If no port has been registered via DKIOCMNOTIFY, no action
 * is taken.
 */
 
kern_return_t vol_notify_dev(dev_t block_dev, 
	dev_t raw_dev,
	const char *form_type,
   	int vol_state,				/* IND_VS_LABEL, etc. */
	const char *dev_str,
	int flags)
{
	return(vol_notify_com(IND_DD_DEV,
		block_dev,
		raw_dev,
		0,
		vol_state,
		form_type,
		dev_str,
		flags));
}

#if	SUPPORT_PORT_DEVICE
	
kern_return_t vol_notify_port(kern_port_t dev_port, 
	char *const form_type,
   	int vol_state,				/* IND_VS_LABEL, etc. */
	int flags)
{
	return(vol_notify_com(IND_DD_PORT,
		0,
		0,
		dev_port,
		vol_state,
		form_type,
		"",
		flags));
}

#endif	SUPPORT_PORT_DEVICE

static kern_return_t vol_notify_com(int in_dev_desc,	
	dev_t block_dev, 
	dev_t raw_dev,
	kern_port_t dev_port, 
   	int vol_state,				/* IND_VS_LABEL, etc. */
	const char *form_type,
	const char *dev_str,
	int flags)
{
	struct insert_notify *notify_msg;
	struct of_insert_notify_dev *dev_msg;
	struct of_insert_notify_port *port_msg;
	int size;	
	kern_return_t krtn;
	
	vol_debug(("vol_notify_com: block_dev=0x%x vol_state=0x%x\n", 
		block_dev, vol_state));
	if(!vol_dev_init_flag) {
 		lock_init(&vol_notify_lock, TRUE);
		vol_dev_init_flag = TRUE;
	}
	if(vol_check_port == (kern_port_t)NULL) {
		vne_t vnep;
		
		/* 
		 * nobody has registered yet. Queue up this request.
		 */
		vnep = (vne_t)kalloc(sizeof(struct vol_notify_entry));
		vnep->dev_desc = in_dev_desc;
		vnep->block_dev = block_dev;
		vnep->raw_dev = raw_dev;
		vnep->dev_port = dev_port;
		vnep->vol_state = vol_state;
		vnep->flags = flags;
		strcpy(vnep->form_type, form_type);
		strcpy(vnep->dev_str, dev_str);
		lock_write(&vol_notify_lock);
		queue_enter(&vol_notify_q,
			vnep,
			struct vol_notify_entry *,
			link); 
		lock_done(&vol_notify_lock);
		return(KERN_SUCCESS);
	}

	switch(in_dev_desc) {
	    case IND_DD_DEV:
	    	size = sizeof(struct of_insert_notify_dev);
		break;
		
#if	SUPPORT_PORT_DEVICE
	    case IND_DD_PORT:
	    	size = sizeof(struct of_insert_notify_port);
#endif	SUPPORT_PORT_DEVICE

	    default:
	    	return(KERN_INVALID_ARGUMENT);
	}
	notify_msg = (struct insert_notify *)kalloc(size);
	*notify_msg = insert_notify_temp;
	notify_msg->in_header.msg_simple      = (in_dev_desc == IND_DD_PORT) ? 
					        FALSE : TRUE;
	notify_msg->in_header.msg_size        = size;
	notify_msg->in_header.msg_remote_port = (port_t)vol_check_port;
	notify_msg->in_vol_state              = vol_state;
	notify_msg->in_dev_desc               = in_dev_desc;
	notify_msg->in_flags		      = flags;
	strcpy(notify_msg->in_form_type, form_type);

	/*
	 * dev or port 
	 */
	switch(in_dev_desc) {
	    case IND_DD_DEV:
	    	dev_msg = (struct of_insert_notify_dev *)notify_msg;
		dev_msg->oid_dev_type.msg_type_name      = MSG_TYPE_INTEGER_32;
		dev_msg->oid_dev_type.msg_type_size       = 32;
		dev_msg->oid_dev_type.msg_type_number     = 2;
		dev_msg->oid_dev_type.msg_type_inline     = TRUE;
		dev_msg->oid_dev_type.msg_type_longform   = FALSE;
		dev_msg->oid_dev_type.msg_type_deallocate = FALSE;
		dev_msg->oid_bdev_t = block_dev;
		dev_msg->oid_cdev_t = raw_dev;
		dev_msg->oid_ds_type.msg_type_name       = MSG_TYPE_CHAR;
		dev_msg->oid_ds_type.msg_type_size       = 8;
		dev_msg->oid_ds_type.msg_type_number     = OID_DEVSTR_LEN;
		dev_msg->oid_ds_type.msg_type_inline     = TRUE;
		dev_msg->oid_ds_type.msg_type_longform   = FALSE;
		dev_msg->oid_ds_type.msg_type_deallocate = FALSE;
		strcpy(dev_msg->oid_dev_str, dev_str);
		break;
		
#if	SUPPORT_PORT_DEVICE

	    case IND_DD_PORT:
	    	port_msg = (struct of_insert_notify_port *)notify_msg;
		port_msg->oip_p_type.msg_type_name       = MSG_TYPE_PORT;
		port_msg->oip_p_type.msg_type_size       = 32;
		port_msg->oip_p_type.msg_type_number     = 1;
		port_msg->oip_p_type.msg_type_inline     = TRUE;
		port_msg->oip_p_type.msg_type_longform   = FALSE;
		port_msg->oip_p_type.msg_type_deallocate = FALSE;
		port_msg->oip_port = (port_t)dev_port;
		
#endif	SUPPORT_PORT_DEVICE
	    default:
	    	break;			// can't happen, avoid compiler
					//    warning

	}
	krtn = msg_send_from_kernel(&notify_msg->in_header,
		SEND_TIMEOUT,
		0);			/* don't block if queue full */
#ifdef	DEBUG
	if(krtn)
		printf("vol_check_notify: msg_send returned %d\n", krtn);
#endif 	DEBUG
	return(krtn);
} /* vol_check_notify */

/*
 * Cancel a (possibly) queued notify message. Normally called when a device
 * is mounted.
 */
void vol_notify_cancel(dev_t device)
{
	vne_t vnep, vne_next_p;
	
	/*
	 * Inserts are always registered for partition a; mounts can be
	 * on other partitions...
	 */
	device &= ~(NPART - 1);
	
	if(!vol_dev_init_flag) {
 		lock_init(&vol_notify_lock, TRUE);
		vol_dev_init_flag = TRUE;
	}

	lock_write(&vol_notify_lock);
	vnep = (vne_t)queue_first(&vol_notify_q);
	while(!queue_end(&vol_notify_q, (queue_entry_t)vnep)) {
		vne_next_p = (vne_t)vnep->link.next;
		if((vnep->block_dev == device) ||
		   (vnep->raw_dev == device)) {
			queue_remove(&vol_notify_q,
				vnep,
				vne_t,
				link); 
			kfree(vnep, sizeof(struct vol_notify_entry));
		}
		vnep = vne_next_p;
	}
	lock_done(&vol_notify_lock);
}

/*
 * Functions to request and cancel alert panels via WSM
 */
 
/*
 * Generic panel request.
 */
kern_return_t vol_panel_request(vpt_func fnc,
	int panel_type,				/* PR_PT_DISK_NUM, etc. */
	int response_type,			/* PR_RT_ACK, etc. */
	int p1,
	int p2,
	int p3,
	int p4,
	char *string1,
	char *string2,
	void *param,
	int *tag) {				/* RETURNED */
	
	kern_return_t krtn;
	vpe_t vpe;
	struct vol_panel_req *panel_msgp;
	
	vol_debug(("vol_panel_request: panel_type=%d\n", panel_type));
	if(panel_req_port == (kern_port_t)NULL) {
		char *disk_string;
		
		/*
		 * Nobody registered to deal with these requests yet. Punt
		 * and send notification to the console; no ack is possible.
		 *
		 * For some panels, this switch is meaningless (but harmless).
		 */
		switch(p2) {
		    case PR_DRIVE_FLOPPY:
			disk_string = "Floppy";
			break;
		    case PR_DRIVE_OPTICAL:
			disk_string = "Optical";
			break;
		    case PR_DRIVE_SCSI:
			disk_string = "SCSI";
			break;
		    default: 
			disk_string = "";
			break;
		}  
		switch(panel_type) {
		    case PR_PT_DISK_NUM:
		    	printf("Please Insert %s Disk %d in Drive %d\n", 
				disk_string, p1, p3);
			break;
		    case PR_PT_DISK_LABEL:
		    	printf("Please Insert %s Disk \'%s\' in Drive %d\n", 
				disk_string, string1, p3);
			break;
		    case PR_PT_DISK_NUM_W:
		    	printf("Wrong Disk: Please Insert %s Disk %d in Drive "
				"%d\n", disk_string, p1, p3);
			break;
		    case PR_PT_DISK_LABEL_W:
		    	printf("Wrong Disk: Please Insert %s Disk \'%s\' in "
				"Drive %d\n", disk_string, string1, p3);
			break;
		    case PR_PT_SWAPDEV_FULL:
		    	printf("***Swap Device Full***\n");
			break;
		    case PR_PT_FILESYS_FULL:
		    	printf("***File System %s Full***\n", string1);
			break;
	 	    case PR_RT_EJECT_REQ:
			printf("Please Eject %s Disk %d\n", disk_string, p3);
			break;
		    default:
		    	/* FIXME: what do we do here? */
			printf("vol_panel_request: bogus panel_type (%d)\n",
				panel_type);
			break;
		}
		return(0);
	}
	/*
	 * Build a panel request message.
	 */
	panel_msgp = (struct vol_panel_req *)kalloc(sizeof(*panel_msgp));
	*panel_msgp = vol_panel_req_temp;
	panel_msgp->pr_header.msg_local_port  	= (port_t)vol_local_port;
	panel_msgp->pr_header.msg_remote_port 	= (port_t)panel_req_port;
	panel_msgp->pr_panel_type 		= panel_type;
	panel_msgp->pr_resp_type 		= response_type;
	panel_msgp->pr_tag	 		= vol_panel_tag++;
	panel_msgp->pr_p1 			= p1;
	panel_msgp->pr_p2			= p2;
	panel_msgp->pr_p3			= p3;
	panel_msgp->pr_p4			= p4;
	if(strlen(string1) >= VP_STRING_LEN)
		string1[VP_STRING_LEN-1] = '\0';
	if(strlen(string2) >= VP_STRING_LEN)
		string2[VP_STRING_LEN-1] = '\0';
	strcpy(panel_msgp->pr_string1, string1);
	strcpy(panel_msgp->pr_string2, string2);
	/*
	 * send the message.
	 */
	krtn = msg_send_from_kernel(&panel_msgp->pr_header,
		SEND_TIMEOUT,
		0);			/* don't block if queue full */
#ifdef	DEBUG
	if(krtn)
		printf("vol_panel_request: msg_send returned %d\n", krtn);
#endif 	DEBUG
	if(krtn)
		return(krtn);
	*tag = panel_msgp->pr_tag;
	/*
	 * Enqueue this event on vol_panel_q if we expect an ack 
	 */
	if(response_type == PR_RT_NONE)
		return(KERN_SUCCESS);
	vpe = (vpe_t)kalloc(sizeof(struct vol_panel_entry));
	if(vpe == NULL)
		return(KERN_RESOURCE_SHORTAGE);
	vpe->tag = panel_msgp->pr_tag;
	vpe->fnc = fnc;
	vpe->param = param;
	queue_enter(&vol_panel_q, vpe, struct vol_panel_entry *, link);
	return(KERN_SUCCESS);
	
} /* vol_panel_request */

/*
 * remove an existing panel.
 */
kern_return_t vol_panel_remove(int tag) {
	struct vol_panel_cancel *msgp;
	kern_return_t krtn = KERN_SUCCESS;
	vpe_t vpe;
	
	vol_debug(("vol_panel_remove\n"));
	if(panel_req_port != (kern_port_t)NULL) {
		msgp = (struct vol_panel_cancel *)kalloc(sizeof(*msgp));
		*msgp = vol_panel_cancel_temp;
		msgp->pc_header.msg_local_port        = (port_t)vol_local_port;
		msgp->pc_header.msg_remote_port       = (port_t)panel_req_port;
		msgp->pc_tag 			      = tag;
		krtn = msg_send_from_kernel(&msgp->pc_header,
			SEND_TIMEOUT,
			0);			/* don't block if queue full */
		if(krtn)
			printf("vol_panel_remove: msg_send returned %d\n",
				krtn);
	}
	/*
	 * Remove entry in vol_panel_q for this panel.
	 */
	vpe = vol_panel_get_entry(tag);
	if(vpe) 
		kfree(vpe, sizeof(*vpe));
	else {
		/*
		 * This is not an error; it could happen in a race condition in 
		 * which this function were called just after we received a
		 * vol_panel_cancel message.
		 */
		vol_debug(("vol_panel_remove: panel resp, tag not found\n"));
	}
	return(krtn);
}

/*
 * Panel requests specifically for disk drivers.
 */
kern_return_t vol_panel_disk_num(vpt_func fnc,
	int volume_num,
	int drive_type,				/* PR_DRIVE_FLOPPY, etc. */
	int drive_num,
	void *param,
	boolean_t wrong_disk,
	int *tag) {				/* RETURNED */
	
	return(vol_panel_request(fnc,
		wrong_disk ? PR_PT_DISK_NUM_W : PR_PT_DISK_NUM,
		PR_RT_ACK,
		volume_num,
		drive_type,
		drive_num,
		0,
		"",
		"",
		param,
		tag));	
}

kern_return_t vol_panel_disk_label(vpt_func fnc,
	char *label,
	int drive_type,				/* PR_DRIVE_FLOPPY, etc. */
	int drive_num,
	void *param,
	boolean_t wrong_disk,
	int *tag) {				/* RETURNED */

	return(vol_panel_request(fnc,
		wrong_disk ? PR_PT_DISK_LABEL_W : PR_PT_DISK_LABEL,
		PR_RT_ACK,
		0,
		drive_type,
		drive_num,
		0,
		label,
		"",
		param,
		tag));	
} 

void vol_thread() {
	/*
	 * The job here is to receive and handle two kinds of messages:
	 *
	 * -- messages from the Workspace (like vol_panel_resp messages). 
	 * -- port death messages, forwarded to us from voltask. The death of 
	 *    panel_req_port will cause all pending panels in vol_panel_q to be
	 *    acked and removed. The death of vol_check_port isn't very 
	 *    interesting; we just NULL it out so we don't send any more
	 *    insertion events.
	 */
	 
	struct vol_panel_resp *msgp;
	kern_return_t krtn;
	vpe_t vpe;

	msgp = (struct vol_panel_resp *)kalloc(MSG_SIZE_MAX);
	
	while(1) {
		msgp->ps_header.msg_local_port = vol_receive_port;
		msgp->ps_header.msg_size       = MSG_SIZE_MAX;
		krtn = msg_receive(&msgp->ps_header, MSG_OPTION_NONE, 0);
		if(krtn) {
			printf("vol_thread: msg_receive() returned %d\n", 
				krtn);
			continue;
		}
		vol_debug(("vol_thread: msg_id = 0x%x\n", 
			msgp->ps_header.msg_id));
		switch(msgp->ps_header.msg_id) {
		    case VOL_PANEL_RESP:
		    	/*
			 * First we have to see if we know about a panel with 
			 * this tag.
			 */
			vpe = vol_panel_get_entry(msgp->ps_tag);
			if(vpe == NULL) {
				/*
				 * This is not an error; it could happen
				 * in a race condition in which this message
				 * came in just after a driver sent a 
				 * vol_panel_cancel message.
				 */
				vol_debug(("vol_thread: panel resp, tag not"
					" found\n"));
				break;
			}
			/*
			 * Perform callout if necessary, then dispose of
			 * entry.
			 */
			vol_debug(("vol_thread: doing VOL_PANEL_RESP "
				"callout\n"));
			if(vpe->fnc) {
				(*vpe->fnc)(vpe->param, 
					vpe->tag, 
					msgp->ps_value);
			}
			kfree(vpe, sizeof(*vpe));
			break;
			
		    case NOTIFY_PORT_DELETED:
			vol_debug(("vol_thread: port death\n"));
			vol_port_death((notification_t *)msgp);
			break;
			
		    default:
		    	printf("vol_thread: bogus message rec'd (msg_id = "
				"%d)\n", msgp->ps_header.msg_id);
			break;
		}
	}
} /* vol_thread() */

static vpe_t vol_panel_get_entry(int tag) {
	/*
	 * get vol_panel_entry associated with 'tag' from vol_panel_q. Removes
	 * entry from vol_panel_q. Returns NULL if entry not found.
	 */
	vpe_t vpe;

	vpe = (vpe_t)queue_first(&vol_panel_q);
	while(!queue_end(&vol_panel_q, (queue_entry_t)vpe)) {
		if(vpe->tag == tag) {
			/*
			 * Found it. 
			 */
			queue_remove(&vol_panel_q,
				vpe,
				struct vol_panel_entry *,
				link);
			return(vpe);
		}
		vpe = (vpe_t)vpe->link.next;
	}
	/*
	 * Not found. 
	 */
	return((vpe_t)NULL);
}

static void vol_port_death(notification_t *nmsg)
{
	/*
	 * voltask has detected port death of one of our ports.
	 */
	vol_debug(("vol_port_death: "));
	if((kern_port_t)nmsg->notify_port == vol_check_port) {
		vol_debug(("vol_check_port dead\n"));
		vol_check_port = PORT_NULL;
	}
	else if ((kern_port_t)nmsg->notify_port == panel_req_port) {
		vpe_t vpe, vpe_next;
		
		vol_debug(("panel_req_port dead\n"));
		/*
		 * Ack and dispose of every vol_panel_entry in vol_panel_q.
		 */
		vpe = (vpe_t)queue_first(&vol_panel_q);
		while(!queue_end(&vol_panel_q, (queue_entry_t)vpe)) {
			vol_debug(("vol_port_death: doing VOL_PANEL_RESP "
				"callout\n"));
			vpe_next = (vpe_t)vpe->link.next;
			if(vpe->fnc) {
				(*vpe->fnc)(vpe->param, 
					vpe->tag, 
					0);	/* as in "disk not 
						 * available" */
			}
			queue_remove(&vol_panel_q,
				vpe,
				struct vol_panel_entry *,
				link);
			kfree(vpe, sizeof(*vpe));
			vpe = vpe_next;
		}
		/*
		 * Finally, inhibit further panel request messages.
		 */
		panel_req_port = PORT_NULL;
	}
	else 
		vol_debug(("bogus notification\n"));
}

void vol_start_thread()
{
	kern_return_t krtn;

	mach_port_t	name;

	krtn = ipc_port_alloc(kernel_task->itk_space, &name, &vol_local_port);
	if(krtn) {
		printf("vol_start_thread: port_alloc returned %d\n", 
			krtn);
		return;
	}
	vol_receive_port = vol_local_port->ip_receiver_name;
	ip_unlock(vol_local_port);
	kernel_thread(kernel_task, vol_thread, (void *)0);
	queue_init(&vol_panel_q);
	vol_thread_alive = 1;
	if(!vol_dev_init_flag) {
 		lock_init(&vol_notify_lock, TRUE);
		vol_dev_init_flag = TRUE;
	}
}

#if	DRIVERKIT
/* 
 * Returns 1 if DKIOCCHECKINSERT has been executed since the last call,
 * else returns 0.
 */
int vol_check_manual_poll()
{
	if(manual_poll) {
		manual_poll = 0;
		return(1);
	}
	else {
		return(0);
	}
}

/*
 * Internal version of ioctl(DKIOCCHECKINSERT).
 */
void vol_check_set_poll()
{
	manual_poll = 1;
}

#endif	DRIVERKIT

/* end of vol.c */