* elf64-ppc.c (dec_dynrel_count): Don't error when elf_gc_sweep_symbol
[external/binutils.git] / gdb / dcache.c
index b1e53e6..6e2c7a2 100644 (file)
@@ -1,13 +1,12 @@
 /* Caching code for GDB, the GNU debugger.
 
-   Copyright 1992, 1993, 1995, 1996, 1998, 1999, 2000, 2001, 2003 Free
-   Software Foundation, Inc.
+   Copyright (C) 1992-2013 Free Software Foundation, Inc.
 
    This file is part of GDB.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
@@ -16,9 +15,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place - Suite 330,
-   Boston, MA 02111-1307, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "defs.h"
 #include "dcache.h"
 #include "gdb_string.h"
 #include "gdbcore.h"
 #include "target.h"
+#include "inferior.h"
+#include "splay-tree.h"
+
+/* Commands with a prefix of `{set,show} dcache'.  */
+static struct cmd_list_element *dcache_set_list = NULL;
+static struct cmd_list_element *dcache_show_list = NULL;
 
 /* The data cache could lead to incorrect results because it doesn't
    know about volatile variables, thus making it impossible to debug
    functions which use memory mapped I/O devices.  Set the nocache
    memory region attribute in those cases.
 
-   In general the dcache speeds up performance, some speed improvement
+   In general the dcache speeds up performance.  Some speed improvement
    comes from the actual caching mechanism, but the major gain is in
    the reduction of the remote protocol overhead; instead of reading
    or writing a large area of memory in 4 byte requests, the cache
-   bundles up the requests into 32 byte (actually LINE_SIZE) chunks.
-   Reducing the overhead to an eighth of what it was.  This is very
-   obvious when displaying a large amount of data,
-
-   eg, x/200x 0 
-
-   caching     |   no    yes 
-   ---------------------------- 
-   first time  |   4 sec  2 sec improvement due to chunking 
-   second time |   4 sec  0 sec improvement due to caching
-
-   The cache structure is unusual, we keep a number of cache blocks
-   (DCACHE_SIZE) and each one caches a LINE_SIZEed area of memory.
-   Within each line we remember the address of the line (always a
-   multiple of the LINE_SIZE) and a vector of bytes over the range.
-   There's another vector which contains the state of the bytes.
-
-   ENTRY_BAD means that the byte is just plain wrong, and has no
-   correspondence with anything else (as it would when the cache is
-   turned on, but nothing has been done to it.
-
-   ENTRY_DIRTY means that the byte has some data in it which should be
-   written out to the remote target one day, but contains correct
-   data.
-
-   ENTRY_OK means that the data is the same in the cache as it is in
-   remote memory.
-
-
-   The ENTRY_DIRTY state is necessary because GDB likes to write large
-   lumps of memory in small bits.  If the caching mechanism didn't
-   maintain the DIRTY information, then something like a two byte
-   write would mean that the entire cache line would have to be read,
-   the two bytes modified and then written out again.  The alternative
-   would be to not read in the cache line in the first place, and just
-   write the two bytes directly into target memory.  The trouble with
-   that is that it really nails performance, because of the remote
-   protocol overhead.  This way, all those little writes are bundled
-   up into an entire cache line write in one go, without having to
-   read the cache line in the first place.
- */
+   bundles up the requests into LINE_SIZE chunks, reducing overhead
+   significantly.  This is most useful when accessing a large amount
+   of data, such as when performing a backtrace.
+
+   The cache is a splay tree along with a linked list for replacement.
+   Each block caches a LINE_SIZE area of memory.  Within each line we
+   remember the address of the line (which must be a multiple of
+   LINE_SIZE) and the actual data block.
+
+   Lines are only allocated as needed, so DCACHE_SIZE really specifies the
+   *maximum* number of lines in the cache.
+
+   At present, the cache is write-through rather than writeback: as soon
+   as data is written to the cache, it is also immediately written to
+   the target.  Therefore, cache lines are never "dirty".  Whether a given
+   line is valid or not depends on where it is stored in the dcache_struct;
+   there is no per-block valid flag.  */
 
 /* NOTE: Interaction of dcache and memory region attributes
 
    the last bit of the .text segment and the first bit of the .data
    segment fall within the same dcache page with a ro/cacheable memory
    region defined for the .text segment and a rw/non-cacheable memory
-   region defined for the .data segment. */
+   region defined for the .data segment.  */
 
