qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH] Add 40-bit DMA support to LSI scsi emulation


From: Ryan Harper
Subject: [Qemu-devel] [PATCH] Add 40-bit DMA support to LSI scsi emulation
Date: Wed, 12 Nov 2008 11:19:28 -0600
User-agent: Mutt/1.5.6+20040907i

This patch fixes Linux machines configured with > 4G of ram and using a
SCSI device.  I'm working on 64-bit DMA support as well which should
hopefully fix 64-bit Windows using SCSI devices as well.

-- 
Ryan Harper
Software Engineer; Linux Technology Center
IBM Corp., Austin, Tx
(512) 838-9253   T/L: 678-9253
address@hidden


diffstat output:
 lsi53c895a.c |   36 ++++++++++++++++++++++++++++++++----
 1 files changed, 32 insertions(+), 4 deletions(-)

Signed-off-by: Ryan Harper <address@hidden>
---

Background:
The LSI device we emulate in QEMU is capable of 32, 40, and 64-bit DMA for block
moves.  The Linux device driver for the LSI device by default compiles support
for 40-bit DMA and configures the LSI devices in register ccntl1 to use 40-bit
DMA.  For systems with more than 4GB of ram, the device driver in Linux may
issue a DMA with an address > than 32-bit.  The current LSI code ignores the
upper 8-bits of the destination and then DMAs into some random location.

This can be observed by booting a Linux guest with a scsi device attached and
configured with more than 4GB of ram.  Linux stalls while probing the scsi bus
and dumps messages like this in dmesg:

[    5.640225] scsi scan: INQUIRY result too short (5), using 36

This happens since the DMA destination for the result of the INQUIRY scsi
command does not contain the result, rather it was DMAed to some other location
because the emulation dropped the upper 8-bits of the target address and doesn't
mask off the lower 23-bits for the proper byte count to transfer.  The Linux
device driver assumes that the result is bogus and marshals on.

The Fix:

This patch introduces the flags for the ccntl1 register which are used to
determine how the guest device driver is configuring the LSI device w.r.t to DMA
address size.  lsi_do_dma now uses target_phys_addr_t instead of a uint32_t to
hold the DMA address, and in the block move code, we decode and store the upper
 8-bits of the 40-bit DMA in the dnad64 register which is combined with dnad to
 form the 40-bit DMA.

Details:

When using 40-bit DMA mode, the OS driver will issue a block move instruction
and use Table Indirect Adressing mode (TIA - bit 28).  Table indirect address
implies that the actual DMA address and byte count are stored somewhere else in
memory rather than in the instruction encoding.  For all table indirect moves
(32, 40, and 64), the lower 24-bits of the 2nd dword of the original
instruction are used as an offset combined with the DSA register to point to the
table.  

Depending on the DMA mode, the table format differers, though all tables are 2
dwords long.  We detect 40-bit Block Move by inspecting ccntl1 register and see
if both LSI_CCNTL1_EN64TIBMV and LSI_CCNTL1_64TIMOD bits are on.  If so,  then
table layout of a 40 bit DMA move has the byte count (amount of data to move) in
the lower 23-bits of the first dword of the indirect table.  The upper 8-bits of
the first dword of the indirect table are to be the top 8-bits of the 40-bit DMA
address and are stored in the dnad64 register.  The lower 32-bits are pulled
from the second dword of the indirect table.

In lsi_do_dma, we combine the dnad64 register with dnad to form the 40-bit DMA
address.  The specification indicates that if smbs register is non-zero, then
the upper 32-bits of the DMA will be pulled from there.


1. 
http://www.lsi.com/DistributionSystem/AssetDocument/files/docs/techdocs/storage_stand_prod/SCSIControllers/lsi53c895a_tech_manual.pdf

Signed-off-by: Ryan Harper <address@hidden>


diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c
index 3f67837..d7608e4 100644
--- a/hw/lsi53c895a.c
+++ b/hw/lsi53c895a.c
@@ -143,6 +143,14 @@ do { fprintf(stderr, "lsi_scsi: error: " fmt , ##args);} 
while (0)
 #define LSI_CCNTL0_PMJCTL 0x40
 #define LSI_CCNTL0_ENPMJ  0x80
 
+#define LSI_CCNTL1_EN64DBMV  0x01
+#define LSI_CCNTL1_EN64TIBMV 0x02
+#define LSI_CCNTL1_64TIMOD   0x04
+#define LSI_CCNTL1_DDAC      0x08
+#define LSI_CCNTL1_ZMOD      0x80
+
+#define LSI_CCNTL1_40BIT (LSI_CCNTL1_EN64TIBMV|LSI_CCNTL1_64TIMOD)
+
 #define PHASE_DO          0
 #define PHASE_DI          1
 #define PHASE_CMD         2
@@ -323,6 +331,13 @@ static void lsi_soft_reset(LSIState *s)
     s->csbc = 0;
 }
 
+static int lsi_dma_40bit(LSIState *s)
+{
+    if ((s->ccntl1 & LSI_CCNTL1_40BIT) == LSI_CCNTL1_40BIT)
+        return 1;
+    return 0;
+}
+
 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);
@@ -449,7 +464,7 @@ static void lsi_resume_script(LSIState *s)
 static void lsi_do_dma(LSIState *s, int out)
 {
     uint32_t count;
-    uint32_t addr;
+    target_phys_addr_t addr;
 
     if (!s->current_dma_len) {
         /* Wait until data is available.  */
@@ -460,9 +475,14 @@ static void lsi_do_dma(LSIState *s, int out)
     count = s->dbc;
     if (count > s->current_dma_len)
         count = s->current_dma_len;
-    DPRINTF("DMA addr=0x%08x len=%d\n", s->dnad, count);
 
     addr = s->dnad;
+    if (lsi_dma_40bit(s))
+        addr |= ((uint64_t)s->dnad64 << 32UL);
+    else if (s->sbms)
+        addr |= ((uint64_t)s->sbms << 32UL);
+
+    DPRINTF("DMA addr=0x%" PRIx64 " len=%d\n", addr, count);
     s->csbc += count;
     s->dnad += count;
     s->dbc -= count;
@@ -839,7 +859,7 @@ static void lsi_wait_reselect(LSIState *s)
 static void lsi_execute_script(LSIState *s)
 {
     uint32_t insn;
-    uint32_t addr;
+    uint32_t addr, addr64;
     int opcode;
     int insn_processed = 0;
 
@@ -848,6 +868,7 @@ again:
     insn_processed++;
     insn = read_dword(s, s->dsp);
     addr = read_dword(s, s->dsp + 4);
+    addr64 = 0;
     DPRINTF("SCRIPTS dsp=%08x opcode %08x arg %08x\n", s->dsp, insn, addr);
     s->dsps = addr;
     s->dcmd = insn >> 24;
@@ -870,9 +891,15 @@ again:
             /* Table indirect addressing.  */
             offset = sxt24(addr);
             cpu_physical_memory_read(s->dsa + offset, (uint8_t *)buf, 8);
-            s->dbc = cpu_to_le32(buf[0]);
+            /* byte count is stored in bits 0:23 only */
+            s->dbc = cpu_to_le32(buf[0]) & 0xffffff;
             s->rbc = s->dbc;
             addr = cpu_to_le32(buf[1]);
+
+            /* 40-bit DMA, upper addr bits [39:32] stored in first DWORD of
+             * table, bits [31:24] */
+            if (lsi_dma_40bit(s))
+                addr64 = cpu_to_le32(buf[0]) >> 24;
         }
         if ((s->sstat1 & PHASE_MASK) != ((insn >> 24) & 7)) {
             DPRINTF("Wrong phase got %d expected %d\n",
@@ -881,6 +908,7 @@ again:
             break;
         }
         s->dnad = addr;
+        s->dnad64 = addr64;
         /* ??? Set ESA.  */
         s->ia = s->dsp - 8;
         switch (s->sstat1 & 0x7) {




reply via email to

[Prev in Thread] Current Thread [Next in Thread]