+#include "libretro-cdrom.h"
+#include "rthreads/rthreads.h"
+#include "retro_timers.h"
+struct cached_buf {
+ unsigned char buf[2352];
+ unsigned int lba;
+};
+static struct {
+ libretro_vfs_implementation_file *h;
+ sthread_t *thread;
+ slock_t *read_lock;
+ slock_t *buf_lock;
+ scond_t *cond;
+ struct cached_buf *buf;
+ unsigned int buf_cnt, thread_exit, do_prefetch;
+ unsigned int total_lba, prefetch_lba;
+} rcdrom;
+
+static void lbacache_do(unsigned int lba)
+{
+ unsigned char m, s, f, buf[2352];
+ unsigned int i = lba % rcdrom.buf_cnt;
+ int ret;
+
+ cdrom_lba_to_msf(lba + 150, &m, &s, &f);
+ slock_lock(rcdrom.read_lock);
+ ret = cdrom_read_sector(rcdrom.h, lba, buf);
+ slock_lock(rcdrom.buf_lock);
+ slock_unlock(rcdrom.read_lock);
+ //printf("%d:%02d:%02d m%d f%d\n", m, s, f, buf[12+3], ((buf[12+4+2] >> 5) & 1) + 1);
+ if (ret) {
+ rcdrom.do_prefetch = 0;
+ slock_unlock(rcdrom.buf_lock);
+ LogErr("cdrom_read_sector failed for lba %d\n", ret, lba);
+ return;
+ }
+
+ if (lba != rcdrom.buf[i].lba) {
+ memcpy(rcdrom.buf[i].buf, buf, sizeof(rcdrom.buf[i].buf));
+ rcdrom.buf[i].lba = lba;
+ }
+ slock_unlock(rcdrom.buf_lock);
+ retro_sleep(0); // why does the main thread stall without this?
+}
+
+static int lbacache_get(unsigned int lba, void *buf)
+{
+ unsigned int i;
+ int ret = 0;
+
+ i = lba % rcdrom.buf_cnt;
+ slock_lock(rcdrom.buf_lock);
+ if (lba == rcdrom.buf[i].lba) {
+ memcpy(buf, rcdrom.buf[i].buf, 2352);
+ ret = 1;
+ }
+ slock_unlock(rcdrom.buf_lock);
+ return ret;
+}
+
+static void rcdrom_prefetch_thread(void *unused)
+{
+ unsigned int buf_cnt, lba, lba_to;
+
+ slock_lock(rcdrom.buf_lock);
+ while (!rcdrom.thread_exit)
+ {
+#ifdef __GNUC__
+ __asm__ __volatile__("":::"memory"); // barrier
+#endif
+ if (!rcdrom.do_prefetch)
+ scond_wait(rcdrom.cond, rcdrom.buf_lock);
+ if (!rcdrom.do_prefetch || !rcdrom.h || rcdrom.thread_exit)
+ continue;
+
+ buf_cnt = rcdrom.buf_cnt;
+ lba = rcdrom.prefetch_lba;
+ lba_to = lba + buf_cnt;
+ if (lba_to > rcdrom.total_lba)
+ lba_to = rcdrom.total_lba;
+ for (; lba < lba_to; lba++) {
+ if (lba != rcdrom.buf[lba % buf_cnt].lba)
+ break;
+ }
+ if (lba == lba_to) {
+ // caching complete
+ rcdrom.do_prefetch = 0;
+ continue;
+ }
+
+ slock_unlock(rcdrom.buf_lock);
+ lbacache_do(lba);
+ slock_lock(rcdrom.buf_lock);
+ }
+ slock_unlock(rcdrom.buf_lock);
+}
+
+static void rcdrom_stop_thread(void)
+{
+ rcdrom.thread_exit = 1;
+ if (rcdrom.buf_lock) {
+ slock_lock(rcdrom.buf_lock);
+ rcdrom.do_prefetch = 0;
+ if (rcdrom.cond)
+ scond_signal(rcdrom.cond);
+ slock_unlock(rcdrom.buf_lock);
+ }
+ if (rcdrom.thread) {
+ sthread_join(rcdrom.thread);
+ rcdrom.thread = NULL;
+ }
+ if (rcdrom.cond) { scond_free(rcdrom.cond); rcdrom.cond = NULL; }
+ if (rcdrom.buf_lock) { slock_free(rcdrom.buf_lock); rcdrom.buf_lock = NULL; }
+ if (rcdrom.read_lock) { slock_free(rcdrom.read_lock); rcdrom.read_lock = NULL; }
+ free(rcdrom.buf);
+ rcdrom.buf = NULL;
+}
+
+// the thread is optional, if anything fails we can do direct reads
+static void rcdrom_start_thread(void)
+{
+ rcdrom_stop_thread();
+ rcdrom.thread_exit = rcdrom.prefetch_lba = rcdrom.do_prefetch = 0;
+ if (rcdrom.buf_cnt == 0)
+ return;
+ rcdrom.buf = calloc(rcdrom.buf_cnt, sizeof(rcdrom.buf[0]));
+ rcdrom.buf_lock = slock_new();
+ rcdrom.read_lock = slock_new();
+ rcdrom.cond = scond_new();
+ if (rcdrom.buf && rcdrom.buf_lock && rcdrom.read_lock && rcdrom.cond) {
+ rcdrom.thread = sthread_create(rcdrom_prefetch_thread, NULL);
+ rcdrom.buf[0].lba = ~0;
+ }
+ if (!rcdrom.thread) {
+ LogErr("cdrom precache thread init failed.\n");
+ rcdrom_stop_thread();
+ }
+}