diff --git a/gdb/dcache.c b/gdb/dcache.c index ae5a479..3969fa3 100644 --- a/gdb/dcache.c +++ b/gdb/dcache.c @@ -34,47 +34,25 @@ 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_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. - */ + 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 essentially a DCACHE_ASSOC-way set-associative cache. Each + each block caches a LINE_SIZE area of memory. Wtihin 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_ASSOC really stores the + *maximum* number of lines associated with a given address. This increases + allocation count and CPU use slightly, for the sake of not allocating + the maximum cache size unless necessary. + + 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 @@ -89,22 +67,23 @@ 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 number of cache blocks stored per set. The total size + of the cache is equal to DCACHE_SET_SIZE * DCACHE_ASSOC * LINE_SIZE. */ +#define DCACHE_SET_SIZE_POWER 6 +#define DCACHE_SET_SIZE (1 << DCACHE_SET_SIZE_POWER) -#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) +/* The 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 LINE_SIZE_POWER 6 #define LINE_SIZE (1 << LINE_SIZE_POWER) +/* The associativity of the cache: how many places a given address + could be stored in. */ +#define DCACHE_ASSOC 4 + /* Each cache block holds LINE_SIZE bytes of data starting at a multiple-of-LINE_SIZE address. */ @@ -112,59 +91,41 @@ #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' }; +#define BLOCKNUM(x) (((x) >> LINE_SIZE_POWER) & (DCACHE_SET_SIZE - 1)) 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 */ +{ + struct dcache_block *next; /* next in list */ + CORE_ADDR addr; /* address of data */ + gdb_byte data[LINE_SIZE]; /* bytes at given address */ - /* whether anything in state is dirty - used to speed up the - dirty scan. */ - int anydirty; + int refs; +}; - int refs; - }; +/* The cache is stored as DCACHE_SET_SIZE separate linked lists of cache + blocks; each list is no longer than DCACHE_ASSOC. Thus, only + DCACHE_ASSOC blocks need to be searched to find the correct one. -/* 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. + Invalidated blocks are never actually free()d except by dcache_free(); + they are kept around in the free list for reuse. */ - 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_set +{ + struct dcache_block *head; + struct dcache_block *tail; + int num; /* Number of lines currently stored. */ +}; 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; +{ + /* blocks */ + struct dcache_set blocks[DCACHE_SET_SIZE]; - /* The cache itself. */ - struct dcache_block *the_cache; - }; + /* free list */ + struct dcache_block *free_head; + struct dcache_block *free_tail; +}; static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr); @@ -174,21 +135,27 @@ 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; +int dcache_stack_enabled_p = 0; + +int remotecache_enabled_p = 0; /* OBSOLETE */ 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); + fprintf_filtered (file, _("Cache use for stack accesses is %s.\n"), value); } +static void +show_remotecache_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); +} DCACHE *last_cache; /* Used by info dcache */ @@ -199,37 +166,45 @@ void dcache_invalidate (DCACHE *dcache) { int i; - dcache->valid_head = 0; - dcache->valid_tail = 0; + struct dcache_set *set; - dcache->free_head = 0; - dcache->free_tail = 0; - - for (i = 0; i < DCACHE_SIZE; i++) + for (i = 0; i < DCACHE_SET_SIZE; i++) { - struct dcache_block *db = dcache->the_cache + i; + set = &dcache->blocks[i]; - if (!dcache->free_head) - dcache->free_head = db; - else - dcache->free_tail->p = db; - dcache->free_tail = db; - db->p = 0; + /* Shuffle all blocks out of each set and into the free list. */ + while (set->head) + { + struct dcache_block *db = set->head; + + set->head = db->next; + + if (!dcache->free_head) + dcache->free_head = db; + else + dcache->free_tail->next = db; + + dcache->free_tail = db; + db->next = NULL; + } + + set->tail = NULL; + set->num = 0; } return; } /* If addr is present in the dcache, return the address of the block - containing it. */ + containing it. */ 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; + /* Search cache blocks for one that is at this address. */ + db = dcache->blocks[BLOCKNUM(addr)].head; while (db) { @@ -238,92 +213,14 @@ dcache_hit (DCACHE *dcache, CORE_ADDR addr) db->refs++; return db; } - db = db->p; + db = db->next; } return NULL; } -/* 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 (¤t_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 */ +/* Fill a cache line from target memory. */ static int dcache_read_line (DCACHE *dcache, struct dcache_block *db) { @@ -334,27 +231,22 @@ dcache_read_line (DCACHE *dcache, struct dcache_block *db) 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) + /* 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_WO) + /* 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; @@ -372,9 +264,6 @@ dcache_read_line (DCACHE *dcache, struct dcache_block *db) len -= res; } - memset (db->state, ENTRY_VALID, sizeof (db->data)); - db->anydirty = 0; - return 1; } @@ -385,62 +274,47 @@ static struct dcache_block * dcache_alloc (DCACHE *dcache, CORE_ADDR addr) { struct dcache_block *db; + struct dcache_set *set; - /* Take something from the free list */ - db = dcache->free_head; - if (db) + set = &dcache->blocks[BLOCKNUM(addr)]; + + if (set->num < DCACHE_ASSOC) { - dcache->free_head = db->p; + /* Take something from the free list if possible. */ + db = dcache->free_head; + if (db) + dcache->free_head = db->next; + else + db = xmalloc (sizeof (struct dcache_block)); + + set->num++; } else { - /* 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; + /* Evict the oldest line first. LRU would be preferable, but this is + fine for now. */ + db = set->head; + set->head = db->next; } 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; + if (!set->head) + set->head = db; else - dcache->valid_tail->p = db; - dcache->valid_tail = db; - db->p = 0; + set->tail->next = db; + set->tail = db; + db->next = NULL; return 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; -} - /* Using the data cache DCACHE return the contents of the byte at address ADDR in the remote machine. - Returns 0 on error. */ + Returns 0 on error. */ static int dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr) @@ -450,13 +324,8 @@ dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr) if (!db) { db = dcache_alloc (dcache, addr); - if (!db) - return 0; - } - - if (db->state[XFORM (addr)] == ENTRY_INVALID) - { - if (!dcache_read_line(dcache, db)) + + if (!dcache_read_line (dcache, db)) return 0; } @@ -466,24 +335,24 @@ dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr) /* 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 to simplify dcache_xfer_memory. */ 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; - } + if (db) + db->data[XFORM (addr)] = *ptr; - db->data[XFORM (addr)] = *ptr; - db->state[XFORM (addr)] = ENTRY_DIRTY; - db->anydirty = 1; return 1; } @@ -491,28 +360,30 @@ dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr) DCACHE * dcache_init (void) { - int csize = sizeof (struct dcache_block) * DCACHE_SIZE; DCACHE *dcache; + int i; dcache = (DCACHE *) xmalloc (sizeof (*dcache)); - dcache->the_cache = (struct dcache_block *) xmalloc (csize); - memset (dcache->the_cache, 0, csize); + for (i = 0; i < DCACHE_SET_SIZE; i++) + dcache->blocks[i].head = NULL; dcache_invalidate (dcache); + dcache->free_head = NULL; + dcache->free_tail = NULL; + last_cache = dcache; return dcache; } -/* Free a data cache */ +/* Free a data cache. */ void dcache_free (DCACHE *dcache) { if (last_cache == dcache) last_cache = NULL; - xfree (dcache->the_cache); xfree (dcache); } @@ -520,89 +391,182 @@ dcache_free (DCACHE *dcache) 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. */ + Returns length of data written or read; 0 for error. */ int -dcache_xfer_memory (DCACHE *dcache, CORE_ADDR memaddr, gdb_byte *myaddr, +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; + /* Do write-through first, so that if it fails, we don't write to + the cache at all. */ + + if (should_write) + { + res = target_write (ops, TARGET_OBJECT_RAW_MEMORY, + NULL, myaddr, memaddr, len); + if (res < len) + return 0; + } + for (i = 0; i < len; i++) { if (!xfunc (dcache, memaddr + i, myaddr + i)) return 0; } + + return len; +} - /* 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. */ +/* 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. */ - if (should_write) - dcache_writeback (dcache); +/* 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 indexno, int lineno) +{ + int j; + struct dcache_block *p; + + if (!last_cache) + { + printf_filtered (_("No data cache available.\n")); + return; + } + + p = last_cache->blocks[indexno].head; + + while (lineno > 0 && p) + { + p = p->next; + lineno--; + } + + if (!p) + { + printf_filtered (_("No such cache line exists.\n")); + return; + } - return len; + printf_filtered (_("Index %d, line %d: address %lx [%d hits]\n"), + indexno, lineno, p->addr, p->refs); + + for (j = 0; j < LINE_SIZE; j++) + { + printf_filtered ("%02x ", p->data[j]); + + /* Print a newline every 16 bytes (48 characters) */ + if ((j % 16 == 15) && (j != LINE_SIZE - 1)) + printf_filtered ("\n"); + } + printf_filtered ("\n"); } static void dcache_info (char *exp, int tty) { struct dcache_block *p; + int i, linecount, refcount, lineno; - printf_filtered (_("Dcache line width %d, depth %d\n"), - LINE_SIZE, DCACHE_SIZE); + if (exp) + { + int indexno, lineno; + char *linestart; + indexno = strtol (exp, &linestart, 10); + if (linestart == exp) + { + printf_filtered (_("Usage: info dcache [indexnumber linenumber]\n")); + return; + } - if (last_cache) + lineno = strtol (linestart + 1, NULL, 10); + dcache_print_line (indexno, lineno); + return; + } + + printf_filtered (_("Dcache line width %d, sets %d, assoc %d\n"), + LINE_SIZE, DCACHE_SET_SIZE, DCACHE_ASSOC); + + if (!last_cache) { - 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); + linecount = 0; + refcount = 0; - for (j = 0; j < LINE_SIZE; j++) - printf_filtered ("%02x", p->data[j] & 0xFF); - printf_filtered (("\n")); + for (i = 0; i < DCACHE_SET_SIZE; i++) + { + p = last_cache->blocks[i].head; + lineno = 0; - for (j = 0; j < LINE_SIZE; j++) - printf_filtered (" %c", state_chars[p->state[j]]); - printf_filtered ("\n"); + while (p) + { + printf_filtered (_("Index %d, line %d: address %lx [%d hits]\n"), + i, lineno, p->addr, p->refs); + linecount++; + lineno++; + refcount += p->refs; + p = p->next; } } + + printf_filtered (_("Cache state: %d active lines, %d hits\n"), linecount, refcount); } 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."), + add_setshow_boolean_cmd ("stackcache", class_support, + &dcache_stack_enabled_p, _("\ +Set cache use for stack access."), _("\ +Show cache use for stack access."), _("\ +When on, use the data cache for all stack access, regardless of any\n\ +configured memory regions. This improves remote performance significantly.\n\ +By default, caching for stack access is off."), NULL, show_dcache_enabled_p, &setlist, &showlist); + add_setshow_boolean_cmd ("remotecache", class_support, + &remotecache_enabled_p, _("\ +Set remotecache flag."), _("\ +Show remotecache flag."), _("\ +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\ +\"stackcache\" flag; \"remotecache\" now does nothing and exists only\n\ +for compatibility reasons."), + NULL, + show_remotecache_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")); +Print information about the data cache.\n\ +With no arguments, this command prints the cache configuration and a\n\ +summary of each line in the cache. Use \"info dcache \"\n\ +to dump the contents of a given line.")); } diff --git a/gdb/dcache.h b/gdb/dcache.h index 32077a6..fdc22c5 100644 --- a/gdb/dcache.h +++ b/gdb/dcache.h @@ -35,7 +35,13 @@ void dcache_free (DCACHE *); /* Simple to call from _xfer_memory */ -int dcache_xfer_memory (DCACHE *cache, CORE_ADDR mem, gdb_byte *my, - int len, int should_write); +int dcache_xfer_memory (struct target_ops *ops, DCACHE *cache, CORE_ADDR mem, + gdb_byte *my, int len, int should_write); + +void dcache_update (DCACHE *dcache, CORE_ADDR memaddr, gdb_byte *myaddr, + int len); + +/* Should we use the dcache for stack access? */ +extern int dcache_stack_enabled_p; #endif /* DCACHE_H */ diff --git a/gdb/target.c b/gdb/target.c index 7a5b768..8c3b2bd 100644 --- a/gdb/target.c +++ b/gdb/target.c @@ -1214,16 +1214,14 @@ memory_xfer_partial (struct target_ops *ops, void *readbuf, const void *writebuf if (region->attrib.cache) { - /* FIXME drow/2006-08-09: This call discards OPS, so the raw - memory request will start back at current_target. */ if (readbuf != NULL) - res = dcache_xfer_memory (target_dcache, memaddr, readbuf, + res = dcache_xfer_memory (ops, target_dcache, memaddr, readbuf, reg_len, 0); else /* FIXME drow/2006-08-09: If we're going to preserve const correctness dcache_xfer_memory should take readbuf and writebuf. */ - res = dcache_xfer_memory (target_dcache, memaddr, + res = dcache_xfer_memory (ops, target_dcache, memaddr, (void *) writebuf, reg_len, 1); if (res <= 0)