-/* This value regulates the number of cache blocks stored.
-   Smaller values reduce the time spent searching for a cache
-   line, and reduce memory requirements, but increase the risk
-   of a line not being in memory */
+/* The maximum number of lines stored.  The total size of the cache is
+   equal to DCACHE_SIZE times LINE_SIZE.  */
+#define DCACHE_DEFAULT_SIZE 4096
+static unsigned dcache_size = DCACHE_DEFAULT_SIZE;
 
-#define DCACHE_SIZE 64
-
-/* This value regulates the size of a cache line.  Smaller values
-   reduce the time taken to read a single byte, but reduce overall
-   throughput.  */
-
-#define LINE_SIZE_POWER (5)
-#define LINE_SIZE (1 << LINE_SIZE_POWER)
+/* The default size of a cache line.  Smaller values reduce the time taken to
+   read a single byte and make the cache more granular, but increase
+   overhead and reduce the effectiveness of the cache as a prefetcher.  */
+#define DCACHE_DEFAULT_LINE_SIZE 64
+static unsigned dcache_line_size = DCACHE_DEFAULT_LINE_SIZE;
 
 /* Each cache block holds LINE_SIZE bytes of data
    starting at a multiple-of-LINE_SIZE address.  */
 
-#define LINE_SIZE_MASK  ((LINE_SIZE - 1))
-#define XFORM(x)       ((x) & LINE_SIZE_MASK)
-#define MASK(x)         ((x) & ~LINE_SIZE_MASK)
-
-
-#define ENTRY_BAD   0          /* data at this byte is wrong */
-#define ENTRY_DIRTY 1          /* data at this byte needs to be written back */
-#define ENTRY_OK    2          /* data at this byte is same as in memory */
-
+#define LINE_SIZE_MASK(dcache)  ((dcache->line_size - 1))
+#define XFORM(dcache, x)       ((x) & LINE_SIZE_MASK (dcache))
+#define MASK(dcache, x)         ((x) & ~LINE_SIZE_MASK (dcache))
 
 struct dcache_block
-  {
-    struct dcache_block *p;    /* next in list */
-    CORE_ADDR addr;            /* Address for which data is recorded.  */
-    char data[LINE_SIZE];      /* bytes at given address */
-    unsigned char state[LINE_SIZE];    /* what state the data is in */
-
-    /* whether anything in state is dirty - used to speed up the 
-       dirty scan. */
-    int anydirty;
-
-    int refs;
-  };
-
-
-/* FIXME: dcache_struct used to have a cache_has_stuff field that was
-   used to record whether the cache had been accessed.  This was used
-   to invalidate the cache whenever caching was (re-)enabled (if the
-   cache was disabled and later re-enabled, it could contain stale
-   data).  This was not needed because the cache is write through and
-   the code that enables, disables, and deletes memory region all
-   invalidate the cache.
+{
+  /* For least-recently-allocated and free lists.  */
+  struct dcache_block *prev;
+  struct dcache_block *next;
 
-   This is overkill, since it also invalidates cache lines from
-   unrelated regions.  One way this could be addressed by adding a
-   new function that takes an address and a length and invalidates
-   only those cache lines that match. */
+  CORE_ADDR addr;              /* address of data */
+  int refs;                    /* # hits */
+  gdb_byte data[1];            /* line_size bytes at given address */
+};
 
 struct dcache_struct
-  {
-    /* free list */
-    struct dcache_block *free_head;
-    struct dcache_block *free_tail;
+{
+  splay_tree tree;
+  struct dcache_block *oldest; /* least-recently-allocated list.  */
 
-    /* in use list */
-    struct dcache_block *valid_head;
-    struct dcache_block *valid_tail;
+  /* The free list is maintained identically to OLDEST to simplify
+     the code: we only need one set of accessors.  */
+  struct dcache_block *freelist;
 
-    /* The cache itself. */
-    struct dcache_block *the_cache;
-  };
+  /* The number of in-use lines in the cache.  */
+  int size;
+  CORE_ADDR line_size;  /* current line_size.  */
 
-static int dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr);
+  /* The ptid of last inferior to use cache or null_ptid.  */
+  ptid_t ptid;
+};
 
-static int dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr);
+typedef void (block_func) (struct dcache_block *block, void *param);
 
 static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr);
 
-static int dcache_write_line (DCACHE *dcache, struct dcache_block *db);
-
 static int dcache_read_line (DCACHE *dcache, struct dcache_block *db);
 
 static struct dcache_block *dcache_alloc (DCACHE *dcache, CORE_ADDR addr);
 
