aboutsummaryrefslogtreecommitdiff
path: root/gdb/dcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/dcache.c')
-rw-r--r--gdb/dcache.c558
1 files changed, 558 insertions, 0 deletions
diff --git a/gdb/dcache.c b/gdb/dcache.c
new file mode 100644
index 0000000..a97a940
--- /dev/null
+++ b/gdb/dcache.c
@@ -0,0 +1,558 @@
+/* Caching code. Typically used by remote back ends for
+ caching remote memory.
+
+ Copyright 1992, 1993, 1995, 1998 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
+ (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.
+
+ 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. */
+
+#include "defs.h"
+#include "dcache.h"
+#include "gdbcmd.h"
+#include "gdb_string.h"
+#include "gdbcore.h"
+
+/*
+ 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 remotecache 0
+
+ In those cases.
+
+ 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.
+
+
+ */
+
+
+/* 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))
+#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 */
+
+
+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;
+};
+
+
+struct dcache_struct
+{
+ /* Function to actually read the target memory. */
+ memxferfunc read_memory;
+
+ /* Function to actually write the target memory */
+ memxferfunc write_memory;
+
+ /* 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;
+
+ /* potentially, if the cache was enabled, and then turned off, and
+ then turned on again, the stuff in it could be stale, so this is
+ used to mark it */
+ int cache_has_stuff;
+} ;
+
+static int dcache_poke_byte PARAMS ((DCACHE *dcache, CORE_ADDR addr,
+ char *ptr));
+
+static int dcache_peek_byte PARAMS ((DCACHE *dcache, CORE_ADDR addr,
+ char *ptr));
+
+static struct dcache_block *dcache_hit PARAMS ((DCACHE *dcache,
+ CORE_ADDR addr));
+
+static int dcache_write_line PARAMS ((DCACHE *dcache,struct dcache_block *db));
+
+static struct dcache_block *dcache_alloc PARAMS ((DCACHE *dcache));
+
+static int dcache_writeback PARAMS ((DCACHE *dcache));
+
+static void dcache_info PARAMS ((char *exp, int tty));
+
+void _initialize_dcache PARAMS ((void));
+
+int remote_dcache = 0;
+
+DCACHE *last_cache; /* Used by info dcache */
+
+
+/* Free all the data cache blocks, thus discarding all cached data. */
+
+void
+dcache_flush (dcache)
+ DCACHE *dcache;
+{
+ int i;
+ dcache->valid_head = 0;
+ dcache->valid_tail = 0;
+
+ dcache->free_head = 0;
+ dcache->free_tail = 0;
+
+ for (i = 0; i < DCACHE_SIZE; i++)
+ {
+ 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;
+ }
+
+ dcache->cache_has_stuff = 0;
+
+ return;
+}
+
+/* If addr is present in the dcache, return the address of the block
+ containing it. */
+
+static struct dcache_block *
+dcache_hit (dcache, addr)
+ DCACHE *dcache;
+ CORE_ADDR addr;
+{
+ register struct dcache_block *db;
+
+ /* Search all cache blocks for one that is at this address. */
+ db = dcache->valid_head;
+
+ while (db)
+ {
+ if (MASK(addr) == db->addr)
+ {
+ db->refs++;
+ return db;
+ }
+ db = db->p;
+ }
+
+ return NULL;
+}
+
+/* Make sure that anything in this line which needs to
+ be written is. */
+
+static int
+dcache_write_line (dcache, db)
+ DCACHE *dcache;
+ register struct dcache_block *db;
+{
+ int s;
+ int e;
+ s = 0;
+ if (db->anydirty)
+ {
+ for (s = 0; s < LINE_SIZE; s++)
+ {
+ if (db->state[s] == ENTRY_DIRTY)
+ {
+ int len = 0;
+ for (e = s ; e < LINE_SIZE; e++, len++)
+ if (db->state[e] != ENTRY_DIRTY)
+ break;
+ {
+ /* all bytes from s..s+len-1 need to
+ be written out */
+ int done = 0;
+ while (done < len) {
+ int t = dcache->write_memory (db->addr + s + done,
+ db->data + s + done,
+ len - done);
+ if (t == 0)
+ return 0;
+ done += t;
+ }
+ memset (db->state + s, ENTRY_OK, len);
+ s = e;
+ }
+ }
+ }
+ 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...). */
+
+static struct dcache_block *
+dcache_alloc (dcache)
+ DCACHE *dcache;
+{
+ register struct dcache_block *db;
+
+ if (remote_dcache == 0)
+ abort ();
+
+ /* Take something from the free list */
+ db = dcache->free_head;
+ if (db)
+ {
+ dcache->free_head = db->p;
+ }
+ else
+ {
+ /* Nothing left on free list, so grab one from the valid list */
+ db = dcache->valid_head;
+ dcache->valid_head = db->p;
+
+ dcache_write_line (dcache, db);
+ }
+
+ /* 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;
+}
+
+/* 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, addr, ptr)
+ DCACHE *dcache;
+ CORE_ADDR addr;
+ char *ptr;
+{
+ register struct dcache_block *db = dcache_hit (dcache, addr);
+ int ok=1;
+ int done = 0;
+ if (db == 0
+ || db->state[XFORM (addr)] == ENTRY_BAD)
+ {
+ if (db)
+ {
+ dcache_write_line (dcache, db);
+ }
+ else
+ db = dcache_alloc (dcache);
+ immediate_quit++;
+ db->addr = MASK (addr);
+ while (done < LINE_SIZE)
+ {
+ int try =
+ (*dcache->read_memory)
+ (db->addr + done,
+ db->data + done,
+ LINE_SIZE - done);
+ if (try == 0)
+ return 0;
+ done += try;
+ }
+ immediate_quit--;
+
+ memset (db->state, ENTRY_OK, sizeof (db->data));
+ db->anydirty = 0;
+ }
+ *ptr = db->data[XFORM (addr)];
+ return ok;
+}
+
+/* Writeback any dirty lines to the remote. */
+static int
+dcache_writeback (dcache)
+ DCACHE *dcache;
+{
+ struct dcache_block *db;
+
+ db = dcache->valid_head;
+
+ while (db)
+ {
+ if (!dcache_write_line (dcache, db))
+ return 0;
+ db = db->p;
+ }
+ return 1;
+}
+
+
+/* Using the data cache DCACHE return the contents of the word at
+ address ADDR in the remote machine. */
+int
+dcache_fetch (dcache, addr)
+ DCACHE *dcache;
+ CORE_ADDR addr;
+{
+ int res;
+
+ if (dcache_xfer_memory (dcache, addr, (char *)&res, sizeof res, 0) != sizeof res)
+ memory_error (EIO, addr);
+
+ return res;
+}
+
+
+/* Write the byte at PTR into ADDR in the data cache.
+ Return zero on write error.
+ */
+
+static int
+dcache_poke_byte (dcache, addr, ptr)
+ DCACHE *dcache;
+ CORE_ADDR addr;
+ char *ptr;
+{
+ register struct dcache_block *db = dcache_hit (dcache, addr);
+
+ if (!db)
+ {
+ db = dcache_alloc (dcache);
+ db->addr = MASK (addr);
+ memset (db->state, ENTRY_BAD, sizeof (db->data));
+ }
+
+ db->data[XFORM (addr)] = *ptr;
+ db->state[XFORM (addr)] = ENTRY_DIRTY;
+ db->anydirty = 1;
+ return 1;
+}
+
+/* Write the word at ADDR both in the data cache and in the remote machine.
+ Return zero on write error.
+ */
+
+int
+dcache_poke (dcache, addr, data)
+ DCACHE *dcache;
+ CORE_ADDR addr;
+ int data;
+{
+ if (dcache_xfer_memory (dcache, addr, (char *)&data, sizeof data, 1) != sizeof data)
+ return 0;
+
+ return dcache_writeback (dcache);
+}
+
+
+/* Initialize the data cache. */
+DCACHE *
+dcache_init (reading, writing)
+ memxferfunc reading;
+ memxferfunc writing;
+{
+ int csize = sizeof (struct dcache_block) * DCACHE_SIZE;
+ DCACHE *dcache;
+
+ dcache = (DCACHE *) xmalloc (sizeof (*dcache));
+ dcache->read_memory = reading;
+ dcache->write_memory = writing;
+
+ dcache->the_cache = (struct dcache_block *) xmalloc (csize);
+ memset (dcache->the_cache, 0, csize);
+
+ dcache_flush (dcache);
+
+ 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.
+
+ 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, memaddr, myaddr, len, should_write)
+ DCACHE *dcache;
+ CORE_ADDR memaddr;
+ char *myaddr;
+ int len;
+ int should_write;
+{
+ int i;
+
+ if (remote_dcache)
+ {
+ int (*xfunc) PARAMS ((DCACHE *dcache, CORE_ADDR addr, char *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;
+ }
+ dcache->cache_has_stuff = 1;
+ dcache_writeback (dcache);
+ }
+ else
+ {
+ memxferfunc xfunc;
+ xfunc = should_write ? dcache->write_memory : dcache->read_memory;
+
+ if (dcache->cache_has_stuff)
+ dcache_flush (dcache);
+
+ len = xfunc (memaddr, myaddr, len);
+ }
+ return len;
+}
+
+static void
+dcache_info (exp, tty)
+ char *exp;
+ int tty;
+{
+ struct dcache_block *p;
+
+ if (!remote_dcache)
+ {
+ printf_filtered ("Dcache not enabled\n");
+ return;
+ }
+ printf_filtered ("Dcache enabled, line width %d, depth %d\n",
+ LINE_SIZE, DCACHE_SIZE);
+
+ printf_filtered ("Cache state:\n");
+
+ for (p = last_cache->valid_head; p; p = p->p)
+ {
+ int j;
+ printf_filtered ("Line at %08xd, referenced %d times\n",
+ 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 (" %2x", p->state[j]);
+ printf_filtered ("\n");
+ }
+}
+
+void
+_initialize_dcache ()
+{
+ add_show_from_set
+ (add_set_cmd ("remotecache", class_support, var_boolean,
+ (char *) &remote_dcache,
+ "\
+Set cache use for remote targets.\n\
+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 on.",
+ &setlist),
+ &showlist);
+
+ add_info ("dcache", dcache_info,
+ "Print information on the dcache performance.");
+
+}