/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2014 David Quintana [gigaherz] * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "CDVD.h" #include #include #include #include #include const u32 sectors_per_read = 16; static_assert(sectors_per_read > 1 && !(sectors_per_read & (sectors_per_read - 1)), "sectors_per_read must by a power of 2"); struct SectorInfo { u32 lsn; // Sectors are read in blocks, not individually u8 data[2352 * sectors_per_read]; }; u32 g_last_sector_block_lsn; static std::thread s_thread; static std::mutex s_notify_lock; static std::condition_variable s_notify_cv; static std::mutex s_request_lock; static std::queue s_request_queue; static std::mutex s_cache_lock; static std::atomic cdvd_is_open; //bits: 12 would use 1<<12 entries, or 4096*16 sectors ~ 128MB #define CACHE_SIZE 12 const u32 CacheSize = 1U << CACHE_SIZE; SectorInfo Cache[CacheSize]; u32 cdvdSectorHash(u32 lsn) { u32 t = 0; int i = 32; u32 m = CacheSize - 1; while (i >= 0) { t ^= lsn & m; lsn >>= CACHE_SIZE; i -= CACHE_SIZE; } return t & m; } void cdvdCacheUpdate(u32 lsn, u8 *data) { std::lock_guard guard(s_cache_lock); u32 entry = cdvdSectorHash(lsn); memcpy(Cache[entry].data, data, 2352 * sectors_per_read); Cache[entry].lsn = lsn; } bool cdvdCacheCheck(u32 lsn) { std::lock_guard guard(s_cache_lock); u32 entry = cdvdSectorHash(lsn); return Cache[entry].lsn == lsn; } bool cdvdCacheFetch(u32 lsn, u8 *data) { std::lock_guard guard(s_cache_lock); u32 entry = cdvdSectorHash(lsn); if (Cache[entry].lsn == lsn) { memcpy(data, Cache[entry].data, 2352 * sectors_per_read); return true; } //printf("NOT IN CACHE\n"); return false; } void cdvdCacheReset() { std::lock_guard guard(s_cache_lock); for (u32 i = 0; i < CacheSize; i++) { Cache[i].lsn = std::numeric_limits::max(); } } bool cdvdReadBlockOfSectors(u32 sector, u8 *data) { u32 count = std::min(sectors_per_read, src->GetSectorCount() - sector); const s32 media = src->GetMediaType(); // TODO: Is it really necessary to retry if it fails? I'm not sure the // second time is really going to be any better. for (int tries = 0; tries < 2; ++tries) { if (media >= 0) { if (src->ReadSectors2048(sector, count, data)) return true; } else { if (src->ReadSectors2352(sector, count, data)) return true; } } return false; } void cdvdCallNewDiscCB() { weAreInNewDiskCB = true; newDiscCB(); weAreInNewDiskCB = false; } bool cdvdUpdateDiscStatus() { bool ready = src->DiscReady(); if (!ready) { if (!disc_has_changed) { disc_has_changed = true; curDiskType = CDVD_TYPE_NODISC; curTrayStatus = CDVD_TRAY_OPEN; cdvdCallNewDiscCB(); } } else { if (disc_has_changed) { curDiskType = CDVD_TYPE_NODISC; curTrayStatus = CDVD_TRAY_CLOSE; disc_has_changed = false; cdvdRefreshData(); { std::lock_guard request_guard(s_request_lock); s_request_queue = decltype(s_request_queue)(); } cdvdCallNewDiscCB(); } } return !ready; } void cdvdThread() { u8 buffer[2352 * sectors_per_read]; u32 prefetches_left = 0; printf(" * CDVD: IO thread started...\n"); std::unique_lock guard(s_notify_lock); while (cdvd_is_open) { if (cdvdUpdateDiscStatus()) { // Need to sleep some to avoid an aggressive spin that sucks the cpu dry. s_notify_cv.wait_for(guard, std::chrono::milliseconds(10)); prefetches_left = 0; continue; } if (prefetches_left == 0) s_notify_cv.wait_for(guard, std::chrono::milliseconds(250)); // check again to make sure we're not done here... if (!cdvd_is_open) break; // Read request bool handling_request = false; u32 request_lsn; { std::lock_guard request_guard(s_request_lock); if (!s_request_queue.empty()) { request_lsn = s_request_queue.front(); s_request_queue.pop(); handling_request = true; } } if (!handling_request) { if (prefetches_left == 0) continue; --prefetches_left; u32 next_prefetch_lsn = g_last_sector_block_lsn + sectors_per_read; request_lsn = next_prefetch_lsn; } // Handle request if (!cdvdCacheCheck(request_lsn)) { if (cdvdReadBlockOfSectors(request_lsn, buffer)) { cdvdCacheUpdate(request_lsn, buffer); } else { // If the read fails, further reads are likely to fail too. prefetches_left = 0; continue; } } g_last_sector_block_lsn = request_lsn; if (!handling_request) continue; // Prefetch u32 next_prefetch_lsn = g_last_sector_block_lsn + sectors_per_read; if (next_prefetch_lsn >= src->GetSectorCount()) { prefetches_left = 0; } else { const u32 max_prefetches = 16; u32 remaining = src->GetSectorCount() - next_prefetch_lsn; prefetches_left = std::min((remaining + sectors_per_read - 1) / sectors_per_read, max_prefetches); } } printf(" * CDVD: IO thread finished.\n"); } bool cdvdStartThread() { cdvd_is_open = true; try { s_thread = std::thread(cdvdThread); } catch (std::system_error &) { cdvd_is_open = false; return false; } cdvdCacheReset(); return true; } void cdvdStopThread() { cdvd_is_open = false; s_notify_cv.notify_one(); s_thread.join(); } void cdvdRequestSector(u32 sector, s32 mode) { if (sector >= src->GetSectorCount()) return; // Align to cache block sector &= ~(sectors_per_read - 1); if (cdvdCacheCheck(sector)) return; { std::lock_guard guard(s_request_lock); s_request_queue.push(sector); } s_notify_cv.notify_one(); } u8 *cdvdGetSector(u32 sector, s32 mode) { static u8 buffer[2352 * sectors_per_read]; // Align to cache block u32 sector_block = sector & ~(sectors_per_read - 1); if (!cdvdCacheFetch(sector_block, buffer)) if (cdvdReadBlockOfSectors(sector_block, buffer)) cdvdCacheUpdate(sector_block, buffer); if (src->GetMediaType() >= 0) { u32 offset = 2048 * (sector - sector_block); return buffer + offset; } u32 offset = 2352 * (sector - sector_block); u8 *data = buffer + offset; switch (mode) { case CDVD_MODE_2048: // Data location depends on CD mode return (data[15] & 3) == 2 ? data + 24 : data + 16; case CDVD_MODE_2328: return data + 24; case CDVD_MODE_2340: return data + 12; } return data; } s32 cdvdDirectReadSector(u32 sector, s32 mode, u8 *buffer) { static u8 data[2352 * sectors_per_read]; if (sector >= src->GetSectorCount()) return -1; // Align to cache block u32 sector_block = sector & ~(sectors_per_read - 1); if (!cdvdCacheFetch(sector_block, data)) { if (cdvdReadBlockOfSectors(sector_block, data)) cdvdCacheUpdate(sector_block, data); } if (src->GetMediaType() >= 0) { u32 offset = 2048 * (sector - sector_block); memcpy(buffer, data + offset, 2048); return 0; } u32 offset = 2352 * (sector - sector_block); u8 *bfr = data + offset; switch (mode) { case CDVD_MODE_2048: // Data location depends on CD mode std::memcpy(buffer, (bfr[15] & 3) == 2 ? bfr + 24 : bfr + 16, 2048); return 0; case CDVD_MODE_2328: memcpy(buffer, bfr + 24, 2328); return 0; case CDVD_MODE_2340: memcpy(buffer, bfr + 12, 2340); return 0; default: memcpy(buffer, bfr, 2352); return 0; } } s32 cdvdGetMediaType() { return src->GetMediaType(); } s32 cdvdRefreshData() { const char *diskTypeName = "Unknown"; //read TOC from device cdvdParseTOC(); if ((etrack == 0) || (strack > etrack)) { curDiskType = CDVD_TYPE_NODISC; } else { s32 mt = cdvdGetMediaType(); if (mt < 0) curDiskType = CDVD_TYPE_DETCTCD; else if (mt == 0) curDiskType = CDVD_TYPE_DETCTDVDS; else curDiskType = CDVD_TYPE_DETCTDVDD; } curTrayStatus = CDVD_TRAY_CLOSE; switch (curDiskType) { case CDVD_TYPE_DETCTDVDD: diskTypeName = "Double-Layer DVD"; break; case CDVD_TYPE_DETCTDVDS: diskTypeName = "Single-Layer DVD"; break; case CDVD_TYPE_DETCTCD: diskTypeName = "CD-ROM"; break; case CDVD_TYPE_NODISC: diskTypeName = "No Disc"; break; } printf(" * CDVD: Disk Type: %s\n", diskTypeName); cdvdCacheReset(); return 0; }