This patch modifies scsi_send_command() to use asynchronous I/O with SCSI Generic.
This allows to use command with data transfers that are not immediate. Old behavior can generate softlockup in the guest kernel. hw/lsi53c895a.c has been modified to manage this new behavior. --- hw/lsi53c895a.c | 25 ++++++++++++++++++------- hw/scsi-generic.c | 3 +-- 2 files changed, 19 insertions(+), 9 deletions(-) Index: qemu/hw/lsi53c895a.c =================================================================== --- qemu.orig/hw/lsi53c895a.c 2007-11-28 13:12:22.000000000 +0100 +++ qemu/hw/lsi53c895a.c 2007-11-28 13:13:42.000000000 +0100 @@ -187,6 +187,7 @@ typedef struct { /* The tag is a combination of the device ID and the SCSI tag. */ uint32_t current_tag; uint32_t current_dma_len; + int command_complete; uint8_t *dma_buf; lsi_queue *queue; int queue_len; @@ -597,6 +598,7 @@ static void lsi_command_complete(void *o if (reason == SCSI_REASON_DONE) { DPRINTF("Command complete sense=%d\n", (int)arg); s->sense = arg; + s->command_complete = 2; if (s->waiting && s->dbc != 0) { /* Raise phase mismatch for short transfers. */ lsi_bad_phase(s, out, PHASE_ST); @@ -613,6 +615,7 @@ static void lsi_command_complete(void *o } DPRINTF("Data ready tag=0x%x len=%d\n", tag, arg); s->current_dma_len = arg; + s->command_complete = 1; if (!s->waiting) return; if (s->waiting == 1 || s->dbc == 0) { @@ -632,6 +635,7 @@ static void lsi_do_command(LSIState *s) s->dbc = 16; cpu_physical_memory_read(s->dnad, buf, s->dbc); s->sfbr = buf[0]; + s->command_complete = 0; n = s->current_dev->send_command(s->current_dev, s->current_tag, buf, s->current_lun); if (n > 0) { @@ -641,13 +645,20 @@ static void lsi_do_command(LSIState *s) lsi_set_phase(s, PHASE_DO); s->current_dev->write_data(s->current_dev, s->current_tag); } - if (n && s->current_dma_len == 0) { - /* Command did not complete immediately so disconnect. */ - lsi_add_msg_byte(s, 2); /* SAVE DATA POINTER */ - lsi_add_msg_byte(s, 4); /* DISCONNECT */ - lsi_set_phase(s, PHASE_MI); - s->msg_action = 1; - lsi_queue_command(s); + + if (!s->command_complete) { + if (n) { + /* Command did not complete immediately so disconnect. */ + lsi_add_msg_byte(s, 2); /* SAVE DATA POINTER */ + lsi_add_msg_byte(s, 4); /* DISCONNECT */ + /* wait data */ + lsi_set_phase(s, PHASE_MI); + s->msg_action = 1; + lsi_queue_command(s); + } else { + /* wait command complete */ + lsi_set_phase(s, PHASE_DI); + } } } Index: qemu/hw/scsi-generic.c =================================================================== --- qemu.orig/hw/scsi-generic.c 2007-11-28 13:13:13.000000000 +0100 +++ qemu/hw/scsi-generic.c 2007-11-28 13:13:42.000000000 +0100 @@ -496,12 +496,11 @@ static int32_t scsi_send_command(SCSIDev free(r->buf); r->buflen = 0; r->buf = NULL; - ret = execute_command(s->bdrv, r, SG_DXFER_NONE, NULL); + ret = execute_command(s->bdrv, r, SG_DXFER_NONE, scsi_command_complete); if (ret == -1) { scsi_command_complete(r, -EINVAL); return 0; } - scsi_command_complete(r, scsi_get_sense(r)); return 0; }