Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 21 Dec 2000 08:01:28 -0700
From:      "Justin T. Gibbs" <gibbs@scsiguy.com>
To:        Nat Lanza <magus@cs.cmu.edu>
Cc:        freebsd-scsi@FreeBSD.ORG
Subject:   Re: Asynchronous commands for the passthrough driver 
Message-ID:  <200012211501.eBLF1SF42268@aslan.scsiguy.com>
In-Reply-To: Your message of "20 Dec 2000 02:02:55 EST." <uoc4rzzmv6o.fsf@hurlame.pdl.cs.cmu.edu> 

next in thread | previous in thread | raw e-mail | index | archive | help
This is a multipart MIME message.

--==_Exmh_-13419104960
Content-Type: text/plain; charset=us-ascii

>I'd like to add support for asynchronous commands to the pass
>driver.

I actually started on an interface for this a *long* time ago, but
never found the time to complete it.  I've included the diffs from
that effort which primarily remove the cruft from the read/write
interface that has never been used and defines an interface for 
async commands.  Implementing the rest of the interface (or even
changing it) is left as an exercise for the reader.
(The SF_RETRY_SELTO changes are from some error recovery cleanups
that are in the pipeline. You can ignore those).

>The simplest solution, and the one I'm currently considering, would be
>to add two new ioctls to the pass driver, CAMIOSENDCOMMAND and
>CAMIOFETCHCOMMAND, and maintain a per-instance queue of completed but
>uncollected CCBs. The first ioctl would enqueue a CCB to the device,
>passing along a completion function that would add the result CCB to
>the pass queue, and return immediately without sleeping the
>caller. The second ioctl would return the first completed CCB on the
>pass queue if there are any CCBs to return, and would either sleep or
>return EAGAIN if the queue is empty.

I chose to implement an interface capable of scheduling and retreiving
whole lists of I/O so a smart application could avoid several uesr/kernel
boundary trips for certain types of applications.  The only problem with
this is defining the semantics of the enqueue ioctl if one or more of the
CCBs contain errors.  You probably want to validate all ccbs before
really sending any of them and then indicate which ccb failed by updating
the hosts copy of the errant ccb's status field and perhaps returning an
offset or pointer to the user.  The other difference in this setup is
that it avoids a copy of the rather large CCB onto the kernel stack
at the time of the ioctl when we'll just have to copy that object
again into malloced storage.

>Alternately, notification could be done through kqueue or maybe a
>poll() on the /dev/pass<n> device.

I suppose an application could have the need to poll on thousands of
pass fds, but not in the near term.  We can always add a kqueue interface
for agregating pass events at a later time giving the user the choice of
which one to use.  It would also be cool if you could register an arbatrary
signal to fire when commands have completed as well as register a different
signal for exceptional events (bus resets, loop up/down, etc.).  The latter
would finally allow you to do everything that you can from within the kernel
from a userland app.

>An additional complication is the issue of data copies for read/write
>requests. In the synchronous case, things are simple -- the
>application making the request is sleeping, so the CAM layer is free
>to copy data to/from the application-provided buffer without worrying
>about what the application might have done to that buffer in the
>meantime. If the application isn't sleeping while the request is being
>processed, there's the possibility that the specified buffer isn't
>available when the CAM layer wants to do the data copy -- maybe that
>buffer is gone, or maybe the application has exited.

This is the same issue that aio has to deal with, and the pass driver
already deals with it.  vmapbuf wires the page into the kernel address
space and ensures that it will not be swapped out until vunmapbuf is called.

Oh, the one thing I'd consider changing in my interface is that perhaps
having the user provide a linked list of entries (linked through the
xpt_links in the ccb header) instead of an array of pointers might make
the enqueue simpler on both sides.  Don't know though.  What do the
listio interfaces do?

--
Justin

--==_Exmh_-13419104960
Content-Type: text/plain ; name="cam.pass.diffs"; charset=us-ascii
Content-Description: cam.pass.diffs
Content-Disposition: attachment; filename="cam.pass.diffs"

