* elf64-ppc.c (dec_dynrel_count): Don't error when elf_gc_sweep_symbol
[external/binutils.git] / gdb / dcache.c
index 864b068..6e2c7a2 100644 (file)
-/* Caching code.  Typically used by remote back ends for
-   caching remote memory.
+/* Caching code for GDB, the GNU debugger.
 
-   Copyright 1992, 1993 Free Software Foundation, Inc.
+   Copyright (C) 1992-2013 Free Software Foundation, Inc.
 
-This file is part of GDB.
+   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
-(at your option) any later version.
+   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 3 of the License, or
+   (at your option) any later version.
 
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "defs.h"
 #include "dcache.h"
+#include "gdbcmd.h"
+#include "gdb_string.h"
+#include "gdbcore.h"
+#include "target.h"
+#include "inferior.h"
+#include "splay-tree.h"
 
-extern int insque();
-extern int remque();
+/* 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 records all the data read from the remote machine
-   since the last time it stopped.
+/* 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.
 
-   Each cache block holds LINE_SIZE bytes of data
+   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 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
+
+   As there is no requirement that memory region attributes be aligned
+   to or be a multiple of the dcache page size, dcache_read_line() and
+   dcache_write_line() must break up the page by memory region.  If a
+   chunk does not have the cache attribute set, an invalid memory type
+   is set, etc., then the chunk is skipped.  Those chunks are handled
+   in target_xfer_memory() (or target_xfer_memory_partial()).
+
+   This doesn't occur very often.  The most common occurance is when
+   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.  */
+
+/* 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;
+
+/* 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))       /* eg 7*2+1= 111*/
-#define XFORM(x)  (((x) & LINE_SIZE_MASK) >> 2)
+#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
+{
+  /* For least-recently-allocated and free lists.  */
+  struct dcache_block *prev;
+  struct dcache_block *next;
+
+  CORE_ADDR addr;              /* address of data */
+  int refs;                    /* # hits */
+  gdb_byte data[1];            /* line_size bytes at given address */
+};
+
+struct dcache_struct
+{
+  splay_tree tree;
+  struct dcache_block *oldest; /* least-recently-allocated list.  */
+
+  /* The free list is maintained identically to OLDEST to simplify
+     the code: we only need one set of accessors.  */
+  struct dcache_block *freelist;
+
+  /* The number of in-use lines in the cache.  */
+  int size;
+  CORE_ADDR line_size;  /* current line_size.  */
+
+  /* The ptid of last inferior to use cache or null_ptid.  */
+  ptid_t ptid;
+};
+
+typedef void (block_func) (struct dcache_block *block, void *param);
+
+static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr);
+
+static int dcache_read_line (DCACHE *dcache, struct dcache_block *db);
+
+static struct dcache_block *dcache_alloc (DCACHE *dcache, CORE_ADDR addr);
+
+static void dcache_info (char *exp, int tty);
+
+void _initialize_dcache (void);
+
+static int dcache_enabled_p = 0; /* OBSOLETE */
+
+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.  */
+
+/* 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?  */
+
+static void
+append_block (struct dcache_block **blist, struct dcache_block *block)
+{
+  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;
+    }
+}
+
+/* Remove BLOCK from circular block list BLIST.  */
+
+static void
+remove_block (struct dcache_block **blist, struct dcache_block *block)
+{
+  if (block->next == block)
+    {
+      *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;
+    }
+}
+
+/* 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 void
+for_each_block (struct dcache_block **blist, block_func *func, void *param)
+{
+  struct dcache_block *db;
+
+  if (*blist == NULL)
+    return;
+
+  db = *blist;
+  do
+    {
+      struct dcache_block *next = db->next;
+
+      func (db, param);
+      db = next;
+    }
+  while (*blist && db != *blist);
+}
+
+/* BLOCK_FUNC routine for dcache_free.  */
+
+static void
+free_block (struct dcache_block *block, void *param)
+{
+  xfree (block);
+}
+
+/* Free a data cache.  */
+
+void
+dcache_free (DCACHE *dcache)
+{
+  if (last_cache == dcache)
+    last_cache = NULL;
+
+  splay_tree_delete (dcache->tree);
+  for_each_block (&dcache->oldest, free_block, NULL);
+  for_each_block (&dcache->freelist, free_block, NULL);
+  xfree (dcache);
+}
+
+
+/* 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_flush (dcache)
-     DCACHE *dcache;
+dcache_invalidate (DCACHE *dcache)
 {
-  register struct dcache_block *db;
+  for_each_block (&dcache->oldest, invalidate_block, dcache);
+
+  dcache->oldest = NULL;
+  dcache->size = 0;
+  dcache->ptid = null_ptid;
 
-  while ((db = dcache->dcache_valid.next) != &dcache->dcache_valid)
+  if (dcache->line_size != dcache_line_size)
     {
-      remque (db);
-      insque (db, &dcache->dcache_free);
+      /* We've been asked to use a different line size.
+        All of our freelist blocks are now the wrong size, so free them.  */
+
+      for_each_block (&dcache->freelist, free_block, dcache);
+      dcache->freelist = NULL;
+      dcache->line_size = dcache_line_size;
     }
 }
 
