pcem/src/ide_atapi.c
SarahW a7d006b637 Rewrote timer code.
The new code is based on timestamps, rather than delays. This eliminates the
need to update every timer in timer_process(), which cost a non-trivial amount
of CPU time on low-end machines.

This involved changes to every user of the timer code, so this change almost
certainly introduces bugs.
2018-08-02 22:06:10 +01:00

565 lines
28 KiB
C

#include "ibm.h"
#include "ide.h"
#include "ide_atapi.h"
#include "scsi.h"
#include "timer.h"
#define IDE_TIME (100 * TIMER_USEC)
#define ATAPI_STATE_IDLE 0
#define ATAPI_STATE_COMMAND 1
#define ATAPI_STATE_GOT_COMMAND 2
#define ATAPI_STATE_NEXT_PHASE 3
#define ATAPI_STATE_READ_STATUS 4
#define ATAPI_STATE_READ_MESSAGE 5
#define ATAPI_STATE_END_PHASE 6
#define ATAPI_STATE_READ_DATA 7
#define ATAPI_STATE_READ_DATA_WAIT 8
#define ATAPI_STATE_WRITE_DATA 9
#define ATAPI_STATE_WRITE_DATA_WAIT 10
#define ATAPI_STATE_RETRY_READ_DMA 11
#define ATAPI_STATE_RETRY_WRITE_DMA 12
#define FEATURES_DMA 0x01
#define FEATURES_OVERLAP 0x02
ATAPI *atapi;
void atapi_data_write(atapi_device_t *atapi_dev, uint16_t val)
{
switch (atapi_dev->state)
{
case ATAPI_STATE_COMMAND:
atapi_dev->command[atapi_dev->command_pos++] = val & 0xff;
atapi_dev->command[atapi_dev->command_pos++] = val >> 8;
if (atapi_dev->command_pos >= 12)
{
atapi_dev->state = ATAPI_STATE_GOT_COMMAND;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
break;
case ATAPI_STATE_WRITE_DATA_WAIT:
atapi_dev->data[atapi_dev->data_write_pos++] = val & 0xff;
atapi_dev->data[atapi_dev->data_write_pos++] = val >> 8;
// pclog("Write ATAPI data %i %i %i\n", atapi_dev->data_write_pos, atapi_dev->data_read_pos, idecallback[atapi_dev->board]);
if (atapi_dev->data_write_pos >= atapi_dev->data_read_pos)
{
atapi_dev->bus_state = 0;
atapi_dev->state = ATAPI_STATE_WRITE_DATA;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
break;
}
}
uint16_t atapi_data_read(atapi_device_t *atapi_dev)
{
uint16_t temp = 0xffff;
switch (atapi_dev->state)
{
case ATAPI_STATE_READ_DATA_WAIT:
if (atapi_dev->data_read_pos >= atapi_dev->data_write_pos)
{
// pclog("ATAPI_STATE_READ_DATA_WAIT - out of data!\n");
break;
}
temp = atapi_dev->data[atapi_dev->data_read_pos++];
if (atapi_dev->data_read_pos < atapi_dev->data_write_pos)
temp |= (atapi_dev->data[atapi_dev->data_read_pos++] << 8);
// pclog("Read data %04x\n", temp);
if (atapi_dev->data_read_pos >= atapi_dev->data_write_pos)
{
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6*IDE_TIME);
}
break;
}
return temp;
}
static int wait_for_bus(scsi_bus_t *bus, int state, int req_needed)
{
int c;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(bus);
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) == state && (bus_state & BUS_BSY))
{
if (!req_needed || (bus_state & BUS_REQ))
return 1;
}
}
return 0;
}
void atapi_command_start(atapi_device_t *atapi, uint8_t features)
{
scsi_bus_t *bus = &atapi->bus;
atapi->use_dma = features & 1;
scsi_bus_update(bus, BUS_SEL | BUS_SETDATA(1 << 0));
if (!(scsi_bus_read(bus) & BUS_BSY))
fatal("STATE_SCSI_SELECT failed to select target\n");
scsi_bus_update(bus, 0);
if (!(scsi_bus_read(bus) & BUS_BSY))
fatal("STATE_SCSI_SELECT failed to select target 2\n");
/*Device should now be selected*/
if (!wait_for_bus(bus, BUS_CD, 1))
fatal("Device failed to request command\n");
atapi->state = ATAPI_STATE_COMMAND;
atapi->command_pos = 0;
}
uint8_t atapi_read_iir(atapi_device_t *atapi_dev)
{
uint8_t val = 0;
int bus_status = atapi_dev->bus_state;//scsi_bus_read(&atapi_dev->bus);
if (bus_status & BUS_CD)
val |= 1;
if (bus_status & BUS_IO)
val |= 2;
// pclog("Read IIR %02X %02x\n", val, bus_status);
return val;
}
uint8_t atapi_read_drq(atapi_device_t *atapi_dev)
{
return atapi_dev->bus_state & BUS_REQ;
}
void atapi_set_transfer_granularity(atapi_device_t *atapi_dev, int size)
{
if (atapi_dev->max_transfer_len > size)
atapi_dev->max_transfer_len -= (atapi_dev->max_transfer_len % size);
}
void atapi_process_packet(atapi_device_t *atapi_dev)
{
// pclog("atapi_process_packet: state=%i\n", atapi_dev->state);
switch (atapi_dev->state)
{
case ATAPI_STATE_COMMAND:
*atapi_dev->atastat = READY_STAT | (*atapi_dev->atastat & ERR_STAT);
atapi_dev->max_transfer_len = (*atapi_dev->cylinder) & ~1;
if (!atapi_dev->max_transfer_len)
atapi_dev->max_transfer_len = 0xfffe;
atapi_dev->bus_state = BUS_CD | BUS_REQ;
break;
case ATAPI_STATE_GOT_COMMAND:
{
int pos = 0;
int bus_state;
int c;
for (c = 0; c < 20; c++)
{
bus_state = scsi_bus_read(&atapi_dev->bus);
if (!(bus_state & BUS_BSY))
fatal("SEND_COMMAND - dropped BSY\n");
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != BUS_CD)
fatal("SEND_COMMAND - bus phase incorrect\n");
if (bus_state & BUS_REQ)
break;
}
if (c == 20)
fatal("SEND_COMMAND timed out\n");
while (1)
{
int bus_out;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&atapi_dev->bus);
if (!(bus_state & BUS_BSY))
fatal("SEND_COMMAND - dropped BSY\n");
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != BUS_CD)
break;
if (bus_state & BUS_REQ)
break;
}
if (c == 20)
{
// pclog("SEND_COMMAND timed out %x\n", scsi_bus_read(&atapi_dev->bus));
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
break;
}
bus_state = scsi_bus_read(&atapi_dev->bus);
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG | BUS_REQ)) != (BUS_CD | BUS_REQ))
{
// pclog("SEND_COMMAND - bus state changed %x\n", bus_state);
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
break;
}
if (pos >= atapi_dev->command_pos)
bus_out = BUS_SETDATA(0); /*Pad out with zeroes*/
else
bus_out = BUS_SETDATA(atapi_dev->command[pos++]);
scsi_bus_update(&atapi_dev->bus, bus_out | BUS_ACK);
scsi_bus_update(&atapi_dev->bus, bus_out & ~BUS_ACK);
if (!(bus_state & BUS_BSY))
fatal("SEND_COMMAND - dropped BSY\n");
// if (pos >= atapi_dev->command_pos)
// fatal("Out of data!\n");
}
}
break;
case ATAPI_STATE_NEXT_PHASE:
{
int c;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&atapi_dev->bus);
// if (!c)
// pclog("NEXT_PHASE - %x\n", bus_state);
if (!(bus_state & BUS_BSY))
fatal("NEXT_PHASE - dropped BSY waiting\n");
if (bus_state & BUS_REQ)
{
scsi_device_t *scsi_dev = atapi_dev->bus.devices[0];
void *scsi_data = atapi_dev->bus.device_data[0];
switch (bus_state & (BUS_IO | BUS_CD | BUS_MSG))
{
case 0:
atapi_dev->data_read_pos = scsi_dev->get_bytes_required(scsi_data);
atapi_dev->data_write_pos = 0;
if (atapi_dev->use_dma)
{
if (ide_bus_master_write_data)
{
if (ide_bus_master_write_data(atapi_dev->board, atapi_dev->data, atapi_dev->data_read_pos))
{
atapi_dev->state = ATAPI_STATE_RETRY_WRITE_DMA;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1*IDE_TIME);
}
else
{
atapi_dev->data_write_pos = atapi_dev->data_read_pos;
atapi_dev->bus_state = 0;
atapi_dev->state = ATAPI_STATE_WRITE_DATA;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
}
else
{
atapi_dev->state = ATAPI_STATE_RETRY_WRITE_DMA;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1*IDE_TIME);
}
}
else
{
if (atapi_dev->data_read_pos > atapi_dev->max_transfer_len)
atapi_dev->data_read_pos = atapi_dev->max_transfer_len;
atapi_dev->state = ATAPI_STATE_WRITE_DATA_WAIT;
*atapi_dev->cylinder = atapi_dev->data_read_pos;
// pclog("WRITE_DATA: bytes=%i\n", *atapi_dev->cylinder);
*atapi_dev->atastat = READY_STAT | DRQ_STAT | (*atapi_dev->atastat & ERR_STAT);
atapi_dev->bus_state = BUS_REQ;
ide_irq_raise(atapi_dev->ide);
}
break;
case BUS_IO:
atapi_dev->state = ATAPI_STATE_READ_DATA;
atapi_dev->data_read_pos = atapi_dev->data_write_pos = 0;
break;
case (BUS_IO | BUS_CD):
atapi_dev->state = ATAPI_STATE_READ_STATUS;
break;
case (BUS_CD | BUS_IO | BUS_MSG):
atapi_dev->state = ATAPI_STATE_READ_MESSAGE;
break;
default:
fatal("NEXT_PHASE - bus state changed %x\n", bus_state);
}
break;
}
}
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
break;
case ATAPI_STATE_READ_STATUS:
{
int c;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&atapi_dev->bus);
if (!(bus_state & BUS_BSY))
fatal("READ_STATUS - dropped BSY waiting\n");
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != (BUS_CD | BUS_IO))
fatal("READ_STATUS - changed phase\n");
if (bus_state & BUS_REQ)
{
// uint8_t status = BUS_GETDATA(bus_state);
int bus_out = 0;
// pclog("Read status %02x\n", status);
scsi_bus_update(&atapi_dev->bus, bus_out | BUS_ACK);
scsi_bus_update(&atapi_dev->bus, bus_out & ~BUS_ACK);
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
break;
}
}
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
break;
case ATAPI_STATE_READ_MESSAGE:
{
int c;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&atapi_dev->bus);
if (!(bus_state & BUS_BSY))
fatal("READ_MESSAGE - dropped BSY waiting\n");
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != (BUS_CD | BUS_IO | BUS_MSG))
{
// pclog("READ_MESSAGE - changed phase\n");
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
break;
}
if (bus_state & BUS_REQ)
{
uint8_t msg = BUS_GETDATA(bus_state);
int bus_out = 0;
// pclog("Read message %02x\n", msg);
scsi_bus_update(&atapi_dev->bus, bus_out | BUS_ACK);
scsi_bus_update(&atapi_dev->bus, bus_out & ~BUS_ACK);
switch (msg)
{
case MSG_COMMAND_COMPLETE:
atapi_dev->state = ATAPI_STATE_END_PHASE;
break;
default:
fatal("READ_MESSAGE - unknown message %02x\n", msg);
}
break;
}
}
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
break;
case ATAPI_STATE_READ_DATA:
{
while (atapi_dev->data_write_pos < atapi_dev->max_transfer_len)
{
int c;
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&atapi_dev->bus);
if (!(bus_state & BUS_BSY))
fatal("READ_DATA - dropped BSY waiting\n");
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != BUS_IO)
{
// pclog("READ_DATA - changed phase\n");
break;
}
if (bus_state & BUS_REQ)
{
uint8_t data = BUS_GETDATA(bus_state);
int bus_out = 0;
atapi_dev->data[atapi_dev->data_write_pos++] = data;
// pclog("Read data %02x %i\n", data, atapi_dev->data_write_pos);
scsi_bus_update(&atapi_dev->bus, bus_out | BUS_ACK);
scsi_bus_update(&atapi_dev->bus, bus_out & ~BUS_ACK);
break;
}
}
if ((scsi_bus_read(&atapi_dev->bus) & (BUS_IO | BUS_CD | BUS_MSG)) != BUS_IO)
break;
}
if (atapi_dev->use_dma)
{
if (ide_bus_master_read_data)
{
if (ide_bus_master_read_data(atapi_dev->board, atapi_dev->data, atapi_dev->data_write_pos))
{
atapi_dev->state = ATAPI_STATE_RETRY_READ_DMA;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1*IDE_TIME);
}
else
{
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1*IDE_TIME);
}
}
else
{
atapi_dev->state = ATAPI_STATE_RETRY_READ_DMA;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1*IDE_TIME);
}
}
else
{
atapi_dev->state = ATAPI_STATE_READ_DATA_WAIT;
*atapi_dev->atastat = READY_STAT | DRQ_STAT | (*atapi_dev->atastat & ERR_STAT);
*atapi_dev->cylinder = atapi_dev->data_write_pos;
// pclog("READ_DATA: bytes=%i\n", *atapi_dev->cylinder);
atapi_dev->bus_state = BUS_IO | BUS_REQ;
ide_irq_raise(atapi_dev->ide);
}
}
break;
case ATAPI_STATE_WRITE_DATA:
{
atapi_dev->data_read_pos = 0;
// pclog("ATAPI_STATE_WRITE_DATA - %i\n", atapi_dev->data_write_pos);
while (atapi_dev->data_read_pos < atapi_dev->data_write_pos)
{
int bus_state = scsi_bus_read(&atapi_dev->bus);
if (!(bus_state & BUS_BSY))
fatal("WRITE_DATA - dropped BSY waiting\n");
if ((bus_state & (BUS_IO | BUS_CD | BUS_MSG)) != 0)
{
// pclog("WRITE_DATA - changed phase\n");
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
break;
}
if (bus_state & BUS_REQ)
{
uint8_t data = atapi_dev->data[atapi_dev->data_read_pos++];
int bus_out;
// pclog("Write data %02x %i\n", data, atapi_dev->data_read_pos-1);
bus_out = BUS_SETDATA(data);
scsi_bus_update(&atapi_dev->bus, bus_out | BUS_ACK);
scsi_bus_update(&atapi_dev->bus, bus_out & ~BUS_ACK);
}
}
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1 * IDE_TIME);
}
break;
case ATAPI_STATE_END_PHASE:
{
int c;
/*Wait for SCSI command to move to next phase*/
for (c = 0; c < 20; c++)
{
int bus_state = scsi_bus_read(&atapi_dev->bus);
if (!(bus_state & BUS_BSY))
{
// pclog("END_PHASE - dropped BSY waiting\n");
atapi_dev->state = ATAPI_STATE_IDLE;
break;
}
if (bus_state & BUS_REQ)
fatal("END_PHASE - unexpected REQ\n");
}
if (atapi_dev->state == ATAPI_STATE_IDLE)
{
scsi_device_t *scsi_dev = atapi_dev->bus.devices[0];
void *scsi_data = atapi_dev->bus.device_data[0];
*atapi_dev->atastat = READY_STAT;
atapi_dev->bus_state = BUS_IO | BUS_CD;
if (scsi_dev->get_status(scsi_data) != STATUS_GOOD)
{
*atapi_dev->error = (scsi_dev->get_sense_key(scsi_data) << 4) | ABRT_ERR;
if (scsi_dev->get_sense_key(scsi_data) == KEY_UNIT_ATTENTION)
*atapi_dev->error |= MCR_ERR;
*atapi_dev->atastat |= ERR_STAT;
}
ide_irq_raise(atapi_dev->ide);
}
else
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
break;
case ATAPI_STATE_RETRY_READ_DMA:
{
if (ide_bus_master_read_data(atapi_dev->board, atapi_dev->data, atapi_dev->data_write_pos))
{
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1*IDE_TIME);
}
else
{
atapi_dev->state = ATAPI_STATE_NEXT_PHASE;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6*IDE_TIME);
}
}
break;
case ATAPI_STATE_RETRY_WRITE_DMA:
{
if (ide_bus_master_write_data(atapi_dev->board, atapi_dev->data, atapi_dev->data_read_pos))
{
timer_set_delay_u64(&ide_timer[atapi_dev->board], 1*IDE_TIME);
}
else
{
atapi_dev->bus_state = 0;
atapi_dev->state = ATAPI_STATE_WRITE_DATA;
timer_set_delay_u64(&ide_timer[atapi_dev->board], 6 * IDE_TIME);
}
}
break;
}
}