diff options
Diffstat (limited to 'gdb/dcache.c')
-rw-r--r-- | gdb/dcache.c | 558 |
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."); + +} |