-/*
- * If addr is present in the dcache, return the address of the block
- * containing it.
- */
-struct dcache_block *
-dcache_hit (dcache, addr)
-     DCACHE *dcache;
-     unsigned int addr;
-{
-  register struct dcache_block *db;
+/* Invalidate the line associated with ADDR.  */
 
-  if (addr & 3)
-    abort ();
+static void
+dcache_invalidate_line (DCACHE *dcache, CORE_ADDR addr)
+{
+  struct dcache_block *db = dcache_hit (dcache, addr);
 
-  /* Search all cache blocks for one that is at this address.  */
-  db = dcache->dcache_valid.next;
-  while (db != &dcache->dcache_valid)
+  if (db)
     {
-      if ((addr & ~LINE_SIZE_MASK) == db->addr)
-       return db;
-      db = db->next;
+      splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
+      remove_block (&dcache->oldest, db);
+      append_block (&dcache->freelist, db);
+      --dcache->size;
     }
-  return NULL;
 }
 
-/*  Return the int data at address ADDR in dcache block DC.  */
-int
-dcache_value (db, addr)
-     struct dcache_block *db;
-     unsigned int addr;
+/* 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;
+}
+
+/* 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)
 {
-  if (addr & 3)
-    abort ();
-  return (db->data[XFORM (addr)]);
+  CORE_ADDR memaddr;
+  gdb_byte *myaddr;
+  int len;
+  int res;
+  int reg_len;
+  struct mem_region *region;
+
+  len = dcache->line_size;
+  memaddr = db->addr;
+  myaddr  = db->data;
+
+  while (len > 0)
+    {
+      /* 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;
+
+      /* 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;
+         len     -= reg_len;
+         continue;
+       }
+      
+      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;
+    }
+
+  return 1;
 }
 
 /* Get a free cache block, put or keep it on the valid list,
-   and return its address.  The caller should store into the block
-   the address and data that it describes, then remque it from the
-   free list and insert it into the valid list.  This procedure
-   prevents errors from creeping in if a memory retrieval is
-   interrupted (which used to put garbage blocks in the valid
-   list...).  */
-struct dcache_block *
-dcache_alloc (dcache)
-     DCACHE *dcache;
+   and return its address.  */
+
+static struct dcache_block *
+dcache_alloc (DCACHE *dcache, CORE_ADDR addr)
+{
+  struct dcache_block *db;
+
+  if (dcache->size >= dcache_size)
+    {
+      /* 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
+    {
+      db = dcache->freelist;
+      if (db)
+       remove_block (&dcache->freelist, db);
+      else
+       db = xmalloc (offsetof (struct dcache_block, data) +
+                     dcache->line_size);
+
+      dcache->size++;
+    }
+
+  db->addr = MASK (dcache, addr);
+  db->refs = 0;
+
+  /* Put DB at the end of the list, it's the newest.  */
+  append_block (&dcache->oldest, db);
+
+  splay_tree_insert (dcache->tree, (splay_tree_key) db->addr,
+                    (splay_tree_value) db);
+
+  return db;
+}
+
+/* Using the data cache DCACHE, store in *PTR the contents of the byte at
+   address ADDR in the remote machine.  
+
+   Returns 1 for success, 0 for error.  */
+
+static int
+dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr)
 {
-  register struct dcache_block *db;
+  struct dcache_block *db = dcache_hit (dcache, addr);
 
-  if ((db = dcache->dcache_free.next) == &dcache->dcache_free)
+  if (!db)
     {
-      /* If we can't get one from the free list, take last valid and put
-        it on the free list.  */
-      db = dcache->dcache_valid.last;
-      remque (db);
-      insque (db, &dcache->dcache_free);
+      db = dcache_alloc (dcache, addr);
+
+      if (!dcache_read_line (dcache, db))
+         return 0;
     }
 
-  remque (db);
-  insque (db, &dcache->dcache_valid);
-  return (db);
+  *ptr = db->data[XFORM (dcache, addr)];
+  return 1;
 }
 
-/* Return the contents of the word at address ADDR in the remote machine,
-   using the data cache.  */
+/* Write the byte at PTR into ADDR in the data cache.
+
+   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, gdb_byte *ptr)
+{
+  struct dcache_block *db = dcache_hit (dcache, addr);
+
+  if (db)
+    db->data[XFORM (dcache, addr)] = *ptr;
+
+  return 1;
+}
+
+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)
+{
+  DCACHE *dcache;
+
+  dcache = (DCACHE *) xmalloc (sizeof (*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;
+}
+
+
+/* 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. 
+
+   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_fetch (dcache, addr)
-     DCACHE *dcache;
-     CORE_ADDR addr;
+dcache_xfer_memory (struct target_ops *ops, DCACHE *dcache,
+                   CORE_ADDR memaddr, gdb_byte *myaddr,
+                   int len, int should_write)
 {
-  register struct dcache_block *db;
+  int i;
+  int res;
+  int (*xfunc) (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr);
+
+  xfunc = should_write ? dcache_poke_byte : dcache_peek_byte;
+
+  /* If this is a different inferior from what we've recorded,
+     flush the cache.  */
 
-  db = dcache_hit (dcache, addr);
-  if (db == 0)
+  if (! ptid_equal (inferior_ptid, dcache->ptid))
     {
-      db = dcache_alloc (dcache);
-      immediate_quit++;
-      (*dcache->read_memory) (addr & ~LINE_SIZE_MASK, (unsigned char *) db->data, LINE_SIZE);
-      immediate_quit--;
-      db->addr = addr & ~LINE_SIZE_MASK;
-      remque (db);             /* Off the free list */
-      insque (db, &dcache->dcache_valid);      /* On the valid list */
+      dcache_invalidate (dcache);
+      dcache->ptid = inferior_ptid;
     }
-  return (dcache_value (db, addr));
+
+  /* Do write-through first, so that if it fails, we don't write to
+     the cache at all.  */
+
+  if (should_write)
+    {
+      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;
 }
 
-/* Write the word at ADDR both in the data cache and in the remote machine.  */
+/* 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_poke (dcache, addr, data)
-     DCACHE *dcache;
-     CORE_ADDR addr;
-     int data;
+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_print_line (int index)
 {
-  register struct dcache_block *db;
+  splay_tree_node n;
+  struct dcache_block *db;
+  int i, j;
 
-  /* First make sure the word is IN the cache.  DB is its cache block.  */
-  db = dcache_hit (dcache, addr);
-  if (db == 0)
+  if (!last_cache)
     {
-      db = dcache_alloc (dcache);
-      immediate_quit++;
-      (*dcache->write_memory) (addr & ~LINE_SIZE_MASK, (unsigned char *) db->data, LINE_SIZE);
-      immediate_quit--;
-      db->addr = addr & ~LINE_SIZE_MASK;
-      remque (db);             /* Off the free list */
-      insque (db, &dcache->dcache_valid);      /* On the valid list */
+      printf_filtered (_("No data cache available.\n"));
+      return;
     }
 
-  /* Modify the word in the cache.  */
-  db->data[XFORM (addr)] = data;
+  n = splay_tree_min (last_cache->tree);
 
-  /* Send the changed word.  */
-  immediate_quit++;
-  (*dcache->write_memory) (addr, (unsigned char *) &data, 4);
-  immediate_quit--;
+  for (i = index; i > 0; --i)
+    {
+      if (!n)
+       break;
+      n = splay_tree_successor (last_cache->tree, n->key);
+    }
+
+  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");
 }
 