-static int dcache_writeback (DCACHE *dcache);
-
 static void dcache_info (char *exp, int tty);
 
 void _initialize_dcache (void);
 
-static int dcache_enabled_p = 0;
+static int dcache_enabled_p = 0; /* OBSOLETE */
 
-DCACHE *last_cache;            /* Used by info dcache */
+static void
+show_dcache_enabled_p (struct ui_file *file, int from_tty,
+                      struct cmd_list_element *c, const char *value)
+{
+  fprintf_filtered (file, _("Deprecated remotecache flag is %s.\n"), value);
+}
 
+static DCACHE *last_cache; /* Used by info dcache.  */
 
-/* Free all the data cache blocks, thus discarding all cached data.  */
+/* Add BLOCK to circular block list BLIST, behind the block at *BLIST.
+   *BLIST is not updated (unless it was previously NULL of course).
+   This is for the least-recently-allocated list's sake:
+   BLIST points to the oldest block.
+   ??? This makes for poor cache usage of the free list,
+   but is it measurable?  */
 
-void
-dcache_invalidate (DCACHE *dcache)
+static void
+append_block (struct dcache_block **blist, struct dcache_block *block)
 {
-  int i;
-  dcache->valid_head = 0;
-  dcache->valid_tail = 0;
+  if (*blist)
+    {
+      block->next = *blist;
+      block->prev = (*blist)->prev;
+      block->prev->next = block;
+      (*blist)->prev = block;
+      /* We don't update *BLIST here to maintain the invariant that for the
+        least-recently-allocated list *BLIST points to the oldest block.  */
+    }
+  else
+    {
+      block->next = block;
+      block->prev = block;
+      *blist = block;
+    }
+}
 
-  dcache->free_head = 0;
-  dcache->free_tail = 0;
+/* Remove BLOCK from circular block list BLIST.  */
 
-  for (i = 0; i < DCACHE_SIZE; i++)
+static void
+remove_block (struct dcache_block **blist, struct dcache_block *block)
+{
+  if (block->next == block)
     {
-      struct dcache_block *db = dcache->the_cache + i;
-
-      if (!dcache->free_head)
-       dcache->free_head = db;
-      else
-       dcache->free_tail->p = db;
-      dcache->free_tail = db;
-      db->p = 0;
+      *blist = NULL;
+    }
+  else
+    {
+      block->next->prev = block->prev;
+      block->prev->next = block->next;
+      /* If we removed the block *BLIST points to, shift it to the next block
+        to maintain the invariant that for the least-recently-allocated list
+        *BLIST points to the oldest block.  */
+      if (*blist == block)
+       *blist = block->next;
     }
-
-  return;
 }
 
-/* If addr is present in the dcache, return the address of the block
-   containing it. */
+/* Iterate over all elements in BLIST, calling FUNC.
+   PARAM is passed to FUNC.
+   FUNC may remove the block it's passed, but only that block.  */
 
-static struct dcache_block *
-dcache_hit (DCACHE *dcache, CORE_ADDR addr)
+static void
+for_each_block (struct dcache_block **blist, block_func *func, void *param)
 {
   struct dcache_block *db;
 
-  /* Search all cache blocks for one that is at this address.  */
-  db = dcache->valid_head;
+  if (*blist == NULL)
+    return;
 
-  while (db)
+  db = *blist;
+  do
     {
-      if (MASK (addr) == db->addr)
-       {
-         db->refs++;
-         return db;
-       }
-      db = db->p;
+      struct dcache_block *next = db->next;
+
+      func (db, param);
+      db = next;
     }
+  while (*blist && db != *blist);
+}
+
+/* BLOCK_FUNC routine for dcache_free.  */
 
-  return NULL;
+static void
+free_block (struct dcache_block *block, void *param)
+{
+  xfree (block);
 }
 
-/* Make sure that anything in this line which needs to
-   be written is. */
+/* Free a data cache.  */
 
-static int
-dcache_write_line (DCACHE *dcache, struct dcache_block *db)
+void
+dcache_free (DCACHE *dcache)
 {
-  CORE_ADDR memaddr;
-  char *myaddr;
-  int len;
-  int res;
-  int reg_len;
-  struct mem_region *region;
+  if (last_cache == dcache)
+    last_cache = NULL;
 
-  if (!db->anydirty)
-    return 1;
+  splay_tree_delete (dcache->tree);
+  for_each_block (&dcache->oldest, free_block, NULL);
+  for_each_block (&dcache->freelist, free_block, NULL);
+  xfree (dcache);
+}
 
