GSoC/Buflib: Enable compaction in buflib.

This enables the ability to allocate (and free) memory dynamically
without fragmentation, through compaction. This means allocations can move
and fragmentation be reduced. Most changes are preparing Rockbox for this,
which many times means adding a move callback which can temporarily disable
movement when the corresponding code is in a critical section.

For now, the audio buffer allocation has a central role, because it's the one
having allocated most. This buffer is able to shrink itself, for which it
needs to stop playback for a very short moment. For this,
audio_buffer_available() returns the size of the audio buffer which can
possibly be used by other allocations because the audio buffer can shrink.

lastfm scrobbling and timestretch can now be toggled at runtime without
requiring a reboot.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30381 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index fb8575e..6375094 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -425,11 +425,32 @@
     return buffer;
 }
 
+static int bf_action_cb(int action, struct gui_synclist* list)
+{
+    if (action == ACTION_STD_OK)
+    {
+        splash(HZ/1, "Attempting a 64k allocation");
+        int handle = core_alloc("test", 64<<10);
+        splash(HZ/2, (handle > 0) ? "Success":"Fail");
+        /* for some reason simplelist doesn't allow adding items here if
+         * info.get_name is given, so use normal list api */
+        gui_synclist_set_nb_items(list, core_get_num_blocks());
+        if (handle > 0)
+            core_free(handle);
+        action = ACTION_REDRAW;
+    }
+    else if (action == ACTION_NONE)
+        action = ACTION_REDRAW;
+    return action;
+}
+
 static bool dbg_buflib_allocs(void)
 {
     struct simplelist_info info;
     simplelist_info_init(&info, "mem allocs", core_get_num_blocks(), NULL);
     info.get_name = bf_getname;
+    info.action_callback = bf_action_cb;
+    info.timeout = HZ/2;
     return simplelist_show_list(&info);
 }
 
diff --git a/apps/dsp.c b/apps/dsp.c
index a728dd7..167c043 100644
--- a/apps/dsp.c
+++ b/apps/dsp.c
@@ -318,30 +318,50 @@
     resample_buf = big_resample_buf;
 }
 
+
+static int move_callback(int handle, void* current, void* new)
+{
+    /* TODO */
+    (void)handle;(void)current;;
+    big_sample_buf = new;
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_callback,
+    .shrink_callback = NULL,
+};
+
+
 void dsp_timestretch_enable(bool enabled)
 {
     /* Hook to set up timestretch buffer on first call to settings_apply() */
-    if (big_sample_buf_count < 0) /* Only do something on first call */
+    static int handle;
+    if (enabled)
     {
-        if (enabled)
-        {
-            int handle;
-            /* Set up timestretch buffers */
-            big_sample_buf_count = SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO;
-            big_sample_buf = small_resample_buf;
-            handle = core_alloc("resample buf",
-                    big_sample_buf_count * RESAMPLE_RATIO * sizeof(int32_t));
-            if (handle > 0)
-                big_resample_buf = core_get_data(handle);
-            else
-                big_sample_buf_count = 0;
+        if (big_sample_buf_count > 0)
+            return; /* already allocated and enabled */
+        /* Set up timestretch buffers */
+        big_sample_buf_count = SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO;
+        big_sample_buf = small_resample_buf;
+        handle = core_alloc_ex("resample buf",
+                big_sample_buf_count * RESAMPLE_RATIO * sizeof(int32_t), &ops);
+        if (handle > 0)
+        {   /* success, now setup tdspeed */
+            big_resample_buf = core_get_data(handle);
+            tdspeed_init();
+            tdspeed_setup(&AUDIO_DSP);
         }
-        else
-        {
-            /* Not enabled at startup, "big" buffers will never be available */
-            big_sample_buf_count = 0;
-        }
-        tdspeed_setup(&AUDIO_DSP);
+    }
+    if (!enabled || (handle <= 0)) /* disable */
+    {
+        dsp_set_timestretch(PITCH_SPEED_100);
+        tdspeed_finish();
+        if (handle > 0)
+            core_free(handle);
+        handle = 0;
+        big_sample_buf = NULL;
+        big_sample_buf_count = 0;
     }
 }
 
@@ -1211,7 +1231,7 @@
  */
 int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count)
 {
-    int32_t *tmp[2];
+    static int32_t *tmp[2]; /* tdspeed_doit() needs it static */
     static long last_yield;
     long tick;
     int written = 0;
diff --git a/apps/filetree.c b/apps/filetree.c
index 1aee80b..35bb2a8 100644
--- a/apps/filetree.c
+++ b/apps/filetree.c
@@ -29,6 +29,7 @@
 #include <limits.h>
 #include "bookmark.h"
 #include "tree.h"
+#include "core_alloc.h"
 #include "settings.h"
 #include "filetypes.h"
 #include "talk.h"
@@ -60,7 +61,8 @@
     int i;
     int start=start_index;
 
-    struct entry *entries = c->cache.entries;
+    tree_lock_cache(c);
+    struct entry *entries = tree_get_entries(c);
 
     for(i = 0;i < c->filesindir;i++)
     {
@@ -77,6 +79,8 @@
         }
     }
 
+    tree_unlock_cache(c);
+
     return start_index;
 }
 
@@ -127,13 +131,15 @@
 {
     int i;
     struct dirent *entry;
-    struct entry* entries = c->cache.entries;
+    struct entry* entries;
     DIR *dir;
 
     dir = opendir(c->currdir);
     if(!dir)
         return;
     /* mark all files as non talking, except the .talk ones */
+    entries = tree_get_entries(c);
+    tree_lock_cache(c);
     for (i=0; i < c->filesindir; i++)
     {
         if (entries[i].attr & ATTR_DIRECTORY)
@@ -177,6 +183,7 @@
             }
         }
     }
+    tree_unlock_cache(c);
     closedir(dir);
 }
 
@@ -287,11 +294,11 @@
     c->dirsindir = 0;
     c->dirfull = false;
 
+    tree_lock_cache(c);
     while ((entry = readdir(dir))) {
         int len;
         struct dirinfo info;
-        struct entry* table = c->cache.entries;
-        struct entry* dptr = &table[files_in_dir];
+        struct entry* dptr = tree_get_entry_at(c, files_in_dir);
         if (!entry)
             break;
 
@@ -369,7 +376,7 @@
 
         ++files_in_dir;
 
-        dptr->name = &c->cache.name_buffer[name_buffer_used];
+        dptr->name = core_get_data(c->cache.name_buffer_handle)+name_buffer_used;
         dptr->time_write =
             (long)info.wrtdate<<16 |
             (long)info.wrttime; /* in one # */
@@ -384,13 +391,14 @@
     closedir(dir);
 
     compare_sort_dir = c->sort_dir;
-    qsort(c->cache.entries, files_in_dir, sizeof(struct entry), compare);
+    qsort(tree_get_entries(c), files_in_dir, sizeof(struct entry), compare);
 
     /* If thumbnail talking is enabled, make an extra run to mark files with
        associated thumbnails, so we don't do unsuccessful spinups later. */
     if (global_settings.talk_file_clip)
         check_file_thumbnails(c); /* map .talk to ours */
 
+    tree_unlock_cache(c);
     return 0;
 }
 #ifdef HAVE_LCD_BITMAP