-/* Initialize the data cache.  */
-DCACHE *
-dcache_init (reading, writing)
-     memxferfunc reading;
-     memxferfunc writing;
+static void
+dcache_info (char *exp, int tty)
 {
-  register i;
-  register struct dcache_block *db;
-  DCACHE *dcache;
+  splay_tree_node n;
+  int i, refcount;
 
-  dcache = xmalloc(sizeof(*dcache));
-  dcache->read_memory = reading;
-  dcache->write_memory = writing;
-  dcache->the_cache = xmalloc(sizeof(*dcache->the_cache) * DCACHE_SIZE);
+  if (exp)
+    {
+      char *linestart;
+
+      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;
 
-  dcache->dcache_free.next = dcache->dcache_free.last = &dcache->dcache_free;
-  dcache->dcache_valid.next = dcache->dcache_valid.last = &dcache->dcache_valid;
-  for (db = dcache->the_cache, i = 0; i < DCACHE_SIZE; i++, db++)
-    insque (db, &dcache->dcache_free);
+      printf_filtered (_("Line %d: address %s [%d hits]\n"),
+                      i, paddress (target_gdbarch (), db->addr), db->refs);
+      i++;
+      refcount += db->refs;
 
-  return(dcache);
+      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
+_initialize_dcache (void)
+{
+  add_setshow_boolean_cmd ("remotecache", class_support,
+                          &dcache_enabled_p, _("\
+Set cache use for remote targets."), _("\
+Show cache use for remote targets."), _("\
+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,
+                          show_dcache_enabled_p,
+                          &setlist, &showlist);
+
+  add_info ("dcache", dcache_info,
+           _("\
+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);
+}