Index: cam/scsi/scsi_pass.c
===================================================================
RCS file: /usr/cvs/src/sys/cam/scsi/scsi_pass.c,v
retrieving revision 1.25
diff -c -r1.25 scsi_pass.c
*** cam/scsi/scsi_pass.c	2000/10/30 08:08:00	1.25
--- cam/scsi/scsi_pass.c	2000/11/03 15:00:44
***************
*** 1,5 ****
  /*
!  * Copyright (c) 1997, 1998 Justin T. Gibbs.
   * Copyright (c) 1997, 1998, 1999 Kenneth D. Merry.
   * All rights reserved.
   *
--- 1,5 ----
  /*
!  * Copyright (c) 1997, 1998, 2000 Justin T. Gibbs.
   * Copyright (c) 1997, 1998, 1999 Kenneth D. Merry.
   * All rights reserved.
   *
***************
*** 42,47 ****
--- 42,48 ----
  #include <cam/cam_ccb.h>
  #include <cam/cam_extend.h>
  #include <cam/cam_periph.h>
+ #include <cam/cam_queue.h>
  #include <cam/cam_xpt_periph.h>
  #include <cam/cam_debug.h>
  
***************
*** 63,81 ****
  	PASS_CCB_WAITING
  } pass_ccb_types;
  
  #define ccb_type	ppriv_field0
  #define ccb_bp		ppriv_ptr1
  
  struct pass_softc {
! 	pass_state	state;
! 	pass_flags	flags;
! 	u_int8_t	pd_type;
! 	struct		bio_queue_head bio_queue;
! 	union ccb	saved_ccb;
! 	struct devstat	device_stats;
! 	dev_t		dev;
  };
  
  #ifndef MIN
  #define MIN(x,y) ((x<y) ? x : y)
  #endif
--- 64,95 ----
  	PASS_CCB_WAITING
  } pass_ccb_types;
  
+ typedef enum {
+ 	PASS_MAPPING_ACTIVE	= 0x01
+ } pass_mapinfo_flags;
+ 
+ struct pass_mapping_info {
+ 	struct cam_periph_map_info mapinfo;
+ 	union ccb		  *user_ccb_addr;
+ 	pass_mapinfo_flags	   flags;
+ };
+ 
  #define ccb_type	ppriv_field0
  #define ccb_bp		ppriv_ptr1
  
  struct pass_softc {
! 	pass_state		state;
! 	pass_flags		flags;
! 	u_int8_t		pd_type;
! 	struct ccb_hdr_tailq	async_command_queue;
! 	u_int			async_num_active;
! 	union ccb		saved_ccb;
! 	struct devstat		device_stats;
! 	dev_t			dev;
  };
  
+ #define PASS_MAX_ASYNC 255
+ 
  #ifndef MIN
  #define MIN(x,y) ((x<y) ? x : y)
  #endif
***************
*** 85,91 ****
  static	d_open_t	passopen;
  static	d_close_t	passclose;
  static	d_ioctl_t	passioctl;
- static	d_strategy_t	passstrategy;
  
  static	periph_init_t	passinit;
  static	periph_ctor_t	passregister;
--- 99,104 ----
***************
*** 112,123 ****
  static struct cdevsw pass_cdevsw = {
  	/* open */	passopen,
  	/* close */	passclose,
! 	/* read */	physread,
! 	/* write */	physwrite,
  	/* ioctl */	passioctl,
  	/* poll */	nopoll,
  	/* mmap */	nommap,
! 	/* strategy */	passstrategy,
  	/* name */	"pass",
  	/* maj */	PASS_CDEV_MAJOR,
  	/* dump */	nodump,
