+static inline bool media_is_dvd(SCSIDiskState *s)
+{
+ uint64_t nb_sectors;
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ if (!bdrv_is_inserted(s->qdev.conf.bs)) {
+ return false;
+ }
+ bdrv_get_geometry(s->qdev.conf.bs, &nb_sectors);
+ return nb_sectors > CD_MAX_SECTORS;
+}
+
+static inline bool media_is_cd(SCSIDiskState *s)
+{
+ uint64_t nb_sectors;
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ if (!bdrv_is_inserted(s->qdev.conf.bs)) {
+ return false;
+ }
+ bdrv_get_geometry(s->qdev.conf.bs, &nb_sectors);
+ return nb_sectors <= CD_MAX_SECTORS;
+}
+
+static int scsi_read_dvd_structure(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ static const int rds_caps_size[5] = {
+ [0] = 2048 + 4,
+ [1] = 4 + 4,
+ [3] = 188 + 4,
+ [4] = 2048 + 4,
+ };
+
+ uint8_t media = r->req.cmd.buf[1];
+ uint8_t layer = r->req.cmd.buf[6];
+ uint8_t format = r->req.cmd.buf[7];
+ int size = -1;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ if (media != 0) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return -1;
+ }
+
+ if (format != 0xff) {
+ if (s->tray_open || !bdrv_is_inserted(s->qdev.conf.bs)) {
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ return -1;
+ }
+ if (media_is_cd(s)) {
+ scsi_check_condition(r, SENSE_CODE(INCOMPATIBLE_FORMAT));
+ return -1;
+ }
+ if (format >= ARRAY_SIZE(rds_caps_size)) {
+ return -1;
+ }
+ size = rds_caps_size[format];
+ memset(outbuf, 0, size);
+ }
+
+ switch (format) {
+ case 0x00: {
+ /* Physical format information */
+ uint64_t nb_sectors;
+ if (layer != 0) {
+ goto fail;
+ }
+ bdrv_get_geometry(s->qdev.conf.bs, &nb_sectors);
+
+ outbuf[4] = 1; /* DVD-ROM, part version 1 */
+ outbuf[5] = 0xf; /* 120mm disc, minimum rate unspecified */
+ outbuf[6] = 1; /* one layer, read-only (per MMC-2 spec) */
+ outbuf[7] = 0; /* default densities */
+
+ stl_be_p(&outbuf[12], (nb_sectors >> 2) - 1); /* end sector */
+ stl_be_p(&outbuf[16], (nb_sectors >> 2) - 1); /* l0 end sector */
+ break;
+ }
+
+ case 0x01: /* DVD copyright information, all zeros */
+ break;
+
+ case 0x03: /* BCA information - invalid field for no BCA info */
+ return -1;
+
+ case 0x04: /* DVD disc manufacturing information, all zeros */
+ break;
+
+ case 0xff: { /* List capabilities */
+ int i;
+ size = 4;
+ for (i = 0; i < ARRAY_SIZE(rds_caps_size); i++) {
+ if (!rds_caps_size[i]) {
+ continue;
+ }
+ outbuf[size] = i;
+ outbuf[size + 1] = 0x40; /* Not writable, readable */
+ stw_be_p(&outbuf[size + 2], rds_caps_size[i]);
+ size += 4;
+ }
+ break;
+ }
+
+ default:
+ return -1;
+ }
+
+ /* Size of buffer, not including 2 byte size field */
+ stw_be_p(outbuf, size - 2);
+ return size;
+
+fail:
+ return -1;
+}
+
+static int scsi_event_status_media(SCSIDiskState *s, uint8_t *outbuf)
+{
+ uint8_t event_code, media_status;
+
+ media_status = 0;
+ if (s->tray_open) {
+ media_status = MS_TRAY_OPEN;
+ } else if (bdrv_is_inserted(s->qdev.conf.bs)) {
+ media_status = MS_MEDIA_PRESENT;
+ }
+
+ /* Event notification descriptor */
+ event_code = MEC_NO_CHANGE;
+ if (media_status != MS_TRAY_OPEN) {
+ if (s->media_event) {
+ event_code = MEC_NEW_MEDIA;
+ s->media_event = false;
+ } else if (s->eject_request) {
+ event_code = MEC_EJECT_REQUESTED;
+ s->eject_request = false;
+ }
+ }
+
+ outbuf[0] = event_code;
+ outbuf[1] = media_status;
+
+ /* These fields are reserved, just clear them. */
+ outbuf[2] = 0;
+ outbuf[3] = 0;
+ return 4;
+}
+
+static int scsi_get_event_status_notification(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ int size;
+ uint8_t *buf = r->req.cmd.buf;
+ uint8_t notification_class_request = buf[4];
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ if ((buf[1] & 1) == 0) {
+ /* asynchronous */
+ return -1;
+ }
+
+ size = 4;
+ outbuf[0] = outbuf[1] = 0;
+ outbuf[3] = 1 << GESN_MEDIA; /* supported events */
+ if (notification_class_request & (1 << GESN_MEDIA)) {
+ outbuf[2] = GESN_MEDIA;
+ size += scsi_event_status_media(s, &outbuf[size]);
+ } else {
+ outbuf[2] = 0x80;
+ }
+ stw_be_p(outbuf, size - 4);
+ return size;
+}
+
+static int scsi_get_configuration(SCSIDiskState *s, uint8_t *outbuf)
+{
+ int current;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ current = media_is_dvd(s) ? MMC_PROFILE_DVD_ROM : MMC_PROFILE_CD_ROM;
+ memset(outbuf, 0, 40);
+ stl_be_p(&outbuf[0], 36); /* Bytes after the data length field */
+ stw_be_p(&outbuf[6], current);
+ /* outbuf[8] - outbuf[19]: Feature 0 - Profile list */
+ outbuf[10] = 0x03; /* persistent, current */
+ outbuf[11] = 8; /* two profiles */
+ stw_be_p(&outbuf[12], MMC_PROFILE_DVD_ROM);
+ outbuf[14] = (current == MMC_PROFILE_DVD_ROM);
+ stw_be_p(&outbuf[16], MMC_PROFILE_CD_ROM);
+ outbuf[18] = (current == MMC_PROFILE_CD_ROM);
+ /* outbuf[20] - outbuf[31]: Feature 1 - Core feature */
+ stw_be_p(&outbuf[20], 1);
+ outbuf[22] = 0x08 | 0x03; /* version 2, persistent, current */
+ outbuf[23] = 8;
+ stl_be_p(&outbuf[24], 1); /* SCSI */
+ outbuf[28] = 1; /* DBE = 1, mandatory */
+ /* outbuf[32] - outbuf[39]: Feature 3 - Removable media feature */
+ stw_be_p(&outbuf[32], 3);
+ outbuf[34] = 0x08 | 0x03; /* version 2, persistent, current */
+ outbuf[35] = 4;
+ outbuf[36] = 0x39; /* tray, load=1, eject=1, unlocked at powerup, lock=1 */
+ /* TODO: Random readable, CD read, DVD read, drive serial number,
+ power management */
+ return 40;
+}
+
+static int scsi_emulate_mechanism_status(SCSIDiskState *s, uint8_t *outbuf)
+{
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ memset(outbuf, 0, 8);
+ outbuf[5] = 1; /* CD-ROM */
+ return 8;
+}
+