@@ -424,15 +432,15 @@
 {
     int rc = GO_TO_PREVIOUS;
     char buf[MAX_PATH];
-    struct entry* table = c->cache.entries;
-    struct entry *file = &table[c->selected_item];
+    struct entry* file = tree_get_entry_at(c, c->selected_item);
+    int file_attr = file->attr;
 
     if (c->currdir[1])
         snprintf(buf,sizeof(buf),"%s/%s",c->currdir, file->name);
     else
         snprintf(buf,sizeof(buf),"/%s",file->name);
 
-    if (file->attr & ATTR_DIRECTORY) {
+    if (file_attr & ATTR_DIRECTORY) {
         memcpy(c->currdir, buf, sizeof(c->currdir));
         if ( c->dirlevel < MAX_DIR_LEVELS )
             c->selected_item_history[c->dirlevel] = c->selected_item;
@@ -444,7 +452,7 @@
         bool play = false;
         int start_index=0;
 
-        switch ( file->attr & FILE_ATTR_MASK ) {
+        switch ( file_attr & FILE_ATTR_MASK ) {
             case FILE_ATTR_M3U:
                 if (!bookmark_autoload(buf))
                     playlist_viewer_ex(buf);
@@ -612,7 +620,7 @@
                 char *plugin = buf, *argument = NULL, lua_path[MAX_PATH];
                 int ret;
 
-                if ((file->attr & FILE_ATTR_MASK) == FILE_ATTR_LUA) {
+                if ((file_attr & FILE_ATTR_MASK) == FILE_ATTR_LUA) {
                     snprintf(lua_path, sizeof(lua_path)-1, "%s/lua.rock", VIEWERS_DIR); /* Use a #define here ? */
                     plugin = lua_path;
                     argument = buf;
@@ -658,6 +666,7 @@
                     break;
                 }
 
+                struct entry* file = tree_get_entry_at(c, c->selected_item);
                 plugin = filetype_get_plugin(file);
                 if (plugin)
                 {
diff --git a/apps/filetypes.c b/apps/filetypes.c
index c52c734..942ff32 100644
--- a/apps/filetypes.c
+++ b/apps/filetypes.c
@@ -184,6 +184,26 @@
 static int viewer_count = 0;
 
 static int strdup_handle, strdup_bufsize, strdup_cur_idx;
+static int move_callback(int handle, void* current, void* new)
+{
+    /*could compare to strdup_handle, but ops is only used once */
+    (void)handle;
+    size_t diff = new - current;
+#define FIX_PTR(x) \
+    { if ((void*)x > current && (void*)x < (current+strdup_bufsize)) x+= diff; }
+    for(int i = 0; i < filetype_count; i++)
+    {
+        FIX_PTR(filetypes[i].extension);
+        FIX_PTR(filetypes[i].plugin);
+    }
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_callback,
+    .shrink_callback = NULL,
+};
+
 static char *filetypes_strdup(char* string)
 {
     char *buffer = core_get_data(strdup_handle) + strdup_cur_idx;
@@ -323,7 +343,7 @@
         return;
 
     strdup_bufsize = filesize(fd);
-    strdup_handle = core_alloc("filetypes", strdup_bufsize);
+    strdup_handle = core_alloc_ex("filetypes", strdup_bufsize, &ops);
     if (strdup_handle <= 0)
         return;
     read_builtin_types();
diff --git a/apps/main.c b/apps/main.c
index cc9c9e8..07a8bba 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -403,9 +403,6 @@
 #endif /* CONFIG_CODEC != SWCODEC */
 
     scrobbler_init();
-#if CONFIG_CODEC == SWCODEC && defined (HAVE_PITCHSCREEN)
-    tdspeed_init();
-#endif /* CONFIG_CODEC == SWCODEC */
 
     audio_init();
     
@@ -659,9 +656,6 @@
     tree_mem_init();
     filetype_init();
     scrobbler_init();
-#if CONFIG_CODEC == SWCODEC && defined (HAVE_PITCHSCREEN)
-    tdspeed_init();
-#endif /* CONFIG_CODEC == SWCODEC */
     theme_init_buffer();
 
 #if CONFIG_CODEC != SWCODEC
diff --git a/apps/menus/playback_menu.c b/apps/menus/playback_menu.c
index 1b1a13a..a219373 100644
--- a/apps/menus/playback_menu.c
+++ b/apps/menus/playback_menu.c
@@ -142,7 +142,7 @@
     {
         case ACTION_EXIT_MENUITEM: /* on exit */
             if (!scrobbler_is_enabled() && global_settings.audioscrobbler)
-                splash(HZ*2, ID2P(LANG_PLEASE_REBOOT));
+                scrobbler_init();
         
             if(scrobbler_is_enabled() && !global_settings.audioscrobbler)
                 scrobbler_shutdown();
diff --git a/apps/mpeg.c b/apps/mpeg.c
index 595f943..0b1413e 100644
--- a/apps/mpeg.c
+++ b/apps/mpeg.c
@@ -39,7 +39,7 @@
 #include "mp3_playback.h"
 #include "talk.h"
 #include "sound.h"
-#include "bitswap.h"
+#include "system.h"
 #include "appevents.h"
 #include "playlist.h"
 #include "cuesheet.h"
@@ -140,6 +140,7 @@
 static bool checked_for_cuesheet = false;
 
 static const char mpeg_thread_name[] = "mpeg";
+static unsigned int audio_thread_id;
 static unsigned int mpeg_errno;
 
 static bool playing = false;    /* We are playing an MP3 stream */
@@ -492,20 +493,81 @@
 #endif /* !SIMULATOR */
 }
 
-/* Buffer must not move. And not shrink for now */
-static struct buflib_callbacks ops = { NULL, NULL };
+static void do_stop(void)
+{
+    is_playing = false;
+    paused = false;
+
+#ifndef SIMULATOR
+    if (playing)
+        playlist_update_resume_info(audio_current_track());
+
+    stop_playing();
+    mpeg_stop_done = true;
+#else
+    playing = false;
+#endif
+}
+
+static void audio_reset_buffer_noalloc(void* buf, size_t bufsize);
+/* Buffer must not move. */
+static int shrink_callback(int handle, unsigned hints, void* start, size_t old_size)
+{
+    long offset = audio_current_track()->offset;
+    int status = audio_status();
+    /* TODO: Do it without stopping playback, if possible */
+    /* don't call audio_hard_stop() as it frees this handle */
+    if (thread_self() == audio_thread_id)
+    {   /* inline case MPEG_STOP (audio_stop()) response
+         * if we're in the audio thread since audio_stop() otherwise deadlocks */
+        do_stop();
+    }
+    else
+        audio_stop();
+    talk_buffer_steal(); /* we obtain control over the buffer */
+
+    /* we should be free to change the buffer now */
+    size_t wanted_size = (hints & BUFLIB_SHRINK_SIZE_MASK);
+    ssize_t size = (ssize_t)old_size - wanted_size;
+    switch (hints & BUFLIB_SHRINK_POS_MASK)
+    {
+        case BUFLIB_SHRINK_POS_BACK:
+            core_shrink(handle, start, size);
+            audio_reset_buffer_noalloc(start, size);
+            break;
+        case BUFLIB_SHRINK_POS_FRONT:
+            core_shrink(handle, start + wanted_size, size);
+            audio_reset_buffer_noalloc(start + wanted_size, size);
+            break;
+    }
+    if (!(status & AUDIO_STATUS_PAUSE))
+    {   /* safe to call even from the audio thread (due to queue_post()) */
+        audio_play(offset);
+    }
+
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = NULL,
+    .shrink_callback = shrink_callback,
+};
+
 unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size)
 {
     (void)talk_buf; /* always grab the voice buffer for now */
 
     if (buffer_size) /* special case for talk_init() */
+        audio_hard_stop();
+
+    if (!audiobuf_handle)
     {
         size_t bufsize;
-        audio_hard_stop();
         /* audio_hard_stop() frees audiobuf, so re-aquire */
         audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops);
         audiobuflen = bufsize;
-        *buffer_size = audiobuflen;
+        if (buffer_size)
+            *buffer_size = audiobuflen;
     }
     mpeg_audiobuf = core_get_data(audiobuf_handle);
 
@@ -1314,15 +1376,7 @@
                 break;
 
             case MPEG_STOP:
-                DEBUGF("MPEG_STOP\n");
-                is_playing = false;
-                paused = false;
-
-                if (playing)
-                    playlist_update_resume_info(audio_current_track());
-
-                stop_playing();
-                mpeg_stop_done = true;
+                do_stop();
                 break;
 
             case MPEG_PAUSE:
@@ -2679,19 +2733,29 @@
     return core_available();
 }
 
-static void audio_reset_buffer(void)
+static void audio_reset_buffer_noalloc(void* buf, size_t bufsize)
 {
     talk_buffer_steal(); /* will use the mp3 buffer */
+    mpeg_audiobuf = buf;
+    audiobuflen = bufsize;
+    if (global_settings.cuesheet)
+    {   /* enable cuesheet support */
+        curr_cuesheet = (struct cuesheet*)mpeg_audiobuf;
+        mpeg_audiobuf = SKIPBYTES(mpeg_audiobuf, sizeof(struct cuesheet));
+        audiobuflen -= sizeof(struct cuesheet);
+    }
+    talkbuf_init(mpeg_audiobuf);
+}
+
+static void audio_reset_buffer(void)
+{
+    size_t bufsize = audiobuflen;
 
     /* alloc buffer if it's was never allocated or freed by audio_hard_stop() */
     if (!audiobuf_handle)
-    {
-        size_t bufsize; /* dont break strict-aliasing */
         audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops);
-        mpeg_audiobuf = core_get_data(audiobuf_handle);
-        audiobuflen = bufsize;
-    }
-    talkbuf_init(mpeg_audiobuf);
+
+    audio_reset_buffer_noalloc(core_get_data(audiobuf_handle), bufsize);
 }
 
 void audio_play(long offset)
@@ -2923,13 +2987,6 @@
 void audio_init(void)
 {
     mpeg_errno = 0;
-    /* cuesheet support */
-    if (global_settings.cuesheet)
-    {
-        int handle = core_alloc("cuesheet", sizeof(struct cuesheet));
-        if (handle > 0)
-            curr_cuesheet = core_get_data(handle);
-    }
 
     talk_init();
     audio_reset_buffer();
@@ -2937,10 +2994,10 @@
 #ifndef SIMULATOR
     queue_init(&mpeg_queue, true);
 #endif /* !SIMULATOR */
-    create_thread(mpeg_thread, mpeg_stack,
-                  sizeof(mpeg_stack), 0, mpeg_thread_name
-                  IF_PRIO(, PRIORITY_SYSTEM)
-                  IF_COP(, CPU));
+    audio_thread_id = create_thread(mpeg_thread, mpeg_stack,
+                          sizeof(mpeg_stack), 0, mpeg_thread_name
+                          IF_PRIO(, PRIORITY_SYSTEM)
+                          IF_COP(, CPU));
 
     memset(trackdata, 0, sizeof(trackdata));
 
diff --git a/apps/playback.c b/apps/playback.c
index e35d652..af077e6 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -732,8 +732,6 @@
     }
 }
 
-/* Buffer must not move. And not shrink for now */
-static struct buflib_callbacks ops = { NULL, NULL };
 static int audiobuf_handle;
 static size_t filebuflen;
 
@@ -744,8 +742,9 @@
     return core_available();
 }
 
-/* Set up the audio buffer for playback */
-static void audio_reset_buffer(void)
+/* Set up the audio buffer for playback
+ * filebuflen must be pre-initialized with the maximum size */
+static void audio_reset_buffer_noalloc(void* filebuf)
 {
     /*
      * Layout audio buffer as follows:
@@ -761,11 +760,6 @@
 
     /* Initially set up file buffer as all space available */
     size_t allocsize;
-    if (audiobuf_handle > 0)
-        audiobuf_handle = core_free(audiobuf_handle);
-
-    audiobuf_handle = core_alloc_maximum("audiobuf", &filebuflen, &ops);
-    unsigned char *filebuf = core_get_data(audiobuf_handle);
 
     /* Subtract whatever voice needs */
     allocsize = talkbuf_init(filebuf);
@@ -830,6 +824,70 @@
     panicf("%s(): EOM (%zu > %zu)", __func__, allocsize, filebuflen);
 }
 
+
+/* Buffer must not move. */
+static int shrink_callback(int handle, unsigned hints, void* start, size_t old_size)
+{
+    long offset = audio_current_track()->offset;
+    int status = audio_status();
+    /* TODO: Do it without stopping playback, if possible */
+    /* don't call audio_hard_stop() as it frees this handle */
+    if (thread_self() == audio_thread_id)
+    {   /* inline case Q_AUDIO_STOP (audio_hard_stop() response
+         * if we're in the audio thread */
+        audio_stop_playback();
+        queue_clear(&audio_queue);
+    }
+    else
+        audio_queue_send(Q_AUDIO_STOP, 1);
+#ifdef PLAYBACK_VOICE
+    voice_stop();
+#endif
+    /* we should be free to change the buffer now */
+    size_t wanted_size = (hints & BUFLIB_SHRINK_SIZE_MASK);
+    ssize_t size = (ssize_t)old_size - wanted_size;
+    /* set final buffer size before calling audio_reset_buffer_noalloc() */
+    filebuflen = size;
+    switch (hints & BUFLIB_SHRINK_POS_MASK)
+    {
+        case BUFLIB_SHRINK_POS_BACK:
+            core_shrink(handle, start, size);
+            audio_reset_buffer_noalloc(start);
+            break;
+        case BUFLIB_SHRINK_POS_FRONT:
+            core_shrink(handle, start + wanted_size, size);
+            audio_reset_buffer_noalloc(start + wanted_size);
+            break;
+    }
+    if ((status & AUDIO_STATUS_PLAY) == AUDIO_STATUS_PLAY)
+    {
+        if (thread_self() == audio_thread_id)
+            audio_start_playback(offset, 0);  /* inline Q_AUDIO_PLAY */
+        else
+            audio_play(offset);
+    }
+
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = NULL,
+    .shrink_callback = shrink_callback,
+};
+
+static void audio_reset_buffer(void)
+{
+    if (audiobuf_handle > 0)
+    {
+        core_free(audiobuf_handle);
+        audiobuf_handle = 0;
+    }
+    audiobuf_handle = core_alloc_maximum("audiobuf", &filebuflen, &ops);
+    unsigned char *filebuf = core_get_data(audiobuf_handle);
+
+    audio_reset_buffer_noalloc(filebuf);
+}
+
 /* Set the buffer margin to begin rebuffering when 'seconds' from empty */
 static void audio_update_filebuf_watermark(int seconds)
 {
diff --git a/apps/playlist.c b/apps/playlist.c
index 77d8141..f6dda97 100644
--- a/apps/playlist.c
+++ b/apps/playlist.c
@@ -993,14 +993,14 @@
     unsigned int current = playlist->indices[playlist->index];
 
     if (playlist->amount > 0)
-        qsort(playlist->indices, playlist->amount,
+        qsort((void*)playlist->indices, playlist->amount,
             sizeof(playlist->indices[0]), compare);
 
 #ifdef HAVE_DIRCACHE
     /** We need to re-check the song names from disk because qsort can't
      * sort two arrays at once :/
      * FIXME: Please implement a better way to do this. */
-    memset(playlist->filenames, 0xff, playlist->max_playlist_size * sizeof(int));
+    memset((void*)playlist->filenames, 0xff, playlist->max_playlist_size * sizeof(int));
     queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
 #endif
 
@@ -1378,7 +1378,7 @@
     
     if (playlist->in_ram && !control_file && max < 0)
     {
-        max = strlcpy(tmp_buf, &playlist->buffer[seek], sizeof(tmp_buf));
+        max = strlcpy(tmp_buf, (char*)&playlist->buffer[seek], sizeof(tmp_buf));
     }
     else if (max < 0)
     {
@@ -1534,9 +1534,10 @@
             break;
         }
         
-        files = tc->cache.entries;
+        files = tree_get_entries(tc);
         num_files = tc->filesindir;
 
+        tree_lock_cache(tc);
         for (i=0; i<num_files; i++)
         {
             /* user abort */
@@ -1562,6 +1563,7 @@
                     start_dir = NULL;
             }
         }
+        tree_unlock_cache(tc);
 
         if (!exit)
         {
@@ -1612,7 +1614,7 @@
         return -2;
     }
     
-    files = tc->cache.entries;
+    files = tree_get_entries(tc);
     num_files = tc->filesindir;
     
     for (i=0; i<num_files; i++)
@@ -1629,6 +1631,7 @@
     if (has_music)
         return 0;
         
+    tree_lock_cache(tc);
     if (has_subdir && recurse)
     {
         for (i=0; i<num_files; i++)
@@ -1647,6 +1650,7 @@
             }
         }
     }
+    tree_unlock_cache(tc);
 
     if (result < 0)
     {
@@ -1925,6 +1929,31 @@
 }
 
 /*
+ * Need no movement protection since all 3 allocations are not passed to
+ * other functions which can yield().
+ */
+static int move_callback(int handle, void* current, void* new)
+{
+    (void)handle;
+    struct playlist_info* playlist = &current_playlist;
+    if (current == playlist->indices)
+        playlist->indices = new;
+    else if (current == playlist->filenames)
+        playlist->filenames = new;
+    /* buffer can possibly point to a new buffer temporarily (playlist_save()).
+     * just don't overwrite the pointer to that temp buffer */
+    else if (current == playlist->buffer)
+        playlist->buffer = new;
+
+    return BUFLIB_CB_OK;
+}
+
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_callback,
+    .shrink_callback = NULL,
+};
+/*
  * Initialize playlist entries at startup
  */
 void playlist_init(void)
@@ -1941,20 +1970,23 @@
     playlist->fd = -1;
     playlist->control_fd = -1;
     playlist->max_playlist_size = global_settings.max_files_in_playlist;
-    handle = core_alloc("playlist idx", playlist->max_playlist_size * sizeof(int));
+    handle = core_alloc_ex("playlist idx",
+                                playlist->max_playlist_size * sizeof(int), &ops);
     playlist->indices = core_get_data(handle);
     playlist->buffer_size =
         AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir;
-    handle = core_alloc("playlist buf", playlist->buffer_size);
+    handle = core_alloc_ex("playlist buf",
+                                playlist->buffer_size, &ops);
     playlist->buffer = core_get_data(handle);
     playlist->control_mutex = &current_playlist_mutex;
 
     empty_playlist(playlist, true);
 
 #ifdef HAVE_DIRCACHE
-    handle = core_alloc("playlist dc", playlist->max_playlist_size * sizeof(int));
+    handle = core_alloc_ex("playlist dc",
+        playlist->max_playlist_size * sizeof(int), &ops);
     playlist->filenames = core_get_data(handle);
-    memset(playlist->filenames, 0xff,
+    memset((void*)playlist->filenames, 0xff,
            playlist->max_playlist_size * sizeof(int));
     create_thread(playlist_thread, playlist_stack, sizeof(playlist_stack),
                   0, playlist_thread_name IF_PRIO(, PRIORITY_BACKGROUND)
@@ -2404,7 +2436,7 @@
 #endif
     playlist->amount++;
     
-    strcpy(&playlist->buffer[playlist->buffer_end_pos], filename);
+    strcpy((char*)&playlist->buffer[playlist->buffer_end_pos], filename);
     playlist->buffer_end_pos += len;
     playlist->buffer[playlist->buffer_end_pos++] = '\0';
 
@@ -2731,6 +2763,7 @@
         }
 
         playlist->buffer_size = 0;
+        playlist->buffer_handle = -1;
         playlist->buffer = NULL;
         playlist->control_mutex = &created_playlist_mutex;
     }
@@ -2779,10 +2812,10 @@
 
     if (playlist->indices && playlist->indices != current_playlist.indices)
     {
-        memcpy(current_playlist.indices, playlist->indices,
+        memcpy((void*)current_playlist.indices, (void*)playlist->indices,
                playlist->max_playlist_size*sizeof(int));
 #ifdef HAVE_DIRCACHE
-        memcpy(current_playlist.filenames, playlist->filenames,
+        memcpy((void*)current_playlist.filenames, (void*)playlist->filenames,
                playlist->max_playlist_size*sizeof(int));
 #endif
     }
@@ -3358,6 +3391,7 @@
     char tmp_buf[MAX_PATH+1];
     int result = 0;
     bool overwrite_current = false;
+    int old_handle = -1;
     char* old_buffer = NULL;
     size_t old_buffer_size = 0;
 
@@ -3380,15 +3414,16 @@
         {
             /* not enough buffer space to store updated indices */
             /* Try to get a buffer */
-            old_buffer = playlist->buffer;
+            old_handle = playlist->buffer_handle;
+            /* can ignore volatile here, because core_get_data() is called later */
+            old_buffer = (char*)playlist->buffer;
             old_buffer_size = playlist->buffer_size;
             playlist->buffer = plugin_get_buffer((size_t*)&playlist->buffer_size);
             if (playlist->buffer_size < (int)(playlist->amount * sizeof(int)))
             {
-                playlist->buffer = old_buffer;
-                playlist->buffer_size = old_buffer_size;
                 splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
-                return -1;
+                result = -1;
+                goto reset_old_buffer;
             }
         }
 
@@ -3409,12 +3444,8 @@
     if (fd < 0)
     {
         splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
-        if (old_buffer != NULL)
-        {
-            playlist->buffer = old_buffer;
-            playlist->buffer_size = old_buffer_size;
-        }
-        return -1;
+        result = -1;
+        goto reset_old_buffer;
     }
 
     display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), false);
@@ -3514,11 +3545,12 @@
     }
 
     cpu_boost(false);
