/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * Emulation of SCSI null device, used for invalid LUN's where * LUN 0 is valid. * * Version: @(#)scsi_null.c 1.0.0 2018/06/12 * * Author: Miran Grca, * * Copyright 2017,2018 Miran Grca. */ #include #include #include #include #include #include #define HAVE_STDARG_H #include "../86box.h" #include "../timer.h" #include "../device.h" #include "../nvr.h" #include "../disk/hdd.h" #include "../disk/hdc.h" #include "../disk/hdc_ide.h" #include "../plat.h" #include "../ui.h" #include "scsi.h" #include "../cdrom/cdrom.h" #include "scsi_device.h" /* Bits of 'status' */ #define ERR_STAT 0x01 #define DRQ_STAT 0x08 /* Data request */ #define DSC_STAT 0x10 #define SERVICE_STAT 0x10 #define READY_STAT 0x40 #define BUSY_STAT 0x80 /* Bits of 'error' */ #define ABRT_ERR 0x04 /* Command aborted */ #define MCR_ERR 0x08 /* Media change request */ #define MAX_BLOCKS_AT_ONCE 340 #define scsi_null_sense_key scsi_null_device_sense[2] #define scsi_null_asc scsi_null_device_sense[12] #define scsi_null_ascq scsi_null_device_sense[13] static uint8_t status, phase, packet_status, error, command, packet_len, sense_desc; static int64_t callback; static uint8_t null_id, null_lun; static uint8_t *temp_buffer; #ifdef ENABLE_SCSI_NULL_LOG int scsi_null_do_log = ENABLE_SCSI_NULL_LOG; #endif static void scsi_null_log(const char *fmt, ...) { #ifdef ENABLE_SCSI_NULL_LOG va_list ap; if (scsi_null_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } #endif } /* Translates ATAPI status (ERR_STAT flag) to SCSI status. */ int scsi_null_err_stat_to_scsi(void) { if (status & ERR_STAT) return SCSI_STATUS_CHECK_CONDITION; else return SCSI_STATUS_OK; } static void scsi_null_command_common(void) { status = BUSY_STAT; phase = 1; if (packet_status == CDROM_PHASE_COMPLETE) { scsi_null_callback(); callback = 0LL; } else callback = -1LL; /* Speed depends on SCSI controller */ } static void scsi_null_command_complete(void) { packet_status = CDROM_PHASE_COMPLETE; scsi_null_command_common(); } static void scsi_null_command_read_dma(void) { packet_status = CDROM_PHASE_DATA_IN_DMA; scsi_null_command_common(); } static void scsi_null_data_command_finish(int len, int block_len, int alloc_len, int direction) { if (alloc_len >= 0) { if (alloc_len < len) len = alloc_len; } if (len == 0) scsi_null_command_complete(); else { if (direction == 0) scsi_null_command_read_dma(); else fatal("SCSI NULL device write command\n"); } } static void scsi_null_set_phase(uint8_t phase) { SCSIDevices[null_id][null_lun].Phase = phase; } static void scsi_null_cmd_error(void) { scsi_null_set_phase(SCSI_PHASE_STATUS); error = ((scsi_null_sense_key & 0xf) << 4) | ABRT_ERR; status = READY_STAT | ERR_STAT; phase = 3; packet_status = 0x80; callback = 50 * SCSI_TIME; scsi_null_log("SCSI NULL: ERROR: %02X/%02X/%02X\n", scsi_null_sense_key, scsi_null_asc, scsi_null_ascq); } static void scsi_null_invalid_lun(void) { scsi_null_sense_key = SENSE_ILLEGAL_REQUEST; scsi_null_asc = ASC_INV_LUN; scsi_null_ascq = 0; scsi_null_set_phase(SCSI_PHASE_STATUS); scsi_null_cmd_error(); } static void scsi_null_invalid_field(void) { scsi_null_sense_key = SENSE_ILLEGAL_REQUEST; scsi_null_asc = ASC_INV_FIELD_IN_CMD_PACKET; scsi_null_ascq = 0; scsi_null_cmd_error(); status = 0x53; } void scsi_null_request_sense(uint8_t *buffer, uint8_t alloc_length, int desc) { /*Will return 18 bytes of 0*/ if (alloc_length != 0) { memset(buffer, 0, alloc_length); if (!desc) if (alloc_length <= 18) memcpy(buffer, scsi_null_device_sense, alloc_length); else { memset(buffer, 0x00, alloc_length); memcpy(buffer, scsi_null_device_sense, 18); } else { buffer[1] = scsi_null_sense_key; buffer[2] = scsi_null_asc; buffer[3] = scsi_null_ascq; } } else return; buffer[0] = 0x70; scsi_null_log("SCSI NULL: Reporting sense: %02X %02X %02X\n", buffer[2], buffer[12], buffer[13]); /* Clear the sense stuff as per the spec. */ scsi_null_sense_key = scsi_null_asc = scsi_null_ascq = 0x00; } void scsi_null_request_sense_for_scsi(uint8_t *buffer, uint8_t alloc_length) { scsi_null_request_sense(buffer, alloc_length, 0); } void scsi_null_command(uint8_t *cdb) { int32_t *BufLen; uint32_t len; int max_len; unsigned idx = 0; unsigned size_idx, preamble_len; BufLen = &SCSIDevices[null_id][null_lun].BufferLength; status &= ~ERR_STAT; packet_len = 0; scsi_null_set_phase(SCSI_PHASE_STATUS); command = cdb[0]; switch (cdb[0]) { case GPCMD_REQUEST_SENSE: /* If there's a unit attention condition and there's a buffered not ready, a standalone REQUEST SENSE should forget about the not ready, and report unit attention straight away. */ sense_desc = cdb [1]; if ((*BufLen == -1) || (cdb[4] < *BufLen)) *BufLen = cdb[4]; if (*BufLen < cdb[4]) cdb[4] = *BufLen; len = (cdb[1] & 1) ? 8 : 18; scsi_null_set_phase(SCSI_PHASE_DATA_IN); scsi_null_data_command_finish(len, len, cdb[4], 0); break; #if 0 case GPCMD_INQUIRY: max_len = cdb[3]; max_len <<= 8; max_len |= cdb[4]; if ((!max_len) || (*BufLen == 0)) { scsi_null_set_phase(SCSI_PHASE_STATUS); packet_status = CDROM_PHASE_COMPLETE; callback = 20 * SCSI_TIME; break; } if (cdb[1] & 1) { scsi_null_invalid_field(); return; } else { temp_buffer = malloc(1024); preamble_len = 5; size_idx = 4; memset(temp_buffer, 0, 8); temp_buffer[0] = (3 << 5); /*Invalid*/ temp_buffer[1] = 0; /*Fixed*/ temp_buffer[2] = 0x02; /*SCSI-2 compliant*/ temp_buffer[3] = 0x02; temp_buffer[4] = 31; temp_buffer[6] = 1; /* 16-bit transfers supported */ temp_buffer[7] = 0x20; /* Wide bus supported */ ide_padstr8(temp_buffer + 8, 8, EMU_NAME); /* Vendor */ ide_padstr8(temp_buffer + 16, 16, "INVALID"); /* Product */ ide_padstr8(temp_buffer + 32, 4, EMU_VERSION); /* Revision */ idx = 36; if (max_len == 96) { temp_buffer[4] = 91; idx = 96; } } temp_buffer[size_idx] = idx - preamble_len; len=idx; if (len > max_len) len = max_len; if ((*BufLen == -1) || (len < *BufLen)) *BufLen = len; if (len > *BufLen) len = *BufLen; scsi_null_set_phase(SCSI_PHASE_DATA_IN); scsi_null_data_command_finish(len, len, max_len, 0); break; #endif default: scsi_null_invalid_lun(); break; } } static void scsi_null_phase_data_in(void) { uint8_t *hdbufferb = SCSIDevices[null_id][null_lun].CmdBuffer; int32_t *BufLen = &SCSIDevices[null_id][null_lun].BufferLength; if (!*BufLen) { scsi_null_log("scsi_null_phase_data_in(): Buffer length is 0\n"); scsi_null_set_phase(SCSI_PHASE_STATUS); return; } switch (command) { case GPCMD_REQUEST_SENSE: scsi_null_log("SCSI NULL: %08X, %08X\n", hdbufferb, *BufLen); scsi_null_request_sense(hdbufferb, *BufLen, sense_desc & 1); break; #if 0 case GPCMD_INQUIRY: memcpy(hdbufferb, temp_buffer, *BufLen); free(temp_buffer); temp_buffer = NULL; scsi_null_log("%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", hdbufferb[0], hdbufferb[1], hdbufferb[2], hdbufferb[3], hdbufferb[4], hdbufferb[5], hdbufferb[6], hdbufferb[7], hdbufferb[8], hdbufferb[9], hdbufferb[10], hdbufferb[11], hdbufferb[12], hdbufferb[13], hdbufferb[14], hdbufferb[15]); break; #endif default: fatal("SCSI NULL: Bad Command for phase 2 (%02X)\n", command); break; } scsi_null_set_phase(SCSI_PHASE_STATUS); } void scsi_null_callback(void) { switch(packet_status) { case CDROM_PHASE_IDLE: scsi_null_log("SCSI NULL: PHASE_IDLE\n"); phase = 1; status = READY_STAT | DRQ_STAT | (status & ERR_STAT); return; case CDROM_PHASE_COMPLETE: scsi_null_log("SCSI NULL: PHASE_COMPLETE\n"); status = READY_STAT; phase = 3; packet_status = 0xFF; return; case CDROM_PHASE_DATA_IN_DMA: scsi_null_log("SCSI NULL: PHASE_DATA_IN_DMA\n"); scsi_null_phase_data_in(); packet_status = CDROM_PHASE_COMPLETE; status = READY_STAT; phase = 3; return; case CDROM_PHASE_ERROR: scsi_null_log("SCSI NULL: PHASE_ERROR\n"); status = READY_STAT | ERR_STAT; phase = 3; return; } } void scsi_null_set_location(uint8_t id, uint8_t lun) { null_id = id; null_lun = lun; }