Commit 4d611c9a authored by pbrook's avatar pbrook
Browse files

SCSI and USB async IO support.


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2107 c046a42c-6fe2-441c-8c8c-71466251a162
parent 4ca9c76f
......@@ -62,6 +62,11 @@ struct ESPState {
uint8_t cmdbuf[TI_BUFSZ];
int cmdlen;
int do_cmd;
uint32_t dma_left;
uint8_t async_buf[TARGET_PAGE_SIZE];
uint32_t async_ptr;
uint32_t async_len;
};
#define STAT_DO 0x00
......@@ -72,6 +77,8 @@ struct ESPState {
#define STAT_MO 0x07
#define STAT_TC 0x10
#define STAT_PE 0x20
#define STAT_GE 0x40
#define STAT_IN 0x80
#define INTR_FC 0x08
......@@ -195,26 +202,85 @@ static void write_response(ESPState *s)
}
static void esp_command_complete(void *opaque, uint32_t tag, int sense)
static void esp_do_dma(ESPState *s)
{
uint32_t dmaptr, minlen, len, from, to;
int to_device;
dmaptr = iommu_translate(s->espdmaregs[1]);
to_device = (s->espdmaregs[0] & DMA_WRITE_MEM) == 0;
from = s->espdmaregs[1];
minlen = s->dma_left;
to = from + minlen;
dmaptr = iommu_translate(s->espdmaregs[1]);
if ((from & TARGET_PAGE_MASK) != (to & TARGET_PAGE_MASK)) {
len = TARGET_PAGE_SIZE - (from & ~TARGET_PAGE_MASK);
} else {
len = to - from;
}
DPRINTF("DMA address p %08x v %08x len %08x, from %08x, to %08x\n", dmaptr, s->espdmaregs[1], len, from, to);
s->espdmaregs[1] += len;
if (s->do_cmd) {
s->ti_size -= len;
DPRINTF("command len %d + %d\n", s->cmdlen, len);
cpu_physical_memory_read(dmaptr, &s->cmdbuf[s->cmdlen], len);
s->ti_size = 0;
s->cmdlen = 0;
s->do_cmd = 0;
do_cmd(s, s->cmdbuf);
return;
} else {
s->async_len = len;
s->dma_left -= len;
if (to_device) {
s->async_ptr = -1;
cpu_physical_memory_read(dmaptr, s->async_buf, len);
scsi_write_data(s->current_dev, s->async_buf, len);
} else {
s->async_ptr = dmaptr;
scsi_read_data(s->current_dev, s->async_buf, len);
}
}
}
static void esp_command_complete(void *opaque, uint32_t reason, int sense)
{
ESPState *s = (ESPState *)opaque;
DPRINTF("SCSI Command complete\n");
if (s->ti_size != 0)
DPRINTF("SCSI command completed unexpectedly\n");
s->ti_size = 0;
if (sense)
DPRINTF("Command failed\n");
s->sense = sense;
s->rregs[4] = STAT_IN | STAT_TC | STAT_ST;
s->ti_size -= s->async_len;
s->espdmaregs[1] += s->async_len;
if (s->async_ptr != (uint32_t)-1) {
cpu_physical_memory_write(s->async_ptr, s->async_buf, s->async_len);
}
if (reason == SCSI_REASON_DONE) {
DPRINTF("SCSI Command complete\n");
if (s->ti_size != 0)
DPRINTF("SCSI command completed unexpectedly\n");
s->ti_size = 0;
if (sense)
DPRINTF("Command failed\n");
s->sense = sense;
} else {
DPRINTF("transfer %d/%d\n", s->dma_left, s->ti_size);
}
if (s->dma_left) {
esp_do_dma(s);
} else {
if (s->ti_size) {
s->rregs[4] |= STAT_IN | STAT_TC;
} else {
s->rregs[4] = STAT_IN | STAT_TC | STAT_ST;
}
s->rregs[5] = INTR_BS;
s->rregs[6] = 0;
s->rregs[7] = 0;
s->espdmaregs[0] |= DMA_INTR;
pic_set_irq(s->irq, 1);
}
}
static void handle_ti(ESPState *s)
{
uint32_t dmaptr, dmalen, minlen, len, from, to;
unsigned int i;
int to_device;
uint8_t buf[TARGET_PAGE_SIZE];
uint32_t dmalen, minlen;
dmalen = s->wregs[0] | (s->wregs[1] << 8);
if (dmalen==0) {
......@@ -227,47 +293,9 @@ static void handle_ti(ESPState *s)
minlen = (dmalen < s->ti_size) ? dmalen : s->ti_size;
DPRINTF("Transfer Information len %d\n", minlen);
if (s->dma) {
dmaptr = iommu_translate(s->espdmaregs[1]);
/* Check if the transfer writes to to reads from the device. */
to_device = (s->espdmaregs[0] & DMA_WRITE_MEM) == 0;
DPRINTF("DMA Direction: %c, addr 0x%8.8x %08x\n",
to_device ? 'r': 'w', dmaptr, s->ti_size);
from = s->espdmaregs[1];
to = from + minlen;
for (i = 0; i < minlen; i += len, from += len) {
dmaptr = iommu_translate(s->espdmaregs[1] + i);
if ((from & TARGET_PAGE_MASK) != (to & TARGET_PAGE_MASK)) {
len = TARGET_PAGE_SIZE - (from & ~TARGET_PAGE_MASK);
} else {
len = to - from;
}
DPRINTF("DMA address p %08x v %08x len %08x, from %08x, to %08x\n", dmaptr, s->espdmaregs[1] + i, len, from, to);
s->ti_size -= len;
if (s->do_cmd) {
DPRINTF("command len %d + %d\n", s->cmdlen, len);
cpu_physical_memory_read(dmaptr, &s->cmdbuf[s->cmdlen], len);
s->ti_size = 0;
s->cmdlen = 0;
s->do_cmd = 0;
do_cmd(s, s->cmdbuf);
return;
} else {
if (to_device) {
cpu_physical_memory_read(dmaptr, buf, len);
scsi_write_data(s->current_dev, buf, len);
} else {
scsi_read_data(s->current_dev, buf, len);
cpu_physical_memory_write(dmaptr, buf, len);
}
}
}
if (s->ti_size) {
s->rregs[4] = STAT_IN | STAT_TC | (to_device ? STAT_DO : STAT_DI);
}
s->rregs[5] = INTR_BS;
s->rregs[6] = 0;
s->rregs[7] = 0;
s->espdmaregs[0] |= DMA_INTR;
s->dma_left = minlen;
s->rregs[4] &= ~STAT_TC;
esp_do_dma(s);
} else if (s->do_cmd) {
DPRINTF("command len %d\n", s->cmdlen);
s->ti_size = 0;
......@@ -276,7 +304,6 @@ static void handle_ti(ESPState *s)
do_cmd(s, s->cmdbuf);
return;
}
pic_set_irq(s->irq, 1);
}
static void esp_reset(void *opaque)
......@@ -320,8 +347,8 @@ static uint32_t esp_mem_readb(void *opaque, target_phys_addr_t addr)
break;
case 5:
// interrupt
// Clear status bits except TC
s->rregs[4] &= STAT_TC;
// Clear interrupt/error status bits
s->rregs[4] &= ~(STAT_IN | STAT_GE | STAT_PE);
pic_set_irq(s->irq, 0);
s->espdmaregs[0] &= ~DMA_INTR;
break;
......@@ -342,6 +369,7 @@ static void esp_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
case 0:
case 1:
s->rregs[saddr] = val;
s->rregs[4] &= ~STAT_TC;
break;
case 2:
// FIFO
......
......@@ -152,6 +152,9 @@ do { fprintf(stderr, "lsi_scsi: " fmt , ##args); } while (0)
/* The HBA is ID 7, so for simplicitly limit to 7 devices. */
#define LSI_MAX_DEVS 7
/* Size of internal DMA buffer for async IO requests. */
#define LSI_DMA_BLOCK_SIZE 0x10000
typedef struct {
PCIDevice pci_dev;
int mmio_io_addr;
......@@ -162,7 +165,9 @@ typedef struct {
int carry; /* ??? Should this be an a visible register somewhere? */
int sense;
uint8_t msg;
/* Nonzero if a Wait Reselect instruction has been issued. */
/* 0 if SCRIPTS are running or stopped.
* 1 if a Wait Reselect instruction has been issued.
* 2 if a DMA operation is in progress. */
int waiting;
SCSIDevice *scsi_dev[LSI_MAX_DEVS];
SCSIDevice *current_dev;
......@@ -226,6 +231,7 @@ typedef struct {
uint32_t csbc;
uint32_t scratch[13]; /* SCRATCHA-SCRATCHR */
uint8_t dma_buf[LSI_DMA_BLOCK_SIZE];
/* Script ram is stored as 32-bit words in host byteorder. */
uint32_t script_ram[2048];
} LSIState;
......@@ -295,6 +301,7 @@ static void lsi_soft_reset(LSIState *s)
static uint8_t lsi_reg_readb(LSIState *s, int offset);
static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val);
static void lsi_execute_script(LSIState *s);
static inline uint32_t read_dword(LSIState *s, uint32_t addr)
{
......@@ -402,21 +409,20 @@ static void lsi_bad_phase(LSIState *s, int out, int new_phase)
lsi_set_phase(s, new_phase);
}
/* Initiate a SCSI layer data transfer. */
static void lsi_do_dma(LSIState *s, int out)
{
uint8_t buf[TARGET_PAGE_SIZE];
uint32_t addr;
uint32_t count;
int n;
count = s->dbc;
addr = s->dnad;
DPRINTF("DMA %s addr=0x%08x len=%d avail=%d\n", out ? "out" : "in",
if (count > LSI_DMA_BLOCK_SIZE)
count = LSI_DMA_BLOCK_SIZE;
DPRINTF("DMA addr=0x%08x len=%d avail=%d\n",
addr, count, s->data_len);
/* ??? Too long transfers are truncated. Don't know if this is the
correct behavior. */
if (count > s->data_len) {
/* If the DMA length is greater then the device data length then
/* If the DMA length is greater than the device data length then
a phase mismatch will occur. */
count = s->data_len;
s->dbc = count;
......@@ -426,20 +432,47 @@ static void lsi_do_dma(LSIState *s, int out)
s->csbc += count;
/* ??? Set SFBR to first data byte. */
while (count) {
n = (count > TARGET_PAGE_SIZE) ? TARGET_PAGE_SIZE : count;
if (out) {
cpu_physical_memory_read(addr, buf, n);
scsi_write_data(s->current_dev, buf, n);
} else {
scsi_read_data(s->current_dev, buf, n);
cpu_physical_memory_write(addr, buf, n);
}
addr += n;
count -= n;
if ((s->sstat1 & PHASE_MASK) == PHASE_DO) {
cpu_physical_memory_read(s->dnad, s->dma_buf, count);
scsi_write_data(s->current_dev, s->dma_buf, count);
} else {
scsi_read_data(s->current_dev, s->dma_buf, count);
}
/* If the DMA did not complete then suspend execution. */
if (s->dbc)
s->waiting = 2;
}
/* Callback to indicate that the SCSI layer has completed a transfer. */
static void lsi_command_complete(void *opaque, uint32_t reason, int sense)
{
LSIState *s = (LSIState *)opaque;
uint32_t count;
int out;
out = ((s->sstat1 & PHASE_MASK) == PHASE_DO);
count = s->dbc;
if (count > LSI_DMA_BLOCK_SIZE)
count = LSI_DMA_BLOCK_SIZE;
if (!out)
cpu_physical_memory_write(s->dnad, s->dma_buf, count);
s->dnad += count;
s->dbc -= count;
if (reason == SCSI_REASON_DONE) {
DPRINTF("Command complete sense=%d\n", sense);
s->sense = sense;
lsi_set_phase(s, PHASE_ST);
}
if (s->dbc) {
lsi_do_dma(s, out);
} else if (s->waiting == 2) {
/* Restart SCRIPTS execution. */
s->waiting = 0;
lsi_execute_script(s);
}
}
static void lsi_do_command(LSIState *s)
{
......@@ -461,15 +494,6 @@ static void lsi_do_command(LSIState *s)
}
}
static void lsi_command_complete(void *opaque, uint32_t tag, int sense)
{
LSIState *s = (LSIState *)opaque;
DPRINTF("Command complete sense=%d\n", sense);
s->sense = sense;
lsi_set_phase(s, PHASE_ST);
}
static void lsi_do_status(LSIState *s)
{
DPRINTF("Get status len=%d sense=%d\n", s->dbc, s->sense);
......@@ -1134,7 +1158,7 @@ static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val)
s->istat0 &= ~LSI_ISTAT0_INTF;
lsi_update_irq(s);
}
if (s->waiting && val & LSI_ISTAT0_SIGP) {
if (s->waiting == 1 && val & LSI_ISTAT0_SIGP) {
DPRINTF("Woken by SIGP\n");
s->waiting = 0;
s->dsp = s->dnad;
......
......@@ -25,6 +25,7 @@ do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0)
#define SENSE_NO_SENSE 0
#define SENSE_NOT_READY 2
#define SENSE_HARDWARE_ERROR 4
#define SENSE_ILLEGAL_REQUEST 5
struct SCSIDevice
......@@ -46,7 +47,13 @@ struct SCSIDevice
int buf_pos;
int buf_len;
int sense;
BlockDriverAIOCB *aiocb;
/* Data still to be transfered after this request completes. */
uint8_t *aiodata;
uint32_t aiolen;
char buf[512];
/* Completion functions may be called from either scsi_{read,write}_data
or from the AIO completion routines. */
scsi_completionfn completion;
void *opaque;
};
......@@ -54,10 +61,49 @@ struct SCSIDevice
static void scsi_command_complete(SCSIDevice *s, int sense)
{
s->sense = sense;
s->completion(s->opaque, s->tag, sense);
s->completion(s->opaque, SCSI_REASON_DONE, sense);
}
/* Read data from a scsi device. Returns nonzero on failure. */
static void scsi_transfer_complete(SCSIDevice *s)
{
s->completion(s->opaque, SCSI_REASON_DATA, 0);
s->aiocb = NULL;
}
static void scsi_read_complete(void * opaque, int ret)
{
SCSIDevice *s = (SCSIDevice *)opaque;
if (ret) {
DPRINTF("IO error\n");
scsi_command_complete(s, SENSE_HARDWARE_ERROR);
}
if (s->aiolen) {
/* Read the remaining data. Full and partial sectors are transferred
separately. */
scsi_read_data(s, s->aiodata, s->aiolen);
} else {
if (s->buf_len == 0 && s->sector_count == 0)
scsi_command_complete(s, SENSE_NO_SENSE);
else
scsi_transfer_complete(s);
}
}
/* Cancel a pending data transfer. */
void scsi_cancel_io(SCSIDevice *s)
{
if (!s->aiocb) {
BADF("Cancel with no pending IO\n");
return;
}
bdrv_aio_cancel(s->aiocb);
s->aiocb = NULL;
}
/* Read data from a scsi device. Returns nonzero on failure.
The transfer may complete asynchronously. */
int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
{
uint32_t n;
......@@ -84,14 +130,19 @@ int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
n = s->sector_count;
if (n != 0) {
bdrv_read(s->bdrv, s->sector, data, n);
data += n * 512;
len -= n * 512;
s->aiolen = len - n * 512;
s->aiodata = data + n * 512;
s->aiocb = bdrv_aio_read(s->bdrv, s->sector, data, n,
scsi_read_complete, s);
if (s->aiocb == NULL)
scsi_command_complete(s, SENSE_HARDWARE_ERROR);
s->sector += n;
s->sector_count -= n;
return 0;
}
if (len && s->sector_count) {
/* TODO: Make this use AIO. */
bdrv_read(s->bdrv, s->sector, s->buf, 1);
s->sector++;
s->sector_count--;
......@@ -106,11 +157,53 @@ int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
if (s->buf_len == 0 && s->sector_count == 0)
scsi_command_complete(s, SENSE_NO_SENSE);
else
scsi_transfer_complete(s);
return 0;
}
/* Read data to a scsi device. Returns nonzero on failure. */
static void scsi_write_complete(void * opaque, int ret)
{
SCSIDevice *s = (SCSIDevice *)opaque;
if (ret) {
fprintf(stderr, "scsi-disc: IO write error\n");
exit(1);
}
if (s->sector_count == 0)
scsi_command_complete(s, SENSE_NO_SENSE);
else
scsi_transfer_complete(s);
}
static uint32_t scsi_write_partial_sector(SCSIDevice *s, uint8_t *data,
uint32_t len)
{
int n;
n = 512 - s->buf_len;
if (n > len)
n = len;
memcpy(s->buf + s->buf_len, data, n);
data += n;
s->buf_len += n;
len -= n;
if (s->buf_len == 512) {
/* A full sector has been accumulated. Write it to disk. */
/* TODO: Make this use async IO. */
bdrv_write(s->bdrv, s->sector, s->buf, 1);
s->buf_len = 0;
s->sector++;
s->sector_count--;
}
return n;
}
/* Write data to a scsi device. Returns nonzero on failure.
The transfer may complete asynchronously. */
int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
{
uint32_t n;
......@@ -125,48 +218,39 @@ int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
return 1;
if (s->buf_len != 0 || len < 512) {
n = 512 - s->buf_len;
if (n > len)
n = len;
memcpy(s->buf + s->buf_len, data, n);
data += n;
s->buf_len += n;
n = scsi_write_partial_sector(s, data, len);
len -= n;
if (s->buf_len == 512) {
/* A full sector has been accumulated. Write it to disk. */
bdrv_write(s->bdrv, s->sector, s->buf, 1);
s->buf_len = 0;
s->sector++;
s->sector_count--;
}
data += n;
}
n = len / 512;
if (n > s->sector_count)
n = s->sector_count;
return 1;
if (n != 0) {
bdrv_write(s->bdrv, s->sector, data, n);
s->aiocb = bdrv_aio_write(s->bdrv, s->sector, data, n,
scsi_write_complete, s);
if (s->aiocb == NULL)
scsi_command_complete(s, SENSE_HARDWARE_ERROR);
data += n * 512;
len -= n * 512;
s->sector += n;
s->sector_count -= n;
}
if (len >= 512)
return 1;
if (len && s->sector_count) {
/* Recurse to complete the partial write. */
return scsi_write_data(s, data, len);
if (len) {
if (s->sector_count == 0)
return 1;
/* Complete a partial write. */
scsi_write_partial_sector(s, data, len);
}
if (n == 0) {
/* Transfer completes immediately. */
if (s->sector_count == 0)
scsi_command_complete(s, SENSE_NO_SENSE);
else
scsi_transfer_complete(s);
}
if (len != 0)
return 1;
if (s->sector_count == 0)
scsi_command_complete(s, SENSE_NO_SENSE);
return 0;
}
......
......@@ -474,19 +474,18 @@ static int usb_mouse_handle_control(USBDevice *dev, int request, int value,
return ret;
}
static int usb_mouse_handle_data(USBDevice *dev, int pid,
uint8_t devep, uint8_t *data, int len)
static int usb_mouse_handle_data(USBDevice *dev, USBPacket *p)
{
USBMouseState *s = (USBMouseState *)dev;
int ret = 0;
switch(pid) {
switch(p->pid) {
case USB_TOKEN_IN:
if (devep == 1) {
if (p->devep == 1) {
if (s->kind == USB_MOUSE)
ret = usb_mouse_poll(s, data, len);
ret = usb_mouse_poll(s, p->data, p->len);
else if (s->kind == USB_TABLET)
ret = usb_tablet_poll(s, data, len);
ret = usb_tablet_poll(s, p->data, p->len);
} else {
goto fail;
}
......
......@@ -180,8 +180,7 @@ static void usb_hub_attach(USBPort *port1, USBDevice *dev)
port->wPortStatus &= ~PORT_STAT_LOW_SPEED;
port->port.dev = dev;
/* send the attach message */
dev->handle_packet(dev,
USB_MSG_ATTACH, 0, 0, NULL, 0);
usb_send_msg(dev, USB_MSG_ATTACH);
} else {
dev = port->port.dev;
if (dev) {
......@@ -192,8 +191,7 @@ static void usb_hub_attach(USBPort *port1, USBDevice *dev)
port->wPortChange |= PORT_STAT_C_ENABLE;
}
/* send the detach message */
dev->handle_packet(dev,
USB_MSG_DETACH, 0, 0, NULL, 0);
usb_send_msg(dev, USB_MSG_DETACH);
port->port.dev = NULL;
}
}
......@@ -349,8 +347,7 @@ static int usb_hub_handle_control(USBDevice *dev, int request, int value,
break;
case PORT_RESET:
if (dev) {
dev->handle_packet(dev,
USB_MSG_RESET, 0, 0, NULL, 0);
usb_send_msg(dev, USB_MSG_RESET);
port->wPortChange |= PORT_STAT_C_RESET;
/* set enable bit */
port->wPortStatus |= PORT_STAT_ENABLE;
......@@ -434,22 +431,21 @@ static int usb_hub_handle_control(USBDevice *dev, int request, int value,
return ret;
}
static int usb_hub_handle_data(USBDevice *dev, int pid,
uint8_t devep, uint8_t *data, int len)
static int usb_hub_handle_data(USBDevice *dev, USBPacket *p)
{
USBHubState *s = (USBHubState *)dev;
int ret;
switch(pid) {