-    if (old_buffer != NULL)
-    {
-        playlist->buffer = old_buffer;
-        playlist->buffer_size = old_buffer_size;
-    }
+
+reset_old_buffer:
+    if (old_handle > 0)
+        old_buffer = core_get_data(old_handle);
+    playlist->buffer = old_buffer;
+    playlist->buffer_size = old_buffer_size;
 
     return result;
 }
@@ -3534,9 +3566,9 @@
     char buf[MAX_PATH+1];
     int result = 0;
     int num_files = 0;
-    int i;
-    struct entry *files;
+    int i;;
     struct tree_context* tc = tree_get_context();
+    struct tree_cache* cache = &tc->cache;
     int old_dirfilter = *(tc->dirfilter);
 
     if (!callback)
@@ -3552,7 +3584,6 @@
         return -1;
     }
 
-    files = tc->cache.entries;
     num_files = tc->filesindir;
 
     /* we've overwritten the dircache so tree browser will need to be
@@ -3568,6 +3599,7 @@
             break;
         }
 
+        struct entry *files = core_get_data(cache->entries_handle);
         if (files[i].attr & ATTR_DIRECTORY)
         {
             if (recurse)
@@ -3586,8 +3618,7 @@
                     result = -1;
                     break;
                 }
-                    
-                files = tc->cache.entries;
+
                 num_files = tc->filesindir;
                 if (!num_files)
                 {
diff --git a/apps/playlist.h b/apps/playlist.h
index f14b5c6..6dd5535 100644
--- a/apps/playlist.h
+++ b/apps/playlist.h
@@ -80,15 +80,16 @@
     int  control_fd;     /* descriptor of the open control file     */
     bool control_created; /* has control file been created?         */
     int  dirlen;         /* Length of the path to the playlist file */
-    unsigned long *indices; /* array of indices                     */
-    int *filenames;      /* Array of dircache indices */
+    volatile unsigned long *indices; /* array of indices            */
+    volatile int *filenames;         /* Array of dircache indices   */
     int  max_playlist_size; /* Max number of files in playlist. Mirror of
                               global_settings.max_files_in_playlist */
     bool in_ram;         /* playlist stored in ram (dirplay)        */
+    int buffer_handle;   /* handle to the below buffer (-1 if non-buflib) */
 
     union {
-        char *buffer;    /* buffer for in-ram playlists             */
-        int  *seek_buf;  /* buffer for seeks in real playlists      */
+        volatile char *buffer;/* buffer for in-ram playlists        */
+        int  *seek_buf;       /* buffer for seeks in real playlists */
     };
     int  buffer_size;    /* size of buffer                          */
     int  buffer_end_pos; /* last position where buffer was written  */
diff --git a/apps/plugin.c b/apps/plugin.c
index 32b77ad..43d9e03 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -789,6 +789,8 @@
 
     /* new stuff at the end, sort into place next time
        the API gets incompatible */
+    tree_get_entries,
+    tree_get_entry_at,
 };
 
 int plugin_load(const char* plugin, const void* parameter)
@@ -865,6 +867,9 @@
     lcd_remote_update();
 #endif
     push_current_activity(ACTIVITY_PLUGIN);
+    /* some plugins assume the entry cache doesn't move and save pointers to it
+     * they should be fixed properly instead of this lock */
+    tree_lock_cache(tree_get_context());
 
     FOR_NB_SCREENS(i)
        viewportmanager_theme_enable(i, false, NULL);
@@ -879,6 +884,7 @@
 
     rc = p_hdr->entry_point(parameter);
     
+    tree_unlock_cache(tree_get_context());
     pop_current_activity();
 
     if (!pfn_tsr_exit)
diff --git a/apps/plugin.h b/apps/plugin.h
index d70e563..1d8413f 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -926,6 +926,8 @@
 
     /* new stuff at the end, sort into place next time
        the API gets incompatible */
+    struct entry* (*tree_get_entries)(struct tree_context* t);
+    struct entry* (*tree_get_entry_at)(struct tree_context* t, int index);
 };
 
 /* plugin header */