-  len = LINE_SIZE;
-  memaddr = db->addr;
-  myaddr  = db->data;
 
-  while (len > 0)
+/* BLOCK_FUNC function for dcache_invalidate.
+   This doesn't remove the block from the oldest list on purpose.
+   dcache_invalidate will do it later.  */
+
+static void
+invalidate_block (struct dcache_block *block, void *param)
+{
+  DCACHE *dcache = (DCACHE *) param;
+
+  splay_tree_remove (dcache->tree, (splay_tree_key) block->addr);
+  append_block (&dcache->freelist, block);
+}
+
+/* Free all the data cache blocks, thus discarding all cached data.  */
+
+void
+dcache_invalidate (DCACHE *dcache)
+{
+  for_each_block (&dcache->oldest, invalidate_block, dcache);
+
+  dcache->oldest = NULL;
+  dcache->size = 0;
+  dcache->ptid = null_ptid;
+
+  if (dcache->line_size != dcache_line_size)
     {
-      int s;
-      int e;
-      int dirty_len;
-      
-      region = lookup_mem_region(memaddr);
-      if (memaddr + len < region->hi)
-       reg_len = len;
-      else
-       reg_len = region->hi - memaddr;
+      /* We've been asked to use a different line size.
+        All of our freelist blocks are now the wrong size, so free them.  */
 
-      if (!region->attrib.cache || region->attrib.mode == MEM_RO)
-       {
-         memaddr += reg_len;
-         myaddr  += reg_len;
-         len     -= reg_len;
-         continue;
-       }
+      for_each_block (&dcache->freelist, free_block, dcache);
+      dcache->freelist = NULL;
+      dcache->line_size = dcache_line_size;
+    }
+}
 
-      while (reg_len > 0)
-       {
-         s = XFORM(memaddr);
-         while (reg_len > 0) {
-           if (db->state[s] == ENTRY_DIRTY)
-             break;
-           s++;
-           reg_len--;
-
-           memaddr++;
-           myaddr++;
-           len--;
-         }
-
-         e = s;
-         while (reg_len > 0) {
-           if (db->state[e] != ENTRY_DIRTY)
-             break;
-           e++;
-           reg_len--;
-         }
-
-         dirty_len = e - s;
-         while (dirty_len > 0)
-           {
-             res = do_xfer_memory(memaddr, myaddr, dirty_len, 1,
-                                  &region->attrib);
-             if (res <= 0)
-               return 0;
-
-             memset (&db->state[XFORM(memaddr)], ENTRY_OK, res);
-             memaddr   += res;
-             myaddr    += res;
-             len       -= res;
-             dirty_len -= res;
-           }
-       }
+/* Invalidate the line associated with ADDR.  */
+
+static void
+dcache_invalidate_line (DCACHE *dcache, CORE_ADDR addr)
+{
+  struct dcache_block *db = dcache_hit (dcache, addr);
+
+  if (db)
+    {
+      splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
+      remove_block (&dcache->oldest, db);
+      append_block (&dcache->freelist, db);
+      --dcache->size;
     }
+}
 
-  db->anydirty = 0;
-  return 1;
+/* If addr is present in the dcache, return the address of the block
+   containing it.  Otherwise return NULL.  */
+
+static struct dcache_block *
+dcache_hit (DCACHE *dcache, CORE_ADDR addr)
+{
+  struct dcache_block *db;
+
+  splay_tree_node node = splay_tree_lookup (dcache->tree,
+                                           (splay_tree_key) MASK (dcache, addr));
+
+  if (!node)
+    return NULL;
+
+  db = (struct dcache_block *) node->value;
+  db->refs++;
+  return db;
 }
 
