X-Git-Url: http://drtracing.org/?a=blobdiff_plain;f=gdb%2Fdcache.c;h=652cfab9e091e86723e2beef7f797bd4d908641e;hb=33fbcbee30e6565db27a4c5076ff61c9fc8e0744;hp=a563d8bff979db89aaac3c7695a9f08ce2cafe97;hpb=e99586d56aa63911e842a974bd8eb7ed20da7af0;p=deliverable%2Fbinutils-gdb.git
diff --git a/gdb/dcache.c b/gdb/dcache.c
index a563d8bff9..652cfab9e0 100644
--- a/gdb/dcache.c
+++ b/gdb/dcache.c
@@ -1,13 +1,13 @@
-/* Caching code. Typically used by remote back ends for
- caching remote memory.
+/* Caching code for GDB, the GNU debugger.
- Copyright 1992-1993, 1995, 1998-1999 Free Software Foundation, Inc.
+ Copyright (C) 1992-1993, 1995-1996, 1998-2001, 2003, 2007-2012 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,507 +16,732 @@
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 . */
#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"
-/*
- 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.
+/* 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;
- set remotecache 0
+/* 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 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.
+ 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. */
- 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.
+#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. */
-/* 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 free list is maintained identically to OLDEST to simplify
+ the code: we only need one set of accessors. */
+ struct dcache_block *freelist;
-#define DCACHE_SIZE 64
+ /* The number of in-use lines in the cache. */
+ int size;
+ CORE_ADDR line_size; /* current line_size. */
-/* This value regulates the size of a cache line. Smaller values
- reduce the time taken to read a single byte, but reduce overall
- throughput. */
+ /* The ptid of last inferior to use cache or null_ptid. */
+ ptid_t ptid;
+};
-#define LINE_SIZE_POWER (5)
-#define LINE_SIZE (1 << LINE_SIZE_POWER)
+typedef void (block_func) (struct dcache_block *block, void *param);
-/* Each cache block holds LINE_SIZE bytes of data
- starting at a multiple-of-LINE_SIZE address. */
+static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr);
-#define LINE_SIZE_MASK ((LINE_SIZE - 1))
-#define XFORM(x) ((x) & LINE_SIZE_MASK)
-#define MASK(x) ((x) & ~LINE_SIZE_MASK)
+static int dcache_read_line (DCACHE *dcache, struct dcache_block *db);
+static struct dcache_block *dcache_alloc (DCACHE *dcache, CORE_ADDR addr);
-#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 */
+static void dcache_info (char *exp, int tty);
+void _initialize_dcache (void);
-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 */
+static int dcache_enabled_p = 0; /* OBSOLETE */
- /* whether anything in state is dirty - used to speed up the
- dirty scan. */
- int anydirty;
+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);
+}
- int refs;
- };
+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? */
-struct dcache_struct
- {
- /* Function to actually read the target memory. */
- memxferfunc read_memory;
+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;
+ }
+}
- /* Function to actually write the target memory */
- memxferfunc write_memory;
+/* Remove BLOCK from circular block list BLIST. */
- /* free list */
- struct dcache_block *free_head;
- struct dcache_block *free_tail;
+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;
+ }
+}
- /* in use list */
- struct dcache_block *valid_head;
- struct dcache_block *valid_tail;
+/* 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. */
- /* The cache itself. */
- struct dcache_block *the_cache;
+static void
+for_each_block (struct dcache_block **blist, block_func *func, void *param)
+{
+ struct dcache_block *db;
- /* 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;
- };
+ if (*blist == NULL)
+ return;
-static int dcache_poke_byte (DCACHE * dcache, CORE_ADDR addr, char *ptr);
+ db = *blist;
+ do
+ {
+ struct dcache_block *next = db->next;
-static int dcache_peek_byte (DCACHE * dcache, CORE_ADDR addr, char *ptr);
+ func (db, param);
+ db = next;
+ }
+ while (*blist && db != *blist);
+}
-static struct dcache_block *dcache_hit (DCACHE * dcache, CORE_ADDR addr);
+/* BLOCK_FUNC routine for dcache_free. */
-static int dcache_write_line (DCACHE * dcache, struct dcache_block *db);
+static void
+free_block (struct dcache_block *block, void *param)
+{
+ xfree (block);
+}
-static struct dcache_block *dcache_alloc (DCACHE * dcache, CORE_ADDR addr);
+/* Free a data cache. */
-static int dcache_writeback (DCACHE * dcache);
+void
+dcache_free (DCACHE *dcache)
+{
+ if (last_cache == dcache)
+ last_cache = NULL;
-static void dcache_info (char *exp, int tty);
+ splay_tree_delete (dcache->tree);
+ for_each_block (&dcache->oldest, free_block, NULL);
+ for_each_block (&dcache->freelist, free_block, NULL);
+ xfree (dcache);
+}
-void _initialize_dcache (void);
-static int dcache_enabled_p = 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. */
-DCACHE *last_cache; /* Used by info dcache */
+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_invd (DCACHE *dcache)
+dcache_invalidate (DCACHE *dcache)
{
- int i;
- dcache->valid_head = 0;
- dcache->valid_tail = 0;
+ for_each_block (&dcache->oldest, invalidate_block, dcache);
- dcache->free_head = 0;
- dcache->free_tail = 0;
+ dcache->oldest = NULL;
+ dcache->size = 0;
+ dcache->ptid = null_ptid;
- for (i = 0; i < DCACHE_SIZE; i++)
+ if (dcache->line_size != dcache_line_size)
{
- struct dcache_block *db = dcache->the_cache + i;
+ /* We've been asked to use a different line size.
+ All of our freelist blocks are now the wrong size, so free them. */
- if (!dcache->free_head)
- dcache->free_head = db;
- else
- dcache->free_tail->p = db;
- dcache->free_tail = db;
- db->p = 0;
+ for_each_block (&dcache->freelist, free_block, dcache);
+ dcache->freelist = NULL;
+ dcache->line_size = dcache_line_size;
}
+}
+
+/* Invalidate the line associated with ADDR. */
- dcache->cache_has_stuff = 0;
+static void
+dcache_invalidate_line (DCACHE *dcache, CORE_ADDR addr)
+{
+ struct dcache_block *db = dcache_hit (dcache, addr);
- return;
+ if (db)
+ {
+ splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
+ remove_block (&dcache->oldest, db);
+ append_block (&dcache->freelist, db);
+ --dcache->size;
+ }
}
/* If addr is present in the dcache, return the address of the block
- containing it. */
+ containing it. Otherwise return NULL. */
static struct dcache_block *
dcache_hit (DCACHE *dcache, CORE_ADDR addr)
{
- register struct dcache_block *db;
+ struct dcache_block *db;
- /* Search all cache blocks for one that is at this address. */
- db = dcache->valid_head;
+ splay_tree_node node = splay_tree_lookup (dcache->tree,
+ (splay_tree_key) MASK (dcache, addr));
- while (db)
- {
- if (MASK (addr) == db->addr)
- {
- db->refs++;
- return db;
- }
- db = db->p;
- }
+ if (!node)
+ return NULL;
- return NULL;
+ db = (struct dcache_block *) node->value;
+ db->refs++;
+ return db;
}
-/* Make sure that anything in this line which needs to
- be written is. */
+/* 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_write_line (DCACHE *dcache, register struct dcache_block *db)
+dcache_read_line (DCACHE *dcache, struct dcache_block *db)
{
- int s;
- int e;
- s = 0;
- if (db->anydirty)
+ 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)
{
- for (s = 0; s < LINE_SIZE; s++)
+ /* 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)
{
- 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;
- }
- }
+ memaddr += reg_len;
+ myaddr += reg_len;
+ len -= reg_len;
+ continue;
}
- db->anydirty = 0;
+
+ res = target_read (¤t_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. */
static struct dcache_block *
dcache_alloc (DCACHE *dcache, CORE_ADDR addr)
{
- register struct dcache_block *db;
-
- if (dcache_enabled_p == 0)
- abort ();
+ 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;
- dcache->valid_head = db->p;
+ db = dcache->freelist;
+ if (db)
+ remove_block (&dcache->freelist, db);
+ else
+ db = xmalloc (offsetof (struct dcache_block, data) +
+ dcache->line_size);
- dcache_write_line (dcache, db);
+ 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;
+ /* 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 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)
{
- 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, addr);
+ struct dcache_block *db = dcache_hit (dcache, addr);
- immediate_quit++;
- 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--;
+ if (!db)
+ {
+ db = dcache_alloc (dcache, addr);
- memset (db->state, ENTRY_OK, sizeof (db->data));
- db->anydirty = 0;
+ if (!dcache_read_line (dcache, db))
+ return 0;
}
- *ptr = db->data[XFORM (addr)];
- return ok;
-}
-
-/* Writeback any dirty lines to the remote. */
-static int
-dcache_writeback (DCACHE *dcache)
-{
- struct dcache_block *db;
- db = dcache->valid_head;
-
- while (db)
- {
- if (!dcache_write_line (dcache, db))
- return 0;
- db = db->p;
- }
+ *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)
{
- register struct dcache_block *db = dcache_hit (dcache, addr);
+ struct dcache_block *db = dcache_hit (dcache, addr);
- if (!db)
- {
- db = dcache_alloc (dcache, addr);
- }
+ 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 (memxferfunc reading, memxferfunc writing)
+dcache_init (void)
{
- 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_invd (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;
-
- free (dcache->the_cache);
- free (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 res;
+ int (*xfunc) (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr);
+
+ xfunc = should_write ? dcache_poke_byte : dcache_peek_byte;
- if (dcache_enabled_p)
+ /* If this is a different inferior from what we've recorded,
+ flush the cache. */
+
+ if (! ptid_equal (inferior_ptid, dcache->ptid))
{
- int (*xfunc) (DCACHE * dcache, CORE_ADDR addr, char *ptr);
- xfunc = should_write ? dcache_poke_byte : dcache_peek_byte;
+ dcache_invalidate (dcache);
+ dcache->ptid = inferior_ptid;
+ }
+
+ /* Do write-through first, so that if it fails, we don't write to
+ the cache at all. */
- for (i = 0; i < len; i++)
+ 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))
{
- if (!xfunc (dcache, memaddr + i, myaddr + i))
- return 0;
+ /* 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;
}
- dcache->cache_has_stuff = 1;
- dcache_writeback (dcache);
}
- else
+
+ 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_print_line (int index)
+{
+ splay_tree_node n;
+ struct dcache_block *db;
+ int i, j;
+
+ if (!last_cache)
{
- memxferfunc xfunc;
- xfunc = should_write ? dcache->write_memory : dcache->read_memory;
+ printf_filtered (_("No data cache available.\n"));
+ return;
+ }
- if (dcache->cache_has_stuff)
- dcache_invd (dcache);
+ n = splay_tree_min (last_cache->tree);
- len = xfunc (memaddr, myaddr, len);
+ for (i = index; i > 0; --i)
+ {
+ if (!n)
+ break;
+ n = splay_tree_successor (last_cache->tree, n->key);
}
- return len;
+
+ 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)
{
- struct dcache_block *p;
+ splay_tree_node n;
+ int i, refcount;
- if (!dcache_enabled_p)
+ if (exp)
{
- printf_filtered ("Dcache not enabled\n");
+ 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 enabled, line width %d, depth %d\n",
- LINE_SIZE, DCACHE_SIZE);
- if (last_cache)
+ 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 ("Cache state:\n");
+ printf_filtered (_("No data cache available.\n"));
+ return;
+ }
- 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);
+ printf_filtered (_("Contains data for %s\n"),
+ target_pid_to_str (last_cache->ptid));
- for (j = 0; j < LINE_SIZE; j++)
- printf_filtered ("%02x", p->data[j] & 0xFF);
- printf_filtered ("\n");
+ refcount = 0;
- for (j = 0; j < LINE_SIZE; j++)
- printf_filtered (" %2x", p->state[j]);
- printf_filtered ("\n");
- }
+ 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);
}
-/* Turn dcache on or off. */
-void
-set_dcache_state (int what)
+static void
+set_dcache_size (char *args, int from_tty,
+ struct cmd_list_element *c)
{
- dcache_enabled_p = !!what;
+ 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_show_from_set
- (add_set_cmd ("remotecache", class_support, var_boolean,
- (char *) &dcache_enabled_p,
- "\
-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 off.",
- &setlist),
- &showlist);
+ 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.");
-
+ _("\
+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 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);
}