diff --git a/apps/plugins/imageviewer/imageviewer.c b/apps/plugins/imageviewer/imageviewer.c
index 80e1ba4..044c835 100644
--- a/apps/plugins/imageviewer/imageviewer.c
+++ b/apps/plugins/imageviewer/imageviewer.c
@@ -136,7 +136,7 @@
 static void get_pic_list(void)
 {
     struct tree_context *tree = rb->tree_get_context();
-    struct entry *dircache = tree->cache.entries;
+    struct entry *dircache = rb->tree_get_entries(tree);
     int i;
     char *pname;
 
diff --git a/apps/plugins/mikmod/mikmod.c b/apps/plugins/mikmod/mikmod.c
index dff0fce..d132f80 100644
--- a/apps/plugins/mikmod/mikmod.c
+++ b/apps/plugins/mikmod/mikmod.c
@@ -185,7 +185,7 @@
 void get_mod_list(void)
 {
     struct tree_context *tree = rb->tree_get_context();
-    struct entry *dircache = tree->cache.entries;
+    struct entry *dircache = rb->tree_get_entries(tree);
     int i;
     char *pname;
 
diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c
index 156ec01..84eae42 100644
--- a/apps/plugins/mpegplayer/mpegplayer.c
+++ b/apps/plugins/mpegplayer/mpegplayer.c
@@ -1876,7 +1876,7 @@
 static bool get_videofile(int direction, char* videofile, size_t bufsize)
 {
     struct tree_context *tree = rb->tree_get_context();
-    struct entry *dircache = tree->cache.entries;
+    struct entry *dircache = rb->tree_get_entries(tree);
     int i, step, end, found = 0;
     char *videoname = rb->strrchr(videofile, '/') + 1;
     size_t rest = bufsize - (videoname - videofile) - 1;
diff --git a/apps/plugins/rockpaint.c b/apps/plugins/rockpaint.c
index add09c7..d1cc8f2 100644
--- a/apps/plugins/rockpaint.c
+++ b/apps/plugins/rockpaint.c
@@ -948,7 +948,7 @@
 
     tree = rb->tree_get_context();
     backup = *tree;
-    dc = tree->cache.entries;
+    dc = rb->tree_get_entries(tree);
     a = backup.currdir+rb->strlen(backup.currdir)-1;
     if( *a != '/' )
     {
diff --git a/apps/scrobbler.c b/apps/scrobbler.c
index a6307d5..78414f3 100644
--- a/apps/scrobbler.c
+++ b/apps/scrobbler.c
@@ -255,6 +255,11 @@
         return -1;
 
     scrobbler_cache = core_alloc("scrobbler", SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN);
+    if (scrobbler_cache <= 0)
+    {
+        logf("SCROOBLER: OOM");
+        return -1;
+    }
 
     add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, scrobbler_change_event);
     cache_pos = 0;
@@ -288,6 +293,9 @@
     {
         remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
         scrobbler_initialised = false;
+        /* get rid of the buffer */
+        core_free(scrobbler_cache);
+        scrobbler_cache = 0;
     }
 }
 
diff --git a/apps/tagcache.c b/apps/tagcache.c
index 753675f..5ab7726 100644
--- a/apps/tagcache.c
+++ b/apps/tagcache.c
@@ -222,6 +222,8 @@
 
 /* Pointer to allocated ramcache_header */
 static struct ramcache_header *ramcache_hdr;
+/* lock entity to temporarily prevent ramcache_hdr from moving */
+static int move_lock;
 #endif
 
 /** 
@@ -1035,6 +1037,8 @@
                 {
                     tfe = (struct tagfile_entry *)
                                         &ramcache_hdr->tags[clause->tag][seek];
+                    /* str points to movable data, but no locking required here,
+                     * as no yield() is following */
                     str = tfe->tag_data;
                 }
             }
@@ -1149,9 +1153,11 @@
 # endif
         )
     {
+        move_lock++; /* lock because below makes a pointer to movable data */
         for (i = tcs->seek_pos; i < current_tcmh.tch.entry_count; i++)
         {
             struct tagcache_seeklist_entry *seeklist;
+            /* idx points to movable data, don't yield or reload */
             struct index_entry *idx = &ramcache_hdr->indices[i];
             if (tcs->seek_list_count == SEEK_LIST_SIZE)
                 break ;
@@ -1175,8 +1181,7 @@
             /* Check for conditions. */
             if (!check_clauses(tcs, idx, tcs->clause, tcs->clause_count))
                 continue;
-            
-            /* Add to the seek list if not already in uniq buffer. */
+            /* Add to the seek list if not already in uniq buffer (doesn't yield)*/
             if (!add_uniqbuf(tcs, idx->tag_seek[tcs->type]))
                 continue;
             
@@ -1187,6 +1192,7 @@
             seeklist->idx_id = i;
             tcs->seek_list_count++;
         }
+        move_lock--;
         
         tcs->seek_pos = i;
         
@@ -1538,10 +1544,11 @@
             struct tagfile_entry *ep;
             
             ep = (struct tagfile_entry *)&ramcache_hdr->tags[tcs->type][tcs->position];
-            tcs->result = ep->tag_data;
-            tcs->result_len = strlen(tcs->result) + 1;
+            /* don't return ep->tag_data directly as it may move */
+            tcs->result_len = strlcpy(buf, ep->tag_data, sizeof(buf)) + 1;
+            tcs->result = buf;
             tcs->idx_id = ep->idx_id;
-            tcs->ramresult = true;
+            tcs->ramresult = false; /* was true before we copied to buf too */
             
             /* Increase position for the next run. This may get overwritten. */
             tcs->position += sizeof(struct tagfile_entry) + ep->tag_length;
@@ -1703,15 +1710,34 @@
     entry = &ramcache_hdr->indices[idx_id];
     
     memset(id3, 0, sizeof(struct mp3entry));
+    char* buf = id3->id3v2buf;
+    ssize_t remaining = sizeof(id3->id3v2buf);
+
+    /* this macro sets id3 strings by copying to the id3v2buf */
+#define SET(x, y) do                                                           \
+    {                                                                          \
+        if (remaining > 0)                                                     \
+        {                                                                      \
+            x          = NULL; /* initialize with null if tag doesn't exist */ \
+            char* src = get_tag_string(entry, y);                              \
+            if (src)                                                           \
+            {                                                                  \
+                x = buf;                                                       \
+                size_t len = strlcpy(buf, src, remaining) +1;                  \
+                buf += len; remaining -= len;                                  \
+            }                                                                  \
+        }                                                                      \
+    } while(0)
     
-    id3->title        = get_tag_string(entry, tag_title);
-    id3->artist       = get_tag_string(entry, tag_artist);
-    id3->album        = get_tag_string(entry, tag_album);
-    id3->genre_string = get_tag_string(entry, tag_genre);
-    id3->composer     = get_tag_string(entry, tag_composer);
-    id3->comment      = get_tag_string(entry, tag_comment);
-    id3->albumartist  = get_tag_string(entry, tag_albumartist);
-    id3->grouping     = get_tag_string(entry, tag_grouping);
+    
+    SET(id3->title,         tag_title);
+    SET(id3->artist,        tag_artist);
+    SET(id3->album,         tag_album);
+    SET(id3->genre_string,  tag_genre);
+    SET(id3->composer,      tag_composer);
+    SET(id3->comment,       tag_comment);
+    SET(id3->albumartist,   tag_albumartist);
+    SET(id3->grouping,      tag_grouping);
 
     id3->length     = get_tag_numeric(entry, tag_length, idx_id);
     id3->playcount  = get_tag_numeric(entry, tag_playcount, idx_id);
@@ -2903,6 +2929,9 @@
 #ifdef HAVE_DIRCACHE
     bool dircache_buffer_stolen = false;
 #endif
+#ifdef HAVE_TC_RAMCACHE
+    bool ramcache_buffer_stolen = false;
+#endif
     bool local_allocation = false;
     
     logf("committing tagcache");
@@ -2976,6 +3005,8 @@
         tempbuf = (char *)(ramcache_hdr + 1);
         tempbuf_size = tc_stat.ramcache_allocated - sizeof(struct ramcache_header) - 128;
         tempbuf_size &= ~0x03;
+        move_lock++;
+        ramcache_buffer_stolen = true;
     }
 #endif
     
@@ -3072,6 +3103,8 @@
 #endif
 
 #ifdef HAVE_TC_RAMCACHE
+    if (ramcache_buffer_stolen)
+        move_lock--;
     /* Reload tagcache. */
     if (tc_stat.ramcache_allocated > 0)
         tagcache_start_scan();
@@ -3689,9 +3722,11 @@
         {
             struct tagfile_entry *tfe;
             int32_t *seek = &ramcache_hdr->indices[idx_id].tag_seek[tag];
-            
+
             tfe = (struct tagfile_entry *)&ramcache_hdr->tags[tag][*seek];
+            move_lock++; /* protect tfe and seek if crc_32() yield()s */
             *seek = crc_32(tfe->tag_data, strlen(tfe->tag_data), 0xffffffff);
+            move_lock--;
             myidx.tag_seek[tag] = *seek;
         }
         else
@@ -3813,6 +3848,30 @@
 #endif
 
 #ifdef HAVE_TC_RAMCACHE
+
+static void fix_ramcache(void* old_addr, void* new_addr)
+{
+    ptrdiff_t offpos = new_addr - old_addr;
+    for (int i = 0; i < TAG_COUNT; i++)
+        ramcache_hdr->tags[i] += offpos;
+}
+
+static int move_cb(int handle, void* current, void* new)
+{
+    (void)handle;
+    if (move_lock > 0)
+        return BUFLIB_CB_CANNOT_MOVE;
+
+    fix_ramcache(current, new);
+    ramcache_hdr = new;
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_cb,
+    .shrink_callback = NULL,
+};
+
 static bool allocate_tagcache(void)
 {
     struct master_header tcmh;
@@ -3833,7 +3892,7 @@
      */
     tc_stat.ramcache_allocated = tcmh.tch.datasize + 256 + TAGCACHE_RESERVE +
         sizeof(struct ramcache_header) + TAG_COUNT*sizeof(void *);
-    int handle = core_alloc("tc ramcache", tc_stat.ramcache_allocated);
+    int handle = core_alloc_ex("tc ramcache", tc_stat.ramcache_allocated, &ops);
     ramcache_hdr = core_get_data(handle);
     memset(ramcache_hdr, 0, sizeof(struct ramcache_header));
     memcpy(&current_tcmh, &tcmh, sizeof current_tcmh);
@@ -3871,12 +3930,13 @@
     
     
     /* Lets allocate real memory and load it */
-    handle = core_alloc("tc ramcache", shdr.tc_stat.ramcache_allocated);
+    handle = core_alloc_ex("tc ramcache", shdr.tc_stat.ramcache_allocated, &ops);
     ramcache_hdr = core_get_data(handle);
+    moev_lock++;
     rc = read(fd, ramcache_hdr, shdr.tc_stat.ramcache_allocated);
+    move_lock--;
     close(fd);
-    
-    offpos = (long)ramcache_hdr - (long)shdr.hdr;
+
     if (rc != shdr.tc_stat.ramcache_allocated)
     {
         logf("read failure!");
@@ -3887,8 +3947,7 @@
     memcpy(&tc_stat, &shdr.tc_stat, sizeof(struct tagcache_stat));
     
     /* Now fix the pointers */
-    for (i = 0; i < TAG_COUNT; i++)
-        ramcache_hdr->tags[i] += offpos;
+    fix_ramcache(shdr.hdr, ramcache_hdr);
     
     /* Load the tagcache master header (should match the actual DB file header). */
     memcpy(&current_tcmh, &shdr.mh, sizeof current_tcmh);
@@ -3919,7 +3978,9 @@
     write(fd, &shdr, sizeof shdr);
     
     /* And dump the data too */
+    move_lock++;
     write(fd, ramcache_hdr, tc_stat.ramcache_allocated);
+    move_lock--;
     close(fd);
     
     return true;
@@ -3962,7 +4023,8 @@
 
     /* Master header copy should already match, this can be redundant to do. */
     memcpy(&current_tcmh, &tcmh, sizeof current_tcmh);
-    
+
+    move_lock++; /* lock for the reset of the scan, simpler to handle */
     idx = ramcache_hdr->indices;
 
     /* Load the master index table. */
@@ -3972,8 +4034,7 @@
         if (bytesleft < 0)
         {
             logf("too big tagcache.");
-            close(fd);
-            return false;
+            goto failure;
         }
 
         /* DEBUG: After tagcache commit and dircache rebuild, hdr-sturcture
@@ -3982,8 +4043,7 @@
         if (rc != sizeof(struct index_entry))
         {
             logf("read error #10");
-            close(fd);
-            return false;
+            goto failure;
         }
     
         idx++;
@@ -4010,7 +4070,7 @@
         p += sizeof(struct tagcache_header);
         
         if ( (fd = open_tag_fd(tch, tag, false)) < 0)
-            return false;
+            goto failure_nofd;
         
         for (ramcache_hdr->entry_count[tag] = 0;
              ramcache_hdr->entry_count[tag] < tch->entry_count;
@@ -4022,7 +4082,7 @@
             {
                 /* Abort if we got a critical event in queue */
                 if (check_event_queue())
-                    return false;
+                    goto failure;
             }
             
             fe = (struct tagfile_entry *)p;
@@ -4032,8 +4092,7 @@
             {
                 /* End of lookup table. */
                 logf("read error #11");
-                close(fd);
-                return false;
+                goto failure;
             }
 
             /* We have a special handling for the filename tags. */
@@ -4051,16 +4110,14 @@
                     buf[10] = '\0';
                     logf("TAG:%s", buf);
                     logf("too long filename");
-                    close(fd);
-                    return false;
+                    goto failure;
                 }
                 
                 rc = read(fd, buf, fe->tag_length);
                 if (rc != fe->tag_length)
                 {
                     logf("read error #12");
-                    close(fd);
-                    return false;
+                    goto failure;
                 }
                 
                 /* Check if the entry has already been removed */
