daily update
[external/binutils.git] / gdb / dcache.c
index 864b068..ae5a479 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, 1993, 1995, 1996, 1998, 1999, 2000, 2001, 2003, 2007,
+   2008, 2009 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"
 
-extern int insque();
-extern int remque();
+/* 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.
 
-/* The data cache records all the data read from the remote machine
-   since the last time it stopped.
+   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,
 
-   Each cache block holds LINE_SIZE bytes 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_INVALID 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_VALID 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.
+ */
+
+/* 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. */
+
+/* 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 */
+
+#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)
+
+/* 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  ((LINE_SIZE - 1))
+#define XFORM(x)       ((x) & LINE_SIZE_MASK)
+#define MASK(x)         ((x) & ~LINE_SIZE_MASK)
+
+
+#define ENTRY_INVALID 0 /* data at this byte is wrong */
+#define ENTRY_DIRTY   1 /* data at this byte needs to be written back */
+#define ENTRY_VALID   2 /* data at this byte is same as in memory */
+
+/* For cache state display by "info dcache".
+   The letters I,D,V map to
+   I = ENTRY_INVALID
+   D = ENTRY_DIRTY
+   V = ENTRY_VALID  */
+static const char state_chars[3] = { 'I', 'D', 'V' };
+
+struct dcache_block
+  {
+    struct dcache_block *p;    /* next in list */
+    CORE_ADDR addr;            /* Address for which data is recorded.  */
+    gdb_byte 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.
+
+   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. */
+
+struct dcache_struct
+  {
+    /* free list */
+    struct dcache_block *free_head;
+    struct dcache_block *free_tail;
+
+    /* in use list */
+    struct dcache_block *valid_head;
+    struct dcache_block *valid_tail;
+
+    /* The cache itself. */
+    struct dcache_block *the_cache;
+  };
+
+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 void
+show_dcache_enabled_p (struct ui_file *file, int from_tty,
+                      struct cmd_list_element *c, const char *value)
+{
+  fprintf_filtered (file, _("Cache use for remote targets is %s.\n"), value);
+}
+
+
+DCACHE *last_cache;            /* Used by info dcache */
+
 
 /* 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;
+  int i;
+  dcache->valid_head = 0;
+  dcache->valid_tail = 0;
+
+  dcache->free_head = 0;
+  dcache->free_tail = 0;
 
-  while ((db = dcache->dcache_valid.next) != &dcache->dcache_valid)
+  for (i = 0; i < DCACHE_SIZE; i++)
     {
-      remque (db);
-      insque (db, &dcache->dcache_free);
+      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;
     }
+
+  return;
 }
 
-/*
- * 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;
+/* If addr is present in the dcache, return the address of the block
+   containing it. */
 
-  if (addr & 3)
-    abort ();
+static struct dcache_block *
+dcache_hit (DCACHE *dcache, CORE_ADDR addr)
+{
+  struct dcache_block *db;
 
   /* Search all cache blocks for one that is at this address.  */
-  db = dcache->dcache_valid.next;
-  while (db != &dcache->dcache_valid)
+  db = dcache->valid_head;
+
+  while (db)
     {
-      if ((addr & ~LINE_SIZE_MASK) == db->addr)
-       return db;
-      db = db->next;
+      if (MASK (addr) == db->addr)
+       {
+         db->refs++;
+         return db;
+       }
+      db = db->p;
     }
+
   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;
+/* Make sure that anything in this line which needs to
+   be written is. */
+
+static int
+dcache_write_line (DCACHE *dcache, struct dcache_block *db)
+{
+  CORE_ADDR memaddr;
+  gdb_byte *myaddr;
+  int len;
+  int res;
+  int reg_len;
+  struct mem_region *region;
+
+  if (!db->anydirty)
+    return 1;
+
+  len = LINE_SIZE;
+  memaddr = db->addr;
+  myaddr  = db->data;
+
+  while (len > 0)
+    {
+      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;
+
+      if (!region->attrib.cache || region->attrib.mode == MEM_RO)
+       {
+         memaddr += reg_len;
+         myaddr  += reg_len;
+         len     -= reg_len;
+         continue;
+       }
+
+      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;
+         res = target_write (&current_target, TARGET_OBJECT_RAW_MEMORY,
+                             NULL, myaddr, memaddr, dirty_len);
+         if (res < dirty_len)
+           return 0;
+
+         memset (&db->state[XFORM(memaddr)], ENTRY_VALID, res);
+         memaddr += res;
+         myaddr += res;
+         len -= res;
+       }
+    }
+
+  db->anydirty = 0;
+  return 1;
+}
+
+/* Read cache line */
+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;
+
+  /* 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;
+  memaddr = db->addr;
+  myaddr  = db->data;
+
+  while (len > 0)
+    {
+      region = lookup_mem_region(memaddr);
+      if (memaddr + len < region->hi)
+       reg_len = len;
+      else
+       reg_len = region->hi - memaddr;
+
+      if (!region->attrib.cache || 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;
+    }
+
+  memset (db->state, ENTRY_VALID, sizeof (db->data));
+  db->anydirty = 0;
+  
+  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)
 {
-  register struct dcache_block *db;
+  struct dcache_block *db;
 
-  if ((db = dcache->dcache_free.next) == &dcache->dcache_free)
+  /* Take something from the free list */
+  db = dcache->free_head;
+  if (db)
+    {
+      dcache->free_head = db->p;
+    }
+  else
     {
-      /* 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);
+      /* Nothing left on free list, so grab one from the valid list */
+      db = dcache->valid_head;
+
+      if (!dcache_write_line (dcache, db))
+       return NULL;
+      
+      dcache->valid_head = db->p;
     }
 
-  remque (db);
-  insque (db, &dcache->dcache_valid);
-  return (db);
+  db->addr = MASK(addr);
+  db->refs = 0;
+  db->anydirty = 0;
+  memset (db->state, ENTRY_INVALID, 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;
 }
 
-/* Return the contents of the word at address ADDR in the remote machine,
-   using the data cache.  */
-int
-dcache_fetch (dcache, addr)
-     DCACHE *dcache;
-     CORE_ADDR addr;
+/* Writeback any dirty lines. */
+static int
+dcache_writeback (DCACHE *dcache)
 {
-  register struct dcache_block *db;
+  struct dcache_block *db;
+
+  db = dcache->valid_head;
 
-  db = dcache_hit (dcache, addr);
-  if (db == 0)
+  while (db)
     {
-      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 */
+      if (!dcache_write_line (dcache, db))
+       return 0;
+      db = db->p;
     }
-  return (dcache_value (db, addr));
+  return 1;
 }
 
-/* Write the word at ADDR both in the data cache and in the remote machine.  */
-void
-dcache_poke (dcache, addr, data)
-     DCACHE *dcache;
-     CORE_ADDR addr;
-     int data;
+
+/* Using the data cache DCACHE return the contents of the byte at
+   address ADDR in the remote machine.  
+
+   Returns 0 on 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);
 
-  /* First make sure the word is IN the cache.  DB is its cache block.  */
-  db = dcache_hit (dcache, addr);
-  if (db == 0)
+  if (!db)
+    {
+      db = dcache_alloc (dcache, addr);
+      if (!db)
+       return 0;
+    }
+  
+  if (db->state[XFORM (addr)] == ENTRY_INVALID)
     {
-      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 */
+      if (!dcache_read_line(dcache, db))
+         return 0;
     }
 
-  /* Modify the word in the cache.  */
-  db->data[XFORM (addr)] = data;
+  *ptr = db->data[XFORM (addr)];
+  return 1;
+}
+
+
+/* Write the byte at PTR into ADDR in the data cache.
+   Return zero on write error.
+ */
+
+static int
+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;
+    }
 