--- 125,136 ----
  static struct cdevsw pass_cdevsw = {
  	/* open */	passopen,
  	/* close */	passclose,
! 	/* read */	noread,
! 	/* write */	nowrite,
  	/* ioctl */	passioctl,
  	/* poll */	nopoll,
  	/* mmap */	nommap,
! 	/* strategy */	nostrategy,
  	/* name */	"pass",
  	/* maj */	PASS_CDEV_MAJOR,
  	/* dump */	nodump,
***************
*** 173,181 ****
  static void
  passoninvalidate(struct cam_periph *periph)
  {
- 	int s;
  	struct pass_softc *softc;
- 	struct bio *q_bp;
  	struct ccb_setasync csa;
  
  	softc = (struct pass_softc *)periph->softc;
--- 186,192 ----
***************
*** 194,218 ****
  	softc->flags |= PASS_FLAG_INVALID;
  
  	/*
! 	 * Although the oninvalidate() routines are always called at
! 	 * splsoftcam, we need to be at splbio() here to keep the buffer
! 	 * queue from being modified while we traverse it.
! 	 */
! 	s = splbio();
! 
! 	/*
! 	 * Return all queued I/O with ENXIO.
  	 * XXX Handle any transactions queued to the card
  	 *     with XPT_ABORT_CCB.
  	 */
- 	while ((q_bp = bioq_first(&softc->bio_queue)) != NULL){
- 		bioq_remove(&softc->bio_queue, q_bp);
- 		q_bp->bio_resid = q_bp->bio_bcount;
- 		q_bp->bio_error = ENXIO;
- 		q_bp->bio_flags |= BIO_ERROR;
- 		biodone(q_bp);
- 	}
- 	splx(s);
  
  	if (bootverbose) {
  		xpt_print_path(periph->path);
--- 205,214 ----
  	softc->flags |= PASS_FLAG_INVALID;
  
  	/*
! 	 * XXX Return all queued I/O with ENXIO.
  	 * XXX Handle any transactions queued to the card
  	 *     with XPT_ABORT_CCB.
  	 */
  
  	if (bootverbose) {
  		xpt_print_path(periph->path);
***************
*** 286,291 ****
--- 282,288 ----
  	struct pass_softc *softc;
  	struct ccb_setasync csa;
  	struct ccb_getdev *cgd;
+ 	int    no_tags;
  
  	cgd = (struct ccb_getdev *)arg;
  	if (periph == NULL) {
***************
*** 310,327 ****
  	bzero(softc, sizeof(*softc));
  	softc->state = PASS_STATE_NORMAL;
  	softc->pd_type = SID_TYPE(&cgd->inq_data);
! 	bioq_init(&softc->bio_queue);
  
  	periph->softc = softc;
- 
  	cam_extend_set(passperiphs, periph->unit_number, periph);
  	/*
  	 * We pass in 0 for a blocksize, since we don't 
  	 * know what the blocksize of this device is, if 
  	 * it even has a blocksize.
  	 */
! 	devstat_add_entry(&softc->device_stats, "pass", periph->unit_number,
! 			  0, DEVSTAT_NO_BLOCKSIZE | DEVSTAT_NO_ORDERED_TAGS,
  			  softc->pd_type |
  			  DEVSTAT_TYPE_IF_SCSI |
  			  DEVSTAT_TYPE_PASS,
--- 307,326 ----
  	bzero(softc, sizeof(*softc));
  	softc->state = PASS_STATE_NORMAL;
  	softc->pd_type = SID_TYPE(&cgd->inq_data);
! 	TAILQ_INIT(&softc->async_command_queue);
  
  	periph->softc = softc;
  	cam_extend_set(passperiphs, periph->unit_number, periph);
+ 
  	/*
  	 * We pass in 0 for a blocksize, since we don't 
  	 * know what the blocksize of this device is, if 
  	 * it even has a blocksize.
  	 */
! 	no_tags = (cgd->inq_data.flags & SID_CmdQue) == 0;
! 	devstat_add_entry(&softc->device_stats, "pass", periph->unit_number, 0,
! 			  DEVSTAT_NO_BLOCKSIZE
! 			  | (no_tags ? DEVSTAT_NO_ORDERED_TAGS : 0),
  			  softc->pd_type |
  			  DEVSTAT_TYPE_IF_SCSI |
  			  DEVSTAT_TYPE_PASS,
***************
*** 448,523 ****
  	return (0);
  }
  
- /*
-  * Actually translate the requested transfer into one the physical driver
-  * can understand.  The transfer is described by a buf and will include
-  * only one physical transfer.
-  */
  static void
- passstrategy(struct bio *bp)
- {
- 	struct cam_periph *periph;
- 	struct pass_softc *softc;
- 	u_int  unit;
- 	int    s;
- 
- 	/*
- 	 * The read/write interface for the passthrough driver doesn't
- 	 * really work right now.  So, we just pass back EINVAL to tell the
- 	 * user to go away.
- 	 */
- 	bp->bio_error = EINVAL;
- 	goto bad;
- 
- 	/* unit = dkunit(bp->bio_dev); */
- 	/* XXX KDM fix this */
- 	unit = minor(bp->bio_dev) & 0xff;
- 
- 	periph = cam_extend_get(passperiphs, unit);
- 	if (periph == NULL) {
- 		bp->bio_error = ENXIO;
- 		goto bad;
- 	}
- 	softc = (struct pass_softc *)periph->softc;
- 
- 	/*
- 	 * Odd number of bytes or negative offset
- 	 */
- 	/* valid request?  */
- 	if (bp->bio_blkno < 0) {
- 		bp->bio_error = EINVAL;
- 		goto bad;
-         }
- 	
- 	/*
- 	 * Mask interrupts so that the pack cannot be invalidated until
- 	 * after we are in the queue.  Otherwise, we might not properly
- 	 * clean up one of the buffers.
- 	 */
- 	s = splbio();
- 	
- 	bioq_insert_tail(&softc->bio_queue, bp);
- 
- 	splx(s);
- 	
- 	/*
- 	 * Schedule ourselves for performing the work.
- 	 */
- 	xpt_schedule(periph, /* XXX priority */1);
- 
- 	return;
- bad:
- 	bp->bio_flags |= BIO_ERROR;
- 
- 	/*
- 	 * Correctly set the buf to indicate a completed xfer
- 	 */
- 	bp->bio_resid = bp->bio_bcount;
- 	biodone(bp);
- 	return;
- }
- 
- static void
  passstart(struct cam_periph *periph, union ccb *start_ccb)
  {
  	struct pass_softc *softc;
--- 447,453 ----
***************
*** 527,579 ****
  
  	switch (softc->state) {
  	case PASS_STATE_NORMAL:
- 	{
- 		struct bio *bp;
- 
  		s = splbio();
! 		bp = bioq_first(&softc->bio_queue);
! 		if (periph->immediate_priority <= periph->pinfo.priority) {
! 			start_ccb->ccb_h.ccb_type = PASS_CCB_WAITING;			
! 			SLIST_INSERT_HEAD(&periph->ccb_list, &start_ccb->ccb_h,
! 					  periph_links.sle);
! 			periph->immediate_priority = CAM_PRIORITY_NONE;
! 			splx(s);
! 			wakeup(&periph->ccb_list);
! 		} else if (bp == NULL) {
! 			splx(s);
! 			xpt_release_ccb(start_ccb);
! 		} else {
! 
! 			bioq_remove(&softc->bio_queue, bp);
! 
! 			devstat_start_transaction(&softc->device_stats);
! 
! 			/*
! 			 * XXX JGibbs -
! 			 * Interpret the contents of the bp as a CCB
! 			 * and pass it to a routine shared by our ioctl
! 			 * code and passtart.
! 			 * For now, just biodone it with EIO so we don't
! 			 * hang.
! 			 */
! 			bp->bio_error = EIO;
! 			bp->bio_flags |= BIO_ERROR;
! 			bp->bio_resid = bp->bio_bcount;
! 			biodone(bp);
! 			bp = bioq_first(&softc->bio_queue);
! 			splx(s);
!   
! 			xpt_action(start_ccb);
! 
! 		}
! 		if (bp != NULL) {
! 			/* Have more work to do, so ensure we stay scheduled */
! 			xpt_schedule(periph, /* XXX priority */1);
! 		}
  		break;
  	}
- 	}
  }
  static void
  passdone(struct cam_periph *periph, union ccb *done_ccb)
  { 
--- 457,473 ----
  
  	switch (softc->state) {
  	case PASS_STATE_NORMAL:
  		s = splbio();
! 		start_ccb->ccb_h.ccb_type = PASS_CCB_WAITING;			
! 		SLIST_INSERT_HEAD(&periph->ccb_list, &start_ccb->ccb_h,
! 				  periph_links.sle);
! 		periph->immediate_priority = CAM_PRIORITY_NONE;
! 		splx(s);
! 		wakeup(&periph->ccb_list);
  		break;
  	}
  }
+ 
  static void
  passdone(struct cam_periph *periph, union ccb *done_ccb)
  { 
***************
*** 583,637 ****
  	softc = (struct pass_softc *)periph->softc;
  	csio = &done_ccb->csio;
  	switch (csio->ccb_h.ccb_type) {
- 	case PASS_CCB_BUFFER_IO:
- 	{
- 		struct bio		*bp;
- 		cam_status		status;
- 		u_int8_t		scsi_status;
- 		devstat_trans_flags	ds_flags;
- 
- 		status = done_ccb->ccb_h.status;
- 		scsi_status = done_ccb->csio.scsi_status;
- 		bp = (struct bio *)done_ccb->ccb_h.ccb_bp;
- 		/* XXX handle errors */
- 		if (!(((status & CAM_STATUS_MASK) == CAM_REQ_CMP)
- 		  && (scsi_status == SCSI_STATUS_OK))) {
- 			int error;
- 			
- 			if ((error = passerror(done_ccb, 0, 0)) == ERESTART) {
- 				/*
- 				 * A retry was scheuled, so
- 				 * just return.
- 				 */
- 				return;
- 			}
- 
- 			/*
- 			 * XXX unfreeze the queue after we complete
- 			 * the abort process
- 			 */
- 			bp->bio_error = error;
- 			bp->bio_flags |= BIO_ERROR;
- 		}
- 
- 		if ((done_ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
- 			ds_flags = DEVSTAT_READ;
- 		else if ((done_ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT)
- 			ds_flags = DEVSTAT_WRITE;
- 		else
- 			ds_flags = DEVSTAT_NO_DATA;
- 
- 		devstat_end_transaction_bio(&softc->device_stats, bp);
- 		biodone(bp);
- 		break;
- 	}
  	case PASS_CCB_WAITING:
- 	{
  		/* Caller will release the CCB */
  		wakeup(&done_ccb->ccb_h.cbfcnp);
  		return;
  	}
- 	}
  	xpt_release_ccb(done_ccb);
  }
  
--- 477,487 ----
***************
*** 715,720 ****
--- 565,599 ----
  
  		break;
  	}
+ 	case CAMIOCOMMANDASYNC:
+ 	{
+ 		struct	pass_command_async *pca;
+ 		union	ccb **pca_buffer;
+ 		size_t	pca_buffer_size;
+ 		int	i;
+ 
+ 		pca = (struct pass_command_async *)addr;
+ 		pca_buffer_size =
+ 		    pca->pca_allocated_entries * sizeof(union ccb *);
+ 		pca_buffer = malloc(pca_buffer_size, M_TEMP, M_WAITOK);
+ 		if (pca_buffer == NULL) {
+ 			error = ENOMEM;
+ 			break;
+ 		}
+ 		error = copyin(pca->pca_buffer, pca_buffer, pca_buffer_size);
+ 		if (error != 0) {
+ 			free(pca_buffer, M_TEMP);
+ 			break;
+ 		}
+ 		for (i = 0; i < pca->pca_allocated_entries; i++) {
+ 
+ 		}
+ 		break;
+ 	}
+ 	case CAMIOCOMMANDSTATUS:
+ 	{
+ 		break;
+ 	}
  	default:
  		error = cam_periph_ioctl(periph, cmd, addr, passerror);
  		break;
***************
*** 792,799 ****
  	error = cam_periph_runccb(ccb,
  				  (ccb->ccb_h.flags & CAM_PASS_ERR_RECOVER) ?
  				  passerror : NULL,
! 				  /* cam_flags */ 0,
! 				  /* sense_flags */SF_RETRY_UA | SF_RETRY_SELTO,
  				  &softc->device_stats);
  
  	if (need_unmap != 0)
--- 671,678 ----
  	error = cam_periph_runccb(ccb,
  				  (ccb->ccb_h.flags & CAM_PASS_ERR_RECOVER) ?
  				  passerror : NULL,
! 				  /* cam_flags */ CAM_RETRY_SELTO,
! 				  /* sense_flags */SF_RETRY_UA,
  				  &softc->device_stats);
  
  	if (need_unmap != 0)
Index: cam/scsi/scsi_pass.h
===================================================================
RCS file: /usr/cvs/src/sys/cam/scsi/scsi_pass.h,v
retrieving revision 1.3
diff -c -r1.3 scsi_pass.h
*** cam/scsi/scsi_pass.h	1999/08/28 00:40:48	1.3
--- cam/scsi/scsi_pass.h	2000/06/14 11:54:05
***************
*** 1,4 ****
--- 1,5 ----
  /*
+  * Copyright (c) 2000 Justin T. Gibbs.
   * Copyright (c) 1997, 1999 Kenneth D. Merry.
   * All rights reserved.
   *
***************
*** 32,38 ****
  
  #include <cam/cam_ccb.h>
  
! #define CAMIOCOMMAND	_IOWR(CAM_VERSION, 2, union ccb)
! #define CAMGETPASSTHRU	_IOWR(CAM_VERSION, 3, union ccb)
  
  #endif
--- 33,54 ----
  
  #include <cam/cam_ccb.h>
  
! /*
!  * Convert to using a pointer to a ccb in the next major version.
!  * This should allow us to avoid an extra copy of the CCB data.
!  */
! #define CAMIOCOMMAND	  _IOWR(CAM_VERSION, 2, union ccb)
! #define CAMGETPASSTHRU	  _IOWR(CAM_VERSION, 3, union ccb)
  
+ struct pass_command_async {
+ 	union ccb	**pca_buffer;
+ 	u_int32_t	  pca_allocated_entries;
+ };
+ #define CAMIOCOMMANDASYNC _IOWR(CAM_VERSION, 4, struct pass_command_async)
+ struct pass_command_status {
+ 	union ccb **pcs_buffer;
+ 	u_int32_t   pcs_allocated_entries;
+ 	u_int32_t   pcs_returned_entries;
+ };
+ #define CAMIOCOMMANDSTATUS _IOWR(CAM_VERSION, 5, struct pass_command_status)
  #endif

--==_Exmh_-13419104960--



To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-scsi" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200012211501.eBLF1SF42268>