@@ -4071,15 +4128,13 @@
                 if (idx->flag & FLAG_DIRCACHE)
                 {
                     logf("internal error!");
-                    close(fd);
-                    return false;
+                    goto failure;
                 }
                 
                 if (idx->tag_seek[tag] != pos)
                 {
                     logf("corrupt data structures!");
-                    close(fd);
-                    return false;
+                    goto failure;
                 }
 
 # ifdef HAVE_DIRCACHE
@@ -4126,8 +4181,7 @@
                 logf("too big tagcache #2");
                 logf("tl: %ld", fe->tag_length);
                 logf("bl: %ld", bytesleft);
-                close(fd);
-                return false;
+                goto failure;
             }
 
             p = fe->tag_data;
@@ -4141,8 +4195,7 @@
                 logf("len=0x%04lx", fe->tag_length); // 0x4000
                 logf("pos=0x%04lx", lseek(fd, 0, SEEK_CUR)); // 0x433
                 logf("tag=0x%02x", tag); // 0x00
-                close(fd);
-                return false;
+                goto failure;
             }
         }
         close(fd);
@@ -4151,7 +4204,14 @@
     tc_stat.ramcache_used = tc_stat.ramcache_allocated - bytesleft;
     logf("tagcache loaded into ram!");
 
+    move_lock--;
     return true;
+
+failure:
+    close(fd);
+failure_nofd:
+    move_lock--;
+    return false;
 }
 #endif /* HAVE_TC_RAMCACHE */
 
diff --git a/apps/tagcache.h b/apps/tagcache.h
index 393a290..6c13efd 100644
--- a/apps/tagcache.h
+++ b/apps/tagcache.h
@@ -190,7 +190,9 @@
 
     /* Exported variables. */
     bool ramsearch;      /* Is ram copy of the tagcache being used. */
-    bool ramresult;      /* False if result is not static, and must be copied. */
+    bool ramresult;      /* False if result is not static, and must be copied.
+                            Currently always false since ramresult buffer is
+                            movable */
     int type;            /* The tag type to be searched. Only nonvirtual tags */
     char *result;        /* The result data for all tags. */
     int result_len;      /* Length of the result including \0 */
diff --git a/apps/tagtree.c b/apps/tagtree.c
index 0d4330b..5766d28 100644
--- a/apps/tagtree.c
+++ b/apps/tagtree.c
@@ -53,6 +53,7 @@
 #include "storage.h"
 #include "dir.h"
 #include "playback.h"
+#include "panic.h"
 
 #define str_or_empty(x) (x ? x : "(NULL)")
 
@@ -60,6 +61,17 @@
 
 static int tagtree_play_folder(struct tree_context* c);
 
+/* this needs to be same size as struct entry (tree.h) and name needs to be
+ * the first; so that they're compatible enough to walk arrays of both
+ * derefencing the name member*/
+struct tagentry {
+    char* name;
+    int newtable;
+    int extraseek;
+};
+
+static struct tagentry* tagtree_get_entry(struct tree_context *c, int id);
+
 #define SEARCHSTR_SIZE 256
 
 enum table {
@@ -96,7 +108,7 @@
 
 /* Capacity 10 000 entries (for example 10k different artists) */
 #define UNIQBUF_SIZE (64*1024)
-static long *uniqbuf;
+static long uniqbuf[UNIQBUF_SIZE / sizeof(long)];
 
 #define MAX_TAGS 5
 #define MAX_MENU_ID_SIZE 32
@@ -163,8 +175,8 @@
 /* Statusbar text of the current view. */
 static char current_title[MAX_TAGS][128];
 
-static struct menu_root *menus[TAGMENU_MAX_MENUS];
-static struct menu_root *menu;
+static struct menu_root * menus[TAGMENU_MAX_MENUS];
+static struct menu_root * menu;
 static struct search_instruction *csi;
 static const char *strp;
 static int menu_count;
@@ -176,8 +188,74 @@
 static struct tree_context *tc;
 
 /* a few memory alloc helper */
-static int tagtree_handle;
+static int tagtree_handle, lock_count;
 static size_t tagtree_bufsize, tagtree_buf_used;
+
+#define UPDATE(x, y) { x = (typeof(x))((char*)(x) + (y)); }
+static int move_callback(int handle, void* current, void* new)
+{
+    (void)handle; (void)current; (void)new;
+    ptrdiff_t diff = new - current;
+
+    if (lock_count > 0)
+        return BUFLIB_CB_CANNOT_MOVE;
+
+    UPDATE(menu, diff);
+    /* loop over menus */
+    for(int i = 0; i < menu_count; i++)
+    {
+        struct menu_root* menu = menus[i];
+        /* then over the menu_entries of a menu */
+        for(int j = 0; j < menu->itemcount; j++)
+        {
+            struct menu_entry* mentry = menu->items[j];
+            /* then over the search_instructions of each menu_entry */
+            for(int k = 0; k < mentry->si.tagorder_count; k++)
+            {
+                for(int l = 0; l < mentry->si.clause_count[k]; l++)
+                {
+                    UPDATE(mentry->si.clause[k][l]->str, diff);
+                    UPDATE(mentry->si.clause[k][l], diff);
+                }
+            }
+            UPDATE(menu->items[j], diff);
+        }
+        UPDATE(menus[i], diff);
+    }
+
+    /* now the same game for formats */
+    for(int i = 0; i < format_count; i++)
+    {
+        for(int j = 0; j < formats[i]->clause_count; j++)
+        {
+            UPDATE(formats[i]->clause[j]->str, diff);
+            UPDATE(formats[i]->clause[j], diff);
+        }
+
+        if (formats[i]->formatstr)
+            UPDATE(formats[i]->formatstr, diff);
+
+        UPDATE(formats[i], diff);
+    }
+    return BUFLIB_CB_OK;
+}
+#undef UPDATE
+
+static inline void tagtree_lock(void)
+{
+    lock_count++;
+}
+
+static inline void tagtree_unlock(void)
+{
+    lock_count--;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_callback,
+    .shrink_callback = NULL,
+};
+
 static void* tagtree_alloc(size_t size)
 {
     char* buf = core_get_data(tagtree_handle) + tagtree_buf_used;
@@ -201,6 +279,7 @@
     return dest;
 }
 
+/* save to call without locking */
 static int get_token_str(char *buf, int size)
 {
     /* Find the start. */
@@ -510,7 +589,8 @@
     {
         int clause_count = 0;
         strp++;
-        
+
+        tagtree_lock();
         while (1)
         {
             struct tagcache_search_clause *newclause;
@@ -529,6 +609,7 @@
             
             clause_count++;
         }
+        tagtree_unlock();
         
         formats[format_count]->clause_count = clause_count;
     }
@@ -593,9 +674,14 @@
         strp++;
         new_clause->type = clause_logical_or;
     }
-    else if (!read_clause(new_clause))
-        return -1;
-    
+    else
+    {
+        tagtree_lock();
+        bool ret = read_clause(new_clause);
+        tagtree_unlock();
+        if (!ret)
+            return -1;
+    }
     inst->clause_count[inst->tagorder_count]++;
     
     return 1;
@@ -616,7 +702,6 @@
     struct search_instruction *inst = &entry->si;
     char buf[MAX_PATH];
     int i;
-    struct menu_root *new_menu;
     
     strp = str;
     
@@ -654,8 +739,7 @@
         
         /* Allocate a new menu unless link is found. */
         menus[menu_count] = tagtree_alloc0(sizeof(struct menu_root));
-        new_menu = menus[menu_count];
-        strlcpy(new_menu->id, buf, MAX_MENU_ID_SIZE);
+        strlcpy(menus[menu_count]->id, buf, MAX_MENU_ID_SIZE);
         entry->link = menu_count;
         ++menu_count;
         
@@ -679,8 +763,11 @@
             break ;
         
         logf("tag: %d", inst->tagorder[inst->tagorder_count]);
-        
+
+        tagtree_lock();
         while ( (ret = get_condition(inst)) > 0 ) ;
+        tagtree_unlock();
+
         if (ret < 0)
             return false;
 
@@ -697,7 +784,7 @@
 {
     struct tagentry *e1 = (struct tagentry *)p1;
     struct tagentry *e2 = (struct tagentry *)p2;
-    
+
     if (sort_inverse)
         return strncasecmp(e2->name, e1->name, MAX_PATH);
     
@@ -1001,11 +1088,11 @@
     if (menu->items[menu->itemcount] == NULL)
         menu->items[menu->itemcount] = tagtree_alloc0(sizeof(struct menu_entry));
 
-    if (!parse_search(menu->items[menu->itemcount], buf))
-        return 0;
-    
-    menu->itemcount++;
-    
+    tagtree_lock();
+    if (parse_search(menu->items[menu->itemcount], buf))
+        menu->itemcount++;
+    tagtree_unlock();
+
     return 0;
 }
 
@@ -1040,15 +1127,20 @@
     menu_count = 0;
     menu = NULL;
     rootmenu = -1;
-    tagtree_handle = core_alloc_maximum("tagtree", &tagtree_bufsize, NULL);
+    tagtree_handle = core_alloc_maximum("tagtree", &tagtree_bufsize, &ops);
     parse_menu(FILE_SEARCH_INSTRUCTIONS);
+
+    /* safety check since tree.c needs to cast tagentry to entry */
+    if (sizeof(struct tagentry) != sizeof(struct entry))
+        panicf("tagentry(%zu) and entry mismatch(%zu)",
+                sizeof(struct tagentry), sizeof(struct entry));
+    if (lock_count > 0)
+        panicf("tagtree locked after parsing");
     
     /* If no root menu is set, assume it's the first single menu
      * we have. That shouldn't normally happen. */
     if (rootmenu < 0)
         rootmenu = 0;
-    
-    uniqbuf = tagtree_alloc(UNIQBUF_SIZE);
 
     add_event(PLAYBACK_EVENT_TRACK_BUFFER, false, tagtree_buffer_event);
     add_event(PLAYBACK_EVENT_TRACK_FINISH, false, tagtree_track_finish_event);
@@ -1181,10 +1273,14 @@
     return 0;
 }
 