-/* Read cache line */
+/* Fill a cache line from target memory.
+   The result is 1 for success, 0 if the (entire) cache line
+   wasn't readable.  */
+
 static int
 dcache_read_line (DCACHE *dcache, struct dcache_block *db)
 {
   CORE_ADDR memaddr;
-  char *myaddr;
+  gdb_byte *myaddr;
   int len;
   int res;
   int reg_len;
   struct mem_region *region;
 
-  /* If there are any dirty bytes in the line, it must be written
-     before a new line can be read */
-  if (db->anydirty)
-    {
-      if (!dcache_write_line (dcache, db))
-       return 0;
-    }
-  
-  len = LINE_SIZE;
+  len = dcache->line_size;
   memaddr = db->addr;
   myaddr  = db->data;
 
   while (len > 0)
     {
-      region = lookup_mem_region(memaddr);
-      if (memaddr + len < region->hi)
+      /* Don't overrun if this block is right at the end of the region.  */
+      region = lookup_mem_region (memaddr);
+      if (region->hi == 0 || memaddr + len < region->hi)
        reg_len = len;
       else
        reg_len = region->hi - memaddr;
 
-      if (!region->attrib.cache || region->attrib.mode == MEM_WO)
+      /* Skip non-readable regions.  The cache attribute can be ignored,
+         since we may be loading this for a stack access.  */
+      if (region->attrib.mode == MEM_WO)
        {
          memaddr += reg_len;
          myaddr  += reg_len;
@@ -358,23 +342,16 @@ dcache_read_line (DCACHE *dcache, struct dcache_block *db)
          continue;
        }
       
-      while (reg_len > 0)
-       {
-         res = do_xfer_memory (memaddr, myaddr, reg_len, 0,
-                               &region->attrib);
-         if (res <= 0)
-           return 0;
-
-         memaddr += res;
-         myaddr  += res;
-         len     -= res;
-         reg_len -= res;
-       }
+      res = target_read (&current_target, TARGET_OBJECT_RAW_MEMORY,
+                        NULL, myaddr, memaddr, reg_len);
+      if (res < reg_len)
+       return 0;
+
+      memaddr += res;
+      myaddr += res;
+      len -= res;
     }
 
-  memset (db->state, ENTRY_OK, sizeof (db->data));
-  db->anydirty = 0;
-  
   return 1;
 }
 
@@ -386,200 +363,339 @@ dcache_alloc (DCACHE *dcache, CORE_ADDR addr)
 {
   struct dcache_block *db;
 
-  /* Take something from the free list */
-  db = dcache->free_head;
-  if (db)
+  if (dcache->size >= dcache_size)
     {
-      dcache->free_head = db->p;
+      /* Evict the least recently allocated line.  */
+      db = dcache->oldest;
+      remove_block (&dcache->oldest, db);
+
+      splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
     }
   else
     {
-      /* Nothing left on free list, so grab one from the valid list */
-      db = dcache->valid_head;
+      db = dcache->freelist;
+      if (db)
+       remove_block (&dcache->freelist, db);
+      else
+       db = xmalloc (offsetof (struct dcache_block, data) +
+                     dcache->line_size);
 
-      if (!dcache_write_line (dcache, db))
-       return NULL;
-      
-      dcache->valid_head = db->p;
+      dcache->size++;
     }
 
-  db->addr = MASK(addr);
+  db->addr = MASK (dcache, addr);
   db->refs = 0;
-  db->anydirty = 0;
-  memset (db->state, ENTRY_BAD, sizeof (db->data));
-
-  /* append this line to end of valid list */
-  if (!dcache->valid_head)
-    dcache->valid_head = db;
-  else
-    dcache->valid_tail->p = db;
-  dcache->valid_tail = db;
-  db->p = 0;
-
-  return db;
-}
 
-/* Writeback any dirty lines. */
-static int
-dcache_writeback (DCACHE *dcache)
-{
-  struct dcache_block *db;
+  /* Put DB at the end of the list, it's the newest.  */
+  append_block (&dcache->oldest, db);
 
-  db = dcache->valid_head;
+  splay_tree_insert (dcache->tree, (splay_tree_key) db->addr,
+                    (splay_tree_value) db);
 
-  while (db)
-    {
-      if (!dcache_write_line (dcache, db))
-       return 0;
-      db = db->p;
-    }
-  return 1;
+  return db;
 }
 
-
-/* Using the data cache DCACHE return the contents of the byte at
+/* Using the data cache DCACHE, store in *PTR the contents of the byte at
    address ADDR in the remote machine.  
 
-   Returns 0 on error. */
+   Returns 1 for success, 0 for error.  */
 
 static int
-dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr)
+dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr)
 {
   struct dcache_block *db = dcache_hit (dcache, addr);
 
   if (!db)
     {
       db = dcache_alloc (dcache, addr);
-      if (!db)
-       return 0;
-    }
-  
-  if (db->state[XFORM (addr)] == ENTRY_BAD)
-    {
-      if (!dcache_read_line(dcache, db))
+
+      if (!dcache_read_line (dcache, db))
          return 0;
     }
 
-  *ptr = db->data[XFORM (addr)];
+  *ptr = db->data[XFORM (dcache, addr)];
   return 1;
 }
 
