83a78d9ba7
The sd driver now uses scsi_execute_req() for almost everything. scsi_execute_req() converts requests into scatterlists. Fix the HP SCSI disk simulator to understand scatterlists for more commands. Without this patch the current kernel will not boot on the simulator (the disks are always detected as having no sectors, and so cannot be mounted). Signed-off-by: Peter Chubb <peterc@gelato.unsw.edu.au> Signed-off-by: Tony Luck <tony.luck@intel.com>
422 lines
9.8 KiB
C
422 lines
9.8 KiB
C
/*
|
|
* Simulated SCSI driver.
|
|
*
|
|
* Copyright (C) 1999, 2001-2003 Hewlett-Packard Co
|
|
* David Mosberger-Tang <davidm@hpl.hp.com>
|
|
* Stephane Eranian <eranian@hpl.hp.com>
|
|
*
|
|
* 02/01/15 David Mosberger Updated for v2.5.1
|
|
* 99/12/18 David Mosberger Added support for READ10/WRITE10 needed by linux v2.3.33
|
|
*/
|
|
#include <linux/blkdev.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/timer.h>
|
|
#include <asm/irq.h>
|
|
|
|
#include <scsi/scsi.h>
|
|
#include <scsi/scsi_cmnd.h>
|
|
#include <scsi/scsi_device.h>
|
|
#include <scsi/scsi_host.h>
|
|
|
|
#define DEBUG_SIMSCSI 0
|
|
|
|
#define SIMSCSI_REQ_QUEUE_LEN 64
|
|
#define DEFAULT_SIMSCSI_ROOT "/var/ski-disks/sd"
|
|
|
|
/* Simulator system calls: */
|
|
|
|
#define SSC_OPEN 50
|
|
#define SSC_CLOSE 51
|
|
#define SSC_READ 52
|
|
#define SSC_WRITE 53
|
|
#define SSC_GET_COMPLETION 54
|
|
#define SSC_WAIT_COMPLETION 55
|
|
|
|
#define SSC_WRITE_ACCESS 2
|
|
#define SSC_READ_ACCESS 1
|
|
|
|
#if DEBUG_SIMSCSI
|
|
int simscsi_debug;
|
|
# define DBG simscsi_debug
|
|
#else
|
|
# define DBG 0
|
|
#endif
|
|
|
|
static struct Scsi_Host *host;
|
|
|
|
static void simscsi_interrupt (unsigned long val);
|
|
static DECLARE_TASKLET(simscsi_tasklet, simscsi_interrupt, 0);
|
|
|
|
struct disk_req {
|
|
unsigned long addr;
|
|
unsigned len;
|
|
};
|
|
|
|
struct disk_stat {
|
|
int fd;
|
|
unsigned count;
|
|
};
|
|
|
|
extern long ia64_ssc (long arg0, long arg1, long arg2, long arg3, int nr);
|
|
|
|
static int desc[16] = {
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
|
|
};
|
|
|
|
static struct queue_entry {
|
|
struct scsi_cmnd *sc;
|
|
} queue[SIMSCSI_REQ_QUEUE_LEN];
|
|
|
|
static int rd, wr;
|
|
static atomic_t num_reqs = ATOMIC_INIT(0);
|
|
|
|
/* base name for default disks */
|
|
static char *simscsi_root = DEFAULT_SIMSCSI_ROOT;
|
|
|
|
#define MAX_ROOT_LEN 128
|
|
|
|
/*
|
|
* used to setup a new base for disk images
|
|
* to use /foo/bar/disk[a-z] as disk images
|
|
* you have to specify simscsi=/foo/bar/disk on the command line
|
|
*/
|
|
static int __init
|
|
simscsi_setup (char *s)
|
|
{
|
|
/* XXX Fix me we may need to strcpy() ? */
|
|
if (strlen(s) > MAX_ROOT_LEN) {
|
|
printk(KERN_ERR "simscsi_setup: prefix too long---using default %s\n",
|
|
simscsi_root);
|
|
}
|
|
simscsi_root = s;
|
|
return 1;
|
|
}
|
|
|
|
__setup("simscsi=", simscsi_setup);
|
|
|
|
static void
|
|
simscsi_interrupt (unsigned long val)
|
|
{
|
|
struct scsi_cmnd *sc;
|
|
|
|
while ((sc = queue[rd].sc) != 0) {
|
|
atomic_dec(&num_reqs);
|
|
queue[rd].sc = 0;
|
|
if (DBG)
|
|
printk("simscsi_interrupt: done with %ld\n", sc->serial_number);
|
|
(*sc->scsi_done)(sc);
|
|
rd = (rd + 1) % SIMSCSI_REQ_QUEUE_LEN;
|
|
}
|
|
}
|
|
|
|
static int
|
|
simscsi_biosparam (struct scsi_device *sdev, struct block_device *n,
|
|
sector_t capacity, int ip[])
|
|
{
|
|
ip[0] = 64; /* heads */
|
|
ip[1] = 32; /* sectors */
|
|
ip[2] = capacity >> 11; /* cylinders */
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
simscsi_readwrite (struct scsi_cmnd *sc, int mode, unsigned long offset, unsigned long len)
|
|
{
|
|
struct disk_stat stat;
|
|
struct disk_req req;
|
|
|
|
req.addr = __pa(sc->request_buffer);
|
|
req.len = len; /* # of bytes to transfer */
|
|
|
|
if (sc->request_bufflen < req.len)
|
|
return;
|
|
|
|
stat.fd = desc[sc->device->id];
|
|
if (DBG)
|
|
printk("simscsi_%s @ %lx (off %lx)\n",
|
|
mode == SSC_READ ? "read":"write", req.addr, offset);
|
|
ia64_ssc(stat.fd, 1, __pa(&req), offset, mode);
|
|
ia64_ssc(__pa(&stat), 0, 0, 0, SSC_WAIT_COMPLETION);
|
|
|
|
if (stat.count == req.len) {
|
|
sc->result = GOOD;
|
|
} else {
|
|
sc->result = DID_ERROR << 16;
|
|
}
|
|
}
|
|
|
|
static void
|
|
simscsi_sg_readwrite (struct scsi_cmnd *sc, int mode, unsigned long offset)
|
|
{
|
|
int list_len = sc->use_sg;
|
|
struct scatterlist *sl = (struct scatterlist *)sc->buffer;
|
|
struct disk_stat stat;
|
|
struct disk_req req;
|
|
|
|
stat.fd = desc[sc->device->id];
|
|
|
|
while (list_len) {
|
|
req.addr = __pa(page_address(sl->page) + sl->offset);
|
|
req.len = sl->length;
|
|
if (DBG)
|
|
printk("simscsi_sg_%s @ %lx (off %lx) use_sg=%d len=%d\n",
|
|
mode == SSC_READ ? "read":"write", req.addr, offset,
|
|
list_len, sl->length);
|
|
ia64_ssc(stat.fd, 1, __pa(&req), offset, mode);
|
|
ia64_ssc(__pa(&stat), 0, 0, 0, SSC_WAIT_COMPLETION);
|
|
|
|
/* should not happen in our case */
|
|
if (stat.count != req.len) {
|
|
sc->result = DID_ERROR << 16;
|
|
return;
|
|
}
|
|
offset += sl->length;
|
|
sl++;
|
|
list_len--;
|
|
}
|
|
sc->result = GOOD;
|
|
}
|
|
|
|
/*
|
|
* function handling both READ_6/WRITE_6 (non-scatter/gather mode)
|
|
* commands.
|
|
* Added 02/26/99 S.Eranian
|
|
*/
|
|
static void
|
|
simscsi_readwrite6 (struct scsi_cmnd *sc, int mode)
|
|
{
|
|
unsigned long offset;
|
|
|
|
offset = (((sc->cmnd[1] & 0x1f) << 16) | (sc->cmnd[2] << 8) | sc->cmnd[3])*512;
|
|
if (sc->use_sg > 0)
|
|
simscsi_sg_readwrite(sc, mode, offset);
|
|
else
|
|
simscsi_readwrite(sc, mode, offset, sc->cmnd[4]*512);
|
|
}
|
|
|
|
static size_t
|
|
simscsi_get_disk_size (int fd)
|
|
{
|
|
struct disk_stat stat;
|
|
size_t bit, sectors = 0;
|
|
struct disk_req req;
|
|
char buf[512];
|
|
|
|
/*
|
|
* This is a bit kludgey: the simulator doesn't provide a direct way of determining
|
|
* the disk size, so we do a binary search, assuming a maximum disk size of 4GB.
|
|
*/
|
|
for (bit = (4UL << 30)/512; bit != 0; bit >>= 1) {
|
|
req.addr = __pa(&buf);
|
|
req.len = sizeof(buf);
|
|
ia64_ssc(fd, 1, __pa(&req), ((sectors | bit) - 1)*512, SSC_READ);
|
|
stat.fd = fd;
|
|
ia64_ssc(__pa(&stat), 0, 0, 0, SSC_WAIT_COMPLETION);
|
|
if (stat.count == sizeof(buf))
|
|
sectors |= bit;
|
|
}
|
|
return sectors - 1; /* return last valid sector number */
|
|
}
|
|
|
|
static void
|
|
simscsi_readwrite10 (struct scsi_cmnd *sc, int mode)
|
|
{
|
|
unsigned long offset;
|
|
|
|
offset = ( (sc->cmnd[2] << 24) | (sc->cmnd[3] << 16)
|
|
| (sc->cmnd[4] << 8) | (sc->cmnd[5] << 0))*512;
|
|
if (sc->use_sg > 0)
|
|
simscsi_sg_readwrite(sc, mode, offset);
|
|
else
|
|
simscsi_readwrite(sc, mode, offset, ((sc->cmnd[7] << 8) | sc->cmnd[8])*512);
|
|
}
|
|
|
|
static void simscsi_fillresult(struct scsi_cmnd *sc, char *buf, unsigned len)
|
|
{
|
|
|
|
int scatterlen = sc->use_sg;
|
|
struct scatterlist *slp;
|
|
|
|
if (scatterlen == 0)
|
|
memcpy(sc->request_buffer, buf, len);
|
|
else for (slp = (struct scatterlist *)sc->buffer; scatterlen-- > 0 && len > 0; slp++) {
|
|
unsigned thislen = min(len, slp->length);
|
|
|
|
memcpy(page_address(slp->page) + slp->offset, buf, thislen);
|
|
slp++;
|
|
len -= thislen;
|
|
}
|
|
}
|
|
|
|
static int
|
|
simscsi_queuecommand (struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
|
|
{
|
|
unsigned int target_id = sc->device->id;
|
|
char fname[MAX_ROOT_LEN+16];
|
|
size_t disk_size;
|
|
char *buf;
|
|
char localbuf[36];
|
|
#if DEBUG_SIMSCSI
|
|
register long sp asm ("sp");
|
|
|
|
if (DBG)
|
|
printk("simscsi_queuecommand: target=%d,cmnd=%u,sc=%lu,sp=%lx,done=%p\n",
|
|
target_id, sc->cmnd[0], sc->serial_number, sp, done);
|
|
#endif
|
|
|
|
sc->result = DID_BAD_TARGET << 16;
|
|
sc->scsi_done = done;
|
|
if (target_id <= 15 && sc->device->lun == 0) {
|
|
switch (sc->cmnd[0]) {
|
|
case INQUIRY:
|
|
if (sc->request_bufflen < 35) {
|
|
break;
|
|
}
|
|
sprintf (fname, "%s%c", simscsi_root, 'a' + target_id);
|
|
desc[target_id] = ia64_ssc(__pa(fname), SSC_READ_ACCESS|SSC_WRITE_ACCESS,
|
|
0, 0, SSC_OPEN);
|
|
if (desc[target_id] < 0) {
|
|
/* disk doesn't exist... */
|
|
break;
|
|
}
|
|
buf = localbuf;
|
|
buf[0] = 0; /* magnetic disk */
|
|
buf[1] = 0; /* not a removable medium */
|
|
buf[2] = 2; /* SCSI-2 compliant device */
|
|
buf[3] = 2; /* SCSI-2 response data format */
|
|
buf[4] = 31; /* additional length (bytes) */
|
|
buf[5] = 0; /* reserved */
|
|
buf[6] = 0; /* reserved */
|
|
buf[7] = 0; /* various flags */
|
|
memcpy(buf + 8, "HP SIMULATED DISK 0.00", 28);
|
|
simscsi_fillresult(sc, buf, 36);
|
|
sc->result = GOOD;
|
|
break;
|
|
|
|
case TEST_UNIT_READY:
|
|
sc->result = GOOD;
|
|
break;
|
|
|
|
case READ_6:
|
|
if (desc[target_id] < 0 )
|
|
break;
|
|
simscsi_readwrite6(sc, SSC_READ);
|
|
break;
|
|
|
|
case READ_10:
|
|
if (desc[target_id] < 0 )
|
|
break;
|
|
simscsi_readwrite10(sc, SSC_READ);
|
|
break;
|
|
|
|
case WRITE_6:
|
|
if (desc[target_id] < 0)
|
|
break;
|
|
simscsi_readwrite6(sc, SSC_WRITE);
|
|
break;
|
|
|
|
case WRITE_10:
|
|
if (desc[target_id] < 0)
|
|
break;
|
|
simscsi_readwrite10(sc, SSC_WRITE);
|
|
break;
|
|
|
|
case READ_CAPACITY:
|
|
if (desc[target_id] < 0 || sc->request_bufflen < 8) {
|
|
break;
|
|
}
|
|
buf = localbuf;
|
|
disk_size = simscsi_get_disk_size(desc[target_id]);
|
|
|
|
buf[0] = (disk_size >> 24) & 0xff;
|
|
buf[1] = (disk_size >> 16) & 0xff;
|
|
buf[2] = (disk_size >> 8) & 0xff;
|
|
buf[3] = (disk_size >> 0) & 0xff;
|
|
/* set block size of 512 bytes: */
|
|
buf[4] = 0;
|
|
buf[5] = 0;
|
|
buf[6] = 2;
|
|
buf[7] = 0;
|
|
simscsi_fillresult(sc, buf, 8);
|
|
sc->result = GOOD;
|
|
break;
|
|
|
|
case MODE_SENSE:
|
|
case MODE_SENSE_10:
|
|
/* sd.c uses this to determine whether disk does write-caching. */
|
|
simscsi_fillresult(sc, (char *)empty_zero_page, sc->request_bufflen);
|
|
sc->result = GOOD;
|
|
break;
|
|
|
|
case START_STOP:
|
|
printk(KERN_ERR "START_STOP\n");
|
|
break;
|
|
|
|
default:
|
|
panic("simscsi: unknown SCSI command %u\n", sc->cmnd[0]);
|
|
}
|
|
}
|
|
if (sc->result == DID_BAD_TARGET) {
|
|
sc->result |= DRIVER_SENSE << 24;
|
|
sc->sense_buffer[0] = 0x70;
|
|
sc->sense_buffer[2] = 0x00;
|
|
}
|
|
if (atomic_read(&num_reqs) >= SIMSCSI_REQ_QUEUE_LEN) {
|
|
panic("Attempt to queue command while command is pending!!");
|
|
}
|
|
atomic_inc(&num_reqs);
|
|
queue[wr].sc = sc;
|
|
wr = (wr + 1) % SIMSCSI_REQ_QUEUE_LEN;
|
|
|
|
tasklet_schedule(&simscsi_tasklet);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
simscsi_host_reset (struct scsi_cmnd *sc)
|
|
{
|
|
printk(KERN_ERR "simscsi_host_reset: not implemented\n");
|
|
return 0;
|
|
}
|
|
|
|
static struct scsi_host_template driver_template = {
|
|
.name = "simulated SCSI host adapter",
|
|
.proc_name = "simscsi",
|
|
.queuecommand = simscsi_queuecommand,
|
|
.eh_host_reset_handler = simscsi_host_reset,
|
|
.bios_param = simscsi_biosparam,
|
|
.can_queue = SIMSCSI_REQ_QUEUE_LEN,
|
|
.this_id = -1,
|
|
.sg_tablesize = SG_ALL,
|
|
.max_sectors = 1024,
|
|
.cmd_per_lun = SIMSCSI_REQ_QUEUE_LEN,
|
|
.use_clustering = DISABLE_CLUSTERING,
|
|
};
|
|
|
|
static int __init
|
|
simscsi_init(void)
|
|
{
|
|
int error;
|
|
|
|
host = scsi_host_alloc(&driver_template, 0);
|
|
if (!host)
|
|
return -ENOMEM;
|
|
|
|
error = scsi_add_host(host, NULL);
|
|
if (!error)
|
|
scsi_scan_host(host);
|
|
return error;
|
|
}
|
|
|
|
static void __exit
|
|
simscsi_exit(void)
|
|
{
|
|
scsi_remove_host(host);
|
|
scsi_host_put(host);
|
|
}
|
|
|
|
module_init(simscsi_init);
|
|
module_exit(simscsi_exit);
|