+static struct tagentry* get_entries(struct tree_context *tc)
+{
+    return core_get_data(tc->cache.entries_handle);
+}
+
 static int retrieve_entries(struct tree_context *c, int offset, bool init)
 {
     struct tagcache_search tcs;
-    struct tagentry *dptr = c->cache.entries;
     struct display_format *fmt;
     int i;
     int namebufused = 0;
@@ -1242,7 +1338,10 @@
                                        csi->result_seek[i]);
         }
     }
-   
+
+    /* because tagcache saves the clauses, we need to lock the buffer
+     * for the entire duration of the search */
+    tagtree_lock();
     for (i = 0; i <= level; i++)
     {
         int j;
@@ -1276,6 +1375,10 @@
         strip = 0;
     }
     
+    /* lock buflib out due to possible yields */
+    tree_lock_cache(c);
+    struct tagentry *dptr = core_get_data(c->cache.entries_handle);
+
     if (tag != tag_title && tag != tag_filename)
     {
         if (offset == 0)
@@ -1315,6 +1418,7 @@
         
         fmt = NULL;
         /* Check the format */
+        tagtree_lock();
         for (i = 0; i < format_count; i++)
         {
             if (formats[i]->group_id != csi->format_id[level])
@@ -1327,6 +1431,7 @@
                 break;
             }
         }