-
 /* Write the byte at PTR into ADDR in the data cache.
-   Return zero on write error.
- */
+
+   The caller is responsible for also promptly writing the data
+   through to target memory.
+
+   If addr is not in cache, this function does nothing; writing to
+   an area of memory which wasn't present in the cache doesn't cause
+   it to be loaded in.
+
+   Always return 1 (meaning success) to simplify dcache_xfer_memory.  */
 
 static int
-dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr)
+dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr)
 {
   struct dcache_block *db = dcache_hit (dcache, addr);
 
-  if (!db)
-    {
-      db = dcache_alloc (dcache, addr);
-      if (!db)
-       return 0;
-    }
+  if (db)
+    db->data[XFORM (dcache, addr)] = *ptr;
 
-  db->data[XFORM (addr)] = *ptr;
-  db->state[XFORM (addr)] = ENTRY_DIRTY;
-  db->anydirty = 1;
   return 1;
 }
 
-/* Initialize the data cache.  */
+static int
+dcache_splay_tree_compare (splay_tree_key a, splay_tree_key b)
+{
+  if (a > b)
+    return 1;
+  else if (a == b)
+    return 0;
+  else
+    return -1;
+}
+
+/* Allocate and initialize a data cache.  */
+
 DCACHE *
 dcache_init (void)
 {
-  int csize = sizeof (struct dcache_block) * DCACHE_SIZE;
   DCACHE *dcache;
 
   dcache = (DCACHE *) xmalloc (sizeof (*dcache));
 
-  dcache->the_cache = (struct dcache_block *) xmalloc (csize);
-  memset (dcache->the_cache, 0, csize);
-
-  dcache_invalidate (dcache);
+  dcache->tree = splay_tree_new (dcache_splay_tree_compare,
+                                NULL,
+                                NULL);
 
+  dcache->oldest = NULL;
+  dcache->freelist = NULL;
+  dcache->size = 0;
+  dcache->line_size = dcache_line_size;
+  dcache->ptid = null_ptid;
   last_cache = dcache;
+
   return dcache;
 }
 
-/* Free a data cache */
-void
-dcache_free (DCACHE *dcache)
-{
-  if (last_cache == dcache)
-    last_cache = NULL;
-
-  xfree (dcache->the_cache);
-  xfree (dcache);
-}
 
 /* Read or write LEN bytes from inferior memory at MEMADDR, transferring
    to or from debugger address MYADDR.  Write to inferior if SHOULD_WRITE is
    nonzero. 
 
-   Returns length of data written or read; 0 for error.  
-
-   This routine is indended to be called by remote_xfer_ functions. */
+   Return the number of bytes actually transfered, or -1 if the
+   transfer is not supported or otherwise fails.  Return of a non-negative
+   value less than LEN indicates that no further transfer is possible.
+   NOTE: This is different than the to_xfer_partial interface, in which
+   positive values less than LEN mean further transfers may be possible.  */
 
 int
-dcache_xfer_memory (DCACHE *dcache, CORE_ADDR memaddr, char *myaddr, int len,
-                   int should_write)
+dcache_xfer_memory (struct target_ops *ops, DCACHE *dcache,
+                   CORE_ADDR memaddr, gdb_byte *myaddr,
+                   int len, int should_write)
 {
   int i;
-  int (*xfunc) (DCACHE *dcache, CORE_ADDR addr, char *ptr);
+  int res;
+  int (*xfunc) (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr);
+
   xfunc = should_write ? dcache_poke_byte : dcache_peek_byte;
 
-  for (i = 0; i < len; i++)
+  /* If this is a different inferior from what we've recorded,
+     flush the cache.  */
+
+  if (! ptid_equal (inferior_ptid, dcache->ptid))
     {
-      if (!xfunc (dcache, memaddr + i, myaddr + i))
-       return 0;
+      dcache_invalidate (dcache);
+      dcache->ptid = inferior_ptid;
     }
 
-  /* FIXME: There may be some benefit from moving the cache writeback
-     to a higher layer, as it could occur after a sequence of smaller
-     writes have been completed (as when a stack frame is constructed
-     for an inferior function call).  Note that only moving it up one
-     level to target_xfer_memory() (also target_xfer_memory_partial())
-     is not sufficent, since we want to coalesce memory transfers that
-     are "logically" connected but not actually a single call to one
-     of the memory transfer functions. */
+  /* Do write-through first, so that if it fails, we don't write to
+     the cache at all.  */
 
   if (should_write)
-    dcache_writeback (dcache);
+    {
+      res = target_write (ops, TARGET_OBJECT_RAW_MEMORY,
+                         NULL, myaddr, memaddr, len);
+      if (res <= 0)
+       return res;
+      /* Update LEN to what was actually written.  */
+      len = res;
+    }
+      
+  for (i = 0; i < len; i++)
+    {
+      if (!xfunc (dcache, memaddr + i, myaddr + i))
+       {
+         /* That failed.  Discard its cache line so we don't have a
+            partially read line.  */
+         dcache_invalidate_line (dcache, memaddr + i);
+         /* If we're writing, we still wrote LEN bytes.  */
+         if (should_write)
+           return len;
+         else
+           return i;
+       }
+    }
     
   return len;
 }
 
