sg.c
来自「linux 内核源代码」· C语言 代码 · 共 2,357 行 · 第 1/5 页
C
2,357 行
/* * 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 - 2005 Douglas Gilbert * * Modified 19-JAN-1998 Richard Gooch <rgooch@atnf.csiro.au> Devfs support * * 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 = 30534; /* 2 digits for each component */#define SG_VERSION_STR "3.5.34"/* * D. P. Gilbert (dgilbert@interlog.com, dougg@triode.net.au), 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/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/scatterlist.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 = "20061027";static int sg_proc_init(void);static void sg_proc_cleanup(void);#endif#define SG_ALLOW_DIO_DEF 0#define SG_ALLOW_DIO_CODE /* compile out by commenting this define */#define SG_MAX_DEVS 32768/* * Suppose you want to calculate the formula muldiv(x,m,d)=int(x * m / d) * Then when using 32 bit integers x * m may overflow during the calculation. * Replacing muldiv(x) by muldiv(x)=((x % d) * m) / d + int(x / d) * m * calculates the same, but prevents the overflow when both m and d * are "small" numbers (like HZ and USER_HZ). * Of course an overflow is inavoidable if the result of muldiv doesn't fit * in 32 bits. */#define MULDIV(X,MUL,DIV) ((((X % DIV) * MUL) / DIV) + ((X / DIV) * MUL))#define SG_DEFAULT_TIMEOUT MULDIV(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#define SG_SECTOR_MSK (SG_SECTOR_SZ - 1)static int sg_add(struct class_device *, struct class_interface *);static void sg_remove(struct class_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 = sg_add, .remove = sg_remove,};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 */ unsigned b_malloc_len; /* actual len malloc'ed in buffer */ struct scatterlist *buffer;/* scatter list */ 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 */ volatile char done; /* 0->before bh, 1->before read, 2->read */} Sg_request;typedef struct sg_fd { /* holds the state of a file descriptor */ struct sg_fd *nextfp; /* NULL when last opened fd on this device */ 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 */ volatile char closed; /* 1 -> fd closed but request(s) outstanding */ char cmd_q; /* 1 -> allow command queuing, 0 -> don't */ char next_cmd_len; /* 0 -> automatic (def), >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 */} Sg_fd;typedef struct sg_device { /* holds the state of each scsi generic device */ struct scsi_device *device; wait_queue_head_t o_excl_wait; /* queue open() when O_EXCL in use */ int sg_tablesize; /* adapter's max scatter-gather table size */ u32 index; /* device index number */ Sg_fd *headfp; /* first open fd belonging to this device */ volatile char detached; /* 0->attached, 1->detached pending removal */ volatile char exclude; /* opened for exclusive access */ 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>] */} Sg_device;static int sg_fasync(int fd, struct file *filp, int mode);/* tasklet or soft irq callback */static void sg_cmd_done(void *data, char *sense, int result, int resid);static int sg_start_req(Sg_request * srp);static void sg_finish_rem_req(Sg_request * srp);static int sg_build_indirect(Sg_scatter_hold * schp, Sg_fd * sfp, int buff_size);static int sg_build_sgat(Sg_scatter_hold * schp, const Sg_fd * sfp, int tablesize);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, const char __user *buf, size_t count, int blocking, int read_only, 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_u_iovec(sg_io_hdr_t * hp, int sg_num, int ind, int wr_xf, int *countp, unsigned char __user **up);static int sg_write_xfer(Sg_request * srp);static int sg_read_xfer(Sg_request * srp);static int sg_read_oxfer(Sg_request * srp, char __user *outp, int num_read_xfer);static void sg_remove_scat(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 struct page *sg_page_malloc(int rqSz, int lowDma, int *retSzp);static void sg_page_free(struct page *page, int size);static Sg_fd *sg_add_sfp(Sg_device * sdp, int dev);static int sg_remove_sfp(Sg_device * sdp, Sg_fd * sfp);static void __sg_remove_sfp(Sg_device * sdp, Sg_fd * sfp);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 int sg_allow_access(unsigned char opcode, char dev_type);static int sg_build_direct(Sg_request * srp, Sg_fd * sfp, int dxfer_len);static Sg_device *sg_get_dev(int dev);#ifdef CONFIG_SCSI_PROC_FSstatic int sg_last_dev(void);#endif#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)static intsg_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 res; int retval; nonseekable_open(inode, filp); SCSI_LOG_TIMEOUT(3, printk("sg_open: dev=%d, flags=0x%x\n", dev, flags)); sdp = sg_get_dev(dev); if ((!sdp) || (!sdp->device)) return -ENXIO; if (sdp->detached) return -ENODEV; /* 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) return retval; if (!((flags & O_NONBLOCK) || scsi_block_when_processing_errors(sdp->device))) { retval = -ENXIO; /* we are in error recovery for this device */ goto error_out; } if (flags & O_EXCL) { if (O_RDONLY == (flags & O_ACCMODE)) { retval = -EPERM; /* Can't lock it with read only access */ goto error_out; } if (sdp->headfp && (flags & O_NONBLOCK)) { retval = -EBUSY; goto error_out; } res = 0; __wait_event_interruptible(sdp->o_excl_wait, ((sdp->headfp || sdp->exclude) ? 0 : (sdp->exclude = 1)), res); if (res) { retval = res; /* -ERESTARTSYS because signal hit process */ goto error_out; } } else if (sdp->exclude) { /* some other fd has an exclusive lock on dev */ if (flags & O_NONBLOCK) { retval = -EBUSY; goto error_out; } res = 0; __wait_event_interruptible(sdp->o_excl_wait, (!sdp->exclude), res); if (res) { retval = res; /* -ERESTARTSYS because signal hit process */ goto error_out; } } if (sdp->detached) { retval = -ENODEV; goto error_out; } if (!sdp->headfp) { /* no existing opens on this device */ sdp->sgdebug = 0; q = sdp->device->request_queue; sdp->sg_tablesize = min(q->max_hw_segments, q->max_phys_segments); } if ((sfp = sg_add_sfp(sdp, dev))) filp->private_data = sfp; else { if (flags & O_EXCL) sdp->exclude = 0; /* undo if error */ retval = -ENOMEM; goto error_out; } return 0; error_out: scsi_device_put(sdp->device); return retval;}/* Following function was formerly called 'sg_close' */static intsg_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, printk("sg_release: %s\n", sdp->disk->disk_name)); sg_fasync(-1, filp, 0); /* remove filp from async notification list */ if (0 == sg_remove_sfp(sdp, sfp)) { /* Returns 1 when sdp gone */ if (!sdp->detached) { scsi_device_put(sdp->device); } sdp->exclude = 0; wake_up_interruptible(&sdp->o_excl_wait); } return 0;}static ssize_tsg_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, printk("sg_read: %s, count=%d\n", sdp->disk->disk_name, (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 (sdp->detached) { retval = -ENODEV; goto free_old_hdr; } if (filp->f_flags & O_NONBLOCK) { retval = -EAGAIN; goto free_old_hdr; } while (1) { retval = 0; /* following macro beats race condition */ __wait_event_interruptible(sfp->read_wait, (sdp->detached || (srp = sg_get_rq_mark(sfp, req_pack_id))), retval); if (sdp->detached) { retval = -ENODEV; goto free_old_hdr; } if (0 == retval) break; /* -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:
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?