+        tagtree_unlock();
 
         if (strcmp(tcs.result, UNTAGGED) == 0)
         {
@@ -1337,7 +1442,7 @@
         
         if (!tcs.ramresult || fmt)
         {
-            dptr->name = &c->cache.name_buffer[namebufused];
+            dptr->name = core_get_data(c->cache.name_buffer_handle)+namebufused;
             
             if (fmt)
             {
@@ -1354,6 +1459,8 @@
                 {
                     logf("format_str() failed");
                     tagcache_search_finish(&tcs);
+                    tree_unlock_cache(c);
+                    tagtree_unlock();
                     return 0;
                 }
                 else
@@ -1392,6 +1499,8 @@
             if (!show_search_progress(false, total_count))
             {   /* user aborted */
                 tagcache_search_finish(&tcs);
+                tree_unlock_cache(c);
+                tagtree_unlock();
                 return current_entry_count;
             }
         }
@@ -1399,15 +1508,17 @@
     
     if (sort)
     {
-        int entry_size = sizeof(struct tagentry);
-        qsort(c->cache.entries + special_entry_count * entry_size,
+        struct tagentry *entries = get_entries(c);
+        qsort(&entries[special_entry_count],
               current_entry_count - special_entry_count,
-              entry_size, compare);
+              sizeof(struct tagentry), compare);
     }
     
     if (!init)
     {
         tagcache_search_finish(&tcs);
+        tree_unlock_cache(c);
+        tagtree_unlock();
         return current_entry_count;
     }
     
@@ -1422,7 +1533,9 @@
     }
     
     tagcache_search_finish(&tcs);
-    
+    tree_unlock_cache(c);
+    tagtree_unlock();
+
     if (!sort && (sort_inverse || sort_limit))
     {
         splashf(HZ*4, ID2P(LANG_SHOWDIR_BUFFER_FULL), total_count);
@@ -1435,7 +1548,7 @@
     
     if (strip)
     {
-        dptr = c->cache.entries;
+        dptr = get_entries(c);
         for (i = special_entry_count; i < current_entry_count; i++, dptr++)
         {
             int len = strlen(dptr->name);
@@ -1446,14 +1559,14 @@
             dptr->name = &dptr->name[strip];
         }
     }
-    
+
     return total_count;
     
 }
 
 static int load_root(struct tree_context *c)
 {
-    struct tagentry *dptr = c->cache.entries;
+    struct tagentry *dptr = core_get_data(c->cache.entries_handle);
     int i;
     
     tc = c;
@@ -1569,6 +1682,10 @@
     c->pos_history[c->dirlevel] = c->firstpos;
     c->dirlevel++;
 
+    /* lock buflib for possible I/O to protect dptr */
+    tree_lock_cache(c);
+    tagtree_lock();
+    
     switch (c->currtable) {
         case ROOT:
             c->currextra = newextra;
@@ -1634,6 +1751,8 @@
                             if (rc < 0 || !searchstring[0])
                             {
                                 tagtree_exit(c);
+                                tree_unlock_cache(c);
+                                tagtree_unlock();
                                 return 0;
                             }
                             if (csi->clause[i][j]->numeric)
@@ -1682,9 +1801,12 @@
             c->dirlevel--;
             break;
     }
+
     
     c->selected_item=0;
     gui_synclist_select_item(&tree_lists, c->selected_item);
+    tree_unlock_cache(c);
+    tagtree_unlock();
 
     return rc;
 }
@@ -1704,14 +1826,13 @@
 int tagtree_get_filename(struct tree_context* c, char *buf, int buflen)
 {
     struct tagcache_search tcs;
-    struct tagentry *entry;
+    int extraseek = tagtree_get_entry(c, c->selected_item)->extraseek;
     
-    entry = tagtree_get_entry(c, c->selected_item);
 
     if (!tagcache_search(&tcs, tag_filename))
         return -1;
 
-    if (!tagcache_retrieve(&tcs, entry->extraseek, tcs.type, buf, buflen))
+    if (!tagcache_retrieve(&tcs, extraseek, tcs.type, buf, buflen))
     {
         tagcache_search_finish(&tcs);
         return -2;
@@ -1790,9 +1911,9 @@
 
 bool tagtree_insert_selection_playlist(int position, bool queue)
 {
-    struct tagentry *dptr;
     char buf[MAX_PATH];
     int dirlevel = tc->dirlevel;
+    int newtable;
 
     show_search_progress(
 #ifdef HAVE_DISK_STORAGE
@@ -1804,10 +1925,10 @@
 
     
     /* We need to set the table to allsubentries. */
-    dptr = tagtree_get_entry(tc, tc->selected_item);
+    newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
     
     /* Insert a single track? */
-    if (dptr->newtable == PLAYTRACK)
+    if (newtable == PLAYTRACK)
     {
         if (tagtree_get_filename(tc, buf, sizeof buf) < 0)
         {
@@ -1819,29 +1940,29 @@
         return true;
     }
     
-    if (dptr->newtable == NAVIBROWSE)
+    if (newtable == NAVIBROWSE)
     {
         tagtree_enter(tc);
         tagtree_load(tc);
-        dptr = tagtree_get_entry(tc, tc->selected_item);
+        newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
     }
-    else if (dptr->newtable != ALLSUBENTRIES)
+    else if (newtable != ALLSUBENTRIES)
     {
-        logf("unsupported table: %d", dptr->newtable);
+        logf("unsupported table: %d", newtable);
         return false;
     }
     
     /* Now the current table should be allsubentries. */
-    if (dptr->newtable != PLAYTRACK)
+    if (newtable != PLAYTRACK)
     {
         tagtree_enter(tc);
         tagtree_load(tc);
-        dptr = tagtree_get_entry(tc, tc->selected_item);
+        newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
     
         /* And now the newtable should be playtrack. */
-        if (dptr->newtable != PLAYTRACK)
+        if (newtable != PLAYTRACK)
         {
-            logf("newtable: %d !!", dptr->newtable);
+            logf("newtable: %d !!", newtable);
             tc->dirlevel = dirlevel;
             return false;
         }
@@ -1886,9 +2007,9 @@
     return 0;
 }
 
-struct tagentry* tagtree_get_entry(struct tree_context *c, int id)
+static struct tagentry* tagtree_get_entry(struct tree_context *c, int id)
 {
-    struct tagentry *entry = (struct tagentry *)c->cache.entries;
+    struct tagentry *entry;
     int realid = id - current_offset;
     
     /* Load the next chunk if necessary. */
@@ -1905,10 +2026,22 @@
         realid = id - current_offset;
         cpu_boost(false);
     }
-    
+
+    entry = get_entries(c);
     return &entry[realid];
 }
 
+char* tagtree_get_entry_name(struct tree_context *c, int id,
+                                    char* buf, size_t bufsize)
+{
+    struct tagentry *entry = tagtree_get_entry(c, id);
+    if (!entry)
+        return NULL;
+    strlcpy(buf, entry->name, bufsize);
+    return buf;
+}
+
+
 char *tagtree_get_title(struct tree_context* c)
 {
     switch (c->currtable)
diff --git a/apps/tagtree.h b/apps/tagtree.h
index aaf5158..26952b4 100644
--- a/apps/tagtree.h
+++ b/apps/tagtree.h
@@ -30,19 +30,14 @@
 #define TAGMENU_MAX_MENUS  32
 #define TAGMENU_MAX_FMTS   32
 
-struct tagentry {
-    char *name;
-    int newtable;
-    int extraseek;
-};
-
 bool tagtree_export(void);
 bool tagtree_import(void);
 void tagtree_init(void) INIT_ATTR;
 int tagtree_enter(struct tree_context* c);
 void tagtree_exit(struct tree_context* c);
 int tagtree_load(struct tree_context* c);
-struct tagentry* tagtree_get_entry(struct tree_context *c, int id);
+char* tagtree_get_entry_name(struct tree_context *c, int id,
+                                    char* buf, size_t bufsize);
 bool tagtree_insert_selection_playlist(int position, bool queue);
 char *tagtree_get_title(struct tree_context* c);
 int tagtree_get_attr(struct tree_context* c);
diff --git a/apps/tdspeed.c b/apps/tdspeed.c
index 476995a..69699e5 100644
--- a/apps/tdspeed.c
+++ b/apps/tdspeed.c
@@ -38,6 +38,46 @@
 
 #define FIXED_BUFSIZE 3072 /* 48KHz factor 3.0 */
 
+static int32_t** dsp_src;
+static int handles[4];
+static int32_t *overlap_buffer[2] = { NULL, NULL };
+static int32_t *outbuf[2] = { NULL, NULL };
+
+static int move_callback(int handle, void* current, void* new)
+{
+    /* TODO */
+    (void)handle;
+    if (dsp_src)
+    {
+        int ch = (current == outbuf[0]) ? 0 : 1;
+        dsp_src[ch] = outbuf[ch] = new;
+    }
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_callback,
+    .shrink_callback = NULL,
+};
+
+static int ovl_move_callback(int handle, void* current, void* new)
+{
+    /* TODO */
+    (void)handle;
+    if (dsp_src)
+    {
+        int ch = (current == overlap_buffer[0]) ? 0 : 1;
+        overlap_buffer[ch] = new;
+    }
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ovl_ops = {
+    .move_callback = ovl_move_callback,
+    .shrink_callback = NULL,
+};
+
+
 static struct tdspeed_state_s
 {
     bool stereo;
@@ -51,39 +91,47 @@
     int32_t *ovl_buff[2];   /* overlap buffer */
 } tdspeed_state;
 
-static int32_t *overlap_buffer[2] = { NULL, NULL };
-static int32_t *outbuf[2] = { NULL, NULL };
-
 void tdspeed_init(void)
 {
-    int handle;
-
     if (!global_settings.timestretch_enabled)
         return;
 
     /* Allocate buffers */
     if (overlap_buffer[0] == NULL)
     {
-        handle = core_alloc("tdspeed ovl left", FIXED_BUFSIZE * sizeof(int32_t));
-        overlap_buffer[0] = core_get_data(handle);
+        handles[0] = core_alloc_ex("tdspeed ovl left", FIXED_BUFSIZE * sizeof(int32_t), &ovl_ops);
+        overlap_buffer[0] = core_get_data(handles[0]);
     }
     if (overlap_buffer[1] == NULL)
     {
-        handle = core_alloc("tdspeed ovl right", FIXED_BUFSIZE * sizeof(int32_t));
-        overlap_buffer[1] = core_get_data(handle);
+        handles[1] = core_alloc_ex("tdspeed ovl right", FIXED_BUFSIZE * sizeof(int32_t), &ovl_ops);
+        overlap_buffer[1] = core_get_data(handles[1]);
     }
     if (outbuf[0] == NULL)
     {
-        handle = core_alloc("tdspeed left", TDSPEED_OUTBUFSIZE * sizeof(int32_t));
-        outbuf[0] = core_get_data(handle);
+        handles[2] = core_alloc_ex("tdspeed left", TDSPEED_OUTBUFSIZE * sizeof(int32_t), &ops);
+        outbuf[0] = core_get_data(handles[2]);
     }
     if (outbuf[1] == NULL)
     {
-        handle = core_alloc("tdspeed right", TDSPEED_OUTBUFSIZE * sizeof(int32_t));
-        outbuf[1] = core_get_data(handle);
+        handles[3] = core_alloc_ex("tdspeed right", TDSPEED_OUTBUFSIZE * sizeof(int32_t), &ops);
+        outbuf[1] = core_get_data(handles[3]);
     }
 }
 
+void tdspeed_finish(void)
+{
+    for(unsigned i = 0; i < ARRAYLEN(handles); i++)
+    {
+        if (handles[i] > 0)
+        {
+            core_free(handles[i]);
+            handles[i] = 0;
+        }
+    }
+    overlap_buffer[0] = overlap_buffer[1] = NULL;
+    outbuf[0]         = outbuf[1]         = NULL;
+}
 
 bool tdspeed_config(int samplerate, bool stereo, int32_t factor)
 {
@@ -390,6 +438,7 @@
 
 int tdspeed_doit(int32_t *src[], int count)
 {
+    dsp_src = src;
     count = tdspeed_apply( (int32_t *[2]) { outbuf[0], outbuf[1] },
                            src, count, 0, TDSPEED_OUTBUFSIZE);
 
diff --git a/apps/tdspeed.h b/apps/tdspeed.h
index c3b7fc4..e91eeb1 100644
--- a/apps/tdspeed.h
+++ b/apps/tdspeed.h
@@ -36,7 +36,8 @@
 #define GET_STRETCH(pitch, speed) \
     ((speed * PITCH_SPEED_100 + pitch   / 2L) / pitch)
 
-void tdspeed_init(void) INIT_ATTR;
+void tdspeed_init(void);
+void tdspeed_finish(void);
 bool tdspeed_config(int samplerate, bool stereo, int32_t factor);
 long tdspeed_est_output_size(void);
 long tdspeed_est_input_size(long size);
diff --git a/apps/tree.c b/apps/tree.c
index 211ddb2..c7484ff 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -104,12 +104,18 @@
 static void ft_play_filename(char *dir, char *file);
 static void say_filetype(int attr);
 
-static struct entry* get_entry_at(struct tree_context *t, int index)
+struct entry* tree_get_entries(struct tree_context *t)
 {
-    struct entry* entries = t->cache.entries;
+    return core_get_data(t->cache.entries_handle);
+}
+
+struct entry* tree_get_entry_at(struct tree_context *t, int index)
+{
+    struct entry* entries = tree_get_entries(t);
     return &entries[index];
 }
 
+
 static const char* tree_get_filename(int selected_item, void *data,
                                      char *buffer, size_t buffer_len)
 {
@@ -122,12 +128,12 @@
 
     if (id3db)
     {
-        return tagtree_get_entry(&tc, selected_item)->name;
+        return tagtree_get_entry_name(&tc, selected_item, buffer, buffer_len);
     }
     else 
 #endif
     {
-        struct entry* e = get_entry_at(local_tc, selected_item);
+        struct entry* e = tree_get_entry_at(local_tc, selected_item);
         name = e->name;
         attr = e->attr;
     }
@@ -169,7 +175,7 @@
     if (*tc.dirfilter == SHOW_ID3DB)
         return -1;
     struct tree_context * local_tc=(struct tree_context *)data;
-    struct entry* e = get_entry_at(local_tc, selected_item);
+    struct entry* e = tree_get_entry_at(local_tc, selected_item);
     return filetype_get_color(e->name, e->attr);
 }
 #endif
@@ -185,7 +191,7 @@
     else
 #endif
     {
-        struct entry* e = get_entry_at(local_tc, selected_item);
+        struct entry* e = tree_get_entry_at(local_tc, selected_item);
         return filetype_get_icon(e->attr);
     }
 }
@@ -197,16 +203,17 @@
     int attr=0;
 #ifdef HAVE_TAGCACHE
     bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB;
+    char buf[AVERAGE_FILENAME_LENGTH*2];
 
     if (id3db)
     {
         attr = tagtree_get_attr(local_tc);
-        name = tagtree_get_entry(local_tc, selected_item)->name;
+        name = tagtree_get_entry_name(local_tc, selected_item, buf, sizeof(buf));
     }
     else
 #endif
     {
-        struct entry* e = get_entry_at(local_tc, selected_item);
+        struct entry* e = tree_get_entry_at(local_tc, selected_item);
         name = e->name;
         attr = e->attr;
     }
@@ -329,7 +336,7 @@
     /* use lastfile to determine the selected item (default=0) */
     for (i=0; i < tc.filesindir; i++)
     {
-        e = get_entry_at(&tc, i);
+        e = tree_get_entry_at(&tc, i);
         if (!strcasecmp(e->name, filename))
             return(i);
     }
@@ -531,7 +538,7 @@
         return NULL;
 #endif
 
-    struct entry* e = get_entry_at(&tc, tc.selected_item);
+    struct entry* e = tree_get_entry_at(&tc, tc.selected_item);
     if (getcwd(buffer, buffer_len))
     {
         if (tc.dirlength)
@@ -650,7 +657,6 @@
     
     gui_synclist_draw(&tree_lists);
     while(1) {
-        struct entry *entries = tc.cache.entries;
         bool restore = false;
         if (tc.dirlevel < 0)
             tc.dirlevel = 0; /* shouldnt be needed.. this code needs work! */
@@ -666,8 +672,9 @@
                 if ( numentries == 0 )
                     break;
 
+                short attr = tree_get_entry_at(&tc, tc.selected_item)->attr;
                 if ((tc.browse->flags & BROWSE_SELECTONLY) &&
-                    !(entries[tc.selected_item].attr & ATTR_DIRECTORY))
+                    !(attr & ATTR_DIRECTORY))
                 {
                     tc.browse->flags |= BROWSE_SELECTED;
                     get_current_file(tc.browse->buf, tc.browse->bufsize);
@@ -792,15 +799,14 @@
                     else
 #endif
                     {
-                        attr = entries[tc.selected_item].attr;
+                        struct entry *entry = tree_get_entry_at(&tc, tc.selected_item);
+                        attr = entry->attr;
 
                         if (currdir[1]) /* Not in / */
                             snprintf(buf, sizeof buf, "%s/%s",
-                                     currdir,
-                                     entries[tc.selected_item].name);
+                                     currdir, entry->name);
                         else /* In / */
-                            snprintf(buf, sizeof buf, "/%s",
-                                     entries[tc.selected_item].name);
+                            snprintf(buf, sizeof buf, "/%s", entry->name);
                     }
                     onplay_result = onplay(buf, attr, curr_context, hotkey);
                 }
@@ -999,10 +1005,36 @@
     return ret_val;
 }
 
+static int move_callback(int handle, void* current, void* new)
+{
+    struct tree_cache* cache = &tc.cache;
+    if (cache->lock_count > 0)
+        return BUFLIB_CB_CANNOT_MOVE;
+
+    size_t diff = new - current;
+    /* FIX_PTR makes sure to not accidentally update static allocations */
+#define FIX_PTR(x) \
+    { if ((void*)x > current && (void*)x < (current+cache->name_buffer_size)) x+= diff; }
+
+    if (handle == cache->name_buffer_handle)
+    {   /* update entry structs, *even if they are struct tagentry */
+        struct entry *this = core_get_data(cache->entries_handle);
+        struct entry *last = this + cache->max_entries;
+        for(; this < last; this++)
+            FIX_PTR(this->name);
+    }
+    /* nothing to do if entries moved */
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_callback,
+    .shrink_callback = NULL,
+};
+
 void tree_mem_init(void)
 {
     /* initialize tree context struct */
-    int handle;
     struct tree_cache* cache = &tc.cache;
     memset(&tc, 0, sizeof(tc));
     tc.dirfilter = &global_settings.dirfilter;
@@ -1010,12 +1042,14 @@
 
     cache->name_buffer_size = AVERAGE_FILENAME_LENGTH *
         global_settings.max_files_in_dir;
-    handle = core_alloc("tree names", cache->name_buffer_size);
-    cache->name_buffer = core_get_data(handle);
+    cache->name_buffer_handle = core_alloc_ex("tree names",
+                                    cache->name_buffer_size,
+                                    &ops);
 
     cache->max_entries = global_settings.max_files_in_dir;
-    handle = core_alloc("tree entries", cache->max_entries*(sizeof(struct entry)));
-    cache->entries = core_get_data(handle);
+    cache->entries_handle = core_alloc_ex("tree entries",
+                                    cache->max_entries*(sizeof(struct entry)),
+                                    &ops);
     tree_get_filetypes(&filetypes, &filetypes_count);
 }
 
diff --git a/apps/tree.h b/apps/tree.h
index c07b92f..2b29605 100644
--- a/apps/tree.h
+++ b/apps/tree.h
@@ -26,26 +26,30 @@
 #include <file.h>
 #include "icon.h"
 
+/* keep this struct compatible (total size and name member)
+ * with struct tagtree_entry (tagtree.h) */
 struct entry {
-    short attr; /* FAT attributes + file type flags */
-    unsigned long time_write; /* Last write time */
     char *name;
+    int attr; /* FAT attributes + file type flags */
+    unsigned time_write; /* Last write time */
 };
 
-
 #define BROWSE_SELECTONLY       0x0001  /* exit on selecting a file */
 #define BROWSE_NO_CONTEXT_MENU  0x0002  /* disable context menu */
 #define BROWSE_SELECTED         0x0100  /* this bit is set if user selected item */
 
 struct tree_context;
+
 struct tree_cache {
-    /* A big buffer with plenty of entry structs,
-     * contains all files and dirs in the current
-     * dir (with filters applied) */
-    void*   entries;
-    char*   name_buffer;
-    int     max_entries;      /* Max entries in the cache */
-    int     name_buffer_size; /* in bytes */
+    /* A big buffer with plenty of entry structs, contains all files and dirs
+     * in the current dir (with filters applied)
+     * Note that they're buflib-allocated and can therefore possibly move
+     * They need to be locked if used around yielding functions */
+    int     entries_handle;         /* handle to the entry cache */
+    int     name_buffer_handle;     /* handle to the name cache */
+    int     max_entries;            /* Max entries in the cache */
+    int     name_buffer_size;       /* in bytes */
+    volatile int lock_count;        /* non-0 if buffers may not move */
 };
 
 struct browse_context {
@@ -95,6 +99,10 @@
     struct browse_context *browse;
 };
 
+/*
+ * Call one of the two below after yields since the entrys may move inbetween */
+struct entry* tree_get_entries(struct tree_context *t);
+struct entry* tree_get_entry_at(struct tree_context *t, int index);
 void tree_drawlists(void);
 void tree_mem_init(void) INIT_ATTR;
 void tree_gui_init(void) INIT_ATTR;
@@ -108,6 +116,14 @@
 int rockbox_browse(struct browse_context *browse);
 bool create_playlist(void);
 void resume_directory(const char *dir);
+static inline void tree_lock_cache(struct tree_context *t)
+{
+    t->cache.lock_count++;
+}
+static inline void tree_unlock_cache(struct tree_context *t)
+{
+    t->cache.lock_count--;
+}
 #ifdef WIN32
 /* it takes an int on windows */
 #define getcwd_size_t int
diff --git a/firmware/buflib.c b/firmware/buflib.c
index 51cf86b..880357c 100644
--- a/firmware/buflib.c
+++ b/firmware/buflib.c
@@ -192,10 +192,6 @@
 static bool
 move_block(struct buflib_context* ctx, union buflib_data* block, int shift)
 {
-#if 1 /* moving temporarily disabled */
-    (void)ctx;(void)block;(void)shift;
-    return false;
-#else
     char* new_start;
     union buflib_data *new_block, *tmp = block[1].handle;
     struct buflib_callbacks *ops = block[2].ops;
@@ -218,7 +214,6 @@
     memmove(new_block, block, block->val * sizeof(union buflib_data));
 
     return true;
-#endif
 }
 
 /* Compact allocations and handle table, adjusting handle pointers as needed.
diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c
index 334801c..6b2260d 100644
--- a/firmware/common/dircache.c
+++ b/firmware/common/dircache.c
@@ -66,12 +66,21 @@
     int fd;
 };
 
-/* Exported structures. */
+/* Unions with char to make pointer arithmetic simpler and avoid casting */
 struct dircache_entry {
     struct dirinfo info;
-    struct dircache_entry *next;
-    struct dircache_entry *up;
-    struct dircache_entry *down;
+    union {
+        struct dircache_entry *next;
+        char* next_char;
+    };
+    union {
+        struct dircache_entry *up;
+        char* up_char;
+    };
+    union {
+        struct dircache_entry *down;
+        char* down_char;
+    };
     long startcluster;
     char *d_name;
 };
@@ -130,6 +139,44 @@
     return &dircache_root[id];
 }
 
+/* flag to make sure buffer doesn't move due to other allocs.
+ * this is set to true completely during dircache build */
+static bool dont_move = false;
+static int dircache_handle;
+static int move_callback(int handle, void* current, void* new)
+{
+    (void)handle;
+    if (dont_move)
+        return BUFLIB_CB_CANNOT_MOVE;
+
+    /* relocate the cache */
+    ptrdiff_t diff = new - current;
+    for(unsigned i = 0; i < entry_count; i++)
+    {
+        if (dircache_root[i].d_name)
+            dircache_root[i].d_name += diff;
+        if (dircache_root[i].next_char)
+            dircache_root[i].next_char += diff;
+        if (dircache_root[i].up_char)
+            dircache_root[i].up_char += diff;
+        if (dircache_root[i].down_char)
+            dircache_root[i].down_char += diff;
+    }
+    dircache_root = new;
+
+    d_names_start -= diff;
+    d_names_end -= diff;
+    dot -= diff;
+    dotdot -= diff;
+
+    return BUFLIB_CB_OK;
+}
+
+static struct buflib_callbacks ops = {
+    .move_callback = move_callback,
+    .shrink_callback = NULL,
+};
+
 #ifdef HAVE_EEPROM_SETTINGS
 /**
  * Open the dircache file to save a snapshot on disk
@@ -573,10 +620,11 @@
     }
     
     allocated_size = maindata.size + DIRCACHE_RESERVE;
-    int handle = core_alloc("dircache", allocated_size);
-    dircache_root = core_get_data(handle);
-    /* needs to be struct-size aligned so that the pointer arithmetic below works */
-    ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry));
+    dircache_handle = core_alloc_ex("dircache", allocated_size, &ops);
+    /* block movement during upcoming I/O */
+    dont_move = true;
+    dircache_root = core_get_data(dircache_handle);
+    ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*));
     entry_count = maindata.entry_count;
     appflags = maindata.appflags;
 
@@ -608,8 +656,9 @@
     dotdot = dot - sizeof("..");
 
     /* d_names are in reverse order, so the last entry points to the first string */
-    ptrdiff_t offset_d_names = maindata.d_names_start - d_names_start,
-              offset_entries = maindata.root_entry - dircache_root;
+    ptrdiff_t offset_d_names = maindata.d_names_start - d_names_start;
+    ptrdiff_t offset_entries = maindata.root_entry - dircache_root;
+    offset_entries *= sizeof(struct dircache_entry); /* make it bytes */
 
     /* offset_entries is less likely to differ, so check if it's 0 in the loop
      * offset_d_names however is almost always non-zero, since dircache_save()
@@ -625,12 +674,12 @@
 
             if (offset_entries == 0)
                 continue;
-            if (dircache_root[i].next)
-                dircache_root[i].next -= offset_entries;
-            if (dircache_root[i].up)
-                dircache_root[i].up -= offset_entries;
-            if (dircache_root[i].down)
-                dircache_root[i].down -= offset_entries;
+            if (dircache_root[i].next_char)
+                dircache_root[i].next_char -= offset_entries;
+            if (dircache_root[i].up_char)
+                dircache_root[i].up_char -= offset_entries;
+            if (dircache_root[i].down_char)
+                dircache_root[i].down_char -= offset_entries;
         }
     }
 
@@ -640,6 +689,7 @@
     logf("Done, %ld KiB used", dircache_size / 1024);
     dircache_initialized = true;
     memset(fd_bindings, 0, sizeof(fd_bindings));
+    dont_move = false;
 
     return 0;
 }
@@ -660,6 +710,7 @@
         return -1;
 
     logf("Saving directory cache");
+    dont_move = true;
     fd = open_dircache_file(O_WRONLY | O_CREAT | O_TRUNC, 0666);
 
     maindata.magic = DIRCACHE_MAGIC;
@@ -698,7 +749,7 @@
         return -4;
     }
 
-    
+    dont_move = false;
     return 0;
 }
 #endif /* HAVE_EEPROM_SETTINGS */
@@ -720,6 +771,7 @@
     /* reset dircache and alloc root entry */
     entry_count = 0;
     root_entry = allocate_entry();
+    dont_move = true;
 
 #ifdef HAVE_MULTIVOLUME
     append_position = root_entry;
@@ -740,6 +792,7 @@
                 cpu_boost(false);
                 dircache_size = 0;
                 dircache_initializing = false;
+                dont_move = false;
                 return -2;
             }
             cpu_boost(false);
@@ -765,7 +818,8 @@
         if (allocated_size - dircache_size < DIRCACHE_RESERVE)
             reserve_used = DIRCACHE_RESERVE - (allocated_size - dircache_size);
     }
