blob: 64835f6b6b8072176c3a343265a225fc488468ea [file] [log] [blame]
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include "../block/blk.h"
#include "scsi.h"
#include "hosts.h"
#include "scsi_ioctl.h"
#define MAX_RETRIES 5
#define MAX_TIMEOUT 200
#define MAX_BUF 4096
#define max(a,b) (((a) > (b)) ? (a) : (b))
/*
* If we are told to probe a host, we will return 0 if the host is not
* present, 1 if the host is present, and will return an identifying
* string at *arg, if arg is non null, filling to the length stored at
* (int *) arg
*/
static int ioctl_probe(struct Scsi_Host * host, void *buffer)
{
int temp;
unsigned int len,slen;
const char * string;
if ((temp = host->hostt->present) && buffer) {
len = get_fs_long ((unsigned long *) buffer);
string = host->hostt->info();
slen = strlen(string);
if (len > slen)
len = slen + 1;
verify_area(VERIFY_WRITE, buffer, len);
memcpy_tofs (buffer, string, len);
}
return temp;
}
/*
*
* The SCSI_IOCTL_SEND_COMMAND ioctl sends a command out to the SCSI host.
* The MAX_TIMEOUT and MAX_RETRIES variables are used.
*
* dev is the SCSI device struct ptr, *(int *) arg is the length of the
* input data, if any, not including the command string & counts,
* *((int *)arg + 1) is the output buffer size in bytes.
*
* *(char *) ((int *) arg)[2] the actual command byte.
*
* Note that no more than MAX_BUF data bytes will be transfered. Since
* SCSI block device size is 512 bytes, I figured 1K was good.
* but (WDE) changed it to 8192 to handle large bad track buffers.
* ERY: I changed this to a dynamic allocation using scsi_malloc - we were
* getting a kernel stack overflow which was crashing the system when we
* were using 8192 bytes.
*
* This size *does not* include the initial lengths that were passed.
*
* The SCSI command is read from the memory location immediately after the
* length words, and the input data is right after the command. The SCSI
* routines know the command size based on the opcode decode.
*
* The output area is then filled in starting from the command byte.
*/
static void scsi_ioctl_done (Scsi_Cmnd * SCpnt)
{
struct request * req;
struct task_struct * p;
req = &SCpnt->request;
req->dev = 0xfffe; /* Busy, but indicate request done */
if ((p = req->waiting) != NULL) {
req->waiting = NULL;
p->state = TASK_RUNNING;
if (p->counter > current->counter)
need_resched = 1;
}
}
static int ioctl_internal_command(Scsi_Device *dev, char * cmd)
{
int result;
Scsi_Cmnd * SCpnt;
SCpnt = allocate_device(NULL, dev->index, 1);
scsi_do_cmd(SCpnt, cmd, NULL, 0,
scsi_ioctl_done, MAX_TIMEOUT,
MAX_RETRIES);
if (SCpnt->request.dev != 0xfffe){
SCpnt->request.waiting = current;
current->state = TASK_UNINTERRUPTIBLE;
while (SCpnt->request.dev != 0xfffe) schedule();
};
if(driver_byte(SCpnt->result) != 0)
switch(SCpnt->sense_buffer[2] & 0xf) {
case ILLEGAL_REQUEST:
if(cmd[0] == ALLOW_MEDIUM_REMOVAL) dev->lockable = 0;
else printk("SCSI device (ioctl) reports ILLEGAL REQUEST.\n");
break;
case NOT_READY: /* This happens if there is no disc in drive */
if(dev->removable){
printk("Device not ready. Make sure there is a disc in the drive.\n");
break;
};
case UNIT_ATTENTION:
if (dev->removable){
dev->changed = 1;
SCpnt->result = 0; /* This is no longer considered an error */
printk("Disc change detected.\n");
break;
};
default: /* Fall through for non-removable media */
printk("SCSI CD error: host %d id %d lun %d return code = %x\n",
dev->host->host_no,
dev->id,
dev->lun,
SCpnt->result);
printk("\tSense class %x, sense error %x, extended sense %x\n",
sense_class(SCpnt->sense_buffer[0]),
sense_error(SCpnt->sense_buffer[0]),
SCpnt->sense_buffer[2] & 0xf);
};
result = SCpnt->result;
SCpnt->request.dev = -1; /* Mark as not busy */
wake_up(&scsi_devices[SCpnt->index].device_wait);
return result;
}
static int ioctl_command(Scsi_Device *dev, void *buffer)
{
char * buf;
char cmd[12];
char * cmd_in;
Scsi_Cmnd * SCpnt;
unsigned char opcode;
int inlen, outlen, cmdlen;
int needed;
int result;
if (!buffer)
return -EINVAL;
inlen = get_fs_long((unsigned long *) buffer);
outlen = get_fs_long( ((unsigned long *) buffer) + 1);
cmd_in = (char *) ( ((int *)buffer) + 2);
opcode = get_fs_byte(cmd_in);
needed = (inlen > outlen ? inlen : outlen);
if(needed){
needed = (needed + 511) & ~511;
if (needed > MAX_BUF) needed = MAX_BUF;
buf = (char *) scsi_malloc(needed);
if (!buf) return -ENOMEM;
} else
buf = NULL;
memcpy_fromfs ((void *) cmd, cmd_in, cmdlen = COMMAND_SIZE (opcode));
memcpy_fromfs ((void *) buf, (void *) (cmd_in + cmdlen), inlen > MAX_BUF ? MAX_BUF : inlen);
cmd[1] = ( cmd[1] & 0x1f ) | (dev->lun << 5);
#ifndef DEBUG_NO_CMD
SCpnt = allocate_device(NULL, dev->index, 1);
scsi_do_cmd(SCpnt, cmd, buf, ((outlen > MAX_BUF) ?
MAX_BUF : outlen), scsi_ioctl_done, MAX_TIMEOUT,
MAX_RETRIES);
if (SCpnt->request.dev != 0xfffe){
SCpnt->request.waiting = current;
current->state = TASK_UNINTERRUPTIBLE;
while (SCpnt->request.dev != 0xfffe) schedule();
};
result = verify_area(VERIFY_WRITE, cmd_in, (outlen > MAX_BUF) ? MAX_BUF : outlen);
if (result)
return result;
memcpy_tofs ((void *) cmd_in, buf, (outlen > MAX_BUF) ? MAX_BUF : outlen);
result = SCpnt->result;
SCpnt->request.dev = -1; /* Mark as not busy */
if (buf) scsi_free(buf, needed);
wake_up(&scsi_devices[SCpnt->index].device_wait);
return result;
#else
{
int i;
printk("scsi_ioctl : device %d. command = ", dev->id);
for (i = 0; i < 12; ++i)
printk("%02x ", cmd[i]);
printk("\nbuffer =");
for (i = 0; i < 20; ++i)
printk("%02x ", buf[i]);
printk("\n");
printk("inlen = %d, outlen = %d, cmdlen = %d\n",
inlen, outlen, cmdlen);
printk("buffer = %d, cmd_in = %d\n", buffer, cmd_in);
}
return 0;
#endif
}
/*
the scsi_ioctl() function differs from most ioctls in that it does
not take a major/minor number as the dev filed. Rather, it takes
a pointer to a scsi_devices[] element, a structure.
*/
int scsi_ioctl (Scsi_Device *dev, int cmd, void *arg)
{
char scsi_cmd[12];
if ((cmd != 0 && dev->index > NR_SCSI_DEVICES))
return -ENODEV;
switch (cmd) {
case SCSI_IOCTL_GET_IDLUN:
verify_area(VERIFY_WRITE, (void *) arg, sizeof(int));
put_fs_long(dev->id + (dev->lun << 8) +
(dev->host->host_no << 16), (unsigned long *) arg);
return 0;
case SCSI_IOCTL_TAGGED_ENABLE:
if(!suser()) return -EACCES;
if(!dev->tagged_supported) return -EINVAL;
dev->tagged_queue = 1;
dev->current_tag = 1;
break;
case SCSI_IOCTL_TAGGED_DISABLE:
if(!suser()) return -EACCES;
if(!dev->tagged_supported) return -EINVAL;
dev->tagged_queue = 0;
dev->current_tag = 0;
break;
case SCSI_IOCTL_PROBE_HOST:
return ioctl_probe(dev->host, arg);
case SCSI_IOCTL_SEND_COMMAND:
return ioctl_command((Scsi_Device *) dev, arg);
case SCSI_IOCTL_DOORLOCK:
if (!dev->removable || !dev->lockable) return 0;
scsi_cmd[0] = ALLOW_MEDIUM_REMOVAL;
scsi_cmd[1] = dev->lun << 5;
scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0;
scsi_cmd[4] = SCSI_REMOVAL_PREVENT;
return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd);
break;
case SCSI_IOCTL_DOORUNLOCK:
if (!dev->removable || !dev->lockable) return 0;
scsi_cmd[0] = ALLOW_MEDIUM_REMOVAL;
scsi_cmd[1] = dev->lun << 5;
scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0;
scsi_cmd[4] = SCSI_REMOVAL_ALLOW;
return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd);
case SCSI_IOCTL_TEST_UNIT_READY:
scsi_cmd[0] = TEST_UNIT_READY;
scsi_cmd[1] = dev->lun << 5;
scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0;
scsi_cmd[4] = 0;
return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd);
break;
default :
return -EINVAL;
}
return -EINVAL;
}
/*
* Just like scsi_ioctl, only callable from kernel space with no
* fs segment fiddling.
*/
int kernel_scsi_ioctl (Scsi_Device *dev, int cmd, void *arg) {
unsigned long oldfs;
int tmp;
oldfs = get_fs();
set_fs(get_ds());
tmp = scsi_ioctl (dev, cmd, arg);
set_fs(oldfs);
return tmp;
}