Release 4.11 drivers/scsi/sg.c
/*
* History:
* Started: Aug 9 by Lawrence Foard (entropy@world.std.com),
* to allow user process control of SCSI devices.
* Development Sponsored by Killy Corp. NY NY
*
* Original driver (sg.c):
* Copyright (C) 1992 Lawrence Foard
* Version 2 and 3 extensions to driver:
* Copyright (C) 1998 - 2014 Douglas Gilbert
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
*/
static int sg_version_num = 30536;
/* 2 digits for each component */
#define SG_VERSION_STR "3.5.36"
/*
* D. P. Gilbert (dgilbert@interlog.com), notes:
* - scsi logging is available via SCSI_LOG_TIMEOUT macros. First
* the kernel/module needs to be built with CONFIG_SCSI_LOGGING
* (otherwise the macros compile to empty statements).
*
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/mtio.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/idr.h>
#include <linux/seq_file.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/blktrace_api.h>
#include <linux/mutex.h>
#include <linux/atomic.h>
#include <linux/ratelimit.h>
#include <linux/uio.h>
#include "scsi.h"
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_driver.h>
#include <scsi/scsi_ioctl.h>
#include <scsi/sg.h>
#include "scsi_logging.h"
#ifdef CONFIG_SCSI_PROC_FS
#include <linux/proc_fs.h>
static char *sg_version_date = "20140603";
static int sg_proc_init(void);
static void sg_proc_cleanup(void);
#endif
#define SG_ALLOW_DIO_DEF 0
#define SG_MAX_DEVS 32768
/* SG_MAX_CDB_SIZE should be 260 (spc4r37 section 3.1.30) however the type
* of sg_io_hdr::cmd_len can only represent 255. All SCSI commands greater
* than 16 bytes are "variable length" whose length is a multiple of 4
*/
#define SG_MAX_CDB_SIZE 252
#define SG_DEFAULT_TIMEOUT mult_frac(SG_DEFAULT_TIMEOUT_USER, HZ, USER_HZ)
int sg_big_buff = SG_DEF_RESERVED_SIZE;
/* N.B. This variable is readable and writeable via
/proc/scsi/sg/def_reserved_size . Each time sg_open() is called a buffer
of this size (or less if there is not enough memory) will be reserved
for use by this file descriptor. [Deprecated usage: this variable is also
readable via /proc/sys/kernel/sg-big-buff if the sg driver is built into
the kernel (i.e. it is not a module).] */
static int def_reserved_size = -1;
/* picks up init parameter */
static int sg_allow_dio = SG_ALLOW_DIO_DEF;
static int scatter_elem_sz = SG_SCATTER_SZ;
static int scatter_elem_sz_prev = SG_SCATTER_SZ;
#define SG_SECTOR_SZ 512
static int sg_add_device(struct device *, struct class_interface *);
static void sg_remove_device(struct device *, struct class_interface *);
static DEFINE_IDR(sg_index_idr);
static DEFINE_RWLOCK(sg_index_lock); /* Also used to lock
file descriptor list for device */
static struct class_interface sg_interface = {
.add_dev = sg_add_device,
.remove_dev = sg_remove_device,
};
typedef struct sg_scatter_hold { /* holding area for scsi scatter gather info */
unsigned short k_use_sg; /* Count of kernel scatter-gather pieces */
unsigned sglist_len; /* size of malloc'd scatter-gather list ++ */
unsigned bufflen; /* Size of (aggregate) data buffer */
struct page **pages;
int page_order;
char dio_in_use; /* 0->indirect IO (or mmap), 1->dio */
unsigned char cmd_opcode; /* first byte of command */
}
Sg_scatter_hold;
struct sg_device; /* forward declarations */
struct sg_fd;
typedef struct sg_request { /* SG_MAX_QUEUE requests outstanding per file */
struct sg_request *nextrp; /* NULL -> tail request (slist) */
struct sg_fd *parentfp; /* NULL -> not in use */
Sg_scatter_hold data; /* hold buffer, perhaps scatter list */
sg_io_hdr_t header; /* scsi command+info, see <scsi/sg.h> */
unsigned char sense_b[SCSI_SENSE_BUFFERSIZE];
char res_used; /* 1 -> using reserve buffer, 0 -> not ... */
char orphan; /* 1 -> drop on sight, 0 -> normal */
char sg_io_owned; /* 1 -> packet belongs to SG_IO */
/* done protected by rq_list_lock */
char done; /* 0->before bh, 1->before read, 2->read */
struct request *rq;
struct bio *bio;
struct execute_work ew;
}
Sg_request;
typedef struct sg_fd { /* holds the state of a file descriptor */
struct list_head sfd_siblings; /* protected by device's sfd_lock */
struct sg_device *parentdp; /* owning device */
wait_queue_head_t read_wait; /* queue read until command done */
rwlock_t rq_list_lock; /* protect access to list in req_arr */
int timeout; /* defaults to SG_DEFAULT_TIMEOUT */
int timeout_user; /* defaults to SG_DEFAULT_TIMEOUT_USER */
Sg_scatter_hold reserve; /* buffer held for this file descriptor */
unsigned save_scat_len; /* original length of trunc. scat. element */
Sg_request *headrp; /* head of request slist, NULL->empty */
struct fasync_struct *async_qp; /* used by asynchronous notification */
Sg_request req_arr[SG_MAX_QUEUE]; /* used as singly-linked list */
char low_dma; /* as in parent but possibly overridden to 1 */
char force_packid; /* 1 -> pack_id input to read(), 0 -> ignored */
char cmd_q; /* 1 -> allow command queuing, 0 -> don't */
unsigned char next_cmd_len; /* 0: automatic, >0: use on next write() */
char keep_orphan; /* 0 -> drop orphan (def), 1 -> keep for read() */
char mmap_called; /* 0 -> mmap() never called on this fd */
struct kref f_ref;
struct execute_work ew;
}
Sg_fd;
typedef struct sg_device { /* holds the state of each scsi generic device */
struct scsi_device *device;
wait_queue_head_t open_wait; /* queue open() when O_EXCL present */
struct mutex open_rel_lock; /* held when in open() or release() */
int sg_tablesize; /* adapter's max scatter-gather table size */
u32 index; /* device index number */
struct list_head sfds;
rwlock_t sfd_lock; /* protect access to sfd list */
atomic_t detaching; /* 0->device usable, 1->device detaching */
bool exclude; /* 1->open(O_EXCL) succeeded and is active */
int open_cnt; /* count of opens (perhaps < num(sfds) ) */
char sgdebug; /* 0->off, 1->sense, 9->dump dev, 10-> all devs */
struct gendisk *disk;
struct cdev * cdev; /* char_dev [sysfs: /sys/cdev/major/sg<n>] */
struct kref d_ref;
}
Sg_device;
/* tasklet or soft irq callback */
static void sg_rq_end_io(struct request *rq, int uptodate);
static int sg_start_req(Sg_request *srp, unsigned char *cmd);
static int sg_finish_rem_req(Sg_request * srp);
static int sg_build_indirect(Sg_scatter_hold * schp, Sg_fd * sfp, int buff_size);
static ssize_t sg_new_read(Sg_fd * sfp, char __user *buf, size_t count,
Sg_request * srp);
static ssize_t sg_new_write(Sg_fd *sfp, struct file *file,
const char __user *buf, size_t count, int blocking,
int read_only, int sg_io_owned, Sg_request **o_srp);
static int sg_common_write(Sg_fd * sfp, Sg_request * srp,
unsigned char *cmnd, int timeout, int blocking);
static int sg_read_oxfer(Sg_request * srp, char __user *outp, int num_read_xfer);
static void sg_remove_scat(Sg_fd * sfp, Sg_scatter_hold * schp);
static void sg_build_reserve(Sg_fd * sfp, int req_size);
static void sg_link_reserve(Sg_fd * sfp, Sg_request * srp, int size);
static void sg_unlink_reserve(Sg_fd * sfp, Sg_request * srp);
static Sg_fd *sg_add_sfp(Sg_device * sdp);
static void sg_remove_sfp(struct kref *);
static Sg_request *sg_get_rq_mark(Sg_fd * sfp, int pack_id);
static Sg_request *sg_add_request(Sg_fd * sfp);
static int sg_remove_request(Sg_fd * sfp, Sg_request * srp);
static int sg_res_in_use(Sg_fd * sfp);
static Sg_device *sg_get_dev(int dev);
static void sg_device_destroy(struct kref *kref);
#define SZ_SG_HEADER sizeof(struct sg_header)
#define SZ_SG_IO_HDR sizeof(sg_io_hdr_t)
#define SZ_SG_IOVEC sizeof(sg_iovec_t)
#define SZ_SG_REQ_INFO sizeof(sg_req_info_t)
#define sg_printk(prefix, sdp, fmt, a...) \
sdev_prefix_printk(prefix, (sdp)->device, \
(sdp)->disk->disk_name, fmt, ##a)
static int sg_allow_access(struct file *filp, unsigned char *cmd)
{
struct sg_fd *sfp = filp->private_data;
if (sfp->parentdp->device->type == TYPE_SCANNER)
return 0;
return blk_verify_command(cmd, filp->f_mode & FMODE_WRITE);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
FUJITA Tomonori | 52 | 100.00% | 1 | 100.00% |
Total | 52 | 100.00% | 1 | 100.00% |
static int
open_wait(Sg_device *sdp, int flags)
{
int retval = 0;
if (flags & O_EXCL) {
while (sdp->open_cnt > 0) {
mutex_unlock(&sdp->open_rel_lock);
retval = wait_event_interruptible(sdp->open_wait,
(atomic_read(&sdp->detaching) ||
!sdp->open_cnt));
mutex_lock(&sdp->open_rel_lock);
if (retval) /* -ERESTARTSYS */
return retval;
if (atomic_read(&sdp->detaching))
return -ENODEV;
}
} else {
while (sdp->exclude) {
mutex_unlock(&sdp->open_rel_lock);
retval = wait_event_interruptible(sdp->open_wait,
(atomic_read(&sdp->detaching) ||
!sdp->exclude));
mutex_lock(&sdp->open_rel_lock);
if (retval) /* -ERESTARTSYS */
return retval;
if (atomic_read(&sdp->detaching))
return -ENODEV;
}
}
return retval;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Douglas Gilbert | 122 | 70.11% | 1 | 33.33% |
James Bottomley | 32 | 18.39% | 1 | 33.33% |
Jörn Engel | 20 | 11.49% | 1 | 33.33% |
Total | 174 | 100.00% | 3 | 100.00% |
/* Returns 0 on success, else a negated errno value */
static int
sg_open(struct inode *inode, struct file *filp)
{
int dev = iminor(inode);
int flags = filp->f_flags;
struct request_queue *q;
Sg_device *sdp;
Sg_fd *sfp;
int retval;
nonseekable_open(inode, filp);
if ((flags & O_EXCL) && (O_RDONLY == (flags & O_ACCMODE)))
return -EPERM; /* Can't lock it with read only access */
sdp = sg_get_dev(dev);
if (IS_ERR(sdp))
return PTR_ERR(sdp);
SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sdp,
"sg_open: flags=0x%x\n", flags));
/* This driver's module count bumped by fops_get in <linux/fs.h> */
/* Prevent the device driver from vanishing while we sleep */
retval = scsi_device_get(sdp->device);
if (retval)
goto sg_put;
retval = scsi_autopm_get_device(sdp->device);
if (retval)
goto sdp_put;
/* scsi_block_when_processing_errors() may block so bypass
* check if O_NONBLOCK. Permits SCSI commands to be issued
* during error recovery. Tread carefully. */
if (!((flags & O_NONBLOCK) ||
scsi_block_when_processing_errors(sdp->device))) {
retval = -ENXIO;
/* we are in error recovery for this device */
goto error_out;
}
mutex_lock(&sdp->open_rel_lock);
if (flags & O_NONBLOCK) {
if (flags & O_EXCL) {
if (sdp->open_cnt > 0) {
retval = -EBUSY;
goto error_mutex_locked;
}
} else {
if (sdp->exclude) {
retval = -EBUSY;
goto error_mutex_locked;
}
}
} else {
retval = open_wait(sdp, flags);
if (retval) /* -ERESTARTSYS or -ENODEV */
goto error_mutex_locked;
}
/* N.B. at this point we are holding the open_rel_lock */
if (flags & O_EXCL)
sdp->exclude = true;
if (sdp->open_cnt < 1) { /* no existing opens */
sdp->sgdebug = 0;
q = sdp->device->request_queue;
sdp->sg_tablesize = queue_max_segments(q);
}
sfp = sg_add_sfp(sdp);
if (IS_ERR(sfp)) {
retval = PTR_ERR(sfp);
goto out_undo;
}
filp->private_data = sfp;
sdp->open_cnt++;
mutex_unlock(&sdp->open_rel_lock);
retval = 0;
sg_put:
kref_put(&sdp->d_ref, sg_device_destroy);
return retval;
out_undo:
if (flags & O_EXCL) {
sdp->exclude = false; /* undo if error */
wake_up_interruptible(&sdp->open_wait);
}
error_mutex_locked:
mutex_unlock(&sdp->open_rel_lock);
error_out:
scsi_autopm_put_device(sdp->device);
sdp_put:
scsi_device_put(sdp->device);
goto sg_put;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Douglas Gilbert | 151 | 35.53% | 2 | 7.41% |
Linus Torvalds (pre-git) | 119 | 28.00% | 12 | 44.44% |
Linus Torvalds | 42 | 9.88% | 2 | 7.41% |
James Bottomley | 29 | 6.82% | 2 | 7.41% |
Alan Stern | 25 | 5.88% | 1 | 3.70% |
Tony Battersby | 20 | 4.71% | 1 | 3.70% |
Hannes Reinecke | 16 | 3.76% | 1 | 3.70% |
Christoph Hellwig | 8 | 1.88% | 1 | 3.70% |
Mike Christie | 7 | 1.65% | 1 | 3.70% |
Vaughan Cao | 5 | 1.18% | 1 | 3.70% |
Al Viro | 1 | 0.24% | 1 | 3.70% |
Martin K. Petersen | 1 | 0.24% | 1 | 3.70% |
Doug Ledford | 1 | 0.24% | 1 | 3.70% |
Total | 425 | 100.00% | 27 | 100.00% |
/* Release resources associated with a successful sg_open()
* Returns 0 on success, else a negated errno value */
static int
sg_release(struct inode *inode, struct file *filp)
{
Sg_device *sdp;
Sg_fd *sfp;
if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp)))
return -ENXIO;
SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sdp, "sg_release\n"));
mutex_lock(&sdp->open_rel_lock);
scsi_autopm_put_device(sdp->device);
kref_put(&sfp->f_ref, sg_remove_sfp);
sdp->open_cnt--;
/* possibly many open()s waiting on exlude clearing, start many;
* only open(O_EXCL)s wait on 0==open_cnt so only start one */
if (sdp->exclude) {
sdp->exclude = false;
wake_up_interruptible_all(&sdp->open_wait);
} else if (0 == sdp->open_cnt) {
wake_up_interruptible(&sdp->open_wait);
}
mutex_unlock(&sdp->open_rel_lock);
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 71 | 46.41% | 3 | 37.50% |
Douglas Gilbert | 57 | 37.25% | 1 | 12.50% |
Tony Battersby | 10 | 6.54% | 1 | 12.50% |
Alan Stern | 7 | 4.58% | 1 | 12.50% |
Vaughan Cao | 4 | 2.61% | 1 | 12.50% |
Hannes Reinecke | 4 | 2.61% | 1 | 12.50% |
Total | 153 | 100.00% | 8 | 100.00% |
static ssize_t
sg_read(struct file *filp, char __user *buf, size_t count, loff_t * ppos)
{
Sg_device *sdp;
Sg_fd *sfp;
Sg_request *srp;
int req_pack_id = -1;
sg_io_hdr_t *hp;
struct sg_header *old_hdr = NULL;
int retval = 0;
if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp)))
return -ENXIO;
SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sdp,
"sg_read: count=%d\n", (int) count));
if (!access_ok(VERIFY_WRITE, buf, count))
return -EFAULT;
if (sfp->force_packid && (count >= SZ_SG_HEADER)) {
old_hdr = kmalloc(SZ_SG_HEADER, GFP_KERNEL);
if (!old_hdr)
return -ENOMEM;
if (__copy_from_user(old_hdr, buf, SZ_SG_HEADER)) {
retval = -EFAULT;
goto free_old_hdr;
}
if (old_hdr->reply_len < 0) {
if (count >= SZ_SG_IO_HDR) {
sg_io_hdr_t *new_hdr;
new_hdr = kmalloc(SZ_SG_IO_HDR, GFP_KERNEL);
if (!new_hdr) {
retval = -ENOMEM;
goto free_old_hdr;
}
retval =__copy_from_user
(new_hdr, buf, SZ_SG_IO_HDR);
req_pack_id = new_hdr->pack_id;
kfree(new_hdr);
if (retval) {
retval = -EFAULT;
goto free_old_hdr;
}
}
} else
req_pack_id = old_hdr->pack_id;
}
srp = sg_get_rq_mark(sfp, req_pack_id);
if (!srp) { /* now wait on packet to arrive */
if (atomic_read(&sdp->detaching)) {
retval = -ENODEV;
goto free_old_hdr;
}
if (filp->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
goto free_old_hdr;
}
retval = wait_event_interruptible(sfp->read_wait,
(atomic_read(&sdp->detaching) ||
(srp = sg_get_rq_mark(sfp, req_pack_id))));
if (atomic_read(&sdp->detaching)) {
retval = -ENODEV;
goto free_old_hdr;
}
if (retval) {
/* -ERESTARTSYS as signal hit process */
goto free_old_hdr;
}
}
if (srp->header.interface_id != '\0') {
retval = sg_new_read(sfp, buf, count, srp);
goto free_old_hdr;
}
hp = &srp->header;
if (old_hdr == NULL) {
old_hdr = kmalloc(SZ_SG_HEADER, GFP_KERNEL);
if (! old_hdr) {
retval = -ENOMEM;
goto free_old_hdr;
}
}
memset(old_hdr, 0, SZ_SG_HEADER);
old_hdr->reply_len = (int) hp->timeout;
old_hdr->pack_len = old_hdr->reply_len; /* old, strange behaviour */
old_hdr->pack_id = hp->pack_id;
old_hdr->twelve_byte =
((srp->data.cmd_opcode >= 0xc0) && (12 == hp->cmd_len)) ? 1 : 0;
old_hdr->target_status = hp->masked_status;
old_hdr->host_status = hp->host_status;
old_hdr->driver_status = hp->driver_status;
if ((CHECK_CONDITION & hp->masked_status) ||
(DRIVER_SENSE & hp->driver_status))
memcpy(old_hdr->sense_buffer, srp->sense_b,
sizeof (old_hdr->sense_buffer));
switch (hp->host_status) {
/* This setup of 'result' is for backward compatibility and is best
ignored by the user who should use target, host + driver status */
case DID_OK:
case DID_PASSTHROUGH:
case DID_SOFT_ERROR:
old_hdr->result = 0;
break;
case DID_NO_CONNECT:
case DID_BUS_BUSY:
case DID_TIME_OUT:
old_hdr->result = EBUSY;
break;
case DID_BAD_TARGET:
case DID_ABORT:
case DID_PARITY:
case DID_RESET:
case DID_BAD_INTR:
old_hdr->result = EIO;
break;
case DID_ERROR:
old_hdr->result = (srp->sense_b[0] == 0 &&
hp->masked_status == GOOD) ? 0 : EIO;
break;
default:
old_hdr->result = EIO;
break;
}
/* Now copy the result back to the user buffer. */
if (count >= SZ_SG_HEADER) {
if (__copy_to_user(buf, old_hdr, SZ_SG_HEADER)) {
retval = -EFAULT;
goto free_old_hdr;
}
buf += SZ_SG_HEADER;
if (count > old_hdr->reply_len)
count = old_hdr->reply_len;
if (count > SZ_SG_HEADER) {
if (sg_read_oxfer(srp, buf, count - SZ_SG_HEADER)) {
retval = -EFAULT;
goto free_old_hdr;
}
}
} else
count = (old_hdr->result == 0) ? 0 : -EIO;
sg_finish_rem_req(srp);
retval = count;
free_old_hdr:
kfree(old_hdr);
return retval;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 507 | 64.42% | 12 | 54.55% |
Douglas Gilbert | 240 | 30.50% | 4 | 18.18% |
Linus Torvalds | 29 | 3.68% | 1 | 4.55% |
Jesper Juhl | 4 | 0.51% | 1 | 4.55% |
Hannes Reinecke | 4 | 0.51% | 1 | 4.55% |
Jörn Engel | 2 | 0.25% | 2 | 9.09% |
Al Viro | 1 | 0.13% | 1 | 4.55% |
Total | 787 | 100.00% | 22 | 100.00% |
static ssize_t
sg_new_read(Sg_fd * sfp, char __user *buf, size_t count, Sg_request * srp)
{
sg_io_hdr_t *hp = &srp->header;
int err = 0, err2;
int len;
if (count < SZ_SG_IO_HDR) {
err = -EINVAL;
goto err_out;
}
hp->sb_len_wr = 0;
if ((hp->mx_sb_len > 0) && hp->sbp) {
if ((CHECK_CONDITION & hp->masked_status) ||
(DRIVER_SENSE & hp->driver_status)) {
int sb_len = SCSI_SENSE_BUFFERSIZE;
sb_len = (hp->mx_sb_len > sb_len) ? sb_len : hp->mx_sb_len;
len = 8 + (int) srp->sense_b[7]; /* Additional sense length field */
len = (len > sb_len) ? sb_len : len;
if (copy_to_user(hp->sbp, srp->sense_b, len)) {
err = -EFAULT;
goto err_out;
}
hp->sb_len_wr = len;
}
}
if (hp->masked_status || hp->host_status || hp->driver_status)
hp->info |= SG_INFO_CHECK;
if (copy_to_user(buf, hp, SZ_SG_IO_HDR)) {
err = -EFAULT;
goto err_out;
}
err_out:
err2 = sg_finish_rem_req(srp);
return err ? : err2 ? : count;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 184 | 77.64% | 5 | 41.67% |
Douglas Gilbert | 22 | 9.28% | 1 | 8.33% |
Linus Torvalds | 20 | 8.44% | 1 | 8.33% |
Tony Battersby | 7 | 2.95% | 1 | 8.33% |
Al Viro | 1 | 0.42% | 1 | 8.33% |
FUJITA Tomonori | 1 | 0.42% | 1 | 8.33% |
Dave Jones | 1 | 0.42% | 1 | 8.33% |
Mike Christie | 1 | 0.42% | 1 | 8.33% |
Total | 237 | 100.00% | 12 | 100.00% |
static ssize_t
sg_write(struct file *filp, const char __user *buf, size_t count, loff_t * ppos)
{
int mxsize, cmd_size, k;
int input_size, blocking;
unsigned char opcode;
Sg_device *sdp;
Sg_fd *sfp;
Sg_request *srp;
struct sg_header old_hdr;
sg_io_hdr_t *hp;
unsigned char cmnd[SG_MAX_CDB_SIZE];
if (unlikely(segment_eq(get_fs(), KERNEL_DS)))
return -EINVAL;
if ((!(sfp = (Sg_fd *) filp->private_data)) || (!(sdp = sfp->parentdp)))
return -ENXIO;
SCSI_LOG_TIMEOUT(3, sg_printk(KERN_INFO, sdp,
"sg_write: count=%d\n", (int) count));
if (atomic_read(&sdp->detaching))
return -ENODEV;
if (!((filp->f_flags & O_NONBLOCK) ||
scsi_block_when_processing_errors(sdp->device)))
return -ENXIO;
if (!access_ok(VERIFY_READ, buf, count))
return -EFAULT; /* protects following copy_from_user()s + get_user()s */
if (count < SZ_SG_HEADER)
return -EIO;
if (__copy_from_user(&old_hdr, buf, SZ_SG_HEADER))
return -EFAULT;
blocking = !(filp->f_flags & O_NONBLOCK);
if (old_hdr.reply_len < 0)
return sg_new_write(sfp, filp, buf, count,
blocking, 0, 0, NULL);
if (count < (SZ_SG_HEADER + 6))
return -EIO; /* The minimum scsi command length is 6 bytes. */
if (!(srp = sg_add_request(sfp))) {
SCSI_LOG_TIMEOUT(1, sg_printk(KERN_INFO, sdp,
"sg_write: queue full\n"));
return -EDOM;
}
buf += SZ_SG_HEADER;
__get_user(opcode, buf);
if (sfp->next_cmd_len > 0) {
cmd_size = sfp->next_cmd_len;
sfp->next_cmd_len = 0; /* reset so only this write() effected */
} else {
cmd_size = COMMAND_SIZE(opcode); /* based on SCSI command group */
if ((opcode >= 0xc0) && old_hdr.twelve_byte)
cmd_size = 12;
}
SCSI_LOG_TIMEOUT(4, sg_printk(KERN_INFO, sdp,
"sg_write: scsi opcode=0x%02x, cmd_size=%d\n", (int) opcode, cmd_size));
/* Determine buffer size. */
input_size = count - cmd_size;
mxsize = (input_size > old_hdr.reply_len) ? input_size : old_hdr.reply_len;
mxsize -= SZ_SG_HEADER;
input_size -= SZ_SG_HEADER;
if (input_size < 0) {
sg_remove_request(sfp, srp);
return -EIO; /* User did not pass enough bytes for this command. */
}
hp = &srp->header;
hp->interface_id = '\0'; /* indicator of old interface tunnelled */