-    
+
+    dont_move = false;
     return 1;
 }
 
@@ -790,7 +844,8 @@
 #endif
             case DIRCACHE_BUILD:
                 thread_enabled = true;
-                dircache_do_rebuild();
+                if (dircache_do_rebuild() < 0)
+                    core_free(dircache_handle);
                 thread_enabled = false;
                 break ;
                 
@@ -848,11 +903,10 @@
 
     if (last_size > DIRCACHE_RESERVE && last_size < DIRCACHE_LIMIT )
     {
-        int handle;
         allocated_size = last_size + DIRCACHE_RESERVE;
-        handle = core_alloc("dircache", allocated_size);
-        dircache_root = core_get_data(handle);
-        ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry));
+        dircache_handle = core_alloc_ex("dircache", allocated_size, &ops);
+        dircache_root = core_get_data(dircache_handle);
+        ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*));
         d_names_start = d_names_end = ((char*)dircache_root)+allocated_size-1;
         dircache_size = 0;
         thread_enabled = true;
@@ -869,10 +923,10 @@
      * after generation the buffer will be compacted with DIRCACHE_RESERVE
      * free bytes inbetween */
     size_t got_size;
-    int handle = core_alloc_maximum("dircache", &got_size, NULL);
-    char* buf = core_get_data(handle);
+    dircache_handle = core_alloc_maximum("dircache", &got_size, &ops);
+    char* buf = core_get_data(dircache_handle);
     dircache_root = (struct dircache_entry*)ALIGN_UP(buf,
-                                                sizeof(struct dircache_entry));
+                                                sizeof(struct dircache_entry*));
     d_names_start = d_names_end = buf + got_size - 1;
     dircache_size = 0;
     generate_dot_d_names();
@@ -909,11 +963,11 @@
     allocated_size = (d_names_end - buf);
     reserve_used = 0;
 
-    core_shrink(handle, dircache_root, allocated_size);
+    core_shrink(dircache_handle, dircache_root, allocated_size);
     return res;
 fail:
     dircache_disable();
-    core_free(handle);
+    core_free(dircache_handle);
     return res;
 }
 
@@ -928,7 +982,9 @@
         *size = 0;
         return NULL;
     }
-    
+
+    /* since we give up the buffer (without freeing), it must not move anymore */
+    dont_move = true;
     *size = dircache_size + (DIRCACHE_RESERVE-reserve_used);
     
     return dircache_root;
diff --git a/firmware/core_alloc.c b/firmware/core_alloc.c
index 75dfc75..2250f5c 100644
--- a/firmware/core_alloc.c
+++ b/firmware/core_alloc.c
@@ -6,7 +6,6 @@
 
 /* not static so it can be discovered by core_get_data() */
 struct buflib_context core_ctx;
-
 void core_allocator_init(void)
 {
     buffer_init();