bus: mvebu-mbus: make sure SDRAM CS for DMA don't overlap the MBus bridge window
authorThomas Petazzoni <thomas.petazzoni@free-electrons.com>
Fri, 9 Jan 2015 16:59:04 +0000 (10:59 -0600)
committerAndrew Lunn <andrew@lunn.ch>
Mon, 19 Jan 2015 22:09:16 +0000 (16:09 -0600)
The mvebu-mbus driver reads the SDRAM window registers, and make the
information about the DRAM CS configuration available to device
drivers using the mv_mbus_dram_info() API. This information is used by
the DMA-capable device drivers to program their address decoding
windows.

Until now, we were basically providing the SDRAM window register
details as is. However, it turns out that the DMA capability of the
CESA cryptographic engine consists in doing DMA being the DRAM and the
crypto SRAM mapped as a MBus window. For this case, it is very
important that the SDRAM CS information does not overlap with the MBus
bridge window.

Therefore, this commit improves the mvebu-mbus driver to make sure we
adjust the SDRAM CS information so that it doesn't overlap with the
MBus bridge window. This problem was reported by Boris Brezillon,
while working on the mv_cesa driver for Armada 37x/38x/XP. We use the
memblock memory information to know where the usable RAM is located,
as this information is guaranteed to be correct on all SoC variants.

We could have used the MBus bridge window registers on Armada 370/XP,
but they are not really used on Armada 375/38x (Cortex-A9 based),
since the PL310 L2 filtering is used instead to discriminate between
RAM accesses and I/O accesses. Therefore, using the memblock
information is more generic and works accross the different platforms.

Reported-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
[Andrew Lunn <andrew@lunn.ch>: Fixed merge conflict]
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
drivers/bus/mvebu-mbus.c

index a62c8ae253c331d652b03a43b5e1d78ffe144668..fb9ec6221730a2d594f66d15e54471aea75cc750 100644 (file)
@@ -58,6 +58,7 @@
 #include <linux/debugfs.h>
 #include <linux/log2.h>
 #include <linux/syscore_ops.h>
+#include <linux/memblock.h>
 
 /*
  * DDR target is the same on all platforms.
 
 /* Relative to mbusbridge_base */
 #define MBUS_BRIDGE_CTRL_OFF   0x0
+#define  MBUS_BRIDGE_SIZE_MASK  0xffff0000
 #define MBUS_BRIDGE_BASE_OFF   0x4
+#define  MBUS_BRIDGE_BASE_MASK  0xffff0000
 
 /* Maximum number of windows, for all known platforms */
 #define MBUS_WINS_MAX           20
@@ -574,36 +577,106 @@ static unsigned int armada_xp_mbus_win_remap_offset(int win)
                return MVEBU_MBUS_NO_REMAP;
 }
 
+/*
+ * Use the memblock information to find the MBus bridge hole in the
+ * physical address space.
+ */
+static void __init
+mvebu_mbus_find_bridge_hole(uint64_t *start, uint64_t *end)
+{
+       struct memblock_region *r;
+       uint64_t s = 0;
+
+       for_each_memblock(memory, r) {
+               /*
+                * This part of the memory is above 4 GB, so we don't
+                * care for the MBus bridge hole.
+                */
+               if (r->base >= 0x100000000)
+                       continue;
+
+               /*
+                * The MBus bridge hole is at the end of the RAM under
+                * the 4 GB limit.
+                */
+               if (r->base + r->size > s)
+                       s = r->base + r->size;
+       }
+
+       *start = s;
+       *end = 0x100000000;
+}
+
 static void __init
 mvebu_mbus_default_setup_cpu_target(struct mvebu_mbus_state *mbus)
 {
        int i;
        int cs;
+       uint64_t mbus_bridge_base, mbus_bridge_end;
 
        mvebu_mbus_dram_info.mbus_dram_target_id = TARGET_DDR;
 
+       mvebu_mbus_find_bridge_hole(&mbus_bridge_base, &mbus_bridge_end);
+
        for (i = 0, cs = 0; i < 4; i++) {
-               u32 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
-               u32 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
+               u64 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
+               u64 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
+               u64 end;
+               struct mbus_dram_window *w;
+
+               /* Ignore entries that are not enabled */
+               if (!(size & DDR_SIZE_ENABLED))
+                       continue;
 
                /*
-                * We only take care of entries for which the chip
-                * select is enabled, and that don't have high base
-                * address bits set (devices can only access the first
-                * 32 bits of the memory).
+                * Ignore entries whose base address is above 2^32,
+                * since devices cannot DMA to such high addresses
                 */
-               if ((size & DDR_SIZE_ENABLED) &&
-                   !(base & DDR_BASE_CS_HIGH_MASK)) {
-                       struct mbus_dram_window *w;
+               if (base & DDR_BASE_CS_HIGH_MASK)
+                       continue;
 
-                       w = &mvebu_mbus_dram_info.cs[cs++];
-                       w->cs_index = i;
-                       w->mbus_attr = 0xf & ~(1 << i);
-                       if (mbus->hw_io_coherency)
-                               w->mbus_attr |= ATTR_HW_COHERENCY;
-                       w->base = base & DDR_BASE_CS_LOW_MASK;
-                       w->size = (size | ~DDR_SIZE_MASK) + 1;
+               base = base & DDR_BASE_CS_LOW_MASK;
+               size = (size | ~DDR_SIZE_MASK) + 1;
+               end = base + size;
+
+               /*
+                * Adjust base/size of the current CS to make sure it
+                * doesn't overlap with the MBus bridge hole. This is
+                * particularly important for devices that do DMA from
+                * DRAM to a SRAM mapped in a MBus window, such as the
+                * CESA cryptographic engine.
+                */
+
+               /*
+                * The CS is fully enclosed inside the MBus bridge
+                * area, so ignore it.
+                */
+               if (base >= mbus_bridge_base && end <= mbus_bridge_end)
+                       continue;
+
+               /*
+                * Beginning of CS overlaps with end of MBus, raise CS
+                * base address, and shrink its size.
+                */
+               if (base >= mbus_bridge_base && end > mbus_bridge_end) {
+                       size -= mbus_bridge_end - base;
+                       base = mbus_bridge_end;
                }
+
+               /*
+                * End of CS overlaps with beginning of MBus, shrink
+                * CS size.
+                */
+               if (base < mbus_bridge_base && end > mbus_bridge_base)
+                       size -= end - mbus_bridge_base;
+
+               w = &mvebu_mbus_dram_info.cs[cs++];
+               w->cs_index = i;
+               w->mbus_attr = 0xf & ~(1 << i);
+               if (mbus->hw_io_coherency)
+                       w->mbus_attr |= ATTR_HW_COHERENCY;
+               w->base = base;
+               w->size = size;
        }
        mvebu_mbus_dram_info.num_cs = cs;
 }