+/* FIXME: There would be some benefit to making the cache write-back and
+   moving the writeback operation to a higher layer, as it could occur
+   after a sequence of smaller writes have been completed (as when a stack
+   frame is constructed for an inferior function call).  Note that only
+   moving it up one level to target_xfer_memory[_partial]() is not
+   sufficient since we want to coalesce memory transfers that are
+   "logically" connected but not actually a single call to one of the
+   memory transfer functions.  */
+
+/* Just update any cache lines which are already present.  This is called
+   by memory_xfer_partial in cases where the access would otherwise not go
+   through the cache.  */
+
+void
+dcache_update (DCACHE *dcache, CORE_ADDR memaddr, gdb_byte *myaddr, int len)
+{
+  int i;
+
+  for (i = 0; i < len; i++)
+    dcache_poke_byte (dcache, memaddr + i, myaddr + i);
+}
+
 static void
-dcache_info (char *exp, int tty)
+dcache_print_line (int index)
 {
-  struct dcache_block *p;
+  splay_tree_node n;
+  struct dcache_block *db;
+  int i, j;
 
-  printf_filtered (_("Dcache line width %d, depth %d\n"),
-                  LINE_SIZE, DCACHE_SIZE);
+  if (!last_cache)
+    {
+      printf_filtered (_("No data cache available.\n"));
+      return;
+    }
 
-  if (last_cache)
+  n = splay_tree_min (last_cache->tree);
+
+  for (i = index; i > 0; --i)
     {
-      printf_filtered (_("Cache state:\n"));
+      if (!n)
+       break;
+      n = splay_tree_successor (last_cache->tree, n->key);
+    }
 
-      for (p = last_cache->valid_head; p; p = p->p)
-       {
-         int j;
-         printf_filtered (_("Line at %s, referenced %d times\n"),
-                          paddr (p->addr), p->refs);
+  if (!n)
+    {
+      printf_filtered (_("No such cache line exists.\n"));
+      return;
+    }
+    
+  db = (struct dcache_block *) n->value;
+
+  printf_filtered (_("Line %d: address %s [%d hits]\n"),
+                  index, paddress (target_gdbarch (), db->addr), db->refs);
+
+  for (j = 0; j < last_cache->line_size; j++)
+    {
+      printf_filtered ("%02x ", db->data[j]);
+
+      /* Print a newline every 16 bytes (48 characters).  */
+      if ((j % 16 == 15) && (j != last_cache->line_size - 1))
+       printf_filtered ("\n");
+    }
+  printf_filtered ("\n");
+}
+
+static void
+dcache_info (char *exp, int tty)
+{
+  splay_tree_node n;
+  int i, refcount;
 
-         for (j = 0; j < LINE_SIZE; j++)
-           printf_filtered ("%02x", p->data[j] & 0xFF);
-         printf_filtered (("\n"));
+  if (exp)
+    {
+      char *linestart;
 
-         for (j = 0; j < LINE_SIZE; j++)
-           printf_filtered ("%2x", p->state[j]);
-         printf_filtered ("\n");
+      i = strtol (exp, &linestart, 10);
+      if (linestart == exp || i < 0)
+       {
+         printf_filtered (_("Usage: info dcache [linenumber]\n"));
+          return;
        }
+
+      dcache_print_line (i);
+      return;
+    }
+
+  printf_filtered (_("Dcache %u lines of %u bytes each.\n"),
+                  dcache_size,
+                  last_cache ? (unsigned) last_cache->line_size
+                  : dcache_line_size);
+
+  if (!last_cache || ptid_equal (last_cache->ptid, null_ptid))
+    {
+      printf_filtered (_("No data cache available.\n"));
+      return;
     }
+
+  printf_filtered (_("Contains data for %s\n"),
+                  target_pid_to_str (last_cache->ptid));
+
+  refcount = 0;
+
+  n = splay_tree_min (last_cache->tree);
+  i = 0;
+
+  while (n)
+    {
+      struct dcache_block *db = (struct dcache_block *) n->value;
+
+      printf_filtered (_("Line %d: address %s [%d hits]\n"),
+                      i, paddress (target_gdbarch (), db->addr), db->refs);
+      i++;
+      refcount += db->refs;
+
+      n = splay_tree_successor (last_cache->tree, n->key);
+    }
+
+  printf_filtered (_("Cache state: %d active lines, %d hits\n"), i, refcount);
+}
+
+static void
+set_dcache_size (char *args, int from_tty,
+                struct cmd_list_element *c)
+{
+  if (dcache_size == 0)
+    {
+      dcache_size = DCACHE_DEFAULT_SIZE;
+      error (_("Dcache size must be greater than 0."));
+    }
+  if (last_cache)
+    dcache_invalidate (last_cache);
+}
+
+static void
+set_dcache_line_size (char *args, int from_tty,
+                     struct cmd_list_element *c)
+{
+  if (dcache_line_size < 2
+      || (dcache_line_size & (dcache_line_size - 1)) != 0)
+    {
+      unsigned d = dcache_line_size;
+      dcache_line_size = DCACHE_DEFAULT_LINE_SIZE;
+      error (_("Invalid dcache line size: %u (must be power of 2)."), d);
+    }
+  if (last_cache)
+    dcache_invalidate (last_cache);
+}
+
+static void
+set_dcache_command (char *arg, int from_tty)
+{
+  printf_unfiltered (
+     "\"set dcache\" must be followed by the name of a subcommand.\n");
+  help_list (dcache_set_list, "set dcache ", -1, gdb_stdout);
+}
+
+static void
+show_dcache_command (char *args, int from_tty)
+{
+  cmd_show_list (dcache_show_list, from_tty, "");
 }
 
 void
@@ -589,16 +705,42 @@ _initialize_dcache (void)
                           &dcache_enabled_p, _("\
 Set cache use for remote targets."), _("\
 Show cache use for remote targets."), _("\
-When on, use data caching for remote targets.  For many remote targets\n\
-this option can offer better throughput for reading target memory.\n\
-Unfortunately, gdb does not currently know anything about volatile\n\
-registers and thus data caching will produce incorrect results with\n\
-volatile registers are in use.  By default, this option is off."),
+This used to enable the data cache for remote targets.  The cache\n\
+functionality is now controlled by the memory region system and the\n\
+\"stack-cache\" flag; \"remotecache\" now does nothing and\n\
+exists only for compatibility reasons."),
                           NULL,
