#include "vl.h"
#define SENSE_NO_SENSE 0
+#define SENSE_NOT_READY 2
+#define SENSE_HARDWARE_ERROR 4
#define SENSE_ILLEGAL_REQUEST 5
struct SCSIDevice
int buf_pos;
int buf_len;
int sense;
+ BlockDriverAIOCB *aiocb;
+ /* Data still to be transfered after this request completes. */
+ uint8_t *aiodata;
+ uint32_t aiolen;
char buf[512];
+ /* Completion functions may be called from either scsi_{read,write}_data
+ or from the AIO completion routines. */
scsi_completionfn completion;
void *opaque;
};
static void scsi_command_complete(SCSIDevice *s, int sense)
{
s->sense = sense;
- s->completion(s->opaque, s->tag, sense);
+ s->completion(s->opaque, SCSI_REASON_DONE, sense);
}
-/* Read data from a scsi device. Returns nonzero on failure. */
+static void scsi_transfer_complete(SCSIDevice *s)
+{
+ s->completion(s->opaque, SCSI_REASON_DATA, 0);
+ s->aiocb = NULL;
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+ SCSIDevice *s = (SCSIDevice *)opaque;
+
+ if (ret) {
+ DPRINTF("IO error\n");
+ scsi_command_complete(s, SENSE_HARDWARE_ERROR);
+ }
+
+ if (s->aiolen) {
+ /* Read the remaining data. Full and partial sectors are transferred
+ separately. */
+ scsi_read_data(s, s->aiodata, s->aiolen);
+ } else {
+ if (s->buf_len == 0 && s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
+ }
+}
+
+/* Cancel a pending data transfer. */
+void scsi_cancel_io(SCSIDevice *s)
+{
+ if (!s->aiocb) {
+ BADF("Cancel with no pending IO\n");
+ return;
+ }
+ bdrv_aio_cancel(s->aiocb);
+ s->aiocb = NULL;
+}
+
+/* Read data from a scsi device. Returns nonzero on failure.
+ The transfer may complete asynchronously. */
int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
{
uint32_t n;
n = s->sector_count;
if (n != 0) {
- bdrv_read(s->bdrv, s->sector, data, n);
- data += n * 512;
- len -= n * 512;
+ s->aiolen = len - n * 512;
+ s->aiodata = data + n * 512;
+ s->aiocb = bdrv_aio_read(s->bdrv, s->sector, data, n,
+ scsi_read_complete, s);
+ if (s->aiocb == NULL)
+ scsi_command_complete(s, SENSE_HARDWARE_ERROR);
s->sector += n;
s->sector_count -= n;
+ return 0;
}
if (len && s->sector_count) {
+ /* TODO: Make this use AIO. */
bdrv_read(s->bdrv, s->sector, s->buf, 1);
s->sector++;
s->sector_count--;
if (s->buf_len == 0 && s->sector_count == 0)
scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
return 0;
}
-/* Read data to a scsi device. Returns nonzero on failure. */
+static void scsi_write_complete(void * opaque, int ret)
+{
+ SCSIDevice *s = (SCSIDevice *)opaque;
+
+ if (ret) {
+ fprintf(stderr, "scsi-disc: IO write error\n");
+ exit(1);
+ }
+
+ if (s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
+}
+
+static uint32_t scsi_write_partial_sector(SCSIDevice *s, uint8_t *data,
+ uint32_t len)
+{
+ int n;
+
+ n = 512 - s->buf_len;
+ if (n > len)
+ n = len;
+
+ memcpy(s->buf + s->buf_len, data, n);
+ data += n;
+ s->buf_len += n;
+ len -= n;
+ if (s->buf_len == 512) {
+ /* A full sector has been accumulated. Write it to disk. */
+ /* TODO: Make this use async IO. */
+ bdrv_write(s->bdrv, s->sector, s->buf, 1);
+ s->buf_len = 0;
+ s->sector++;
+ s->sector_count--;
+ }
+ return n;
+}
+
+/* Write data to a scsi device. Returns nonzero on failure.
+ The transfer may complete asynchronously. */
int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
{
uint32_t n;
return 1;
if (s->buf_len != 0 || len < 512) {
- n = 512 - s->buf_len;
- if (n > len)
- n = len;
-
- memcpy(s->buf + s->buf_len, data, n);
- data += n;
- s->buf_len += n;
+ n = scsi_write_partial_sector(s, data, len);
len -= n;
- if (s->buf_len == 512) {
- /* A full sector has been accumulated. Write it to disk. */
- bdrv_write(s->bdrv, s->sector, s->buf, 1);
- s->buf_len = 0;
- s->sector++;
- s->sector_count--;
- }
+ data += n;
}
n = len / 512;
if (n > s->sector_count)
- n = s->sector_count;
+ return 1;
if (n != 0) {
- bdrv_write(s->bdrv, s->sector, data, n);
+ s->aiocb = bdrv_aio_write(s->bdrv, s->sector, data, n,
+ scsi_write_complete, s);
+ if (s->aiocb == NULL)
+ scsi_command_complete(s, SENSE_HARDWARE_ERROR);
data += n * 512;
len -= n * 512;
s->sector += n;
s->sector_count -= n;
}
- if (len >= 512)
- return 1;
-
- if (len && s->sector_count) {
- /* Recurse to complete the partial write. */
- return scsi_write_data(s, data, len);
+ if (len) {
+ if (s->sector_count == 0)
+ return 1;
+ /* Complete a partial write. */
+ scsi_write_partial_sector(s, data, len);
+ }
+ if (n == 0) {
+ /* Transfer completes immediately. */
+ if (s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
}
-
- if (len != 0)
- return 1;
-
- if (s->sector_count == 0)
- scsi_command_complete(s, SENSE_NO_SENSE);
return 0;
}
cmdlen = 12;
break;
default:
- BADF("Unsupported command length\n");
+ BADF("Unsupported command length, command %x\n", s->command);
goto fail;
}
#ifdef DEBUG_SCSI
}
memcpy(&s->buf[8], "QEMU ", 8);
memcpy(&s->buf[32], QEMU_VERSION, 4);
- s->buf[2] = 3; /* SCSI-3 */
+ /* Identify device as SCSI-3 rev 1.
+ Some later commands are also implemented. */
+ s->buf[2] = 3;
s->buf[3] = 2; /* Format 2 */
s->buf[4] = 32;
s->buf_len = 36;
break;
case 0x1a:
case 0x5a:
- DPRINTF("Mode Sense (page %d, len %d)\n", buf[2], len);
- if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) {
- memset(s->buf, 0, 4);
- s->buf[0] = 4; /* Mode data length. */
- s->buf[1] = 0; /* Default media type. */
- s->buf[2] = 0x80; /* Readonly. */
- s->buf[3] = 0; /* Block descriptor length. */
- } else {
- memset(s->buf, 0, 0x16);
- s->buf[0] = 0x16; /* Mode data length (4 + 0x12). */
+ {
+ char *p;
+ int page;
+
+ page = buf[2] & 0x3f;
+ DPRINTF("Mode Sense (page %d, len %d)\n", page, len);
+ p = s->buf;
+ memset(p, 0, 4);
s->buf[1] = 0; /* Default media type. */
- s->buf[2] = 0; /* Write enabled. */
s->buf[3] = 0; /* Block descriptor length. */
- /* Caching page. */
- s->buf[4 + 0] = 8;
- s->buf[4 + 1] = 0x12;
- s->buf[4 + 2] = 4; /* WCE */
- if (len > 0x16)
- len = 0x16;
+ if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) {
+ s->buf[2] = 0x80; /* Readonly. */
+ }
+ p += 4;
+ if ((page == 8 || page == 0x3f)) {
+ /* Caching page. */
+ p[0] = 8;
+ p[1] = 0x12;
+ p[2] = 4; /* WCE */
+ p += 19;
+ }
+ if ((page == 0x3f || page == 0x2a)
+ && (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM)) {
+ /* CD Capabilities and Mechanical Status page. */
+ p[0] = 0x2a;
+ p[1] = 0x14;
+ p[2] = 3; // CD-R & CD-RW read
+ p[3] = 0; // Writing not supported
+ p[4] = 0x7f; /* Audio, composite, digital out,
+ mode 2 form 1&2, multi session */
+ p[5] = 0xff; /* CD DA, DA accurate, RW supported,
+ RW corrected, C2 errors, ISRC,
+ UPC, Bar code */
+ p[6] = 0x2d | (bdrv_is_locked(s->bdrv)? 2 : 0);
+ /* Locking supported, jumper present, eject, tray */
+ p[7] = 0; /* no volume & mute control, no
+ changer */
+ p[8] = (50 * 176) >> 8; // 50x read speed
+ p[9] = (50 * 176) & 0xff;
+ p[10] = 0 >> 8; // No volume
+ p[11] = 0 & 0xff;
+ p[12] = 2048 >> 8; // 2M buffer
+ p[13] = 2048 & 0xff;
+ p[14] = (16 * 176) >> 8; // 16x read speed current
+ p[15] = (16 * 176) & 0xff;
+ p[18] = (16 * 176) >> 8; // 16x write speed
+ p[19] = (16 * 176) & 0xff;
+ p[20] = (16 * 176) >> 8; // 16x write speed current
+ p[21] = (16 * 176) & 0xff;
+ p += 21;
+ }
+ s->buf_len = p - s->buf;
+ s->buf[0] = s->buf_len - 4;
+ if (s->buf_len > len)
+ s->buf_len = len;
}
- s->buf_len = len;
+ break;
+ case 0x1b:
+ DPRINTF("Start Stop Unit\n");
+ break;
+ case 0x1e:
+ DPRINTF("Prevent Allow Medium Removal (prevent = %d)\n", buf[4] & 3);
+ bdrv_set_locked(s->bdrv, buf[4] & 1);
break;
case 0x25:
DPRINTF("Read Capacity\n");
/* The normal LEN field for this command is zero. */
memset(s->buf, 0, 8);
bdrv_get_geometry(s->bdrv, &nb_sectors);
- s->buf[0] = (nb_sectors >> 24) & 0xff;
- s->buf[1] = (nb_sectors >> 16) & 0xff;
- s->buf[2] = (nb_sectors >> 8) & 0xff;
- s->buf[3] = nb_sectors & 0xff;
- s->buf[4] = 0;
- s->buf[5] = 0;
- s->buf[6] = s->cluster_size * 2;
- s->buf[7] = 0;
- s->buf_len = 8;
+ /* Returned value is the address of the last sector. */
+ if (nb_sectors) {
+ nb_sectors--;
+ s->buf[0] = (nb_sectors >> 24) & 0xff;
+ s->buf[1] = (nb_sectors >> 16) & 0xff;
+ s->buf[2] = (nb_sectors >> 8) & 0xff;
+ s->buf[3] = nb_sectors & 0xff;
+ s->buf[4] = 0;
+ s->buf[5] = 0;
+ s->buf[6] = s->cluster_size * 2;
+ s->buf[7] = 0;
+ s->buf_len = 8;
+ } else {
+ scsi_command_complete(s, SENSE_NOT_READY);
+ }
break;
case 0x08:
case 0x28:
break;
case 0x35:
DPRINTF("Syncronise cache (sector %d, count %d)\n", lba, len);
- /* ??? Extend block layer and use fsync to implement this. */
+ bdrv_flush(s->bdrv);
break;
case 0x43:
{
DPRINTF("Read TOC error\n");
goto fail;
}
+ case 0x46:
+ DPRINTF("Get Configuration (rt %d, maxlen %d)\n", buf[1] & 3, len);
+ memset(s->buf, 0, 8);
+ /* ??? This shoud probably return much more information. For now
+ just return the basic header indicating the CD-ROM profile. */
+ s->buf[7] = 8; // CD-ROM
+ s->buf_len = 8;
+ break;
case 0x56:
DPRINTF("Reserve(10)\n");
if (buf[1] & 3)