X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/af6d510ddbef5e0be9ce40464c2bf46da58eacf2..22d48de65c88c42e3cb2b000491dc6089a240e2a:/hw/scsi-disk.c diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index b8d60862cb..a9c727905a 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -43,6 +43,7 @@ do { printf("scsi-disk: " fmt , ## __VA_ARGS__); } while (0) #define SCSI_DMA_BUF_SIZE 131072 #define SCSI_MAX_INQUIRY_LEN 256 +#define SCSI_MAX_MODE_LEN 256 typedef struct SCSIDiskState SCSIDiskState; @@ -446,7 +447,7 @@ static void scsi_write_complete(void * opaque, int ret) return; } else { scsi_init_iovec(r, SCSI_DMA_BUF_SIZE); - DPRINTF("Write complete tag=0x%x more=%d\n", r->req.tag, r->qiov.size); + DPRINTF("Write complete tag=0x%x more=%zd\n", r->req.tag, r->qiov.size); scsi_req_data(&r->req, r->qiov.size); } @@ -959,148 +960,157 @@ static int mode_sense_page(SCSIDiskState *s, int page, uint8_t **p_outbuf, [MODE_PAGE_AUDIO_CTL] = (1 << TYPE_ROM), [MODE_PAGE_CAPABILITIES] = (1 << TYPE_ROM), }; - uint8_t *p = *p_outbuf; + + uint8_t *p = *p_outbuf + 2; + int length; if ((mode_sense_valid[page] & (1 << s->qdev.type)) == 0) { return -1; } - p[0] = page; - /* * If Changeable Values are requested, a mask denoting those mode parameters * that are changeable shall be returned. As we currently don't support * parameter changes via MODE_SELECT all bits are returned set to zero. * The buffer was already menset to zero by the caller of this function. + * + * The offsets here are off by two compared to the descriptions in the + * SCSI specs, because those include a 2-byte header. This is unfortunate, + * but it is done so that offsets are consistent within our implementation + * of MODE SENSE and MODE SELECT. MODE SELECT has to deal with both + * 2-byte and 4-byte headers. */ switch (page) { case MODE_PAGE_HD_GEOMETRY: - p[1] = 0x16; + length = 0x16; if (page_control == 1) { /* Changeable Values */ break; } /* if a geometry hint is available, use it */ - p[2] = (s->qdev.conf.cyls >> 16) & 0xff; - p[3] = (s->qdev.conf.cyls >> 8) & 0xff; - p[4] = s->qdev.conf.cyls & 0xff; - p[5] = s->qdev.conf.heads & 0xff; + p[0] = (s->qdev.conf.cyls >> 16) & 0xff; + p[1] = (s->qdev.conf.cyls >> 8) & 0xff; + p[2] = s->qdev.conf.cyls & 0xff; + p[3] = s->qdev.conf.heads & 0xff; /* Write precomp start cylinder, disabled */ - p[6] = (s->qdev.conf.cyls >> 16) & 0xff; - p[7] = (s->qdev.conf.cyls >> 8) & 0xff; - p[8] = s->qdev.conf.cyls & 0xff; + p[4] = (s->qdev.conf.cyls >> 16) & 0xff; + p[5] = (s->qdev.conf.cyls >> 8) & 0xff; + p[6] = s->qdev.conf.cyls & 0xff; /* Reduced current start cylinder, disabled */ - p[9] = (s->qdev.conf.cyls >> 16) & 0xff; - p[10] = (s->qdev.conf.cyls >> 8) & 0xff; - p[11] = s->qdev.conf.cyls & 0xff; + p[7] = (s->qdev.conf.cyls >> 16) & 0xff; + p[8] = (s->qdev.conf.cyls >> 8) & 0xff; + p[9] = s->qdev.conf.cyls & 0xff; /* Device step rate [ns], 200ns */ - p[12] = 0; - p[13] = 200; + p[10] = 0; + p[11] = 200; /* Landing zone cylinder */ + p[12] = 0xff; + p[13] = 0xff; p[14] = 0xff; - p[15] = 0xff; - p[16] = 0xff; /* Medium rotation rate [rpm], 5400 rpm */ - p[20] = (5400 >> 8) & 0xff; - p[21] = 5400 & 0xff; + p[18] = (5400 >> 8) & 0xff; + p[19] = 5400 & 0xff; break; case MODE_PAGE_FLEXIBLE_DISK_GEOMETRY: - p[1] = 0x1e; + length = 0x1e; if (page_control == 1) { /* Changeable Values */ break; } /* Transfer rate [kbit/s], 5Mbit/s */ - p[2] = 5000 >> 8; - p[3] = 5000 & 0xff; + p[0] = 5000 >> 8; + p[1] = 5000 & 0xff; /* if a geometry hint is available, use it */ - p[4] = s->qdev.conf.heads & 0xff; - p[5] = s->qdev.conf.secs & 0xff; - p[6] = s->qdev.blocksize >> 8; + p[2] = s->qdev.conf.heads & 0xff; + p[3] = s->qdev.conf.secs & 0xff; + p[4] = s->qdev.blocksize >> 8; + p[6] = (s->qdev.conf.cyls >> 8) & 0xff; + p[7] = s->qdev.conf.cyls & 0xff; + /* Write precomp start cylinder, disabled */ p[8] = (s->qdev.conf.cyls >> 8) & 0xff; p[9] = s->qdev.conf.cyls & 0xff; - /* Write precomp start cylinder, disabled */ + /* Reduced current start cylinder, disabled */ p[10] = (s->qdev.conf.cyls >> 8) & 0xff; p[11] = s->qdev.conf.cyls & 0xff; - /* Reduced current start cylinder, disabled */ - p[12] = (s->qdev.conf.cyls >> 8) & 0xff; - p[13] = s->qdev.conf.cyls & 0xff; /* Device step rate [100us], 100us */ - p[14] = 0; - p[15] = 1; + p[12] = 0; + p[13] = 1; /* Device step pulse width [us], 1us */ - p[16] = 1; + p[14] = 1; /* Device head settle delay [100us], 100us */ - p[17] = 0; - p[18] = 1; + p[15] = 0; + p[16] = 1; /* Motor on delay [0.1s], 0.1s */ - p[19] = 1; + p[17] = 1; /* Motor off delay [0.1s], 0.1s */ - p[20] = 1; + p[18] = 1; /* Medium rotation rate [rpm], 5400 rpm */ - p[28] = (5400 >> 8) & 0xff; - p[29] = 5400 & 0xff; + p[26] = (5400 >> 8) & 0xff; + p[27] = 5400 & 0xff; break; case MODE_PAGE_CACHING: - p[0] = 8; - p[1] = 0x12; - if (page_control == 1) { /* Changeable Values */ - break; - } - if (bdrv_enable_write_cache(s->qdev.conf.bs)) { - p[2] = 4; /* WCE */ + length = 0x12; + if (page_control == 1 || /* Changeable Values */ + bdrv_enable_write_cache(s->qdev.conf.bs)) { + p[0] = 4; /* WCE */ } break; case MODE_PAGE_R_W_ERROR: - p[1] = 10; - p[2] = 0x80; /* Automatic Write Reallocation Enabled */ + length = 10; + if (page_control == 1) { /* Changeable Values */ + break; + } + p[0] = 0x80; /* Automatic Write Reallocation Enabled */ if (s->qdev.type == TYPE_ROM) { - p[3] = 0x20; /* Read Retry Count */ + p[1] = 0x20; /* Read Retry Count */ } break; case MODE_PAGE_AUDIO_CTL: - p[1] = 14; + length = 14; break; case MODE_PAGE_CAPABILITIES: - p[1] = 0x14; + length = 0x14; if (page_control == 1) { /* Changeable Values */ break; } - p[2] = 0x3b; /* CD-R & CD-RW read */ - p[3] = 0; /* Writing not supported */ - p[4] = 0x7f; /* Audio, composite, digital out, + p[0] = 0x3b; /* CD-R & CD-RW read */ + p[1] = 0; /* Writing not supported */ + p[2] = 0x7f; /* Audio, composite, digital out, mode 2 form 1&2, multi session */ - p[5] = 0xff; /* CD DA, DA accurate, RW supported, + p[3] = 0xff; /* CD DA, DA accurate, RW supported, RW corrected, C2 errors, ISRC, UPC, Bar code */ - p[6] = 0x2d | (s->tray_locked ? 2 : 0); + p[4] = 0x2d | (s->tray_locked ? 2 : 0); /* Locking supported, jumper present, eject, tray */ - p[7] = 0; /* no volume & mute control, no + p[5] = 0; /* no volume & mute control, no changer */ - p[8] = (50 * 176) >> 8; /* 50x read speed */ - p[9] = (50 * 176) & 0xff; - p[10] = 2 >> 8; /* Two volume levels */ - p[11] = 2 & 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[6] = (50 * 176) >> 8; /* 50x read speed */ + p[7] = (50 * 176) & 0xff; + p[8] = 2 >> 8; /* Two volume levels */ + p[9] = 2 & 0xff; + p[10] = 2048 >> 8; /* 2M buffer */ + p[11] = 2048 & 0xff; + p[12] = (16 * 176) >> 8; /* 16x read speed current */ + p[13] = (16 * 176) & 0xff; + p[16] = (16 * 176) >> 8; /* 16x write speed */ + p[17] = (16 * 176) & 0xff; + p[18] = (16 * 176) >> 8; /* 16x write speed current */ p[19] = (16 * 176) & 0xff; - p[20] = (16 * 176) >> 8; /* 16x write speed current */ - p[21] = (16 * 176) & 0xff; break; default: return -1; } - *p_outbuf += p[1] + 2; - return p[1] + 2; + assert(length < 256); + (*p_outbuf)[0] = page; + (*p_outbuf)[1] = length; + *p_outbuf += length + 2; + return length + 2; } static int scsi_disk_emulate_mode_sense(SCSIDiskReq *r, uint8_t *outbuf) @@ -1237,8 +1247,14 @@ static int scsi_disk_emulate_start_stop(SCSIDiskReq *r) SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); bool start = req->cmd.buf[4] & 1; bool loej = req->cmd.buf[4] & 2; /* load on start, eject on !start */ + int pwrcnd = req->cmd.buf[4] & 0xf0; - if (s->qdev.type == TYPE_ROM && loej) { + if (pwrcnd) { + /* eject/load only happens for power condition == 0 */ + return 0; + } + + if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) && loej) { if (!start && !s->tray_open && s->tray_locked) { scsi_check_condition(r, bdrv_is_inserted(s->qdev.conf.bs) @@ -1261,7 +1277,7 @@ static void scsi_disk_emulate_read_data(SCSIRequest *req) int buflen = r->iov.iov_len; if (buflen) { - DPRINTF("Read buf_len=%zd\n", buflen); + DPRINTF("Read buf_len=%d\n", buflen); r->iov.iov_len = 0; r->started = true; scsi_req_data(&r->req, buflen); @@ -1272,13 +1288,174 @@ static void scsi_disk_emulate_read_data(SCSIRequest *req) scsi_req_complete(&r->req, GOOD); } +static int scsi_disk_check_mode_select(SCSIDiskState *s, int page, + uint8_t *inbuf, int inlen) +{ + uint8_t mode_current[SCSI_MAX_MODE_LEN]; + uint8_t mode_changeable[SCSI_MAX_MODE_LEN]; + uint8_t *p; + int len, expected_len, changeable_len, i; + + /* The input buffer does not include the page header, so it is + * off by 2 bytes. + */ + expected_len = inlen + 2; + if (expected_len > SCSI_MAX_MODE_LEN) { + return -1; + } + + p = mode_current; + memset(mode_current, 0, inlen + 2); + len = mode_sense_page(s, page, &p, 0); + if (len < 0 || len != expected_len) { + return -1; + } + + p = mode_changeable; + memset(mode_changeable, 0, inlen + 2); + changeable_len = mode_sense_page(s, page, &p, 1); + assert(changeable_len == len); + + /* Check that unchangeable bits are the same as what MODE SENSE + * would return. + */ + for (i = 2; i < len; i++) { + if (((mode_current[i] ^ inbuf[i - 2]) & ~mode_changeable[i]) != 0) { + return -1; + } + } + return 0; +} + +static void scsi_disk_apply_mode_select(SCSIDiskState *s, int page, uint8_t *p) +{ + switch (page) { + case MODE_PAGE_CACHING: + bdrv_set_enable_write_cache(s->qdev.conf.bs, (p[0] & 4) != 0); + break; + + default: + break; + } +} + +static int mode_select_pages(SCSIDiskReq *r, uint8_t *p, int len, bool change) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); + + while (len > 0) { + int page, subpage, page_len; + + /* Parse both possible formats for the mode page headers. */ + page = p[0] & 0x3f; + if (p[0] & 0x40) { + if (len < 4) { + goto invalid_param_len; + } + subpage = p[1]; + page_len = lduw_be_p(&p[2]); + p += 4; + len -= 4; + } else { + if (len < 2) { + goto invalid_param_len; + } + subpage = 0; + page_len = p[1]; + p += 2; + len -= 2; + } + + if (subpage) { + goto invalid_param; + } + if (page_len > len) { + goto invalid_param_len; + } + + if (!change) { + if (scsi_disk_check_mode_select(s, page, p, page_len) < 0) { + goto invalid_param; + } + } else { + scsi_disk_apply_mode_select(s, page, p); + } + + p += page_len; + len -= page_len; + } + return 0; + +invalid_param: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM)); + return -1; + +invalid_param_len: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN)); + return -1; +} + +static void scsi_disk_emulate_mode_select(SCSIDiskReq *r, uint8_t *inbuf) +{ + uint8_t *p = inbuf; + int cmd = r->req.cmd.buf[0]; + int len = r->req.cmd.xfer; + int hdr_len = (cmd == MODE_SELECT ? 4 : 8); + int bd_len; + int pass; + + /* We only support PF=1, SP=0. */ + if ((r->req.cmd.buf[1] & 0x11) != 0x10) { + goto invalid_field; + } + + if (len < hdr_len) { + goto invalid_param_len; + } + + bd_len = (cmd == MODE_SELECT ? p[3] : lduw_be_p(&p[6])); + len -= hdr_len; + p += hdr_len; + if (len < bd_len) { + goto invalid_param_len; + } + if (bd_len != 0 && bd_len != 8) { + goto invalid_param; + } + + len -= bd_len; + p += bd_len; + + /* Ensure no change is made if there is an error! */ + for (pass = 0; pass < 2; pass++) { + if (mode_select_pages(r, p, len, pass == 1) < 0) { + assert(pass == 0); + return; + } + } + scsi_req_complete(&r->req, GOOD); + return; + +invalid_param: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM)); + return; + +invalid_param_len: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN)); + return; + +invalid_field: + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); + return; +} + static void scsi_disk_emulate_write_data(SCSIRequest *req) { SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); if (r->iov.iov_len) { int buflen = r->iov.iov_len; - DPRINTF("Write buf_len=%zd\n", buflen); + DPRINTF("Write buf_len=%d\n", buflen); r->iov.iov_len = 0; scsi_req_data(&r->req, buflen); return; @@ -1288,7 +1465,7 @@ static void scsi_disk_emulate_write_data(SCSIRequest *req) case MODE_SELECT: case MODE_SELECT_10: /* This also clears the sense buffer for REQUEST SENSE. */ - scsi_req_complete(&r->req, GOOD); + scsi_disk_emulate_mode_select(r, r->iov.iov_base); break; default: @@ -1521,19 +1698,9 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) break; case MODE_SELECT: DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer); - /* We don't support mode parameter changes. - Allow the mode parameter header + block descriptors only. */ - if (r->req.cmd.xfer > 12) { - goto illegal_request; - } break; case MODE_SELECT_10: DPRINTF("Mode Select(10) (len %lu)\n", (long)r->req.cmd.xfer); - /* We don't support mode parameter changes. - Allow the mode parameter header + block descriptors only. */ - if (r->req.cmd.xfer > 16) { - goto illegal_request; - } break; case WRITE_SAME_10: nb_sectors = lduw_be_p(&req->cmd.buf[7]); @@ -1541,6 +1708,10 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) case WRITE_SAME_16: nb_sectors = ldl_be_p(&req->cmd.buf[10]) & 0xffffffffULL; write_same: + if (bdrv_is_read_only(s->qdev.conf.bs)) { + scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED)); + return 0; + } if (r->req.cmd.lba > s->qdev.max_lba) { goto illegal_lba; } @@ -1613,15 +1784,16 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) case READ_16: len = r->req.cmd.xfer / s->qdev.blocksize; DPRINTF("Read (sector %" PRId64 ", count %d)\n", r->req.cmd.lba, len); - if (r->req.cmd.lba > s->qdev.max_lba) { + if (r->req.cmd.buf[1] & 0xe0) { + goto illegal_request; + } + if (r->req.cmd.lba > r->req.cmd.lba + len || + r->req.cmd.lba + len - 1 > s->qdev.max_lba) { goto illegal_lba; } r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512); r->sector_count = len * (s->qdev.blocksize / 512); break; - case VERIFY_10: - case VERIFY_12: - case VERIFY_16: case WRITE_6: case WRITE_10: case WRITE_12: @@ -1629,11 +1801,23 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) case WRITE_VERIFY_10: case WRITE_VERIFY_12: case WRITE_VERIFY_16: + if (bdrv_is_read_only(s->qdev.conf.bs)) { + scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED)); + return 0; + } + /* fallthrough */ + case VERIFY_10: + case VERIFY_12: + case VERIFY_16: len = r->req.cmd.xfer / s->qdev.blocksize; DPRINTF("Write %s(sector %" PRId64 ", count %d)\n", (command & 0xe) == 0xe ? "And Verify " : "", r->req.cmd.lba, len); - if (r->req.cmd.lba > s->qdev.max_lba) { + if (r->req.cmd.buf[1] & 0xe0) { + goto illegal_request; + } + if (r->req.cmd.lba > r->req.cmd.lba + len || + r->req.cmd.lba + len - 1 > s->qdev.max_lba) { goto illegal_lba; } r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512); @@ -1641,6 +1825,9 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) break; default: abort(); + illegal_request: + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); + return 0; illegal_lba: scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); return 0; @@ -1679,6 +1866,19 @@ static void scsi_destroy(SCSIDevice *dev) blockdev_mark_auto_del(s->qdev.conf.bs); } +static void scsi_disk_resize_cb(void *opaque) +{ + SCSIDiskState *s = opaque; + + /* SPC lists this sense code as available only for + * direct-access devices. + */ + if (s->qdev.type == TYPE_DISK) { + scsi_device_set_ua(&s->qdev, SENSE_CODE(CAPACITY_CHANGED)); + scsi_device_report_change(&s->qdev, SENSE_CODE(CAPACITY_CHANGED)); + } +} + static void scsi_cd_change_media_cb(void *opaque, bool load) { SCSIDiskState *s = opaque; @@ -1695,7 +1895,7 @@ static void scsi_cd_change_media_cb(void *opaque, bool load) */ s->media_changed = load; s->tray_open = !load; - s->qdev.unit_attention = SENSE_CODE(UNIT_ATTENTION_NO_MEDIUM); + scsi_device_set_ua(&s->qdev, SENSE_CODE(UNIT_ATTENTION_NO_MEDIUM)); s->media_event = true; s->eject_request = false; } @@ -1720,11 +1920,17 @@ static bool scsi_cd_is_medium_locked(void *opaque) return ((SCSIDiskState *)opaque)->tray_locked; } -static const BlockDevOps scsi_cd_block_ops = { +static const BlockDevOps scsi_disk_removable_block_ops = { .change_media_cb = scsi_cd_change_media_cb, .eject_request_cb = scsi_cd_eject_request_cb, .is_tray_open = scsi_cd_is_tray_open, .is_medium_locked = scsi_cd_is_medium_locked, + + .resize_cb = scsi_disk_resize_cb, +}; + +static const BlockDevOps scsi_disk_block_ops = { + .resize_cb = scsi_disk_resize_cb, }; static void scsi_disk_unit_attention_reported(SCSIDevice *dev) @@ -1732,7 +1938,7 @@ static void scsi_disk_unit_attention_reported(SCSIDevice *dev) SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev); if (s->media_changed) { s->media_changed = false; - s->qdev.unit_attention = SENSE_CODE(MEDIUM_CHANGED); + scsi_device_set_ua(&s->qdev, SENSE_CODE(MEDIUM_CHANGED)); } } @@ -1769,7 +1975,9 @@ static int scsi_initfn(SCSIDevice *dev) } if (s->features & (1 << SCSI_DISK_F_REMOVABLE)) { - bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_cd_block_ops, s); + bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_disk_removable_block_ops, s); + } else { + bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_disk_block_ops, s); } bdrv_set_buffer_alignment(s->qdev.conf.bs, s->qdev.blocksize); @@ -1885,23 +2093,24 @@ static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun, const SCSIReqOps *ops; uint8_t command; + command = buf[0]; + ops = scsi_disk_reqops_dispatch[command]; + if (!ops) { + ops = &scsi_disk_emulate_reqops; + } + req = scsi_req_alloc(ops, &s->qdev, tag, lun, hba_private); + #ifdef DEBUG_SCSI - DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, buf[0]); + DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]); { int i; - for (i = 1; i < r->req.cmd.len; i++) { + for (i = 1; i < req->cmd.len; i++) { printf(" 0x%02x", buf[i]); } printf("\n"); } #endif - command = buf[0]; - ops = scsi_disk_reqops_dispatch[command]; - if (!ops) { - ops = &scsi_disk_emulate_reqops; - } - req = scsi_req_alloc(ops, &s->qdev, tag, lun, hba_private); return req; }