-                          NULL, /* FIXME: i18n: */
+                          show_dcache_enabled_p,
                           &setlist, &showlist);
 
   add_info ("dcache", dcache_info,
-           _("Print information on the dcache performance."));
-
+           _("\
+Print information on the dcache performance.\n\
+With no arguments, this command prints the cache configuration and a\n\
+summary of each line in the cache.  Use \"info dcache <lineno> to dump\"\n\
+the contents of a given line."));
+
+  add_prefix_cmd ("dcache", class_obscure, set_dcache_command, _("\
+Use this command to set number of lines in dcache and line-size."),
+                 &dcache_set_list, "set dcache ", /*allow_unknown*/0, &setlist);
+  add_prefix_cmd ("dcache", class_obscure, show_dcache_command, _("\
+Show dcachesettings."),
+                 &dcache_show_list, "show dcache ", /*allow_unknown*/0, &showlist);
+
+  add_setshow_uinteger_cmd ("line-size", class_obscure,
+                           &dcache_line_size, _("\
+Set dcache line size in bytes (must be power of 2)."), _("\
+Show dcache line size."),
+                           NULL,
+                           set_dcache_line_size,
+                           NULL,
+                           &dcache_set_list, &dcache_show_list);
+  add_setshow_uinteger_cmd ("size", class_obscure,
+                           &dcache_size, _("\
+Set number of dcache lines."), _("\
+Show number of dcache lines."),
+                           NULL,
+                           set_dcache_size,
+                           NULL,
+                           &dcache_set_list, &dcache_show_list);
 }