blob: 7509699cf88c514f6887277d94be0b93c12015e8 [file] [log] [blame]
/*
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
Borrows code from st driver.
*/
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mtio.h>
#include <linux/ioctl.h>
#include <linux/fcntl.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include "../block/blk.h"
#include "scsi.h"
#include "scsi_ioctl.h"
#include "sg.h"
#define MAJOR_NR 21
int NR_SG=0;
int MAX_SG=0;
#ifdef SG_BIG_BUFF
static char *big_buff;
static struct wait_queue *big_wait; /* wait for buffer available */
static int big_inuse=0;
#endif
struct scsi_generic
{
Scsi_Device *device;
int users; /* how many people have it open? */
struct wait_queue *generic_wait; /* wait for device to be available */
struct wait_queue *read_wait; /* wait for response */
struct wait_queue *write_wait; /* wait for free buffer */
int timeout; /* current default value for device */
int buff_len; /* length of current buffer */
char *buff; /* the buffer */
struct sg_header header; /* header of pending command */
char exclude; /* opened for exclusive access */
char pending; /* don't accept writes now */
char complete; /* command complete allow a read */
};
static struct scsi_generic *scsi_generics=NULL;
static int sg_ioctl(struct inode * inode,struct file * file,
unsigned int cmd_in, unsigned long arg)
{
int dev = MINOR(inode->i_rdev);
if ((dev<0) || (dev>=NR_SG))
return -ENODEV;
switch(cmd_in)
{
case SG_SET_TIMEOUT:
scsi_generics[dev].timeout=get_fs_long((int *) arg);
return 0;
case SG_GET_TIMEOUT:
return scsi_generics[dev].timeout;
default:
return scsi_ioctl(scsi_generics[dev].device, cmd_in, (void *) arg);
}
}
static int sg_open(struct inode * inode, struct file * filp)
{
int dev=MINOR(inode->i_rdev);
int flags=filp->f_flags;
if (dev>=NR_SG)
return -ENODEV;
if (O_RDWR!=(flags & O_ACCMODE))
return -EACCES;
if (flags & O_EXCL)
{
while(scsi_generics[dev].users)
{
if (flags & O_NONBLOCK)
return -EBUSY;
interruptible_sleep_on(&scsi_generics[dev].generic_wait);
if (current->signal & ~current->blocked)
return -ERESTARTSYS;
}
scsi_generics[dev].exclude=1;
}
else
while(scsi_generics[dev].exclude)
{
if (flags & O_NONBLOCK)
return -EBUSY;
interruptible_sleep_on(&scsi_generics[dev].generic_wait);
if (current->signal & ~current->blocked)
return -ERESTARTSYS;
}
if (!scsi_generics[dev].users && scsi_generics[dev].pending && scsi_generics[dev].complete)
{
scsi_free(scsi_generics[dev].buff,scsi_generics[dev].buff_len);
scsi_generics[dev].pending=0;
}
if (!scsi_generics[dev].users)
scsi_generics[dev].timeout=SG_DEFAULT_TIMEOUT;
scsi_generics[dev].users++;
return 0;
}
static void sg_close(struct inode * inode, struct file * filp)
{
int dev=MINOR(inode->i_rdev);
scsi_generics[dev].users--;
scsi_generics[dev].exclude=0;
wake_up(&scsi_generics[dev].generic_wait);
}
static char *sg_malloc(int size)
{
if (size<=4096)
return (char *) scsi_malloc(size);
#ifdef SG_BIG_BUFF
if (size<SG_BIG_BUFF)
{
while(big_inuse)
{
interruptible_sleep_on(&big_wait);
if (current->signal & ~current->blocked)
return NULL;
}
big_inuse=1;
return big_buff;
}
#endif
return NULL;
}
static void sg_free(char *buff,int size)
{
#ifdef SG_BIG_BUFF
if (buff==big_buff)
{
big_inuse=0;
wake_up(&big_wait);
return;
}
#endif
scsi_free(buff,size);
}
static int sg_read(struct inode *inode,struct file *filp,char *buf,int count)
{
int dev=MINOR(inode->i_rdev);
int i;
struct scsi_generic *device=&scsi_generics[dev];
if ((i=verify_area(VERIFY_WRITE,buf,count)))
return i;
while(!device->pending || !device->complete)
{
if (filp->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
interruptible_sleep_on(&device->read_wait);
if (current->signal & ~current->blocked)
return -ERESTARTSYS;
}
device->header.pack_len=device->header.reply_len;
device->header.result=0;
if (count>=sizeof(struct sg_header))
{
memcpy_tofs(buf,&device->header,sizeof(struct sg_header));
buf+=sizeof(struct sg_header);
if (count>device->header.pack_len)
count=device->header.pack_len;
memcpy_tofs(buf,device->buff,count-sizeof(struct sg_header));
}
else
count=0;
sg_free(device->buff,device->buff_len);
device->pending=0;
wake_up(&device->write_wait);
return count;
}
static void sg_command_done(Scsi_Cmnd * SCpnt)
{
int dev=SCpnt->request.dev;
struct scsi_generic *device=&scsi_generics[dev];
if (!device->pending)
{
printk("unexpected done for sg %d\n",dev);
SCpnt->request.dev=-1;
return;
}
if (SCpnt->sense_buffer[0])
{
device->header.result=EIO;
}
else
device->header.result=SCpnt->result;
device->complete=1;
SCpnt->request.dev=-1;
wake_up(&scsi_generics[dev].read_wait);
}
static int sg_write(struct inode *inode,struct file *filp,char *buf,int count)
{
int dev=MINOR(inode->i_rdev);
Scsi_Cmnd *SCpnt;
int bsize,size,amt,i;
unsigned char cmnd[MAX_COMMAND_SIZE];
struct scsi_generic *device=&scsi_generics[dev];
if ((i=verify_area(VERIFY_READ,buf,count)))
return i;
if (count<sizeof(struct sg_header))
return -EIO;
/* make sure we can fit */
while(device->pending)
{
if (filp->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
#ifdef DEBUG
printk("sg_write: sleeping on pending request\n");
#endif
interruptible_sleep_on(&device->write_wait);
if (current->signal & ~current->blocked)
return -ERESTARTSYS;
}
device->pending=1;
device->complete=0;
memcpy_fromfs(&device->header,buf,sizeof(struct sg_header));
/* fix input size */
device->header.pack_len=count;
buf+=sizeof(struct sg_header);
bsize=(device->header.pack_len>device->header.reply_len) ? device->header.pack_len : device->header.reply_len;
bsize-=sizeof(struct sg_header);
amt=bsize;
if (!bsize)
bsize++;
bsize=(bsize+511) & ~511;
if ((bsize<0) || !(device->buff=sg_malloc(device->buff_len=bsize)))
{
device->pending=0;
wake_up(&device->write_wait);
return -ENOMEM;
}
#ifdef DEBUG
printk("allocating device\n");
#endif
if (!(SCpnt=allocate_device(NULL,device->device->index, !(filp->f_flags & O_NONBLOCK))))
{
device->pending=0;
wake_up(&device->write_wait);
sg_free(device->buff,device->buff_len);
return -EWOULDBLOCK;
}
#ifdef DEBUG
printk("device allocated\n");
#endif
/* now issue command */
SCpnt->request.dev=dev;
SCpnt->sense_buffer[0]=0;
size=COMMAND_SIZE(get_fs_byte(buf));
memcpy_fromfs(cmnd,buf,size);
buf+=size;
memcpy_fromfs(device->buff,buf,device->header.pack_len-size-sizeof(struct sg_header));
cmnd[1]=(cmnd[1] & 0x1f) | (device->device->lun<<5);
#ifdef DEBUG
printk("do cmd\n");
#endif
scsi_do_cmd (SCpnt,(void *) cmnd,
(void *) device->buff,amt,sg_command_done,device->timeout,SG_DEFAULT_RETRIES);
#ifdef DEBUG
printk("done cmd\n");
#endif
return count;
}
static struct file_operations sg_fops = {
NULL, /* lseek */
sg_read, /* read */
sg_write, /* write */
NULL, /* readdir */
NULL, /* select */
sg_ioctl, /* ioctl */
NULL, /* mmap */
sg_open, /* open */
sg_close, /* release */
NULL /* fsync */
};
/* Driver initialization */
unsigned long sg_init(unsigned long mem_start, unsigned long mem_end)
{
if (register_chrdev(MAJOR_NR,"sg",&sg_fops))
{
printk("Unable to get major %d for Generic SCSI device\n",MAJOR_NR);
return mem_start;
}
if (NR_SG == 0) return mem_start;
#ifdef DEBUG
printk("sg: Init generic device.\n");
#endif
#ifdef SG_BIG_BUFF
big_buff= (char *) mem_start;
mem_start+=SG_BIG_BUFF;
#endif
return mem_start;
}
unsigned long sg_init1(unsigned long mem_start, unsigned long mem_end)
{
scsi_generics = (struct scsi_generic *) mem_start;
mem_start += MAX_SG * sizeof(struct scsi_generic);
return mem_start;
};
void sg_attach(Scsi_Device * SDp)
{
if(NR_SG >= MAX_SG)
panic ("scsi_devices corrupt (sg)");
scsi_generics[NR_SG].device=SDp;
scsi_generics[NR_SG].users=0;
scsi_generics[NR_SG].generic_wait=NULL;
scsi_generics[NR_SG].read_wait=NULL;
scsi_generics[NR_SG].write_wait=NULL;
scsi_generics[NR_SG].exclude=0;
scsi_generics[NR_SG].pending=0;
scsi_generics[NR_SG].timeout=SG_DEFAULT_TIMEOUT;
NR_SG++;
};