-  /* Send the changed word.  */
-  immediate_quit++;
-  (*dcache->write_memory) (addr, (unsigned char *) &data, 4);
-  immediate_quit--;
+  db->data[XFORM (addr)] = *ptr;
+  db->state[XFORM (addr)] = ENTRY_DIRTY;
+  db->anydirty = 1;
+  return 1;
 }
 
 /* Initialize the data cache.  */
 DCACHE *
-dcache_init (reading, writing)
-     memxferfunc reading;
-     memxferfunc writing;
+dcache_init (void)
 {
-  register i;
-  register struct dcache_block *db;
+  int csize = sizeof (struct dcache_block) * DCACHE_SIZE;
   DCACHE *dcache;
 
-  dcache = xmalloc(sizeof(*dcache));
-  dcache->read_memory = reading;
-  dcache->write_memory = writing;
-  dcache->the_cache = xmalloc(sizeof(*dcache->the_cache) * DCACHE_SIZE);
+  dcache = (DCACHE *) xmalloc (sizeof (*dcache));
+
+  dcache->the_cache = (struct dcache_block *) xmalloc (csize);
+  memset (dcache->the_cache, 0, csize);
 
-  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);
+  dcache_invalidate (dcache);
 
-  return(dcache);
+  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. */
+
+int
+dcache_xfer_memory (DCACHE *dcache, CORE_ADDR memaddr, gdb_byte *myaddr,
+                   int len, int should_write)
+{
+  int i;
+  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 (!xfunc (dcache, memaddr + i, myaddr + i))
+       return 0;
+    }
+
+  /* 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. */
+
+  if (should_write)
+    dcache_writeback (dcache);
+    
+  return len;
+}
+
+static void
+dcache_info (char *exp, int tty)
+{
+  struct dcache_block *p;
+
+  printf_filtered (_("Dcache line width %d, depth %d\n"),
+                  LINE_SIZE, DCACHE_SIZE);
+
+  if (last_cache)
+    {
+      printf_filtered (_("Cache state:\n"));
+
+      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);
+
+         for (j = 0; j < LINE_SIZE; j++)
+           printf_filtered ("%02x", p->data[j] & 0xFF);
+         printf_filtered (("\n"));
+
+         for (j = 0; j < LINE_SIZE; j++)
+           printf_filtered (" %c", state_chars[p->state[j]]);
+         printf_filtered ("\n");
+       }
+    }
+}
+
+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."), _("\
+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."),
+                          NULL,
+                          show_dcache_enabled_p,
+                          &setlist, &showlist);
+
+  add_info ("dcache", dcache_info,
+           _("\
+Print information on the dcache performance.\n\
+The state of each cached byte is represented by a letter:\n\
+  I = invalid\n\
+  D = dirty\n\
+  V = valid"));
+}