396 lines
8.6 KiB
C
396 lines
8.6 KiB
C
/*
|
|
* 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, <mgrca8@gmail.com>
|
|
*
|
|
* Copyright 2017,2018 Miran Grca.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <wchar.h>
|
|
#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;
|
|
}
|