summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/sa.466
-rw-r--r--sys/cam/scsi/scsi_sa.c117
-rw-r--r--sys/kern/kern_physio.c41
-rw-r--r--sys/sys/conf.h1
-rw-r--r--sys/sys/param.h2
5 files changed, 221 insertions, 6 deletions
diff --git a/share/man/man4/sa.4 b/share/man/man4/sa.4
index eae3bf5..aa72f68 100644
--- a/share/man/man4/sa.4
+++ b/share/man/man4/sa.4
@@ -25,7 +25,7 @@
.\"
.\" $FreeBSD$
.\"
-.Dd June 6, 1999
+.Dd August 23, 2013
.Dt SA 4
.Os
.Sh NAME
@@ -159,6 +159,70 @@ of 0.
(As above, if the file mark is never read, it remains for the next
process to read if in no-rewind mode.)
.El
+.Sh BLOCK SIZES
+By default, the driver will NOT accept reads or writes to a tape device that
+are larger than may be written to or read from the mounted tape using a single
+write or read request.
+Because of this, the application author may have confidence that his wishes
+are respected in terms of the block size written to tape.
+For example, if the user tries to write a 256KB block to the tape, but the
+controller can handle no more than 128KB, the write will fail.
+The previous
+.Fx
+behavior, prior to
+.Fx
+10.0,
+was to break up large reads or writes into smaller blocks when going to the
+tape.
+The problem with that behavior, though, is that it hides the actual on-tape
+block size from the application writer, at least in variable block mode.
+.Pp
+If the user would like his large reads and writes broken up into separate
+pieces, he may set the following loader tunables.
+Note that these tunables WILL GO AWAY in
+.Fx 11.0 .
+They are provided for transition purposes only.
+.Bl -tag -width 12
+.It kern.cam.sa.allow_io_split
+.Pp
+This variable, when set to 1, will configure all
+.Nm
+devices to split large buffers into smaller pieces when needed.
+.It kern.cam.sa.%d.allow_io_split
+.Pp
+This variable, when set to 1, will configure the given
+.Nm
+unit to split large buffers into multiple pieces.
+This will override the global setting, if it exists.
+.El
+.Pp
+There are several
+.Xr sysctl 8
+variables available to view block handling parameters:
+.Bl -tag -width 12
+.It kern.cam.sa.%d.allow_io_split
+.Pp
+This variable allows the user to see, but not modify, the current I/O split
+setting.
+The user is not permitted to modify this setting so that there is no chance
+of behavior changing for the application while a tape is mounted.
+.It kern.cam.sa.%d.maxio
+.Pp
+This variable shows the maximum I/O size in bytes that is allowed by the
+combination of kernel tuning parameters (MAXPHYS, DFLTPHYS) and the
+capabilities of the controller that is attached to the tape drive.
+Applications may look at this value for a guide on how large an I/O may be
+permitted, but should keep in mind that the actual maximum may be
+restricted further by the tape drive via the
+.Tn SCSI
+READ BLOCK LIMITS command.
+.It kern.cam.sa.%d.cpi_maxio
+.Pp
+This variable shows the maximum I/O size supported by the controller, in
+bytes, that is reported via the CAM Path Inquiry CCB (XPT_PATH_INQ).
+If this is 0, that means that the controller has not reported a maximum I/O
+size.
+.El
.Sh FILE MARK HANDLING
The handling of file marks on write is automatic.
If the user has
diff --git a/sys/cam/scsi/scsi_sa.c b/sys/cam/scsi/scsi_sa.c
index f9b0872..6b941e0 100644
--- a/sys/cam/scsi/scsi_sa.c
+++ b/sys/cam/scsi/scsi_sa.c
@@ -43,6 +43,8 @@ __FBSDID("$FreeBSD$");
#include <sys/mtio.h>
#ifdef _KERNEL
#include <sys/conf.h>
+#include <sys/sysctl.h>
+#include <sys/taskqueue.h>
#endif
#include <sys/fcntl.h>
#include <sys/devicestat.h>
@@ -223,6 +225,8 @@ struct sa_softc {
u_int32_t max_blk;
u_int32_t min_blk;
u_int32_t maxio;
+ u_int32_t cpi_maxio;
+ int allow_io_split;
u_int32_t comp_algorithm;
u_int32_t saved_comp_algorithm;
u_int32_t media_blksize;
@@ -268,6 +272,10 @@ struct sa_softc {
open_rdonly : 1, /* open read-only */
open_pending_mount : 1, /* open pending mount */
ctrl_mode : 1; /* control device open */
+
+ struct task sysctl_task;
+ struct sysctl_ctx_list sysctl_ctx;
+ struct sysctl_oid *sysctl_tree;
};
struct sa_quirk_entry {
@@ -426,6 +434,22 @@ static int sardpos(struct cam_periph *periph, int, u_int32_t *);
static int sasetpos(struct cam_periph *periph, int, u_int32_t *);
+#ifndef SA_DEFAULT_IO_SPLIT
+#define SA_DEFAULT_IO_SPLIT 0
+#endif
+
+static int sa_allow_io_split = SA_DEFAULT_IO_SPLIT;
+
+/*
+ * Tunable to allow the user to set a global allow_io_split value. Note
+ * that this WILL GO AWAY in FreeBSD 11.0. Silently splitting the I/O up
+ * is bad behavior, because it hides the true tape block size from the
+ * application.
+ */
+TUNABLE_INT("kern.cam.sa.allow_io_split", &sa_allow_io_split);
+static SYSCTL_NODE(_kern_cam, OID_AUTO, sa, CTLFLAG_RD, 0,
+ "CAM Sequential Access Tape Driver");
+
static struct periph_driver sadriver =
{
sainit, "sa",
@@ -1448,6 +1472,49 @@ saasync(void *callback_arg, u_int32_t code,
}
}
+static void
+sasysctlinit(void *context, int pending)
+{
+ struct cam_periph *periph;
+ struct sa_softc *softc;
+ char tmpstr[80], tmpstr2[80];
+
+ periph = (struct cam_periph *)context;
+ /*
+ * If the periph is invalid, no need to setup the sysctls.
+ */
+ if (periph->flags & CAM_PERIPH_INVALID)
+ goto bailout;
+
+ softc = (struct sa_softc *)periph->softc;
+
+ snprintf(tmpstr, sizeof(tmpstr), "CAM SA unit %d", periph->unit_number);
+ snprintf(tmpstr2, sizeof(tmpstr2), "%u", periph->unit_number);
+
+ sysctl_ctx_init(&softc->sysctl_ctx);
+ softc->sysctl_tree = SYSCTL_ADD_NODE(&softc->sysctl_ctx,
+ SYSCTL_STATIC_CHILDREN(_kern_cam_sa), OID_AUTO, tmpstr2,
+ CTLFLAG_RD, 0, tmpstr);
+ if (softc->sysctl_tree == NULL)
+ goto bailout;
+
+ SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
+ OID_AUTO, "allow_io_split", CTLTYPE_INT | CTLFLAG_RDTUN,
+ &softc->allow_io_split, 0, "Allow Splitting I/O");
+ SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
+ OID_AUTO, "maxio", CTLTYPE_INT | CTLFLAG_RD,
+ &softc->maxio, 0, "Maximum I/O size");
+ SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
+ OID_AUTO, "cpi_maxio", CTLTYPE_INT | CTLFLAG_RD,
+ &softc->cpi_maxio, 0, "Maximum Controller I/O size");
+
+bailout:
+ /*
+ * Release the reference that was held when this task was enqueued.
+ */
+ cam_periph_release(periph);
+}
+
static cam_status
saregister(struct cam_periph *periph, void *arg)
{
@@ -1455,6 +1522,7 @@ saregister(struct cam_periph *periph, void *arg)
struct ccb_getdev *cgd;
struct ccb_pathinq cpi;
caddr_t match;
+ char tmpstr[80];
int i;
cgd = (struct ccb_getdev *)arg;
@@ -1509,21 +1577,55 @@ saregister(struct cam_periph *periph, void *arg)
XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_TAPE);
/*
- * If maxio isn't set, we fall back to DFLTPHYS. If it is set, we
- * take it whether or not it's larger than MAXPHYS. physio will
- * break it down into pieces small enough to fit in a buffer.
+ * Load the default value that is either compiled in, or loaded
+ * in the global kern.cam.sa.allow_io_split tunable.
+ */
+ softc->allow_io_split = sa_allow_io_split;
+
+ /*
+ * Load a per-instance tunable, if it exists. NOTE that this
+ * tunable WILL GO AWAY in FreeBSD 11.0.
+ */
+ snprintf(tmpstr, sizeof(tmpstr), "kern.cam.sa.%u.allow_io_split",
+ periph->unit_number);
+ TUNABLE_INT_FETCH(tmpstr, &softc->allow_io_split);
+
+ /*
+ * If maxio isn't set, we fall back to DFLTPHYS. Otherwise we take
+ * the smaller of cpi.maxio or MAXPHYS.
*/
if (cpi.maxio == 0)
softc->maxio = DFLTPHYS;
+ else if (cpi.maxio > MAXPHYS)
+ softc->maxio = MAXPHYS;
else
softc->maxio = cpi.maxio;
/*
+ * Record the controller's maximum I/O size so we can report it to
+ * the user later.
+ */
+ softc->cpi_maxio = cpi.maxio;
+
+ /*
+ * By default we tell physio that we do not want our I/O split.
+ * The user needs to have a 1:1 mapping between the size of his
+ * write to a tape character device and the size of the write
+ * that actually goes down to the drive.
+ */
+ if (softc->allow_io_split == 0)
+ softc->si_flags = SI_NOSPLIT;
+ else
+ softc->si_flags = 0;
+
+ TASK_INIT(&softc->sysctl_task, 0, sasysctlinit, periph);
+
+ /*
* If the SIM supports unmapped I/O, let physio know that we can
* handle unmapped buffers.
*/
if (cpi.hba_misc & PIM_UNMAPPED)
- softc->si_flags = SI_UNMAPPED;
+ softc->si_flags |= SI_UNMAPPED;
softc->devs.ctl_dev = make_dev(&sa_cdevsw, SAMINOR(SA_CTLDEV,
0, SA_ATYPE_R), UID_ROOT, GID_OPERATOR,
@@ -1586,6 +1688,13 @@ saregister(struct cam_periph *periph, void *arg)
cam_periph_lock(periph);
/*
+ * Bump the peripheral refcount for the sysctl thread, in case we
+ * get invalidated before the thread has a chance to run.
+ */
+ cam_periph_acquire(periph);
+ taskqueue_enqueue(taskqueue_thread, &softc->sysctl_task);
+
+ /*
* Add an async callback so that we get
* notified if this device goes away.
*/
diff --git a/sys/kern/kern_physio.c b/sys/kern/kern_physio.c
index ab9c344..88cd0cf 100644
--- a/sys/kern/kern_physio.c
+++ b/sys/kern/kern_physio.c
@@ -54,6 +54,36 @@ physio(struct cdev *dev, struct uio *uio, int ioflag)
dev->si_iosize_max = DFLTPHYS;
}
+ /*
+ * If the driver does not want I/O to be split, that means that we
+ * need to reject any requests that will not fit into one buffer.
+ */
+ if ((dev->si_flags & SI_NOSPLIT) &&
+ ((uio->uio_resid > dev->si_iosize_max) ||
+ (uio->uio_resid > MAXPHYS) ||
+ (uio->uio_iovcnt > 1))) {
+ /*
+ * Tell the user why his I/O was rejected.
+ */
+ if (uio->uio_resid > dev->si_iosize_max)
+ printf("%s: request size %zd > si_iosize_max=%d, "
+ "cannot split request\n", devtoname(dev),
+ uio->uio_resid, dev->si_iosize_max);
+
+ if (uio->uio_resid > MAXPHYS)
+ printf("%s: request size %zd > MAXPHYS=%d, "
+ "cannot split request\n", devtoname(dev),
+ uio->uio_resid, MAXPHYS);
+
+ if (uio->uio_iovcnt > 1)
+ printf("%s: request vectors=%d > 1, "
+ "cannot split request\n", devtoname(dev),
+ uio->uio_iovcnt);
+
+ error = EFBIG;
+ goto doerror;
+ }
+
for (i = 0; i < uio->uio_iovcnt; i++) {
while (uio->uio_iov[i].iov_len) {
bp->b_flags = 0;
@@ -83,6 +113,17 @@ physio(struct cdev *dev, struct uio *uio, int ioflag)
*/
iolen = ((vm_offset_t) bp->b_data) & PAGE_MASK;
if ((bp->b_bcount + iolen) > bp->b_kvasize) {
+ /*
+ * This device does not want I/O to be split.
+ */
+ if (dev->si_flags & SI_NOSPLIT) {
+ printf("%s: request ptr %#jx is not "
+ "on a page boundary, cannot split "
+ "request\n", devtoname(dev),
+ (uintmax_t)bp->b_data);
+ error = EFBIG;
+ goto doerror;
+ }
bp->b_bcount = bp->b_kvasize;
if (iolen != 0)
bp->b_bcount -= PAGE_SIZE;
diff --git a/sys/sys/conf.h b/sys/sys/conf.h
index fcce859..e9a2f55 100644
--- a/sys/sys/conf.h
+++ b/sys/sys/conf.h
@@ -62,6 +62,7 @@ struct cdev {
#define SI_DUMPDEV 0x0080 /* is kernel dumpdev */
#define SI_CLONELIST 0x0200 /* on a clone list */
#define SI_UNMAPPED 0x0400 /* can handle unmapped I/O */
+#define SI_NOSPLIT 0x0800 /* I/O should not be split up */
struct timespec si_atime;
struct timespec si_ctime;
struct timespec si_mtime;
diff --git a/sys/sys/param.h b/sys/sys/param.h
index d5905fe..67f4835 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -58,7 +58,7 @@
* in the range 5 to 9.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 1000048 /* Master, propagated to newvers */
+#define __FreeBSD_version 1000049 /* Master, propagated to newvers */
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,
OpenPOWER on IntegriCloud