X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gdb%2Fdcache.c;h=1716def4c4a08188ecfce039658aa52af385e1e3;hb=fa4dc567aec7664b3508dd9401b1fbb6d304f281;hp=cc115ccc027a402b4da6316bf1df06a8ac9f8af5;hpb=6951700025cba17b96a672a18681ec47158618ae;p=external%2Fbinutils.git diff --git a/gdb/dcache.c b/gdb/dcache.c index cc115cc..1716def 100644 --- a/gdb/dcache.c +++ b/gdb/dcache.c @@ -1,13 +1,12 @@ /* Caching code for GDB, the GNU debugger. - Copyright 1992, 1993, 1995, 1996, 1998, 1999, 2000, 2001, 2003 Free - Software Foundation, Inc. + Copyright (C) 1992-2019 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,67 +15,47 @@ 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 "target-dcache.h" +#include "inferior.h" +#include "splay-tree.h" +#include "gdbarch.h" + +/* Commands with a prefix of `{set,show} dcache'. */ +static struct cmd_list_element *dcache_set_list = NULL; +static struct cmd_list_element *dcache_show_list = NULL; /* The data cache could lead to incorrect results because it doesn't know about volatile variables, thus making it impossible to debug functions which use memory mapped I/O devices. Set the nocache memory region attribute in those cases. - In general the dcache speeds up performance, some speed improvement + In general the dcache speeds up performance. Some speed improvement comes from the actual caching mechanism, but the major gain is in the reduction of the remote protocol overhead; instead of reading or writing a large area of memory in 4 byte requests, the cache - bundles up the requests into 32 byte (actually LINE_SIZE) chunks. - Reducing the overhead to an eighth of what it was. This is very - obvious when displaying a large amount of data, - - eg, x/200x 0 - - caching | no yes - ---------------------------- - first time | 4 sec 2 sec improvement due to chunking - second time | 4 sec 0 sec improvement due to caching - - The cache structure is unusual, we keep a number of cache blocks - (DCACHE_SIZE) and each one caches a LINE_SIZEed area of memory. - Within each line we remember the address of the line (always a - multiple of the LINE_SIZE) and a vector of bytes over the range. - There's another vector which contains the state of the bytes. - - ENTRY_BAD means that the byte is just plain wrong, and has no - correspondence with anything else (as it would when the cache is - turned on, but nothing has been done to it. - - ENTRY_DIRTY means that the byte has some data in it which should be - written out to the remote target one day, but contains correct - data. - - ENTRY_OK means that the data is the same in the cache as it is in - remote memory. - - - The ENTRY_DIRTY state is necessary because GDB likes to write large - lumps of memory in small bits. If the caching mechanism didn't - maintain the DIRTY information, then something like a two byte - write would mean that the entire cache line would have to be read, - the two bytes modified and then written out again. The alternative - would be to not read in the cache line in the first place, and just - write the two bytes directly into target memory. The trouble with - that is that it really nails performance, because of the remote - protocol overhead. This way, all those little writes are bundled - up into an entire cache line write in one go, without having to - read the cache line in the first place. - */ + bundles up the requests into LINE_SIZE chunks, reducing overhead + significantly. This is most useful when accessing a large amount + of data, such as when performing a backtrace. + + The cache is a splay tree along with a linked list for replacement. + Each block caches a LINE_SIZE area of memory. Within each line we + remember the address of the line (which must be a multiple of + LINE_SIZE) and the actual data block. + + Lines are only allocated as needed, so DCACHE_SIZE really specifies the + *maximum* number of lines in the cache. + + At present, the cache is write-through rather than writeback: as soon + as data is written to the cache, it is also immediately written to + the target. Therefore, cache lines are never "dirty". Whether a given + line is valid or not depends on where it is stored in the dcache_struct; + there is no per-block valid flag. */ /* NOTE: Interaction of dcache and memory region attributes @@ -91,184 +70,262 @@ the last bit of the .text segment and the first bit of the .data segment fall within the same dcache page with a ro/cacheable memory region defined for the .text segment and a rw/non-cacheable memory - region defined for the .data segment. */ + region defined for the .data segment. */ -/* This value regulates the number of cache blocks stored. - Smaller values reduce the time spent searching for a cache - line, and reduce memory requirements, but increase the risk - of a line not being in memory */ +/* The maximum number of lines stored. The total size of the cache is + equal to DCACHE_SIZE times LINE_SIZE. */ +#define DCACHE_DEFAULT_SIZE 4096 +static unsigned dcache_size = DCACHE_DEFAULT_SIZE; -#define DCACHE_SIZE 64 - -/* This value regulates the size of a cache line. Smaller values - reduce the time taken to read a single byte, but reduce overall - throughput. */ - -#define LINE_SIZE_POWER (5) -#define LINE_SIZE (1 << LINE_SIZE_POWER) +/* The default size of a cache line. Smaller values reduce the time taken to + read a single byte and make the cache more granular, but increase + overhead and reduce the effectiveness of the cache as a prefetcher. */ +#define DCACHE_DEFAULT_LINE_SIZE 64 +static unsigned dcache_line_size = DCACHE_DEFAULT_LINE_SIZE; /* Each cache block holds LINE_SIZE bytes of data starting at a multiple-of-LINE_SIZE address. */ -#define LINE_SIZE_MASK ((LINE_SIZE - 1)) -#define XFORM(x) ((x) & LINE_SIZE_MASK) -#define MASK(x) ((x) & ~LINE_SIZE_MASK) +#define 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; -#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 */ + 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. */ -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 */ + /* The free list is maintained identically to OLDEST to simplify + the code: we only need one set of accessors. */ + struct dcache_block *freelist; - /* whether anything in state is dirty - used to speed up the - dirty scan. */ - int anydirty; + /* The number of in-use lines in the cache. */ + int size; + CORE_ADDR line_size; /* current line_size. */ - int refs; - }; + /* The ptid of last inferior to use cache or null_ptid. */ + ptid_t ptid; +}; +typedef void (block_func) (struct dcache_block *block, void *param); -/* 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. +static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr); - 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. */ +static int dcache_read_line (DCACHE *dcache, struct dcache_block *db); -struct dcache_struct - { - /* free list */ - struct dcache_block *free_head; - struct dcache_block *free_tail; +static struct dcache_block *dcache_alloc (DCACHE *dcache, CORE_ADDR addr); - /* in use list */ - struct dcache_block *valid_head; - struct dcache_block *valid_tail; +static int dcache_enabled_p = 0; /* OBSOLETE */ - /* The cache itself. */ - struct dcache_block *the_cache; - }; +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 int dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr); +/* 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 int dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr); +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; + } +} -static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr); +/* Remove BLOCK from circular block list BLIST. */ -static int dcache_write_line (DCACHE *dcache, struct dcache_block *db); +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; + } +} -static int dcache_read_line (DCACHE *dcache, struct dcache_block *db); +/* Iterate over all elements in BLIST, calling FUNC. + PARAM is passed to FUNC. + FUNC may remove the block it's passed, but only that block. */ -static struct dcache_block *dcache_alloc (DCACHE *dcache, CORE_ADDR addr); +static void +for_each_block (struct dcache_block **blist, block_func *func, void *param) +{ + struct dcache_block *db; -static int dcache_writeback (DCACHE *dcache); + if (*blist == NULL) + return; -static void dcache_info (char *exp, int tty); + db = *blist; + do + { + struct dcache_block *next = db->next; -void _initialize_dcache (void); + func (db, param); + db = next; + } + while (*blist && db != *blist); +} -static int dcache_enabled_p = 0; +/* 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) +{ + splay_tree_delete (dcache->tree); + for_each_block (&dcache->oldest, free_block, NULL); + for_each_block (&dcache->freelist, free_block, NULL); + xfree (dcache); +} -DCACHE *last_cache; /* Used by info 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_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. */ + +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) { 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) { CORE_ADDR memaddr; - char *myaddr; + gdb_byte *myaddr; int len; int res; int reg_len; struct mem_region *region; - if (!db->anydirty) - return 1; - - len = LINE_SIZE; + len = dcache->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) + /* Don't overrun if this block is right at the end of the region. */ + region = lookup_mem_region (memaddr); + if (region->hi == 0 || memaddr + len < region->hi) reg_len = len; else reg_len = region->hi - memaddr; - if (!region->attrib.cache || region->attrib.mode == MEM_RO) + /* 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; @@ -276,105 +333,15 @@ dcache_write_line (DCACHE *dcache, register struct dcache_block *db) 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; - while (dirty_len > 0) - { - res = do_xfer_memory(memaddr, myaddr, dirty_len, 1, - ®ion->attrib); - if (res <= 0) - return 0; - - memset (&db->state[XFORM(memaddr)], ENTRY_OK, res); - memaddr += res; - myaddr += res; - len -= res; - dirty_len -= res; - } - } - } - - db->anydirty = 0; - return 1; -} - -/* Read cache line */ -static int -dcache_read_line (DCACHE *dcache, struct dcache_block *db) -{ - CORE_ADDR memaddr; - char *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)) + res = target_read_raw_memory (memaddr, myaddr, reg_len); + if (res != 0) 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; - } - - while (reg_len > 0) - { - res = do_xfer_memory (memaddr, myaddr, reg_len, 0, - ®ion->attrib); - if (res <= 0) - return 0; - - memaddr += res; - myaddr += res; - len -= res; - reg_len -= res; - } + memaddr += reg_len; + myaddr += reg_len; + len -= reg_len; } - memset (db->state, ENTRY_OK, sizeof (db->data)); - db->anydirty = 0; - return 1; } @@ -386,219 +353,381 @@ dcache_alloc (DCACHE *dcache, CORE_ADDR addr) { struct dcache_block *db; - /* Take something from the free list */ - db = dcache->free_head; - if (db) + if (dcache->size >= dcache_size) { - dcache->free_head = db->p; + /* Evict the least recently allocated line. */ + db = dcache->oldest; + remove_block (&dcache->oldest, db); + + splay_tree_remove (dcache->tree, (splay_tree_key) db->addr); } else { - /* Nothing left on free list, so grab one from the valid list */ - db = dcache->valid_head; + db = dcache->freelist; + if (db) + remove_block (&dcache->freelist, db); + else + db = ((struct dcache_block *) + xmalloc (offsetof (struct dcache_block, data) + + dcache->line_size)); - if (!dcache_write_line (dcache, db)) - return NULL; - - dcache->valid_head = db->p; + dcache->size++; } - db->addr = MASK(addr); + db->addr = MASK (dcache, addr); db->refs = 0; - db->anydirty = 0; - memset (db->state, ENTRY_BAD, sizeof (db->data)); - /* append this line to end of valid list */ - if (!dcache->valid_head) - dcache->valid_head = db; - else - dcache->valid_tail->p = db; - dcache->valid_tail = db; - db->p = 0; + /* Put DB at the end of the list, it's the newest. */ + append_block (&dcache->oldest, db); - return db; -} + splay_tree_insert (dcache->tree, (splay_tree_key) db->addr, + (splay_tree_value) db); -/* Writeback any dirty lines. */ -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; - } - return 1; + return db; } - -/* Using the data cache DCACHE return the contents of the byte at +/* Using the data cache DCACHE, store in *PTR the contents of the byte at address ADDR in the remote machine. - Returns 0 on error. */ + Returns 1 for success, 0 for error. */ static int -dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr) +dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr) { struct dcache_block *db = dcache_hit (dcache, addr); if (!db) { db = dcache_alloc (dcache, addr); - if (!db) - return 0; - } - - if (db->state[XFORM (addr)] == ENTRY_BAD) - { - if (!dcache_read_line(dcache, db)) + + if (!dcache_read_line (dcache, db)) return 0; } - *ptr = db->data[XFORM (addr)]; + *ptr = db->data[XFORM (dcache, addr)]; return 1; } - /* Write the byte at PTR into ADDR in the data cache. - Return zero on write error. - */ -static int -dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, char *ptr) + The caller should have written the data through to target memory + already. + + 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. */ + +static void +dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, const gdb_byte *ptr) { struct dcache_block *db = dcache_hit (dcache, addr); - if (!db) - { - db = dcache_alloc (dcache, addr); - if (!db) - return 0; - } + if (db) + db->data[XFORM (dcache, addr)] = *ptr; +} - db->data[XFORM (addr)] = *ptr; - db->state[XFORM (addr)] = ENTRY_DIRTY; - db->anydirty = 1; - return 1; +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; } -/* Initialize the data cache. */ +/* Allocate and initialize a data cache. */ + DCACHE * dcache_init (void) { - int csize = sizeof (struct dcache_block) * DCACHE_SIZE; - DCACHE *dcache; + DCACHE *dcache = XNEW (DCACHE); - dcache = (DCACHE *) xmalloc (sizeof (*dcache)); + dcache->tree = splay_tree_new (dcache_splay_tree_compare, + NULL, + NULL); - dcache->the_cache = (struct dcache_block *) xmalloc (csize); - memset (dcache->the_cache, 0, csize); + dcache->oldest = NULL; + dcache->freelist = NULL; + dcache->size = 0; + dcache->line_size = dcache_line_size; + dcache->ptid = null_ptid; - dcache_invalidate (dcache); - - last_cache = dcache; return dcache; } -/* Free a data cache */ -void -dcache_free (DCACHE *dcache) + +/* Read LEN bytes from dcache memory at MEMADDR, transferring to + debugger address MYADDR. If the data is presently cached, this + fills the cache. Arguments/return are like the target_xfer_partial + interface. */ + +enum target_xfer_status +dcache_read_memory_partial (struct target_ops *ops, DCACHE *dcache, + CORE_ADDR memaddr, gdb_byte *myaddr, + ULONGEST len, ULONGEST *xfered_len) { - if (last_cache == dcache) - last_cache = NULL; + ULONGEST i; - xfree (dcache->the_cache); - xfree (dcache); -} + /* If this is a different inferior from what we've recorded, + flush the cache. */ -/* 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. + if (inferior_ptid != dcache->ptid) + { + dcache_invalidate (dcache); + dcache->ptid = inferior_ptid; + } - Returns length of data written or read; 0 for error. + for (i = 0; i < len; i++) + { + if (!dcache_peek_byte (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); + break; + } + } + + if (i == 0) + { + /* Even though reading the whole line failed, we may be able to + read a piece starting where the caller wanted. */ + return raw_memory_xfer_partial (ops, myaddr, NULL, memaddr, len, + xfered_len); + } + else + { + *xfered_len = i; + return TARGET_XFER_OK; + } +} + +/* 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. */ - This routine is indended to be called by remote_xfer_ functions. */ +/* Just update any cache lines which are already present. This is + called by the target_xfer_partial machinery when writing raw + memory. */ -int -dcache_xfer_memory (DCACHE *dcache, CORE_ADDR memaddr, char *myaddr, int len, - int should_write) +void +dcache_update (DCACHE *dcache, enum target_xfer_status status, + CORE_ADDR memaddr, const gdb_byte *myaddr, + ULONGEST len) { - int i; - int (*xfunc) (DCACHE *dcache, CORE_ADDR addr, char *ptr); - xfunc = should_write ? dcache_poke_byte : dcache_peek_byte; + ULONGEST i; for (i = 0; i < len; i++) + if (status == TARGET_XFER_OK) + dcache_poke_byte (dcache, memaddr + i, myaddr + i); + else + { + /* Discard the whole cache line so we don't have a partially + valid line. */ + dcache_invalidate_line (dcache, memaddr + i); + } +} + +/* Print DCACHE line INDEX. */ + +static void +dcache_print_line (DCACHE *dcache, int index) +{ + splay_tree_node n; + struct dcache_block *db; + int i, j; + + if (dcache == NULL) { - if (!xfunc (dcache, memaddr + i, myaddr + i)) - return 0; + printf_filtered (_("No data cache available.\n")); + return; } - /* 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); + n = splay_tree_min (dcache->tree); + + for (i = index; i > 0; --i) + { + if (!n) + break; + n = splay_tree_successor (dcache->tree, n->key); + } + + if (!n) + { + printf_filtered (_("No such cache line exists.\n")); + return; + } - return len; + 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 < dcache->line_size; j++) + { + printf_filtered ("%02x ", db->data[j]); + + /* Print a newline every 16 bytes (48 characters). */ + if ((j % 16 == 15) && (j != dcache->line_size - 1)) + printf_filtered ("\n"); + } + printf_filtered ("\n"); } +/* Parse EXP and show the info about DCACHE. */ + static void -dcache_info (char *exp, int tty) +dcache_info_1 (DCACHE *dcache, const char *exp) { - struct dcache_block *p; - - printf_filtered ("Dcache line width %d, depth %d\n", - LINE_SIZE, DCACHE_SIZE); + splay_tree_node n; + int i, refcount; - if (last_cache) + if (exp) { - printf_filtered ("Cache state:\n"); + char *linestart; - for (p = last_cache->valid_head; p; p = p->p) + i = strtol (exp, &linestart, 10); + if (linestart == exp || i < 0) { - int j; - printf_filtered ("Line at %s, referenced %d times\n", - paddr (p->addr), p->refs); + printf_filtered (_("Usage: info dcache [LINENUMBER]\n")); + return; + } - for (j = 0; j < LINE_SIZE; j++) - printf_filtered ("%02x", p->data[j] & 0xFF); - printf_filtered ("\n"); + dcache_print_line (dcache, i); + return; + } - for (j = 0; j < LINE_SIZE; j++) - printf_filtered ("%2x", p->state[j]); - printf_filtered ("\n"); - } + printf_filtered (_("Dcache %u lines of %u bytes each.\n"), + dcache_size, + dcache ? (unsigned) dcache->line_size + : dcache_line_size); + + if (dcache == NULL || dcache->ptid == null_ptid) + { + printf_filtered (_("No data cache available.\n")); + return; + } + + printf_filtered (_("Contains data for %s\n"), + target_pid_to_str (dcache->ptid).c_str ()); + + refcount = 0; + + n = splay_tree_min (dcache->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 (dcache->tree, n->key); } + + printf_filtered (_("Cache state: %d active lines, %d hits\n"), i, refcount); +} + +static void +info_dcache_command (const char *exp, int tty) +{ + dcache_info_1 (target_dcache_get (), exp); +} + +static void +set_dcache_size (const 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.")); + } + target_dcache_invalidate (); +} + +static void +set_dcache_line_size (const 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); + } + target_dcache_invalidate (); +} + +static void +set_dcache_command (const 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 ", all_commands, gdb_stdout); +} + +static void +show_dcache_command (const 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_info ("dcache", dcache_info, - "Print information on the dcache performance."); - + 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", info_dcache_command, + _("\ +Print information on the dcache performance.\n\ +Usage: info dcache [LINENUMBER]\n\ +With no arguments, this command prints the cache configuration and a\n\ +summary of each line in the cache. With an argument, dump\"\n\ +the contents of the 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_zuinteger_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_zuinteger_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); }